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