转自:http://www.cnblogs.com/yhp-smarthome/p/7336947.html

前言: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\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隧道简述相关推荐

  1. linux查看出口IP

    linux查看出口IP 1,614 views, Linux, by 木木. 因为某种原因,需要知道本地服务器出口IP,可以用以下方法检测: wget http://members.3322.org/ ...

  2. linux配置静态IP后ping外网不通的解决方案

    linux配置静态IP后ping外网不通的解决方案 参考文章: (1)linux配置静态IP后ping外网不通的解决方案 (2)https://www.cnblogs.com/litiammmm/p/ ...

  3. Linux Kernel TCP/IP Stack — L3 Layer — netfilter 框架

    目录 文章目录 目录 netfilter 框架 netfilter 的组成模块 netfilter 的 Hook 机制实现 netfilter 的工作原理 规则(Rules) 链(Chains) 表( ...

  4. Linux Kernel TCP/IP Stack — L2 Layer — Linux Bridge(虚拟网桥)的基本操作

    目录 文章目录 目录 Linux bridge 的基本操作 创建 Bridge 将 veth pair 连上 Bridge 为 Bridge 配置 IP 地址 将物理网卡接口设备挂靠 Bridge L ...

  5. linux c socket ip地址 字符串 数字 转换 inet_addr inet_ntoa

    目录 0.转换函数 1.介绍inet_addr函数 2.介绍inet_ntoa函数 3.一般使用总结 inet_addr 将字符串形式的IP地址 -> 网络字节顺序  的整型值 inet_nto ...

  6. 试试Linux下的ip命令,ifconfig已经过时了

    linux的ip命令和ifconfig类似,但前者功能更强大,并旨在取代后者.使用ip命令,只需一个命令,你就能很轻松地执行一些网络管理任务.ifconfig是net-tools中已被废弃使用的一个命 ...

  7. linux系统改ip地址 永久生效,Linux修改IP永久生效

    修改IP永久生效按以下方法: 1)修改配置文件 vi /etc/sysconfig/network-scripts/ifcfg-eth0(eth0,第一块网卡,如果是第二块则为eth1) 按如下修改i ...

  8. 为VMware虚拟机中的Linux系统设置固定IP的方法

    这篇文章主要介绍了为VMware虚拟机中的Linux系统设置固定IP的方法,包括以nat方式固定ip上网的方法,需要的朋友可以参考下 1.配置DNS: 修改 /etc/resolv.conf 文件,添 ...

  9. 如何配置php的ip地址吗,linux如何配置IP

    linux如何配置IP? IP地址配置 rhel7/centos7添加了新的IP地址配置工具,但之前的老版本没有,所以这里可参考两种方法. 方法一:nmcli工具(新版系统可用) 第1步 查看网卡名称 ...

最新文章

  1. https open api_钉钉API发送消息
  2. SQL : 在SQL Server 2008(Or Express)中如何Open并编辑数据表【转】
  3. 微软bi 架构 服务器,微软BI体系结构.
  4. 带有Swagger的Spring Rest API –创建文档
  5. python面向对象类创建人物类包含姓名、职业_python面向对象类
  6. 合泰单片机市场占有率_holtek单片机图文全面详解
  7. 转载 2020-02-18 在KVM主机和虚拟机之间共享目录
  8. SpringBoot+SpringMVC+MybatisPlus框架整合实现分页插件查询
  9. 电子游戏发展史话——《doom启示录》读后感(三)
  10. 心电信号质量评估——ecg_qc工具包使用方法
  11. 编写简单的内核模块——Linux操作系统原理与应用(陈莉君第2版13页)
  12. vue关闭eslint语法检查
  13. 【SaaS考试认证】aPaaS_腾讯千帆神笔
  14. 推荐几款HTML5开发工具
  15. 用python做元旦贺卡_用AI帮你画新年贺卡:只需输入几个单词,就能模仿大师名作...
  16. 交互工具 Framer 中文网全面更新,你可以分享灵感啦
  17. C语言编译、链接简介
  18. 如何解决百度云下载慢的问题
  19. html position与z-index定位学习
  20. 二进制形式配置k8s集群(二)-生成证书

热门文章

  1. 记一次低级错误:feign.FeignException: status 404 reading XXXClient#XXMethod(Long)
  2. 如何学好编程(一):什么叫编程
  3. ES系列:查看所有索引及其状态
  4. python3进阶之方法重写(三)
  5. 思科 | VLAN 间路由实验(三层交换机)
  6. forEach for 循环跳出问题
  7. 1231. 航班时间
  8. 短视频火爆全网也难逃一死
  9. 比较基因组学分析(Comparative Genomics Analysis)
  10. Python实战案例分享:爬取当当网商品数据