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