xdp-tutorial packet01-parsingをやってみる

github.com

packet01-parsingでは以下を行う。

  • 指定したICMPパケットを許可し、それ以外のICMPパケットはドロップする。
  • ICMPパケットでないものは許可。

Assignment1

parse_ethhdr()のbouds checkのバグフィックスをする。

テスト環境構築

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

コード修正

--- a/packet01-parsing/xdp_prog_kern.c
+++ b/packet01-parsing/xdp_prog_kern.c
@@ -36,7 +36,7 @@ static __always_inline int parse_ethhdr(struct hdr_cursor *nh,
        /* Byte-count bounds check; check if current pointer + size of header
         * is after data_end.
         */
-       if (nh->pos + 1 > data_end)
+       if (nh->pos + hdrsize > data_end)
                return -1;

        nh->pos += hdrsize;

Assignment2

parse_ip6hdr()を実装して、IPv6パケットをパースする。

テスト環境構築

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

コード修正

--- a/packet01-parsing/xdp_prog_kern.c
+++ b/packet01-parsing/xdp_prog_kern.c
@@ -46,11 +46,18 @@ static __always_inline int parse_ethhdr(struct hdr_cursor *nh,
 }

 /* Assignment 2: Implement and use this */
-/*static __always_inline int parse_ip6hdr(struct hdr_cursor *nh,
+static __always_inline int parse_ip6hdr(struct hdr_cursor *nh,
                                        void *data_end,
                                        struct ipv6hdr **ip6hdr)
 {
-}*/
+       struct ipv6hdr *ip6h = nh->pos;
+
+       if (ip6h + 1 > data_end)
+               return -1;
+       nh->pos = ip6h + 1;
+       *ip6hdr = ip6h;
+       return ip6h->nexthdr;
+}

 /* Assignment 3: Implement and use this */
 /*static __always_inline int parse_icmp6hdr(struct hdr_cursor *nh,
@@ -65,6 +72,7 @@ int  xdp_parser_func(struct xdp_md *ctx)
        void *data_end = (void *)(long)ctx->data_end;
        void *data = (void *)(long)ctx->data;
        struct ethhdr *eth;
+       struct ipv6hdr *ipv6;

        /* 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
@@ -86,6 +94,9 @@ int  xdp_parser_func(struct xdp_md *ctx)
        nh_type = parse_ethhdr(&nh, data_end, &eth);
        if (nh_type != bpf_htons(ETH_P_IPV6))
                goto out;
+       nh_type = parse_ip6hdr(&nh, data_end, &ipv6);
+       if (nh_type != IPPROTO_ICMPV6)
+               goto out;

        /* Assignment additions go below here */

Assignment3

parse_icmp6hdr()を実装して、seqenceが奇数のICMPパケットのみを許可する。

コード修正

--- a/packet01-parsing/xdp_prog_kern.c
+++ b/packet01-parsing/xdp_prog_kern.c
@@ -60,11 +60,18 @@ static __always_inline int parse_ip6hdr(struct hdr_cursor *nh,
 }

 /* Assignment 3: Implement and use this */
-/*static __always_inline int parse_icmp6hdr(struct hdr_cursor *nh,
+static __always_inline int parse_icmp6hdr(struct hdr_cursor *nh,
                                          void *data_end,
                                          struct icmp6hdr **icmp6hdr)
 {
-}*/
+       struct icmp6hdr *icmp6h = nh->pos;
+
+       if (icmp6h + 1 > data_end)
+               return -1;
+       nh->pos = icmp6h + 1;
+       *icmp6hdr = icmp6h;
+       return bpf_ntohs(icmp6h->icmp6_sequence);
+}

 SEC("xdp")
 int  xdp_parser_func(struct xdp_md *ctx)
@@ -73,6 +80,8 @@ int  xdp_parser_func(struct xdp_md *ctx)
        void *data = (void *)(long)ctx->data;
        struct ethhdr *eth;
        struct ipv6hdr *ipv6;
+       struct icmp6hdr *icmp6;
+       int icmp_seq;

        /* 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
@@ -97,6 +106,9 @@ int  xdp_parser_func(struct xdp_md *ctx)
        nh_type = parse_ip6hdr(&nh, data_end, &ipv6);
        if (nh_type != IPPROTO_ICMPV6)
                goto out;
+       icmp_seq = parse_icmp6hdr(&nh, data_end, &icmp6);
+       if (icmp_seq % 2 == 1)
+               goto out;

        /* Assignment additions go below here */

TEST

$ make
$ sudo ./xdp-loader unload veth-packet01 --all
$ sudo ./xdp-loader load --prog-name xdp_parser_func --mode skb  veth-packet01 xdp_prog_kern.o
$ ping -6 fc00:dead:cafe:1::2
PING fc00:dead:cafe:1::2(fc00:dead:cafe:1::2) 56 data bytes
64 bytes from fc00:dead:cafe:1::2: icmp_seq=1 ttl=64 time=0.057 ms
64 bytes from fc00:dead:cafe:1::2: icmp_seq=3 ttl=64 time=0.082 ms
64 bytes from fc00:dead:cafe:1::2: icmp_seq=5 ttl=64 time=0.075 ms
^C
--- fc00:dead:cafe:1::2 ping statistics ---
5 packets transmitted, 3 received, 40% packet loss, time 4084ms
rtt min/avg/max/mdev = 0.057/0.071/0.082/0.010 ms

奇数シーケンスのパケットのみpingが通ってる。

Assignment4

VLANをサポートさせる。 また、通常のタグパケットだけでなく、ダブルタグVLANも考慮する。

テスト環境構築

以下コマンドでvlan環境を構築する。

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

下図のような環境が出来上がる。

試しにhost namespaceからveth0.2のアドレスに向けてpingを打ってみる。

$ ping -6 fc00:dead:cafe:2001::2 -I veth-packet01.2 -c 2
PING fc00:dead:cafe:2001::2(fc00:dead:cafe:2001::2) from fc00:dead:cafe:2001::1 veth-packet01.2: 56 data bytes
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=1 ttl=64 time=0.097 ms
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=2 ttl=64 time=0.099 ms

--- fc00:dead:cafe:2001::2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1008ms
rtt min/avg/max/mdev = 0.097/0.098/0.099/0.001 ms

veth-packet01.2で取得したキャプチャ

veth-packet01で取得したキャプチャ

veth-packet01.2(VLANインターフェース)でvlan tagの付与・取り外しがちゃんと行われている。

コード修正

--- a/packet01-parsing/xdp_prog_kern.c
+++ b/packet01-parsing/xdp_prog_kern.c
@@ -8,6 +8,7 @@
 #include <linux/icmpv6.h>
 #include <bpf/bpf_helpers.h>
 #include <bpf/bpf_endian.h>
+#include <pcap/vlan.h>
 /* Defines xdp_stats_map from packet04 */
 #include "../common/xdp_stats_kern_user.h"
 #include "../common/xdp_stats_kern.h"
@@ -17,6 +18,10 @@ struct hdr_cursor {
        void *pos;
 };

+static __always_inline int proto_is_vlan(__u16 h_proto)
+{
+       return !!(h_proto == bpf_htons(ETH_P_8021Q) || h_proto == bpf_htons(ETH_P_8021AD));
+}
 /* Packet parsing helpers.
  *
  * Each helper parses a packet header, including doing bounds checking, and
@@ -31,18 +36,38 @@ static __always_inline int parse_ethhdr(struct hdr_cursor *nh,
                                        struct ethhdr **ethhdr)
 {
        struct ethhdr *eth = nh->pos;
-       int hdrsize = sizeof(*eth);
+       __u16 *proto = NULL;
+       struct vlan_tag *vtag;

        /* Byte-count bounds check; check if current pointer + size of header
         * is after data_end.
         */
-       if (nh->pos + hdrsize > data_end)
+       if ((nh->pos + sizeof(eth->h_dest)) > data_end)
                return -1;
-
-       nh->pos += hdrsize;
+       nh->pos += sizeof(eth->h_dest);
+       if ((nh->pos + sizeof(eth->h_source)) > data_end)
+               return -1;
+       nh->pos += sizeof(eth->h_source);
+
+       int i = 0;
+       #pragma unroll
+       for (i = 0; i < 2; i++) {
+               if ((nh->pos + sizeof(vtag->vlan_tpid)) > data_end)
+                       return -1;
+               if (!proto_is_vlan(*(__u16 *)nh->pos))
+                       break;
+               if ((nh->pos + sizeof(struct vlan_tag)) > data_end)
+                       return -1;
+               nh->pos += sizeof(struct vlan_tag);
+       }
+
+       if ((nh->pos + sizeof(eth->h_proto)) > data_end)
+               return -1;
+       proto = (__u16 *)nh->pos;
+       nh->pos += sizeof(eth->h_proto);
        *ethhdr = eth;

-       return eth->h_proto; /* network-byte-order */
+       return *proto; /* network-byte-order */
 }

 /* Assignment 2: Implement and use this */

TEST

$ make
$ sudo ./xdp-loader unload veth-packet01 --all
$ sudo ./xdp-loader load --prog-name xdp_parser_func --mode skb  veth-packet01 xdp_prog_kern.o
$ ping -6 fc00:dead:cafe:2001::2
PING fc00:dead:cafe:2001::2(fc00:dead:cafe:2001::2) 56 data bytes
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=1 ttl=64 time=0.081 ms
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=3 ttl=64 time=0.092 ms
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=5 ttl=64 time=0.088 ms
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=7 ttl=64 time=0.091 ms
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=9 ttl=64 time=0.087 ms
^C
--- fc00:dead:cafe:2001::2 ping statistics ---
9 packets transmitted, 5 received, 44.4444% packet loss, time 8180ms
rtt min/avg/max/mdev = 0.081/0.087/0.092/0.003 ms

Assignment5

IPv4をサポートさせる。

テスト環境構築

以下コマンドでipv4環境を構築する。

sudo ../testenv/testenv.sh setup --name veth-packet01 --vlan --legacy-ip

確認した所、vlan interfaceにIPアドレスアサインされてないようなので、アサインする。

$ sudo ip addr add 192.168.101.1/24 dev veth-packet01.1
$ sudo ip addr add 192.168.102.1/24 dev veth-packet01.2
$ sudo ip netns exec veth-packet01 ip addr add 192.168.101.2/24 dev veth0.1
$ sudo ip netns exec veth-packet01 ip addr add 192.168.102.2/24 dev veth0.2

試しにhost namespaceからveth0.2のアドレスに向けてpingを打ってみる。

$ ping 192.168.102.2 -c 2
PING 192.168.102.2 (192.168.102.2) 56(84) bytes of data.
64 bytes from 192.168.102.2: icmp_seq=1 ttl=64 time=0.240 ms
64 bytes from 192.168.102.2: icmp_seq=2 ttl=64 time=0.091 ms

--- 192.168.102.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.091/0.165/0.240/0.074 ms

veth-packet01.2で取得したキャプチャ

veth-packet01で取得したキャプチャ

コード修正

--- a/packet01-parsing/xdp_prog_kern.c
+++ b/packet01-parsing/xdp_prog_kern.c
@@ -4,7 +4,9 @@
 #include <linux/in.h>
 #include <linux/if_ether.h>
 #include <linux/if_packet.h>
+#include <linux/ip.h>
 #include <linux/ipv6.h>
+#include <linux/icmp.h>
 #include <linux/icmpv6.h>
 #include <bpf/bpf_helpers.h>
 #include <bpf/bpf_endian.h>
@@ -70,6 +72,22 @@ static __always_inline int parse_ethhdr(struct hdr_cursor *nh,
        return *proto; /* network-byte-order */
 }

+static __always_inline int parse_iphdr(struct hdr_cursor *nh,
+                                       void *data_end,
+                                       struct iphdr **iphdr)
+{
+       struct iphdr *iph = nh->pos;
+
+       if (iph + 1 > data_end)
+               return -1;
+       int hdrsize = iph->ihl * 4;
+       if (nh->pos + hdrsize > data_end)
+               return -1;
+       nh->pos += hdrsize;
+       *iphdr = iph;
+       return iph->protocol;
+}
+
 /* Assignment 2: Implement and use this */
 static __always_inline int parse_ip6hdr(struct hdr_cursor *nh,
                                        void *data_end,
@@ -84,6 +102,19 @@ static __always_inline int parse_ip6hdr(struct hdr_cursor *nh,
        return ip6h->nexthdr;
 }

+static __always_inline int parse_icmphdr(struct hdr_cursor *nh,
+                                         void *data_end,
+                                         struct icmphdr **icmphdr)
+{
+       struct icmphdr *icmph = nh->pos;
+
+       if (icmph + 1 > data_end)
+               return -1;
+       nh->pos = icmph + 1;
+       *icmphdr = icmph;
+       return bpf_ntohs(icmph->un.echo.sequence);
+}
+
 /* Assignment 3: Implement and use this */
 static __always_inline int parse_icmp6hdr(struct hdr_cursor *nh,
                                          void *data_end,
@@ -104,7 +135,9 @@ int  xdp_parser_func(struct xdp_md *ctx)
        void *data_end = (void *)(long)ctx->data_end;
        void *data = (void *)(long)ctx->data;
        struct ethhdr *eth;
+       struct iphdr *ip;
        struct ipv6hdr *ipv6;
+       struct icmphdr *icmp;
        struct icmp6hdr *icmp6;
        int icmp_seq;

@@ -126,12 +159,19 @@ int  xdp_parser_func(struct xdp_md *ctx)
         * 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))
-               goto out;
-       nh_type = parse_ip6hdr(&nh, data_end, &ipv6);
-       if (nh_type != IPPROTO_ICMPV6)
+       if (nh_type == bpf_htons(ETH_P_IP)) {
+               nh_type = parse_iphdr(&nh, data_end, &ip);
+               if (nh_type != IPPROTO_ICMP)
+                       goto out;
+               icmp_seq = parse_icmphdr(&nh, data_end, &icmp);
+       } else if (nh_type == bpf_htons(ETH_P_IPV6)) {
+               nh_type = parse_ip6hdr(&nh, data_end, &ipv6);
+               if (nh_type != IPPROTO_ICMPV6)
+                       goto out;
+               icmp_seq = parse_icmp6hdr(&nh, data_end, &icmp6);
+       } else {
                goto out;
-       icmp_seq = parse_icmp6hdr(&nh, data_end, &icmp6);
+       }
        if (icmp_seq % 2 == 1)
                goto out;

TEST

$ make
$ sudo ./xdp-loader unload veth-packet01 --all
$ sudo ./xdp-loader load --prog-name xdp_parser_func --mode skb  veth-packet01 xdp_prog_kern.o
$ ping 192.168.102.2
PING 192.168.102.2 (192.168.102.2) 56(84) bytes of data.
64 bytes from 192.168.102.2: icmp_seq=1 ttl=64 time=0.204 ms
64 bytes from 192.168.102.2: icmp_seq=3 ttl=64 time=0.104 ms
64 bytes from 192.168.102.2: icmp_seq=5 ttl=64 time=0.091 ms
64 bytes from 192.168.102.2: icmp_seq=7 ttl=64 time=0.087 ms
64 bytes from 192.168.102.2: icmp_seq=9 ttl=64 time=0.083 ms
64 bytes from 192.168.102.2: icmp_seq=11 ttl=64 time=0.086 ms
--- 192.168.102.2 ping statistics ---
11 packets transmitted, 6 received, 45.4545% packet loss, time 10223ms
rtt min/avg/max/mdev = 0.083/0.109/0.204/0.042 ms
$ ping -6 fc00:dead:cafe:2001::2
PING fc00:dead:cafe:2001::2(fc00:dead:cafe:2001::2) 56 data bytes
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=1 ttl=64 time=0.074 ms
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=3 ttl=64 time=0.095 ms
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=5 ttl=64 time=0.088 ms
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=7 ttl=64 time=0.088 ms
64 bytes from fc00:dead:cafe:2001::2: icmp_seq=9 ttl=64 time=0.090 ms
--- fc00:dead:cafe:2001::2 ping statistics ---
10 packets transmitted, 5 received, 50% packet loss, time 9201ms
rtt min/avg/max/mdev = 0.074/0.087/0.095/0.007 ms

補足

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

sudo ../testenv/testenv.sh setup --name veth-packet01
sudo ../testenv/testenv.sh setup --name veth-packet01 --vlan
sudo ../testenv/testenv.sh setup --name veth-packet01 --vlan --legacy-ip

sudo ../testenv/testenv.sh enter --name veth-packet01
sudo ../testenv/testenv.sh teardown

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

sudo ./xdp-loader load --prog-name xdp_parser_func --mode skb  veth-packet01 xdp_prog_kern.o
sudo ./xdp-loader unload veth-packet01 --all
sudo ./xdp-loader status