NetworkManager/src/libnm-core-aux-intern/nm-libnm-core-utils.c
Beniamino Galvani 2b4b8d7e7e libnm-core: add function to get a warning message for unreachable gateways
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.
2026-03-11 15:12:15 +01:00

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
}