summaryrefslogtreecommitdiff
path: root/clients
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2019-03-07 17:54:38 +0100
committerThomas Haller <thaller@redhat.com>2019-03-07 17:54:38 +0100
commitbb25a1c80528b737333c25ef62e350e1dc3d146b (patch)
tree6f124aa8f6d1c9287c99fad6249fcbb4dfbe8164 /clients
parent9bcd634cbf5b233905b53d46a68413d80f2483c1 (diff)
parent3990c92fbf4789d8de2264b39f9d1b690f5dd4d5 (diff)
downloadNetworkManager-bb25a1c80528b737333c25ef62e350e1dc3d146b.tar.gz
wireguard: merge branch 'th/wireguard-import'
https://github.com/NetworkManager/NetworkManager/pull/304
Diffstat (limited to 'clients')
-rw-r--r--clients/cli/common.c34
-rw-r--r--clients/cli/common.h4
-rw-r--r--clients/cli/connections.c45
-rw-r--r--clients/common/nm-client-utils.h7
-rw-r--r--clients/common/nm-vpn-helpers.c559
-rw-r--r--clients/common/nm-vpn-helpers.h3
-rw-r--r--clients/common/tests/test-general.c96
-rw-r--r--clients/common/tests/wg-test0.conf18
-rw-r--r--clients/common/tests/wg-test1.conf3
-rw-r--r--clients/common/tests/wg-test2.conf8
-rw-r--r--clients/common/tests/wg-test3.conf3
11 files changed, 754 insertions, 26 deletions
diff --git a/clients/cli/common.c b/clients/cli/common.c
index 50dd0eb5cb..3c1c315d5e 100644
--- a/clients/cli/common.c
+++ b/clients/cli/common.c
@@ -1349,23 +1349,39 @@ nmc_do_cmd (NmCli *nmc, const NMCCommand cmds[], const char *cmd, int argc, char
/**
* nmc_complete_strings:
* @prefix: a string to match
- * @...: a %NULL-terminated list of candidate strings
+ * @nargs: the number of elements in @args. Or -1 if @args is a NULL terminated
+ * strv array.
+ * @args: the argument list. If @nargs is not -1, then some elements may
+ * be %NULL to indicate to silently skip the values.
*
* Prints all the matching candidates for completion. Useful when there's
* no better way to suggest completion other than a hardcoded string list.
*/
void
-nmc_complete_strings (const char *prefix, ...)
+nmc_complete_strv (const char *prefix, gssize nargs, const char *const*args)
{
- va_list args;
- const char *candidate;
+ gsize i, n;
+
+ if (prefix && !prefix[0])
+ prefix = NULL;
+
+ if (nargs < 0) {
+ nm_assert (nargs == -1);
+ n = NM_PTRARRAY_LEN (args);
+ } else
+ n = (gsize) nargs;
+
+ for (i = 0; i < n; i++) {
+ const char *candidate = args[i];
- va_start (args, prefix);
- while ((candidate = va_arg (args, const char *))) {
- if (!*prefix || matches (prefix, candidate))
- g_print ("%s\n", candidate);
+ if (!candidate)
+ continue;
+ if ( prefix
+ && !matches (prefix, candidate))
+ continue;
+
+ g_print ("%s\n", candidate);
}
- va_end (args);
}
/**
diff --git a/clients/cli/common.h b/clients/cli/common.h
index 71734acc98..688f2819f6 100644
--- a/clients/cli/common.h
+++ b/clients/cli/common.h
@@ -88,7 +88,9 @@ typedef struct {
void nmc_do_cmd (NmCli *nmc, const NMCCommand cmds[], const char *cmd, int argc, char **argv);
-void nmc_complete_strings (const char *prefix, ...) G_GNUC_NULL_TERMINATED;
+void nmc_complete_strv (const char *prefix, gssize nargs, const char *const*args);
+
+#define nmc_complete_strings(prefix, ...) nmc_complete_strv ((prefix), NM_NARG (__VA_ARGS__), (const char *const[]) { __VA_ARGS__ })
void nmc_complete_bool (const char *prefix);
diff --git a/clients/cli/connections.c b/clients/cli/connections.c
index 63cb0bb90c..6db44f8760 100644
--- a/clients/cli/connections.c
+++ b/clients/cli/connections.c
@@ -8888,8 +8888,11 @@ do_connection_import (NmCli *nmc, int argc, char **argv)
}
while (argc > 0) {
- if (argc == 1 && nmc->complete)
- nmc_complete_strings (*argv, "type", "file", NULL);
+ if (argc == 1 && nmc->complete) {
+ nmc_complete_strings (*argv,
+ type ? NULL : "type",
+ filename ? NULL : "file");
+ }
if (strcmp (*argv, "type") == 0) {
argc--;
@@ -8899,8 +8902,13 @@ do_connection_import (NmCli *nmc, int argc, char **argv)
NMC_RETURN (nmc, NMC_RESULT_ERROR_USER_INPUT);
}
- if (argc == 1 && nmc->complete)
- complete_option ((const NMMetaAbstractInfo *) nm_meta_property_info_vpn_service_type, *argv, NULL);
+ if ( argc == 1
+ && nmc->complete) {
+ nmc_complete_strings (*argv, "wireguard");
+ complete_option ((const NMMetaAbstractInfo *) nm_meta_property_info_vpn_service_type,
+ *argv,
+ NULL);
+ }
if (!type)
type = *argv;
@@ -8940,21 +8948,26 @@ do_connection_import (NmCli *nmc, int argc, char **argv)
NMC_RETURN (nmc, NMC_RESULT_ERROR_USER_INPUT);
}
- service_type = nm_vpn_plugin_info_list_find_service_type (nm_vpn_get_plugin_infos (), type);
- if (!service_type) {
- g_string_printf (nmc->return_text, _("Error: failed to find VPN plugin for %s."), type);
- NMC_RETURN (nmc, NMC_RESULT_ERROR_UNKNOWN);
- }
+ if (nm_streq (type, "wireguard"))
+ connection = nm_vpn_wireguard_import (filename, &error);
+ else {
+ service_type = nm_vpn_plugin_info_list_find_service_type (nm_vpn_get_plugin_infos (), type);
+ if (!service_type) {
+ g_string_printf (nmc->return_text, _("Error: failed to find VPN plugin for %s."), type);
+ NMC_RETURN (nmc, NMC_RESULT_ERROR_UNKNOWN);
+ }
- /* Import VPN configuration */
- plugin = nm_vpn_get_editor_plugin (service_type, &error);
- if (!plugin) {
- g_string_printf (nmc->return_text, _("Error: failed to load VPN plugin: %s."),
- error->message);
- NMC_RETURN (nmc, NMC_RESULT_ERROR_UNKNOWN);
+ /* Import VPN configuration */
+ plugin = nm_vpn_get_editor_plugin (service_type, &error);
+ if (!plugin) {
+ g_string_printf (nmc->return_text, _("Error: failed to load VPN plugin: %s."),
+ error->message);
+ NMC_RETURN (nmc, NMC_RESULT_ERROR_UNKNOWN);
+ }
+
+ connection = nm_vpn_editor_plugin_import (plugin, filename, &error);
}
- connection = nm_vpn_editor_plugin_import (plugin, filename, &error);
if (!connection) {
g_string_printf (nmc->return_text, _("Error: failed to import '%s': %s."),
filename, error->message);
diff --git a/clients/common/nm-client-utils.h b/clients/common/nm-client-utils.h
index 9d818873c0..a5bc05fab0 100644
--- a/clients/common/nm-client-utils.h
+++ b/clients/common/nm-client-utils.h
@@ -24,6 +24,13 @@
#include "nm-active-connection.h"
#include "nm-device.h"
+
+#define nm_auto_unref_ip_address nm_auto (_nm_ip_address_unref)
+NM_AUTO_DEFINE_FCN0 (NMIPAddress *, _nm_ip_address_unref, nm_ip_address_unref)
+
+#define nm_auto_unref_wgpeer nm_auto (_nm_auto_unref_wgpeer)
+NM_AUTO_DEFINE_FCN0 (NMWireGuardPeer *, _nm_auto_unref_wgpeer, nm_wireguard_peer_unref)
+
const NMObject **nmc_objects_sort_by_path (const NMObject *const*objs, gssize len);
const char *nmc_string_is_valid (const char *input, const char **allowed, GError **error);
diff --git a/clients/common/nm-vpn-helpers.c b/clients/common/nm-vpn-helpers.c
index a2e7dc0374..2353b7691e 100644
--- a/clients/common/nm-vpn-helpers.c
+++ b/clients/common/nm-vpn-helpers.c
@@ -25,7 +25,13 @@
#include "nm-vpn-helpers.h"
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#include "nm-client-utils.h"
#include "nm-utils.h"
+#include "nm-utils/nm-io-utils.h"
+#include "nm-utils/nm-secret-utils.h"
/*****************************************************************************/
@@ -247,3 +253,556 @@ nm_vpn_openconnect_authenticate_helper (const char *host,
return TRUE;
}
+static gboolean
+_wg_complete_peer (GPtrArray **p_peers,
+ NMWireGuardPeer *peer_take,
+ gsize peer_start_line_nr,
+ const char *filename,
+ GError **error)
+{
+ nm_auto_unref_wgpeer NMWireGuardPeer *peer = peer_take;
+ gs_free_error GError *local = NULL;
+
+ if (!peer)
+ return TRUE;
+
+ if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &local)) {
+ nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+ _("Invalid peer starting at %s:%zu: %s"),
+ filename,
+ peer_start_line_nr,
+ local->message);
+ return FALSE;
+ }
+
+ if (!*p_peers)
+ *p_peers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_wireguard_peer_unref);
+ g_ptr_array_add (*p_peers, g_steal_pointer (&peer));
+ return TRUE;
+}
+
+static gboolean
+_line_match (char *line, const char *key, gsize key_len, const char **out_key, char **out_value)
+{
+ nm_assert (line);
+ nm_assert (key);
+ nm_assert (strlen (key) == key_len);
+ nm_assert (!strchr (key, '='));
+ nm_assert (out_key && !*out_key);
+ nm_assert (out_value && !*out_value);
+
+ /* Note that `wg-quick` (linux.bash) does case-insensitive comparison (shopt -s nocasematch).
+ * `wg setconf` does case-insensitive comparison too (with strncasecmp, which is locale dependent).
+ *
+ * We do a case-insensitive comparison of the key, however in a locale-independent manner. */
+
+ if (g_ascii_strncasecmp (line, key, key_len) != 0)
+ return FALSE;
+
+ if (line[key_len] != '=')
+ return FALSE;
+
+ *out_key = key;
+ *out_value = &line[key_len + 1];
+ return TRUE;
+}
+
+#define line_match(line, key, out_key, out_value) \
+ _line_match ((line), ""key"", NM_STRLEN (key), (out_key), (out_value))
+
+static gboolean
+value_split_word (char **line_remainder, char **out_word)
+{
+ char *str;
+
+ if ((*line_remainder)[0] == '\0')
+ return FALSE;
+
+ *out_word = *line_remainder;
+
+ str = strchrnul (*line_remainder, ',');
+ if (str[0] == ',') {
+ str[0] = '\0';
+ *line_remainder = &str[1];
+ } else
+ *line_remainder = str;
+ return TRUE;
+}
+
+NMConnection *
+nm_vpn_wireguard_import (const char *filename,
+ GError **error)
+{
+ nm_auto_clear_secret_ptr NMSecretPtr file_content = NM_SECRET_PTR_INIT ();
+ char ifname[IFNAMSIZ];
+ gs_free char *uuid = NULL;
+ gboolean ifname_valid = FALSE;
+ const char *cstr;
+ char *line_remainder;
+ gs_unref_object NMConnection *connection = NULL;
+ NMSettingConnection *s_con;
+ NMSettingIPConfig *s_ip4;
+ NMSettingIPConfig *s_ip6;
+ NMSettingWireGuard *s_wg;
+ gs_free_error GError *local = NULL;
+ enum {
+ LINE_CONTEXT_INIT,
+ LINE_CONTEXT_INTERFACE,
+ LINE_CONTEXT_PEER,
+ } line_context;
+ gsize line_nr;
+ gsize current_peer_start_line_nr = 0;
+ nm_auto_unref_wgpeer NMWireGuardPeer *current_peer = NULL;
+ gs_unref_ptrarray GPtrArray *data_dns_v4 = NULL;
+ gs_unref_ptrarray GPtrArray *data_dns_v6 = NULL;
+ gs_unref_ptrarray GPtrArray *data_addr_v4 = NULL;
+ gs_unref_ptrarray GPtrArray *data_addr_v6 = NULL;
+ gs_unref_ptrarray GPtrArray *data_peers = NULL;
+ const char *data_private_key = NULL;
+ gint64 data_table;
+ guint data_listen_port = 0;
+ guint data_fwmark = 0;
+ guint data_mtu = 0;
+ int is_v4;
+ guint i;
+
+ g_return_val_if_fail (filename, NULL);
+ g_return_val_if_fail (!error || !*error, NULL);
+
+ /* contrary to "wg-quick", we never interpret the filename as "/etc/wireguard/$INTERFACE.conf".
+ * If the filename has no '/', it is interpreted as relative to the current working directory.
+ * However, we do require a suitable filename suffix and that the name corresponds to the interface
+ * name. */
+ cstr = strrchr (filename, '/');
+ cstr = cstr ? &cstr[1] : filename;
+ if (NM_STR_HAS_SUFFIX (cstr, ".conf")) {
+ gsize len = strlen (cstr) - NM_STRLEN (".conf");
+
+ if (len > 0 && len < sizeof (ifname)) {
+ memcpy (ifname, cstr, len);
+ ifname[len] = '\0';
+
+ if (nm_utils_is_valid_iface_name (ifname, NULL))
+ ifname_valid = TRUE;
+ }
+ }
+ if (!ifname_valid) {
+ nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN,
+ _("The WireGuard config file must be a valid interface name followed by \".conf\""));
+ return FALSE;
+ }
+
+ if (nm_utils_file_get_contents (-1,
+ filename,
+ 10*1024*1024,
+ NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET,
+ &file_content.str,
+ &file_content.len,
+ error) < 0)
+ return NULL;
+
+ /* We interpret the file like `wg-quick up` and `wg setconf` do.
+ *
+ * Of course the WireGuard scripts do something fundamentlly different. They
+ * perform actions to configure the WireGuard link in kernel, add routes and
+ * addresses, and call resolvconf. It all happens at the time when the script
+ * run.
+ *
+ * This code here instead generates a NetworkManager connection profile so that
+ * NetworkManager will apply a similar configuration when later activating the profile. */
+
+#define _TABLE_AUTO ((gint64) -1)
+#define _TABLE_OFF ((gint64) -2)
+
+ data_table = _TABLE_AUTO;
+
+ line_remainder = file_content.str;
+ line_context = LINE_CONTEXT_INIT;
+ line_nr = 0;
+ while (line_remainder[0] != '\0') {
+ const char *matched_key = NULL;
+ char *value = NULL;
+ char *line;
+ char ch;
+ gint64 i64;
+
+ line_nr++;
+
+ line = line_remainder;
+ line_remainder = strchrnul (line, '\n');
+ if (line_remainder[0] != '\0')
+ (line_remainder++)[0] = '\0';
+
+ /* Drop all spaces and truncate at first '#'.
+ * See wg's config_read_line().
+ *
+ * Note that wg-quick doesn't do that.
+ *
+ * Neither `wg setconf` nor `wg-quick` does a strict parsing.
+ * We don't either. Just try to interpret the file (mostly) the same as
+ * they would.
+ */
+ {
+ gsize l, n;
+
+ n = 0;
+ for (l = 0; (ch = line[l]); l++) {
+ if (g_ascii_isspace (ch)) {
+ /* wg-setconf strips all whitespace before parsing the content. That means,
+ * *[I nterface]" will be accepted. We do that too. */
+ continue;
+ }
+ if (ch == '#')
+ break;
+ line[n++] = line[l];
+ }
+ if (n == 0)
+ continue;
+ line[n] = '\0';
+ }
+
+ if (g_ascii_strcasecmp (line, "[Interface]") == 0) {
+ if (!_wg_complete_peer (&data_peers,
+ g_steal_pointer (&current_peer),
+ current_peer_start_line_nr,
+ filename,
+ error))
+ return FALSE;
+ line_context = LINE_CONTEXT_INTERFACE;
+ continue;
+ }
+
+ if (g_ascii_strcasecmp (line, "[Peer]") == 0) {
+ if (!_wg_complete_peer (&data_peers,
+ g_steal_pointer (&current_peer),
+ current_peer_start_line_nr,
+ filename,
+ error))
+ return FALSE;
+ current_peer_start_line_nr = line_nr;
+ current_peer = nm_wireguard_peer_new ();
+ line_context = LINE_CONTEXT_PEER;
+ continue;
+ }
+
+ if (line_context == LINE_CONTEXT_INTERFACE) {
+
+ if (line_match (line, "Address", &matched_key, &value)) {
+ char *value_word;
+
+ while (value_split_word (&value, &value_word)) {
+ GPtrArray **p_data_addr;
+ NMIPAddr addr_bin;
+ int addr_family;
+ int prefix_len;
+
+ if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC,
+ value_word,
+ &addr_family,
+ &addr_bin,
+ &prefix_len))
+ goto fail_invalid_value;
+
+ p_data_addr = (addr_family == AF_INET)
+ ? &data_addr_v4
+ : &data_addr_v6;
+
+ if (!*p_data_addr)
+ *p_data_addr = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_address_unref);
+
+ g_ptr_array_add (*p_data_addr,
+ nm_ip_address_new_binary (addr_family,
+ &addr_bin,
+ prefix_len == -1
+ ? ((addr_family == AF_INET) ? 32 : 128)
+ : prefix_len,
+ NULL));
+ }
+ continue;
+ }
+
+ if (line_match (line, "MTU", &matched_key, &value)) {
+ i64 = _nm_utils_ascii_str_to_int64 (value, 0, 0, G_MAXUINT32, -1);
+ if (i64 == -1)
+ goto fail_invalid_value;
+
+ /* wg-quick accepts the "MTU" value, but it also fetches routes to
+ * autodetect it. NetworkManager won't do that, we can only configure
+ * an explict MTU or no autodetection will be performed. */
+ data_mtu = i64;
+ continue;
+ }
+
+ if (line_match (line, "DNS", &matched_key, &value)) {
+ char *value_word;
+
+ while (value_split_word (&value, &value_word)) {
+ char addr_s[NM_CONST_MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
+ GPtrArray **p_data_dns;
+ NMIPAddr addr_bin;
+ int addr_family;
+
+ if (!nm_utils_parse_inaddr_bin (AF_UNSPEC,
+ value_word,
+ &addr_family,
+ &addr_bin))
+ goto fail_invalid_value;
+
+ p_data_dns = (addr_family == AF_INET)
+ ? &data_dns_v4
+ : &data_dns_v6;
+ if (!*p_data_dns)
+ *p_data_dns = g_ptr_array_new_with_free_func (g_free);
+
+ inet_ntop (addr_family, &addr_bin, addr_s, sizeof (addr_s));
+ g_ptr_array_add (*p_data_dns, g_strdup (addr_s));
+ }
+ continue;
+ }
+
+ if (line_match (line, "Table", &matched_key, &value)) {
+
+ if (nm_streq (value, "auto"))
+ data_table = _TABLE_AUTO;
+ else if (nm_streq (value, "off"))
+ data_table = _TABLE_OFF;
+ else {
+ /* we don't support table names from /etc/iproute2/rt_tables
+ * But we accept hex like `ip route add` would. */
+ i64 = _nm_utils_ascii_str_to_int64 (value, 0, 0, G_MAXINT32, -1);
+ if (i64 == -1)
+ goto fail_invalid_value;
+ data_table = i64;
+ }
+ continue;
+ }
+
+ if ( line_match (line, "PreUp", &matched_key, &value)
+ || line_match (line, "PreDown", &matched_key, &value)
+ || line_match (line, "PostUp", &matched_key, &value)
+ || line_match (line, "PostDown", &matched_key, &value)) {
+ /* we don't run any scripts. Silently ignore these paramters. */
+ continue;
+ }
+
+ if (line_match (line, "SaveConfig", &matched_key, &value)) {
+ /* we ignore the setting, but enforce that it's either true or false (like
+ * wg-quick. */
+ if (!NM_IN_STRSET (value, "true", "false"))
+ goto fail_invalid_value;
+ continue;
+ }
+
+ if (line_match (line, "ListenPort", &matched_key, &value)) {
+ /* we don't use getaddrinfo(), unlike `wg setconf`. Just interpret
+ * the port as plain decimal number. */
+ i64 = _nm_utils_ascii_str_to_int64 (value, 10, 0, 0xFFFF, -1);
+ if (i64 == -1)
+ goto fail_invalid_value;
+ data_listen_port = i64;
+ continue;
+ }
+
+ if (line_match (line, "FwMark", &matched_key, &value)) {
+ if (nm_streq (value, "off"))
+ data_fwmark = 0;
+ else {
+ i64 = _nm_utils_ascii_str_to_int64 (value, 0, 0, G_MAXINT32, -1);
+ if (i64 == -1)
+ goto fail_invalid_value;
+ data_fwmark = i64;
+ }
+ continue;
+ }
+
+ if (line_match (line, "PrivateKey", &matched_key, &value)) {
+ if (!nm_utils_base64secret_decode (value, NM_WIREGUARD_PUBLIC_KEY_LEN, NULL))
+ goto fail_invalid_secret;
+ data_private_key = value;
+ continue;
+ }
+
+ goto fail_invalid_line;
+ }
+
+
+ if (line_context == LINE_CONTEXT_PEER) {
+
+ if (line_match (line, "Endpoint", &matched_key, &value)) {
+ if (!nm_wireguard_peer_set_endpoint (current_peer, value, FALSE))
+ goto fail_invalid_value;
+ continue;
+ }
+
+ if (line_match (line, "PublicKey", &matched_key, &value)) {
+ if (!nm_wireguard_peer_set_public_key (current_peer, value, FALSE))
+ goto fail_invalid_value;
+ continue;
+ }
+
+ if (line_match (line, "AllowedIPs", &matched_key, &value)) {
+ char *value_word;
+
+ while (value_split_word (&value, &value_word)) {
+ if (!nm_wireguard_peer_append_allowed_ip (current_peer,
+ value_word,
+ FALSE))
+ goto fail_invalid_value;
+ }
+ continue;
+ }
+
+ if (line_match (line, "PersistentKeepalive", &matched_key, &value)) {
+ if (nm_streq (value, "off"))
+ i64 = 0;
+ else {
+ i64 = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT16, -1);
+ if (i64 == -1)
+ goto fail_invalid_value;
+ }
+ nm_wireguard_peer_set_persistent_keepalive (current_peer, i64);
+ continue;
+ }
+
+ if (line_match (line, "PresharedKey", &matched_key, &value)) {
+ if (!nm_wireguard_peer_set_preshared_key (current_peer, value, FALSE))
+ goto fail_invalid_secret;
+ nm_wireguard_peer_set_preshared_key_flags (current_peer, NM_SETTING_SECRET_FLAG_NONE);
+ continue;
+ }
+
+ goto fail_invalid_line;
+ }
+
+fail_invalid_line:
+ nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT,
+ _("unrecognized line at %s:%zu"),
+ filename, line_nr);
+ return FALSE;
+fail_invalid_value:
+ nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT,
+ _("invalid value for '%s' at %s:%zu"),
+ matched_key, filename, line_nr);
+ return FALSE;
+fail_invalid_secret:
+ nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT,
+ _("invalid secret '%s' at %s:%zu"),
+ matched_key, filename, line_nr);
+ return FALSE;
+ }
+
+ if (!_wg_complete_peer (&data_peers,
+ g_steal_pointer (&current_peer),
+ current_peer_start_line_nr,
+ filename,
+ error))
+ return FALSE;
+
+ connection = nm_simple_connection_new ();
+ s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
+ nm_connection_add_setting (connection, NM_SETTING (s_con));
+ s_ip4 = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
+ nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+ s_ip6 = NM_SETTING_IP_CONFIG (nm_setting_ip6_config_new ());
+ nm_connection_add_setting (connection, NM_SETTING (s_ip6));
+ s_wg = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
+ nm_connection_add_setting (connection, NM_SETTING (s_wg));
+
+ uuid = nm_utils_uuid_generate ();
+
+ g_object_set (s_con,
+ NM_SETTING_CONNECTION_ID,
+ ifname,
+ NM_SETTING_CONNECTION_UUID,
+ uuid,
+ NM_SETTING_CONNECTION_TYPE,
+ NM_SETTING_WIREGUARD_SETTING_NAME,
+ NM_SETTING_CONNECTION_INTERFACE_NAME,
+ ifname,
+ NULL);
+
+ g_object_set (s_wg,
+ NM_SETTING_WIREGUARD_PRIVATE_KEY,
+ data_private_key,
+ NM_SETTING_WIREGUARD_LISTEN_PORT,
+ data_listen_port,
+ NM_SETTING_WIREGUARD_FWMARK,
+ data_fwmark,
+ NM_SETTING_WIREGUARD_MTU,
+ data_mtu,
+ NULL);
+
+ if (data_peers) {
+ for (i = 0; i < data_peers->len; i++)
+ nm_setting_wireguard_append_peer (s_wg, data_peers->pdata[i]);
+ }
+
+ for (is_v4 = 0; is_v4 < 2; is_v4++) {
+ const char *method_disabled = is_v4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
+ const char *method_manual = is_v4 ? NM_SETTING_IP4_CONFIG_METHOD_MANUAL : NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
+ NMSettingIPConfig *s_ip = is_v4 ? s_ip4 : s_ip6;
+ GPtrArray *data_dns = is_v4 ? data_dns_v4 : data_dns_v6;
+ GPtrArray *data_addr = is_v4 ? data_addr_v4 : data_addr_v6;
+
+ if (data_dns && !data_addr) {
+ /* When specifying "DNS", we also require an "Address" for the same address
+ * family. That is because a NMSettingIPConfig cannot have @method_disabled
+ * and DNS settings at the same time.
+ *
+ * We don't have addresses. Silently ignore the DNS setting. */
+ data_dns = NULL;
+ }
+
+ g_object_set (s_ip,
+ NM_SETTING_IP_CONFIG_METHOD,
+ data_addr ? method_manual : method_disabled,
+ NULL);
+
+ if (data_addr) {
+ for (i = 0; i < data_addr->len; i++)
+ nm_setting_ip_config_add_address (s_ip, data_addr->pdata[i]);
+ }
+ if (data_dns) {
+ for (i = 0; i < data_dns->len; i++)
+ nm_setting_ip_config_add_dns (s_ip, data_dns->pdata[i]);
+ }
+
+ if (data_table == _TABLE_AUTO) {
+ /* in the "auto" setting, wg-quick adds peer-routes automatically to the main
+ * table. NetworkManager will do that too, but there are differences:
+ *
+ * - NetworkManager (contrary to wg-quick) does not check whether the peer-route is necessary.
+ * It will always add a route for each allowed-ips range, even if there is already another
+ * route that would ensure packets to the endpoint are routed via the WireGuard interface.
+ * If you don't want that, disable "wireguard.peer-routes", and add the necessary routes
+ * yourself to "ipv4.routes" and "ipv6.routes".
+ *
+ * - With "auto", wg-quick also configures policy routing to handle default-routes (/0) to
+ * avoid routing loops. That is not yet solved by NetworkManager, you need to configure
+ * that explicitly (for example, by adding a direct route to the gateway on the interface
+ * that has the default-route, or by using a script (possibly dispatcher script).
+ */
+ } else if (data_table == _TABLE_OFF) {
+ if (is_v4) {
+ g_object_set (s_wg,
+ NM_SETTING_WIREGUARD_PEER_ROUTES,
+ FALSE,
+ NULL);
+ }
+ } else {
+ g_object_set (s_ip,
+ NM_SETTING_IP_CONFIG_ROUTE_TABLE,
+ (guint) data_table,
+ NULL);
+ }
+ }
+
+ if (!nm_connection_normalize (connection, NULL, NULL, &local)) {
+ nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT,
+ _("Failed to create WireGuard connection: %s"),
+ local->message);
+ return FALSE;
+ }
+
+ return g_steal_pointer (&connection);
+}
diff --git a/clients/common/nm-vpn-helpers.h b/clients/common/nm-vpn-helpers.h
index 4c15faa1b8..686687ba4d 100644
--- a/clients/common/nm-vpn-helpers.h
+++ b/clients/common/nm-vpn-helpers.h
@@ -39,4 +39,7 @@ gboolean nm_vpn_openconnect_authenticate_helper (const char *host,
int *status,
GError **error);
+NMConnection *nm_vpn_wireguard_import (const char *filename,
+ GError **error);
+
#endif /* __NM_VPN_HELPERS_H__ */
diff --git a/clients/common/tests/test-general.c b/clients/common/tests/test-general.c
index 8f96eb262f..c39901bae8 100644
--- a/clients/common/tests/test-general.c
+++ b/clients/common/tests/test-general.c
@@ -20,6 +20,7 @@
#include "nm-default.h"
#include "nm-meta-setting-access.h"
+#include "nm-vpn-helpers.h"
#include "nm-utils/nm-test-utils.h"
@@ -145,6 +146,96 @@ test_client_meta_check (void)
/*****************************************************************************/
+static void
+test_client_import_wireguard_test0 (void)
+{
+ gs_unref_object NMConnection *connection;
+ NMSettingWireGuard *s_wg;
+ NMWireGuardPeer *peer;
+ gs_free_error GError *error = NULL;
+
+ connection = nm_vpn_wireguard_import (NM_BUILD_SRCDIR"/clients/common/tests/wg-test0.conf",
+ &error);
+
+ g_assert_no_error (error);
+
+ g_assert_cmpstr (nm_connection_get_id (connection), ==, "wg-test0");
+ g_assert_cmpstr (nm_connection_get_interface_name (connection), ==, "wg-test0");
+ g_assert_cmpstr (nm_connection_get_connection_type (connection), ==, NM_SETTING_WIREGUARD_SETTING_NAME);
+
+ s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+
+ g_assert_cmpint (nm_setting_wireguard_get_listen_port (s_wg), ==, 51820);
+ g_assert_cmpstr (nm_setting_wireguard_get_private_key (s_wg), ==, "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=");
+
+ g_assert_cmpint (nm_setting_wireguard_get_peers_len (s_wg), ==, 3);
+
+ peer = nm_setting_wireguard_get_peer (s_wg, 0);
+ g_assert_cmpstr (nm_wireguard_peer_get_public_key (peer), ==, "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=");
+ g_assert_cmpstr (nm_wireguard_peer_get_endpoint (peer), ==, "192.95.5.67:1234");
+ g_assert_cmpint (nm_wireguard_peer_get_allowed_ips_len (peer), ==, 2);
+ g_assert_cmpstr (nm_wireguard_peer_get_allowed_ip (peer, 0, NULL), ==, "10.192.122.3/32");
+ g_assert_cmpstr (nm_wireguard_peer_get_allowed_ip (peer, 1, NULL), ==, "10.192.124.1/24");
+
+ peer = nm_setting_wireguard_get_peer (s_wg, 1);
+ g_assert_cmpstr (nm_wireguard_peer_get_public_key (peer), ==, "TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=");
+ g_assert_cmpstr (nm_wireguard_peer_get_endpoint (peer), ==, "[2607:5300:60:6b0::c05f:543]:2468");
+ g_assert_cmpint (nm_wireguard_peer_get_allowed_ips_len (peer), ==, 2);
+ g_assert_cmpstr (nm_wireguard_peer_get_allowed_ip (peer, 0, NULL), ==, "10.192.122.4/32");
+ g_assert_cmpstr (nm_wireguard_peer_get_allowed_ip (peer, 1, NULL), ==, "192.168.0.0/16");
+
+ peer = nm_setting_wireguard_get_peer (s_wg, 2);
+ g_assert_cmpstr (nm_wireguard_peer_get_public_key (peer), ==, "gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=");
+ g_assert_cmpstr (nm_wireguard_peer_get_endpoint (peer), ==, "test.wireguard.com:18981");
+ g_assert_cmpint (nm_wireguard_peer_get_allowed_ips_len (peer), ==, 1);
+ g_assert_cmpstr (nm_wireguard_peer_get_allowed_ip (peer, 0, NULL), ==, "10.10.10.230/32");
+}
+
+static void
+test_client_import_wireguard_test1 (void)
+{
+ gs_free_error GError *error = NULL;
+
+ nm_vpn_wireguard_import (NM_BUILD_SRCDIR"/clients/common/tests/wg-test1.conf", &error);
+ g_assert_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT);
+ g_assert (g_str_has_prefix (error->message, "invalid secret 'PrivateKey'"));
+ g_assert (g_str_has_suffix (error->message, "wg-test1.conf:2"));
+}
+
+static void
+test_client_import_wireguard_test2 (void)
+{
+ gs_free_error GError *error = NULL;
+
+ nm_vpn_wireguard_import (NM_BUILD_SRCDIR"/clients/common/tests/wg-test2.conf", &error);
+
+ g_assert_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT);
+ g_assert (g_str_has_prefix (error->message, "unrecognized line at"));
+ g_assert (g_str_has_suffix (error->message, "wg-test2.conf:5"));
+}
+
+static void
+test_client_import_wireguard_test3 (void)
+{
+ gs_free_error GError *error = NULL;
+
+ nm_vpn_wireguard_import (NM_BUILD_SRCDIR"/clients/common/tests/wg-test3.conf", &error);
+ g_assert_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT);
+ g_assert (g_str_has_prefix (error->message, "invalid value for 'ListenPort'"));
+ g_assert (g_str_has_suffix (error->message, "wg-test3.conf:3"));
+}
+
+static void
+test_client_import_wireguard_missing (void)
+{
+ gs_free_error GError *error = NULL;
+
+ nm_vpn_wireguard_import (NM_BUILD_SRCDIR"/clients/common/tests/wg-missing.conf", &error);
+ g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT);
+}
+
+/*****************************************************************************/
+
NMTST_DEFINE ();
int
@@ -153,6 +244,11 @@ main (int argc, char **argv)
nmtst_init (&argc, &argv, TRUE);
g_test_add_func ("/client/meta/check", test_client_meta_check);
+ g_test_add_func ("/client/import/wireguard/test0", test_client_import_wireguard_test0);
+ g_test_add_func ("/client/import/wireguard/test1", test_client_import_wireguard_test1);
+ g_test_add_func ("/client/import/wireguard/test2", test_client_import_wireguard_test2);
+ g_test_add_func ("/client/import/wireguard/test3", test_client_import_wireguard_test3);
+ g_test_add_func ("/client/import/wireguard/missing", test_client_import_wireguard_missing);
return g_test_run ();
}
diff --git a/clients/common/tests/wg-test0.conf b/clients/common/tests/wg-test0.conf
new file mode 100644
index 0000000000..61438c2942
--- /dev/null
+++ b/clients/common/tests/wg-test0.conf
@@ -0,0 +1,18 @@
+[Interface]
+PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
+ListenPort = 51820
+
+[Peer]
+PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
+Endpoint = 192.95.5.67:1234
+AllowedIPs = 10.192.122.3/32, 10.192.124.1/24
+
+[Peer]
+PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
+Endpoint = [2607:5300:60:6b0::c05f:543]:2468
+AllowedIPs = 10.192.122.4/32, 192.168.0.0/16
+
+[Peer]
+PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
+Endpoint = test.wireguard.com:18981
+AllowedIPs = 10.10.10.230/32
diff --git a/clients/common/tests/wg-test1.conf b/clients/common/tests/wg-test1.conf
new file mode 100644
index 0000000000..ceb267acba
--- /dev/null
+++ b/clients/common/tests/wg-test1.conf
@@ -0,0 +1,3 @@
+[Interface]
+PrivateKey = bad
+ListenPort = 51820
diff --git a/clients/common/tests/wg-test2.conf b/clients/common/tests/wg-test2.conf
new file mode 100644
index 0000000000..f691c2af65
--- /dev/null
+++ b/clients/common/tests/wg-test2.conf
@@ -0,0 +1,8 @@
+[Interface]
+PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
+ListenPort = 51820
+
+[Beer]
+PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
+Endpoint = 192.95.5.67:1234
+AllowedIPs = 10.192.122.3/32, 10.192.124.1/24
diff --git a/clients/common/tests/wg-test3.conf b/clients/common/tests/wg-test3.conf
new file mode 100644
index 0000000000..9956e4004d
--- /dev/null
+++ b/clients/common/tests/wg-test3.conf
@@ -0,0 +1,3 @@
+[Interface]
+PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
+ListenPort = 666666666