diff --git a/panels/network/cc-network-panel.c b/panels/network/cc-network-panel.c index 24a6c6f8bd5e70036a3376e90a3b4055b1b913cf..6a3074ced5e10d0313de535701b7e768ce334e5c 100644 --- a/panels/network/cc-network-panel.c +++ b/panels/network/cc-network-panel.c @@ -94,6 +94,7 @@ enum { }; static void handle_argv (CcNetworkPanel *self); +static void device_managed_cb (CcNetworkPanel *self, GParamSpec *pspec, NMDevice *device); CC_PANEL_REGISTER (CcNetworkPanel, cc_network_panel) @@ -514,6 +515,10 @@ active_connections_changed (CcNetworkPanel *self) devices = nm_active_connection_get_devices (connection); for (j = 0; devices && j < devices->len; j++) g_debug (" %s", nm_device_get_udi (g_ptr_array_index (devices, j))); + + if (nm_is_wireguard_connection (connection)) + g_debug (" WireGuard connection: %s", nm_active_connection_get_id(connection)); + if (NM_IS_VPN_CONNECTION (connection)) g_debug (" VPN base connection: %s", nm_active_connection_get_specific_object_path (connection)); @@ -630,7 +635,7 @@ add_connection (CcNetworkPanel *self, NMConnection *connection) g_debug ("add %s/%s remote connection: %s", type, g_type_name_from_instance ((GTypeInstance*)connection), nm_connection_get_path (connection)); - if (!iface) + if (!iface || g_strcmp0 (type, "wireguard") == 0) panel_add_vpn_device (self, connection); } diff --git a/panels/network/connection-editor/ce-page-details.c b/panels/network/connection-editor/ce-page-details.c index e0f43ed5d5cacc12cb35548934fa657b265cafd4..b6dd53bd672eecd906edc76443216f28d581104a 100644 --- a/panels/network/connection-editor/ce-page-details.c +++ b/panels/network/connection-editor/ce-page-details.c @@ -477,7 +477,8 @@ connect_details_page (CEPageDetails *self) gtk_button_set_label (self->forget_button, _("Forget Connection")); else if (g_str_equal (type, NM_SETTING_WIRED_SETTING_NAME)) gtk_button_set_label (self->forget_button, _("Remove Connection Profile")); - else if (g_str_equal (type, NM_SETTING_VPN_SETTING_NAME)) + else if (g_str_equal (type, NM_SETTING_VPN_SETTING_NAME) || + g_str_equal (type, NM_SETTING_WIREGUARD_SETTING_NAME)) gtk_button_set_label (self->forget_button, _("Remove VPN")); else gtk_widget_hide (GTK_WIDGET (self->forget_button)); diff --git a/panels/network/connection-editor/ce-page-wireguard.c b/panels/network/connection-editor/ce-page-wireguard.c new file mode 100644 index 0000000000000000000000000000000000000000..113ea11f76e73799e0d11961ff2a52765a340609 --- /dev/null +++ b/panels/network/connection-editor/ce-page-wireguard.c @@ -0,0 +1,502 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2022 Nathan-J. Hirschauer + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include +#include + +#include "ce-page.h" +#include "ce-page-wireguard.h" +#include "nma-ui-utils.h" +#include "vpn-helpers.h" + +#include + +struct _CEPageWireguard +{ + GtkBox parent; + + GtkGrid *main_box; + GtkEntry *entry_conname; + GtkEntry *entry_ifname; + GtkEntry *entry_private_key; + GtkSpinButton *spin_listen_port; + GtkSpinButton *spin_fwmark; + GtkSpinButton *spin_mtu; + GtkWidget *peers_box; + GtkWidget *empty_listbox; + GtkButton *button_add_peer; + GtkCheckButton *checkbutton_peer_routes; + + NMConnection *connection; + NMSettingConnection *setting_connection; + NMSettingWireGuard *setting_wireguard; +}; + +struct _WireguardPeer +{ + GtkBox parent; + + GtkBox *box; + GtkLabel *peer_label; + GtkMenuButton *button_configure; + GtkMenuButton *button_delete; + + GtkPopover *peer_popover; + + // Provided by peer_popover + GtkEntry *entry_public_key; + GtkEntry *entry_allowed_ips; + GtkEntry *entry_endpoint; + GtkEntry *entry_psk; + GtkSpinButton *spin_persistent_keepalive; + GtkButton *button_apply; + + // Used to track whether the peer was newly constructed + gboolean is_unsaved; + + CEPageWireguard *ce_pg_wg; + NMWireGuardPeer *nm_wg_peer; +}; + +static void ce_page_iface_init (CEPageInterface *); + +G_DEFINE_TYPE_WITH_CODE (CEPageWireguard, ce_page_wireguard, GTK_TYPE_BOX, + G_IMPLEMENT_INTERFACE (ce_page_get_type (), ce_page_iface_init)); +G_DEFINE_TYPE (WireguardPeer, wireguard_peer, GTK_TYPE_BOX); + +static void +ce_page_wireguard_dispose (GObject *object) +{ + CEPageWireguard *self = CE_PAGE_WIREGUARD (object); + + g_clear_object (&self->setting_connection); + g_clear_object (&self->setting_wireguard); + G_OBJECT_CLASS (ce_page_wireguard_parent_class)->dispose (object); +} + +static const gchar * +ce_page_wireguard_get_security_setting (CEPage *page) +{ + return NM_SETTING_WIREGUARD_SETTING_NAME; +} + +static const gchar * +ce_page_wireguard_get_title (CEPage *page) +{ + return _("WireGuard"); +} + +static void +ui_to_setting (CEPageWireguard *self, + GError **error) +{ + // Transform UI values to NM_SETTING + NMSettingSecretFlags secret_flags; + + // Update peers + GtkWidget *widget; + GList *peers = NULL; + guint num_peers = 0; + for (widget = gtk_widget_get_first_child (GTK_WIDGET (self->peers_box)); + widget != NULL; + widget = gtk_widget_get_next_sibling (widget)) + peers = g_list_append (peers, widget); + + for (GList *p = peers; p != NULL; p = p->next) { + WireguardPeer *peer = p->data; + if (!WIREGUARD_IS_PEER (peer)) + continue; + + nm_setting_wireguard_set_peer (self->setting_wireguard, peer->nm_wg_peer, num_peers); + + num_peers++; + } + + g_list_free (peers); + g_object_set (self->setting_connection, + NM_SETTING_CONNECTION_INTERFACE_NAME, gtk_editable_get_text (GTK_EDITABLE (self->entry_ifname)), + NM_SETTING_CONNECTION_ID, gtk_editable_get_text (GTK_EDITABLE (self->entry_conname)), + NULL); + + g_object_set (self->setting_wireguard, + NM_SETTING_WIREGUARD_PRIVATE_KEY, gtk_editable_get_text (GTK_EDITABLE (self->entry_private_key)), + NM_SETTING_WIREGUARD_FWMARK, (guint32)gtk_spin_button_get_value_as_int (self->spin_fwmark), + NM_SETTING_WIREGUARD_MTU, (guint32)gtk_spin_button_get_value_as_int (self->spin_mtu), + NM_SETTING_WIREGUARD_LISTEN_PORT, (guint32)gtk_spin_button_get_value_as_int (self->spin_listen_port), + NULL); + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (self->entry_private_key)); + nm_setting_set_secret_flags ((NMSetting *)self->setting_wireguard, + NM_SETTING_WIREGUARD_PRIVATE_KEY, + secret_flags, NULL); + nma_utils_update_password_storage (GTK_WIDGET (self->entry_private_key), + secret_flags, + (NMSetting *)self->setting_wireguard, + NM_SETTING_WIREGUARD_PRIVATE_KEY); +} + +static gboolean +ce_page_wireguard_validate (CEPage *page, + NMConnection *connection, + GError **error) +{ + CEPageWireguard *self = CE_PAGE_WIREGUARD (page); + ui_to_setting (self, error); + + for (guint p = 0; p < nm_setting_wireguard_get_peers_len (self->setting_wireguard); p++) { + NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (self->setting_wireguard, p); + + if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, error)) + return FALSE; + } + + return nm_setting_verify (NM_SETTING (self->setting_connection), connection, error) && + nm_setting_verify_secrets (NM_SETTING (self->setting_wireguard), connection, error) && + nm_setting_verify (NM_SETTING (self->setting_wireguard), connection, error); +} + +static void +ce_page_wireguard_init (CEPageWireguard *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +ce_page_wireguard_class_init (CEPageWireguardClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + object_class->dispose = ce_page_wireguard_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/wireguard-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, main_box); + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, entry_conname); + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, entry_ifname); + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, entry_private_key); + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, spin_listen_port); + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, spin_fwmark); + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, spin_mtu); + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, checkbutton_peer_routes); + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, peers_box); + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, empty_listbox); + gtk_widget_class_bind_template_child (widget_class, CEPageWireguard, button_add_peer); +} + +static void +ce_page_iface_init (CEPageInterface *iface) +{ + iface->get_security_setting = ce_page_wireguard_get_security_setting; + iface->get_title = ce_page_wireguard_get_title; + iface->validate = ce_page_wireguard_validate; +} + +static void +toggle_show_secret_cb (GtkEntry *entry_private_key, gpointer user_data) +{ + gtk_entry_set_visibility (entry_private_key, + !gtk_entry_get_visibility (entry_private_key)); +} + +gchar * +peer_allowed_ips_to_str (NMWireGuardPeer *peer) +{ + gchar *allowed_ips = NULL; + if (nm_wireguard_peer_get_allowed_ips_len (peer) != 0) { + allowed_ips = (gchar *)nm_wireguard_peer_get_allowed_ip (peer, 0, NULL); + + for (guint i = 1; i < nm_wireguard_peer_get_allowed_ips_len (peer); i++) { + allowed_ips = g_strconcat (allowed_ips, ", ", + nm_wireguard_peer_get_allowed_ip (peer, i, NULL), + NULL); + } + } + return allowed_ips; +} + +static void +handle_peer_changed_cb (GtkButton *apply_button, WireguardPeer *wg_peer) +{ + NMWireGuardPeer *nm_wg_peer; + NMSettingSecretFlags secret_flags; + + gboolean peer_is_valid = TRUE; + const gchar *endpoint = gtk_editable_get_text (GTK_EDITABLE (wg_peer->entry_endpoint)); + const gchar *public_key = gtk_editable_get_text (GTK_EDITABLE (wg_peer->entry_public_key)); + const gchar *psk = gtk_editable_get_text (GTK_EDITABLE (wg_peer->entry_psk)); + const gchar *allowed_ips = gtk_editable_get_text (GTK_EDITABLE (wg_peer->entry_allowed_ips)); + guint16 keepalive = gtk_spin_button_get_value_as_int (wg_peer->spin_persistent_keepalive); + + nm_wg_peer = nm_wireguard_peer_new_clone (wg_peer->nm_wg_peer, TRUE); + + widget_unset_error (GTK_WIDGET (wg_peer->entry_endpoint)); + if (!nm_wireguard_peer_set_endpoint (nm_wg_peer, + endpoint && endpoint[0] ? endpoint : NULL, + FALSE)) { + widget_set_error (GTK_WIDGET (wg_peer->entry_endpoint)); + peer_is_valid = FALSE; + } + + widget_unset_error (GTK_WIDGET (wg_peer->entry_public_key)); + if (!nm_wireguard_peer_set_public_key (nm_wg_peer, + public_key && public_key[0] ? public_key : NULL, + FALSE)) { + widget_set_error (GTK_WIDGET (wg_peer->entry_public_key)); + peer_is_valid = FALSE; + } + + widget_unset_error (GTK_WIDGET (wg_peer->entry_psk)); + if (!nm_wireguard_peer_set_preshared_key (nm_wg_peer, + psk && psk[0] ? psk : NULL, + FALSE)) { + widget_set_error (GTK_WIDGET (wg_peer->entry_psk)); + peer_is_valid = FALSE; + } else if (psk && psk[0]) { + secret_flags = nma_utils_menu_to_secret_flags (GTK_WIDGET (wg_peer->entry_psk)); + nm_wireguard_peer_set_preshared_key_flags (nm_wg_peer, secret_flags); + nma_utils_update_password_storage (GTK_WIDGET (wg_peer->entry_psk), + nm_wireguard_peer_get_preshared_key_flags (wg_peer->nm_wg_peer), + NULL, + NULL); + } + + nm_wireguard_peer_set_persistent_keepalive (nm_wg_peer, keepalive); + + /* Only update allowed IPs if a value actually changed. + * Otherwise, the comparison will always differ, touching + * the connection without any real changes. + */ + widget_unset_error (GTK_WIDGET (wg_peer->entry_allowed_ips)); + if (g_strcmp0(peer_allowed_ips_to_str (nm_wg_peer), allowed_ips) != 0) { + nm_wireguard_peer_clear_allowed_ips (nm_wg_peer); + char **strv = g_strsplit (allowed_ips, ",", -1); + for (guint i = 0; strv && strv[i]; i++) { + if (!nm_wireguard_peer_append_allowed_ip (nm_wg_peer, + g_strstrip (strv[i]), + FALSE)) { + widget_set_error (GTK_WIDGET (wg_peer->entry_allowed_ips)); + peer_is_valid = FALSE; + } + } + g_strfreev (strv); + } + + if (!nm_wireguard_peer_is_valid (nm_wg_peer, TRUE, TRUE, NULL) || !peer_is_valid) + return; + + if (nm_wireguard_peer_cmp (wg_peer->nm_wg_peer, nm_wg_peer, NM_SETTING_COMPARE_FLAG_EXACT) == 0) { + gtk_popover_popdown (wg_peer->peer_popover); + return; + } + + // Indicate that the peer has now been succesfully configured + wg_peer->is_unsaved = FALSE; + + // Update peer list + gtk_label_set_text (wg_peer->peer_label, gtk_editable_get_text (GTK_EDITABLE (wg_peer->entry_endpoint))); + + wg_peer->nm_wg_peer = nm_wg_peer; + g_signal_emit_by_name (wg_peer->ce_pg_wg, "changed", wg_peer->ce_pg_wg); + gtk_popover_popdown (wg_peer->peer_popover); +} + +static void +destroy_peer (WireguardPeer *wg_peer) +{ + nm_wireguard_peer_unref (wg_peer->nm_wg_peer); + GtkWidget* parent = gtk_widget_get_parent (GTK_WIDGET (wg_peer)); + gtk_box_remove (GTK_BOX (parent), GTK_WIDGET (wg_peer)); + gtk_widget_set_visible (GTK_WIDGET (wg_peer->ce_pg_wg->empty_listbox), + nm_setting_wireguard_get_peers_len (wg_peer->ce_pg_wg->setting_wireguard) < 1); +} + +static void +handle_peer_delete_cb (GtkButton *delete_button, WireguardPeer *wg_peer) +{ + NMWireGuardPeer *peer; + for (guint p = 0; p < nm_setting_wireguard_get_peers_len (wg_peer->ce_pg_wg->setting_wireguard); p++) { + peer = nm_setting_wireguard_get_peer (wg_peer->ce_pg_wg->setting_wireguard, p); + if (nm_wireguard_peer_cmp (wg_peer->nm_wg_peer, peer, NM_SETTING_COMPARE_FLAG_EXACT) == 0) { + nm_setting_wireguard_remove_peer (wg_peer->ce_pg_wg->setting_wireguard, p); + break; + } + } + + destroy_peer (wg_peer); + g_signal_emit_by_name (wg_peer->ce_pg_wg, "changed", wg_peer->ce_pg_wg); +} + +static void +handle_abort_new_peer (GtkPopover *peer_popover, WireguardPeer *wg_peer) +{ + if (wg_peer->is_unsaved == TRUE) + destroy_peer (wg_peer); + else + g_signal_handlers_disconnect_by_func (peer_popover, handle_abort_new_peer, wg_peer); + + gtk_widget_set_visible (GTK_WIDGET (wg_peer->ce_pg_wg->empty_listbox), + nm_setting_wireguard_get_peers_len (wg_peer->ce_pg_wg->setting_wireguard) < 1); +} + +WireguardPeer * +add_nm_wg_peer_to_list (CEPageWireguard *self, NMWireGuardPeer *peer) +{ + WireguardPeer *wg_peer; + gchar *endpoint = (gchar *)nm_wireguard_peer_get_endpoint (peer); + if (!endpoint) { + /* Translators: Unknown endpoint host for WireGuard (invalid setting) */ + endpoint = _("Unknown"); + } + + wg_peer = wireguard_peer_new (self); + wg_peer->nm_wg_peer = peer; + wg_peer->is_unsaved = FALSE; + + gtk_label_set_text (wg_peer->peer_label, endpoint); + + gtk_editable_set_text (GTK_EDITABLE (wg_peer->entry_endpoint), endpoint); + if (peer_allowed_ips_to_str (peer) != NULL) + gtk_editable_set_text (GTK_EDITABLE (wg_peer->entry_allowed_ips), peer_allowed_ips_to_str (peer)); + if (nm_wireguard_peer_get_preshared_key (peer) != NULL) + gtk_editable_set_text (GTK_EDITABLE (wg_peer->entry_psk), nm_wireguard_peer_get_preshared_key (peer)); + if (nm_wireguard_peer_get_public_key (peer) != NULL) + gtk_editable_set_text (GTK_EDITABLE (wg_peer->entry_public_key), nm_wireguard_peer_get_public_key (peer)); + + gtk_spin_button_set_value (wg_peer->spin_persistent_keepalive, nm_wireguard_peer_get_persistent_keepalive (peer)); + + g_signal_connect (wg_peer->button_apply, "clicked", G_CALLBACK (handle_peer_changed_cb), wg_peer); + g_signal_connect (wg_peer->button_delete, "clicked", G_CALLBACK (handle_peer_delete_cb), wg_peer); + g_signal_connect (wg_peer->entry_psk, "icon-press", G_CALLBACK (toggle_show_secret_cb), NULL); + gtk_widget_show (GTK_WIDGET (wg_peer)); + gtk_box_append (GTK_BOX (self->peers_box), GTK_WIDGET (wg_peer)); + + return wg_peer; +} + +static void +handle_peer_add_cb (GtkButton *add_button, CEPageWireguard *self) +{ + NMWireGuardPeer *nm_wg_peer = nm_wireguard_peer_new (); + WireguardPeer *wg_peer = add_nm_wg_peer_to_list (self, nm_wg_peer); + wg_peer->is_unsaved = TRUE; + wg_peer->ce_pg_wg = self; + + gtk_widget_set_visible (GTK_WIDGET (self->empty_listbox), FALSE); + gtk_label_set_text (wg_peer->peer_label, _("Unsaved peer")); + gtk_popover_popup (wg_peer->peer_popover); + g_signal_connect (wg_peer->peer_popover, "closed", G_CALLBACK (handle_abort_new_peer), wg_peer); +} + +static void +finish_setup (CEPageWireguard *self, gpointer unused, GError *error, gpointer user_data) +{ + const gchar *ifname, *conname, *privkey; + const guint32 listen_port_default = 51820; + guint32 listen_port, fwmark, mtu; + + self->setting_connection = nm_connection_get_setting_connection (self->connection); + self->setting_wireguard = (NMSettingWireGuard *)nm_connection_get_setting (self->connection, NM_TYPE_SETTING_WIREGUARD); + + conname = nm_connection_get_id (self->connection); + if (conname != NULL) + gtk_editable_set_text (GTK_EDITABLE (self->entry_conname), conname); + ifname = nm_connection_get_interface_name (self->connection); + if (ifname != NULL) + gtk_editable_set_text (GTK_EDITABLE (self->entry_ifname), ifname); + privkey = nm_setting_wireguard_get_private_key (self->setting_wireguard); + if (privkey != NULL) + gtk_editable_set_text (GTK_EDITABLE (self->entry_private_key), privkey); + g_signal_connect (self->entry_private_key, "icon-press", G_CALLBACK (toggle_show_secret_cb), NULL); + + listen_port = nm_setting_wireguard_get_listen_port (self->setting_wireguard); + if (listen_port != 0 && listen_port != 51820) + gtk_spin_button_set_value (self->spin_listen_port, listen_port); + else { + gtk_spin_button_set_value (self->spin_listen_port, listen_port_default); + } + fwmark = nm_setting_wireguard_get_fwmark (self->setting_wireguard); + gtk_spin_button_set_value (self->spin_fwmark, fwmark); + mtu = nm_setting_wireguard_get_mtu (self->setting_wireguard); + gtk_spin_button_set_value (self->spin_mtu, mtu); + for (guint p = 0; + p < nm_setting_wireguard_get_peers_len (self->setting_wireguard); + p++) { + NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (self->setting_wireguard, p); + add_nm_wg_peer_to_list (self, peer); + } + + gtk_widget_set_visible (self->empty_listbox, + nm_setting_wireguard_get_peers_len (self->setting_wireguard) < 1); + gtk_check_button_set_active (self->checkbutton_peer_routes, + nm_setting_wireguard_get_peer_routes (self->setting_wireguard)); + + g_signal_connect (self->button_add_peer, "clicked", G_CALLBACK (handle_peer_add_cb), self); +} + +CEPageWireguard * +ce_page_wireguard_new (NMConnection *connection) +{ + CEPageWireguard *self = CE_PAGE_WIREGUARD (g_object_new (ce_page_wireguard_get_type (), NULL)); + + self->connection = g_object_ref (connection); + + g_signal_connect (self, "initialized", G_CALLBACK (finish_setup), NULL); + + return self; +} + +static void +wireguard_peer_init (WireguardPeer *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +WireguardPeer * +wireguard_peer_new (CEPageWireguard *parent) +{ + WireguardPeer *self; + + self = g_object_new (wireguard_peer_get_type (), NULL); + self->ce_pg_wg = parent; + self->is_unsaved = TRUE; + return self; +} + +static void +wireguard_peer_class_init (WireguardPeerClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/wireguard-peer.ui"); + + gtk_widget_class_bind_template_child (widget_class, WireguardPeer, peer_label); + gtk_widget_class_bind_template_child (widget_class, WireguardPeer, button_configure); + gtk_widget_class_bind_template_child (widget_class, WireguardPeer, button_delete); + gtk_widget_class_bind_template_child (widget_class, WireguardPeer, entry_public_key); + gtk_widget_class_bind_template_child (widget_class, WireguardPeer, entry_allowed_ips); + gtk_widget_class_bind_template_child (widget_class, WireguardPeer, entry_endpoint); + gtk_widget_class_bind_template_child (widget_class, WireguardPeer, entry_psk); + gtk_widget_class_bind_template_child (widget_class, WireguardPeer, spin_persistent_keepalive); + gtk_widget_class_bind_template_child (widget_class, WireguardPeer, peer_popover); + gtk_widget_class_bind_template_child (widget_class, WireguardPeer, button_apply); +} \ No newline at end of file diff --git a/panels/network/connection-editor/ce-page-wireguard.h b/panels/network/connection-editor/ce-page-wireguard.h new file mode 100644 index 0000000000000000000000000000000000000000..b5623db604b2ea75b974dac2e28f316baa709244 --- /dev/null +++ b/panels/network/connection-editor/ce-page-wireguard.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2022 Nathan-J. Hirschauer + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CEPageWireguard, ce_page_wireguard, CE, PAGE_WIREGUARD, GtkBox) +G_DECLARE_FINAL_TYPE (WireguardPeer, wireguard_peer, WIREGUARD, PEER, GtkBox) + +gchar *peer_allowed_ips_to_str (NMWireGuardPeer *peer); +WireguardPeer *add_nm_wg_peer_to_list (CEPageWireguard *self, NMWireGuardPeer *peer); + +CEPageWireguard *ce_page_wireguard_new (NMConnection *connection); +WireguardPeer *wireguard_peer_new (CEPageWireguard *parent); + +G_END_DECLS diff --git a/panels/network/connection-editor/connection-editor.gresource.xml b/panels/network/connection-editor/connection-editor.gresource.xml index 3d06f5a77895969d70227d0abd050a7881943066..897af848ef1b1adddeb6e1f762a3a6a0c7cc0544 100644 --- a/panels/network/connection-editor/connection-editor.gresource.xml +++ b/panels/network/connection-editor/connection-editor.gresource.xml @@ -9,6 +9,8 @@ ip6-page.ui security-page.ui vpn-page.ui + wireguard-page.ui + wireguard-peer.ui wifi-page.ui diff --git a/panels/network/connection-editor/meson.build b/panels/network/connection-editor/meson.build index cdd64e6162baa82cd68704013016956fabdbadff..94e0426d1a581cf0e14b89d9689615bb8ef4bdad 100644 --- a/panels/network/connection-editor/meson.build +++ b/panels/network/connection-editor/meson.build @@ -10,6 +10,7 @@ sources = files( 'ce-page-ip6.c', 'ce-page-security.c', 'ce-page-vpn.c', + 'ce-page-wireguard.c', 'ce-page-wifi.c', 'ce-page.c', 'net-connection-editor.c', @@ -25,6 +26,8 @@ resource_data = files( 'ip6-page.ui', 'security-page.ui', 'vpn-page.ui', + 'wireguard-page.ui', + 'wireguard-peer.ui', 'wifi-page.ui' ) diff --git a/panels/network/connection-editor/net-connection-editor.c b/panels/network/connection-editor/net-connection-editor.c index 46b1661e74d9c03759d2ded3c47ed7fd1efb3ccd..03d9fcac076e2c1bfa029ca19d8316e60d3c5e4a 100644 --- a/panels/network/connection-editor/net-connection-editor.c +++ b/panels/network/connection-editor/net-connection-editor.c @@ -37,6 +37,7 @@ #include "ce-page-ethernet.h" #include "ce-page-8021x-security.h" #include "ce-page-vpn.h" +#include "ce-page-wireguard.h" #include "vpn-helpers.h" enum { @@ -581,6 +582,7 @@ net_connection_editor_set_connection (NetConnectionEditor *self, gboolean is_wired; gboolean is_wifi; gboolean is_vpn; + gboolean is_wireguard; self->is_new_connection = !nm_client_get_connection_by_uuid (self->client, nm_connection_get_uuid (connection)); @@ -603,6 +605,7 @@ net_connection_editor_set_connection (NetConnectionEditor *self, is_wired = g_str_equal (type, NM_SETTING_WIRED_SETTING_NAME); is_wifi = g_str_equal (type, NM_SETTING_WIRELESS_SETTING_NAME); is_vpn = g_str_equal (type, NM_SETTING_VPN_SETTING_NAME); + is_wireguard = g_str_equal (type, NM_SETTING_WIREGUARD_SETTING_NAME); if (!self->is_new_connection) add_page (self, CE_PAGE (ce_page_details_new (self->connection, self->device, self->ap, self))); @@ -613,6 +616,8 @@ net_connection_editor_set_connection (NetConnectionEditor *self, add_page (self, CE_PAGE (ce_page_ethernet_new (self->connection, self->client))); else if (is_vpn) add_page (self, CE_PAGE (ce_page_vpn_new (self->connection))); + else if (is_wireguard) + add_page (self, CE_PAGE (ce_page_wireguard_new (self->connection))); else { /* Unsupported type */ net_connection_editor_do_fallback (self, type); @@ -643,7 +648,9 @@ net_connection_editor_set_connection (NetConnectionEditor *self, } static NMConnection * -complete_vpn_connection (NetConnectionEditor *self, NMConnection *connection) +complete_vpn_connection (NetConnectionEditor *self, + NMConnection *connection, + GType setting_type) { NMSettingConnection *s_con; NMSetting *s_type; @@ -675,9 +682,9 @@ complete_vpn_connection (NetConnectionEditor *self, NMConnection *connection) NULL); } - s_type = nm_connection_get_setting (connection, NM_TYPE_SETTING_VPN); + s_type = nm_connection_get_setting (connection, setting_type); if (!s_type) { - s_type = g_object_new (NM_TYPE_SETTING_VPN, NULL); + s_type = g_object_new (setting_type, NULL); nm_connection_add_setting (connection, s_type); } @@ -705,6 +712,8 @@ static void vpn_import_complete (NMConnection *connection, gpointer user_data) { NetConnectionEditor *self = user_data; + NMSetting *s_type = NULL; + NMSettingConnection *s_con; if (!connection) { /* The import code shows its own error dialogs. */ @@ -712,7 +721,17 @@ vpn_import_complete (NMConnection *connection, gpointer user_data) return; } - complete_vpn_connection (self, connection); + s_type = nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD); + if (s_type) + complete_vpn_connection (self, connection, NM_TYPE_SETTING_WIREGUARD); + else + complete_vpn_connection (self, connection, NM_TYPE_SETTING_VPN); + + /* Mark the connection as private to this user, and non-autoconnect */ + s_con = nm_connection_get_setting_connection (connection); + g_object_set (s_con, NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL); + nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL); + finish_add_connection (self, connection); } @@ -721,17 +740,22 @@ vpn_type_activated (NetConnectionEditor *self, GtkWidget *row) { const char *service_name = g_object_get_data (G_OBJECT (row), "service_name"); NMConnection *connection; - NMSettingVpn *s_vpn; + NMSettingVpn *s_vpn = NULL; NMSettingConnection *s_con; + GType s_type = NM_TYPE_SETTING_VPN; if (!strcmp (service_name, "import")) { vpn_import (GTK_WINDOW (self), vpn_import_complete, self); return; + } else if (!strcmp (service_name, "wireguard")) { + s_type = NM_TYPE_SETTING_WIREGUARD; } - connection = complete_vpn_connection (self, NULL); - s_vpn = nm_connection_get_setting_vpn (connection); - g_object_set (s_vpn, NM_SETTING_VPN_SERVICE_TYPE, service_name, NULL); + connection = complete_vpn_connection (self, NULL, s_type); + if (s_type == NM_TYPE_SETTING_VPN) { + s_vpn = nm_connection_get_setting_vpn (connection); + g_object_set (s_vpn, NM_SETTING_VPN_SERVICE_TYPE, service_name, NULL); + } /* Mark the connection as private to this user, and non-autoconnect */ s_con = nm_connection_get_setting_connection (connection); @@ -795,6 +819,34 @@ select_vpn_type (NetConnectionEditor *self, GtkListBox *list) gtk_list_box_append (list, row); } + /* Translators: VPN add dialog Wireguard description */ + gchar *desc = _("Free and open-source VPN solution designed for ease " + "of use, high speed performance and low attack surface."); + gchar *desc_markup = g_markup_printf_escaped ("%s", desc); + + row = gtk_list_box_row_new (); + + row_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_set_margin_start (row_box, 12); + gtk_widget_set_margin_end (row_box, 12); + gtk_widget_set_margin_top (row_box, 12); + gtk_widget_set_margin_bottom (row_box, 12); + + name_label = gtk_label_new (_("WireGuard")); + gtk_widget_set_halign (name_label, GTK_ALIGN_START); + gtk_box_append (GTK_BOX (row_box), name_label); + + desc_label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (desc_label), desc_markup); + gtk_label_set_wrap (GTK_LABEL (desc_label), TRUE); + gtk_widget_set_halign (desc_label, GTK_ALIGN_START); + gtk_widget_add_css_class (desc_label, "dim-label"); + gtk_box_append (GTK_BOX (row_box), desc_label); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), row_box); + g_object_set_data (G_OBJECT (row), "service_name", "wireguard"); + gtk_list_box_append (list, row); + /* Import */ row = gtk_list_box_row_new (); diff --git a/panels/network/connection-editor/vpn-helpers.c b/panels/network/connection-editor/vpn-helpers.c index e0358a9ebdd8d44d8ee78b685b5a6a9abcf44576..238a9162f91be4da27e43d31b1aa970804294470 100644 --- a/panels/network/connection-editor/vpn-helpers.c +++ b/panels/network/connection-editor/vpn-helpers.c @@ -167,6 +167,11 @@ import_vpn_from_file_cb (GtkWidget *dialog, gint response, gpointer user_data) } filename = g_file_get_path (file); + +#if NM_CHECK_VERSION (1,40,0) + connection = nm_conn_wireguard_import (filename, &error); +#endif + for (iter = vpn_get_plugins (); !connection && iter; iter = iter->next) { NMVpnEditorPlugin *plugin; diff --git a/panels/network/connection-editor/wireguard-page.ui b/panels/network/connection-editor/wireguard-page.ui new file mode 100644 index 0000000000000000000000000000000000000000..326a02b94a7b634ddcb190167e9828471d84abaa --- /dev/null +++ b/panels/network/connection-editor/wireguard-page.ui @@ -0,0 +1,275 @@ + + + + + + diff --git a/panels/network/connection-editor/wireguard-peer.ui b/panels/network/connection-editor/wireguard-peer.ui new file mode 100644 index 0000000000000000000000000000000000000000..41dd022ca04ae409178aeb9c3c3d7537798a396f --- /dev/null +++ b/panels/network/connection-editor/wireguard-peer.ui @@ -0,0 +1,231 @@ + + + + + 65535 + 1 + 10 + + + + left + + + 10 + 10 + 10 + 10 + vertical + 12 + top + + + grid + 6 + 5 + 6 + 1 + + + end + _Endpoint + 1 + + 0 + 0 + + + + + + An endpoint IP or hostname, followed by a colon, and then a port number. + 1 + 1 + + 1 + 0 + + + + + + end + _Public key + 1 + + 0 + 1 + + + + + + A base64 public key calculated by 'wg pubkey' from a private key. + 1 + 1 + dialog-password + 0 + 0 + + 1 + 1 + + + + + + end + _Pre-shared key + 1 + + 0 + 2 + + + + + + A base64 preshared key generated by 'wg genpsk'. Optional, and may be omitted. Adds an additional layer of symmetric-key cryptography to be mixed into the already existing public-key cryptography, for post-quantum resistance. + 1 + 0 + + 1 + dialog-password + 0 + 0 + + 1 + 2 + + + + + + end + Allowed _IPs + 1 + + 0 + 3 + + + + + + Comma-separated list of IP (v4 or v6) addresses with CIDR masks from which incoming traffic for this peer is allowed and to which outgoing traffic for this peer is directed. + 1 + 1 + + 1 + 3 + + + + + + end + _Persistent keepalive + 1 + + 0 + 4 + + + + + + 6 + + + How often to send an authenticated empty packet to the peer for the purpose of keeping a stateful firewall or NAT mapping valid. This is optional and not recommended outside of specific setups. + 1 + adjustment_keepalive + 1 + 1 + + + + + seconds + + + + 1 + 4 + + + + + + + + 1 + end + 1 + Apply + + + + + + + diff --git a/panels/network/net-vpn.c b/panels/network/net-vpn.c index 74009d52d0f9247bc647a3f8bbd00a0b83daefef..b64fad448a98c162947430fbe995eb680bc46b64 100644 --- a/panels/network/net-vpn.c +++ b/panels/network/net-vpn.c @@ -82,12 +82,19 @@ nm_device_refresh_vpn_ui (NetVpn *self) a = (NMActiveConnection*)acs->pdata[i]; auuid = nm_active_connection_get_uuid (a); - if (NM_IS_VPN_CONNECTION (a) && strcmp (auuid, uuid) == 0) { + if (strcmp (auuid, uuid) == 0) { + if (NM_IS_VPN_CONNECTION (a)) + state = nm_vpn_connection_get_vpn_state (NM_VPN_CONNECTION (a)); + else if (nm_is_wireguard_connection (a)) + state = nm_active_connection_get_state (a); + else { + /* Unknown/Unhandled type */ + break; + } self->active_connection = g_object_ref (a); g_signal_connect_object (a, "notify::vpn-state", G_CALLBACK (nm_device_refresh_vpn_ui), self, G_CONNECT_SWAPPED); - state = nm_vpn_connection_get_vpn_state (NM_VPN_CONNECTION (a)); break; } } @@ -226,3 +233,14 @@ net_vpn_get_connection (NetVpn *self) g_return_val_if_fail (NET_IS_VPN (self), NULL); return self->connection; } + +gboolean +nm_is_wireguard_connection (NMActiveConnection *c) { + const GPtrArray *devices; + devices = nm_active_connection_get_devices (c); + for (int j = 0; devices && j < devices->len; j++) { + if (NM_IS_DEVICE_WIREGUARD (g_ptr_array_index (devices, j))) + return TRUE; + } + return FALSE; +} diff --git a/panels/network/net-vpn.h b/panels/network/net-vpn.h index 183545814668a4eb9fa64c1f1a265f585ce1812d..59062572f4cb24f6a60f772ba78dcf9c63bae651 100644 --- a/panels/network/net-vpn.h +++ b/panels/network/net-vpn.h @@ -34,4 +34,6 @@ NetVpn *net_vpn_new (NMClient *client, NMConnection *net_vpn_get_connection (NetVpn *vpn); +gboolean nm_is_wireguard_connection (NMActiveConnection *c); + G_END_DECLS