test_xdp_vlan.c 7.3 KB

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