xdp-tutorial packet02-rewritingをやってみる

github.com

bpf_xdp_adjust_head()はパケットの先頭ポインタをずらすことができる。 つまり、パケット長が長くしたり、パケット長を短くするときに使える。(例えば、encap処理やdecap処理)bpf_xdp_adjust_headのあるパラメータに正の値を与えると、パケット長が短くなり、負の値を与えるとパケット長が長くなる。

イメージとしてはこんな感じ。

 098 | 099 | 100 | 101 | ...         | 500
           ^                         ^
    - <--- | ---> +           - <--- | ---> +
    (start-pointer)           (end-pointer)

パケットの末尾のポインタをずらすbpf_xdp_adjust_tail()ってのもある。

Assignment1

TCP/UDPパケットのdst portを-1する。

テスト環境構築

sudo ../testenv/testenv.sh setup --name veth-packet02

コード修正

  • コード中のコメントを見ると、xdp_port_rewrite_func()に処理を実装するらしい。
  • xdp_port_rewrite_func()にはpacket01-pasingのときに実装した機能が記載されている。つまり、IP headerまでのパースにはこの関数の処理の一部をそのままコピペすればよい。
  • プロトコルごとのパース処理はpacket01-parsingのときは自前ですべて実装していたが、今回はこれが提供されている。実装はcommon/parsing_helpers.hに存在している。プロトコルごとのパース処理は原則これを利用する。※私がここで実装したものとは微妙に差異があるため注意。
--- a/packet02-rewriting/xdp_prog_kern.c
+++ b/packet02-rewriting/xdp_prog_kern.c
@@ -57,7 +57,54 @@ static __always_inline int vlan_tag_push(struct xdp_md *ctx,
 SEC("xdp")
 int xdp_port_rewrite_func(struct xdp_md *ctx)
 {
-       return XDP_PASS;
+       void *data_end = (void *)(long)ctx->data_end;
+       void *data = (void *)(long)ctx->data;
+
+       /* Default action XDP_PASS, imply everything we couldn't parse, or that
+        * we don't want to deal with, we just pass up the stack and let the
+        * kernel deal with it.
+        */
+       __u32 action = XDP_PASS; /* Default action */
+
+       /* These keep track of the next header type and iterator pointer */
+       struct hdr_cursor nh;
+       int nh_type;
+       nh.pos = data;
+
+       struct ethhdr *eth;
+
+       /* Packet parsing in steps: Get each header one at a time, aborting if
+        * parsing fails. Each helper function does sanity checking (is the
+        * header type in the packet correct?), and bounds checking.
+        */
+       nh_type = parse_ethhdr(&nh, data_end, &eth);
+
+       if (nh_type == bpf_htons(ETH_P_IPV6)) {
+               struct ipv6hdr *ip6h;
+               nh_type = parse_ip6hdr(&nh, data_end, &ip6h);
+       } else if (nh_type == bpf_htons(ETH_P_IP)) {
+               struct iphdr *iph;
+               nh_type = parse_iphdr(&nh, data_end, &iph);
+       }
+       if (nh_type == IPPROTO_TCP) {
+               struct tcphdr *tcph;
+               int hdrlen;
+               if ((hdrlen = parse_tcphdr(&nh, data_end, &tcph)) < 0) {
+                       goto out;
+               }
+               tcph->dest = bpf_htons(bpf_ntohs(tcph->dest) - 1);
+               //return XDP_PASS;
+       } else if (nh_type == IPPROTO_UDP) {
+               struct udphdr *udph;
+               int hdrlen;
+               if ((hdrlen = parse_udphdr(&nh, data_end, &udph)) < 0) {
+                       goto out;
+               }
+               udph->dest = bpf_htons(bpf_ntohs(udph->dest) - 1);
+               //return XDP_PASS;
+       }
+ out:
+       return xdp_stats_record_action(ctx, action);
 }

 /* VLAN swapper; will pop outermost VLAN tag if it exists, otherwise push a new

TEST

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

make
sudo ./xdp-loader unload veth-packet02 --all
sudo ./xdp-loader load --prog-name xdp_port_rewrite_func --mode skb  veth-packet02 xdp_prog_kern.o

dst portを2000に指定してudpパケットを送信する。

sudo ../testenv/testenv.sh exec -- socat - 'udp6:[fc00:dead:cafe:1::1]:2000'

veth-packet02でパケットをキャプチャするとdst portが2000から1999に変わってることが分かる。

Assignment2

一番外側にあるVLANタグを取り外す。

テスト環境構築

sudo ../testenv/testenv.sh setup --name veth-packet02 --vlan

コード修正

vlan_tag_pop()が定義されているので、処理はここに実装する。

--- a/packet02-rewriting/xdp_prog_kern.c
+++ b/packet02-rewriting/xdp_prog_kern.c
@@ -3,6 +3,8 @@
 #include <linux/in.h>
 #include <bpf/bpf_helpers.h>
 #include <bpf/bpf_endian.h>
+#include <pcap/vlan.h>
+#include <string.h>

 // The parsing helper functions from the packet01 lesson have moved here
 #include "../common/parsing_helpers.h"
@@ -16,30 +18,38 @@
  */
 static __always_inline int vlan_tag_pop(struct xdp_md *ctx, struct ethhdr *eth)
 {
-       /*
        void *data_end = (void *)(long)ctx->data_end;
        struct ethhdr eth_cpy;
-       struct vlan_hdr *vlh;
+       struct vlan_tag *vlh;
        __be16 h_proto;
-       */
        int vlid = -1;

-       /* Check if there is a vlan tag to pop */
-
        /* Still need to do bounds checking */

        /* Save vlan ID for returning, h_proto for updating Ethernet header */
+       memcpy(&vlh, (void *)eth + sizeof(eth->h_dest) + sizeof(eth->h_source), sizeof(struct vlan_tag));
+       vlid = bpf_ntohs(vlh->vlan_tci) & 0x0fff;
+       h_proto = *(__be16 *)((void *)eth + sizeof(eth->h_dest) + sizeof(eth->h_source) + sizeof(struct vlan_tag));

        /* Make a copy of the outer Ethernet header before we cut it off */
+       memcpy(&eth_cpy, eth, sizeof(struct ethhdr));

        /* Actually adjust the head pointer */
+       if (bpf_xdp_adjust_head(ctx, sizeof(struct vlan_tag)))
+               return -1;

        /* Need to re-evaluate data *and* data_end and do new bounds checking
         * after adjusting head
         */
+       eth = (void *)(long)ctx->data;
+       data_end = (void *)(long)ctx->data_end;
+       if (eth + 1 > data_end)
+               return -1;

        /* Copy back the old Ethernet header and update the proto type */

+       memcpy(eth, &eth_cpy, sizeof(struct ethhdr));
+       eth->h_proto = h_proto;

        return vlid;
 }

TEST

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

$ make
$ sudo ./xdp-loader unload veth-packet02 --all
$ sudo ./xdp-loader load --prog-name xdp_vlan_swap_func --mode skb  veth-packet02 xdp_prog_kern.o
$ sudo ../testenv/testenv.sh ping --vlan
Running ping from inside test environment:

PING fc00:dead:cafe:1001::1(fc00:dead:cafe:1001::1) 56 data bytes
64 bytes from fc00:dead:cafe:1001::1: icmp_seq=1 ttl=64 time=0.044 ms
64 bytes from fc00:dead:cafe:1001::1: icmp_seq=2 ttl=64 time=0.111 ms
^C
--- fc00:dead:cafe:1001::1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1027ms
rtt min/avg/max/mdev = 0.044/0.077/0.111/0.033 ms

veth-packet02でキャプチャすると以下のようになる。

icmp echo requestからvlanタグが消えているのが分かる。

Assignment3

VLANタグがついてなかったらVLANタグを付与する。

テスト環境構築

sudo ../testenv/testenv.sh setup --name veth-packet02

コード修正

vlan_tag_push()に処理を実装する。

--- a/packet02-rewriting/xdp_prog_kern.c
+++ b/packet02-rewriting/xdp_prog_kern.c
@@ -60,9 +60,31 @@ static __always_inline int vlan_tag_pop(struct xdp_md *ctx, struct ethhdr *eth)
 /* Pushes a new VLAN tag after the Ethernet header. Returns 0 on success,
  * -1 on failure.
  */
-static __always_inline int vlan_tag_push(struct xdp_md *ctx,
-                                        struct ethhdr *eth, int vlid)
-{
+static __always_inline int vlan_tag_push(struct xdp_md *ctx, struct ethhdr *eth,
+                                         int vlid) {
+       void *data_end = (void *)(long)ctx->data_end;
+       struct ethhdr eth_cpy;
+       struct vlan_tag vlh = {
+           .vlan_tpid = bpf_htons(ETH_P_8021Q),
+           .vlan_tci = bpf_htons(vlid),
+       };
+
+       if (eth + 1 > data_end) {
+         return -1;
+       }
+       memcpy(&eth_cpy, eth, sizeof(struct ethhdr));
+       if (bpf_xdp_adjust_head(ctx, -(int)sizeof(struct vlan_tag)))
+         return -1;
+       eth = (void *)(long)ctx->data;
+       data_end = (void *)(long)ctx->data_end;
+       if ((void *)eth + sizeof(struct ethhdr) + sizeof(struct vlan_tag) > data_end) {
+               return -1;
+       }
+       memcpy(eth, &eth_cpy, sizeof(struct ethhdr));
+       memcpy(&eth->h_proto, &vlh, sizeof(struct vlan_tag));
+       void *p = (void *)&eth + sizeof(eth->h_dest) + sizeof(eth->h_source) + sizeof(struct vlan_tag);
+       memcpy(p, &eth_cpy.h_proto, sizeof(eth_cpy.h_proto));
+
        return 0;
 }

TEST

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

$ make
$ sudo ./xdp-loader unload veth-packet02 --all
$ sudo ./xdp-loader load --prog-name xdp_vlan_swap_func --mode skb  veth-packet02 xdp_prog_kern.o

pingする。

$ sudo ../testenv/testenv.sh ping

veth-packet02でキャプチャすると以下のようになる。

icmp echo requestにvlanタグが付与されているのが分かる。

補足

testenv.shコピペ用コマンドメモ

sudo ../testenv/testenv.sh setup --name veth-packet02
sudo ../testenv/testenv.sh setup --name veth-packet02 --vlan
sudo ../testenv/testenv.sh setup --name veth-packet02 --vlan --legacy-ip
sudo ../testenv/testenv.sh enter --name veth-packet02
sudo ../testenv/testenv.sh teardown

xdp-loaderコピペ用コマンドメモ

sudo ./xdp-loader load --prog-name xdp_port_rewrite_func --mode skb  veth-packet02 xdp_prog_kern.o
sudo ./xdp-loader load --prog-name xdp_vlan_swap_func --mode skb  veth-packet02 xdp_prog_kern.o
sudo ./xdp-loader unload veth-packet02 --all
sudo ./xdp-loader status