1 /* SPDX-License-Identifier: GPL-2.0
2 * Copyright(c) 2018 Jesper Dangaard Brouer.
4 * XDP/TC VLAN manipulation example
6 * GOTCHA: Remember to disable NIC hardware offloading of VLANs,
7 * else the VLAN tags are NOT inlined in the packet payload:
9 * # ethtool -K ixgbe2 rxvlan off
12 * # ethtool -k ixgbe2 | grep rx-vlan-offload
13 * rx-vlan-offload: off
19 #include <linux/bpf.h>
20 #include <linux/if_ether.h>
21 #include <linux/if_vlan.h>
23 #include <linux/pkt_cls.h>
25 #include "bpf_helpers.h"
26 #include "bpf_endian.h"
28 /* linux/if_vlan.h have not exposed this as UAPI, thus mirror some here
30 * struct vlan_hdr - vlan header
31 * @h_vlan_TCI: priority and VLAN ID
32 * @h_vlan_encapsulated_proto: packet type ID or len
36 __be16 h_vlan_encapsulated_proto
;
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
50 __u8 vlan_outer_offset
;
51 __u8 vlan_inner_offset
;
54 char _license
[] SEC("license") = "GPL";
56 static __always_inline
57 bool parse_eth_frame(struct ethhdr
*eth
, void *data_end
, struct parse_pkt
*pkt
)
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
)
67 eth_type
= eth
->h_proto
;
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
;
74 vlan_hdr
= (void *)eth
+ offset
;
75 pkt
->vlan_outer_offset
= offset
;
76 pkt
->vlan_outer
= bpf_ntohs(vlan_hdr
->h_vlan_TCI
)
78 eth_type
= vlan_hdr
->h_vlan_encapsulated_proto
;
79 offset
+= sizeof(*vlan_hdr
);
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
;
87 vlan_hdr
= (void *)eth
+ offset
;
88 pkt
->vlan_inner_offset
= offset
;
89 pkt
->vlan_inner
= bpf_ntohs(vlan_hdr
->h_vlan_TCI
)
91 eth_type
= vlan_hdr
->h_vlan_encapsulated_proto
;
92 offset
+= sizeof(*vlan_hdr
);
95 pkt
->l3_proto
= bpf_ntohs(eth_type
); /* Convert to host-byte-order */
96 pkt
->l3_offset
= offset
;
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) */
105 SEC("xdp_drop_vlan_4011")
106 int xdp_prognum0(struct xdp_md
*ctx
)
108 void *data_end
= (void *)(long)ctx
->data_end
;
109 void *data
= (void *)(long)ctx
->data
;
110 struct parse_pkt pkt
= { 0 };
112 if (!parse_eth_frame(data
, data_end
, &pkt
))
115 /* Drop specific VLAN ID example */
116 if (pkt
.vlan_outer
== TESTVLAN
)
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
127 Commands to setup VLAN on Linux to test packets gets dropped:
129 export ROOTDEV=ixgbe2
131 ip link add link $ROOTDEV name $ROOTDEV.$VLANID type vlan id $VLANID
132 ip link set dev $ROOTDEV.$VLANID up
134 ip link set dev $ROOTDEV mtu 1508
135 ip addr add 100.64.40.11/24 dev $ROOTDEV.$VLANID
137 Load prog with ip tool:
139 ip link set $ROOTDEV xdp off
140 ip link set $ROOTDEV xdp object xdp_vlan01_kern.o section xdp_drop_vlan_4011
144 /* Changing VLAN to zero, have same practical effect as removing the VLAN. */
147 SEC("xdp_vlan_change")
148 int xdp_prognum1(struct xdp_md
*ctx
)
150 void *data_end
= (void *)(long)ctx
->data_end
;
151 void *data
= (void *)(long)ctx
->data
;
152 struct parse_pkt pkt
= { 0 };
154 if (!parse_eth_frame(data
, data_end
, &pkt
))
157 /* Change specific VLAN ID */
158 if (pkt
.vlan_outer
== TESTVLAN
) {
159 struct _vlan_hdr
*vlan_hdr
= data
+ pkt
.vlan_outer_offset
;
161 /* Modifying VLAN, preserve top 4 bits */
162 vlan_hdr
->h_vlan_TCI
=
163 bpf_htons((bpf_ntohs(vlan_hdr
->h_vlan_TCI
) & 0xf000)
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.
176 #ifndef ETH_ALEN /* Ethernet MAC address length */
177 #define ETH_ALEN 6 /* bytes */
179 #define VLAN_HDR_SZ 4 /* bytes */
181 SEC("xdp_vlan_remove_outer")
182 int xdp_prognum2(struct xdp_md
*ctx
)
184 void *data_end
= (void *)(long)ctx
->data_end
;
185 void *data
= (void *)(long)ctx
->data
;
186 struct parse_pkt pkt
= { 0 };
189 if (!parse_eth_frame(data
, data_end
, &pkt
))
192 /* Skip packet if no outer VLAN was detected */
193 if (pkt
.vlan_outer_offset
== 0)
196 /* Moving Ethernet header, dest overlap with src, memmove handle this */
200 * Notice: Taking over vlan_hdr->h_vlan_encapsulated_proto, by
201 * only moving two MAC addrs (12 bytes), not overwriting last 2 bytes
203 __builtin_memmove(dest
, data
, ETH_ALEN
* 2);
204 /* Note: LLVM built-in memmove inlining require size to be constant */
206 /* Move start of packet header seen by Linux kernel stack */
207 bpf_xdp_adjust_head(ctx
, VLAN_HDR_SZ
);
212 static __always_inline
213 void shift_mac_4bytes_16bit(void *data
)
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 */
225 static __always_inline
226 void shift_mac_4bytes_32bit(void *data
)
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
240 SEC("xdp_vlan_remove_outer2")
241 int xdp_prognum3(struct xdp_md
*ctx
)
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 };
248 if (!parse_eth_frame(orig_eth
, data_end
, &pkt
))
251 /* Skip packet if no outer VLAN was detected */
252 if (pkt
.vlan_outer_offset
== 0)
255 /* Simply shift down MAC addrs 4 bytes, overwrite h_proto + TCI */
256 shift_mac_4bytes_32bit(data
);
258 /* Move start of packet header seen by Linux kernel stack */
259 bpf_xdp_adjust_head(ctx
, VLAN_HDR_SZ
);
264 /*=====================================
265 * BELOW: TC-hook based ebpf programs
266 * ====================================
267 * The TC-clsact eBPF programs (currently) need to be attach via TC commands
271 int _tc_progA(struct __sk_buff
*ctx
)
273 bpf_skb_vlan_push(ctx
, bpf_htons(ETH_P_8021Q
), TESTVLAN
);
278 Commands to setup TC to use above bpf prog:
280 export ROOTDEV=ixgbe2
281 export FILE=xdp_vlan01_kern.o
283 # Re-attach clsact to clear/flush existing role
284 tc qdisc del dev $ROOTDEV clsact 2> /dev/null ;\
285 tc qdisc add dev $ROOTDEV clsact
287 # Attach BPF prog EGRESS
288 tc filter add dev $ROOTDEV egress \
289 prio 1 handle 1 bpf da obj $FILE sec tc_vlan_push
291 tc filter show dev $ROOTDEV egress