static int destroy_sibling_or_exp(struct net *net, const struct nf_conntrack_tuple *t) { const struct nf_conntrack_tuple_hash *h; struct nf_conntrack_expect *exp; struct nf_conn *sibling; pr_debug("trying to timeout ct or exp for tuple "); nf_ct_dump_tuple(t); h = nf_conntrack_find_get(net, t); if (h) { sibling = nf_ct_tuplehash_to_ctrack(h); pr_debug("setting timeout of conntrack %p to 0\n", sibling); sibling->proto.gre.timeout = 0; sibling->proto.gre.stream_timeout = 0; if (del_timer(&sibling->timeout)) sibling->timeout.function((unsigned long)sibling); nf_ct_put(sibling); return 1; } else { exp = nf_ct_expect_find_get(net, t); if (exp) { pr_debug("unexpect_related of expect %p\n", exp); nf_ct_unexpect_related(exp); nf_ct_expect_put(exp); return 1; } } return 0; }
static void xt_ct_tg_destroy_v1(const struct xt_tgdtor_param *par) { struct xt_ct_target_info_v1 *info = par->targinfo; struct nf_conn *ct = info->ct; struct nf_conn_help *help; #ifdef CONFIG_NF_CONNTRACK_TIMEOUT struct nf_conn_timeout *timeout_ext; typeof(nf_ct_timeout_put_hook) timeout_put; #endif if (!nf_ct_is_untracked(ct)) { help = nfct_help(ct); if (help) module_put(help->helper->me); nf_ct_l3proto_module_put(par->family); #ifdef CONFIG_NF_CONNTRACK_TIMEOUT rcu_read_lock(); timeout_put = rcu_dereference(nf_ct_timeout_put_hook); if (timeout_put) { timeout_ext = nf_ct_timeout_find(ct); if (timeout_ext) timeout_put(timeout_ext->timeout); } rcu_read_unlock(); #endif } nf_ct_put(info->ct); }
static int destroy_sibling_or_exp(struct vrf* vrf, const struct nf_conntrack_tuple *t) { struct nf_conntrack_tuple_hash *h; struct nf_conntrack_expect *exp; struct nf_conn *sibling; DEBUGP("trying to timeout ct or exp for tuple "); NF_CT_DUMP_TUPLE(t); h = nf_conntrack_find_get(vrf, t, NULL, 0); if (h) { sibling = nf_ct_tuplehash_to_ctrack(h); DEBUGP("setting timeout of conntrack %p to 0\n", sibling); sibling->proto.gre.timeout = 0; sibling->proto.gre.stream_timeout = 0; nf_ct_put(sibling); return 1; } else { exp = nf_conntrack_expect_find_get(vrf,t); if (exp) { DEBUGP("unexpect_related of expect %p\n", exp); nf_conntrack_unexpect_related(exp); nf_conntrack_expect_put(exp); return 1; } } return 0; }
static void __net_exit synproxy_net_exit(struct net *net) { struct synproxy_net *snet = synproxy_pernet(net); nf_ct_put(snet->tmpl); synproxy_proc_exit(net); free_percpu(snet->stats); }
static int tcf_connmark(struct sk_buff *skb, const struct tc_action *a, struct tcf_result *res) { const struct nf_conntrack_tuple_hash *thash; struct nf_conntrack_tuple tuple; enum ip_conntrack_info ctinfo; struct tcf_connmark_info *ca = a->priv; struct nf_conntrack_zone zone; struct nf_conn *c; int proto; spin_lock(&ca->tcf_lock); ca->tcf_tm.lastuse = jiffies; bstats_update(&ca->tcf_bstats, skb); if (skb->protocol == htons(ETH_P_IP)) { if (skb->len < sizeof(struct iphdr)) goto out; proto = NFPROTO_IPV4; } else if (skb->protocol == htons(ETH_P_IPV6)) { if (skb->len < sizeof(struct ipv6hdr)) goto out; proto = NFPROTO_IPV6; } else { goto out; } c = nf_ct_get(skb, &ctinfo); if (c) { skb->mark = c->mark; /* using overlimits stats to count how many packets marked */ ca->tcf_qstats.overlimits++; goto out; } if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb), proto, ca->net, &tuple)) goto out; zone.id = ca->zone; zone.dir = NF_CT_DEFAULT_ZONE_DIR; thash = nf_conntrack_find_get(ca->net, &zone, &tuple); if (!thash) goto out; c = nf_ct_tuplehash_to_ctrack(thash); /* using overlimits stats to count how many packets marked */ ca->tcf_qstats.overlimits++; skb->mark = c->mark; nf_ct_put(c); out: spin_unlock(&ca->tcf_lock); return ca->tcf_action; }
/* Fast function for those who don't want to parse /proc (and I don't * blame them). * Reversing the socket's dst/src point of view gives us the reply * mapping. */ static int getorigdst(struct sock *sk, int optval, void __user *user, int *len) { const struct inet_sock *inet = inet_sk(sk); const struct nf_conntrack_tuple_hash *h; struct nf_conntrack_tuple tuple; memset(&tuple, 0, sizeof(tuple)); lock_sock(sk); tuple.src.u3.ip = inet->inet_rcv_saddr; tuple.src.u.tcp.port = inet->inet_sport; tuple.dst.u3.ip = inet->inet_daddr; tuple.dst.u.tcp.port = inet->inet_dport; tuple.src.l3num = PF_INET; tuple.dst.protonum = sk->sk_protocol; release_sock(sk); /* We only do TCP and SCTP at the moment: is there a better way? */ if (tuple.dst.protonum != IPPROTO_TCP && tuple.dst.protonum != IPPROTO_SCTP) { pr_debug("SO_ORIGINAL_DST: Not a TCP/SCTP socket\n"); return -ENOPROTOOPT; } if ((unsigned int)*len < sizeof(struct sockaddr_in)) { pr_debug("SO_ORIGINAL_DST: len %d not %zu\n", *len, sizeof(struct sockaddr_in)); return -EINVAL; } h = nf_conntrack_find_get(sock_net(sk), &nf_ct_zone_dflt, &tuple); if (h) { struct sockaddr_in sin; struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(h); sin.sin_family = AF_INET; sin.sin_port = ct->tuplehash[IP_CT_DIR_ORIGINAL] .tuple.dst.u.tcp.port; sin.sin_addr.s_addr = ct->tuplehash[IP_CT_DIR_ORIGINAL] .tuple.dst.u3.ip; memset(sin.sin_zero, 0, sizeof(sin.sin_zero)); pr_debug("SO_ORIGINAL_DST: %pI4 %u\n", &sin.sin_addr.s_addr, ntohs(sin.sin_port)); nf_ct_put(ct); if (copy_to_user(user, &sin, sizeof(sin)) != 0) return -EFAULT; else return 0; } pr_debug("SO_ORIGINAL_DST: Can't find %pI4/%u-%pI4/%u.\n", &tuple.src.u3.ip, ntohs(tuple.src.u.tcp.port), &tuple.dst.u3.ip, ntohs(tuple.dst.u.tcp.port)); return -ENOENT; }
/* flush the event cache - touches other CPU's data and must not be called * while packets are still passing through the code */ void nf_ct_event_cache_flush(void) { struct nf_conntrack_ecache *ecache; int cpu; for_each_possible_cpu(cpu) { ecache = &per_cpu(nf_conntrack_ecache, cpu); if (ecache->ct) nf_ct_put(ecache->ct); } }
/* deliver cached events and clear cache entry - must be called with locally * disabled softirqs */ static inline void __nf_ct_deliver_cached_events(struct nf_conntrack_ecache *ecache) { if (nf_ct_is_confirmed(ecache->ct) && !nf_ct_is_dying(ecache->ct) && ecache->events) atomic_notifier_call_chain(&nf_conntrack_chain, ecache->events, ecache->ct); ecache->events = 0; nf_ct_put(ecache->ct); ecache->ct = NULL; }
/* Reversing the socket's dst/src point of view gives us the reply mapping. */ static int getorigdst(struct sock *sk, int optval, void __user *user, int *len) { const struct inet_sock *inet = inet_sk(sk); const struct nf_conntrack_tuple_hash *h; struct nf_conntrack_tuple tuple; NF_CT_TUPLE_U_BLANK(&tuple); tuple.src.u3.ip = inet->rcv_saddr; tuple.src.u.tcp.port = inet->sport; tuple.dst.u3.ip = inet->daddr; tuple.dst.u.tcp.port = inet->dport; tuple.src.l3num = PF_INET; tuple.dst.protonum = IPPROTO_TCP; /* We only do TCP at the moment: is there a better way? */ if (strcmp(sk->sk_prot->name, "TCP")) { pr_debug("SO_ORIGINAL_DST: Not a TCP socket\n"); return -ENOPROTOOPT; } if ((unsigned int) *len < sizeof(struct sockaddr_in)) { pr_debug("SO_ORIGINAL_DST: len %d not %Zu\n", *len, sizeof(struct sockaddr_in)); return -EINVAL; } h = nf_conntrack_find_get(&tuple); if (h) { struct sockaddr_in sin; struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(h); sin.sin_family = AF_INET; sin.sin_port = ct->tuplehash[IP_CT_DIR_ORIGINAL] .tuple.dst.u.tcp.port; sin.sin_addr.s_addr = ct->tuplehash[IP_CT_DIR_ORIGINAL] .tuple.dst.u3.ip; memset(sin.sin_zero, 0, sizeof(sin.sin_zero)); pr_debug("SO_ORIGINAL_DST: %u.%u.%u.%u %u\n", NIPQUAD(sin.sin_addr.s_addr), ntohs(sin.sin_port)); nf_ct_put(ct); if (copy_to_user(user, &sin, sizeof(sin)) != 0) return -EFAULT; else return 0; } pr_debug("SO_ORIGINAL_DST: Can't find %u.%u.%u.%u/%u-%u.%u.%u.%u/%u.\n", NIPQUAD(tuple.src.u3.ip), ntohs(tuple.src.u.tcp.port), NIPQUAD(tuple.dst.u3.ip), ntohs(tuple.dst.u.tcp.port)); return -ENOENT; }
static void xt_ct_tg_destroy(const struct xt_tgdtor_param *par) { struct xt_ct_target_info *info = par->targinfo; struct nf_conn *ct = info->ct; struct nf_conn_help *help; if (!nf_ct_is_untracked(ct)) { help = nfct_help(ct); if (help) module_put(help->helper->me); nf_ct_l3proto_module_put(par->family); } nf_ct_put(info->ct); }
static int authd_sock_get_connmark(struct sock *sk, void __user *user, int *len) { const struct inet_sock *inet = inet_sk(sk); const struct nf_conntrack_tuple_hash *h; struct nf_conntrack_tuple tuple; struct nf_conn *ct; u_int32_t mark; memset(&tuple, 0, sizeof(tuple)); tuple.src.u3.ip = inet->inet_rcv_saddr; tuple.src.u.tcp.port = inet->inet_sport; tuple.dst.u3.ip = inet->inet_daddr; tuple.dst.u.tcp.port = inet->inet_dport; tuple.src.l3num = PF_INET; tuple.dst.protonum = IPPROTO_TCP; /* We only do TCP at the moment: is there a better way? */ if (strcmp(sk->sk_prot->name, "TCP")) { pr_debug("SO_AUTHD: Not a TCP socket\n"); return -ENOPROTOOPT; } if ((unsigned int) *len < sizeof(mark)) { pr_debug("SO_AUTHD: len %d not %Zu\n", *len, sizeof(mark)); return -EINVAL; } h = nf_conntrack_find_get(sock_net(sk), NF_CT_DEFAULT_ZONE, &tuple); if (!h) { pr_debug("SO_AUTHD: Can't find %pI4/%u-%pI4/%u.\n", &tuple.src.u3.ip, ntohs(tuple.src.u.tcp.port), &tuple.dst.u3.ip, ntohs(tuple.dst.u.tcp.port)); return -ENOENT; } ct = nf_ct_tuplehash_to_ctrack(h); mark = ct->mark; nf_ct_put(ct); if (copy_to_user(user, &mark, sizeof(mark)) != 0) return -EFAULT; *len = sizeof(mark); return 0; }
static unsigned int ipv4_conntrack_local(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { if (ip_is_fragment(ip_hdr(skb))) { /* IP_NODEFRAG setsockopt set */ enum ip_conntrack_info ctinfo; struct nf_conn *tmpl; tmpl = nf_ct_get(skb, &ctinfo); if (tmpl && nf_ct_is_template(tmpl)) { /* when skipping ct, clear templates to avoid fooling * later targets/matches */ skb->_nfct = 0; nf_ct_put(tmpl); } return NF_ACCEPT; } return nf_conntrack_in(state->net, PF_INET, state->hook, skb); }
static int count_them(struct net *net, struct xt_connlimit_data *data, const struct nf_conntrack_tuple *tuple, const union nf_inet_addr *addr, const union nf_inet_addr *mask, u_int8_t family) { const struct nf_conntrack_tuple_hash *found; struct xt_connlimit_conn *conn; struct xt_connlimit_conn *tmp; struct nf_conn *found_ct; struct list_head *hash; bool addit = true; int matches = 0; if (family == NFPROTO_IPV6) hash = &data->iphash[connlimit_iphash6(addr, mask)]; else hash = &data->iphash[connlimit_iphash(addr->ip & mask->ip)]; rcu_read_lock(); /* check the saved connections */ list_for_each_entry_safe(conn, tmp, hash, list) { found = nf_conntrack_find_get(net, NF_CT_DEFAULT_ZONE, &conn->tuple); found_ct = NULL; if (found != NULL) found_ct = nf_ct_tuplehash_to_ctrack(found); if (found_ct != NULL && nf_ct_tuple_equal(&conn->tuple, tuple) && !already_closed(found_ct)) /* * Just to be sure we have it only once in the list. * We should not see tuples twice unless someone hooks * this into a table without "-p tcp --syn". */ addit = false; if (found == NULL) { /* this one is gone */ list_del(&conn->list); kfree(conn); continue; } if (already_closed(found_ct)) { /* * we do not care about connections which are * closed already -> ditch it */ nf_ct_put(found_ct); list_del(&conn->list); kfree(conn); continue; } if (same_source_net(addr, mask, &conn->tuple.src.u3, family)) /* same source network -> be counted! */ ++matches; nf_ct_put(found_ct); }
/* * sfe_cm_sync_rule() * Synchronize a connection's state. */ static void sfe_cm_sync_rule(struct sfe_ipv4_sync *sis) { struct nf_conntrack_tuple_hash *h; struct nf_conntrack_tuple tuple; struct nf_conn *ct; struct nf_conn_counter *acct; /* * Create a tuple so as to be able to look up a connection */ memset(&tuple, 0, sizeof(tuple)); tuple.src.u3.ip = sis->src_ip; tuple.src.u.all = (__be16)sis->src_port; tuple.src.l3num = AF_INET; tuple.dst.u3.ip = sis->dest_ip; tuple.dst.dir = IP_CT_DIR_ORIGINAL; tuple.dst.protonum = (uint8_t)sis->protocol; tuple.dst.u.all = (__be16)sis->dest_port; DEBUG_TRACE("update connection - p: %d, s: %pI4:%u, d: %pI4:%u\n", (int)tuple.dst.protonum, &tuple.src.u3.ip, (unsigned int)ntohs(tuple.src.u.all), &tuple.dst.u3.ip, (unsigned int)ntohs(tuple.dst.u.all)); /* * Look up conntrack connection */ h = nf_conntrack_find_get(&init_net, NF_CT_DEFAULT_ZONE, &tuple); if (unlikely(!h)) { DEBUG_TRACE("no connection found\n"); return; } ct = nf_ct_tuplehash_to_ctrack(h); NF_CT_ASSERT(ct->timeout.data == (unsigned long)ct); /* * Only update if this is not a fixed timeout */ if (!test_bit(IPS_FIXED_TIMEOUT_BIT, &ct->status)) { ct->timeout.expires += sis->delta_jiffies; } acct = nf_conn_acct_find(ct); if (acct) { spin_lock_bh(&ct->lock); atomic64_set(&acct[IP_CT_DIR_ORIGINAL].packets, sis->src_packet_count); atomic64_set(&acct[IP_CT_DIR_ORIGINAL].bytes, sis->src_byte_count); atomic64_set(&acct[IP_CT_DIR_REPLY].packets, sis->dest_packet_count); atomic64_set(&acct[IP_CT_DIR_REPLY].bytes, sis->dest_byte_count); spin_unlock_bh(&ct->lock); } switch (sis->protocol) { case IPPROTO_TCP: spin_lock_bh(&ct->lock); if (ct->proto.tcp.seen[0].td_maxwin < sis->src_td_max_window) { ct->proto.tcp.seen[0].td_maxwin = sis->src_td_max_window; } if ((int32_t)(ct->proto.tcp.seen[0].td_end - sis->src_td_end) < 0) { ct->proto.tcp.seen[0].td_end = sis->src_td_end; } if ((int32_t)(ct->proto.tcp.seen[0].td_maxend - sis->src_td_max_end) < 0) { ct->proto.tcp.seen[0].td_maxend = sis->src_td_max_end; } if (ct->proto.tcp.seen[1].td_maxwin < sis->dest_td_max_window) { ct->proto.tcp.seen[1].td_maxwin = sis->dest_td_max_window; } if ((int32_t)(ct->proto.tcp.seen[1].td_end - sis->dest_td_end) < 0) { ct->proto.tcp.seen[1].td_end = sis->dest_td_end; } if ((int32_t)(ct->proto.tcp.seen[1].td_maxend - sis->dest_td_max_end) < 0) { ct->proto.tcp.seen[1].td_maxend = sis->dest_td_max_end; } spin_unlock_bh(&ct->lock); break; } /* * Release connection */ nf_ct_put(ct); }
static int xt_ct_tg_check_v0(const struct xt_tgchk_param *par) { struct xt_ct_target_info *info = par->targinfo; struct xt_ct_target_info_v1 info_v1 = { .flags = info->flags, .zone = info->zone, .ct_events = info->ct_events, .exp_events = info->exp_events, }; int ret; if (info->flags & ~XT_CT_NOTRACK) return -EINVAL; memcpy(info_v1.helper, info->helper, sizeof(info->helper)); ret = xt_ct_tg_check(par, &info_v1); if (ret < 0) return ret; info->ct = info_v1.ct; return ret; } static int xt_ct_tg_check_v1(const struct xt_tgchk_param *par) { struct xt_ct_target_info_v1 *info = par->targinfo; if (info->flags & ~XT_CT_NOTRACK) return -EINVAL; return xt_ct_tg_check(par, par->targinfo); } static int xt_ct_tg_check_v2(const struct xt_tgchk_param *par) { struct xt_ct_target_info_v1 *info = par->targinfo; if (info->flags & ~XT_CT_MASK) return -EINVAL; return xt_ct_tg_check(par, par->targinfo); } static void xt_ct_destroy_timeout(struct nf_conn *ct) { #ifdef CONFIG_NF_CONNTRACK_TIMEOUT struct nf_conn_timeout *timeout_ext; typeof(nf_ct_timeout_put_hook) timeout_put; rcu_read_lock(); timeout_put = rcu_dereference(nf_ct_timeout_put_hook); if (timeout_put) { timeout_ext = nf_ct_timeout_find(ct); if (timeout_ext) timeout_put(timeout_ext->timeout); } rcu_read_unlock(); #endif } static void xt_ct_tg_destroy(const struct xt_tgdtor_param *par, struct xt_ct_target_info_v1 *info) { struct nf_conn *ct = info->ct; struct nf_conn_help *help; if (ct && !nf_ct_is_untracked(ct)) { help = nfct_help(ct); if (help) module_put(help->helper->me); nf_ct_l3proto_module_put(par->family); xt_ct_destroy_timeout(ct); nf_ct_put(info->ct); } } static void xt_ct_tg_destroy_v0(const struct xt_tgdtor_param *par) { struct xt_ct_target_info *info = par->targinfo; struct xt_ct_target_info_v1 info_v1 = { .flags = info->flags, .zone = info->zone, .ct_events = info->ct_events, .exp_events = info->exp_events, .ct = info->ct, }; memcpy(info_v1.helper, info->helper, sizeof(info->helper)); xt_ct_tg_destroy(par, &info_v1); } static void xt_ct_tg_destroy_v1(const struct xt_tgdtor_param *par) { xt_ct_tg_destroy(par, par->targinfo); } static struct xt_target xt_ct_tg_reg[] __read_mostly = { { .name = "CT", .family = NFPROTO_UNSPEC, .targetsize = sizeof(struct xt_ct_target_info), .checkentry = xt_ct_tg_check_v0, .destroy = xt_ct_tg_destroy_v0, .target = xt_ct_target_v0, .table = "raw", .me = THIS_MODULE, }, { .name = "CT", .family = NFPROTO_UNSPEC, .revision = 1, .targetsize = sizeof(struct xt_ct_target_info_v1), .checkentry = xt_ct_tg_check_v1, .destroy = xt_ct_tg_destroy_v1, .target = xt_ct_target_v1, .table = "raw", .me = THIS_MODULE, }, { .name = "CT",
static int ipv6_getorigdst(struct sock *sk, int optval, void __user *user, int *len) { struct nf_conntrack_tuple tuple = { .src.l3num = NFPROTO_IPV6 }; const struct ipv6_pinfo *inet6 = inet6_sk(sk); const struct inet_sock *inet = inet_sk(sk); const struct nf_conntrack_tuple_hash *h; struct sockaddr_in6 sin6; struct nf_conn *ct; __be32 flow_label; int bound_dev_if; lock_sock(sk); tuple.src.u3.in6 = sk->sk_v6_rcv_saddr; tuple.src.u.tcp.port = inet->inet_sport; tuple.dst.u3.in6 = sk->sk_v6_daddr; tuple.dst.u.tcp.port = inet->inet_dport; tuple.dst.protonum = sk->sk_protocol; bound_dev_if = sk->sk_bound_dev_if; flow_label = inet6->flow_label; release_sock(sk); if (tuple.dst.protonum != IPPROTO_TCP && tuple.dst.protonum != IPPROTO_SCTP) return -ENOPROTOOPT; if (*len < 0 || (unsigned int)*len < sizeof(sin6)) return -EINVAL; h = nf_conntrack_find_get(sock_net(sk), &nf_ct_zone_dflt, &tuple); if (!h) { pr_debug("IP6T_SO_ORIGINAL_DST: Can't find %pI6c/%u-%pI6c/%u.\n", &tuple.src.u3.ip6, ntohs(tuple.src.u.tcp.port), &tuple.dst.u3.ip6, ntohs(tuple.dst.u.tcp.port)); return -ENOENT; } ct = nf_ct_tuplehash_to_ctrack(h); sin6.sin6_family = AF_INET6; sin6.sin6_port = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.tcp.port; sin6.sin6_flowinfo = flow_label & IPV6_FLOWINFO_MASK; memcpy(&sin6.sin6_addr, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.in6, sizeof(sin6.sin6_addr)); nf_ct_put(ct); sin6.sin6_scope_id = ipv6_iface_scope_id(&sin6.sin6_addr, bound_dev_if); return copy_to_user(user, &sin6, sizeof(sin6)) ? -EFAULT : 0; } static struct nf_sockopt_ops so_getorigdst6 = { .pf = NFPROTO_IPV6, .get_optmin = IP6T_SO_ORIGINAL_DST, .get_optmax = IP6T_SO_ORIGINAL_DST + 1, .get = ipv6_getorigdst, .owner = THIS_MODULE, }; static unsigned int ipv6_confirm(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct nf_conn *ct; enum ip_conntrack_info ctinfo; unsigned char pnum = ipv6_hdr(skb)->nexthdr; int protoff; __be16 frag_off; ct = nf_ct_get(skb, &ctinfo); if (!ct || ctinfo == IP_CT_RELATED_REPLY) goto out; protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &pnum, &frag_off); if (protoff < 0 || (frag_off & htons(~0x7)) != 0) { pr_debug("proto header not found\n"); goto out; } /* adjust seqs for loopback traffic only in outgoing direction */ if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) && !nf_is_loopback_packet(skb)) { if (!nf_ct_seq_adjust(skb, ct, ctinfo, protoff)) { NF_CT_STAT_INC_ATOMIC(nf_ct_net(ct), drop); return NF_DROP; } } out: /* We've seen it coming out the other side: confirm it */ return nf_conntrack_confirm(skb); } static unsigned int ipv6_conntrack_in(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { return nf_conntrack_in(state->net, PF_INET6, state->hook, skb); } static unsigned int ipv6_conntrack_local(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { return nf_conntrack_in(state->net, PF_INET6, state->hook, skb); } static unsigned int ipv6_helper(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct nf_conn *ct; const struct nf_conn_help *help; const struct nf_conntrack_helper *helper; enum ip_conntrack_info ctinfo; __be16 frag_off; int protoff; u8 nexthdr; /* This is where we call the helper: as the packet goes out. */ ct = nf_ct_get(skb, &ctinfo); if (!ct || ctinfo == IP_CT_RELATED_REPLY) return NF_ACCEPT; help = nfct_help(ct); if (!help) return NF_ACCEPT; /* rcu_read_lock()ed by nf_hook_thresh */ helper = rcu_dereference(help->helper); if (!helper) return NF_ACCEPT; nexthdr = ipv6_hdr(skb)->nexthdr; protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr, &frag_off); if (protoff < 0 || (frag_off & htons(~0x7)) != 0) { pr_debug("proto header not found\n"); return NF_ACCEPT; } return helper->help(skb, protoff, ct, ctinfo); } static const struct nf_hook_ops ipv6_conntrack_ops[] = { { .hook = ipv6_conntrack_in, .pf = NFPROTO_IPV6, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP6_PRI_CONNTRACK, }, { .hook = ipv6_conntrack_local, .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP6_PRI_CONNTRACK, }, { .hook = ipv6_helper,