专栏首页Serverless+使用 BPF 统计网络流量
原创

使用 BPF 统计网络流量

本文介绍使用 BPF 统计网络流量。网络流量是云产品的重要计费指标,服务器每秒可以处理上百万的数据包,这也要求有高效的方法来统计流量,而 BPF 最初作为网络包处理的技术,被设计和构造成可以支持这个速率的流量处理。

使用 libpcap

BPF 之前的时代,我们可以使用 libpcap 实现与 tcpdump 类似的方式,捕获网络流量并拷贝到用户程序中进行统计。

下面是一个简单的示例:

#define ETHERNET_HEADER_LEN 14
#define MIN_IP_HEADER_LEN 20
#define IP_HL(ip) (((ip)->ihl) & 0x0f)

const char* dev = "br-7e20abc6df31";
const char* filter_expr = "src net 172.20.0.0/16";

int64_t traffic = 0;

// Reference:
//      https://www.tcpdump.org/manpages/
//      https://tools.ietf.org/html/rfc791
void traffic_stat() {
    // find the IPv4 network number and netmask for a device
    bpf_u_int32 net = 0;
    bpf_u_int32 mask = 0;
    char errbuf[PCAP_ERRBUF_SIZE] = {0};
    int ret = pcap_lookupnet(dev, &net, &mask, errbuf);
    if (ret == PCAP_ERROR) {
        fprintf(stderr, "pcap_lookupnet failed: %s\n", errbuf);
        exit(EXIT_FAILURE);
    }

    // open a device for capturing
    int promisc = 1;
    int timeout = 1000;   // in milliseconds
    const int SNAP_LEN = 64;
    auto handle = pcap_open_live(dev, SNAP_LEN, promisc, timeout, errbuf);
    if (!handle) {
        fprintf(stderr, "pcap_open_live failed: %s\n", errbuf);
        exit(EXIT_FAILURE);
    }

    // compile a filter expression
    struct bpf_program fp;
    int optimize = 1;
    ret = pcap_compile(handle, &fp, filter_expr, optimize, net);
    if (ret == PCAP_ERROR) {
        fprintf(stderr, "pcap_compile failed: %s\n", pcap_geterr(handle));
        exit(EXIT_FAILURE);
    }

    // set the filter
    ret = pcap_setfilter(handle, &fp);
    if (ret == PCAP_ERROR) {
        fprintf(stderr, "pcap_setfilter failed: %s\n", pcap_geterr(handle));
        exit(EXIT_FAILURE);
    }

    // process packets from a live capture
    int packet_count = -1;  // -1 means infinity
    pcap_loop(handle, packet_count, [](u_char* args, const struct pcap_pkthdr* header, const u_char* bytes) {

        auto ip_header = reinterpret_cast<iphdr*>(const_cast<u_char*>(bytes) + ETHERNET_HEADER_LEN);
        const int ip_header_len = IP_HL(ip_header) * 4;
        if (ip_header_len < MIN_IP_HEADER_LEN) {
            return;
        }

        auto len = ntohs(ip_header->tot_len);
        if (len <= 0) {
            return;
        }

        auto traffic = reinterpret_cast<int64_t*>(args);
        *traffic += len;

    }, reinterpret_cast<u_char*>(&traffic));

    // free a BPF program
    pcap_freecode(&fp);
    // close the capture device
    pcap_close(handle);
}

br-7e20abc6df31 是使用 Docker 创建的网桥,IP 是 172.20.0.1,使用 src net 172.20.0.0/16 表达式过滤出站流量。

这个程序可以做正确的事情,即统计流量。问题在于,它需要把所有流经网卡的流量都拷贝到用户程序,然后在进行统计,而这些拷贝随后就被丢弃,浪费了大量系统资源。

大杀器 BPF

BPF 显然是这个问题的完美解决方案。我们需要的只是累计出站流量,如果能放到内核态执行,意味着我们不需要拷贝网络数据包。

下面是一个使用 libbpf 编写的程序,它的作用与上述 libpcap 的作用一致:

#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

int ifindex = 0;
__u64 traffic = 0;

SEC("tp_btf/netif_receive_skb")
int BPF_PROG(netif_receive_skb, struct sk_buff *skb)
{
    if (skb->dev->ifindex == ifindex) {
        traffic += skb->data_len;
    }
    return 0;
}

char LICENSE[] SEC("license") = "GPL";

这里,ifindex 即上述网络设备 br-7e20abc6df31 的接口索引。

在这个程序中,我们把它挂载在了 netif_receive_skb 这个 tracepoint 上,网卡接收到数据包后,我们判断是否是目的设备,若是,则累加流量。没有数据拷贝,没有上下文切换,简单高效。

实战

使用 dd 创建一个 512M 大小的文件:

$ dd if=/dev/zero of=/tmp/testfile bs=4096 count=131072

创建一个新的网络设备:

$ sudo docker network create my-tc-net

使用上面创建的网络运行一个 nginx 服务器,提供文件下载:

$ sudo docker run -d --rm \
        -p 10086:80 \
        -v /tmp/testfile:/home/data/testfile \
        -v $(PWD)/default.conf:/etc/nginx/conf.d/default.conf \
        --name my-nginx \
        --network my-tc-net \
        nginx:alpine

这是我们的服务器配置:

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    location /downloads/ {
        alias               /home/data/;
    }
}

运行上述的流量统计程序:

$ sudo ./trafficstat

下载文件:

$ curl http://localhost:10086/downloads/testfile --output testfile

查看流量统计输出:

$ sudo ./trafficstat
...
traffic in bytes: 536878816
...

符合预期,大功告成。本文源代码可在这里找到。

结论

本文通过实例演示了使用 libbpf 编写 BPF 程序,实现在内核态高效统计网络流量的方案。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【云原生技术研究】BPF使能软件定义内核

    BPF通过一种软件定义的方式,将内核的行为和数据暴露给用户空间,开发者可以通过在用户空间编写BPF程序,加载到内核空间执行,进而实现对内核行为的灵活管理和控制

    绿盟科技研究通讯
  • 全面介绍eBPF-概念

    前面介绍了BCC可观测性和BCC网络,但对底层使用的eBPF的介绍相对较少,且官方欠缺对网络方面的介绍。下面对eBPF进行全面介绍。

    charlieroro
  • FFShark-一种可以监控全世界网络的100G可编程智能NIC

    最近几天,老美又加大了对华为的约束,几乎将华为逼上绝路。老美之所以费尽心机的制裁华为,最重要的原因就是以5G为代表的网络战略地位的抢夺。网络,已不仅仅是数据通...

    网络交换FPGA
  • 一种可以监控全世界网络的可编程智能NIC

    最近几天,老美又加大了对华为的约束,几乎将华为逼上绝路。老美之所以费尽心机的制裁华为,最重要的原因就是以5G为代表的网络战略地位的抢夺。网络,已不仅仅是数据通...

    网络交换FPGA
  • eBPF技术简介

    “eBPF 是我见过的 Linux 中最神奇的技术,没有之一,已成为 Linux 内核中顶级子模块,从 tcpdump 中用作网络包过滤的经典 cbpf,到成为...

    CNCF
  • Linux超能力BPF技术介绍及学习分享(技术创作101训练营)

    近两年BPF技术跃然成为了一项热门技术,在刚刚结束的KubeCon 2020 Europe会议上有7个关于BPF的技术分享, 而在KubeCon 2020 Ch...

    nevermosby
  • 为容器时代设计的高级 eBPF 内核特性(FOSDEM, 2021)

    本文翻译自 2021 年 Daniel Borkmann 在 FOSDEM 的一篇分享:Advanced eBPF kernel features for th...

    米开朗基杨
  • eBPF文章翻译(1)—eBPF介绍

    Brendan Gregg,他在2017年的linux.conf.au大会上的演讲提到「内核虚拟机eBPF」,表示,”超能力终于来到了Linux操作系统“。让e...

    nevermosby
  • 大规模微服务利器:eBPF + Kubernetes 介绍

    本文翻译自 2020 年 Daniel Borkmann 在 KubeCon 的一篇分享: eBPF and Kubernetes: Little Helper...

    CNCF
  • 你的第一个XDP BPF 程序

    https://github.com/nevermosby/linux-bpf-learning

    nevermosby
  • 你的第一个TC BPF 程序

    https://github.com/nevermosby/linux-bpf-learning

    nevermosby
  • 首届eBPF.io Summit 2020见闻

    第一次在Thomas的Twitter上看到要办eBPF技术大会的时候,兴奋不已,作为一个已经学习BPF技术大半年并且还在持续学习中的爱好者,就像粉丝看到偶像要办...

    nevermosby
  • 基于eBPF的微服务网络安全(Cilium 1)

    翻译自:Network security for microservices with eBPF

    charlieroro
  • BPF数据传递的桥梁——BPF Map(一)

    https://github.com/nevermosby/linux-bpf-learning/tree/master/bpf/bpf-maps

    nevermosby
  • 使用Cilium增强Istio|通过Socket感知BPF程序

    8月1日凌晨,Istio 1.0发布,已生产就绪! Cilium社区感谢所有Istio贡献者为此付出的巨大努力。我们很幸运能够参与社区活动,为Istio做出贡献...

    CNCF
  • 调试你的BPF程序

    文章涉及的实验环境和代码可以到这个git repo获取: https://github.com/nevermosby/linux-bpf-learning

    nevermosby
  • 基于 eBPF 实现容器运行时安全

    BPF 全称是「Berkeley Packet Filter」,中文翻译为「伯克利包过滤器」。它源于 1992 年伯克利实验室,Steven McCanne 和...

    灵雀云
  • 绕过conntrack,使用eBPF增强 IPVS优化K8s网络性能

    作者范建明、洪志国、张浩,均为腾讯云容器产品中心高级工程师,负责容器网络和Service Mesh,容器Runtime,调度等相关研发工作。

    腾讯云原生
  • eBPF在android上的使用

    eBPF是extended BPF的缩写,而BPF是Berkeley Packet Filter的缩写。对linux网络比较熟悉的伙伴对BPF应该比较了解,它通...

    刘盼

扫码关注云+社区

领取腾讯云代金券