From: Michael Tremer Date: Tue, 19 Sep 2023 12:54:53 +0000 (+0000) Subject: Makefile: Fix typo in localstatedir X-Git-Url: http://git.ipfire.org/?p=people%2Fms%2Fnetwork.git;a=commitdiff_plain;h=HEAD;hp=2cb783babd59716366984c8908e70285f23347f3 Makefile: Fix typo in localstatedir Signed-off-by: Michael Tremer --- diff --git a/.gitignore b/.gitignore index bb093d36..c45db754 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile.am b/Makefile.am index a5ea1235..7ca12292 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/config/vpn/security-policies/performance b/config/vpn/security-policies/performance index b226d8db..209f43da 100644 --- a/config/vpn/security-policies/performance +++ b/config/vpn/security-policies/performance @@ -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" diff --git a/config/vpn/security-policies/system b/config/vpn/security-policies/system index db30e69c..6ceb0c48 100644 --- a/config/vpn/security-policies/system +++ b/config/vpn/security-policies/system @@ -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" diff --git a/configure.ac b/configure.ac index 37c17e3e..7883ae9e 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/man/network-vpn-ipsec.txt b/man/network-vpn-ipsec.txt index 25347a81..d60d41d8 100644 --- a/man/network-vpn-ipsec.txt +++ b/man/network-vpn-ipsec.txt @@ -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. diff --git a/src/functions/functions.vpn-security-policies b/src/functions/functions.vpn-security-policies index d1d720b6..138e8210 100644 --- a/src/functions/functions.vpn-security-policies +++ b/src/functions/functions.vpn-security-policies @@ -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() { diff --git a/src/libnetwork/network/libnetwork.h b/src/libnetwork/network/libnetwork.h index 2919fc9e..e69fd04e 100644 --- a/src/libnetwork/network/libnetwork.h +++ b/src/libnetwork/network/libnetwork.h @@ -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 index 00000000..97ea8866 --- /dev/null +++ b/src/modprobe.d/no-copybreak.conf @@ -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 index 00000000..99202dd0 --- /dev/null +++ b/src/networkctl/command.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include + +#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 index 00000000..f8f295eb --- /dev/null +++ b/src/networkctl/command.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKCTL_COMMAND_H +#define NETWORKCTL_COMMAND_H + +#include + +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 index 00000000..fde77b82 --- /dev/null +++ b/src/networkctl/main.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include + +#include + +#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 index 00000000..439177d4 --- /dev/null +++ b/src/networkctl/port.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include + +#include + +#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 index 00000000..2326ce68 --- /dev/null +++ b/src/networkctl/port.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..de7cd8dd --- /dev/null +++ b/src/networkctl/terminal.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include + +#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 index 00000000..b7bffec7 --- /dev/null +++ b/src/networkctl/terminal.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..ee1d0a28 --- /dev/null +++ b/src/networkctl/zone.c @@ -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 . # +# # +#############################################################################*/ + +#include + +#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 index 00000000..5eddd989 --- /dev/null +++ b/src/networkctl/zone.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..afcbca15 --- /dev/null +++ b/src/networkd/address.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_ADDRESS_H +#define NETWORKD_ADDRESS_H + +#include +#include +#include +#include + +#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 index 00000000..8158c84f --- /dev/null +++ b/src/networkd/bus.c @@ -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 . # +# # +#############################################################################*/ + +#include + +#include +#include + +#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 index 00000000..29b1b4a5 --- /dev/null +++ b/src/networkd/bus.h @@ -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 . # +# # +#############################################################################*/ + +#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 +#include + +#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 index 00000000..53fd8e30 --- /dev/null +++ b/src/networkd/config.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 00000000..3e7c0977 --- /dev/null +++ b/src/networkd/config.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_CONFIG_H +#define NETWORKD_CONFIG_H + +#include +#include + +#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 index 00000000..f5f7abd0 --- /dev/null +++ b/src/networkd/daemon-bus.c @@ -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 . # +# # +#############################################################################*/ + +#include + +#include + +#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 index 00000000..c3149633 --- /dev/null +++ b/src/networkd/daemon-bus.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..dfbcc151 --- /dev/null +++ b/src/networkd/daemon.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 index 00000000..2d56d797 --- /dev/null +++ b/src/networkd/daemon.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_DAEMON_H +#define NETWORKD_DAEMON_H + +#include +#include + +#include +#include + +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 index 00000000..c751985c --- /dev/null +++ b/src/networkd/devmon.c @@ -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 . # +# # +#############################################################################*/ + +#include + +#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 index 00000000..0d8970aa --- /dev/null +++ b/src/networkd/devmon.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_DEVMON_H +#define NETWORKD_DEVMON_H + +#include + +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 index 00000000..6c2f66f1 --- /dev/null +++ b/src/networkd/json.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_JSON_H +#define NETWORKD_JSON_H + +#include +#include + +#include + +// 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 index 00000000..cb79dd28 --- /dev/null +++ b/src/networkd/link.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include + +#include +#include + +#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 index 00000000..c2f7b7e6 --- /dev/null +++ b/src/networkd/link.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_LINK_H +#define NETWORKD_LINK_H + +#include + +#include + +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 index 00000000..40926f3e --- /dev/null +++ b/src/networkd/links.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include +#include + +#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 index 00000000..21e6cfb3 --- /dev/null +++ b/src/networkd/links.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..c4809e84 --- /dev/null +++ b/src/networkd/logging.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include + +#include + +#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 index 00000000..e835064d --- /dev/null +++ b/src/networkd/logging.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_LOGGING_H +#define NETWORKD_LOGGING_H + +#include + +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 index 00000000..f5f09f5a --- /dev/null +++ b/src/networkd/main.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 00000000..7ee8fadf --- /dev/null +++ b/src/networkd/networkd.service.in @@ -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 index 00000000..96e8e15a --- /dev/null +++ b/src/networkd/org.ipfire.network1.conf @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/src/networkd/org.ipfire.network1.policy b/src/networkd/org.ipfire.network1.policy new file mode 100644 index 00000000..46318f11 --- /dev/null +++ b/src/networkd/org.ipfire.network1.policy @@ -0,0 +1,19 @@ + + + + + The IPFire Project + https://www.ipfire.org + + + Reload Network Settings + Authentication is required to reload network settings. + + auth_admin + auth_admin + auth_admin_keep + + unix-user:network + + diff --git a/src/networkd/org.ipfire.network1.service b/src/networkd/org.ipfire.network1.service new file mode 100644 index 00000000..fdeda666 --- /dev/null +++ b/src/networkd/org.ipfire.network1.service @@ -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 index 00000000..60649572 --- /dev/null +++ b/src/networkd/port-bonding.c @@ -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 . # +# # +#############################################################################*/ + +#include + +#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 index 00000000..e5c8c322 --- /dev/null +++ b/src/networkd/port-bonding.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_PORT_BONDING_H +#define NETWORKD_PORT_BONDING_H + +#include + +#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 index 00000000..41f8ec41 --- /dev/null +++ b/src/networkd/port-bus.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include + +#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 index 00000000..373c2812 --- /dev/null +++ b/src/networkd/port-bus.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..8a440083 --- /dev/null +++ b/src/networkd/port-dummy.c @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..34a72656 --- /dev/null +++ b/src/networkd/port-dummy.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..a48b45e4 --- /dev/null +++ b/src/networkd/port-ethernet.c @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..50dade2e --- /dev/null +++ b/src/networkd/port-ethernet.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..029ef50c --- /dev/null +++ b/src/networkd/port-veth.c @@ -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 . # +# # +#############################################################################*/ + +#include + +#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 index 00000000..aa4a03b2 --- /dev/null +++ b/src/networkd/port-veth.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..c759f719 --- /dev/null +++ b/src/networkd/port-vlan.c @@ -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 . # +# # +#############################################################################*/ + +#include + +#include + +#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 index 00000000..c8072389 --- /dev/null +++ b/src/networkd/port-vlan.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_PORT_VLAN_H +#define NETWORKD_PORT_VLAN_H + +#include +#include + +#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 index 00000000..141bba59 --- /dev/null +++ b/src/networkd/port.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include +#include + +#include + +#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 index 00000000..97700795 --- /dev/null +++ b/src/networkd/port.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_PORT_H +#define NETWORKD_PORT_H + +#include + +#include + +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 index 00000000..95a13e37 --- /dev/null +++ b/src/networkd/ports.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include + +#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 index 00000000..4e41f112 --- /dev/null +++ b/src/networkd/ports.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..c10602ec --- /dev/null +++ b/src/networkd/stats-collector.c @@ -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 . # +# # +#############################################################################*/ + +#include + +#include +#include + +#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 index 00000000..ea11c117 --- /dev/null +++ b/src/networkd/stats-collector.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_STATS_COLLECTOR_H +#define NETWORKD_STATS_COLLECTOR_H + +#include + +#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 index 00000000..a36b0238 --- /dev/null +++ b/src/networkd/string.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef NETWORKD_STRING_H +#define NETWORKD_STRING_H + +#include +#include +#include +#include +#include + +#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 index 00000000..e2f5cf16 --- /dev/null +++ b/src/networkd/util.c @@ -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 . # +# # +#############################################################################*/ + +#include "util.h" diff --git a/src/networkd/util.h b/src/networkd/util.h new file mode 100644 index 00000000..11317ff1 --- /dev/null +++ b/src/networkd/util.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..a06deb5b --- /dev/null +++ b/src/networkd/zone-bus.c @@ -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 . # +# # +#############################################################################*/ + +#include + +#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 index 00000000..db257f73 --- /dev/null +++ b/src/networkd/zone-bus.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..1610dc0c --- /dev/null +++ b/src/networkd/zone.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include + +#include + +#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 index 00000000..2ece268c --- /dev/null +++ b/src/networkd/zone.h @@ -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 . # +# # +#############################################################################*/ + +#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 +#include + +#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 index 00000000..654e637e --- /dev/null +++ b/src/networkd/zones.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include +#include + +#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 index 00000000..ad39fd27 --- /dev/null +++ b/src/networkd/zones.h @@ -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 . # +# # +#############################################################################*/ + +#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 index 00000000..e69de29b diff --git a/test/networkd/00_launch.t/test.sh b/test/networkd/00_launch.t/test.sh new file mode 100644 index 00000000..f3d7bbc5 --- /dev/null +++ b/test/networkd/00_launch.t/test.sh @@ -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 index 00000000..36a350b3 --- /dev/null +++ b/test/networkd/01_dummy.t/config/ports/d0 @@ -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 index 00000000..d040863b --- /dev/null +++ b/test/networkd/01_dummy.t/config/ports/d1 @@ -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 index 00000000..e69de29b diff --git a/test/networkd/01_dummy.t/test.sh b/test/networkd/01_dummy.t/test.sh new file mode 100644 index 00000000..fff0d698 --- /dev/null +++ b/test/networkd/01_dummy.t/test.sh @@ -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 index 00000000..3eda034c --- /dev/null +++ b/test/networkd/test.sh @@ -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 . # +# # +############################################################################### + +# 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 $?