diff --git a/network/network.nm b/network/network.nm
index 85a9d15b3..2df8135b2 100644
--- a/network/network.nm
+++ b/network/network.nm
@@ -5,7 +5,7 @@
 
 name       = network
 version    = 010
-release    = 5
+release    = 6
 
 maintainer = Michael Tremer <michael.tremer@ipfire.org>
 groups     = Base Networking/Tools
@@ -24,9 +24,12 @@ source_dl  = https://source.ipfire.org/releases/network/
 
 build
 	requires
+		asciidoc
 		autoconf
 		automake
 		docbook-xsl
+		json-c-devel
+		libcap-devel
 		libnl3-devel
 		libxslt
 		systemd-devel
diff --git a/network/patches/0001-Bump-version-to-011.patch b/network/patches/0001-Bump-version-to-011.patch
new file mode 100644
index 000000000..2b9422662
--- /dev/null
+++ b/network/patches/0001-Bump-version-to-011.patch
@@ -0,0 +1,26 @@
+From af91a344198a1f3c47dc18905870818a0758d427 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 24 Sep 2018 21:55:51 +0100
+Subject: [PATCH 001/304] Bump version to 011
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ configure.ac | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/configure.ac b/configure.ac
+index 9baab31..08e9089 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -21,7 +21,7 @@
+ AC_PREREQ([2.64])
+ 
+ AC_INIT([network],
+-	[010],
++	[011],
+ 	[info@ipfire.org],
+ 	[network],
+ 	[http://www.ipfire.org/])
+-- 
+2.39.2
+
diff --git a/network/patches/0002-bridge-Check-input-and-return-useful-errors.patch b/network/patches/0002-bridge-Check-input-and-return-useful-errors.patch
new file mode 100644
index 000000000..91cbdec60
--- /dev/null
+++ b/network/patches/0002-bridge-Check-input-and-return-useful-errors.patch
@@ -0,0 +1,144 @@
+From b99bbd83b94d380bd07dcace8fb0e95b76b01e9f Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 24 Sep 2018 23:13:22 +0200
+Subject: [PATCH 002/304] bridge: Check input and return useful errors
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/zones/bridge | 80 +++++++++++++++++++++++++++++++++---------
+ 1 file changed, 63 insertions(+), 17 deletions(-)
+
+diff --git a/src/hooks/zones/bridge b/src/hooks/zones/bridge
+index 38b2b5f..838a513 100644
+--- a/src/hooks/zones/bridge
++++ b/src/hooks/zones/bridge
+@@ -23,13 +23,12 @@
+ 
+ HOOK_MANPAGE="network-zone-bridge"
+ 
+-HOOK_SETTINGS="HOOK STP STP_FORWARD_DELAY STP_HELLO STP_MAXAGE"
+-HOOK_SETTINGS="${HOOK_SETTINGS} STP_PRIORITY MAC MTU"
++HOOK_SETTINGS="HOOK ADDRESS STP STP_FORWARD_DELAY STP_HELLO STP_MAXAGE"
++HOOK_SETTINGS="${HOOK_SETTINGS} STP_PRIORITY MTU"
+ 
+ HOOK_PORT_SETTINGS="COST PRIORITY"
+ 
+ # Default values
+-MAC=""
+ MTU=1500
+ STP="on"
+ STP_FORWARD_DELAY=0
+@@ -38,7 +37,9 @@ STP_MAXAGE=20
+ STP_PRIORITY=512
+ 
+ hook_check_settings() {
+-	assert ismac MAC
++	assert ismac ADDRESS
++
++	# Spanning Tree Protocol
+ 	assert isbool STP
+ 	assert isinteger STP_HELLO
+ 	assert isinteger STP_FORWARD_DELAY
+@@ -49,33 +50,78 @@ hook_check_settings() {
+ hook_parse_cmdline() {
+ 	while [ $# -gt 0 ]; do
+ 		case "${1}" in
++			--address=*)
++				ADDRESS="$(cli_get_val "${1}")"
++
++				if ! mac_is_valid "${ADDRESS}"; then
++					error "Invalid MAC address: ${ADDRESS}"
++					return ${EXIT_ERROR}
++				fi
++				;;
++
++			--mtu=*)
++				MTU="$(cli_get_val "${1}")"
++
++				if ! mtu_is_valid "ethernet" "${MTU}"; then
++					error "Invalid MTU: ${MTU}"
++					return ${EXIT_ERROR}
++				fi
++				;;
++
+ 			--stp=*)
+-				STP=${1#--stp=}
++				STP="$(cli_get_val "${1}")"
++
++				if enabled STP; then
++					STP="on"
++				elif disabled STP; then
++					STP="off"
++				else
++					error "Invalid value for STP: ${STP}"
++					return ${EXIT_ERROR}
++				fi
+ 				;;
++
+ 			--stp-hello=*)
+-				STP_HELLO=${1#--stp-hello=}
++				STP_HELLO="$(cli_get_val "${1}")"
++
++				if ! isinteger STP_HELLO; then
++					error "Invalid STP hello time: ${STP_HELLO}"
++					return ${EXIT_ERROR}
++				fi
+ 				;;
++
+ 			--stp-forward-delay=*)
+-				STP_FORWARD_DELAY=${1#--stp-forward-delay=}
++				STP_FORWARD_DELAY="$(cli_get_val "${1}")"
++
++				if ! isinteger STP_FORWARD_DELAY; then
++					error "Invalid STP forwarding delay: ${STP_FORWARD_DELAY}"
++					return ${EXIT_ERROR}
++				fi
+ 				;;
++
+ 			--stp-priority=*)
+-				STP_PRIORITY=${1#--stp-priority=}
+-				;;
+-			--mtu=*)
+-				MTU=${1#--mtu=}
+-				;;
+-			--mac=*)
+-				MAC=${1#--mac=}
++				STP_PRIORITY="$(cli_get_val "${1}")"
++
++				if ! isinteger STP_PRIORITY; then
++					error "Invalid STP priority: ${STP_PRIORITY}"
++					return ${EXIT_ERROR}
++				fi
+ 				;;
++
+ 			*)
+-				warning "Ignoring unknown option '${1}'"
++				error "Unknown argument: ${1}"
++				return ${EXIT_ERROR}
+ 				;;
+ 		esac
+ 		shift
+ 	done
+ 
+ 	# Generate a random MAC address if the user passed no one
+-	isset MAC || MAC="$(mac_generate)"
++	if isset ADDRESS; then
++		ADDRESS="$(mac_generate)"
++	fi
++
++	return ${EXIT_OK}
+ }
+ 
+ hook_up() {
+@@ -87,7 +133,7 @@ hook_up() {
+ 	# Create the bridge if it does not already exist.
+ 	if ! device_exists "${zone}"; then
+ 		bridge_create "${zone}" \
+-			--address="${MAC}" \
++			--address="${ADDRESS}" \
+ 			--mtu="${MTU}"
+ 	fi
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0003-bridge-Fix-assertion-for-MTU.patch b/network/patches/0003-bridge-Fix-assertion-for-MTU.patch
new file mode 100644
index 000000000..ce712991d
--- /dev/null
+++ b/network/patches/0003-bridge-Fix-assertion-for-MTU.patch
@@ -0,0 +1,32 @@
+From d95e2fdc65aeeca72ef326102f26727199b27b95 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 24 Sep 2018 23:15:26 +0200
+Subject: [PATCH 003/304] bridge: Fix assertion for MTU
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/zones/bridge | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/hooks/zones/bridge b/src/hooks/zones/bridge
+index 838a513..d610814 100644
+--- a/src/hooks/zones/bridge
++++ b/src/hooks/zones/bridge
+@@ -38,13 +38,13 @@ STP_PRIORITY=512
+ 
+ hook_check_settings() {
+ 	assert ismac ADDRESS
++	assert isset MTU && assert mtu_is_valid "ethernet" "${MTU}"
+ 
+ 	# Spanning Tree Protocol
+ 	assert isbool STP
+ 	assert isinteger STP_HELLO
+ 	assert isinteger STP_FORWARD_DELAY
+ 	assert isinteger STP_PRIORITY
+-	assert isinteger MTU
+ }
+ 
+ hook_parse_cmdline() {
+-- 
+2.39.2
+
diff --git a/network/patches/0004-bridge-Reorder-functions-into-the-common-order.patch b/network/patches/0004-bridge-Reorder-functions-into-the-common-order.patch
new file mode 100644
index 000000000..d2a3d3810
--- /dev/null
+++ b/network/patches/0004-bridge-Reorder-functions-into-the-common-order.patch
@@ -0,0 +1,127 @@
+From 1fc4b3cac15c709b3a6f4a3171265a5cff793f47 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 24 Sep 2018 23:17:30 +0200
+Subject: [PATCH 004/304] bridge: Reorder functions into the common order
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/zones/bridge | 96 +++++++++++++++++++++---------------------
+ 1 file changed, 49 insertions(+), 47 deletions(-)
+
+diff --git a/src/hooks/zones/bridge b/src/hooks/zones/bridge
+index d610814..fb81673 100644
+--- a/src/hooks/zones/bridge
++++ b/src/hooks/zones/bridge
+@@ -172,53 +172,6 @@ hook_up() {
+ 	exit ${EXIT_OK}
+ }
+ 
+-hook_hotplug() {
+-	local zone="${1}"
+-	assert isset zone
+-
+-	case "$(hotplug_action)" in
+-		add)
+-			# Attach all ports when zone is coming up
+-			if hotplug_event_interface_is_zone "${zone}"; then
+-				# Bring up all ports
+-				local port
+-				for port in $(zone_get_ports "${zone}"); do
+-					log DEBUG "Trying to attach port ${port} to ${zone}"
+-
+-					hook_port_up "${zone}" "${port}"
+-				done
+-
+-			# Handle ports of this zone that have just been added
+-			elif hotplug_event_interface_is_port_of_zone "${zone}"; then
+-				# Attach the device if the parent bridge is up
+-				if zone_is_active "${zone}"; then
+-					hook_port_up "${zone}" "${INTERFACE}"
+-				fi
+-			fi
+-			;;
+-		remove)
+-			if hotplug_event_interface_is_zone "${zone}"; then
+-				# Bring down/destroy all ports
+-				local port
+-				for port in $(zone_get_ports "${zone}"); do
+-					log DEBUG "Trying to detach port ${port} from ${zone}"
+-
+-					hook_port_down "${zone}" "${port}"
+-				done
+-
+-			# Handle ports of this zone that have just been removed
+-			elif hotplug_event_interface_is_port_of_zone "${zone}"; then
+-				hook_port_down "${zone}" "${INTERFACE}"
+-			fi
+-			;;
+-		*)
+-			exit ${EXIT_NOT_HANDLED}
+-			;;
+-	esac
+-
+-	exit ${EXIT_OK}
+-}
+-
+ hook_down() {
+ 	local zone="${1}"
+ 	assert isset zone
+@@ -294,6 +247,55 @@ hook_status() {
+ 	exit ${EXIT_OK}
+ }
+ 
++hook_hotplug() {
++	local zone="${1}"
++	assert isset zone
++
++	case "$(hotplug_action)" in
++		add)
++			# Attach all ports when zone is coming up
++			if hotplug_event_interface_is_zone "${zone}"; then
++				# Bring up all ports
++				local port
++				for port in $(zone_get_ports "${zone}"); do
++					log DEBUG "Trying to attach port ${port} to ${zone}"
++
++					hook_port_up "${zone}" "${port}"
++				done
++
++			# Handle ports of this zone that have just been added
++			elif hotplug_event_interface_is_port_of_zone "${zone}"; then
++				# Attach the device if the parent bridge is up
++				if zone_is_active "${zone}"; then
++					hook_port_up "${zone}" "${INTERFACE}"
++				fi
++			fi
++			;;
++
++		remove)
++			if hotplug_event_interface_is_zone "${zone}"; then
++				# Bring down/destroy all ports
++				local port
++				for port in $(zone_get_ports "${zone}"); do
++					log DEBUG "Trying to detach port ${port} from ${zone}"
++
++					hook_port_down "${zone}" "${port}"
++				done
++
++			# Handle ports of this zone that have just been removed
++			elif hotplug_event_interface_is_port_of_zone "${zone}"; then
++				hook_port_down "${zone}" "${INTERFACE}"
++			fi
++			;;
++
++		*)
++			exit ${EXIT_NOT_HANDLED}
++			;;
++	esac
++
++	exit ${EXIT_OK}
++}
++
+ hook_check_port_settings() {
+ 	if isset COST; then
+ 		assert isinteger COST
+-- 
+2.39.2
+
diff --git a/network/patches/0005-bridge-Set-proper-defaults.patch b/network/patches/0005-bridge-Set-proper-defaults.patch
new file mode 100644
index 000000000..adbe32bea
--- /dev/null
+++ b/network/patches/0005-bridge-Set-proper-defaults.patch
@@ -0,0 +1,78 @@
+From c259c985bc98ad89350f81b68db58925163a43eb Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 24 Sep 2018 23:29:25 +0200
+Subject: [PATCH 005/304] bridge: Set proper defaults
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hook | 12 ++++++++++++
+ src/hooks/zones/bridge       | 20 +++++++++++++-------
+ 2 files changed, 25 insertions(+), 7 deletions(-)
+
+diff --git a/src/functions/functions.hook b/src/functions/functions.hook
+index ad51ad5..2f3ced0 100644
+--- a/src/functions/functions.hook
++++ b/src/functions/functions.hook
+@@ -124,6 +124,18 @@ hook_help() {
+ 	exit $?
+ }
+ 
++# Sets all settings in HOOK_SETTINGS to their DEFAULT_* values
++hook_set_defaults() {
++	local setting
++	for setting in ${HOOK_SETTINGS}; do
++		local default="DEFAULT_${setting}"
++
++		if isset ${default}; then
++			assign "${setting}" "${!default}"
++		fi
++	done
++}
++
+ config_get_hook() {
+ 	local config=${1}
+ 
+diff --git a/src/hooks/zones/bridge b/src/hooks/zones/bridge
+index fb81673..1144ba0 100644
+--- a/src/hooks/zones/bridge
++++ b/src/hooks/zones/bridge
+@@ -29,12 +29,10 @@ HOOK_SETTINGS="${HOOK_SETTINGS} STP_PRIORITY MTU"
+ HOOK_PORT_SETTINGS="COST PRIORITY"
+ 
+ # Default values
+-MTU=1500
+-STP="on"
+-STP_FORWARD_DELAY=0
+-STP_HELLO=2
+-STP_MAXAGE=20
+-STP_PRIORITY=512
++DEFAULT_STP_FORWARD_DELAY=0
++DEFAULT_STP_HELLO=2
++DEFAULT_STP_MAXAGE=20
++DEFAULT_STP_PRIORITY=512
+ 
+ hook_check_settings() {
+ 	assert ismac ADDRESS
+@@ -117,10 +115,18 @@ hook_parse_cmdline() {
+ 	done
+ 
+ 	# Generate a random MAC address if the user passed no one
+-	if isset ADDRESS; then
++	if ! isset ADDRESS; then
+ 		ADDRESS="$(mac_generate)"
+ 	fi
+ 
++	# Enable Spanning Tree Protocol by default
++	if ! isset STP; then
++		STP="on"
++	fi
++
++	# Set all other defaults
++	hook_set_defaults
++
+ 	return ${EXIT_OK}
+ }
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0006-bridge-Order-arguments-in-alphabetical-order.patch b/network/patches/0006-bridge-Order-arguments-in-alphabetical-order.patch
new file mode 100644
index 000000000..95f14cad5
--- /dev/null
+++ b/network/patches/0006-bridge-Order-arguments-in-alphabetical-order.patch
@@ -0,0 +1,46 @@
+From b76b7d88a5fc7271e9a16d4acb531cdfe45f3957 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 24 Sep 2018 23:31:43 +0200
+Subject: [PATCH 006/304] bridge: Order arguments in alphabetical order
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/zones/bridge | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/src/hooks/zones/bridge b/src/hooks/zones/bridge
+index 1144ba0..98aaef8 100644
+--- a/src/hooks/zones/bridge
++++ b/src/hooks/zones/bridge
+@@ -79,20 +79,20 @@ hook_parse_cmdline() {
+ 				fi
+ 				;;
+ 
+-			--stp-hello=*)
+-				STP_HELLO="$(cli_get_val "${1}")"
++			--stp-forward-delay=*)
++				STP_FORWARD_DELAY="$(cli_get_val "${1}")"
+ 
+-				if ! isinteger STP_HELLO; then
+-					error "Invalid STP hello time: ${STP_HELLO}"
++				if ! isinteger STP_FORWARD_DELAY; then
++					error "Invalid STP forwarding delay: ${STP_FORWARD_DELAY}"
+ 					return ${EXIT_ERROR}
+ 				fi
+ 				;;
+ 
+-			--stp-forward-delay=*)
+-				STP_FORWARD_DELAY="$(cli_get_val "${1}")"
++			--stp-hello=*)
++				STP_HELLO="$(cli_get_val "${1}")"
+ 
+-				if ! isinteger STP_FORWARD_DELAY; then
+-					error "Invalid STP forwarding delay: ${STP_FORWARD_DELAY}"
++				if ! isinteger STP_HELLO; then
++					error "Invalid STP hello time: ${STP_HELLO}"
+ 					return ${EXIT_ERROR}
+ 				fi
+ 				;;
+-- 
+2.39.2
+
diff --git a/network/patches/0007-bridge-Add-option-to-missing-stp-max-age.patch b/network/patches/0007-bridge-Add-option-to-missing-stp-max-age.patch
new file mode 100644
index 000000000..0a48d11c1
--- /dev/null
+++ b/network/patches/0007-bridge-Add-option-to-missing-stp-max-age.patch
@@ -0,0 +1,33 @@
+From 0f8d47058e6dedc5f20caf367a5296647ec950d1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 24 Sep 2018 23:32:40 +0200
+Subject: [PATCH 007/304] bridge: Add option to missing --stp-max-age=
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/zones/bridge | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/src/hooks/zones/bridge b/src/hooks/zones/bridge
+index 98aaef8..93a3a31 100644
+--- a/src/hooks/zones/bridge
++++ b/src/hooks/zones/bridge
+@@ -97,6 +97,15 @@ hook_parse_cmdline() {
+ 				fi
+ 				;;
+ 
++			--stp-max-age=*)
++				STP_MAXAGE="$(cli_get_val "${1}")"
++
++				if ! isinteger STP_MAXAGE; then
++					error "Invalid STP max age: ${STP_MAXAGE}"
++					return ${EXIT_ERROR}
++				fi
++				;;
++
+ 			--stp-priority=*)
+ 				STP_PRIORITY="$(cli_get_val "${1}")"
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0008-Remove-unused-function.patch b/network/patches/0008-Remove-unused-function.patch
new file mode 100644
index 000000000..90cdc0976
--- /dev/null
+++ b/network/patches/0008-Remove-unused-function.patch
@@ -0,0 +1,29 @@
+From 5b29153cd4527392d6ca4bf8d3cba491db8d490e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 1 Oct 2018 00:07:37 +0200
+Subject: [PATCH 008/304] Remove unused function
+
+Fixes: #11423
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.zone | 4 ----
+ 1 file changed, 4 deletions(-)
+
+diff --git a/src/functions/functions.zone b/src/functions/functions.zone
+index b9d475f..57e0b71 100644
+--- a/src/functions/functions.zone
++++ b/src/functions/functions.zone
+@@ -619,10 +619,6 @@ zone_config_list() {
+ 	done
+ }
+ 
+-zone_config_show() {
+-	zone_config_cmd "show" "$@"
+-}
+-
+ # Returns a list of all used ids for a zone
+ zone_config_list_ids() {
+ 	assert [ $# -eq 1 ]
+-- 
+2.39.2
+
diff --git a/network/patches/0009-bonding-Validate-any-MAC-address-passed.patch b/network/patches/0009-bonding-Validate-any-MAC-address-passed.patch
new file mode 100644
index 000000000..91c45617c
--- /dev/null
+++ b/network/patches/0009-bonding-Validate-any-MAC-address-passed.patch
@@ -0,0 +1,33 @@
+From 7b9557028a381206c573e42a7f5294d20aa0609b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 1 Oct 2018 01:02:27 +0200
+Subject: [PATCH 009/304] bonding; Validate any MAC address passed
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/bonding | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/src/hooks/ports/bonding b/src/hooks/ports/bonding
+index 2880a78..40d849f 100644
+--- a/src/hooks/ports/bonding
++++ b/src/hooks/ports/bonding
+@@ -39,8 +39,14 @@ hook_parse_cmdline() {
+ 	while [ $# -gt 0 ]; do
+ 		case "${1}" in
+ 			--address=*)
+-				ADDRESS=$(cli_get_val "${1}")
++				ADDRESS="$(cli_get_val "${1}")"
++
++				if ! mac_is_valid "${ADDRESS}"; then
++					error "Invalid MAC address: ${ADDRESS}"
++					return ${EXIT_ERROR}
++				fi
+ 				;;
++
+ 			--miimon=*)
+ 				MIIMON=$(cli_get_val "${1}")
+ 				;;
+-- 
+2.39.2
+
diff --git a/network/patches/0010-ip-tunnel-Set-TTL-to-255-by-default.patch b/network/patches/0010-ip-tunnel-Set-TTL-to-255-by-default.patch
new file mode 100644
index 000000000..6f8a943c4
--- /dev/null
+++ b/network/patches/0010-ip-tunnel-Set-TTL-to-255-by-default.patch
@@ -0,0 +1,34 @@
+From ae2c5b2b954bfc5282f0ef359d0960a2cd610e14 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Dec 2018 13:38:13 +0100
+Subject: [PATCH 010/304] ip-tunnel: Set TTL to 255 by default
+
+By default, the Linux kernel inherits the TTL of the transported
+packet. Usually with BGP, the TTL is deliberately set to 1 or very
+low numbers which causes the packet to be dropped after the first
+hop.
+
+Since the tunnel should be routed, we set this to a default value
+of 255 and ignore the TTL of the encapsulated packet.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.ip-tunnel | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.ip-tunnel b/src/functions/functions.ip-tunnel
+index 1184a84..11eb3c7 100644
+--- a/src/functions/functions.ip-tunnel
++++ b/src/functions/functions.ip-tunnel
+@@ -77,7 +77,7 @@ ip_tunnel_add() {
+ 	shift
+ 
+ 	local mode
+-	local ttl
++	local ttl=255
+ 
+ 	local address
+ 	local remote_address
+-- 
+2.39.2
+
diff --git a/network/patches/0011-bird-Add-some-generic-configuration-file.patch b/network/patches/0011-bird-Add-some-generic-configuration-file.patch
new file mode 100644
index 000000000..d89917387
--- /dev/null
+++ b/network/patches/0011-bird-Add-some-generic-configuration-file.patch
@@ -0,0 +1,107 @@
+From 6a1b0fb170c7d66559935a6a4f8ee0e2bfdbf485 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 16 Dec 2018 17:10:47 +0000
+Subject: [PATCH 011/304] bird: Add some generic configuration file
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                  |  1 +
+ src/functions/functions.bird | 74 ++++++++++++++++++++++++++++++++++++
+ 2 files changed, 75 insertions(+)
+ create mode 100644 src/functions/functions.bird
+
+diff --git a/Makefile.am b/Makefile.am
+index 399652e..0139f95 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -127,6 +127,7 @@ EXTRA_DIST += \
+ dist_network_DATA = \
+ 	src/functions/functions.at \
+ 	src/functions/functions.auth \
++	src/functions/functions.bird \
+ 	src/functions/functions.bonding \
+ 	src/functions/functions.bridge \
+ 	src/functions/functions.cli \
+diff --git a/src/functions/functions.bird b/src/functions/functions.bird
+new file mode 100644
+index 0000000..9c8b006
+--- /dev/null
++++ b/src/functions/functions.bird
+@@ -0,0 +1,74 @@
++#!/bin/bash
++###############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2018  IPFire Network Development Team                         #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++###############################################################################
++
++BIRD_CONF="/etc/bird.conf"
++
++bird_start() {
++	service_start "bird.service"
++}
++
++bird_stop() {
++	service_stop "bird.service"
++}
++
++bird_reload() {
++	service_reload "bird.service"
++}
++
++bird_generate_config() {
++	log DEBUG "Write BIRD configuration file"
++
++	# Write header
++	config_header "bird" > ${BIRD_CONF}
++
++	# Write some basic settings
++	local proto
++	(
++		print "# Log everything to syslog"
++		print "log syslog all;"
++		print
++
++		print "# Turn on internal watchdog"
++		print "watchdog warning 5s;"
++		print "watchdog timeout 30s;"
++		print
++
++		print "# Define default route tables"
++		print "ipv6 table master6;"
++		print "ipv4 table master4;"
++
++		print "# Enable device configuration"
++		print "protocol device {}"
++		print
++
++		print "# Export all routes to kernel"
++		for proto in ipv6 ipv4; do
++			print "protocol kernel {"
++			print "	${proto} {"
++			print "		table ${proto/ipv/master};"
++			print "		export all;"
++			print "	};"
++			print "	learn;"
++			print "}"
++			print
++		done
++	) >> ${BIRD_CONF}
++}
+-- 
+2.39.2
+
diff --git a/network/patches/0012-bird-Apply-static-routes-instead-of-doing-that-manua.patch b/network/patches/0012-bird-Apply-static-routes-instead-of-doing-that-manua.patch
new file mode 100644
index 000000000..887df6386
--- /dev/null
+++ b/network/patches/0012-bird-Apply-static-routes-instead-of-doing-that-manua.patch
@@ -0,0 +1,146 @@
+From 0a5787976dd85db212fc5046c85d2aad6c64da5c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 16 Dec 2018 17:47:57 +0000
+Subject: [PATCH 012/304] bird: Apply static routes instead of doing that
+ manually with ip
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.bird    | 52 ++++++++++++++++++++++++++++++++-
+ src/functions/functions.route   | 38 +++---------------------
+ src/functions/functions.routing |  3 --
+ 3 files changed, 55 insertions(+), 38 deletions(-)
+
+diff --git a/src/functions/functions.bird b/src/functions/functions.bird
+index 9c8b006..c6fea32 100644
+--- a/src/functions/functions.bird
++++ b/src/functions/functions.bird
+@@ -60,7 +60,7 @@ bird_generate_config() {
+ 		print
+ 
+ 		print "# Export all routes to kernel"
+-		for proto in ipv6 ipv4; do
++		for proto in ${IP_SUPPORTED_PROTOCOLS}; do
+ 			print "protocol kernel {"
+ 			print "	${proto} {"
+ 			print "		table ${proto/ipv/master};"
+@@ -71,4 +71,54 @@ bird_generate_config() {
+ 			print
+ 		done
+ 	) >> ${BIRD_CONF}
++
++	# Static routes
++	for proto in ${IP_SUPPORTED_PROTOCOLS}; do
++		print "protocol static {"
++		print "	${proto};"
++		print
++
++		# Read routes for this protocol from configuration
++		__bird_static_routes "${proto}"
++
++		print "}"
++		print
++	done >> ${BIRD_CONF}
++}
++
++__bird_static_routes() {
++	local proto="${1}"
++	assert isset proto
++
++	local ${NETWORK_CONFIG_ROUTES_PARAMS}
++	local line
++	while read line; do
++		route_parse_line "${line}"
++		[ $? -eq ${EXIT_OK} ] || continue
++
++		local type
++		local arg
++		for arg in unreachable prohibit blackhole; do
++			if enabled "${arg}"; then
++				type="${arg}"
++				break
++			fi
++		done
++
++		# Skip all routes of another protocol
++		local _proto="$(ip_detect_protocol "${network}")"
++		if [ "${proto}" != "${_proto}" ]; then
++			continue
++		fi
++
++		case "${type}" in
++			unreachable|prohibit|blackhole)
++				print "	route ${network} ${type};"
++				;;
++
++			*)
++				print "	route ${network} via ${gateway};"
++				;;
++		esac
++	done < ${NETWORK_CONFIG_ROUTES}
+ }
+diff --git a/src/functions/functions.route b/src/functions/functions.route
+index 7ca4f59..e6ea244 100644
+--- a/src/functions/functions.route
++++ b/src/functions/functions.route
+@@ -393,41 +393,11 @@ route_parse_line() {
+ }
+ 
+ route_apply() {
+-	local table="static"
+-	local type
++	# Re-generate BIRD configuration
++	bird_generate_config
+ 
+-	log DEBUG "Applying static routes..."
+-
+-	# Flush the routing table.
+-	route_table_flush ${table}
+-
+-	local ${NETWORK_CONFIG_ROUTES_PARAMS}
+-	local line
+-	while read line; do
+-		route_parse_line ${line}
+-		[ $? -eq ${EXIT_OK} ] || continue
+-
+-		type="unicast"
+-		local arg
+-		for arg in unreachable prohibit blackhole; do
+-			if enabled ${arg}; then
+-				type="${arg}"
+-				break
+-			fi
+-		done
+-
+-		# Add the route.
+-		route_entry_add ${network} --table="static" --proto="static" \
+-			--type="${type}" --gateway="${gateway}" --mtu="${mtu}"
+-		local ret=$?
+-
+-		if [ ${ret} -ne ${EXIT_OK} ]; then
+-			log WARNING "Could not set route '${network}'."
+-		fi
+-	done < ${NETWORK_CONFIG_ROUTES}
+-
+-	# Create a lookup rule for the static routing table.
+-	route_rule_add --lookup="static" --priority=1000
++	# Reload the daemon
++	bird_reload
+ }
+ 
+ route_entry_add() {
+diff --git a/src/functions/functions.routing b/src/functions/functions.routing
+index 2436585..c7aac09 100644
+--- a/src/functions/functions.routing
++++ b/src/functions/functions.routing
+@@ -181,7 +181,4 @@ routing_update() {
+ 	cmd ${routing_cmd}
+ 
+ 	cmd ${ip_cmd} rule add from ${local_ip_address} lookup ${table}
+-
+-	# Apply all static routes
+-	route_apply
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0013-bird-Re-generate-configuration-when-network-is-initi.patch b/network/patches/0013-bird-Re-generate-configuration-when-network-is-initi.patch
new file mode 100644
index 000000000..1c3458ec4
--- /dev/null
+++ b/network/patches/0013-bird-Re-generate-configuration-when-network-is-initi.patch
@@ -0,0 +1,28 @@
+From eb6b47dcc7d5d541064ad90787ae55df3c3a8453 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 16 Dec 2018 17:55:25 +0000
+Subject: [PATCH 013/304] bird: (Re-)generate configuration when network is
+ initialised
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/network | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/network b/src/network
+index 69d77d5..b8f734e 100644
+--- a/src/network
++++ b/src/network
+@@ -1410,6 +1410,9 @@ case "${action}" in
+ 		# Update resolv.conf(5) when initializing the network
+ 		dns_generate_resolvconf
+ 
++		# Update bird configuration
++		bird_generate_config
++
+ 		# Also execute all triggers
+ 		triggers_execute_all "init"
+ 		;;
+-- 
+2.39.2
+
diff --git a/network/patches/0014-dns-Always-enable-EDNS0.patch b/network/patches/0014-dns-Always-enable-EDNS0.patch
new file mode 100644
index 000000000..bd8ce2427
--- /dev/null
+++ b/network/patches/0014-dns-Always-enable-EDNS0.patch
@@ -0,0 +1,32 @@
+From c27b38b437fa82a2227d554f4855c116395995ce Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 13 Feb 2019 17:45:05 +0000
+Subject: [PATCH 014/304] dns: Always enable EDNS0
+
+This is for all DNS queries originating from the firewall.
+
+Since we have had DNS Flag Day, we are expecting all DNS servers
+to support this now. If not, then you are very unlucky.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.dns | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/functions/functions.dns b/src/functions/functions.dns
+index 4cd5cb4..890f1ac 100644
+--- a/src/functions/functions.dns
++++ b/src/functions/functions.dns
+@@ -245,6 +245,9 @@ dns_generate_resolvconf() {
+ 
+ 	config_header "resolver configutation file" > ${file}
+ 
++	# Always enable EDNS0
++	print "option edns0\n" >> "${file}"
++
+ 	if enabled DNS_RANDOMIZE; then
+ 		print "option rotate\n" >> ${file}
+ 	fi
+-- 
+2.39.2
+
diff --git a/network/patches/0015-wireless-ap-Use-automatic-channel-selection-ACS-by-d.patch b/network/patches/0015-wireless-ap-Use-automatic-channel-selection-ACS-by-d.patch
new file mode 100644
index 000000000..9b6099b56
--- /dev/null
+++ b/network/patches/0015-wireless-ap-Use-automatic-channel-selection-ACS-by-d.patch
@@ -0,0 +1,27 @@
+From 469bc87f91538d668a32f9c38a3d8b1b4679c7ae Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 18 Mar 2019 19:46:06 +0100
+Subject: [PATCH 015/304] wireless-ap: Use automatic channel selection (ACS) by
+ default
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/wireless-ap | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 32d1a5a..52ca238 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -28,7 +28,7 @@ HOOK_SETTINGS="${HOOK_SETTINGS} ENCRYPTION KEY"
+ 
+ ADDRESS=$(mac_generate)
+ BROADCAST_SSID=on
+-CHANNEL=1
++CHANNEL=0
+ ENCRYPTION=""
+ KEY=""
+ SSID=
+-- 
+2.39.2
+
diff --git a/network/patches/0016-wireless-ap-Allow-to-disable-DFS-in-configuration.patch b/network/patches/0016-wireless-ap-Allow-to-disable-DFS-in-configuration.patch
new file mode 100644
index 000000000..717b30550
--- /dev/null
+++ b/network/patches/0016-wireless-ap-Allow-to-disable-DFS-in-configuration.patch
@@ -0,0 +1,102 @@
+From 7b297fb22fb16db920d68224b232e5acc652688a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 18 Mar 2019 19:58:25 +0100
+Subject: [PATCH 016/304] wireless-ap: Allow to disable DFS in configuration
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd   | 10 +++++++++-
+ src/helpers/hostapd-config-helper |  1 +
+ src/hooks/ports/wireless-ap       | 16 ++++++++++++++++
+ 3 files changed, 26 insertions(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 3f64e79..e19f9b3 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -36,6 +36,7 @@ hostapd_config_write() {
+ 	local broadcast_ssid
+ 	local channel
+ 	local country_code="$(wireless_get_reg_domain)"
++	local dfs="on"
+ 	local encryption
+ 	local key
+ 	local mode
+@@ -50,6 +51,9 @@ hostapd_config_write() {
+ 			--channel=*)
+ 				channel=$(cli_get_val "${1}")
+ 				;;
++			--dfs=*)
++				dfs="$(cli_get_val "${1}")"
++				;;
+ 			--encryption=*)
+ 				encryption=$(cli_get_val "${1}")
+ 				;;
+@@ -177,7 +181,11 @@ hostapd_config_write() {
+ 		print "ieee80211d=1"
+ 
+ 		# Enable Radar Detection
+-		print "ieee80211h=1"
++		if enabled dfs; then
++			print "ieee80211h=1"
++		else
++			print "ieee80211h=0"
++		fi
+ 
+ 		print # empty line
+ 
+diff --git a/src/helpers/hostapd-config-helper b/src/helpers/hostapd-config-helper
+index cb12af0..30d3456 100644
+--- a/src/helpers/hostapd-config-helper
++++ b/src/helpers/hostapd-config-helper
+@@ -40,6 +40,7 @@ case "${action}" in
+ 		hostapd_config_write ${port} ${config_file} \
+ 			--broadcast-ssid="${BROADCAST_SSID}" \
+ 			--channel="${CHANNEL}" \
++			--dfs="${DFS}" \
+ 			--encryption="${ENCRYPTION}" \
+ 			--key="${KEY}" \
+ 			--mode="${MODE}" \
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 52ca238..49c0a84 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -33,12 +33,16 @@ ENCRYPTION=""
+ KEY=""
+ SSID=
+ 
++# Perform radar detection by default when possible
++DFS="on"
++
+ hook_check_settings() {
+ 	assert isset ADDRESS
+ 	assert ismac ADDRESS
+ 	assert isset BROADCAST_SSID
+ 	assert isbool BROADCAST_SSID
+ 	assert isset CHANNEL
++	assert isbool DFS
+ 	assert isset MODE
+ 	assert isoneof MODE ${HOSTAPD_SUPPORTED_MODES}
+ 	assert isset PHY
+@@ -63,6 +67,18 @@ hook_parse_cmdline() {
+ 			--channel=*)
+ 				CHANNEL=$(cli_get_val "${1}")
+ 				;;
++			--dfs=*)
++				DFS="$(cli_get_val "${1}")"
++
++				if enabled DFS; then
++					DFS="on"
++				elif disabled DFS; then
++					DFS="off"
++				else
++					error "Invalid value for DFS: ${DFS}"
++					return ${EXIT_ERROR}
++				fi
++				;;
+ 			--encryption=*)
+ 				ENCRYPTION=$(cli_get_val "${1}")
+ 				;;
+-- 
+2.39.2
+
diff --git a/network/patches/0017-hostapd-Disable-DFS-automatically-when-not-supported.patch b/network/patches/0017-hostapd-Disable-DFS-automatically-when-not-supported.patch
new file mode 100644
index 000000000..a087dc1a1
--- /dev/null
+++ b/network/patches/0017-hostapd-Disable-DFS-automatically-when-not-supported.patch
@@ -0,0 +1,99 @@
+From dc6d97fbf2064365f5b84496a77227b4e3ca03d6 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 18 Mar 2019 20:10:56 +0100
+Subject: [PATCH 017/304] hostapd: Disable DFS automatically when not supported
+ by hardware
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd  |  2 +-
+ src/functions/functions.phy      | 22 ++++++++++++++++++++++
+ src/functions/functions.wireless | 13 +++++++++++++
+ src/network                      |  7 +++++++
+ 4 files changed, 43 insertions(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index e19f9b3..b855994 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -181,7 +181,7 @@ hostapd_config_write() {
+ 		print "ieee80211d=1"
+ 
+ 		# Enable Radar Detection
+-		if enabled dfs; then
++		if enabled dfs && wireless_supports_dfs "${device}"; then
+ 			print "ieee80211h=1"
+ 		else
+ 			print "ieee80211h=0"
+diff --git a/src/functions/functions.phy b/src/functions/functions.phy
+index 96287a5..064ca7b 100644
+--- a/src/functions/functions.phy
++++ b/src/functions/functions.phy
+@@ -188,3 +188,25 @@ phy_supports_ht_capability() {
+ 
+ 	list_match "${capability}" $(__phy_list_ht_capabilities "${phy}")
+ }
++
++# Returns TRUE if the PHY supports DFS
++phy_supports_dfs() {
++	local phy="${1}"
++	assert isset phy
++
++	local driver="$(phy_get_driver "${phy}")"
++	if ! isset driver; then
++		return ${EXIT_ERROR}
++	fi
++
++	# This is basically a whilelist of drivers which support this
++	# There is no better detection
++	case "${driver}" in
++		ath10k_*|ath9k|ath5k)
++			return ${EXIT_TRUE}
++			;;
++		*)
++			return ${EXIT_FALSE}
++			;;
++	esac
++}
+diff --git a/src/functions/functions.wireless b/src/functions/functions.wireless
+index 3608e11..221866e 100644
+--- a/src/functions/functions.wireless
++++ b/src/functions/functions.wireless
+@@ -515,3 +515,16 @@ wireless_get_vht_caps() {
+ 
+ 	network-phy-list-vht-caps "${phy}"
+ }
++
++wireless_supports_dfs() {
++	local device="${1}"
++	assert isset device
++
++	local phy="$(device_get_phy "${device}")"
++	if ! isset phy; then
++		log ERROR "Could not determine PHY for ${device}"
++		return ${EXIT_ERROR}
++	fi
++
++	phy_supports_dfs "${phy}"
++}
+diff --git a/src/network b/src/network
+index b8f734e..de2e663 100644
+--- a/src/network
++++ b/src/network
+@@ -277,6 +277,13 @@ cli_device_status_phy() {
+ 		cli_space
+ 	fi
+ 
++	cli_headline 2 "Features"
++
++	cli_print_fmt1 2 "DFS" \
++		"$(phy_supports_dfs "${phy}" && print "Supported" || print "Not Supported")"
++
++	cli_space
++
+ 	return ${EXIT_OK}
+ }
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0018-wireless-ap-Add-CLI-to-set-channel-bandwidth.patch b/network/patches/0018-wireless-ap-Add-CLI-to-set-channel-bandwidth.patch
new file mode 100644
index 000000000..ef53e6273
--- /dev/null
+++ b/network/patches/0018-wireless-ap-Add-CLI-to-set-channel-bandwidth.patch
@@ -0,0 +1,87 @@
+From 54094fc7ae1bc17e8d8361f7758d9404f1eeff02 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 18 Mar 2019 20:50:44 +0100
+Subject: [PATCH 018/304] wireless-ap: Add CLI to set channel bandwidth
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.wireless | 20 ++++++++++++++++++++
+ src/hooks/ports/wireless-ap      | 10 ++++++++++
+ 2 files changed, 30 insertions(+)
+
+diff --git a/src/functions/functions.wireless b/src/functions/functions.wireless
+index 221866e..0437d27 100644
+--- a/src/functions/functions.wireless
++++ b/src/functions/functions.wireless
+@@ -29,6 +29,14 @@ WIRELESS_DEFAULT_ENCRYPTION_MODE="NONE"
+ WIRELESS_VALID_ENCRYPTION_MODES="WPA2-PSK-SHA256 WPA2-PSK \
+ 	WPA-PSK-SHA256 WPA-PSK NONE"
+ 
++declare -A WIRELESS_CHANNEL_BANDWIDTHS=(
++	["802.11ac"]="20 40 80 160 80+80"
++	["802.11a/n"]="20 40"
++	["802.11a"]="20 40"
++	["802.11g/n"]="20 40"
++	["802.11g"]="20 40"
++)
++
+ cli_wireless() {
+ 	local action=${1}
+ 	shift 1
+@@ -309,6 +317,18 @@ wireless_channel_is_valid() {
+ 	return ${EXIT_FALSE}
+ }
+ 
++wireless_channel_bandwidth_is_valid() {
++	local mode="${1}"
++	assert isset mode
++
++	local bandwidth="${2}"
++	assert isset bandwidth
++
++	local bandwidths="${WIRELESS_CHANNEL_BANDWIDTHS["${mode}"]}"
++
++	list_match "${bandwidth}" ${bandwidths}
++}
++
+ wireless_channel_is_ht40_plus() {
+ 	local channel="${1}"
+ 	assert isinteger channel
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 49c0a84..8b626bf 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -29,6 +29,7 @@ HOOK_SETTINGS="${HOOK_SETTINGS} ENCRYPTION KEY"
+ ADDRESS=$(mac_generate)
+ BROADCAST_SSID=on
+ CHANNEL=0
++CHANNEL_BANDWIDTH=
+ ENCRYPTION=""
+ KEY=""
+ SSID=
+@@ -67,6 +68,9 @@ hook_parse_cmdline() {
+ 			--channel=*)
+ 				CHANNEL=$(cli_get_val "${1}")
+ 				;;
++			--channel-bandwidth=*)
++				CHANNEL_BANDWIDTH="$(cli_get_val "${1}")"
++				;;
+ 			--dfs=*)
+ 				DFS="$(cli_get_val "${1}")"
+ 
+@@ -121,6 +125,12 @@ hook_parse_cmdline() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
++	# Channel bandwidth must match the mode
++	if isset CHANNEL_BANDWIDTH && ! wireless_channel_bandwidth_is_valid "${MODE}" "${CHANNEL_BANDWIDTH}"; then
++		error "Channel Bandwidth '${CHANNEL_BANDWIDTH}' is not supported"
++		return ${EXIT_ERROR}
++	fi
++
+ 	# Save address of phy do identify it again
+ 	PHY=$(phy_get ${PHY})
+ 	PHY=$(phy_get_address ${PHY})
+-- 
+2.39.2
+
diff --git a/network/patches/0019-wireless-ap-Forgot-to-add-configuration-variables-to.patch b/network/patches/0019-wireless-ap-Forgot-to-add-configuration-variables-to.patch
new file mode 100644
index 000000000..d6a92619a
--- /dev/null
+++ b/network/patches/0019-wireless-ap-Forgot-to-add-configuration-variables-to.patch
@@ -0,0 +1,29 @@
+From 40c95a6b261e8fdadca97f21ff7cd2a11af3bfb3 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 18 Mar 2019 21:21:37 +0100
+Subject: [PATCH 019/304] wireless-ap: Forgot to add configuration variables to
+ file
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/wireless-ap | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 8b626bf..5e00014 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -23,8 +23,8 @@
+ 
+ HOOK_PORT_PATTERN="${PORT_PATTERN_ACCESSPOINT}"
+ 
+-HOOK_SETTINGS="ADDRESS BROADCAST_SSID CHANNEL MODE PHY SSID"
+-HOOK_SETTINGS="${HOOK_SETTINGS} ENCRYPTION KEY"
++HOOK_SETTINGS="ADDRESS BROADCAST_SSID CHANNEL CHANNEL_BANDWIDTH DFS MODE PHY"
++HOOK_SETTINGS="${HOOK_SETTINGS} ENCRYPTION KEY SSID"
+ 
+ ADDRESS=$(mac_generate)
+ BROADCAST_SSID=on
+-- 
+2.39.2
+
diff --git a/network/patches/0020-hostapd-Apply-channel-bandwidth-to-configuration.patch b/network/patches/0020-hostapd-Apply-channel-bandwidth-to-configuration.patch
new file mode 100644
index 000000000..cf9962ddb
--- /dev/null
+++ b/network/patches/0020-hostapd-Apply-channel-bandwidth-to-configuration.patch
@@ -0,0 +1,109 @@
+From f9e980d91e081613e5dcc7899c28fbdfc7a4c172 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 18 Mar 2019 21:24:02 +0100
+Subject: [PATCH 020/304] hostapd: Apply channel bandwidth to configuration
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd   | 26 ++++++++++++++++++++++++++
+ src/helpers/hostapd-config-helper |  1 +
+ src/hooks/ports/wireless-ap       |  2 +-
+ 3 files changed, 28 insertions(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index b855994..57f8c1e 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -35,6 +35,7 @@ hostapd_config_write() {
+ 
+ 	local broadcast_ssid
+ 	local channel
++	local channel_bandwidth
+ 	local country_code="$(wireless_get_reg_domain)"
+ 	local dfs="on"
+ 	local encryption
+@@ -51,6 +52,9 @@ hostapd_config_write() {
+ 			--channel=*)
+ 				channel=$(cli_get_val "${1}")
+ 				;;
++			--channel-bandwidth=*)
++				channel_bandwidth="$(cli_get_val "${1}")"
++				;;
+ 			--dfs=*)
+ 				dfs="$(cli_get_val "${1}")"
+ 				;;
+@@ -107,10 +111,17 @@ hostapd_config_write() {
+ 		assert isset key
+ 	fi
+ 
++	# Check channel bandwidth for validity
++	if isset channel_bandwidth && ! wireless_channel_bandwidth_is_valid "${mode}" "${channel_bandwidth}"; then
++		error "Invalid channel bandwidth for ${mode}: ${channel_bandwidth}"
++		return ${EXIT_ERROR}
++	fi
++
+ 	# 802.11ac/n flags
+ 	local ieee80211ac
+ 	local ieee80211n
+ 	local vht_caps
++	local vht_oper_chwidth="0"
+ 	local ht_caps
+ 
+ 	local hw_mode
+@@ -149,6 +160,18 @@ hostapd_config_write() {
+ 
+ 			# Fetch HT caps
+ 			ht_caps="$(wireless_get_ht_caps "${device}")"
++
++			case "${channel_bandwidth}" in
++				80)
++					vht_oper_chwidth="1"
++					;;
++				160)
++					vht_oper_chwidth="2"
++					;;
++				80+80)
++					vht_oper_chwidth="3"
++					;;
++			esac
+ 			;;
+ 	esac
+ 
+@@ -221,6 +244,9 @@ hostapd_config_write() {
+ 		# Enable HT caps
+ 		print "ht_capab=${ht_caps}"
+ 
++		# Wider Channels
++		print "vht_oper_chwidth=${vht_oper_chwidth}"
++
+ 		print
+ 	) >> ${file}
+ 
+diff --git a/src/helpers/hostapd-config-helper b/src/helpers/hostapd-config-helper
+index 30d3456..8af3097 100644
+--- a/src/helpers/hostapd-config-helper
++++ b/src/helpers/hostapd-config-helper
+@@ -40,6 +40,7 @@ case "${action}" in
+ 		hostapd_config_write ${port} ${config_file} \
+ 			--broadcast-ssid="${BROADCAST_SSID}" \
+ 			--channel="${CHANNEL}" \
++			--channel-bandwidth="${CHANNEL_BANDWIDTH}" \
+ 			--dfs="${DFS}" \
+ 			--encryption="${ENCRYPTION}" \
+ 			--key="${KEY}" \
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 5e00014..983f0f9 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -127,7 +127,7 @@ hook_parse_cmdline() {
+ 
+ 	# Channel bandwidth must match the mode
+ 	if isset CHANNEL_BANDWIDTH && ! wireless_channel_bandwidth_is_valid "${MODE}" "${CHANNEL_BANDWIDTH}"; then
+-		error "Channel Bandwidth '${CHANNEL_BANDWIDTH}' is not supported"
++		error "Channel Bandwidth '${CHANNEL_BANDWIDTH}' is not supported for ${MODE}"
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0021-wireless-ap-Enable-ACS-only-for-ath-devices.patch b/network/patches/0021-wireless-ap-Enable-ACS-only-for-ath-devices.patch
new file mode 100644
index 000000000..2bd7a17f7
--- /dev/null
+++ b/network/patches/0021-wireless-ap-Enable-ACS-only-for-ath-devices.patch
@@ -0,0 +1,117 @@
+From 1b4aa2ca01c5d0bd45213187e6a58b4cc0f57547 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 21 Mar 2019 20:22:56 +0100
+Subject: [PATCH 021/304] wireless-ap: Enable ACS only for ath* devices
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd  |  6 ++++++
+ src/functions/functions.phy      | 22 ++++++++++++++++++++++
+ src/functions/functions.wireless | 13 +++++++++++++
+ src/hooks/ports/wireless-ap      |  9 ++++++++-
+ 4 files changed, 49 insertions(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 57f8c1e..9024ab2 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -111,6 +111,12 @@ hostapd_config_write() {
+ 		assert isset key
+ 	fi
+ 
++	# With channel 0, ACS must be supported
++	if [ ${channel} -eq 0 ] && ! wireless_supports_acs "${device}"; then
++		error "ACS requested, but not supported by ${device}"
++		return ${EXIT_ERROR}
++	fi
++
+ 	# Check channel bandwidth for validity
+ 	if isset channel_bandwidth && ! wireless_channel_bandwidth_is_valid "${mode}" "${channel_bandwidth}"; then
+ 		error "Invalid channel bandwidth for ${mode}: ${channel_bandwidth}"
+diff --git a/src/functions/functions.phy b/src/functions/functions.phy
+index 064ca7b..ee0f2a2 100644
+--- a/src/functions/functions.phy
++++ b/src/functions/functions.phy
+@@ -189,6 +189,28 @@ phy_supports_ht_capability() {
+ 	list_match "${capability}" $(__phy_list_ht_capabilities "${phy}")
+ }
+ 
++# Returns TRUE if the PHY supports ACS
++phy_supports_acs() {
++	local phy="${1}"
++	assert isset phy
++
++	local driver="$(phy_get_driver "${phy}")"
++	if ! isset driver; then
++		return ${EXIT_ERROR}
++	fi
++
++	# This is basically a whilelist of drivers which support this
++	# There is no better detection
++	case "${driver}" in
++		ath10k_*|ath9k|ath5k)
++			return ${EXIT_TRUE}
++			;;
++		*)
++			return ${EXIT_FALSE}
++			;;
++	esac
++}
++
+ # Returns TRUE if the PHY supports DFS
+ phy_supports_dfs() {
+ 	local phy="${1}"
+diff --git a/src/functions/functions.wireless b/src/functions/functions.wireless
+index 0437d27..9e72fe0 100644
+--- a/src/functions/functions.wireless
++++ b/src/functions/functions.wireless
+@@ -536,6 +536,19 @@ wireless_get_vht_caps() {
+ 	network-phy-list-vht-caps "${phy}"
+ }
+ 
++wireless_supports_acs() {
++	local device="${1}"
++	assert isset device
++
++	local phy="$(device_get_phy "${device}")"
++	if ! isset phy; then
++		log ERROR "Could not determine PHY for ${device}"
++		return ${EXIT_ERROR}
++	fi
++
++	phy_supports_acs "${phy}"
++}
++
+ wireless_supports_dfs() {
+ 	local device="${1}"
+ 	assert isset device
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 983f0f9..0c42b61 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -28,7 +28,7 @@ HOOK_SETTINGS="${HOOK_SETTINGS} ENCRYPTION KEY SSID"
+ 
+ ADDRESS=$(mac_generate)
+ BROADCAST_SSID=on
+-CHANNEL=0
++CHANNEL=
+ CHANNEL_BANDWIDTH=
+ ENCRYPTION=""
+ KEY=""
+@@ -125,6 +125,13 @@ hook_parse_cmdline() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
++	# Automatically enable ACS if no channel is set and ACS is available
++	if ! isset CHANNEL && phy_supports_acs "${PHY}"; then
++		CHANNEL="0"
++
++		log INFO "Automatic Channel Selection (ACS) enabled"
++	fi
++
+ 	# Channel bandwidth must match the mode
+ 	if isset CHANNEL_BANDWIDTH && ! wireless_channel_bandwidth_is_valid "${MODE}" "${CHANNEL_BANDWIDTH}"; then
+ 		error "Channel Bandwidth '${CHANNEL_BANDWIDTH}' is not supported for ${MODE}"
+-- 
+2.39.2
+
diff --git a/network/patches/0022-wireless-ap-Allow-setting-the-wireless-environment-i.patch b/network/patches/0022-wireless-ap-Allow-setting-the-wireless-environment-i.patch
new file mode 100644
index 000000000..57c2a889c
--- /dev/null
+++ b/network/patches/0022-wireless-ap-Allow-setting-the-wireless-environment-i.patch
@@ -0,0 +1,164 @@
+From 7842c2ce43d1f185e65bb9f2beead96376e2bd34 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 21 Mar 2019 22:14:43 +0100
+Subject: [PATCH 022/304] wireless-ap: Allow setting the wireless environment
+ (indoor/outdoor)
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd   | 26 +++++++++++++++++++++++++-
+ src/functions/functions.wireless  |  9 +++++++++
+ src/helpers/hostapd-config-helper |  1 +
+ src/hooks/ports/wireless-ap       | 14 +++++++++++++-
+ 4 files changed, 48 insertions(+), 2 deletions(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 9024ab2..94b06db 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -39,6 +39,7 @@ hostapd_config_write() {
+ 	local country_code="$(wireless_get_reg_domain)"
+ 	local dfs="on"
+ 	local encryption
++	local environment="${WIRELESS_DEFAULT_ENVIRONMENT}"
+ 	local key
+ 	local mode
+ 	local ssid
+@@ -61,6 +62,9 @@ hostapd_config_write() {
+ 			--encryption=*)
+ 				encryption=$(cli_get_val "${1}")
+ 				;;
++			--environment=*)
++				environment="$(cli_get_val "${1}")"
++				;;
+ 			--key=*)
+ 				key=$(cli_get_val "${1}")
+ 				;;
+@@ -111,6 +115,12 @@ hostapd_config_write() {
+ 		assert isset key
+ 	fi
+ 
++	# Check wireless environment
++	if ! wireless_environment_is_valid "${environment}"; then
++		error "Invalid wireless environment: ${environment}"
++		return ${EXIT_ERROR}
++	fi
++
+ 	# With channel 0, ACS must be supported
+ 	if [ ${channel} -eq 0 ] && ! wireless_supports_acs "${device}"; then
+ 		error "ACS requested, but not supported by ${device}"
+@@ -208,6 +218,21 @@ hostapd_config_write() {
+ 
+ 		# Advertise country code and maximum transmission power
+ 		print "ieee80211d=1"
++		print "country_code=${country_code}"
++
++		# Wireless Environment
++		case "${environment}" in
++			indoor)
++				print "country3=0x49"
++				       country3
++				;;
++			outdoor)
++				print "country3=0x4f"
++				;;
++			indoor+outdoor)
++				print "country3=0x20"
++				;;
++		esac
+ 
+ 		# Enable Radar Detection
+ 		if enabled dfs && wireless_supports_dfs "${device}"; then
+@@ -230,7 +255,6 @@ hostapd_config_write() {
+ 		fi
+ 
+ 		print "channel=${channel}"
+-		print "country_code=${country_code}"
+ 		print "ignore_broadcast_ssid=${ignore_broadcast_ssid}"
+ 
+ 		if contains_spaces "${ssid}"; then
+diff --git a/src/functions/functions.wireless b/src/functions/functions.wireless
+index 9e72fe0..12204c0 100644
+--- a/src/functions/functions.wireless
++++ b/src/functions/functions.wireless
+@@ -37,6 +37,9 @@ declare -A WIRELESS_CHANNEL_BANDWIDTHS=(
+ 	["802.11g"]="20 40"
+ )
+ 
++WIRELESS_ENVIRONMENTS=( "indoor+outdoor" "indoor" "outdoor" )
++WIRELESS_DEFAULT_ENVIRONMENT="${WIRELESS_ENVIRONMENTS[0]}"
++
+ cli_wireless() {
+ 	local action=${1}
+ 	shift 1
+@@ -561,3 +564,9 @@ wireless_supports_dfs() {
+ 
+ 	phy_supports_dfs "${phy}"
+ }
++
++wireless_environment_is_valid() {
++	local environment="${1}"
++
++	list_match "${environment}" "${WIRELESS_ENVIRONMENTS[@]}"
++}
+diff --git a/src/helpers/hostapd-config-helper b/src/helpers/hostapd-config-helper
+index 8af3097..d3292c3 100644
+--- a/src/helpers/hostapd-config-helper
++++ b/src/helpers/hostapd-config-helper
+@@ -43,6 +43,7 @@ case "${action}" in
+ 			--channel-bandwidth="${CHANNEL_BANDWIDTH}" \
+ 			--dfs="${DFS}" \
+ 			--encryption="${ENCRYPTION}" \
++			--environment="${ENVIRONMENT}" \
+ 			--key="${KEY}" \
+ 			--mode="${MODE}" \
+ 			--ssid="${SSID}" \
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 0c42b61..6db39b8 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -24,7 +24,7 @@
+ HOOK_PORT_PATTERN="${PORT_PATTERN_ACCESSPOINT}"
+ 
+ HOOK_SETTINGS="ADDRESS BROADCAST_SSID CHANNEL CHANNEL_BANDWIDTH DFS MODE PHY"
+-HOOK_SETTINGS="${HOOK_SETTINGS} ENCRYPTION KEY SSID"
++HOOK_SETTINGS="${HOOK_SETTINGS} ENCRYPTION ENVIRONMENT KEY SSID"
+ 
+ ADDRESS=$(mac_generate)
+ BROADCAST_SSID=on
+@@ -37,6 +37,8 @@ SSID=
+ # Perform radar detection by default when possible
+ DFS="on"
+ 
++ENVIRONMENT="${WIRELESS_DEFAULT_ENVIRONMENT}"
++
+ hook_check_settings() {
+ 	assert isset ADDRESS
+ 	assert ismac ADDRESS
+@@ -57,6 +59,8 @@ hook_check_settings() {
+ 		assert [ ${#KEY} -ge 8 ]
+ 		assert [ ${#KEY} -le 63 ]
+ 	fi
++
++	assert wireless_environment_is_valid "${ENVIRONMENT}"
+ }
+ 
+ hook_parse_cmdline() {
+@@ -86,6 +90,14 @@ hook_parse_cmdline() {
+ 			--encryption=*)
+ 				ENCRYPTION=$(cli_get_val "${1}")
+ 				;;
++			--environment=*)
++				ENVIRONMENT="$(cli_get_val "${1}")"
++
++				if ! wireless_environment_is_valid "${ENVIRONMENT}"; then
++					error "Invalid wireless environment: ${ENVIRONMENT}"
++					return ${EXIT_ERROR}
++				fi
++				;;
+ 			--key=*)
+ 				KEY=$(cli_get_val "${1}")
+ 				;;
+-- 
+2.39.2
+
diff --git a/network/patches/0023-hostapd-Remove-now-useless-comment.patch b/network/patches/0023-hostapd-Remove-now-useless-comment.patch
new file mode 100644
index 000000000..0e224a2c9
--- /dev/null
+++ b/network/patches/0023-hostapd-Remove-now-useless-comment.patch
@@ -0,0 +1,26 @@
+From 09f00f0df436a3280b93b7570c6b9ae3152cf21e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 22 Mar 2019 11:40:32 +0100
+Subject: [PATCH 023/304] hostapd: Remove now useless comment
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 94b06db..eb177fe 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -214,8 +214,6 @@ hostapd_config_write() {
+ 	fi
+ 
+ 	(
+-		print "# Default settings"
+-
+ 		# Advertise country code and maximum transmission power
+ 		print "ieee80211d=1"
+ 		print "country_code=${country_code}"
+-- 
+2.39.2
+
diff --git a/network/patches/0024-hostapd-Always-enable-Transmit-Power-Control.patch b/network/patches/0024-hostapd-Always-enable-Transmit-Power-Control.patch
new file mode 100644
index 000000000..bac1030a2
--- /dev/null
+++ b/network/patches/0024-hostapd-Always-enable-Transmit-Power-Control.patch
@@ -0,0 +1,30 @@
+From 9602617288e200c0935d5888746f58c23b2f7af7 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 22 Mar 2019 11:45:03 +0100
+Subject: [PATCH 024/304] hostapd: Always enable Transmit Power Control
+
+Also advertise this to clients
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index eb177fe..dd52e56 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -232,6 +232,10 @@ hostapd_config_write() {
+ 				;;
+ 		esac
+ 
++		# Always advertise TPC
++		print "local_pwr_constraint=3"
++		print "spectrum_mgmt_required=1"
++
+ 		# Enable Radar Detection
+ 		if enabled dfs && wireless_supports_dfs "${device}"; then
+ 			print "ieee80211h=1"
+-- 
+2.39.2
+
diff --git a/network/patches/0025-hostapd-Set-default-WMM-settings.patch b/network/patches/0025-hostapd-Set-default-WMM-settings.patch
new file mode 100644
index 000000000..7e8e2dab1
--- /dev/null
+++ b/network/patches/0025-hostapd-Set-default-WMM-settings.patch
@@ -0,0 +1,72 @@
+From fcdbed86e00c02550682c110d768ff9a557ba8d7 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 22 Mar 2019 12:02:25 +0100
+Subject: [PATCH 025/304] hostapd: Set default WMM settings
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd | 47 ++++++++++++++++++++++++++++++++-
+ 1 file changed, 46 insertions(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index dd52e56..911a141 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -265,8 +265,53 @@ hostapd_config_write() {
+ 			print "ssid=${ssid}"
+ 		fi
+ 
+-		# WMM
++		# WMM & WMM-PS Unscheduled Automatic Power Save Delivery
+ 		print "wmm_enabled=${wmm}"
++		print "uapsd_advertisement_enabled=1"
++
++		# Low Priority / AC_BK = Background
++		print "wmm_ac_bk_cwmin=4"
++		print "wmm_ac_bk_cwmax=10"
++		print "wmm_ac_bk_aifs=7"
++		print "wmm_ac_bk_txop_limit=0"
++		print "wmm_ac_bk_acm=0"
++		print "tx_queue_data3_aifs=7"
++		print "tx_queue_data3_cwmin=15"
++		print "tx_queue_data3_cwmax=1023"
++		print "tx_queue_data3_burst=0"
++
++		# Normal Priority / AC_BE = Best Effort
++		print "wmm_ac_be_aifs=3"
++		print "wmm_ac_be_cwmin=4"
++		print "wmm_ac_be_cwmax=10"
++		print "wmm_ac_be_txop_limit=0"
++		print "wmm_ac_be_acm=0"
++		print "tx_queue_data2_aifs=3"
++		print "tx_queue_data2_cwmin=15"
++		print "tx_queue_data2_cwmax=63"
++		print "tx_queue_data2_burst=0"
++
++		# High Priority / AC_VI = Video
++		print "wmm_ac_vi_aifs=2"
++		print "wmm_ac_vi_cwmin=3"
++		print "wmm_ac_vi_cwmax=4"
++		print "wmm_ac_vi_txop_limit=94"
++		print "wmm_ac_vi_acm=0"
++		print "tx_queue_data1_aifs=1"
++		print "tx_queue_data1_cwmin=7"
++		print "tx_queue_data1_cwmax=15"
++		print "tx_queue_data1_burst=3.0"
++
++		# Highest Priority / AC_VO = Voice
++		print "wmm_ac_vo_aifs=2"
++		print "wmm_ac_vo_cwmin=2"
++		print "wmm_ac_vo_cwmax=3"
++		print "wmm_ac_vo_txop_limit=47"
++		print "wmm_ac_vo_acm=0"
++		print "tx_queue_data0_aifs=1"
++		print "tx_queue_data0_cwmin=3"
++		print "tx_queue_data0_cwmax=7"
++		print "tx_queue_data0_burst=1.5"
+ 
+ 		# Enable VHT caps
+ 		if isset vht_caps; then
+-- 
+2.39.2
+
diff --git a/network/patches/0026-hostapd-Kick-stations-that-are-too-far-away.patch b/network/patches/0026-hostapd-Kick-stations-that-are-too-far-away.patch
new file mode 100644
index 000000000..09f16b114
--- /dev/null
+++ b/network/patches/0026-hostapd-Kick-stations-that-are-too-far-away.patch
@@ -0,0 +1,27 @@
+From 4d4bca7eec3d036e1cbed28fc823d06d08008d78 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 22 Mar 2019 12:02:46 +0100
+Subject: [PATCH 026/304] hostapd: Kick stations that are too far away
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 911a141..8b281cc 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -265,6 +265,9 @@ hostapd_config_write() {
+ 			print "ssid=${ssid}"
+ 		fi
+ 
++		# Kick stations that are too far away
++		print "disassoc_low_ack=1"
++
+ 		# WMM & WMM-PS Unscheduled Automatic Power Save Delivery
+ 		print "wmm_enabled=${wmm}"
+ 		print "uapsd_advertisement_enabled=1"
+-- 
+2.39.2
+
diff --git a/network/patches/0027-hostapd-Always-qoute-SSID.patch b/network/patches/0027-hostapd-Always-qoute-SSID.patch
new file mode 100644
index 000000000..9e65758fa
--- /dev/null
+++ b/network/patches/0027-hostapd-Always-qoute-SSID.patch
@@ -0,0 +1,34 @@
+From 4873f3299807fb0fde7c7f71736dd9318c708ca1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 22 Mar 2019 12:08:08 +0100
+Subject: [PATCH 027/304] hostapd: Always qoute SSID
+
+hostapd has a new parameter that always allows us to set
+the SSID as a quoted UTF8 string
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd | 7 ++-----
+ 1 file changed, 2 insertions(+), 5 deletions(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 8b281cc..245b4cf 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -259,11 +259,8 @@ hostapd_config_write() {
+ 		print "channel=${channel}"
+ 		print "ignore_broadcast_ssid=${ignore_broadcast_ssid}"
+ 
+-		if contains_spaces "${ssid}"; then
+-			print "ssid=\"${ssid}\""
+-		else
+-			print "ssid=${ssid}"
+-		fi
++		print "ssid2=\"${ssid}\""
++		print "utf8_ssid=1"
+ 
+ 		# Kick stations that are too far away
+ 		print "disassoc_low_ack=1"
+-- 
+2.39.2
+
diff --git a/network/patches/0028-wireless-ap-Allow-to-enable-disable-802.11w-Manageme.patch b/network/patches/0028-wireless-ap-Allow-to-enable-disable-802.11w-Manageme.patch
new file mode 100644
index 000000000..dc0872607
--- /dev/null
+++ b/network/patches/0028-wireless-ap-Allow-to-enable-disable-802.11w-Manageme.patch
@@ -0,0 +1,130 @@
+From 34ca39360410ab03c7909494e6291bbb65622e3d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 22 Mar 2019 12:27:38 +0100
+Subject: [PATCH 028/304] wireless-ap: Allow to enable/disable 802.11w
+ Management Frame Protection
+
+This is disabled by default, because loads of stations have issues
+associating with an AP that has 802.11w enabled.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd   | 17 +++++++++++++++++
+ src/helpers/hostapd-config-helper |  1 +
+ src/hooks/ports/wireless-ap       | 18 ++++++++++++++++++
+ 3 files changed, 36 insertions(+)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 245b4cf..bf0c5fc 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -41,6 +41,7 @@ hostapd_config_write() {
+ 	local encryption
+ 	local environment="${WIRELESS_DEFAULT_ENVIRONMENT}"
+ 	local key
++	local mfp="off"
+ 	local mode
+ 	local ssid
+ 	local wmm="1"
+@@ -68,6 +69,9 @@ hostapd_config_write() {
+ 			--key=*)
+ 				key=$(cli_get_val "${1}")
+ 				;;
++			--mfp=*)
++				mfp="$(cli_get_val "${1}")"
++				;;
+ 			--mode=*)
+ 				mode=$(cli_get_val "${1}")
+ 
+@@ -133,6 +137,12 @@ hostapd_config_write() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
++	# Management Frame Proection
++	if ! isbool mfp; then
++		error "Invalid value for --mfp: ${mfp}"
++		return ${EXIT_ERROR}
++	fi
++
+ 	# 802.11ac/n flags
+ 	local ieee80211ac
+ 	local ieee80211n
+@@ -325,6 +335,13 @@ hostapd_config_write() {
+ 		print "vht_oper_chwidth=${vht_oper_chwidth}"
+ 
+ 		print
++
++		# 802.11w - Management Frame Protection (MFP)
++		if enabled mfp; then
++			print "ieee80211w=2" # required
++		else
++			print "ieee80211w=0"
++		fi
+ 	) >> ${file}
+ 
+ 	# Control interface.
+diff --git a/src/helpers/hostapd-config-helper b/src/helpers/hostapd-config-helper
+index d3292c3..7af723d 100644
+--- a/src/helpers/hostapd-config-helper
++++ b/src/helpers/hostapd-config-helper
+@@ -45,6 +45,7 @@ case "${action}" in
+ 			--encryption="${ENCRYPTION}" \
+ 			--environment="${ENVIRONMENT}" \
+ 			--key="${KEY}" \
++			--mfp="${MFP}" \
+ 			--mode="${MODE}" \
+ 			--ssid="${SSID}" \
+ 		|| exit $?
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 6db39b8..7073cbc 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -25,6 +25,7 @@ HOOK_PORT_PATTERN="${PORT_PATTERN_ACCESSPOINT}"
+ 
+ HOOK_SETTINGS="ADDRESS BROADCAST_SSID CHANNEL CHANNEL_BANDWIDTH DFS MODE PHY"
+ HOOK_SETTINGS="${HOOK_SETTINGS} ENCRYPTION ENVIRONMENT KEY SSID"
++HOOK_SETTINGS="${HOOK_SETTINGS} MFP"
+ 
+ ADDRESS=$(mac_generate)
+ BROADCAST_SSID=on
+@@ -37,6 +38,10 @@ SSID=
+ # Perform radar detection by default when possible
+ DFS="on"
+ 
++# 802.11w - Management Frame Protection
++# Disable by default because many clients cannot connect when enabled
++MFP="off"
++
+ ENVIRONMENT="${WIRELESS_DEFAULT_ENVIRONMENT}"
+ 
+ hook_check_settings() {
+@@ -46,6 +51,7 @@ hook_check_settings() {
+ 	assert isbool BROADCAST_SSID
+ 	assert isset CHANNEL
+ 	assert isbool DFS
++	assert isbool MFP
+ 	assert isset MODE
+ 	assert isoneof MODE ${HOSTAPD_SUPPORTED_MODES}
+ 	assert isset PHY
+@@ -104,6 +110,18 @@ hook_parse_cmdline() {
+ 			--mac=*)
+ 				ADDRESS=$(cli_get_val "${1}")
+ 				;;
++			--mfp=*)
++				MFP="$(cli_get_val "${1}")"
++
++				if enabled MFP; then
++					MFP="on"
++				elif disabled MFP; then
++					MFP="off"
++				else
++					error "Invalid value for --mfp: ${MFP}"
++					return ${EXIT_ERROR}
++				fi
++				;;
+ 			--mode=*)
+ 				MODE=$(cli_get_val "${1}")
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0029-network-Show-when-a-PHY-supports-ACS.patch b/network/patches/0029-network-Show-when-a-PHY-supports-ACS.patch
new file mode 100644
index 000000000..f18008404
--- /dev/null
+++ b/network/patches/0029-network-Show-when-a-PHY-supports-ACS.patch
@@ -0,0 +1,26 @@
+From 304b20a828e0987943ccda6f1c4321682195a67a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 29 Mar 2019 18:46:25 +0100
+Subject: [PATCH 029/304] network: Show when a PHY supports ACS
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/network | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/network b/src/network
+index de2e663..3535133 100644
+--- a/src/network
++++ b/src/network
+@@ -279,6 +279,8 @@ cli_device_status_phy() {
+ 
+ 	cli_headline 2 "Features"
+ 
++	cli_print_fmt1 2 "Automatic Channel Selection" \
++		"$(phy_supports_acs "${phy}" && print "Supported" || print "Not Supported")"
+ 	cli_print_fmt1 2 "DFS" \
+ 		"$(phy_supports_dfs "${phy}" && print "Supported" || print "Not Supported")"
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0030-Move-cli_device_status_phy-to-functions.phy.patch b/network/patches/0030-Move-cli_device_status_phy-to-functions.phy.patch
new file mode 100644
index 000000000..eaf70d3fd
--- /dev/null
+++ b/network/patches/0030-Move-cli_device_status_phy-to-functions.phy.patch
@@ -0,0 +1,112 @@
+From 01648ba604f9d0c922193553cfcb36dae0bfddaf Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 29 Mar 2019 18:47:47 +0100
+Subject: [PATCH 030/304] Move cli_device_status_phy() to functions.phy
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.phy | 38 +++++++++++++++++++++++++++++++++++++
+ src/network                 | 38 -------------------------------------
+ 2 files changed, 38 insertions(+), 38 deletions(-)
+
+diff --git a/src/functions/functions.phy b/src/functions/functions.phy
+index ee0f2a2..120117c 100644
+--- a/src/functions/functions.phy
++++ b/src/functions/functions.phy
+@@ -21,6 +21,44 @@
+ 
+ PHY_DIR="/sys/class/ieee80211"
+ 
++cli_device_status_phy() {
++	local phy="${1}"
++	assert phy_exists "${phy}"
++
++	local address="$(phy_get_address "${phy}")"
++	cli_print_fmt1 1 "Address" "${address}"
++
++	# Show kernel module
++	local driver="$(phy_get_driver "${phy}")"
++	if isset driver; then
++		cli_print_fmt1 1 "Driver" "${driver}"
++	fi
++
++	cli_space
++
++	local devices="$(phy_get_devices "${phy}")"
++	if isset devices; then
++		cli_headline 2 "Soft interfaces"
++
++		local device
++		for device in ${devices}; do
++			cli_print 2 "* %s" "${device}"
++		done
++		cli_space
++	fi
++
++	cli_headline 2 "Features"
++
++	cli_print_fmt1 2 "Automatic Channel Selection" \
++		"$(phy_supports_acs "${phy}" && print "Supported" || print "Not Supported")"
++	cli_print_fmt1 2 "DFS" \
++		"$(phy_supports_dfs "${phy}" && print "Supported" || print "Not Supported")"
++
++	cli_space
++
++	return ${EXIT_OK}
++}
++
+ phy_dir() {
+ 	local phy=${1}
+ 
+diff --git a/src/network b/src/network
+index 3535133..300ba94 100644
+--- a/src/network
++++ b/src/network
+@@ -251,44 +251,6 @@ cli_device_status_serial() {
+ 	fi
+ }
+ 
+-cli_device_status_phy() {
+-	local phy="${1}"
+-	assert phy_exists "${phy}"
+-
+-	local address="$(phy_get_address "${phy}")"
+-	cli_print_fmt1 1 "Address" "${address}"
+-
+-	# Show kernel module
+-	local driver="$(phy_get_driver "${phy}")"
+-	if isset driver; then
+-		cli_print_fmt1 1 "Driver" "${driver}"
+-	fi
+-
+-	cli_space
+-
+-	local devices="$(phy_get_devices "${phy}")"
+-	if isset devices; then
+-		cli_headline 2 "Soft interfaces"
+-
+-		local device
+-		for device in ${devices}; do
+-			cli_print 2 "* %s" "${device}"
+-		done
+-		cli_space
+-	fi
+-
+-	cli_headline 2 "Features"
+-
+-	cli_print_fmt1 2 "Automatic Channel Selection" \
+-		"$(phy_supports_acs "${phy}" && print "Supported" || print "Not Supported")"
+-	cli_print_fmt1 2 "DFS" \
+-		"$(phy_supports_dfs "${phy}" && print "Supported" || print "Not Supported")"
+-
+-	cli_space
+-
+-	return ${EXIT_OK}
+-}
+-
+ cli_device_discover() {
+ 	local device=${1}
+ 	shift
+-- 
+2.39.2
+
diff --git a/network/patches/0031-hostapd-Dump-config-file-in-debug-mode.patch b/network/patches/0031-hostapd-Dump-config-file-in-debug-mode.patch
new file mode 100644
index 000000000..26e05b097
--- /dev/null
+++ b/network/patches/0031-hostapd-Dump-config-file-in-debug-mode.patch
@@ -0,0 +1,52 @@
+From 7c91c167d10cbe3d390f0dc8c426eed0abf243b4 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 11:26:38 +0100
+Subject: [PATCH 031/304] hostapd: Dump config file in debug mode
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd |  3 +++
+ src/functions/functions.util    | 13 +++++++++++++
+ 2 files changed, 16 insertions(+)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index bf0c5fc..737bd1a 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -378,6 +378,9 @@ hostapd_config_write() {
+ 		) >> ${file}
+ 	fi
+ 
++	# Log configuration file
++	file_to_log DEBUG "${file}"
++
+ 	return ${EXIT_OK}
+ }
+ 
+diff --git a/src/functions/functions.util b/src/functions/functions.util
+index b767423..4c1dbb4 100644
+--- a/src/functions/functions.util
++++ b/src/functions/functions.util
+@@ -248,6 +248,19 @@ file_get_age() {
+ 	return ${EXIT_ERROR}
+ }
+ 
++file_to_log() {
++	local level="${1}"
++	assert isset level
++
++	local file="${2}"
++	assert file_exists "${file}"
++
++	local line
++	while read line; do
++		log "${level}" "${line}"
++	done < "${file}"
++}
++
+ make_directory() {
+ 	local path="${1}"
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0032-wireless-ap-Automatically-enable-all-supported-ciphe.patch b/network/patches/0032-wireless-ap-Automatically-enable-all-supported-ciphe.patch
new file mode 100644
index 000000000..7277180b2
--- /dev/null
+++ b/network/patches/0032-wireless-ap-Automatically-enable-all-supported-ciphe.patch
@@ -0,0 +1,519 @@
+From 2e4e3c88ba2543e5bf4bf3f92977990c281a00bb Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 11:27:50 +0100
+Subject: [PATCH 032/304] wireless-ap: Automatically enable all supported
+ ciphers
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                          |   7 ++
+ src/functions/functions.hostapd      |  65 +++++++++++-
+ src/functions/functions.phy          |  17 +++
+ src/libnetwork/libnetwork.sym        |  37 ++++---
+ src/libnetwork/network/phy.h         |  22 ++++
+ src/libnetwork/phy.c                 | 149 +++++++++++++++++++++++++++
+ src/utils/.gitignore                 |   1 +
+ src/utils/network-phy-list-ciphers.c |  61 +++++++++++
+ 8 files changed, 340 insertions(+), 19 deletions(-)
+ create mode 100644 src/utils/network-phy-list-ciphers.c
+
+diff --git a/Makefile.am b/Makefile.am
+index 0139f95..1b5e7e9 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -301,6 +301,7 @@ EXTRA_DIST += \
+ 
+ util_PROGRAMS = \
+ 	src/utils/network-phy-list-channels \
++	src/utils/network-phy-list-ciphers \
+ 	src/utils/network-phy-list-ht-caps \
+ 	src/utils/network-phy-list-vht-caps
+ 
+@@ -310,6 +311,12 @@ src_utils_network_phy_list_channels_SOURCES = \
+ src_utils_network_phy_list_channels_LDADD = \
+ 	src/libnetwork.la
+ 
++src_utils_network_phy_list_ciphers_SOURCES = \
++	src/utils/network-phy-list-ciphers.c
++
++src_utils_network_phy_list_ciphers_LDADD = \
++	src/libnetwork.la
++
+ src_utils_network_phy_list_ht_caps_SOURCES = \
+ 	src/utils/network-phy-list-ht-caps.c
+ 
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 737bd1a..6111457 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -23,6 +23,19 @@ HOSTAPD_CONTROL_INTERFACE_DIR="/run/hostapd/ctrl"
+ 
+ HOSTAPD_SUPPORTED_MODES="802.11a 802.11a/n 802.11ac 802.11g 802.11g/n"
+ 
++HOSTAPD_SUPPORTED_PAIRWISE_CIPHERS=(
++	"GCMP-256"	# Galois/counter mode protocol with 256 bit key
++	"CCMP-256"	# AES in Counter mode with CBC-MAC with 256 bit key
++	"GCMP-128"	# Galois/counter mode protocol with 128 bit key
++	"CCMP-128"	# AES in Counter mode with CBC-MAC with 128 bit key
++)
++
++# This must be supported by all stations on the network and therefore
++# can effectively only be CCMP
++HOSTAPD_SUPPORTED_GROUP_CIPHERS=(
++	"CCMP-128"
++)
++
+ hostapd_config_write() {
+ 	local device=${1}
+ 	assert isset device
+@@ -33,6 +46,16 @@ hostapd_config_write() {
+ 	# Shift the device and file argument.
+ 	shift 2
+ 
++	# Device must exist
++	if ! device_exists "${device}"; then
++		error "Cannot write hostapd configuration for non-existant device: ${device}"
++		return ${EXIT_ERROR}
++	fi
++
++	# Get the phy for device
++	local phy="$(device_get_phy "${device}")"
++	assert isset phy
++
+ 	local broadcast_ssid
+ 	local channel
+ 	local channel_bandwidth
+@@ -201,6 +224,25 @@ hostapd_config_write() {
+ 			;;
+ 	esac
+ 
++	# Cryptography
++	local cipher
++
++	# Get all supported pairwise ciphers
++	local pairwise_ciphers=()
++	for cipher in ${HOSTAPD_SUPPORTED_PAIRWISE_CIPHERS[*]}; do
++		if phy_supports_cipher "${phy}" "${cipher}"; then
++			pairwise_ciphers+=( "$(hostapd_cipher_name "${cipher}")" )
++		fi
++	done
++
++	# Get all supported group ciphers
++	local group_ciphers=()
++	for cipher in ${HOSTAPD_SUPPORTED_GROUP_CIPHERS[*]}; do
++		if phy_supports_cipher "${phy}" "${cipher}"; then
++			group_ciphers+=( "$(hostapd_cipher_name "${cipher}")" )
++		fi
++	done
++
+ 	# Create configuration directory.
+ 	local config_dir=$(dirname ${file})
+ 	mkdir -p ${HOSTAPD_CONTROL_INTERFACE_DIR} ${config_dir} 2>/dev/null
+@@ -372,8 +414,9 @@ hostapd_config_write() {
+ 			print "wpa=${encryption_mode}"
+ 			print "wpa_passphrase=${key}"
+ 			print "wpa_key_mgmt=WPA-PSK"
+-			print "wpa_pairwise=TKIP"
+-			print "rsn_pairwise=CCMP"
++			print "wpa_pairwise=${pairwise_ciphers[*]}"
++			print "rsn_pairwise=${pairwise_ciphers[*]}"
++			print "group_cipher=${group_ciphers[*]}"
+ 			print
+ 		) >> ${file}
+ 	fi
+@@ -407,3 +450,21 @@ hostapd_stop() {
+ 
+ 	service_stop "hostapd@${device}.service"
+ }
++
++hostapd_cipher_name() {
++	local cipher="${1}"
++
++	case "${cipher}" in
++		CCMP-128)
++			print "CCMP"
++			;;
++
++		GCMP-128)
++			print "GCMP"
++			;;
++
++		*)
++			print "${cipher}"
++			;;
++	esac
++}
+diff --git a/src/functions/functions.phy b/src/functions/functions.phy
+index 120117c..c06389c 100644
+--- a/src/functions/functions.phy
++++ b/src/functions/functions.phy
+@@ -208,6 +208,23 @@ phy_supports_channel() {
+ 	return ${EXIT_FALSE}
+ }
+ 
++phy_list_ciphers() {
++	local phy="${1}"
++	assert isset phy
++
++	network-phy-list-ciphers "${phy}"
++}
++
++phy_supports_cipher() {
++	local phy="${1}"
++	assert isset phy
++
++	local cipher="${2}"
++	assert isset cipher
++
++	list_match "${cipher}" $(phy_list_ciphers "${phy}")
++}
++
+ __phy_list_ht_capabilities() {
+ 	local phy="${1}"
+ 	assert isset phy
+diff --git a/src/libnetwork/libnetwork.sym b/src/libnetwork/libnetwork.sym
+index 593c4a2..034d43f 100644
+--- a/src/libnetwork/libnetwork.sym
++++ b/src/libnetwork/libnetwork.sym
+@@ -1,21 +1,24 @@
+ LIBNETWORK_0 {
+ global:
+-        network_interface_get_name;
+-        network_interface_new;
+-        network_interface_ref;
+-        network_interface_unref;
+-        network_new;
+-        network_phy_has_ht_capability;
+-        network_phy_has_vht_capability;
+-        network_phy_list_channels;
+-        network_phy_list_ht_capabilities;
+-        network_phy_list_vht_capabilities;
+-        network_phy_new;
+-        network_phy_ref;
+-        network_phy_unref;
+-        network_ref;
+-        network_unref;
+-        network_version;
++	network_interface_get_name;
++	network_interface_new;
++	network_interface_ref;
++	network_interface_unref;
++	network_new;
++	network_phy_get_cipher_string;
++	network_phy_has_ht_capability;
++	network_phy_has_vht_capability;
++	network_phy_list_channels;
++	network_phy_list_ciphers;
++	network_phy_list_ht_capabilities;
++	network_phy_list_vht_capabilities;
++	network_phy_supports_cipher;
++	network_phy_new;
++	network_phy_ref;
++	network_phy_unref;
++	network_ref;
++	network_unref;
++	network_version;
+ local:
+-        *;
++	*;
+ };
+diff --git a/src/libnetwork/network/phy.h b/src/libnetwork/network/phy.h
+index 9059680..bc6dafb 100644
+--- a/src/libnetwork/network/phy.h
++++ b/src/libnetwork/network/phy.h
+@@ -30,6 +30,25 @@ int network_phy_new(struct network_ctx*, struct network_phy** phy, const char* n
+ struct network_phy* network_phy_ref(struct network_phy* phy);
+ struct network_phy* network_phy_unref(struct network_phy* phy);
+ 
++enum network_phy_ciphers {
++	NETWORK_PHY_CIPHER_WEP40           = (1 <<  0),
++	NETWORK_PHY_CIPHER_TKIP            = (1 <<  1),
++	NETWORK_PHY_CIPHER_CCMP128         = (1 <<  2),
++	NETWORK_PHY_CIPHER_WEP104          = (1 <<  3),
++	NETWORK_PHY_CIPHER_CMAC128         = (1 <<  4),
++	NETWORK_PHY_CIPHER_GCMP128         = (1 <<  5),
++	NETWORK_PHY_CIPHER_GCMP256         = (1 <<  6),
++	NETWORK_PHY_CIPHER_CCMP256         = (1 <<  7),
++	NETWORK_PHY_CIPHER_GMAC128         = (1 <<  8),
++	NETWORK_PHY_CIPHER_GMAC256         = (1 <<  9),
++	NETWORK_PHY_CIPHER_CMAC256         = (1 << 10),
++	NETWORK_PHY_CIPHER_WPISMS4         = (1 << 11),
++};
++
++const char* network_phy_get_cipher_string(const enum network_phy_ciphers cipher);
++int network_phy_supports_cipher(struct network_phy* phy, const enum network_phy_ciphers cipher);
++char* network_phy_list_ciphers(struct network_phy* phy);
++
+ enum network_phy_ht_caps {
+ 	NETWORK_PHY_HT_CAP_RX_LDPC         = (1 <<  0),
+ 	NETWORK_PHY_HT_CAP_HT40            = (1 <<  1),
+@@ -81,6 +100,9 @@ char* network_phy_list_ht_capabilities(struct network_phy* phy);
+ struct nl_msg* network_phy_make_netlink_message(struct network_phy* phy,
+ 	enum nl80211_commands cmd, int flags);
+ 
++#define foreach_cipher(cipher) \
++	for(enum network_phy_ciphers cipher = NETWORK_PHY_CIPHER_WEP40; cipher <= NETWORK_PHY_CIPHER_WPISMS4; cipher <<= 1)
++
+ #define foreach_vht_cap(cap) \
+ 	for(int cap = NETWORK_PHY_VHT_CAP_VHT160; cap <= NETWORK_PHY_VHT_CAP_TX_ANTENNA_PATTERN; cap <<= 1)
+ 
+diff --git a/src/libnetwork/phy.c b/src/libnetwork/phy.c
+index 0bf9c81..e3f2aad 100644
+--- a/src/libnetwork/phy.c
++++ b/src/libnetwork/phy.c
+@@ -52,6 +52,7 @@ struct network_phy {
+ 
+ 	TAILQ_HEAD(head, network_phy_channel) channels;
+ 
++	enum network_phy_ciphers ciphers;
+ 	ssize_t max_mpdu_length;
+ 	unsigned int vht_caps;
+ 	unsigned int ht_caps;
+@@ -80,6 +81,81 @@ static int phy_get_index(const char* name) {
+ 	return atoi(index);
+ }
+ 
++static void phy_parse_ciphers(struct network_phy* phy, __u32* ciphers, int num) {
++	enum network_phy_ciphers cipher;
++
++	// Reset value
++	phy->ciphers = 0;
++
++	for (int i = 0; i < num; i++) {
++		switch (ciphers[i]) {
++			case 0x000fac01:
++				cipher = NETWORK_PHY_CIPHER_WEP40;
++				break;
++
++			case 0x000fac02:
++				cipher = NETWORK_PHY_CIPHER_TKIP;
++				break;
++
++			case 0x000fac04:
++				cipher = NETWORK_PHY_CIPHER_CCMP128;
++				break;
++
++			case 0x000fac05:
++				cipher = NETWORK_PHY_CIPHER_WEP104;
++				break;
++
++			case 0x000fac06:
++				cipher = NETWORK_PHY_CIPHER_CMAC128;
++				break;
++
++			case 0x000fac08:
++				cipher = NETWORK_PHY_CIPHER_GCMP128;
++				break;
++
++			case 0x000fac09:
++				cipher = NETWORK_PHY_CIPHER_GCMP256;
++				break;
++
++			/*
++				I have no idea what these are. My card reports them but
++				I could not find out anything about them.
++			*/
++			case 0x000fac0a:
++			case 0x000fac0b:
++			case 0x000fac0c:
++			case 0x000fac0d:
++				continue;
++
++			case 0x000fac10:
++				cipher = NETWORK_PHY_CIPHER_CCMP256;
++				break;
++
++			case 0x000fac11:
++				cipher = NETWORK_PHY_CIPHER_GMAC128;
++				break;
++
++			case 0x000fac12:
++				cipher = NETWORK_PHY_CIPHER_GMAC256;
++				break;
++
++			case 0x000fac13:
++				cipher = NETWORK_PHY_CIPHER_CMAC256;
++				break;
++
++			case 0x00147201:
++				cipher = NETWORK_PHY_CIPHER_WPISMS4;
++				break;
++
++			default:
++				ERROR(phy->ctx, "Unknown cipher found: %x\n", ciphers[i]);
++				continue;
++		}
++
++		phy->ciphers |= cipher;
++	}
++}
++
+ static void phy_parse_vht_capabilities(struct network_phy* phy, __u32 caps) {
+ 	// Max MPDU length
+ 	switch (caps & 0x3) {
+@@ -325,6 +401,13 @@ static int phy_parse_info(struct nl_msg* msg, void* data) {
+ 	nla_parse(attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ 		genlmsg_attrlen(gnlh, 0), NULL);
+ 
++	// Ciphers
++	if (attrs[NL80211_ATTR_CIPHER_SUITES]) {
++		int num = nla_len(attrs[NL80211_ATTR_CIPHER_SUITES]) / sizeof(__u32);
++		__u32* ciphers = nla_data(attrs[NL80211_ATTR_CIPHER_SUITES]);
++		phy_parse_ciphers(phy, ciphers, num);
++	}
++
+ 	if (attrs[NL80211_ATTR_WIPHY_BANDS]) {
+ 		struct nlattr* nl_band;
+ 		int i;
+@@ -464,6 +547,72 @@ nla_put_failure:
+ 	return NULL;
+ }
+ 
++NETWORK_EXPORT const char* network_phy_get_cipher_string(const enum network_phy_ciphers cipher) {
++	switch (cipher) {
++		case NETWORK_PHY_CIPHER_WEP40:
++			return "WEP40";
++
++		case NETWORK_PHY_CIPHER_TKIP:
++			return "TKIP";
++
++		case NETWORK_PHY_CIPHER_CCMP128:
++			return "CCMP-128";
++
++		case NETWORK_PHY_CIPHER_WEP104:
++			return "WEP-104";
++
++		case NETWORK_PHY_CIPHER_CMAC128:
++			return "CMAC-128";
++
++		case NETWORK_PHY_CIPHER_GCMP128:
++			return "GCMP-128";
++
++		case NETWORK_PHY_CIPHER_GCMP256:
++			return "GCMP-256";
++
++		case NETWORK_PHY_CIPHER_CCMP256:
++			return "CCMP-256";
++
++		case NETWORK_PHY_CIPHER_GMAC128:
++			return "GMAC-128";
++
++		case NETWORK_PHY_CIPHER_GMAC256:
++			return "GMAC-256";
++
++		case NETWORK_PHY_CIPHER_CMAC256:
++			return "CMAC-256";
++
++		case NETWORK_PHY_CIPHER_WPISMS4:
++			return "WPI-SMS4";
++	}
++
++	return NULL;
++}
++
++NETWORK_EXPORT int network_phy_supports_cipher(struct network_phy* phy, const enum network_phy_ciphers cipher) {
++	return phy->ciphers & cipher;
++}
++
++NETWORK_EXPORT char* network_phy_list_ciphers(struct network_phy* phy) {
++	char* buffer = NULL;
++
++	foreach_cipher(cipher) {
++		if (network_phy_supports_cipher(phy, cipher)) {
++			const char* s = network_phy_get_cipher_string(cipher);
++
++			if (!s)
++				continue;
++
++			if (buffer)
++				asprintf(&buffer, "%s %s", buffer, s);
++			else
++				asprintf(&buffer, "%s", s);
++		}
++	}
++
++	return buffer;
++}
++
+ NETWORK_EXPORT int network_phy_has_vht_capability(struct network_phy* phy, const enum network_phy_vht_caps cap) {
+ 	return phy->vht_caps & cap;
+ }
+diff --git a/src/utils/.gitignore b/src/utils/.gitignore
+index 11cf3b6..df712dc 100644
+--- a/src/utils/.gitignore
++++ b/src/utils/.gitignore
+@@ -1,3 +1,4 @@
+ /network-phy-list-channels
++/network-phy-list-ciphers
+ /network-phy-list-ht-caps
+ /network-phy-list-vht-caps
+diff --git a/src/utils/network-phy-list-ciphers.c b/src/utils/network-phy-list-ciphers.c
+new file mode 100644
+index 0000000..0132c0c
+--- /dev/null
++++ b/src/utils/network-phy-list-ciphers.c
+@@ -0,0 +1,61 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2019 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <stdio.h>
++#include <stdlib.h>
++
++#include <network/libnetwork.h>
++#include <network/logging.h>
++#include <network/phy.h>
++
++int main(int argc, char** argv) {
++    struct network_ctx* ctx = NULL;
++    struct network_phy* phy = NULL;
++    int r;
++
++    if (argc < 2) {
++        fprintf(stderr, "No enough arguments\n");
++        r = 2;
++        goto END;
++    }
++
++    // Initialise context
++    r = network_new(&ctx);
++    if (r)
++        return r;
++
++    r = network_phy_new(ctx, &phy, argv[1]);
++    if (r) {
++        fprintf(stderr, "Could not find %s\n", argv[1]);
++        goto END;
++    }
++
++    // Print all supported ciphers
++    char* ciphers = network_phy_list_ciphers(phy);
++    if (ciphers && *ciphers) {
++        printf("%s\n", ciphers);
++        free(ciphers);
++    }
++
++END:
++    network_phy_unref(phy);
++    network_unref(ctx);
++    return r;
++}
+-- 
+2.39.2
+
diff --git a/network/patches/0033-hostapd-Enable-WPA-authentication-with-SHA256.patch b/network/patches/0033-hostapd-Enable-WPA-authentication-with-SHA256.patch
new file mode 100644
index 000000000..70a50a310
--- /dev/null
+++ b/network/patches/0033-hostapd-Enable-WPA-authentication-with-SHA256.patch
@@ -0,0 +1,26 @@
+From 27380e6e6343faa0b2c1a87234ecf21ecc6f0840 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 12:47:32 +0100
+Subject: [PATCH 033/304] hostapd: Enable WPA authentication with SHA256
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 6111457..79fb4db 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -413,7 +413,7 @@ hostapd_config_write() {
+ 			print "# Encryption settings"
+ 			print "wpa=${encryption_mode}"
+ 			print "wpa_passphrase=${key}"
+-			print "wpa_key_mgmt=WPA-PSK"
++			print "wpa_key_mgmt=WPA-PSK-SHA256 WPA-PSK"
+ 			print "wpa_pairwise=${pairwise_ciphers[*]}"
+ 			print "rsn_pairwise=${pairwise_ciphers[*]}"
+ 			print "group_cipher=${group_ciphers[*]}"
+-- 
+2.39.2
+
diff --git a/network/patches/0034-hooks-Automatically-set-defaults-for-all-port-hooks.patch b/network/patches/0034-hooks-Automatically-set-defaults-for-all-port-hooks.patch
new file mode 100644
index 000000000..f2940d01a
--- /dev/null
+++ b/network/patches/0034-hooks-Automatically-set-defaults-for-all-port-hooks.patch
@@ -0,0 +1,98 @@
+From 4637109c42417e34c02631cd8391bccc7f2733cb Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 13:03:59 +0100
+Subject: [PATCH 034/304] hooks: Automatically set defaults for all port hooks
+
+Before, this was broken so that all configuration parameters
+had to be passed all the time.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hook |  5 ++---
+ src/header-port              |  4 ++++
+ src/hooks/ports/bonding      |  5 ++---
+ src/hooks/ports/wireless-ap  | 15 +++++----------
+ 4 files changed, 13 insertions(+), 16 deletions(-)
+
+diff --git a/src/functions/functions.hook b/src/functions/functions.hook
+index 2f3ced0..c0ebfcb 100644
+--- a/src/functions/functions.hook
++++ b/src/functions/functions.hook
+@@ -130,9 +130,8 @@ hook_set_defaults() {
+ 	for setting in ${HOOK_SETTINGS}; do
+ 		local default="DEFAULT_${setting}"
+ 
+-		if isset ${default}; then
+-			assign "${setting}" "${!default}"
+-		fi
++		# Sets the default or empty
++		assign "${setting}" "${!default}"
+ 	done
+ }
+ 
+diff --git a/src/header-port b/src/header-port
+index ce1c192..141228a 100644
+--- a/src/header-port
++++ b/src/header-port
+@@ -44,6 +44,10 @@ hook_hotplug_rename() {
+ 
+ hook_default_new() {
+ 	local ${HOOK_SETTINGS}
++
++	# Import all default variables
++	hook_set_defaults
++
+ 	if ! hook_parse_cmdline "$@"; then
+ 		return ${EXIT_ERROR}
+ 	fi
+diff --git a/src/hooks/ports/bonding b/src/hooks/ports/bonding
+index 40d849f..f0572c3 100644
+--- a/src/hooks/ports/bonding
++++ b/src/hooks/ports/bonding
+@@ -23,9 +23,8 @@
+ 
+ HOOK_SETTINGS="ADDRESS MIIMON MODE OFFLOADING SLAVES"
+ 
+-SLAVES=""
+-MIIMON=100
+-MODE="balance-rr"
++DEFAULT_MIIMON=100
++DEFAULT_MODE="balance-rr"
+ 
+ hook_check_settings() {
+ 	assert isset ADDRESS
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 7073cbc..2bb4977 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -27,22 +27,17 @@ HOOK_SETTINGS="ADDRESS BROADCAST_SSID CHANNEL CHANNEL_BANDWIDTH DFS MODE PHY"
+ HOOK_SETTINGS="${HOOK_SETTINGS} ENCRYPTION ENVIRONMENT KEY SSID"
+ HOOK_SETTINGS="${HOOK_SETTINGS} MFP"
+ 
+-ADDRESS=$(mac_generate)
+-BROADCAST_SSID=on
+-CHANNEL=
+-CHANNEL_BANDWIDTH=
+-ENCRYPTION=""
+-KEY=""
+-SSID=
++# Broadcast SSID by default
++DEFAULT_BROADCAST_SSID="on"
+ 
+ # Perform radar detection by default when possible
+-DFS="on"
++DEFAULT_DFS="on"
+ 
+ # 802.11w - Management Frame Protection
+ # Disable by default because many clients cannot connect when enabled
+-MFP="off"
++DEFAULT_MFP="off"
+ 
+-ENVIRONMENT="${WIRELESS_DEFAULT_ENVIRONMENT}"
++DEFAULT_ENVIRONMENT="${WIRELESS_DEFAULT_ENVIRONMENT}"
+ 
+ hook_check_settings() {
+ 	assert isset ADDRESS
+-- 
+2.39.2
+
diff --git a/network/patches/0035-hooks-Import-zone-default-settings-too.patch b/network/patches/0035-hooks-Import-zone-default-settings-too.patch
new file mode 100644
index 000000000..c31e8e94d
--- /dev/null
+++ b/network/patches/0035-hooks-Import-zone-default-settings-too.patch
@@ -0,0 +1,176 @@
+From 53e764a73d5a04f653a4fda3c7f8810e8de13ed8 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 13:10:58 +0100
+Subject: [PATCH 035/304] hooks: Import zone default settings, too
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/header-zone           |  5 +++++
+ src/hooks/zones/bridge    | 11 ++---------
+ src/hooks/zones/ip-tunnel |  8 +-------
+ src/hooks/zones/modem     | 12 +-----------
+ src/hooks/zones/pppoe     | 20 ++------------------
+ 5 files changed, 11 insertions(+), 45 deletions(-)
+
+diff --git a/src/header-zone b/src/header-zone
+index ead4a32..7ad3e39 100644
+--- a/src/header-zone
++++ b/src/header-zone
+@@ -34,6 +34,11 @@ hook_new() {
+ 	assert isset zone
+ 	shift
+ 
++	local ${HOOK_SETTINGS}
++
++	# Import all default variables
++	hook_set_defaults
++
+ 	if ! hook_parse_cmdline "$@"; then
+ 		return ${EXIT_ERROR}
+ 	fi
+diff --git a/src/hooks/zones/bridge b/src/hooks/zones/bridge
+index 93a3a31..33d5811 100644
+--- a/src/hooks/zones/bridge
++++ b/src/hooks/zones/bridge
+@@ -29,6 +29,7 @@ HOOK_SETTINGS="${HOOK_SETTINGS} STP_PRIORITY MTU"
+ HOOK_PORT_SETTINGS="COST PRIORITY"
+ 
+ # Default values
++DEFAULT_STP="on"
+ DEFAULT_STP_FORWARD_DELAY=0
+ DEFAULT_STP_HELLO=2
+ DEFAULT_STP_MAXAGE=20
+@@ -123,19 +124,11 @@ hook_parse_cmdline() {
+ 		shift
+ 	done
+ 
+-	# Generate a random MAC address if the user passed no one
++	# Generate a random MAC address if the user passed none
+ 	if ! isset ADDRESS; then
+ 		ADDRESS="$(mac_generate)"
+ 	fi
+ 
+-	# Enable Spanning Tree Protocol by default
+-	if ! isset STP; then
+-		STP="on"
+-	fi
+-
+-	# Set all other defaults
+-	hook_set_defaults
+-
+ 	return ${EXIT_OK}
+ }
+ 
+diff --git a/src/hooks/zones/ip-tunnel b/src/hooks/zones/ip-tunnel
+index c9c73ba..e4be361 100644
+--- a/src/hooks/zones/ip-tunnel
++++ b/src/hooks/zones/ip-tunnel
+@@ -26,13 +26,7 @@ SUPPORTED_IP_TUNNEL_MODES="gre vti"
+ HOOK_SETTINGS="HOOK MARK MODE PEER LOCAL_ADDRESS"
+ 
+ # Default mode of the tunnel
+-MODE="gre"
+-
+-# The IP address of the tunnel endpoint where to connect to
+-PEER=
+-
+-# The local IP address of the tunnel endpoint
+-LOCAL_ADDRESS=
++DEFAULT_MODE="gre"
+ 
+ hook_check_settings() {
+ 	assert isset MODE && assert isoneof MODE ${SUPPORTED_IP_TUNNEL_MODES}
+diff --git a/src/hooks/zones/modem b/src/hooks/zones/modem
+index 1b4c3c0..50d43c7 100644
+--- a/src/hooks/zones/modem
++++ b/src/hooks/zones/modem
+@@ -27,47 +27,37 @@ MODEM_ALLOWED_AUTH_METHODS="${PPP_ALLOWED_AUTH_METHODS}"
+ HOOK_SETTINGS="HOOK"
+ 
+ # Access Point Name.
+-APN=
+ HOOK_SETTINGS="${HOOK_SETTINGS} APN"
+ 
+ # Sets the authentication algortihm that must be used.
+-AUTH=
+ HOOK_SETTINGS="${HOOK_SETTINGS} AUTH"
+ 
+ # Baudrate.
+-BAUDRATE=921600
++DEFAULT_BAUDRATE=921600
+ HOOK_SETTINGS="${HOOK_SETTINGS} BAUDRATE"
+ 
+ # The device name of the serial device.
+ # XXX how can we make sure that this does not change all the time?
+-DEVICE=
+ HOOK_SETTINGS="${HOOK_SETTINGS} DEVICE"
+ 
+ # A monitor device.
+ # Send AT commands to this device, when the primary device is
+ # connected.
+-MONITOR_DEVICE=
+ HOOK_SETTINGS="${HOOK_SETTINGS} MONITOR_DEVICE"
+ 
+ # Maximum transmission unit.
+-MTU=
+ HOOK_SETTINGS="${HOOK_SETTINGS} MTU"
+ 
+ # User credentials.
+-USERNAME=
+-PASSWORD=
+ HOOK_SETTINGS="${HOOK_SETTINGS} USERNAME PASSWORD"
+ 
+ # PIN code.
+-PIN=
+ HOOK_SETTINGS="${HOOK_SETTINGS} PIN"
+ 
+ # Phone number.
+-PHONE_NUMBER=
+ HOOK_SETTINGS="${HOOK_SETTINGS} PHONE_NUMBER"
+ 
+ # IMSI
+-IMSI=
+ HOOK_SETTINGS="${HOOK_SETTINGS} IMSI"
+ 
+ hook_check_settings() {
+diff --git a/src/hooks/zones/pppoe b/src/hooks/zones/pppoe
+index e113c92..cd3913b 100644
+--- a/src/hooks/zones/pppoe
++++ b/src/hooks/zones/pppoe
+@@ -24,31 +24,15 @@
+ HOOK_SETTINGS="HOOK ACCESS_CONCENTRATOR AUTH USERNAME PASSWORD"
+ HOOK_SETTINGS="${HOOK_SETTINGS} SERVICE_NAME MTU IPV6 PREFIX_DELEGATION"
+ 
+-# User credentials for the dialin.
+-USERNAME=""
+-PASSWORD=""
+-
+-# Set the authentication mechanism.
+-AUTH=
+-
+-# Access Concentrator.
+-ACCESS_CONCENTRATOR=""
+-
+-# Service name.
+-SERVICE_NAME=""
+-
+-# Maximum Transmission Unit.
+-MTU=
+-
+ # This hook can work with all authentication methods supported by pppd.
+ PPPOE_SUPPORTED_AUTH_METHODS="${PPP_SUPPORTED_AUTH_METHODS}"
+ PPPOE_PLUGIN="rp-pppoe.so"
+ 
+ # Request an IPv6 address.
+-IPV6="true"
++DEFAULT_IPV6="true"
+ 
+ # Use IPv6 prefix delegation.
+-PREFIX_DELEGATION="true"
++DEFAULT_PREFIX_DELEGATION="true"
+ 
+ hook_check_settings() {
+ 	assert isset USERNAME
+-- 
+2.39.2
+
diff --git a/network/patches/0036-Convert-HOOK_SETTINGS-into-an-array.patch b/network/patches/0036-Convert-HOOK_SETTINGS-into-an-array.patch
new file mode 100644
index 000000000..ad1fc9ade
--- /dev/null
+++ b/network/patches/0036-Convert-HOOK_SETTINGS-into-an-array.patch
@@ -0,0 +1,628 @@
+From d389e96b6c0a73fefd907bc99401b4ce4021bf97 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 13:49:08 +0100
+Subject: [PATCH 036/304] Convert HOOK_SETTINGS into an array
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hook  |  2 +-
+ src/functions/functions.ports |  4 +--
+ src/functions/functions.zone  |  6 ++---
+ src/header-port               | 10 ++++----
+ src/header-zone               |  2 +-
+ src/hooks/ports/bonding       | 20 +++++++++------
+ src/hooks/ports/dummy         | 10 +++++---
+ src/hooks/ports/ethernet      | 19 +++++++++------
+ src/hooks/ports/ip-tunnel     | 16 ++++++++----
+ src/hooks/ports/vlan          | 14 +++++++----
+ src/hooks/ports/wireless-ap   | 19 ++++++++++++---
+ src/hooks/ports/wireless-mesh | 12 ++++++---
+ src/hooks/zones/bridge        | 17 ++++++++++---
+ src/hooks/zones/ip-tunnel     |  7 +++++-
+ src/hooks/zones/modem         | 46 ++++++++++-------------------------
+ src/hooks/zones/pppoe         | 12 +++++++--
+ src/hooks/zones/wireless      |  5 +++-
+ 17 files changed, 132 insertions(+), 89 deletions(-)
+
+diff --git a/src/functions/functions.hook b/src/functions/functions.hook
+index c0ebfcb..fb68037 100644
+--- a/src/functions/functions.hook
++++ b/src/functions/functions.hook
+@@ -127,7 +127,7 @@ hook_help() {
+ # Sets all settings in HOOK_SETTINGS to their DEFAULT_* values
+ hook_set_defaults() {
+ 	local setting
+-	for setting in ${HOOK_SETTINGS}; do
++	for setting in ${HOOK_SETTINGS[*]}; do
+ 		local default="DEFAULT_${setting}"
+ 
+ 		# Sets the default or empty
+diff --git a/src/functions/functions.ports b/src/functions/functions.ports
+index f70adf6..fb22715 100644
+--- a/src/functions/functions.ports
++++ b/src/functions/functions.ports
+@@ -85,7 +85,7 @@ port_settings_read() {
+ 	# Save the HOOK variable.
+ 	local hook="${HOOK}"
+ 
+-	settings_read "$(port_file "${port}")" ${HOOK_SETTINGS}
++	settings_read "$(port_file "${port}")" ${HOOK_SETTINGS[*]}
+ 
+ 	# Restore hook.
+ 	HOOK="${hook}"
+@@ -100,7 +100,7 @@ port_settings_write() {
+ 	if function_exists "hook_check_settings"; then
+ 		list_append args "--check=\"hook_check_settings\""
+ 	fi
+-	list_append args HOOK ${HOOK_SETTINGS}
++	list_append args HOOK ${HOOK_SETTINGS[*]}
+ 
+ 	settings_write "$(port_file "${port}")" ${args}
+ }
+diff --git a/src/functions/functions.zone b/src/functions/functions.zone
+index 57e0b71..a0d3cfb 100644
+--- a/src/functions/functions.zone
++++ b/src/functions/functions.zone
+@@ -1248,8 +1248,8 @@ zone_settings_read() {
+ 	shift
+ 
+ 	local args
+-	if [ $# -eq 0 ] && [ -n "${HOOK_SETTINGS}" ]; then
+-		list_append args ${HOOK_SETTINGS}
++	if [ $# -eq 0 ] && [ -n "${HOOK_SETTINGS[*]}" ]; then
++		list_append args ${HOOK_SETTINGS[*]}
+ 	else
+ 		list_append args "$@"
+ 	fi
+@@ -1271,7 +1271,7 @@ zone_settings_write() {
+ 	if function_exists "hook_check_settings"; then
+ 		list_append args "--check=\"hook_check_settings\""
+ 	fi
+-	list_append args ${HOOK_SETTINGS}
++	list_append args HOOK ${HOOK_SETTINGS[*]}
+ 
+ 	settings_write "${NETWORK_ZONES_DIR}/${zone}/settings" ${args}
+ }
+diff --git a/src/header-port b/src/header-port
+index 141228a..2d8a820 100644
+--- a/src/header-port
++++ b/src/header-port
+@@ -43,7 +43,7 @@ hook_hotplug_rename() {
+ }
+ 
+ hook_default_new() {
+-	local ${HOOK_SETTINGS}
++	local ${HOOK_SETTINGS[*]}
+ 
+ 	# Import all default variables
+ 	hook_set_defaults
+@@ -57,7 +57,7 @@ hook_default_new() {
+ 	local port=$(port_find_free ${HOOK_PORT_PATTERN})
+ 	assert isset port
+ 
+-	port_settings_write "${port}" ${HOOK_SETTINGS}
++	port_settings_write "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	exit ${EXIT_OK}
+ }
+@@ -72,7 +72,7 @@ hook_default_edit() {
+ 	shift
+ 
+ 	# Read settings
+-	if ! port_settings_read "${port}" ${HOOK_SETTINGS}; then
++	if ! port_settings_read "${port}" ${HOOK_SETTINGS[*]}; then
+ 		error "Could not read settings for port ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+@@ -83,7 +83,7 @@ hook_default_edit() {
+ 	fi
+ 
+ 	# Save settings
+-	if ! port_settings_write "${port}" ${HOOK_SETTINGS}; then
++	if ! port_settings_write "${port}" ${HOOK_SETTINGS[*]}; then
+ 		error "Could not write settings for port ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+@@ -102,7 +102,7 @@ hook_edit() {
+ hook_children() {
+ 	local port="${1}"
+ 
+-	if ! port_settings_read "${port}" ${HOOK_SETTINGS}; then
++	if ! port_settings_read "${port}" ${HOOK_SETTINGS[*]}; then
+ 		log ERROR "Could not read port settings: ${port}"
+ 		return ${EXIT_OK}
+ 	fi
+diff --git a/src/header-zone b/src/header-zone
+index 7ad3e39..2174b01 100644
+--- a/src/header-zone
++++ b/src/header-zone
+@@ -34,7 +34,7 @@ hook_new() {
+ 	assert isset zone
+ 	shift
+ 
+-	local ${HOOK_SETTINGS}
++	local ${HOOK_SETTINGS[*]}
+ 
+ 	# Import all default variables
+ 	hook_set_defaults
+diff --git a/src/hooks/ports/bonding b/src/hooks/ports/bonding
+index f0572c3..09fb74f 100644
+--- a/src/hooks/ports/bonding
++++ b/src/hooks/ports/bonding
+@@ -21,7 +21,13 @@
+ 
+ . /usr/lib/network/header-port
+ 
+-HOOK_SETTINGS="ADDRESS MIIMON MODE OFFLOADING SLAVES"
++HOOK_SETTINGS=(
++	"ADDRESS"
++	"MIIMON"
++	"MODE"
++	"OFFLOADING"
++	"SLAVES"
++)
+ 
+ DEFAULT_MIIMON=100
+ DEFAULT_MODE="balance-rr"
+@@ -110,7 +116,7 @@ hook_new() {
+ 	assert isset port
+ 
+ 	# Save configuration
+-	if port_settings_write "${port}" ${HOOK_SETTINGS}; then
++	if port_settings_write "${port}" ${HOOK_SETTINGS[*]}; then
+ 		log INFO "New port ${port} has been created"
+ 	else
+ 		error "Could not save configuration for ${port}"
+@@ -162,7 +168,7 @@ hook_create() {
+ 	# Exit silently if the device already exists
+ 	device_exists "${port}" && exit ${EXIT_OK}
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	# Create the bonding devices
+ 	bonding_create "${port}" \
+@@ -178,7 +184,7 @@ hook_remove() {
+ 	local port="${1}"
+ 	assert isset port
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	# Remove the bonding device
+ 	if device_exists "${port}"; then
+@@ -190,7 +196,7 @@ hook_up() {
+ 	local port="${1}"
+ 	assert isset port
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	# Auto-enable or disable hardware offloading
+ 	if ! isset OFFLOADING || enabled OFFLOADING; then
+@@ -213,7 +219,7 @@ hook_down() {
+ 	local port="${1}"
+ 	assert isset port
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	# Bring down all slaves
+ 	local slave
+@@ -234,7 +240,7 @@ hook_hotplug() {
+ 			# Handle events of the same interface
+ 			if hotplug_event_port_is_interface "${port}"; then
+ 				# Read configuration
+-				port_settings_read "${port}" ${HOOK_SETTINGS}
++				port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 				# Bring up all slaves
+ 				# Attach those which already exist and try to create
+diff --git a/src/hooks/ports/dummy b/src/hooks/ports/dummy
+index 61d2f94..1c4b3c9 100644
+--- a/src/hooks/ports/dummy
++++ b/src/hooks/ports/dummy
+@@ -21,7 +21,9 @@
+ 
+ . /usr/lib/network/header-port
+ 
+-HOOK_SETTINGS="ADDRESS"
++HOOK_SETTINGS=(
++	"ADDRESS"
++)
+ 
+ hook_check_settings() {
+ 	assert ismac ADDRESS
+@@ -60,7 +62,7 @@ hook_new() {
+ 	local port=$(port_find_free ${DUMMY_PORT_PATTERN})
+ 	assert isset port
+ 
+-	if port_settings_write "${port}" ${HOOK_SETTINGS}; then
++	if port_settings_write "${port}" ${HOOK_SETTINGS[*]}; then
+ 		log INFO "New dummy port '${port}' has been created"
+ 	fi
+ 
+@@ -72,7 +74,7 @@ hook_create() {
+ 	assert isset port
+ 
+ 	# Read configuration
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	# Create the dummy device
+ 	dummy_create "${port}" "${ADDRESS}"
+@@ -115,7 +117,7 @@ hook_hotplug_rename() {
+ 	local device=${2}
+ 	assert isset device
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	if [ "${ADDRESS}" = "$(device_get_address ${device})" ]; then
+ 		log DEBUG "Device '${device}' equals port '${port}'."
+diff --git a/src/hooks/ports/ethernet b/src/hooks/ports/ethernet
+index 0d9c5cd..f3e3f9f 100644
+--- a/src/hooks/ports/ethernet
++++ b/src/hooks/ports/ethernet
+@@ -21,10 +21,13 @@
+ 
+ . /usr/lib/network/header-port
+ 
+-# DEVICE equals the actual MAC address of the device.
+-# If ADDRESS is set, the device will get ADDRESS set for its MAC address.
+-
+-HOOK_SETTINGS="ADDRESS ADVERTISED_LINK_SPEEDS DEVICE OFFLOADING MTU"
++HOOK_SETTINGS=(
++	"ADDRESS"
++	"ADVERTISED_LINK_SPEEDS"
++	"DEVICE"
++	"OFFLOADING"
++	"MTU"
++)
+ 
+ hook_check_settings() {
+ 	assert ismac DEVICE
+@@ -114,7 +117,7 @@ hook_new() {
+ 
+ 	local DEVICE="$(device_get_address "${device}")"
+ 
+-	if ! port_settings_write "${port}" ${HOOK_SETTINGS}; then
++	if ! port_settings_write "${port}" ${HOOK_SETTINGS[*]}; then
+ 		log ERROR "Could not write settings for port ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+@@ -129,8 +132,8 @@ hook_create() {
+ hook_up() {
+ 	local port="${1}"
+ 
+-	local ${HOOK_SETTINGS}
+-	if ! port_settings_read "${port}" ${HOOK_SETTINGS}; then
++	local ${HOOK_SETTINGS[*]}
++	if ! port_settings_read "${port}" ${HOOK_SETTINGS[*]}; then
+ 		log ERROR "Could not read settings for port ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+@@ -177,7 +180,7 @@ hook_hotplug_rename() {
+ 	assert isset device
+ 
+ 	# Read in the conifguration file.
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	# Get the current MAC address of the device.
+ 	local address=$(device_get_address ${device})
+diff --git a/src/hooks/ports/ip-tunnel b/src/hooks/ports/ip-tunnel
+index 3943e4c..b426963 100644
+--- a/src/hooks/ports/ip-tunnel
++++ b/src/hooks/ports/ip-tunnel
+@@ -23,7 +23,13 @@
+ 
+ SUPPORTED_IP_TUNNEL_MODES="gretap"
+ 
+-HOOK_SETTINGS="ADDRESS MARK MODE PEER LOCAL_ADDRESS"
++HOOK_SETTINGS=(
++	"ADDRESS"
++	"MARK"
++	"MODE"
++	"PEER"
++	"LOCAL_ADDRESS"
++)
+ 
+ hook_check_settings() {
+ 	assert isset MODE
+@@ -108,8 +114,8 @@ hook_create() {
+ 	local port="${1}"
+ 	assert isset port
+ 
+-	local ${HOOK_SETTINGS}
+-	if ! port_settings_read "${port}" ${HOOK_SETTINGS}; then
++	local ${HOOK_SETTINGS[*]}
++	if ! port_settings_read "${port}" ${HOOK_SETTINGS[*]}; then
+ 		log ERROR "Could not read settings for port ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+@@ -146,8 +152,8 @@ hook_hotplug_rename() {
+ 	local device="${2}"
+ 	assert isset device
+ 
+-	local ${HOOK_SETTINGS}
+-	if ! port_settings_read "${port}" ${HOOK_SETTINGS}; then
++	local ${HOOK_SETTINGS[*]}
++	if ! port_settings_read "${port}" ${HOOK_SETTINGS[*]}; then
+ 		log ERROR "Could not read settings for port ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index bc12a9e..e9aa545 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -21,7 +21,11 @@
+ 
+ . /usr/lib/network/header-port
+ 
+-HOOK_SETTINGS="ADDRESS PARENT_DEVICE TAG"
++HOOK_SETTINGS=(
++	"ADDRESS"
++	"PARENT_DEVICE"
++	"TAG"
++)
+ 
+ PORT_PARENTS_VAR="PARENT"
+ 
+@@ -68,7 +72,7 @@ hook_new() {
+ 
+ 	local port="${PARENT_DEVICE}${VLAN_PORT_INTERFIX}${TAG}"
+ 
+-	port_settings_write "${port}" ${HOOK_SETTINGS}
++	port_settings_write "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	exit ${EXIT_OK}
+ }
+@@ -78,7 +82,7 @@ hook_edit() {
+ 	assert isset port
+ 	shift
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	while [ $# -gt 0 ]; do
+ 		case "${1}" in
+@@ -92,7 +96,7 @@ hook_edit() {
+ 		shift
+ 	done
+ 
+-	port_settings_write "${port}" ${HOOK_SETTINGS}
++	port_settings_write "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	exit ${EXIT_OK}	
+ }
+@@ -104,7 +108,7 @@ hook_create() {
+ 	device_exists "${port}" && exit ${EXIT_OK}
+ 
+ 	# Read configruation
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	# Create the VLAN device
+ 	vlan_create "${port}" "${PARENT_DEVICE}" "${TAG}" "${ADDRESS}"
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 2bb4977..8d495d2 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -23,9 +23,20 @@
+ 
+ HOOK_PORT_PATTERN="${PORT_PATTERN_ACCESSPOINT}"
+ 
+-HOOK_SETTINGS="ADDRESS BROADCAST_SSID CHANNEL CHANNEL_BANDWIDTH DFS MODE PHY"
+-HOOK_SETTINGS="${HOOK_SETTINGS} ENCRYPTION ENVIRONMENT KEY SSID"
+-HOOK_SETTINGS="${HOOK_SETTINGS} MFP"
++HOOK_SETTINGS=(
++	"ADDRESS"
++	"BROADCAST_SSID"
++	"CHANNEL"
++	"CHANNEL_BANDWIDTH"
++	"DFS"
++	"ENCRYPTION"
++	"ENVIRONMENT"
++	"KEY"
++	"MFP"
++	"MODE"
++	"PHY"
++	"SSID"
++)
+ 
+ # Broadcast SSID by default
+ DEFAULT_BROADCAST_SSID="on"
+@@ -186,7 +197,7 @@ hook_create() {
+ 
+ 	device_exists "${port}" && exit ${EXIT_OK}
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	# Check if the PHY is present.
+ 	local phy=$(phy_get ${PHY})
+diff --git a/src/hooks/ports/wireless-mesh b/src/hooks/ports/wireless-mesh
+index 4fb4dc9..306263d 100644
+--- a/src/hooks/ports/wireless-mesh
++++ b/src/hooks/ports/wireless-mesh
+@@ -23,7 +23,13 @@
+ 
+ HOOK_PORT_PATTERN="${PORT_PATTERN_MESH}"
+ 
+-HOOK_SETTINGS="ADDRESS MESH_ID CHANNEL PHY PSK"
++HOOK_SETTINGS=(
++	"ADDRESS"
++	"CHANNEL"
++	"MESH_ID"
++	"PHY"
++	"PSK"
++)
+ 
+ hook_check_settings() {
+ 	assert ismac ADDRESS
+@@ -84,7 +90,7 @@ hook_create() {
+ 	assert isset port
+ 
+ 	# Read settings
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	# Check if the PHY is present.
+ 	local phy="$(phy_get "${PHY}")"
+@@ -143,7 +149,7 @@ hook_hotplug() {
+ 	local port="${1}"
+ 	assert isset port
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS}
++	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
+ 
+ 	case "$(hotplug_action)" in
+ 		add)
+diff --git a/src/hooks/zones/bridge b/src/hooks/zones/bridge
+index 33d5811..0b18331 100644
+--- a/src/hooks/zones/bridge
++++ b/src/hooks/zones/bridge
+@@ -23,10 +23,19 @@
+ 
+ HOOK_MANPAGE="network-zone-bridge"
+ 
+-HOOK_SETTINGS="HOOK ADDRESS STP STP_FORWARD_DELAY STP_HELLO STP_MAXAGE"
+-HOOK_SETTINGS="${HOOK_SETTINGS} STP_PRIORITY MTU"
+-
+-HOOK_PORT_SETTINGS="COST PRIORITY"
++HOOK_SETTINGS=(
++	"ADDRESS"
++	"STP"
++	"STP_FORWARD_DELAY"
++	"STP_HELLO STP_MAXAGE"
++	"STP_PRIORITY"
++	"MTU"
++)
++
++HOOK_PORT_SETTINGS=(
++	"COST"
++	"PRIORITY"
++)
+ 
+ # Default values
+ DEFAULT_STP="on"
+diff --git a/src/hooks/zones/ip-tunnel b/src/hooks/zones/ip-tunnel
+index e4be361..634154e 100644
+--- a/src/hooks/zones/ip-tunnel
++++ b/src/hooks/zones/ip-tunnel
+@@ -23,7 +23,12 @@
+ 
+ SUPPORTED_IP_TUNNEL_MODES="gre vti"
+ 
+-HOOK_SETTINGS="HOOK MARK MODE PEER LOCAL_ADDRESS"
++HOOK_SETTINGS=(
++	"MARK"
++	"MODE"
++	"PEER"
++	"LOCAL_ADDRESS"
++)
+ 
+ # Default mode of the tunnel
+ DEFAULT_MODE="gre"
+diff --git a/src/hooks/zones/modem b/src/hooks/zones/modem
+index 50d43c7..e12b104 100644
+--- a/src/hooks/zones/modem
++++ b/src/hooks/zones/modem
+@@ -24,41 +24,21 @@
+ # Modems support all authentication methods, that pppd does support.
+ MODEM_ALLOWED_AUTH_METHODS="${PPP_ALLOWED_AUTH_METHODS}"
+ 
+-HOOK_SETTINGS="HOOK"
++HOOK_SETTINGS=(
++	"APN"
++	"AUTH"
++	"BAUDRATE"
++	"DEVICE"
++	"MONITOR_DEVICE"
++	"MTU"
++	"USERNAME"
++	"PASSWORD"
++	"PIN"
++	"PHONE_NUMBER"
++	"IMSI"
++)
+ 
+-# Access Point Name.
+-HOOK_SETTINGS="${HOOK_SETTINGS} APN"
+-
+-# Sets the authentication algortihm that must be used.
+-HOOK_SETTINGS="${HOOK_SETTINGS} AUTH"
+-
+-# Baudrate.
+ DEFAULT_BAUDRATE=921600
+-HOOK_SETTINGS="${HOOK_SETTINGS} BAUDRATE"
+-
+-# The device name of the serial device.
+-# XXX how can we make sure that this does not change all the time?
+-HOOK_SETTINGS="${HOOK_SETTINGS} DEVICE"
+-
+-# A monitor device.
+-# Send AT commands to this device, when the primary device is
+-# connected.
+-HOOK_SETTINGS="${HOOK_SETTINGS} MONITOR_DEVICE"
+-
+-# Maximum transmission unit.
+-HOOK_SETTINGS="${HOOK_SETTINGS} MTU"
+-
+-# User credentials.
+-HOOK_SETTINGS="${HOOK_SETTINGS} USERNAME PASSWORD"
+-
+-# PIN code.
+-HOOK_SETTINGS="${HOOK_SETTINGS} PIN"
+-
+-# Phone number.
+-HOOK_SETTINGS="${HOOK_SETTINGS} PHONE_NUMBER"
+-
+-# IMSI
+-HOOK_SETTINGS="${HOOK_SETTINGS} IMSI"
+ 
+ hook_check_settings() {
+ 	assert isset DEVICE
+diff --git a/src/hooks/zones/pppoe b/src/hooks/zones/pppoe
+index cd3913b..4f7ae51 100644
+--- a/src/hooks/zones/pppoe
++++ b/src/hooks/zones/pppoe
+@@ -21,8 +21,16 @@
+ 
+ . /usr/lib/network/header-zone
+ 
+-HOOK_SETTINGS="HOOK ACCESS_CONCENTRATOR AUTH USERNAME PASSWORD"
+-HOOK_SETTINGS="${HOOK_SETTINGS} SERVICE_NAME MTU IPV6 PREFIX_DELEGATION"
++HOOK_SETTINGS=(
++	"ACCESS_CONCENTRATOR"
++	"AUTH"
++	"USERNAME"
++	"PASSWORD"
++	"SERVICE_NAME"
++	"MTU"
++	"IPV6"
++	"PREFIX_DELEGATION"
++)
+ 
+ # This hook can work with all authentication methods supported by pppd.
+ PPPOE_SUPPORTED_AUTH_METHODS="${PPP_SUPPORTED_AUTH_METHODS}"
+diff --git a/src/hooks/zones/wireless b/src/hooks/zones/wireless
+index 553d917..9c52dce 100644
+--- a/src/hooks/zones/wireless
++++ b/src/hooks/zones/wireless
+@@ -21,7 +21,10 @@
+ 
+ . /usr/lib/network/header-zone
+ 
+-HOOK_SETTINGS="HOOK ADDRESS PHY"
++HOOK_SETTINGS=(
++	"ADDRESS"
++	"PHY"
++)
+ 
+ hook_check_settings() {
+ 	assert ismac ADDRESS
+-- 
+2.39.2
+
diff --git a/network/patches/0037-settings-Some-code-refactoring.patch b/network/patches/0037-settings-Some-code-refactoring.patch
new file mode 100644
index 000000000..e3f473f0b
--- /dev/null
+++ b/network/patches/0037-settings-Some-code-refactoring.patch
@@ -0,0 +1,133 @@
+From 227d458f4fac10cbf0970515edd3227913fc1bf4 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 14:04:35 +0100
+Subject: [PATCH 037/304] settings: Some code refactoring
+
+No functional changes
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hook  | 13 +++++++++++++
+ src/functions/functions.ports | 10 ++--------
+ src/functions/functions.zone  | 32 ++++++++------------------------
+ 3 files changed, 23 insertions(+), 32 deletions(-)
+
+diff --git a/src/functions/functions.hook b/src/functions/functions.hook
+index fb68037..11887cd 100644
+--- a/src/functions/functions.hook
++++ b/src/functions/functions.hook
+@@ -124,6 +124,19 @@ hook_help() {
+ 	exit $?
+ }
+ 
++# Dummy functions being overlayed by hooks
++hook_check_settings() {
++	:
++}
++
++hook_check_config_settings() {
++	:
++}
++
++hook_check_port_settings() {
++	:
++}
++
+ # Sets all settings in HOOK_SETTINGS to their DEFAULT_* values
+ hook_set_defaults() {
+ 	local setting
+diff --git a/src/functions/functions.ports b/src/functions/functions.ports
+index fb22715..d8a9140 100644
+--- a/src/functions/functions.ports
++++ b/src/functions/functions.ports
+@@ -94,15 +94,9 @@ port_settings_read() {
+ port_settings_write() {
+ 	local port="${1}"
+ 	assert isset port
+-	shift
+-
+-	local args
+-	if function_exists "hook_check_settings"; then
+-		list_append args "--check=\"hook_check_settings\""
+-	fi
+-	list_append args HOOK ${HOOK_SETTINGS[*]}
+ 
+-	settings_write "$(port_file "${port}")" ${args}
++	settings_write "$(port_file "${port}")" \
++		--check="hook_check_settings" HOOK ${HOOK_SETTINGS[*]}
+ }
+ 
+ port_file() {
+diff --git a/src/functions/functions.zone b/src/functions/functions.zone
+index a0d3cfb..e81371b 100644
+--- a/src/functions/functions.zone
++++ b/src/functions/functions.zone
+@@ -1267,13 +1267,8 @@ zone_settings_write() {
+ 	local zone="${1}"
+ 	assert isset zone
+ 
+-	local args
+-	if function_exists "hook_check_settings"; then
+-		list_append args "--check=\"hook_check_settings\""
+-	fi
+-	list_append args HOOK ${HOOK_SETTINGS[*]}
+-
+-	settings_write "${NETWORK_ZONES_DIR}/${zone}/settings" ${args}
++	settings_write "${NETWORK_ZONES_DIR}/${zone}/settings" \
++		--check="hook_check_settings" HOOK ${HOOK_SETTINGS[*]}
+ }
+ 
+ zone_settings_set() {
+@@ -1328,7 +1323,7 @@ zone_config_settings_read() {
+ }
+ 
+ zone_config_settings_write() {
+-	assert [ $# -ge 2 ]
++	assert [ $# -eq 2 ]
+ 
+ 	local zone="${1}"
+ 	local hook="${2}"
+@@ -1336,14 +1331,9 @@ zone_config_settings_write() {
+ 
+ 	assert isset id
+ 
+-	local args
+-	if function_exists "hook_check_config_settings"; then
+-		list_append args "--check=\"hook_check_config_settings\""
+-	fi
+-	list_append args ${HOOK_CONFIG_SETTINGS}
+-
+ 	local path="${NETWORK_ZONES_DIR}/${zone}/configs/${hook}.${id}"
+-	settings_write "${path}" ${args}
++	settings_write "${path}" \
++		--check="hook_check_config_settings" ${HOOK_CONFIG_SETTINGS[*]}
+ }
+ 
+ zone_config_settings_destroy() {
+@@ -1416,20 +1406,14 @@ zone_port_settings_read() {
+ }
+ 
+ zone_port_settings_write() {
+-	assert [ $# -ge 2 ]
++	assert [ $# -eq 2 ]
+ 
+ 	local zone="${1}"
+ 	local port="${2}"
+-	shift 2
+-
+-	local args
+-	if function_exists "hook_check_port_settings"; then
+-		list_append args "--check=\"hook_check_port_settings\""
+-	fi
+-	list_append args ${HOOK_PORT_SETTINGS}
+ 
+ 	local path="${NETWORK_ZONES_DIR}/${zone}/ports/${port}"
+-	settings_write "${path}" ${args}
++	settings_write "${path}" \
++		--check="hook_check_port_settings" ${HOOK_PORT_SETTINGS[*]}
+ }
+ 
+ zone_port_settings_remove() {
+-- 
+2.39.2
+
diff --git a/network/patches/0038-ports-Drop-HOOK_SETTINGS-variable.patch b/network/patches/0038-ports-Drop-HOOK_SETTINGS-variable.patch
new file mode 100644
index 000000000..87b3a1f12
--- /dev/null
+++ b/network/patches/0038-ports-Drop-HOOK_SETTINGS-variable.patch
@@ -0,0 +1,239 @@
+From eba9fa9c0b647552d8a43fb6ff5ab00c2ab21402 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 14:14:56 +0100
+Subject: [PATCH 038/304] ports: Drop HOOK_SETTINGS variable
+
+This does not need to be passed to the port_settings_* functions
+any more which makes them more easy to use
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/bonding       | 12 ++++++------
+ src/hooks/ports/dummy         |  6 +++---
+ src/hooks/ports/ethernet      |  6 +++---
+ src/hooks/ports/ip-tunnel     |  4 ++--
+ src/hooks/ports/vlan          |  8 ++++----
+ src/hooks/ports/wireless-ap   |  2 +-
+ src/hooks/ports/wireless-mesh |  4 ++--
+ 7 files changed, 21 insertions(+), 21 deletions(-)
+
+diff --git a/src/hooks/ports/bonding b/src/hooks/ports/bonding
+index 09fb74f..a0cf5c0 100644
+--- a/src/hooks/ports/bonding
++++ b/src/hooks/ports/bonding
+@@ -116,7 +116,7 @@ hook_new() {
+ 	assert isset port
+ 
+ 	# Save configuration
+-	if port_settings_write "${port}" ${HOOK_SETTINGS[*]}; then
++	if port_settings_write "${port}"; then
+ 		log INFO "New port ${port} has been created"
+ 	else
+ 		error "Could not save configuration for ${port}"
+@@ -168,7 +168,7 @@ hook_create() {
+ 	# Exit silently if the device already exists
+ 	device_exists "${port}" && exit ${EXIT_OK}
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	# Create the bonding devices
+ 	bonding_create "${port}" \
+@@ -184,7 +184,7 @@ hook_remove() {
+ 	local port="${1}"
+ 	assert isset port
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	# Remove the bonding device
+ 	if device_exists "${port}"; then
+@@ -196,7 +196,7 @@ hook_up() {
+ 	local port="${1}"
+ 	assert isset port
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	# Auto-enable or disable hardware offloading
+ 	if ! isset OFFLOADING || enabled OFFLOADING; then
+@@ -219,7 +219,7 @@ hook_down() {
+ 	local port="${1}"
+ 	assert isset port
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	# Bring down all slaves
+ 	local slave
+@@ -240,7 +240,7 @@ hook_hotplug() {
+ 			# Handle events of the same interface
+ 			if hotplug_event_port_is_interface "${port}"; then
+ 				# Read configuration
+-				port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++				port_settings_read "${port}"
+ 
+ 				# Bring up all slaves
+ 				# Attach those which already exist and try to create
+diff --git a/src/hooks/ports/dummy b/src/hooks/ports/dummy
+index 1c4b3c9..387c88b 100644
+--- a/src/hooks/ports/dummy
++++ b/src/hooks/ports/dummy
+@@ -62,7 +62,7 @@ hook_new() {
+ 	local port=$(port_find_free ${DUMMY_PORT_PATTERN})
+ 	assert isset port
+ 
+-	if port_settings_write "${port}" ${HOOK_SETTINGS[*]}; then
++	if port_settings_write "${port}"; then
+ 		log INFO "New dummy port '${port}' has been created"
+ 	fi
+ 
+@@ -74,7 +74,7 @@ hook_create() {
+ 	assert isset port
+ 
+ 	# Read configuration
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	# Create the dummy device
+ 	dummy_create "${port}" "${ADDRESS}"
+@@ -117,7 +117,7 @@ hook_hotplug_rename() {
+ 	local device=${2}
+ 	assert isset device
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	if [ "${ADDRESS}" = "$(device_get_address ${device})" ]; then
+ 		log DEBUG "Device '${device}' equals port '${port}'."
+diff --git a/src/hooks/ports/ethernet b/src/hooks/ports/ethernet
+index f3e3f9f..5f76e15 100644
+--- a/src/hooks/ports/ethernet
++++ b/src/hooks/ports/ethernet
+@@ -117,7 +117,7 @@ hook_new() {
+ 
+ 	local DEVICE="$(device_get_address "${device}")"
+ 
+-	if ! port_settings_write "${port}" ${HOOK_SETTINGS[*]}; then
++	if ! port_settings_write "${port}"; then
+ 		log ERROR "Could not write settings for port ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+@@ -133,7 +133,7 @@ hook_up() {
+ 	local port="${1}"
+ 
+ 	local ${HOOK_SETTINGS[*]}
+-	if ! port_settings_read "${port}" ${HOOK_SETTINGS[*]}; then
++	if ! port_settings_read "${port}"; then
+ 		log ERROR "Could not read settings for port ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+@@ -180,7 +180,7 @@ hook_hotplug_rename() {
+ 	assert isset device
+ 
+ 	# Read in the conifguration file.
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	# Get the current MAC address of the device.
+ 	local address=$(device_get_address ${device})
+diff --git a/src/hooks/ports/ip-tunnel b/src/hooks/ports/ip-tunnel
+index b426963..fa7193c 100644
+--- a/src/hooks/ports/ip-tunnel
++++ b/src/hooks/ports/ip-tunnel
+@@ -115,7 +115,7 @@ hook_create() {
+ 	assert isset port
+ 
+ 	local ${HOOK_SETTINGS[*]}
+-	if ! port_settings_read "${port}" ${HOOK_SETTINGS[*]}; then
++	if ! port_settings_read "${port}"; then
+ 		log ERROR "Could not read settings for port ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+@@ -153,7 +153,7 @@ hook_hotplug_rename() {
+ 	assert isset device
+ 
+ 	local ${HOOK_SETTINGS[*]}
+-	if ! port_settings_read "${port}" ${HOOK_SETTINGS[*]}; then
++	if ! port_settings_read "${port}"; then
+ 		log ERROR "Could not read settings for port ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index e9aa545..f511986 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -72,7 +72,7 @@ hook_new() {
+ 
+ 	local port="${PARENT_DEVICE}${VLAN_PORT_INTERFIX}${TAG}"
+ 
+-	port_settings_write "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_write "${port}"
+ 
+ 	exit ${EXIT_OK}
+ }
+@@ -82,7 +82,7 @@ hook_edit() {
+ 	assert isset port
+ 	shift
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	while [ $# -gt 0 ]; do
+ 		case "${1}" in
+@@ -96,7 +96,7 @@ hook_edit() {
+ 		shift
+ 	done
+ 
+-	port_settings_write "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_write "${port}"
+ 
+ 	exit ${EXIT_OK}	
+ }
+@@ -108,7 +108,7 @@ hook_create() {
+ 	device_exists "${port}" && exit ${EXIT_OK}
+ 
+ 	# Read configruation
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	# Create the VLAN device
+ 	vlan_create "${port}" "${PARENT_DEVICE}" "${TAG}" "${ADDRESS}"
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 8d495d2..e393f5f 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -197,7 +197,7 @@ hook_create() {
+ 
+ 	device_exists "${port}" && exit ${EXIT_OK}
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	# Check if the PHY is present.
+ 	local phy=$(phy_get ${PHY})
+diff --git a/src/hooks/ports/wireless-mesh b/src/hooks/ports/wireless-mesh
+index 306263d..35f0950 100644
+--- a/src/hooks/ports/wireless-mesh
++++ b/src/hooks/ports/wireless-mesh
+@@ -90,7 +90,7 @@ hook_create() {
+ 	assert isset port
+ 
+ 	# Read settings
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	# Check if the PHY is present.
+ 	local phy="$(phy_get "${PHY}")"
+@@ -149,7 +149,7 @@ hook_hotplug() {
+ 	local port="${1}"
+ 	assert isset port
+ 
+-	port_settings_read "${port}" ${HOOK_SETTINGS[*]}
++	port_settings_read "${port}"
+ 
+ 	case "$(hotplug_action)" in
+ 		add)
+-- 
+2.39.2
+
diff --git a/network/patches/0039-hotplug-Remove-multiple-copies-of-the-same-function.patch b/network/patches/0039-hotplug-Remove-multiple-copies-of-the-same-function.patch
new file mode 100644
index 000000000..ee5f6592e
--- /dev/null
+++ b/network/patches/0039-hotplug-Remove-multiple-copies-of-the-same-function.patch
@@ -0,0 +1,162 @@
+From 12f9c8d2550c8fcab536bb8b971caddfa8ee0c80 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 14:58:12 +0100
+Subject: [PATCH 039/304] hotplug: Remove multiple copies of the same function
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/header-port           | 26 ++++++++++++++++++++++++++
+ src/hooks/ports/dummy     | 36 +-----------------------------------
+ src/hooks/ports/ethernet  | 22 +---------------------
+ src/hooks/ports/ip-tunnel | 23 +----------------------
+ 4 files changed, 29 insertions(+), 78 deletions(-)
+
+diff --git a/src/header-port b/src/header-port
+index 2d8a820..d75fdd8 100644
+--- a/src/header-port
++++ b/src/header-port
+@@ -42,6 +42,32 @@ hook_hotplug_rename() {
+ 	exit ${EXIT_FALSE}
+ }
+ 
++hook_hotplug_rename_by_address() {
++	local port="${1}"
++	assert isset port
++
++	local device="${2}"
++	assert isset device
++
++	# Read in the conifguration file.
++	if ! port_settings_read "${port}"; then
++		return ${EXIT_ERROR}
++	fi
++
++	# Get the current MAC address of the device.
++	local address="$(device_get_address "${device}")"
++	assert isset address
++
++	# Check if the address matches with the configuration.
++	if list_match "${address}" "${ADDRESS}" "${DEVICE}"; then
++		log DEBUG "Device '${device}' is port '${port}'"
++		return ${EXIT_OK}
++	fi
++
++	log DEBUG "Device '${device}' is not port '${port}'"
++	return ${EXIT_ERROR}
++}
++
+ hook_default_new() {
+ 	local ${HOOK_SETTINGS[*]}
+ 
+diff --git a/src/hooks/ports/dummy b/src/hooks/ports/dummy
+index 387c88b..3688831 100644
+--- a/src/hooks/ports/dummy
++++ b/src/hooks/ports/dummy
+@@ -90,40 +90,6 @@ hook_remove() {
+ 	dummy_remove "${port}"
+ }
+ 
+-hook_up() {
+-	local port="${1}"
+-	assert isset port
+-
+-	# Bring up the port.
+-	device_set_up ${port}
+-
+-	exit ${EXIT_OK}
+-}
+-
+-hook_down() {
+-	local port="${1}"
+-	assert isset port
+-
+-	# Tear down the port.
+-	device_set_down ${port}
+-
+-	exit ${EXIT_OK}
+-}
+-
+ hook_hotplug_rename() {
+-	local port=${1}
+-	assert isset port
+-
+-	local device=${2}
+-	assert isset device
+-
+-	port_settings_read "${port}"
+-
+-	if [ "${ADDRESS}" = "$(device_get_address ${device})" ]; then
+-		log DEBUG "Device '${device}' equals port '${port}'."
+-		exit ${EXIT_OK}
+-	fi
+-
+-	log DEBUG "Device '${device}' does not equal port '${port}'."
+-	exit ${EXIT_ERROR}
++	hook_hotplug_rename_by_address "$@"
+ }
+diff --git a/src/hooks/ports/ethernet b/src/hooks/ports/ethernet
+index 5f76e15..82664fa 100644
+--- a/src/hooks/ports/ethernet
++++ b/src/hooks/ports/ethernet
+@@ -173,25 +173,5 @@ hook_remove() {
+ }
+ 
+ hook_hotplug_rename() {
+-	local port=${1}
+-	assert isset port
+-
+-	local device=${2}
+-	assert isset device
+-
+-	# Read in the conifguration file.
+-	port_settings_read "${port}"
+-
+-	# Get the current MAC address of the device.
+-	local address=$(device_get_address ${device})
+-	assert isset address
+-
+-	# Check if the address matches with the configuration.
+-	if list_match "${address}" ${DEVICE} ${ADDRESS}; then
+-		log DEBUG "Device '${device}' equals port '${port}'."
+-		exit ${EXIT_OK}
+-	fi
+-
+-	log DEBUG "Device '${device}' does not equal port '${port}'."
+-	exit ${EXIT_ERROR}
++	hook_hotplug_rename_by_address "$@"
+ }
+diff --git a/src/hooks/ports/ip-tunnel b/src/hooks/ports/ip-tunnel
+index fa7193c..482511e 100644
+--- a/src/hooks/ports/ip-tunnel
++++ b/src/hooks/ports/ip-tunnel
+@@ -146,26 +146,5 @@ hook_remove() {
+ }
+ 
+ hook_hotplug_rename() {
+-	local port="${1}"
+-	assert isset port
+-
+-	local device="${2}"
+-	assert isset device
+-
+-	local ${HOOK_SETTINGS[*]}
+-	if ! port_settings_read "${port}"; then
+-		log ERROR "Could not read settings for port ${port}"
+-		return ${EXIT_ERROR}
+-	fi
+-
+-	# Get the current MAC address of the device.
+-	local address="$(device_get_address ${device})"
+-	assert isset address
+-
+-	# Return OK on match
+-	if [ "${ADDRESS}" = "${address}" ]; then
+-		return ${EXIT_OK}
+-	fi
+-
+-	return ${EXIT_ERROR}
++	hook_hotplug_rename_by_address "$@"
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0040-wireless-ap-Remove-support-for-WPA.patch b/network/patches/0040-wireless-ap-Remove-support-for-WPA.patch
new file mode 100644
index 000000000..7ec95a7b1
--- /dev/null
+++ b/network/patches/0040-wireless-ap-Remove-support-for-WPA.patch
@@ -0,0 +1,58 @@
+From 66fdbcaf15d3fb7ce4a1e0f7e6299818f4638c84 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 15:02:34 +0100
+Subject: [PATCH 040/304] wireless-ap: Remove support for WPA
+
+This is a deprecated protocol and not secure.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd | 8 +-------
+ src/hooks/ports/wireless-ap     | 2 +-
+ 2 files changed, 2 insertions(+), 8 deletions(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 79fb4db..d3eaa74 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -138,7 +138,7 @@ hostapd_config_write() {
+ 
+ 	# Check if key is set when encryption is used.
+ 	if isset encryption; then
+-		assert isoneof encryption WPA WPA2 WPA/WPA2
++		assert isoneof encryption WPA2
+ 		assert isset key
+ 	fi
+ 
+@@ -398,15 +398,9 @@ hostapd_config_write() {
+ 	if isset encryption; then
+ 		local encryption_mode=0
+ 		case "${encryption}" in
+-			WPA)
+-				encryption_mode=1
+-				;;
+ 			WPA2)
+ 				encryption_mode=2
+ 				;;
+-			WPA/WPA2)
+-				encryption_mode=3
+-				;;
+ 		esac
+ 
+ 		(
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index e393f5f..a964fac 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -65,7 +65,7 @@ hook_check_settings() {
+ 	assert isset SSID
+ 
+ 	if isset ENCRYPTION; then
+-		assert isoneof ENCRYPTION WPA WPA2 WPA/WPA2
++		assert isoneof ENCRYPTION WPA2
+ 
+ 		assert isset KEY
+ 		assert [ ${#KEY} -ge 8 ]
+-- 
+2.39.2
+
diff --git a/network/patches/0041-wireless-ap-Add-support-for-WPA3-and-rewrite-WPA2.patch b/network/patches/0041-wireless-ap-Add-support-for-WPA3-and-rewrite-WPA2.patch
new file mode 100644
index 000000000..55b78d002
--- /dev/null
+++ b/network/patches/0041-wireless-ap-Add-support-for-WPA3-and-rewrite-WPA2.patch
@@ -0,0 +1,280 @@
+From 0a4c5abab952ae0d864505f037f46cd0a27d6701 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 16:12:53 +0100
+Subject: [PATCH 041/304] wireless-ap: Add support for WPA3 and rewrite WPA2
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd   | 90 +++++++++++++++++++++----------
+ src/helpers/hostapd-config-helper |  5 +-
+ src/hooks/ports/wireless-ap       | 38 +++++++------
+ 3 files changed, 86 insertions(+), 47 deletions(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index d3eaa74..6c2fbd9 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -61,13 +61,14 @@ hostapd_config_write() {
+ 	local channel_bandwidth
+ 	local country_code="$(wireless_get_reg_domain)"
+ 	local dfs="on"
+-	local encryption
+ 	local environment="${WIRELESS_DEFAULT_ENVIRONMENT}"
+-	local key
+ 	local mfp="off"
+ 	local mode
++	local secret
+ 	local ssid
+ 	local wmm="1"
++	local wpa2_personal="off"
++	local wpa3_personal="off"
+ 
+ 	while [ $# -gt 0 ]; do
+ 		case "${1}" in
+@@ -89,9 +90,6 @@ hostapd_config_write() {
+ 			--environment=*)
+ 				environment="$(cli_get_val "${1}")"
+ 				;;
+-			--key=*)
+-				key=$(cli_get_val "${1}")
+-				;;
+ 			--mfp=*)
+ 				mfp="$(cli_get_val "${1}")"
+ 				;;
+@@ -103,6 +101,9 @@ hostapd_config_write() {
+ 					return ${EXIT_ERROR}
+ 				fi
+ 				;;
++			--secret=*)
++				secret="$(cli_get_val "${1}")"
++				;;
+ 			--ssid=*)
+ 				ssid=$(cli_get_val "${1}")
+ 				;;
+@@ -114,6 +115,12 @@ hostapd_config_write() {
+ 					wmm="0"
+ 				fi
+ 				;;
++			--wpa2-personal=*)
++				wpa2_personal="$(cli_get_bool "${1}")"
++				;;
++			--wpa3-personal=*)
++				wpa3_personal="$(cli_get_bool "${1}")"
++				;;
+ 			*)
+ 				warning_log "Ignoring unknown argument '${1}'."
+ 				;;			
+@@ -136,12 +143,6 @@ hostapd_config_write() {
+ 	assert isset mode
+ 	assert isset ssid
+ 
+-	# Check if key is set when encryption is used.
+-	if isset encryption; then
+-		assert isoneof encryption WPA2
+-		assert isset key
+-	fi
+-
+ 	# Check wireless environment
+ 	if ! wireless_environment_is_valid "${environment}"; then
+ 		error "Invalid wireless environment: ${environment}"
+@@ -166,6 +167,12 @@ hostapd_config_write() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
++	# Check if secret is set for personal authentication
++	if ! isset secret && (enabled WPA3_PERSONAL || enabled WPA2_PERSONAL); then
++		error "Secret not set but personal authentication enabled"
++		return ${EXIT_ERROR}
++	fi
++
+ 	# 802.11ac/n flags
+ 	local ieee80211ac
+ 	local ieee80211n
+@@ -394,27 +401,52 @@ hostapd_config_write() {
+ 		print
+ 	) >> ${file}
+ 
+-	# Encryption settings
+-	if isset encryption; then
+-		local encryption_mode=0
+-		case "${encryption}" in
+-			WPA2)
+-				encryption_mode=2
+-				;;
+-		esac
++	# Authentication Settings
++	local wpa
++	local wpa_key_mgmt
++	local wpa_passphrase
++	local sae_password
++	local wpa_strict_rekey
++
++	# WPA3 Personal
++	if enabled WPA3_PERSONAL; then
++		# Enable RSN
++		wpa="2"
++
++		# Add WPA key management
++		list_append wpa_key_mgmt "SAE"
++		sae_password="${secret}"
++	fi
++
++	# WPA2 Personal
++	if enabled WPA2_PERSONAL; then
++		# Enable RSN
++		wpa="2"
++
++		# Add WPA key management
++		list_append wpa_key_mgmt "WPA-PSK-SHA256" "WPA-PSK"
++		wpa_passphrase="${secret}"
+ 
+-		(
+-			print "# Encryption settings"
+-			print "wpa=${encryption_mode}"
+-			print "wpa_passphrase=${key}"
+-			print "wpa_key_mgmt=WPA-PSK-SHA256 WPA-PSK"
+-			print "wpa_pairwise=${pairwise_ciphers[*]}"
+-			print "rsn_pairwise=${pairwise_ciphers[*]}"
+-			print "group_cipher=${group_ciphers[*]}"
+-			print
+-		) >> ${file}
++		# Enable WPA strict rekey
++		wpa_strict_rekey="1"
+ 	fi
+ 
++	# Enable RSN ciphers when RSN is enabled
++	local rsn_pairwise
++	local group_cipher
++	if [ "${wpa}" = "2" ]; then
++		rsn_pairwise="${pairwise_ciphers[*]}"
++		group_cipher="${group_ciphers[*]}"
++	fi
++
++	local var
++	for var in wpa wpa_key_mgmt wpa_passphrase sae_password \
++			rsn_pairwise group_cipher wpa_strict_rekey; do
++		if [ -n "${!var}" ]; then
++			print "${var}=${!var}"
++		fi
++	done >> "${file}"
++
+ 	# Log configuration file
+ 	file_to_log DEBUG "${file}"
+ 
+diff --git a/src/helpers/hostapd-config-helper b/src/helpers/hostapd-config-helper
+index 7af723d..6d9f685 100644
+--- a/src/helpers/hostapd-config-helper
++++ b/src/helpers/hostapd-config-helper
+@@ -42,12 +42,13 @@ case "${action}" in
+ 			--channel="${CHANNEL}" \
+ 			--channel-bandwidth="${CHANNEL_BANDWIDTH}" \
+ 			--dfs="${DFS}" \
+-			--encryption="${ENCRYPTION}" \
+ 			--environment="${ENVIRONMENT}" \
+-			--key="${KEY}" \
++			--secret="${SECRET}" \
+ 			--mfp="${MFP}" \
+ 			--mode="${MODE}" \
+ 			--ssid="${SSID}" \
++			--wpa3-personal="${WPA3_PERSONAL}" \
++			--wpa2-personal="${WPA2_PERSONAL}" \
+ 		|| exit $?
+ 		;;
+ 
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index a964fac..7176ee5 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -29,15 +29,20 @@ HOOK_SETTINGS=(
+ 	"CHANNEL"
+ 	"CHANNEL_BANDWIDTH"
+ 	"DFS"
+-	"ENCRYPTION"
+ 	"ENVIRONMENT"
+-	"KEY"
+ 	"MFP"
+ 	"MODE"
+ 	"PHY"
++	"SECRET"
+ 	"SSID"
++	"WPA3_PERSONAL"
++	"WPA2_PERSONAL"
+ )
+ 
++# Disable WPA3+2 by default
++DEFAULT_WPA3_PERSONAL="off"
++DEFAULT_WPA2_PERSONAL="off"
++
+ # Broadcast SSID by default
+ DEFAULT_BROADCAST_SSID="on"
+ 
+@@ -64,14 +69,6 @@ hook_check_settings() {
+ 	assert ismac PHY
+ 	assert isset SSID
+ 
+-	if isset ENCRYPTION; then
+-		assert isoneof ENCRYPTION WPA2
+-
+-		assert isset KEY
+-		assert [ ${#KEY} -ge 8 ]
+-		assert [ ${#KEY} -le 63 ]
+-	fi
+-
+ 	assert wireless_environment_is_valid "${ENVIRONMENT}"
+ }
+ 
+@@ -99,9 +96,6 @@ hook_parse_cmdline() {
+ 					return ${EXIT_ERROR}
+ 				fi
+ 				;;
+-			--encryption=*)
+-				ENCRYPTION=$(cli_get_val "${1}")
+-				;;
+ 			--environment=*)
+ 				ENVIRONMENT="$(cli_get_val "${1}")"
+ 
+@@ -110,9 +104,6 @@ hook_parse_cmdline() {
+ 					return ${EXIT_ERROR}
+ 				fi
+ 				;;
+-			--key=*)
+-				KEY=$(cli_get_val "${1}")
+-				;;
+ 			--mac=*)
+ 				ADDRESS=$(cli_get_val "${1}")
+ 				;;
+@@ -140,9 +131,18 @@ hook_parse_cmdline() {
+ 			--phy=*)
+ 				PHY=$(cli_get_val "${1}")
+ 				;;
++			--secret=*)
++				SECRET="$(cli_get_val "${1}")"
++				;;
+ 			--ssid=*)
+ 				SSID=$(cli_get_val "${1}")
+ 				;;
++			--wpa2-personal=*)
++				WPA2_PERSONAL="$(cli_get_bool "${1}")"
++				;;
++			--wpa3-personal=*)
++				WPA3_PERSONAL="$(cli_get_bool "${1}")"
++				;;
+ 			*)
+ 				warning "Ignoring unknown argument '${1}'"
+ 				;;
+@@ -174,6 +174,12 @@ hook_parse_cmdline() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
++	# Check if SECRET is set when WPA* is enabled
++	if ! isset SECRET && (enabled WPA3_PERSONAL || enabled WPA2_PERSONAL); then
++		error "Secret is not set when PSK authentication is enabled"
++		return ${EXIT_ERROR}
++	fi
++
+ 	# Save address of phy do identify it again
+ 	PHY=$(phy_get ${PHY})
+ 	PHY=$(phy_get_address ${PHY})
+-- 
+2.39.2
+
diff --git a/network/patches/0042-hotplug-rename-Drop-unused-variable.patch b/network/patches/0042-hotplug-rename-Drop-unused-variable.patch
new file mode 100644
index 000000000..bbf85305c
--- /dev/null
+++ b/network/patches/0042-hotplug-rename-Drop-unused-variable.patch
@@ -0,0 +1,27 @@
+From 729cc3a2518ac4db00dd2ab390f7d253154f3333 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 16:19:24 +0100
+Subject: [PATCH 042/304] hotplug-rename: Drop unused variable
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/udev/network-hotplug-rename | 3 ---
+ 1 file changed, 3 deletions(-)
+
+diff --git a/src/udev/network-hotplug-rename b/src/udev/network-hotplug-rename
+index 903a07c..5f82f7c 100644
+--- a/src/udev/network-hotplug-rename
++++ b/src/udev/network-hotplug-rename
+@@ -28,9 +28,6 @@ LOG_DISABLE_STDOUT="true"
+ # Read network settings
+ network_settings_read
+ 
+-# Setup the locking
+-LOCKFILE="${LOCK_DIR}/.network-rename-lock"
+-
+ # Check if the INTERFACE variable is properly set.
+ assert isset INTERFACE
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0043-hostapd-Allow-WPA2-authentication-only-with-SHA256.patch b/network/patches/0043-hostapd-Allow-WPA2-authentication-only-with-SHA256.patch
new file mode 100644
index 000000000..166386eda
--- /dev/null
+++ b/network/patches/0043-hostapd-Allow-WPA2-authentication-only-with-SHA256.patch
@@ -0,0 +1,29 @@
+From 21ef3b742e6031cb40d0da94015aced31fc18be2 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 16:22:45 +0100
+Subject: [PATCH 043/304] hostapd: Allow WPA2 authentication only with SHA256
+
+This experimental change disables support for the legacy WPA2
+authentication that does not support SHA256.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 6c2fbd9..095beb8 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -424,7 +424,7 @@ hostapd_config_write() {
+ 		wpa="2"
+ 
+ 		# Add WPA key management
+-		list_append wpa_key_mgmt "WPA-PSK-SHA256" "WPA-PSK"
++		list_append wpa_key_mgmt "WPA-PSK-SHA256"
+ 		wpa_passphrase="${secret}"
+ 
+ 		# Enable WPA strict rekey
+-- 
+2.39.2
+
diff --git a/network/patches/0044-wireless-ap-Enable-802.11w-by-default.patch b/network/patches/0044-wireless-ap-Enable-802.11w-by-default.patch
new file mode 100644
index 000000000..fd371582c
--- /dev/null
+++ b/network/patches/0044-wireless-ap-Enable-802.11w-by-default.patch
@@ -0,0 +1,31 @@
+From 298a1ffe3f10ec14416c3aed19bb541553de160a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 16:23:55 +0100
+Subject: [PATCH 044/304] wireless-ap: Enable 802.11w by default
+
+This causes some problems on broken Intel systems, but I
+guess it is better to prefer security than compatibility in the
+default settings.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/wireless-ap | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 7176ee5..9676369 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -50,8 +50,7 @@ DEFAULT_BROADCAST_SSID="on"
+ DEFAULT_DFS="on"
+ 
+ # 802.11w - Management Frame Protection
+-# Disable by default because many clients cannot connect when enabled
+-DEFAULT_MFP="off"
++DEFAULT_MFP="on"
+ 
+ DEFAULT_ENVIRONMENT="${WIRELESS_DEFAULT_ENVIRONMENT}"
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0045-hooks-Use-cli_get_bool-convenience-function-where-ev.patch b/network/patches/0045-hooks-Use-cli_get_bool-convenience-function-where-ev.patch
new file mode 100644
index 000000000..1b19ff1d9
--- /dev/null
+++ b/network/patches/0045-hooks-Use-cli_get_bool-convenience-function-where-ev.patch
@@ -0,0 +1,100 @@
+From f6659cc56ecdef375fb868a3a44ada37b4cbfc3c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 16:30:05 +0100
+Subject: [PATCH 045/304] hooks: Use cli_get_bool convenience function where
+ ever possible
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/bonding     | 11 +----------
+ src/hooks/ports/ethernet    | 11 +----------
+ src/hooks/ports/wireless-ap | 22 ++--------------------
+ 3 files changed, 4 insertions(+), 40 deletions(-)
+
+diff --git a/src/hooks/ports/bonding b/src/hooks/ports/bonding
+index a0cf5c0..96cb854 100644
+--- a/src/hooks/ports/bonding
++++ b/src/hooks/ports/bonding
+@@ -59,16 +59,7 @@ hook_parse_cmdline() {
+ 				MODE=$(cli_get_val "${1}")
+ 				;;
+ 			--offloading=*)
+-				OFFLOADING="$(cli_get_val "${1}")"
+-
+-				if enabled OFFLOADING; then
+-					OFFLOADING="on"
+-				elif disabled OFFLOADING; then
+-					OFFLOADING="off"
+-				else
+-					error "Invalid value for offloading: ${OFFLOADING}"
+-					return ${EXIT_ERROR}
+-				fi
++				OFFLOADING="$(cli_get_bool "${1}")"
+ 				;;
+ 			+*)
+ 				local slave=$(cli_get_val "${1:1}")
+diff --git a/src/hooks/ports/ethernet b/src/hooks/ports/ethernet
+index 82664fa..80b5503 100644
+--- a/src/hooks/ports/ethernet
++++ b/src/hooks/ports/ethernet
+@@ -85,16 +85,7 @@ hook_parse_cmdline() {
+ 				;;
+ 
+ 			--offloading=*)
+-				OFFLOADING="$(cli_get_val "${1}")"
+-
+-				if enabled OFFLOADING; then
+-					OFFLOADING="on"
+-				elif disabled OFFLOADING; then
+-					OFFLOADING="off"
+-				else
+-					error "Invalid value for offloading: ${OFFLOADING}"
+-					return ${EXIT_ERROR}
+-				fi
++				OFFLOADING="$(cli_get_bool "${1}")"
+ 				;;
+ 
+ 			*)
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 9676369..2528585 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -84,16 +84,7 @@ hook_parse_cmdline() {
+ 				CHANNEL_BANDWIDTH="$(cli_get_val "${1}")"
+ 				;;
+ 			--dfs=*)
+-				DFS="$(cli_get_val "${1}")"
+-
+-				if enabled DFS; then
+-					DFS="on"
+-				elif disabled DFS; then
+-					DFS="off"
+-				else
+-					error "Invalid value for DFS: ${DFS}"
+-					return ${EXIT_ERROR}
+-				fi
++				DFS="$(cli_get_bool "${1}")"
+ 				;;
+ 			--environment=*)
+ 				ENVIRONMENT="$(cli_get_val "${1}")"
+@@ -107,16 +98,7 @@ hook_parse_cmdline() {
+ 				ADDRESS=$(cli_get_val "${1}")
+ 				;;
+ 			--mfp=*)
+-				MFP="$(cli_get_val "${1}")"
+-
+-				if enabled MFP; then
+-					MFP="on"
+-				elif disabled MFP; then
+-					MFP="off"
+-				else
+-					error "Invalid value for --mfp: ${MFP}"
+-					return ${EXIT_ERROR}
+-				fi
++				MFP="$(cli_get_bool "${1}")"
+ 				;;
+ 			--mode=*)
+ 				MODE=$(cli_get_val "${1}")
+-- 
+2.39.2
+
diff --git a/network/patches/0046-hook-Rename-HOOK_CONFIG_SETTINGS-to-HOOK_SETTINGS.patch b/network/patches/0046-hook-Rename-HOOK_CONFIG_SETTINGS-to-HOOK_SETTINGS.patch
new file mode 100644
index 000000000..29b8c1a55
--- /dev/null
+++ b/network/patches/0046-hook-Rename-HOOK_CONFIG_SETTINGS-to-HOOK_SETTINGS.patch
@@ -0,0 +1,196 @@
+From 636f1b96fc0b60c47cf5636f95b1ee6c856a701c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 16:54:04 +0100
+Subject: [PATCH 046/304] hook: Rename HOOK_CONFIG_SETTINGS to HOOK_SETTINGS
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.zone   |  8 ++++----
+ src/header-config              |  5 ++++-
+ src/hooks/configs/dhcp         | 13 +++++++++----
+ src/hooks/configs/ipv6-auto    | 15 +++++----------
+ src/hooks/configs/pppoe-server | 24 +++++++++---------------
+ src/hooks/configs/static       |  6 +++++-
+ 6 files changed, 36 insertions(+), 35 deletions(-)
+
+diff --git a/src/functions/functions.zone b/src/functions/functions.zone
+index e81371b..28fbecd 100644
+--- a/src/functions/functions.zone
++++ b/src/functions/functions.zone
+@@ -1312,8 +1312,8 @@ zone_config_settings_read() {
+ 	shift 2
+ 
+ 	local args
+-	if [ $# -eq 0 ] && [ -n "${HOOK_CONFIG_SETTINGS}" ]; then
+-		list_append args ${HOOK_CONFIG_SETTINGS}
++	if [ $# -eq 0 ] && [ -n "${HOOK_SETTINGS[*]}" ]; then
++		list_append args ${HOOK_SETTINGS[*]}
+ 	else
+ 		list_append args "$@"
+ 	fi
+@@ -1323,7 +1323,7 @@ zone_config_settings_read() {
+ }
+ 
+ zone_config_settings_write() {
+-	assert [ $# -eq 2 ]
++	assert [ $# -eq 3 ]
+ 
+ 	local zone="${1}"
+ 	local hook="${2}"
+@@ -1333,7 +1333,7 @@ zone_config_settings_write() {
+ 
+ 	local path="${NETWORK_ZONES_DIR}/${zone}/configs/${hook}.${id}"
+ 	settings_write "${path}" \
+-		--check="hook_check_config_settings" ${HOOK_CONFIG_SETTINGS[*]}
++		--check="hook_check_config_settings" HOOK ${HOOK_SETTINGS[*]}
+ }
+ 
+ zone_config_settings_destroy() {
+diff --git a/src/header-config b/src/header-config
+index 4458eaa..baeca5e 100644
+--- a/src/header-config
++++ b/src/header-config
+@@ -26,6 +26,9 @@ hook_new() {
+ 	local id=$(zone_config_get_new_id ${zone})
+ 	log DEBUG "ID for the config is: ${id}"
+ 
++	# Import all default variables
++	hook_set_defaults
++
+ 	# Parse command line arguments
+ 	if ! hook_parse_cmdline "${id}" "$@"; then
+ 		# Return an error if the parsing of the cmd line fails
+@@ -64,7 +67,7 @@ hook_edit() {
+ 		fi
+ 	fi
+ 
+-	local ${HOOK_CONFIG_SETTINGS}
++	local ${HOOK_SETTINGS}
+ 
+ 	# If reading the config fails we cannot go on
+ 	if ! zone_config_settings_read "${zone}" "${config}"; then
+diff --git a/src/hooks/configs/dhcp b/src/hooks/configs/dhcp
+index b643022..1ad0694 100644
+--- a/src/hooks/configs/dhcp
++++ b/src/hooks/configs/dhcp
+@@ -21,11 +21,13 @@
+ 
+ . /usr/lib/network/header-config
+ 
+-HOOK_CONFIG_SETTINGS="HOOK ENABLE_IPV6 ENABLE_IPV4"
++HOOK_SETTINGS=(
++	"ENABLE_IPV6"
++	"ENABLE_IPV4"
++)
+ 
+-# Default settings.
+-ENABLE_IPV6="on"
+-ENABLE_IPV4="on"
++DEFAULT_ENABLE_IPV6="on"
++DEFAULT_ENABLE_IPV4="on"
+ 
+ hook_check_config_settings() {
+ 	assert isset ENABLE_IPV6
+@@ -78,6 +80,9 @@ hook_new() {
+ 	local id=$(zone_config_get_new_id ${zone})
+ 	log DEBUG "ID for the config is: ${id}"
+ 
++	# Import defaults
++	hook_set_defaults
++
+ 	if ! hook_parse_cmdline "${id}" "$@"; then
+ 		# Return an error if the parsing of the cmd line fails
+ 		return ${EXIT_ERROR}
+diff --git a/src/hooks/configs/ipv6-auto b/src/hooks/configs/ipv6-auto
+index 8796723..6fd90a5 100644
+--- a/src/hooks/configs/ipv6-auto
++++ b/src/hooks/configs/ipv6-auto
+@@ -21,10 +21,12 @@
+ 
+ . /usr/lib/network/header-config
+ 
+-HOOK_CONFIG_SETTINGS="HOOK PRIVACY_EXTENSIONS"
++HOOK_SETTINGS=(
++	"PRIVACY_EXTENSIONS"
++)
+ 
+ # Privacy Extensions are disabled by default
+-PRIVACY_EXTENSIONS="off"
++DEFAULT_PRIVACY_EXTENSIONS="off"
+ 
+ hook_check_config_settings() {
+ 	assert isbool PRIVACY_EXTENSIONS
+@@ -35,17 +37,10 @@ hook_parse_cmdline() {
+ 	shift
+ 
+ 	local arg
+-
+ 	while read arg; do
+ 		case "${arg}" in
+ 			--privacy-extensions=*)
+-				local val="$(cli_get_val "${arg}")"
+-
+-				if enabled val; then
+-					PRIVACY_EXTENSIONS="on"
+-				else
+-					PRIVACY_EXTENSIONS="off"
+-				fi
++				PRIVACY_EXTENSIONS="$(cli_get_bool "${arg}")"
+ 				;;
+ 		esac
+ 	done <<< "$(args "$@")"
+diff --git a/src/hooks/configs/pppoe-server b/src/hooks/configs/pppoe-server
+index 6a2c014..4d79549 100644
+--- a/src/hooks/configs/pppoe-server
++++ b/src/hooks/configs/pppoe-server
+@@ -21,21 +21,15 @@
+ 
+ . /usr/lib/network/header-config
+ 
+-HOOK_CONFIG_SETTINGS="HOOK DNS_SERVERS MTU SERVICE_NAME SUBNET MAX_SESSIONS"
+-
+-# Maximum Transmission Unit.
+-MTU=1492
+-
+-# Service Name.
+-SERVICE_NAME=
+-
+-# A subnet. Addresses from this subnet will be given to the remote hosts.
+-# The net address will be the gateway address for the PPPoE server.
+-SUBNET=
+-
+-# Defines the max. number of sessions per MAC address.
+-# 0 = unlimited.
+-MAX_SESSIONS=0
++HOOK_SETTINGS=(
++	"DNS_SERVERS"
++	"MTU"
++	"SERVICE_NAME"
++	"SUBNET MAX_SESSIONS"
++)
++
++DEFAULT_MTU=1492
++DEFAULT_MAX_SESSIONS=0
+ 
+ hook_check_config_settings() {
+ 	assert isset MTU
+diff --git a/src/hooks/configs/static b/src/hooks/configs/static
+index 23ae2d8..6fddc32 100644
+--- a/src/hooks/configs/static
++++ b/src/hooks/configs/static
+@@ -21,7 +21,11 @@
+ 
+ . /usr/lib/network/header-config
+ 
+-HOOK_CONFIG_SETTINGS="HOOK ADDRESS PREFIX GATEWAY"
++HOOK_SETTINGS=(
++	"ADDRESS"
++	"PREFIX"
++	"GATEWAY"
++)
+ 
+ hook_check_config_settings() {
+ 	local protocol="$(ip_detect_protocol "${ADDRESS}")"
+-- 
+2.39.2
+
diff --git a/network/patches/0047-dhcp-Rename-enabled-from-configuration-parameters.patch b/network/patches/0047-dhcp-Rename-enabled-from-configuration-parameters.patch
new file mode 100644
index 000000000..a7bf304c6
--- /dev/null
+++ b/network/patches/0047-dhcp-Rename-enabled-from-configuration-parameters.patch
@@ -0,0 +1,99 @@
+From 8ece5c30bf5917d4cd6dfb460207d1e85eb5df73 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 16:57:31 +0100
+Subject: [PATCH 047/304] dhcp: Rename "enabled" from configuration parameters
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/configs/dhcp | 38 +++++++++++++++-----------------------
+ 1 file changed, 15 insertions(+), 23 deletions(-)
+
+diff --git a/src/hooks/configs/dhcp b/src/hooks/configs/dhcp
+index 1ad0694..1c75193 100644
+--- a/src/hooks/configs/dhcp
++++ b/src/hooks/configs/dhcp
+@@ -22,18 +22,16 @@
+ . /usr/lib/network/header-config
+ 
+ HOOK_SETTINGS=(
+-	"ENABLE_IPV6"
+-	"ENABLE_IPV4"
++	"IPV6"
++	"IPV4"
+ )
+ 
+-DEFAULT_ENABLE_IPV6="on"
+-DEFAULT_ENABLE_IPV4="on"
++DEFAULT_IPV6="on"
++DEFAULT_IPV4="on"
+ 
+ hook_check_config_settings() {
+-	assert isset ENABLE_IPV6
+-	assert isbool ENABLE_IPV6
+-	assert isset ENABLE_IPV4
+-	assert isbool ENABLE_IPV4
++	assert isbool IPV6
++	assert isbool IPV4
+ }
+ 
+ hook_parse_cmdline() {
+@@ -42,17 +40,11 @@ hook_parse_cmdline() {
+ 
+ 	while [ $# -gt 0 ]; do
+ 		case "${1}" in
+-			--enable-ipv6)
+-				ENABLE_IPV6="on"
++			--ipv6)
++				IPV6="$(cli_get_bool "${1}")"
+ 				;;
+-			--disable-ipv6)
+-				ENABLE_IPV6="off"
+-				;;
+-			--enable-ipv4)
+-				ENABLE_IPV4="on"
+-				;;
+-			--disable-ipv4)
+-				ENABLE_IPV4="off"
++			--ipv4)
++				IPV4="$(cli_get_bool "${1}")"
+ 				;;
+ 			*)
+ 				warning "Ignoring unknown option '${1}'"
+@@ -62,8 +54,8 @@ hook_parse_cmdline() {
+ 	done
+ 
+ 	# Check if the user disabled ipv6 and ipv4
+-	if ! enabled ENABLE_IPV6 && ! enabled ENABLE_IPV4; then
+-		log ERROR "You disabled IPv6 and IPv4. At least one must be enabled"
++	if ! enabled IPV6 && ! enabled IPV4; then
++		error "You disabled IPv6 and IPv4. At least one must be enabled"
+ 		return ${EXIT_ERROR}
+ 	fi
+ }
+@@ -106,12 +98,12 @@ hook_up() {
+ 	zone_config_settings_read "${zone}" "${config}"
+ 
+ 	# Start dhclient for IPv6 on this zone if enabled.
+-	if enabled ENABLE_IPV6; then
++	if enabled IPV6; then
+ 		dhclient_start ${zone} ipv6
+ 	fi
+ 
+ 	# Start dhclient for IPv4 on this zone if enabled.
+-	if enabled ENABLE_IPV4; then
++	if enabled IPV4; then
+ 		dhclient_start ${zone} ipv4
+ 	fi
+ 
+@@ -165,7 +157,7 @@ hook_status() {
+ 
+ 		cli_print_fmt1 3 "${proto}"
+ 
+-		if enabled ENABLE_${proto^^}; then
++		if enabled "${proto^^}"; then
+ 			cli_print_fmt1 4 "Status" "enabled"
+ 
+ 			local address="$(db_get "${zone}/${_proto}/local-ip-address")"
+-- 
+2.39.2
+
diff --git a/network/patches/0048-dhcp-Fix-syntax-error-in-last-commit.patch b/network/patches/0048-dhcp-Fix-syntax-error-in-last-commit.patch
new file mode 100644
index 000000000..1f085d2ed
--- /dev/null
+++ b/network/patches/0048-dhcp-Fix-syntax-error-in-last-commit.patch
@@ -0,0 +1,30 @@
+From e80eb68607dbdad381e3bb113521609c44fa8cd6 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 17:05:44 +0100
+Subject: [PATCH 048/304] dhcp: Fix syntax error in last commit
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/configs/dhcp | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/hooks/configs/dhcp b/src/hooks/configs/dhcp
+index 1c75193..ba5608a 100644
+--- a/src/hooks/configs/dhcp
++++ b/src/hooks/configs/dhcp
+@@ -40,10 +40,10 @@ hook_parse_cmdline() {
+ 
+ 	while [ $# -gt 0 ]; do
+ 		case "${1}" in
+-			--ipv6)
++			--ipv6=*)
+ 				IPV6="$(cli_get_bool "${1}")"
+ 				;;
+-			--ipv4)
++			--ipv4=*)
+ 				IPV4="$(cli_get_bool "${1}")"
+ 				;;
+ 			*)
+-- 
+2.39.2
+
diff --git a/network/patches/0049-hooks-Add-HOOK_UNIQUE-which-stops-us-from-creating-m.patch b/network/patches/0049-hooks-Add-HOOK_UNIQUE-which-stops-us-from-creating-m.patch
new file mode 100644
index 000000000..d790e6c64
--- /dev/null
+++ b/network/patches/0049-hooks-Add-HOOK_UNIQUE-which-stops-us-from-creating-m.patch
@@ -0,0 +1,158 @@
+From fdd9ac5fdd66b6cbdf014554281a9bb11ed0379d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 17:05:58 +0100
+Subject: [PATCH 049/304] hooks: Add HOOK_UNIQUE which stops us from creating
+ multiple instances
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/header-config              |  9 +++++++++
+ src/hooks/configs/dhcp         | 25 -------------------------
+ src/hooks/configs/ipv6-auto    | 22 ----------------------
+ src/hooks/configs/pppoe-server | 22 ----------------------
+ src/hooks/configs/static       |  3 +++
+ 5 files changed, 12 insertions(+), 69 deletions(-)
+
+diff --git a/src/header-config b/src/header-config
+index baeca5e..c6a775c 100644
+--- a/src/header-config
++++ b/src/header-config
+@@ -19,10 +19,19 @@
+ #                                                                             #
+ ###############################################################################
+ 
++# Allow only one instance of this hook
++HOOK_UNIQUE="true"
++
+ hook_new() {
+ 	local zone="${1}"
+ 	shift
+ 
++	# Check if we are allowed to have multiple configurations of $HOOK
++	if enabled HOOK_UNIQUE && zone_config_hook_is_configured "${zone}" "${HOOK}"; then
++		error "You can only have one configuration of type ${HOOK}"
++		return ${EXIT_CONF_ERROR}
++	fi
++
+ 	local id=$(zone_config_get_new_id ${zone})
+ 	log DEBUG "ID for the config is: ${id}"
+ 
+diff --git a/src/hooks/configs/dhcp b/src/hooks/configs/dhcp
+index ba5608a..127ce59 100644
+--- a/src/hooks/configs/dhcp
++++ b/src/hooks/configs/dhcp
+@@ -60,31 +60,6 @@ hook_parse_cmdline() {
+ 	fi
+ }
+ 
+-hook_new() {
+-	local zone="${1}"
+-	shift
+-
+-	if zone_config_hook_is_configured ${zone} "dhcp"; then
+-		log ERROR "You can configure the dhcp hook only once for a zone"
+-		return ${EXIT_ERROR}
+-	fi
+-
+-	local id=$(zone_config_get_new_id ${zone})
+-	log DEBUG "ID for the config is: ${id}"
+-
+-	# Import defaults
+-	hook_set_defaults
+-
+-	if ! hook_parse_cmdline "${id}" "$@"; then
+-		# Return an error if the parsing of the cmd line fails
+-		return ${EXIT_ERROR}
+-	fi
+-
+-	zone_config_settings_write "${zone}" "${HOOK}" "${id}"
+-
+-	exit ${EXIT_OK}
+-}
+-
+ hook_up() {
+ 	local zone=${1}
+ 	local config=${2}
+diff --git a/src/hooks/configs/ipv6-auto b/src/hooks/configs/ipv6-auto
+index 6fd90a5..ecfafcd 100644
+--- a/src/hooks/configs/ipv6-auto
++++ b/src/hooks/configs/ipv6-auto
+@@ -46,28 +46,6 @@ hook_parse_cmdline() {
+ 	done <<< "$(args "$@")"
+ }
+ 
+-hook_new() {
+-	local zone="${1}"
+-	shift
+-
+-	if zone_config_hook_is_configured ${zone} "ipv6-auto"; then
+-		log ERROR "You can configure the ipv6-auto hook only once for a zone"
+-		return ${EXIT_ERROR}
+-	fi
+-
+-	local id=$(zone_config_get_new_id ${zone})
+-	log DEBUG "ID for the config is: ${id}"
+-
+-	if ! hook_parse_cmdline "${id}" "$@"; then
+-		# Return an error if the parsing of the cmd line fails
+-		return ${EXIT_ERROR}
+-	fi
+-
+-	zone_config_settings_write "${zone}" "${HOOK}" "${id}"
+-
+-	exit ${EXIT_OK}
+-}
+-
+ hook_up() {
+ 	local zone=${1}
+ 	shift
+diff --git a/src/hooks/configs/pppoe-server b/src/hooks/configs/pppoe-server
+index 4d79549..e800bf4 100644
+--- a/src/hooks/configs/pppoe-server
++++ b/src/hooks/configs/pppoe-server
+@@ -93,28 +93,6 @@ hook_parse_cmdline() {
+ 	done
+ }
+ 
+-hook_new() {
+-	local zone=${1}
+-	shift
+-
+-	if zone_config_hook_is_configured ${zone} "pppoe-server"; then
+-		log ERROR "You can configure the pppoe-server hook only once for a zone"
+-		return ${EXIT_ERROR}
+-	fi
+-
+-	local id=$(zone_config_get_new_id ${zone})
+-	log DEBUG "ID for the config is: ${id}"
+-
+-	if ! hook_parse_cmdline "${id}" "$@"; then
+-		# Return an error if the parsing of the cmd line fails
+-		return ${EXIT_ERROR}
+-	fi
+-
+-	zone_config_settings_write "${zone}" "${HOOK}" "${id}"
+-
+-	exit ${EXIT_OK}
+-}
+-
+ hook_up() {
+ 	local zone=${1}
+ 	local config=${2}
+diff --git a/src/hooks/configs/static b/src/hooks/configs/static
+index 6fddc32..046183a 100644
+--- a/src/hooks/configs/static
++++ b/src/hooks/configs/static
+@@ -21,6 +21,9 @@
+ 
+ . /usr/lib/network/header-config
+ 
++# Allow multiple instances of this hook
++HOOK_UNIQUE="false"
++
+ HOOK_SETTINGS=(
+ 	"ADDRESS"
+ 	"PREFIX"
+-- 
+2.39.2
+
diff --git a/network/patches/0050-wireless-ap-Check-that-secret-has-the-correct-length.patch b/network/patches/0050-wireless-ap-Check-that-secret-has-the-correct-length.patch
new file mode 100644
index 000000000..00e9e26ba
--- /dev/null
+++ b/network/patches/0050-wireless-ap-Check-that-secret-has-the-correct-length.patch
@@ -0,0 +1,105 @@
+From d695b280e9972311ae8c4bc688c0898ade1281e6 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 18:14:07 +0100
+Subject: [PATCH 050/304] wireless-ap: Check that secret has the correct length
+ and no invalid characters
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.util     | 13 +++++++++++++
+ src/functions/functions.wireless | 23 +++++++++++------------
+ src/hooks/ports/wireless-ap      | 14 +++++++++++---
+ 3 files changed, 35 insertions(+), 15 deletions(-)
+
+diff --git a/src/functions/functions.util b/src/functions/functions.util
+index 4c1dbb4..7379a98 100644
+--- a/src/functions/functions.util
++++ b/src/functions/functions.util
+@@ -745,6 +745,19 @@ contains_spaces() {
+ 	return ${EXIT_FALSE}
+ }
+ 
++contains_non_ascii_characters() {
++	local value="$@"
++
++	# Strip away all ASCII characters
++	local non_ascii="${value//[[:ascii:]]/}"
++
++	if isset non_ascii; then
++		return ${EXIT_TRUE}
++	fi
++
++	return ${EXIT_FALSE}
++}
++
+ string_match() {
+ 	local match=${1}
+ 	local string=${2}
+diff --git a/src/functions/functions.wireless b/src/functions/functions.wireless
+index 12204c0..733a356 100644
+--- a/src/functions/functions.wireless
++++ b/src/functions/functions.wireless
+@@ -397,24 +397,23 @@ wireless_set_channel() {
+ }
+ 
+ wireless_pre_shared_key_is_valid() {
+-	local encryption_mode="${1}"
+-	local psk="${2}"
++	local psk="${1}"
+ 
+ 	# Length of the PSK
+ 	local l="${#psk}"
+ 
+-	case "${encryption_mode}" in
+-		# For WPA*, the key must be between 8 and 63 chars
+-		WPA2-PSK|WPA2-PSK-SHA256|WPA-PSK|WPA-PSK-SHA256)
+-			if [ ${l} -ge 8 ] && [ ${l} -le 63 ]; then
+-				return ${EXIT_TRUE}
+-			fi
++	# For WPA*, the key must be between 8 and 63 chars
++	if [ ${l} -lt 8 ] || [ ${l} -gt 63 ]; then
++		return ${EXIT_FALSE}
++	fi
+ 
+-			return ${EXIT_FALSE}
+-			;;
+-	esac
++	# Can only contain ASCII chararcters
++	if contains_non_ascii_characters "${psk}"; then
++		return ${EXIT_FALSE}
++	fi
+ 
+-	return ${EXIT_ERROR}
++	# Seems OK
++	return ${EXIT_TRUE}
+ }
+ 
+ wireless_client_is_connected() {
+diff --git a/src/hooks/ports/wireless-ap b/src/hooks/ports/wireless-ap
+index 2528585..26e14d6 100644
+--- a/src/hooks/ports/wireless-ap
++++ b/src/hooks/ports/wireless-ap
+@@ -156,9 +156,17 @@ hook_parse_cmdline() {
+ 	fi
+ 
+ 	# Check if SECRET is set when WPA* is enabled
+-	if ! isset SECRET && (enabled WPA3_PERSONAL || enabled WPA2_PERSONAL); then
+-		error "Secret is not set when PSK authentication is enabled"
+-		return ${EXIT_ERROR}
++	if enabled WPA3_PERSONAL || enabled WPA2_PERSONAL; then
++		if ! isset SECRET; then
++			error "Secret is not set when PSK authentication is enabled"
++			return ${EXIT_ERROR}
++		fi
++
++		# Check if SECRET is valid
++		if ! wireless_pre_shared_key_is_valid "${SECRET}"; then
++			error "The secret is in an invalid format"
++			return ${EXIT_ERROR}
++		fi
+ 	fi
+ 
+ 	# Save address of phy do identify it again
+-- 
+2.39.2
+
diff --git a/network/patches/0051-Drop-old-locking-functions.patch b/network/patches/0051-Drop-old-locking-functions.patch
new file mode 100644
index 000000000..4a0ee12cc
--- /dev/null
+++ b/network/patches/0051-Drop-old-locking-functions.patch
@@ -0,0 +1,235 @@
+From d4564f2b7efa20ea025b6918b012656927fd342a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 18:51:13 +0100
+Subject: [PATCH 051/304] Drop old locking functions
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.device   | 12 ++----
+ src/functions/functions.editor   | 51 +++++++++--------------
+ src/functions/functions.firewall |  3 +-
+ src/functions/functions.lock     | 70 --------------------------------
+ 4 files changed, 26 insertions(+), 110 deletions(-)
+
+diff --git a/src/functions/functions.device b/src/functions/functions.device
+index 48f2440..f52eee5 100644
+--- a/src/functions/functions.device
++++ b/src/functions/functions.device
+@@ -997,15 +997,11 @@ device_get_link_string() {
+ }
+ 
+ device_auto_configure_smp_affinity() {
+-	assert [ $# -eq 1 ]
+-
+-	local device=${1}
+-
+-	if lock_acquire "smp-affinity" 60; then
+-		device_set_smp_affinity ${device} auto
++	local device="${1}"
++	assert isset device
+ 
+-		lock_release "smp-affinity"
+-	fi
++	lock "smp-affinity" \
++		device_set_smp_affinity "${device}" "auto"
+ }
+ 
+ device_set_smp_affinity() {
+diff --git a/src/functions/functions.editor b/src/functions/functions.editor
+index 6edac62..8f0cc0b 100644
+--- a/src/functions/functions.editor
++++ b/src/functions/functions.editor
+@@ -19,17 +19,6 @@
+ #                                                                             #
+ ###############################################################################
+ 
+-editor_cleanup() {
+-	# Cleanup after a file was edited
+-	assert [ $# -eq 2 ]
+-
+-	local file=${1}
+-	local temp_file=${2}
+-
+-	lock_release "${file}.lock"
+-	rm -f ${temp_file}
+-}
+-
+ editor_find_best() {
+ 	# Open a file with the best available editor
+ 	assert [ $# -eq 1 ]
+@@ -62,31 +51,26 @@ editor_find_best() {
+ }
+ 
+ editor() {
+-	# This function open a file for editing and take care of all preperation and postprocessing
+-	assert [ $# -ge 1 ]
++	local file="${1}"
++	assert isset file
+ 
+-	local file=${1}
+ 	if [ ! -f ${file} ] || [ ! -w ${file} ]; then
+ 		error "${file} is not valid file or is not writeable"
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
+-	local check_func=${2}
++	lock "${file}.lock" __editor "$@"
++}
+ 
+-	# check if the file is locked
+-	if lock_exists "${file}.lock"; then
+-		error "Cannot edit ${file} because it is locked"
+-		return ${EXIT_ERROR}
+-	fi
++__editor() {
++	# This function open a file for editing and take care of all preperation and postprocessing
++	assert [ $# -ge 1 ]
+ 
+-	# lock the file
+-	if ! lock_acquire "${file}.lock"; then
+-		error "Cannot lock file ${file}"
+-		return ${EXIT_ERROR}
+-	fi
++	local file="${1}"
++	local check_func="${2}"
+ 
+ 	# create a temporary file
+-	local temp_file=$(mktemp)
++	local temp_file="$(mktemp)"
+ 
+ 	if ! [ -f "${temp_file}" ]; then
+ 		error "Cannot create temporary file"
+@@ -98,21 +82,26 @@ editor() {
+ 	# edit the file
+ 	if ! editor_find_best "${temp_file}"; then
+ 		error "Could not edit ${file}"
+-		# cleanup
+-		editor_cleanup "${file}" "${temp_file}"
++
++		# Delete temporary file
++		file_delete "${temp_file}"
++
++		return ${EXIT_ERROR}
+ 	fi
+ 
+ 	# run the check if we have one
+ 	if isset check_func && ! editor_check "${check_func}" "${temp_file}"; then
++		# Delete temporary file
++		file_delete "${temp_file}"
++
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
+ 	# copy the changes back
+ 	cp -f "${temp_file}" "${file}"
+ 
+-	# cleanup
+-	editor_cleanup "${file}" "${temp_file}"
+-
++	# Delete temporary file
++	file_delete "${temp_file}"
+ }
+ 
+ editor_check() {
+diff --git a/src/functions/functions.firewall b/src/functions/functions.firewall
+index 347916e..e22576b 100644
+--- a/src/functions/functions.firewall
++++ b/src/functions/functions.firewall
+@@ -269,7 +269,8 @@ firewall_panic() {
+ }
+ 
+ firewall_lock_acquire() {
+-	lock_acquire ${RUN_DIR}/.firewall_lock
++	# XXX DEPRECATED
++	#lock_acquire ${RUN_DIR}/.firewall_lock
+ 
+ 	# Make sure the lock is released after the firewall
+ 	# script has crashed or exited early.
+diff --git a/src/functions/functions.lock b/src/functions/functions.lock
+index 6295a22..fd15e5e 100644
+--- a/src/functions/functions.lock
++++ b/src/functions/functions.lock
+@@ -19,16 +19,6 @@
+ #                                                                             #
+ ###############################################################################
+ 
+-__lock_path() {
+-	local name=${1}
+-
+-	if [ "${name:0:1}" = "/" ]; then
+-		echo "${name}"
+-	else
+-		echo "${LOCK_DIR}/network-${name}"
+-	fi
+-}
+-
+ lock() {
+ 	local lock="${1}"
+ 	shift
+@@ -65,63 +55,3 @@ lock() {
+ 		exit ${ret}
+ 	) 9>${lock} || exit $?
+ }
+-
+-lock_exists() {
+-	local name=${1}
+-	assert isset name
+-
+-	local lockfile=$(__lock_path ${name})
+-
+-	if [ -e "${lockfile}" ]; then
+-		return ${EXIT_TRUE}
+-	else
+-		return ${EXIT_FALSE}
+-	fi
+-}
+-
+-lock_acquire() {
+-	local name=${1}
+-	assert isset name
+-
+-	# timeout value in seconds
+-	local timeout=${2}
+-
+-	if ! isset timeout; then
+-		timeout=0
+-	fi
+-
+-	local lockfile=$(__lock_path ${name})
+-
+-	timeout=$(( ${timeout} * 4 ))
+-
+-	log DEBUG "Acquiring lock '${name}'"
+-
+-	# Wait until lock is available
+-	while [ ${timeout} -gt 0 ] && [ -e "${lockfile}" ]; do
+-		timeout=$(( ${timeout} - 1 ))
+-		sleep 0.25
+-	done
+-
+-	# If another lock still exists, we return an error
+-	if [ -e "${lockfile}" ]; then
+-		error "Could not acquire lock '${name}'"
+-		return ${EXIT_ERROR}
+-	fi
+-
+-	# Write out pid to the lockfile and make sure that
+-	# nobody else can access it.
+-	echo "$$" > ${lockfile}
+-	chmod 600 ${lockfile}
+-}
+-
+-lock_release() {
+-	local name=${1}
+-	assert isset name
+-
+-	local lockfile=$(__lock_path ${name})
+-
+-	log DEBUG "Releasing lock '${name}'"
+-
+-	# Remove the lockfile (okay if it does not exist).
+-	rm -f ${lockfile}
+-}
+-- 
+2.39.2
+
diff --git a/network/patches/0052-ip-tunnel-Enable-support-for-6in4-tunnels.patch b/network/patches/0052-ip-tunnel-Enable-support-for-6in4-tunnels.patch
new file mode 100644
index 000000000..403991b84
--- /dev/null
+++ b/network/patches/0052-ip-tunnel-Enable-support-for-6in4-tunnels.patch
@@ -0,0 +1,26 @@
+From 55dcff454fa68dc2ff82f3dfbbafd75d3799b0ae Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 18:56:04 +0100
+Subject: [PATCH 052/304] ip-tunnel: Enable support for 6in4 tunnels
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/zones/ip-tunnel | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/hooks/zones/ip-tunnel b/src/hooks/zones/ip-tunnel
+index 634154e..c4a4fb4 100644
+--- a/src/hooks/zones/ip-tunnel
++++ b/src/hooks/zones/ip-tunnel
+@@ -21,7 +21,7 @@
+ 
+ . /usr/lib/network/header-zone
+ 
+-SUPPORTED_IP_TUNNEL_MODES="gre vti"
++SUPPORTED_IP_TUNNEL_MODES="gre sit vti"
+ 
+ HOOK_SETTINGS=(
+ 	"MARK"
+-- 
+2.39.2
+
diff --git a/network/patches/0053-lock-Cleanup-lock-files.patch b/network/patches/0053-lock-Cleanup-lock-files.patch
new file mode 100644
index 000000000..44176d537
--- /dev/null
+++ b/network/patches/0053-lock-Cleanup-lock-files.patch
@@ -0,0 +1,37 @@
+From 1ed79f5432d0bd4c4f0c8f8692b488c268e379a4 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 30 Mar 2019 19:03:24 +0100
+Subject: [PATCH 053/304] lock: Cleanup lock files
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.lock | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.lock b/src/functions/functions.lock
+index fd15e5e..c01fcf3 100644
+--- a/src/functions/functions.lock
++++ b/src/functions/functions.lock
+@@ -29,6 +29,7 @@ lock() {
+ 	fi
+ 
+ 	local timeout="60"
++	local ret=0
+ 
+ 	# Make partent directory
+ 	make_parent_directory "${lock}"
+@@ -53,5 +54,10 @@ lock() {
+ 		log DEBUG "Released lock ${lock}"
+ 
+ 		exit ${ret}
+-	) 9>${lock} || exit $?
++	) 9>${lock} || ret=$?
++
++	# Cleanup log file
++	file_delete "${lock}"
++
++	return ${ret}
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0054-hostapd-Require-MFP-for-SAE-when-it-is-enabled.patch b/network/patches/0054-hostapd-Require-MFP-for-SAE-when-it-is-enabled.patch
new file mode 100644
index 000000000..2b20f4cbd
--- /dev/null
+++ b/network/patches/0054-hostapd-Require-MFP-for-SAE-when-it-is-enabled.patch
@@ -0,0 +1,45 @@
+From 1ef692c599a77fcb0683e3196b8f4b56f52644da Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 13:10:30 +0200
+Subject: [PATCH 054/304] hostapd: Require MFP for SAE when it is enabled
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.hostapd | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.hostapd b/src/functions/functions.hostapd
+index 095beb8..410e6e5 100644
+--- a/src/functions/functions.hostapd
++++ b/src/functions/functions.hostapd
+@@ -407,6 +407,7 @@ hostapd_config_write() {
+ 	local wpa_passphrase
+ 	local sae_password
+ 	local wpa_strict_rekey
++	local sae_require_mfp
+ 
+ 	# WPA3 Personal
+ 	if enabled WPA3_PERSONAL; then
+@@ -416,6 +417,10 @@ hostapd_config_write() {
+ 		# Add WPA key management
+ 		list_append wpa_key_mgmt "SAE"
+ 		sae_password="${secret}"
++
++		if enabled MFP; then
++			sae_require_mfp="1"
++		fi
+ 	fi
+ 
+ 	# WPA2 Personal
+@@ -441,7 +446,7 @@ hostapd_config_write() {
+ 
+ 	local var
+ 	for var in wpa wpa_key_mgmt wpa_passphrase sae_password \
+-			rsn_pairwise group_cipher wpa_strict_rekey; do
++			rsn_pairwise group_cipher wpa_strict_rekeyi sae_require_mfp; do
+ 		if [ -n "${!var}" ]; then
+ 			print "${var}=${!var}"
+ 		fi
+-- 
+2.39.2
+
diff --git a/network/patches/0055-bird-Write-IPv6-router-advertisement-configuration.patch b/network/patches/0055-bird-Write-IPv6-router-advertisement-configuration.patch
new file mode 100644
index 000000000..32dc97ba2
--- /dev/null
+++ b/network/patches/0055-bird-Write-IPv6-router-advertisement-configuration.patch
@@ -0,0 +1,117 @@
+From 7a3747a1b0d2e219600979aa4286e8ffd96d5b59 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 14:14:55 +0200
+Subject: [PATCH 055/304] bird: Write IPv6 router advertisement configuration
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.bird | 89 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 89 insertions(+)
+
+diff --git a/src/functions/functions.bird b/src/functions/functions.bird
+index c6fea32..950bb78 100644
+--- a/src/functions/functions.bird
++++ b/src/functions/functions.bird
+@@ -84,6 +84,9 @@ bird_generate_config() {
+ 		print "}"
+ 		print
+ 	done >> ${BIRD_CONF}
++
++	# Write IPv6 Router Advertisement configuration
++	__bird_ipv6_radv >> ${BIRD_CONF}
+ }
+ 
+ __bird_static_routes() {
+@@ -122,3 +125,89 @@ __bird_static_routes() {
+ 		esac
+ 	done < ${NETWORK_CONFIG_ROUTES}
+ }
++
++__bird_ipv6_radv() {
++	print "protocol radv {"
++
++	local zone
++	for zone in $(zones_get_local); do
++		log DEBUG "Writing bird radv configuration for ${zone}"
++
++		# Skip if there is no prefix or prefix is link-local.
++		local addr="$(db_get "${zone}/ipv6/local-ip-address")"
++		if [ -z "${addr}" ] || [ "${addr:0:5}" = "fe80:" ]; then
++			continue
++		fi
++
++		# Check if the subnet is configured by the DHCP server.
++		local dhcp="false"
++		local prefix="$(ipv6_get_network "${addr}")"
++		if isset prefix && dhcpd_subnet_match ipv6 "${prefix}"; then
++			dhcp="true"
++		fi
++
++		print "	interface \"${zone}\" {"
++			# Failover to other routers within 10s
++			print "		max ra interval 10;"
++
++			# Tell clients we are running DHCP
++			if enabled dhcp; then
++				print "		managed yes;"
++				print "		other config yes;"
++			fi
++
++			if device_exists "${zone}"; then
++				# Announce link MTU
++				local mtu="$(device_get_mtu "${zone}")"
++				print "		link mtu ${mtu};"
++			fi
++
++			print # empty line
++
++			# Announce all prefixes
++			print "		prefix ::/0 {"
++
++			if enabled dhcp; then
++				print "			autonomous off;"
++			fi
++
++			print "		};"
++		print "	};\n"
++	done
++
++	# Advertise any DNS servers
++	if enabled DNS_ADVERTISE_SERVERS; then
++		# Get a list of all IPv6 name servers
++		local servers=()
++		local server
++		for server in $(dns_server_list_sorted); do
++			# Skip any non-IPv6 servers
++			ipv6_is_valid "${server}" || continue
++
++			servers+=( "${server}" )
++		done
++
++		if isset servers; then
++			print "	rdnss {"
++
++			local server
++			for server in ${servers}; do
++				print "		ns ${server};"
++			done
++
++			print "	};"
++		fi
++	fi
++
++	# DNS Search Domain
++	print "	dnssl {"
++
++	local domain
++	for domain in $(dns_get_search_domains); do
++		print "		domain \"${domain}\";"
++	done
++
++	print "	};"
++
++	print "}\n"
++}
+-- 
+2.39.2
+
diff --git a/network/patches/0056-Drop-code-for-radvd.patch b/network/patches/0056-Drop-code-for-radvd.patch
new file mode 100644
index 000000000..52241fd28
--- /dev/null
+++ b/network/patches/0056-Drop-code-for-radvd.patch
@@ -0,0 +1,314 @@
+From 1cb20d39b29a1bd73cef2926cc4aae651f653ca7 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 14:20:40 +0200
+Subject: [PATCH 056/304] Drop code for radvd
+
+This is now being replaced by bird.
+
+Bird is running anyways and can do this job just as well.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                     |   2 -
+ src/functions/functions.bird    |  11 +++
+ src/functions/functions.dns     |   8 +-
+ src/functions/functions.radvd   | 160 --------------------------------
+ src/functions/functions.routing |   4 +-
+ src/network-radvd-config        |  35 -------
+ 6 files changed, 17 insertions(+), 203 deletions(-)
+ delete mode 100644 src/functions/functions.radvd
+ delete mode 100644 src/network-radvd-config
+
+diff --git a/Makefile.am b/Makefile.am
+index 1b5e7e9..ce587b7 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -173,7 +173,6 @@ dist_network_DATA = \
+ 	src/functions/functions.ports \
+ 	src/functions/functions.ppp \
+ 	src/functions/functions.pppoe-server \
+-	src/functions/functions.radvd \
+ 	src/functions/functions.route \
+ 	src/functions/functions.routing \
+ 	src/functions/functions.serial \
+@@ -193,7 +192,6 @@ dist_network_DATA = \
+ 	src/functions/functions.wireless-networks \
+ 	src/functions/functions.wpa_supplicant \
+ 	src/functions/functions.zone \
+-	src/network-radvd-config \
+ 	src/header-config \
+ 	src/header-port \
+ 	src/header-zone
+diff --git a/src/functions/functions.bird b/src/functions/functions.bird
+index 950bb78..55d43b5 100644
+--- a/src/functions/functions.bird
++++ b/src/functions/functions.bird
+@@ -33,6 +33,17 @@ bird_reload() {
+ 	service_reload "bird.service"
+ }
+ 
++# Update configuration any apply it in one go
++bird_update() {
++	if ! bird_generate_config; then
++		log ERROR "Could not write Bird configuration"
++		return ${EXIT_ERROR}
++	fi
++
++	# Reload bird
++	bird_reload
++}
++
+ bird_generate_config() {
+ 	log DEBUG "Write BIRD configuration file"
+ 
+diff --git a/src/functions/functions.dns b/src/functions/functions.dns
+index 890f1ac..0e058be 100644
+--- a/src/functions/functions.dns
++++ b/src/functions/functions.dns
+@@ -31,8 +31,8 @@ NETWORK_SETTINGS_FILE_PARAMS="${NETWORK_SETTINGS_FILE_PARAMS} DNS_RANDOMIZE"
+ DNS_SEARCH_DOMAINS=""
+ NETWORK_SETTINGS_FILE_PARAMS="${NETWORK_SETTINGS_FILE_PARAMS} DNS_SEARCH_DOMAINS"
+ 
+-# Set this option to true if the DNS servers should be advertised by
+-# radvd.
++# Set this option to true if the DNS servers should be advertised in
++# IPv6 router advertisements
+ DNS_ADVERTISE_SERVERS="true"
+ 
+ DNS_SERVER_CONFIG_FILE="${NETWORK_CONFIG_DIR}/dns-servers"
+@@ -234,8 +234,8 @@ dns_server_update() {
+ 	# Regenerate /etc/resolv.conf
+ 	dns_generate_resolvconf
+ 
+-	# Restart radvd which propagates IPv6 DNS servers
+-	radvd_update
++	# Update bird about IPv6 DNS server changes
++	bird_update
+ }
+ 
+ dns_generate_resolvconf() {
+diff --git a/src/functions/functions.radvd b/src/functions/functions.radvd
+deleted file mode 100644
+index 1c8b8d0..0000000
+--- a/src/functions/functions.radvd
++++ /dev/null
+@@ -1,160 +0,0 @@
+-#!/bin/bash
+-###############################################################################
+-#                                                                             #
+-# IPFire.org - A linux based firewall                                         #
+-# Copyright (C) 2010  Michael Tremer & Christian Schmidt                      #
+-#                                                                             #
+-# This program is free software: you can redistribute it and/or modify        #
+-# it under the terms of the GNU General Public License as published by        #
+-# the Free Software Foundation, either version 3 of the License, or           #
+-# (at your option) any later version.                                         #
+-#                                                                             #
+-# This program is distributed in the hope that it will be useful,             #
+-# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+-# GNU General Public License for more details.                                #
+-#                                                                             #
+-# You should have received a copy of the GNU General Public License           #
+-# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+-#                                                                             #
+-###############################################################################
+-
+-RADVD_CONFIGFILE="/etc/radvd.conf"
+-
+-radvd_update() {
+-	# (Re-)write the configuration file
+-	if radvd_write_config; then
+-		# Reload the radvd service if it is already running
+-		if service_is_active radvd; then
+-			service_reload radvd
+-			return ${EXIT_OK}
+-		fi
+-
+-		# Start the radvd service
+-		service_start radvd
+-	fi
+-}
+-
+-radvd_clear_config() {
+-	log DEBUG "Clearing radv daemon configuration file"
+-
+-	config_header "radv daemon configuration file" > ${RADVD_CONFIGFILE}
+-
+-	return ${EXIT_OK}
+-}
+-
+-radvd_write_config() {
+-	radvd_clear_config
+-
+-	# Write the configuration for all zones.
+-	local zone
+-
+-	# The return value determine if radvd is started or not
+-	local return_value=${EXIT_FALSE}
+-
+-	for zone in $(zones_get_local); do
+-		if __radvd_config_interface ${zone}; then
+-			# We return TRUE when __radvd_config_interface returns True
+-			return_value=${EXIT_TRUE}
+-		fi
+-	done >> ${RADVD_CONFIGFILE}
+-
+-	return ${return_value}
+-}
+-
+-# This function return ${EXIT_FALSE} if no radvd config was written and ${EXIT_TRUE} in all other cases
+-__radvd_config_interface() {
+-	local zone=${1}
+-	assert isset zone
+-
+-	log DEBUG "Writing radvd configuration for ${zone}."
+-
+-	# If the interface does not provide any routing information,
+-	# we can skip this whole stuff.
+-	if ! db_exists "${zone}/ipv6"; then
+-		return ${EXIT_FALSE}
+-	fi
+-
+-	# Skip if zone is not active.
+-	local active="$(db_get "${zone}/ipv6/active")"
+-	[ "${active}" = "0" ] && return ${EXIT_FALSE}
+-
+-	# Skip if there is no prefix or prefix is link-local.
+-	local addr="$(db_get "${zone}/ipv6/local-ip-address")"
+-	if [ -z "${addr}" ] || [ "${addr:0:5}" = "fe80:" ]; then
+-		return ${EXIT_FALSE}
+-	fi
+-
+-	# Check if the subnet is configured by the DHCP server.
+-	local dhcpd="false"
+-	local prefix="$(ipv6_get_network "${addr}")"
+-	if isset prefix && dhcpd_subnet_match ipv6 "${prefix}"; then
+-		dhcpd="true"
+-	fi
+-
+-	print "interface ${zone} {"
+-	print "	AdvSendAdvert on;"
+-	print "	MinRtrAdvInterval 3;"
+-	print "	MaxRtrAdvInterval 10;"
+-	print "	IgnoreIfMissing on;"
+-
+-	if enabled dhcpd; then
+-		print "	AdvManagedFlag on;"
+-		print "	AdvOtherConfigFlag on;"
+-	fi
+-
+-	print
+-	print "	prefix ::/64 {"
+-	print "		AdvOnLink on;"
+-
+-	if enabled dhcpd; then
+-		print "		AdvRouterAddr off;"
+-		print "		AdvAutonomous off;"
+-	else
+-		print "		AdvRouterAddr on;"
+-		print "		AdvAutonomous on;"
+-	fi
+-
+-	print "	};"
+-	print
+-
+-	# Add the DNS configuration.
+-	__radvd_config_dns ${zone}
+-
+-	print "};"
+-	print
+-
+-	return ${EXIT_TRUE}
+-}
+-
+-__radvd_config_dns() {
+-	local zone=${1}
+-
+-	# Do nothing, when this option is not enabled.
+-	enabled DNS_ADVERTISE_SERVERS || return ${EXIT_OK}
+-
+-	# XXX it is kind of difficult to announce our local
+-	# resolver.
+-
+-	local server servers
+-	for server in $(dns_server_list_sorted); do
+-		# Filter out non IPv6 addresses.
+-		ipv6_is_valid ${server} || continue
+-
+-		servers="${servers} ${server}"
+-	done
+-
+-	# Remove whitespaces.
+-	servers=$(echo ${servers})
+-
+-	# If there are no servers to announce, we stop right here.
+-	if ! isset servers; then
+-		log DEBUG "No servers to announce."
+-		return ${EXIT_OK}
+-	fi
+-
+-	print "	RDNSS ${servers} {"
+-	print "		# Use the defaults here."
+-	print "	};"
+-	print
+-}
+diff --git a/src/functions/functions.routing b/src/functions/functions.routing
+index c7aac09..351cc53 100644
+--- a/src/functions/functions.routing
++++ b/src/functions/functions.routing
+@@ -80,8 +80,8 @@ routing_default_update() {
+ 		# Remove too much spaces.
+ 		routes=$(echo ${routes})
+ 
+-		# Reload radvd configuration
+-		[[ "${proto}" = "ipv6" ]] && radvd_update
++		# Reload bird configuration
++		[[ "${proto}" = "ipv6" ]] && bird_update
+ 
+ 		# Remove all default routes.
+ 		if [ -z "${routes}" ]; then
+diff --git a/src/network-radvd-config b/src/network-radvd-config
+deleted file mode 100644
+index e9809e1..0000000
+--- a/src/network-radvd-config
++++ /dev/null
+@@ -1,35 +0,0 @@
+-#!/bin/bash
+-###############################################################################
+-#                                                                             #
+-# IPFire.org - A linux based firewall                                         #
+-# Copyright (C) 2011  Michael Tremer & Christian Schmidt                      #
+-#                                                                             #
+-# This program is free software: you can redistribute it and/or modify        #
+-# it under the terms of the GNU General Public License as published by        #
+-# the Free Software Foundation, either version 3 of the License, or           #
+-# (at your option) any later version.                                         #
+-#                                                                             #
+-# This program is distributed in the hope that it will be useful,             #
+-# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+-# GNU General Public License for more details.                                #
+-#                                                                             #
+-# You should have received a copy of the GNU General Public License           #
+-# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+-#                                                                             #
+-###############################################################################
+-
+-. /lib/network/functions
+-
+-case "${1}" in
+-	start)
+-		# Write the radvd configuration file.
+-		radvd_write_config
+-		;;
+-	stop)
+-		# Clear all contents in the configuration file.
+-		radvd_clear_config
+-		;;
+-esac
+-
+-exit ${EXIT_OK}
+-- 
+2.39.2
+
diff --git a/network/patches/0057-.gitignore-Ignore-vim-s-swp-files.patch b/network/patches/0057-.gitignore-Ignore-vim-s-swp-files.patch
new file mode 100644
index 000000000..4b6f140c7
--- /dev/null
+++ b/network/patches/0057-.gitignore-Ignore-vim-s-swp-files.patch
@@ -0,0 +1,25 @@
+From f116762cf279b39749bea053eca0e873c60e23f1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 14:21:18 +0200
+Subject: [PATCH 057/304] .gitignore: Ignore vim's swp files
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ .gitignore | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/.gitignore b/.gitignore
+index a6df183..36c85a1 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -20,6 +20,7 @@
+ *.lo
+ *.o
+ *.stamp
++*.swp
+ *.trs
+ *~
+ .deps
+-- 
+2.39.2
+
diff --git a/network/patches/0058-bird-Make-sure-the-daemon-is-always-running.patch b/network/patches/0058-bird-Make-sure-the-daemon-is-always-running.patch
new file mode 100644
index 000000000..f8c7758fc
--- /dev/null
+++ b/network/patches/0058-bird-Make-sure-the-daemon-is-always-running.patch
@@ -0,0 +1,73 @@
+From 39beacd0549be57fde9eb350c2c9292094537629 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 14:28:44 +0200
+Subject: [PATCH 058/304] bird: Make sure the daemon is always running
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.bird  | 14 ++++++++++++++
+ src/functions/functions.route |  7 ++-----
+ src/network                   |  4 ++--
+ 3 files changed, 18 insertions(+), 7 deletions(-)
+
+diff --git a/src/functions/functions.bird b/src/functions/functions.bird
+index 55d43b5..1bbac8c 100644
+--- a/src/functions/functions.bird
++++ b/src/functions/functions.bird
+@@ -33,6 +33,20 @@ bird_reload() {
+ 	service_reload "bird.service"
+ }
+ 
++bird_enable() {
++	# Generate configuration file
++	if ! bird_generate_config; then
++		log ERROR "Could not write Bird configuration"
++		return ${EXIT_ERROR}
++	fi
++
++	# Enable the service to be automatically started next time
++	service_enable "bird.service"
++
++	# Start it now
++	bird_start
++}
++
+ # Update configuration any apply it in one go
+ bird_update() {
+ 	if ! bird_generate_config; then
+diff --git a/src/functions/functions.route b/src/functions/functions.route
+index e6ea244..b833822 100644
+--- a/src/functions/functions.route
++++ b/src/functions/functions.route
+@@ -393,11 +393,8 @@ route_parse_line() {
+ }
+ 
+ route_apply() {
+-	# Re-generate BIRD configuration
+-	bird_generate_config
+-
+-	# Reload the daemon
+-	bird_reload
++	# Update bird
++	bird_update
+ }
+ 
+ route_entry_add() {
+diff --git a/src/network b/src/network
+index 300ba94..be06d8a 100644
+--- a/src/network
++++ b/src/network
+@@ -1381,8 +1381,8 @@ case "${action}" in
+ 		# Update resolv.conf(5) when initializing the network
+ 		dns_generate_resolvconf
+ 
+-		# Update bird configuration
+-		bird_generate_config
++		# Make sure bird is running
++		bird_enable
+ 
+ 		# Also execute all triggers
+ 		triggers_execute_all "init"
+-- 
+2.39.2
+
diff --git a/network/patches/0059-configure-Require-asciidoc.patch b/network/patches/0059-configure-Require-asciidoc.patch
new file mode 100644
index 000000000..93e95bae8
--- /dev/null
+++ b/network/patches/0059-configure-Require-asciidoc.patch
@@ -0,0 +1,28 @@
+From 57496df2abdaa620e8ce68abfa5ad65b211a3484 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 26 Sep 2018 22:14:27 +0200
+Subject: [PATCH 059/304] configure: Require asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ configure.ac | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/configure.ac b/configure.ac
+index 08e9089..117850f 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -143,6 +143,10 @@ PKG_CHECK_MODULES([LIBNL], [libnl-3.0 libnl-genl-3.0])
+ 
+ # ------------------------------------------------------------------------------
+ 
++AC_CHECK_PROGS(ASCIIDOC, [asciidoc])
++
++# ------------------------------------------------------------------------------
++
+ AC_CONFIG_HEADERS(config.h)
+ AC_CONFIG_FILES([
+ 	Makefile
+-- 
+2.39.2
+
diff --git a/network/patches/0060-man-Add-test-page-for-asciidoc.patch b/network/patches/0060-man-Add-test-page-for-asciidoc.patch
new file mode 100644
index 000000000..c50e1903e
--- /dev/null
+++ b/network/patches/0060-man-Add-test-page-for-asciidoc.patch
@@ -0,0 +1,94 @@
+From 8f591cfc10d1876523d608d9643f0a82517c2add Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 26 Sep 2018 22:42:36 +0200
+Subject: [PATCH 060/304] man: Add test page for asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am    | 14 +++++++++++---
+ man/.gitignore |  1 +
+ man/test.txt   | 11 +++++++++++
+ 3 files changed, 23 insertions(+), 3 deletions(-)
+ create mode 100644 man/test.txt
+
+diff --git a/Makefile.am b/Makefile.am
+index ce587b7..d01e223 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -89,6 +89,10 @@ INSTALL_EXEC_HOOKS += \
+ 
+ # ------------------------------------------------------------------------------
+ 
++AM_V_ASCIIDOC   = $(AM_V_ASCIIDOC_$(V))
++AM_V_ASCIIDOC_  = $(AM_V_ASCIIDOC_$(AM_DEFAULT_VERBOSITY))
++AM_V_ASCIIDOC_0 = @echo "  ASCIIDOC" $@;
++
+ AM_V_DOWNLOAD   = $(AM_V_DOWNLOAD_$(V))
+ AM_V_DOWNLOAD_  = $(AM_V_DOWNLOAD_$(AM_DEFAULT_VERBOSITY))
+ AM_V_DOWNLOAD_0 = @echo "  LOAD    " $@;
+@@ -449,6 +453,7 @@ INSTALL_DIRS += \
+ # ------------------------------------------------------------------------------
+ 
+ MANPAGES = \
++	man/test.8 \
+ 	man/firewall-settings.8 \
+ 	man/network.8 \
+ 	man/network-color.8 \
+@@ -472,8 +477,8 @@ MANPAGES = \
+ 	man/network-zone-pppoe.8 \
+ 	man/network-zone-wireless.8
+ 
+-MANPAGES_XML  = $(patsubst %.8,%.xml,$(MANPAGES))
+-MANPAGES_HTML = $(patsubst %.xml,%.html,$(MANPAGES_XML))
++MANPAGES_TXT  = $(patsubst %.8,%.txt,$(MANPAGES))
++MANPAGES_HTML = $(patsubst %.txt,%.html,$(MANPAGES))
+ 
+ .PHONY: man
+ man: $(MANPAGES) $(MANPAGES_HTML)
+@@ -489,7 +494,7 @@ CLEANFILES += \
+ 	$(MANPAGES_HTML)
+ 
+ EXTRA_DIST += \
+-	$(MANPAGES_XML) \
++	$(MANPAGES_TXT) \
+ 	man/custom-html.xsl
+ 
+ XSLTPROC_FLAGS = \
+@@ -507,6 +512,9 @@ XSLTPROC_COMMAND_MAN = \
+ XSLTPROC_COMMAND_HTML = \
+ 	$(AM_V_XSLT)$(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(srcdir)/man/custom-html.xsl $<
+ 
++man/%.xml: man/%.txt
++	$(AM_V_ASCIIDOC)$(ASCIIDOC) -d manpage -b docbook -o $@ $<
++
+ man/%.8: man/%.xml
+ 	$(XSLTPROC_COMMAND_MAN)
+ 
+diff --git a/man/.gitignore b/man/.gitignore
+index 237049a..f891826 100644
+--- a/man/.gitignore
++++ b/man/.gitignore
+@@ -1,2 +1,3 @@
+ /*.[13578]
+ /*.html
++/*.xml
+diff --git a/man/test.txt b/man/test.txt
+new file mode 100644
+index 0000000..4c9d35d
+--- /dev/null
++++ b/man/test.txt
+@@ -0,0 +1,11 @@
++test(8)
++=======
++
++NAME
++----
++test - Hello World!
++
++SYNOPSIS
++--------
++[verse]
++'hello world' [<options>] <file>
+-- 
+2.39.2
+
diff --git a/network/patches/0061-man-Use-asciidoc-to-generate-HTML-pages-directly.patch b/network/patches/0061-man-Use-asciidoc-to-generate-HTML-pages-directly.patch
new file mode 100644
index 000000000..fd2e569ec
--- /dev/null
+++ b/network/patches/0061-man-Use-asciidoc-to-generate-HTML-pages-directly.patch
@@ -0,0 +1,86 @@
+From a7d2fef75b529c8cc10c4d22fca3114e30542394 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 26 Sep 2018 23:04:35 +0200
+Subject: [PATCH 061/304] man: Use asciidoc to generate HTML pages directly
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am         | 10 +++-------
+ man/custom-html.xsl | 31 -------------------------------
+ 2 files changed, 3 insertions(+), 38 deletions(-)
+ delete mode 100644 man/custom-html.xsl
+
+diff --git a/Makefile.am b/Makefile.am
+index d01e223..c4f8b45 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -494,8 +494,7 @@ CLEANFILES += \
+ 	$(MANPAGES_HTML)
+ 
+ EXTRA_DIST += \
+-	$(MANPAGES_TXT) \
+-	man/custom-html.xsl
++	$(MANPAGES_TXT)
+ 
+ XSLTPROC_FLAGS = \
+ 	--nonet \
+@@ -509,17 +508,14 @@ XSLTPROC_COMMAND_MAN = \
+ 	$(AM_V_XSLT)$(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) \
+ 		http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+ 
+-XSLTPROC_COMMAND_HTML = \
+-	$(AM_V_XSLT)$(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(srcdir)/man/custom-html.xsl $<
+-
+ man/%.xml: man/%.txt
+ 	$(AM_V_ASCIIDOC)$(ASCIIDOC) -d manpage -b docbook -o $@ $<
+ 
+ man/%.8: man/%.xml
+ 	$(XSLTPROC_COMMAND_MAN)
+ 
+-man/%.html: man/%.xml man/custom-html.xsl
+-	$(XSLTPROC_COMMAND_HTML)
++man/%.html: man/%.txt
++	$(AM_V_ASCIIDOC)$(ASCIIDOC) -b html5 -a icons -a theme=flask -o $@ $<
+ 
+ # ------------------------------------------------------------------------------
+ 
+diff --git a/man/custom-html.xsl b/man/custom-html.xsl
+deleted file mode 100644
+index fe2b54e..0000000
+--- a/man/custom-html.xsl
++++ /dev/null
+@@ -1,31 +0,0 @@
+-<?xml version='1.0'?>
+-
+-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+-
+-<xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl"/>
+-
+-<!-- translate man page references to links to html pages -->
+-<xsl:template match="citerefentry">
+-  <a>
+-    <xsl:attribute name="href">
+-      <xsl:value-of select="refentrytitle"/><xsl:text>.html</xsl:text>
+-    </xsl:attribute>
+-    <xsl:call-template name="inline.charseq"/>
+-  </a>
+-</xsl:template>
+-
+-<!-- add Index link at top of page -->
+-<xsl:template name="user.header.content">
+-  <a>
+-    <xsl:attribute name="href">
+-      <xsl:text>index.html</xsl:text>
+-    </xsl:attribute>
+-    <xsl:text>Index</xsl:text>
+-  </a>
+-  <hr/>
+-</xsl:template>
+-
+-<!-- Switch things to UTF-8, ISO-8859-1 is soo yesteryear -->
+-<xsl:output method="html" encoding="UTF-8" indent="no"/>
+-
+-</xsl:stylesheet>
+-- 
+2.39.2
+
diff --git a/network/patches/0062-man-Add-asciidoc-configuration-file.patch b/network/patches/0062-man-Add-asciidoc-configuration-file.patch
new file mode 100644
index 000000000..3183ec913
--- /dev/null
+++ b/network/patches/0062-man-Add-asciidoc-configuration-file.patch
@@ -0,0 +1,62 @@
+From baf429f17d664bbc6d141c13ce6ed52091803c3b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 27 Sep 2018 00:22:59 +0200
+Subject: [PATCH 062/304] man: Add asciidoc configuration file
+
+This adds a short command to link to other man pages
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am       | 12 ++++++++----
+ man/asciidoc.conf | 12 ++++++++++++
+ 2 files changed, 20 insertions(+), 4 deletions(-)
+ create mode 100644 man/asciidoc.conf
+
+diff --git a/Makefile.am b/Makefile.am
+index c4f8b45..ebf3be7 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -508,14 +508,18 @@ XSLTPROC_COMMAND_MAN = \
+ 	$(AM_V_XSLT)$(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) \
+ 		http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+ 
+-man/%.xml: man/%.txt
+-	$(AM_V_ASCIIDOC)$(ASCIIDOC) -d manpage -b docbook -o $@ $<
++man/%.xml: man/%.txt man/asciidoc.conf
++	$(AM_V_ASCIIDOC)$(ASCIIDOC) \
++		-f man/asciidoc.conf \
++		-d manpage -b docbook -o $@ $<
+ 
+ man/%.8: man/%.xml
+ 	$(XSLTPROC_COMMAND_MAN)
+ 
+-man/%.html: man/%.txt
+-	$(AM_V_ASCIIDOC)$(ASCIIDOC) -b html5 -a icons -a theme=flask -o $@ $<
++man/%.html: man/%.txt man/asciidoc.conf
++	$(AM_V_ASCIIDOC)$(ASCIIDOC) \
++		-f man/asciidoc.conf \
++		-b html5 -a icons -a theme=flask -o $@ $<
+ 
+ # ------------------------------------------------------------------------------
+ 
+diff --git a/man/asciidoc.conf b/man/asciidoc.conf
+new file mode 100644
+index 0000000..243f81f
+--- /dev/null
++++ b/man/asciidoc.conf
+@@ -0,0 +1,12 @@
++ifdef::backend-docbook[]
++[link-inlinemacro]
++{0%{target}}
++{0#<citerefentry>}
++{0#<refentrytitle>{target}</refentrytitle><manvolnum>{0}</manvolnum>}
++{0#</citerefentry>}
++endif::backend-docbook[]
++
++ifdef::backend-html5[]
++[link-inlinemacro]
++<a href="{target}.html">{target}{0?({0})}</a>
++endif::backend-html5[]
+-- 
+2.39.2
+
diff --git a/network/patches/0063-man-Convert-network-8-from-docbook-to-asciidoc.patch b/network/patches/0063-man-Convert-network-8-from-docbook-to-asciidoc.patch
new file mode 100644
index 000000000..e5341d529
--- /dev/null
+++ b/network/patches/0063-man-Convert-network-8-from-docbook-to-asciidoc.patch
@@ -0,0 +1,503 @@
+From 44d5ffe94daa496c95bf91860a5211272d8f3ff1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 27 Sep 2018 00:25:12 +0200
+Subject: [PATCH 063/304] man: Convert network(8) from docbook to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network.txt | 107 ++++++++++++++
+ man/network.xml | 368 ------------------------------------------------
+ 2 files changed, 107 insertions(+), 368 deletions(-)
+ create mode 100644 man/network.txt
+ delete mode 100644 man/network.xml
+
+diff --git a/man/network.txt b/man/network.txt
+new file mode 100644
+index 0000000..569449e
+--- /dev/null
++++ b/man/network.txt
+@@ -0,0 +1,107 @@
++network(8)
++==========
++
++NAME
++----
++network - IPFire Network Configuration Program
++
++SYNOPSIS
++--------
++[verse]
++'network' [<options>] <command> ...
++
++DESCRIPTION
++-----------
++The 'network' command is a tool which configures the network on every IPFire
++system. It is a fast and versatile way to create, edit and remove configurations,
++review the status of the network and it is working in the background of the
++system make sure that things are running smoothly.
++
++OPTIONS
++-------
++-d::
++--debug::
++	Enabled debugging mode.
++	In this mode, there wll be debug output on the console and written to
++	the log.
++	The debugging mode can be permanently enabled by setting 'DEBUG=1'.
++
++COMMANDS
++--------
++The following commands are understood:
++
++'start' [ZONE]::
++	Starts a zone. That means the zone is being created and brought up.
++	If one or more zones are passed to the command, only these will be
++	started.
++
++'stop' [ZONE]::
++	Stops a zone. This is the inverse of the 'start' command.
++
++'restart' [ZONE]::
++	Restarts a zone.
++
++'status' [ZONE]::
++	Shows an overview of the status of the zone.
++
++'zone' ...::
++	Commands to configure zones. See link:network-zone[8] for details.
++
++'port' ...::
++	Commands to configure ports. See link:network-port[8] for details.
++
++'device' ...::
++	See the status or execute commands to network devices.
++	See link:network-device[8] for details.
++
++'hostname' [HOSTNAME]::
++	Without the optional 'HOSTNAME' argument, this command will print
++	the configured hostname.
++	Passing 'HOSTNAME' will set it as the new hostname.
++
++'settings' ...::
++	Shows and alters global configuration parameters.
++	See link:network-settings[8] for details.
++
++'dns-server' ...::
++	This command allows to configure DNS servers.
++	See link::network-dns-server[8] for details.
++
++'route' ...::
++	This command allows managing static routes.
++	See link:network-route[8] for details.
++
++'vpn' ...::
++	The command allows managing VPN connections.
++	See link:network-vpn[8] for details.
++
++'reset'::
++	This command will reset all network configuration.
++	All zones, ports and other settings will be removed.
++
++'help' ...::
++	Shows this man page.
++
++EXIT CODES
++----------
++The 'network' command will normally exit with code zero.
++If there has been aproblem and the requested action could not be performed,
++the exit code is unequal to zero.
++
++BUGS
++----
++Please report all bugs to the bugtracker at https://bugzilla.ipfire.org/.
++
++AUTHORS
++-------
++Michael Tremer
++
++SEE ALSO
++--------
++link:network-settings[8]
++link:network-device[8]
++link:network-dns-server[8]
++link:network-performance-tuning[8]
++link:network-port[8]
++link:network-quick-start[8]
++link:network-zone[8]
+diff --git a/man/network.xml b/man/network.xml
+deleted file mode 100644
+index 0a97453..0000000
+--- a/man/network.xml
++++ /dev/null
+@@ -1,368 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network">
+-	<refentryinfo>
+-		<title>network</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network</command>
+-			<arg choice="opt" rep="repeat">OPTIONS</arg>
+-			<arg choice="plain">COMMAND</arg>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The <command>network</command> command is a tool which configures
+-			the network on every IPFire system. It is a fast and versatile
+-			way to create, edit and remove configurations, review the status
+-			of the network and it is working in the background of the system
+-			making sure that things are running smoothly.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Options</title>
+-
+-		<para>
+-			The following options are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<option>-d</option>
+-				</term>
+-				<term>
+-					<option>--debug</option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enables the debugging mode.
+-						In this mode, there will be debug output on
+-						the console and written to the log.
+-					</para>
+-					<para>
+-						The debugging mode can be permanently enabled by setting
+-						<varname>DEBUG=1</varname>
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>start <replaceable>ZONE-NAME</replaceable>...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Starts a zone. That means the network zone will be created
+-						and brought up.
+-						If one or more zone names are passed to the command, only
+-						these will be started.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>stop <replaceable>ZONE-NAME</replaceable>...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Stops a zone. This is the inverse of the <command>start</command>
+-						command.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>restart <replaceable>ZONE-NAME</replaceable>...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Restarts a zone.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>status <replaceable>ZONE-NAME</replaceable>...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Shows a human-readable overview of the status
+-						of the network zone.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>zone ...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Configure a zone or show status information.
+-						See <citerefentry>
+-							<refentrytitle>network-zone</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry> for details.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>port ...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Configure a port or show status information.
+-						See <citerefentry>
+-							<refentrytitle>network-port</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry> for details.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>device ...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Show status information about network devices.
+-						See <citerefentry>
+-							<refentrytitle>network-device</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry> for details.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>config <replaceable><varname>KEY=VALUE</varname></replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Shows and alters global configuration parameters.
+-						See <citerefentry>
+-							<refentrytitle>network-settings</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry> for details.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>help [<replaceable>TYPE</replaceable>
+-						<replaceable>HOOK</replaceable>|<replaceable>TYPE</replaceable> list-hooks]</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Calling <command>network help</command> without any
+-						arguments will show you this man page.
+-					</para>
+-					<para>
+-						<command>network help <replaceable>TYPE</replaceable> list-hooks</command>
+-						will print a list of all hooks of <replaceable>TYPE</replaceable>.
+-					</para>
+-					<para>
+-						You may optionally pass two arguments, to view the help
+-						of a certain hook.
+-						The type of the hook <replaceable>TYPE</replaceable>
+-						needs to be passed as well as the name of the hook
+-						<replaceable>HOOK</replaceable>.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>hostname <replaceable>HOSTNAME</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The <command>hostname</command> command will return the
+-						currently configured hostname of the system.
+-					</para>
+-					<para>
+-						If a new hostname is added to the command line,
+-						it will be configured, but will be set after the next
+-						reboot.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>dns-server ...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The <command>dns-server</command> command will help you
+-						configuring the local DNS servers.
+-						See <citerefentry>
+-							<refentrytitle>network-dns-server</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry> for details.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>route ...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The <command>route</command> command allows managing static routes.
+-						See <citerefentry>
+-							<refentrytitle>network-route</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry> for details.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>vpn ...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The <command>vpn</command> allows managing VPN connections.
+-						See <citerefentry>
+-							<refentrytitle>network-vpn</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry> for details.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>reset</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The <command>reset</command> command will reset all
+-						network configuration. That means all zone configurations
+-						will be removed and there will be no networking after the
+-						next reboot.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Exit Codes</title>
+-
+-		<para>
+-			The <command>network</command> command will normally exit with code 0.
+-			If there has been a problem and the requested action could not be done,
+-			the exit code is unequal to zero.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Bugs</title>
+-
+-		<para>
+-			Please report all bugs to the official bugtracker at
+-			http://bugs.ipfire.org/.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network-settings</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-device</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-dns-server</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-performance-tuning</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-port</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-quick-start</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-zone</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0064-man-Convert-network-color-8-to-asciidoc.patch b/network/patches/0064-man-Convert-network-color-8-to-asciidoc.patch
new file mode 100644
index 000000000..ff765aa14
--- /dev/null
+++ b/network/patches/0064-man-Convert-network-color-8-to-asciidoc.patch
@@ -0,0 +1,152 @@
+From b2f5dc13f74d0b740885f99a7d1408480da582cf Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 27 Sep 2018 00:34:35 +0200
+Subject: [PATCH 064/304] man: Convert network-color(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-color.txt | 33 ++++++++++++++++
+ man/network-color.xml | 91 -------------------------------------------
+ 2 files changed, 33 insertions(+), 91 deletions(-)
+ create mode 100644 man/network-color.txt
+ delete mode 100644 man/network-color.xml
+
+diff --git a/man/network-color.txt b/man/network-color.txt
+new file mode 100644
+index 0000000..7c95e18
+--- /dev/null
++++ b/man/network-color.txt
+@@ -0,0 +1,33 @@
++network-color(8)
++================
++
++NAME
++----
++network-color - IPFire Network Configuration Control Program
++
++DESCRIPTION
++-----------
++The 'color' command helps to manage colors for zones and ports.
++The color is being used to make identification of a zone or port easier on the
++command line and web user interface.
++
++COMMANDS
++--------
++The following commands are understood:
++
++'set' [AABBCC]::
++	The color of a zone or port is set with the 'set' command.
++	It is required to pass a color in hex formatting.
++
++'reset'::
++	Resets the color of a zone or port to blank.
++
++AUTHOR
++------
++Jonatan Schlag
++
++SEE ALSO
++--------
++link:network[8]
++link:network-zone[8]
++link:network-port[8]
+diff --git a/man/network-color.xml b/man/network-color.xml
+deleted file mode 100644
+index caf2349..0000000
+--- a/man/network-color.xml
++++ /dev/null
+@@ -1,91 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-color">
+-	<refentryinfo>
+-		<title>networ-color</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Jonatan</firstname>
+-				<surname>Schlag</surname>
+-				<email>jonatan.schlag@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-color</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-color</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The <command>color</command> helps to manage colors for zone and ports.
+-			The color is used to make identification of a zone or port easier on the
+-			command line or web user interface.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>set <replaceable>00AABB</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The color of a zone or port is set with the <command>set</command> command.
+-						It is always required to pass a valid color hex value (e.g. 880400).
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-			<varlistentry>
+-				<term>
+-					<command>reset</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command resets the color of a zone or port to blank.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-	</variablelist>
+-	</refsect1>
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-zone</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-port</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0065-man-Drop-test-page.patch b/network/patches/0065-man-Drop-test-page.patch
new file mode 100644
index 000000000..5dd0ddc36
--- /dev/null
+++ b/network/patches/0065-man-Drop-test-page.patch
@@ -0,0 +1,44 @@
+From 91305dee4f83ca35758e756903e3324117a26a7d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 27 Sep 2018 00:36:02 +0200
+Subject: [PATCH 065/304] man: Drop test page
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am  |  1 -
+ man/test.txt | 11 -----------
+ 2 files changed, 12 deletions(-)
+ delete mode 100644 man/test.txt
+
+diff --git a/Makefile.am b/Makefile.am
+index ebf3be7..55d5d18 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -453,7 +453,6 @@ INSTALL_DIRS += \
+ # ------------------------------------------------------------------------------
+ 
+ MANPAGES = \
+-	man/test.8 \
+ 	man/firewall-settings.8 \
+ 	man/network.8 \
+ 	man/network-color.8 \
+diff --git a/man/test.txt b/man/test.txt
+deleted file mode 100644
+index 4c9d35d..0000000
+--- a/man/test.txt
++++ /dev/null
+@@ -1,11 +0,0 @@
+-test(8)
+-=======
+-
+-NAME
+-----
+-test - Hello World!
+-
+-SYNOPSIS
+---------
+-[verse]
+-'hello world' [<options>] <file>
+-- 
+2.39.2
+
diff --git a/network/patches/0066-man-network-color-Add-synopsis.patch b/network/patches/0066-man-network-color-Add-synopsis.patch
new file mode 100644
index 000000000..6097f2a90
--- /dev/null
+++ b/network/patches/0066-man-network-color-Add-synopsis.patch
@@ -0,0 +1,32 @@
+From 62191ec375cf7fc957690d88c663ae7ad479a1a4 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 27 Sep 2018 00:47:19 +0200
+Subject: [PATCH 066/304] man: network-color: Add synopsis
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-color.txt | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/man/network-color.txt b/man/network-color.txt
+index 7c95e18..f3be474 100644
+--- a/man/network-color.txt
++++ b/man/network-color.txt
+@@ -3,7 +3,13 @@ network-color(8)
+ 
+ NAME
+ ----
+-network-color - IPFire Network Configuration Control Program
++network-color - Allows assigning a color to a zone or port
++
++SYNOPSIS
++--------
++[verse]
++'network' [zone ZONE|port PORT] color set AABBCC
++'network' [zone ZONE|port PORT] reset
+ 
+ DESCRIPTION
+ -----------
+-- 
+2.39.2
+
diff --git a/network/patches/0067-man-Convert-firewall-settings-to-asciidoc.patch b/network/patches/0067-man-Convert-firewall-settings-to-asciidoc.patch
new file mode 100644
index 000000000..8751171af
--- /dev/null
+++ b/network/patches/0067-man-Convert-firewall-settings-to-asciidoc.patch
@@ -0,0 +1,409 @@
+From 66fe74f95f4da254fc1162c591a40012c17aab07 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 21:16:10 +0200
+Subject: [PATCH 067/304] man: Convert firewall-settings to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/firewall-settings.txt |  97 +++++++++++++
+ man/firewall-settings.xml | 284 --------------------------------------
+ 2 files changed, 97 insertions(+), 284 deletions(-)
+ create mode 100644 man/firewall-settings.txt
+ delete mode 100644 man/firewall-settings.xml
+
+diff --git a/man/firewall-settings.txt b/man/firewall-settings.txt
+new file mode 100644
+index 0000000..20038e3
+--- /dev/null
++++ b/man/firewall-settings.txt
+@@ -0,0 +1,97 @@
++firewall-settings(8)
++====================
++
++NAME
++----
++firewall-settings - Global firewall settings
++
++SYNOPSIS
++--------
++[verse]
++'firewall settings'
++'firewall settings' KEY=VALUE ...
++
++DESCRIPTION
++-----------
++This command is used to set global firewall settings.
++Please have a look at the individual man pages for more options.
++
++COMMANDS
++--------
++If no argument is given, the configuration will be dumped to the console.
++
++You may set a new value by adding the variable name and the new value to
++the command line.
++
++SETTINGS
++--------
++=== CONNTRACK_MAX_CONNECTIONS = 16384
++Limits the max. number of simultaneous connections.
++
++Modify this if you want to handle a larger number of concurrent
++connections. Every connection will use approx. 16 kBytes of memory.
++
++=== CONNTRACK_UDP_TIMEOUT = 60
++Defines the timeout (in seconds) the kernel will wait until
++a half-assured UDP connection is fully established.
++
++=== FIREWALL_ACCEPT_ICMP_REDIRECTS = [true|false]
++Enable if you want to accept ICMP redirect messages.
++
++=== FIREWALL_CLAMP_PATH_MTU = [true|false]
++If Path MTU Discovery does not work well, enable this option.
++
++It sets the MSS value of a packet so that the remote site would
++never send a packet bigger than the MSS value.
++
++No ICMP packets are needed to make this work, so use this on
++networks with broken ICMP filtering.
++
++=== FIREWALL_DEFAULT_TTL = 64
++Here you can change the default TTL used for sending packets.
++
++The given value must be between 10 and 255.
++Don't mess with this unless you know what you are doing.
++
++=== FIREWALL_LOG_BAD_TCP_FLAGS = [true|false]
++Enable this to log TCP packets with bad flags or options.
++
++=== FIREWALL_LOG_INVALID_ICMP = [true|false]
++Enable this to log INVALID ICMP packets.
++
++=== FIREWALL_LOG_INVALID_TCP = [true|false]
++Enable this to log INVALID TCP packets.
++
++=== FIREWALL_LOG_INVALID_UDP = [true|false]
++Enable this to log INVALID UDP packets.
++
++=== FIREWALL_LOG_MARTIANS = [true|false]
++Enable this to log packets with impossible addresses.
++
++=== FIREWALL_LOG_STEALTH_SCANS = [true|false]
++Enable this to log all stealth scans.
++
++=== FIREWALL_PMTU_DISCOVERY = [true|false]
++Enables Path MTU Discovery.
++
++=== FIREWALL_RP_FILTER = [true|false]
++Enable to drop connection from non-routable IPs,
++e.g. prevent source routing.
++
++=== FIREWALL_SYN_COOKIES = [true|false]
++Enable for SYN-flood protection.
++
++=== FIREWALL_USE_ECN = [true|false]
++Enables the ECN (Explicit Congestion Notification) TCP flag.
++
++Some routers on the Internet still do not support ECN properly.
++When this setting is disabled, ECN is only advertised
++when asked for.
++
++AUTHORS
++-------
++Michael Tremer
++
++SEE ALSO
++--------
++link:firewall[8]
+diff --git a/man/firewall-settings.xml b/man/firewall-settings.xml
+deleted file mode 100644
+index 7357f4c..0000000
+--- a/man/firewall-settings.xml
++++ /dev/null
+@@ -1,284 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="firewall-settings">
+-	<refentryinfo>
+-		<title>firewall-settings</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>firewall-settings</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>firewall-settings</refname>
+-		<refpurpose>Firewall Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>firewall-settings</command>
+-		</cmdsynopsis>
+-
+-		<cmdsynopsis>
+-			<command>firewall-settings <replaceable>KEY=VALUE</replaceable></command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The <command>firewall-settings</command> command may be used to set
+-			global firewall settingsuration options.
+-		</para>
+-		<para>
+-			Please have a look at the individual man pages for more options.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			If no additional argument is given, running the command will
+-			dump a list of all settingsuration variables and their current values.
+-		</para>
+-
+-		<para>
+-			You may set a new value by adding the variable name and the new
+-			value to the command line.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Variables</title>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<varname>CONNTRACK_MAX_CONNECTIONS</varname> = <replaceable>16384</replaceable>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Limits the max. number of simultaneous connections.
+-					</para>
+-					<para>
+-						Modify this if you want to handle a larger number of concurrent
+-						connections. Every connection will use approx. 16 kBytes of memory.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>CONNTRACK_UDP_TIMEOUT</varname> = <replaceable>60</replaceable>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Defines the timeout (in seconds) the kernel will wait until
+-						a half-assured UDP connection is fully established.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_ACCEPT_ICMP_REDIRECTS</varname> = [true|<emphasis>false</emphasis>]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enable if you want to accept ICMP redirect messages.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_CLAMP_PATH_MTU</varname> = [true|<emphasis>false</emphasis>]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						If Path MTU Discovery does not work well, enable this option.
+-						It sets the MSS value of a packet so that the remote site would
+-						never send a packet bigger than the MSS value.
+-					</para>
+-					<para>
+-						No ICMP packets are needed to make this work, so use this on
+-						networks with broken ICMP filtering.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_DEFAULT_TTL</varname> = <replaceable>64</replaceable>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Here you can change the default TTL used for sending packets.
+-					</para>
+-					<para>
+-						The given value must be between 10 and 255.
+-						Don't mess with this unless you know what you are doing.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_LOG_BAD_TCP_FLAGS</varname> = [<emphasis>true</emphasis>|false]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enable this to log TCP packets with bad flags or options.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_LOG_INVALID_ICMP</varname> = [<emphasis>true</emphasis>|false]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enable this to log INVALID ICMP packets.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_LOG_INVALID_TCP</varname> = [<emphasis>true</emphasis>|false]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enable this to log INVALID TCP packets.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_LOG_INVALID_UDP</varname> = [<emphasis>true</emphasis>|false]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enable this to log INVALID UDP packets.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_LOG_MARTIANS</varname> = [true|<emphasis>false</emphasis>]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enable this to log packets with impossible addresses.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_LOG_STEALTH_SCANS</varname> = [<emphasis>true</emphasis>|false]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enable this to log all stealth scans.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_PMTU_DISCOVERY</varname> = [true|<emphasis>false</emphasis>]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enables Path MTU Discovery.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_RP_FILTER</varname> = [<emphasis>true</emphasis>|false]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enable to drop connection from non-routable IPs,
+-						e.g. prevent source routing.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_SYN_COOKIES</varname> = [<emphasis>true</emphasis>|false]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enable for SYN-flood protection.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>FIREWALL_USE_ECN</varname> = [<emphasis>true</emphasis>|false]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Enables the ECN (Explicit Congestion Notification) TCP flag.
+-					</para>
+-					<para>
+-						Some routers on the Internet still do not support ECN properly,
+-						so this is not enabled by default.
+-						When this setting is disabled, ECN is only advertised
+-						when asked for.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>firewall</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0068-man-Convert-network-description-8-to-asciidoc.patch b/network/patches/0068-man-Convert-network-description-8-to-asciidoc.patch
new file mode 100644
index 000000000..4c993a4b7
--- /dev/null
+++ b/network/patches/0068-man-Convert-network-description-8-to-asciidoc.patch
@@ -0,0 +1,144 @@
+From c601b69e5d8db595fee00241702ee8bd2689c49e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 21:24:48 +0200
+Subject: [PATCH 068/304] man: Convert network-description(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                 |  1 -
+ man/include-description.txt | 11 +++++
+ man/network-description.xml | 92 -------------------------------------
+ 3 files changed, 11 insertions(+), 93 deletions(-)
+ create mode 100644 man/include-description.txt
+ delete mode 100644 man/network-description.xml
+
+diff --git a/Makefile.am b/Makefile.am
+index 55d5d18..287a111 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -456,7 +456,6 @@ MANPAGES = \
+ 	man/firewall-settings.8 \
+ 	man/network.8 \
+ 	man/network-color.8 \
+-	man/network-description.8 \
+ 	man/network-device.8 \
+ 	man/network-dhcp.8 \
+ 	man/network-dns-server.8 \
+diff --git a/man/include-description.txt b/man/include-description.txt
+new file mode 100644
+index 0000000..a39ba55
+--- /dev/null
++++ b/man/include-description.txt
+@@ -0,0 +1,11 @@
++'description edit'::
++	This command opens an editor and allows you to edit title and description.
++
++	NOTE: The formation of the description is similar to a git commit.
++	Every description has a title, the first line of the description.
++	The title is shown on the status page and in the web user interface.
++	It should be something short like "Office Lan" or "DMZ".
++	After the title can follow a longer description.
++
++'description show'::
++	Prints the description.
+diff --git a/man/network-description.xml b/man/network-description.xml
+deleted file mode 100644
+index f1722b4..0000000
+--- a/man/network-description.xml
++++ /dev/null
+@@ -1,92 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-color">
+-	<refentryinfo>
+-		<title>networ-color</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Jonatan</firstname>
+-				<surname>Schlag</surname>
+-				<email>jonatan.schlag@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-description</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-description</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The <command>description</command> command make it possible to add descriptions to zone and ports.
+-			A description is similar to a git commit. Every description has a title, the first line of the describtion file.
+-			The title is shown on the status page and in the webinterface. It should be something short like Office Lan or DMZ.
+-			After the title can follow a longer description. You can write whatever you want.
+-			This longer description is shown via the <command>show</command> command
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>edit</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-					This command opens an editor and allows you to edit title and description.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-			<varlistentry>
+-				<term>
+-					<command>show</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command prints title and the longer description in a nice way.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-	</variablelist>
+-	</refsect1>
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-zone</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-port</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0069-man-Convert-network-device-8-to-asciidoc.patch b/network/patches/0069-man-Convert-network-device-8-to-asciidoc.patch
new file mode 100644
index 000000000..1cdc8b87d
--- /dev/null
+++ b/network/patches/0069-man-Convert-network-device-8-to-asciidoc.patch
@@ -0,0 +1,254 @@
+From 9d2265232d8a1c399617e347bda66a8019d8b36d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 21:40:53 +0200
+Subject: [PATCH 069/304] man: Convert network-device(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-device.txt |  61 +++++++++++++++
+ man/network-device.xml | 165 -----------------------------------------
+ 2 files changed, 61 insertions(+), 165 deletions(-)
+ create mode 100644 man/network-device.txt
+ delete mode 100644 man/network-device.xml
+
+diff --git a/man/network-device.txt b/man/network-device.txt
+new file mode 100644
+index 0000000..33fcefa
+--- /dev/null
++++ b/man/network-device.txt
+@@ -0,0 +1,61 @@
++network(8)
++==========
++
++NAME
++----
++network-device - Controls network devices
++
++SYNOPSIS
++--------
++[verse]
++'network device' [<options>] <command> ...
++
++DESCRIPTION
++-----------
++The 'network device' command shows low-level status information
++of network devices and other things.
++
++COMMANDS
++--------
++The following commands are understood:
++
++'list'::
++	This command shows a list of all device that are currently present
++	on this system. This includes PHYs and serial devices as well.
++
++'DEVICE discover'::
++	Runs a discovery for many hooks on the given device.
++
++	This will check if the hook can find for example a DHCP server or
++	DSLAM and thus predict for what the device should be used.
++
++'DEVICE identify'::
++	This command only works for Ethernet adapters and will make those
++	that support this feature flash for a few seconds.
++
++	It is handy to find the right device to put the cable in.
++
++'DEVICE monitor'::
++	This command creates a monitor interface for wireless modules.
++
++	An instance of link:tcpdump[8] will be started and show all
++	frames that are sent or received on the 802.11 layer (layer 2).
++
++'DEVICE status'::
++	This will show you very detailed information about the given device.
++
++'DEVICE unlock'::
++	This command will unlock the SIM card in a modem.
++	Only serial devices are supported which are the most 4G or 3G modems.
++
++	For the PIN or PUK code, the user will be prompted.
++
++AUTHORS
++-------
++Michael Tremer
++
++SEE ALSO
++--------
++link:network[8]
++link:network-port[8]
++link:network-zone[8]
+diff --git a/man/network-device.xml b/man/network-device.xml
+deleted file mode 100644
+index 11dc04e..0000000
+--- a/man/network-device.xml
++++ /dev/null
+@@ -1,165 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-device">
+-	<refentryinfo>
+-		<title>network-device</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-device</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-device</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network device <arg choice="plain">COMMAND</arg></command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			With help of the <command>device</command> subcommands, it is very easy
+-			to get status information about network devices and to do some more
+-			things.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>list</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The <command>list</command> command will show a list
+-						of all devices that are currently plugged in or active
+-						on the system.
+-						This includes PHYs and serial devices as well.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>DEVICE</replaceable> status</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This will show you very detailed information about the given
+-						device.
+-					</para>
+-					<para>
+-						This is all about the ethernet parts of the device and
+-						does not contain any IP information as this is defined
+-						as a zone (<citerefentry>
+-							<refentrytitle>network-zone</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry>).
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>DEVICE</replaceable> identify</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command only works for Ethernet adapters and will
+-						make those that support this feature flash for a few
+-						seconds.
+-						It is handy to find the right device to put the cable in.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>DEVICE</replaceable> discover</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Runs a discovery for many hooks on the given device.
+-						This will check if the hook can find for example a DHCP
+-						server or DSLAM and thus predict for what the device
+-						should be used.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>DEVICE</replaceable> unlock</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command will unlock the SIM card in a modem.
+-						Only serial devices are supported which are the most
+-						UMTS or 3G modems.
+-					</para>
+-					<para>
+-						For the PIN or PUK code, the user will be prompted.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>DEVICE</replaceable> monitor</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The <command>monitor</command> command is used to
+-						create a monitor interface for wireless modules.
+-						An instance of tcpdump will be started and show
+-						all frames that are sent or received on the 802.11
+-						layer (layer 2) of the wireless network.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0070-man-Convert-network-dhcp-8-to-asciidoc.patch b/network/patches/0070-man-Convert-network-dhcp-8-to-asciidoc.patch
new file mode 100644
index 000000000..95cd6ce5e
--- /dev/null
+++ b/network/patches/0070-man-Convert-network-dhcp-8-to-asciidoc.patch
@@ -0,0 +1,217 @@
+From c6e12dc53a1e65a0089ee0ddb0573a29bc2acd8a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 21:46:49 +0200
+Subject: [PATCH 070/304] man: Convert network-dhcp(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-dhcp.txt |  44 +++++++++++++
+ man/network-dhcp.xml | 145 -------------------------------------------
+ 2 files changed, 44 insertions(+), 145 deletions(-)
+ create mode 100644 man/network-dhcp.txt
+ delete mode 100644 man/network-dhcp.xml
+
+diff --git a/man/network-dhcp.txt b/man/network-dhcp.txt
+new file mode 100644
+index 0000000..a448619
+--- /dev/null
++++ b/man/network-dhcp.txt
+@@ -0,0 +1,44 @@
++network(8)
++==========
++
++NAME
++----
++network-dhcp - Controls the DHCP Server
++
++SYNOPSIS
++--------
++[verse]
++'network dhcpv6' <command> ...
++'network dhcpv4' <command> ...
++
++DESCRIPTION
++-----------
++With help of the DHCP commands it is possible to configure DHCP
++servers for IPv6 and IPv4.
++
++COMMANDS
++--------
++The following commands are understood:
++
++'start'::
++	Starts the DHCP server.
++
++'stop'::
++	Stops the DHCP server.
++
++'restart'::
++	Restarts the DHCP server.
++
++'reload'::
++	Reload the DHCP server configuration.
++
++'subnet ...'::
++	TODO
++
++AUTHORS
++-------
++Michael Tremer
++
++SEE ALSO
++--------
++link:network[8]
+diff --git a/man/network-dhcp.xml b/man/network-dhcp.xml
+deleted file mode 100644
+index cc081bb..0000000
+--- a/man/network-dhcp.xml
++++ /dev/null
+@@ -1,145 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-dhcp">
+-	<refentryinfo>
+-		<title>network-dhcp</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-dhcp</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-dhcp</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network <arg choice="plain">[dhcpv6|dhcpv4]</arg> <arg choice="plain">command</arg> ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			With help of the <command>dhcp</command> commands it is possible to
+-			configure DHCP servers for IPv6 and IPv4.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>start</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Starts the DHCP service.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>stop</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Stops the DHCP service.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>restart</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Restarts the DHCP service immediately.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>reload</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Reload the DHCP service configuration.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>show</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Shows the DHCP configuration.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>subnet ...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Use this command to manage subnets.
+-						See <citerefentry>
+-							<refentrytitle>network-dhcp-subnet</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry> for details.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-dhcp-subnet</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0071-man-Convert-network-dns-server-8-to-asciidoc.patch b/network/patches/0071-man-Convert-network-dns-server-8-to-asciidoc.patch
new file mode 100644
index 000000000..a8f55a10b
--- /dev/null
+++ b/network/patches/0071-man-Convert-network-dns-server-8-to-asciidoc.patch
@@ -0,0 +1,306 @@
+From 063089cbdb2745248bd8556e87de4a0d2bc8091d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 21:59:01 +0200
+Subject: [PATCH 071/304] man: Convert network-dns-server(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-dns-server.txt |  75 ++++++++++++++
+ man/network-dns-server.xml | 203 -------------------------------------
+ 2 files changed, 75 insertions(+), 203 deletions(-)
+ create mode 100644 man/network-dns-server.txt
+ delete mode 100644 man/network-dns-server.xml
+
+diff --git a/man/network-dns-server.txt b/man/network-dns-server.txt
+new file mode 100644
+index 0000000..bd01ca7
+--- /dev/null
++++ b/man/network-dns-server.txt
+@@ -0,0 +1,75 @@
++network-dns-server(8)
++=====================
++
++NAME
++----
++network-dns-server - Controls the DNS settings
++
++SYNOPSIS
++--------
++[verse]
++'network dns-server' add SERVER [PRIORITY]
++'network dns-server' remove SERVER
++'network dns-server' list
++'network dns-server' update
++
++DESCRIPTION
++-----------
++With this command, you will be able to configure the local DNS
++configuration.
++
++You may add and remove DNS servers as well as view the settings.
++
++COMMANDS
++--------
++The following commands are understood:
++
++'add' SERVER [PRIORITY]::
++	A new DNS server may be added to the list by the
++	'add' command.
++	A priority that will rank the server my optionally be given.
++
++	NOTE: SERVER must be a valid IP address and PRIORITY
++	must be a positive number.
++	The smaller this number, the higher is is the rank of
++	the server.
++
++'remove' SERVER::
++	The given server will be removed from the list of DNS servers.
++
++'list'::
++	Shows a list of all servers that are currently in use.
++
++'update'::
++	This command will re-create the system's configuration
++	files. It should not be required to use this command
++	very often.
++
++SETTINGS
++--------
++The following settings may be set using link:network-settings[8]:
++
++'DNS_USE_LOCAL_RESOLVER = [true|false]'::
++	This option defines whether the local DNS resolver should
++	be used or not.
++
++	Basically, the option adds localhost to the list of nameservers
++	in link:resolv.conf[5].
++
++'DNS_SEARCH_DOMAINS ='::
++	This setting configures the search domains for DNS queries
++	made by the local system.
++
++'DNS_RANDOMIZE = [true|false]'::
++	This option will break the DNS server ranks and will query
++	them in a random order which is useful to load-balance
++	multiple DNS servers.
++
++AUTHORS
++-------
++Michael Tremer
++
++SEE ALSO
++--------
++link:network[8],
++link:network-settings[8]
+diff --git a/man/network-dns-server.xml b/man/network-dns-server.xml
+deleted file mode 100644
+index aec52d4..0000000
+--- a/man/network-dns-server.xml
++++ /dev/null
+@@ -1,203 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-dns-server">
+-	<refentryinfo>
+-		<title>network-dns-server</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-dns-server</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-dns-server</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network dns-server <arg choice="plain">[add|remove]</arg> <arg choice="plain">SERVER</arg> [<arg choice="plain">PRIORITY</arg>]</command>
+-		</cmdsynopsis>
+-
+-		<cmdsynopsis>
+-			<command>network dns-server <arg choice="plain">[list|update]</arg></command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			With help of the <command>dns-server</command> subcommand, you will
+-			be able to configure the local DNS configuration.
+-			DNS is short for Domain Name System.
+-		</para>
+-		<para>
+-			You may add and remove DNS servers as well as view the settings.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>
+-						add
+-						<replaceable>SERVER</replaceable>
+-						[<replaceable>PRIORITY</replaceable>]
+-					</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						A new DNS server may be added to the list by the
+-						<command>add</command> command. A priority that will
+-						rank the server my optionally be given.
+-					</para>
+-					<para>
+-						<replaceable>SERVER</replaceable> must be a valid IP address
+-						and <replaceable>PRIORITY</replaceable> must be a positive
+-						integer number. The smaller this number, the higher is
+-						is the rank of the server.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>
+-						remove
+-						<replaceable>SERVER</replaceable>
+-					</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The given server will be removed from the list of
+-						DNS servers.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>list</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Shows a list of all servers that are currently in use.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>update</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command will re-create the system's configuration
+-						files. It should not be required to use this command
+-						very often.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Variables</title>
+-
+-		<para>
+-			These variables may be set by using the <citerefentry>
+-				<refentrytitle>network-settings</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry> command.
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<varname>DNS_USE_LOCAL_RESOLVER</varname>=[<emphasis>true</emphasis>|false]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This option defines whether the local DNS resolver should
+-						be used or not.
+-					</para>
+-					<para>
+-						Basically, the option adds localhost to the list of
+-						nameservers in <citerefentry>
+-							<refentrytitle>resolv.conf</refentrytitle>
+-							<manvolnum>5</manvolnum>
+-						</citerefentry>.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>DNS_SEARCH_DOMAINS</varname>=
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This setting configures the search domains for DNS queries
+-						made by the local system.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>DNS_RANDOMIZE</varname>=[true|<emphasis>false</emphasis>]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This option will break the DNS server ranks and will query
+-						them in a random order which is useful to load-balance
+-						multiple DNS servers.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-settings</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0072-man-Convert-network-performance-tuning-8-to-asciidoc.patch b/network/patches/0072-man-Convert-network-performance-tuning-8-to-asciidoc.patch
new file mode 100644
index 000000000..dd508342b
--- /dev/null
+++ b/network/patches/0072-man-Convert-network-performance-tuning-8-to-asciidoc.patch
@@ -0,0 +1,135 @@
+From c20f292770a6423c112b7f96d724bb13c4019d2a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 22:04:08 +0200
+Subject: [PATCH 072/304] man: Convert network-performance-tuning(8) to
+ asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-performance-tuning.txt | 33 ++++++++++++++
+ man/network-performance-tuning.xml | 73 ------------------------------
+ 2 files changed, 33 insertions(+), 73 deletions(-)
+ create mode 100644 man/network-performance-tuning.txt
+ delete mode 100644 man/network-performance-tuning.xml
+
+diff --git a/man/network-performance-tuning.txt b/man/network-performance-tuning.txt
+new file mode 100644
+index 0000000..763ee21
+--- /dev/null
++++ b/man/network-performance-tuning.txt
+@@ -0,0 +1,33 @@
++network-performance-tuning(8)
++=============================
++
++NAME
++----
++network-performance-tuning - Performance Tuning for Networking
++
++DESCRIPTION
++-----------
++This page contains a summary of some performance tuning techniques
++that this system is using.
++
++=== SMP Affinity
++
++This system is automatically using SMP affinity for every physical
++network controller, if supported.
++
++A processor core is assigned to handle all interrupts of a certain
++network controller which will result in minimising cache misses,
++reducing network latency and quite possibly increasing throughput.
++
++The algorithm is trying to balance all network controllers across
++all processors.
++
++See /proc/interrups for the distribution of interrupts. 
++
++AUTHORS
++-------
++Michael Tremer
++
++SEE ALSO
++--------
++link:network[8]
+diff --git a/man/network-performance-tuning.xml b/man/network-performance-tuning.xml
+deleted file mode 100644
+index 898f142..0000000
+--- a/man/network-performance-tuning.xml
++++ /dev/null
+@@ -1,73 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-performance-tuning">
+-	<refentryinfo>
+-		<title>network-performance-tuning</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-performance-tuning</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-performance-tuning</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			This page contains a summary of some performance tuning techniques
+-			that this system is using.
+-		</para>
+-	</refsect1>
+-
+-	<refsect2>
+-		<title>SMP Affinity</title>
+-
+-		<para>
+-			This system is automatically using SMP affinity for every physical
+-			network controller, if supported.
+-		</para>
+-
+-		<para>
+-			A processor core is assigned to handle all interrupts of a certain
+-			network controller which will result in minimising cache misses,
+-			reducing network latency and quite possibly increasing throughput.
+-		</para>
+-
+-		<para>
+-			The algorithm is trying to balance all network controllers across
+-			all processors.
+-		</para>
+-
+-		<para>
+-			See /proc/interrups for the distribution of interrupts. 
+-		</para>
+-	</refsect2>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0073-man-Convert-network-port-8-to-asciidoc.patch b/network/patches/0073-man-Convert-network-port-8-to-asciidoc.patch
new file mode 100644
index 000000000..d892b4ff7
--- /dev/null
+++ b/network/patches/0073-man-Convert-network-port-8-to-asciidoc.patch
@@ -0,0 +1,370 @@
+From 065346332054e3b2be85bee3f6d71a3dc34d6275 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 22:27:40 +0200
+Subject: [PATCH 073/304] man: Convert network-port(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-port.txt |  97 +++++++++++++++++
+ man/network-port.xml | 245 -------------------------------------------
+ 2 files changed, 97 insertions(+), 245 deletions(-)
+ create mode 100644 man/network-port.txt
+ delete mode 100644 man/network-port.xml
+
+diff --git a/man/network-port.txt b/man/network-port.txt
+new file mode 100644
+index 0000000..08b9e90
+--- /dev/null
++++ b/man/network-port.txt
+@@ -0,0 +1,97 @@
++network-port(8)
++===============
++
++NAME
++----
++network-port - Controls Network Ports
++
++SYNOPSIS
++--------
++[verse]
++'network port' new HOOK ...
++'network port' destroy PORT
++'network port' PORT color
++'network port' PORT create
++'network port' PORT description edit
++'network port' PORT description show
++'network port' PORT down
++'network port' PORT edit ...
++'network port' PORT identify
++'network port' PORT remove
++'network port' PORT status
++'network port' PORT up
++
++DESCRIPTION
++-----------
++This command creates, deletes, changes and views the configuration
++and status of ports.
++
++NOTE: A port is a physical or virtual device that is directly connected
++to an other network. It connects those and zones together.
++The 'network device' command shows status information of network devices
++and other things.
++
++COMMANDS
++--------
++The following commands are understood:
++
++'new' HOOK ...::
++	A new port may be created with this command.
++	HOOK must be a valid hook which may require more options.
++
++'destroy' PORT::
++	Destroys the port PORT.
++	The port is removed from any zones it is attached to and shut down.
++
++For all other commands, the name of the port needs to be passed first:
++
++'color'::
++	This command allows settings a color for a port.
++	See link:network-color[8] for more information.
++
++'create'::
++	This will create devices for the existing port PORT.
++
++	This does not create a new port. It will just create the (possibly
++	virtual) interface this port (i.e. create an interface for a WiFi
++	module or a VLAN device).
++
++	The interface is not brought up. Use the 'up' command to do that.
++
++include::include-description.txt[]
++
++'down'::
++	Shuts down the port.
++
++'edit'::
++	This command can be used to alter the configuration of a port.
++	Consult the documentation of the port hook to find out what is supported.
++
++'identify'::
++	This command will make the port flash for a few seconds
++	so that you can identify the correct network adapters
++	in the system.
++
++	This is not supported by all network adapters.
++
++'remove'::
++	This will remove an existing PORT.
++
++	This does not destroy the port. It inverses the operation performed
++	by the 'create' command.
++
++'status'::
++	This will show some detailed information about the status
++	of the specified port.
++
++'up'::
++	Brings up the port. It has to be created first.
++
++AUTHORS
++-------
++Michael Tremer
++
++SEE ALSO
++--------
++link:network[8],
++link:network-zone[8]
+diff --git a/man/network-port.xml b/man/network-port.xml
+deleted file mode 100644
+index 5c0a8ae..0000000
+--- a/man/network-port.xml
++++ /dev/null
+@@ -1,245 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-port">
+-	<refentryinfo>
+-		<title>network-port</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-port</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-port</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network port <arg choice="plain">[new|destroy]</arg> <replaceable>PORT</replaceable> ...</command>
+-		</cmdsynopsis>
+-
+-		<cmdsynopsis>
+-			<command>network port <replaceable>PORT</replaceable> <arg choice="plain">command</arg> ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			With help of the <command>port</command> command, you can create, delete,
+-			change and view the configuration and status of ports.
+-		</para>
+-
+-		<para>
+-			A port is a physical or virtual device that is directly connected
+-			to an other network. If connects those and zones together.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>new <replaceable>HOOK</replaceable> <arg choice="opt" rep="repeat">ARGUMENTS</arg></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						A new port may be created by the <command>new</command>
+-						command.
+-					</para>
+-					<para>
+-						<replaceable>HOOK</replaceable> must be a valid
+-						hook which may require more <replaceable>ARGUMENTS</replaceable>.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>destroy <replaceable>PORT</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						A port can be destroyed with this command.
+-					</para>
+-					<para>
+-						The port is removed from any zones it is attached
+-						to and shut down.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-
+-		<para>
+-			For all other commands, the name of the port needs to be passed first:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>PORT</replaceable> create</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This will create an existing <replaceable>PORT</replaceable>.
+-					</para>
+-					<para>
+-						This does not create a new port. It will just create the (possibly
+-						virtual) interface this port (i.e. create an interface for a WiFi
+-						module or a VLAN device).
+-					</para>
+-					<para>
+-						The interface is not brought up. Use the <command>up</command> command
+-						to do that.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>PORT</replaceable> remove</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This will remove an existing <replaceable>PORT</replaceable>.
+-					</para>
+-					<para>
+-						This does not destroy the port. It inverses the operation performed
+-						by the <command>create</command> command.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>PORT</replaceable> [up|down]</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						These commands will bring the port up or down. It has to be
+-						created first.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>PORT</replaceable> color</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command allows settings a color for a port.
+-						See
+-						<citerefentry>
+-						<refentrytitle>network-color</refentrytitle>
+-						<manvolnum>8</manvolnum>
+-						</citerefentry>
+-						for more information.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>PORT</replaceable>description</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command allows you to add a description to a port.
+-						See
+-						<citerefentry>
+-						<refentrytitle>network-description</refentrytitle>
+-						<manvolnum>8</manvolnum>,
+-						</citerefentry>
+-						for more information.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>PORT</replaceable> edit <arg choice="opt" rep="repeat">ARGUMENTS</arg></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The <command>edit</command> command can be used to alter
+-						the configuration of a port. Consult the documentation of the
+-						port hook to find out which <replaceable>ARGUMENTS</replaceable>
+-						are supported.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>PORT</replaceable> status</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This will show some detailed information about the state
+-						if the specified port.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>PORT</replaceable> identify</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command will make the port flash for a few seconds
+-						so that you can identify the correct network adapters
+-						in the system.
+-					</para>
+-					<para>
+-						This is not supported by all network adapters.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>,
+-			</citerefentry>
+-			<citerefentry>
+-				<refentrytitle>network-zone</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0074-man-Converting-network-quick-start-8-to-asciidoc.patch b/network/patches/0074-man-Converting-network-quick-start-8-to-asciidoc.patch
new file mode 100644
index 000000000..8f99ad13a
--- /dev/null
+++ b/network/patches/0074-man-Converting-network-quick-start-8-to-asciidoc.patch
@@ -0,0 +1,275 @@
+From 27b9807e568edee69afa758481be164662770901 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 22:40:35 +0200
+Subject: [PATCH 074/304] man: Converting network-quick-start(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-quick-start.txt |  92 +++++++++++++++++++++
+ man/network-quick-start.xml | 155 ------------------------------------
+ 2 files changed, 92 insertions(+), 155 deletions(-)
+ create mode 100644 man/network-quick-start.txt
+ delete mode 100644 man/network-quick-start.xml
+
+diff --git a/man/network-quick-start.txt b/man/network-quick-start.txt
+new file mode 100644
+index 0000000..02ebfe0
+--- /dev/null
++++ b/man/network-quick-start.txt
+@@ -0,0 +1,92 @@
++network-quick-start(8)
++======================
++
++NAME
++----
++network-quick-start - Quick Start Guide for Networking
++
++DESCRIPTION
++-----------
++The link:network[8] is a very powerful command that allows you to configure
++the entire networking stack.
++Unfortunately that makes it quite complicated to use as well.
++
++This guide tries to be a good starting point to set up basic networking with
++the 'network' command.
++
++=== Adding an Uplink Zone
++
++The first step is to create a new uplink zone with name 'upl0'.
++
++This zone will be of the link:network-zone-bridge[8] type which is the default
++for all local networks.
++
++------------
++# network zone new upl0 bridge
++------------
++
++The zone will be created and brought up immediately.
++
++=== Attaching Ports
++
++To connect the zone to the physical world outside of our box we will need
++to attach ports to the zone. That is done with a single command.
++To execute this command, we will need to know which ports are available.
++One of the easiest way to find out about that is to use the auto-completion
++feature of the shell like this:
++
++------------
++# network zone upl0 port attach [TAB] [TAB]
++------------
++
++That will list all not yet attached ports. The following command will actually
++attach the port (which is 'p0' in this example).
++
++-----------
++# network zone upl0 port attach p0
++-----------
++
++You can as well get a list of all detected devices, zones and ports by running:
++
++-----------
++# network device list
++-----------
++
++To a zone of the 'bridge' type you may attach more than just one port if you
++wish to.
++
++=== IP Connectivity
++
++After a zone has been created and ports have been attached, you are now
++able to add IP connectivity.
++
++The easiest way to do that is using DHCP which can be enabled by this simple command:
++
++------------
++# network zone upl0 config new dhcp
++------------
++
++=== Debugging
++
++You may see the current status of the network by running this command:
++
++------------
++# network status
++------------
++
++The entire network can be restarted by running:
++
++------------
++# network restart
++------------
++
++AUTHORS
++-------
++Michael Tremer
++
++SEE ALSO
++--------
++link:network[8],
++link:network-device[8],
++link:network-port[8],
++link:network-zone[8]
+diff --git a/man/network-quick-start.xml b/man/network-quick-start.xml
+deleted file mode 100644
+index ea79700..0000000
+--- a/man/network-quick-start.xml
++++ /dev/null
+@@ -1,155 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network">
+-	<refentryinfo>
+-		<title>network-quick-start</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-quick-start</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-quick-start</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsect1>
+-		<title>Quick Start Guide</title>
+-
+-		<para>
+-			The <command>network</command> is a very powerful command that allows
+-			you to configure the entire networking stack. Unfortunately that makes
+-			it quite complicated to use as well.
+-			This guide tries to be a good starting point to set up basic networking
+-			with the <command>network</command> command.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Add an uplink zone</title>
+-
+-		<para>
+-			The first step is to create a new uplink zone with name
+-			<replaceable>upl0</replaceable>.
+-			This zone will be of the <replaceable>bridge</replaceable> type which is
+-			the default for all local networks.
+-		</para>
+-
+-		<programlisting># network zone new <replaceable>upl0</replaceable> <replaceable>bridge</replaceable></programlisting>
+-
+-		<para>
+-			The zone will be created and brought up immediately.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Attaching ports</title>
+-
+-		<para>
+-			To connect the zone to the physical world outside of our box we will need
+-			to attach ports to the zone.
+-			That is done with a single command.
+-			To execute this command, we will need to know which ports are available.
+-			One of the easiest way to find out about that is to use the auto-completion
+-			feature of the shell like this:
+-		</para>
+-
+-		<programlisting># network zone <replaceable>upl0</replaceable> port attach [TAB] [TAB]</programlisting>
+-
+-		<para>
+-			That will list all not yet attached ports.
+-			The following command will actually attach the port
+-			(which is <replaceable>p0</replaceable> in this example).
+-		</para>
+-
+-		<programlisting># network zone <replaceable>upl0</replaceable> port attach <replaceable>p0</replaceable></programlisting>
+-
+-		<para>
+-			You can as well get a list of all detected devices,
+-			zones and ports by running:
+-		</para>
+-
+-		<programlisting># network device list</programlisting>
+-
+-		<para>
+-			To a zone of the <replaceable>bridge</replaceable> type you may attach more
+-			than just one port if you wish so.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>IP connectivity</title>
+-
+-		<para>
+-			After a zone has been created and ports have been attached, you are now
+-			able to add IP connectivity.
+-			The easiest way to do that is using DHCP which can be enabled by this
+-			simple command:
+-		</para>
+-
+-		<programlisting># network zone <replaceable>upl0</replaceable> config new <replaceable>ipv6-dhcp</replaceable></programlisting>
+-
+-		<para>
+-			And for IPv4:
+-		</para>
+-
+-		<programlisting># network zone <replaceable>upl0</replaceable> config new <replaceable>ipv4-dhcp</replaceable></programlisting>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Debugging</title>
+-
+-		<para>
+-			You may see the current status of the network by running this command:
+-		</para>
+-
+-		<programlisting># network status</programlisting>
+-
+-		<para>
+-			The entire network can be restarted by running:
+-		</para>
+-
+-		<programlisting># network restart</programlisting>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-config</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-device</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-port</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-zone</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0075-man-Use-include-for-color-commands.patch b/network/patches/0075-man-Use-include-for-color-commands.patch
new file mode 100644
index 000000000..eccd76aec
--- /dev/null
+++ b/network/patches/0075-man-Use-include-for-color-commands.patch
@@ -0,0 +1,55 @@
+From d28ccf91678256bc299fed2c10b066682487b1e9 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 22:53:20 +0200
+Subject: [PATCH 075/304] man: Use include for color commands
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/include-color.txt | 9 +++++++++
+ man/network-port.txt  | 7 +++----
+ 2 files changed, 12 insertions(+), 4 deletions(-)
+ create mode 100644 man/include-color.txt
+
+diff --git a/man/include-color.txt b/man/include-color.txt
+new file mode 100644
+index 0000000..073c01b
+--- /dev/null
++++ b/man/include-color.txt
+@@ -0,0 +1,9 @@
++'color set <color>'::
++	The color is set with this command and required to be passed in
++	RGB hex formatting
++
++	NOTE: The color is being used to make identification of network devices
++	easier on the command line and web user interface.
++
++'color reset'::
++	Resets the color to blank.
+diff --git a/man/network-port.txt b/man/network-port.txt
+index 08b9e90..0c26f33 100644
+--- a/man/network-port.txt
++++ b/man/network-port.txt
+@@ -10,7 +10,8 @@ SYNOPSIS
+ [verse]
+ 'network port' new HOOK ...
+ 'network port' destroy PORT
+-'network port' PORT color
++'network port' PORT color set <color>
++'network port' PORT color reset
+ 'network port' PORT create
+ 'network port' PORT description edit
+ 'network port' PORT description show
+@@ -45,9 +46,7 @@ The following commands are understood:
+ 
+ For all other commands, the name of the port needs to be passed first:
+ 
+-'color'::
+-	This command allows settings a color for a port.
+-	See link:network-color[8] for more information.
++include::include-color.txt[]
+ 
+ 'create'::
+ 	This will create devices for the existing port PORT.
+-- 
+2.39.2
+
diff --git a/network/patches/0076-man-Drop-old-network-color-8-man-page.patch b/network/patches/0076-man-Drop-old-network-color-8-man-page.patch
new file mode 100644
index 000000000..a670acfb6
--- /dev/null
+++ b/network/patches/0076-man-Drop-old-network-color-8-man-page.patch
@@ -0,0 +1,72 @@
+From 7c329515f1e23231c315d41b55c4d9bea58c7d1c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 22:54:02 +0200
+Subject: [PATCH 076/304] man: Drop old network-color(8) man page
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |  1 -
+ man/network-color.txt | 39 ---------------------------------------
+ 2 files changed, 40 deletions(-)
+ delete mode 100644 man/network-color.txt
+
+diff --git a/Makefile.am b/Makefile.am
+index 287a111..26f2e9c 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -455,7 +455,6 @@ INSTALL_DIRS += \
+ MANPAGES = \
+ 	man/firewall-settings.8 \
+ 	man/network.8 \
+-	man/network-color.8 \
+ 	man/network-device.8 \
+ 	man/network-dhcp.8 \
+ 	man/network-dns-server.8 \
+diff --git a/man/network-color.txt b/man/network-color.txt
+deleted file mode 100644
+index f3be474..0000000
+--- a/man/network-color.txt
++++ /dev/null
+@@ -1,39 +0,0 @@
+-network-color(8)
+-================
+-
+-NAME
+-----
+-network-color - Allows assigning a color to a zone or port
+-
+-SYNOPSIS
+---------
+-[verse]
+-'network' [zone ZONE|port PORT] color set AABBCC
+-'network' [zone ZONE|port PORT] reset
+-
+-DESCRIPTION
+------------
+-The 'color' command helps to manage colors for zones and ports.
+-The color is being used to make identification of a zone or port easier on the
+-command line and web user interface.
+-
+-COMMANDS
+---------
+-The following commands are understood:
+-
+-'set' [AABBCC]::
+-	The color of a zone or port is set with the 'set' command.
+-	It is required to pass a color in hex formatting.
+-
+-'reset'::
+-	Resets the color of a zone or port to blank.
+-
+-AUTHOR
+-------
+-Jonatan Schlag
+-
+-SEE ALSO
+---------
+-link:network[8]
+-link:network-zone[8]
+-link:network-port[8]
+-- 
+2.39.2
+
diff --git a/network/patches/0077-man-Fix-page-headers.patch b/network/patches/0077-man-Fix-page-headers.patch
new file mode 100644
index 000000000..ccf34d48b
--- /dev/null
+++ b/network/patches/0077-man-Fix-page-headers.patch
@@ -0,0 +1,38 @@
+From ec3a18b8cf262977d6fd73cee231338ce1b96ffd Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 30 Sep 2018 22:55:51 +0200
+Subject: [PATCH 077/304] man: Fix page headers
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-device.txt | 4 ++--
+ man/network-dhcp.txt   | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/man/network-device.txt b/man/network-device.txt
+index 33fcefa..4f1c1b0 100644
+--- a/man/network-device.txt
++++ b/man/network-device.txt
+@@ -1,5 +1,5 @@
+-network(8)
+-==========
++network-device(8)
++=================
+ 
+ NAME
+ ----
+diff --git a/man/network-dhcp.txt b/man/network-dhcp.txt
+index a448619..bcb768e 100644
+--- a/man/network-dhcp.txt
++++ b/man/network-dhcp.txt
+@@ -1,5 +1,5 @@
+-network(8)
+-==========
++network-dhcp(8)
++===============
+ 
+ NAME
+ ----
+-- 
+2.39.2
+
diff --git a/network/patches/0078-man-Convert-network-route-8-to-asciidoc.patch b/network/patches/0078-man-Convert-network-route-8-to-asciidoc.patch
new file mode 100644
index 000000000..b2030772a
--- /dev/null
+++ b/network/patches/0078-man-Convert-network-route-8-to-asciidoc.patch
@@ -0,0 +1,157 @@
+From 70172845e300fb2bf491d471224bd087b0c4e0f4 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 15:08:46 +0200
+Subject: [PATCH 078/304] man: Convert network-route(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-route.txt | 32 ++++++++++++++
+ man/network-route.xml | 97 -------------------------------------------
+ 2 files changed, 32 insertions(+), 97 deletions(-)
+ create mode 100644 man/network-route.txt
+ delete mode 100644 man/network-route.xml
+
+diff --git a/man/network-route.txt b/man/network-route.txt
+new file mode 100644
+index 0000000..bf3ddb4
+--- /dev/null
++++ b/man/network-route.txt
+@@ -0,0 +1,32 @@
++network-route(8)
++================
++
++NAME
++----
++network-route - Manage Routing
++
++SYNOPSIS
++--------
++[verse]
++'network route' COMMAND ...
++
++DESCRIPTION
++-----------
++This command helps to manage routes.
++
++COMMANDS
++--------
++The following commands are understood:
++
++'static' ...::
++	Static routes are managed by the 'static' command followed by the options
++	for static routes which are described in link:network-route-static[8]
++
++AUTHORS
++-------
++Michael Tremer
++
++SEE ALSO
++--------
++link:network[8],
++link:network-route-static[8]
+diff --git a/man/network-route.xml b/man/network-route.xml
+deleted file mode 100644
+index 207a5ce..0000000
+--- a/man/network-route.xml
++++ /dev/null
+@@ -1,97 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-route">
+-	<refentryinfo>
+-		<title>network-route</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-route</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-route</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network route <arg choice="plain">COMMAND</arg></command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The <command>route</command> helps to manage routes.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>static</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Static routes are managed by the <command>static</command> command
+-						followed by the options for static routes which are described in:
+-						<citerefentry>
+-							<refentrytitle>network-route-static</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry>
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-	</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Route Types</title>
+-
+-		<refsect2>
+-			<title>static</title>
+-
+-			<para>
+-				A static route is a route which does not change when the network changes.
+-			</para>
+-		</refsect2>
+-</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-route-static</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0079-.gitignore-Ignore-DS_Store.patch b/network/patches/0079-.gitignore-Ignore-DS_Store.patch
new file mode 100644
index 000000000..010ed44d1
--- /dev/null
+++ b/network/patches/0079-.gitignore-Ignore-DS_Store.patch
@@ -0,0 +1,26 @@
+From 82003431a4998e04e0e67f12ee6c3b6e5e802901 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 15:10:49 +0200
+Subject: [PATCH 079/304] .gitignore: Ignore DS_Store
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ .gitignore | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/.gitignore b/.gitignore
+index 36c85a1..bb093d3 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -14,6 +14,8 @@
+ /*.tar.bz2
+ /*.tar.gz
+ /*.tar.xz
++.DS_Store
++._.DS_Store
+ *.log
+ *.cache
+ *.la
+-- 
+2.39.2
+
diff --git a/network/patches/0080-man-Convert-network-route-static-8-to-asciidoc.patch b/network/patches/0080-man-Convert-network-route-static-8-to-asciidoc.patch
new file mode 100644
index 000000000..8d2f01413
--- /dev/null
+++ b/network/patches/0080-man-Convert-network-route-static-8-to-asciidoc.patch
@@ -0,0 +1,280 @@
+From d715390e9b1c4cc72bd22b915a842acc96912108 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 15:34:19 +0200
+Subject: [PATCH 080/304] man: Convert network-route-static(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-route-static.txt |  69 +++++++++++++
+ man/network-route-static.xml | 183 -----------------------------------
+ 2 files changed, 69 insertions(+), 183 deletions(-)
+ create mode 100644 man/network-route-static.txt
+ delete mode 100644 man/network-route-static.xml
+
+diff --git a/man/network-route-static.txt b/man/network-route-static.txt
+new file mode 100644
+index 0000000..d4774b2
+--- /dev/null
++++ b/man/network-route-static.txt
+@@ -0,0 +1,69 @@
++= network-route-static(8)
++Michael Tremer <michael.tremer@ipfire.org>
++
++== NAME
++network-route - Manage Static Routing
++
++== SYNOPSIS
++[verse]
++'network route static' COMMAND ...
++'network route static add' NETWORK [--gateway=GATEWAY,--unreachable,--prohibit,--blackhole] [--mtu=MTU]
++'network route static remove' NETWORK
++'network route static list' [--protocol=ipv6|ipv4]
++
++== DESCRIPTION
++This command helps to manage routes.
++
++== COMMANDS
++The following commands are understood:
++
++'add' NETWORK ...::
++	A new route may be added by the 'add' command. It is required to pass a
++	valid network prefix NETWORK, which can be either IPv6 or IPv4.
++	+
++	For unicast routes, the '--gateway=GATEWAY' option must be passed, where
++	GATEWAY is a valid IP address of the same protocol type as the network
++	prefix is.
++	+
++	Use '--unreachable', '--prohibit', '--blackhole' can be used to create of
++	that type. See ROUTE TYPES below for more information about these options.
++	+
++	The optional '--mtu=MTU' parameter defines the MTU along the path to the
++	destination and must be an integer number. This will show you very
++	detailed information about the given device.
++
++'remove' NETWORK::
++	A route can be removed with this command.
++	+
++	NETWORK is the network prefix of an existing route.
++
++'list'::
++	Shows a list of all configured routes.
++	+
++	Output can be filtered by passing --protocol=[ipv6|ipv4].
++
++== ROUTE TYPES
++
++[horizontal]
++'unicast'::
++	A unicast route is the most common route in routing tables. It is a route to
++	a destination network address, which describes the path to the destination.
++	Use the '--gateway=GATEWAY' option to create such a route.
++
++'unreachable'::
++	When a route is determined and the routing decision process returns a
++	destination with an unreachable route type, an ICMP unreachable message is
++	generated and returned to the source address.
++
++'prohibit'::
++	This works like an _unreachable_ route, but the returned ICMP message is an
++	ICMP prohibited message.
++
++'blackhole'::
++	Packets matching this kind of route are silently discarded.
++	There will be no ICMP message sent to the source and no packet be forwarded.
++
++== SEE ALSO
++link:network[8],
++link:network-route[8],
++link:ip-route[8]
+diff --git a/man/network-route-static.xml b/man/network-route-static.xml
+deleted file mode 100644
+index d43eb62..0000000
+--- a/man/network-route-static.xml
++++ /dev/null
+@@ -1,183 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-route-static">
+-	<refentryinfo>
+-		<title>network-route-static</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-route-static</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-route-static</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network route static <arg choice="plain">COMMAND</arg></command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The <command>route static</command> helps to manage static routes.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>add <replaceable>NETWORK</replaceable> [<option>--gateway=GATEWAY</option>, <option>--unreachable</option>, <option>--prohibit</option>, <option>--blackhole</option>] [<option>--mtu=MTU</option>]</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						A new route may be added by the <command>add</command> command.
+-						It is always required to pass a valid network prefix
+-						<replaceable>NETWORK</replaceable>, which can be either
+-						IPv6 or IPv4.
+-					</para>
+-					<para>
+-						For unicast routes, the <option>--gateway=GATEWAY</option>
+-						option must be passed, where <varname>GATEWAY</varname>
+-						is a valid IP address of the same protocol type as the
+-						network prefix is.
+-					</para>
+-					<para>
+-						Use <option>--unreachable</option>, <option>--prohibit</option>,
+-						<option>--blackhole</option> can be used to create of that
+-						type. See <emphasis>ROUTE TYPES</emphasis> below for more
+-						information about these options.
+-					</para>
+-					<para>
+-						The optional <option>--mtu=MTU</option> parameter defines the
+-						MTU along the path to the destination and must be an integer
+-						number. This will show you very detailed information about
+-						the given device.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>remove <replaceable>NETWORK</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						A route can be removed with the command.
+-					</para>
+-					<para>
+-						<replaceable>NETWORK</replaceable> is the network prefix
+-						of an existing route.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>list [<option>--protocol=ipv6|ipv4</option>]</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Shows a list of all configured routes.
+-					</para>
+-					<para>
+-						Pass the protocol option to filter the output only for the
+-						given protocol.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Route Types</title>
+-
+-		<refsect2>
+-			<title>unicast</title>
+-
+-			<para>
+-				A unicast route is the most common route in routing tables.
+-				It is a route to a destination network address, which describes
+-				the path to the destination.
+-				Use the <option>--gateway=GATEWAY</option> option to create such
+-				a route.
+-			</para>
+-		</refsect2>
+-
+-		<refsect2>
+-			<title>unreachable</title>
+-
+-			<para>
+-				When a route is determined and the routing decision process
+-				returns a destination with an unreachable route type, an ICMP
+-				unreachable message is generated and returned to the source
+-				address.
+-			</para>
+-		</refsect2>
+-
+-		<refsect2>
+-			<title>prohibit</title>
+-
+-			<para>
+-				This works like an <emphasis>unreachable</emphasis> route, but
+-				the returned ICMP message is an ICMP prohibited message.
+-			</para>
+-		</refsect2>
+-
+-		<refsect2>
+-			<title>blackhole</title>
+-
+-			<para>
+-				Packets matching this kind of route are silently discarded.
+-				There will be no ICMP message sent to the source and no packet
+-				be forwarded.
+-			</para>
+-		</refsect2>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-route</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>ip-route</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0081-man-Convert-network-settings-8-to-asciidoc.patch b/network/patches/0081-man-Convert-network-settings-8-to-asciidoc.patch
new file mode 100644
index 000000000..f0e629f46
--- /dev/null
+++ b/network/patches/0081-man-Convert-network-settings-8-to-asciidoc.patch
@@ -0,0 +1,190 @@
+From daebec37ca3cd19e000d1a9c1a77448d8c155fcd Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 15:47:14 +0200
+Subject: [PATCH 081/304] man: Convert network-settings(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-settings.txt |  44 +++++++++++++++
+ man/network-settings.xml | 118 ---------------------------------------
+ 2 files changed, 44 insertions(+), 118 deletions(-)
+ create mode 100644 man/network-settings.txt
+ delete mode 100644 man/network-settings.xml
+
+diff --git a/man/network-settings.txt b/man/network-settings.txt
+new file mode 100644
+index 0000000..e77f038
+--- /dev/null
++++ b/man/network-settings.txt
+@@ -0,0 +1,44 @@
++= network-settings(8)
++Michael Tremer <michael.tremer@ipfire.org>
++
++== NAME
++network-settings - Change global network settings
++
++== SYNOPSIS
++'network settings'
++'network settings' KEY=VALUE
++
++== DESCRIPTION
++The 'network settings' command may be used to set global settings.
++
++Please have a look at the individual man pages for more options.
++
++== COMMANDS
++If no additional argument is given, running the command will dump a list of
++all settings variables and their current values.
++
++You may set a new value by adding the variable name and the new
++value to the command line.
++
++== VARIABLES
++
++'DEBUG=[true|_false_]'::
++	The DEBUG will control whether debug logging is enabled or not.
++	Additionally to writing debug log messages to the log files, the messages
++	will be displayed on the console as well.
++
++'WIRELESS_REGULATORY_DOMAIN=_00_'::
++	The wireless regulatory domain is set globally for the entire system with
++	the WIRELESS_REGULATORY_DOMAIN setting.
++	+
++	The default is '00' which is the _world_ setting.
++	+
++	Valid values are country codes for countries which have their own
++	regulatory domain.
++
++== AUTHORS
++Michael Tremer
++
++== SEE ALSO
++link:network[8],
++link:network-dns-server[8]
+diff --git a/man/network-settings.xml b/man/network-settings.xml
+deleted file mode 100644
+index 7d1c70d..0000000
+--- a/man/network-settings.xml
++++ /dev/null
+@@ -1,118 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-settings">
+-	<refentryinfo>
+-		<title>network-settings</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-settings</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-settings</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network settings</command>
+-		</cmdsynopsis>
+-
+-		<cmdsynopsis>
+-			<command>network settings <replaceable>KEY=VALUE</replaceable></command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The <command>network settings</command> command may be used to set
+-			global settingsuration options.
+-		</para>
+-		<para>
+-			Please have a look at the individual man pages for more options.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			If no additional argument is given, running the command will
+-			dump a list of all settingsuration variables and their current values.
+-		</para>
+-
+-		<para>
+-			You may set a new value by adding the variable name and the new
+-			value to the command line.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Variables</title>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<varname>DEBUG</varname>=[<emphasis>0</emphasis>|1]
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The <varname>DEBUG</varname> will control whether debug
+-						logging is enabled or not. Additionally to writing debug
+-						log messages to the log files, the messages will be displayed
+-						on the console as well.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<varname>WIRELESS_REGULATORY_DOMAIN</varname>=<emphasis>00</emphasis>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The wireless regulatory domain is set globally for the
+-						entire system with the <varname>WIRELESS_REGULATORY_DOMAIN</varname>
+-						setting. The default is <emphasis>00</emphasis> which
+-						is the <emphasis>world</emphasis> setting.
+-						Valid values are country codes for countries which have their
+-						own regulatory domain.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-dns-server</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0082-man-Convert-network-vpn-8-to-asciidoc.patch b/network/patches/0082-man-Convert-network-vpn-8-to-asciidoc.patch
new file mode 100644
index 000000000..b284f76dc
--- /dev/null
+++ b/network/patches/0082-man-Convert-network-vpn-8-to-asciidoc.patch
@@ -0,0 +1,135 @@
+From 9848b81e6e8c2732920d9a7a115110723e2b07bb Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 15:59:21 +0200
+Subject: [PATCH 082/304] man: Convert network-vpn(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-vpn.txt | 26 +++++++++++++++
+ man/network-vpn.xml | 81 ---------------------------------------------
+ 2 files changed, 26 insertions(+), 81 deletions(-)
+ create mode 100644 man/network-vpn.txt
+ delete mode 100644 man/network-vpn.xml
+
+diff --git a/man/network-vpn.txt b/man/network-vpn.txt
+new file mode 100644
+index 0000000..5a905db
+--- /dev/null
++++ b/man/network-vpn.txt
+@@ -0,0 +1,26 @@
++= network-vpn(8)
++Michael Tremer <michael.tremer@ipfire.org>
++
++== NAME
++network-vpn - Configure Virtual Private Networks
++
++== SYNOPSIS
++'network vpn' COMMAND ...
++
++== DESCRIPTION
++The 'vpn' command allows to create, delete, edit and show the status of VPN
++connections and the configuration around it.
++
++== COMMANDS
++The following commands are understood:
++
++'security-policies' ...::
++	Use this command to manage security policies.
++	+
++	See link:network-vpn-security-policies[8] for details.
++
++== AUTHORS
++Michael Tremer
++
++== SEE ALSO
++link:network[8]
+diff --git a/man/network-vpn.xml b/man/network-vpn.xml
+deleted file mode 100644
+index d71d14a..0000000
+--- a/man/network-vpn.xml
++++ /dev/null
+@@ -1,81 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-vpn">
+-	<refentryinfo>
+-		<title>network-vpn</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-vpn</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-vpn</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network <arg choice="plain">vpn</arg> <arg choice="plain">command</arg> ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The <command>vpn</command> command allows to create, delete, edit
+-			and show the status of VPN connections and the configuration around it.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>security-policies ...</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Use this command to manage security policies.
+-						See <citerefentry>
+-							<refentrytitle>network-vpn-security-policies</refentrytitle>
+-							<manvolnum>8</manvolnum>
+-						</citerefentry> for details.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0083-man-Convert-network-vpn-security-policies-8-to-ascii.patch b/network/patches/0083-man-Convert-network-vpn-security-policies-8-to-ascii.patch
new file mode 100644
index 000000000..6aa5702e5
--- /dev/null
+++ b/network/patches/0083-man-Convert-network-vpn-security-policies-8-to-ascii.patch
@@ -0,0 +1,453 @@
+From 0a31681e96ee9ed656bf5ce531d4057079a897be Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 16:30:26 +0200
+Subject: [PATCH 083/304] man: Convert network-vpn-security-policies(8) to
+ asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-vpn-security-policies.txt | 111 +++++++++
+ man/network-vpn-security-policies.xml | 313 --------------------------
+ 2 files changed, 111 insertions(+), 313 deletions(-)
+ create mode 100644 man/network-vpn-security-policies.txt
+ delete mode 100644 man/network-vpn-security-policies.xml
+
+diff --git a/man/network-vpn-security-policies.txt b/man/network-vpn-security-policies.txt
+new file mode 100644
+index 0000000..f9dc91a
+--- /dev/null
++++ b/man/network-vpn-security-policies.txt
+@@ -0,0 +1,111 @@
++= network-vpn-security-policies(8)
++Michael Tremer <michael.tremer@ipfire.org>
++
++== NAME
++network-vpn-security-policies - Configure VPN Security Policies
++
++== SYNOPSIS
++[verse]
++'network vpn security-policies [new|destroy]' NAME...
++'network vpn security-policies' NAME COMMAND ...
++
++== DESCRIPTION
++With help of the 'vpn security-policies', it is possible to create, destroy
++and edit VPN security policies.
++
++A security policy is a definition of ciphers and algorithms for integrity
++and key-exchanges for VPN connections.
++
++== COMMANDS
++The following commands are understood:
++
++'new NAME'::
++	A new security policy may be created with the 'new' command.
++	+
++	NAME does not allow any spaces.
++
++'destroy NAME'::
++	A security policy can be destroyed with this command.
++	+
++	If the policy is still in use, it cannot be deleted.
++
++For all other commands, the name of the security policy needs to be passed first:
++
++'NAME show'::
++	Shows the configuration of the security policy.
++
++'NAME key-exchange' [IKEv2|IKEv1]::
++	Defines the key exchange algorithm that should be used to initiate an
++	IPsec VPN connection.
++
++'NAME ciphers' [CIPHER-LIST|+CIPHER ...|-CIPHER ...]::
++	This command allows modifying the cipher list.
++	A new CIPHER-LIST can be passed which will replace the current configuration.
++	Alternatively, new ciphers can be added by prepending a + sign to the cipher
++	name and can removed likewise using -.
++	+
++	A cipher is an algorithm that encrypts and decrypts data to be able to
++	transmit it over an insecure channel.
++
++'NAME integrities' [INTEGRITY-LIST|+INTEGRITY ...|-INTEGRITY ...]::
++	This command allows modifying the integrity list similar to the
++	'ciphers' command.
++	+
++	Integrity algorithms are used to be able to determine if data has been
++	altered when being transferred over an untrusted channel.
++
++'NAME pseudo-random-functions' [PSEUDO-RANDOM-FUNCTION-LIST|+PSEUDO-RANDOM-FUNCTION...|-PSEUDO-RANDOM-FUNCTION]::
++	This command allows modifying the list of pseudo random functions
++	similar to the 'ciphers' command.
++	+
++	These functions are used in combination with an AEAD cipher only.
++
++'NAME group-types' [GROUP-TYPES-LIST|+GROUP-TYPE ...|-GROUP-TYPE]::
++	This command allows modifying the list of group types similar to the
++	'ciphers' command.
++	+
++	These algorithms are used to negotiate a shared secret of an insecure channel.
++
++'NAME pfs' [on|off]::
++	This command allows to enable or disable Perfect Forward Secrecy (PFS).
++	If PFS is enabled, the encrypted channels of a VPN connection will be
++	renegotiated regularly to avoid that the same keys are used for too long.
++	If an attacker is able to obtain a key that was used to encrypt the
++	data, it is only possible to decrypt a certain amount of data.
++	+
++	It is strongly recommended to enable PFS at all times.
++
++'NAME lifetime' LIFETIME::
++	This command allows to define how often the VPN connection is
++	renegotiated if PFS is enabled.
++
++'NAME compression' [on|off]::
++	This command allows to enable or disable compression.
++	If compression is enabled, all data is being compressed before being
++	sent through the VPN.
++	This setting is ignored if the peer does not support this.
++
++== System Policies
++
++The system comes with builtin policies that cannot be modified by the user.
++They are intended to provide good defaults for various situations.
++
++[horizontal]
++'system'::
++	This policy is the default for every VPN connection and allows using
++	all ciphers, integrity and key-exchange algorithms that are recommended
++	to use and have not been proven or assumed to be broken, yet.
++	+
++	Over time, this policy will change whenever an algorithm has been broken
++	and is not recommended to be used any more.
++
++'performance'::
++	This policy is recommended to be used on systems that are not very powerful.
++	Algorithms with smaller key lengths, but still considered to be secure
++	are being used.
++
++System policies cannot be deleted.
++
++== SEE ALSO
++link:network[8],
++link:network-vpn[8]
+diff --git a/man/network-vpn-security-policies.xml b/man/network-vpn-security-policies.xml
+deleted file mode 100644
+index 40e6213..0000000
+--- a/man/network-vpn-security-policies.xml
++++ /dev/null
+@@ -1,313 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-zone">
+-	<refentryinfo>
+-		<title>network-vpn-security-policies</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-vpn-security-policies</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-vpn-security-policies</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network vpn security-policies <arg choice="plain">[new|destroy]</arg> <replaceable>NAME</replaceable> ...</command>
+-		</cmdsynopsis>
+-
+-		<cmdsynopsis>
+-			<command>network vpn security-policies <replaceable>NAME</replaceable> <arg choice="plain">command</arg> ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			With help of the <command>vpn security-policies</command>, it is possible
+-			to create, destroy and edit VPN security policies.
+-		</para>
+-		<para>
+-			A security policy is a definition of ciphers and algorithms for integrity
+-			and key-exchanges for VPN connections.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>new <replaceable>NAME</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						A new security policy may be created with the
+-						<command>new</command> command.
+-					</para>
+-
+-					<para>
+-						<replaceable>NAME</replaceable> does not allow any spaces.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>destroy <replaceable>NAME</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						A security policy can be destroyed with this command.
+-					</para>
+-					<para>
+-						If the policy is still in use, it cannot be deleted.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-
+-		<para>
+-			For all other commands, the name of the security policy needs to be passed first:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>NAME</replaceable> show</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Shows the configuration of the security policy.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>NAME</replaceable> key-exchange <replaceable>[IKEv2|IKEv1]</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Defines the key exchange algorithm that should be used to
+-						initiate an IPsec VPN connection.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>NAME</replaceable> ciphers <replaceable>[CIPHER-LIST|+CIPHER ...|-CIPHER ...]</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command allows modifying the cipher list.
+-					</para>
+-
+-					<para>
+-						A new <replaceable>CIPHER-LIST</replaceable> can be passed
+-						which will replace the current configuration.
+-						Alternatively, new ciphers can be added by prepending a
+-						+ sign to the cipher name and can removed likewise
+-						using -.
+-					</para>
+-
+-					<para>
+-						A cipher is an algorithm that encrypts and decrypts data
+-						to be able to transmit it over an insecure channel.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>NAME</replaceable> integrities <replaceable>[INTEGRITY-LIST|+INTEGRITY ...|-INTEGRITY ...]</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command allows modifying the integrity list
+-						similar to the <command>ciphers</command> command.
+-					</para>
+-
+-					<para>
+-						Integrity algorithms are used to be able to determine
+-						if data has been altered when being transfered over
+-						an untrusted channel.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>NAME</replaceable> pseudo-random-functions <replaceable>[PSEUDO-RANDOM-FUNCTION-LIST|+PSEUDO-RANDOM-FUNCTION...|-PSEUDO-RANDOM-FUNCTION]</replaceable>
+-					</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command allows modifying the list of pseudo random functions
+-						similar to the <command>ciphers</command> command.
+-					</para>
+-
+-					<para>
+-						These functions are used in combination with an AEAD cipher only.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>NAME</replaceable> group-types <replaceable>[GROUP-TYPES-LIST|+GROUP-TYPE ...|-GROUP-TYPE]</replaceable>
+-					</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command allows modifying the list of group types
+-						similar to the <command>ciphers</command> command.
+-					</para>
+-
+-					<para>
+-						These algorithms are used to negotiate a shared secret
+-						of an insecure channel.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>NAME</replaceable> pfs <replaceable>[on|off]</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command allows to enable or disable Perfect Forward Secrecy (PFS).
+-					</para>
+-
+-					<para>
+-						If PFS is enabled, the encrypted channels of a VPN connection will be
+-						renegotiated regularly to avoid that the same keys are used for too long.
+-						If an attacker is able to obtain a key that was used to encrypt the
+-						data, it is only possible to decrypt a certain amount of data.
+-					</para>
+-
+-					<para>
+-						It is strongly recommended to enable PFS at all times.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>NAME</replaceable> lifetime <replaceable>LIFETIME</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command allows to define how often the VPN connection is
+-						renegotiated if PFS is enabled.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>NAME</replaceable> compression <replaceable>[on|off]</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command allows to enable or disable compression.
+-					</para>
+-
+-					<para>
+-						If compression is enabled, all data is being compressed before being
+-						sent through the VPN.
+-						This setting is ignored if the peer does not support this.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>System Policies</title>
+-
+-		<para>
+-			The system comes with builtin policies that cannot be modified by the user.
+-			They are intended to provide good defaults for various situations.
+-		</para>
+-
+-		<refsect2>
+-			<title>system</title>
+-
+-			<para>
+-				This policy is the default for every VPN connection and allows using
+-				all ciphers, integrity and key-exchange algorithms that are recommended
+-				to use and have not been proven or assumed to be broken, yet.
+-			</para>
+-
+-			<para>
+-				Over time, this policy will change whenever an algorithm has been broken
+-				and is not recommended to be used any more.
+-			</para>
+-		</refsect2>
+-
+-		<refsect2>
+-			<title>performance</title>
+-
+-			<para>
+-				This policy is recommended to be used on systems that are not very powerful.
+-				Algorithms with smaller key lengths, but still considered to be secure
+-				are being used.
+-			</para>
+-		</refsect2>
+-
+-		<para>
+-			System policies cannot be deleted.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-vpn</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0084-man-Convert-network-zone-8-to-asciidoc.patch b/network/patches/0084-man-Convert-network-zone-8-to-asciidoc.patch
new file mode 100644
index 000000000..0edf995fa
--- /dev/null
+++ b/network/patches/0084-man-Convert-network-zone-8-to-asciidoc.patch
@@ -0,0 +1,375 @@
+From 357723e90cb0f700c4315b6016543db4230df5fb Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 16:46:07 +0200
+Subject: [PATCH 084/304] man: Convert network-zone(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/include-color.txt       |   2 +-
+ man/include-description.txt |   2 +-
+ man/network-zone.txt        |  73 +++++++++++
+ man/network-zone.xml        | 247 ------------------------------------
+ 4 files changed, 75 insertions(+), 249 deletions(-)
+ create mode 100644 man/network-zone.txt
+ delete mode 100644 man/network-zone.xml
+
+diff --git a/man/include-color.txt b/man/include-color.txt
+index 073c01b..4b417a5 100644
+--- a/man/include-color.txt
++++ b/man/include-color.txt
+@@ -1,7 +1,7 @@
+ 'color set <color>'::
+ 	The color is set with this command and required to be passed in
+ 	RGB hex formatting
+-
++	+
+ 	NOTE: The color is being used to make identification of network devices
+ 	easier on the command line and web user interface.
+ 
+diff --git a/man/include-description.txt b/man/include-description.txt
+index a39ba55..49bac64 100644
+--- a/man/include-description.txt
++++ b/man/include-description.txt
+@@ -1,6 +1,6 @@
+ 'description edit'::
+ 	This command opens an editor and allows you to edit title and description.
+-
++	+
+ 	NOTE: The formation of the description is similar to a git commit.
+ 	Every description has a title, the first line of the description.
+ 	The title is shown on the status page and in the web user interface.
+diff --git a/man/network-zone.txt b/man/network-zone.txt
+new file mode 100644
+index 0000000..88a1988
+--- /dev/null
++++ b/man/network-zone.txt
+@@ -0,0 +1,73 @@
++= network-zone(8)
++Michael Tremer <michael.tremer@ipfire.org>
++
++== NAME
++network-zone - Manage network zones
++
++== SYNOPSIS
++[verse]
++'network zone [new|destroy]' ZONE
++'network zone' ZONE ...
++
++== DESCRIPTION
++With help of the 'zone' command, it is very easy to configure network zones.
++
++It is possible to create zones and remove them. Zones may also be brought up
++and down and reconfigured. Their status may be viewed as well.
++
++== COMMANDS
++The following commands are understood:
++
++'new ZONE HOOK OPTIONS'::
++	A new zone may be created by the 'create' command.
++	There are at least two arguments required.
++	+
++	ZONE must be valid name for a zone which does not already exist.
++	HOOK is a valid zone hook which may require additional options.
++
++'destroy ZONE'::
++	A zone can be destroyed with this command.
++	+
++	There are two possible ways to remove a zone. The case is when the zone is
++	not up. Then, it will be removed immediately. When the zone is current up
++	and used, it will tagged to be remove later, after it has been brought down.
++
++For all other commands, the name of the zone needs to be passed first:
++
++'edit OPTIONS'::
++	The settings of a zone may be edited after it has been created.
++	The options that can be passed depend on the hook that is used for the zone.
++	Run 'network zone ZONE edit --help' to learn more about that.
++	+
++	It usually is required to restart/reload the zone until the new settings
++	are taken into account.
++
++'[up|down]'::
++	These commands will bring the zone up/down. This is done without control
++	of systemd, therefore not intended to be done in a productive environment.
++	However, these commands may be used for debugging.
++
++'[enable|disable]'::
++	These commands will enable or disable the zone. An enabled zone will
++	automatically be started either during the boot process or a hotplug event
++	of an associated port or other device.
++
++'status'::
++	This will show some detailed information about the state if the specified zone.
++
++include::include-color.txt[]
++
++include::include-description.txt[]
++
++'identify'::
++	This command will make all ports of the zone flash for a few seconds so
++	that you can identify the correct network adapters in the system.
++
++'rename' NAME::
++	Renames the zone to NAME.
++	+
++	The command will shut down the zone if it is up and start it again with
++	the new name. If the zone is not up it won't be started.
++
++== SEE ALSO
++link:network[8]
+diff --git a/man/network-zone.xml b/man/network-zone.xml
+deleted file mode 100644
+index 99fa8b8..0000000
+--- a/man/network-zone.xml
++++ /dev/null
+@@ -1,247 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-zone">
+-	<refentryinfo>
+-		<title>network-zone</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-zone</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-zone</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network zone <arg choice="plain">[new|destroy]</arg> <replaceable>ZONE</replaceable> ...</command>
+-		</cmdsynopsis>
+-
+-		<cmdsynopsis>
+-			<command>network zone <replaceable>ZONE</replaceable> <arg choice="plain">command</arg> ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			With help of the <command>zone</command> command, it is very easy to
+-			configure network zones.
+-		</para>
+-		<para>
+-			It is possible to create zones and remove them. Zones may also
+-			be brought up and down and reconfigured. Their status may be viewed
+-			as well.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Commands</title>
+-
+-		<para>
+-			The following commands are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command>new <replaceable>ZONE</replaceable> <replaceable>HOOK</replaceable> <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						A new zone may be created by the <command>create</command>
+-						command. There are at least two arguments required.
+-					</para>
+-					<para>
+-						<replaceable>ZONE</replaceable> must be valid name for a
+-						zone which does not already exist.
+-						<replaceable>HOOK</replaceable> is a valid zone hook which
+-						may require additional options.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command>destroy <replaceable>ZONE</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						A zone can be destroyed with this command.
+-					</para>
+-					<para>
+-						There are two possible ways to remove a zone. The case
+-						is when the zone is not up. Then, it will be removed
+-						immediately. When the zone is current up and used, it
+-						will tagged to be remove later, after it has been brought
+-						down.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-
+-		<para>
+-			For all other commands, the name of the zone needs to be passed first:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>ZONE</replaceable> edit <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The settings of a zone may be edited after it has been created.
+-						The options that can be passed depend on the hook that is used
+-						for the zone.
+-						Run <command>network zone <replaceable>ZONE</replaceable> edit --help</command>
+-						to learn more about that.
+-					</para>
+-					<para>
+-						It usually is required to restart/reload the zone until
+-						the new settings are taken into account.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>ZONE</replaceable> [up|down]</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						These commands will bring the zone up/down. This is done
+-						without control of systemd, therefore not intended to be
+-						done in a productive environment.
+-						However, these commands may be used for debugging.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>ZONE</replaceable> [enable|disable]</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						These commands will enable or disable the zone. An enabled
+-						zone will automatically be started either during the boot process
+-						or a hotplug event of an associated port or other device.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>ZONE</replaceable> status</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This will show some detailed information about the state
+-						if the specified zone.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>ZONE</replaceable> color</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command allows settings a color for a zone.
+-						See
+-						<citerefentry>
+-						<refentrytitle>network-color</refentrytitle>
+-						<manvolnum>8</manvolnum>
+-						</citerefentry>
+-						for more information.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>ZONE</replaceable>description</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command make is possible to add a description to a zone.
+-						See
+-						<citerefentry>
+-						<refentrytitle>network-description</refentrytitle>
+-						<manvolnum>8</manvolnum>,
+-						</citerefentry>
+-						for more information.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>ZONE</replaceable> identify</command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This command will make all ports of the zone flash for
+-						a few seconds so that you can identify the correct network
+-						adapters in the system.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<command><replaceable>ZONE</replaceable> rename <replaceable>NAME</replaceable></command>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Renames the zone to <replaceable>NAME</replaceable>.
+-					</para>
+-					<para>
+-						The command will shut down the zone if it is up and
+-						start it again with the new name. If the zone is not
+-						up it won't be started.
+-					</para>
+-					<para>
+-						Zones that are marked to be destroyed cannot be renamed.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0085-man-Convert-network-zone-bridge-8-to-asciidoc.patch b/network/patches/0085-man-Convert-network-zone-bridge-8-to-asciidoc.patch
new file mode 100644
index 000000000..6d114cd02
--- /dev/null
+++ b/network/patches/0085-man-Convert-network-zone-bridge-8-to-asciidoc.patch
@@ -0,0 +1,255 @@
+From 010f24cfc6e363815ae6a408a16e8b07c069c1a7 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 16:59:10 +0200
+Subject: [PATCH 085/304] man: Convert network-zone-bridge(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-zone-bridge.txt |  55 ++++++++++++
+ man/network-zone-bridge.xml | 172 ------------------------------------
+ 2 files changed, 55 insertions(+), 172 deletions(-)
+ create mode 100644 man/network-zone-bridge.txt
+ delete mode 100644 man/network-zone-bridge.xml
+
+diff --git a/man/network-zone-bridge.txt b/man/network-zone-bridge.txt
+new file mode 100644
+index 0000000..2e4f839
+--- /dev/null
++++ b/man/network-zone-bridge.txt
+@@ -0,0 +1,55 @@
++= network-zone-bridge(8)
++Michael Tremer <michael.tremer@ipfire.org>
++
++== NAME
++network-zone-bridge - Manage network zones
++
++== SYNOPSIS
++[verse]
++'network zone new ZONE bridge' ...
++'network zone ZONE edit' ...
++
++== DESCRIPTION
++The bridge hook creates an ethernet bridge which acts as an unmanaged network
++switch. It contains one or multiple physical network interfaces or virtual
++devices which will be connected to each other.
++
++The bridge hook is the preferred hook for local area network zones which are
++connected to an ethernet network.
++
++== OPTIONS
++The following options are understood:
++
++'--address=ADDRESS'::
++	By this option, you may define the MAC address of the bridge. If this option
++	is missing, a random MAC address will be generated.
++
++'--mtu=MTU'::
++	Sets the default MTU of the bridge.
++	All ports in the bridge must support this MTU value.
++
++'--stp=[_on_|off]'::
++	This option enables or disables use of the _Spanning Tree Protocol_ (STP).
++	This protocol is used to avoid loops in networks by dynamically disabling
++	packet forwarding on links.
++	+
++	It is highly recommended to leave this option enabled when you add more
++	than one device to the zone. Read below how the behaviour of STP can be changed.
++
++Spanning Tree Protocol (802.1D) configuration options:
++
++'--stp-forward-delay=_0_'::
++	This sets the default time the interfaces are hold off after they have been
++	added to a bridge. The default value is 0.
++
++'--stp-hello=_2_'::
++	This option defines how often a hello message should be sent. The value is
++	given in seconds and the default is 2.
++
++'--stp-priority=512'::
++	The STP priority sets the ranking of this network device within the network.
++	The bridge with the best rank (0 is best) will become the root bridge.
++
++== SEE ALSO
++link:network[8],
++link:network-zone[8]
+diff --git a/man/network-zone-bridge.xml b/man/network-zone-bridge.xml
+deleted file mode 100644
+index a77118b..0000000
+--- a/man/network-zone-bridge.xml
++++ /dev/null
+@@ -1,172 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-zone-bridge">
+-	<refentryinfo>
+-		<title>network-zone-bridge</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-zone-bridge</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-zone-bridge</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network zone new <replaceable>ZONE</replaceable> bridge ...</command>
+-		</cmdsynopsis>
+-
+-		<cmdsynopsis>
+-			<command>network zone <replaceable>ZONE</replaceable> edit ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The bridge hook creates an ethernet bridge which acts as an unmanaged network
+-			switch. It contains one or multiple phyisical network interfaces or virtual
+-			devices which will be connected to each other.
+-		</para>
+-		<para>
+-			The bridge hook is the prefered hook for local area network zones which are
+-			connected to an ethernet network.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Options</title>
+-
+-		<para>
+-			The following options are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<option>--stp=[<emphasis>on</emphasis>|off]</option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This option enables or disable the use of the
+-						<emphasis>Spanning Tree Protocol</emphasis> (STP).
+-						This protocol is used to avoid loops in networks by
+-						dynamically disabling packet forwarding on links.
+-					</para>
+-					<para>
+-						It is highly recommended to leave this option enabled
+-						when you add more than one device to the zone.
+-						Read below how the behaviour of STP can be changed.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--mtu=<replaceable>MTU</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the default MTU of the bridge.
+-						All ports in the bridge must support this MTU value.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--address=<replaceable>ADDRESS</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						By this option, you may define the MAC address of the
+-						bridge. If this option is missing, a random MAC address
+-						will be generated.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-
+-		<para>
+-			Spanning Tree Protocol (802.1D) configuration options:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<option>--stp-forward-delay=<replaceable>0</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This sets the default time the interfaces are hold off
+-						after they have been added to a bridge.
+-						The default value is 0.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--stp-hello=<replaceable>2</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This option defines how often a hello message should be
+-						sent. The value is given in seconds and the default is 2.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--stp-priority=<replaceable>512</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The STP priority sets the ranking of this network device
+-						within the network. The bridge with the best rank
+-						(0 is best) will become the root bridge.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-zone</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0086-man-Convert-network-zone-config-pppoe-server-8-to-as.patch b/network/patches/0086-man-Convert-network-zone-config-pppoe-server-8-to-as.patch
new file mode 100644
index 000000000..3afd01e9a
--- /dev/null
+++ b/network/patches/0086-man-Convert-network-zone-config-pppoe-server-8-to-as.patch
@@ -0,0 +1,222 @@
+From 718371b565fdb93719f68b5a2dcf719dd57a4e93 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 17:15:50 +0200
+Subject: [PATCH 086/304] man: Convert network-zone-config-pppoe-server(8) to
+ asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-zone-config-pppoe-server.txt |  50 ++++++++
+ man/network-zone-config-pppoe-server.xml | 143 -----------------------
+ 2 files changed, 50 insertions(+), 143 deletions(-)
+ create mode 100644 man/network-zone-config-pppoe-server.txt
+ delete mode 100644 man/network-zone-config-pppoe-server.xml
+
+diff --git a/man/network-zone-config-pppoe-server.txt b/man/network-zone-config-pppoe-server.txt
+new file mode 100644
+index 0000000..72dff8e
+--- /dev/null
++++ b/man/network-zone-config-pppoe-server.txt
+@@ -0,0 +1,50 @@
++= network-zone-config-pppoe-server(8)
++
++== NAME
++network-zone-config-pppoe-server - PPPoE Server Settings
++
++== SYNOPSIS
++[verse]
++`network zone ZONE config create pppoe-server ...`
++`network zone ZONE config pppoe-server edit ...`
++
++== DESCRIPTION
++This configuration hook enables a **PPPoE Server** on a zone.
++
++== OPTIONS
++The following options are understood:
++
++`--subnet=SUBNET`::
++	The `--subnet` option defines an IPv4 pool of which IP addresses are
++	assigned to the remote hosts. The first address of the subnet will be used
++	for the gateway which is the PPPoE server itself.
++	+
++	The subnet must at least have two IP addresses.
++
++`--mtu=MTU`::
++	Set the required MTU (Maximum Transmission Unit) for the PPP connection.
++	The default value is 1492 bytes which is a common MTU for DSL connections.
++
++`--service-name=SERVICE NAME`::
++	This option receives a string which will be used as the service name. The
++	service name is sent out to the clients and used for identification but
++	not authorisation purposes.
++	+
++	The default is an empty value.
++
++`--max-sessions=0`::
++	Limit the number of sessions that may be established by the same MAC address.
++	This must be a positive number.
++	0 permits an unlimited number of sessions per MAC address.
++
++== EXAMPLES
++
++This command creates a PPPoE server that will assign an IP address from the
++192.168.0.0/16 subnet:
++
++  network zone net0 config create pppoe-server --subnet=192.168.0.0/16
++
++== SEE ALSO
++link:network[8],
++link:network-zone[8],
++link:network-zone-config[8]
+diff --git a/man/network-zone-config-pppoe-server.xml b/man/network-zone-config-pppoe-server.xml
+deleted file mode 100644
+index e6d497e..0000000
+--- a/man/network-zone-config-pppoe-server.xml
++++ /dev/null
+@@ -1,143 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-zone-config-pppoe-server">
+-	<refentryinfo>
+-		<title>network-zone-config-pppoe-server</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-zone-config-pppoe-server</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-zone-config-pppoe-server</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network zone <replaceable>ZONE</replaceable> config create pppoe-server ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			This configuration hook enables a <emphasis>PPPoE server</emphasis>
+-			functionality to a zone which is of an ethernet-like type.
+-		</para>
+-		<para>
+-			The PPPoE server is mostly for development purpose and performs pretty
+-			well. However, it is not recommended to use it in production environments.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Options</title>
+-
+-		<para>
+-			The following options are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<option>--subnet=<replaceable>SUBNET</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The <option>--subnet</option> option defines an IPv4 pool
+-						of which IP addresses are assigned to the remote hosts.
+-						The first address of the subnet will be used for the
+-						gateway which is the PPPoE server itself.
+-					</para>
+-					<para>
+-						The subnet must at least have two IP addresses.
+-						Broadcast and network addresses will be used as well.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--mtu=<replaceable>MTU</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Set the required MTU (Maximum Transmission Unit) for
+-						the PPP connection.
+-					</para>
+-					<para>
+-						The default value is 1492 bytes which is a common MTU for
+-						DSL connections.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--service-name=<replaceable>STRING</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						This options receives a string which will be used as the
+-						service name. The service name is sent out to the clients
+-						and used for identification but not authorization purposes.
+-					</para>
+-					<para>
+-						The default is an empty value.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--max-sessions=<emphasis>0</emphasis></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Limit the max. number of sessions that may be established
+-						by the same MAC address.
+-					</para>
+-					<para>
+-						This must be a positive number. 0 permits an unlimited
+-						number of sessions per MAC address.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-zone-config</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0087-man-Convert-network-zone-ip-tunnel-8-to-asciidoc.patch b/network/patches/0087-man-Convert-network-zone-ip-tunnel-8-to-asciidoc.patch
new file mode 100644
index 000000000..ce05d4197
--- /dev/null
+++ b/network/patches/0087-man-Convert-network-zone-ip-tunnel-8-to-asciidoc.patch
@@ -0,0 +1,184 @@
+From 6e94de3efa35088eb322ced2653efeec5f5c29fd Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 17:23:07 +0200
+Subject: [PATCH 087/304] man: Convert network-zone-ip-tunnel(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-zone-ip-tunnel.txt |  35 ++++++++++
+ man/network-zone-ip-tunnel.xml | 121 ---------------------------------
+ 2 files changed, 35 insertions(+), 121 deletions(-)
+ create mode 100644 man/network-zone-ip-tunnel.txt
+ delete mode 100644 man/network-zone-ip-tunnel.xml
+
+diff --git a/man/network-zone-ip-tunnel.txt b/man/network-zone-ip-tunnel.txt
+new file mode 100644
+index 0000000..cb30731
+--- /dev/null
++++ b/man/network-zone-ip-tunnel.txt
+@@ -0,0 +1,35 @@
++= network-zone-ip-tunnel(8)
++Michael Tremer <michael.tremer@ipfire.org>
++
++== NAME
++network-zone-ip-tunnel - Manage IP Tunnels
++
++== SYNOPSIS
++[verse]
++`network zone new ZONE ip-tunnel ...`
++`network zone ZONE edit ...`
++
++== DESCRIPTION
++The ip-tunnel hook is used to create IP tunnels that use protocols like GRE to
++encapsulate IP packets.
++
++== OPTIONS
++The following options are understood:
++
++`--mode=MODE`::
++	Sets the protocol that is being used to encapsulate IP packets.
++	Currently only **GRE** is supported.
++
++`--peer=PEER`::
++	The address of the peer that terminates the remote end of this tunnel.
++	+
++	If left empty, connections from any IP address will be accepted.
++
++`--local-address=LOCAL-ADDRESS`::
++	The local IP address the tunnel originates from.
++	+
++	This is optional and if unset a useful default will be used.
++
++== SEE ALSO
++link:network[8],
++link:network-zone[8]
+diff --git a/man/network-zone-ip-tunnel.xml b/man/network-zone-ip-tunnel.xml
+deleted file mode 100644
+index a1cc257..0000000
+--- a/man/network-zone-ip-tunnel.xml
++++ /dev/null
+@@ -1,121 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-zone-ip-tunnel">
+-	<refentryinfo>
+-		<title>network-zone-ip-tunnel</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-zone-ip-tunnel</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-zone-ip-tunnel</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network zone new <replaceable>ZONE</replaceable> ip-tunnel ...</command>
+-		</cmdsynopsis>
+-
+-		<cmdsynopsis>
+-			<command>network zone <replaceable>ZONE</replaceable> edit ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The ip-tunnel hook is used to create IP tunnels that use protocols
+-			like GRE to encapsulate IP packets.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Options</title>
+-
+-		<para>
+-			The following options are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<option>--mode=<replaceable>MODE</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the protocol that is being used to encapsulate
+-						IP packets.
+-						Currently only <replaceable>gre</replaceable> is supported.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--peer=<replaceable>PEER</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The address of the peer that terminates the remote
+-						end of this tunnel.
+-					</para>
+-
+-					<para>
+-						If left empty, connections from any IP address will
+-						be accepted.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--local-address=<replaceable>LOCAL-ADDRESS</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The local IP address the tunnel originates from.
+-					</para>
+-
+-					<para>
+-						This is optional and if unset a useful default will be used.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-zone</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0088-man-Convert-network-zone-modem-8-to-asciidoc.patch b/network/patches/0088-man-Convert-network-zone-modem-8-to-asciidoc.patch
new file mode 100644
index 000000000..d140ce3f8
--- /dev/null
+++ b/network/patches/0088-man-Convert-network-zone-modem-8-to-asciidoc.patch
@@ -0,0 +1,329 @@
+From 23eec7d08e289749759927bcf4c2387cbfcbdce2 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 17:34:10 +0200
+Subject: [PATCH 088/304] man: Convert network-zone-modem(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-zone-modem.txt |  66 +++++++++++
+ man/network-zone-modem.xml | 235 -------------------------------------
+ 2 files changed, 66 insertions(+), 235 deletions(-)
+ create mode 100644 man/network-zone-modem.txt
+ delete mode 100644 man/network-zone-modem.xml
+
+diff --git a/man/network-zone-modem.txt b/man/network-zone-modem.txt
+new file mode 100644
+index 0000000..6b09622
+--- /dev/null
++++ b/man/network-zone-modem.txt
+@@ -0,0 +1,66 @@
++= network-zone-modem(8)
++Michael Tremer <michael.tremer@ipfire.org>
++
++== NAME
++network-zone-modem - Configure serial modems
++
++== SYNOPSIS
++[verse]
++`network zone new ZONE modem ...`
++
++== DESCRIPTION
++The modem hook uses a serial interface to establish a PPP session to an Internet
++Service Provider. This method is used by 56k modems and mobile networks like
++LTE, GSM and 3G.
++
++== OPTIONS
++The following options are understood:
++
++`--device=DEVICE`::
++	Sets the serial device that is used to connect. Example: /dev/ttyUSB0
++
++`--monitor-device=DEVICE`::
++	The optional monitor device is used to collect status information like
++	signal strength and link quality while the connection is established.
++
++`--imsi=IMSI`::
++	Set the IMSI of the SIM card inside the wireless modem to identify it when
++	it is plugged in at runtime.
++
++`--pin=PIN`::
++	The PIN number of the SIM card.
++	This will be used to unlock the SIM card when it is locked.
++
++`--apn=APN`::
++	Sets the Access Point Name (APN) that the modem connects to.
++
++`--phone-number=PHONE-NUMBER`::
++	Sets the phone number that is dialled by the modem when the connection is
++	to be established.
++
++`--username=USERNAME`::
++	Sets the username for authentication.
++
++`--password=PASSWORD`::
++	Sets the password for authentication.
++	+
++	Use the `--auth=` option to transmit it in a secure manner to the provider.
++
++`--baudrate=921600`::
++	The baudrate for the serial link to the modem.
++
++`--mtu=N`::
++	Sets the default MTU of the PPP connection.
++
++`--auth=[chap|pap]`::
++	Define the authentication method that is used to authenticate against your
++	provider. The default is to use the provider's preference.
++
++	* _Challange-Handshake Authentication Protocol_ (`chap`) is the preferred,
++	secure method.
++	* _Password Authentication Protocol_ (`pap`) sends the plaintext password
++	to the authentication server which is the reason why it should be avoided.
++
++== SEE ALSO
++link:network[8],
++link:network-zone[8]
+diff --git a/man/network-zone-modem.xml b/man/network-zone-modem.xml
+deleted file mode 100644
+index 97a1d35..0000000
+--- a/man/network-zone-modem.xml
++++ /dev/null
+@@ -1,235 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-zone-modem">
+-	<refentryinfo>
+-		<title>network-zone-modem</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-zone-modem</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-zone-modem</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network zone new <replaceable>ZONE</replaceable> modem ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The modem hook uses a serial interface to establish a PPP session to an
+-			Internet Service Provider. This method is used by 56k modems and mobile
+-			networks like LTE, GSM and 3G.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Options</title>
+-
+-		<para>
+-			The following options are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<option>--device=<replaceable>DEVICE</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the serial device that is used to connect.
+-					</para>
+-					<para>
+-						Example: /dev/ttyUSB0
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--monitor-device=<replaceable>DEVICE</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The optional monitor device is used to collect status
+-						information like signal strength and link quality while
+-						the connection is established.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--imsi=<replaceable>IMSI</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Set the IMSI of the SIM card inside the wireless modem
+-						to identify it when it is plugged in at runtime.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--pin=<replaceable>PIN</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The PIN number of the SIM card.
+-					</para>
+-					<para>
+-						This will be used to unlock the SIM card when it
+-						is locked.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--apn=<replaceable>APN</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the Access Point Name (<replaceable>APN</replaceable>)
+-						that the modem connects to.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--phone-number=<replaceable>PHONE-NUMBER</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the phone number that is dialed by the modem when
+-						the connection is to be established.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--username=<replaceable>USERNAME</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the username for authentication.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--password=<replaceable>PASSWORD</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the password for authentication.
+-					</para>
+-					<para>
+-						Use the <option>--auth=</option> option to transmit it
+-						in a secure manner to the provider.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--baudrate=<emphasis>921600</emphasis></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						The baudrate for the serial link to the modem.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--mtu=<emphasis>N</emphasis></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the default MTU of the PPP connection.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--auth=[chap|pap]</option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Define the authentication method that is used to
+-						authenticate against your provider.
+-						The default is to use the provider's preference.
+-					</para>
+-					<itemizedlist>
+-						<listitem>
+-							<para>
+-								<emphasis>Challange-Handshake Authentication Protocol</emphasis>
+-								(chap) is the preferred secure method.
+-							</para>
+-						</listitem>
+-						<listitem>
+-							<para>
+-								<emphasis>Password Authentication Protocol</emphasis>
+-								(pap) sends the plaintext password to the authentication
+-								server which is the reason why it should be avoided to use PAP.
+-							</para>
+-						</listitem>
+-					</itemizedlist>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-zone</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0089-man-Convert-network-zone-pppoe-8-to-asciidoc.patch b/network/patches/0089-man-Convert-network-zone-pppoe-8-to-asciidoc.patch
new file mode 100644
index 000000000..191195159
--- /dev/null
+++ b/network/patches/0089-man-Convert-network-zone-pppoe-8-to-asciidoc.patch
@@ -0,0 +1,266 @@
+From d4f0a25cd61fe6a7d0cb711e269e9b75925edf23 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 17:42:10 +0200
+Subject: [PATCH 089/304] man: Convert network-zone-pppoe(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-zone-pppoe.txt |  52 +++++++++++
+ man/network-zone-pppoe.xml | 186 -------------------------------------
+ 2 files changed, 52 insertions(+), 186 deletions(-)
+ create mode 100644 man/network-zone-pppoe.txt
+ delete mode 100644 man/network-zone-pppoe.xml
+
+diff --git a/man/network-zone-pppoe.txt b/man/network-zone-pppoe.txt
+new file mode 100644
+index 0000000..93b55f2
+--- /dev/null
++++ b/man/network-zone-pppoe.txt
+@@ -0,0 +1,52 @@
++= network-zone-pppoe(8)
++Michael Tremer <michael.tremer@ipfire.org>
++
++== NAME
++network-zone-pppoe - PPP over Ethernet
++
++== SYNOPSIS
++[verse]
++`network zone new ZONE pppoe ...`
++
++== DESCRIPTION
++The `pppoe` hook creates a PPPoE connection to your ISP.
++
++== OPTIONS
++The following options are understood:
++
++`--username=USERNAME`::
++	Sets the username for authentication.
++
++`--password=PASSWORD`::
++	Sets the password for authentication.
++	+
++	Use the `--auth=` option to transmit it in a secure manner to the provider.
++
++`--mtu=N`::
++	Sets the default MTU of the PPP connection.
++
++`--auth=[chap|pap]`::
++	Define the authentication method that is used to authenticate against your
++	provider. The default is to use the provider's preference.
++
++	* _Challange-Handshake Authentication Protocol_ (`chap`) is the preferred,
++	secure method.
++	* _Password Authentication Protocol_ (`pap`) sends the plaintext password
++	to the authentication server which is the reason why it should be avoided.
++
++`--access-concentrator=STRING`::
++	By this option, you may define the name of the access concentrator.
++
++`--service-name=STRING`::
++	By this option, you may define the service name.
++
++`--ipv6=[on|off]`::
++	By this option, you may enable or disable IPv6.
++
++`--prefix-delegation=[on|off]`::
++	By this option, you may enable or disable the delegation through your
++	provider of one IPv6 prefix to your system.
++
++== SEE ALSO
++link:network[8],
++link:network-zone[8]
+diff --git a/man/network-zone-pppoe.xml b/man/network-zone-pppoe.xml
+deleted file mode 100644
+index 36c4d0e..0000000
+--- a/man/network-zone-pppoe.xml
++++ /dev/null
+@@ -1,186 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-zone-pppoe">
+-	<refentryinfo>
+-		<title>network-zone-pppoe</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-zone-pppoe</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-zone-pppoe</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network zone new <replaceable>ZONE</replaceable> pppoe ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The pppoe hook creates a PPPoE connection to your ISP.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Options</title>
+-
+-		<para>
+-			The following options are understood:
+-		</para>
+-
+-		<variablelist>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--username=<replaceable>USERNAME</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the username for authentication.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--password=<replaceable>PASSWORD</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the password for authentication.
+-					</para>
+-					<para>
+-						Use the <option>--auth=</option> option to transmit it
+-						in a secure manner to the provider.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--mtu=<emphasis>N</emphasis></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Sets the default MTU of the PPP connection.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--auth=[chap|pap]</option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Define the authentication method that is used to
+-						authenticate against your provider.
+-						The default is to use the provider's preference.
+-					</para>
+-					<itemizedlist>
+-						<listitem>
+-							<para>
+-								<emphasis>Challange-Handshake Authentication Protocol</emphasis>
+-								(chap) is the preferred secure method.
+-							</para>
+-						</listitem>
+-						<listitem>
+-							<para>
+-								<emphasis>Password Authentication Protocol</emphasis>
+-								(pap) sends the plaintext password to the authentication
+-								server which is the reason why it should be avoided to use PAP.
+-							</para>
+-						</listitem>
+-					</itemizedlist>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--access-concentrator=<replaceable>STRING</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						By this option, you may define the name of the access concentrator.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--service-name=<replaceable>STRING</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						By this option, you may define the service name.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--ipv6=[<emphasis>on</emphasis>|off]</option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						By this option, you may enable or disable IPv6
+-					</para>				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--prefix-delegation=[<emphasis>on</emphasis>|off]</option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						 By this option, you may enable or disable the delegation through your provider of one IPv6 prefix to your system.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-zone</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0090-man-Convert-network-zone-wireless-8-to-asciidoc.patch b/network/patches/0090-man-Convert-network-zone-wireless-8-to-asciidoc.patch
new file mode 100644
index 000000000..e5c9cf7ff
--- /dev/null
+++ b/network/patches/0090-man-Convert-network-zone-wireless-8-to-asciidoc.patch
@@ -0,0 +1,167 @@
+From bc2b9c75cd5b73e1c2de5463fc1c0bc94b6dad93 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 17:53:20 +0200
+Subject: [PATCH 090/304] man: Convert network-zone-wireless(8) to asciidoc
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-zone-wireless.txt |  32 ++++++++++
+ man/network-zone-wireless.xml | 107 ----------------------------------
+ 2 files changed, 32 insertions(+), 107 deletions(-)
+ create mode 100644 man/network-zone-wireless.txt
+ delete mode 100644 man/network-zone-wireless.xml
+
+diff --git a/man/network-zone-wireless.txt b/man/network-zone-wireless.txt
+new file mode 100644
+index 0000000..368ac2a
+--- /dev/null
++++ b/man/network-zone-wireless.txt
+@@ -0,0 +1,32 @@
++= network-zone-wireless(8)
++Michael Tremer <michael.tremer@ipfire.org>
++
++== NAME
++network-zone-wireless - Wireless Networks
++
++== SYNOPSIS
++[verse]
++`network zone new ZONE wireless ...`
++
++== DESCRIPTION
++The wireless hook uses a WiFi interface and connects to a wireless access point
++in station mode.
++
++Configuration and credentials for any wireless networks to connect to can be
++configured by using link:network-wireless-network[8].
++
++== OPTIONS
++The following options are understood:
++
++`--phy=PHY`::
++	Takes the MAC address or name of the physical layer that is used to create
++	a virtual wireless interface.
++
++`--address=ADDRESS`::
++	Define a MAC address that is used for the virtual wireless device. This
++	parameter is optional and a random MAC address will be generated when
++	omitted.
++
++== SEE ALSO
++link:network[8],
++link:network-zone[8]
+diff --git a/man/network-zone-wireless.xml b/man/network-zone-wireless.xml
+deleted file mode 100644
+index 0931245..0000000
+--- a/man/network-zone-wireless.xml
++++ /dev/null
+@@ -1,107 +0,0 @@
+-<?xml version="1.0"?>
+-<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+-	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+-
+-<refentry id="network-zone-wireless">
+-	<refentryinfo>
+-		<title>network-zone-wireless</title>
+-		<productname>network</productname>
+-
+-		<authorgroup>
+-			<author>
+-				<contrib>Developer</contrib>
+-				<firstname>Michael</firstname>
+-				<surname>Tremer</surname>
+-				<email>michael.tremer@ipfire.org</email>
+-			</author>
+-		</authorgroup>
+-	</refentryinfo>
+-
+-	<refmeta>
+-		<refentrytitle>network-zone-wireless</refentrytitle>
+-		<manvolnum>8</manvolnum>
+-	</refmeta>
+-
+-	<refnamediv>
+-		<refname>network-zone-wireless</refname>
+-		<refpurpose>Network Configuration Control Program</refpurpose>
+-	</refnamediv>
+-
+-	<refsynopsisdiv>
+-		<cmdsynopsis>
+-			<command>network zone new <replaceable>ZONE</replaceable> wireless ...</command>
+-		</cmdsynopsis>
+-	</refsynopsisdiv>
+-
+-	<refsect1>
+-		<title>Description</title>
+-
+-		<para>
+-			The wireless hook uses a WiFi interface and connects to a
+-			wireless access point in station mode.
+-		</para>
+-
+-		<para>
+-			Configuration and credentials for any wireless networks to
+-			connect to can be configured by using the
+-			<command>network wireless network</command> command.
+-
+-			See <citerefentry>
+-				<refentrytitle>network-wireless-networks</refentrytitle>
+-				<manvolnum>8</manvolnum>,
+-			</citerefentry> for details.
+-		</para>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>Options</title>
+-
+-		<para>
+-			The following options are understood:
+-		</para>
+-
+-		<variablelist>
+-			<varlistentry>
+-				<term>
+-					<option>--phy=<replaceable>PHY</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Takes the MAC address or name of the physical layer
+-						that is used to create a virtual wireless interface.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-
+-			<varlistentry>
+-				<term>
+-					<option>--address=<replaceable>ADDRESS</replaceable></option>
+-				</term>
+-
+-				<listitem>
+-					<para>
+-						Define a MAC address that is used for the virtual
+-						wireless device. This parameter is optional and
+-						a random MAC address will be generated when omitted.
+-					</para>
+-				</listitem>
+-			</varlistentry>
+-		</variablelist>
+-	</refsect1>
+-
+-	<refsect1>
+-		<title>See Also</title>
+-
+-		<para>
+-			<citerefentry>
+-				<refentrytitle>network</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>,
+-			<citerefentry>
+-				<refentrytitle>network-zone</refentrytitle>
+-				<manvolnum>8</manvolnum>
+-			</citerefentry>
+-		</para>
+-	</refsect1>
+-</refentry>
+-- 
+2.39.2
+
diff --git a/network/patches/0091-man-Cleanup-XML-files.patch b/network/patches/0091-man-Cleanup-XML-files.patch
new file mode 100644
index 000000000..3cda095e2
--- /dev/null
+++ b/network/patches/0091-man-Cleanup-XML-files.patch
@@ -0,0 +1,43 @@
+From d746901bf5d4b4eb7591d1e009fad2960647e034 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 20:01:00 +0200
+Subject: [PATCH 091/304] man: Cleanup XML files
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/Makefile.am b/Makefile.am
+index 26f2e9c..d79b0f2 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -475,7 +475,8 @@ MANPAGES = \
+ 	man/network-zone-wireless.8
+ 
+ MANPAGES_TXT  = $(patsubst %.8,%.txt,$(MANPAGES))
+-MANPAGES_HTML = $(patsubst %.txt,%.html,$(MANPAGES))
++MANPAGES_HTML = $(patsubst %.txt,%.html,$(MANPAGES_TXT))
++MANPAGES_XML  = $(patsubst %.txt,%.xml,$(MANPAGES_TXT))
+ 
+ .PHONY: man
+ man: $(MANPAGES) $(MANPAGES_HTML)
+@@ -484,11 +485,13 @@ man_MANS = \
+ 	$(MANPAGES)
+ 
+ noinst_DATA += \
+-	$(MANPAGES_HTML)
++	$(MANPAGES_HTML) \
++	$(MANPAGES_XML)
+ 
+ CLEANFILES += \
+ 	$(man_MANS) \
+-	$(MANPAGES_HTML)
++	$(MANPAGES_HTML) \
++	$(MANPAGES_XML)
+ 
+ EXTRA_DIST += \
+ 	$(MANPAGES_TXT)
+-- 
+2.39.2
+
diff --git a/network/patches/0092-man-Make-distcheck-happy.patch b/network/patches/0092-man-Make-distcheck-happy.patch
new file mode 100644
index 000000000..18d763532
--- /dev/null
+++ b/network/patches/0092-man-Make-distcheck-happy.patch
@@ -0,0 +1,54 @@
+From 5d881996d1a5cf6211ae1fa0d4c4cd6fe6867f79 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 20:08:00 +0200
+Subject: [PATCH 092/304] man: Make distcheck happy
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am | 14 +++++++++-----
+ 1 file changed, 9 insertions(+), 5 deletions(-)
+
+diff --git a/Makefile.am b/Makefile.am
+index d79b0f2..0257b02 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -494,6 +494,7 @@ CLEANFILES += \
+ 	$(MANPAGES_XML)
+ 
+ EXTRA_DIST += \
++	man/asciidoc.conf \
+ 	$(MANPAGES_TXT)
+ 
+ XSLTPROC_FLAGS = \
+@@ -505,20 +506,23 @@ XSLTPROC_FLAGS = \
+ 	--stringparam man.copyright.section.enabled 1
+ 
+ XSLTPROC_COMMAND_MAN = \
+-	$(AM_V_XSLT)$(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) \
++	$(AM_V_XSLT)$(MKDIR_P) $(dir $@) && \
++	$(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) \
+ 		http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+ 
+ man/%.xml: man/%.txt man/asciidoc.conf
+-	$(AM_V_ASCIIDOC)$(ASCIIDOC) \
+-		-f man/asciidoc.conf \
++	$(AM_V_ASCIIDOC)$(MKDIR_P) $(dir $@) && \
++	$(ASCIIDOC) \
++		-f $(abs_srcdir)/man/asciidoc.conf \
+ 		-d manpage -b docbook -o $@ $<
+ 
+ man/%.8: man/%.xml
+ 	$(XSLTPROC_COMMAND_MAN)
+ 
+ man/%.html: man/%.txt man/asciidoc.conf
+-	$(AM_V_ASCIIDOC)$(ASCIIDOC) \
+-		-f man/asciidoc.conf \
++	$(AM_V_ASCIIDOC)$(MKDIR_P) $(dir $@) && \
++	$(ASCIIDOC) \
++		-f $(abs_srcdir)/man/asciidoc.conf \
+ 		-b html5 -a icons -a theme=flask -o $@ $<
+ 
+ # ------------------------------------------------------------------------------
+-- 
+2.39.2
+
diff --git a/network/patches/0093-man-Include-include-files-in-tarball.patch b/network/patches/0093-man-Include-include-files-in-tarball.patch
new file mode 100644
index 000000000..7270d85bc
--- /dev/null
+++ b/network/patches/0093-man-Include-include-files-in-tarball.patch
@@ -0,0 +1,28 @@
+From ff43523863b7ad7f50f5dfd4fdf80251ef01fa51 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 20:14:34 +0200
+Subject: [PATCH 093/304] man: Include include files in tarball
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/Makefile.am b/Makefile.am
+index 0257b02..6b77f0a 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -474,6 +474,10 @@ MANPAGES = \
+ 	man/network-zone-pppoe.8 \
+ 	man/network-zone-wireless.8
+ 
++EXTRA_DIST += \
++	man/include-color.txt \
++	man/include-description.txt
++
+ MANPAGES_TXT  = $(patsubst %.8,%.txt,$(MANPAGES))
+ MANPAGES_HTML = $(patsubst %.txt,%.html,$(MANPAGES_TXT))
+ MANPAGES_XML  = $(patsubst %.txt,%.xml,$(MANPAGES_TXT))
+-- 
+2.39.2
+
diff --git a/network/patches/0094-man-network-route-static-Fix-name.patch b/network/patches/0094-man-network-route-static-Fix-name.patch
new file mode 100644
index 000000000..c13af1a79
--- /dev/null
+++ b/network/patches/0094-man-network-route-static-Fix-name.patch
@@ -0,0 +1,26 @@
+From 71bdead694bdae2e40e8a9f99403b4ec2db77914 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 31 Mar 2019 20:17:09 +0200
+Subject: [PATCH 094/304] man: network-route-static: Fix name
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-route-static.txt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/man/network-route-static.txt b/man/network-route-static.txt
+index d4774b2..c6c3fea 100644
+--- a/man/network-route-static.txt
++++ b/man/network-route-static.txt
+@@ -2,7 +2,7 @@
+ Michael Tremer <michael.tremer@ipfire.org>
+ 
+ == NAME
+-network-route - Manage Static Routing
++network-route-static - Manage Static Routing
+ 
+ == SYNOPSIS
+ [verse]
+-- 
+2.39.2
+
diff --git a/network/patches/0095-Makefile-Add-target-to-upload-HTML-man-pages.patch b/network/patches/0095-Makefile-Add-target-to-upload-HTML-man-pages.patch
new file mode 100644
index 000000000..b6f262fdb
--- /dev/null
+++ b/network/patches/0095-Makefile-Add-target-to-upload-HTML-man-pages.patch
@@ -0,0 +1,28 @@
+From 2d2e96269516032b3bc4f2222067f6b82398a70a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 1 Apr 2019 12:31:53 +0200
+Subject: [PATCH 095/304] Makefile: Add target to upload HTML man pages
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/Makefile.am b/Makefile.am
+index 6b77f0a..955f2b7 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -529,6 +529,10 @@ man/%.html: man/%.txt man/asciidoc.conf
+ 		-f $(abs_srcdir)/man/asciidoc.conf \
+ 		-b html5 -a icons -a theme=flask -o $@ $<
+ 
++.PHONY: upload-man
++upload-man: $(MANPAGES_HTML)
++	rsync -avHz --delete --progress $(MANPAGES_HTML) ms@people.ipfire.org:/pub/man-pages/$(PACKAGE_NAME)/
++
+ # ------------------------------------------------------------------------------
+ 
+ substitutions = \
+-- 
+2.39.2
+
diff --git a/network/patches/0096-man-Do-not-generate-HTML-documentation-in-normal-bui.patch b/network/patches/0096-man-Do-not-generate-HTML-documentation-in-normal-bui.patch
new file mode 100644
index 000000000..b1c34b320
--- /dev/null
+++ b/network/patches/0096-man-Do-not-generate-HTML-documentation-in-normal-bui.patch
@@ -0,0 +1,29 @@
+From 95556ed6aa03a160df0ed6e929389c3d7283b87b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 1 Apr 2019 12:45:55 +0200
+Subject: [PATCH 096/304] man: Do not generate HTML documentation in normal
+ build
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am | 4 ----
+ 1 file changed, 4 deletions(-)
+
+diff --git a/Makefile.am b/Makefile.am
+index 955f2b7..4c26a9d 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -488,10 +488,6 @@ man: $(MANPAGES) $(MANPAGES_HTML)
+ man_MANS = \
+ 	$(MANPAGES)
+ 
+-noinst_DATA += \
+-	$(MANPAGES_HTML) \
+-	$(MANPAGES_XML)
+-
+ CLEANFILES += \
+ 	$(man_MANS) \
+ 	$(MANPAGES_HTML) \
+-- 
+2.39.2
+
diff --git a/network/patches/0097-man-Fix-authorship-warnings.patch b/network/patches/0097-man-Fix-authorship-warnings.patch
new file mode 100644
index 000000000..317a63569
--- /dev/null
+++ b/network/patches/0097-man-Fix-authorship-warnings.patch
@@ -0,0 +1,205 @@
+From 6b1e747472ac60192146fc5ddba12b4a5d021194 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 1 Apr 2019 12:47:02 +0200
+Subject: [PATCH 097/304] man: Fix authorship warnings
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-route-static.txt             | 4 +++-
+ man/network-settings.txt                 | 1 -
+ man/network-vpn-security-policies.txt    | 4 +++-
+ man/network-zone-bridge.txt              | 4 +++-
+ man/network-zone-config-pppoe-server.txt | 3 +++
+ man/network-zone-ip-tunnel.txt           | 4 +++-
+ man/network-zone-modem.txt               | 4 +++-
+ man/network-zone-pppoe.txt               | 4 +++-
+ man/network-zone-wireless.txt            | 4 +++-
+ man/network-zone.txt                     | 4 +++-
+ 10 files changed, 27 insertions(+), 9 deletions(-)
+
+diff --git a/man/network-route-static.txt b/man/network-route-static.txt
+index c6c3fea..4ba97eb 100644
+--- a/man/network-route-static.txt
++++ b/man/network-route-static.txt
+@@ -1,5 +1,4 @@
+ = network-route-static(8)
+-Michael Tremer <michael.tremer@ipfire.org>
+ 
+ == NAME
+ network-route-static - Manage Static Routing
+@@ -63,6 +62,9 @@ The following commands are understood:
+ 	Packets matching this kind of route are silently discarded.
+ 	There will be no ICMP message sent to the source and no packet be forwarded.
+ 
++== AUTHORS
++Michael Tremer
++
+ == SEE ALSO
+ link:network[8],
+ link:network-route[8],
+diff --git a/man/network-settings.txt b/man/network-settings.txt
+index e77f038..a1c1ae3 100644
+--- a/man/network-settings.txt
++++ b/man/network-settings.txt
+@@ -1,5 +1,4 @@
+ = network-settings(8)
+-Michael Tremer <michael.tremer@ipfire.org>
+ 
+ == NAME
+ network-settings - Change global network settings
+diff --git a/man/network-vpn-security-policies.txt b/man/network-vpn-security-policies.txt
+index f9dc91a..3c843d7 100644
+--- a/man/network-vpn-security-policies.txt
++++ b/man/network-vpn-security-policies.txt
+@@ -1,5 +1,4 @@
+ = network-vpn-security-policies(8)
+-Michael Tremer <michael.tremer@ipfire.org>
+ 
+ == NAME
+ network-vpn-security-policies - Configure VPN Security Policies
+@@ -106,6 +105,9 @@ They are intended to provide good defaults for various situations.
+ 
+ System policies cannot be deleted.
+ 
++== AUTHORS
++Michael Tremer
++
+ == SEE ALSO
+ link:network[8],
+ link:network-vpn[8]
+diff --git a/man/network-zone-bridge.txt b/man/network-zone-bridge.txt
+index 2e4f839..46c78a6 100644
+--- a/man/network-zone-bridge.txt
++++ b/man/network-zone-bridge.txt
+@@ -1,5 +1,4 @@
+ = network-zone-bridge(8)
+-Michael Tremer <michael.tremer@ipfire.org>
+ 
+ == NAME
+ network-zone-bridge - Manage network zones
+@@ -50,6 +49,9 @@ Spanning Tree Protocol (802.1D) configuration options:
+ 	The STP priority sets the ranking of this network device within the network.
+ 	The bridge with the best rank (0 is best) will become the root bridge.
+ 
++== AUTHORS
++Michael Tremer
++
+ == SEE ALSO
+ link:network[8],
+ link:network-zone[8]
+diff --git a/man/network-zone-config-pppoe-server.txt b/man/network-zone-config-pppoe-server.txt
+index 72dff8e..7d83bd1 100644
+--- a/man/network-zone-config-pppoe-server.txt
++++ b/man/network-zone-config-pppoe-server.txt
+@@ -44,6 +44,9 @@ This command creates a PPPoE server that will assign an IP address from the
+ 
+   network zone net0 config create pppoe-server --subnet=192.168.0.0/16
+ 
++== AUTHORS
++Michael Tremer
++
+ == SEE ALSO
+ link:network[8],
+ link:network-zone[8],
+diff --git a/man/network-zone-ip-tunnel.txt b/man/network-zone-ip-tunnel.txt
+index cb30731..8e2f30a 100644
+--- a/man/network-zone-ip-tunnel.txt
++++ b/man/network-zone-ip-tunnel.txt
+@@ -1,5 +1,4 @@
+ = network-zone-ip-tunnel(8)
+-Michael Tremer <michael.tremer@ipfire.org>
+ 
+ == NAME
+ network-zone-ip-tunnel - Manage IP Tunnels
+@@ -30,6 +29,9 @@ The following options are understood:
+ 	+
+ 	This is optional and if unset a useful default will be used.
+ 
++== AUTHORS
++Michael Tremer
++
+ == SEE ALSO
+ link:network[8],
+ link:network-zone[8]
+diff --git a/man/network-zone-modem.txt b/man/network-zone-modem.txt
+index 6b09622..082bb21 100644
+--- a/man/network-zone-modem.txt
++++ b/man/network-zone-modem.txt
+@@ -1,5 +1,4 @@
+ = network-zone-modem(8)
+-Michael Tremer <michael.tremer@ipfire.org>
+ 
+ == NAME
+ network-zone-modem - Configure serial modems
+@@ -61,6 +60,9 @@ The following options are understood:
+ 	* _Password Authentication Protocol_ (`pap`) sends the plaintext password
+ 	to the authentication server which is the reason why it should be avoided.
+ 
++== AUTHORS
++Michael Tremer
++
+ == SEE ALSO
+ link:network[8],
+ link:network-zone[8]
+diff --git a/man/network-zone-pppoe.txt b/man/network-zone-pppoe.txt
+index 93b55f2..1a1c4f3 100644
+--- a/man/network-zone-pppoe.txt
++++ b/man/network-zone-pppoe.txt
+@@ -1,5 +1,4 @@
+ = network-zone-pppoe(8)
+-Michael Tremer <michael.tremer@ipfire.org>
+ 
+ == NAME
+ network-zone-pppoe - PPP over Ethernet
+@@ -47,6 +46,9 @@ The following options are understood:
+ 	By this option, you may enable or disable the delegation through your
+ 	provider of one IPv6 prefix to your system.
+ 
++== AUTHORS
++Michael Tremer
++
+ == SEE ALSO
+ link:network[8],
+ link:network-zone[8]
+diff --git a/man/network-zone-wireless.txt b/man/network-zone-wireless.txt
+index 368ac2a..531f8ff 100644
+--- a/man/network-zone-wireless.txt
++++ b/man/network-zone-wireless.txt
+@@ -1,5 +1,4 @@
+ = network-zone-wireless(8)
+-Michael Tremer <michael.tremer@ipfire.org>
+ 
+ == NAME
+ network-zone-wireless - Wireless Networks
+@@ -27,6 +26,9 @@ The following options are understood:
+ 	parameter is optional and a random MAC address will be generated when
+ 	omitted.
+ 
++== AUTHORS
++Michael Tremer
++
+ == SEE ALSO
+ link:network[8],
+ link:network-zone[8]
+diff --git a/man/network-zone.txt b/man/network-zone.txt
+index 88a1988..2c2c6f0 100644
+--- a/man/network-zone.txt
++++ b/man/network-zone.txt
+@@ -1,5 +1,4 @@
+ = network-zone(8)
+-Michael Tremer <michael.tremer@ipfire.org>
+ 
+ == NAME
+ network-zone - Manage network zones
+@@ -69,5 +68,8 @@ include::include-description.txt[]
+ 	The command will shut down the zone if it is up and start it again with
+ 	the new name. If the zone is not up it won't be started.
+ 
++== AUTHORS
++Michael Tremer
++
+ == SEE ALSO
+ link:network[8]
+-- 
+2.39.2
+
diff --git a/network/patches/0098-man-Make-syntax-format-more-similar-across-files.patch b/network/patches/0098-man-Make-syntax-format-more-similar-across-files.patch
new file mode 100644
index 000000000..5752c844c
--- /dev/null
+++ b/network/patches/0098-man-Make-syntax-format-more-similar-across-files.patch
@@ -0,0 +1,583 @@
+From 39cfece88a2978f946e1713fbf1e2be3faf124d6 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 1 Apr 2019 19:49:01 +0200
+Subject: [PATCH 098/304] man: Make syntax format more similar across files
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/firewall-settings.txt          | 29 +++++------
+ man/network-device.txt             | 37 ++++++--------
+ man/network-dhcp.txt               | 35 ++++++--------
+ man/network-dns-server.txt         | 50 ++++++++-----------
+ man/network-performance-tuning.txt | 17 +++----
+ man/network-port.txt               | 77 ++++++++++++++----------------
+ man/network-quick-start.txt        | 15 ++----
+ man/network-route-static.txt       | 18 +++----
+ 8 files changed, 116 insertions(+), 162 deletions(-)
+
+diff --git a/man/firewall-settings.txt b/man/firewall-settings.txt
+index 20038e3..81c9bd9 100644
+--- a/man/firewall-settings.txt
++++ b/man/firewall-settings.txt
+@@ -1,30 +1,25 @@
+-firewall-settings(8)
+-====================
++= firewall-settings(8)
+ 
+-NAME
+-----
++== NAME
+ firewall-settings - Global firewall settings
+ 
+-SYNOPSIS
+---------
++== SYNOPSIS
+ [verse]
+-'firewall settings'
+-'firewall settings' KEY=VALUE ...
++`firewall settings`
++`firewall settings` KEY=VALUE ...
+ 
+-DESCRIPTION
+------------
++== DESCRIPTION
+ This command is used to set global firewall settings.
+ Please have a look at the individual man pages for more options.
+ 
+-COMMANDS
+---------
++== COMMANDS
+ If no argument is given, the configuration will be dumped to the console.
+ 
+ You may set a new value by adding the variable name and the new value to
+ the command line.
+ 
+-SETTINGS
+---------
++== SETTINGS
++
+ === CONNTRACK_MAX_CONNECTIONS = 16384
+ Limits the max. number of simultaneous connections.
+ 
+@@ -88,10 +83,8 @@ Some routers on the Internet still do not support ECN properly.
+ When this setting is disabled, ECN is only advertised
+ when asked for.
+ 
+-AUTHORS
+--------
++== AUTHORS
+ Michael Tremer
+ 
+-SEE ALSO
+---------
++== SEE ALSO
+ link:firewall[8]
+diff --git a/man/network-device.txt b/man/network-device.txt
+index 4f1c1b0..d70536e 100644
+--- a/man/network-device.txt
++++ b/man/network-device.txt
+@@ -1,61 +1,54 @@
+-network-device(8)
+-=================
++= network-device(8)
+ 
+-NAME
+-----
++== NAME
+ network-device - Controls network devices
+ 
+-SYNOPSIS
+---------
++== SYNOPSIS
+ [verse]
+-'network device' [<options>] <command> ...
++`network device` COMMAND ...
+ 
+-DESCRIPTION
+------------
+-The 'network device' command shows low-level status information
++== DESCRIPTION
++The `network device` command shows low-level status information
+ of network devices and other things.
+ 
+-COMMANDS
+---------
++== COMMANDS
+ The following commands are understood:
+ 
+-'list'::
++`list`::
+ 	This command shows a list of all device that are currently present
+ 	on this system. This includes PHYs and serial devices as well.
+ 
+-'DEVICE discover'::
++`DEVICE discover`::
+ 	Runs a discovery for many hooks on the given device.
+ 
+ 	This will check if the hook can find for example a DHCP server or
+ 	DSLAM and thus predict for what the device should be used.
+ 
+-'DEVICE identify'::
++`DEVICE identify`::
+ 	This command only works for Ethernet adapters and will make those
+ 	that support this feature flash for a few seconds.
+ 
+ 	It is handy to find the right device to put the cable in.
+ 
+-'DEVICE monitor'::
++`DEVICE monitor`::
+ 	This command creates a monitor interface for wireless modules.
+ 
+ 	An instance of link:tcpdump[8] will be started and show all
+ 	frames that are sent or received on the 802.11 layer (layer 2).
+ 
+-'DEVICE status'::
++`DEVICE status`::
+ 	This will show you very detailed information about the given device.
+ 
+-'DEVICE unlock'::
++`DEVICE unlock`::
+ 	This command will unlock the SIM card in a modem.
+ 	Only serial devices are supported which are the most 4G or 3G modems.
+ 
+ 	For the PIN or PUK code, the user will be prompted.
+ 
+-AUTHORS
+--------
++== AUTHORS
+ Michael Tremer
+ 
+-SEE ALSO
+---------
++== SEE ALSO
+ link:network[8]
+ link:network-port[8]
+ link:network-zone[8]
+diff --git a/man/network-dhcp.txt b/man/network-dhcp.txt
+index bcb768e..11e5fb4 100644
+--- a/man/network-dhcp.txt
++++ b/man/network-dhcp.txt
+@@ -1,44 +1,37 @@
+-network-dhcp(8)
+-===============
++= network-dhcp(8)
+ 
+-NAME
+-----
++== NAME
+ network-dhcp - Controls the DHCP Server
+ 
+-SYNOPSIS
+---------
++== SYNOPSIS
+ [verse]
+-'network dhcpv6' <command> ...
+-'network dhcpv4' <command> ...
++`network dhcpv6` COMMAND ...
++`network dhcpv4` COMMAND ...
+ 
+-DESCRIPTION
+------------
++== DESCRIPTION
+ With help of the DHCP commands it is possible to configure DHCP
+ servers for IPv6 and IPv4.
+ 
+-COMMANDS
+---------
++== COMMANDS
+ The following commands are understood:
+ 
+-'start'::
++`start`::
+ 	Starts the DHCP server.
+ 
+-'stop'::
++`stop`::
+ 	Stops the DHCP server.
+ 
+-'restart'::
++`restart`::
+ 	Restarts the DHCP server.
+ 
+-'reload'::
++`reload`::
+ 	Reload the DHCP server configuration.
+ 
+-'subnet ...'::
++`subnet ...`::
+ 	TODO
+ 
+-AUTHORS
+--------
++== AUTHORS
+ Michael Tremer
+ 
+-SEE ALSO
+---------
++== SEE ALSO
+ link:network[8]
+diff --git a/man/network-dns-server.txt b/man/network-dns-server.txt
+index bd01ca7..f5019ce 100644
+--- a/man/network-dns-server.txt
++++ b/man/network-dns-server.txt
+@@ -1,75 +1,67 @@
+-network-dns-server(8)
+-=====================
++= network-dns-server(8)
+ 
+-NAME
+-----
++== NAME
+ network-dns-server - Controls the DNS settings
+ 
+-SYNOPSIS
+---------
++== SYNOPSIS
+ [verse]
+-'network dns-server' add SERVER [PRIORITY]
+-'network dns-server' remove SERVER
+-'network dns-server' list
+-'network dns-server' update
++`network dns-server add` SERVER [PRIORITY]
++`network dns-server remove` SERVER
++`network dns-server list`
++`network dns-server update`
+ 
+-DESCRIPTION
+------------
++== DESCRIPTION
+ With this command, you will be able to configure the local DNS
+ configuration.
+ 
+ You may add and remove DNS servers as well as view the settings.
+ 
+-COMMANDS
+---------
++== COMMANDS
+ The following commands are understood:
+ 
+-'add' SERVER [PRIORITY]::
++`add SERVER [PRIORITY]`::
+ 	A new DNS server may be added to the list by the
+ 	'add' command.
+ 	A priority that will rank the server my optionally be given.
+-
++	+
+ 	NOTE: SERVER must be a valid IP address and PRIORITY
+ 	must be a positive number.
+ 	The smaller this number, the higher is is the rank of
+ 	the server.
+ 
+-'remove' SERVER::
++`remove SERVER`::
+ 	The given server will be removed from the list of DNS servers.
+ 
+-'list'::
++`list`::
+ 	Shows a list of all servers that are currently in use.
+ 
+-'update'::
++`update`::
+ 	This command will re-create the system's configuration
+ 	files. It should not be required to use this command
+ 	very often.
+ 
+-SETTINGS
+---------
++== SETTINGS
+ The following settings may be set using link:network-settings[8]:
+ 
+-'DNS_USE_LOCAL_RESOLVER = [true|false]'::
++`DNS_USE_LOCAL_RESOLVER = [true|false]`::
+ 	This option defines whether the local DNS resolver should
+ 	be used or not.
+-
++	+
+ 	Basically, the option adds localhost to the list of nameservers
+ 	in link:resolv.conf[5].
+ 
+-'DNS_SEARCH_DOMAINS ='::
++`DNS_SEARCH_DOMAINS =`::
+ 	This setting configures the search domains for DNS queries
+ 	made by the local system.
+ 
+-'DNS_RANDOMIZE = [true|false]'::
++`DNS_RANDOMIZE = [true|false]`::
+ 	This option will break the DNS server ranks and will query
+ 	them in a random order which is useful to load-balance
+ 	multiple DNS servers.
+ 
+-AUTHORS
+--------
++== AUTHORS
+ Michael Tremer
+ 
+-SEE ALSO
+---------
++== SEE ALSO
+ link:network[8],
+ link:network-settings[8]
+diff --git a/man/network-performance-tuning.txt b/man/network-performance-tuning.txt
+index 763ee21..4672bbc 100644
+--- a/man/network-performance-tuning.txt
++++ b/man/network-performance-tuning.txt
+@@ -1,12 +1,9 @@
+-network-performance-tuning(8)
+-=============================
++= network-performance-tuning(8)
+ 
+-NAME
+-----
++== NAME
+ network-performance-tuning - Performance Tuning for Networking
+ 
+-DESCRIPTION
+------------
++== DESCRIPTION
+ This page contains a summary of some performance tuning techniques
+ that this system is using.
+ 
+@@ -22,12 +19,10 @@ reducing network latency and quite possibly increasing throughput.
+ The algorithm is trying to balance all network controllers across
+ all processors.
+ 
+-See /proc/interrups for the distribution of interrupts. 
++See /proc/interrupts for the distribution of interrupts.
+ 
+-AUTHORS
+--------
++== AUTHORS
+ Michael Tremer
+ 
+-SEE ALSO
+---------
++== SEE ALSO
+ link:network[8]
+diff --git a/man/network-port.txt b/man/network-port.txt
+index 0c26f33..54cd58c 100644
+--- a/man/network-port.txt
++++ b/man/network-port.txt
+@@ -1,29 +1,25 @@
+-network-port(8)
+-===============
++= network-port(8)
+ 
+-NAME
+-----
++== NAME
+ network-port - Controls Network Ports
+ 
+-SYNOPSIS
+---------
++== SYNOPSIS
+ [verse]
+-'network port' new HOOK ...
+-'network port' destroy PORT
+-'network port' PORT color set <color>
+-'network port' PORT color reset
+-'network port' PORT create
+-'network port' PORT description edit
+-'network port' PORT description show
+-'network port' PORT down
+-'network port' PORT edit ...
+-'network port' PORT identify
+-'network port' PORT remove
+-'network port' PORT status
+-'network port' PORT up
+-
+-DESCRIPTION
+------------
++`network port new HOOK ...`
++`network port destroy PORT`
++`network port PORT color set COLOR`
++`network port PORT color reset`
++`network port PORT create`
++`network port PORT description edit`
++`network port PORT description show`
++`network port PORT down`
++`network port PORT edit ...`
++`network port PORT identify`
++`network port PORT remove`
++`network port PORT status`
++`network port PORT up`
++
++== DESCRIPTION
+ This command creates, deletes, changes and views the configuration
+ and status of ports.
+ 
+@@ -32,15 +28,14 @@ to an other network. It connects those and zones together.
+ The 'network device' command shows status information of network devices
+ and other things.
+ 
+-COMMANDS
+---------
++== COMMANDS
+ The following commands are understood:
+ 
+-'new' HOOK ...::
++`new HOOK ...`::
+ 	A new port may be created with this command.
+ 	HOOK must be a valid hook which may require more options.
+ 
+-'destroy' PORT::
++`destroy PORT`::
+ 	Destroys the port PORT.
+ 	The port is removed from any zones it is attached to and shut down.
+ 
+@@ -48,49 +43,47 @@ For all other commands, the name of the port needs to be passed first:
+ 
+ include::include-color.txt[]
+ 
+-'create'::
++`create`::
+ 	This will create devices for the existing port PORT.
+-
++	+
+ 	This does not create a new port. It will just create the (possibly
+ 	virtual) interface this port (i.e. create an interface for a WiFi
+ 	module or a VLAN device).
+-
+-	The interface is not brought up. Use the 'up' command to do that.
++	+
++	The interface is not brought up. Use the `up` command to do that.
+ 
+ include::include-description.txt[]
+ 
+-'down'::
++`down`::
+ 	Shuts down the port.
+ 
+-'edit'::
++`edit`::
+ 	This command can be used to alter the configuration of a port.
+ 	Consult the documentation of the port hook to find out what is supported.
+ 
+-'identify'::
++`identify`::
+ 	This command will make the port flash for a few seconds
+ 	so that you can identify the correct network adapters
+ 	in the system.
+-
++	+
+ 	This is not supported by all network adapters.
+ 
+-'remove'::
++`remove`::
+ 	This will remove an existing PORT.
+-
++	+
+ 	This does not destroy the port. It inverses the operation performed
+ 	by the 'create' command.
+ 
+-'status'::
++`status`::
+ 	This will show some detailed information about the status
+ 	of the specified port.
+ 
+-'up'::
++`up`::
+ 	Brings up the port. It has to be created first.
+ 
+-AUTHORS
+--------
++== AUTHORS
+ Michael Tremer
+ 
+-SEE ALSO
+---------
++== SEE ALSO
+ link:network[8],
+ link:network-zone[8]
+diff --git a/man/network-quick-start.txt b/man/network-quick-start.txt
+index 02ebfe0..1ab5866 100644
+--- a/man/network-quick-start.txt
++++ b/man/network-quick-start.txt
+@@ -1,12 +1,9 @@
+-network-quick-start(8)
+-======================
++= network-quick-start(8)
+ 
+-NAME
+-----
++== NAME
+ network-quick-start - Quick Start Guide for Networking
+ 
+-DESCRIPTION
+------------
++== DESCRIPTION
+ The link:network[8] is a very powerful command that allows you to configure
+ the entire networking stack.
+ Unfortunately that makes it quite complicated to use as well.
+@@ -80,12 +77,10 @@ The entire network can be restarted by running:
+ # network restart
+ ------------
+ 
+-AUTHORS
+--------
++== AUTHORS
+ Michael Tremer
+ 
+-SEE ALSO
+---------
++== SEE ALSO
+ link:network[8],
+ link:network-device[8],
+ link:network-port[8],
+diff --git a/man/network-route-static.txt b/man/network-route-static.txt
+index 4ba97eb..43a1277 100644
+--- a/man/network-route-static.txt
++++ b/man/network-route-static.txt
+@@ -5,10 +5,10 @@ network-route-static - Manage Static Routing
+ 
+ == SYNOPSIS
+ [verse]
+-'network route static' COMMAND ...
+-'network route static add' NETWORK [--gateway=GATEWAY,--unreachable,--prohibit,--blackhole] [--mtu=MTU]
+-'network route static remove' NETWORK
+-'network route static list' [--protocol=ipv6|ipv4]
++`network route static COMMAND ...`
++`network route static add NETWORK [--gateway=GATEWAY,--unreachable,--prohibit,--blackhole] [--mtu=MTU]`
++`network route static remove NETWORK`
++`network route static list` [--protocol=ipv6|ipv4]`
+ 
+ == DESCRIPTION
+ This command helps to manage routes.
+@@ -20,14 +20,14 @@ The following commands are understood:
+ 	A new route may be added by the 'add' command. It is required to pass a
+ 	valid network prefix NETWORK, which can be either IPv6 or IPv4.
+ 	+
+-	For unicast routes, the '--gateway=GATEWAY' option must be passed, where
++	For unicast routes, the `--gateway=GATEWAY` option must be passed, where
+ 	GATEWAY is a valid IP address of the same protocol type as the network
+ 	prefix is.
+ 	+
+-	Use '--unreachable', '--prohibit', '--blackhole' can be used to create of
++	Use `--unreachable`, `--prohibit`, `--blackhole` can be used to create of
+ 	that type. See ROUTE TYPES below for more information about these options.
+ 	+
+-	The optional '--mtu=MTU' parameter defines the MTU along the path to the
++	The optional `--mtu=MTU` parameter defines the MTU along the path to the
+ 	destination and must be an integer number. This will show you very
+ 	detailed information about the given device.
+ 
+@@ -39,7 +39,7 @@ The following commands are understood:
+ 'list'::
+ 	Shows a list of all configured routes.
+ 	+
+-	Output can be filtered by passing --protocol=[ipv6|ipv4].
++	Output can be filtered by passing `--protocol=[ipv6|ipv4]`.
+ 
+ == ROUTE TYPES
+ 
+@@ -47,7 +47,7 @@ The following commands are understood:
+ 'unicast'::
+ 	A unicast route is the most common route in routing tables. It is a route to
+ 	a destination network address, which describes the path to the destination.
+-	Use the '--gateway=GATEWAY' option to create such a route.
++	Use the `--gateway=GATEWAY` option to create such a route.
+ 
+ 'unreachable'::
+ 	When a route is determined and the routing decision process returns a
+-- 
+2.39.2
+
diff --git a/network/patches/0099-hooks-Add-overwritable-function-to-determine-the-por.patch b/network/patches/0099-hooks-Add-overwritable-function-to-determine-the-por.patch
new file mode 100644
index 000000000..09b95df3c
--- /dev/null
+++ b/network/patches/0099-hooks-Add-overwritable-function-to-determine-the-por.patch
@@ -0,0 +1,53 @@
+From d673165c5456e29013def29ed1fa9f202110665a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 11:50:13 +0200
+Subject: [PATCH 099/304] hooks: Add overwritable function to determine the
+ port name
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/header-port | 18 +++++++++++++-----
+ 1 file changed, 13 insertions(+), 5 deletions(-)
+
+diff --git a/src/header-port b/src/header-port
+index d75fdd8..7d0b272 100644
+--- a/src/header-port
++++ b/src/header-port
+@@ -68,6 +68,12 @@ hook_hotplug_rename_by_address() {
+ 	return ${EXIT_ERROR}
+ }
+ 
++# Returns the suggested name of the port
++hook_find_port_name() {
++	assert isset HOOK_PORT_PATTERN
++	port_find_free "${HOOK_PORT_PATTERN}"
++}
++
+ hook_default_new() {
+ 	local ${HOOK_SETTINGS[*]}
+ 
+@@ -78,14 +84,16 @@ hook_default_new() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
+-	assert isset HOOK_PORT_PATTERN
+-
+-	local port=$(port_find_free ${HOOK_PORT_PATTERN})
++	# Determine a name for this port
++	local port="$(hook_find_port_name)"
+ 	assert isset port
+ 
+-	port_settings_write "${port}" ${HOOK_SETTINGS[*]}
++	# Save settings
++	if ! port_settings_write "${port}" ${HOOK_SETTINGS[*]}; then
++		return ${EXIT_ERROR}
++	fi
+ 
+-	exit ${EXIT_OK}
++	return ${EXIT_OK}
+ }
+ 
+ hook_new() {
+-- 
+2.39.2
+
diff --git a/network/patches/0100-vlan-Convert-hook-to-use-parse_cmdline-function.patch b/network/patches/0100-vlan-Convert-hook-to-use-parse_cmdline-function.patch
new file mode 100644
index 000000000..3a6445824
--- /dev/null
+++ b/network/patches/0100-vlan-Convert-hook-to-use-parse_cmdline-function.patch
@@ -0,0 +1,76 @@
+From 96045e9c044a709407b40df4145011e335929a3e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 11:55:35 +0200
+Subject: [PATCH 100/304] vlan: Convert hook to use parse_cmdline function
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/vlan | 42 ++++++++----------------------------------
+ 1 file changed, 8 insertions(+), 34 deletions(-)
+
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index f511986..69f5144 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -51,7 +51,14 @@ hook_check_settings() {
+ 	done
+ }
+ 
+-hook_new() {
++hook_find_port_name() {
++	assert isset PARENT_DEVICE
++	assert isset TAG
++
++	print "${PARENT_DEVICE}${VLAN_PORT_INTERFIX}${TAG}"
++}
++
++hook_parse_cmdline() {
+ 	while [ $# -gt 0 ]; do
+ 		case "${1}" in
+ 			--parent-device=*)
+@@ -63,42 +70,9 @@ hook_new() {
+ 			--tag=*)
+ 				TAG=$(cli_get_val "${1}")
+ 				;;
+-			*)
+-				warning "Unknown argument '${1}'"
+-				;;
+-		esac
+-		shift
+-	done
+-
+-	local port="${PARENT_DEVICE}${VLAN_PORT_INTERFIX}${TAG}"
+-
+-	port_settings_write "${port}"
+-
+-	exit ${EXIT_OK}
+-}
+-
+-hook_edit() {
+-	local port=${1}
+-	assert isset port
+-	shift
+-
+-	port_settings_read "${port}"
+-
+-	while [ $# -gt 0 ]; do
+-		case "${1}" in
+-			--address=*)
+-				ADDRESS=$(cli_get_val "${1}")
+-				;;
+-			*)
+-				warning "Unknown argument '${1}'"
+-				;;
+ 		esac
+ 		shift
+ 	done
+-
+-	port_settings_write "${port}"
+-
+-	exit ${EXIT_OK}	
+ }
+ 
+ hook_create() {
+-- 
+2.39.2
+
diff --git a/network/patches/0101-vlan-Validate-and-always-set-MAC-address.patch b/network/patches/0101-vlan-Validate-and-always-set-MAC-address.patch
new file mode 100644
index 000000000..75a5310ee
--- /dev/null
+++ b/network/patches/0101-vlan-Validate-and-always-set-MAC-address.patch
@@ -0,0 +1,42 @@
+From abb655547c79f72b97451c02ba285b13c68e5a2a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 12:00:02 +0200
+Subject: [PATCH 101/304] vlan: Validate and always set MAC address
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/vlan | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index 69f5144..0147e54 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -66,6 +66,12 @@ hook_parse_cmdline() {
+ 				;;
+ 			--address=*)
+ 				ADDRESS=$(cli_get_val "${1}")
++
++				# Validate address
++				if ! mac_is_valid "${ADDRESS}"; then
++					error "Invalid MAC address given: ${ADDRESS}"
++					return ${EXIT_CONF_ERROR}
++				fi
+ 				;;
+ 			--tag=*)
+ 				TAG=$(cli_get_val "${1}")
+@@ -73,6 +79,11 @@ hook_parse_cmdline() {
+ 		esac
+ 		shift
+ 	done
++
++	# Generate a random MAC address if none given
++	if ! isset ADDRESS; then
++		ADDRESS="$(mac_generate)"
++	fi
+ }
+ 
+ hook_create() {
+-- 
+2.39.2
+
diff --git a/network/patches/0102-vlan-Fail-when-unknown-command-line-parameters-are-b.patch b/network/patches/0102-vlan-Fail-when-unknown-command-line-parameters-are-b.patch
new file mode 100644
index 000000000..28c850b34
--- /dev/null
+++ b/network/patches/0102-vlan-Fail-when-unknown-command-line-parameters-are-b.patch
@@ -0,0 +1,29 @@
+From 0cf39f2d5178f624161b8c4329140bd00b06019c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 12:02:15 +0200
+Subject: [PATCH 102/304] vlan: Fail when unknown command line parameters are
+ being passed
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/vlan | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index 0147e54..39dbfff 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -76,6 +76,10 @@ hook_parse_cmdline() {
+ 			--tag=*)
+ 				TAG=$(cli_get_val "${1}")
+ 				;;
++			-*)
++				error "Unknown argument '${1}'"
++				return ${EXIT_CONF_ERROR}
++				;;
+ 		esac
+ 		shift
+ 	done
+-- 
+2.39.2
+
diff --git a/network/patches/0103-vlan-Rename-PARENT_DEVICE-to-PARENT_PORT.patch b/network/patches/0103-vlan-Rename-PARENT_DEVICE-to-PARENT_PORT.patch
new file mode 100644
index 000000000..897e75be7
--- /dev/null
+++ b/network/patches/0103-vlan-Rename-PARENT_DEVICE-to-PARENT_PORT.patch
@@ -0,0 +1,102 @@
+From a2f35a67d83bd3a4a4438c2b7b8cbc2ee0002e38 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 12:08:05 +0200
+Subject: [PATCH 103/304] vlan: Rename PARENT_DEVICE to PARENT_PORT
+
+It technically is a port
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/vlan             | 26 ++++++++++++++++----------
+ test/nitsi/test/port-vlan/recipe |  2 +-
+ 2 files changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index 39dbfff..f19eda4 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -23,14 +23,14 @@
+ 
+ HOOK_SETTINGS=(
+ 	"ADDRESS"
+-	"PARENT_DEVICE"
++	"PARENT_PORT"
+ 	"TAG"
+ )
+ 
+-PORT_PARENTS_VAR="PARENT"
++PORT_PARENTS_VAR="PARENT_PORT"
+ 
+ hook_check_settings() {
+-	assert isset PARENT_DEVICE
++	assert isset PARENT_PORT
+ 	assert isinteger TAG
+ 
+ 	if isset ADDRESS; then
+@@ -52,18 +52,15 @@ hook_check_settings() {
+ }
+ 
+ hook_find_port_name() {
+-	assert isset PARENT_DEVICE
++	assert isset PARENT_PORT
+ 	assert isset TAG
+ 
+-	print "${PARENT_DEVICE}${VLAN_PORT_INTERFIX}${TAG}"
++	print "${PARENT_PORT}${VLAN_PORT_INTERFIX}${TAG}"
+ }
+ 
+ hook_parse_cmdline() {
+ 	while [ $# -gt 0 ]; do
+ 		case "${1}" in
+-			--parent-device=*)
+-				PARENT_DEVICE=$(cli_get_val "${1}")
+-				;;
+ 			--address=*)
+ 				ADDRESS=$(cli_get_val "${1}")
+ 
+@@ -73,10 +70,19 @@ hook_parse_cmdline() {
+ 					return ${EXIT_CONF_ERROR}
+ 				fi
+ 				;;
++			--port=*)
++				PARENT_PORT=$(cli_get_val "${1}")
++
++				# Check if PARENT_PORT exists
++				if ! port_exists "${PARENT_PORT}"; then
++					error "Port '${PARENT_PORT}' does not exist"
++					return ${EXIT_CONF_ERROR}
++				fi
++				;;
+ 			--tag=*)
+ 				TAG=$(cli_get_val "${1}")
+ 				;;
+-			-*)
++			*)
+ 				error "Unknown argument '${1}'"
+ 				return ${EXIT_CONF_ERROR}
+ 				;;
+@@ -100,7 +106,7 @@ hook_create() {
+ 	port_settings_read "${port}"
+ 
+ 	# Create the VLAN device
+-	vlan_create "${port}" "${PARENT_DEVICE}" "${TAG}" "${ADDRESS}"
++	vlan_create "${port}" "${PARENT_PORT}" "${TAG}" "${ADDRESS}"
+ 
+ 	exit ${EXIT_OK}
+ }
+diff --git a/test/nitsi/test/port-vlan/recipe b/test/nitsi/test/port-vlan/recipe
+index d41377b..7a99251 100644
+--- a/test/nitsi/test/port-vlan/recipe
++++ b/test/nitsi/test/port-vlan/recipe
+@@ -17,7 +17,7 @@ bob: network zone upl0 config new static 192.168.100.102/24
+ all: network status
+ 
+ # Create a vlan device with parent port attached to net1
+-all: network port new vlan --parent-device=${p_net1} --tag=42
++all: network port new vlan --port="${p_net1}" --tag=42
+ all: network zone upl0 port attach "${p_net1}v42"
+ 
+ # Test if the vlan works by pinging bob
+-- 
+2.39.2
+
diff --git a/network/patches/0104-vlan-Check-if-parent-device-exists-before-bringing-i.patch b/network/patches/0104-vlan-Check-if-parent-device-exists-before-bringing-i.patch
new file mode 100644
index 000000000..6f4ebeded
--- /dev/null
+++ b/network/patches/0104-vlan-Check-if-parent-device-exists-before-bringing-i.patch
@@ -0,0 +1,35 @@
+From 4776723194ad4d1ba75d1b373c1892e44ddcbf97 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 12:15:01 +0200
+Subject: [PATCH 104/304] vlan: Check if parent device exists before bringing
+ it up
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/vlan | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index f19eda4..98178e3 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -103,7 +103,15 @@ hook_create() {
+ 	device_exists "${port}" && exit ${EXIT_OK}
+ 
+ 	# Read configruation
+-	port_settings_read "${port}"
++	if ! port_settings_read "${port}"; then
++		return ${EXIT_ERROR}
++	fi
++
++	# Check if the parent port exists
++	if ! port_exists "${PARENT_PORT}"; then
++		error "Port '${PARENT_PORT}' does not exist"
++		return ${EXIT_ERROR}
++	fi
+ 
+ 	# Create the VLAN device
+ 	vlan_create "${port}" "${PARENT_PORT}" "${TAG}" "${ADDRESS}"
+-- 
+2.39.2
+
diff --git a/network/patches/0105-vlan-Simplify-vlan_remove.patch b/network/patches/0105-vlan-Simplify-vlan_remove.patch
new file mode 100644
index 000000000..b6d1d2bf1
--- /dev/null
+++ b/network/patches/0105-vlan-Simplify-vlan_remove.patch
@@ -0,0 +1,32 @@
+From 23ddd3765e344e06f379a5ccc5c2cfcbfca9c7b7 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 12:15:41 +0200
+Subject: [PATCH 105/304] vlan: Simplify vlan_remove()
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.vlan | 8 +-------
+ 1 file changed, 1 insertion(+), 7 deletions(-)
+
+diff --git a/src/functions/functions.vlan b/src/functions/functions.vlan
+index 97028b0..d83e3ad 100644
+--- a/src/functions/functions.vlan
++++ b/src/functions/functions.vlan
+@@ -88,13 +88,7 @@ vlan_create() {
+ }
+ 
+ vlan_remove() {
+-	local device=${1}
+-	assert isset device
+-
+-	# Set down device (if not already done).
+-	device_set_down ${device}
+-
+-	device_delete ${device}
++	device_delete "$@"
+ }
+ 
+ vlan_get_parent() {
+-- 
+2.39.2
+
diff --git a/network/patches/0106-vlan-Refactor-vlan_create.patch b/network/patches/0106-vlan-Refactor-vlan_create.patch
new file mode 100644
index 000000000..250739fda
--- /dev/null
+++ b/network/patches/0106-vlan-Refactor-vlan_create.patch
@@ -0,0 +1,146 @@
+From d3a0f73d7b2b6d4f634083f5620752e57a7a691b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 12:28:17 +0200
+Subject: [PATCH 106/304] vlan: Refactor vlan_create()
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.vlan | 81 +++++++++++++++++++++++-------------
+ src/hooks/ports/vlan         | 10 ++++-
+ 2 files changed, 60 insertions(+), 31 deletions(-)
+
+diff --git a/src/functions/functions.vlan b/src/functions/functions.vlan
+index d83e3ad..99a8baa 100644
+--- a/src/functions/functions.vlan
++++ b/src/functions/functions.vlan
+@@ -38,53 +38,76 @@ EOF
+ }
+ 
+ vlan_create() {
+-	local device=${1}
+-	assert isset device
++	local device="${1}"
++	shift
+ 
+-	local parent=${2}
+-	assert isset parent
++	assert isset device
+ 
+-	local tag=${3}
+-	assert isinteger tag
++	local address
++	local parent
++	local tag
++
++	# Parse command line arguments
++	while [ $# -gt 0 ]; do
++		case "${1}" in
++			--address=*)
++				address=$(cli_get_val "${1}")
++				;;
++			--parent=*)
++				parent=$(cli_get_val "${1}")
++				;;
++			--tag=*)
++				tag=$(cli_get_val "${1}")
++				;;
++			*)
++				error "Unrecognized argument: ${1}"
++				return ${EXIT_ERROR}
++				;;
++		esac
++		shift
++	done
++
++	# Generate a random MAC address if none was passed
++	if ! isset address; then
++		address="$(mac_generate)"
++	fi
+ 
+-	local address=${4}
+-	if isset address; then
+-		assert ismac address
++	# Check if address is valid
++	if ! ismac address; then
++		log ERROR "Invalid mac address: ${address}"
++		return ${EXIT_ERROR}
+ 	fi
+ 
+-	# Check if a device with the name does already exist.
+-	if device_exists ${device}; then
+-		log ERROR "device '${device}' does already exist"
++	# Check if a device with the name does already exist
++	if device_exists "${device}"; then
++		log ERROR "Device '${device}' already exists"
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
+-	# Check if the parent device exists.
+-	if ! device_exists ${parent}; then
+-		log ERROR "parent device '${parent}' does not exist"
++	# Check if the parent device exists
++	if ! device_exists "${parent}"; then
++		log ERROR "Parent device '${parent}' does not exist"
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
+ 	# Load ebtables stuff.
+ 	vlan_init
+ 
+-	local command="ip link add link ${parent} name ${device}"
++	# Make the command
++	local command=(
++		ip link add link "${parent}" name "${device}"
++			address "${address}" type vlan id "${tag}"
++	)
+ 
+-	if isset address; then
+-		command="${command} address ${address}"
++	# Run the command
++	if ! cmd_quiet "${command[*]}"; then
++		log ERROR "Could not create VLAN device ${device}: $?"
++		return ${EXIT_ERROR}
+ 	fi
+ 
+-	command="${command} type vlan id ${tag}"
+-
+-	cmd_quiet ${command}
+-	local ret=$?
+-
+-	if [ ${ret} -eq ${EXIT_OK} ]; then
+-		log DEBUG "vlan device '${device}' has been created"
+-	else
+-		log ERROR "could not create vlan device '${device}': ${ret}"
+-	fi
++	log DEBUG "Created VLAN device ${device} (parent = ${parent}, id = ${tag})"
+ 
+-	return ${ret}
++	return ${EXIT_OK}
+ }
+ 
+ vlan_remove() {
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index 98178e3..4715b1f 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -114,9 +114,15 @@ hook_create() {
+ 	fi
+ 
+ 	# Create the VLAN device
+-	vlan_create "${port}" "${PARENT_PORT}" "${TAG}" "${ADDRESS}"
++	if ! vlan_create "${port}" \
++			--address="${ADDRESS}" \
++			--parent="${PARENT_PORT}" \
++			--tag="${TAG}"; then
++		error "Could not create port: ${port}"
++		return ${EXIT_ERROR}
++	fi
+ 
+-	exit ${EXIT_OK}
++	return ${EXIT_OK}
+ }
+ 
+ hook_remove() {
+-- 
+2.39.2
+
diff --git a/network/patches/0107-vlan-Create-partent-port-if-necessary.patch b/network/patches/0107-vlan-Create-partent-port-if-necessary.patch
new file mode 100644
index 000000000..2201b68f2
--- /dev/null
+++ b/network/patches/0107-vlan-Create-partent-port-if-necessary.patch
@@ -0,0 +1,30 @@
+From 68cacd23226f401f1676e8bfc975467647cefef0 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 12:29:57 +0200
+Subject: [PATCH 107/304] vlan: Create partent port (if necessary)
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/ports/vlan | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index 4715b1f..384ad50 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -113,6 +113,12 @@ hook_create() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
++	# Create the partent port first
++	if ! port_create "${PARENT_PORT}"; then
++		error "Could not bring up parent port: ${PARENT_PORT}"
++		return ${EXIT_ERROR}
++	fi
++
+ 	# Create the VLAN device
+ 	if ! vlan_create "${port}" \
+ 			--address="${ADDRESS}" \
+-- 
+2.39.2
+
diff --git a/network/patches/0108-vlan-Drop-ebtables-stuff.patch b/network/patches/0108-vlan-Drop-ebtables-stuff.patch
new file mode 100644
index 000000000..4945f7dd2
--- /dev/null
+++ b/network/patches/0108-vlan-Drop-ebtables-stuff.patch
@@ -0,0 +1,49 @@
+From 5338fb1423a84f9faeb597a2b67606fff1f6d6ab Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 12:30:48 +0200
+Subject: [PATCH 108/304] vlan: Drop ebtables stuff
+
+We no longer have ebtables
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.vlan | 16 ----------------
+ 1 file changed, 16 deletions(-)
+
+diff --git a/src/functions/functions.vlan b/src/functions/functions.vlan
+index 99a8baa..c542bb5 100644
+--- a/src/functions/functions.vlan
++++ b/src/functions/functions.vlan
+@@ -24,19 +24,6 @@ PROC_NET_VLAN_CONFIG="${PROC_NET_VLAN}/config"
+ 
+ VLAN_PORT_INTERFIX="v"
+ 
+-vlan_init() {
+-	ebtables-restore <<EOF
+-*filter
+-:INPUT ACCEPT
+-:FORWARD ACCEPT
+-:OUTPUT ACCEPT
+-
+-*broute
+-:BROUTING ACCEPT
+--A BROUTING -p 802_1Q -j DROP
+-EOF
+-}
+-
+ vlan_create() {
+ 	local device="${1}"
+ 	shift
+@@ -90,9 +77,6 @@ vlan_create() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
+-	# Load ebtables stuff.
+-	vlan_init
+-
+ 	# Make the command
+ 	local command=(
+ 		ip link add link "${parent}" name "${device}"
+-- 
+2.39.2
+
diff --git a/network/patches/0109-vlan-Rename-tag-to-id.patch b/network/patches/0109-vlan-Rename-tag-to-id.patch
new file mode 100644
index 000000000..69998bca2
--- /dev/null
+++ b/network/patches/0109-vlan-Rename-tag-to-id.patch
@@ -0,0 +1,159 @@
+From f24529e498b1c3fe60196c34356e5b005a22ae4c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 12:34:49 +0200
+Subject: [PATCH 109/304] vlan: Rename tag to id
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.vlan     | 12 ++++++------
+ src/hooks/ports/vlan             | 26 +++++++++++++-------------
+ test/nitsi/test/port-vlan/recipe |  2 +-
+ 3 files changed, 20 insertions(+), 20 deletions(-)
+
+diff --git a/src/functions/functions.vlan b/src/functions/functions.vlan
+index c542bb5..9a70c95 100644
+--- a/src/functions/functions.vlan
++++ b/src/functions/functions.vlan
+@@ -31,8 +31,8 @@ vlan_create() {
+ 	assert isset device
+ 
+ 	local address
++	local id
+ 	local parent
+-	local tag
+ 
+ 	# Parse command line arguments
+ 	while [ $# -gt 0 ]; do
+@@ -40,12 +40,12 @@ vlan_create() {
+ 			--address=*)
+ 				address=$(cli_get_val "${1}")
+ 				;;
++			--id=*)
++				id=$(cli_get_val "${1}")
++				;;
+ 			--parent=*)
+ 				parent=$(cli_get_val "${1}")
+ 				;;
+-			--tag=*)
+-				tag=$(cli_get_val "${1}")
+-				;;
+ 			*)
+ 				error "Unrecognized argument: ${1}"
+ 				return ${EXIT_ERROR}
+@@ -80,7 +80,7 @@ vlan_create() {
+ 	# Make the command
+ 	local command=(
+ 		ip link add link "${parent}" name "${device}"
+-			address "${address}" type vlan id "${tag}"
++			address "${address}" type vlan id "${id}"
+ 	)
+ 
+ 	# Run the command
+@@ -89,7 +89,7 @@ vlan_create() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
+-	log DEBUG "Created VLAN device ${device} (parent = ${parent}, id = ${tag})"
++	log DEBUG "Created VLAN device ${device} (parent = ${parent}, id = ${id})"
+ 
+ 	return ${EXIT_OK}
+ }
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index 384ad50..97b6985 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -23,39 +23,39 @@
+ 
+ HOOK_SETTINGS=(
+ 	"ADDRESS"
++	"ID"
+ 	"PARENT_PORT"
+-	"TAG"
+ )
+ 
+ PORT_PARENTS_VAR="PARENT_PORT"
+ 
+ hook_check_settings() {
+ 	assert isset PARENT_PORT
+-	assert isinteger TAG
++	assert isinteger ID
+ 
+ 	if isset ADDRESS; then
+ 		assert ismac ADDRESS
+ 	fi
+ 
+-	if [ ${TAG} -gt 4096 ]; then
+-		error "TAG is greater than 4096."
++	if [ ${ID} -gt 4096 ]; then
++		error "ID is greater than 4096."
+ 		exit ${EXIT_ERROR}
+ 	fi
+ 
+ 	local reserved
+ 	for reserved in 0 4095; do
+-		if [ "${TAG}" = "${reserved}" ]; then
+-			error "TAG=${reserved} is reserved."
++		if [ "${ID}" = "${reserved}" ]; then
++			error "ID=${reserved} is reserved."
+ 			exit ${EXIT_ERROR}
+ 		fi
+ 	done
+ }
+ 
+ hook_find_port_name() {
++	assert isset ID
+ 	assert isset PARENT_PORT
+-	assert isset TAG
+ 
+-	print "${PARENT_PORT}${VLAN_PORT_INTERFIX}${TAG}"
++	print "${PARENT_PORT}${VLAN_PORT_INTERFIX}${ID}"
+ }
+ 
+ hook_parse_cmdline() {
+@@ -70,6 +70,9 @@ hook_parse_cmdline() {
+ 					return ${EXIT_CONF_ERROR}
+ 				fi
+ 				;;
++			--id=*)
++				ID=$(cli_get_val "${1}")
++				;;
+ 			--port=*)
+ 				PARENT_PORT=$(cli_get_val "${1}")
+ 
+@@ -79,9 +82,6 @@ hook_parse_cmdline() {
+ 					return ${EXIT_CONF_ERROR}
+ 				fi
+ 				;;
+-			--tag=*)
+-				TAG=$(cli_get_val "${1}")
+-				;;
+ 			*)
+ 				error "Unknown argument '${1}'"
+ 				return ${EXIT_CONF_ERROR}
+@@ -122,8 +122,8 @@ hook_create() {
+ 	# Create the VLAN device
+ 	if ! vlan_create "${port}" \
+ 			--address="${ADDRESS}" \
+-			--parent="${PARENT_PORT}" \
+-			--tag="${TAG}"; then
++			--id="${id}" \
++			--parent="${PARENT_PORT}"; then
+ 		error "Could not create port: ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+diff --git a/test/nitsi/test/port-vlan/recipe b/test/nitsi/test/port-vlan/recipe
+index 7a99251..2341e19 100644
+--- a/test/nitsi/test/port-vlan/recipe
++++ b/test/nitsi/test/port-vlan/recipe
+@@ -17,7 +17,7 @@ bob: network zone upl0 config new static 192.168.100.102/24
+ all: network status
+ 
+ # Create a vlan device with parent port attached to net1
+-all: network port new vlan --port="${p_net1}" --tag=42
++all: network port new vlan --port="${p_net1}" --id=42
+ all: network zone upl0 port attach "${p_net1}v42"
+ 
+ # Test if the vlan works by pinging bob
+-- 
+2.39.2
+
diff --git a/network/patches/0110-vlan-Validate-ID.patch b/network/patches/0110-vlan-Validate-ID.patch
new file mode 100644
index 000000000..f4abee55f
--- /dev/null
+++ b/network/patches/0110-vlan-Validate-ID.patch
@@ -0,0 +1,110 @@
+From fc1e91cca425c8e929df76dad4488066070879dd Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 12:41:36 +0200
+Subject: [PATCH 110/304] vlan: Validate ID
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.vlan | 25 ++++++++++++++++++++++++-
+ src/hooks/ports/vlan         | 26 +++++++++-----------------
+ 2 files changed, 33 insertions(+), 18 deletions(-)
+
+diff --git a/src/functions/functions.vlan b/src/functions/functions.vlan
+index 9a70c95..ee2fb34 100644
+--- a/src/functions/functions.vlan
++++ b/src/functions/functions.vlan
+@@ -24,6 +24,23 @@ PROC_NET_VLAN_CONFIG="${PROC_NET_VLAN}/config"
+ 
+ VLAN_PORT_INTERFIX="v"
+ 
++vlan_valid_id() {
++	local id="${1}"
++
++	# Must be an integer
++	if ! isinteger id; then
++		return ${EXIT_FALSE}
++	fi
++
++	# Must be between 1 and 4095
++	if [ ${id} -ge 1 ] && [ ${id} -le 4096 ]; then
++		return ${EXIT_TRUE}
++	fi
++
++	# Otherwise this is invalid
++	return ${EXIT_FALSE}
++}
++
+ vlan_create() {
+ 	local device="${1}"
+ 	shift
+@@ -31,7 +48,7 @@ vlan_create() {
+ 	assert isset device
+ 
+ 	local address
+-	local id
++	local id=1
+ 	local parent
+ 
+ 	# Parse command line arguments
+@@ -65,6 +82,12 @@ vlan_create() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
++	# Check VLAN ID
++	if ! vlan_valid_id "${id}"; then
++		log ERROR "Invalid VLAN ID: ${id}"
++		return ${EXIT_ERROR}
++	fi
++
+ 	# Check if a device with the name does already exist
+ 	if device_exists "${device}"; then
+ 		log ERROR "Device '${device}' already exists"
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index 97b6985..7f99dbc 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -30,25 +30,11 @@ HOOK_SETTINGS=(
+ PORT_PARENTS_VAR="PARENT_PORT"
+ 
+ hook_check_settings() {
++	assert ismac ADDRESS
+ 	assert isset PARENT_PORT
+-	assert isinteger ID
+-
+-	if isset ADDRESS; then
+-		assert ismac ADDRESS
+-	fi
+-
+-	if [ ${ID} -gt 4096 ]; then
+-		error "ID is greater than 4096."
+-		exit ${EXIT_ERROR}
+-	fi
+ 
+-	local reserved
+-	for reserved in 0 4095; do
+-		if [ "${ID}" = "${reserved}" ]; then
+-			error "ID=${reserved} is reserved."
+-			exit ${EXIT_ERROR}
+-		fi
+-	done
++	assert isinteger ID
++	assert vlan_valid_id "${ID}"
+ }
+ 
+ hook_find_port_name() {
+@@ -72,6 +58,12 @@ hook_parse_cmdline() {
+ 				;;
+ 			--id=*)
+ 				ID=$(cli_get_val "${1}")
++
++				# Validate VLAN ID
++				if ! vlan_valid_id "${ID}"; then
++					error "Invalid VLAN ID: ${ID}"
++					return ${EXIT_CONF_ERROR}
++				fi
+ 				;;
+ 			--port=*)
+ 				PARENT_PORT=$(cli_get_val "${1}")
+-- 
+2.39.2
+
diff --git a/network/patches/0111-util-Add-abort-which-will-stop-the-program-immediate.patch b/network/patches/0111-util-Add-abort-which-will-stop-the-program-immediate.patch
new file mode 100644
index 000000000..f14d8610a
--- /dev/null
+++ b/network/patches/0111-util-Add-abort-which-will-stop-the-program-immediate.patch
@@ -0,0 +1,48 @@
+From 9532462fe04658d728ecbf263b586111f73fe2b2 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 13:16:47 +0200
+Subject: [PATCH 111/304] util: Add abort() which will stop the program
+ immediately
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.util | 17 +++++++++++++++--
+ 1 file changed, 15 insertions(+), 2 deletions(-)
+
+diff --git a/src/functions/functions.util b/src/functions/functions.util
+index 7379a98..39ad861 100644
+--- a/src/functions/functions.util
++++ b/src/functions/functions.util
+@@ -469,14 +469,27 @@ assert() {
+ 	local assertion="$@"
+ 
+ 	if ! ${assertion}; then
+-		error_log "Assertion '${assertion}' failed."
+ 		backtrace
+-		exit ${EXIT_ERROR_ASSERT}
++
++		# End the program here
++		abort "Assertion failed: ${assertion}"
+ 	fi
+ 
+ 	return ${EXIT_OK}
+ }
+ 
++# Ends the program immediately without cleaning up
++abort() {
++	local msg="$@"
++
++	# Print message
++	if isset msg; then
++		log ERROR "${msg}"
++	fi
++
++	exit ${EXIT_ERROR_ASSERT}
++}
++
+ # This function checks, if the given argument is an assert error
+ # exit code. If this is the case, the script will halt immediately.
+ assert_check_retval() {
+-- 
+2.39.2
+
diff --git a/network/patches/0112-vlan-Add-support-for-802.1ad-QinQ.patch b/network/patches/0112-vlan-Add-support-for-802.1ad-QinQ.patch
new file mode 100644
index 000000000..62f77efca
--- /dev/null
+++ b/network/patches/0112-vlan-Add-support-for-802.1ad-QinQ.patch
@@ -0,0 +1,139 @@
+From 2eb7011cb5447f9568c8136940f59a047e1b8dae Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 3 Jun 2019 13:17:06 +0200
+Subject: [PATCH 112/304] vlan: Add support for 802.1ad (QinQ)
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.vlan | 24 +++++++++++++++++++++++-
+ src/hooks/ports/vlan         | 22 ++++++++++++++++++++--
+ 2 files changed, 43 insertions(+), 3 deletions(-)
+
+diff --git a/src/functions/functions.vlan b/src/functions/functions.vlan
+index ee2fb34..fbaa34f 100644
+--- a/src/functions/functions.vlan
++++ b/src/functions/functions.vlan
+@@ -22,6 +22,11 @@
+ PROC_NET_VLAN="/proc/net/vlan"
+ PROC_NET_VLAN_CONFIG="${PROC_NET_VLAN}/config"
+ 
++VLAN_SUPPORTED_PROTOCOLS=(
++	"802.1Q"	# default
++	"802.1ad"
++)
++
+ VLAN_PORT_INTERFIX="v"
+ 
+ vlan_valid_id() {
+@@ -41,6 +46,13 @@ vlan_valid_id() {
+ 	return ${EXIT_FALSE}
+ }
+ 
++vlan_supported_protocol() {
++	local proto="${1}"
++	assert isset proto
++
++	list_match "${proto}" "${VLAN_SUPPORTED_PROTOCOLS[@]}"
++}
++
+ vlan_create() {
+ 	local device="${1}"
+ 	shift
+@@ -50,6 +62,7 @@ vlan_create() {
+ 	local address
+ 	local id=1
+ 	local parent
++	local protocol="${VLAN_SUPPORTED_PROTOCOLS[0]}"
+ 
+ 	# Parse command line arguments
+ 	while [ $# -gt 0 ]; do
+@@ -63,6 +76,9 @@ vlan_create() {
+ 			--parent=*)
+ 				parent=$(cli_get_val "${1}")
+ 				;;
++			--protocol=*)
++				protocol=$(cli_get_val "${1}")
++				;;
+ 			*)
+ 				error "Unrecognized argument: ${1}"
+ 				return ${EXIT_ERROR}
+@@ -82,6 +98,12 @@ vlan_create() {
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
++	# Check protocol
++	if ! vlan_supported_protocol "${protocol}"; then
++		log ERROR "Invalid protocol: ${protocol}"
++		return ${EXIT_ERROR}
++	fi
++
+ 	# Check VLAN ID
+ 	if ! vlan_valid_id "${id}"; then
+ 		log ERROR "Invalid VLAN ID: ${id}"
+@@ -103,7 +125,7 @@ vlan_create() {
+ 	# Make the command
+ 	local command=(
+ 		ip link add link "${parent}" name "${device}"
+-			address "${address}" type vlan id "${id}"
++			address "${address}" type vlan proto "${protocol}" id "${id}"
+ 	)
+ 
+ 	# Run the command
+diff --git a/src/hooks/ports/vlan b/src/hooks/ports/vlan
+index 7f99dbc..af563ee 100644
+--- a/src/hooks/ports/vlan
++++ b/src/hooks/ports/vlan
+@@ -25,14 +25,21 @@ HOOK_SETTINGS=(
+ 	"ADDRESS"
+ 	"ID"
+ 	"PARENT_PORT"
++	"PROTOCOL"
+ )
+ 
++# Set the default to 802.1Q
++DEFAULT_PROTOCOL="${VLAN_SUPPORTED_PROTOCOLS[0]}"
++
+ PORT_PARENTS_VAR="PARENT_PORT"
+ 
+ hook_check_settings() {
+ 	assert ismac ADDRESS
+ 	assert isset PARENT_PORT
+ 
++	assert isset PROTOCOL
++	assert vlan_supported_protocol "${PROTOCOL}"
++
+ 	assert isinteger ID
+ 	assert vlan_valid_id "${ID}"
+ }
+@@ -74,6 +81,16 @@ hook_parse_cmdline() {
+ 					return ${EXIT_CONF_ERROR}
+ 				fi
+ 				;;
++			--protocol=*)
++				PROTOCOL="$(cli_get_val "${1}")"
++
++				# Check if PROTOCOL is supported
++				if ! vlan_supported_protocol "${PROTOCOL}"; then
++					error "Protocol '${PROTOCOL}' is not supported"
++					error "Choose one of ${VLAN_SUPPORTED_PROTOCOLS[*]}"
++					return ${EXIT_CONF_ERROR}
++				fi
++				;;
+ 			*)
+ 				error "Unknown argument '${1}'"
+ 				return ${EXIT_CONF_ERROR}
+@@ -114,8 +131,9 @@ hook_create() {
+ 	# Create the VLAN device
+ 	if ! vlan_create "${port}" \
+ 			--address="${ADDRESS}" \
+-			--id="${id}" \
+-			--parent="${PARENT_PORT}"; then
++			--id="${ID}" \
++			--parent="${PARENT_PORT}" \
++			--protocol="${PROTOCOL}"; then
+ 		error "Could not create port: ${port}"
+ 		return ${EXIT_ERROR}
+ 	fi
+-- 
+2.39.2
+
diff --git a/network/patches/0113-Do-not-try-to-start-Bird-during-boot-process.patch b/network/patches/0113-Do-not-try-to-start-Bird-during-boot-process.patch
new file mode 100644
index 000000000..7e25e5d9f
--- /dev/null
+++ b/network/patches/0113-Do-not-try-to-start-Bird-during-boot-process.patch
@@ -0,0 +1,57 @@
+From ecc7067479d165f4178f04248d86898cf50e3d95 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 20 Jun 2019 23:09:01 +0200
+Subject: [PATCH 113/304] Do not try to start Bird during boot process
+
+We should not do this in the network script and let just
+systemd take care of this. Otherwise we would end up in
+an infinite loop during the boot process.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.bird | 14 --------------
+ src/network                  |  3 ---
+ 2 files changed, 17 deletions(-)
+
+diff --git a/src/functions/functions.bird b/src/functions/functions.bird
+index 1bbac8c..55d43b5 100644
+--- a/src/functions/functions.bird
++++ b/src/functions/functions.bird
+@@ -33,20 +33,6 @@ bird_reload() {
+ 	service_reload "bird.service"
+ }
+ 
+-bird_enable() {
+-	# Generate configuration file
+-	if ! bird_generate_config; then
+-		log ERROR "Could not write Bird configuration"
+-		return ${EXIT_ERROR}
+-	fi
+-
+-	# Enable the service to be automatically started next time
+-	service_enable "bird.service"
+-
+-	# Start it now
+-	bird_start
+-}
+-
+ # Update configuration any apply it in one go
+ bird_update() {
+ 	if ! bird_generate_config; then
+diff --git a/src/network b/src/network
+index be06d8a..30f87a0 100644
+--- a/src/network
++++ b/src/network
+@@ -1381,9 +1381,6 @@ case "${action}" in
+ 		# Update resolv.conf(5) when initializing the network
+ 		dns_generate_resolvconf
+ 
+-		# Make sure bird is running
+-		bird_enable
+-
+ 		# Also execute all triggers
+ 		triggers_execute_all "init"
+ 		;;
+-- 
+2.39.2
+
diff --git a/network/patches/0114-configure-Break-when-asciidoc-cannot-be-found.patch b/network/patches/0114-configure-Break-when-asciidoc-cannot-be-found.patch
new file mode 100644
index 000000000..1fc0c289d
--- /dev/null
+++ b/network/patches/0114-configure-Break-when-asciidoc-cannot-be-found.patch
@@ -0,0 +1,27 @@
+From 9665b7963d263fd83ac132a84a3809fc6a03287a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 21 Jun 2019 06:30:44 +0100
+Subject: [PATCH 114/304] configure: Break when asciidoc cannot be found
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ configure.ac | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/configure.ac b/configure.ac
+index 117850f..340cfd6 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -144,6 +144,9 @@ PKG_CHECK_MODULES([LIBNL], [libnl-3.0 libnl-genl-3.0])
+ # ------------------------------------------------------------------------------
+ 
+ AC_CHECK_PROGS(ASCIIDOC, [asciidoc])
++if test -z "${ASCIIDOC}"; then
++	AC_MSG_ERROR([Required program 'asciidoc' not found])
++fi
+ 
+ # ------------------------------------------------------------------------------
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0115-Fix-creating-new-configs.patch b/network/patches/0115-Fix-creating-new-configs.patch
new file mode 100644
index 000000000..6f1c964e4
--- /dev/null
+++ b/network/patches/0115-Fix-creating-new-configs.patch
@@ -0,0 +1,29 @@
+From f1081966991d55ccd182b45f58fc0fde31437f77 Mon Sep 17 00:00:00 2001
+From: Stefan Schantl <stefan.schantl@ipfire.org>
+Date: Sat, 22 Jun 2019 09:52:37 +0000
+Subject: [PATCH 115/304] Fix creating new configs
+
+The id argument was missing for the zone_config_settings_write.
+
+Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/header-config | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/header-config b/src/header-config
+index c6a775c..ed647cd 100644
+--- a/src/header-config
++++ b/src/header-config
+@@ -45,7 +45,7 @@ hook_new() {
+ 	fi
+ 
+ 	# Write configuration to disk
+-	if ! zone_config_settings_write "${zone}" "${HOOK}"; then
++	if ! zone_config_settings_write "${zone}" "${HOOK}" "${id}"; then
+ 		return ${EXIT_ERROR}
+ 	fi
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0116-inetcalc-Fix-compiler-warnings.patch b/network/patches/0116-inetcalc-Fix-compiler-warnings.patch
new file mode 100644
index 000000000..9b6dca279
--- /dev/null
+++ b/network/patches/0116-inetcalc-Fix-compiler-warnings.patch
@@ -0,0 +1,30 @@
+From d07532fad069c51d188ba7b93539488499d5dbf9 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 22 Jun 2019 13:31:03 +0000
+Subject: [PATCH 116/304] inetcalc: Fix compiler warnings
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/inetcalc.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/src/inetcalc.c b/src/inetcalc.c
+index 1841c84..7c072e9 100644
+--- a/src/inetcalc.c
++++ b/src/inetcalc.c
+@@ -134,9 +134,9 @@ static int default_prefix(const int family) {
+ static int ip_address_parse_simple(ip_address_t* ip, const int family, const char* address) {
+ 	assert(family == AF_INET || family == AF_INET6);
+ 
+-	size_t address_length = strlen(address);
+-	char buffer[address_length + 1];
+-	strncpy(buffer, address, sizeof(buffer));
++	// Copy input to stack
++	char buffer[512];
++	strncpy(buffer, address, sizeof(buffer) - 1);
+ 
+ 	// Search for a prefix or subnet mask
+ 	char* prefix = strchr(buffer, '/');
+-- 
+2.39.2
+
diff --git a/network/patches/0117-firewall-Drop-separate-scripts-for-IPv6-and-IPv4.patch b/network/patches/0117-firewall-Drop-separate-scripts-for-IPv6-and-IPv4.patch
new file mode 100644
index 000000000..bb9f90e7a
--- /dev/null
+++ b/network/patches/0117-firewall-Drop-separate-scripts-for-IPv6-and-IPv4.patch
@@ -0,0 +1,70 @@
+From 0c5d22de5c22c9264dcb839df72440a1d11faa0c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 21 Jun 2019 06:34:23 +0100
+Subject: [PATCH 117/304] firewall: Drop separate scripts for IPv6 and IPv4
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                 |  3 +--
+ src/{firewall6 => firewall} |  0
+ src/firewall4               | 29 -----------------------------
+ 3 files changed, 1 insertion(+), 31 deletions(-)
+ rename src/{firewall6 => firewall} (100%)
+ delete mode 100644 src/firewall4
+
+diff --git a/Makefile.am b/Makefile.am
+index 4c26a9d..a36a4ab 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -115,8 +115,7 @@ dist_doc_DATA = \
+ dist_sbin_SCRIPTS = \
+ 	src/dhclient-script \
+ 	src/firewall-config \
+-	src/firewall4 \
+-	src/firewall6 \
++	src/firewall \
+ 	src/network
+ 
+ network_DATA = \
+diff --git a/src/firewall6 b/src/firewall
+similarity index 100%
+rename from src/firewall6
+rename to src/firewall
+diff --git a/src/firewall4 b/src/firewall4
+deleted file mode 100644
+index 55eed2c..0000000
+--- a/src/firewall4
++++ /dev/null
+@@ -1,29 +0,0 @@
+-#!/bin/bash
+-###############################################################################
+-#                                                                             #
+-# IPFire.org - A linux based firewall                                         #
+-# Copyright (C) 2012  IPFire Network Development Team                         #
+-#                                                                             #
+-# This program is free software: you can redistribute it and/or modify        #
+-# it under the terms of the GNU General Public License as published by        #
+-# the Free Software Foundation, either version 3 of the License, or           #
+-# (at your option) any later version.                                         #
+-#                                                                             #
+-# This program is distributed in the hope that it will be useful,             #
+-# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+-# GNU General Public License for more details.                                #
+-#                                                                             #
+-# You should have received a copy of the GNU General Public License           #
+-# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+-#                                                                             #
+-###############################################################################
+-
+-. /usr/lib/network/functions
+-
+-# Read firewall settings
+-firewall_settings_read
+-
+-firewall_cli "ipv4" "$@"
+-
+-exit ${EXIT_ERROR}
+-- 
+2.39.2
+
diff --git a/network/patches/0118-systemd-Remove-double-firewall-scripts.patch b/network/patches/0118-systemd-Remove-double-firewall-scripts.patch
new file mode 100644
index 000000000..d9090af5f
--- /dev/null
+++ b/network/patches/0118-systemd-Remove-double-firewall-scripts.patch
@@ -0,0 +1,76 @@
+From 67131768c979c66ad3717e46cb81a068b14eafee Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 22 Jun 2019 13:43:04 +0000
+Subject: [PATCH 118/304] systemd: Remove double firewall scripts
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                                        |  3 +--
+ .../{firewall4.service.in => firewall.service}     |  6 +++---
+ src/systemd/firewall6.service.in                   | 14 --------------
+ 3 files changed, 4 insertions(+), 19 deletions(-)
+ rename src/systemd/{firewall4.service.in => firewall.service} (62%)
+ delete mode 100644 src/systemd/firewall6.service.in
+
+diff --git a/Makefile.am b/Makefile.am
+index a36a4ab..81cf50d 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -363,8 +363,7 @@ UNINSTALL_EXEC_HOOKS += ppp-uninstall-hook
+ 
+ if HAVE_SYSTEMD
+ systemdsystemunit_DATA = \
+-	src/systemd/firewall4.service \
+-	src/systemd/firewall6.service \
++	src/systemd/firewall.service \
+ 	src/systemd/firewall-init.service \
+ 	src/systemd/network-init.service \
+ 	src/systemd/network@.service
+diff --git a/src/systemd/firewall4.service.in b/src/systemd/firewall.service
+similarity index 62%
+rename from src/systemd/firewall4.service.in
+rename to src/systemd/firewall.service
+index 568f5e7..34797e5 100644
+--- a/src/systemd/firewall4.service.in
++++ b/src/systemd/firewall.service
+@@ -1,5 +1,5 @@
+ [Unit]
+-Description=Firewall for IPv4
++Description=Firewall for IPFire
+ After=firewall-init.service
+ Before=network.target
+ Requires=firewall-init.service
+@@ -7,8 +7,8 @@ Requires=firewall-init.service
+ [Service]
+ Type=oneshot
+ RemainAfterExit=yes
+-ExecStart=@sbindir@/firewall4 start
+-ExecStop=@sbindir@/firewall4 stop
++ExecStart=@sbindir@/firewall start
++ExecStop=@sbindir@/firewall stop
+ 
+ [Install]
+ WantedBy=multi-user.target
+diff --git a/src/systemd/firewall6.service.in b/src/systemd/firewall6.service.in
+deleted file mode 100644
+index 873bfe6..0000000
+--- a/src/systemd/firewall6.service.in
++++ /dev/null
+@@ -1,14 +0,0 @@
+-[Unit]
+-Description=Firewall for IPv6
+-After=firewall-init.service
+-Before=network.target
+-Requires=firewall-init.service
+-
+-[Service]
+-Type=oneshot
+-RemainAfterExit=yes
+-ExecStart=@sbindir@/firewall6 start
+-ExecStop=@sbindir@/firewall6 stop
+-
+-[Install]
+-WantedBy=multi-user.target
+-- 
+2.39.2
+
diff --git a/network/patches/0119-firewall-Add-init-action-to-main-script.patch b/network/patches/0119-firewall-Add-init-action-to-main-script.patch
new file mode 100644
index 000000000..82a8ae26e
--- /dev/null
+++ b/network/patches/0119-firewall-Add-init-action-to-main-script.patch
@@ -0,0 +1,54 @@
+From 3e446cf0bff8c1dc409479bf02b0fc8912847c13 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 22 Jun 2019 13:50:00 +0000
+Subject: [PATCH 119/304] firewall: Add init action to main script
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/firewall | 26 +++++++++++++++++++++++++-
+ 1 file changed, 25 insertions(+), 1 deletion(-)
+
+diff --git a/src/firewall b/src/firewall
+index db7284c..c47ac61 100644
+--- a/src/firewall
++++ b/src/firewall
+@@ -19,11 +19,35 @@
+ #                                                                             #
+ ###############################################################################
+ 
++# Parse the command line
++while [ $# -gt 0 ]; do
++	case "${1}" in
++		-d|--debug)
++			DEBUG=1
++			;;
++		*)
++			action=${1}
++			;;
++	esac
++	shift
++	[ -n "${action}" ] && break
++done
++
+ . /usr/lib/network/functions
+ 
+ # Read firewall settings
+ firewall_settings_read
+ 
+-firewall_cli "ipv6" "$@"
++case "${action}" in
++	# Initialise kernel with firewall settings
++	init)
++		firewall_kernel_init
++		exit $?
++		;;
++
++	*)
++		firewall_cli "ipv6" "${action}" "$@"
++		;;
++esac
+ 
+ exit ${EXIT_ERROR}
+-- 
+2.39.2
+
diff --git a/network/patches/0120-firewall-Drop-initialisation-helper-script.patch b/network/patches/0120-firewall-Drop-initialisation-helper-script.patch
new file mode 100644
index 000000000..f77ad79db
--- /dev/null
+++ b/network/patches/0120-firewall-Drop-initialisation-helper-script.patch
@@ -0,0 +1,79 @@
+From 19b14da45fb83638878b14e77303194733679bc1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 22 Jun 2019 13:52:09 +0000
+Subject: [PATCH 120/304] firewall: Drop initialisation helper script
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                          |  1 -
+ src/helpers/firewall-kernel-init     | 30 ----------------------------
+ src/systemd/firewall-init.service.in |  4 ++--
+ 3 files changed, 2 insertions(+), 33 deletions(-)
+ delete mode 100644 src/helpers/firewall-kernel-init
+
+diff --git a/Makefile.am b/Makefile.am
+index 81cf50d..0974ba8 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -206,7 +206,6 @@ dist_network_SCRIPTS = \
+ 
+ dist_helpers_SCRIPTS = \
+ 	src/helpers/dhcpd-config-helper \
+-	src/helpers/firewall-kernel-init \
+ 	src/helpers/hostapd-config-helper \
+ 	src/helpers/ipsec-updown \
+ 	src/helpers/pppd-angel \
+diff --git a/src/helpers/firewall-kernel-init b/src/helpers/firewall-kernel-init
+deleted file mode 100644
+index aea82c4..0000000
+--- a/src/helpers/firewall-kernel-init
++++ /dev/null
+@@ -1,30 +0,0 @@
+-#!/bin/bash
+-###############################################################################
+-#                                                                             #
+-# IPFire.org - A linux based firewall                                         #
+-# Copyright (C) 2012  IPFire Network Development Team                         #
+-#                                                                             #
+-# This program is free software: you can redistribute it and/or modify        #
+-# it under the terms of the GNU General Public License as published by        #
+-# the Free Software Foundation, either version 3 of the License, or           #
+-# (at your option) any later version.                                         #
+-#                                                                             #
+-# This program is distributed in the hope that it will be useful,             #
+-# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+-# GNU General Public License for more details.                                #
+-#                                                                             #
+-# You should have received a copy of the GNU General Public License           #
+-# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+-#                                                                             #
+-###############################################################################
+-
+-. /usr/lib/network/functions
+-
+-# Read firewall settings
+-firewall_setttings_read
+-
+-# Initialize kernel parameters for the firewall.
+-firewall_kernel_init
+-
+-exit ${EXIT_OK}
+diff --git a/src/systemd/firewall-init.service.in b/src/systemd/firewall-init.service.in
+index 24497e9..91dd058 100644
+--- a/src/systemd/firewall-init.service.in
++++ b/src/systemd/firewall-init.service.in
+@@ -1,8 +1,8 @@
+ [Unit]
+-Description=Initialize kernel parameters for the firewalls
++Description=Initialize kernel parameters for the firewall
+ Before=network.target
+ 
+ [Service]
+ Type=oneshot
+ RemainAfterExit=yes
+-ExecStart=@helpersdir@/firewall-kernel-init
++ExecStart=@sbindir@/firewall init
+-- 
+2.39.2
+
diff --git a/network/patches/0121-Revert-firewall-Disable-PMTU-by-default.patch b/network/patches/0121-Revert-firewall-Disable-PMTU-by-default.patch
new file mode 100644
index 000000000..01c70c7be
--- /dev/null
+++ b/network/patches/0121-Revert-firewall-Disable-PMTU-by-default.patch
@@ -0,0 +1,28 @@
+From 70c56486267789a3767e22833548694a9b69e1b8 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 22 Jun 2019 13:55:00 +0000
+Subject: [PATCH 121/304] Revert "firewall: Disable PMTU by default"
+
+This reverts commit b3a66a5c00bc4e39ce0db34e2ac96c4911b4e31a.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.constants-firewall | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.constants-firewall b/src/functions/functions.constants-firewall
+index 4f7f503..d42189a 100644
+--- a/src/functions/functions.constants-firewall
++++ b/src/functions/functions.constants-firewall
+@@ -78,7 +78,7 @@ FIREWALL_USE_ECN="true"
+ FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_USE_ECN"
+ 
+ # Path MTU discovery
+-FIREWALL_PMTU_DISCOVERY="false"
++FIREWALL_PMTU_DISCOVERY="true"
+ FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_PMTU_DISCOVERY"
+ 
+ # Default TTL
+-- 
+2.39.2
+
diff --git a/network/patches/0122-firewall-Fix-reading-writing-settings.patch b/network/patches/0122-firewall-Fix-reading-writing-settings.patch
new file mode 100644
index 000000000..d0cd4b263
--- /dev/null
+++ b/network/patches/0122-firewall-Fix-reading-writing-settings.patch
@@ -0,0 +1,147 @@
+From c69adafd8ad8abf4f14b6fe110bbd8efb5eca596 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 22 Jun 2019 14:11:15 +0000
+Subject: [PATCH 122/304] firewall: Fix reading/writing settings
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.constants-firewall | 40 ++++++++++++----------
+ src/functions/functions.settings           |  6 ++--
+ 2 files changed, 25 insertions(+), 21 deletions(-)
+
+diff --git a/src/functions/functions.constants-firewall b/src/functions/functions.constants-firewall
+index d42189a..2ca9390 100644
+--- a/src/functions/functions.constants-firewall
++++ b/src/functions/functions.constants-firewall
+@@ -19,6 +19,10 @@
+ #                                                                             #
+ ###############################################################################
+ 
++# Firewall file configuration
++FIREWALL_SETTINGS_DIR="/etc/firewall"
++FIREWALL_SETTINGS_FILE="${FIREWALL_SETTINGS_DIR}/settings"
++
+ # This variable is used to point to a directory
+ # in which the iptables ruleset will be generated.
+ IPTABLES_TMPDIR=
+@@ -32,78 +36,78 @@ FIREWALL_MACROS_DIRS="${FIREWALL_CONFIG_DIR}/macros"
+ FIREWALL_MACROS_DIRS="${FIREWALL_MACROS_DIRS} /usr/share/firewall/macros"
+ 
+ # List of parameters which are saved in the configuration file.
+-FIREWALL_CONFIG_PARAMS=""
++FIREWALL_SETTINGS=( "DEBUG" )
+ 
+ # Valid arguments in the rules file.
+ FIREWALL_RULES_CONFIG_PARAMS="src dst proto action sport dport in out"
+ 
+ # Define the default logging method (nflog or syslog).
+ FIREWALL_LOG_METHOD="nflog"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_LOG_METHOD"
++FIREWALL_SETTINGS+=( "FIREWALL_LOG_METHOD" )
+ 
+ # Set the default threshold for the nflog method.
+ FIREWALL_NFLOG_THRESHOLD=30
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_NFLOG_THRESHOLD"
++FIREWALL_SETTINGS+=( "FIREWALL_NFLOG_THRESHOLD" )
+ 
+ # Enable clamping MSS for braindead ISPs which filter ICMP packets.
+ FIREWALL_CLAMP_PATH_MTU="false"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_CLAMP_PATH_MTU"
++FIREWALL_SETTINGS+=( "FIREWALL_CLAMP_PATH_MTU" )
+ 
+ # Conntrack: Max. amount of simultaneous connections.
+ CONNTRACK_MAX_CONNECTIONS="16384"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} CONNTRACK_MAX_CONNECTIONS"
++FIREWALL_SETTINGS+=( "CONNTRACK_MAX_CONNECTIONS" )
+ 
+ # Conntrack: UDP timeout
+ CONNTRACK_UDP_TIMEOUT="60"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} CONNTRACK_UDP_TIMEOUT"
++FIREWALL_SETTINGS+=( "CONNTRACK_UDP_TIMEOUT" )
+ 
+ # Use SYN cookies or not
+ FIREWALL_SYN_COOKIES="true"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_SYN_COOKIES"
++FIREWALL_SETTINGS+=( "FIREWALL_SYN_COOKIES" )
+ 
+ # rp_filter
+ FIREWALL_RP_FILTER="true"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_RP_FILTER"
++FIREWALL_SETTINGS+=( "FIREWALL_RP_FILTER" )
+ 
+ # Log martians
+ FIREWALL_LOG_MARTIANS="false"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_LOG_MARTIANS"
++FIREWALL_SETTINGS+=( "FIREWALL_LOG_MARTIANS" )
+ 
+ # Accept ICMP redirects
+ FIREWALL_ACCEPT_ICMP_REDIRECTS="false"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_ACCEPT_ICMP_REDIRECTS"
++FIREWALL_SETTINGS+=( "FIREWALL_ACCEPT_ICMP_REDIRECTS" )
+ 
+ # ECN (Explicit Congestion Notification)
+ FIREWALL_USE_ECN="true"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_USE_ECN"
++FIREWALL_SETTINGS+=( "FIREWALL_USE_ECN" )
+ 
+ # Path MTU discovery
+ FIREWALL_PMTU_DISCOVERY="true"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_PMTU_DISCOVERY"
++FIREWALL_SETTINGS+=( "FIREWALL_PMTU_DISCOVERY" )
+ 
+ # Default TTL
+ FIREWALL_DEFAULT_TTL="64"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_DEFAULT_TTL"
++FIREWALL_SETTINGS+=( "FIREWALL_DEFAULT_TTL" )
+ 
+ # Log stealth scans
+ FIREWALL_LOG_STEALTH_SCANS="true"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_LOG_STEALTH_SCANS"
++FIREWALL_SETTINGS+=( "FIREWALL_LOG_STEALTH_SCANS" )
+ 
+ # Log packets with bad TCP flags
+ FIREWALL_LOG_BAD_TCP_FLAGS="true"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_LOG_BAD_TCP_FLAGS"
++FIREWALL_SETTINGS+=( "FIREWALL_LOG_BAD_TCP_FLAGS" )
+ 
+ # Log INVALID TCP packets
+ FIREWALL_LOG_INVALID_TCP="true"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_LOG_INVALID_TCP"
++FIREWALL_SETTINGS+=( "FIREWALL_LOG_INVALID_TCP" )
+ 
+ # Log INVALID UDP packets
+ FIREWALL_LOG_INVALID_UDP="true"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_LOG_INVALID_UDP"
++FIREWALL_SETTINGS+=( "FIREWALL_LOG_INVALID_UDP" )
+ 
+ # Log INVALID ICMP packets
+ FIREWALL_LOG_INVALID_ICMP="true"
+-FIREWALL_CONFIG_PARAMS="${FIREWALL_CONFIG_PARAMS} FIREWALL_LOG_INVALID_ICMP"
++FIREWALL_SETTINGS+=( "FIREWALL_LOG_INVALID_ICMP" )
+ 
+ FIREWALL_SUPPORTED_PROTOCOLS="tcp udp icmp igmp esp ah gre"
+ FIREWALL_PROTOCOLS_SUPPORTING_PORTS="tcp udp"
+diff --git a/src/functions/functions.settings b/src/functions/functions.settings
+index 69f4c23..5728e72 100644
+--- a/src/functions/functions.settings
++++ b/src/functions/functions.settings
+@@ -297,13 +297,13 @@ network_settings_list() {
+ }
+ 
+ firewall_settings_read() {
+-	settings_read "${FIREWALL_SETTINGS_FILE}" "${FIREWALL_SETTINGS_PARAMS}"
++	settings_read "${FIREWALL_SETTINGS_FILE}" "${FIREWALL_SETTINGS[*]}"
+ }
+ 
+ firewall_settings_write() {
+-	settings_write "${FIREWALL_SETTINGS_FILE}" "${FIREWALL_SETTINGS_PARAMS}"
++	settings_write "${FIREWALL_SETTINGS_FILE}" "${FIREWALL_SETTINGS[*]}"
+ }
+ 
+ firewall_settings_print() {
+-	settings_print "${FIREWALL_SETTINGS_PARAMS}"
++	settings_print "${FIREWALL_SETTINGS[*]}"
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0123-firewall-Drop-firewall-config-command-in-favour-of-f.patch b/network/patches/0123-firewall-Drop-firewall-config-command-in-favour-of-f.patch
new file mode 100644
index 000000000..8f26fe8ec
--- /dev/null
+++ b/network/patches/0123-firewall-Drop-firewall-config-command-in-favour-of-f.patch
@@ -0,0 +1,80 @@
+From 12c8f41a0791a517d5cc7cd30bd566896891f092 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 22 Jun 2019 14:16:07 +0000
+Subject: [PATCH 123/304] firewall: Drop firewall-config command in favour of
+ "firewall settings"
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am         |  1 -
+ src/firewall        |  5 +++++
+ src/firewall-config | 29 -----------------------------
+ 3 files changed, 5 insertions(+), 30 deletions(-)
+ delete mode 100644 src/firewall-config
+
+diff --git a/Makefile.am b/Makefile.am
+index 0974ba8..4fe5068 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -114,7 +114,6 @@ dist_doc_DATA = \
+ 
+ dist_sbin_SCRIPTS = \
+ 	src/dhclient-script \
+-	src/firewall-config \
+ 	src/firewall \
+ 	src/network
+ 
+diff --git a/src/firewall b/src/firewall
+index c47ac61..569f413 100644
+--- a/src/firewall
++++ b/src/firewall
+@@ -45,6 +45,11 @@ case "${action}" in
+ 		exit $?
+ 		;;
+ 
++	settings)
++		firewall_cli_settings "$@"
++		exit $?
++		;;
++
+ 	*)
+ 		firewall_cli "ipv6" "${action}" "$@"
+ 		;;
+diff --git a/src/firewall-config b/src/firewall-config
+deleted file mode 100644
+index 53ec175..0000000
+--- a/src/firewall-config
++++ /dev/null
+@@ -1,29 +0,0 @@
+-#!/bin/bash
+-###############################################################################
+-#                                                                             #
+-# IPFire.org - A linux based firewall                                         #
+-# Copyright (C) 2012  IPFire Network Development Team                         #
+-#                                                                             #
+-# This program is free software: you can redistribute it and/or modify        #
+-# it under the terms of the GNU General Public License as published by        #
+-# the Free Software Foundation, either version 3 of the License, or           #
+-# (at your option) any later version.                                         #
+-#                                                                             #
+-# This program is distributed in the hope that it will be useful,             #
+-# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+-# GNU General Public License for more details.                                #
+-#                                                                             #
+-# You should have received a copy of the GNU General Public License           #
+-# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+-#                                                                             #
+-###############################################################################
+-
+-. /usr/lib/network/functions
+-
+-# Read firewall settings
+-firewall_settings_read
+-
+-firewall_cli_settings "$@"
+-
+-exit ${EXIT_ERROR}
+-- 
+2.39.2
+
diff --git a/network/patches/0124-wireless-Do-not-attempt-DFS-when-reg-domain-is-set-t.patch b/network/patches/0124-wireless-Do-not-attempt-DFS-when-reg-domain-is-set-t.patch
new file mode 100644
index 000000000..d7e631785
--- /dev/null
+++ b/network/patches/0124-wireless-Do-not-attempt-DFS-when-reg-domain-is-set-t.patch
@@ -0,0 +1,30 @@
+From 038a7f3628f6b7648f89bb3ef6813e757fed6fec Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 23 Jun 2019 10:30:17 +0000
+Subject: [PATCH 124/304] wireless: Do not attempt DFS when reg domain is set
+ to world
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.wireless | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/src/functions/functions.wireless b/src/functions/functions.wireless
+index 733a356..860b2dd 100644
+--- a/src/functions/functions.wireless
++++ b/src/functions/functions.wireless
+@@ -555,6 +555,11 @@ wireless_supports_dfs() {
+ 	local device="${1}"
+ 	assert isset device
+ 
++	# DFS is not supported if wireless reg domain is set to world
++	if [ -n "${WIRELESS_REGULATORY_DOMAIN}" ] || [ "${WIRELESS_REGULATORY_DOMAIN}" = "00" ]; then
++		return ${EXIT_FALSE}
++	fi
++
+ 	local phy="$(device_get_phy "${device}")"
+ 	if ! isset phy; then
+ 		log ERROR "Could not determine PHY for ${device}"
+-- 
+2.39.2
+
diff --git a/network/patches/0125-bird-Start-service-when-needed-and-not-already-runni.patch b/network/patches/0125-bird-Start-service-when-needed-and-not-already-runni.patch
new file mode 100644
index 000000000..ad660cf7c
--- /dev/null
+++ b/network/patches/0125-bird-Start-service-when-needed-and-not-already-runni.patch
@@ -0,0 +1,42 @@
+From 0d99f882ea0f8c4b1c55f7107067a0cb35fedfb3 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 23 Jun 2019 10:33:48 +0000
+Subject: [PATCH 125/304] bird: Start service when needed and not already
+ running
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/functions/functions.bird | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/src/functions/functions.bird b/src/functions/functions.bird
+index 55d43b5..cbcb6e2 100644
+--- a/src/functions/functions.bird
++++ b/src/functions/functions.bird
+@@ -21,6 +21,10 @@
+ 
+ BIRD_CONF="/etc/bird.conf"
+ 
++bird_is_active() {
++	service_is_active "bird.service"
++}
++
+ bird_start() {
+ 	service_start "bird.service"
+ }
+@@ -41,7 +45,11 @@ bird_update() {
+ 	fi
+ 
+ 	# Reload bird
+-	bird_reload
++	if bird_is_active; then
++		bird_reload
++	else
++		bird_start
++	fi
+ }
+ 
+ bird_generate_config() {
+-- 
+2.39.2
+
diff --git a/network/patches/0126-ip-tunnel-Support-setting-MTU-on-tunnels.patch b/network/patches/0126-ip-tunnel-Support-setting-MTU-on-tunnels.patch
new file mode 100644
index 000000000..78fc906ba
--- /dev/null
+++ b/network/patches/0126-ip-tunnel-Support-setting-MTU-on-tunnels.patch
@@ -0,0 +1,60 @@
+From 9515b03940a0fac2db3fff105638f49a53f85e7d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 23 Jun 2019 11:57:17 +0000
+Subject: [PATCH 126/304] ip-tunnel: Support setting MTU on tunnels
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/hooks/zones/ip-tunnel | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+diff --git a/src/hooks/zones/ip-tunnel b/src/hooks/zones/ip-tunnel
+index c4a4fb4..3c885cb 100644
+--- a/src/hooks/zones/ip-tunnel
++++ b/src/hooks/zones/ip-tunnel
+@@ -26,6 +26,7 @@ SUPPORTED_IP_TUNNEL_MODES="gre sit vti"
+ HOOK_SETTINGS=(
+ 	"MARK"
+ 	"MODE"
++	"MTU"
+ 	"PEER"
+ 	"LOCAL_ADDRESS"
+ )
+@@ -33,6 +34,9 @@ HOOK_SETTINGS=(
+ # Default mode of the tunnel
+ DEFAULT_MODE="gre"
+ 
++# Default MTU
++DEFAULT_MTU="1480"
++
+ hook_check_settings() {
+ 	assert isset MODE && assert isoneof MODE ${SUPPORTED_IP_TUNNEL_MODES}
+ 
+@@ -67,6 +71,16 @@ hook_parse_cmdline() {
+ 				fi
+ 				;;
+ 
++			--mtu=*)
++				MTU="$(cli_get_val "${1}")"
++
++				# Validate MTU
++				if ! mtu_is_valid "ipv6" "${MTU}"; then
++					error "Invalid MTU: ${MTU}"
++					return ${EXIT_ERROR}
++				fi
++				;;
++
+ 			--peer=*)
+ 				PEER="$(cli_get_val "${1}")"
+ 				;;
+@@ -116,6 +130,7 @@ hook_up() {
+ 	if ! device_exists "${zone}"; then
+ 		ip_tunnel_add "${zone}" \
+ 			--mode="${MODE}" \
++			--mtu="${MTU}" \
+ 			--remote-address="${PEER}" \
+ 			--local-address="${LOCAL_ADDRESS}" \
+ 			--ikey="${MARK}" \
+-- 
+2.39.2
+
diff --git a/network/patches/0127-firewall-Fix-generating-systemd-file.patch b/network/patches/0127-firewall-Fix-generating-systemd-file.patch
new file mode 100644
index 000000000..9248fb44f
--- /dev/null
+++ b/network/patches/0127-firewall-Fix-generating-systemd-file.patch
@@ -0,0 +1,33 @@
+From b41f1f866ef816e6ea7dd9e23e11e36a588ed611 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 23 Jun 2019 15:37:41 +0000
+Subject: [PATCH 127/304] firewall: Fix generating systemd file
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                                           | 3 +--
+ src/systemd/{firewall.service => firewall.service.in} | 0
+ 2 files changed, 1 insertion(+), 2 deletions(-)
+ rename src/systemd/{firewall.service => firewall.service.in} (100%)
+
+diff --git a/Makefile.am b/Makefile.am
+index 4fe5068..78da25f 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -374,8 +374,7 @@ INSTALL_DIRS += \
+ endif
+ 
+ EXTRA_DIST += \
+-	src/systemd/firewall4.service.in \
+-	src/systemd/firewall6.service.in \
++	src/systemd/firewall.service.in \
+ 	src/systemd/firewall-init.service.in \
+ 	src/systemd/network-init.service.in \
+ 	src/systemd/network@.service.in
+diff --git a/src/systemd/firewall.service b/src/systemd/firewall.service.in
+similarity index 100%
+rename from src/systemd/firewall.service
+rename to src/systemd/firewall.service.in
+-- 
+2.39.2
+
diff --git a/network/patches/0128-Make-generating-man-pages-optional.patch b/network/patches/0128-Make-generating-man-pages-optional.patch
new file mode 100644
index 000000000..1dd835994
--- /dev/null
+++ b/network/patches/0128-Make-generating-man-pages-optional.patch
@@ -0,0 +1,69 @@
+From dbe28a055de31302f0b8101e4e294394c6c2b63c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 21 Jun 2019 14:37:03 +0100
+Subject: [PATCH 128/304] Make generating man-pages optional
+
+Fixes: #11862
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am  |  4 +++-
+ configure.ac | 12 +++++++++++-
+ 2 files changed, 14 insertions(+), 2 deletions(-)
+
+diff --git a/Makefile.am b/Makefile.am
+index 78da25f..b6ba5ac 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -480,11 +480,13 @@ MANPAGES_XML  = $(patsubst %.txt,%.xml,$(MANPAGES_TXT))
+ .PHONY: man
+ man: $(MANPAGES) $(MANPAGES_HTML)
+ 
++if ENABLE_MANPAGES
+ man_MANS = \
+ 	$(MANPAGES)
++endif
+ 
+ CLEANFILES += \
+-	$(man_MANS) \
++	$(MANPAGES) \
+ 	$(MANPAGES_HTML) \
+ 	$(MANPAGES_XML)
+ 
+diff --git a/configure.ac b/configure.ac
+index 340cfd6..37c17e3 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -123,6 +123,14 @@ AS_IF([test "x$enable_debug" = "xyes"], [
+ 	AC_DEFINE(ENABLE_DEBUG, [1], [Debug messages.])
+ ])
+ 
++# ------------------------------------------------------------------------------
++
++have_manpages=no
++AC_ARG_ENABLE(manpages, AS_HELP_STRING([--disable-man-pages],
++	[do not install man pages]))
++AS_IF([test "x$enable_manpages" != xno], [have_manpages=yes])
++AM_CONDITIONAL(ENABLE_MANPAGES, [test "x$have_manpages" = "xyes"])
++
+ # ------------------------------------------------------------------------------
+ AC_ARG_WITH([systemdsystemunitdir],
+ 	AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
+@@ -144,7 +152,7 @@ PKG_CHECK_MODULES([LIBNL], [libnl-3.0 libnl-genl-3.0])
+ # ------------------------------------------------------------------------------
+ 
+ AC_CHECK_PROGS(ASCIIDOC, [asciidoc])
+-if test -z "${ASCIIDOC}"; then
++if test "${have_manpages}" = "yes" && test -z "${ASCIIDOC}"; then
+ 	AC_MSG_ERROR([Required program 'asciidoc' not found])
+ fi
+ 
+@@ -167,4 +175,6 @@ AC_MSG_RESULT([
+ 
+ 	systemdsystemunitdir: $systemdsystemunitdir
+ 	udevdir:              $udevdir
++
++	Generate man-pages:   ${have_manpages}
+ ])
+-- 
+2.39.2
+
diff --git a/network/patches/0129-Add-documentation-for-the-IPsec-VPN.patch b/network/patches/0129-Add-documentation-for-the-IPsec-VPN.patch
new file mode 100644
index 000000000..4cf29d53f
--- /dev/null
+++ b/network/patches/0129-Add-documentation-for-the-IPsec-VPN.patch
@@ -0,0 +1,135 @@
+From 18bace574c15e966b8e3571cc00be287236162b5 Mon Sep 17 00:00:00 2001
+From: Jonatan Schlag <jonatan.schlag@ipfire.org>
+Date: Mon, 24 Jun 2019 13:30:14 +0200
+Subject: [PATCH 129/304] Add documentation for the IPsec VPN
+
+Signed-off-by: Jonatan Schlag <jonatan.schlag@ipfire.org>
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-vpn-ipsec.txt | 97 +++++++++++++++++++++++++++++++++++++++
+ man/network-vpn.txt       |  5 ++
+ 2 files changed, 102 insertions(+)
+ create mode 100644 man/network-vpn-ipsec.txt
+
+diff --git a/man/network-vpn-ipsec.txt b/man/network-vpn-ipsec.txt
+new file mode 100644
+index 0000000..25347a8
+--- /dev/null
++++ b/man/network-vpn-ipsec.txt
+@@ -0,0 +1,97 @@
++= network-vpn-security-policies(8)
++
++== NAME
++network-ipsec - Configure IPsec VPN connections 
++
++== SYNOPSIS
++[verse]
++'network vpn ipsec [new|destroy]' NAME...
++'network vpn ipsec' NAME COMMAND ...
++
++== DESCRIPTION
++With help of the 'vpn ipsec', it is possible to create, destroy
++and edit IPsec VPN connections.
++
++
++== COMMANDS
++The following commands are understood:
++
++'new NAME'::
++	A new IPsec VPN connection may be created with the 'new' command.
++	+
++	NAME does not allow any spaces.
++
++'destroy NAME'::
++	A IPsec VPN connection can be destroyed with this command.
++
++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 
++
++'NAME authentication mode'::
++	Set the authentication mode out of the following available modes:
++	* psk
++
++'NAME authentication psk PSK'::
++	Set the pre-shared-key to PSK, only useful when the authentication mode is psk:
++
++include::include-color.txt[]
++
++include::include-description.txt[]
++
++'NAME down'::
++	Shutdown a etablished IPsec VPN connection
++
++'NAME inactivity-timeout TIME'::
++	Set the inactivity timeout with TIME in seconds or in the format hh:mm:ss
++
++'NAME local id ID'::
++	Specify the identity of the local system.
++	+
++	The ID must be in one of the following formats:
++	* IP address
++	* FQDN
++	* a string which starts with @
++
++'NAME local prefix [PREFIX-LIST|+PREFIX ...|-PREFIX ...]'::
++	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. 
++
++'NAME peer PEER'::
++	Set the peer to which the IPsec VPN connection should be etablished.
++
++'NAME remote id ID'::
++	Specify the identity of the remote machine.
++	+
++	The ID must be in one of the following formats:
++	* IP address
++	* FQDN
++	* A string which starts with @
++
++'NAME remote prefix [PREFIX-LIST|+PREFIX ...|-PREFIX ...]'::
++	Specify the subnets which the remote side makes available to us.
++
++'NAME security-policy'::
++	Set the security policy which the connection uses.
++	+
++	See link:network-vpn-security-policies[8] for details.
++
++'NAME up'::
++	Establishes the IPsec VPN connection to the remote peer.
++
++'NAME zone'::
++	When you specify a zone of type ip-tunnel here the IPsec connection is established over a vti tunnel.
++	The remote and local prefixes are ignored. Imagine a fiber connection between this two machines, and how you would use it.
++	The IPsec VPN connection works in the same way. You must configure routes and IP addresses of the ip-tunnel hook manually.
++
++
++== AUTHORS
++Michael Tremer,
++Jonatan Schlag
++
++== SEE ALSO
++link:network[8],
++link:network-vpn[8]
+diff --git a/man/network-vpn.txt b/man/network-vpn.txt
+index 5a905db..be33606 100644
+--- a/man/network-vpn.txt
++++ b/man/network-vpn.txt
+@@ -19,6 +19,11 @@ The following commands are understood:
+ 	+
+ 	See link:network-vpn-security-policies[8] for details.
+ 
++'ipsec' ...::
++	Use this command to manage ipsec vpn connections.
++	+
++	See link:network-vpn-ipsec[8] for details.
++
+ == AUTHORS
+ Michael Tremer
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0130-Makefile-Add-network-vpn-ipsec-8.patch b/network/patches/0130-Makefile-Add-network-vpn-ipsec-8.patch
new file mode 100644
index 000000000..2832396ec
--- /dev/null
+++ b/network/patches/0130-Makefile-Add-network-vpn-ipsec-8.patch
@@ -0,0 +1,25 @@
+From 2612a6f4bb0bcc3e155425a653705146eb65d7cd Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 24 Jun 2019 13:28:01 +0100
+Subject: [PATCH 130/304] Makefile: Add network-vpn-ipsec(8)
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/Makefile.am b/Makefile.am
+index b6ba5ac..a5ea123 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -460,6 +460,7 @@ MANPAGES = \
+ 	man/network-route-static.8 \
+ 	man/network-settings.8 \
+ 	man/network-vpn.8 \
++	man/network-vpn-ipsec.8 \
+ 	man/network-vpn-security-policies.8 \
+ 	man/network-zone.8 \
+ 	man/network-zone-bridge.8 \
+-- 
+2.39.2
+
diff --git a/network/patches/0131-security-policies-performance-Remove-CBC-ciphers.patch b/network/patches/0131-security-policies-performance-Remove-CBC-ciphers.patch
new file mode 100644
index 000000000..92b549ec4
--- /dev/null
+++ b/network/patches/0131-security-policies-performance-Remove-CBC-ciphers.patch
@@ -0,0 +1,23 @@
+From 2cb783babd59716366984c8908e70285f23347f3 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 24 Jun 2019 13:28:12 +0100
+Subject: [PATCH 131/304] security-policies: performance: Remove CBC ciphers
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ config/vpn/security-policies/performance | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/config/vpn/security-policies/performance b/config/vpn/security-policies/performance
+index 9b8e943..b226d8d 100644
+--- a/config/vpn/security-policies/performance
++++ b/config/vpn/security-policies/performance
+@@ -1,4 +1,4 @@
+-CIPHERS="CHACHA20-POLY1305 AES128-GCM128 AES128-CBC"
++CIPHERS="CHACHA20-POLY1305 AES128-GCM128"
+ COMPRESSION="off"
+ GROUP_TYPES="ECP521 ECP384 ECP256 ECP224 ECP192 CURVE25519"
+ INTEGRITIES="SHA256"
+-- 
+2.39.2
+
diff --git a/network/patches/0132-IPsec-Add-support-for-Curve448.patch b/network/patches/0132-IPsec-Add-support-for-Curve448.patch
new file mode 100644
index 000000000..0238aad9a
--- /dev/null
+++ b/network/patches/0132-IPsec-Add-support-for-Curve448.patch
@@ -0,0 +1,65 @@
+From 27208caa363cad7c2250bdff5b99a9bc16a5ca91 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 2 Oct 2019 10:36:13 +0000
+Subject: [PATCH 132/304] IPsec: Add support for Curve448
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ config/vpn/security-policies/performance      | 2 +-
+ config/vpn/security-policies/system           | 2 +-
+ src/functions/functions.vpn-security-policies | 6 +++++-
+ 3 files changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/config/vpn/security-policies/performance b/config/vpn/security-policies/performance
+index b226d8d..209f43d 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 db30e69..6ceb0c4 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/src/functions/functions.vpn-security-policies b/src/functions/functions.vpn-security-policies
+index d1d720b..138e821 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() {
+-- 
+2.39.2
+
diff --git a/network/patches/0133-Disable-copybreak.patch b/network/patches/0133-Disable-copybreak.patch
new file mode 100644
index 000000000..c1294c8fb
--- /dev/null
+++ b/network/patches/0133-Disable-copybreak.patch
@@ -0,0 +1,112 @@
+From ea4abb82bc6e613ddebd6235f792dd5bbbc469c9 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 7 May 2020 20:30:03 +0100
+Subject: [PATCH 133/304] Disable copybreak
+
+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.
+
+Fixes: #11930
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+Signed-off-by: Arne Fitzenreiter <arne_f@ipfire.org>
+---
+ Makefile.am                      |  6 +++++
+ src/modprobe.d/no-copybreak.conf | 44 ++++++++++++++++++++++++++++++++
+ 2 files changed, 50 insertions(+)
+ create mode 100644 src/modprobe.d/no-copybreak.conf
+
+diff --git a/Makefile.am b/Makefile.am
+index a5ea123..4aa7314 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
+@@ -396,6 +397,11 @@ dist_sysctl_DATA = \
+ 
+ # ------------------------------------------------------------------------------
+ 
++dist_modprobe_DATA = \
++	src/modprobe.d/no-copybreak.conf
++
++# ------------------------------------------------------------------------------
++
+ dist_bashcompletion_SCRIPTS = \
+ 	src/bash-completion/network
+ 
+diff --git a/src/modprobe.d/no-copybreak.conf b/src/modprobe.d/no-copybreak.conf
+new file mode 100644
+index 0000000..97ea886
+--- /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
+-- 
+2.39.2
+
diff --git a/network/patches/0134-configure-Check-for-libsystemd.patch b/network/patches/0134-configure-Check-for-libsystemd.patch
new file mode 100644
index 000000000..c47bd0bb8
--- /dev/null
+++ b/network/patches/0134-configure-Check-for-libsystemd.patch
@@ -0,0 +1,25 @@
+From ed993fc9d436da0788eca6f80374c9cd85b8bb9b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 22 Jan 2023 12:33:11 +0000
+Subject: [PATCH 134/304] configure: Check for libsystemd
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ configure.ac | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/configure.ac b/configure.ac
+index 37c17e3..f3a9c17 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -148,6 +148,7 @@ AM_CONDITIONAL(HAVE_UDEV, [test -n "$with_udevdir"])
+ # ------------------------------------------------------------------------------
+ 
+ PKG_CHECK_MODULES([LIBNL], [libnl-3.0 libnl-genl-3.0])
++PKG_CHECK_MODULES([SYSTEMD], [libsystemd])
+ 
+ # ------------------------------------------------------------------------------
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0135-Makefile-Add-scaffolding-for-networkd.patch b/network/patches/0135-Makefile-Add-scaffolding-for-networkd.patch
new file mode 100644
index 000000000..1f716ffae
--- /dev/null
+++ b/network/patches/0135-Makefile-Add-scaffolding-for-networkd.patch
@@ -0,0 +1,93 @@
+From 050f4ece8900b9212de57b3564381d82540323aa Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 22 Jan 2023 12:41:47 +0000
+Subject: [PATCH 135/304] Makefile: Add scaffolding for networkd
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ .gitignore          |  1 +
+ Makefile.am         | 18 ++++++++++++++++++
+ src/networkd/main.c | 23 +++++++++++++++++++++++
+ 3 files changed, 42 insertions(+)
+ create mode 100644 src/networkd/main.c
+
+diff --git a/.gitignore b/.gitignore
+index bb093d3..e3bae67 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -3,6 +3,7 @@
+ /config.*
+ /libtool
+ /missing
++/networkd
+ /src/functions/functions
+ /src/inetcalc
+ /src/libnetwork/libnetwork.pc
+diff --git a/Makefile.am b/Makefile.am
+index 4aa7314..64ad94d 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -61,6 +61,7 @@ INSTALL_DIRS =
+ INSTALL_EXEC_HOOKS =
+ UNINSTALL_EXEC_HOOKS =
+ noinst_DATA =
++sbin_PROGRAMS =
+ 
+ AM_CPPFLAGS = \
+ 	$(OUR_CPPFLAGS) \
+@@ -299,6 +300,23 @@ EXTRA_DIST += \
+ 
+ # ------------------------------------------------------------------------------
+ 
++sbin_PROGRAMS += \
++	networkd
++
++dist_networkd_SOURCES = \
++	src/networkd/main.c
++
++networkd_CPPFLAGS = \
++	$(AM_CPPFLAGS)
++
++networkd_CFLAGS = \
++	$(AM_CFLAGS)
++
++networkd_LDFLAGS = \
++	$(AM_LDFLAGS)
++
++# ------------------------------------------------------------------------------
++
+ util_PROGRAMS = \
+ 	src/utils/network-phy-list-channels \
+ 	src/utils/network-phy-list-ciphers \
+diff --git a/src/networkd/main.c b/src/networkd/main.c
+new file mode 100644
+index 0000000..14aafdd
+--- /dev/null
++++ b/src/networkd/main.c
+@@ -0,0 +1,23 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++int main(int argc, char** argv) {
++	return 0;
++}
+-- 
+2.39.2
+
diff --git a/network/patches/0136-networkd-Link-against-systemd.patch b/network/patches/0136-networkd-Link-against-systemd.patch
new file mode 100644
index 000000000..0ff8ce40e
--- /dev/null
+++ b/network/patches/0136-networkd-Link-against-systemd.patch
@@ -0,0 +1,34 @@
+From 5d326bbb3f564cdb7031d80850bd3fe3c7565233 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 29 Jan 2023 21:18:34 +0000
+Subject: [PATCH 136/304] networkd: Link against systemd
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/Makefile.am b/Makefile.am
+index 64ad94d..74b2fae 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -310,11 +310,15 @@ networkd_CPPFLAGS = \
+ 	$(AM_CPPFLAGS)
+ 
+ networkd_CFLAGS = \
+-	$(AM_CFLAGS)
++	$(AM_CFLAGS) \
++	$(SYSTEMD_CFLAGS)
+ 
+ networkd_LDFLAGS = \
+ 	$(AM_LDFLAGS)
+ 
++networkd_LDADD = \
++	$(SYSTEMD_LIBS)
++
+ # ------------------------------------------------------------------------------
+ 
+ util_PROGRAMS = \
+-- 
+2.39.2
+
diff --git a/network/patches/0137-networkd-Tell-systemd-about-the-daemon-status.patch b/network/patches/0137-networkd-Tell-systemd-about-the-daemon-status.patch
new file mode 100644
index 000000000..005bffe4b
--- /dev/null
+++ b/network/patches/0137-networkd-Tell-systemd-about-the-daemon-status.patch
@@ -0,0 +1,47 @@
+From 26acbb4e03e3a44e6046884eab25f6c7e376c105 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 29 Jan 2023 21:18:53 +0000
+Subject: [PATCH 137/304] networkd: Tell systemd about the daemon status
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/main.c | 24 +++++++++++++++++++++++-
+ 1 file changed, 23 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/main.c b/src/networkd/main.c
+index 14aafdd..2429ff5 100644
+--- a/src/networkd/main.c
++++ b/src/networkd/main.c
+@@ -18,6 +18,28 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <errno.h>
++#include <stdlib.h>
++
++#include <systemd/sd-daemon.h>
++#include <systemd/sd-event.h>
++
+ int main(int argc, char** argv) {
+-	return 0;
++	// XXX Drop privileges
++
++	// We are now ready to process any requests
++	sd_notify(0, "READY=1\n" "STATUS=Processing requests...");
++
++	// Run event loop
++	// XXX TODO
++
++	// Let systemd know that we are shutting down
++	sd_notify(0, "STOPPING=1\n" "STATUS=Shutting down...");
++
++	return EXIT_SUCCESS;
++
++ERROR:
++	sd_notifyf(0, "ERRNO=%i", errno);
++
++	return EXIT_FAILURE;
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0138-networkd-Create-a-simple-daemon-class.patch b/network/patches/0138-networkd-Create-a-simple-daemon-class.patch
new file mode 100644
index 000000000..0ac30e734
--- /dev/null
+++ b/network/patches/0138-networkd-Create-a-simple-daemon-class.patch
@@ -0,0 +1,170 @@
+From 112358f3e2d40e148a259b1a7ab466c99947ed4b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 29 Jan 2023 21:29:09 +0000
+Subject: [PATCH 138/304] networkd: Create a simple daemon class
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |  2 ++
+ src/networkd/daemon.c | 56 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/daemon.h | 31 ++++++++++++++++++++++++
+ src/networkd/main.c   | 16 ++++++++++++-
+ 4 files changed, 104 insertions(+), 1 deletion(-)
+ create mode 100644 src/networkd/daemon.c
+ create mode 100644 src/networkd/daemon.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 74b2fae..40d900e 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -304,6 +304,8 @@ sbin_PROGRAMS += \
+ 	networkd
+ 
+ dist_networkd_SOURCES = \
++	src/networkd/daemon.c \
++	src/networkd/daemon.h \
+ 	src/networkd/main.c
+ 
+ networkd_CPPFLAGS = \
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+new file mode 100644
+index 0000000..f635f38
+--- /dev/null
++++ b/src/networkd/daemon.c
+@@ -0,0 +1,56 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <stdlib.h>
++
++#include "daemon.h"
++
++struct nw_daemon {
++	int nrefs;
++};
++
++int nw_daemon_create(struct nw_daemon** daemon) {
++	struct nw_daemon* d = calloc(1, sizeof(*d));
++	if (!d)
++		return 1;
++
++	// Initialize reference counter
++	d->nrefs = 1;
++
++	return 0;
++}
++
++static void nw_daemon_free(struct nw_daemon* daemon) {
++	free(daemon);
++}
++
++struct nw_daemon* nw_daemon_ref(struct nw_daemon* daemon) {
++	daemon->nrefs++;
++
++	return daemon;
++}
++
++struct nw_daemon* nw_daemon_unref(struct nw_daemon* daemon) {
++	if (--daemon->nrefs > 0)
++		return daemon;
++
++	nw_daemon_free(daemon);
++	return NULL;
++}
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+new file mode 100644
+index 0000000..309794e
+--- /dev/null
++++ b/src/networkd/daemon.h
+@@ -0,0 +1,31 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_DAEMON_H
++#define NETWORKD_DAEMON_H
++
++struct nw_daemon;
++
++int nw_daemon_create(struct nw_daemon** daemon);
++
++struct nw_daemon* nw_daemon_ref(struct nw_daemon* daemon);
++struct nw_daemon* nw_daemon_unref(struct nw_daemon* daemon);
++
++#endif /* NETWORKD_DAEMON_H */
+diff --git a/src/networkd/main.c b/src/networkd/main.c
+index 2429ff5..48fb2ae 100644
+--- a/src/networkd/main.c
++++ b/src/networkd/main.c
+@@ -24,9 +24,19 @@
+ #include <systemd/sd-daemon.h>
+ #include <systemd/sd-event.h>
+ 
++#include "daemon.h"
++
+ int main(int argc, char** argv) {
++	struct nw_daemon* daemon = NULL;
++	int r;
++
+ 	// XXX Drop privileges
+ 
++	// Create the daemon
++	r = nw_daemon_create(&daemon);
++	if (r)
++		goto ERROR;
++
+ 	// We are now ready to process any requests
+ 	sd_notify(0, "READY=1\n" "STATUS=Processing requests...");
+ 
+@@ -36,10 +46,14 @@ int main(int argc, char** argv) {
+ 	// Let systemd know that we are shutting down
+ 	sd_notify(0, "STOPPING=1\n" "STATUS=Shutting down...");
+ 
+-	return EXIT_SUCCESS;
++	goto CLEANUP;
+ 
+ ERROR:
+ 	sd_notifyf(0, "ERRNO=%i", errno);
+ 
++CLEANUP:
++	if (daemon)
++		nw_daemon_unref(daemon);
++
+ 	return EXIT_FAILURE;
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0139-networkd-Create-an-event-loop.patch b/network/patches/0139-networkd-Create-an-event-loop.patch
new file mode 100644
index 000000000..aa45d9f2d
--- /dev/null
+++ b/network/patches/0139-networkd-Create-an-event-loop.patch
@@ -0,0 +1,147 @@
+From c7e1b5db6903099797cf700516706a827ae9cc3e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 29 Jan 2023 21:49:22 +0000
+Subject: [PATCH 139/304] networkd: Create an event loop
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 63 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/daemon.h |  2 ++
+ src/networkd/main.c   |  7 ++---
+ 3 files changed, 69 insertions(+), 3 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index f635f38..25fdbdb 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -20,13 +20,44 @@
+ 
+ #include <stdlib.h>
+ 
++#include <systemd/sd-event.h>
++
+ #include "daemon.h"
+ 
+ struct nw_daemon {
+ 	int nrefs;
++
++	// Event Loop
++	sd_event* loop;
+ };
+ 
++static int nw_daemon_setup_loop(struct 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: %m\n");
++		return 1;
++	}
++
++	return 0;
++}
++
++static int nw_daemon_setup(struct nw_daemon* daemon) {
++	int r;
++
++	// Setup the event loop
++	r = nw_daemon_setup_loop(daemon);
++	if (r)
++		return r;
++
++	return 0;
++}
++
+ int nw_daemon_create(struct nw_daemon** daemon) {
++	int r;
++
+ 	struct nw_daemon* d = calloc(1, sizeof(*d));
+ 	if (!d)
+ 		return 1;
+@@ -34,10 +65,26 @@ int nw_daemon_create(struct nw_daemon** daemon) {
+ 	// Initialize reference counter
+ 	d->nrefs = 1;
+ 
++	// 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_free(struct nw_daemon* daemon) {
++	if (daemon->loop)
++		sd_event_unref(daemon->loop);
++
+ 	free(daemon);
+ }
+ 
+@@ -54,3 +101,19 @@ struct nw_daemon* nw_daemon_unref(struct nw_daemon* daemon) {
+ 	nw_daemon_free(daemon);
+ 	return NULL;
+ }
++
++/*
++	This function contains the main loop of the daemon...
++*/
++int nw_daemon_run(struct nw_daemon* daemon) {
++	int r;
++
++	// Launch the event loop
++	r = sd_event_loop(daemon->loop);
++	if (r) {
++		//ERROR("Could not run the event loop: %m\n");
++		return r;
++	}
++
++	return 0;
++}
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index 309794e..215972d 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -28,4 +28,6 @@ int nw_daemon_create(struct nw_daemon** daemon);
+ struct nw_daemon* nw_daemon_ref(struct nw_daemon* daemon);
+ struct nw_daemon* nw_daemon_unref(struct nw_daemon* daemon);
+ 
++int nw_daemon_run(struct nw_daemon* daemon);
++
+ #endif /* NETWORKD_DAEMON_H */
+diff --git a/src/networkd/main.c b/src/networkd/main.c
+index 48fb2ae..80123ad 100644
+--- a/src/networkd/main.c
++++ b/src/networkd/main.c
+@@ -22,7 +22,6 @@
+ #include <stdlib.h>
+ 
+ #include <systemd/sd-daemon.h>
+-#include <systemd/sd-event.h>
+ 
+ #include "daemon.h"
+ 
+@@ -40,8 +39,10 @@ int main(int argc, char** argv) {
+ 	// We are now ready to process any requests
+ 	sd_notify(0, "READY=1\n" "STATUS=Processing requests...");
+ 
+-	// Run event loop
+-	// XXX TODO
++	// Run the daemon
++	r = nw_daemon_run(daemon);
++	if (r)
++		goto ERROR;
+ 
+ 	// Let systemd know that we are shutting down
+ 	sd_notify(0, "STOPPING=1\n" "STATUS=Shutting down...");
+-- 
+2.39.2
+
diff --git a/network/patches/0140-networkd-Enable-the-service-watchdog.patch b/network/patches/0140-networkd-Enable-the-service-watchdog.patch
new file mode 100644
index 000000000..6371661fe
--- /dev/null
+++ b/network/patches/0140-networkd-Enable-the-service-watchdog.patch
@@ -0,0 +1,31 @@
+From 3b2316e448ad4353964779b4c31edfe20b96ea4f Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 29 Jan 2023 21:53:19 +0000
+Subject: [PATCH 140/304] networkd: Enable the service watchdog
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 25fdbdb..c6acefe 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -41,6 +41,13 @@ static int nw_daemon_setup_loop(struct nw_daemon* daemon) {
+ 		return 1;
+ 	}
+ 
++	// Enable the watchdog
++	r = sd_event_set_watchdog(daemon->loop, 1);
++	if (r < 0) {
++		//ERROR("Could not activate watchdog: %m\n");
++		return 1;
++	}
++
+ 	return 0;
+ }
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0141-networkd-Add-some-very-simple-logging.patch b/network/patches/0141-networkd-Add-some-very-simple-logging.patch
new file mode 100644
index 000000000..e7e9ecb9a
--- /dev/null
+++ b/network/patches/0141-networkd-Add-some-very-simple-logging.patch
@@ -0,0 +1,104 @@
+From c251a9ddf0771b85af69f226e886bdd511e2353d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 29 Jan 2023 21:57:32 +0000
+Subject: [PATCH 141/304] networkd: Add some very simple logging
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am            |  1 +
+ src/networkd/daemon.c  |  7 ++++---
+ src/networkd/logging.h | 32 ++++++++++++++++++++++++++++++++
+ 3 files changed, 37 insertions(+), 3 deletions(-)
+ create mode 100644 src/networkd/logging.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 40d900e..abba646 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -306,6 +306,7 @@ sbin_PROGRAMS += \
+ dist_networkd_SOURCES = \
+ 	src/networkd/daemon.c \
+ 	src/networkd/daemon.h \
++	src/networkd/logging.h \
+ 	src/networkd/main.c
+ 
+ networkd_CPPFLAGS = \
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index c6acefe..4d21bb6 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -23,6 +23,7 @@
+ #include <systemd/sd-event.h>
+ 
+ #include "daemon.h"
++#include "logging.h"
+ 
+ struct nw_daemon {
+ 	int nrefs;
+@@ -37,14 +38,14 @@ static int nw_daemon_setup_loop(struct nw_daemon* daemon) {
+ 	// Fetch a reference to the default event loop
+ 	r = sd_event_default(&daemon->loop);
+ 	if (r < 0) {
+-		//ERROR("Could not setup event loop: %m\n");
++		ERROR("Could not setup event loop: %m\n");
+ 		return 1;
+ 	}
+ 
+ 	// Enable the watchdog
+ 	r = sd_event_set_watchdog(daemon->loop, 1);
+ 	if (r < 0) {
+-		//ERROR("Could not activate watchdog: %m\n");
++		ERROR("Could not activate watchdog: %m\n");
+ 		return 1;
+ 	}
+ 
+@@ -118,7 +119,7 @@ int nw_daemon_run(struct nw_daemon* daemon) {
+ 	// Launch the event loop
+ 	r = sd_event_loop(daemon->loop);
+ 	if (r) {
+-		//ERROR("Could not run the event loop: %m\n");
++		ERROR("Could not run the event loop: %m\n");
+ 		return r;
+ 	}
+ 
+diff --git a/src/networkd/logging.h b/src/networkd/logging.h
+new file mode 100644
+index 0000000..9d51f21
+--- /dev/null
++++ b/src/networkd/logging.h
+@@ -0,0 +1,32 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_LOGGING_H
++#define NETWORKD_LOGGING_H
++
++#include <stdio.h>
++
++/*
++	This is just something simple which will work for now...
++*/
++#define ERROR(...) fprintf(stderr, __VA_ARGS__)
++#define DEBUG(...) printf(__VA_ARGS__)
++
++#endif /* NETWORKD_LOGGING_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0142-networkd-Register-SIGTERM-SIGINT-SIGHUP.patch b/network/patches/0142-networkd-Register-SIGTERM-SIGINT-SIGHUP.patch
new file mode 100644
index 000000000..61ba97e7c
--- /dev/null
+++ b/network/patches/0142-networkd-Register-SIGTERM-SIGINT-SIGHUP.patch
@@ -0,0 +1,90 @@
+From 025f60f1f2f22db43edd4bb52b6cff48ceb718a0 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 29 Jan 2023 22:24:53 +0000
+Subject: [PATCH 142/304] networkd: Register SIGTERM/SIGINT/SIGHUP
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 46 +++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 44 insertions(+), 2 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 4d21bb6..483252d 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -18,7 +18,9 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <errno.h>
+ #include <stdlib.h>
++#include <string.h>
+ 
+ #include <systemd/sd-event.h>
+ 
+@@ -32,20 +34,60 @@ struct nw_daemon {
+ 	sd_event* loop;
+ };
+ 
++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) {
++	DEBUG("Received signal to reload...\n");
++
++	// TODO
++
++	return 0;
++}
++
+ static int nw_daemon_setup_loop(struct 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: %m\n");
++		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: %m\n");
++		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;
+ 	}
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0143-networkd-Add-scaffolding-to-reload-the-daemon.patch b/network/patches/0143-networkd-Add-scaffolding-to-reload-the-daemon.patch
new file mode 100644
index 000000000..e83dae62c
--- /dev/null
+++ b/network/patches/0143-networkd-Add-scaffolding-to-reload-the-daemon.patch
@@ -0,0 +1,93 @@
+From 27a3a5969dcc76cc7af7b0dd491703ee2c8baa70 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 29 Jan 2023 22:29:05 +0000
+Subject: [PATCH 143/304] networkd: Add scaffolding to reload the daemon
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 23 +++++++++++++++++------
+ src/networkd/daemon.h |  2 ++
+ 2 files changed, 19 insertions(+), 6 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 483252d..98fb5bd 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -34,18 +34,21 @@ struct nw_daemon {
+ 	sd_event* loop;
+ };
+ 
+-static int nw_daemon_terminate(sd_event_source* source, const struct signalfd_siginfo* si,
++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,
++static int __nw_daemon_reload(sd_event_source* source, const struct signalfd_siginfo* si,
+ 		void* data) {
++	struct nw_daemon* daemon = (struct nw_daemon*)daemon;
++
+ 	DEBUG("Received signal to reload...\n");
+ 
+-	// TODO
++	// Reload the daemon
++	nw_daemon_reload(daemon);
+ 
+ 	return 0;
+ }
+@@ -69,7 +72,7 @@ static int nw_daemon_setup_loop(struct nw_daemon* daemon) {
+ 
+ 	// Listen for SIGTERM
+ 	r = sd_event_add_signal(daemon->loop, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK,
+-		nw_daemon_terminate, daemon);
++		__nw_daemon_terminate, daemon);
+ 	if (r < 0) {
+ 		ERROR("Could not register handling SIGTERM: %s\n", strerror(-r));
+ 		return 1;
+@@ -77,7 +80,7 @@ static int nw_daemon_setup_loop(struct nw_daemon* daemon) {
+ 
+ 	// Listen for SIGINT
+ 	r = sd_event_add_signal(daemon->loop, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK,
+-		nw_daemon_terminate, daemon);
++		__nw_daemon_terminate, daemon);
+ 	if (r < 0) {
+ 		ERROR("Could not register handling SIGINT: %s\n", strerror(-r));
+ 		return 1;
+@@ -85,7 +88,7 @@ static int nw_daemon_setup_loop(struct nw_daemon* daemon) {
+ 
+ 	// Listen for SIGHUP
+ 	r = sd_event_add_signal(daemon->loop, NULL, SIGHUP|SD_EVENT_SIGNAL_PROCMASK,
+-		nw_daemon_reload, daemon);
++		__nw_daemon_reload, daemon);
+ 	if (r < 0) {
+ 		ERROR("Could not register handling SIGHUP: %s\n", strerror(-r));
+ 		return 1;
+@@ -167,3 +170,11 @@ int nw_daemon_run(struct nw_daemon* daemon) {
+ 
+ 	return 0;
+ }
++
++int nw_daemon_reload(struct nw_daemon* daemon) {
++	DEBUG("Reloading daemon...\n");
++
++	// XXX TODO
++
++	return 0;
++}
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index 215972d..5b14ef3 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -30,4 +30,6 @@ struct nw_daemon* nw_daemon_unref(struct nw_daemon* daemon);
+ 
+ int nw_daemon_run(struct nw_daemon* daemon);
+ 
++int nw_daemon_reload(struct nw_daemon* daemon);
++
+ #endif /* NETWORKD_DAEMON_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0144-configure-Enable-system-extensions-to-define-_GNU_SO.patch b/network/patches/0144-configure-Enable-system-extensions-to-define-_GNU_SO.patch
new file mode 100644
index 000000000..ced811542
--- /dev/null
+++ b/network/patches/0144-configure-Enable-system-extensions-to-define-_GNU_SO.patch
@@ -0,0 +1,27 @@
+From 374d06ada7c1a3381aa08496abc7c9f173fd8d2f Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 29 Jan 2023 22:52:37 +0000
+Subject: [PATCH 144/304] configure: Enable system extensions to define
+ _GNU_SOURCE
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ configure.ac | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/configure.ac b/configure.ac
+index f3a9c17..b820794 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -65,6 +65,8 @@ AC_PROG_CC_C99
+ AC_PROG_CC_C_O
+ AC_PROG_GCC_TRADITIONAL
+ 
++AC_USE_SYSTEM_EXTENSIONS
++
+ CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [\
+ 	-pipe \
+ 	-Wall \
+-- 
+2.39.2
+
diff --git a/network/patches/0145-networkd-Add-scaffolding-to-connect-to-dbus.patch b/network/patches/0145-networkd-Add-scaffolding-to-connect-to-dbus.patch
new file mode 100644
index 000000000..d7729101a
--- /dev/null
+++ b/network/patches/0145-networkd-Add-scaffolding-to-connect-to-dbus.patch
@@ -0,0 +1,255 @@
+From 09a6af1750714828bf109e17882ac0e7d0a6c738 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 02:08:50 +0000
+Subject: [PATCH 145/304] networkd: Add scaffolding to connect to dbus
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |   2 +
+ src/networkd/bus.c    | 131 ++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/bus.h    |  35 +++++++++++
+ src/networkd/daemon.c |  12 ++++
+ 4 files changed, 180 insertions(+)
+ create mode 100644 src/networkd/bus.c
+ create mode 100644 src/networkd/bus.h
+
+diff --git a/Makefile.am b/Makefile.am
+index abba646..60a1bcd 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -304,6 +304,8 @@ sbin_PROGRAMS += \
+ 	networkd
+ 
+ dist_networkd_SOURCES = \
++	src/networkd/bus.c \
++	src/networkd/bus.h \
+ 	src/networkd/daemon.c \
+ 	src/networkd/daemon.h \
+ 	src/networkd/logging.h \
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+new file mode 100644
+index 0000000..95862d2
+--- /dev/null
++++ b/src/networkd/bus.c
+@@ -0,0 +1,131 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <stdlib.h>
++#include <string.h>
++
++#include <systemd/sd-bus.h>
++#include <systemd/sd-event.h>
++
++#include "bus.h"
++#include "daemon.h"
++#include "logging.h"
++
++static int nw_bus_daemon_reload(sd_bus_message* m, void* data, sd_bus_error* ret_error) {
++	return 1;
++}
++
++static const sd_bus_vtable daemon_vtable[] = {
++        SD_BUS_VTABLE_START(0),
++        SD_BUS_METHOD("Reload", SD_BUS_NO_ARGS, SD_BUS_NO_RESULT,
++        	nw_bus_daemon_reload, SD_BUS_VTABLE_UNPRIVILEGED),
++        SD_BUS_VTABLE_END,
++};
++
++int nw_bus_connect(sd_bus* bus, sd_event* loop) {
++	int r;
++
++	// Create a bus object
++	r = sd_bus_new(&bus);
++	if (r < 0) {
++		ERROR("Could not allocate a bus object: %s\n", strerror(-r));
++		return 1;
++	}
++
++	// Set description
++	r = sd_bus_set_description(bus, 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(bus, 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(bus, 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(bus, 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(bus, 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(bus, 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(bus);
++	if (r < 0) {
++		ERROR("Could not connect to bus: %s\n", strerror(-r));
++		return 1;
++	}
++
++	// Register any functions
++	r = sd_bus_add_object_vtable(bus, NULL,
++		NETWORKD_BUS_OBJECT_PATH, NETWORKD_BUS_INTERFACE_NAME, daemon_vtable, NULL);
++	if (r < 0) {
++		ERROR("Could not add object vtable: %s\n", strerror(-r));
++		return 1;
++	}
++
++	// Request interface name
++	// XXX Should this be async?
++	// XXX How do we get the actual error message from dbus?
++	r = sd_bus_request_name(bus, NETWORKD_BUS_INTERFACE_NAME, 0);
++	if (r < 0) {
++		ERROR("Could not request bus name: %s\n", strerror(-r));
++		return 1;
++	}
++
++	// Attach the event loop
++	r = sd_bus_attach_event(bus, loop, 0);
++	if (r < 0) {
++		ERROR("Could not attach bus to event loop: %s\n", strerror(-r));
++		return 1;
++	}
++
++	return 0;
++}
+diff --git a/src/networkd/bus.h b/src/networkd/bus.h
+new file mode 100644
+index 0000000..90c1556
+--- /dev/null
++++ b/src/networkd/bus.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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_BUS_H
++#define NETWORKD_BUS_H
++
++#define NETWORKD_BUS_DESCRIPTION		"networkd"
++#define NETWORKD_BUS_OBJECT_PATH		"/org/ipfire/network1"
++#define NETWORKD_BUS_INTERFACE_NAME		"org.ipfire.network1"
++
++#define DEFAULT_SYSTEM_BUS_ADDRESS		"unix:path=/run/dbus/system_bus_socket"
++
++#include <systemd/sd-bus.h>
++#include <systemd/sd-event.h>
++
++int nw_bus_connect(sd_bus* bus, sd_event* loop);
++
++#endif /* NETWORKD_BUS_H */
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 98fb5bd..3aee8ca 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -22,8 +22,10 @@
+ #include <stdlib.h>
+ #include <string.h>
+ 
++#include <systemd/sd-bus.h>
+ #include <systemd/sd-event.h>
+ 
++#include "bus.h"
+ #include "daemon.h"
+ #include "logging.h"
+ 
+@@ -32,6 +34,9 @@ struct nw_daemon {
+ 
+ 	// Event Loop
+ 	sd_event* loop;
++
++	// DBus Connection
++	sd_bus* bus;
+ };
+ 
+ static int __nw_daemon_terminate(sd_event_source* source, const struct signalfd_siginfo* si,
+@@ -105,6 +110,11 @@ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 	if (r)
+ 		return r;
+ 
++	// Connect to the system bus
++	r = nw_bus_connect(daemon->bus, daemon->loop);
++	if (r)
++		return r;
++
+ 	return 0;
+ }
+ 
+@@ -135,6 +145,8 @@ ERROR:
+ }
+ 
+ static void nw_daemon_free(struct nw_daemon* daemon) {
++	if (daemon->bus)
++		sd_bus_unref(daemon->bus);
+ 	if (daemon->loop)
+ 		sd_event_unref(daemon->loop);
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0146-networkd-Add-scaffolding-for-config-objects.patch b/network/patches/0146-networkd-Add-scaffolding-for-config-objects.patch
new file mode 100644
index 000000000..c50840ce3
--- /dev/null
+++ b/network/patches/0146-networkd-Add-scaffolding-for-config-objects.patch
@@ -0,0 +1,131 @@
+From 6b666d6221c5797f78fa4942561c931609d8f656 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 02:16:06 +0000
+Subject: [PATCH 146/304] networkd: Add scaffolding for config objects
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |  2 ++
+ src/networkd/config.c | 58 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/config.h | 31 +++++++++++++++++++++++
+ 3 files changed, 91 insertions(+)
+ create mode 100644 src/networkd/config.c
+ create mode 100644 src/networkd/config.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 60a1bcd..50a6034 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -306,6 +306,8 @@ sbin_PROGRAMS += \
+ dist_networkd_SOURCES = \
+ 	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/logging.h \
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+new file mode 100644
+index 0000000..d18fb12
+--- /dev/null
++++ b/src/networkd/config.c
+@@ -0,0 +1,58 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <stdlib.h>
++
++#include "config.h"
++
++struct nw_config {
++	int nrefs;
++};
++
++static void nw_config_free(struct nw_config* config) {
++	free(config);
++}
++
++int nw_config_create(struct nw_config** config) {
++	struct nw_config* c = calloc(1, sizeof(*c));
++	if (!c)
++		return 1;
++
++	// Initialize reference counter
++	c->nrefs = 1;
++
++	*config = c;
++
++	return 0;
++}
++
++struct nw_config* nw_config_ref(struct nw_config* config) {
++	config->nrefs++;
++
++	return config;
++}
++
++struct nw_config* nw_config_unref(struct nw_config* config) {
++	if (--config->nrefs > 0)
++		return config;
++
++	nw_config_free(config);
++	return NULL;
++}
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+new file mode 100644
+index 0000000..a8aed7f
+--- /dev/null
++++ b/src/networkd/config.h
+@@ -0,0 +1,31 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_CONFIG_H
++#define NETWORKD_CONFIG_H
++
++struct nw_config;
++
++int nw_config_create(struct nw_config** config);
++
++struct nw_config* nw_config_ref(struct nw_config* config);
++struct nw_config* nw_config_unref(struct nw_config* config);
++
++#endif /* NETWORKD_CONFIG_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0147-networkd-Add-scaffolding-to-read-configuration-files.patch b/network/patches/0147-networkd-Add-scaffolding-to-read-configuration-files.patch
new file mode 100644
index 000000000..18b7e2aa7
--- /dev/null
+++ b/network/patches/0147-networkd-Add-scaffolding-to-read-configuration-files.patch
@@ -0,0 +1,94 @@
+From c81a6335fd0e1912fa3e8268ba85b9cd048cf243 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 02:26:41 +0000
+Subject: [PATCH 147/304] networkd: Add scaffolding to read configuration files
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 50 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/config.h |  3 +++
+ 2 files changed, 53 insertions(+)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index d18fb12..e808531 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -18,9 +18,11 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <stdio.h>
+ #include <stdlib.h>
+ 
+ #include "config.h"
++#include "logging.h"
+ 
+ struct nw_config {
+ 	int nrefs;
+@@ -56,3 +58,51 @@ struct nw_config* nw_config_unref(struct nw_config* config) {
+ 	nw_config_free(config);
+ 	return NULL;
+ }
++
++static int nw_config_parse(struct nw_config* config, FILE* f) {
++	// XXX TODO
++
++	return 0;
++}
++
++int nw_config_readf(struct nw_config** config, FILE* f) {
++	int r;
++
++	// Create a new config object
++	r = nw_config_create(config);
++	if (r)
++		return r;
++
++	// Parse the configuration
++	r = nw_config_parse(*config, f);
++	if (r)
++		goto ERROR;
++
++	return 0;
++
++ERROR:
++	nw_config_free(*config);
++	return r;
++}
++
++int nw_config_read(struct nw_config** config, const char* path) {
++	FILE* f = NULL;
++	int r;
++
++	// Open the file
++	f = fopen(path, "r");
++	if (!f) {
++		ERROR("Could not read configuration file %s: %m\n", path);
++		r = 1;
++		goto ERROR;
++	}
++
++	// Read from file
++	r = nw_config_readf(config, f);
++
++ERROR:
++	if (f)
++		fclose(f);
++
++	return r;
++}
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index a8aed7f..559ba94 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -28,4 +28,7 @@ int nw_config_create(struct nw_config** config);
+ struct nw_config* nw_config_ref(struct nw_config* config);
+ struct nw_config* nw_config_unref(struct nw_config* config);
+ 
++int nw_config_readf(struct nw_config** config, FILE* f);
++int nw_config_read(struct nw_config** config, const char* path);
++
+ #endif /* NETWORKD_CONFIG_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0148-networkd-Implement-setting-configuration-values.patch b/network/patches/0148-networkd-Implement-setting-configuration-values.patch
new file mode 100644
index 000000000..cc2641125
--- /dev/null
+++ b/network/patches/0148-networkd-Implement-setting-configuration-values.patch
@@ -0,0 +1,283 @@
+From 4237caa2ed47385e3471822348d908846c745b29 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 03:04:01 +0000
+Subject: [PATCH 148/304] networkd: Implement setting configuration values
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |   3 +-
+ src/networkd/config.c | 114 ++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/config.h |   7 +++
+ src/networkd/string.h |  76 ++++++++++++++++++++++++++++
+ 4 files changed, 199 insertions(+), 1 deletion(-)
+ create mode 100644 src/networkd/string.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 50a6034..1b18f62 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -311,7 +311,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/daemon.c \
+ 	src/networkd/daemon.h \
+ 	src/networkd/logging.h \
+-	src/networkd/main.c
++	src/networkd/main.c \
++	src/networkd/string.h
+ 
+ networkd_CPPFLAGS = \
+ 	$(AM_CPPFLAGS)
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index e808531..72a0cfb 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -18,17 +18,72 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
++#include <string.h>
++#include <sys/queue.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 {
+ 	int nrefs;
++
++	STAILQ_HEAD(entries, nw_config_entry) entries;
+ };
+ 
++static void nw_config_entry_free(struct nw_config_entry* entry) {
++	free(entry);
++}
++
++static struct nw_config_entry* nw_config_entry_create(
++		struct 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);
++
++ERROR:
++	nw_config_entry_free(entry);
++	return NULL;
++}
++
+ static void nw_config_free(struct 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);
++	}
++
+ 	free(config);
+ }
+ 
+@@ -40,6 +95,9 @@ int nw_config_create(struct nw_config** config) {
+ 	// Initialize reference counter
+ 	c->nrefs = 1;
+ 
++	// Initialise entries
++	STAILQ_INIT(&c->entries);
++
+ 	*config = c;
+ 
+ 	return 0;
+@@ -106,3 +164,59 @@ ERROR:
+ 
+ 	return r;
+ }
++
++static struct nw_config_entry* nw_config_find(struct 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(struct 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;
++}
++
++int nw_config_set(struct nw_config* config, const char* key, const char* value) {
++	struct nw_config_entry* entry = NULL;
++
++	// 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);
++}
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index 559ba94..a793229 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -21,6 +21,9 @@
+ #ifndef NETWORKD_CONFIG_H
+ #define NETWORKD_CONFIG_H
+ 
++#define NETWORK_CONFIG_KEY_MAX_LENGTH		128
++#define NETWORK_CONFIG_VALUE_MAX_LENGTH		2048
++
+ struct nw_config;
+ 
+ int nw_config_create(struct nw_config** config);
+@@ -31,4 +34,8 @@ struct nw_config* nw_config_unref(struct nw_config* config);
+ int nw_config_readf(struct nw_config** config, FILE* f);
+ int nw_config_read(struct nw_config** config, const char* path);
+ 
++int nw_config_del(struct nw_config* config, const char* key);
++
++int nw_config_set(struct nw_config* config, const char* key, const char* value);
++
+ #endif /* NETWORKD_CONFIG_H */
+diff --git a/src/networkd/string.h b/src/networkd/string.h
+new file mode 100644
+index 0000000..3ac4846
+--- /dev/null
++++ b/src/networkd/string.h
+@@ -0,0 +1,76 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_STRING_H
++#define NETWORKD_STRING_H
++
++#include <stdarg.h>
++
++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 1;
++
++	// Check if the entire string could be written
++	if ((size_t)required >= length) {
++		errno = ENOMEM;
++		return 1;
++	}
++
++	// 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);
++}
++
++#endif /* NETWORKD_STRING_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0149-networkd-Implement-reading-configuration-values.patch b/network/patches/0149-networkd-Implement-reading-configuration-values.patch
new file mode 100644
index 000000000..b008145bd
--- /dev/null
+++ b/network/patches/0149-networkd-Implement-reading-configuration-values.patch
@@ -0,0 +1,55 @@
+From d39683a626fd7f5fe2aea15365a27b3f7727ad0b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 03:09:57 +0000
+Subject: [PATCH 149/304] networkd: Implement reading configuration values
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 21 +++++++++++++++++++++
+ src/networkd/config.h |  3 +++
+ 2 files changed, 24 insertions(+)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 72a0cfb..a3db7c6 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -220,3 +220,24 @@ int nw_config_set(struct nw_config* config, const char* key, const char* value)
+ 	// Store the new value
+ 	return nw_string_set(entry->value, value);
+ }
++
++const char* nw_config_get(struct 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;
++}
++
++unsigned int nw_config_get_unsigned_int(struct nw_config* config, const char* key) {
++	const char* value = nw_config_get(config, key);
++
++	// Return zero if not set
++	if (!value)
++		return 0;
++
++	return strtoul(value, NULL, 10);
++}
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index a793229..c5a2f7f 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -38,4 +38,7 @@ int nw_config_del(struct nw_config* config, const char* key);
+ 
+ int nw_config_set(struct nw_config* config, const char* key, const char* value);
+ 
++const char* nw_config_get(struct nw_config* config, const char* key);
++unsigned int nw_config_get_unsigned_int(struct nw_config* config, const char* key);
++
+ #endif /* NETWORKD_CONFIG_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0150-networkd-Implement-writing-configuration-files.patch b/network/patches/0150-networkd-Implement-writing-configuration-files.patch
new file mode 100644
index 000000000..c66dda673
--- /dev/null
+++ b/network/patches/0150-networkd-Implement-writing-configuration-files.patch
@@ -0,0 +1,79 @@
+From d3dfdb77779004950cf3afb19e456b7d5c39c940 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 03:17:30 +0000
+Subject: [PATCH 150/304] networkd: Implement writing configuration files
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 40 ++++++++++++++++++++++++++++++++++++++++
+ src/networkd/config.h |  3 +++
+ 2 files changed, 43 insertions(+)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index a3db7c6..b2a23f0 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -165,6 +165,46 @@ ERROR:
+ 	return r;
+ }
+ 
++int nw_config_writef(struct 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;
++}
++
++int nw_config_write(struct nw_config* config, const char* path) {
++	int r;
++
++	FILE* f = fopen(path, "w");
++	if (!f) {
++		ERROR("Failed to open %s for writing: %m\n", path);
++		r = 1;
++		goto ERROR;
++	}
++
++	// Write configuration
++	r = nw_config_writef(config, f);
++
++ERROR:
++	if (f)
++		fclose(f);
++
++	return r;
++}
++
+ static struct nw_config_entry* nw_config_find(struct nw_config* config, const char* key) {
+ 	struct nw_config_entry* entry = NULL;
+ 
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index c5a2f7f..90e73f4 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -34,6 +34,9 @@ struct nw_config* nw_config_unref(struct nw_config* config);
+ int nw_config_readf(struct nw_config** config, FILE* f);
+ int nw_config_read(struct nw_config** config, const char* path);
+ 
++int nw_config_writef(struct nw_config* config, FILE* f);
++int nw_config_write(struct nw_config* config, const char* path);
++
+ int nw_config_del(struct nw_config* config, const char* key);
+ 
+ int nw_config_set(struct nw_config* config, const char* key, const char* value);
+-- 
+2.39.2
+
diff --git a/network/patches/0151-networkd-Read-main-configuration-file.patch b/network/patches/0151-networkd-Read-main-configuration-file.patch
new file mode 100644
index 000000000..4a6a2ee67
--- /dev/null
+++ b/network/patches/0151-networkd-Read-main-configuration-file.patch
@@ -0,0 +1,54 @@
+From 2ac78f288a7c1f8b363cf0c86fc196aeeb8e8264 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 03:20:23 +0000
+Subject: [PATCH 151/304] networkd: Read main configuration file
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 3aee8ca..bd5f1f0 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -26,12 +26,15 @@
+ #include <systemd/sd-event.h>
+ 
+ #include "bus.h"
++#include "config.h"
+ #include "daemon.h"
+ #include "logging.h"
+ 
+ struct nw_daemon {
+ 	int nrefs;
+ 
++	struct nw_config* config;
++
+ 	// Event Loop
+ 	sd_event* loop;
+ 
+@@ -105,6 +108,11 @@ static int nw_daemon_setup_loop(struct nw_daemon* daemon) {
+ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 	int r;
+ 
++	// Read configuration file
++	r = nw_config_read(&daemon->config, "/etc/network/settings");
++	if (r)
++		return r;
++
+ 	// Setup the event loop
+ 	r = nw_daemon_setup_loop(daemon);
+ 	if (r)
+@@ -145,6 +153,8 @@ ERROR:
+ }
+ 
+ static void nw_daemon_free(struct nw_daemon* daemon) {
++	if (daemon->config)
++		nw_config_unref(daemon->config);
+ 	if (daemon->bus)
+ 		sd_bus_unref(daemon->bus);
+ 	if (daemon->loop)
+-- 
+2.39.2
+
diff --git a/network/patches/0152-networkd-Set-configuration-path-from-build-scripts.patch b/network/patches/0152-networkd-Set-configuration-path-from-build-scripts.patch
new file mode 100644
index 000000000..a06cb3b29
--- /dev/null
+++ b/network/patches/0152-networkd-Set-configuration-path-from-build-scripts.patch
@@ -0,0 +1,41 @@
+From 5c1452a43a6a8d3d3596fa8be38741e62852ce5c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 03:29:45 +0000
+Subject: [PATCH 152/304] networkd: Set configuration path from build scripts
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           | 3 ++-
+ src/networkd/daemon.c | 2 +-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/Makefile.am b/Makefile.am
+index 1b18f62..3cc7ce9 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -315,7 +315,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/string.h
+ 
+ networkd_CPPFLAGS = \
+-	$(AM_CPPFLAGS)
++	$(AM_CPPFLAGS) \
++	-DCONFIG_DIR="\"$(configdir)\""
+ 
+ networkd_CFLAGS = \
+ 	$(AM_CFLAGS) \
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index bd5f1f0..9077c54 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -109,7 +109,7 @@ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Read configuration file
+-	r = nw_config_read(&daemon->config, "/etc/network/settings");
++	r = nw_config_read(&daemon->config, CONFIG_DIR "/settings");
+ 	if (r)
+ 		return r;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0153-networkd-Add-scaffolding-for-zones.patch b/network/patches/0153-networkd-Add-scaffolding-for-zones.patch
new file mode 100644
index 000000000..8b720d38f
--- /dev/null
+++ b/network/patches/0153-networkd-Add-scaffolding-for-zones.patch
@@ -0,0 +1,192 @@
+From e4eebc6e6a0091729831fd1361d0a9fd0d205114 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 03:42:57 +0000
+Subject: [PATCH 153/304] networkd: Add scaffolding for zones
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |  4 ++-
+ src/networkd/config.h |  2 ++
+ src/networkd/string.h |  3 ++
+ src/networkd/zone.c   | 83 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/zone.h   | 35 ++++++++++++++++++
+ 5 files changed, 126 insertions(+), 1 deletion(-)
+ create mode 100644 src/networkd/zone.c
+ create mode 100644 src/networkd/zone.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 3cc7ce9..5bb9c4d 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -312,7 +312,9 @@ dist_networkd_SOURCES = \
+ 	src/networkd/daemon.h \
+ 	src/networkd/logging.h \
+ 	src/networkd/main.c \
+-	src/networkd/string.h
++	src/networkd/string.h \
++	src/networkd/zone.c \
++	src/networkd/zone.h
+ 
+ networkd_CPPFLAGS = \
+ 	$(AM_CPPFLAGS) \
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index 90e73f4..3474ae1 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -21,6 +21,8 @@
+ #ifndef NETWORKD_CONFIG_H
+ #define NETWORKD_CONFIG_H
+ 
++#include <stdio.h>
++
+ #define NETWORK_CONFIG_KEY_MAX_LENGTH		128
+ #define NETWORK_CONFIG_VALUE_MAX_LENGTH		2048
+ 
+diff --git a/src/networkd/string.h b/src/networkd/string.h
+index 3ac4846..52b5add 100644
+--- a/src/networkd/string.h
++++ b/src/networkd/string.h
+@@ -21,7 +21,10 @@
+ #ifndef NETWORKD_STRING_H
+ #define NETWORKD_STRING_H
+ 
++#include <errno.h>
+ #include <stdarg.h>
++#include <stdio.h>
++#include <string.h>
+ 
+ static inline int __nw_string_vformat(char* s, const size_t length,
+ 		const char* format, va_list args) {
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+new file mode 100644
+index 0000000..5066c4b
+--- /dev/null
++++ b/src/networkd/zone.c
+@@ -0,0 +1,83 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <stdlib.h>
++
++#include "config.h"
++#include "string.h"
++#include "zone.h"
++
++struct nw_zone {
++	int nrefs;
++
++	char name[NETWORK_ZONE_NAME_MAX_LENGTH];
++
++	// Configuration
++	struct nw_config *config;
++};
++
++static void nw_zone_free(struct nw_zone* zone) {
++	if (zone->config)
++		nw_config_unref(zone->config);
++
++	free(zone);
++}
++
++int nw_zone_create(struct nw_zone** zone, const char* name) {
++	int r;
++
++	// Allocate a new object
++	struct nw_zone* z = calloc(1, sizeof(*z));
++	if (!z)
++		return 1;
++
++	// Initialize reference counter
++	z->nrefs = 1;
++
++	// Store the name
++	r = nw_string_set(z->name, name);
++	if (r)
++		goto ERROR;
++
++	*zone = z;
++	return 0;
++
++ERROR:
++	nw_zone_free(z);
++	return r;
++}
++
++struct nw_zone* nw_zone_ref(struct nw_zone* zone) {
++	zone->nrefs++;
++
++	return zone;
++}
++
++struct nw_zone* nw_zone_unref(struct nw_zone* zone) {
++	if (--zone->nrefs > 0)
++		return zone;
++
++	nw_zone_free(zone);
++	return NULL;
++}
++
++const char* nw_zone_name(struct nw_zone* zone) {
++	return zone->name;
++}
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+new file mode 100644
+index 0000000..78287ab
+--- /dev/null
++++ b/src/networkd/zone.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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_ZONE_H
++#define NETWORKD_ZONE_H
++
++#define NETWORK_ZONE_NAME_MAX_LENGTH		16
++
++struct nw_zone;
++
++int nw_zone_create(struct nw_zone** zone, const char* name);
++
++struct nw_zone* nw_zone_ref(struct nw_zone* zone);
++struct nw_zone* nw_zone_unref(struct nw_zone* zone);
++
++const char* nw_zone_name(struct nw_zone* zone);
++
++#endif /* NETWORKD_ZONE_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0154-networkd-Install-a-dbus-service-file.patch b/network/patches/0154-networkd-Install-a-dbus-service-file.patch
new file mode 100644
index 000000000..c5fdc33df
--- /dev/null
+++ b/network/patches/0154-networkd-Install-a-dbus-service-file.patch
@@ -0,0 +1,82 @@
+From 063eb4d841e8fc4625d889a9d5f8ac832bd60f0d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 04:05:07 +0000
+Subject: [PATCH 154/304] networkd: Install a dbus service file
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                              |  4 ++++
+ configure.ac                             | 15 +++++++++++++++
+ src/networkd/org.ipfire.network1.service |  5 +++++
+ 3 files changed, 24 insertions(+)
+ create mode 100644 src/networkd/org.ipfire.network1.service
+
+diff --git a/Makefile.am b/Makefile.am
+index 5bb9c4d..5cfeefa 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -62,6 +62,7 @@ INSTALL_EXEC_HOOKS =
+ UNINSTALL_EXEC_HOOKS =
+ noinst_DATA =
+ sbin_PROGRAMS =
++dist_dbussystembusdir_DATA =
+ 
+ AM_CPPFLAGS = \
+ 	$(OUR_CPPFLAGS) \
+@@ -330,6 +331,9 @@ networkd_LDFLAGS = \
+ networkd_LDADD = \
+ 	$(SYSTEMD_LIBS)
+ 
++dist_dbussystembusdir_DATA += \
++	src/networkd/org.ipfire.network1.service
++
+ # ------------------------------------------------------------------------------
+ 
+ util_PROGRAMS = \
+diff --git a/configure.ac b/configure.ac
+index b820794..8634a39 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -133,6 +133,20 @@ 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([dbussystembusdir], AS_HELP_STRING([--with-dbussystembusdir=DIR],
++		[path to D-Bus system bus services directory]), [with_dbussystembusdir=${withval}])
++if test -z "${with_dbussystembusdir}"; then
++	AC_MSG_CHECKING([D-Bus system bus services dir])
++	with_dbussystembusdir="$($PKG_CONFIG --variable=system_bus_services_dir dbus-1)"
++	if test -z "${with_dbussystembusdir}"; then
++		AC_MSG_ERROR([D-Bus system bus services directory is required])
++	fi
++	AC_MSG_RESULT([${with_dbussystembusdir}])
++fi
++AC_SUBST(dbussystembusdir, [${with_dbussystembusdir}])
++
+ # ------------------------------------------------------------------------------
+ AC_ARG_WITH([systemdsystemunitdir],
+ 	AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
+@@ -176,6 +190,7 @@ AC_MSG_RESULT([
+ 
+ 	prefix:               $prefix
+ 
++	dbussystembusdir:     ${dbussystembusdir}
+ 	systemdsystemunitdir: $systemdsystemunitdir
+ 	udevdir:              $udevdir
+ 
+diff --git a/src/networkd/org.ipfire.network1.service b/src/networkd/org.ipfire.network1.service
+new file mode 100644
+index 0000000..fdeda66
+--- /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
+-- 
+2.39.2
+
diff --git a/network/patches/0155-networkd-Install-a-dbus-policy.patch b/network/patches/0155-networkd-Install-a-dbus-policy.patch
new file mode 100644
index 000000000..5185e467a
--- /dev/null
+++ b/network/patches/0155-networkd-Install-a-dbus-policy.patch
@@ -0,0 +1,87 @@
+From d5fab986b9ac9a692b26e5f5d49a51dce85ccf59 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 18:47:30 +0000
+Subject: [PATCH 155/304] networkd: Install a dbus policy
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                           |  8 ++++++--
+ configure.ac                          | 12 ++++++++++++
+ src/networkd/org.ipfire.network1.conf | 16 ++++++++++++++++
+ 3 files changed, 34 insertions(+), 2 deletions(-)
+ create mode 100644 src/networkd/org.ipfire.network1.conf
+
+diff --git a/Makefile.am b/Makefile.am
+index 5cfeefa..eeda5ba 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -62,7 +62,8 @@ INSTALL_EXEC_HOOKS =
+ UNINSTALL_EXEC_HOOKS =
+ noinst_DATA =
+ sbin_PROGRAMS =
+-dist_dbussystembusdir_DATA =
++dist_dbuspolicy_DATA =
++dist_dbussystembus_DATA =
+ 
+ AM_CPPFLAGS = \
+ 	$(OUR_CPPFLAGS) \
+@@ -331,7 +332,10 @@ networkd_LDFLAGS = \
+ networkd_LDADD = \
+ 	$(SYSTEMD_LIBS)
+ 
+-dist_dbussystembusdir_DATA += \
++dist_dbuspolicy_DATA += \
++	src/networkd/org.ipfire.network1.conf
++
++dist_dbussystembus_DATA += \
+ 	src/networkd/org.ipfire.network1.service
+ 
+ # ------------------------------------------------------------------------------
+diff --git a/configure.ac b/configure.ac
+index 8634a39..e612ff8 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -135,6 +135,18 @@ 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=DIR],
+ 		[path to D-Bus system bus services directory]), [with_dbussystembusdir=${withval}])
+ if test -z "${with_dbussystembusdir}"; then
+diff --git a/src/networkd/org.ipfire.network1.conf b/src/networkd/org.ipfire.network1.conf
+new file mode 100644
+index 0000000..96e8e15
+--- /dev/null
++++ b/src/networkd/org.ipfire.network1.conf
+@@ -0,0 +1,16 @@
++<?xml version="1.0"?> <!--*-nxml-*-->
++<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
++	"https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
++
++<busconfig>
++	<policy user="network">
++		<allow own="org.ipfire.network1"/>
++		<allow send_destination="org.ipfire.network1"/>
++		<allow receive_sender="org.ipfire.network1"/>
++	</policy>
++
++	<policy context="default">
++		<allow send_destination="org.ipfire.network1"/>
++		<allow receive_sender="org.ipfire.network1"/>
++	</policy>
++</busconfig>
+-- 
+2.39.2
+
diff --git a/network/patches/0156-configure-Tidy-up-dbus-path-detection.patch b/network/patches/0156-configure-Tidy-up-dbus-path-detection.patch
new file mode 100644
index 000000000..887870a32
--- /dev/null
+++ b/network/patches/0156-configure-Tidy-up-dbus-path-detection.patch
@@ -0,0 +1,53 @@
+From 9ca8914ef2c65a669aa05e37f2dbe232f54ecbf1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 18:51:34 +0000
+Subject: [PATCH 156/304] configure: Tidy up dbus path detection
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ configure.ac | 22 +++++++++++-----------
+ 1 file changed, 11 insertions(+), 11 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index e612ff8..ba5967d 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -147,17 +147,16 @@ AC_ARG_WITH(
+ )
+ AC_SUBST(dbuspolicydir)
+ 
+-AC_ARG_WITH([dbussystembusdir], AS_HELP_STRING([--with-dbussystembusdir=DIR],
+-		[path to D-Bus system bus services directory]), [with_dbussystembusdir=${withval}])
+-if test -z "${with_dbussystembusdir}"; then
+-	AC_MSG_CHECKING([D-Bus system bus services dir])
+-	with_dbussystembusdir="$($PKG_CONFIG --variable=system_bus_services_dir dbus-1)"
+-	if test -z "${with_dbussystembusdir}"; then
+-		AC_MSG_ERROR([D-Bus system bus services directory is required])
+-	fi
+-	AC_MSG_RESULT([${with_dbussystembusdir}])
+-fi
+-AC_SUBST(dbussystembusdir, [${with_dbussystembusdir}])
++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([systemdsystemunitdir],
+@@ -202,6 +201,7 @@ AC_MSG_RESULT([
+ 
+ 	prefix:               $prefix
+ 
++	dbuspolicydir:        ${dbuspolicydir}
+ 	dbussystembusdir:     ${dbussystembusdir}
+ 	systemdsystemunitdir: $systemdsystemunitdir
+ 	udevdir:              $udevdir
+-- 
+2.39.2
+
diff --git a/network/patches/0157-configure-Drop-non-sensical-CFLAGS-and-add-more-warn.patch b/network/patches/0157-configure-Drop-non-sensical-CFLAGS-and-add-more-warn.patch
new file mode 100644
index 000000000..3e608f6af
--- /dev/null
+++ b/network/patches/0157-configure-Drop-non-sensical-CFLAGS-and-add-more-warn.patch
@@ -0,0 +1,77 @@
+From a465e68bd8bf11f7cd41089941ad558dbda34f19 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 18:54:54 +0000
+Subject: [PATCH 157/304] configure: Drop non-sensical CFLAGS and add more
+ warnings
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ configure.ac | 42 ++++++++++++++----------------------------
+ 1 file changed, 14 insertions(+), 28 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index ba5967d..9560838 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([
+@@ -65,37 +67,21 @@ AC_PROG_CC_C99
+ AC_PROG_CC_C_O
+ AC_PROG_GCC_TRADITIONAL
+ 
+-AC_USE_SYSTEM_EXTENSIONS
+-
+ 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)
++	-Wchar-subscripts \
++	-Wformat-security \
++	-Wmissing-declarations \
++	-Wmissing-prototypes \
++	-Wnested-externs \
++	-Wpointer-arith \
++	-Wshadow \
++	-Wsign-compare \
++	-Wstrict-prototypes \
++	-Wtype-limits \
++])
+ 
+-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)
+-
+-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)
+ 
+ # ------------------------------------------------------------------------------
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0158-libnetwork-Fix-prototype-of-network_version.patch b/network/patches/0158-libnetwork-Fix-prototype-of-network_version.patch
new file mode 100644
index 000000000..6dbfcad59
--- /dev/null
+++ b/network/patches/0158-libnetwork-Fix-prototype-of-network_version.patch
@@ -0,0 +1,26 @@
+From ac17b7ef5c2dc801dc9eb179927096bc18e7bc58 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 18:56:17 +0000
+Subject: [PATCH 158/304] libnetwork: Fix prototype of network_version()
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/libnetwork/network/libnetwork.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/libnetwork/network/libnetwork.h b/src/libnetwork/network/libnetwork.h
+index 2919fc9..e69fd04 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
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0159-networkd-Move-systemd-notifications-into-daemon-obje.patch b/network/patches/0159-networkd-Move-systemd-notifications-into-daemon-obje.patch
new file mode 100644
index 000000000..3c3051800
--- /dev/null
+++ b/network/patches/0159-networkd-Move-systemd-notifications-into-daemon-obje.patch
@@ -0,0 +1,104 @@
+From 8a88982f6823b376f2eace8c718dc930072de667 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 19:00:51 +0000
+Subject: [PATCH 159/304] networkd: Move systemd notifications into daemon
+ object
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 19 ++++++++++++++++---
+ src/networkd/main.c   | 24 ++++--------------------
+ 2 files changed, 20 insertions(+), 23 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 9077c54..b93877e 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -23,6 +23,7 @@
+ #include <string.h>
+ 
+ #include <systemd/sd-bus.h>
++#include <systemd/sd-daemon.h>
+ #include <systemd/sd-event.h>
+ 
+ #include "bus.h"
+@@ -183,14 +184,26 @@ struct nw_daemon* nw_daemon_unref(struct nw_daemon* daemon) {
+ int nw_daemon_run(struct 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) {
+-		ERROR("Could not run the event loop: %m\n");
+-		return r;
++	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...");
++
+ 	return 0;
++
++ERROR:
++	sd_notifyf(0, "ERRNO=%i", -r);
++
++	return 1;
+ }
+ 
+ int nw_daemon_reload(struct nw_daemon* daemon) {
+diff --git a/src/networkd/main.c b/src/networkd/main.c
+index 80123ad..51a9bba 100644
+--- a/src/networkd/main.c
++++ b/src/networkd/main.c
+@@ -18,10 +18,7 @@
+ #                                                                             #
+ #############################################################################*/
+ 
+-#include <errno.h>
+-#include <stdlib.h>
+-
+-#include <systemd/sd-daemon.h>
++#include <stddef.h>
+ 
+ #include "daemon.h"
+ 
+@@ -34,27 +31,14 @@ int main(int argc, char** argv) {
+ 	// Create the daemon
+ 	r = nw_daemon_create(&daemon);
+ 	if (r)
+-		goto ERROR;
+-
+-	// We are now ready to process any requests
+-	sd_notify(0, "READY=1\n" "STATUS=Processing requests...");
++		return r;
+ 
+ 	// Run the daemon
+ 	r = nw_daemon_run(daemon);
+-	if (r)
+-		goto ERROR;
+-
+-	// Let systemd know that we are shutting down
+-	sd_notify(0, "STOPPING=1\n" "STATUS=Shutting down...");
+-
+-	goto CLEANUP;
+-
+-ERROR:
+-	sd_notifyf(0, "ERRNO=%i", errno);
+ 
+-CLEANUP:
++	// Cleanup
+ 	if (daemon)
+ 		nw_daemon_unref(daemon);
+ 
+-	return EXIT_FAILURE;
++	return r;
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0160-man-Fix-incorrect-name-on-IPsec-man-page.patch b/network/patches/0160-man-Fix-incorrect-name-on-IPsec-man-page.patch
new file mode 100644
index 000000000..ba3b4f6bf
--- /dev/null
+++ b/network/patches/0160-man-Fix-incorrect-name-on-IPsec-man-page.patch
@@ -0,0 +1,45 @@
+From fbe46265eecc9064df8819d9bd08e5fcb0ea985c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 19:03:41 +0000
+Subject: [PATCH 160/304] man: Fix incorrect name on IPsec man page
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ man/network-vpn-ipsec.txt | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/man/network-vpn-ipsec.txt b/man/network-vpn-ipsec.txt
+index 25347a8..d60d41d 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.
+-- 
+2.39.2
+
diff --git a/network/patches/0161-networkd-Install-some-simple-PolicyKit-policy.patch b/network/patches/0161-networkd-Install-some-simple-PolicyKit-policy.patch
new file mode 100644
index 000000000..68634a0e1
--- /dev/null
+++ b/network/patches/0161-networkd-Install-some-simple-PolicyKit-policy.patch
@@ -0,0 +1,94 @@
+From bf98bd657165f87eafae1442a6396239d74cd88e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 19:26:35 +0000
+Subject: [PATCH 161/304] networkd: Install some simple PolicyKit policy
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                             |  4 ++++
+ configure.ac                            | 13 +++++++++++++
+ src/networkd/org.ipfire.network1.policy | 19 +++++++++++++++++++
+ 3 files changed, 36 insertions(+)
+ create mode 100644 src/networkd/org.ipfire.network1.policy
+
+diff --git a/Makefile.am b/Makefile.am
+index eeda5ba..4802de3 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -64,6 +64,7 @@ noinst_DATA =
+ sbin_PROGRAMS =
+ dist_dbuspolicy_DATA =
+ dist_dbussystembus_DATA =
++dist_polkitpolicy_DATA =
+ 
+ AM_CPPFLAGS = \
+ 	$(OUR_CPPFLAGS) \
+@@ -338,6 +339,9 @@ dist_dbuspolicy_DATA += \
+ dist_dbussystembus_DATA += \
+ 	src/networkd/org.ipfire.network1.service
+ 
++dist_polkitpolicy_DATA += \
++	src/networkd/org.ipfire.network1.policy
++
+ # ------------------------------------------------------------------------------
+ 
+ util_PROGRAMS = \
+diff --git a/configure.ac b/configure.ac
+index 9560838..16f0724 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -144,6 +144,18 @@ AC_ARG_WITH(
+ )
+ 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]),
+@@ -189,6 +201,7 @@ AC_MSG_RESULT([
+ 
+ 	dbuspolicydir:        ${dbuspolicydir}
+ 	dbussystembusdir:     ${dbussystembusdir}
++	polkitpolicydir:      ${polkitpolicydir}
+ 	systemdsystemunitdir: $systemdsystemunitdir
+ 	udevdir:              $udevdir
+ 
+diff --git a/src/networkd/org.ipfire.network1.policy b/src/networkd/org.ipfire.network1.policy
+new file mode 100644
+index 0000000..46318f1
+--- /dev/null
++++ b/src/networkd/org.ipfire.network1.policy
+@@ -0,0 +1,19 @@
++<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
++<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
++        "https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
++
++<policyconfig>
++	<vendor>The IPFire Project</vendor>
++	<vendor_url>https://www.ipfire.org</vendor_url>
++
++	<action id="org.ipfire.network1.reload">
++		<description gettext-domain="systemd">Reload Network Settings</description>
++		<message gettext-domain="network">Authentication is required to reload network settings.</message>
++		<defaults>
++			<allow_any>auth_admin</allow_any>
++			<allow_inactive>auth_admin</allow_inactive>
++			<allow_active>auth_admin_keep</allow_active>
++		</defaults>
++		<annotate key="org.freedesktop.policykit.owner">unix-user:network</annotate>
++	</action>
++</policyconfig>
+-- 
+2.39.2
+
diff --git a/network/patches/0162-networkd-Call-function-when-we-are-connected-to-dbus.patch b/network/patches/0162-networkd-Call-function-when-we-are-connected-to-dbus.patch
new file mode 100644
index 000000000..62c5161e8
--- /dev/null
+++ b/network/patches/0162-networkd-Call-function-when-we-are-connected-to-dbus.patch
@@ -0,0 +1,51 @@
+From bf42846c2ac47b5ecb8eea5cba618b130a6401ab Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 30 Jan 2023 20:55:42 +0000
+Subject: [PATCH 162/304] networkd: Call function when we are connected to dbus
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/bus.c | 18 +++++++++++++++++-
+ 1 file changed, 17 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+index 95862d2..36ae7c9 100644
+--- a/src/networkd/bus.c
++++ b/src/networkd/bus.c
+@@ -28,10 +28,18 @@
+ #include "daemon.h"
+ #include "logging.h"
+ 
+-static int nw_bus_daemon_reload(sd_bus_message* m, void* data, sd_bus_error* ret_error) {
++static int nw_bus_daemon_reload(sd_bus_message* m, void* data, sd_bus_error* error) {
+ 	return 1;
+ }
+ 
++static int nw_bus_on_connect(sd_bus_message* m, void* data, sd_bus_error* error) {
++	struct nw_daemon* daemon = (struct nw_daemon*)data;
++
++	DEBUG("Connected to D-Bus\n");
++
++	return 0;
++}
++
+ static const sd_bus_vtable daemon_vtable[] = {
+         SD_BUS_VTABLE_START(0),
+         SD_BUS_METHOD("Reload", SD_BUS_NO_ARGS, SD_BUS_NO_RESULT,
+@@ -127,5 +135,13 @@ int nw_bus_connect(sd_bus* bus, sd_event* loop) {
+ 		return 1;
+ 	}
+ 
++	// Request receiving a connect signal
++	r = sd_bus_match_signal_async(bus, 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 0;
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0163-networkd-Install-a-systemd-service-file.patch b/network/patches/0163-networkd-Install-a-systemd-service-file.patch
new file mode 100644
index 000000000..460834fea
--- /dev/null
+++ b/network/patches/0163-networkd-Install-a-systemd-service-file.patch
@@ -0,0 +1,116 @@
+From 5d968c0188b8181db6f392f925d7875e12e7e21b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 15:26:34 +0000
+Subject: [PATCH 163/304] networkd: Install a systemd service file
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ .gitignore                       |  1 +
+ Makefile.am                      | 12 ++++++++-
+ src/networkd/networkd.service.in | 45 ++++++++++++++++++++++++++++++++
+ 3 files changed, 57 insertions(+), 1 deletion(-)
+ create mode 100644 src/networkd/networkd.service.in
+
+diff --git a/.gitignore b/.gitignore
+index e3bae67..9194c93 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -8,6 +8,7 @@
+ /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 4802de3..3a3f82c 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -65,6 +65,7 @@ sbin_PROGRAMS =
+ dist_dbuspolicy_DATA =
+ dist_dbussystembus_DATA =
+ dist_polkitpolicy_DATA =
++systemdsystemunit_DATA =
+ 
+ AM_CPPFLAGS = \
+ 	$(OUR_CPPFLAGS) \
+@@ -342,6 +343,15 @@ dist_dbussystembus_DATA += \
+ 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
++
+ # ------------------------------------------------------------------------------
+ 
+ util_PROGRAMS = \
+@@ -406,7 +416,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 \
+diff --git a/src/networkd/networkd.service.in b/src/networkd/networkd.service.in
+new file mode 100644
+index 0000000..4361023
+--- /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=@sbindir@/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
+-- 
+2.39.2
+
diff --git a/network/patches/0164-networkd-Fully-implement-bus-handler-for-Reload.patch b/network/patches/0164-networkd-Fully-implement-bus-handler-for-Reload.patch
new file mode 100644
index 000000000..65054fa0b
--- /dev/null
+++ b/network/patches/0164-networkd-Fully-implement-bus-handler-for-Reload.patch
@@ -0,0 +1,32 @@
+From 1dc98e400cd9c17edffa8f94f02a38ca1777cef8 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 15:26:57 +0000
+Subject: [PATCH 164/304] networkd: Fully implement bus handler for Reload
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/bus.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+index 36ae7c9..56eda87 100644
+--- a/src/networkd/bus.c
++++ b/src/networkd/bus.c
+@@ -29,7 +29,13 @@
+ #include "logging.h"
+ 
+ static int nw_bus_daemon_reload(sd_bus_message* m, void* data, sd_bus_error* error) {
+-	return 1;
++	struct nw_daemon* daemon = (struct 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_bus_on_connect(sd_bus_message* m, void* data, sd_bus_error* error) {
+-- 
+2.39.2
+
diff --git a/network/patches/0165-networkd-Asynchronously-register-to-the-bus.patch b/network/patches/0165-networkd-Asynchronously-register-to-the-bus.patch
new file mode 100644
index 000000000..c122a8ee2
--- /dev/null
+++ b/network/patches/0165-networkd-Asynchronously-register-to-the-bus.patch
@@ -0,0 +1,28 @@
+From 1e5ff643f2e18e5133ec932d5355f110ca1bfbf4 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 15:27:18 +0000
+Subject: [PATCH 165/304] networkd: Asynchronously register to the bus
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/bus.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+index 56eda87..5224aaa 100644
+--- a/src/networkd/bus.c
++++ b/src/networkd/bus.c
+@@ -126,9 +126,7 @@ int nw_bus_connect(sd_bus* bus, sd_event* loop) {
+ 	}
+ 
+ 	// Request interface name
+-	// XXX Should this be async?
+-	// XXX How do we get the actual error message from dbus?
+-	r = sd_bus_request_name(bus, NETWORKD_BUS_INTERFACE_NAME, 0);
++	r = sd_bus_request_name_async(bus, NULL, NETWORKD_BUS_INTERFACE_NAME, 0, NULL, NULL);
+ 	if (r < 0) {
+ 		ERROR("Could not request bus name: %s\n", strerror(-r));
+ 		return 1;
+-- 
+2.39.2
+
diff --git a/network/patches/0166-networkd-config-Split-flushing-all-entries-into-a-fu.patch b/network/patches/0166-networkd-config-Split-flushing-all-entries-into-a-fu.patch
new file mode 100644
index 000000000..e9601d3e8
--- /dev/null
+++ b/network/patches/0166-networkd-config-Split-flushing-all-entries-into-a-fu.patch
@@ -0,0 +1,71 @@
+From 91915f80f60943afec29743eda7258f5bf65e8e5 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 21:46:11 +0000
+Subject: [PATCH 166/304] networkd: config: Split flushing all entries into a
+ function
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 25 ++++++++++++++++---------
+ src/networkd/config.h |  2 ++
+ 2 files changed, 18 insertions(+), 9 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index b2a23f0..a11ee1c 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -74,15 +74,8 @@ ERROR:
+ }
+ 
+ static void nw_config_free(struct 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);
+-	}
++	// Flush all entries
++	nw_config_flush(config);
+ 
+ 	free(config);
+ }
+@@ -117,6 +110,20 @@ struct nw_config* nw_config_unref(struct nw_config* config) {
+ 	return NULL;
+ }
+ 
++int nw_config_flush(struct 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;
++}
++
+ static int nw_config_parse(struct nw_config* config, FILE* f) {
+ 	// XXX TODO
+ 
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index 3474ae1..3878063 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -33,6 +33,8 @@ int nw_config_create(struct nw_config** config);
+ struct nw_config* nw_config_ref(struct nw_config* config);
+ struct nw_config* nw_config_unref(struct nw_config* config);
+ 
++int nw_config_flush(struct nw_config* config);
++
+ int nw_config_readf(struct nw_config** config, FILE* f);
+ int nw_config_read(struct nw_config** config, const char* path);
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0167-networkd-Change-config-read-functions-to-not-create-.patch b/network/patches/0167-networkd-Change-config-read-functions-to-not-create-.patch
new file mode 100644
index 000000000..5cc42ff66
--- /dev/null
+++ b/network/patches/0167-networkd-Change-config-read-functions-to-not-create-.patch
@@ -0,0 +1,70 @@
+From 3239c3b1a98103e79ae7ab524fc12386a781249f Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 22:03:29 +0000
+Subject: [PATCH 167/304] networkd: Change config read functions to not create
+ a new instance
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 24 ++----------------------
+ src/networkd/config.h |  4 ++--
+ 2 files changed, 4 insertions(+), 24 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index a11ee1c..0048e44 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -124,33 +124,13 @@ int nw_config_flush(struct nw_config* config) {
+ 	return 0;
+ }
+ 
+-static int nw_config_parse(struct nw_config* config, FILE* f) {
++int nw_config_readf(struct nw_config* config, FILE* f) {
+ 	// XXX TODO
+ 
+ 	return 0;
+ }
+ 
+-int nw_config_readf(struct nw_config** config, FILE* f) {
+-	int r;
+-
+-	// Create a new config object
+-	r = nw_config_create(config);
+-	if (r)
+-		return r;
+-
+-	// Parse the configuration
+-	r = nw_config_parse(*config, f);
+-	if (r)
+-		goto ERROR;
+-
+-	return 0;
+-
+-ERROR:
+-	nw_config_free(*config);
+-	return r;
+-}
+-
+-int nw_config_read(struct nw_config** config, const char* path) {
++int nw_config_read(struct nw_config* config, const char* path) {
+ 	FILE* f = NULL;
+ 	int r;
+ 
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index 3878063..c4f9125 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -35,8 +35,8 @@ struct nw_config* nw_config_unref(struct nw_config* config);
+ 
+ int nw_config_flush(struct nw_config* config);
+ 
+-int nw_config_readf(struct nw_config** config, FILE* f);
+-int nw_config_read(struct nw_config** config, const char* path);
++int nw_config_readf(struct nw_config* config, FILE* f);
++int nw_config_read(struct nw_config* config, const char* path);
+ 
+ int nw_config_writef(struct nw_config* config, FILE* f);
+ int nw_config_write(struct nw_config* config, const char* path);
+-- 
+2.39.2
+
diff --git a/network/patches/0168-networkd-Store-the-path-with-the-configuration-objec.patch b/network/patches/0168-networkd-Store-the-path-with-the-configuration-objec.patch
new file mode 100644
index 000000000..5b745e3f8
--- /dev/null
+++ b/network/patches/0168-networkd-Store-the-path-with-the-configuration-objec.patch
@@ -0,0 +1,200 @@
+From b076fa8b6b7aa4b5746b8ef01469e074b6a228ae Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 22:37:06 +0000
+Subject: [PATCH 168/304] networkd: Store the path with the configuration
+ object
+
+This makes it easier to call read and write functions without
+re-composing the path again and again...
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 64 +++++++++++++++++++++++++++++++++++++------
+ src/networkd/config.h | 11 ++++----
+ src/networkd/daemon.c |  2 +-
+ 3 files changed, 61 insertions(+), 16 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 0048e44..e531ec6 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -19,6 +19,7 @@
+ #############################################################################*/
+ 
+ #include <errno.h>
++#include <limits.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -38,6 +39,9 @@ struct nw_config_entry {
+ struct nw_config {
+ 	int nrefs;
+ 
++	// The path to the configuration file
++	char path[PATH_MAX];
++
+ 	STAILQ_HEAD(entries, nw_config_entry) entries;
+ };
+ 
+@@ -80,7 +84,9 @@ static void nw_config_free(struct nw_config* config) {
+ 	free(config);
+ }
+ 
+-int nw_config_create(struct nw_config** config) {
++int nw_config_create(struct nw_config** config, const char* path) {
++	int r;
++
+ 	struct nw_config* c = calloc(1, sizeof(*c));
+ 	if (!c)
+ 		return 1;
+@@ -91,9 +97,26 @@ int nw_config_create(struct nw_config** config) {
+ 	// Initialise entries
+ 	STAILQ_INIT(&c->entries);
+ 
++	// Store the path
++	if (path) {
++		r = nw_string_set(c->path, path);
++		if (r)
++			goto ERROR;
++
++		// Try to read the configuration from path
++		r = nw_config_read(c);
++		if (r)
++			goto ERROR;
++	}
++
+ 	*config = c;
+ 
+ 	return 0;
++
++ERROR:
++	nw_config_free(c);
++
++	return r;
+ }
+ 
+ struct nw_config* nw_config_ref(struct nw_config* config) {
+@@ -110,6 +133,13 @@ struct nw_config* nw_config_unref(struct nw_config* config) {
+ 	return NULL;
+ }
+ 
++const char* nw_config_path(struct nw_config* config) {
++	if (*config->path)
++		return config->path;
++
++	return NULL;
++}
++
+ int nw_config_flush(struct nw_config* config) {
+ 	struct nw_config_entry* entry = NULL;
+ 
+@@ -124,20 +154,30 @@ int nw_config_flush(struct nw_config* config) {
+ 	return 0;
+ }
+ 
+-int nw_config_readf(struct nw_config* config, FILE* f) {
++static int nw_config_readf(struct nw_config* config, FILE* f) {
+ 	// XXX TODO
+ 
+ 	return 0;
+ }
+ 
+-int nw_config_read(struct nw_config* config, const char* path) {
++int nw_config_read(struct nw_config* config) {
+ 	FILE* f = NULL;
+ 	int r;
+ 
++	// We cannot read if path is not set
++	if (!*config->path) {
++		errno = ENOTSUP;
++		return 1;
++	}
++
+ 	// Open the file
+-	f = fopen(path, "r");
++	f = fopen(config->path, "r");
+ 	if (!f) {
+-		ERROR("Could not read configuration file %s: %m\n", path);
++		// Silently ignore if the file does not exist
++		if (errno == ENOENT)
++			return 0;
++
++		ERROR("Could not read configuration file %s: %m\n", config->path);
+ 		r = 1;
+ 		goto ERROR;
+ 	}
+@@ -152,7 +192,7 @@ ERROR:
+ 	return r;
+ }
+ 
+-int nw_config_writef(struct nw_config* config, FILE* f) {
++static int nw_config_writef(struct nw_config* config, FILE* f) {
+ 	struct nw_config_entry* entry = NULL;
+ 	int r;
+ 
+@@ -172,12 +212,18 @@ int nw_config_writef(struct nw_config* config, FILE* f) {
+ 	return 0;
+ }
+ 
+-int nw_config_write(struct nw_config* config, const char* path) {
++int nw_config_write(struct nw_config* config) {
+ 	int r;
+ 
+-	FILE* f = fopen(path, "w");
++	// We cannot write if path is not set
++	if (!*config->path) {
++		errno = ENOTSUP;
++		return 1;
++	}
++
++	FILE* f = fopen(config->path, "w");
+ 	if (!f) {
+-		ERROR("Failed to open %s for writing: %m\n", path);
++		ERROR("Failed to open %s for writing: %m\n", config->path);
+ 		r = 1;
+ 		goto ERROR;
+ 	}
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index c4f9125..5b9910b 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -28,18 +28,17 @@
+ 
+ struct nw_config;
+ 
+-int nw_config_create(struct nw_config** config);
++int nw_config_create(struct nw_config** config, const char* path);
+ 
+ struct nw_config* nw_config_ref(struct nw_config* config);
+ struct nw_config* nw_config_unref(struct nw_config* config);
+ 
+-int nw_config_flush(struct nw_config* config);
++const char* nw_config_path(struct nw_config* config);
+ 
+-int nw_config_readf(struct nw_config* config, FILE* f);
+-int nw_config_read(struct nw_config* config, const char* path);
++int nw_config_flush(struct nw_config* config);
+ 
+-int nw_config_writef(struct nw_config* config, FILE* f);
+-int nw_config_write(struct nw_config* config, const char* path);
++int nw_config_read(struct nw_config* config);
++int nw_config_write(struct nw_config* config);
+ 
+ int nw_config_del(struct nw_config* config, const char* key);
+ 
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index b93877e..5b097c2 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -110,7 +110,7 @@ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Read configuration file
+-	r = nw_config_read(&daemon->config, CONFIG_DIR "/settings");
++	r = nw_config_create(&daemon->config, CONFIG_DIR "/settings");
+ 	if (r)
+ 		return r;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0169-networkd-zones-Try-to-read-configuration-automatical.patch b/network/patches/0169-networkd-zones-Try-to-read-configuration-automatical.patch
new file mode 100644
index 000000000..6d7eb9b40
--- /dev/null
+++ b/network/patches/0169-networkd-zones-Try-to-read-configuration-automatical.patch
@@ -0,0 +1,137 @@
+From 00b5de56ae30d4d8f0288ec160570ab6129aec79 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 22:39:34 +0000
+Subject: [PATCH 169/304] networkd: zones: Try to read configuration
+ automatically
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/string.h | 25 ++++++++++++++++++++++
+ src/networkd/zone.c   | 49 +++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 74 insertions(+)
+
+diff --git a/src/networkd/string.h b/src/networkd/string.h
+index 52b5add..4ae2dbf 100644
+--- a/src/networkd/string.h
++++ b/src/networkd/string.h
+@@ -26,6 +26,9 @@
+ #include <stdio.h>
+ #include <string.h>
+ 
++#define nw_string_vformat(s, format, ...) \
++	__nw_string_vformat(s, sizeof(s), format, __VA_ARGS__)
++
+ static inline int __nw_string_vformat(char* s, const size_t length,
+ 		const char* format, va_list args) {
+ 	// Write string to buffer
+@@ -76,4 +79,26 @@ static inline int __nw_string_set(char* s, const size_t length, const char* valu
+ 	return __nw_string_format(s, length, "%s", value);
+ }
+ 
++/*
++	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);
++}
++
+ #endif /* NETWORKD_STRING_H */
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index 5066c4b..4790d33 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -18,6 +18,7 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <limits.h>
+ #include <stdlib.h>
+ 
+ #include "config.h"
+@@ -33,6 +34,32 @@ struct nw_zone {
+ 	struct nw_config *config;
+ };
+ 
++#define nw_zone_path(zone, path, format, ...) \
++	__nw_zone_path(zone, path, sizeof(path), format, __VA_ARGS__)
++
++static int __nw_zone_path(struct nw_zone* zone, char* p, const size_t length,
++		const char* format, ...) {
++	char prefix[NAME_MAX];
++	char suffix[NAME_MAX];
++	va_list args;
++	int r;
++
++	// Format the prefix
++	r = nw_string_format(prefix, "%s/zones/%s", CONFIG_DIR, zone->name);
++	if (r)
++		return r;
++
++	// Format the suffix
++	va_start(args, format);
++	r = nw_string_vformat(suffix, format, args);
++	va_end(args);
++	if (r)
++		return r;
++
++	// Join the two parts together
++	return __nw_path_join(p, length, prefix, suffix);
++}
++
+ static void nw_zone_free(struct nw_zone* zone) {
+ 	if (zone->config)
+ 		nw_config_unref(zone->config);
+@@ -40,6 +67,23 @@ static void nw_zone_free(struct nw_zone* zone) {
+ 	free(zone);
+ }
+ 
++static int nw_zone_setup(struct nw_zone* zone) {
++	char path[PATH_MAX];
++	int r;
++
++	// Compose the path to the main configuration file
++	r = nw_zone_path(zone, path, "%s", "settings");
++	if (r)
++		return r;
++
++	// Initialize the configuration
++	r = nw_config_create(&zone->config, path);
++	if (r)
++		return r;
++
++	return 0;
++}
++
+ int nw_zone_create(struct nw_zone** zone, const char* name) {
+ 	int r;
+ 
+@@ -56,6 +100,11 @@ int nw_zone_create(struct nw_zone** zone, const char* name) {
+ 	if (r)
+ 		goto ERROR;
+ 
++	// Setup the zone
++	r = nw_zone_setup(z);
++	if (r)
++		goto ERROR;
++
+ 	*zone = z;
+ 	return 0;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0170-networkd-Read-all-zones-from-configuration.patch b/network/patches/0170-networkd-Read-all-zones-from-configuration.patch
new file mode 100644
index 000000000..bbf4a09c5
--- /dev/null
+++ b/network/patches/0170-networkd-Read-all-zones-from-configuration.patch
@@ -0,0 +1,201 @@
+From 19e74c9b5d7e9372a3ea9d27579ba8608626459d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 22:40:04 +0000
+Subject: [PATCH 170/304] networkd: Read all zones from configuration
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 131 +++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 130 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 5b097c2..86731d1 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -18,9 +18,11 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <dirent.h>
+ #include <errno.h>
+ #include <stdlib.h>
+ #include <string.h>
++#include <sys/queue.h>
+ 
+ #include <systemd/sd-bus.h>
+ #include <systemd/sd-daemon.h>
+@@ -30,6 +32,14 @@
+ #include "config.h"
+ #include "daemon.h"
+ #include "logging.h"
++#include "zone.h"
++
++struct nw_daemon_zone {
++	struct nw_zone* zone;
++
++	// Link to the other zones
++	STAILQ_ENTRY(nw_daemon_zone) nodes;
++};
+ 
+ struct nw_daemon {
+ 	int nrefs;
+@@ -41,6 +51,9 @@ struct nw_daemon {
+ 
+ 	// DBus Connection
+ 	sd_bus* bus;
++
++	// Zones
++	STAILQ_HEAD(zones, nw_daemon_zone) zones;
+ };
+ 
+ static int __nw_daemon_terminate(sd_event_source* source, const struct signalfd_siginfo* si,
+@@ -106,7 +119,85 @@ static int nw_daemon_setup_loop(struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
+-static int nw_daemon_setup(struct nw_daemon* daemon) {
++static int nw_daemon_load_zone_filter(const struct dirent* path) {
++	const char* fn = path->d_name;
++
++	// Ignore everything starting with '.'
++	if (*fn == '.')
++		return 0;
++
++	// Ignore anything that isn't a directory
++	if (path->d_type != DT_DIR)
++		return 0;
++
++	return 1;
++}
++
++static int nw_daemon_add_zone(struct nw_daemon* daemon, struct nw_zone* zone) {
++	// Allocate a new entry
++	struct nw_daemon_zone* 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(&daemon->zones, entry, nodes);
++
++	return 0;
++}
++
++static int nw_daemon_load_zones(struct nw_daemon* daemon) {
++	struct dirent** paths = NULL;
++	int n;
++	int r = 0;
++
++	struct nw_zone* zone = NULL;
++
++	// Scan the zones directory
++	n = scandir(CONFIG_DIR "/zones", &paths, nw_daemon_load_zone_filter, alphasort);
++	if (n < 0) {
++		ERROR("Could not load zones: %m\n");
++		return 1;
++	}
++
++	DEBUG("Found %d zone(s)\n", n);
++
++	// Load all zones
++	for (int i = 0; i < n; i++) {
++		const char* name = paths[i]->d_name;
++
++		DEBUG("Loading zone '%s'...\n", name);
++
++		// Create a new zone object
++		r = nw_zone_create(&zone, name);
++		if (r)
++			goto ERROR;
++
++		// Store the zone
++		r = nw_daemon_add_zone(daemon, zone);
++		if (r) {
++			nw_zone_unref(zone);
++			goto ERROR;
++		}
++
++		nw_zone_unref(zone);
++	}
++
++ERROR:
++	// Free paths
++	if (paths) {
++		for (int i = 0; i < n; i++) {
++			free(paths[i]);
++		}
++		free(paths);
++	}
++
++	return r;
++}
++
++static int nw_daemon_load_config(struct nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Read configuration file
+@@ -114,6 +205,22 @@ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 	if (r)
+ 		return r;
+ 
++	// Load zones
++	r = nw_daemon_load_zones(daemon);
++	if (r)
++		return r;
++
++	return r;
++}
++
++static int nw_daemon_setup(struct 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)
+@@ -137,6 +244,9 @@ int nw_daemon_create(struct nw_daemon** daemon) {
+ 	// Initialize reference counter
+ 	d->nrefs = 1;
+ 
++	// Initialize zones
++	STAILQ_INIT(&d->zones);
++
+ 	// Setup the daemon
+ 	r = nw_daemon_setup(d);
+ 	if (r)
+@@ -153,7 +263,26 @@ ERROR:
+ 	return r;
+ }
+ 
++static void nw_daemon_free_zones(struct nw_daemon* daemon) {
++	struct nw_daemon_zone* entry = NULL;
++
++	while (!STAILQ_EMPTY(&daemon->zones)) {
++		entry = STAILQ_FIRST(&daemon->zones);
++
++		// Dereference the zone
++		nw_zone_unref(entry->zone);
++
++		// Remove the entry from the list
++		STAILQ_REMOVE_HEAD(&daemon->zones, nodes);
++
++		// Free the entry
++		free(entry);
++	}
++}
++
+ static void nw_daemon_free(struct nw_daemon* daemon) {
++	nw_daemon_free_zones(daemon);
++
+ 	if (daemon->config)
+ 		nw_config_unref(daemon->config);
+ 	if (daemon->bus)
+-- 
+2.39.2
+
diff --git a/network/patches/0171-networkd-bus-Create-a-unified-function-to-register-a.patch b/network/patches/0171-networkd-bus-Create-a-unified-function-to-register-a.patch
new file mode 100644
index 000000000..3cca53689
--- /dev/null
+++ b/network/patches/0171-networkd-bus-Create-a-unified-function-to-register-a.patch
@@ -0,0 +1,128 @@
+From 9806a600f4cdf68aa2c0e509e40be6ed9ad08b44 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 23:04:27 +0000
+Subject: [PATCH 171/304] networkd: bus: Create a unified function to register
+ an interface
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/bus.c | 52 +++++++++++++++++++++++++++++++++-------------
+ src/networkd/bus.h | 14 +++++++++++--
+ 2 files changed, 49 insertions(+), 17 deletions(-)
+
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+index 5224aaa..eab123c 100644
+--- a/src/networkd/bus.c
++++ b/src/networkd/bus.c
+@@ -38,6 +38,19 @@ static int nw_bus_daemon_reload(sd_bus_message* m, void* data, sd_bus_error* err
+ 	return sd_bus_reply_method_return(m, NULL);
+ }
+ 
++static const sd_bus_vtable daemon_vtable[] = {
++        SD_BUS_VTABLE_START(0),
++        SD_BUS_METHOD("Reload", SD_BUS_NO_ARGS, SD_BUS_NO_RESULT,
++			nw_bus_daemon_reload, SD_BUS_VTABLE_UNPRIVILEGED),
++        SD_BUS_VTABLE_END,
++};
++
++const struct nw_bus_implementation daemon_implementation = {
++	.path = "/org/ipfire/network1",
++	.interface = "org.ipfire.network1",
++	.vtables = BUS_VTABLES(daemon_vtable),
++};
++
+ static int nw_bus_on_connect(sd_bus_message* m, void* data, sd_bus_error* error) {
+ 	struct nw_daemon* daemon = (struct nw_daemon*)data;
+ 
+@@ -46,13 +59,6 @@ static int nw_bus_on_connect(sd_bus_message* m, void* data, sd_bus_error* error)
+ 	return 0;
+ }
+ 
+-static const sd_bus_vtable daemon_vtable[] = {
+-        SD_BUS_VTABLE_START(0),
+-        SD_BUS_METHOD("Reload", SD_BUS_NO_ARGS, SD_BUS_NO_RESULT,
+-        	nw_bus_daemon_reload, SD_BUS_VTABLE_UNPRIVILEGED),
+-        SD_BUS_VTABLE_END,
+-};
+-
+ int nw_bus_connect(sd_bus* bus, sd_event* loop) {
+ 	int r;
+ 
+@@ -117,16 +123,13 @@ int nw_bus_connect(sd_bus* bus, sd_event* loop) {
+ 		return 1;
+ 	}
+ 
+-	// Register any functions
+-	r = sd_bus_add_object_vtable(bus, NULL,
+-		NETWORKD_BUS_OBJECT_PATH, NETWORKD_BUS_INTERFACE_NAME, daemon_vtable, NULL);
+-	if (r < 0) {
+-		ERROR("Could not add object vtable: %s\n", strerror(-r));
+-		return 1;
+-	}
++	// Register the implementation
++	r = nw_bus_register_implementation(bus, &daemon_implementation, NULL);
++	if (r)
++		return r;
+ 
+ 	// Request interface name
+-	r = sd_bus_request_name_async(bus, NULL, NETWORKD_BUS_INTERFACE_NAME, 0, NULL, NULL);
++	r = sd_bus_request_name_async(bus, NULL, "org.ipfire.network1", 0, NULL, NULL);
+ 	if (r < 0) {
+ 		ERROR("Could not request bus name: %s\n", strerror(-r));
+ 		return 1;
+@@ -149,3 +152,22 @@ int nw_bus_connect(sd_bus* bus, sd_event* loop) {
+ 
+ 	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);
++
++	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;
++		}
++	}
++
++	return 0;
++}
+diff --git a/src/networkd/bus.h b/src/networkd/bus.h
+index 90c1556..05846e5 100644
+--- a/src/networkd/bus.h
++++ b/src/networkd/bus.h
+@@ -22,8 +22,6 @@
+ #define NETWORKD_BUS_H
+ 
+ #define NETWORKD_BUS_DESCRIPTION		"networkd"
+-#define NETWORKD_BUS_OBJECT_PATH		"/org/ipfire/network1"
+-#define NETWORKD_BUS_INTERFACE_NAME		"org.ipfire.network1"
+ 
+ #define DEFAULT_SYSTEM_BUS_ADDRESS		"unix:path=/run/dbus/system_bus_socket"
+ 
+@@ -32,4 +30,16 @@
+ 
+ int nw_bus_connect(sd_bus* bus, sd_event* loop);
+ 
++struct nw_bus_implementation {
++	const char* path;
++	const char* interface;
++
++	const sd_bus_vtable** vtables;
++};
++
++#define BUS_VTABLES(...) ((const sd_bus_vtable* []){ __VA_ARGS__, NULL })
++
++int nw_bus_register_implementation(sd_bus* bus,
++	const struct nw_bus_implementation* impl, void* data);
++
+ #endif /* NETWORKD_BUS_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0172-networkd-Add-a-dummy-bus-implementation-for-zones.patch b/network/patches/0172-networkd-Add-a-dummy-bus-implementation-for-zones.patch
new file mode 100644
index 000000000..6aeef4273
--- /dev/null
+++ b/network/patches/0172-networkd-Add-a-dummy-bus-implementation-for-zones.patch
@@ -0,0 +1,99 @@
+From ed00ef209405d6da4478a8eb050e8792187a2619 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 23:19:03 +0000
+Subject: [PATCH 172/304] networkd: Add a dummy bus implementation for zones
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am             |  4 +++-
+ src/networkd/zone-bus.c | 27 +++++++++++++++++++++++++++
+ src/networkd/zone-bus.h | 28 ++++++++++++++++++++++++++++
+ 3 files changed, 58 insertions(+), 1 deletion(-)
+ create mode 100644 src/networkd/zone-bus.c
+ create mode 100644 src/networkd/zone-bus.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 3a3f82c..2046fef 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -318,7 +318,9 @@ dist_networkd_SOURCES = \
+ 	src/networkd/main.c \
+ 	src/networkd/string.h \
+ 	src/networkd/zone.c \
+-	src/networkd/zone.h
++	src/networkd/zone.h \
++	src/networkd/zone-bus.c \
++	src/networkd/zone-bus.h
+ 
+ networkd_CPPFLAGS = \
+ 	$(AM_CPPFLAGS) \
+diff --git a/src/networkd/zone-bus.c b/src/networkd/zone-bus.c
+new file mode 100644
+index 0000000..97396c5
+--- /dev/null
++++ b/src/networkd/zone-bus.c
+@@ -0,0 +1,27 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include "bus.h"
++#include "zone-bus.h"
++
++const struct nw_bus_implementation zone_bus_impl = {
++	"/org/ipfire/network1/zone",
++	"org.ipfire.network1.Zone",
++};
+diff --git a/src/networkd/zone-bus.h b/src/networkd/zone-bus.h
+new file mode 100644
+index 0000000..0d5583e
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_ZONE_BUS_H
++#define NETWORKD_ZONE_BUS_H
++
++#include "bus.h"
++
++extern const struct nw_bus_implementation zone_bus_impl;
++
++#endif /* NETWORKD_ZONE_BUS_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0173-networkd-Pass-daemon-to-all-functions-called-by-the-.patch b/network/patches/0173-networkd-Pass-daemon-to-all-functions-called-by-the-.patch
new file mode 100644
index 000000000..d8ff55d71
--- /dev/null
+++ b/network/patches/0173-networkd-Pass-daemon-to-all-functions-called-by-the-.patch
@@ -0,0 +1,66 @@
+From eaeca0f9f44f0c791f21add7162ee6c7eea5ee4a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Feb 2023 23:25:17 +0000
+Subject: [PATCH 173/304] networkd: Pass daemon to all functions called by the
+ bus
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/bus.c    | 4 ++--
+ src/networkd/bus.h    | 4 +++-
+ src/networkd/daemon.c | 2 +-
+ 3 files changed, 6 insertions(+), 4 deletions(-)
+
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+index eab123c..e854f7b 100644
+--- a/src/networkd/bus.c
++++ b/src/networkd/bus.c
+@@ -59,7 +59,7 @@ static int nw_bus_on_connect(sd_bus_message* m, void* data, sd_bus_error* error)
+ 	return 0;
+ }
+ 
+-int nw_bus_connect(sd_bus* bus, sd_event* loop) {
++int nw_bus_connect(sd_bus* bus, sd_event* loop, struct nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Create a bus object
+@@ -124,7 +124,7 @@ int nw_bus_connect(sd_bus* bus, sd_event* loop) {
+ 	}
+ 
+ 	// Register the implementation
+-	r = nw_bus_register_implementation(bus, &daemon_implementation, NULL);
++	r = nw_bus_register_implementation(bus, &daemon_implementation, daemon);
+ 	if (r)
+ 		return r;
+ 
+diff --git a/src/networkd/bus.h b/src/networkd/bus.h
+index 05846e5..55e65b8 100644
+--- a/src/networkd/bus.h
++++ b/src/networkd/bus.h
+@@ -28,7 +28,9 @@
+ #include <systemd/sd-bus.h>
+ #include <systemd/sd-event.h>
+ 
+-int nw_bus_connect(sd_bus* bus, sd_event* loop);
++#include "daemon.h"
++
++int nw_bus_connect(sd_bus* bus, sd_event* loop, struct nw_daemon* daemon);
+ 
+ struct nw_bus_implementation {
+ 	const char* path;
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 86731d1..a478e60 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -227,7 +227,7 @@ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 		return r;
+ 
+ 	// Connect to the system bus
+-	r = nw_bus_connect(daemon->bus, daemon->loop);
++	r = nw_bus_connect(daemon->bus, daemon->loop, daemon);
+ 	if (r)
+ 		return r;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0174-networkd-Move-zone-list-into-an-own-object.patch b/network/patches/0174-networkd-Move-zone-list-into-an-own-object.patch
new file mode 100644
index 000000000..6145c971e
--- /dev/null
+++ b/network/patches/0174-networkd-Move-zone-list-into-an-own-object.patch
@@ -0,0 +1,493 @@
+From 0c8e7342a7da002e877ed445d0223453acd4b93d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 2 Feb 2023 00:38:13 +0000
+Subject: [PATCH 174/304] networkd: Move zone list into an own object
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |   2 +
+ src/networkd/daemon.c | 121 ++----------------------
+ src/networkd/daemon.h |   4 +
+ src/networkd/zones.c  | 213 ++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/zones.h  |  35 +++++++
+ 5 files changed, 263 insertions(+), 112 deletions(-)
+ create mode 100644 src/networkd/zones.c
+ create mode 100644 src/networkd/zones.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 2046fef..ae4cb85 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -317,6 +317,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/logging.h \
+ 	src/networkd/main.c \
+ 	src/networkd/string.h \
++	src/networkd/zones.c \
++	src/networkd/zones.h \
+ 	src/networkd/zone.c \
+ 	src/networkd/zone.h \
+ 	src/networkd/zone-bus.c \
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index a478e60..588aa4e 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -18,11 +18,8 @@
+ #                                                                             #
+ #############################################################################*/
+ 
+-#include <dirent.h>
+ #include <errno.h>
+ #include <stdlib.h>
+-#include <string.h>
+-#include <sys/queue.h>
+ 
+ #include <systemd/sd-bus.h>
+ #include <systemd/sd-daemon.h>
+@@ -33,13 +30,7 @@
+ #include "daemon.h"
+ #include "logging.h"
+ #include "zone.h"
+-
+-struct nw_daemon_zone {
+-	struct nw_zone* zone;
+-
+-	// Link to the other zones
+-	STAILQ_ENTRY(nw_daemon_zone) nodes;
+-};
++#include "zones.h"
+ 
+ struct nw_daemon {
+ 	int nrefs;
+@@ -53,7 +44,7 @@ struct nw_daemon {
+ 	sd_bus* bus;
+ 
+ 	// Zones
+-	STAILQ_HEAD(zones, nw_daemon_zone) zones;
++	struct nw_zones* zones;
+ };
+ 
+ static int __nw_daemon_terminate(sd_event_source* source, const struct signalfd_siginfo* si,
+@@ -119,84 +110,6 @@ static int nw_daemon_setup_loop(struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
+-static int nw_daemon_load_zone_filter(const struct dirent* path) {
+-	const char* fn = path->d_name;
+-
+-	// Ignore everything starting with '.'
+-	if (*fn == '.')
+-		return 0;
+-
+-	// Ignore anything that isn't a directory
+-	if (path->d_type != DT_DIR)
+-		return 0;
+-
+-	return 1;
+-}
+-
+-static int nw_daemon_add_zone(struct nw_daemon* daemon, struct nw_zone* zone) {
+-	// Allocate a new entry
+-	struct nw_daemon_zone* 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(&daemon->zones, entry, nodes);
+-
+-	return 0;
+-}
+-
+-static int nw_daemon_load_zones(struct nw_daemon* daemon) {
+-	struct dirent** paths = NULL;
+-	int n;
+-	int r = 0;
+-
+-	struct nw_zone* zone = NULL;
+-
+-	// Scan the zones directory
+-	n = scandir(CONFIG_DIR "/zones", &paths, nw_daemon_load_zone_filter, alphasort);
+-	if (n < 0) {
+-		ERROR("Could not load zones: %m\n");
+-		return 1;
+-	}
+-
+-	DEBUG("Found %d zone(s)\n", n);
+-
+-	// Load all zones
+-	for (int i = 0; i < n; i++) {
+-		const char* name = paths[i]->d_name;
+-
+-		DEBUG("Loading zone '%s'...\n", name);
+-
+-		// Create a new zone object
+-		r = nw_zone_create(&zone, name);
+-		if (r)
+-			goto ERROR;
+-
+-		// Store the zone
+-		r = nw_daemon_add_zone(daemon, zone);
+-		if (r) {
+-			nw_zone_unref(zone);
+-			goto ERROR;
+-		}
+-
+-		nw_zone_unref(zone);
+-	}
+-
+-ERROR:
+-	// Free paths
+-	if (paths) {
+-		for (int i = 0; i < n; i++) {
+-			free(paths[i]);
+-		}
+-		free(paths);
+-	}
+-
+-	return r;
+-}
+-
+ static int nw_daemon_load_config(struct nw_daemon* daemon) {
+ 	int r;
+ 
+@@ -206,7 +119,7 @@ static int nw_daemon_load_config(struct nw_daemon* daemon) {
+ 		return r;
+ 
+ 	// Load zones
+-	r = nw_daemon_load_zones(daemon);
++	r = nw_zones_load(&daemon->zones);
+ 	if (r)
+ 		return r;
+ 
+@@ -244,9 +157,6 @@ int nw_daemon_create(struct nw_daemon** daemon) {
+ 	// Initialize reference counter
+ 	d->nrefs = 1;
+ 
+-	// Initialize zones
+-	STAILQ_INIT(&d->zones);
+-
+ 	// Setup the daemon
+ 	r = nw_daemon_setup(d);
+ 	if (r)
+@@ -263,26 +173,9 @@ ERROR:
+ 	return r;
+ }
+ 
+-static void nw_daemon_free_zones(struct nw_daemon* daemon) {
+-	struct nw_daemon_zone* entry = NULL;
+-
+-	while (!STAILQ_EMPTY(&daemon->zones)) {
+-		entry = STAILQ_FIRST(&daemon->zones);
+-
+-		// Dereference the zone
+-		nw_zone_unref(entry->zone);
+-
+-		// Remove the entry from the list
+-		STAILQ_REMOVE_HEAD(&daemon->zones, nodes);
+-
+-		// Free the entry
+-		free(entry);
+-	}
+-}
+-
+ static void nw_daemon_free(struct nw_daemon* daemon) {
+-	nw_daemon_free_zones(daemon);
+-
++	if (daemon->zones)
++		nw_zones_unref(daemon->zones);
+ 	if (daemon->config)
+ 		nw_config_unref(daemon->config);
+ 	if (daemon->bus)
+@@ -342,3 +235,7 @@ int nw_daemon_reload(struct nw_daemon* daemon) {
+ 
+ 	return 0;
+ }
++
++struct nw_zones* nw_daemon_zones(struct nw_daemon* daemon) {
++	return nw_zones_ref(daemon->zones);
++}
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index 5b14ef3..bc69795 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -21,6 +21,8 @@
+ #ifndef NETWORKD_DAEMON_H
+ #define NETWORKD_DAEMON_H
+ 
++#include "zone.h"
++
+ struct nw_daemon;
+ 
+ int nw_daemon_create(struct nw_daemon** daemon);
+@@ -32,4 +34,6 @@ int nw_daemon_run(struct nw_daemon* daemon);
+ 
+ int nw_daemon_reload(struct nw_daemon* daemon);
+ 
++struct nw_zones* nw_daemon_zones(struct nw_daemon* daemon);
++
+ #endif /* NETWORKD_DAEMON_H */
+diff --git a/src/networkd/zones.c b/src/networkd/zones.c
+new file mode 100644
+index 0000000..7d04267
+--- /dev/null
++++ b/src/networkd/zones.c
+@@ -0,0 +1,213 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <dirent.h>
++#include <stdlib.h>
++#include <string.h>
++#include <sys/queue.h>
++
++#include "logging.h"
++#include "zone.h"
++#include "zones.h"
++
++struct nw_zones_entry {
++	struct nw_zone* zone;
++
++	// Link to the other entries
++	STAILQ_ENTRY(nw_zones_entry) nodes;
++};
++
++struct nw_zones {
++	int nrefs;
++
++	STAILQ_HEAD(entries, nw_zones_entry) entries;
++};
++
++static int nw_zones_create(struct nw_zones** zones) {
++	struct nw_zones* z = calloc(1, sizeof(*z));
++	if (!z)
++		return 1;
++
++	// 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(struct 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);
++	}
++}
++
++struct nw_zones* nw_zones_ref(struct nw_zones* zones) {
++	zones->nrefs++;
++
++	return zones;
++}
++
++struct nw_zones* nw_zones_unref(struct nw_zones* zones) {
++	if (--zones->nrefs > 0)
++		return zones;
++
++	nw_zones_free(zones);
++	return NULL;
++}
++
++static int nw_zones_add_zone(struct nw_zones* zones, struct 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);
++
++	return 0;
++}
++
++static int nw_zones_load_filter(const struct dirent* path) {
++	const char* fn = path->d_name;
++
++	// Ignore everything starting with '.'
++	if (*fn == '.')
++		return 0;
++
++	// Ignore anything that isn't a directory
++	if (path->d_type != DT_DIR)
++		return 0;
++
++	return 1;
++}
++
++static int __nw_zones_load(struct nw_zones* zones) {
++	struct dirent** paths = NULL;
++	int n;
++	int r = 0;
++
++	struct nw_zone* zone = NULL;
++
++	// Scan the zones directory
++	n = scandir(CONFIG_DIR "/zones", &paths, nw_zones_load_filter, alphasort);
++	if (n < 0) {
++		ERROR("Could not load zones: %m\n");
++		return 1;
++	}
++
++	DEBUG("Found %d zone(s)\n", n);
++
++	// Load all zones
++	for (int i = 0; i < n; i++) {
++		const char* name = paths[i]->d_name;
++
++		DEBUG("Loading zone '%s'...\n", name);
++
++		// Create a new zone object
++		r = nw_zone_create(&zone, name);
++		if (r)
++			goto ERROR;
++
++		// Store the zone
++		r = nw_zones_add_zone(zones, zone);
++		if (r) {
++			nw_zone_unref(zone);
++			goto ERROR;
++		}
++
++		nw_zone_unref(zone);
++	}
++
++ERROR:
++	// Free paths
++	if (paths) {
++		for (int i = 0; i < n; i++) {
++			free(paths[i]);
++		}
++		free(paths);
++	}
++
++	return r;
++}
++
++int nw_zones_load(struct nw_zones** zones) {
++	int r;
++
++	// Create a new zones object
++	r = nw_zones_create(zones);
++	if (r)
++		return r;
++
++	// Load all zones
++	r = __nw_zones_load(*zones);
++	if (r)
++		goto ERROR;
++
++	return 0;
++
++ERROR:
++	nw_zones_unref(*zones);
++	return r;
++}
++
++size_t nw_zones_num(struct 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;
++}
++
++struct nw_zone* nw_zones_get_by_name(struct 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;
++}
+diff --git a/src/networkd/zones.h b/src/networkd/zones.h
+new file mode 100644
+index 0000000..0d34c3e
+--- /dev/null
++++ b/src/networkd/zones.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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_ZONES_H
++#define NETWORKD_ZONES_H
++
++struct nw_zones;
++
++struct nw_zones* nw_zones_ref(struct nw_zones* zones);
++struct nw_zones* nw_zones_unref(struct nw_zones* zones);
++
++int nw_zones_load(struct nw_zones** zones);
++
++size_t nw_zones_num(struct nw_zones* zones);
++
++struct nw_zone* nw_zones_get_by_name(struct nw_zones* zones, const char* name);
++
++#endif /* NETWORKD_ZONES_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0175-networkd-Implement-enumerating-zones-on-the-bus.patch b/network/patches/0175-networkd-Implement-enumerating-zones-on-the-bus.patch
new file mode 100644
index 000000000..490d257c6
--- /dev/null
+++ b/network/patches/0175-networkd-Implement-enumerating-zones-on-the-bus.patch
@@ -0,0 +1,238 @@
+From ebc65f19ecbf67f9c8a739316afe4892d4f6a732 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 2 Feb 2023 00:53:21 +0000
+Subject: [PATCH 175/304] networkd: Implement enumerating zones on the bus
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/bus.c      | 18 +++++++++++++++++
+ src/networkd/bus.h      |  3 +++
+ src/networkd/zone-bus.c | 29 +++++++++++++++++++++++++++
+ src/networkd/zone.c     | 14 +++++++++++++
+ src/networkd/zone.h     |  2 ++
+ src/networkd/zones.c    | 44 +++++++++++++++++++++++++++++++++++++++++
+ src/networkd/zones.h    |  2 ++
+ 7 files changed, 112 insertions(+)
+
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+index e854f7b..26876fa 100644
+--- a/src/networkd/bus.c
++++ b/src/networkd/bus.c
+@@ -27,6 +27,7 @@
+ #include "bus.h"
+ #include "daemon.h"
+ #include "logging.h"
++#include "zone-bus.h"
+ 
+ static int nw_bus_daemon_reload(sd_bus_message* m, void* data, sd_bus_error* error) {
+ 	struct nw_daemon* daemon = (struct nw_daemon*)data;
+@@ -49,6 +50,7 @@ const struct nw_bus_implementation daemon_implementation = {
+ 	.path = "/org/ipfire/network1",
+ 	.interface = "org.ipfire.network1",
+ 	.vtables = BUS_VTABLES(daemon_vtable),
++	.children = BUS_IMPLEMENTATIONS(&zone_bus_impl),
+ };
+ 
+ static int nw_bus_on_connect(sd_bus_message* m, void* data, sd_bus_error* error) {
+@@ -169,5 +171,21 @@ int nw_bus_register_implementation(sd_bus* bus,
+ 		}
+ 	}
+ 
++	// 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
+index 55e65b8..6232ebb 100644
+--- a/src/networkd/bus.h
++++ b/src/networkd/bus.h
+@@ -37,8 +37,11 @@ struct nw_bus_implementation {
+ 	const char* interface;
+ 
+ 	const sd_bus_vtable** vtables;
++	sd_bus_node_enumerator_t node_enumerator;
++	const struct nw_bus_implementation** children;
+ };
+ 
++#define BUS_IMPLEMENTATIONS(...) ((const struct nw_bus_implementation* []) { __VA_ARGS__, NULL })
+ #define BUS_VTABLES(...) ((const sd_bus_vtable* []){ __VA_ARGS__, NULL })
+ 
+ int nw_bus_register_implementation(sd_bus* bus,
+diff --git a/src/networkd/zone-bus.c b/src/networkd/zone-bus.c
+index 97396c5..ebd8789 100644
+--- a/src/networkd/zone-bus.c
++++ b/src/networkd/zone-bus.c
+@@ -19,9 +19,38 @@
+ #############################################################################*/
+ 
+ #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
++	struct nw_daemon* daemon = (struct nw_daemon*)data;
++
++	// Fetch zones
++	struct 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;
++}
+ 
+ const struct nw_bus_implementation zone_bus_impl = {
+ 	"/org/ipfire/network1/zone",
+ 	"org.ipfire.network1.Zone",
++	//.fallback_vtables = BUS_FALLBACK_VTABLES({zone_bus_vtable, nw_zone_object_find}),
++	.node_enumerator = nw_zone_node_enumerator,
+ };
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index 4790d33..f78334e 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -21,6 +21,8 @@
+ #include <limits.h>
+ #include <stdlib.h>
+ 
++#include <systemd/sd-bus.h>
++
+ #include "config.h"
+ #include "string.h"
+ #include "zone.h"
+@@ -130,3 +132,15 @@ struct nw_zone* nw_zone_unref(struct nw_zone* zone) {
+ const char* nw_zone_name(struct nw_zone* zone) {
+ 	return zone->name;
+ }
++
++char* nw_zone_bus_path(struct 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;
++}
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 78287ab..081a720 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -32,4 +32,6 @@ struct nw_zone* nw_zone_unref(struct nw_zone* zone);
+ 
+ const char* nw_zone_name(struct nw_zone* zone);
+ 
++char* nw_zone_bus_path(struct nw_zone* zone);
++
+ #endif /* NETWORKD_ZONE_H */
+diff --git a/src/networkd/zones.c b/src/networkd/zones.c
+index 7d04267..4f739f5 100644
+--- a/src/networkd/zones.c
++++ b/src/networkd/zones.c
+@@ -37,7 +37,11 @@ struct nw_zones_entry {
+ struct nw_zones {
+ 	int nrefs;
+ 
++	// Zone Entries
+ 	STAILQ_HEAD(entries, nw_zones_entry) entries;
++
++	// A counter of the zone entries
++	unsigned int num;
+ };
+ 
+ static int nw_zones_create(struct nw_zones** zones) {
+@@ -100,6 +104,9 @@ static int nw_zones_add_zone(struct nw_zones* zones, struct nw_zone* zone) {
+ 	// Add it to the list
+ 	STAILQ_INSERT_TAIL(&zones->entries, entry, nodes);
+ 
++	// Increment the counter
++	zones->num++;
++
+ 	return 0;
+ }
+ 
+@@ -211,3 +218,40 @@ struct nw_zone* nw_zones_get_by_name(struct nw_zones* zones, const char* name) {
+ 	// No match found
+ 	return NULL;
+ }
++
++int nw_zones_bus_paths(struct 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;
++}
+diff --git a/src/networkd/zones.h b/src/networkd/zones.h
+index 0d34c3e..6e2686d 100644
+--- a/src/networkd/zones.h
++++ b/src/networkd/zones.h
+@@ -32,4 +32,6 @@ size_t nw_zones_num(struct nw_zones* zones);
+ 
+ struct nw_zone* nw_zones_get_by_name(struct nw_zones* zones, const char* name);
+ 
++int nw_zones_bus_paths(struct nw_zones* zones, char*** paths);
++
+ #endif /* NETWORKD_ZONES_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0176-networkd-Return-zone-when-it-is-being-accessed-by-it.patch b/network/patches/0176-networkd-Return-zone-when-it-is-being-accessed-by-it.patch
new file mode 100644
index 000000000..0f442e34b
--- /dev/null
+++ b/network/patches/0176-networkd-Return-zone-when-it-is-being-accessed-by-it.patch
@@ -0,0 +1,159 @@
+From bd9ffd6a19a8bf403a534998e0c2d3e2bf9b51c1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 2 Feb 2023 01:07:51 +0000
+Subject: [PATCH 176/304] networkd: Return zone when it is being accessed by
+ its path
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/bus.c      | 12 ++++++++++++
+ src/networkd/bus.h      |  8 +++++++-
+ src/networkd/daemon.c   | 11 +++++++++++
+ src/networkd/daemon.h   |  4 ++++
+ src/networkd/zone-bus.c | 34 +++++++++++++++++++++++++++++++++-
+ 5 files changed, 67 insertions(+), 2 deletions(-)
+
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+index 26876fa..cab2ae6 100644
+--- a/src/networkd/bus.c
++++ b/src/networkd/bus.c
+@@ -162,6 +162,7 @@ int nw_bus_register_implementation(sd_bus* bus,
+ 	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) {
+@@ -171,6 +172,17 @@ int nw_bus_register_implementation(sd_bus* bus,
+ 		}
+ 	}
+ 
++	// 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);
+diff --git a/src/networkd/bus.h b/src/networkd/bus.h
+index 6232ebb..42ab2d3 100644
+--- a/src/networkd/bus.h
++++ b/src/networkd/bus.h
+@@ -32,15 +32,21 @@
+ 
+ int nw_bus_connect(sd_bus* bus, sd_event* loop, struct nw_daemon* daemon);
+ 
++struct nw_bus_vtable_pair {
++	const sd_bus_vtable* vtable;
++	sd_bus_object_find_t object_find;
++};
++
+ 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;
+ };
+ 
++#define BUS_FALLBACK_VTABLES(...) ((const struct nw_bus_vtable_pair[]) { __VA_ARGS__, {} })
+ #define BUS_IMPLEMENTATIONS(...) ((const struct nw_bus_implementation* []) { __VA_ARGS__, NULL })
+ #define BUS_VTABLES(...) ((const sd_bus_vtable* []){ __VA_ARGS__, NULL })
+ 
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 588aa4e..5b5cbb4 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -236,6 +236,17 @@ int nw_daemon_reload(struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
++/*
++	Zones
++*/
++
+ struct nw_zones* nw_daemon_zones(struct nw_daemon* daemon) {
+ 	return nw_zones_ref(daemon->zones);
+ }
++
++struct nw_zone* nw_daemon_get_zone_by_name(struct 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
+index bc69795..891c297 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -34,6 +34,10 @@ int nw_daemon_run(struct nw_daemon* daemon);
+ 
+ int nw_daemon_reload(struct nw_daemon* daemon);
+ 
++/*
++	Zones
++*/
+ struct nw_zones* nw_daemon_zones(struct nw_daemon* daemon);
++struct nw_zone* nw_daemon_get_zone_by_name(struct nw_daemon* daemon, const char* name);
+ 
+ #endif /* NETWORKD_DAEMON_H */
+diff --git a/src/networkd/zone-bus.c b/src/networkd/zone-bus.c
+index ebd8789..aafbfaf 100644
+--- a/src/networkd/zone-bus.c
++++ b/src/networkd/zone-bus.c
+@@ -48,9 +48,41 @@ ERROR:
+ 	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
++	struct nw_daemon* daemon = (struct 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
++	struct nw_zone* zone = nw_daemon_get_zone_by_name(daemon, name);
++	if (!zone)
++		return 0;
++
++	// Match!
++	*found = zone;
++
++	nw_zone_unref(zone);
++
++	return 1;
++}
++
++static const sd_bus_vtable zone_vtable[] = {
++	SD_BUS_VTABLE_START(0),
++
++	SD_BUS_VTABLE_END
++};
++
+ const struct nw_bus_implementation zone_bus_impl = {
+ 	"/org/ipfire/network1/zone",
+ 	"org.ipfire.network1.Zone",
+-	//.fallback_vtables = BUS_FALLBACK_VTABLES({zone_bus_vtable, nw_zone_object_find}),
++	.fallback_vtables = BUS_FALLBACK_VTABLES({zone_vtable, nw_zone_object_find}),
+ 	.node_enumerator = nw_zone_node_enumerator,
+ };
+-- 
+2.39.2
+
diff --git a/network/patches/0177-networkd-Split-daemon-bus-implementation-into-a-sepa.patch b/network/patches/0177-networkd-Split-daemon-bus-implementation-into-a-sepa.patch
new file mode 100644
index 000000000..d1219c248
--- /dev/null
+++ b/network/patches/0177-networkd-Split-daemon-bus-implementation-into-a-sepa.patch
@@ -0,0 +1,175 @@
+From d6edaf630588fbc6b54a315f49f252e226c15342 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 2 Feb 2023 01:23:31 +0000
+Subject: [PATCH 177/304] networkd: Split daemon bus implementation into a
+ separate file
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am               |  2 ++
+ src/networkd/bus.c        | 29 ++---------------------
+ src/networkd/daemon-bus.c | 49 +++++++++++++++++++++++++++++++++++++++
+ src/networkd/daemon-bus.h | 28 ++++++++++++++++++++++
+ 4 files changed, 81 insertions(+), 27 deletions(-)
+ create mode 100644 src/networkd/daemon-bus.c
+ create mode 100644 src/networkd/daemon-bus.h
+
+diff --git a/Makefile.am b/Makefile.am
+index ae4cb85..3972d8d 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -314,6 +314,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/config.h \
+ 	src/networkd/daemon.c \
+ 	src/networkd/daemon.h \
++	src/networkd/daemon-bus.c \
++	src/networkd/daemon-bus.h \
+ 	src/networkd/logging.h \
+ 	src/networkd/main.c \
+ 	src/networkd/string.h \
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+index cab2ae6..258aacd 100644
+--- a/src/networkd/bus.c
++++ b/src/networkd/bus.c
+@@ -19,39 +19,14 @@
+ #############################################################################*/
+ 
+ #include <stdlib.h>
+-#include <string.h>
+ 
+ #include <systemd/sd-bus.h>
+ #include <systemd/sd-event.h>
+ 
+ #include "bus.h"
+ #include "daemon.h"
++#include "daemon-bus.h"
+ #include "logging.h"
+-#include "zone-bus.h"
+-
+-static int nw_bus_daemon_reload(sd_bus_message* m, void* data, sd_bus_error* error) {
+-	struct nw_daemon* daemon = (struct nw_daemon*)data;
+-
+-	// Reload the daemon
+-	nw_daemon_reload(daemon);
+-
+-	// Respond with an empty message
+-	return sd_bus_reply_method_return(m, NULL);
+-}
+-
+-static const sd_bus_vtable daemon_vtable[] = {
+-        SD_BUS_VTABLE_START(0),
+-        SD_BUS_METHOD("Reload", SD_BUS_NO_ARGS, SD_BUS_NO_RESULT,
+-			nw_bus_daemon_reload, SD_BUS_VTABLE_UNPRIVILEGED),
+-        SD_BUS_VTABLE_END,
+-};
+-
+-const struct nw_bus_implementation daemon_implementation = {
+-	.path = "/org/ipfire/network1",
+-	.interface = "org.ipfire.network1",
+-	.vtables = BUS_VTABLES(daemon_vtable),
+-	.children = BUS_IMPLEMENTATIONS(&zone_bus_impl),
+-};
+ 
+ static int nw_bus_on_connect(sd_bus_message* m, void* data, sd_bus_error* error) {
+ 	struct nw_daemon* daemon = (struct nw_daemon*)data;
+@@ -126,7 +101,7 @@ int nw_bus_connect(sd_bus* bus, sd_event* loop, struct nw_daemon* daemon) {
+ 	}
+ 
+ 	// Register the implementation
+-	r = nw_bus_register_implementation(bus, &daemon_implementation, daemon);
++	r = nw_bus_register_implementation(bus, &daemon_bus_impl, daemon);
+ 	if (r)
+ 		return r;
+ 
+diff --git a/src/networkd/daemon-bus.c b/src/networkd/daemon-bus.c
+new file mode 100644
+index 0000000..aac9775
+--- /dev/null
++++ b/src/networkd/daemon-bus.c
+@@ -0,0 +1,49 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <systemd/sd-bus.h>
++
++#include "bus.h"
++#include "daemon.h"
++#include "zone-bus.h"
++
++static int nw_daemon_bus_reload(sd_bus_message* m, void* data, sd_bus_error* error) {
++	struct nw_daemon* daemon = (struct nw_daemon*)data;
++
++	// Reload the daemon
++	nw_daemon_reload(daemon);
++
++	// Respond with an empty message
++	return sd_bus_reply_method_return(m, NULL);
++}
++
++static const sd_bus_vtable daemon_vtable[] = {
++	SD_BUS_VTABLE_START(0),
++	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 struct nw_bus_implementation daemon_bus_impl = {
++	.path = "/org/ipfire/network1",
++	.interface = "org.ipfire.network1",
++	.vtables = BUS_VTABLES(daemon_vtable),
++	.children = BUS_IMPLEMENTATIONS(&zone_bus_impl),
++};
+diff --git a/src/networkd/daemon-bus.h b/src/networkd/daemon-bus.h
+new file mode 100644
+index 0000000..787e92a
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_DAEMON_BUS_H
++#define NETWORKD_DAEMON_BUS_H
++
++#include "bus.h"
++
++extern const struct nw_bus_implementation daemon_bus_impl;
++
++#endif /* NETWORKD_DAEMON_BUS_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0178-networkd-Add-a-test-bus-property-to-set-the-MTU.patch b/network/patches/0178-networkd-Add-a-test-bus-property-to-set-the-MTU.patch
new file mode 100644
index 000000000..2e5452d76
--- /dev/null
+++ b/network/patches/0178-networkd-Add-a-test-bus-property-to-set-the-MTU.patch
@@ -0,0 +1,201 @@
+From 9368a163388c9f8665dbdedaccae70ee168df714 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 4 Feb 2023 22:10:56 +0000
+Subject: [PATCH 178/304] networkd: Add a test bus property to set the MTU
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c   | 38 +++++++++++++++++++++++++-------------
+ src/networkd/config.h   |  5 +++--
+ src/networkd/zone-bus.c | 34 ++++++++++++++++++++++++++++++++++
+ src/networkd/zone.c     | 14 ++++++++++++++
+ src/networkd/zone.h     |  7 +++++++
+ 5 files changed, 83 insertions(+), 15 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index e531ec6..788308f 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -273,6 +273,17 @@ int nw_config_del(struct nw_config* config, const char* key) {
+ 	return 0;
+ }
+ 
++const char* nw_config_get(struct 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(struct nw_config* config, const char* key, const char* value) {
+ 	struct nw_config_entry* entry = NULL;
+ 
+@@ -294,23 +305,24 @@ int nw_config_set(struct nw_config* config, const char* key, const char* value)
+ 	return nw_string_set(entry->value, value);
+ }
+ 
+-const char* nw_config_get(struct 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;
+-}
+-
+-unsigned int nw_config_get_unsigned_int(struct nw_config* config, const char* key) {
++int nw_config_get_int(struct nw_config* config, const char* key, const int __default) {
+ 	const char* value = nw_config_get(config, key);
+ 
+ 	// Return zero if not set
+ 	if (!value)
+-		return 0;
++		return __default;
+ 
+ 	return strtoul(value, NULL, 10);
+ }
++
++int nw_config_set_int(struct 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\n", value);
++	if (r)
++		return r;
++
++	return nw_config_set(config, key, __value);
++}
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index 5b9910b..b8d8555 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -42,9 +42,10 @@ int nw_config_write(struct nw_config* config);
+ 
+ int nw_config_del(struct nw_config* config, const char* key);
+ 
++const char* nw_config_get(struct nw_config* config, const char* key);
+ int nw_config_set(struct nw_config* config, const char* key, const char* value);
+ 
+-const char* nw_config_get(struct nw_config* config, const char* key);
+-unsigned int nw_config_get_unsigned_int(struct nw_config* config, const char* key);
++int nw_config_get_int(struct nw_config* config, const char* key, const int __default);
++int nw_config_set_int(struct nw_config* config, const char* key, const int value);
+ 
+ #endif /* NETWORKD_CONFIG_H */
+diff --git a/src/networkd/zone-bus.c b/src/networkd/zone-bus.c
+index aafbfaf..e9c2ecc 100644
+--- a/src/networkd/zone-bus.c
++++ b/src/networkd/zone-bus.c
+@@ -18,6 +18,8 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <errno.h>
++
+ #include "bus.h"
+ #include "daemon.h"
+ #include "logging.h"
+@@ -74,9 +76,41 @@ static int nw_zone_object_find(sd_bus* bus, const char* path, const char* interf
+ 	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) {
++	struct nw_zone* zone = (struct 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;
++
++	struct nw_zone* zone = (struct 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
+ };
+ 
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index f78334e..c64706f 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -24,6 +24,7 @@
+ #include <systemd/sd-bus.h>
+ 
+ #include "config.h"
++#include "logging.h"
+ #include "string.h"
+ #include "zone.h"
+ 
+@@ -144,3 +145,16 @@ char* nw_zone_bus_path(struct nw_zone* zone) {
+ 
+ 	return p;
+ }
++
++/*
++	MTU
++*/
++unsigned int nw_zone_mtu(struct nw_zone* zone) {
++	return nw_config_get_int(zone->config, "MTU", NETWORK_ZONE_DEFAULT_MTU);
++}
++
++int nw_zone_set_mtu(struct 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);
++}
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 081a720..4a00412 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -22,6 +22,7 @@
+ #define NETWORKD_ZONE_H
+ 
+ #define NETWORK_ZONE_NAME_MAX_LENGTH		16
++#define NETWORK_ZONE_DEFAULT_MTU			1500
+ 
+ struct nw_zone;
+ 
+@@ -34,4 +35,10 @@ const char* nw_zone_name(struct nw_zone* zone);
+ 
+ char* nw_zone_bus_path(struct nw_zone* zone);
+ 
++/*
++	MTU
++*/
++unsigned int nw_zone_mtu(struct nw_zone* zone);
++int nw_zone_set_mtu(struct nw_zone* zone, unsigned int mtu);
++
+ #endif /* NETWORKD_ZONE_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0179-networkd-Connect-to-udev.patch b/network/patches/0179-networkd-Connect-to-udev.patch
new file mode 100644
index 000000000..e5e06191b
--- /dev/null
+++ b/network/patches/0179-networkd-Connect-to-udev.patch
@@ -0,0 +1,203 @@
+From b286cd831946a3134f5355fe45901a8a4057dce1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 5 Feb 2023 11:05:23 +0000
+Subject: [PATCH 179/304] networkd: Connect to udev
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |  2 ++
+ src/networkd/daemon.c | 64 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/devmon.c | 27 ++++++++++++++++++
+ src/networkd/devmon.h | 28 +++++++++++++++++++
+ 4 files changed, 121 insertions(+)
+ create mode 100644 src/networkd/devmon.c
+ create mode 100644 src/networkd/devmon.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 3972d8d..2ebe622 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -316,6 +316,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/daemon.h \
+ 	src/networkd/daemon-bus.c \
+ 	src/networkd/daemon-bus.h \
++	src/networkd/devmon.c \
++	src/networkd/devmon.h \
+ 	src/networkd/logging.h \
+ 	src/networkd/main.c \
+ 	src/networkd/string.h \
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 5b5cbb4..960fd96 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -23,15 +23,20 @@
+ 
+ #include <systemd/sd-bus.h>
+ #include <systemd/sd-daemon.h>
++#include <systemd/sd-device.h>
+ #include <systemd/sd-event.h>
+ 
+ #include "bus.h"
+ #include "config.h"
+ #include "daemon.h"
++#include "devmon.h"
+ #include "logging.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;
+ 
+@@ -43,6 +48,9 @@ struct nw_daemon {
+ 	// DBus Connection
+ 	sd_bus* bus;
+ 
++	// udev Connection
++	sd_device_monitor* devmon;
++
+ 	// Zones
+ 	struct nw_zones* zones;
+ };
+@@ -126,6 +134,57 @@ static int nw_daemon_load_config(struct nw_daemon* daemon) {
+ 	return r;
+ }
+ 
++static int nw_start_device_monitor(struct 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_setup(struct nw_daemon* daemon) {
+ 	int r;
+ 
+@@ -144,6 +203,11 @@ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 	if (r)
+ 		return r;
+ 
++	// Connect to udev
++	r = nw_start_device_monitor(daemon);
++	if (r)
++		return r;
++
+ 	return 0;
+ }
+ 
+diff --git a/src/networkd/devmon.c b/src/networkd/devmon.c
+new file mode 100644
+index 0000000..0a8c26d
+--- /dev/null
++++ b/src/networkd/devmon.c
+@@ -0,0 +1,27 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <systemd/sd-device.h>
++
++#include "devmon.h"
++
++int nw_devmon_handle_uevent(sd_device_monitor* monitor, sd_device* device, void* data) {
++	return 0;
++}
+diff --git a/src/networkd/devmon.h b/src/networkd/devmon.h
+new file mode 100644
+index 0000000..0d8970a
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_DEVMON_H
++#define NETWORKD_DEVMON_H
++
++#include <systemd/sd-device.h>
++
++int nw_devmon_handle_uevent(sd_device_monitor* monitor, sd_device* device, void* data);
++
++#endif /* NETWORKD_DEVMON_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0180-networkd-Change-to-a-non-privileged-user-right-away.patch b/network/patches/0180-networkd-Change-to-a-non-privileged-user-right-away.patch
new file mode 100644
index 000000000..71fb343c5
--- /dev/null
+++ b/network/patches/0180-networkd-Change-to-a-non-privileged-user-right-away.patch
@@ -0,0 +1,105 @@
+From eb3f6449e9319715fbb6135b1a47d18eaac2169e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 5 Feb 2023 12:06:19 +0000
+Subject: [PATCH 180/304] networkd: Change to a non-privileged user right away
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/main.c | 73 ++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 72 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/main.c b/src/networkd/main.c
+index 51a9bba..fbd6c3b 100644
+--- a/src/networkd/main.c
++++ b/src/networkd/main.c
+@@ -18,15 +18,86 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <grp.h>
++#include <pwd.h>
+ #include <stddef.h>
++#include <sys/prctl.h>
++#include <sys/types.h>
++#include <unistd.h>
+ 
+ #include "daemon.h"
++#include "logging.h"
++
++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;
++	}
++
++	return 0;
++}
+ 
+ int main(int argc, char** argv) {
+ 	struct nw_daemon* daemon = NULL;
+ 	int r;
+ 
+-	// XXX Drop privileges
++	// Drop privileges
++	r = drop_privileges("network");
++	if (r)
++		return r;
+ 
+ 	// Create the daemon
+ 	r = nw_daemon_create(&daemon);
+-- 
+2.39.2
+
diff --git a/network/patches/0181-networkd-Drop-all-capabilities-except-a-few-we-would.patch b/network/patches/0181-networkd-Drop-all-capabilities-except-a-few-we-would.patch
new file mode 100644
index 000000000..50d627fdc
--- /dev/null
+++ b/network/patches/0181-networkd-Drop-all-capabilities-except-a-few-we-would.patch
@@ -0,0 +1,195 @@
+From dabf344de60d568e304a7f4d10548813ee4ce588 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 5 Feb 2023 13:17:21 +0000
+Subject: [PATCH 181/304] networkd: Drop all capabilities except a few we would
+ like to keep
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am         |   2 +
+ configure.ac        |   1 +
+ src/networkd/main.c | 121 ++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 124 insertions(+)
+
+diff --git a/Makefile.am b/Makefile.am
+index 2ebe622..df61552 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -334,12 +334,14 @@ networkd_CPPFLAGS = \
+ 
+ networkd_CFLAGS = \
+ 	$(AM_CFLAGS) \
++	$(CAP_CFLAGS) \
+ 	$(SYSTEMD_CFLAGS)
+ 
+ networkd_LDFLAGS = \
+ 	$(AM_LDFLAGS)
+ 
+ networkd_LDADD = \
++	$(CAP_LIBS) \
+ 	$(SYSTEMD_LIBS)
+ 
+ dist_dbuspolicy_DATA += \
+diff --git a/configure.ac b/configure.ac
+index 16f0724..e1baa64 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -172,6 +172,7 @@ AM_CONDITIONAL(HAVE_UDEV, [test -n "$with_udevdir"])
+ 
+ # ------------------------------------------------------------------------------
+ 
++PKG_CHECK_MODULES([CAP], [libcap])
+ PKG_CHECK_MODULES([LIBNL], [libnl-3.0 libnl-genl-3.0])
+ PKG_CHECK_MODULES([SYSTEMD], [libsystemd])
+ 
+diff --git a/src/networkd/main.c b/src/networkd/main.c
+index fbd6c3b..9a0fd74 100644
+--- a/src/networkd/main.c
++++ b/src/networkd/main.c
+@@ -19,8 +19,10 @@
+ #############################################################################*/
+ 
+ #include <grp.h>
++#include <linux/capability.h>
+ #include <pwd.h>
+ #include <stddef.h>
++#include <sys/capability.h>
+ #include <sys/prctl.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+@@ -28,6 +30,120 @@
+ #include "daemon.h"
+ #include "logging.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;
+@@ -87,6 +203,11 @@ static int drop_privileges(const char* user) {
+ 		return 1;
+ 	}
+ 
++	// Drop capabilities
++	r = drop_capabilities();
++	if (r)
++		return r;
++
+ 	return 0;
+ }
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0182-networkd-Connect-to-the-kernel-s-netlink-interface.patch b/network/patches/0182-networkd-Connect-to-the-kernel-s-netlink-interface.patch
new file mode 100644
index 000000000..ccee33026
--- /dev/null
+++ b/network/patches/0182-networkd-Connect-to-the-kernel-s-netlink-interface.patch
@@ -0,0 +1,95 @@
+From e62b300ef78484b882d2da8683ebe79896523a13 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 9 Feb 2023 19:46:25 +0000
+Subject: [PATCH 182/304] networkd: Connect to the kernel's netlink interface
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 50 +++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 50 insertions(+)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 960fd96..13e1257 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -25,6 +25,7 @@
+ #include <systemd/sd-daemon.h>
+ #include <systemd/sd-device.h>
+ #include <systemd/sd-event.h>
++#include <systemd/sd-netlink.h>
+ 
+ #include "bus.h"
+ #include "config.h"
+@@ -45,6 +46,9 @@ struct nw_daemon {
+ 	// Event Loop
+ 	sd_event* loop;
+ 
++	// Netlink Connection
++	sd_netlink* rtnl;
++
+ 	// DBus Connection
+ 	sd_bus* bus;
+ 
+@@ -185,6 +189,47 @@ static int nw_start_device_monitor(struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
++static int _rtnl_dummy(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
++	DEBUG("_rtnl_dummy called\n");
++
++	return 0;
++}
++
++static int nw_daemon_connect_rtnl(struct 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, _rtnl_dummy, NULL,
++			daemon, "networkd-RTM_NEWLINK");
++	if (r < 0) {
++		ERROR("Could not register RTM_NEWLINK: %m\n");
++		return 1;
++	}
++
++	return 0;
++}
++
+ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 	int r;
+ 
+@@ -198,6 +243,11 @@ static int nw_daemon_setup(struct nw_daemon* 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)
+-- 
+2.39.2
+
diff --git a/network/patches/0183-networkd-Link-against-libnetwork.patch b/network/patches/0183-networkd-Link-against-libnetwork.patch
new file mode 100644
index 000000000..73a400d3c
--- /dev/null
+++ b/network/patches/0183-networkd-Link-against-libnetwork.patch
@@ -0,0 +1,25 @@
+From b53b186e84560a3376da5e858e41c23bb4526847 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 9 Feb 2023 19:50:47 +0000
+Subject: [PATCH 183/304] networkd: Link against libnetwork
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/Makefile.am b/Makefile.am
+index df61552..80218d9 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -341,6 +341,7 @@ networkd_LDFLAGS = \
+ 	$(AM_LDFLAGS)
+ 
+ networkd_LDADD = \
++	src/libnetwork.la \
+ 	$(CAP_LIBS) \
+ 	$(SYSTEMD_LIBS)
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0184-networkd-Add-a-link-object.patch b/network/patches/0184-networkd-Add-a-link-object.patch
new file mode 100644
index 000000000..33272bae2
--- /dev/null
+++ b/network/patches/0184-networkd-Add-a-link-object.patch
@@ -0,0 +1,145 @@
+From 87a1e1e0b052f814aac355b60aedf736eef0bead Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 9 Feb 2023 20:05:00 +0000
+Subject: [PATCH 184/304] networkd: Add a link object
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am         |  2 ++
+ src/networkd/link.c | 72 +++++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/link.h | 31 +++++++++++++++++++
+ 3 files changed, 105 insertions(+)
+ create mode 100644 src/networkd/link.c
+ create mode 100644 src/networkd/link.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 80218d9..e8946b7 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -318,6 +318,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/daemon-bus.h \
+ 	src/networkd/devmon.c \
+ 	src/networkd/devmon.h \
++	src/networkd/link.c \
++	src/networkd/link.h \
+ 	src/networkd/logging.h \
+ 	src/networkd/main.c \
+ 	src/networkd/string.h \
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+new file mode 100644
+index 0000000..06e3101
+--- /dev/null
++++ b/src/networkd/link.c
+@@ -0,0 +1,72 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <stddef.h>
++#include <stdlib.h>
++
++#include "daemon.h"
++#include "link.h"
++
++struct nw_link {
++	struct nw_daemon* daemon;
++	int nrefs;
++
++	// Interface Index
++	int ifindex;
++};
++
++int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex) {
++	// Allocate a new object
++	struct nw_link* 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;
++
++	// Store the ifindex
++	l->ifindex = ifindex;
++
++	*link = l;
++
++	return 0;
++}
++
++static void nw_link_free(struct nw_link* link) {
++	if (link->daemon)
++		nw_daemon_unref(link->daemon);
++}
++
++struct nw_link* nw_link_ref(struct nw_link* link) {
++	link->nrefs++;
++
++	return link;
++}
++
++struct nw_link* nw_link_unref(struct nw_link* link) {
++	if (--link->nrefs > 0)
++		return link;
++
++	nw_link_free(link);
++	return NULL;
++}
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+new file mode 100644
+index 0000000..cd79fde
+--- /dev/null
++++ b/src/networkd/link.h
+@@ -0,0 +1,31 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_LINK_H
++#define NETWORKD_LINK_H
++
++struct nw_link;
++
++int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex);
++
++struct nw_link* nw_link_ref(struct nw_link* link);
++struct nw_link* nw_link_unref(struct nw_link* link);
++
++#endif /* NETWORKD_LINK_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0185-networkd-Add-a-container-for-links.patch b/network/patches/0185-networkd-Add-a-container-for-links.patch
new file mode 100644
index 000000000..327ce5c39
--- /dev/null
+++ b/network/patches/0185-networkd-Add-a-container-for-links.patch
@@ -0,0 +1,303 @@
+From 18b8c8415c595442f796638f85a244915339c974 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 9 Feb 2023 20:25:29 +0000
+Subject: [PATCH 185/304] networkd: Add a container for links
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |   2 +
+ src/networkd/daemon.c |  48 ++++++++++++++++++-
+ src/networkd/link.h   |   2 +
+ src/networkd/links.c  | 105 ++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/links.h  |  35 ++++++++++++++
+ 5 files changed, 190 insertions(+), 2 deletions(-)
+ create mode 100644 src/networkd/links.c
+ create mode 100644 src/networkd/links.h
+
+diff --git a/Makefile.am b/Makefile.am
+index e8946b7..024bf45 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -320,6 +320,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/devmon.h \
+ 	src/networkd/link.c \
+ 	src/networkd/link.h \
++	src/networkd/links.c \
++	src/networkd/links.h \
+ 	src/networkd/logging.h \
+ 	src/networkd/main.c \
+ 	src/networkd/string.h \
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 13e1257..27b118e 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -31,6 +31,7 @@
+ #include "config.h"
+ #include "daemon.h"
+ #include "devmon.h"
++#include "links.h"
+ #include "logging.h"
+ #include "zone.h"
+ #include "zones.h"
+@@ -55,6 +56,9 @@ struct nw_daemon {
+ 	// udev Connection
+ 	sd_device_monitor* devmon;
+ 
++	// Links
++	struct nw_links* links;
++
+ 	// Zones
+ 	struct nw_zones* zones;
+ };
+@@ -230,6 +234,28 @@ static int nw_daemon_connect_rtnl(struct nw_daemon* daemon, int fd) {
+ 	return 0;
+ }
+ 
++static int nw_daemon_enumerate_links(struct 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(struct nw_daemon* daemon) {
++	int r;
++
++	// Links
++	r = nw_daemon_enumerate_links(daemon);
++	if (r)
++		return r;
++
++	return 0;
++}
++
+ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 	int r;
+ 
+@@ -258,6 +284,11 @@ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 	if (r)
+ 		return r;
+ 
++	// Enumerate everything we need to know
++	r = nw_daemon_enumerate(daemon);
++	if (r)
++		return r;
++
+ 	return 0;
+ }
+ 
+@@ -287,11 +318,19 @@ ERROR:
+ 	return r;
+ }
+ 
+-static void nw_daemon_free(struct nw_daemon* daemon) {
++static void nw_daemon_cleanup(struct nw_daemon* daemon) {
+ 	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(struct nw_daemon* daemon) {
++	// Cleanup common objects
++	nw_daemon_cleanup(daemon);
++
+ 	if (daemon->bus)
+ 		sd_bus_unref(daemon->bus);
+ 	if (daemon->loop)
+@@ -330,15 +369,20 @@ int nw_daemon_run(struct nw_daemon* daemon) {
+ 		goto ERROR;
+ 	}
+ 
+-
+ 	// Let systemd know that we are shutting down
+ 	sd_notify(0, "STOPPING=1\n" "STATUS=Shutting down...");
+ 
++	// Cleanup everything
++	nw_daemon_cleanup(daemon);
++
+ 	return 0;
+ 
+ ERROR:
+ 	sd_notifyf(0, "ERRNO=%i", -r);
+ 
++	// Cleanup everything
++	nw_daemon_cleanup(daemon);
++
+ 	return 1;
+ }
+ 
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index cd79fde..6ffe843 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -21,6 +21,8 @@
+ #ifndef NETWORKD_LINK_H
+ #define NETWORKD_LINK_H
+ 
++#include "daemon.h"
++
+ struct nw_link;
+ 
+ int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex);
+diff --git a/src/networkd/links.c b/src/networkd/links.c
+new file mode 100644
+index 0000000..b81d6a5
+--- /dev/null
++++ b/src/networkd/links.c
+@@ -0,0 +1,105 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <stddef.h>
++#include <stdlib.h>
++#include <sys/queue.h>
++
++#include "daemon.h"
++#include "link.h"
++#include "links.h"
++
++struct nw_links_entry {
++	struct nw_link* link;
++
++	// Link to the other entries
++	STAILQ_ENTRY(nw_links_entry) nodes;
++};
++
++struct nw_links {
++	struct 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(struct nw_links** links, struct nw_daemon* daemon) {
++	struct 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(struct 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);
++}
++
++struct nw_links* nw_links_ref(struct nw_links* links) {
++	links->nrefs++;
++
++	return links;
++}
++
++struct nw_links* nw_links_unref(struct nw_links* links) {
++	if (--links->nrefs > 0)
++		return links;
++
++	nw_links_free(links);
++	return NULL;
++}
++
++int nw_links_enumerate(struct nw_links* links) {
++	// TODO
++
++	return 0;
++}
+diff --git a/src/networkd/links.h b/src/networkd/links.h
+new file mode 100644
+index 0000000..b2976e4
+--- /dev/null
++++ b/src/networkd/links.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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_LINKS_H
++#define NETWORKD_LINKS_H
++
++#include "daemon.h"
++
++struct nw_links;
++
++int nw_links_create(struct nw_links** links, struct nw_daemon* daemon);
++
++struct nw_links* nw_links_ref(struct nw_links* links);
++struct nw_links* nw_links_unref(struct nw_links* links);
++
++int nw_links_enumerate(struct nw_links* links);
++
++#endif /* NETWORKD_LINKS_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0186-networkd-Enumerate-all-links-on-startup.patch b/network/patches/0186-networkd-Enumerate-all-links-on-startup.patch
new file mode 100644
index 000000000..b547f60f4
--- /dev/null
+++ b/network/patches/0186-networkd-Enumerate-all-links-on-startup.patch
@@ -0,0 +1,172 @@
+From eaf649ee9bdfa7e20ba381a505e2c5b48501c242 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 9 Feb 2023 20:41:02 +0000
+Subject: [PATCH 186/304] networkd: Enumerate all links on startup
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 16 +++++++++-------
+ src/networkd/daemon.h |  7 +++++++
+ src/networkd/link.c   |  7 +++++++
+ src/networkd/link.h   |  2 ++
+ src/networkd/links.c  | 39 +++++++++++++++++++++++++++++++++++++--
+ 5 files changed, 62 insertions(+), 9 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 27b118e..88ce8e7 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -31,6 +31,7 @@
+ #include "config.h"
+ #include "daemon.h"
+ #include "devmon.h"
++#include "link.h"
+ #include "links.h"
+ #include "logging.h"
+ #include "zone.h"
+@@ -193,12 +194,6 @@ static int nw_start_device_monitor(struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
+-static int _rtnl_dummy(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
+-	DEBUG("_rtnl_dummy called\n");
+-
+-	return 0;
+-}
+-
+ static int nw_daemon_connect_rtnl(struct nw_daemon* daemon, int fd) {
+ 	int r;
+ 
+@@ -224,7 +219,7 @@ static int nw_daemon_connect_rtnl(struct nw_daemon* daemon, int fd) {
+ 	}
+ 
+ 	// Register callback for new interfaces
+-	r = sd_netlink_add_match(daemon->rtnl, NULL, RTM_NEWLINK, _rtnl_dummy, NULL,
++	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");
+@@ -394,6 +389,13 @@ int nw_daemon_reload(struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
++/*
++	Netlink
++*/
++sd_netlink* nw_daemon_get_rtnl(struct nw_daemon* daemon) {
++	return daemon->rtnl;
++}
++
+ /*
+ 	Zones
+ */
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index 891c297..543685f 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -21,6 +21,8 @@
+ #ifndef NETWORKD_DAEMON_H
+ #define NETWORKD_DAEMON_H
+ 
++#include <systemd/sd-netlink.h>
++
+ #include "zone.h"
+ 
+ struct nw_daemon;
+@@ -34,6 +36,11 @@ int nw_daemon_run(struct nw_daemon* daemon);
+ 
+ int nw_daemon_reload(struct nw_daemon* daemon);
+ 
++/*
++	Netlink
++*/
++sd_netlink* nw_daemon_get_rtnl(struct nw_daemon* daemon);
++
+ /*
+ 	Zones
+ */
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 06e3101..ec0593b 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -23,6 +23,7 @@
+ 
+ #include "daemon.h"
+ #include "link.h"
++#include "logging.h"
+ 
+ struct nw_link {
+ 	struct nw_daemon* daemon;
+@@ -70,3 +71,9 @@ struct nw_link* nw_link_unref(struct nw_link* link) {
+ 	nw_link_free(link);
+ 	return NULL;
+ }
++
++int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
++	DEBUG("nw_link_process called\n");
++
++	return 0;
++}
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index 6ffe843..6003133 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -30,4 +30,6 @@ int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex)
+ struct nw_link* nw_link_ref(struct nw_link* link);
+ struct nw_link* nw_link_unref(struct nw_link* link);
+ 
++int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data);
++
+ #endif /* NETWORKD_LINK_H */
+diff --git a/src/networkd/links.c b/src/networkd/links.c
+index b81d6a5..22584f2 100644
+--- a/src/networkd/links.c
++++ b/src/networkd/links.c
+@@ -99,7 +99,42 @@ struct nw_links* nw_links_unref(struct nw_links* links) {
+ }
+ 
+ int nw_links_enumerate(struct nw_links* links) {
+-	// TODO
++	sd_netlink_message* req = NULL;
++	sd_netlink_message* res = NULL;
++	int r;
+ 
+-	return 0;
++	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;
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0187-networkd-Create-a-link-object-for-each-interface.patch b/network/patches/0187-networkd-Create-a-link-object-for-each-interface.patch
new file mode 100644
index 000000000..cfa266627
--- /dev/null
+++ b/network/patches/0187-networkd-Create-a-link-object-for-each-interface.patch
@@ -0,0 +1,362 @@
+From 766f08ca103698e6578228e11f81c1d73d687d65 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 9 Feb 2023 21:38:09 +0000
+Subject: [PATCH 187/304] networkd: Create a link object for each interface
+
+We are also listening for netlink events that add and delete any
+interfaces.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c |  29 +++++++++++
+ src/networkd/daemon.h |  12 ++++-
+ src/networkd/link.c   | 117 ++++++++++++++++++++++++++++++++++++++++--
+ src/networkd/link.h   |   2 +
+ src/networkd/links.c  |  54 +++++++++++++++++++
+ src/networkd/links.h  |   6 +++
+ 6 files changed, 215 insertions(+), 5 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 88ce8e7..dc2aed6 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -226,6 +226,14 @@ static int nw_daemon_connect_rtnl(struct nw_daemon* daemon, int fd) {
+ 		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;
+ }
+ 
+@@ -396,6 +404,27 @@ sd_netlink* nw_daemon_get_rtnl(struct nw_daemon* daemon) {
+ 	return daemon->rtnl;
+ }
+ 
++/*
++	Links
++*/
++struct nw_links* nw_daemon_links(struct nw_daemon* daemon) {
++	return nw_links_ref(daemon->links);
++}
++
++void nw_daemon_drop_link(struct nw_daemon* daemon, struct nw_link* link) {
++	if (!daemon->links)
++		return;
++
++	nw_links_drop_link(daemon->links, link);
++}
++
++struct nw_link* nw_daemon_get_link_by_ifindex(struct nw_daemon* daemon, int ifindex) {
++	if (!daemon->links)
++		return NULL;
++
++	return nw_links_get_by_ifindex(daemon->links, ifindex);
++}
++
+ /*
+ 	Zones
+ */
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index 543685f..8e534c5 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -23,10 +23,11 @@
+ 
+ #include <systemd/sd-netlink.h>
+ 
+-#include "zone.h"
+-
+ struct nw_daemon;
+ 
++#include "link.h"
++#include "zone.h"
++
+ int nw_daemon_create(struct nw_daemon** daemon);
+ 
+ struct nw_daemon* nw_daemon_ref(struct nw_daemon* daemon);
+@@ -41,6 +42,13 @@ int nw_daemon_reload(struct nw_daemon* daemon);
+ */
+ sd_netlink* nw_daemon_get_rtnl(struct nw_daemon* daemon);
+ 
++/*
++	Links
++*/
++struct nw_links* nw_daemon_links(struct nw_daemon* daemon);
++void nw_daemon_drop_link(struct nw_daemon* daemon, struct nw_link* link);
++struct nw_link* nw_daemon_get_link_by_ifindex(struct nw_daemon* daemon, int ifindex);
++
+ /*
+ 	Zones
+ */
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index ec0593b..b6ece82 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -21,8 +21,11 @@
+ #include <stddef.h>
+ #include <stdlib.h>
+ 
++#include <systemd/sd-netlink.h>
++
+ #include "daemon.h"
+ #include "link.h"
++#include "links.h"
+ #include "logging.h"
+ 
+ struct nw_link {
+@@ -48,12 +51,16 @@ int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex)
+ 	// Store the ifindex
+ 	l->ifindex = ifindex;
+ 
++	DEBUG("New link allocated (ifindex = %d)\n", l->ifindex);
++
+ 	*link = l;
+ 
+ 	return 0;
+ }
+ 
+ static void nw_link_free(struct nw_link* link) {
++	DEBUG("Freeing link (ifindex = %d)\n", link->ifindex);
++
+ 	if (link->daemon)
+ 		nw_daemon_unref(link->daemon);
+ }
+@@ -72,8 +79,112 @@ struct nw_link* nw_link_unref(struct nw_link* link) {
+ 	return NULL;
+ }
+ 
+-int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
+-	DEBUG("nw_link_process called\n");
++int nw_link_ifindex(struct nw_link* link) {
++	return link->ifindex;
++}
+ 
+-	return 0;
++int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
++	struct nw_links* links = NULL;
++	struct nw_link* link = NULL;
++	const char* ifname = NULL;
++	int ifindex;
++	uint16_t type;
++	int r;
++
++	struct nw_daemon* daemon = (struct 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;
++				}
++			}
++
++			// TODO Import any data from the netlink message
++
++			// Add it to the list
++			r = nw_links_add_link(links, link);
++			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;
+ }
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index 6003133..e482d71 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -30,6 +30,8 @@ int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex)
+ struct nw_link* nw_link_ref(struct nw_link* link);
+ struct nw_link* nw_link_unref(struct nw_link* link);
+ 
++int nw_link_ifindex(struct nw_link* link);
++
+ int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data);
+ 
+ #endif /* NETWORKD_LINK_H */
+diff --git a/src/networkd/links.c b/src/networkd/links.c
+index 22584f2..044a27d 100644
+--- a/src/networkd/links.c
++++ b/src/networkd/links.c
+@@ -23,6 +23,7 @@
+ #include <sys/queue.h>
+ 
+ #include "daemon.h"
++#include "logging.h"
+ #include "link.h"
+ #include "links.h"
+ 
+@@ -98,6 +99,49 @@ struct nw_links* nw_links_unref(struct nw_links* links) {
+ 	return NULL;
+ }
+ 
++static struct nw_links_entry* nw_links_find_link(struct 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(struct 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(struct 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--;
++}
++
+ int nw_links_enumerate(struct nw_links* links) {
+ 	sd_netlink_message* req = NULL;
+ 	sd_netlink_message* res = NULL;
+@@ -138,3 +182,13 @@ ERROR:
+ 
+ 	return r;
+ }
++
++struct nw_link* nw_links_get_by_ifindex(struct 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);
++}
+diff --git a/src/networkd/links.h b/src/networkd/links.h
+index b2976e4..2f2bbea 100644
+--- a/src/networkd/links.h
++++ b/src/networkd/links.h
+@@ -22,6 +22,7 @@
+ #define NETWORKD_LINKS_H
+ 
+ #include "daemon.h"
++#include "link.h"
+ 
+ struct nw_links;
+ 
+@@ -30,6 +31,11 @@ int nw_links_create(struct nw_links** links, struct nw_daemon* daemon);
+ struct nw_links* nw_links_ref(struct nw_links* links);
+ struct nw_links* nw_links_unref(struct nw_links* links);
+ 
++int nw_links_add_link(struct nw_links* links, struct nw_link* link);
++void nw_links_drop_link(struct nw_links* links, struct nw_link* link);
++
+ int nw_links_enumerate(struct nw_links* links);
+ 
++struct nw_link* nw_links_get_by_ifindex(struct nw_links* links, int ifindex);
++
+ #endif /* NETWORKD_LINKS_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0188-networkd-Only-add-link-if-we-created-it.patch b/network/patches/0188-networkd-Only-add-link-if-we-created-it.patch
new file mode 100644
index 000000000..844c432c0
--- /dev/null
+++ b/network/patches/0188-networkd-Only-add-link-if-we-created-it.patch
@@ -0,0 +1,38 @@
+From b8a1a68c48849accb658b926f3596857dba41654 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 10 Feb 2023 09:53:47 +0000
+Subject: [PATCH 188/304] networkd: Only add link if we created it
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index b6ece82..32a66cd 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -159,15 +159,15 @@ int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
+ 					ERROR("Could not create link: %m\n");
+ 					goto ERROR;
+ 				}
++
++				// Add it to the list
++				r = nw_links_add_link(links, link);
++				if (r)
++					goto ERROR;
+ 			}
+ 
+ 			// TODO Import any data from the netlink message
+ 
+-			// Add it to the list
+-			r = nw_links_add_link(links, link);
+-			if (r)
+-				goto ERROR;
+-
+ 			break;
+ 
+ 		case RTM_DELLINK:
+-- 
+2.39.2
+
diff --git a/network/patches/0189-networkd-Import-interface-name.patch b/network/patches/0189-networkd-Import-interface-name.patch
new file mode 100644
index 000000000..00f801dd7
--- /dev/null
+++ b/network/patches/0189-networkd-Import-interface-name.patch
@@ -0,0 +1,106 @@
+From f650f6cd802686e545d41d00be4a0abd67765391 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 10 Feb 2023 10:03:08 +0000
+Subject: [PATCH 189/304] networkd: Import interface name
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 52 ++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 51 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 32a66cd..40f809f 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -18,8 +18,10 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <net/if.h>
+ #include <stddef.h>
+ #include <stdlib.h>
++#include <string.h>
+ 
+ #include <systemd/sd-netlink.h>
+ 
+@@ -27,6 +29,7 @@
+ #include "link.h"
+ #include "links.h"
+ #include "logging.h"
++#include "string.h"
+ 
+ struct nw_link {
+ 	struct nw_daemon* daemon;
+@@ -34,6 +37,9 @@ struct nw_link {
+ 
+ 	// Interface Index
+ 	int ifindex;
++
++	// Interface Name
++	char ifname[IF_NAMESIZE];
+ };
+ 
+ int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex) {
+@@ -83,6 +89,47 @@ int nw_link_ifindex(struct nw_link* link) {
+ 	return link->ifindex;
+ }
+ 
++static int nw_link_update_ifname(struct 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);
++
++	return 0;
++}
++
++/*
++	This function is called whenever anything changes, so that we can
++	update our internal link object.
++*/
++static int nw_link_update(struct nw_link* link, sd_netlink_message* message) {
++	int r;
++
++	// Update the interface name
++	r = nw_link_update_ifname(link, message);
++	if (r)
++		return r;
++
++	return 0;
++}
++
+ int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
+ 	struct nw_links* links = NULL;
+ 	struct nw_link* link = NULL;
+@@ -166,7 +213,10 @@ int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
+ 					goto ERROR;
+ 			}
+ 
+-			// TODO Import any data from the netlink message
++			// Import any data from the netlink message
++			r = nw_link_update(link, message);
++			if (r)
++				goto ERROR;
+ 
+ 			break;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0190-networkd-Read-link-MTU.patch b/network/patches/0190-networkd-Read-link-MTU.patch
new file mode 100644
index 000000000..9da1cd019
--- /dev/null
+++ b/network/patches/0190-networkd-Read-link-MTU.patch
@@ -0,0 +1,96 @@
+From f37df5ea0920add075e294db28a3ec5ed83b0fe5 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 10 Feb 2023 10:13:37 +0000
+Subject: [PATCH 190/304] networkd: Read link MTU
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 58 +++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 58 insertions(+)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 40f809f..62be893 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -40,6 +40,11 @@ struct nw_link {
+ 
+ 	// Interface Name
+ 	char ifname[IF_NAMESIZE];
++
++	// MTU
++	uint32_t mtu;
++	uint32_t min_mtu;
++	uint32_t max_mtu;
+ };
+ 
+ int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex) {
+@@ -115,6 +120,54 @@ static int nw_link_update_ifname(struct nw_link* link, sd_netlink_message* messa
+ 	return 0;
+ }
+ 
++static int nw_link_update_mtu(struct 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;
++}
++
+ /*
+ 	This function is called whenever anything changes, so that we can
+ 	update our internal link object.
+@@ -127,6 +180,11 @@ static int nw_link_update(struct nw_link* link, sd_netlink_message* message) {
+ 	if (r)
+ 		return r;
+ 
++	// Update the MTU
++	r = nw_link_update_mtu(link, message);
++	if (r)
++		return r;
++
+ 	return 0;
+ }
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0191-networkd-config-Actually-return-entry-instead-of-fre.patch b/network/patches/0191-networkd-config-Actually-return-entry-instead-of-fre.patch
new file mode 100644
index 000000000..19ebcc0fb
--- /dev/null
+++ b/network/patches/0191-networkd-config-Actually-return-entry-instead-of-fre.patch
@@ -0,0 +1,27 @@
+From 82f84c5ff3079de8b57f5a10a118fb4ecab20ffb Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 10 Feb 2023 15:24:19 +0000
+Subject: [PATCH 191/304] networkd: config: Actually return entry instead of
+ freeing it straight away
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 788308f..ef6218e 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -72,6 +72,8 @@ static struct nw_config_entry* nw_config_entry_create(
+ 	// Append the new entry
+ 	STAILQ_INSERT_TAIL(&config->entries, entry, nodes);
+ 
++	return entry;
++
+ ERROR:
+ 	nw_config_entry_free(entry);
+ 	return NULL;
+-- 
+2.39.2
+
diff --git a/network/patches/0192-networkd-config-Implement-reading-configuration-file.patch b/network/patches/0192-networkd-config-Implement-reading-configuration-file.patch
new file mode 100644
index 000000000..b8fc1dbef
--- /dev/null
+++ b/network/patches/0192-networkd-config-Implement-reading-configuration-file.patch
@@ -0,0 +1,139 @@
+From 5ef56cff5e19dcda00a76a40846fe3def9e6239f Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 10 Feb 2023 15:24:53 +0000
+Subject: [PATCH 192/304] networkd: config: Implement reading configuration
+ files
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 46 ++++++++++++++++++++++++++++++++++++++++---
+ src/networkd/string.h | 41 ++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 84 insertions(+), 3 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index ef6218e..17d6d89 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -157,9 +157,49 @@ int nw_config_flush(struct nw_config* config) {
+ }
+ 
+ static int nw_config_readf(struct nw_config* config, FILE* f) {
+-	// XXX TODO
++	char* line = NULL;
++	size_t length = 0;
++	int r;
+ 
+-	return 0;
++	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_read(struct nw_config* config) {
+@@ -204,7 +244,7 @@ static int nw_config_writef(struct nw_config* config, FILE* f) {
+ 			continue;
+ 
+ 		// Write the entry
+-		r = fprintf(f, "%s=\"%s\"\n", entry->key, entry->value);
++		r = fprintf(f, "%s=%s\n", entry->key, entry->value);
+ 		if (r < 0) {
+ 			ERROR("Failed to write configuration: %m\n");
+ 			return r;
+diff --git a/src/networkd/string.h b/src/networkd/string.h
+index 4ae2dbf..f3c5b71 100644
+--- a/src/networkd/string.h
++++ b/src/networkd/string.h
+@@ -21,6 +21,7 @@
+ #ifndef NETWORKD_STRING_H
+ #define NETWORKD_STRING_H
+ 
++#include <ctype.h>
+ #include <errno.h>
+ #include <stdarg.h>
+ #include <stdio.h>
+@@ -79,6 +80,46 @@ static inline int __nw_string_set(char* s, const size_t length, const char* valu
+ 	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;
++}
++
+ /*
+ 	Paths
+ */
+-- 
+2.39.2
+
diff --git a/network/patches/0193-networkd-Add-scaffolding-for-ports.patch b/network/patches/0193-networkd-Add-scaffolding-for-ports.patch
new file mode 100644
index 000000000..ca9832e08
--- /dev/null
+++ b/network/patches/0193-networkd-Add-scaffolding-for-ports.patch
@@ -0,0 +1,159 @@
+From abeab06924b22765dbc410c563f27077f348b5e9 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 10 Feb 2023 16:15:46 +0000
+Subject: [PATCH 193/304] networkd: Add scaffolding for ports
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am         |  2 ++
+ src/networkd/port.c | 84 +++++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/port.h | 33 ++++++++++++++++++
+ 3 files changed, 119 insertions(+)
+ create mode 100644 src/networkd/port.c
+ create mode 100644 src/networkd/port.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 024bf45..4bd3975 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -324,6 +324,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/links.h \
+ 	src/networkd/logging.h \
+ 	src/networkd/main.c \
++	src/networkd/port.c \
++	src/networkd/port.h \
+ 	src/networkd/string.h \
+ 	src/networkd/zones.c \
+ 	src/networkd/zones.h \
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+new file mode 100644
+index 0000000..019521f
+--- /dev/null
++++ b/src/networkd/port.c
+@@ -0,0 +1,84 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <net/if.h>
++#include <stdlib.h>
++
++#include "config.h"
++#include "string.h"
++#include "port.h"
++
++struct nw_port {
++	int nrefs;
++
++	char name[IF_NAMESIZE];
++
++	// Configuration
++	struct nw_config *config;
++};
++
++static void nw_port_free(struct nw_port* port) {
++	if (port->config)
++		nw_config_unref(port->config);
++
++	free(port);
++}
++
++int nw_port_create(struct nw_port** port, const char* name) {
++	int r;
++
++	// Allocate a new object
++	struct nw_port* p = calloc(1, sizeof(*p));
++	if (!p)
++		return 1;
++
++	// Initialize reference counter
++	p->nrefs = 1;
++
++	// Store the name
++	r = nw_string_set(p->name, name);
++	if (r)
++		goto ERROR;
++
++	*port = p;
++	return 0;
++
++ERROR:
++	nw_port_free(p);
++	return r;
++}
++
++struct nw_port* nw_port_ref(struct nw_port* port) {
++	port->nrefs++;
++
++	return port;
++}
++
++struct nw_port* nw_port_unref(struct nw_port* port) {
++	if (--port->nrefs > 0)
++		return port;
++
++	nw_port_free(port);
++	return NULL;
++}
++
++const char* nw_port_name(struct nw_port* port) {
++	return port->name;
++}
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+new file mode 100644
+index 0000000..cea6203
+--- /dev/null
++++ b/src/networkd/port.h
+@@ -0,0 +1,33 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_PORT_H
++#define NETWORKD_PORT_H
++
++struct nw_port;
++
++int nw_port_create(struct nw_port** port, const char* name);
++
++struct nw_port* nw_port_ref(struct nw_port* port);
++struct nw_port* nw_port_unref(struct nw_port* port);
++
++const char* nw_port_name(struct nw_port* port);
++
++#endif /* NETWORKD_PORT_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0194-networkd-Add-port-container.patch b/network/patches/0194-networkd-Add-port-container.patch
new file mode 100644
index 000000000..53004d0a9
--- /dev/null
+++ b/network/patches/0194-networkd-Add-port-container.patch
@@ -0,0 +1,240 @@
+From b8db2713c790000860e1c3b0d4f8e588cfa5ac04 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 10 Feb 2023 16:26:36 +0000
+Subject: [PATCH 194/304] networkd: Add port container
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |   2 +
+ src/networkd/daemon.c |  23 ++++++++++
+ src/networkd/ports.c  | 100 ++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/ports.h  |  35 +++++++++++++++
+ 4 files changed, 160 insertions(+)
+ create mode 100644 src/networkd/ports.c
+ create mode 100644 src/networkd/ports.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 4bd3975..d78d61e 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -326,6 +326,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/main.c \
+ 	src/networkd/port.c \
+ 	src/networkd/port.h \
++	src/networkd/ports.c \
++	src/networkd/ports.h \
+ 	src/networkd/string.h \
+ 	src/networkd/zones.c \
+ 	src/networkd/zones.h \
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index dc2aed6..aee27e3 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -34,6 +34,7 @@
+ #include "link.h"
+ #include "links.h"
+ #include "logging.h"
++#include "ports.h"
+ #include "zone.h"
+ #include "zones.h"
+ 
+@@ -62,6 +63,10 @@ struct nw_daemon {
+ 
+ 	// Zones
+ 	struct nw_zones* zones;
++
++	// Ports
++	struct nw_ports* ports;
++
+ };
+ 
+ static int __nw_daemon_terminate(sd_event_source* source, const struct signalfd_siginfo* si,
+@@ -248,6 +253,17 @@ static int nw_daemon_enumerate_links(struct nw_daemon* daemon) {
+ 	return nw_links_enumerate(daemon->links);
+ }
+ 
++static int nw_daemon_enumerate_ports(struct 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(struct nw_daemon* daemon) {
+ 	int r;
+ 
+@@ -256,6 +272,11 @@ static int nw_daemon_enumerate(struct nw_daemon* daemon) {
+ 	if (r)
+ 		return r;
+ 
++	// Ports
++	r = nw_daemon_enumerate_ports(daemon);
++	if (r)
++		return r;
++
+ 	return 0;
+ }
+ 
+@@ -322,6 +343,8 @@ ERROR:
+ }
+ 
+ static void nw_daemon_cleanup(struct nw_daemon* daemon) {
++	if (daemon->ports)
++		nw_ports_unref(daemon->ports);
+ 	if (daemon->zones)
+ 		nw_zones_unref(daemon->zones);
+ 	if (daemon->links)
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+new file mode 100644
+index 0000000..500153a
+--- /dev/null
++++ b/src/networkd/ports.c
+@@ -0,0 +1,100 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <stdlib.h>
++#include <string.h>
++#include <sys/queue.h>
++
++#include "logging.h"
++#include "port.h"
++#include "ports.h"
++
++struct nw_ports_entry {
++	struct nw_port* port;
++
++	// Link to the other entries
++	STAILQ_ENTRY(nw_ports_entry) nodes;
++};
++
++struct nw_ports {
++	struct 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(struct nw_ports** ports, struct nw_daemon* daemon) {
++	struct 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(struct 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);
++	}
++}
++
++struct nw_ports* nw_ports_ref(struct nw_ports* ports) {
++	ports->nrefs++;
++
++	return ports;
++}
++
++struct nw_ports* nw_ports_unref(struct nw_ports* ports) {
++	if (--ports->nrefs > 0)
++		return ports;
++
++	nw_ports_free(ports);
++	return NULL;
++}
++
++int nw_ports_enumerate(struct nw_ports* ports) {
++	return 0; // XXX TODO
++}
+diff --git a/src/networkd/ports.h b/src/networkd/ports.h
+new file mode 100644
+index 0000000..76a4a3a
+--- /dev/null
++++ b/src/networkd/ports.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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_PORTS_H
++#define NETWORKD_PORTS_H
++
++struct nw_ports;
++
++#include "daemon.h"
++
++int nw_ports_create(struct nw_ports** ports, struct nw_daemon* daemon);
++
++struct nw_ports* nw_ports_ref(struct nw_ports* ports);
++struct nw_ports* nw_ports_unref(struct nw_ports* ports);
++
++int nw_ports_enumerate(struct nw_ports* ports);
++
++#endif /* NETWORKD_ZONES_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0195-networkd-Enumerate-ports-on-startup.patch b/network/patches/0195-networkd-Enumerate-ports-on-startup.patch
new file mode 100644
index 000000000..30b406c35
--- /dev/null
+++ b/network/patches/0195-networkd-Enumerate-ports-on-startup.patch
@@ -0,0 +1,247 @@
+From c791afc87a22936e9564e449182ee6452659887e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 10:59:03 +0000
+Subject: [PATCH 195/304] networkd: Enumerate ports on startup
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |  2 ++
+ src/networkd/port.h   |  2 ++
+ src/networkd/ports.c  | 61 ++++++++++++++++++++++++++++++++++++++++++-
+ src/networkd/string.h |  8 ++++++
+ src/networkd/util.c   | 61 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/util.h   | 29 ++++++++++++++++++++
+ 6 files changed, 162 insertions(+), 1 deletion(-)
+ create mode 100644 src/networkd/util.c
+ create mode 100644 src/networkd/util.h
+
+diff --git a/Makefile.am b/Makefile.am
+index d78d61e..505d679 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -329,6 +329,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/ports.c \
+ 	src/networkd/ports.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 \
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index cea6203..250a694 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -21,6 +21,8 @@
+ #ifndef NETWORKD_PORT_H
+ #define NETWORKD_PORT_H
+ 
++#define PORT_CONFIG_DIR			CONFIG_DIR "/ports"
++
+ struct nw_port;
+ 
+ int nw_port_create(struct nw_port** port, const char* name);
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+index 500153a..19a3a59 100644
+--- a/src/networkd/ports.c
++++ b/src/networkd/ports.c
+@@ -21,10 +21,13 @@
+ #include <stdlib.h>
+ #include <string.h>
+ #include <sys/queue.h>
++#include <sys/stat.h>
+ 
+ #include "logging.h"
+ #include "port.h"
+ #include "ports.h"
++#include "string.h"
++#include "util.h"
+ 
+ struct nw_ports_entry {
+ 	struct nw_port* port;
+@@ -95,6 +98,62 @@ struct nw_ports* nw_ports_unref(struct nw_ports* ports) {
+ 	return NULL;
+ }
+ 
++static int nw_ports_add_port(struct nw_ports* ports, struct 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(const char* path, const struct stat* s, void* data) {
++	struct nw_port* port = NULL;
++	int r;
++
++	struct nw_ports* ports = (struct nw_ports*)data;
++
++	// Skip anything that isn't a regular file
++	if (!S_ISREG(s->st_mode))
++		return 0;
++
++	// Find the basename of the file
++	const char* name = nw_path_basename(path);
++
++	// Break on invalid paths
++	if (!name)
++		return 0;
++
++	// Skip any hidden files
++	if (*name == '.')
++		return 0;
++
++	// Create a new port
++	r = nw_port_create(&port, name);
++	if (r)
++		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(struct nw_ports* ports) {
+-	return 0; // XXX TODO
++	return nw_ftw(PORT_CONFIG_DIR, PORT_CONFIG_DIR "/*", __nw_ports_enumerate, ports);
+ }
+diff --git a/src/networkd/string.h b/src/networkd/string.h
+index f3c5b71..6ff44f9 100644
+--- a/src/networkd/string.h
++++ b/src/networkd/string.h
+@@ -142,4 +142,12 @@ static inline int __nw_path_join(char* s, const size_t length,
+ 	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 0000000..0381ea8
+--- /dev/null
++++ b/src/networkd/util.c
+@@ -0,0 +1,61 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <errno.h>
++#include <fnmatch.h>
++#include <ftw.h>
++#include <stddef.h>
++#include <stdio.h>
++
++#include "util.h"
++
++int nw_ftw(const char* path, const char* filter,
++		int (*callback)(const char* path, const struct stat* s, void* data), void* data) {
++	/*
++		This is a nested function which allows us to pass some custom pointer to
++		the callback function as that isn't possible with nftw().
++	*/
++	int __callback(const char* p, const struct stat* s, int type, struct FTW* ftw) {
++		int r;
++
++		// Filter out anything we don't want
++		if (filter) {
++			r = fnmatch(filter, p, FNM_PATHNAME);
++
++			switch (r) {
++				// Pattern didn't match
++				case FNM_NOMATCH:
++					return 0;
++
++				// Pattern matched
++				case 0:
++					break;
++
++				// Error
++				default:
++					return 1;
++			}
++		}
++
++		return callback(p, s, data);
++	}
++
++	return nftw(path, __callback, 0, 0);
++}
+diff --git a/src/networkd/util.h b/src/networkd/util.h
+new file mode 100644
+index 0000000..950afda
+--- /dev/null
++++ b/src/networkd/util.h
+@@ -0,0 +1,29 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_UTIL_H
++#define NETWORKD_UTIL_H
++
++#include <sys/stat.h>
++
++int nw_ftw(const char* path, const char* filter,
++		int (*callback)(const char* path, const struct stat* s, void* data), void* data);
++
++#endif /* NETWORKD_UTIL_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0196-networkd-Perform-port-setup-from-configuration.patch b/network/patches/0196-networkd-Perform-port-setup-from-configuration.patch
new file mode 100644
index 000000000..96bc04918
--- /dev/null
+++ b/network/patches/0196-networkd-Perform-port-setup-from-configuration.patch
@@ -0,0 +1,147 @@
+From 827435c82f60ba1b7717d74faeac6cb6b85d6588 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 11:21:02 +0000
+Subject: [PATCH 196/304] networkd: Perform port setup from configuration
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c | 76 +++++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/port.h |  5 +++
+ 2 files changed, 81 insertions(+)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 019521f..f78489b 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -18,10 +18,12 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <limits.h>
+ #include <net/if.h>
+ #include <stdlib.h>
+ 
+ #include "config.h"
++#include "logging.h"
+ #include "string.h"
+ #include "port.h"
+ 
+@@ -29,11 +31,31 @@ struct nw_port {
+ 	int nrefs;
+ 
+ 	char name[IF_NAMESIZE];
++	nw_port_type_t type;
+ 
+ 	// Configuration
+ 	struct nw_config *config;
+ };
+ 
++static const struct nw_port_type_map {
++	nw_port_type_t type;
++	const char* name;
++} nw_port_type_map[] = {
++	{ NW_PORT_DUMMY, "dummy" },
++	{ NW_PORT_UNKNOWN, NULL },
++};
++
++static nw_port_type_t nw_port_type_from_string(const char* s) {
++	const struct nw_port_type_map* map = NULL;
++
++	for (map = nw_port_type_map; *map->name; map++) {
++		if (strcmp(map->name, s) == 0)
++			return map->type;
++	}
++
++	return NW_PORT_UNKNOWN;
++}
++
+ static void nw_port_free(struct nw_port* port) {
+ 	if (port->config)
+ 		nw_config_unref(port->config);
+@@ -41,6 +63,55 @@ static void nw_port_free(struct nw_port* port) {
+ 	free(port);
+ }
+ 
++static int nw_port_setup_common(struct nw_port* port) {
++	return 0; // XXX TODO
++}
++
++static nw_port_type_t nw_port_setup_type(struct nw_port* port) {
++	const char* type = nw_config_get(port->config, "TYPE");
++	if (!type)
++		return NW_PORT_UNKNOWN;
++
++	return nw_port_type_from_string(type);
++}
++
++static int nw_port_setup(struct nw_port* port) {
++	char path[PATH_MAX];
++	int r;
++
++	// Compose the path to the main configuration file
++	r = nw_path_join(path, PORT_CONFIG_DIR, port->name);
++	if (r)
++		return r;
++
++	// Initialize the configuration
++	r = nw_config_create(&port->config, path);
++	if (r)
++		return r;
++
++	// Determine type
++	port->type = nw_port_setup_type(port);
++	if (!port->type) {
++		ERROR("Could not determine type of port %s\n", port->name);
++		return 0;
++	}
++
++	// Perform some common initialization
++	r = nw_port_setup_common(port);
++	if (r)
++		return r;
++
++	// Call any custom initialization
++	switch (port->type) {
++		// These do not need any special initialization
++		case NW_PORT_DUMMY:
++		case NW_PORT_UNKNOWN:
++			break;
++	}
++
++	return 0;
++}
++
+ int nw_port_create(struct nw_port** port, const char* name) {
+ 	int r;
+ 
+@@ -57,6 +128,11 @@ int nw_port_create(struct nw_port** port, const char* name) {
+ 	if (r)
+ 		goto ERROR;
+ 
++	// Setup the port
++	r = nw_port_setup(p);
++	if (r)
++		goto ERROR;
++
+ 	*port = p;
+ 	return 0;
+ 
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 250a694..af7fbdd 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -23,6 +23,11 @@
+ 
+ #define PORT_CONFIG_DIR			CONFIG_DIR "/ports"
+ 
++typedef enum nw_port_type {
++	NW_PORT_UNKNOWN = 0,
++	NW_PORT_DUMMY,
++} nw_port_type_t;
++
+ struct nw_port;
+ 
+ int nw_port_create(struct nw_port** port, const char* name);
+-- 
+2.39.2
+
diff --git a/network/patches/0197-networkd-Read-Ethernet-address-from-configuration.patch b/network/patches/0197-networkd-Read-Ethernet-address-from-configuration.patch
new file mode 100644
index 000000000..38ca85ee1
--- /dev/null
+++ b/network/patches/0197-networkd-Read-Ethernet-address-from-configuration.patch
@@ -0,0 +1,145 @@
+From 2e7f4705a61e8435e22d49fcff49ecf8dde389ef Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 11:48:23 +0000
+Subject: [PATCH 197/304] networkd: Read Ethernet address from configuration
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am            |  1 +
+ src/networkd/address.h | 47 ++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/port.c    | 36 +++++++++++++++++++++++++++++++-
+ 3 files changed, 83 insertions(+), 1 deletion(-)
+ create mode 100644 src/networkd/address.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 505d679..a27736a 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -308,6 +308,7 @@ sbin_PROGRAMS += \
+ 	networkd
+ 
+ dist_networkd_SOURCES = \
++	src/networkd/address.h \
+ 	src/networkd/bus.c \
+ 	src/networkd/bus.h \
+ 	src/networkd/config.c \
+diff --git a/src/networkd/address.h b/src/networkd/address.h
+new file mode 100644
+index 0000000..1d9fbfc
+--- /dev/null
++++ b/src/networkd/address.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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_ADDRESS_H
++#define NETWORKD_ADDRESS_H
++
++#include <netinet/ether.h>
++#include <string.h>
++
++typedef struct ether_addr nw_address_t;
++
++static inline int nw_address_from_string(nw_address_t* addr, const char* s) {
++	struct ether_addr* p = ether_aton_r(s, addr);
++	if (!p)
++		return 1;
++
++	return 0;
++}
++
++static inline char* nw_address_to_string(const nw_address_t* addr) {
++	char buffer[ETH_ALEN];
++
++	char* p = ether_ntoa_r(addr, buffer);
++	if (!p)
++		return NULL;
++
++	return strdup(buffer);
++}
++
++#endif /* NETWORKD_ADDRESS_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index f78489b..aff79a5 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -20,8 +20,10 @@
+ 
+ #include <limits.h>
+ #include <net/if.h>
++#include <stdint.h>
+ #include <stdlib.h>
+ 
++#include "address.h"
+ #include "config.h"
+ #include "logging.h"
+ #include "string.h"
+@@ -35,6 +37,9 @@ struct nw_port {
+ 
+ 	// Configuration
+ 	struct nw_config *config;
++
++	// Common attributes
++	nw_address_t address;
+ };
+ 
+ static const struct nw_port_type_map {
+@@ -63,8 +68,37 @@ static void nw_port_free(struct nw_port* port) {
+ 	free(port);
+ }
+ 
++static int nw_port_setup_address(struct nw_port* port) {
++	int r;
++
++	// Read ADDRESS from configuration
++	const char* s = nw_config_get(port->config, "ADDRESS");
++	if (!s) {
++		ERROR("Port %s: Address isn't set\n", port->name);
++		return 1;
++	}
++
++	// Parse the address
++	r = nw_address_from_string(&port->address, s);
++	if (r) {
++		ERROR("Port %s: Could not parse address: %m\n", port->name);
++		return r;
++	}
++
++	// XXX Do we need to check for multicast here?
++
++	return 0;
++}
++
+ static int nw_port_setup_common(struct nw_port* port) {
+-	return 0; // XXX TODO
++	int r;
++
++	// Address
++	r = nw_port_setup_address(port);
++	if (r)
++		return r;
++
++	return 0;
+ }
+ 
+ static nw_port_type_t nw_port_setup_type(struct nw_port* port) {
+-- 
+2.39.2
+
diff --git a/network/patches/0198-networkd-Generate-a-random-Ethernet-address-for-port.patch b/network/patches/0198-networkd-Generate-a-random-Ethernet-address-for-port.patch
new file mode 100644
index 000000000..8067e204d
--- /dev/null
+++ b/network/patches/0198-networkd-Generate-a-random-Ethernet-address-for-port.patch
@@ -0,0 +1,93 @@
+From 9a93da54510ec2b49e0890d2b420a097316eaace Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 12:01:42 +0000
+Subject: [PATCH 198/304] networkd: Generate a random Ethernet address for
+ ports
+
+This happens when either no address was set, or it cannot be parsed.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/address.h | 25 +++++++++++++++++++++++++
+ src/networkd/port.c    | 14 ++++++++++++--
+ 2 files changed, 37 insertions(+), 2 deletions(-)
+
+diff --git a/src/networkd/address.h b/src/networkd/address.h
+index 1d9fbfc..f875b26 100644
+--- a/src/networkd/address.h
++++ b/src/networkd/address.h
+@@ -23,6 +23,9 @@
+ 
+ #include <netinet/ether.h>
+ #include <string.h>
++#include <sys/random.h>
++
++#include "logging.h"
+ 
+ typedef struct ether_addr nw_address_t;
+ 
+@@ -44,4 +47,26 @@ static inline char* nw_address_to_string(const nw_address_t* addr) {
+ 	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] &= 0xfe;
++
++	// Set the software-generated bit
++	addr->ether_addr_octet[1] |= 0x02;
++
++	return 0;
++}
++
+ #endif /* NETWORKD_ADDRESS_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index aff79a5..4c32592 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -75,18 +75,28 @@ static int nw_port_setup_address(struct nw_port* port) {
+ 	const char* s = nw_config_get(port->config, "ADDRESS");
+ 	if (!s) {
+ 		ERROR("Port %s: Address isn't set\n", port->name);
+-		return 1;
++		goto ERROR;
+ 	}
+ 
+ 	// Parse the address
+ 	r = nw_address_from_string(&port->address, s);
+ 	if (r) {
+ 		ERROR("Port %s: Could not parse address: %m\n", port->name);
+-		return r;
++		goto ERROR;
+ 	}
+ 
+ 	// XXX Do we need to check for multicast here?
+ 
++	return 0;
++
++ERROR:
++	// Generate a random Ethernet address
++	r = nw_address_generate(&port->address);
++	if (r) {
++		ERROR("Could not generate a random Ethernet address: %m\n");
++		return r;
++	}
++
+ 	return 0;
+ }
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0199-networkd-Introduce-address-flags-for-better-readabil.patch b/network/patches/0199-networkd-Introduce-address-flags-for-better-readabil.patch
new file mode 100644
index 000000000..35671509c
--- /dev/null
+++ b/network/patches/0199-networkd-Introduce-address-flags-for-better-readabil.patch
@@ -0,0 +1,43 @@
+From 35fc59cd654c084cfe2550ed874d768f624b591e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 12:06:18 +0000
+Subject: [PATCH 199/304] networkd: Introduce address flags for better
+ readability
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/address.h | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/src/networkd/address.h b/src/networkd/address.h
+index f875b26..0885d75 100644
+--- a/src/networkd/address.h
++++ b/src/networkd/address.h
+@@ -29,6 +29,11 @@
+ 
+ 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) {
+ 	struct ether_addr* p = ether_aton_r(s, addr);
+ 	if (!p)
+@@ -61,10 +66,10 @@ static inline int nw_address_generate(nw_address_t* addr) {
+ 	}
+ 
+ 	// Clear the multicast bit
+-	addr->ether_addr_octet[0] &= 0xfe;
++	addr->ether_addr_octet[0] &= ~NW_ADDRESS_MULTICAST;
+ 
+ 	// Set the software-generated bit
+-	addr->ether_addr_octet[1] |= 0x02;
++	addr->ether_addr_octet[0] |= NW_ADDRESS_SOFTWAREASSIGNED;
+ 
+ 	return 0;
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0200-networkd-Check-if-Ethernet-addresses-from-config-are.patch b/network/patches/0200-networkd-Check-if-Ethernet-addresses-from-config-are.patch
new file mode 100644
index 000000000..b1f3b2ab4
--- /dev/null
+++ b/network/patches/0200-networkd-Check-if-Ethernet-addresses-from-config-are.patch
@@ -0,0 +1,46 @@
+From d81d9cf5582b9be41968c10c01bf436b066d4e34 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 12:10:15 +0000
+Subject: [PATCH 200/304] networkd: Check if Ethernet addresses from config are
+ usable
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/address.h | 4 ++++
+ src/networkd/port.c    | 7 ++++++-
+ 2 files changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/address.h b/src/networkd/address.h
+index 0885d75..d284a0b 100644
+--- a/src/networkd/address.h
++++ b/src/networkd/address.h
+@@ -74,4 +74,8 @@ static inline int nw_address_generate(nw_address_t* addr) {
+ 	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/port.c b/src/networkd/port.c
+index 4c32592..89f3181 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -85,7 +85,12 @@ static int nw_port_setup_address(struct nw_port* port) {
+ 		goto ERROR;
+ 	}
+ 
+-	// XXX Do we need to check for multicast here?
++	// Check if this address is usable
++	r = nw_address_is_multicast(&port->address);
++	if (r) {
++		DEBUG("Port %s: Multicast bit is set on Ethernet address\n", port->name);
++		goto ERROR;
++	}
+ 
+ 	return 0;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0201-networkd-Export-ports-over-dbus.patch b/network/patches/0201-networkd-Export-ports-over-dbus.patch
new file mode 100644
index 000000000..4381b54bb
--- /dev/null
+++ b/network/patches/0201-networkd-Export-ports-over-dbus.patch
@@ -0,0 +1,347 @@
+From 7297ba7fab067d53560eaeba7e8aa992cef3308a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 12:34:41 +0000
+Subject: [PATCH 201/304] networkd: Export ports over dbus
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am               |  6 ++-
+ src/networkd/daemon-bus.c |  3 +-
+ src/networkd/daemon.c     | 14 ++++++
+ src/networkd/daemon.h     |  6 +++
+ src/networkd/port-bus.c   | 89 +++++++++++++++++++++++++++++++++++++++
+ src/networkd/port-bus.h   | 28 ++++++++++++
+ src/networkd/port.c       | 14 ++++++
+ src/networkd/port.h       |  2 +
+ src/networkd/ports.c      | 52 +++++++++++++++++++++++
+ src/networkd/ports.h      |  6 ++-
+ 10 files changed, 216 insertions(+), 4 deletions(-)
+ create mode 100644 src/networkd/port-bus.c
+ create mode 100644 src/networkd/port-bus.h
+
+diff --git a/Makefile.am b/Makefile.am
+index a27736a..893f1b8 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -325,10 +325,12 @@ dist_networkd_SOURCES = \
+ 	src/networkd/links.h \
+ 	src/networkd/logging.h \
+ 	src/networkd/main.c \
+-	src/networkd/port.c \
+-	src/networkd/port.h \
+ 	src/networkd/ports.c \
+ 	src/networkd/ports.h \
++	src/networkd/port.c \
++	src/networkd/port.h \
++	src/networkd/port-bus.c \
++	src/networkd/port-bus.h \
+ 	src/networkd/string.h \
+ 	src/networkd/util.c \
+ 	src/networkd/util.h \
+diff --git a/src/networkd/daemon-bus.c b/src/networkd/daemon-bus.c
+index aac9775..ca0754f 100644
+--- a/src/networkd/daemon-bus.c
++++ b/src/networkd/daemon-bus.c
+@@ -22,6 +22,7 @@
+ 
+ #include "bus.h"
+ #include "daemon.h"
++#include "port-bus.h"
+ #include "zone-bus.h"
+ 
+ static int nw_daemon_bus_reload(sd_bus_message* m, void* data, sd_bus_error* error) {
+@@ -45,5 +46,5 @@ const struct nw_bus_implementation daemon_bus_impl = {
+ 	.path = "/org/ipfire/network1",
+ 	.interface = "org.ipfire.network1",
+ 	.vtables = BUS_VTABLES(daemon_vtable),
+-	.children = BUS_IMPLEMENTATIONS(&zone_bus_impl),
++	.children = BUS_IMPLEMENTATIONS(&port_bus_impl, &zone_bus_impl),
+ };
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index aee27e3..795a8b7 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -448,6 +448,20 @@ struct nw_link* nw_daemon_get_link_by_ifindex(struct nw_daemon* daemon, int ifin
+ 	return nw_links_get_by_ifindex(daemon->links, ifindex);
+ }
+ 
++/*
++	Ports
++*/
++struct nw_ports* nw_daemon_ports(struct nw_daemon* daemon) {
++	return nw_ports_ref(daemon->ports);
++}
++
++struct nw_port* nw_daemon_get_port_by_name(struct nw_daemon* daemon, const char* name) {
++	if (!daemon->ports)
++		return NULL;
++
++	return nw_ports_get_by_name(daemon->ports, name);
++}
++
+ /*
+ 	Zones
+ */
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index 8e534c5..e08537e 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -49,6 +49,12 @@ struct nw_links* nw_daemon_links(struct nw_daemon* daemon);
+ void nw_daemon_drop_link(struct nw_daemon* daemon, struct nw_link* link);
+ struct nw_link* nw_daemon_get_link_by_ifindex(struct nw_daemon* daemon, int ifindex);
+ 
++/*
++	Ports
++*/
++struct nw_ports* nw_daemon_ports(struct nw_daemon* daemon);
++struct nw_port* nw_daemon_get_port_by_name(struct nw_daemon* daemon, const char* name);
++
+ /*
+ 	Zones
+ */
+diff --git a/src/networkd/port-bus.c b/src/networkd/port-bus.c
+new file mode 100644
+index 0000000..8c327b9
+--- /dev/null
++++ b/src/networkd/port-bus.c
+@@ -0,0 +1,89 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <errno.h>
++
++#include "bus.h"
++#include "daemon.h"
++#include "logging.h"
++#include "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
++	struct nw_daemon* daemon = (struct nw_daemon*)data;
++
++	// Fetch ports
++	struct 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
++	struct nw_daemon* daemon = (struct 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
++	struct 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 const sd_bus_vtable port_vtable[] = {
++	SD_BUS_VTABLE_START(0),
++	SD_BUS_VTABLE_END
++};
++
++const struct 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 0000000..95e49a8
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_PORT_BUS_H
++#define NETWORKD_PORT_BUS_H
++
++#include "bus.h"
++
++extern const struct nw_bus_implementation port_bus_impl;
++
++#endif /* NETWORKD_PORT_BUS_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 89f3181..d1e3f7f 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -23,6 +23,8 @@
+ #include <stdint.h>
+ #include <stdlib.h>
+ 
++#include <systemd/sd-bus.h>
++
+ #include "address.h"
+ #include "config.h"
+ #include "logging.h"
+@@ -207,3 +209,15 @@ struct nw_port* nw_port_unref(struct nw_port* port) {
+ const char* nw_port_name(struct nw_port* port) {
+ 	return port->name;
+ }
++
++char* nw_port_bus_path(struct 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;
++}
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index af7fbdd..e3655cf 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -37,4 +37,6 @@ struct nw_port* nw_port_unref(struct nw_port* port);
+ 
+ const char* nw_port_name(struct nw_port* port);
+ 
++char* nw_port_bus_path(struct nw_port* port);
++
+ #endif /* NETWORKD_PORT_H */
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+index 19a3a59..24bd257 100644
+--- a/src/networkd/ports.c
++++ b/src/networkd/ports.c
+@@ -157,3 +157,55 @@ ERROR:
+ int nw_ports_enumerate(struct nw_ports* ports) {
+ 	return nw_ftw(PORT_CONFIG_DIR, PORT_CONFIG_DIR "/*", __nw_ports_enumerate, ports);
+ }
++
++struct nw_port* nw_ports_get_by_name(struct 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(struct 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;
++}
+diff --git a/src/networkd/ports.h b/src/networkd/ports.h
+index 76a4a3a..e807e25 100644
+--- a/src/networkd/ports.h
++++ b/src/networkd/ports.h
+@@ -32,4 +32,8 @@ struct nw_ports* nw_ports_unref(struct nw_ports* ports);
+ 
+ int nw_ports_enumerate(struct nw_ports* ports);
+ 
+-#endif /* NETWORKD_ZONES_H */
++struct nw_port* nw_ports_get_by_name(struct nw_ports* ports, const char* name);
++
++int nw_ports_bus_paths(struct nw_ports* ports, char*** paths);
++
++#endif /* NETWORKD_PORTS_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0202-networkd-address-Fix-buffer-to-Ethernet-address-stri.patch b/network/patches/0202-networkd-address-Fix-buffer-to-Ethernet-address-stri.patch
new file mode 100644
index 000000000..b72c77c0f
--- /dev/null
+++ b/network/patches/0202-networkd-address-Fix-buffer-to-Ethernet-address-stri.patch
@@ -0,0 +1,27 @@
+From 8c569e1ca29435a0caa6ecc3b2118ac2dbac4466 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 13:53:52 +0000
+Subject: [PATCH 202/304] networkd: address: Fix buffer to Ethernet address
+ strings
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/address.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/networkd/address.h b/src/networkd/address.h
+index d284a0b..b7556f4 100644
+--- a/src/networkd/address.h
++++ b/src/networkd/address.h
+@@ -43,7 +43,7 @@ static inline int nw_address_from_string(nw_address_t* addr, const char* s) {
+ }
+ 
+ static inline char* nw_address_to_string(const nw_address_t* addr) {
+-	char buffer[ETH_ALEN];
++	char buffer[18];
+ 
+ 	char* p = ether_ntoa_r(addr, buffer);
+ 	if (!p)
+-- 
+2.39.2
+
diff --git a/network/patches/0203-networkd-ports-Export-Ethernet-address-over-dbus.patch b/network/patches/0203-networkd-ports-Export-Ethernet-address-over-dbus.patch
new file mode 100644
index 000000000..91a0ac533
--- /dev/null
+++ b/network/patches/0203-networkd-ports-Export-Ethernet-address-over-dbus.patch
@@ -0,0 +1,102 @@
+From 4a383286ff94b4bc6efdde95c26070b68b8942e3 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 13:54:33 +0000
+Subject: [PATCH 203/304] networkd: ports: Export Ethernet address over dbus
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-bus.c | 34 ++++++++++++++++++++++++++++++++++
+ src/networkd/port.c     |  4 ++++
+ src/networkd/port.h     |  4 ++++
+ 3 files changed, 42 insertions(+)
+
+diff --git a/src/networkd/port-bus.c b/src/networkd/port-bus.c
+index 8c327b9..1a48655 100644
+--- a/src/networkd/port-bus.c
++++ b/src/networkd/port-bus.c
+@@ -19,7 +19,9 @@
+ #############################################################################*/
+ 
+ #include <errno.h>
++#include <stdlib.h>
+ 
++#include "address.h"
+ #include "bus.h"
+ #include "daemon.h"
+ #include "logging.h"
+@@ -76,8 +78,40 @@ static int nw_port_object_find(sd_bus* bus, const char* path, const char* interf
+ 	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) {
++	struct nw_port* port = (struct 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 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),
++
+ 	SD_BUS_VTABLE_END
+ };
+ 
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index d1e3f7f..e59e760 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -221,3 +221,7 @@ char* nw_port_bus_path(struct nw_port* port) {
+ 
+ 	return p;
+ }
++
++const nw_address_t* nw_port_get_address(struct nw_port* port) {
++	return &port->address;
++}
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index e3655cf..caf1433 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -30,6 +30,8 @@ typedef enum nw_port_type {
+ 
+ struct nw_port;
+ 
++#include "address.h"
++
+ int nw_port_create(struct nw_port** port, const char* name);
+ 
+ struct nw_port* nw_port_ref(struct nw_port* port);
+@@ -39,4 +41,6 @@ const char* nw_port_name(struct nw_port* port);
+ 
+ char* nw_port_bus_path(struct nw_port* port);
+ 
++const nw_address_t* nw_port_get_address(struct nw_port* port);
++
+ #endif /* NETWORKD_PORT_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0204-networkd-Add-method-to-fetch-corresponding-link-to-p.patch b/network/patches/0204-networkd-Add-method-to-fetch-corresponding-link-to-p.patch
new file mode 100644
index 000000000..e762f961c
--- /dev/null
+++ b/network/patches/0204-networkd-Add-method-to-fetch-corresponding-link-to-p.patch
@@ -0,0 +1,201 @@
+From 30d4ab67ce9936c6668d93229c8cc808c725338c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 14:10:16 +0000
+Subject: [PATCH 204/304] networkd: Add method to fetch corresponding link to
+ port
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c |  7 +++++++
+ src/networkd/daemon.h |  1 +
+ src/networkd/link.h   |  5 +++--
+ src/networkd/links.c  | 15 +++++++++++++++
+ src/networkd/links.h  |  1 +
+ src/networkd/port.c   | 13 ++++++++++++-
+ src/networkd/port.h   |  3 ++-
+ src/networkd/ports.c  |  2 +-
+ 8 files changed, 42 insertions(+), 5 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 795a8b7..c4acff5 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -448,6 +448,13 @@ struct nw_link* nw_daemon_get_link_by_ifindex(struct nw_daemon* daemon, int ifin
+ 	return nw_links_get_by_ifindex(daemon->links, ifindex);
+ }
+ 
++struct nw_link* nw_daemon_get_link_by_name(struct nw_daemon* daemon, const char* name) {
++	if (!daemon->links)
++		return NULL;
++
++	return nw_links_get_by_name(daemon->links, name);
++}
++
+ /*
+ 	Ports
+ */
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index e08537e..a40dd60 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -48,6 +48,7 @@ sd_netlink* nw_daemon_get_rtnl(struct nw_daemon* daemon);
+ struct nw_links* nw_daemon_links(struct nw_daemon* daemon);
+ void nw_daemon_drop_link(struct nw_daemon* daemon, struct nw_link* link);
+ struct nw_link* nw_daemon_get_link_by_ifindex(struct nw_daemon* daemon, int ifindex);
++struct nw_link* nw_daemon_get_link_by_name(struct nw_daemon* daemon, const char* name);
+ 
+ /*
+ 	Ports
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index e482d71..72ddfa9 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -21,16 +21,17 @@
+ #ifndef NETWORKD_LINK_H
+ #define NETWORKD_LINK_H
+ 
+-#include "daemon.h"
+-
+ struct nw_link;
+ 
++#include "daemon.h"
++
+ int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex);
+ 
+ struct nw_link* nw_link_ref(struct nw_link* link);
+ struct nw_link* nw_link_unref(struct nw_link* link);
+ 
+ int nw_link_ifindex(struct nw_link* link);
++const char* nw_link_name(struct nw_link* link);
+ 
+ int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data);
+ 
+diff --git a/src/networkd/links.c b/src/networkd/links.c
+index 044a27d..7aa83a3 100644
+--- a/src/networkd/links.c
++++ b/src/networkd/links.c
+@@ -20,6 +20,7 @@
+ 
+ #include <stddef.h>
+ #include <stdlib.h>
++#include <string.h>
+ #include <sys/queue.h>
+ 
+ #include "daemon.h"
+@@ -192,3 +193,17 @@ struct nw_link* nw_links_get_by_ifindex(struct nw_links* links, int ifindex) {
+ 
+ 	return nw_link_ref(entry->link);
+ }
++
++struct nw_link* nw_links_get_by_name(struct nw_links* links, const char* name) {
++	struct nw_links_entry* entry = NULL;
++
++	STAILQ_FOREACH(entry, &links->entries, nodes) {
++		const char* n = nw_link_name(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
+index 2f2bbea..2b5c787 100644
+--- a/src/networkd/links.h
++++ b/src/networkd/links.h
+@@ -37,5 +37,6 @@ void nw_links_drop_link(struct nw_links* links, struct nw_link* link);
+ int nw_links_enumerate(struct nw_links* links);
+ 
+ struct nw_link* nw_links_get_by_ifindex(struct nw_links* links, int ifindex);
++struct nw_link* nw_links_get_by_name(struct nw_links* links, const char* name);
+ 
+ #endif /* NETWORKD_LINKS_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index e59e760..33e75d7 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -27,11 +27,13 @@
+ 
+ #include "address.h"
+ #include "config.h"
++#include "link.h"
+ #include "logging.h"
+ #include "string.h"
+ #include "port.h"
+ 
+ struct nw_port {
++	struct nw_daemon* daemon;
+ 	int nrefs;
+ 
+ 	char name[IF_NAMESIZE];
+@@ -66,6 +68,8 @@ static nw_port_type_t nw_port_type_from_string(const char* s) {
+ static void nw_port_free(struct nw_port* port) {
+ 	if (port->config)
+ 		nw_config_unref(port->config);
++	if (port->daemon)
++		nw_daemon_unref(port->daemon);
+ 
+ 	free(port);
+ }
+@@ -163,7 +167,7 @@ static int nw_port_setup(struct nw_port* port) {
+ 	return 0;
+ }
+ 
+-int nw_port_create(struct nw_port** port, const char* name) {
++int nw_port_create(struct nw_port** port, struct nw_daemon* daemon, const char* name) {
+ 	int r;
+ 
+ 	// Allocate a new object
+@@ -171,6 +175,9 @@ int nw_port_create(struct nw_port** port, const char* name) {
+ 	if (!p)
+ 		return 1;
+ 
++	// Store a reference to the daemon
++	p->daemon = nw_daemon_ref(daemon);
++
+ 	// Initialize reference counter
+ 	p->nrefs = 1;
+ 
+@@ -222,6 +229,10 @@ char* nw_port_bus_path(struct nw_port* port) {
+ 	return p;
+ }
+ 
++static struct nw_link* nw_port_get_link(struct nw_port* port) {
++	return nw_daemon_get_link_by_name(port->daemon, port->name);
++}
++
+ const nw_address_t* nw_port_get_address(struct nw_port* port) {
+ 	return &port->address;
+ }
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index caf1433..92d60b2 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -31,8 +31,9 @@ typedef enum nw_port_type {
+ struct nw_port;
+ 
+ #include "address.h"
++#include "daemon.h"
+ 
+-int nw_port_create(struct nw_port** port, const char* name);
++int nw_port_create(struct nw_port** port, struct nw_daemon* daemon, const char* name);
+ 
+ struct nw_port* nw_port_ref(struct nw_port* port);
+ struct nw_port* nw_port_unref(struct nw_port* port);
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+index 24bd257..9cec111 100644
+--- a/src/networkd/ports.c
++++ b/src/networkd/ports.c
+@@ -138,7 +138,7 @@ static int __nw_ports_enumerate(const char* path, const struct stat* s, void* da
+ 		return 0;
+ 
+ 	// Create a new port
+-	r = nw_port_create(&port, name);
++	r = nw_port_create(&port, ports->daemon, name);
+ 	if (r)
+ 		goto ERROR;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0205-networkd-Use-typedef-to-keep-type-names-shorter.patch b/network/patches/0205-networkd-Use-typedef-to-keep-type-names-shorter.patch
new file mode 100644
index 000000000..c8f82a9e4
--- /dev/null
+++ b/network/patches/0205-networkd-Use-typedef-to-keep-type-names-shorter.patch
@@ -0,0 +1,1617 @@
+From 2361667ee3e575e7c1b3044936a7b466283d7996 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 17:44:42 +0000
+Subject: [PATCH 205/304] networkd: Use typedef to keep type names shorter
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/bus.c        |  4 +--
+ src/networkd/bus.h        | 10 +++----
+ src/networkd/config.c     | 36 +++++++++++------------
+ src/networkd/config.h     | 26 ++++++++---------
+ src/networkd/daemon-bus.c |  4 +--
+ src/networkd/daemon-bus.h |  2 +-
+ src/networkd/daemon.c     | 60 +++++++++++++++++++--------------------
+ src/networkd/daemon.h     | 34 ++++++++++++----------
+ src/networkd/link.c       | 26 ++++++++---------
+ src/networkd/link.h       | 12 ++++----
+ src/networkd/links.c      | 26 ++++++++---------
+ src/networkd/links.h      | 18 ++++++------
+ src/networkd/main.c       |  2 +-
+ src/networkd/port-bus.c   | 12 ++++----
+ src/networkd/port-bus.h   |  2 +-
+ src/networkd/port.c       | 30 ++++++++++----------
+ src/networkd/port.h       | 14 ++++-----
+ src/networkd/ports.c      | 26 ++++++++---------
+ src/networkd/ports.h      | 14 ++++-----
+ src/networkd/zone-bus.c   | 14 ++++-----
+ src/networkd/zone-bus.h   |  2 +-
+ src/networkd/zone.c       | 24 ++++++++--------
+ src/networkd/zone.h       | 16 +++++------
+ src/networkd/zones.c      | 26 ++++++++---------
+ src/networkd/zones.h      | 14 ++++-----
+ 25 files changed, 229 insertions(+), 225 deletions(-)
+
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+index 258aacd..1daa035 100644
+--- a/src/networkd/bus.c
++++ b/src/networkd/bus.c
+@@ -29,14 +29,14 @@
+ #include "logging.h"
+ 
+ static int nw_bus_on_connect(sd_bus_message* m, void* data, sd_bus_error* error) {
+-	struct nw_daemon* daemon = (struct nw_daemon*)data;
++	nw_daemon* daemon = (nw_daemon*)data;
+ 
+ 	DEBUG("Connected to D-Bus\n");
+ 
+ 	return 0;
+ }
+ 
+-int nw_bus_connect(sd_bus* bus, sd_event* loop, struct nw_daemon* daemon) {
++int nw_bus_connect(sd_bus* bus, sd_event* loop, nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Create a bus object
+diff --git a/src/networkd/bus.h b/src/networkd/bus.h
+index 42ab2d3..05b4c63 100644
+--- a/src/networkd/bus.h
++++ b/src/networkd/bus.h
+@@ -30,27 +30,27 @@
+ 
+ #include "daemon.h"
+ 
+-int nw_bus_connect(sd_bus* bus, sd_event* loop, struct nw_daemon* daemon);
++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;
+ };
+ 
+-struct nw_bus_implementation {
++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 struct nw_bus_implementation* []) { __VA_ARGS__, NULL })
++#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 struct nw_bus_implementation* impl, void* data);
++	const nw_bus_implementation* impl, void* data);
+ 
+ #endif /* NETWORKD_BUS_H */
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 17d6d89..b3d5284 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -50,7 +50,7 @@ static void nw_config_entry_free(struct nw_config_entry* entry) {
+ }
+ 
+ static struct nw_config_entry* nw_config_entry_create(
+-		struct nw_config* config, const char* key) {
++		nw_config* config, const char* key) {
+ 	int r;
+ 
+ 	// Check input value
+@@ -79,17 +79,17 @@ ERROR:
+ 	return NULL;
+ }
+ 
+-static void nw_config_free(struct nw_config* config) {
++static void nw_config_free(nw_config* config) {
+ 	// Flush all entries
+ 	nw_config_flush(config);
+ 
+ 	free(config);
+ }
+ 
+-int nw_config_create(struct nw_config** config, const char* path) {
++int nw_config_create(nw_config** config, const char* path) {
+ 	int r;
+ 
+-	struct nw_config* c = calloc(1, sizeof(*c));
++	nw_config* c = calloc(1, sizeof(*c));
+ 	if (!c)
+ 		return 1;
+ 
+@@ -121,13 +121,13 @@ ERROR:
+ 	return r;
+ }
+ 
+-struct nw_config* nw_config_ref(struct nw_config* config) {
++nw_config* nw_config_ref(nw_config* config) {
+ 	config->nrefs++;
+ 
+ 	return config;
+ }
+ 
+-struct nw_config* nw_config_unref(struct nw_config* config) {
++nw_config* nw_config_unref(nw_config* config) {
+ 	if (--config->nrefs > 0)
+ 		return config;
+ 
+@@ -135,14 +135,14 @@ struct nw_config* nw_config_unref(struct nw_config* config) {
+ 	return NULL;
+ }
+ 
+-const char* nw_config_path(struct nw_config* config) {
++const char* nw_config_path(nw_config* config) {
+ 	if (*config->path)
+ 		return config->path;
+ 
+ 	return NULL;
+ }
+ 
+-int nw_config_flush(struct nw_config* config) {
++int nw_config_flush(nw_config* config) {
+ 	struct nw_config_entry* entry = NULL;
+ 
+ 	while (!STAILQ_EMPTY(&config->entries)) {
+@@ -156,7 +156,7 @@ int nw_config_flush(struct nw_config* config) {
+ 	return 0;
+ }
+ 
+-static int nw_config_readf(struct nw_config* config, FILE* f) {
++static int nw_config_readf(nw_config* config, FILE* f) {
+ 	char* line = NULL;
+ 	size_t length = 0;
+ 	int r;
+@@ -202,7 +202,7 @@ static int nw_config_readf(struct nw_config* config, FILE* f) {
+ 	return r;
+ }
+ 
+-int nw_config_read(struct nw_config* config) {
++int nw_config_read(nw_config* config) {
+ 	FILE* f = NULL;
+ 	int r;
+ 
+@@ -234,7 +234,7 @@ ERROR:
+ 	return r;
+ }
+ 
+-static int nw_config_writef(struct nw_config* config, FILE* f) {
++static int nw_config_writef(nw_config* config, FILE* f) {
+ 	struct nw_config_entry* entry = NULL;
+ 	int r;
+ 
+@@ -254,7 +254,7 @@ static int nw_config_writef(struct nw_config* config, FILE* f) {
+ 	return 0;
+ }
+ 
+-int nw_config_write(struct nw_config* config) {
++int nw_config_write(nw_config* config) {
+ 	int r;
+ 
+ 	// We cannot write if path is not set
+@@ -280,7 +280,7 @@ ERROR:
+ 	return r;
+ }
+ 
+-static struct nw_config_entry* nw_config_find(struct nw_config* config, const char* key) {
++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) {
+@@ -296,7 +296,7 @@ static struct nw_config_entry* nw_config_find(struct nw_config* config, const ch
+ 	return NULL;
+ }
+ 
+-int nw_config_del(struct nw_config* config, const char* key) {
++int nw_config_del(nw_config* config, const char* key) {
+ 	struct nw_config_entry* entry = NULL;
+ 
+ 	// Find an entry matching the key
+@@ -315,7 +315,7 @@ int nw_config_del(struct nw_config* config, const char* key) {
+ 	return 0;
+ }
+ 
+-const char* nw_config_get(struct nw_config* config, const char* key) {
++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
+@@ -326,7 +326,7 @@ const char* nw_config_get(struct nw_config* config, const char* key) {
+ 	return NULL;
+ }
+ 
+-int nw_config_set(struct nw_config* config, const char* key, const char* value) {
++int nw_config_set(nw_config* config, const char* key, const char* value) {
+ 	struct nw_config_entry* entry = NULL;
+ 
+ 	// Delete the entry if val is NULL
+@@ -347,7 +347,7 @@ int nw_config_set(struct nw_config* config, const char* key, const char* value)
+ 	return nw_string_set(entry->value, value);
+ }
+ 
+-int nw_config_get_int(struct nw_config* config, const char* key, const int __default) {
++int nw_config_get_int(nw_config* config, const char* key, const int __default) {
+ 	const char* value = nw_config_get(config, key);
+ 
+ 	// Return zero if not set
+@@ -357,7 +357,7 @@ int nw_config_get_int(struct nw_config* config, const char* key, const int __def
+ 	return strtoul(value, NULL, 10);
+ }
+ 
+-int nw_config_set_int(struct nw_config* config, const char* key, const int value) {
++int nw_config_set_int(nw_config* config, const char* key, const int value) {
+ 	char __value[1024];
+ 	int r;
+ 
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index b8d8555..041a10e 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -26,26 +26,26 @@
+ #define NETWORK_CONFIG_KEY_MAX_LENGTH		128
+ #define NETWORK_CONFIG_VALUE_MAX_LENGTH		2048
+ 
+-struct nw_config;
++typedef struct nw_config nw_config;
+ 
+-int nw_config_create(struct nw_config** config, const char* path);
++int nw_config_create(nw_config** config, const char* path);
+ 
+-struct nw_config* nw_config_ref(struct nw_config* config);
+-struct nw_config* nw_config_unref(struct nw_config* config);
++nw_config* nw_config_ref(nw_config* config);
++nw_config* nw_config_unref(nw_config* config);
+ 
+-const char* nw_config_path(struct nw_config* config);
++const char* nw_config_path(nw_config* config);
+ 
+-int nw_config_flush(struct nw_config* config);
++int nw_config_flush(nw_config* config);
+ 
+-int nw_config_read(struct nw_config* config);
+-int nw_config_write(struct nw_config* config);
++int nw_config_read(nw_config* config);
++int nw_config_write(nw_config* config);
+ 
+-int nw_config_del(struct nw_config* config, const char* key);
++int nw_config_del(nw_config* config, const char* key);
+ 
+-const char* nw_config_get(struct nw_config* config, const char* key);
+-int nw_config_set(struct nw_config* config, const char* key, const char* value);
++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(struct nw_config* config, const char* key, const int __default);
+-int nw_config_set_int(struct nw_config* config, const char* key, const int 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);
+ 
+ #endif /* NETWORKD_CONFIG_H */
+diff --git a/src/networkd/daemon-bus.c b/src/networkd/daemon-bus.c
+index ca0754f..93a0411 100644
+--- a/src/networkd/daemon-bus.c
++++ b/src/networkd/daemon-bus.c
+@@ -26,7 +26,7 @@
+ #include "zone-bus.h"
+ 
+ static int nw_daemon_bus_reload(sd_bus_message* m, void* data, sd_bus_error* error) {
+-	struct nw_daemon* daemon = (struct nw_daemon*)data;
++	nw_daemon* daemon = (nw_daemon*)data;
+ 
+ 	// Reload the daemon
+ 	nw_daemon_reload(daemon);
+@@ -42,7 +42,7 @@ static const sd_bus_vtable daemon_vtable[] = {
+ 	SD_BUS_VTABLE_END,
+ };
+ 
+-const struct nw_bus_implementation daemon_bus_impl = {
++const nw_bus_implementation daemon_bus_impl = {
+ 	.path = "/org/ipfire/network1",
+ 	.interface = "org.ipfire.network1",
+ 	.vtables = BUS_VTABLES(daemon_vtable),
+diff --git a/src/networkd/daemon-bus.h b/src/networkd/daemon-bus.h
+index 787e92a..c314963 100644
+--- a/src/networkd/daemon-bus.h
++++ b/src/networkd/daemon-bus.h
+@@ -23,6 +23,6 @@
+ 
+ #include "bus.h"
+ 
+-extern const struct nw_bus_implementation daemon_bus_impl;
++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
+index c4acff5..9b9bf73 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -44,7 +44,7 @@
+ struct nw_daemon {
+ 	int nrefs;
+ 
+-	struct nw_config* config;
++	nw_config* config;
+ 
+ 	// Event Loop
+ 	sd_event* loop;
+@@ -59,13 +59,13 @@ struct nw_daemon {
+ 	sd_device_monitor* devmon;
+ 
+ 	// Links
+-	struct nw_links* links;
++	nw_links* links;
+ 
+ 	// Zones
+-	struct nw_zones* zones;
++	nw_zones* zones;
+ 
+ 	// Ports
+-	struct nw_ports* ports;
++	nw_ports* ports;
+ 
+ };
+ 
+@@ -78,7 +78,7 @@ static int __nw_daemon_terminate(sd_event_source* source, const struct signalfd_
+ 
+ static int __nw_daemon_reload(sd_event_source* source, const struct signalfd_siginfo* si,
+ 		void* data) {
+-	struct nw_daemon* daemon = (struct nw_daemon*)daemon;
++	nw_daemon* daemon = (nw_daemon*)daemon;
+ 
+ 	DEBUG("Received signal to reload...\n");
+ 
+@@ -88,7 +88,7 @@ static int __nw_daemon_reload(sd_event_source* source, const struct signalfd_sig
+ 	return 0;
+ }
+ 
+-static int nw_daemon_setup_loop(struct nw_daemon* daemon) {
++static int nw_daemon_setup_loop(nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Fetch a reference to the default event loop
+@@ -132,7 +132,7 @@ static int nw_daemon_setup_loop(struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
+-static int nw_daemon_load_config(struct nw_daemon* daemon) {
++static int nw_daemon_load_config(nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Read configuration file
+@@ -148,7 +148,7 @@ static int nw_daemon_load_config(struct nw_daemon* daemon) {
+ 	return r;
+ }
+ 
+-static int nw_start_device_monitor(struct nw_daemon* daemon) {
++static int nw_start_device_monitor(nw_daemon* daemon) {
+ 	int r;
+ 
+ 	const char* subsystems[] = {
+@@ -199,7 +199,7 @@ static int nw_start_device_monitor(struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
+-static int nw_daemon_connect_rtnl(struct nw_daemon* daemon, int fd) {
++static int nw_daemon_connect_rtnl(nw_daemon* daemon, int fd) {
+ 	int r;
+ 
+ 	// Connect to Netlink
+@@ -242,7 +242,7 @@ static int nw_daemon_connect_rtnl(struct nw_daemon* daemon, int fd) {
+ 	return 0;
+ }
+ 
+-static int nw_daemon_enumerate_links(struct nw_daemon* daemon) {
++static int nw_daemon_enumerate_links(nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Create a new links container
+@@ -253,7 +253,7 @@ static int nw_daemon_enumerate_links(struct nw_daemon* daemon) {
+ 	return nw_links_enumerate(daemon->links);
+ }
+ 
+-static int nw_daemon_enumerate_ports(struct nw_daemon* daemon) {
++static int nw_daemon_enumerate_ports(nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Create a new ports container
+@@ -264,7 +264,7 @@ static int nw_daemon_enumerate_ports(struct nw_daemon* daemon) {
+ 	return nw_ports_enumerate(daemon->ports);
+ }
+ 
+-static int nw_daemon_enumerate(struct nw_daemon* daemon) {
++static int nw_daemon_enumerate(nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Links
+@@ -280,7 +280,7 @@ static int nw_daemon_enumerate(struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
+-static int nw_daemon_setup(struct nw_daemon* daemon) {
++static int nw_daemon_setup(nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Read the configuration
+@@ -316,10 +316,10 @@ static int nw_daemon_setup(struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
+-int nw_daemon_create(struct nw_daemon** daemon) {
++int nw_daemon_create(nw_daemon** daemon) {
+ 	int r;
+ 
+-	struct nw_daemon* d = calloc(1, sizeof(*d));
++	nw_daemon* d = calloc(1, sizeof(*d));
+ 	if (!d)
+ 		return 1;
+ 
+@@ -342,7 +342,7 @@ ERROR:
+ 	return r;
+ }
+ 
+-static void nw_daemon_cleanup(struct nw_daemon* daemon) {
++static void nw_daemon_cleanup(nw_daemon* daemon) {
+ 	if (daemon->ports)
+ 		nw_ports_unref(daemon->ports);
+ 	if (daemon->zones)
+@@ -353,7 +353,7 @@ static void nw_daemon_cleanup(struct nw_daemon* daemon) {
+ 		nw_config_unref(daemon->config);
+ }
+ 
+-static void nw_daemon_free(struct nw_daemon* daemon) {
++static void nw_daemon_free(nw_daemon* daemon) {
+ 	// Cleanup common objects
+ 	nw_daemon_cleanup(daemon);
+ 
+@@ -365,13 +365,13 @@ static void nw_daemon_free(struct nw_daemon* daemon) {
+ 	free(daemon);
+ }
+ 
+-struct nw_daemon* nw_daemon_ref(struct nw_daemon* daemon) {
++nw_daemon* nw_daemon_ref(nw_daemon* daemon) {
+ 	daemon->nrefs++;
+ 
+ 	return daemon;
+ }
+ 
+-struct nw_daemon* nw_daemon_unref(struct nw_daemon* daemon) {
++nw_daemon* nw_daemon_unref(nw_daemon* daemon) {
+ 	if (--daemon->nrefs > 0)
+ 		return daemon;
+ 
+@@ -382,7 +382,7 @@ struct nw_daemon* nw_daemon_unref(struct nw_daemon* daemon) {
+ /*
+ 	This function contains the main loop of the daemon...
+ */
+-int nw_daemon_run(struct nw_daemon* daemon) {
++int nw_daemon_run(nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// We are now ready to process any requests
+@@ -412,7 +412,7 @@ ERROR:
+ 	return 1;
+ }
+ 
+-int nw_daemon_reload(struct nw_daemon* daemon) {
++int nw_daemon_reload(nw_daemon* daemon) {
+ 	DEBUG("Reloading daemon...\n");
+ 
+ 	// XXX TODO
+@@ -423,32 +423,32 @@ int nw_daemon_reload(struct nw_daemon* daemon) {
+ /*
+ 	Netlink
+ */
+-sd_netlink* nw_daemon_get_rtnl(struct nw_daemon* daemon) {
++sd_netlink* nw_daemon_get_rtnl(nw_daemon* daemon) {
+ 	return daemon->rtnl;
+ }
+ 
+ /*
+ 	Links
+ */
+-struct nw_links* nw_daemon_links(struct nw_daemon* daemon) {
++nw_links* nw_daemon_links(nw_daemon* daemon) {
+ 	return nw_links_ref(daemon->links);
+ }
+ 
+-void nw_daemon_drop_link(struct nw_daemon* daemon, struct nw_link* link) {
++void nw_daemon_drop_link(nw_daemon* daemon, nw_link* link) {
+ 	if (!daemon->links)
+ 		return;
+ 
+ 	nw_links_drop_link(daemon->links, link);
+ }
+ 
+-struct nw_link* nw_daemon_get_link_by_ifindex(struct nw_daemon* daemon, int ifindex) {
++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);
+ }
+ 
+-struct nw_link* nw_daemon_get_link_by_name(struct nw_daemon* daemon, const char* name) {
++nw_link* nw_daemon_get_link_by_name(nw_daemon* daemon, const char* name) {
+ 	if (!daemon->links)
+ 		return NULL;
+ 
+@@ -458,11 +458,11 @@ struct nw_link* nw_daemon_get_link_by_name(struct nw_daemon* daemon, const char*
+ /*
+ 	Ports
+ */
+-struct nw_ports* nw_daemon_ports(struct nw_daemon* daemon) {
++nw_ports* nw_daemon_ports(nw_daemon* daemon) {
+ 	return nw_ports_ref(daemon->ports);
+ }
+ 
+-struct nw_port* nw_daemon_get_port_by_name(struct nw_daemon* daemon, const char* name) {
++nw_port* nw_daemon_get_port_by_name(nw_daemon* daemon, const char* name) {
+ 	if (!daemon->ports)
+ 		return NULL;
+ 
+@@ -473,11 +473,11 @@ struct nw_port* nw_daemon_get_port_by_name(struct nw_daemon* daemon, const char*
+ 	Zones
+ */
+ 
+-struct nw_zones* nw_daemon_zones(struct nw_daemon* daemon) {
++nw_zones* nw_daemon_zones(nw_daemon* daemon) {
+ 	return nw_zones_ref(daemon->zones);
+ }
+ 
+-struct nw_zone* nw_daemon_get_zone_by_name(struct nw_daemon* daemon, const char* name) {
++nw_zone* nw_daemon_get_zone_by_name(nw_daemon* daemon, const char* name) {
+ 	if (!daemon->zones)
+ 		return NULL;
+ 
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index a40dd60..1694599 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -23,43 +23,47 @@
+ 
+ #include <systemd/sd-netlink.h>
+ 
+-struct nw_daemon;
++typedef struct nw_daemon nw_daemon;
+ 
+ #include "link.h"
++#include "links.h"
++#include "port.h"
++#include "ports.h"
+ #include "zone.h"
++#include "zones.h"
+ 
+-int nw_daemon_create(struct nw_daemon** daemon);
++int nw_daemon_create(nw_daemon** daemon);
+ 
+-struct nw_daemon* nw_daemon_ref(struct nw_daemon* daemon);
+-struct nw_daemon* nw_daemon_unref(struct nw_daemon* daemon);
++nw_daemon* nw_daemon_ref(nw_daemon* daemon);
++nw_daemon* nw_daemon_unref(nw_daemon* daemon);
+ 
+-int nw_daemon_run(struct nw_daemon* daemon);
++int nw_daemon_run(nw_daemon* daemon);
+ 
+-int nw_daemon_reload(struct nw_daemon* daemon);
++int nw_daemon_reload(nw_daemon* daemon);
+ 
+ /*
+ 	Netlink
+ */
+-sd_netlink* nw_daemon_get_rtnl(struct nw_daemon* daemon);
++sd_netlink* nw_daemon_get_rtnl(nw_daemon* daemon);
+ 
+ /*
+ 	Links
+ */
+-struct nw_links* nw_daemon_links(struct nw_daemon* daemon);
+-void nw_daemon_drop_link(struct nw_daemon* daemon, struct nw_link* link);
+-struct nw_link* nw_daemon_get_link_by_ifindex(struct nw_daemon* daemon, int ifindex);
+-struct nw_link* nw_daemon_get_link_by_name(struct nw_daemon* daemon, const char* name);
++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
+ */
+-struct nw_ports* nw_daemon_ports(struct nw_daemon* daemon);
+-struct nw_port* nw_daemon_get_port_by_name(struct nw_daemon* daemon, const char* name);
++nw_ports* nw_daemon_ports(nw_daemon* daemon);
++nw_port* nw_daemon_get_port_by_name(nw_daemon* daemon, const char* name);
+ 
+ /*
+ 	Zones
+ */
+-struct nw_zones* nw_daemon_zones(struct nw_daemon* daemon);
+-struct nw_zone* nw_daemon_get_zone_by_name(struct nw_daemon* daemon, const char* name);
++nw_zones* nw_daemon_zones(nw_daemon* daemon);
++nw_zone* nw_daemon_get_zone_by_name(nw_daemon* daemon, const char* name);
+ 
+ #endif /* NETWORKD_DAEMON_H */
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 62be893..2f95dc3 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -32,7 +32,7 @@
+ #include "string.h"
+ 
+ struct nw_link {
+-	struct nw_daemon* daemon;
++	nw_daemon* daemon;
+ 	int nrefs;
+ 
+ 	// Interface Index
+@@ -47,9 +47,9 @@ struct nw_link {
+ 	uint32_t max_mtu;
+ };
+ 
+-int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex) {
++int nw_link_create(nw_link** link, nw_daemon* daemon, int ifindex) {
+ 	// Allocate a new object
+-	struct nw_link* l = calloc(1, sizeof(*l));
++	nw_link* l = calloc(1, sizeof(*l));
+ 	if (!l)
+ 		return 1;
+ 
+@@ -69,20 +69,20 @@ int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex)
+ 	return 0;
+ }
+ 
+-static void nw_link_free(struct nw_link* link) {
++static void nw_link_free(nw_link* link) {
+ 	DEBUG("Freeing link (ifindex = %d)\n", link->ifindex);
+ 
+ 	if (link->daemon)
+ 		nw_daemon_unref(link->daemon);
+ }
+ 
+-struct nw_link* nw_link_ref(struct nw_link* link) {
++nw_link* nw_link_ref(nw_link* link) {
+ 	link->nrefs++;
+ 
+ 	return link;
+ }
+ 
+-struct nw_link* nw_link_unref(struct nw_link* link) {
++nw_link* nw_link_unref(nw_link* link) {
+ 	if (--link->nrefs > 0)
+ 		return link;
+ 
+@@ -90,11 +90,11 @@ struct nw_link* nw_link_unref(struct nw_link* link) {
+ 	return NULL;
+ }
+ 
+-int nw_link_ifindex(struct nw_link* link) {
++int nw_link_ifindex(nw_link* link) {
+ 	return link->ifindex;
+ }
+ 
+-static int nw_link_update_ifname(struct nw_link* link, sd_netlink_message* message) {
++static int nw_link_update_ifname(nw_link* link, sd_netlink_message* message) {
+ 	const char* ifname = NULL;
+ 	int r;
+ 
+@@ -120,7 +120,7 @@ static int nw_link_update_ifname(struct nw_link* link, sd_netlink_message* messa
+ 	return 0;
+ }
+ 
+-static int nw_link_update_mtu(struct nw_link* link, sd_netlink_message* message) {
++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;
+@@ -172,7 +172,7 @@ static int nw_link_update_mtu(struct nw_link* link, sd_netlink_message* message)
+ 	This function is called whenever anything changes, so that we can
+ 	update our internal link object.
+ */
+-static int nw_link_update(struct nw_link* link, sd_netlink_message* message) {
++static int nw_link_update(nw_link* link, sd_netlink_message* message) {
+ 	int r;
+ 
+ 	// Update the interface name
+@@ -189,14 +189,14 @@ static int nw_link_update(struct nw_link* link, sd_netlink_message* message) {
+ }
+ 
+ int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
+-	struct nw_links* links = NULL;
+-	struct nw_link* link = NULL;
++	nw_links* links = NULL;
++	nw_link* link = NULL;
+ 	const char* ifname = NULL;
+ 	int ifindex;
+ 	uint16_t type;
+ 	int r;
+ 
+-	struct nw_daemon* daemon = (struct nw_daemon*)data;
++	nw_daemon* daemon = (nw_daemon*)data;
+ 
+ 	// Fetch links
+ 	links = nw_daemon_links(daemon);
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index 72ddfa9..acb5e7b 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -21,17 +21,17 @@
+ #ifndef NETWORKD_LINK_H
+ #define NETWORKD_LINK_H
+ 
+-struct nw_link;
++typedef struct nw_link nw_link;
+ 
+ #include "daemon.h"
+ 
+-int nw_link_create(struct nw_link** link, struct nw_daemon* daemon, int ifindex);
++int nw_link_create(nw_link** link, nw_daemon* daemon, int ifindex);
+ 
+-struct nw_link* nw_link_ref(struct nw_link* link);
+-struct nw_link* nw_link_unref(struct nw_link* link);
++nw_link* nw_link_ref(nw_link* link);
++nw_link* nw_link_unref(nw_link* link);
+ 
+-int nw_link_ifindex(struct nw_link* link);
+-const char* nw_link_name(struct nw_link* link);
++int nw_link_ifindex(nw_link* link);
++const char* nw_link_name(nw_link* link);
+ 
+ int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data);
+ 
+diff --git a/src/networkd/links.c b/src/networkd/links.c
+index 7aa83a3..9f41034 100644
+--- a/src/networkd/links.c
++++ b/src/networkd/links.c
+@@ -29,14 +29,14 @@
+ #include "links.h"
+ 
+ struct nw_links_entry {
+-	struct nw_link* link;
++	nw_link* link;
+ 
+ 	// Link to the other entries
+ 	STAILQ_ENTRY(nw_links_entry) nodes;
+ };
+ 
+ struct nw_links {
+-	struct nw_daemon* daemon;
++	nw_daemon* daemon;
+ 	int nrefs;
+ 
+ 	// Link Entries
+@@ -46,8 +46,8 @@ struct nw_links {
+ 	unsigned int num;
+ };
+ 
+-int nw_links_create(struct nw_links** links, struct nw_daemon* daemon) {
+-	struct nw_links* l = calloc(1, sizeof(*l));
++int nw_links_create(nw_links** links, nw_daemon* daemon) {
++	nw_links* l = calloc(1, sizeof(*l));
+ 	if (!l)
+ 		return 1;
+ 
+@@ -66,7 +66,7 @@ int nw_links_create(struct nw_links** links, struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
+-static void nw_links_free(struct nw_links* links) {
++static void nw_links_free(nw_links* links) {
+ 	struct nw_links_entry* entry = NULL;
+ 
+ 	while (!STAILQ_EMPTY(&links->entries)) {
+@@ -86,13 +86,13 @@ static void nw_links_free(struct nw_links* links) {
+ 		nw_daemon_unref(links->daemon);
+ }
+ 
+-struct nw_links* nw_links_ref(struct nw_links* links) {
++nw_links* nw_links_ref(nw_links* links) {
+ 	links->nrefs++;
+ 
+ 	return links;
+ }
+ 
+-struct nw_links* nw_links_unref(struct nw_links* links) {
++nw_links* nw_links_unref(nw_links* links) {
+ 	if (--links->nrefs > 0)
+ 		return links;
+ 
+@@ -100,7 +100,7 @@ struct nw_links* nw_links_unref(struct nw_links* links) {
+ 	return NULL;
+ }
+ 
+-static struct nw_links_entry* nw_links_find_link(struct nw_links* links, const int ifindex) {
++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) {
+@@ -112,7 +112,7 @@ static struct nw_links_entry* nw_links_find_link(struct nw_links* links, const i
+ 	return NULL;
+ }
+ 
+-int nw_links_add_link(struct nw_links* links, struct nw_link* link) {
++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)
+@@ -130,7 +130,7 @@ int nw_links_add_link(struct nw_links* links, struct nw_link* link) {
+ 	return 0;
+ }
+ 
+-void nw_links_drop_link(struct nw_links* links, struct nw_link* link) {
++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));
+@@ -143,7 +143,7 @@ void nw_links_drop_link(struct nw_links* links, struct nw_link* link) {
+ 	links->num--;
+ }
+ 
+-int nw_links_enumerate(struct nw_links* links) {
++int nw_links_enumerate(nw_links* links) {
+ 	sd_netlink_message* req = NULL;
+ 	sd_netlink_message* res = NULL;
+ 	int r;
+@@ -184,7 +184,7 @@ ERROR:
+ 	return r;
+ }
+ 
+-struct nw_link* nw_links_get_by_ifindex(struct nw_links* links, int ifindex) {
++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);
+@@ -194,7 +194,7 @@ struct nw_link* nw_links_get_by_ifindex(struct nw_links* links, int ifindex) {
+ 	return nw_link_ref(entry->link);
+ }
+ 
+-struct nw_link* nw_links_get_by_name(struct nw_links* links, const char* name) {
++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) {
+diff --git a/src/networkd/links.h b/src/networkd/links.h
+index 2b5c787..21e6cfb 100644
+--- a/src/networkd/links.h
++++ b/src/networkd/links.h
+@@ -24,19 +24,19 @@
+ #include "daemon.h"
+ #include "link.h"
+ 
+-struct nw_links;
++typedef struct nw_links nw_links;
+ 
+-int nw_links_create(struct nw_links** links, struct nw_daemon* daemon);
++int nw_links_create(nw_links** links, nw_daemon* daemon);
+ 
+-struct nw_links* nw_links_ref(struct nw_links* links);
+-struct nw_links* nw_links_unref(struct nw_links* links);
++nw_links* nw_links_ref(nw_links* links);
++nw_links* nw_links_unref(nw_links* links);
+ 
+-int nw_links_add_link(struct nw_links* links, struct nw_link* link);
+-void nw_links_drop_link(struct nw_links* links, struct nw_link* link);
++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(struct nw_links* links);
++int nw_links_enumerate(nw_links* links);
+ 
+-struct nw_link* nw_links_get_by_ifindex(struct nw_links* links, int ifindex);
+-struct nw_link* nw_links_get_by_name(struct nw_links* links, const char* name);
++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/main.c b/src/networkd/main.c
+index 9a0fd74..d3afc2a 100644
+--- a/src/networkd/main.c
++++ b/src/networkd/main.c
+@@ -212,7 +212,7 @@ static int drop_privileges(const char* user) {
+ }
+ 
+ int main(int argc, char** argv) {
+-	struct nw_daemon* daemon = NULL;
++	nw_daemon* daemon = NULL;
+ 	int r;
+ 
+ 	// Drop privileges
+diff --git a/src/networkd/port-bus.c b/src/networkd/port-bus.c
+index 1a48655..996be92 100644
+--- a/src/networkd/port-bus.c
++++ b/src/networkd/port-bus.c
+@@ -36,10 +36,10 @@ static int nw_port_node_enumerator(sd_bus* bus, const char* path, void* data,
+ 	DEBUG("Enumerating ports...\n");
+ 
+ 	// Fetch a reference to the daemon
+-	struct nw_daemon* daemon = (struct nw_daemon*)data;
++	nw_daemon* daemon = (nw_daemon*)data;
+ 
+ 	// Fetch ports
+-	struct nw_ports* ports = nw_daemon_ports(daemon);
++	nw_ports* ports = nw_daemon_ports(daemon);
+ 
+ 	// Make bus paths for all ports
+ 	r = nw_ports_bus_paths(ports, nodes);
+@@ -58,7 +58,7 @@ static int nw_port_object_find(sd_bus* bus, const char* path, const char* interf
+ 	int r;
+ 
+ 	// Fetch a reference to the daemon
+-	struct nw_daemon* daemon = (struct nw_daemon*)data;
++	nw_daemon* daemon = (nw_daemon*)data;
+ 
+ 	// Decode the path of the requested object
+ 	r = sd_bus_path_decode(path, "/org/ipfire/network1/port", &name);
+@@ -66,7 +66,7 @@ static int nw_port_object_find(sd_bus* bus, const char* path, const char* interf
+ 		return 0;
+ 
+ 	// Find the port
+-	struct nw_port* port = nw_daemon_get_port_by_name(daemon, name);
++	nw_port* port = nw_daemon_get_port_by_name(daemon, name);
+ 	if (!port)
+ 		return 0;
+ 
+@@ -80,7 +80,7 @@ static int nw_port_object_find(sd_bus* bus, const char* path, const char* interf
+ 
+ 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) {
+-	struct nw_port* port = (struct nw_port*)data;
++	nw_port* port = (nw_port*)data;
+ 	int r;
+ 
+ 	// Fetch the address
+@@ -115,7 +115,7 @@ static const sd_bus_vtable port_vtable[] = {
+ 	SD_BUS_VTABLE_END
+ };
+ 
+-const struct nw_bus_implementation port_bus_impl = {
++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}),
+diff --git a/src/networkd/port-bus.h b/src/networkd/port-bus.h
+index 95e49a8..373c281 100644
+--- a/src/networkd/port-bus.h
++++ b/src/networkd/port-bus.h
+@@ -23,6 +23,6 @@
+ 
+ #include "bus.h"
+ 
+-extern const struct nw_bus_implementation port_bus_impl;
++extern const nw_bus_implementation port_bus_impl;
+ 
+ #endif /* NETWORKD_PORT_BUS_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 33e75d7..41b8b24 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -33,14 +33,14 @@
+ #include "port.h"
+ 
+ struct nw_port {
+-	struct nw_daemon* daemon;
++	nw_daemon* daemon;
+ 	int nrefs;
+ 
+ 	char name[IF_NAMESIZE];
+ 	nw_port_type_t type;
+ 
+ 	// Configuration
+-	struct nw_config *config;
++	nw_config *config;
+ 
+ 	// Common attributes
+ 	nw_address_t address;
+@@ -65,7 +65,7 @@ static nw_port_type_t nw_port_type_from_string(const char* s) {
+ 	return NW_PORT_UNKNOWN;
+ }
+ 
+-static void nw_port_free(struct nw_port* port) {
++static void nw_port_free(nw_port* port) {
+ 	if (port->config)
+ 		nw_config_unref(port->config);
+ 	if (port->daemon)
+@@ -74,7 +74,7 @@ static void nw_port_free(struct nw_port* port) {
+ 	free(port);
+ }
+ 
+-static int nw_port_setup_address(struct nw_port* port) {
++static int nw_port_setup_address(nw_port* port) {
+ 	int r;
+ 
+ 	// Read ADDRESS from configuration
+@@ -111,7 +111,7 @@ ERROR:
+ 	return 0;
+ }
+ 
+-static int nw_port_setup_common(struct nw_port* port) {
++static int nw_port_setup_common(nw_port* port) {
+ 	int r;
+ 
+ 	// Address
+@@ -122,7 +122,7 @@ static int nw_port_setup_common(struct nw_port* port) {
+ 	return 0;
+ }
+ 
+-static nw_port_type_t nw_port_setup_type(struct nw_port* port) {
++static nw_port_type_t nw_port_setup_type(nw_port* port) {
+ 	const char* type = nw_config_get(port->config, "TYPE");
+ 	if (!type)
+ 		return NW_PORT_UNKNOWN;
+@@ -130,7 +130,7 @@ static nw_port_type_t nw_port_setup_type(struct nw_port* port) {
+ 	return nw_port_type_from_string(type);
+ }
+ 
+-static int nw_port_setup(struct nw_port* port) {
++static int nw_port_setup(nw_port* port) {
+ 	char path[PATH_MAX];
+ 	int r;
+ 
+@@ -167,11 +167,11 @@ static int nw_port_setup(struct nw_port* port) {
+ 	return 0;
+ }
+ 
+-int nw_port_create(struct nw_port** port, struct nw_daemon* daemon, const char* name) {
++int nw_port_create(nw_port** port, nw_daemon* daemon, const char* name) {
+ 	int r;
+ 
+ 	// Allocate a new object
+-	struct nw_port* p = calloc(1, sizeof(*p));
++	nw_port* p = calloc(1, sizeof(*p));
+ 	if (!p)
+ 		return 1;
+ 
+@@ -199,13 +199,13 @@ ERROR:
+ 	return r;
+ }
+ 
+-struct nw_port* nw_port_ref(struct nw_port* port) {
++nw_port* nw_port_ref(nw_port* port) {
+ 	port->nrefs++;
+ 
+ 	return port;
+ }
+ 
+-struct nw_port* nw_port_unref(struct nw_port* port) {
++nw_port* nw_port_unref(nw_port* port) {
+ 	if (--port->nrefs > 0)
+ 		return port;
+ 
+@@ -213,11 +213,11 @@ struct nw_port* nw_port_unref(struct nw_port* port) {
+ 	return NULL;
+ }
+ 
+-const char* nw_port_name(struct nw_port* port) {
++const char* nw_port_name(nw_port* port) {
+ 	return port->name;
+ }
+ 
+-char* nw_port_bus_path(struct nw_port* port) {
++char* nw_port_bus_path(nw_port* port) {
+ 	char* p = NULL;
+ 	int r;
+ 
+@@ -229,10 +229,10 @@ char* nw_port_bus_path(struct nw_port* port) {
+ 	return p;
+ }
+ 
+-static struct nw_link* nw_port_get_link(struct nw_port* port) {
++static nw_link* nw_port_get_link(nw_port* port) {
+ 	return nw_daemon_get_link_by_name(port->daemon, port->name);
+ }
+ 
+-const nw_address_t* nw_port_get_address(struct nw_port* port) {
++const nw_address_t* nw_port_get_address(nw_port* port) {
+ 	return &port->address;
+ }
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 92d60b2..9881846 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -28,20 +28,20 @@ typedef enum nw_port_type {
+ 	NW_PORT_DUMMY,
+ } nw_port_type_t;
+ 
+-struct nw_port;
++typedef struct nw_port nw_port;
+ 
+ #include "address.h"
+ #include "daemon.h"
+ 
+-int nw_port_create(struct nw_port** port, struct nw_daemon* daemon, const char* name);
++int nw_port_create(nw_port** port, nw_daemon* daemon, const char* name);
+ 
+-struct nw_port* nw_port_ref(struct nw_port* port);
+-struct nw_port* nw_port_unref(struct nw_port* port);
++nw_port* nw_port_ref(nw_port* port);
++nw_port* nw_port_unref(nw_port* port);
+ 
+-const char* nw_port_name(struct nw_port* port);
++const char* nw_port_name(nw_port* port);
+ 
+-char* nw_port_bus_path(struct nw_port* port);
++char* nw_port_bus_path(nw_port* port);
+ 
+-const nw_address_t* nw_port_get_address(struct nw_port* port);
++const nw_address_t* nw_port_get_address(nw_port* port);
+ 
+ #endif /* NETWORKD_PORT_H */
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+index 9cec111..35ed048 100644
+--- a/src/networkd/ports.c
++++ b/src/networkd/ports.c
+@@ -30,14 +30,14 @@
+ #include "util.h"
+ 
+ struct nw_ports_entry {
+-	struct nw_port* port;
++	nw_port* port;
+ 
+ 	// Link to the other entries
+ 	STAILQ_ENTRY(nw_ports_entry) nodes;
+ };
+ 
+ struct nw_ports {
+-	struct nw_daemon* daemon;
++	nw_daemon* daemon;
+ 	int nrefs;
+ 
+ 	// Port Entries
+@@ -47,8 +47,8 @@ struct nw_ports {
+ 	unsigned int num;
+ };
+ 
+-int nw_ports_create(struct nw_ports** ports, struct nw_daemon* daemon) {
+-	struct nw_ports* p = calloc(1, sizeof(*p));
++int nw_ports_create(nw_ports** ports, nw_daemon* daemon) {
++	nw_ports* p = calloc(1, sizeof(*p));
+ 	if (!p)
+ 		return 1;
+ 
+@@ -67,7 +67,7 @@ int nw_ports_create(struct nw_ports** ports, struct nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
+-static void nw_ports_free(struct nw_ports* ports) {
++static void nw_ports_free(nw_ports* ports) {
+ 	struct nw_ports_entry* entry = NULL;
+ 
+ 	while (!STAILQ_EMPTY(&ports->entries)) {
+@@ -84,13 +84,13 @@ static void nw_ports_free(struct nw_ports* ports) {
+ 	}
+ }
+ 
+-struct nw_ports* nw_ports_ref(struct nw_ports* ports) {
++nw_ports* nw_ports_ref(nw_ports* ports) {
+ 	ports->nrefs++;
+ 
+ 	return ports;
+ }
+ 
+-struct nw_ports* nw_ports_unref(struct nw_ports* ports) {
++nw_ports* nw_ports_unref(nw_ports* ports) {
+ 	if (--ports->nrefs > 0)
+ 		return ports;
+ 
+@@ -98,7 +98,7 @@ struct nw_ports* nw_ports_unref(struct nw_ports* ports) {
+ 	return NULL;
+ }
+ 
+-static int nw_ports_add_port(struct nw_ports* ports, struct nw_port* port) {
++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)
+@@ -117,10 +117,10 @@ static int nw_ports_add_port(struct nw_ports* ports, struct nw_port* port) {
+ }
+ 
+ static int __nw_ports_enumerate(const char* path, const struct stat* s, void* data) {
+-	struct nw_port* port = NULL;
++	nw_port* port = NULL;
+ 	int r;
+ 
+-	struct nw_ports* ports = (struct nw_ports*)data;
++	nw_ports* ports = (nw_ports*)data;
+ 
+ 	// Skip anything that isn't a regular file
+ 	if (!S_ISREG(s->st_mode))
+@@ -154,11 +154,11 @@ ERROR:
+ 	return r;
+ }
+ 
+-int nw_ports_enumerate(struct nw_ports* ports) {
++int nw_ports_enumerate(nw_ports* ports) {
+ 	return nw_ftw(PORT_CONFIG_DIR, PORT_CONFIG_DIR "/*", __nw_ports_enumerate, ports);
+ }
+ 
+-struct nw_port* nw_ports_get_by_name(struct nw_ports* ports, const char* name) {
++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) {
+@@ -173,7 +173,7 @@ struct nw_port* nw_ports_get_by_name(struct nw_ports* ports, const char* name) {
+ 	return NULL;
+ }
+ 
+-int nw_ports_bus_paths(struct nw_ports* ports, char*** paths) {
++int nw_ports_bus_paths(nw_ports* ports, char*** paths) {
+ 	struct nw_ports_entry* entry = NULL;
+ 	char* path = NULL;
+ 
+diff --git a/src/networkd/ports.h b/src/networkd/ports.h
+index e807e25..f58c3a4 100644
+--- a/src/networkd/ports.h
++++ b/src/networkd/ports.h
+@@ -21,19 +21,19 @@
+ #ifndef NETWORKD_PORTS_H
+ #define NETWORKD_PORTS_H
+ 
+-struct nw_ports;
++typedef struct nw_ports nw_ports;
+ 
+ #include "daemon.h"
+ 
+-int nw_ports_create(struct nw_ports** ports, struct nw_daemon* daemon);
++int nw_ports_create(nw_ports** ports, nw_daemon* daemon);
+ 
+-struct nw_ports* nw_ports_ref(struct nw_ports* ports);
+-struct nw_ports* nw_ports_unref(struct nw_ports* ports);
++nw_ports* nw_ports_ref(nw_ports* ports);
++nw_ports* nw_ports_unref(nw_ports* ports);
+ 
+-int nw_ports_enumerate(struct nw_ports* ports);
++int nw_ports_enumerate(nw_ports* ports);
+ 
+-struct nw_port* nw_ports_get_by_name(struct nw_ports* ports, const char* name);
++struct nw_port* nw_ports_get_by_name(nw_ports* ports, const char* name);
+ 
+-int nw_ports_bus_paths(struct nw_ports* ports, char*** paths);
++int nw_ports_bus_paths(nw_ports* ports, char*** paths);
+ 
+ #endif /* NETWORKD_PORTS_H */
+diff --git a/src/networkd/zone-bus.c b/src/networkd/zone-bus.c
+index e9c2ecc..a06deb5 100644
+--- a/src/networkd/zone-bus.c
++++ b/src/networkd/zone-bus.c
+@@ -34,10 +34,10 @@ static int nw_zone_node_enumerator(sd_bus* bus, const char* path, void* data,
+ 	DEBUG("Enumerating zones...\n");
+ 
+ 	// Fetch a reference to the daemon
+-	struct nw_daemon* daemon = (struct nw_daemon*)data;
++	nw_daemon* daemon = (nw_daemon*)data;
+ 
+ 	// Fetch zones
+-	struct nw_zones* zones = nw_daemon_zones(daemon);
++	nw_zones* zones = nw_daemon_zones(daemon);
+ 
+ 	// Make bus paths for all zones
+ 	r = nw_zones_bus_paths(zones, nodes);
+@@ -56,7 +56,7 @@ static int nw_zone_object_find(sd_bus* bus, const char* path, const char* interf
+ 	int r;
+ 
+ 	// Fetch a reference to the daemon
+-	struct nw_daemon* daemon = (struct nw_daemon*)data;
++	nw_daemon* daemon = (nw_daemon*)data;
+ 
+ 	// Decode the path of the requested object
+ 	r = sd_bus_path_decode(path, "/org/ipfire/network1/zone", &name);
+@@ -64,7 +64,7 @@ static int nw_zone_object_find(sd_bus* bus, const char* path, const char* interf
+ 		return 0;
+ 
+ 	// Find the zone
+-	struct nw_zone* zone = nw_daemon_get_zone_by_name(daemon, name);
++	nw_zone* zone = nw_daemon_get_zone_by_name(daemon, name);
+ 	if (!zone)
+ 		return 0;
+ 
+@@ -81,7 +81,7 @@ static int nw_zone_object_find(sd_bus* bus, const char* path, const char* interf
+ */
+ 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) {
+-	struct nw_zone* zone = (struct nw_zone*)data;
++	nw_zone* zone = (nw_zone*)data;
+ 
+ 	return sd_bus_message_append(reply, "u", nw_zone_mtu(zone));
+ }
+@@ -91,7 +91,7 @@ static int nw_zone_bus_set_mtu(sd_bus* bus, const char* path, const char* interf
+ 	unsigned int mtu = 0;
+ 	int r;
+ 
+-	struct nw_zone* zone = (struct nw_zone*)data;
++	nw_zone* zone = (nw_zone*)data;
+ 
+ 	// Parse the value
+ 	r = sd_bus_message_read(value, "u", &mtu);
+@@ -114,7 +114,7 @@ static const sd_bus_vtable zone_vtable[] = {
+ 	SD_BUS_VTABLE_END
+ };
+ 
+-const struct nw_bus_implementation zone_bus_impl = {
++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}),
+diff --git a/src/networkd/zone-bus.h b/src/networkd/zone-bus.h
+index 0d5583e..db257f7 100644
+--- a/src/networkd/zone-bus.h
++++ b/src/networkd/zone-bus.h
+@@ -23,6 +23,6 @@
+ 
+ #include "bus.h"
+ 
+-extern const struct nw_bus_implementation zone_bus_impl;
++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
+index c64706f..b6fa65d 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -34,13 +34,13 @@ struct nw_zone {
+ 	char name[NETWORK_ZONE_NAME_MAX_LENGTH];
+ 
+ 	// Configuration
+-	struct nw_config *config;
++	nw_config *config;
+ };
+ 
+ #define nw_zone_path(zone, path, format, ...) \
+ 	__nw_zone_path(zone, path, sizeof(path), format, __VA_ARGS__)
+ 
+-static int __nw_zone_path(struct nw_zone* zone, char* p, const size_t length,
++static int __nw_zone_path(nw_zone* zone, char* p, const size_t length,
+ 		const char* format, ...) {
+ 	char prefix[NAME_MAX];
+ 	char suffix[NAME_MAX];
+@@ -63,14 +63,14 @@ static int __nw_zone_path(struct nw_zone* zone, char* p, const size_t length,
+ 	return __nw_path_join(p, length, prefix, suffix);
+ }
+ 
+-static void nw_zone_free(struct nw_zone* zone) {
++static void nw_zone_free(nw_zone* zone) {
+ 	if (zone->config)
+ 		nw_config_unref(zone->config);
+ 
+ 	free(zone);
+ }
+ 
+-static int nw_zone_setup(struct nw_zone* zone) {
++static int nw_zone_setup(nw_zone* zone) {
+ 	char path[PATH_MAX];
+ 	int r;
+ 
+@@ -87,11 +87,11 @@ static int nw_zone_setup(struct nw_zone* zone) {
+ 	return 0;
+ }
+ 
+-int nw_zone_create(struct nw_zone** zone, const char* name) {
++int nw_zone_create(nw_zone** zone, const char* name) {
+ 	int r;
+ 
+ 	// Allocate a new object
+-	struct nw_zone* z = calloc(1, sizeof(*z));
++	nw_zone* z = calloc(1, sizeof(*z));
+ 	if (!z)
+ 		return 1;
+ 
+@@ -116,13 +116,13 @@ ERROR:
+ 	return r;
+ }
+ 
+-struct nw_zone* nw_zone_ref(struct nw_zone* zone) {
++nw_zone* nw_zone_ref(nw_zone* zone) {
+ 	zone->nrefs++;
+ 
+ 	return zone;
+ }
+ 
+-struct nw_zone* nw_zone_unref(struct nw_zone* zone) {
++nw_zone* nw_zone_unref(nw_zone* zone) {
+ 	if (--zone->nrefs > 0)
+ 		return zone;
+ 
+@@ -130,11 +130,11 @@ struct nw_zone* nw_zone_unref(struct nw_zone* zone) {
+ 	return NULL;
+ }
+ 
+-const char* nw_zone_name(struct nw_zone* zone) {
++const char* nw_zone_name(nw_zone* zone) {
+ 	return zone->name;
+ }
+ 
+-char* nw_zone_bus_path(struct nw_zone* zone) {
++char* nw_zone_bus_path(nw_zone* zone) {
+ 	char* p = NULL;
+ 	int r;
+ 
+@@ -149,11 +149,11 @@ char* nw_zone_bus_path(struct nw_zone* zone) {
+ /*
+ 	MTU
+ */
+-unsigned int nw_zone_mtu(struct nw_zone* zone) {
++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(struct nw_zone* zone, unsigned int 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);
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 4a00412..1c8e6b6 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -24,21 +24,21 @@
+ #define NETWORK_ZONE_NAME_MAX_LENGTH		16
+ #define NETWORK_ZONE_DEFAULT_MTU			1500
+ 
+-struct nw_zone;
++typedef struct nw_zone nw_zone;
+ 
+-int nw_zone_create(struct nw_zone** zone, const char* name);
++int nw_zone_create(nw_zone** zone, const char* name);
+ 
+-struct nw_zone* nw_zone_ref(struct nw_zone* zone);
+-struct nw_zone* nw_zone_unref(struct nw_zone* zone);
++nw_zone* nw_zone_ref(nw_zone* zone);
++nw_zone* nw_zone_unref(nw_zone* zone);
+ 
+-const char* nw_zone_name(struct nw_zone* zone);
++const char* nw_zone_name(nw_zone* zone);
+ 
+-char* nw_zone_bus_path(struct nw_zone* zone);
++char* nw_zone_bus_path(nw_zone* zone);
+ 
+ /*
+ 	MTU
+ */
+-unsigned int nw_zone_mtu(struct nw_zone* zone);
+-int nw_zone_set_mtu(struct nw_zone* zone, unsigned int mtu);
++unsigned int nw_zone_mtu(nw_zone* zone);
++int nw_zone_set_mtu(nw_zone* zone, unsigned int mtu);
+ 
+ #endif /* NETWORKD_ZONE_H */
+diff --git a/src/networkd/zones.c b/src/networkd/zones.c
+index 4f739f5..779bbe1 100644
+--- a/src/networkd/zones.c
++++ b/src/networkd/zones.c
+@@ -28,7 +28,7 @@
+ #include "zones.h"
+ 
+ struct nw_zones_entry {
+-	struct nw_zone* zone;
++	nw_zone* zone;
+ 
+ 	// Link to the other entries
+ 	STAILQ_ENTRY(nw_zones_entry) nodes;
+@@ -44,8 +44,8 @@ struct nw_zones {
+ 	unsigned int num;
+ };
+ 
+-static int nw_zones_create(struct nw_zones** zones) {
+-	struct nw_zones* z = calloc(1, sizeof(*z));
++static int nw_zones_create(nw_zones** zones) {
++	nw_zones* z = calloc(1, sizeof(*z));
+ 	if (!z)
+ 		return 1;
+ 
+@@ -61,7 +61,7 @@ static int nw_zones_create(struct nw_zones** zones) {
+ 	return 0;
+ }
+ 
+-static void nw_zones_free(struct nw_zones* zones) {
++static void nw_zones_free(nw_zones* zones) {
+ 	struct nw_zones_entry* entry = NULL;
+ 
+ 	while (!STAILQ_EMPTY(&zones->entries)) {
+@@ -78,13 +78,13 @@ static void nw_zones_free(struct nw_zones* zones) {
+ 	}
+ }
+ 
+-struct nw_zones* nw_zones_ref(struct nw_zones* zones) {
++nw_zones* nw_zones_ref(nw_zones* zones) {
+ 	zones->nrefs++;
+ 
+ 	return zones;
+ }
+ 
+-struct nw_zones* nw_zones_unref(struct nw_zones* zones) {
++nw_zones* nw_zones_unref(nw_zones* zones) {
+ 	if (--zones->nrefs > 0)
+ 		return zones;
+ 
+@@ -92,7 +92,7 @@ struct nw_zones* nw_zones_unref(struct nw_zones* zones) {
+ 	return NULL;
+ }
+ 
+-static int nw_zones_add_zone(struct nw_zones* zones, struct nw_zone* zone) {
++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)
+@@ -124,12 +124,12 @@ static int nw_zones_load_filter(const struct dirent* path) {
+ 	return 1;
+ }
+ 
+-static int __nw_zones_load(struct nw_zones* zones) {
++static int __nw_zones_load(nw_zones* zones) {
+ 	struct dirent** paths = NULL;
+ 	int n;
+ 	int r = 0;
+ 
+-	struct nw_zone* zone = NULL;
++	nw_zone* zone = NULL;
+ 
+ 	// Scan the zones directory
+ 	n = scandir(CONFIG_DIR "/zones", &paths, nw_zones_load_filter, alphasort);
+@@ -173,7 +173,7 @@ ERROR:
+ 	return r;
+ }
+ 
+-int nw_zones_load(struct nw_zones** zones) {
++int nw_zones_load(nw_zones** zones) {
+ 	int r;
+ 
+ 	// Create a new zones object
+@@ -193,7 +193,7 @@ ERROR:
+ 	return r;
+ }
+ 
+-size_t nw_zones_num(struct nw_zones* zones) {
++size_t nw_zones_num(nw_zones* zones) {
+ 	struct nw_zones_entry* entry = NULL;
+ 	size_t length = 0;
+ 
+@@ -204,7 +204,7 @@ size_t nw_zones_num(struct nw_zones* zones) {
+ 	return length;
+ }
+ 
+-struct nw_zone* nw_zones_get_by_name(struct nw_zones* zones, const char* name) {
++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) {
+@@ -219,7 +219,7 @@ struct nw_zone* nw_zones_get_by_name(struct nw_zones* zones, const char* name) {
+ 	return NULL;
+ }
+ 
+-int nw_zones_bus_paths(struct nw_zones* zones, char*** paths) {
++int nw_zones_bus_paths(nw_zones* zones, char*** paths) {
+ 	struct nw_zones_entry* entry = NULL;
+ 	char* path = NULL;
+ 
+diff --git a/src/networkd/zones.h b/src/networkd/zones.h
+index 6e2686d..e41c74f 100644
+--- a/src/networkd/zones.h
++++ b/src/networkd/zones.h
+@@ -21,17 +21,17 @@
+ #ifndef NETWORKD_ZONES_H
+ #define NETWORKD_ZONES_H
+ 
+-struct nw_zones;
++typedef struct nw_zones nw_zones;
+ 
+-struct nw_zones* nw_zones_ref(struct nw_zones* zones);
+-struct nw_zones* nw_zones_unref(struct nw_zones* zones);
++nw_zones* nw_zones_ref(nw_zones* zones);
++nw_zones* nw_zones_unref(nw_zones* zones);
+ 
+-int nw_zones_load(struct nw_zones** zones);
++int nw_zones_load(nw_zones** zones);
+ 
+-size_t nw_zones_num(struct nw_zones* zones);
++size_t nw_zones_num(nw_zones* zones);
+ 
+-struct nw_zone* nw_zones_get_by_name(struct nw_zones* zones, const char* name);
++nw_zone* nw_zones_get_by_name(nw_zones* zones, const char* name);
+ 
+-int nw_zones_bus_paths(struct nw_zones* zones, char*** paths);
++int nw_zones_bus_paths(nw_zones* zones, char*** paths);
+ 
+ #endif /* NETWORKD_ZONES_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0206-networkd-Store-a-reference-to-the-daemon-in-zone.patch b/network/patches/0206-networkd-Store-a-reference-to-the-daemon-in-zone.patch
new file mode 100644
index 000000000..44827567e
--- /dev/null
+++ b/network/patches/0206-networkd-Store-a-reference-to-the-daemon-in-zone.patch
@@ -0,0 +1,180 @@
+From dd84704eadbf2f55f650b5879095bafad7ffec30 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 17:51:28 +0000
+Subject: [PATCH 206/304] networkd: Store a reference to the daemon in zone
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c |  2 +-
+ src/networkd/zone.c   |  9 ++++++++-
+ src/networkd/zone.h   |  4 +++-
+ src/networkd/zones.c  | 18 ++++++++++++++----
+ src/networkd/zones.h  |  4 +++-
+ 5 files changed, 29 insertions(+), 8 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 9b9bf73..8122f58 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -141,7 +141,7 @@ static int nw_daemon_load_config(nw_daemon* daemon) {
+ 		return r;
+ 
+ 	// Load zones
+-	r = nw_zones_load(&daemon->zones);
++	r = nw_zones_load(&daemon->zones, daemon);
+ 	if (r)
+ 		return r;
+ 
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index b6fa65d..30f22d6 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -24,11 +24,13 @@
+ #include <systemd/sd-bus.h>
+ 
+ #include "config.h"
++#include "daemon.h"
+ #include "logging.h"
+ #include "string.h"
+ #include "zone.h"
+ 
+ struct nw_zone {
++	nw_daemon* daemon;
+ 	int nrefs;
+ 
+ 	char name[NETWORK_ZONE_NAME_MAX_LENGTH];
+@@ -66,6 +68,8 @@ static int __nw_zone_path(nw_zone* zone, char* p, const size_t length,
+ static void nw_zone_free(nw_zone* zone) {
+ 	if (zone->config)
+ 		nw_config_unref(zone->config);
++	if (zone->daemon)
++		nw_daemon_unref(zone->daemon);
+ 
+ 	free(zone);
+ }
+@@ -87,7 +91,7 @@ static int nw_zone_setup(nw_zone* zone) {
+ 	return 0;
+ }
+ 
+-int nw_zone_create(nw_zone** zone, const char* name) {
++int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const char* name) {
+ 	int r;
+ 
+ 	// Allocate a new object
+@@ -95,6 +99,9 @@ int nw_zone_create(nw_zone** zone, const char* name) {
+ 	if (!z)
+ 		return 1;
+ 
++	// Store a reference to the daemon
++	z->daemon = nw_daemon_ref(daemon);
++
+ 	// Initialize reference counter
+ 	z->nrefs = 1;
+ 
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 1c8e6b6..9df30ec 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -26,7 +26,9 @@
+ 
+ typedef struct nw_zone nw_zone;
+ 
+-int nw_zone_create(nw_zone** zone, const char* name);
++#include "daemon.h"
++
++int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const char* name);
+ 
+ nw_zone* nw_zone_ref(nw_zone* zone);
+ nw_zone* nw_zone_unref(nw_zone* zone);
+diff --git a/src/networkd/zones.c b/src/networkd/zones.c
+index 779bbe1..fd43f1c 100644
+--- a/src/networkd/zones.c
++++ b/src/networkd/zones.c
+@@ -23,6 +23,7 @@
+ #include <string.h>
+ #include <sys/queue.h>
+ 
++#include "daemon.h"
+ #include "logging.h"
+ #include "zone.h"
+ #include "zones.h"
+@@ -35,6 +36,7 @@ struct nw_zones_entry {
+ };
+ 
+ struct nw_zones {
++	nw_daemon* daemon;
+ 	int nrefs;
+ 
+ 	// Zone Entries
+@@ -44,11 +46,14 @@ struct nw_zones {
+ 	unsigned int num;
+ };
+ 
+-static int nw_zones_create(nw_zones** zones) {
++static 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;
+ 
+@@ -76,6 +81,11 @@ static void nw_zones_free(nw_zones* zones) {
+ 		// Free the entry
+ 		free(entry);
+ 	}
++
++	if (zones->daemon)
++		nw_daemon_unref(zones->daemon);
++
++	free(zones);
+ }
+ 
+ nw_zones* nw_zones_ref(nw_zones* zones) {
+@@ -147,7 +157,7 @@ static int __nw_zones_load(nw_zones* zones) {
+ 		DEBUG("Loading zone '%s'...\n", name);
+ 
+ 		// Create a new zone object
+-		r = nw_zone_create(&zone, name);
++		r = nw_zone_create(&zone, zones->daemon, name);
+ 		if (r)
+ 			goto ERROR;
+ 
+@@ -173,11 +183,11 @@ ERROR:
+ 	return r;
+ }
+ 
+-int nw_zones_load(nw_zones** zones) {
++int nw_zones_load(nw_zones** zones, nw_daemon* daemon) {
+ 	int r;
+ 
+ 	// Create a new zones object
+-	r = nw_zones_create(zones);
++	r = nw_zones_create(zones, daemon);
+ 	if (r)
+ 		return r;
+ 
+diff --git a/src/networkd/zones.h b/src/networkd/zones.h
+index e41c74f..330e524 100644
+--- a/src/networkd/zones.h
++++ b/src/networkd/zones.h
+@@ -23,10 +23,12 @@
+ 
+ typedef struct nw_zones nw_zones;
+ 
++#include "daemon.h"
++
+ nw_zones* nw_zones_ref(nw_zones* zones);
+ nw_zones* nw_zones_unref(nw_zones* zones);
+ 
+-int nw_zones_load(nw_zones** zones);
++int nw_zones_load(nw_zones** zones, nw_daemon* daemon);
+ 
+ size_t nw_zones_num(nw_zones* zones);
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0207-networkd-Refactor-enumerating-zones.patch b/network/patches/0207-networkd-Refactor-enumerating-zones.patch
new file mode 100644
index 000000000..e1dec5603
--- /dev/null
+++ b/network/patches/0207-networkd-Refactor-enumerating-zones.patch
@@ -0,0 +1,231 @@
+From 7af642e14376b599183cd19091d4cab28ddc11e7 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 18:00:26 +0000
+Subject: [PATCH 207/304] networkd: Refactor enumerating zones
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 21 +++++++---
+ src/networkd/zone.h   |  2 +
+ src/networkd/zones.c  | 97 +++++++++++++------------------------------
+ src/networkd/zones.h  |  4 +-
+ 4 files changed, 50 insertions(+), 74 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 8122f58..6a6def3 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -140,11 +140,6 @@ static int nw_daemon_load_config(nw_daemon* daemon) {
+ 	if (r)
+ 		return r;
+ 
+-	// Load zones
+-	r = nw_zones_load(&daemon->zones, daemon);
+-	if (r)
+-		return r;
+-
+ 	return r;
+ }
+ 
+@@ -264,6 +259,17 @@ static int nw_daemon_enumerate_ports(nw_daemon* daemon) {
+ 	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;
+ 
+@@ -277,6 +283,11 @@ static int nw_daemon_enumerate(nw_daemon* daemon) {
+ 	if (r)
+ 		return r;
+ 
++	// Zones
++	r = nw_daemon_enumerate_zones(daemon);
++	if (r)
++		return r;
++
+ 	return 0;
+ }
+ 
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 9df30ec..2748e6d 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -21,6 +21,8 @@
+ #ifndef NETWORKD_ZONE_H
+ #define NETWORKD_ZONE_H
+ 
++#define ZONE_CONFIG_DIR			CONFIG_DIR "/zones"
++
+ #define NETWORK_ZONE_NAME_MAX_LENGTH		16
+ #define NETWORK_ZONE_DEFAULT_MTU			1500
+ 
+diff --git a/src/networkd/zones.c b/src/networkd/zones.c
+index fd43f1c..1b0ffdc 100644
+--- a/src/networkd/zones.c
++++ b/src/networkd/zones.c
+@@ -25,6 +25,8 @@
+ 
+ #include "daemon.h"
+ #include "logging.h"
++#include "string.h"
++#include "util.h"
+ #include "zone.h"
+ #include "zones.h"
+ 
+@@ -46,7 +48,7 @@ struct nw_zones {
+ 	unsigned int num;
+ };
+ 
+-static int nw_zones_create(nw_zones** zones, nw_daemon* daemon) {
++int nw_zones_create(nw_zones** zones, nw_daemon* daemon) {
+ 	nw_zones* z = calloc(1, sizeof(*z));
+ 	if (!z)
+ 		return 1;
+@@ -120,89 +122,48 @@ static int nw_zones_add_zone(nw_zones* zones, nw_zone* zone) {
+ 	return 0;
+ }
+ 
+-static int nw_zones_load_filter(const struct dirent* path) {
+-	const char* fn = path->d_name;
+-
+-	// Ignore everything starting with '.'
+-	if (*fn == '.')
+-		return 0;
+-
+-	// Ignore anything that isn't a directory
+-	if (path->d_type != DT_DIR)
+-		return 0;
+-
+-	return 1;
+-}
+-
+-static int __nw_zones_load(nw_zones* zones) {
+-	struct dirent** paths = NULL;
+-	int n;
+-	int r = 0;
+-
++static int __nw_zones_enumerate(const char* path, const struct stat* s, void* data) {
+ 	nw_zone* zone = NULL;
++	int r;
+ 
+-	// Scan the zones directory
+-	n = scandir(CONFIG_DIR "/zones", &paths, nw_zones_load_filter, alphasort);
+-	if (n < 0) {
+-		ERROR("Could not load zones: %m\n");
+-		return 1;
+-	}
+-
+-	DEBUG("Found %d zone(s)\n", n);
+-
+-	// Load all zones
+-	for (int i = 0; i < n; i++) {
+-		const char* name = paths[i]->d_name;
+-
+-		DEBUG("Loading zone '%s'...\n", name);
+-
+-		// Create a new zone object
+-		r = nw_zone_create(&zone, zones->daemon, name);
+-		if (r)
+-			goto ERROR;
+-
+-		// Store the zone
+-		r = nw_zones_add_zone(zones, zone);
+-		if (r) {
+-			nw_zone_unref(zone);
+-			goto ERROR;
+-		}
++	nw_zones* zones = (nw_zones*)data;
+ 
+-		nw_zone_unref(zone);
+-	}
++	// Skip anything that isn't a directory
++	if (!S_ISDIR(s->st_mode))
++		return 0;
+ 
+-ERROR:
+-	// Free paths
+-	if (paths) {
+-		for (int i = 0; i < n; i++) {
+-			free(paths[i]);
+-		}
+-		free(paths);
+-	}
++	// Find the basename of the file
++	const char* name = nw_path_basename(path);
+ 
+-	return r;
+-}
++	// Break on invalid paths
++	if (!name)
++		return 0;
+ 
+-int nw_zones_load(nw_zones** zones, nw_daemon* daemon) {
+-	int r;
++	// Skip any hidden files
++	if (*name == '.')
++		return 0;
+ 
+-	// Create a new zones object
+-	r = nw_zones_create(zones, daemon);
++	// Create a new zone
++	r = nw_zone_create(&zone, zones->daemon, name);
+ 	if (r)
+-		return r;
++		goto ERROR;
+ 
+-	// Load all zones
+-	r = __nw_zones_load(*zones);
++	// Add the zone to the list
++	r = nw_zones_add_zone(zones, zone);
+ 	if (r)
+ 		goto ERROR;
+ 
+-	return 0;
+-
+ ERROR:
+-	nw_zones_unref(*zones);
++	if (zone)
++		nw_zone_unref(zone);
++
+ 	return r;
+ }
+ 
++int nw_zones_enumerate(nw_zones* zones) {
++	return nw_ftw(ZONE_CONFIG_DIR, ZONE_CONFIG_DIR "/*", __nw_zones_enumerate, zones);
++}
++
+ size_t nw_zones_num(nw_zones* zones) {
+ 	struct nw_zones_entry* entry = NULL;
+ 	size_t length = 0;
+diff --git a/src/networkd/zones.h b/src/networkd/zones.h
+index 330e524..dbf7ccd 100644
+--- a/src/networkd/zones.h
++++ b/src/networkd/zones.h
+@@ -25,10 +25,12 @@ typedef struct nw_zones nw_zones;
+ 
+ #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_load(nw_zones** zones, nw_daemon* daemon);
++int nw_zones_enumerate(nw_zones* zones);
+ 
+ size_t nw_zones_num(nw_zones* zones);
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0208-networkd-Save-configuration-when-the-daemon-exits.patch b/network/patches/0208-networkd-Save-configuration-when-the-daemon-exits.patch
new file mode 100644
index 000000000..75c62ada2
--- /dev/null
+++ b/network/patches/0208-networkd-Save-configuration-when-the-daemon-exits.patch
@@ -0,0 +1,239 @@
+From 605e975f53ed433718df5d101496474870d6439c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 11 Feb 2023 18:29:32 +0000
+Subject: [PATCH 208/304] networkd: Save configuration when the daemon exits
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 31 +++++++++++++++++++++++++++++++
+ src/networkd/daemon.h |  2 ++
+ src/networkd/main.c   |  1 +
+ src/networkd/port.c   | 10 ++++++++++
+ src/networkd/port.h   |  2 ++
+ src/networkd/ports.c  | 13 +++++++++++++
+ src/networkd/ports.h  |  2 ++
+ src/networkd/zone.c   | 10 ++++++++++
+ src/networkd/zone.h   |  2 ++
+ src/networkd/zones.c  | 13 +++++++++++++
+ src/networkd/zones.h  |  2 ++
+ 11 files changed, 88 insertions(+)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 6a6def3..c67c759 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -409,6 +409,11 @@ int nw_daemon_run(nw_daemon* daemon) {
+ 	// 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);
+ 
+@@ -431,6 +436,32 @@ int nw_daemon_reload(nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
++/*
++	Saves the configuration to disk
++*/
++int nw_daemon_save(nw_daemon* daemon) {
++	int r;
++
++	DEBUG("Saving configuration...\n");
++
++	// Save settings
++	r = nw_config_write(daemon->config);
++	if (r)
++		return r;
++
++	// 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;
++}
++
+ /*
+ 	Netlink
+ */
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index 1694599..6f4c217 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -41,6 +41,8 @@ int nw_daemon_run(nw_daemon* daemon);
+ 
+ int nw_daemon_reload(nw_daemon* daemon);
+ 
++int nw_daemon_save(nw_daemon* daemon);
++
+ /*
+ 	Netlink
+ */
+diff --git a/src/networkd/main.c b/src/networkd/main.c
+index d3afc2a..c8b9a79 100644
+--- a/src/networkd/main.c
++++ b/src/networkd/main.c
+@@ -29,6 +29,7 @@
+ 
+ #include "daemon.h"
+ #include "logging.h"
++#include "port.h"
+ 
+ static int cap_acquire_setpcap(void) {
+ 	cap_flag_value_t value;
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 41b8b24..30bd98f 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -213,6 +213,16 @@ nw_port* nw_port_unref(nw_port* port) {
+ 	return NULL;
+ }
+ 
++int nw_port_save(nw_port* port) {
++	int r;
++
++	r = nw_config_write(port->config);
++	if (r)
++		return r;
++
++	return 0;
++}
++
+ const char* nw_port_name(nw_port* port) {
+ 	return port->name;
+ }
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 9881846..8861046 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -38,6 +38,8 @@ int nw_port_create(nw_port** port, nw_daemon* daemon, const char* name);
+ nw_port* nw_port_ref(nw_port* port);
+ nw_port* nw_port_unref(nw_port* port);
+ 
++int nw_port_save(nw_port* port);
++
+ const char* nw_port_name(nw_port* port);
+ 
+ char* nw_port_bus_path(nw_port* port);
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+index 35ed048..a87ca03 100644
+--- a/src/networkd/ports.c
++++ b/src/networkd/ports.c
+@@ -98,6 +98,19 @@ nw_ports* nw_ports_unref(nw_ports* 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));
+diff --git a/src/networkd/ports.h b/src/networkd/ports.h
+index f58c3a4..40c9ae1 100644
+--- a/src/networkd/ports.h
++++ b/src/networkd/ports.h
+@@ -30,6 +30,8 @@ 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);
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index 30f22d6..1e4abc3 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -137,6 +137,16 @@ nw_zone* nw_zone_unref(nw_zone* zone) {
+ 	return NULL;
+ }
+ 
++int nw_zone_save(nw_zone* zone) {
++	int r;
++
++	r = nw_config_write(zone->config);
++	if (r)
++		return r;
++
++	return 0;
++}
++
+ const char* nw_zone_name(nw_zone* zone) {
+ 	return zone->name;
+ }
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 2748e6d..088bb2f 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -35,6 +35,8 @@ int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const char* name);
+ nw_zone* nw_zone_ref(nw_zone* zone);
+ nw_zone* nw_zone_unref(nw_zone* zone);
+ 
++int nw_zone_save(nw_zone* zone);
++
+ const char* nw_zone_name(nw_zone* zone);
+ 
+ char* nw_zone_bus_path(nw_zone* zone);
+diff --git a/src/networkd/zones.c b/src/networkd/zones.c
+index 1b0ffdc..ea0d8de 100644
+--- a/src/networkd/zones.c
++++ b/src/networkd/zones.c
+@@ -104,6 +104,19 @@ nw_zones* nw_zones_unref(nw_zones* 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));
+diff --git a/src/networkd/zones.h b/src/networkd/zones.h
+index dbf7ccd..c887bd0 100644
+--- a/src/networkd/zones.h
++++ b/src/networkd/zones.h
+@@ -30,6 +30,8 @@ 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);
+-- 
+2.39.2
+
diff --git a/network/patches/0209-networkd-Store-any-flags.patch b/network/patches/0209-networkd-Store-any-flags.patch
new file mode 100644
index 000000000..33427641c
--- /dev/null
+++ b/network/patches/0209-networkd-Store-any-flags.patch
@@ -0,0 +1,69 @@
+From c65300b47197145ac899bd2e80597b4c6b44ca6d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 13 Feb 2023 15:05:41 +0000
+Subject: [PATCH 209/304] networkd: Store any flags
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 31 +++++++++++++++++++++++++++++++
+ 1 file changed, 31 insertions(+)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 2f95dc3..78ae106 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -45,6 +45,9 @@ struct nw_link {
+ 	uint32_t mtu;
+ 	uint32_t min_mtu;
+ 	uint32_t max_mtu;
++
++	// Flags
++	unsigned int flags;
+ };
+ 
+ int nw_link_create(nw_link** link, nw_daemon* daemon, int ifindex) {
+@@ -168,6 +171,29 @@ static int nw_link_update_mtu(nw_link* link, sd_netlink_message* message) {
+ 	return 0;
+ }
+ 
++static int nw_link_update_flags(nw_link* link, sd_netlink_message* message) {
++	unsigned int flags = 0;
++	int r;
++
++	// Fetch flags
++	r = sd_rtnl_message_link_get_flags(message, &flags);
++	if (r < 0) {
++		return DEBUG("Could not read link flags: %m\n");
++		return 1;
++	}
++
++	// End here if there have been no changes
++	if (link->flags == flags)
++		return 0;
++
++	// XXX We should log any changes here
++
++	// Store the new flags
++	link->flags = flags;
++
++	return 0;
++}
++
+ /*
+ 	This function is called whenever anything changes, so that we can
+ 	update our internal link object.
+@@ -185,6 +211,11 @@ static int nw_link_update(nw_link* link, sd_netlink_message* message) {
+ 	if (r)
+ 		return r;
+ 
++	// Update flags
++	r = nw_link_update_flags(link, message);
++	if (r)
++		return r;
++
+ 	return 0;
+ }
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0210-networkd-Store-operstate-too.patch b/network/patches/0210-networkd-Store-operstate-too.patch
new file mode 100644
index 000000000..2c0b950d2
--- /dev/null
+++ b/network/patches/0210-networkd-Store-operstate-too.patch
@@ -0,0 +1,59 @@
+From 69ef50c7403a5e09fcdf6ee4b53f94665920277a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 13 Feb 2023 15:10:01 +0000
+Subject: [PATCH 210/304] networkd: Store operstate, too
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 15 +++++++++++++--
+ 1 file changed, 13 insertions(+), 2 deletions(-)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 78ae106..6392a90 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -48,6 +48,7 @@ struct nw_link {
+ 
+ 	// Flags
+ 	unsigned int flags;
++	uint8_t operstate;
+ };
+ 
+ int nw_link_create(nw_link** link, nw_daemon* daemon, int ifindex) {
+@@ -173,6 +174,7 @@ static int nw_link_update_mtu(nw_link* link, sd_netlink_message* message) {
+ 
+ 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
+@@ -182,14 +184,23 @@ static int nw_link_update_flags(nw_link* link, sd_netlink_message* message) {
+ 		return 1;
+ 	}
+ 
++	// Fetch operstate
++	r = sd_netlink_message_read_u8(message, IFLA_OPERSTATE, &operstate);
++	if (r < 1) {
++		ERROR("Could not read operstate: %m\n");
++		return 1;
++	}
++
+ 	// End here if there have been no changes
+-	if (link->flags == flags)
++	if (link->flags == flags && link->operstate == operstate)
+ 		return 0;
+ 
+ 	// XXX We should log any changes here
+ 
+-	// Store the new flags
++	// Store the new flags & operstate
+ 	link->flags = flags;
++	link->operstate = operstate;
++
+ 
+ 	return 0;
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0211-networkd-Add-function-to-check-whether-a-link-has-a-.patch b/network/patches/0211-networkd-Add-function-to-check-whether-a-link-has-a-.patch
new file mode 100644
index 000000000..b5ec88638
--- /dev/null
+++ b/network/patches/0211-networkd-Add-function-to-check-whether-a-link-has-a-.patch
@@ -0,0 +1,96 @@
+From dfd49c2cc85fcee2303a96bc2186de0e21d8da4f Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 13 Feb 2023 15:14:01 +0000
+Subject: [PATCH 211/304] networkd: Add function to check whether a link has a
+ carrier
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 32 ++++++++++++++++++++++++++++++--
+ src/networkd/link.h |  2 ++
+ 2 files changed, 32 insertions(+), 2 deletions(-)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 6392a90..80478f4 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -18,7 +18,7 @@
+ #                                                                             #
+ #############################################################################*/
+ 
+-#include <net/if.h>
++#include <linux/if.h>
+ #include <stddef.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -39,7 +39,7 @@ struct nw_link {
+ 	int ifindex;
+ 
+ 	// Interface Name
+-	char ifname[IF_NAMESIZE];
++	char ifname[IFNAMSIZ];
+ 
+ 	// MTU
+ 	uint32_t mtu;
+@@ -98,6 +98,20 @@ int nw_link_ifindex(nw_link* link) {
+ 	return link->ifindex;
+ }
+ 
++// 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_update_ifname(nw_link* link, sd_netlink_message* message) {
+ 	const char* ifname = NULL;
+ 	int r;
+@@ -197,10 +211,24 @@ static int nw_link_update_flags(nw_link* link, sd_netlink_message* message) {
+ 
+ 	// 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;
+ }
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index acb5e7b..3d8d397 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -33,6 +33,8 @@ nw_link* nw_link_unref(nw_link* link);
+ int nw_link_ifindex(nw_link* link);
+ const char* nw_link_name(nw_link* link);
+ 
++int nw_link_has_carrier(nw_link* link);
++
+ int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data);
+ 
+ #endif /* NETWORKD_LINK_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0212-networkd-Add-methods-to-check-zones-ports-for-carrie.patch b/network/patches/0212-networkd-Add-methods-to-check-zones-ports-for-carrie.patch
new file mode 100644
index 000000000..e0058a804
--- /dev/null
+++ b/network/patches/0212-networkd-Add-methods-to-check-zones-ports-for-carrie.patch
@@ -0,0 +1,92 @@
+From 20375a083cbb965971b3b471b161e6475cef3c4b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 13 Feb 2023 15:34:40 +0000
+Subject: [PATCH 212/304] networkd: Add methods to check zones/ports for
+ carrier
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c | 13 +++++++++++++
+ src/networkd/port.h |  2 ++
+ src/networkd/zone.c | 19 +++++++++++++++++++
+ src/networkd/zone.h |  2 ++
+ 4 files changed, 36 insertions(+)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 30bd98f..c6c8781 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -246,3 +246,16 @@ static nw_link* nw_port_get_link(nw_port* port) {
+ const nw_address_t* nw_port_get_address(nw_port* port) {
+ 	return &port->address;
+ }
++
++int nw_port_has_carrier(nw_port* port) {
++	int has_carrier = 0;
++
++	// Fetch link
++	nw_link* link = nw_port_get_link(port);
++	if (link) {
++		has_carrier = nw_link_has_carrier(link);
++		nw_link_unref(link);
++	}
++
++	return has_carrier;
++}
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 8861046..33241ed 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -46,4 +46,6 @@ char* nw_port_bus_path(nw_port* port);
+ 
+ const nw_address_t* nw_port_get_address(nw_port* port);
+ 
++int nw_port_has_carrier(nw_port* port);
++
+ #endif /* NETWORKD_PORT_H */
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index 1e4abc3..8fe07c5 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -163,6 +163,25 @@ char* nw_zone_bus_path(nw_zone* zone) {
+ 	return p;
+ }
+ 
++static nw_link* nw_zone_get_link(nw_zone* zone) {
++	return nw_daemon_get_link_by_name(zone->daemon, zone->name);
++}
++
++// Carrier
++
++int nw_zone_has_carrier(nw_zone* zone) {
++	int has_carrier = 0;
++
++	// Fetch link
++	nw_link* link = nw_zone_get_link(zone);
++	if (link) {
++		has_carrier = nw_link_has_carrier(link);
++		nw_link_unref(link);
++	}
++
++	return has_carrier;
++}
++
+ /*
+ 	MTU
+ */
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 088bb2f..f5a2355 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -41,6 +41,8 @@ const char* nw_zone_name(nw_zone* zone);
+ 
+ char* nw_zone_bus_path(nw_zone* zone);
+ 
++int nw_zone_has_carrier(nw_zone* zone);
++
+ /*
+ 	MTU
+ */
+-- 
+2.39.2
+
diff --git a/network/patches/0213-networkd-Log-to-journald.patch b/network/patches/0213-networkd-Log-to-journald.patch
new file mode 100644
index 000000000..6f3257eac
--- /dev/null
+++ b/network/patches/0213-networkd-Log-to-journald.patch
@@ -0,0 +1,137 @@
+From 1a70a6864ca83ed91c9acfd6be5a64185ac34917 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 13 Feb 2023 16:00:43 +0000
+Subject: [PATCH 213/304] networkd: Log to journald
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am            |  1 +
+ src/networkd/link.c    |  2 +-
+ src/networkd/logging.c | 65 ++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/logging.h | 10 +++++--
+ 4 files changed, 74 insertions(+), 4 deletions(-)
+ create mode 100644 src/networkd/logging.c
+
+diff --git a/Makefile.am b/Makefile.am
+index 893f1b8..eded292 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -323,6 +323,7 @@ dist_networkd_SOURCES = \
+ 	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 \
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 80478f4..7f49606 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -194,7 +194,7 @@ static int nw_link_update_flags(nw_link* link, sd_netlink_message* message) {
+ 	// Fetch flags
+ 	r = sd_rtnl_message_link_get_flags(message, &flags);
+ 	if (r < 0) {
+-		return DEBUG("Could not read link flags: %m\n");
++		DEBUG("Could not read link flags: %m\n");
+ 		return 1;
+ 	}
+ 
+diff --git a/src/networkd/logging.c b/src/networkd/logging.c
+new file mode 100644
+index 0000000..c4809e8
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <errno.h>
++#include <stdio.h>
++#include <stdlib.h>
++
++#include <systemd/sd-journal.h>
++
++#include "logging.h"
++
++void nw_log(int priority, const char* file,
++		int line, const char* fn, const char* format, ...) {
++	char* buffer = NULL;
++	va_list args;
++	int r;
++
++	// Format log message
++	va_start(args, format);
++	r = vasprintf(&buffer, format, args);
++	va_end(args);
++	if (r < 0)
++		return;
++
++	// Send message to journald
++	r = sd_journal_send(
++		"MESSAGE=%s", buffer,
++		"PRIORITY=%d", priority,
++
++		// Syslog compat
++		"SYSLOG_IDENTIFIER=networkd",
++
++		// Debugging stuff
++		"ERRNO=%d", errno,
++		"CODE_FILE=%s", file,
++		"CODE_LINE=%d", line,
++		"CODE_FUNC=%s", fn,
++
++		NULL
++	);
++
++	// Fall back to standard output
++	if (r)
++		sd_journal_perror(buffer);
++
++	// Cleanup
++	free(buffer);
++}
+diff --git a/src/networkd/logging.h b/src/networkd/logging.h
+index 9d51f21..ea75ba0 100644
+--- a/src/networkd/logging.h
++++ b/src/networkd/logging.h
+@@ -21,12 +21,16 @@
+ #ifndef NETWORKD_LOGGING_H
+ #define NETWORKD_LOGGING_H
+ 
+-#include <stdio.h>
++#include <syslog.h>
++
++void nw_log(int priority, const char *file, int line, const char* fn,
++	const char *format, ...) __attribute__((format(printf, 5, 6)));
+ 
+ /*
+ 	This is just something simple which will work for now...
+ */
+-#define ERROR(...) fprintf(stderr, __VA_ARGS__)
+-#define DEBUG(...) printf(__VA_ARGS__)
++#define INFO(args...)  nw_log(LOG_INFO, __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 */
+-- 
+2.39.2
+
diff --git a/network/patches/0214-networkd-Install-in-usr-lib-network.patch b/network/patches/0214-networkd-Install-in-usr-lib-network.patch
new file mode 100644
index 000000000..3b524d04d
--- /dev/null
+++ b/network/patches/0214-networkd-Install-in-usr-lib-network.patch
@@ -0,0 +1,49 @@
+From ac0188ee9bffceedc94417e586bcbe21daa96c74 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Mar 2023 15:34:32 +0000
+Subject: [PATCH 214/304] networkd: Install in /usr/lib/network
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                      | 4 ++--
+ src/networkd/networkd.service.in | 2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/Makefile.am b/Makefile.am
+index eded292..707eabe 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -61,7 +61,7 @@ INSTALL_DIRS =
+ INSTALL_EXEC_HOOKS =
+ UNINSTALL_EXEC_HOOKS =
+ noinst_DATA =
+-sbin_PROGRAMS =
++network_PROGRAMS =
+ dist_dbuspolicy_DATA =
+ dist_dbussystembus_DATA =
+ dist_polkitpolicy_DATA =
+@@ -304,7 +304,7 @@ EXTRA_DIST += \
+ 
+ # ------------------------------------------------------------------------------
+ 
+-sbin_PROGRAMS += \
++network_PROGRAMS += \
+ 	networkd
+ 
+ dist_networkd_SOURCES = \
+diff --git a/src/networkd/networkd.service.in b/src/networkd/networkd.service.in
+index 4361023..7ee8fad 100644
+--- a/src/networkd/networkd.service.in
++++ b/src/networkd/networkd.service.in
+@@ -15,7 +15,7 @@ AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET
+ BusName=org.ipfire.network1
+ CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW
+ DeviceAllow=char-* rw
+-ExecStart=@sbindir@/networkd
++ExecStart=@networkdir@/networkd
+ FileDescriptorStoreMax=512
+ LockPersonality=yes
+ MemoryDenyWriteExecute=yes
+-- 
+2.39.2
+
diff --git a/network/patches/0215-networkctl-Create-some-scaffolding.patch b/network/patches/0215-networkctl-Create-some-scaffolding.patch
new file mode 100644
index 000000000..c054332c4
--- /dev/null
+++ b/network/patches/0215-networkctl-Create-some-scaffolding.patch
@@ -0,0 +1,94 @@
+From 840738f6baf2d3024df6ae0e9a887c5b1bef3a50 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Mar 2023 15:41:17 +0000
+Subject: [PATCH 215/304] networkctl: Create some scaffolding
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ .gitignore            |  1 +
+ Makefile.am           | 19 +++++++++++++++++++
+ src/networkctl/main.c | 23 +++++++++++++++++++++++
+ 3 files changed, 43 insertions(+)
+ create mode 100644 src/networkctl/main.c
+
+diff --git a/.gitignore b/.gitignore
+index 9194c93..c45db75 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -3,6 +3,7 @@
+ /config.*
+ /libtool
+ /missing
++/networkctl
+ /networkd
+ /src/functions/functions
+ /src/inetcalc
+diff --git a/Makefile.am b/Makefile.am
+index 707eabe..1640060 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -379,6 +379,24 @@ CLEANFILES += \
+ 
+ # ------------------------------------------------------------------------------
+ 
++bin_PROGRAMS += \
++	networkctl
++
++dist_networkctl_SOURCES = \
++	src/networkctl/main.c
++
++networkctl_CFLAGS = \
++	$(AM_CFLAGS) \
++	$(SYSTEMD_CFLAGS)
++
++networkctl_LDFLAGS = \
++	$(AM_LDFLAGS)
++
++networkctl_LDADD = \
++	$(SYSTEMD_LIBS)
++
++# ------------------------------------------------------------------------------
++
+ util_PROGRAMS = \
+ 	src/utils/network-phy-list-channels \
+ 	src/utils/network-phy-list-ciphers \
+@@ -622,6 +640,7 @@ substitutions = \
+ 	'|builddir=$(abs_builddir)|' \
+ 	'|prefix=$(prefix)|' \
+ 	'|exec_prefix=$(exec_prefix)|' \
++	'|bindir=$(bindir)|' \
+ 	'|sbindir=$(sbindir)|' \
+ 	'|networkdir=$(networkdir)|' \
+ 	'|helpersdir=$(helpersdir)|' \
+diff --git a/src/networkctl/main.c b/src/networkctl/main.c
+new file mode 100644
+index 0000000..14aafdd
+--- /dev/null
++++ b/src/networkctl/main.c
+@@ -0,0 +1,23 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++int main(int argc, char** argv) {
++	return 0;
++}
+-- 
+2.39.2
+
diff --git a/network/patches/0216-networkctl-Connect-to-the-system-bus.patch b/network/patches/0216-networkctl-Connect-to-the-system-bus.patch
new file mode 100644
index 000000000..2ba84a6d8
--- /dev/null
+++ b/network/patches/0216-networkctl-Connect-to-the-system-bus.patch
@@ -0,0 +1,45 @@
+From d9b54f58d6abedf04cfa893cae479b05533f470f Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Mar 2023 15:52:20 +0000
+Subject: [PATCH 216/304] networkctl: Connect to the system bus
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkctl/main.c | 22 +++++++++++++++++++++-
+ 1 file changed, 21 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkctl/main.c b/src/networkctl/main.c
+index 14aafdd..6ecaeb6 100644
+--- a/src/networkctl/main.c
++++ b/src/networkctl/main.c
+@@ -18,6 +18,26 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <stdio.h>
++
++#include <systemd/sd-bus.h>
++
+ int main(int argc, char** argv) {
+-	return 0;
++	sd_bus* bus = NULL;
++	int r;
++
++	// 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;
++	}
++
++	// XXX TODO Do all the work
++
++ERROR:
++	if (bus)
++		sd_bus_flush_close_unref(bus);
++
++	return r;
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0217-networkctl-Add-some-help-and-version-arguments.patch b/network/patches/0217-networkctl-Add-some-help-and-version-arguments.patch
new file mode 100644
index 000000000..9f9eaecf0
--- /dev/null
+++ b/network/patches/0217-networkctl-Add-some-help-and-version-arguments.patch
@@ -0,0 +1,94 @@
+From bc44ba7128b38d16455899acaa145daa2eb6aade Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Mar 2023 16:13:11 +0000
+Subject: [PATCH 217/304] networkctl: Add some help and version arguments
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkctl/main.c | 63 ++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 62 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkctl/main.c b/src/networkctl/main.c
+index 6ecaeb6..494fd7d 100644
+--- a/src/networkctl/main.c
++++ b/src/networkctl/main.c
+@@ -18,14 +18,75 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <errno.h>
++#include <getopt.h>
+ #include <stdio.h>
++#include <unistd.h>
+ 
+ #include <systemd/sd-bus.h>
+ 
+-int main(int argc, char** argv) {
++static int version(void) {
++	printf("networkctl %s\n", PACKAGE_VERSION);
++
++	return 0;
++}
++
++static int help(void) {
++	printf(
++		"%s [OPTIONS...] COMMAND\n\n"
++		"Options:\n"
++		"  -h --help          Show help\n"
++		"     --version       Show version\n",
++		program_invocation_short_name
++	);
++
++	return 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':
++				return help();
++
++			case ARG_VERSION:
++				return 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) {
+-- 
+2.39.2
+
diff --git a/network/patches/0218-networkctl-Implement-a-basic-command-dispatcher.patch b/network/patches/0218-networkctl-Implement-a-basic-command-dispatcher.patch
new file mode 100644
index 000000000..74e8eae38
--- /dev/null
+++ b/network/patches/0218-networkctl-Implement-a-basic-command-dispatcher.patch
@@ -0,0 +1,172 @@
+From f3bb976c4be0fe41959d7623f4ae3269c0e95e1d Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 1 Mar 2023 16:55:45 +0000
+Subject: [PATCH 218/304] networkctl: Implement a basic command dispatcher
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am              |  2 ++
+ src/networkctl/command.c | 58 ++++++++++++++++++++++++++++++++++++++++
+ src/networkctl/command.h | 34 +++++++++++++++++++++++
+ src/networkctl/main.c    | 19 ++++++++++++-
+ 4 files changed, 112 insertions(+), 1 deletion(-)
+ create mode 100644 src/networkctl/command.c
+ create mode 100644 src/networkctl/command.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 1640060..186af94 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -383,6 +383,8 @@ bin_PROGRAMS += \
+ 	networkctl
+ 
+ dist_networkctl_SOURCES = \
++	src/networkctl/command.c \
++	src/networkctl/command.h \
+ 	src/networkctl/main.c
+ 
+ networkctl_CFLAGS = \
+diff --git a/src/networkctl/command.c b/src/networkctl/command.c
+new file mode 100644
+index 0000000..7114efe
+--- /dev/null
++++ b/src/networkctl/command.c
+@@ -0,0 +1,58 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <errno.h>
++#include <getopt.h>
++#include <string.h>
++
++#include "command.h"
++
++static const struct command* command_find(const struct command* commands, const char* verb) {
++	for (const struct command* command = commands; command->verb; command++) {
++		if (strcmp(command->verb, verb) == 0)
++			return command;
++	}
++
++	return NULL;
++}
++
++int command_dispatch(sd_bus* bus, const struct command* commands, int argc, char* argv[]) {
++	const struct command* command = NULL;
++
++	argc -= optind;
++	argv += optind;
++	optind = 1;
++
++	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, argv);
++}
+diff --git a/src/networkctl/command.h b/src/networkctl/command.h
+new file mode 100644
+index 0000000..f8f295e
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKCTL_COMMAND_H
++#define NETWORKCTL_COMMAND_H
++
++#include <systemd/sd-bus.h>
++
++struct command {
++	const char* verb;
++	int flags;
++	int (*callback)(sd_bus* bus, int argc, char* argv[]);
++};
++
++int command_dispatch(sd_bus* bus, const struct command* commands, int argc, char* argv[]);
++
++#endif /* NETWORKCTL_COMMAND_H */
+diff --git a/src/networkctl/main.c b/src/networkctl/main.c
+index 494fd7d..0cbc3e6 100644
+--- a/src/networkctl/main.c
++++ b/src/networkctl/main.c
+@@ -25,6 +25,22 @@
+ 
+ #include <systemd/sd-bus.h>
+ 
++#include "command.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[] = {
++		{ "status", 0, networkctl_status },
++		{ NULL },
++	};
++
++	return command_dispatch(bus, commands, argc, argv);
++}
++
+ static int version(void) {
+ 	printf("networkctl %s\n", PACKAGE_VERSION);
+ 
+@@ -94,7 +110,8 @@ int main(int argc, char* argv[]) {
+ 		goto ERROR;
+ 	}
+ 
+-	// XXX TODO Do all the work
++	// Run a command
++	r = networkctl_main(bus, argc, argv);
+ 
+ ERROR:
+ 	if (bus)
+-- 
+2.39.2
+
diff --git a/network/patches/0219-networkd-Implement-ListZones-bus-command.patch b/network/patches/0219-networkd-Implement-ListZones-bus-command.patch
new file mode 100644
index 000000000..e6558d155
--- /dev/null
+++ b/network/patches/0219-networkd-Implement-ListZones-bus-command.patch
@@ -0,0 +1,169 @@
+From b4faae0abf9bd02b4aa02660a85d58497ad8b07b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 11:25:56 +0000
+Subject: [PATCH 219/304] networkd: Implement ListZones bus command
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon-bus.c | 55 +++++++++++++++++++++++++++++++++++++++
+ src/networkd/daemon.c     |  7 +++++
+ src/networkd/daemon.h     |  1 +
+ src/networkd/zones.c      | 13 +++++++++
+ src/networkd/zones.h      |  4 +++
+ 5 files changed, 80 insertions(+)
+
+diff --git a/src/networkd/daemon-bus.c b/src/networkd/daemon-bus.c
+index 93a0411..7620eed 100644
+--- a/src/networkd/daemon-bus.c
++++ b/src/networkd/daemon-bus.c
+@@ -18,12 +18,16 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <stdlib.h>
++
+ #include <systemd/sd-bus.h>
+ 
+ #include "bus.h"
+ #include "daemon.h"
++#include "logging.h"
+ #include "port-bus.h"
+ #include "zone-bus.h"
++#include "zones.h"
+ 
+ static int nw_daemon_bus_reload(sd_bus_message* m, void* data, sd_bus_error* error) {
+ 	nw_daemon* daemon = (nw_daemon*)data;
+@@ -35,8 +39,59 @@ static int nw_daemon_bus_reload(sd_bus_message* m, void* data, sd_bus_error* err
+ 	return sd_bus_reply_method_return(m, NULL);
+ }
+ 
++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);
++
++	r = sd_bus_message_append(reply, "(so)", name, 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("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,
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index c67c759..925b207 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -519,6 +519,13 @@ 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;
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index 6f4c217..ce9a660 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -66,6 +66,7 @@ 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/zones.c b/src/networkd/zones.c
+index ea0d8de..521da51 100644
+--- a/src/networkd/zones.c
++++ b/src/networkd/zones.c
+@@ -239,3 +239,16 @@ ERROR:
+ 
+ 	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;
++}
+diff --git a/src/networkd/zones.h b/src/networkd/zones.h
+index c887bd0..019955e 100644
+--- a/src/networkd/zones.h
++++ b/src/networkd/zones.h
+@@ -23,6 +23,8 @@
+ 
+ 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);
+@@ -40,4 +42,6 @@ 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);
++
+ #endif /* NETWORKD_ZONES_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0220-networkctl-Implement-zone-list-command.patch b/network/patches/0220-networkctl-Implement-zone-list-command.patch
new file mode 100644
index 000000000..05492bc75
--- /dev/null
+++ b/network/patches/0220-networkctl-Implement-zone-list-command.patch
@@ -0,0 +1,188 @@
+From 537fae0bc0758b2b93a80dff94918752ff01b813 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 11:26:43 +0000
+Subject: [PATCH 220/304] networkctl: Implement "zone list" command
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |  4 +-
+ src/networkctl/main.c |  2 +
+ src/networkctl/zone.c | 97 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkctl/zone.h | 26 ++++++++++++
+ 4 files changed, 128 insertions(+), 1 deletion(-)
+ create mode 100644 src/networkctl/zone.c
+ create mode 100644 src/networkctl/zone.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 186af94..80a7236 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -385,7 +385,9 @@ bin_PROGRAMS += \
+ dist_networkctl_SOURCES = \
+ 	src/networkctl/command.c \
+ 	src/networkctl/command.h \
+-	src/networkctl/main.c
++	src/networkctl/main.c \
++	src/networkctl/zone.c \
++	src/networkctl/zone.h
+ 
+ networkctl_CFLAGS = \
+ 	$(AM_CFLAGS) \
+diff --git a/src/networkctl/main.c b/src/networkctl/main.c
+index 0cbc3e6..a08256c 100644
+--- a/src/networkctl/main.c
++++ b/src/networkctl/main.c
+@@ -26,6 +26,7 @@
+ #include <systemd/sd-bus.h>
+ 
+ #include "command.h"
++#include "zone.h"
+ 
+ static int networkctl_status(sd_bus* bus, int argc, char* argv[]) {
+ 	printf("%s called\n", __FUNCTION__);
+@@ -35,6 +36,7 @@ static int networkctl_status(sd_bus* bus, int argc, char* argv[]) {
+ static int networkctl_main(sd_bus* bus, int argc, char* argv[]) {
+ 	static const struct command commands[] = {
+ 		{ "status", 0, networkctl_status },
++		{ "zone",   0, networkctl_zone },
+ 		{ NULL },
+ 	};
+ 
+diff --git a/src/networkctl/zone.c b/src/networkctl/zone.c
+new file mode 100644
+index 0000000..ee1d0a2
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <systemd/sd-bus.h>
++
++#include "command.h"
++#include "zone.h"
++
++typedef int (*networkctl_zone_walk_callback)
++	(sd_bus* bus, const char* path, const char* name, void* data);
++
++static int networkctl_zone_walk(sd_bus* bus,
++		networkctl_zone_walk_callback callback, void* data) {
++	sd_bus_message* reply = NULL;
++	sd_bus_error error = SD_BUS_ERROR_NULL;
++	int r;
++
++	// Call ListZones
++	r = sd_bus_call_method(bus, "org.ipfire.network1", "/org/ipfire/network1",
++		"org.ipfire.network1", "ListZones", &error, &reply, "");
++	if (r < 0) {
++		fprintf(stderr, "ListZones call failed: %m\n");
++		goto ERROR;
++	}
++
++	const char* name = NULL;
++	const char* path = NULL;
++
++	// Open the container
++	r = sd_bus_message_enter_container(reply, 'a', "(so)");
++	if (r < 0) {
++		fprintf(stderr, "Could not open container: %m\n");
++		goto ERROR;
++	}
++
++	// Iterate over all zones
++	for (;;) {
++		r = sd_bus_message_read(reply, "(so)", &name, &path);
++		if (r < 0)
++			goto ERROR;
++
++		// Break if we reached the end of the container
++		if (r == 0)
++			break;
++
++		// Call the callback
++		r = callback(bus, path, name, data);
++		if (r)
++			goto ERROR;
++	}
++
++	// Close the container
++	sd_bus_message_exit_container(reply);
++
++ERROR:
++	if (reply)
++		sd_bus_message_unref(reply);
++	sd_bus_error_free(&error);
++
++	return r;
++}
++
++static int __networkctl_zone_list(sd_bus* bus, const char* path, const char* name, void* data) {
++	printf("%s\n", name);
++
++	return 0;
++}
++
++static int networkctl_zone_list(sd_bus* bus, int argc, char* argv[]) {
++	return networkctl_zone_walk(bus, __networkctl_zone_list, NULL);
++}
++
++int networkctl_zone(sd_bus* bus, int argc, char* argv[]) {
++	static const struct command commands[] = {
++		{ "list", 0, networkctl_zone_list },
++		{ NULL },
++	};
++
++	return command_dispatch(bus, commands, argc, argv);
++}
+diff --git a/src/networkctl/zone.h b/src/networkctl/zone.h
+new file mode 100644
+index 0000000..5eddd98
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKCTL_ZONE_H
++#define NETWORKCTL_ZONE_H
++
++int networkctl_zone(sd_bus* bus, int argc, char* argv[]);
++
++#endif /* NETWORKCTL_ZONE_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0221-networkd-Fix-finding-links-by-name.patch b/network/patches/0221-networkd-Fix-finding-links-by-name.patch
new file mode 100644
index 000000000..bac9705bc
--- /dev/null
+++ b/network/patches/0221-networkd-Fix-finding-links-by-name.patch
@@ -0,0 +1,56 @@
+From 207abe4eabad65c2d1e120f7c0fc711d17ed017a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 11:57:19 +0000
+Subject: [PATCH 221/304] networkd: Fix finding links by name
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c  | 4 ++++
+ src/networkd/link.h  | 2 +-
+ src/networkd/links.c | 2 +-
+ 3 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 7f49606..0fb388b 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -98,6 +98,10 @@ int nw_link_ifindex(nw_link* link) {
+ 	return link->ifindex;
+ }
+ 
++const char* nw_link_ifname(nw_link* link) {
++	return link->ifname;
++}
++
+ // Carrier
+ 
+ int nw_link_has_carrier(nw_link* link) {
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index 3d8d397..779621b 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -31,7 +31,7 @@ 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_name(nw_link* link);
++const char* nw_link_ifname(nw_link* link);
+ 
+ int nw_link_has_carrier(nw_link* link);
+ 
+diff --git a/src/networkd/links.c b/src/networkd/links.c
+index 9f41034..3dd06d0 100644
+--- a/src/networkd/links.c
++++ b/src/networkd/links.c
+@@ -198,7 +198,7 @@ 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_name(entry->link);
++		const char* n = nw_link_ifname(entry->link);
+ 
+ 		if (strcmp(name, n) == 0)
+ 			return nw_link_ref(entry->link);
+-- 
+2.39.2
+
diff --git a/network/patches/0222-networkd-ports-Keep-a-permanent-reference-to-links.patch b/network/patches/0222-networkd-ports-Keep-a-permanent-reference-to-links.patch
new file mode 100644
index 000000000..cb4989442
--- /dev/null
+++ b/network/patches/0222-networkd-ports-Keep-a-permanent-reference-to-links.patch
@@ -0,0 +1,81 @@
+From 02801f0d507924a4efb8c462f6fc775ab5793ebc Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 11:57:48 +0000
+Subject: [PATCH 222/304] networkd: ports: Keep a permanent reference to links
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c | 30 ++++++++++++++++++++----------
+ 1 file changed, 20 insertions(+), 10 deletions(-)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index c6c8781..3c4f0b3 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -36,6 +36,9 @@ struct nw_port {
+ 	nw_daemon* daemon;
+ 	int nrefs;
+ 
++	// Link
++	nw_link* link;
++
+ 	char name[IF_NAMESIZE];
+ 	nw_port_type_t type;
+ 
+@@ -66,6 +69,8 @@ static nw_port_type_t nw_port_type_from_string(const char* s) {
+ }
+ 
+ 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)
+@@ -134,6 +139,14 @@ static int nw_port_setup(nw_port* port) {
+ 	char path[PATH_MAX];
+ 	int r;
+ 
++	// Find the link
++	port->link = nw_daemon_get_link_by_name(port->daemon, port->name);
++	if (port->link) {
++		DEBUG("%s: Found matching link %d\n", port->name, nw_link_ifindex(port->link));
++	} else {
++		DEBUG("%s: Could not find matching link\n", port->name);
++	}
++
+ 	// Compose the path to the main configuration file
+ 	r = nw_path_join(path, PORT_CONFIG_DIR, port->name);
+ 	if (r)
+@@ -240,7 +253,10 @@ char* nw_port_bus_path(nw_port* port) {
+ }
+ 
+ static nw_link* nw_port_get_link(nw_port* port) {
+-	return 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) {
+@@ -248,14 +264,8 @@ const nw_address_t* nw_port_get_address(nw_port* port) {
+ }
+ 
+ int nw_port_has_carrier(nw_port* port) {
+-	int has_carrier = 0;
+-
+-	// Fetch link
+-	nw_link* link = nw_port_get_link(port);
+-	if (link) {
+-		has_carrier = nw_link_has_carrier(link);
+-		nw_link_unref(link);
+-	}
++	if (!port->link)
++		return 0;
+ 
+-	return has_carrier;
++	return nw_link_has_carrier(port->link);
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0223-networkd-zones-Keep-a-permanent-reference-to-links.patch b/network/patches/0223-networkd-zones-Keep-a-permanent-reference-to-links.patch
new file mode 100644
index 000000000..bafe16a5b
--- /dev/null
+++ b/network/patches/0223-networkd-zones-Keep-a-permanent-reference-to-links.patch
@@ -0,0 +1,89 @@
+From 4e989bd539cc0aea61345156a6401e7282357d0c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 12:02:40 +0000
+Subject: [PATCH 223/304] networkd: zones: Keep a permanent reference to links
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/zone.c | 31 +++++++++++++++++++++----------
+ 1 file changed, 21 insertions(+), 10 deletions(-)
+
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index 8fe07c5..4fda1e5 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -25,6 +25,7 @@
+ 
+ #include "config.h"
+ #include "daemon.h"
++#include "link.h"
+ #include "logging.h"
+ #include "string.h"
+ #include "zone.h"
+@@ -33,6 +34,9 @@ struct nw_zone {
+ 	nw_daemon* daemon;
+ 	int nrefs;
+ 
++	// Link
++	nw_link* link;
++
+ 	char name[NETWORK_ZONE_NAME_MAX_LENGTH];
+ 
+ 	// Configuration
+@@ -66,6 +70,8 @@ static int __nw_zone_path(nw_zone* zone, char* p, const size_t length,
+ }
+ 
+ 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)
+@@ -78,6 +84,14 @@ static int nw_zone_setup(nw_zone* zone) {
+ 	char path[PATH_MAX];
+ 	int r;
+ 
++	// Find the link
++	zone->link = nw_daemon_get_link_by_name(zone->daemon, zone->name);
++	if (zone->link) {
++		DEBUG("%s: Found matching link %d\n", zone->name, nw_link_ifindex(zone->link));
++	} else {
++		DEBUG("%s: Could not find matching link\n", zone->name);
++	}
++
+ 	// Compose the path to the main configuration file
+ 	r = nw_zone_path(zone, path, "%s", "settings");
+ 	if (r)
+@@ -164,22 +178,19 @@ char* nw_zone_bus_path(nw_zone* zone) {
+ }
+ 
+ static nw_link* nw_zone_get_link(nw_zone* zone) {
+-	return nw_daemon_get_link_by_name(zone->daemon, zone->name);
++	if (!zone->link)
++		return NULL;
++
++	return nw_link_ref(zone->link);
+ }
+ 
+ // Carrier
+ 
+ int nw_zone_has_carrier(nw_zone* zone) {
+-	int has_carrier = 0;
+-
+-	// Fetch link
+-	nw_link* link = nw_zone_get_link(zone);
+-	if (link) {
+-		has_carrier = nw_link_has_carrier(link);
+-		nw_link_unref(link);
+-	}
++	if (!zone->link)
++		return 0;
+ 
+-	return has_carrier;
++	return nw_link_has_carrier(zone->link);
+ }
+ 
+ /*
+-- 
+2.39.2
+
diff --git a/network/patches/0224-networkd-config-Add-functions-to-handle-boolean-valu.patch b/network/patches/0224-networkd-config-Add-functions-to-handle-boolean-valu.patch
new file mode 100644
index 000000000..4b2ad1b9b
--- /dev/null
+++ b/network/patches/0224-networkd-config-Add-functions-to-handle-boolean-valu.patch
@@ -0,0 +1,63 @@
+From 8b0b5c6d9e99c09341fe90b1278763fd3f70b53a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 13:04:17 +0000
+Subject: [PATCH 224/304] networkd: config: Add functions to handle boolean
+ values
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 28 ++++++++++++++++++++++++++++
+ src/networkd/config.h |  3 +++
+ 2 files changed, 31 insertions(+)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index b3d5284..d879ace 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -368,3 +368,31 @@ int nw_config_set_int(nw_config* config, const char* key, const int value) {
+ 
+ 	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 (strcmp(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");
++}
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index 041a10e..0b25f75 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -48,4 +48,7 @@ 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);
++
+ #endif /* NETWORKD_CONFIG_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0225-networkd-Try-to-reconfigure-all-ports-and-zones-on-s.patch b/network/patches/0225-networkd-Try-to-reconfigure-all-ports-and-zones-on-s.patch
new file mode 100644
index 000000000..8ea8c778b
--- /dev/null
+++ b/network/patches/0225-networkd-Try-to-reconfigure-all-ports-and-zones-on-s.patch
@@ -0,0 +1,210 @@
+From b9769b09ea2943e4b212806c11f31f14f627b08f Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 13:06:25 +0000
+Subject: [PATCH 225/304] networkd: Try to reconfigure all ports and zones on
+ startup
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 36 ++++++++++++++++++++++++++++++++++++
+ src/networkd/port.c   |  4 ++++
+ src/networkd/port.h   |  2 ++
+ src/networkd/ports.c  | 21 +++++++++++++++++++++
+ src/networkd/ports.h  |  6 ++++++
+ src/networkd/zone.c   |  4 ++++
+ src/networkd/zone.h   |  2 ++
+ src/networkd/zones.c  |  8 ++++++++
+ src/networkd/zones.h  |  2 ++
+ 9 files changed, 85 insertions(+)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 925b207..c6dad91 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -291,6 +291,37 @@ static int nw_daemon_enumerate(nw_daemon* daemon) {
+ 	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_setup(nw_daemon* daemon) {
+ 	int r;
+ 
+@@ -324,6 +355,11 @@ static int nw_daemon_setup(nw_daemon* daemon) {
+ 	if (r)
+ 		return r;
+ 
++	// (Re-)configure everything
++	r = nw_daemon_reconfigure(daemon);
++	if (r)
++		return r;
++
+ 	return 0;
+ }
+ 
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 3c4f0b3..ec40830 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -263,6 +263,10 @@ const nw_address_t* nw_port_get_address(nw_port* port) {
+ 	return &port->address;
+ }
+ 
++int nw_port_reconfigure(nw_port* port) {
++	return 0; // XXX TODO
++}
++
+ int nw_port_has_carrier(nw_port* port) {
+ 	if (!port->link)
+ 		return 0;
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 33241ed..17c8c3c 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -46,6 +46,8 @@ char* nw_port_bus_path(nw_port* port);
+ 
+ const nw_address_t* nw_port_get_address(nw_port* port);
+ 
++int nw_port_reconfigure(nw_port* port);
++
+ int nw_port_has_carrier(nw_port* port);
+ 
+ #endif /* NETWORKD_PORT_H */
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+index a87ca03..87135d8 100644
+--- a/src/networkd/ports.c
++++ b/src/networkd/ports.c
+@@ -222,3 +222,24 @@ ERROR:
+ 
+ 	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
+index 40c9ae1..68ae532 100644
+--- a/src/networkd/ports.h
++++ b/src/networkd/ports.h
+@@ -23,6 +23,8 @@
+ 
+ typedef struct nw_ports nw_ports;
+ 
++typedef int (*nw_ports_walk_callback)(nw_daemon* daemon, nw_port* port, void* data);
++
+ #include "daemon.h"
+ 
+ int nw_ports_create(nw_ports** ports, nw_daemon* daemon);
+@@ -38,4 +40,8 @@ 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/zone.c b/src/networkd/zone.c
+index 4fda1e5..9daa0d3 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -184,6 +184,10 @@ static nw_link* nw_zone_get_link(nw_zone* zone) {
+ 	return nw_link_ref(zone->link);
+ }
+ 
++int nw_zone_reconfigure(nw_zone* zone) {
++	return 0; // XXX TODO
++}
++
+ // Carrier
+ 
+ int nw_zone_has_carrier(nw_zone* zone) {
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index f5a2355..591e467 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -41,6 +41,8 @@ const char* nw_zone_name(nw_zone* zone);
+ 
+ char* nw_zone_bus_path(nw_zone* zone);
+ 
++int nw_zone_reconfigure(nw_zone* zone);
++
+ int nw_zone_has_carrier(nw_zone* zone);
+ 
+ /*
+diff --git a/src/networkd/zones.c b/src/networkd/zones.c
+index 521da51..84a6673 100644
+--- a/src/networkd/zones.c
++++ b/src/networkd/zones.c
+@@ -252,3 +252,11 @@ int nw_zones_walk(nw_zones* zones, nw_zones_walk_callback callback, void* data)
+ 
+ 	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
+index 019955e..ad39fd2 100644
+--- a/src/networkd/zones.h
++++ b/src/networkd/zones.h
+@@ -44,4 +44,6 @@ 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 */
+-- 
+2.39.2
+
diff --git a/network/patches/0226-networkd-Implement-deleting-links.patch b/network/patches/0226-networkd-Implement-deleting-links.patch
new file mode 100644
index 000000000..ba7e4160e
--- /dev/null
+++ b/network/patches/0226-networkd-Implement-deleting-links.patch
@@ -0,0 +1,147 @@
+From 371b836adc35d69cdd7a4a71144ec5e0f7ef42e1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 13:33:13 +0000
+Subject: [PATCH 226/304] networkd: Implement deleting links
+
+This is a little bit rough but generally does work.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 66 +++++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/link.h |  2 ++
+ src/networkd/port.c | 19 ++++++++++++-
+ 3 files changed, 86 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 0fb388b..949d0d8 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -41,6 +41,11 @@ struct nw_link {
+ 	// Interface Name
+ 	char ifname[IFNAMSIZ];
+ 
++	enum nw_link_state {
++		NW_LINK_UNKNOWN = 0,
++		NW_LINK_DESTROYED,
++	} state;
++
+ 	// MTU
+ 	uint32_t mtu;
+ 	uint32_t min_mtu;
+@@ -94,6 +99,16 @@ nw_link* nw_link_unref(nw_link* 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;
+ }
+@@ -370,3 +385,54 @@ ERROR:
+ 
+ 	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;
++}
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index 779621b..58a825a 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -37,4 +37,6 @@ 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);
++
+ #endif /* NETWORKD_LINK_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index ec40830..7d02305 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -263,8 +263,25 @@ 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");
++}
++
+ int nw_port_reconfigure(nw_port* port) {
+-	return 0; // XXX TODO
++	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;
++	}
++
++	// XXX TODO
+ }
+ 
+ int nw_port_has_carrier(nw_port* port) {
+-- 
+2.39.2
+
diff --git a/network/patches/0227-networkd-Automatically-reference-dereference-links-t.patch b/network/patches/0227-networkd-Automatically-reference-dereference-links-t.patch
new file mode 100644
index 000000000..465c14c07
--- /dev/null
+++ b/network/patches/0227-networkd-Automatically-reference-dereference-links-t.patch
@@ -0,0 +1,368 @@
+From 611d4aca5d48567a7a7ddb0dd3446a97e91aad54 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 14:15:01 +0000
+Subject: [PATCH 227/304] networkd: Automatically reference/dereference links
+ to zones/ports
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c |  7 ++++
+ src/networkd/daemon.h |  1 +
+ src/networkd/link.c   | 10 ++++++
+ src/networkd/links.c  |  6 ++++
+ src/networkd/port.c   | 82 +++++++++++++++++++++++++++++++++++--------
+ src/networkd/port.h   |  3 ++
+ src/networkd/zone.c   | 78 +++++++++++++++++++++++++++++++++-------
+ src/networkd/zone.h   |  3 ++
+ 8 files changed, 164 insertions(+), 26 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index c6dad91..02ca8e2 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -540,6 +540,13 @@ 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;
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index ce9a660..a14d33b 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -60,6 +60,7 @@ 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);
+ 
+ /*
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 949d0d8..09b9a62 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -114,6 +114,10 @@ int nw_link_ifindex(nw_link* link) {
+ }
+ 
+ const char* nw_link_ifname(nw_link* link) {
++	// Return NULL if name isn't set
++	if (!*link->ifname)
++		return NULL;
++
+ 	return link->ifname;
+ }
+ 
+@@ -154,6 +158,12 @@ static int nw_link_update_ifname(nw_link* link, sd_netlink_message* message) {
+ 
+ 	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;
+ }
+ 
+diff --git a/src/networkd/links.c b/src/networkd/links.c
+index 3dd06d0..40926f3 100644
+--- a/src/networkd/links.c
++++ b/src/networkd/links.c
+@@ -141,6 +141,12 @@ void nw_links_drop_link(nw_links* links, struct nw_link* 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) {
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 7d02305..37ab3a7 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -135,39 +135,65 @@ static nw_port_type_t nw_port_setup_type(nw_port* port) {
+ 	return nw_port_type_from_string(type);
+ }
+ 
++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;
+ 	char path[PATH_MAX];
+ 	int r;
+ 
+ 	// Find the link
+-	port->link = nw_daemon_get_link_by_name(port->daemon, port->name);
+-	if (port->link) {
+-		DEBUG("%s: Found matching link %d\n", port->name, nw_link_ifindex(port->link));
+-	} else {
+-		DEBUG("%s: Could not find matching link\n", port->name);
++	link = nw_daemon_get_link_by_name(port->daemon, port->name);
++	if (link) {
++		r = nw_port_set_link(port, link);
++		if (r)
++			goto ERROR;
+ 	}
+ 
+ 	// Compose the path to the main configuration file
+ 	r = nw_path_join(path, PORT_CONFIG_DIR, port->name);
+ 	if (r)
+-		return r;
++		goto ERROR;
+ 
+ 	// Initialize the configuration
+ 	r = nw_config_create(&port->config, path);
+ 	if (r)
+-		return r;
++		goto ERROR;
+ 
+ 	// Determine type
+ 	port->type = nw_port_setup_type(port);
+ 	if (!port->type) {
+ 		ERROR("Could not determine type of port %s\n", port->name);
+-		return 0;
++		goto ERROR;
+ 	}
+ 
+ 	// Perform some common initialization
+ 	r = nw_port_setup_common(port);
+ 	if (r)
+-		return r;
++		goto ERROR;
+ 
+ 	// Call any custom initialization
+ 	switch (port->type) {
+@@ -177,7 +203,11 @@ static int nw_port_setup(nw_port* port) {
+ 			break;
+ 	}
+ 
+-	return 0;
++ERROR:
++	if (link)
++		nw_link_unref(link);
++
++	return r;
+ }
+ 
+ int nw_port_create(nw_port** port, nw_daemon* daemon, const char* name) {
+@@ -252,11 +282,35 @@ char* nw_port_bus_path(nw_port* port) {
+ 	return p;
+ }
+ 
+-static nw_link* nw_port_get_link(nw_port* port) {
+-	if (!port->link)
+-		return NULL;
++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;
+ 
+-	return nw_link_ref(port->link);
++	// Drop the link if it matches
++	if (port->link == link)
++		return nw_port_set_link(port, NULL);
++
++	return 0;
+ }
+ 
+ const nw_address_t* nw_port_get_address(nw_port* port) {
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 17c8c3c..3981c82 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -44,6 +44,9 @@ 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);
+ 
+ int nw_port_reconfigure(nw_port* port);
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index 9daa0d3..00ab017 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -80,29 +80,59 @@ static void nw_zone_free(nw_zone* zone) {
+ 	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;
+ 	char path[PATH_MAX];
+ 	int r;
+ 
+ 	// Find the link
+-	zone->link = nw_daemon_get_link_by_name(zone->daemon, zone->name);
+-	if (zone->link) {
+-		DEBUG("%s: Found matching link %d\n", zone->name, nw_link_ifindex(zone->link));
+-	} else {
+-		DEBUG("%s: Could not find matching link\n", zone->name);
++	link = nw_daemon_get_link_by_name(zone->daemon, zone->name);
++	if (link) {
++		r = nw_zone_set_link(zone, link);
++		if (r)
++			goto ERROR;
+ 	}
+ 
+ 	// Compose the path to the main configuration file
+ 	r = nw_zone_path(zone, path, "%s", "settings");
+ 	if (r)
+-		return r;
++		goto ERROR;
+ 
+ 	// Initialize the configuration
+ 	r = nw_config_create(&zone->config, path);
+ 	if (r)
+-		return r;
++		goto ERROR;
+ 
+-	return 0;
++ERROR:
++	if (link)
++		nw_link_unref(link);
++
++	return r;
+ }
+ 
+ int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const char* name) {
+@@ -177,11 +207,35 @@ char* nw_zone_bus_path(nw_zone* zone) {
+ 	return p;
+ }
+ 
+-static nw_link* nw_zone_get_link(nw_zone* zone) {
+-	if (!zone->link)
+-		return NULL;
++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 nw_link_ref(zone->link);
++	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) {
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 591e467..ad348d7 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -41,6 +41,9 @@ 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);
+-- 
+2.39.2
+
diff --git a/network/patches/0228-networkd-ports-Create-dummy-function-to-create-links.patch b/network/patches/0228-networkd-ports-Create-dummy-function-to-create-links.patch
new file mode 100644
index 000000000..3e5f06ff3
--- /dev/null
+++ b/network/patches/0228-networkd-ports-Create-dummy-function-to-create-links.patch
@@ -0,0 +1,46 @@
+From 96b1b84d857124d5b145859e25e3bfe7572eeb73 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 14:29:22 +0000
+Subject: [PATCH 228/304] networkd: ports: Create dummy function to create
+ links
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c | 13 +++++++++++++
+ 1 file changed, 13 insertions(+)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 37ab3a7..5694024 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -321,6 +321,10 @@ static int nw_port_is_disabled(nw_port* port) {
+ 	return nw_config_get_bool(port->config, "DISABLED");
+ }
+ 
++static int nw_port_create_link(nw_port* port) {
++	return 0; // XXX TODO
++}
++
+ int nw_port_reconfigure(nw_port* port) {
+ 	int r;
+ 
+@@ -335,7 +339,16 @@ int nw_port_reconfigure(nw_port* port) {
+ 		return 0;
+ 	}
+ 
++	// If there is no link, we will try to create it
++	if (!port->link) {
++		r = nw_port_create_link(port);
++		if (r)
++			return r;
++	}
++
+ 	// XXX TODO
++
++	return 0;
+ }
+ 
+ int nw_port_has_carrier(nw_port* port) {
+-- 
+2.39.2
+
diff --git a/network/patches/0229-networkd-daemon-Correctly-store-reference-to-bus.patch b/network/patches/0229-networkd-daemon-Correctly-store-reference-to-bus.patch
new file mode 100644
index 000000000..fbf54fce6
--- /dev/null
+++ b/network/patches/0229-networkd-daemon-Correctly-store-reference-to-bus.patch
@@ -0,0 +1,157 @@
+From 442b2fc2a34e8f6b7343d616d07c1aecb667a652 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 17:35:48 +0000
+Subject: [PATCH 229/304] networkd: daemon: Correctly store reference to bus
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/bus.c    | 30 +++++++++++++++++-------------
+ src/networkd/bus.h    |  2 +-
+ src/networkd/daemon.c |  2 +-
+ 3 files changed, 19 insertions(+), 15 deletions(-)
+
+diff --git a/src/networkd/bus.c b/src/networkd/bus.c
+index 1daa035..8158c84 100644
+--- a/src/networkd/bus.c
++++ b/src/networkd/bus.c
+@@ -36,18 +36,19 @@ static int nw_bus_on_connect(sd_bus_message* m, void* data, sd_bus_error* error)
+ 	return 0;
+ }
+ 
+-int nw_bus_connect(sd_bus* bus, sd_event* loop, nw_daemon* daemon) {
++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(&bus);
++	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(bus, NETWORKD_BUS_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;
+@@ -58,21 +59,21 @@ int nw_bus_connect(sd_bus* bus, sd_event* loop, nw_daemon* daemon) {
+ 		address = DEFAULT_SYSTEM_BUS_ADDRESS;
+ 
+ 	// Set bus address
+-	r = sd_bus_set_address(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(bus, 1);
++	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(bus, 1,
++	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));
+@@ -80,53 +81,56 @@ int nw_bus_connect(sd_bus* bus, sd_event* loop, nw_daemon* daemon) {
+ 	}
+ 
+ 	// Automatically bind when the socket is available
+-	r = sd_bus_set_watch_bind(bus, 1);
++	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(bus, 1);
++	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(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(bus, &daemon_bus_impl, daemon);
++	r = nw_bus_register_implementation(b, &daemon_bus_impl, daemon);
+ 	if (r)
+ 		return r;
+ 
+ 	// Request interface name
+-	r = sd_bus_request_name_async(bus, NULL, "org.ipfire.network1", 0, NULL, NULL);
++	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(bus, loop, 0);
++	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(bus, NULL, "org.freedesktop.DBus.Local",
++	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;
+ }
+ 
+diff --git a/src/networkd/bus.h b/src/networkd/bus.h
+index 05b4c63..29b1b4a 100644
+--- a/src/networkd/bus.h
++++ b/src/networkd/bus.h
+@@ -30,7 +30,7 @@
+ 
+ #include "daemon.h"
+ 
+-int nw_bus_connect(sd_bus* bus, sd_event* loop, nw_daemon* daemon);
++int nw_bus_connect(sd_bus** bus, sd_event* loop, nw_daemon* daemon);
+ 
+ struct nw_bus_vtable_pair {
+ 	const sd_bus_vtable* vtable;
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 02ca8e2..c8e65f4 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -341,7 +341,7 @@ static int nw_daemon_setup(nw_daemon* daemon) {
+ 		return r;
+ 
+ 	// Connect to the system bus
+-	r = nw_bus_connect(daemon->bus, daemon->loop, daemon);
++	r = nw_bus_connect(&daemon->bus, daemon->loop, daemon);
+ 	if (r)
+ 		return r;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0230-networkd-Collect-stats-regulary-and-emit-them-on-dbu.patch b/network/patches/0230-networkd-Collect-stats-regulary-and-emit-them-on-dbu.patch
new file mode 100644
index 000000000..34057f31b
--- /dev/null
+++ b/network/patches/0230-networkd-Collect-stats-regulary-and-emit-them-on-dbu.patch
@@ -0,0 +1,667 @@
+From 15240e0819685c30a4955ae161374b5a3fc9d313 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 14 Apr 2023 17:43:12 +0000
+Subject: [PATCH 230/304] networkd: Collect stats regulary and emit them on
+ dbus
+
+This is useful for us monitoring interface throughput (e.g. in
+collecty).
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                    |   2 +
+ src/networkd/daemon.c          |  44 ++++++++
+ src/networkd/daemon.h          |   6 +
+ src/networkd/link.c            |  81 ++++++++++++++
+ src/networkd/link.h            |   6 +
+ src/networkd/port.c            |  31 +++++-
+ src/networkd/port.h            |   5 +
+ src/networkd/stats-collector.c | 197 +++++++++++++++++++++++++++++++++
+ src/networkd/stats-collector.h |  37 +++++++
+ src/networkd/zone.c            |  30 +++++
+ src/networkd/zone.h            |   6 +
+ 11 files changed, 444 insertions(+), 1 deletion(-)
+ create mode 100644 src/networkd/stats-collector.c
+ create mode 100644 src/networkd/stats-collector.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 80a7236..22d90d4 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -332,6 +332,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/port.h \
+ 	src/networkd/port-bus.c \
+ 	src/networkd/port-bus.h \
++	src/networkd/stats-collector.c \
++	src/networkd/stats-collector.h \
+ 	src/networkd/string.h \
+ 	src/networkd/util.c \
+ 	src/networkd/util.h \
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index c8e65f4..749a70b 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -35,6 +35,7 @@
+ #include "links.h"
+ #include "logging.h"
+ #include "ports.h"
++#include "stats-collector.h"
+ #include "zone.h"
+ #include "zones.h"
+ 
+@@ -67,6 +68,8 @@ struct nw_daemon {
+ 	// 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,
+@@ -322,6 +325,33 @@ static int nw_daemon_reconfigure(nw_daemon* daemon) {
+ 	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;
+ 
+@@ -360,6 +390,11 @@ static int nw_daemon_setup(nw_daemon* daemon) {
+ 	if (r)
+ 		return r;
+ 
++	// Start the stats collector
++	r = nw_daemon_starts_stats_collector(daemon);
++	if (r)
++		return r;
++
+ 	return 0;
+ }
+ 
+@@ -404,6 +439,8 @@ static void nw_daemon_free(nw_daemon* daemon) {
+ 	// Cleanup common objects
+ 	nw_daemon_cleanup(daemon);
+ 
++	if (daemon->stats_collector_event)
++		sd_event_source_unref(daemon->stats_collector_event);
+ 	if (daemon->bus)
+ 		sd_bus_unref(daemon->bus);
+ 	if (daemon->loop)
+@@ -498,6 +535,13 @@ int nw_daemon_save(nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
++/*
++	Bus
++*/
++sd_bus* nw_daemon_get_bus(nw_daemon* daemon) {
++	return daemon->bus;
++}
++
+ /*
+ 	Netlink
+ */
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index a14d33b..74e19e6 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -21,6 +21,7 @@
+ #ifndef NETWORKD_DAEMON_H
+ #define NETWORKD_DAEMON_H
+ 
++#include <systemd/sd-bus.h>
+ #include <systemd/sd-netlink.h>
+ 
+ typedef struct nw_daemon nw_daemon;
+@@ -43,6 +44,11 @@ int nw_daemon_reload(nw_daemon* daemon);
+ 
+ int nw_daemon_save(nw_daemon* daemon);
+ 
++/*
++	Bus
++*/
++sd_bus* nw_daemon_get_bus(nw_daemon* daemon);
++
+ /*
+ 	Netlink
+ */
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 09b9a62..1edf20d 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -19,6 +19,7 @@
+ #############################################################################*/
+ 
+ #include <linux/if.h>
++#include <linux/if_link.h>
+ #include <stddef.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -46,6 +47,9 @@ struct nw_link {
+ 		NW_LINK_DESTROYED,
+ 	} state;
+ 
++	// Stats
++	struct rtnl_link_stats64 stats64;
++
+ 	// MTU
+ 	uint32_t mtu;
+ 	uint32_t min_mtu;
+@@ -121,6 +125,83 @@ const char* nw_link_ifname(nw_link* link) {
+ 	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) {
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index 58a825a..2bab47c 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -21,6 +21,8 @@
+ #ifndef NETWORKD_LINK_H
+ #define NETWORKD_LINK_H
+ 
++#include <linux/if_link.h>
++
+ typedef struct nw_link nw_link;
+ 
+ #include "daemon.h"
+@@ -33,6 +35,10 @@ 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);
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 5694024..7638c7b 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -29,8 +29,9 @@
+ #include "config.h"
+ #include "link.h"
+ #include "logging.h"
+-#include "string.h"
+ #include "port.h"
++#include "stats-collector.h"
++#include "string.h"
+ 
+ struct nw_port {
+ 	nw_daemon* daemon;
+@@ -357,3 +358,31 @@ int nw_port_has_carrier(nw_port* port) {
+ 
+ 	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;
++}
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 3981c82..9dcd6c2 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -53,4 +53,9 @@ int nw_port_reconfigure(nw_port* port);
+ 
+ int nw_port_has_carrier(nw_port* port);
+ 
++// 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);
++
+ #endif /* NETWORKD_PORT_H */
+diff --git a/src/networkd/stats-collector.c b/src/networkd/stats-collector.c
+new file mode 100644
+index 0000000..c10602e
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <stdlib.h>
++
++#include <systemd/sd-bus.h>
++#include <systemd/sd-event.h>
++
++#include "logging.h"
++#include "port.h"
++#include "stats-collector.h"
++#include "zone.h"
++
++static int __nw_stats_collector_port(nw_daemon* daemon, nw_port* port, void* data) {
++	return nw_port_update_stats(port);
++}
++
++static int __nw_stats_collector_zone(nw_daemon* daemon, nw_zone* zone, void* data) {
++	return nw_zone_update_stats(zone);
++}
++
++int nw_stats_collector(sd_event_source* s, long unsigned int usec, void* data) {
++	nw_daemon* daemon = (nw_daemon*)data;
++	int r;
++
++	DEBUG("Stats collector has been called\n");
++
++	// Schedule the next call
++	r = sd_event_source_set_time(s, usec + NW_STATS_COLLECTOR_INTERVAL);
++	if (r < 0)
++		return r;
++
++	// Ports
++	r = nw_daemon_ports_walk(daemon, __nw_stats_collector_port, NULL);
++	if (r)
++		return r;
++
++	// Zones
++	r = nw_daemon_zones_walk(daemon, __nw_stats_collector_zone, NULL);
++	if (r)
++		return r;
++
++	return 0;
++}
++
++static int nw_stats_collector_emit_stats(nw_daemon* daemon, const char* path,
++		const char* interface, const char* member, const struct rtnl_link_stats64* stats64) {
++	sd_bus_message* m = NULL;
++	int r;
++
++	sd_bus* bus = nw_daemon_get_bus(daemon);
++
++	// Allocate a new message
++	r = sd_bus_message_new_signal(bus, &m, path, interface, member);
++	if (r < 0) {
++		errno = -r;
++		ERROR("Could not allocate bus message: %m\n");
++		goto ERROR;
++	}
++
++	// Open the container
++	r = sd_bus_message_open_container(m, 'a', "{st}");
++	if (r < 0) {
++		ERROR("Could not open container: %m\n");
++		goto ERROR;
++	}
++
++	const struct stats64_entry {
++		const char* key;
++		uint64_t value;
++	} entries[] = {
++		{ "rx-packets",          stats64->rx_packets },
++		{ "tx-packets",          stats64->tx_packets },
++		{ "rx-bytes",            stats64->rx_bytes },
++		{ "tx-bytes",            stats64->tx_bytes },
++		{ "rx-errors",           stats64->rx_errors },
++		{ "tx-errors",           stats64->tx_errors },
++		{ "rx-dropped",          stats64->rx_dropped },
++		{ "tx-dropped",          stats64->tx_dropped },
++		{ "multicast",           stats64->multicast },
++		{ "collisions",          stats64->collisions },
++
++		// Detailed RX errors
++		{ "rx-length-errors",    stats64->rx_length_errors },
++		{ "rx-over-errors",      stats64->rx_over_errors },
++		{ "rx-crc-errors",       stats64->rx_crc_errors },
++		{ "rx-frame-errors",     stats64->rx_frame_errors },
++		{ "rx-fifo-errors",      stats64->rx_fifo_errors },
++		{ "rx-missed-errors",    stats64->rx_missed_errors },
++
++		// Detailed TX errors
++		{ "tx-aborted-errors",   stats64->tx_aborted_errors },
++		{ "tx-carrier-errors",   stats64->tx_carrier_errors },
++		{ "tx-fifo-errors",      stats64->tx_fifo_errors },
++		{ "tx-heartbeat-errors", stats64->tx_heartbeat_errors },
++		{ "tx-window-errors",    stats64->tx_window_errors },
++
++		{ NULL },
++	};
++
++	for (const struct stats64_entry* e = entries; e->key; e++) {
++		r = sd_bus_message_append(m, "{st}", e->key, e->value);
++		if (r < 0) {
++			ERROR("Could not set stat value: %m\n");
++			goto ERROR;
++		}
++	}
++
++	// Close the container
++	r = sd_bus_message_close_container(m);
++	if (r < 0) {
++		ERROR("Could not close container: %m\n");
++		goto ERROR;
++	}
++
++	// Emit the signal
++	r = sd_bus_send(bus, m, NULL);
++	if (r < 0) {
++		ERROR("Could not emit the stats signal for %s: %m\n", path);
++		goto ERROR;
++	}
++
++ERROR:
++	if (m)
++		sd_bus_message_unref(m);
++
++	return r;
++}
++
++int nw_stats_collector_emit_port_stats(nw_daemon* daemon, nw_port* port) {
++	const struct rtnl_link_stats64* stats64 = NULL;
++	char* path = NULL;
++	int r;
++
++	// Fetch the bus path
++	path = nw_port_bus_path(port);
++
++	// Fetch the stats
++	stats64 = nw_port_get_stats64(port);
++
++	// Emit the stats
++	r = nw_stats_collector_emit_stats(daemon, path,
++		"org.ipfire.network1.Port", "Stats", stats64);
++	if (r < 0) {
++		ERROR("Could not emit stats for port %s: %m\n", nw_port_name(port));
++		goto ERROR;
++	}
++
++ERROR:
++	if (path)
++		free(path);
++
++	return r;
++}
++
++int nw_stats_collector_emit_zone_stats(nw_daemon* daemon, nw_zone* zone) {
++	const struct rtnl_link_stats64* stats64 = NULL;
++	char* path = NULL;
++	int r;
++
++	// Fetch the bus path
++	path = nw_zone_bus_path(zone);
++
++	// Fetch the stats
++	stats64 = nw_zone_get_stats64(zone);
++
++	// Emit the stats
++	r = nw_stats_collector_emit_stats(daemon, path,
++		"org.ipfire.network1.Zone", "Stats", stats64);
++	if (r < 0) {
++		ERROR("Could not emit stats for zone %s: %m\n", nw_zone_name(zone));
++		goto ERROR;
++	}
++
++ERROR:
++	if (path)
++		free(path);
++
++	return r;
++}
+diff --git a/src/networkd/stats-collector.h b/src/networkd/stats-collector.h
+new file mode 100644
+index 0000000..ea11c11
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_STATS_COLLECTOR_H
++#define NETWORKD_STATS_COLLECTOR_H
++
++#include <systemd/sd-event.h>
++
++#include "daemon.h"
++#include "port.h"
++#include "zone.h"
++
++#define NW_STATS_COLLECTOR_INTERVAL			15 * 1000000ULL // 15 sec in µsec
++
++int nw_stats_collector(sd_event_source* s, long unsigned int usec, void* data);
++
++int nw_stats_collector_emit_port_stats(nw_daemon* daemon, nw_port* port);
++int nw_stats_collector_emit_zone_stats(nw_daemon* daemon, nw_zone* zone);
++
++#endif /* NETWORKD_STATS_COLLECTOR_H */
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index 00ab017..3f47a26 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -19,6 +19,7 @@
+ #############################################################################*/
+ 
+ #include <limits.h>
++#include <linux/if_link.h>
+ #include <stdlib.h>
+ 
+ #include <systemd/sd-bus.h>
+@@ -27,6 +28,7 @@
+ #include "daemon.h"
+ #include "link.h"
+ #include "logging.h"
++#include "stats-collector.h"
+ #include "string.h"
+ #include "zone.h"
+ 
+@@ -263,3 +265,31 @@ int nw_zone_set_mtu(nw_zone* zone, unsigned int 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
+index ad348d7..9737b45 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -28,6 +28,8 @@
+ 
+ typedef struct nw_zone nw_zone;
+ 
++#include <linux/if_link.h>
++
+ #include "daemon.h"
+ 
+ int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const char* name);
+@@ -54,4 +56,8 @@ int nw_zone_has_carrier(nw_zone* zone);
+ 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 */
+-- 
+2.39.2
+
diff --git a/network/patches/0231-address-Fix-output-buffer-size-when-formatting-MAC-a.patch b/network/patches/0231-address-Fix-output-buffer-size-when-formatting-MAC-a.patch
new file mode 100644
index 000000000..716fc51b3
--- /dev/null
+++ b/network/patches/0231-address-Fix-output-buffer-size-when-formatting-MAC-a.patch
@@ -0,0 +1,27 @@
+From 35bf96bb311d13924cd96668736b474c1576f553 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 15 Apr 2023 10:45:53 +0000
+Subject: [PATCH 231/304] address: Fix output buffer size when formatting MAC
+ addresses
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/address.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/networkd/address.h b/src/networkd/address.h
+index b7556f4..7937d62 100644
+--- a/src/networkd/address.h
++++ b/src/networkd/address.h
+@@ -43,7 +43,7 @@ static inline int nw_address_from_string(nw_address_t* addr, const char* s) {
+ }
+ 
+ static inline char* nw_address_to_string(const nw_address_t* addr) {
+-	char buffer[18];
++	char buffer[20];
+ 
+ 	char* p = ether_ntoa_r(addr, buffer);
+ 	if (!p)
+-- 
+2.39.2
+
diff --git a/network/patches/0232-config-Fail-if-there-is-garbage-after-intergers.patch b/network/patches/0232-config-Fail-if-there-is-garbage-after-intergers.patch
new file mode 100644
index 000000000..d5b55cc45
--- /dev/null
+++ b/network/patches/0232-config-Fail-if-there-is-garbage-after-intergers.patch
@@ -0,0 +1,42 @@
+From 92c8a4fe8806d0a84f4c546be75c0f08dc6d7200 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 15 Apr 2023 10:46:32 +0000
+Subject: [PATCH 232/304] config: Fail if there is garbage after intergers
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index d879ace..42e0172 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -348,13 +348,23 @@ 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) {
++	char* p = NULL;
++	int r;
++
+ 	const char* value = nw_config_get(config, key);
+ 
+ 	// Return zero if not set
+ 	if (!value)
+ 		return __default;
+ 
+-	return strtoul(value, NULL, 10);
++	// 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) {
+-- 
+2.39.2
+
diff --git a/network/patches/0233-config-Avoid-adding-empty-line-after-integers.patch b/network/patches/0233-config-Avoid-adding-empty-line-after-integers.patch
new file mode 100644
index 000000000..6494350da
--- /dev/null
+++ b/network/patches/0233-config-Avoid-adding-empty-line-after-integers.patch
@@ -0,0 +1,26 @@
+From 2d00051763072b26fa5c1ba421afe2f0928d57ce Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 15 Apr 2023 11:13:22 +0000
+Subject: [PATCH 233/304] config: Avoid adding empty line after integers
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 42e0172..27cfad8 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -372,7 +372,7 @@ int nw_config_set_int(nw_config* config, const char* key, const int value) {
+ 	int r;
+ 
+ 	// Format the value as string
+-	r = nw_string_format(__value, "%d\n", value);
++	r = nw_string_format(__value, "%d", value);
+ 	if (r)
+ 		return r;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0234-ports-Require-type-to-be-set-at-all-times.patch b/network/patches/0234-ports-Require-type-to-be-set-at-all-times.patch
new file mode 100644
index 000000000..da6ff24ab
--- /dev/null
+++ b/network/patches/0234-ports-Require-type-to-be-set-at-all-times.patch
@@ -0,0 +1,133 @@
+From 06bc93d3136c4bd2d75f7c50051b8b96d29dab59 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 15 Apr 2023 11:15:15 +0000
+Subject: [PATCH 234/304] ports: Require type to be set at all times
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c  | 50 ++++++++++++++++++++++++++++++--------------
+ src/networkd/port.h  |  5 ++++-
+ src/networkd/ports.c |  2 +-
+ 3 files changed, 39 insertions(+), 18 deletions(-)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 7638c7b..aad6210 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -128,14 +128,6 @@ static int nw_port_setup_common(nw_port* port) {
+ 	return 0;
+ }
+ 
+-static nw_port_type_t nw_port_setup_type(nw_port* port) {
+-	const char* type = nw_config_get(port->config, "TYPE");
+-	if (!type)
+-		return NW_PORT_UNKNOWN;
+-
+-	return nw_port_type_from_string(type);
+-}
+-
+ 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)
+@@ -184,13 +176,6 @@ static int nw_port_setup(nw_port* port) {
+ 	if (r)
+ 		goto ERROR;
+ 
+-	// Determine type
+-	port->type = nw_port_setup_type(port);
+-	if (!port->type) {
+-		ERROR("Could not determine type of port %s\n", port->name);
+-		goto ERROR;
+-	}
+-
+ 	// Perform some common initialization
+ 	r = nw_port_setup_common(port);
+ 	if (r)
+@@ -211,7 +196,7 @@ ERROR:
+ 	return r;
+ }
+ 
+-int nw_port_create(nw_port** port, nw_daemon* daemon, const char* name) {
++int nw_port_create(nw_port** port, nw_daemon* daemon, nw_port_type_t type, const char* name) {
+ 	int r;
+ 
+ 	// Allocate a new object
+@@ -225,6 +210,9 @@ int nw_port_create(nw_port** port, nw_daemon* daemon, const char* name) {
+ 	// Initialize reference counter
+ 	p->nrefs = 1;
+ 
++	// Store the type
++	p->type = type;
++
+ 	// Store the name
+ 	r = nw_string_set(p->name, name);
+ 	if (r)
+@@ -243,6 +231,36 @@ ERROR:
+ 	return r;
+ }
+ 
++int nw_port_create_from_config(nw_port** port, nw_daemon* daemon,
++		const char* name, const char* path) {
++	nw_config* config = NULL;
++	int r;
++
++	// Initialize the configuration
++	r = nw_config_create(&config, path);
++	if (r)
++		goto ERROR;
++
++	// Fetch the type
++	const char* type = nw_config_get(config, "TYPE");
++	if (!type) {
++		ERROR("Port configuration %s has no TYPE\n", path);
++		r = 1;
++		goto ERROR;
++	}
++
++	// Create a new port
++	r = nw_port_create(port, daemon, nw_port_type_from_string(type), name);
++	if (r)
++		goto ERROR;
++
++ERROR:
++	if (config)
++		nw_config_unref(config);
++
++	return r;
++}
++
+ nw_port* nw_port_ref(nw_port* port) {
+ 	port->nrefs++;
+ 
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 9dcd6c2..2f4319b 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -33,7 +33,10 @@ typedef struct nw_port nw_port;
+ #include "address.h"
+ #include "daemon.h"
+ 
+-int nw_port_create(nw_port** port, nw_daemon* daemon, const char* name);
++int nw_port_create(nw_port** port, nw_daemon* daemon,
++	nw_port_type_t type, const char* name);
++int nw_port_create_from_config(nw_port** port, nw_daemon* daemon,
++	const char* name, const char* path);
+ 
+ nw_port* nw_port_ref(nw_port* port);
+ nw_port* nw_port_unref(nw_port* port);
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+index 87135d8..f0a3ebb 100644
+--- a/src/networkd/ports.c
++++ b/src/networkd/ports.c
+@@ -151,7 +151,7 @@ static int __nw_ports_enumerate(const char* path, const struct stat* s, void* da
+ 		return 0;
+ 
+ 	// Create a new port
+-	r = nw_port_create(&port, ports->daemon, name);
++	r = nw_port_create_from_config(&port, ports->daemon, name, path);
+ 	if (r)
+ 		goto ERROR;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0235-port-Implement-reading-writing-VLAN-settings.patch b/network/patches/0235-port-Implement-reading-writing-VLAN-settings.patch
new file mode 100644
index 000000000..687e9eb94
--- /dev/null
+++ b/network/patches/0235-port-Implement-reading-writing-VLAN-settings.patch
@@ -0,0 +1,287 @@
+From c7761af8c8f278f3adb7d422144e13f845089a4b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 15 Apr 2023 11:16:33 +0000
+Subject: [PATCH 235/304] port: Implement reading/writing VLAN settings
+
+This is just simple test to see where the configuration could be going.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c   | 166 +++++++++++++++++++++++++++++++++++++++++-
+ src/networkd/port.h   |  13 ++++
+ src/networkd/string.h |   5 ++
+ 3 files changed, 182 insertions(+), 2 deletions(-)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index aad6210..a240c61 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -40,21 +40,32 @@ struct nw_port {
+ 	// Link
+ 	nw_link* link;
+ 
+-	char name[IF_NAMESIZE];
+ 	nw_port_type_t type;
++	char name[IF_NAMESIZE];
+ 
+ 	// Configuration
+ 	nw_config *config;
+ 
+ 	// Common attributes
+ 	nw_address_t address;
++
++	// VLAN settings
++	struct nw_port_vlan {
++		nw_port* parent;
++		int id;
++
++		// If the parent has not been read from the configuration we will
++		// save the name and try to find it later.
++		char __parent_name[IF_NAMESIZE];
++	} vlan;
+ };
+ 
+ static const struct nw_port_type_map {
+ 	nw_port_type_t type;
+ 	const char* name;
+ } nw_port_type_map[] = {
+-	{ NW_PORT_DUMMY, "dummy" },
++	{ NW_PORT_DUMMY,   "dummy" },
++	{ NW_PORT_VLAN,    "vlan" },
+ 	{ NW_PORT_UNKNOWN, NULL },
+ };
+ 
+@@ -128,6 +139,28 @@ static int nw_port_setup_common(nw_port* port) {
+ 	return 0;
+ }
+ 
++static int nw_port_setup_vlan(nw_port* port) {
++	int r;
++
++	// VLAN ID
++	int id = nw_config_get_int(port->config, "VLAN_ID", NW_VLAN_ID_INVALID);
++	if (id) {
++		r = nw_port_set_vlan_id(port, id);
++		if (r)
++			return r;
++	}
++
++	// Parent Port
++	const char* parent = nw_config_get(port->config, "VLAN_PARENT");
++	if (parent) {
++		r = nw_string_set(port->vlan.__parent_name, parent);
++		if (r)
++			return r;
++	}
++
++	return 0;
++}
++
+ 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)
+@@ -183,6 +216,12 @@ static int nw_port_setup(nw_port* port) {
+ 
+ 	// Call any custom initialization
+ 	switch (port->type) {
++		case NW_PORT_VLAN:
++			r = nw_port_setup_vlan(port);
++			if (r)
++				goto ERROR;
++			break;
++
+ 		// These do not need any special initialization
+ 		case NW_PORT_DUMMY:
+ 		case NW_PORT_UNKNOWN:
+@@ -275,14 +314,50 @@ nw_port* nw_port_unref(nw_port* port) {
+ 	return NULL;
+ }
+ 
++static int nw_port_save_vlan(nw_port* port) {
++	int r;
++
++	// VLAN ID
++	r = nw_config_set_int(port->config, "VLAN_ID", port->vlan.id);
++	if (r)
++		return r;
++
++	// Parent Port
++	r = nw_config_set(port->config, "VLAN_PARENT",
++		(port->vlan.parent) ? nw_port_name(port->vlan.parent) : port->vlan.__parent_name);
++	if (r)
++		return r;
++
++	return 0;
++}
++
+ int nw_port_save(nw_port* port) {
+ 	int r;
+ 
++	switch (port->type) {
++		// VLAN
++		case NW_PORT_VLAN:
++			r = nw_port_save_vlan(port);
++			if (r)
++				goto ERROR;
++			break;
++
++		// These types do not have any special settings
++		case NW_PORT_DUMMY:
++		case NW_PORT_UNKNOWN:
++			break;
++	}
++
+ 	r = nw_config_write(port->config);
+ 	if (r)
+ 		return r;
+ 
+ 	return 0;
++
++ERROR:
++	ERROR("Could not save configuration for port %s: %m\n", port->name);
++
++	return 1;
+ }
+ 
+ const char* nw_port_name(nw_port* port) {
+@@ -404,3 +479,90 @@ int nw_port_update_stats(nw_port* port) {
+ 
+ 	return 0;
+ }
++
++static int nw_port_check_type(nw_port* port, const nw_port_type_t type) {
++	if (port->type == type)
++		return 0;
++
++	errno = ENOTSUP;
++	return -errno;
++}
++
++/*
++	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;
++}
++
++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);
++
++	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.h b/src/networkd/port.h
+index 2f4319b..d89e1e0 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -26,8 +26,14 @@
+ typedef enum nw_port_type {
+ 	NW_PORT_UNKNOWN = 0,
+ 	NW_PORT_DUMMY,
++	NW_PORT_VLAN,
+ } nw_port_type_t;
+ 
++// VLAN
++#define NW_VLAN_ID_INVALID		0
++#define NW_VLAN_ID_MIN			1
++#define NW_VLAN_ID_MAX			4096
++
+ typedef struct nw_port nw_port;
+ 
+ #include "address.h"
+@@ -61,4 +67,11 @@ 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);
+ 
++// VLAN
++int nw_port_get_vlan_id(nw_port* port);
++int nw_port_set_vlan_id(nw_port* port, int id);
++
++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_H */
+diff --git a/src/networkd/string.h b/src/networkd/string.h
+index 6ff44f9..5bdfc3d 100644
+--- a/src/networkd/string.h
++++ b/src/networkd/string.h
+@@ -120,6 +120,11 @@ static inline int nw_string_strip(char* s) {
+ 	return 0;
+ }
+ 
++static inline void nw_string_empty(char* s) {
++	if (s)
++		*s = '\0';
++}
++
+ /*
+ 	Paths
+ */
+-- 
+2.39.2
+
diff --git a/network/patches/0236-ports-Implement-destroying-a-port.patch b/network/patches/0236-ports-Implement-destroying-a-port.patch
new file mode 100644
index 000000000..121334aac
--- /dev/null
+++ b/network/patches/0236-ports-Implement-destroying-a-port.patch
@@ -0,0 +1,193 @@
+From c403eb4c480316090d9ca3fd2915b1bf25ac10c0 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 15 Apr 2023 11:46:22 +0000
+Subject: [PATCH 236/304] ports: Implement destroying a port
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 12 ++++++++++
+ src/networkd/config.h |  2 ++
+ src/networkd/link.c   |  2 ++
+ src/networkd/port.c   | 54 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/port.h   |  3 +++
+ src/networkd/zone.c   |  9 ++++++++
+ src/networkd/zone.h   |  2 ++
+ 7 files changed, 84 insertions(+)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 27cfad8..8269ede 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -24,6 +24,7 @@
+ #include <stdlib.h>
+ #include <string.h>
+ #include <sys/queue.h>
++#include <unistd.h>
+ 
+ #include "config.h"
+ #include "logging.h"
+@@ -135,6 +136,17 @@ nw_config* nw_config_unref(nw_config* config) {
+ 	return NULL;
+ }
+ 
++int nw_config_destroy(nw_config* config) {
++	int r;
++
++	// Drop all entries
++	r = nw_config_flush(config);
++	if (r)
++		return r;
++
++	return unlink(config->path);
++}
++
+ const char* nw_config_path(nw_config* config) {
+ 	if (*config->path)
+ 		return config->path;
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index 0b25f75..e17c016 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -33,6 +33,8 @@ int nw_config_create(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_destroy(nw_config* config);
++
+ const char* nw_config_path(nw_config* config);
+ 
+ int nw_config_flush(nw_config* config);
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 1edf20d..603aabe 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -308,12 +308,14 @@ static int nw_link_update_flags(nw_link* link, sd_netlink_message* message) {
+ 		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)
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index a240c61..23ffd0b 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -314,6 +314,60 @@ nw_port* nw_port_unref(nw_port* port) {
+ 	return NULL;
+ }
+ 
++int nw_port_destroy(nw_port* port) {
++	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)
++			return r;
++	}
++
++	// Dereference the port from other ports
++	r = nw_daemon_ports_walk(port->daemon, __nw_port_drop_port, port);
++	if (r)
++		return r;
++
++	// Dereference the port from other zones
++	r = nw_daemon_zones_walk(port->daemon, __nw_zone_drop_port, port);
++	if (r)
++		return r;
++
++	// Destroy the configuration
++	r = nw_config_destroy(port->config);
++	if (r)
++		return r;
++
++	// Reset type
++	port->type = NW_PORT_UNKNOWN;
++
++	return 0;
++}
++
++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) {
++		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_DUMMY:
++		case NW_PORT_UNKNOWN:
++			break;
++	}
++
++	return 0;
++}
++
+ static int nw_port_save_vlan(nw_port* port) {
+ 	int r;
+ 
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index d89e1e0..032d82c 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -47,6 +47,9 @@ int nw_port_create_from_config(nw_port** port, nw_daemon* daemon,
+ 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);
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index 3f47a26..9f5b7f8 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -183,6 +183,15 @@ nw_zone* nw_zone_unref(nw_zone* 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) {
+ 	int r;
+ 
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 9737b45..480440f 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -37,6 +37,8 @@ int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const char* name);
+ 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);
+-- 
+2.39.2
+
diff --git a/network/patches/0237-ports-Create-scaffolding-for-operations-struct.patch b/network/patches/0237-ports-Create-scaffolding-for-operations-struct.patch
new file mode 100644
index 000000000..69b8e3caf
--- /dev/null
+++ b/network/patches/0237-ports-Create-scaffolding-for-operations-struct.patch
@@ -0,0 +1,156 @@
+From 9e8af30e5ff0ab717f7d878f8e9c62323ec161a1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 14:28:35 +0000
+Subject: [PATCH 237/304] ports: Create scaffolding for operations struct
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am               |  2 ++
+ src/networkd/port-dummy.c | 28 ++++++++++++++++++++++++++++
+ src/networkd/port-dummy.h | 28 ++++++++++++++++++++++++++++
+ src/networkd/port.c       | 11 +++++++++++
+ src/networkd/port.h       |  7 +++++++
+ 5 files changed, 76 insertions(+)
+ create mode 100644 src/networkd/port-dummy.c
+ create mode 100644 src/networkd/port-dummy.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 22d90d4..15e9fa0 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -332,6 +332,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/port.h \
+ 	src/networkd/port-bus.c \
+ 	src/networkd/port-bus.h \
++	src/networkd/port-dummy.c \
++	src/networkd/port-dummy.h \
+ 	src/networkd/stats-collector.c \
+ 	src/networkd/stats-collector.h \
+ 	src/networkd/string.h \
+diff --git a/src/networkd/port-dummy.c b/src/networkd/port-dummy.c
+new file mode 100644
+index 0000000..cc4b649
+--- /dev/null
++++ b/src/networkd/port-dummy.c
+@@ -0,0 +1,28 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include "port.h"
++#include "port-dummy.h"
++
++nw_port_ops_t nw_port_ops_dummy = {
++	// There is no special configuration
++	.config_read = NULL,
++	.config_write = NULL,
++};
+diff --git a/src/networkd/port-dummy.h b/src/networkd/port-dummy.h
+new file mode 100644
+index 0000000..0a29c84
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_PORT_DUMMY_H
++#define NETWORKD_PORT_DUMMY_H
++
++#include "port.h"
++
++extern nw_port_ops_t nw_port_ops_dummy;
++
++#endif /* NETWORKD_PORT_DUMMY_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 23ffd0b..6ba4875 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -30,6 +30,7 @@
+ #include "link.h"
+ #include "logging.h"
+ #include "port.h"
++#include "port-dummy.h"
+ #include "stats-collector.h"
+ #include "string.h"
+ 
+@@ -49,6 +50,9 @@ struct nw_port {
+ 	// Common attributes
+ 	nw_address_t address;
+ 
++	// Type Operations
++	nw_port_ops_t ops;
++
+ 	// VLAN settings
+ 	struct nw_port_vlan {
+ 		nw_port* parent;
+@@ -252,6 +256,13 @@ int nw_port_create(nw_port** port, nw_daemon* daemon, nw_port_type_t type, const
+ 	// Store the type
+ 	p->type = type;
+ 
++	// Set operations
++	switch (p->type) {
++		case NW_PORT_DUMMY:
++			p->ops = nw_port_ops_dummy;
++			break;
++	}
++
+ 	// Store the name
+ 	r = nw_string_set(p->name, name);
+ 	if (r)
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 032d82c..76df7a7 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -37,8 +37,15 @@ typedef enum nw_port_type {
+ typedef struct nw_port nw_port;
+ 
+ #include "address.h"
++#include "config.h"
+ #include "daemon.h"
+ 
++typedef struct nw_port_ops {
++	// Configuration
++	int (*config_read)(nw_port* port, nw_config* config);
++	int (*config_write)(nw_port* port, nw_config* config);
++} nw_port_ops_t;
++
+ int nw_port_create(nw_port** port, nw_daemon* daemon,
+ 	nw_port_type_t type, const char* name);
+ int nw_port_create_from_config(nw_port** port, nw_daemon* daemon,
+-- 
+2.39.2
+
diff --git a/network/patches/0238-ports-Move-VLAN-stuff-into-its-own-file.patch b/network/patches/0238-ports-Move-VLAN-stuff-into-its-own-file.patch
new file mode 100644
index 000000000..3e83ff9c5
--- /dev/null
+++ b/network/patches/0238-ports-Move-VLAN-stuff-into-its-own-file.patch
@@ -0,0 +1,591 @@
+From ff88697584a58e86b3abdad0b5017fda382f50e9 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 14:54:58 +0000
+Subject: [PATCH 238/304] ports: Move VLAN stuff into its own file
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am              |   2 +
+ src/networkd/port-vlan.c | 150 +++++++++++++++++++++++++++++
+ src/networkd/port-vlan.h |  36 +++++++
+ src/networkd/port.c      | 203 ++++++---------------------------------
+ src/networkd/port.h      |  51 ++++++++--
+ src/networkd/ports.h     |   1 +
+ 6 files changed, 261 insertions(+), 182 deletions(-)
+ create mode 100644 src/networkd/port-vlan.c
+ create mode 100644 src/networkd/port-vlan.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 15e9fa0..aafa59c 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -334,6 +334,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/port-bus.h \
+ 	src/networkd/port-dummy.c \
+ 	src/networkd/port-dummy.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 \
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+new file mode 100644
+index 0000000..c9581f6
+--- /dev/null
++++ b/src/networkd/port-vlan.c
+@@ -0,0 +1,150 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include "config.h"
++#include "daemon.h"
++#include "logging.h"
++#include "port.h"
++#include "port-vlan.h"
++#include "string.h"
++
++static int nw_port_vlan_config_read(nw_port* port) {
++	int r;
++
++	// VLAN ID
++	int id = nw_config_get_int(port->config, "VLAN_ID", NW_VLAN_ID_INVALID);
++	if (id) {
++		r = nw_port_set_vlan_id(port, id);
++		if (r)
++			return r;
++	}
++
++	// Parent Port
++	const char* parent = nw_config_get(port->config, "VLAN_PARENT");
++	if (parent) {
++		r = nw_string_set(port->vlan.__parent_name, parent);
++		if (r)
++			return r;
++	}
++
++	return 0;
++}
++
++static int nw_port_vlan_config_write(nw_port* port) {
++	int r;
++
++	// VLAN ID
++	r = nw_config_set_int(port->config, "VLAN_ID", port->vlan.id);
++	if (r)
++		return r;
++
++	// Parent Port
++	r = nw_config_set(port->config, "VLAN_PARENT",
++		(port->vlan.parent) ? nw_port_name(port->vlan.parent) : port->vlan.__parent_name);
++	if (r)
++		return r;
++
++	return 0;
++}
++
++nw_port_ops_t nw_port_ops_vlan = {
++	// Configuration
++	.config_read = nw_port_vlan_config_read,
++	.config_write = nw_port_vlan_config_write,
++};
++
++/*
++	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;
++}
++
++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);
++
++	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 0000000..2bacb24
+--- /dev/null
++++ b/src/networkd/port-vlan.h
+@@ -0,0 +1,36 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_PORT_VLAN_H
++#define NETWORKD_PORT_VLAN_H
++
++#include "port.h"
++
++extern nw_port_ops_t nw_port_ops_vlan;
++
++// ID
++int nw_port_get_vlan_id(nw_port* port);
++int nw_port_set_vlan_id(nw_port* port, int id);
++
++// 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
+index 6ba4875..b3b7d66 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -31,39 +31,10 @@
+ #include "logging.h"
+ #include "port.h"
+ #include "port-dummy.h"
++#include "port-vlan.h"
+ #include "stats-collector.h"
+ #include "string.h"
+ 
+-struct nw_port {
+-	nw_daemon* daemon;
+-	int nrefs;
+-
+-	// Link
+-	nw_link* link;
+-
+-	nw_port_type_t type;
+-	char name[IF_NAMESIZE];
+-
+-	// Configuration
+-	nw_config *config;
+-
+-	// Common attributes
+-	nw_address_t address;
+-
+-	// Type Operations
+-	nw_port_ops_t ops;
+-
+-	// VLAN settings
+-	struct nw_port_vlan {
+-		nw_port* parent;
+-		int id;
+-
+-		// If the parent has not been read from the configuration we will
+-		// save the name and try to find it later.
+-		char __parent_name[IF_NAMESIZE];
+-	} vlan;
+-};
+-
+ static const struct nw_port_type_map {
+ 	nw_port_type_t type;
+ 	const char* name;
+@@ -143,28 +114,6 @@ static int nw_port_setup_common(nw_port* port) {
+ 	return 0;
+ }
+ 
+-static int nw_port_setup_vlan(nw_port* port) {
+-	int r;
+-
+-	// VLAN ID
+-	int id = nw_config_get_int(port->config, "VLAN_ID", NW_VLAN_ID_INVALID);
+-	if (id) {
+-		r = nw_port_set_vlan_id(port, id);
+-		if (r)
+-			return r;
+-	}
+-
+-	// Parent Port
+-	const char* parent = nw_config_get(port->config, "VLAN_PARENT");
+-	if (parent) {
+-		r = nw_string_set(port->vlan.__parent_name, parent);
+-		if (r)
+-			return r;
+-	}
+-
+-	return 0;
+-}
+-
+ 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)
+@@ -219,17 +168,10 @@ static int nw_port_setup(nw_port* port) {
+ 		goto ERROR;
+ 
+ 	// Call any custom initialization
+-	switch (port->type) {
+-		case NW_PORT_VLAN:
+-			r = nw_port_setup_vlan(port);
+-			if (r)
+-				goto ERROR;
+-			break;
+-
+-		// These do not need any special initialization
+-		case NW_PORT_DUMMY:
+-		case NW_PORT_UNKNOWN:
+-			break;
++	if (port->ops.config_read) {
++		r = port->ops.config_read(port);
++		if (r)
++			goto ERROR;
+ 	}
+ 
+ ERROR:
+@@ -261,6 +203,10 @@ int nw_port_create(nw_port** port, nw_daemon* daemon, nw_port_type_t type, const
+ 		case NW_PORT_DUMMY:
+ 			p->ops = nw_port_ops_dummy;
+ 			break;
++
++		case NW_PORT_VLAN:
++			p->ops = nw_port_ops_vlan;
++			break;
+ 	}
+ 
+ 	// Store the name
+@@ -379,40 +325,17 @@ int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
+ 	return 0;
+ }
+ 
+-static int nw_port_save_vlan(nw_port* port) {
+-	int r;
+-
+-	// VLAN ID
+-	r = nw_config_set_int(port->config, "VLAN_ID", port->vlan.id);
+-	if (r)
+-		return r;
+-
+-	// Parent Port
+-	r = nw_config_set(port->config, "VLAN_PARENT",
+-		(port->vlan.parent) ? nw_port_name(port->vlan.parent) : port->vlan.__parent_name);
+-	if (r)
+-		return r;
+-
+-	return 0;
+-}
+-
+ int nw_port_save(nw_port* port) {
+ 	int r;
+ 
+-	switch (port->type) {
+-		// VLAN
+-		case NW_PORT_VLAN:
+-			r = nw_port_save_vlan(port);
+-			if (r)
+-				goto ERROR;
+-			break;
+-
+-		// These types do not have any special settings
+-		case NW_PORT_DUMMY:
+-		case NW_PORT_UNKNOWN:
+-			break;
++	// Call the custom handler
++	if (port->ops.config_write) {
++		r = port->ops.config_write(port);
++		if (r)
++			goto ERROR;
+ 	}
+ 
++	// Write the configuration
+ 	r = nw_config_write(port->config);
+ 	if (r)
+ 		return r;
+@@ -481,7 +404,20 @@ static int nw_port_is_disabled(nw_port* port) {
+ }
+ 
+ static int nw_port_create_link(nw_port* port) {
+-	return 0; // XXX TODO
++	int r;
++
++	// Fail if the function isn't set
++	if (!port->ops.create_link) {
++		errno = ENOTSUP;
++		return -errno;
++	}
++
++	// Create the link
++	r = port->ops.create_link(port);
++	if (r)
++		ERROR("Could not create link %s: %m\n", port->name);
++
++	return r;
+ }
+ 
+ int nw_port_reconfigure(nw_port* port) {
+@@ -545,89 +481,10 @@ int nw_port_update_stats(nw_port* port) {
+ 	return 0;
+ }
+ 
+-static int nw_port_check_type(nw_port* port, const nw_port_type_t type) {
++int nw_port_check_type(nw_port* port, const nw_port_type_t type) {
+ 	if (port->type == type)
+ 		return 0;
+ 
+ 	errno = ENOTSUP;
+ 	return -errno;
+ }
+-
+-/*
+-	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;
+-}
+-
+-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);
+-
+-	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.h b/src/networkd/port.h
+index 76df7a7..5253d69 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -21,6 +21,10 @@
+ #ifndef NETWORKD_PORT_H
+ #define NETWORKD_PORT_H
+ 
++#ifndef IF_NAMESIZE
++#define IF_NAMESIZE 16
++#endif
++
+ #define PORT_CONFIG_DIR			CONFIG_DIR "/ports"
+ 
+ typedef enum nw_port_type {
+@@ -42,10 +46,44 @@ typedef struct nw_port nw_port;
+ 
+ typedef struct nw_port_ops {
+ 	// Configuration
+-	int (*config_read)(nw_port* port, nw_config* config);
+-	int (*config_write)(nw_port* port, nw_config* config);
++	int (*config_read)(nw_port* port);
++	int (*config_write)(nw_port* port);
++
++	// Link
++	int (*create_link)(nw_port* port);
++	int (*destroy_link)(nw_port* port);
+ } nw_port_ops_t;
+ 
++struct nw_port {
++	nw_daemon* daemon;
++	int nrefs;
++
++	// Link
++	nw_link* link;
++
++	nw_port_type_t type;
++	char name[IF_NAMESIZE];
++
++	// Configuration
++	nw_config *config;
++
++	// Common attributes
++	nw_address_t address;
++
++	// Type Operations
++	nw_port_ops_t ops;
++
++	// VLAN settings
++	struct nw_port_vlan {
++		nw_port* parent;
++		int id;
++
++		// If the parent has not been read from the configuration we will
++		// save the name and try to find it later.
++		char __parent_name[IF_NAMESIZE];
++	} vlan;
++};
++
+ int nw_port_create(nw_port** port, nw_daemon* daemon,
+ 	nw_port_type_t type, const char* name);
+ int nw_port_create_from_config(nw_port** port, nw_daemon* daemon,
+@@ -72,16 +110,11 @@ 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_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);
+ 
+-// VLAN
+-int nw_port_get_vlan_id(nw_port* port);
+-int nw_port_set_vlan_id(nw_port* port, int id);
+-
+-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_H */
+diff --git a/src/networkd/ports.h b/src/networkd/ports.h
+index 68ae532..4e41f11 100644
+--- a/src/networkd/ports.h
++++ b/src/networkd/ports.h
+@@ -26,6 +26,7 @@ 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);
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0239-ports-Implement-creating-links-from-ports.patch b/network/patches/0239-ports-Implement-creating-links-from-ports.patch
new file mode 100644
index 000000000..6c7f890f0
--- /dev/null
+++ b/network/patches/0239-ports-Implement-creating-links-from-ports.patch
@@ -0,0 +1,294 @@
+From 240e331b2043ae254c6468ed3ce2ec6d8caf98db Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 16:26:44 +0000
+Subject: [PATCH 239/304] ports: Implement creating links from ports
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-dummy.c |   5 +-
+ src/networkd/port-vlan.c  |  20 +++++
+ src/networkd/port.c       | 149 +++++++++++++++++++++++++++++++++++---
+ src/networkd/port.h       |  10 ++-
+ 4 files changed, 170 insertions(+), 14 deletions(-)
+
+diff --git a/src/networkd/port-dummy.c b/src/networkd/port-dummy.c
+index cc4b649..09367d8 100644
+--- a/src/networkd/port-dummy.c
++++ b/src/networkd/port-dummy.c
+@@ -18,11 +18,8 @@
+ #                                                                             #
+ #############################################################################*/
+ 
+-#include "port.h"
+ #include "port-dummy.h"
+ 
+ nw_port_ops_t nw_port_ops_dummy = {
+-	// There is no special configuration
+-	.config_read = NULL,
+-	.config_write = NULL,
++	.kind = "dummy",
+ };
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index c9581f6..edf91d9 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -18,6 +18,8 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <systemd/sd-netlink.h>
++
+ #include "config.h"
+ #include "daemon.h"
+ #include "logging.h"
+@@ -64,10 +66,28 @@ static int nw_port_vlan_config_write(nw_port* port) {
+ 	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;
++
++	return 0;
++}
++
+ nw_port_ops_t nw_port_ops_vlan = {
++	.kind = "vlan",
++
+ 	// Configuration
+ 	.config_read = nw_port_vlan_config_read,
+ 	.config_write = nw_port_vlan_config_write,
++
++	.get_parent_port = nw_port_get_vlan_parent,
++
++	// Link
++	.create_link = nw_port_vlan_create_link,
+ };
+ 
+ /*
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index b3b7d66..a7cbab6 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -72,7 +72,7 @@ static int nw_port_setup_address(nw_port* port) {
+ 	// Read ADDRESS from configuration
+ 	const char* s = nw_config_get(port->config, "ADDRESS");
+ 	if (!s) {
+-		ERROR("Port %s: Address isn't set\n", port->name);
++		ERROR("Port %s: Address is not set\n", port->name);
+ 		goto ERROR;
+ 	}
+ 
+@@ -271,6 +271,16 @@ nw_port* nw_port_unref(nw_port* 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) {
+ 	int r;
+ 
+@@ -395,6 +405,17 @@ int __nw_port_drop_link(nw_daemon* daemon, nw_port* port, void* data) {
+ 	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;
+ }
+@@ -403,19 +424,129 @@ static int nw_port_is_disabled(nw_port* port) {
+ 	return nw_config_get_bool(port->config, "DISABLED");
+ }
+ 
++static nw_link* nw_port_get_parent_link(nw_port* port) {
++	nw_port* parent = NULL;
++	nw_link* link = NULL;
++
++	// Do nothing if not implemented
++	if (!port->ops.get_parent_port)
++		goto ERROR;
++
++	// Fetch the parent
++	parent = port->ops.get_parent_port(port);
++	if (!parent)
++		goto ERROR;
++
++	// Fetch the link
++	link = nw_port_get_link(parent);
++
++ERROR:
++	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;
+ 
+-	// Fail if the function isn't set
+-	if (!port->ops.create_link) {
+-		errno = ENOTSUP;
+-		return -errno;
++	sd_netlink* rtnl = nw_daemon_get_rtnl(port->daemon);
++
++	// 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;
+ 	}
+ 
+-	// Create the link
+-	r = port->ops.create_link(port);
+-	if (r)
+-		ERROR("Could not create link %s: %m\n", port->name);
++	// 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 MAC address, etc.
++
++	// 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 (port->ops.create_link) {
++		r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, port->ops.kind);
++		if (r < 0) {
++			ERROR("Could not open IFLA_INFO_DATA container: %s\n", strerror(-r));
++			goto ERROR;
++		}
++
++		r = port->ops.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, port->ops.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;
+ }
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 5253d69..d44ecb6 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -21,6 +21,8 @@
+ #ifndef NETWORKD_PORT_H
+ #define NETWORKD_PORT_H
+ 
++#include <systemd/sd-netlink.h>
++
+ #ifndef IF_NAMESIZE
+ #define IF_NAMESIZE 16
+ #endif
+@@ -45,12 +47,18 @@ typedef struct nw_port nw_port;
+ #include "daemon.h"
+ 
+ typedef struct nw_port_ops {
++	// IFLA_INFO_KIND/IFLA_INFO_DATA
++	const char* kind;
++
+ 	// Configuration
+ 	int (*config_read)(nw_port* port);
+ 	int (*config_write)(nw_port* port);
+ 
++	// Get Parent Port
++	nw_port* (*get_parent_port)(nw_port* port);
++
+ 	// Link
+-	int (*create_link)(nw_port* port);
++	int (*create_link)(nw_port* port, sd_netlink_message* message);
+ 	int (*destroy_link)(nw_port* port);
+ } nw_port_ops_t;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0240-ports-Rename-the-ops-struct-as-we-will-need-to-store.patch b/network/patches/0240-ports-Rename-the-ops-struct-as-we-will-need-to-store.patch
new file mode 100644
index 000000000..45390b028
--- /dev/null
+++ b/network/patches/0240-ports-Rename-the-ops-struct-as-we-will-need-to-store.patch
@@ -0,0 +1,219 @@
+From 89f8f6af09b8ebf897ca6f91a3abb3b3f40fcaad Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 16:42:11 +0000
+Subject: [PATCH 240/304] ports: Rename the ops struct as we will need to store
+ more things than function pointers
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-dummy.c |  2 +-
+ src/networkd/port-dummy.h |  2 +-
+ src/networkd/port-vlan.c  | 17 ++++++++++-------
+ src/networkd/port-vlan.h  |  2 +-
+ src/networkd/port.c       | 24 ++++++++++++------------
+ src/networkd/port.h       | 27 ++++++++++++++++-----------
+ 6 files changed, 41 insertions(+), 33 deletions(-)
+
+diff --git a/src/networkd/port-dummy.c b/src/networkd/port-dummy.c
+index 09367d8..d6842d0 100644
+--- a/src/networkd/port-dummy.c
++++ b/src/networkd/port-dummy.c
+@@ -20,6 +20,6 @@
+ 
+ #include "port-dummy.h"
+ 
+-nw_port_ops_t nw_port_ops_dummy = {
++nw_port_info_t nw_port_info_dummy = {
+ 	.kind = "dummy",
+ };
+diff --git a/src/networkd/port-dummy.h b/src/networkd/port-dummy.h
+index 0a29c84..b07cf39 100644
+--- a/src/networkd/port-dummy.h
++++ b/src/networkd/port-dummy.h
+@@ -23,6 +23,6 @@
+ 
+ #include "port.h"
+ 
+-extern nw_port_ops_t nw_port_ops_dummy;
++extern nw_port_info_t nw_port_info_dummy;
+ 
+ #endif /* NETWORKD_PORT_DUMMY_H */
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index edf91d9..f1cd9fb 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -77,17 +77,20 @@ static int nw_port_vlan_create_link(nw_port* port, sd_netlink_message* m) {
+ 	return 0;
+ }
+ 
+-nw_port_ops_t nw_port_ops_vlan = {
++nw_port_info_t nw_port_info_vlan = {
+ 	.kind = "vlan",
+ 
+-	// Configuration
+-	.config_read = nw_port_vlan_config_read,
+-	.config_write = nw_port_vlan_config_write,
++	// Operations
++	.ops = {
++		// Configuration
++		.config_read = nw_port_vlan_config_read,
++		.config_write = nw_port_vlan_config_write,
+ 
+-	.get_parent_port = nw_port_get_vlan_parent,
++		.get_parent_port = nw_port_get_vlan_parent,
+ 
+-	// Link
+-	.create_link = nw_port_vlan_create_link,
++		// Link
++		.create_link = nw_port_vlan_create_link,
++	},
+ };
+ 
+ /*
+diff --git a/src/networkd/port-vlan.h b/src/networkd/port-vlan.h
+index 2bacb24..c666dfb 100644
+--- a/src/networkd/port-vlan.h
++++ b/src/networkd/port-vlan.h
+@@ -23,7 +23,7 @@
+ 
+ #include "port.h"
+ 
+-extern nw_port_ops_t nw_port_ops_vlan;
++extern nw_port_info_t nw_port_info_vlan;
+ 
+ // ID
+ int nw_port_get_vlan_id(nw_port* port);
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index a7cbab6..061e13c 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -168,8 +168,8 @@ static int nw_port_setup(nw_port* port) {
+ 		goto ERROR;
+ 
+ 	// Call any custom initialization
+-	if (port->ops.config_read) {
+-		r = port->ops.config_read(port);
++	if (NW_PORT_OPS(port)->config_read) {
++		r = NW_PORT_OPS(port)->config_read(port);
+ 		if (r)
+ 			goto ERROR;
+ 	}
+@@ -201,11 +201,11 @@ int nw_port_create(nw_port** port, nw_daemon* daemon, nw_port_type_t type, const
+ 	// Set operations
+ 	switch (p->type) {
+ 		case NW_PORT_DUMMY:
+-			p->ops = nw_port_ops_dummy;
++			p->info = &nw_port_info_dummy;
+ 			break;
+ 
+ 		case NW_PORT_VLAN:
+-			p->ops = nw_port_ops_vlan;
++			p->info = &nw_port_info_vlan;
+ 			break;
+ 	}
+ 
+@@ -339,8 +339,8 @@ int nw_port_save(nw_port* port) {
+ 	int r;
+ 
+ 	// Call the custom handler
+-	if (port->ops.config_write) {
+-		r = port->ops.config_write(port);
++	if (NW_PORT_OPS(port)->config_write) {
++		r = NW_PORT_OPS(port)->config_write(port);
+ 		if (r)
+ 			goto ERROR;
+ 	}
+@@ -429,11 +429,11 @@ static nw_link* nw_port_get_parent_link(nw_port* port) {
+ 	nw_link* link = NULL;
+ 
+ 	// Do nothing if not implemented
+-	if (!port->ops.get_parent_port)
++	if (!NW_PORT_OPS(port)->get_parent_port)
+ 		goto ERROR;
+ 
+ 	// Fetch the parent
+-	parent = port->ops.get_parent_port(port);
++	parent = NW_PORT_OPS(port)->get_parent_port(port);
+ 	if (!parent)
+ 		goto ERROR;
+ 
+@@ -502,14 +502,14 @@ static int nw_port_create_link(nw_port* port) {
+ 		goto ERROR;
+ 
+ 	// Run the custom setup
+-	if (port->ops.create_link) {
+-		r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, port->ops.kind);
++	if (NW_PORT_OPS(port)->create_link) {
++		r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, NW_PORT_INFO(port)->kind);
+ 		if (r < 0) {
+ 			ERROR("Could not open IFLA_INFO_DATA container: %s\n", strerror(-r));
+ 			goto ERROR;
+ 		}
+ 
+-		r = port->ops.create_link(port, m);
++		r = NW_PORT_OPS(port)->create_link(port, m);
+ 		if (r) {
+ 			ERROR("Could not create port %s: %m\n", port->name);
+ 			goto ERROR;
+@@ -522,7 +522,7 @@ static int nw_port_create_link(nw_port* port) {
+ 
+ 	// Just set IFLA_INFO_KIND if there is no custom function
+ 	} else {
+-		r = sd_netlink_message_append_string(m, IFLA_INFO_KIND, port->ops.kind);
++		r = sd_netlink_message_append_string(m, IFLA_INFO_KIND, NW_PORT_INFO(port)->kind);
+ 		if (r < 0)
+ 			goto ERROR;
+ 	}
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index d44ecb6..b73c5fe 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -46,21 +46,26 @@ typedef struct nw_port nw_port;
+ #include "config.h"
+ #include "daemon.h"
+ 
+-typedef struct nw_port_ops {
++typedef struct nw_port_info {
+ 	// IFLA_INFO_KIND/IFLA_INFO_DATA
+ 	const char* kind;
+ 
+-	// Configuration
+-	int (*config_read)(nw_port* port);
+-	int (*config_write)(nw_port* port);
++	struct nw_port_ops {
++		// Configuration
++		int (*config_read)(nw_port* port);
++		int (*config_write)(nw_port* port);
+ 
+-	// Get Parent Port
+-	nw_port* (*get_parent_port)(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);
+-} nw_port_ops_t;
++		// Link
++		int (*create_link)(nw_port* port, sd_netlink_message* message);
++		int (*destroy_link)(nw_port* port);
++	} ops;
++} nw_port_info_t;
++
++#define NW_PORT_INFO(port) (port->info)
++#define NW_PORT_OPS(port) (&NW_PORT_INFO(port)->ops)
+ 
+ struct nw_port {
+ 	nw_daemon* daemon;
+@@ -79,7 +84,7 @@ struct nw_port {
+ 	nw_address_t address;
+ 
+ 	// Type Operations
+-	nw_port_ops_t ops;
++	nw_port_info_t* info;
+ 
+ 	// VLAN settings
+ 	struct nw_port_vlan {
+-- 
+2.39.2
+
diff --git a/network/patches/0241-ports-Implement-listing-ports-over-DBus.patch b/network/patches/0241-ports-Implement-listing-ports-over-DBus.patch
new file mode 100644
index 000000000..8c08bc270
--- /dev/null
+++ b/network/patches/0241-ports-Implement-listing-ports-over-DBus.patch
@@ -0,0 +1,256 @@
+From 4c99b8ed5da20ba81845486bbcb9a841b7cd2119 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 16:50:35 +0000
+Subject: [PATCH 241/304] ports: Implement listing ports over DBus
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am               |  2 +
+ src/networkctl/main.c     |  2 +
+ src/networkctl/port.c     | 97 +++++++++++++++++++++++++++++++++++++++
+ src/networkctl/port.h     | 26 +++++++++++
+ src/networkd/daemon-bus.c | 51 ++++++++++++++++++++
+ 5 files changed, 178 insertions(+)
+ create mode 100644 src/networkctl/port.c
+ create mode 100644 src/networkctl/port.h
+
+diff --git a/Makefile.am b/Makefile.am
+index aafa59c..273302b 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -392,6 +392,8 @@ 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/zone.c \
+ 	src/networkctl/zone.h
+ 
+diff --git a/src/networkctl/main.c b/src/networkctl/main.c
+index a08256c..73908f8 100644
+--- a/src/networkctl/main.c
++++ b/src/networkctl/main.c
+@@ -26,6 +26,7 @@
+ #include <systemd/sd-bus.h>
+ 
+ #include "command.h"
++#include "port.h"
+ #include "zone.h"
+ 
+ static int networkctl_status(sd_bus* bus, int argc, char* argv[]) {
+@@ -35,6 +36,7 @@ static int networkctl_status(sd_bus* bus, int argc, char* argv[]) {
+ 
+ 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 },
+diff --git a/src/networkctl/port.c b/src/networkctl/port.c
+new file mode 100644
+index 0000000..689e2f1
+--- /dev/null
++++ b/src/networkctl/port.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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <systemd/sd-bus.h>
++
++#include "command.h"
++#include "port.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_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);
++}
++
++int networkctl_port(sd_bus* bus, int argc, char* argv[]) {
++	static const struct command commands[] = {
++		{ "list", 0, networkctl_port_list },
++		{ 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 0000000..2326ce6
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKCTL_PORT_H
++#define NETWORKCTL_PORT_H
++
++int networkctl_port(sd_bus* bus, int argc, char* argv[]);
++
++#endif /* NETWORKCTL_PORT_H */
+diff --git a/src/networkd/daemon-bus.c b/src/networkd/daemon-bus.c
+index 7620eed..4de8c0f 100644
+--- a/src/networkd/daemon-bus.c
++++ b/src/networkd/daemon-bus.c
+@@ -39,6 +39,55 @@ static int nw_daemon_bus_reload(sd_bus_message* m, void* data, sd_bus_error* err
+ 	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);
++
++	r = sd_bus_message_append(reply, "(so)", name, 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;
+@@ -90,6 +139,8 @@ ERROR:
+ 
+ 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,
+-- 
+2.39.2
+
diff --git a/network/patches/0242-daemon-Fix-return-code-handling-when-listing-ports-z.patch b/network/patches/0242-daemon-Fix-return-code-handling-when-listing-ports-z.patch
new file mode 100644
index 000000000..ad4948402
--- /dev/null
+++ b/network/patches/0242-daemon-Fix-return-code-handling-when-listing-ports-z.patch
@@ -0,0 +1,56 @@
+From 78b41a2319c3c247a0223187edaea4c9ba98e83b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 16:52:59 +0000
+Subject: [PATCH 242/304] daemon: Fix return code handling when listing
+ ports/zones
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon-bus.c | 20 ++++++++++++++++++--
+ 1 file changed, 18 insertions(+), 2 deletions(-)
+
+diff --git a/src/networkd/daemon-bus.c b/src/networkd/daemon-bus.c
+index 4de8c0f..f5f7abd 100644
+--- a/src/networkd/daemon-bus.c
++++ b/src/networkd/daemon-bus.c
+@@ -49,9 +49,17 @@ static int __nw_daemon_bus_list_ports(nw_daemon* daemon, nw_port* port, void* da
+ 	// 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;
+ 
+-	free(path);
++	// Success
++	r = 0;
++
++ERROR:
++	if (path)
++		free(path);
+ 
+ 	return r;
+ }
+@@ -98,9 +106,17 @@ static int __nw_daemon_bus_list_zones(nw_daemon* daemon, nw_zone* zone, void* da
+ 	// 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;
+ 
+-	free(path);
++	// Success
++	r = 0;
++
++ERROR:
++	if (path)
++		free(path);
+ 
+ 	return r;
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0243-ports-Do-not-expect-to-come-back-after-creating-link.patch b/network/patches/0243-ports-Do-not-expect-to-come-back-after-creating-link.patch
new file mode 100644
index 000000000..53d7548f5
--- /dev/null
+++ b/network/patches/0243-ports-Do-not-expect-to-come-back-after-creating-link.patch
@@ -0,0 +1,32 @@
+From 895f7134d55c38f3059e88e128d5670187222e69 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 17:05:06 +0000
+Subject: [PATCH 243/304] ports: Do not expect to come back after creating
+ links
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c | 7 ++-----
+ 1 file changed, 2 insertions(+), 5 deletions(-)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 061e13c..c87ed59 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -566,11 +566,8 @@ int nw_port_reconfigure(nw_port* port) {
+ 	}
+ 
+ 	// If there is no link, we will try to create it
+-	if (!port->link) {
+-		r = nw_port_create_link(port);
+-		if (r)
+-			return r;
+-	}
++	if (!port->link)
++		return nw_port_create_link(port);
+ 
+ 	// XXX TODO
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0244-ports-Set-the-configure-MAC-address-when-creating-li.patch b/network/patches/0244-ports-Set-the-configure-MAC-address-when-creating-li.patch
new file mode 100644
index 000000000..f979cdc40
--- /dev/null
+++ b/network/patches/0244-ports-Set-the-configure-MAC-address-when-creating-li.patch
@@ -0,0 +1,34 @@
+From 1574bc0061fa712150316d7bc77954f3264363f8 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 17:09:01 +0000
+Subject: [PATCH 244/304] ports: Set the configure MAC address when creating
+ links
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c | 9 ++++++++-
+ 1 file changed, 8 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index c87ed59..891b831 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -486,7 +486,14 @@ static int nw_port_create_link(nw_port* port) {
+ 		goto ERROR;
+ 	}
+ 
+-	// XXX Set common things like MAC address, etc.
++	// XXX Set common things like MTU, etc.
++
++	// Set MAC 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);
+-- 
+2.39.2
+
diff --git a/network/patches/0245-ports-Show-message-when-creating-ports.patch b/network/patches/0245-ports-Show-message-when-creating-ports.patch
new file mode 100644
index 000000000..ee3cf696a
--- /dev/null
+++ b/network/patches/0245-ports-Show-message-when-creating-ports.patch
@@ -0,0 +1,26 @@
+From 575d1a3f407c29ab5b388d11ba94424adb86423a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 17:10:34 +0000
+Subject: [PATCH 245/304] ports: Show message when creating ports
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 891b831..dfce5b6 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -472,6 +472,8 @@ static int nw_port_create_link(nw_port* port) {
+ 
+ 	sd_netlink* rtnl = nw_daemon_get_rtnl(port->daemon);
+ 
++	DEBUG("Creating port %s...\n", port->name);
++
+ 	// Create a new link
+ 	r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0);
+ 	if (r < 0) {
+-- 
+2.39.2
+
diff --git a/network/patches/0246-ports-Constify-info-struct.patch b/network/patches/0246-ports-Constify-info-struct.patch
new file mode 100644
index 000000000..5cc786617
--- /dev/null
+++ b/network/patches/0246-ports-Constify-info-struct.patch
@@ -0,0 +1,80 @@
+From 5ddde002bc70617e45632ddc3dfdcf792a2ee745 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 17:12:52 +0000
+Subject: [PATCH 246/304] ports: Constify info struct
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-dummy.c | 2 +-
+ src/networkd/port-dummy.h | 2 +-
+ src/networkd/port-vlan.c  | 2 +-
+ src/networkd/port-vlan.h  | 2 +-
+ src/networkd/port.h       | 2 +-
+ 5 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/src/networkd/port-dummy.c b/src/networkd/port-dummy.c
+index d6842d0..e71e740 100644
+--- a/src/networkd/port-dummy.c
++++ b/src/networkd/port-dummy.c
+@@ -20,6 +20,6 @@
+ 
+ #include "port-dummy.h"
+ 
+-nw_port_info_t nw_port_info_dummy = {
++const nw_port_info_t nw_port_info_dummy = {
+ 	.kind = "dummy",
+ };
+diff --git a/src/networkd/port-dummy.h b/src/networkd/port-dummy.h
+index b07cf39..b74c991 100644
+--- a/src/networkd/port-dummy.h
++++ b/src/networkd/port-dummy.h
+@@ -23,6 +23,6 @@
+ 
+ #include "port.h"
+ 
+-extern nw_port_info_t nw_port_info_dummy;
++extern const nw_port_info_t nw_port_info_dummy;
+ 
+ #endif /* NETWORKD_PORT_DUMMY_H */
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index f1cd9fb..5e970e9 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -77,7 +77,7 @@ static int nw_port_vlan_create_link(nw_port* port, sd_netlink_message* m) {
+ 	return 0;
+ }
+ 
+-nw_port_info_t nw_port_info_vlan = {
++const nw_port_info_t nw_port_info_vlan = {
+ 	.kind = "vlan",
+ 
+ 	// Operations
+diff --git a/src/networkd/port-vlan.h b/src/networkd/port-vlan.h
+index c666dfb..f02c529 100644
+--- a/src/networkd/port-vlan.h
++++ b/src/networkd/port-vlan.h
+@@ -23,7 +23,7 @@
+ 
+ #include "port.h"
+ 
+-extern nw_port_info_t nw_port_info_vlan;
++extern const nw_port_info_t nw_port_info_vlan;
+ 
+ // ID
+ int nw_port_get_vlan_id(nw_port* port);
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index b73c5fe..8bc4db8 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -84,7 +84,7 @@ struct nw_port {
+ 	nw_address_t address;
+ 
+ 	// Type Operations
+-	nw_port_info_t* info;
++	const nw_port_info_t* info;
+ 
+ 	// VLAN settings
+ 	struct nw_port_vlan {
+-- 
+2.39.2
+
diff --git a/network/patches/0247-ports-Log-when-we-created-a-random-Ethernet-address.patch b/network/patches/0247-ports-Log-when-we-created-a-random-Ethernet-address.patch
new file mode 100644
index 000000000..df9d75b6a
--- /dev/null
+++ b/network/patches/0247-ports-Log-when-we-created-a-random-Ethernet-address.patch
@@ -0,0 +1,50 @@
+From d667fff47beb1fbdf570006b112e64500528a139 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 4 Jun 2023 17:19:19 +0000
+Subject: [PATCH 247/304] ports: Log when we created a random Ethernet address
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index dfce5b6..d5ffe42 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -67,6 +67,7 @@ static void nw_port_free(nw_port* port) {
+ }
+ 
+ static int nw_port_setup_address(nw_port* port) {
++	char* __address = NULL;
+ 	int r;
+ 
+ 	// Read ADDRESS from configuration
+@@ -100,6 +101,15 @@ ERROR:
+ 		return r;
+ 	}
+ 
++	// Format the generated address
++	__address = nw_address_to_string(&port->address);
++	if (__address) {
++		ERROR("Generated a random Ethernet address for %s: %s\n", port->name, __address);
++
++		// Free the address
++		free(__address);
++	}
++
+ 	return 0;
+ }
+ 
+@@ -490,7 +500,7 @@ static int nw_port_create_link(nw_port* port) {
+ 
+ 	// XXX Set common things like MTU, etc.
+ 
+-	// Set MAC address
++	// 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));
+-- 
+2.39.2
+
diff --git a/network/patches/0248-ports-Add-the-most-basic-supports-for-bonding.patch b/network/patches/0248-ports-Add-the-most-basic-supports-for-bonding.patch
new file mode 100644
index 000000000..8e75993b1
--- /dev/null
+++ b/network/patches/0248-ports-Add-the-most-basic-supports-for-bonding.patch
@@ -0,0 +1,309 @@
+From 95c5dca23c2fa6326cdc59736f48f20e40ed3561 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 5 Jun 2023 14:51:41 +0000
+Subject: [PATCH 248/304] ports: Add the most basic supports for bonding
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                 |   2 +
+ src/networkd/port-bonding.c | 142 ++++++++++++++++++++++++++++++++++++
+ src/networkd/port-bonding.h |  37 ++++++++++
+ src/networkd/port.c         |   7 ++
+ src/networkd/port.h         |  10 ++-
+ 5 files changed, 196 insertions(+), 2 deletions(-)
+ create mode 100644 src/networkd/port-bonding.c
+ create mode 100644 src/networkd/port-bonding.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 273302b..ad42f49 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -330,6 +330,8 @@ dist_networkd_SOURCES = \
+ 	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 \
+diff --git a/src/networkd/port-bonding.c b/src/networkd/port-bonding.c
+new file mode 100644
+index 0000000..6bddc43
+--- /dev/null
++++ b/src/networkd/port-bonding.c
+@@ -0,0 +1,142 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <systemd/sd-netlink.h>
++
++#include "config.h"
++#include "daemon.h"
++#include "logging.h"
++#include "port.h"
++#include "port-bonding.h"
++#include "string.h"
++
++const struct nw_port_bonding_mode {
++	const int mode;
++	const char* string;
++} nw_port_bonding_modes[] = {
++	{ BOND_MODE_ROUNDROBIN,   "round-robin" },
++	{ BOND_MODE_ACTIVEBACKUP, "active-backup" },
++	{ BOND_MODE_XOR,          "xor" },
++	{ BOND_MODE_BROADCAST,    "broadcast" },
++	{ BOND_MODE_8023AD,       "802.3ad" },
++	{ BOND_MODE_TLB,          "tlb" },
++	{ BOND_MODE_ALB,          "alb" },
++	{ -1, NULL },
++};
++
++static int nw_port_bonding_mode_from_string(const char* string) {
++	const struct nw_port_bonding_mode* m = NULL;
++
++	for (m = nw_port_bonding_modes; m->string; m++) {
++		if (strcmp(m->string, string) == 0)
++			return m->mode;
++	}
++
++	return -1;
++}
++
++static const char* nw_port_bonding_mode_to_string(const int mode) {
++	const struct nw_port_bonding_mode* m = NULL;
++
++	for (m = nw_port_bonding_modes; m->string; m++) {
++		if (m->mode == mode)
++			return m->string;
++	}
++
++	return NULL;
++}
++
++static int nw_port_bonding_config_read(nw_port* port) {
++	int r;
++
++	// Mode
++	const char* mode = nw_config_get(port->config, "BONDING_MODE");
++	if (mode) {
++		r = nw_port_bonding_set_mode(port, mode);
++		if (r)
++			return r;
++	}
++
++	return 0;
++}
++
++static int nw_port_bonding_config_write(nw_port* port) {
++	int r;
++
++	// Mode
++	r = nw_config_set(port->config, "BONDING_MODE",
++			nw_port_bonding_mode_to_string(port->bonding.mode));
++	if (r)
++		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;
++}
++
++const nw_port_info_t nw_port_info_bonding = {
++	.kind = "bond",
++
++	// Operations
++	.ops = {
++		// Configuration
++		.config_read = nw_port_bonding_config_read,
++		.config_write = nw_port_bonding_config_write,
++
++		// Link
++		.create_link = nw_port_bonding_create_link,
++	},
++};
++
++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 BOND_MODE_ROUNDROBIN:
++		case BOND_MODE_ACTIVEBACKUP:
++		case BOND_MODE_XOR:
++		case BOND_MODE_BROADCAST:
++		case BOND_MODE_8023AD:
++		case BOND_MODE_TLB:
++		case BOND_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 0000000..5cd2c43
+--- /dev/null
++++ b/src/networkd/port-bonding.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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_PORT_BONDING_H
++#define NETWORKD_PORT_BONDING_H
++
++#include <linux/if_bonding.h>
++
++#include "port.h"
++
++struct nw_port_bonding {
++	int mode;
++};
++
++extern const nw_port_info_t nw_port_info_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.c b/src/networkd/port.c
+index d5ffe42..b44cc88 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -30,6 +30,7 @@
+ #include "link.h"
+ #include "logging.h"
+ #include "port.h"
++#include "port-bonding.h"
+ #include "port-dummy.h"
+ #include "port-vlan.h"
+ #include "stats-collector.h"
+@@ -39,6 +40,7 @@ static const struct nw_port_type_map {
+ 	nw_port_type_t type;
+ 	const char* name;
+ } nw_port_type_map[] = {
++	{ NW_PORT_BONDING, "bonding" },
+ 	{ NW_PORT_DUMMY,   "dummy" },
+ 	{ NW_PORT_VLAN,    "vlan" },
+ 	{ NW_PORT_UNKNOWN, NULL },
+@@ -210,6 +212,10 @@ int nw_port_create(nw_port** port, nw_daemon* daemon, nw_port_type_t type, const
+ 
+ 	// Set operations
+ 	switch (p->type) {
++		case NW_PORT_BONDING:
++			p->info = &nw_port_info_bonding;
++			break;
++
+ 		case NW_PORT_DUMMY:
+ 			p->info = &nw_port_info_dummy;
+ 			break;
+@@ -337,6 +343,7 @@ int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
+ 			}
+ 			break;
+ 
++		case NW_PORT_BONDING:
+ 		case NW_PORT_DUMMY:
+ 		case NW_PORT_UNKNOWN:
+ 			break;
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 8bc4db8..f8beed3 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -31,6 +31,7 @@
+ 
+ typedef enum nw_port_type {
+ 	NW_PORT_UNKNOWN = 0,
++	NW_PORT_BONDING,
+ 	NW_PORT_DUMMY,
+ 	NW_PORT_VLAN,
+ } nw_port_type_t;
+@@ -41,12 +42,14 @@ typedef enum nw_port_type {
+ #define NW_VLAN_ID_MAX			4096
+ 
+ typedef struct nw_port nw_port;
++typedef struct nw_port_info nw_port_info_t;
+ 
+ #include "address.h"
+ #include "config.h"
+ #include "daemon.h"
++#include "port-bonding.h"
+ 
+-typedef struct nw_port_info {
++struct nw_port_info {
+ 	// IFLA_INFO_KIND/IFLA_INFO_DATA
+ 	const char* kind;
+ 
+@@ -62,7 +65,7 @@ typedef struct nw_port_info {
+ 		int (*create_link)(nw_port* port, sd_netlink_message* message);
+ 		int (*destroy_link)(nw_port* port);
+ 	} ops;
+-} nw_port_info_t;
++};
+ 
+ #define NW_PORT_INFO(port) (port->info)
+ #define NW_PORT_OPS(port) (&NW_PORT_INFO(port)->ops)
+@@ -86,6 +89,9 @@ struct nw_port {
+ 	// Type Operations
+ 	const nw_port_info_t* info;
+ 
++	// Bonding Settings
++	struct nw_port_bonding bonding;
++
+ 	// VLAN settings
+ 	struct nw_port_vlan {
+ 		nw_port* parent;
+-- 
+2.39.2
+
diff --git a/network/patches/0249-ports-Move-VLAN-settings-into-its-own-header-file.patch b/network/patches/0249-ports-Move-VLAN-settings-into-its-own-header-file.patch
new file mode 100644
index 000000000..3772a8caf
--- /dev/null
+++ b/network/patches/0249-ports-Move-VLAN-settings-into-its-own-header-file.patch
@@ -0,0 +1,64 @@
+From 4d6ac814d5a46ca08a6f3cd6a1431a17cd65bc3e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 5 Jun 2023 14:54:20 +0000
+Subject: [PATCH 249/304] ports: Move VLAN settings into its own header file
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-vlan.h | 11 +++++++++++
+ src/networkd/port.h      | 10 ++--------
+ 2 files changed, 13 insertions(+), 8 deletions(-)
+
+diff --git a/src/networkd/port-vlan.h b/src/networkd/port-vlan.h
+index f02c529..cffc178 100644
+--- a/src/networkd/port-vlan.h
++++ b/src/networkd/port-vlan.h
+@@ -23,6 +23,17 @@
+ 
+ #include "port.h"
+ 
++struct nw_port_vlan {
++	nw_port* parent;
++
++	// The VLAN ID
++	int id;
++
++	// If the parent has not been read from the configuration we will
++	// save the name and try to find it later.
++	char __parent_name[IF_NAMESIZE];
++};
++
+ extern const nw_port_info_t nw_port_info_vlan;
+ 
+ // ID
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index f8beed3..21e2a12 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -48,6 +48,7 @@ typedef struct nw_port_info nw_port_info_t;
+ #include "config.h"
+ #include "daemon.h"
+ #include "port-bonding.h"
++#include "port-vlan.h"
+ 
+ struct nw_port_info {
+ 	// IFLA_INFO_KIND/IFLA_INFO_DATA
+@@ -93,14 +94,7 @@ struct nw_port {
+ 	struct nw_port_bonding bonding;
+ 
+ 	// VLAN settings
+-	struct nw_port_vlan {
+-		nw_port* parent;
+-		int id;
+-
+-		// If the parent has not been read from the configuration we will
+-		// save the name and try to find it later.
+-		char __parent_name[IF_NAMESIZE];
+-	} vlan;
++	struct nw_port_vlan vlan;
+ };
+ 
+ int nw_port_create(nw_port** port, nw_daemon* daemon,
+-- 
+2.39.2
+
diff --git a/network/patches/0250-networkctl-Fix-typo-in-bus-method-name.patch b/network/patches/0250-networkctl-Fix-typo-in-bus-method-name.patch
new file mode 100644
index 000000000..89c6ff5ea
--- /dev/null
+++ b/network/patches/0250-networkctl-Fix-typo-in-bus-method-name.patch
@@ -0,0 +1,26 @@
+From 0744719e820e526593c5c7c0b4b86ce756509333 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 5 Jun 2023 14:55:00 +0000
+Subject: [PATCH 250/304] networkctl: Fix typo in bus method name
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkctl/port.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/networkctl/port.c b/src/networkctl/port.c
+index 689e2f1..3a886ff 100644
+--- a/src/networkctl/port.c
++++ b/src/networkctl/port.c
+@@ -36,7 +36,7 @@ static int networkctl_port_walk(sd_bus* bus,
+ 	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");
++		fprintf(stderr, "ListPorts call failed: %m\n");
+ 		goto ERROR;
+ 	}
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0251-ports-Implement-a-function-the-generally-fetches-the.patch b/network/patches/0251-ports-Implement-a-function-the-generally-fetches-the.patch
new file mode 100644
index 000000000..bc749276e
--- /dev/null
+++ b/network/patches/0251-ports-Implement-a-function-the-generally-fetches-the.patch
@@ -0,0 +1,163 @@
+From 5a5c346c5dac343ec8242adcd04f1fbe542640ff Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 5 Jun 2023 16:47:14 +0000
+Subject: [PATCH 251/304] ports: Implement a function the generally fetches the
+ parent port
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/json.h | 89 +++++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/port.c | 17 +++++----
+ src/networkd/port.h |  2 +
+ 3 files changed, 101 insertions(+), 7 deletions(-)
+ create mode 100644 src/networkd/json.h
+
+diff --git a/src/networkd/json.h b/src/networkd/json.h
+new file mode 100644
+index 0000000..33e237a
+--- /dev/null
++++ b/src/networkd/json.h
+@@ -0,0 +1,89 @@
++/*#############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_JSON_H
++#define NETWORKD_JSON_H
++
++#include <errno.h>
++
++#include <json.h>
++
++// Give some sane names to the reference count functions
++#define json_object_ref json_object_get
++#define json_object_unref json_object_put
++
++static inline int __json_object_add(struct json_object* o,
++		const char* key, struct json_object* value) {
++	int r;
++
++	// Add the object
++	r = json_object_object_add(o, key, value);
++	if (r < 0) {
++		if (value)
++			json_object_unref(value);
++	}
++
++	return r;
++}
++
++static inline int json_object_add_string(struct json_object* o,
++		const char* key, const char* value) {
++	struct json_object* element = NULL;
++
++	// Create a JSON object from the string
++	element = json_object_new_string(value);
++	if (!element)
++		return -errno;
++
++	return __json_object_add(o, key, element);
++}
++
++static inline int json_object_add_int64(struct json_object* o,
++		const char* key, const int64_t value) {
++	struct json_object* element = NULL;
++
++	// Create a JSON object
++	element = json_object_new_int64(value);
++	if (!element)
++		return -errno;
++
++	return __json_object_add(o, key, element);
++}
++
++static inline int json_to_string(struct json_object* o, char** s, size_t* l) {
++	// Format JSON to string
++	const char* buffer = json_object_to_json_string_ext(o,
++		JSON_C_TO_STRING_PRETTY|JSON_C_TO_STRING_PRETTY_TAB);
++	if (!buffer)
++		return -errno;
++
++	// Copy the string to the heap
++	*s = strdup(buffer);
++	if (!*s)
++		return -errno;
++
++	// If requested, store the length of the string
++	if (l)
++		*l = strlen(*s);
++
++	return 0;
++}
++
++#endif /* NETWORKD_JSON_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index b44cc88..c9440d4 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -441,23 +441,26 @@ 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_OPS(port)->get_parent_port)
++		return NULL;
++
++	return NW_PORT_OPS(port)->get_parent_port(port);
++}
++
+ static nw_link* nw_port_get_parent_link(nw_port* port) {
+ 	nw_port* parent = NULL;
+ 	nw_link* link = NULL;
+ 
+-	// Do nothing if not implemented
+-	if (!NW_PORT_OPS(port)->get_parent_port)
+-		goto ERROR;
+-
+ 	// Fetch the parent
+-	parent = NW_PORT_OPS(port)->get_parent_port(port);
++	parent = nw_port_get_parent_port(port);
+ 	if (!parent)
+-		goto ERROR;
++		return NULL;
+ 
+ 	// Fetch the link
+ 	link = nw_port_get_link(parent);
+ 
+-ERROR:
++	// Cleanup
+ 	if (parent)
+ 		nw_port_unref(parent);
+ 
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 21e2a12..38d19d1 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -119,6 +119,8 @@ 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);
+-- 
+2.39.2
+
diff --git a/network/patches/0252-config-Compare-truthiness-case-insensitively.patch b/network/patches/0252-config-Compare-truthiness-case-insensitively.patch
new file mode 100644
index 000000000..528a6ef4f
--- /dev/null
+++ b/network/patches/0252-config-Compare-truthiness-case-insensitively.patch
@@ -0,0 +1,26 @@
+From 8923434ceb4264fea6044564ca7254bd28cf106e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 5 Jun 2023 16:47:41 +0000
+Subject: [PATCH 252/304] config: Compare truthiness case-insensitively
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 8269ede..78448df 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -407,7 +407,7 @@ int nw_config_get_bool(nw_config* config, const char* key) {
+ 
+ 	// Check if we match any known true words
+ 	for (const char** s = nw_config_true; *s; s++) {
+-		if (strcmp(value, *s) == 0)
++		if (strcasecmp(value, *s) == 0)
+ 			return 1;
+ 	}
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0253-configure-Depend-on-JSON-C.patch b/network/patches/0253-configure-Depend-on-JSON-C.patch
new file mode 100644
index 000000000..0e485e0a9
--- /dev/null
+++ b/network/patches/0253-configure-Depend-on-JSON-C.patch
@@ -0,0 +1,46 @@
+From 6384cfdf37f873154f695f6cac67fe1926b5bad8 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 5 Jun 2023 16:48:30 +0000
+Subject: [PATCH 253/304] configure: Depend on JSON-C
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am  | 2 ++
+ configure.ac | 1 +
+ 2 files changed, 3 insertions(+)
+
+diff --git a/Makefile.am b/Makefile.am
+index ad42f49..466ad73 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -357,6 +357,7 @@ networkd_CPPFLAGS = \
+ networkd_CFLAGS = \
+ 	$(AM_CFLAGS) \
+ 	$(CAP_CFLAGS) \
++	$(JSON_C_CFLAGS) \
+ 	$(SYSTEMD_CFLAGS)
+ 
+ networkd_LDFLAGS = \
+@@ -365,6 +366,7 @@ networkd_LDFLAGS = \
+ networkd_LDADD = \
+ 	src/libnetwork.la \
+ 	$(CAP_LIBS) \
++	$(JSON_C_LIBS) \
+ 	$(SYSTEMD_LIBS)
+ 
+ dist_dbuspolicy_DATA += \
+diff --git a/configure.ac b/configure.ac
+index e1baa64..7883ae9 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -173,6 +173,7 @@ 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])
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0254-ports-Add-bus-method-to-export-port-information-as-J.patch b/network/patches/0254-ports-Add-bus-method-to-export-port-information-as-J.patch
new file mode 100644
index 000000000..e1650a218
--- /dev/null
+++ b/network/patches/0254-ports-Add-bus-method-to-export-port-information-as-J.patch
@@ -0,0 +1,302 @@
+From e9b0614e1d653a733c76a3246fd800566e3e1561 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Mon, 5 Jun 2023 16:48:43 +0000
+Subject: [PATCH 254/304] ports: Add bus method to export port information as
+ JSON
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                 |  1 +
+ src/networkd/port-bonding.c | 16 +++++++++++
+ src/networkd/port-bus.c     | 46 +++++++++++++++++++++++++++++++
+ src/networkd/port-vlan.c    | 28 +++++++++++++++++++
+ src/networkd/port.c         | 55 +++++++++++++++++++++++++++++++++++++
+ src/networkd/port.h         |  9 ++++++
+ 6 files changed, 155 insertions(+)
+
+diff --git a/Makefile.am b/Makefile.am
+index 466ad73..f4c22b4 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -319,6 +319,7 @@ dist_networkd_SOURCES = \
+ 	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 \
+diff --git a/src/networkd/port-bonding.c b/src/networkd/port-bonding.c
+index 6bddc43..bbc3fb7 100644
+--- a/src/networkd/port-bonding.c
++++ b/src/networkd/port-bonding.c
+@@ -100,6 +100,19 @@ static int nw_port_bonding_create_link(nw_port* port, sd_netlink_message* m) {
+ 	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_info_t nw_port_info_bonding = {
+ 	.kind = "bond",
+ 
+@@ -111,6 +124,9 @@ const nw_port_info_t nw_port_info_bonding = {
+ 
+ 		// Link
+ 		.create_link = nw_port_bonding_create_link,
++
++		// JSON
++		.to_json = nw_port_bonding_to_json,
+ 	},
+ };
+ 
+diff --git a/src/networkd/port-bus.c b/src/networkd/port-bus.c
+index 996be92..41f8ec4 100644
+--- a/src/networkd/port-bus.c
++++ b/src/networkd/port-bus.c
+@@ -20,10 +20,12 @@
+ 
+ #include <errno.h>
+ #include <stdlib.h>
++#include <systemd/sd-bus.h>
+ 
+ #include "address.h"
+ #include "bus.h"
+ #include "daemon.h"
++#include "json.h"
+ #include "logging.h"
+ #include "port.h"
+ #include "port-bus.h"
+@@ -105,6 +107,46 @@ ERROR:
+ 	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),
+ 
+@@ -112,6 +154,10 @@ static const sd_bus_vtable port_vtable[] = {
+ 	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
+ };
+ 
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index 5e970e9..ac4a5b4 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -22,6 +22,7 @@
+ 
+ #include "config.h"
+ #include "daemon.h"
++#include "json.h"
+ #include "logging.h"
+ #include "port.h"
+ #include "port-vlan.h"
+@@ -77,6 +78,30 @@ static int nw_port_vlan_create_link(nw_port* port, sd_netlink_message* m) {
+ 	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;
++
++	// 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_info_t nw_port_info_vlan = {
+ 	.kind = "vlan",
+ 
+@@ -90,6 +115,9 @@ const nw_port_info_t nw_port_info_vlan = {
+ 
+ 		// Link
+ 		.create_link = nw_port_vlan_create_link,
++
++		// JSON
++		.to_json = nw_port_vlan_to_json,
+ 	},
+ };
+ 
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index c9440d4..a7fb826 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -27,6 +27,7 @@
+ 
+ #include "address.h"
+ #include "config.h"
++#include "json.h"
+ #include "link.h"
+ #include "logging.h"
+ #include "port.h"
+@@ -645,3 +646,57 @@ int nw_port_check_type(nw_port* port, const nw_port_type_t type) {
+ 	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_INFO(port)->kind);
++	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;
++	}
++
++	// Call custom stuff
++	if (NW_PORT_OPS(port)->to_json) {
++		r = NW_PORT_OPS(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
+index 38d19d1..e16b957 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -21,6 +21,8 @@
+ #ifndef NETWORKD_PORT_H
+ #define NETWORKD_PORT_H
+ 
++#include <json.h>
++
+ #include <systemd/sd-netlink.h>
+ 
+ #ifndef IF_NAMESIZE
+@@ -47,6 +49,7 @@ typedef struct nw_port_info nw_port_info_t;
+ #include "address.h"
+ #include "config.h"
+ #include "daemon.h"
++#include "json.h"
+ #include "port-bonding.h"
+ #include "port-vlan.h"
+ 
+@@ -65,6 +68,9 @@ struct nw_port_info {
+ 		// 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);
+ 	} ops;
+ };
+ 
+@@ -132,4 +138,7 @@ 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 */
+-- 
+2.39.2
+
diff --git a/network/patches/0255-networkctl-Fix-parsing-commands.patch b/network/patches/0255-networkctl-Fix-parsing-commands.patch
new file mode 100644
index 000000000..77544697d
--- /dev/null
+++ b/network/patches/0255-networkctl-Fix-parsing-commands.patch
@@ -0,0 +1,52 @@
+From d4028bc3f6b422a45f8f2ad41df2e59152474b6e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Tue, 6 Jun 2023 10:24:08 +0000
+Subject: [PATCH 255/304] networkctl: Fix parsing commands
+
+We used to send the argument that was last parsed to the next function
+which probably isn't very useful.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkctl/command.c | 6 +-----
+ src/networkctl/main.c    | 2 +-
+ 2 files changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/src/networkctl/command.c b/src/networkctl/command.c
+index 7114efe..99202dd 100644
+--- a/src/networkctl/command.c
++++ b/src/networkctl/command.c
+@@ -36,10 +36,6 @@ static const struct command* command_find(const struct command* commands, const
+ int command_dispatch(sd_bus* bus, const struct command* commands, int argc, char* argv[]) {
+ 	const struct command* command = NULL;
+ 
+-	argc -= optind;
+-	argv += optind;
+-	optind = 1;
+-
+ 	if (!argc) {
+ 		fprintf(stderr, "Command required\n");
+ 		return -EINVAL;
+@@ -54,5 +50,5 @@ int command_dispatch(sd_bus* bus, const struct command* commands, int argc, char
+ 		return -EINVAL;
+ 	}
+ 
+-	return command->callback(bus, argc, argv);
++	return command->callback(bus, argc - 1, argv + 1);
+ }
+diff --git a/src/networkctl/main.c b/src/networkctl/main.c
+index 73908f8..0ba7284 100644
+--- a/src/networkctl/main.c
++++ b/src/networkctl/main.c
+@@ -115,7 +115,7 @@ int main(int argc, char* argv[]) {
+ 	}
+ 
+ 	// Run a command
+-	r = networkctl_main(bus, argc, argv);
++	r = networkctl_main(bus, argc - 1, argv + 1);
+ 
+ ERROR:
+ 	if (bus)
+-- 
+2.39.2
+
diff --git a/network/patches/0256-networkctl-Implement-dump-command-for-ports-which-sh.patch b/network/patches/0256-networkctl-Implement-dump-command-for-ports-which-sh.patch
new file mode 100644
index 000000000..45fd3b24d
--- /dev/null
+++ b/network/patches/0256-networkctl-Implement-dump-command-for-ports-which-sh.patch
@@ -0,0 +1,94 @@
+From d566048705bb24b12d46fa4ed7cf9352286d8086 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Tue, 6 Jun 2023 10:24:45 +0000
+Subject: [PATCH 256/304] networkctl: Implement "dump" command for ports which
+ shows the JSON
+
+This is just for debugging purposes.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkctl/port.c | 51 +++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 51 insertions(+)
+
+diff --git a/src/networkctl/port.c b/src/networkctl/port.c
+index 3a886ff..67bc003 100644
+--- a/src/networkctl/port.c
++++ b/src/networkctl/port.c
+@@ -18,8 +18,11 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <limits.h>
++
+ #include <systemd/sd-bus.h>
+ 
++#include "../networkd/string.h"
+ #include "command.h"
+ #include "port.h"
+ 
+@@ -77,6 +80,53 @@ ERROR:
+ 	return r;
+ }
+ 
++// Dump
++
++static int networkctl_port_dump(sd_bus* bus, int argc, char* argv[]) {
++	sd_bus_message* reply = NULL;
++	sd_bus_error error = SD_BUS_ERROR_NULL;
++	char path[PATH_MAX];
++	const char* text = NULL;
++	int r;
++
++	if (argc < 1) {
++		fprintf(stderr, "Port required\n");
++		return -EINVAL;
++	}
++
++	// Make port path
++	r = nw_string_format(path, "/org/ipfire/network1/port/%s", argv[0]);
++	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", &text);
++	if (r < 0) {
++		fprintf(stderr, "Could not parse bus message: %s\n", strerror(-r));
++		goto ERROR;
++	}
++
++	// Print the text
++	if (text)
++		printf("%s\n", text);
++
++ERROR:
++	if (reply)
++		sd_bus_message_unref(reply);
++
++	return r;
++}
++
++// List
++
+ static int __networkctl_port_list(sd_bus* bus, const char* path, const char* name, void* data) {
+ 	printf("%s\n", name);
+ 
+@@ -89,6 +139,7 @@ static int networkctl_port_list(sd_bus* bus, int argc, char* argv[]) {
+ 
+ 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 },
+ 		{ NULL },
+ 	};
+-- 
+2.39.2
+
diff --git a/network/patches/0257-string-Add-macros-to-easily-define-string-table-look.patch b/network/patches/0257-string-Add-macros-to-easily-define-string-table-look.patch
new file mode 100644
index 000000000..29a80be49
--- /dev/null
+++ b/network/patches/0257-string-Add-macros-to-easily-define-string-table-look.patch
@@ -0,0 +1,70 @@
+From f9975100fdb9b9d66d9f21436e7973f9824bbcde Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 7 Jun 2023 13:16:58 +0000
+Subject: [PATCH 257/304] string: Add macros to easily define string table
+ lookups
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/string.h | 45 +++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 45 insertions(+)
+
+diff --git a/src/networkd/string.h b/src/networkd/string.h
+index 5bdfc3d..d94e270 100644
+--- a/src/networkd/string.h
++++ b/src/networkd/string.h
+@@ -125,6 +125,51 @@ static inline void nw_string_empty(char* s) {
+ 		*s = '\0';
+ }
+ 
++/*
++	Tables
++*/
++
++struct nw_string_table {
++	const int id;
++	const char* string;
++};
++
++static inline const char* nw_string_table_lookup_string(
++		const struct nw_string_table* table, const int id) {
++	const struct nw_string_table* 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 struct nw_string_table* table, const char* string) {
++	const struct nw_string_table* 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
+ */
+-- 
+2.39.2
+
diff --git a/network/patches/0258-ports-VLAN-Implement-choosing-a-protocol.patch b/network/patches/0258-ports-VLAN-Implement-choosing-a-protocol.patch
new file mode 100644
index 000000000..ac95117c0
--- /dev/null
+++ b/network/patches/0258-ports-VLAN-Implement-choosing-a-protocol.patch
@@ -0,0 +1,156 @@
+From d872e169c7d38b741b0fd01796202a19357d622c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Wed, 7 Jun 2023 13:17:36 +0000
+Subject: [PATCH 258/304] ports: VLAN: Implement choosing a protocol
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-vlan.c | 49 ++++++++++++++++++++++++++++++++++++++++
+ src/networkd/port-vlan.h | 14 ++++++++++++
+ 2 files changed, 63 insertions(+)
+
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index ac4a5b4..6e13ba9 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -18,6 +18,8 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <linux/if_link.h>
++
+ #include <systemd/sd-netlink.h>
+ 
+ #include "config.h"
+@@ -28,6 +30,14 @@
+ #include "port-vlan.h"
+ #include "string.h"
+ 
++const struct nw_string_table 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_config_read(nw_port* port) {
+ 	int r;
+ 
+@@ -39,6 +49,14 @@ static int nw_port_vlan_config_read(nw_port* port) {
+ 			return r;
+ 	}
+ 
++	// VLAN Protocol
++	const char* proto = nw_config_get(port->config, "VLAN_PROTO");
++	if (proto) {
++		r = nw_port_set_vlan_proto(port, nw_port_vlan_proto_from_string(proto));
++		if (r)
++			return r;
++	}
++
+ 	// Parent Port
+ 	const char* parent = nw_config_get(port->config, "VLAN_PARENT");
+ 	if (parent) {
+@@ -58,6 +76,12 @@ static int nw_port_vlan_config_write(nw_port* port) {
+ 	if (r)
+ 		return r;
+ 
++	// VLAN Protocol
++	r = nw_config_set(port->config, "VLAN_PROTO",
++		nw_port_vlan_proto_to_string(port->vlan.proto));
++	if (r)
++		return r;
++
+ 	// Parent Port
+ 	r = nw_config_set(port->config, "VLAN_PARENT",
+ 		(port->vlan.parent) ? nw_port_name(port->vlan.parent) : port->vlan.__parent_name);
+@@ -75,6 +99,11 @@ static int nw_port_vlan_create_link(nw_port* port, sd_netlink_message* m) {
+ 	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;
+ }
+ 
+@@ -87,6 +116,12 @@ static int nw_port_vlan_to_json(nw_port* port, struct json_object* o) {
+ 	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) {
+@@ -155,6 +190,20 @@ int nw_port_set_vlan_id(nw_port* port, int 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;
+ 
+diff --git a/src/networkd/port-vlan.h b/src/networkd/port-vlan.h
+index cffc178..17d3134 100644
+--- a/src/networkd/port-vlan.h
++++ b/src/networkd/port-vlan.h
+@@ -21,14 +21,25 @@
+ #ifndef NETWORKD_PORT_VLAN_H
+ #define NETWORKD_PORT_VLAN_H
+ 
++#include <arpa/inet.h>
++#include <linux/if_ether.h>
++
+ #include "port.h"
+ 
++typedef enum nw_port_vlan_proto {
++	NW_VLAN_PROTO_8021Q  = ETH_P_8021Q,
++	NW_VLAN_PROTO_8021ad = ETH_P_8021AD,
++} nw_port_vlan_proto_t;
++
+ 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[IF_NAMESIZE];
+@@ -40,6 +51,9 @@ extern const nw_port_info_t nw_port_info_vlan;
+ 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);
+-- 
+2.39.2
+
diff --git a/network/patches/0259-config-Extend-the-parser-to-easier-read-write-config.patch b/network/patches/0259-config-Extend-the-parser-to-easier-read-write-config.patch
new file mode 100644
index 000000000..6b593b0f3
--- /dev/null
+++ b/network/patches/0259-config-Extend-the-parser-to-easier-read-write-config.patch
@@ -0,0 +1,716 @@
+From 082d81a3b4115422a0d58fe8c3cf504350c76bfe Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 15:04:38 +0000
+Subject: [PATCH 259/304] config: Extend the parser to easier read/write
+ configs
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/address.h      |   6 +-
+ src/networkd/config.c       | 197 +++++++++++++++++++++++++++++++++++-
+ src/networkd/config.h       |  38 +++++++
+ src/networkd/port-bonding.c |  34 ++++---
+ src/networkd/port-vlan.c    |  57 ++++-------
+ src/networkd/port.c         | 113 ++++++---------------
+ src/networkd/port.h         |   5 +-
+ 7 files changed, 313 insertions(+), 137 deletions(-)
+
+diff --git a/src/networkd/address.h b/src/networkd/address.h
+index 7937d62..afcbca1 100644
+--- a/src/networkd/address.h
++++ b/src/networkd/address.h
+@@ -21,6 +21,7 @@
+ #ifndef NETWORKD_ADDRESS_H
+ #define NETWORKD_ADDRESS_H
+ 
++#include <errno.h>
+ #include <netinet/ether.h>
+ #include <string.h>
+ #include <sys/random.h>
+@@ -35,9 +36,12 @@ enum {
+ };
+ 
+ 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 1;
++		return -errno;
+ 
+ 	return 0;
+ }
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 78448df..6f22da2 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -26,6 +26,7 @@
+ #include <sys/queue.h>
+ #include <unistd.h>
+ 
++#include "address.h"
+ #include "config.h"
+ #include "logging.h"
+ #include "string.h"
+@@ -37,19 +38,37 @@ struct nw_config_entry {
+ 	char value[NETWORK_CONFIG_KEY_MAX_LENGTH];
+ };
+ 
++struct nw_config_option {
++	STAILQ_ENTRY(nw_config_option) nodes;
++
++	const char* key;
++	void* data;
++
++	// Callbacks
++	nw_config_option_read_callback_t read_callback;
++	nw_config_option_write_callback_t write_callback;
++};
++
+ struct nw_config {
+ 	int nrefs;
+ 
+ 	// The path to the configuration file
+ 	char path[PATH_MAX];
+ 
+-	STAILQ_HEAD(entries, nw_config_entry) entries;
++	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;
+@@ -81,9 +100,20 @@ ERROR:
+ }
+ 
+ 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);
+ }
+ 
+@@ -100,6 +130,9 @@ int nw_config_create(nw_config** config, const char* path) {
+ 	// Initialise entries
+ 	STAILQ_INIT(&c->entries);
+ 
++	// Initialise options
++	STAILQ_INIT(&c->options);
++
+ 	// Store the path
+ 	if (path) {
+ 		r = nw_string_set(c->path, path);
+@@ -147,6 +180,33 @@ int nw_config_destroy(nw_config* config) {
+ 	return unlink(config->path);
+ }
+ 
++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;
++}
++
+ const char* nw_config_path(nw_config* config) {
+ 	if (*config->path)
+ 		return config->path;
+@@ -341,6 +401,9 @@ const char* nw_config_get(nw_config* config, const char* key) {
+ 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);
+@@ -418,3 +481,135 @@ 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) {
+ 	return nw_config_set(config, key, value ? "true" : "false");
+ }
++
++/*
++	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->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->data);
++		if (r < 0)
++			return r;
++	}
++
++	return 0;
++}
++
++int nw_config_option_add(nw_config* config, const char* key, void* data,
++		nw_config_option_read_callback_t read_callback,
++		nw_config_option_write_callback_t write_callback) {
++	// Check input
++	if (!key || !data || !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 data
++	option->data = data;
++
++	// Set callbacks
++	option->read_callback = read_callback;
++	option->write_callback = write_callback;
++
++	// 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* data) {
++	int* p = (int*)data;
++
++	// Fetch the value
++	*p = nw_config_get_int(config, key, -1);
++
++	return 0;
++}
++
++int nw_config_write_int(nw_config* config, const char* key, const void* data) {
++	return 0;
++}
++
++// String
++
++int nw_config_read_string(nw_config* config, const char* key, void* data) {
++	const char** p = (const char**)data;
++
++	// Fetch the value
++	const char* value = nw_config_get(config, key);
++	if (value)
++		*p = value;
++
++	return 0;
++}
++
++int nw_config_write_string(nw_config* config, const char* key, const void* data) {
++	const char** value = (const char**)data;
++
++	return nw_config_set(config, key, *value);
++}
++
++// Address
++
++int nw_config_read_address(nw_config* config, const char* key, void* data) {
++	nw_address_t* address = (nw_address_t*)data;
++	int r;
++
++	// Fetch the value
++	const char* value = nw_config_get(config, key);
++	if (!value)
++		return -EINVAL;
++
++	r = nw_address_from_string(address, value);
++	if (r < 0)
++		ERROR("Could not parse address: %s\n", value);
++
++	return r;
++}
++
++int nw_config_write_address(nw_config* config, const char* key, const void* data) {
++	const nw_address_t* address = (nw_address_t*)data;
++	int r;
++
++	// Format the address to string
++	char* value = nw_address_to_string(address);
++	if (!value)
++		return -errno;
++
++	// Store the value
++	r = nw_config_set(config, key, value);
++	if (r < 0)
++		goto ERROR;
++
++	// Success
++	r = 0;
++
++ERROR:
++	if (value)
++		free(value);
++
++	return r;
++}
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index e17c016..b5417e4 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -34,6 +34,7 @@ nw_config* nw_config_ref(nw_config* config);
+ nw_config* nw_config_unref(nw_config* config);
+ 
+ int nw_config_destroy(nw_config* config);
++int nw_config_copy(nw_config* config, nw_config** copy);
+ 
+ const char* nw_config_path(nw_config* config);
+ 
+@@ -53,4 +54,41 @@ 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);
+ 
++/*
++	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* data);
++typedef int (*nw_config_option_write_callback_t)
++	(nw_config* config, const char* key, const void* data);
++
++int nw_config_option_add(nw_config* config, const char* key, void* value,
++	nw_config_option_read_callback_t read_callback,
++	nw_config_option_write_callback_t write_callback);
++
++#define NW_CONFIG_OPTION(config, key, data, read_callback, write_callback) \
++	nw_config_option_add(config, key, data, read_callback, write_callback)
++
++#define NW_CONFIG_OPTION_STRING(config, key, data) \
++	nw_config_option_add(config, key, data, nw_config_read_string, nw_config_write_string)
++
++int nw_config_read_string(nw_config* config, const char* key, void* data);
++int nw_config_write_string(nw_config* config, const char* key, const void* data);
++
++#define NW_CONFIG_OPTION_INT(config, key, data) \
++	nw_config_option_add(config, key, data, nw_config_read_int, nw_config_write_int)
++
++int nw_config_read_int(nw_config* config, const char* key, void* data);
++int nw_config_write_int(nw_config* config, const char* key, const void* data);
++
++#define NW_CONFIG_OPTION_ADDRESS(config, key, data) \
++	nw_config_option_add(config, key, data, nw_config_read_address, nw_config_write_address)
++
++int nw_config_read_address(nw_config* config, const char* key, void* data);
++int nw_config_write_address(nw_config* config, const char* key, const void* data);
++
+ #endif /* NETWORKD_CONFIG_H */
+diff --git a/src/networkd/port-bonding.c b/src/networkd/port-bonding.c
+index bbc3fb7..a2e0fc3 100644
+--- a/src/networkd/port-bonding.c
++++ b/src/networkd/port-bonding.c
+@@ -63,27 +63,32 @@ static const char* nw_port_bonding_mode_to_string(const int mode) {
+ 	return NULL;
+ }
+ 
+-static int nw_port_bonding_config_read(nw_port* port) {
+-	int r;
+-
+-	// Mode
+-	const char* mode = nw_config_get(port->config, "BONDING_MODE");
+-	if (mode) {
+-		r = nw_port_bonding_set_mode(port, mode);
+-		if (r)
+-			return r;
++static int nw_port_bonding_read_mode(nw_config* config, const char* key, void* data) {
++	int* mode = (int*)data;
++
++	const char* value = nw_config_get(config, key);
++	if (value) {
++		*mode = nw_port_bonding_mode_from_string(value);
++		if (!*mode)
++			return -errno;
+ 	}
+ 
+ 	return 0;
+ }
+ 
+-static int nw_port_bonding_config_write(nw_port* port) {
++static int nw_port_bonding_write_mode(nw_config* config, const char* key, const void* data) {
++	const int* mode = (int*)data;
++
++	return nw_config_set(config, key, nw_port_bonding_mode_to_string(*mode));
++}
++
++static int nw_port_bonding_setup(nw_port* port) {
+ 	int r;
+ 
+ 	// Mode
+-	r = nw_config_set(port->config, "BONDING_MODE",
+-			nw_port_bonding_mode_to_string(port->bonding.mode));
+-	if (r)
++	r = NW_CONFIG_OPTION(port->config, "BONDING_MODE", &port->bonding.mode,
++			nw_port_bonding_read_mode, nw_port_bonding_write_mode);
++	if (r < 0)
+ 		return r;
+ 
+ 	return 0;
+@@ -119,8 +124,7 @@ const nw_port_info_t nw_port_info_bonding = {
+ 	// Operations
+ 	.ops = {
+ 		// Configuration
+-		.config_read = nw_port_bonding_config_read,
+-		.config_write = nw_port_bonding_config_write,
++		.setup = nw_port_bonding_setup,
+ 
+ 		// Link
+ 		.create_link = nw_port_bonding_create_link,
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index 6e13ba9..2d89a09 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -38,54 +38,42 @@ const struct nw_string_table nw_port_vlan_proto[] = {
+ 
+ NW_STRING_TABLE_LOOKUP(nw_port_vlan_proto_t, nw_port_vlan_proto)
+ 
+-static int nw_port_vlan_config_read(nw_port* port) {
+-	int r;
+-
+-	// VLAN ID
+-	int id = nw_config_get_int(port->config, "VLAN_ID", NW_VLAN_ID_INVALID);
+-	if (id) {
+-		r = nw_port_set_vlan_id(port, id);
+-		if (r)
+-			return r;
++static int nw_port_vlan_read_proto(nw_config* config, const char* key, void* data) {
++	nw_port_vlan_proto_t* proto = (nw_port_vlan_proto_t*)data;
++
++	const char* value = nw_config_get(config, key);
++	if (value) {
++		*proto = nw_port_vlan_proto_from_string(data);
++		if (!*proto)
++			return -errno;
+ 	}
+ 
+-	// VLAN Protocol
+-	const char* proto = nw_config_get(port->config, "VLAN_PROTO");
+-	if (proto) {
+-		r = nw_port_set_vlan_proto(port, nw_port_vlan_proto_from_string(proto));
+-		if (r)
+-			return r;
+-	}
++	return 0;
++}
+ 
+-	// Parent Port
+-	const char* parent = nw_config_get(port->config, "VLAN_PARENT");
+-	if (parent) {
+-		r = nw_string_set(port->vlan.__parent_name, parent);
+-		if (r)
+-			return r;
+-	}
++static int nw_port_vlan_write_proto(nw_config* config, const char* key, const void* data) {
++	const nw_port_vlan_proto_t* proto = (nw_port_vlan_proto_t*)data;
+ 
+-	return 0;
++	return nw_config_set(config, key, nw_port_vlan_proto_to_string(*proto));
+ }
+ 
+-static int nw_port_vlan_config_write(nw_port* port) {
++static int nw_port_vlan_setup(nw_port* port) {
+ 	int r;
+ 
+ 	// VLAN ID
+-	r = nw_config_set_int(port->config, "VLAN_ID", port->vlan.id);
+-	if (r)
++	r = NW_CONFIG_OPTION_INT(port->config, "VLAN_ID", &port->vlan.id);
++	if (r < 0)
+ 		return r;
+ 
+ 	// VLAN Protocol
+-	r = nw_config_set(port->config, "VLAN_PROTO",
+-		nw_port_vlan_proto_to_string(port->vlan.proto));
+-	if (r)
++	r = NW_CONFIG_OPTION(port->config, "VLAN_PROTO", &port->vlan.proto,
++			nw_port_vlan_read_proto, nw_port_vlan_write_proto);
++	if (r < 0)
+ 		return r;
+ 
+ 	// Parent Port
+-	r = nw_config_set(port->config, "VLAN_PARENT",
+-		(port->vlan.parent) ? nw_port_name(port->vlan.parent) : port->vlan.__parent_name);
+-	if (r)
++	r = NW_CONFIG_OPTION_STRING(port->config, "VLAN_PARENT", &port->vlan.__parent_name);
++	if (r < 0)
+ 		return r;
+ 
+ 	return 0;
+@@ -143,8 +131,7 @@ const nw_port_info_t nw_port_info_vlan = {
+ 	// Operations
+ 	.ops = {
+ 		// Configuration
+-		.config_read = nw_port_vlan_config_read,
+-		.config_write = nw_port_vlan_config_write,
++		.setup = nw_port_vlan_setup,
+ 
+ 		.get_parent_port = nw_port_get_vlan_parent,
+ 
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index a7fb826..cab8fc5 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -69,64 +69,6 @@ static void nw_port_free(nw_port* port) {
+ 	free(port);
+ }
+ 
+-static int nw_port_setup_address(nw_port* port) {
+-	char* __address = NULL;
+-	int r;
+-
+-	// Read ADDRESS from configuration
+-	const char* s = nw_config_get(port->config, "ADDRESS");
+-	if (!s) {
+-		ERROR("Port %s: Address is not set\n", port->name);
+-		goto ERROR;
+-	}
+-
+-	// Parse the address
+-	r = nw_address_from_string(&port->address, s);
+-	if (r) {
+-		ERROR("Port %s: Could not parse address: %m\n", port->name);
+-		goto ERROR;
+-	}
+-
+-	// Check if this address is usable
+-	r = nw_address_is_multicast(&port->address);
+-	if (r) {
+-		DEBUG("Port %s: Multicast bit is set on Ethernet address\n", port->name);
+-		goto ERROR;
+-	}
+-
+-	return 0;
+-
+-ERROR:
+-	// Generate a random Ethernet address
+-	r = nw_address_generate(&port->address);
+-	if (r) {
+-		ERROR("Could not generate a random Ethernet address: %m\n");
+-		return r;
+-	}
+-
+-	// Format the generated address
+-	__address = nw_address_to_string(&port->address);
+-	if (__address) {
+-		ERROR("Generated a random Ethernet address for %s: %s\n", port->name, __address);
+-
+-		// Free the address
+-		free(__address);
+-	}
+-
+-	return 0;
+-}
+-
+-static int nw_port_setup_common(nw_port* port) {
+-	int r;
+-
+-	// Address
+-	r = nw_port_setup_address(port);
+-	if (r)
+-		return r;
+-
+-	return 0;
+-}
+-
+ 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)
+@@ -154,7 +96,6 @@ static int nw_port_set_link(nw_port* port, nw_link* link) {
+ 
+ static int nw_port_setup(nw_port* port) {
+ 	nw_link* link = NULL;
+-	char path[PATH_MAX];
+ 	int r;
+ 
+ 	// Find the link
+@@ -165,28 +106,32 @@ static int nw_port_setup(nw_port* port) {
+ 			goto ERROR;
+ 	}
+ 
+-	// Compose the path to the main configuration file
+-	r = nw_path_join(path, PORT_CONFIG_DIR, port->name);
+-	if (r)
+-		goto ERROR;
+-
+-	// Initialize the configuration
+-	r = nw_config_create(&port->config, path);
+-	if (r)
++	// 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;
++	}
+ 
+-	// Perform some common initialization
+-	r = nw_port_setup_common(port);
+-	if (r)
++	// Setup options
++	r = NW_CONFIG_OPTION_ADDRESS(port->config, "ADDRESS", &port->address);
++	if (r < 0)
+ 		goto ERROR;
+ 
+ 	// Call any custom initialization
+-	if (NW_PORT_OPS(port)->config_read) {
+-		r = NW_PORT_OPS(port)->config_read(port);
+-		if (r)
++	if (NW_PORT_OPS(port)->setup) {
++		r = NW_PORT_OPS(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);
+@@ -194,7 +139,8 @@ ERROR:
+ 	return r;
+ }
+ 
+-int nw_port_create(nw_port** port, nw_daemon* daemon, nw_port_type_t type, const char* name) {
++int nw_port_create(nw_port** port, nw_daemon* daemon,
++		nw_port_type_t type, const char* name, nw_config* config) {
+ 	int r;
+ 
+ 	// Allocate a new object
+@@ -231,6 +177,11 @@ int nw_port_create(nw_port** port, nw_daemon* daemon, nw_port_type_t type, const
+ 	if (r)
+ 		goto ERROR;
+ 
++	// Copy the configuration
++	r = nw_config_copy(config, &p->config);
++	if (r)
++		goto ERROR;
++
+ 	// Setup the port
+ 	r = nw_port_setup(p);
+ 	if (r)
+@@ -263,7 +214,7 @@ int nw_port_create_from_config(nw_port** port, nw_daemon* daemon,
+ 	}
+ 
+ 	// Create a new port
+-	r = nw_port_create(port, daemon, nw_port_type_from_string(type), name);
++	r = nw_port_create(port, daemon, nw_port_type_from_string(type), name, config);
+ 	if (r)
+ 		goto ERROR;
+ 
+@@ -356,12 +307,10 @@ int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
+ int nw_port_save(nw_port* port) {
+ 	int r;
+ 
+-	// Call the custom handler
+-	if (NW_PORT_OPS(port)->config_write) {
+-		r = NW_PORT_OPS(port)->config_write(port);
+-		if (r)
+-			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);
+@@ -371,7 +320,7 @@ int nw_port_save(nw_port* port) {
+ 	return 0;
+ 
+ ERROR:
+-	ERROR("Could not save configuration for port %s: %m\n", port->name);
++	ERROR("Could not save configuration for port %s: %s\n", port->name, strerror(-r));
+ 
+ 	return 1;
+ }
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index e16b957..ef68a89 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -59,8 +59,7 @@ struct nw_port_info {
+ 
+ 	struct nw_port_ops {
+ 		// Configuration
+-		int (*config_read)(nw_port* port);
+-		int (*config_write)(nw_port* port);
++		int (*setup)(nw_port* port);
+ 
+ 		// Get Parent Port
+ 		nw_port* (*get_parent_port)(nw_port* port);
+@@ -104,7 +103,7 @@ struct nw_port {
+ };
+ 
+ int nw_port_create(nw_port** port, nw_daemon* daemon,
+-	nw_port_type_t type, const char* name);
++	nw_port_type_t type, const char* name, nw_config* config);
+ int nw_port_create_from_config(nw_port** port, nw_daemon* daemon,
+ 	const char* name, const char* path);
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0260-ports-bonding-Convert-mode-to-string-table.patch b/network/patches/0260-ports-bonding-Convert-mode-to-string-table.patch
new file mode 100644
index 000000000..2723006fd
--- /dev/null
+++ b/network/patches/0260-ports-bonding-Convert-mode-to-string-table.patch
@@ -0,0 +1,93 @@
+From 582536b21276f11b040eef80e185635b312556da Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 15:12:00 +0000
+Subject: [PATCH 260/304] ports: bonding: Convert mode to string table
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-bonding.c | 41 ++++++++-----------------------------
+ src/networkd/port-bonding.h | 12 ++++++++++-
+ 2 files changed, 20 insertions(+), 33 deletions(-)
+
+diff --git a/src/networkd/port-bonding.c b/src/networkd/port-bonding.c
+index a2e0fc3..e765791 100644
+--- a/src/networkd/port-bonding.c
++++ b/src/networkd/port-bonding.c
+@@ -27,41 +27,18 @@
+ #include "port-bonding.h"
+ #include "string.h"
+ 
+-const struct nw_port_bonding_mode {
+-	const int mode;
+-	const char* string;
+-} nw_port_bonding_modes[] = {
+-	{ BOND_MODE_ROUNDROBIN,   "round-robin" },
+-	{ BOND_MODE_ACTIVEBACKUP, "active-backup" },
+-	{ BOND_MODE_XOR,          "xor" },
+-	{ BOND_MODE_BROADCAST,    "broadcast" },
+-	{ BOND_MODE_8023AD,       "802.3ad" },
+-	{ BOND_MODE_TLB,          "tlb" },
+-	{ BOND_MODE_ALB,          "alb" },
++const struct nw_string_table 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 },
+ };
+ 
+-static int nw_port_bonding_mode_from_string(const char* string) {
+-	const struct nw_port_bonding_mode* m = NULL;
+-
+-	for (m = nw_port_bonding_modes; m->string; m++) {
+-		if (strcmp(m->string, string) == 0)
+-			return m->mode;
+-	}
+-
+-	return -1;
+-}
+-
+-static const char* nw_port_bonding_mode_to_string(const int mode) {
+-	const struct nw_port_bonding_mode* m = NULL;
+-
+-	for (m = nw_port_bonding_modes; m->string; m++) {
+-		if (m->mode == mode)
+-			return m->string;
+-	}
+-
+-	return NULL;
+-}
++NW_STRING_TABLE_LOOKUP(nw_port_bonding_mode_t, nw_port_bonding_mode)
+ 
+ static int nw_port_bonding_read_mode(nw_config* config, const char* key, void* data) {
+ 	int* mode = (int*)data;
+diff --git a/src/networkd/port-bonding.h b/src/networkd/port-bonding.h
+index 5cd2c43..e21c251 100644
+--- a/src/networkd/port-bonding.h
++++ b/src/networkd/port-bonding.h
+@@ -25,8 +25,18 @@
+ 
+ #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 {
+-	int mode;
++	nw_port_bonding_mode_t mode;
+ };
+ 
+ extern const nw_port_info_t nw_port_info_bonding;
+-- 
+2.39.2
+
diff --git a/network/patches/0261-config-Rename-data-to-value-as-it-holds-a-reference-.patch b/network/patches/0261-config-Rename-data-to-value-as-it-holds-a-reference-.patch
new file mode 100644
index 000000000..0f032586d
--- /dev/null
+++ b/network/patches/0261-config-Rename-data-to-value-as-it-holds-a-reference-.patch
@@ -0,0 +1,221 @@
+From cc54472edbcc3a4325b9bad6eff1532bcd42c891 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 15:17:24 +0000
+Subject: [PATCH 261/304] config: Rename "data" to "value" as it holds a
+ reference to it
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 64 ++++++++++++++++++++-----------------------
+ src/networkd/config.h | 28 +++++++++----------
+ 2 files changed, 43 insertions(+), 49 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 6f22da2..b142d17 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -42,7 +42,7 @@ struct nw_config_option {
+ 	STAILQ_ENTRY(nw_config_option) nodes;
+ 
+ 	const char* key;
+-	void* data;
++	void* value;
+ 
+ 	// Callbacks
+ 	nw_config_option_read_callback_t read_callback;
+@@ -491,7 +491,7 @@ int nw_config_options_read(nw_config* config) {
+ 	int r;
+ 
+ 	STAILQ_FOREACH(option, &config->options, nodes) {
+-		r = option->read_callback(config, option->key, option->data);
++		r = option->read_callback(config, option->key, option->value);
+ 		if (r < 0)
+ 			return r;
+ 	}
+@@ -504,7 +504,7 @@ int nw_config_options_write(nw_config* config) {
+ 	int r;
+ 
+ 	STAILQ_FOREACH(option, &config->options, nodes) {
+-		r = option->write_callback(config, option->key, option->data);
++		r = option->write_callback(config, option->key, option->value);
+ 		if (r < 0)
+ 			return r;
+ 	}
+@@ -512,11 +512,11 @@ int nw_config_options_write(nw_config* config) {
+ 	return 0;
+ }
+ 
+-int nw_config_option_add(nw_config* config, const char* key, void* data,
++int nw_config_option_add(nw_config* config, const char* key, void* value,
+ 		nw_config_option_read_callback_t read_callback,
+ 		nw_config_option_write_callback_t write_callback) {
+ 	// Check input
+-	if (!key || !data || !read_callback || !write_callback)
++	if (!key || !value || !read_callback || !write_callback)
+ 		return -EINVAL;
+ 
+ 	// Allocate a new option
+@@ -527,8 +527,8 @@ int nw_config_option_add(nw_config* config, const char* key, void* data,
+ 	// Set key
+ 	option->key = key;
+ 
+-	// Set data
+-	option->data = data;
++	// Set value
++	option->value = value;
+ 
+ 	// Set callbacks
+ 	option->read_callback = read_callback;
+@@ -540,67 +540,61 @@ int nw_config_option_add(nw_config* config, const char* key, void* data,
+ 	return 0;
+ }
+ 
+-int nw_config_read_int(nw_config* config, const char* key, void* data) {
+-	int* p = (int*)data;
+-
++int nw_config_read_int(nw_config* config, const char* key, void* value) {
+ 	// Fetch the value
+-	*p = nw_config_get_int(config, key, -1);
++	*(int*)value = nw_config_get_int(config, key, -1);
+ 
+ 	return 0;
+ }
+ 
+-int nw_config_write_int(nw_config* config, const char* key, const void* data) {
++int nw_config_write_int(nw_config* config, const char* key, const void* value) {
+ 	return 0;
+ }
+ 
+ // String
+ 
+-int nw_config_read_string(nw_config* config, const char* key, void* data) {
+-	const char** p = (const char**)data;
+-
++int nw_config_read_string(nw_config* config, const char* key, void* value) {
+ 	// Fetch the value
+-	const char* value = nw_config_get(config, key);
+-	if (value)
+-		*p = 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* data) {
+-	const char** value = (const char**)data;
+-
+-	return nw_config_set(config, key, *value);
++int nw_config_write_string(nw_config* config, const char* key, const void* value) {
++	return nw_config_set(config, key, *(const char**)value);
+ }
+ 
+ // Address
+ 
+-int nw_config_read_address(nw_config* config, const char* key, void* data) {
+-	nw_address_t* address = (nw_address_t*)data;
++int nw_config_read_address(nw_config* config, const char* key, void* value) {
++	nw_address_t* address = (nw_address_t*)value;
+ 	int r;
+ 
+ 	// Fetch the value
+-	const char* value = nw_config_get(config, key);
+-	if (!value)
++	const char* p = nw_config_get(config, key);
++	if (!p)
+ 		return -EINVAL;
+ 
+-	r = nw_address_from_string(address, value);
++	r = nw_address_from_string(address, p);
+ 	if (r < 0)
+-		ERROR("Could not parse address: %s\n", value);
++		ERROR("Could not parse address: %s\n", p);
+ 
+ 	return r;
+ }
+ 
+-int nw_config_write_address(nw_config* config, const char* key, const void* data) {
+-	const nw_address_t* address = (nw_address_t*)data;
++int nw_config_write_address(nw_config* config, const char* key, const void* value) {
++	const nw_address_t* address = (nw_address_t*)value;
+ 	int r;
+ 
+ 	// Format the address to string
+-	char* value = nw_address_to_string(address);
+-	if (!value)
++	char* p = nw_address_to_string(address);
++	if (!p)
+ 		return -errno;
+ 
+ 	// Store the value
+-	r = nw_config_set(config, key, value);
++	r = nw_config_set(config, key, p);
+ 	if (r < 0)
+ 		goto ERROR;
+ 
+@@ -608,8 +602,8 @@ int nw_config_write_address(nw_config* config, const char* key, const void* data
+ 	r = 0;
+ 
+ ERROR:
+-	if (value)
+-		free(value);
++	if (p)
++		free(p);
+ 
+ 	return r;
+ }
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index b5417e4..7185038 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -62,9 +62,9 @@ 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* data);
++	(nw_config* config, const char* key, void* value);
+ typedef int (*nw_config_option_write_callback_t)
+-	(nw_config* config, const char* key, const void* data);
++	(nw_config* config, const char* key, const void* value);
+ 
+ int nw_config_option_add(nw_config* config, const char* key, void* value,
+ 	nw_config_option_read_callback_t read_callback,
+@@ -73,22 +73,22 @@ int nw_config_option_add(nw_config* config, const char* key, void* value,
+ #define NW_CONFIG_OPTION(config, key, data, read_callback, write_callback) \
+ 	nw_config_option_add(config, key, data, read_callback, write_callback)
+ 
+-#define NW_CONFIG_OPTION_STRING(config, key, data) \
+-	nw_config_option_add(config, key, data, nw_config_read_string, nw_config_write_string)
++#define NW_CONFIG_OPTION_STRING(config, key, value) \
++	nw_config_option_add(config, key, value, nw_config_read_string, nw_config_write_string)
+ 
+-int nw_config_read_string(nw_config* config, const char* key, void* data);
+-int nw_config_write_string(nw_config* config, const char* key, const void* data);
++int nw_config_read_string(nw_config* config, const char* key, void* value);
++int nw_config_write_string(nw_config* config, const char* key, const void* value);
+ 
+-#define NW_CONFIG_OPTION_INT(config, key, data) \
+-	nw_config_option_add(config, key, data, nw_config_read_int, nw_config_write_int)
++#define NW_CONFIG_OPTION_INT(config, key, value) \
++	nw_config_option_add(config, key, value, nw_config_read_int, nw_config_write_int)
+ 
+-int nw_config_read_int(nw_config* config, const char* key, void* data);
+-int nw_config_write_int(nw_config* config, const char* key, const void* data);
++int nw_config_read_int(nw_config* config, const char* key, void* value);
++int nw_config_write_int(nw_config* config, const char* key, const void* value);
+ 
+-#define NW_CONFIG_OPTION_ADDRESS(config, key, data) \
+-	nw_config_option_add(config, key, data, nw_config_read_address, nw_config_write_address)
++#define NW_CONFIG_OPTION_ADDRESS(config, key, value) \
++	nw_config_option_add(config, key, value, nw_config_read_address, nw_config_write_address)
+ 
+-int nw_config_read_address(nw_config* config, const char* key, void* data);
+-int nw_config_write_address(nw_config* config, const char* key, const void* data);
++int nw_config_read_address(nw_config* config, const char* key, void* value);
++int nw_config_write_address(nw_config* config, const char* key, const void* value);
+ 
+ #endif /* NETWORKD_CONFIG_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0262-config-Add-data-pointer-to-callbacks.patch b/network/patches/0262-config-Add-data-pointer-to-callbacks.patch
new file mode 100644
index 000000000..a97c8d0ac
--- /dev/null
+++ b/network/patches/0262-config-Add-data-pointer-to-callbacks.patch
@@ -0,0 +1,255 @@
+From 3eeb38b7ad3a343645127f11fea281ed2a22e80a Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 15:23:10 +0000
+Subject: [PATCH 262/304] config: Add data pointer to callbacks
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c       | 23 ++++++++++++++---------
+ src/networkd/config.h       | 28 ++++++++++++++--------------
+ src/networkd/port-bonding.c | 18 ++++++++++--------
+ src/networkd/port-vlan.c    | 18 ++++++++++--------
+ 4 files changed, 48 insertions(+), 39 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index b142d17..39e9881 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -47,6 +47,7 @@ struct nw_config_option {
+ 	// Callbacks
+ 	nw_config_option_read_callback_t read_callback;
+ 	nw_config_option_write_callback_t write_callback;
++	void* data;
+ };
+ 
+ struct nw_config {
+@@ -491,7 +492,7 @@ int nw_config_options_read(nw_config* config) {
+ 	int r;
+ 
+ 	STAILQ_FOREACH(option, &config->options, nodes) {
+-		r = option->read_callback(config, option->key, option->value);
++		r = option->read_callback(config, option->key, option->value, option->data);
+ 		if (r < 0)
+ 			return r;
+ 	}
+@@ -504,7 +505,7 @@ int nw_config_options_write(nw_config* config) {
+ 	int r;
+ 
+ 	STAILQ_FOREACH(option, &config->options, nodes) {
+-		r = option->write_callback(config, option->key, option->value);
++		r = option->write_callback(config, option->key, option->value, option->data);
+ 		if (r < 0)
+ 			return r;
+ 	}
+@@ -514,7 +515,7 @@ int nw_config_options_write(nw_config* config) {
+ 
+ int nw_config_option_add(nw_config* config, const char* key, void* value,
+ 		nw_config_option_read_callback_t read_callback,
+-		nw_config_option_write_callback_t write_callback) {
++		nw_config_option_write_callback_t write_callback, void* data) {
+ 	// Check input
+ 	if (!key || !value || !read_callback || !write_callback)
+ 		return -EINVAL;
+@@ -533,6 +534,7 @@ int nw_config_option_add(nw_config* config, const char* key, void* value,
+ 	// 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);
+@@ -540,20 +542,21 @@ int nw_config_option_add(nw_config* config, const char* key, void* value,
+ 	return 0;
+ }
+ 
+-int nw_config_read_int(nw_config* config, const char* key, void* value) {
++int nw_config_read_int(nw_config* config, const char* key, void* value, 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) {
++int nw_config_write_int(nw_config* config,
++		const char* key, const void* value, void* data) {
+ 	return 0;
+ }
+ 
+ // String
+ 
+-int nw_config_read_string(nw_config* config, const char* key, void* value) {
++int nw_config_read_string(nw_config* config, const char* key, void* value, void* data) {
+ 	// Fetch the value
+ 	const char* p = nw_config_get(config, key);
+ 	if (p)
+@@ -562,13 +565,14 @@ int nw_config_read_string(nw_config* config, const char* key, void* value) {
+ 	return 0;
+ }
+ 
+-int nw_config_write_string(nw_config* config, const char* key, const void* value) {
++int nw_config_write_string(nw_config* config,
++		const char* key, const void* value, void* data) {
+ 	return nw_config_set(config, key, *(const char**)value);
+ }
+ 
+ // Address
+ 
+-int nw_config_read_address(nw_config* config, const char* key, void* value) {
++int nw_config_read_address(nw_config* config, const char* key, void* value, void* data) {
+ 	nw_address_t* address = (nw_address_t*)value;
+ 	int r;
+ 
+@@ -584,7 +588,8 @@ int nw_config_read_address(nw_config* config, const char* key, void* value) {
+ 	return r;
+ }
+ 
+-int nw_config_write_address(nw_config* config, const char* key, const void* value) {
++int nw_config_write_address(nw_config* config,
++		const char* key, const void* value, void* data) {
+ 	const nw_address_t* address = (nw_address_t*)value;
+ 	int r;
+ 
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index 7185038..d6b8db8 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -62,33 +62,33 @@ 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);
++	(nw_config* config, const char* key, void* value, void* data);
+ typedef int (*nw_config_option_write_callback_t)
+-	(nw_config* config, const char* key, const void* value);
++	(nw_config* config, const char* key, const void* value, void* data);
+ 
+ int nw_config_option_add(nw_config* config, const char* key, void* value,
+ 	nw_config_option_read_callback_t read_callback,
+-	nw_config_option_write_callback_t write_callback);
++	nw_config_option_write_callback_t write_callback, void* data);
+ 
+-#define NW_CONFIG_OPTION(config, key, data, read_callback, write_callback) \
+-	nw_config_option_add(config, key, data, read_callback, write_callback)
++#define NW_CONFIG_OPTION(config, key, value, read_callback, write_callback, data) \
++	nw_config_option_add(config, key, value, read_callback, write_callback, data)
+ 
+ #define NW_CONFIG_OPTION_STRING(config, key, value) \
+-	nw_config_option_add(config, key, value, nw_config_read_string, nw_config_write_string)
++	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);
+-int nw_config_write_string(nw_config* config, const char* key, const void* value);
++int nw_config_read_string(nw_config* config, const char* key, void* value, void* data);
++int nw_config_write_string(nw_config* config, const char* key, const void* value, void* data);
+ 
+ #define NW_CONFIG_OPTION_INT(config, key, value) \
+-	nw_config_option_add(config, key, value, nw_config_read_int, nw_config_write_int)
++	nw_config_option_add(config, key, value, nw_config_read_int, nw_config_write_int, NULL)
+ 
+-int nw_config_read_int(nw_config* config, const char* key, void* value);
+-int nw_config_write_int(nw_config* config, const char* key, const void* value);
++int nw_config_read_int(nw_config* config, const char* key, void* value, void* data);
++int nw_config_write_int(nw_config* config, const char* key, const void* value, void* data);
+ 
+ #define NW_CONFIG_OPTION_ADDRESS(config, key, value) \
+-	nw_config_option_add(config, key, value, nw_config_read_address, nw_config_write_address)
++	nw_config_option_add(config, key, value, nw_config_read_address, nw_config_write_address, NULL)
+ 
+-int nw_config_read_address(nw_config* config, const char* key, void* value);
+-int nw_config_write_address(nw_config* config, const char* key, const void* value);
++int nw_config_read_address(nw_config* config, const char* key, void* value, void* data);
++int nw_config_write_address(nw_config* config, const char* key, const void* value, void* data);
+ 
+ #endif /* NETWORKD_CONFIG_H */
+diff --git a/src/networkd/port-bonding.c b/src/networkd/port-bonding.c
+index e765791..7bd54e1 100644
+--- a/src/networkd/port-bonding.c
++++ b/src/networkd/port-bonding.c
+@@ -40,12 +40,13 @@ const struct nw_string_table nw_port_bonding_mode[] = {
+ 
+ NW_STRING_TABLE_LOOKUP(nw_port_bonding_mode_t, nw_port_bonding_mode)
+ 
+-static int nw_port_bonding_read_mode(nw_config* config, const char* key, void* data) {
+-	int* mode = (int*)data;
++static int nw_port_bonding_read_mode(nw_config* config,
++		const char* key, void* value, void* data) {
++	int* mode = (int*)value;
+ 
+-	const char* value = nw_config_get(config, key);
+-	if (value) {
+-		*mode = nw_port_bonding_mode_from_string(value);
++	const char* p = nw_config_get(config, key);
++	if (p) {
++		*mode = nw_port_bonding_mode_from_string(p);
+ 		if (!*mode)
+ 			return -errno;
+ 	}
+@@ -53,8 +54,9 @@ static int nw_port_bonding_read_mode(nw_config* config, const char* key, void* d
+ 	return 0;
+ }
+ 
+-static int nw_port_bonding_write_mode(nw_config* config, const char* key, const void* data) {
+-	const int* mode = (int*)data;
++static int nw_port_bonding_write_mode(nw_config* config,
++		const char* key, const void* value, void* data) {
++	const int* mode = (int*)value;
+ 
+ 	return nw_config_set(config, key, nw_port_bonding_mode_to_string(*mode));
+ }
+@@ -64,7 +66,7 @@ static int nw_port_bonding_setup(nw_port* port) {
+ 
+ 	// Mode
+ 	r = NW_CONFIG_OPTION(port->config, "BONDING_MODE", &port->bonding.mode,
+-			nw_port_bonding_read_mode, nw_port_bonding_write_mode);
++			nw_port_bonding_read_mode, nw_port_bonding_write_mode, NULL);
+ 	if (r < 0)
+ 		return r;
+ 
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index 2d89a09..e3f93d7 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -38,12 +38,13 @@ const struct nw_string_table nw_port_vlan_proto[] = {
+ 
+ NW_STRING_TABLE_LOOKUP(nw_port_vlan_proto_t, nw_port_vlan_proto)
+ 
+-static int nw_port_vlan_read_proto(nw_config* config, const char* key, void* data) {
+-	nw_port_vlan_proto_t* proto = (nw_port_vlan_proto_t*)data;
++static int nw_port_vlan_read_proto(nw_config* config,
++		const char* key, void* value, void* data) {
++	nw_port_vlan_proto_t* proto = (nw_port_vlan_proto_t*)value;
+ 
+-	const char* value = nw_config_get(config, key);
+-	if (value) {
+-		*proto = nw_port_vlan_proto_from_string(data);
++	const char* p = nw_config_get(config, key);
++	if (p) {
++		*proto = nw_port_vlan_proto_from_string(p);
+ 		if (!*proto)
+ 			return -errno;
+ 	}
+@@ -51,8 +52,9 @@ static int nw_port_vlan_read_proto(nw_config* config, const char* key, void* dat
+ 	return 0;
+ }
+ 
+-static int nw_port_vlan_write_proto(nw_config* config, const char* key, const void* data) {
+-	const nw_port_vlan_proto_t* proto = (nw_port_vlan_proto_t*)data;
++static int nw_port_vlan_write_proto(nw_config* config,
++		const char* key, const void* value, void* data) {
++	const nw_port_vlan_proto_t* proto = (nw_port_vlan_proto_t*)value;
+ 
+ 	return nw_config_set(config, key, nw_port_vlan_proto_to_string(*proto));
+ }
+@@ -67,7 +69,7 @@ static int nw_port_vlan_setup(nw_port* port) {
+ 
+ 	// VLAN Protocol
+ 	r = NW_CONFIG_OPTION(port->config, "VLAN_PROTO", &port->vlan.proto,
+-			nw_port_vlan_read_proto, nw_port_vlan_write_proto);
++			nw_port_vlan_read_proto, nw_port_vlan_write_proto, NULL);
+ 	if (r < 0)
+ 		return r;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0263-string-Define-an-own-type-for-string-tables.patch b/network/patches/0263-string-Define-an-own-type-for-string-tables.patch
new file mode 100644
index 000000000..b309634cb
--- /dev/null
+++ b/network/patches/0263-string-Define-an-own-type-for-string-tables.patch
@@ -0,0 +1,75 @@
+From 644eb1c88dc179c3efdeb6af32c5d153d5483957 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 15:30:41 +0000
+Subject: [PATCH 263/304] string: Define an own type for string tables
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-bonding.c |  2 +-
+ src/networkd/port-vlan.c    |  2 +-
+ src/networkd/string.h       | 12 ++++++------
+ 3 files changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/src/networkd/port-bonding.c b/src/networkd/port-bonding.c
+index 7bd54e1..b1d2c18 100644
+--- a/src/networkd/port-bonding.c
++++ b/src/networkd/port-bonding.c
+@@ -27,7 +27,7 @@
+ #include "port-bonding.h"
+ #include "string.h"
+ 
+-const struct nw_string_table nw_port_bonding_mode[] = {
++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" },
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index e3f93d7..792fd28 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -30,7 +30,7 @@
+ #include "port-vlan.h"
+ #include "string.h"
+ 
+-const struct nw_string_table nw_port_vlan_proto[] = {
++const nw_string_table_t nw_port_vlan_proto[] = {
+ 	{ NW_VLAN_PROTO_8021Q,  "802.1Q" },
+ 	{ NW_VLAN_PROTO_8021ad, "802.1ad" },
+ 	{ -1, NULL },
+diff --git a/src/networkd/string.h b/src/networkd/string.h
+index d94e270..270ed6b 100644
+--- a/src/networkd/string.h
++++ b/src/networkd/string.h
+@@ -129,14 +129,14 @@ static inline void nw_string_empty(char* s) {
+ 	Tables
+ */
+ 
+-struct nw_string_table {
++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 struct nw_string_table* table, const int id) {
+-	const struct nw_string_table* entry = NULL;
++		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)
+@@ -146,8 +146,8 @@ static inline const char* nw_string_table_lookup_string(
+ }
+ 
+ static inline int nw_string_table_lookup_id(
+-		const struct nw_string_table* table, const char* string) {
+-	const struct nw_string_table* entry = NULL;
++		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)
+-- 
+2.39.2
+
diff --git a/network/patches/0264-config-Implement-option-that-looks-up-string-tables.patch b/network/patches/0264-config-Implement-option-that-looks-up-string-tables.patch
new file mode 100644
index 000000000..6faf489ad
--- /dev/null
+++ b/network/patches/0264-config-Implement-option-that-looks-up-string-tables.patch
@@ -0,0 +1,184 @@
+From 7442668a42febaf6ad533c6d7647ad7847cb4775 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 15:46:20 +0000
+Subject: [PATCH 264/304] config: Implement option that looks up string tables
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c       | 37 +++++++++++++++++++++++++++++++++++++
+ src/networkd/config.h       | 14 ++++++++++++++
+ src/networkd/port-bonding.c | 25 ++-----------------------
+ src/networkd/port-vlan.c    | 25 ++-----------------------
+ 4 files changed, 55 insertions(+), 46 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 39e9881..ee5e8b8 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -570,6 +570,43 @@ int nw_config_write_string(nw_config* config,
+ 	return nw_config_set(config, key, *(const char**)value);
+ }
+ 
++// String Table
++
++int nw_config_read_string_table(nw_config* config, const char* key, void* value, 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, 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, void* data) {
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index d6b8db8..63e9d18 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -73,18 +73,32 @@ int nw_config_option_add(nw_config* config, const char* key, void* value,
+ #define NW_CONFIG_OPTION(config, key, value, read_callback, write_callback, data) \
+ 	nw_config_option_add(config, key, value, 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, void* data);
+ int nw_config_write_string(nw_config* config, const char* key, const void* value, void* data);
+ 
++// String Table
++
++#define NW_CONFIG_OPTION_STRING_TABLE(config, key, value, table) \
++	nw_config_option_add(config, key, value, 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, void* data);
++int nw_config_write_string_table(nw_config* config, const char* key, const void* value, void* data);
++
++// Integer
++
+ #define NW_CONFIG_OPTION_INT(config, key, value) \
+ 	nw_config_option_add(config, key, value, nw_config_read_int, nw_config_write_int, NULL)
+ 
+ int nw_config_read_int(nw_config* config, const char* key, void* value, void* data);
+ int nw_config_write_int(nw_config* config, const char* key, const void* value, void* data);
+ 
++// Address
++
+ #define NW_CONFIG_OPTION_ADDRESS(config, key, value) \
+ 	nw_config_option_add(config, key, value, nw_config_read_address, nw_config_write_address, NULL)
+ 
+diff --git a/src/networkd/port-bonding.c b/src/networkd/port-bonding.c
+index b1d2c18..4e4c8a7 100644
+--- a/src/networkd/port-bonding.c
++++ b/src/networkd/port-bonding.c
+@@ -40,33 +40,12 @@ const nw_string_table_t nw_port_bonding_mode[] = {
+ 
+ NW_STRING_TABLE_LOOKUP(nw_port_bonding_mode_t, nw_port_bonding_mode)
+ 
+-static int nw_port_bonding_read_mode(nw_config* config,
+-		const char* key, void* value, void* data) {
+-	int* mode = (int*)value;
+-
+-	const char* p = nw_config_get(config, key);
+-	if (p) {
+-		*mode = nw_port_bonding_mode_from_string(p);
+-		if (!*mode)
+-			return -errno;
+-	}
+-
+-	return 0;
+-}
+-
+-static int nw_port_bonding_write_mode(nw_config* config,
+-		const char* key, const void* value, void* data) {
+-	const int* mode = (int*)value;
+-
+-	return nw_config_set(config, key, nw_port_bonding_mode_to_string(*mode));
+-}
+-
+ static int nw_port_bonding_setup(nw_port* port) {
+ 	int r;
+ 
+ 	// Mode
+-	r = NW_CONFIG_OPTION(port->config, "BONDING_MODE", &port->bonding.mode,
+-			nw_port_bonding_read_mode, nw_port_bonding_write_mode, NULL);
++	r = NW_CONFIG_OPTION_STRING_TABLE(port->config,
++			"BONDING_MODE", &port->bonding.mode, nw_port_bonding_mode);
+ 	if (r < 0)
+ 		return r;
+ 
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index 792fd28..8f5372e 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -38,27 +38,6 @@ const nw_string_table_t nw_port_vlan_proto[] = {
+ 
+ NW_STRING_TABLE_LOOKUP(nw_port_vlan_proto_t, nw_port_vlan_proto)
+ 
+-static int nw_port_vlan_read_proto(nw_config* config,
+-		const char* key, void* value, void* data) {
+-	nw_port_vlan_proto_t* proto = (nw_port_vlan_proto_t*)value;
+-
+-	const char* p = nw_config_get(config, key);
+-	if (p) {
+-		*proto = nw_port_vlan_proto_from_string(p);
+-		if (!*proto)
+-			return -errno;
+-	}
+-
+-	return 0;
+-}
+-
+-static int nw_port_vlan_write_proto(nw_config* config,
+-		const char* key, const void* value, void* data) {
+-	const nw_port_vlan_proto_t* proto = (nw_port_vlan_proto_t*)value;
+-
+-	return nw_config_set(config, key, nw_port_vlan_proto_to_string(*proto));
+-}
+-
+ static int nw_port_vlan_setup(nw_port* port) {
+ 	int r;
+ 
+@@ -68,8 +47,8 @@ static int nw_port_vlan_setup(nw_port* port) {
+ 		return r;
+ 
+ 	// VLAN Protocol
+-	r = NW_CONFIG_OPTION(port->config, "VLAN_PROTO", &port->vlan.proto,
+-			nw_port_vlan_read_proto, nw_port_vlan_write_proto, NULL);
++	r = NW_CONFIG_OPTION_STRING_TABLE(port->config,
++			"VLAN_PROTO", &port->vlan.proto, nw_port_vlan_proto);
+ 	if (r < 0)
+ 		return r;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0265-ports-Store-the-parent-name.patch b/network/patches/0265-ports-Store-the-parent-name.patch
new file mode 100644
index 000000000..62d2d4863
--- /dev/null
+++ b/network/patches/0265-ports-Store-the-parent-name.patch
@@ -0,0 +1,32 @@
+From d200865e663a1a15b43d7462cbcddf7c13dc6997 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 15:48:20 +0000
+Subject: [PATCH 265/304] ports: Store the parent name
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-vlan.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index 8f5372e..45abebe 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -209,9 +209,13 @@ int nw_port_set_vlan_parent(nw_port* port, nw_port* parent) {
+ 	}
+ 
+ 	// Store the new parent
+-	if (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;
+-- 
+2.39.2
+
diff --git a/network/patches/0266-ports-VLAN-Make-all-constants-uppercase.patch b/network/patches/0266-ports-VLAN-Make-all-constants-uppercase.patch
new file mode 100644
index 000000000..cde9e5d33
--- /dev/null
+++ b/network/patches/0266-ports-VLAN-Make-all-constants-uppercase.patch
@@ -0,0 +1,49 @@
+From 4db7e54816eb20319d2f90336a029386b0cd3e91 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 15:49:27 +0000
+Subject: [PATCH 266/304] ports: VLAN: Make all constants uppercase
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-vlan.c | 4 ++--
+ src/networkd/port-vlan.h | 2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index 45abebe..56b0274 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -32,7 +32,7 @@
+ 
+ const nw_string_table_t nw_port_vlan_proto[] = {
+ 	{ NW_VLAN_PROTO_8021Q,  "802.1Q" },
+-	{ NW_VLAN_PROTO_8021ad, "802.1ad" },
++	{ NW_VLAN_PROTO_8021AD, "802.1ad" },
+ 	{ -1, NULL },
+ };
+ 
+@@ -161,7 +161,7 @@ int nw_port_set_vlan_id(nw_port* port, int id) {
+ 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:
++		case NW_VLAN_PROTO_8021AD:
+ 			port->vlan.proto = proto;
+ 			break;
+ 
+diff --git a/src/networkd/port-vlan.h b/src/networkd/port-vlan.h
+index 17d3134..b4df562 100644
+--- a/src/networkd/port-vlan.h
++++ b/src/networkd/port-vlan.h
+@@ -28,7 +28,7 @@
+ 
+ typedef enum nw_port_vlan_proto {
+ 	NW_VLAN_PROTO_8021Q  = ETH_P_8021Q,
+-	NW_VLAN_PROTO_8021ad = ETH_P_8021AD,
++	NW_VLAN_PROTO_8021AD = ETH_P_8021AD,
+ } nw_port_vlan_proto_t;
+ 
+ struct nw_port_vlan {
+-- 
+2.39.2
+
diff --git a/network/patches/0267-ports-Unify-type.patch b/network/patches/0267-ports-Unify-type.patch
new file mode 100644
index 000000000..d0ab4df5c
--- /dev/null
+++ b/network/patches/0267-ports-Unify-type.patch
@@ -0,0 +1,440 @@
+From c464b5d1e146402f153f98a5e5d04b76b4d74e10 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 16:19:05 +0000
+Subject: [PATCH 267/304] ports: Unify type
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-bonding.c | 17 ++++------
+ src/networkd/port-bonding.h |  2 +-
+ src/networkd/port-dummy.c   |  2 +-
+ src/networkd/port-dummy.h   |  2 +-
+ src/networkd/port-vlan.c    | 19 +++++------
+ src/networkd/port-vlan.h    |  2 +-
+ src/networkd/port.c         | 68 ++++++++++++++++---------------------
+ src/networkd/port.h         | 66 +++++++++++++++++------------------
+ 8 files changed, 80 insertions(+), 98 deletions(-)
+
+diff --git a/src/networkd/port-bonding.c b/src/networkd/port-bonding.c
+index 4e4c8a7..ad02b5d 100644
+--- a/src/networkd/port-bonding.c
++++ b/src/networkd/port-bonding.c
+@@ -76,20 +76,17 @@ ERROR:
+ 	return r;
+ }
+ 
+-const nw_port_info_t nw_port_info_bonding = {
++const nw_port_type_t nw_port_type_bonding = {
+ 	.kind = "bond",
+ 
+-	// Operations
+-	.ops = {
+-		// Configuration
+-		.setup = nw_port_bonding_setup,
++	// Configuration
++	.setup = nw_port_bonding_setup,
+ 
+-		// Link
+-		.create_link = nw_port_bonding_create_link,
++	// Link
++	.create_link = nw_port_bonding_create_link,
+ 
+-		// JSON
+-		.to_json = nw_port_bonding_to_json,
+-	},
++	// JSON
++	.to_json = nw_port_bonding_to_json,
+ };
+ 
+ const char* nw_port_bonding_get_mode(nw_port* port) {
+diff --git a/src/networkd/port-bonding.h b/src/networkd/port-bonding.h
+index e21c251..e5c8c32 100644
+--- a/src/networkd/port-bonding.h
++++ b/src/networkd/port-bonding.h
+@@ -39,7 +39,7 @@ struct nw_port_bonding {
+ 	nw_port_bonding_mode_t mode;
+ };
+ 
+-extern const nw_port_info_t nw_port_info_bonding;
++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);
+diff --git a/src/networkd/port-dummy.c b/src/networkd/port-dummy.c
+index e71e740..8a44008 100644
+--- a/src/networkd/port-dummy.c
++++ b/src/networkd/port-dummy.c
+@@ -20,6 +20,6 @@
+ 
+ #include "port-dummy.h"
+ 
+-const nw_port_info_t nw_port_info_dummy = {
++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
+index b74c991..34a7265 100644
+--- a/src/networkd/port-dummy.h
++++ b/src/networkd/port-dummy.h
+@@ -23,6 +23,6 @@
+ 
+ #include "port.h"
+ 
+-extern const nw_port_info_t nw_port_info_dummy;
++extern const nw_port_type_t nw_port_type_dummy;
+ 
+ #endif /* NETWORKD_PORT_DUMMY_H */
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index 56b0274..06047ab 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -106,22 +106,19 @@ ERROR:
+ 	return r;
+ }
+ 
+-const nw_port_info_t nw_port_info_vlan = {
++const nw_port_type_t nw_port_type_vlan = {
+ 	.kind = "vlan",
+ 
+-	// Operations
+-	.ops = {
+-		// Configuration
+-		.setup = nw_port_vlan_setup,
++	// Configuration
++	.setup = nw_port_vlan_setup,
+ 
+-		.get_parent_port = nw_port_get_vlan_parent,
++	.get_parent_port = nw_port_get_vlan_parent,
+ 
+-		// Link
+-		.create_link = nw_port_vlan_create_link,
++	// Link
++	.create_link = nw_port_vlan_create_link,
+ 
+-		// JSON
+-		.to_json = nw_port_vlan_to_json,
+-	},
++	// JSON
++	.to_json = nw_port_vlan_to_json,
+ };
+ 
+ /*
+diff --git a/src/networkd/port-vlan.h b/src/networkd/port-vlan.h
+index b4df562..6ecf6a4 100644
+--- a/src/networkd/port-vlan.h
++++ b/src/networkd/port-vlan.h
+@@ -45,7 +45,7 @@ struct nw_port_vlan {
+ 	char __parent_name[IF_NAMESIZE];
+ };
+ 
+-extern const nw_port_info_t nw_port_info_vlan;
++extern const nw_port_type_t nw_port_type_vlan;
+ 
+ // ID
+ int nw_port_get_vlan_id(nw_port* port);
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index cab8fc5..c4d6ffe 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -37,26 +37,14 @@
+ #include "stats-collector.h"
+ #include "string.h"
+ 
+-static const struct nw_port_type_map {
+-	nw_port_type_t type;
+-	const char* name;
+-} nw_port_type_map[] = {
++static const nw_string_table_t nw_port_type_id[] = {
+ 	{ NW_PORT_BONDING, "bonding" },
+ 	{ NW_PORT_DUMMY,   "dummy" },
+ 	{ NW_PORT_VLAN,    "vlan" },
+-	{ NW_PORT_UNKNOWN, NULL },
++	{ -1, NULL },
+ };
+ 
+-static nw_port_type_t nw_port_type_from_string(const char* s) {
+-	const struct nw_port_type_map* map = NULL;
+-
+-	for (map = nw_port_type_map; *map->name; map++) {
+-		if (strcmp(map->name, s) == 0)
+-			return map->type;
+-	}
+-
+-	return NW_PORT_UNKNOWN;
+-}
++NW_STRING_TABLE_LOOKUP(nw_port_type_id_t, nw_port_type_id)
+ 
+ static void nw_port_free(nw_port* port) {
+ 	if (port->link)
+@@ -119,8 +107,8 @@ static int nw_port_setup(nw_port* port) {
+ 		goto ERROR;
+ 
+ 	// Call any custom initialization
+-	if (NW_PORT_OPS(port)->setup) {
+-		r = NW_PORT_OPS(port)->setup(port);
++	if (NW_PORT_TYPE(port)->setup) {
++		r = NW_PORT_TYPE(port)->setup(port);
+ 		if (r < 0)
+ 			goto ERROR;
+ 	}
+@@ -140,7 +128,7 @@ ERROR:
+ }
+ 
+ int nw_port_create(nw_port** port, nw_daemon* daemon,
+-		nw_port_type_t type, const char* name, nw_config* config) {
++		const nw_port_type_id_t type, const char* name, nw_config* config) {
+ 	int r;
+ 
+ 	// Allocate a new object
+@@ -154,21 +142,18 @@ int nw_port_create(nw_port** port, nw_daemon* daemon,
+ 	// Initialize reference counter
+ 	p->nrefs = 1;
+ 
+-	// Store the type
+-	p->type = type;
+-
+ 	// Set operations
+-	switch (p->type) {
++	switch (type) {
+ 		case NW_PORT_BONDING:
+-			p->info = &nw_port_info_bonding;
++			p->type = &nw_port_type_bonding;
+ 			break;
+ 
+ 		case NW_PORT_DUMMY:
+-			p->info = &nw_port_info_dummy;
++			p->type = &nw_port_type_dummy;
+ 			break;
+ 
+ 		case NW_PORT_VLAN:
+-			p->info = &nw_port_info_vlan;
++			p->type = &nw_port_type_vlan;
+ 			break;
+ 	}
+ 
+@@ -214,7 +199,7 @@ int nw_port_create_from_config(nw_port** port, nw_daemon* daemon,
+ 	}
+ 
+ 	// Create a new port
+-	r = nw_port_create(port, daemon, nw_port_type_from_string(type), name, config);
++	r = nw_port_create(port, daemon, nw_port_type_id_from_string(type), name, config);
+ 	if (r)
+ 		goto ERROR;
+ 
+@@ -286,7 +271,7 @@ 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) {
++	switch (port->type->id) {
+ 		case NW_PORT_VLAN:
+ 			if (port->vlan.parent == dropped_port) {
+ 				r = nw_port_set_vlan_parent(port, NULL);
+@@ -392,10 +377,10 @@ static int nw_port_is_disabled(nw_port* port) {
+ }
+ 
+ nw_port* nw_port_get_parent_port(nw_port* port) {
+-	if (!NW_PORT_OPS(port)->get_parent_port)
++	if (!NW_PORT_TYPE(port)->get_parent_port)
+ 		return NULL;
+ 
+-	return NW_PORT_OPS(port)->get_parent_port(port);
++	return NW_PORT_TYPE(port)->get_parent_port(port);
+ }
+ 
+ static nw_link* nw_port_get_parent_link(nw_port* port) {
+@@ -444,6 +429,13 @@ static int nw_port_create_link(nw_port* port) {
+ 
+ 	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) {
+@@ -481,14 +473,14 @@ static int nw_port_create_link(nw_port* port) {
+ 		goto ERROR;
+ 
+ 	// Run the custom setup
+-	if (NW_PORT_OPS(port)->create_link) {
+-		r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, NW_PORT_INFO(port)->kind);
++	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_OPS(port)->create_link(port, m);
++		r = NW_PORT_TYPE(port)->create_link(port, m);
+ 		if (r) {
+ 			ERROR("Could not create port %s: %m\n", port->name);
+ 			goto ERROR;
+@@ -501,7 +493,7 @@ static int nw_port_create_link(nw_port* port) {
+ 
+ 	// Just set IFLA_INFO_KIND if there is no custom function
+ 	} else {
+-		r = sd_netlink_message_append_string(m, IFLA_INFO_KIND, NW_PORT_INFO(port)->kind);
++		r = sd_netlink_message_append_string(m, IFLA_INFO_KIND, NW_PORT_TYPE(port)->kind);
+ 		if (r < 0)
+ 			goto ERROR;
+ 	}
+@@ -588,8 +580,8 @@ int nw_port_update_stats(nw_port* port) {
+ 	return 0;
+ }
+ 
+-int nw_port_check_type(nw_port* port, const nw_port_type_t type) {
+-	if (port->type == type)
++int nw_port_check_type(nw_port* port, const nw_port_type_id_t type) {
++	if (port->type->id == type)
+ 		return 0;
+ 
+ 	errno = ENOTSUP;
+@@ -616,7 +608,7 @@ int nw_port_to_json(nw_port* port, struct json_object** object) {
+ 		goto ERROR;
+ 
+ 	// Add Type
+-	r = json_object_add_string(o, "Type", NW_PORT_INFO(port)->kind);
++	r = json_object_add_string(o, "Type", nw_port_type_id_to_string(port->type->id));
+ 	if (r < 0)
+ 		goto ERROR;
+ 
+@@ -629,8 +621,8 @@ int nw_port_to_json(nw_port* port, struct json_object** object) {
+ 	}
+ 
+ 	// Call custom stuff
+-	if (NW_PORT_OPS(port)->to_json) {
+-		r = NW_PORT_OPS(port)->to_json(port, o);
++	if (NW_PORT_TYPE(port)->to_json) {
++		r = NW_PORT_TYPE(port)->to_json(port, o);
+ 		if (r < 0)
+ 			goto ERROR;
+ 	}
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index ef68a89..c9add71 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -31,50 +31,49 @@
+ 
+ #define PORT_CONFIG_DIR			CONFIG_DIR "/ports"
+ 
+-typedef enum nw_port_type {
+-	NW_PORT_UNKNOWN = 0,
+-	NW_PORT_BONDING,
+-	NW_PORT_DUMMY,
+-	NW_PORT_VLAN,
+-} nw_port_type_t;
+-
+ // VLAN
+ #define NW_VLAN_ID_INVALID		0
+ #define NW_VLAN_ID_MIN			1
+ #define NW_VLAN_ID_MAX			4096
+ 
+ typedef struct nw_port nw_port;
+-typedef struct nw_port_info nw_port_info_t;
+ 
+-#include "address.h"
+-#include "config.h"
+-#include "daemon.h"
+-#include "json.h"
+-#include "port-bonding.h"
+-#include "port-vlan.h"
++typedef enum nw_port_type_id {
++	NW_PORT_UNKNOWN = 0,
++	NW_PORT_BONDING,
++	NW_PORT_DUMMY,
++	NW_PORT_VLAN,
++} nw_port_type_id_t;
++
++typedef struct nw_port_type {
++	// Type ID
++	nw_port_type_id_t id;
+ 
+-struct nw_port_info {
+ 	// IFLA_INFO_KIND/IFLA_INFO_DATA
+ 	const char* kind;
+ 
+-	struct nw_port_ops {
+-		// Configuration
+-		int (*setup)(nw_port* port);
++	// Configuration
++	int (*setup)(nw_port* port);
+ 
+-		// Get Parent Port
+-		nw_port* (*get_parent_port)(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);
++	// 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);
+-	} ops;
+-};
++	// 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-vlan.h"
+ 
+-#define NW_PORT_INFO(port) (port->info)
+-#define NW_PORT_OPS(port) (&NW_PORT_INFO(port)->ops)
++#define NW_PORT_TYPE(port) (port->type)
+ 
+ struct nw_port {
+ 	nw_daemon* daemon;
+@@ -83,7 +82,7 @@ struct nw_port {
+ 	// Link
+ 	nw_link* link;
+ 
+-	nw_port_type_t type;
++	const nw_port_type_t* type;
+ 	char name[IF_NAMESIZE];
+ 
+ 	// Configuration
+@@ -92,9 +91,6 @@ struct nw_port {
+ 	// Common attributes
+ 	nw_address_t address;
+ 
+-	// Type Operations
+-	const nw_port_info_t* info;
+-
+ 	// Bonding Settings
+ 	struct nw_port_bonding bonding;
+ 
+@@ -103,7 +99,7 @@ struct nw_port {
+ };
+ 
+ int nw_port_create(nw_port** port, nw_daemon* daemon,
+-	nw_port_type_t type, const char* name, nw_config* config);
++	const nw_port_type_id_t type, const char* name, nw_config* config);
+ int nw_port_create_from_config(nw_port** port, nw_daemon* daemon,
+ 	const char* name, const char* path);
+ 
+@@ -130,7 +126,7 @@ 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_t type);
++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);
+-- 
+2.39.2
+
diff --git a/network/patches/0268-ports-Move-VLAN-constants-to-VLAN-header.patch b/network/patches/0268-ports-Move-VLAN-constants-to-VLAN-header.patch
new file mode 100644
index 000000000..87b90df53
--- /dev/null
+++ b/network/patches/0268-ports-Move-VLAN-constants-to-VLAN-header.patch
@@ -0,0 +1,46 @@
+From 81ab55ae98a4280265fc7b3bee2c3770d621c152 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 16:22:11 +0000
+Subject: [PATCH 268/304] ports: Move VLAN constants to VLAN header
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-vlan.h | 5 +++++
+ src/networkd/port.h      | 5 -----
+ 2 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/src/networkd/port-vlan.h b/src/networkd/port-vlan.h
+index 6ecf6a4..e17f7e7 100644
+--- a/src/networkd/port-vlan.h
++++ b/src/networkd/port-vlan.h
+@@ -31,6 +31,11 @@ typedef enum nw_port_vlan_proto {
+ 	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;
+ 
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index c9add71..15b8bc1 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -31,11 +31,6 @@
+ 
+ #define PORT_CONFIG_DIR			CONFIG_DIR "/ports"
+ 
+-// VLAN
+-#define NW_VLAN_ID_INVALID		0
+-#define NW_VLAN_ID_MIN			1
+-#define NW_VLAN_ID_MAX			4096
+-
+ typedef struct nw_port nw_port;
+ 
+ typedef enum nw_port_type_id {
+-- 
+2.39.2
+
diff --git a/network/patches/0269-ports-Drop-UNKNOWN-type.patch b/network/patches/0269-ports-Drop-UNKNOWN-type.patch
new file mode 100644
index 000000000..a82298573
--- /dev/null
+++ b/network/patches/0269-ports-Drop-UNKNOWN-type.patch
@@ -0,0 +1,48 @@
+From b249255d5ca7cdff8ea97e0a0cdd39f1bf0e6e63 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 16:26:13 +0000
+Subject: [PATCH 269/304] ports: Drop UNKNOWN type
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c | 4 ----
+ src/networkd/port.h | 1 -
+ 2 files changed, 5 deletions(-)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index c4d6ffe..c14d439 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -261,9 +261,6 @@ int nw_port_destroy(nw_port* port) {
+ 	if (r)
+ 		return r;
+ 
+-	// Reset type
+-	port->type = NW_PORT_UNKNOWN;
+-
+ 	return 0;
+ }
+ 
+@@ -282,7 +279,6 @@ int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
+ 
+ 		case NW_PORT_BONDING:
+ 		case NW_PORT_DUMMY:
+-		case NW_PORT_UNKNOWN:
+ 			break;
+ 	}
+ 
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 15b8bc1..b96b13e 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -34,7 +34,6 @@
+ typedef struct nw_port nw_port;
+ 
+ typedef enum nw_port_type_id {
+-	NW_PORT_UNKNOWN = 0,
+ 	NW_PORT_BONDING,
+ 	NW_PORT_DUMMY,
+ 	NW_PORT_VLAN,
+-- 
+2.39.2
+
diff --git a/network/patches/0270-ports-Implement-scaffolding-for-configuration.patch b/network/patches/0270-ports-Implement-scaffolding-for-configuration.patch
new file mode 100644
index 000000000..b04a1bf4e
--- /dev/null
+++ b/network/patches/0270-ports-Implement-scaffolding-for-configuration.patch
@@ -0,0 +1,74 @@
+From ea7dc1bbab6162c319ce738161af813f2d1b7241 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 16:35:33 +0000
+Subject: [PATCH 270/304] ports: Implement scaffolding for configuration
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.c | 30 ++++++++++++++++++++++++++++++
+ src/networkd/port.h |  1 +
+ 2 files changed, 31 insertions(+)
+
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index c14d439..fb5d418 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -127,6 +127,19 @@ ERROR:
+ 	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;
+@@ -172,6 +185,23 @@ int nw_port_create(nw_port** port, nw_daemon* daemon,
+ 	if (r)
+ 		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;
+ 
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index b96b13e..7c2e436 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -48,6 +48,7 @@ typedef struct nw_port_type {
+ 
+ 	// Configuration
+ 	int (*setup)(nw_port* port);
++	int (*validate)(nw_port* port);
+ 
+ 	// Get Parent Port
+ 	nw_port* (*get_parent_port)(nw_port* port);
+-- 
+2.39.2
+
diff --git a/network/patches/0271-ports-VLAN-Validate-configuration.patch b/network/patches/0271-ports-VLAN-Validate-configuration.patch
new file mode 100644
index 000000000..6078928b7
--- /dev/null
+++ b/network/patches/0271-ports-VLAN-Validate-configuration.patch
@@ -0,0 +1,53 @@
+From 749b1a85fdbf4ec0ec0ac3b3fe07cdeffd72460c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 16:41:13 +0000
+Subject: [PATCH 271/304] ports: VLAN: Validate configuration
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-vlan.c | 22 ++++++++++++++++++++++
+ 1 file changed, 22 insertions(+)
+
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index 06047ab..25a59ee 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -60,6 +60,27 @@ static int nw_port_vlan_setup(nw_port* port) {
+ 	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;
+ 
+@@ -111,6 +132,7 @@ const nw_port_type_t nw_port_type_vlan = {
+ 
+ 	// Configuration
+ 	.setup = nw_port_vlan_setup,
++	.validate = nw_port_vlan_validate,
+ 
+ 	.get_parent_port = nw_port_get_vlan_parent,
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0272-daemon-Don-t-crash-when-a-port-could-not-be-loaded.patch b/network/patches/0272-daemon-Don-t-crash-when-a-port-could-not-be-loaded.patch
new file mode 100644
index 000000000..a4daf071d
--- /dev/null
+++ b/network/patches/0272-daemon-Don-t-crash-when-a-port-could-not-be-loaded.patch
@@ -0,0 +1,40 @@
+From ac7659bf6a5f84f473424c6976e4ccd6c2e72979 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 16:46:12 +0000
+Subject: [PATCH 272/304] daemon: Don't crash when a port could not be loaded
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/ports.c | 16 ++++++++++++++--
+ 1 file changed, 14 insertions(+), 2 deletions(-)
+
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+index f0a3ebb..65545e8 100644
+--- a/src/networkd/ports.c
++++ b/src/networkd/ports.c
+@@ -152,8 +152,20 @@ static int __nw_ports_enumerate(const char* path, const struct stat* s, void* da
+ 
+ 	// Create a new port
+ 	r = nw_port_create_from_config(&port, ports->daemon, name, path);
+-	if (r)
+-		goto ERROR;
++	switch (r) {
++		// All okay
++		case 0:
++			break;
++
++		// Invalid configuration
++		case 1:
++			ERROR("Could not open port %s\n", name);
++			r = 0;
++			goto ERROR;
++
++		default:
++			goto ERROR;
++	}
+ 
+ 	// Add the port to the list
+ 	r = nw_ports_add_port(ports, port);
+-- 
+2.39.2
+
diff --git a/network/patches/0273-string-Have-all-functions-return-negative-values-on-.patch b/network/patches/0273-string-Have-all-functions-return-negative-values-on-.patch
new file mode 100644
index 000000000..ad88f04bf
--- /dev/null
+++ b/network/patches/0273-string-Have-all-functions-return-negative-values-on-.patch
@@ -0,0 +1,33 @@
+From 45a3d5aaca4e1d64132d7b4190209bb743efaa68 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Thu, 8 Jun 2023 17:05:54 +0000
+Subject: [PATCH 273/304] string: Have all functions return negative values on
+ error
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/string.h | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+diff --git a/src/networkd/string.h b/src/networkd/string.h
+index 270ed6b..a36b023 100644
+--- a/src/networkd/string.h
++++ b/src/networkd/string.h
+@@ -37,12 +37,11 @@ static inline int __nw_string_vformat(char* s, const size_t length,
+ 
+ 	// Catch any errors
+ 	if (required < 0)
+-		return 1;
++		return required;
+ 
+ 	// Check if the entire string could be written
+ 	if ((size_t)required >= length) {
+-		errno = ENOMEM;
+-		return 1;
++		return -ENOMEM;
+ 	}
+ 
+ 	// Success
+-- 
+2.39.2
+
diff --git a/network/patches/0274-networkd-Parse-command-line-arguments.patch b/network/patches/0274-networkd-Parse-command-line-arguments.patch
new file mode 100644
index 000000000..de3674bec
--- /dev/null
+++ b/network/patches/0274-networkd-Parse-command-line-arguments.patch
@@ -0,0 +1,132 @@
+From 3138451ba526d9ada4e74cffccd10c5807c9bc8e Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 04:58:39 +0000
+Subject: [PATCH 274/304] networkd: Parse command line arguments
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 45 ++++++++++++++++++++++++++++++++++++++++++-
+ src/networkd/daemon.h |  2 +-
+ src/networkd/main.c   |  2 +-
+ 3 files changed, 46 insertions(+), 3 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 749a70b..31fda8e 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -19,6 +19,8 @@
+ #############################################################################*/
+ 
+ #include <errno.h>
++#include <getopt.h>
++#include <limits.h>
+ #include <stdlib.h>
+ 
+ #include <systemd/sd-bus.h>
+@@ -36,6 +38,7 @@
+ #include "logging.h"
+ #include "ports.h"
+ #include "stats-collector.h"
++#include "string.h"
+ #include "zone.h"
+ #include "zones.h"
+ 
+@@ -45,6 +48,7 @@
+ struct nw_daemon {
+ 	int nrefs;
+ 
++	char config_path[PATH_MAX];
+ 	nw_config* config;
+ 
+ 	// Event Loop
+@@ -91,6 +95,40 @@ static int __nw_daemon_reload(sd_event_source* source, const struct signalfd_sig
+ 	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_string_set(daemon->config_path, 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;
+ 
+@@ -398,7 +436,7 @@ static int nw_daemon_setup(nw_daemon* daemon) {
+ 	return 0;
+ }
+ 
+-int nw_daemon_create(nw_daemon** daemon) {
++int nw_daemon_create(nw_daemon** daemon, int argc, char* argv[]) {
+ 	int r;
+ 
+ 	nw_daemon* d = calloc(1, sizeof(*d));
+@@ -408,6 +446,11 @@ int nw_daemon_create(nw_daemon** daemon) {
+ 	// 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)
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index 74e19e6..8653af3 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -33,7 +33,7 @@ typedef struct nw_daemon nw_daemon;
+ #include "zone.h"
+ #include "zones.h"
+ 
+-int nw_daemon_create(nw_daemon** daemon);
++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);
+diff --git a/src/networkd/main.c b/src/networkd/main.c
+index c8b9a79..f5f09f5 100644
+--- a/src/networkd/main.c
++++ b/src/networkd/main.c
+@@ -222,7 +222,7 @@ int main(int argc, char** argv) {
+ 		return r;
+ 
+ 	// Create the daemon
+-	r = nw_daemon_create(&daemon);
++	r = nw_daemon_create(&daemon, argc, argv);
+ 	if (r)
+ 		return r;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0275-networkd-Open-config-directory-and-keep-a-handle-to-.patch b/network/patches/0275-networkd-Open-config-directory-and-keep-a-handle-to-.patch
new file mode 100644
index 000000000..cef8539d8
--- /dev/null
+++ b/network/patches/0275-networkd-Open-config-directory-and-keep-a-handle-to-.patch
@@ -0,0 +1,163 @@
+From 5a925d5f91e32245446ba568da2549b08913c0c7 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 05:28:51 +0000
+Subject: [PATCH 275/304] networkd: Open config directory and keep a handle to
+ it
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c |  2 +-
+ src/networkd/config.h |  1 +
+ src/networkd/daemon.c | 71 ++++++++++++++++++++++++++++++++++++++-----
+ 3 files changed, 66 insertions(+), 8 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index ee5e8b8..b7f3f72 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -229,7 +229,7 @@ int nw_config_flush(nw_config* config) {
+ 	return 0;
+ }
+ 
+-static int nw_config_readf(nw_config* config, FILE* f) {
++int nw_config_readf(nw_config* config, FILE* f) {
+ 	char* line = NULL;
+ 	size_t length = 0;
+ 	int r;
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index 63e9d18..d532da3 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -40,6 +40,7 @@ const char* nw_config_path(nw_config* config);
+ 
+ int nw_config_flush(nw_config* config);
+ 
++int nw_config_readf(nw_config* config, FILE* f);
+ int nw_config_read(nw_config* config);
+ int nw_config_write(nw_config* config);
+ 
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index 31fda8e..e645bee 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -18,7 +18,9 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <dirent.h>
+ #include <errno.h>
++#include <fcntl.h>
+ #include <getopt.h>
+ #include <limits.h>
+ #include <stdlib.h>
+@@ -48,7 +50,7 @@
+ struct nw_daemon {
+ 	int nrefs;
+ 
+-	char config_path[PATH_MAX];
++	DIR* config_dir;
+ 	nw_config* config;
+ 
+ 	// Event Loop
+@@ -95,6 +97,43 @@ static int __nw_daemon_reload(sd_event_source* source, const struct signalfd_sig
+ 	return 0;
+ }
+ 
++/*
++	Configuration
++*/
++
++static int nw_daemon_config_open(nw_daemon* daemon, const char* path) {
++	daemon->config_dir = opendir(path);
++	if (!daemon->config_dir) {
++		ERROR("Could not open %s: %m\n", path);
++		return -errno;
++	}
++
++	return 0;
++}
++
++static FILE* nw_daemon_config_fopen(nw_daemon* daemon, const char* path, const char* mode) {
++	int r;
++
++	// If no configuration path has been opened yet, we will open something
++	if (!daemon->config_dir) {
++		r = nw_daemon_config_open(daemon, CONFIG_DIR);
++		if (r < 0) {
++			errno = -r;
++			return NULL;
++		}
++	}
++
++	// Open the file
++	int fd = openat(dirfd(daemon->config_dir), path, 0);
++	if (fd < 0) {
++		ERROR("Could not open configuration file %s: %m\n", path);
++		return NULL;
++	}
++
++	// Return a file handle
++	return fdopen(fd, mode);
++}
++
+ static int nw_daemon_parse_argv(nw_daemon* daemon, int argc, char* argv[]) {
+ 	enum {
+ 		ARG_CONFIG,
+@@ -114,10 +153,9 @@ static int nw_daemon_parse_argv(nw_daemon* daemon, int argc, char* argv[]) {
+ 
+ 		switch (c) {
+ 			case ARG_CONFIG:
+-				r = nw_string_set(daemon->config_path, optarg);
++				r = nw_daemon_config_open(daemon, optarg);
+ 				if (r < 0)
+ 					return r;
+-
+ 				break;
+ 
+ 			// Abort on any unrecognised option
+@@ -174,12 +212,29 @@ static int nw_daemon_setup_loop(nw_daemon* daemon) {
+ }
+ 
+ static int nw_daemon_load_config(nw_daemon* daemon) {
++	FILE* f = NULL;
+ 	int r;
+ 
+-	// Read configuration file
+-	r = nw_config_create(&daemon->config, CONFIG_DIR "/settings");
+-	if (r)
+-		return r;
++	// Open the configuration file
++	f = nw_daemon_config_fopen(daemon, "settings", "r");
++	if (!f) {
++		r = -errno;
++		goto ERROR;
++	}
++
++	// Create configuration
++	r = nw_config_create(&daemon->config, NULL);
++	if (r < 0)
++		goto ERROR;
++
++	// Parse configuration
++	r = nw_config_readf(daemon->config, f);
++	if (r < 0)
++		goto ERROR;
++
++ERROR:
++	if (f)
++		fclose(f);
+ 
+ 	return r;
+ }
+@@ -482,6 +537,8 @@ static void nw_daemon_free(nw_daemon* daemon) {
+ 	// Cleanup common objects
+ 	nw_daemon_cleanup(daemon);
+ 
++	if (daemon->config_dir)
++		closedir(daemon->config_dir);
+ 	if (daemon->stats_collector_event)
+ 		sd_event_source_unref(daemon->stats_collector_event);
+ 	if (daemon->bus)
+-- 
+2.39.2
+
diff --git a/network/patches/0276-networkd-Hold-a-file-descriptor-instead-of-DIR.patch b/network/patches/0276-networkd-Hold-a-file-descriptor-instead-of-DIR.patch
new file mode 100644
index 000000000..f6e74eecd
--- /dev/null
+++ b/network/patches/0276-networkd-Hold-a-file-descriptor-instead-of-DIR.patch
@@ -0,0 +1,99 @@
+From f6e3fd80ad4d86681ad2949567b6763c41530f16 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 05:37:57 +0000
+Subject: [PATCH 276/304] networkd: Hold a file descriptor instead of DIR*
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/daemon.c | 34 ++++++++++++++++------------------
+ 1 file changed, 16 insertions(+), 18 deletions(-)
+
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index e645bee..f0ef4aa 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -18,12 +18,12 @@
+ #                                                                             #
+ #############################################################################*/
+ 
+-#include <dirent.h>
+ #include <errno.h>
+ #include <fcntl.h>
+ #include <getopt.h>
+ #include <limits.h>
+ #include <stdlib.h>
++#include <unistd.h>
+ 
+ #include <systemd/sd-bus.h>
+ #include <systemd/sd-daemon.h>
+@@ -50,7 +50,8 @@
+ struct nw_daemon {
+ 	int nrefs;
+ 
+-	DIR* config_dir;
++	// Configuration
++	int configfd;
+ 	nw_config* config;
+ 
+ 	// Event Loop
+@@ -102,8 +103,9 @@ static int __nw_daemon_reload(sd_event_source* source, const struct signalfd_sig
+ */
+ 
+ static int nw_daemon_config_open(nw_daemon* daemon, const char* path) {
+-	daemon->config_dir = opendir(path);
+-	if (!daemon->config_dir) {
++	// Open the directory
++	daemon->configfd = open(path, O_DIRECTORY);
++	if (daemon->configfd < 0) {
+ 		ERROR("Could not open %s: %m\n", path);
+ 		return -errno;
+ 	}
+@@ -112,19 +114,8 @@ static int nw_daemon_config_open(nw_daemon* daemon, const char* path) {
+ }
+ 
+ static FILE* nw_daemon_config_fopen(nw_daemon* daemon, const char* path, const char* mode) {
+-	int r;
+-
+-	// If no configuration path has been opened yet, we will open something
+-	if (!daemon->config_dir) {
+-		r = nw_daemon_config_open(daemon, CONFIG_DIR);
+-		if (r < 0) {
+-			errno = -r;
+-			return NULL;
+-		}
+-	}
+-
+ 	// Open the file
+-	int fd = openat(dirfd(daemon->config_dir), path, 0);
++	int fd = openat(daemon->configfd, path, 0);
+ 	if (fd < 0) {
+ 		ERROR("Could not open configuration file %s: %m\n", path);
+ 		return NULL;
+@@ -215,6 +206,13 @@ static int nw_daemon_load_config(nw_daemon* daemon) {
+ 	FILE* f = NULL;
+ 	int r;
+ 
++	// If no configuration path has been opened yet, we will open something
++	if (!daemon->configfd) {
++		r = nw_daemon_config_open(daemon, CONFIG_DIR);
++		if (r < 0)
++			goto ERROR;
++	}
++
+ 	// Open the configuration file
+ 	f = nw_daemon_config_fopen(daemon, "settings", "r");
+ 	if (!f) {
+@@ -537,8 +535,8 @@ static void nw_daemon_free(nw_daemon* daemon) {
+ 	// Cleanup common objects
+ 	nw_daemon_cleanup(daemon);
+ 
+-	if (daemon->config_dir)
+-		closedir(daemon->config_dir);
++	if (daemon->configfd > 0)
++		close(daemon->configfd);
+ 	if (daemon->stats_collector_event)
+ 		sd_event_source_unref(daemon->stats_collector_event);
+ 	if (daemon->bus)
+-- 
+2.39.2
+
diff --git a/network/patches/0277-networkd-Add-a-simple-test-environment.patch b/network/patches/0277-networkd-Add-a-simple-test-environment.patch
new file mode 100644
index 000000000..34754b7f2
--- /dev/null
+++ b/network/patches/0277-networkd-Add-a-simple-test-environment.patch
@@ -0,0 +1,197 @@
+From c920185ee99d0f143946925aba0668785225f6f4 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 07:46:40 +0000
+Subject: [PATCH 277/304] networkd: Add a simple test environment
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                               |  24 ++++-
+ test/networkd/00_launch.t/config/settings |   0
+ test/networkd/00_launch.t/test.sh         |   4 +
+ test/networkd/test.sh                     | 108 ++++++++++++++++++++++
+ 4 files changed, 132 insertions(+), 4 deletions(-)
+ create mode 100644 test/networkd/00_launch.t/config/settings
+ create mode 100644 test/networkd/00_launch.t/test.sh
+ create mode 100644 test/networkd/test.sh
+
+diff --git a/Makefile.am b/Makefile.am
+index f4c22b4..fe1e4d1 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -59,6 +59,7 @@ DISTCLEANFILES =
+ EXTRA_DIST =
+ INSTALL_DIRS =
+ INSTALL_EXEC_HOOKS =
++TESTS =
+ UNINSTALL_EXEC_HOOKS =
+ noinst_DATA =
+ network_PROGRAMS =
+@@ -690,12 +691,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 \
+@@ -706,6 +705,23 @@ 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
++
++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 \
++	$(SHELL) test/networkd/test.sh
++
+ # - NITSI tests ----------------------------------------------------------------
+ 
+ # Files for the virtual environment
+diff --git a/test/networkd/00_launch.t/config/settings b/test/networkd/00_launch.t/config/settings
+new file mode 100644
+index 0000000..e69de29
+diff --git a/test/networkd/00_launch.t/test.sh b/test/networkd/00_launch.t/test.sh
+new file mode 100644
+index 0000000..f3d7bbc
+--- /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/test.sh b/test/networkd/test.sh
+new file mode 100644
+index 0000000..2af7479
+--- /dev/null
++++ b/test/networkd/test.sh
+@@ -0,0 +1,108 @@
++#!/bin/bash
++###############################################################################
++#                                                                             #
++# IPFire.org - A linux based firewall                                         #
++# Copyright (C) 2023 IPFire Network Development Team                          #
++#                                                                             #
++# This program is free software: you can redistribute it and/or modify        #
++# it under the terms of the GNU General Public License as published by        #
++# the Free Software Foundation, either version 3 of the License, or           #
++# (at your option) any later version.                                         #
++#                                                                             #
++# This program is distributed in the hope that it will be useful,             #
++# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
++# GNU General Public License for more details.                                #
++#                                                                             #
++# You should have received a copy of the GNU General Public License           #
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++###############################################################################
++
++# Break if anything fails
++set -e
++
++# Turn on job control
++set -o monitor
++
++run_script() {
++	local script="${1}"
++	shift
++
++	if [ -f "${script}" ]; then
++		echo "Launching ${script}..."
++
++		# Launch the script in a separate shell and echo every command
++		if ! ${SHELL} -xe "${script}"; then
++			echo "${script} failed" >&2
++			return 1
++		fi
++	fi
++
++	return 0
++}
++
++# Launches networkd in the background
++launch_networkd() {
++	echo "Launching networkd..."
++
++	# Launch it!
++	coproc networkd { ./networkd "$@"; }
++
++	echo "networkd launched as PID ${networkd_PID}"
++}
++
++terminate_networkd() {
++	if [ -n "${networkd_PID}" ]; then
++		# Send SIGTERM
++		kill -TERM "${networkd_PID}"
++
++		# Wait until networkd has finished
++		echo "Waiting for networkd to terminate..."
++		wait "${networkd_PID}"
++
++		echo "networkd has terminated"
++	fi
++}
++
++# Make sure networkd has been terminated when this script exits
++trap "terminate_networkd" 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 $?
+-- 
+2.39.2
+
diff --git a/network/patches/0278-test-Run-ip-d-link-to-show-the-status-of-the-environ.patch b/network/patches/0278-test-Run-ip-d-link-to-show-the-status-of-the-environ.patch
new file mode 100644
index 000000000..c471daed9
--- /dev/null
+++ b/network/patches/0278-test-Run-ip-d-link-to-show-the-status-of-the-environ.patch
@@ -0,0 +1,48 @@
+From 9ddb7dad28a2122317b0551884640afdf14deadb Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 07:49:54 +0000
+Subject: [PATCH 278/304] test: Run "ip -d link" to show the status of the
+ environment
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ test/networkd/test.sh | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+diff --git a/test/networkd/test.sh b/test/networkd/test.sh
+index 2af7479..a22aeb3 100644
+--- a/test/networkd/test.sh
++++ b/test/networkd/test.sh
+@@ -42,6 +42,19 @@ run_script() {
+ 	return 0
+ }
+ 
++dump_command() {
++	echo "Output of $@"
++
++	# Run the command
++	$@ 2>&1
++
++	echo "EOF"
++}
++
++dump_status() {
++	dump_command "ip -d link"
++}
++
+ # Launches networkd in the background
+ launch_networkd() {
+ 	echo "Launching networkd..."
+@@ -54,6 +67,9 @@ launch_networkd() {
+ 
+ terminate_networkd() {
+ 	if [ -n "${networkd_PID}" ]; then
++		# Collect some status information
++		dump_status
++
+ 		# Send SIGTERM
+ 		kill -TERM "${networkd_PID}"
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0279-networkctl-Terminate-after-showing-help-or-version.patch b/network/patches/0279-networkctl-Terminate-after-showing-help-or-version.patch
new file mode 100644
index 000000000..ea97f6077
--- /dev/null
+++ b/network/patches/0279-networkctl-Terminate-after-showing-help-or-version.patch
@@ -0,0 +1,64 @@
+From dc2a5320b848576772c261b76b23b87715d118a7 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 07:51:50 +0000
+Subject: [PATCH 279/304] networkctl: Terminate after showing help or version
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkctl/main.c | 13 +++++++------
+ 1 file changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/src/networkctl/main.c b/src/networkctl/main.c
+index 0ba7284..fde77b8 100644
+--- a/src/networkctl/main.c
++++ b/src/networkctl/main.c
+@@ -21,6 +21,7 @@
+ #include <errno.h>
+ #include <getopt.h>
+ #include <stdio.h>
++#include <stdlib.h>
+ #include <unistd.h>
+ 
+ #include <systemd/sd-bus.h>
+@@ -45,13 +46,13 @@ static int networkctl_main(sd_bus* bus, int argc, char* argv[]) {
+ 	return command_dispatch(bus, commands, argc, argv);
+ }
+ 
+-static int version(void) {
++static void version(void) {
+ 	printf("networkctl %s\n", PACKAGE_VERSION);
+ 
+-	return 0;
++	exit(0);
+ }
+ 
+-static int help(void) {
++static void help(void) {
+ 	printf(
+ 		"%s [OPTIONS...] COMMAND\n\n"
+ 		"Options:\n"
+@@ -60,7 +61,7 @@ static int help(void) {
+ 		program_invocation_short_name
+ 	);
+ 
+-	return 0;
++	exit(0);
+ }
+ 
+ static int parse_argv(int argc, char* argv[]) {
+@@ -82,10 +83,10 @@ static int parse_argv(int argc, char* argv[]) {
+ 
+ 		switch (c) {
+ 			case 'h':
+-				return help();
++				help();
+ 
+ 			case ARG_VERSION:
+-				return version();
++				version();
+ 
+ 			case '?':
+ 				return -EINVAL;
+-- 
+2.39.2
+
diff --git a/network/patches/0280-test-Be-less-patient-if-networkd-does-not-want-to-te.patch b/network/patches/0280-test-Be-less-patient-if-networkd-does-not-want-to-te.patch
new file mode 100644
index 000000000..33af11d9d
--- /dev/null
+++ b/network/patches/0280-test-Be-less-patient-if-networkd-does-not-want-to-te.patch
@@ -0,0 +1,84 @@
+From f1e43a02719530f865fd7818b60b706c551d143c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 08:20:41 +0000
+Subject: [PATCH 280/304] test: Be less patient if networkd does not want to
+ terminate
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ test/networkd/test.sh | 43 +++++++++++++++++++++++++++++++------------
+ 1 file changed, 31 insertions(+), 12 deletions(-)
+
+diff --git a/test/networkd/test.sh b/test/networkd/test.sh
+index a22aeb3..7690978 100644
+--- a/test/networkd/test.sh
++++ b/test/networkd/test.sh
+@@ -63,27 +63,43 @@ launch_networkd() {
+ 	coproc networkd { ./networkd "$@"; }
+ 
+ 	echo "networkd launched as PID ${networkd_PID}"
++
++	sleep 1
+ }
+ 
+ terminate_networkd() {
+-	if [ -n "${networkd_PID}" ]; then
+-		# Collect some status information
+-		dump_status
+-
+-		# Send SIGTERM
+-		kill -TERM "${networkd_PID}"
++	local seconds=0
+ 
+-		# Wait until networkd has finished
+-		echo "Waiting for networkd to terminate..."
+-		wait "${networkd_PID}"
++	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
+ }
+ 
+-# Make sure networkd has been terminated when this script exits
+-trap "terminate_networkd" EXIT
+-
+ main() {
+ 	local test="${1}"
+ 	shift
+@@ -112,6 +128,9 @@ main() {
+ 	# Terminate networkd
+ 	terminate_networkd
+ 
++	# Collect some status information
++	dump_status
++
+ 	# Run cleanup script
+ 	if ! run_script "${test}/cleanup.sh"; then
+ 		return 1
+-- 
+2.39.2
+
diff --git a/network/patches/0281-test-Run-networkd-as-root-in-its-own-namespace.patch b/network/patches/0281-test-Run-networkd-as-root-in-its-own-namespace.patch
new file mode 100644
index 000000000..44e59411c
--- /dev/null
+++ b/network/patches/0281-test-Run-networkd-as-root-in-its-own-namespace.patch
@@ -0,0 +1,26 @@
+From 119cc5be2dc054f619226a27ffd855e849330adf Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 09:27:51 +0000
+Subject: [PATCH 281/304] test: Run networkd as root in its own namespace
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/Makefile.am b/Makefile.am
+index fe1e4d1..08e04a1 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -720,7 +720,7 @@ EXTRA_DIST += \
+ 
+ # Run all networkd tests in their own namespaces
+ T_LOG_COMPILER = unshare --net --ipc --uts --user --cgroup --time --pid --fork \
+-	$(SHELL) test/networkd/test.sh
++	--map-root-user --keep-caps $(SHELL) test/networkd/test.sh
+ 
+ # - NITSI tests ----------------------------------------------------------------
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0282-ports-Refactor-enumerating-ports.patch b/network/patches/0282-ports-Refactor-enumerating-ports.patch
new file mode 100644
index 000000000..7295bf7bc
--- /dev/null
+++ b/network/patches/0282-ports-Refactor-enumerating-ports.patch
@@ -0,0 +1,636 @@
+From 8edf3da1c28feb4e2af80786c5e41e52f07b0f73 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 09:29:01 +0000
+Subject: [PATCH 282/304] ports: Refactor enumerating ports
+
+This entails a little rewrite how we deal with where configuration files
+are stored.
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 117 ++++++++++--------------------------------
+ src/networkd/config.h |  11 ++--
+ src/networkd/daemon.c |  23 ++++++---
+ src/networkd/daemon.h |   9 ++++
+ src/networkd/port.c   |  64 +++++++++++++++++------
+ src/networkd/port.h   |   3 +-
+ src/networkd/ports.c  |  80 +++++++++++++++++------------
+ src/networkd/zone.c   |  27 ++++++++--
+ 8 files changed, 172 insertions(+), 162 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index b7f3f72..3d444c4 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -53,9 +53,6 @@ struct nw_config_option {
+ struct nw_config {
+ 	int nrefs;
+ 
+-	// The path to the configuration file
+-	char path[PATH_MAX];
+-
+ 	STAILQ_HEAD(config_entries, nw_config_entry) entries;
+ 
+ 	// Options
+@@ -118,7 +115,7 @@ static void nw_config_free(nw_config* config) {
+ 	free(config);
+ }
+ 
+-int nw_config_create(nw_config** config, const char* path) {
++int nw_config_create(nw_config** config, FILE* f) {
+ 	int r;
+ 
+ 	nw_config* c = calloc(1, sizeof(*c));
+@@ -134,15 +131,10 @@ int nw_config_create(nw_config** config, const char* path) {
+ 	// Initialise options
+ 	STAILQ_INIT(&c->options);
+ 
+-	// Store the path
+-	if (path) {
+-		r = nw_string_set(c->path, path);
+-		if (r)
+-			goto ERROR;
+-
+-		// Try to read the configuration from path
+-		r = nw_config_read(c);
+-		if (r)
++	// Read configuration
++	if (f) {
++		r = nw_config_read(c, f);
++		if (r < 0)
+ 			goto ERROR;
+ 	}
+ 
+@@ -156,6 +148,25 @@ ERROR:
+ 	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);
++
++ERROR:
++	if (f)
++		fclose(f);
++
++	return r;
++}
++
+ nw_config* nw_config_ref(nw_config* config) {
+ 	config->nrefs++;
+ 
+@@ -170,17 +181,6 @@ nw_config* nw_config_unref(nw_config* config) {
+ 	return NULL;
+ }
+ 
+-int nw_config_destroy(nw_config* config) {
+-	int r;
+-
+-	// Drop all entries
+-	r = nw_config_flush(config);
+-	if (r)
+-		return r;
+-
+-	return unlink(config->path);
+-}
+-
+ int nw_config_copy(nw_config* config, nw_config** copy) {
+ 	struct nw_config_entry* entry = NULL;
+ 	nw_config* c = NULL;
+@@ -208,13 +208,6 @@ ERROR:
+ 	return r;
+ }
+ 
+-const char* nw_config_path(nw_config* config) {
+-	if (*config->path)
+-		return config->path;
+-
+-	return NULL;
+-}
+-
+ int nw_config_flush(nw_config* config) {
+ 	struct nw_config_entry* entry = NULL;
+ 
+@@ -229,7 +222,7 @@ int nw_config_flush(nw_config* config) {
+ 	return 0;
+ }
+ 
+-int nw_config_readf(nw_config* config, FILE* f) {
++int nw_config_read(nw_config* config, FILE* f) {
+ 	char* line = NULL;
+ 	size_t length = 0;
+ 	int r;
+@@ -275,39 +268,7 @@ int nw_config_readf(nw_config* config, FILE* f) {
+ 	return r;
+ }
+ 
+-int nw_config_read(nw_config* config) {
+-	FILE* f = NULL;
+-	int r;
+-
+-	// We cannot read if path is not set
+-	if (!*config->path) {
+-		errno = ENOTSUP;
+-		return 1;
+-	}
+-
+-	// Open the file
+-	f = fopen(config->path, "r");
+-	if (!f) {
+-		// Silently ignore if the file does not exist
+-		if (errno == ENOENT)
+-			return 0;
+-
+-		ERROR("Could not read configuration file %s: %m\n", config->path);
+-		r = 1;
+-		goto ERROR;
+-	}
+-
+-	// Read from file
+-	r = nw_config_readf(config, f);
+-
+-ERROR:
+-	if (f)
+-		fclose(f);
+-
+-	return r;
+-}
+-
+-static int nw_config_writef(nw_config* config, FILE* f) {
++int nw_config_write(nw_config* config, FILE* f) {
+ 	struct nw_config_entry* entry = NULL;
+ 	int r;
+ 
+@@ -327,32 +288,6 @@ static int nw_config_writef(nw_config* config, FILE* f) {
+ 	return 0;
+ }
+ 
+-int nw_config_write(nw_config* config) {
+-	int r;
+-
+-	// We cannot write if path is not set
+-	if (!*config->path) {
+-		errno = ENOTSUP;
+-		return 1;
+-	}
+-
+-	FILE* f = fopen(config->path, "w");
+-	if (!f) {
+-		ERROR("Failed to open %s for writing: %m\n", config->path);
+-		r = 1;
+-		goto ERROR;
+-	}
+-
+-	// Write configuration
+-	r = nw_config_writef(config, f);
+-
+-ERROR:
+-	if (f)
+-		fclose(f);
+-
+-	return r;
+-}
+-
+ static struct nw_config_entry* nw_config_find(nw_config* config, const char* key) {
+ 	struct nw_config_entry* entry = NULL;
+ 
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index d532da3..b25d05e 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -28,21 +28,18 @@
+ 
+ typedef struct nw_config nw_config;
+ 
+-int nw_config_create(nw_config** config, const char* path);
++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_destroy(nw_config* config);
+ int nw_config_copy(nw_config* config, nw_config** copy);
+ 
+-const char* nw_config_path(nw_config* config);
+-
+ int nw_config_flush(nw_config* config);
+ 
+-int nw_config_readf(nw_config* config, FILE* f);
+-int nw_config_read(nw_config* config);
+-int nw_config_write(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);
+ 
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index f0ef4aa..a62e343 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -113,7 +113,7 @@ static int nw_daemon_config_open(nw_daemon* daemon, const char* path) {
+ 	return 0;
+ }
+ 
+-static FILE* nw_daemon_config_fopen(nw_daemon* daemon, const char* path, const char* mode) {
++FILE* nw_daemon_config_fopen(nw_daemon* daemon, const char* path, const char* mode) {
+ 	// Open the file
+ 	int fd = openat(daemon->configfd, path, 0);
+ 	if (fd < 0) {
+@@ -125,6 +125,16 @@ static FILE* nw_daemon_config_fopen(nw_daemon* daemon, const char* path, const c
+ 	return fdopen(fd, mode);
+ }
+ 
++DIR* nw_daemon_config_opendir(nw_daemon* daemon, const char* path) {
++	int fd = openat(daemon->configfd, path, O_DIRECTORY);
++	if (fd < 0) {
++		ERROR("Could not open configuration directory %s: %m\n", path);
++		return NULL;
++	}
++
++	return fdopendir(fd);
++}
++
+ static int nw_daemon_parse_argv(nw_daemon* daemon, int argc, char* argv[]) {
+ 	enum {
+ 		ARG_CONFIG,
+@@ -221,12 +231,7 @@ static int nw_daemon_load_config(nw_daemon* daemon) {
+ 	}
+ 
+ 	// Create configuration
+-	r = nw_config_create(&daemon->config, NULL);
+-	if (r < 0)
+-		goto ERROR;
+-
+-	// Parse configuration
+-	r = nw_config_readf(daemon->config, f);
++	r = nw_config_create(&daemon->config, f);
+ 	if (r < 0)
+ 		goto ERROR;
+ 
+@@ -615,10 +620,12 @@ int nw_daemon_save(nw_daemon* daemon) {
+ 
+ 	DEBUG("Saving configuration...\n");
+ 
++#if 0
+ 	// Save settings
+-	r = nw_config_write(daemon->config);
++	r = nw_config_write(daemon->config, f);
+ 	if (r)
+ 		return r;
++#endif
+ 
+ 	// Save ports
+ 	r = nw_ports_save(daemon->ports);
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index 8653af3..b03086c 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -21,6 +21,9 @@
+ #ifndef NETWORKD_DAEMON_H
+ #define NETWORKD_DAEMON_H
+ 
++#include <dirent.h>
++#include <stdio.h>
++
+ #include <systemd/sd-bus.h>
+ #include <systemd/sd-netlink.h>
+ 
+@@ -44,6 +47,12 @@ int nw_daemon_reload(nw_daemon* daemon);
+ 
+ int nw_daemon_save(nw_daemon* daemon);
+ 
++/*
++	Configuration
++*/
++FILE* nw_daemon_config_fopen(nw_daemon* daemon, const char* path, const char* mode);
++DIR* nw_daemon_config_opendir(nw_daemon* daemon, const char* path);
++
+ /*
+ 	Bus
+ */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index fb5d418..7d654e3 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -147,7 +147,7 @@ int nw_port_create(nw_port** port, nw_daemon* daemon,
+ 	// Allocate a new object
+ 	nw_port* p = calloc(1, sizeof(*p));
+ 	if (!p)
+-		return 1;
++		return -errno;
+ 
+ 	// Store a reference to the daemon
+ 	p->daemon = nw_daemon_ref(daemon);
+@@ -172,17 +172,17 @@ int nw_port_create(nw_port** port, nw_daemon* daemon,
+ 
+ 	// Store the name
+ 	r = nw_string_set(p->name, name);
+-	if (r)
++	if (r < 0)
+ 		goto ERROR;
+ 
+ 	// Copy the configuration
+ 	r = nw_config_copy(config, &p->config);
+-	if (r)
++	if (r < 0)
+ 		goto ERROR;
+ 
+ 	// Setup the port
+ 	r = nw_port_setup(p);
+-	if (r)
++	if (r < 0)
+ 		goto ERROR;
+ 
+ 	// Validate the configuration
+@@ -210,32 +210,47 @@ ERROR:
+ 	return r;
+ }
+ 
+-int nw_port_create_from_config(nw_port** port, nw_daemon* daemon,
+-		const char* name, const char* path) {
++int nw_port_open(nw_port** port, nw_daemon* daemon, const char* name) {
+ 	nw_config* config = NULL;
++	FILE* f = NULL;
++	char path[PATH_MAX];
+ 	int r;
+ 
++	// Make path
++	r = nw_string_format(path, "ports/%s", name);
++	if (r < 0)
++		goto ERROR;
++
++	// Open the configuration file
++	f = nw_daemon_config_fopen(daemon, path, "r");
++	if (!f) {
++		r = -errno;
++		goto ERROR;
++	}
++
+ 	// Initialize the configuration
+-	r = nw_config_create(&config, path);
+-	if (r)
++	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 configuration %s has no TYPE\n", path);
+-		r = 1;
++		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)
++	if (r < 0)
+ 		goto ERROR;
+ 
+ ERROR:
+ 	if (config)
+ 		nw_config_unref(config);
++	if (f)
++		fclose(f);
+ 
+ 	return r;
+ }
+@@ -316,24 +331,39 @@ int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
+ }
+ 
+ int nw_port_save(nw_port* port) {
++	char path[PATH_MAX];
++	FILE* f = NULL;
+ 	int r;
+ 
++	// Compose path
++	r = nw_string_format(path, "ports/%s", port->name);
++	if (r < 0)
++		return r;
++
++	// Open file
++	f = nw_daemon_config_fopen(port->daemon, path, "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);
+-	if (r)
+-		return r;
+-
+-	return 0;
++	r = nw_config_write(port->config, f);
++	if (r < 0)
++		goto ERROR;
+ 
+ ERROR:
+-	ERROR("Could not save configuration for port %s: %s\n", port->name, strerror(-r));
++	if (f)
++		fclose(f);
++	if (r)
++		ERROR("Could not save configuration for port %s: %s\n", port->name, strerror(-r));
+ 
+-	return 1;
++	return r;
+ }
+ 
+ const char* nw_port_name(nw_port* port) {
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 7c2e436..efa2fdb 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -95,8 +95,7 @@ struct nw_port {
+ 
+ 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_create_from_config(nw_port** port, nw_daemon* daemon,
+-	const char* name, const char* path);
++int nw_port_open(nw_port** port, nw_daemon* daemon, const char* name);
+ 
+ nw_port* nw_port_ref(nw_port* port);
+ nw_port* nw_port_unref(nw_port* port);
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+index 65545e8..761e564 100644
+--- a/src/networkd/ports.c
++++ b/src/networkd/ports.c
+@@ -18,6 +18,7 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <dirent.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <sys/queue.h>
+@@ -129,43 +130,14 @@ static int nw_ports_add_port(nw_ports* ports, nw_port* port) {
+ 	return 0;
+ }
+ 
+-static int __nw_ports_enumerate(const char* path, const struct stat* s, void* data) {
++static int nw_ports_enumerate_port(nw_ports* ports, const char* name) {
+ 	nw_port* port = NULL;
+ 	int r;
+ 
+-	nw_ports* ports = (nw_ports*)data;
+-
+-	// Skip anything that isn't a regular file
+-	if (!S_ISREG(s->st_mode))
+-		return 0;
+-
+-	// Find the basename of the file
+-	const char* name = nw_path_basename(path);
+-
+-	// Break on invalid paths
+-	if (!name)
+-		return 0;
+-
+-	// Skip any hidden files
+-	if (*name == '.')
+-		return 0;
+-
+ 	// Create a new port
+-	r = nw_port_create_from_config(&port, ports->daemon, name, path);
+-	switch (r) {
+-		// All okay
+-		case 0:
+-			break;
+-
+-		// Invalid configuration
+-		case 1:
+-			ERROR("Could not open port %s\n", name);
+-			r = 0;
+-			goto ERROR;
+-
+-		default:
+-			goto ERROR;
+-	}
++	r = nw_port_open(&port, ports->daemon, name);
++	if (r < 0 || r == 1)
++		goto ERROR;
+ 
+ 	// Add the port to the list
+ 	r = nw_ports_add_port(ports, port);
+@@ -180,7 +152,47 @@ ERROR:
+ }
+ 
+ int nw_ports_enumerate(nw_ports* ports) {
+-	return nw_ftw(PORT_CONFIG_DIR, PORT_CONFIG_DIR "/*", __nw_ports_enumerate, ports);
++	DIR* d = NULL;
++	struct dirent* entry = NULL;
++	int r;
++
++	// Open the ports directory
++	d = nw_daemon_config_opendir(ports->daemon, "ports");
++	if (!d) {
++		switch (errno) {
++			case ENOENT:
++				return 0;
++
++			default:
++				return -errno;
++		}
++	}
++
++	for (;;) {
++		// Read the next entry
++		entry = readdir(d);
++		if (!entry)
++			break;
++
++		// Skip anything that is not a regular file
++		if (entry->d_type != DT_REG)
++			continue;
++
++		// Skip hidden files
++		if (entry->d_name[0] == '.')
++			continue;
++
++		// Enumerate the port
++		r = nw_ports_enumerate_port(ports, entry->d_name);
++		if (r < 0)
++			goto ERROR;
++	}
++
++ERROR:
++	if (d)
++		closedir(d);
++
++	return r;
+ }
+ 
+ nw_port* nw_ports_get_by_name(nw_ports* ports, const char* name) {
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index 9f5b7f8..cc5fdaf 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -120,6 +120,7 @@ static int nw_zone_setup(nw_zone* zone) {
+ 			goto ERROR;
+ 	}
+ 
++#if 0
+ 	// Compose the path to the main configuration file
+ 	r = nw_zone_path(zone, path, "%s", "settings");
+ 	if (r)
+@@ -129,6 +130,7 @@ static int nw_zone_setup(nw_zone* zone) {
+ 	r = nw_config_create(&zone->config, path);
+ 	if (r)
+ 		goto ERROR;
++#endif
+ 
+ ERROR:
+ 	if (link)
+@@ -193,13 +195,32 @@ int __nw_zone_drop_port(nw_daemon* daemon, nw_zone* zone, void* data) {
+ }
+ 
+ int nw_zone_save(nw_zone* zone) {
++	char path[PATH_MAX];
++	FILE* f = NULL;
+ 	int r;
+ 
+-	r = nw_config_write(zone->config);
++	// Compose path
++	r = nw_string_format(path, "zones/%s/settings", zone->name);
++	if (r < 0)
++		goto ERROR;
++
++	// Open file
++	f = nw_daemon_config_fopen(zone->daemon, path, "w");
++	if (!f) {
++		r = -errno;
++		goto ERROR;
++	}
++
++	// Write the configuration
++	r = nw_config_write(zone->config, f);
+ 	if (r)
+-		return r;
++		goto ERROR;
+ 
+-	return 0;
++ERROR:
++	if (f)
++		fclose(f);
++
++	return r;
+ }
+ 
+ const char* nw_zone_name(nw_zone* zone) {
+-- 
+2.39.2
+
diff --git a/network/patches/0283-tests-Add-new-test-that-creates-two-dummy-interfaces.patch b/network/patches/0283-tests-Add-new-test-that-creates-two-dummy-interfaces.patch
new file mode 100644
index 000000000..17a4933da
--- /dev/null
+++ b/network/patches/0283-tests-Add-new-test-that-creates-two-dummy-interfaces.patch
@@ -0,0 +1,67 @@
+From b2134e9ee25fe3aedddc9f6ef812881724122a81 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 09:38:00 +0000
+Subject: [PATCH 283/304] tests: Add new test that creates two dummy interfaces
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                              | 3 ++-
+ test/networkd/01_dummy.t/config/ports/d0 | 2 ++
+ test/networkd/01_dummy.t/config/ports/d1 | 2 ++
+ test/networkd/01_dummy.t/config/settings | 0
+ test/networkd/01_dummy.t/test.sh         | 7 +++++++
+ 5 files changed, 13 insertions(+), 1 deletion(-)
+ create mode 100644 test/networkd/01_dummy.t/config/ports/d0
+ create mode 100644 test/networkd/01_dummy.t/config/ports/d1
+ create mode 100644 test/networkd/01_dummy.t/config/settings
+ create mode 100644 test/networkd/01_dummy.t/test.sh
+
+diff --git a/Makefile.am b/Makefile.am
+index 08e04a1..2df9185 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -710,7 +710,8 @@ TESTS += $(dist_check_SCRIPTS)
+ TEST_EXTENSIONS = .t
+ 
+ NETWORKD_TESTS = \
+-	test/networkd/00_launch.t
++	test/networkd/00_launch.t \
++	test/networkd/01_dummy.t
+ 
+ TESTS += $(NETWORKD_TESTS)
+ 
+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 0000000..36a350b
+--- /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 0000000..d040863
+--- /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 0000000..e69de29
+diff --git a/test/networkd/01_dummy.t/test.sh b/test/networkd/01_dummy.t/test.sh
+new file mode 100644
+index 0000000..fff0d69
+--- /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
+-- 
+2.39.2
+
diff --git a/network/patches/0284-tests-Always-dump-the-environment.patch b/network/patches/0284-tests-Always-dump-the-environment.patch
new file mode 100644
index 000000000..20711f981
--- /dev/null
+++ b/network/patches/0284-tests-Always-dump-the-environment.patch
@@ -0,0 +1,47 @@
+From 5d4157c6655aea0673a60894420a9d5a0fa63dd6 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 09:38:35 +0000
+Subject: [PATCH 284/304] tests: Always dump the environment
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ test/networkd/test.sh | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/test/networkd/test.sh b/test/networkd/test.sh
+index 7690978..9cecf0a 100644
+--- a/test/networkd/test.sh
++++ b/test/networkd/test.sh
+@@ -64,6 +64,9 @@ launch_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
+ }
+ 
+@@ -100,6 +103,9 @@ terminate_networkd() {
+ 	fi
+ }
+ 
++# Collect some status information
++trap dump_status EXIT
++
+ main() {
+ 	local test="${1}"
+ 	shift
+@@ -128,9 +134,6 @@ main() {
+ 	# Terminate networkd
+ 	terminate_networkd
+ 
+-	# Collect some status information
+-	dump_status
+-
+ 	# Run cleanup script
+ 	if ! run_script "${test}/cleanup.sh"; then
+ 		return 1
+-- 
+2.39.2
+
diff --git a/network/patches/0285-test-Collect-more-information-from-test-environment.patch b/network/patches/0285-test-Collect-more-information-from-test-environment.patch
new file mode 100644
index 000000000..186fbe731
--- /dev/null
+++ b/network/patches/0285-test-Collect-more-information-from-test-environment.patch
@@ -0,0 +1,26 @@
+From fcc334a2e325a5d08b7abecb0eb67d7c944b4f35 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 10:00:06 +0000
+Subject: [PATCH 285/304] test: Collect more information from test environment
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ test/networkd/test.sh | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/test/networkd/test.sh b/test/networkd/test.sh
+index 9cecf0a..3eda034 100644
+--- a/test/networkd/test.sh
++++ b/test/networkd/test.sh
+@@ -52,6 +52,8 @@ dump_command() {
+ }
+ 
+ dump_status() {
++	dump_command "printenv"
++	dump_command "ps aux"
+ 	dump_command "ip -d link"
+ }
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0286-ports-bonding-Use-correct-enum-for-mode.patch b/network/patches/0286-ports-bonding-Use-correct-enum-for-mode.patch
new file mode 100644
index 000000000..79e10b021
--- /dev/null
+++ b/network/patches/0286-ports-bonding-Use-correct-enum-for-mode.patch
@@ -0,0 +1,38 @@
+From 5f9c43922b2b9e52ad61e5b8203854881da793a4 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 10:17:58 +0000
+Subject: [PATCH 286/304] ports: bonding: Use correct enum for mode
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-bonding.c | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/src/networkd/port-bonding.c b/src/networkd/port-bonding.c
+index ad02b5d..6064957 100644
+--- a/src/networkd/port-bonding.c
++++ b/src/networkd/port-bonding.c
+@@ -97,13 +97,13 @@ 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 BOND_MODE_ROUNDROBIN:
+-		case BOND_MODE_ACTIVEBACKUP:
+-		case BOND_MODE_XOR:
+-		case BOND_MODE_BROADCAST:
+-		case BOND_MODE_8023AD:
+-		case BOND_MODE_TLB:
+-		case BOND_MODE_ALB:
++		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;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0287-networkd-json-Include-string.h.patch b/network/patches/0287-networkd-json-Include-string.h.patch
new file mode 100644
index 000000000..f0774c47f
--- /dev/null
+++ b/network/patches/0287-networkd-json-Include-string.h.patch
@@ -0,0 +1,25 @@
+From e5c50dd3c189c96bead686cc728121d6f22dcd49 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 10:37:00 +0000
+Subject: [PATCH 287/304] networkd: json: Include string.h
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/json.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/networkd/json.h b/src/networkd/json.h
+index 33e237a..19ced9a 100644
+--- a/src/networkd/json.h
++++ b/src/networkd/json.h
+@@ -22,6 +22,7 @@
+ #define NETWORKD_JSON_H
+ 
+ #include <errno.h>
++#include <string.h>
+ 
+ #include <json.h>
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0288-ports-Add-support-for-VETH.patch b/network/patches/0288-ports-Add-support-for-VETH.patch
new file mode 100644
index 000000000..b818eac50
--- /dev/null
+++ b/network/patches/0288-ports-Add-support-for-VETH.patch
@@ -0,0 +1,228 @@
+From 67d3fef1d79e933b8af64a39f89fef4b1f8b31cb Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 10:37:47 +0000
+Subject: [PATCH 288/304] ports: Add support for VETH
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am              |  2 +
+ src/networkd/port-veth.c | 81 ++++++++++++++++++++++++++++++++++++++++
+ src/networkd/port-veth.h | 35 +++++++++++++++++
+ src/networkd/port.c      |  6 +++
+ src/networkd/port.h      |  5 +++
+ 5 files changed, 129 insertions(+)
+ create mode 100644 src/networkd/port-veth.c
+ create mode 100644 src/networkd/port-veth.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 2df9185..caab99e 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -338,6 +338,8 @@ dist_networkd_SOURCES = \
+ 	src/networkd/port-bus.h \
+ 	src/networkd/port-dummy.c \
+ 	src/networkd/port-dummy.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 \
+diff --git a/src/networkd/port-veth.c b/src/networkd/port-veth.c
+new file mode 100644
+index 0000000..44b1a0d
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <linux/veth.h>
++
++#include "json.h"
++#include "port.h"
++#include "port-veth.h"
++
++static int nw_port_veth_setup(nw_port* port) {
++	int r;
++
++	// Peer
++	r = NW_CONFIG_OPTION_STRING(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 0000000..aa4a03b
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_PORT_VETH_H
++#define NETWORKD_PORT_VETH_H
++
++#include "port.h"
++
++// Maximum length of the peer name
++#define NW_VETH_PEER_MAX 64
++
++struct nw_port_veth {
++	char peer[NW_VETH_PEER_MAX];
++};
++
++extern const nw_port_type_t nw_port_type_veth;
++
++#endif /* NETWORKD_PORT_VETH_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 7d654e3..fb62520 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -33,6 +33,7 @@
+ #include "port.h"
+ #include "port-bonding.h"
+ #include "port-dummy.h"
++#include "port-veth.h"
+ #include "port-vlan.h"
+ #include "stats-collector.h"
+ #include "string.h"
+@@ -40,6 +41,7 @@
+ static const nw_string_table_t nw_port_type_id[] = {
+ 	{ NW_PORT_BONDING, "bonding" },
+ 	{ NW_PORT_DUMMY,   "dummy" },
++	{ NW_PORT_VETH,    "veth", },
+ 	{ NW_PORT_VLAN,    "vlan" },
+ 	{ -1, NULL },
+ };
+@@ -165,6 +167,9 @@ int nw_port_create(nw_port** port, nw_daemon* daemon,
+ 			p->type = &nw_port_type_dummy;
+ 			break;
+ 
++		case NW_PORT_VETH:
++			p->type = &nw_port_type_veth;
++
+ 		case NW_PORT_VLAN:
+ 			p->type = &nw_port_type_vlan;
+ 			break;
+@@ -324,6 +329,7 @@ int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
+ 
+ 		case NW_PORT_BONDING:
+ 		case NW_PORT_DUMMY:
++		case NW_PORT_VETH:
+ 			break;
+ 	}
+ 
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index efa2fdb..5c0a2a1 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -36,6 +36,7 @@ typedef struct nw_port nw_port;
+ typedef enum nw_port_type_id {
+ 	NW_PORT_BONDING,
+ 	NW_PORT_DUMMY,
++	NW_PORT_VETH,
+ 	NW_PORT_VLAN,
+ } nw_port_type_id_t;
+ 
+@@ -66,6 +67,7 @@ typedef struct nw_port_type {
+ #include "daemon.h"
+ #include "json.h"
+ #include "port-bonding.h"
++#include "port-veth.h"
+ #include "port-vlan.h"
+ 
+ #define NW_PORT_TYPE(port) (port->type)
+@@ -89,6 +91,9 @@ struct nw_port {
+ 	// Bonding Settings
+ 	struct nw_port_bonding bonding;
+ 
++	// VETH Settings
++	struct nw_port_veth veth;
++
+ 	// VLAN settings
+ 	struct nw_port_vlan vlan;
+ };
+-- 
+2.39.2
+
diff --git a/network/patches/0289-config-Add-string-buffer-type.patch b/network/patches/0289-config-Add-string-buffer-type.patch
new file mode 100644
index 000000000..1bf877080
--- /dev/null
+++ b/network/patches/0289-config-Add-string-buffer-type.patch
@@ -0,0 +1,264 @@
+From e633147dcf3bc46e0686e106cb013dfccf30b1c6 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 11:19:30 +0000
+Subject: [PATCH 289/304] config: Add string buffer type
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c    | 45 +++++++++++++++++++++++++++---------
+ src/networkd/config.h    | 49 +++++++++++++++++++++++++++-------------
+ src/networkd/port-veth.c |  2 +-
+ src/networkd/port-vlan.c |  3 ++-
+ 4 files changed, 70 insertions(+), 29 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index 3d444c4..c6281cb 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -43,6 +43,7 @@ struct nw_config_option {
+ 
+ 	const char* key;
+ 	void* value;
++	size_t length;
+ 
+ 	// Callbacks
+ 	nw_config_option_read_callback_t read_callback;
+@@ -427,7 +428,8 @@ int nw_config_options_read(nw_config* config) {
+ 	int r;
+ 
+ 	STAILQ_FOREACH(option, &config->options, nodes) {
+-		r = option->read_callback(config, option->key, option->value, option->data);
++		r = option->read_callback(config,
++			option->key, option->value, option->length, option->data);
+ 		if (r < 0)
+ 			return r;
+ 	}
+@@ -440,7 +442,8 @@ int nw_config_options_write(nw_config* config) {
+ 	int r;
+ 
+ 	STAILQ_FOREACH(option, &config->options, nodes) {
+-		r = option->write_callback(config, option->key, option->value, option->data);
++		r = option->write_callback(config,
++			option->key, option->value, option->length, option->data);
+ 		if (r < 0)
+ 			return r;
+ 	}
+@@ -448,7 +451,8 @@ int nw_config_options_write(nw_config* config) {
+ 	return 0;
+ }
+ 
+-int nw_config_option_add(nw_config* config, const char* key, void* value,
++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
+@@ -465,6 +469,7 @@ int nw_config_option_add(nw_config* config, const char* key, void* value,
+ 
+ 	// Set value
+ 	option->value = value;
++	option->length = length;
+ 
+ 	// Set callbacks
+ 	option->read_callback = read_callback;
+@@ -477,7 +482,8 @@ int nw_config_option_add(nw_config* config, const char* key, void* value,
+ 	return 0;
+ }
+ 
+-int nw_config_read_int(nw_config* config, const char* key, void* value, void* data) {
++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);
+ 
+@@ -485,13 +491,14 @@ int nw_config_read_int(nw_config* config, const char* key, void* value, void* da
+ }
+ 
+ int nw_config_write_int(nw_config* config,
+-		const char* key, const void* value, void* data) {
++		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, void* data) {
++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)
+@@ -501,13 +508,28 @@ int nw_config_read_string(nw_config* config, const char* key, void* value, void*
+ }
+ 
+ int nw_config_write_string(nw_config* config,
+-		const char* key, const void* value, void* data) {
++		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, void* data) {
++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;
+ 
+@@ -529,7 +551,7 @@ int nw_config_read_string_table(nw_config* config, const char* key, void* value,
+ }
+ 
+ int nw_config_write_string_table(nw_config* config,
+-		const char* key, const void* value, void* data) {
++		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;
+@@ -544,7 +566,8 @@ int nw_config_write_string_table(nw_config* config,
+ 
+ // Address
+ 
+-int nw_config_read_address(nw_config* config, const char* key, void* value, void* data) {
++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;
+ 
+@@ -561,7 +584,7 @@ int nw_config_read_address(nw_config* config, const char* key, void* value, void
+ }
+ 
+ int nw_config_write_address(nw_config* config,
+-		const char* key, const void* value, void* data) {
++		const char* key, const void* value, const size_t length, void* data) {
+ 	const nw_address_t* address = (nw_address_t*)value;
+ 	int r;
+ 
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index b25d05e..4b8bc01 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -60,47 +60,64 @@ 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, void* data);
++	(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, void* data);
++	(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,
++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, read_callback, write_callback, data) \
+-	nw_config_option_add(config, key, value, read_callback, write_callback, 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, void* data);
+-int nw_config_write_string(nw_config* config, const char* key, const void* value, void* data);
++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, nw_config_read_string_table, nw_config_write_string_table, (void*)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, void* data);
+-int nw_config_write_string_table(nw_config* config, const char* key, const void* value, void* data);
++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, nw_config_read_int, nw_config_write_int, NULL)
++	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, void* data);
+-int nw_config_write_int(nw_config* config, const char* key, const void* value, void* data);
++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, nw_config_read_address, nw_config_write_address, NULL)
++	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, void* data);
+-int nw_config_write_address(nw_config* config, const char* key, const void* value, void* data);
++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/port-veth.c b/src/networkd/port-veth.c
+index 44b1a0d..029ef50 100644
+--- a/src/networkd/port-veth.c
++++ b/src/networkd/port-veth.c
+@@ -28,7 +28,7 @@ static int nw_port_veth_setup(nw_port* port) {
+ 	int r;
+ 
+ 	// Peer
+-	r = NW_CONFIG_OPTION_STRING(port->config, "VETH_PEER", &port->veth.peer);
++	r = NW_CONFIG_OPTION_STRING_BUFFER(port->config, "VETH_PEER", port->veth.peer);
+ 	if (r < 0)
+ 		return 1;
+ 
+diff --git a/src/networkd/port-vlan.c b/src/networkd/port-vlan.c
+index 25a59ee..c759f71 100644
+--- a/src/networkd/port-vlan.c
++++ b/src/networkd/port-vlan.c
+@@ -53,7 +53,8 @@ static int nw_port_vlan_setup(nw_port* port) {
+ 		return r;
+ 
+ 	// Parent Port
+-	r = NW_CONFIG_OPTION_STRING(port->config, "VLAN_PARENT", &port->vlan.__parent_name);
++	r = NW_CONFIG_OPTION_STRING_BUFFER(port->config,
++		"VLAN_PARENT", port->vlan.__parent_name);
+ 	if (r < 0)
+ 		return r;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0290-networkctl-Add-color-functions.patch b/network/patches/0290-networkctl-Add-color-functions.patch
new file mode 100644
index 000000000..9dd6aae4f
--- /dev/null
+++ b/network/patches/0290-networkctl-Add-color-functions.patch
@@ -0,0 +1,176 @@
+From aa7cc0927e2ea5cde54685e01290be8c0afdc31c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 12:30:33 +0000
+Subject: [PATCH 290/304] networkctl: Add color functions
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am               |  2 +
+ src/networkctl/terminal.c | 51 ++++++++++++++++++++++++
+ src/networkctl/terminal.h | 83 +++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 136 insertions(+)
+ create mode 100644 src/networkctl/terminal.c
+ create mode 100644 src/networkctl/terminal.h
+
+diff --git a/Makefile.am b/Makefile.am
+index caab99e..95fff11 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -402,6 +402,8 @@ dist_networkctl_SOURCES = \
+ 	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
+ 
+diff --git a/src/networkctl/terminal.c b/src/networkctl/terminal.c
+new file mode 100644
+index 0000000..de7cd8d
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include <stdlib.h>
++#include <unistd.h>
++
++#include "terminal.h"
++
++// Cache the color mode
++static color_mode_t __color_mode = COLORS_UNKNOWN;
++
++static color_mode_t detect_color_mode(void) {
++	const char* s = NULL;
++
++	// Check for NO_COLOR and if found turn off colours
++	s = secure_getenv("NO_COLOR");
++	if (s)
++		return COLORS_OFF;
++
++	// Disable colours if this isn't an interactive terminal
++	if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO))
++		return COLORS_OFF;
++
++	// Otherwise we enable colours
++	return COLORS_ON;
++}
++
++color_mode_t color_mode() {
++	if (__color_mode == COLORS_UNKNOWN) {
++		__color_mode = detect_color_mode();
++	}
++
++	return __color_mode;
++}
+diff --git a/src/networkctl/terminal.h b/src/networkctl/terminal.h
+new file mode 100644
+index 0000000..b7bffec
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKCTL_TERMINAL_H
++#define NETWORKCTL_TERMINAL_H
++
++typedef enum color_mode {
++	COLORS_UNKNOWN = 0,
++	COLORS_OFF,
++	COLORS_ON,
++} color_mode_t;
++
++// Reset
++#define COLOR_RESET              "\x1B[0m"
++
++// Highlight
++#define COLOR_HIGHLIGHT          "\x1B[0;1;39m"
++
++// Regular Colors
++#define COLOR_BLACK              "\x1B[0;30m"
++#define COLOR_RED                "\x1B[0;31m"
++#define COLOR_GREEN              "\x1B[0;32m"
++#define COLOR_YELLOW             "\x1B[0;33m"
++#define COLOR_BLUE               "\x1B[0;34m"
++#define COLOR_MAGENTA            "\x1B[0;35m"
++#define COLOR_CYAN               "\x1B[0;36m"
++#define COLOR_WHITE              "\x1B[0;37m"
++
++#define COLOR_BRIGHT_BLACK       "\x1B[0;90m"
++#define COLOR_BRIGHT_RED         "\x1B[0;91m"
++#define COLOR_BRIGHT_GREEN       "\x1B[0;92m"
++#define COLOR_BRIGHT_YELLOW      "\x1B[0;93m"
++#define COLOR_BRIGHT_BLUE        "\x1B[0;94m"
++#define COLOR_BRIGHT_MAGENTA     "\x1B[0;95m"
++#define COLOR_BRIGHT_CYAN        "\x1B[0;96m"
++#define COLOR_BRIGHT_WHITE       "\x1B[0;97m"
++
++#define COLOR_HIGHLIGHT_BLACK    "\x1B[0;1;30m"
++#define COLOR_HIGHLIGHT_RED      "\x1B[0;1;31m"
++#define COLOR_HIGHLIGHT_GREEN    "\x1B[0;1;32m"
++#define COLOR_HIGHLIGHT_YELLOW   "\x1B[0;1;33m"
++#define COLOR_HIGHLIGHT_BLUE     "\x1B[0;1;34m"
++#define COLOR_HIGHLIGHT_MAGENTA  "\x1B[0;1;35m"
++#define COLOR_HIGHLIGHT_CYAN     "\x1B[0;1;36m"
++#define COLOR_HIGHLIGHT_WHITE    "\x1B[0;1;37m"
++
++// Returns the color mode
++color_mode_t color_mode(void);
++
++#define COLOR_FUNC(name, color) \
++	static inline const char* color_##name(void) { \
++		return (color_mode() == COLORS_ON) ? COLOR_ ## color : ""; \
++	}
++
++COLOR_FUNC(reset,     RESET)
++COLOR_FUNC(highlight, HIGHLIGHT)
++COLOR_FUNC(black,     BLACK)
++COLOR_FUNC(red,       RED)
++COLOR_FUNC(green,     GREEN)
++COLOR_FUNC(yellow,    YELLOW)
++COLOR_FUNC(blue,      BLUE)
++COLOR_FUNC(magenta,   MAGENTA)
++COLOR_FUNC(cyan,      CYAN)
++COLOR_FUNC(white,     WHITE)
++
++#endif /* NETWORKCTL_TERMINAL_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0291-networkctl-Move-describe-into-an-own-function.patch b/network/patches/0291-networkctl-Move-describe-into-an-own-function.patch
new file mode 100644
index 000000000..99aa22bd3
--- /dev/null
+++ b/network/patches/0291-networkctl-Move-describe-into-an-own-function.patch
@@ -0,0 +1,106 @@
+From 8dc40ed861b5ee93f176caf792fc818f681fd9b4 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 12:40:55 +0000
+Subject: [PATCH 291/304] networkctl: Move describe into an own function
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkctl/port.c | 48 ++++++++++++++++++++++++++++++++-----------
+ 1 file changed, 36 insertions(+), 12 deletions(-)
+
+diff --git a/src/networkctl/port.c b/src/networkctl/port.c
+index 67bc003..b6a95c5 100644
+--- a/src/networkctl/port.c
++++ b/src/networkctl/port.c
+@@ -19,6 +19,7 @@
+ #############################################################################*/
+ 
+ #include <limits.h>
++#include <stdlib.h>
+ 
+ #include <systemd/sd-bus.h>
+ 
+@@ -80,22 +81,19 @@ ERROR:
+ 	return r;
+ }
+ 
+-// Dump
+-
+-static int networkctl_port_dump(sd_bus* bus, int argc, char* argv[]) {
++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* text = NULL;
++	const char* t = NULL;
+ 	int r;
+ 
+-	if (argc < 1) {
+-		fprintf(stderr, "Port required\n");
++	// Check input
++	if (!name || !text)
+ 		return -EINVAL;
+-	}
+ 
+ 	// Make port path
+-	r = nw_string_format(path, "/org/ipfire/network1/port/%s", argv[0]);
++	r = nw_string_format(path, "/org/ipfire/network1/port/%s", name);
+ 	if (r < 0)
+ 		goto ERROR;
+ 
+@@ -108,15 +106,16 @@ static int networkctl_port_dump(sd_bus* bus, int argc, char* argv[]) {
+ 	}
+ 
+ 	// Read the text
+-	r = sd_bus_message_read(reply, "s", &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;
+ 	}
+ 
+-	// Print the text
+-	if (text)
+-		printf("%s\n", text);
++	// Copy text to heap
++	*text = strdup(t);
++	if (!*text)
++		r = -errno;
+ 
+ ERROR:
+ 	if (reply)
+@@ -125,6 +124,31 @@ ERROR:
+ 	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) {
+-- 
+2.39.2
+
diff --git a/network/patches/0292-networkctl-Implement-scaffolding-to-show-ports.patch b/network/patches/0292-networkctl-Implement-scaffolding-to-show-ports.patch
new file mode 100644
index 000000000..df434b0af
--- /dev/null
+++ b/network/patches/0292-networkctl-Implement-scaffolding-to-show-ports.patch
@@ -0,0 +1,145 @@
+From 3363ba36e33d5fc9c455825aeeabf4b615cde3c1 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 14:14:42 +0000
+Subject: [PATCH 292/304] networkctl: Implement scaffolding to show ports
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am           |  2 ++
+ src/networkctl/port.c | 68 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/json.h   |  9 ++++++
+ 3 files changed, 79 insertions(+)
+
+diff --git a/Makefile.am b/Makefile.am
+index 95fff11..2632ca7 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -409,12 +409,14 @@ dist_networkctl_SOURCES = \
+ 
+ networkctl_CFLAGS = \
+ 	$(AM_CFLAGS) \
++	$(JSON_C_CFLAGS) \
+ 	$(SYSTEMD_CFLAGS)
+ 
+ networkctl_LDFLAGS = \
+ 	$(AM_LDFLAGS)
+ 
+ networkctl_LDADD = \
++	$(JSON_C_LIBS) \
+ 	$(SYSTEMD_LIBS)
+ 
+ # ------------------------------------------------------------------------------
+diff --git a/src/networkctl/port.c b/src/networkctl/port.c
+index b6a95c5..439177d 100644
+--- a/src/networkctl/port.c
++++ b/src/networkctl/port.c
+@@ -23,9 +23,11 @@
+ 
+ #include <systemd/sd-bus.h>
+ 
++#include "../networkd/json.h"
+ #include "../networkd/string.h"
+ #include "command.h"
+ #include "port.h"
++#include "terminal.h"
+ 
+ typedef int (*networkctl_port_walk_callback)
+ 	(sd_bus* bus, const char* path, const char* name, void* data);
+@@ -161,10 +163,76 @@ 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 },
+ 	};
+ 
+diff --git a/src/networkd/json.h b/src/networkd/json.h
+index 19ced9a..6c2f66f 100644
+--- a/src/networkd/json.h
++++ b/src/networkd/json.h
+@@ -87,4 +87,13 @@ static inline int json_to_string(struct json_object* o, char** s, size_t* l) {
+ 	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 */
+-- 
+2.39.2
+
diff --git a/network/patches/0293-ports-Add-link-stuff-to-JSON-output.patch b/network/patches/0293-ports-Add-link-stuff-to-JSON-output.patch
new file mode 100644
index 000000000..6b3a79ef0
--- /dev/null
+++ b/network/patches/0293-ports-Add-link-stuff-to-JSON-output.patch
@@ -0,0 +1,82 @@
+From 69467266f8b3d8a925604b3ffdeda060c5d409cd Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 14:19:04 +0000
+Subject: [PATCH 293/304] ports: Add link stuff to JSON output
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 14 ++++++++++++++
+ src/networkd/link.h |  4 ++++
+ src/networkd/port.c |  7 +++++++
+ 3 files changed, 25 insertions(+)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 603aabe..60abd46 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -27,6 +27,7 @@
+ #include <systemd/sd-netlink.h>
+ 
+ #include "daemon.h"
++#include "json.h"
+ #include "link.h"
+ #include "links.h"
+ #include "logging.h"
+@@ -529,3 +530,16 @@ ERROR:
+ 
+ 	return r;
+ }
++
++// JSON
++
++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;
++
++	return 0;
++}
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index 2bab47c..36c7d9d 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -26,6 +26,7 @@
+ typedef struct nw_link nw_link;
+ 
+ #include "daemon.h"
++#include "json.h"
+ 
+ int nw_link_create(nw_link** link, nw_daemon* daemon, int ifindex);
+ 
+@@ -45,4 +46,7 @@ int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data);
+ 
+ int nw_link_destroy(nw_link* link);
+ 
++// JSON
++int nw_link_to_json(nw_link* link, struct json_object* o);
++
+ #endif /* NETWORKD_LINK_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index fb62520..84fceee 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -682,6 +682,13 @@ int nw_port_to_json(nw_port* port, struct json_object** object) {
+ 			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);
+-- 
+2.39.2
+
diff --git a/network/patches/0294-link-Add-device-stuff-to-JSON-output.patch b/network/patches/0294-link-Add-device-stuff-to-JSON-output.patch
new file mode 100644
index 000000000..95e39543b
--- /dev/null
+++ b/network/patches/0294-link-Add-device-stuff-to-JSON-output.patch
@@ -0,0 +1,185 @@
+From cf75e1dbc997b8a785e5f4d75e6a3d0efd594cf5 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 14:44:06 +0000
+Subject: [PATCH 294/304] link: Add device stuff to JSON output
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 113 ++++++++++++++++++++++++++++++++++++++++----
+ 1 file changed, 105 insertions(+), 8 deletions(-)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 60abd46..23918cb 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -24,6 +24,7 @@
+ #include <stdlib.h>
+ #include <string.h>
+ 
++#include <systemd/sd-device.h>
+ #include <systemd/sd-netlink.h>
+ 
+ #include "daemon.h"
+@@ -48,6 +49,9 @@ struct nw_link {
+ 		NW_LINK_DESTROYED,
+ 	} state;
+ 
++	// Device
++	struct sd_device* device;
++
+ 	// Stats
+ 	struct rtnl_link_stats64 stats64;
+ 
+@@ -61,11 +65,35 @@ struct nw_link {
+ 	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 1;
++		return -errno;
+ 
+ 	// Store a reference to the daemon
+ 	l->daemon = nw_daemon_ref(daemon);
+@@ -78,16 +106,16 @@ int nw_link_create(nw_link** link, nw_daemon* daemon, int ifindex) {
+ 
+ 	DEBUG("New link allocated (ifindex = %d)\n", l->ifindex);
+ 
+-	*link = l;
++	r = nw_link_setup_device(l);
++	if (r < 0)
++		goto ERROR;
+ 
++	*link = l;
+ 	return 0;
+-}
+-
+-static void nw_link_free(nw_link* link) {
+-	DEBUG("Freeing link (ifindex = %d)\n", link->ifindex);
+ 
+-	if (link->daemon)
+-		nw_daemon_unref(link->daemon);
++ERROR:
++	nw_link_free(l);
++	return r;
+ }
+ 
+ nw_link* nw_link_ref(nw_link* link) {
+@@ -126,6 +154,19 @@ const char* nw_link_ifname(nw_link* link) {
+ 	return link->ifname;
+ }
+ 
++static int nw_link_get_sd_device(nw_link* link, struct sd_device** device) {
++	int r;
++
++	// Fetch sd-device
++	r = sd_device_new_from_ifindex(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;
++}
++
+ // Stats
+ 
+ const struct rtnl_link_stats64* nw_link_get_stats64(nw_link* link) {
+@@ -533,6 +574,57 @@ ERROR:
+ 
+ // 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;
+ 
+@@ -541,5 +633,10 @@ int nw_link_to_json(nw_link* link, struct json_object* o) {
+ 	if (r < 0)
+ 		return r;
+ 
++	// Add sd-device stuff
++	r = nw_link_device_to_json(link, o);
++	if (r < 0)
++		return r;
++
+ 	return 0;
+ }
+-- 
+2.39.2
+
diff --git a/network/patches/0295-ports-Add-scaffolding-for-physical-Ethernet-interfac.patch b/network/patches/0295-ports-Add-scaffolding-for-physical-Ethernet-interfac.patch
new file mode 100644
index 000000000..ec87e49e8
--- /dev/null
+++ b/network/patches/0295-ports-Add-scaffolding-for-physical-Ethernet-interfac.patch
@@ -0,0 +1,246 @@
+From f4f98a384fd5cb95b04be989ec488b2ecfc1960f Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Fri, 9 Jun 2023 14:59:54 +0000
+Subject: [PATCH 295/304] ports: Add scaffolding for physical Ethernet
+ interfaces
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am                  |  2 ++
+ src/networkd/link.c          | 13 --------
+ src/networkd/port-ethernet.c | 63 ++++++++++++++++++++++++++++++++++++
+ src/networkd/port-ethernet.h | 34 +++++++++++++++++++
+ src/networkd/port.c          | 16 ++++++---
+ src/networkd/port.h          |  5 +++
+ 6 files changed, 116 insertions(+), 17 deletions(-)
+ create mode 100644 src/networkd/port-ethernet.c
+ create mode 100644 src/networkd/port-ethernet.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 2632ca7..8c0d0c0 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -338,6 +338,8 @@ dist_networkd_SOURCES = \
+ 	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 \
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 23918cb..f172208 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -154,19 +154,6 @@ const char* nw_link_ifname(nw_link* link) {
+ 	return link->ifname;
+ }
+ 
+-static int nw_link_get_sd_device(nw_link* link, struct sd_device** device) {
+-	int r;
+-
+-	// Fetch sd-device
+-	r = sd_device_new_from_ifindex(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;
+-}
+-
+ // Stats
+ 
+ const struct rtnl_link_stats64* nw_link_get_stats64(nw_link* link) {
+diff --git a/src/networkd/port-ethernet.c b/src/networkd/port-ethernet.c
+new file mode 100644
+index 0000000..a48b45e
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#include "config.h"
++#include "json.h"
++#include "port.h"
++#include "port-ethernet.h"
++
++static int nw_port_ethernet_setup(nw_port* port) {
++	int r;
++
++	// Permanent Address
++	r = NW_CONFIG_OPTION_ADDRESS(port->config,
++		"PERMANENT_ADDRESS", &port->ethernet.permanent_address);
++	if (r < 0)
++		return r;
++
++	return 0;
++}
++
++static int nw_port_ethernet_to_json(nw_port* port, struct json_object* o) {
++	char* address = NULL;
++	int r = 0;
++
++	// Permanent Address
++	address = nw_address_to_string(&port->ethernet.permanent_address);
++	if (address) {
++		r = json_object_add_string(o, "PermanentAddress", address);
++		if (r < 0)
++			goto ERROR;
++	}
++
++ERROR:
++	if (address)
++		free(address);
++
++	return r;
++}
++
++const nw_port_type_t nw_port_type_ethernet = {
++	// Configuration
++	.setup = nw_port_ethernet_setup,
++
++	// JSON
++	.to_json = nw_port_ethernet_to_json,
++};
+diff --git a/src/networkd/port-ethernet.h b/src/networkd/port-ethernet.h
+new file mode 100644
+index 0000000..50dade2
+--- /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 <http://www.gnu.org/licenses/>.       #
++#                                                                             #
++#############################################################################*/
++
++#ifndef NETWORKD_PORT_ETHERNET_H
++#define NETWORKD_PORT_ETHERNET_H
++
++#include "address.h"
++#include "port.h"
++
++struct nw_port_ethernet {
++	// Permanent Address
++	nw_address_t permanent_address;
++};
++
++extern const nw_port_type_t nw_port_type_ethernet;
++
++#endif /* NETWORKD_PORT_ETHERNET_H */
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index 84fceee..f547956 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -33,16 +33,18 @@
+ #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_VETH,    "veth", },
+-	{ NW_PORT_VLAN,    "vlan" },
++	{ NW_PORT_BONDING,  "bonding" },
++	{ NW_PORT_DUMMY,    "dummy" },
++	{ NW_PORT_ETHERNET, "ethernet" },
++	{ NW_PORT_VETH,     "veth", },
++	{ NW_PORT_VLAN,     "vlan" },
+ 	{ -1, NULL },
+ };
+ 
+@@ -167,8 +169,13 @@ int nw_port_create(nw_port** port, nw_daemon* daemon,
+ 			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;
+@@ -329,6 +336,7 @@ int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
+ 
+ 		case NW_PORT_BONDING:
+ 		case NW_PORT_DUMMY:
++		case NW_PORT_ETHERNET:
+ 		case NW_PORT_VETH:
+ 			break;
+ 	}
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 5c0a2a1..f1cb3d2 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -36,6 +36,7 @@ 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;
+@@ -67,6 +68,7 @@ typedef struct nw_port_type {
+ #include "daemon.h"
+ #include "json.h"
+ #include "port-bonding.h"
++#include "port-ethernet.h"
+ #include "port-veth.h"
+ #include "port-vlan.h"
+ 
+@@ -91,6 +93,9 @@ struct nw_port {
+ 	// Bonding Settings
+ 	struct nw_port_bonding bonding;
+ 
++	// Ethernet Settings
++	struct nw_port_ethernet ethernet;
++
+ 	// VETH Settings
+ 	struct nw_port_veth veth;
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0296-logging-Add-WARNING-log-level.patch b/network/patches/0296-logging-Add-WARNING-log-level.patch
new file mode 100644
index 000000000..8e81e26ba
--- /dev/null
+++ b/network/patches/0296-logging-Add-WARNING-log-level.patch
@@ -0,0 +1,25 @@
+From 008488e0f95c140f7df74cadd238db16321a7737 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 10 Jun 2023 11:22:46 +0000
+Subject: [PATCH 296/304] logging: Add WARNING log level
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/logging.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/networkd/logging.h b/src/networkd/logging.h
+index ea75ba0..e835064 100644
+--- a/src/networkd/logging.h
++++ b/src/networkd/logging.h
+@@ -30,6 +30,7 @@ void nw_log(int priority, const char *file, int line, const char* fn,
+ 	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)
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0297-networkd-Handle-any-uevents-for-links.patch b/network/patches/0297-networkd-Handle-any-uevents-for-links.patch
new file mode 100644
index 000000000..89c6dcce5
--- /dev/null
+++ b/network/patches/0297-networkd-Handle-any-uevents-for-links.patch
@@ -0,0 +1,148 @@
+From 40e2c8ca02fae3d175e8d49498a3b3b443b1c6ef Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 10 Jun 2023 11:29:22 +0000
+Subject: [PATCH 297/304] networkd: Handle any uevents for links
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/devmon.c | 67 +++++++++++++++++++++++++++++++++++++++++++
+ src/networkd/link.c   | 19 ++++++++++++
+ src/networkd/link.h   |  5 ++++
+ 3 files changed, 91 insertions(+)
+
+diff --git a/src/networkd/devmon.c b/src/networkd/devmon.c
+index 0a8c26d..c751985 100644
+--- a/src/networkd/devmon.c
++++ b/src/networkd/devmon.c
+@@ -20,8 +20,75 @@
+ 
+ #include <systemd/sd-device.h>
+ 
++#include "daemon.h"
+ #include "devmon.h"
++#include "logging.h"
++
++static int nw_daemon_handle_uevent_net(nw_daemon* daemon,
++		sd_device* device, sd_device_action_t action) {
++	nw_link* link = NULL;
++	int ifindex;
++	int r;
++
++	// Fetch ifindex
++	r = sd_device_get_ifindex(device, &ifindex);
++	if (r < 0) {
++		ERROR("Could not get ifindex from uevent: %s\n", strerror(-r));
++		goto ERROR;
++	}
++
++	// Fetch the link
++	link = nw_daemon_get_link_by_ifindex(daemon, ifindex);
++	if (!link) {
++		DEBUG("Could not fetch link %d, ignoring\n", ifindex);
++		r = 0;
++		goto ERROR;
++	}
++
++	// Let the link handle its uevent
++	r = nw_link_handle_uevent(link, device, action);
++
++ERROR:
++	if (link)
++		nw_link_unref(link);
++
++	return r;
++}
+ 
+ int nw_devmon_handle_uevent(sd_device_monitor* monitor, sd_device* device, void* data) {
++	sd_device_action_t action;
++	const char* subsystem = NULL;
++	int r;
++
++	// Fetch daemon
++	nw_daemon* daemon = (nw_daemon*)data;
++
++	// Fetch action
++	r = sd_device_get_action(device, &action);
++	if (r < 0) {
++		WARNING("Could not get uevent action, ignoring: %s\n", strerror(-r));
++		return r;
++	}
++
++	// Fetch subsystem
++	r = sd_device_get_subsystem(device, &subsystem);
++	if (r < 0) {
++		ERROR("Could not get uevent subsystem, ignoring: %s\n", strerror(-r));
++		return r;
++	}
++
++	// Handle any links
++	if (strcmp(subsystem, "net") == 0) {
++		r = nw_daemon_handle_uevent_net(daemon, device, action);
++
++	} else {
++		DEBUG("Received an uevent for an unhandled subsystem '%s', ignoring\n", subsystem);
++		return 0;
++	}
++
++	// Log if something went wrong
++	if (r < 0)
++		ERROR("Failed processing uevent: %s\n", strerror(-r));
++
+ 	return 0;
+ }
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index f172208..b0d9e86 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -559,6 +559,25 @@ ERROR:
+ 	return r;
+ }
+ 
++// uevent
++
++int nw_link_handle_uevent(nw_link* link, sd_device* device, sd_device_action_t action) {
++	// 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) {
+diff --git a/src/networkd/link.h b/src/networkd/link.h
+index 36c7d9d..c2f7b7e 100644
+--- a/src/networkd/link.h
++++ b/src/networkd/link.h
+@@ -23,6 +23,8 @@
+ 
+ #include <linux/if_link.h>
+ 
++#include <systemd/sd-device.h>
++
+ typedef struct nw_link nw_link;
+ 
+ #include "daemon.h"
+@@ -46,6 +48,9 @@ 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);
+ 
+-- 
+2.39.2
+
diff --git a/network/patches/0298-links-Initialize-udev-device-when-links-are-created.patch b/network/patches/0298-links-Initialize-udev-device-when-links-are-created.patch
new file mode 100644
index 000000000..997073bf7
--- /dev/null
+++ b/network/patches/0298-links-Initialize-udev-device-when-links-are-created.patch
@@ -0,0 +1,83 @@
+From 4d7437ddbcb56da5d64ae9189bdb2c8c161e4a9b Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sat, 10 Jun 2023 12:04:18 +0000
+Subject: [PATCH 298/304] links: Initialize udev device when links are created
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 52 +++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 52 insertions(+)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index b0d9e86..8ba5da5 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -245,6 +245,53 @@ 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;
+@@ -476,6 +523,11 @@ int nw_link_process(sd_netlink* rtnl, sd_netlink_message* message, void* data) {
+ 					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)
+-- 
+2.39.2
+
diff --git a/network/patches/0299-link-Skip-uevent-when-the-device-is-renaming.patch b/network/patches/0299-link-Skip-uevent-when-the-device-is-renaming.patch
new file mode 100644
index 000000000..8870dd155
--- /dev/null
+++ b/network/patches/0299-link-Skip-uevent-when-the-device-is-renaming.patch
@@ -0,0 +1,60 @@
+From 7d01b16d6ae3502e48bf99b572be627a2d8a2eac Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 11 Jun 2023 11:07:25 +0000
+Subject: [PATCH 299/304] link: Skip uevent when the device is renaming
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/link.c | 35 +++++++++++++++++++++++++++++++++++
+ 1 file changed, 35 insertions(+)
+
+diff --git a/src/networkd/link.c b/src/networkd/link.c
+index 8ba5da5..cb79dd2 100644
+--- a/src/networkd/link.c
++++ b/src/networkd/link.c
+@@ -613,7 +613,42 @@ ERROR:
+ 
+ // 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);
+-- 
+2.39.2
+
diff --git a/network/patches/0300-networkd-Implement-smarter-handling-of-the-configura.patch b/network/patches/0300-networkd-Implement-smarter-handling-of-the-configura.patch
new file mode 100644
index 000000000..dfc0ac3a0
--- /dev/null
+++ b/network/patches/0300-networkd-Implement-smarter-handling-of-the-configura.patch
@@ -0,0 +1,951 @@
+From 25808f650e39dbcdf3e2d0ba42d079a9d5e43079 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 11 Jun 2023 13:02:35 +0000
+Subject: [PATCH 300/304] networkd: Implement smarter handling of the
+ configuration file hierarchy
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/config.c | 207 +++++++++++++++++++++++++++++++++++++++++-
+ src/networkd/config.h |  22 +++++
+ src/networkd/daemon.c |  72 +++++----------
+ src/networkd/daemon.h |   7 +-
+ src/networkd/port.c   |  66 +++++++-------
+ src/networkd/port.h   |   2 +-
+ src/networkd/ports.c  |  50 +++-------
+ src/networkd/zone.c   | 112 +++++++++++++----------
+ src/networkd/zone.h   |   9 +-
+ src/networkd/zones.c  |  37 ++++----
+ 10 files changed, 386 insertions(+), 198 deletions(-)
+
+diff --git a/src/networkd/config.c b/src/networkd/config.c
+index c6281cb..53fd8e3 100644
+--- a/src/networkd/config.c
++++ b/src/networkd/config.c
+@@ -18,12 +18,16 @@
+ #                                                                             #
+ #############################################################################*/
+ 
++#include <dirent.h>
+ #include <errno.h>
++#include <fcntl.h>
+ #include <limits.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <sys/queue.h>
++#include <sys/types.h>
++#include <sys/stat.h>
+ #include <unistd.h>
+ 
+ #include "address.h"
+@@ -161,7 +165,6 @@ int nw_config_open(nw_config** config, const char* path) {
+ 	// Create a new configuration
+ 	r = nw_config_create(config, f);
+ 
+-ERROR:
+ 	if (f)
+ 		fclose(f);
+ 
+@@ -419,6 +422,208 @@ 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
+ */
+diff --git a/src/networkd/config.h b/src/networkd/config.h
+index 4b8bc01..3e7c097 100644
+--- a/src/networkd/config.h
++++ b/src/networkd/config.h
+@@ -21,6 +21,7 @@
+ #ifndef NETWORKD_CONFIG_H
+ #define NETWORKD_CONFIG_H
+ 
++#include <dirent.h>
+ #include <stdio.h>
+ 
+ #define NETWORK_CONFIG_KEY_MAX_LENGTH		128
+@@ -52,6 +53,27 @@ 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
+ */
+diff --git a/src/networkd/daemon.c b/src/networkd/daemon.c
+index a62e343..dfbcc15 100644
+--- a/src/networkd/daemon.c
++++ b/src/networkd/daemon.c
+@@ -51,7 +51,7 @@ struct nw_daemon {
+ 	int nrefs;
+ 
+ 	// Configuration
+-	int configfd;
++	nw_configd* configd;
+ 	nw_config* config;
+ 
+ 	// Event Loop
+@@ -103,36 +103,14 @@ static int __nw_daemon_reload(sd_event_source* source, const struct signalfd_sig
+ */
+ 
+ static int nw_daemon_config_open(nw_daemon* daemon, const char* path) {
+-	// Open the directory
+-	daemon->configfd = open(path, O_DIRECTORY);
+-	if (daemon->configfd < 0) {
+-		ERROR("Could not open %s: %m\n", path);
+-		return -errno;
+-	}
+-
+-	return 0;
+-}
+-
+-FILE* nw_daemon_config_fopen(nw_daemon* daemon, const char* path, const char* mode) {
+-	// Open the file
+-	int fd = openat(daemon->configfd, path, 0);
+-	if (fd < 0) {
+-		ERROR("Could not open configuration file %s: %m\n", path);
+-		return NULL;
+-	}
+-
+-	// Return a file handle
+-	return fdopen(fd, mode);
+-}
++	int r;
+ 
+-DIR* nw_daemon_config_opendir(nw_daemon* daemon, const char* path) {
+-	int fd = openat(daemon->configfd, path, O_DIRECTORY);
+-	if (fd < 0) {
+-		ERROR("Could not open configuration directory %s: %m\n", path);
+-		return NULL;
+-	}
++	// Open the configuration directory
++	r = nw_configd_create(&daemon->configd, path);
++	if (r < 0)
++		return r;
+ 
+-	return fdopendir(fd);
++	return 0;
+ }
+ 
+ static int nw_daemon_parse_argv(nw_daemon* daemon, int argc, char* argv[]) {
+@@ -213,33 +191,17 @@ static int nw_daemon_setup_loop(nw_daemon* daemon) {
+ }
+ 
+ static int nw_daemon_load_config(nw_daemon* daemon) {
+-	FILE* f = NULL;
+ 	int r;
+ 
+ 	// If no configuration path has been opened yet, we will open something
+-	if (!daemon->configfd) {
++	if (!daemon->configd) {
+ 		r = nw_daemon_config_open(daemon, CONFIG_DIR);
+ 		if (r < 0)
+-			goto ERROR;
++			return r;
+ 	}
+ 
+ 	// Open the configuration file
+-	f = nw_daemon_config_fopen(daemon, "settings", "r");
+-	if (!f) {
+-		r = -errno;
+-		goto ERROR;
+-	}
+-
+-	// Create configuration
+-	r = nw_config_create(&daemon->config, f);
+-	if (r < 0)
+-		goto ERROR;
+-
+-ERROR:
+-	if (f)
+-		fclose(f);
+-
+-	return r;
++	return nw_configd_open_config(&daemon->config, daemon->configd, "settings");
+ }
+ 
+ static int nw_start_device_monitor(nw_daemon* daemon) {
+@@ -540,8 +502,8 @@ static void nw_daemon_free(nw_daemon* daemon) {
+ 	// Cleanup common objects
+ 	nw_daemon_cleanup(daemon);
+ 
+-	if (daemon->configfd > 0)
+-		close(daemon->configfd);
++	if (daemon->configd)
++		nw_configd_unref(daemon->configd);
+ 	if (daemon->stats_collector_event)
+ 		sd_event_source_unref(daemon->stats_collector_event);
+ 	if (daemon->bus)
+@@ -640,6 +602,16 @@ int nw_daemon_save(nw_daemon* daemon) {
+ 	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
+ */
+diff --git a/src/networkd/daemon.h b/src/networkd/daemon.h
+index b03086c..2d56d79 100644
+--- a/src/networkd/daemon.h
++++ b/src/networkd/daemon.h
+@@ -29,6 +29,7 @@
+ 
+ typedef struct nw_daemon nw_daemon;
+ 
++#include "config.h"
+ #include "link.h"
+ #include "links.h"
+ #include "port.h"
+@@ -47,11 +48,7 @@ int nw_daemon_reload(nw_daemon* daemon);
+ 
+ int nw_daemon_save(nw_daemon* daemon);
+ 
+-/*
+-	Configuration
+-*/
+-FILE* nw_daemon_config_fopen(nw_daemon* daemon, const char* path, const char* mode);
+-DIR* nw_daemon_config_opendir(nw_daemon* daemon, const char* path);
++nw_configd* nw_daemon_configd(nw_daemon* daemon, const char* path);
+ 
+ /*
+ 	Bus
+diff --git a/src/networkd/port.c b/src/networkd/port.c
+index f547956..141bba5 100644
+--- a/src/networkd/port.c
++++ b/src/networkd/port.c
+@@ -222,24 +222,10 @@ ERROR:
+ 	return r;
+ }
+ 
+-int nw_port_open(nw_port** port, nw_daemon* daemon, const char* name) {
++int nw_port_open(nw_port** port, nw_daemon* daemon, const char* name, FILE* f) {
+ 	nw_config* config = NULL;
+-	FILE* f = NULL;
+-	char path[PATH_MAX];
+ 	int r;
+ 
+-	// Make path
+-	r = nw_string_format(path, "ports/%s", name);
+-	if (r < 0)
+-		goto ERROR;
+-
+-	// Open the configuration file
+-	f = nw_daemon_config_fopen(daemon, path, "r");
+-	if (!f) {
+-		r = -errno;
+-		goto ERROR;
+-	}
+-
+ 	// Initialize the configuration
+ 	r = nw_config_create(&config, f);
+ 	if (r < 0)
+@@ -248,7 +234,7 @@ int nw_port_open(nw_port** port, nw_daemon* daemon, const char* name) {
+ 	// Fetch the type
+ 	const char* type = nw_config_get(config, "TYPE");
+ 	if (!type) {
+-		ERROR("Port configuration %s has no TYPE\n", path);
++		ERROR("Port %s has no TYPE\n", name);
+ 		r = -ENOTSUP;
+ 		goto ERROR;
+ 	}
+@@ -261,8 +247,6 @@ int nw_port_open(nw_port** port, nw_daemon* daemon, const char* name) {
+ ERROR:
+ 	if (config)
+ 		nw_config_unref(config);
+-	if (f)
+-		fclose(f);
+ 
+ 	return r;
+ }
+@@ -292,6 +276,7 @@ static void __nw_port_unref(void* data) {
+ }
+ 
+ int nw_port_destroy(nw_port* port) {
++	nw_configd* configd = NULL;
+ 	int r;
+ 
+ 	DEBUG("Destroying port %s\n", port->name);
+@@ -299,26 +284,33 @@ int nw_port_destroy(nw_port* port) {
+ 	// Destroy the physical link (if exists)
+ 	if (port->link) {
+ 		r = nw_link_destroy(port->link);
+-		if (r)
+-			return r;
++		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)
+-		return r;
++	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)
+-		return r;
++	if (r < 0)
++		goto ERROR;
+ 
+-	// Destroy the configuration
+-	r = nw_config_destroy(port->config);
+-	if (r)
+-		return r;
++	// 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;
++	}
+ 
+-	return 0;
++ERROR:
++	if (configd)
++		nw_configd_unref(configd);
++
++	return r;
+ }
+ 
+ int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
+@@ -345,17 +337,19 @@ int __nw_port_drop_port(nw_daemon* daemon, nw_port* port, void* data) {
+ }
+ 
+ int nw_port_save(nw_port* port) {
+-	char path[PATH_MAX];
++	nw_configd* configd = NULL;
+ 	FILE* f = NULL;
+ 	int r;
+ 
+-	// Compose path
+-	r = nw_string_format(path, "ports/%s", port->name);
+-	if (r < 0)
+-		return r;
++	// Fetch configuration directory
++	configd = nw_daemon_configd(port->daemon, "ports");
++	if (!configd) {
++		r = -errno;
++		goto ERROR;
++	}
+ 
+ 	// Open file
+-	f = nw_daemon_config_fopen(port->daemon, path, "w");
++	f = nw_configd_fopen(configd, port->name, "w");
+ 	if (!f) {
+ 		r = -errno;
+ 		goto ERROR;
+@@ -372,6 +366,8 @@ int nw_port_save(nw_port* port) {
+ 		goto ERROR;
+ 
+ ERROR:
++	if (configd)
++		nw_configd_unref(configd);
+ 	if (f)
+ 		fclose(f);
+ 	if (r)
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index f1cb3d2..3cbe4b0 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -105,7 +105,7 @@ struct nw_port {
+ 
+ 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);
++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);
+diff --git a/src/networkd/ports.c b/src/networkd/ports.c
+index 761e564..95a13e3 100644
+--- a/src/networkd/ports.c
++++ b/src/networkd/ports.c
+@@ -130,12 +130,14 @@ static int nw_ports_add_port(nw_ports* ports, nw_port* port) {
+ 	return 0;
+ }
+ 
+-static int nw_ports_enumerate_port(nw_ports* ports, const char* name) {
++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, name);
++	r = nw_port_open(&port, ports->daemon, entry->d_name, f);
+ 	if (r < 0 || r == 1)
+ 		goto ERROR;
+ 
+@@ -152,45 +154,19 @@ ERROR:
+ }
+ 
+ int nw_ports_enumerate(nw_ports* ports) {
+-	DIR* d = NULL;
+-	struct dirent* entry = NULL;
++	nw_configd* configd = NULL;
+ 	int r;
+ 
+-	// Open the ports directory
+-	d = nw_daemon_config_opendir(ports->daemon, "ports");
+-	if (!d) {
+-		switch (errno) {
+-			case ENOENT:
+-				return 0;
+-
+-			default:
+-				return -errno;
+-		}
+-	}
+-
+-	for (;;) {
+-		// Read the next entry
+-		entry = readdir(d);
+-		if (!entry)
+-			break;
+-
+-		// Skip anything that is not a regular file
+-		if (entry->d_type != DT_REG)
+-			continue;
++	// Fetch ports configuration directory
++	configd = nw_daemon_configd(ports->daemon, "ports");
++	if (!configd)
++		return 0;
+ 
+-		// Skip hidden files
+-		if (entry->d_name[0] == '.')
+-			continue;
++	// Walk through all files
++	r = nw_configd_walk(configd, __nw_ports_enumerate, ports);
+ 
+-		// Enumerate the port
+-		r = nw_ports_enumerate_port(ports, entry->d_name);
+-		if (r < 0)
+-			goto ERROR;
+-	}
+-
+-ERROR:
+-	if (d)
+-		closedir(d);
++	// Cleanup
++	nw_configd_unref(configd);
+ 
+ 	return r;
+ }
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index cc5fdaf..19d221f 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -32,6 +32,12 @@
+ #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)
++
+ struct nw_zone {
+ 	nw_daemon* daemon;
+ 	int nrefs;
+@@ -45,32 +51,6 @@ struct nw_zone {
+ 	nw_config *config;
+ };
+ 
+-#define nw_zone_path(zone, path, format, ...) \
+-	__nw_zone_path(zone, path, sizeof(path), format, __VA_ARGS__)
+-
+-static int __nw_zone_path(nw_zone* zone, char* p, const size_t length,
+-		const char* format, ...) {
+-	char prefix[NAME_MAX];
+-	char suffix[NAME_MAX];
+-	va_list args;
+-	int r;
+-
+-	// Format the prefix
+-	r = nw_string_format(prefix, "%s/zones/%s", CONFIG_DIR, zone->name);
+-	if (r)
+-		return r;
+-
+-	// Format the suffix
+-	va_start(args, format);
+-	r = nw_string_vformat(suffix, format, args);
+-	va_end(args);
+-	if (r)
+-		return r;
+-
+-	// Join the two parts together
+-	return __nw_path_join(p, length, prefix, suffix);
+-}
+-
+ static void nw_zone_free(nw_zone* zone) {
+ 	if (zone->link)
+ 		nw_link_unref(zone->link);
+@@ -109,8 +89,7 @@ static int nw_zone_set_link(nw_zone* zone, nw_link* link) {
+ 
+ static int nw_zone_setup(nw_zone* zone) {
+ 	nw_link* link = NULL;
+-	char path[PATH_MAX];
+-	int r;
++	int r = 0;
+ 
+ 	// Find the link
+ 	link = nw_daemon_get_link_by_name(zone->daemon, zone->name);
+@@ -120,18 +99,6 @@ static int nw_zone_setup(nw_zone* zone) {
+ 			goto ERROR;
+ 	}
+ 
+-#if 0
+-	// Compose the path to the main configuration file
+-	r = nw_zone_path(zone, path, "%s", "settings");
+-	if (r)
+-		goto ERROR;
+-
+-	// Initialize the configuration
+-	r = nw_config_create(&zone->config, path);
+-	if (r)
+-		goto ERROR;
+-#endif
+-
+ ERROR:
+ 	if (link)
+ 		nw_link_unref(link);
+@@ -139,13 +106,15 @@ ERROR:
+ 	return r;
+ }
+ 
+-int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const char* name) {
++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
+-	nw_zone* z = calloc(1, sizeof(*z));
++	z = calloc(1, sizeof(*z));
+ 	if (!z)
+-		return 1;
++		return -errno;
+ 
+ 	// Store a reference to the daemon
+ 	z->daemon = nw_daemon_ref(daemon);
+@@ -158,6 +127,11 @@ int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const char* 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)
+@@ -171,6 +145,35 @@ ERROR:
+ 	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++;
+ 
+@@ -195,30 +198,41 @@ int __nw_zone_drop_port(nw_daemon* daemon, nw_zone* zone, void* data) {
+ }
+ 
+ int nw_zone_save(nw_zone* zone) {
+-	char path[PATH_MAX];
++	nw_configd* configd = NULL;
+ 	FILE* f = NULL;
+ 	int r;
+ 
+-	// Compose path
+-	r = nw_string_format(path, "zones/%s/settings", zone->name);
+-	if (r < 0)
++	// Fetch configuration directory
++	configd = nw_daemon_configd(zone->daemon, "zones");
++	if (!configd) {
++		r = -errno;
+ 		goto ERROR;
++	}
+ 
+ 	// Open file
+-	f = nw_daemon_config_fopen(zone->daemon, path, "w");
++	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)
++	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;
+ }
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 480440f..6ab951b 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -28,11 +28,18 @@
+ 
+ typedef struct nw_zone nw_zone;
+ 
++typedef enum nw_zone_type_id {
++	__EMPTY
++} nw_zone_type_id_t;
++
+ #include <linux/if_link.h>
+ 
++#include "config.h"
+ #include "daemon.h"
+ 
+-int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const char* name);
++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);
+diff --git a/src/networkd/zones.c b/src/networkd/zones.c
+index 84a6673..654e637 100644
+--- a/src/networkd/zones.c
++++ b/src/networkd/zones.c
+@@ -135,30 +135,15 @@ static int nw_zones_add_zone(nw_zones* zones, nw_zone* zone) {
+ 	return 0;
+ }
+ 
+-static int __nw_zones_enumerate(const char* path, const struct stat* s, void* data) {
++static int __nw_zones_enumerate(struct dirent* entry, FILE* f, void* data) {
+ 	nw_zone* zone = NULL;
+ 	int r;
+ 
+ 	nw_zones* zones = (nw_zones*)data;
+ 
+-	// Skip anything that isn't a directory
+-	if (!S_ISDIR(s->st_mode))
+-		return 0;
+-
+-	// Find the basename of the file
+-	const char* name = nw_path_basename(path);
+-
+-	// Break on invalid paths
+-	if (!name)
+-		return 0;
+-
+-	// Skip any hidden files
+-	if (*name == '.')
+-		return 0;
+-
+ 	// Create a new zone
+-	r = nw_zone_create(&zone, zones->daemon, name);
+-	if (r)
++	r = nw_zone_open(&zone, zones->daemon, entry->d_name, f);
++	if (r < 0 || r == 1)
+ 		goto ERROR;
+ 
+ 	// Add the zone to the list
+@@ -174,7 +159,21 @@ ERROR:
+ }
+ 
+ int nw_zones_enumerate(nw_zones* zones) {
+-	return nw_ftw(ZONE_CONFIG_DIR, ZONE_CONFIG_DIR "/*", __nw_zones_enumerate, 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) {
+-- 
+2.39.2
+
diff --git a/network/patches/0301-zones-Move-struct-nw_zone-into-header.patch b/network/patches/0301-zones-Move-struct-nw_zone-into-header.patch
new file mode 100644
index 000000000..84f8b72ec
--- /dev/null
+++ b/network/patches/0301-zones-Move-struct-nw_zone-into-header.patch
@@ -0,0 +1,115 @@
+From 416ba6cd142bc5006b1c977bec86fc12dfffc80c Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 11 Jun 2023 13:09:41 +0000
+Subject: [PATCH 301/304] zones: Move "struct nw_zone" into header
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port-vlan.h |  2 +-
+ src/networkd/port.h      |  6 +-----
+ src/networkd/zone.c      | 13 -------------
+ src/networkd/zone.h      | 16 +++++++++++++++-
+ 4 files changed, 17 insertions(+), 20 deletions(-)
+
+diff --git a/src/networkd/port-vlan.h b/src/networkd/port-vlan.h
+index e17f7e7..c807238 100644
+--- a/src/networkd/port-vlan.h
++++ b/src/networkd/port-vlan.h
+@@ -47,7 +47,7 @@ struct nw_port_vlan {
+ 
+ 	// If the parent has not been read from the configuration we will
+ 	// save the name and try to find it later.
+-	char __parent_name[IF_NAMESIZE];
++	char __parent_name[IFNAMSIZ];
+ };
+ 
+ extern const nw_port_type_t nw_port_type_vlan;
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 3cbe4b0..422a65f 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -25,10 +25,6 @@
+ 
+ #include <systemd/sd-netlink.h>
+ 
+-#ifndef IF_NAMESIZE
+-#define IF_NAMESIZE 16
+-#endif
+-
+ #define PORT_CONFIG_DIR			CONFIG_DIR "/ports"
+ 
+ typedef struct nw_port nw_port;
+@@ -82,7 +78,7 @@ struct nw_port {
+ 	nw_link* link;
+ 
+ 	const nw_port_type_t* type;
+-	char name[IF_NAMESIZE];
++	char name[IFNAMSIZ];
+ 
+ 	// Configuration
+ 	nw_config *config;
+diff --git a/src/networkd/zone.c b/src/networkd/zone.c
+index 19d221f..1610dc0 100644
+--- a/src/networkd/zone.c
++++ b/src/networkd/zone.c
+@@ -38,19 +38,6 @@ static const nw_string_table_t nw_zone_type_id[] = {
+ 
+ NW_STRING_TABLE_LOOKUP(nw_zone_type_id_t, nw_zone_type_id)
+ 
+-struct nw_zone {
+-	nw_daemon* daemon;
+-	int nrefs;
+-
+-	// Link
+-	nw_link* link;
+-
+-	char name[NETWORK_ZONE_NAME_MAX_LENGTH];
+-
+-	// Configuration
+-	nw_config *config;
+-};
+-
+ static void nw_zone_free(nw_zone* zone) {
+ 	if (zone->link)
+ 		nw_link_unref(zone->link);
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 6ab951b..2d1e1dc 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -23,7 +23,6 @@
+ 
+ #define ZONE_CONFIG_DIR			CONFIG_DIR "/zones"
+ 
+-#define NETWORK_ZONE_NAME_MAX_LENGTH		16
+ #define NETWORK_ZONE_DEFAULT_MTU			1500
+ 
+ typedef struct nw_zone nw_zone;
+@@ -32,10 +31,25 @@ typedef enum nw_zone_type_id {
+ 	__EMPTY
+ } nw_zone_type_id_t;
+ 
++#include <linux/if.h>
+ #include <linux/if_link.h>
+ 
+ #include "config.h"
+ #include "daemon.h"
++#include "link.h"
++
++struct nw_zone {
++	nw_daemon* daemon;
++	int nrefs;
++
++	// Link
++	nw_link* link;
++
++	char name[IFNAMSIZ];
++
++	// Configuration
++	nw_config *config;
++};
+ 
+ int nw_zone_create(nw_zone** zone, nw_daemon* daemon, const nw_zone_type_id_t type,
+ 	const char* name, nw_config* config);
+-- 
+2.39.2
+
diff --git a/network/patches/0302-Drop-unused-configuration-file-paths.patch b/network/patches/0302-Drop-unused-configuration-file-paths.patch
new file mode 100644
index 000000000..a390a86f4
--- /dev/null
+++ b/network/patches/0302-Drop-unused-configuration-file-paths.patch
@@ -0,0 +1,40 @@
+From 5548c63c9425f39472e75fef86ad2b8877657997 Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 11 Jun 2023 13:10:31 +0000
+Subject: [PATCH 302/304] Drop unused configuration file paths
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/port.h | 2 --
+ src/networkd/zone.h | 2 --
+ 2 files changed, 4 deletions(-)
+
+diff --git a/src/networkd/port.h b/src/networkd/port.h
+index 422a65f..9770079 100644
+--- a/src/networkd/port.h
++++ b/src/networkd/port.h
+@@ -25,8 +25,6 @@
+ 
+ #include <systemd/sd-netlink.h>
+ 
+-#define PORT_CONFIG_DIR			CONFIG_DIR "/ports"
+-
+ typedef struct nw_port nw_port;
+ 
+ typedef enum nw_port_type_id {
+diff --git a/src/networkd/zone.h b/src/networkd/zone.h
+index 2d1e1dc..2ece268 100644
+--- a/src/networkd/zone.h
++++ b/src/networkd/zone.h
+@@ -21,8 +21,6 @@
+ #ifndef NETWORKD_ZONE_H
+ #define NETWORKD_ZONE_H
+ 
+-#define ZONE_CONFIG_DIR			CONFIG_DIR "/zones"
+-
+ #define NETWORK_ZONE_DEFAULT_MTU			1500
+ 
+ typedef struct nw_zone nw_zone;
+-- 
+2.39.2
+
diff --git a/network/patches/0303-util-Drop-nw_ftw.patch b/network/patches/0303-util-Drop-nw_ftw.patch
new file mode 100644
index 000000000..dbd75ba6d
--- /dev/null
+++ b/network/patches/0303-util-Drop-nw_ftw.patch
@@ -0,0 +1,77 @@
+From 10edad249159db9d6ef1e2b6e7a8fac78f183fce Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Sun, 11 Jun 2023 13:11:11 +0000
+Subject: [PATCH 303/304] util: Drop nw_ftw
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ src/networkd/util.c | 40 ----------------------------------------
+ src/networkd/util.h |  5 -----
+ 2 files changed, 45 deletions(-)
+
+diff --git a/src/networkd/util.c b/src/networkd/util.c
+index 0381ea8..e2f5cf1 100644
+--- a/src/networkd/util.c
++++ b/src/networkd/util.c
+@@ -18,44 +18,4 @@
+ #                                                                             #
+ #############################################################################*/
+ 
+-#include <errno.h>
+-#include <fnmatch.h>
+-#include <ftw.h>
+-#include <stddef.h>
+-#include <stdio.h>
+-
+ #include "util.h"
+-
+-int nw_ftw(const char* path, const char* filter,
+-		int (*callback)(const char* path, const struct stat* s, void* data), void* data) {
+-	/*
+-		This is a nested function which allows us to pass some custom pointer to
+-		the callback function as that isn't possible with nftw().
+-	*/
+-	int __callback(const char* p, const struct stat* s, int type, struct FTW* ftw) {
+-		int r;
+-
+-		// Filter out anything we don't want
+-		if (filter) {
+-			r = fnmatch(filter, p, FNM_PATHNAME);
+-
+-			switch (r) {
+-				// Pattern didn't match
+-				case FNM_NOMATCH:
+-					return 0;
+-
+-				// Pattern matched
+-				case 0:
+-					break;
+-
+-				// Error
+-				default:
+-					return 1;
+-			}
+-		}
+-
+-		return callback(p, s, data);
+-	}
+-
+-	return nftw(path, __callback, 0, 0);
+-}
+diff --git a/src/networkd/util.h b/src/networkd/util.h
+index 950afda..11317ff 100644
+--- a/src/networkd/util.h
++++ b/src/networkd/util.h
+@@ -21,9 +21,4 @@
+ #ifndef NETWORKD_UTIL_H
+ #define NETWORKD_UTIL_H
+ 
+-#include <sys/stat.h>
+-
+-int nw_ftw(const char* path, const char* filter,
+-		int (*callback)(const char* path, const struct stat* s, void* data), void* data);
+-
+ #endif /* NETWORKD_UTIL_H */
+-- 
+2.39.2
+
diff --git a/network/patches/0304-Makefile-Fix-typo-in-localstatedir.patch b/network/patches/0304-Makefile-Fix-typo-in-localstatedir.patch
new file mode 100644
index 000000000..9f4bdd45b
--- /dev/null
+++ b/network/patches/0304-Makefile-Fix-typo-in-localstatedir.patch
@@ -0,0 +1,26 @@
+From b9ac9712ab5dadf45ddc3749d7c9e393bc3eddaf Mon Sep 17 00:00:00 2001
+From: Michael Tremer <michael.tremer@ipfire.org>
+Date: Tue, 19 Sep 2023 12:54:53 +0000
+Subject: [PATCH 304/304] Makefile: Fix typo in localstatedir
+
+Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
+---
+ Makefile.am | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/Makefile.am b/Makefile.am
+index 8c0d0c0..7ca1229 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -51,7 +51,7 @@ hooks_zonesdir   = $(hooksdir)/zones
+ 
+ triggersdir      = $(networkdir)/triggers
+ 
+-logdir           = $(localestatedir)/log/network
++logdir           = $(localstatedir)/log/network
+ utildir          = $(networkdir)
+ 
+ CLEANFILES =
+-- 
+2.39.2
+
diff --git a/network/patches/network-fix-logdir-path.patch b/network/patches/network-fix-logdir-path.patch
deleted file mode 100644
index f708c030a..000000000
--- a/network/patches/network-fix-logdir-path.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-commit 88e5f32944b8dc1c4b1c74028c7d46c37b2aad34
-Author: Stefan Schantl <stefan.schantl@ipfire.org>
-Date:   Sun Mar 19 15:58:11 2023 +0100
-
-    Makefile.am: Fix typo defining the logdir
-    
-    The correct value name should be "localstatedir"
-    
-    Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
-
-diff --git a/Makefile.am b/Makefile.am
-index 893f1b8..5dc4629 100644
---- a/Makefile.am
-+++ b/Makefile.am
-@@ -51,7 +51,7 @@ hooks_zonesdir   = $(hooksdir)/zones
- 
- triggersdir      = $(networkdir)/triggers
- 
--logdir           = $(localestatedir)/log/network
-+logdir           = $(localstatedir)/log/network
- utildir          = $(networkdir)
- 
- CLEANFILES =
