目前内核支持的封装类型由枚举类型lwtunnel_encap_types定义,如下所示支持MPLS、IP、ILA、IP6、SEG6、BPF和SEG6_LOCAL等7种类型。函数lwtunnel_valid_encap_type负责检验用户配置(通过netlink接口)的封装类型是否合法,合法的封装类型必须大于LWTUNNEL_ENCAP_NONE,并且小于__LWTUNNEL_ENCAP_MAX;而且内核必须已经注册了相应封装类型的操作函数集(由结构体lwtunnel_encap_add_ops表示),其通过函数lwtunnel_encap_add_ops进行注册。

enum lwtunnel_encap_types {
    LWTUNNEL_ENCAP_NONE,
    LWTUNNEL_ENCAP_MPLS,
    LWTUNNEL_ENCAP_IP,
    LWTUNNEL_ENCAP_ILA,
    LWTUNNEL_ENCAP_IP6,
    LWTUNNEL_ENCAP_SEG6,
    LWTUNNEL_ENCAP_BPF,
    LWTUNNEL_ENCAP_SEG6_LOCAL,
    __LWTUNNEL_ENCAP_MAX,
}; 

内核使用结构体lwtunnel_encap_ops表示特定的封装类型操作函数集,系统中所有注册的封装类型操作函数集保存在全局数组lwtun_encaps中。

static const struct lwtunnel_encap_ops __rcu *lwtun_encaps[LWTUNNEL_ENCAP_MAX + 1] __read_mostly;

下表列出了内核中封装类型和操作函数集的对应信息:

 +---------------------------+-------------------+--------------------+|    LWTUNNEL_ENCAP_MPLS    |  mpls_iptun_ops   |  mpls_iptunnel.c   |+---------------------------+-------------------+--------------------+|    LWTUNNEL_ENCAP_IP      |  ip_tun_lwt_ops   |  ip_tunnel_core.c  |+---------------------------+-------------------+--------------------+|    LWTUNNEL_ENCAP_ILA     |  ila_encap_ops    |  ila_lwt.c         |+---------------------------+-------------------+--------------------+|    LWTUNNEL_ENCAP_IP6     |  ip6_tun_lwt_ops  |  ip_tunnel_core.c  |+---------------------------+-------------------+--------------------+|    LWTUNNEL_ENCAP_SEG6    |  seg6_iptun_ops   |  seg6_iptunnel.c   |+---------------------------+-------------------+--------------------+|    LWTUNNEL_ENCAP_BPF     |  bpf_encap_ops    |  lwt_bpf.c         |+---------------------------+-------------------+--------------------+| LWTUNNEL_ENCAP_SEG6_LOCAL |  seg6_local_ops   |  seg6_local.c      |+---------------------------+-------------------+--------------------+

路由系统隧道配置

LWTUNNEL系统的功能实现绑定在内核路由系统上。如下IP命令用于配置封装类型为IP或者GENEVE的轻量级隧道。

使用external关键字配置IP隧道与GENEVE隧道:
ip link add vxlan1 type vxlan dstport 4789 external
ip link set dev vxlan1 up
ip addr add 20.1.1.1/24 dev vxlan1
ip route add 10.1.1.1 encap ip id 30001 dst 20.1.1.2 dev vxlan1

ip link add gnv0 type geneve external
ip link set dev gnv0 up
ip route add 172.16.20.1/32 encap ip id 1234 dst 192.168.100.3 dev gnv0

不使用external配置vxlan与geneve隧道:
ip link add dev vxlan1 type vxlan id 30001 remote 20.1.1.2 dstport 4789
ip route add 10.1.1.1 dev vxlan1

ip link add dev gnv0 type geneve remote 192.168.100.3 vni 1234 dstport 5678           
ip route add 172.16.20.1 dev gnv0

与路由系统结合在一起的LWTUNNEL轻量级隧道,将隧道信息保存在路由系统中。内核中函数inet_rtm_newroute负责处理以上的ip route命令新添加的路由表项:其首先调用函数rtm_to_fib_config将netlink消息中的数据转换保存到内核的fib_config结构体中,成员fc_encap_type保存封装类型,成员fc_encap保存整个封装数据:

static int rtm_to_fib_config(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, struct fib_config *cfg, ...)
{nlmsg_for_each_attr(attr, nlh, sizeof(struct rtmsg), remaining) {switch (nla_type(attr)) {case RTA_ENCAP:cfg->fc_encap = attr;break;case RTA_ENCAP_TYPE:cfg->fc_encap_type = nla_get_u16(attr);err = lwtunnel_valid_encap_type(cfg->fc_encap_type, extack);break;}}
} 

其次调用fib_table_insert函数将路由表项插入到内核FIB表中,fib_create_info负责在此之前创建要插入的fib_info结构信息。其检查fib_config结构的成员fc_encap是否有值,有值表明配置了隧道封装信息。随即创建特定隧道封装的lwtunnel_state信息,并且保存在fib_info的成员fib_nh下一跳数据结构中,以供路由匹配到相应流量时使用。

struct fib_info *fib_create_info(struct fib_config *cfg, struct netlink_ext_ack *extack)
{    struct fib_nh *nh = fi->fib_nh;if (cfg->fc_encap) {struct lwtunnel_state *lwtstate;err = lwtunnel_build_state(cfg->fc_encap_type, cfg->fc_encap, AF_INET, cfg, &lwtstate, extack);nh->nh_lwtstate = lwtstate_get(lwtstate);}
}

以IP封装类型为例,其build_state函数为ip_tun_build_state,除了要分配通用的lwtunnel_state数据结构外,还分配了特定于IP封装的ip_tunnel_info结构体(紧随lwtunnel_state结构体空间之后),特定于IP封装类型的信息保存在ip_tunnel_info私有结构体中,如封装的id、dst等信息。每种特定的封装如有必要都会分配私有的数据结构,如MPLS封装,在mpls_build_state函数中,分配mpls_iptunnel_encap数据结构以及label所需空间。

static int ip_tun_build_state(...)
{struct ip_tunnel_info *tun_info;struct lwtunnel_state *new_state;new_state = lwtunnel_state_alloc(sizeof(*tun_info));new_state->type = LWTUNNEL_ENCAP_IP;tun_info = lwt_tun_info(new_state);if (tb[LWTUNNEL_IP_ID])tun_info->key.tun_id = nla_get_be64(tb[LWTUNNEL_IP_ID]);
}

轻量级隧道路由入口

进入本机的数据包,在经过NF_INET_PRE_ROUTING hook点之后,交由内核函数ip_rcv_finish处理,其通过查找路由表决定数据包的流向,即ip_route_input_noref函数查询入口路由,决定是送往本地应用还是转发。在查询到路由表项后使用__mkroute_input函数创建路由缓存结构rtable。

static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{if (!skb_valid_dst(skb)) {err = ip_route_input_noref(skb, iph->daddr, iph->saddr, iph->tos, dev);}return dst_input(skb);
}
static int __mkroute_input(struct sk_buff *skb, const struct fib_result *res, struct in_device *in_dev, __be32 daddr, __be32 saddr, u32 tos)
{rth->dst.input = ip_forward;rt_set_nexthop(rth, daddr, res, fnhe, res->fi, res->type, itag, do_cache);set_lwt_redirect(rth);skb_dst_set(skb, &rth->dst);
}

其中rt_set_nexthop函数,根据路由的下一跳信息,找到在上一节中使用ip route命令配置封装路由时,创建的lwtunnel_state结构。其保存有指定的封装类型相关的信息,赋值给路由缓存结构中的lwtstate成员。

static void rt_set_nexthop(...)
{if (fi) {struct fib_nh *nh = &FIB_RES_NH(*res);rt->dst.lwtstate = lwtstate_get(nh->nh_lwtstate);}
}

如果配置了路由相关的轻量级隧道,路由的结果出了本地上送和转发之后,第三条路就是重定向到轻量级隧道中,即函数set_lwt_redirect,如果此关联隧道为输入方向(设置了LWTUNNEL_STATE_INPUT_REDIRECT标志),将其input函数指针修改为隧道自身的输入函数lwtunnel_input,并且保留原有的input指针函数到orig_input成员中,对于像BPF类型的隧道,在其进行完bpf相关处理后,还会调用orig_input指针函数继续被重定向之前的接收流程。

static void set_lwt_redirect(struct rtable *rth)
{if (lwtunnel_output_redirect(rth->dst.lwtstate)) {rth->dst.lwtstate->orig_output = rth->dst.output;rth->dst.output = lwtunnel_output;}if (lwtunnel_input_redirect(rth->dst.lwtstate)) {rth->dst.lwtstate->orig_input = rth->dst.input;rth->dst.input = lwtunnel_input;}
}

最终,在路由查询完成之后,ip_rcv_finish函数调用路由指定的input函数dst_input,此处调用的为lwtunnel_input函数。

轻量级隧道路由出口

出口函数的设置位于出口路由的生成函数__mkroute_output中。即在查找到出口路由之后,生成rtable路由缓存项时,检查rtable下一跳中是否关联有轻量级隧道状态信息lwtunnel_state,如果存在并且此隧道为出口方向(置位LWTUNNEL_STATE_OUTPUT_REDIRECT),路由缓存结构的output指针指向隧道的lwtunnel_output函数。此过程与LWTUNNEL路由入口的处理方式一致,仅是方向上的差别。

static struct rtable *__mkroute_output(const struct fib_result *res, const struct flowi4 *fl4, ...)
{struct fib_info *fi = res->fi;struct fib_nh_exception *fnhe;rt_set_nexthop(rth, fl4->daddr, res, fnhe, fi, type, 0, do_cache);set_lwt_redirect(rth);
}

rth->dst.output函数由dst_output进行封装调用,对于源自本机的数据包,调用位置在NF_INET_LOCAL_OUT hook点之后,参见函数__ip_local_out,遍历完此hook点挂载的函数之后,调用dst_output,实际为调用lwtunnel_output函数。

int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,net, sk, skb, NULL, skb_dst(skb)->dev, dst_output);
}

对于转发的数据包,其调用位置位于函数ip_forward_finish中,在hook点NF_INET_FORWARD遍历完成之后调用。

int ip_forward(struct sk_buff *skb)
{return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD,net, NULL, skb, skb->dev, rt->dst.dev, ip_forward_finish);
}
static int ip_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{return dst_output(net, sk, skb);
}

传输类型XMIT

之前阐述的通过set_lwt_redirect函数重定向的轻量级隧道为INPUT或者OUTPUT类型,通过dst_input或者dst_output函数调用嫁接在内核网络协议栈中。此处的XMIT传输类型,直接编码在ip_finish_output2函数中,此处理位于NF_INET_POST_ROUTING hook点之后,目前仅有MPLS隧道在使用,其通过mpls_xmit函数为数据包增加MPLS标签,之后将数据包发送到网络中。

static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb)
{if (lwtunnel_xmit_redirect(dst->lwtstate)) {int res = lwtunnel_xmit(skb);if (res < 0 || res == LWTUNNEL_XMIT_DONE)return res;}
}

内核版本

Linux-4.15

Linux内核轻量级隧道相关推荐

  1. linux内核支持我vxlan,Linux内核轻量级隧道

    目前内核支持的封装类型由枚举类型lwtunnel_encap_types定义,如下所示支持MPLS.IP.ILA.IP6.SEG6.BPF和SEG6_LOCAL等7种类型.函数lwtunnel_val ...

  2. Linux内核的namespace机制分析

    1.  Linux内核namespace机制 Linux Namespaces机制提供一种资源隔离方案.PID,IPC,Network等系统资源不再是全局性的,而是属于某个特定的Namespace.每 ...

  3. UCloud基于Linux内核新特性的下一代外网网关设计及相关开源工作

    2019独角兽企业重金招聘Python工程师标准>>> UCloud外网网关是为了承载外网IP.负载均衡等产品的外网出入向流量,当前基于Linux内核的OVS/GRE tunnel/ ...

  4. linux 内核配置简介

    Gentoo Linux Gentoo内核(gentoo-sources)特有的选项 Gentoo Linux support CONFIG_GENTOO_LINUX 选"Y"后, ...

  5. linux学习笔记 linux内核6.0.2目录结构

    一.linux内核目录 arch 包含和硬件体系结构相关的代码,每种平台占一个相应的目录,如i386.arm.arm64.powerpc.mips等.Linux内核目前已经支持30种左右的体系结构.在 ...

  6. Windows程序员初学Linux内核(附Linux内核各版本历史纪年表)

    我是荔园微风,作为一名在IT界整整25年的老兵,最近受邀给年轻人讲了一场Windows内核和Linux内核相关的讲座.大家听得非常认真.下面我把其中一些PPT放上来和大家分享. Windows内核(右 ...

  7. Linux内核发展史和linux发行版

    参考链接:Linux内核发展史(1)和Linux内核简介.版本号.发布历史及发行版 一.巨人的肩膀 其实,除了之前提到的Minix系统外,Linux系统本身也是站在巨人的肩膀上,在它发布之前操作系统就 ...

  8. Linux内核中锁机制之完成量、互斥量

    在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等 ...

  9. Facebook 开源了一整套重要的 Linux 内核组件与工具!

    近日,Facebook 开源了一套解决重要计算集群管理问题的 Linux 内核组件和相关工具,这些项目覆盖了资源控制.资源利用.工作负载隔离.负载均衡.测量和监控等方面:BPF.Btrfs.Netco ...

最新文章

  1. Android---组件篇---Handler的使用(1)[转]
  2. 【体验】ESP32-CAM可能是最便宜的“监控”方案,ESP32-CAM程序下载调试
  3. 查看服务器cpu是否支持VT
  4. 基于 Java Web 的毕业设计选题管理平台--选题报告与需求规格说明书
  5. sqoop建表_Sqoop基础教程
  6. springboot的application.properties与.yml的区别
  7. 亚马逊E2主机的参数配置流程
  8. mimes.php,php – Laravel文件上传验证
  9. python函数局部变量_Python局部函数– functoolspartial()
  10. Python安装时import matplotlib.pyplot as plt报错
  11. 大战设计模式【10】—— 外观模式
  12. 出没干日月鸿蒙之内翻译,李白《大鹏遇希有鸟赋》原文及翻译赏析
  13. 域名被污染解决方法及DNS污染清洗方法
  14. Guava Splitter,Splitter与Java split的对比
  15. 文本文件和二进制文件的区别和联系
  16. 2022年1~6中国市场汽车销量可视化
  17. 什么是精灵图?如何使用精灵图
  18. BERT cased和uncased的区别
  19. 进程通信实例之父子进程通信
  20. 外贸电子商务网站的网络广告推广方案

热门文章

  1. 神经网络硕士就业前景,神经网络就业怎么样
  2. 经典消费者生产者问题
  3. python灰度图生成g代码_Artcam2009利用灰度图生成雕刻机所需的G代码
  4. php微信40037,微信公众号发送小程序模板消息提示40037?。
  5. 阿里大于短信服务API Demo及使用说明
  6. uniapp调用wx.scanQRCode()方法,安卓手机正常,苹果手机异常的问题
  7. 程序调试介绍及其使用
  8. 新世纪音乐顶尖作品(15首)
  9. C语言 计算e及e的次方的近似值
  10. git 切换远程分支