Branch data Line data Source code
1 : : /* SPDX-License-Identifier: GPL-2.0-only */
2 : : /*
3 : : * Copyright (c) Meta Platforms, Inc. and affiliates.
4 : : */
5 : :
6 : : #include "cgen/matcher/packet.h"
7 : :
8 : : #include <linux/bpf.h>
9 : : #include <linux/bpf_common.h>
10 : : #include <linux/in.h> // NOLINT
11 : : #include <linux/ipv6.h>
12 : :
13 : : #include <assert.h>
14 : : #include <errno.h>
15 : : #include <stdint.h>
16 : :
17 : : #include <bpfilter/helper.h>
18 : : #include <bpfilter/logger.h>
19 : : #include <bpfilter/matcher.h>
20 : :
21 : : #include "cgen/matcher/cmp.h"
22 : : #include "cgen/matcher/meta.h"
23 : : #include "cgen/matcher/set.h"
24 : : #include "cgen/program.h"
25 : : #include "cgen/stub.h"
26 : : #include "filter.h"
27 : :
28 : : /**
29 : : * Packet matcher codegen follows a three-stage pipeline:
30 : : *
31 : : * 1. Protocol check: `_bf_program_generate_rule()` (in program.c)
32 : : * emits deduplicated protocol guards before the matcher loop,
33 : : * so each L3/L4 protocol is verified at most once per rule.
34 : : *
35 : : * 2. Header + field load: `bf_stub_load_header()` loads the header
36 : : * base address into `R6`, then `_bf_matcher_pkt_load_field()` reads
37 : : * the target field into the specified register (and `reg+1` for
38 : : * 128-bit values such as IPv6 addresses).
39 : : *
40 : : * 3. Comparison: A `bf_cmp_*` function compares the value in the
41 : : * specified register against the matcher's reference payload.
42 : : */
43 : :
44 : : #define BF_IPV6_EH_HOPOPTS(x) ((x) << 0)
45 : : #define BF_IPV6_EH_ROUTING(x) ((x) << 1)
46 : : #define BF_IPV6_EH_FRAGMENT(x) ((x) << 2)
47 : : #define BF_IPV6_EH_AH(x) ((x) << 3)
48 : : #define BF_IPV6_EH_DSTOPTS(x) ((x) << 4)
49 : : #define BF_IPV6_EH_MH(x) ((x) << 5)
50 : :
51 : : /**
52 : : * @brief Load a packet field from the header into the specified register.
53 : : *
54 : : * `R6` must already point to the header base. For 128-bit fields (IPv6
55 : : * addresses), the low 8 bytes are loaded into `reg` and the high 8 bytes into
56 : : * `reg + 1`.
57 : : *
58 : : * @param program Program to emit into. Can't be NULL.
59 : : * @param meta Matcher metadata describing field offset and size. Can't be NULL.
60 : : * @param reg BPF register to load the value into.
61 : : * @return 0 on success, negative errno on error.
62 : : */
63 : 1546 : static int _bf_matcher_pkt_load_field(struct bf_program *program,
64 : : const struct bf_matcher_meta *meta,
65 : : int reg)
66 : : {
67 [ + + + - : 1546 : switch (meta->hdr_payload_size) {
+ - ]
68 : 536 : case 1:
69 [ - + ]: 536 : EMIT(program,
70 : : BPF_LDX_MEM(BPF_B, reg, BPF_REG_6, meta->hdr_payload_offset));
71 : 536 : break;
72 : 440 : case 2:
73 [ - + ]: 440 : EMIT(program,
74 : : BPF_LDX_MEM(BPF_H, reg, BPF_REG_6, meta->hdr_payload_offset));
75 : 440 : break;
76 : 240 : case 4:
77 [ - + ]: 240 : EMIT(program,
78 : : BPF_LDX_MEM(BPF_W, reg, BPF_REG_6, meta->hdr_payload_offset));
79 : 240 : break;
80 : 0 : case 8:
81 [ # # ]: 0 : EMIT(program,
82 : : BPF_LDX_MEM(BPF_DW, reg, BPF_REG_6, meta->hdr_payload_offset));
83 : 0 : break;
84 : 330 : case 16:
85 [ - + ]: 330 : EMIT(program,
86 : : BPF_LDX_MEM(BPF_DW, reg, BPF_REG_6, meta->hdr_payload_offset));
87 [ - + ]: 330 : EMIT(program, BPF_LDX_MEM(BPF_DW, reg + 1, BPF_REG_6,
88 : : meta->hdr_payload_offset + 8));
89 : 330 : break;
90 : : default:
91 : : return -EINVAL;
92 : : }
93 : :
94 : : return 0;
95 : : }
96 : :
97 : 1546 : static int _bf_matcher_pkt_load(struct bf_program *program,
98 : : const struct bf_matcher_meta *meta, int reg)
99 : : {
100 : : int r;
101 : :
102 : 1546 : r = bf_stub_load_header(program, meta, BPF_REG_6);
103 [ + - ]: 1546 : if (r)
104 : : return r;
105 : :
106 : 1546 : return _bf_matcher_pkt_load_field(program, meta, reg);
107 : : }
108 : :
109 : : /**
110 : : * @brief Generic load + value compare for matchers whose field size and offset
111 : : * are fully described by `_bf_matcher_metas`.
112 : : *
113 : : * Emits: header load -> field load -> `bf_cmp_value`.
114 : : *
115 : : * @param program Program to generate bytecode into. Can't be NULL.
116 : : * @param matcher Matcher to generate bytecode for. Can't be NULL.
117 : : * @param meta Matcher metadata describing field size and offset. Can't be NULL.
118 : : * @return 0 on success, negative errno on error.
119 : : */
120 : 576 : static int _bf_matcher_pkt_load_and_cmp(struct bf_program *program,
121 : : const struct bf_matcher *matcher,
122 : : const struct bf_matcher_meta *meta)
123 : : {
124 : : int r;
125 : :
126 : 576 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
127 [ + - ]: 576 : if (r)
128 : : return r;
129 : :
130 : 576 : return bf_cmp_value(program, bf_matcher_get_op(matcher),
131 : 576 : bf_matcher_payload(matcher), meta->hdr_payload_size,
132 : : BPF_REG_1);
133 : : }
134 : :
135 : 370 : static int _bf_matcher_pkt_generate_net(struct bf_program *program,
136 : : const struct bf_matcher *matcher,
137 : : const struct bf_matcher_meta *meta)
138 : : {
139 : 370 : const uint32_t prefixlen = *(const uint32_t *)bf_matcher_payload(matcher);
140 : 370 : const void *data =
141 : 370 : (const uint8_t *)bf_matcher_payload(matcher) + sizeof(uint32_t);
142 : : int r;
143 : :
144 : 370 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
145 [ + - ]: 370 : if (r)
146 : : return r;
147 : :
148 : 370 : return bf_cmp_masked_value(program, bf_matcher_get_op(matcher), data,
149 : 370 : prefixlen, meta->hdr_payload_size, BPF_REG_1);
150 : : }
151 : :
152 : 440 : static int _bf_matcher_pkt_generate_port(struct bf_program *program,
153 : : const struct bf_matcher *matcher,
154 : : const struct bf_matcher_meta *meta)
155 : : {
156 : : int r;
157 : :
158 : 440 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
159 [ + - ]: 440 : if (r)
160 : : return r;
161 : :
162 [ + + ]: 440 : if (bf_matcher_get_op(matcher) == BF_MATCHER_RANGE) {
163 : 120 : uint16_t *ports = (uint16_t *)bf_matcher_payload(matcher);
164 : : /* Convert the big-endian value stored in the packet into a
165 : : * little-endian value for x86 and arm before comparing it to the
166 : : * reference value. This is a JLT/JGT comparison, we need to have the
167 : : * MSB where the machine expects then. */
168 [ - + ]: 120 : EMIT(program, BPF_BSWAP(BPF_REG_1, 16));
169 : 120 : return bf_cmp_range(program, ports[0], ports[1], BPF_REG_1);
170 : : }
171 : :
172 : 320 : return bf_cmp_value(program, bf_matcher_get_op(matcher),
173 : 320 : bf_matcher_payload(matcher), meta->hdr_payload_size,
174 : : BPF_REG_1);
175 : : }
176 : :
177 : : static int
178 : 140 : _bf_matcher_pkt_generate_tcp_flags(struct bf_program *program,
179 : : const struct bf_matcher *matcher,
180 : : const struct bf_matcher_meta *meta)
181 : : {
182 : : int r;
183 : :
184 : 140 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
185 [ + - ]: 140 : if (r)
186 : : return r;
187 : :
188 [ + + ]: 140 : switch (bf_matcher_get_op(matcher)) {
189 : 80 : case BF_MATCHER_ANY:
190 : : case BF_MATCHER_ALL:
191 : 80 : return bf_cmp_bitfield(program, bf_matcher_get_op(matcher),
192 : 80 : *(uint8_t *)bf_matcher_payload(matcher),
193 : : BPF_REG_1);
194 : 60 : default:
195 : 60 : return bf_cmp_value(program, bf_matcher_get_op(matcher),
196 : 60 : bf_matcher_payload(matcher), meta->hdr_payload_size,
197 : : BPF_REG_1);
198 : : }
199 : : }
200 : :
201 : : static int
202 : 100 : _bf_matcher_pkt_generate_ip6_nexthdr(struct bf_program *program,
203 : : const struct bf_matcher *matcher)
204 : : {
205 : 100 : const uint8_t ehdr = *(uint8_t *)bf_matcher_payload(matcher);
206 : : uint8_t eh_mask;
207 : :
208 [ + + ]: 100 : switch (ehdr) {
209 : 20 : case IPPROTO_HOPOPTS:
210 : : case IPPROTO_ROUTING:
211 : : case IPPROTO_DSTOPTS:
212 : : case IPPROTO_FRAGMENT:
213 : : case IPPROTO_AH:
214 : : case IPPROTO_MH:
215 [ + + ]: 20 : eh_mask = (BF_IPV6_EH_HOPOPTS(ehdr == IPPROTO_HOPOPTS) |
216 [ + - ]: 10 : BF_IPV6_EH_ROUTING(ehdr == IPPROTO_ROUTING) |
217 [ - + ]: 20 : BF_IPV6_EH_FRAGMENT(ehdr == IPPROTO_FRAGMENT) |
218 [ - + ]: 20 : BF_IPV6_EH_AH(ehdr == IPPROTO_AH) |
219 [ + - ]: 20 : BF_IPV6_EH_DSTOPTS(ehdr == IPPROTO_DSTOPTS) |
220 : : BF_IPV6_EH_MH(ehdr == IPPROTO_MH));
221 [ - + ]: 20 : EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_10,
222 : : BF_PROG_CTX_OFF(ipv6_eh)));
223 [ - + ]: 20 : EMIT(program, BPF_ALU64_IMM(BPF_AND, BPF_REG_1, eh_mask));
224 [ - + - + ]: 20 : EMIT_FIXUP_JMP_NEXT_RULE(
225 : : program, BPF_JMP_IMM((bf_matcher_get_op(matcher) == BF_MATCHER_EQ) ?
226 : : BPF_JEQ :
227 : : BPF_JNE,
228 : : BPF_REG_1, 0, 0));
229 : 20 : break;
230 : 80 : default:
231 : : /* check l4 protocols using `BPF_REG_8` */
232 [ + + - + ]: 90 : EMIT_FIXUP_JMP_NEXT_RULE(
233 : : program, BPF_JMP_IMM((bf_matcher_get_op(matcher) == BF_MATCHER_EQ) ?
234 : : BPF_JNE :
235 : : BPF_JEQ,
236 : : BPF_REG_8, ehdr, 0));
237 : 80 : break;
238 : : }
239 : :
240 : : return 0;
241 : : }
242 : :
243 : 20 : static int _bf_matcher_pkt_generate_ip4_dscp(struct bf_program *program,
244 : : const struct bf_matcher *matcher,
245 : : const struct bf_matcher_meta *meta)
246 : : {
247 : : uint8_t dscp;
248 : : int r;
249 : :
250 : 20 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
251 [ + - ]: 20 : if (r)
252 : : return r;
253 : :
254 : 20 : dscp = *(uint8_t *)bf_matcher_payload(matcher);
255 : :
256 : : /* IPv4 TOS byte: [DSCP 6b] [ECN 2b]. Mask with 0xfc to isolate
257 : : * the 6-bit DSCP field, then compare against dscp << 2. */
258 [ - + ]: 20 : EMIT(program, BPF_ALU64_IMM(BPF_AND, BPF_REG_1, 0xfc));
259 : :
260 [ + + - + ]: 30 : EMIT_FIXUP_JMP_NEXT_RULE(
261 : : program,
262 : : BPF_JMP_IMM(bf_matcher_get_op(matcher) == BF_MATCHER_EQ ? BPF_JNE :
263 : : BPF_JEQ,
264 : : BPF_REG_1, (uint8_t)dscp << 2, 0));
265 : :
266 : 20 : return 0;
267 : : }
268 : :
269 : 20 : static int _bf_matcher_pkt_generate_ip6_dscp(struct bf_program *program,
270 : : const struct bf_matcher *matcher,
271 : : const struct bf_matcher_meta *meta)
272 : : {
273 : : uint8_t dscp;
274 : : int r;
275 : :
276 : 20 : r = bf_stub_load_header(program, meta, BPF_REG_6);
277 [ + - ]: 20 : if (r)
278 : : return r;
279 : :
280 : 20 : dscp = *(uint8_t *)bf_matcher_payload(matcher);
281 : :
282 : : /* IPv6 DSCP occupies bits 6-11 of the header (big-endian view):
283 : : * [version 4b] [DSCP 6b] [ECN 2b] [flow label (high 4b)]
284 : : * Load 2 bytes, convert to big-endian, mask with 0x0fc0 to isolate
285 : : * the 6-bit DSCP field, then compare against dscp << 6. */
286 [ - + ]: 20 : EMIT(program, BPF_LDX_MEM(BPF_H, BPF_REG_1, BPF_REG_6, 0));
287 [ - + ]: 20 : EMIT(program, BPF_ENDIAN(BPF_TO_BE, BPF_REG_1, 16));
288 [ - + ]: 20 : EMIT(program, BPF_ALU64_IMM(BPF_AND, BPF_REG_1, 0x0fc0));
289 : :
290 [ + + - + ]: 30 : EMIT_FIXUP_JMP_NEXT_RULE(
291 : : program,
292 : : BPF_JMP_IMM(bf_matcher_get_op(matcher) == BF_MATCHER_EQ ? BPF_JNE :
293 : : BPF_JEQ,
294 : : BPF_REG_1, (uint16_t)dscp << 6, 0));
295 : :
296 : 20 : return 0;
297 : : }
298 : :
299 : 2425 : int bf_matcher_generate_packet(struct bf_program *program,
300 : : const struct bf_matcher *matcher)
301 : : {
302 : : const struct bf_matcher_meta *meta;
303 : :
304 : : assert(program);
305 : : assert(matcher);
306 : :
307 : 2425 : meta = bf_matcher_get_meta(bf_matcher_get_type(matcher));
308 : :
309 [ + - + + : 2425 : switch (bf_matcher_get_type(matcher)) {
+ + + + +
+ - ]
310 : 477 : case BF_MATCHER_META_IFACE:
311 : : case BF_MATCHER_META_L3_PROTO:
312 : : case BF_MATCHER_META_L4_PROTO:
313 : : case BF_MATCHER_META_PROBABILITY:
314 : : case BF_MATCHER_META_SPORT:
315 : : case BF_MATCHER_META_DPORT:
316 : : case BF_MATCHER_META_FLOW_PROBABILITY:
317 : 477 : return bf_matcher_generate_meta(program, matcher);
318 : 0 : case BF_MATCHER_META_MARK:
319 : : case BF_MATCHER_META_FLOW_HASH:
320 [ # # ]: 0 : return bf_err_r(-ENOTSUP,
321 : : "matcher '%s' is not supported by this flavor",
322 : : bf_matcher_type_to_str(bf_matcher_get_type(matcher)));
323 : 20 : case BF_MATCHER_IP4_DSCP:
324 : 20 : return _bf_matcher_pkt_generate_ip4_dscp(program, matcher, meta);
325 : 576 : case BF_MATCHER_IP4_SADDR:
326 : : case BF_MATCHER_IP4_DADDR:
327 : : case BF_MATCHER_IP4_PROTO:
328 : : case BF_MATCHER_IP6_SADDR:
329 : : case BF_MATCHER_IP6_DADDR:
330 : : case BF_MATCHER_ICMP_TYPE:
331 : : case BF_MATCHER_ICMP_CODE:
332 : : case BF_MATCHER_ICMPV6_TYPE:
333 : : case BF_MATCHER_ICMPV6_CODE:
334 : 576 : return _bf_matcher_pkt_load_and_cmp(program, matcher, meta);
335 : 370 : case BF_MATCHER_IP4_SNET:
336 : : case BF_MATCHER_IP4_DNET:
337 : : case BF_MATCHER_IP6_SNET:
338 : : case BF_MATCHER_IP6_DNET:
339 : 370 : return _bf_matcher_pkt_generate_net(program, matcher, meta);
340 : 440 : case BF_MATCHER_TCP_SPORT:
341 : : case BF_MATCHER_TCP_DPORT:
342 : : case BF_MATCHER_UDP_SPORT:
343 : : case BF_MATCHER_UDP_DPORT:
344 : 440 : return _bf_matcher_pkt_generate_port(program, matcher, meta);
345 : 140 : case BF_MATCHER_TCP_FLAGS:
346 : 140 : return _bf_matcher_pkt_generate_tcp_flags(program, matcher, meta);
347 : 100 : case BF_MATCHER_IP6_NEXTHDR:
348 : 100 : return _bf_matcher_pkt_generate_ip6_nexthdr(program, matcher);
349 : 20 : case BF_MATCHER_IP6_DSCP:
350 : 20 : return _bf_matcher_pkt_generate_ip6_dscp(program, matcher, meta);
351 : 282 : case BF_MATCHER_SET:
352 : 282 : return bf_matcher_generate_set(program, matcher);
353 : 0 : default:
354 [ # # ]: 0 : return bf_err_r(-EINVAL, "unknown matcher type %d",
355 : : bf_matcher_get_type(matcher));
356 : : }
357 : : }
|