linuxsir首页 LinuxSir.Org | Linux、BSD、Solaris、Unix | 开源传万世,因有我参与欢迎您!
网站首页 | 设为首页 | 加入收藏
您所在的位置:主页 > Linux基础建设 >

Linux IP in IP隧道简述

时间:2019-10-06  来源:未知  作者:admin666

前言: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;
    }
#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公社的RSS地址:https://www.linuxidc.com/rssFeed.aspx

友情链接
  • Mozilla发布Firefox 67.0.4,修复沙箱逃逸漏洞
  • 蚂蚁金服正式成为CNCF云原生计算基金会黄金会员
  • Firefox 68将采用Microsoft BITS安装更新
  • OpenSSH增加对存储在RAM中的私钥的保护
  • 谷歌想实现自己的curl,为什么?
  • Raspberry Pi 4发布:更快的CPU、更大的内存
  • Firefox的UA将移除CPU架构信息
  • Ubuntu放弃支持32位应用程序实属乌龙,Steam会否重回Ubuntu怀抱
  • Qt 5.13稳定版发布:引入glTF 2.0、改进Wayland以及支持Lottie动
  • 红帽企业Linux 7现已内置Redis 5最新版
  • Slack进入微软内部禁用服务清单,GitHub也在其列?
  • 安全的全新编程语言V发布首个可用版本
  • Windows Terminal已上架,快尝鲜
  • 阿里巴巴微服务开源生态报告No.1
  • 面世两年,Google地球将支持所有基于Chromium的浏览器
  • 推进企业容器化持续创新,Rancher ECIC千人盛典完美收官
  • CentOS 8.0最新构建状态公布,或于数周后发布
  • Debian移植RISC
  • 微软拆分操作系统的计划初现雏形
  • Oracle发布基于VS Code的开发者工具,轻松使用Oracle数据库
  • Ubuntu 19.10停止支持32位的x86架构
  • 微软为Windows Terminal推出全新logo
  • 联想ThinkPad P系列笔记本预装Ubuntu系统
  • 微软发布适用于Win7/8的Microsoft Edge预览版
  • 启智平台发布联邦学习开源数据协作项目OpenI纵横
  • 经过六个多月的延迟,微软终于推出Hyper
  • ZFS On Linux 0.8.1 发布,Python可移植性工作
  • DragonFly BSD 5.6.0 发布,HAMMER2状态良好
  • Linux Kernel 5.2
  • CentOS 8.0 看起来还需要几周的时间
  • 百度网盘Linux版正式发布
  • PCIe 6.0宣布:带宽翻倍 狂飙至256GB/s
  • PHP 7.4 Alpha 发布,FFI扩展,预加载Opcache以获得更好的性能
  • Canonical将在未来的Ubuntu版本中放弃对32位架构的支持
  • Scala 2.13 发布,改进的编译器性能
  • 微软的GitHub收购了Pull Panda,并且使所有订阅完全免费
  • Windows Subsystem for Linux 2 (WSL 2)现在适用于Windows 10用
  • Debian 10 “Buster”的RISC
  • MariaDB宣布发布MariaDB Enterprise Server 10.4
  • DXVK 1.2.2 发布,带来微小的CPU开销优化
  • DragonFlyBSD 5.6 RC1 发布,VM优化,默认为HAMMER2
  • PrimeNG 8.0.0 发布,支持Angular 8,FocusTrap等
  • GIMP 2.10.12 发布,一些有用的改进
  • 清华大学Anaconda 镜像服务即将恢复
  • Debian GNU/Linux 10 “Buster” 操作系统将于2019年7月6日发布
  • 时时彩论坛
  • 五星体育斯诺克
  • 北单比分直播
  • 河北11选5走势图
  • 福建体彩36选7开奖结果
  • 九龙图库下载