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;
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;
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++) {
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;
if (!NM_IN_SET(dns_server.scheme,

View file

@ -3987,7 +3987,7 @@ _l3cfg_routed_dns_apply(NML3Cfg *self, const NML3ConfigData *l3cd)
NMDnsServer dns;
int r;
if (!nm_dns_uri_parse(addr_family, nameservers[i], &dns))
if (!nm_dns_uri_parse(addr_family, nameservers[i], &dns, NULL))
continue;
/* 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++) {
NMDnsServer dns;
char tag[256];
gs_free_error GError *local = NULL;
numbered_tag(tag, "DNS", i);
nm_clear_g_free(&value);
@ -2065,12 +2066,13 @@ make_ip4_setting(shvarFile *ifcfg,
if (!v)
break;
if (!nm_dns_uri_parse(AF_UNSPEC, v, &dns)) {
if (!nm_dns_uri_parse(AF_UNSPEC, v, &dns, &local)) {
g_set_error(error,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION,
"Invalid DNS server address '%s'",
v);
"Invalid DNS server address '%s': %s",
v,
local->message);
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())
*/
for (i = 1; i < 10000; i++) {
gs_free_error GError *err = NULL;
NMDnsServer dns;
char tag[256];
@ -2616,14 +2619,15 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
if (!v)
break;
if (!nm_dns_uri_parse(AF_UNSPEC, v, &dns)) {
if (!nm_dns_uri_parse(AF_UNSPEC, v, &dns, &err)) {
if (is_disabled)
continue;
g_set_error(error,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION,
"Invalid DNS server address '%s'",
v);
"Invalid DNS server address '%s': %s",
v,
err->message);
return NULL;
}
if (dns.addr_family == AF_INET6) {

View file

@ -2076,3 +2076,8 @@ global:
nm_setting_sriov_get_preserve_on_down;
nm_sriov_preserve_on_down_get_type;
} 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
* @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:
@ -731,7 +732,7 @@ nm_mptcp_flags_normalize(NMMptcpFlags flags)
* Returns: %TRUE on success, %FALSE on failure
*/
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;
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)
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;
}
*dns = (NMDnsServer) {0};
@ -765,12 +771,21 @@ nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns)
}
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;
}
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;
}
dns->servername = name;
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);
name++;
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;
}
dns->servername = name;
@ -797,8 +815,13 @@ nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns)
addr_family = AF_INET6;
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;
}
addr = nm_strndup_a(100, addr_port, end - addr_port, &addr_heap);
/* IPv6 link-local scope-id */
@ -806,18 +829,29 @@ nm_dns_uri_parse(int addr_family, const char *str, NMDnsServer *dns)
if (perc) {
*perc = '\0';
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;
}
}
/* port */
end++;
if (*end == ':') {
end++;
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;
}
}
} else if (addr_family != AF_INET6) {
/* 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);
port++;
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;
}
}
} 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))
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)
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)))
&& (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;
}
@ -870,7 +930,7 @@ nm_dns_uri_parse_plain(int addr_family, const char *str, char *out_addrstr, NMIP
{
NMDnsServer dns;
if (!nm_dns_uri_parse(addr_family, str, &dns))
if (!nm_dns_uri_parse(addr_family, str, &dns, NULL))
return FALSE;
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(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;
nm_inet_ntop(dns.addr_family, &dns.addr, addrstr);

View file

@ -330,7 +330,7 @@ typedef struct {
guint16 port;
} 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
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);

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);
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,
key,
key,
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),
list[i])) {
list[i],
error->message)) {
do {
nm_clear_g_free(&list[i]);
} 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 *
_nm_setting_ip_config_get_dns_array(NMSettingIPConfig *setting)
{
@ -5631,13 +5650,18 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
if (priv->dns) {
for (i = 0; i < priv->dns->len; 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,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("%u. DNS server address is invalid"),
(i + 1u));
_("%u. DNS server address is invalid: %s"),
(i + 1u),
local->message);
g_prefix_error(error,
"%s.%s: ",
nm_setting_get_name(setting),

View file

@ -11400,12 +11400,16 @@ test_connection_path(void)
static void
t_dns_0(const char *str)
{
gs_free_error GError *error = NULL;
NMDnsServer server = {};
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(error);
g_assert(error->message);
g_assert(error->message[0] != '\0');
}
static void
@ -11422,10 +11426,12 @@ dns_uri_parse_ok(const char *str,
gboolean ret;
for (int i = 0; i < 2; i++) {
gs_free_error GError *error = NULL;
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_no_error(error);
g_assert_cmpint(addr_family, ==, dns.addr_family);
g_assert_cmpint(port, ==, dns.port);
@ -11436,8 +11442,9 @@ dns_uri_parse_ok(const char *str,
g_assert_cmpstr(addrstr, ==, addr);
/* 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(error);
}
}

View file

@ -567,6 +567,9 @@ int nm_setting_ip_config_get_shared_dhcp_lease_time(NMSettingIPConfig *setting);
NM_AVAILABLE_IN_1_54
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
#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
reader_parse_global_dns(Reader *reader, char *argument)
{
if (!nm_dns_uri_parse(AF_UNSPEC, argument, NULL)) {
_LOGW(LOGD_CORE, "rd.net.dns: invalid server '%s'", argument);
gs_free_error GError *error = NULL;
if (!nm_dns_uri_parse(AF_UNSPEC, argument, NULL, &error)) {
_LOGW(LOGD_CORE, "rd.net.dns: invalid server '%s': %s", argument, error->message);
return;
}

View file

@ -2664,7 +2664,7 @@ test_global_dns(void)
gs_free char *dns_resolve_mode = NULL;
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,
&hostname,
&carrier_timeout_sec,