diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml
index d950eb578b..380d0088ce 100644
--- a/man/systemd.netdev.xml
+++ b/man/systemd.netdev.xml
@@ -163,6 +163,10 @@
netdevsim
A simulator. This simulated networking device is used for testing various networking APIs and at this time is particularly focused on testing hardware offloading related interfaces.
+
+ fou
+ Foo-over-UDP tunneling.
+
@@ -879,8 +883,76 @@
+
+ FooOverUDP=
+
+ A boolean. Specifies whether FooOverUDP= tunnel is to be configured.
+ Defaults to false. For more detail information see
+ Foo over UDP
+
+
+
+ FOUDestinationPort=
+
+ The FOUDestinationPort= specifies the UDP destination port for encapsulation.
+ This field is mandatory and is not set by default.
+
+
+
+ FOUSourcePort=
+
+ The FOUSourcePort= specifies the UDP source port for encapsulation. Defaults to 0,
+ that is, the source port for packets is left to the network stack to decide.
+
+
+
+ Encapsulation=
+
+ Accepts the same key as [FooOverUDP]
+
+
+
+
+ [FooOverUDP] Section Options
+
+ The [FooOverUDP] section only applies for
+ netdevs of kind fou and accepts the
+ following keys:
+
+
+
+ Protocol=
+
+ The Protocol= specifies the protocol number of the
+ packets arriving at the UDP port. This field is mandatory and is not set by default. Valid range is 1-255.
+
+
+
+ Encapsulation=
+
+ Specifies the encapsulation mechanism used to store networking packets of various protocols inside the UDP packets. Supports the following values:
+
+ FooOverUDP provides the simplest no frills model of UDP encapsulation, it simply encapsulates
+ packets directly in the UDP payload.
+ GenericUDPEncapsulation is a generic and extensible encapsulation, it allows encapsulation of packets for any IP
+ protocol and optional data as part of the encapsulation.
+ For more detailed information see Generic UDP Encapsulation.
+ Defaults to FooOverUDP.
+
+
+
+
+ Port=
+
+ Specifies the port number, where the IP encapsulation packets will arrive. Please take note that the packets
+ will arrive with the encapsulation will be removed. Then they will be manually fed back into the network stack, and sent ahead
+ for delivery to the real destination. This option is mandatory.
+
+
+
+
[Peer] Section Options
@@ -1379,6 +1451,32 @@ Local=192.168.223.238
Remote=192.169.224.239
TTL=64
+
+ /etc/systemd/network/1-fou-tunnel.netdev
+ [NetDev]
+Name=fou-tun
+Kind=fou
+
+[FooOverUDP]
+Port=5555
+Protocol=4
+
+
+
+ /etc/systemd/network/25-fou-ipip.netdev
+ [NetDev]
+[NetDev]
+Name=ipip-tun
+Kind=ipip
+
+[Tunnel]
+Independent=true
+Local=10.65.208.212
+Remote=10.65.208.211
+FooOverUDP=true
+FOUDestinationPort=5555
+
+
/etc/systemd/network/25-tap.netdev
[NetDev]
diff --git a/meson.build b/meson.build
index 2209c935ad..f4eabe7373 100644
--- a/meson.build
+++ b/meson.build
@@ -464,6 +464,9 @@ foreach decl : [['IFLA_INET6_ADDR_GEN_MODE', 'linux/if_link.h'],
['FRA_UID_RANGE', 'linux/fib_rules.h'],
['LO_FLAGS_PARTSCAN', 'linux/loop.h'],
['VXCAN_INFO_PEER', 'linux/can/vxcan.h'],
+ ['FOU_ATTR_REMCSUM_NOPARTIAL', 'linux/fou.h'],
+ ['FOU_CMD_GET', 'linux/fou.h'],
+ ['FOU_ENCAP_GUE', 'linux/fou.h'],
]
prefix = decl.length() > 2 ? decl[2] : ''
have = cc.has_header_symbol(decl[1], decl[0], prefix : prefix)
diff --git a/src/basic/missing.h b/src/basic/missing.h
index 477992fbcb..e666e2ba2d 100644
--- a/src/basic/missing.h
+++ b/src/basic/missing.h
@@ -1409,4 +1409,43 @@ struct statx {
#define TASK_COMM_LEN 16
#endif
+#ifndef FOU_GENL_NAME
+#define FOU_GENL_NAME "fou"
+#endif
+
+#ifndef FOU_GENL_VERSION
+#define FOU_GENL_VERSION 0x1
+#endif
+
+#if !HAVE_FOU_ATTR_REMCSUM_NOPARTIAL
+#define FOU_ATTR_UNSPEC 0
+#define FOU_ATTR_PORT 1
+#define FOU_ATTR_AF 2
+#define FOU_ATTR_IPPROTO 3
+#define FOU_ATTR_TYPE 4
+#define FOU_ATTR_REMCSUM_NOPARTIAL 5
+#define __FOU_ATTR_MAX 6
+
+#define FOU_ATTR_MAX (__FOU_ATTR_MAX - 1)
+#endif
+
+#if !HAVE_FOU_CMD_GET
+#define FOU_CMD_UNSPEC 0
+#define FOU_CMD_ADD 1
+#define FOU_CMD_DEL 2
+#define FOU_CMD_GET 3
+#define __FOU_CMD_MAX 4
+
+#define FOU_CMD_MAX (__FOU_CMD_MAX - 1)
+#endif
+
+#if !HAVE_FOU_ENCAP_GUE
+#define FOU_ENCAP_UNSPEC 0
+#define FOU_ENCAP_DIRECT 1
+#define FOU_ENCAP_GUE 2
+#define __FOU_ENCAP_MAX 3
+
+#define FOU_ENCAP_MAX (__FOU_ENCAP_MAX - 1)
+#endif
+
#include "missing_syscall.h"
diff --git a/src/libsystemd/sd-netlink/generic-netlink.c b/src/libsystemd/sd-netlink/generic-netlink.c
index 347bf4cbd5..3445757da9 100644
--- a/src/libsystemd/sd-netlink/generic-netlink.c
+++ b/src/libsystemd/sd-netlink/generic-netlink.c
@@ -12,6 +12,7 @@ typedef struct {
static const genl_family genl_families[] = {
[SD_GENL_ID_CTRL] = { .name = "", .version = 1 },
[SD_GENL_WIREGUARD] = { .name = "wireguard", .version = 1 },
+ [SD_GENL_FOU] = { .name = "fou", .version = 1 },
};
int sd_genl_socket_open(sd_netlink **ret) {
diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c
index c93fe9cb4c..b50a8c8303 100644
--- a/src/libsystemd/sd-netlink/netlink-types.c
+++ b/src/libsystemd/sd-netlink/netlink-types.c
@@ -16,6 +16,11 @@
#include
#include
#include
+
+#if HAVE_FOU_CMD_GET
+#include
+#endif
+
#if HAVE_VXCAN_INFO_PEER
#include
#endif
@@ -733,9 +738,34 @@ static const NLTypeSystem genl_ctrl_id_ctrl_type_system = {
.types = genl_ctrl_id_ctrl_cmds,
};
+static const NLType genl_fou_types[] = {
+ [FOU_ATTR_PORT] = { .type = NETLINK_TYPE_U16 },
+ [FOU_ATTR_AF] = { .type = NETLINK_TYPE_U8 },
+ [FOU_ATTR_IPPROTO] = { .type = NETLINK_TYPE_U8 },
+ [FOU_ATTR_TYPE] = { .type = NETLINK_TYPE_U8 },
+ [FOU_ATTR_REMCSUM_NOPARTIAL] = { .type = NETLINK_TYPE_FLAG },
+};
+
+static const NLTypeSystem genl_fou_type_system = {
+ .count = ELEMENTSOF(genl_fou_types),
+ .types = genl_fou_types,
+};
+
+static const NLType genl_fou_cmds[] = {
+ [FOU_CMD_ADD] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_fou_type_system },
+ [FOU_CMD_DEL] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_fou_type_system },
+ [FOU_CMD_GET] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_fou_type_system },
+};
+
+static const NLTypeSystem genl_fou_cmds_type_system = {
+ .count = ELEMENTSOF(genl_fou_cmds),
+ .types = genl_fou_cmds,
+};
+
static const NLType genl_families[] = {
- [SD_GENL_ID_CTRL] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_ctrl_id_ctrl_type_system },
+ [SD_GENL_ID_CTRL] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_ctrl_id_ctrl_type_system },
[SD_GENL_WIREGUARD] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_wireguard_type_system },
+ [SD_GENL_FOU] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_fou_cmds_type_system},
};
const NLTypeSystem genl_family_type_system_root = {
diff --git a/src/network/meson.build b/src/network/meson.build
index 8f63815b12..175d2f822c 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -35,6 +35,8 @@ sources = files('''
netdev/wireguard.h
netdev/netdevsim.c
netdev/netdevsim.h
+ netdev/fou-tunnel.c
+ netdev/fou-tunnel.h
networkd-address-label.c
networkd-address-label.h
networkd-address-pool.c
diff --git a/src/network/netdev/fou-tunnel.c b/src/network/netdev/fou-tunnel.c
new file mode 100644
index 0000000000..6ec04c2f0f
--- /dev/null
+++ b/src/network/netdev/fou-tunnel.c
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include
+#include
+#include
+
+#include "conf-parser.h"
+#include "missing.h"
+#include "netdev/fou-tunnel.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "sd-netlink.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "util.h"
+
+static const char* const fou_encap_type_table[_NETDEV_FOO_OVER_UDP_ENCAP_MAX] = {
+ [NETDEV_FOO_OVER_UDP_ENCAP_DIRECT] = "FooOverUDP",
+ [NETDEV_FOO_OVER_UDP_ENCAP_GUE] = "GenericUDPEncapsulation",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(fou_encap_type, FooOverUDPEncapType);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_fou_encap_type, fou_encap_type, FooOverUDPEncapType, "Failed to parse Foo Over UDP Encap type");
+
+static int netdev_fill_fou_tunnel_message(NetDev *netdev, sd_netlink_message **ret) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ FouTunnel *t;
+ int r;
+
+ assert(netdev);
+
+ t = FOU(netdev);
+
+ assert(t);
+
+ r = sd_genl_message_new(netdev->manager->genl, SD_GENL_FOU, FOU_CMD_ADD, &m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Failed to allocate generic netlink message: %m");
+
+ r = sd_netlink_message_append_u16(m, FOU_ATTR_PORT, htobe16(t->port));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PORT attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, FOU_ATTR_TYPE, FOU_ENCAP_GUE);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_TYPE attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, FOU_ATTR_AF, AF_INET);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_AF attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, FOU_ATTR_IPPROTO, t->fou_protocol);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_IPPROTO attribute: %m");
+
+ *ret = m;
+ m = NULL;
+
+ return 0;
+}
+
+static int netdev_fou_tunnel_create(NetDev *netdev) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL;
+ uint32_t serial;
+ FouTunnel *t;
+ int r;
+
+ assert(netdev);
+
+ t = FOU(netdev);
+
+ assert(t);
+
+ r = netdev_fill_fou_tunnel_message(netdev, &m);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_send(netdev->manager->genl, m, &serial);
+ if (r < 0 && r != -EADDRINUSE)
+ return log_netdev_error_errno(netdev, r, "Failed to add FooOverUDP tunnel: %m");
+
+ return 0;
+}
+
+static int netdev_fou_tunnel_verify(NetDev *netdev, const char *filename) {
+ FouTunnel *t;
+
+ assert(netdev);
+ assert(filename);
+
+ t = FOU(netdev);
+
+ assert(t);
+
+ if (t->fou_encap_type == NETDEV_FOO_OVER_UDP_ENCAP_DIRECT && t->fou_protocol <= 0) {
+ log_netdev_error(netdev, "FooOverUDP missing protocol configured in %s. Ignoring", filename);
+ return -EINVAL;
+ }
+
+ if (t->fou_encap_type == NETDEV_FOO_OVER_UDP_ENCAP_GUE && t->fou_protocol > 0) {
+ log_netdev_error(netdev, "FooOverUDP GUE can't be set with protocol configured in %s. Ignoring", filename);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void fou_tunnel_init(NetDev *netdev) {
+ FouTunnel *t;
+
+ assert(netdev);
+
+ t = FOU(netdev);
+
+ assert(t);
+
+ t->fou_encap_type = NETDEV_FOO_OVER_UDP_ENCAP_DIRECT;
+}
+
+const NetDevVTable foutnl_vtable = {
+ .object_size = sizeof(FouTunnel),
+ .init = fou_tunnel_init,
+ .sections = "Match\0NetDev\0FooOverUDP\0",
+ .create = netdev_fou_tunnel_create,
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+ .config_verify = netdev_fou_tunnel_verify,
+};
diff --git a/src/network/netdev/fou-tunnel.h b/src/network/netdev/fou-tunnel.h
new file mode 100644
index 0000000000..58bcab3164
--- /dev/null
+++ b/src/network/netdev/fou-tunnel.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#if HAVE_FOU_CMD_GET
+#include
+#endif
+
+#include "in-addr-util.h"
+#include "netdev/netdev.h"
+
+typedef enum FooOverUDPEncapType {
+ NETDEV_FOO_OVER_UDP_ENCAP_UNSPEC = FOU_ENCAP_UNSPEC,
+ NETDEV_FOO_OVER_UDP_ENCAP_DIRECT = FOU_ENCAP_DIRECT,
+ NETDEV_FOO_OVER_UDP_ENCAP_GUE = FOU_ENCAP_GUE,
+ _NETDEV_FOO_OVER_UDP_ENCAP_MAX,
+ _NETDEV_FOO_OVER_UDP_ENCAP_INVALID = -1,
+} FooOverUDPEncapType;
+
+typedef struct FouTunnel {
+ NetDev meta;
+
+ uint8_t fou_protocol;
+
+ uint16_t port;
+
+ FooOverUDPEncapType fou_encap_type;
+} FouTunnel;
+
+DEFINE_NETDEV_CAST(FOU, FouTunnel);
+extern const NetDevVTable foutnl_vtable;
+
+const char *fou_encap_type_to_string(FooOverUDPEncapType d) _const_;
+FooOverUDPEncapType fou_encap_type_from_string(const char *d) _pure_;
+
+int config_parse_fou_encap_type(const char *unit, const char *filename,
+ unsigned line, const char *section,
+ unsigned section_line, const char *lvalue,
+ int ltype, const char *rvalue, void *data,
+ void *userdata);
diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf
index dfcac9adbf..96c8fb1832 100644
--- a/src/network/netdev/netdev-gperf.gperf
+++ b/src/network/netdev/netdev-gperf.gperf
@@ -19,6 +19,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
#include "netdev/netdev.h"
#include "netdev/vxcan.h"
#include "netdev/wireguard.h"
+#include "netdev/fou-tunnel.h"
#include "vlan-util.h"
%}
struct ConfigPerfItem;
@@ -65,6 +66,13 @@ Tunnel.CopyDSCP, config_parse_bool, 0,
Tunnel.EncapsulationLimit, config_parse_encap_limit, 0, offsetof(Tunnel, encap_limit)
Tunnel.Independent, config_parse_bool, 0, offsetof(Tunnel, independent)
Tunnel.AllowLocalRemote, config_parse_tristate, 0, offsetof(Tunnel, allow_localremote)
+Tunnel.FooOverUDP, config_parse_bool, 0, offsetof(Tunnel, fou_tunnel)
+Tunnel.FOUDestinationPort, config_parse_ip_port, 0, offsetof(Tunnel, fou_destination_port)
+Tunnel.FOUSourcePort, config_parse_ip_port, 0, offsetof(Tunnel, encap_src_port)
+Tunnel.Encapsulation, config_parse_fou_encap_type, 0, offsetof(Tunnel, fou_encap_type)
+FooOverUDP.Protocol, config_parse_uint8, 0, offsetof(FouTunnel, fou_protocol)
+FooOverUDP.Encapsulation, config_parse_fou_encap_type, 0, offsetof(FouTunnel, fou_encap_type)
+FooOverUDP.Port, config_parse_ip_port, 0, offsetof(FouTunnel, port)
Peer.Name, config_parse_ifname, 0, offsetof(Veth, ifname_peer)
Peer.MACAddress, config_parse_hwaddr, 0, offsetof(Veth, mac_peer)
VXCAN.Peer, config_parse_ifname, 0, offsetof(VxCan, ifname_peer)
diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c
index 17bfa518dc..93df63b5c1 100644
--- a/src/network/netdev/netdev.c
+++ b/src/network/netdev/netdev.c
@@ -34,6 +34,7 @@
#include "netdev/vxcan.h"
#include "netdev/wireguard.h"
#include "netdev/netdevsim.h"
+#include "netdev/fou-tunnel.h"
const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_BRIDGE] = &bridge_vtable,
@@ -62,6 +63,7 @@ const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_VXCAN] = &vxcan_vtable,
[NETDEV_KIND_WIREGUARD] = &wireguard_vtable,
[NETDEV_KIND_NETDEVSIM] = &netdevsim_vtable,
+ [NETDEV_KIND_FOU] = &foutnl_vtable,
};
static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = {
@@ -91,6 +93,7 @@ static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_VXCAN] = "vxcan",
[NETDEV_KIND_WIREGUARD] = "wireguard",
[NETDEV_KIND_NETDEVSIM] = "netdevsim",
+ [NETDEV_KIND_FOU] = "fou",
};
DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetDevKind);
diff --git a/src/network/netdev/netdev.h b/src/network/netdev/netdev.h
index bdab18043b..d073216b14 100644
--- a/src/network/netdev/netdev.h
+++ b/src/network/netdev/netdev.h
@@ -43,6 +43,7 @@ typedef enum NetDevKind {
NETDEV_KIND_VXCAN,
NETDEV_KIND_WIREGUARD,
NETDEV_KIND_NETDEVSIM,
+ NETDEV_KIND_FOU,
_NETDEV_KIND_MAX,
_NETDEV_KIND_INVALID = -1
} NetDevKind;
diff --git a/src/network/netdev/tunnel.c b/src/network/netdev/tunnel.c
index c15ca74717..e39dff7b90 100644
--- a/src/network/netdev/tunnel.c
+++ b/src/network/netdev/tunnel.c
@@ -8,6 +8,10 @@
#include "sd-netlink.h"
+#if HAVE_FOU_CMD_GET
+#include
+#endif
+
#include "conf-parser.h"
#include "missing.h"
#include "networkd-link.h"
@@ -61,6 +65,21 @@ static int netdev_ipip_fill_message_create(NetDev *netdev, Link *link, sd_netlin
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_PMTUDISC attribute: %m");
+ if (t->fou_tunnel) {
+
+ r = sd_netlink_message_append_u16(m, IFLA_IPTUN_ENCAP_TYPE, t->fou_encap_type);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_TYPE attribute: %m");
+
+ r = sd_netlink_message_append_u16(m, IFLA_IPTUN_ENCAP_SPORT, htobe16(t->encap_src_port));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_SPORT attribute: %m");
+
+ r = sd_netlink_message_append_u16(m, IFLA_IPTUN_ENCAP_DPORT, htobe16(t->fou_destination_port));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_DPORT attribute: %m");
+ }
+
return r;
}
@@ -417,6 +436,11 @@ static int netdev_tunnel_verify(NetDev *netdev, const char *filename) {
return -EINVAL;
}
+ if (t->fou_tunnel && t->fou_destination_port <= 0) {
+ log_netdev_error(netdev, "FooOverUDP missing port configured in %s. Ignoring", filename);
+ return -EINVAL;
+ }
+
return 0;
}
@@ -600,6 +624,7 @@ static void ipip_init(NetDev *n) {
assert(t);
t->pmtudisc = true;
+ t->fou_encap_type = FOU_ENCAP_DIRECT;
}
static void sit_init(NetDev *n) {
diff --git a/src/network/netdev/tunnel.h b/src/network/netdev/tunnel.h
index 029d845378..40ddb1c043 100644
--- a/src/network/netdev/tunnel.h
+++ b/src/network/netdev/tunnel.h
@@ -4,6 +4,7 @@
#include "in-addr-util.h"
#include "netdev/netdev.h"
+#include "netdev/fou-tunnel.h"
typedef enum Ip6TnlMode {
NETDEV_IP6_TNL_MODE_IP6IP6,
@@ -40,10 +41,15 @@ typedef struct Tunnel {
union in_addr_union remote;
Ip6TnlMode ip6tnl_mode;
+ FooOverUDPEncapType fou_encap_type;
bool pmtudisc;
bool copy_dscp;
bool independent;
+ bool fou_tunnel;
+
+ uint16_t encap_src_port;
+ uint16_t fou_destination_port;
} Tunnel;
DEFINE_NETDEV_CAST(IPIP, Tunnel);
@@ -96,6 +102,7 @@ int config_parse_encap_limit(const char *unit, const char *filename,
unsigned section_line, const char *lvalue,
int ltype, const char *rvalue, void *data,
void *userdata);
+
int config_parse_tunnel_key(const char *unit, const char *filename,
unsigned line, const char *section,
unsigned section_line, const char *lvalue,
diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h
index 51f0fa16b4..7650ff4532 100644
--- a/src/systemd/sd-netlink.h
+++ b/src/systemd/sd-netlink.h
@@ -32,7 +32,7 @@ _SD_BEGIN_DECLARATIONS;
typedef struct sd_netlink sd_netlink;
typedef struct sd_genl_socket sd_genl_socket;
typedef struct sd_netlink_message sd_netlink_message;
-typedef enum {SD_GENL_ID_CTRL, SD_GENL_WIREGUARD} sd_genl_family;
+typedef enum {SD_GENL_ID_CTRL, SD_GENL_WIREGUARD, SD_GENL_FOU} sd_genl_family;
/* callback */