// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2013 - 2015 Red Hat, Inc.
*/
/**
* SECTION:nm-vpn-helpers
* @short_description: VPN-related utilities
*/
#include "nm-default.h"
#include "nm-vpn-helpers.h"
#include <arpa/inet.h>
#include <net/if.h>
#include "nm-client-utils.h"
#include "nm-utils.h"
#include "nm-glib-aux/nm-io-utils.h"
#include "nm-glib-aux/nm-secret-utils.h"
/*****************************************************************************/
NMVpnEditorPlugin *
nm_vpn_get_editor_plugin (const char *service_type, GError **error)
{
NMVpnEditorPlugin *plugin = NULL;
NMVpnPluginInfo *plugin_info;
gs_free_error GError *local = NULL;
g_return_val_if_fail (service_type, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
plugin_info = nm_vpn_plugin_info_list_find_by_service (nm_vpn_get_plugin_infos (), service_type);
if (!plugin_info) {
g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_FAILED,
_("unknown VPN plugin \"%s\""), service_type);
return NULL;
}
plugin = nm_vpn_plugin_info_get_editor_plugin (plugin_info);
if (!plugin)
plugin = nm_vpn_plugin_info_load_editor_plugin (plugin_info, &local);
if (!plugin) {
if ( !nm_vpn_plugin_info_get_plugin (plugin_info)
&& nm_vpn_plugin_info_lookup_property (plugin_info, NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "properties")) {
g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_FAILED,
_("cannot load legacy-only VPN plugin \"%s\" for \"%s\""),
nm_vpn_plugin_info_get_name (plugin_info),
nm_vpn_plugin_info_get_filename (plugin_info));
} else if (g_error_matches (local, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_FAILED,
_("cannot load VPN plugin \"%s\" due to missing \"%s\". Missing client plugin?"),
nm_vpn_plugin_info_get_name (plugin_info),
nm_vpn_plugin_info_get_plugin (plugin_info));
} else {
g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_FAILED,
_("failed to load VPN plugin \"%s\": %s"),
nm_vpn_plugin_info_get_name (plugin_info),
local->message);
}
return NULL;
}
return plugin;
}
GSList *
nm_vpn_get_plugin_infos (void)
{
static bool plugins_loaded;
static GSList *plugins = NULL;
if (G_LIKELY (plugins_loaded))
return plugins;
plugins_loaded = TRUE;
plugins = nm_vpn_plugin_info_list_load ();
return plugins;
}
gboolean
nm_vpn_supports_ipv6 (NMConnection *connection)
{
NMSettingVpn *s_vpn;
const char *service_type;
NMVpnEditorPlugin *plugin;
guint32 capabilities;
s_vpn = nm_connection_get_setting_vpn (connection);
g_return_val_if_fail (s_vpn != NULL, FALSE);
service_type = nm_setting_vpn_get_service_type (s_vpn);
if (!service_type)
return FALSE;
plugin = nm_vpn_get_editor_plugin (service_type, NULL);
if (!plugin)
return FALSE;
capabilities = nm_vpn_editor_plugin_get_capabilities (plugin);
return NM_FLAGS_HAS (capabilities, NM_VPN_EDITOR_PLUGIN_CAPABILITY_IPV6);
}
const NmcVpnPasswordName *
nm_vpn_get_secret_names (const char *service_type)
{
const char *type;
if (!service_type)
return NULL;
if ( !NM_STR_HAS_PREFIX (service_type, NM_DBUS_INTERFACE)
|| service_type[NM_STRLEN (NM_DBUS_INTERFACE)] != '.') {
/* all our well-known, hard-coded vpn-types start with NM_DBUS_INTERFACE. */
return NULL;
}
type = service_type + (NM_STRLEN (NM_DBUS_INTERFACE) + 1);
#define _VPN_PASSWORD_LIST(...) \
({ \
static const NmcVpnPasswordName _arr[] = { \
__VA_ARGS__ \
{ 0 }, \
}; \
_arr; \
})
if (NM_IN_STRSET (type, "pptp",
"iodine",
"ssh",
"l2tp",
"fortisslvpn")) {
return _VPN_PASSWORD_LIST (
{ "password", N_("Password") },
);
}
if (NM_IN_STRSET (type, "openvpn")) {
return _VPN_PASSWORD_LIST (
{ "password", N_("Password") },
{ "cert-pass", N_("Certificate password") },
{ "http-proxy-password", N_("HTTP proxy password") },
);
}
if (NM_IN_STRSET (type, "vpnc")) {
return _VPN_PASSWORD_LIST (
{ "Xauth password", N_("Password") },
{ "IPSec secret", N_("Group password") },
);
};
if (NM_IN_STRSET (type, "openswan",
"libreswan",
"strongswan")) {
return _VPN_PASSWORD_LIST (
{ "xauthpassword", N_("Password") },
{ "pskvalue", N_("Group password") },
);
};
if (NM_IN_STRSET (type, "openconnect")) {
return _VPN_PASSWORD_LIST (
{ "gateway", N_("Gateway") },
{ "cookie", N_("Cookie") },
{ "gwcert", N_("Gateway certificate hash") },
);
};
return NULL;
}
static gboolean
_extract_variable_value (char *line, const char *tag, char **value)
{
char *p1, *p2;
if (!g_str_has_prefix (line, tag))
return FALSE;
p1 = line + strlen (tag);
p2 = line + strlen (line) - 1;
if ((*p1 == '\'' || *p1 == '"') && (*p1 == *p2)) {
p1++;
*p2 = '\0';
}
NM_SET_OUT (value, g_strdup (p1));
return TRUE;
}
gboolean
nm_vpn_openconnect_authenticate_helper (const char *host,
char **cookie,
char **gateway,
char **gwcert,
int *status,
GError **error)
{
gs_free char *output = NULL;
gs_free const char **output_v = NULL;
const char *const*iter;
const char *path;
const char *const DEFAULT_PATHS[] = {
"/sbin/",
"/usr/sbin/",
"/usr/local/sbin/",
"/bin/",
"/usr/bin/",
"/usr/local/bin/",
NULL,
};
path = nm_utils_file_search_in_paths ("openconnect", "/usr/sbin/openconnect", DEFAULT_PATHS,
G_FILE_TEST_IS_EXECUTABLE, NULL, NULL, error);
if (!path)
return FALSE;
if (!g_spawn_sync (NULL,
(char **) NM_MAKE_STRV (path, "--authenticate", host),
NULL,
G_SPAWN_SEARCH_PATH
| G_SPAWN_CHILD_INHERITS_STDIN,
NULL,
NULL,
&output,
NULL,
status,
error))
return FALSE;
/* Parse output and set cookie, gateway and gwcert
* output example:
* COOKIE='loremipsum'
* HOST='1.2.3.4'
* FINGERPRINT='sha1:32bac90cf09a722e10ecc1942c67fe2ac8c21e2e'
*/
output_v = nm_utils_strsplit_set_with_empty (output, "\r\n");
for (iter = output_v; iter && *iter; iter++) {
char *s_mutable = (char *) *iter;
_extract_variable_value (s_mutable, "COOKIE=", cookie);
_extract_variable_value (s_mutable, "HOST=", gateway);
_extract_variable_value (s_mutable, "FINGERPRINT=", gwcert);
}
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_search = 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_ifname_valid (ifname, NMU_IFACE_KERNEL, NULL))
ifname_valid = TRUE;
}
}
if (!ifname_valid) {
nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN,
_("The name of the WireGuard config 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,
NULL,
error))
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 (¤t_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 (¤t_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 explicit 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)) {
GPtrArray **p_data_dns;
NMIPAddr addr_bin;
int addr_family;
if (nm_utils_parse_inaddr_bin (AF_UNSPEC,
value_word,
&addr_family,
&addr_bin)) {
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);
g_ptr_array_add (*p_data_dns,
nm_utils_inet_ntop_dup (addr_family, &addr_bin));
continue;
}
if (!data_dns_search)
data_dns_search = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (data_dns_search, g_strdup (value_word));
}
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 parameters. */
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 (¤t_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_DISABLED;
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;
GPtrArray *data_dns_search2 = data_dns_search;
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;
data_dns_search2 = NULL;
}
g_object_set (s_ip,
NM_SETTING_IP_CONFIG_METHOD,
data_addr ? method_manual : method_disabled,
NULL);
/* For WireGuard profiles, always set dns-priority to a negative value,
* so that DNS servers on other profiles get ignored. This is also what
* wg-quick does, by calling `resolvconf -x`. */
g_object_set (s_ip,
NM_SETTING_IP_CONFIG_DNS_PRIORITY,
(int) -10,
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]);
/* Of the wg-quick doesn't specify a search domain, assume the user
* wants to use the domain server for all searches. */
if (!data_dns_search2)
nm_setting_ip_config_add_dns_search (s_ip, "~");
}
if (data_dns_search2) {
for (i = 0; i < data_dns_search2->len; i++)
nm_setting_ip_config_add_dns_search (s_ip, data_dns_search2->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.
* The imported connection profile will have wireguard.ip4-auto-default-route and
* wireguard.ip6-auto-default-route set to "default". It will thus configure wg-quick's
* policy routing if the profile has any AllowedIPs ranges with /0.
*/
} 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);
}