首页 > 编程知识 正文

gtp隧道协议,GTP隧道

时间:2023-05-03 08:16:05 阅读:185270 作者:53

       网络开发有很多种方式/工具,细细想想,罗列下来应该主要可分为下面几类。

硬件类
       专用集成电路:以交换芯片为代表的固定处理逻辑的Asic(Application Specific Integrated Circuit)
       可编程硬件:如FPGA,NP;还有比较新的支持openflow的broadcom OF-DPA,支持P4的barefoot Tofino 。软件类
       内核态:驱动、TCP/IP协议栈、XDP
       用户态:socket、tun、tan
       用户/内核结合:DPDK、Netmap
       相对于其他的网络开发方式/工具,XDP出现的时间较晚。所以先对XDP简单的给与介绍。 XDP简介

       XDP (eXpress Data Path),最早出现在Linux内核4.8版本,其基于eBPF实现。说到eBPF,就要引入这一切的开始BPF(Berkeley Packet Filter, 伯克利包过滤器)。BPF,很早就引入了到Linux内核。对于我或可能大部分人来说,第一次听到BPF,估计是使用tcpdump、libpcap等之类与网络抓包BPF语法。如下面

'host 192.168.0.1 and (port 80 or port 8080)'

       BPF最开始的用处也的确在此,通常情况抓包是分了分析定位某个网络应用的通讯问题,即不是关心网卡上的所有流量。如果要从大量网络报文中,筛选出关系的网络数据。一种方式是内核把所有网络报文都发给用户态,有用户态程序进行筛选。这种方法缺点很明显,每个报文都要经过内核到用户态的内存拷贝。所以,BPF的方式,提供一种过滤式,过滤式会动态编码成一种BPF目标码(中间码),最后内核BPF虚机完成BPF目标码的执行,即完成报文过滤的功能。BPF只把符合过滤条件的报文上送到用户态。

       上述介绍的BPF,现称为classical BPF。引用wiki的资料。
       “eBPF由Alexei Starovoitov在PluMgrid工作时设计,这家公司专注于研究新的方法来设计软件定义网络解决方案。在它只是一个提议时,Daniel Borkmann——Red Hat公司的内核工程师,帮助修改使得它能够进入内核代码并完全替代已有的BPF实现。这是二十年来BPF首次主要的更新,使得BPF成为了一个通用的虚拟机。”
       到此,Linux内核给开发者开了一扇窗户。这扇窗户不仅提高了系统的观察性,例如kprobes、uprobes、tracepoints以及perf_events的支持。(注:个人观点,ebpf对系统可观察性的贡献意义远大于网络处理)。
       下面这张图,就很好的介绍了XDP的组成和总体处理过程。

1) 在用户态写好BPF代码,编译成BPF object
2) BPF loader把BPF下载到BPF虚机(此虚机非Kvm虚机,个人理解为与jvm同类的)
3) 不同的action code返回码,定义了报文的后续处理方式:
XDP_PASS:报文继续上送到协议栈
XDP_DROP: 丢包
XDP_ABORTED:丢包且tracepoint exception
XDP_TX: 将报文从接收的网卡转发
XDP_REDIRECT: 报文重定向到另一个网卡或发送到用户态AF_XDP socke
       细心的你,是否发现XDP只能处理接收方向的报文,对本机发送方向的报文,无法做处理。这一点似乎就没有netfilter灵活。但如果作为一个中间网络转发设备来说,XDP只处理接收方向的报文,似乎也没什么问题。下面就以移动核心网UPF处理GTPU隧道的处理做背景,XDP应用的简单实例介绍。代码在https://github.com/801room/upf-xdp.完全的玩具代码,但基本的技术点和方案应该体现出来了。

简单实例


       代码分了两部分。一部分为go编写,将BPF object加载到内核并绑定到指定的网卡上,并可设置map与bpf object交互,其作为控制面。

err := bpf.LoadElf(elf)…xdp := bpf.GetProgramByName("upf_input")…err = xdp.Attach(iface)

另一部分,为c编写,将其编译成BPF object,作为数据面。

SEC("xdp")int upf_input(struct xdp_md *ctx){ void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; return eth_handle(ctx, eth);}

       编译器会将SEC(”xdp”)编译成elf中的一个段,bpf.GetProgramByName会寻找此段,并找到函数入口。
       后面代码从二层逐层处理,倒是没有特别要说明的。需要注意的是,xdp里面不能有没有边界的循环,不能有全局变量,访问报文内容时先校验访问的有效边界。例如下面这段代码会加载bpf时候报错。

static u32 eth_handle(struct xdp_md *ctx, struct ethhdr *ethh){ u16 eth_type; u64 offset; struct vlan_hdr *vlan_hdr; eth_type = htons(ethh->h_proto); switch(eth_type) { ... }}

需要修改成这个代码,访问前先判断是否越界。

static u32 eth_handle(struct xdp_md *ctx, struct ethhdr *ethh){ void *data_end = (void *)(long)ctx->data_end; u16 eth_type; u64 offset; struct vlan_hdr *vlan_hdr; offset = sizeof(*ethh); if((void *)ethh + offset > data_end) { bpf_debug("Cannot parse L2n"); return XDP_PASS; } eth_type = htons(ethh->h_proto); switch(eth_type) { ... }}

       最后,要说的是bpf_xdp_adjust_head(),此函数可以修改报文头,push或pull。具体的bpf相关函数定义见。
https://man7.org/linux/man-pages/man7/bpf-helpers.7.html

小结

       Xdp相对P4来说,对与Linux网络开发人员,门槛和学习曲线要低很多。对于简单的转发和ACL之类的功能。用xdp即快又简单。如果是隧道封装/解封之类的处理,xdp应该也比较得心应手。
       而就如P4一样,对于深度报文解析和需要复杂策略控制,可能就有点困难了。笔者最终目标是写一个UPF,经过P4和XDP简单调研之后,还是乖乖的拥抱VPP吧。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。