summary refs log tree commit diff
path: root/src/nmtui
diff options
context:
space:
mode:
Diffstat (limited to 'src/nmtui')
-rw-r--r--src/nmtui/meson.build4
-rw-r--r--src/nmtui/nm-editor-bindings.c223
-rw-r--r--src/nmtui/nm-editor-bindings.h50
-rw-r--r--src/nmtui/nm-editor-utils.c27
-rw-r--r--src/nmtui/nmt-connect-connection-list.c1
-rw-r--r--src/nmtui/nmt-editor.c6
-rw-r--r--src/nmtui/nmt-editor.h2
-rw-r--r--src/nmtui/nmt-page-bond-port.c64
-rw-r--r--src/nmtui/nmt-page-bond-port.h36
-rw-r--r--src/nmtui/nmt-page-bond.c15
-rw-r--r--src/nmtui/nmt-page-bridge-port.c8
-rw-r--r--src/nmtui/nmt-page-bridge.c7
-rw-r--r--src/nmtui/nmt-page-dsl.c8
-rw-r--r--src/nmtui/nmt-page-ethernet.c7
-rw-r--r--src/nmtui/nmt-page-infiniband.c8
-rw-r--r--src/nmtui/nmt-page-ip-tunnel.c7
-rw-r--r--src/nmtui/nmt-page-ip4.c11
-rw-r--r--src/nmtui/nmt-page-ip6.c11
-rw-r--r--src/nmtui/nmt-page-team-port.c10
-rw-r--r--src/nmtui/nmt-page-team.c9
-rw-r--r--src/nmtui/nmt-page-vlan.c19
-rw-r--r--src/nmtui/nmt-page-wifi.c13
-rw-r--r--src/nmtui/nmt-page-wireguard.c121
-rw-r--r--src/nmtui/nmt-page-wireguard.h34
-rw-r--r--src/nmtui/nmt-wireguard-peer-editor.c247
-rw-r--r--src/nmtui/nmt-wireguard-peer-editor.h35
-rw-r--r--src/nmtui/nmt-wireguard-peer-list.c451
-rw-r--r--src/nmtui/nmt-wireguard-peer-list.h42
-rw-r--r--src/nmtui/nmtui.c2
29 files changed, 1390 insertions, 88 deletions
diff --git a/src/nmtui/meson.build b/src/nmtui/meson.build
index fe440175..e6cac268 100644
--- a/src/nmtui/meson.build
+++ b/src/nmtui/meson.build
@@ -18,6 +18,7 @@ executable(
     'nmt-mac-entry.c',
     'nmt-mtu-entry.c',
     'nmt-page-bond.c',
+    'nmt-page-bond-port.c',
     'nmt-page-bridge.c',
     'nmt-page-bridge-port.c',
     'nmt-page-dsl.c',
@@ -31,12 +32,15 @@ executable(
     'nmt-page-team-port.c',
     'nmt-page-vlan.c',
     'nmt-page-wifi.c',
+    'nmt-page-wireguard.c',
     'nmt-password-dialog.c',
     'nmt-password-fields.c',
     'nmt-route-editor.c',
     'nmt-route-entry.c',
     'nmt-route-table.c',
     'nmt-slave-list.c',
+    'nmt-wireguard-peer-list.c',
+    'nmt-wireguard-peer-editor.c',
     'nmtui.c',
     'nmtui-connect.c',
     'nmtui-edit.c',
diff --git a/src/nmtui/nm-editor-bindings.c b/src/nmtui/nm-editor-bindings.c
index 2b1c4748..59474fba 100644
--- a/src/nmtui/nm-editor-bindings.c
+++ b/src/nmtui/nm-editor-bindings.c
@@ -576,6 +576,229 @@ nm_editor_bind_ip_route_to_strings(int           addr_family,
                                 NULL);
 }
 
+gboolean
+peer_transform_to_public_key_string(GBinding *    binding,
+                                    const GValue *source_value,
+                                    GValue *      target_value,
+                                    gpointer      user_data)
+{
+    NMWireGuardPeer *peer;
+    char *           string;
+
+    peer = g_value_get_boxed(source_value);
+    if (peer && nm_wireguard_peer_get_public_key(peer)) {
+        string = g_strdup(nm_wireguard_peer_get_public_key(peer));
+        g_value_take_string(target_value, string);
+    } else
+        g_value_set_string(target_value, "");
+    return TRUE;
+}
+
+gboolean
+peer_transform_to_allowed_ips_string(GBinding *    binding,
+                                     const GValue *source_value,
+                                     GValue *      target_value,
+                                     gpointer      user_data)
+{
+    NMWireGuardPeer *peer;
+    GString *        string = NULL;
+    guint            i, len;
+
+    peer = g_value_get_boxed(source_value);
+    if (!peer)
+        return TRUE;
+
+    len = nm_wireguard_peer_get_allowed_ips_len(peer);
+    for (i = 0; i < len; i++) {
+        if (!string)
+            string = g_string_new("");
+        else
+            g_string_append_c(string, ',');
+        g_string_append(string, nm_wireguard_peer_get_allowed_ip(peer, i, NULL));
+    }
+    if (string)
+        g_value_take_string(target_value, g_string_free(string, FALSE));
+
+    return TRUE;
+}
+
+gboolean
+peer_transform_to_endpoint_string(GBinding *    binding,
+                                  const GValue *source_value,
+                                  GValue *      target_value,
+                                  gpointer      user_data)
+{
+    NMWireGuardPeer *peer;
+    char *           string;
+
+    peer = g_value_get_boxed(source_value);
+    if (peer && nm_wireguard_peer_get_endpoint(peer)) {
+        string = g_strdup_printf("%s", nm_wireguard_peer_get_endpoint(peer));
+        g_value_take_string(target_value, string);
+    } else
+        g_value_set_string(target_value, "");
+    return TRUE;
+}
+
+gboolean
+peer_transform_to_preshared_key_string(GBinding *    binding,
+                                       const GValue *source_value,
+                                       GValue *      target_value,
+                                       gpointer      user_data)
+{
+    NMWireGuardPeer *peer;
+    char *           string;
+
+    peer = g_value_get_boxed(source_value);
+    if (peer && nm_wireguard_peer_get_preshared_key(peer)) {
+        string = g_strdup_printf("%s", nm_wireguard_peer_get_preshared_key(peer));
+        g_value_take_string(target_value, string);
+    } else
+        g_value_set_string(target_value, "");
+    return TRUE;
+}
+
+gboolean
+peer_transform_to_persistent_keepalive_string(GBinding *    binding,
+                                              const GValue *source_value,
+                                              GValue *      target_value,
+                                              gpointer      user_data)
+{
+    NMWireGuardPeer *peer;
+    char *           string;
+
+    peer = g_value_get_boxed(source_value);
+    if (peer && nm_wireguard_peer_get_persistent_keepalive(peer)) {
+        string = g_strdup_printf("%d", nm_wireguard_peer_get_persistent_keepalive(peer));
+        g_value_take_string(target_value, string);
+    } else
+        g_value_set_string(target_value, "");
+    return TRUE;
+}
+
+gboolean
+peer_transform_from_public_key_string(GBinding *    binding,
+                                      const GValue *source_value,
+                                      GValue *      target_value,
+                                      gpointer      user_data)
+{
+    NMWireGuardPeer *peer;
+    const char *     text;
+
+    text = g_value_get_string(source_value);
+
+    /* Fetch the original property value */
+    g_object_get(g_binding_get_source(binding),
+                 g_binding_get_source_property(binding),
+                 &peer,
+                 NULL);
+
+    nm_wireguard_peer_set_public_key(peer, text, TRUE);
+
+    g_value_take_boxed(target_value, peer);
+    return TRUE;
+}
+
+gboolean
+peer_transform_from_allowed_ips_string(GBinding *    binding,
+                                       const GValue *source_value,
+                                       GValue *      target_value,
+                                       gpointer      user_data)
+{
+    NMWireGuardPeer *peer;
+    const char *     text;
+    char **          strv;
+    guint            i;
+
+    text = g_value_get_string(source_value);
+
+    /* Fetch the original property value */
+    g_object_get(g_binding_get_source(binding),
+                 g_binding_get_source_property(binding),
+                 &peer,
+                 NULL);
+
+    nm_wireguard_peer_clear_allowed_ips(peer);
+
+    strv = g_strsplit(text, ",", -1);
+    for (i = 0; strv && strv[i]; i++)
+        nm_wireguard_peer_append_allowed_ip(peer, g_strstrip(strv[i]), TRUE);
+    g_strfreev(strv);
+
+    g_value_take_boxed(target_value, peer);
+    return TRUE;
+}
+
+gboolean
+peer_transform_from_endpoint_string(GBinding *    binding,
+                                    const GValue *source_value,
+                                    GValue *      target_value,
+                                    gpointer      user_data)
+{
+    NMWireGuardPeer *peer;
+    const char *     text;
+
+    text = g_value_get_string(source_value);
+
+    /* Fetch the original property value */
+    g_object_get(g_binding_get_source(binding),
+                 g_binding_get_source_property(binding),
+                 &peer,
+                 NULL);
+
+    nm_wireguard_peer_set_endpoint(peer, text, TRUE);
+
+    g_value_take_boxed(target_value, peer);
+    return TRUE;
+}
+
+gboolean
+peer_transform_from_preshared_key_string(GBinding *    binding,
+                                         const GValue *source_value,
+                                         GValue *      target_value,
+                                         gpointer      user_data)
+{
+    NMWireGuardPeer *peer;
+    const char *     text;
+
+    text = g_value_get_string(source_value);
+
+    /* Fetch the original property value */
+    g_object_get(g_binding_get_source(binding),
+                 g_binding_get_source_property(binding),
+                 &peer,
+                 NULL);
+
+    nm_wireguard_peer_set_preshared_key(peer, text && text[0] ? text : NULL, TRUE);
+    nm_wireguard_peer_set_preshared_key_flags(peer, NM_SETTING_SECRET_FLAG_NONE);
+
+    g_value_take_boxed(target_value, peer);
+    return TRUE;
+}
+
+gboolean
+peer_transform_from_persistent_keepalive_string(GBinding *    binding,
+                                                const GValue *source_value,
+                                                GValue *      target_value,
+                                                gpointer      user_data)
+{
+    NMWireGuardPeer *peer;
+    const char *     text;
+
+    text = g_value_get_string(source_value);
+
+    /* Fetch the original property value */
+    g_object_get(g_binding_get_source(binding),
+                 g_binding_get_source_property(binding),
+                 &peer,
+                 NULL);
+
+    nm_wireguard_peer_set_persistent_keepalive(peer, atoi(text));
+
+    g_value_take_boxed(target_value, peer);
+    return TRUE;
+}
+
 /* Wireless security method binding */
 typedef struct {
     NMConnection *             connection;
diff --git a/src/nmtui/nm-editor-bindings.h b/src/nmtui/nm-editor-bindings.h
index 027a59a9..a9bf51ed 100644
--- a/src/nmtui/nm-editor-bindings.h
+++ b/src/nmtui/nm-editor-bindings.h
@@ -53,4 +53,54 @@ void nm_editor_bind_wireless_security_wep_key(NMSettingWirelessSecurity *s_wsec,
 
 void nm_editor_bind_vlan_name(NMSettingVlan *s_vlan, NMSettingConnection *s_con);
 
+gboolean peer_transform_to_public_key_string(GBinding *    binding,
+                                             const GValue *source_value,
+                                             GValue *      target_value,
+                                             gpointer      user_data);
+
+gboolean peer_transform_to_allowed_ips_string(GBinding *    binding,
+                                              const GValue *source_value,
+                                              GValue *      target_value,
+                                              gpointer      user_data);
+
+gboolean peer_transform_to_endpoint_string(GBinding *    binding,
+                                           const GValue *source_value,
+                                           GValue *      target_value,
+                                           gpointer      user_data);
+
+gboolean peer_transform_to_preshared_key_string(GBinding *    binding,
+                                                const GValue *source_value,
+                                                GValue *      target_value,
+                                                gpointer      user_data);
+
+gboolean peer_transform_to_persistent_keepalive_string(GBinding *    binding,
+                                                       const GValue *source_value,
+                                                       GValue *      target_value,
+                                                       gpointer      user_data);
+
+gboolean peer_transform_from_public_key_string(GBinding *    binding,
+                                               const GValue *source_value,
+                                               GValue *      target_value,
+                                               gpointer      user_data);
+
+gboolean peer_transform_from_allowed_ips_string(GBinding *    binding,
+                                                const GValue *source_value,
+                                                GValue *      target_value,
+                                                gpointer      user_data);
+
+gboolean peer_transform_from_endpoint_string(GBinding *    binding,
+                                             const GValue *source_value,
+                                             GValue *      target_value,
+                                             gpointer      user_data);
+
+gboolean peer_transform_from_preshared_key_string(GBinding *    binding,
+                                                  const GValue *source_value,
+                                                  GValue *      target_value,
+                                                  gpointer      user_data);
+
+gboolean peer_transform_from_persistent_keepalive_string(GBinding *    binding,
+                                                         const GValue *source_value,
+                                                         GValue *      target_value,
+                                                         gpointer      user_data);
+
 #endif /* NM_EDITOR_BINDINGS_H */
diff --git a/src/nmtui/nm-editor-utils.c b/src/nmtui/nm-editor-utils.c
index c6b03bd8..2b6acbbe 100644
--- a/src/nmtui/nm-editor-utils.c
+++ b/src/nmtui/nm-editor-utils.c
@@ -16,7 +16,7 @@
 
 #include "nm-editor-utils.h"
 #if 0
-    #include "libnmc-base/nm-vpn-helpers.h"
+#include "libnmc-base/nm-vpn-helpers.h"
 
 static GSList *vpn_plugins;
 
@@ -66,6 +66,22 @@ bond_connection_setup_func(NMConnection *connection, NMSettingConnection *s_con,
     }
 }
 
+static void
+wireguard_connection_setup_func(NMConnection *       connection,
+                                NMSettingConnection *s_con,
+                                NMSetting *          s_hw)
+{
+    NMSettingIPConfig *s_ip;
+
+    s_ip = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_object_set(s_ip, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL);
+    nm_connection_add_setting(connection, (NMSetting *) s_ip);
+
+    s_ip = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    g_object_set(s_ip, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DISABLED, NULL);
+    nm_connection_add_setting(connection, (NMSetting *) s_ip);
+}
+
 typedef void (*NMEditorNewConnectionSetupFunc)(NMConnection *       connection,
                                                NMSettingConnection *s_con,
                                                NMSetting *          s_hw);
@@ -243,6 +259,15 @@ nm_editor_utils_get_connection_type_list(void)
     }
 #endif
 
+    item                        = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name             = _("WireGuard");
+    item->data.setting_type     = NM_TYPE_SETTING_WIREGUARD;
+    item->data.device_type      = NM_TYPE_DEVICE_WIREGUARD;
+    item->data.virtual          = TRUE;
+    item->id_format             = _("WireGuard connection %d");
+    item->connection_setup_func = wireguard_connection_setup_func;
+    g_ptr_array_add(array, item);
+
     g_ptr_array_sort(array, sort_types);
     g_ptr_array_add(array, NULL);
 
diff --git a/src/nmtui/nmt-connect-connection-list.c b/src/nmtui/nmt-connect-connection-list.c
index 4503dc92..486e1400 100644
--- a/src/nmtui/nmt-connect-connection-list.c
+++ b/src/nmtui/nmt-connect-connection-list.c
@@ -100,6 +100,7 @@ static const char *device_sort_order[]   = {"NMDeviceEthernet",
                                           NM_SETTING_TEAM_SETTING_NAME,
                                           NM_SETTING_BRIDGE_SETTING_NAME,
                                           NM_SETTING_IP_TUNNEL_SETTING_NAME,
+                                          NM_SETTING_WIREGUARD_SETTING_NAME,
                                           "NMDeviceModem",
                                           "NMDeviceBt"};
 static const int   device_sort_order_len = G_N_ELEMENTS(device_sort_order);
diff --git a/src/nmtui/nmt-editor.c b/src/nmtui/nmt-editor.c
index 0ef23693..94e9b7a8 100644
--- a/src/nmtui/nmt-editor.c
+++ b/src/nmtui/nmt-editor.c
@@ -26,6 +26,7 @@
 #include "nmt-mtu-entry.h"
 
 #include "nmt-page-bond.h"
+#include "nmt-page-bond-port.h"
 #include "nmt-page-bridge.h"
 #include "nmt-page-bridge-port.h"
 #include "nmt-page-dsl.h"
@@ -39,6 +40,7 @@
 #include "nmt-page-team-port.h"
 #include "nmt-page-vlan.h"
 #include "nmt-page-wifi.h"
+#include "nmt-page-wireguard.h"
 
 #include "libnmc-setting/nm-meta-setting-access.h"
 
@@ -372,6 +374,8 @@ nmt_editor_constructed(GObject *object)
         page = nmt_page_wifi_new(priv->edit_connection, deventry);
     else if (nm_connection_is_type(priv->edit_connection, NM_SETTING_IP_TUNNEL_SETTING_NAME))
         page = nmt_page_ip_tunnel_new(priv->edit_connection, deventry);
+    else if (nm_connection_is_type(priv->edit_connection, NM_SETTING_WIREGUARD_SETTING_NAME))
+        page = nmt_page_wireguard_new(priv->edit_connection, deventry);
     else
         g_assert_not_reached();
 
@@ -384,6 +388,8 @@ nmt_editor_constructed(GObject *object)
             add_sections_for_page(editor, grid, nmt_page_bridge_port_new(priv->edit_connection));
         else if (!strcmp(slave_type, NM_SETTING_TEAM_SETTING_NAME))
             add_sections_for_page(editor, grid, nmt_page_team_port_new(priv->edit_connection));
+        else if (nm_streq(slave_type, NM_SETTING_BOND_SETTING_NAME))
+            add_sections_for_page(editor, grid, nmt_page_bond_port_new(priv->edit_connection));
     } else {
         NmtNewtWidget *section;
 
diff --git a/src/nmtui/nmt-editor.h b/src/nmtui/nmt-editor.h
index 49fe89d3..602a61ae 100644
--- a/src/nmtui/nmt-editor.h
+++ b/src/nmtui/nmt-editor.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
diff --git a/src/nmtui/nmt-page-bond-port.c b/src/nmtui/nmt-page-bond-port.c
new file mode 100644
index 00000000..156553c2
--- /dev/null
+++ b/src/nmtui/nmt-page-bond-port.c
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-bond-port
+ * @short_description: The editor page for Bond ports
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-bond-port.h"
+
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+
+G_DEFINE_TYPE(NmtPageBondPort, nmt_page_bond_port, NMT_TYPE_EDITOR_PAGE)
+
+static void
+nmt_page_bond_port_init(NmtPageBondPort *bond)
+{}
+
+NmtEditorPage *
+nmt_page_bond_port_new(NMConnection *conn)
+{
+    return g_object_new(NMT_TYPE_PAGE_BOND_PORT, "connection", conn, NULL);
+}
+
+static void
+nmt_page_bond_port_constructed(GObject *object)
+{
+    NmtPageBondPort *  bond = NMT_PAGE_BOND_PORT(object);
+    NmtEditorSection * section;
+    NmtEditorGrid *    grid;
+    NMSettingBondPort *s_port;
+    NmtNewtWidget *    widget;
+    NMConnection *     conn;
+
+    conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(bond));
+    s_port = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_BOND_PORT);
+
+    section = nmt_editor_section_new(_("BOND PORT"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_newt_entry_numeric_new(10, 0, 63);
+    g_object_bind_property(s_port,
+                           NM_SETTING_BOND_PORT_QUEUE_ID,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Queue ID"), widget, NULL);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(bond), section);
+
+    G_OBJECT_CLASS(nmt_page_bond_port_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_bond_port_class_init(NmtPageBondPortClass *bond_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(bond_class);
+
+    object_class->constructed = nmt_page_bond_port_constructed;
+}
diff --git a/src/nmtui/nmt-page-bond-port.h b/src/nmtui/nmt-page-bond-port.h
new file mode 100644
index 00000000..387acf4c
--- /dev/null
+++ b/src/nmtui/nmt-page-bond-port.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_BOND_PORT_H
+#define NMT_PAGE_BOND_PORT_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_BOND_PORT (nmt_page_bond_port_get_type())
+#define NMT_PAGE_BOND_PORT(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_BOND_PORT, NmtPageBondPort))
+#define NMT_PAGE_BOND_PORT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_BOND_PORT, NmtPageBondPortClass))
+#define NMT_IS_PAGE_BOND_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_BOND_PORT))
+#define NMT_IS_PAGE_BOND_PORT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_BOND_PORT))
+#define NMT_PAGE_BOND_PORT_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_BOND_PORT, NmtPageBondPortClass))
+
+typedef struct {
+    NmtEditorPage parent;
+
+} NmtPageBondPort;
+
+typedef struct {
+    NmtEditorPageClass parent;
+
+} NmtPageBondPortClass;
+
+GType nmt_page_bond_port_get_type(void);
+
+NmtEditorPage *nmt_page_bond_port_new(NMConnection *conn);
+
+#endif /* NMT_PAGE_BOND_PORT_H */
diff --git a/src/nmtui/nmt-page-bond.c b/src/nmtui/nmt-page-bond.c
index d3fab1ff..aea7949e 100644
--- a/src/nmtui/nmt-page-bond.c
+++ b/src/nmtui/nmt-page-bond.c
@@ -334,20 +334,11 @@ nmt_page_bond_constructed(GObject *object)
     NmtNewtWidget *     widget, *label;
     NMConnection *      conn;
 
-    conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(bond));
-    s_bond = nm_connection_get_setting_bond(conn);
-    if (!s_bond) {
-        nm_connection_add_setting(conn, nm_setting_bond_new());
-        s_bond = nm_connection_get_setting_bond(conn);
-    }
+    conn         = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(bond));
+    s_bond       = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_BOND);
     priv->s_bond = s_bond;
 
-    s_wired = nm_connection_get_setting_wired(conn);
-    if (!s_wired) {
-        nm_connection_add_setting(conn, nm_setting_wired_new());
-        s_wired = nm_connection_get_setting_wired(conn);
-    }
-
+    s_wired = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_WIRED);
     section = nmt_editor_section_new(_("BOND"), NULL, TRUE);
     grid    = nmt_editor_section_get_body(section);
 
diff --git a/src/nmtui/nmt-page-bridge-port.c b/src/nmtui/nmt-page-bridge-port.c
index 30163d15..f6c18d7c 100644
--- a/src/nmtui/nmt-page-bridge-port.c
+++ b/src/nmtui/nmt-page-bridge-port.c
@@ -12,6 +12,8 @@
 
 #include "nmt-page-bridge-port.h"
 
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+
 G_DEFINE_TYPE(NmtPageBridgePort, nmt_page_bridge_port, NMT_TYPE_EDITOR_PAGE)
 
 NmtEditorPage *
@@ -35,11 +37,7 @@ nmt_page_bridge_port_constructed(GObject *object)
     NMConnection *       conn;
 
     conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(bridge));
-    s_port = nm_connection_get_setting_bridge_port(conn);
-    if (!s_port) {
-        nm_connection_add_setting(conn, nm_setting_bridge_port_new());
-        s_port = nm_connection_get_setting_bridge_port(conn);
-    }
+    s_port = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_BRIDGE_PORT);
 
     section = nmt_editor_section_new(_("BRIDGE PORT"), NULL, TRUE);
     grid    = nmt_editor_section_get_body(section);
diff --git a/src/nmtui/nmt-page-bridge.c b/src/nmtui/nmt-page-bridge.c
index 52af8db1..684b17bd 100644
--- a/src/nmtui/nmt-page-bridge.c
+++ b/src/nmtui/nmt-page-bridge.c
@@ -12,6 +12,7 @@
 
 #include "nmt-page-bridge.h"
 
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-address-list.h"
 #include "nmt-slave-list.h"
 
@@ -53,11 +54,7 @@ nmt_page_bridge_constructed(GObject *object)
     NMConnection *        conn;
 
     conn     = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(bridge));
-    s_bridge = nm_connection_get_setting_bridge(conn);
-    if (!s_bridge) {
-        nm_connection_add_setting(conn, nm_setting_bridge_new());
-        s_bridge = nm_connection_get_setting_bridge(conn);
-    }
+    s_bridge = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_BRIDGE);
 
     section = nmt_editor_section_new(_("BRIDGE"), NULL, TRUE);
     grid    = nmt_editor_section_get_body(section);
diff --git a/src/nmtui/nmt-page-dsl.c b/src/nmtui/nmt-page-dsl.c
index d7e0d144..a0d8cdf4 100644
--- a/src/nmtui/nmt-page-dsl.c
+++ b/src/nmtui/nmt-page-dsl.c
@@ -11,6 +11,8 @@
 #include "libnm-client-aux-extern/nm-default-client.h"
 
 #include "nmt-page-dsl.h"
+
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-page-ethernet.h"
 #include "nmt-page-ppp.h"
 #include "nmt-password-fields.h"
@@ -83,11 +85,7 @@ nmt_page_dsl_constructed(GObject *object)
     const GSList *     sections, *iter;
 
     conn    = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(dsl));
-    s_pppoe = nm_connection_get_setting_pppoe(conn);
-    if (!s_pppoe) {
-        nm_connection_add_setting(conn, nm_setting_pppoe_new());
-        s_pppoe = nm_connection_get_setting_pppoe(conn);
-    }
+    s_pppoe = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_PPPOE);
 
     section = build_dsl_section(dsl, s_pppoe);
     nmt_editor_page_add_section(NMT_EDITOR_PAGE(dsl), section);
diff --git a/src/nmtui/nmt-page-ethernet.c b/src/nmtui/nmt-page-ethernet.c
index f5dc9119..addbd3f3 100644
--- a/src/nmtui/nmt-page-ethernet.c
+++ b/src/nmtui/nmt-page-ethernet.c
@@ -15,6 +15,7 @@
 #include <linux/if_ether.h>
 #include <linux/if_infiniband.h>
 
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-mac-entry.h"
 #include "nmt-mtu-entry.h"
 
@@ -42,11 +43,7 @@ nmt_page_ethernet_constructed(GObject *object)
     NMConnection *    conn;
 
     conn    = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(ethernet));
-    s_wired = nm_connection_get_setting_wired(conn);
-    if (!s_wired) {
-        nm_connection_add_setting(conn, nm_setting_wired_new());
-        s_wired = nm_connection_get_setting_wired(conn);
-    }
+    s_wired = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_WIRED);
 
     deventry = nmt_editor_page_device_get_device_entry(NMT_EDITOR_PAGE_DEVICE(object));
     g_object_bind_property(s_wired,
diff --git a/src/nmtui/nmt-page-infiniband.c b/src/nmtui/nmt-page-infiniband.c
index c0e3611b..1216208f 100644
--- a/src/nmtui/nmt-page-infiniband.c
+++ b/src/nmtui/nmt-page-infiniband.c
@@ -11,6 +11,8 @@
 #include "libnm-client-aux-extern/nm-default-client.h"
 
 #include "nmt-page-infiniband.h"
+
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-mtu-entry.h"
 
 G_DEFINE_TYPE(NmtPageInfiniband, nmt_page_infiniband, NMT_TYPE_EDITOR_PAGE_DEVICE)
@@ -46,11 +48,7 @@ nmt_page_infiniband_constructed(GObject *object)
     NMConnection *       conn;
 
     conn = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(infiniband));
-    s_ib = nm_connection_get_setting_infiniband(conn);
-    if (!s_ib) {
-        nm_connection_add_setting(conn, nm_setting_infiniband_new());
-        s_ib = nm_connection_get_setting_infiniband(conn);
-    }
+    s_ib = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_INFINIBAND);
     /* initialize 'transport-mode' if it is NULL */
     if (!nm_setting_infiniband_get_transport_mode(s_ib)) {
         g_object_set(G_OBJECT(s_ib), NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
diff --git a/src/nmtui/nmt-page-ip-tunnel.c b/src/nmtui/nmt-page-ip-tunnel.c
index da6d12b9..96864919 100644
--- a/src/nmtui/nmt-page-ip-tunnel.c
+++ b/src/nmtui/nmt-page-ip-tunnel.c
@@ -11,6 +11,7 @@
 
 #include "nmt-page-ip-tunnel.h"
 
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-device-entry.h"
 #include "nmt-mtu-entry.h"
 
@@ -102,11 +103,7 @@ nmt_page_ip_tunnel_constructed(GObject *object)
     GClosure *              s2w, *w2s;
 
     conn        = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(ip_tunnel));
-    s_ip_tunnel = nm_connection_get_setting_ip_tunnel(conn);
-    if (!s_ip_tunnel) {
-        nm_connection_add_setting(conn, nm_setting_ip_tunnel_new());
-        s_ip_tunnel = nm_connection_get_setting_ip_tunnel(conn);
-    }
+    s_ip_tunnel = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_IP_TUNNEL);
 
     /* Initialize the mode for new connections */
     if (nm_setting_ip_tunnel_get_mode(s_ip_tunnel) == NM_IP_TUNNEL_MODE_UNKNOWN) {
diff --git a/src/nmtui/nmt-page-ip4.c b/src/nmtui/nmt-page-ip4.c
index ecd71447..a7e4bdae 100644
--- a/src/nmtui/nmt-page-ip4.c
+++ b/src/nmtui/nmt-page-ip4.c
@@ -10,9 +10,11 @@
 
 #include "libnm-client-aux-extern/nm-default-client.h"
 
+#include "nmt-page-ip4.h"
+
 #include <stdlib.h>
 
-#include "nmt-page-ip4.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-ip-entry.h"
 #include "nmt-address-list.h"
 #include "nmt-route-editor.h"
@@ -84,14 +86,13 @@ nmt_page_ip4_constructed(GObject *object)
     NMConnection *     conn;
 
     conn  = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(ip4));
-    s_ip4 = nm_connection_get_setting_ip4_config(conn);
-    if (!s_ip4) {
-        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    s_ip4 = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_IP4_CONFIG);
+    /* initialize 'method' to auto if it is NULL */
+    if (!nm_setting_ip_config_get_method(s_ip4)) {
         g_object_set(G_OBJECT(s_ip4),
                      NM_SETTING_IP_CONFIG_METHOD,
                      NM_SETTING_IP4_CONFIG_METHOD_AUTO,
                      NULL);
-        nm_connection_add_setting(conn, (NMSetting *) s_ip4);
     }
 
     widget = nmt_newt_popup_new(ip4methods);
diff --git a/src/nmtui/nmt-page-ip6.c b/src/nmtui/nmt-page-ip6.c
index f79c553d..0d4de65a 100644
--- a/src/nmtui/nmt-page-ip6.c
+++ b/src/nmtui/nmt-page-ip6.c
@@ -10,9 +10,11 @@
 
 #include "libnm-client-aux-extern/nm-default-client.h"
 
+#include "nmt-page-ip6.h"
+
 #include <stdlib.h>
 
-#include "nmt-page-ip6.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-ip-entry.h"
 #include "nmt-address-list.h"
 #include "nmt-route-editor.h"
@@ -85,14 +87,13 @@ nmt_page_ip6_constructed(GObject *object)
     NMConnection *     conn;
 
     conn  = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(ip6));
-    s_ip6 = nm_connection_get_setting_ip6_config(conn);
-    if (!s_ip6) {
-        s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    s_ip6 = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_IP6_CONFIG);
+    /* initialize 'method' to auto if it is NULL */
+    if (!nm_setting_ip_config_get_method(s_ip6)) {
         g_object_set(G_OBJECT(s_ip6),
                      NM_SETTING_IP_CONFIG_METHOD,
                      NM_SETTING_IP6_CONFIG_METHOD_AUTO,
                      NULL);
-        nm_connection_add_setting(conn, (NMSetting *) s_ip6);
     }
 
     widget = nmt_newt_popup_new(ip6methods);
diff --git a/src/nmtui/nmt-page-team-port.c b/src/nmtui/nmt-page-team-port.c
index 13bb84f0..c1f3cbc6 100644
--- a/src/nmtui/nmt-page-team-port.c
+++ b/src/nmtui/nmt-page-team-port.c
@@ -12,6 +12,8 @@
 
 #include "nmt-page-team-port.h"
 
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+
 G_DEFINE_TYPE(NmtPageTeamPort, nmt_page_team_port, NMT_TYPE_EDITOR_PAGE)
 
 #define NMT_PAGE_TEAM_PORT_GET_PRIVATE(o) \
@@ -63,12 +65,8 @@ nmt_page_team_port_constructed(GObject *object)
     NmtNewtWidget *         widget;
     NMConnection *          conn;
 
-    conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(team));
-    s_port = nm_connection_get_setting_team_port(conn);
-    if (!s_port) {
-        nm_connection_add_setting(conn, nm_setting_team_port_new());
-        s_port = nm_connection_get_setting_team_port(conn);
-    }
+    conn         = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(team));
+    s_port       = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_TEAM_PORT);
     priv->s_port = s_port;
 
     section = nmt_editor_section_new(_("TEAM PORT"), NULL, TRUE);
diff --git a/src/nmtui/nmt-page-team.c b/src/nmtui/nmt-page-team.c
index 3cda1257..d61c06c5 100644
--- a/src/nmtui/nmt-page-team.c
+++ b/src/nmtui/nmt-page-team.c
@@ -12,6 +12,7 @@
 
 #include "nmt-page-team.h"
 
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-slave-list.h"
 
 G_DEFINE_TYPE(NmtPageTeam, nmt_page_team, NMT_TYPE_EDITOR_PAGE_DEVICE)
@@ -111,12 +112,8 @@ nmt_page_team_constructed(GObject *object)
     NmtNewtWidget *     widget;
     NMConnection *      conn;
 
-    conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(team));
-    s_team = nm_connection_get_setting_team(conn);
-    if (!s_team) {
-        nm_connection_add_setting(conn, nm_setting_team_new());
-        s_team = nm_connection_get_setting_team(conn);
-    }
+    conn         = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(team));
+    s_team       = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_TEAM);
     priv->s_team = s_team;
 
     section = nmt_editor_section_new(_("TEAM"), NULL, TRUE);
diff --git a/src/nmtui/nmt-page-vlan.c b/src/nmtui/nmt-page-vlan.c
index 299a7267..5baf9756 100644
--- a/src/nmtui/nmt-page-vlan.c
+++ b/src/nmtui/nmt-page-vlan.c
@@ -10,11 +10,12 @@
 
 #include "libnm-client-aux-extern/nm-default-client.h"
 
-#include "nm-editor-bindings.h"
+#include "nmt-page-vlan.h"
 
 #include <linux/if_ether.h>
 
-#include "nmt-page-vlan.h"
+#include "nm-editor-bindings.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-device-entry.h"
 #include "nmt-mac-entry.h"
 #include "nmt-mtu-entry.h"
@@ -49,17 +50,9 @@ nmt_page_vlan_constructed(GObject *object)
     NmtNewtWidget *   widget, *parent, *id_entry;
     NMConnection *    conn;
 
-    conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(vlan));
-    s_vlan = nm_connection_get_setting_vlan(conn);
-    if (!s_vlan) {
-        nm_connection_add_setting(conn, nm_setting_vlan_new());
-        s_vlan = nm_connection_get_setting_vlan(conn);
-    }
-    s_wired = nm_connection_get_setting_wired(conn);
-    if (!s_wired) {
-        nm_connection_add_setting(conn, nm_setting_wired_new());
-        s_wired = nm_connection_get_setting_wired(conn);
-    }
+    conn    = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(vlan));
+    s_vlan  = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_VLAN);
+    s_wired = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_WIRED);
 
     section = nmt_editor_section_new(_("VLAN"), NULL, TRUE);
     grid    = nmt_editor_section_get_body(section);
diff --git a/src/nmtui/nmt-page-wifi.c b/src/nmtui/nmt-page-wifi.c
index 247a0e4e..1a476b02 100644
--- a/src/nmtui/nmt-page-wifi.c
+++ b/src/nmtui/nmt-page-wifi.c
@@ -14,10 +14,12 @@
 
 #include "libnm-client-aux-extern/nm-default-client.h"
 
+#include "nmt-page-wifi.h"
+
 #include <stdlib.h>
 #include <linux/if_ether.h>
 
-#include "nmt-page-wifi.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-mac-entry.h"
 #include "nmt-mtu-entry.h"
 #include "nmt-password-fields.h"
@@ -164,13 +166,8 @@ nmt_page_wifi_constructed(GObject *object)
     NMConnection *             conn;
 
     conn       = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(wifi));
-    s_wireless = nm_connection_get_setting_wireless(conn);
-    if (!s_wireless) {
-        nm_connection_add_setting(conn, nm_setting_wireless_new());
-        s_wireless = nm_connection_get_setting_wireless(conn);
-    }
-
-    s_wsec = nm_connection_get_setting_wireless_security(conn);
+    s_wireless = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_WIRELESS);
+    s_wsec     = nm_connection_get_setting_wireless_security(conn);
     if (!s_wsec) {
         /* It makes things simpler if we always have a
          * NMSettingWirelessSecurity; we'll hold a ref on one, and add
diff --git a/src/nmtui/nmt-page-wireguard.c b/src/nmtui/nmt-page-wireguard.c
new file mode 100644
index 00000000..30a984e0
--- /dev/null
+++ b/src/nmtui/nmt-page-wireguard.c
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ */
+/**
+ * SECTION:nmt-page-wireguard
+ * @short_description: The editor page for WireGuard connections
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-wireguard.h"
+
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+#include "nmt-device-entry.h"
+#include "nmt-mtu-entry.h"
+#include "nmt-wireguard-peer-list.h"
+
+G_DEFINE_TYPE(NmtPageWireGuard, nmt_page_wireguard, NMT_TYPE_EDITOR_PAGE_DEVICE)
+
+#define NMT_PAGE_WIREGUARD_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_WIREGUARD, NmtPageWireGuardPrivate))
+
+typedef struct {
+    NmtWireguardPeerList *peers;
+} NmtPageWireGuardPrivate;
+
+NmtEditorPage *
+nmt_page_wireguard_new(NMConnection *conn, NmtDeviceEntry *deventry)
+{
+    return g_object_new(NMT_TYPE_PAGE_WIREGUARD,
+                        "connection",
+                        conn,
+                        "device-entry",
+                        deventry,
+                        NULL);
+}
+
+static void
+nmt_page_wireguard_init(NmtPageWireGuard *wireguard)
+{}
+
+static void
+nmt_page_wireguard_constructed(GObject *object)
+{
+    NmtPageWireGuard *       wireguard = NMT_PAGE_WIREGUARD(object);
+    NmtPageWireGuardPrivate *priv      = NMT_PAGE_WIREGUARD_GET_PRIVATE(wireguard);
+    NmtEditorSection *       section;
+    NmtEditorGrid *          grid;
+    NMSettingWireGuard *     s_wireguard;
+    NmtNewtWidget *          widget;
+    NMConnection *           conn;
+
+    conn        = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(wireguard));
+    s_wireguard = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_WIREGUARD);
+
+    section = nmt_editor_section_new(_("WireGuard"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Private key"), widget, NULL);
+    g_object_bind_property(s_wireguard,
+                           NM_SETTING_WIREGUARD_PRIVATE_KEY,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Listen port"), widget, NULL);
+    g_object_bind_property(s_wireguard,
+                           NM_SETTING_WIREGUARD_LISTEN_PORT,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Fwmark"), widget, NULL);
+    g_object_bind_property(s_wireguard,
+                           NM_SETTING_WIREGUARD_FWMARK,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    widget = nmt_mtu_entry_new();
+    nmt_editor_grid_append(grid, _("MTU"), widget, NULL);
+    g_object_bind_property(s_wireguard,
+                           NM_SETTING_WIREGUARD_MTU,
+                           widget,
+                           "mtu",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    widget = nmt_newt_checkbox_new(_("Add peer routes"));
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+    g_object_bind_property(s_wireguard,
+                           NM_SETTING_WIREGUARD_PEER_ROUTES,
+                           widget,
+                           "active",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    widget = nmt_newt_separator_new();
+    nmt_editor_grid_append(grid, _("Peers"), widget, NULL);
+    nmt_editor_grid_set_row_flags(grid, widget, NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT);
+
+    widget = nmt_wireguard_peer_list_new(s_wireguard);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+    priv->peers = NMT_WIREGUARD_PEER_LIST(widget);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(wireguard), section);
+
+    G_OBJECT_CLASS(nmt_page_wireguard_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_wireguard_class_init(NmtPageWireGuardClass *wireguard_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(wireguard_class);
+
+    g_type_class_add_private(wireguard_class, sizeof(NmtPageWireGuardPrivate));
+
+    object_class->constructed = nmt_page_wireguard_constructed;
+}
diff --git a/src/nmtui/nmt-page-wireguard.h b/src/nmtui/nmt-page-wireguard.h
new file mode 100644
index 00000000..8e39139c
--- /dev/null
+++ b/src/nmtui/nmt-page-wireguard.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_WIREGUARD_H
+#define NMT_PAGE_WIREGUARD_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_WIREGUARD (nmt_page_wireguard_get_type())
+#define NMT_PAGE_WIREGUARD(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_WIREGUARD, NmtPageWireGuard))
+#define NMT_PAGE_WIREGUARD_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_WIREGUARD, NmtPageWireGuardClass))
+#define NMT_IS_PAGE_WIREGUARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_WIREGUARD))
+#define NMT_IS_PAGE_WIREGUARD_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_WIREGUARD))
+#define NMT_PAGE_WIREGUARD_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_WIREGUARD, NmtPageWireGuardClass))
+
+typedef struct {
+    NmtEditorPageDevice parent;
+} NmtPageWireGuard;
+
+typedef struct {
+    NmtEditorPageDeviceClass parent;
+} NmtPageWireGuardClass;
+
+GType nmt_page_wireguard_get_type(void);
+
+NmtEditorPage *nmt_page_wireguard_new(NMConnection *conn, NmtDeviceEntry *deventry);
+
+#endif /* NMT_PAGE_WIREGUARD_H */
diff --git a/src/nmtui/nmt-wireguard-peer-editor.c b/src/nmtui/nmt-wireguard-peer-editor.c
new file mode 100644
index 00000000..810bdf69
--- /dev/null
+++ b/src/nmtui/nmt-wireguard-peer-editor.c
@@ -0,0 +1,247 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ */
+/**
+ * SECTION:nmt-wireguard-peer-editor
+ * @short_description: The editor page for Peer connections
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-wireguard-peer-editor.h"
+#include "nmt-page-wireguard.h"
+
+#include "nmt-device-entry.h"
+#include "nmt-mtu-entry.h"
+#include "nmt-wireguard-peer-list.h"
+
+#include "nm-editor-bindings.h"
+
+G_DEFINE_TYPE(NmtWireguardPeerEditor, nmt_wireguard_peer_editor, NMT_TYPE_NEWT_FORM)
+
+#define NMT_WIREGUARD_PEER_EDITOR_GET_PRIVATE(o)                 \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o),                            \
+                                 NMT_TYPE_WIREGUARD_PEER_EDITOR, \
+                                 NmtWireguardPeerEditorPrivate))
+
+typedef struct {
+    NMSettingWireGuard *orig_setting;
+    NMSettingWireGuard *edit_setting;
+    NMWireGuardPeer *   peer;
+    NmtNewtEntry *      private_key;
+} NmtWireguardPeerEditorPrivate;
+
+enum {
+    PROP_0,
+
+    PROP_SETTING,
+    PROP_PEER,
+    PROP_PUBLIC_KEY,
+
+    LAST_PROP
+};
+
+NmtNewtForm *
+nmt_wireguard_peer_editor_new(NMSettingWireGuard *setting, NMWireGuardPeer *peer)
+{
+    return g_object_new(NMT_TYPE_WIREGUARD_PEER_EDITOR, "setting", setting, "peer", peer, NULL);
+}
+
+static void
+nmt_wireguard_peer_editor_init(NmtWireguardPeerEditor *peer)
+{}
+
+static void
+save_peer_and_exit(NmtNewtButton *button, gpointer user_data)
+{
+    NmtWireguardPeerEditor *       editor = user_data;
+    NmtWireguardPeerEditorPrivate *priv   = NMT_WIREGUARD_PEER_EDITOR_GET_PRIVATE(editor);
+
+    nm_setting_wireguard_append_peer(priv->orig_setting, priv->peer);
+
+    nmt_newt_form_quit(NMT_NEWT_FORM(editor));
+}
+
+static void
+nmt_wireguard_peer_editor_constructed(GObject *object)
+{
+    NmtWireguardPeerEditor *peer = NMT_WIREGUARD_PEER_EDITOR(object);
+    NmtEditorSection *      section;
+    NmtEditorGrid *         grid;
+    NmtNewtWidget *         widget, *label;
+    NmtNewtWidget *         buttons, *ok, *cancel;
+
+    if (G_OBJECT_CLASS(nmt_wireguard_peer_editor_parent_class)->constructed)
+        G_OBJECT_CLASS(nmt_wireguard_peer_editor_parent_class)->constructed(object);
+
+    section = nmt_editor_section_new(_("Peer"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Public key"), widget, NULL);
+    g_object_bind_property_full(object,
+                                "peer",
+                                widget,
+                                "text",
+                                G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                                peer_transform_to_public_key_string,
+                                peer_transform_from_public_key_string,
+                                NULL,
+                                NULL);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Allowed IPs"), widget, NULL);
+    g_object_bind_property_full(peer,
+                                "peer",
+                                widget,
+                                "text",
+                                G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                                peer_transform_to_allowed_ips_string,
+                                peer_transform_from_allowed_ips_string,
+                                NULL,
+                                NULL);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Endpoint"), widget, NULL);
+    g_object_bind_property_full(peer,
+                                "peer",
+                                widget,
+                                "text",
+                                G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                                peer_transform_to_endpoint_string,
+                                peer_transform_from_endpoint_string,
+                                NULL,
+                                NULL);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Preshared key"), widget, NULL);
+    g_object_bind_property_full(peer,
+                                "peer",
+                                widget,
+                                "text",
+                                G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                                peer_transform_to_preshared_key_string,
+                                peer_transform_from_preshared_key_string,
+                                NULL,
+                                NULL);
+
+    widget = nmt_newt_entry_numeric_new(10, 0, G_MAXINT);
+    label  = nmt_newt_label_new(C_("seconds", "seconds"));
+    nmt_editor_grid_append(grid, _("Persistent keepalive"), widget, label);
+    g_object_bind_property_full(peer,
+                                "peer",
+                                widget,
+                                "text",
+                                G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                                peer_transform_to_persistent_keepalive_string,
+                                peer_transform_from_persistent_keepalive_string,
+                                NULL,
+                                NULL);
+
+    buttons = nmt_newt_grid_new();
+    nmt_editor_grid_append(grid, NULL, buttons, NULL);
+    nmt_newt_widget_set_padding(buttons, 0, 1, 0, 0);
+
+    cancel = g_object_ref_sink(nmt_newt_button_new(_("Cancel")));
+    nmt_newt_widget_set_exit_on_activate(cancel, TRUE);
+    nmt_newt_grid_add(NMT_NEWT_GRID(buttons), cancel, 0, 0);
+    nmt_newt_grid_set_flags(NMT_NEWT_GRID(buttons),
+                            cancel,
+                            NMT_NEWT_GRID_EXPAND_X | NMT_NEWT_GRID_ANCHOR_RIGHT
+                                | NMT_NEWT_GRID_FILL_Y);
+
+    ok = g_object_ref_sink(nmt_newt_button_new(_("OK")));
+    g_signal_connect(ok, "clicked", G_CALLBACK(save_peer_and_exit), peer);
+    nmt_newt_grid_add(NMT_NEWT_GRID(buttons), ok, 1, 0);
+    nmt_newt_widget_set_padding(ok, 1, 0, 0, 0);
+    g_object_bind_property(NMT_NEWT_GRID(buttons), "valid", ok, "sensitive", G_BINDING_SYNC_CREATE);
+
+    nmt_newt_form_set_content(NMT_NEWT_FORM(peer), NMT_NEWT_WIDGET(section));
+}
+
+static void
+nmt_wireguard_peer_editor_set_property(GObject *     object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec *  pspec)
+{
+    NmtWireguardPeerEditorPrivate *priv = NMT_WIREGUARD_PEER_EDITOR_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_SETTING:
+        priv->orig_setting = g_value_dup_object(value);
+        priv->edit_setting =
+            NM_SETTING_WIREGUARD(nm_setting_duplicate(NM_SETTING(priv->orig_setting)));
+        break;
+    case PROP_PEER:
+        priv->peer = g_value_dup_boxed(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_wireguard_peer_editor_get_property(GObject *   object,
+                                       guint       prop_id,
+                                       GValue *    value,
+                                       GParamSpec *pspec)
+{
+    NmtWireguardPeerEditorPrivate *priv = NMT_WIREGUARD_PEER_EDITOR_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_SETTING:
+        g_value_set_object(value, priv->edit_setting);
+        break;
+    case PROP_PEER:
+        g_value_set_boxed(value, priv->peer);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_wireguard_peer_editor_class_init(NmtWireguardPeerEditorClass *peer_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(peer_class);
+
+    g_type_class_add_private(peer_class, sizeof(NmtWireguardPeerEditorPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_wireguard_peer_editor_constructed;
+    object_class->set_property = nmt_wireguard_peer_editor_set_property;
+    object_class->get_property = nmt_wireguard_peer_editor_get_property;
+
+    /* properties */
+
+    /**
+     * NmtPeerPage:setting:
+     *
+     * The page's #NMSettingWireGuard.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_SETTING,
+        g_param_spec_object("setting",
+                            "",
+                            "",
+                            NM_TYPE_SETTING_WIREGUARD,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * NmtPeerPage:peer:
+     *
+     * The page's #NMWireGuardPeer.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_PEER,
+                                    g_param_spec_boxed("peer",
+                                                       "",
+                                                       "",
+                                                       nm_wireguard_peer_get_type(),
+                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-wireguard-peer-editor.h b/src/nmtui/nmt-wireguard-peer-editor.h
new file mode 100644
index 00000000..ffe76c8c
--- /dev/null
+++ b/src/nmtui/nmt-wireguard-peer-editor.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ */
+
+#ifndef NMT_WIREGUARD_PEER_EDITOR_H
+#define NMT_WIREGUARD_PEER_EDITOR_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_WIREGUARD_PEER_EDITOR (nmt_wireguard_peer_editor_get_type())
+#define NMT_WIREGUARD_PEER_EDITOR(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_WIREGUARD_PEER_EDITOR, NmtWireguardPeerEditor))
+#define NMT_WIREGUARD_PEER_EDITOR_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_WIREGUARD_PEER_EDITOR, NmtWireguardPeerEditorClass))
+#define NMT_IS_WIREGUARD_PEER_EDITOR(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_WIREGUARD_PEER_EDITOR))
+#define NMT_IS_WIREGUARD_PEER_EDITOR_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_WIREGUARD_PEER_EDITOR))
+#define NMT_WIREGUARD_PEER_EDITOR_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_WIREGUARD_PEER_EDITOR, NmtWireguardPeerEditorClass))
+
+typedef struct {
+    NmtNewtForm parent;
+} NmtWireguardPeerEditor;
+
+typedef struct {
+    NmtNewtFormClass parent;
+} NmtWireguardPeerEditorClass;
+
+GType nmt_wireguard_peer_editor_get_type(void);
+
+NmtNewtForm *nmt_wireguard_peer_editor_new(NMSettingWireGuard *setting, NMWireGuardPeer *peer);
+
+#endif /* NMT_WIREGUARD_PEER_EDITOR_H */
diff --git a/src/nmtui/nmt-wireguard-peer-list.c b/src/nmtui/nmt-wireguard-peer-list.c
new file mode 100644
index 00000000..03f3ef9a
--- /dev/null
+++ b/src/nmtui/nmt-wireguard-peer-list.c
@@ -0,0 +1,451 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-wireguard-peer-list:
+ * @short_description: An editable list of a connection's peers
+ *
+ * #NmtWireguardPeerList implements an #NmtNewtGrid for the
+ * peers of a connection.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmtui.h"
+#include "nmt-wireguard-peer-list.h"
+#include "nmt-wireguard-peer-editor.h"
+
+G_DEFINE_TYPE(NmtWireguardPeerList, nmt_wireguard_peer_list, NMT_TYPE_NEWT_GRID)
+
+#define NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_WIREGUARD_PEER_LIST, NmtWireguardPeerListPrivate))
+
+typedef struct {
+    NMSettingWireGuard *setting;
+    GSList *            peers;
+
+    NmtNewtListbox *  listbox;
+    NmtNewtButtonBox *buttons;
+
+    NmtNewtWidget *add;
+    NmtNewtWidget *edit;
+    NmtNewtWidget *delete;
+} NmtWireguardPeerListPrivate;
+
+enum {
+    PROP_0,
+
+    PROP_SETTING,
+    PROP_PEERS,
+    PROP_NUM_PEERS,
+
+    LAST_PROP
+};
+
+enum {
+    ADD_PEER,
+    EDIT_PEER,
+    REMOVE_PEER,
+
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/**
+ * nmt_wireguard_peer_list_new:
+ * @master: the master #NMSettingWireGuard whose peers are being listed
+ *
+ * Creates a new #NmtWireguardPeerList.
+ *
+ * Returns: a new #NmtWireguardPeerList.
+ */
+NmtNewtWidget *
+nmt_wireguard_peer_list_new(NMSettingWireGuard *setting)
+{
+    return g_object_new(NMT_TYPE_WIREGUARD_PEER_LIST, "setting", setting, NULL);
+}
+
+static void
+add_clicked(NmtNewtButton *button, gpointer list)
+{
+    g_signal_emit(list, signals[ADD_PEER], 0);
+}
+
+static void
+edit_clicked(NmtNewtButton *button, gpointer list)
+{
+    g_signal_emit(list, signals[EDIT_PEER], 0);
+}
+
+static void
+delete_clicked(NmtNewtButton *button, gpointer list)
+{
+    g_signal_emit(list, signals[REMOVE_PEER], 0);
+}
+
+static void
+listbox_activated(NmtNewtWidget *listbox, gpointer list)
+{
+    NmtWireguardPeerListPrivate *priv = NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(list);
+
+    edit_clicked(NMT_NEWT_BUTTON(priv->edit), list);
+}
+
+static void
+nmt_wireguard_peer_list_init(NmtWireguardPeerList *list)
+{
+    NmtWireguardPeerListPrivate *priv = NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(list);
+    NmtNewtWidget *              listbox, *buttons;
+    NmtNewtGrid *                grid = NMT_NEWT_GRID(list);
+
+    listbox       = g_object_new(NMT_TYPE_NEWT_LISTBOX,
+                           "flags",
+                           NMT_NEWT_LISTBOX_SCROLL | NMT_NEWT_LISTBOX_BORDER,
+                           "skip-null-keys",
+                           TRUE,
+                           NULL);
+    priv->listbox = NMT_NEWT_LISTBOX(listbox);
+    nmt_newt_grid_add(grid, listbox, 0, 0);
+    nmt_newt_grid_set_flags(grid,
+                            listbox,
+                            NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y | NMT_NEWT_GRID_EXPAND_X
+                                | NMT_NEWT_GRID_EXPAND_Y);
+    g_signal_connect(priv->listbox, "activated", G_CALLBACK(listbox_activated), list);
+
+    buttons       = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_VERTICAL);
+    priv->buttons = NMT_NEWT_BUTTON_BOX(buttons);
+    nmt_newt_grid_add(grid, buttons, 1, 0);
+    nmt_newt_widget_set_padding(buttons, 1, 1, 0, 1);
+    nmt_newt_grid_set_flags(grid,
+                            buttons,
+                            NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y | NMT_NEWT_GRID_EXPAND_Y);
+
+    priv->add = nmt_newt_button_box_add_start(priv->buttons, _("Add"));
+    g_signal_connect(priv->add, "clicked", G_CALLBACK(add_clicked), list);
+
+    priv->edit = nmt_newt_button_box_add_start(priv->buttons, _("Edit..."));
+    g_signal_connect(priv->edit, "clicked", G_CALLBACK(edit_clicked), list);
+
+    priv->delete = nmt_newt_button_box_add_start(priv->buttons, _("Delete"));
+    g_signal_connect(priv->delete, "clicked", G_CALLBACK(delete_clicked), list);
+}
+
+static void nmt_wireguard_peer_list_rebuild(NmtWireguardPeerList *list);
+
+static void
+rebuild_on_peer_changed(gpointer list)
+{
+    nmt_wireguard_peer_list_rebuild(list);
+}
+
+static void
+free_peers(NmtWireguardPeerList *list)
+{
+    NmtWireguardPeerListPrivate *priv = NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(list);
+
+    g_slist_free(priv->peers);
+    priv->peers = NULL;
+}
+
+static void
+nmt_wireguard_peer_list_finalize(GObject *object)
+{
+    NmtWireguardPeerListPrivate *priv = NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(object);
+
+    free_peers(NMT_WIREGUARD_PEER_LIST(object));
+    g_object_unref(priv->setting);
+    g_object_unref(priv->peers);
+
+    G_OBJECT_CLASS(nmt_wireguard_peer_list_parent_class)->finalize(object);
+}
+
+static void
+nmt_wireguard_peer_list_add_peer(NmtWireguardPeerList *list)
+{
+    NmtWireguardPeerListPrivate *priv = NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(list);
+    NMWireGuardPeer *            peer = nm_wireguard_peer_new();
+    NmtNewtForm *                editor;
+
+    editor = nmt_wireguard_peer_editor_new(priv->setting, peer);
+
+    if (!editor)
+        return;
+
+    nmt_newt_form_run_sync(editor);
+    g_object_unref(editor);
+    rebuild_on_peer_changed(list);
+}
+
+static void
+nmt_wireguard_peer_list_edit_peer(NmtWireguardPeerList *list)
+{
+    NmtWireguardPeerListPrivate *priv = NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(list);
+    NMWireGuardPeer *            orig_peer, *edit_peer;
+    NmtNewtForm *                editor;
+    int                          selected_row;
+
+    selected_row = nmt_newt_listbox_get_active(priv->listbox);
+
+    if (selected_row >= 0) {
+        orig_peer = nm_setting_wireguard_get_peer(priv->setting, (guint) selected_row);
+        edit_peer = nm_wireguard_peer_new_clone(orig_peer, TRUE);
+        editor    = nmt_wireguard_peer_editor_new(priv->setting, edit_peer);
+        if (!editor)
+            return;
+        nmt_newt_form_run_sync(editor);
+        g_object_unref(editor);
+        rebuild_on_peer_changed(list);
+    }
+}
+
+static void
+nmt_wireguard_peer_list_remove_peer(NmtWireguardPeerList *list)
+{
+    NmtWireguardPeerListPrivate *priv = NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(list);
+    int                          selected_row;
+
+    selected_row = nmt_newt_listbox_get_active(priv->listbox);
+
+    if (selected_row >= 0) {
+        nm_setting_wireguard_remove_peer(priv->setting, (guint) selected_row);
+        rebuild_on_peer_changed(list);
+    }
+}
+
+static void
+nmt_wireguard_peer_list_set_property(GObject *     object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec *  pspec)
+{
+    NmtWireguardPeerListPrivate *priv = NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_SETTING:
+        priv->setting = g_value_get_pointer(value);
+        break;
+    case PROP_PEERS:
+        priv->peers = g_value_get_pointer(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_wireguard_peer_list_get_property(GObject *   object,
+                                     guint       prop_id,
+                                     GValue *    value,
+                                     GParamSpec *pspec)
+{
+    NmtWireguardPeerListPrivate *priv = NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(object);
+    GPtrArray *                  peers;
+    GSList *                     iter;
+
+    switch (prop_id) {
+    case PROP_SETTING:
+        g_value_set_pointer(value, priv->setting);
+        break;
+    case PROP_PEERS:
+        peers = g_ptr_array_new_with_free_func(g_object_unref);
+        for (iter = priv->peers; iter; iter = iter->next)
+            g_ptr_array_add(peers, g_object_ref(iter->data));
+        g_value_take_boxed(value, peers);
+        break;
+    case PROP_NUM_PEERS:
+        g_value_set_int(value, g_slist_length(priv->peers));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_wireguard_peer_list_rebuild(NmtWireguardPeerList *list)
+{
+    NmtWireguardPeerListPrivate *priv = NMT_WIREGUARD_PEER_LIST_GET_PRIVATE(list);
+    GSList *                     iter;
+    NMWireGuardPeer *            peer, *selected_peer;
+    int                          i, row, selected_row, num;
+    NMSettingWireGuard *         setting = priv->setting;
+
+    selected_row  = nmt_newt_listbox_get_active(priv->listbox);
+    selected_peer = nmt_newt_listbox_get_active_key(priv->listbox);
+
+    free_peers(list);
+    num = nm_setting_wireguard_get_peers_len(setting);
+    for (i = 0; i < num; i++) {
+        peer        = nm_setting_wireguard_get_peer(setting, i);
+        priv->peers = g_slist_append(priv->peers, peer);
+    }
+    g_object_notify(G_OBJECT(list), "peers");
+    g_object_notify(G_OBJECT(list), "num-peers");
+
+    nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->edit), priv->peers != NULL);
+    nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->delete), priv->peers != NULL);
+
+    nmt_newt_listbox_clear(priv->listbox);
+
+    for (iter = priv->peers, row = 0; iter; iter = iter->next, row++) {
+        peer = iter->data;
+        nmt_newt_listbox_append(priv->listbox, nm_wireguard_peer_get_public_key(peer), peer);
+        if (peer == selected_peer)
+            selected_row = row;
+    }
+    if (selected_row >= row)
+        selected_row = row - 1;
+    nmt_newt_listbox_set_active(priv->listbox, selected_row);
+}
+
+static void
+rebuild_on_peers_changed(GObject *object, GParamSpec *pspec, gpointer list)
+{
+    nmt_wireguard_peer_list_rebuild(list);
+}
+
+static void
+nmt_wireguard_peer_list_constructed(GObject *object)
+{
+    NmtWireguardPeerList *list = NMT_WIREGUARD_PEER_LIST(object);
+
+    g_signal_connect(nm_client,
+                     "notify::" NM_CLIENT_CONNECTIONS,
+                     G_CALLBACK(rebuild_on_peers_changed),
+                     list);
+
+    nmt_wireguard_peer_list_rebuild(list);
+
+    G_OBJECT_CLASS(nmt_wireguard_peer_list_parent_class)->constructed(object);
+}
+
+static void
+nmt_wireguard_peer_list_class_init(NmtWireguardPeerListClass *list_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(list_class);
+
+    g_type_class_add_private(list_class, sizeof(NmtWireguardPeerListPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_wireguard_peer_list_constructed;
+    object_class->set_property = nmt_wireguard_peer_list_set_property;
+    object_class->get_property = nmt_wireguard_peer_list_get_property;
+    object_class->finalize     = nmt_wireguard_peer_list_finalize;
+
+    list_class->add_peer    = nmt_wireguard_peer_list_add_peer;
+    list_class->edit_peer   = nmt_wireguard_peer_list_edit_peer;
+    list_class->remove_peer = nmt_wireguard_peer_list_remove_peer;
+
+    /* signals */
+
+    /**
+     * NmtWireguardPeerList::add-connection:
+     * @list: the #NmtWireguardPeerList
+     *
+     * Emitted when the user clicks the list's "Add" button.
+     */
+    signals[ADD_PEER] = g_signal_new("add-peer",
+                                     G_OBJECT_CLASS_TYPE(object_class),
+                                     G_SIGNAL_RUN_FIRST,
+                                     G_STRUCT_OFFSET(NmtWireguardPeerListClass, add_peer),
+                                     NULL,
+                                     NULL,
+                                     NULL,
+                                     G_TYPE_NONE,
+                                     0);
+
+    /**
+     * NmtWireguardPeerList::edit-connection:
+     * @list: the #NmtWireguardPeerList
+     * @connection: the connection to edit
+     *
+     * Emitted when the user clicks the list's "Edit" button, or
+     * hits "Return" on the listbox.
+     */
+    signals[EDIT_PEER] = g_signal_new("edit-peer",
+                                      G_OBJECT_CLASS_TYPE(object_class),
+                                      G_SIGNAL_RUN_FIRST,
+                                      G_STRUCT_OFFSET(NmtWireguardPeerListClass, edit_peer),
+                                      NULL,
+                                      NULL,
+                                      NULL,
+                                      G_TYPE_NONE,
+                                      0);
+
+    /**
+     * NmtWireguardPeerList::remove-connection:
+     * @list: the #NmtWireguardPeerList
+     * @connection: the connection to remove
+     *
+     * Emitted when the user clicks the list's "Delete" button.
+     */
+    signals[REMOVE_PEER] = g_signal_new("remove-peer",
+                                        G_OBJECT_CLASS_TYPE(object_class),
+                                        G_SIGNAL_RUN_FIRST,
+                                        G_STRUCT_OFFSET(NmtWireguardPeerListClass, remove_peer),
+                                        NULL,
+                                        NULL,
+                                        NULL,
+                                        G_TYPE_NONE,
+                                        0);
+
+    /* properties */
+
+    /**
+     * NmtWireguardPeerListFilter:
+     * @list: the #NmtWireguardPeerList
+     * @connection: an #NMConnection
+     * @user_data: the user data
+     *
+     * Decides whether @connection should be displayed in @list.
+     *
+     * Returns: %TRUE or %FALSE
+     */
+    /**
+     * NmtWireguardPeerList:connection-filter:
+     *
+     * A callback function for filtering which connections appear in
+     * the list.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_SETTING,
+        g_param_spec_pointer("setting",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * NmtWireguardPeerList:connections:
+     *
+     * The list of connections in the widget.
+     *
+     * Element-Type: #NMConnection
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_PEERS,
+                                    g_param_spec_boxed("peers",
+                                                       "",
+                                                       "",
+                                                       G_TYPE_PTR_ARRAY,
+                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * NmtWireguardPeerList:num-connections:
+     *
+     * The number of connections in the widget.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_NUM_PEERS,
+                                    g_param_spec_int("num-peers",
+                                                     "",
+                                                     "",
+                                                     0,
+                                                     G_MAXINT,
+                                                     0,
+                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-wireguard-peer-list.h b/src/nmtui/nmt-wireguard-peer-list.h
new file mode 100644
index 00000000..de3faf12
--- /dev/null
+++ b/src/nmtui/nmt-wireguard-peer-list.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ */
+
+#ifndef NMT_WIREGUARD_PEER_LIST_H
+#define NMT_WIREGUARD_PEER_LIST_H
+
+#include "libnmt-newt/nmt-newt.h"
+#include "nmtui-edit.h"
+
+#define NMT_TYPE_WIREGUARD_PEER_LIST (nmt_wireguard_peer_list_get_type())
+#define NMT_WIREGUARD_PEER_LIST(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_WIREGUARD_PEER_LIST, NmtWireguardPeerList))
+#define NMT_WIREGUARD_PEER_LIST_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_WIREGUARD_PEER_LIST, NmtWireguardPeerListClass))
+#define NMT_IS_WIREGUARD_PEER_LIST(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_WIREGUARD_PEER_LIST))
+#define NMT_IS_WIREGUARD_PEER_LIST_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_WIREGUARD_PEER_LIST))
+#define NMT_WIREGUARD_PEER_LIST_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_WIREGUARD_PEER_LIST, NmtWireguardPeerListClass))
+
+typedef struct {
+    NmtNewtGrid parent;
+
+} NmtWireguardPeerList;
+
+typedef struct {
+    NmtNewtGridClass parent;
+
+    /* signals */
+    void (*add_peer)(NmtWireguardPeerList *list);
+    void (*edit_peer)(NmtWireguardPeerList *list);
+    void (*remove_peer)(NmtWireguardPeerList *list);
+} NmtWireguardPeerListClass;
+
+GType nmt_wireguard_peer_list_get_type(void);
+
+NmtNewtWidget *nmt_wireguard_peer_list_new(NMSettingWireGuard *setting);
+
+#endif /* NMT_WIREGUARD_PEER_LIST_H */
diff --git a/src/nmtui/nmtui.c b/src/nmtui/nmtui.c
index 5edf70e5..897e0911 100644
--- a/src/nmtui/nmtui.c
+++ b/src/nmtui/nmtui.c
@@ -283,7 +283,7 @@ main(int argc, char **argv)
 
     startup_data.argc = argc;
     startup_data.argv = argv;
-    g_idle_add(idle_run_subprogram, &startup_data);
+    nm_g_idle_add(idle_run_subprogram, &startup_data);
     loop = g_main_loop_new(NULL, FALSE);
     g_main_loop_run(loop);
     g_main_loop_unref(loop);