/config.*
/libtool
/missing
+/networkctl
+/networkd
/src/functions/functions
/src/inetcalc
/src/libnetwork/libnetwork.pc
/src/network.pc
+/src/networkd/networkd.service
/src/ppp/ip-updown
/src/systemd/*.service
/test/nitsi/test/settings
configdir = $(sysconfdir)/network
bashcompletiondir= $(datadir)/bash-completion/completions
libexecdir = $(prefix)/lib
+modprobedir = $(prefix)/lib/modprobe.d
pkgconfigdir = $(libdir)/pkgconfig
pppdir = $(sysconfdir)/ppp
systemconfigdir = $(datadir)/network
triggersdir = $(networkdir)/triggers
-logdir = $(localestatedir)/log/network
+logdir = $(localstatedir)/log/network
utildir = $(networkdir)
CLEANFILES =
EXTRA_DIST =
INSTALL_DIRS =
INSTALL_EXEC_HOOKS =
+TESTS =
UNINSTALL_EXEC_HOOKS =
noinst_DATA =
+network_PROGRAMS =
+dist_dbuspolicy_DATA =
+dist_dbussystembus_DATA =
+dist_polkitpolicy_DATA =
+systemdsystemunit_DATA =
AM_CPPFLAGS = \
$(OUR_CPPFLAGS) \
# ------------------------------------------------------------------------------
+network_PROGRAMS += \
+ networkd
+
+dist_networkd_SOURCES = \
+ src/networkd/address.h \
+ src/networkd/bus.c \
+ src/networkd/bus.h \
+ src/networkd/config.c \
+ src/networkd/config.h \
+ src/networkd/daemon.c \
+ src/networkd/daemon.h \
+ src/networkd/daemon-bus.c \
+ src/networkd/daemon-bus.h \
+ src/networkd/devmon.c \
+ src/networkd/devmon.h \
+ src/networkd/json.h \
+ src/networkd/link.c \
+ src/networkd/link.h \
+ src/networkd/links.c \
+ src/networkd/links.h \
+ src/networkd/logging.c \
+ src/networkd/logging.h \
+ src/networkd/main.c \
+ src/networkd/ports.c \
+ src/networkd/ports.h \
+ src/networkd/port.c \
+ src/networkd/port.h \
+ src/networkd/port-bonding.c \
+ src/networkd/port-bonding.h \
+ src/networkd/port-bus.c \
+ src/networkd/port-bus.h \
+ src/networkd/port-dummy.c \
+ src/networkd/port-dummy.h \
+ src/networkd/port-ethernet.c \
+ src/networkd/port-ethernet.h \
+ src/networkd/port-veth.c \
+ src/networkd/port-veth.h \
+ src/networkd/port-vlan.c \
+ src/networkd/port-vlan.h \
+ src/networkd/stats-collector.c \
+ src/networkd/stats-collector.h \
+ src/networkd/string.h \
+ src/networkd/util.c \
+ src/networkd/util.h \
+ src/networkd/zones.c \
+ src/networkd/zones.h \
+ src/networkd/zone.c \
+ src/networkd/zone.h \
+ src/networkd/zone-bus.c \
+ src/networkd/zone-bus.h
+
+networkd_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -DCONFIG_DIR="\"$(configdir)\""
+
+networkd_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CAP_CFLAGS) \
+ $(JSON_C_CFLAGS) \
+ $(SYSTEMD_CFLAGS)
+
+networkd_LDFLAGS = \
+ $(AM_LDFLAGS)
+
+networkd_LDADD = \
+ src/libnetwork.la \
+ $(CAP_LIBS) \
+ $(JSON_C_LIBS) \
+ $(SYSTEMD_LIBS)
+
+dist_dbuspolicy_DATA += \
+ src/networkd/org.ipfire.network1.conf
+
+dist_dbussystembus_DATA += \
+ src/networkd/org.ipfire.network1.service
+
+dist_polkitpolicy_DATA += \
+ src/networkd/org.ipfire.network1.policy
+
+systemdsystemunit_DATA += \
+ src/networkd/networkd.service
+
+EXTRA_DIST += \
+ src/networkd/networkd.service.in
+
+CLEANFILES += \
+ src/networkd/networkd.service
+
+# ------------------------------------------------------------------------------
+
+bin_PROGRAMS += \
+ networkctl
+
+dist_networkctl_SOURCES = \
+ src/networkctl/command.c \
+ src/networkctl/command.h \
+ src/networkctl/main.c \
+ src/networkctl/port.c \
+ src/networkctl/port.h \
+ src/networkctl/terminal.c \
+ src/networkctl/terminal.h \
+ src/networkctl/zone.c \
+ src/networkctl/zone.h
+
+networkctl_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(JSON_C_CFLAGS) \
+ $(SYSTEMD_CFLAGS)
+
+networkctl_LDFLAGS = \
+ $(AM_LDFLAGS)
+
+networkctl_LDADD = \
+ $(JSON_C_LIBS) \
+ $(SYSTEMD_LIBS)
+
+# ------------------------------------------------------------------------------
+
util_PROGRAMS = \
src/utils/network-phy-list-channels \
src/utils/network-phy-list-ciphers \
# ------------------------------------------------------------------------------
if HAVE_SYSTEMD
-systemdsystemunit_DATA = \
+systemdsystemunit_DATA += \
src/systemd/firewall.service \
src/systemd/firewall-init.service \
src/systemd/network-init.service \
# ------------------------------------------------------------------------------
+dist_modprobe_DATA = \
+ src/modprobe.d/no-copybreak.conf
+
+# ------------------------------------------------------------------------------
+
dist_bashcompletion_SCRIPTS = \
src/bash-completion/network
'|builddir=$(abs_builddir)|' \
'|prefix=$(prefix)|' \
'|exec_prefix=$(exec_prefix)|' \
+ '|bindir=$(bindir)|' \
'|sbindir=$(sbindir)|' \
'|networkdir=$(networkdir)|' \
'|helpersdir=$(helpersdir)|' \
dist_check_DATA = \
test/constants.sh \
- test/test-functions
+ test/test-functions \
+ test/networkd/test.sh
dist_check_SCRIPTS = \
- $(TESTS)
-
-TESTS = \
test/load-library \
test/functions/ip/ip_detect_protocol \
test/functions/ip/ip_get_prefix \
test/functions/ip/ip_protocol_is_supported \
test/functions/ip/ip_split_prefix
+TESTS += $(dist_check_SCRIPTS)
+
+TEST_EXTENSIONS = .t
+
+NETWORKD_TESTS = \
+ test/networkd/00_launch.t \
+ test/networkd/01_dummy.t
+
+TESTS += $(NETWORKD_TESTS)
+
+EXTRA_DIST += \
+ test/networkd/test.sh \
+ $(NETWORKD_TESTS)
+
+# Run all networkd tests in their own namespaces
+T_LOG_COMPILER = unshare --net --ipc --uts --user --cgroup --time --pid --fork \
+ --map-root-user --keep-caps $(SHELL) test/networkd/test.sh
+
# - NITSI tests ----------------------------------------------------------------
# Files for the virtual environment
AC_CONFIG_AUX_DIR([build-aux])
+AC_USE_SYSTEM_EXTENSIONS
+AC_SYS_LARGEFILE
AC_PREFIX_DEFAULT([/usr])
AM_INIT_AUTOMAKE([
AC_PROG_GCC_TRADITIONAL
CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [\
- -pipe \
-Wall \
- -Wextra \
- -Wno-inline \
- -Wundef \
- "-Wformat=2 -Wformat-security -Wformat-nonliteral" \
- -Wno-unused-parameter \
- -Wno-unused-result \
- -fno-strict-aliasing \
- -ffunction-sections \
- -fdata-sections \
- -fstack-protector-all \
- --param=ssp-buffer-size=4])
-AC_SUBST([OUR_CFLAGS], $with_cflags)
-
-AS_CASE([$CFLAGS], [*-O[[12345g\ ]]*],
- [CC_CHECK_FLAGS_APPEND([with_cppflags], [CPPFLAGS], [\
- -Wp,-D_FORTIFY_SOURCE=2])],
- [AC_MSG_RESULT([skipping -D_FORTIFY_SOURCE, optimization not enabled])])
-AC_SUBST([OUR_CPPFLAGS], $with_cppflags)
+ -Wchar-subscripts \
+ -Wformat-security \
+ -Wmissing-declarations \
+ -Wmissing-prototypes \
+ -Wnested-externs \
+ -Wpointer-arith \
+ -Wshadow \
+ -Wsign-compare \
+ -Wstrict-prototypes \
+ -Wtype-limits \
+])
-CC_CHECK_FLAGS_APPEND([with_ldflags], [LDFLAGS], [\
- -Wl,--as-needed \
- -Wl,--no-undefined \
- -Wl,--gc-sections \
- -Wl,-z,relro \
- -Wl,-z,now])
-AC_SUBST([OUR_LDFLAGS], $with_ldflags)
+AC_SUBST([OUR_CFLAGS], $with_cflags)
# ------------------------------------------------------------------------------
AS_IF([test "x$enable_manpages" != xno], [have_manpages=yes])
AM_CONDITIONAL(ENABLE_MANPAGES, [test "x$have_manpages" = "xyes"])
+# ------------------------------------------------------------------------------
+
+AC_ARG_WITH(
+ [dbuspolicydir],
+ AS_HELP_STRING(
+ [--with-dbuspolicydir=arg],
+ [directory for D-Bus policies (default: ${dbusdatadir|datarootdir}/dbus-1/system.d)]
+ ),
+ [dbuspolicydir="$withval"],
+ [PKG_CHECK_VAR([dbusdatadir], [dbus-1], [datadir],, [dbusdatadir="${datarootdir}"])
+ dbuspolicydir="${dbusdatadir}/dbus-1/system.d"]
+)
+AC_SUBST(dbuspolicydir)
+
+AC_ARG_WITH(
+ [dbussystembusdir],
+ AS_HELP_STRING(
+ [--with-dbussystembusdir=arg],
+ [path to D-Bus system bus services directory]
+ ),
+ [dbussystembusdir="$withval"],
+ [PKG_CHECK_VAR([dbussystembusdir], [dbus-1], [system_bus_services_dir],, [dbussystembusdir="${datarootdir}"])]
+)
+AC_SUBST(dbuspolicydir)
+
+AC_ARG_WITH(
+ [polkitpolicydir],
+ AS_HELP_STRING(
+ [--with-polkitpolicydir=arg],
+ [directory for PolicyKit policies]
+ ),
+ [polkitpolicydir="$withval"],
+ [PKG_CHECK_VAR([polkitpolicydir], [polkit], [actiondir])
+ polkitpolicydir="${datadir}/polkit-1/actions"]
+)
+AC_SUBST(polkitpolicydir)
+
# ------------------------------------------------------------------------------
AC_ARG_WITH([systemdsystemunitdir],
AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
# ------------------------------------------------------------------------------
+PKG_CHECK_MODULES([CAP], [libcap])
+PKG_CHECK_MODULES([JSON_C], [json-c])
PKG_CHECK_MODULES([LIBNL], [libnl-3.0 libnl-genl-3.0])
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd])
# ------------------------------------------------------------------------------
prefix: $prefix
+ dbuspolicydir: ${dbuspolicydir}
+ dbussystembusdir: ${dbussystembusdir}
+ polkitpolicydir: ${polkitpolicydir}
systemdsystemunitdir: $systemdsystemunitdir
udevdir: $udevdir
-= network-vpn-security-policies(8)
+= network-vpn-ipsec(8)
== NAME
-network-ipsec - Configure IPsec VPN connections
+network-vpn-ipsec - Configure IPsec VPN connections
== SYNOPSIS
[verse]
For all other commands, the name of the IPsec VPN connection needs to be passed first:
'NAME show'::
- Shows the configuration of the IPsec VPN connection
+ Shows the configuration of the IPsec VPN connection
'NAME authentication mode'::
Set the authentication mode out of the following available modes:
Specify the subnets of the local system which should be made available to the remote peer.
'NAME mode [transport|tunnel]'::
- Set the mode of the IPsec VPN connection.
+ Set the mode of the IPsec VPN connection.
'NAME peer PEER'::
Set the peer to which the IPsec VPN connection should be etablished.
int network_get_log_priority(struct network_ctx* ctx);
void network_set_log_priority(struct network_ctx* ctx, int priority);
-const char* network_version();
+const char* network_version(void);
#ifdef NETWORK_PRIVATE
--- /dev/null
+#
+# Some network interface drivers employ a scheme known as "copybreak"
+# in which they make a copy of a received skb if the size of the
+# buffer is below a particular threshold, then return the original
+# receive skb back to the pool. Since these drivers initially
+# allocate a buffer size that is larger than the largest possible
+# packet, this scheme returns that large buffer to the pool quickly,
+# and uses a smaller one.
+#
+# The primary benefit of copybreak is better memory utilization. On
+# systems where the data is ultimately going to be copied out to user
+# space, the copybreak scheme is "low cost" because it has the side
+# benefit of priming the cache for that later copy. But on a router
+# that only touches the header fields of a received packet, the cost
+# can be relatively higher. And on modern systems the memory savings
+# is rarely an important consideration.
+#
+# Some of the drivers that employ copybreak make the feature
+# configurable via a module parameter. This file disables copybreak
+# in some of those drivers. Generally this results in an improvement
+# in forwarding performance for traffic using these drivers.
+#
+
+options 3c515 rx_copybreak=0
+options 3c59x rx_copybreak=0
+options bcm63xx copybreak=0
+options cxgb copybreak=0
+options e1000 copybreak=0
+options e1000e copybreak=0
+options epic100 rx_copybreak=0
+options fealnx rx_copybreak=0
+options hamachi rx_copybreak=0
+options ixgb copybreak=0
+options natsemi rx_copybreak=0
+options pch_gbe copybreak=0
+options pcnet32 rx_copybreak=0
+options sis190 rx_copybreak=0
+options sky2 copybreak=0
+options starfire rx_copybreak=0
+options sundance rx_copybreak=0
+options typhoon rx_copybreak=0
+options via-rhine rx_copybreak=0
+options via-velocity rx_copybreak=0
+options yellowfin rx_copybreak=0
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <errno.h>
+#include <getopt.h>
+#include <string.h>
+
+#include "command.h"
+
+static const struct command* command_find(const struct command* commands, const char* verb) {
+ for (const struct command* command = commands; command->verb; command++) {
+ if (strcmp(command->verb, verb) == 0)
+ return command;
+ }
+
+ return NULL;
+}
+
+int command_dispatch(sd_bus* bus, const struct command* commands, int argc, char* argv[]) {
+ const struct command* command = NULL;
+
+ if (!argc) {
+ fprintf(stderr, "Command required\n");
+ return -EINVAL;
+ }
+
+ const char* verb = argv[0];
+
+ // Find a matching command
+ command = command_find(commands, verb);
+ if (!command) {
+ fprintf(stderr, "Unknown command '%s'\n", verb);
+ return -EINVAL;
+ }
+
+ return command->callback(bus, argc - 1, argv + 1);
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKCTL_COMMAND_H
+#define NETWORKCTL_COMMAND_H
+
+#include <systemd/sd-bus.h>
+
+struct command {
+ const char* verb;
+ int flags;
+ int (*callback)(sd_bus* bus, int argc, char* argv[]);
+};
+
+int command_dispatch(sd_bus* bus, const struct command* commands, int argc, char* argv[]);
+
+#endif /* NETWORKCTL_COMMAND_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <systemd/sd-bus.h>
+
+#include "command.h"
+#include "port.h"
+#include "zone.h"
+
+static int networkctl_status(sd_bus* bus, int argc, char* argv[]) {
+ printf("%s called\n", __FUNCTION__);
+ return 0;
+}
+
+static int networkctl_main(sd_bus* bus, int argc, char* argv[]) {
+ static const struct command commands[] = {
+ { "port", 0, networkctl_port },
+ { "status", 0, networkctl_status },
+ { "zone", 0, networkctl_zone },
+ { NULL },
+ };
+
+ return command_dispatch(bus, commands, argc, argv);
+}
+
+static void version(void) {
+ printf("networkctl %s\n", PACKAGE_VERSION);
+
+ exit(0);
+}
+
+static void help(void) {
+ printf(
+ "%s [OPTIONS...] COMMAND\n\n"
+ "Options:\n"
+ " -h --help Show help\n"
+ " --version Show version\n",
+ program_invocation_short_name
+ );
+
+ exit(0);
+}
+
+static int parse_argv(int argc, char* argv[]) {
+ enum {
+ ARG_VERSION,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { NULL },
+ };
+ int c;
+
+ for (;;) {
+ c = getopt_long(argc, argv, "h", options, NULL);
+ if (c < 0)
+ break;
+
+ switch (c) {
+ case 'h':
+ help();
+
+ case ARG_VERSION:
+ version();
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char* argv[]) {
+ sd_bus* bus = NULL;
+ int r;
+
+ // Parse command line arguments
+ r = parse_argv(argc, argv);
+ if (r)
+ goto ERROR;
+
+ // Connect to system bus
+ r = sd_bus_open_system(&bus);
+ if (r < 0) {
+ fprintf(stderr, "Could not connect to system bus: %m\n");
+ goto ERROR;
+ }
+
+ // Run a command
+ r = networkctl_main(bus, argc - 1, argv + 1);
+
+ERROR:
+ if (bus)
+ sd_bus_flush_close_unref(bus);
+
+ return r;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <limits.h>
+#include <stdlib.h>
+
+#include <systemd/sd-bus.h>
+
+#include "../networkd/json.h"
+#include "../networkd/string.h"
+#include "command.h"
+#include "port.h"
+#include "terminal.h"
+
+typedef int (*networkctl_port_walk_callback)
+ (sd_bus* bus, const char* path, const char* name, void* data);
+
+static int networkctl_port_walk(sd_bus* bus,
+ networkctl_port_walk_callback callback, void* data) {
+ sd_bus_message* reply = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ // Call Listports
+ r = sd_bus_call_method(bus, "org.ipfire.network1", "/org/ipfire/network1",
+ "org.ipfire.network1", "ListPorts", &error, &reply, "");
+ if (r < 0) {
+ fprintf(stderr, "ListPorts call failed: %m\n");
+ goto ERROR;
+ }
+
+ const char* name = NULL;
+ const char* path = NULL;
+
+ // Open the container
+ r = sd_bus_message_enter_container(reply, 'a', "(so)");
+ if (r < 0) {
+ fprintf(stderr, "Could not open container: %m\n");
+ goto ERROR;
+ }
+
+ // Iterate over all ports
+ for (;;) {
+ r = sd_bus_message_read(reply, "(so)", &name, &path);
+ if (r < 0)
+ goto ERROR;
+
+ // Break if we reached the end of the container
+ if (r == 0)
+ break;
+
+ // Call the callback
+ r = callback(bus, path, name, data);
+ if (r)
+ goto ERROR;
+ }
+
+ // Close the container
+ sd_bus_message_exit_container(reply);
+
+ERROR:
+ if (reply)
+ sd_bus_message_unref(reply);
+ sd_bus_error_free(&error);
+
+ return r;
+}
+
+static int networkctl_port_describe(sd_bus* bus, const char* name, char** text) {
+ sd_bus_message* reply = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ char path[PATH_MAX];
+ const char* t = NULL;
+ int r;
+
+ // Check input
+ if (!name || !text)
+ return -EINVAL;
+
+ // Make port path
+ r = nw_string_format(path, "/org/ipfire/network1/port/%s", name);
+ if (r < 0)
+ goto ERROR;
+
+ // Call Describe
+ r = sd_bus_call_method(bus, "org.ipfire.network1", path,
+ "org.ipfire.network1.Port", "Describe", &error, &reply, "");
+ if (r < 0) {
+ fprintf(stderr, "Describe() call failed: %m\n");
+ goto ERROR;
+ }
+
+ // Read the text
+ r = sd_bus_message_read(reply, "s", &t);
+ if (r < 0) {
+ fprintf(stderr, "Could not parse bus message: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ // Copy text to heap
+ *text = strdup(t);
+ if (!*text)
+ r = -errno;
+
+ERROR:
+ if (reply)
+ sd_bus_message_unref(reply);
+
+ return r;
+}
+
+// Dump
+
+static int networkctl_port_dump(sd_bus* bus, int argc, char* argv[]) {
+ char* text = NULL;
+ int r;
+
+ if (argc < 1) {
+ fprintf(stderr, "Port required\n");
+ return -EINVAL;
+ }
+
+ // Describe the port
+ r = networkctl_port_describe(bus, argv[0], &text);
+ if (r < 0)
+ return r;
+
+ // Print the text
+ printf("%s\n", text);
+
+ if (text)
+ free(text);
+
+ return 0;
+}
+
+// List
+
+static int __networkctl_port_list(sd_bus* bus, const char* path, const char* name, void* data) {
+ printf("%s\n", name);
+
+ return 0;
+}
+
+static int networkctl_port_list(sd_bus* bus, int argc, char* argv[]) {
+ return networkctl_port_walk(bus, __networkctl_port_list, NULL);
+}
+
+// Show
+
+#define SHOW_LINE " %-12s : %s\n"
+
+static int __networkctl_port_show(sd_bus* bus, const char* path, const char* name, void* data) {
+ struct json_object* object = NULL;
+ enum json_tokener_error json_error;
+ char* describe = NULL;
+ int r;
+
+ // Describe this port
+ r = networkctl_port_describe(bus, name, &describe);
+ if (r < 0)
+ goto ERROR;
+
+ // Parse JSON
+ object = json_tokener_parse_verbose(describe, &json_error);
+ if (!object) {
+ fprintf(stderr, "Could not parse port %s: %s\n",
+ name, json_tokener_error_desc(json_error));
+ return -EINVAL;
+ }
+
+ // Show headline
+ printf("Port %s%s%s\n", color_highlight(), name, color_reset());
+
+ // Show type
+ const char* type = json_object_fetch_string(object, "Type");
+ if (type)
+ printf(SHOW_LINE, "Type", type);
+
+ // Show address
+ const char* address = json_object_fetch_string(object, "Address");
+ if (address)
+ printf(SHOW_LINE, "Address", address);
+
+ // Show an empty line at the end
+ printf("\n");
+
+ // Success!
+ r = 0;
+
+ERROR:
+ if (describe)
+ free(describe);
+ if (object)
+ json_object_unref(object);
+
+ return r;
+}
+
+static int networkctl_port_show(sd_bus* bus, int argc, char* argv[]) {
+ switch (argc) {
+ case 0:
+ return networkctl_port_walk(bus, __networkctl_port_show, NULL);
+
+ case 1:
+ return __networkctl_port_show(bus, NULL, argv[0], NULL);
+
+ default:
+ fprintf(stderr, "Too many arguments\n");
+ return 1;
+ }
+}
+
+int networkctl_port(sd_bus* bus, int argc, char* argv[]) {
+ static const struct command commands[] = {
+ { "dump", 0, networkctl_port_dump },
+ { "list", 0, networkctl_port_list },
+ { "show", 0, networkctl_port_show },
+ { NULL },
+ };
+
+ return command_dispatch(bus, commands, argc, argv);
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKCTL_PORT_H
+#define NETWORKCTL_PORT_H
+
+int networkctl_port(sd_bus* bus, int argc, char* argv[]);
+
+#endif /* NETWORKCTL_PORT_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "terminal.h"
+
+// Cache the color mode
+static color_mode_t __color_mode = COLORS_UNKNOWN;
+
+static color_mode_t detect_color_mode(void) {
+ const char* s = NULL;
+
+ // Check for NO_COLOR and if found turn off colours
+ s = secure_getenv("NO_COLOR");
+ if (s)
+ return COLORS_OFF;
+
+ // Disable colours if this isn't an interactive terminal
+ if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO))
+ return COLORS_OFF;
+
+ // Otherwise we enable colours
+ return COLORS_ON;
+}
+
+color_mode_t color_mode() {
+ if (__color_mode == COLORS_UNKNOWN) {
+ __color_mode = detect_color_mode();
+ }
+
+ return __color_mode;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKCTL_TERMINAL_H
+#define NETWORKCTL_TERMINAL_H
+
+typedef enum color_mode {
+ COLORS_UNKNOWN = 0,
+ COLORS_OFF,
+ COLORS_ON,
+} color_mode_t;
+
+// Reset
+#define COLOR_RESET "\x1B[0m"
+
+// Highlight
+#define COLOR_HIGHLIGHT "\x1B[0;1;39m"
+
+// Regular Colors
+#define COLOR_BLACK "\x1B[0;30m"
+#define COLOR_RED "\x1B[0;31m"
+#define COLOR_GREEN "\x1B[0;32m"
+#define COLOR_YELLOW "\x1B[0;33m"
+#define COLOR_BLUE "\x1B[0;34m"
+#define COLOR_MAGENTA "\x1B[0;35m"
+#define COLOR_CYAN "\x1B[0;36m"
+#define COLOR_WHITE "\x1B[0;37m"
+
+#define COLOR_BRIGHT_BLACK "\x1B[0;90m"
+#define COLOR_BRIGHT_RED "\x1B[0;91m"
+#define COLOR_BRIGHT_GREEN "\x1B[0;92m"
+#define COLOR_BRIGHT_YELLOW "\x1B[0;93m"
+#define COLOR_BRIGHT_BLUE "\x1B[0;94m"
+#define COLOR_BRIGHT_MAGENTA "\x1B[0;95m"
+#define COLOR_BRIGHT_CYAN "\x1B[0;96m"
+#define COLOR_BRIGHT_WHITE "\x1B[0;97m"
+
+#define COLOR_HIGHLIGHT_BLACK "\x1B[0;1;30m"
+#define COLOR_HIGHLIGHT_RED "\x1B[0;1;31m"
+#define COLOR_HIGHLIGHT_GREEN "\x1B[0;1;32m"
+#define COLOR_HIGHLIGHT_YELLOW "\x1B[0;1;33m"
+#define COLOR_HIGHLIGHT_BLUE "\x1B[0;1;34m"
+#define COLOR_HIGHLIGHT_MAGENTA "\x1B[0;1;35m"
+#define COLOR_HIGHLIGHT_CYAN "\x1B[0;1;36m"
+#define COLOR_HIGHLIGHT_WHITE "\x1B[0;1;37m"
+
+// Returns the color mode
+color_mode_t color_mode(void);
+
+#define COLOR_FUNC(name, color) \
+ static inline const char* color_##name(void) { \
+ return (color_mode() == COLORS_ON) ? COLOR_ ## color : ""; \
+ }
+
+COLOR_FUNC(reset, RESET)
+COLOR_FUNC(highlight, HIGHLIGHT)
+COLOR_FUNC(black, BLACK)
+COLOR_FUNC(red, RED)
+COLOR_FUNC(green, GREEN)
+COLOR_FUNC(yellow, YELLOW)
+COLOR_FUNC(blue, BLUE)
+COLOR_FUNC(magenta, MAGENTA)
+COLOR_FUNC(cyan, CYAN)
+COLOR_FUNC(white, WHITE)
+
+#endif /* NETWORKCTL_TERMINAL_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <systemd/sd-bus.h>
+
+#include "command.h"
+#include "zone.h"
+
+typedef int (*networkctl_zone_walk_callback)
+ (sd_bus* bus, const char* path, const char* name, void* data);
+
+static int networkctl_zone_walk(sd_bus* bus,
+ networkctl_zone_walk_callback callback, void* data) {
+ sd_bus_message* reply = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ // Call ListZones
+ r = sd_bus_call_method(bus, "org.ipfire.network1", "/org/ipfire/network1",
+ "org.ipfire.network1", "ListZones", &error, &reply, "");
+ if (r < 0) {
+ fprintf(stderr, "ListZones call failed: %m\n");
+ goto ERROR;
+ }
+
+ const char* name = NULL;
+ const char* path = NULL;
+
+ // Open the container
+ r = sd_bus_message_enter_container(reply, 'a', "(so)");
+ if (r < 0) {
+ fprintf(stderr, "Could not open container: %m\n");
+ goto ERROR;
+ }
+
+ // Iterate over all zones
+ for (;;) {
+ r = sd_bus_message_read(reply, "(so)", &name, &path);
+ if (r < 0)
+ goto ERROR;
+
+ // Break if we reached the end of the container
+ if (r == 0)
+ break;
+
+ // Call the callback
+ r = callback(bus, path, name, data);
+ if (r)
+ goto ERROR;
+ }
+
+ // Close the container
+ sd_bus_message_exit_container(reply);
+
+ERROR:
+ if (reply)
+ sd_bus_message_unref(reply);
+ sd_bus_error_free(&error);
+
+ return r;
+}
+
+static int __networkctl_zone_list(sd_bus* bus, const char* path, const char* name, void* data) {
+ printf("%s\n", name);
+
+ return 0;
+}
+
+static int networkctl_zone_list(sd_bus* bus, int argc, char* argv[]) {
+ return networkctl_zone_walk(bus, __networkctl_zone_list, NULL);
+}
+
+int networkctl_zone(sd_bus* bus, int argc, char* argv[]) {
+ static const struct command commands[] = {
+ { "list", 0, networkctl_zone_list },
+ { NULL },
+ };
+
+ return command_dispatch(bus, commands, argc, argv);
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKCTL_ZONE_H
+#define NETWORKCTL_ZONE_H
+
+int networkctl_zone(sd_bus* bus, int argc, char* argv[]);
+
+#endif /* NETWORKCTL_ZONE_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_ADDRESS_H
+#define NETWORKD_ADDRESS_H
+
+#include <errno.h>
+#include <netinet/ether.h>
+#include <string.h>
+#include <sys/random.h>
+
+#include "logging.h"
+
+typedef struct ether_addr nw_address_t;
+
+enum {
+ NW_ADDRESS_MULTICAST = (1 << 0),
+ NW_ADDRESS_SOFTWAREASSIGNED = (1 << 1),
+};
+
+static inline int nw_address_from_string(nw_address_t* addr, const char* s) {
+ if (!s)
+ return -EINVAL;
+
+ struct ether_addr* p = ether_aton_r(s, addr);
+ if (!p)
+ return -errno;
+
+ return 0;
+}
+
+static inline char* nw_address_to_string(const nw_address_t* addr) {
+ char buffer[20];
+
+ char* p = ether_ntoa_r(addr, buffer);
+ if (!p)
+ return NULL;
+
+ return strdup(buffer);
+}
+
+static inline int nw_address_generate(nw_address_t* addr) {
+ ssize_t bytes = getrandom(addr, sizeof(*addr), 0);
+ if (bytes < 0) {
+ ERROR("getrandom() failed: %m\n");
+ return 1;
+ }
+
+ // Check if we filled the entire buffer
+ if (bytes < (ssize_t)sizeof(*addr)) {
+ ERROR("Could not gather enough randomness\n");
+ return 1;
+ }
+
+ // Clear the multicast bit
+ addr->ether_addr_octet[0] &= ~NW_ADDRESS_MULTICAST;
+
+ // Set the software-generated bit
+ addr->ether_addr_octet[0] |= NW_ADDRESS_SOFTWAREASSIGNED;
+
+ return 0;
+}
+
+static inline int nw_address_is_multicast(const nw_address_t* addr) {
+ return (addr->ether_addr_octet[0] & NW_ADDRESS_MULTICAST);
+}
+
+#endif /* NETWORKD_ADDRESS_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <stdlib.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-event.h>
+
+#include "bus.h"
+#include "daemon.h"
+#include "daemon-bus.h"
+#include "logging.h"
+
+static int nw_bus_on_connect(sd_bus_message* m, void* data, sd_bus_error* error) {
+ nw_daemon* daemon = (nw_daemon*)data;
+
+ DEBUG("Connected to D-Bus\n");
+
+ return 0;
+}
+
+int nw_bus_connect(sd_bus** bus, sd_event* loop, nw_daemon* daemon) {
+ sd_bus* b = NULL;
+ int r;
+
+ // Create a bus object
+ r = sd_bus_new(&b);
+ if (r < 0) {
+ ERROR("Could not allocate a bus object: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Set description
+ r = sd_bus_set_description(b, NETWORKD_BUS_DESCRIPTION);
+ if (r < 0) {
+ ERROR("Could not set bus description: %s\n", strerror(-r));
+ return 1;
+ }
+
+ const char* address = secure_getenv("DBUS_SYSTEM_BUS_ADDRESS");
+ if (!address)
+ address = DEFAULT_SYSTEM_BUS_ADDRESS;
+
+ // Set bus address
+ r = sd_bus_set_address(b, address);
+ if (r < 0) {
+ ERROR("Could not set bus address: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Set bus client
+ r = sd_bus_set_bus_client(b, 1);
+ if (r < 0) {
+ ERROR("Could not set bus client: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Request some credentials for all messages
+ r = sd_bus_negotiate_creds(b, 1,
+ SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS);
+ if (r < 0) {
+ ERROR("Could not negotiate creds: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Automatically bind when the socket is available
+ r = sd_bus_set_watch_bind(b, 1);
+ if (r < 0) {
+ ERROR("Could not watch socket: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Emit a connected signal when we are connected
+ r = sd_bus_set_connected_signal(b, 1);
+ if (r < 0) {
+ ERROR("Could not enable sending a connect signal: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Connect to the bus
+ r = sd_bus_start(b);
+ if (r < 0) {
+ ERROR("Could not connect to bus: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Register the implementation
+ r = nw_bus_register_implementation(b, &daemon_bus_impl, daemon);
+ if (r)
+ return r;
+
+ // Request interface name
+ r = sd_bus_request_name_async(b, NULL, "org.ipfire.network1", 0, NULL, NULL);
+ if (r < 0) {
+ ERROR("Could not request bus name: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Attach the event loop
+ r = sd_bus_attach_event(b, loop, 0);
+ if (r < 0) {
+ ERROR("Could not attach bus to event loop: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Request receiving a connect signal
+ r = sd_bus_match_signal_async(b, NULL, "org.freedesktop.DBus.Local",
+ NULL, "org.freedesktop.DBus.Local", "Connected", nw_bus_on_connect, NULL, NULL);
+ if (r < 0) {
+ ERROR("Could not request match on Connected signal: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Return reference
+ *bus = b;
+
+ return 0;
+}
+
+int nw_bus_register_implementation(sd_bus* bus,
+ const struct nw_bus_implementation* impl, void* data) {
+ int r;
+
+ DEBUG("Registering bus object implementation for path=%s iface=%s\n",
+ impl->path, impl->interface);
+
+ // Register vtables
+ for (const sd_bus_vtable** vtable = impl->vtables; vtable && *vtable; vtable++) {
+ r = sd_bus_add_object_vtable(bus, NULL, impl->path, impl->interface, *vtable, data);
+ if (r < 0) {
+ ERROR("Could not register bus path %s with interface %s: %m\n",
+ impl->path, impl->interface);
+ return 1;
+ }
+ }
+
+ // Register fallback vtables
+ for (const struct nw_bus_vtable_pair* p = impl->fallback_vtables; p && p->vtable; p++) {
+ r = sd_bus_add_fallback_vtable(bus, NULL, impl->path, impl->interface,
+ p->vtable, p->object_find, data);
+ if (r < 0) {
+ ERROR("Could not register bus path %s with interface %s: %m\n",
+ impl->path, impl->interface);
+ return 1;
+ }
+ }
+
+ // Register the node enumerator
+ if (impl->node_enumerator) {
+ r = sd_bus_add_node_enumerator(bus, NULL, impl->path, impl->node_enumerator, data);
+ if (r < 0) {
+ ERROR("Could not add the node enumerator for %s: %m\n", impl->path);
+ return 1;
+ }
+ }
+
+ // Register any child implementations
+ for (int i = 0; impl->children && impl->children[i]; i++) {
+ r = nw_bus_register_implementation(bus, impl->children[i], data);
+ if (r)
+ return r;
+ }
+
+ return 0;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_BUS_H
+#define NETWORKD_BUS_H
+
+#define NETWORKD_BUS_DESCRIPTION "networkd"
+
+#define DEFAULT_SYSTEM_BUS_ADDRESS "unix:path=/run/dbus/system_bus_socket"
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-event.h>
+
+#include "daemon.h"
+
+int nw_bus_connect(sd_bus** bus, sd_event* loop, nw_daemon* daemon);
+
+struct nw_bus_vtable_pair {
+ const sd_bus_vtable* vtable;
+ sd_bus_object_find_t object_find;
+};
+
+typedef struct nw_bus_implementation {
+ const char* path;
+ const char* interface;
+ const sd_bus_vtable** vtables;
+ const struct nw_bus_vtable_pair* fallback_vtables;
+ sd_bus_node_enumerator_t node_enumerator;
+ const struct nw_bus_implementation** children;
+} nw_bus_implementation;
+
+#define BUS_FALLBACK_VTABLES(...) ((const struct nw_bus_vtable_pair[]) { __VA_ARGS__, {} })
+#define BUS_IMPLEMENTATIONS(...) ((const nw_bus_implementation* []) { __VA_ARGS__, NULL })
+#define BUS_VTABLES(...) ((const sd_bus_vtable* []){ __VA_ARGS__, NULL })
+
+int nw_bus_register_implementation(sd_bus* bus,
+ const nw_bus_implementation* impl, void* data);
+
+#endif /* NETWORKD_BUS_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "address.h"
+#include "config.h"
+#include "logging.h"
+#include "string.h"
+
+struct nw_config_entry {
+ STAILQ_ENTRY(nw_config_entry) nodes;
+
+ char key[NETWORK_CONFIG_KEY_MAX_LENGTH];
+ char value[NETWORK_CONFIG_KEY_MAX_LENGTH];
+};
+
+struct nw_config_option {
+ STAILQ_ENTRY(nw_config_option) nodes;
+
+ const char* key;
+ void* value;
+ size_t length;
+
+ // Callbacks
+ nw_config_option_read_callback_t read_callback;
+ nw_config_option_write_callback_t write_callback;
+ void* data;
+};
+
+struct nw_config {
+ int nrefs;
+
+ STAILQ_HEAD(config_entries, nw_config_entry) entries;
+
+ // Options
+ STAILQ_HEAD(parser_entries, nw_config_option) options;
+};
+
+static void nw_config_entry_free(struct nw_config_entry* entry) {
+ free(entry);
+}
+
+static void nw_config_option_free(struct nw_config_option* option) {
+ free(option);
+}
+
+static struct nw_config_entry* nw_config_entry_create(
+ nw_config* config, const char* key) {
+ int r;
+
+ // Check input value
+ if (!key) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ // Allocate a new object
+ struct nw_config_entry* entry = calloc(1, sizeof(*entry));
+ if (!entry)
+ return NULL;
+
+ // Store the key
+ r = nw_string_set(entry->key, key);
+ if (r)
+ goto ERROR;
+
+ // Append the new entry
+ STAILQ_INSERT_TAIL(&config->entries, entry, nodes);
+
+ return entry;
+
+ERROR:
+ nw_config_entry_free(entry);
+ return NULL;
+}
+
+static void nw_config_free(nw_config* config) {
+ struct nw_config_option* option = NULL;
+
+ // Flush all entries
+ nw_config_flush(config);
+
+ // Free all options
+ while (!STAILQ_EMPTY(&config->options)) {
+ option = STAILQ_FIRST(&config->options);
+ STAILQ_REMOVE_HEAD(&config->options, nodes);
+
+ // Free the options
+ nw_config_option_free(option);
+ }
+
+ free(config);
+}
+
+int nw_config_create(nw_config** config, FILE* f) {
+ int r;
+
+ nw_config* c = calloc(1, sizeof(*c));
+ if (!c)
+ return 1;
+
+ // Initialize reference counter
+ c->nrefs = 1;
+
+ // Initialise entries
+ STAILQ_INIT(&c->entries);
+
+ // Initialise options
+ STAILQ_INIT(&c->options);
+
+ // Read configuration
+ if (f) {
+ r = nw_config_read(c, f);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ *config = c;
+
+ return 0;
+
+ERROR:
+ nw_config_free(c);
+
+ return r;
+}
+
+int nw_config_open(nw_config** config, const char* path) {
+ FILE* f = NULL;
+ int r;
+
+ // Open path
+ f = fopen(path, "r");
+ if (!f)
+ return -errno;
+
+ // Create a new configuration
+ r = nw_config_create(config, f);
+
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+nw_config* nw_config_ref(nw_config* config) {
+ config->nrefs++;
+
+ return config;
+}
+
+nw_config* nw_config_unref(nw_config* config) {
+ if (--config->nrefs > 0)
+ return config;
+
+ nw_config_free(config);
+ return NULL;
+}
+
+int nw_config_copy(nw_config* config, nw_config** copy) {
+ struct nw_config_entry* entry = NULL;
+ nw_config* c = NULL;
+ int r;
+
+ // Create a new configuration
+ r = nw_config_create(&c, NULL);
+ if (r)
+ return r;
+
+ // Copy everything
+ STAILQ_FOREACH(entry, &config->entries, nodes) {
+ r = nw_config_set(c, entry->key, entry->value);
+ if (r)
+ goto ERROR;
+ }
+
+ *copy = c;
+ return 0;
+
+ERROR:
+ if (c)
+ nw_config_unref(c);
+
+ return r;
+}
+
+int nw_config_flush(nw_config* config) {
+ struct nw_config_entry* entry = NULL;
+
+ while (!STAILQ_EMPTY(&config->entries)) {
+ entry = STAILQ_FIRST(&config->entries);
+ STAILQ_REMOVE_HEAD(&config->entries, nodes);
+
+ // Free the entry
+ nw_config_entry_free(entry);
+ }
+
+ return 0;
+}
+
+int nw_config_read(nw_config* config, FILE* f) {
+ char* line = NULL;
+ size_t length = 0;
+ int r;
+
+ ssize_t bytes_read = 0;
+
+ char* key = NULL;
+ char* val = NULL;
+
+ for (;;) {
+ // Read the next line
+ bytes_read = getline(&line, &length, f);
+ if (bytes_read < 0)
+ break;
+
+ // Key starts at the beginning of the line
+ key = line;
+
+ // Value starts after '='
+ val = strchr(line, '=');
+
+ // Invalid line without a '=' character
+ if (!val)
+ continue;
+
+ // Split the string
+ *val++ = '\0';
+
+ // Strip any whitespace from value
+ r = nw_string_strip(val);
+ if (r)
+ break;
+
+ // Store the setting
+ r = nw_config_set(config, key, val);
+ if (r)
+ break;
+ }
+
+ if (line)
+ free(line);
+
+ return r;
+}
+
+int nw_config_write(nw_config* config, FILE* f) {
+ struct nw_config_entry* entry = NULL;
+ int r;
+
+ STAILQ_FOREACH(entry, &config->entries, nodes) {
+ // Skip if value is NULL
+ if (!*entry->value)
+ continue;
+
+ // Write the entry
+ r = fprintf(f, "%s=%s\n", entry->key, entry->value);
+ if (r < 0) {
+ ERROR("Failed to write configuration: %m\n");
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static struct nw_config_entry* nw_config_find(nw_config* config, const char* key) {
+ struct nw_config_entry* entry = NULL;
+
+ STAILQ_FOREACH(entry, &config->entries, nodes) {
+ // Key must match
+ if (strcmp(entry->key, key) != 0)
+ continue;
+
+ // Match!
+ return entry;
+ }
+
+ // No match
+ return NULL;
+}
+
+int nw_config_del(nw_config* config, const char* key) {
+ struct nw_config_entry* entry = NULL;
+
+ // Find an entry matching the key
+ entry = nw_config_find(config, key);
+
+ // If there is no entry, there is nothing to do
+ if (!entry)
+ return 0;
+
+ // Otherwise remove the object
+ STAILQ_REMOVE(&config->entries, entry, nw_config_entry, nodes);
+
+ // Free the entry
+ nw_config_entry_free(entry);
+
+ return 0;
+}
+
+const char* nw_config_get(nw_config* config, const char* key) {
+ struct nw_config_entry* entry = nw_config_find(config, key);
+
+ // Return the value if found and set
+ if (entry && *entry->value)
+ return entry->value;
+
+ // Otherwise return NULL
+ return NULL;
+}
+
+int nw_config_set(nw_config* config, const char* key, const char* value) {
+ struct nw_config_entry* entry = NULL;
+
+ // Log the change
+ DEBUG("%p: Setting %s = %s\n", config, key, value);
+
+ // Delete the entry if val is NULL
+ if (!value)
+ return nw_config_del(config, key);
+
+ // Find any existing entries
+ entry = nw_config_find(config, key);
+
+ // Create a new entry if it doesn't exist, yet
+ if (!entry) {
+ entry = nw_config_entry_create(config, key);
+ if (!entry)
+ return 1;
+ }
+
+ // Store the new value
+ return nw_string_set(entry->value, value);
+}
+
+int nw_config_get_int(nw_config* config, const char* key, const int __default) {
+ char* p = NULL;
+ int r;
+
+ const char* value = nw_config_get(config, key);
+
+ // Return zero if not set
+ if (!value)
+ return __default;
+
+ // Parse the input
+ r = strtoul(value, &p, 10);
+
+ // If we have characters following the input, we throw it away
+ if (p)
+ return __default;
+
+ return r;
+}
+
+int nw_config_set_int(nw_config* config, const char* key, const int value) {
+ char __value[1024];
+ int r;
+
+ // Format the value as string
+ r = nw_string_format(__value, "%d", value);
+ if (r)
+ return r;
+
+ return nw_config_set(config, key, __value);
+}
+
+static const char* nw_config_true[] = {
+ "true",
+ "yes",
+ "1",
+ NULL,
+};
+
+int nw_config_get_bool(nw_config* config, const char* key) {
+ const char* value = nw_config_get(config, key);
+
+ // No value indicates false
+ if (!value)
+ return 0;
+
+ // Check if we match any known true words
+ for (const char** s = nw_config_true; *s; s++) {
+ if (strcasecmp(value, *s) == 0)
+ return 1;
+ }
+
+ // No match means false
+ return 0;
+}
+
+int nw_config_set_bool(nw_config* config, const char* key, const int value) {
+ return nw_config_set(config, key, value ? "true" : "false");
+}
+
+/*
+ Directory
+*/
+
+struct nw_configd {
+ int nrefs;
+
+ char path[PATH_MAX];
+ int fd;
+};
+
+static void nw_configd_free(nw_configd* dir) {
+ if (dir->fd >= 0)
+ close(dir->fd);
+
+ free(dir);
+}
+
+static int __nw_configd_create(nw_configd** dir, int fd, const char* path) {
+ nw_configd* d = NULL;
+ int r;
+
+ // Allocate a new object
+ d = calloc(1, sizeof(*d));
+ if (!d)
+ return -errno;
+
+ // Initialize the reference counter
+ d->nrefs = 1;
+
+ // Store the file descriptor
+ d->fd = dup(fd);
+ if (d->fd < 0) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Store path
+ if (path) {
+ r = nw_string_set(d->path, path);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ *dir = d;
+ return 0;
+
+ERROR:
+ nw_configd_free(d);
+ return r;
+}
+
+int nw_configd_create(nw_configd** dir, const char* path) {
+ int fd;
+
+ // Open the directory
+ fd = open(path, O_DIRECTORY);
+ if (fd < 0) {
+ ERROR("Could not open %s: %m\n", path);
+ return -errno;
+ }
+
+ return __nw_configd_create(dir, fd, path);
+}
+
+nw_configd* nw_configd_ref(nw_configd* dir) {
+ dir->nrefs++;
+
+ return dir;
+}
+
+nw_configd* nw_configd_unref(nw_configd* dir) {
+ if (--dir->nrefs > 0)
+ return dir;
+
+ nw_configd_free(dir);
+ return NULL;
+}
+
+static int nw_configd_open(nw_configd* dir, const char* path, int flags) {
+ return openat(dir->fd, path, flags);
+}
+
+FILE* nw_configd_fopen(nw_configd* dir, const char* path, const char* mode) {
+ int fd;
+
+ // Open file
+ fd = nw_configd_open(dir, path, 0);
+ if (fd < 0)
+ return NULL;
+
+ // Return a file handle
+ return fdopen(fd, mode);
+}
+
+int nw_configd_open_config(nw_config** config, nw_configd* dir, const char* path) {
+ FILE* f = NULL;
+ int r;
+
+ // Open the file
+ f = nw_configd_fopen(dir, path, "r");
+ if (!f)
+ return -errno;
+
+ // Create configuration
+ r = nw_config_create(config, f);
+ if (r < 0)
+ goto ERROR;
+
+ERROR:
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+int nw_configd_unlink(nw_configd* dir, const char* path, int flags) {
+ return unlinkat(dir->fd, path, flags);
+}
+
+nw_configd* nw_configd_descend(nw_configd* dir, const char* path) {
+ nw_configd* d = NULL;
+ char p[PATH_MAX];
+ int fd = -1;
+ int r;
+
+ // Join paths
+ r = nw_path_join(p, dir->path, path);
+ if (r < 0)
+ goto ERROR;
+
+ // Open directory
+ fd = nw_configd_open(dir, path, O_DIRECTORY);
+ if (fd < 0) {
+ ERROR("Could not open %s: %m\n", p);
+ goto ERROR;
+ }
+
+ // Create a new config directory object
+ r = __nw_configd_create(&d, fd, p);
+ if (r < 0)
+ goto ERROR;
+
+ERROR:
+ if (fd >= 0)
+ close(fd);
+
+ return d;
+}
+
+int nw_configd_walk(nw_configd* dir, nw_configd_walk_callback callback, void* data) {
+ FILE* f = NULL;
+ DIR* d = NULL;
+ struct dirent* e = NULL;
+ int r;
+
+ // Re-open the directory
+ d = fdopendir(dir->fd);
+ if (!d) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Walk trough everything
+ for (;;) {
+ // Read the next entry
+ e = readdir(d);
+ if (!e)
+ break;
+
+ // Skip anything that is not a regular file
+ if (e->d_type != DT_REG)
+ continue;
+
+ // Skip hidden files
+ if (e->d_name[0] == '.')
+ continue;
+
+ // Open the file
+ f = nw_configd_fopen(dir, e->d_name, "r");
+ if (!f) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Call the callback
+ r = callback(e, f, data);
+ fclose(f);
+
+ if (r < 0)
+ goto ERROR;
+ }
+
+ r = 0;
+
+ERROR:
+ if (d)
+ closedir(d);
+
+ return r;
+}
+
+/*
+ Options
+*/
+
+int nw_config_options_read(nw_config* config) {
+ struct nw_config_option* option = NULL;
+ int r;
+
+ STAILQ_FOREACH(option, &config->options, nodes) {
+ r = option->read_callback(config,
+ option->key, option->value, option->length, option->data);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int nw_config_options_write(nw_config* config) {
+ struct nw_config_option* option = NULL;
+ int r;
+
+ STAILQ_FOREACH(option, &config->options, nodes) {
+ r = option->write_callback(config,
+ option->key, option->value, option->length, option->data);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int nw_config_option_add(nw_config* config,
+ const char* key, void* value, const size_t length,
+ nw_config_option_read_callback_t read_callback,
+ nw_config_option_write_callback_t write_callback, void* data) {
+ // Check input
+ if (!key || !value || !read_callback || !write_callback)
+ return -EINVAL;
+
+ // Allocate a new option
+ struct nw_config_option* option = calloc(1, sizeof(*option));
+ if (!option)
+ return -errno;
+
+ // Set key
+ option->key = key;
+
+ // Set value
+ option->value = value;
+ option->length = length;
+
+ // Set callbacks
+ option->read_callback = read_callback;
+ option->write_callback = write_callback;
+ option->data = data;
+
+ // Append the new option
+ STAILQ_INSERT_TAIL(&config->options, option, nodes);
+
+ return 0;
+}
+
+int nw_config_read_int(nw_config* config,
+ const char* key, void* value, const size_t length, void* data) {
+ // Fetch the value
+ *(int*)value = nw_config_get_int(config, key, -1);
+
+ return 0;
+}
+
+int nw_config_write_int(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data) {
+ return 0;
+}
+
+// String
+
+int nw_config_read_string(nw_config* config,
+ const char* key, void* value, const size_t length, void* data) {
+ // Fetch the value
+ const char* p = nw_config_get(config, key);
+ if (p)
+ *(const char**)value = p;
+
+ return 0;
+}
+
+int nw_config_write_string(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data) {
+ return nw_config_set(config, key, *(const char**)value);
+}
+
+// String Buffer
+
+int nw_config_read_string_buffer(nw_config* config,
+ const char* key, void* value, const size_t length, void* data) {
+ char* string = (char*)value;
+
+ // Fetch the value
+ const char* p = nw_config_get(config, key);
+ if (p)
+ return __nw_string_set(string, length, p);
+
+ return 0;
+}
+
+// String Table
+
+int nw_config_read_string_table(nw_config* config,
+ const char* key, void* value, const size_t length, void* data) {
+ const char* s = NULL;
+ int* v = (int*)value;
+
+ const nw_string_table_t* table = (nw_string_table_t*)data;
+
+ // Fetch the string
+ s = nw_config_get(config, key);
+ if (!s)
+ return -errno;
+
+ // Lookup the string in the table
+ *v = nw_string_table_lookup_id(table, s);
+
+ // If the result is negative, nothing was found
+ if (*v < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+int nw_config_write_string_table(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data) {
+ int* v = (int*)value;
+
+ const nw_string_table_t* table = (nw_string_table_t*)data;
+
+ // Lookup the string
+ const char* s = nw_string_table_lookup_string(table, *v);
+ if (!s)
+ return -errno;
+
+ return nw_config_set(config, key, s);
+}
+
+// Address
+
+int nw_config_read_address(nw_config* config,
+ const char* key, void* value, const size_t length, void* data) {
+ nw_address_t* address = (nw_address_t*)value;
+ int r;
+
+ // Fetch the value
+ const char* p = nw_config_get(config, key);
+ if (!p)
+ return -EINVAL;
+
+ r = nw_address_from_string(address, p);
+ if (r < 0)
+ ERROR("Could not parse address: %s\n", p);
+
+ return r;
+}
+
+int nw_config_write_address(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data) {
+ const nw_address_t* address = (nw_address_t*)value;
+ int r;
+
+ // Format the address to string
+ char* p = nw_address_to_string(address);
+ if (!p)
+ return -errno;
+
+ // Store the value
+ r = nw_config_set(config, key, p);
+ if (r < 0)
+ goto ERROR;
+
+ // Success
+ r = 0;
+
+ERROR:
+ if (p)
+ free(p);
+
+ return r;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_CONFIG_H
+#define NETWORKD_CONFIG_H
+
+#include <dirent.h>
+#include <stdio.h>
+
+#define NETWORK_CONFIG_KEY_MAX_LENGTH 128
+#define NETWORK_CONFIG_VALUE_MAX_LENGTH 2048
+
+typedef struct nw_config nw_config;
+
+int nw_config_create(nw_config** config, FILE* f);
+int nw_config_open(nw_config** config, const char* path);
+
+nw_config* nw_config_ref(nw_config* config);
+nw_config* nw_config_unref(nw_config* config);
+
+int nw_config_copy(nw_config* config, nw_config** copy);
+
+int nw_config_flush(nw_config* config);
+
+int nw_config_read(nw_config* config, FILE* f);
+int nw_config_write(nw_config* config, FILE* f);
+
+int nw_config_del(nw_config* config, const char* key);
+
+const char* nw_config_get(nw_config* config, const char* key);
+int nw_config_set(nw_config* config, const char* key, const char* value);
+
+int nw_config_get_int(nw_config* config, const char* key, const int __default);
+int nw_config_set_int(nw_config* config, const char* key, const int value);
+
+int nw_config_get_bool(nw_config* config, const char* key);
+int nw_config_set_bool(nw_config* config, const char* key, const int value);
+
+/*
+ Directory
+*/
+
+typedef struct nw_configd nw_configd;
+
+int nw_configd_create(nw_configd** dir, const char* path);
+
+nw_configd* nw_configd_ref(nw_configd* dir);
+nw_configd* nw_configd_unref(nw_configd* dir);
+
+FILE* nw_configd_fopen(nw_configd* dir, const char* path, const char* mode);
+int nw_configd_open_config(nw_config** config, nw_configd* dir, const char* path);
+int nw_configd_unlink(nw_configd* dir, const char* path, int flags);
+
+nw_configd* nw_configd_descend(nw_configd* dir, const char* path);
+
+typedef int (*nw_configd_walk_callback)(struct dirent* entry, FILE* f, void* data);
+
+int nw_configd_walk(nw_configd* dir, nw_configd_walk_callback callback, void* data);
+
+/*
+ Options
+*/
+
+int nw_config_options_read(nw_config* config);
+int nw_config_options_write(nw_config* config);
+
+typedef int (*nw_config_option_read_callback_t)
+ (nw_config* config, const char* key, void* value, const size_t length, void* data);
+typedef int (*nw_config_option_write_callback_t)
+ (nw_config* config, const char* key, const void* value, const size_t length, void* data);
+
+int nw_config_option_add(nw_config* config, const char* key, void* value, const size_t length,
+ nw_config_option_read_callback_t read_callback,
+ nw_config_option_write_callback_t write_callback, void* data);
+
+#define NW_CONFIG_OPTION(config, key, value, length, read_callback, write_callback, data) \
+ nw_config_option_add(config, key, value, length, read_callback, write_callback, data)
+
+// String
+
+#define NW_CONFIG_OPTION_STRING(config, key, value) \
+ nw_config_option_add(config, key, value, nw_config_read_string, nw_config_write_string, NULL)
+
+int nw_config_read_string(nw_config* config,
+ const char* key, void* value, const size_t length, void* data);
+int nw_config_write_string(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data);
+
+#define NW_CONFIG_OPTION_STRING_BUFFER(config, key, value) \
+ nw_config_option_add(config, key, value, sizeof(value), \
+ nw_config_read_string_buffer, nw_config_write_string_buffer, NULL)
+
+int nw_config_read_string_buffer(nw_config* config,
+ const char* key, void* value, const size_t length, void* data);
+#define nw_config_write_string_buffer nw_config_write_string
+
+// String Table
+
+#define NW_CONFIG_OPTION_STRING_TABLE(config, key, value, table) \
+ nw_config_option_add(config, key, value, 0, \
+ nw_config_read_string_table, nw_config_write_string_table, (void*)table)
+
+int nw_config_read_string_table(nw_config* config,
+ const char* key, void* value, const size_t length, void* data);
+int nw_config_write_string_table(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data);
+
+// Integer
+
+#define NW_CONFIG_OPTION_INT(config, key, value) \
+ nw_config_option_add(config, key, value, 0, nw_config_read_int, nw_config_write_int, NULL)
+
+int nw_config_read_int(nw_config* config,
+ const char* key, void* value, const size_t length, void* data);
+int nw_config_write_int(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data);
+
+// Address
+
+#define NW_CONFIG_OPTION_ADDRESS(config, key, value) \
+ nw_config_option_add(config, key, value, 0, nw_config_read_address, nw_config_write_address, NULL)
+
+int nw_config_read_address(nw_config* config,
+ const char* key, void* value, const size_t length, void* data);
+int nw_config_write_address(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data);
+
+#endif /* NETWORKD_CONFIG_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <stdlib.h>
+
+#include <systemd/sd-bus.h>
+
+#include "bus.h"
+#include "daemon.h"
+#include "logging.h"
+#include "port-bus.h"
+#include "zone-bus.h"
+#include "zones.h"
+
+static int nw_daemon_bus_reload(sd_bus_message* m, void* data, sd_bus_error* error) {
+ nw_daemon* daemon = (nw_daemon*)data;
+
+ // Reload the daemon
+ nw_daemon_reload(daemon);
+
+ // Respond with an empty message
+ return sd_bus_reply_method_return(m, NULL);
+}
+
+static int __nw_daemon_bus_list_ports(nw_daemon* daemon, nw_port* port, void* data) {
+ sd_bus_message* reply = (sd_bus_message*)data;
+ int r;
+
+ // Fetch port name
+ const char* name = nw_port_name(port);
+
+ // Fetch bus path
+ char* path = nw_port_bus_path(port);
+
+ // Append the port to the message
+ r = sd_bus_message_append(reply, "(so)", name, path);
+ if (r < 0)
+ goto ERROR;
+
+ // Success
+ r = 0;
+
+ERROR:
+ if (path)
+ free(path);
+
+ return r;
+}
+
+static int nw_daemon_bus_list_ports(sd_bus_message* m, void* data, sd_bus_error* error) {
+ nw_daemon* daemon = (nw_daemon*)data;
+ sd_bus_message* reply = NULL;
+ int r;
+
+ // Form a reply message
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ goto ERROR;
+
+ r = sd_bus_message_open_container(reply, 'a', "(so)");
+ if (r < 0)
+ goto ERROR;
+
+ r = nw_daemon_ports_walk(daemon, __nw_daemon_bus_list_ports, reply);
+ if (r < 0)
+ goto ERROR;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ goto ERROR;
+
+ // Send the reply
+ r = sd_bus_send(NULL, reply, NULL);
+
+ERROR:
+ if (reply)
+ sd_bus_message_unref(reply);
+
+ return r;
+}
+
+static int __nw_daemon_bus_list_zones(nw_daemon* daemon, nw_zone* zone, void* data) {
+ sd_bus_message* reply = (sd_bus_message*)data;
+ int r;
+
+ // Fetch zone name
+ const char* name = nw_zone_name(zone);
+
+ // Fetch bus path
+ char* path = nw_zone_bus_path(zone);
+
+ // Append the zone to the message
+ r = sd_bus_message_append(reply, "(so)", name, path);
+ if (r < 0)
+ goto ERROR;
+
+ // Success
+ r = 0;
+
+ERROR:
+ if (path)
+ free(path);
+
+ return r;
+}
+
+static int nw_daemon_bus_list_zones(sd_bus_message* m, void* data, sd_bus_error* error) {
+ nw_daemon* daemon = (nw_daemon*)data;
+ sd_bus_message* reply = NULL;
+ int r;
+
+ // Form a reply message
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ goto ERROR;
+
+ r = sd_bus_message_open_container(reply, 'a', "(so)");
+ if (r < 0)
+ goto ERROR;
+
+ r = nw_daemon_zones_walk(daemon, __nw_daemon_bus_list_zones, reply);
+ if (r < 0)
+ goto ERROR;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ goto ERROR;
+
+ // Send the reply
+ r = sd_bus_send(NULL, reply, NULL);
+
+ERROR:
+ if (reply)
+ sd_bus_message_unref(reply);
+
+ return r;
+}
+
+static const sd_bus_vtable daemon_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD_WITH_ARGS("ListPorts", SD_BUS_NO_ARGS, SD_BUS_RESULT("a(so)", links),
+ nw_daemon_bus_list_ports, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("ListZones", SD_BUS_NO_ARGS, SD_BUS_RESULT("a(so)", links),
+ nw_daemon_bus_list_zones, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("Reload", SD_BUS_NO_ARGS, SD_BUS_NO_RESULT,
+ nw_daemon_bus_reload, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_VTABLE_END,
+};
+
+const nw_bus_implementation daemon_bus_impl = {
+ .path = "/org/ipfire/network1",
+ .interface = "org.ipfire.network1",
+ .vtables = BUS_VTABLES(daemon_vtable),
+ .children = BUS_IMPLEMENTATIONS(&port_bus_impl, &zone_bus_impl),
+};
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_DAEMON_BUS_H
+#define NETWORKD_DAEMON_BUS_H
+
+#include "bus.h"
+
+extern const nw_bus_implementation daemon_bus_impl;
+
+#endif /* NETWORKD_DAEMON_BUS_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-device.h>
+#include <systemd/sd-event.h>
+#include <systemd/sd-netlink.h>
+
+#include "bus.h"
+#include "config.h"
+#include "daemon.h"
+#include "devmon.h"
+#include "link.h"
+#include "links.h"
+#include "logging.h"
+#include "ports.h"
+#include "stats-collector.h"
+#include "string.h"
+#include "zone.h"
+#include "zones.h"
+
+// Increase the receive buffer to 128 MiB
+#define RCVBUF_SIZE 128 * 1024 * 1024
+
+struct nw_daemon {
+ int nrefs;
+
+ // Configuration
+ nw_configd* configd;
+ nw_config* config;
+
+ // Event Loop
+ sd_event* loop;
+
+ // Netlink Connection
+ sd_netlink* rtnl;
+
+ // DBus Connection
+ sd_bus* bus;
+
+ // udev Connection
+ sd_device_monitor* devmon;
+
+ // Links
+ nw_links* links;
+
+ // Zones
+ nw_zones* zones;
+
+ // Ports
+ nw_ports* ports;
+
+ // Stats Collector
+ sd_event_source* stats_collector_event;
+};
+
+static int __nw_daemon_terminate(sd_event_source* source, const struct signalfd_siginfo* si,
+ void* data) {
+ DEBUG("Received signal to terminate...\n");
+
+ return sd_event_exit(sd_event_source_get_event(source), 0);
+}
+
+static int __nw_daemon_reload(sd_event_source* source, const struct signalfd_siginfo* si,
+ void* data) {
+ nw_daemon* daemon = (nw_daemon*)daemon;
+
+ DEBUG("Received signal to reload...\n");
+
+ // Reload the daemon
+ nw_daemon_reload(daemon);
+
+ return 0;
+}
+
+/*
+ Configuration
+*/
+
+static int nw_daemon_config_open(nw_daemon* daemon, const char* path) {
+ int r;
+
+ // Open the configuration directory
+ r = nw_configd_create(&daemon->configd, path);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int nw_daemon_parse_argv(nw_daemon* daemon, int argc, char* argv[]) {
+ enum {
+ ARG_CONFIG,
+ };
+ int r;
+
+ static const struct option options[] = {
+ { "config", required_argument, NULL, ARG_CONFIG },
+ { NULL },
+ };
+ int c;
+
+ for (;;) {
+ c = getopt_long(argc, argv, "", options, NULL);
+ if (c < 0)
+ break;
+
+ switch (c) {
+ case ARG_CONFIG:
+ r = nw_daemon_config_open(daemon, optarg);
+ if (r < 0)
+ return r;
+ break;
+
+ // Abort on any unrecognised option
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int nw_daemon_setup_loop(nw_daemon* daemon) {
+ int r;
+
+ // Fetch a reference to the default event loop
+ r = sd_event_default(&daemon->loop);
+ if (r < 0) {
+ ERROR("Could not setup event loop: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Enable the watchdog
+ r = sd_event_set_watchdog(daemon->loop, 1);
+ if (r < 0) {
+ ERROR("Could not activate watchdog: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Listen for SIGTERM
+ r = sd_event_add_signal(daemon->loop, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK,
+ __nw_daemon_terminate, daemon);
+ if (r < 0) {
+ ERROR("Could not register handling SIGTERM: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Listen for SIGINT
+ r = sd_event_add_signal(daemon->loop, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK,
+ __nw_daemon_terminate, daemon);
+ if (r < 0) {
+ ERROR("Could not register handling SIGINT: %s\n", strerror(-r));
+ return 1;
+ }
+
+ // Listen for SIGHUP
+ r = sd_event_add_signal(daemon->loop, NULL, SIGHUP|SD_EVENT_SIGNAL_PROCMASK,
+ __nw_daemon_reload, daemon);
+ if (r < 0) {
+ ERROR("Could not register handling SIGHUP: %s\n", strerror(-r));
+ return 1;
+ }
+
+ return 0;
+}
+
+static int nw_daemon_load_config(nw_daemon* daemon) {
+ int r;
+
+ // If no configuration path has been opened yet, we will open something
+ if (!daemon->configd) {
+ r = nw_daemon_config_open(daemon, CONFIG_DIR);
+ if (r < 0)
+ return r;
+ }
+
+ // Open the configuration file
+ return nw_configd_open_config(&daemon->config, daemon->configd, "settings");
+}
+
+static int nw_start_device_monitor(nw_daemon* daemon) {
+ int r;
+
+ const char* subsystems[] = {
+ "net",
+ "ieee80211",
+ "rfkill",
+ NULL,
+ };
+
+ // Create a new connection to monitor any devices
+ r = sd_device_monitor_new(&daemon->devmon);
+ if (r < 0) {
+ ERROR("Could not inititalize the device monitor: %m\n");
+ return 1;
+ }
+
+ // Increase the receive buffer
+ r = sd_device_monitor_set_receive_buffer_size(daemon->devmon, RCVBUF_SIZE);
+ if (r < 0) {
+ ERROR("Could not increase buffer size for the device monitor: %m\n");
+ return 1;
+ }
+
+ // Filter for events for all relevant subsystems
+ for (const char** subsystem = subsystems; *subsystem; subsystem++) {
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(
+ daemon->devmon, *subsystem, NULL);
+ if (r < 1) {
+ ERROR("Could not add device monitor for the %s subsystem: %m\n", *subsystem);
+ return 1;
+ }
+ }
+
+ // Attach the device monitor to the event loop
+ r = sd_device_monitor_attach_event(daemon->devmon, daemon->loop);
+ if (r < 0) {
+ ERROR("Could not attach the device monitor to the event loop: %m\n");
+ return 1;
+ }
+
+ // Start processing events...
+ r = sd_device_monitor_start(daemon->devmon, nw_devmon_handle_uevent, daemon);
+ if (r < 0) {
+ ERROR("Could not start the device monitor: %m\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static int nw_daemon_connect_rtnl(nw_daemon* daemon, int fd) {
+ int r;
+
+ // Connect to Netlink
+ r = sd_netlink_open(&daemon->rtnl);
+ if (r < 0) {
+ ERROR("Could not connect to the kernel's netlink interface: %m\n");
+ return 1;
+ }
+
+ // Increase the receive buffer
+ r = sd_netlink_increase_rxbuf(daemon->rtnl, RCVBUF_SIZE);
+ if (r < 0) {
+ ERROR("Could not increase receive buffer for the netlink socket: %m\n");
+ return 1;
+ }
+
+ // Connect it to the event loop
+ r = sd_netlink_attach_event(daemon->rtnl, daemon->loop, 0);
+ if (r < 0) {
+ ERROR("Could not connect the netlink socket to the event loop: %m\n");
+ return 1;
+ }
+
+ // Register callback for new interfaces
+ r = sd_netlink_add_match(daemon->rtnl, NULL, RTM_NEWLINK, nw_link_process, NULL,
+ daemon, "networkd-RTM_NEWLINK");
+ if (r < 0) {
+ ERROR("Could not register RTM_NEWLINK: %m\n");
+ return 1;
+ }
+
+ // Register callback for deleted interfaces
+ r = sd_netlink_add_match(daemon->rtnl, NULL, RTM_DELLINK, nw_link_process, NULL,
+ daemon, "networkd-RTM_DELLINK");
+ if (r < 0) {
+ ERROR("Could not register RTM_DELLINK: %m\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static int nw_daemon_enumerate_links(nw_daemon* daemon) {
+ int r;
+
+ // Create a new links container
+ r = nw_links_create(&daemon->links, daemon);
+ if (r)
+ return r;
+
+ return nw_links_enumerate(daemon->links);
+}
+
+static int nw_daemon_enumerate_ports(nw_daemon* daemon) {
+ int r;
+
+ // Create a new ports container
+ r = nw_ports_create(&daemon->ports, daemon);
+ if (r)
+ return r;
+
+ return nw_ports_enumerate(daemon->ports);
+}
+
+static int nw_daemon_enumerate_zones(nw_daemon* daemon) {
+ int r;
+
+ // Create a new zones container
+ r = nw_zones_create(&daemon->zones, daemon);
+ if (r)
+ return r;
+
+ return nw_zones_enumerate(daemon->zones);
+}
+
+static int nw_daemon_enumerate(nw_daemon* daemon) {
+ int r;
+
+ // Links
+ r = nw_daemon_enumerate_links(daemon);
+ if (r)
+ return r;
+
+ // Ports
+ r = nw_daemon_enumerate_ports(daemon);
+ if (r)
+ return r;
+
+ // Zones
+ r = nw_daemon_enumerate_zones(daemon);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+static int __nw_daemon_reconfigure(sd_event_source* s, void* data) {
+ nw_daemon* daemon = (nw_daemon*)data;
+ int r;
+
+ DEBUG("Reconfiguring...\n");
+
+ // Reconfigure all zones
+ r = nw_zones_reconfigure(daemon->zones);
+ if (r)
+ return r;
+
+ // Reconfigure all ports
+ r = nw_ports_reconfigure(daemon->ports);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+static int nw_daemon_reconfigure(nw_daemon* daemon) {
+ int r;
+
+ r = sd_event_add_defer(daemon->loop, NULL, __nw_daemon_reconfigure, daemon);
+ if (r) {
+ ERROR("Could not schedule re-configuration task: %m\n");
+ return r;
+ }
+
+ return 0;
+}
+
+static int nw_daemon_starts_stats_collector(nw_daemon* daemon) {
+ sd_event_source* s = NULL;
+ int r;
+
+ // Register the stats collector main function
+ r = sd_event_add_time_relative(daemon->loop, &s, CLOCK_MONOTONIC, 0, 0,
+ nw_stats_collector, daemon);
+ if (r < 0) {
+ ERROR("Could not start the stats collector: %m\n");
+ goto ERROR;
+ }
+
+ // Keep calling the stats collector for forever
+ r = sd_event_source_set_enabled(s, SD_EVENT_ON);
+ if (r < 0)
+ goto ERROR;
+
+ // Keep a reference to the event source
+ daemon->stats_collector_event = sd_event_source_ref(s);
+
+ERROR:
+ if (s)
+ sd_event_source_unref(s);
+
+ return r;
+}
+
+static int nw_daemon_setup(nw_daemon* daemon) {
+ int r;
+
+ // Read the configuration
+ r = nw_daemon_load_config(daemon);
+ if (r)
+ return r;
+
+ // Setup the event loop
+ r = nw_daemon_setup_loop(daemon);
+ if (r)
+ return r;
+
+ // Connect to the kernel's netlink interface
+ r = nw_daemon_connect_rtnl(daemon, 0);
+ if (r)
+ return r;
+
+ // Connect to the system bus
+ r = nw_bus_connect(&daemon->bus, daemon->loop, daemon);
+ if (r)
+ return r;
+
+ // Connect to udev
+ r = nw_start_device_monitor(daemon);
+ if (r)
+ return r;
+
+ // Enumerate everything we need to know
+ r = nw_daemon_enumerate(daemon);
+ if (r)
+ return r;
+
+ // (Re-)configure everything
+ r = nw_daemon_reconfigure(daemon);
+ if (r)
+ return r;
+
+ // Start the stats collector
+ r = nw_daemon_starts_stats_collector(daemon);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+int nw_daemon_create(nw_daemon** daemon, int argc, char* argv[]) {
+ int r;
+
+ nw_daemon* d = calloc(1, sizeof(*d));
+ if (!d)
+ return 1;
+
+ // Initialize reference counter
+ d->nrefs = 1;
+
+ // Parse command line arguments
+ r = nw_daemon_parse_argv(d, argc, argv);
+ if (r)
+ goto ERROR;
+
+ // Setup the daemon
+ r = nw_daemon_setup(d);
+ if (r)
+ goto ERROR;
+
+ // Set the reference
+ *daemon = d;
+
+ return 0;
+
+ERROR:
+ nw_daemon_unref(d);
+
+ return r;
+}
+
+static void nw_daemon_cleanup(nw_daemon* daemon) {
+ if (daemon->ports)
+ nw_ports_unref(daemon->ports);
+ if (daemon->zones)
+ nw_zones_unref(daemon->zones);
+ if (daemon->links)
+ nw_links_unref(daemon->links);
+ if (daemon->config)
+ nw_config_unref(daemon->config);
+}
+
+static void nw_daemon_free(nw_daemon* daemon) {
+ // Cleanup common objects
+ nw_daemon_cleanup(daemon);
+
+ if (daemon->configd)
+ nw_configd_unref(daemon->configd);
+ if (daemon->stats_collector_event)
+ sd_event_source_unref(daemon->stats_collector_event);
+ if (daemon->bus)
+ sd_bus_unref(daemon->bus);
+ if (daemon->loop)
+ sd_event_unref(daemon->loop);
+
+ free(daemon);
+}
+
+nw_daemon* nw_daemon_ref(nw_daemon* daemon) {
+ daemon->nrefs++;
+
+ return daemon;
+}
+
+nw_daemon* nw_daemon_unref(nw_daemon* daemon) {
+ if (--daemon->nrefs > 0)
+ return daemon;
+
+ nw_daemon_free(daemon);
+ return NULL;
+}
+
+/*
+ This function contains the main loop of the daemon...
+*/
+int nw_daemon_run(nw_daemon* daemon) {
+ int r;
+
+ // We are now ready to process any requests
+ sd_notify(0, "READY=1\n" "STATUS=Processing requests...");
+
+ // Launch the event loop
+ r = sd_event_loop(daemon->loop);
+ if (r < 0) {
+ ERROR("Could not run the event loop: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ // Let systemd know that we are shutting down
+ sd_notify(0, "STOPPING=1\n" "STATUS=Shutting down...");
+
+ // Save the configuration
+ r = nw_daemon_save(daemon);
+ if (r)
+ goto ERROR;
+
+ // Cleanup everything
+ nw_daemon_cleanup(daemon);
+
+ return 0;
+
+ERROR:
+ sd_notifyf(0, "ERRNO=%i", -r);
+
+ // Cleanup everything
+ nw_daemon_cleanup(daemon);
+
+ return 1;
+}
+
+int nw_daemon_reload(nw_daemon* daemon) {
+ DEBUG("Reloading daemon...\n");
+
+ // XXX TODO
+
+ return 0;
+}
+
+/*
+ Saves the configuration to disk
+*/
+int nw_daemon_save(nw_daemon* daemon) {
+ int r;
+
+ DEBUG("Saving configuration...\n");
+
+#if 0
+ // Save settings
+ r = nw_config_write(daemon->config, f);
+ if (r)
+ return r;
+#endif
+
+ // Save ports
+ r = nw_ports_save(daemon->ports);
+ if (r)
+ return r;
+
+ // Save zones
+ r = nw_zones_save(daemon->zones);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+nw_configd* nw_daemon_configd(nw_daemon* daemon, const char* path) {
+ if (!daemon->configd)
+ return NULL;
+
+ if (path)
+ return nw_configd_descend(daemon->configd, path);
+
+ return nw_configd_ref(daemon->configd);
+}
+
+/*
+ Bus
+*/
+sd_bus* nw_daemon_get_bus(nw_daemon* daemon) {
+ return daemon->bus;
+}
+
+/*
+ Netlink
+*/
+sd_netlink* nw_daemon_get_rtnl(nw_daemon* daemon) {
+ return daemon->rtnl;
+}
+
+/*
+ Links
+*/
+nw_links* nw_daemon_links(nw_daemon* daemon) {
+ return nw_links_ref(daemon->links);
+}
+
+void nw_daemon_drop_link(nw_daemon* daemon, nw_link* link) {
+ if (!daemon->links)
+ return;
+
+ nw_links_drop_link(daemon->links, link);
+}
+
+nw_link* nw_daemon_get_link_by_ifindex(nw_daemon* daemon, int ifindex) {
+ if (!daemon->links)
+ return NULL;
+
+ return nw_links_get_by_ifindex(daemon->links, ifindex);
+}
+
+nw_link* nw_daemon_get_link_by_name(nw_daemon* daemon, const char* name) {
+ if (!daemon->links)
+ return NULL;
+
+ return nw_links_get_by_name(daemon->links, name);
+}
+
+/*
+ Ports
+*/
+nw_ports* nw_daemon_ports(nw_daemon* daemon) {
+ return nw_ports_ref(daemon->ports);
+}
+
+int nw_daemon_ports_walk(nw_daemon* daemon, nw_ports_walk_callback callback, void* data) {
+ if (!daemon->ports)
+ return 0;
+
+ return nw_ports_walk(daemon->ports, callback, data);
+}
+
+nw_port* nw_daemon_get_port_by_name(nw_daemon* daemon, const char* name) {
+ if (!daemon->ports)
+ return NULL;
+
+ return nw_ports_get_by_name(daemon->ports, name);
+}
+
+/*
+ Zones
+*/
+
+nw_zones* nw_daemon_zones(nw_daemon* daemon) {
+ return nw_zones_ref(daemon->zones);
+}
+
+int nw_daemon_zones_walk(nw_daemon* daemon, nw_zones_walk_callback callback, void* data) {
+ if (!daemon->zones)
+ return 0;
+
+ return nw_zones_walk(daemon->zones, callback, data);
+}
+
+nw_zone* nw_daemon_get_zone_by_name(nw_daemon* daemon, const char* name) {
+ if (!daemon->zones)
+ return NULL;
+
+ return nw_zones_get_by_name(daemon->zones, name);
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_DAEMON_H
+#define NETWORKD_DAEMON_H
+
+#include <dirent.h>
+#include <stdio.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-netlink.h>
+
+typedef struct nw_daemon nw_daemon;
+
+#include "config.h"
+#include "link.h"
+#include "links.h"
+#include "port.h"
+#include "ports.h"
+#include "zone.h"
+#include "zones.h"
+
+int nw_daemon_create(nw_daemon** daemon, int argc, char* argv[]);
+
+nw_daemon* nw_daemon_ref(nw_daemon* daemon);
+nw_daemon* nw_daemon_unref(nw_daemon* daemon);
+
+int nw_daemon_run(nw_daemon* daemon);
+
+int nw_daemon_reload(nw_daemon* daemon);
+
+int nw_daemon_save(nw_daemon* daemon);
+
+nw_configd* nw_daemon_configd(nw_daemon* daemon, const char* path);
+
+/*
+ Bus
+*/
+sd_bus* nw_daemon_get_bus(nw_daemon* daemon);
+
+/*
+ Netlink
+*/
+sd_netlink* nw_daemon_get_rtnl(nw_daemon* daemon);
+
+/*
+ Links
+*/
+nw_links* nw_daemon_links(nw_daemon* daemon);
+void nw_daemon_drop_link(nw_daemon* daemon, nw_link* link);
+nw_link* nw_daemon_get_link_by_ifindex(nw_daemon* daemon, int ifindex);
+nw_link* nw_daemon_get_link_by_name(nw_daemon* daemon, const char* name);
+
+/*
+ Ports
+*/
+nw_ports* nw_daemon_ports(nw_daemon* daemon);
+int nw_daemon_ports_walk(nw_daemon* daemon, nw_ports_walk_callback callback, void* data);
+nw_port* nw_daemon_get_port_by_name(nw_daemon* daemon, const char* name);
+
+/*
+ Zones
+*/
+nw_zones* nw_daemon_zones(nw_daemon* daemon);
+int nw_daemon_zones_walk(nw_daemon* daemon, nw_zones_walk_callback callback, void* data);
+nw_zone* nw_daemon_get_zone_by_name(nw_daemon* daemon, const char* name);
+
+#endif /* NETWORKD_DAEMON_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <systemd/sd-device.h>
+
+#include "daemon.h"
+#include "devmon.h"
+#include "logging.h"
+
+static int nw_daemon_handle_uevent_net(nw_daemon* daemon,
+ sd_device* device, sd_device_action_t action) {
+ nw_link* link = NULL;
+ int ifindex;
+ int r;
+
+ // Fetch ifindex
+ r = sd_device_get_ifindex(device, &ifindex);
+ if (r < 0) {
+ ERROR("Could not get ifindex from uevent: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ // Fetch the link
+ link = nw_daemon_get_link_by_ifindex(daemon, ifindex);
+ if (!link) {
+ DEBUG("Could not fetch link %d, ignoring\n", ifindex);
+ r = 0;
+ goto ERROR;
+ }
+
+ // Let the link handle its uevent
+ r = nw_link_handle_uevent(link, device, action);
+
+ERROR:
+ if (link)
+ nw_link_unref(link);
+
+ return r;
+}
+
+int nw_devmon_handle_uevent(sd_device_monitor* monitor, sd_device* device, void* data) {
+ sd_device_action_t action;
+ const char* subsystem = NULL;
+ int r;
+
+ // Fetch daemon
+ nw_daemon* daemon = (nw_daemon*)data;
+
+ // Fetch action
+ r = sd_device_get_action(device, &action);
+ if (r < 0) {
+ WARNING("Could not get uevent action, ignoring: %s\n", strerror(-r));
+ return r;
+ }
+
+ // Fetch subsystem
+ r = sd_device_get_subsystem(device, &subsystem);
+ if (r < 0) {
+ ERROR("Could not get uevent subsystem, ignoring: %s\n", strerror(-r));
+ return r;
+ }
+
+ // Handle any links
+ if (strcmp(subsystem, "net") == 0) {
+ r = nw_daemon_handle_uevent_net(daemon, device, action);
+
+ } else {
+ DEBUG("Received an uevent for an unhandled subsystem '%s', ignoring\n", subsystem);
+ return 0;
+ }
+
+ // Log if something went wrong
+ if (r < 0)
+ ERROR("Failed processing uevent: %s\n", strerror(-r));
+
+ return 0;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_DEVMON_H
+#define NETWORKD_DEVMON_H
+
+#include <systemd/sd-device.h>
+
+int nw_devmon_handle_uevent(sd_device_monitor* monitor, sd_device* device, void* data);
+
+#endif /* NETWORKD_DEVMON_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_JSON_H
+#define NETWORKD_JSON_H
+
+#include <errno.h>
+#include <string.h>
+
+#include <json.h>
+
+// Give some sane names to the reference count functions
+#define json_object_ref json_object_get
+#define json_object_unref json_object_put
+
+static inline int __json_object_add(struct json_object* o,
+ const char* key, struct json_object* value) {
+ int r;
+
+ // Add the object
+ r = json_object_object_add(o, key, value);
+ if (r < 0) {
+ if (value)
+ json_object_unref(value);
+ }
+
+ return r;
+}
+
+static inline int json_object_add_string(struct json_object* o,
+ const char* key, const char* value) {
+ struct json_object* element = NULL;
+
+ // Create a JSON object from the string
+ element = json_object_new_string(value);
+ if (!element)
+ return -errno;
+
+ return __json_object_add(o, key, element);
+}
+
+static inline int json_object_add_int64(struct json_object* o,
+ const char* key, const int64_t value) {
+ struct json_object* element = NULL;
+
+ // Create a JSON object
+ element = json_object_new_int64(value);
+ if (!element)
+ return -errno;
+
+ return __json_object_add(o, key, element);
+}
+
+static inline int json_to_string(struct json_object* o, char** s, size_t* l) {
+ // Format JSON to string
+ const char* buffer = json_object_to_json_string_ext(o,
+ JSON_C_TO_STRING_PRETTY|JSON_C_TO_STRING_PRETTY_TAB);
+ if (!buffer)
+ return -errno;
+
+ // Copy the string to the heap
+ *s = strdup(buffer);
+ if (!*s)
+ return -errno;
+
+ // If requested, store the length of the string
+ if (l)
+ *l = strlen(*s);
+
+ return 0;
+}
+
+static inline const char* json_object_fetch_string(
+ struct json_object* o, const char* key) {
+ struct json_object* e = json_object_object_get(o, key);
+ if (!e)
+ return NULL;
+
+ return json_object_get_string(e);
+}
+
+#endif /* NETWORKD_JSON_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <linux/if.h>
+#include <linux/if_link.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <systemd/sd-device.h>
+#include <systemd/sd-netlink.h>
+
+#include "daemon.h"
+#include "json.h"
+#include "link.h"
+#include "links.h"
+#include "logging.h"
+#include "string.h"
+
+struct nw_link {
+ nw_daemon* daemon;
+ int nrefs;
+
+ // Interface Index
+ int ifindex;
+
+ // Interface Name
+ char ifname[IFNAMSIZ];
+
+ enum nw_link_state {
+ NW_LINK_UNKNOWN = 0,
+ NW_LINK_DESTROYED,
+ } state;
+
+ // Device
+ struct sd_device* device;
+
+ // Stats
+ struct rtnl_link_stats64 stats64;
+
+ // MTU
+ uint32_t mtu;
+ uint32_t min_mtu;
+ uint32_t max_mtu;
+
+ // Flags
+ unsigned int flags;
+ uint8_t operstate;
+};
+
+static int nw_link_setup_device(nw_link* link) {
+ int r;
+
+ // Fetch sd-device
+ r = sd_device_new_from_ifindex(&link->device, link->ifindex);
+ if (r < 0) {
+ ERROR("Could not fetch sd-device for link %d: %s\n", link->ifindex, strerror(-r));
+ return r;
+ }
+
+ return 0;
+}
+
+static void nw_link_free(nw_link* link) {
+ DEBUG("Freeing link (ifindex = %d)\n", link->ifindex);
+
+ if (link->device)
+ sd_device_unref(link->device);
+ if (link->daemon)
+ nw_daemon_unref(link->daemon);
+}
+
+int nw_link_create(nw_link** link, nw_daemon* daemon, int ifindex) {
+ int r;
+
+ // Allocate a new object
+ nw_link* l = calloc(1, sizeof(*l));
+ if (!l)
+ return -errno;
+
+ // Store a reference to the daemon
+ l->daemon = nw_daemon_ref(daemon);
+
+ // Initialize the reference counter
+ l->nrefs = 1;
+
+ // Store the ifindex
+ l->ifindex = ifindex;
+
+ DEBUG("New link allocated (ifindex = %d)\n", l->ifindex);
+
+ r = nw_link_setup_device(l);
+ if (r < 0)
+ goto ERROR;
+
+ *link = l;
+ return 0;
+
+ERROR:
+ nw_link_free(l);
+ return r;
+}
+
+nw_link* nw_link_ref(nw_link* link) {
+ link->nrefs++;
+
+ return link;
+}
+
+nw_link* nw_link_unref(nw_link* link) {
+ if (--link->nrefs > 0)
+ return link;
+
+ nw_link_free(link);
+ return NULL;
+}
+
+/*
+ This is a helper function for when we pass a reference to the event loop
+ it will have to dereference the link instance later.
+*/
+static void __nw_link_unref(void* data) {
+ nw_link* link = (nw_link*)data;
+
+ nw_link_unref(link);
+}
+
+int nw_link_ifindex(nw_link* link) {
+ return link->ifindex;
+}
+
+const char* nw_link_ifname(nw_link* link) {
+ // Return NULL if name isn't set
+ if (!*link->ifname)
+ return NULL;
+
+ return link->ifname;
+}
+
+// Stats
+
+const struct rtnl_link_stats64* nw_link_get_stats64(nw_link* link) {
+ return &link->stats64;
+}
+
+static int nw_link_call_getlink(nw_link* link,
+ int (*callback)(sd_netlink* rtnl, sd_netlink_message* m, void* data)) {
+ sd_netlink_message* m = NULL;
+ int r;
+
+ sd_netlink* rtnl = nw_daemon_get_rtnl(link->daemon);
+ if (!rtnl)
+ return 1;
+
+ // Create a new message
+ r = sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, link->ifindex);
+ if (r < 0) {
+ ERROR("Could not allocate RTM_GETLINK message: %m\n");
+ goto ERROR;
+ }
+
+ // Send the message
+ r = sd_netlink_call_async(rtnl, NULL, m, callback,
+ __nw_link_unref, nw_link_ref(link), -1, NULL);
+ if (r < 0) {
+ ERROR("Could not send rtnetlink message: %m\n");
+ goto ERROR;
+ }
+
+ERROR:
+ if (m)
+ sd_netlink_message_unref(m);
+
+ return r;
+}
+
+static int __nw_link_update_stats(sd_netlink* rtnl, sd_netlink_message* m, void* data) {
+ nw_link* link = (nw_link*)data;
+ int r;
+
+ // Fetch the stats
+ r = sd_netlink_message_read(m, IFLA_STATS64, sizeof(link->stats64), &link->stats64);
+ if (r < 0)
+ return r;
+
+ DEBUG("Link %d: Stats updated\n", link->ifindex);
+
+ // Log stats
+ DEBUG(" Packets : RX: %12llu, TX: %12llu\n",
+ link->stats64.rx_packets, link->stats64.tx_packets);
+ DEBUG(" Bytes : RX: %12llu, TX: %12llu\n",
+ link->stats64.rx_bytes, link->stats64.tx_bytes);
+ DEBUG(" Errors : RX: %12llu, TX: %12llu\n",
+ link->stats64.rx_errors, link->stats64.tx_errors);
+ DEBUG(" Dropped : RX: %12llu, TX: %12llu\n",
+ link->stats64.rx_dropped, link->stats64.rx_dropped);
+ DEBUG(" Multicast : %llu\n", link->stats64.multicast);
+ DEBUG(" Collisions : %llu\n", link->stats64.collisions);
+
+ // Notify ports that stats have been updated
+ r = nw_daemon_ports_walk(link->daemon, __nw_port_update_stats, link);
+ if (r)
+ return r;
+
+ // Notify zones that stats have been updated
+ r = nw_daemon_zones_walk(link->daemon, __nw_zone_update_stats, link);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+int nw_link_update_stats(nw_link* link) {
+ return nw_link_call_getlink(link, __nw_link_update_stats);
+}
+
+// Carrier
+
+int nw_link_has_carrier(nw_link* link) {
+ return link->operstate == IF_OPER_UP;
+}
+
+static int nw_link_carrier_gained(nw_link* link) {
+ return 0; // XXX TODO
+}
+
+static int nw_link_carrier_lost(nw_link* link) {
+ return 0; // XXX TODO
+}
+
+static int nw_link_initialize(nw_link* link) {
+ sd_device *device = NULL;
+ int r;
+
+ // Fetch device
+ r = sd_device_new_from_ifindex(&device, link->ifindex);
+ if (r < 0) {
+ WARNING("Could not find device for link %d: %s\n", link->ifindex, strerror(-r));
+ r = 0;
+ goto ERROR;
+ }
+
+ // Check if device is initialized
+ r = sd_device_get_is_initialized(device);
+ switch (r) {
+ // Initialized - fallthrough
+ case 0:
+ break;
+
+ case 1:
+ DEBUG("The device has not been initialized, yet\n");
+ r = 0;
+ goto ERROR;
+
+ default:
+ WARNING("Could not check whether device is initialized, ignoring: %s\n",
+ strerror(-r));
+ goto ERROR;
+ }
+
+ // XXX Check renaming?!
+
+ if (link->device)
+ sd_device_unref(link->device);
+
+ // Store the device
+ link->device = sd_device_ref(device);
+
+ DEBUG("Link %d has been initialized\n", link->ifindex);
+
+ERROR:
+ if (device)
+ sd_device_unref(device);
+
+ return r;
+}
+
+static int nw_link_update_ifname(nw_link* link, sd_netlink_message* message) {
+ const char* ifname = NULL;
+ int r;
+
+ r = sd_netlink_message_read_string(message, IFLA_IFNAME, &ifname);
+ if (r < 0) {
+ ERROR("Could not read link name for link %d: %m\n", link->ifindex);
+ return 1;
+ }
+
+ // Do nothing if the name is already set
+ if (strcmp(link->ifname, ifname) == 0)
+ return 0;
+
+ // Otherwise update the name
+ r = nw_string_set(link->ifname, ifname);
+ if (r) {
+ ERROR("Could not set link name: %m\n");
+ return 1;
+ }
+
+ DEBUG("Link %d has been renamed to '%s'\n", link->ifindex, link->ifname);
+
+ // Assign link to ports
+ nw_daemon_ports_walk(link->daemon, __nw_port_set_link, link);
+
+ // Assign link to zones
+ nw_daemon_zones_walk(link->daemon, __nw_zone_set_link, link);
+
+ return 0;
+}
+
+static int nw_link_update_mtu(nw_link* link, sd_netlink_message* message) {
+ uint32_t mtu = 0;
+ uint32_t min_mtu = 0;
+ uint32_t max_mtu = 0;
+ int r;
+
+ // Read the MTU
+ r = sd_netlink_message_read_u32(message, IFLA_MTU, &mtu);
+ if (r < 0) {
+ ERROR("Could not read MTU for link %d: %m\n", link->ifindex);
+ return r;
+ }
+
+ // Read the minimum MTU
+ r = sd_netlink_message_read_u32(message, IFLA_MIN_MTU, &min_mtu);
+ if (r < 0) {
+ ERROR("Could not read the minimum MTU for link %d: %m\n", link->ifindex);
+ return r;
+ }
+
+ // Read the maximum MTU
+ r = sd_netlink_message_read_u32(message, IFLA_MAX_MTU, &max_mtu);
+ if (r < 0) {
+ ERROR("Could not read the maximum MTU for link %d: %m\n", link->ifindex);
+ return r;
+ }
+
+ // Set the maximum MTU to infinity
+ if (!max_mtu)
+ max_mtu = UINT32_MAX;
+
+ // Store min/max MTU
+ link->min_mtu = min_mtu;
+ link->max_mtu = max_mtu;
+
+ // End here, if the MTU has not been changed
+ if (link->mtu == mtu)
+ return 0;
+
+ DEBUG("Link %d: MTU has changed to %" PRIu32 " (min: %" PRIu32 ", max: %" PRIu32 ")\n",
+ link->ifindex, link->mtu, link->min_mtu, link->max_mtu);
+
+ // Store MTU
+ link->mtu = mtu;
+
+ return 0;
+}
+
+static int nw_link_update_flags(nw_link* link, sd_netlink_message* message) {
+ unsigned int flags = 0;
+ uint8_t operstate = 0;
+ int r;
+
+ // Fetch flags
+ r = sd_rtnl_message_link_get_flags(message, &flags);
+ if (r < 0) {
+ DEBUG("Could not read link flags: %m\n");
+ return 1;
+ }
+
+#if 0
+ // Fetch operstate
+ r = sd_netlink_message_read_u8(message, IFLA_OPERSTATE, &operstate);
+ if (r < 1) {
+ ERROR("Could not read operstate: %m\n");
+ return 1;
+ }
+#endif
+
+ // End here if there have been no changes
+ if (link->flags == flags && link->operstate == operstate)
+ return 0;
+
+ // XXX We should log any changes here
+
+ // Fetch current carrier state
+ const int had_carrier = nw_link_has_carrier(link);
+
+ // Store the new flags & operstate
+ link->flags = flags;
+ link->operstate = operstate;
+
+ // Notify if carrier was gained or lost
+ if (!had_carrier && nw_link_has_carrier(link)) {
+ r = nw_link_carrier_gained(link);
+ if (r < 0)
+ return r;
+
+ } else if (had_carrier && !nw_link_has_carrier(link)) {
+ r = nw_link_carrier_lost(link);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+/*
+ This function is called whenever anything changes, so that we can
+ update our internal link object.
+*/
+static int nw_link_update(nw_link* link, sd_netlink_message* message) {
+ int r;
+
+ // Update the interface name
+ r = nw_link_update_ifname(link, message);
+ if (r)
+ return r;
+
+ // Update the MTU
+ r = nw_link_update_mtu(link, message);
+ if (r)
+ return r;
+
+ // Update flags
+ r = nw_link_update_flags(link, message);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
+ nw_links* links = NULL;
+ nw_link* link = NULL;
+ const char* ifname = NULL;
+ int ifindex;
+ uint16_t type;
+ int r;
+
+ nw_daemon* daemon = (nw_daemon*)data;
+
+ // Fetch links
+ links = nw_daemon_links(daemon);
+ if (!links) {
+ r = 1;
+ goto ERROR;
+ }
+
+ // Check if this message could be received
+ if (sd_netlink_message_is_error(message)) {
+ r = sd_netlink_message_get_errno(message);
+ if (r < 0)
+ ERROR("Could not receive link message: %m\n");
+
+ goto IGNORE;
+ }
+
+ // Fetch the message type
+ r = sd_netlink_message_get_type(message, &type);
+ if (r < 0) {
+ ERROR("Could not fetch message type: %m\n");
+ goto IGNORE;
+ }
+
+ // Check type
+ switch (type) {
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ break;
+
+ default:
+ ERROR("Received an unexpected message (type %u)\n", type);
+ goto IGNORE;
+ }
+
+ // Fetch the interface index
+ r = sd_rtnl_message_link_get_ifindex(message, &ifindex);
+ if (r < 0) {
+ ERROR("Could not fetch ifindex: %m\n");
+ goto IGNORE;
+ }
+
+ // Check interface index
+ if (ifindex <= 0) {
+ ERROR("Received an invalid ifindex\n");
+ goto IGNORE;
+ }
+
+ // Fetch the interface name
+ r = sd_netlink_message_read_string(message, IFLA_IFNAME, &ifname);
+ if (r < 0) {
+ ERROR("Received a netlink message without interface name: %m\n");
+ goto IGNORE;
+ }
+
+ // Try finding an existing link
+ link = nw_daemon_get_link_by_ifindex(daemon, ifindex);
+
+ switch (type) {
+ case RTM_NEWLINK:
+ // If the link doesn't exist, create it
+ if (!link) {
+ r = nw_link_create(&link, daemon, ifindex);
+ if (r) {
+ ERROR("Could not create link: %m\n");
+ goto ERROR;
+ }
+
+ // Initialize the link
+ r = nw_link_initialize(link);
+ if (r < 0)
+ goto ERROR;
+
+ // Add it to the list
+ r = nw_links_add_link(links, link);
+ if (r)
+ goto ERROR;
+ }
+
+ // Import any data from the netlink message
+ r = nw_link_update(link, message);
+ if (r)
+ goto ERROR;
+
+ break;
+
+ case RTM_DELLINK:
+ if (link)
+ nw_links_drop_link(links, link);
+
+ break;
+ }
+
+IGNORE:
+ r = 0;
+
+ERROR:
+ if (links)
+ nw_links_unref(links);
+ if (link)
+ nw_link_unref(link);
+
+ return r;
+}
+
+static int __nw_link_destroy(sd_netlink* rtnl, sd_netlink_message* m, void* data) {
+ nw_link* link = (nw_link*)data;
+ int r;
+
+ // Check if the operation was successful
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0) {
+ ERROR("Could not remove link %d: %m\n", link->ifindex);
+ // XXX We should extract the error message
+
+ return 0;
+ }
+
+ // Mark this link as destroyed
+ link->state = NW_LINK_DESTROYED;
+
+ return 0;
+}
+
+int nw_link_destroy(nw_link* link) {
+ sd_netlink_message* m = NULL;
+ int r;
+
+ sd_netlink* rtnl = nw_daemon_get_rtnl(link->daemon);
+ if (!rtnl)
+ return 1;
+
+ DEBUG("Destroying link %d\n", link->ifindex);
+
+ // Create a new message
+ r = sd_rtnl_message_new_link(rtnl, &m, RTM_DELLINK, link->ifindex);
+ if (r < 0) {
+ ERROR("Could not allocate RTM_DELLINK message: %m\n");
+ goto ERROR;
+ }
+
+ // Send the message
+ r = sd_netlink_call_async(rtnl, NULL, m, __nw_link_destroy,
+ __nw_link_unref, nw_link_ref(link), -1, NULL);
+ if (r < 0) {
+ ERROR("Could not send rtnetlink message: %m\n");
+ goto ERROR;
+ }
+
+ERROR:
+ if (m)
+ sd_netlink_message_unref(m);
+
+ return r;
+}
+
+// uevent
+
+static int nw_link_uevent_device_is_renaming(sd_device* device) {
+ int r;
+
+ r = sd_device_get_property_value(device, "ID_RENAMING", NULL);
+ switch (r) {
+ case -ENOENT:
+ return 0;
+
+ case 0:
+ return 1;
+
+ default:
+ return r;
+ }
+}
+
+int nw_link_handle_uevent(nw_link* link, sd_device* device, sd_device_action_t action) {
+ int r;
+
+ // Check if the device is renaming
+ r = nw_link_uevent_device_is_renaming(device);
+ switch (r) {
+ // Not renaming - Fallthrough
+ case 0:
+ break;
+
+ case 1:
+ DEBUG("Device is renaming, skipping initialization\n");
+ return 0;
+
+ default:
+ ERROR("Could not determine whether the device is being renamed: %s\n",
+ strerror(-r));
+ return r;
+ }
+
+ // We need to remove or replace the stored device as it is now outdated
+ if (link->device) {
+ sd_device_unref(link->device);
+
+ // If the device has been removed, we remove its reference
+ if (action == SD_DEVICE_REMOVE)
+ link->device = NULL;
+
+ // Otherwise we just store the new one
+ else
+ link->device = sd_device_ref(device);
+ }
+
+ return 0;
+}
+
+// JSON
+
+static int nw_link_device_to_json(nw_link* link, struct json_object* o) {
+ const char* driver = NULL;
+ const char* vendor = NULL;
+ const char* model = NULL;
+ int r;
+
+ // Fetch driver
+ r = sd_device_get_driver(link->device, &driver);
+ if (r < 0 && r != -ENOENT)
+ return r;
+
+ // Add driver
+ if (driver) {
+ r = json_object_add_string(o, "Driver", driver);
+ if (r < 0)
+ return r;
+ }
+
+ // Fetch vendor
+ r = sd_device_get_property_value(link->device, "ID_VENDOR_FROM_DATABASE", &vendor);
+ if (r < 0) {
+ r = sd_device_get_property_value(link->device, "ID_VENDOR", &vendor);
+ if (r < 0 && r != -ENOENT)
+ return r;
+ }
+
+ // Add vendor
+ if (vendor) {
+ r = json_object_add_string(o, "Vendor", vendor);
+ if (r < 0)
+ return r;
+ }
+
+ // Fetch model
+ r = sd_device_get_property_value(link->device, "ID_MODEL_FROM_DATABASE", &model);
+ if (r < 0) {
+ r = sd_device_get_property_value(link->device, "ID_MODEL", &model);
+ if (r < 0 && r != -ENOENT)
+ return r;
+ }
+
+ // Add model
+ if (model) {
+ r = json_object_add_string(o, "Model", model);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int nw_link_to_json(nw_link* link, struct json_object* o) {
+ int r;
+
+ // Add ifindex
+ r = json_object_add_int64(o, "LinkIndex", link->ifindex);
+ if (r < 0)
+ return r;
+
+ // Add sd-device stuff
+ r = nw_link_device_to_json(link, o);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_LINK_H
+#define NETWORKD_LINK_H
+
+#include <linux/if_link.h>
+
+#include <systemd/sd-device.h>
+
+typedef struct nw_link nw_link;
+
+#include "daemon.h"
+#include "json.h"
+
+int nw_link_create(nw_link** link, nw_daemon* daemon, int ifindex);
+
+nw_link* nw_link_ref(nw_link* link);
+nw_link* nw_link_unref(nw_link* link);
+
+int nw_link_ifindex(nw_link* link);
+const char* nw_link_ifname(nw_link* link);
+
+// Stats
+const struct rtnl_link_stats64* nw_link_get_stats64(nw_link* link);
+int nw_link_update_stats(nw_link* link);
+
+int nw_link_has_carrier(nw_link* link);
+
+int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data);
+
+int nw_link_destroy(nw_link* link);
+
+// uevent
+int nw_link_handle_uevent(nw_link* link, sd_device* device, sd_device_action_t action);
+
+// JSON
+int nw_link_to_json(nw_link* link, struct json_object* o);
+
+#endif /* NETWORKD_LINK_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+
+#include "daemon.h"
+#include "logging.h"
+#include "link.h"
+#include "links.h"
+
+struct nw_links_entry {
+ nw_link* link;
+
+ // Link to the other entries
+ STAILQ_ENTRY(nw_links_entry) nodes;
+};
+
+struct nw_links {
+ nw_daemon* daemon;
+ int nrefs;
+
+ // Link Entries
+ STAILQ_HEAD(entries, nw_links_entry) entries;
+
+ // A counter of the link entries
+ unsigned int num;
+};
+
+int nw_links_create(nw_links** links, nw_daemon* daemon) {
+ nw_links* l = calloc(1, sizeof(*l));
+ if (!l)
+ return 1;
+
+ // Store a reference to the daemon
+ l->daemon = nw_daemon_ref(daemon);
+
+ // Initialize the reference counter
+ l->nrefs = 1;
+
+ // Initialize entries
+ STAILQ_INIT(&l->entries);
+
+ // Reference the pointer
+ *links = l;
+
+ return 0;
+}
+
+static void nw_links_free(nw_links* links) {
+ struct nw_links_entry* entry = NULL;
+
+ while (!STAILQ_EMPTY(&links->entries)) {
+ entry = STAILQ_FIRST(&links->entries);
+
+ // Dereference the zone
+ nw_link_unref(entry->link);
+
+ // Remove the entry from the list
+ STAILQ_REMOVE_HEAD(&links->entries, nodes);
+
+ // Free the entry
+ free(entry);
+ }
+
+ if (links->daemon)
+ nw_daemon_unref(links->daemon);
+}
+
+nw_links* nw_links_ref(nw_links* links) {
+ links->nrefs++;
+
+ return links;
+}
+
+nw_links* nw_links_unref(nw_links* links) {
+ if (--links->nrefs > 0)
+ return links;
+
+ nw_links_free(links);
+ return NULL;
+}
+
+static struct nw_links_entry* nw_links_find_link(nw_links* links, const int ifindex) {
+ struct nw_links_entry* entry = NULL;
+
+ STAILQ_FOREACH(entry, &links->entries, nodes) {
+ if (nw_link_ifindex(entry->link) == ifindex)
+ return entry;
+ }
+
+ // No match found
+ return NULL;
+}
+
+int nw_links_add_link(nw_links* links, struct nw_link* link) {
+ // Allocate a new entry
+ struct nw_links_entry* entry = calloc(1, sizeof(*entry));
+ if (!entry)
+ return 1;
+
+ // Reference the link
+ entry->link = nw_link_ref(link);
+
+ // Add it to the list
+ STAILQ_INSERT_TAIL(&links->entries, entry, nodes);
+
+ // Increment the counter
+ links->num++;
+
+ return 0;
+}
+
+void nw_links_drop_link(nw_links* links, struct nw_link* link) {
+ struct nw_links_entry* entry = NULL;
+
+ entry = nw_links_find_link(links, nw_link_ifindex(link));
+ if (!entry)
+ return;
+
+ DEBUG("Dropping link %d\n", nw_link_ifindex(entry->link));
+
+ STAILQ_REMOVE(&links->entries, entry, nw_links_entry, nodes);
+ links->num--;
+
+ // Drop link from all ports
+ nw_daemon_ports_walk(links->daemon, __nw_port_drop_link, link);
+
+ // Drop link from all zones
+ nw_daemon_zones_walk(links->daemon, __nw_zone_drop_link, link);
+}
+
+int nw_links_enumerate(nw_links* links) {
+ sd_netlink_message* req = NULL;
+ sd_netlink_message* res = NULL;
+ int r;
+
+ sd_netlink* rtnl = nw_daemon_get_rtnl(links->daemon);
+ if (!rtnl)
+ return 1;
+
+ r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
+ if (r < 0)
+ return 1;
+
+ r = sd_netlink_message_set_request_dump(req, 1);
+ if (r < 0)
+ return 1;
+
+ r = sd_netlink_call(rtnl, req, 0, &res);
+ if (r < 0)
+ return 1;
+
+ sd_netlink_message* m = res;
+
+ // Iterate through all replies
+ do {
+ r = nw_link_process(rtnl, m, links->daemon);
+ if (r)
+ goto ERROR;
+ } while ((m = sd_netlink_message_next(m)));
+
+ERROR:
+ if (m)
+ sd_netlink_message_unref(m);
+ if (req)
+ sd_netlink_message_unref(req);
+ if (res)
+ sd_netlink_message_unref(res);
+
+ return r;
+}
+
+nw_link* nw_links_get_by_ifindex(nw_links* links, int ifindex) {
+ struct nw_links_entry* entry = NULL;
+
+ entry = nw_links_find_link(links, ifindex);
+ if (!entry)
+ return NULL;
+
+ return nw_link_ref(entry->link);
+}
+
+nw_link* nw_links_get_by_name(nw_links* links, const char* name) {
+ struct nw_links_entry* entry = NULL;
+
+ STAILQ_FOREACH(entry, &links->entries, nodes) {
+ const char* n = nw_link_ifname(entry->link);
+
+ if (strcmp(name, n) == 0)
+ return nw_link_ref(entry->link);
+ }
+
+ // No match found
+ return NULL;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_LINKS_H
+#define NETWORKD_LINKS_H
+
+#include "daemon.h"
+#include "link.h"
+
+typedef struct nw_links nw_links;
+
+int nw_links_create(nw_links** links, nw_daemon* daemon);
+
+nw_links* nw_links_ref(nw_links* links);
+nw_links* nw_links_unref(nw_links* links);
+
+int nw_links_add_link(nw_links* links, nw_link* link);
+void nw_links_drop_link(nw_links* links, nw_link* link);
+
+int nw_links_enumerate(nw_links* links);
+
+nw_link* nw_links_get_by_ifindex(nw_links* links, int ifindex);
+nw_link* nw_links_get_by_name(nw_links* links, const char* name);
+
+#endif /* NETWORKD_LINKS_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <systemd/sd-journal.h>
+
+#include "logging.h"
+
+void nw_log(int priority, const char* file,
+ int line, const char* fn, const char* format, ...) {
+ char* buffer = NULL;
+ va_list args;
+ int r;
+
+ // Format log message
+ va_start(args, format);
+ r = vasprintf(&buffer, format, args);
+ va_end(args);
+ if (r < 0)
+ return;
+
+ // Send message to journald
+ r = sd_journal_send(
+ "MESSAGE=%s", buffer,
+ "PRIORITY=%d", priority,
+
+ // Syslog compat
+ "SYSLOG_IDENTIFIER=networkd",
+
+ // Debugging stuff
+ "ERRNO=%d", errno,
+ "CODE_FILE=%s", file,
+ "CODE_LINE=%d", line,
+ "CODE_FUNC=%s", fn,
+
+ NULL
+ );
+
+ // Fall back to standard output
+ if (r)
+ sd_journal_perror(buffer);
+
+ // Cleanup
+ free(buffer);
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_LOGGING_H
+#define NETWORKD_LOGGING_H
+
+#include <syslog.h>
+
+void nw_log(int priority, const char *file, int line, const char* fn,
+ const char *format, ...) __attribute__((format(printf, 5, 6)));
+
+/*
+ This is just something simple which will work for now...
+*/
+#define INFO(args...) nw_log(LOG_INFO, __FILE__, __LINE__, __FUNCTION__, ## args)
+#define WARNING(args...) nw_log(LOG_WARNING, __FILE__, __LINE__, __FUNCTION__, ## args)
+#define ERROR(args...) nw_log(LOG_ERR, __FILE__, __LINE__, __FUNCTION__, ## args)
+#define DEBUG(args...) nw_log(LOG_DEBUG, __FILE__, __LINE__, __FUNCTION__, ## args)
+
+#endif /* NETWORKD_LOGGING_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <grp.h>
+#include <linux/capability.h>
+#include <pwd.h>
+#include <stddef.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "daemon.h"
+#include "logging.h"
+#include "port.h"
+
+static int cap_acquire_setpcap(void) {
+ cap_flag_value_t value;
+ int r;
+
+ // Fetch current capabilities
+ cap_t caps = cap_get_proc();
+
+ // Check if CAP_SETPCAP is already enabled
+ r = cap_get_flag(caps, CAP_SETPCAP, CAP_EFFECTIVE, &value);
+ if (r) {
+ ERROR("The kernel does not seem to know CAP_SETPCAP: %m\n");
+ goto ERROR;
+ }
+
+ // It CAP_SETPCAP isn't set, we will try to set it
+ if (value != CAP_SET) {
+ const cap_value_t cap = CAP_SETPCAP;
+
+ r = cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, CAP_SET);
+ if (r) {
+ ERROR("Could not set CAP_SETPCAP: %m\n");
+ goto ERROR;
+ }
+
+ // Store capabilities
+ r = cap_set_proc(caps);
+ if (r) {
+ ERROR("Could not acquire effective CAP_SETPCAP capability: %m\n");
+ goto ERROR;
+ }
+ }
+
+ERROR:
+ if (caps)
+ cap_free(caps);
+
+ return r;
+}
+
+static cap_flag_value_t keep_cap(const cap_value_t cap, const cap_value_t* keep_caps) {
+ for (const cap_value_t* c = keep_caps; *c; c++) {
+ if (cap == *c)
+ return CAP_SET;
+ }
+
+ return CAP_CLEAR;
+}
+
+static int drop_capabilities(void) {
+ int r;
+
+ const cap_value_t keep_caps[] = {
+ CAP_NET_ADMIN,
+ CAP_NET_BIND_SERVICE,
+ CAP_NET_BROADCAST,
+ CAP_NET_RAW,
+ 0,
+ };
+
+ // Acquire CAP_SETPCAP
+ r = cap_acquire_setpcap();
+ if (r)
+ return r;
+
+ // Fetch the current set of capabilities
+ cap_t caps = cap_get_proc();
+
+ // Drop all capabilities that we do not need
+ for (cap_value_t cap = 1; cap <= CAP_LAST_CAP; cap++) {
+ // Skip any capabilities we would like to skip
+ cap_flag_value_t flag = keep_cap(cap, keep_caps);
+
+ // Drop the capability from the bounding set
+ if (flag == CAP_CLEAR) {
+ r = prctl(PR_CAPBSET_DROP, cap);
+ if (r) {
+ ERROR("Could not drop capability from the bounding set: %m\n");
+ goto ERROR;
+ }
+ }
+
+ r = cap_set_flag(caps, CAP_INHERITABLE, 1, &cap, CAP_CLEAR);
+ if (r) {
+ ERROR("Could not set capability %d: %m\n", (int)cap);
+ goto ERROR;
+ }
+
+ r = cap_set_flag(caps, CAP_PERMITTED, 1, &cap, flag);
+ if (r) {
+ ERROR("Could not set capability %d: %m\n", (int)cap);
+ goto ERROR;
+ }
+
+ r = cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, flag);
+ if (r) {
+ ERROR("Could not set capability %d: %m\n", (int)cap);
+ goto ERROR;
+ }
+ }
+
+ // Restore capabilities
+ r = cap_set_proc(caps);
+ if (r) {
+ ERROR("Could not restore capabilities: %m\n");
+ goto ERROR;
+ }
+
+ERROR:
+ if (caps)
+ cap_free(caps);
+
+ return r;
+}
+
+static int drop_privileges(const char* user) {
+ struct passwd* passwd = NULL;
+ int r;
+
+ // Fetch the current user
+ uid_t current_uid = getuid();
+
+ // If we have not been launched by root, we will assume that we have already
+ // been launched with a minimal set of privileges.
+ if (current_uid > 0)
+ return 0;
+
+ DEBUG("Dropping privileges...\n");
+
+ // Fetch information about the desired user
+ passwd = getpwnam(user);
+ if (!passwd) {
+ ERROR("Could not find user %s: %m\n", user);
+ return 1;
+ }
+
+ uid_t uid = passwd->pw_uid;
+ gid_t gid = passwd->pw_gid;
+
+ // Change group
+ r = setresgid(gid, gid, gid);
+ if (r) {
+ ERROR("Could not change group to %d: %m\n", gid);
+ return 1;
+ }
+
+ // Set any supplementary groups
+ r = setgroups(0, NULL);
+ if (r) {
+ ERROR("Could not set supplementary groups: %m\n");
+ return 1;
+ }
+
+ // Do not drop any capabilities when we change to the new user
+ r = prctl(PR_SET_KEEPCAPS, 1);
+ if (r) {
+ ERROR("Could not set PR_SET_KEEPCAPS: %m\n");
+ return 1;
+ }
+
+ // Change to the new user
+ r = setresuid(uid, uid, uid);
+ if (r) {
+ ERROR("Could not change user to %d: %m\n", uid);
+ return 1;
+ }
+
+ // Reset PR_SET_KEEPCAPS
+ r = prctl(PR_SET_KEEPCAPS, 0);
+ if (r) {
+ ERROR("Could not set PR_SET_KEEPCAPS: %m\n");
+ return 1;
+ }
+
+ // Drop capabilities
+ r = drop_capabilities();
+ if (r)
+ return r;
+
+ return 0;
+}
+
+int main(int argc, char** argv) {
+ nw_daemon* daemon = NULL;
+ int r;
+
+ // Drop privileges
+ r = drop_privileges("network");
+ if (r)
+ return r;
+
+ // Create the daemon
+ r = nw_daemon_create(&daemon, argc, argv);
+ if (r)
+ return r;
+
+ // Run the daemon
+ r = nw_daemon_run(daemon);
+
+ // Cleanup
+ if (daemon)
+ nw_daemon_unref(daemon);
+
+ return r;
+}
--- /dev/null
+[Unit]
+Description=Network Configuration
+Documentation=man:networkd.service(8)
+
+ConditionCapability=CAP_NET_ADMIN
+DefaultDependencies=no
+# systemd-udevd.service can be dropped once tuntap is moved to netlink
+After=systemd-udevd.service network-pre.target systemd-sysusers.service systemd-sysctl.service
+Before=network.target multi-user.target shutdown.target
+Conflicts=shutdown.target
+Wants=network.target
+
+[Service]
+AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW
+BusName=org.ipfire.network1
+CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW
+DeviceAllow=char-* rw
+ExecStart=@networkdir@/networkd
+FileDescriptorStoreMax=512
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+ProtectProc=invisible
+ProtectClock=yes
+ProtectControlGroups=yes
+ProtectHome=yes
+ProtectKernelLogs=yes
+ProtectKernelModules=yes
+ProtectSystem=strict
+Restart=on-failure
+RestartSec=0
+RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6 AF_PACKET
+RestrictNamespaces=yes
+RestrictRealtime=yes
+RestrictSUIDSGID=yes
+SystemCallArchitectures=native
+SystemCallErrorNumber=EPERM
+SystemCallFilter=@system-service
+Type=notify-reload
+User=network
+WatchdogSec=3min
+
+[Install]
+WantedBy=multi-user.target
+Alias=dbus-org.ipfire.network1.service
--- /dev/null
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+ <policy user="network">
+ <allow own="org.ipfire.network1"/>
+ <allow send_destination="org.ipfire.network1"/>
+ <allow receive_sender="org.ipfire.network1"/>
+ </policy>
+
+ <policy context="default">
+ <allow send_destination="org.ipfire.network1"/>
+ <allow receive_sender="org.ipfire.network1"/>
+ </policy>
+</busconfig>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<policyconfig>
+ <vendor>The IPFire Project</vendor>
+ <vendor_url>https://www.ipfire.org</vendor_url>
+
+ <action id="org.ipfire.network1.reload">
+ <description gettext-domain="systemd">Reload Network Settings</description>
+ <message gettext-domain="network">Authentication is required to reload network settings.</message>
+ <defaults>
+ <allow_any>auth_admin</allow_any>
+ <allow_inactive>auth_admin</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.owner">unix-user:network</annotate>
+ </action>
+</policyconfig>
--- /dev/null
+[D-BUS Service]
+Name=org.ipfire.network1
+Exec=/bin/false
+User=root
+SystemdService=dbus-org.ipfire.network1.service
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <systemd/sd-netlink.h>
+
+#include "config.h"
+#include "daemon.h"
+#include "logging.h"
+#include "port.h"
+#include "port-bonding.h"
+#include "string.h"
+
+const nw_string_table_t nw_port_bonding_mode[] = {
+ { NW_BONDING_MODE_ROUNDROBIN, "round-robin" },
+ { NW_BONDING_MODE_ACTIVEBACKUP, "active-backup" },
+ { NW_BONDING_MODE_XOR, "xor" },
+ { NW_BONDING_MODE_BROADCAST, "broadcast" },
+ { NW_BONDING_MODE_8023AD, "802.3ad" },
+ { NW_BONDING_MODE_TLB, "tlb" },
+ { NW_BONDING_MODE_ALB, "alb" },
+ { -1, NULL },
+};
+
+NW_STRING_TABLE_LOOKUP(nw_port_bonding_mode_t, nw_port_bonding_mode)
+
+static int nw_port_bonding_setup(nw_port* port) {
+ int r;
+
+ // Mode
+ r = NW_CONFIG_OPTION_STRING_TABLE(port->config,
+ "BONDING_MODE", &port->bonding.mode, nw_port_bonding_mode);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int nw_port_bonding_create_link(nw_port* port, sd_netlink_message* m) {
+ int r;
+
+ // Set mode
+ r = sd_netlink_message_append_u8(m, IFLA_BOND_MODE, port->bonding.mode);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int nw_port_bonding_to_json(nw_port* port, struct json_object* o) {
+ int r;
+
+ // Add mode
+ r = json_object_add_string(o, "BondingMode",
+ nw_port_bonding_mode_to_string(port->bonding.mode));
+ if (r < 0)
+ goto ERROR;
+
+ERROR:
+ return r;
+}
+
+const nw_port_type_t nw_port_type_bonding = {
+ .kind = "bond",
+
+ // Configuration
+ .setup = nw_port_bonding_setup,
+
+ // Link
+ .create_link = nw_port_bonding_create_link,
+
+ // JSON
+ .to_json = nw_port_bonding_to_json,
+};
+
+const char* nw_port_bonding_get_mode(nw_port* port) {
+ return nw_port_bonding_mode_to_string(port->bonding.mode);
+}
+
+int nw_port_bonding_set_mode(nw_port* port, const char* mode) {
+ const int m = nw_port_bonding_mode_from_string(mode);
+
+ switch (m) {
+ case NW_BONDING_MODE_ROUNDROBIN:
+ case NW_BONDING_MODE_ACTIVEBACKUP:
+ case NW_BONDING_MODE_XOR:
+ case NW_BONDING_MODE_BROADCAST:
+ case NW_BONDING_MODE_8023AD:
+ case NW_BONDING_MODE_TLB:
+ case NW_BONDING_MODE_ALB:
+ port->bonding.mode = m;
+ break;
+
+ default:
+ ERROR("%s: Unsupported bonding mode '%s'\n", port->name, mode);
+ errno = ENOTSUP;
+ return 1;
+ }
+
+ return 0;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_PORT_BONDING_H
+#define NETWORKD_PORT_BONDING_H
+
+#include <linux/if_bonding.h>
+
+#include "port.h"
+
+typedef enum nw_port_bonding_mode {
+ NW_BONDING_MODE_ROUNDROBIN = BOND_MODE_ROUNDROBIN,
+ NW_BONDING_MODE_ACTIVEBACKUP = BOND_MODE_ACTIVEBACKUP,
+ NW_BONDING_MODE_XOR = BOND_MODE_XOR,
+ NW_BONDING_MODE_BROADCAST = BOND_MODE_BROADCAST,
+ NW_BONDING_MODE_8023AD = BOND_MODE_8023AD,
+ NW_BONDING_MODE_TLB = BOND_MODE_TLB,
+ NW_BONDING_MODE_ALB = BOND_MODE_ALB,
+} nw_port_bonding_mode_t;
+
+struct nw_port_bonding {
+ nw_port_bonding_mode_t mode;
+};
+
+extern const nw_port_type_t nw_port_type_bonding;
+
+const char* nw_port_bonding_get_mode(nw_port* port);
+int nw_port_bonding_set_mode(nw_port* port, const char* mode);
+
+#endif /* NETWORKD_PORT_BONDING_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <errno.h>
+#include <stdlib.h>
+#include <systemd/sd-bus.h>
+
+#include "address.h"
+#include "bus.h"
+#include "daemon.h"
+#include "json.h"
+#include "logging.h"
+#include "port.h"
+#include "port-bus.h"
+#include "ports.h"
+
+static int nw_port_node_enumerator(sd_bus* bus, const char* path, void* data,
+ char*** nodes, sd_bus_error* error) {
+ int r;
+
+ DEBUG("Enumerating ports...\n");
+
+ // Fetch a reference to the daemon
+ nw_daemon* daemon = (nw_daemon*)data;
+
+ // Fetch ports
+ nw_ports* ports = nw_daemon_ports(daemon);
+
+ // Make bus paths for all ports
+ r = nw_ports_bus_paths(ports, nodes);
+ if (r)
+ goto ERROR;
+
+ERROR:
+ nw_ports_unref(ports);
+
+ return r;
+}
+
+static int nw_port_object_find(sd_bus* bus, const char* path, const char* interface,
+ void* data, void** found, sd_bus_error* error) {
+ char* name = NULL;
+ int r;
+
+ // Fetch a reference to the daemon
+ nw_daemon* daemon = (nw_daemon*)data;
+
+ // Decode the path of the requested object
+ r = sd_bus_path_decode(path, "/org/ipfire/network1/port", &name);
+ if (r <= 0)
+ return 0;
+
+ // Find the port
+ nw_port* port = nw_daemon_get_port_by_name(daemon, name);
+ if (!port)
+ return 0;
+
+ // Match!
+ *found = port;
+
+ nw_port_unref(port);
+
+ return 1;
+}
+
+static int nw_port_bus_get_address(sd_bus* bus, const char* path, const char* interface,
+ const char* property, sd_bus_message* reply, void* data, sd_bus_error* error) {
+ nw_port* port = (nw_port*)data;
+ int r;
+
+ // Fetch the address
+ const nw_address_t* address = nw_port_get_address(port);
+
+ // Format the address as a string
+ char* s = nw_address_to_string(address);
+ if (!s) {
+ // XXX How to handle any errors?
+ return 0;
+ }
+
+ // Append the address to the return value
+ r = sd_bus_message_append(reply, "s", s);
+ if (r)
+ goto ERROR;
+
+ERROR:
+ if (s)
+ free(s);
+
+ return r;
+}
+
+static int nw_port_bus_describe(sd_bus_message* message, void* data,
+ sd_bus_error* error) {
+ sd_bus_message* reply = NULL;
+ struct json_object* json = NULL;
+ char* text = NULL;
+ int r;
+
+ nw_port* port = (nw_port*)data;
+
+ // Export all data to JSON
+ r = nw_port_to_json(port, &json);
+ if (r < 0)
+ goto ERROR;
+
+ // Convert JSON to string
+ r = json_to_string(json, &text, NULL);
+ if (r < 0)
+ goto ERROR;
+
+ // Create a reply message
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ goto ERROR;
+
+ r = sd_bus_message_append(reply, "s", text);
+ if (r < 0)
+ goto ERROR;
+
+ // Send the reply
+ r = sd_bus_send(NULL, reply, NULL);
+
+ERROR:
+ if (reply)
+ sd_bus_message_unref(reply);
+ if (text)
+ free(text);
+
+ return r;
+}
+
+static const sd_bus_vtable port_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+
+ // Address
+ SD_BUS_PROPERTY("Address", "s", nw_port_bus_get_address,
+ 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+
+ // Operations
+ SD_BUS_METHOD_WITH_ARGS("Describe", SD_BUS_NO_ARGS, SD_BUS_RESULT("s", json),
+ nw_port_bus_describe, SD_BUS_VTABLE_UNPRIVILEGED),
+
+ SD_BUS_VTABLE_END
+};
+
+const nw_bus_implementation port_bus_impl = {
+ "/org/ipfire/network1/port",
+ "org.ipfire.network1.Port",
+ .fallback_vtables = BUS_FALLBACK_VTABLES({port_vtable, nw_port_object_find}),
+ .node_enumerator = nw_port_node_enumerator,
+};
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_PORT_BUS_H
+#define NETWORKD_PORT_BUS_H
+
+#include "bus.h"
+
+extern const nw_bus_implementation port_bus_impl;
+
+#endif /* NETWORKD_PORT_BUS_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include "port-dummy.h"
+
+const nw_port_type_t nw_port_type_dummy = {
+ .kind = "dummy",
+};
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_PORT_DUMMY_H
+#define NETWORKD_PORT_DUMMY_H
+
+#include "port.h"
+
+extern const nw_port_type_t nw_port_type_dummy;
+
+#endif /* NETWORKD_PORT_DUMMY_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include "config.h"
+#include "json.h"
+#include "port.h"
+#include "port-ethernet.h"
+
+static int nw_port_ethernet_setup(nw_port* port) {
+ int r;
+
+ // Permanent Address
+ r = NW_CONFIG_OPTION_ADDRESS(port->config,
+ "PERMANENT_ADDRESS", &port->ethernet.permanent_address);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int nw_port_ethernet_to_json(nw_port* port, struct json_object* o) {
+ char* address = NULL;
+ int r = 0;
+
+ // Permanent Address
+ address = nw_address_to_string(&port->ethernet.permanent_address);
+ if (address) {
+ r = json_object_add_string(o, "PermanentAddress", address);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ERROR:
+ if (address)
+ free(address);
+
+ return r;
+}
+
+const nw_port_type_t nw_port_type_ethernet = {
+ // Configuration
+ .setup = nw_port_ethernet_setup,
+
+ // JSON
+ .to_json = nw_port_ethernet_to_json,
+};
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_PORT_ETHERNET_H
+#define NETWORKD_PORT_ETHERNET_H
+
+#include "address.h"
+#include "port.h"
+
+struct nw_port_ethernet {
+ // Permanent Address
+ nw_address_t permanent_address;
+};
+
+extern const nw_port_type_t nw_port_type_ethernet;
+
+#endif /* NETWORKD_PORT_ETHERNET_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <linux/veth.h>
+
+#include "json.h"
+#include "port.h"
+#include "port-veth.h"
+
+static int nw_port_veth_setup(nw_port* port) {
+ int r;
+
+ // Peer
+ r = NW_CONFIG_OPTION_STRING_BUFFER(port->config, "VETH_PEER", port->veth.peer);
+ if (r < 0)
+ return 1;
+
+ return 0;
+}
+
+static int nw_port_veth_create_link(nw_port* port, sd_netlink_message* m) {
+ int r;
+
+ // Open the container
+ r = sd_netlink_message_open_container(m, VETH_INFO_PEER);
+ if (r < 0)
+ return r;
+
+ // Set VETH Peer
+ r = sd_netlink_message_append_string(m, IFLA_VLAN_ID, port->veth.peer);
+ if (r < 0)
+ return r;
+
+ // Close the container
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int nw_port_veth_to_json(nw_port* port, struct json_object* o) {
+ int r;
+
+ // Add the VETH Peer
+ r = json_object_add_string(o, "VETHPeer", port->veth.peer);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+const nw_port_type_t nw_port_type_veth = {
+ .kind = "veth",
+
+ // Configuration
+ .setup = nw_port_veth_setup,
+
+ // Link
+ .create_link = nw_port_veth_create_link,
+
+ // JSON
+ .to_json = nw_port_veth_to_json,
+};
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_PORT_VETH_H
+#define NETWORKD_PORT_VETH_H
+
+#include "port.h"
+
+// Maximum length of the peer name
+#define NW_VETH_PEER_MAX 64
+
+struct nw_port_veth {
+ char peer[NW_VETH_PEER_MAX];
+};
+
+extern const nw_port_type_t nw_port_type_veth;
+
+#endif /* NETWORKD_PORT_VETH_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <linux/if_link.h>
+
+#include <systemd/sd-netlink.h>
+
+#include "config.h"
+#include "daemon.h"
+#include "json.h"
+#include "logging.h"
+#include "port.h"
+#include "port-vlan.h"
+#include "string.h"
+
+const nw_string_table_t nw_port_vlan_proto[] = {
+ { NW_VLAN_PROTO_8021Q, "802.1Q" },
+ { NW_VLAN_PROTO_8021AD, "802.1ad" },
+ { -1, NULL },
+};
+
+NW_STRING_TABLE_LOOKUP(nw_port_vlan_proto_t, nw_port_vlan_proto)
+
+static int nw_port_vlan_setup(nw_port* port) {
+ int r;
+
+ // VLAN ID
+ r = NW_CONFIG_OPTION_INT(port->config, "VLAN_ID", &port->vlan.id);
+ if (r < 0)
+ return r;
+
+ // VLAN Protocol
+ r = NW_CONFIG_OPTION_STRING_TABLE(port->config,
+ "VLAN_PROTO", &port->vlan.proto, nw_port_vlan_proto);
+ if (r < 0)
+ return r;
+
+ // Parent Port
+ r = NW_CONFIG_OPTION_STRING_BUFFER(port->config,
+ "VLAN_PARENT", port->vlan.__parent_name);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int nw_port_vlan_validate(nw_port* port) {
+ // Check if the VLAN ID is within range
+ if (port->vlan.id < NW_VLAN_ID_MIN || port->vlan.id > NW_VLAN_ID_MAX) {
+ ERROR("%s: Invalid VLAN ID %d\n", port->name, port->vlan.id);
+ return 1;
+ }
+
+ // Validate protocol
+ switch (port->vlan.proto) {
+ case NW_VLAN_PROTO_8021Q:
+ case NW_VLAN_PROTO_8021AD:
+ break;
+
+ default:
+ ERROR("%p: Invalid VLAN protocol\n", port->name);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int nw_port_vlan_create_link(nw_port* port, sd_netlink_message* m) {
+ int r;
+
+ // Set VLAN ID
+ r = sd_netlink_message_append_u16(m, IFLA_VLAN_ID, port->vlan.id);
+ if (r < 0)
+ return r;
+
+ // Set VLAN protocol
+ r = sd_netlink_message_append_u16(m, IFLA_VLAN_PROTOCOL, htobe16(port->vlan.proto));
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int nw_port_vlan_to_json(nw_port* port, struct json_object* o) {
+ nw_port* parent = NULL;
+ int r;
+
+ // Add the VLAN ID
+ r = json_object_add_int64(o, "VLANId", port->vlan.id);
+ if (r < 0)
+ goto ERROR;
+
+ // Add the VLAN Protocol
+ r = json_object_add_string(o, "VLANProtocol",
+ nw_port_vlan_proto_to_string(port->vlan.proto));
+ if (r < 0)
+ goto ERROR;
+
+ // Fetch the parent
+ parent = nw_port_get_parent_port(port);
+ if (parent) {
+ r = json_object_add_string(o, "VLANParentPort", nw_port_name(parent));
+ if (r < 0)
+ goto ERROR;
+ }
+
+ERROR:
+ if (parent)
+ nw_port_unref(parent);
+
+ return r;
+}
+
+const nw_port_type_t nw_port_type_vlan = {
+ .kind = "vlan",
+
+ // Configuration
+ .setup = nw_port_vlan_setup,
+ .validate = nw_port_vlan_validate,
+
+ .get_parent_port = nw_port_get_vlan_parent,
+
+ // Link
+ .create_link = nw_port_vlan_create_link,
+
+ // JSON
+ .to_json = nw_port_vlan_to_json,
+};
+
+/*
+ VLAN
+*/
+int nw_port_get_vlan_id(nw_port* port) {
+ int r;
+
+ // Check type
+ r = nw_port_check_type(port, NW_PORT_VLAN);
+ if (r < 0)
+ return r;
+
+ return port->vlan.id;
+}
+
+int nw_port_set_vlan_id(nw_port* port, int id) {
+ int r;
+
+ // Check type
+ r = nw_port_check_type(port, NW_PORT_VLAN);
+ if (r < 0)
+ return r;
+
+ // Check if the VLAN ID is within range
+ if (id < NW_VLAN_ID_MIN || id > NW_VLAN_ID_MAX)
+ return -EINVAL;
+
+ // Store the ID
+ port->vlan.id = id;
+
+ DEBUG("Port %s: Set VLAN ID to %d\n", port->name, port->vlan.id);
+
+ return 0;
+}
+
+int nw_port_set_vlan_proto(nw_port* port, const nw_port_vlan_proto_t proto) {
+ switch (proto) {
+ case NW_VLAN_PROTO_8021Q:
+ case NW_VLAN_PROTO_8021AD:
+ port->vlan.proto = proto;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+nw_port* nw_port_get_vlan_parent(nw_port* port) {
+ int r;
+
+ // Check type
+ r = nw_port_check_type(port, NW_PORT_VLAN);
+ if (r < 0)
+ return NULL;
+
+ // Try to find a reference to the parent if none exists
+ if (!port->vlan.parent && *port->vlan.__parent_name)
+ port->vlan.parent = nw_daemon_get_port_by_name(port->daemon, port->vlan.__parent_name);
+
+ if (port->vlan.parent)
+ return nw_port_ref(port->vlan.parent);
+
+ // No port found
+ return NULL;
+}
+
+int nw_port_set_vlan_parent(nw_port* port, nw_port* parent) {
+ int r;
+
+ // Check type
+ r = nw_port_check_type(port, NW_PORT_VLAN);
+ if (r < 0)
+ return r;
+
+ // Reset the former parent name
+ nw_string_empty(port->vlan.__parent_name);
+
+ // Dereference the former parent
+ if (port->vlan.parent) {
+ nw_port_unref(port->vlan.parent);
+ port->vlan.parent = NULL;
+ }
+
+ // Store the new parent
+ if (parent) {
+ port->vlan.parent = nw_port_ref(parent);
+
+ // Store the name
+ nw_string_set(port->vlan.__parent_name, nw_port_name(parent));
+ }
+
+ DEBUG("Port %s: Set VLAN parent to %s\n", port->name, nw_port_name(port->vlan.parent));
+
+ return 0;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_PORT_VLAN_H
+#define NETWORKD_PORT_VLAN_H
+
+#include <arpa/inet.h>
+#include <linux/if_ether.h>
+
+#include "port.h"
+
+typedef enum nw_port_vlan_proto {
+ NW_VLAN_PROTO_8021Q = ETH_P_8021Q,
+ NW_VLAN_PROTO_8021AD = ETH_P_8021AD,
+} nw_port_vlan_proto_t;
+
+// VLAN ID
+#define NW_VLAN_ID_INVALID 0
+#define NW_VLAN_ID_MIN 1
+#define NW_VLAN_ID_MAX 4096
+
+struct nw_port_vlan {
+ nw_port* parent;
+
+ // The VLAN ID
+ int id;
+
+ // Protocol
+ nw_port_vlan_proto_t proto;
+
+ // If the parent has not been read from the configuration we will
+ // save the name and try to find it later.
+ char __parent_name[IFNAMSIZ];
+};
+
+extern const nw_port_type_t nw_port_type_vlan;
+
+// ID
+int nw_port_get_vlan_id(nw_port* port);
+int nw_port_set_vlan_id(nw_port* port, int id);
+
+// Protocol
+int nw_port_set_vlan_proto(nw_port* port, const nw_port_vlan_proto_t proto);
+
+// Parent Port
+nw_port* nw_port_get_vlan_parent(nw_port* port);
+int nw_port_set_vlan_parent(nw_port* port, nw_port* parent);
+
+#endif /* NETWORKD_PORT_VLAN_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <limits.h>
+#include <net/if.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <systemd/sd-bus.h>
+
+#include "address.h"
+#include "config.h"
+#include "json.h"
+#include "link.h"
+#include "logging.h"
+#include "port.h"
+#include "port-bonding.h"
+#include "port-dummy.h"
+#include "port-ethernet.h"
+#include "port-veth.h"
+#include "port-vlan.h"
+#include "stats-collector.h"
+#include "string.h"
+
+static const nw_string_table_t nw_port_type_id[] = {
+ { NW_PORT_BONDING, "bonding" },
+ { NW_PORT_DUMMY, "dummy" },
+ { NW_PORT_ETHERNET, "ethernet" },
+ { NW_PORT_VETH, "veth", },
+ { NW_PORT_VLAN, "vlan" },
+ { -1, NULL },
+};
+
+NW_STRING_TABLE_LOOKUP(nw_port_type_id_t, nw_port_type_id)
+
+static void nw_port_free(nw_port* port) {
+ if (port->link)
+ nw_link_unref(port->link);
+ if (port->config)
+ nw_config_unref(port->config);
+ if (port->daemon)
+ nw_daemon_unref(port->daemon);
+
+ free(port);
+}
+
+static int nw_port_set_link(nw_port* port, nw_link* link) {
+ // Do nothing if the same link is being re-assigned
+ if (port->link == link)
+ return 0;
+
+ // Dereference the former link if set
+ if (port->link)
+ nw_link_unref(port->link);
+
+ // Store the new link
+ if (link) {
+ port->link = nw_link_ref(link);
+
+ DEBUG("Port %s: Assigned link %d\n", port->name, nw_link_ifindex(port->link));
+
+ // Or clear the pointer if no link has been provided
+ } else {
+ port->link = NULL;
+
+ DEBUG("Port %s: Removed link\n", port->name);
+ }
+
+ return 0;
+}
+
+static int nw_port_setup(nw_port* port) {
+ nw_link* link = NULL;
+ int r;
+
+ // Find the link
+ link = nw_daemon_get_link_by_name(port->daemon, port->name);
+ if (link) {
+ r = nw_port_set_link(port, link);
+ if (r)
+ goto ERROR;
+ }
+
+ // Generate a random Ethernet address
+ r = nw_address_generate(&port->address);
+ if (r < 0) {
+ ERROR("Could not generate an Ethernet address: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ // Setup options
+ r = NW_CONFIG_OPTION_ADDRESS(port->config, "ADDRESS", &port->address);
+ if (r < 0)
+ goto ERROR;
+
+ // Call any custom initialization
+ if (NW_PORT_TYPE(port)->setup) {
+ r = NW_PORT_TYPE(port)->setup(port);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ // Parse the configuration
+ r = nw_config_options_read(port->config);
+ if (r < 0) {
+ ERROR("Could not read configuration for port %s: %s\n", port->name, strerror(-r));
+ goto ERROR;
+ }
+
+ERROR:
+ if (link)
+ nw_link_unref(link);
+
+ return r;
+}
+
+static int nw_port_validate(nw_port* port) {
+ int r = 0;
+
+ // Validate the port configuration
+ if (NW_PORT_TYPE(port)->validate) {
+ r = NW_PORT_TYPE(port)->validate(port);
+ if (r < 0)
+ ERROR("Could not check configuration for %s: %s\n", port->name, strerror(-r));
+ }
+
+ return r;
+}
+
+int nw_port_create(nw_port** port, nw_daemon* daemon,
+ const nw_port_type_id_t type, const char* name, nw_config* config) {
+ int r;
+
+ // Allocate a new object
+ nw_port* p = calloc(1, sizeof(*p));
+ if (!p)
+ return -errno;
+
+ // Store a reference to the daemon
+ p->daemon = nw_daemon_ref(daemon);
+
+ // Initialize reference counter
+ p->nrefs = 1;
+
+ // Set operations
+ switch (type) {
+ case NW_PORT_BONDING:
+ p->type = &nw_port_type_bonding;
+ break;
+
+ case NW_PORT_DUMMY:
+ p->type = &nw_port_type_dummy;
+ break;
+
+ case NW_PORT_ETHERNET:
+ p->type = &nw_port_type_ethernet;
+ break;
+
+ case NW_PORT_VETH:
+ p->type = &nw_port_type_veth;
+ break;
+
+ case NW_PORT_VLAN:
+ p->type = &nw_port_type_vlan;
+ break;
+ }
+
+ // Store the name
+ r = nw_string_set(p->name, name);
+ if (r < 0)
+ goto ERROR;
+
+ // Copy the configuration
+ r = nw_config_copy(config, &p->config);
+ if (r < 0)
+ goto ERROR;
+
+ // Setup the port
+ r = nw_port_setup(p);
+ if (r < 0)
+ goto ERROR;
+
+ // Validate the configuration
+ r = nw_port_validate(p);
+ switch (r) {
+ // Configuration is valid
+ case 0:
+ break;
+
+ // Configuration is invalid
+ case 1:
+ ERROR("%s: Invalid configuration\n", p->name);
+ goto ERROR;
+
+ // Error
+ default:
+ goto ERROR;
+ }
+
+ *port = p;
+ return 0;
+
+ERROR:
+ nw_port_free(p);
+ return r;
+}
+
+int nw_port_open(nw_port** port, nw_daemon* daemon, const char* name, FILE* f) {
+ nw_config* config = NULL;
+ int r;
+
+ // Initialize the configuration
+ r = nw_config_create(&config, f);
+ if (r < 0)
+ goto ERROR;
+
+ // Fetch the type
+ const char* type = nw_config_get(config, "TYPE");
+ if (!type) {
+ ERROR("Port %s has no TYPE\n", name);
+ r = -ENOTSUP;
+ goto ERROR;
+ }
+
+ // Create a new port
+ r = nw_port_create(port, daemon, nw_port_type_id_from_string(type), name, config);
+ if (r < 0)
+ goto ERROR;
+
+ERROR:
+ if (config)
+ nw_config_unref(config);
+
+ return r;
+}
+
+nw_port* nw_port_ref(nw_port* port) {
+ port->nrefs++;
+
+ return port;
+}
+
+nw_port* nw_port_unref(nw_port* port) {
+ if (--port->nrefs > 0)
+ return port;
+
+ nw_port_free(port);
+ return NULL;
+}
+
+/*
+ This is a helper function for when we pass a reference to the event loop
+ it will have to dereference the port instance later.
+*/
+static void __nw_port_unref(void* data) {
+ nw_port* port = (nw_port*)data;
+
+ nw_port_unref(port);
+}
+
+int nw_port_destroy(nw_port* port) {
+ nw_configd* configd = NULL;
+ int r;
+
+ DEBUG("Destroying port %s\n", port->name);
+
+ // Destroy the physical link (if exists)
+ if (port->link) {
+ r = nw_link_destroy(port->link);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ // Dereference the port from other ports
+ r = nw_daemon_ports_walk(port->daemon, __nw_port_drop_port, port);
+ if (r < 0)
+ goto ERROR;
+
+ // Dereference the port from other zones
+ r = nw_daemon_zones_walk(port->daemon, __nw_zone_drop_port, port);
+ if (r < 0)
+ goto ERROR;
+
+ // Fetch the configuration directory
+ configd = nw_daemon_configd(port->daemon, "ports");
+ if (configd) {
+ r = nw_configd_unlink(configd, port->name, 0);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ERROR:
+ if (configd)
+ nw_configd_unref(configd);
+
+ return r;
+}
+
+int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
+ nw_port* dropped_port = (nw_port*)data;
+ int r;
+
+ switch (port->type->id) {
+ case NW_PORT_VLAN:
+ if (port->vlan.parent == dropped_port) {
+ r = nw_port_set_vlan_parent(port, NULL);
+ if (r)
+ return r;
+ }
+ break;
+
+ case NW_PORT_BONDING:
+ case NW_PORT_DUMMY:
+ case NW_PORT_ETHERNET:
+ case NW_PORT_VETH:
+ break;
+ }
+
+ return 0;
+}
+
+int nw_port_save(nw_port* port) {
+ nw_configd* configd = NULL;
+ FILE* f = NULL;
+ int r;
+
+ // Fetch configuration directory
+ configd = nw_daemon_configd(port->daemon, "ports");
+ if (!configd) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Open file
+ f = nw_configd_fopen(configd, port->name, "w");
+ if (!f) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Write out the configuration
+ r = nw_config_options_write(port->config);
+ if (r < 0)
+ goto ERROR;
+
+ // Write the configuration
+ r = nw_config_write(port->config, f);
+ if (r < 0)
+ goto ERROR;
+
+ERROR:
+ if (configd)
+ nw_configd_unref(configd);
+ if (f)
+ fclose(f);
+ if (r)
+ ERROR("Could not save configuration for port %s: %s\n", port->name, strerror(-r));
+
+ return r;
+}
+
+const char* nw_port_name(nw_port* port) {
+ return port->name;
+}
+
+char* nw_port_bus_path(nw_port* port) {
+ char* p = NULL;
+ int r;
+
+ // Encode the bus path
+ r = sd_bus_path_encode("/org/ipfire/network1/port", port->name, &p);
+ if (r < 0)
+ return NULL;
+
+ return p;
+}
+
+int __nw_port_set_link(nw_daemon* daemon, nw_port* port, void* data) {
+ nw_link* link = (nw_link*)data;
+
+ // Fetch the link name
+ const char* ifname = nw_link_ifname(link);
+ if (!ifname) {
+ ERROR("Link does not have a name set\n");
+ return 1;
+ }
+
+ // Set link if the name matches
+ if (strcmp(port->name, ifname) == 0)
+ return nw_port_set_link(port, link);
+
+ // If we have the link set but the name did not match, we will remove it
+ else if (port->link == link)
+ return nw_port_set_link(port, NULL);
+
+ return 0;
+}
+
+int __nw_port_drop_link(nw_daemon* daemon, nw_port* port, void* data) {
+ nw_link* link = (nw_link*)data;
+
+ // Drop the link if it matches
+ if (port->link == link)
+ return nw_port_set_link(port, NULL);
+
+ return 0;
+}
+
+static nw_link* nw_port_get_link(nw_port* port) {
+ // Fetch the link if not set
+ if (!port->link)
+ port->link = nw_daemon_get_link_by_name(port->daemon, port->name);
+
+ if (!port->link)
+ return NULL;
+
+ return nw_link_ref(port->link);
+}
+
+const nw_address_t* nw_port_get_address(nw_port* port) {
+ return &port->address;
+}
+
+static int nw_port_is_disabled(nw_port* port) {
+ return nw_config_get_bool(port->config, "DISABLED");
+}
+
+nw_port* nw_port_get_parent_port(nw_port* port) {
+ if (!NW_PORT_TYPE(port)->get_parent_port)
+ return NULL;
+
+ return NW_PORT_TYPE(port)->get_parent_port(port);
+}
+
+static nw_link* nw_port_get_parent_link(nw_port* port) {
+ nw_port* parent = NULL;
+ nw_link* link = NULL;
+
+ // Fetch the parent
+ parent = nw_port_get_parent_port(port);
+ if (!parent)
+ return NULL;
+
+ // Fetch the link
+ link = nw_port_get_link(parent);
+
+ // Cleanup
+ if (parent)
+ nw_port_unref(parent);
+
+ return link;
+}
+
+static int __nw_port_create_link(sd_netlink* rtnl, sd_netlink_message* m, void* data) {
+ nw_port* port = (nw_port*)data;
+ int r;
+
+ // Check if the operation was successful
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0) {
+ ERROR("Could not create port %s: %s\n", port->name, strerror(-r));
+ // XXX We should extract the error message
+
+ return 0;
+ }
+
+ DEBUG("Successfully created %s\n", port->name);
+
+ return 0;
+}
+
+static int nw_port_create_link(nw_port* port) {
+ sd_netlink_message* m = NULL;
+ nw_link* link = NULL;
+ int r;
+
+ sd_netlink* rtnl = nw_daemon_get_rtnl(port->daemon);
+
+ DEBUG("Creating port %s...\n", port->name);
+
+ // Check the kind
+ if (!NW_PORT_TYPE(port)->kind) {
+ ERROR("Port type has no kind\n");
+ r = -ENOTSUP;
+ goto ERROR;
+ }
+
+ // Create a new link
+ r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0);
+ if (r < 0) {
+ ERROR("Could not create netlink message: %m\n");
+ goto ERROR;
+ }
+
+ // Set the name
+ r = sd_netlink_message_append_string(m, IFLA_IFNAME, port->name);
+ if (r < 0) {
+ ERROR("Could not set port name: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ // XXX Set common things like MTU, etc.
+
+ // Set Ethernet address
+ r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &port->address);
+ if (r < 0) {
+ ERROR("Could not set MAC address: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ // Fetch the parent link
+ link = nw_port_get_parent_link(port);
+ if (link) {
+ r = sd_netlink_message_append_u32(m, IFLA_LINK, nw_link_ifindex(link));
+ if (r < 0)
+ goto ERROR;
+ }
+
+ // Open an IFLA_LINKINFO container
+ r = sd_netlink_message_open_container(m, IFLA_LINKINFO);
+ if (r < 0)
+ goto ERROR;
+
+ // Run the custom setup
+ if (NW_PORT_TYPE(port)->create_link) {
+ r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, NW_PORT_TYPE(port)->kind);
+ if (r < 0) {
+ ERROR("Could not open IFLA_INFO_DATA container: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ r = NW_PORT_TYPE(port)->create_link(port, m);
+ if (r) {
+ ERROR("Could not create port %s: %m\n", port->name);
+ goto ERROR;
+ }
+
+ // Close the container
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ goto ERROR;
+
+ // Just set IFLA_INFO_KIND if there is no custom function
+ } else {
+ r = sd_netlink_message_append_string(m, IFLA_INFO_KIND, NW_PORT_TYPE(port)->kind);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ // Close the container
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ goto ERROR;
+
+ // Send the message
+ r = sd_netlink_call_async(rtnl, NULL, m, __nw_port_create_link,
+ __nw_port_unref, nw_port_ref(port), -1, NULL);
+ if (r < 0) {
+ ERROR("Could not send netlink message: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ r = 0;
+
+ERROR:
+ if (m)
+ sd_netlink_message_unref(m);
+ if (link)
+ nw_link_unref(link);
+
+ return r;
+}
+
+int nw_port_reconfigure(nw_port* port) {
+ int r;
+
+ // If the port is disabled, we will try to destroy it
+ if (nw_port_is_disabled(port)) {
+ if (port->link) {
+ r = nw_link_destroy(port->link);
+ if (r)
+ return r;
+ }
+
+ return 0;
+ }
+
+ // If there is no link, we will try to create it
+ if (!port->link)
+ return nw_port_create_link(port);
+
+ // XXX TODO
+
+ return 0;
+}
+
+int nw_port_has_carrier(nw_port* port) {
+ if (!port->link)
+ return 0;
+
+ return nw_link_has_carrier(port->link);
+}
+
+/*
+ Stats
+*/
+
+const struct rtnl_link_stats64* nw_port_get_stats64(nw_port* port) {
+ if (!port->link)
+ return NULL;
+
+ return nw_link_get_stats64(port->link);
+}
+
+int __nw_port_update_stats(nw_daemon* daemon, nw_port* port, void* data) {
+ nw_link* link = (nw_link*)data;
+
+ // Emit stats if link matches
+ if (port->link == link)
+ return nw_stats_collector_emit_port_stats(daemon, port);
+
+ return 0;
+}
+
+int nw_port_update_stats(nw_port* port) {
+ if (port->link)
+ return nw_link_update_stats(port->link);
+
+ return 0;
+}
+
+int nw_port_check_type(nw_port* port, const nw_port_type_id_t type) {
+ if (port->type->id == type)
+ return 0;
+
+ errno = ENOTSUP;
+ return -errno;
+}
+
+/*
+ JSON
+*/
+int nw_port_to_json(nw_port* port, struct json_object** object) {
+ char* address = NULL;
+ int r;
+
+ // Create a new JSON object
+ struct json_object* o = json_object_new_object();
+ if (!o) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Add name
+ r = json_object_add_string(o, "Name", port->name);
+ if (r < 0)
+ goto ERROR;
+
+ // Add Type
+ r = json_object_add_string(o, "Type", nw_port_type_id_to_string(port->type->id));
+ if (r < 0)
+ goto ERROR;
+
+ // Add address
+ address = nw_address_to_string(&port->address);
+ if (address) {
+ r = json_object_add_string(o, "Address", address);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ // Add link stuff
+ if (port->link) {
+ r = nw_link_to_json(port->link, o);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ // Call custom stuff
+ if (NW_PORT_TYPE(port)->to_json) {
+ r = NW_PORT_TYPE(port)->to_json(port, o);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ // Success
+ r = 0;
+
+ // Return a reference to the created object
+ *object = json_object_ref(o);
+
+ERROR:
+ if (address)
+ free(address);
+ if (o)
+ json_object_unref(o);
+
+ return r;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_PORT_H
+#define NETWORKD_PORT_H
+
+#include <json.h>
+
+#include <systemd/sd-netlink.h>
+
+typedef struct nw_port nw_port;
+
+typedef enum nw_port_type_id {
+ NW_PORT_BONDING,
+ NW_PORT_DUMMY,
+ NW_PORT_ETHERNET,
+ NW_PORT_VETH,
+ NW_PORT_VLAN,
+} nw_port_type_id_t;
+
+typedef struct nw_port_type {
+ // Type ID
+ nw_port_type_id_t id;
+
+ // IFLA_INFO_KIND/IFLA_INFO_DATA
+ const char* kind;
+
+ // Configuration
+ int (*setup)(nw_port* port);
+ int (*validate)(nw_port* port);
+
+ // Get Parent Port
+ nw_port* (*get_parent_port)(nw_port* port);
+
+ // Link
+ int (*create_link)(nw_port* port, sd_netlink_message* message);
+ int (*destroy_link)(nw_port* port);
+
+ // JSON
+ int (*to_json)(nw_port* port, struct json_object* object);
+} nw_port_type_t;
+
+#include "address.h"
+#include "config.h"
+#include "daemon.h"
+#include "json.h"
+#include "port-bonding.h"
+#include "port-ethernet.h"
+#include "port-veth.h"
+#include "port-vlan.h"
+
+#define NW_PORT_TYPE(port) (port->type)
+
+struct nw_port {
+ nw_daemon* daemon;
+ int nrefs;
+
+ // Link
+ nw_link* link;
+
+ const nw_port_type_t* type;
+ char name[IFNAMSIZ];
+
+ // Configuration
+ nw_config *config;
+
+ // Common attributes
+ nw_address_t address;
+
+ // Bonding Settings
+ struct nw_port_bonding bonding;
+
+ // Ethernet Settings
+ struct nw_port_ethernet ethernet;
+
+ // VETH Settings
+ struct nw_port_veth veth;
+
+ // VLAN settings
+ struct nw_port_vlan vlan;
+};
+
+int nw_port_create(nw_port** port, nw_daemon* daemon,
+ const nw_port_type_id_t type, const char* name, nw_config* config);
+int nw_port_open(nw_port** port, nw_daemon* daemon, const char* name, FILE* f);
+
+nw_port* nw_port_ref(nw_port* port);
+nw_port* nw_port_unref(nw_port* port);
+
+int nw_port_destroy(nw_port* port);
+int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data);
+
+int nw_port_save(nw_port* port);
+
+const char* nw_port_name(nw_port* port);
+
+char* nw_port_bus_path(nw_port* port);
+
+int __nw_port_set_link(nw_daemon* daemon, nw_port* port, void* data);
+int __nw_port_drop_link(nw_daemon* daemon, nw_port* port, void* data);
+
+const nw_address_t* nw_port_get_address(nw_port* port);
+
+nw_port* nw_port_get_parent_port(nw_port* port);
+
+int nw_port_reconfigure(nw_port* port);
+
+int nw_port_has_carrier(nw_port* port);
+
+int nw_port_check_type(nw_port* port, const nw_port_type_id_t type);
+
+// Stats
+const struct rtnl_link_stats64* nw_port_get_stats64(nw_port* port);
+int __nw_port_update_stats(nw_daemon* daemon, nw_port* port, void* data);
+int nw_port_update_stats(nw_port* port);
+
+// JSON
+int nw_port_to_json(nw_port* port, struct json_object** object);
+
+#endif /* NETWORKD_PORT_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include "logging.h"
+#include "port.h"
+#include "ports.h"
+#include "string.h"
+#include "util.h"
+
+struct nw_ports_entry {
+ nw_port* port;
+
+ // Link to the other entries
+ STAILQ_ENTRY(nw_ports_entry) nodes;
+};
+
+struct nw_ports {
+ nw_daemon* daemon;
+ int nrefs;
+
+ // Port Entries
+ STAILQ_HEAD(entries, nw_ports_entry) entries;
+
+ // A counter of the port entries
+ unsigned int num;
+};
+
+int nw_ports_create(nw_ports** ports, nw_daemon* daemon) {
+ nw_ports* p = calloc(1, sizeof(*p));
+ if (!p)
+ return 1;
+
+ // Store a reference to the daemon
+ p->daemon = nw_daemon_ref(daemon);
+
+ // Initialize the reference counter
+ p->nrefs = 1;
+
+ // Initialize entries
+ STAILQ_INIT(&p->entries);
+
+ // Reference the pointer
+ *ports = p;
+
+ return 0;
+}
+
+static void nw_ports_free(nw_ports* ports) {
+ struct nw_ports_entry* entry = NULL;
+
+ while (!STAILQ_EMPTY(&ports->entries)) {
+ entry = STAILQ_FIRST(&ports->entries);
+
+ // Dereference the port
+ nw_port_unref(entry->port);
+
+ // Remove the entry from the list
+ STAILQ_REMOVE_HEAD(&ports->entries, nodes);
+
+ // Free the entry
+ free(entry);
+ }
+}
+
+nw_ports* nw_ports_ref(nw_ports* ports) {
+ ports->nrefs++;
+
+ return ports;
+}
+
+nw_ports* nw_ports_unref(nw_ports* ports) {
+ if (--ports->nrefs > 0)
+ return ports;
+
+ nw_ports_free(ports);
+ return NULL;
+}
+
+int nw_ports_save(nw_ports* ports) {
+ struct nw_ports_entry* entry = NULL;
+ int r;
+
+ STAILQ_FOREACH(entry, &ports->entries, nodes) {
+ r = nw_port_save(entry->port);
+ if (r)
+ return r;
+ }
+
+ return 0;
+}
+
+static int nw_ports_add_port(nw_ports* ports, nw_port* port) {
+ // Allocate a new entry
+ struct nw_ports_entry* entry = calloc(1, sizeof(*entry));
+ if (!entry)
+ return 1;
+
+ // Reference the port
+ entry->port = nw_port_ref(port);
+
+ // Add it to the list
+ STAILQ_INSERT_TAIL(&ports->entries, entry, nodes);
+
+ // Increment the counter
+ ports->num++;
+
+ return 0;
+}
+
+static int __nw_ports_enumerate(struct dirent* entry, FILE* f, void* data) {
+ nw_port* port = NULL;
+ int r;
+
+ nw_ports* ports = (nw_ports*)data;
+
+ // Create a new port
+ r = nw_port_open(&port, ports->daemon, entry->d_name, f);
+ if (r < 0 || r == 1)
+ goto ERROR;
+
+ // Add the port to the list
+ r = nw_ports_add_port(ports, port);
+ if (r)
+ goto ERROR;
+
+ERROR:
+ if (port)
+ nw_port_unref(port);
+
+ return r;
+}
+
+int nw_ports_enumerate(nw_ports* ports) {
+ nw_configd* configd = NULL;
+ int r;
+
+ // Fetch ports configuration directory
+ configd = nw_daemon_configd(ports->daemon, "ports");
+ if (!configd)
+ return 0;
+
+ // Walk through all files
+ r = nw_configd_walk(configd, __nw_ports_enumerate, ports);
+
+ // Cleanup
+ nw_configd_unref(configd);
+
+ return r;
+}
+
+nw_port* nw_ports_get_by_name(nw_ports* ports, const char* name) {
+ struct nw_ports_entry* entry = NULL;
+
+ STAILQ_FOREACH(entry, &ports->entries, nodes) {
+ const char* __name = nw_port_name(entry->port);
+
+ // If the name matches, return a reference to the zone
+ if (strcmp(name, __name) == 0)
+ return nw_port_ref(entry->port);
+ }
+
+ // No match found
+ return NULL;
+}
+
+int nw_ports_bus_paths(nw_ports* ports, char*** paths) {
+ struct nw_ports_entry* entry = NULL;
+ char* path = NULL;
+
+ // Allocate an array for all paths
+ char** p = calloc(ports->num + 1, sizeof(*p));
+ if (!p)
+ return 1;
+
+ unsigned int i = 0;
+
+ // Walk through all ports
+ STAILQ_FOREACH(entry, &ports->entries, nodes) {
+ // Generate the bus path
+ path = nw_port_bus_path(entry->port);
+ if (!path)
+ goto ERROR;
+
+ // Append the bus path to the array
+ p[i++] = path;
+ }
+
+ // Return pointer
+ *paths = p;
+
+ return 0;
+
+ERROR:
+ if (p) {
+ for (char** e = p; *e; e++)
+ free(*e);
+ free(p);
+ }
+
+ return 1;
+}
+
+int nw_ports_walk(nw_ports* ports, nw_ports_walk_callback callback, void* data) {
+ struct nw_ports_entry* entry = NULL;
+ int r;
+
+ STAILQ_FOREACH(entry, &ports->entries, nodes) {
+ r = callback(ports->daemon, entry->port, data);
+ if (r)
+ return r;
+ }
+
+ return 0;
+}
+
+static int __nw_ports_reconfigure(nw_daemon* daemon, nw_port* port, void* data) {
+ return nw_port_reconfigure(port);
+}
+
+int nw_ports_reconfigure(nw_ports* ports) {
+ return nw_ports_walk(ports, __nw_ports_reconfigure, NULL);
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_PORTS_H
+#define NETWORKD_PORTS_H
+
+typedef struct nw_ports nw_ports;
+
+typedef int (*nw_ports_walk_callback)(nw_daemon* daemon, nw_port* port, void* data);
+
+#include "daemon.h"
+#include "port.h"
+
+int nw_ports_create(nw_ports** ports, nw_daemon* daemon);
+
+nw_ports* nw_ports_ref(nw_ports* ports);
+nw_ports* nw_ports_unref(nw_ports* ports);
+
+int nw_ports_save(nw_ports* ports);
+
+int nw_ports_enumerate(nw_ports* ports);
+
+struct nw_port* nw_ports_get_by_name(nw_ports* ports, const char* name);
+
+int nw_ports_bus_paths(nw_ports* ports, char*** paths);
+
+int nw_ports_walk(nw_ports* ports, nw_ports_walk_callback callback, void* data);
+
+int nw_ports_reconfigure(nw_ports* ports);
+
+#endif /* NETWORKD_PORTS_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <stdlib.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-event.h>
+
+#include "logging.h"
+#include "port.h"
+#include "stats-collector.h"
+#include "zone.h"
+
+static int __nw_stats_collector_port(nw_daemon* daemon, nw_port* port, void* data) {
+ return nw_port_update_stats(port);
+}
+
+static int __nw_stats_collector_zone(nw_daemon* daemon, nw_zone* zone, void* data) {
+ return nw_zone_update_stats(zone);
+}
+
+int nw_stats_collector(sd_event_source* s, long unsigned int usec, void* data) {
+ nw_daemon* daemon = (nw_daemon*)data;
+ int r;
+
+ DEBUG("Stats collector has been called\n");
+
+ // Schedule the next call
+ r = sd_event_source_set_time(s, usec + NW_STATS_COLLECTOR_INTERVAL);
+ if (r < 0)
+ return r;
+
+ // Ports
+ r = nw_daemon_ports_walk(daemon, __nw_stats_collector_port, NULL);
+ if (r)
+ return r;
+
+ // Zones
+ r = nw_daemon_zones_walk(daemon, __nw_stats_collector_zone, NULL);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+static int nw_stats_collector_emit_stats(nw_daemon* daemon, const char* path,
+ const char* interface, const char* member, const struct rtnl_link_stats64* stats64) {
+ sd_bus_message* m = NULL;
+ int r;
+
+ sd_bus* bus = nw_daemon_get_bus(daemon);
+
+ // Allocate a new message
+ r = sd_bus_message_new_signal(bus, &m, path, interface, member);
+ if (r < 0) {
+ errno = -r;
+ ERROR("Could not allocate bus message: %m\n");
+ goto ERROR;
+ }
+
+ // Open the container
+ r = sd_bus_message_open_container(m, 'a', "{st}");
+ if (r < 0) {
+ ERROR("Could not open container: %m\n");
+ goto ERROR;
+ }
+
+ const struct stats64_entry {
+ const char* key;
+ uint64_t value;
+ } entries[] = {
+ { "rx-packets", stats64->rx_packets },
+ { "tx-packets", stats64->tx_packets },
+ { "rx-bytes", stats64->rx_bytes },
+ { "tx-bytes", stats64->tx_bytes },
+ { "rx-errors", stats64->rx_errors },
+ { "tx-errors", stats64->tx_errors },
+ { "rx-dropped", stats64->rx_dropped },
+ { "tx-dropped", stats64->tx_dropped },
+ { "multicast", stats64->multicast },
+ { "collisions", stats64->collisions },
+
+ // Detailed RX errors
+ { "rx-length-errors", stats64->rx_length_errors },
+ { "rx-over-errors", stats64->rx_over_errors },
+ { "rx-crc-errors", stats64->rx_crc_errors },
+ { "rx-frame-errors", stats64->rx_frame_errors },
+ { "rx-fifo-errors", stats64->rx_fifo_errors },
+ { "rx-missed-errors", stats64->rx_missed_errors },
+
+ // Detailed TX errors
+ { "tx-aborted-errors", stats64->tx_aborted_errors },
+ { "tx-carrier-errors", stats64->tx_carrier_errors },
+ { "tx-fifo-errors", stats64->tx_fifo_errors },
+ { "tx-heartbeat-errors", stats64->tx_heartbeat_errors },
+ { "tx-window-errors", stats64->tx_window_errors },
+
+ { NULL },
+ };
+
+ for (const struct stats64_entry* e = entries; e->key; e++) {
+ r = sd_bus_message_append(m, "{st}", e->key, e->value);
+ if (r < 0) {
+ ERROR("Could not set stat value: %m\n");
+ goto ERROR;
+ }
+ }
+
+ // Close the container
+ r = sd_bus_message_close_container(m);
+ if (r < 0) {
+ ERROR("Could not close container: %m\n");
+ goto ERROR;
+ }
+
+ // Emit the signal
+ r = sd_bus_send(bus, m, NULL);
+ if (r < 0) {
+ ERROR("Could not emit the stats signal for %s: %m\n", path);
+ goto ERROR;
+ }
+
+ERROR:
+ if (m)
+ sd_bus_message_unref(m);
+
+ return r;
+}
+
+int nw_stats_collector_emit_port_stats(nw_daemon* daemon, nw_port* port) {
+ const struct rtnl_link_stats64* stats64 = NULL;
+ char* path = NULL;
+ int r;
+
+ // Fetch the bus path
+ path = nw_port_bus_path(port);
+
+ // Fetch the stats
+ stats64 = nw_port_get_stats64(port);
+
+ // Emit the stats
+ r = nw_stats_collector_emit_stats(daemon, path,
+ "org.ipfire.network1.Port", "Stats", stats64);
+ if (r < 0) {
+ ERROR("Could not emit stats for port %s: %m\n", nw_port_name(port));
+ goto ERROR;
+ }
+
+ERROR:
+ if (path)
+ free(path);
+
+ return r;
+}
+
+int nw_stats_collector_emit_zone_stats(nw_daemon* daemon, nw_zone* zone) {
+ const struct rtnl_link_stats64* stats64 = NULL;
+ char* path = NULL;
+ int r;
+
+ // Fetch the bus path
+ path = nw_zone_bus_path(zone);
+
+ // Fetch the stats
+ stats64 = nw_zone_get_stats64(zone);
+
+ // Emit the stats
+ r = nw_stats_collector_emit_stats(daemon, path,
+ "org.ipfire.network1.Zone", "Stats", stats64);
+ if (r < 0) {
+ ERROR("Could not emit stats for zone %s: %m\n", nw_zone_name(zone));
+ goto ERROR;
+ }
+
+ERROR:
+ if (path)
+ free(path);
+
+ return r;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_STATS_COLLECTOR_H
+#define NETWORKD_STATS_COLLECTOR_H
+
+#include <systemd/sd-event.h>
+
+#include "daemon.h"
+#include "port.h"
+#include "zone.h"
+
+#define NW_STATS_COLLECTOR_INTERVAL 15 * 1000000ULL // 15 sec in µsec
+
+int nw_stats_collector(sd_event_source* s, long unsigned int usec, void* data);
+
+int nw_stats_collector_emit_port_stats(nw_daemon* daemon, nw_port* port);
+int nw_stats_collector_emit_zone_stats(nw_daemon* daemon, nw_zone* zone);
+
+#endif /* NETWORKD_STATS_COLLECTOR_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_STRING_H
+#define NETWORKD_STRING_H
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#define nw_string_vformat(s, format, ...) \
+ __nw_string_vformat(s, sizeof(s), format, __VA_ARGS__)
+
+static inline int __nw_string_vformat(char* s, const size_t length,
+ const char* format, va_list args) {
+ // Write string to buffer
+ const ssize_t required = vsnprintf(s, length, format, args);
+
+ // Catch any errors
+ if (required < 0)
+ return required;
+
+ // Check if the entire string could be written
+ if ((size_t)required >= length) {
+ return -ENOMEM;
+ }
+
+ // Success
+ return 0;
+}
+
+#define nw_string_format(s, format, ...) \
+ __nw_string_format(s, sizeof(s), format, __VA_ARGS__)
+
+static inline int __nw_string_format(char* s, const size_t length,
+ const char* format, ...) {
+ va_list args;
+ int r;
+
+ // Call __nw_string_vformat
+ va_start(args, format);
+ r = __nw_string_vformat(s, length, format, args);
+ va_end(args);
+
+ return r;
+}
+
+#define nw_string_set(s, value) __nw_string_set(s, sizeof(s), value)
+
+static inline int __nw_string_set(char* s, const size_t length, const char* value) {
+ // If value is NULL, we will overwrite the buffer with just zeros
+ if (!value) {
+ for (unsigned int i = 0; i < length; i++)
+ s[i] = '\0';
+
+ return 0;
+ }
+
+ // Otherwise just copy
+ return __nw_string_format(s, length, "%s", value);
+}
+
+static inline int nw_string_lstrip(char* s) {
+ char* p = s;
+
+ // Count any leading spaces
+ while (*p && isspace(*p))
+ p++;
+
+ // Move the string to the beginning of the buffer
+ while (*p)
+ *s++ = *p++;
+
+ // Terminate the string
+ *s = '\0';
+
+ return 0;
+}
+
+static inline int nw_string_rstrip(char* s) {
+ ssize_t l = strlen(s) - 1;
+
+ while (l >= 0 && isspace(s[l]))
+ s[l--] = '\0';
+
+ return 0;
+}
+
+static inline int nw_string_strip(char* s) {
+ int r;
+
+ r = nw_string_lstrip(s);
+ if (r)
+ return r;
+
+ r = nw_string_rstrip(s);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+static inline void nw_string_empty(char* s) {
+ if (s)
+ *s = '\0';
+}
+
+/*
+ Tables
+*/
+
+typedef struct nw_string_table {
+ const int id;
+ const char* string;
+} nw_string_table_t;
+
+static inline const char* nw_string_table_lookup_string(
+ const nw_string_table_t* table, const int id) {
+ const nw_string_table_t* entry = NULL;
+
+ for (entry = table; entry->string; entry++)
+ if (entry->id == id)
+ return entry->string;
+
+ return NULL;
+}
+
+static inline int nw_string_table_lookup_id(
+ const nw_string_table_t* table, const char* string) {
+ const nw_string_table_t* entry = NULL;
+
+ for (entry = table; entry->string; entry++)
+ if (strcmp(entry->string, string) == 0)
+ return entry->id;
+
+ return -1;
+}
+
+#define NW_STRING_TABLE_LOOKUP_ID(type, table, method) \
+ __attribute__((unused)) static type method(const char* s) { \
+ return nw_string_table_lookup_id(table, s); \
+ }
+
+#define NW_STRING_TABLE_LOOKUP_STRING(type, table, method) \
+ __attribute__((unused)) static const char* method(const type id) { \
+ return nw_string_table_lookup_string(table, id); \
+ }
+
+#define NW_STRING_TABLE_LOOKUP(type, table) \
+ NW_STRING_TABLE_LOOKUP_ID(type, table, table ## _from_string) \
+ NW_STRING_TABLE_LOOKUP_STRING(type, table, table ## _to_string)
+
+/*
+ Paths
+*/
+
+#define nw_path_join(s, first, second) \
+ __nw_path_join(s, sizeof(s), first, second)
+
+static inline int __nw_path_join(char* s, const size_t length,
+ const char* first, const char* second) {
+ if (!first)
+ return __nw_string_format(s, length, "%s", second);
+
+ if (!second)
+ return __nw_string_format(s, length, "%s", first);
+
+ // Remove leading slashes from second argument
+ while (*second == '/')
+ second++;
+
+ return __nw_string_format(s, length, "%s/%s", first, second);
+}
+
+static inline const char* nw_path_basename(const char* path) {
+ const char* basename = strrchr(path, '/');
+ if (!basename)
+ return NULL;
+
+ return basename + 1;
+}
+
+#endif /* NETWORKD_STRING_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include "util.h"
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_UTIL_H
+#define NETWORKD_UTIL_H
+
+#endif /* NETWORKD_UTIL_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <errno.h>
+
+#include "bus.h"
+#include "daemon.h"
+#include "logging.h"
+#include "zone.h"
+#include "zone-bus.h"
+#include "zones.h"
+
+static int nw_zone_node_enumerator(sd_bus* bus, const char* path, void* data,
+ char*** nodes, sd_bus_error* error) {
+ int r;
+
+ DEBUG("Enumerating zones...\n");
+
+ // Fetch a reference to the daemon
+ nw_daemon* daemon = (nw_daemon*)data;
+
+ // Fetch zones
+ nw_zones* zones = nw_daemon_zones(daemon);
+
+ // Make bus paths for all zones
+ r = nw_zones_bus_paths(zones, nodes);
+ if (r)
+ goto ERROR;
+
+ERROR:
+ nw_zones_unref(zones);
+
+ return r;
+}
+
+static int nw_zone_object_find(sd_bus* bus, const char* path, const char* interface,
+ void* data, void** found, sd_bus_error* error) {
+ char* name = NULL;
+ int r;
+
+ // Fetch a reference to the daemon
+ nw_daemon* daemon = (nw_daemon*)data;
+
+ // Decode the path of the requested object
+ r = sd_bus_path_decode(path, "/org/ipfire/network1/zone", &name);
+ if (r <= 0)
+ return 0;
+
+ // Find the zone
+ nw_zone* zone = nw_daemon_get_zone_by_name(daemon, name);
+ if (!zone)
+ return 0;
+
+ // Match!
+ *found = zone;
+
+ nw_zone_unref(zone);
+
+ return 1;
+}
+
+/*
+ MTU
+*/
+static int nw_zone_bus_get_mtu(sd_bus* bus, const char *path, const char *interface,
+ const char* property, sd_bus_message* reply, void* data, sd_bus_error *error) {
+ nw_zone* zone = (nw_zone*)data;
+
+ return sd_bus_message_append(reply, "u", nw_zone_mtu(zone));
+}
+
+static int nw_zone_bus_set_mtu(sd_bus* bus, const char* path, const char* interface,
+ const char* property, sd_bus_message* value, void* data, sd_bus_error* error) {
+ unsigned int mtu = 0;
+ int r;
+
+ nw_zone* zone = (nw_zone*)data;
+
+ // Parse the value
+ r = sd_bus_message_read(value, "u", &mtu);
+ if (r < 0)
+ return r;
+
+ if (!nw_zone_set_mtu(zone, mtu))
+ return -errno;
+
+ return 0;
+}
+
+static const sd_bus_vtable zone_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+
+ // MTU
+ SD_BUS_WRITABLE_PROPERTY("MTU", "u", nw_zone_bus_get_mtu, nw_zone_bus_set_mtu,
+ 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+
+ SD_BUS_VTABLE_END
+};
+
+const nw_bus_implementation zone_bus_impl = {
+ "/org/ipfire/network1/zone",
+ "org.ipfire.network1.Zone",
+ .fallback_vtables = BUS_FALLBACK_VTABLES({zone_vtable, nw_zone_object_find}),
+ .node_enumerator = nw_zone_node_enumerator,
+};
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_ZONE_BUS_H
+#define NETWORKD_ZONE_BUS_H
+
+#include "bus.h"
+
+extern const nw_bus_implementation zone_bus_impl;
+
+#endif /* NETWORKD_ZONE_BUS_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <limits.h>
+#include <linux/if_link.h>
+#include <stdlib.h>
+
+#include <systemd/sd-bus.h>
+
+#include "config.h"
+#include "daemon.h"
+#include "link.h"
+#include "logging.h"
+#include "stats-collector.h"
+#include "string.h"
+#include "zone.h"
+
+static const nw_string_table_t nw_zone_type_id[] = {
+ { -1, NULL },
+};
+
+NW_STRING_TABLE_LOOKUP(nw_zone_type_id_t, nw_zone_type_id)
+
+static void nw_zone_free(nw_zone* zone) {
+ if (zone->link)
+ nw_link_unref(zone->link);
+ if (zone->config)
+ nw_config_unref(zone->config);
+ if (zone->daemon)
+ nw_daemon_unref(zone->daemon);
+
+ free(zone);
+}
+
+static int nw_zone_set_link(nw_zone* zone, nw_link* link) {
+ // Do nothing if the same link is being re-assigned
+ if (zone->link == link)
+ return 0;
+
+ // Dereference the former link if set
+ if (zone->link)
+ nw_link_unref(zone->link);
+
+ // Store the new link
+ if (link) {
+ zone->link = nw_link_ref(link);
+
+ DEBUG("Zone %s: Assigned link %d\n", zone->name, nw_link_ifindex(zone->link));
+
+ // Or clear the pointer if no link has been provided
+ } else {
+ zone->link = NULL;
+
+ DEBUG("Zone %s: Removed link\n", zone->name);
+ }
+
+ return 0;
+}
+
+static int nw_zone_setup(nw_zone* zone) {
+ nw_link* link = NULL;
+ int r = 0;
+
+ // Find the link
+ link = nw_daemon_get_link_by_name(zone->daemon, zone->name);
+ if (link) {
+ r = nw_zone_set_link(zone, link);
+ if (r)
+ goto ERROR;
+ }
+
+ERROR:
+ if (link)
+ nw_link_unref(link);
+
+ return r;
+}
+
+int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const nw_zone_type_id_t type,
+ const char* name, nw_config* config) {
+ nw_zone* z = NULL;
+ int r;
+
+ // Allocate a new object
+ z = calloc(1, sizeof(*z));
+ if (!z)
+ return -errno;
+
+ // Store a reference to the daemon
+ z->daemon = nw_daemon_ref(daemon);
+
+ // Initialize reference counter
+ z->nrefs = 1;
+
+ // Store the name
+ r = nw_string_set(z->name, name);
+ if (r)
+ goto ERROR;
+
+ // Copy the configuration
+ r = nw_config_copy(config, &z->config);
+ if (r < 0)
+ goto ERROR;
+
+ // Setup the zone
+ r = nw_zone_setup(z);
+ if (r)
+ goto ERROR;
+
+ *zone = z;
+ return 0;
+
+ERROR:
+ nw_zone_free(z);
+ return r;
+}
+
+int nw_zone_open(nw_zone** zone, nw_daemon* daemon, const char* name, FILE* f) {
+ nw_config* config = NULL;
+ int r;
+
+ // Initialize the configuration
+ r = nw_config_create(&config, f);
+ if (r < 0)
+ goto ERROR;
+
+ // Fetch the type
+ const char* type = nw_config_get(config, "TYPE");
+ if (!type) {
+ ERROR("Zone %s has no TYPE\n", name);
+ r = -ENOTSUP;
+ goto ERROR;
+ }
+
+ // Create a new zone
+ r = nw_zone_create(zone, daemon, nw_zone_type_id_from_string(type), name, config);
+ if (r < 0)
+ goto ERROR;
+
+ERROR:
+ if (config)
+ nw_config_unref(config);
+
+ return r;
+}
+
+nw_zone* nw_zone_ref(nw_zone* zone) {
+ zone->nrefs++;
+
+ return zone;
+}
+
+nw_zone* nw_zone_unref(nw_zone* zone) {
+ if (--zone->nrefs > 0)
+ return zone;
+
+ nw_zone_free(zone);
+ return NULL;
+}
+
+int __nw_zone_drop_port(nw_daemon* daemon, nw_zone* zone, void* data) {
+ nw_port* dropped_port = (nw_port*)data;
+
+ // XXX TODO
+ (void)dropped_port;
+
+ return 0;
+}
+
+int nw_zone_save(nw_zone* zone) {
+ nw_configd* configd = NULL;
+ FILE* f = NULL;
+ int r;
+
+ // Fetch configuration directory
+ configd = nw_daemon_configd(zone->daemon, "zones");
+ if (!configd) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Open file
+ f = nw_configd_fopen(configd, zone->name, "w");
+ if (!f) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Write out the configuration
+ r = nw_config_options_write(zone->config);
+ if (r < 0)
+ goto ERROR;
+
+ // Write the configuration
+ r = nw_config_write(zone->config, f);
+ if (r < 0)
+ goto ERROR;
+
+ERROR:
+ if (configd)
+ nw_configd_unref(configd);
+ if (f)
+ fclose(f);
+ if (r)
+ ERROR("Could not save configuration for zone %s: %s\n", zone->name, strerror(-r));
+
+ return r;
+}
+
+const char* nw_zone_name(nw_zone* zone) {
+ return zone->name;
+}
+
+char* nw_zone_bus_path(nw_zone* zone) {
+ char* p = NULL;
+ int r;
+
+ // Encode the bus path
+ r = sd_bus_path_encode("/org/ipfire/network1/zone", zone->name, &p);
+ if (r < 0)
+ return NULL;
+
+ return p;
+}
+
+int __nw_zone_set_link(nw_daemon* daemon, nw_zone* zone, void* data) {
+ nw_link* link = (nw_link*)data;
+
+ // Fetch the link name
+ const char* ifname = nw_link_ifname(link);
+ if (!ifname) {
+ ERROR("Link does not have a name set\n");
+ return 1;
+ }
+
+ // Set link if the name matches
+ if (strcmp(zone->name, ifname) == 0)
+ return nw_zone_set_link(zone, link);
+
+ // If we have the link set but the name did not match, we will remove it
+ else if (zone->link == link)
+ return nw_zone_set_link(zone, NULL);
+
+ return 0;
+}
+
+int __nw_zone_drop_link(nw_daemon* daemon, nw_zone* zone, void* data) {
+ nw_link* link = (nw_link*)data;
+
+ // Drop the link if it matches
+ if (zone->link == link)
+ return nw_zone_set_link(zone, NULL);
+
+ return 0;
+}
+
+int nw_zone_reconfigure(nw_zone* zone) {
+ return 0; // XXX TODO
+}
+
+// Carrier
+
+int nw_zone_has_carrier(nw_zone* zone) {
+ if (!zone->link)
+ return 0;
+
+ return nw_link_has_carrier(zone->link);
+}
+
+/*
+ MTU
+*/
+unsigned int nw_zone_mtu(nw_zone* zone) {
+ return nw_config_get_int(zone->config, "MTU", NETWORK_ZONE_DEFAULT_MTU);
+}
+
+int nw_zone_set_mtu(nw_zone* zone, unsigned int mtu) {
+ DEBUG("Change MTU of %s to %u\n", zone->name, mtu);
+
+ return nw_config_set_int(zone->config, "MTU", mtu);
+}
+
+/*
+ Stats
+*/
+
+const struct rtnl_link_stats64* nw_zone_get_stats64(nw_zone* zone) {
+ if (!zone->link)
+ return NULL;
+
+ return nw_link_get_stats64(zone->link);
+}
+
+int __nw_zone_update_stats(nw_daemon* daemon, nw_zone* zone, void* data) {
+ nw_link* link = (nw_link*)data;
+
+ // Emit stats if link matches
+ if (zone->link == link)
+ return nw_stats_collector_emit_zone_stats(daemon, zone);
+
+ return 0;
+}
+
+int nw_zone_update_stats(nw_zone* zone) {
+ if (zone->link)
+ return nw_link_update_stats(zone->link);
+
+ return 0;
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_ZONE_H
+#define NETWORKD_ZONE_H
+
+#define NETWORK_ZONE_DEFAULT_MTU 1500
+
+typedef struct nw_zone nw_zone;
+
+typedef enum nw_zone_type_id {
+ __EMPTY
+} nw_zone_type_id_t;
+
+#include <linux/if.h>
+#include <linux/if_link.h>
+
+#include "config.h"
+#include "daemon.h"
+#include "link.h"
+
+struct nw_zone {
+ nw_daemon* daemon;
+ int nrefs;
+
+ // Link
+ nw_link* link;
+
+ char name[IFNAMSIZ];
+
+ // Configuration
+ nw_config *config;
+};
+
+int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const nw_zone_type_id_t type,
+ const char* name, nw_config* config);
+int nw_zone_open(nw_zone** zone, nw_daemon* daemon, const char* name, FILE* f);
+
+nw_zone* nw_zone_ref(nw_zone* zone);
+nw_zone* nw_zone_unref(nw_zone* zone);
+
+int __nw_zone_drop_port(nw_daemon* daemon, nw_zone* zone, void* data);
+
+int nw_zone_save(nw_zone* zone);
+
+const char* nw_zone_name(nw_zone* zone);
+
+char* nw_zone_bus_path(nw_zone* zone);
+
+int __nw_zone_set_link(nw_daemon* daemon, nw_zone* zone, void* data);
+int __nw_zone_drop_link(nw_daemon* daemon, nw_zone* zone, void* data);
+
+int nw_zone_reconfigure(nw_zone* zone);
+
+int nw_zone_has_carrier(nw_zone* zone);
+
+/*
+ MTU
+*/
+unsigned int nw_zone_mtu(nw_zone* zone);
+int nw_zone_set_mtu(nw_zone* zone, unsigned int mtu);
+
+const struct rtnl_link_stats64* nw_zone_get_stats64(nw_zone* zone);
+int __nw_zone_update_stats(nw_daemon* daemon, nw_zone* zone, void* data);
+int nw_zone_update_stats(nw_zone* zone);
+
+#endif /* NETWORKD_ZONE_H */
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+
+#include "daemon.h"
+#include "logging.h"
+#include "string.h"
+#include "util.h"
+#include "zone.h"
+#include "zones.h"
+
+struct nw_zones_entry {
+ nw_zone* zone;
+
+ // Link to the other entries
+ STAILQ_ENTRY(nw_zones_entry) nodes;
+};
+
+struct nw_zones {
+ nw_daemon* daemon;
+ int nrefs;
+
+ // Zone Entries
+ STAILQ_HEAD(entries, nw_zones_entry) entries;
+
+ // A counter of the zone entries
+ unsigned int num;
+};
+
+int nw_zones_create(nw_zones** zones, nw_daemon* daemon) {
+ nw_zones* z = calloc(1, sizeof(*z));
+ if (!z)
+ return 1;
+
+ // Store a reference to the daemon
+ z->daemon = nw_daemon_ref(daemon);
+
+ // Initialize the reference counter
+ z->nrefs = 1;
+
+ // Initialize entries
+ STAILQ_INIT(&z->entries);
+
+ // Reference the pointer
+ *zones = z;
+
+ return 0;
+}
+
+static void nw_zones_free(nw_zones* zones) {
+ struct nw_zones_entry* entry = NULL;
+
+ while (!STAILQ_EMPTY(&zones->entries)) {
+ entry = STAILQ_FIRST(&zones->entries);
+
+ // Dereference the zone
+ nw_zone_unref(entry->zone);
+
+ // Remove the entry from the list
+ STAILQ_REMOVE_HEAD(&zones->entries, nodes);
+
+ // Free the entry
+ free(entry);
+ }
+
+ if (zones->daemon)
+ nw_daemon_unref(zones->daemon);
+
+ free(zones);
+}
+
+nw_zones* nw_zones_ref(nw_zones* zones) {
+ zones->nrefs++;
+
+ return zones;
+}
+
+nw_zones* nw_zones_unref(nw_zones* zones) {
+ if (--zones->nrefs > 0)
+ return zones;
+
+ nw_zones_free(zones);
+ return NULL;
+}
+
+int nw_zones_save(nw_zones* zones) {
+ struct nw_zones_entry* entry = NULL;
+ int r;
+
+ STAILQ_FOREACH(entry, &zones->entries, nodes) {
+ r = nw_zone_save(entry->zone);
+ if (r)
+ return r;
+ }
+
+ return 0;
+}
+
+static int nw_zones_add_zone(nw_zones* zones, nw_zone* zone) {
+ // Allocate a new entry
+ struct nw_zones_entry* entry = calloc(1, sizeof(*entry));
+ if (!entry)
+ return 1;
+
+ // Reference the zone
+ entry->zone = nw_zone_ref(zone);
+
+ // Add it to the list
+ STAILQ_INSERT_TAIL(&zones->entries, entry, nodes);
+
+ // Increment the counter
+ zones->num++;
+
+ return 0;
+}
+
+static int __nw_zones_enumerate(struct dirent* entry, FILE* f, void* data) {
+ nw_zone* zone = NULL;
+ int r;
+
+ nw_zones* zones = (nw_zones*)data;
+
+ // Create a new zone
+ r = nw_zone_open(&zone, zones->daemon, entry->d_name, f);
+ if (r < 0 || r == 1)
+ goto ERROR;
+
+ // Add the zone to the list
+ r = nw_zones_add_zone(zones, zone);
+ if (r)
+ goto ERROR;
+
+ERROR:
+ if (zone)
+ nw_zone_unref(zone);
+
+ return r;
+}
+
+int nw_zones_enumerate(nw_zones* zones) {
+ nw_configd* configd = NULL;
+ int r;
+
+ // Fetch zones configuration directory
+ configd = nw_daemon_configd(zones->daemon, "zones");
+ if (!configd)
+ return 0;
+
+ // Walk through all files
+ r = nw_configd_walk(configd, __nw_zones_enumerate, zones);
+
+ // Cleanup
+ nw_configd_unref(configd);
+
+ return r;
+}
+
+size_t nw_zones_num(nw_zones* zones) {
+ struct nw_zones_entry* entry = NULL;
+ size_t length = 0;
+
+ // Count all zones
+ STAILQ_FOREACH(entry, &zones->entries, nodes)
+ length++;
+
+ return length;
+}
+
+nw_zone* nw_zones_get_by_name(nw_zones* zones, const char* name) {
+ struct nw_zones_entry* entry = NULL;
+
+ STAILQ_FOREACH(entry, &zones->entries, nodes) {
+ const char* __name = nw_zone_name(entry->zone);
+
+ // If the name matches, return a reference to the zone
+ if (strcmp(name, __name) == 0)
+ return nw_zone_ref(entry->zone);
+ }
+
+ // No match found
+ return NULL;
+}
+
+int nw_zones_bus_paths(nw_zones* zones, char*** paths) {
+ struct nw_zones_entry* entry = NULL;
+ char* path = NULL;
+
+ // Allocate an array for all paths
+ char** p = calloc(zones->num + 1, sizeof(*p));
+ if (!p)
+ return 1;
+
+ unsigned int i = 0;
+
+ // Walk through all zones
+ STAILQ_FOREACH(entry, &zones->entries, nodes) {
+ // Generate the bus path
+ path = nw_zone_bus_path(entry->zone);
+ if (!path)
+ goto ERROR;
+
+ // Append the bus path to the array
+ p[i++] = path;
+ }
+
+ // Return pointer
+ *paths = p;
+
+ return 0;
+
+ERROR:
+ if (p) {
+ for (char** e = p; *e; e++)
+ free(*e);
+ free(p);
+ }
+
+ return 1;
+}
+
+int nw_zones_walk(nw_zones* zones, nw_zones_walk_callback callback, void* data) {
+ struct nw_zones_entry* entry = NULL;
+ int r;
+
+ STAILQ_FOREACH(entry, &zones->entries, nodes) {
+ r = callback(zones->daemon, entry->zone, data);
+ if (r)
+ return r;
+ }
+
+ return 0;
+}
+
+static int __nw_zones_reconfigure(nw_daemon* daemon, nw_zone* zone, void* data) {
+ return nw_zone_reconfigure(zone);
+}
+
+int nw_zones_reconfigure(nw_zones* zones) {
+ return nw_zones_walk(zones, __nw_zones_reconfigure, NULL);
+}
--- /dev/null
+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef NETWORKD_ZONES_H
+#define NETWORKD_ZONES_H
+
+typedef struct nw_zones nw_zones;
+
+typedef int (*nw_zones_walk_callback)(nw_daemon* daemon, nw_zone* zone, void* data);
+
+#include "daemon.h"
+
+int nw_zones_create(nw_zones** zones, nw_daemon* daemon);
+
+nw_zones* nw_zones_ref(nw_zones* zones);
+nw_zones* nw_zones_unref(nw_zones* zones);
+
+int nw_zones_save(nw_zones* zones);
+
+int nw_zones_enumerate(nw_zones* zones);
+
+size_t nw_zones_num(nw_zones* zones);
+
+nw_zone* nw_zones_get_by_name(nw_zones* zones, const char* name);
+
+int nw_zones_bus_paths(nw_zones* zones, char*** paths);
+
+int nw_zones_walk(nw_zones* zones, nw_zones_walk_callback callback, void* data);
+
+int nw_zones_reconfigure(nw_zones* zones);
+
+#endif /* NETWORKD_ZONES_H */
--- /dev/null
+#!/bin/bash
+
+# Simply run networkctl to check whether it works
+./networkctl --version
--- /dev/null
+TYPE=dummy
+ADDRESS=00:11:22:33:44:55
--- /dev/null
+TYPE=dummy
+ADDRESS=00:55:44:33:22:11
--- /dev/null
+#!/bin/bash
+
+# Dump status of d0
+./networkctl port dump d0
+
+# Dump status of d1
+./networkctl port dump d1
--- /dev/null
+#!/bin/bash
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+# Break if anything fails
+set -e
+
+# Turn on job control
+set -o monitor
+
+run_script() {
+ local script="${1}"
+ shift
+
+ if [ -f "${script}" ]; then
+ echo "Launching ${script}..."
+
+ # Launch the script in a separate shell and echo every command
+ if ! ${SHELL} -xe "${script}"; then
+ echo "${script} failed" >&2
+ return 1
+ fi
+ fi
+
+ return 0
+}
+
+dump_command() {
+ echo "Output of $@"
+
+ # Run the command
+ $@ 2>&1
+
+ echo "EOF"
+}
+
+dump_status() {
+ dump_command "printenv"
+ dump_command "ps aux"
+ dump_command "ip -d link"
+}
+
+# Launches networkd in the background
+launch_networkd() {
+ echo "Launching networkd..."
+
+ # Launch it!
+ coproc networkd { ./networkd "$@"; }
+
+ echo "networkd launched as PID ${networkd_PID}"
+
+ # Wait until networkd is initialized
+ # XXX Calling sleep(8) is very racy and should be replaced by something that
+ # waits until networkd has connected to dbus
+ sleep 1
+}
+
+terminate_networkd() {
+ local seconds=0
+
+ if [ -n "${networkd_PID}" ]; then
+ while [ -n "${networkd_PID}" ] && kill -0 "${networkd_PID}"; do
+ case "${seconds}" in
+ # Send SIGTERM in the beginning
+ 0)
+ echo "Sending SIGTERM to networkd"
+ kill -TERM "${networkd_PID}"
+ ;;
+
+ # After 5 seconds, send SIGKILL
+ 5)
+ echo "Sending SIGKILL to networkd"
+ kill -KILL "${networkd_PID}"
+
+ # It is an error, if we have to kill networkd
+ exit 1
+ ;;
+ esac
+
+ # Wait for a moment
+ sleep 1
+
+ # Increment seconds
+ (( seconds++ ))
+ done
+
+ echo "networkd has terminated"
+ fi
+}
+
+# Collect some status information
+trap dump_status EXIT
+
+main() {
+ local test="${1}"
+ shift
+
+ echo "Running ${test}..."
+
+ # Check if the test exists
+ if [ ! -d "${test}" ]; then
+ echo "Test '${test}' does not exist" >&2
+ return 2
+ fi
+
+ # Run prepare script
+ if ! run_script "${test}/prepare.sh"; then
+ return 1
+ fi
+
+ # Launch networkd
+ launch_networkd --config="${test}/config"
+
+ # Run test script
+ if ! run_script "${test}/test.sh"; then
+ return 1
+ fi
+
+ # Terminate networkd
+ terminate_networkd
+
+ # Run cleanup script
+ if ! run_script "${test}/cleanup.sh"; then
+ return 1
+ fi
+
+ return 0
+}
+
+# Call main()
+main "$@" || exit $?