注册helper
nf_conntrack_ftp_init是连接跟踪ftp模块的初始化函数,可以看到其调用了nf_conntrack_helpers_register来注册helper;
1 static int __init nf_conntrack_ftp_init(void) 2 { 3 int i, ret = 0; 4 5 NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_ftp_master)); 6 7 ftp_buffer = kmalloc(65536, GFP_KERNEL); 8 if (!ftp_buffer) 9 return -ENOMEM; 10 11 if (ports_c == 0) 12 ports[ports_c++] = FTP_PORT; 13 14 /* FIXME should be configurable whether IPv4 and IPv6 FTP connections 15 are tracked or not - YK */ 16 /* 初始化helper */ 17 for (i = 0; i < ports_c; i++) { 18 nf_ct_helper_init(&ftp[2 * i], AF_INET, IPPROTO_TCP, "ftp", 19 FTP_PORT, ports[i], ports[i], &ftp_exp_policy, 20 0, help, nf_ct_ftp_from_nlattr, THIS_MODULE); 21 nf_ct_helper_init(&ftp[2 * i + 1], AF_INET6, IPPROTO_TCP, "ftp", 22 FTP_PORT, ports[i], ports[i], &ftp_exp_policy, 23 0, help, nf_ct_ftp_from_nlattr, THIS_MODULE); 24 } 25 26 /* 注册helper */ 27 ret = nf_conntrack_helpers_register(ftp, ports_c * 2); 28 if (ret < 0) { 29 pr_err("failed to register helpers "); 30 kfree(ftp_buffer); 31 return ret; 32 } 33 34 return 0; 35 }
模块通过调用nf_conntrack_helper_register函数将helper添加到对应的hash中;函数首先会对是否有相同匹配的helper进行检查,不存在才会存放到hash中;
1 int nf_conntrack_helper_register(struct nf_conntrack_helper *me) 2 { 3 struct nf_conntrack_tuple_mask mask = { .src.u.all = htons(0xFFFF) }; 4 unsigned int h = helper_hash(&me->tuple); 5 struct nf_conntrack_helper *cur; 6 int ret = 0, i; 7 8 BUG_ON(me->expect_policy == NULL); 9 BUG_ON(me->expect_class_max >= NF_CT_MAX_EXPECT_CLASSES); 10 BUG_ON(strlen(me->name) > NF_CT_HELPER_NAME_LEN - 1); 11 12 /* 允许的最大期望连接超额 */ 13 if (me->expect_policy->max_expected > NF_CT_EXPECT_MAX_CNT) 14 return -EINVAL; 15 16 mutex_lock(&nf_ct_helper_mutex); 17 /* 遍历helper hash,查找是否已存在,条件(名称相同 &&(三层协议为指定||三层协议相同)&&四层协议相同 */ 18 for (i = 0; i < nf_ct_helper_hsize; i++) { 19 hlist_for_each_entry(cur, &nf_ct_helper_hash[i], hnode) { 20 if (!strcmp(cur->name, me->name) && /* 名称相同 */ 21 (cur->tuple.src.l3num == NFPROTO_UNSPEC || /* 三层协议未指定 */ 22 cur->tuple.src.l3num == me->tuple.src.l3num) && /* 三层协议相同 */ 23 cur->tuple.dst.protonum == me->tuple.dst.protonum) { /* 四层协议相同 */ 24 ret = -EEXIST; 25 goto out; 26 } 27 } 28 } 29 30 /* avoid unpredictable behaviour for auto_assign_helper */ 31 /* 不是userspace,遍历hash,进行tuple和掩码的比较,查看是否是已存在 */ 32 if (!(me->flags & NF_CT_HELPER_F_USERSPACE)) { 33 hlist_for_each_entry(cur, &nf_ct_helper_hash[h], hnode) { 34 if (nf_ct_tuple_src_mask_cmp(&cur->tuple, &me->tuple, 35 &mask)) { 36 ret = -EEXIST; 37 goto out; 38 } 39 } 40 } 41 42 /* 将helper加入到helper hash */ 43 refcount_set(&me->refcnt, 1); 44 hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]); 45 nf_ct_helper_count++; 46 out: 47 mutex_unlock(&nf_ct_helper_mutex); 48 return ret; 49 }
关联helper到conntrack
在有新数据包进入的时候,如果没有对应连接跟踪,需要调用init_conntrack新建一个连接跟踪,其中会设置连接跟踪的helper;
1 static int 2 resolve_normal_ct(struct net *net, struct nf_conn *tmpl, 3 struct sk_buff *skb, 4 unsigned int dataoff, 5 u_int16_t l3num, 6 u_int8_t protonum, 7 struct nf_conntrack_l3proto *l3proto, 8 struct nf_conntrack_l4proto *l4proto) 9 { 10 const struct nf_conntrack_zone *zone; 11 struct nf_conntrack_tuple tuple; 12 struct nf_conntrack_tuple_hash *h; 13 enum ip_conntrack_info ctinfo; 14 struct nf_conntrack_zone tmp; 15 struct nf_conn *ct; 16 u32 hash; 17 18 /* 将源目的地址端口协议方向等字段设置到tuple */ 19 if (!nf_ct_get_tuple(skb, skb_network_offset(skb), 20 dataoff, l3num, protonum, net, &tuple, l3proto, 21 l4proto)) { 22 pr_debug("Can't get tuple "); 23 return 0; 24 } 25 26 /* look for tuple match */ 27 /* 从hash中查找tuple */ 28 zone = nf_ct_zone_tmpl(tmpl, skb, &tmp); 29 hash = hash_conntrack_raw(&tuple, net); 30 h = __nf_conntrack_find_get(net, zone, &tuple, hash); 31 32 /* 未找到该tuple */ 33 if (!h) { 34 /* 创建一个节点 */ 35 h = init_conntrack(net, tmpl, &tuple, l3proto, l4proto, 36 skb, dataoff, hash); 37 if (!h) 38 return 0; 39 if (IS_ERR(h)) 40 return PTR_ERR(h); 41 } 42 43 /* 获取到nf_conn */ 44 ct = nf_ct_tuplehash_to_ctrack(h); 45 46 /* It exists; we have (non-exclusive) reference. */ 47 /* 应答方向,已建立连接应答 */ 48 if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY) { 49 ctinfo = IP_CT_ESTABLISHED_REPLY; 50 } 51 /* 原始方向 */ 52 else { 53 /* Once we've had two way comms, always ESTABLISHED. */ 54 /* 已经见过应答了,那么是已连接状态 */ 55 if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) { 56 pr_debug("normal packet for %p ", ct); 57 ctinfo = IP_CT_ESTABLISHED; 58 } 59 /* 有期望连接标记,则设置关联字段 */ 60 else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) { 61 pr_debug("related packet for %p ", ct); 62 ctinfo = IP_CT_RELATED; 63 } 64 /* 其他情况,新连接 */ 65 else { 66 pr_debug("new packet for %p ", ct); 67 ctinfo = IP_CT_NEW; 68 } 69 } 70 71 /* skb关联nf_conn */ 72 nf_ct_set(skb, ct, ctinfo); 73 return 0; 74 }
init_conntrack函数调用__nf_ct_try_assign_helper来对helper进行赋值;
1 static noinline struct nf_conntrack_tuple_hash * 2 init_conntrack(struct net *net, struct nf_conn *tmpl, 3 const struct nf_conntrack_tuple *tuple, 4 struct nf_conntrack_l3proto *l3proto, 5 struct nf_conntrack_l4proto *l4proto, 6 struct sk_buff *skb, 7 unsigned int dataoff, u32 hash) 8 { 9 /*...*/ 10 11 /* 尝试设置helper */ 12 if (!exp) 13 __nf_ct_try_assign_helper(ct, tmpl, GFP_ATOMIC); 14 15 /*...*/ 16 17 return &ct->tuplehash[IP_CT_DIR_ORIGINAL]; 18 }
__nf_ct_try_assign_helper函数完成对helper的设置,其中在helper为空的时候调用nf_ct_lookup_helper查找helper;
1 int __nf_ct_try_assign_helper(struct nf_conn *ct, struct nf_conn *tmpl, 2 gfp_t flags) 3 { 4 struct nf_conntrack_helper *helper = NULL; 5 struct nf_conn_help *help; 6 struct net *net = nf_ct_net(ct); 7 8 /* We already got a helper explicitly attached. The function 9 * nf_conntrack_alter_reply - in case NAT is in use - asks for looking 10 * the helper up again. Since now the user is in full control of 11 * making consistent helper configurations, skip this automatic 12 * re-lookup, otherwise we'll lose the helper. 13 */ 14 if (test_bit(IPS_HELPER_BIT, &ct->status)) 15 return 0; 16 17 /* 原关联存在,记录helper为原关联的helper */ 18 if (tmpl != NULL) { 19 help = nfct_help(tmpl); 20 if (help != NULL) { 21 helper = help->helper; 22 set_bit(IPS_HELPER_BIT, &ct->status); 23 } 24 } 25 26 /* 新连接跟踪的help */ 27 help = nfct_help(ct); 28 29 /* helper为空 */ 30 if (helper == NULL) { 31 /* 根据tuple和mask查找helper */ 32 helper = nf_ct_lookup_helper(ct, net); 33 34 /* 没找到,赋值为NULL */ 35 if (helper == NULL) { 36 if (help) 37 RCU_INIT_POINTER(help->helper, NULL); 38 return 0; 39 } 40 } 41 42 /* help为空 */ 43 if (help == NULL) { 44 /* 为连接跟踪添加help扩展 */ 45 help = nf_ct_helper_ext_add(ct, helper, flags); 46 if (help == NULL) 47 return -ENOMEM; 48 } 49 /* 扩展不为空 */ 50 else { 51 /* We only allow helper re-assignment of the same sort since 52 * we cannot reallocate the helper extension area. 53 */ 54 struct nf_conntrack_helper *tmp = rcu_dereference(help->helper); 55 56 /* 已有的help和新的helper所属的help不是同一个扩展help */ 57 if (tmp && tmp->help != helper->help) { 58 RCU_INIT_POINTER(help->helper, NULL); 59 return 0; 60 } 61 } 62 63 /* 设置helper */ 64 rcu_assign_pointer(help->helper, helper); 65 66 return 0; 67 }
1 static struct nf_conntrack_helper * 2 nf_ct_lookup_helper(struct nf_conn *ct, struct net *net) 3 { 4 if (!net->ct.sysctl_auto_assign_helper) { 5 if (net->ct.auto_assign_helper_warned) 6 return NULL; 7 if (!__nf_ct_helper_find(&ct->tuplehash[IP_CT_DIR_REPLY].tuple)) 8 return NULL; 9 pr_info("nf_conntrack: default automatic helper assignment " 10 "has been turned off for security reasons and CT-based " 11 " firewall rule not found. Use the iptables CT target " 12 "to attach helpers instead. "); 13 net->ct.auto_assign_helper_warned = 1; 14 return NULL; 15 } 16 17 /* 根据tuple查找 */ 18 return __nf_ct_helper_find(&ct->tuplehash[IP_CT_DIR_REPLY].tuple); 19 }
__nf_ct_helper_find会遍历第一部分讲到的保存已注册helper的hash表,通过tuple和mask来查找对应helper;
1 static struct nf_conntrack_helper * 2 __nf_ct_helper_find(const struct nf_conntrack_tuple *tuple) 3 { 4 struct nf_conntrack_helper *helper; 5 struct nf_conntrack_tuple_mask mask = { .src.u.all = htons(0xFFFF) }; 6 unsigned int h; 7 8 if (!nf_ct_helper_count) 9 return NULL; 10 11 h = helper_hash(tuple); 12 13 /* 遍历对应hash,根据tuple和mask查找helper */ 14 hlist_for_each_entry_rcu(helper, &nf_ct_helper_hash[h], hnode) { 15 if (nf_ct_tuple_src_mask_cmp(tuple, &helper->tuple, &mask)) 16 return helper; 17 } 18 return NULL; 19 }
对比过程,在三层地址,四层端口,协议均相同的情况,认为找到helper;
1 static inline bool 2 nf_ct_tuple_src_mask_cmp(const struct nf_conntrack_tuple *t1, 3 const struct nf_conntrack_tuple *t2, 4 const struct nf_conntrack_tuple_mask *mask) 5 { 6 int count; 7 8 /* 判断三层地址是否相同 */ 9 for (count = 0; count < NF_CT_TUPLE_L3SIZE; count++) { 10 if ((t1->src.u3.all[count] ^ t2->src.u3.all[count]) & 11 mask->src.u3.all[count]) 12 return false; 13 } 14 15 /* 判断四层端口是否相同 */ 16 if ((t1->src.u.all ^ t2->src.u.all) & mask->src.u.all) 17 return false; 18 19 /* 判断协议是否相同 */ 20 if (t1->src.l3num != t2->src.l3num || 21 t1->dst.protonum != t2->dst.protonum) 22 return false; 23 24 25 /* 地址+端口+协议都相同,已存在,返回true */ 26 return true; 27 }
调用helper
在连接跟踪的ipv4_helper钩子函数中,会查找连接跟踪的对应的helper,并执行helper的help函数完成扩展功能;
1 static unsigned int ipv4_helper(void *priv, 2 struct sk_buff *skb, 3 const struct nf_hook_state *state) 4 { 5 struct nf_conn *ct; 6 enum ip_conntrack_info ctinfo; 7 const struct nf_conn_help *help; 8 const struct nf_conntrack_helper *helper; 9 10 /* This is where we call the helper: as the packet goes out. */ 11 /* 获取skb关联的nf_conn */ 12 ct = nf_ct_get(skb, &ctinfo); 13 /* 未关联,或者是 已建立连接的关联连接的响应 */ 14 if (!ct || ctinfo == IP_CT_RELATED_REPLY) 15 return NF_ACCEPT; 16 17 /* 获取help扩展 */ 18 help = nfct_help(ct); 19 20 /* 没有扩展 */ 21 if (!help) 22 return NF_ACCEPT; 23 24 /* rcu_read_lock()ed by nf_hook_thresh */ 25 /* 获得helper */ 26 helper = rcu_dereference(help->helper); 27 if (!helper) 28 return NF_ACCEPT; 29 30 /* 执行扩展的help函数 */ 31 return helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb), 32 ct, ctinfo); 33 }