Linux IP in IP隧道简述
来源:互联网 发布:ssh弱算法支持 漏洞 编辑:程序博客网 时间:2024/06/05 06:20
转自:http://www.cnblogs.com/yhp-smarthome/p/7336947.html
前言: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()
之后在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()
宏发送出去。
- Linux IP in IP隧道简述
- Linux中IP隧道
- linux如何建立IP隧道
- Linux IP地址隧道访问
- DHCP+NAT+IP隧道
- IP 隧道概述
- Linux中IP隧道的分析与建议[转贴]
- Linux 下ssh创建ip加密码隧道
- 在Linux上通过IPIP实现IP隧道
- Linux 下ssh创建ip加密码隧道
- Linux内核工程导论——网络:IP:隧道
- Linux 下ssh创建ip加密码隧道
- linux网络编程 TCP/IP简述
- IP 隧道技术:基础篇
- IP隧道实现虚拟服务器
- linux之vpn服务器间ip隧道跳转多ip路由走向分流
- Static IP address in linux
- TCP/IP简述
- JDBC基本概念
- 蓝桥杯 基础练习 特殊回文数/回文数
- [译] ubuntu install tomcat maven
- MFC ListControl用法
- jzoj5478. 【NOIP2017提高组】列队
- Linux IP in IP隧道简述
- Java8---Stream的介绍和相关概念(1)
- [Java性能剖析]JVM Management API
- 基础学习笔记之opencv(3):haartraining生成.xml文件过程
- 最简单实用的截图工具--效果图
- Can you answer these queries IV(线段树区间和)
- faster_rcnn 编译caffe-fast-rcnn时报错
- 利用Performance API分析网站性能
- 明天是几号?