]> git.ipfire.org Git - people/ms/network.git/blame - src/networkd/port.c
ports: Refactor enumerating ports
[people/ms/network.git] / src / networkd / port.c
CommitLineData
abeab069
MT
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
827435c8 21#include <limits.h>
abeab069 22#include <net/if.h>
2e7f4705 23#include <stdint.h>
abeab069
MT
24#include <stdlib.h>
25
7297ba7f
MT
26#include <systemd/sd-bus.h>
27
2e7f4705 28#include "address.h"
abeab069 29#include "config.h"
e9b0614e 30#include "json.h"
30d4ab67 31#include "link.h"
827435c8 32#include "logging.h"
abeab069 33#include "port.h"
95c5dca2 34#include "port-bonding.h"
9e8af30e 35#include "port-dummy.h"
ff886975 36#include "port-vlan.h"
15240e08
MT
37#include "stats-collector.h"
38#include "string.h"
abeab069 39
c464b5d1 40static const nw_string_table_t nw_port_type_id[] = {
95c5dca2 41 { NW_PORT_BONDING, "bonding" },
c7761af8
MT
42 { NW_PORT_DUMMY, "dummy" },
43 { NW_PORT_VLAN, "vlan" },
c464b5d1 44 { -1, NULL },
827435c8
MT
45};
46
c464b5d1 47NW_STRING_TABLE_LOOKUP(nw_port_type_id_t, nw_port_type_id)
827435c8 48
2361667e 49static void nw_port_free(nw_port* port) {
02801f0d
MT
50 if (port->link)
51 nw_link_unref(port->link);
abeab069
MT
52 if (port->config)
53 nw_config_unref(port->config);
30d4ab67
MT
54 if (port->daemon)
55 nw_daemon_unref(port->daemon);
abeab069
MT
56
57 free(port);
58}
59
611d4aca
MT
60static 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
2361667e 85static int nw_port_setup(nw_port* port) {
611d4aca 86 nw_link* link = NULL;
827435c8
MT
87 int r;
88
02801f0d 89 // Find the link
611d4aca
MT
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;
02801f0d
MT
95 }
96
082d81a3
MT
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));
611d4aca 101 goto ERROR;
082d81a3 102 }
827435c8 103
082d81a3
MT
104 // Setup options
105 r = NW_CONFIG_OPTION_ADDRESS(port->config, "ADDRESS", &port->address);
106 if (r < 0)
611d4aca 107 goto ERROR;
827435c8
MT
108
109 // Call any custom initialization
c464b5d1
MT
110 if (NW_PORT_TYPE(port)->setup) {
111 r = NW_PORT_TYPE(port)->setup(port);
082d81a3 112 if (r < 0)
ff886975 113 goto ERROR;
827435c8
MT
114 }
115
082d81a3
MT
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
611d4aca
MT
123ERROR:
124 if (link)
125 nw_link_unref(link);
126
127 return r;
827435c8
MT
128}
129
ea7dc1bb
MT
130static 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
082d81a3 143int nw_port_create(nw_port** port, nw_daemon* daemon,
c464b5d1 144 const nw_port_type_id_t type, const char* name, nw_config* config) {
abeab069
MT
145 int r;
146
147 // Allocate a new object
2361667e 148 nw_port* p = calloc(1, sizeof(*p));
abeab069 149 if (!p)
8edf3da1 150 return -errno;
abeab069 151
30d4ab67
MT
152 // Store a reference to the daemon
153 p->daemon = nw_daemon_ref(daemon);
154
abeab069
MT
155 // Initialize reference counter
156 p->nrefs = 1;
157
9e8af30e 158 // Set operations
c464b5d1 159 switch (type) {
95c5dca2 160 case NW_PORT_BONDING:
c464b5d1 161 p->type = &nw_port_type_bonding;
95c5dca2
MT
162 break;
163
9e8af30e 164 case NW_PORT_DUMMY:
c464b5d1 165 p->type = &nw_port_type_dummy;
9e8af30e 166 break;
ff886975
MT
167
168 case NW_PORT_VLAN:
c464b5d1 169 p->type = &nw_port_type_vlan;
ff886975 170 break;
9e8af30e
MT
171 }
172
abeab069
MT
173 // Store the name
174 r = nw_string_set(p->name, name);
8edf3da1 175 if (r < 0)
abeab069
MT
176 goto ERROR;
177
082d81a3
MT
178 // Copy the configuration
179 r = nw_config_copy(config, &p->config);
8edf3da1 180 if (r < 0)
082d81a3
MT
181 goto ERROR;
182
827435c8
MT
183 // Setup the port
184 r = nw_port_setup(p);
8edf3da1 185 if (r < 0)
827435c8
MT
186 goto ERROR;
187
ea7dc1bb
MT
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
abeab069
MT
205 *port = p;
206 return 0;
207
208ERROR:
209 nw_port_free(p);
210 return r;
211}
212
8edf3da1 213int nw_port_open(nw_port** port, nw_daemon* daemon, const char* name) {
06bc93d3 214 nw_config* config = NULL;
8edf3da1
MT
215 FILE* f = NULL;
216 char path[PATH_MAX];
06bc93d3
MT
217 int r;
218
8edf3da1
MT
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
06bc93d3 231 // Initialize the configuration
8edf3da1
MT
232 r = nw_config_create(&config, f);
233 if (r < 0)
06bc93d3
MT
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);
8edf3da1 240 r = -ENOTSUP;
06bc93d3
MT
241 goto ERROR;
242 }
243
244 // Create a new port
c464b5d1 245 r = nw_port_create(port, daemon, nw_port_type_id_from_string(type), name, config);
8edf3da1 246 if (r < 0)
06bc93d3
MT
247 goto ERROR;
248
249ERROR:
250 if (config)
251 nw_config_unref(config);
8edf3da1
MT
252 if (f)
253 fclose(f);
06bc93d3
MT
254
255 return r;
256}
257
2361667e 258nw_port* nw_port_ref(nw_port* port) {
abeab069
MT
259 port->nrefs++;
260
261 return port;
262}
263
2361667e 264nw_port* nw_port_unref(nw_port* port) {
abeab069
MT
265 if (--port->nrefs > 0)
266 return port;
267
268 nw_port_free(port);
269 return NULL;
270}
271
240e331b
MT
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*/
276static void __nw_port_unref(void* data) {
277 nw_port* port = (nw_port*)data;
278
279 nw_port_unref(port);
280}
281
c403eb4c
MT
282int 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
c403eb4c
MT
309 return 0;
310}
311
312int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
313 nw_port* dropped_port = (nw_port*)data;
314 int r;
315
c464b5d1 316 switch (port->type->id) {
c403eb4c
MT
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
95c5dca2 325 case NW_PORT_BONDING:
c403eb4c 326 case NW_PORT_DUMMY:
c403eb4c
MT
327 break;
328 }
329
330 return 0;
331}
332
605e975f 333int nw_port_save(nw_port* port) {
8edf3da1
MT
334 char path[PATH_MAX];
335 FILE* f = NULL;
605e975f
MT
336 int r;
337
8edf3da1
MT
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
082d81a3
MT
350 // Write out the configuration
351 r = nw_config_options_write(port->config);
352 if (r < 0)
353 goto ERROR;
c7761af8 354
ff886975 355 // Write the configuration
8edf3da1
MT
356 r = nw_config_write(port->config, f);
357 if (r < 0)
358 goto ERROR;
c7761af8
MT
359
360ERROR:
8edf3da1
MT
361 if (f)
362 fclose(f);
363 if (r)
364 ERROR("Could not save configuration for port %s: %s\n", port->name, strerror(-r));
c7761af8 365
8edf3da1 366 return r;
605e975f
MT
367}
368
2361667e 369const char* nw_port_name(nw_port* port) {
abeab069
MT
370 return port->name;
371}
7297ba7f 372
2361667e 373char* nw_port_bus_path(nw_port* port) {
7297ba7f
MT
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}
4a383286 384
611d4aca
MT
385int __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
406int __nw_port_drop_link(nw_daemon* daemon, nw_port* port, void* data) {
407 nw_link* link = (nw_link*)data;
02801f0d 408
611d4aca
MT
409 // Drop the link if it matches
410 if (port->link == link)
411 return nw_port_set_link(port, NULL);
412
413 return 0;
30d4ab67
MT
414}
415
240e331b
MT
416static 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
2361667e 427const nw_address_t* nw_port_get_address(nw_port* port) {
4a383286
MT
428 return &port->address;
429}
20375a08 430
371b836a
MT
431static int nw_port_is_disabled(nw_port* port) {
432 return nw_config_get_bool(port->config, "DISABLED");
433}
434
5a5c346c 435nw_port* nw_port_get_parent_port(nw_port* port) {
c464b5d1 436 if (!NW_PORT_TYPE(port)->get_parent_port)
5a5c346c
MT
437 return NULL;
438
c464b5d1 439 return NW_PORT_TYPE(port)->get_parent_port(port);
5a5c346c
MT
440}
441
240e331b
MT
442static nw_link* nw_port_get_parent_link(nw_port* port) {
443 nw_port* parent = NULL;
444 nw_link* link = NULL;
445
240e331b 446 // Fetch the parent
5a5c346c 447 parent = nw_port_get_parent_port(port);
240e331b 448 if (!parent)
5a5c346c 449 return NULL;
240e331b
MT
450
451 // Fetch the link
452 link = nw_port_get_link(parent);
453
5a5c346c 454 // Cleanup
240e331b
MT
455 if (parent)
456 nw_port_unref(parent);
457
458 return link;
459}
460
461static 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
96b1b84d 479static int nw_port_create_link(nw_port* port) {
240e331b
MT
480 sd_netlink_message* m = NULL;
481 nw_link* link = NULL;
ff886975
MT
482 int r;
483
240e331b
MT
484 sd_netlink* rtnl = nw_daemon_get_rtnl(port->daemon);
485
575d1a3f
MT
486 DEBUG("Creating port %s...\n", port->name);
487
c464b5d1
MT
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
240e331b
MT
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;
ff886975
MT
507 }
508
1574bc00
MT
509 // XXX Set common things like MTU, etc.
510
d667fff4 511 // Set Ethernet address
1574bc00
MT
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 }
240e331b
MT
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
c464b5d1
MT
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);
240e331b
MT
534 if (r < 0) {
535 ERROR("Could not open IFLA_INFO_DATA container: %s\n", strerror(-r));
536 goto ERROR;
537 }
538
c464b5d1 539 r = NW_PORT_TYPE(port)->create_link(port, m);
240e331b
MT
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 {
c464b5d1 552 r = sd_netlink_message_append_string(m, IFLA_INFO_KIND, NW_PORT_TYPE(port)->kind);
240e331b
MT
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
572ERROR:
573 if (m)
574 sd_netlink_message_unref(m);
575 if (link)
576 nw_link_unref(link);
ff886975
MT
577
578 return r;
96b1b84d
MT
579}
580
b9769b09 581int nw_port_reconfigure(nw_port* port) {
371b836a
MT
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
96b1b84d 595 // If there is no link, we will try to create it
895f7134
MT
596 if (!port->link)
597 return nw_port_create_link(port);
96b1b84d 598
371b836a 599 // XXX TODO
96b1b84d
MT
600
601 return 0;
b9769b09
MT
602}
603
20375a08 604int nw_port_has_carrier(nw_port* port) {
02801f0d
MT
605 if (!port->link)
606 return 0;
20375a08 607
02801f0d 608 return nw_link_has_carrier(port->link);
20375a08 609}
15240e08
MT
610
611/*
612 Stats
613*/
614
615const 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
622int __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
632int nw_port_update_stats(nw_port* port) {
633 if (port->link)
634 return nw_link_update_stats(port->link);
635
636 return 0;
637}
c7761af8 638
c464b5d1
MT
639int nw_port_check_type(nw_port* port, const nw_port_type_id_t type) {
640 if (port->type->id == type)
c7761af8
MT
641 return 0;
642
643 errno = ENOTSUP;
644 return -errno;
645}
e9b0614e
MT
646
647/*
648 JSON
649*/
650int 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
c464b5d1 667 r = json_object_add_string(o, "Type", nw_port_type_id_to_string(port->type->id));
e9b0614e
MT
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
c464b5d1
MT
680 if (NW_PORT_TYPE(port)->to_json) {
681 r = NW_PORT_TYPE(port)->to_json(port, o);
e9b0614e
MT
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
692ERROR:
693 if (address)
694 free(address);
695 if (o)
696 json_object_unref(o);
697
698 return r;
699}