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