zoukankan      html  css  js  c++  java
  • Linux IP in IP隧道简述

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

    一. IPIP的初始化

    Linux中的IPIP隧道文件主要分布在tunnel4.cipip.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
    ");
    		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
    ");
    		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
    ");
    	if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
    		printk(KERN_CRIT "inet_init: Cannot add UDP protocol
    ");
    	if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
    		printk(KERN_CRIT "inet_init: Cannot add TCP protocol
    ");
    #ifdef CONFIG_IP_MULTICAST
    	if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
    		printk(KERN_CRIT "inet_init: Cannot add IGMP protocol
    ");
    #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
    ");
    		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()

    之后在ipip_fb_tunnel_init()中对IPIP隧道进行了参数的设置,包括名字,协议号什么的。最后就注册这个新创建的设备吧

    if ((err = register_netdev(ipn->fb_tunnel_dev)))
    		goto err_reg_dev;
    

    这样整个的初始化过程就做完了,下面简单分析一下发送和接收的过程。

    二. IPIP的接收

    我们之前说到过,对应从网卡收上来的报文,过完链路层后就会到ip_rcv()中,大概是这样的路线:

    ip_rcv()->ip_rcv_finish()->ip_local_deliver()->ip_local_deliver_finish(),最终会在其中看到

    ret = ipprot->handler(skb);
    if (ret < 0) {
      protocol = -ret;
      goto resubmit;
    }
    

    调用注册的协议的处理函数,也就是最终会调到tunnel4_rcv()->ipip_rcv()

    if ((tunnel = ipip_tunnel_lookup(dev_net(skb->dev),
    					iph->saddr, iph->daddr)) != NULL) { /* 查找对应的tunnel */
    		if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
    			read_unlock(&ipip_lock);
    			kfree_skb(skb);
    			return 0;
    		}
    
    		secpath_reset(skb);
    
    		skb->mac_header = skb->network_header; /* 修改报文的mac头指向网络层开始,为了下面使用netif_rx		能传给上层? */
    		skb_reset_network_header(skb);
    		skb->protocol = htons(ETH_P_IP);
    		skb->pkt_type = PACKET_HOST;  /* 填充报文信息 */
    
    		tunnel->dev->stats.rx_packets++;
    		tunnel->dev->stats.rx_bytes += skb->len;
    		skb->dev = tunnel->dev;
    		skb_dst_drop(skb);
    		nf_reset(skb);
    		ipip_ecn_decapsulate(iph, skb);
    		netif_rx(skb);  /* 传递给上层协议栈 */
    		read_unlock(&ipip_lock);
    		return 0;
    	}
    

    三. IPIP的发送

    在初始化的时候,我们看到IPIP报文的发送时通过ipip_tunnel_xmit()函数进行的。在发送时,要给原有的IP报文头前添加新的IP头,我们略过这个函数的前面的路由处理的部分,直接看关键的添加报文头的地方:

    max_headroom = (LL_RESERVED_SPACE(tdev)+sizeof(struct iphdr));
    
    	if (skb_headroom(skb) < max_headroom || skb_shared(skb) ||
    	    (skb_cloned(skb) && !skb_clone_writable(skb, 0))) {
    		struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);/* 为新的报文头分配空间 */
    		if (!new_skb) {  
    			ip_rt_put(rt);
    			stats->tx_dropped++;
    			dev_kfree_skb(skb);
    			return NETDEV_TX_OK;
    		}
    		if (skb->sk)
    			skb_set_owner_w(new_skb, skb->sk);
    		dev_kfree_skb(skb);
    		skb = new_skb;
    		old_iph = ip_hdr(skb);
    	}
    
    	skb->transport_header = skb->network_header; /* 重新设置传输层的头位置 */
    	skb_push(skb, sizeof(struct iphdr));
    	skb_reset_network_header(skb);
    	memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
    	IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED |
    			      IPSKB_REROUTED);
    	skb_dst_drop(skb);
    	skb_dst_set(skb, &rt->u.dst);
    
    	/*
    	 *	Push down and install the IPIP header.
    	 */
    
    	/* 设置新的IP头字段 */
    	iph 			=	ip_hdr(skb);
    	iph->version		=	4;
    	iph->ihl		=	sizeof(struct iphdr)>>2;
    	iph->frag_off		=	df;
    	iph->protocol		=	IPPROTO_IPIP;
    	iph->tos		=	INET_ECN_encapsulate(tos, old_iph->tos);
    	iph->daddr		=	rt->rt_dst;
    	iph->saddr		=	rt->rt_src;
    
    	if ((iph->ttl = tiph->ttl) == 0)
    		iph->ttl	=	old_iph->ttl;
    

    最后调用IPTUNNEL_XMIT()宏发送出去。

  • 相关阅读:
    古文_硕鼠,原文及翻译
    使用php模拟post的几种方法
    alpha版、beta版、rc版的意思
    8007003Windows Update遇到未知错误
    树上10只鸟,开枪打死1只,还剩几只?
    [转]乐死我了,怎么样成为一个全栈程序员(Full Stack Developer),附我想专注的语言
    [转]Visual C++ RunTime的特征——非烫即屯
    斗破苍穹中的几个人物图片
    吐槽一下中国的大学的教材
    百度知道里关于C++的讨论
  • 原文地址:https://www.cnblogs.com/yhp-smarthome/p/7336947.html
Copyright © 2011-2022 走看看