]> git.ipfire.org Git - people/ms/network.git/commitdiff
Makefile: Fix typo in localstatedir master
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 19 Sep 2023 12:54:53 +0000 (12:54 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 19 Sep 2023 12:54:53 +0000 (12:54 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
75 files changed:
.gitignore
Makefile.am
config/vpn/security-policies/performance
config/vpn/security-policies/system
configure.ac
man/network-vpn-ipsec.txt
src/functions/functions.vpn-security-policies
src/libnetwork/network/libnetwork.h
src/modprobe.d/no-copybreak.conf [new file with mode: 0644]
src/networkctl/command.c [new file with mode: 0644]
src/networkctl/command.h [new file with mode: 0644]
src/networkctl/main.c [new file with mode: 0644]
src/networkctl/port.c [new file with mode: 0644]
src/networkctl/port.h [new file with mode: 0644]
src/networkctl/terminal.c [new file with mode: 0644]
src/networkctl/terminal.h [new file with mode: 0644]
src/networkctl/zone.c [new file with mode: 0644]
src/networkctl/zone.h [new file with mode: 0644]
src/networkd/address.h [new file with mode: 0644]
src/networkd/bus.c [new file with mode: 0644]
src/networkd/bus.h [new file with mode: 0644]
src/networkd/config.c [new file with mode: 0644]
src/networkd/config.h [new file with mode: 0644]
src/networkd/daemon-bus.c [new file with mode: 0644]
src/networkd/daemon-bus.h [new file with mode: 0644]
src/networkd/daemon.c [new file with mode: 0644]
src/networkd/daemon.h [new file with mode: 0644]
src/networkd/devmon.c [new file with mode: 0644]
src/networkd/devmon.h [new file with mode: 0644]
src/networkd/json.h [new file with mode: 0644]
src/networkd/link.c [new file with mode: 0644]
src/networkd/link.h [new file with mode: 0644]
src/networkd/links.c [new file with mode: 0644]
src/networkd/links.h [new file with mode: 0644]
src/networkd/logging.c [new file with mode: 0644]
src/networkd/logging.h [new file with mode: 0644]
src/networkd/main.c [new file with mode: 0644]
src/networkd/networkd.service.in [new file with mode: 0644]
src/networkd/org.ipfire.network1.conf [new file with mode: 0644]
src/networkd/org.ipfire.network1.policy [new file with mode: 0644]
src/networkd/org.ipfire.network1.service [new file with mode: 0644]
src/networkd/port-bonding.c [new file with mode: 0644]
src/networkd/port-bonding.h [new file with mode: 0644]
src/networkd/port-bus.c [new file with mode: 0644]
src/networkd/port-bus.h [new file with mode: 0644]
src/networkd/port-dummy.c [new file with mode: 0644]
src/networkd/port-dummy.h [new file with mode: 0644]
src/networkd/port-ethernet.c [new file with mode: 0644]
src/networkd/port-ethernet.h [new file with mode: 0644]
src/networkd/port-veth.c [new file with mode: 0644]
src/networkd/port-veth.h [new file with mode: 0644]
src/networkd/port-vlan.c [new file with mode: 0644]
src/networkd/port-vlan.h [new file with mode: 0644]
src/networkd/port.c [new file with mode: 0644]
src/networkd/port.h [new file with mode: 0644]
src/networkd/ports.c [new file with mode: 0644]
src/networkd/ports.h [new file with mode: 0644]
src/networkd/stats-collector.c [new file with mode: 0644]
src/networkd/stats-collector.h [new file with mode: 0644]
src/networkd/string.h [new file with mode: 0644]
src/networkd/util.c [new file with mode: 0644]
src/networkd/util.h [new file with mode: 0644]
src/networkd/zone-bus.c [new file with mode: 0644]
src/networkd/zone-bus.h [new file with mode: 0644]
src/networkd/zone.c [new file with mode: 0644]
src/networkd/zone.h [new file with mode: 0644]
src/networkd/zones.c [new file with mode: 0644]
src/networkd/zones.h [new file with mode: 0644]
test/networkd/00_launch.t/config/settings [new file with mode: 0644]
test/networkd/00_launch.t/test.sh [new file with mode: 0644]
test/networkd/01_dummy.t/config/ports/d0 [new file with mode: 0644]
test/networkd/01_dummy.t/config/ports/d1 [new file with mode: 0644]
test/networkd/01_dummy.t/config/settings [new file with mode: 0644]
test/networkd/01_dummy.t/test.sh [new file with mode: 0644]
test/networkd/test.sh [new file with mode: 0644]

index bb093d362108af04c3590e866dc540d386a0d180..c45db754c1f0bb194ae848ffd7ae8849f975b8eb 100644 (file)
@@ -3,10 +3,13 @@
 /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
index a5ea1235c7b8a774ead5ce3c0ec6aa800ef037e2..7ca12292dc6889852f0eaf41f3fc299ba7f9033e 100644 (file)
@@ -31,6 +31,7 @@ AUTOMAKE_OPTIONS = color-tests
 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
@@ -50,7 +51,7 @@ hooks_zonesdir   = $(hooksdir)/zones
 
 triggersdir      = $(networkdir)/triggers
 
-logdir           = $(localestatedir)/log/network
+logdir           = $(localstatedir)/log/network
 utildir          = $(networkdir)
 
 CLEANFILES =
@@ -58,8 +59,14 @@ DISTCLEANFILES =
 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) \
@@ -298,6 +305,124 @@ EXTRA_DIST += \
 
 # ------------------------------------------------------------------------------
 
+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 \
@@ -360,7 +485,7 @@ UNINSTALL_EXEC_HOOKS += ppp-uninstall-hook
 # ------------------------------------------------------------------------------
 
 if HAVE_SYSTEMD
-systemdsystemunit_DATA = \
+systemdsystemunit_DATA += \
        src/systemd/firewall.service \
        src/systemd/firewall-init.service \
        src/systemd/network-init.service \
@@ -396,6 +521,11 @@ dist_sysctl_DATA = \
 
 # ------------------------------------------------------------------------------
 
+dist_modprobe_DATA = \
+       src/modprobe.d/no-copybreak.conf
+
+# ------------------------------------------------------------------------------
+
 dist_bashcompletion_SCRIPTS = \
        src/bash-completion/network
 
@@ -536,6 +666,7 @@ substitutions = \
        '|builddir=$(abs_builddir)|' \
        '|prefix=$(prefix)|' \
        '|exec_prefix=$(exec_prefix)|' \
+       '|bindir=$(bindir)|' \
        '|sbindir=$(sbindir)|' \
        '|networkdir=$(networkdir)|' \
        '|helpersdir=$(helpersdir)|' \
@@ -568,12 +699,10 @@ TESTS_ENVIRONMENT = \
 
 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 \
@@ -584,6 +713,24 @@ TESTS = \
        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
index b226d8db6cdf4c4d787156ac9a797afd70351814..209f43da6882e3b93c71a14a9d4dd5f1642289e7 100644 (file)
@@ -1,6 +1,6 @@
 CIPHERS="CHACHA20-POLY1305 AES128-GCM128"
 COMPRESSION="off"
-GROUP_TYPES="ECP521 ECP384 ECP256 ECP224 ECP192 CURVE25519"
+GROUP_TYPES="CURVE25519 CURVE448 ECP521 ECP384 ECP256 ECP224 ECP192"
 INTEGRITIES="SHA256"
 PSEUDO_RANDOM_FUNCTIONS="SHA256"
 KEY_EXCHANGE="ikev2"
index db30e69c9d5827d0436ced86e53db66f0db642ee..6ceb0c4859a44f5ccb8c42d96536a46ffe6d6b50 100644 (file)
@@ -1,7 +1,7 @@
 KEY_EXCHANGE="ikev2"
 CIPHERS="CHACHA20-POLY1305 AES256-GCM128 AES256-CBC AES192-GCM128 AES192-CBC AES128-GCM128 AES128-CBC"
 INTEGRITIES="SHA512 SHA384 SHA256"
-GROUP_TYPES="CURVE25519 ECP521 ECP384 ECP256 ECP224 ECP192 MODP8192 MODP6144 MODP4096 MODP2048"
+GROUP_TYPES="CURVE25519 CURVE448 ECP521 ECP384 ECP256 ECP224 ECP192 MODP8192 MODP6144 MODP4096 MODP2048"
 PSEUDO_RANDOM_FUNCTIONS="SHA512 SHA384 SHA256"
 LIFETIME="28800"
 PFS="on"
index 37c17e3e42ecbb1d87605c2b0b3ae98462859fe9..7883ae9ee44bdf37066f744d9106f14b7dc4532f 100644 (file)
@@ -28,6 +28,8 @@ AC_INIT([network],
 
 AC_CONFIG_AUX_DIR([build-aux])
 
+AC_USE_SYSTEM_EXTENSIONS
+AC_SYS_LARGEFILE
 AC_PREFIX_DEFAULT([/usr])
 
 AM_INIT_AUTOMAKE([
@@ -66,34 +68,20 @@ AC_PROG_CC_C_O
 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)
 
 # ------------------------------------------------------------------------------
 
@@ -131,6 +119,43 @@ AC_ARG_ENABLE(manpages, AS_HELP_STRING([--disable-man-pages],
 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]),
@@ -147,7 +172,10 @@ AM_CONDITIONAL(HAVE_UDEV, [test -n "$with_udevdir"])
 
 # ------------------------------------------------------------------------------
 
+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])
 
 # ------------------------------------------------------------------------------
 
@@ -173,6 +201,9 @@ AC_MSG_RESULT([
 
        prefix:               $prefix
 
+       dbuspolicydir:        ${dbuspolicydir}
+       dbussystembusdir:     ${dbussystembusdir}
+       polkitpolicydir:      ${polkitpolicydir}
        systemdsystemunitdir: $systemdsystemunitdir
        udevdir:              $udevdir
 
index 25347a818c2fa2a453633a1221a83dfde302fa52..d60d41d82bab5d967e0caa9ef89f4d98768fa17d 100644 (file)
@@ -1,7 +1,7 @@
-= 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]
@@ -27,7 +27,7 @@ The following commands are understood:
 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:
@@ -58,7 +58,7 @@ include::include-description.txt[]
        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.
index d1d720b63b0342346b83599a29e29df1042a6f82..138e8210e12096dc696ba3c47287dac2acf330fe 100644 (file)
@@ -263,6 +263,9 @@ declare -A VPN_SUPPORTED_GROUP_TYPES=(
 
        # Curve25519
        [CURVE25519]="256 bit Elliptic Curve 25519"
+
+       # Curve448
+       [CURVE448]="224 bit Elliptic Curve 448"
 )
 
 declare -A GROUP_TYPE_TO_STRONGSWAN=(
@@ -289,8 +292,9 @@ declare -A GROUP_TYPE_TO_STRONGSWAN=(
        [ECP384BP]="ecp384bp"
        [ECP512BP]="ecp512bp"
 
-       # Curve25519
+       # More Curves
        [CURVE25519]="curve25519"
+       [CURVE448]="curve448"
 )
 
 cli_vpn_security_policies() {
index 2919fc9eb37331f1eaed5a333be682df7b91aafb..e69fd04ecde6efd1dec8e2cc59f22dbe30ce7962 100644 (file)
@@ -36,7 +36,7 @@ void network_set_log_fn(struct network_ctx* ctx,
 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
 
diff --git a/src/modprobe.d/no-copybreak.conf b/src/modprobe.d/no-copybreak.conf
new file mode 100644 (file)
index 0000000..97ea886
--- /dev/null
@@ -0,0 +1,44 @@
+#
+# 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
diff --git a/src/networkctl/command.c b/src/networkctl/command.c
new file mode 100644 (file)
index 0000000..99202dd
--- /dev/null
@@ -0,0 +1,54 @@
+/*#############################################################################
+#                                                                             #
+# 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);
+}
diff --git a/src/networkctl/command.h b/src/networkctl/command.h
new file mode 100644 (file)
index 0000000..f8f295e
--- /dev/null
@@ -0,0 +1,34 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkctl/main.c b/src/networkctl/main.c
new file mode 100644 (file)
index 0000000..fde77b8
--- /dev/null
@@ -0,0 +1,126 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkctl/port.c b/src/networkctl/port.c
new file mode 100644 (file)
index 0000000..439177d
--- /dev/null
@@ -0,0 +1,240 @@
+/*#############################################################################
+#                                                                             #
+# 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);
+}
diff --git a/src/networkctl/port.h b/src/networkctl/port.h
new file mode 100644 (file)
index 0000000..2326ce6
--- /dev/null
@@ -0,0 +1,26 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkctl/terminal.c b/src/networkctl/terminal.c
new file mode 100644 (file)
index 0000000..de7cd8d
--- /dev/null
@@ -0,0 +1,51 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkctl/terminal.h b/src/networkctl/terminal.h
new file mode 100644 (file)
index 0000000..b7bffec
--- /dev/null
@@ -0,0 +1,83 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkctl/zone.c b/src/networkctl/zone.c
new file mode 100644 (file)
index 0000000..ee1d0a2
--- /dev/null
@@ -0,0 +1,97 @@
+/*#############################################################################
+#                                                                             #
+# 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);
+}
diff --git a/src/networkctl/zone.h b/src/networkctl/zone.h
new file mode 100644 (file)
index 0000000..5eddd98
--- /dev/null
@@ -0,0 +1,26 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/address.h b/src/networkd/address.h
new file mode 100644 (file)
index 0000000..afcbca1
--- /dev/null
@@ -0,0 +1,85 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/bus.c b/src/networkd/bus.c
new file mode 100644 (file)
index 0000000..8158c84
--- /dev/null
@@ -0,0 +1,182 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/bus.h b/src/networkd/bus.h
new file mode 100644 (file)
index 0000000..29b1b4a
--- /dev/null
@@ -0,0 +1,56 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/config.c b/src/networkd/config.c
new file mode 100644 (file)
index 0000000..53fd8e3
--- /dev/null
@@ -0,0 +1,814 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/config.h b/src/networkd/config.h
new file mode 100644 (file)
index 0000000..3e7c097
--- /dev/null
@@ -0,0 +1,145 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/daemon-bus.c b/src/networkd/daemon-bus.c
new file mode 100644 (file)
index 0000000..f5f7abd
--- /dev/null
@@ -0,0 +1,172 @@
+/*#############################################################################
+#                                                                             #
+# 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),
+};
diff --git a/src/networkd/daemon-bus.h b/src/networkd/daemon-bus.h
new file mode 100644 (file)
index 0000000..c314963
--- /dev/null
@@ -0,0 +1,28 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
new file mode 100644 (file)
index 0000000..dfbcc15
--- /dev/null
@@ -0,0 +1,698 @@
+/*#############################################################################
+#                                                                             #
+# 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);
+}
diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
new file mode 100644 (file)
index 0000000..2d56d79
--- /dev/null
@@ -0,0 +1,85 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/devmon.c b/src/networkd/devmon.c
new file mode 100644 (file)
index 0000000..c751985
--- /dev/null
@@ -0,0 +1,94 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/devmon.h b/src/networkd/devmon.h
new file mode 100644 (file)
index 0000000..0d8970a
--- /dev/null
@@ -0,0 +1,28 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/json.h b/src/networkd/json.h
new file mode 100644 (file)
index 0000000..6c2f66f
--- /dev/null
@@ -0,0 +1,99 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/link.c b/src/networkd/link.c
new file mode 100644 (file)
index 0000000..cb79dd2
--- /dev/null
@@ -0,0 +1,735 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/link.h b/src/networkd/link.h
new file mode 100644 (file)
index 0000000..c2f7b7e
--- /dev/null
@@ -0,0 +1,57 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/links.c b/src/networkd/links.c
new file mode 100644 (file)
index 0000000..40926f3
--- /dev/null
@@ -0,0 +1,215 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/links.h b/src/networkd/links.h
new file mode 100644 (file)
index 0000000..21e6cfb
--- /dev/null
@@ -0,0 +1,42 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/logging.c b/src/networkd/logging.c
new file mode 100644 (file)
index 0000000..c4809e8
--- /dev/null
@@ -0,0 +1,65 @@
+/*#############################################################################
+#                                                                             #
+# 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);
+}
diff --git a/src/networkd/logging.h b/src/networkd/logging.h
new file mode 100644 (file)
index 0000000..e835064
--- /dev/null
@@ -0,0 +1,37 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/main.c b/src/networkd/main.c
new file mode 100644 (file)
index 0000000..f5f09f5
--- /dev/null
@@ -0,0 +1,237 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/networkd.service.in b/src/networkd/networkd.service.in
new file mode 100644 (file)
index 0000000..7ee8fad
--- /dev/null
@@ -0,0 +1,45 @@
+[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
diff --git a/src/networkd/org.ipfire.network1.conf b/src/networkd/org.ipfire.network1.conf
new file mode 100644 (file)
index 0000000..96e8e15
--- /dev/null
@@ -0,0 +1,16 @@
+<?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>
diff --git a/src/networkd/org.ipfire.network1.policy b/src/networkd/org.ipfire.network1.policy
new file mode 100644 (file)
index 0000000..46318f1
--- /dev/null
@@ -0,0 +1,19 @@
+<?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>
diff --git a/src/networkd/org.ipfire.network1.service b/src/networkd/org.ipfire.network1.service
new file mode 100644 (file)
index 0000000..fdeda66
--- /dev/null
@@ -0,0 +1,5 @@
+[D-BUS Service]
+Name=org.ipfire.network1
+Exec=/bin/false
+User=root
+SystemdService=dbus-org.ipfire.network1.service
diff --git a/src/networkd/port-bonding.c b/src/networkd/port-bonding.c
new file mode 100644 (file)
index 0000000..6064957
--- /dev/null
@@ -0,0 +1,117 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/port-bonding.h b/src/networkd/port-bonding.h
new file mode 100644 (file)
index 0000000..e5c8c32
--- /dev/null
@@ -0,0 +1,47 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/port-bus.c b/src/networkd/port-bus.c
new file mode 100644 (file)
index 0000000..41f8ec4
--- /dev/null
@@ -0,0 +1,169 @@
+/*#############################################################################
+#                                                                             #
+# 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,
+};
diff --git a/src/networkd/port-bus.h b/src/networkd/port-bus.h
new file mode 100644 (file)
index 0000000..373c281
--- /dev/null
@@ -0,0 +1,28 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/port-dummy.c b/src/networkd/port-dummy.c
new file mode 100644 (file)
index 0000000..8a44008
--- /dev/null
@@ -0,0 +1,25 @@
+/*#############################################################################
+#                                                                             #
+# 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",
+};
diff --git a/src/networkd/port-dummy.h b/src/networkd/port-dummy.h
new file mode 100644 (file)
index 0000000..34a7265
--- /dev/null
@@ -0,0 +1,28 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/port-ethernet.c b/src/networkd/port-ethernet.c
new file mode 100644 (file)
index 0000000..a48b45e
--- /dev/null
@@ -0,0 +1,63 @@
+/*#############################################################################
+#                                                                             #
+# 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,
+};
diff --git a/src/networkd/port-ethernet.h b/src/networkd/port-ethernet.h
new file mode 100644 (file)
index 0000000..50dade2
--- /dev/null
@@ -0,0 +1,34 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/port-veth.c b/src/networkd/port-veth.c
new file mode 100644 (file)
index 0000000..029ef50
--- /dev/null
@@ -0,0 +1,81 @@
+/*#############################################################################
+#                                                                             #
+# 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,
+};
diff --git a/src/networkd/port-veth.h b/src/networkd/port-veth.h
new file mode 100644 (file)
index 0000000..aa4a03b
--- /dev/null
@@ -0,0 +1,35 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
new file mode 100644 (file)
index 0000000..c759f71
--- /dev/null
@@ -0,0 +1,242 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/port-vlan.h b/src/networkd/port-vlan.h
new file mode 100644 (file)
index 0000000..c807238
--- /dev/null
@@ -0,0 +1,66 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/port.c b/src/networkd/port.c
new file mode 100644 (file)
index 0000000..141bba5
--- /dev/null
@@ -0,0 +1,716 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/port.h b/src/networkd/port.h
new file mode 100644 (file)
index 0000000..9770079
--- /dev/null
@@ -0,0 +1,137 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/ports.c b/src/networkd/ports.c
new file mode 100644 (file)
index 0000000..95a13e3
--- /dev/null
@@ -0,0 +1,245 @@
+/*#############################################################################
+#                                                                             #
+# 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);
+}
diff --git a/src/networkd/ports.h b/src/networkd/ports.h
new file mode 100644 (file)
index 0000000..4e41f11
--- /dev/null
@@ -0,0 +1,48 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/stats-collector.c b/src/networkd/stats-collector.c
new file mode 100644 (file)
index 0000000..c10602e
--- /dev/null
@@ -0,0 +1,197 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/stats-collector.h b/src/networkd/stats-collector.h
new file mode 100644 (file)
index 0000000..ea11c11
--- /dev/null
@@ -0,0 +1,37 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/string.h b/src/networkd/string.h
new file mode 100644 (file)
index 0000000..a36b023
--- /dev/null
@@ -0,0 +1,202 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/util.c b/src/networkd/util.c
new file mode 100644 (file)
index 0000000..e2f5cf1
--- /dev/null
@@ -0,0 +1,21 @@
+/*#############################################################################
+#                                                                             #
+# 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"
diff --git a/src/networkd/util.h b/src/networkd/util.h
new file mode 100644 (file)
index 0000000..11317ff
--- /dev/null
@@ -0,0 +1,24 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/zone-bus.c b/src/networkd/zone-bus.c
new file mode 100644 (file)
index 0000000..a06deb5
--- /dev/null
@@ -0,0 +1,122 @@
+/*#############################################################################
+#                                                                             #
+# 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,
+};
diff --git a/src/networkd/zone-bus.h b/src/networkd/zone-bus.h
new file mode 100644 (file)
index 0000000..db257f7
--- /dev/null
@@ -0,0 +1,28 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/zone.c b/src/networkd/zone.c
new file mode 100644 (file)
index 0000000..1610dc0
--- /dev/null
@@ -0,0 +1,326 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/networkd/zone.h b/src/networkd/zone.h
new file mode 100644 (file)
index 0000000..2ece268
--- /dev/null
@@ -0,0 +1,84 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/src/networkd/zones.c b/src/networkd/zones.c
new file mode 100644 (file)
index 0000000..654e637
--- /dev/null
@@ -0,0 +1,261 @@
+/*#############################################################################
+#                                                                             #
+# 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);
+}
diff --git a/src/networkd/zones.h b/src/networkd/zones.h
new file mode 100644 (file)
index 0000000..ad39fd2
--- /dev/null
@@ -0,0 +1,49 @@
+/*#############################################################################
+#                                                                             #
+# 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 */
diff --git a/test/networkd/00_launch.t/config/settings b/test/networkd/00_launch.t/config/settings
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/networkd/00_launch.t/test.sh b/test/networkd/00_launch.t/test.sh
new file mode 100644 (file)
index 0000000..f3d7bbc
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+# Simply run networkctl to check whether it works
+./networkctl --version
diff --git a/test/networkd/01_dummy.t/config/ports/d0 b/test/networkd/01_dummy.t/config/ports/d0
new file mode 100644 (file)
index 0000000..36a350b
--- /dev/null
@@ -0,0 +1,2 @@
+TYPE=dummy
+ADDRESS=00:11:22:33:44:55
diff --git a/test/networkd/01_dummy.t/config/ports/d1 b/test/networkd/01_dummy.t/config/ports/d1
new file mode 100644 (file)
index 0000000..d040863
--- /dev/null
@@ -0,0 +1,2 @@
+TYPE=dummy
+ADDRESS=00:55:44:33:22:11
diff --git a/test/networkd/01_dummy.t/config/settings b/test/networkd/01_dummy.t/config/settings
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/networkd/01_dummy.t/test.sh b/test/networkd/01_dummy.t/test.sh
new file mode 100644 (file)
index 0000000..fff0d69
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# Dump status of d0
+./networkctl port dump d0
+
+# Dump status of d1
+./networkctl port dump d1
diff --git a/test/networkd/test.sh b/test/networkd/test.sh
new file mode 100644 (file)
index 0000000..3eda034
--- /dev/null
@@ -0,0 +1,148 @@
+#!/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 $?