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