本文共 2989 字,大约阅读时间需要 9 分钟。
预置条件:
PC1发送包到PC2,PC1和PC2在不同的局域网中,要使得PC1的包能够到达PC2,有两种途径:
这里,我们只介绍ip tunnel这种方式。
PC1发往PC2的包的src为192.168.1.10,dst为192.168.2.10,PC1首先将包发往网关GW1。在网关GW1和GW2之间建立ip tunnel,使得PC1的包能到达GW2。
先介绍网关GW1和GW2的ip tunnel的配置。
GW1:
#ip tunnel add tun0 mode ipip remote 10.70.128.20 local 10.70.128.10 ttl 64
#ip link set tun0 up
#ip route add 192.168.2.0/24 dev tun0
GW1的配置与GW1类似。
这3条命令后面两条很好理解,一个是使得tun0设备up起来,另一条是配置路由,即所有发往192.168.2.0/24的包都路由到tun0设备,从tun0这个设备发出去。
我们着重来介绍下ip tunnel这条命令。
Ip tunnel命令的实现是向kernel的ipip module发送IOC命令SIOCADDTUNNEL(参考iproute2-4.20/ip/iptunnel.c)
我们来看看ipip module的处理。
(linux-4.7.1/net/ipv4/ipip.c)
ipip_tunnel_ioctl()->ip_tunnel_ioctl()->ip_tunnel_create()->__ip_tunnel_create()
__ip_tunnel_create()分配struct net_device, 即创建tun0设备。
在alloc_netdev()中会调用这个ops->setup()。
在ipip module挂载的时候,ipip_init()->register_pernet_device(&ipip_net_ops)
register_pernet_device(&ipip_net_ops)会调用ipip_net_ops.init(),即调用ipip_init_net();
ipip_init_net()
->ip_tunnel_init_net(net, ipip_net_id, &ipip_link_ops, "tunl0")
->itn->fb_tunnel_dev = __ip_tunnel_create(net, ops, &parms)
所以这里的ops->setup()就是ipip_link_ops.setup(), 即ipip_tunnel_setup()。
这里设置了dev->netdev_ops为ipip_netdev_ops.
最后,将ip tunnel的信息记录到该dev。
即tunnel->parms记录了配置命令中的remote,local ttl等参数信息。
配置完ip tunnel后up了该tunnel device,并配置路由到该device的路由entry.
接下来看GW1收到PC1发往PC2的包时的处理。
以Kenel ipv4为例来说明函数调用过程。
ip_rcv() ->
NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, net, NULL, skb, dev, NULL, ip_rcv_finish) ->
ip_rcv_finish() -> ip_route_input_noref() -> ip_route_input_slow() -> fib_lookup() //查找路由表
-> ip_mkroute_input()
PC1发往PC2的包的路由结果不是local,所以要求GW1的eth0的forward特性要打开,否则就是HOST UNREACHABLE.
Ip_mkroute_input() -> __mkroute_input() 设置dst.input为ip_forward(), 设置dst.output为ip_output()
ip_rcv_finish() -> dst_input(skb),即进入了ip_forward()处理。
ip_forward() ->
NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, net, NULL, skb, skb->dev, rt->dst.dev, ip_forward_finish) ->
ip_forward_finish() -> dst_output(net, sk, skb) //即ip_output()
ip_output() -> //这里会设置skb->dev 为出去的接口dev;
NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, net, sk, skb, NULL, dev, ip_finish_output, !(IPCB(skb)->flags & IPSKB_REROUTED)) ->
Ip_finish_output() -> ip_finish_output2()
Ip_finish_output2()首先查找neighbour, __ipv4_neigh_lookup_noref(), 然后调用dst_neigh_output()
dst_neigh_output() -> n->output(n, skb) //这里这个n->output()就是neigh_direct_output()
neigh_direct_output() -> dev_queue_xmit() -> __dev_queue_xmit()
__dev_queue_xmit()判断设备是否up,在up状态下调用dev_hard_start_xmit()
dev_hard_start_xmit() -> xmit_one() -> netdev_start_xmit() -> __netdev_start_xmit()
__netdev_start_xmit()这里调用dev->netdev_ops->ndo_start_xmit()
在ip tunnel这个例子中,这个dev->netdev_ops->ndo_start_xmit()就是ipip_tunnel_xmit()
ipip_tunnel_xmit() -> ip_tunnel_xmit() -> iptunnel_xmit()
ip_tunnel_xmit()会以ip tunnel命令中的local, remote为key进行路由查找,并设置包的dst skb_dst()为这个找到的路由entry. 这个路由的结果是从GW1 eth1出去。
iptunnel_xmit()会build一个外层的iphdr, 改iphdr的src为ip tunnel命令中local, dst为ip tunnel命令中的remote。最后ip_local_out()发出去。
所以PC1发往PC2的包,在GW1的eth1的出去后的形式为:
转载地址:http://ttlci.baihongyu.com/