Linux IP in IP隧道简述

前言:IPIP隧道是一种三层隧道,通过把原来的IP包封装在新的IP包里面,来创建隧道传输。本篇简单分析Linux(2.6.32版本)中的IPIP隧道的实现过程,期望有所借鉴,造出轮子:-)

一. IPIP的初始化

Linux中的IPIP隧道文件主要分布在tunnel4.c和ipip.c文件中。因为是三层隧道,在IP报文中填充的三层协议自然就不能是常见的TCP和UDP,所以,Linux抽象了一个隧道层,位置就相当于传输层,主要的实现就是在tunnel4.c中。来看看他们的初始化:

抽象的隧道层和IPIP模块都是以注册模块的方式进行初始化

module_init(tunnel4_init); module_init(ipip_init);

首先看隧道层的初始化,主要的工作就是注册隧道协议和对应的处理函数:

static int __init tunnel4_init(void) { if (inet_add_protocol(&tunnel4_protocol, IPPROTO_IPIP)) { printk(KERN_ERR "tunnel4 init: can't add protocol\n"); return -EAGAIN; } #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) if (inet_add_protocol(&tunnel64_protocol, IPPROTO_IPV6)) { printk(KERN_ERR "tunnel64 init: can't add protocol\n"); inet_del_protocol(&tunnel4_protocol, IPPROTO_IPIP); return -EAGAIN; } #endif return 0; }

inet_add_protocol(&tunnel4_protocol, IPPROTO_IPIP)把IPIP隧道协议注册进inet_protos全局数组中,而inet_protos中的其他协议注册是在inet_init()中:

if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0) printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n"); if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0) printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n"); if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0) printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n"); #ifdef CONFIG_IP_MULTICAST if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0) printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n"); #endif

看一下隧道层的处理函数:

static const struct net_protocol tunnel4_protocol = { .handler = tunnel4_rcv, .err_handler = tunnel4_err, .no_policy = 1, .netns_ok = 1, };

这样注册完后,当接收到三层类型是IPPROTO_IPIP时,就会调用tunnel4_rcv进行下一步的处理。可以说在隧道层对隧道协议进行的注册,保证能够识别接收到隧道包。而对隧道包的处理则是在IPIP中完成的。

for (handler = tunnel4_handlers; handler; handler = handler->next) if (!handler->handler(skb)) return 0; icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);

在隧道层的处理函数中进一步调用注册的不同隧道协议的处理函数,分别处理。

接下来进一步看IPIP的初始化部分:

static int __init ipip_init(void) { int err; printk(banner); if (xfrm4_tunnel_register(&ipip_handler, AF_INET)) { printk(KERN_INFO "ipip init: can't register tunnel\n"); return -EAGAIN; } err = register_pernet_gen_device(&ipip_net_id, &ipip_net_ops); if (err) xfrm4_tunnel_deregister(&ipip_handler, AF_INET); return err; }

IPIP模块初始化的部分也十分精简,主要就是两部分的工作,一个是注册协议相关的处理函数等;另一个是创建对应的虚拟设备。

首先是注册了IPIP对应的处理函数

static struct xfrm_tunnel ipip_handler = { .handler = ipip_rcv, .err_handler = ipip_err, .priority = 1, };

可以看到,从隧道层的处理函数进一步找到IPIP的处理函数后,IPIP报文就会最终进入ipip_rcv()处理,这部分在后面再详细说明。

再来看创建设备部分:

register_pernet_gen_device()->register_pernet_operations(),在其中,最后调用了操作集中的初始化函数

if (ops->init == NULL) return 0; return ops->init(&init_net);

对应的操作函数集如下:

static struct pernet_operations ipip_net_ops = { .init = ipip_init_net, .exit = ipip_exit_net, };

这样,就进入到ipip_init_net()中,终于看到创建设备咯

ipn->fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), "tunl0", ipip_tunnel_setup); if (!ipn->fb_tunnel_dev) { err = -ENOMEM; goto err_alloc_dev; }

在创建设备时,对设备还进行了初始化配置ipip_tunnel_setup()

static void ipip_tunnel_setup(struct net_device *dev) { dev->netdev_ops = &ipip_netdev_ops; dev->destructor = free_netdev; dev->type = ARPHRD_TUNNEL; dev->hard_header_len = LL_MAX_HEADER + sizeof(struct iphdr); dev->mtu = ETH_DATA_LEN - sizeof(struct iphdr); dev->flags = IFF_NOARP; dev->iflink = 0; dev->addr_len = 4; dev->features |= NETIF_F_NETNS_LOCAL; dev->priv_flags &= ~IFF_XMIT_DST_RELEASE; }

这里看到有设备的操作集dev->netdev_ops = &ipip_netdev_ops;,通过这个,我们能知道这个设备都能进行哪些操作:

static const struct net_device_ops ipip_netdev_ops = { .ndo_uninit = ipip_tunnel_uninit, .ndo_start_xmit = ipip_tunnel_xmit, .ndo_do_ioctl = ipip_tunnel_ioctl, .ndo_change_mtu = ipip_tunnel_change_mtu, };

可以看出设备最后的发送函数就是ipip_tunnel_xmit()

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/11710.html