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