Line data Source code
1 : /* SPDX-License-Identifier: GPL-2.0-only */
2 : /*
3 : * Copyright (c) 2023 Meta Platforms, Inc. and affiliates.
4 : */
5 :
6 : #include "bpfilter/cgen/nf.h"
7 :
8 : #include <linux/bpf.h>
9 : #include <linux/bpf_common.h>
10 : #include <linux/if_ether.h>
11 : #include <linux/netfilter.h>
12 :
13 : #include <stdbool.h>
14 : #include <stddef.h>
15 : #include <stdint.h>
16 : #include <sys/socket.h>
17 :
18 : #include "bpfilter/cgen/jmp.h"
19 : #include "bpfilter/cgen/prog/link.h"
20 : #include "bpfilter/cgen/program.h"
21 : #include "bpfilter/cgen/stub.h"
22 : #include "bpfilter/cgen/swich.h"
23 : #include "core/btf.h"
24 : #include "core/flavor.h"
25 : #include "core/helper.h"
26 : #include "core/hook.h"
27 : #include "core/list.h"
28 : #include "core/logger.h"
29 : #include "core/verdict.h"
30 :
31 : #include "external/filter.h"
32 :
33 : #define BF_NF_PRIO_EVEN 2
34 : #define BF_NF_PRIO_ODD 1
35 :
36 : static int _bf_nf_gen_inline_prologue(struct bf_program *program);
37 : static int _bf_nf_gen_inline_epilogue(struct bf_program *program);
38 : static int _bf_nf_get_verdict(enum bf_verdict verdict);
39 : static int _bf_nf_attach_prog(
40 : struct bf_program *new_prog, struct bf_program *old_prog,
41 : int (*get_new_link_cb)(struct bf_program *prog, struct bf_link *old_link,
42 : struct bf_link **new_link));
43 : static int _bf_nf_detach_prog(struct bf_program *program);
44 :
45 : const struct bf_flavor_ops bf_flavor_ops_nf = {
46 : .gen_inline_prologue = _bf_nf_gen_inline_prologue,
47 : .gen_inline_epilogue = _bf_nf_gen_inline_epilogue,
48 : .get_verdict = _bf_nf_get_verdict,
49 : .attach_prog = _bf_nf_attach_prog,
50 : .detach_prog = _bf_nf_detach_prog,
51 : };
52 :
53 : // Forward definition to avoid headers clusterfuck.
54 : uint16_t htons(uint16_t hostshort);
55 :
56 : static inline bool _bf_nf_hook_is_ingress(enum bf_hook hook)
57 : {
58 0 : return hook == BF_HOOK_NF_PRE_ROUTING || hook == BF_HOOK_NF_LOCAL_IN ||
59 : hook == BF_HOOK_NF_FORWARD;
60 : }
61 :
62 0 : static int _bf_nf_gen_inline_prologue(struct bf_program *program)
63 : {
64 : int r;
65 : int offset;
66 :
67 0 : bf_assert(program);
68 :
69 : // Copy the ifindex from to bpf_nf_ctx.state.{in,out}.ifindex the runtime context
70 0 : if ((offset = bf_btf_get_field_off("bpf_nf_ctx", "state")) < 0)
71 : return offset;
72 0 : EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, offset));
73 0 : if (_bf_nf_hook_is_ingress(program->hook)) {
74 0 : if ((offset = bf_btf_get_field_off("nf_hook_state", "in")) < 0)
75 : return offset;
76 0 : EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_2, offset));
77 : } else {
78 0 : if ((offset = bf_btf_get_field_off("nf_hook_state", "out")) < 0)
79 : return offset;
80 0 : EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_2, offset));
81 : }
82 :
83 0 : if ((offset = bf_btf_get_field_off("net_device", "ifindex")) < 0)
84 : return offset;
85 0 : EMIT(program, BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_3, offset));
86 0 : EMIT(program,
87 : BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_4, BF_PROG_CTX_OFF(ifindex)));
88 :
89 : /* BPF_PROG_TYPE_CGROUP_SKB doesn't provide access the the Ethernet header,
90 : * so we can't parse it and discover the L3 protocol ID.
91 : * Instead, we use the __sk_buff.family value and convert it to the
92 : * corresponding ethertype. */
93 0 : if ((offset = bf_btf_get_field_off("nf_hook_state", "pf")) < 0)
94 : return offset;
95 0 : EMIT(program, BPF_LDX_MEM(BPF_B, BPF_REG_3, BPF_REG_2, offset));
96 :
97 : {
98 0 : _cleanup_bf_swich_ struct bf_swich swich =
99 0 : bf_swich_get(program, BPF_REG_3);
100 :
101 0 : EMIT_SWICH_OPTION(&swich, AF_INET,
102 : BPF_MOV64_IMM(BPF_REG_7, htons(ETH_P_IP)));
103 0 : EMIT_SWICH_OPTION(&swich, AF_INET6,
104 : BPF_MOV64_IMM(BPF_REG_7, htons(ETH_P_IPV6)));
105 0 : EMIT_SWICH_DEFAULT(&swich, BPF_MOV64_IMM(BPF_REG_7, 0));
106 :
107 0 : r = bf_swich_generate(&swich);
108 0 : if (r)
109 : return r;
110 : }
111 :
112 0 : EMIT(program, BPF_ST_MEM(BPF_W, BPF_REG_10, BF_PROG_CTX_OFF(l3_offset), 0));
113 :
114 : // Calculate the packet size (+ETH_HLEN) and store it into the runtime context
115 0 : if ((offset = bf_btf_get_field_off("bpf_nf_ctx", "skb")) < 0)
116 : return offset;
117 0 : EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, offset));
118 0 : if ((offset = bf_btf_get_field_off("sk_buff", "len")) < 0)
119 : return offset;
120 0 : EMIT(program, BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, offset));
121 0 : EMIT(program, BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, ETH_HLEN));
122 0 : EMIT(program,
123 : BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_2, BF_PROG_CTX_OFF(pkt_size)));
124 :
125 0 : r = bf_stub_make_ctx_skb_dynptr(program, BPF_REG_1);
126 0 : if (r)
127 : return r;
128 :
129 0 : r = bf_stub_parse_l3_hdr(program);
130 0 : if (r)
131 : return r;
132 :
133 0 : r = bf_stub_parse_l4_hdr(program);
134 : if (r)
135 : return r;
136 :
137 : return 0;
138 : }
139 :
140 0 : static int _bf_nf_gen_inline_epilogue(struct bf_program *program)
141 : {
142 : UNUSED(program);
143 :
144 0 : return 0;
145 : }
146 :
147 : /**
148 : * Convert a standard verdict into a return value.
149 : *
150 : * @param verdict Verdict to convert. Must be valid.
151 : * @return TC return code corresponding to the verdict, as an integer.
152 : */
153 0 : static int _bf_nf_get_verdict(enum bf_verdict verdict)
154 : {
155 0 : bf_assert(0 <= verdict && verdict < _BF_TERMINAL_VERDICT_MAX);
156 :
157 : static const int verdicts[] = {
158 : [BF_VERDICT_ACCEPT] = NF_ACCEPT,
159 : [BF_VERDICT_DROP] = NF_DROP,
160 : };
161 :
162 : static_assert(ARRAY_SIZE(verdicts) == _BF_TERMINAL_VERDICT_MAX);
163 :
164 0 : return verdicts[verdict];
165 : }
166 :
167 0 : static int _bf_nf_attach_prog(struct bf_program *new_prog,
168 : struct bf_program *old_prog,
169 : int (*get_new_link_cb)(struct bf_program *prog,
170 : struct bf_link *old_link,
171 : struct bf_link **new_link))
172 : {
173 : int r;
174 :
175 0 : bf_assert(new_prog && get_new_link_cb);
176 :
177 0 : if (old_prog && !bf_list_is_empty(&old_prog->links)) {
178 : /* BPF Netfilter programs can't be attached to NFPROTO_INET to filter
179 : * on both IPv4 and IPv6 at the same time. As a workaround, we attach
180 : * them to **both** NFPROTO_IPV4 and NFPROTO_IPV6. */
181 0 : bf_list_foreach (&old_prog->links, old_link_node) {
182 : struct bf_link *link;
183 0 : struct bpf_link_info info = {};
184 0 : struct bf_link *old_link = bf_list_node_get_data(old_link_node);
185 :
186 0 : r = bf_link_get_info(old_link, &info);
187 0 : if (r)
188 0 : return bf_err_r(r, "failed to get old Netfilter link info");
189 :
190 0 : r = get_new_link_cb(new_prog, NULL, &link);
191 0 : if (r)
192 : return r;
193 :
194 : /* BPF Netfilter programs can't be updated, so we need to create a
195 : * new link every time we want to attach a new program at the same
196 : * location. However, we can't create a new link with the same
197 : * priority. Hence, we use 100 and 101 successively. */
198 0 : r = bf_link_attach_nf(
199 : link, new_prog->runtime.prog_fd, info.netfilter.pf,
200 0 : info.netfilter.priority == BF_NF_PRIO_EVEN ? BF_NF_PRIO_ODD :
201 : BF_NF_PRIO_EVEN);
202 0 : if (r)
203 0 : return bf_err_r(r, "failed to attach Netfilter program");
204 : }
205 : } else {
206 : struct bf_link *link;
207 :
208 0 : r = get_new_link_cb(new_prog, NULL, &link);
209 0 : if (r)
210 0 : return r;
211 :
212 0 : r = bf_link_attach_nf(link, new_prog->runtime.prog_fd, NFPROTO_IPV4,
213 : BF_NF_PRIO_EVEN);
214 0 : if (r)
215 0 : return bf_err_r(r, "failed to attach Netfilter IPv4 program");
216 :
217 0 : r = get_new_link_cb(new_prog, NULL, &link);
218 0 : if (r)
219 : return r;
220 :
221 0 : r = bf_link_attach_nf(link, new_prog->runtime.prog_fd, NFPROTO_IPV6,
222 : BF_NF_PRIO_EVEN);
223 0 : if (r)
224 0 : return bf_err_r(r, "failed to attach Netfilter IPv6 program");
225 : }
226 :
227 : return 0;
228 : }
229 :
230 : /**
231 : * Unload the Netfilter BPF bytecode image.
232 : *
233 : * @param program Codegen containing the image to unload. Can't be NULL.
234 : * @return 0 on success, negative error code on failure.
235 : */
236 0 : static int _bf_nf_detach_prog(struct bf_program *program)
237 : {
238 0 : bf_assert(program);
239 :
240 0 : return bf_link_detach(
241 0 : bf_list_node_get_data(bf_list_get_head(&program->links)));
242 : }
|