/* 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 #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 ,.")); 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 ,.")); 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 }