]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-bridge-mdb.c
Merge pull request #31648 from neighbourhoodie/review-content
[thirdparty/systemd.git] / src / network / networkd-bridge-mdb.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 /* Make sure the net/if.h header is included before any linux/ one */
4 #include <net/if.h>
5 #include <linux/if_bridge.h>
6
7 #include "netlink-util.h"
8 #include "networkd-bridge-mdb.h"
9 #include "networkd-link.h"
10 #include "networkd-manager.h"
11 #include "networkd-network.h"
12 #include "networkd-queue.h"
13 #include "string-util.h"
14 #include "vlan-util.h"
15
16 #define STATIC_BRIDGE_MDB_ENTRIES_PER_NETWORK_MAX 1024U
17
18 /* remove MDB entry. */
19 BridgeMDB *bridge_mdb_free(BridgeMDB *mdb) {
20 if (!mdb)
21 return NULL;
22
23 if (mdb->network) {
24 assert(mdb->section);
25 hashmap_remove(mdb->network->bridge_mdb_entries_by_section, mdb->section);
26 }
27
28 config_section_free(mdb->section);
29
30 return mfree(mdb);
31 }
32
33 DEFINE_SECTION_CLEANUP_FUNCTIONS(BridgeMDB, bridge_mdb_free);
34
35 /* create a new MDB entry or get an existing one. */
36 static int bridge_mdb_new_static(
37 Network *network,
38 const char *filename,
39 unsigned section_line,
40 BridgeMDB **ret) {
41
42 _cleanup_(config_section_freep) ConfigSection *n = NULL;
43 _cleanup_(bridge_mdb_freep) BridgeMDB *mdb = NULL;
44 int r;
45
46 assert(network);
47 assert(ret);
48 assert(filename);
49 assert(section_line > 0);
50
51 r = config_section_new(filename, section_line, &n);
52 if (r < 0)
53 return r;
54
55 /* search entry in hashmap first. */
56 mdb = hashmap_get(network->bridge_mdb_entries_by_section, n);
57 if (mdb) {
58 *ret = TAKE_PTR(mdb);
59 return 0;
60 }
61
62 if (hashmap_size(network->bridge_mdb_entries_by_section) >= STATIC_BRIDGE_MDB_ENTRIES_PER_NETWORK_MAX)
63 return -E2BIG;
64
65 /* allocate space for an MDB entry. */
66 mdb = new(BridgeMDB, 1);
67 if (!mdb)
68 return -ENOMEM;
69
70 /* init MDB structure. */
71 *mdb = (BridgeMDB) {
72 .network = network,
73 .section = TAKE_PTR(n),
74 };
75
76 r = hashmap_ensure_put(&network->bridge_mdb_entries_by_section, &config_section_hash_ops, mdb->section, mdb);
77 if (r < 0)
78 return r;
79
80 /* return allocated MDB structure. */
81 *ret = TAKE_PTR(mdb);
82 return 0;
83 }
84
85 static int bridge_mdb_configure_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) {
86 int r;
87
88 assert(m);
89 assert(link);
90
91 r = sd_netlink_message_get_errno(m);
92 if (r == -EINVAL && streq_ptr(link->kind, "bridge") && link->master_ifindex <= 0) {
93 /* To configure bridge MDB entries on bridge master, 1bc844ee0faa1b92e3ede00bdd948021c78d7088 (v5.4) is required. */
94 if (!link->manager->bridge_mdb_on_master_not_supported) {
95 log_link_warning_errno(link, r, "Kernel seems not to support bridge MDB entries on bridge master, ignoring: %m");
96 link->manager->bridge_mdb_on_master_not_supported = true;
97 }
98 } else if (r < 0 && r != -EEXIST) {
99 log_link_message_warning_errno(link, m, r, "Could not add MDB entry");
100 link_enter_failed(link);
101 return 1;
102 }
103
104 if (link->static_bridge_mdb_messages == 0) {
105 link->static_bridge_mdb_configured = true;
106 link_check_ready(link);
107 }
108
109 return 1;
110 }
111
112 /* send a request to the kernel to add an MDB entry */
113 static int bridge_mdb_configure(BridgeMDB *mdb, Link *link, Request *req) {
114 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
115 struct br_mdb_entry entry;
116 int r;
117
118 assert(mdb);
119 assert(link);
120 assert(link->manager);
121 assert(req);
122
123 if (DEBUG_LOGGING)
124 log_link_debug(link, "Configuring bridge MDB entry: MulticastGroupAddress=%s, VLANId=%u",
125 IN_ADDR_TO_STRING(mdb->family, &mdb->group_addr), mdb->vlan_id);
126
127 entry = (struct br_mdb_entry) {
128 /* If MDB entry is added on bridge master, then the state must be MDB_TEMPORARY.
129 * See br_mdb_add_group() in net/bridge/br_mdb.c of kernel. */
130 .state = link->master_ifindex <= 0 ? MDB_TEMPORARY : MDB_PERMANENT,
131 .ifindex = link->ifindex,
132 .vid = mdb->vlan_id,
133 };
134
135 switch (mdb->family) {
136 case AF_INET:
137 entry.addr.u.ip4 = mdb->group_addr.in.s_addr;
138 entry.addr.proto = htobe16(ETH_P_IP);
139 break;
140
141 case AF_INET6:
142 entry.addr.u.ip6 = mdb->group_addr.in6;
143 entry.addr.proto = htobe16(ETH_P_IPV6);
144 break;
145
146 default:
147 assert_not_reached();
148 }
149
150 r = sd_rtnl_message_new_mdb(link->manager->rtnl, &m, RTM_NEWMDB,
151 link->master_ifindex > 0 ? link->master_ifindex : link->ifindex);
152 if (r < 0)
153 return r;
154
155 r = sd_netlink_message_append_data(m, MDBA_SET_ENTRY, &entry, sizeof(entry));
156 if (r < 0)
157 return r;
158
159 return request_call_netlink_async(link->manager->rtnl, m, req);
160 }
161
162 static bool bridge_mdb_is_ready_to_configure(Link *link) {
163 Link *master;
164
165 assert(link);
166
167 if (!link_is_ready_to_configure(link, false))
168 return false;
169
170 if (!link->master_set)
171 return false;
172
173 if (link->master_ifindex <= 0 && streq_ptr(link->kind, "bridge"))
174 return true; /* The interface is bridge master. */
175
176 if (link_get_master(link, &master) < 0)
177 return false;
178
179 if (!streq_ptr(master->kind, "bridge"))
180 return false;
181
182 if (!IN_SET(master->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
183 return false;
184
185 if (master->set_flags_messages > 0)
186 return false;
187
188 if (!link_has_carrier(master))
189 return false;
190
191 return true;
192 }
193
194 static int bridge_mdb_process_request(Request *req, Link *link, void *userdata) {
195 BridgeMDB *mdb = ASSERT_PTR(userdata);
196 int r;
197
198 assert(req);
199 assert(link);
200
201 if (!bridge_mdb_is_ready_to_configure(link))
202 return 0;
203
204 r = bridge_mdb_configure(mdb, link, req);
205 if (r < 0)
206 return log_link_warning_errno(link, r, "Failed to configure bridge MDB: %m");
207
208 return 1;
209 }
210
211 int link_request_static_bridge_mdb(Link *link) {
212 BridgeMDB *mdb;
213 int r;
214
215 assert(link);
216 assert(link->manager);
217
218 link->static_bridge_mdb_configured = false;
219
220 if (!link->network)
221 return 0;
222
223 if (hashmap_isempty(link->network->bridge_mdb_entries_by_section))
224 goto finish;
225
226 HASHMAP_FOREACH(mdb, link->network->bridge_mdb_entries_by_section) {
227 r = link_queue_request_full(link, REQUEST_TYPE_BRIDGE_MDB,
228 mdb, NULL,
229 trivial_hash_func,
230 trivial_compare_func,
231 bridge_mdb_process_request,
232 &link->static_bridge_mdb_messages,
233 bridge_mdb_configure_handler,
234 NULL);
235 if (r < 0)
236 return log_link_error_errno(link, r, "Failed to request MDB entry to multicast group database: %m");
237 }
238
239 finish:
240 if (link->static_bridge_mdb_messages == 0) {
241 link->static_bridge_mdb_configured = true;
242 link_check_ready(link);
243 } else {
244 log_link_debug(link, "Setting bridge MDB entries.");
245 link_set_state(link, LINK_STATE_CONFIGURING);
246 }
247
248 return 0;
249 }
250
251 static int bridge_mdb_verify(BridgeMDB *mdb) {
252 if (section_is_invalid(mdb->section))
253 return -EINVAL;
254
255 if (mdb->family == AF_UNSPEC)
256 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
257 "%s: [BridgeMDB] section without MulticastGroupAddress= field configured. "
258 "Ignoring [BridgeMDB] section from line %u.",
259 mdb->section->filename, mdb->section->line);
260
261 if (!in_addr_is_multicast(mdb->family, &mdb->group_addr))
262 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
263 "%s: MulticastGroupAddress= is not a multicast address. "
264 "Ignoring [BridgeMDB] section from line %u.",
265 mdb->section->filename, mdb->section->line);
266
267 if (mdb->family == AF_INET) {
268 if (in4_addr_is_local_multicast(&mdb->group_addr.in))
269 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
270 "%s: MulticastGroupAddress= is a local multicast address. "
271 "Ignoring [BridgeMDB] section from line %u.",
272 mdb->section->filename, mdb->section->line);
273 } else {
274 if (in6_addr_is_link_local_all_nodes(&mdb->group_addr.in6))
275 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
276 "%s: MulticastGroupAddress= is the multicast all nodes address. "
277 "Ignoring [BridgeMDB] section from line %u.",
278 mdb->section->filename, mdb->section->line);
279 }
280
281 return 0;
282 }
283
284 void network_drop_invalid_bridge_mdb_entries(Network *network) {
285 BridgeMDB *mdb;
286
287 assert(network);
288
289 HASHMAP_FOREACH(mdb, network->bridge_mdb_entries_by_section)
290 if (bridge_mdb_verify(mdb) < 0)
291 bridge_mdb_free(mdb);
292 }
293
294 /* parse the VLAN Id from config files. */
295 int config_parse_mdb_vlan_id(
296 const char *unit,
297 const char *filename,
298 unsigned line,
299 const char *section,
300 unsigned section_line,
301 const char *lvalue,
302 int ltype,
303 const char *rvalue,
304 void *data,
305 void *userdata) {
306
307 _cleanup_(bridge_mdb_free_or_set_invalidp) BridgeMDB *mdb = NULL;
308 Network *network = userdata;
309 int r;
310
311 assert(filename);
312 assert(section);
313 assert(lvalue);
314 assert(rvalue);
315 assert(data);
316
317 r = bridge_mdb_new_static(network, filename, section_line, &mdb);
318 if (r < 0)
319 return log_oom();
320
321 r = config_parse_vlanid(unit, filename, line, section,
322 section_line, lvalue, ltype,
323 rvalue, &mdb->vlan_id, userdata);
324 if (r < 0)
325 return r;
326
327 TAKE_PTR(mdb);
328 return 0;
329 }
330
331 /* parse the multicast group from config files. */
332 int config_parse_mdb_group_address(
333 const char *unit,
334 const char *filename,
335 unsigned line,
336 const char *section,
337 unsigned section_line,
338 const char *lvalue,
339 int ltype,
340 const char *rvalue,
341 void *data,
342 void *userdata) {
343
344 _cleanup_(bridge_mdb_free_or_set_invalidp) BridgeMDB *mdb = NULL;
345 Network *network = userdata;
346 int r;
347
348 assert(filename);
349 assert(section);
350 assert(lvalue);
351 assert(rvalue);
352 assert(data);
353
354 r = bridge_mdb_new_static(network, filename, section_line, &mdb);
355 if (r < 0)
356 return log_oom();
357
358 r = in_addr_from_string_auto(rvalue, &mdb->family, &mdb->group_addr);
359 if (r < 0) {
360 log_syntax(unit, LOG_WARNING, filename, line, r, "Cannot parse multicast group address: %m");
361 return 0;
362 }
363
364 TAKE_PTR(mdb);
365 return 0;
366 }