Branch data Line data Source code
1 : : /* SPDX-License-Identifier: GPL-2.0-only */
2 : : /*
3 : : * Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
4 : : */
5 : :
6 : : #include "cgen/matcher/meta.h"
7 : :
8 : : #include <linux/bpf.h>
9 : : #include <linux/bpf_common.h>
10 : : #include <linux/if_ether.h>
11 : : #include <linux/in.h> // NOLINT
12 : : #include <linux/tcp.h>
13 : : #include <linux/udp.h>
14 : :
15 : : #include <endian.h>
16 : : #include <errno.h>
17 : : #include <stddef.h>
18 : : #include <stdint.h>
19 : :
20 : : #include <bpfilter/elfstub.h>
21 : : #include <bpfilter/logger.h>
22 : : #include <bpfilter/matcher.h>
23 : :
24 : : #include "cgen/jmp.h"
25 : : #include "cgen/matcher/cmp.h"
26 : : #include "cgen/program.h"
27 : : #include "cgen/runtime.h"
28 : : #include "cgen/swich.h"
29 : : #include "filter.h"
30 : :
31 : : /** @todo Add support for input and output interface filtering based on the
32 : : * program's hook. */
33 : 30 : static int _bf_matcher_generate_meta_iface(struct bf_program *program,
34 : : const struct bf_matcher *matcher)
35 : : {
36 [ + - ]: 30 : EMIT(program,
37 : : BPF_LDX_MEM(BPF_H, BPF_REG_1, BPF_REG_10, BF_PROG_CTX_OFF(ifindex)));
38 [ - + ]: 30 : EMIT_FIXUP_JMP_NEXT_RULE(
39 : : program, BPF_JMP_IMM(bf_cmp_get_jmp_ins(matcher), BPF_REG_1,
40 : : *(uint32_t *)bf_matcher_payload(matcher), 0));
41 : :
42 : 30 : return 0;
43 : : }
44 : :
45 : : static int
46 : 80 : _bf_matcher_generate_meta_probability(struct bf_program *program,
47 : : const struct bf_matcher *matcher)
48 : : {
49 : 80 : float proba = *(float *)bf_matcher_payload(matcher);
50 : 80 : uint32_t threshold = (uint32_t)((double)UINT32_MAX * (proba / 100.0));
51 : :
52 [ + - ]: 80 : EMIT(program, BPF_EMIT_CALL(BPF_FUNC_get_prandom_u32));
53 : :
54 [ + + ]: 80 : if (bf_matcher_get_negate(matcher)) {
55 [ - + ]: 20 : EMIT_FIXUP_JMP_NEXT_RULE(program,
56 : : BPF_JMP_IMM(BPF_JLE, BPF_REG_0, threshold, 0));
57 : : } else {
58 [ - + ]: 60 : EMIT_FIXUP_JMP_NEXT_RULE(program,
59 : : BPF_JMP_IMM(BPF_JGT, BPF_REG_0, threshold, 0));
60 : : }
61 : :
62 : : return 0;
63 : : }
64 : :
65 : 248 : static int _bf_matcher_generate_meta_port(struct bf_program *program,
66 : : const struct bf_matcher *matcher)
67 : : {
68 : 248 : _clean_bf_swich_ struct bf_swich swich;
69 : 248 : uint16_t *port = (uint16_t *)bf_matcher_payload(matcher);
70 : : int r;
71 : :
72 : : // Load L4 header address into r6
73 [ + - ]: 248 : EMIT(program,
74 : : BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_10, BF_PROG_CTX_OFF(l4_hdr)));
75 : :
76 : : // Get the packet's port into r1
77 [ - + ]: 248 : swich = bf_swich_get(program, BPF_REG_8);
78 [ + + - + ]: 386 : EMIT_SWICH_OPTION(
79 : : &swich, IPPROTO_TCP,
80 : : BPF_LDX_MEM(BPF_H, BPF_REG_1, BPF_REG_6,
81 : : bf_matcher_get_type(matcher) == BF_MATCHER_META_SPORT ?
82 : : offsetof(struct tcphdr, source) :
83 : : offsetof(struct tcphdr, dest)));
84 [ + + - + ]: 386 : EMIT_SWICH_OPTION(
85 : : &swich, IPPROTO_UDP,
86 : : BPF_LDX_MEM(BPF_H, BPF_REG_1, BPF_REG_6,
87 : : bf_matcher_get_type(matcher) == BF_MATCHER_META_SPORT ?
88 : : offsetof(struct udphdr, source) :
89 : : offsetof(struct udphdr, dest)));
90 [ - + ]: 248 : EMIT_SWICH_DEFAULT(&swich, BPF_MOV64_IMM(BPF_REG_1, 0));
91 : :
92 : 248 : r = bf_swich_generate(&swich);
93 [ - + ]: 248 : if (r)
94 [ # # ]: 0 : return bf_err_r(r, "failed to generate swich for meta.(s|d)port");
95 : :
96 : : // If r1 == 0: no TCP nor UDP header found, jump to the next rule
97 [ - + ]: 248 : EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_1, 0, 0));
98 : :
99 [ + + ]: 248 : if (bf_matcher_get_op(matcher) == BF_MATCHER_RANGE) {
100 [ + - ]: 60 : EMIT(program, BPF_BSWAP(BPF_REG_1, 16));
101 : 60 : return bf_cmp_range(program, matcher, port[0], port[1], BPF_REG_1);
102 : : }
103 : :
104 : 188 : return bf_cmp_value(program, matcher, port, 2, BPF_REG_1);
105 : : }
106 : :
107 : : static int
108 : 25 : _bf_matcher_generate_meta_flow_probability(struct bf_program *program,
109 : : const struct bf_matcher *matcher)
110 : : {
111 : 25 : float proba = *(float *)bf_matcher_payload(matcher);
112 : 25 : uint32_t threshold = (uint32_t)((double)UINT32_MAX * (proba / 100.0));
113 : :
114 : : // Ensure L3 is IPv4 or IPv6, skip to next rule otherwise
115 [ + - ]: 25 : EMIT(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_7, htobe16(ETH_P_IP), 2));
116 [ + - ]: 25 : EMIT(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_7, htobe16(ETH_P_IPV6), 1));
117 [ - + ]: 25 : EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP_A(0));
118 : :
119 : : // Ensure L4 is TCP or UDP, skip to next rule otherwise
120 [ + - ]: 25 : EMIT(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_8, IPPROTO_TCP, 2));
121 [ + - ]: 25 : EMIT(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_8, IPPROTO_UDP, 1));
122 [ - + ]: 25 : EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP_A(0));
123 : :
124 : : /* Calculate flow hash using the bf_flow_hash elfstub.
125 : : *
126 : : * The elfstub computes a 32-bit hash from the packet's 5-tuple
127 : : * (src ip, dst ip, src port, dst port, protocol) plus IPv6 flow label.
128 : : * This ensures all packets in a flow get the same hash value, making
129 : : * the probability decision consistent per-flow rather than per-packet.
130 : : *
131 : : * Arguments:
132 : : * - r1: pointer to bf_runtime context
133 : : * - r2: L3 protocol ID (from r7, set by prologue)
134 : : * - r3: L4 protocol ID (from r8, set by prologue)
135 : : *
136 : : * Return: hash value in r0 */
137 [ + - ]: 25 : EMIT(program, BPF_MOV64_REG(BPF_REG_1, BPF_REG_10));
138 [ + - ]: 25 : EMIT(program, BPF_ALU64_IMM(BPF_ADD, BPF_REG_1,
139 : : -(int)sizeof(struct bf_runtime))); // r1 = ctx
140 [ + - ]: 25 : EMIT(program, BPF_MOV64_REG(BPF_REG_2, BPF_REG_7)); // r2 = l3_proto
141 [ + - ]: 25 : EMIT(program, BPF_MOV64_REG(BPF_REG_3, BPF_REG_8)); // r3 = l4_proto
142 : :
143 : : // Call the elfstub - result in r0
144 [ + - ]: 25 : EMIT_FIXUP_ELFSTUB(program, BF_ELFSTUB_FLOW_HASH);
145 : :
146 : : /* Compare the computed hash with the threshold based on probability.
147 : : * The hash is uniformly distributed across 32 bits, so we compare against
148 : : * UINT32_MAX * (proba / 100.0) to select the desired percentage of flows. */
149 [ + + ]: 25 : if (bf_matcher_get_negate(matcher)) {
150 [ - + ]: 10 : EMIT_FIXUP_JMP_NEXT_RULE(
151 : : program, BPF_JMP32_IMM(BPF_JLE, BPF_REG_0, threshold, 0));
152 : : } else {
153 [ - + ]: 15 : EMIT_FIXUP_JMP_NEXT_RULE(
154 : : program, BPF_JMP32_IMM(BPF_JGT, BPF_REG_0, threshold, 0));
155 : : }
156 : :
157 : : return 0;
158 : : }
159 : :
160 : 531 : int bf_matcher_generate_meta(struct bf_program *program,
161 : : const struct bf_matcher *matcher)
162 : : {
163 [ + + + + : 531 : switch (bf_matcher_get_type(matcher)) {
+ + - - ]
164 : 30 : case BF_MATCHER_META_IFACE:
165 : 30 : return _bf_matcher_generate_meta_iface(program, matcher);
166 : 86 : case BF_MATCHER_META_L3_PROTO: {
167 : 86 : uint16_t be_val = htobe16(*(uint16_t *)bf_matcher_payload(matcher));
168 : 86 : return bf_cmp_value(program, matcher, &be_val, 2, BPF_REG_7);
169 : : }
170 : 62 : case BF_MATCHER_META_L4_PROTO:
171 : 62 : return bf_cmp_value(program, matcher, bf_matcher_payload(matcher), 1,
172 : : BPF_REG_8);
173 : 80 : case BF_MATCHER_META_PROBABILITY:
174 : 80 : return _bf_matcher_generate_meta_probability(program, matcher);
175 : 248 : case BF_MATCHER_META_SPORT:
176 : : case BF_MATCHER_META_DPORT:
177 : 248 : return _bf_matcher_generate_meta_port(program, matcher);
178 : 25 : case BF_MATCHER_META_FLOW_PROBABILITY:
179 : 25 : return _bf_matcher_generate_meta_flow_probability(program, matcher);
180 : 0 : case BF_MATCHER_META_MARK:
181 : : case BF_MATCHER_META_FLOW_HASH:
182 [ # # ]: 0 : return bf_err_r(-ENOTSUP,
183 : : "matcher '%s' requires flavor-specific dispatch",
184 : : bf_matcher_type_to_str(bf_matcher_get_type(matcher)));
185 : 0 : default:
186 [ # # ]: 0 : return bf_err_r(-EINVAL, "unknown matcher type %d",
187 : : bf_matcher_get_type(matcher));
188 : : }
189 : : }
|