diff --git a/libnm-core/nm-core-types-internal.h b/libnm-core/nm-core-types-internal.h index 4d43aaf45d..d7dce9521e 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 efdce5911f..a0a7f6202e 100644 --- a/src/nm-types.h +++ b/src/nm-types.h @@ -174,6 +174,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, @@ -209,6 +210,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 9acd7ac52a..7d6eff6075 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -181,6 +181,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 @@ -482,6 +516,18 @@ _support_rta_pref_get (void) return _support_rta_pref >= 0; } +/***************************************************************************** + * Support Generic Netlink family + *****************************************************************************/ + +static int +_support_genl_family (struct nl_sock *genl, const char *name) +{ + int family_id = genl_ctrl_resolve (genl, name); + _LOG2D ("kernel-support: genetlink: %s: %s", name, family_id ? "detected" : "not detected"); + return family_id; +} + /****************************************************************** * Various utilities ******************************************************************/ @@ -563,6 +609,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" }, @@ -1760,6 +1807,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) @@ -1977,6 +2215,8 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr need_ext_data = TRUE; lnk_data_complete_from_cache = FALSE; break; + case NM_LINK_TYPE_WIREGUARD: + break; default: lnk_data_complete_from_cache = FALSE; break; @@ -2027,6 +2267,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; if (need_ext_data && obj->_link.ext_data == NULL) { @@ -3143,6 +3403,8 @@ typedef struct { gint is_handling; } delayed_action; + + int wireguard_family_id; } NMLinuxPlatformPrivate; struct _NMLinuxPlatform { @@ -6088,6 +6350,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->genl, "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->genl, msg) < 0) + goto err; + + if (nl_recvmsgs (priv->genl, &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 99dd8490dd..94a5ab0101 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -1932,6 +1934,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); +} + /*****************************************************************************/ /** @@ -5463,6 +5471,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 : "", + 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 @@ -6302,6 +6404,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 567c93ef35..00df7fa31d 100644 --- a/src/platform/nm-platform.h +++ b/src/platform/nm-platform.h @@ -732,6 +732,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, @@ -1225,6 +1232,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, @@ -1419,6 +1427,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); @@ -1432,6 +1441,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); @@ -1443,6 +1456,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); @@ -1479,6 +1493,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 666a1d1dd2..fa84b4edce 100644 --- a/src/platform/nmp-object.c +++ b/src/platform/nmp-object.c @@ -346,6 +346,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) { @@ -470,6 +586,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) { @@ -724,6 +849,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) \ @@ -791,6 +962,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) { @@ -869,6 +1049,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. @@ -935,6 +1130,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) \ @@ -2860,5 +3063,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 fab98b8f62..c4e3da3fdc 100644 --- a/src/platform/nmp-object.h +++ b/src/platform/nmp-object.h @@ -216,6 +216,13 @@ typedef struct { NMPlatformLnkVxlan _public; } NMPObjectLnkVxlan; +typedef struct { + NMPlatformLnkWireguard _public; + + gsize peers_len; + NMWireguardPeer *peers; +} NMPObjectLnkWireguard; + typedef struct { NMPlatformIP4Address _public; } NMPObjectIP4Address; @@ -281,6 +288,9 @@ struct _NMPObject { NMPlatformLnkVxlan lnk_vxlan; NMPObjectLnkVxlan _lnk_vxlan; + NMPlatformLnkWireguard lnk_wireguard; + NMPObjectLnkWireguard _lnk_wireguard; + NMPlatformIPAddress ip_address; NMPlatformIPXAddress ipx_address; NMPlatformIP4Address ip4_address;