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 : 946 : static int _bf_matcher_pkt_load_field(struct bf_program *program,
64 : : const struct bf_matcher_meta *meta,
65 : : int reg)
66 : : {
67 [ + + + - : 946 : switch (meta->hdr_payload_size) {
+ - ]
68 : 376 : case 1:
69 [ - + ]: 376 : EMIT(program,
70 : : BPF_LDX_MEM(BPF_B, reg, BPF_REG_6, meta->hdr_payload_offset));
71 : 376 : break;
72 : 320 : case 2:
73 [ - + ]: 320 : EMIT(program,
74 : : BPF_LDX_MEM(BPF_H, reg, BPF_REG_6, meta->hdr_payload_offset));
75 : 320 : break;
76 : 80 : case 4:
77 [ - + ]: 80 : EMIT(program,
78 : : BPF_LDX_MEM(BPF_W, reg, BPF_REG_6, meta->hdr_payload_offset));
79 : 80 : 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 : 170 : case 16:
85 [ - + ]: 170 : EMIT(program,
86 : : BPF_LDX_MEM(BPF_DW, reg, BPF_REG_6, meta->hdr_payload_offset));
87 [ - + ]: 170 : EMIT(program, BPF_LDX_MEM(BPF_DW, reg + 1, BPF_REG_6,
88 : : meta->hdr_payload_offset + 8));
89 : 170 : break;
90 : : default:
91 : : return -EINVAL;
92 : : }
93 : :
94 : : return 0;
95 : : }
96 : :
97 : 946 : 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 : 946 : r = bf_stub_load_header(program, meta, BPF_REG_6);
103 [ + - ]: 946 : if (r)
104 : : return r;
105 : :
106 : 946 : 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 : 396 : 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 : 396 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
127 [ + - ]: 396 : if (r)
128 : : return r;
129 : :
130 : 396 : return bf_cmp_value(program, bf_matcher_get_op(matcher),
131 : 396 : bf_matcher_payload(matcher), meta->hdr_payload_size,
132 : : BPF_REG_1);
133 : : }
134 : :
135 : 130 : 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 : 130 : const uint32_t prefixlen = *(const uint32_t *)bf_matcher_payload(matcher);
140 : 130 : const void *data =
141 : 130 : (const uint8_t *)bf_matcher_payload(matcher) + sizeof(uint32_t);
142 : : int r;
143 : :
144 : 130 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
145 [ + - ]: 130 : if (r)
146 : : return r;
147 : :
148 : 130 : return bf_cmp_masked_value(program, bf_matcher_get_op(matcher), data,
149 : 130 : prefixlen, meta->hdr_payload_size, BPF_REG_1);
150 : : }
151 : :
152 : 320 : 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 : 320 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
159 [ + - ]: 320 : if (r)
160 : : return r;
161 : :
162 [ + + ]: 320 : if (bf_matcher_get_op(matcher) == BF_MATCHER_RANGE) {
163 : 80 : 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 [ - + ]: 80 : EMIT(program, BPF_BSWAP(BPF_REG_1, 16));
169 : 80 : return bf_cmp_range(program, ports[0], ports[1], BPF_REG_1);
170 : : }
171 : :
172 : 240 : return bf_cmp_value(program, bf_matcher_get_op(matcher),
173 : 240 : bf_matcher_payload(matcher), meta->hdr_payload_size,
174 : : BPF_REG_1);
175 : : }
176 : :
177 : : static int
178 : 100 : _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 : 100 : r = _bf_matcher_pkt_load(program, meta, BPF_REG_1);
185 [ + - ]: 100 : if (r)
186 : : return r;
187 : :
188 [ + + ]: 100 : switch (bf_matcher_get_op(matcher)) {
189 : 60 : case BF_MATCHER_ANY:
190 : : case BF_MATCHER_ALL:
191 : 60 : return bf_cmp_bitfield(program, bf_matcher_get_op(matcher),
192 : 60 : *(uint8_t *)bf_matcher_payload(matcher),
193 : : BPF_REG_1);
194 : 40 : default:
195 : 40 : return bf_cmp_value(program, bf_matcher_get_op(matcher),
196 : 40 : bf_matcher_payload(matcher), meta->hdr_payload_size,
197 : : BPF_REG_1);
198 : : }
199 : : }
200 : :
201 : : static int
202 : 80 : _bf_matcher_pkt_generate_ip6_nexthdr(struct bf_program *program,
203 : : const struct bf_matcher *matcher)
204 : : {
205 : 80 : const uint8_t ehdr = *(uint8_t *)bf_matcher_payload(matcher);
206 : : uint8_t eh_mask;
207 : :
208 [ + + ]: 80 : 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 : 60 : default:
231 : : /* check l4 protocols using `BPF_REG_8` */
232 [ - + - + ]: 60 : 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 : 60 : break;
238 : : }
239 : :
240 : : return 0;
241 : : }
242 : :
243 : 0 : static int _bf_matcher_pkt_generate_ip6_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 : 0 : r = bf_stub_load_header(program, meta, BPF_REG_6);
251 [ # # ]: 0 : if (r)
252 : : return r;
253 : :
254 : 0 : dscp = *(uint8_t *)bf_matcher_payload(matcher);
255 : :
256 : : /* IPv6 DSCP (traffic class) spans bits 4-11 of the header:
257 : : * Byte 0: version (4 bits) | dscp_high (4 bits)
258 : : * Byte 1: dscp_low (4 bits) | flow_label_high (4 bits)
259 : : * Load 2 bytes, mask with 0x0ff0, compare against dscp << 4. */
260 [ # # ]: 0 : EMIT(program, BPF_LDX_MEM(BPF_H, BPF_REG_1, BPF_REG_6, 0));
261 [ # # ]: 0 : EMIT(program, BPF_ENDIAN(BPF_TO_BE, BPF_REG_1, 16));
262 [ # # ]: 0 : EMIT(program, BPF_ALU64_IMM(BPF_AND, BPF_REG_1, 0x0ff0));
263 : :
264 [ # # # # ]: 0 : EMIT_FIXUP_JMP_NEXT_RULE(
265 : : program,
266 : : BPF_JMP_IMM(bf_matcher_get_op(matcher) == BF_MATCHER_EQ ? BPF_JNE :
267 : : BPF_JEQ,
268 : : BPF_REG_1, (uint16_t)dscp << 4, 0));
269 : :
270 : 0 : return 0;
271 : : }
272 : :
273 : 1460 : int bf_matcher_generate_packet(struct bf_program *program,
274 : : const struct bf_matcher *matcher)
275 : : {
276 : : const struct bf_matcher_meta *meta;
277 : :
278 : : assert(program);
279 : : assert(matcher);
280 : :
281 : 1460 : meta = bf_matcher_get_meta(bf_matcher_get_type(matcher));
282 : :
283 [ + - + + : 1460 : switch (bf_matcher_get_type(matcher)) {
+ + + - +
- ]
284 : 332 : case BF_MATCHER_META_IFACE:
285 : : case BF_MATCHER_META_L3_PROTO:
286 : : case BF_MATCHER_META_L4_PROTO:
287 : : case BF_MATCHER_META_PROBABILITY:
288 : : case BF_MATCHER_META_SPORT:
289 : : case BF_MATCHER_META_DPORT:
290 : : case BF_MATCHER_META_FLOW_PROBABILITY:
291 : 332 : return bf_matcher_generate_meta(program, matcher);
292 : 0 : case BF_MATCHER_META_MARK:
293 : : case BF_MATCHER_META_FLOW_HASH:
294 [ # # ]: 0 : return bf_err_r(-ENOTSUP,
295 : : "matcher '%s' is not supported by this flavor",
296 : : bf_matcher_type_to_str(bf_matcher_get_type(matcher)));
297 : 396 : case BF_MATCHER_IP4_SADDR:
298 : : case BF_MATCHER_IP4_DADDR:
299 : : case BF_MATCHER_IP4_PROTO:
300 : : case BF_MATCHER_IP4_DSCP:
301 : : case BF_MATCHER_IP6_SADDR:
302 : : case BF_MATCHER_IP6_DADDR:
303 : : case BF_MATCHER_ICMP_TYPE:
304 : : case BF_MATCHER_ICMP_CODE:
305 : : case BF_MATCHER_ICMPV6_TYPE:
306 : : case BF_MATCHER_ICMPV6_CODE:
307 : 396 : return _bf_matcher_pkt_load_and_cmp(program, matcher, meta);
308 : 130 : case BF_MATCHER_IP4_SNET:
309 : : case BF_MATCHER_IP4_DNET:
310 : : case BF_MATCHER_IP6_SNET:
311 : : case BF_MATCHER_IP6_DNET:
312 : 130 : return _bf_matcher_pkt_generate_net(program, matcher, meta);
313 : 320 : case BF_MATCHER_TCP_SPORT:
314 : : case BF_MATCHER_TCP_DPORT:
315 : : case BF_MATCHER_UDP_SPORT:
316 : : case BF_MATCHER_UDP_DPORT:
317 : 320 : return _bf_matcher_pkt_generate_port(program, matcher, meta);
318 : 100 : case BF_MATCHER_TCP_FLAGS:
319 : 100 : return _bf_matcher_pkt_generate_tcp_flags(program, matcher, meta);
320 : 80 : case BF_MATCHER_IP6_NEXTHDR:
321 : 80 : return _bf_matcher_pkt_generate_ip6_nexthdr(program, matcher);
322 : 0 : case BF_MATCHER_IP6_DSCP:
323 : 0 : return _bf_matcher_pkt_generate_ip6_dscp(program, matcher, meta);
324 : 102 : case BF_MATCHER_SET:
325 : 102 : return bf_matcher_generate_set(program, matcher);
326 : 0 : default:
327 [ # # ]: 0 : return bf_err_r(-EINVAL, "unknown matcher type %d",
328 : : bf_matcher_get_type(matcher));
329 : : }
330 : : }
|