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