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