summary refs log tree commit diff
path: root/src/libnm-core-impl/nm-setting-wireguard.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-core-impl/nm-setting-wireguard.c')
-rw-r--r--src/libnm-core-impl/nm-setting-wireguard.c2608
1 files changed, 2608 insertions, 0 deletions
diff --git a/src/libnm-core-impl/nm-setting-wireguard.c b/src/libnm-core-impl/nm-setting-wireguard.c
new file mode 100644
index 00000000..3290ff56
--- /dev/null
+++ b/src/libnm-core-impl/nm-setting-wireguard.c
@@ -0,0 +1,2608 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018 - 2019 Red Hat, Inc.
+ */
+
+#include "libnm-core-impl/nm-default-libnm-core.h"
+
+#include "nm-setting-wireguard.h"
+
+#include "nm-setting-private.h"
+#include "nm-utils-private.h"
+#include "nm-connection-private.h"
+#include "libnm-glib-aux/nm-secret-utils.h"
+
+/*****************************************************************************/
+
+/**
+ * SECTION:nm-setting-wireguard
+ * @short_description: Describes connection properties for wireguard related options
+ *
+ * The #NMSettingWireGuard object is a #NMSetting subclass that contains settings
+ * for configuring WireGuard.
+ **/
+
+/*****************************************************************************/
+
+static NMWireGuardPeer *_wireguard_peer_dup(const NMWireGuardPeer *self);
+
+G_DEFINE_BOXED_TYPE(NMWireGuardPeer,
+                    nm_wireguard_peer,
+                    _wireguard_peer_dup,
+                    nm_wireguard_peer_unref)
+
+/* NMWireGuardPeer can also track invalid allowed-ip settings, and only reject
+ * them later during is_valid(). Such values are marked by a leading 'X' character
+ * in the @allowed_ips. It is expected, that such values are the exception, and
+ * commonly not present. */
+#define ALLOWED_IP_INVALID_X     'X'
+#define ALLOWED_IP_INVALID_X_STR "X"
+
+/**
+ * NMWireGuardPeer:
+ *
+ * The settings of one WireGuard peer.
+ *
+ * Since: 1.16
+ */
+struct _NMWireGuardPeer {
+    NMSockAddrEndpoint * endpoint;
+    char *               public_key;
+    char *               preshared_key;
+    GPtrArray *          allowed_ips;
+    guint                refcount;
+    NMSettingSecretFlags preshared_key_flags;
+    guint16              persistent_keepalive;
+    bool                 public_key_valid : 1;
+    bool                 preshared_key_valid : 1;
+    bool                 sealed : 1;
+};
+
+static gboolean
+NM_IS_WIREGUARD_PEER(const NMWireGuardPeer *self, gboolean also_sealed)
+{
+    return self && self->refcount > 0 && (also_sealed || !self->sealed);
+}
+
+/**
+ * nm_wireguard_peer_new:
+ *
+ * Returns: (transfer full): a new, default, unsealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_wireguard_peer_new(void)
+{
+    NMWireGuardPeer *self;
+
+    self  = g_slice_new(NMWireGuardPeer);
+    *self = (NMWireGuardPeer){
+        .refcount            = 1,
+        .preshared_key_flags = NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
+    };
+    return self;
+}
+
+/**
+ * nm_wireguard_peer_new_clone:
+ * @self: the #NMWireGuardPeer instance to copy.
+ * @with_secrets: if %TRUE, the preshared-key secrets are copied
+ *  as well. Otherwise, they will be removed.
+ *
+ * Returns: (transfer full): a clone of @self. This instance
+ *   is always unsealed.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_wireguard_peer_new_clone(const NMWireGuardPeer *self, gboolean with_secrets)
+{
+    NMWireGuardPeer *new;
+    guint i;
+
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL);
+
+    new  = g_slice_new(NMWireGuardPeer);
+    *new = (NMWireGuardPeer){
+        .refcount             = 1,
+        .public_key           = g_strdup(self->public_key),
+        .public_key_valid     = self->public_key_valid,
+        .preshared_key        = with_secrets ? g_strdup(self->preshared_key) : NULL,
+        .preshared_key_valid  = self->preshared_key_valid,
+        .preshared_key_flags  = self->preshared_key_flags,
+        .endpoint             = nm_sock_addr_endpoint_ref(self->endpoint),
+        .persistent_keepalive = self->persistent_keepalive,
+    };
+    if (self->allowed_ips && self->allowed_ips->len > 0) {
+        new->allowed_ips = g_ptr_array_new_full(self->allowed_ips->len, g_free);
+        for (i = 0; i < self->allowed_ips->len; i++) {
+            g_ptr_array_add(new->allowed_ips, g_strdup(self->allowed_ips->pdata[i]));
+        }
+    }
+    return new;
+}
+
+/**
+ * nm_wireguard_peer_ref:
+ * @self: (allow-none): the #NMWireGuardPeer instance
+ *
+ * This is not thread-safe.
+ *
+ * Returns: returns the input argument @self after incrementing
+ *   the reference count.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_wireguard_peer_ref(NMWireGuardPeer *self)
+{
+    if (!self)
+        return NULL;
+
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL);
+
+    nm_assert(self->refcount < G_MAXUINT);
+
+    self->refcount++;
+    return self;
+}
+
+/**
+ * nm_wireguard_peer_unref:
+ * @self: (allow-none): the #NMWireGuardPeer instance
+ *
+ * Drop a reference to @self. If the last reference is dropped,
+ * the instance is freed and all associate data released.
+ *
+ * This is not thread-safe.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_unref(NMWireGuardPeer *self)
+{
+    if (!self)
+        return;
+
+    g_return_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE));
+
+    if (--self->refcount > 0)
+        return;
+
+    nm_sock_addr_endpoint_unref(self->endpoint);
+    if (self->allowed_ips)
+        g_ptr_array_unref(self->allowed_ips);
+    g_free(self->public_key);
+    nm_free_secret(self->preshared_key);
+    g_slice_free(NMWireGuardPeer, self);
+}
+
+/**
+ * _wireguard_peer_dup:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Duplicates the #NMWireGuardPeer instance. Note that if @self
+ * is already sealed, this increments the reference count and
+ * returns it. If the instance is still unsealed, it is copied.
+ *
+ * Returns: (transfer full): a duplicate of @self, or (if the
+ *   instance is sealed and thus immutable) a reference to @self.
+ *   As such, the instance will be sealed if and only if @self is
+ *   sealed.
+ */
+static NMWireGuardPeer *
+_wireguard_peer_dup(const NMWireGuardPeer *self)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL);
+
+    if (self->sealed)
+        return nm_wireguard_peer_ref((NMWireGuardPeer *) self);
+    return nm_wireguard_peer_new_clone(self, TRUE);
+}
+
+/**
+ * nm_wireguard_peer_seal:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Seal the #NMWireGuardPeer instance. Afterwards, it is a bug
+ * to call all functions that modify the instance (except ref/unref).
+ * A sealed instance cannot be unsealed again, but you can create
+ * an unsealed copy with nm_wireguard_peer_new_clone().
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_seal(NMWireGuardPeer *self)
+{
+    g_return_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE));
+
+    self->sealed = TRUE;
+
+    if (self->allowed_ips) {
+        if (self->allowed_ips->len == 0)
+            nm_clear_pointer(&self->allowed_ips, g_ptr_array_unref);
+    }
+}
+
+/**
+ * nm_wireguard_peer_is_sealed:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: whether @self is sealed or not.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_is_sealed(const NMWireGuardPeer *self)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), FALSE);
+
+    return self->sealed;
+}
+
+/**
+ * nm_wireguard_peer_get_public_key:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: (transfer none): the public key or %NULL if unset.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_wireguard_peer_get_public_key(const NMWireGuardPeer *self)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL);
+
+    return self->public_key;
+}
+
+/**
+ * nm_wireguard_peer_set_public_key:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @public_key: (allow-none) (transfer none): the new public
+ *   key or %NULL to clear the public key.
+ * @accept_invalid: if %TRUE and @public_key is not %NULL and
+ *   invalid, then do not modify the instance.
+ *
+ * Reset the public key. Note that if the public key is valid, it
+ * will be normalized (which may or may not modify the set value).
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Returns: %TRUE if the key was valid or %NULL. Returns
+ *   %FALSE for invalid keys. Depending on @accept_invalid
+ *   will an invalid key be set or not.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_set_public_key(NMWireGuardPeer *self,
+                                 const char *     public_key,
+                                 gboolean         accept_invalid)
+{
+    char *   public_key_normalized = NULL;
+    gboolean is_valid;
+
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE);
+
+    if (!public_key) {
+        nm_clear_g_free(&self->public_key);
+        return TRUE;
+    }
+
+    is_valid = nm_utils_base64secret_normalize(public_key,
+                                               NM_WIREGUARD_PUBLIC_KEY_LEN,
+                                               &public_key_normalized);
+    nm_assert(is_valid == (public_key_normalized != NULL));
+
+    if (!is_valid && !accept_invalid)
+        return FALSE;
+
+    self->public_key_valid = is_valid;
+    g_free(self->public_key);
+    self->public_key = public_key_normalized ?: g_strdup(public_key);
+    return is_valid;
+}
+
+void
+_nm_wireguard_peer_set_public_key_bin(NMWireGuardPeer *self,
+                                      const guint8 public_key[static NM_WIREGUARD_PUBLIC_KEY_LEN])
+{
+    g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE));
+
+    nm_clear_g_free(&self->public_key);
+
+    if (!public_key)
+        return;
+
+    self->public_key       = g_base64_encode(public_key, NM_WIREGUARD_PUBLIC_KEY_LEN);
+    self->public_key_valid = TRUE;
+}
+
+/**
+ * nm_wireguard_peer_get_preshared_key:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: (transfer none): the preshared key or %NULL if unset.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_wireguard_peer_get_preshared_key(const NMWireGuardPeer *self)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL);
+
+    return self->preshared_key;
+}
+
+/**
+ * nm_wireguard_peer_set_preshared_key:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @preshared_key: (allow-none) (transfer none): the new preshared
+ *   key or %NULL to clear the preshared key.
+ * @accept_invalid: whether to allow setting the key to an invalid
+ *   value. If %FALSE, @self is unchanged if the key is invalid
+ *   and if %FALSE is returned.
+ *
+ * Reset the preshared key. Note that if the preshared key is valid, it
+ * will be normalized (which may or may not modify the set value).
+ *
+ * Note that the preshared-key is a secret and consequently has corresponding
+ * preshared-key-flags property. This is so that secrets can be optional
+ * and requested on demand from a secret-agent. Also, an invalid  preshared-key
+ * may optionally cause nm_wireguard_peer_is_valid() to fail or it may
+ * be accepted.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Returns: %TRUE if the preshared-key is valid, otherwise %FALSE.
+ *   %NULL is considered a valid value.
+ *   If the key is invalid, it depends on @accept_invalid whether the
+ *   previous value was reset.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_set_preshared_key(NMWireGuardPeer *self,
+                                    const char *     preshared_key,
+                                    gboolean         accept_invalid)
+{
+    char *   preshared_key_normalized = NULL;
+    gboolean is_valid;
+
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE);
+
+    if (!preshared_key) {
+        nm_clear_pointer(&self->preshared_key, nm_free_secret);
+        return TRUE;
+    }
+
+    is_valid = nm_utils_base64secret_normalize(preshared_key,
+                                               NM_WIREGUARD_SYMMETRIC_KEY_LEN,
+                                               &preshared_key_normalized);
+    nm_assert(is_valid == (preshared_key_normalized != NULL));
+
+    if (!is_valid && !accept_invalid)
+        return FALSE;
+
+    self->preshared_key_valid = is_valid;
+    nm_free_secret(self->preshared_key);
+    self->preshared_key = preshared_key_normalized ?: g_strdup(preshared_key);
+    return is_valid;
+}
+
+/**
+ * nm_wireguard_peer_get_preshared_key_flags:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: get the secret flags for the preshared-key.
+ *
+ * Since: 1.16
+ */
+NMSettingSecretFlags
+nm_wireguard_peer_get_preshared_key_flags(const NMWireGuardPeer *self)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), 0);
+
+    return self->preshared_key_flags;
+}
+
+/**
+ * nm_wireguard_peer_set_preshared_key_flags:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @preshared_key_flags: the secret flags to set.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_set_preshared_key_flags(NMWireGuardPeer *    self,
+                                          NMSettingSecretFlags preshared_key_flags)
+{
+    g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE));
+
+    self->preshared_key_flags = preshared_key_flags;
+}
+
+/**
+ * nm_wireguard_peer_get_persistent_keepalive:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: get the persistent-keepalive setting in seconds. Set to zero to disable
+ *   keep-alive.
+ *
+ * Since: 1.16
+ */
+guint16
+nm_wireguard_peer_get_persistent_keepalive(const NMWireGuardPeer *self)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), 0);
+
+    return self->persistent_keepalive;
+}
+
+/**
+ * nm_wireguard_peer_set_persistent_keepalive:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @persistent_keepalive: the keep-alive value to set.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_set_persistent_keepalive(NMWireGuardPeer *self, guint16 persistent_keepalive)
+{
+    g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE));
+
+    self->persistent_keepalive = persistent_keepalive;
+}
+
+NMSockAddrEndpoint *
+_nm_wireguard_peer_get_endpoint(const NMWireGuardPeer *self)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL);
+
+    return self->endpoint;
+}
+
+/**
+ * nm_wireguard_peer_get_endpoint:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: (transfer none): the endpoint or %NULL if none was set.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_wireguard_peer_get_endpoint(const NMWireGuardPeer *self)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL);
+
+    return self->endpoint ? nm_sock_addr_endpoint_get_endpoint(self->endpoint) : NULL;
+}
+
+void
+_nm_wireguard_peer_set_endpoint(NMWireGuardPeer *self, NMSockAddrEndpoint *endpoint)
+{
+    NMSockAddrEndpoint *old;
+
+    nm_assert(NM_IS_WIREGUARD_PEER(self, FALSE));
+
+    old            = self->endpoint;
+    self->endpoint = nm_sock_addr_endpoint_ref(endpoint);
+    nm_sock_addr_endpoint_unref(old);
+}
+
+/**
+ * nm_wireguard_peer_set_endpoint:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @endpoint: the socket address endpoint to set or %NULL.
+ * @allow_invalid: if %TRUE, also invalid values are set.
+ *   If %FALSE, the function does nothing for invalid @endpoint
+ *   arguments.
+ *
+ * Sets or clears the endpoint of @self.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Returns: %TRUE if the endpoint is %NULL or valid. For an
+ *   invalid @endpoint argument, %FALSE is returned. Depending
+ *   on @allow_invalid, the instance will be modified.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_set_endpoint(NMWireGuardPeer *self, const char *endpoint, gboolean allow_invalid)
+{
+    NMSockAddrEndpoint *old;
+    NMSockAddrEndpoint *new;
+    gboolean is_valid;
+
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE);
+
+    if (!endpoint) {
+        nm_clear_pointer(&self->endpoint, nm_sock_addr_endpoint_unref);
+        return TRUE;
+    }
+
+    new = nm_sock_addr_endpoint_new(endpoint);
+
+    is_valid = (nm_sock_addr_endpoint_get_host(new) != NULL);
+
+    if (!allow_invalid && !is_valid) {
+        nm_sock_addr_endpoint_unref(new);
+        return FALSE;
+    }
+
+    old            = self->endpoint;
+    self->endpoint = new;
+    nm_sock_addr_endpoint_unref(old);
+    return is_valid;
+}
+
+/**
+ * nm_wireguard_peer_get_allowed_ips_len:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: the number of allowed-ips entries.
+ *
+ * Since: 1.16
+ */
+guint
+nm_wireguard_peer_get_allowed_ips_len(const NMWireGuardPeer *self)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), 0);
+
+    return self->allowed_ips ? self->allowed_ips->len : 0u;
+}
+
+/**
+ * nm_wireguard_peer_get_allowed_ip:
+ * @self: the #NMWireGuardPeer instance
+ * @idx: the index from zero to (allowed-ips-len - 1) to
+ *   retrieve.
+ * @out_is_valid: (allow-none): %TRUE if the returned value is a valid allowed-ip
+ *   setting.
+ *   This parameter is wrongly not marked as (out) argument, it is
+ *   thus not accessible via introspection. This cannot be fixed without
+ *   breaking API for introspection users.
+ *
+ * Returns: (transfer none): the allowed-ip setting at index @idx.
+ *   If @idx is out of range, %NULL will be returned.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_wireguard_peer_get_allowed_ip(const NMWireGuardPeer *self, guint idx, gboolean *out_is_valid)
+{
+    const char *s;
+
+    /* With LTO, the compiler might warn about the g_return_val_if_fail()
+     * code path not initializing the output argument. Workaround that by
+     * always setting the out argument. */
+    NM_SET_OUT(out_is_valid, FALSE);
+
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL);
+
+    if (!self->allowed_ips || idx >= self->allowed_ips->len)
+        return NULL;
+
+    s = self->allowed_ips->pdata[idx];
+    NM_SET_OUT(out_is_valid, s[0] != ALLOWED_IP_INVALID_X);
+    return s[0] == ALLOWED_IP_INVALID_X ? &s[1] : s;
+}
+
+/**
+ * nm_wireguard_peer_clear_allowed_ips:
+ * @self: the unsealed #NMWireGuardPeer instance
+ *
+ * Removes all allowed-ip entries.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_clear_allowed_ips(NMWireGuardPeer *self)
+{
+    g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE));
+
+    if (self->allowed_ips)
+        g_ptr_array_set_size(self->allowed_ips, 0);
+}
+
+static gboolean
+_peer_append_allowed_ip(NMWireGuardPeer *self, const char *allowed_ip, gboolean accept_invalid)
+{
+    int      addr_family;
+    int      prefix;
+    NMIPAddr addrbin;
+    char *   str;
+    gboolean is_valid = TRUE;
+
+    nm_assert(NM_IS_WIREGUARD_PEER(self, FALSE));
+    nm_assert(allowed_ip);
+
+    /* normalize the address (if it is valid. Otherwise, take it
+     * as-is (it will render the instance invalid). */
+    if (!nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC, allowed_ip, &addr_family, &addrbin, &prefix)) {
+        if (!accept_invalid)
+            return FALSE;
+        /* mark the entry as invalid by having a "X" prefix. */
+        str      = g_strconcat(ALLOWED_IP_INVALID_X_STR, allowed_ip, NULL);
+        is_valid = FALSE;
+    } else {
+        char addrstr[NM_UTILS_INET_ADDRSTRLEN];
+
+        nm_assert_addr_family(addr_family);
+
+        nm_utils_inet_ntop(addr_family, &addrbin, addrstr);
+        if (prefix >= 0)
+            str = g_strdup_printf("%s/%d", addrstr, prefix);
+        else
+            str = g_strdup(addrstr);
+        nm_assert(str[0] != ALLOWED_IP_INVALID_X);
+    }
+
+    if (!self->allowed_ips)
+        self->allowed_ips = g_ptr_array_new_with_free_func(g_free);
+
+    g_ptr_array_add(self->allowed_ips, str);
+    return is_valid;
+}
+
+/**
+ * nm_wireguard_peer_append_allowed_ip:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @allowed_ip: the allowed-ip entry to set.
+ * @accept_invalid: if %TRUE, also invalid @allowed_ip value
+ *   will be appended. Otherwise, the function does nothing
+ *   in face of invalid values and returns %FALSE.
+ *
+ * Appends @allowed_ip setting to the list. This does not check
+ * for duplicates and always appends @allowed_ip to the end of the
+ * list. If @allowed_ip is valid, it will be normalized and a modified
+ * for might be appended. If @allowed_ip is invalid, it will still be
+ * appended, but later verification will fail.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Returns: %TRUE if the value is a valid allowed-ips value, %FALSE otherwise.
+ *   Depending on @accept_invalid, also invalid values are added.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_append_allowed_ip(NMWireGuardPeer *self,
+                                    const char *     allowed_ip,
+                                    gboolean         accept_invalid)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE);
+    g_return_val_if_fail(allowed_ip, FALSE);
+
+    return _peer_append_allowed_ip(self, allowed_ip, accept_invalid);
+}
+
+/**
+ * nm_wireguard_peer_remove_allowed_ip:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @idx: the index from zero to (allowed-ips-len - 1) to
+ *   retrieve. If the index is out of range, %FALSE is returned
+ *   and nothing is done.
+ *
+ * Removes the allowed-ip at the given @idx. This shifts all
+ * following entries one index down.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Returns: %TRUE if @idx was valid and the allowed-ip was removed.
+ *   %FALSE otherwise, and the peer will not be changed.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_remove_allowed_ip(NMWireGuardPeer *self, guint idx)
+{
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE);
+
+    if (!self->allowed_ips || idx >= self->allowed_ips->len)
+        return FALSE;
+
+    g_ptr_array_remove_index(self->allowed_ips, idx);
+    return TRUE;
+}
+
+/**
+ * nm_wireguard_peer_is_valid:
+ * @self: the #NMWireGuardPeer instance
+ * @check_secrets: if %TRUE, non-secret properties are validated.
+ *   Otherwise, they are ignored for this purpose.
+ * @check_non_secrets: if %TRUE, secret properties are validated.
+ *   Otherwise, they are ignored for this purpose.
+ * @error: the #GError location for returning the failure reason.
+ *
+ * Returns:  %TRUE if the peer is valid or fails with an error
+ *   reason.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_is_valid(const NMWireGuardPeer *self,
+                           gboolean               check_non_secrets,
+                           gboolean               check_secrets,
+                           GError **              error)
+{
+    guint i;
+
+    g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    if (check_non_secrets) {
+        if (!self->public_key) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                                _("missing public-key for peer"));
+            return FALSE;
+        } else if (!self->public_key_valid) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("invalid public-key for peer"));
+            return FALSE;
+        }
+    }
+
+    if (check_secrets) {
+        if (self->preshared_key && !self->preshared_key_valid) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("invalid preshared-key for peer"));
+            return FALSE;
+        }
+    }
+
+    if (check_non_secrets) {
+        if (!_nm_utils_secret_flags_validate(self->preshared_key_flags,
+                                             NULL,
+                                             NULL,
+                                             NM_SETTING_SECRET_FLAG_NONE,
+                                             error))
+            return FALSE;
+    }
+
+    if (check_non_secrets) {
+        if (self->endpoint && !nm_sock_addr_endpoint_get_host(self->endpoint)) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("invalid endpoint for peer"));
+            return FALSE;
+        }
+
+        if (self->allowed_ips) {
+            for (i = 0; i < self->allowed_ips->len; i++) {
+                const char *s = self->allowed_ips->pdata[i];
+
+                if (s[0] == ALLOWED_IP_INVALID_X) {
+                    g_set_error(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("invalid IP address \"%s\" for allowed-ip of peer"),
+                                &s[1]);
+                    return FALSE;
+                }
+            }
+        }
+
+        if (!_nm_setting_secret_flags_valid(self->preshared_key_flags)) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("invalid preshared-key-flags for peer"));
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+/**
+ * nm_wireguard_peer_cmp:
+ * @a: (allow-none): the #NMWireGuardPeer to compare.
+ * @b: (allow-none): the other #NMWireGuardPeer to compare.
+ * @compare_flags: #NMSettingCompareFlags to affect the comparison.
+ *
+ * Returns: zero of the two instances are equivalent or
+ *   a non-zero integer otherwise. This defines a total ordering
+ *   over the peers. Whether a peer is sealed or not, does not
+ *   affect the comparison.
+ *
+ * Since: 1.16
+ */
+int
+nm_wireguard_peer_cmp(const NMWireGuardPeer *a,
+                      const NMWireGuardPeer *b,
+                      NMSettingCompareFlags  compare_flags)
+{
+    guint i, n;
+
+    NM_CMP_SELF(a, b);
+
+    /* regardless of the @compare_flags, the public-key is the ID of the peer. It must
+     * always be compared. */
+    NM_CMP_FIELD_BOOL(a, b, public_key_valid);
+    NM_CMP_FIELD_STR0(a, b, public_key);
+
+    if (NM_FLAGS_ANY(compare_flags,
+                     NM_SETTING_COMPARE_FLAG_INFERRABLE | NM_SETTING_COMPARE_FLAG_FUZZY))
+        return 0;
+
+    NM_CMP_FIELD_BOOL(a, b, endpoint);
+    if (a->endpoint) {
+        NM_CMP_DIRECT_STRCMP0(nm_sock_addr_endpoint_get_endpoint(a->endpoint),
+                              nm_sock_addr_endpoint_get_endpoint(b->endpoint));
+    }
+
+    NM_CMP_FIELD(a, b, persistent_keepalive);
+
+    NM_CMP_DIRECT((n = (a->allowed_ips ? a->allowed_ips->len : 0u)),
+                  (b->allowed_ips ? b->allowed_ips->len : 0u));
+    for (i = 0; i < n; i++)
+        NM_CMP_DIRECT_STRCMP0(a->allowed_ips->pdata[i], b->allowed_ips->pdata[i]);
+
+    NM_CMP_FIELD(a, b, preshared_key_flags);
+
+    if (!NM_FLAGS_HAS(compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)) {
+        if (NM_FLAGS_HAS(compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS)
+            && NM_FLAGS_HAS(a->preshared_key_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)) {
+            /* pass */
+        } else if (NM_FLAGS_HAS(compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)
+                   && NM_FLAGS_HAS(a->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)) {
+            /* pass */
+        } else {
+            NM_CMP_FIELD_BOOL(a, b, preshared_key_valid);
+            NM_CMP_FIELD_STR0(a, b, preshared_key);
+        }
+    }
+
+    return 0;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    const char *     public_key;
+    NMWireGuardPeer *peer;
+    guint            idx;
+} PeerData;
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_FWMARK,
+                                  PROP_IP4_AUTO_DEFAULT_ROUTE,
+                                  PROP_IP6_AUTO_DEFAULT_ROUTE,
+                                  PROP_LISTEN_PORT,
+                                  PROP_MTU,
+                                  PROP_PEER_ROUTES,
+                                  PROP_PRIVATE_KEY,
+                                  PROP_PRIVATE_KEY_FLAGS, );
+
+typedef struct {
+    char *               private_key;
+    GPtrArray *          peers_arr;
+    GHashTable *         peers_hash;
+    NMSettingSecretFlags private_key_flags;
+    NMTernary            ip4_auto_default_route;
+    NMTernary            ip6_auto_default_route;
+    guint32              fwmark;
+    guint32              mtu;
+    guint16              listen_port;
+    bool                 private_key_valid : 1;
+    bool                 peer_routes : 1;
+} NMSettingWireGuardPrivate;
+
+/**
+ * NMSettingWireGuard:
+ *
+ * WireGuard Settings
+ *
+ * Since: 1.16
+ */
+struct _NMSettingWireGuard {
+    NMSetting                 parent;
+    NMSettingWireGuardPrivate _priv;
+};
+
+struct _NMSettingWireGuardClass {
+    NMSettingClass parent;
+};
+
+G_DEFINE_TYPE(NMSettingWireGuard, nm_setting_wireguard, NM_TYPE_SETTING)
+
+#define NM_SETTING_WIREGUARD_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMSettingWireGuard, NM_IS_SETTING_WIREGUARD, NMSetting)
+
+/*****************************************************************************/
+
+#define peers_psk_get_secret_name_a(public_key, to_free)                                        \
+    nm_construct_name_a(NM_SETTING_WIREGUARD_PEERS ".%s." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, \
+                        (public_key),                                                           \
+                        (to_free))
+
+#define peers_psk_get_secret_name_dup(public_key)                                           \
+    g_strdup_printf(NM_SETTING_WIREGUARD_PEERS ".%s." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, \
+                    (public_key))
+
+#define peers_psk_get_secret_parse_a(secret_public_key, public_key_free)                           \
+    ({                                                                                             \
+        const char *_secret_public_key = (secret_public_key);                                      \
+        char **     _public_key_free   = (public_key_free);                                        \
+        const char *_public_key        = NULL;                                                     \
+                                                                                                   \
+        nm_assert(_public_key_free && !*_public_key_free);                                         \
+                                                                                                   \
+        if (NM_STR_HAS_PREFIX(_secret_public_key, NM_SETTING_WIREGUARD_PEERS ".")) {               \
+            _secret_public_key += NM_STRLEN(NM_SETTING_WIREGUARD_PEERS ".");                       \
+            if (NM_STR_HAS_SUFFIX(_secret_public_key, "." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) { \
+                _public_key =                                                                      \
+                    nm_strndup_a(300,                                                              \
+                                 _secret_public_key,                                               \
+                                 strlen(_secret_public_key)                                        \
+                                     - NM_STRLEN("." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY),        \
+                                 _public_key_free);                                                \
+            }                                                                                      \
+        }                                                                                          \
+                                                                                                   \
+        _public_key;                                                                               \
+    })
+
+/*****************************************************************************/
+
+/**
+ * nm_setting_wireguard_get_private_key:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: (transfer none): the set private-key or %NULL.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_setting_wireguard_get_private_key(NMSettingWireGuard *self)
+{
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NULL);
+
+    return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->private_key;
+}
+
+/**
+ * nm_setting_wireguard_get_private_key_flags:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the secret-flags for #NMSettingWireGuard:private-key.
+ *
+ * Since: 1.16
+ */
+NMSettingSecretFlags
+nm_setting_wireguard_get_private_key_flags(NMSettingWireGuard *self)
+{
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0);
+
+    return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->private_key_flags;
+}
+
+/**
+ * nm_setting_wireguard_get_fwmark:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the set firewall mark.
+ *
+ * Since: 1.16
+ */
+guint32
+nm_setting_wireguard_get_fwmark(NMSettingWireGuard *self)
+{
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0);
+
+    return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->fwmark;
+}
+
+/**
+ * nm_setting_wireguard_get_listen_port:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the set UDP listen port.
+ *
+ * Since: 1.16
+ */
+guint16
+nm_setting_wireguard_get_listen_port(NMSettingWireGuard *self)
+{
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0);
+
+    return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->listen_port;
+}
+
+/**
+ * nm_setting_wireguard_get_peer_routes:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: whether automatically add peer routes.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_setting_wireguard_get_peer_routes(NMSettingWireGuard *self)
+{
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), TRUE);
+
+    return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->peer_routes;
+}
+
+/**
+ * nm_setting_wireguard_get_mtu:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the MTU of the setting.
+ *
+ * Since: 1.16
+ */
+guint32
+nm_setting_wireguard_get_mtu(NMSettingWireGuard *self)
+{
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0);
+
+    return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->mtu;
+}
+
+/**
+ * nm_setting_wireguard_get_ip4_auto_default_route:
+ * @self: the #NMSettingWireGuard setting.
+ *
+ * Returns: the "ip4-auto-default-route" property of the setting.
+ *
+ * Since: 1.20
+ */
+NMTernary
+nm_setting_wireguard_get_ip4_auto_default_route(NMSettingWireGuard *self)
+{
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NM_TERNARY_DEFAULT);
+
+    return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->ip4_auto_default_route;
+}
+
+/**
+ * nm_setting_wireguard_get_ip6_auto_default_route:
+ * @self: the #NMSettingWireGuard setting.
+ *
+ * Returns: the "ip6-auto-default-route" property of the setting.
+ *
+ * Since: 1.20
+ */
+NMTernary
+nm_setting_wireguard_get_ip6_auto_default_route(NMSettingWireGuard *self)
+{
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NM_TERNARY_DEFAULT);
+
+    return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->ip6_auto_default_route;
+}
+
+/*****************************************************************************/
+
+static void
+_peer_free(PeerData *pd)
+{
+    nm_assert(pd);
+
+    nm_wireguard_peer_unref(pd->peer);
+    g_slice_free(PeerData, pd);
+}
+
+/*****************************************************************************/
+
+static void
+_peers_notify(gpointer self)
+{
+    _nm_setting_emit_property_changed(self);
+}
+
+static PeerData *
+_peers_get(NMSettingWireGuardPrivate *priv, guint idx)
+{
+    PeerData *pd;
+
+    nm_assert(priv);
+    nm_assert(idx < priv->peers_arr->len);
+
+    pd = priv->peers_arr->pdata[idx];
+
+    nm_assert(pd);
+    nm_assert(pd->idx == idx);
+    nm_assert(NM_IS_WIREGUARD_PEER(pd->peer, TRUE));
+    nm_assert(nm_wireguard_peer_is_sealed(pd->peer));
+    nm_assert(pd->public_key == nm_wireguard_peer_get_public_key(pd->peer));
+    nm_assert(g_hash_table_lookup(priv->peers_hash, pd) == pd);
+
+    return pd;
+}
+
+static PeerData *
+_peers_get_by_public_key(NMSettingWireGuardPrivate *priv,
+                         const char *               public_key,
+                         gboolean                   try_with_normalized_key)
+{
+    gs_free char *public_key_normalized = NULL;
+    PeerData *    pd;
+
+again:
+    nm_assert(priv);
+    nm_assert(public_key);
+
+    pd = g_hash_table_lookup(priv->peers_hash, &public_key);
+    if (pd) {
+        nm_assert(_peers_get(priv, pd->idx) == pd);
+        return pd;
+    }
+    if (try_with_normalized_key
+        && nm_utils_base64secret_normalize(public_key,
+                                           NM_WIREGUARD_PUBLIC_KEY_LEN,
+                                           &public_key_normalized)) {
+        public_key              = public_key_normalized;
+        try_with_normalized_key = FALSE;
+        goto again;
+    }
+    return NULL;
+}
+
+static void
+_peers_remove(NMSettingWireGuardPrivate *priv, PeerData *pd, gboolean do_free)
+{
+    guint i;
+
+    nm_assert(pd);
+    nm_assert(_peers_get(priv, pd->idx) == pd);
+
+    for (i = pd->idx + 1; i < priv->peers_arr->len; i++)
+        _peers_get(priv, i)->idx--;
+
+    g_ptr_array_remove_index(priv->peers_arr, pd->idx);
+    if (!g_hash_table_remove(priv->peers_hash, pd))
+        nm_assert_not_reached();
+    if (do_free)
+        _peer_free(pd);
+}
+
+/**
+ * nm_setting_wireguard_get_peers_len:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the number of registered peers.
+ *
+ * Since: 1.16
+ */
+guint
+nm_setting_wireguard_get_peers_len(NMSettingWireGuard *self)
+{
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0);
+
+    return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->peers_arr->len;
+}
+
+/**
+ * nm_setting_wireguard_get_peer:
+ * @self: the #NMSettingWireGuard instance
+ * @idx: the index to lookup.
+ *
+ * Returns: (transfer none): the #NMWireGuardPeer entry at
+ *   index @idx. If the index is out of range, %NULL is returned.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_setting_wireguard_get_peer(NMSettingWireGuard *self, guint idx)
+{
+    NMSettingWireGuardPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NULL);
+
+    priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self);
+
+    if (idx >= priv->peers_arr->len)
+        return NULL;
+
+    return _peers_get(priv, idx)->peer;
+}
+
+/**
+ * nm_setting_wireguard_get_peer_by_public_key:
+ * @self: the #NMSettingWireGuard instance
+ * @public_key: the public key for looking up the
+ *   peer.
+ * @out_idx: (out) (allow-none): optional output argument
+ *   for the index of the found peer. If no index is found,
+ *   this is set to the nm_setting_wireguard_get_peers_len().
+ *
+ * Returns: (transfer none): the #NMWireGuardPeer instance with a
+ *   matching public key. If no such peer exists, %NULL is returned.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_setting_wireguard_get_peer_by_public_key(NMSettingWireGuard *self,
+                                            const char *        public_key,
+                                            guint *             out_idx)
+{
+    NMSettingWireGuardPrivate *priv;
+    PeerData *                 pd;
+
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NULL);
+    g_return_val_if_fail(public_key, NULL);
+
+    priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self);
+
+    pd = _peers_get_by_public_key(priv, public_key, TRUE);
+    if (!pd) {
+        NM_SET_OUT(out_idx, priv->peers_arr->len);
+        return NULL;
+    }
+    NM_SET_OUT(out_idx, pd->idx);
+    return pd->peer;
+}
+
+static gboolean
+_peers_set(NMSettingWireGuardPrivate *priv,
+           NMWireGuardPeer *          peer,
+           guint                      idx,
+           gboolean                   check_same_key)
+{
+    PeerData *  pd_same_key = NULL;
+    PeerData *  pd_idx      = NULL;
+    const char *public_key;
+
+    nm_assert(idx <= priv->peers_arr->len);
+
+    public_key = nm_wireguard_peer_get_public_key(peer);
+
+    if (idx < priv->peers_arr->len) {
+        pd_idx = _peers_get(priv, idx);
+
+        if (pd_idx->peer == peer)
+            return FALSE;
+
+        if (check_same_key && nm_streq(public_key, nm_wireguard_peer_get_public_key(pd_idx->peer)))
+            check_same_key = FALSE;
+    }
+
+    nm_wireguard_peer_seal(peer);
+    nm_wireguard_peer_ref(peer);
+
+    if (check_same_key) {
+        pd_same_key = _peers_get_by_public_key(priv, public_key, FALSE);
+        if (pd_same_key) {
+            if (pd_idx) {
+                nm_assert(pd_same_key != pd_idx);
+                _peers_remove(priv, pd_same_key, TRUE);
+                pd_same_key = NULL;
+            } else {
+                if (pd_same_key->peer == peer && pd_same_key->idx == priv->peers_arr->len - 1) {
+                    nm_wireguard_peer_unref(peer);
+                    return FALSE;
+                }
+                _peers_remove(priv, pd_same_key, FALSE);
+                nm_wireguard_peer_unref(pd_same_key->peer);
+            }
+        }
+    } else
+        nm_assert(_peers_get_by_public_key(priv, public_key, FALSE) == pd_idx);
+
+    if (pd_idx) {
+        g_hash_table_remove(priv->peers_hash, pd_idx);
+        nm_wireguard_peer_unref(pd_idx->peer);
+        pd_idx->public_key = public_key;
+        pd_idx->peer       = peer;
+        g_hash_table_add(priv->peers_hash, pd_idx);
+        return TRUE;
+    }
+
+    if (!pd_same_key)
+        pd_same_key = g_slice_new(PeerData);
+
+    *pd_same_key = (PeerData){
+        .peer       = peer,
+        .public_key = public_key,
+        .idx        = priv->peers_arr->len,
+    };
+
+    g_ptr_array_add(priv->peers_arr, pd_same_key);
+    if (!nm_g_hash_table_add(priv->peers_hash, pd_same_key))
+        nm_assert_not_reached();
+
+    nm_assert(_peers_get(priv, pd_same_key->idx) == pd_same_key);
+
+    return TRUE;
+}
+
+static gboolean
+_peers_append(NMSettingWireGuardPrivate *priv, NMWireGuardPeer *peer, gboolean check_same_key)
+{
+    return _peers_set(priv, peer, priv->peers_arr->len, check_same_key);
+}
+
+/**
+ * nm_setting_wireguard_set_peer:
+ * @self: the #NMSettingWireGuard instance
+ * @peer: the #NMWireGuardPeer instance to set.
+ *   This seals @peer and keeps a reference on the
+ *   instance.
+ * @idx: the index, in the range of 0 to the number of
+ *   peers (including). That means, if @idx is one past
+ *   the end of the number of peers, this is the same as
+ *   nm_setting_wireguard_append_peer(). Otherwise, the
+ *   peer at this index is replaced.
+ *
+ * If @idx is one past the last peer, the behavior is the same
+ * as nm_setting_wireguard_append_peer().
+ * Otherwise, the peer will be at @idx and replace the peer
+ * instance at that index. Note that if a peer with the same
+ * public-key exists on another index, then that peer will also
+ * be replaced. In that case, the number of peers will shrink
+ * by one (because the one at @idx got replace and then one
+ * with the same public-key got removed). This also means,
+ * that the resulting index afterwards may be one less than
+ * @idx (if another peer with a lower index was dropped).
+ *
+ * Since: 1.16
+ */
+void
+nm_setting_wireguard_set_peer(NMSettingWireGuard *self, NMWireGuardPeer *peer, guint idx)
+{
+    NMSettingWireGuardPrivate *priv;
+
+    g_return_if_fail(NM_IS_SETTING_WIREGUARD(self));
+    g_return_if_fail(NM_IS_WIREGUARD_PEER(peer, TRUE));
+
+    priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self);
+
+    g_return_if_fail(idx <= priv->peers_arr->len);
+
+    if (_peers_set(priv, peer, idx, TRUE))
+        _peers_notify(self);
+}
+
+/**
+ * nm_setting_wireguard_append_peer:
+ * @self: the #NMSettingWireGuard instance
+ * @peer: the #NMWireGuardPeer instance to append.
+ *   This seals @peer and keeps a reference on the
+ *   instance.
+ *
+ * If a peer with the same public-key already exists, that
+ * one is replaced by @peer. The new @peer is always appended
+ * (or moved to) the end, so in case a peer is replaced, the
+ * indexes are shifted and the number of peers stays unchanged.
+ *
+ * Since: 1.16
+ */
+void
+nm_setting_wireguard_append_peer(NMSettingWireGuard *self, NMWireGuardPeer *peer)
+{
+    g_return_if_fail(NM_IS_SETTING_WIREGUARD(self));
+    g_return_if_fail(NM_IS_WIREGUARD_PEER(peer, TRUE));
+
+    if (_peers_append(NM_SETTING_WIREGUARD_GET_PRIVATE(self), peer, TRUE))
+        _peers_notify(self);
+}
+
+/**
+ * nm_setting_wireguard_remove_peer
+ * @self: the #NMSettingWireGuard instance
+ * @idx: the index to remove.
+ *
+ * Returns: %TRUE if @idx was in range and a peer
+ *   was removed. Otherwise, @self is unchanged.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_setting_wireguard_remove_peer(NMSettingWireGuard *self, guint idx)
+{
+    NMSettingWireGuardPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), FALSE);
+
+    priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self);
+
+    if (idx >= priv->peers_arr->len)
+        return FALSE;
+
+    _peers_remove(priv, _peers_get(priv, idx), TRUE);
+    _peers_notify(self);
+    return TRUE;
+}
+
+static guint
+_peers_clear(NMSettingWireGuardPrivate *priv)
+{
+    guint l;
+
+    l = priv->peers_arr->len;
+    while (priv->peers_arr->len > 0) {
+        _peers_remove(priv, _peers_get(priv, priv->peers_arr->len - 1), TRUE);
+    }
+    return l;
+}
+
+/**
+ * nm_setting_wireguard_:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the number of cleared peers.
+ *
+ * Since: 1.16
+ */
+guint
+nm_setting_wireguard_clear_peers(NMSettingWireGuard *self)
+{
+    guint l;
+
+    g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0);
+
+    l = _peers_clear(NM_SETTING_WIREGUARD_GET_PRIVATE(self));
+    if (l > 0)
+        _peers_notify(self);
+    return l;
+}
+
+/*****************************************************************************/
+
+static GVariant *
+_peers_dbus_only_synth(const NMSettInfoSetting *               sett_info,
+                       guint                                   property_idx,
+                       NMConnection *                          connection,
+                       NMSetting *                             setting,
+                       NMConnectionSerializationFlags          flags,
+                       const NMConnectionSerializationOptions *options)
+{
+    NMSettingWireGuard *       self = NM_SETTING_WIREGUARD(setting);
+    NMSettingWireGuardPrivate *priv;
+    gboolean                   any_peers = FALSE;
+    GVariantBuilder            peers_builder;
+    guint                      i_peer, n_peers;
+    guint                      i;
+
+    n_peers = nm_setting_wireguard_get_peers_len(self);
+    if (n_peers == 0)
+        return NULL;
+
+    priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self);
+
+    for (i_peer = 0; i_peer < n_peers; i_peer++) {
+        const NMWireGuardPeer *peer = _peers_get(priv, i_peer)->peer;
+        GVariantBuilder        builder;
+
+        if (!peer->public_key)
+            continue;
+
+        g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+
+        g_variant_builder_add(&builder,
+                              "{sv}",
+                              NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY,
+                              g_variant_new_string(peer->public_key));
+
+        if (_nm_connection_serialize_non_secret(flags) && peer->endpoint)
+            g_variant_builder_add(
+                &builder,
+                "{sv}",
+                NM_WIREGUARD_PEER_ATTR_ENDPOINT,
+                g_variant_new_string(nm_sock_addr_endpoint_get_endpoint(peer->endpoint)));
+
+        if (_nm_connection_serialize_secrets(flags, peer->preshared_key_flags)
+            && peer->preshared_key)
+            g_variant_builder_add(&builder,
+                                  "{sv}",
+                                  NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY,
+                                  g_variant_new_string(peer->preshared_key));
+
+        if (_nm_connection_serialize_non_secret(flags)
+            && peer->preshared_key_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
+            g_variant_builder_add(&builder,
+                                  "{sv}",
+                                  NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS,
+                                  g_variant_new_uint32(peer->preshared_key_flags));
+
+        if (_nm_connection_serialize_non_secret(flags) && peer->persistent_keepalive != 0)
+            g_variant_builder_add(&builder,
+                                  "{sv}",
+                                  NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE,
+                                  g_variant_new_uint32(peer->persistent_keepalive));
+
+        if (_nm_connection_serialize_non_secret(flags) && peer->allowed_ips
+            && peer->allowed_ips->len > 0) {
+            const char *const *  strv       = (const char *const *) peer->allowed_ips->pdata;
+            gs_free const char **strv_fixed = NULL;
+
+            for (i = 0; i < peer->allowed_ips->len; i++) {
+                /* This is a bit odd. The original peer->allowed_ips is a strv array
+                 * of all allowed-ips, but some of them may have an ALLOWED_IP_INVALID_X prefix
+                 * (as first character).
+                 *
+                 * When we serialize the array to GVariant, we need to get rid of this
+                 * character, by pointing to the second character.
+                 *
+                 * In that case, first clone the strv array to strv_fixed. Once we cloned
+                 * it, adjust the pointer to drop the first character. */
+                if (strv[i][0] != ALLOWED_IP_INVALID_X) {
+                    /* this string is valid, no need for adjustment. */
+                    continue;
+                }
+                if (!strv_fixed) {
+                    strv_fixed = nm_memdup(strv, sizeof(strv[0]) * peer->allowed_ips->len);
+                    strv       = strv_fixed;
+                }
+                ((const char **) strv)[i]++;
+            }
+            g_variant_builder_add(&builder,
+                                  "{sv}",
+                                  NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS,
+                                  g_variant_new_strv(strv, peer->allowed_ips->len));
+        }
+
+        if (!any_peers) {
+            g_variant_builder_init(&peers_builder, G_VARIANT_TYPE("aa{sv}"));
+            any_peers = TRUE;
+        }
+        g_variant_builder_add(&peers_builder, "a{sv}", &builder);
+    }
+
+    return any_peers ? g_variant_builder_end(&peers_builder) : NULL;
+}
+
+static gboolean
+_peers_dbus_only_set(NMSetting *         setting,
+                     GVariant *          connection_dict,
+                     const char *        property,
+                     GVariant *          value,
+                     NMSettingParseFlags parse_flags,
+                     GError **           error)
+{
+    GVariantIter iter_peers;
+    GVariant *   peer_var;
+    guint        i_peer;
+    gboolean     success       = FALSE;
+    gboolean     peers_changed = FALSE;
+
+    nm_assert(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")));
+
+    g_variant_iter_init(&iter_peers, value);
+
+    i_peer = 0;
+    while (g_variant_iter_next(&iter_peers, "@a{sv}", &peer_var)) {
+        _nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var;
+        nm_auto_unref_wgpeer NMWireGuardPeer *peer           = NULL;
+        const char *                          cstr;
+        guint32                               u32;
+        GVariant *                            var;
+
+        i_peer++;
+
+        if (!g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) {
+            if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                            _("peer #%u has no public-key"),
+                            i_peer);
+                goto out;
+            }
+            continue;
+        }
+
+        peer = nm_wireguard_peer_new();
+        if (!nm_wireguard_peer_set_public_key(peer, cstr, TRUE)) {
+            if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                            _("peer #%u has invalid public-key"),
+                            i_peer);
+                goto out;
+            }
+            continue;
+        }
+
+        if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_ENDPOINT, "&s", &cstr)) {
+            nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL;
+
+            ep = nm_sock_addr_endpoint_new(cstr);
+            if (!nm_sock_addr_endpoint_get_host(ep)) {
+                if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+                    g_set_error(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                                _("peer #%u has invalid endpoint"),
+                                i_peer);
+                    goto out;
+                }
+            } else
+                _nm_wireguard_peer_set_endpoint(peer, ep);
+        }
+
+        if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr))
+            nm_wireguard_peer_set_preshared_key(peer, cstr, TRUE);
+
+        if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, "u", &u32))
+            nm_wireguard_peer_set_preshared_key_flags(peer, u32);
+
+        if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, "u", &u32))
+            nm_wireguard_peer_set_persistent_keepalive(peer, u32);
+
+        if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, "@as", &var)) {
+            _nm_unused gs_unref_variant GVariant *var_free    = var;
+            gs_free const char **                 allowed_ips = NULL;
+            gsize                                 i, l;
+
+            allowed_ips = g_variant_get_strv(var, &l);
+            if (allowed_ips) {
+                for (i = 0; i < l; i++) {
+                    if (_peer_append_allowed_ip(peer, allowed_ips[i], FALSE))
+                        continue;
+                    if (!NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
+                        continue;
+                    g_set_error(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                                _("peer #%u has invalid allowed-ips setting"),
+                                i_peer);
+                    goto out;
+                }
+            }
+        }
+
+        if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+            gs_free_error GError *local = NULL;
+
+            if (!nm_wireguard_peer_is_valid(peer, TRUE, FALSE, &local)) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                            _("peer #%u is invalid: %s"),
+                            i_peer,
+                            local->message);
+                goto out;
+            }
+        }
+
+        /* we could easily reject duplicate peers (by public-key) or duplicate GVariant attributes.
+         * However, don't do that. In case of duplicate values, the latter peer overwrite the earlier
+         * and GVariant attributes are ignored by g_variant_lookup() above. */
+        if (_peers_append(NM_SETTING_WIREGUARD_GET_PRIVATE(setting), peer, TRUE))
+            peers_changed = TRUE;
+    }
+
+    success = TRUE;
+
+out:
+    if (peers_changed)
+        _peers_notify(setting);
+    return success;
+}
+
+/*****************************************************************************/
+
+static gboolean
+verify(NMSetting *setting, NMConnection *connection, GError **error)
+{
+    NMSettingWireGuard *       s_wg = NM_SETTING_WIREGUARD(setting);
+    NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting);
+    guint                      i;
+
+    if (!_nm_connection_verify_required_interface_name(connection, error))
+        return FALSE;
+
+    if (!_nm_utils_secret_flags_validate(nm_setting_wireguard_get_private_key_flags(s_wg),
+                                         NM_SETTING_WIREGUARD_SETTING_NAME,
+                                         NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS,
+                                         NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
+                                         error))
+        return FALSE;
+
+    for (i = 0; i < priv->peers_arr->len; i++) {
+        NMWireGuardPeer *peer = _peers_get(priv, i)->peer;
+
+        if (!nm_wireguard_peer_is_valid(peer, TRUE, FALSE, error)) {
+            g_prefix_error(error,
+                           "%s.%s[%u]: ",
+                           NM_SETTING_WIREGUARD_SETTING_NAME,
+                           NM_SETTING_WIREGUARD_PEERS,
+                           i);
+            return FALSE;
+        }
+    }
+
+    if (connection) {
+        NMSettingIPConfig *s_ip4;
+        NMSettingIPConfig *s_ip6;
+        const char *       method;
+
+        /* WireGuard is Layer 3 only. For the moment, we only support a restricted set of
+         * IP methods. We may relax that later, once we fix the implementations so they
+         * actually work. */
+
+        if ((s_ip4 = nm_connection_get_setting_ip4_config(connection))
+            && (method = nm_setting_ip_config_get_method(s_ip4))
+            && !NM_IN_STRSET(method,
+                             NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
+                             NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("method \"%s\" is not supported for WireGuard"),
+                        method);
+            g_prefix_error(error,
+                           "%s.%s: ",
+                           NM_SETTING_IP4_CONFIG_SETTING_NAME,
+                           NM_SETTING_IP_CONFIG_METHOD);
+            return FALSE;
+        }
+
+        if ((s_ip6 = nm_connection_get_setting_ip6_config(connection))
+            && (method = nm_setting_ip_config_get_method(s_ip6))
+            && !NM_IN_STRSET(method,
+                             NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                             NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
+                             NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                             NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("method \"%s\" is not supported for WireGuard"),
+                        method);
+            g_prefix_error(error,
+                           "%s.%s: ",
+                           NM_SETTING_IP6_CONFIG_SETTING_NAME,
+                           NM_SETTING_IP_CONFIG_METHOD);
+            return FALSE;
+        }
+    }
+
+    /* private-key is a secret, hence we cannot verify it like a regular property. */
+    return TRUE;
+}
+
+static gboolean
+verify_secrets(NMSetting *setting, NMConnection *connection, GError **error)
+{
+    NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting);
+    guint                      i;
+
+    if (priv->private_key && !priv->private_key_valid) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("key must be 32 bytes base64 encoded"));
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_WIREGUARD_SETTING_NAME,
+                       NM_SETTING_WIREGUARD_PRIVATE_KEY);
+        return FALSE;
+    }
+
+    for (i = 0; i < priv->peers_arr->len; i++) {
+        NMWireGuardPeer *peer = _peers_get(priv, i)->peer;
+
+        if (!nm_wireguard_peer_is_valid(peer, FALSE, TRUE, error)) {
+            g_prefix_error(error,
+                           "%s.%s[%u]: ",
+                           NM_SETTING_WIREGUARD_SETTING_NAME,
+                           NM_SETTING_WIREGUARD_PEERS,
+                           i);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static GPtrArray *
+need_secrets(NMSetting *setting)
+{
+    NMSettingWireGuardPrivate *priv    = NM_SETTING_WIREGUARD_GET_PRIVATE(setting);
+    GPtrArray *                secrets = NULL;
+    guint                      i;
+
+    if (!priv->private_key || !priv->private_key_valid) {
+        secrets = g_ptr_array_new_full(1, g_free);
+        g_ptr_array_add(secrets, g_strdup(NM_SETTING_WIREGUARD_PRIVATE_KEY));
+    }
+
+    for (i = 0; i < priv->peers_arr->len; i++) {
+        NMWireGuardPeer *peer = _peers_get(priv, i)->peer;
+
+        if (NM_FLAGS_HAS(peer->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED))
+            continue;
+
+        if (peer->preshared_key_valid)
+            continue;
+
+        if (!peer->public_key_valid)
+            continue;
+
+        if (!secrets)
+            secrets = g_ptr_array_new_full(1, g_free);
+        g_ptr_array_add(secrets, peers_psk_get_secret_name_dup(peer->public_key));
+    }
+
+    return secrets;
+}
+
+static gboolean
+clear_secrets(const NMSettInfoSetting *        sett_info,
+              guint                            property_idx,
+              NMSetting *                      setting,
+              NMSettingClearSecretsWithFlagsFn func,
+              gpointer                         user_data)
+{
+    if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) {
+        NMSettingWireGuardPrivate *priv          = NM_SETTING_WIREGUARD_GET_PRIVATE(setting);
+        gboolean                   peers_changed = FALSE;
+        guint                      i, j;
+
+        j = 0;
+        for (i = 0; i < priv->peers_arr->len; i++) {
+            NMWireGuardPeer *peer = _peers_get(priv, i)->peer;
+
+            if (!peer->preshared_key)
+                continue;
+
+            if (func) {
+                gs_free char *name_free = NULL;
+                const char *  name;
+
+                if (j++ < 5) {
+                    /* we use alloca() inside a loop here, but it is guarded to happen at most
+                     * a few times. */
+                    name = peers_psk_get_secret_name_a(/* lgtm [cpp/alloca-in-loop] */
+                                                       peer->public_key,
+                                                       &name_free);
+                } else {
+                    name_free = peers_psk_get_secret_name_dup(peer->public_key);
+                    name      = name_free;
+                }
+
+                if (!func(setting, name, peer->preshared_key_flags, user_data))
+                    continue;
+            }
+
+            {
+                nm_auto_unref_wgpeer NMWireGuardPeer *peer2 = NULL;
+
+                peer2 = nm_wireguard_peer_new_clone(peer, FALSE);
+
+                if (_peers_set(priv, peer2, i, FALSE))
+                    peers_changed = TRUE;
+            }
+        }
+
+        if (peers_changed)
+            _peers_notify(setting);
+        return peers_changed;
+    }
+
+    return NM_SETTING_CLASS(nm_setting_wireguard_parent_class)
+        ->clear_secrets(sett_info, property_idx, setting, func, user_data);
+}
+
+static int
+update_one_secret(NMSetting *setting, const char *key, GVariant *value, GError **error)
+{
+    NMSettingWireGuard *       self = NM_SETTING_WIREGUARD(setting);
+    NMSettingWireGuardPrivate *priv;
+    gboolean                   has_changes = FALSE;
+    gboolean                   has_error   = FALSE;
+    GVariantIter               iter_peers;
+    GVariant *                 peer_var;
+    guint                      i_peer;
+
+    if (!nm_streq(key, NM_SETTING_WIREGUARD_PEERS)) {
+        return NM_SETTING_CLASS(nm_setting_wireguard_parent_class)
+            ->update_one_secret(setting, key, value, error);
+    }
+
+    if (!g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}"))) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+                            _("invalid peer secrets"));
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_WIREGUARD_SETTING_NAME,
+                       NM_SETTING_WIREGUARD_PEERS);
+        return NM_SETTING_UPDATE_SECRET_ERROR;
+    }
+
+    priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self);
+
+    g_variant_iter_init(&iter_peers, value);
+
+    i_peer = 0;
+    while (g_variant_iter_next(&iter_peers, "@a{sv}", &peer_var)) {
+        _nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var;
+        nm_auto_unref_wgpeer NMWireGuardPeer *peer           = NULL;
+        PeerData *                            pd;
+        const char *                          cstr;
+
+        i_peer++;
+
+        if (!g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) {
+            if (!has_error) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+                            _("peer #%u lacks public-key"),
+                            i_peer - 1);
+                g_prefix_error(error,
+                               "%s.%s: ",
+                               NM_SETTING_WIREGUARD_SETTING_NAME,
+                               NM_SETTING_WIREGUARD_PEERS);
+                has_error = TRUE;
+            }
+            continue;
+        }
+
+        pd = _peers_get_by_public_key(priv, cstr, TRUE);
+        if (!pd) {
+            if (!has_error) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+                            _("non-existing peer '%s'"),
+                            cstr);
+                g_prefix_error(error,
+                               "%s.%s: ",
+                               NM_SETTING_WIREGUARD_SETTING_NAME,
+                               NM_SETTING_WIREGUARD_PEERS);
+                has_error = TRUE;
+            }
+            continue;
+        }
+
+        if (!g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr)) {
+            /* no preshared-key. Ignore the rest.
+             *
+             * In particular, we don't reject all unknown fields. */
+            continue;
+        }
+
+        if (nm_streq0(cstr, nm_wireguard_peer_get_preshared_key(pd->peer)))
+            continue;
+
+        peer = nm_wireguard_peer_new_clone(pd->peer, FALSE);
+        nm_wireguard_peer_set_preshared_key(peer, cstr, TRUE);
+
+        if (!_peers_set(priv, peer, pd->idx, FALSE))
+            nm_assert_not_reached();
+        has_changes = TRUE;
+    }
+
+    if (has_error)
+        return NM_SETTING_UPDATE_SECRET_ERROR;
+    if (has_changes)
+        return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
+    return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;
+}
+
+static NMTernary
+compare_property(const NMSettInfoSetting *sett_info,
+                 guint                    property_idx,
+                 NMConnection *           con_a,
+                 NMSetting *              set_a,
+                 NMConnection *           con_b,
+                 NMSetting *              set_b,
+                 NMSettingCompareFlags    flags)
+{
+    NMSettingWireGuardPrivate *a_priv;
+    NMSettingWireGuardPrivate *b_priv;
+    guint                      i;
+
+    if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) {
+        if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE))
+            return NM_TERNARY_DEFAULT;
+
+        if (!set_b)
+            return TRUE;
+
+        a_priv = NM_SETTING_WIREGUARD_GET_PRIVATE(set_a);
+        b_priv = NM_SETTING_WIREGUARD_GET_PRIVATE(set_b);
+
+        if (a_priv->peers_arr->len != b_priv->peers_arr->len)
+            return FALSE;
+        for (i = 0; i < a_priv->peers_arr->len; i++) {
+            NMWireGuardPeer *a_peer = _peers_get(a_priv, i)->peer;
+            NMWireGuardPeer *b_peer = _peers_get(b_priv, i)->peer;
+
+            if (nm_wireguard_peer_cmp(a_peer, b_peer, flags) != 0)
+                return FALSE;
+        }
+
+        return TRUE;
+    }
+
+    return NM_SETTING_CLASS(nm_setting_wireguard_parent_class)
+        ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags);
+}
+
+static void
+duplicate_copy_properties(const NMSettInfoSetting *sett_info, NMSetting *src, NMSetting *dst)
+{
+    NMSettingWireGuardPrivate *priv_src = NM_SETTING_WIREGUARD_GET_PRIVATE(src);
+    NMSettingWireGuardPrivate *priv_dst = NM_SETTING_WIREGUARD_GET_PRIVATE(dst);
+    guint                      i;
+    gboolean                   peers_changed = FALSE;
+
+    NM_SETTING_CLASS(nm_setting_wireguard_parent_class)
+        ->duplicate_copy_properties(sett_info, src, dst);
+
+    /* We don't bother comparing the existing peers with what we are about to set.
+     * Always reset all. */
+    if (_peers_clear(priv_dst) > 0)
+        peers_changed = TRUE;
+    for (i = 0; i < priv_src->peers_arr->len; i++) {
+        if (_peers_append(priv_dst, _peers_get(priv_src, i)->peer, FALSE))
+            peers_changed = TRUE;
+    }
+    if (peers_changed)
+        _peers_notify(dst);
+}
+
+static void
+enumerate_values(const NMSettInfoProperty *property_info,
+                 NMSetting *               setting,
+                 NMSettingValueIterFn      func,
+                 gpointer                  user_data)
+{
+    if (nm_streq(property_info->name, NM_SETTING_WIREGUARD_PEERS)) {
+        NMSettingWireGuardPrivate * priv  = NM_SETTING_WIREGUARD_GET_PRIVATE(setting);
+        nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
+        GPtrArray *                 ptr   = NULL;
+        guint                       i;
+
+        if (priv->peers_arr && priv->peers_arr->len > 0) {
+            ptr = g_ptr_array_new_with_free_func((GDestroyNotify) nm_wireguard_peer_unref);
+            for (i = 0; i < priv->peers_arr->len; i++)
+                g_ptr_array_add(ptr, nm_wireguard_peer_ref(_peers_get(priv, i)->peer));
+        }
+        g_value_init(&value, G_TYPE_PTR_ARRAY);
+        g_value_take_boxed(&value, ptr);
+        func(setting, property_info->name, &value, 0, user_data);
+        return;
+    }
+
+    NM_SETTING_CLASS(nm_setting_wireguard_parent_class)
+        ->enumerate_values(property_info, setting, func, user_data);
+}
+
+static gboolean
+aggregate(NMSetting *setting, int type_i, gpointer arg)
+{
+    NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting);
+    NMConnectionAggregateType  type = type_i;
+    NMSettingSecretFlags       secret_flags;
+    guint                      i;
+
+    nm_assert(NM_IN_SET(type,
+                        NM_CONNECTION_AGGREGATE_ANY_SECRETS,
+                        NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS));
+
+    switch (type) {
+    case NM_CONNECTION_AGGREGATE_ANY_SECRETS:
+        if (priv->private_key)
+            goto out_done;
+        for (i = 0; i < priv->peers_arr->len; i++) {
+            if (nm_wireguard_peer_get_preshared_key(_peers_get(priv, i)->peer))
+                goto out_done;
+        }
+        break;
+
+    case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS:
+#if NM_MORE_ASSERTS
+        if (!nm_setting_get_secret_flags(setting,
+                                         NM_SETTING_WIREGUARD_PRIVATE_KEY,
+                                         &secret_flags,
+                                         NULL))
+            nm_assert_not_reached();
+        nm_assert(secret_flags == priv->private_key_flags);
+#endif
+        if (priv->private_key_flags == NM_SETTING_SECRET_FLAG_NONE)
+            goto out_done;
+        for (i = 0; i < priv->peers_arr->len; i++) {
+            secret_flags = nm_wireguard_peer_get_preshared_key_flags(_peers_get(priv, i)->peer);
+            if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
+                goto out_done;
+        }
+        break;
+    }
+
+    return FALSE;
+
+out_done:
+    *((gboolean *) arg) = TRUE;
+    return TRUE;
+}
+
+static gboolean
+get_secret_flags(NMSetting *           setting,
+                 const char *          secret_name,
+                 NMSettingSecretFlags *out_flags,
+                 GError **             error)
+{
+    if (NM_STR_HAS_PREFIX(secret_name, NM_SETTING_WIREGUARD_PEERS ".")) {
+        NMSettingWireGuardPrivate *priv            = NM_SETTING_WIREGUARD_GET_PRIVATE(setting);
+        gs_free char *             public_key_free = NULL;
+        const char *               public_key;
+        PeerData *                 pd;
+
+        public_key = peers_psk_get_secret_parse_a(secret_name, &public_key_free);
+        if (public_key && (pd = _peers_get_by_public_key(priv, public_key, FALSE))) {
+            NM_SET_OUT(out_flags, nm_wireguard_peer_get_preshared_key_flags(pd->peer));
+            return TRUE;
+        }
+    }
+
+    return NM_SETTING_CLASS(nm_setting_wireguard_parent_class)
+        ->get_secret_flags(setting, secret_name, out_flags, error);
+}
+
+static gboolean
+set_secret_flags(NMSetting *          setting,
+                 const char *         secret_name,
+                 NMSettingSecretFlags flags,
+                 GError **            error)
+{
+    if (NM_STR_HAS_PREFIX(secret_name, NM_SETTING_WIREGUARD_PEERS ".")) {
+        NMSettingWireGuard *       self            = NM_SETTING_WIREGUARD(setting);
+        NMSettingWireGuardPrivate *priv            = NM_SETTING_WIREGUARD_GET_PRIVATE(self);
+        gs_free char *             public_key_free = NULL;
+        const char *               public_key;
+        PeerData *                 pd;
+
+        public_key = peers_psk_get_secret_parse_a(secret_name, &public_key_free);
+        if (public_key && (pd = _peers_get_by_public_key(priv, public_key, FALSE))) {
+            if (nm_wireguard_peer_get_preshared_key_flags(pd->peer) != flags) {
+                nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+
+                peer                      = nm_wireguard_peer_new_clone(pd->peer, TRUE);
+                peer->preshared_key_flags = flags;
+                if (_peers_set(priv, peer, pd->idx, FALSE))
+                    _peers_notify(self);
+            }
+
+            return TRUE;
+        }
+    }
+
+    return NM_SETTING_CLASS(nm_setting_wireguard_parent_class)
+        ->set_secret_flags(setting, secret_name, flags, error);
+}
+
+static void
+for_each_secret(NMSetting *                    setting,
+                const char *                   data_key,
+                GVariant *                     data_val,
+                gboolean                       remove_non_secrets,
+                _NMConnectionForEachSecretFunc callback,
+                gpointer                       callback_data,
+                GVariantBuilder *              setting_builder)
+{
+    NMSettingWireGuard *       s_wg;
+    NMSettingWireGuardPrivate *priv;
+    GVariantBuilder            peers_builder;
+    GVariantIter *             peer_iter;
+    GVariantIter               data_iter;
+    const char *               key;
+
+    if (!nm_streq(data_key, NM_SETTING_WIREGUARD_PEERS)) {
+        NM_SETTING_CLASS(nm_setting_wireguard_parent_class)
+            ->for_each_secret(setting,
+                              data_key,
+                              data_val,
+                              remove_non_secrets,
+                              callback,
+                              callback_data,
+                              setting_builder);
+        return;
+    }
+
+    if (!g_variant_is_of_type(data_val, G_VARIANT_TYPE("aa{sv}"))) {
+        /* invalid type. Silently ignore content as we cannot find secret-keys
+         * here. */
+        return;
+    }
+
+    s_wg = NM_SETTING_WIREGUARD(setting);
+    priv = NM_SETTING_WIREGUARD_GET_PRIVATE(s_wg);
+
+    g_variant_builder_init(&peers_builder, G_VARIANT_TYPE("aa{sv}"));
+    g_variant_iter_init(&data_iter, data_val);
+    while (g_variant_iter_next(&data_iter, "a{sv}", &peer_iter)) {
+        _nm_unused nm_auto_free_variant_iter GVariantIter *peer_iter_free = peer_iter;
+        gs_unref_variant GVariant *preshared_key                          = NULL;
+        PeerData *                 pd                                     = NULL;
+        NMSettingSecretFlags       secret_flags;
+        GVariant *                 val;
+        GVariantBuilder            peer_builder;
+
+        g_variant_builder_init(&peer_builder, G_VARIANT_TYPE("a{sv}"));
+
+        while (g_variant_iter_next(peer_iter, "{&sv}", &key, &val)) {
+            _nm_unused gs_unref_variant GVariant *val_free = val;
+
+            if (nm_streq(key, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) {
+                if (!preshared_key && g_variant_is_of_type(val, G_VARIANT_TYPE_STRING))
+                    preshared_key = g_variant_ref(val);
+                continue;
+            }
+
+            if (nm_streq(key, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY)) {
+                if (!pd && g_variant_is_of_type(val, G_VARIANT_TYPE_STRING))
+                    pd = _peers_get_by_public_key(priv, g_variant_get_string(val, NULL), TRUE);
+            } else if (remove_non_secrets)
+                continue;
+
+            g_variant_builder_add(&peer_builder, "{sv}", key, val);
+        }
+
+        if (pd && preshared_key) {
+            /* without specifying a public-key of an existing peer, the secret is
+             * ignored. */
+            secret_flags = nm_wireguard_peer_get_preshared_key_flags(pd->peer);
+            if (callback(secret_flags, callback_data))
+                g_variant_builder_add(&peer_builder,
+                                      "{sv}",
+                                      NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY,
+                                      preshared_key);
+        }
+
+        g_variant_builder_add(&peers_builder, "a{sv}", &peer_builder);
+    }
+
+    g_variant_builder_add(setting_builder,
+                          "{sv}",
+                          NM_SETTING_WIREGUARD_PEERS,
+                          g_variant_builder_end(&peers_builder));
+}
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMSettingWireGuard *       setting = NM_SETTING_WIREGUARD(object);
+    NMSettingWireGuardPrivate *priv    = NM_SETTING_WIREGUARD_GET_PRIVATE(setting);
+
+    switch (prop_id) {
+    case PROP_FWMARK:
+        g_value_set_uint(value, priv->fwmark);
+        break;
+    case PROP_IP4_AUTO_DEFAULT_ROUTE:
+        g_value_set_enum(value, priv->ip4_auto_default_route);
+        break;
+    case PROP_IP6_AUTO_DEFAULT_ROUTE:
+        g_value_set_enum(value, priv->ip6_auto_default_route);
+        break;
+    case PROP_LISTEN_PORT:
+        g_value_set_uint(value, priv->listen_port);
+        break;
+    case PROP_MTU:
+        g_value_set_uint(value, priv->mtu);
+        break;
+    case PROP_PEER_ROUTES:
+        g_value_set_boolean(value, priv->peer_routes);
+        break;
+    case PROP_PRIVATE_KEY:
+        g_value_set_string(value, priv->private_key);
+        break;
+    case PROP_PRIVATE_KEY_FLAGS:
+        g_value_set_flags(value, priv->private_key_flags);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(object);
+    const char *               str;
+
+    switch (prop_id) {
+    case PROP_FWMARK:
+        priv->fwmark = g_value_get_uint(value);
+        break;
+    case PROP_IP4_AUTO_DEFAULT_ROUTE:
+        priv->ip4_auto_default_route = g_value_get_enum(value);
+        break;
+    case PROP_IP6_AUTO_DEFAULT_ROUTE:
+        priv->ip6_auto_default_route = g_value_get_enum(value);
+        break;
+    case PROP_LISTEN_PORT:
+        priv->listen_port = g_value_get_uint(value);
+        break;
+    case PROP_MTU:
+        priv->mtu = g_value_get_uint(value);
+        break;
+    case PROP_PEER_ROUTES:
+        priv->peer_routes = g_value_get_boolean(value);
+        break;
+    case PROP_PRIVATE_KEY:
+        nm_clear_pointer(&priv->private_key, nm_free_secret);
+        str = g_value_get_string(value);
+        if (str) {
+            if (nm_utils_base64secret_normalize(str,
+                                                NM_WIREGUARD_PUBLIC_KEY_LEN,
+                                                &priv->private_key))
+                priv->private_key_valid = TRUE;
+            else {
+                priv->private_key       = g_strdup(str);
+                priv->private_key_valid = FALSE;
+            }
+        }
+        break;
+    case PROP_PRIVATE_KEY_FLAGS:
+        priv->private_key_flags = g_value_get_flags(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_setting_wireguard_init(NMSettingWireGuard *setting)
+{
+    NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting);
+
+    priv->peers_arr              = g_ptr_array_new();
+    priv->peers_hash             = g_hash_table_new(nm_pstr_hash, nm_pstr_equal);
+    priv->peer_routes            = TRUE;
+    priv->ip4_auto_default_route = NM_TERNARY_DEFAULT;
+    priv->ip6_auto_default_route = NM_TERNARY_DEFAULT;
+}
+
+/**
+ * nm_setting_wireguard_new:
+ *
+ * Creates a new #NMSettingWireGuard object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingWireGuard object
+ *
+ * Since: 1.16
+ **/
+NMSetting *
+nm_setting_wireguard_new(void)
+{
+    return g_object_new(NM_TYPE_SETTING_WIREGUARD, NULL);
+}
+
+static void
+finalize(GObject *object)
+{
+    NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(object);
+
+    nm_free_secret(priv->private_key);
+
+    _peers_clear(priv);
+    g_ptr_array_unref(priv->peers_arr);
+    g_hash_table_unref(priv->peers_hash);
+
+    G_OBJECT_CLASS(nm_setting_wireguard_parent_class)->finalize(object);
+}
+
+static void
+nm_setting_wireguard_class_init(NMSettingWireGuardClass *klass)
+{
+    GObjectClass *  object_class        = G_OBJECT_CLASS(klass);
+    NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
+    GArray *        properties_override = _nm_sett_info_property_override_create_array();
+
+    object_class->get_property = get_property;
+    object_class->set_property = set_property;
+    object_class->finalize     = finalize;
+
+    setting_class->verify                    = verify;
+    setting_class->verify_secrets            = verify_secrets;
+    setting_class->need_secrets              = need_secrets;
+    setting_class->clear_secrets             = clear_secrets;
+    setting_class->update_one_secret         = update_one_secret;
+    setting_class->compare_property          = compare_property;
+    setting_class->duplicate_copy_properties = duplicate_copy_properties;
+    setting_class->enumerate_values          = enumerate_values;
+    setting_class->aggregate                 = aggregate;
+    setting_class->get_secret_flags          = get_secret_flags;
+    setting_class->set_secret_flags          = set_secret_flags;
+    setting_class->for_each_secret           = for_each_secret;
+
+    /**
+     * NMSettingWireGuard:private-key:
+     *
+     * The 256 bit private-key in base64 encoding.
+     *
+     * Since: 1.16
+     **/
+    obj_properties[PROP_PRIVATE_KEY] =
+        g_param_spec_string(NM_SETTING_WIREGUARD_PRIVATE_KEY,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingWireGuard:private-key-flags:
+     *
+     * Flags indicating how to handle the #NMSettingWirelessSecurity:private-key
+     * property.
+     *
+     * Since: 1.16
+     **/
+    obj_properties[PROP_PRIVATE_KEY_FLAGS] =
+        g_param_spec_flags(NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS,
+                           "",
+                           "",
+                           NM_TYPE_SETTING_SECRET_FLAGS,
+                           NM_SETTING_SECRET_FLAG_NONE,
+                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingWireGuard:fwmark:
+     *
+     * The use of fwmark is optional and is by default off. Setting it to 0
+     * disables it. Otherwise, it is a 32-bit fwmark for outgoing packets.
+     *
+     * Note that "ip4-auto-default-route" or "ip6-auto-default-route" enabled,
+     * implies to automatically choose a fwmark.
+     *
+     * Since: 1.16
+     **/
+    obj_properties[PROP_FWMARK] =
+        g_param_spec_uint(NM_SETTING_WIREGUARD_FWMARK,
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT32,
+                          0,
+                          G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingWireGuard:listen-port:
+     *
+     * The listen-port. If listen-port is not specified, the port will be chosen
+     * randomly when the interface comes up.
+     *
+     * Since: 1.16
+     **/
+    obj_properties[PROP_LISTEN_PORT] =
+        g_param_spec_uint(NM_SETTING_WIREGUARD_LISTEN_PORT,
+                          "",
+                          "",
+                          0,
+                          65535,
+                          0,
+                          G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingWireGuard:peer-routes:
+     *
+     * Whether to automatically add routes for the AllowedIPs ranges
+     * of the peers. If %TRUE (the default), NetworkManager will automatically
+     * add routes in the routing tables according to ipv4.route-table and
+     * ipv6.route-table. Usually you want this automatism enabled.
+     * If %FALSE, no such routes are added automatically. In this case, the
+     * user may want to configure static routes in ipv4.routes and ipv6.routes,
+     * respectively.
+     *
+     * Note that if the peer's AllowedIPs is "0.0.0.0/0" or "::/0" and the profile's
+     * ipv4.never-default or ipv6.never-default setting is enabled, the peer route for
+     * this peer won't be added automatically.
+     *
+     * Since: 1.16
+     **/
+    _nm_setting_property_define_boolean(properties_override,
+                                        obj_properties,
+                                        NM_SETTING_WIREGUARD_PEER_ROUTES,
+                                        PROP_PEER_ROUTES,
+                                        TRUE,
+                                        NM_SETTING_PARAM_INFERRABLE,
+                                        nm_setting_wireguard_get_peer_routes);
+
+    /**
+     * NMSettingWireGuard:mtu:
+     *
+     * If non-zero, only transmit packets of the specified size or smaller,
+     * breaking larger packets up into multiple fragments.
+     *
+     * If zero a default MTU is used. Note that contrary to wg-quick's MTU
+     * setting, this does not take into account the current routes at the
+     * time of activation.
+     *
+     * Since: 1.16
+     **/
+    obj_properties[PROP_MTU] =
+        g_param_spec_uint(NM_SETTING_WIREGUARD_MTU,
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT32,
+                          0,
+                          G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingWireGuard:ip4-auto-default-route:
+     *
+     * Whether to enable special handling of the IPv4 default route.
+     * If enabled, the IPv4 default route from wireguard.peer-routes
+     * will be placed to a dedicated routing-table and two policy routing rules
+     * will be added. The fwmark number is also used as routing-table for the default-route,
+     * and if fwmark is zero, an unused fwmark/table is chosen automatically.
+     * This corresponds to what wg-quick does with Table=auto and what WireGuard
+     * calls "Improved Rule-based Routing".
+     *
+     * Note that for this automatism to work, you usually don't want to set
+     * ipv4.gateway, because that will result in a conflicting default route.
+     *
+     * Leaving this at the default will enable this option automatically
+     * if ipv4.never-default is not set and there are any peers that use
+     * a default-route as allowed-ips.
+     *
+     * Since: 1.20
+     **/
+    obj_properties[PROP_IP4_AUTO_DEFAULT_ROUTE] = g_param_spec_enum(
+        NM_SETTING_WIREGUARD_IP4_AUTO_DEFAULT_ROUTE,
+        "",
+        "",
+        NM_TYPE_TERNARY,
+        NM_TERNARY_DEFAULT,
+        NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingWireGuard:ip6-auto-default-route:
+     *
+     * Like ip4-auto-default-route, but for the IPv6 default route.
+     *
+     * Since: 1.20
+     **/
+    obj_properties[PROP_IP6_AUTO_DEFAULT_ROUTE] = g_param_spec_enum(
+        NM_SETTING_WIREGUARD_IP6_AUTO_DEFAULT_ROUTE,
+        "",
+        "",
+        NM_TYPE_TERNARY,
+        NM_TERNARY_DEFAULT,
+        NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    /* ---dbus---
+     * property: peers
+     * format: array of 'a{sv}'
+     * description: Array of dictionaries for the WireGuard peers.
+     * ---end---
+     */
+    _nm_properties_override_dbus(
+        properties_override,
+        NM_SETTING_WIREGUARD_PEERS,
+        NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"),
+                                       .to_dbus_fcn   = _peers_dbus_only_synth,
+                                       .from_dbus_fcn = _peers_dbus_only_set, ));
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+    _nm_setting_class_commit_full(setting_class,
+                                  NM_META_SETTING_TYPE_WIREGUARD,
+                                  NULL,
+                                  properties_override);
+}