Back to home page

OSCL-LXR

 
 

    


0001 /* SPDX-License-Identifier: GPL-2.0
0002  *  Copyright(c) 2018 Jesper Dangaard Brouer.
0003  *
0004  * XDP/TC VLAN manipulation example
0005  *
0006  * GOTCHA: Remember to disable NIC hardware offloading of VLANs,
0007  * else the VLAN tags are NOT inlined in the packet payload:
0008  *
0009  *  # ethtool -K ixgbe2 rxvlan off
0010  *
0011  * Verify setting:
0012  *  # ethtool -k ixgbe2 | grep rx-vlan-offload
0013  *  rx-vlan-offload: off
0014  *
0015  */
0016 #include <stddef.h>
0017 #include <stdbool.h>
0018 #include <string.h>
0019 #include <linux/bpf.h>
0020 #include <linux/if_ether.h>
0021 #include <linux/if_vlan.h>
0022 #include <linux/in.h>
0023 #include <linux/pkt_cls.h>
0024 
0025 #include <bpf/bpf_helpers.h>
0026 #include <bpf/bpf_endian.h>
0027 
0028 /* linux/if_vlan.h have not exposed this as UAPI, thus mirror some here
0029  *
0030  *  struct vlan_hdr - vlan header
0031  *  @h_vlan_TCI: priority and VLAN ID
0032  *  @h_vlan_encapsulated_proto: packet type ID or len
0033  */
0034 struct _vlan_hdr {
0035     __be16 h_vlan_TCI;
0036     __be16 h_vlan_encapsulated_proto;
0037 };
0038 #define VLAN_PRIO_MASK      0xe000 /* Priority Code Point */
0039 #define VLAN_PRIO_SHIFT     13
0040 #define VLAN_CFI_MASK       0x1000 /* Canonical Format Indicator */
0041 #define VLAN_TAG_PRESENT    VLAN_CFI_MASK
0042 #define VLAN_VID_MASK       0x0fff /* VLAN Identifier */
0043 #define VLAN_N_VID      4096
0044 
0045 struct parse_pkt {
0046     __u16 l3_proto;
0047     __u16 l3_offset;
0048     __u16 vlan_outer;
0049     __u16 vlan_inner;
0050     __u8  vlan_outer_offset;
0051     __u8  vlan_inner_offset;
0052 };
0053 
0054 char _license[] SEC("license") = "GPL";
0055 
0056 static __always_inline
0057 bool parse_eth_frame(struct ethhdr *eth, void *data_end, struct parse_pkt *pkt)
0058 {
0059     __u16 eth_type;
0060     __u8 offset;
0061 
0062     offset = sizeof(*eth);
0063     /* Make sure packet is large enough for parsing eth + 2 VLAN headers */
0064     if ((void *)eth + offset + (2*sizeof(struct _vlan_hdr)) > data_end)
0065         return false;
0066 
0067     eth_type = eth->h_proto;
0068 
0069     /* Handle outer VLAN tag */
0070     if (eth_type == bpf_htons(ETH_P_8021Q)
0071         || eth_type == bpf_htons(ETH_P_8021AD)) {
0072         struct _vlan_hdr *vlan_hdr;
0073 
0074         vlan_hdr = (void *)eth + offset;
0075         pkt->vlan_outer_offset = offset;
0076         pkt->vlan_outer = bpf_ntohs(vlan_hdr->h_vlan_TCI)
0077                 & VLAN_VID_MASK;
0078         eth_type        = vlan_hdr->h_vlan_encapsulated_proto;
0079         offset += sizeof(*vlan_hdr);
0080     }
0081 
0082     /* Handle inner (double) VLAN tag */
0083     if (eth_type == bpf_htons(ETH_P_8021Q)
0084         || eth_type == bpf_htons(ETH_P_8021AD)) {
0085         struct _vlan_hdr *vlan_hdr;
0086 
0087         vlan_hdr = (void *)eth + offset;
0088         pkt->vlan_inner_offset = offset;
0089         pkt->vlan_inner = bpf_ntohs(vlan_hdr->h_vlan_TCI)
0090                 & VLAN_VID_MASK;
0091         eth_type        = vlan_hdr->h_vlan_encapsulated_proto;
0092         offset += sizeof(*vlan_hdr);
0093     }
0094 
0095     pkt->l3_proto = bpf_ntohs(eth_type); /* Convert to host-byte-order */
0096     pkt->l3_offset = offset;
0097 
0098     return true;
0099 }
0100 
0101 /* Hint, VLANs are choosen to hit network-byte-order issues */
0102 #define TESTVLAN 4011 /* 0xFAB */
0103 // #define TO_VLAN  4000 /* 0xFA0 (hint 0xOA0 = 160) */
0104 
0105 SEC("xdp_drop_vlan_4011")
0106 int  xdp_prognum0(struct xdp_md *ctx)
0107 {
0108     void *data_end = (void *)(long)ctx->data_end;
0109     void *data     = (void *)(long)ctx->data;
0110     struct parse_pkt pkt = { 0 };
0111 
0112     if (!parse_eth_frame(data, data_end, &pkt))
0113         return XDP_ABORTED;
0114 
0115     /* Drop specific VLAN ID example */
0116     if (pkt.vlan_outer == TESTVLAN)
0117         return XDP_ABORTED;
0118     /*
0119      * Using XDP_ABORTED makes it possible to record this event,
0120      * via tracepoint xdp:xdp_exception like:
0121      *  # perf record -a -e xdp:xdp_exception
0122      *  # perf script
0123      */
0124     return XDP_PASS;
0125 }
0126 /*
0127 Commands to setup VLAN on Linux to test packets gets dropped:
0128 
0129  export ROOTDEV=ixgbe2
0130  export VLANID=4011
0131  ip link add link $ROOTDEV name $ROOTDEV.$VLANID type vlan id $VLANID
0132  ip link set dev  $ROOTDEV.$VLANID up
0133 
0134  ip link set dev $ROOTDEV mtu 1508
0135  ip addr add 100.64.40.11/24 dev $ROOTDEV.$VLANID
0136 
0137 Load prog with ip tool:
0138 
0139  ip link set $ROOTDEV xdp off
0140  ip link set $ROOTDEV xdp object xdp_vlan01_kern.o section xdp_drop_vlan_4011
0141 
0142 */
0143 
0144 /* Changing VLAN to zero, have same practical effect as removing the VLAN. */
0145 #define TO_VLAN 0
0146 
0147 SEC("xdp_vlan_change")
0148 int  xdp_prognum1(struct xdp_md *ctx)
0149 {
0150     void *data_end = (void *)(long)ctx->data_end;
0151     void *data     = (void *)(long)ctx->data;
0152     struct parse_pkt pkt = { 0 };
0153 
0154     if (!parse_eth_frame(data, data_end, &pkt))
0155         return XDP_ABORTED;
0156 
0157     /* Change specific VLAN ID */
0158     if (pkt.vlan_outer == TESTVLAN) {
0159         struct _vlan_hdr *vlan_hdr = data + pkt.vlan_outer_offset;
0160 
0161         /* Modifying VLAN, preserve top 4 bits */
0162         vlan_hdr->h_vlan_TCI =
0163             bpf_htons((bpf_ntohs(vlan_hdr->h_vlan_TCI) & 0xf000)
0164                   | TO_VLAN);
0165     }
0166 
0167     return XDP_PASS;
0168 }
0169 
0170 /*
0171  * Show XDP+TC can cooperate, on creating a VLAN rewriter.
0172  * 1. Create a XDP prog that can "pop"/remove a VLAN header.
0173  * 2. Create a TC-bpf prog that egress can add a VLAN header.
0174  */
0175 
0176 #ifndef ETH_ALEN /* Ethernet MAC address length */
0177 #define ETH_ALEN    6   /* bytes */
0178 #endif
0179 #define VLAN_HDR_SZ 4   /* bytes */
0180 
0181 SEC("xdp_vlan_remove_outer")
0182 int  xdp_prognum2(struct xdp_md *ctx)
0183 {
0184     void *data_end = (void *)(long)ctx->data_end;
0185     void *data     = (void *)(long)ctx->data;
0186     struct parse_pkt pkt = { 0 };
0187     char *dest;
0188 
0189     if (!parse_eth_frame(data, data_end, &pkt))
0190         return XDP_ABORTED;
0191 
0192     /* Skip packet if no outer VLAN was detected */
0193     if (pkt.vlan_outer_offset == 0)
0194         return XDP_PASS;
0195 
0196     /* Moving Ethernet header, dest overlap with src, memmove handle this */
0197     dest = data;
0198     dest+= VLAN_HDR_SZ;
0199     /*
0200      * Notice: Taking over vlan_hdr->h_vlan_encapsulated_proto, by
0201      * only moving two MAC addrs (12 bytes), not overwriting last 2 bytes
0202      */
0203     __builtin_memmove(dest, data, ETH_ALEN * 2);
0204     /* Note: LLVM built-in memmove inlining require size to be constant */
0205 
0206     /* Move start of packet header seen by Linux kernel stack */
0207     bpf_xdp_adjust_head(ctx, VLAN_HDR_SZ);
0208 
0209     return XDP_PASS;
0210 }
0211 
0212 static __always_inline
0213 void shift_mac_4bytes_16bit(void *data)
0214 {
0215     __u16 *p = data;
0216 
0217     p[7] = p[5]; /* delete p[7] was vlan_hdr->h_vlan_TCI */
0218     p[6] = p[4]; /* delete p[6] was ethhdr->h_proto */
0219     p[5] = p[3];
0220     p[4] = p[2];
0221     p[3] = p[1];
0222     p[2] = p[0];
0223 }
0224 
0225 static __always_inline
0226 void shift_mac_4bytes_32bit(void *data)
0227 {
0228     __u32 *p = data;
0229 
0230     /* Assuming VLAN hdr present. The 4 bytes in p[3] that gets
0231      * overwritten, is ethhdr->h_proto and vlan_hdr->h_vlan_TCI.
0232      * The vlan_hdr->h_vlan_encapsulated_proto take over role as
0233      * ethhdr->h_proto.
0234      */
0235     p[3] = p[2];
0236     p[2] = p[1];
0237     p[1] = p[0];
0238 }
0239 
0240 SEC("xdp_vlan_remove_outer2")
0241 int  xdp_prognum3(struct xdp_md *ctx)
0242 {
0243     void *data_end = (void *)(long)ctx->data_end;
0244     void *data     = (void *)(long)ctx->data;
0245     struct ethhdr *orig_eth = data;
0246     struct parse_pkt pkt = { 0 };
0247 
0248     if (!parse_eth_frame(orig_eth, data_end, &pkt))
0249         return XDP_ABORTED;
0250 
0251     /* Skip packet if no outer VLAN was detected */
0252     if (pkt.vlan_outer_offset == 0)
0253         return XDP_PASS;
0254 
0255     /* Simply shift down MAC addrs 4 bytes, overwrite h_proto + TCI */
0256     shift_mac_4bytes_32bit(data);
0257 
0258     /* Move start of packet header seen by Linux kernel stack */
0259     bpf_xdp_adjust_head(ctx, VLAN_HDR_SZ);
0260 
0261     return XDP_PASS;
0262 }
0263 
0264 /*=====================================
0265  *  BELOW: TC-hook based ebpf programs
0266  * ====================================
0267  * The TC-clsact eBPF programs (currently) need to be attach via TC commands
0268  */
0269 
0270 SEC("tc_vlan_push")
0271 int _tc_progA(struct __sk_buff *ctx)
0272 {
0273     bpf_skb_vlan_push(ctx, bpf_htons(ETH_P_8021Q), TESTVLAN);
0274 
0275     return TC_ACT_OK;
0276 }
0277 /*
0278 Commands to setup TC to use above bpf prog:
0279 
0280 export ROOTDEV=ixgbe2
0281 export FILE=xdp_vlan01_kern.o
0282 
0283 # Re-attach clsact to clear/flush existing role
0284 tc qdisc del dev $ROOTDEV clsact 2> /dev/null ;\
0285 tc qdisc add dev $ROOTDEV clsact
0286 
0287 # Attach BPF prog EGRESS
0288 tc filter add dev $ROOTDEV egress \
0289   prio 1 handle 1 bpf da obj $FILE sec tc_vlan_push
0290 
0291 tc filter show dev $ROOTDEV egress
0292 */