前言 不知道继续看nftables是不是一个错误的决定,因为好像这个子系统最近的更新都只是用户态的工具更新了,并且在今年下半年没有见到有新的相关CVE。但是在之前对此子系统进行分析的时候分析的节奏是非常的片面的,对于其中很多模块的实现目的都不太清楚,在经历了这几个月特别是和我的同事交流学习以及我自己又回过头去挖了几个路由器的洞之后感觉自己对审计代码又有了更新的认识。所以,我最终还是觉得将netfilter从头到尾分析一边,即便是没有找到漏洞也是一次不错的学习经历。如果在这一遍分析之后我仍未发现可疑的代码可能就会选择去分析ebpf或是其他闭源的iot设备了。
netfilter初始化分析
本文的内容基本围绕上图展开,应该会涵盖上图的所有部分以及上图中不存在的部分。
HOOK初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int __init netfilter_init (void ) { int ret; ret = register_pernet_subsys(&netfilter_net_ops); if (ret < 0 ) goto err; #ifdef CONFIG_LWTUNNEL ret = netfilter_lwtunnel_init(); if (ret < 0 ) goto err_lwtunnel_pernet; #endif ret = netfilter_log_init(); if (ret < 0 ) goto err_log_pernet; return 0 ; err_log_pernet: #ifdef CONFIG_LWTUNNEL netfilter_lwtunnel_fini(); err_lwtunnel_pernet: #endif unregister_pernet_subsys(&netfilter_net_ops); err: return ret; }
首先关于netfilter的初始化十分简单,其中最重要的部分就是注册了子系统。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static void __net_init__netfilter_net_init(struct nf_hook_entries __rcu **e, int max) { int h; for (h = 0 ; h < max; h++) RCU_INIT_POINTER(e[h], NULL ); } static int __net_init netfilter_net_init (struct net *net) { __netfilter_net_init(net->nf.hooks_ipv4, ARRAY_SIZE(net->nf.hooks_ipv4)); __netfilter_net_init(net->nf.hooks_ipv6, ARRAY_SIZE(net->nf.hooks_ipv6)); #ifdef CONFIG_NETFILTER_FAMILY_ARP __netfilter_net_init(net->nf.hooks_arp, ARRAY_SIZE(net->nf.hooks_arp)); #endif #ifdef CONFIG_NETFILTER_FAMILY_BRIDGE __netfilter_net_init(net->nf.hooks_bridge, ARRAY_SIZE(net->nf.hooks_bridge)); #endif #ifdef CONFIG_PROC_FS net->nf.proc_netfilter = proc_net_mkdir(net, "netfilter" , net->proc_net); if (!net->nf.proc_netfilter) { if (!net_eq(net, &init_net)) pr_err("cannot create netfilter proc entry" ); return -ENOMEM; } #endif return 0 ; }
而在系统的初始化函数中就是简单的初始化了各个hook,即将数组赋值为null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 struct netns_nf {#if defined CONFIG_PROC_FS struct proc_dir_entry *proc_netfilter ; #endif const struct nf_logger __rcu *nf_loggers [NFPROTO_NUMPROTO ]; #ifdef CONFIG_SYSCTL struct ctl_table_header *nf_log_dir_header ; #ifdef CONFIG_LWTUNNEL struct ctl_table_header *nf_lwtnl_dir_header ; #endif #endif struct nf_hook_entries __rcu *hooks_ipv4 [NF_INET_NUMHOOKS ]; struct nf_hook_entries __rcu *hooks_ipv6 [NF_INET_NUMHOOKS ]; #ifdef CONFIG_NETFILTER_FAMILY_ARP struct nf_hook_entries __rcu *hooks_arp [NF_ARP_NUMHOOKS ]; #endif #ifdef CONFIG_NETFILTER_FAMILY_BRIDGE struct nf_hook_entries __rcu *hooks_bridge [NF_INET_NUMHOOKS ]; #endif #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV4) unsigned int defrag_ipv4_users; #endif #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6) unsigned int defrag_ipv6_users; #endif }; #endif
上述就是在net结构体中用于表示netfilter的结构体,可以看到的是默认有的是ipv4、ipv4以及loggers这三个成员,在开启相应选项就会开启相应的成员如arp的hook。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct nf_hook_entries { u16 num_hook_entries; struct nf_hook_entry hooks []; };
这里的注释说明的大概是(我英文不好,通过看源码分析出来的)后面会在申请改结构体时为orig_ops[]和head留空间。
上述结构体中的num_hook_entries的含义也就是在该hook点有多少entry。
1 2 3 4 5 6 7 8 9 10 11 struct nf_hook_ops { nf_hookfn *hook; struct net_device *dev ; void *priv; u8 pf; enum nf_hook_ops_type hook_ops_type :8 ; unsigned int hooknum; int priority; };
在这一函数就中目前需要注意的是pf成员指代的是该hook的协议,还有hooknum成员指代的是所位于的hook点,在后续的注册hook的过程就是通过这两个成员来确定位置的。
1 2 3 4 5 6 7 8 9 enum nf_inet_hooks { NF_INET_PRE_ROUTING, NF_INET_LOCAL_IN, NF_INET_FORWARD, NF_INET_LOCAL_OUT, NF_INET_POST_ROUTING, NF_INET_NUMHOOKS, NF_INET_INGRESS = NF_INET_NUMHOOKS, };
上面的hooknum须为以上中的一个,这么来看就可以很清晰的知道了nf_hook_ops结构体中比较重要的成员的含义了。
HOOK注册过程 在nftable中,没有初始化相应hook,它是通过用户指定base chain到某一个hook点中。
但是在nftable中有一个初始化ops的过程(在此处仅初始化以下部分hook)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int __init nft_chain_filter_init (void ) { int err; err = nft_chain_filter_netdev_init(); if (err < 0 ) return err; nft_chain_filter_ipv4_init(); nft_chain_filter_ipv6_init(); nft_chain_filter_arp_init(); nft_chain_filter_inet_init(); nft_chain_filter_bridge_init(); return 0 ; }
在上述函数会对几种协议进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 static unsigned int nft_do_chain_ipv4 (void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct nft_pktinfo pkt ; nft_set_pktinfo(&pkt, skb, state); nft_set_pktinfo_ipv4(&pkt); return nft_do_chain(&pkt, priv); } static const struct nft_chain_type nft_chain_filter_ipv4 = { .name = "filter" , .type = NFT_CHAIN_T_DEFAULT, .family = NFPROTO_IPV4, .hook_mask = (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_LOCAL_OUT) | (1 << NF_INET_FORWARD) | (1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_POST_ROUTING), .hooks = { [NF_INET_LOCAL_IN] = nft_do_chain_ipv4, [NF_INET_LOCAL_OUT] = nft_do_chain_ipv4, [NF_INET_FORWARD] = nft_do_chain_ipv4, [NF_INET_PRE_ROUTING] = nft_do_chain_ipv4, [NF_INET_POST_ROUTING] = nft_do_chain_ipv4, }, }; static void nft_chain_filter_ipv4_init (void ) { nft_register_chain_type(&nft_chain_filter_ipv4); }
这里以ipv4位例,上面会执行nft_register_chain_type函数将前面的结构体进行注册。
1 2 3 4 5 6 7 8 9 10 11 void nft_register_chain_type (const struct nft_chain_type *ctype) { nfnl_lock(NFNL_SUBSYS_NFTABLES); if (WARN_ON(__nft_chain_type_get(ctype->family, ctype->type))) { nfnl_unlock(NFNL_SUBSYS_NFTABLES); return ; } chain_type[ctype->family][ctype->type] = ctype; nfnl_unlock(NFNL_SUBSYS_NFTABLES); } EXPORT_SYMBOL_GPL(nft_register_chain_type);
该函数的实现如上,主要是通过结构体的协议族以及类型进行定位,将其放入到全局变量chain_type中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 static int nf_tables_addchain (struct nft_ctx *ctx, u8 family, u8 genmask, u8 policy, u32 flags, struct netlink_ext_ack *extack) { const struct nlattr * const *nla = ctx->nla; struct nft_table *table = ctx->table; struct nft_base_chain *basechain ; struct net *net = ctx->net; char name[NFT_NAME_MAXLEN]; struct nft_rule_blob *blob ; struct nft_trans *trans ; struct nft_chain *chain ; int err; if (nla[NFTA_CHAIN_HOOK]) { struct nft_stats __percpu *stats = NULL ; struct nft_chain_hook hook = {}; if (table->flags & __NFT_TABLE_F_UPDATE) return -EINVAL; if (flags & NFT_CHAIN_BINDING) return -EOPNOTSUPP; err = nft_chain_parse_hook(net, NULL , nla, &hook, family, flags, extack); if (err < 0 ) return err; basechain = kzalloc(sizeof (*basechain), GFP_KERNEL_ACCOUNT); if (basechain == NULL ) { nft_chain_release_hook(&hook); return -ENOMEM; } chain = &basechain->chain; if (nla[NFTA_CHAIN_COUNTERS]) { stats = nft_stats_alloc(nla[NFTA_CHAIN_COUNTERS]); if (IS_ERR(stats)) { nft_chain_release_hook(&hook); kfree(basechain); return PTR_ERR(stats); } rcu_assign_pointer(basechain->stats, stats); } err = nft_basechain_init(basechain, family, &hook, flags); if (err < 0 ) { nft_chain_release_hook(&hook); kfree(basechain); free_percpu(stats); return err; } if (stats) static_branch_inc(&nft_counters_enabled); } else { } err = nf_tables_register_hook(net, table, chain); if (err < 0 ) goto err_register_hook; return 0 ; err_register_hook: nft_chain_del(chain); err_chain_add: nft_trans_destroy(trans); err_trans: nft_use_dec_restore(&table->use); err_destroy_chain: nf_tables_chain_destroy(ctx); return err; }
当用户指定了NFTA_CHAIN_HOOK就会进入到base chain的分支中,这里首先会通过nft_chain_parse_hook函数进行解析hook,其内容主要是获取到hook的协议、hook点位、优先级以及type(即前面的结构体)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static int nft_basechain_init (struct nft_base_chain *basechain, u8 family, struct nft_chain_hook *hook, u32 flags) { struct nft_chain *chain ; struct nft_hook *h ; basechain->type = hook->type; INIT_LIST_HEAD(&basechain->hook_list); chain = &basechain->chain; if (nft_base_chain_netdev(family, hook->num)) { list_splice_init(&hook->list , &basechain->hook_list); list_for_each_entry(h, &basechain->hook_list, list ) nft_basechain_hook_init(&h->ops, family, hook, chain); } nft_basechain_hook_init(&basechain->ops, family, hook, chain); chain->flags |= NFT_CHAIN_BASE | flags; basechain->policy = NF_ACCEPT; if (chain->flags & NFT_CHAIN_HW_OFFLOAD && !nft_chain_offload_support(basechain)) { list_splice_init(&basechain->hook_list, &hook->list ); return -EOPNOTSUPP; } flow_block_init(&basechain->flow_block); return 0 ; }
随后通过nft_basechain_init函数初始化base chain。
1 2 3 4 5 6 7 8 9 10 11 static void nft_basechain_hook_init (struct nf_hook_ops *ops, u8 family, const struct nft_chain_hook *hook, struct nft_chain *chain) { ops->pf = family; ops->hooknum = hook->num; ops->priority = hook->priority; ops->priv = chain; ops->hook = hook->type->hooks[ops->hooknum]; ops->hook_ops_type = NF_HOOK_OP_NF_TABLES; }
在其中主要通过nft_basechain_hook_init函数对base chain的ops进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static int nf_tables_register_hook (struct net *net, const struct nft_table *table, struct nft_chain *chain) { struct nft_base_chain *basechain ; const struct nf_hook_ops *ops ; if (table->flags & NFT_TABLE_F_DORMANT || !nft_is_base_chain(chain)) return 0 ; basechain = nft_base_chain(chain); ops = &basechain->ops; if (basechain->type->ops_register) return basechain->type->ops_register(net, ops); if (nft_base_chain_netdev(table->family, basechain->ops.hooknum)) return nft_netdev_register_hooks(net, &basechain->hook_list); return nf_register_net_hook(net, &basechain->ops); }
在完成对base chain的初始化之后,会在添加chain的最后一步进入到nf_tables_register_hook函数中,将base chain注册到hook。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 int nf_register_net_hook (struct net *net, const struct nf_hook_ops *reg) { int err; if (reg->pf == NFPROTO_INET) { if (reg->hooknum == NF_INET_INGRESS) { err = __nf_register_net_hook(net, NFPROTO_INET, reg); if (err < 0 ) return err; } else { err = __nf_register_net_hook(net, NFPROTO_IPV4, reg); if (err < 0 ) return err; err = __nf_register_net_hook(net, NFPROTO_IPV6, reg); if (err < 0 ) { __nf_unregister_net_hook(net, NFPROTO_IPV4, reg); return err; } } } else { err = __nf_register_net_hook(net, reg->pf, reg); if (err < 0 ) return err; } return 0 ; } EXPORT_SYMBOL(nf_register_net_hook);
在上述函数会根据协议进入不同分支然后执行__nf_register_net_hook。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 static int __nf_register_net_hook(struct net *net, int pf, const struct nf_hook_ops *reg) { struct nf_hook_entries *p , *new_hooks ; struct nf_hook_entries __rcu **pp ; int err; switch (pf) { case NFPROTO_NETDEV: #ifndef CONFIG_NETFILTER_INGRESS if (reg->hooknum == NF_NETDEV_INGRESS) return -EOPNOTSUPP; #endif #ifndef CONFIG_NETFILTER_EGRESS if (reg->hooknum == NF_NETDEV_EGRESS) return -EOPNOTSUPP; #endif if ((reg->hooknum != NF_NETDEV_INGRESS && reg->hooknum != NF_NETDEV_EGRESS) || !reg->dev || dev_net(reg->dev) != net) return -EINVAL; break ; case NFPROTO_INET: if (reg->hooknum != NF_INET_INGRESS) break ; err = nf_ingress_check(net, reg, NF_INET_INGRESS); if (err < 0 ) return err; break ; } pp = nf_hook_entry_head(net, pf, reg->hooknum, reg->dev); if (!pp) return -EINVAL; mutex_lock(&nf_hook_mutex); p = nf_entry_dereference(*pp); new_hooks = nf_hook_entries_grow(p, reg); if (!IS_ERR(new_hooks)) { hooks_validate(new_hooks); rcu_assign_pointer(*pp, new_hooks); } mutex_unlock(&nf_hook_mutex); if (IS_ERR(new_hooks)) return PTR_ERR(new_hooks); #ifdef CONFIG_NETFILTER_INGRESS if (nf_ingress_hook(reg, pf)) net_inc_ingress_queue(); #endif #ifdef CONFIG_NETFILTER_EGRESS if (nf_egress_hook(reg, pf)) net_inc_egress_queue(); #endif nf_static_key_inc(reg, pf); BUG_ON(p == new_hooks); nf_hook_entries_free(p); return 0 ; }
前面的switch主要做的事就是验证协议和ops是否合法。随后通过nf_hook_entry_head函数找到nf_hook_entries的头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 static struct nf_hook_entries *nf_hook_entries_grow (const struct nf_hook_entries *old, const struct nf_hook_ops *reg) { unsigned int i, alloc_entries, nhooks, old_entries; struct nf_hook_ops **orig_ops = NULL ; struct nf_hook_ops **new_ops ; struct nf_hook_entries *new ; bool inserted = false ; alloc_entries = 1 ; old_entries = old ? old->num_hook_entries : 0 ; if (old) { orig_ops = nf_hook_entries_get_hook_ops(old); for (i = 0 ; i < old_entries; i++) { if (orig_ops[i] != &dummy_ops) alloc_entries++; if (reg->priority == orig_ops[i]->priority && reg->hook_ops_type == NF_HOOK_OP_BPF) return ERR_PTR(-EBUSY); } } if (alloc_entries > MAX_HOOK_COUNT) return ERR_PTR(-E2BIG); new = allocate_hook_entries_size(alloc_entries); if (!new ) return ERR_PTR(-ENOMEM); new_ops = nf_hook_entries_get_hook_ops(new ); i = 0 ; nhooks = 0 ; while (i < old_entries) { if (orig_ops[i] == &dummy_ops) { ++i; continue ; } if (inserted || reg->priority > orig_ops[i]->priority) { new_ops[nhooks] = (void *)orig_ops[i]; new ->hooks[nhooks] = old->hooks[i]; i++; } else { new_ops[nhooks] = (void *)reg; new ->hooks[nhooks].hook = reg->hook; new ->hooks[nhooks].priv = reg->priv; inserted = true ; } nhooks++; } if (!inserted) { new_ops[nhooks] = (void *)reg; new ->hooks[nhooks].hook = reg->hook; new ->hooks[nhooks].priv = reg->priv; } return new ; }
随后进入到nf_hook_entries_grow函数获得新的nf_hook_entries。到这一步就可以理解到前面我所提到的nf_hook_entries结构体的申请,后半部分就是根据priority进行排序,具体逻辑很简单就不多赘述。
后续则是将其添加到hook中。
HOOK触发过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int ip_rcv (struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { struct net *net = dev_net(dev); skb = ip_rcv_core(skb, net); if (skb == NULL ) return NET_RX_DROP; return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, net, NULL , skb, dev, NULL , ip_rcv_finish); }
上述的ip_rcv函数是ip接受的入口点,其中的ip_rcv_core函数主要的操作是对数据包的检验以及裁剪等,在完成该步骤后进入到NF_HOOK中。
1 2 3 4 5 6 7 8 9 10 static inline int NF_HOOK (uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb, struct net_device *in, struct net_device *out, int (*okfn)(struct net *, struct sock *, struct sk_buff *)) { int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn); if (ret == 1 ) ret = okfn(net, sk, skb); return ret; }
在该函数中会先调用nf_hook而不是直接调用okfn回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 static inline int nf_hook (u_int8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb, struct net_device *indev, struct net_device *outdev, int (*okfn)(struct net *, struct sock *, struct sk_buff *)) { struct nf_hook_entries *hook_head = NULL ; int ret = 1 ; #ifdef CONFIG_JUMP_LABEL if (__builtin_constant_p(pf) && __builtin_constant_p(hook) && !static_key_false(&nf_hooks_needed[pf][hook])) return 1 ; #endif rcu_read_lock(); switch (pf) { case NFPROTO_IPV4: hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]); break ; case NFPROTO_IPV6: hook_head = rcu_dereference(net->nf.hooks_ipv6[hook]); break ; case NFPROTO_ARP: #ifdef CONFIG_NETFILTER_FAMILY_ARP if (WARN_ON_ONCE(hook >= ARRAY_SIZE(net->nf.hooks_arp))) break ; hook_head = rcu_dereference(net->nf.hooks_arp[hook]); #endif break ; case NFPROTO_BRIDGE: #ifdef CONFIG_NETFILTER_FAMILY_BRIDGE hook_head = rcu_dereference(net->nf.hooks_bridge[hook]); #endif break ; default : WARN_ON_ONCE(1 ); break ; } if (hook_head) { struct nf_hook_state state ; nf_hook_state_init(&state, hook, pf, indev, outdev, sk, net, okfn); ret = nf_hook_slow(skb, &state, hook_head, 0 ); } rcu_read_unlock(); return ret; }
函数内部首先根据协议找到hook的头部,随后进入下面对state进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static inline void nf_hook_state_init (struct nf_hook_state *p, unsigned int hook, u_int8_t pf, struct net_device *indev, struct net_device *outdev, struct sock *sk, struct net *net, int (*okfn)(struct net *, struct sock *, struct sk_buff *)) { p->hook = hook; p->pf = pf; p->in = indev; p->out = outdev; p->sk = sk; p->net = net; p->okfn = okfn; }
在完成对state的初始化之后调用nf_hook_slow函数进行hook点位的调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 int nf_hook_slow (struct sk_buff *skb, struct nf_hook_state *state, const struct nf_hook_entries *e, unsigned int s) { unsigned int verdict; int ret; for (; s < e->num_hook_entries; s++) { verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state); switch (verdict & NF_VERDICT_MASK) { case NF_ACCEPT: break ; case NF_DROP: kfree_skb_reason(skb, SKB_DROP_REASON_NETFILTER_DROP); ret = NF_DROP_GETERR(verdict); if (ret == 0 ) ret = -EPERM; return ret; case NF_QUEUE: ret = nf_queue(skb, state, s, verdict); if (ret == 1 ) continue ; return ret; default : return 0 ; } } return 1 ; } EXPORT_SYMBOL(nf_hook_slow);
上面注释对该函数的返回值做了说明。该函数在进入循环就会调用nf_hook_entry_hookfn函数来调用hook。
1 2 3 4 5 6 static inline int nf_hook_entry_hookfn (const struct nf_hook_entry *entry, struct sk_buff *skb, struct nf_hook_state *state) { return entry->hook(entry->priv, skb, state); }
结合前面的来看,这里hook的其实就是basechain->ops->hook->type->hooks[ops->hooknum];即nft_do_chain_ipv4函数。
1 2 3 4 5 6 7 8 9 10 11 static unsigned int nft_do_chain_ipv4 (void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct nft_pktinfo pkt ; nft_set_pktinfo(&pkt, skb, state); nft_set_pktinfo_ipv4(&pkt); return nft_do_chain(&pkt, priv); }
而该函数中前面主要是对pkt的初始化,最后进入到我们熟悉的nft_do_chain函数。
conntrack实现 问题 在nf_ct_set函数中如何处理skb->_nfct成员的。
buckets在内核中的结构,以及如何工作(看起来像是一个容纳nf_conn的哈希表)