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. */
bridge_mdb_free(BridgeMDB * mdb)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 config_section_free(mdb->section);
28
29 return mfree(mdb);
30 }
31
32 DEFINE_SECTION_CLEANUP_FUNCTIONS(BridgeMDB, bridge_mdb_free);
33
34 /* create a new MDB entry or get an existing one. */
bridge_mdb_new_static(Network * network,const char * filename,unsigned section_line,BridgeMDB ** ret)35 static int bridge_mdb_new_static(
36 Network *network,
37 const char *filename,
38 unsigned section_line,
39 BridgeMDB **ret) {
40
41 _cleanup_(config_section_freep) ConfigSection *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 = 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, &config_section_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
bridge_mdb_configure_handler(sd_netlink * rtnl,sd_netlink_message * m,Request * req,Link * link,void * userdata)84 static int bridge_mdb_configure_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) {
85 int r;
86
87 assert(m);
88 assert(link);
89
90 r = sd_netlink_message_get_errno(m);
91 if (r == -EINVAL && streq_ptr(link->kind, "bridge") && link->master_ifindex <= 0) {
92 /* To configure bridge MDB entries on bridge master, 1bc844ee0faa1b92e3ede00bdd948021c78d7088 (v5.4) is required. */
93 if (!link->manager->bridge_mdb_on_master_not_supported) {
94 log_link_warning_errno(link, r, "Kernel seems not to support bridge MDB entries on bridge master, ignoring: %m");
95 link->manager->bridge_mdb_on_master_not_supported = true;
96 }
97 } else if (r < 0 && r != -EEXIST) {
98 log_link_message_warning_errno(link, m, r, "Could not add MDB entry");
99 link_enter_failed(link);
100 return 1;
101 }
102
103 if (link->static_bridge_mdb_messages == 0) {
104 link->static_bridge_mdb_configured = true;
105 link_check_ready(link);
106 }
107
108 return 1;
109 }
110
111 /* send a request to the kernel to add an MDB entry */
bridge_mdb_configure(BridgeMDB * mdb,Link * link,Request * req)112 static int bridge_mdb_configure(BridgeMDB *mdb, Link *link, Request *req) {
113 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
114 struct br_mdb_entry entry;
115 int r;
116
117 assert(mdb);
118 assert(link);
119 assert(link->manager);
120 assert(req);
121
122 if (DEBUG_LOGGING) {
123 _cleanup_free_ char *a = NULL;
124
125 (void) in_addr_to_string(mdb->family, &mdb->group_addr, &a);
126 log_link_debug(link, "Configuring bridge MDB entry: MulticastGroupAddress=%s, VLANId=%u",
127 strna(a), mdb->vlan_id);
128 }
129
130 entry = (struct br_mdb_entry) {
131 /* If MDB entry is added on bridge master, then the state must be MDB_TEMPORARY.
132 * See br_mdb_add_group() in net/bridge/br_mdb.c of kernel. */
133 .state = link->master_ifindex <= 0 ? MDB_TEMPORARY : MDB_PERMANENT,
134 .ifindex = link->ifindex,
135 .vid = mdb->vlan_id,
136 };
137
138 switch (mdb->family) {
139 case AF_INET:
140 entry.addr.u.ip4 = mdb->group_addr.in.s_addr;
141 entry.addr.proto = htobe16(ETH_P_IP);
142 break;
143
144 case AF_INET6:
145 entry.addr.u.ip6 = mdb->group_addr.in6;
146 entry.addr.proto = htobe16(ETH_P_IPV6);
147 break;
148
149 default:
150 assert_not_reached();
151 }
152
153 r = sd_rtnl_message_new_mdb(link->manager->rtnl, &m, RTM_NEWMDB,
154 link->master_ifindex > 0 ? link->master_ifindex : link->ifindex);
155 if (r < 0)
156 return r;
157
158 r = sd_netlink_message_append_data(m, MDBA_SET_ENTRY, &entry, sizeof(entry));
159 if (r < 0)
160 return r;
161
162 return request_call_netlink_async(link->manager->rtnl, m, req);
163 }
164
bridge_mdb_is_ready_to_configure(Link * link)165 static bool bridge_mdb_is_ready_to_configure(Link *link) {
166 Link *master;
167
168 assert(link);
169
170 if (!link_is_ready_to_configure(link, false))
171 return false;
172
173 if (!link->master_set)
174 return false;
175
176 if (link->master_ifindex <= 0 && streq_ptr(link->kind, "bridge"))
177 return true; /* The interface is bridge master. */
178
179 if (link_get_master(link, &master) < 0)
180 return false;
181
182 if (!streq_ptr(master->kind, "bridge"))
183 return false;
184
185 if (!IN_SET(master->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
186 return false;
187
188 if (master->set_flags_messages > 0)
189 return false;
190
191 if (!link_has_carrier(master))
192 return false;
193
194 return true;
195 }
196
bridge_mdb_process_request(Request * req,Link * link,void * userdata)197 static int bridge_mdb_process_request(Request *req, Link *link, void *userdata) {
198 BridgeMDB *mdb = ASSERT_PTR(userdata);
199 int r;
200
201 assert(req);
202 assert(link);
203
204 if (!bridge_mdb_is_ready_to_configure(link))
205 return 0;
206
207 r = bridge_mdb_configure(mdb, link, req);
208 if (r < 0)
209 return log_link_warning_errno(link, r, "Failed to configure bridge MDB: %m");
210
211 return 1;
212 }
213
link_request_static_bridge_mdb(Link * link)214 int link_request_static_bridge_mdb(Link *link) {
215 BridgeMDB *mdb;
216 int r;
217
218 assert(link);
219 assert(link->manager);
220
221 link->static_bridge_mdb_configured = false;
222
223 if (!link->network)
224 return 0;
225
226 if (hashmap_isempty(link->network->bridge_mdb_entries_by_section))
227 goto finish;
228
229 HASHMAP_FOREACH(mdb, link->network->bridge_mdb_entries_by_section) {
230 r = link_queue_request_full(link, REQUEST_TYPE_BRIDGE_MDB,
231 mdb, NULL,
232 trivial_hash_func,
233 trivial_compare_func,
234 bridge_mdb_process_request,
235 &link->static_bridge_mdb_messages,
236 bridge_mdb_configure_handler,
237 NULL);
238 if (r < 0)
239 return log_link_error_errno(link, r, "Failed to request MDB entry to multicast group database: %m");
240 }
241
242 finish:
243 if (link->static_bridge_mdb_messages == 0) {
244 link->static_bridge_mdb_configured = true;
245 link_check_ready(link);
246 } else {
247 log_link_debug(link, "Setting bridge MDB entries.");
248 link_set_state(link, LINK_STATE_CONFIGURING);
249 }
250
251 return 0;
252 }
253
bridge_mdb_verify(BridgeMDB * mdb)254 static int bridge_mdb_verify(BridgeMDB *mdb) {
255 if (section_is_invalid(mdb->section))
256 return -EINVAL;
257
258 if (mdb->family == AF_UNSPEC)
259 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
260 "%s: [BridgeMDB] section without MulticastGroupAddress= field configured. "
261 "Ignoring [BridgeMDB] section from line %u.",
262 mdb->section->filename, mdb->section->line);
263
264 if (!in_addr_is_multicast(mdb->family, &mdb->group_addr))
265 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
266 "%s: MulticastGroupAddress= is not a multicast address. "
267 "Ignoring [BridgeMDB] section from line %u.",
268 mdb->section->filename, mdb->section->line);
269
270 if (mdb->family == AF_INET) {
271 if (in4_addr_is_local_multicast(&mdb->group_addr.in))
272 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
273 "%s: MulticastGroupAddress= is a local multicast address. "
274 "Ignoring [BridgeMDB] section from line %u.",
275 mdb->section->filename, mdb->section->line);
276 } else {
277 if (in6_addr_is_link_local_all_nodes(&mdb->group_addr.in6))
278 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
279 "%s: MulticastGroupAddress= is the multicast all nodes address. "
280 "Ignoring [BridgeMDB] section from line %u.",
281 mdb->section->filename, mdb->section->line);
282 }
283
284 return 0;
285 }
286
network_drop_invalid_bridge_mdb_entries(Network * network)287 void network_drop_invalid_bridge_mdb_entries(Network *network) {
288 BridgeMDB *mdb;
289
290 assert(network);
291
292 HASHMAP_FOREACH(mdb, network->bridge_mdb_entries_by_section)
293 if (bridge_mdb_verify(mdb) < 0)
294 bridge_mdb_free(mdb);
295 }
296
297 /* parse the VLAN Id from config files. */
config_parse_mdb_vlan_id(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)298 int config_parse_mdb_vlan_id(
299 const char *unit,
300 const char *filename,
301 unsigned line,
302 const char *section,
303 unsigned section_line,
304 const char *lvalue,
305 int ltype,
306 const char *rvalue,
307 void *data,
308 void *userdata) {
309
310 _cleanup_(bridge_mdb_free_or_set_invalidp) BridgeMDB *mdb = NULL;
311 Network *network = userdata;
312 int r;
313
314 assert(filename);
315 assert(section);
316 assert(lvalue);
317 assert(rvalue);
318 assert(data);
319
320 r = bridge_mdb_new_static(network, filename, section_line, &mdb);
321 if (r < 0)
322 return log_oom();
323
324 r = config_parse_vlanid(unit, filename, line, section,
325 section_line, lvalue, ltype,
326 rvalue, &mdb->vlan_id, userdata);
327 if (r < 0)
328 return r;
329
330 TAKE_PTR(mdb);
331 return 0;
332 }
333
334 /* parse the multicast group from config files. */
config_parse_mdb_group_address(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)335 int config_parse_mdb_group_address(
336 const char *unit,
337 const char *filename,
338 unsigned line,
339 const char *section,
340 unsigned section_line,
341 const char *lvalue,
342 int ltype,
343 const char *rvalue,
344 void *data,
345 void *userdata) {
346
347 _cleanup_(bridge_mdb_free_or_set_invalidp) BridgeMDB *mdb = NULL;
348 Network *network = userdata;
349 int r;
350
351 assert(filename);
352 assert(section);
353 assert(lvalue);
354 assert(rvalue);
355 assert(data);
356
357 r = bridge_mdb_new_static(network, filename, section_line, &mdb);
358 if (r < 0)
359 return log_oom();
360
361 r = in_addr_from_string_auto(rvalue, &mdb->family, &mdb->group_addr);
362 if (r < 0) {
363 log_syntax(unit, LOG_WARNING, filename, line, r, "Cannot parse multicast group address: %m");
364 return 0;
365 }
366
367 TAKE_PTR(mdb);
368 return 0;
369 }
370