Linux内核路由器模式 内网ip的nat转换实现流程

Linux内核路由器模式 内网ip的nat转换实现流程_第1张图片

Linux内核路由器模式 内网ip的nat转换实现流程_第2张图片


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;





最后借用一张转来的图

Linux内核路由器模式 内网ip的nat转换实现流程_第3张图片


Linux内核路由器模式 内网ip的nat转换实现流程_第4张图片



你可能感兴趣的:(Linux内核路由器模式 内网ip的nat转换实现流程)