Linux协议栈-netfilter(3)-NAT

来源:互联网 发布:网络编辑是什么 编辑:程序博客网 时间:2024/05/22 01:33

本文对netfilter中NAT部分的源码进行分析,读者需要先对NAT的基本概念有一个大致了解。

1. NAT模块的初始化

NAT模块的初始化过程主要是初始化一些全局变量以及注册NAT相关的hook函数。在下面nf_nat_init()函数和nf_nat_standalone_init()函数的流程图中用红色标记了要初始化的全局数据结构。

nf_nat_init()函数:


nf_nat_standalone_init()函数:


NAT表是一个xt_table,定义如下:

static struct xt_table nat_table = {.name= "nat",.valid_hooks= NAT_VALID_HOOKS,.me= THIS_MODULE,.af= AF_INET,};

iptables的表如filter, nat,mangle表都是通过ipt_register_table()注册的,在netfilter中被使用。我们需要知道iptables的表中的每条规则都包括三部分:

entry:规则的入口,同时做一些匹配数据包的工作。

match:匹配数据包的条件大多放在这里。

target:对于符合条件的数据包要执行的动作放在这里。

NAT表中的每条规则就包括上面三个部分。

注册NAT表时传入的第三个参数nat_initial_table定义如下:

static struct{struct ipt_replace repl;struct ipt_standard entries[3];struct ipt_error term;} nat_initial_table __net_initdata = {.repl = {.name = "nat",.valid_hooks = NAT_VALID_HOOKS,.num_entries = 4,.size = sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),.hook_entry = {[NF_INET_PRE_ROUTING] = 0,[NF_INET_POST_ROUTING] = sizeof(struct ipt_standard),[NF_INET_LOCAL_OUT] = sizeof(struct ipt_standard) * 2},.underflow = {[NF_INET_PRE_ROUTING] = 0,[NF_INET_POST_ROUTING] = sizeof(struct ipt_standard),[NF_INET_LOCAL_OUT] = sizeof(struct ipt_standard) * 2},},.entries = {IPT_STANDARD_INIT(NF_ACCEPT),/* PRE_ROUTING */IPT_STANDARD_INIT(NF_ACCEPT),/* POST_ROUTING */IPT_STANDARD_INIT(NF_ACCEPT),/* LOCAL_OUT */},.term = IPT_ERROR_INIT,/* ERROR */};

xt_register_target()函数为iptables 规则注册target,这里注册了snat和dnat两个target,他们的定义如下:

static struct xt_target ipt_snat_reg __read_mostly = {.name= "SNAT",.target= ipt_snat_target,.targetsize= sizeof(struct nf_nat_multi_range_compat),.table= "nat",.hooks= 1 << NF_INET_POST_ROUTING,.checkentry= ipt_snat_checkentry,.family= AF_INET,};static struct xt_target ipt_dnat_reg __read_mostly = {.name= "DNAT",.target= ipt_dnat_target,.targetsize= sizeof(struct nf_nat_multi_range_compat),.table= "nat",.hooks= (1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_LOCAL_OUT),.checkentry= ipt_dnat_checkentry,.family= AF_INET,};

介绍完NAT模块的初始化,接下来看看NAT处理数据包的过程。

2. NAT处理流程

NAT模块通过挂在netfilter上的hook函数起作用,其任务就是将设置好的NAT表中的iptables规则作用于conntrack连接,使其做NAT转换,并生成新的conntrack连接。而后续相同的数据包直接根据新的conntrack连接进行NAT转换而不需要再匹配NAT表。NAT表与netfilter其他表的区别就是一个连接上的数据包只需要查找一次NAT表。

NAT有四个hook点,这四个hook点的函数都是调用nf_nat_fn(),其中PRE_ROUTING和LOCAL_OUT做DNAT,POST_ROUTING和LOCAL_IN做SNAT。但LOCAL IN和LOCAL OUT上的hook点一般不做工作,因此我们只关注PRE ROUTING和POST ROUTING的hook点。

下文中有的地方将conntrack简写为ct。

nf_nat_fn()函数:

static unsigned intnf_nat_fn(unsigned int hooknum,  struct sk_buff *skb,  const struct net_device *in,  const struct net_device *out,  int (*okfn)(struct sk_buff *)){struct nf_conn *ct;enum ip_conntrack_info ctinfo;struct nf_conn_nat *nat;/* maniptype == SRC for postrouting. *//* 判断应该做SNAT还是DNAT */enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum); /* 分片包就报warning,因为在这之前已经过了defrag的hook了。 */NF_CT_ASSERT(!(ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)));/*获得skb的nf_conn结构,因为conntrack的hook在NAT之前,所以skb中应该有nf_conn了,并从skb->nfctinfo中获得当前连接跟踪的状态。 */ct = nf_ct_get(skb, &ctinfo); if (!ct) /* 找不到tuple可能因为数据包不合法,就如序列号过大 */return NF_ACCEPT;/* Don't try to NAT if this packet is not conntracked */if (ct == &nf_conntrack_untracked)return NF_ACCEPT;/* 在ct->ext中查找存不存在关于NAT的extension,没找到则新建。在本函数中用不到,NAT的extension在后面介绍。 */nat = nfct_nat(ct);if (!nat) {/* NAT module was loaded late. */if (nf_ct_is_confirmed(ct)) {printk("CT not confirmed ct=%p\n\n",ct);return NF_ACCEPT;}/* GFP即get free page,这些宏指定了内存分配时的优先级。这里只是分配空间 */nat = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC);if (nat == NULL) {pr_debug("failed to add NAT extension\n");return NF_ACCEPT;}}/* 判断连接状态 */switch (ctinfo) {case IP_CT_RELATED:case IP_CT_RELATED+IP_CT_IS_REPLY:if (ip_hdr(skb)->protocol == IPPROTO_ICMP) {if (!nf_nat_icmp_reply_translation(ct, ctinfo,   hooknum, skb))return NF_DROP;elsereturn NF_ACCEPT;}case IP_CT_NEW: /* 新来的,还没创建conntrack条目,需要查找NAT表 *//* Seen it before?  This can happen for loopback, retrans,   or local packets.. *//* 另外,如果只有单方向数据,这个if也会使其不需要查找nat表。 */if (!nf_nat_initialized(ct, maniptype)) {unsigned int ret;if (hooknum == NF_INET_LOCAL_IN)/* LOCAL_IN hook doesn't have a chain!  */ret = alloc_null_binding(ct, hooknum);else/* 在nat(iptable)表中匹配该hook中的iptables规则并执行target。结果是给skb指向的ct做了NAT,且更新ct->status为IPS_DST_NAT_DONE_BIT或IPS_SRC_NAT_DONE_BIT。 */ret = nf_nat_rule_find(skb, hooknum, in, out,       ct);if (ret != NF_ACCEPT) {return ret;}} elsepr_debug("Already setup manip %s for ct %p\n", maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST", ct);break;default:/* ESTABLISHED或REPLY的连接,就直接根据ct修改skb了。 */NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED ||     ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));}/* 前面已经修改了连接跟踪,这里正式修改了数据包里的地址 */return nf_nat_packet(ct, ctinfo, hooknum, skb);}

其中nf_nat_rule_find()函数通过调用ipt_do_table(skb, hooknum, in, out,net->ipv4.nat_table)来修改ct做NAT,ipt_do_table()函数的工作就是匹配iptables规则matches并执行target。以下面的数据包为例:

src ip:192.168.1.102,dst ip:192.168.2.100,wan ip:192.168.2.1

数据包从路由器LAN->WAN方向传输,即从局域网到公网,所以需要做SNAT,即在POST_ROUTING处为数据包做NAT。

在nf_nat_rule_find()函数前后ct的变化如下(TCP和UDP相同):

PRE_ROUTING:

 

源ip/port

目的ip/port

skb->nfctinfo

ipt_do_table()之前

192.168.1.102:3386

192.168.2.100:10115

IP_CT_NEW

192.168.2.100:10115

192.168.1.102:3386

IP_CT_NEW

ipt_do_table()之后

192.168.1.102:3386

192.168.2.100:10115

IP_CT_NEW

192.168.2.100:10115

192.168.1.102:3386

IP_CT_NEW

POST_ROUTING:

 

源ip/port

目的ip/port

skb->nfctinfo

ipt_do_table()之前

192.168.1.102:3386

192.168.2.100:10115

IP_CT_NEW

192.168.2.100:10115

192.168.1.102:3386

IP_CT_NEW

ipt_do_table()之后

192.168.1.102:3386

192.168.2.100:10115

IP_CT_NEW

192.168.2.100:10115

192.168.2.1:3386

IP_CT_NEW

skb的状态skb->nfctinfo在nf_conntrack_in()根据ct的状态ct->status被更新,即下一个包进来的时候才会更新。第一个数据包根据iptables设置的NAT规则做NAT后,conntrack条目被更新。

之后的数据包再进入netfilter的时候,由于conntrack优先级在NAT之前,所以skb->nfctinfo会在进入nf_nat_fn()之前更新,因此就会使用新的conntrack做NAT而无需再查找NAT表中的规则。之后的数据包的状态可能为IP_CT_IS_REPLY或IP_CT_ESTABLISHED,如果有expect连接,则可能为IP_CT_RELATED或IP_CT_RELATED + IP_CT_IS_REPLY。

如果只有单方向的数据包,那skb的状态在nf_conntrack_in()就不会被修改,所以会一直是IP_CT_NEW,因此后续数据包会一直查找NAT表。不过一般不会一直都是单方向的,即使跑UDP不分片包的数据,一般也会看到有REPLY方向的数据包,有了REPLY方向的数据包,在conntrack钩子就会更新conntrack和skb的状态。

ICMP做NAT要做一下特殊介绍,在POST_ROUTING处的连接如下:

 

源ip/port

目的ip/port

skb->nfctinfo

ipt_do_table()之前

192.168.1.102: 768

192.168.2.100:2048

IP_CT_NEW

192.168.2.100: 768

192.168.1.102:0

IP_CT_NEW

ipt_do_table()之后

192.168.1.102: 768

192.168.2.100: 2048

IP_CT_NEW

192.168.2.100: 768

192.168.2.1:0

IP_CT_NEW

由于ICMP没有端口号,所以会以ICMP报头中的Identifier作为源port,以ICMP报头中的Type + Code字段作为目的port。如果内网有多台主机,Identifier会发生变化,即连接上的源port会发生变化,以此区分不同的连接。而由于目的port是根据Type + Code确定的,这个值对于特定类型的ICMP报文的值是一样的。

UDP分片包做NAT:

由于defrag的hook在conntrack之前,所以不用担心分片包的问题。






0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 苹果手机自带的天气没有了怎么办? 衣服在洗衣机里忘记拿出来怎么办 苹果se手机系统占内存太大怎么办 客人把饭店老板打了民警怎么办 商场嫌品牌低端不让入驻怎么办 带着孩子坐飞机座位不在一起怎么办 公司老板跑路了员工该怎么办 超市买的衣服防盗扣忘记取了怎么办 在超市买的衣服那个扣没取怎么办啊 超市散称商品条码老记不住怎么办 app账号密码忘记了怎么办注销难 幼儿老师遇到家长比较孩子该怎么办 发的微信公众号内容重复了怎么办 招嫖诈骗微信转账被骗怎么办 朋友在深圳龙岗被传销骗了要怎么办 怀疑家里人被传销组织骗去了怎么办 怀疑家人被骗进传销了该怎么办 b本扣3分了怎么办2018 抖音里面就剩人头的视频怎么办 自学参加普通高考那小高考怎么办 如果小学生长胸只长了一个该怎么办 脸上毛多导致的毛孔粗怎么办 吃了激素药头发掉的厉害怎么办 剪发的剪子中间的螺丝扣总掉怎么办 染完头发后一段时间长新头发怎么办 睡觉头发老被老公压着怎么办 漆盖关节不自在怎么办?吃什么好 摆床头的位置后面是弧形位怎么办 君子兰的根全烂掉了只剩茎怎么办 老板不发工资怎么办 没签合同的 牙套粘在牙齿上的松了怎么办 缝的线长在肉里怎么办 小孩喜欢用舌头顶牙齿缝怎么办 1岁多小宝贝全身长红点怎么办 生完胸好涨但很难把奶吸出来怎么办 怀孕快9个月内裤上有白带怎么办 一早起来发现内裤有白色液体怎么办 阴部骚痒白带多白带多小腹痛怎么办 15岁一周鲁了3次怎么办 月经摊迟三天内裤有白带怎么办 怀孕内裤上有黄黄的分泌物怎么办