Branch data 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 : : #define _GNU_SOURCE
7 : :
8 : : #include <argp.h>
9 : : #include <errno.h>
10 : : #include <signal.h>
11 : : #include <stdint.h>
12 : : #include <string.h>
13 : : #include <sys/socket.h>
14 : : #include <sys/un.h>
15 : : #include <unistd.h>
16 : :
17 : : #include <bpfilter/btf.h>
18 : : #include <bpfilter/dump.h>
19 : : #include <bpfilter/helper.h>
20 : : #include <bpfilter/io.h>
21 : : #include <bpfilter/logger.h>
22 : : #include <bpfilter/ns.h>
23 : : #include <bpfilter/request.h>
24 : : #include <bpfilter/response.h>
25 : : #include <bpfilter/version.h>
26 : :
27 : : #include "ctx.h"
28 : :
29 : : #define BF_DEFAULT_BPFFS_PATH "/sys/fs/bpf"
30 : :
31 : : enum
32 : : {
33 : : BF_OPT_NO_IPTABLES_KEY,
34 : : BF_OPT_NO_NFTABLES_KEY,
35 : : BF_OPT_NO_CLI_KEY,
36 : : BF_OPT_WITH_BPF_TOKEN,
37 : : BF_OPT_BPFFS_PATH,
38 : : };
39 : :
40 : : struct bf_options
41 : : {
42 : : bool transient;
43 : : bool with_bpf_token;
44 : : const char *bpffs_path;
45 : : uint16_t verbose;
46 : : };
47 : :
48 : : static const char *_bf_verbose_strs[] = {
49 : : [BF_VERBOSE_DEBUG] = "debug",
50 : : [BF_VERBOSE_BPF] = "bpf",
51 : : [BF_VERBOSE_BYTECODE] = "bytecode",
52 : : };
53 : :
54 : : static_assert_enum_mapping(_bf_verbose_strs, _BF_VERBOSE_MAX);
55 : :
56 : 29 : static enum bf_verbose _bf_verbose_from_str(const char *str)
57 : : {
58 : : assert(str);
59 : :
60 [ + - ]: 29 : for (enum bf_verbose verbose = 0; verbose < _BF_VERBOSE_MAX; ++verbose) {
61 [ - + ]: 29 : if (bf_streq(_bf_verbose_strs[verbose], str))
62 : : return verbose;
63 : : }
64 : :
65 : : return -EINVAL;
66 : : }
67 : :
68 : : static struct argp_option _bf_options[] = {
69 : : {"transient", 't', 0, 0,
70 : : "Do not load or save runtime context and remove all BPF programs on shutdown",
71 : : 0},
72 : : {"buffer-len", 'b', "BUF_LEN_POW", 0,
73 : : "DEPRECATED. Size of the BPF log buffer as a power of 2 (only used when --verbose is used). Default: 16.",
74 : : 0},
75 : : {"no-iptables", BF_OPT_NO_IPTABLES_KEY, 0, 0,
76 : : "DEPRECATED. Disable iptables support", 0},
77 : : {"no-nftables", BF_OPT_NO_NFTABLES_KEY, 0, 0,
78 : : "DEPRECATED. Disable nftables support", 0},
79 : : {"no-cli", BF_OPT_NO_CLI_KEY, 0, 0, "DEPRECATED. Disable CLI support", 0},
80 : : {"with-bpf-token", BF_OPT_WITH_BPF_TOKEN, NULL, 0,
81 : : "Use a BPF token with the bpf() system calls. The token is created from the bpffs instance mounted at /sys/fs/bpf.",
82 : : 0},
83 : : {"bpffs-path", BF_OPT_BPFFS_PATH, "BPFFS_PATH", 0,
84 : : "Path to the bpffs to pin the BPF objects into. Defaults to " BF_DEFAULT_BPFFS_PATH
85 : : ".",
86 : : 0},
87 : : {"verbose", 'v', "VERBOSE_FLAG", 0,
88 : : "Verbose flags to enable. Can be used more than once.", 0},
89 : : {0},
90 : : };
91 : :
92 : 203 : static error_t _bf_opts_parser(int key, char *arg, struct argp_state *state)
93 : : {
94 : 203 : struct bf_options *args = state->input;
95 : : enum bf_verbose opt;
96 : :
97 : : (void)arg;
98 : :
99 [ - - - - : 203 : switch (key) {
- - + +
+ ]
100 : 0 : case 't':
101 : 0 : args->transient = true;
102 : 0 : break;
103 : 0 : case 'b':
104 [ # # ]: 0 : bf_warn(
105 : : "--buffer-len is deprecated, buffer size is defined automatically");
106 : : break;
107 : 0 : case BF_OPT_NO_IPTABLES_KEY:
108 [ # # ]: 0 : bf_warn("--no-iptables is deprecated");
109 : : break;
110 : 0 : case BF_OPT_NO_NFTABLES_KEY:
111 [ # # ]: 0 : bf_warn("--no-nftables is deprecated");
112 : : break;
113 : 0 : case BF_OPT_NO_CLI_KEY:
114 [ # # ]: 0 : bf_warn("--no-cli is deprecated");
115 : : break;
116 : 0 : case BF_OPT_WITH_BPF_TOKEN:
117 : 0 : args->with_bpf_token = true;
118 [ # # ]: 0 : bf_info("using a BPF token");
119 : : break;
120 : 29 : case BF_OPT_BPFFS_PATH:
121 : 29 : args->bpffs_path = arg;
122 [ - + ]: 29 : bf_info("using bpffs at %s", args->bpffs_path);
123 : : break;
124 : 29 : case 'v':
125 : 29 : opt = _bf_verbose_from_str(arg);
126 [ - + ]: 29 : if ((int)opt < 0) {
127 [ # # ]: 0 : return bf_err_r(
128 : : (int)opt,
129 : : "unknown --verbose option '%s', valid --verbose options: [debug, bpf, bytecode]",
130 : : arg);
131 : : }
132 [ + - ]: 29 : bf_info("enabling verbose for '%s'", arg);
133 [ + - ]: 29 : if (opt == BF_VERBOSE_DEBUG)
134 : 29 : bf_log_set_level(BF_LOG_DBG);
135 : 29 : args->verbose |= BF_FLAG(opt);
136 : 29 : break;
137 : : default:
138 : : return ARGP_ERR_UNKNOWN;
139 : : }
140 : :
141 : : return 0;
142 : : }
143 : :
144 : 29 : static int _bf_opts_init(struct bf_options *opts, int argc, char *argv[])
145 : : {
146 : 29 : struct argp argp = {_bf_options, _bf_opts_parser, NULL, NULL, 0, NULL,
147 : : NULL};
148 : :
149 : 29 : return argp_parse(&argp, argc, argv, 0, 0, opts);
150 : : }
151 : :
152 : : /**
153 : : * Global flag to indicate whether the daemon should stop.
154 : : */
155 : : static volatile sig_atomic_t _bf_stop_received = 0;
156 : :
157 : : /**
158 : : * Set atomic flag to stop the daemon if specific signals are received.
159 : : *
160 : : * @param sig Signal number.
161 : : */
162 : 28 : void _bf_sig_handler(int sig)
163 : : {
164 : : (void)sig;
165 : :
166 : 28 : _bf_stop_received = 1;
167 : 28 : }
168 : :
169 : : /**
170 : : * Initialize bpfilter's daemon runtime.
171 : : *
172 : : * Setup signal handler (for graceful shutdown), initialize a fresh context,
173 : : * discover existing chains from bpffs, and initialise various front-ends.
174 : : *
175 : : * @return 0 on success, negative error code on failure.
176 : : */
177 : 29 : static int _bf_init(int argc, char *argv[])
178 : : {
179 : 29 : struct sigaction sighandler = {.sa_handler = _bf_sig_handler};
180 : 29 : struct bf_options opts = {
181 : : .transient = false,
182 : : .with_bpf_token = false,
183 : : .bpffs_path = BF_DEFAULT_BPFFS_PATH,
184 : : .verbose = 0,
185 : : };
186 : : int r;
187 : :
188 [ - + ]: 29 : if (sigaction(SIGINT, &sighandler, NULL) < 0)
189 [ # # ]: 0 : return bf_err_r(errno, "can't override handler for SIGINT");
190 : :
191 [ - + ]: 29 : if (sigaction(SIGTERM, &sighandler, NULL) < 0)
192 [ # # ]: 0 : return bf_err_r(errno, "can't override handler for SIGTERM");
193 : :
194 [ + - ]: 29 : bf_info("starting bpfilter version %s", BF_VERSION);
195 : :
196 : 29 : r = _bf_opts_init(&opts, argc, argv);
197 [ - + ]: 29 : if (r < 0)
198 [ # # ]: 0 : return bf_err_r(r, "failed to parse command line arguments");
199 : :
200 : 29 : r = bf_ensure_dir(BF_RUNTIME_DIR);
201 [ - + ]: 29 : if (r)
202 [ # # ]: 0 : return bf_err_r(r, "failed to ensure runtime directory exists");
203 : :
204 : 29 : r = bf_ctx_setup(opts.transient, opts.with_bpf_token, opts.bpffs_path,
205 : 29 : opts.verbose);
206 [ - + ]: 29 : if (r)
207 [ # # ]: 0 : return bf_err_r(r, "failed to setup context");
208 : :
209 : 29 : bf_ctx_dump(EMPTY_PREFIX);
210 : :
211 : 29 : return 0;
212 : : }
213 : :
214 : : extern int bf_request_handler(const struct bf_request *request,
215 : : struct bf_response **response);
216 : :
217 : : /**
218 : : * Process a request.
219 : : *
220 : : * If the handler returns 0, @p response is expected to be filled, and ready
221 : : * to be returned to the client.
222 : : * If the handler returns a negative error code, @p response is filled by @ref
223 : : * _bf_process_request with a generated error response and 0 is returned. If
224 : : * generating the error response fails, then 0 is returned.
225 : : *
226 : : * In other words, if 0 is returned, @p response is ready to be sent back, if
227 : : * a negative error code is returned, an error occured during @p request
228 : : * processing, and no response is available.
229 : : *
230 : : * @param request Request to process. Can't be NULL.
231 : : * @param response Response to fill. Can't be NULL.
232 : : * @return 0 on success, negative error code on failure.
233 : : */
234 : 176 : static int _bf_process_request(struct bf_request *request,
235 : : struct bf_response **response)
236 : : {
237 : : int r;
238 : :
239 : : assert(request);
240 : : assert(response);
241 : :
242 [ - + ]: 352 : if (bf_request_cmd(request) < 0 ||
243 : 176 : bf_request_cmd(request) >= _BF_REQ_CMD_MAX) {
244 [ # # ]: 0 : bf_warn("received a request with command %d, unknown command, ignoring",
245 : : bf_request_cmd(request));
246 : 0 : return bf_response_new_failure(response, -EINVAL);
247 : : }
248 : :
249 [ + - ]: 176 : bf_info("processing request %s",
250 : : bf_request_cmd_to_str(bf_request_cmd(request)));
251 : :
252 : 176 : r = bf_request_handler(request, response);
253 [ - + ]: 176 : if (r) {
254 : : /* We failed to process the request, so we need to generate an
255 : : * error. If the error response is successfully generated, then we
256 : : * return 0, otherwise we return the error code. */
257 : 0 : r = bf_response_new_failure(response, r);
258 : : }
259 : :
260 : : return r;
261 : : }
262 : :
263 : : /**
264 : : * Loop and process requests.
265 : : *
266 : : * Create a socket and perform blocking accept() calls. For each connection,
267 : : * receive a request, process it, and send the response back.
268 : : *
269 : : * If a signal is received, @ref _bf_stop_received will be set to 1 by @ref
270 : : * _bf_sig_handler and blocking call to `accept()` will be interrupted.
271 : : *
272 : : * @return 0 on success, negative error code on failure.
273 : : */
274 : 29 : static int _bf_run(void)
275 : : {
276 : 29 : _cleanup_close_ int fd = -1;
277 : 29 : _cleanup_close_ int lock = -1;
278 : 29 : struct sockaddr_un addr = {};
279 : : struct ucred peer_cred;
280 : 29 : socklen_t peer_cred_len = sizeof(peer_cred);
281 : : int r;
282 : :
283 : 29 : lock = bf_acquire_lock(BF_LOCK_PATH);
284 [ + + ]: 29 : if (lock < 0) {
285 [ + - ]: 1 : return bf_err_r(
286 : : lock,
287 : : "failed to acquire the daemon lock, is the daemon already running? Error");
288 : : }
289 : :
290 : 28 : fd = socket(AF_UNIX, SOCK_STREAM, 0);
291 [ - + ]: 28 : if (fd < 0)
292 [ # # ]: 0 : return bf_err_r(errno, "failed to create socket");
293 : :
294 : : // We have a lock on the lock file, so no other daemon is running, we can
295 : : // remove the socket file (if any).
296 : 28 : unlink(BF_SOCKET_PATH);
297 : 28 : addr.sun_family = AF_UNIX;
298 : 28 : strncpy(addr.sun_path, BF_SOCKET_PATH, sizeof(addr.sun_path) - 1);
299 : :
300 : 28 : r = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
301 [ - + ]: 28 : if (r < 0) {
302 [ # # ]: 0 : return bf_err_r(errno, "failed to bind socket to %s", BF_SOCKET_PATH);
303 : : }
304 : :
305 : 28 : r = listen(fd, 1);
306 [ - + ]: 28 : if (r < 0)
307 [ # # ]: 0 : return bf_err_r(errno, "listen() failed");
308 : :
309 [ + - ]: 28 : bf_info("waiting for requests...");
310 : :
311 [ + + ]: 232 : while (!_bf_stop_received) {
312 : 204 : _cleanup_close_ int client_fd = -1;
313 : 204 : _free_bf_request_ struct bf_request *request = NULL;
314 : 204 : _free_bf_response_ struct bf_response *response = NULL;
315 : 204 : _clean_bf_ns_ struct bf_ns ns = bf_ns_default();
316 : :
317 : 204 : client_fd = accept(fd, NULL, NULL);
318 [ + + ]: 204 : if (client_fd < 0) {
319 [ + - ]: 28 : if (_bf_stop_received) {
320 [ + - ]: 28 : bf_info("received stop signal, exiting...");
321 : 28 : continue;
322 : : }
323 : :
324 [ # # ]: 0 : bf_err_r(errno, "failed to accept connection, ignoring");
325 : 0 : continue;
326 : : }
327 : :
328 : : // NOLINTNEXTLINE: SOL_SOCKET and SO_PEERCRED can't be directly included
329 : 176 : r = getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &peer_cred,
330 : : &peer_cred_len);
331 [ - + ]: 176 : if (r) {
332 [ # # ]: 0 : bf_err_r(errno,
333 : : "failed to read the client's credentials, ignoring");
334 : 0 : continue;
335 : : }
336 : :
337 : 176 : r = bf_ns_init(&ns, peer_cred.pid);
338 [ - + ]: 176 : if (r) {
339 [ # # ]: 0 : bf_err_r(r, "failed to open the client's namespaces, ignoring");
340 : 0 : continue;
341 : : }
342 : :
343 : 176 : r = bf_recv_request(client_fd, &request);
344 [ - + ]: 176 : if (r) {
345 [ # # ]: 0 : bf_err_r(r, "failed to receive request, ignoring");
346 : 0 : continue;
347 : : }
348 : :
349 : 176 : bf_request_set_ns(request, &ns);
350 : 176 : bf_request_set_fd(request, client_fd);
351 : :
352 : 176 : r = _bf_process_request(request, &response);
353 [ - + ]: 176 : if (r) {
354 [ # # ]: 0 : bf_err_r(r, "failed to process request, ignoring");
355 : 0 : continue;
356 : : }
357 : :
358 : 176 : r = bf_send_response(client_fd, response);
359 [ - + ]: 176 : if (r) {
360 [ # # ]: 0 : bf_err_r(r, "failed to send response, ignoring");
361 : 0 : continue;
362 : : }
363 : : }
364 : :
365 : : return 0;
366 : : }
367 : :
368 : 29 : int main(int argc, char *argv[])
369 : : {
370 : : int r;
371 : :
372 : 29 : bf_logger_setup();
373 : :
374 : 29 : argp_program_version = "bpfilter version " BF_VERSION;
375 : 29 : argp_program_bug_address = BF_CONTACT;
376 : :
377 : 29 : r = bf_btf_setup();
378 [ - + ]: 29 : if (r < 0)
379 [ # # ]: 0 : return bf_err_r(r, "failed to setup BTF module");
380 : :
381 : 29 : r = _bf_init(argc, argv);
382 [ - + ]: 29 : if (r < 0)
383 [ # # ]: 0 : return bf_err_r(r, "failed to initialize bpfilter");
384 : :
385 : 29 : r = _bf_run();
386 [ + + ]: 29 : if (r < 0)
387 [ + - ]: 1 : return bf_err_r(r, "run() failed");
388 : :
389 : 28 : bf_ctx_teardown();
390 : 28 : bf_btf_teardown();
391 : :
392 : 28 : return r;
393 : : }
|