]>
Commit | Line | Data |
---|---|---|
a032b68d | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
e735f4d4 | 2 | /*** |
b012e921 | 3 | Copyright © 2014 Intel Corporation. All rights reserved. |
e735f4d4 MP |
4 | ***/ |
5 | ||
e735f4d4 | 6 | #include <net/ethernet.h> |
db2df898 | 7 | #include <net/if.h> |
e735f4d4 | 8 | |
db2df898 | 9 | #include "alloc-util.h" |
e1f67bc7 | 10 | #include "bridge.h" |
d9dfd233 | 11 | #include "netlink-util.h" |
d9dfd233 | 12 | #include "networkd-fdb.h" |
a032b68d | 13 | #include "networkd-link.h" |
2897b343 | 14 | #include "networkd-manager.h" |
a032b68d | 15 | #include "networkd-network.h" |
f2dec872 | 16 | #include "parse-util.h" |
f2dec872 | 17 | #include "string-table.h" |
5a920b42 | 18 | #include "vlan-util.h" |
e1f67bc7 | 19 | #include "vxlan.h" |
5a920b42 MP |
20 | |
21 | #define STATIC_FDB_ENTRIES_PER_NETWORK_MAX 1024U | |
e735f4d4 | 22 | |
a032b68d MB |
23 | /* remove and FDB entry. */ |
24 | FdbEntry *fdb_entry_free(FdbEntry *fdb_entry) { | |
25 | if (!fdb_entry) | |
26 | return NULL; | |
27 | ||
28 | if (fdb_entry->network) { | |
29 | assert(fdb_entry->section); | |
30 | hashmap_remove(fdb_entry->network->fdb_entries_by_section, fdb_entry->section); | |
31 | } | |
32 | ||
33 | network_config_section_free(fdb_entry->section); | |
34 | return mfree(fdb_entry); | |
35 | } | |
f2dec872 | 36 | |
a032b68d | 37 | DEFINE_NETWORK_SECTION_FUNCTIONS(FdbEntry, fdb_entry_free); |
f2dec872 | 38 | |
e735f4d4 | 39 | /* create a new FDB entry or get an existing one. */ |
bb4f798a | 40 | static int fdb_entry_new_static( |
5a920b42 | 41 | Network *network, |
6e866b33 MB |
42 | const char *filename, |
43 | unsigned section_line, | |
5a920b42 MP |
44 | FdbEntry **ret) { |
45 | ||
6e866b33 | 46 | _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; |
b012e921 | 47 | _cleanup_(fdb_entry_freep) FdbEntry *fdb_entry = NULL; |
6e866b33 | 48 | int r; |
e735f4d4 MP |
49 | |
50 | assert(network); | |
5a920b42 | 51 | assert(ret); |
a032b68d MB |
52 | assert(filename); |
53 | assert(section_line > 0); | |
6e866b33 | 54 | |
a032b68d MB |
55 | r = network_config_section_new(filename, section_line, &n); |
56 | if (r < 0) | |
57 | return r; | |
e735f4d4 | 58 | |
a032b68d MB |
59 | /* search entry in hashmap first. */ |
60 | fdb_entry = hashmap_get(network->fdb_entries_by_section, n); | |
61 | if (fdb_entry) { | |
62 | *ret = TAKE_PTR(fdb_entry); | |
63 | return 0; | |
e735f4d4 MP |
64 | } |
65 | ||
a032b68d | 66 | if (hashmap_size(network->fdb_entries_by_section) >= STATIC_FDB_ENTRIES_PER_NETWORK_MAX) |
5a920b42 MP |
67 | return -E2BIG; |
68 | ||
e735f4d4 | 69 | /* allocate space for and FDB entry. */ |
6e866b33 MB |
70 | fdb_entry = new(FdbEntry, 1); |
71 | if (!fdb_entry) | |
e735f4d4 | 72 | return -ENOMEM; |
e735f4d4 MP |
73 | |
74 | /* init FDB structure. */ | |
6e866b33 MB |
75 | *fdb_entry = (FdbEntry) { |
76 | .network = network, | |
a032b68d | 77 | .section = TAKE_PTR(n), |
f2dec872 BR |
78 | .vni = VXLAN_VID_MAX + 1, |
79 | .fdb_ntf_flags = NEIGHBOR_CACHE_ENTRY_FLAGS_SELF, | |
6e866b33 | 80 | }; |
e735f4d4 | 81 | |
3a6ce677 | 82 | r = hashmap_ensure_put(&network->fdb_entries_by_section, &network_config_hash_ops, fdb_entry->section, fdb_entry); |
a032b68d MB |
83 | if (r < 0) |
84 | return r; | |
e735f4d4 MP |
85 | |
86 | /* return allocated FDB structure. */ | |
b012e921 | 87 | *ret = TAKE_PTR(fdb_entry); |
e735f4d4 MP |
88 | |
89 | return 0; | |
90 | } | |
91 | ||
6e866b33 | 92 | static int set_fdb_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { |
e735f4d4 MP |
93 | int r; |
94 | ||
95 | assert(link); | |
96 | ||
f2dec872 BR |
97 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) |
98 | return 1; | |
99 | ||
86f210e9 | 100 | r = sd_netlink_message_get_errno(m); |
f2dec872 | 101 | if (r < 0 && r != -EEXIST) { |
46cdbd49 | 102 | log_link_message_warning_errno(link, m, r, "Could not add FDB entry"); |
f2dec872 BR |
103 | link_enter_failed(link); |
104 | return 1; | |
105 | } | |
e735f4d4 MP |
106 | |
107 | return 1; | |
108 | } | |
109 | ||
110 | /* send a request to the kernel to add a FDB entry in its static MAC table. */ | |
a032b68d | 111 | static int fdb_entry_configure(Link *link, FdbEntry *fdb_entry) { |
4c89c718 | 112 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; |
e735f4d4 MP |
113 | int r; |
114 | ||
115 | assert(link); | |
8a584da2 | 116 | assert(link->network); |
e735f4d4 MP |
117 | assert(link->manager); |
118 | assert(fdb_entry); | |
119 | ||
e735f4d4 | 120 | /* create new RTM message */ |
a032b68d | 121 | r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_NEWNEIGH, link->ifindex, AF_BRIDGE); |
e735f4d4 | 122 | if (r < 0) |
46cdbd49 | 123 | return log_link_error_errno(link, r, "Could not create RTM_NEWNEIGH message: %m"); |
e735f4d4 | 124 | |
f2dec872 | 125 | r = sd_rtnl_message_neigh_set_flags(req, fdb_entry->fdb_ntf_flags); |
e735f4d4 | 126 | if (r < 0) |
46cdbd49 | 127 | return log_link_error_errno(link, r, "Could not set neighbor flags: %m"); |
e735f4d4 MP |
128 | |
129 | /* only NUD_PERMANENT state supported. */ | |
130 | r = sd_rtnl_message_neigh_set_state(req, NUD_NOARP | NUD_PERMANENT); | |
131 | if (r < 0) | |
46cdbd49 | 132 | return log_link_error_errno(link, r, "Could not set neighbor state: %m"); |
e735f4d4 | 133 | |
f2dec872 | 134 | r = sd_netlink_message_append_data(req, NDA_LLADDR, &fdb_entry->mac_addr, sizeof(fdb_entry->mac_addr)); |
e735f4d4 | 135 | if (r < 0) |
46cdbd49 | 136 | return log_link_error_errno(link, r, "Could not append NDA_LLADDR attribute: %m"); |
e735f4d4 MP |
137 | |
138 | /* VLAN Id is optional. We'll add VLAN Id only if it's specified. */ | |
f2dec872 | 139 | if (fdb_entry->vlan_id > 0) { |
86f210e9 | 140 | r = sd_netlink_message_append_u16(req, NDA_VLAN, fdb_entry->vlan_id); |
e735f4d4 | 141 | if (r < 0) |
46cdbd49 | 142 | return log_link_error_errno(link, r, "Could not append NDA_VLAN attribute: %m"); |
e735f4d4 MP |
143 | } |
144 | ||
3a6ce677 | 145 | if (in_addr_is_set(fdb_entry->family, &fdb_entry->destination_addr)) { |
f2dec872 BR |
146 | r = netlink_message_append_in_addr_union(req, NDA_DST, fdb_entry->family, &fdb_entry->destination_addr); |
147 | if (r < 0) | |
148 | return log_link_error_errno(link, r, "Could not append NDA_DST attribute: %m"); | |
149 | } | |
150 | ||
151 | if (fdb_entry->vni <= VXLAN_VID_MAX) { | |
152 | r = sd_netlink_message_append_u32(req, NDA_VNI, fdb_entry->vni); | |
153 | if (r < 0) | |
154 | return log_link_error_errno(link, r, "Could not append NDA_VNI attribute: %m"); | |
155 | } | |
156 | ||
e735f4d4 | 157 | /* send message to the kernel to update its internal static MAC table. */ |
f2dec872 | 158 | r = netlink_call_async(link->manager->rtnl, NULL, req, set_fdb_handler, |
6e866b33 | 159 | link_netlink_destroy_callback, link); |
e3bff60a MP |
160 | if (r < 0) |
161 | return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); | |
e735f4d4 | 162 | |
6e866b33 MB |
163 | link_ref(link); |
164 | ||
f2dec872 | 165 | return 1; |
e735f4d4 MP |
166 | } |
167 | ||
a032b68d MB |
168 | int link_set_bridge_fdb(Link *link) { |
169 | FdbEntry *fdb_entry; | |
170 | int r; | |
e735f4d4 | 171 | |
a032b68d MB |
172 | assert(link); |
173 | assert(link->network); | |
e735f4d4 | 174 | |
a032b68d MB |
175 | HASHMAP_FOREACH(fdb_entry, link->network->fdb_entries_by_section) { |
176 | r = fdb_entry_configure(link, fdb_entry); | |
177 | if (r < 0) | |
178 | return log_link_error_errno(link, r, "Failed to add MAC entry to static MAC table: %m"); | |
e735f4d4 MP |
179 | } |
180 | ||
a032b68d MB |
181 | return 0; |
182 | } | |
183 | ||
184 | void network_drop_invalid_fdb_entries(Network *network) { | |
185 | FdbEntry *fdb_entry; | |
186 | ||
187 | assert(network); | |
188 | ||
189 | HASHMAP_FOREACH(fdb_entry, network->fdb_entries_by_section) | |
190 | if (section_is_invalid(fdb_entry->section)) | |
191 | fdb_entry_free(fdb_entry); | |
e735f4d4 MP |
192 | } |
193 | ||
194 | /* parse the HW address from config files. */ | |
e3bff60a MP |
195 | int config_parse_fdb_hwaddr( |
196 | const char *unit, | |
197 | const char *filename, | |
198 | unsigned line, | |
199 | const char *section, | |
200 | unsigned section_line, | |
201 | const char *lvalue, | |
202 | int ltype, | |
203 | const char *rvalue, | |
204 | void *data, | |
205 | void *userdata) { | |
206 | ||
e735f4d4 | 207 | Network *network = userdata; |
bb4f798a | 208 | _cleanup_(fdb_entry_free_or_set_invalidp) FdbEntry *fdb_entry = NULL; |
e735f4d4 MP |
209 | int r; |
210 | ||
211 | assert(filename); | |
212 | assert(section); | |
213 | assert(lvalue); | |
214 | assert(rvalue); | |
215 | assert(data); | |
216 | ||
6e866b33 | 217 | r = fdb_entry_new_static(network, filename, section_line, &fdb_entry); |
e3bff60a MP |
218 | if (r < 0) |
219 | return log_oom(); | |
e735f4d4 | 220 | |
f2dec872 BR |
221 | r = ether_addr_from_string(rvalue, &fdb_entry->mac_addr); |
222 | if (r < 0) { | |
a10f5d05 | 223 | log_syntax(unit, LOG_WARNING, filename, line, r, "Not a valid MAC address, ignoring assignment: %s", rvalue); |
e735f4d4 MP |
224 | return 0; |
225 | } | |
226 | ||
227 | fdb_entry = NULL; | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
232 | /* parse the VLAN Id from config files. */ | |
e3bff60a MP |
233 | int config_parse_fdb_vlan_id( |
234 | const char *unit, | |
235 | const char *filename, | |
236 | unsigned line, | |
237 | const char *section, | |
238 | unsigned section_line, | |
239 | const char *lvalue, | |
240 | int ltype, | |
241 | const char *rvalue, | |
242 | void *data, | |
243 | void *userdata) { | |
244 | ||
e735f4d4 | 245 | Network *network = userdata; |
bb4f798a | 246 | _cleanup_(fdb_entry_free_or_set_invalidp) FdbEntry *fdb_entry = NULL; |
e735f4d4 MP |
247 | int r; |
248 | ||
249 | assert(filename); | |
250 | assert(section); | |
251 | assert(lvalue); | |
252 | assert(rvalue); | |
253 | assert(data); | |
254 | ||
6e866b33 | 255 | r = fdb_entry_new_static(network, filename, section_line, &fdb_entry); |
e3bff60a MP |
256 | if (r < 0) |
257 | return log_oom(); | |
e735f4d4 | 258 | |
5a920b42 MP |
259 | r = config_parse_vlanid(unit, filename, line, section, |
260 | section_line, lvalue, ltype, | |
261 | rvalue, &fdb_entry->vlan_id, userdata); | |
e3bff60a | 262 | if (r < 0) |
e735f4d4 | 263 | return r; |
e735f4d4 MP |
264 | |
265 | fdb_entry = NULL; | |
266 | ||
267 | return 0; | |
268 | } | |
f2dec872 BR |
269 | |
270 | int config_parse_fdb_destination( | |
271 | const char *unit, | |
272 | const char *filename, | |
273 | unsigned line, | |
274 | const char *section, | |
275 | unsigned section_line, | |
276 | const char *lvalue, | |
277 | int ltype, | |
278 | const char *rvalue, | |
279 | void *data, | |
280 | void *userdata) { | |
281 | ||
282 | _cleanup_(fdb_entry_free_or_set_invalidp) FdbEntry *fdb_entry = NULL; | |
283 | Network *network = userdata; | |
284 | int r; | |
285 | ||
286 | assert(filename); | |
287 | assert(section); | |
288 | assert(lvalue); | |
289 | assert(rvalue); | |
290 | assert(data); | |
291 | ||
292 | r = fdb_entry_new_static(network, filename, section_line, &fdb_entry); | |
293 | if (r < 0) | |
294 | return log_oom(); | |
295 | ||
296 | r = in_addr_from_string_auto(rvalue, &fdb_entry->family, &fdb_entry->destination_addr); | |
7de6915e MB |
297 | if (r < 0) { |
298 | log_syntax(unit, LOG_WARNING, filename, line, r, | |
299 | "FDB destination IP address is invalid, ignoring assignment: %s", | |
300 | rvalue); | |
301 | return 0; | |
302 | } | |
f2dec872 BR |
303 | |
304 | fdb_entry = NULL; | |
305 | ||
306 | return 0; | |
307 | } | |
308 | ||
309 | int config_parse_fdb_vxlan_vni( | |
310 | const char *unit, | |
311 | const char *filename, | |
312 | unsigned line, | |
313 | const char *section, | |
314 | unsigned section_line, | |
315 | const char *lvalue, | |
316 | int ltype, | |
317 | const char *rvalue, | |
318 | void *data, | |
319 | void *userdata) { | |
320 | ||
321 | _cleanup_(fdb_entry_free_or_set_invalidp) FdbEntry *fdb_entry = NULL; | |
322 | Network *network = userdata; | |
323 | uint32_t vni; | |
324 | int r; | |
325 | ||
326 | assert(filename); | |
327 | assert(section); | |
328 | assert(lvalue); | |
329 | assert(rvalue); | |
330 | assert(data); | |
331 | ||
332 | r = fdb_entry_new_static(network, filename, section_line, &fdb_entry); | |
333 | if (r < 0) | |
334 | return log_oom(); | |
335 | ||
336 | r = safe_atou32(rvalue, &vni); | |
337 | if (r < 0) { | |
a10f5d05 | 338 | log_syntax(unit, LOG_WARNING, filename, line, r, |
f2dec872 BR |
339 | "Failed to parse VXLAN Network Identifier (VNI), ignoring assignment: %s", |
340 | rvalue); | |
341 | return 0; | |
342 | } | |
343 | ||
344 | if (vni > VXLAN_VID_MAX) { | |
a10f5d05 | 345 | log_syntax(unit, LOG_WARNING, filename, line, 0, |
f2dec872 BR |
346 | "FDB invalid VXLAN Network Identifier (VNI), ignoring assignment: %s", |
347 | rvalue); | |
348 | return 0; | |
349 | } | |
350 | ||
351 | fdb_entry->vni = vni; | |
352 | fdb_entry = NULL; | |
353 | ||
354 | return 0; | |
355 | } | |
356 | ||
a032b68d MB |
357 | static const char* const fdb_ntf_flags_table[_NEIGHBOR_CACHE_ENTRY_FLAGS_MAX] = { |
358 | [NEIGHBOR_CACHE_ENTRY_FLAGS_USE] = "use", | |
359 | [NEIGHBOR_CACHE_ENTRY_FLAGS_SELF] = "self", | |
360 | [NEIGHBOR_CACHE_ENTRY_FLAGS_MASTER] = "master", | |
361 | [NEIGHBOR_CACHE_ENTRY_FLAGS_ROUTER] = "router", | |
362 | }; | |
363 | ||
364 | DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(fdb_ntf_flags, NeighborCacheEntryFlags); | |
365 | ||
f2dec872 BR |
366 | int config_parse_fdb_ntf_flags( |
367 | const char *unit, | |
368 | const char *filename, | |
369 | unsigned line, | |
370 | const char *section, | |
371 | unsigned section_line, | |
372 | const char *lvalue, | |
373 | int ltype, | |
374 | const char *rvalue, | |
375 | void *data, | |
376 | void *userdata) { | |
377 | ||
378 | _cleanup_(fdb_entry_free_or_set_invalidp) FdbEntry *fdb_entry = NULL; | |
379 | Network *network = userdata; | |
f2dec872 BR |
380 | int r; |
381 | ||
382 | assert(filename); | |
383 | assert(section); | |
384 | assert(lvalue); | |
385 | assert(rvalue); | |
386 | assert(data); | |
387 | ||
388 | r = fdb_entry_new_static(network, filename, section_line, &fdb_entry); | |
389 | if (r < 0) | |
390 | return log_oom(); | |
391 | ||
3a6ce677 | 392 | NeighborCacheEntryFlags f = fdb_ntf_flags_from_string(rvalue); |
f2dec872 | 393 | if (f < 0) { |
3a6ce677 | 394 | log_syntax(unit, LOG_WARNING, filename, line, f, |
f2dec872 BR |
395 | "FDB failed to parse AssociatedWith=, ignoring assignment: %s", |
396 | rvalue); | |
397 | return 0; | |
398 | } | |
399 | ||
400 | fdb_entry->fdb_ntf_flags = f; | |
401 | fdb_entry = NULL; | |
402 | ||
403 | return 0; | |
404 | } |