[PATCH v4 3/3] platform: add support for WireGuard links



Add support for a new wireguard link type to the platform code. For now
this only covers querying existing links via genetlink and parsing them
into platform objects.
---

Notes:
    Changes in v3->v4:
    
    * Fixed using nm_hash_update_vals to hash array members
    * Removed initial one-shot resolution of wireguard genl family
    * Renamed 'cidr' to 'mask'
    * Removed spurious added blank line

 libnm-core/nm-core-types-internal.h |  25 +++
 src/nm-types.h                      |   2 +
 src/platform/nm-linux-platform.c    | 313 ++++++++++++++++++++++++++++
 src/platform/nm-platform.c          | 123 +++++++++++
 src/platform/nm-platform.h          |  15 ++
 src/platform/nmp-object.c           | 219 +++++++++++++++++++
 src/platform/nmp-object.h           |  10 +
 7 files changed, 707 insertions(+)

diff --git a/libnm-core/nm-core-types-internal.h b/libnm-core/nm-core-types-internal.h
index 4d43aaf45..d7dce9521 100644
--- a/libnm-core/nm-core-types-internal.h
+++ b/libnm-core/nm-core-types-internal.h
@@ -31,6 +31,31 @@ typedef struct {
        guint32 to;
 } NMVlanQosMapping;
 
+typedef struct {
+       NMIPAddr ip;
+       guint8 family;
+       guint8 mask;
+} NMWireguardAllowedIP;
+
+#define NM_WG_PUBLIC_KEY_LEN 32
+#define NM_WG_SYMMETRIC_KEY_LEN 32
+
+typedef struct {
+       guint8 public_key[NM_WG_PUBLIC_KEY_LEN];
+       guint8 preshared_key[NM_WG_SYMMETRIC_KEY_LEN];
+       union {
+               struct sockaddr addr;
+               struct sockaddr_in addr4;
+               struct sockaddr_in6 addr6;
+       } endpoint;
+       guint16 persistent_keepalive_interval;
+       struct timespec last_handshake_time;
+       guint64 rx_bytes, tx_bytes;
+
+       gsize allowedips_len;
+       NMWireguardAllowedIP *allowedips;
+} NMWireguardPeer;
+
 #define _NM_IP_TUNNEL_FLAG_ALL_IP6TNL \
        ( NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT \
        | NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS \
diff --git a/src/nm-types.h b/src/nm-types.h
index 1477d2269..6f73289b0 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -170,6 +170,7 @@ typedef enum {
        NM_LINK_TYPE_VETH,
        NM_LINK_TYPE_VLAN,
        NM_LINK_TYPE_VXLAN,
+       NM_LINK_TYPE_WIREGUARD,
 
        /* Software types with slaves */
        NM_LINK_TYPE_BRIDGE = 0x10000 | 0x20000,
@@ -202,6 +203,7 @@ typedef enum {
        NMP_OBJECT_TYPE_LNK_TUN,
        NMP_OBJECT_TYPE_LNK_VLAN,
        NMP_OBJECT_TYPE_LNK_VXLAN,
+       NMP_OBJECT_TYPE_LNK_WIREGUARD,
 
        __NMP_OBJECT_TYPE_LAST,
        NMP_OBJECT_TYPE_MAX = __NMP_OBJECT_TYPE_LAST - 1,
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 32d4203b8..e3eccb604 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -173,6 +173,40 @@ G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
 
 /*****************************************************************************/
 
+#define WG_CMD_GET_DEVICE 0
+#define WG_CMD_SET_DEVICE 1
+
+#define WGDEVICE_A_UNSPEC                      0
+#define WGDEVICE_A_IFINDEX                     1
+#define WGDEVICE_A_IFNAME                      2
+#define WGDEVICE_A_PRIVATE_KEY                 3
+#define WGDEVICE_A_PUBLIC_KEY                  4
+#define WGDEVICE_A_FLAGS                       5
+#define WGDEVICE_A_LISTEN_PORT                 6
+#define WGDEVICE_A_FWMARK                      7
+#define WGDEVICE_A_PEERS                       8
+#define WGDEVICE_A_MAX                         8
+
+#define WGPEER_A_UNSPEC                        0
+#define WGPEER_A_PUBLIC_KEY                    1
+#define WGPEER_A_PRESHARED_KEY                 2
+#define WGPEER_A_FLAGS                         3
+#define WGPEER_A_ENDPOINT                      4
+#define WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL 5
+#define WGPEER_A_LAST_HANDSHAKE_TIME           6
+#define WGPEER_A_RX_BYTES                      7
+#define WGPEER_A_TX_BYTES                      8
+#define WGPEER_A_ALLOWEDIPS                    9
+#define WGPEER_A_MAX                           9
+
+#define WGALLOWEDIP_A_UNSPEC                   0
+#define WGALLOWEDIP_A_FAMILY                   1
+#define WGALLOWEDIP_A_IPADDR                   2
+#define WGALLOWEDIP_A_CIDR_MASK                3
+#define WGALLOWEDIP_A_MAX                      3
+
+/*****************************************************************************/
+
 #define _NMLOG_PREFIX_NAME                "platform-linux"
 #define _NMLOG_DOMAIN                     LOGD_PLATFORM
 #define _NMLOG2_DOMAIN                    LOGD_PLATFORM
@@ -564,6 +598,7 @@ static const LinkDesc linktypes[] = {
        { NM_LINK_TYPE_VETH,          "veth",        "veth",        NULL },
        { NM_LINK_TYPE_VLAN,          "vlan",        "vlan",        "vlan" },
        { NM_LINK_TYPE_VXLAN,         "vxlan",       "vxlan",       "vxlan" },
+       { NM_LINK_TYPE_WIREGUARD,     "wireguard",   "wireguard",   "wireguard" },
 
        { NM_LINK_TYPE_BRIDGE,        "bridge",      "bridge",      "bridge" },
        { NM_LINK_TYPE_BOND,          "bond",        "bond",        "bond" },
@@ -1675,6 +1710,197 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 
 /*****************************************************************************/
 
+/* Context to build a NMPObjectLnkWireguard instance.
+ * GArray wrappers are discarded after processing all netlink messages. */
+struct _wireguard_device_buf {
+       NMPObject *obj;
+       GArray *peers;
+       GArray *allowedips;
+};
+
+static int
+_wireguard_update_from_allowedips_nla (struct _wireguard_device_buf *buf,
+                                       struct nlattr *allowedip_attr)
+{
+       static const struct nla_policy allowedip_policy[WGALLOWEDIP_A_MAX + 1] = {
+               [WGALLOWEDIP_A_FAMILY]    = { .type = NLA_U16 },
+               [WGALLOWEDIP_A_IPADDR]    = { .minlen = sizeof (struct in_addr) },
+               [WGALLOWEDIP_A_CIDR_MASK] = { .type = NLA_U8 },
+       };
+       struct nlattr *tba[WGALLOWEDIP_A_MAX + 1];
+       NMWireguardPeer *peer = &g_array_index (buf->peers, NMWireguardPeer, buf->peers->len - 1);
+       NMWireguardAllowedIP *allowedip;
+       NMWireguardAllowedIP new_allowedip = {0};
+       int addr_len;
+       int ret;
+
+       ret = nla_parse_nested (tba, WGALLOWEDIP_A_MAX, allowedip_attr, allowedip_policy);
+       if (ret)
+               goto errout;
+
+       g_array_append_val (buf->allowedips, new_allowedip);
+       allowedip = &g_array_index (buf->allowedips, NMWireguardAllowedIP, buf->allowedips->len - 1);
+       peer->allowedips_len++;
+
+       if (tba[WGALLOWEDIP_A_FAMILY])
+               allowedip->family = nla_get_u16 (tba[WGALLOWEDIP_A_FAMILY]);
+
+       if (allowedip->family == AF_INET)
+               addr_len = sizeof (in_addr_t);
+       else if (allowedip->family == AF_INET6)
+               addr_len = sizeof (struct in6_addr);
+       else {
+               ret = -EAFNOSUPPORT;
+               goto errout;
+       }
+
+       ret = -EMSGSIZE;
+       _check_addr_or_errout (tba, WGALLOWEDIP_A_IPADDR, addr_len);
+       if (tba[WGALLOWEDIP_A_IPADDR])
+               nla_memcpy (&allowedip->ip, tba[WGALLOWEDIP_A_IPADDR], addr_len);
+
+       if (tba[WGALLOWEDIP_A_CIDR_MASK])
+               allowedip->mask = nla_get_u8 (tba[WGALLOWEDIP_A_CIDR_MASK]);
+
+       ret = 0;
+errout:
+       return ret;
+}
+
+static int
+_wireguard_update_from_peers_nla (struct _wireguard_device_buf *buf,
+                                  struct nlattr *peer_attr)
+{
+       static const struct nla_policy peer_policy[WGPEER_A_MAX + 1] = {
+               [WGPEER_A_PUBLIC_KEY]                    = { .minlen = NM_WG_PUBLIC_KEY_LEN },
+               [WGPEER_A_PRESHARED_KEY]                 = { },
+               [WGPEER_A_FLAGS]                         = { .type = NLA_U32 },
+               [WGPEER_A_ENDPOINT]                      = { },
+               [WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = { .type = NLA_U16 },
+               [WGPEER_A_LAST_HANDSHAKE_TIME]           = { },
+               [WGPEER_A_RX_BYTES]                      = { .type = NLA_U64 },
+               [WGPEER_A_TX_BYTES]                      = { .type = NLA_U64 },
+               [WGPEER_A_ALLOWEDIPS]                    = { .type = NLA_NESTED },
+       };
+       struct nlattr *tbp[WGPEER_A_MAX + 1];
+       NMWireguardPeer * const last = buf->peers->len ? &g_array_index (buf->peers, NMWireguardPeer, 
buf->peers->len - 1) : NULL;
+       NMWireguardPeer *peer;
+       NMWireguardPeer new_peer = {0};
+       int ret;
+
+       if (nla_parse_nested (tbp, WGPEER_A_MAX, peer_attr, peer_policy)) {
+               ret = -EBADMSG;
+               goto errout;
+       }
+
+       if (!tbp[WGPEER_A_PUBLIC_KEY]) {
+               ret = -EBADMSG;
+               goto errout;
+       }
+
+       /* a peer with the same public key as last peer is just a continuation for extra AllowedIPs */
+       if (last && !memcmp (nla_data (tbp[WGPEER_A_PUBLIC_KEY]), last->public_key, sizeof 
(last->public_key))) {
+               peer = last;
+               goto add_allowedips;
+       }
+
+       /* otherwise, start a new peer */
+       g_array_append_val (buf->peers, new_peer);
+       peer = &g_array_index (buf->peers, NMWireguardPeer, buf->peers->len - 1);
+
+       nla_memcpy (&peer->public_key, tbp[WGPEER_A_PUBLIC_KEY], sizeof (peer->public_key));
+
+       if (tbp[WGPEER_A_PRESHARED_KEY])
+               nla_memcpy (&peer->preshared_key, tbp[WGPEER_A_PRESHARED_KEY], sizeof (peer->preshared_key));
+       if (tbp[WGPEER_A_ENDPOINT]) {
+               struct sockaddr *addr = nla_data (tbp[WGPEER_A_ENDPOINT]);
+               if (addr->sa_family == AF_INET)
+                       nla_memcpy (&peer->endpoint.addr4, tbp[WGPEER_A_ENDPOINT], sizeof 
(peer->endpoint.addr4));
+               else if (addr->sa_family == AF_INET6)
+                       nla_memcpy (&peer->endpoint.addr6, tbp[WGPEER_A_ENDPOINT], sizeof 
(peer->endpoint.addr6));
+       }
+       if (tbp[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL])
+               peer->persistent_keepalive_interval = nla_get_u64 
(tbp[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
+       if (tbp[WGPEER_A_LAST_HANDSHAKE_TIME])
+               nla_memcpy (&peer->last_handshake_time, tbp[WGPEER_A_LAST_HANDSHAKE_TIME], sizeof 
(peer->last_handshake_time));
+       if (tbp[WGPEER_A_RX_BYTES])
+               peer->rx_bytes = nla_get_u64 (tbp[WGPEER_A_RX_BYTES]);
+       if (tbp[WGPEER_A_TX_BYTES])
+               peer->tx_bytes = nla_get_u64 (tbp[WGPEER_A_TX_BYTES]);
+
+       peer->allowedips = NULL;
+       peer->allowedips_len = 0;
+
+add_allowedips:
+       if (tbp[WGPEER_A_ALLOWEDIPS]) {
+               struct nlattr *attr;
+               int rem;
+
+               nla_for_each_nested (attr, tbp[WGPEER_A_ALLOWEDIPS], rem) {
+                       ret = _wireguard_update_from_allowedips_nla (buf, attr);
+                       if (ret)
+                               goto errout;
+               }
+       }
+
+       ret = 0;
+errout:
+       return ret;
+}
+
+static int
+_wireguard_get_device_cb (struct nl_msg *msg, void *arg)
+{
+       static const struct nla_policy device_policy[WGDEVICE_A_MAX + 1] = {
+               [WGDEVICE_A_IFINDEX]     = { .type = NLA_U32 },
+               [WGDEVICE_A_IFNAME]      = { .type = NLA_NUL_STRING, .maxlen = IFNAMSIZ },
+               [WGDEVICE_A_PRIVATE_KEY] = { },
+               [WGDEVICE_A_PUBLIC_KEY]  = { },
+               [WGDEVICE_A_FLAGS]       = { .type = NLA_U32 },
+               [WGDEVICE_A_LISTEN_PORT] = { .type = NLA_U16 },
+               [WGDEVICE_A_FWMARK]      = { .type = NLA_U32 },
+               [WGDEVICE_A_PEERS]       = { .type = NLA_NESTED },
+       };
+       struct _wireguard_device_buf *buf = arg;
+       struct nlattr *tbd[WGDEVICE_A_MAX + 1];
+       NMPlatformLnkWireguard *props = &buf->obj->lnk_wireguard;
+       struct nlmsghdr *nlh = nlmsg_hdr (msg);
+       int ret;
+
+       ret = genlmsg_parse (nlh, 0, tbd, WGDEVICE_A_MAX, device_policy);
+       if (ret)
+               goto errout;
+
+       if (tbd[WGDEVICE_A_PRIVATE_KEY])
+               nla_memcpy (props->private_key, tbd[WGDEVICE_A_PRIVATE_KEY], sizeof (props->private_key));
+       if (tbd[WGDEVICE_A_PUBLIC_KEY])
+               nla_memcpy (props->public_key, tbd[WGDEVICE_A_PUBLIC_KEY], sizeof (props->public_key));
+       if (tbd[WGDEVICE_A_LISTEN_PORT])
+               props->listen_port = nla_get_u16 (tbd[WGDEVICE_A_LISTEN_PORT]);
+       if (tbd[WGDEVICE_A_FWMARK])
+               props->fwmark = nla_get_u32 (tbd[WGDEVICE_A_FWMARK]);
+
+       if (tbd[WGDEVICE_A_PEERS]) {
+               struct nlattr *attr;
+               int rem;
+
+               nla_for_each_nested (attr, tbd[WGDEVICE_A_PEERS], rem) {
+                       ret = _wireguard_update_from_peers_nla (buf, attr);
+                       if (ret)
+                               goto errout;
+               }
+       }
+
+       return NL_OK;
+errout:
+       return NL_SKIP;
+}
+
+static gboolean
+_wireguard_get_link_properties (NMPlatform *platform, const NMPlatformLink *link, NMPObject *obj);
+
+/*****************************************************************************/
+
 /* Copied and heavily modified from libnl3's link_msg_parser(). */
 static NMPObject *
 _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr *nlh, gboolean id_only)
@@ -1880,6 +2106,8 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
        case NM_LINK_TYPE_VXLAN:
                lnk_data = _parse_lnk_vxlan (nl_info_kind, nl_info_data);
                break;
+       case NM_LINK_TYPE_WIREGUARD:
+               break;
        default:
                lnk_data_complete_from_cache = FALSE;
                break;
@@ -1922,6 +2150,26 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
                }
        }
 
+       if (obj->link.type == NM_LINK_TYPE_WIREGUARD) {
+               nm_auto_nmpobj NMPObject *lnk_data_now;
+
+               /* The WireGuard kernel module does not yet send link update
+                * notifications, so we don't actually update the cache. For
+                * now, always refetch link data here. */
+               lnk_data_now = nmp_object_new (NMP_OBJECT_TYPE_LNK_WIREGUARD, NULL);
+               if (!_wireguard_get_link_properties (platform, &obj->link, lnk_data_now)) {
+                       _LOGE ("wireguard: %d %s: failed to get properties",
+                              obj->link.ifindex,
+                              obj->link.name ?: "");
+               }
+
+               if (lnk_data && nmp_object_cmp (lnk_data, lnk_data_now))
+                       nmp_object_unref (g_steal_pointer (&lnk_data));
+
+               if (!lnk_data)
+                       lnk_data = (NMPObject *) nmp_object_ref (lnk_data_now);
+       }
+
        obj->_link.netlink.lnk = lnk_data;
 
        obj->_link.netlink.is_in_netlink = TRUE;
@@ -3013,6 +3261,8 @@ typedef struct {
        } delayed_action;
 
        GHashTable *wifi_data;
+
+       int wireguard_family_id;
 } NMLinuxPlatformPrivate;
 
 struct _NMLinuxPlatform {
@@ -5931,6 +6181,69 @@ link_release (NMPlatform *platform, int master, int slave)
 
 /*****************************************************************************/
 
+static gboolean
+_wireguard_get_link_properties (NMPlatform *platform, const NMPlatformLink *link, NMPObject *obj)
+{
+       NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+       nm_auto_nlmsg struct nl_msg *msg = NULL;
+       struct _wireguard_device_buf buf = {
+               .obj = obj,
+               .peers = g_array_new (FALSE, FALSE, sizeof (NMWireguardPeer)),
+               .allowedips = g_array_new (FALSE, FALSE, sizeof (NMWireguardAllowedIP)),
+       };
+       struct nl_cb cb = {
+               .valid_cb = _wireguard_get_device_cb,
+               .valid_arg = &buf,
+       };
+       guint i, j;
+
+       if (!priv->wireguard_family_id)
+               priv->wireguard_family_id = _support_genl_family (priv->genlh, "wireguard");
+
+       if (!priv->wireguard_family_id) {
+               _LOG2W ("kernel support not available for wireguard link %s", link->name);
+               goto err;
+       }
+
+       msg = nlmsg_alloc ();
+       if (!msg)
+               goto err;
+
+       if (!genlmsg_put (msg, NL_AUTO_PORT, NL_AUTO_SEQ, priv->wireguard_family_id,
+                         0, NLM_F_DUMP, WG_CMD_GET_DEVICE, 1))
+               goto err;
+
+       NLA_PUT_U32 (msg, WGDEVICE_A_IFINDEX, link->ifindex);
+
+       if (nl_send_auto (priv->genlh, msg) < 0)
+               goto err;
+
+       if (nl_recvmsgs (priv->genlh, &cb) < 0)
+               goto err;
+
+       /* have each peer point to its own chunk of the allowedips buffer */
+       for (i = 0, j = 0; i < buf.peers->len; i++) {
+               NMWireguardPeer *p = &g_array_index (buf.peers, NMWireguardPeer, i);
+               p->allowedips = &g_array_index (buf.allowedips, NMWireguardAllowedIP, j);
+               j += p->allowedips_len;
+       }
+       /* drop the wrapper (but also the buffer if no peer points to it) */
+       g_array_free (buf.allowedips, buf.peers->len ? FALSE : TRUE);
+
+       obj->_lnk_wireguard.peers_len = buf.peers->len;
+       obj->_lnk_wireguard.peers = (NMWireguardPeer *) g_array_free (buf.peers, FALSE);
+
+       return TRUE;
+
+err:
+nla_put_failure:
+       g_array_free (buf.peers, TRUE);
+       g_array_free (buf.allowedips, TRUE);
+       return FALSE;
+}
+
+/*****************************************************************************/
+
 static gboolean
 _infiniband_partition_action (NMPlatform *platform,
                               InfinibandAction action,
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index f75019e8b..5f27e11e0 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -27,6 +27,8 @@
 #include <unistd.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
 #include <string.h>
 #include <linux/ip.h>
 #include <linux/if_tun.h>
@@ -1912,6 +1914,12 @@ nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformL
        return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VXLAN, out_link);
 }
 
+const NMPlatformLnkWireguard *
+nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+       return _link_get_lnk (self, ifindex, NM_LINK_TYPE_WIREGUARD, out_link);
+}
+
 /*****************************************************************************/
 
 /**
@@ -5298,6 +5306,100 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
        return buf;
 }
 
+const char *
+nm_platform_wireguard_peer_to_string (const NMWireguardPeer *peer, char *buf, gsize len)
+{
+       gs_free char *public_b64 = NULL;
+       char s_address[INET6_ADDRSTRLEN] = {0};
+       char s_endpoint[INET6_ADDRSTRLEN + NI_MAXSERV + sizeof("endpoint []:") + 1] = {0};
+       guint8 nonzero_key = 0;
+       gsize i;
+
+       nm_utils_to_string_buffer_init (&buf, &len);
+
+       if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6) {
+               char s_service[NI_MAXSERV];
+               socklen_t addr_len = 0;
+
+               if (peer->endpoint.addr.sa_family == AF_INET)
+                       addr_len = sizeof (struct sockaddr_in);
+               else if (peer->endpoint.addr.sa_family == AF_INET6)
+                       addr_len = sizeof (struct sockaddr_in6);
+               if (!getnameinfo (&peer->endpoint.addr, addr_len, s_address, sizeof(s_address), s_service, 
sizeof(s_service), NI_DGRAM | NI_NUMERICSERV | NI_NUMERICHOST)) {
+                       if (peer->endpoint.addr.sa_family == AF_INET6 && strchr (s_address, ':'))
+                               g_snprintf(s_endpoint, sizeof (s_endpoint), "endpoint [%s]:%s ", s_address, 
s_service);
+                       else
+                               g_snprintf(s_endpoint, sizeof (s_endpoint), "endpoint %s:%s ", s_address, 
s_service);
+               }
+       }
+
+
+       for (i = 0; i < sizeof (peer->preshared_key); i++)
+               nonzero_key |= peer->preshared_key[i];
+
+       public_b64 = g_base64_encode (peer->public_key, sizeof (peer->public_key));
+
+       nm_utils_strbuf_append (&buf, &len,
+                               "{ "
+                               "public_key %s "
+                               "%s" /* preshared key indicator */
+                               "%s" /* endpoint */
+                               "rx %"G_GUINT64_FORMAT" "
+                               "tx %"G_GUINT64_FORMAT" "
+                               "allowedips (%"G_GSIZE_FORMAT") {",
+                               public_b64,
+                               nonzero_key ? "preshared_key (hidden) " : "",
+                               s_endpoint,
+                               peer->rx_bytes,
+                               peer->tx_bytes,
+                               peer->allowedips_len);
+
+
+       for (i = 0; i < peer->allowedips_len; i++) {
+               NMWireguardAllowedIP *allowedip = &peer->allowedips[i];
+               const char *ret;
+
+               ret = inet_ntop (allowedip->family, &allowedip->ip, s_address, sizeof(s_address));
+
+               nm_utils_strbuf_append (&buf, &len,
+                                       " %s/%u",
+                                       ret ? s_address : "<EAFNOSUPPORT>",
+                                       allowedip->mask);
+       }
+
+       nm_utils_strbuf_append_str (&buf, &len, " } }");
+       return buf;
+}
+
+const char *
+nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireguard *lnk, char *buf, gsize len)
+{
+       gs_free char *public_b64 = NULL;
+       guint8 nonzero_key = 0;
+       gsize i;
+
+       if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+               return buf;
+
+       public_b64 = g_base64_encode (lnk->public_key, sizeof (lnk->public_key));
+
+       for (i = 0; i < sizeof (lnk->private_key); i++)
+               nonzero_key |= lnk->private_key[i];
+
+       g_snprintf (buf, len,
+                   "wireguard "
+                   "public_key %s "
+                   "%s" /* private key indicator */
+                   "listen_port %u "
+                   "fwmark 0x%x",
+                   public_b64,
+                   nonzero_key ? "private_key (hidden) " : "",
+                   lnk->listen_port,
+                   lnk->fwmark);
+
+       return buf;
+}
+
 /**
  * nm_platform_ip4_address_to_string:
  * @route: pointer to NMPlatformIP4Address address structure
@@ -6123,6 +6225,27 @@ nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan
        return 0;
 }
 
+void
+nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireguard *obj, NMHashState *h)
+{
+       nm_hash_update_vals (h,
+                            obj->listen_port,
+                            obj->fwmark);
+       nm_hash_update (h, obj->private_key, sizeof (obj->private_key));
+       nm_hash_update (h, obj->public_key, sizeof (obj->public_key));
+}
+
+int
+nm_platform_lnk_wireguard_cmp (const NMPlatformLnkWireguard *a, const NMPlatformLnkWireguard *b)
+{
+       NM_CMP_SELF (a, b);
+       NM_CMP_FIELD (a, b, listen_port);
+       NM_CMP_FIELD (a, b, fwmark);
+       NM_CMP_FIELD_MEMCMP (a, b, private_key);
+       NM_CMP_FIELD_MEMCMP (a, b, public_key);
+       return 0;
+}
+
 void
 nm_platform_ip4_address_hash_update (const NMPlatformIP4Address *obj, NMHashState *h)
 {
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 866df7362..5b3783812 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -723,6 +723,13 @@ typedef struct {
        bool l3miss:1;
 } NMPlatformLnkVxlan;
 
+typedef struct {
+       guint8 private_key[NM_WG_PUBLIC_KEY_LEN];
+       guint8 public_key[NM_WG_PUBLIC_KEY_LEN];
+       guint16 listen_port;
+       guint32 fwmark;
+} NMPlatformLnkWireguard;
+
 typedef enum {
        NM_PLATFORM_LINK_DUPLEX_UNKNOWN,
        NM_PLATFORM_LINK_DUPLEX_HALF,
@@ -1198,6 +1205,7 @@ const NMPlatformLnkSit *nm_platform_link_get_lnk_sit (NMPlatform *self, int ifin
 const NMPlatformLnkTun *nm_platform_link_get_lnk_tun (NMPlatform *self, int ifindex, const NMPlatformLink 
**out_link);
 const NMPlatformLnkVlan *nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink 
**out_link);
 const NMPlatformLnkVxlan *nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const 
NMPlatformLink **out_link);
+const NMPlatformLnkWireguard *nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const 
NMPlatformLink **out_link);
 
 NMPlatformError nm_platform_link_vlan_add (NMPlatform *self,
                                            const char *name,
@@ -1375,6 +1383,7 @@ const char *nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *bu
 const char *nm_platform_lnk_tun_to_string (const NMPlatformLnkTun *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireguard *lnk, char *buf, gsize len);
 const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len);
 const char *nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *buf, gsize len);
 const char *nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsize len);
@@ -1388,6 +1397,10 @@ const char *nm_platform_vlan_qos_mapping_to_string (const char *name,
                                                     char *buf,
                                                     gsize len);
 
+const char *nm_platform_wireguard_peer_to_string (const NMWireguardPeer *peer,
+                                                  char *buf,
+                                                  gsize len);
+
 int nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b);
 int nm_platform_lnk_gre_cmp (const NMPlatformLnkGre *a, const NMPlatformLnkGre *b);
 int nm_platform_lnk_infiniband_cmp (const NMPlatformLnkInfiniband *a, const NMPlatformLnkInfiniband *b);
@@ -1399,6 +1412,7 @@ int nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *
 int nm_platform_lnk_tun_cmp (const NMPlatformLnkTun *a, const NMPlatformLnkTun *b);
 int nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b);
 int nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b);
+int nm_platform_lnk_wireguard_cmp (const NMPlatformLnkWireguard *a, const NMPlatformLnkWireguard *b);
 int nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b);
 int nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6Address *b);
 
@@ -1435,6 +1449,7 @@ void nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *obj, NMHashState *
 void nm_platform_lnk_tun_hash_update (const NMPlatformLnkTun *obj, NMHashState *h);
 void nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h);
 void nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h);
+void nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireguard *obj, NMHashState *h);
 
 void nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h);
 void nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h);
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 50e6825bf..2c33bbfc5 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -343,6 +343,122 @@ _vlan_xgress_qos_mappings_cpy (guint *dst_n_map,
 
 /*****************************************************************************/
 
+static void
+_wireguard_peers_hash_update (gsize n_peers,
+                              const NMWireguardPeer *peers,
+                              NMHashState *h)
+{
+       gsize i, j;
+
+       nm_hash_update_val (h, n_peers);
+       for (i = 0; i < n_peers; i++) {
+               const NMWireguardPeer *p = &peers[i];
+
+               nm_hash_update (h, p->public_key, sizeof (p->public_key));
+               nm_hash_update (h, p->preshared_key, sizeof (p->preshared_key));
+               nm_hash_update_vals (h,
+                                    p->persistent_keepalive_interval,
+                                    p->allowedips_len,
+                                    p->rx_bytes,
+                                    p->tx_bytes,
+                                    p->last_handshake_time.tv_sec,
+                                    p->last_handshake_time.tv_nsec,
+                                    p->endpoint.addr.sa_family);
+
+               if (p->endpoint.addr.sa_family == AF_INET)
+                       nm_hash_update_val (h, p->endpoint.addr4);
+               else if (p->endpoint.addr.sa_family == AF_INET6)
+                       nm_hash_update_val (h, p->endpoint.addr6);
+               else if (p->endpoint.addr.sa_family != AF_UNSPEC)
+                       g_assert_not_reached ();
+
+               for (j = 0; j < p->allowedips_len; j++) {
+                       const NMWireguardAllowedIP *ip = &p->allowedips[j];
+
+                       nm_hash_update_vals (h, ip->family, ip->mask);
+
+                       if (ip->family == AF_INET)
+                               nm_hash_update_val (h, ip->ip.addr4);
+                       else if (ip->family == AF_INET6)
+                               nm_hash_update_val (h, ip->ip.addr6);
+                       else if (ip->family != AF_UNSPEC)
+                               g_assert_not_reached ();
+               }
+       }
+}
+
+static int
+_wireguard_peers_cmp (gsize n_peers,
+                      const NMWireguardPeer *p1,
+                      const NMWireguardPeer *p2)
+{
+       gsize i, j;
+
+       for (i = 0; i < n_peers; i++) {
+               const NMWireguardPeer *a = &p1[i];
+               const NMWireguardPeer *b = &p2[i];
+
+               NM_CMP_FIELD (a, b, last_handshake_time.tv_sec);
+               NM_CMP_FIELD (a, b, last_handshake_time.tv_nsec);
+               NM_CMP_FIELD (a, b, rx_bytes);
+               NM_CMP_FIELD (a, b, tx_bytes);
+               NM_CMP_FIELD (a, b, allowedips_len);
+               NM_CMP_FIELD (a, b, persistent_keepalive_interval);
+               NM_CMP_FIELD (a, b, endpoint.addr.sa_family);
+               NM_CMP_FIELD_MEMCMP (a, b, public_key);
+               NM_CMP_FIELD_MEMCMP (a, b, preshared_key);
+
+               if (a->endpoint.addr.sa_family == AF_INET)
+                       NM_CMP_FIELD_MEMCMP (a, b, endpoint.addr4);
+               else if (a->endpoint.addr.sa_family == AF_INET6)
+                       NM_CMP_FIELD_MEMCMP (a, b, endpoint.addr6);
+               else if (a->endpoint.addr.sa_family != AF_UNSPEC)
+                       g_assert_not_reached ();
+
+               for (j = 0; j < a->allowedips_len; j++) {
+                       const NMWireguardAllowedIP *aip = &a->allowedips[j];
+                       const NMWireguardAllowedIP *bip = &b->allowedips[j];
+
+                       NM_CMP_FIELD (aip, bip, family);
+                       NM_CMP_FIELD (aip, bip, mask);
+
+                       if (aip->family == AF_INET)
+                               NM_CMP_FIELD_MEMCMP (&aip->ip, &bip->ip, addr4);
+                       else if (aip->family == AF_INET6)
+                               NM_CMP_FIELD_MEMCMP (&aip->ip, &bip->ip, addr6);
+                       else if (aip->family != AF_UNSPEC)
+                               g_assert_not_reached ();
+               }
+       }
+
+       return 0;
+}
+
+static void
+_wireguard_peers_cpy (gsize *dst_n_peers,
+                      NMWireguardPeer **dst_peers,
+                      gsize src_n_peers,
+                      const NMWireguardPeer *src_peers)
+{
+       if (src_n_peers == 0) {
+               g_clear_pointer (dst_peers, g_free);
+               *dst_n_peers = 0;
+       } else if (   src_n_peers != *dst_n_peers
+                  || _wireguard_peers_cmp (src_n_peers, *dst_peers, src_peers) != 0) {
+               gsize i;
+               g_clear_pointer (dst_peers, g_free);
+               *dst_n_peers = src_n_peers;
+               if (src_n_peers > 0)
+                       *dst_peers = g_memdup (src_peers, sizeof (*src_peers) * src_n_peers);
+               for (i = 0; i < src_n_peers; i++) {
+                       dst_peers[i]->allowedips = g_memdup (src_peers[i].allowedips, sizeof 
(src_peers[i].allowedips) * src_peers[i].allowedips_len);
+                       dst_peers[i]->allowedips_len = src_peers[i].allowedips_len;
+               }
+       }
+}
+
+/*****************************************************************************/
+
 static const char *
 _link_get_driver (struct udev_device *udevice, const char *kind, int ifindex)
 {
@@ -466,6 +582,15 @@ _vt_cmd_obj_dispose_lnk_vlan (NMPObject *obj)
        g_free ((gpointer) obj->_lnk_vlan.egress_qos_map);
 }
 
+static void
+_vt_cmd_obj_dispose_lnk_wireguard (NMPObject *obj)
+{
+       if (obj->_lnk_wireguard.peers_len)
+               g_free (obj->_lnk_wireguard.peers[0].allowedips);
+
+       g_free (obj->_lnk_wireguard.peers);
+}
+
 static NMPObject *
 _nmp_object_new_from_class (const NMPClass *klass)
 {
@@ -720,6 +845,52 @@ _vt_cmd_obj_to_string_lnk_vlan (const NMPObject *obj, NMPObjectToStringMode to_s
        }
 }
 
+static const char *
+_vt_cmd_obj_to_string_lnk_wireguard (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, 
gsize buf_size)
+{
+       const NMPClass *klass;
+       char buf2[sizeof (_nm_utils_to_string_buffer)];
+       char *b;
+       gsize i, l;
+
+       klass = NMP_OBJECT_GET_CLASS (obj);
+
+       switch (to_string_mode) {
+       case NMP_OBJECT_TO_STRING_ID:
+               g_snprintf (buf, buf_size, "%p", obj);
+               return buf;
+       case NMP_OBJECT_TO_STRING_ALL:
+               b = buf;
+
+               nm_utils_strbuf_append (&b, &buf_size,
+                                       "[%s,%p,%u,%calive,%cvisible; %s "
+                                       "peers (%" G_GSIZE_FORMAT ") {",
+                                       klass->obj_type_name, obj, obj->parent._ref_count,
+                                       nmp_object_is_alive (obj) ? '+' : '-',
+                                       nmp_object_is_visible (obj) ? '+' : '-',
+                                       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof 
(buf2)),
+                                       obj->_lnk_wireguard.peers_len);
+
+               for (i = 0; i < obj->_lnk_wireguard.peers_len; i++) {
+                       const NMWireguardPeer *peer = &obj->_lnk_wireguard.peers[i];
+                       nm_platform_wireguard_peer_to_string (peer, b, buf_size);
+                       l = strlen (b);
+                       b += l;
+                       buf_size -= l;
+               }
+
+               nm_utils_strbuf_append_str (&b, &buf_size, " }");
+
+               return buf;
+       case NMP_OBJECT_TO_STRING_PUBLIC:
+               NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+
+               return buf;
+       default:
+               g_return_val_if_reached ("ERROR");
+       }
+}
+
 #define _vt_cmd_plobj_to_string_id(type, plat_type, ...) \
 static const char * \
 _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsize buf_len) \
@@ -787,6 +958,15 @@ _vt_cmd_obj_hash_update_lnk_vlan (const NMPObject *obj, NMHashState *h)
                                               h);
 }
 
+static void
+_vt_cmd_obj_hash_update_lnk_wireguard (const NMPObject *obj, NMHashState *h)
+{
+       nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LNK_WIREGUARD);
+
+       nm_platform_lnk_wireguard_hash_update (&obj->lnk_wireguard, h);
+       _wireguard_peers_hash_update (obj->_lnk_wireguard.peers_len, obj->_lnk_wireguard.peers, h);
+}
+
 int
 nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
 {
@@ -865,6 +1045,21 @@ _vt_cmd_obj_cmp_lnk_vlan (const NMPObject *obj1, const NMPObject *obj2)
        return c;
 }
 
+static int
+_vt_cmd_obj_cmp_lnk_wireguard (const NMPObject *obj1, const NMPObject *obj2)
+{
+       int c;
+
+       c = nm_platform_lnk_wireguard_cmp (&obj1->lnk_wireguard, &obj2->lnk_wireguard);
+       if (c)
+               return c;
+
+       if (obj1->_lnk_wireguard.peers_len != obj2->_lnk_wireguard.peers_len)
+               return obj1->_lnk_wireguard.peers_len < obj2->_lnk_wireguard.peers_len ? -1 : 1;
+
+       return _wireguard_peers_cmp(obj1->_lnk_wireguard.peers_len, obj1->_lnk_wireguard.peers, 
obj2->_lnk_wireguard.peers);
+}
+
 /* @src is a const object, which is not entirely correct for link types, where
  * we increase the ref count for src->_link.udev.device.
  * Hence, nmp_object_copy() can violate the const promise of @src.
@@ -925,6 +1120,14 @@ _vt_cmd_obj_copy_lnk_vlan (NMPObject *dst, const NMPObject *src)
                                       src->_lnk_vlan.egress_qos_map);
 }
 
+static void
+_vt_cmd_obj_copy_lnk_wireguard (NMPObject *dst, const NMPObject *src)
+{
+       dst->lnk_wireguard = src->lnk_wireguard;
+       _wireguard_peers_cpy (&dst->_lnk_wireguard.peers_len, &dst->_lnk_wireguard.peers,
+                             src->_lnk_wireguard.peers_len, src->_lnk_wireguard.peers);
+}
+
 #define _vt_cmd_plobj_id_copy(type, plat_type, cmd) \
 static void \
 _vt_cmd_plobj_id_copy_##type (NMPlatformObject *_dst, const NMPlatformObject *_src) \
@@ -2817,5 +3020,21 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
                .cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState 
*h)) nm_platform_lnk_vxlan_hash_update,
                .cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const 
NMPlatformObject *obj2)) nm_platform_lnk_vxlan_cmp,
        },
+       [NMP_OBJECT_TYPE_LNK_WIREGUARD - 1] = {
+               .parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+               .obj_type                           = NMP_OBJECT_TYPE_LNK_WIREGUARD,
+               .sizeof_data                        = sizeof (NMPObjectLnkWireguard),
+               .sizeof_public                      = sizeof (NMPlatformLnkWireguard),
+               .obj_type_name                      = "wireguard",
+               .lnk_link_type                      = NM_LINK_TYPE_WIREGUARD,
+               .cmd_obj_hash_update                = _vt_cmd_obj_hash_update_lnk_wireguard,
+               .cmd_obj_cmp                        = _vt_cmd_obj_cmp_lnk_wireguard,
+               .cmd_obj_copy                       = _vt_cmd_obj_copy_lnk_wireguard,
+               .cmd_obj_dispose                    = _vt_cmd_obj_dispose_lnk_wireguard,
+               .cmd_obj_to_string                  = _vt_cmd_obj_to_string_lnk_wireguard,
+               .cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char 
*buf, gsize len)) nm_platform_lnk_wireguard_to_string,
+               .cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState 
*h)) nm_platform_lnk_vlan_hash_update,
+               .cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const 
NMPlatformObject *obj2)) nm_platform_lnk_vlan_cmp,
+       },
 };
 
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index f473f4628..31adb4ae6 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -213,6 +213,13 @@ typedef struct {
        NMPlatformLnkVxlan _public;
 } NMPObjectLnkVxlan;
 
+typedef struct {
+       NMPlatformLnkWireguard _public;
+
+       gsize peers_len;
+       NMWireguardPeer *peers;
+} NMPObjectLnkWireguard;
+
 typedef struct {
        NMPlatformIP4Address _public;
 } NMPObjectIP4Address;
@@ -278,6 +285,9 @@ struct _NMPObject {
                NMPlatformLnkVxlan      lnk_vxlan;
                NMPObjectLnkVxlan       _lnk_vxlan;
 
+               NMPlatformLnkWireguard  lnk_wireguard;
+               NMPObjectLnkWireguard   _lnk_wireguard;
+
                NMPlatformIPAddress     ip_address;
                NMPlatformIPXAddress    ipx_address;
                NMPlatformIP4Address    ip4_address;
-- 
2.17.1



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]