连接跟踪初始化
基础参数的初始化:nf_conntrack_standalone_init 会调用nf_conntrack_init_start 完成连接跟踪基础参数的初始化, hash slab 扩展项 等;
nf_conntrack_l3proto_ipv4_init 函数初始化了协议和tuple操作函数的相关初始化;
static int ipv4_net_init(struct net *net) { int ret = 0; /* 注册了和 IPv4 相关的几个 4 层 TCP、UDP、ICMP等协议 3个l4proto与1个l3proto在pernet的初始化 */ ret = nf_ct_l4proto_pernet_register(net, &nf_conntrack_l4proto_tcp4); if (ret < 0) { pr_err("nf_conntrack_tcp4: pernet registration failed\n"); goto out_tcp; } ret = nf_ct_l4proto_pernet_register(net, &nf_conntrack_l4proto_udp4); if (ret < 0) { pr_err("nf_conntrack_udp4: pernet registration failed\n"); goto out_udp; } ret = nf_ct_l4proto_pernet_register(net, &nf_conntrack_l4proto_icmp); if (ret < 0) { pr_err("nf_conntrack_icmp4: pernet registration failed\n"); goto out_icmp; } ret = nf_ct_l3proto_pernet_register(net, &nf_conntrack_l3proto_ipv4); if (ret < 0) { pr_err("nf_conntrack_ipv4: pernet registration failed\n"); goto out_ipv4; } return 0; out_ipv4: nf_ct_l4proto_pernet_unregister(net, &nf_conntrack_l4proto_icmp); out_icmp: nf_ct_l4proto_pernet_unregister(net, &nf_conntrack_l4proto_udp4); out_udp: nf_ct_l4proto_pernet_unregister(net, &nf_conntrack_l4proto_tcp4); out_tcp: return ret; }
static int __init nf_conntrack_l3proto_ipv4_init(void) { int ret = 0; need_conntrack(); nf_defrag_ipv4_enable(); /* 用户态与内核态交互通信的方法sockopt,写法也简单. 缺点就是使用 copy_from_user()/copy_to_user()完成内核和用户的通信, 效率其实不高, 多用在传递控制 选项 信息,不适合做大量的数据传输 */ ret = nf_register_sockopt(&so_getorigdst); if (ret < 0) { pr_err("Unable to register netfilter socket option\n"); return ret; } /**调用ipv4_net_init 完成相关初始化*/ ret = register_pernet_subsys(&ipv4_net_ops); if (ret < 0) { pr_err("nf_conntrack_ipv4: can't register pernet ops\n"); goto cleanup_sockopt; } /*注册hook */ ret = nf_register_hooks(ipv4_conntrack_ops, ARRAY_SIZE(ipv4_conntrack_ops)); if (ret < 0) { pr_err("nf_conntrack_ipv4: can't register hooks.\n"); goto cleanup_pernet; } /* nf_conntrack_l4proto tcp相关初始化 */ ret = nf_ct_l4proto_register(&nf_conntrack_l4proto_tcp4); if (ret < 0) { pr_err("nf_conntrack_ipv4: can't register tcp4 proto.\n"); goto cleanup_hooks; } /* nf_conntrack_l4proto udp相关初始化 */ ret = nf_ct_l4proto_register(&nf_conntrack_l4proto_udp4); if (ret < 0) { pr_err("nf_conntrack_ipv4: can't register udp4 proto.\n"); goto cleanup_tcp4; } /* nf_conntrack_l4proto icmp相关初始化 */ ret = nf_ct_l4proto_register(&nf_conntrack_l4proto_icmp); if (ret < 0) { pr_err("nf_conntrack_ipv4: can't register icmpv4 proto.\n"); goto cleanup_udp4; } /* nf_conntrack_l3proto ip相关初始化 */ ret = nf_ct_l3proto_register(&nf_conntrack_l3proto_ipv4); if (ret < 0) { pr_err("nf_conntrack_ipv4: can't register ipv4 proto.\n"); goto cleanup_icmpv4; } return ret; }
nf_conntrack_l3proto_ipv4 结构中包含了基础信息 tuple 钩子回调函数;
struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv4 __read_mostly = { .l3proto = PF_INET, .name = "ipv4", .pkt_to_tuple = ipv4_pkt_to_tuple, .invert_tuple = ipv4_invert_tuple, .print_tuple = ipv4_print_tuple, .get_l4proto = ipv4_get_l4proto, #if IS_ENABLED(CONFIG_NF_CT_NETLINK) .tuple_to_nlattr = ipv4_tuple_to_nlattr, .nlattr_tuple_size = ipv4_nlattr_tuple_size, .nlattr_to_tuple = ipv4_nlattr_to_tuple, .nla_policy = ipv4_nla_policy, #endif #if defined(CONFIG_SYSCTL) && defined(CONFIG_NF_CONNTRACK_PROC_COMPAT) .ctl_table_path = "net/ipv4/netfilter", #endif .init_net = ipv4_init_net, .me = THIS_MODULE, }; static bool ipv4_pkt_to_tuple(const struct sk_buff *skb, unsigned int nhoff, struct nf_conntrack_tuple *tuple) {/* 从ip头中获取源目的地址,存入tuple */ const __be32 *ap; __be32 _addrs[2]; ap = skb_header_pointer(skb, nhoff + offsetof(struct iphdr, saddr), sizeof(u_int32_t) * 2, _addrs); if (ap == NULL) return false; tuple->src.u3.ip = ap[0]; tuple->dst.u3.ip = ap[1]; return true; } static bool ipv4_invert_tuple(struct nf_conntrack_tuple *tuple, const struct nf_conntrack_tuple *orig) { tuple->src.u3.ip = orig->dst.u3.ip; tuple->dst.u3.ip = orig->src.u3.ip; /* 根据原tuple地址设置新tuple,源目的地址均相反 */ return true; } static void ipv4_print_tuple(struct seq_file *s, const struct nf_conntrack_tuple *tuple) { seq_printf(s, "src=%pI4 dst=%pI4 ", &tuple->src.u3.ip, &tuple->dst.u3.ip); } static int ipv4_get_l4proto(const struct sk_buff *skb, unsigned int nhoff, unsigned int *dataoff, u_int8_t *protonum) { const struct iphdr *iph; struct iphdr _iph; /* 获取ip头中的协议 */ iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_iph); if (iph == NULL) return -NF_ACCEPT; /* Conntrack defragments packets, we might still see fragments * inside ICMP packets though. */ if (iph->frag_off & htons(IP_OFFSET)) return -NF_ACCEPT; *dataoff = nhoff + (iph->ihl << 2); *protonum = iph->protocol; /* Check bogus IP headers */ if (*dataoff > skb->len) { pr_debug("nf_conntrack_ipv4: bogus IPv4 packet: " "nhoff %u, ihl %u, skblen %u\n", nhoff, iph->ihl << 2, skb->len); return -NF_ACCEPT; } return NF_ACCEPT; }
contrack _in helper confirm 等钩子函数的注册
* Connection tracking may drop packets, but never alters them, so make it the first hook. */ static struct nf_hook_ops ipv4_conntrack_ops[] __read_mostly = { { .hook = ipv4_conntrack_in, .pf = NFPROTO_IPV4, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP_PRI_CONNTRACK, }, { .hook = ipv4_conntrack_local, .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP_PRI_CONNTRACK, }, { .hook = ipv4_helper, .pf = NFPROTO_IPV4, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP_PRI_CONNTRACK_HELPER, }, { .hook = ipv4_confirm, .pf = NFPROTO_IPV4, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP_PRI_CONNTRACK_CONFIRM, }, { .hook = ipv4_helper, .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_IN, .priority = NF_IP_PRI_CONNTRACK_HELPER, }, { .hook = ipv4_confirm, .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_IN, .priority = NF_IP_PRI_CONNTRACK_CONFIRM, }, };
基于ip层的协议回实现自己的nf_contrack_l4proto,现在以udp为例
das
struct nf_conntrack_l4proto nf_conntrack_l4proto_udp4 __read_mostly = { .l3proto = PF_INET, .l4proto = IPPROTO_UDP, .name = "udp", .allow_clash = true, .pkt_to_tuple = udp_pkt_to_tuple, .invert_tuple = udp_invert_tuple, .print_tuple = udp_print_tuple, .packet = udp_packet, .get_timeouts = udp_get_timeouts, .new = udp_new, .error = udp_error, #if IS_ENABLED(CONFIG_NF_CT_NETLINK) .tuple_to_nlattr = nf_ct_port_tuple_to_nlattr, .nlattr_to_tuple = nf_ct_port_nlattr_to_tuple, .nlattr_tuple_size = nf_ct_port_nlattr_tuple_size, .nla_policy = nf_ct_port_nla_policy, #endif #if IS_ENABLED(CONFIG_NF_CT_NETLINK_TIMEOUT) .ctnl_timeout = { .nlattr_to_obj = udp_timeout_nlattr_to_obj, .obj_to_nlattr = udp_timeout_obj_to_nlattr, .nlattr_max = CTA_TIMEOUT_UDP_MAX, .obj_size = sizeof(unsigned int) * CTA_TIMEOUT_UDP_MAX, .nla_policy = udp_timeout_nla_policy, }, #endif /* CONFIG_NF_CT_NETLINK_TIMEOUT */ .init_net = udp_init_net, .get_net_proto = udp_get_net_proto, };
static bool udp_pkt_to_tuple(const struct sk_buff *skb, unsigned int dataoff, struct net *net, struct nf_conntrack_tuple *tuple) { const struct udphdr *hp; struct udphdr _hdr; /*获取四册协议对应元祖信息*/ /* Actually only need first 8 bytes. */ hp = skb_header_pointer(skb, dataoff, sizeof(_hdr), &_hdr); if (hp == NULL) return false; tuple->src.u.udp.port = hp->source; tuple->dst.u.udp.port = hp->dest; return true; }
其注册L3 L4回调信息 后结构示意图如下所示:
在netfilter框架中利用nf_register_hook(struct nf_hook_ops *reg)、nf_unregister_hook(struct nf_hook_ops *reg)函数注册自己的钩子项;
上图来自:http://bbs.chinaunix.net/thread-4082396-1-1.html