xdp-tutorial basic04-pinning-mapsをやってみる

github.com

Assignment1

eBPFプログラムが再ロードされた場合、xdp_statsプログラムでそれを検知して、mapを再取得するというもの。

解いてみる

誤ったファイルディスクリプタを使っていないかbpfマップを検証する関数を実装する

問題文に書いてある通り、新しいbpfプログラムが再ロードされたら、bpfマップのidが変わるので、それを利用して、誤ったファイルディスクリプタを使っていないか検証できる。

xdp_stats.c

@@ -180,6 +180,16 @@ static bool map_collect(int fd, __u32 map_type, __u32 key, struct record *rec)
        return true;
 }

+static int is_wrong_fd(__u32 id, char * pin_dir) {
+       struct bpf_map_info info = { 0 };
+       int map_fd = open_bpf_map_file(pin_dir, "xdp_stats_map", &info);
+       if (map_fd < 0) {
+               return -1;
+       }
+       close(map_fd);
+       return id != info.id;
+}
+
 static void stats_collect(int map_fd, __u32 map_type,
                          struct stats_record *stats_rec)
 {

stats_pollのループの中でbpfマップを検証する。

stats_pollのループの中でbpfマップを検証するように修正を行う。
open_bpf_map_fileでエラーが起きた場合にはxdp_statsをEXIT_FAILで終了させている。
EXIT_FAILはcommon_difine.hで定義されていた終了値コードで、これを使わなくても良いが、既存のコードに合わせて使用している。

xdp_stats.c

@@ -191,7 +201,7 @@ static void stats_collect(int map_fd, __u32 map_type,
        }
 }

-static void stats_poll(int map_fd, __u32 map_type, int interval)
+static int stats_poll(int map_fd, __u32 map_type, int interval, __u32 id, char * pin_dir)
 {
        struct stats_record prev, record = { 0 };

@@ -203,6 +213,11 @@ static void stats_poll(int map_fd, __u32 map_type, int interval)
        usleep(1000000/4);

        while (1) {
+               int result;
+               result = is_wrong_fd(id, pin_dir);
+               if (result != 0) {
+                       return result;
+               }
                prev = record; /* struct copy */
                stats_collect(map_fd, map_type, &record);
                stats_print(&record, &prev);
@@ -247,29 +262,38 @@ int main(int argc, char **argv)
                return EXIT_FAIL_OPTION;
        }

-       stats_map_fd = open_bpf_map_file(pin_dir, "xdp_stats_map", &info);
-       if (stats_map_fd < 0) {
-               return EXIT_FAIL_BPF;
-       }
-
-       /* check map info, e.g. datarec is expected size */
-       map_expect.key_size    = sizeof(__u32);
-       map_expect.value_size  = sizeof(struct datarec);
-       map_expect.max_entries = XDP_ACTION_MAX;
-       err = check_map_fd_info(&info, &map_expect);
-       if (err) {
-               fprintf(stderr, "ERR: map via FD not compatible\n");
-               return err;
-       }
-       if (verbose) {
-               printf("\nCollecting stats from BPF map\n");
-               printf(" - BPF map (bpf_map_type:%d) id:%d name:%s"
-                      " key_size:%d value_size:%d max_entries:%d\n",
-                      info.type, info.id, info.name,
-                      info.key_size, info.value_size, info.max_entries
-                      );
+       while (1) {
+               stats_map_fd = open_bpf_map_file(pin_dir, "xdp_stats_map", &info);
+               if (stats_map_fd < 0) {
+                       return EXIT_FAIL_BPF;
+               }
+
+               /* check map info, e.g. datarec is expected size */
+               map_expect.key_size    = sizeof(__u32);
+               map_expect.value_size  = sizeof(struct datarec);
+               map_expect.max_entries = XDP_ACTION_MAX;
+               err = check_map_fd_info(&info, &map_expect);
+               if (err) {
+                       fprintf(stderr, "ERR: map via FD not compatible\n");
+                       return err;
+               }
+               if (verbose) {
+                       printf("\nCollecting stats from BPF map\n");
+                       printf(" - BPF map (bpf_map_type:%d) id:%d name:%s"
+                              " key_size:%d value_size:%d max_entries:%d\n",
+                              info.type, info.id, info.name,
+                              info.key_size, info.value_size, info.max_entries
+                              );
+               }
+
+               int result;
+               result = stats_poll(stats_map_fd, info.type, interval, info.id, pin_dir);
+               if (result == -1) {
+                       goto ERROR_CASE;
+               }
+               printf("bpf map will be reloaded.\n");
        }
-
-       stats_poll(stats_map_fd, info.type, interval);
        return EXIT_OK;
+ERROR_CASE:
+       return EXIT_FAIL;
 }

試してみる

注意点としてbasic04では以下のようにprgonameを明示的に指定する必要がある。
また、attach_modeとしてデフォルトでXDP_MODE_NATIVEが指定されている。
私の環境はXDP_MODE_NATIVEを実行できる環境ではないので、-Aを指定してSKBモードで動作させている。

$ sudo ./xdp_loader --progname=xdp_pass_func --dev=lo -A
$ sudo ./xdp_stats --dev=lo

上記を実行後、別ウィンドウでxdpプログラムを以下のように再ロードする。

$ sudo ./xdp_loader --progname=xdp_pass_func --dev=lo -A

xdp_statsの出力は以下となった。

Collecting stats from BPF map
 - BPF map (bpf_map_type:6) id:71 name:xdp_stats_map key_size:4 value_size:16 max_entries:5
XDP-action
XDP_ABORTED            0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:0.250328
XDP_DROP               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:0.250258
XDP_PASS               2 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:0.250259
XDP_TX                 0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:0.250259
XDP_REDIRECT           0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:0.250260

XDP-action
XDP_ABORTED            0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000448
XDP_DROP               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000450
XDP_PASS               2 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000450
XDP_TX                 0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000450
XDP_REDIRECT           0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000450

XDP-action
XDP_ABORTED            0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000432
XDP_DROP               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000432
XDP_PASS               2 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000432
XDP_TX                 0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000432
XDP_REDIRECT           0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000432

XDP-action
XDP_ABORTED            0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000417
XDP_DROP               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000417
XDP_PASS               2 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000417
XDP_TX                 0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000417
XDP_REDIRECT           0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000417

XDP-action
XDP_ABORTED            0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000586
XDP_DROP               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000586
XDP_PASS               2 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000586
XDP_TX                 0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000586
XDP_REDIRECT           0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000586

XDP-action
XDP_ABORTED            0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000425
XDP_DROP               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000425
XDP_PASS               2 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000425
XDP_TX                 0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000425
XDP_REDIRECT           0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000425

bpf map will be reloaded.

Collecting stats from BPF map
 - BPF map (bpf_map_type:6) id:79 name:xdp_stats_map key_size:4 value_size:16 max_entries:5
XDP-action
XDP_ABORTED            0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:0.250216
XDP_DROP               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:0.250216
XDP_PASS               1 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:0.250216
XDP_TX                 0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:0.250216
XDP_REDIRECT           0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:0.250217

bpf_loaderを再実行したタイミングでbpf map will be reloadedというログが出るのを確認できた。

Assignment2

ピンどめされたbpfマップを再利用するように修正する。
再利用のやり方は問題文が載っているページにほぼ載っている。

解いてみる

問題文が載っているページのヒントにしたがって解く

ピンどめされたbpfマップがあるとき、そのままの状態だとそれをクリアしているが、それをクリアせずに再利用させる。

xdp_loader.c

@@ -69,7 +69,7 @@ const char *pin_basedir =  "/sys/fs/bpf";
 const char *map_name    =  "xdp_stats_map";

 /* Pinning maps under /sys/fs/bpf in subdir */
-int pin_maps_in_bpf_object(struct bpf_object *bpf_obj, const char *subdir)
+int pin_maps_in_bpf_object(struct bpf_object *bpf_obj, const char *subdir, char *filename)
 {
        char map_filename[PATH_MAX];
        char pin_dir[PATH_MAX];
@@ -90,16 +90,11 @@ int pin_maps_in_bpf_object(struct bpf_object *bpf_obj, const char *subdir)

        /* Existing/previous XDP prog might not have cleaned up */
        if (access(map_filename, F_OK ) != -1 ) {
-               if (verbose)
-                       printf(" - Unpinning (remove) prev maps in %s/\n",
-                              pin_dir);
-
-               /* Basically calls unlink(3) on map_filename */
-               err = bpf_object__unpin_maps(bpf_obj, pin_dir);
-               if (err) {
-                       fprintf(stderr, "ERR: UNpinning maps in %s\n", pin_dir);
-                       return EXIT_FAIL_BPF;
-               }
+               int pinned_map_fd = bpf_obj_get(map_filename);
+               struct bpf_object *obj = bpf_object__open(filename);
+               struct bpf_map    *map = bpf_object__find_map_by_name(obj, map_name);
+               bpf_map__reuse_fd(map, pinned_map_fd);
+               bpf_object__load(obj);
        }
        if (verbose)
                printf(" - Pinning maps in %s/\n", pin_dir);
@@ -150,7 +145,7 @@ int main(int argc, char **argv)
        }

        /* Use the --dev name as subdir for exporting/pinning maps */
-       err = pin_maps_in_bpf_object(xdp_program__bpf_obj(program), cfg.ifname);
+       err = pin_maps_in_bpf_object(xdp_program__bpf_obj(program), cfg.ifname, cfg.filename);
        if (err) {
                fprintf(stderr, "ERR: pinning maps\n");
                return err;

上のdiffにおける以下の部分は丸々、問題文ページにあるやり方の所をコピペしてやっている。

+               int pinned_map_fd = bpf_obj_get(map_filename);
+               struct bpf_object *obj = bpf_object__open(filename);
+               struct bpf_map    *map = bpf_object__find_map_by_name(obj, map_name);
+               bpf_map__reuse_fd(map, pinned_map_fd);
+               bpf_object__load(obj);