]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-mdb.c
7b0c10a4af33dd6f12c74e01c293b12b984786e1
[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 < 0 && r != -EEXIST) {
107 log_link_message_warning_errno(link, m, r, "Could not add MDB entry");
108 link_enter_failed(link);
109 return 1;
110 }
111
112 if (link->bridge_mdb_messages == 0) {
113 link->bridge_mdb_configured = true;
114 link_check_ready(link);
115 }
116
117 return 1;
118 }
119
120 static int link_get_bridge_master_ifindex(Link *link) {
121 assert(link);
122
123 if (link->network && link->network->bridge)
124 return link->network->bridge->ifindex;
125
126 if (streq_ptr(link->kind, "bridge"))
127 return link->ifindex;
128
129 return 0;
130 }
131
132 /* send a request to the kernel to add an MDB entry */
133 static int mdb_entry_configure(Link *link, MdbEntry *mdb_entry) {
134 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
135 struct br_mdb_entry entry;
136 int master, r;
137
138 assert(link);
139 assert(link->network);
140 assert(link->manager);
141 assert(mdb_entry);
142
143 if (DEBUG_LOGGING) {
144 _cleanup_free_ char *a = NULL;
145
146 (void) in_addr_to_string(mdb_entry->family, &mdb_entry->group_addr, &a);
147 log_link_debug(link, "Configuring bridge MDB entry: MulticastGroupAddress=%s, VLANId=%u",
148 strna(a), mdb_entry->vlan_id);
149 }
150
151 master = link_get_bridge_master_ifindex(link);
152 if (master <= 0)
153 return log_link_error_errno(link, SYNTHETIC_ERRNO(EINVAL), "Invalid bridge master ifindex %i", master);
154
155 entry = (struct br_mdb_entry) {
156 /* If MDB entry is added on bridge master, then the state must be MDB_TEMPORARY.
157 * See br_mdb_add_group() in net/bridge/br_mdb.c of kernel. */
158 .state = master == link->ifindex ? MDB_TEMPORARY : MDB_PERMANENT,
159 .ifindex = link->ifindex,
160 .vid = mdb_entry->vlan_id,
161 };
162
163 /* create new RTM message */
164 r = sd_rtnl_message_new_mdb(link->manager->rtnl, &req, RTM_NEWMDB, master);
165 if (r < 0)
166 return log_link_error_errno(link, r, "Could not create RTM_NEWMDB message: %m");
167
168 switch (mdb_entry->family) {
169 case AF_INET:
170 entry.addr.u.ip4 = mdb_entry->group_addr.in.s_addr;
171 entry.addr.proto = htobe16(ETH_P_IP);
172 break;
173
174 case AF_INET6:
175 entry.addr.u.ip6 = mdb_entry->group_addr.in6;
176 entry.addr.proto = htobe16(ETH_P_IPV6);
177 break;
178
179 default:
180 assert_not_reached("Invalid address family");
181 }
182
183 r = sd_netlink_message_append_data(req, MDBA_SET_ENTRY, &entry, sizeof(entry));
184 if (r < 0)
185 return log_link_error_errno(link, r, "Could not append MDBA_SET_ENTRY attribute: %m");
186
187 r = netlink_call_async(link->manager->rtnl, NULL, req, set_mdb_handler,
188 link_netlink_destroy_callback, link);
189 if (r < 0)
190 return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
191
192 link_ref(link);
193
194 return 1;
195 }
196
197 int link_set_bridge_mdb(Link *link) {
198 MdbEntry *mdb_entry;
199 int r;
200
201 assert(link);
202
203 link->bridge_mdb_configured = false;
204
205 if (!link->network)
206 return 0;
207
208 if (LIST_IS_EMPTY(link->network->static_mdb_entries))
209 goto finish;
210
211 if (!link_has_carrier(link))
212 return log_link_debug(link, "Link does not have carrier yet, setting MDB entries later.");
213
214 if (link->network->bridge) {
215 Link *master;
216
217 r = link_get(link->manager, link->network->bridge->ifindex, &master);
218 if (r < 0)
219 return log_link_error_errno(link, r, "Failed to get Link object for Bridge=%s", link->network->bridge->ifname);
220
221 if (!link_has_carrier(master))
222 return log_link_debug(link, "Bridge interface %s does not have carrier yet, setting MDB entries later.", link->network->bridge->ifname);
223
224 } else if (!streq_ptr(link->kind, "bridge")) {
225 log_link_warning(link, "Link is neither a bridge master nor a bridge port, ignoring [BridgeMDB] sections.");
226 goto finish;
227 }
228
229 LIST_FOREACH(static_mdb_entries, mdb_entry, link->network->static_mdb_entries) {
230 r = mdb_entry_configure(link, mdb_entry);
231 if (r < 0)
232 return log_link_error_errno(link, r, "Failed to add MDB entry to multicast group database: %m");
233
234 link->bridge_mdb_messages++;
235 }
236
237 finish:
238 if (link->bridge_mdb_messages == 0) {
239 link->bridge_mdb_configured = true;
240 link_check_ready(link);
241 }
242
243 return 0;
244 }
245
246 /* parse the VLAN Id from config files. */
247 int config_parse_mdb_vlan_id(
248 const char *unit,
249 const char *filename,
250 unsigned line,
251 const char *section,
252 unsigned section_line,
253 const char *lvalue,
254 int ltype,
255 const char *rvalue,
256 void *data,
257 void *userdata) {
258
259 _cleanup_(mdb_entry_free_or_set_invalidp) MdbEntry *mdb_entry = NULL;
260 Network *network = userdata;
261 int r;
262
263 assert(filename);
264 assert(section);
265 assert(lvalue);
266 assert(rvalue);
267 assert(data);
268
269 r = mdb_entry_new_static(network, filename, section_line, &mdb_entry);
270 if (r < 0)
271 return log_oom();
272
273 r = config_parse_vlanid(unit, filename, line, section,
274 section_line, lvalue, ltype,
275 rvalue, &mdb_entry->vlan_id, userdata);
276 if (r < 0)
277 return r;
278
279 mdb_entry = NULL;
280
281 return 0;
282 }
283
284 /* parse the multicast group from config files. */
285 int config_parse_mdb_group_address(
286 const char *unit,
287 const char *filename,
288 unsigned line,
289 const char *section,
290 unsigned section_line,
291 const char *lvalue,
292 int ltype,
293 const char *rvalue,
294 void *data,
295 void *userdata) {
296
297 _cleanup_(mdb_entry_free_or_set_invalidp) MdbEntry *mdb_entry = NULL;
298 Network *network = userdata;
299 int r;
300
301 assert(filename);
302 assert(section);
303 assert(lvalue);
304 assert(rvalue);
305 assert(data);
306
307 r = mdb_entry_new_static(network, filename, section_line, &mdb_entry);
308 if (r < 0)
309 return log_oom();
310
311 r = in_addr_from_string_auto(rvalue, &mdb_entry->family, &mdb_entry->group_addr);
312 if (r < 0) {
313 log_syntax(unit, LOG_WARNING, filename, line, r, "Cannot parse multicast group address: %m");
314 return 0;
315 }
316
317 mdb_entry = NULL;
318
319 return 0;
320 }
321
322 int mdb_entry_verify(MdbEntry *mdb_entry) {
323 if (section_is_invalid(mdb_entry->section))
324 return -EINVAL;
325
326 if (mdb_entry->family == AF_UNSPEC)
327 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
328 "%s: [BridgeMDB] section without MulticastGroupAddress= field configured. "
329 "Ignoring [BridgeMDB] section from line %u.",
330 mdb_entry->section->filename, mdb_entry->section->line);
331
332 if (!in_addr_is_multicast(mdb_entry->family, &mdb_entry->group_addr))
333 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
334 "%s: MulticastGroupAddress= is not a multicast address. "
335 "Ignoring [BridgeMDB] section from line %u.",
336 mdb_entry->section->filename, mdb_entry->section->line);
337
338 if (mdb_entry->family == AF_INET) {
339 if (in4_addr_is_local_multicast(&mdb_entry->group_addr.in))
340 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
341 "%s: MulticastGroupAddress= is a local multicast address. "
342 "Ignoring [BridgeMDB] section from line %u.",
343 mdb_entry->section->filename, mdb_entry->section->line);
344 } else {
345 if (in6_addr_is_link_local_all_nodes(&mdb_entry->group_addr.in6))
346 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
347 "%s: MulticastGroupAddress= is the multicast all nodes address. "
348 "Ignoring [BridgeMDB] section from line %u.",
349 mdb_entry->section->filename, mdb_entry->section->line);
350 }
351
352 return 0;
353 }