bpf_xdp_adjust_head()
はパケットの先頭ポインタをずらすことができる。
つまり、パケット長が長くしたり、パケット長を短くするときに使える。(例えば、encap処理やdecap処理)bpf_xdp_adjust_headのあるパラメータに正の値を与えると、パケット長が短くなり、負の値を与えるとパケット長が長くなる。
イメージとしてはこんな感じ。
098 | 099 | 100 | 101 | ... | 500 ^ ^ - <--- | ---> + - <--- | ---> + (start-pointer) (end-pointer)
パケットの末尾のポインタをずらすbpf_xdp_adjust_tail()
ってのもある。
Assignment1
テスト環境構築
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, ð); + + 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(ð_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, ð_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(ð_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, ð_cpy, sizeof(struct ethhdr)); + memcpy(ð->h_proto, &vlh, sizeof(struct vlan_tag)); + void *p = (void *)ð + sizeof(eth->h_dest) + sizeof(eth->h_source) + sizeof(struct vlan_tag); + memcpy(p, ð_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