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/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/chain.h>
18 : : #include <bpfilter/elfstub.h>
19 : : #include <bpfilter/helper.h>
20 : : #include <bpfilter/logger.h>
21 : : #include <bpfilter/matcher.h>
22 : : #include <bpfilter/rule.h>
23 : : #include <bpfilter/set.h>
24 : :
25 : : #include "cgen/matcher/cmp.h"
26 : : #include "cgen/matcher/meta.h"
27 : : #include "cgen/matcher/set.h"
28 : : #include "cgen/program.h"
29 : : #include "cgen/runtime.h"
30 : : #include "cgen/stub.h"
31 : : #include "filter.h"
32 : :
33 : : /**
34 : : * Packet matcher codegen follows a three-stage pipeline:
35 : : *
36 : : * 1. Protocol check: `_bf_program_generate_rule()` (in program.c)
37 : : * emits deduplicated protocol guards before the matcher loop,
38 : : * so each L3/L4 protocol is verified at most once per rule.
39 : : *
40 : : * 2. Header + field load: `bf_stub_load_header()` loads the header
41 : : * base address into `R6`, then `_bf_matcher_pkt_load_field()` reads
42 : : * the target field into the specified register (and `reg+1` for
43 : : * 128-bit values such as IPv6 addresses).
44 : : *
45 : : * 3. Comparison: A `bf_cmp_*` function compares the value in the
46 : : * specified register against the matcher's reference payload.
47 : : */
48 : :
49 : : #define BF_IPV6_EH_HOPOPTS(x) ((x) << 0)
50 : : #define BF_IPV6_EH_ROUTING(x) ((x) << 1)
51 : : #define BF_IPV6_EH_FRAGMENT(x) ((x) << 2)
52 : : #define BF_IPV6_EH_AH(x) ((x) << 3)
53 : : #define BF_IPV6_EH_DSTOPTS(x) ((x) << 4)
54 : : #define BF_IPV6_EH_MH(x) ((x) << 5)
55 : :
56 : : /**
57 : : * @brief Load a packet field from the header into the specified register.
58 : : *
59 : : * `R6` must already point to the header base. For 128-bit fields (IPv6
60 : : * addresses), the low 8 bytes are loaded into `reg` and the high 8 bytes into
61 : : * `reg + 1`.
62 : : *
63 : : * @param program Program to emit into. Can't be NULL.
64 : : * @param meta Matcher metadata describing field offset and size. Can't be NULL.
65 : : * @param reg BPF register to load the value into.
66 : : * @return 0 on success, negative errno on error.
67 : : */
68 : 1655 : static int _bf_matcher_pkt_load_field(struct bf_program *program,
69 : : const struct bf_matcher_meta *meta,
70 : : int reg)
71 : : {
72 [ + + + - : 1655 : switch (meta->hdr_payload_size) {
+ - ]
73 : 598 : case 1:
74 [ + - ]: 598 : EMIT(program,
75 : : BPF_LDX_MEM(BPF_B, reg, BPF_REG_6, meta->hdr_payload_offset));
76 : : break;
77 : 450 : case 2:
78 [ + - ]: 450 : EMIT(program,
79 : : BPF_LDX_MEM(BPF_H, reg, BPF_REG_6, meta->hdr_payload_offset));
80 : : break;
81 : 267 : case 4:
82 [ + - ]: 267 : EMIT(program,
83 : : BPF_LDX_MEM(BPF_W, reg, BPF_REG_6, meta->hdr_payload_offset));
84 : : break;
85 : 0 : case 8:
86 [ # # ]: 0 : EMIT(program,
87 : : BPF_LDX_MEM(BPF_DW, reg, BPF_REG_6, meta->hdr_payload_offset));
88 : : break;
89 : 340 : case 16:
90 [ + - ]: 340 : EMIT(program,
91 : : BPF_LDX_MEM(BPF_DW, reg, BPF_REG_6, meta->hdr_payload_offset));
92 [ + - ]: 340 : EMIT(program, BPF_LDX_MEM(BPF_DW, reg + 1, BPF_REG_6,
93 : : meta->hdr_payload_offset + 8));
94 : : break;
95 : : default:
96 : : return -EINVAL;
97 : : }
98 : :
99 : : return 0;
100 : : }
101 : :
102 : 1655 : static int _bf_matcher_pkt_load(struct bf_program *program,
103 : : const struct bf_matcher_meta *meta, int reg)
104 : : {
105 : : int r;
106 : :
107 : 1655 : r = bf_stub_load_header(program, meta, BPF_REG_6);
108 [ + - ]: 1655 : if (r)
109 : : return r;
110 : :
111 : 1655 : return _bf_matcher_pkt_load_field(program, meta, reg);
112 : : }
113 : :
114 : : /**
115 : : * @brief Generic load + value compare for matchers whose field size and offset
116 : : * are fully described by `_bf_matcher_metas`.
117 : : *
118 : : * Emits: header load -> field load -> `bf_cmp_value`.
119 : : *
120 : : * @param program Program to generate bytecode into. Can't be NULL.
121 : : * @param matcher Matcher to generate bytecode for. Can't be NULL.
122 : : * @param meta Matcher metadata describing field size and offset. Can't be NULL.
123 : : * @return 0 on success, negative errno on error.
124 : : */
125 : 645 : static int _bf_matcher_pkt_load_and_cmp(struct bf_program *program,
126 : : const struct bf_matcher *matcher,
127 : : const struct bf_matcher_meta *meta)
128 : : {
129 : : int r;
130 : :
131 : 645 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
132 [ + - ]: 645 : if (r)
133 : : return r;
134 : :
135 : 645 : return bf_cmp_value(program, matcher, bf_matcher_payload(matcher),
136 : 645 : meta->hdr_payload_size, BPF_REG_1);
137 : : }
138 : :
139 : 380 : static int _bf_matcher_pkt_generate_net(struct bf_program *program,
140 : : const struct bf_matcher *matcher,
141 : : const struct bf_matcher_meta *meta)
142 : : {
143 : 380 : const uint32_t prefixlen = *(const uint32_t *)bf_matcher_payload(matcher);
144 : 380 : const void *data =
145 : 380 : (const uint8_t *)bf_matcher_payload(matcher) + sizeof(uint32_t);
146 : : int r;
147 : :
148 : 380 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
149 [ + - ]: 380 : if (r)
150 : : return r;
151 : :
152 : 380 : return bf_cmp_masked_value(program, matcher, data, prefixlen,
153 : 380 : meta->hdr_payload_size, BPF_REG_1);
154 : : }
155 : :
156 : 450 : static int _bf_matcher_pkt_generate_port(struct bf_program *program,
157 : : const struct bf_matcher *matcher,
158 : : const struct bf_matcher_meta *meta)
159 : : {
160 : : int r;
161 : :
162 : 450 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
163 [ + - ]: 450 : if (r)
164 : : return r;
165 : :
166 [ + + ]: 450 : if (bf_matcher_get_op(matcher) == BF_MATCHER_RANGE) {
167 : 130 : uint16_t *ports = (uint16_t *)bf_matcher_payload(matcher);
168 : : /* Convert the big-endian value stored in the packet into a
169 : : * little-endian value for x86 and arm before comparing it to the
170 : : * reference value. This is a JLT/JGT comparison, we need to have the
171 : : * MSB where the machine expects then. */
172 [ + - ]: 130 : EMIT(program, BPF_BSWAP(BPF_REG_1, 16));
173 : 130 : return bf_cmp_range(program, matcher, ports[0], ports[1], BPF_REG_1);
174 : : }
175 : :
176 : 320 : return bf_cmp_value(program, matcher, bf_matcher_payload(matcher),
177 : 320 : meta->hdr_payload_size, BPF_REG_1);
178 : : }
179 : :
180 : : static int
181 : 160 : _bf_matcher_pkt_generate_tcp_flags(struct bf_program *program,
182 : : const struct bf_matcher *matcher,
183 : : const struct bf_matcher_meta *meta)
184 : : {
185 : : int r;
186 : :
187 : 160 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
188 [ + - ]: 160 : if (r)
189 : : return r;
190 : :
191 [ + + - ]: 160 : switch (bf_matcher_get_op(matcher)) {
192 : 100 : case BF_MATCHER_ANY:
193 : : case BF_MATCHER_ALL:
194 : 100 : return bf_cmp_bitfield(program, matcher,
195 : 100 : *(uint8_t *)bf_matcher_payload(matcher),
196 : : BPF_REG_1);
197 : 60 : case BF_MATCHER_EQ:
198 : 60 : return bf_cmp_value(program, matcher, bf_matcher_payload(matcher),
199 : 60 : meta->hdr_payload_size, BPF_REG_1);
200 : 0 : default:
201 [ # # ]: 0 : return bf_err_r(-EINVAL, "unsupported operator %d",
202 : : bf_matcher_get_op(matcher));
203 : : }
204 : : }
205 : :
206 : : static int
207 : 100 : _bf_matcher_pkt_generate_ip6_nexthdr(struct bf_program *program,
208 : : const struct bf_matcher *matcher)
209 : : {
210 : 100 : const uint8_t ehdr = *(uint8_t *)bf_matcher_payload(matcher);
211 : : uint8_t eh_mask;
212 : : uint8_t jmp_op;
213 : :
214 : 100 : jmp_op = bf_cmp_get_jmp_ins(matcher);
215 : :
216 [ + + ]: 100 : switch (ehdr) {
217 : 20 : case IPPROTO_HOPOPTS:
218 : : case IPPROTO_ROUTING:
219 : : case IPPROTO_DSTOPTS:
220 : : case IPPROTO_FRAGMENT:
221 : : case IPPROTO_AH:
222 : : case IPPROTO_MH:
223 [ + + ]: 20 : eh_mask = (BF_IPV6_EH_HOPOPTS(ehdr == IPPROTO_HOPOPTS) |
224 [ + - ]: 10 : BF_IPV6_EH_ROUTING(ehdr == IPPROTO_ROUTING) |
225 [ - + ]: 20 : BF_IPV6_EH_FRAGMENT(ehdr == IPPROTO_FRAGMENT) |
226 [ - + ]: 20 : BF_IPV6_EH_AH(ehdr == IPPROTO_AH) |
227 [ + - ]: 40 : BF_IPV6_EH_DSTOPTS(ehdr == IPPROTO_DSTOPTS) |
228 : : BF_IPV6_EH_MH(ehdr == IPPROTO_MH));
229 [ + - ]: 20 : EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_10,
230 : : BF_PROG_CTX_OFF(ipv6_eh)));
231 [ + - ]: 20 : EMIT(program, BPF_ALU64_IMM(BPF_AND, BPF_REG_1, eh_mask));
232 : :
233 : : /* Extension header check: after AND with eh_mask, a non-zero
234 : : * result means the header is present. For EQ (jmp_op=JNE),
235 : : * we want to skip if zero (not present), so we use the
236 : : * *opposite* opcode here. */
237 [ - + - + ]: 20 : EMIT_FIXUP_JMP_NEXT_RULE(
238 : : program, BPF_JMP_IMM(jmp_op == BPF_JNE ? BPF_JEQ : BPF_JNE,
239 : : BPF_REG_1, 0, 0));
240 : 20 : break;
241 : 80 : default:
242 : : /* check l4 protocols using `BPF_REG_8` */
243 [ - + ]: 80 : EMIT_FIXUP_JMP_NEXT_RULE(program,
244 : : BPF_JMP_IMM(jmp_op, BPF_REG_8, ehdr, 0));
245 : 80 : break;
246 : : }
247 : :
248 : : return 0;
249 : : }
250 : :
251 : 20 : static int _bf_matcher_pkt_generate_ip4_dscp(struct bf_program *program,
252 : : const struct bf_matcher *matcher,
253 : : const struct bf_matcher_meta *meta)
254 : : {
255 : : uint8_t dscp;
256 : : int r;
257 : :
258 : 20 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
259 [ + - ]: 20 : if (r)
260 : : return r;
261 : :
262 : 20 : dscp = *(uint8_t *)bf_matcher_payload(matcher);
263 : :
264 : : /* IPv4 TOS byte: [DSCP 6b] [ECN 2b]. Mask with 0xfc to isolate
265 : : * the 6-bit DSCP field, then compare against dscp << 2. */
266 [ + - ]: 20 : EMIT(program, BPF_ALU64_IMM(BPF_AND, BPF_REG_1, 0xfc));
267 : :
268 [ - + ]: 20 : EMIT_FIXUP_JMP_NEXT_RULE(program,
269 : : BPF_JMP_IMM(bf_cmp_get_jmp_ins(matcher), BPF_REG_1,
270 : : (uint8_t)dscp << 2, 0));
271 : :
272 : 20 : return 0;
273 : : }
274 : :
275 : 20 : static int _bf_matcher_pkt_generate_ip6_dscp(struct bf_program *program,
276 : : const struct bf_matcher *matcher,
277 : : const struct bf_matcher_meta *meta)
278 : : {
279 : : uint8_t dscp;
280 : : int r;
281 : :
282 : 20 : r = bf_stub_load_header(program, meta, BPF_REG_6);
283 [ + - ]: 20 : if (r)
284 : : return r;
285 : :
286 : 20 : dscp = *(uint8_t *)bf_matcher_payload(matcher);
287 : :
288 : : /* IPv6 DSCP occupies bits 6-11 of the header (big-endian view):
289 : : * [version 4b] [DSCP 6b] [ECN 2b] [flow label (high 4b)]
290 : : * Load 2 bytes, convert to big-endian, mask with 0x0fc0 to isolate
291 : : * the 6-bit DSCP field, then compare against dscp << 6. */
292 [ + - ]: 20 : EMIT(program, BPF_LDX_MEM(BPF_H, BPF_REG_1, BPF_REG_6, 0));
293 [ + - ]: 20 : EMIT(program, BPF_ENDIAN(BPF_TO_BE, BPF_REG_1, 16));
294 [ + - ]: 20 : EMIT(program, BPF_ALU64_IMM(BPF_AND, BPF_REG_1, 0x0fc0));
295 : :
296 [ - + ]: 20 : EMIT_FIXUP_JMP_NEXT_RULE(program,
297 : : BPF_JMP_IMM(bf_cmp_get_jmp_ins(matcher), BPF_REG_1,
298 : : (uint16_t)dscp << 6, 0));
299 : :
300 : 20 : return 0;
301 : : }
302 : :
303 : 262 : static int _bf_matcher_pkt_generate_set(struct bf_program *program,
304 : : const struct bf_matcher *matcher)
305 : : {
306 : : const struct bf_set *set;
307 : : size_t offset = 0;
308 : : int r;
309 : :
310 : : assert(program);
311 : : assert(matcher);
312 : :
313 : 262 : set = bf_chain_get_set_for_matcher(program->runtime.chain, matcher);
314 [ - + ]: 262 : if (!set) {
315 [ # # ]: 0 : return bf_err_r(-ENOENT, "set #%u not found in %s",
316 : : *(uint32_t *)bf_matcher_payload(matcher),
317 : : program->runtime.chain->name);
318 : : }
319 : :
320 [ + + ]: 262 : if (set->use_trie) {
321 : 40 : const struct bf_matcher_meta *meta = bf_matcher_get_meta(set->key[0]);
322 : :
323 [ - + ]: 40 : if (!meta) {
324 [ # # ]: 0 : return bf_err_r(-EINVAL, "missing meta for '%s'",
325 : : bf_matcher_type_to_str(set->key[0]));
326 : : }
327 : :
328 : 40 : r = bf_stub_load_header(program, meta, BPF_REG_6);
329 [ - + ]: 40 : if (r)
330 [ # # ]: 0 : return bf_err_r(r, "failed to load protocol header into BPF_REG_6");
331 : :
332 : 40 : return bf_set_generate_trie_lookup(
333 : 40 : program, matcher, meta->hdr_payload_offset, meta->hdr_payload_size);
334 : : }
335 : :
336 [ + + ]: 449 : for (size_t i = 0; i < set->n_comps; ++i) {
337 : 227 : enum bf_matcher_type type = set->key[i];
338 : 227 : const struct bf_matcher_meta *meta = bf_matcher_get_meta(type);
339 : :
340 [ - + ]: 227 : if (!meta) {
341 [ # # ]: 0 : return bf_err_r(-EINVAL, "missing meta for '%s'",
342 : : bf_matcher_type_to_str(type));
343 : : }
344 : :
345 : 227 : r = bf_stub_load_header(program, meta, BPF_REG_6);
346 [ - + ]: 227 : if (r)
347 [ # # ]: 0 : return bf_err_r(r, "failed to load protocol header into BPF_REG_6");
348 : :
349 : 227 : r = bf_stub_stx_payload(program, meta, offset);
350 [ - + ]: 227 : if (r) {
351 [ # # ]: 0 : return bf_err_r(r,
352 : : "failed to generate bytecode to load packet data");
353 : : }
354 : :
355 : 227 : offset += meta->hdr_payload_size;
356 : : }
357 : :
358 : 222 : return bf_set_generate_map_lookup(program, matcher, BF_PROG_SCR_OFF(0));
359 : : }
360 : :
361 : 2544 : int bf_packet_gen_inline_matcher(struct bf_program *program,
362 : : const struct bf_matcher *matcher)
363 : : {
364 : : const struct bf_matcher_meta *meta;
365 : :
366 : : assert(program);
367 : : assert(matcher);
368 : :
369 : 2544 : meta = bf_matcher_get_meta(bf_matcher_get_type(matcher));
370 : :
371 [ + - + + : 2544 : switch (bf_matcher_get_type(matcher)) {
+ + + + +
+ - ]
372 : 507 : case BF_MATCHER_META_IFACE:
373 : : case BF_MATCHER_META_L3_PROTO:
374 : : case BF_MATCHER_META_L4_PROTO:
375 : : case BF_MATCHER_META_PROBABILITY:
376 : : case BF_MATCHER_META_SPORT:
377 : : case BF_MATCHER_META_DPORT:
378 : : case BF_MATCHER_META_FLOW_PROBABILITY:
379 : 507 : return bf_matcher_generate_meta(program, matcher);
380 : 0 : case BF_MATCHER_META_MARK:
381 : : case BF_MATCHER_META_FLOW_HASH:
382 [ # # ]: 0 : return bf_err_r(-ENOTSUP,
383 : : "matcher '%s' is not supported by this flavor",
384 : : bf_matcher_type_to_str(bf_matcher_get_type(matcher)));
385 : 20 : case BF_MATCHER_IP4_DSCP:
386 : 20 : return _bf_matcher_pkt_generate_ip4_dscp(program, matcher, meta);
387 : 645 : case BF_MATCHER_IP4_SADDR:
388 : : case BF_MATCHER_IP4_DADDR:
389 : : case BF_MATCHER_IP4_PROTO:
390 : : case BF_MATCHER_IP6_SADDR:
391 : : case BF_MATCHER_IP6_DADDR:
392 : : case BF_MATCHER_ICMP_TYPE:
393 : : case BF_MATCHER_ICMP_CODE:
394 : : case BF_MATCHER_ICMPV6_TYPE:
395 : : case BF_MATCHER_ICMPV6_CODE:
396 : 645 : return _bf_matcher_pkt_load_and_cmp(program, matcher, meta);
397 : 380 : case BF_MATCHER_IP4_SNET:
398 : : case BF_MATCHER_IP4_DNET:
399 : : case BF_MATCHER_IP6_SNET:
400 : : case BF_MATCHER_IP6_DNET:
401 : 380 : return _bf_matcher_pkt_generate_net(program, matcher, meta);
402 : 450 : case BF_MATCHER_TCP_SPORT:
403 : : case BF_MATCHER_TCP_DPORT:
404 : : case BF_MATCHER_UDP_SPORT:
405 : : case BF_MATCHER_UDP_DPORT:
406 : 450 : return _bf_matcher_pkt_generate_port(program, matcher, meta);
407 : 160 : case BF_MATCHER_TCP_FLAGS:
408 : 160 : return _bf_matcher_pkt_generate_tcp_flags(program, matcher, meta);
409 : 100 : case BF_MATCHER_IP6_NEXTHDR:
410 : 100 : return _bf_matcher_pkt_generate_ip6_nexthdr(program, matcher);
411 : 20 : case BF_MATCHER_IP6_DSCP:
412 : 20 : return _bf_matcher_pkt_generate_ip6_dscp(program, matcher, meta);
413 : 262 : case BF_MATCHER_SET:
414 : 262 : return _bf_matcher_pkt_generate_set(program, matcher);
415 : 0 : default:
416 [ # # ]: 0 : return bf_err_r(-EINVAL, "unknown matcher type %d",
417 : : bf_matcher_get_type(matcher));
418 : : }
419 : : }
420 : :
421 : 38 : int bf_packet_gen_inline_log(struct bf_program *program,
422 : : const struct bf_rule *rule)
423 : : {
424 : : assert(program);
425 : : assert(rule);
426 : :
427 [ + - ]: 38 : EMIT(program, BPF_MOV64_REG(BPF_REG_1, BPF_REG_10));
428 [ + - ]: 38 : EMIT(program, BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, BF_PROG_CTX_OFF(arg)));
429 [ + - ]: 38 : EMIT(program, BPF_MOV64_IMM(BPF_REG_2, rule->index));
430 [ + - ]: 38 : EMIT(program, BPF_MOV64_IMM(BPF_REG_3, rule->log));
431 [ + - ]: 38 : EMIT(program, BPF_MOV64_IMM(BPF_REG_4, rule->verdict));
432 : :
433 : : // Pack l3_proto and l4_proto
434 [ + - ]: 38 : EMIT(program, BPF_MOV64_REG(BPF_REG_5, BPF_REG_7));
435 [ + - ]: 38 : EMIT(program, BPF_ALU64_IMM(BPF_LSH, BPF_REG_5, 16));
436 [ + - ]: 38 : EMIT(program, BPF_ALU64_REG(BPF_OR, BPF_REG_5, BPF_REG_8));
437 : :
438 : 38 : EMIT_FIXUP_ELFSTUB(program, BF_ELFSTUB_PKT_LOG);
439 : :
440 : : return 0;
441 : : }
|