summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2022-10-17 09:28:04 +0200
committerThomas Haller <thaller@redhat.com>2022-10-27 09:11:31 +0200
commit6f9090538f39ad48321a17e8acbd03ae9fd0c938 (patch)
tree1a7e9583a5e0b53e338e9b01846bd2b8707c7475
parenta20aae326fc8c230c7ac1504dd8270be2aff2f36 (diff)
dns: accept DoT SNI server name in "ipv[46].dns" settings
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.c12
-rw-r--r--src/core/nm-l3-config-data.c6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c40
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-13
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c15
-rw-r--r--src/libnm-core-aux-intern/nm-libnm-core-utils.c110
-rw-r--r--src/libnm-core-aux-intern/nm-libnm-core-utils.h29
-rw-r--r--src/libnm-core-impl/nm-keyfile.c13
-rw-r--r--src/libnm-core-impl/nm-setting-ip-config.c48
-rw-r--r--src/libnm-core-impl/nm-utils.c13
-rw-r--r--src/libnm-core-impl/tests/test-general.c176
-rw-r--r--src/libnmc-setting/settings-docs.h.in4
-rw-r--r--src/nmcli/generate-docs-nm-settings-nmcli.xml.in4
15 files changed, 424 insertions, 54 deletions
diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c
index b82c9c8385..759fb0cc01 100644
--- a/src/core/devices/wifi/nm-wifi-utils.c
+++ b/src/core/devices/wifi/nm-wifi-utils.c
@@ -1578,9 +1578,19 @@ ip_config_to_iwd_config(int addr_family, GKeyFile *file, NMSettingIPConfig *s_ip
if (num) {
nm_str_buf_reset(&strbuf);
for (i = 0; i < num; i++) {
+ char sbuf[NM_INET_ADDRSTRLEN];
+ NMIPAddr a;
+
+ if (!nm_utils_dnsname_parse_assert(addr_family,
+ nm_setting_ip_config_get_dns(s_ip, i),
+ NULL,
+ &a,
+ NULL))
+ continue;
+
if (strbuf.len > 0)
nm_str_buf_append_c(&strbuf, ' ');
- nm_str_buf_append(&strbuf, nm_setting_ip_config_get_dns(s_ip, i));
+ nm_str_buf_append(&strbuf, nm_inet_ntop(addr_family, &a, sbuf));
}
/* It doesn't matter whether we add the DNS under [IPv4] or [IPv6]
* except that with method=auto the list will override the
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index 2d4389323f..7e3d1d931b 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -2844,12 +2844,16 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co
nnameservers = nm_setting_ip_config_get_num_dns(s_ip);
for (i = 0; i < nnameservers; i++) {
+ const char *server_name;
const char *s;
NMIPAddr ip;
s = nm_setting_ip_config_get_dns(s_ip, i);
- if (!nm_inet_parse_bin(addr_family, s, NULL, &ip))
+
+ if (!nm_utils_dnsname_parse_assert(addr_family, s, NULL, &ip, &server_name))
continue;
+
+ /* TODO: handle server_name. */
nm_l3_config_data_add_nameserver(self, addr_family, &ip);
}
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 96f5969728..0680b23e71 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -2047,7 +2047,9 @@ make_ip4_setting(shvarFile *ifcfg,
* Pick up just IPv4 addresses (IPv6 addresses are taken by make_ip6_setting())
*/
for (i = 1; i < 10000; i++) {
- char tag[256];
+ int af;
+ NMIPAddr ip;
+ char tag[256];
numbered_tag(tag, "DNS", i);
nm_clear_g_free(&value);
@@ -2055,18 +2057,18 @@ make_ip4_setting(shvarFile *ifcfg,
if (!v)
break;
- if (nm_inet_is_valid(AF_INET, v)) {
- if (!nm_setting_ip_config_add_dns(s_ip4, v))
- PARSE_WARNING("duplicate DNS server %s", tag);
- } else if (nm_inet_is_valid(AF_INET6, v)) {
- /* Ignore IPv6 addresses */
- } else {
+ if (!nm_utils_dnsname_parse(AF_UNSPEC, v, &af, &ip, NULL)) {
g_set_error(error,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION,
"Invalid DNS server address '%s'",
v);
return NULL;
+ } else if (af == AF_INET) {
+ if (!nm_setting_ip_config_add_dns(s_ip4, v))
+ PARSE_WARNING("duplicate DNS server %s", tag);
+ } else {
+ /* Ignore IPv6 addresses */
}
}
@@ -2586,7 +2588,9 @@ 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++) {
- char tag[256];
+ int af;
+ NMIPAddr ip;
+ char tag[256];
numbered_tag(tag, "DNS", i);
nm_clear_g_free(&value);
@@ -2594,16 +2598,7 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
if (!v)
break;
- if (nm_inet_is_valid(AF_INET6, v)) {
- if (is_disabled) {
- PARSE_WARNING("ignore DNS server addresses with method disabled/ignore");
- break;
- }
- if (!nm_setting_ip_config_add_dns(s_ip6, v))
- PARSE_WARNING("duplicate DNS server %s", tag);
- } else if (nm_inet_is_valid(AF_INET, v)) {
- /* Ignore IPv4 addresses */
- } else {
+ if (!nm_utils_dnsname_parse(AF_UNSPEC, v, &af, &ip, NULL)) {
if (is_disabled)
continue;
g_set_error(error,
@@ -2612,6 +2607,15 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
"Invalid DNS server address '%s'",
v);
return NULL;
+ } else if (af == AF_INET6) {
+ if (is_disabled) {
+ PARSE_WARNING("ignore DNS server addresses with method disabled/ignore");
+ break;
+ }
+ if (!nm_setting_ip_config_add_dns(s_ip6, v))
+ PARSE_WARNING("duplicate DNS server %s", tag);
+ } else {
+ /* Ignore IPv4 addresses */
}
}
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1
index ecb36c3792..5e40362e9f 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1
@@ -1,4 +1,7 @@
DNS1="192.0.2.1"
+DNS2="192.0.2.2#adfs.afddsaf"
+DNS3="1::2"
+DNS4="1::3#dfdf.er"
IPADDR="102.0.2.2"
GATEWAY="192.0.2.1"
NETMASK="255.254.0.0"
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
index 87493ac2b3..d0eeeb9911 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
@@ -1,4 +1,7 @@
DNS1=192.0.2.1
+DNS2=192.0.2.2#adfs.afddsaf
+DNS3=1::2
+DNS4=1::3#dfdf.er
IPADDR=102.0.2.2
GATEWAY=192.0.2.1
NETMASK=255.254.0.0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes
index 7faf49bdae..fac9a5a28c 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes
@@ -9,7 +9,7 @@ IPV6INIT=no
MTU=1492
NM_CONTROLLED=yes
DNS1=4.2.2.1
-DNS2=4.2.2.2
+DNS2=4.2.2.2#dns.name
IPADDR=192.168.1.5
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index 653a52133a..aba9b922dc 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -461,6 +461,7 @@ test_read_netmask_1(void)
gs_free char *content = NULL;
NMSettingConnection *s_con;
NMSettingIPConfig *s_ip4;
+ NMSettingIPConfig *s_ip6;
NMIPAddress *ip4_addr;
const char *FILENAME = TEST_IFCFG_DIR "/ifcfg-netmask-1";
@@ -470,12 +471,20 @@ test_read_netmask_1(void)
g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System netmask-1");
s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
- g_assert_cmpuint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 1);
+ g_assert_cmpuint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
+ g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "192.0.2.1");
+ g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "192.0.2.2#adfs.afddsaf");
+
ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
g_assert(ip4_addr);
g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "102.0.2.2");
g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 15);
+ s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
+ g_assert_cmpuint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2);
+ g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1::2");
+ g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "1::3#dfdf.er");
+
nmtst_assert_connection_verifies_without_normalization(connection);
content = nmtst_file_get_contents(FILENAME);
@@ -1390,6 +1399,10 @@ test_read_wired_static_routes(void)
g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32);
nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local");
nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SCOPE, 254);
+
+ g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
+ g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1");
+ g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2#dns.name");
}
static void
diff --git a/src/libnm-core-aux-intern/nm-libnm-core-utils.c b/src/libnm-core-aux-intern/nm-libnm-core-utils.c
index b76ff0fa3d..0e464fba42 100644
--- a/src/libnm-core-aux-intern/nm-libnm-core-utils.c
+++ b/src/libnm-core-aux-intern/nm-libnm-core-utils.c
@@ -520,3 +520,113 @@ nm_mptcp_flags_normalize(NMMptcpFlags flags)
return flags;
}
+
+/*****************************************************************************/
+
+gboolean
+nm_utils_dnsname_parse(int addr_family,
+ const char *dns,
+ int *out_addr_family,
+ gpointer /* (NMIPAddr **) */ out_addr,
+ const char **out_servername)
+{
+ gs_free char *dns_heap = NULL;
+ const char *s;
+ NMIPAddr addr;
+
+ nm_assert_addr_family_or_unspec(addr_family);
+ nm_assert(!out_addr || out_addr_family || NM_IN_SET(addr_family, AF_INET, AF_INET6));
+
+ if (!dns)
+ return FALSE;
+
+ s = strchr(dns, '#');
+
+ if (s) {
+ dns = nm_strndup_a(200, dns, s - dns, &dns_heap);
+ s++;
+ }
+
+ if (s && s[0] == '\0') {
+ /* "ADDR#" empty DoT SNI name is not allowed. */
+ return FALSE;
+ }
+
+ if (!nm_inet_parse_bin(addr_family, dns, &addr_family, out_addr ? &addr : NULL))
+ return FALSE;
+
+ NM_SET_OUT(out_addr_family, addr_family);
+ if (out_addr)
+ nm_ip_addr_set(addr_family, out_addr, &addr);
+ NM_SET_OUT(out_servername, s);
+ return TRUE;
+}
+
+const char *
+nm_utils_dnsname_construct(int addr_family,
+ gconstpointer /* (const NMIPAddr *) */ addr,
+ const char *server_name,
+ char *result,
+ gsize result_len)
+{
+ char sbuf[NM_INET_ADDRSTRLEN];
+ gsize l;
+ int d;
+
+ nm_assert_addr_family(addr_family);
+ nm_assert(addr);
+ nm_assert(!server_name || !nm_str_is_empty(server_name));
+
+ nm_inet_ntop(addr_family, addr, sbuf);
+
+ if (!server_name) {
+ l = g_strlcpy(result, sbuf, result_len);
+ } else {
+ d = g_snprintf(result, result_len, "%s#%s", sbuf, server_name);
+ nm_assert(d >= 0);
+ l = (gsize) d;
+ }
+
+ return l < result_len ? result : NULL;
+}
+
+const char *
+nm_utils_dnsname_normalize(int addr_family, const char *dns, char **out_free)
+{
+ char sbuf[NM_INET_ADDRSTRLEN];
+ const char *server_name;
+ char *s;
+ NMIPAddr a;
+ gsize l;
+
+ nm_assert_addr_family_or_unspec(addr_family);
+ nm_assert(dns);
+ nm_assert(out_free && !*out_free);
+
+ if (!nm_utils_dnsname_parse(addr_family, dns, &addr_family, &a, &server_name))
+ return NULL;
+
+ nm_inet_ntop(addr_family, &a, sbuf);
+
+ l = strlen(sbuf);
+
+ /* In the vast majority of cases, the name is in fact normalized. Check
+ * whether it is, and don't duplicate the string. */
+ if (strncmp(dns, sbuf, l) == 0) {
+ if (server_name) {
+ if (dns[l] == '#' && nm_streq(&dns[l + 1], server_name))
+ return dns;
+ } else {
+ if (dns[l] == '\0')
+ return dns;
+ }
+ }
+
+ if (!server_name)
+ s = g_strdup(sbuf);
+ else
+ s = g_strconcat(sbuf, "#", server_name, NULL);
+
+ *out_free = s;
+ return s;
+}
diff --git a/src/libnm-core-aux-intern/nm-libnm-core-utils.h b/src/libnm-core-aux-intern/nm-libnm-core-utils.h
index 0208bfdb24..63e8be4ec1 100644
--- a/src/libnm-core-aux-intern/nm-libnm-core-utils.h
+++ b/src/libnm-core-aux-intern/nm-libnm-core-utils.h
@@ -279,4 +279,33 @@ gpointer _nm_connection_new_setting(NMConnection *connection, GType gtype);
NMMptcpFlags nm_mptcp_flags_normalize(NMMptcpFlags flags);
+/*****************************************************************************/
+
+gboolean nm_utils_dnsname_parse(int addr_family,
+ const char *dns,
+ int *out_addr_family,
+ gpointer /* (NMIPAddr **) */ out_addr,
+ const char **out_servername);
+
+#define nm_utils_dnsname_parse_assert(addr_family, dns, out_addr_family, out_addr, out_servername) \
+ ({ \
+ gboolean _good; \
+ \
+ _good = nm_utils_dnsname_parse((addr_family), \
+ (dns), \
+ (out_addr_family), \
+ (out_addr), \
+ (out_servername)); \
+ nm_assert(_good); \
+ _good; \
+ })
+
+const char *nm_utils_dnsname_construct(int addr_family,
+ gconstpointer /* (const NMIPAddr *) */ addr,
+ const char *server_name,
+ char *result,
+ gsize result_len);
+
+const char *nm_utils_dnsname_normalize(int addr_family, const char *dns, char **out_free);
+
#endif /* __NM_LIBNM_SHARED_UTILS_H__ */
diff --git a/src/libnm-core-impl/nm-keyfile.c b/src/libnm-core-impl/nm-keyfile.c
index ae6d892c28..a41974dd02 100644
--- a/src/libnm-core-impl/nm-keyfile.c
+++ b/src/libnm-core-impl/nm-keyfile.c
@@ -1102,7 +1102,9 @@ ip_dns_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
{
int addr_family;
gs_strfreev char **list = NULL;
- gsize i, n, length;
+ gsize length;
+ gsize n;
+ gsize i;
nm_assert(NM_IS_SETTING_IP4_CONFIG(setting) || NM_IS_SETTING_IP6_CONFIG(setting));
@@ -1115,13 +1117,10 @@ ip_dns_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
if (length == 0)
return;
- addr_family = NM_IS_SETTING_IP4_CONFIG(setting) ? AF_INET : AF_INET6;
-
- n = 0;
- for (i = 0; i < length; i++) {
- NMIPAddr addr;
+ addr_family = NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting);
- if (inet_pton(addr_family, list[i], &addr) <= 0) {
+ for (i = 0, n = 0; i < length; i++) {
+ if (!nm_utils_dnsname_parse(addr_family, list[i], NULL, NULL, NULL)) {
if (!read_handle_warn(info,
key,
key,
diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c
index e2bcebf2d5..218a5a745d 100644
--- a/src/libnm-core-impl/nm-setting-ip-config.c
+++ b/src/libnm-core-impl/nm-setting-ip-config.c
@@ -4041,22 +4041,23 @@ static gboolean
_ip_config_add_dns(NMSettingIPConfig *setting, const char *dns)
{
NMSettingIPConfigPrivate *priv;
- const int addr_family = NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting);
- NMIPAddr dns_bin;
- char dns_canonical[NM_INET_ADDRSTRLEN];
+ gs_free char *s_free = NULL;
+ const char *s;
nm_assert(NM_IS_SETTING_IP_CONFIG(setting));
nm_assert(dns);
priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
- if (valid_ip(addr_family, dns, &dns_bin, NULL))
- dns = nm_inet_ntop(addr_family, &dns_bin, dns_canonical);
+ s = nm_utils_dnsname_normalize(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting), dns, &s_free);
+ if (!s)
+ s = dns;
- if (nm_strv_ptrarray_contains(priv->dns, dns))
+ if (nm_strv_ptrarray_contains(priv->dns, s))
return FALSE;
- nm_strv_ptrarray_add_string_dup(nm_strv_ptrarray_ensure(&priv->dns), dns);
+ nm_strv_ptrarray_add_string_take(nm_strv_ptrarray_ensure(&priv->dns),
+ g_steal_pointer(&s_free) ?: g_strdup(s));
return TRUE;
}
@@ -4124,9 +4125,6 @@ gboolean
nm_setting_ip_config_remove_dns_by_value(NMSettingIPConfig *setting, const char *dns)
{
NMSettingIPConfigPrivate *priv;
- int addr_family;
- NMIPAddr dns_bin;
- char dns_canonical[NM_INET_ADDRSTRLEN];
gssize idx;
g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE);
@@ -4134,16 +4132,23 @@ nm_setting_ip_config_remove_dns_by_value(NMSettingIPConfig *setting, const char
priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
- addr_family = NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting);
+ /* "priv->dns" can only contain normalized or invalid values. Expect that
+ * "dns" is normalized already, so lookup first for that string. Only
+ * if that fails, fallback to normalize "dns". */
+ idx = nm_strv_ptrarray_find_first(priv->dns, dns);
+ if (idx < 0) {
+ gs_free char *s_free = NULL;
+ const char *s;
- if (valid_ip(addr_family, dns, &dns_bin, NULL))
- dns = nm_inet_ntop(addr_family, &dns_bin, dns_canonical);
+ s = nm_utils_dnsname_normalize(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting), dns, &s_free);
+ if (s && !nm_streq(dns, s))
+ idx = nm_strv_ptrarray_find_first(priv->dns, dns);
+ }
- idx = nm_strv_ptrarray_find_first(priv->dns, dns);
if (idx < 0)
return FALSE;
- g_ptr_array_remove_index(nm_strv_ptrarray_ensure(&priv->dns), idx);
+ g_ptr_array_remove_index(priv->dns, idx);
_notify(setting, PROP_DNS);
return TRUE;
}
@@ -5442,12 +5447,16 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
for (i = 0; i < priv->dns->len; i++) {
const char *dns = priv->dns->pdata[i];
- if (!nm_inet_is_valid(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting), dns)) {
+ if (!nm_utils_dnsname_parse(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting),
+ dns,
+ NULL,
+ NULL,
+ NULL)) {
g_set_error(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
- _("%d. DNS server address is invalid"),
- (int) (i + 1));
+ _("%u. DNS server address is invalid"),
+ (i + 1u));
g_prefix_error(error,
"%s.%s: ",
nm_setting_get_name(setting),
@@ -6213,6 +6222,9 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
* NMSettingIPConfig:dns:
*
* Array of IP addresses of DNS servers.
+ *
+ * For DoT (DNS over TLS), the SNI server name can be specified by appending
+ * "#example.com" to the IP address of the DNS server.
**/
obj_properties[PROP_DNS] =
g_param_spec_boxed(NM_SETTING_IP_CONFIG_DNS,
diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c
index 77eecea441..adcd1d258b 100644
--- a/src/libnm-core-impl/nm-utils.c
+++ b/src/libnm-core-impl/nm-utils.c
@@ -1317,8 +1317,12 @@ _nm_utils_ip4_dns_to_variant(const char *const *dns, gssize len)
for (i = 0; i < l; i++) {
in_addr_t ip;
- if (inet_pton(AF_INET, dns[i], &ip) == 1)
- g_variant_builder_add(&builder, "u", ip);
+ /* We can only represent the IP address on the legacy property "ipv4.dns".
+ * Expose what we can. */
+ if (!nm_utils_dnsname_parse(AF_INET, dns[i], NULL, &ip, NULL))
+ continue;
+
+ g_variant_builder_add(&builder, "u", ip);
}
return g_variant_builder_end(&builder);
@@ -1639,8 +1643,11 @@ _nm_utils_ip6_dns_to_variant(const char *const *dns, gssize len)
for (i = 0; i < l; i++) {
struct in6_addr ip;
- if (inet_pton(AF_INET6, dns[i], &ip) != 1)
+ /* We can only represent the IP address on the legacy property "ipv6.dns".
+ * Expose what we can. */
+ if (!nm_utils_dnsname_parse(AF_INET6, dns[i], NULL, &ip, NULL))
continue;
+
g_variant_builder_add(&builder, "@ay", nm_g_variant_new_ay_in6addr(&ip));
}
return g_variant_builder_end(&builder);
diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c
index d38a26471a..9317451daf 100644
--- a/src/libnm-core-impl/tests/test-general.c
+++ b/src/libnm-core-impl/tests/test-general.c
@@ -11165,6 +11165,181 @@ test_connection_path(void)
/*****************************************************************************/
+static void
+_t_dnsname_1(const char *str, const char *exp_addr, const char *exp_server_name)
+{
+ int addr_family;
+ NMIPAddr exp_addr_bin;
+ gboolean addr_family_request;
+ gboolean r;
+ int detect_addr_family;
+ NMIPAddr detect_addr;
+ const char *detect_server_name;
+ int *p_detect_addr_family = &detect_addr_family;
+ NMIPAddr *p_detect_addr = &detect_addr;
+ const char **p_detect_server_name = &detect_server_name;
+ char str_construct_buf[100];
+ char str_construct_buf2[100];
+ const char *str_construct;
+ const char *str_construct2;
+ gsize l;
+ const char *str_normalized;
+ gs_free char *str_normalized_alloc = NULL;
+
+ g_assert(str);
+ g_assert(exp_addr);
+
+ r = nm_inet_parse_bin(AF_UNSPEC, exp_addr, &addr_family, &exp_addr_bin);
+ g_assert(r);
+ g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
+
+ addr_family_request = nmtst_get_rand_bool();
+ if (nmtst_get_rand_bool())
+ p_detect_addr = NULL;
+ if ((addr_family_request || !p_detect_addr) && nmtst_get_rand_bool())
+ p_detect_addr_family = NULL;
+ if (nmtst_get_rand_bool())
+ p_detect_server_name = NULL;
+
+ r = nm_utils_dnsname_parse(addr_family_request ? addr_family : AF_UNSPEC,
+ str,
+ p_detect_addr_family,
+ p_detect_addr,
+ p_detect_server_name);
+ g_assert(r);
+
+ if (p_detect_addr_family)
+ g_assert_cmpint(addr_family, ==, detect_addr_family);
+ if (p_detect_addr)
+ g_assert_cmpstr(nmtst_inet_to_string(addr_family, &detect_addr), ==, exp_addr);
+ if (p_detect_server_name)
+ g_assert_cmpstr(detect_server_name, ==, exp_server_name);
+
+ r = nm_utils_dnsname_parse(addr_family == AF_INET ? AF_INET6 : AF_INET,
+ str,
+ p_detect_addr_family,
+ p_detect_addr,
+ p_detect_server_name);
+ g_assert(!r);
+
+ /* Construct the expected value. */
+ str_construct = nm_utils_dnsname_construct(addr_family,
+ &exp_addr_bin,
+ exp_server_name,
+ str_construct_buf,
+ sizeof(str_construct_buf));
+ g_assert(str_construct);
+ g_assert(str_construct == str_construct_buf);
+ g_assert(strlen(str_construct) < sizeof(str_construct_buf));
+
+ /* Check that a too short buffer causes truncation. */
+ l = nmtst_get_rand_uint32() % (strlen(str_construct) + 10);
+ str_construct2 = nm_utils_dnsname_construct(addr_family,
+ &exp_addr_bin,
+ exp_server_name,
+ str_construct_buf2,
+ l);
+ if (str_construct2) {
+ g_assert(str_construct2 == str_construct_buf2);
+ g_assert_cmpstr(str_construct2, ==, str_construct);
+ g_assert(l > strlen(str_construct));
+ } else
+ g_assert(l <= strlen(str_construct));
+
+ if (!nm_streq(str_construct, str)) {
+ _t_dnsname_1(str_construct, exp_addr, exp_server_name);
+ }
+
+ str_normalized = nm_utils_dnsname_normalize(nmtst_get_rand_bool() ? addr_family : AF_UNSPEC,
+ str,
+ &str_normalized_alloc);
+ g_assert(str_normalized);
+ if (str_normalized_alloc) {
+ g_assert(str_normalized == str_normalized_alloc);
+ g_assert_cmpstr(str_normalized, !=, str);
+ } else {
+ g_assert(str == str_normalized);
+ }
+ g_assert_cmpstr(str_normalized, ==, str_construct);
+
+ nm_clear_g_free(&str_normalized_alloc);
+ str_normalized = nm_utils_dnsname_normalize(addr_family == AF_INET ? AF_INET6 : AF_INET,
+ str,
+ &str_normalized_alloc);
+ g_assert(!str_normalized);
+ g_assert(!str_normalized_alloc);
+}
+
+static void
+_t_dnsname_0(const char *str)
+{
+ gboolean addr_family_request;
+ int detect_addr_family;
+ NMIPAddr detect_addr;
+ const char *detect_server_name;
+ int *p_detect_addr_family = &detect_addr_family;
+ NMIPAddr *p_detect_addr = &detect_addr;
+ const char **p_detect_server_name = &detect_server_name;
+ const char *str_normalized;
+ gs_free char *str_normalized_alloc = NULL;
+ gboolean r;
+
+ g_assert(str);
+
+ addr_family_request = nmtst_get_rand_bool();
+ if (nmtst_get_rand_bool())
+ p_detect_addr = NULL;
+ if ((addr_family_request || !p_detect_addr) && nmtst_get_rand_bool())
+ p_detect_addr_family = NULL;
+ if (nmtst_get_rand_bool())
+ p_detect_server_name = NULL;
+
+ r = nm_utils_dnsname_parse(addr_family_request ? nmtst_rand_select(AF_INET, AF_INET6)
+ : AF_UNSPEC,
+ str,
+ p_detect_addr_family,
+ p_detect_addr,
+ p_detect_server_name);
+ g_assert(!r);
+
+ str_normalized = nm_utils_dnsname_normalize(nmtst_rand_select(AF_UNSPEC, AF_INET, AF_INET6),
+ str,
+ &str_normalized_alloc);
+ g_assert(!str_normalized);
+ g_assert(!str_normalized_alloc);
+}
+
+static void
+test_dnsname(void)
+{
+ _t_dnsname_1("1.2.3.4", "1.2.3.4", NULL);
+ _t_dnsname_1("1.2.3.4#foo", "1.2.3.4", "foo");
+ _t_dnsname_1("1::#x", "1::", "x");
+ _t_dnsname_1("1::0#x", "1::", "x");
+ _t_dnsname_1("192.168.0.1", "192.168.0.1", NULL);
+ _t_dnsname_1("192.168.0.1#test.com", "192.168.0.1", "test.com");
+ _t_dnsname_1("fe80::18", "fe80::18", NULL);
+ _t_dnsname_1("fe80::18#hoge.com", "fe80::18", "hoge.com");
+
+ _t_dnsname_0("1.2.3.4#");
+ _t_dnsname_0("1::0#");
+ _t_dnsname_0("192.168.0.1:53");
+ _t_dnsname_0("192.168.0.1:53#example.com");
+ _t_dnsname_0("fe80::18%19");
+ _t_dnsname_0("fe80::18%lo");
+ _t_dnsname_0("[fe80::18]:53");
+ _t_dnsname_0("[fe80::18]:53%19");
+ _t_dnsname_0("[fe80::18]:53%lo");
+ _t_dnsname_0("fe80::18%19#hoge.com");
+ _t_dnsname_0("[fe80::18]:53#hoge.com");
+ _t_dnsname_0("[fe80::18]:53%19");
+ _t_dnsname_0("[fe80::18]:53%19#hoge.com");
+ _t_dnsname_0("[fe80::18]:53%lo");
+ _t_dnsname_0("[fe80::18]:53%lo#hoge.com");
+}
+
+/*****************************************************************************/
+
NMTST_DEFINE();
int
@@ -11511,6 +11686,7 @@ main(int argc, char **argv)
g_test_add_func("/core/general/test_system_encodings", test_system_encodings);
g_test_add_func("/core/general/test_direct_string_is_refstr", test_direct_string_is_refstr);
g_test_add_func("/core/general/test_connection_path", test_connection_path);
+ g_test_add_func("/core/general/test_dnsname", test_dnsname);
return g_test_run();
}
diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in
index cf87f38c15..3ee4aab51b 100644
--- a/src/libnmc-setting/settings-docs.h.in
+++ b/src/libnmc-setting/settings-docs.h.in
@@ -167,7 +167,7 @@
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_SEND_HOSTNAME N_("If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer. If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_TIMEOUT N_("A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER N_("The Vendor Class Identifier DHCP option (60). Special characters in the data string may be escaped using C-style escapes, nevertheless this property cannot contain nul bytes. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the DHCP option is not sent to the server. Since 1.28")
-#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS N_("Array of IP addresses of DNS servers.")
+#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS N_("Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending \"#example.com\" to the IP address of the DNS server.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting. A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections. It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain. Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually). When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins. If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).")
@@ -193,7 +193,7 @@
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_REJECT_SERVERS N_("Array of servers from which DHCP offers must be rejected. This property is useful to avoid getting a lease from misconfigured or rogue servers. For DHCPv4, each element must be an IPv4 address, optionally followed by a slash and a prefix length (e.g. \"192.168.122.0/24\"). This property is currently not implemented for DHCPv6.")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_SEND_HOSTNAME N_("If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer. If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent.")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_TIMEOUT N_("A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS N_("Array of IP addresses of DNS servers.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS N_("Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending \"#example.com\" to the IP address of the DNS server.")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting. A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections. It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain. Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually). When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins. If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).")
diff --git a/src/nmcli/generate-docs-nm-settings-nmcli.xml.in b/src/nmcli/generate-docs-nm-settings-nmcli.xml.in
index 464d53b9ab..1a26b86a94 100644
--- a/src/nmcli/generate-docs-nm-settings-nmcli.xml.in
+++ b/src/nmcli/generate-docs-nm-settings-nmcli.xml.in
@@ -652,7 +652,7 @@
<property name="method"
description="IP configuration method. NMSettingIP4Config and NMSettingIP6Config both support &quot;disabled&quot;, &quot;auto&quot;, &quot;manual&quot;, and &quot;link-local&quot;. See the subclass-specific documentation for other values. In general, for the &quot;auto&quot; method, properties such as &quot;dns&quot; and &quot;routes&quot; specify information that is added on to the information returned from automatic configuration. The &quot;ignore-auto-routes&quot; and &quot;ignore-auto-dns&quot; properties modify this behavior. For methods that imply no upstream network, such as &quot;shared&quot; or &quot;link-local&quot;, these properties must be empty. For IPv4 method &quot;shared&quot;, the IP subnet can be configured by adding one manual IPv4 address or otherwise 10.42.x.0/24 is chosen. Note that the shared method must be configured on the interface which shares the internet to a subnet, not on the uplink which is shared." />
<property name="dns"
- description="Array of IP addresses of DNS servers." />
+ description="Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending &quot;#example.com&quot; to the IP address of the DNS server." />
<property name="dns-search"
description="List of DNS search domains. Domains starting with a tilde (&apos;~&apos;) are considered &apos;routing&apos; domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting &quot;ignore-auto-dns&quot;. Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." />
<property name="dns-options"
@@ -709,7 +709,7 @@
<property name="method"
description="IP configuration method. NMSettingIP4Config and NMSettingIP6Config both support &quot;disabled&quot;, &quot;auto&quot;, &quot;manual&quot;, and &quot;link-local&quot;. See the subclass-specific documentation for other values. In general, for the &quot;auto&quot; method, properties such as &quot;dns&quot; and &quot;routes&quot; specify information that is added on to the information returned from automatic configuration. The &quot;ignore-auto-routes&quot; and &quot;ignore-auto-dns&quot; properties modify this behavior. For methods that imply no upstream network, such as &quot;shared&quot; or &quot;link-local&quot;, these properties must be empty. For IPv4 method &quot;shared&quot;, the IP subnet can be configured by adding one manual IPv4 address or otherwise 10.42.x.0/24 is chosen. Note that the shared method must be configured on the interface which shares the internet to a subnet, not on the uplink which is shared." />
<property name="dns"
- description="Array of IP addresses of DNS servers." />
+ description="Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending &quot;#example.com&quot; to the IP address of the DNS server." />
<property name="dns-search"
description="List of DNS search domains. Domains starting with a tilde (&apos;~&apos;) are considered &apos;routing&apos; domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting &quot;ignore-auto-dns&quot;. Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." />
<property name="dns-options"