ebpf 网络过滤器实践

84 篇文章 0 订阅

一、前言

不要认为是一些小的demo而忽略对它的学习,往往一个复杂的代码,复杂的工程是一个个小demo拼接成的。

ebpf 网络过滤器这门技术在网络监控领域用的非常多,ebpf 编程我现在用的比较多的其实只有几个探针,kprobe、uprobe 以及linux网络过滤器。

今天读了 <<Linux内核观测技术BPF>>以及linux 内核源码中的sock_user1.c sock_kern.c ,linux内核的demo我认为写的还是很简单,而且这本书里面写的也很简单,打算对书里和linux内核的demo里的例子做一下实践。

内核代码很复杂,以后有空再看,今晚对linux 网络过滤器这块部分进行了实践。

心中一直有个疑问,好多网文都说他是cbpf的革新,那么cbpf filter 在pcap 中有过滤数据包能力,那ebpf是如何做到的呢?

二、实践

场景1:ebpf 统计网络包

ebpf程序

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
/* Packet types */

/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_LEGACY__
#define __BPF_LEGACY__

/* llvm builtin functions that eBPF C program may use to
 * emit BPF_LD_ABS and BPF_LD_IND instructions
 */
unsigned long long load_byte(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.byte");
unsigned long long load_half(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.half");
unsigned long long load_word(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.word");

#endif


#define PACKET_OUTGOING		4		/* Outgoing of any type */


#define ETH_ALEN	6		/* Octets in one ethernet addr	 */
#define ETH_TLEN	2		/* Octets in ethernet type field */
#define ETH_HLEN	14		/* Total octets in header.	 */
#define ETH_ZLEN	60		/* Min. octets in frame sans FCS */
#define ETH_DATA_LEN	1500		/* Max. octets in payload	 */
#define ETH_FRAME_LEN	1514		/* Max. octets in frame sans FCS */
#define ETH_FCS_LEN	4		/* Octets in the FCS		 */
 
char LICENSE[] SEC("license") = "Dual BSD/GPL";
 
struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__type(key, u32);
	__type(value, long);
	__uint(max_entries, 256);
} my_map SEC(".maps");

SEC("socket/test")
int bpf_prog1(struct __sk_buff *skb)
{
	int index = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol));
	long *value;

	if (skb->pkt_type != PACKET_OUTGOING)
		return -1;

	value = bpf_map_lookup_elem(&my_map, &index);
	if (value)
		__sync_fetch_and_add(value, skb->len);


	return 0;
}
char _license[] SEC("license") = "GPL";

编译ebpf程序:

/usr/bin/clang-14 -g -O2 -target bpf  -D__TARGET_ARCH_x86_64 -c kern1.c -o kernel_write.o

user 部分程序

其实很简单 创建一个链路套接字,然后把我们的bpf程序使用SO_ATTACH_BPF附着上去就可以了

common.h

/* SPDX-License-Identifier: GPL-2.0 */
#include <stdlib.h>
#include <stdio.h>
#include <linux/unistd.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <linux/if_packet.h>
#include <arpa/inet.h>

static inline int open_raw_sock(const char *name)
{
	struct sockaddr_ll sll;
	int sock;

	sock = socket(PF_PACKET, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, htons(ETH_P_ALL));
	if (sock < 0) {
		printf("cannot create raw socket\n");
		return -1;
	}

	memset(&sll, 0, sizeof(sll));
	sll.sll_family = AF_PACKET;
	sll.sll_ifindex = if_nametoindex(name);
	sll.sll_protocol = htons(ETH_P_ALL);
	if (bind(sock, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
		printf("bind to %s: %s\n", name, strerror(errno));
		close(sock);
		return -1;
	}

	return sock;
}

#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <bpf/bpf.h>
#include <assert.h>
#include <bpf/libbpf.h>
#include <net/ethernet.h> /* the L2 protocols */// ETH_P_ALL
#include <linux/if_packet.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/ip.h> // ip head
#include <netinet/tcp.h> // ip head
#include <netinet/udp.h> // udp head
#include <signal.h>

#include <string.h>

#include "common.h"


#define PACKET_SIZE 1000
#ifndef ETH_P_WSMP
#define ETH_P_WSMP	0x88DC
#endif
 
 
int main(int argc, char **argv)
{
    char msg[255];
    struct bpf_object *obj;
	struct bpf_program *prog;
	int map_fd, prog_fd;
	char filename[256];
	int i, sock, err;
	FILE *f;


	obj = bpf_object__open_file("kernel_write.o", NULL);
	if (libbpf_get_error(obj))
		return 1;

    prog = bpf_object__find_program_by_name(obj, "bpf_prog1");
	bpf_program__set_type(prog, BPF_PROG_TYPE_SOCKET_FILTER);

	err = bpf_object__load(obj);
	if (err)
		return 1;

	prog_fd = bpf_program__fd(prog);
	map_fd = bpf_object__find_map_fd_by_name(obj, "my_map");

	sock = open_raw_sock("lo");

	if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd,
			  sizeof(prog_fd))) {
        fprintf(stderr, "ERROR: bpf_program__attach failed\n");
        return -1;
    }

    f = popen("ping -4 -c5 localhost", "r");
	(void) f;

	for (i = 0; i < 5; i++) {
		long long tcp_cnt, udp_cnt, icmp_cnt;
		int key;

		key = IPPROTO_TCP;
		assert(bpf_map_lookup_elem(map_fd, &key, &tcp_cnt) == 0);

		key = IPPROTO_UDP;
		assert(bpf_map_lookup_elem(map_fd, &key, &udp_cnt) == 0);

		key = IPPROTO_ICMP;
		assert(bpf_map_lookup_elem(map_fd, &key, &icmp_cnt) == 0);

		printf("TCP %lld UDP %lld ICMP %lld bytes\n",
		       tcp_cnt, udp_cnt, icmp_cnt);
		sleep(1);
	}


	return 0;
 
}

编译程序:

g++ user.c -l bpf -o user

运行程序:

./user

运行结果:

root@zhanglei-HP-ZHAN-66-Pro-14-inch-G5-Notebook-PC:/home/zhanglei/data/ebpf-test# ./user 
TCP 0 UDP 0 ICMP 0 bytes
TCP 152 UDP 0 ICMP 196 bytes
TCP 304 UDP 0 ICMP 392 bytes
TCP 456 UDP 0 ICMP 588 bytes
TCP 1890 UDP 0 ICMP 784 bytes

结论:

ebpf 程序具有统计网络流量包的作用

场景2、不同的返回值对RAW SOCKET的影响

我们将user 部分程序进行修改,attach之后调用readfrom 从原始套接字上继续读数据,看到不同的返回值对recv 所产生的影响

user程序:

tcp.h:

//
// Created by root on 22-11-11.
//

#include <net/ethernet.h> /* the L2 protocols */// ETH_P_ALL
#include <linux/if_packet.h>
#include <linux/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/ip.h> // ip head
#include <netinet/tcp.h> // ip head
#include <netinet/udp.h> // udp head
#include <signal.h>
#include <stdlib.h>

#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
struct vlan_8021q_header {
    u_int16_t	priority_cfi_vid;
    u_int16_t	ether_type;
};


#include "tcp.h"
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <bpf/bpf.h>
#include <assert.h>
#include <bpf/libbpf.h>
#include <net/ethernet.h> /* the L2 protocols */// ETH_P_ALL
#include <linux/if_packet.h>
#include <linux/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/ip.h> // ip head
#include <netinet/tcp.h> // ip head
#include <netinet/udp.h> // udp head
#include <signal.h>

static int running = 1;

void sigint(int signum)
{
    if (!running){
        exit(EXIT_FAILURE);
    }
    running = 0;
}

void sigalarm(int signum)
{
    if (!running){
        exit(EXIT_FAILURE);
    }
    running = 0;
}
#define PACKET_SIZE 1000
#ifndef ETH_P_WSMP
#define ETH_P_WSMP	0x88DC
#endif

void print_string_hex(char *buf, unsigned short length)
{
    int i =0;
    printf("\n\t\t");
    for( i = 0; i < length; i++){
        printf(" %02X", (buf[i]&0xFF));
        if( (i + 1) % 16 == 0 )
        {
            printf("\n\t\t");
        }
    }
    printf("\n\n");
}

// 校验和函数
unsigned short csum(unsigned short *buf, int nwords)
{
    unsigned long sum;
    for(sum=0; nwords>0; nwords--)
        sum += *buf++;
    sum = (sum >> 16) + (sum &0xffff);
    sum += (sum >> 16);
    return (unsigned short)(~sum);
}





// 发送mac层数据
int main()
{
    struct sockaddr_ll stTagAddr;
    memset(&stTagAddr, 0 , sizeof(stTagAddr));
    stTagAddr.sll_family    = AF_PACKET;//填写AF_PACKET,不再经协议层处理
    stTagAddr.sll_protocol  = htons(ETH_P_ALL);

    int ret;
    struct ifreq req;
    int sd;
    sd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));//这个sd就是用来获取eth0的index,完了就关闭
    if(sd == -1)
    {
        printf("error to create socket pf_inet:%s\n", strerror(errno));
        return -1;
    }

    // todo 此处需要更改为你使用的网卡名称
    char *eth_name = "lo";
    strncpy(req.ifr_name, eth_name, strlen(eth_name));//通过设备名称获取index
    ret=ioctl(sd, SIOCGIFINDEX, &req);
    if(ret <0)
    {
        perror("SIOCGIFHWADDR");
        return -1;
    }

    // 获取mac地址
    struct ifreq if_mac;
    memset(&if_mac, 0, sizeof(struct ifreq));
    strncpy(if_mac.ifr_name, eth_name, strlen(eth_name));
    if (ioctl(sd, SIOCGIFHWADDR, &if_mac) < 0)
    {
        perror("SIOCGIFHWADDR");
        return -1;
    }


    close(sd);
    if (ret==-1)
    {
        printf("Get eth0 index err:%d=>%s \n", errno, strerror(errno));
        return -1;
    }

    // int SockFd = socket(PF_PACKET, SOCK_RAW, htons(VSTRONG_PROTOCOL));
    int SockFd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (-1 == SockFd)
    {
        printf("create socket error:%d.\n",errno);
        return -11;
    }

    struct sockaddr_ll s_addr;
    /* prepare sockaddr_ll */
    memset(&s_addr, 0, sizeof(s_addr));
    s_addr.sll_family   = AF_PACKET;
    s_addr.sll_protocol = htons(ETH_P_IP);
    s_addr.sll_ifindex  = req.ifr_ifindex;

    /* bind to interface */
    if (bind(SockFd, (struct sockaddr *) &s_addr, sizeof(s_addr)) == -1)
    {
        perror("error to bind:");
        exit(EXIT_FAILURE);
    }

    //处理信号
    /* enable signal */
    signal(SIGINT, sigint);
    signal(SIGALRM, sigalarm);


    int  recv = 0;
    uint8_t buff[PACKET_SIZE];
    printf("wait recv data\n");


    char msg[255];
    struct bpf_object *obj;
	struct bpf_program *prog;
	int map_fd, prog_fd;
	char filename[256];
	int i, sock, err;
	FILE *f;


	obj = bpf_object__open_file("kernel_write.o", NULL);
	if (libbpf_get_error(obj))
		return 1;

    prog = bpf_object__find_program_by_name(obj, "bpf_prog1");
	bpf_program__set_type(prog, BPF_PROG_TYPE_SOCKET_FILTER);

	err = bpf_object__load(obj);
	if (err)
		return 1;

	prog_fd = bpf_program__fd(prog);
	map_fd = bpf_object__find_map_fd_by_name(obj, "my_map");

	if (setsockopt(SockFd, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd,
			  sizeof(prog_fd))) {
        fprintf(stderr, "ERROR: bpf_program__attach failed\n");
        return -1;
    }


    while(1)
    {
        recv = recvfrom(SockFd, buff, PACKET_SIZE, 0, NULL, NULL);
        printf("recv :%d \n", recv);
        
    }


    close(SockFd);

}

ebpf程序

1、返回-1

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
/* Packet types */

/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_LEGACY__
#define __BPF_LEGACY__

/* llvm builtin functions that eBPF C program may use to
 * emit BPF_LD_ABS and BPF_LD_IND instructions
 */
unsigned long long load_byte(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.byte");
unsigned long long load_half(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.half");
unsigned long long load_word(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.word");

#endif


#define PACKET_OUTGOING		4		/* Outgoing of any type */


#define ETH_ALEN	6		/* Octets in one ethernet addr	 */
#define ETH_TLEN	2		/* Octets in ethernet type field */
#define ETH_HLEN	14		/* Total octets in header.	 */
#define ETH_ZLEN	60		/* Min. octets in frame sans FCS */
#define ETH_DATA_LEN	1500		/* Max. octets in payload	 */
#define ETH_FRAME_LEN	1514		/* Max. octets in frame sans FCS */
#define ETH_FCS_LEN	4		/* Octets in the FCS		 */
 
char LICENSE[] SEC("license") = "Dual BSD/GPL";
 
struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__type(key, u32);
	__type(value, long);
	__uint(max_entries, 256);
} my_map SEC(".maps");

SEC("socket/test")
int bpf_prog1(struct __sk_buff *skb)
{

	return -1;
}
char _license[] SEC("license") = "GPL";

读取数据包:

root@zhanglei-HP-ZHAN-66-Pro-14-inch-G5-Notebook-PC:/home/zhanglei/data/ebpf-test# ./user 
wait recv data
recv :86 
recv :66 
recv :86 
recv :66 
recv :374 
recv :374 
recv :384 
recv :66 
recv :1000 
recv :66 
recv :1000 
recv :73 
recv :384 
recv :66 
recv :1000 
recv :66 
recv :1000 
recv :73 
recv :66 
recv :86 
recv :66 
recv :86 
recv :66 

总结:

返回-1 并不会过滤链路套接字的任何数据包

2.返回0

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
/* Packet types */

/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_LEGACY__
#define __BPF_LEGACY__

/* llvm builtin functions that eBPF C program may use to
 * emit BPF_LD_ABS and BPF_LD_IND instructions
 */
unsigned long long load_byte(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.byte");
unsigned long long load_half(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.half");
unsigned long long load_word(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.word");

#endif


#define PACKET_OUTGOING		4		/* Outgoing of any type */


#define ETH_ALEN	6		/* Octets in one ethernet addr	 */
#define ETH_TLEN	2		/* Octets in ethernet type field */
#define ETH_HLEN	14		/* Total octets in header.	 */
#define ETH_ZLEN	60		/* Min. octets in frame sans FCS */
#define ETH_DATA_LEN	1500		/* Max. octets in payload	 */
#define ETH_FRAME_LEN	1514		/* Max. octets in frame sans FCS */
#define ETH_FCS_LEN	4		/* Octets in the FCS		 */
 
char LICENSE[] SEC("license") = "Dual BSD/GPL";
 
struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__type(key, u32);
	__type(value, long);
	__uint(max_entries, 256);
} my_map SEC(".maps");

SEC("socket/test")
int bpf_prog1(struct __sk_buff *skb)
{

	return 0;
}
char _license[] SEC("license") = "GPL";

然后运行user程序

root@zhanglei-HP-ZHAN-66-Pro-14-inch-G5-Notebook-PC:/home/zhanglei/data/ebpf-test# ./user 
wait recv data

总结:

如果返回0,那么他会过滤到链路套接字的数据包

3. 返回1

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
/* Packet types */

/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_LEGACY__
#define __BPF_LEGACY__

/* llvm builtin functions that eBPF C program may use to
 * emit BPF_LD_ABS and BPF_LD_IND instructions
 */
unsigned long long load_byte(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.byte");
unsigned long long load_half(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.half");
unsigned long long load_word(void *skb,
			     unsigned long long off) asm("llvm.bpf.load.word");

#endif


#define PACKET_OUTGOING		4		/* Outgoing of any type */


#define ETH_ALEN	6		/* Octets in one ethernet addr	 */
#define ETH_TLEN	2		/* Octets in ethernet type field */
#define ETH_HLEN	14		/* Total octets in header.	 */
#define ETH_ZLEN	60		/* Min. octets in frame sans FCS */
#define ETH_DATA_LEN	1500		/* Max. octets in payload	 */
#define ETH_FRAME_LEN	1514		/* Max. octets in frame sans FCS */
#define ETH_FCS_LEN	4		/* Octets in the FCS		 */
 
char LICENSE[] SEC("license") = "Dual BSD/GPL";
 
struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__type(key, u32);
	__type(value, long);
	__uint(max_entries, 256);
} my_map SEC(".maps");

SEC("socket/test")
int bpf_prog1(struct __sk_buff *skb)
{

	return 1;
}
char _license[] SEC("license") = "GPL";

运行结果:

root@zhanglei-HP-ZHAN-66-Pro-14-inch-G5-Notebook-PC:/home/zhanglei/data/ebpf-test# ./user 
wait recv data
recv :1 
recv :1 
recv :1 
recv :1 
recv :1 
recv :1 
recv :1 
recv :1 
recv :1 
recv :1 
recv :1 
recv :1

总结:

返回1 那么会造成recvfrom 只读到了1个字节,也就是说返回的数字,就代表recvfrom读到的字节数,如果是-1代表不过滤

三、总结

ebpf 网络过滤器具有统计监控以及数据过滤的功能,下次有空我会写个关于分类过滤器的调研

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值