mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-05-18 03:28:09 +02:00
We are going to print the same warning message in different places (the daemon, nmcli, nmtui). Add a function to return the message. Note that the message needs to be translated in clients but not in the daemon logs.
1344 lines
46 KiB
C
1344 lines
46 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
|
|
|
|
#include "nm-libnm-core-utils.h"
|
|
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#include "nm-common-macros.h"
|
|
#include "nm-errors.h"
|
|
#include "libnm-core-public/nm-connection.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
const char **
|
|
nm_utils_bond_option_ip_split(const char *arp_ip_target)
|
|
{
|
|
return nm_strsplit_set_full(arp_ip_target, ",", NM_STRSPLIT_SET_FLAGS_STRSTRIP);
|
|
}
|
|
|
|
void
|
|
_nm_setting_bond_remove_options_miimon(NMSettingBond *s_bond)
|
|
{
|
|
g_return_if_fail(NM_IS_SETTING_BOND(s_bond));
|
|
|
|
nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_MIIMON);
|
|
nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_UPDELAY);
|
|
nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY);
|
|
nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY);
|
|
}
|
|
|
|
void
|
|
_nm_setting_bond_remove_options_arp_interval(NMSettingBond *s_bond)
|
|
{
|
|
g_return_if_fail(NM_IS_SETTING_BOND(s_bond));
|
|
|
|
nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
|
|
nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
|
|
nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_NS_IP6_TARGET);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
|
|
_nm_setting_bond_ad_select_from_string,
|
|
NMBondAdSelect,
|
|
{
|
|
G_STATIC_ASSERT_EXPR(_NM_BOND_AD_SELECT_NUM <= 3);
|
|
|
|
if (name && name[0] < '0' + _NM_BOND_AD_SELECT_NUM && name[0] >= '0' && name[1] == '\0') {
|
|
return name[0] - '0';
|
|
}
|
|
},
|
|
{ return NM_BOND_AD_SELECT_STABLE; },
|
|
{"bandwidth", NM_BOND_AD_SELECT_BANDWIDTH},
|
|
{"count", NM_BOND_AD_SELECT_COUNT},
|
|
{"stable", NM_BOND_AD_SELECT_STABLE}, );
|
|
|
|
NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
|
|
_nm_setting_bond_arp_all_targets_from_string,
|
|
NMBondArpAllTargets,
|
|
{
|
|
G_STATIC_ASSERT_EXPR(_NM_BOND_ARP_ALL_TARGETS_NUM <= 2);
|
|
|
|
if (name && name[0] < '0' + _NM_BOND_ARP_ALL_TARGETS_NUM && name[0] >= '0'
|
|
&& name[1] == '\0') {
|
|
return name[0] - '0';
|
|
}
|
|
},
|
|
{ return NM_BOND_ARP_ALL_TARGETS_ANY; },
|
|
{"all", NM_BOND_ARP_ALL_TARGETS_ALL},
|
|
{"any", NM_BOND_ARP_ALL_TARGETS_ANY}, );
|
|
|
|
NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
|
|
_nm_setting_bond_fail_over_mac_from_string,
|
|
NMBondFailOverMac,
|
|
{
|
|
G_STATIC_ASSERT_EXPR(_NM_BOND_FAIL_OVER_MAC_NUM <= 3);
|
|
|
|
if (name && name[0] < '0' + _NM_BOND_FAIL_OVER_MAC_NUM && name[0] >= '0'
|
|
&& name[1] == '\0') {
|
|
return name[0] - '0';
|
|
}
|
|
},
|
|
{ return NM_BOND_FAIL_OVER_MAC_NONE; },
|
|
{"active", NM_BOND_FAIL_OVER_MAC_ACTIVE},
|
|
{"follow", NM_BOND_FAIL_OVER_MAC_FOLLOW},
|
|
{"none", NM_BOND_FAIL_OVER_MAC_NONE}, );
|
|
|
|
NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
|
|
_nm_setting_bond_lacp_active_from_string,
|
|
NMBondLacpActive,
|
|
{
|
|
G_STATIC_ASSERT_EXPR(_NM_BOND_LACP_ACTIVE_NUM <= 2);
|
|
|
|
if (name && name[0] < '0' + _NM_BOND_LACP_ACTIVE_NUM && name[0] >= '0' && name[1] == '\0') {
|
|
return name[0] - '0';
|
|
}
|
|
},
|
|
{ return NM_BOND_LACP_ACTIVE_ON; },
|
|
{"off", NM_BOND_LACP_ACTIVE_OFF},
|
|
{"on", NM_BOND_LACP_ACTIVE_ON}, );
|
|
|
|
NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
|
|
_nm_setting_bond_lacp_rate_from_string,
|
|
NMBondLacpRate,
|
|
{
|
|
G_STATIC_ASSERT_EXPR(_NM_BOND_LACP_RATE_NUM <= 2);
|
|
|
|
if (name && name[0] < '0' + _NM_BOND_LACP_RATE_NUM && name[0] >= '0' && name[1] == '\0') {
|
|
return name[0] - '0';
|
|
}
|
|
},
|
|
{ return NM_BOND_LACP_RATE_SLOW; },
|
|
{"fast", NM_BOND_LACP_RATE_FAST},
|
|
{"slow", NM_BOND_LACP_RATE_SLOW}, );
|
|
|
|
NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
|
|
_nm_setting_bond_arp_validate_from_string,
|
|
NMBondArpValidate,
|
|
{
|
|
G_STATIC_ASSERT_EXPR(_NM_BOND_ARP_VALIDATE_NUM <= 7);
|
|
|
|
if (name && name[0] < '0' + _NM_BOND_ARP_VALIDATE_NUM && name[0] >= '0'
|
|
&& name[1] == '\0') {
|
|
return name[0] - '0';
|
|
}
|
|
},
|
|
{ return NM_BOND_ARP_VALIDATE_NONE; },
|
|
{"active", NM_BOND_ARP_VALIDATE_ACTIVE},
|
|
{"all", NM_BOND_ARP_VALIDATE_ALL},
|
|
{"backup", NM_BOND_ARP_VALIDATE_BACKUP},
|
|
{"filter", NM_BOND_ARP_VALIDATE_FILTER},
|
|
{"filter_active", NM_BOND_ARP_VALIDATE_FILTER_ACTIVE},
|
|
{"filter_backup", NM_BOND_ARP_VALIDATE_FILTER_BACKUP},
|
|
{"none", NM_BOND_ARP_VALIDATE_NONE}, );
|
|
|
|
NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
|
|
_nm_setting_bond_primary_reselect_from_string,
|
|
NMBondPrimaryReselect,
|
|
{
|
|
G_STATIC_ASSERT_EXPR(_NM_BOND_PRIMARY_RESELECT_NUM <= 3);
|
|
|
|
if (name && name[0] < '0' + _NM_BOND_PRIMARY_RESELECT_NUM && name[0] >= '0'
|
|
&& name[1] == '\0') {
|
|
return name[0] - '0';
|
|
}
|
|
},
|
|
{ return NM_BOND_PRIMARY_RESELECT_ALWAYS; },
|
|
{"always", NM_BOND_PRIMARY_RESELECT_ALWAYS},
|
|
{"better", NM_BOND_PRIMARY_RESELECT_BETTER},
|
|
{"failure", NM_BOND_PRIMARY_RESELECT_FAILURE}, );
|
|
|
|
NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
|
|
_nm_setting_bond_xmit_hash_policy_from_string,
|
|
NMBondXmitHashPolicy,
|
|
{
|
|
G_STATIC_ASSERT_EXPR(_NM_BOND_XMIT_HASH_POLICY_NUM <= 6);
|
|
|
|
if (name && name[0] < '0' + _NM_BOND_XMIT_HASH_POLICY_NUM && name[0] >= '0'
|
|
&& name[1] == '\0') {
|
|
return name[0] - '0';
|
|
}
|
|
},
|
|
{ return NM_BOND_XMIT_HASH_POLICY_LAYER2; },
|
|
{"encap2+3", NM_BOND_XMIT_HASH_POLICY_ENCAP2_3},
|
|
{"encap3+4", NM_BOND_XMIT_HASH_POLICY_ENCAP3_4},
|
|
{"layer2", NM_BOND_XMIT_HASH_POLICY_LAYER2},
|
|
{"layer2+3", NM_BOND_XMIT_HASH_POLICY_LAYER2_3},
|
|
{"layer3+4", NM_BOND_XMIT_HASH_POLICY_LAYER3_4},
|
|
{"vlan+srcmac", NM_BOND_XMIT_HASH_POLICY_VLAN_SRCMAC}, );
|
|
|
|
NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
|
|
_nm_setting_bond_mode_from_string,
|
|
NMBondMode,
|
|
{
|
|
G_STATIC_ASSERT_EXPR(_NM_BOND_MODE_NUM <= 9);
|
|
|
|
if (name && name[0] < '0' + _NM_BOND_MODE_NUM && name[0] >= '0' && name[1] == '\0') {
|
|
return name[0] - '0';
|
|
}
|
|
},
|
|
{ return NM_BOND_MODE_UNKNOWN; },
|
|
{"802.3ad", NM_BOND_MODE_8023AD},
|
|
{"active-backup", NM_BOND_MODE_ACTIVEBACKUP},
|
|
{"balance-alb", NM_BOND_MODE_ALB},
|
|
{"balance-rr", NM_BOND_MODE_ROUNDROBIN},
|
|
{"balance-tlb", NM_BOND_MODE_TLB},
|
|
{"balance-xor", NM_BOND_MODE_XOR},
|
|
{"broadcast", NM_BOND_MODE_BROADCAST}, );
|
|
|
|
const char *
|
|
_nm_setting_bond_mode_to_string(int mode)
|
|
{
|
|
static const char *const modes[] = {
|
|
[NM_BOND_MODE_8023AD] = "802.3ad",
|
|
[NM_BOND_MODE_ACTIVEBACKUP] = "active-backup",
|
|
[NM_BOND_MODE_ALB] = "balance-alb",
|
|
[NM_BOND_MODE_BROADCAST] = "broadcast",
|
|
[NM_BOND_MODE_ROUNDROBIN] = "balance-rr",
|
|
[NM_BOND_MODE_TLB] = "balance-tlb",
|
|
[NM_BOND_MODE_XOR] = "balance-xor",
|
|
};
|
|
|
|
G_STATIC_ASSERT(G_N_ELEMENTS(modes) == _NM_BOND_MODE_NUM);
|
|
|
|
if (NM_MORE_ASSERT_ONCE(5)) {
|
|
char sbuf[100];
|
|
int i;
|
|
NMBondMode m;
|
|
|
|
for (i = 0; i < (int) G_N_ELEMENTS(modes); i++) {
|
|
nm_assert(modes[i]);
|
|
nm_assert(i == _nm_setting_bond_mode_from_string(modes[i]));
|
|
nm_assert(i == _nm_setting_bond_mode_from_string(nm_sprintf_buf(sbuf, "%d", i)));
|
|
}
|
|
nm_assert(NM_BOND_MODE_UNKNOWN == _nm_setting_bond_mode_from_string(NULL));
|
|
nm_assert(NM_BOND_MODE_UNKNOWN == _nm_setting_bond_mode_from_string(""));
|
|
for (i = -2; i < ((int) G_N_ELEMENTS(modes)) + 20; i++) {
|
|
if (i < 0 || i >= G_N_ELEMENTS(modes))
|
|
m = NM_BOND_MODE_UNKNOWN;
|
|
else
|
|
m = i;
|
|
nm_assert(m == _nm_setting_bond_mode_from_string(nm_sprintf_buf(sbuf, "%d", i)));
|
|
}
|
|
}
|
|
|
|
if (mode >= 0 && mode < (int) G_N_ELEMENTS(modes))
|
|
return modes[mode];
|
|
return NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
gboolean
|
|
nm_utils_vlan_priority_map_parse_str(NMVlanPriorityMap map_type,
|
|
const char *str,
|
|
gboolean allow_wildcard_to,
|
|
guint32 *out_from,
|
|
guint32 *out_to,
|
|
gboolean *out_has_wildcard_to)
|
|
{
|
|
const char *s2;
|
|
gint64 v1, v2;
|
|
|
|
nm_assert(str);
|
|
|
|
s2 = strchr(str, ':');
|
|
|
|
if (!s2) {
|
|
if (!allow_wildcard_to)
|
|
return FALSE;
|
|
v1 = _nm_utils_ascii_str_to_int64(str, 10, 0, G_MAXUINT32, -1);
|
|
v2 = -1;
|
|
} else {
|
|
gs_free char *s1_free = NULL;
|
|
gsize s1_len = (s2 - str);
|
|
|
|
s2 = nm_str_skip_leading_spaces(&s2[1]);
|
|
if (s2[0] == '\0' || (s2[0] == '*' && NM_STRCHAR_ALL(&s2[1], ch, g_ascii_isspace(ch)))) {
|
|
if (!allow_wildcard_to)
|
|
return FALSE;
|
|
v2 = -1;
|
|
} else {
|
|
v2 = _nm_utils_ascii_str_to_int64(s2, 10, 0, G_MAXUINT32, -1);
|
|
if (v2 < 0 || (guint32) v2 > nm_utils_vlan_priority_map_get_max_prio(map_type, FALSE))
|
|
return FALSE;
|
|
}
|
|
|
|
v1 = _nm_utils_ascii_str_to_int64(nm_strndup_a(100, str, s1_len, &s1_free),
|
|
10,
|
|
0,
|
|
G_MAXUINT32,
|
|
-1);
|
|
}
|
|
|
|
if (v1 < 0 || (guint32) v1 > nm_utils_vlan_priority_map_get_max_prio(map_type, TRUE))
|
|
return FALSE;
|
|
|
|
NM_SET_OUT(out_from, v1);
|
|
NM_SET_OUT(out_to, v2 < 0 ? 0u : (guint) v2);
|
|
NM_SET_OUT(out_has_wildcard_to, v2 < 0);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
const char *const nm_auth_permission_names_by_idx[NM_CLIENT_PERMISSION_LAST] = {
|
|
[NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK - 1] = NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK,
|
|
[NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK - 1] =
|
|
NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK,
|
|
[NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK,
|
|
[NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS - 1] =
|
|
NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS,
|
|
[NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI,
|
|
[NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX,
|
|
[NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN,
|
|
[NM_CLIENT_PERMISSION_NETWORK_CONTROL - 1] = NM_AUTH_PERMISSION_NETWORK_CONTROL,
|
|
[NM_CLIENT_PERMISSION_RELOAD - 1] = NM_AUTH_PERMISSION_RELOAD,
|
|
[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS - 1] =
|
|
NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS,
|
|
[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME - 1] =
|
|
NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME,
|
|
[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN - 1] = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN,
|
|
[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM - 1] = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM,
|
|
[NM_CLIENT_PERMISSION_SLEEP_WAKE - 1] = NM_AUTH_PERMISSION_SLEEP_WAKE,
|
|
[NM_CLIENT_PERMISSION_WIFI_SCAN - 1] = NM_AUTH_PERMISSION_WIFI_SCAN,
|
|
[NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN - 1] = NM_AUTH_PERMISSION_WIFI_SHARE_OPEN,
|
|
[NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED - 1] = NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED,
|
|
};
|
|
|
|
const NMClientPermission nm_auth_permission_sorted[NM_CLIENT_PERMISSION_LAST] = {
|
|
NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK,
|
|
NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK,
|
|
NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK,
|
|
NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS,
|
|
NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI,
|
|
NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX,
|
|
NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN,
|
|
NM_CLIENT_PERMISSION_NETWORK_CONTROL,
|
|
NM_CLIENT_PERMISSION_RELOAD,
|
|
NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS,
|
|
NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME,
|
|
NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN,
|
|
NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM,
|
|
NM_CLIENT_PERMISSION_SLEEP_WAKE,
|
|
NM_CLIENT_PERMISSION_WIFI_SCAN,
|
|
NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN,
|
|
NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED,
|
|
};
|
|
|
|
const char *
|
|
nm_auth_permission_to_string(NMClientPermission permission)
|
|
{
|
|
if (permission < 1)
|
|
return NULL;
|
|
if (permission > NM_CLIENT_PERMISSION_LAST)
|
|
return NULL;
|
|
return nm_auth_permission_names_by_idx[permission - 1];
|
|
}
|
|
|
|
#define AUTH_PERMISSION_PREFIX "org.freedesktop.NetworkManager."
|
|
|
|
static int
|
|
_nm_auth_permission_from_string_cmp(gconstpointer a, gconstpointer b, gpointer user_data)
|
|
{
|
|
const NMClientPermission *const p = a;
|
|
const char *const needle = b;
|
|
const char *ss = nm_auth_permission_names_by_idx[*p - 1];
|
|
|
|
nm_assert(NM_STR_HAS_PREFIX(ss, AUTH_PERMISSION_PREFIX));
|
|
nm_assert(ss[NM_STRLEN(AUTH_PERMISSION_PREFIX)] != '\0');
|
|
|
|
return strcmp(&ss[NM_STRLEN(AUTH_PERMISSION_PREFIX)], needle);
|
|
}
|
|
|
|
NMClientPermission
|
|
nm_auth_permission_from_string(const char *str)
|
|
{
|
|
gssize idx;
|
|
|
|
if (!str)
|
|
return NM_CLIENT_PERMISSION_NONE;
|
|
|
|
if (!NM_STR_HAS_PREFIX(str, AUTH_PERMISSION_PREFIX))
|
|
return NM_CLIENT_PERMISSION_NONE;
|
|
idx = nm_array_find_bsearch(nm_auth_permission_sorted,
|
|
G_N_ELEMENTS(nm_auth_permission_sorted),
|
|
sizeof(nm_auth_permission_sorted[0]),
|
|
&str[NM_STRLEN(AUTH_PERMISSION_PREFIX)],
|
|
_nm_auth_permission_from_string_cmp,
|
|
NULL);
|
|
if (idx < 0)
|
|
return NM_CLIENT_PERMISSION_NONE;
|
|
return nm_auth_permission_sorted[idx];
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
NMClientPermissionResult
|
|
nm_client_permission_result_from_string(const char *nm)
|
|
{
|
|
if (!nm)
|
|
return NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
|
|
if (nm_streq(nm, "yes"))
|
|
return NM_CLIENT_PERMISSION_RESULT_YES;
|
|
if (nm_streq(nm, "no"))
|
|
return NM_CLIENT_PERMISSION_RESULT_NO;
|
|
if (nm_streq(nm, "auth"))
|
|
return NM_CLIENT_PERMISSION_RESULT_AUTH;
|
|
return NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
|
|
}
|
|
|
|
const char *
|
|
nm_client_permission_result_to_string(NMClientPermissionResult permission)
|
|
{
|
|
switch (permission) {
|
|
case NM_CLIENT_PERMISSION_RESULT_YES:
|
|
return "yes";
|
|
case NM_CLIENT_PERMISSION_RESULT_NO:
|
|
return "no";
|
|
case NM_CLIENT_PERMISSION_RESULT_AUTH:
|
|
return "auth";
|
|
case NM_CLIENT_PERMISSION_RESULT_UNKNOWN:
|
|
return "unknown";
|
|
}
|
|
nm_assert_not_reached();
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
nm_utils_validate_dhcp4_vendor_class_id(const char *vci, GError **error)
|
|
{
|
|
const char *bin;
|
|
gsize unescaped_len;
|
|
gs_free char *to_free = NULL;
|
|
|
|
g_return_val_if_fail(!error || !(*error), FALSE);
|
|
g_return_val_if_fail(vci, FALSE);
|
|
|
|
if (vci[0] == '\0') {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("property cannot be an empty string"));
|
|
g_prefix_error(error,
|
|
"%s.%s: ",
|
|
NM_SETTING_IP4_CONFIG_SETTING_NAME,
|
|
NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER);
|
|
return FALSE;
|
|
}
|
|
|
|
bin = nm_utils_buf_utf8safe_unescape(vci,
|
|
NM_UTILS_STR_UTF8_SAFE_FLAG_NONE,
|
|
&unescaped_len,
|
|
(gpointer *) &to_free);
|
|
/* a DHCP option cannot be longer than 255 bytes */
|
|
if (unescaped_len > 255) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("property cannot be longer than 255 bytes"));
|
|
g_prefix_error(error,
|
|
"%s.%s: ",
|
|
NM_SETTING_IP4_CONFIG_SETTING_NAME,
|
|
NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER);
|
|
return FALSE;
|
|
}
|
|
if (strlen(bin) != unescaped_len) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("property cannot contain any nul bytes"));
|
|
g_prefix_error(error,
|
|
"%s.%s: ",
|
|
NM_SETTING_IP4_CONFIG_SETTING_NAME,
|
|
NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
nm_utils_validate_dhcp_dscp(const char *dscp, GError **error)
|
|
{
|
|
g_return_val_if_fail(!error || !(*error), FALSE);
|
|
g_return_val_if_fail(dscp, FALSE);
|
|
|
|
if (!NM_IN_STRSET(dscp, "CS0", "CS4", "CS6")) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("invalid DSCP value; allowed values are: 'CS0', 'CS4', 'CS6'"));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
nm_utils_validate_shared_dhcp_range(const char *shared_dhcp_range,
|
|
GPtrArray *addresses,
|
|
GError **error)
|
|
{
|
|
const char *start_address_str;
|
|
const char *end_address_str;
|
|
NMIPAddress *interface_address_with_prefix;
|
|
NMIPAddr interface_address;
|
|
NMIPAddr start_address;
|
|
NMIPAddr end_address;
|
|
guint32 i;
|
|
guint32 mask;
|
|
guint32 prefix_length;
|
|
guint32 start_network;
|
|
guint32 end_network;
|
|
guint32 interface_network;
|
|
guint32 start_ip_length;
|
|
bool range_is_in_interface_network;
|
|
gs_free char *to_free = NULL;
|
|
|
|
g_return_val_if_fail(!error || !(*error), FALSE);
|
|
|
|
if (!shared_dhcp_range) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("NULL DHCP range; it should be provided as <START_IP>,<END_IP>."));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!*shared_dhcp_range) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (!addresses) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("Non-NULL range and NULL addresses detected."));
|
|
return FALSE;
|
|
}
|
|
|
|
end_address_str = strchr(shared_dhcp_range, ',');
|
|
if (!end_address_str) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("Invalid DHCP range; it should be provided as <START_IP>,<END_IP>."));
|
|
return FALSE;
|
|
}
|
|
|
|
start_ip_length = end_address_str - shared_dhcp_range;
|
|
if (start_ip_length > 15) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("Start IP has invalid length."));
|
|
return FALSE;
|
|
}
|
|
|
|
start_address_str = nm_strndup_a(200, shared_dhcp_range, start_ip_length, &to_free);
|
|
++end_address_str; /* end address is pointing to ',', shift it to the actual address */
|
|
|
|
if (!nm_inet_parse_bin(AF_INET, start_address_str, NULL, &start_address)) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("Start IP is invalid."));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!nm_inet_parse_bin(AF_INET, end_address_str, NULL, &end_address)) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("End IP is invalid."));
|
|
return FALSE;
|
|
}
|
|
|
|
if (ntohl(start_address.addr4) > ntohl(end_address.addr4)) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("Start IP should be lower than the end IP."));
|
|
return FALSE;
|
|
}
|
|
|
|
range_is_in_interface_network = FALSE;
|
|
for (i = 0; i < (*addresses).len; ++i) {
|
|
interface_address_with_prefix = (NMIPAddress *) addresses->pdata[i];
|
|
nm_inet_parse_bin(AF_INET,
|
|
nm_ip_address_get_address(interface_address_with_prefix),
|
|
NULL,
|
|
&interface_address);
|
|
prefix_length = nm_ip_address_get_prefix(interface_address_with_prefix);
|
|
mask = nm_utils_ip4_prefix_to_netmask(prefix_length);
|
|
|
|
interface_network = interface_address.addr4 & mask;
|
|
start_network = start_address.addr4 & mask;
|
|
end_network = end_address.addr4 & mask;
|
|
|
|
if (start_network == interface_network && end_network == interface_network) {
|
|
range_is_in_interface_network = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!range_is_in_interface_network) {
|
|
g_set_error_literal(
|
|
error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("Requested range is not in any network configured on the interface."));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
nm_utils_validate_shared_dhcp_lease_time(int shared_dhcp_lease_time, GError **error)
|
|
{
|
|
g_return_val_if_fail(!error || !(*error), FALSE);
|
|
|
|
if (shared_dhcp_lease_time == 0 || shared_dhcp_lease_time == G_MAXINT32) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (shared_dhcp_lease_time < NM_MIN_FINITE_LEASE_TIME
|
|
|| NM_MAX_FINITE_LEASE_TIME < shared_dhcp_lease_time) {
|
|
g_set_error(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("Invalid DHCP lease time value; it should be either default or a positive "
|
|
"number between %u and %u or %s."),
|
|
NM_MIN_FINITE_LEASE_TIME,
|
|
NM_MAX_FINITE_LEASE_TIME,
|
|
NM_INFINITE_LEASE_TIME);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
nm_settings_connection_validate_permission_user(const char *item, gssize len)
|
|
{
|
|
gsize l;
|
|
|
|
if (!item)
|
|
return FALSE;
|
|
|
|
if (len < 0) {
|
|
nm_assert(len == -1);
|
|
l = strlen(item);
|
|
} else
|
|
l = (gsize) len;
|
|
|
|
if (l == 0)
|
|
return FALSE;
|
|
|
|
if (!g_utf8_validate(item, l, NULL))
|
|
return FALSE;
|
|
|
|
if (l >= 100)
|
|
return FALSE;
|
|
|
|
if (memchr(item, ':', l))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gpointer
|
|
_nm_connection_ensure_setting(NMConnection *connection, GType gtype)
|
|
{
|
|
return nm_connection_get_setting(connection, gtype)
|
|
?: _nm_connection_new_setting(connection, gtype);
|
|
}
|
|
|
|
gpointer
|
|
_nm_connection_new_setting(NMConnection *connection, GType gtype)
|
|
{
|
|
NMSetting *setting;
|
|
|
|
nm_assert(g_type_is_a(gtype, NM_TYPE_SETTING));
|
|
|
|
setting = g_object_new(gtype, NULL);
|
|
nm_connection_add_setting(connection, setting);
|
|
return setting;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
NMMptcpFlags
|
|
nm_mptcp_flags_normalize(NMMptcpFlags flags)
|
|
{
|
|
/* Certain combinations of flags are incompatible. Normalize them.
|
|
*
|
|
* This function never returns 0x0 (NONE). If the flags are neither
|
|
* disabled,enabled-on-global-iface,enabled, then we default to "enabled". */
|
|
|
|
if (NM_FLAGS_HAS(flags, NM_MPTCP_FLAGS_DISABLED)) {
|
|
/* If the disabled flag is set, then that's the end of it. */
|
|
return NM_MPTCP_FLAGS_DISABLED;
|
|
}
|
|
|
|
/* Clear all unknown flags. */
|
|
flags &= _NM_MPTCP_FLAGS_ALL;
|
|
|
|
/* Not disabled means enabled. */
|
|
flags |= NM_MPTCP_FLAGS_ENABLED;
|
|
|
|
if (NM_FLAGS_ALL(flags, NM_MPTCP_FLAGS_SIGNAL | NM_MPTCP_FLAGS_FULLMESH))
|
|
flags = NM_FLAGS_UNSET(flags, NM_MPTCP_FLAGS_FULLMESH);
|
|
|
|
return flags;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/*
|
|
* nm_dns_uri_parse:
|
|
* @addr_family: the address family, or AF_UNSPEC to autodetect it
|
|
* @str: the name server URI string to parse
|
|
* @dns: the name server descriptor to fill, or %NULL
|
|
* @error: the error to set if the string cannot be parsed
|
|
*
|
|
* Parses the given name server URI string. Each name server is represented
|
|
* by the following grammar:
|
|
*
|
|
* NAMESERVER := { PLAIN | TLS_URI | UDP_URI }
|
|
* PLAIN := { ipv4address | ipv6address } [ '#' SERVERNAME ]
|
|
* TLS_URI := 'dns+tls://' URI_ADDRESS [ ':' PORT ] [ '#' SERVERNAME ]
|
|
* UDP_URI := 'dns+udp://' URI_ADDRESS [ ':' PORT ]
|
|
* URI_ADDRESS := { ipv4address | '[' ipv6address [ '%' ifname ] ']' }
|
|
*
|
|
* Examples:
|
|
*
|
|
* 192.0.2.0
|
|
* 192.0.2.0#example.com
|
|
* 2001:db8::1
|
|
* dns+tls://192.0.2.0
|
|
* dns+tls://[2001:db8::1]
|
|
* dns+tls://192.0.2.0:53#example.com
|
|
* dns+udp://[fe80::1%enp1s0]
|
|
*
|
|
* Note that on return, the lifetime of the members in the @dns struct is
|
|
* the same as the input string @str.
|
|
*
|
|
* Returns: %TRUE on success, %FALSE on failure
|
|
*/
|
|
gboolean
|
|
nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns, GError **error)
|
|
{
|
|
NMDnsServer dns_stack;
|
|
gs_free char *addr_port_heap = NULL;
|
|
gs_free char *addr_heap = NULL;
|
|
const char *addr_port;
|
|
const char *addr;
|
|
const char *name;
|
|
const char *port;
|
|
|
|
nm_assert_addr_family_or_unspec(addr_family);
|
|
|
|
if (!dns)
|
|
dns = &dns_stack;
|
|
|
|
if (!str) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("the string is empty"));
|
|
return FALSE;
|
|
}
|
|
|
|
*dns = (NMDnsServer) {0};
|
|
|
|
if (NM_STR_HAS_PREFIX(str, "dns+tls://")) {
|
|
dns->scheme = NM_DNS_URI_SCHEME_TLS;
|
|
str += NM_STRLEN("dns+tls://");
|
|
} else if (NM_STR_HAS_PREFIX(str, "dns+udp://")) {
|
|
dns->scheme = NM_DNS_URI_SCHEME_UDP;
|
|
str += NM_STRLEN("dns+udp://");
|
|
} else {
|
|
name = strchr(str, '#');
|
|
if (name) {
|
|
str = nm_strndup_a(200, str, name - str, &addr_heap);
|
|
name++;
|
|
}
|
|
|
|
if (name && name[0] == '\0') {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("the DNS-over-TLS server name is empty"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!nm_inet_parse_bin(addr_family, str, &dns->addr_family, &dns->addr)) {
|
|
g_set_error(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("\"%s\" is not a valid IP address or a supported URI"),
|
|
str);
|
|
return FALSE;
|
|
}
|
|
|
|
dns->servername = name;
|
|
dns->scheme = NM_DNS_URI_SCHEME_NONE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
addr_port = str;
|
|
name = strrchr(addr_port, '#');
|
|
if (name) {
|
|
addr_port = nm_strndup_a(100, addr_port, name - addr_port, &addr_port_heap);
|
|
name++;
|
|
if (*name == '\0') {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("the DNS-over-TLS server name is empty"));
|
|
return FALSE;
|
|
}
|
|
dns->servername = name;
|
|
}
|
|
|
|
if (addr_family != AF_INET && *addr_port == '[') {
|
|
const char *end;
|
|
char *perc;
|
|
|
|
addr_family = AF_INET6;
|
|
addr_port++;
|
|
end = strchr(addr_port, ']');
|
|
if (!end) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("unterminated square bracket"));
|
|
return FALSE;
|
|
}
|
|
addr = nm_strndup_a(100, addr_port, end - addr_port, &addr_heap);
|
|
|
|
/* IPv6 link-local scope-id */
|
|
perc = (char *) strchr(addr, '%');
|
|
if (perc) {
|
|
*perc = '\0';
|
|
if (g_strlcpy(dns->interface, perc + 1, sizeof(dns->interface))
|
|
>= sizeof(dns->interface)) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("the interface name is too long"));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* port */
|
|
end++;
|
|
if (*end == ':') {
|
|
end++;
|
|
dns->port = _nm_utils_ascii_str_to_int64(end, 10, 1, 65535, 0);
|
|
if (dns->port == 0) {
|
|
g_set_error(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("\"%s\" is not a valid port number"),
|
|
end);
|
|
return FALSE;
|
|
}
|
|
}
|
|
} else if (addr_family != AF_INET6) {
|
|
/* square brackets are mandatory for IPv6, so it must be IPv4 */
|
|
|
|
addr_family = AF_INET;
|
|
addr = addr_port;
|
|
|
|
/* port */
|
|
port = strchr(addr_port, ':');
|
|
if (port) {
|
|
addr = nm_strndup_a(100, addr_port, port - addr_port, &addr_heap);
|
|
port++;
|
|
dns->port = _nm_utils_ascii_str_to_int64(port, 10, 1, 65535, 0);
|
|
if (dns->port == 0) {
|
|
g_set_error(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("\"%s\" is not a valid port number"),
|
|
port);
|
|
return FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("IPv6 addresses must be enclosed in square brackets"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!nm_inet_parse_bin(addr_family, addr, &dns->addr_family, &dns->addr)) {
|
|
g_set_error(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("\"%s\" is not a valid IP address"),
|
|
addr);
|
|
return FALSE;
|
|
}
|
|
|
|
if (dns->scheme != NM_DNS_URI_SCHEME_TLS && dns->servername) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("the server name is only supported for DNS-over-TLS"));
|
|
return FALSE;
|
|
}
|
|
|
|
/* For now, allow the interface only for IPv6 link-local addresses */
|
|
if (dns->interface[0]
|
|
&& (dns->addr_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&dns->addr.addr6))) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("the scope-id is only supported for IPv6 link-local addresses"));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* @nm_dns_uri_parse_plain:
|
|
* @addr_family: the address family, or AF_UNSPEC to autodetect it
|
|
* @str: the name server URI string
|
|
* @out_addrstr: the buffer to fill with the address string on return,
|
|
* or %NULL. Must be of size at least NM_INET_ADDRSTRLEN.
|
|
* @out_addr: the %NMIPAddr struct to fill on return, or %NULL
|
|
*
|
|
* Returns whether the string contains a "plain" (DNS over UDP on port 53)
|
|
* name server. In such case, it fills the arguments with the address
|
|
* of the name server.
|
|
*
|
|
* Returns: %TRUE on success, %FALSE if the string can't be parsed or
|
|
* if it's not a plain name server.
|
|
*/
|
|
gboolean
|
|
nm_dns_uri_parse_plain(int addr_family, const char *str, char *out_addrstr, NMIPAddr *out_addr)
|
|
{
|
|
NMDnsServer dns;
|
|
|
|
if (!nm_dns_uri_parse(addr_family, str, &dns, NULL))
|
|
return FALSE;
|
|
|
|
switch (dns.scheme) {
|
|
case NM_DNS_URI_SCHEME_TLS:
|
|
return FALSE;
|
|
case NM_DNS_URI_SCHEME_NONE:
|
|
NM_SET_OUT(out_addr, dns.addr);
|
|
if (out_addrstr) {
|
|
nm_inet_ntop(dns.addr_family, &dns.addr, out_addrstr);
|
|
}
|
|
return TRUE;
|
|
case NM_DNS_URI_SCHEME_UDP:
|
|
if (dns.port != NM_DNS_PORT_UNDEFINED && dns.port != 53)
|
|
return FALSE;
|
|
if (dns.interface[0])
|
|
return FALSE;
|
|
NM_SET_OUT(out_addr, dns.addr);
|
|
if (out_addrstr) {
|
|
nm_inet_ntop(dns.addr_family, &dns.addr, out_addrstr);
|
|
}
|
|
return TRUE;
|
|
case NM_DNS_URI_SCHEME_UNKNOWN:
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* @nm_dns_uri_normalize:
|
|
* @addr_family: the address family, or AF_UNSPEC to autodetect it
|
|
* @str: the name server URI string
|
|
* @out_free: the newly-allocated string to set on return, or %NULL
|
|
*
|
|
* Returns the "normal" representation for the given name server URI.
|
|
* Note that a plain name server (DNS over UDP on port 53) is always
|
|
* represented in the "legacy" (non-URI) form.
|
|
*
|
|
* Returns: the normalized DNS URI
|
|
*/
|
|
const char *
|
|
nm_dns_uri_normalize(int addr_family, const char *str, char **out_free)
|
|
{
|
|
NMDnsServer dns;
|
|
char addrstr[NM_INET_ADDRSTRLEN];
|
|
char portstr[32];
|
|
char *ret;
|
|
gsize len;
|
|
|
|
nm_assert_addr_family_or_unspec(addr_family);
|
|
nm_assert(str);
|
|
nm_assert(out_free && !*out_free);
|
|
|
|
if (!nm_dns_uri_parse(addr_family, str, &dns, NULL))
|
|
return NULL;
|
|
|
|
nm_inet_ntop(dns.addr_family, &dns.addr, addrstr);
|
|
|
|
if (dns.port != NM_DNS_PORT_UNDEFINED) {
|
|
nm_assert(dns.port >= 1 && dns.port <= 65535);
|
|
g_snprintf(portstr, sizeof(portstr), "%" G_GUINT16_FORMAT, dns.port);
|
|
}
|
|
|
|
switch (dns.scheme) {
|
|
case NM_DNS_URI_SCHEME_NONE:
|
|
len = strlen(addrstr);
|
|
/* In the vast majority of cases, the name is in fact normalized. Check
|
|
* whether it is, and don't duplicate the string. */
|
|
if (strncmp(str, addrstr, len) == 0) {
|
|
if (dns.servername) {
|
|
if (str[len] == '#' && nm_streq(&str[len + 1], dns.servername))
|
|
return str;
|
|
} else {
|
|
if (str[len] == '\0')
|
|
return str;
|
|
}
|
|
}
|
|
|
|
if (!dns.servername)
|
|
ret = g_strdup(addrstr);
|
|
else
|
|
ret = g_strconcat(addrstr, "#", dns.servername, NULL);
|
|
break;
|
|
case NM_DNS_URI_SCHEME_UDP:
|
|
if (dns.interface[0] || dns.port != NM_DNS_PORT_UNDEFINED) {
|
|
ret = g_strdup_printf("dns+udp://%s%s%s%s%s%s%s",
|
|
dns.addr_family == AF_INET6 ? "[" : "",
|
|
addrstr,
|
|
dns.interface[0] ? "%" : "",
|
|
dns.interface[0] ? dns.interface : "",
|
|
dns.addr_family == AF_INET6 ? "]" : "",
|
|
dns.port != NM_DNS_PORT_UNDEFINED ? ":" : "",
|
|
dns.port != NM_DNS_PORT_UNDEFINED ? portstr : "");
|
|
break;
|
|
}
|
|
ret = g_strdup_printf("%s%s%s", addrstr, dns.servername ? "#" : "", dns.servername ?: "");
|
|
break;
|
|
case NM_DNS_URI_SCHEME_TLS:
|
|
ret = g_strdup_printf("dns+tls://%s%s%s%s%s%s%s%s%s",
|
|
dns.addr_family == AF_INET6 ? "[" : "",
|
|
addrstr,
|
|
dns.interface[0] ? "%%" : "",
|
|
dns.interface[0] ? dns.interface : "",
|
|
dns.addr_family == AF_INET6 ? "]" : "",
|
|
dns.port != NM_DNS_PORT_UNDEFINED ? ":" : "",
|
|
dns.port != NM_DNS_PORT_UNDEFINED ? portstr : "",
|
|
dns.servername ? "#" : "",
|
|
dns.servername ?: "");
|
|
break;
|
|
case NM_DNS_URI_SCHEME_UNKNOWN:
|
|
default:
|
|
nm_assert_not_reached();
|
|
ret = NULL;
|
|
}
|
|
|
|
*out_free = ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
* nm_setting_ovs_other_config_check_key:
|
|
* @key: (nullable): the key to check
|
|
* @error: a #GError, %NULL to ignore.
|
|
*
|
|
* Checks whether @key is a valid key for OVS' other-config.
|
|
* This means, the key cannot be %NULL, not too large and valid ASCII.
|
|
* Also, only digits and numbers are allowed with a few special
|
|
* characters.
|
|
*
|
|
* Returns: %TRUE if @key is a valid user data key.
|
|
*/
|
|
gboolean
|
|
nm_setting_ovs_other_config_check_key(const char *key, GError **error)
|
|
{
|
|
gsize len;
|
|
|
|
g_return_val_if_fail(!error || !*error, FALSE);
|
|
|
|
if (!key || !key[0]) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("missing key"));
|
|
return FALSE;
|
|
}
|
|
len = strlen(key);
|
|
if (len > 255u) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("key is too long"));
|
|
return FALSE;
|
|
}
|
|
if (!g_utf8_validate(key, len, NULL)) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("key must be UTF8"));
|
|
return FALSE;
|
|
}
|
|
if (!NM_STRCHAR_ALL(key, ch, nm_ascii_is_regular_char(ch))) {
|
|
/* Probably OVS is more forgiving about what makes a valid key for
|
|
* an other-key. However, we are strict (at least, for now). */
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("key contains invalid characters"));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* nm_setting_ovs_other_config_check_val:
|
|
* @val: (nullable): the value to check
|
|
* @error: a #GError, %NULL to ignore.
|
|
*
|
|
* Checks whether @val is a valid user data value. This means,
|
|
* value is not %NULL, not too large and valid UTF-8.
|
|
*
|
|
* Returns: %TRUE if @val is a valid user data value.
|
|
*/
|
|
gboolean
|
|
nm_setting_ovs_other_config_check_val(const char *val, GError **error)
|
|
{
|
|
gsize len;
|
|
|
|
g_return_val_if_fail(!error || !*error, FALSE);
|
|
|
|
if (!val) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("value is missing"));
|
|
return FALSE;
|
|
}
|
|
|
|
len = strlen(val);
|
|
if (len > (2u * 1024u)) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("value is too large"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_utf8_validate(val, len, NULL)) {
|
|
g_set_error_literal(error,
|
|
NM_CONNECTION_ERROR,
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
_("value is not valid UTF8"));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
NMIPAddr dest;
|
|
guint prefix;
|
|
} DirectRoute;
|
|
|
|
static void
|
|
_setting_ip_config_collect_unreachable_gateways(NMSettingIPConfig *s_ip, GHashTable **result)
|
|
{
|
|
const int addr_family = nm_setting_ip_config_get_addr_family(s_ip);
|
|
guint num_routes;
|
|
guint num_addrs;
|
|
guint n_direct_routes = 0;
|
|
NMIPAddr gw_bin = NM_IP_ADDR_INIT;
|
|
guint i;
|
|
guint j;
|
|
const char *gateway;
|
|
gs_free DirectRoute *direct_routes = NULL;
|
|
|
|
num_routes = nm_setting_ip_config_get_num_routes(s_ip);
|
|
num_addrs = nm_setting_ip_config_get_num_addresses(s_ip);
|
|
|
|
direct_routes = g_new0(DirectRoute, num_routes + num_addrs);
|
|
|
|
/* Collect direct routes (routes without a gateway) from the setting. */
|
|
for (i = 0; i < num_routes; i++) {
|
|
NMIPRoute *route = nm_setting_ip_config_get_route(s_ip, i);
|
|
|
|
if (nm_ip_route_get_next_hop(route))
|
|
continue;
|
|
|
|
nm_ip_route_get_dest_binary(route, &direct_routes[n_direct_routes].dest);
|
|
direct_routes[n_direct_routes].prefix = nm_ip_route_get_prefix(route);
|
|
n_direct_routes++;
|
|
}
|
|
|
|
/* Add prefix routes (device routes) for each static address. */
|
|
for (i = 0; i < num_addrs; i++) {
|
|
NMIPAddress *addr = nm_setting_ip_config_get_address(s_ip, i);
|
|
|
|
nm_ip_address_get_address_binary(addr, &direct_routes[n_direct_routes].dest);
|
|
direct_routes[n_direct_routes].prefix = nm_ip_address_get_prefix(addr);
|
|
n_direct_routes++;
|
|
}
|
|
|
|
/* Check the setting's default gateway. */
|
|
gateway = nm_setting_ip_config_get_gateway(s_ip);
|
|
if (gateway && nm_inet_parse_bin(addr_family, gateway, NULL, &gw_bin)) {
|
|
gboolean reachable = FALSE;
|
|
|
|
if (addr_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&gw_bin.addr6))
|
|
reachable = TRUE;
|
|
|
|
if (!reachable) {
|
|
for (j = 0; j < n_direct_routes; j++) {
|
|
if (nm_ip_addr_same_prefix(addr_family,
|
|
&gw_bin,
|
|
&direct_routes[j].dest,
|
|
direct_routes[j].prefix)) {
|
|
reachable = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!reachable) {
|
|
if (!*result)
|
|
*result = g_hash_table_new(nm_str_hash, g_str_equal);
|
|
g_hash_table_add(*result, (gpointer) gateway);
|
|
}
|
|
}
|
|
|
|
/* Check gateways of each route in the setting. */
|
|
for (i = 0; i < num_routes; i++) {
|
|
NMIPRoute *route = nm_setting_ip_config_get_route(s_ip, i);
|
|
NMIPAddr next_hop = NM_IP_ADDR_INIT;
|
|
gboolean reachable = FALSE;
|
|
GVariant *attribute;
|
|
|
|
if (!nm_ip_route_get_next_hop_binary(route, &next_hop))
|
|
continue;
|
|
|
|
if (addr_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&next_hop.addr6))
|
|
continue;
|
|
|
|
attribute = nm_ip_route_get_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK);
|
|
if (attribute && g_variant_is_of_type(attribute, G_VARIANT_TYPE("b"))
|
|
&& g_variant_get_boolean(attribute)) {
|
|
/* the gateway of a onlink route is reachable */
|
|
continue;
|
|
}
|
|
|
|
for (j = 0; j < n_direct_routes; j++) {
|
|
if (nm_ip_addr_same_prefix(addr_family,
|
|
&next_hop,
|
|
&direct_routes[j].dest,
|
|
direct_routes[j].prefix)) {
|
|
reachable = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!reachable) {
|
|
if (!*result)
|
|
*result = g_hash_table_new(nm_str_hash, g_str_equal);
|
|
g_hash_table_add(*result, (gpointer) nm_ip_route_get_next_hop(route));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* nm_connection_get_unreachable_gateways:
|
|
* @connection: the #NMConnection
|
|
*
|
|
* Checks whether there are gateways (either the default gateway or gateways
|
|
* of routes) that are not directly reachable in the IPv4 and IPv6 settings
|
|
* of the connection. A gateway is considered directly reachable if it falls
|
|
* within the subnet of a direct route (a route without a next hop) or of a
|
|
* prefix route from a static address.
|
|
*
|
|
* Returns: a %NULL-terminated array of gateway strings not directly reachable,
|
|
* or %NULL if all gateways are reachable. The individual strings are owned
|
|
* by the setting. Free the returned array with g_free().
|
|
*/
|
|
const char **
|
|
nm_connection_get_unreachable_gateways(NMConnection *connection)
|
|
{
|
|
gs_unref_hashtable GHashTable *result = NULL;
|
|
NMSettingIPConfig *s_ip;
|
|
guint len;
|
|
const char **strv;
|
|
|
|
if (!connection)
|
|
return NULL;
|
|
|
|
s_ip = nm_connection_get_setting_ip4_config(connection);
|
|
if (s_ip
|
|
&& nm_streq0(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
|
|
_setting_ip_config_collect_unreachable_gateways(s_ip, &result);
|
|
}
|
|
|
|
s_ip = nm_connection_get_setting_ip6_config(connection);
|
|
if (s_ip
|
|
&& nm_streq0(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
|
|
_setting_ip_config_collect_unreachable_gateways(s_ip, &result);
|
|
}
|
|
|
|
if (result) {
|
|
strv = (const char **) g_hash_table_get_keys_as_array(result, &len);
|
|
nm_strv_sort(strv, len);
|
|
return strv;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* nm_connection_get_unreachable_gateways_warning:
|
|
* @connection: the #NMConnection
|
|
* @translate: whether to translate the message (use %TRUE for user-facing
|
|
* tools like nmcli, %FALSE for daemon logs)
|
|
*
|
|
* Checks whether there are unreachable gateways in the connection and returns
|
|
* a formatted warning message if so.
|
|
*
|
|
* Returns: a warning message string, or %NULL if all gateways are reachable.
|
|
* Free with g_free().
|
|
*/
|
|
char *
|
|
nm_connection_get_unreachable_gateways_warning(NMConnection *connection, gboolean translate)
|
|
{
|
|
gs_free const char **gateways = NULL;
|
|
gs_free char *gw_list = NULL;
|
|
const char *msg =
|
|
N_("the following gateways are not directly reachable from any configured address or "
|
|
"route: %s. NetworkManager currently adds on-link routes for them automatically, "
|
|
"but this will change in the future. Consider adding addresses or routes whose "
|
|
"subnets cover these gateways");
|
|
|
|
gateways = nm_connection_get_unreachable_gateways(connection);
|
|
if (!gateways)
|
|
return NULL;
|
|
|
|
gw_list = g_strjoinv(", ", (char **) gateways);
|
|
|
|
NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral")
|
|
if (translate)
|
|
return g_strdup_printf(_(msg), gw_list);
|
|
return g_strdup_printf(msg, gw_list);
|
|
NM_PRAGMA_WARNING_REENABLE
|
|
}
|