]> git.ipfire.org Git - people/ms/network.git/blob - src/networkd/port.c
ports: Refactor enumerating ports
[people/ms/network.git] / src / networkd / port.c
1 /*#############################################################################
2 # #
3 # IPFire.org - A linux based firewall #
4 # Copyright (C) 2023 IPFire Network Development Team #
5 # #
6 # This program is free software: you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation, either version 3 of the License, or #
9 # (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
18 # #
19 #############################################################################*/
20
21 #include <limits.h>
22 #include <net/if.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25
26 #include <systemd/sd-bus.h>
27
28 #include "address.h"
29 #include "config.h"
30 #include "json.h"
31 #include "link.h"
32 #include "logging.h"
33 #include "port.h"
34 #include "port-bonding.h"
35 #include "port-dummy.h"
36 #include "port-vlan.h"
37 #include "stats-collector.h"
38 #include "string.h"
39
40 static const nw_string_table_t nw_port_type_id[] = {
41 { NW_PORT_BONDING, "bonding" },
42 { NW_PORT_DUMMY, "dummy" },
43 { NW_PORT_VLAN, "vlan" },
44 { -1, NULL },
45 };
46
47 NW_STRING_TABLE_LOOKUP(nw_port_type_id_t, nw_port_type_id)
48
49 static void nw_port_free(nw_port* port) {
50 if (port->link)
51 nw_link_unref(port->link);
52 if (port->config)
53 nw_config_unref(port->config);
54 if (port->daemon)
55 nw_daemon_unref(port->daemon);
56
57 free(port);
58 }
59
60 static int nw_port_set_link(nw_port* port, nw_link* link) {
61 // Do nothing if the same link is being re-assigned
62 if (port->link == link)
63 return 0;
64
65 // Dereference the former link if set
66 if (port->link)
67 nw_link_unref(port->link);
68
69 // Store the new link
70 if (link) {
71 port->link = nw_link_ref(link);
72
73 DEBUG("Port %s: Assigned link %d\n", port->name, nw_link_ifindex(port->link));
74
75 // Or clear the pointer if no link has been provided
76 } else {
77 port->link = NULL;
78
79 DEBUG("Port %s: Removed link\n", port->name);
80 }
81
82 return 0;
83 }
84
85 static int nw_port_setup(nw_port* port) {
86 nw_link* link = NULL;
87 int r;
88
89 // Find the link
90 link = nw_daemon_get_link_by_name(port->daemon, port->name);
91 if (link) {
92 r = nw_port_set_link(port, link);
93 if (r)
94 goto ERROR;
95 }
96
97 // Generate a random Ethernet address
98 r = nw_address_generate(&port->address);
99 if (r < 0) {
100 ERROR("Could not generate an Ethernet address: %s\n", strerror(-r));
101 goto ERROR;
102 }
103
104 // Setup options
105 r = NW_CONFIG_OPTION_ADDRESS(port->config, "ADDRESS", &port->address);
106 if (r < 0)
107 goto ERROR;
108
109 // Call any custom initialization
110 if (NW_PORT_TYPE(port)->setup) {
111 r = NW_PORT_TYPE(port)->setup(port);
112 if (r < 0)
113 goto ERROR;
114 }
115
116 // Parse the configuration
117 r = nw_config_options_read(port->config);
118 if (r < 0) {
119 ERROR("Could not read configuration for port %s: %s\n", port->name, strerror(-r));
120 goto ERROR;
121 }
122
123 ERROR:
124 if (link)
125 nw_link_unref(link);
126
127 return r;
128 }
129
130 static int nw_port_validate(nw_port* port) {
131 int r = 0;
132
133 // Validate the port configuration
134 if (NW_PORT_TYPE(port)->validate) {
135 r = NW_PORT_TYPE(port)->validate(port);
136 if (r < 0)
137 ERROR("Could not check configuration for %s: %s\n", port->name, strerror(-r));
138 }
139
140 return r;
141 }
142
143 int nw_port_create(nw_port** port, nw_daemon* daemon,
144 const nw_port_type_id_t type, const char* name, nw_config* config) {
145 int r;
146
147 // Allocate a new object
148 nw_port* p = calloc(1, sizeof(*p));
149 if (!p)
150 return -errno;
151
152 // Store a reference to the daemon
153 p->daemon = nw_daemon_ref(daemon);
154
155 // Initialize reference counter
156 p->nrefs = 1;
157
158 // Set operations
159 switch (type) {
160 case NW_PORT_BONDING:
161 p->type = &nw_port_type_bonding;
162 break;
163
164 case NW_PORT_DUMMY:
165 p->type = &nw_port_type_dummy;
166 break;
167
168 case NW_PORT_VLAN:
169 p->type = &nw_port_type_vlan;
170 break;
171 }
172
173 // Store the name
174 r = nw_string_set(p->name, name);
175 if (r < 0)
176 goto ERROR;
177
178 // Copy the configuration
179 r = nw_config_copy(config, &p->config);
180 if (r < 0)
181 goto ERROR;
182
183 // Setup the port
184 r = nw_port_setup(p);
185 if (r < 0)
186 goto ERROR;
187
188 // Validate the configuration
189 r = nw_port_validate(p);
190 switch (r) {
191 // Configuration is valid
192 case 0:
193 break;
194
195 // Configuration is invalid
196 case 1:
197 ERROR("%s: Invalid configuration\n", p->name);
198 goto ERROR;
199
200 // Error
201 default:
202 goto ERROR;
203 }
204
205 *port = p;
206 return 0;
207
208 ERROR:
209 nw_port_free(p);
210 return r;
211 }
212
213 int nw_port_open(nw_port** port, nw_daemon* daemon, const char* name) {
214 nw_config* config = NULL;
215 FILE* f = NULL;
216 char path[PATH_MAX];
217 int r;
218
219 // Make path
220 r = nw_string_format(path, "ports/%s", name);
221 if (r < 0)
222 goto ERROR;
223
224 // Open the configuration file
225 f = nw_daemon_config_fopen(daemon, path, "r");
226 if (!f) {
227 r = -errno;
228 goto ERROR;
229 }
230
231 // Initialize the configuration
232 r = nw_config_create(&config, f);
233 if (r < 0)
234 goto ERROR;
235
236 // Fetch the type
237 const char* type = nw_config_get(config, "TYPE");
238 if (!type) {
239 ERROR("Port configuration %s has no TYPE\n", path);
240 r = -ENOTSUP;
241 goto ERROR;
242 }
243
244 // Create a new port
245 r = nw_port_create(port, daemon, nw_port_type_id_from_string(type), name, config);
246 if (r < 0)
247 goto ERROR;
248
249 ERROR:
250 if (config)
251 nw_config_unref(config);
252 if (f)
253 fclose(f);
254
255 return r;
256 }
257
258 nw_port* nw_port_ref(nw_port* port) {
259 port->nrefs++;
260
261 return port;
262 }
263
264 nw_port* nw_port_unref(nw_port* port) {
265 if (--port->nrefs > 0)
266 return port;
267
268 nw_port_free(port);
269 return NULL;
270 }
271
272 /*
273 This is a helper function for when we pass a reference to the event loop
274 it will have to dereference the port instance later.
275 */
276 static void __nw_port_unref(void* data) {
277 nw_port* port = (nw_port*)data;
278
279 nw_port_unref(port);
280 }
281
282 int nw_port_destroy(nw_port* port) {
283 int r;
284
285 DEBUG("Destroying port %s\n", port->name);
286
287 // Destroy the physical link (if exists)
288 if (port->link) {
289 r = nw_link_destroy(port->link);
290 if (r)
291 return r;
292 }
293
294 // Dereference the port from other ports
295 r = nw_daemon_ports_walk(port->daemon, __nw_port_drop_port, port);
296 if (r)
297 return r;
298
299 // Dereference the port from other zones
300 r = nw_daemon_zones_walk(port->daemon, __nw_zone_drop_port, port);
301 if (r)
302 return r;
303
304 // Destroy the configuration
305 r = nw_config_destroy(port->config);
306 if (r)
307 return r;
308
309 return 0;
310 }
311
312 int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
313 nw_port* dropped_port = (nw_port*)data;
314 int r;
315
316 switch (port->type->id) {
317 case NW_PORT_VLAN:
318 if (port->vlan.parent == dropped_port) {
319 r = nw_port_set_vlan_parent(port, NULL);
320 if (r)
321 return r;
322 }
323 break;
324
325 case NW_PORT_BONDING:
326 case NW_PORT_DUMMY:
327 break;
328 }
329
330 return 0;
331 }
332
333 int nw_port_save(nw_port* port) {
334 char path[PATH_MAX];
335 FILE* f = NULL;
336 int r;
337
338 // Compose path
339 r = nw_string_format(path, "ports/%s", port->name);
340 if (r < 0)
341 return r;
342
343 // Open file
344 f = nw_daemon_config_fopen(port->daemon, path, "w");
345 if (!f) {
346 r = -errno;
347 goto ERROR;
348 }
349
350 // Write out the configuration
351 r = nw_config_options_write(port->config);
352 if (r < 0)
353 goto ERROR;
354
355 // Write the configuration
356 r = nw_config_write(port->config, f);
357 if (r < 0)
358 goto ERROR;
359
360 ERROR:
361 if (f)
362 fclose(f);
363 if (r)
364 ERROR("Could not save configuration for port %s: %s\n", port->name, strerror(-r));
365
366 return r;
367 }
368
369 const char* nw_port_name(nw_port* port) {
370 return port->name;
371 }
372
373 char* nw_port_bus_path(nw_port* port) {
374 char* p = NULL;
375 int r;
376
377 // Encode the bus path
378 r = sd_bus_path_encode("/org/ipfire/network1/port", port->name, &p);
379 if (r < 0)
380 return NULL;
381
382 return p;
383 }
384
385 int __nw_port_set_link(nw_daemon* daemon, nw_port* port, void* data) {
386 nw_link* link = (nw_link*)data;
387
388 // Fetch the link name
389 const char* ifname = nw_link_ifname(link);
390 if (!ifname) {
391 ERROR("Link does not have a name set\n");
392 return 1;
393 }
394
395 // Set link if the name matches
396 if (strcmp(port->name, ifname) == 0)
397 return nw_port_set_link(port, link);
398
399 // If we have the link set but the name did not match, we will remove it
400 else if (port->link == link)
401 return nw_port_set_link(port, NULL);
402
403 return 0;
404 }
405
406 int __nw_port_drop_link(nw_daemon* daemon, nw_port* port, void* data) {
407 nw_link* link = (nw_link*)data;
408
409 // Drop the link if it matches
410 if (port->link == link)
411 return nw_port_set_link(port, NULL);
412
413 return 0;
414 }
415
416 static nw_link* nw_port_get_link(nw_port* port) {
417 // Fetch the link if not set
418 if (!port->link)
419 port->link = nw_daemon_get_link_by_name(port->daemon, port->name);
420
421 if (!port->link)
422 return NULL;
423
424 return nw_link_ref(port->link);
425 }
426
427 const nw_address_t* nw_port_get_address(nw_port* port) {
428 return &port->address;
429 }
430
431 static int nw_port_is_disabled(nw_port* port) {
432 return nw_config_get_bool(port->config, "DISABLED");
433 }
434
435 nw_port* nw_port_get_parent_port(nw_port* port) {
436 if (!NW_PORT_TYPE(port)->get_parent_port)
437 return NULL;
438
439 return NW_PORT_TYPE(port)->get_parent_port(port);
440 }
441
442 static nw_link* nw_port_get_parent_link(nw_port* port) {
443 nw_port* parent = NULL;
444 nw_link* link = NULL;
445
446 // Fetch the parent
447 parent = nw_port_get_parent_port(port);
448 if (!parent)
449 return NULL;
450
451 // Fetch the link
452 link = nw_port_get_link(parent);
453
454 // Cleanup
455 if (parent)
456 nw_port_unref(parent);
457
458 return link;
459 }
460
461 static int __nw_port_create_link(sd_netlink* rtnl, sd_netlink_message* m, void* data) {
462 nw_port* port = (nw_port*)data;
463 int r;
464
465 // Check if the operation was successful
466 r = sd_netlink_message_get_errno(m);
467 if (r < 0) {
468 ERROR("Could not create port %s: %s\n", port->name, strerror(-r));
469 // XXX We should extract the error message
470
471 return 0;
472 }
473
474 DEBUG("Successfully created %s\n", port->name);
475
476 return 0;
477 }
478
479 static int nw_port_create_link(nw_port* port) {
480 sd_netlink_message* m = NULL;
481 nw_link* link = NULL;
482 int r;
483
484 sd_netlink* rtnl = nw_daemon_get_rtnl(port->daemon);
485
486 DEBUG("Creating port %s...\n", port->name);
487
488 // Check the kind
489 if (!NW_PORT_TYPE(port)->kind) {
490 ERROR("Port type has no kind\n");
491 r = -ENOTSUP;
492 goto ERROR;
493 }
494
495 // Create a new link
496 r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0);
497 if (r < 0) {
498 ERROR("Could not create netlink message: %m\n");
499 goto ERROR;
500 }
501
502 // Set the name
503 r = sd_netlink_message_append_string(m, IFLA_IFNAME, port->name);
504 if (r < 0) {
505 ERROR("Could not set port name: %s\n", strerror(-r));
506 goto ERROR;
507 }
508
509 // XXX Set common things like MTU, etc.
510
511 // Set Ethernet address
512 r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &port->address);
513 if (r < 0) {
514 ERROR("Could not set MAC address: %s\n", strerror(-r));
515 goto ERROR;
516 }
517
518 // Fetch the parent link
519 link = nw_port_get_parent_link(port);
520 if (link) {
521 r = sd_netlink_message_append_u32(m, IFLA_LINK, nw_link_ifindex(link));
522 if (r < 0)
523 goto ERROR;
524 }
525
526 // Open an IFLA_LINKINFO container
527 r = sd_netlink_message_open_container(m, IFLA_LINKINFO);
528 if (r < 0)
529 goto ERROR;
530
531 // Run the custom setup
532 if (NW_PORT_TYPE(port)->create_link) {
533 r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, NW_PORT_TYPE(port)->kind);
534 if (r < 0) {
535 ERROR("Could not open IFLA_INFO_DATA container: %s\n", strerror(-r));
536 goto ERROR;
537 }
538
539 r = NW_PORT_TYPE(port)->create_link(port, m);
540 if (r) {
541 ERROR("Could not create port %s: %m\n", port->name);
542 goto ERROR;
543 }
544
545 // Close the container
546 r = sd_netlink_message_close_container(m);
547 if (r < 0)
548 goto ERROR;
549
550 // Just set IFLA_INFO_KIND if there is no custom function
551 } else {
552 r = sd_netlink_message_append_string(m, IFLA_INFO_KIND, NW_PORT_TYPE(port)->kind);
553 if (r < 0)
554 goto ERROR;
555 }
556
557 // Close the container
558 r = sd_netlink_message_close_container(m);
559 if (r < 0)
560 goto ERROR;
561
562 // Send the message
563 r = sd_netlink_call_async(rtnl, NULL, m, __nw_port_create_link,
564 __nw_port_unref, nw_port_ref(port), -1, NULL);
565 if (r < 0) {
566 ERROR("Could not send netlink message: %s\n", strerror(-r));
567 goto ERROR;
568 }
569
570 r = 0;
571
572 ERROR:
573 if (m)
574 sd_netlink_message_unref(m);
575 if (link)
576 nw_link_unref(link);
577
578 return r;
579 }
580
581 int nw_port_reconfigure(nw_port* port) {
582 int r;
583
584 // If the port is disabled, we will try to destroy it
585 if (nw_port_is_disabled(port)) {
586 if (port->link) {
587 r = nw_link_destroy(port->link);
588 if (r)
589 return r;
590 }
591
592 return 0;
593 }
594
595 // If there is no link, we will try to create it
596 if (!port->link)
597 return nw_port_create_link(port);
598
599 // XXX TODO
600
601 return 0;
602 }
603
604 int nw_port_has_carrier(nw_port* port) {
605 if (!port->link)
606 return 0;
607
608 return nw_link_has_carrier(port->link);
609 }
610
611 /*
612 Stats
613 */
614
615 const struct rtnl_link_stats64* nw_port_get_stats64(nw_port* port) {
616 if (!port->link)
617 return NULL;
618
619 return nw_link_get_stats64(port->link);
620 }
621
622 int __nw_port_update_stats(nw_daemon* daemon, nw_port* port, void* data) {
623 nw_link* link = (nw_link*)data;
624
625 // Emit stats if link matches
626 if (port->link == link)
627 return nw_stats_collector_emit_port_stats(daemon, port);
628
629 return 0;
630 }
631
632 int nw_port_update_stats(nw_port* port) {
633 if (port->link)
634 return nw_link_update_stats(port->link);
635
636 return 0;
637 }
638
639 int nw_port_check_type(nw_port* port, const nw_port_type_id_t type) {
640 if (port->type->id == type)
641 return 0;
642
643 errno = ENOTSUP;
644 return -errno;
645 }
646
647 /*
648 JSON
649 */
650 int nw_port_to_json(nw_port* port, struct json_object** object) {
651 char* address = NULL;
652 int r;
653
654 // Create a new JSON object
655 struct json_object* o = json_object_new_object();
656 if (!o) {
657 r = -errno;
658 goto ERROR;
659 }
660
661 // Add name
662 r = json_object_add_string(o, "Name", port->name);
663 if (r < 0)
664 goto ERROR;
665
666 // Add Type
667 r = json_object_add_string(o, "Type", nw_port_type_id_to_string(port->type->id));
668 if (r < 0)
669 goto ERROR;
670
671 // Add address
672 address = nw_address_to_string(&port->address);
673 if (address) {
674 r = json_object_add_string(o, "Address", address);
675 if (r < 0)
676 goto ERROR;
677 }
678
679 // Call custom stuff
680 if (NW_PORT_TYPE(port)->to_json) {
681 r = NW_PORT_TYPE(port)->to_json(port, o);
682 if (r < 0)
683 goto ERROR;
684 }
685
686 // Success
687 r = 0;
688
689 // Return a reference to the created object
690 *object = json_object_ref(o);
691
692 ERROR:
693 if (address)
694 free(address);
695 if (o)
696 json_object_unref(o);
697
698 return r;
699 }