0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

如何用eBPF實現(xiàn)一個學習型網(wǎng)橋

Linux閱碼場 ? 來源:未知 ? 2019-11-28 16:56 ? 次閱讀

eBPF技術風靡當下,eBPF字節(jié)碼正以星火燎原之勢被HOOK在Linux內(nèi)核中越來越多的位置,在這些HOOK點上,我們可以像編寫普通應用程序一樣編寫內(nèi)核的HOOK程序,與以往為了實現(xiàn)一個功能動輒patch一整套邏輯框架代碼(比如Netfilter)相比,eBPF的工作方式非常靈活。

我們先來看一下目前eBPF的一些重要HOOK點:


將來這個is_XXX序列肯定會不斷增加,布滿整個內(nèi)核(有點密集恐懼癥癥狀了...)。

本文將描述如何用eBPF實現(xiàn)一個學習型網(wǎng)橋的快速轉發(fā),并將其部署在XDP。

在開始之前,為了讓所有人都能看懂本文,我們先來回顧一些前置知識,如果暫時還不懂這些前置知識,沒關系,先把程序run起來是一個很好的起點,如果到時候你覺得沒意思,再放棄也不遲。

前置知識

什么是BPF和eBPF

簡單來講,BPF是一套完整的 計算機體系結構 。和x86,ARM這些類似,BPF包含自己的指令集和運行時邏輯,同理,就像在x86平臺編程,最終要落實到x86匯編指令一樣,BPF字節(jié)碼也可以看成是匯編指令的序列。我們通過tcpdump的-d/-dd參數(shù)可見一斑:

[root@localhost ~]# tcpdump -i any tcp and host 1.1.1.1 -d

(000) ldh [14]

(001) jeq #0x86dd jt 10 jf 2

(002) jeq #0x800 jt 3 jf 10

(003) ldb [25]

(004) jeq #0x6 jt 5 jf 10

(005) ld [28]

(006) jeq #0x1010101 jt 9 jf 7

(007) ld [32]

(008) jeq #0x1010101 jt 9 jf 10

(009) ret #262144

(010) ret #0

[root@localhost ~]#

BPF的歷史非常古老,早在1992年就被構建出來了,其背后的思想是, “與其把數(shù)據(jù)包復制到用戶空間執(zhí)行用戶態(tài)程序過濾,不如把過濾程序灌進內(nèi)核去。”

遺憾的是,BPF后來并沒有大行其道,只是被應用于非常有限的并不起眼的比如抓包層面。因此,由于它的語法并不復雜,人們直接手寫B(tài)PF匯編指令碼經(jīng)簡單封裝即可生成最終的字節(jié)碼。

當人們認識到BPF非常強壯的功能并準備將其大用時,指令系統(tǒng)以及操作系統(tǒng)內(nèi)核均已經(jīng)持續(xù)進化了好多年,這意味著簡單的BPF不能再滿足需要,它需要 “被復雜化” 。

于是就出現(xiàn)了eBPF,即extended BPF??傮w而言,eBPF相比BPF有了以下改進:1. 更復雜的指令系統(tǒng)。2. 更多可調用的函數(shù)。3. ...詳情可參見下面的鏈接:https://lwn.net/Articles/740157/

就像匯編語言進化到C語言一樣,直接手寫eBPF字節(jié)碼顯得即笨拙又低效,于是人們開始使用C語言直接編寫eBPF程序,然后用編譯器將其編譯成eBPF字節(jié)碼。遺憾的是,目前eBPF體系結構還不被gcc支持,不過很快就會支持了。我們不得不使用 特定的編譯器 來編譯eBPF的C代碼,比如clang。

什么是XDP

XDP,即eXpress Data Path,它其實是位于網(wǎng)卡驅動程序里的一個快速處理數(shù)據(jù)包的HOOK點,為什么快?基于以下兩點:

數(shù)據(jù)包處理位置非常底層,避開了很多內(nèi)核skb處理開銷。

可以將很多處理邏輯Offload到網(wǎng)卡硬件。

顯而易見,在XDP這個HOOK點灌進來一點eBPF字節(jié)碼,將是一件令人愉快的事情。

學習型網(wǎng)橋

Linux的Bridge模塊就是一個學習型網(wǎng)橋,其實就是一個現(xiàn)代交換式以太網(wǎng)交換機,它可以從端口學習到MAC地址,在內(nèi)部生成MAC/端口映射表,以優(yōu)化轉發(fā)效率。

本文我們將用eBPF實現(xiàn)的網(wǎng)橋就是一個學習型網(wǎng)橋,并且它的數(shù)據(jù)路徑和控制路徑相分離,用eBPF字節(jié)碼實現(xiàn)的正是其數(shù)據(jù)路徑,它將被灌入XDP,而控制路徑則由一個用戶態(tài)程序實現(xiàn)。

如何編譯eBPF程序

理論的學習自在平時,當打開電腦的時候,最快的速度run起來一些東西令人愉悅。我們不想花大量的時間在環(huán)境的搭建上。對于eBPF程序,內(nèi)核源碼樹的samples/bpf目錄將是一個非常好的起點。

以我自己的環(huán)境為例,我使用的是Ubuntu 19.10發(fā)行版,5.3.0-19-generic內(nèi)核,安裝源碼后,編譯之,最后編譯samples/bpf即可:

root@zhaoya-VirtualBox:/usr/src/linux-source-5.3.0/linux-source-5.3.0/samples/bpf# make

make -C ../../ /usr/src/linux-source-5.3.0/linux-source-5.3.0/samples/bpf/ BPF_SAMPLES_PATH=/usr/src/linux-source-5.3.0/linux-source-5.3.0/samples/bpf

make[1]: Entering directory '/usr/src/linux-source-5.3.0/linux-source-5.3.0'

CALL scripts/checksyscalls.sh

CALL scripts/atomic/check-atomics.sh

DESCEND objtool

...

samples/bpf目錄下的代碼都是比較典型的范例,我們照貓畫虎就能實現(xiàn)我們想要的功能。

大體上,每一個范例均由兩個部分組成:

XXX_kern.c文件:eBPF字節(jié)碼本身。

XXX_user.c文件:用戶態(tài)控制程序,控制eBPF字節(jié)碼的注入,更新。

即然我們要實現(xiàn)一個網(wǎng)橋,那么文件名我們可以確定為:

xdpbridgekern.c

xdpbridgeuser.c

同時我們修改Makefile文件,加入這兩個文件即可:

root@zhaoya-VirtualBox: samples/bpf# cat Makefile

...

hostprogs-y += xdp2

hostprogs-y += xdp_bridge

hostprogs-y += xdp_router_ipv4

...

xdp_bridge-objs := xdp_bridge_user.o

xdp_router_ipv4-objs := xdp_router_ipv4_user.o

...

always += xdp2_kern.o

always += xdp_bridge_kern.o

always += xdp_router_ipv4_kern.o

網(wǎng)橋XDP快速轉發(fā)的實現(xiàn)

對上述前置知識有了充分的理解之后,代碼就非常簡單了,我們剩下的工作就是填充xdpbridgekern.c和xdpbridgeuser.c兩個C文件,然后make它們。

我們先來看xdpbridgekern.c文件:

// xdp_bridge_kern.c

#include

#include

#include "bpf_helpers.h"

// mac_port_map保存該交換機的MAC/端口映射

struct bpf_map_def SEC("maps") mac_port_map = {

.type = BPF_MAP_TYPE_HASH,

.key_size = sizeof(long),

.value_size = sizeof(int),

.max_entries = 100,

};

// 以下函數(shù)是網(wǎng)橋轉發(fā)路徑的eBPF主函數(shù)實現(xiàn)

SEC("xdp_br")

int xdp_bridge_prog(struct xdp_md *ctx)

{

void *data_end = (void *)(long)ctx->data_end;

void *data = (void *)(long)ctx->data;

long dst_mac = 0;

int in_index = ctx->ingress_ifindex, *out_index;

// data即數(shù)據(jù)包開始位置

struct ethhdr *eth = (struct ethhdr *)data;

char info_fmt[] = "Destination Address: %lx Redirect to:[%d] From:[%d] ";

// 畸形包必須丟棄,否則無法通過內(nèi)核的eBPF字節(jié)碼合法性檢查

if (data + sizeof(struct ethhdr) > data_end) {

return XDP_DROP;

}

// 獲取目標MAC地址

__builtin_memcpy(&dst_mac, eth->h_dest, 6);

// 在MAC/端口映射表里查找對應該MAC的端口

out_index = bpf_map_lookup_elem(&mac_port_map, &dst_mac);

if (out_index == NULL) {

// 如若找不到,則上傳到慢速路徑,必要時由控制路徑更新MAC/端口表項。

return XDP_PASS;

}

// 非Hairpin下生效

if (in_index == *out_index) { // Hairpin ?

return XDP_DROP;

}

// 簡單打印些調試信息

bpf_trace_printk(info_fmt, sizeof(info_fmt), dst_mac, *out_index, in_index);

// 轉發(fā)到出端口

return bpf_redirect(*out_index, 0);

}

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

這里有必要說一下內(nèi)核對eBPF程序的合法性檢查,這個檢查一點都不多余,它確保你的eBPF代碼是安全的。這樣才不會造成內(nèi)核數(shù)據(jù)結構被破壞掉,否則,如果任意eBPF程序都能注入內(nèi)核,那結局顯然是細思極恐的。

現(xiàn)在繼續(xù)我們的用戶態(tài)C代碼:

// xdp_bridge_user.c

#include

#include

#include

#include

#include

#include

#include

#include "bpf_util.h"

int flags = XDP_FLAGS_UPDATE_IF_NOEXIST;

static int mac_port_map_fd;

static int *ifindex_list;

// 退出時卸載掉XDP的eBPF字節(jié)碼

static void int_exit(int sig)

{

int i = 0;

for (i = 0; i < 2; i++) {

bpf_set_link_xdp_fd(ifindex_list[i], -1, 0);

}

exit(0);

}

int main(int argc, char *argv[])

{

int sock, i;

char buf[1024];

char filename[64];

static struct sockaddr_nl g_addr;

struct bpf_object *obj;

struct bpf_prog_load_attr prog_load_attr = {

// prog_type指明eBPF字節(jié)碼注入的位置,我們網(wǎng)橋的例子中當然是XDP

.prog_type = BPF_PROG_TYPE_XDP,

};

int prog_fd;

snprintf(filename, sizeof(filename), "xdp_bridge_kern.o");

prog_load_attr.file = filename;

// 載入eBPF字節(jié)碼

if (bpf_prog_load_xattr(&prog_load_attr, &obj, &prog_fd)) {

return 1;

}

mac_port_map_fd = bpf_object__find_map_fd_by_name(obj, "mac_port_map");

ifindex_list = (int *)calloc(2, sizeof(int *));

// 我們的例子中僅僅支持兩個端口的網(wǎng)橋,事實上可以多個。

ifindex_list[0] = if_nametoindex(argv[1]);

ifindex_list[1] = if_nametoindex(argv[2]);

for (i = 0; i < 2/*total */; i++) {

// 將eBPF字節(jié)碼注入到感興趣網(wǎng)卡的XDP

if (bpf_set_link_xdp_fd(ifindex_list[i], prog_fd, flags) < 0) {

printf("link set xdp fd failed ");

return 1;

}

}

signal(SIGINT, int_exit);

bzero(&g_addr, sizeof(g_addr));

g_addr.nl_family = AF_NETLINK;

g_addr.nl_groups = RTM_NEWNEIGH;

if ((sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) {

int_exit(0);

return -1;

}

if (bind(sock, (struct sockaddr *) &g_addr, sizeof(g_addr)) < 0) {

int_exit(0);

return 1;

}

// 持續(xù)監(jiān)聽socket,捕獲Linux網(wǎng)橋上傳的notify信息,從而更新,刪除eBPF的map里特定的MAC/端口表項

while (1) {

int len;

struct nlmsghdr *nh;

struct ndmsg *ifimsg ;

int ifindex = 0;

unsigned char *cmac;

unsigned long lkey = 0;

len = recv(sock, buf, sizeof(buf), 0);

if (len <= 0) continue;

for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {

ifimsg = NLMSG_DATA(nh) ;

if (ifimsg->ndm_family != AF_BRIDGE) {

continue;

}

// 獲取notify信息中的端口

ifindex = ifimsg->ndm_ifindex;

for (i = 0; i < 2; i++) {

if (ifindex == ifindex_list[i]) break;

}

if (i == 2) continue;

// 獲取notify信息中的MAC地址

cmac = (unsigned char *)ifimsg + sizeof(struct ndmsg) + 4;

memcpy(&lkey, cmac, 6);

if (nh->nlmsg_type == RTM_DELNEIGH) {

bpf_map_delete_elem(mac_port_map_fd, (const void *)&lkey);

printf("Delete XDP bpf map-[HW Address:Port] item Key:[%lx] Value:[%d] ", lkey, ifindex);

} else if (nh->nlmsg_type == RTM_NEWNEIGH) {

bpf_map_update_elem(mac_port_map_fd, (const void *)&lkey, (const void *)&ifindex, 0);

printf("Update XDP bpf map-[HW Address:Port] item Key:[%lx] Value:[%d] ", lkey, ifindex);

}

}

}

}

用戶態(tài)程序同樣很容易理解。

數(shù)據(jù)面和控制面分離,這是網(wǎng)絡設備的標準路數(shù),幾十年前就這樣了,如今我們也能簡單實現(xiàn)一個了,很有趣不是嗎?

run起來

執(zhí)行make之后,我們可以得到可執(zhí)行文件xdpbridge以及eBPF字節(jié)碼文件xdpbridge_kern.o,在當前目錄下直接執(zhí)行即可:

root@zhaoya-VirtualBox:samples/bpf# ./xdp_bridge enp0s9 enp0s10

在另一個終端查看eBPF字節(jié)碼里的map,即MAC/端口映射表:

root@zhaoya-VirtualBox:/home/zhaoya# bpftool p |tail -n 4

166: xdp name xdp_bridge_prog tag 956a68e9ac54a0b3 gpl

loaded_at 2019-11-08T01:14:46+0800 uid 0

xlated 576B jited 340B memlock 4096B map_ids 105

btf_id 114

root@zhaoya-VirtualBox:/home/zhaoya# bpftool map dump id 105

Found 0 elements

root@zhaoya-VirtualBox:/home/zhaoya#

OK,一切順利?,F(xiàn)在讓我們正式用它搭建一個網(wǎng)橋吧。

暫時X掉xdp_bridge程序的運行,讓我們一步一步來。

首先構建下面的拓撲:

中間的Linux Bridge主機(后面簡稱主機B)的enp0s9,enp0s10網(wǎng)卡將是我們注入eBPF字節(jié)碼的位置。

現(xiàn)在讓我們在主機B上創(chuàng)建一個標準的Linux網(wǎng)橋:

brctl addbr br0;

brctl addif br0 enp0s9;

brctl addif br0 enp0s10;

ifconfig br0 up;

在主機H1和主機H2的enp0s9上配置同網(wǎng)段的地址:

H1-enp0s9:40.40.40.201/24

H2-enp0s9:40.40.40.100/24

互相ping確認是通的,并且主機B的enp0s9/enp0s10可以抓到雙向包,這說明主機B的Linux標準網(wǎng)橋工作是OK的。

接下來,停掉這一切,把br0也刪除掉。重新運行xdpbridge程序,確認OK后創(chuàng)建Linux標準網(wǎng)橋,從H1來ping H2,很暢通,同時我們會發(fā)現(xiàn)主機B的xdpbridge程序的輸出:

root@zhaoya-VirtualBox:/usr/src/linux-source-5.3.0/linux-source-5.3.0/samples/bpf# ./xdp_bridge enp0s9 enp0s10

Update XDP bpf map-[HW Address:Port] item Key:[683dbb270008] Value:[4]

Update XDP bpf map-[HW Address:Port] item Key:[683dbb270008] Value:[4]

Update XDP bpf map-[HW Address:Port] item Key:[e7f09f270008] Value:[5]

Update XDP bpf map-[HW Address:Port] item Key:[e7f09f270008] Value:[5]

Update XDP bpf map-[HW Address:Port] item Key:[e6f09f270008] Value:[4]

很顯然,eBPF的map學習到了新的MAC地址,我們可以用bpftool確認:

root@zhaoya-VirtualBox:~# bpftool p |tail -n 4

170: xdp name xdp_bridge_prog tag 956a68e9ac54a0b3 gpl

loaded_at 2019-11-08T01:26:19+0800 uid 0

xlated 576B jited 340B memlock 4096B map_ids 107

btf_id 117

root@zhaoya-VirtualBox:~# bpftool map dump id 107

key: 08 00 27 9f f0 e7 00 00 value: 05 00 00 00

key: 08 00 27 9f f0 e6 00 00 value: 04 00 00 00

key: 08 00 27 bb 3d 68 00 00 value: 04 00 00 00

Found 3 elements

此時,主機B的enp0s9和enp0s10就抓不到任何H1和H2之間單播包了。廣播包仍然會被上傳到慢速路徑被標準Linux網(wǎng)橋處理。

我們看trace日志:

root@zhaoya-VirtualBox:~# cat /sys/kernel/debug/tracing/trace_pipe

-0 [003] ..s. 44274.198178: 0: Destination Address: e6f09f270008 Redirect to:[4] From:[5]

...

雖然主機B的網(wǎng)卡上沒有抓到包,但如何確保數(shù)據(jù)包真的就是從XDP的eBPF字節(jié)碼轉發(fā)走的而不是直接飛過去的呢?

很好的問題,這作為下一個練習不是更好嗎?嗯,你應該試試加一個統(tǒng)計功能,而這個并不復雜。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11207

    瀏覽量

    208717
  • 網(wǎng)橋
    +關注

    關注

    0

    文章

    129

    瀏覽量

    16938
  • BPF
    BPF
    +關注

    關注

    0

    文章

    24

    瀏覽量

    3968

原文標題:實現(xiàn)一個基于XDP_eBPF的學習型網(wǎng)橋

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    基于LPC1114的學習型紅外遙控器程序設計

    本設計就是采用LPC1114+OLED+紅外接收頭構成學習型紅外遙控器,
    發(fā)表于 12-14 16:44 ?6003次閱讀

    分享下學習型紅外遙控資料

    分享下學習型紅外遙控資料
    發(fā)表于 07-30 19:47

    學習型遙控器

    學習型遙控器
    發(fā)表于 08-16 16:59

    智能學習型紅外遙控器設計

    智能學習型紅外遙控器設計
    發(fā)表于 08-16 19:26

    用89C52 做一個學習型遙控器

    現(xiàn)在有單片機89C52 紅外線發(fā)射二級管 三級管 按鈕 電阻 電容 0038紅外接收 晶振 想做一個 學習型遙控器 遙控電視 和空調 帶串口的更好 需要給我電路圖和元器件型號 還有單片機文件 不要粘貼復制的那種 網(wǎng)上找了很多 都不行 需要懂行的朋友幫助 非常感謝你們 急
    發(fā)表于 02-13 09:04

    學習型紅外遙控器設計

    有沒有前輩知道怎么寫基于MSP430F149的學習型紅外遙控器設計程序的?麻煩聯(lián)系我。O(∩_∩)O謝謝984300719
    發(fā)表于 05-01 15:22

    如何增加學習型遙控器的學習距離?

    如圖,學習型遙控器,那部分是學習天線,如何增大信號?
    發(fā)表于 08-16 09:48

    請問單片機解碼433MHZ EV1527學習型編碼ic 學習功能如何用程序實現(xiàn)?

    目前我在做一個51單片機解碼433Mhz模塊的EV1527學習型編碼IC,不知道如何學習對碼?
    發(fā)表于 09-09 15:04

    基于AT89C52的學習型遙控器的設計

    本文介紹了種基于 AT89C52 的學習型遙控器,并對其工作原理及軟、硬件的設計和實現(xiàn)方法進行了詳細的闡述。關鍵詞: AT89C52; 學習型遙控器; 紅外遙控編碼Abstract:
    發(fā)表于 08-14 08:58 ?208次下載

    基于NiosⅡ的紅外學習型遙控器設計

      本文設計了種基于NiosⅡ的紅外學習型遙控器,把載波頻率測量、紅外信號解調、脈寬測量、調制發(fā)送IP核集中到FPG
    發(fā)表于 12-15 10:39 ?2568次閱讀

    自主學習型網(wǎng)絡課件的研究與開發(fā)

    隨著網(wǎng)絡信息的高速發(fā)展,傳統(tǒng)網(wǎng)絡課件以被動知識展示為主要手段已經(jīng)不能滿足學習者自主學習的需求。自主學習型網(wǎng)絡課件立足學習者自身知識儲備,可以實現(xiàn)
    發(fā)表于 10-24 15:19 ?0次下載
    自主<b class='flag-5'>學習型</b>網(wǎng)絡課件的研究與開發(fā)

    學習型紅外線遙控設計與制作解析

    本文主要介紹了學習型紅外線遙控設計與制作解析.
    發(fā)表于 06-26 08:00 ?92次下載

    關于R8C/Lx學習型遙控器設計的介紹

    R8C/Lx學習型遙控器參考設計
    的頭像 發(fā)表于 07-23 01:04 ?3199次閱讀

    紅外學習型遙控器方案說明

    紅外學習型遙控器可通過學習操作學習其它遙控器上的部分按鍵,實現(xiàn)遙控器可遙控兩種設備,方便用戶操作。
    的頭像 發(fā)表于 10-18 11:31 ?4282次閱讀

    學習型遙控器的設計與實現(xiàn)

    電子發(fā)燒友網(wǎng)站提供《學習型遙控器的設計與實現(xiàn).doc》資料免費下載
    發(fā)表于 10-24 09:33 ?1次下載
    <b class='flag-5'>學習型</b>遙控器的設計與<b class='flag-5'>實現(xiàn)</b>