linux/tools/testing/selftests/bpf/progs/test_xdp_vlan.c
<<
>>
Prefs
   1/* SPDX-License-Identifier: GPL-2.0
   2 *  Copyright(c) 2018 Jesper Dangaard Brouer.
   3 *
   4 * XDP/TC VLAN manipulation example
   5 *
   6 * GOTCHA: Remember to disable NIC hardware offloading of VLANs,
   7 * else the VLAN tags are NOT inlined in the packet payload:
   8 *
   9 *  # ethtool -K ixgbe2 rxvlan off
  10 *
  11 * Verify setting:
  12 *  # ethtool -k ixgbe2 | grep rx-vlan-offload
  13 *  rx-vlan-offload: off
  14 *
  15 */
  16#include <stddef.h>
  17#include <stdbool.h>
  18#include <string.h>
  19#include <linux/bpf.h>
  20#include <linux/if_ether.h>
  21#include <linux/if_vlan.h>
  22#include <linux/in.h>
  23#include <linux/pkt_cls.h>
  24
  25#include <bpf/bpf_helpers.h>
  26#include <bpf/bpf_endian.h>
  27
  28/* linux/if_vlan.h have not exposed this as UAPI, thus mirror some here
  29 *
  30 *      struct vlan_hdr - vlan header
  31 *      @h_vlan_TCI: priority and VLAN ID
  32 *      @h_vlan_encapsulated_proto: packet type ID or len
  33 */
  34struct _vlan_hdr {
  35        __be16 h_vlan_TCI;
  36        __be16 h_vlan_encapsulated_proto;
  37};
  38#define VLAN_PRIO_MASK          0xe000 /* Priority Code Point */
  39#define VLAN_PRIO_SHIFT         13
  40#define VLAN_CFI_MASK           0x1000 /* Canonical Format Indicator */
  41#define VLAN_TAG_PRESENT        VLAN_CFI_MASK
  42#define VLAN_VID_MASK           0x0fff /* VLAN Identifier */
  43#define VLAN_N_VID              4096
  44
  45struct parse_pkt {
  46        __u16 l3_proto;
  47        __u16 l3_offset;
  48        __u16 vlan_outer;
  49        __u16 vlan_inner;
  50        __u8  vlan_outer_offset;
  51        __u8  vlan_inner_offset;
  52};
  53
  54char _license[] SEC("license") = "GPL";
  55
  56static __always_inline
  57bool parse_eth_frame(struct ethhdr *eth, void *data_end, struct parse_pkt *pkt)
  58{
  59        __u16 eth_type;
  60        __u8 offset;
  61
  62        offset = sizeof(*eth);
  63        /* Make sure packet is large enough for parsing eth + 2 VLAN headers */
  64        if ((void *)eth + offset + (2*sizeof(struct _vlan_hdr)) > data_end)
  65                return false;
  66
  67        eth_type = eth->h_proto;
  68
  69        /* Handle outer VLAN tag */
  70        if (eth_type == bpf_htons(ETH_P_8021Q)
  71            || eth_type == bpf_htons(ETH_P_8021AD)) {
  72                struct _vlan_hdr *vlan_hdr;
  73
  74                vlan_hdr = (void *)eth + offset;
  75                pkt->vlan_outer_offset = offset;
  76                pkt->vlan_outer = bpf_ntohs(vlan_hdr->h_vlan_TCI)
  77                                & VLAN_VID_MASK;
  78                eth_type        = vlan_hdr->h_vlan_encapsulated_proto;
  79                offset += sizeof(*vlan_hdr);
  80        }
  81
  82        /* Handle inner (double) VLAN tag */
  83        if (eth_type == bpf_htons(ETH_P_8021Q)
  84            || eth_type == bpf_htons(ETH_P_8021AD)) {
  85                struct _vlan_hdr *vlan_hdr;
  86
  87                vlan_hdr = (void *)eth + offset;
  88                pkt->vlan_inner_offset = offset;
  89                pkt->vlan_inner = bpf_ntohs(vlan_hdr->h_vlan_TCI)
  90                                & VLAN_VID_MASK;
  91                eth_type        = vlan_hdr->h_vlan_encapsulated_proto;
  92                offset += sizeof(*vlan_hdr);
  93        }
  94
  95        pkt->l3_proto = bpf_ntohs(eth_type); /* Convert to host-byte-order */
  96        pkt->l3_offset = offset;
  97
  98        return true;
  99}
 100
 101/* Hint, VLANs are choosen to hit network-byte-order issues */
 102#define TESTVLAN 4011 /* 0xFAB */
 103// #define TO_VLAN  4000 /* 0xFA0 (hint 0xOA0 = 160) */
 104
 105SEC("xdp_drop_vlan_4011")
 106int  xdp_prognum0(struct xdp_md *ctx)
 107{
 108        void *data_end = (void *)(long)ctx->data_end;
 109        void *data     = (void *)(long)ctx->data;
 110        struct parse_pkt pkt = { 0 };
 111
 112        if (!parse_eth_frame(data, data_end, &pkt))
 113                return XDP_ABORTED;
 114
 115        /* Drop specific VLAN ID example */
 116        if (pkt.vlan_outer == TESTVLAN)
 117                return XDP_ABORTED;
 118        /*
 119         * Using XDP_ABORTED makes it possible to record this event,
 120         * via tracepoint xdp:xdp_exception like:
 121         *  # perf record -a -e xdp:xdp_exception
 122         *  # perf script
 123         */
 124        return XDP_PASS;
 125}
 126/*
 127Commands to setup VLAN on Linux to test packets gets dropped:
 128
 129 export ROOTDEV=ixgbe2
 130 export VLANID=4011
 131 ip link add link $ROOTDEV name $ROOTDEV.$VLANID type vlan id $VLANID
 132 ip link set dev  $ROOTDEV.$VLANID up
 133
 134 ip link set dev $ROOTDEV mtu 1508
 135 ip addr add 100.64.40.11/24 dev $ROOTDEV.$VLANID
 136
 137Load prog with ip tool:
 138
 139 ip link set $ROOTDEV xdp off
 140 ip link set $ROOTDEV xdp object xdp_vlan01_kern.o section xdp_drop_vlan_4011
 141
 142*/
 143
 144/* Changing VLAN to zero, have same practical effect as removing the VLAN. */
 145#define TO_VLAN 0
 146
 147SEC("xdp_vlan_change")
 148int  xdp_prognum1(struct xdp_md *ctx)
 149{
 150        void *data_end = (void *)(long)ctx->data_end;
 151        void *data     = (void *)(long)ctx->data;
 152        struct parse_pkt pkt = { 0 };
 153
 154        if (!parse_eth_frame(data, data_end, &pkt))
 155                return XDP_ABORTED;
 156
 157        /* Change specific VLAN ID */
 158        if (pkt.vlan_outer == TESTVLAN) {
 159                struct _vlan_hdr *vlan_hdr = data + pkt.vlan_outer_offset;
 160
 161                /* Modifying VLAN, preserve top 4 bits */
 162                vlan_hdr->h_vlan_TCI =
 163                        bpf_htons((bpf_ntohs(vlan_hdr->h_vlan_TCI) & 0xf000)
 164                                  | TO_VLAN);
 165        }
 166
 167        return XDP_PASS;
 168}
 169
 170/*
 171 * Show XDP+TC can cooperate, on creating a VLAN rewriter.
 172 * 1. Create a XDP prog that can "pop"/remove a VLAN header.
 173 * 2. Create a TC-bpf prog that egress can add a VLAN header.
 174 */
 175
 176#ifndef ETH_ALEN /* Ethernet MAC address length */
 177#define ETH_ALEN        6       /* bytes */
 178#endif
 179#define VLAN_HDR_SZ     4       /* bytes */
 180
 181SEC("xdp_vlan_remove_outer")
 182int  xdp_prognum2(struct xdp_md *ctx)
 183{
 184        void *data_end = (void *)(long)ctx->data_end;
 185        void *data     = (void *)(long)ctx->data;
 186        struct parse_pkt pkt = { 0 };
 187        char *dest;
 188
 189        if (!parse_eth_frame(data, data_end, &pkt))
 190                return XDP_ABORTED;
 191
 192        /* Skip packet if no outer VLAN was detected */
 193        if (pkt.vlan_outer_offset == 0)
 194                return XDP_PASS;
 195
 196        /* Moving Ethernet header, dest overlap with src, memmove handle this */
 197        dest = data;
 198        dest+= VLAN_HDR_SZ;
 199        /*
 200         * Notice: Taking over vlan_hdr->h_vlan_encapsulated_proto, by
 201         * only moving two MAC addrs (12 bytes), not overwriting last 2 bytes
 202         */
 203        __builtin_memmove(dest, data, ETH_ALEN * 2);
 204        /* Note: LLVM built-in memmove inlining require size to be constant */
 205
 206        /* Move start of packet header seen by Linux kernel stack */
 207        bpf_xdp_adjust_head(ctx, VLAN_HDR_SZ);
 208
 209        return XDP_PASS;
 210}
 211
 212static __always_inline
 213void shift_mac_4bytes_16bit(void *data)
 214{
 215        __u16 *p = data;
 216
 217        p[7] = p[5]; /* delete p[7] was vlan_hdr->h_vlan_TCI */
 218        p[6] = p[4]; /* delete p[6] was ethhdr->h_proto */
 219        p[5] = p[3];
 220        p[4] = p[2];
 221        p[3] = p[1];
 222        p[2] = p[0];
 223}
 224
 225static __always_inline
 226void shift_mac_4bytes_32bit(void *data)
 227{
 228        __u32 *p = data;
 229
 230        /* Assuming VLAN hdr present. The 4 bytes in p[3] that gets
 231         * overwritten, is ethhdr->h_proto and vlan_hdr->h_vlan_TCI.
 232         * The vlan_hdr->h_vlan_encapsulated_proto take over role as
 233         * ethhdr->h_proto.
 234         */
 235        p[3] = p[2];
 236        p[2] = p[1];
 237        p[1] = p[0];
 238}
 239
 240SEC("xdp_vlan_remove_outer2")
 241int  xdp_prognum3(struct xdp_md *ctx)
 242{
 243        void *data_end = (void *)(long)ctx->data_end;
 244        void *data     = (void *)(long)ctx->data;
 245        struct ethhdr *orig_eth = data;
 246        struct parse_pkt pkt = { 0 };
 247
 248        if (!parse_eth_frame(orig_eth, data_end, &pkt))
 249                return XDP_ABORTED;
 250
 251        /* Skip packet if no outer VLAN was detected */
 252        if (pkt.vlan_outer_offset == 0)
 253                return XDP_PASS;
 254
 255        /* Simply shift down MAC addrs 4 bytes, overwrite h_proto + TCI */
 256        shift_mac_4bytes_32bit(data);
 257
 258        /* Move start of packet header seen by Linux kernel stack */
 259        bpf_xdp_adjust_head(ctx, VLAN_HDR_SZ);
 260
 261        return XDP_PASS;
 262}
 263
 264/*=====================================
 265 *  BELOW: TC-hook based ebpf programs
 266 * ====================================
 267 * The TC-clsact eBPF programs (currently) need to be attach via TC commands
 268 */
 269
 270SEC("tc_vlan_push")
 271int _tc_progA(struct __sk_buff *ctx)
 272{
 273        bpf_skb_vlan_push(ctx, bpf_htons(ETH_P_8021Q), TESTVLAN);
 274
 275        return TC_ACT_OK;
 276}
 277/*
 278Commands to setup TC to use above bpf prog:
 279
 280export ROOTDEV=ixgbe2
 281export FILE=xdp_vlan01_kern.o
 282
 283# Re-attach clsact to clear/flush existing role
 284tc qdisc del dev $ROOTDEV clsact 2> /dev/null ;\
 285tc qdisc add dev $ROOTDEV clsact
 286
 287# Attach BPF prog EGRESS
 288tc filter add dev $ROOTDEV egress \
 289  prio 1 handle 1 bpf da obj $FILE sec tc_vlan_push
 290
 291tc filter show dev $ROOTDEV egress
 292*/
 293