BPF 進階筆記(二):BPF Map 類型詳解:使用場景、程序示例

語言: CN / TW / HK

關於本文

內核目前支持 30 來種 BPF map 類型。對於主要的類型,本文將介紹其:

  1. 使用場景 :適合用來做什麼?
  2. 程序示例 :一些實際例子。

本文參考:

  1. notes-on-bpf-3 ,內容較老,基於內核 4.14

關於 “BPF 進階筆記” 系列

平時學習使用 BPF 時所整理。由於是筆記而非教程,因此內容不會追求連貫,有基礎的 同學可作查漏補缺之用。

文中涉及的代碼,如無特殊説明,均基於內核 5.8/5.10 版本。

  • 關於 “BPF 進階筆記” 系列
    • BPF map 類型:完整列表
  • ————————————————————————
  • ————————————————————————
      • 場景一:將內核態得到的數據,傳遞給用户態程序
      • 場景二:存放全局配置信息,供 BPF 程序使用
      • 1. 將內核態數據傳遞到用户態: samples/bpf/sockex2
  • 2 BPF_MAP_TYPE_PERCPU_HASH
      • 1. samples/bpf/map_perf_test_kern.c
  • 3 BPF_MAP_TYPE_LRU_HASH
      • 場景一:連接跟蹤(conntrack)表、NAT 表等固定大小哈希表
      • 1. samples/bpf/map_perf_test_kern.c
      • 2. Cilium Conntrack & NAT 表
  • 4 BPF_MAP_TYPE_LRU_PERCPU_HASH
  • 5 BPF_MAP_TYPE_HASH_OF_MAPS
      • 1. samples/bpf/test_map_in_map_kern.c
  • ————————————————————————
  • ————————————————————————
      • 1. 根據協議類型(proto as key)統計流量: samples/bpf/sockex1
  • 2 BPF_MAP_TYPE_PERCPU_ARRAY
  • 3 BPF_MAP_TYPE_PROG_ARRAY
    • 使用場景:尾調用(tail call)
      • 1. 根據協議類型尾調用到下一層 parser: samples/bpf/sockex3
  • 4 BPF_MAP_TYPE_PERF_EVENT_ARRAY
    • 使用場景:保存 tracing 結果
      • 1. 保存 perf event: samples/bpf/trace_output_kern.c
  • 5 BPF_MAP_TYPE_ARRAY_OF_MAPS
      • 1. samples/bpf/map_perf_test_kern.c
      • 2. samples/bpf/test_map_in_map_kern.c
  • 6 BPF_MAP_TYPE_CGROUP_ARRAY
      • 場景一:cgroup 級別的包過濾(拒絕/放行)
      • 場景二:cgroup 級別的進程過濾(權限控制等)
      • 1. Pin & update pinned cgroup array: samples/bpf/test_cgrp2_array_pin.c
      • 2. CGroup 級別的包過濾: samples/bpf/test_cgrp2_tc_kern.c
      • 3. 判斷進程是否在給定 cgroup 中: samples/bpf/test_current_task_under_cgroup_kern.c
  • ————————————————————————
  • ————————————————————————
  • 1 BPF_MAP_TYPE_CGROUP_ARRAY
  • 2 BPF_MAP_TYPE_CGROUP_STORAGE
      • 場景一:cgroup 內所有 BPF 程序的共享存儲
      • 1. samples/bpf/hbm_kern.h :host bandwidth manager
  • 3 BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE
  • ————————————————————————
  • ————————————————————————
  • 1 BPF_MAP_TYPE_STACK_TRACE
      • 場景一:存儲 profiling 信息
      • 1. 打印調用棧: samples/bpf/offwaketime_kern.c
  • 3 BPF_MAP_TYPE_RINGBUF
  • 4 BPF_MAP_TYPE_PERF_EVENT_ARRAY
      • 1. samples/bpf/trace_output :trace write() 系統調用
  • ————————————————————————
  • ————————————————————————
  • 1 BPF_MAP_TYPE_SOCKMAP
      • 場景一:socket redirection(重定向)
  • 2 BPF_MAP_TYPE_REUSEPORT_SOCKARRAY
      • 場景一:配合 _SK_REUSEPORT 類型 BPF 程序,加速 socket 查找
  • 3 BPF_MAP_TYPE_SK_STORAGE
      • 場景一:per-socket 存儲空間
      • 1. 在內核定期 dump socket 詳情: samples/bpf/tcp_dumpstats_kern.c
  • ————————————————————————
  • ————————————————————————
  • 1 BPF_MAP_TYPE_SOCKHASH
      • 1. (譯) 利用 ebpf sockmap/redirection 提升 socket 性能(2020)
  • 2 BPF_MAP_TYPE_DEVMAP
      • 場景一:存放 XDP 配置信息
      • 場景二:XDP redirection
      • 1. 存儲 XDP 配置信息: samples/bpf/xdp_fwd_kern.c
      • 2. XDP 重定向: samples/bpf/xdp_redirect_map_kern.c
      • 3. 極簡 XDP 路由器: samples/bpf/xdp_router_ipv4_kern.c
  • 3 BPF_MAP_TYPE_DEVMAP_HASH
  • 4 BPF_MAP_TYPE_XSKMAP
      • 1. XDP socket 重定向: samples/bpf/xdpsock_kern.c
  • ————————————————————————
  • ————————————————————————
  • 1 BPF_MAP_TYPE_CPUMAP
  • 3 BPF_MAP_TYPE_STRUCT_OPS
  • 4 BPF_MAP_TYPE_LPM_TRIE
      • 場景一:存儲 IP 路由等
      • 1. samples/bpf/map_perf_test_kern.c
      • 2. samples/bpf/xdp_router_ipv4_kern.c
  • ————————————————————————
  • ————————————————————————
    • 用户空間 map 操作: tools/lib/bpf/bpf.c 提供的封裝
    • 聲明/創建 BPF map 的方式

基礎

BPF map 類型:完整列表

所有 map 類型的 定義

// include/uapi/linux/bpf.h

enum bpf_map_type {
    BPF_MAP_TYPE_UNSPEC,
    BPF_MAP_TYPE_HASH,               // 哈希表
    BPF_MAP_TYPE_ARRAY,              // 數組
    BPF_MAP_TYPE_PROG_ARRAY,         // 存放 BPF 程序的數組
    BPF_MAP_TYPE_PERF_EVENT_ARRAY,
    BPF_MAP_TYPE_PERCPU_HASH,
    BPF_MAP_TYPE_PERCPU_ARRAY,
    BPF_MAP_TYPE_STACK_TRACE,
    BPF_MAP_TYPE_CGROUP_ARRAY,
    BPF_MAP_TYPE_LRU_HASH,
    BPF_MAP_TYPE_LRU_PERCPU_HASH,
    BPF_MAP_TYPE_LPM_TRIE,
    BPF_MAP_TYPE_ARRAY_OF_MAPS,
    BPF_MAP_TYPE_HASH_OF_MAPS,
    BPF_MAP_TYPE_DEVMAP,
    BPF_MAP_TYPE_SOCKMAP,
    BPF_MAP_TYPE_CPUMAP,
    BPF_MAP_TYPE_XSKMAP,
    BPF_MAP_TYPE_SOCKHASH,
    BPF_MAP_TYPE_CGROUP_STORAGE,
    BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
    BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
    BPF_MAP_TYPE_QUEUE,
    BPF_MAP_TYPE_STACK,
    BPF_MAP_TYPE_SK_STORAGE,
    BPF_MAP_TYPE_DEVMAP_HASH,
    BPF_MAP_TYPE_STRUCT_OPS,
    BPF_MAP_TYPE_RINGBUF,
};

————————————————————————

Hash Maps

————————————————————————

Hash map 的實現見 kernel/bpf/hashtab.c 。 五種類型共用一套代碼:

BPF_MAP_TYPE_HASH
BPF_MAP_TYPE_PERCPU_HASH
BPF_MAP_TYPE_LRU_HASH
BPF_MAP_TYPE_LRU_PERCPU_HASH
BPF_MAP_TYPE_HASH_OF_MAPS

Hash map 的特點:

  • key 的長度沒有限制 ,但顯然應該大於 0。
  • 給定 key 查找 value 時,內部通過哈希實現,而非數組索引。
  • key/value 是可刪除的 ;作為對比, Array 類型 的 map 中,key/value 是 不可刪除的 (但用空值覆蓋掉 value ,可實現刪除效果)。

    原因其實也很簡單:哈希表是鏈表,可以刪除鏈表中的元素;array 是內存空間連續的 數組,即使某個 index 處的 value 不用了,這段內存區域還是得留着,不可能將其釋放掉。

不帶與帶 PERCPU 的 map 的區別:

  • 前者是 global 的,只有一個實例;後者是 cpu-local 的,每個 CPU 上都有一個 map 實例;
  • 多核併發訪問時,global map 要加鎖;per-cpu map 無需加鎖,每個核上的程序訪問 local-cpu 上的 map;最後將所有 CPU 上的 map 彙總。

1 BPF_MAP_TYPE_HASH

最簡單的哈希 map。

初始化時需要指定 支持的最大條目數 (max_entries)。 滿了之後繼續插入數據時,會報 E2BIG 錯誤。

使用場景

場景一:將內核態得到的數據,傳遞給用户態程序

這是非常典型的 在內核態和用户態傳遞數據 場景。

例如,BPF 程序過濾網絡設備設備上的包,統計流量信息,並將其寫到 map。 用户態程序從 map 讀取統計,做後續處理。

場景二:存放全局配置信息,供 BPF 程序使用

例如,對於防火牆功能的 BPF 程序,將過濾規則放到 map 裏。用户態控制程序通過 bpftool 之類的工具更新 map 裏的配置信息,BPF 程序動態加載。

程序示例

1. 將內核態數據傳遞到用户態: samples/bpf/sockex2

這個例子用 BPF 程序 過濾網絡設備設備上的包 ,統計包數和字節數, 並以目的 IP 地址為 key 將統計信息寫到 map:

// samples/bpf/sockex2_kern.c

struct {
    __uint(type, BPF_MAP_TYPE_HASH);  // BPF map 類型
    __type(key, __be32);              // 目的 IP 地址
    __type(value, struct pair);       // 包數和字節數
    __uint(max_entries, 1024);        // 最大 entry 數量
} hash_map SEC(".maps");

SEC("socket2")
int bpf_prog2(struct __sk_buff *skb)
{
    flow_dissector(skb, &flow);

    key = flow.dst; // 目的 IP 地址
    value = bpf_map_lookup_elem(&hash_map, &key);
    if (value) {    // 如果已經存在,則更新相應計數
        __sync_fetch_and_add(&value->packets, 1);
        __sync_fetch_and_add(&value->bytes, skb->len);
    } else {        // 否則,新建一個 entry
        struct pair val = {1, skb->len};
        bpf_map_update_elem(&hash_map, &key, &val, BPF_ANY);
    }
    return 0;
}

2 BPF_MAP_TYPE_PERCPU_HASH

使用場景

基本同上。

程序示例

1. samples/bpf/map_perf_test_kern.c

3 BPF_MAP_TYPE_LRU_HASH

普通 hash map 的問題是有大小限制,超過最大數量後無法再插入了。LRU map 可以避 免這個問題,如果 map 滿了,再插入時它會自動將 最久未被使用(least recently used) 的 entry 從 map 中移除。

使用場景

場景一:連接跟蹤(conntrack)表、NAT 表等固定大小哈希表

滿了之後最老的 entry 會被踢出去。

程序示例

1. samples/bpf/map_perf_test_kern.c

2. Cilium Conntrack & NAT 表

TODO: update this.

4 BPF_MAP_TYPE_LRU_PERCPU_HASH

基本同上。

5 BPF_MAP_TYPE_HASH_OF_MAPS

map-in-map: 第一個 map 內的元素是指向另一個 map 的指針 。 與後面將介紹的 BPF_MAP_TYPE_ARRAY_OF_MAPS 類似,但外層 map 使用的是哈希而不是數組。

相關 commit message

使用場景

場景一:map-in-map

程序示例

1. samples/bpf/test_map_in_map_kern.c

測試瞭如下兩級查找場景:

  1. Array of array
  2. Hash of array
  3. Hash of hash

————————————————————————

Array Maps

————————————————————————

1 BPF_MAP_TYPE_ARRAY

最大的特點: key 就是數組中的索引(index) (因此 key 一定 是整形),因此無需對 key 進行哈希。

使用場景:key 是整形

程序示例

1. 根據協議類型(proto as key)統計流量: samples/bpf/sockex1

// samples/bpf/sockex1_kern.c

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, u32);                  // L4 協議類型(長度是 uint8),例如 IPPROTO_TCP,範圍是 0~255
    __type(value, long);               // 累計包長(skb->len)
    __uint(max_entries, 256);
} my_map SEC(".maps");

SEC("socket1")
int bpf_prog1(struct __sk_buff *skb)
{
    int index = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol)); // L4 協議類型

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

    // 注意:在用户態程序和這段 BPF 程序裏都沒有往 my_map 裏插入數據;
    //   * 如果這是 hash map 類型,那下面的 lookup 一定失敗,因為我們沒插入過任何數據;
    //   * 但這裏是 array 類型,而且 index 表示的 L4 協議類型,在 IP 頭裏佔一個字節,因此範圍在 255 以內;
    //     又 map 的長度聲明為 256,所以這裏的 lookup 一定能定位到 array 的某個位置,即查找一定成功。
    value = bpf_map_lookup_elem(&my_map, &index);
    if (value)
        __sync_fetch_and_add(value, skb->len);

    return 0;
}

2 BPF_MAP_TYPE_PERCPU_ARRAY

基本同上。

3 BPF_MAP_TYPE_PROG_ARRAY

程序數組,尾調用 bpf_tail_call() 時會用到。

  • key:任意整形(因為要作為 array index),具體表示什麼由使用者設計(例如表示協議類型 proto)。
  • value: BPF 程序的文件描述符(fd)

使用場景:尾調用(tail call)

程序示例

1. 根據協議類型尾調用到下一層 parser: samples/bpf/sockex3

4 BPF_MAP_TYPE_PERF_EVENT_ARRAY

使用場景:保存 tracing 結果

程序示例

1. 保存 perf event: samples/bpf/trace_output_kern.c

// samples/bpf/trace_output_kern.c

struct bpf_map_def SEC("maps") my_map = {
    .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
    .key_size = sizeof(int),
    .value_size = sizeof(u32),
    .max_entries = 2,
};

SEC("kprobe/sys_write")
int bpf_prog1(struct pt_regs *ctx)
{
    struct S {
        u64 pid;
        u64 cookie;
    } data;

    data.pid = bpf_get_current_pid_tgid();
    data.cookie = 0x12345678;

    bpf_perf_event_output(ctx, &my_map, 0, &data, sizeof(data));

    return 0;
}

5 BPF_MAP_TYPE_ARRAY_OF_MAPS

使用場景:map-in-map

map-in-map,values 是指向內層 map 的 fd。只支持兩層 map。 two levels of map,也就是一層 map 嵌套另一層 map。

BPF_MAP_TYPE_PROG_ARRAY 類型的 BPF 程序 不支持 map-in-map 功能 ,因為這會使 tail call 的 verification 更加困難。 詳見 patch

程序示例

1. samples/bpf/map_perf_test_kern.c

2. samples/bpf/test_map_in_map_kern.c

6 BPF_MAP_TYPE_CGROUP_ARRAY

在用户空間存放 cgroup fds,用來 檢查給定的 skb 是否與 cgroup_array[index] 指向的 cgroup 關聯

使用場景

場景一:cgroup 級別的包過濾(拒絕/放行)

場景二:cgroup 級別的進程過濾(權限控制等)

程序示例

1. Pin & update pinned cgroup array: samples/bpf/test_cgrp2_array_pin.c

程序功能:

  1. 將 cgroupv2 array pin 到 BPFFS
  2. 更新 pinned cgroupv2 array
// samples/bpf/test_cgrp2_array_pin.c

    if (create_array) {
        array_fd = bpf_create_map(BPF_MAP_TYPE_CGROUP_ARRAY, sizeof(uint32_t), sizeof(uint32_t), 1, 0);
    } else {
        array_fd = bpf_obj_get(pinned_file);
    }

    bpf_map_update_elem(array_fd, &array_key, &cg2_fd, 0);

    if (create_array) {
        ret = bpf_obj_pin(array_fd, pinned_file);
    }

2. CGroup 級別的包過濾: samples/bpf/test_cgrp2_tc_kern.c

核心是調用 bpf_skb_under_cgroup() 判斷 skb 是否在給定 cgroup 中:

// samples/bpf/test_cgrp2_tc_kern.c

struct bpf_elf_map SEC("maps") test_cgrp2_array_pin = {
    .type        = BPF_MAP_TYPE_CGROUP_ARRAY,
    .size_key    = sizeof(uint32_t),
    .size_value  = sizeof(uint32_t),
    .pinning     = PIN_GLOBAL_NS,
    .max_elem    = 1,
};

SEC("filter")
int handle_egress(struct __sk_buff *skb)
{
    ...
    if (bpf_skb_under_cgroup(skb, &test_cgrp2_array_pin, 0) != 1) {
        bpf_trace_printk(pass_msg, sizeof(pass_msg));
        return TC_ACT_OK;
    }
    ...
}

3. 判斷進程是否在給定 cgroup 中: samples/bpf/test_current_task_under_cgroup_kern.c

調用 bpf_current_task_under_cgroup() 判斷當前進程是否在給定 cgroup 中

struct bpf_map_def SEC("maps") cgroup_map = {
    .type            = BPF_MAP_TYPE_CGROUP_ARRAY,
    .key_size        = sizeof(u32),
    .value_size        = sizeof(u32),
    .max_entries    = 1,
};

/* Writes the last PID that called sync to a map at index 0 */
SEC("kprobe/sys_sync")
int bpf_prog1(struct pt_regs *ctx)
{
    ...
    if (!bpf_current_task_under_cgroup(&cgroup_map, 0))
        return 0;
    ...
}

————————————————————————

CGroup Maps

————————————————————————

1 BPF_MAP_TYPE_CGROUP_ARRAY

已經有詳細介紹。

2 BPF_MAP_TYPE_CGROUP_STORAGE

Attach 到一個 cgroup 的所有 BPF 程序,會共用一組 cgroup storage,包括:

for (stype = 0; stype < MAX_BPF_CGROUP_STORAGE_TYPE; stype++)
        storages[stype] = bpf_cgroup_storage_alloc(prog, stype);

這裏的 types 目前只有兩種:

  1. shared
  2. per-cpu

使用場景

場景一:cgroup 內所有 BPF 程序的共享存儲

程序示例

1. samples/bpf/hbm_kern.h :host bandwidth manager

struct {
    __uint(type, BPF_MAP_TYPE_CGROUP_STORAGE);
    __type(key, struct bpf_cgroup_storage_key);
    __type(value, struct hbm_vqueue);
} queue_state SEC(".maps");

3 BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE

同上。

————————————————————————

Tracing Maps

————————————————————————

1 BPF_MAP_TYPE_STACK_TRACE

內核程序能通過 bpf_get_stackid() helper 存儲 stack 信息。 將 stack 信息關聯到一個 id,而這個 id 是 對當前棧的 指令指針地址(instruction pointer address)進行 32-bit hash 得到的。

使用場景

場景一:存儲 profiling 信息

在內核中獲取 stack id,用它作為 key 更新另一個 map。 例如通過對指定的 stack traces 進行 profiling,統計它們的出現次數,或者將 stack trace 信息與當前 pid 關聯起來。

程序示例

1. 打印調用棧: samples/bpf/offwaketime_kern.c

2 BPF_MAP_TYPE_STACK

使用場景

場景一:

3 BPF_MAP_TYPE_RINGBUF

使用場景

場景一:

4 BPF_MAP_TYPE_PERF_EVENT_ARRAY

使用場景

場景一:Perf events

BPF 程序將數據存儲在 mmap() 共享內存中,用户空間程序可以訪問。

場景:

  • 非固定大小數據(不適合 map)
  • 無需與其他 BPF 程序共享數據

程序示例

1. samples/bpf/trace_output :trace write() 系統調用

————————————————————————

Socket Maps

————————————————————————

1 BPF_MAP_TYPE_SOCKMAP

主要用於 socket redirection:將 sockets 信息插入到 map,後面執行到 bpf_sockmap_redirect() 時,用 map 裏的信息觸發重定向。

使用場景

場景一:socket redirection(重定向)

程序示例

TODO.

2 BPF_MAP_TYPE_REUSEPORT_SOCKARRAY

配合 BPF_PROG_TYPE_SK_REUSEPORT 類型的 BPF 程序使用,加速 socket 查找。

使用場景

場景一:配合 _SK_REUSEPORT 類型 BPF 程序,加速 socket 查找

3 BPF_MAP_TYPE_SK_STORAGE

使用場景

場景一:per-socket 存儲空間

程序示例

1. 在內核定期 dump socket 詳情: samples/bpf/tcp_dumpstats_kern.c

struct {
    __u32 type;
    __u32 map_flags;
    int *key;
    __u64 *value;
} bpf_next_dump SEC(".maps") = {
    .type = BPF_MAP_TYPE_SK_STORAGE,
    .map_flags = BPF_F_NO_PREALLOC,
};

SEC("sockops")
int _sockops(struct bpf_sock_ops *ctx)
{
    struct bpf_tcp_sock *tcp_sk;
    struct bpf_sock *sk;
    __u64 *next_dump;

    switch (ctx->op) {
    case BPF_SOCK_OPS_TCP_CONNECT_CB:
        bpf_sock_ops_cb_flags_set(ctx, BPF_SOCK_OPS_RTT_CB_FLAG);
        return 1;
    case BPF_SOCK_OPS_RTT_CB:
        break;
    default:
        return 1;
    }

    sk = ctx->sk;
    next_dump = bpf_sk_storage_get(&bpf_next_dump, sk, 0, BPF_SK_STORAGE_GET_F_CREATE);
    now = bpf_ktime_get_ns();
    if (now < *next_dump)
        return 1;

    tcp_sk = bpf_tcp_sock(sk);
    *next_dump = now + INTERVAL;

    bpf_printk("dsack_dups=%u delivered=%u\n", tcp_sk->dsack_dups, tcp_sk->delivered);
    bpf_printk("delivered_ce=%u icsk_retransmits=%u\n", tcp_sk->delivered_ce, tcp_sk->icsk_retransmits);

    return 1;
}

————————————————————————

XDP Maps

————————————————————————

1 BPF_MAP_TYPE_SOCKHASH

使用場景

場景一:XDP 重定向

程序示例

1. (譯) 利用 ebpf sockmap/redirection 提升 socket 性能(2020)

2 BPF_MAP_TYPE_DEVMAP

功能與 sockmap 類似,但用於 XDP 場景 ,在 bpf_redirect() 時觸發。

使用場景

場景一:存放 XDP 配置信息

對於 TC BPF 程序,配置信息放到普通的 hash 或 array map 裏就行了 。但對於 XDP 程序來説,由於它們開始執行的位置非常靠前,此時大部分網絡基礎設施它們都是用 不了的。因此引入了一些專門針對 XDP 的基礎設施,例如這裏的 DEVMAP(對應 TC 場景 下的普通 BPF MAP)。

場景二:XDP redirection

程序示例

1. 存儲 XDP 配置信息: samples/bpf/xdp_fwd_kern.c

這個例子裏,將允許通過哪些網卡發送數據的配置信息放到了一個 DEVMAP 裏,

struct {
    __uint(type, BPF_MAP_TYPE_DEVMAP);
    __uint(key_size, sizeof(int));      // key 表示 ifindex,即網卡 ID
    __uint(value_size, sizeof(int));    // val 表示是否允許從這個網卡發送(TX)數據
    __uint(max_entries, 64);
} xdp_tx_ports SEC(".maps");

主邏輯裏查詢這個 map,判斷是否能通過這個網卡發送數據:

if (rc == BPF_FIB_LKUP_RET_SUCCESS) {
        // Verify egress index has been configured as TX-port.
        if (!bpf_map_lookup_elem(&xdp_tx_ports, &fib_params.ifindex))
            return XDP_PASS;

        memcpy(eth->h_dest, fib_params.dmac, ETH_ALEN);
        memcpy(eth->h_source, fib_params.smac, ETH_ALEN);
        return bpf_redirect_map(&xdp_tx_ports, fib_params.ifindex, 0);
    }

2. XDP 重定向: samples/bpf/xdp_redirect_map_kern.c

將包從指定網卡重定向出去:

struct {
    __uint(type, BPF_MAP_TYPE_DEVMAP);
    __uint(key_size, sizeof(int));    // virtual port index
    __uint(value_size, sizeof(int));  // physical port index
    __uint(max_entries, 100);
} tx_port SEC(".maps");

SEC("xdp_redirect_map")
int xdp_redirect_map_prog(struct xdp_md *ctx)
{
    swap_src_dst_mac(data);

    /* send packet out physical port */
    return bpf_redirect_map(&tx_port, vport, 0);
}

3. 極簡 XDP 路由器: samples/bpf/xdp_router_ipv4_kern.c

用到了多種類型的 MAP,實現 IPv4 路由功能:

/* Map for trie implementation*/
struct {
    __uint(type, BPF_MAP_TYPE_LPM_TRIE);
    __uint(key_size, 8);
    __uint(value_size, sizeof(struct trie_value));
    __uint(max_entries, 50);
    __uint(map_flags, BPF_F_NO_PREALLOC);
} lpm_map SEC(".maps");

/* Map for ARP table*/
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, __be32);
    __type(value, __be64);
    __uint(max_entries, 50);
} arp_table SEC(".maps");

/* Map to keep the exact match entries in the route table*/
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, __be32);
    __type(value, struct direct_map);
    __uint(max_entries, 50);
} exact_match SEC(".maps");

struct {
    __uint(type, BPF_MAP_TYPE_DEVMAP);
    __uint(key_size, sizeof(int));
    __uint(value_size, sizeof(int));
    __uint(max_entries, 100);
} tx_port SEC(".maps");

3 BPF_MAP_TYPE_DEVMAP_HASH

同上。

4 BPF_MAP_TYPE_XSKMAP

都是 XDP map,都可用於 XDP socket 重定向, 與 DEVMAP 有什麼區別?

使用場景:XDP

程序示例

1. XDP socket 重定向: samples/bpf/xdpsock_kern.c

struct {
    __uint(type, BPF_MAP_TYPE_XSKMAP);
    __uint(max_entries, MAX_SOCKS);
    __uint(key_size, sizeof(int));
    __uint(value_size, sizeof(int));
} xsks_map SEC(".maps");

static unsigned int rr;

SEC("xdp_sock") int xdp_sock_prog(struct xdp_md *ctx)
{
    rr = (rr + 1) & (MAX_SOCKS - 1);
    return bpf_redirect_map(&xsks_map, rr, XDP_DROP);
}

————————————————————————

其他 Maps

————————————————————————

1 BPF_MAP_TYPE_CPUMAP

使用場景

場景一:

2 BPF_MAP_TYPE_QUEUE

使用場景

場景一:

3 BPF_MAP_TYPE_STRUCT_OPS

使用場景

場景一:

4 BPF_MAP_TYPE_LPM_TRIE

支持高效的 longest-prefix matching。

使用場景

場景一:存儲 IP 路由等

程序示例

1. samples/bpf/map_perf_test_kern.c

2. samples/bpf/xdp_router_ipv4_kern.c

3. Cilium (TODO)

————————————————————————

其他相關內容

————————————————————————

用户空間 map 操作: tools/lib/bpf/bpf.c 提供的封裝

BPF map 是內核對象,為方便從用户空間對 map 進行操作, tools/lib/bpf/bpf.c 封裝了一些通用 API。例如,

# 帶 _node 字樣的函數或類型都表示感知 NUMA 結構
int bpf_create_map_node(enum bpf_map_type map_type, const char *name,
       int key_size, int value_size, int max_entries, __u32 map_flags, int node);

int bpf_create_map_in_map_node(enum bpf_map_type map_type, const char *name,
       int key_size, int inner_map_fd, int max_entries, __u32 map_flags, int node);

二者最後都會執行到 bpf 系統調用:

sys_bpf(BPF_MAP_CREATE, &attr, sizeof(attr));

聲明/創建 BPF map 的方式

常規方式

下面是來自 samples/bpf 中的一個例子:

// samples/bpf/lathist_kern.c

struct bpf_map_def SEC("maps") my_map = {
    .type        = BPF_MAP_TYPE_ARRAY,
    .key_size    = sizeof(int),
    .value_size  = sizeof(u64),
    .max_entries = MAX_CPU,
};

以上聲明瞭一個 BPF map,

  • map 類型是 BPF_MAP_TYPE_ARRAY
  • 指定 key 和 value 的長度;

    BPF map 是任意類型的 key/value 存儲,key 和 value 類型都是在 BPF 程序中定義的。 因此內核關心的並不是 key/value 類型,而且它們的大小(長度)。BPF map 操作也 用的的是 key/value 的 void * 類型地址。BPF 程序需要解析 map 數據時,先拿到 key/value 地址,然後自己做相應的強制類型轉換。

  • 數組最大長度(map size)

建議聲明 map 時優先使用這種封裝好的方式,不要重複造輪子。

tc/iproute2 方式

如果使用的是 tc/iproute2,那聲明和創建 map 的過程會稍有不同,見 iproute2 源碼

// samples/bpf/tc_l2_redirect_kern.c

// key 結構體需要 64bit 對其,否則內核校驗器會拒絕加載程序
struct bpf_elf_map {
    __u32 type;
    __u32 size_key;
    __u32 size_value;
    __u32 max_elem;
    __u32 flags;
    __u32 id;
    __u32 pinning;
};

struct bpf_elf_map SEC("maps") tun_iface = {
    .type = BPF_MAP_TYPE_ARRAY,
    .size_key = sizeof(int),
    .size_value = sizeof(int),
    .pinning = PIN_GLOBAL_NS,
    .max_elem = 1,
};

Map pinning

/* Object pinning settings */

#define PIN_NONE        0
#define PIN_OBJECT_NS   1
#define PIN_GLOBAL_NS   2 // 綁定到 `/sys/fs/bpf/tc/globals/` 下面

這個選項決定了 以何種文件系統方式將 map 暴露出來

例如,如果使用的是 libbpf 庫,

  • 可以通過 bpf_obj_pin(fd, path) 將 map fd 綁定到文件系統中的指定文件
  • 接下來,其他程序 獲取這個 fd ,只需執行 bpf_obj_get(pinned_file)