xdp-tutorial packet03-redirectingをやってみる

github.com

前提

alias t='sudo ../testenv/testenv.sh'

Assignment1

ICMP echoサーバーを実装するというもの。

テスト環境構築

t setup --name test --legacy-ip

コード修正

まずは以下を実施する。

  • swap_src_dst_mac()の実装
  • swap_src_dst_ipv6()の実装
  • swap_src_dst_ipv4()の実装
--- a/packet03-redirecting/xdp_prog_kern.c
+++ b/packet03-redirecting/xdp_prog_kern.c
@@ -34,16 +34,28 @@ static __always_inline void swap_src_dst_mac(struct ethhdr *eth)
 {
        /* Assignment 1: swap source and destination addresses in the eth.
         * For simplicity you can use the memcpy macro defined above */
+       unsigned char   tmp[ETH_ALEN];
+       memcpy(tmp, eth->h_dest, sizeof(eth->h_dest));
+       memcpy(eth->h_dest, eth->h_source, sizeof(eth->h_dest));
+       memcpy(eth->h_source, tmp, sizeof(eth->h_dest));
 }

 static __always_inline void swap_src_dst_ipv6(struct ipv6hdr *ipv6)
 {
        /* Assignment 1: swap source and destination addresses in the iphv6dr */
+       struct in6_addr tmp;
+       memcpy(&tmp, &ipv6->daddr, sizeof(struct in6_addr));
+       memcpy(&ipv6->daddr, &ipv6->saddr, sizeof(struct in6_addr));
+       memcpy(&ipv6->saddr, &tmp, sizeof(struct in6_addr));
 }

 static __always_inline void swap_src_dst_ipv4(struct iphdr *iphdr)
 {
        /* Assignment 1: swap source and destination addresses in the iphdr */
+       __be32 tmp;
+       memcpy(&tmp, &iphdr->daddr, sizeof(iphdr->daddr));
+       memcpy(&iphdr->daddr, &iphdr->saddr, sizeof(iphdr->daddr));
+       memcpy(&iphdr->saddr, &tmp, sizeof(iphdr->daddr));
 }

続いて以下を実装する。

  • icmp typeフィールドの変更
  • icmp header checksumの更新
@@ -105,6 +117,16 @@ int xdp_icmp_echo_func(struct xdp_md *ctx)

        /* Assignment 1: patch the packet and update the checksum. You can use
         * the echo_reply variable defined above to fix the ICMP Type field. */
+       struct icmphdr_common before = *icmphdr;
+       icmphdr->type = echo_reply;
+       __u32 size = sizeof(struct icmphdr);
+       if ((void *)icmphdr + 32 > data_end) {
+               goto out;
+       }
+       __u32 sum = bpf_csum_diff((__be32 *)&before, size, (__be32 *)icmphdr, size, 0);
+       sum = (sum & 0xffff) + (sum >> 16);
+       __u16 new_checksum = ~sum;
+       icmphdr->cksum = new_checksum;

        bpf_printk("echo_reply: %d", echo_reply);

checksumの計算はbpf_csum_diff()を使う。ただ、これだと32bitのチェックサムになるので、これを16bitチェックサムに縮める必要がある。 これについてはRFC1071のサンプルコードに従ってやればよい。

TEST

XDPプログラムをロードする。

sudo ./xdp-loader unload test --all
t exec -n test -- ./xdp-loader load --prog-name xdp_pass_func veth0 xdp_prog_kern.o
t load -n test -- --prog-name xdp_icmp_echo_func xdp_prog_kern.o

pingでicmp replyが返るか検証する。

$sudo ../testenv/testenv.sh enter --name veth-packet03
$ping 10.11.1.1 -c 3

icmp replyパケットがXDPプログラムで作られたものであることを確認するためにbpf_printk()の出力を確認する。

$cat /sys/kernel/debug/tracing/trace_pipe
            ping-5561    [001] ..s21 47419.968928: bpf_trace_printk: echo_reply: 0
            ping-5561    [001] ..s21 47420.982531: bpf_trace_printk: echo_reply: 0
            ping-5561    [001] ..s21 47422.006479: bpf_trace_printk: echo_reply: 0

Assignment2

テスト環境構築

以下のような形を作ってあげる。

t setup --name left
t setup --name right

コード修正

宛先MACを書き換えて、egressのifindexを指定してbpf_redirect()を呼び出すだけ。

--- a/packet03-redirecting/xdp_prog_kern.c
+++ b/packet03-redirecting/xdp_prog_kern.c
@@ -146,8 +146,8 @@ int xdp_redirect_func(struct xdp_md *ctx)
        struct ethhdr *eth;
        int eth_type;
        int action = XDP_PASS;
-       /* unsigned char dst[ETH_ALEN] = {} */  /* Assignment 2: fill in with the MAC address of the left inner interface */
-       /* unsigned ifindex = 0; */             /* Assignment 2: fill in with the ifindex of the left interface */
+       unsigned char dst[ETH_ALEN] = {0x56, 0x78, 0x59, 0x76, 0xf0, 0x33};
+       unsigned int ifindex = 5;

        /* These keep track of the next header type and iterator pointer */
        nh.pos = data;
@@ -157,8 +157,8 @@ int xdp_redirect_func(struct xdp_md *ctx)
        if (eth_type == -1)
                goto out;

-       /* Assignment 2: set a proper destination address and call the
-        * bpf_redirect() with proper parameters, action = bpf_redirect(...) */
+       memcpy(eth->h_dest, dst, ETH_ALEN);
+       action = bpf_redirect(ifindex, 0);

 out:
        return xdp_stats_record_action(ctx, action);

TEST

XDPプログラムをrightインターフェースにロードしてやる。

t load -n right -- --prog-name xdp_redirect_func xdp_prog_kern.o

right namespaceのveth0からleft namespaceのveth0へpingを実行してやる。

t enter --name right
# ping -6 fc00:dead:cafe:1::2

以下を確認してやればよい。

  • bpf_redirect()の返り値がXDP_REDIRECTになっているか
  • left network namespaceのveth0に到達するパケットのsource MACがright network namespaceのveth0になっているか

bpf_redirect()の返り値については問題文にある通りxdp_statsで確認してやってもよいし、bpf_printkでプリントデバッグしても簡単に確認できる。

パケットについては以下の画像のようにsorce macがright network namespaceのveth0のものになっていることが確認できる。

差分のためにXDPプログラムをロードしない状態パケットを以下に載せておく。当たり前だが、source macはhost namespaceのleftのものになっている。

Assignment3

Assignment2ではinterface numberとMACをハードコードしたが、それをしないようにしてやる。
といっても、これはカーネルの情報を参照するようにするみたいな話ではなくて(それはAssignment4でやる)、ebpf mapから値を参照するようにするというもの。

テスト環境構築

構成はAssignment2と同じ

t setup --name left
t setup --name right

コード修正

userland側の修正。

--- a/packet03-redirecting/xdp_prog_user.c
+++ b/packet03-redirecting/xdp_prog_user.c
@@ -27,6 +27,7 @@ static const char *__doc__ = "XDP redirect helper\n"

 #include "../common/xdp_stats_kern_user.h"

 static const struct option_wrapper long_options[] = {

        {{"help",        no_argument,           NULL, 'h' },
@@ -52,9 +53,13 @@ static const struct option_wrapper long_options[] = {

 static int parse_mac(char *str, unsigned char mac[ETH_ALEN])
 {
-       /* Assignment 3: parse a MAC address in this function and place the
-        * result in the mac array */
-
+       int i, j;
+       char tmp[3];
+       tmp[2] = '\0';
+       for (i = 0, j = 0; i < strlen(str); i+=3, j++) {
+               memcpy(tmp, str + i, 2);
+               mac[j] = strtoul(tmp ,NULL, 16);
+       }
        return 0;
 }

@@ -126,6 +131,10 @@ int main(int argc, char **argv)

        /* Assignment 3: open the tx_port map corresponding to the cfg.ifname interface */
        map_fd = -1;
+       map_fd = open_bpf_map_file(pin_dir, "tx_port", NULL);
+       if (map_fd < 0) {
+               return EXIT_FAIL_BPF;
+       }

        printf("map dir: %s\n", pin_dir);

@@ -137,6 +146,10 @@ int main(int argc, char **argv)

                /* Assignment 3: open the redirect_params map corresponding to the cfg.ifname interface */
                map_fd = -1;
+               map_fd = open_bpf_map_file(pin_dir, "redirect_params", NULL);
+               if (map_fd < 0) {
+                       return EXIT_FAIL_BPF;
+               }

                /* Setup the mapping containing MAC addresses */
                if (write_iface_params(map_fd, src, dest) < 0) {

open_bpf_map_file()はbasic04でも出てきたpinningされたbpf_mapファイルをopenする関数。

TEST

t load -n left -- --prog-name xdp_redirect_map_func xdp_prog_kern.o
t load -n right -- --prog-name xdp_redirect_map_func xdp_prog_kern.o
t exec -n left -- ./xdp-loader load --prog-name xdp_pass_func veth0 xdp_prog_kern.o
t exec -n right -- ./xdp-loader load --prog-name xdp_pass_func veth0 xdp_prog_kern.o
t redirect right left

../testenv/testenv.sh redirectで実行される内容は以下の通り。

    local src="$1"
    local dest="$2"
    local src_mac=$(ip netns exec $src cat /sys/class/net/veth0/address)
    local dest_mac=$(ip netns exec $dest cat /sys/class/net/veth0/address)

    # set bidirectional forwarding
    ./xdp_prog_user -d $src -r $dest --src-mac $src_mac --dest-mac $dest_mac
    ./xdp_prog_user -d $dest -r $src --src-mac $dest_mac --dest-mac $src_mac

この状態でrightのveth0からleftのveth0にpingを打ってやる。

# ping -6 fc00:dead:cafe:1::2

xdp_statsを確認してやると、rightでもleftでもXDP_REDIRECTしていることがわかる。

sudo ./xdp_stats -d leftの出力

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

sudo ./xdp_stats -d rightの出力

XDP-action
XDP_ABORTED            0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000545
XDP_DROP               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000544
XDP_PASS               3 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000544
XDP_TX                 0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000545
XDP_REDIRECT           6 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000545

Assignment4

Assignment3ではxdp_prog_userを使って対象のIFとMACを手入力しているので、これではハードコーディングとさして変わりない。
Assignment4ではパケットの宛先アドレスをkeyにしてkernelのFIBから、redirectするifindexやsrc mac/dst macを適切に解決するようにする。

テスト環境構築

t setup -n uno --legacy-ip
t setup -n dos --legacy-ip
t setup -n tres --legacy-ip

コード修正

xdp_router_func()を実装していく。原則、コメントに則って行う。
ip_decrease_ttl()チェックサムの計算のところはkernelのsample codeのやつ(参考リンク3)をほぼコピペしてきただけ。

--- a/packet03-redirecting/xdp_prog_kern.c
+++ b/packet03-redirecting/xdp_prog_kern.c
@@ -180,7 +180,10 @@ out:
 /* from include/net/ip.h */
 static __always_inline int ip_decrease_ttl(struct iphdr *iph)
 {
-       /* Assignment 4: see samples/bpf/xdp_fwd_kern.c from the kernel */
+       __u32 check = (__u32)iph->check;
+
+       check += (__u32)bpf_htons(0x0100);
+       iph->check = (__u16)(check + (check >= 0xFFFF));
        return --iph->ttl;
 }

@@ -218,10 +221,13 @@ int xdp_router_func(struct xdp_md *ctx)
                        goto out;

                /* Assignment 4: fill the fib_params structure for the AF_INET case */
+               fib_params.family = AF_INET;
+               fib_params.l4_protocol  = iph->protocol;
+               fib_params.ipv4_src = iph->saddr;
+               fib_params.ipv4_dst = iph->daddr;
        } else if (h_proto == bpf_htons(ETH_P_IPV6)) {
-               /* These pointers can be used to assign structures instead of executing memcpy: */
-               /* struct in6_addr *src = (struct in6_addr *) fib_params.ipv6_src; */
-               /* struct in6_addr *dst = (struct in6_addr *) fib_params.ipv6_dst; */
+               struct in6_addr *src = (struct in6_addr *) fib_params.ipv6_src;
+               struct in6_addr *dst = (struct in6_addr *) fib_params.ipv6_dst;

                ip6h = data + nh_off;
                if (ip6h + 1 > data_end) {
@@ -233,6 +239,10 @@ int xdp_router_func(struct xdp_md *ctx)
                        goto out;

                /* Assignment 4: fill the fib_params structure for the AF_INET6 case */
+               fib_params.family = AF_INET6;
+               fib_params.l4_protocol  = ip6h->nexthdr;
+               *src = ip6h->saddr;
+               *dst = ip6h->daddr;
        } else {
                goto out;
        }
@@ -247,11 +257,9 @@ int xdp_router_func(struct xdp_md *ctx)
                else if (h_proto == bpf_htons(ETH_P_IPV6))
                        ip6h->hop_limit--;

-               /* Assignment 4: fill in the eth destination and source
-                * addresses and call the bpf_redirect function */
-               /* memcpy(eth->h_dest, ???, ETH_ALEN); */
-               /* memcpy(eth->h_source, ???, ETH_ALEN); */
-               /* action = bpf_redirect(???, 0); */
+               memcpy(eth->h_dest, fib_params.dmac, ETH_ALEN);
+               memcpy(eth->h_source, fib_params.smac, ETH_ALEN);
+               action = bpf_redirect(fib_params.ifindex, 0);
                break;
        case BPF_FIB_LKUP_RET_BLACKHOLE:    /* dest is blackholed; can be dropped */
        case BPF_FIB_LKUP_RET_UNREACHABLE:  /* dest is unreachable; can be dropped */

TEST

XDPプログラムをロードする。

t load -n uno -- --prog-name xdp_router_func xdp_prog_kern.o
t load -n dos -- --prog-name xdp_router_func xdp_prog_kern.o
t load -n tres -- --prog-name xdp_router_func xdp_prog_kern.o
t exec -n uno -- ./xdp-loader load --prog-name xdp_pass_func veth0 xdp_prog_kern.o
t exec -n dos -- ./xdp-loader load --prog-name xdp_pass_func veth0 xdp_prog_kern.o
t exec -n tres -- ./xdp-loader load --prog-name xdp_pass_func veth0 xdp_prog_kern.o

各インターフェースでxdp_statsを動かす。

sudo ./xdp_stats -d uno
sudo ./xdp_stats -d dos
sudo ./xdp_stats -d tres

各namespaceのvethから別のnamespaceにあるvethにpingを打つと、それぞれのIFでXDP_REDIRECTがカウントアップされる。
たとえばunoのvethからtresのvethにpingを打つ。

$ sudo ../testenv/testenv.sh enter --name uno
# ping 10.11.3.2 -c 1

すると、unoとtresのそれぞれでXDP_REDIRECTがカウントアップされるのが確認できる。

sudo ./xdp_stats -d unoの出力

XDP-action
XDP_ABORTED            0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000415
XDP_DROP               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000414
XDP_PASS               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000414
XDP_TX                 0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000414
XDP_REDIRECT           1 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000414

sudo ./xdp_stats -d tresの出力

XDP-action
XDP_ABORTED            0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000365
XDP_DROP               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000365
XDP_PASS               0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000366
XDP_TX                 0 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000366
XDP_REDIRECT           1 pkts (         0 pps)           0 Kbytes (     0 Mbits/s) period:2.000366

補足

packet03を行うに当たりxdp-tutorialに以下2つの修正を行った。

github.com

github.com

参考リンク

  1. RFC 1071 - Computing the Internet checksum

  2. samples/bpf/tcbpf1_kern.c - kernel/hikey-linaro - Git at Google

  3. linux/samples/bpf/xdp_fwd_kern.c at dccb07f2914cdab2ac3a5b6c98406f765acab803 · torvalds/linux · GitHub