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/swich.h"
7 :
8 : #include <linux/bpf.h>
9 : #include <linux/bpf_common.h>
10 :
11 : #include <errno.h>
12 : #include <stddef.h>
13 : #include <stdint.h>
14 : #include <stdlib.h>
15 : #include <string.h>
16 :
17 : #include "bpfilter/cgen/jmp.h"
18 : #include "bpfilter/cgen/program.h"
19 : #include "core/helper.h"
20 : #include "core/list.h"
21 : #include "core/logger.h"
22 :
23 : #include "external/filter.h"
24 :
25 : /// Cleanup attribute for a @ref bf_swich_option variable.
26 : #define _free_bf_swich_option_ __attribute__((cleanup(_bf_swich_option_free)))
27 :
28 : /**
29 : * @struct bf_swich_option
30 : *
31 : * Represent a @c case of the switch: contains the instructions to execute if
32 : * the switch's register is equal to a specific value.
33 : */
34 : struct bf_swich_option
35 : {
36 : /// Immediate value to match against the switch's register.
37 : uint32_t imm;
38 : /// Jump context used to jump from the comparison to the bytecode to
39 : /// execute, then to the end of the switch.
40 : struct bf_jmpctx jmp;
41 : /// Number of instructions for this option.
42 : size_t insns_len;
43 : /// Instructions to execute, as a flexible array member.
44 : struct bpf_insn insns[];
45 : };
46 :
47 : static void _bf_swich_option_free(struct bf_swich_option **option);
48 :
49 : /**
50 : * Allocate and initialize a new switch option (case).
51 : *
52 : * @param option Switch option to allocate and initialize. Can't be NULL.
53 : * @param imm Immediate value to match against the switch's register.
54 : * @param insns Instructions to execute if the option matches.
55 : * @param insns_len Number of instructions in @p insns .
56 : * @return 0 on success, or negative errno value on failure.
57 : */
58 13 : static int _bf_swich_option_new(struct bf_swich_option **option, uint32_t imm,
59 : const struct bpf_insn *insns, size_t insns_len)
60 : {
61 11 : _free_bf_swich_option_ struct bf_swich_option *_option = NULL;
62 :
63 13 : bf_assert(option);
64 12 : bf_assert(insns);
65 :
66 11 : _option = malloc(sizeof(*_option) + (sizeof(struct bpf_insn) * insns_len));
67 11 : if (!_option)
68 : return -ENOMEM;
69 :
70 11 : _option->imm = imm;
71 11 : _option->insns_len = insns_len;
72 11 : memcpy(_option->insns, insns, sizeof(struct bpf_insn) * insns_len);
73 :
74 11 : *option = TAKE_PTR(_option);
75 :
76 11 : return 0;
77 : }
78 :
79 : /**
80 : * Free a switch option.
81 : *
82 : * Free @p option is it points to valid memory. If @p option points to a NULL
83 : * pointer, nothing is done.
84 : *
85 : * @param option Option to free. Can't be NULL.
86 : */
87 36 : static void _bf_swich_option_free(struct bf_swich_option **option)
88 : {
89 36 : bf_assert(option);
90 :
91 35 : if (!*option)
92 : return;
93 :
94 11 : free(*option);
95 11 : *option = NULL;
96 : }
97 :
98 5 : int bf_swich_init(struct bf_swich *swich, struct bf_program *program, int reg)
99 : {
100 5 : bf_assert(swich);
101 4 : bf_assert(program);
102 :
103 3 : swich->program = program;
104 3 : swich->reg = reg;
105 :
106 3 : bf_list_init(
107 : &swich->options,
108 3 : (bf_list_ops[]) {{.free = (bf_list_ops_free)_bf_swich_option_free}});
109 :
110 3 : swich->default_opt = NULL;
111 :
112 3 : return 0;
113 : }
114 :
115 7 : void bf_swich_cleanup(struct bf_swich *swich)
116 : {
117 7 : bf_assert(swich);
118 :
119 6 : bf_list_clean(&swich->options);
120 6 : _bf_swich_option_free(&swich->default_opt);
121 6 : free(swich->default_opt);
122 6 : }
123 :
124 6 : int bf_swich_add_option(struct bf_swich *swich, uint32_t imm,
125 : const struct bpf_insn *insns, size_t insns_len)
126 : {
127 6 : _free_bf_swich_option_ struct bf_swich_option *option = NULL;
128 : int r;
129 :
130 6 : bf_assert(swich);
131 6 : bf_assert(insns);
132 :
133 6 : r = _bf_swich_option_new(&option, imm, insns, insns_len);
134 6 : if (r)
135 : return r;
136 :
137 6 : r = bf_list_add_tail(&swich->options, option);
138 6 : if (r)
139 : return r;
140 :
141 6 : TAKE_PTR(option);
142 :
143 6 : return 0;
144 : }
145 :
146 2 : int bf_swich_set_default(struct bf_swich *swich, const struct bpf_insn *insns,
147 : size_t insns_len)
148 : {
149 2 : _free_bf_swich_option_ struct bf_swich_option *option = NULL;
150 : int r;
151 :
152 2 : bf_assert(swich);
153 2 : bf_assert(insns);
154 :
155 2 : if (swich->default_opt) {
156 1 : bf_warn("default bf_swich option already exists, replacing it");
157 1 : _bf_swich_option_free(&swich->default_opt);
158 : }
159 :
160 2 : r = _bf_swich_option_new(&option, 0, insns, insns_len);
161 2 : if (r)
162 : return r;
163 :
164 2 : swich->default_opt = TAKE_PTR(option);
165 :
166 2 : return 0;
167 : }
168 :
169 2 : int bf_swich_generate(struct bf_swich *swich)
170 : {
171 2 : struct bf_program *program = swich->program;
172 :
173 : // Match an option against the value and jump to the related bytecode
174 14 : bf_list_foreach (&swich->options, option_node) {
175 6 : struct bf_swich_option *option = bf_list_node_get_data(option_node);
176 :
177 6 : option->jmp = bf_jmpctx_get(
178 : program, BPF_JMP_IMM(BPF_JEQ, swich->reg, option->imm, 0));
179 : }
180 :
181 : // Insert the default option if any
182 2 : if (swich->default_opt)
183 1 : swich->default_opt->jmp = bf_jmpctx_get(program, BPF_JMP_A(0));
184 :
185 : // Insert each option's bytecode
186 14 : bf_list_foreach (&swich->options, option_node) {
187 6 : struct bf_swich_option *option = bf_list_node_get_data(option_node);
188 :
189 6 : bf_jmpctx_cleanup(&option->jmp);
190 18 : for (size_t i = 0; i < option->insns_len; ++i)
191 12 : EMIT(program, option->insns[i]);
192 6 : option->jmp = bf_jmpctx_get(program, BPF_JMP_A(0));
193 : }
194 :
195 : // Insert the default instructions
196 2 : if (swich->default_opt) {
197 1 : bf_jmpctx_cleanup(&swich->default_opt->jmp);
198 4 : for (size_t i = 0; i < swich->default_opt->insns_len; ++i)
199 3 : EMIT(program, swich->default_opt->insns[i]);
200 : }
201 :
202 16 : bf_list_foreach (&swich->options, option_node) {
203 6 : struct bf_swich_option *option = bf_list_node_get_data(option_node);
204 :
205 6 : bf_jmpctx_cleanup(&option->jmp);
206 : }
207 :
208 : return 0;
209 : }
|