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