merge: branch 'bg/dns-server-valid'

libnm: add nm_dns_server_validate()

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2251
This commit is contained in:
Beniamino Galvani 2025-08-18 12:04:03 +00:00
commit 11da867072
13 changed files with 152 additions and 44 deletions

View file

@ -374,7 +374,7 @@ server_builder_append_base(GVariantBuilder *argument_builder,
NMDnsServer dns_server; NMDnsServer dns_server;
gsize addr_size; gsize addr_size;
if (!nm_dns_uri_parse(address_family, address_string, &dns_server)) if (!nm_dns_uri_parse(address_family, address_string, &dns_server, NULL))
return FALSE; return FALSE;
addr_size = nm_utils_addr_family_to_size(dns_server.addr_family); addr_size = nm_utils_addr_family_to_size(dns_server.addr_family);

View file

@ -398,7 +398,7 @@ update_add_ip_config(NMDnsSystemdResolved *self,
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
NMDnsServer dns_server; NMDnsServer dns_server;
if (!nm_dns_uri_parse(ip_data->addr_family, strarr[i], &dns_server)) if (!nm_dns_uri_parse(ip_data->addr_family, strarr[i], &dns_server, NULL))
continue; continue;
if (!NM_IN_SET(dns_server.scheme, if (!NM_IN_SET(dns_server.scheme,

View file

@ -3987,7 +3987,7 @@ _l3cfg_routed_dns_apply(NML3Cfg *self, const NML3ConfigData *l3cd)
NMDnsServer dns; NMDnsServer dns;
int r; int r;
if (!nm_dns_uri_parse(addr_family, nameservers[i], &dns)) if (!nm_dns_uri_parse(addr_family, nameservers[i], &dns, NULL))
continue; continue;
/* Find the gateway to the DNS over the current interface. When /* Find the gateway to the DNS over the current interface. When

View file

@ -2058,6 +2058,7 @@ make_ip4_setting(shvarFile *ifcfg,
for (i = 1; i < 10000; i++) { for (i = 1; i < 10000; i++) {
NMDnsServer dns; NMDnsServer dns;
char tag[256]; char tag[256];
gs_free_error GError *local = NULL;
numbered_tag(tag, "DNS", i); numbered_tag(tag, "DNS", i);
nm_clear_g_free(&value); nm_clear_g_free(&value);
@ -2065,12 +2066,13 @@ make_ip4_setting(shvarFile *ifcfg,
if (!v) if (!v)
break; break;
if (!nm_dns_uri_parse(AF_UNSPEC, v, &dns)) { if (!nm_dns_uri_parse(AF_UNSPEC, v, &dns, &local)) {
g_set_error(error, g_set_error(error,
NM_SETTINGS_ERROR, NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"Invalid DNS server address '%s'", "Invalid DNS server address '%s': %s",
v); v,
local->message);
return NULL; return NULL;
} }
@ -2607,6 +2609,7 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
* Pick up just IPv6 addresses (IPv4 addresses are taken by make_ip4_setting()) * Pick up just IPv6 addresses (IPv4 addresses are taken by make_ip4_setting())
*/ */
for (i = 1; i < 10000; i++) { for (i = 1; i < 10000; i++) {
gs_free_error GError *err = NULL;
NMDnsServer dns; NMDnsServer dns;
char tag[256]; char tag[256];
@ -2616,14 +2619,15 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
if (!v) if (!v)
break; break;
if (!nm_dns_uri_parse(AF_UNSPEC, v, &dns)) { if (!nm_dns_uri_parse(AF_UNSPEC, v, &dns, &err)) {
if (is_disabled) if (is_disabled)
continue; continue;
g_set_error(error, g_set_error(error,
NM_SETTINGS_ERROR, NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"Invalid DNS server address '%s'", "Invalid DNS server address '%s': %s",
v); v,
err->message);
return NULL; return NULL;
} }
if (dns.addr_family == AF_INET6) { if (dns.addr_family == AF_INET6) {

View file

@ -2076,3 +2076,8 @@ global:
nm_setting_sriov_get_preserve_on_down; nm_setting_sriov_get_preserve_on_down;
nm_sriov_preserve_on_down_get_type; nm_sriov_preserve_on_down_get_type;
} libnm_1_52_0; } libnm_1_52_0;
libnm_1_56_0 {
global:
nm_dns_server_validate;
} libnm_1_54_0;

View file

@ -705,6 +705,7 @@ nm_mptcp_flags_normalize(NMMptcpFlags flags)
* @addr_family: the address family, or AF_UNSPEC to autodetect it * @addr_family: the address family, or AF_UNSPEC to autodetect it
* @str: the name server URI string to parse * @str: the name server URI string to parse
* @dns: the name server descriptor to fill, or %NULL * @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 * Parses the given name server URI string. Each name server is represented
* by the following grammar: * by the following grammar:
@ -731,7 +732,7 @@ nm_mptcp_flags_normalize(NMMptcpFlags flags)
* Returns: %TRUE on success, %FALSE on failure * Returns: %TRUE on success, %FALSE on failure
*/ */
gboolean gboolean
nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns) nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns, GError **error)
{ {
NMDnsServer dns_stack; NMDnsServer dns_stack;
gs_free char *addr_port_heap = NULL; gs_free char *addr_port_heap = NULL;
@ -746,8 +747,13 @@ nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns)
if (!dns) if (!dns)
dns = &dns_stack; dns = &dns_stack;
if (!str) if (!str) {
g_set_error_literal(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("the string is empty"));
return FALSE; return FALSE;
}
*dns = (NMDnsServer) {0}; *dns = (NMDnsServer) {0};
@ -765,12 +771,21 @@ nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns)
} }
if (name && name[0] == '\0') { if (name && name[0] == '\0') {
/* empty DoT server name is not allowed */ g_set_error_literal(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("the DNS-over-TLS server name is empty"));
return FALSE; return FALSE;
} }
if (!nm_inet_parse_bin(addr_family, str, &dns->addr_family, &dns->addr)) 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; return FALSE;
}
dns->servername = name; dns->servername = name;
dns->scheme = NM_DNS_URI_SCHEME_NONE; dns->scheme = NM_DNS_URI_SCHEME_NONE;
@ -784,7 +799,10 @@ nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns)
addr_port = nm_strndup_a(100, addr_port, name - addr_port, &addr_port_heap); addr_port = nm_strndup_a(100, addr_port, name - addr_port, &addr_port_heap);
name++; name++;
if (*name == '\0') { if (*name == '\0') {
/* empty DoT server name not allowed */ g_set_error_literal(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("the DNS-over-TLS server name is empty"));
return FALSE; return FALSE;
} }
dns->servername = name; dns->servername = name;
@ -797,8 +815,13 @@ nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns)
addr_family = AF_INET6; addr_family = AF_INET6;
addr_port++; addr_port++;
end = strchr(addr_port, ']'); end = strchr(addr_port, ']');
if (!end) if (!end) {
g_set_error_literal(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("unterminated square bracket"));
return FALSE; return FALSE;
}
addr = nm_strndup_a(100, addr_port, end - addr_port, &addr_heap); addr = nm_strndup_a(100, addr_port, end - addr_port, &addr_heap);
/* IPv6 link-local scope-id */ /* IPv6 link-local scope-id */
@ -806,18 +829,29 @@ nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns)
if (perc) { if (perc) {
*perc = '\0'; *perc = '\0';
if (g_strlcpy(dns->interface, perc + 1, sizeof(dns->interface)) if (g_strlcpy(dns->interface, perc + 1, sizeof(dns->interface))
>= 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; return FALSE;
} }
}
/* port */ /* port */
end++; end++;
if (*end == ':') { if (*end == ':') {
end++; end++;
dns->port = _nm_utils_ascii_str_to_int64(end, 10, 1, 65535, 0); dns->port = _nm_utils_ascii_str_to_int64(end, 10, 1, 65535, 0);
if (dns->port == 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; return FALSE;
} }
}
} else if (addr_family != AF_INET6) { } else if (addr_family != AF_INET6) {
/* square brackets are mandatory for IPv6, so it must be IPv4 */ /* square brackets are mandatory for IPv6, so it must be IPv4 */
@ -830,23 +864,49 @@ nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns)
addr = nm_strndup_a(100, addr_port, port - addr_port, &addr_heap); addr = nm_strndup_a(100, addr_port, port - addr_port, &addr_heap);
port++; port++;
dns->port = _nm_utils_ascii_str_to_int64(port, 10, 1, 65535, 0); dns->port = _nm_utils_ascii_str_to_int64(port, 10, 1, 65535, 0);
if (dns->port == 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; return FALSE;
} }
}
} else { } else {
g_set_error_literal(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("IPv6 addresses must be enclosed in square brackets"));
return FALSE; return FALSE;
} }
if (!nm_inet_parse_bin(addr_family, addr, &dns->addr_family, &dns->addr)) 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; return FALSE;
}
if (dns->scheme != NM_DNS_URI_SCHEME_TLS && dns->servername) 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; return FALSE;
}
/* For now, allow the interface only for IPv6 link-local addresses */ /* For now, allow the interface only for IPv6 link-local addresses */
if (dns->interface[0] if (dns->interface[0]
&& (dns->addr_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&dns->addr.addr6))) && (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 FALSE;
}
return TRUE; return TRUE;
} }
@ -870,7 +930,7 @@ nm_dns_uri_parse_plain(int addr_family, const char *str, char *out_addrstr, NMIP
{ {
NMDnsServer dns; NMDnsServer dns;
if (!nm_dns_uri_parse(addr_family, str, &dns)) if (!nm_dns_uri_parse(addr_family, str, &dns, NULL))
return FALSE; return FALSE;
switch (dns.scheme) { switch (dns.scheme) {
@ -922,7 +982,7 @@ nm_dns_uri_normalize(int addr_family, const char *str, char **out_free)
nm_assert(str); nm_assert(str);
nm_assert(out_free && !*out_free); nm_assert(out_free && !*out_free);
if (!nm_dns_uri_parse(addr_family, str, &dns)) if (!nm_dns_uri_parse(addr_family, str, &dns, NULL))
return NULL; return NULL;
nm_inet_ntop(dns.addr_family, &dns.addr, addrstr); nm_inet_ntop(dns.addr_family, &dns.addr, addrstr);

View file

@ -330,7 +330,7 @@ typedef struct {
guint16 port; guint16 port;
} NMDnsServer; } NMDnsServer;
gboolean nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *out_dns); gboolean nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *out_dns, GError **error);
gboolean gboolean
nm_dns_uri_parse_plain(int addr_family, const char *str, char *out_addrstr, NMIPAddr *out_addr); nm_dns_uri_parse_plain(int addr_family, const char *str, char *out_addrstr, NMIPAddr *out_addr);
const char *nm_dns_uri_normalize(int addr_family, const char *str, char **out_free); const char *nm_dns_uri_normalize(int addr_family, const char *str, char **out_free);

View file

@ -1158,14 +1158,17 @@ ip_dns_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
addr_family = NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting); addr_family = NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting);
for (i = 0, n = 0; i < length; i++) { for (i = 0, n = 0; i < length; i++) {
if (!nm_dns_uri_parse(addr_family, list[i], NULL)) { gs_free_error GError *error = NULL;
if (!nm_dns_uri_parse(addr_family, list[i], NULL, &error)) {
if (!read_handle_warn(info, if (!read_handle_warn(info,
key, key,
key, key,
NM_KEYFILE_WARN_SEVERITY_WARN, NM_KEYFILE_WARN_SEVERITY_WARN,
_("ignoring invalid DNS server IPv%c address '%s'"), _("ignoring invalid DNS server IPv%c address '%s': %s"),
nm_utils_addr_family_to_char(addr_family), nm_utils_addr_family_to_char(addr_family),
list[i])) { list[i],
error->message)) {
do { do {
nm_clear_g_free(&list[i]); nm_clear_g_free(&list[i]);
} while (++i < length); } while (++i < length);

View file

@ -4221,6 +4221,25 @@ nm_setting_ip_config_clear_dns(NMSettingIPConfig *setting)
} }
} }
/**
* nm_dns_server_validate:
* @str: the string containing the DNS server
* @family: the IP address family (%AF_INET for IPv4, %AF_INET6 for IPv6,
* %AF_UNSPEC to accept both IPv4 and IPv6)
* @error: (nullable): a pointer to %NULL #GError, or %NULL
*
* Validates a DNS name server string.
*
* Return: %TRUE if the name server is valid, %FALSE otherwise
*
* Since: 1.56
*/
gboolean
nm_dns_server_validate(const char *str, int family, GError **error)
{
return nm_dns_uri_parse(family, str, NULL, error);
}
GPtrArray * GPtrArray *
_nm_setting_ip_config_get_dns_array(NMSettingIPConfig *setting) _nm_setting_ip_config_get_dns_array(NMSettingIPConfig *setting)
{ {
@ -5631,13 +5650,18 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
if (priv->dns) { if (priv->dns) {
for (i = 0; i < priv->dns->len; i++) { for (i = 0; i < priv->dns->len; i++) {
const char *dns = priv->dns->pdata[i]; const char *dns = priv->dns->pdata[i];
gs_free_error GError *local = NULL;
if (!nm_dns_uri_parse(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting), dns, NULL)) { if (!nm_dns_uri_parse(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting),
dns,
NULL,
&local)) {
g_set_error(error, g_set_error(error,
NM_CONNECTION_ERROR, NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("%u. DNS server address is invalid"), _("%u. DNS server address is invalid: %s"),
(i + 1u)); (i + 1u),
local->message);
g_prefix_error(error, g_prefix_error(error,
"%s.%s: ", "%s.%s: ",
nm_setting_get_name(setting), nm_setting_get_name(setting),

View file

@ -11400,12 +11400,16 @@ test_connection_path(void)
static void static void
t_dns_0(const char *str) t_dns_0(const char *str)
{ {
gs_free_error GError *error = NULL;
NMDnsServer server = {}; NMDnsServer server = {};
gboolean ret; gboolean ret;
ret = nm_dns_uri_parse(AF_UNSPEC, str, &server); ret = nm_dns_uri_parse(AF_UNSPEC, str, &server, &error);
g_assert(!ret); g_assert(!ret);
g_assert(error);
g_assert(error->message);
g_assert(error->message[0] != '\0');
} }
static void static void
@ -11422,10 +11426,12 @@ dns_uri_parse_ok(const char *str,
gboolean ret; gboolean ret;
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
gs_free_error GError *error = NULL;
gboolean af_unspec = i; gboolean af_unspec = i;
ret = nm_dns_uri_parse(af_unspec ? AF_UNSPEC : addr_family, str, &dns); ret = nm_dns_uri_parse(af_unspec ? AF_UNSPEC : addr_family, str, &dns, &error);
g_assert(ret); g_assert(ret);
g_assert_no_error(error);
g_assert_cmpint(addr_family, ==, dns.addr_family); g_assert_cmpint(addr_family, ==, dns.addr_family);
g_assert_cmpint(port, ==, dns.port); g_assert_cmpint(port, ==, dns.port);
@ -11436,8 +11442,9 @@ dns_uri_parse_ok(const char *str,
g_assert_cmpstr(addrstr, ==, addr); g_assert_cmpstr(addrstr, ==, addr);
/* Parse with the wrong address family must fail */ /* Parse with the wrong address family must fail */
ret = nm_dns_uri_parse(addr_family == AF_INET ? AF_INET6 : AF_INET, str, &dns); ret = nm_dns_uri_parse(addr_family == AF_INET ? AF_INET6 : AF_INET, str, &dns, &error);
g_assert(!ret); g_assert(!ret);
g_assert(error);
} }
} }

View file

@ -567,6 +567,9 @@ int nm_setting_ip_config_get_shared_dhcp_lease_time(NMSettingIPConfig *setting);
NM_AVAILABLE_IN_1_54 NM_AVAILABLE_IN_1_54
NMSettingIPConfigForwarding nm_setting_ip_config_get_forwarding(NMSettingIPConfig *setting); NMSettingIPConfigForwarding nm_setting_ip_config_get_forwarding(NMSettingIPConfig *setting);
NM_AVAILABLE_IN_1_56
gboolean nm_dns_server_validate(const char *str, int family, GError **error);
G_END_DECLS G_END_DECLS
#endif /* NM_SETTING_IP_CONFIG_H */ #endif /* NM_SETTING_IP_CONFIG_H */

View file

@ -1273,8 +1273,10 @@ reader_parse_rd_znet(Reader *reader, char *argument, gboolean net_ifnames)
static void static void
reader_parse_global_dns(Reader *reader, char *argument) reader_parse_global_dns(Reader *reader, char *argument)
{ {
if (!nm_dns_uri_parse(AF_UNSPEC, argument, NULL)) { gs_free_error GError *error = NULL;
_LOGW(LOGD_CORE, "rd.net.dns: invalid server '%s'", argument);
if (!nm_dns_uri_parse(AF_UNSPEC, argument, NULL, &error)) {
_LOGW(LOGD_CORE, "rd.net.dns: invalid server '%s': %s", argument, error->message);
return; return;
} }

View file

@ -2664,7 +2664,7 @@ test_global_dns(void)
gs_free char *dns_resolve_mode = NULL; gs_free char *dns_resolve_mode = NULL;
gint64 carrier_timeout_sec = 0; gint64 carrier_timeout_sec = 0;
NMTST_EXPECT_NM_WARN("cmdline-reader: rd.net.dns: invalid server 'foobar'"); NMTST_EXPECT_NM_WARN("cmdline-reader: rd.net.dns: invalid server 'foobar':*");
connections = _parse(ARGV, connections = _parse(ARGV,
&hostname, &hostname,
&carrier_timeout_sec, &carrier_timeout_sec,