]>
Commit | Line | Data |
---|---|---|
3db468ea DM |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | ||
3db468ea DM |
3 | #include <net/if.h> |
4 | ||
3db468ea | 5 | #include "netlink-util.h" |
dbf63196 | 6 | #include "networkd-link.h" |
3db468ea DM |
7 | #include "networkd-manager.h" |
8 | #include "networkd-mdb.h" | |
dbf63196 | 9 | #include "networkd-network.h" |
8e412d64 | 10 | #include "string-util.h" |
3db468ea DM |
11 | #include "vlan-util.h" |
12 | ||
13 | #define STATIC_MDB_ENTRIES_PER_NETWORK_MAX 1024U | |
14 | ||
8c06da1c YW |
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) { | |
03c9738e YW |
21 | assert(mdb_entry->section); |
22 | hashmap_remove(mdb_entry->network->mdb_entries_by_section, mdb_entry->section); | |
8c06da1c YW |
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 | ||
3db468ea DM |
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); | |
03c9738e YW |
45 | assert(filename); |
46 | assert(section_line > 0); | |
3db468ea | 47 | |
03c9738e YW |
48 | r = network_config_section_new(filename, section_line, &n); |
49 | if (r < 0) | |
50 | return r; | |
3db468ea | 51 | |
03c9738e YW |
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; | |
3db468ea DM |
57 | } |
58 | ||
03c9738e | 59 | if (hashmap_size(network->mdb_entries_by_section) >= STATIC_MDB_ENTRIES_PER_NETWORK_MAX) |
3db468ea DM |
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, | |
03c9738e | 70 | .section = TAKE_PTR(n), |
3db468ea DM |
71 | }; |
72 | ||
03c9738e YW |
73 | r = hashmap_ensure_allocated(&network->mdb_entries_by_section, &network_config_hash_ops); |
74 | if (r < 0) | |
75 | return r; | |
3db468ea | 76 | |
03c9738e YW |
77 | r = hashmap_put(network->mdb_entries_by_section, mdb_entry->section, mdb_entry); |
78 | if (r < 0) | |
79 | return r; | |
3db468ea DM |
80 | |
81 | /* return allocated MDB structure. */ | |
82 | *ret = TAKE_PTR(mdb_entry); | |
3db468ea DM |
83 | return 0; |
84 | } | |
85 | ||
3db468ea DM |
86 | static int set_mdb_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { |
87 | int r; | |
88 | ||
89 | assert(link); | |
1f241589 YW |
90 | assert(link->bridge_mdb_messages > 0); |
91 | ||
92 | link->bridge_mdb_messages--; | |
3db468ea DM |
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); | |
17972401 YW |
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) { | |
3db468ea DM |
105 | log_link_message_warning_errno(link, m, r, "Could not add MDB entry"); |
106 | link_enter_failed(link); | |
107 | return 1; | |
108 | } | |
109 | ||
1f241589 YW |
110 | if (link->bridge_mdb_messages == 0) { |
111 | link->bridge_mdb_configured = true; | |
112 | link_check_ready(link); | |
113 | } | |
114 | ||
3db468ea DM |
115 | return 1; |
116 | } | |
117 | ||
a55100e6 YW |
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 | ||
3db468ea | 130 | /* send a request to the kernel to add an MDB entry */ |
1f241589 | 131 | static int mdb_entry_configure(Link *link, MdbEntry *mdb_entry) { |
3db468ea DM |
132 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; |
133 | struct br_mdb_entry entry; | |
a55100e6 | 134 | int master, r; |
3db468ea DM |
135 | |
136 | assert(link); | |
137 | assert(link->network); | |
138 | assert(link->manager); | |
139 | assert(mdb_entry); | |
140 | ||
8e412d64 YW |
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 | ||
a55100e6 YW |
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 | ||
3db468ea | 153 | entry = (struct br_mdb_entry) { |
a55100e6 YW |
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, | |
3db468ea DM |
157 | .ifindex = link->ifindex, |
158 | .vid = mdb_entry->vlan_id, | |
159 | }; | |
160 | ||
161 | /* create new RTM message */ | |
a55100e6 | 162 | r = sd_rtnl_message_new_mdb(link->manager->rtnl, &req, RTM_NEWMDB, master); |
3db468ea DM |
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 | } | |
6c9ebb7a | 194 | |
1f241589 YW |
195 | int link_set_bridge_mdb(Link *link) { |
196 | MdbEntry *mdb_entry; | |
1f241589 YW |
197 | int r; |
198 | ||
199 | assert(link); | |
17972401 | 200 | assert(link->manager); |
1f241589 YW |
201 | |
202 | link->bridge_mdb_configured = false; | |
203 | ||
204 | if (!link->network) | |
205 | return 0; | |
206 | ||
03c9738e | 207 | if (hashmap_isempty(link->network->mdb_entries_by_section)) |
a55100e6 | 208 | goto finish; |
1f241589 YW |
209 | |
210 | if (!link_has_carrier(link)) | |
211 | return log_link_debug(link, "Link does not have carrier yet, setting MDB entries later."); | |
212 | ||
a55100e6 YW |
213 | if (link->network->bridge) { |
214 | Link *master; | |
1f241589 | 215 | |
a55100e6 YW |
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; | |
17972401 YW |
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; | |
a55100e6 | 229 | } |
1f241589 | 230 | |
03c9738e | 231 | HASHMAP_FOREACH(mdb_entry, link->network->mdb_entries_by_section) { |
1f241589 YW |
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 | ||
a55100e6 | 239 | finish: |
1f241589 YW |
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 | ||
ee4522ce YW |
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 | ||
13ffa39f | 281 | void network_drop_invalid_mdb_entries(Network *network) { |
ee4522ce YW |
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 | ||
6c9ebb7a YW |
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 | } |