]>
Commit | Line | Data |
---|---|---|
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" |
30d4ab67 | 30 | #include "link.h" |
827435c8 | 31 | #include "logging.h" |
abeab069 | 32 | #include "port.h" |
15240e08 MT |
33 | #include "stats-collector.h" |
34 | #include "string.h" | |
abeab069 MT |
35 | |
36 | struct nw_port { | |
2361667e | 37 | nw_daemon* daemon; |
abeab069 MT |
38 | int nrefs; |
39 | ||
02801f0d MT |
40 | // Link |
41 | nw_link* link; | |
42 | ||
827435c8 | 43 | nw_port_type_t type; |
c7761af8 | 44 | char name[IF_NAMESIZE]; |
abeab069 MT |
45 | |
46 | // Configuration | |
2361667e | 47 | nw_config *config; |
2e7f4705 MT |
48 | |
49 | // Common attributes | |
50 | nw_address_t address; | |
c7761af8 MT |
51 | |
52 | // VLAN settings | |
53 | struct nw_port_vlan { | |
54 | nw_port* parent; | |
55 | int id; | |
56 | ||
57 | // If the parent has not been read from the configuration we will | |
58 | // save the name and try to find it later. | |
59 | char __parent_name[IF_NAMESIZE]; | |
60 | } vlan; | |
abeab069 MT |
61 | }; |
62 | ||
827435c8 MT |
63 | static const struct nw_port_type_map { |
64 | nw_port_type_t type; | |
65 | const char* name; | |
66 | } nw_port_type_map[] = { | |
c7761af8 MT |
67 | { NW_PORT_DUMMY, "dummy" }, |
68 | { NW_PORT_VLAN, "vlan" }, | |
827435c8 MT |
69 | { NW_PORT_UNKNOWN, NULL }, |
70 | }; | |
71 | ||
72 | static nw_port_type_t nw_port_type_from_string(const char* s) { | |
73 | const struct nw_port_type_map* map = NULL; | |
74 | ||
75 | for (map = nw_port_type_map; *map->name; map++) { | |
76 | if (strcmp(map->name, s) == 0) | |
77 | return map->type; | |
78 | } | |
79 | ||
80 | return NW_PORT_UNKNOWN; | |
81 | } | |
82 | ||
2361667e | 83 | static void nw_port_free(nw_port* port) { |
02801f0d MT |
84 | if (port->link) |
85 | nw_link_unref(port->link); | |
abeab069 MT |
86 | if (port->config) |
87 | nw_config_unref(port->config); | |
30d4ab67 MT |
88 | if (port->daemon) |
89 | nw_daemon_unref(port->daemon); | |
abeab069 MT |
90 | |
91 | free(port); | |
92 | } | |
93 | ||
2361667e | 94 | static int nw_port_setup_address(nw_port* port) { |
2e7f4705 MT |
95 | int r; |
96 | ||
97 | // Read ADDRESS from configuration | |
98 | const char* s = nw_config_get(port->config, "ADDRESS"); | |
99 | if (!s) { | |
100 | ERROR("Port %s: Address isn't set\n", port->name); | |
9a93da54 | 101 | goto ERROR; |
2e7f4705 MT |
102 | } |
103 | ||
104 | // Parse the address | |
105 | r = nw_address_from_string(&port->address, s); | |
106 | if (r) { | |
107 | ERROR("Port %s: Could not parse address: %m\n", port->name); | |
9a93da54 | 108 | goto ERROR; |
2e7f4705 MT |
109 | } |
110 | ||
d81d9cf5 MT |
111 | // Check if this address is usable |
112 | r = nw_address_is_multicast(&port->address); | |
113 | if (r) { | |
114 | DEBUG("Port %s: Multicast bit is set on Ethernet address\n", port->name); | |
115 | goto ERROR; | |
116 | } | |
2e7f4705 | 117 | |
9a93da54 MT |
118 | return 0; |
119 | ||
120 | ERROR: | |
121 | // Generate a random Ethernet address | |
122 | r = nw_address_generate(&port->address); | |
123 | if (r) { | |
124 | ERROR("Could not generate a random Ethernet address: %m\n"); | |
125 | return r; | |
126 | } | |
127 | ||
2e7f4705 MT |
128 | return 0; |
129 | } | |
130 | ||
2361667e | 131 | static int nw_port_setup_common(nw_port* port) { |
2e7f4705 MT |
132 | int r; |
133 | ||
134 | // Address | |
135 | r = nw_port_setup_address(port); | |
136 | if (r) | |
137 | return r; | |
138 | ||
139 | return 0; | |
827435c8 MT |
140 | } |
141 | ||
c7761af8 MT |
142 | static int nw_port_setup_vlan(nw_port* port) { |
143 | int r; | |
144 | ||
145 | // VLAN ID | |
146 | int id = nw_config_get_int(port->config, "VLAN_ID", NW_VLAN_ID_INVALID); | |
147 | if (id) { | |
148 | r = nw_port_set_vlan_id(port, id); | |
149 | if (r) | |
150 | return r; | |
151 | } | |
152 | ||
153 | // Parent Port | |
154 | const char* parent = nw_config_get(port->config, "VLAN_PARENT"); | |
155 | if (parent) { | |
156 | r = nw_string_set(port->vlan.__parent_name, parent); | |
157 | if (r) | |
158 | return r; | |
159 | } | |
160 | ||
161 | return 0; | |
162 | } | |
163 | ||
611d4aca MT |
164 | static int nw_port_set_link(nw_port* port, nw_link* link) { |
165 | // Do nothing if the same link is being re-assigned | |
166 | if (port->link == link) | |
167 | return 0; | |
168 | ||
169 | // Dereference the former link if set | |
170 | if (port->link) | |
171 | nw_link_unref(port->link); | |
172 | ||
173 | // Store the new link | |
174 | if (link) { | |
175 | port->link = nw_link_ref(link); | |
176 | ||
177 | DEBUG("Port %s: Assigned link %d\n", port->name, nw_link_ifindex(port->link)); | |
178 | ||
179 | // Or clear the pointer if no link has been provided | |
180 | } else { | |
181 | port->link = NULL; | |
182 | ||
183 | DEBUG("Port %s: Removed link\n", port->name); | |
184 | } | |
185 | ||
186 | return 0; | |
187 | } | |
188 | ||
2361667e | 189 | static int nw_port_setup(nw_port* port) { |
611d4aca | 190 | nw_link* link = NULL; |
827435c8 MT |
191 | char path[PATH_MAX]; |
192 | int r; | |
193 | ||
02801f0d | 194 | // Find the link |
611d4aca MT |
195 | link = nw_daemon_get_link_by_name(port->daemon, port->name); |
196 | if (link) { | |
197 | r = nw_port_set_link(port, link); | |
198 | if (r) | |
199 | goto ERROR; | |
02801f0d MT |
200 | } |
201 | ||
827435c8 MT |
202 | // Compose the path to the main configuration file |
203 | r = nw_path_join(path, PORT_CONFIG_DIR, port->name); | |
204 | if (r) | |
611d4aca | 205 | goto ERROR; |
827435c8 MT |
206 | |
207 | // Initialize the configuration | |
208 | r = nw_config_create(&port->config, path); | |
209 | if (r) | |
611d4aca | 210 | goto ERROR; |
827435c8 | 211 | |
827435c8 MT |
212 | // Perform some common initialization |
213 | r = nw_port_setup_common(port); | |
214 | if (r) | |
611d4aca | 215 | goto ERROR; |
827435c8 MT |
216 | |
217 | // Call any custom initialization | |
218 | switch (port->type) { | |
c7761af8 MT |
219 | case NW_PORT_VLAN: |
220 | r = nw_port_setup_vlan(port); | |
221 | if (r) | |
222 | goto ERROR; | |
223 | break; | |
224 | ||
827435c8 MT |
225 | // These do not need any special initialization |
226 | case NW_PORT_DUMMY: | |
227 | case NW_PORT_UNKNOWN: | |
228 | break; | |
229 | } | |
230 | ||
611d4aca MT |
231 | ERROR: |
232 | if (link) | |
233 | nw_link_unref(link); | |
234 | ||
235 | return r; | |
827435c8 MT |
236 | } |
237 | ||
06bc93d3 | 238 | int nw_port_create(nw_port** port, nw_daemon* daemon, nw_port_type_t type, const char* name) { |
abeab069 MT |
239 | int r; |
240 | ||
241 | // Allocate a new object | |
2361667e | 242 | nw_port* p = calloc(1, sizeof(*p)); |
abeab069 MT |
243 | if (!p) |
244 | return 1; | |
245 | ||
30d4ab67 MT |
246 | // Store a reference to the daemon |
247 | p->daemon = nw_daemon_ref(daemon); | |
248 | ||
abeab069 MT |
249 | // Initialize reference counter |
250 | p->nrefs = 1; | |
251 | ||
06bc93d3 MT |
252 | // Store the type |
253 | p->type = type; | |
254 | ||
abeab069 MT |
255 | // Store the name |
256 | r = nw_string_set(p->name, name); | |
257 | if (r) | |
258 | goto ERROR; | |
259 | ||
827435c8 MT |
260 | // Setup the port |
261 | r = nw_port_setup(p); | |
262 | if (r) | |
263 | goto ERROR; | |
264 | ||
abeab069 MT |
265 | *port = p; |
266 | return 0; | |
267 | ||
268 | ERROR: | |
269 | nw_port_free(p); | |
270 | return r; | |
271 | } | |
272 | ||
06bc93d3 MT |
273 | int nw_port_create_from_config(nw_port** port, nw_daemon* daemon, |
274 | const char* name, const char* path) { | |
275 | nw_config* config = NULL; | |
276 | int r; | |
277 | ||
278 | // Initialize the configuration | |
279 | r = nw_config_create(&config, path); | |
280 | if (r) | |
281 | goto ERROR; | |
282 | ||
283 | // Fetch the type | |
284 | const char* type = nw_config_get(config, "TYPE"); | |
285 | if (!type) { | |
286 | ERROR("Port configuration %s has no TYPE\n", path); | |
287 | r = 1; | |
288 | goto ERROR; | |
289 | } | |
290 | ||
291 | // Create a new port | |
292 | r = nw_port_create(port, daemon, nw_port_type_from_string(type), name); | |
293 | if (r) | |
294 | goto ERROR; | |
295 | ||
296 | ERROR: | |
297 | if (config) | |
298 | nw_config_unref(config); | |
299 | ||
300 | return r; | |
301 | } | |
302 | ||
2361667e | 303 | nw_port* nw_port_ref(nw_port* port) { |
abeab069 MT |
304 | port->nrefs++; |
305 | ||
306 | return port; | |
307 | } | |
308 | ||
2361667e | 309 | nw_port* nw_port_unref(nw_port* port) { |
abeab069 MT |
310 | if (--port->nrefs > 0) |
311 | return port; | |
312 | ||
313 | nw_port_free(port); | |
314 | return NULL; | |
315 | } | |
316 | ||
c403eb4c MT |
317 | int nw_port_destroy(nw_port* port) { |
318 | int r; | |
319 | ||
320 | DEBUG("Destroying port %s\n", port->name); | |
321 | ||
322 | // Destroy the physical link (if exists) | |
323 | if (port->link) { | |
324 | r = nw_link_destroy(port->link); | |
325 | if (r) | |
326 | return r; | |
327 | } | |
328 | ||
329 | // Dereference the port from other ports | |
330 | r = nw_daemon_ports_walk(port->daemon, __nw_port_drop_port, port); | |
331 | if (r) | |
332 | return r; | |
333 | ||
334 | // Dereference the port from other zones | |
335 | r = nw_daemon_zones_walk(port->daemon, __nw_zone_drop_port, port); | |
336 | if (r) | |
337 | return r; | |
338 | ||
339 | // Destroy the configuration | |
340 | r = nw_config_destroy(port->config); | |
341 | if (r) | |
342 | return r; | |
343 | ||
344 | // Reset type | |
345 | port->type = NW_PORT_UNKNOWN; | |
346 | ||
347 | return 0; | |
348 | } | |
349 | ||
350 | int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) { | |
351 | nw_port* dropped_port = (nw_port*)data; | |
352 | int r; | |
353 | ||
354 | switch (port->type) { | |
355 | case NW_PORT_VLAN: | |
356 | if (port->vlan.parent == dropped_port) { | |
357 | r = nw_port_set_vlan_parent(port, NULL); | |
358 | if (r) | |
359 | return r; | |
360 | } | |
361 | break; | |
362 | ||
363 | case NW_PORT_DUMMY: | |
364 | case NW_PORT_UNKNOWN: | |
365 | break; | |
366 | } | |
367 | ||
368 | return 0; | |
369 | } | |
370 | ||
c7761af8 MT |
371 | static int nw_port_save_vlan(nw_port* port) { |
372 | int r; | |
373 | ||
374 | // VLAN ID | |
375 | r = nw_config_set_int(port->config, "VLAN_ID", port->vlan.id); | |
376 | if (r) | |
377 | return r; | |
378 | ||
379 | // Parent Port | |
380 | r = nw_config_set(port->config, "VLAN_PARENT", | |
381 | (port->vlan.parent) ? nw_port_name(port->vlan.parent) : port->vlan.__parent_name); | |
382 | if (r) | |
383 | return r; | |
384 | ||
385 | return 0; | |
386 | } | |
387 | ||
605e975f MT |
388 | int nw_port_save(nw_port* port) { |
389 | int r; | |
390 | ||
c7761af8 MT |
391 | switch (port->type) { |
392 | // VLAN | |
393 | case NW_PORT_VLAN: | |
394 | r = nw_port_save_vlan(port); | |
395 | if (r) | |
396 | goto ERROR; | |
397 | break; | |
398 | ||
399 | // These types do not have any special settings | |
400 | case NW_PORT_DUMMY: | |
401 | case NW_PORT_UNKNOWN: | |
402 | break; | |
403 | } | |
404 | ||
605e975f MT |
405 | r = nw_config_write(port->config); |
406 | if (r) | |
407 | return r; | |
408 | ||
409 | return 0; | |
c7761af8 MT |
410 | |
411 | ERROR: | |
412 | ERROR("Could not save configuration for port %s: %m\n", port->name); | |
413 | ||
414 | return 1; | |
605e975f MT |
415 | } |
416 | ||
2361667e | 417 | const char* nw_port_name(nw_port* port) { |
abeab069 MT |
418 | return port->name; |
419 | } | |
7297ba7f | 420 | |
2361667e | 421 | char* nw_port_bus_path(nw_port* port) { |
7297ba7f MT |
422 | char* p = NULL; |
423 | int r; | |
424 | ||
425 | // Encode the bus path | |
426 | r = sd_bus_path_encode("/org/ipfire/network1/port", port->name, &p); | |
427 | if (r < 0) | |
428 | return NULL; | |
429 | ||
430 | return p; | |
431 | } | |
4a383286 | 432 | |
611d4aca MT |
433 | int __nw_port_set_link(nw_daemon* daemon, nw_port* port, void* data) { |
434 | nw_link* link = (nw_link*)data; | |
435 | ||
436 | // Fetch the link name | |
437 | const char* ifname = nw_link_ifname(link); | |
438 | if (!ifname) { | |
439 | ERROR("Link does not have a name set\n"); | |
440 | return 1; | |
441 | } | |
442 | ||
443 | // Set link if the name matches | |
444 | if (strcmp(port->name, ifname) == 0) | |
445 | return nw_port_set_link(port, link); | |
446 | ||
447 | // If we have the link set but the name did not match, we will remove it | |
448 | else if (port->link == link) | |
449 | return nw_port_set_link(port, NULL); | |
450 | ||
451 | return 0; | |
452 | } | |
453 | ||
454 | int __nw_port_drop_link(nw_daemon* daemon, nw_port* port, void* data) { | |
455 | nw_link* link = (nw_link*)data; | |
02801f0d | 456 | |
611d4aca MT |
457 | // Drop the link if it matches |
458 | if (port->link == link) | |
459 | return nw_port_set_link(port, NULL); | |
460 | ||
461 | return 0; | |
30d4ab67 MT |
462 | } |
463 | ||
2361667e | 464 | const nw_address_t* nw_port_get_address(nw_port* port) { |
4a383286 MT |
465 | return &port->address; |
466 | } | |
20375a08 | 467 | |
371b836a MT |
468 | static int nw_port_is_disabled(nw_port* port) { |
469 | return nw_config_get_bool(port->config, "DISABLED"); | |
470 | } | |
471 | ||
96b1b84d MT |
472 | static int nw_port_create_link(nw_port* port) { |
473 | return 0; // XXX TODO | |
474 | } | |
475 | ||
b9769b09 | 476 | int nw_port_reconfigure(nw_port* port) { |
371b836a MT |
477 | int r; |
478 | ||
479 | // If the port is disabled, we will try to destroy it | |
480 | if (nw_port_is_disabled(port)) { | |
481 | if (port->link) { | |
482 | r = nw_link_destroy(port->link); | |
483 | if (r) | |
484 | return r; | |
485 | } | |
486 | ||
487 | return 0; | |
488 | } | |
489 | ||
96b1b84d MT |
490 | // If there is no link, we will try to create it |
491 | if (!port->link) { | |
492 | r = nw_port_create_link(port); | |
493 | if (r) | |
494 | return r; | |
495 | } | |
496 | ||
371b836a | 497 | // XXX TODO |
96b1b84d MT |
498 | |
499 | return 0; | |
b9769b09 MT |
500 | } |
501 | ||
20375a08 | 502 | int nw_port_has_carrier(nw_port* port) { |
02801f0d MT |
503 | if (!port->link) |
504 | return 0; | |
20375a08 | 505 | |
02801f0d | 506 | return nw_link_has_carrier(port->link); |
20375a08 | 507 | } |
15240e08 MT |
508 | |
509 | /* | |
510 | Stats | |
511 | */ | |
512 | ||
513 | const struct rtnl_link_stats64* nw_port_get_stats64(nw_port* port) { | |
514 | if (!port->link) | |
515 | return NULL; | |
516 | ||
517 | return nw_link_get_stats64(port->link); | |
518 | } | |
519 | ||
520 | int __nw_port_update_stats(nw_daemon* daemon, nw_port* port, void* data) { | |
521 | nw_link* link = (nw_link*)data; | |
522 | ||
523 | // Emit stats if link matches | |
524 | if (port->link == link) | |
525 | return nw_stats_collector_emit_port_stats(daemon, port); | |
526 | ||
527 | return 0; | |
528 | } | |
529 | ||
530 | int nw_port_update_stats(nw_port* port) { | |
531 | if (port->link) | |
532 | return nw_link_update_stats(port->link); | |
533 | ||
534 | return 0; | |
535 | } | |
c7761af8 MT |
536 | |
537 | static int nw_port_check_type(nw_port* port, const nw_port_type_t type) { | |
538 | if (port->type == type) | |
539 | return 0; | |
540 | ||
541 | errno = ENOTSUP; | |
542 | return -errno; | |
543 | } | |
544 | ||
545 | /* | |
546 | VLAN | |
547 | */ | |
548 | int nw_port_get_vlan_id(nw_port* port) { | |
549 | int r; | |
550 | ||
551 | // Check type | |
552 | r = nw_port_check_type(port, NW_PORT_VLAN); | |
553 | if (r < 0) | |
554 | return r; | |
555 | ||
556 | return port->vlan.id; | |
557 | } | |
558 | ||
559 | int nw_port_set_vlan_id(nw_port* port, int id) { | |
560 | int r; | |
561 | ||
562 | // Check type | |
563 | r = nw_port_check_type(port, NW_PORT_VLAN); | |
564 | if (r < 0) | |
565 | return r; | |
566 | ||
567 | // Check if the VLAN ID is within range | |
568 | if (id < NW_VLAN_ID_MIN || id > NW_VLAN_ID_MAX) | |
569 | return -EINVAL; | |
570 | ||
571 | // Store the ID | |
572 | port->vlan.id = id; | |
573 | ||
574 | DEBUG("Port %s: Set VLAN ID to %d\n", port->name, port->vlan.id); | |
575 | ||
576 | return 0; | |
577 | } | |
578 | ||
579 | nw_port* nw_port_get_vlan_parent(nw_port* port) { | |
580 | int r; | |
581 | ||
582 | // Check type | |
583 | r = nw_port_check_type(port, NW_PORT_VLAN); | |
584 | if (r < 0) | |
585 | return NULL; | |
586 | ||
587 | // Try to find a reference to the parent if none exists | |
588 | if (!port->vlan.parent && *port->vlan.__parent_name) | |
589 | port->vlan.parent = nw_daemon_get_port_by_name(port->daemon, port->vlan.__parent_name); | |
590 | ||
591 | if (port->vlan.parent) | |
592 | return nw_port_ref(port->vlan.parent); | |
593 | ||
594 | // No port found | |
595 | return NULL; | |
596 | } | |
597 | ||
598 | int nw_port_set_vlan_parent(nw_port* port, nw_port* parent) { | |
599 | int r; | |
600 | ||
601 | // Check type | |
602 | r = nw_port_check_type(port, NW_PORT_VLAN); | |
603 | if (r < 0) | |
604 | return r; | |
605 | ||
606 | // Reset the former parent name | |
607 | nw_string_empty(port->vlan.__parent_name); | |
608 | ||
609 | // Dereference the former parent | |
610 | if (port->vlan.parent) { | |
611 | nw_port_unref(port->vlan.parent); | |
612 | port->vlan.parent = NULL; | |
613 | } | |
614 | ||
615 | // Store the new parent | |
616 | if (parent) | |
617 | port->vlan.parent = nw_port_ref(parent); | |
618 | ||
619 | DEBUG("Port %s: Set VLAN parent to %s\n", port->name, nw_port_name(port->vlan.parent)); | |
620 | ||
621 | return 0; | |
622 | } |