Linux内核网络源码走读之Netfilter( 四 )


本文主要聚焦内核源码,关于用户空间的iptables命令,后面另起文章学习
NATNAT(Network Address Translation)网络地址转换,主要用于IP地址转换或端口转换 。NAT最常见的用途之一是,让局域网中一组使用私有IP地址的主机能够通过网关的公网IP访问Internet 。
NAT初始化与上节介绍的过滤表一样,NAT表也是一个xt_table对象 。
static const struct xt_table nf_nat_ipv4_table = {.name= "nat",.valid_hooks= (1 << NF_INET_PRE_ROUTING) |(1 << NF_INET_POST_ROUTING) |(1 << NF_INET_LOCAL_OUT) |(1 << NF_INET_LOCAL_IN),.me= THIS_MODULE,.af= NFPROTO_IPV4,.table_init = iptable_nat_table_init,};nat表的netfilter钩子函数:
static const struct nf_hook_ops nf_nat_ipv4_ops[] = {/* Before packet filtering, change destination */{.hook= iptable_nat_ipv4_in,.pf= NFPROTO_IPV4,.nat_hook= true,.hooknum= NF_INET_PRE_ROUTING,.priority= NF_IP_PRI_NAT_DST,},/* After packet filtering, change source */{.hook= iptable_nat_ipv4_out,.pf= NFPROTO_IPV4,.nat_hook= true,.hooknum= NF_INET_POST_ROUTING,.priority= NF_IP_PRI_NAT_SRC,},/* Before packet filtering, change destination */{.hook= iptable_nat_ipv4_local_fn,.pf= NFPROTO_IPV4,.nat_hook= true,.hooknum= NF_INET_LOCAL_OUT,.priority= NF_IP_PRI_NAT_DST,},/* After packet filtering, change source */{.hook= iptable_nat_ipv4_fn,.pf= NFPROTO_IPV4,.nat_hook= true,.hooknum= NF_INET_LOCAL_IN,.priority= NF_IP_PRI_NAT_SRC,},};nat表的初始化:
static int __init iptable_nat_init(void)iptable_nat_table_init(&init_net)struct ipt_replace *repl;repl = ipt_alloc_initial_table(&nf_nat_ipv4_table);//调用ipt_register_table注册nat表ret = ipt_register_table(net, &nf_nat_ipv4_table, repl,nf_nat_ipv4_ops, &net->ipv4.nat_table);NAT钩子回调函数NAT的核心实现位于
net/netfilter/nf_nat_core.c 。NAT实现的基本元素为结构nf_nat_l4proto和nf_nat_l3proto 。(在3.7之前的内核中,使用的是结构nf_nat_protocol) 。这两个结构都包含函数指针manip_pkt(),它会修改数据报头 。下面看下这两个结构 。
static const struct nf_nat_l3proto nf_nat_l3proto_ipv4 = {.l3proto= NFPROTO_IPV4,.in_range= nf_nat_ipv4_in_range,.secure_port= nf_nat_ipv4_secure_port,.manip_pkt= nf_nat_ipv4_manip_pkt, //修改ip包.csum_update= nf_nat_ipv4_csum_update,.csum_recalc= nf_nat_ipv4_csum_recalc,#if IS_ENABLED(CONFIG_NF_CT_NETLINK).nlattr_to_range= nf_nat_ipv4_nlattr_to_range,#endif#ifdef CONFIG_XFRM.decode_session= nf_nat_ipv4_decode_session,#endif};//专门看下这个修改ip包的函数nf_nat_ipv4_manip_pktstatic bool nf_nat_ipv4_manip_pkt(struct sk_buff *skb,unsigned int iphdroff,const struct nf_nat_l4proto *l4proto,const struct nf_conntrack_tuple *target,enum nf_nat_manip_type maniptype)...if (maniptype == NF_NAT_MANIP_SRC) {csum_replace4(&iph->check, iph->saddr, target->src.u3.ip);iph->saddr = target->src.u3.ip; //修改源IP} else {csum_replace4(&iph->check, iph->daddr, target->dst.u3.ip);iph->daddr = target->dst.u3.ip; //修改目标IP}//TCPconst struct nf_nat_l4proto nf_nat_l4proto_tcp = {.l4proto= IPPROTO_TCP,.manip_pkt= tcp_manip_pkt, //修改IP包.in_range= nf_nat_l4proto_in_range,.unique_tuple= tcp_unique_tuple,#if IS_ENABLED(CONFIG_NF_CT_NETLINK).nlattr_to_range= nf_nat_l4proto_nlattr_to_range,#endif};//看下tcp_manip_pkt, udp的类似static bool tcp_manip_pkt(struct sk_buff *skb,const struct nf_nat_l3proto *l3proto,unsigned int iphdroff, unsigned int hdroff,const struct nf_conntrack_tuple *tuple,enum nf_nat_manip_type maniptype)...if (maniptype == NF_NAT_MANIP_SRC) {/* Get rid of src port */newport = tuple->src.u.tcp.port;portptr = &hdr->source;} else {/* Get rid of dst port */newport = tuple->dst.u.tcp.port;portptr = &hdr->dest;}oldport = *portptr;*portptr = newport; //修改端口号继续看下NAT模块注册的netfilter钩子函数 。IPv4 NAT模块在4个挂载点注册了钩子函数,这4个函数最终都调用到nf_nat_ipv4_fn() 。
unsigned int nf_nat_ipv4_fn(void *priv, struct sk_buff *skb, const struct nf_hook_state *state,unsigned int (*do_chain)(void *priv,struct sk_buff *skb,const struct nf_hook_state *state,struct nf_conn *ct))struct nf_conn *ct;enum ip_conntrack_info ctinfo;ct = nf_ct_get(skb, &ctinfo);if (!ct)return NF_ACCEPT; //没有连接跟踪就直接返回switch (ctinfo)case IP_CT_NEW:if (!nf_nat_initialized(ct, maniptype))//do_chain最终调用ipt_do_table,在nat标准查找指定条目,找到则调用target的回调函数do_chain(priv, skb, state, ct);//执行报文修改操作nf_nat_packet(ct, ctinfo, state->hook, skb);//这里的l3proto对应前面讲的nf_nat_l3proto_ipv4l3proto = __nf_nat_l3proto_find(target.src.l3num);//如果是TCP的话,l4proto是nf_nat_l4proto_tcpl4proto = __nf_nat_l4proto_find(target.src.l3num,target.dst.protonum)l3proto->manip_pkt(skb, 0, l4proto, &target, mtype) //调用manip_pkt函数


推荐阅读