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 <string.h>
12 : : #include <sys/socket.h>
13 : : #include <sys/un.h>
14 : : #include <unistd.h>
15 : :
16 : : #include <bpfilter/btf.h>
17 : : #include <bpfilter/dump.h>
18 : : #include <bpfilter/front.h>
19 : : #include <bpfilter/helper.h>
20 : : #include <bpfilter/io.h>
21 : : #include <bpfilter/logger.h>
22 : : #include <bpfilter/ns.h>
23 : : #include <bpfilter/pack.h>
24 : : #include <bpfilter/request.h>
25 : : #include <bpfilter/response.h>
26 : : #include <bpfilter/version.h>
27 : :
28 : : #include "ctx.h"
29 : : #include "opts.h"
30 : : #include "xlate/front.h"
31 : :
32 : : /**
33 : : * Global flag to indicate whether the daemon should stop.
34 : : */
35 : : static volatile sig_atomic_t _bf_stop_received = 0;
36 : :
37 : : /**
38 : : * Path to bpfilter's runtime context file.
39 : : *
40 : : * bpfilter will periodically save its internal context back to disk, to prevent
41 : : * spurious service interruption to lose information about the current state of
42 : : * the daemon.
43 : : *
44 : : * This runtime context is read back when the daemon is restarted, so bpfilter
45 : : * can manage the BPF programs that survived the daemon reboot.
46 : : */
47 : : static const char *ctx_path = BF_RUNTIME_DIR "/data.bin";
48 : :
49 : : /**
50 : : * Set atomic flag to stop the daemon if specific signals are received.
51 : : *
52 : : * @param sig Signal number.
53 : : */
54 : 19 : void _bf_sig_handler(int sig)
55 : : {
56 : : UNUSED(sig);
57 : :
58 : 19 : _bf_stop_received = 1;
59 : 19 : }
60 : :
61 : : /**
62 : : * Load bpfilter's runtime context from disk.
63 : : *
64 : : * Read the daemon's runtime context from @p path and initialize the internal
65 : : * context with it.
66 : : *
67 : : * @param path Path to the context file.
68 : : * @return This function will return:
69 : : * - 1 if the runtime context has been succesfully restored from the disk.
70 : : * - 0 if no serialized context has been found on the disk.
71 : : * - < 0 on error.
72 : : */
73 : 20 : static int _bf_load(const char *path)
74 : : {
75 : 20 : _free_bf_rpack_ bf_rpack_t *pack = NULL;
76 : 20 : _cleanup_free_ void *data = NULL;
77 : : bf_rpack_node_t child, array_node;
78 : : size_t data_len;
79 : : int r;
80 : :
81 : : bf_assert(path);
82 : :
83 [ + + ]: 20 : if (access(ctx_path, F_OK)) {
84 [ - + ]: 18 : if (errno != ENOENT) {
85 [ # # ]: 0 : return bf_info_r(errno, "failed test access to context file: %s",
86 : : path);
87 : : }
88 : :
89 [ + - ]: 18 : bf_info("no serialized context found on disk, "
90 : : "a new context will be created");
91 : :
92 : 18 : return 0;
93 : : }
94 : :
95 : 2 : r = bf_read_file(path, &data, &data_len);
96 [ + - ]: 2 : if (r < 0)
97 : : return r;
98 : :
99 : 2 : r = bf_rpack_new(&pack, data, data_len);
100 [ + - ]: 2 : if (r)
101 : : return r;
102 : :
103 : 2 : r = bf_rpack_kv_obj(bf_rpack_root(pack), "ctx", &child);
104 [ + - ]: 2 : if (r)
105 : : return r;
106 : :
107 : 2 : r = bf_ctx_load(child);
108 [ + - ]: 2 : if (r < 0)
109 : : return r;
110 : :
111 : 2 : r = bf_rpack_kv_array(bf_rpack_root(pack), "cache", &child);
112 [ + - ]: 2 : if (r)
113 : : return r;
114 [ + - + + : 16 : bf_rpack_array_foreach (child, array_node) {
+ + ]
115 [ - + ]: 6 : if (bf_rpack_is_nil(array_node))
116 : 0 : continue;
117 : :
118 : 6 : r = bf_front_ops_get(i)->unpack(array_node);
119 [ - + ]: 6 : if (r < 0) {
120 [ # # ]: 0 : return bf_err_r(r, "failed to restore context for %s",
121 : : bf_front_to_str(i));
122 : : }
123 : : }
124 : :
125 [ + - ]: 2 : bf_dbg("loaded serialized context from %s", path);
126 : :
127 : : return 1;
128 : : }
129 : :
130 : : /**
131 : : * Save bpfilter's runtime context to disk.
132 : : *
133 : : * @param path Path to the context file.
134 : : * @return 0 on success, negative error code on failure.
135 : : */
136 : 108 : static int _bf_save(const char *path)
137 : : {
138 : 108 : _free_bf_wpack_ bf_wpack_t *pack = NULL;
139 : : const void *data;
140 : : size_t data_len;
141 : : int r;
142 : :
143 : : bf_assert(path);
144 : :
145 [ + + ]: 108 : if (bf_ctx_is_empty()) {
146 : : /* If the context is empty, we don't need to save it and we can remove
147 : : * the existing save. */
148 : 31 : unlink(path);
149 : 31 : return 0;
150 : : }
151 : :
152 : 77 : r = bf_wpack_new(&pack);
153 [ + - ]: 77 : if (r)
154 : : return r;
155 : :
156 : 77 : bf_wpack_open_object(pack, "ctx");
157 : 77 : r = bf_ctx_save(pack);
158 [ + - ]: 77 : if (r)
159 : : return r;
160 : 77 : bf_wpack_close_object(pack);
161 : :
162 : : /** @todo Front packing should be name-based to avoid relying on the
163 : : * enumeration order. */
164 : 77 : bf_wpack_open_array(pack, "cache");
165 [ + + ]: 308 : for (int i = 0; i < _BF_FRONT_MAX; ++i) {
166 [ + - ]: 231 : if (bf_opts_is_front_enabled(i)) {
167 : 231 : bf_wpack_open_object(pack, NULL);
168 : 231 : r = bf_front_ops_get(i)->pack(pack);
169 [ + - ]: 231 : if (r < 0)
170 : : return r;
171 : 231 : bf_wpack_close_object(pack);
172 : : } else {
173 : 0 : bf_wpack_nil(pack);
174 : : }
175 : : }
176 : 77 : bf_wpack_close_array(pack);
177 : :
178 : 77 : r = bf_wpack_get_data(pack, &data, &data_len);
179 [ + - ]: 77 : if (r)
180 : : return r;
181 : :
182 : 77 : r = bf_write_file(path, data, data_len);
183 [ + - ]: 77 : if (r < 0)
184 : : return r;
185 : :
186 [ - + ]: 77 : bf_dbg("saved serialized context to %s", path);
187 : :
188 : : return 0;
189 : : }
190 : :
191 : : /**
192 : : * Initialize bpfilter's daemon runtime.
193 : : *
194 : : * Setup signal handler (for graceful shutdown), load context from disk, and
195 : : * initialise various front-ends.
196 : : *
197 : : * If no context can be loaded, a new one is initialized from scratch.
198 : : *
199 : : * Front-ends' @p init function is called every time. They are responsible for
200 : : * checking whether they need to perform any initialization or not, depending
201 : : * on the loaded runtime context.
202 : : *
203 : : * Updated context is saved back to disk.
204 : : *
205 : : * @return 0 on success, negative error code on failure.
206 : : */
207 : 20 : static int _bf_init(int argc, char *argv[])
208 : : {
209 : 20 : struct sigaction sighandler = {.sa_handler = _bf_sig_handler};
210 : : int r = 0;
211 : :
212 [ - + ]: 20 : if (sigaction(SIGINT, &sighandler, NULL) < 0)
213 [ # # ]: 0 : return bf_err_r(errno, "can't override handler for SIGINT");
214 : :
215 [ - + ]: 20 : if (sigaction(SIGTERM, &sighandler, NULL) < 0)
216 [ # # ]: 0 : return bf_err_r(errno, "can't override handler for SIGTERM");
217 : :
218 [ + - ]: 20 : bf_info("starting bpfilter version %s", BF_VERSION);
219 : :
220 : 20 : r = bf_opts_init(argc, argv);
221 [ - + ]: 20 : if (r < 0)
222 [ # # ]: 0 : return bf_err_r(r, "failed to parse command line arguments");
223 : :
224 : 20 : r = bf_ensure_dir(BF_RUNTIME_DIR);
225 [ - + ]: 20 : if (r)
226 [ # # ]: 0 : return bf_err_r(r, "failed to ensure runtime directory exists");
227 : :
228 : : // Either load context, or initialize it from scratch.
229 [ + - ]: 20 : if (!bf_opts_transient()) {
230 : 20 : r = _bf_load(ctx_path);
231 [ - + ]: 20 : if (r < 0)
232 [ # # ]: 0 : return bf_err_r(r, "failed to restore bpfilter context");
233 : : }
234 : :
235 [ + - + + ]: 20 : if (bf_opts_transient() || r == 0) {
236 : 18 : r = bf_ctx_setup();
237 [ - + ]: 18 : if (r < 0)
238 [ # # ]: 0 : return bf_err_r(r, "failed to setup context");
239 : : }
240 : :
241 : 20 : bf_ctx_dump(EMPTY_PREFIX);
242 : :
243 [ + + ]: 80 : for (enum bf_front front = 0; front < _BF_FRONT_MAX; ++front) {
244 [ - + ]: 60 : if (!bf_opts_is_front_enabled(front))
245 : 0 : continue;
246 : :
247 : 60 : r = bf_front_ops_get(front)->setup();
248 [ - + ]: 60 : if (r < 0) {
249 [ # # ]: 0 : return bf_err_r(r, "failed to setup front-end %s",
250 : : bf_front_to_str(front));
251 : : }
252 : :
253 [ + - ]: 60 : bf_dbg("completed setup for %s", bf_front_to_str(front));
254 : : }
255 : :
256 [ + - ]: 20 : if (!bf_opts_transient()) {
257 : 20 : r = _bf_save(ctx_path);
258 [ + - ]: 20 : if (r < 0) {
259 [ # # ]: 0 : return bf_err_r(r, "failed to backup context at %s", ctx_path);
260 : : }
261 : : }
262 : :
263 : : return 0;
264 : : }
265 : :
266 : : /**
267 : : * Clean up bpfilter's daemon runtime.
268 : : *
269 : : * @return 0 on success, negative error code on failure.
270 : : */
271 : 19 : static int _bf_clean(void)
272 : : {
273 : 19 : _cleanup_close_ int pindir_fd = -1;
274 : : int r;
275 : :
276 [ + + ]: 76 : for (enum bf_front front = 0; front < _BF_FRONT_MAX; ++front) {
277 [ - + ]: 57 : if (!bf_opts_is_front_enabled(front))
278 : 0 : continue;
279 : :
280 : 57 : r = bf_front_ops_get(front)->teardown();
281 [ - + ]: 57 : if (r < 0) {
282 [ # # ]: 0 : bf_warn_r(r, "failed to teardown front-end %s, continuing",
283 : : bf_front_to_str(front));
284 : : }
285 : : }
286 : :
287 : 19 : bf_ctx_teardown(bf_opts_transient());
288 : :
289 : 19 : r = bf_ctx_rm_pindir();
290 [ - + - - ]: 19 : if (r < 0 && r != -ENOENT && errno != -ENOTEMPTY)
291 [ # # ]: 0 : return bf_err_r(r, "failed to remove pin directory");
292 : :
293 : : return 0;
294 : : }
295 : :
296 : : /**
297 : : * Process a request.
298 : : *
299 : : * The handler corresponding to @p bf_request_front(request) will be called (if any).
300 : : * If the handler returns 0, @p response is expected to be filled, and ready
301 : : * to be returned to the client.
302 : : * If the handler returns a negative error code, @p response is filled by @ref
303 : : * _bf_process_request with a generated error response and 0 is returned. If
304 : : * generating the error response fails, then 0 is returned.
305 : : *
306 : : * In other words, if 0 is returned, @p response is ready to be sent back, if
307 : : * a negative error code is returned, an error occured during @p request
308 : : * processing, and no response is available.
309 : : *
310 : : * @param request Request to process. Can't be NULL.
311 : : * @param response Response to fill. Can't be NULL.
312 : : * @return 0 on success, negative error code on failure.
313 : : */
314 : 101 : static int _bf_process_request(struct bf_request *request,
315 : : struct bf_response **response)
316 : : {
317 : : const struct bf_front_ops *ops;
318 : : int r;
319 : :
320 : : bf_assert(request && response);
321 : :
322 [ - + ]: 202 : if (bf_request_front(request) < 0 ||
323 : 101 : bf_request_front(request) >= _BF_FRONT_MAX) {
324 [ # # ]: 0 : bf_warn("received a request from front %d, unknown front, ignoring",
325 : : bf_request_front(request));
326 : 0 : return bf_response_new_failure(response, -EINVAL);
327 : : }
328 : :
329 [ - + ]: 202 : if (bf_request_cmd(request) < 0 ||
330 : 101 : bf_request_cmd(request) >= _BF_REQ_CMD_MAX) {
331 [ # # ]: 0 : bf_warn("received a request with command %d, unknown command, ignoring",
332 : : bf_request_cmd(request));
333 : 0 : return bf_response_new_failure(response, -EINVAL);
334 : : }
335 : :
336 [ - + ]: 101 : if (!bf_opts_is_front_enabled(bf_request_front(request))) {
337 [ # # ]: 0 : bf_warn("received a request from %s, but front is disabled, ignoring",
338 : : bf_front_to_str(bf_request_front(request)));
339 : 0 : return bf_response_new_failure(response, -ENOTSUP);
340 : : }
341 : :
342 [ + - ]: 101 : bf_info("processing request %s from %s",
343 : : bf_request_cmd_to_str(bf_request_cmd(request)),
344 : : bf_front_to_str(bf_request_front(request)));
345 : :
346 : 101 : ops = bf_front_ops_get(bf_request_front(request));
347 : 101 : r = ops->request_handler(request, response);
348 [ - + ]: 101 : if (r) {
349 : : /* We failed to process the request, so we need to generate an
350 : : * error. If the error response is successfully generated, then we
351 : : * return 0, otherwise we return the error code. */
352 : 0 : r = bf_response_new_failure(response, r);
353 : : }
354 : :
355 [ + - + + ]: 202 : if (!bf_opts_transient() &&
356 [ + + ]: 200 : (bf_request_cmd(request) == BF_REQ_RULESET_FLUSH ||
357 [ + + ]: 190 : bf_request_cmd(request) == BF_REQ_RULESET_SET ||
358 [ + + ]: 149 : bf_request_cmd(request) == BF_REQ_CHAIN_SET ||
359 [ + + ]: 104 : bf_request_cmd(request) == BF_REQ_CHAIN_LOAD ||
360 [ + + ]: 82 : bf_request_cmd(request) == BF_REQ_CHAIN_ATTACH ||
361 [ + + ]: 67 : bf_request_cmd(request) == BF_REQ_CHAIN_UPDATE ||
362 : 31 : bf_request_cmd(request) == BF_REQ_CHAIN_FLUSH))
363 : 88 : r = _bf_save(ctx_path);
364 : :
365 : : return r;
366 : : }
367 : :
368 : : /**
369 : : * Loop and process requests.
370 : : *
371 : : * Create a socket and perform blocking accept() calls. For each connection,
372 : : * receive a request, process it, and send the response back.
373 : : *
374 : : * If a signal is received, @ref _bf_stop_received will be set to 1 by @ref
375 : : * _bf_sig_handler and blocking call to `accept()` will be interrupted.
376 : : *
377 : : * @return 0 on success, negative error code on failure.
378 : : */
379 : 20 : static int _bf_run(void)
380 : : {
381 : 20 : _cleanup_close_ int fd = -1;
382 : 20 : _cleanup_close_ int lock = -1;
383 : 20 : struct sockaddr_un addr = {};
384 : : struct ucred peer_cred;
385 : 20 : socklen_t peer_cred_len = sizeof(peer_cred);
386 : : int r;
387 : :
388 : 20 : lock = bf_acquire_lock(BF_LOCK_PATH);
389 [ + + ]: 20 : if (lock < 0) {
390 [ + - ]: 1 : return bf_err_r(
391 : : lock,
392 : : "failed to acquire the daemon lock, is the daemon already running? Error");
393 : : }
394 : :
395 : 19 : fd = socket(AF_UNIX, SOCK_STREAM, 0);
396 [ - + ]: 19 : if (fd < 0)
397 [ # # ]: 0 : return bf_err_r(errno, "failed to create socket");
398 : :
399 : : // We have a lock on the lock file, so no other daemon is running, we can
400 : : // remove the socket file (if any).
401 : 19 : unlink(BF_SOCKET_PATH);
402 : 19 : addr.sun_family = AF_UNIX;
403 : 19 : strncpy(addr.sun_path, BF_SOCKET_PATH, sizeof(addr.sun_path) - 1);
404 : :
405 : 19 : r = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
406 [ - + ]: 19 : if (r < 0) {
407 [ # # ]: 0 : return bf_err_r(errno, "failed to bind socket to %s", BF_SOCKET_PATH);
408 : : }
409 : :
410 : 19 : r = listen(fd, 1);
411 [ - + ]: 19 : if (r < 0)
412 [ # # ]: 0 : return bf_err_r(errno, "listen() failed");
413 : :
414 [ + - ]: 19 : bf_info("waiting for requests...");
415 : :
416 [ + + ]: 139 : while (!_bf_stop_received) {
417 : 120 : _cleanup_close_ int client_fd = -1;
418 : 120 : _free_bf_request_ struct bf_request *request = NULL;
419 : 120 : _free_bf_response_ struct bf_response *response = NULL;
420 : 120 : _clean_bf_ns_ struct bf_ns ns = bf_ns_default();
421 : :
422 : 120 : client_fd = accept(fd, NULL, NULL);
423 [ + + ]: 120 : if (client_fd < 0) {
424 [ + - ]: 19 : if (_bf_stop_received) {
425 [ + - ]: 19 : bf_info("received stop signal, exiting...");
426 : 19 : continue;
427 : : }
428 : :
429 [ # # ]: 0 : bf_err_r(errno, "failed to accept connection, ignoring");
430 : 0 : continue;
431 : : }
432 : :
433 : : // NOLINTNEXTLINE: SOL_SOCKET and SO_PEERCRED can't be directly included
434 : 101 : r = getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &peer_cred,
435 : : &peer_cred_len);
436 [ - + ]: 101 : if (r) {
437 [ # # ]: 0 : bf_err_r(errno,
438 : : "failed to read the client's credentials, ignoring");
439 : 0 : continue;
440 : : }
441 : :
442 : 101 : r = bf_ns_init(&ns, peer_cred.pid);
443 [ - + ]: 101 : if (r) {
444 [ # # ]: 0 : bf_err_r(r, "failed to open the client's namespaces, ignoring");
445 : 0 : continue;
446 : : }
447 : :
448 : 101 : r = bf_recv_request(client_fd, &request);
449 [ - + ]: 101 : if (r) {
450 [ # # ]: 0 : bf_err_r(r, "failed to receive request, ignoring");
451 : 0 : continue;
452 : : }
453 : :
454 : 101 : bf_request_set_ns(request, &ns);
455 : 101 : bf_request_set_fd(request, client_fd);
456 : :
457 : 101 : r = _bf_process_request(request, &response);
458 [ - + ]: 101 : if (r) {
459 [ # # ]: 0 : bf_err_r(r, "failed to process request, ignoring");
460 : 0 : continue;
461 : : }
462 : :
463 : 101 : r = bf_send_response(client_fd, response);
464 [ - + ]: 101 : if (r) {
465 [ # # ]: 0 : bf_err_r(r, "failed to send response, ignoring");
466 : 0 : continue;
467 : : }
468 : : }
469 : :
470 : : return 0;
471 : : }
472 : :
473 : 20 : int main(int argc, char *argv[])
474 : : {
475 : : int r;
476 : :
477 : 20 : bf_logger_setup();
478 : :
479 : 20 : argp_program_version = "bpfilter version " BF_VERSION;
480 : 20 : argp_program_bug_address = BF_CONTACT;
481 : :
482 : 20 : r = bf_btf_setup();
483 [ - + ]: 20 : if (r < 0)
484 [ # # ]: 0 : return bf_err_r(r, "failed to setup BTF module");
485 : :
486 : 20 : r = _bf_init(argc, argv);
487 [ - + ]: 20 : if (r < 0)
488 [ # # ]: 0 : return bf_err_r(r, "failed to initialize bpfilter");
489 : :
490 : 20 : r = _bf_run();
491 [ + + ]: 20 : if (r < 0)
492 [ + - ]: 1 : return bf_err_r(r, "run() failed");
493 : :
494 : 19 : _bf_clean();
495 : 19 : bf_btf_teardown();
496 : :
497 : 19 : return r;
498 : : }
|