nat表需要的三个链:
1.PREROUTING:可以在这里定义进行目的NAT的规则,因为路由器进行路由时只检查数据包的目的ip地址,所以为了使数据包得以正确路由,我们必须在路由之前就进行目的NAT;
2.POSTROUTING:可以在这里定义进行源NAT的规则,系统在决定了数据包的路由以后在执行该链中的规则。
3.OUTPUT:定义对本地产生的数据包的目的NAT规则。
需要用到的几个动作选项:
redirect | 将数据包重定向到另一台主机的某个端口,通常用实现透明代理和对外开放内网某些服务。 | ||||
snat | 源地址转换,改变数据包的源地址 | ||||
dnat | 目的地址转换,改变数据包的目的地址 | ||||
masquerade | IP伪装,只适用于ADSL等动态拨号上网的IP伪装,如果主机IP是静态分配的,就用snat |
PRERROUTING:DNAT 、REDIRECT (路由之前)只支持-i,不支持-o。在作出路由之前,对目的地址进行修改
POSTROUTING:SNAT、MASQUERADE (路由之后)只支持-o,不支持-i。在作出路由之后,对源地址进行修改
OUTPUT:DNAT 、REDIRECT (本机)DNAT和REDIRECT规则用来处理来自NAT主机本身生成的出站数据包.
对于内网到外网的自动ip转换,这里用的是masquerade
-A POSTROUTING -o wan -j MASQUERADE
第一个数据包
do_softirq
net_rx_action
process_backlog
br_handle_frame
NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL, br_andle_frame_finish);
调用hook函数br_nf_pre_routing
由于lan口都是桥接到br0网关上,所以数据会经过网桥走,然后再由hook函数br_nf_pre_routing,再到ip层
NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, skb->dev, NULL, br_nf_pre_routing_finish);
在这里遇到ct连接信息初始化的接口
ipv4_conntrack_in
nf_conntrack_in
l3proto = __nf_ct_l3proto_find(pf); //获取l3和l4层协议
l4proto = __nf_ct_l4proto_find(pf, protonum);
resolve_normal_ct
nf_ct_get_tuple 获取tuple信息
__nf_conntrack_find_get 根据tuple在hashtable中获取对应的ct信息
init_conntrack 第一个起始数据没有获取到,进行分配初始化ct信息,根据数据包内容填充两个方向的五元组信息
__nf_conntrack_alloc 申请一个对应ct,关联l4协议,加入未确认的ct table。
l4proto->new
hlist_nulls_add_head_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode, &net->ct.unconfirmed);
ct->status默认为0,所以*ctinfo = IP_CT_NEW;
l4proto->packet(ct, skb, dataoff, ctinfo, pf, hooknum, timeouts) 向l4协议喂包,更新ct定时器信息。
ct信息初始化完毕
br_nf_pre_routing_finish之后ip包继续进入ip层,由于不是本地数据,路由过后会进入转发流程,
ct连接信息的建立br_nf_pre_routing
br_nf_pre_routing_finish
NF_HOOK_THRESH(NFPROTO_BRIDGE,NF_BR_PRE_ROUTING,skb, skb->dev, NULL,br_handle_frame_finish,1);
br_handle_frame_finish
br_pass_frame_up
NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,netif_receive_skb);
__netif_receive_skb
ip_rcv
NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
ip_rcv_finish 路由后进入转发流程
dst_input
ip_forward
NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev,rt->dst.dev, ip_forward_finish);
ip_forward_finish
dst_output
NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev,ip_finish_output, !(IPCB(skb)->flags & IPSKB_REROUTED));
nf_nat_out
nf_nat_fn
nf_nat_rule_find
ipt_do_table
masquerade_tg
newsrc = inet_select_addr(par->out, rt->rt_gateway, RT_SCOPE_UNIVERSE);
获取nat的ip,这里就是设置wan口ip
nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_SRC) 填充到newrange,然后设置ct info
get_unique_tuple 获取一个tuple
find_best_ips_proto(zone, tuple, range, ct, maniptype); 根据填充的newrange设置新的tuple->src.u3.ip
proto->unique_tuple(tuple, range, maniptype, ct); 根据协议获取一个可用的端口nat_tcp/nat_udp
如果前后tuple不一致,更新ct连接信息中的tuple,更新status为nat
if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
struct nf_conntrack_tuple reply;
/* Alter conntrack table so will recognize replies. */
nf_ct_invert_tuplepr(&reply, &new_tuple);
nf_conntrack_alter_reply(ct, &reply);
/* Non-atomic: we own this at the moment. */
if (maniptype == NF_NAT_MANIP_SRC)
ct->status |= IPS_SRC_NAT;
else
ct->status |= IPS_DST_NAT;
}
net->ipv4.nat_bysource[srchash] 更新记录
nat转换需要的连接信息就更新完成了
nat的ct信息更新之后,接下来进行实际的nat转换,对ip包的地址端口进行修改
紧接着上面的流程之后调用nf_nat_packet
nf_nat_packet(ct, ctinfo, hooknum, skb)
manip_pkt(target.dst.protonum, skb, 0, &target, mtype)
p = __nf_nat_proto_find(proto);
if (!p->manip_pkt(skb, iphdroff, target, maniptype)) 找到对应的nat l4协议接口进行nat转换,重新计算crc校验和,l4中更新port
这里tcp的crc 计算时包含了伪头部ip地址,所以也需要更新
if (maniptype == NF_NAT_MANIP_SRC) {
csum_replace4(&iph->check, iph->saddr, target->src.u3.ip);
iph->saddr = target->src.u3.ip;
} else {
csum_replace4(&iph->check, iph->daddr, target->dst.u3.ip);
iph->daddr = target->dst.u3.ip;
}
之后再进行ip修改和crc的更新
接下来应该是对连接信息的confirm
在NF_INET_POST_ROUTING上的另一个hook函数ipv4_confirm
获取连接信息,如果状态为new则进行confirm
if (!ct || ctinfo == IP_CT_RELATED_REPLY)
goto out;
nf_conntrack_confirm
__nf_conntrack_confirm
hlist_nulls_del_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode);
ct->timeout.expires += jiffies;
add_timer(&ct->timeout);
ct->status |= IPS_CONFIRMED;
__nf_conntrack_hash_insert(ct, hash, repl_hash);
第二个数据包
先从wan口传递到内核backlog中
process_backlog
__netif_receive_skb
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
进入pre routing的hook函数调用流程
ipv4_conntrack_in
nf_conntrack_in
resolve_normal_ct
成功找到之前建立的ct连接信息
根据方向判断设置状态
if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY) {
*ctinfo = IP_CT_ESTABLISHED_REPLY;
/* Please set reply bit if this packet OK */
*set_reply = 1;
}
l4proto->packet(ct, skb, dataoff, ctinfo, pf, hooknum, timeouts); //l4喂包
if (set_reply && !test_and_set_bit(IPS_SEEN_REPLY_BIT, &ct->status))
nf_conntrack_event_cache(IPCT_REPLY, ct);
nf_nat_in
nf_nat_fn
nf_nat_packet(ct, ctinfo, hooknum, skb)
statusbit = IPS_DST_NAT;
manip_pkt(target.dst.protonum, skb, 0, &target, mtype) //跟前面说明类似,进行目的ip、端口的转换
ip_rcv_finish
ip_route_input_noref 路由找到skb的去向,这里ip包非本地数据需要转发
dst_input(skb);
ip_forward
NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev, rt->dst.dev, ip_forward_finish);
ip_forward_finish
dst_output(skb)
ip_output
NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev, ip_finish_output, !(IPCB(skb)->flags & IPSKB_REROUTED));
ip_finish_output
ip_finish_output2 判断headroom是否需要调整
neigh = dst_get_neighbour_noref(dst); //获取邻居信息,寻找转发出口
if (neigh) {
int res = neigh_output(neigh, skb);
rcu_read_unlock();
return res;
}
neigh_output
neigh_hh_output //根据邻居表添加mac头部
dev_queue_xmit
dev_hard_start_xmit
br_dev_xmit //由于是桥接口br0,所以进入br xmit
br_deliver
__br_deliver
NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL, skb->dev, br_forward_finish);
br_forward_finish
NF_HOOK(NFPROTO_BRIDGE, NF_BR_POST_ROUTING, skb, NULL, skb->dev, br_dev_queue_push_xmit);
br_dev_queue_push_xmit
dev_queue_xmit 从网口再出去到达连接的设备端
第三个数据包
第三个数据包流程基本和第一个差不多,不过在ct处理中会更新状为*ctinfo = IP_CT_ESTABLISHED;
最后借用一张转来的图