Linux IP in IP隧道简述
转自: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;}
#endifreturn 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_MULTICASTif (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 1,614 views, Linux, by 木木. 因为某种原因,需要知道本地服务器出口IP,可以用以下方法检测: wget http://members.3322.org/ ...
- linux配置静态IP后ping外网不通的解决方案
linux配置静态IP后ping外网不通的解决方案 参考文章: (1)linux配置静态IP后ping外网不通的解决方案 (2)https://www.cnblogs.com/litiammmm/p/ ...
- Linux Kernel TCP/IP Stack — L3 Layer — netfilter 框架
目录 文章目录 目录 netfilter 框架 netfilter 的组成模块 netfilter 的 Hook 机制实现 netfilter 的工作原理 规则(Rules) 链(Chains) 表( ...
- Linux Kernel TCP/IP Stack — L2 Layer — Linux Bridge(虚拟网桥)的基本操作
目录 文章目录 目录 Linux bridge 的基本操作 创建 Bridge 将 veth pair 连上 Bridge 为 Bridge 配置 IP 地址 将物理网卡接口设备挂靠 Bridge L ...
- linux c socket ip地址 字符串 数字 转换 inet_addr inet_ntoa
目录 0.转换函数 1.介绍inet_addr函数 2.介绍inet_ntoa函数 3.一般使用总结 inet_addr 将字符串形式的IP地址 -> 网络字节顺序 的整型值 inet_nto ...
- 试试Linux下的ip命令,ifconfig已经过时了
linux的ip命令和ifconfig类似,但前者功能更强大,并旨在取代后者.使用ip命令,只需一个命令,你就能很轻松地执行一些网络管理任务.ifconfig是net-tools中已被废弃使用的一个命 ...
- linux系统改ip地址 永久生效,Linux修改IP永久生效
修改IP永久生效按以下方法: 1)修改配置文件 vi /etc/sysconfig/network-scripts/ifcfg-eth0(eth0,第一块网卡,如果是第二块则为eth1) 按如下修改i ...
- 为VMware虚拟机中的Linux系统设置固定IP的方法
这篇文章主要介绍了为VMware虚拟机中的Linux系统设置固定IP的方法,包括以nat方式固定ip上网的方法,需要的朋友可以参考下 1.配置DNS: 修改 /etc/resolv.conf 文件,添 ...
- 如何配置php的ip地址吗,linux如何配置IP
linux如何配置IP? IP地址配置 rhel7/centos7添加了新的IP地址配置工具,但之前的老版本没有,所以这里可参考两种方法. 方法一:nmcli工具(新版系统可用) 第1步 查看网卡名称 ...
最新文章
- https open api_钉钉API发送消息
- SQL : 在SQL Server 2008(Or Express)中如何Open并编辑数据表【转】
- 微软bi 架构 服务器,微软BI体系结构.
- 带有Swagger的Spring Rest API –创建文档
- python面向对象类创建人物类包含姓名、职业_python面向对象类
- 合泰单片机市场占有率_holtek单片机图文全面详解
- 转载 2020-02-18 在KVM主机和虚拟机之间共享目录
- SpringBoot+SpringMVC+MybatisPlus框架整合实现分页插件查询
- 电子游戏发展史话——《doom启示录》读后感(三)
- 心电信号质量评估——ecg_qc工具包使用方法
- 编写简单的内核模块——Linux操作系统原理与应用(陈莉君第2版13页)
- vue关闭eslint语法检查
- 【SaaS考试认证】aPaaS_腾讯千帆神笔
- 推荐几款HTML5开发工具
- 用python做元旦贺卡_用AI帮你画新年贺卡:只需输入几个单词,就能模仿大师名作...
- 交互工具 Framer 中文网全面更新,你可以分享灵感啦
- C语言编译、链接简介
- 如何解决百度云下载慢的问题
- html position与z-index定位学习
- 二进制形式配置k8s集群(二)-生成证书
热门文章
- 记一次低级错误:feign.FeignException: status 404 reading XXXClient#XXMethod(Long)
- 如何学好编程(一):什么叫编程
- ES系列:查看所有索引及其状态
- python3进阶之方法重写(三)
- 思科 | VLAN 间路由实验(三层交换机)
- forEach for 循环跳出问题
- 1231. 航班时间
- 短视频火爆全网也难逃一死
- 比较基因组学分析(Comparative Genomics Analysis)
- Python实战案例分享:爬取当当网商品数据