1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /***
3   Copyright © 2014 Intel Corporation. All rights reserved.
4 ***/
5 
6 #include <net/ethernet.h>
7 #include <net/if.h>
8 
9 #include "alloc-util.h"
10 #include "bridge.h"
11 #include "netlink-util.h"
12 #include "networkd-bridge-fdb.h"
13 #include "networkd-link.h"
14 #include "networkd-manager.h"
15 #include "networkd-network.h"
16 #include "networkd-queue.h"
17 #include "networkd-util.h"
18 #include "parse-util.h"
19 #include "string-table.h"
20 #include "vlan-util.h"
21 #include "vxlan.h"
22 
23 #define STATIC_BRIDGE_FDB_ENTRIES_PER_NETWORK_MAX 1024U
24 
25 /* remove and FDB entry. */
bridge_fdb_free(BridgeFDB * fdb)26 BridgeFDB *bridge_fdb_free(BridgeFDB *fdb) {
27         if (!fdb)
28                 return NULL;
29 
30         if (fdb->network) {
31                 assert(fdb->section);
32                 hashmap_remove(fdb->network->bridge_fdb_entries_by_section, fdb->section);
33         }
34 
35         config_section_free(fdb->section);
36 
37         free(fdb->outgoing_ifname);
38         return mfree(fdb);
39 }
40 
41 DEFINE_SECTION_CLEANUP_FUNCTIONS(BridgeFDB, bridge_fdb_free);
42 
43 /* create a new FDB entry or get an existing one. */
bridge_fdb_new_static(Network * network,const char * filename,unsigned section_line,BridgeFDB ** ret)44 static int bridge_fdb_new_static(
45                 Network *network,
46                 const char *filename,
47                 unsigned section_line,
48                 BridgeFDB **ret) {
49 
50         _cleanup_(config_section_freep) ConfigSection *n = NULL;
51         _cleanup_(bridge_fdb_freep) BridgeFDB *fdb = NULL;
52         int r;
53 
54         assert(network);
55         assert(ret);
56         assert(filename);
57         assert(section_line > 0);
58 
59         r = config_section_new(filename, section_line, &n);
60         if (r < 0)
61                 return r;
62 
63         /* search entry in hashmap first. */
64         fdb = hashmap_get(network->bridge_fdb_entries_by_section, n);
65         if (fdb) {
66                 *ret = TAKE_PTR(fdb);
67                 return 0;
68         }
69 
70         if (hashmap_size(network->bridge_fdb_entries_by_section) >= STATIC_BRIDGE_FDB_ENTRIES_PER_NETWORK_MAX)
71                 return -E2BIG;
72 
73         /* allocate space for and FDB entry. */
74         fdb = new(BridgeFDB, 1);
75         if (!fdb)
76                 return -ENOMEM;
77 
78         /* init FDB structure. */
79         *fdb = (BridgeFDB) {
80                 .network = network,
81                 .section = TAKE_PTR(n),
82                 .vni = VXLAN_VID_MAX + 1,
83                 .ntf_flags = NEIGHBOR_CACHE_ENTRY_FLAGS_SELF,
84         };
85 
86         r = hashmap_ensure_put(&network->bridge_fdb_entries_by_section, &config_section_hash_ops, fdb->section, fdb);
87         if (r < 0)
88                 return r;
89 
90         /* return allocated FDB structure. */
91         *ret = TAKE_PTR(fdb);
92 
93         return 0;
94 }
95 
bridge_fdb_configure_handler(sd_netlink * rtnl,sd_netlink_message * m,Request * req,Link * link,void * userdata)96 static int bridge_fdb_configure_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) {
97         int r;
98 
99         assert(m);
100         assert(link);
101 
102         r = sd_netlink_message_get_errno(m);
103         if (r < 0 && r != -EEXIST) {
104                 log_link_message_warning_errno(link, m, r, "Could not add bridge FDB entry");
105                 link_enter_failed(link);
106                 return 0;
107         }
108 
109         if (link->static_bridge_fdb_messages == 0) {
110                 log_link_debug(link, "Bridge FDB entries set");
111                 link->static_bridge_fdb_configured = true;
112                 link_check_ready(link);
113         }
114 
115         return 0;
116 }
117 
118 /* send a request to the kernel to add a FDB entry in its static MAC table. */
bridge_fdb_configure_message(const BridgeFDB * fdb,Link * link,sd_netlink_message * req)119 static int bridge_fdb_configure_message(const BridgeFDB *fdb, Link *link, sd_netlink_message *req) {
120         int r;
121 
122         assert(fdb);
123         assert(link);
124 
125         r = sd_rtnl_message_neigh_set_flags(req, fdb->ntf_flags);
126         if (r < 0)
127                 return r;
128 
129         /* only NUD_PERMANENT state supported. */
130         r = sd_rtnl_message_neigh_set_state(req, NUD_NOARP | NUD_PERMANENT);
131         if (r < 0)
132                 return r;
133 
134         r = sd_netlink_message_append_data(req, NDA_LLADDR, &fdb->mac_addr, sizeof(fdb->mac_addr));
135         if (r < 0)
136                 return r;
137 
138         /* VLAN Id is optional. We'll add VLAN Id only if it's specified. */
139         if (fdb->vlan_id > 0) {
140                 r = sd_netlink_message_append_u16(req, NDA_VLAN, fdb->vlan_id);
141                 if (r < 0)
142                         return r;
143         }
144 
145         if (fdb->outgoing_ifindex > 0) {
146                 r = sd_netlink_message_append_u32(req, NDA_IFINDEX, fdb->outgoing_ifindex);
147                 if (r < 0)
148                         return r;
149         }
150 
151         if (in_addr_is_set(fdb->family, &fdb->destination_addr)) {
152                 r = netlink_message_append_in_addr_union(req, NDA_DST, fdb->family, &fdb->destination_addr);
153                 if (r < 0)
154                         return r;
155         }
156 
157         if (fdb->vni <= VXLAN_VID_MAX) {
158                 r = sd_netlink_message_append_u32(req, NDA_VNI, fdb->vni);
159                 if (r < 0)
160                         return r;
161         }
162 
163         return 0;
164 }
165 
bridge_fdb_configure(BridgeFDB * fdb,Link * link,Request * req)166 static int bridge_fdb_configure(BridgeFDB *fdb, Link *link, Request *req) {
167         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
168         int r;
169 
170         assert(fdb);
171         assert(link);
172         assert(link->manager);
173         assert(req);
174 
175         r = sd_rtnl_message_new_neigh(link->manager->rtnl, &m, RTM_NEWNEIGH, link->ifindex, AF_BRIDGE);
176         if (r < 0)
177                 return r;
178 
179         r = bridge_fdb_configure_message(fdb, link, m);
180         if (r < 0)
181                 return r;
182 
183         return request_call_netlink_async(link->manager->rtnl, m, req);
184 }
185 
bridge_fdb_is_ready_to_configure(BridgeFDB * fdb,Link * link)186 static bool bridge_fdb_is_ready_to_configure(BridgeFDB *fdb, Link *link) {
187         Link *out = NULL;
188 
189         assert(fdb);
190         assert(link);
191         assert(link->manager);
192 
193         if (!link_is_ready_to_configure(link, false))
194                 return false;
195 
196         if (fdb->outgoing_ifname) {
197                 if (link_get_by_name(link->manager, fdb->outgoing_ifname, &out) < 0)
198                         return false;
199 
200                 fdb->outgoing_ifindex = out->ifindex;
201         } else if (fdb->outgoing_ifindex > 0) {
202                 if (link_get_by_index(link->manager, fdb->outgoing_ifindex, &out) < 0)
203                         return false;
204         }
205         if (out && !link_is_ready_to_configure(out, false))
206                 return false;
207 
208         return true;
209 }
210 
bridge_fdb_process_request(Request * req,Link * link,void * userdata)211 static int bridge_fdb_process_request(Request *req, Link *link, void *userdata) {
212         BridgeFDB *fdb = ASSERT_PTR(userdata);
213         int r;
214 
215         assert(req);
216         assert(link);
217 
218         if (!bridge_fdb_is_ready_to_configure(fdb, link))
219                 return 0;
220 
221         r = bridge_fdb_configure(fdb, link, req);
222         if (r < 0)
223                 return log_link_warning_errno(link, r, "Failed to configure bridge FDB: %m");
224 
225         return 1;
226 }
227 
link_request_static_bridge_fdb(Link * link)228 int link_request_static_bridge_fdb(Link *link) {
229         BridgeFDB *fdb;
230         int r;
231 
232         assert(link);
233         assert(link->network);
234 
235         link->static_bridge_fdb_configured = false;
236 
237         HASHMAP_FOREACH(fdb, link->network->bridge_fdb_entries_by_section) {
238                 r = link_queue_request_full(link, REQUEST_TYPE_BRIDGE_FDB,
239                                             fdb, NULL,
240                                             trivial_hash_func,
241                                             trivial_compare_func,
242                                             bridge_fdb_process_request,
243                                             &link->static_bridge_fdb_messages,
244                                             bridge_fdb_configure_handler,
245                                             NULL);
246                 if (r < 0)
247                         return log_link_error_errno(link, r, "Failed to request static bridge FDB entry: %m");
248         }
249 
250         if (link->static_bridge_fdb_messages == 0) {
251                 link->static_bridge_fdb_configured = true;
252                 link_check_ready(link);
253         } else {
254                 log_link_debug(link, "Setting bridge FDB entries");
255                 link_set_state(link, LINK_STATE_CONFIGURING);
256         }
257 
258         return 0;
259 }
260 
network_drop_invalid_bridge_fdb_entries(Network * network)261 void network_drop_invalid_bridge_fdb_entries(Network *network) {
262         BridgeFDB *fdb;
263 
264         assert(network);
265 
266         HASHMAP_FOREACH(fdb, network->bridge_fdb_entries_by_section)
267                 if (section_is_invalid(fdb->section))
268                         bridge_fdb_free(fdb);
269 }
270 
271 /* parse the HW address from config files. */
config_parse_fdb_hwaddr(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)272 int config_parse_fdb_hwaddr(
273                 const char *unit,
274                 const char *filename,
275                 unsigned line,
276                 const char *section,
277                 unsigned section_line,
278                 const char *lvalue,
279                 int ltype,
280                 const char *rvalue,
281                 void *data,
282                 void *userdata) {
283 
284         _cleanup_(bridge_fdb_free_or_set_invalidp) BridgeFDB *fdb = NULL;
285         Network *network = userdata;
286         int r;
287 
288         assert(filename);
289         assert(section);
290         assert(lvalue);
291         assert(rvalue);
292         assert(data);
293 
294         r = bridge_fdb_new_static(network, filename, section_line, &fdb);
295         if (r < 0)
296                 return log_oom();
297 
298         r = parse_ether_addr(rvalue, &fdb->mac_addr);
299         if (r < 0) {
300                 log_syntax(unit, LOG_WARNING, filename, line, r, "Not a valid MAC address, ignoring assignment: %s", rvalue);
301                 return 0;
302         }
303 
304         TAKE_PTR(fdb);
305         return 0;
306 }
307 
308 /* parse the VLAN Id from config files. */
config_parse_fdb_vlan_id(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)309 int config_parse_fdb_vlan_id(
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_(bridge_fdb_free_or_set_invalidp) BridgeFDB *fdb = NULL;
322         Network *network = userdata;
323         int r;
324 
325         assert(filename);
326         assert(section);
327         assert(lvalue);
328         assert(rvalue);
329         assert(data);
330 
331         r = bridge_fdb_new_static(network, filename, section_line, &fdb);
332         if (r < 0)
333                 return log_oom();
334 
335         r = config_parse_vlanid(unit, filename, line, section,
336                                 section_line, lvalue, ltype,
337                                 rvalue, &fdb->vlan_id, userdata);
338         if (r < 0)
339                 return r;
340 
341         TAKE_PTR(fdb);
342         return 0;
343 }
344 
config_parse_fdb_destination(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)345 int config_parse_fdb_destination(
346                 const char *unit,
347                 const char *filename,
348                 unsigned line,
349                 const char *section,
350                 unsigned section_line,
351                 const char *lvalue,
352                 int ltype,
353                 const char *rvalue,
354                 void *data,
355                 void *userdata) {
356 
357         _cleanup_(bridge_fdb_free_or_set_invalidp) BridgeFDB *fdb = NULL;
358         Network *network = userdata;
359         int r;
360 
361         assert(filename);
362         assert(section);
363         assert(lvalue);
364         assert(rvalue);
365         assert(data);
366 
367         r = bridge_fdb_new_static(network, filename, section_line, &fdb);
368         if (r < 0)
369                 return log_oom();
370 
371         r = in_addr_from_string_auto(rvalue, &fdb->family, &fdb->destination_addr);
372         if (r < 0) {
373                 log_syntax(unit, LOG_WARNING, filename, line, r,
374                            "FDB destination IP address is invalid, ignoring assignment: %s",
375                            rvalue);
376                 return 0;
377         }
378 
379         TAKE_PTR(fdb);
380         return 0;
381 }
382 
config_parse_fdb_vxlan_vni(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)383 int config_parse_fdb_vxlan_vni(
384                 const char *unit,
385                 const char *filename,
386                 unsigned line,
387                 const char *section,
388                 unsigned section_line,
389                 const char *lvalue,
390                 int ltype,
391                 const char *rvalue,
392                 void *data,
393                 void *userdata) {
394 
395         _cleanup_(bridge_fdb_free_or_set_invalidp) BridgeFDB *fdb = NULL;
396         Network *network = userdata;
397         uint32_t vni;
398         int r;
399 
400         assert(filename);
401         assert(section);
402         assert(lvalue);
403         assert(rvalue);
404         assert(data);
405 
406         r = bridge_fdb_new_static(network, filename, section_line, &fdb);
407         if (r < 0)
408                 return log_oom();
409 
410         r = safe_atou32(rvalue, &vni);
411         if (r < 0) {
412                 log_syntax(unit, LOG_WARNING, filename, line, r,
413                            "Failed to parse VXLAN Network Identifier (VNI), ignoring assignment: %s",
414                            rvalue);
415                 return 0;
416         }
417 
418         if (vni > VXLAN_VID_MAX) {
419                 log_syntax(unit, LOG_WARNING, filename, line, 0,
420                            "FDB invalid VXLAN Network Identifier (VNI), ignoring assignment: %s",
421                            rvalue);
422                 return 0;
423         }
424 
425         fdb->vni = vni;
426 
427         TAKE_PTR(fdb);
428         return 0;
429 }
430 
431 static const char* const ntf_flags_table[_NEIGHBOR_CACHE_ENTRY_FLAGS_MAX] = {
432         [NEIGHBOR_CACHE_ENTRY_FLAGS_USE] = "use",
433         [NEIGHBOR_CACHE_ENTRY_FLAGS_SELF] = "self",
434         [NEIGHBOR_CACHE_ENTRY_FLAGS_MASTER] = "master",
435         [NEIGHBOR_CACHE_ENTRY_FLAGS_ROUTER] = "router",
436 };
437 
438 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(ntf_flags, NeighborCacheEntryFlags);
439 
config_parse_fdb_ntf_flags(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)440 int config_parse_fdb_ntf_flags(
441                 const char *unit,
442                 const char *filename,
443                 unsigned line,
444                 const char *section,
445                 unsigned section_line,
446                 const char *lvalue,
447                 int ltype,
448                 const char *rvalue,
449                 void *data,
450                 void *userdata) {
451 
452         _cleanup_(bridge_fdb_free_or_set_invalidp) BridgeFDB *fdb = NULL;
453         Network *network = userdata;
454         NeighborCacheEntryFlags f;
455         int r;
456 
457         assert(filename);
458         assert(section);
459         assert(lvalue);
460         assert(rvalue);
461         assert(data);
462 
463         r = bridge_fdb_new_static(network, filename, section_line, &fdb);
464         if (r < 0)
465                 return log_oom();
466 
467         f = ntf_flags_from_string(rvalue);
468         if (f < 0) {
469                 log_syntax(unit, LOG_WARNING, filename, line, f,
470                            "FDB failed to parse AssociatedWith=, ignoring assignment: %s",
471                            rvalue);
472                 return 0;
473         }
474 
475         fdb->ntf_flags = f;
476 
477         TAKE_PTR(fdb);
478         return 0;
479 }
480 
config_parse_fdb_interface(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)481 int config_parse_fdb_interface(
482                 const char *unit,
483                 const char *filename,
484                 unsigned line,
485                 const char *section,
486                 unsigned section_line,
487                 const char *lvalue,
488                 int ltype,
489                 const char *rvalue,
490                 void *data,
491                 void *userdata) {
492 
493         _cleanup_(bridge_fdb_free_or_set_invalidp) BridgeFDB *fdb = NULL;
494         Network *network = userdata;
495         int r;
496 
497         assert(filename);
498         assert(section);
499         assert(lvalue);
500         assert(rvalue);
501         assert(data);
502 
503         r = bridge_fdb_new_static(network, filename, section_line, &fdb);
504         if (r < 0)
505                 return log_oom();
506 
507         if (isempty(rvalue)) {
508                 fdb->outgoing_ifname = mfree(fdb->outgoing_ifname);
509                 fdb->outgoing_ifindex = 0;
510                 TAKE_PTR(fdb);
511                 return 0;
512         }
513 
514         r = parse_ifindex(rvalue);
515         if (r > 0) {
516                 fdb->outgoing_ifname = mfree(fdb->outgoing_ifname);
517                 fdb->outgoing_ifindex = r;
518                 TAKE_PTR(fdb);
519                 return 0;
520         }
521 
522         if (!ifname_valid_full(rvalue, IFNAME_VALID_ALTERNATIVE)) {
523                 log_syntax(unit, LOG_WARNING, filename, line, 0,
524                            "Invalid interface name in %s=, ignoring assignment: %s", lvalue, rvalue);
525                 return 0;
526         }
527 
528         r = free_and_strdup(&fdb->outgoing_ifname, rvalue);
529         if (r < 0)
530                 return log_oom();
531         fdb->outgoing_ifindex = 0;
532 
533         TAKE_PTR(fdb);
534         return 0;
535 }
536