第一部分 前置:
大概两个月前, 有幸读到了csdn大神dog250写的一篇有关tun的文章, 里面详细介绍了如何演变tun读写的架构, 感觉非常厉害, 但是又有很多迷惑, 尤其是他提到了需要使用ebpf来开启tun的steering的功能, 那就更是一无所知了. 感兴趣的同学可以看下他的原文, 链接如下.
tun虚拟网卡该怎么玩不该怎么玩_Netfilter,iptables/OpenVPN/TCP guard:-(-优快云博客
虽然不是很懂, 但我还是硬着头皮在高版本linux内核环境中ioctl TUNSETSTEERINGEBPF功能, 虽然编译不会报错, 但是启动tun测试程序的时候会失败. 好吧, 只能搜索ebpf的相关资料了. 断断续续搜索了一个多月, 一直无法入门, 那就只能看linux内核里的ebpf测试用例了.
下载内核源码, 编译bpf测试用例. 当然这个也花了不少时间, 为了后来的同学能有个参考, 同样也写了一篇博客, 见以下链接:
前几天又大概看了下tun的源码, 发现里面有个函数(见下图), 突然就有点顿悟了, 那就根据测试用例写ebpf的测试程序吧.
第二部分 源码:
代码分为两个文件, 一个是tun_steer_user.c, 一个是tun_steer_kern.c, 详细代码如下:
tun_steer_user.c:
/*
tun_steer_user.c
打开两个tun队列
两个线程轮询read(fd, data, data_len), 成功后write(fd, data, data_len)
数据就是ping数据
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <linux/bpf.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <linux/if_tun.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
typedef struct mixer
{
int index;
int fd;
char *msg;
}mixer_t;
static int create_tun(const char *tun_name, int *fds, int fds_size, const int *prog)
{
int i = 0;
for(; i < fds_size; ++i)
{
struct ifreq ifr;
int fd = 0;
if ((fd = open("/dev/net/tun", O_RDWR)) == -1)
{
printf("open tun failed: %s\n", strerror(errno));
return -1;
}
ifr.ifr_flags = IFF_NO_PI | IFF_MULTI_QUEUE | IFF_TUN;
strcpy(ifr.ifr_name, tun_name);
if (ioctl(fd, TUNSETIFF, (void *) &ifr) == -1)
{
printf("ioctl tun failed: %s\n", strerror(errno));
return -1;
}
if (ioctl(fd, TUNSETSTEERINGEBPF, (void *)prog) == -1)
{
printf("ioctl tun TUNSETSTEERINGEBPF failed: %s\n", strerror(errno));
return -1;
}
fds[i] = fd;
}
return 0;
}
static void *ping_pong(void *arg)
{
mixer_t *m = (mixer_t *)arg;
int ret = 0, tun = m->fd, index = m->index;
unsigned char buf[256] = {};
for(;;)
{
unsigned char ip[4];
ret = read(tun, buf, sizeof(buf));
if (ret < 0)
{
printf("exit\n");
break;
}
memcpy(ip, &buf[12], 4);
memcpy(&buf[12], &buf[16], 4);
memcpy(&buf[16], ip, 4);
buf[20] = 0;
*((unsigned short*)&buf[22]) += 8;
if (buf[0] != 0x60)
{
printf("%s read from fd: %d, index: %d\n", m->msg, tun, index);
}
ret = write(tun, buf, ret);
}
return NULL;
}
int main(int ac, char **argv)
{
struct bpf_object *obj;
int prog_fd;
char filename[256];
int fds[2];
snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
if (bpf_prog_load(filename, BPF_PROG_TYPE_SOCKET_FILTER, &obj, &prog_fd))
{
return -1;
}
if (create_tun("tun0", fds, sizeof(fds)/sizeof(int), &prog_fd))
{
return -1;
}
FILE *f = popen("/usr/sbin/ifconfig tun0 10.10.0.1 pointopoint 10.10.0.2", "r");
fclose(f);
f = popen("/usr/sbin/route add -net 10.10.0.0 netmask 255.255.0.0 gw 10.10.0.2", "r");
fclose(f);
mixer_t sub;
sub.index = 0;
sub.fd = fds[0];
sub.msg = "sub thread";
pthread_t pid;
pthread_create(&pid, NULL, ping_pong, (void *)&sub);
mixer_t mai;
mai.index = 1;
mai.fd = fds[1];
mai.msg = "main thread";
ping_pong((void *)&mai);
return 0;
}
tun_steer_kern.c:
/*
tun_steer_kern.c
获取目的地址, 并根据最后两个字节的值返回不同的值;
比如: 10.10.0.6, 最后两个字节是6, 返回0.
*/
#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/if_packet.h>
#include <uapi/linux/ip.h>
#include "bpf_helpers.h"
SEC("tun_steer")
int bpf_prog1(struct __sk_buff *skb)
{
u32 nhoff = skb->cb[0];
u32 daddr = load_word(skb, nhoff + offsetof(struct iphdr, daddr));
u32 ret = daddr & 0x0000ffff;
if (ret < 0x0010)
{
return 0;
}
return 1;
}
char _license[] SEC("license") = "GPL";
第三部分 编译:
在bpf的Makefile里添加4行代码
hostprogs-y += tracex6
hostprogs-y += tracex7
hostprogs-y += tun_steer // 第一行, 可以根据上面两行找到位置
tracex6-objs := bpf_load.o tracex6_user.o
tracex7-objs := bpf_load.o tracex7_user.o
tun_steer-objs := bpf_load.o tun_steer_user.o // 第二行
always += tracex6_kern.o
always += tracex7_kern.o
always += tun_steer_kern.o // 第三行
HOSTLOADLIBES_test_overhead += -lrt
HOSTLOADLIBES_xdpsock += -pthread
HOSTLOADLIBES_tun_steer += -pthread // 第四行
Makefile规则已更改, 可以编译了.
第四部分 测试:
在本机打开另一个终端
1, 先ping 10.10.0.6, 查看打印
2, 再ping 10.10.0.15, 查看打印
3, 再ping 10.10.0.16, 查看打印
4, 再ping 10.10.0.30, 查看打印
结果如下:
可以看到, 开启tun steer功能后, 内核代码返回的值, 可以决定tun的哪个队列有数据.
结束.