Skip to content

Commit 07aa509

Browse files
authored
Merge pull request #62 from xdp-project/tutorial29-improve-parsing
Tutorial: investigate compiler optimizations
2 parents 3a1c469 + cd9c0c1 commit 07aa509

File tree

7 files changed

+249
-4
lines changed

7 files changed

+249
-4
lines changed

common/common.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ $(COPY_STATS): $(LOADER_DIR)/${COPY_STATS:=.c} $(COMMON_H)
6868
make -C $(LOADER_DIR) $(COPY_STATS)
6969
cp $(LOADER_DIR)/$(COPY_STATS) $(COPY_STATS)
7070
# Needing xdp_stats imply depending on header files:
71-
EXTRA_DEPS := $(COMMON_DIR)/xdp_stats_kern.h $(COMMON_DIR)/xdp_stats_kern_user.h
71+
EXTRA_DEPS += $(COMMON_DIR)/xdp_stats_kern.h $(COMMON_DIR)/xdp_stats_kern_user.h
7272
endif
7373

7474
# For build dependency on this file, if it gets updated

common/parsing_helpers.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,22 @@ struct icmphdr_common {
5454
__sum16 cksum;
5555
};
5656

57-
#define VLAN_MAX_DEPTH 5
57+
/* Allow users of header file to redefine VLAN max depth */
58+
#ifndef VLAN_MAX_DEPTH
59+
#define VLAN_MAX_DEPTH 4
60+
#endif
5861

5962
static __always_inline int proto_is_vlan(__u16 h_proto)
6063
{
6164
return !!(h_proto == bpf_htons(ETH_P_8021Q) ||
6265
h_proto == bpf_htons(ETH_P_8021AD));
6366
}
6467

68+
/* Notice, parse_ethhdr() will skip VLAN tags, by advancing nh->pos and returns
69+
* next header EtherType, BUT the ethhdr pointer supplied still points to the
70+
* Ethernet header. Thus, caller can look at eth->h_proto to see if this was a
71+
* VLAN tagged packet.
72+
*/
6573
static __always_inline int parse_ethhdr(struct hdr_cursor *nh, void *data_end,
6674
struct ethhdr **ethhdr)
6775
{

packet-solutions/Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
22

33
XDP_TARGETS := xdp_prog_kern_02 xdp_prog_kern_03
4+
XDP_TARGETS += xdp_vlan01_kern
5+
XDP_TARGETS += xdp_vlan02_kern
46
USER_TARGETS := xdp_prog_user
57

68
LIBBPF_DIR = ../libbpf/src/
79
COMMON_DIR = ../common
810

911
COPY_LOADER := xdp_loader
1012
COPY_STATS := xdp_stats
11-
EXTRA_DEPS := $(COMMON_DIR)/parsing_helpers.h
13+
EXTRA_DEPS += $(COMMON_DIR)/parsing_helpers.h
1214

1315
include $(COMMON_DIR)/common.mk

packet-solutions/xdp_prog_kern_02.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* SPDX-License-Identifier: GPL-2.0 */
22
#include <linux/bpf.h>
33
#include <linux/in.h>
4+
5+
#include <linux/if_ether.h>
46
#include "bpf_helpers.h"
57
#include "bpf_endian.h"
68

packet-solutions/xdp_vlan01_kern.c

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
2+
#include <linux/bpf.h>
3+
#include <linux/if_ether.h>
4+
5+
#include "bpf_helpers.h"
6+
#include "bpf_endian.h"
7+
8+
/* NOTICE: Re-defining VLAN header levels to parse */
9+
#define VLAN_MAX_DEPTH 10
10+
//#include "../common/parsing_helpers.h"
11+
/*
12+
* NOTICE: Copied over parts of ../common/parsing_helpers.h
13+
* to make it easier to point out compiler optimizations
14+
*/
15+
16+
/* Header cursor to keep track of current parsing position */
17+
struct hdr_cursor {
18+
void *pos;
19+
};
20+
21+
static __always_inline int proto_is_vlan(__u16 h_proto)
22+
{
23+
return !!(h_proto == bpf_htons(ETH_P_8021Q) ||
24+
h_proto == bpf_htons(ETH_P_8021AD));
25+
}
26+
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; /* NOTICE: unsigned type */
35+
};
36+
37+
/* Notice, parse_ethhdr() will skip VLAN tags, by advancing nh->pos and returns
38+
* next header EtherType, BUT the ethhdr pointer supplied still points to the
39+
* Ethernet header. Thus, caller can look at eth->h_proto to see if this was a
40+
* VLAN tagged packet.
41+
*/
42+
static __always_inline int parse_ethhdr(struct hdr_cursor *nh, void *data_end,
43+
struct ethhdr **ethhdr)
44+
{
45+
struct ethhdr *eth = nh->pos;
46+
int hdrsize = sizeof(*eth);
47+
struct vlan_hdr *vlh;
48+
__u16 h_proto;
49+
int i;
50+
51+
/* Byte-count bounds check; check if current pointer + size of header
52+
* is after data_end.
53+
*/
54+
if (nh->pos + hdrsize > data_end)
55+
return -1;
56+
57+
nh->pos += hdrsize;
58+
*ethhdr = eth;
59+
vlh = nh->pos;
60+
h_proto = eth->h_proto;
61+
62+
/* Use loop unrolling to avoid the verifier restriction on loops;
63+
* support up to VLAN_MAX_DEPTH layers of VLAN encapsulation.
64+
*/
65+
#pragma unroll
66+
for (i = 0; i < VLAN_MAX_DEPTH; i++) {
67+
if (!proto_is_vlan(h_proto))
68+
break;
69+
70+
if (vlh + 1 > data_end)
71+
break;
72+
73+
h_proto = vlh->h_vlan_encapsulated_proto;
74+
vlh++;
75+
}
76+
77+
nh->pos = vlh;
78+
return bpf_ntohs(h_proto);
79+
}
80+
81+
SEC("xdp_vlan01")
82+
int xdp_vlan_01(struct xdp_md *ctx)
83+
{
84+
void *data_end = (void *)(long)ctx->data_end;
85+
void *data = (void *)(long)ctx->data;
86+
87+
/* These keep track of the next header type and iterator pointer */
88+
struct hdr_cursor nh;
89+
int nh_type;
90+
nh.pos = data;
91+
92+
struct ethhdr *eth;
93+
nh_type = parse_ethhdr(&nh, data_end, &eth);
94+
if (nh_type < 0)
95+
return XDP_ABORTED;
96+
97+
/* The LLVM compiler is very clever, and will remove above walking of
98+
* VLAN headers (the loop unroll).
99+
*
100+
* The returned value nh_type, variable (__u16) h_proto in
101+
* parse_ethhdr(), is only compared against a negative value (signed).
102+
* The compile see that it can remove the VLAN loop, because:
103+
* 1. h_proto = vlh->h_vlan_encapsulated_proto can only be >= 0
104+
* 2. we never read nh->pos (so it removes nh->pos = vlh;).
105+
*/
106+
107+
/* Accessing eth pointer is still valid after compiler optimization */
108+
if (proto_is_vlan(eth->h_proto))
109+
return XDP_DROP;
110+
111+
/* Hint: to inspect BPF byte-code run:
112+
* llvm-objdump -S xdp_vlan01_kern.o
113+
*/
114+
return XDP_PASS;
115+
}

packet-solutions/xdp_vlan02_kern.c

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
2+
#include <linux/bpf.h>
3+
#include <linux/if_ether.h>
4+
#include <linux/in.h>
5+
6+
#include "bpf_helpers.h"
7+
#include "bpf_endian.h"
8+
9+
/* NOTICE: Re-defining VLAN header levels to parse */
10+
#define VLAN_MAX_DEPTH 10
11+
#include "../common/parsing_helpers.h"
12+
13+
#define VLAN_VID_MASK 0x0fff /* VLAN Identifier */
14+
struct vlans {
15+
__u16 id[VLAN_MAX_DEPTH];
16+
};
17+
18+
/* Based on parse_ethhdr() */
19+
static __always_inline int __parse_ethhdr_vlan(struct hdr_cursor *nh,
20+
void *data_end,
21+
struct ethhdr **ethhdr,
22+
struct vlans* vlans)
23+
{
24+
struct ethhdr *eth = nh->pos;
25+
int hdrsize = sizeof(*eth);
26+
struct vlan_hdr *vlh;
27+
__u16 h_proto;
28+
int i;
29+
30+
/* Byte-count bounds check; check if current pointer + size of header
31+
* is after data_end.
32+
*/
33+
if (nh->pos + hdrsize > data_end)
34+
return -1;
35+
36+
nh->pos += hdrsize;
37+
*ethhdr = eth;
38+
vlh = nh->pos;
39+
h_proto = eth->h_proto;
40+
41+
/* Use loop unrolling to avoid the verifier restriction on loops;
42+
* support up to VLAN_MAX_DEPTH layers of VLAN encapsulation.
43+
*/
44+
#pragma unroll
45+
for (i = 0; i < VLAN_MAX_DEPTH; i++) {
46+
if (!proto_is_vlan(h_proto))
47+
break;
48+
49+
if (vlh + 1 > data_end)
50+
break;
51+
52+
h_proto = vlh->h_vlan_encapsulated_proto;
53+
if (vlans) {
54+
vlans->id[i] = vlh->h_vlan_TCI & VLAN_VID_MASK;
55+
}
56+
vlh++;
57+
}
58+
59+
nh->pos = vlh;
60+
return bpf_ntohs(h_proto);
61+
}
62+
63+
SEC("xdp_vlan02")
64+
int xdp_vlan_02(struct xdp_md *ctx)
65+
{
66+
void *data_end = (void *)(long)ctx->data_end;
67+
void *data = (void *)(long)ctx->data;
68+
69+
/* These keep track of the next header type and iterator pointer */
70+
struct hdr_cursor nh;
71+
int eth_type;
72+
nh.pos = data;
73+
74+
struct vlans vlans;
75+
76+
struct ethhdr *eth;
77+
eth_type = __parse_ethhdr_vlan(&nh, data_end, &eth, &vlans);
78+
79+
if (eth_type < 0)
80+
return XDP_ABORTED;
81+
82+
/* The LLVM compiler is very clever, it sees that program only access
83+
* 2nd "inner" vlan (array index 1), and only does loop unroll of 2, and
84+
* only does the VLAN_VID_MASK in the 2nd "inner" vlan case.
85+
*/
86+
if (vlans.id[1] == 42)
87+
return XDP_ABORTED;
88+
89+
/* If using eth_type (even compare against zero), it will cause full
90+
* loop unroll and walking all VLANs (for VLAN_MAX_DEPTH). Still only
91+
* "inner" VLAN is masked out.
92+
*/
93+
#if 0
94+
if (eth_type == 0)
95+
return XDP_PASS;
96+
#endif
97+
98+
/* Unless we only want to manipulate VLAN, then next step will naturally
99+
* be parsing the next L3 headers. This (also) cause compiler to create
100+
* VLAN loop, as this uses nh->pos
101+
*/
102+
#if 0
103+
int ip_type;
104+
struct iphdr *iphdr;
105+
if (eth_type == ETH_P_IP) {
106+
ip_type = parse_iphdr(&nh, data_end, &iphdr);
107+
if (eth_type < 0)
108+
return XDP_ABORTED;
109+
110+
if (ip_type == IPPROTO_UDP)
111+
return XDP_DROP;
112+
}
113+
#endif
114+
/* Hint: to inspect BPF byte-code run:
115+
* llvm-objdump -S xdp_vlan02_kern.o
116+
*/
117+
return XDP_PASS;
118+
}

0 commit comments

Comments
 (0)