diff options
author | Thomas Haller <thaller@redhat.com> | 2021-02-11 14:43:45 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2021-02-11 14:43:45 +0100 |
commit | a21fdd42376233eee5ad2c75338322ad4641de11 (patch) | |
tree | 3af2a310babdc21167d0a73edae20ca272e3c0b4 | |
parent | 04e824a2568c1cb22f1d4b4ea2ef0e9faf682e94 (diff) | |
parent | 4b874019addeac0b1f88d7cb9f46b9021c540563 (diff) | |
download | NetworkManager-a21fdd42376233eee5ad2c75338322ad4641de11.tar.gz |
dhcp: merge branch 'th/dhcp-nettools-lease-parse'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/750
-rw-r--r-- | libnm-core/nm-utils.c | 3 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-shared-utils.c | 36 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-shared-utils.h | 3 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-str-buf.h | 9 | ||||
-rw-r--r-- | shared/systemd/nm-sd-utils-shared.c | 18 | ||||
-rw-r--r-- | shared/systemd/nm-sd-utils-shared.h | 2 | ||||
-rw-r--r-- | src/core/dhcp/nm-dhcp-client.c | 31 | ||||
-rw-r--r-- | src/core/dhcp/nm-dhcp-nettools.c | 940 | ||||
-rw-r--r-- | src/core/dhcp/nm-dhcp-options.c | 268 | ||||
-rw-r--r-- | src/core/dhcp/nm-dhcp-options.h | 53 | ||||
-rw-r--r-- | src/core/dhcp/nm-dhcp-systemd.c | 104 | ||||
-rw-r--r-- | src/core/dhcp/nm-dhcp-utils.c | 285 | ||||
-rw-r--r-- | src/core/dhcp/nm-dhcp-utils.h | 31 | ||||
-rw-r--r-- | src/core/dhcp/tests/test-dhcp-utils.c | 53 | ||||
-rw-r--r-- | src/core/ndisc/nm-lndp-ndisc.c | 2 |
15 files changed, 1033 insertions, 805 deletions
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index e19d5ed5fc..6ceef1e402 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -3623,7 +3623,8 @@ nm_utils_file_search_in_paths(const char * progname, if (!path[0]) continue; - nm_str_buf_reset(&strbuf, path); + nm_str_buf_reset(&strbuf); + nm_str_buf_append(&strbuf, path); nm_str_buf_ensure_trailing_c(&strbuf, '/'); s = nm_str_buf_append0(&strbuf, progname); diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c index 492a3d5098..3215a33b5b 100644 --- a/shared/nm-glib-aux/nm-shared-utils.c +++ b/shared/nm-glib-aux/nm-shared-utils.c @@ -786,27 +786,27 @@ nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a, return 0; } -/** - * _nm_utils_ip4_get_default_prefix: - * @ip: an IPv4 address (in network byte order) - * - * When the Internet was originally set up, various ranges of IP addresses were - * segmented into three network classes: A, B, and C. This function will return - * a prefix that is associated with the IP address specified defining where it - * falls in the predefined classes. - * - * Returns: the default class prefix for the given IP - **/ -/* The function is originally from ipcalc.c of Red Hat's initscripts. */ +/*****************************************************************************/ + guint32 -_nm_utils_ip4_get_default_prefix(guint32 ip) +_nm_utils_ip4_get_default_prefix0(in_addr_t ip) { - if (((ntohl(ip) & 0xFF000000) >> 24) <= 127) - return 8; /* Class A - 255.0.0.0 */ - else if (((ntohl(ip) & 0xFF000000) >> 24) <= 191) - return 16; /* Class B - 255.255.0.0 */ + /* The function is originally from ipcalc.c of Red Hat's initscripts. */ + switch (ntohl(ip) >> 24) { + case 0 ... 127: + return 8; /* Class A */ + case 128 ... 191: + return 16; /* Class B */ + case 192 ... 223: + return 24; /* Class C */ + } + return 0; +} - return 24; /* Class C - 255.255.255.0 */ +guint32 +_nm_utils_ip4_get_default_prefix(in_addr_t ip) +{ + return _nm_utils_ip4_get_default_prefix0(ip) ?: 24; } gboolean diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h index c432b125b6..7d330458c1 100644 --- a/shared/nm-glib-aux/nm-shared-utils.h +++ b/shared/nm-glib-aux/nm-shared-utils.h @@ -699,7 +699,8 @@ nm_utils_escaped_tokens_options_escape_val(const char *val, char **out_to_free) /*****************************************************************************/ guint32 _nm_utils_ip4_prefix_to_netmask(guint32 prefix); -guint32 _nm_utils_ip4_get_default_prefix(guint32 ip); +guint32 _nm_utils_ip4_get_default_prefix0(in_addr_t ip); +guint32 _nm_utils_ip4_get_default_prefix(in_addr_t ip); gconstpointer nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint8 plen); diff --git a/shared/nm-glib-aux/nm-str-buf.h b/shared/nm-glib-aux/nm-str-buf.h index 6edd451ee5..cb0d3fb189 100644 --- a/shared/nm-glib-aux/nm-str-buf.h +++ b/shared/nm-glib-aux/nm-str-buf.h @@ -292,8 +292,10 @@ nm_str_buf_append_c_len(NMStrBuf *strbuf, char ch, gsize len) } } -static inline void -nm_str_buf_reset(NMStrBuf *strbuf, const char *str) +/*****************************************************************************/ + +static inline NMStrBuf * +nm_str_buf_reset(NMStrBuf *strbuf) { _nm_str_buf_assert(strbuf); @@ -305,8 +307,7 @@ nm_str_buf_reset(NMStrBuf *strbuf, const char *str) strbuf->_priv_len = 0; } - if (str) - nm_str_buf_append(strbuf, str); + return strbuf; } /*****************************************************************************/ diff --git a/shared/systemd/nm-sd-utils-shared.c b/shared/systemd/nm-sd-utils-shared.c index c93e89c630..f0504aa937 100644 --- a/shared/systemd/nm-sd-utils-shared.c +++ b/shared/systemd/nm-sd-utils-shared.c @@ -93,6 +93,24 @@ nm_sd_hostname_is_valid(const char *s, bool allow_trailing_dot) : (ValidHostnameFlags) 0); } +char * +nm_sd_dns_name_normalize(const char *s) +{ + nm_auto_free char *n = NULL; + int r; + + r = dns_name_normalize(s, 0, &n); + if (r < 0) + return NULL; + + nm_assert(n); + + /* usually we try not to mix malloc/g_malloc and free/g_free. In practice, + * they are the same. So here we return a buffer allocated with malloc(), + * and the caller should free it with g_free(). */ + return g_steal_pointer(&n); +} + /*****************************************************************************/ static gboolean diff --git a/shared/systemd/nm-sd-utils-shared.h b/shared/systemd/nm-sd-utils-shared.h index b4a1b189d3..45089c074d 100644 --- a/shared/systemd/nm-sd-utils-shared.h +++ b/shared/systemd/nm-sd-utils-shared.h @@ -28,6 +28,8 @@ nm_sd_dns_name_to_wire_format(const char *domain, guint8 *buffer, size_t len, gb int nm_sd_dns_name_is_valid(const char *s); gboolean nm_sd_hostname_is_valid(const char *s, bool allow_trailing_dot); +char *nm_sd_dns_name_normalize(const char *s); + /*****************************************************************************/ gboolean nm_sd_http_url_is_valid_https(const char *url); diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c index f5047244ed..c38c814ead 100644 --- a/src/core/dhcp/nm-dhcp-client.c +++ b/src/core/dhcp/nm-dhcp-client.c @@ -20,6 +20,7 @@ #include "NetworkManagerUtils.h" #include "nm-utils.h" #include "nm-dhcp-utils.h" +#include "nm-dhcp-options.h" #include "platform/nm-platform.h" #include "nm-dhcp-client-logging.h" @@ -437,8 +438,7 @@ nm_dhcp_client_set_state(NMDhcpClient *self, NMIPConfig * ip_config, GHashTable * options) { - NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); - gs_free char * event_id = NULL; + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) { g_return_if_fail(NM_IS_IP_CONFIG_ADDR_FAMILY(ip_config, priv->addr_family)); @@ -462,23 +462,36 @@ nm_dhcp_client_set_state(NMDhcpClient *self, && !NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) return; - if (_LOGI_ENABLED()) { + if (_LOGD_ENABLED()) { gs_free const char **keys = NULL; guint i, nkeys; keys = nm_utils_strdict_get_keys(options, TRUE, &nkeys); for (i = 0; i < nkeys; i++) { - _LOGI("option %-20s => '%s'", keys[i], (char *) g_hash_table_lookup(options, keys[i])); + _LOGD("option %-20s => '%s'", keys[i], (char *) g_hash_table_lookup(options, keys[i])); } } - if (priv->addr_family == AF_INET6) + if (_LOGT_ENABLED() && priv->addr_family == AF_INET6) { + gs_free char *event_id = NULL; + event_id = nm_dhcp_utils_get_dhcp6_event_id(options); + if (event_id) + _LOGT("event-id: \"%s\"", event_id); + } - _LOGI("state changed %s -> %s%s%s%s", - state_to_string(priv->state), - state_to_string(new_state), - NM_PRINT_FMT_QUOTED(event_id, ", event ID=\"", event_id, "\"", "")); + if (_LOGI_ENABLED()) { + const char *req_str = + NM_IS_IPv4(priv->addr_family) + ? nm_dhcp_option_request_string(AF_INET, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS) + : nm_dhcp_option_request_string(AF_INET6, NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS); + const char *addr = nm_g_hash_table_lookup(options, req_str); + + _LOGI("state changed %s -> %s%s%s%s", + state_to_string(priv->state), + state_to_string(new_state), + NM_PRINT_FMT_QUOTED(addr, ", address=", addr, "", "")); + } priv->state = new_state; g_signal_emit(G_OBJECT(self), signals[SIGNAL_STATE_CHANGED], 0, new_state, ip_config, options); diff --git a/src/core/dhcp/nm-dhcp-nettools.c b/src/core/dhcp/nm-dhcp-nettools.c index ae1aeadfa4..116e1bdb24 100644 --- a/src/core/dhcp/nm-dhcp-nettools.c +++ b/src/core/dhcp/nm-dhcp-nettools.c @@ -15,6 +15,7 @@ #include "nm-glib-aux/nm-dedup-multi.h" #include "nm-std-aux/unaligned.h" +#include "nm-glib-aux/nm-str-buf.h" #include "nm-utils.h" #include "nm-config.h" @@ -81,66 +82,39 @@ set_error_nettools(GError **error, int r, const char *message) nm_utils_error_set(error, r, "%s (code %d)", message, r); } -/*****************************************************************************/ - -#define DHCP_MAX_FQDN_LENGTH 255 - -enum { - NM_IN_ADDR_CLASS_A, - NM_IN_ADDR_CLASS_B, - NM_IN_ADDR_CLASS_C, - NM_IN_ADDR_CLASS_INVALID, -}; - -static int -in_addr_class(struct in_addr addr) +static inline int +_client_lease_query(NDhcp4ClientLease *lease, + uint8_t option, + const uint8_t ** datap, + size_t * n_datap) { - switch (ntohl(addr.s_addr) >> 24) { - case 0 ... 127: - return NM_IN_ADDR_CLASS_A; - case 128 ... 191: - return NM_IN_ADDR_CLASS_B; - case 192 ... 223: - return NM_IN_ADDR_CLASS_C; - default: - return NM_IN_ADDR_CLASS_INVALID; - } + return n_dhcp4_client_lease_query(lease, option, (guint8 **) datap, n_datap); } -static gboolean -lease_option_consume(void *out, size_t n_out, uint8_t **datap, size_t *n_datap) -{ - if (*n_datap < n_out) - return FALSE; +/*****************************************************************************/ - memcpy(out, *datap, n_out); - *datap += n_out; - *n_datap -= n_out; - return TRUE; -} +#define DHCP_MAX_FQDN_LENGTH 255 -static gboolean -lease_option_next_in_addr(struct in_addr *addrp, uint8_t **datap, size_t *n_datap) -{ - return lease_option_consume(addrp, sizeof(struct in_addr), datap, n_datap); -} +/*****************************************************************************/ static gboolean -lease_option_next_route(struct in_addr *destp, - uint8_t * plenp, - struct in_addr *gatewayp, - gboolean classless, - uint8_t ** datap, - size_t * n_datap) +lease_option_consume_route(const uint8_t **datap, + size_t * n_datap, + gboolean classless, + in_addr_t * destp, + uint8_t * plenp, + in_addr_t * gatewayp) { - struct in_addr dest = {}, gateway; - uint8_t * data = *datap; + in_addr_t dest; + in_addr_t gateway; + const uint8_t *data = *datap; size_t n_data = *n_datap; uint8_t plen; - uint8_t bytes; if (classless) { - if (!lease_option_consume(&plen, sizeof(plen), &data, &n_data)) + uint8_t bytes; + + if (!nm_dhcp_lease_data_consume(&data, &n_data, &plen, sizeof(plen))) return FALSE; if (plen > 32) @@ -148,30 +122,21 @@ lease_option_next_route(struct in_addr *destp, bytes = plen == 0 ? 0 : ((plen - 1) / 8) + 1; - if (!lease_option_consume(&dest, bytes, &data, &n_data)) + dest = 0; + if (!nm_dhcp_lease_data_consume(&data, &n_data, &dest, bytes)) return FALSE; } else { - if (!lease_option_next_in_addr(&dest, &data, &n_data)) + if (!nm_dhcp_lease_data_consume_in_addr(&data, &n_data, &dest)) return FALSE; - switch (in_addr_class(dest)) { - case NM_IN_ADDR_CLASS_A: - plen = 8; - break; - case NM_IN_ADDR_CLASS_B: - plen = 16; - break; - case NM_IN_ADDR_CLASS_C: - plen = 24; - break; - case NM_IN_ADDR_CLASS_INVALID: + plen = _nm_utils_ip4_get_default_prefix0(dest); + if (plen == 0) return FALSE; - } } - dest.s_addr = nm_utils_ip4_address_clear_host_address(dest.s_addr, plen); + dest = nm_utils_ip4_address_clear_host_address(dest, plen); - if (!lease_option_next_in_addr(&gateway, &data, &n_data)) + if (!nm_dhcp_lease_data_consume_in_addr(&data, &n_data, &gateway)) return FALSE; *destp = dest; @@ -182,170 +147,7 @@ lease_option_next_route(struct in_addr *destp, return TRUE; } -static gboolean -lease_option_print_label(GString *str, size_t n_label, uint8_t **datap, size_t *n_datap) -{ - for (size_t i = 0; i < n_label; ++i) { - uint8_t c; - - if (!lease_option_consume(&c, sizeof(c), datap, n_datap)) - return FALSE; - - switch (c) { - case 'a' ... 'z': - case 'A' ... 'Z': - case '0' ... '9': - case '-': - case '_': - g_string_append_c(str, c); - break; - case '.': - case '\\': - g_string_append_printf(str, "\\%c", c); - break; - default: - g_string_append_printf(str, "\\%3d", c); - } - } - - return TRUE; -} - -static gboolean -lease_option_print_domain_name(GString * str, - uint8_t * cache, - size_t * n_cachep, - uint8_t **datap, - size_t * n_datap) -{ - uint8_t * domain; - size_t n_domain, n_cache = *n_cachep; - uint8_t **domainp = datap; - size_t * n_domainp = n_datap; - gboolean first = TRUE; - uint8_t c; - - /* - * We are given two adjacent memory regions. The @cache contains alreday parsed - * domain names, and the @datap contains the remaining data to parse. - * - * A domain name is formed from a sequence of labels. Each label start with - * a length byte, where the two most significant bits are unset. A zero-length - * label indicates the end of the domain name. - * - * Alternatively, a label can be followed by an offset (indicated by the two - * most significant bits being set in the next byte that is read). The offset - * is an offset into the cache, where the next label of the domain name can - * be found. - * - * Note, that each time a jump to an offset is performed, the size of the - * cache shrinks, so this is guaranteed to terminate. - */ - if (cache + n_cache != *datap) - return FALSE; - - for (;;) { - if (!lease_option_consume(&c, sizeof(c), domainp, n_domainp)) - return FALSE; - - switch (c & 0xC0) { - case 0x00: /* label length */ - { - size_t n_label = c; - - if (n_label == 0) { - /* - * We reached the final label of the domain name. Adjust - * the cache to include the consumed data, and return. - */ - *n_cachep = *datap - cache; - return TRUE; - } - - if (!first) - g_string_append_c(str, '.'); - else - first = FALSE; - - if (!lease_option_print_label(str, n_label, domainp, n_domainp)) - return FALSE; - - break; - } - case 0xC0: /* back pointer */ - { - size_t offset = (c & 0x3F) << 16; - - /* - * The offset is given as two bytes (in big endian), where the - * two high bits are masked out. - */ - - if (!lease_option_consume(&c, sizeof(c), domainp, n_domainp)) - return FALSE; - - offset += c; - - if (offset >= n_cache) - return FALSE; - - domain = cache + offset; - n_domain = n_cache - offset; - n_cache = offset; - - domainp = &domain; - n_domainp = &n_domain; - - break; - } - default: - return FALSE; - } - } -} - -static gboolean -lease_get_in_addr(NDhcp4ClientLease *lease, guint8 option, struct in_addr *addrp) -{ - struct in_addr addr; - uint8_t * data; - size_t n_data; - int r; - - r = n_dhcp4_client_lease_query(lease, option, &data, &n_data); - if (r) - return FALSE; - - if (!lease_option_next_in_addr(&addr, &data, &n_data)) - return FALSE; - - if (n_data != 0) - return FALSE; - - *addrp = addr; - return TRUE; -} - -static gboolean -lease_get_u16(NDhcp4ClientLease *lease, uint8_t option, uint16_t *u16p) -{ - uint8_t *data; - size_t n_data; - uint16_t be16; - int r; - - r = n_dhcp4_client_lease_query(lease, option, &data, &n_data); - if (r) - return FALSE; - - if (n_data != sizeof(be16)) - return FALSE; - - memcpy(&be16, data, sizeof(be16)); - - *u16p = ntohs(be16); - return TRUE; -} +/*****************************************************************************/ static gboolean lease_parse_address(NDhcp4ClientLease *lease, @@ -353,15 +155,17 @@ lease_parse_address(NDhcp4ClientLease *lease, GHashTable * options, GError ** error) { - char addr_str[NM_UTILS_INET_ADDRSTRLEN]; struct in_addr a_address; - struct in_addr a_netmask; + in_addr_t a_netmask; struct in_addr a_next_server; guint32 a_plen; guint64 nettools_lifetime; guint32 a_lifetime; guint32 a_timestamp; guint64 a_expiry; + const guint8 * l_data; + gsize l_data_len; + int r; n_dhcp4_client_lease_get_yiaddr(lease, &a_address); if (a_address.s_addr == INADDR_ANY) { @@ -416,44 +220,40 @@ lease_parse_address(NDhcp4ClientLease *lease, / NM_UTILS_NSEC_PER_SEC); } - if (!lease_get_in_addr(lease, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, &a_netmask)) { + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, &l_data, &l_data_len); + if (r != 0 || !nm_dhcp_lease_data_parse_in_addr(l_data, l_data_len, &a_netmask)) { nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "could not get netmask from lease"); return FALSE; } - _nm_utils_inet4_ntop(a_address.s_addr, addr_str); - a_plen = nm_utils_ip4_netmask_to_prefix(a_netmask.s_addr); + a_plen = nm_utils_ip4_netmask_to_prefix(a_netmask); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, - addr_str); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_SUBNET_MASK, - _nm_utils_inet4_ntop(a_netmask.s_addr, addr_str)); + nm_dhcp_option_add_option_in_addr(options, + AF_INET, + NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, + a_address.s_addr); + nm_dhcp_option_add_option_in_addr(options, + AF_INET, + NM_DHCP_OPTION_DHCP4_SUBNET_MASK, + a_netmask); nm_dhcp_option_add_option_u64(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME, (guint64) a_lifetime); if (a_expiry != G_MAXUINT64) { - nm_dhcp_option_add_option_u64(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_NM_EXPIRY, - a_expiry); + nm_dhcp_option_add_option_u64(options, AF_INET, NM_DHCP_OPTION_DHCP4_NM_EXPIRY, a_expiry); } n_dhcp4_client_lease_get_siaddr(lease, &a_next_server); if (a_next_server.s_addr != INADDR_ANY) { - _nm_utils_inet4_ntop(a_next_server.s_addr, addr_str); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, - addr_str); + nm_dhcp_option_add_option_in_addr(options, + AF_INET, + NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, + a_next_server.s_addr); } nm_ip4_config_add_address(ip4_config, @@ -474,78 +274,52 @@ static void lease_parse_address_list(NDhcp4ClientLease * lease, NMIP4Config * ip4_config, NMDhcpOptionDhcp4Options option, - GHashTable * options) + GHashTable * options, + NMStrBuf * sbuf) { - nm_auto_free_gstring GString *str = NULL; - char addr_str[NM_UTILS_INET_ADDRSTRLEN]; - struct in_addr addr; - uint8_t * data; - size_t n_data; - int r; - - r = n_dhcp4_client_lease_query(lease, option, &data, &n_data); - if (r) + const guint8 *l_data; + gsize l_data_len; + int r; + + r = _client_lease_query(lease, option, &l_data, &l_data_len); + if (r != 0) + return; + + if (l_data_len == 0 || l_data_len % 4 != 0) return; - nm_gstring_prepare(&str); + nm_str_buf_reset(sbuf); + + for (; l_data_len > 0; l_data_len -= 4, l_data += 4) { + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + const in_addr_t addr = unaligned_read_ne32(l_data); - while (lease_option_next_in_addr(&addr, &data, &n_data)) { - _nm_utils_inet4_ntop(addr.s_addr, addr_str); - g_string_append(nm_gstring_add_space_delimiter(str), addr_str); + nm_str_buf_append_required_delimiter(sbuf, ' '); + nm_str_buf_append(sbuf, _nm_utils_inet4_ntop(addr, addr_str)); switch (option) { case NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER: - if (addr.s_addr == 0 || nm_ip4_addr_is_localhost(addr.s_addr)) { + if (addr == 0 || nm_ip4_addr_is_localhost(addr)) { /* Skip localhost addresses, like also networkd does. * See https://github.com/systemd/systemd/issues/4524. */ continue; } - nm_ip4_config_add_nameserver(ip4_config, addr.s_addr); + nm_ip4_config_add_nameserver(ip4_config, addr); break; case NM_DHCP_OPTION_DHCP4_NIS_SERVERS: - nm_ip4_config_add_nis_server(ip4_config, addr.s_addr); + nm_ip4_config_add_nis_server(ip4_config, addr); break; case NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER: - nm_ip4_config_add_wins(ip4_config, addr.s_addr); + nm_ip4_config_add_wins(ip4_config, addr); + break; + case NM_DHCP_OPTION_DHCP4_NTP_SERVER: break; default: nm_assert_not_reached(); } } - nm_dhcp_option_add_option(options, _nm_dhcp_option_dhcp4_options, option, str->str); -} - -static void -lease_parse_server_id(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) -{ - struct in_addr addr; - char addr_str[NM_UTILS_INET_ADDRSTRLEN]; - - if (n_dhcp4_client_lease_get_server_identifier(lease, &addr)) - return; - - _nm_utils_inet4_ntop(addr.s_addr, addr_str); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_SERVER_ID, - addr_str); -} - -static void -lease_parse_broadcast(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) -{ - struct in_addr addr; - char addr_str[NM_UTILS_INET_ADDRSTRLEN]; - - if (!lease_get_in_addr(lease, NM_DHCP_OPTION_DHCP4_BROADCAST, &addr)) - return; - - _nm_utils_inet4_ntop(addr.s_addr, addr_str); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_BROADCAST, - addr_str); + nm_dhcp_option_add_option(options, AF_INET, option, nm_str_buf_get_str(sbuf)); } static void @@ -553,39 +327,37 @@ lease_parse_routes(NDhcp4ClientLease *lease, NMIP4Config * ip4_config, GHashTable * options, guint32 route_table, - guint32 route_metric) + guint32 route_metric, + NMStrBuf * sbuf) { - nm_auto_free_gstring GString *str = NULL; - char dest_str[NM_UTILS_INET_ADDRSTRLEN]; - char gateway_str[NM_UTILS_INET_ADDRSTRLEN]; - const char * s; - struct in_addr dest, gateway; - uint8_t plen; - guint32 m; - gboolean has_router_from_classless = FALSE, has_classless = FALSE; - guint32 default_route_metric = route_metric; - uint8_t * data; - size_t n_data; - int r; - - r = n_dhcp4_client_lease_query(lease, - NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, - &data, - &n_data); - if (!r) { - nm_gstring_prepare(&str); + char dest_str[NM_UTILS_INET_ADDRSTRLEN]; + char gateway_str[NM_UTILS_INET_ADDRSTRLEN]; + in_addr_t dest; + in_addr_t gateway; + uint8_t plen; + guint32 m; + gboolean has_router_from_classless = FALSE; + gboolean has_classless = FALSE; + guint32 default_route_metric = route_metric; + const guint8 *l_data; + gsize l_data_len; + int r; + + r = _client_lease_query(lease, + NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, + &l_data, + &l_data_len); + if (r == 0) { + nm_str_buf_reset(sbuf); has_classless = TRUE; - while (lease_option_next_route(&dest, &plen, &gateway, TRUE, &data, &n_data)) { - _nm_utils_inet4_ntop(dest.s_addr, dest_str); - _nm_utils_inet4_ntop(gateway.s_addr, gateway_str); + while (lease_option_consume_route(&l_data, &l_data_len, TRUE, &dest, &plen, &gateway)) { + _nm_utils_inet4_ntop(dest, dest_str); + _nm_utils_inet4_ntop(gateway, gateway_str); - g_string_append_printf(nm_gstring_add_space_delimiter(str), - "%s/%d %s", - dest_str, - (int) plen, - gateway_str); + nm_str_buf_append_required_delimiter(sbuf, ' '); + nm_str_buf_append_printf(sbuf, "%s/%d %s", dest_str, (int) plen, gateway_str); if (plen == 0) { /* if there are multiple default routes, we add them with differing @@ -602,34 +374,32 @@ lease_parse_routes(NDhcp4ClientLease *lease, nm_ip4_config_add_route( ip4_config, &((const NMPlatformIP4Route){ - .network = dest.s_addr, + .network = dest, .plen = plen, - .gateway = gateway.s_addr, + .gateway = gateway, .rt_source = NM_IP_CONFIG_SOURCE_DHCP, .metric = m, .table_coerced = nm_platform_route_table_coerce(route_table), }), NULL); } + nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, - str->str); + nm_str_buf_get_str(sbuf)); } - r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, &data, &n_data); - if (!r) { - nm_gstring_prepare(&str); + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, &l_data, &l_data_len); + if (r == 0) { + nm_str_buf_reset(sbuf); - while (lease_option_next_route(&dest, &plen, &gateway, FALSE, &data, &n_data)) { - _nm_utils_inet4_ntop(dest.s_addr, dest_str); - _nm_utils_inet4_ntop(gateway.s_addr, gateway_str); + while (lease_option_consume_route(&l_data, &l_data_len, FALSE, &dest, &plen, &gateway)) { + _nm_utils_inet4_ntop(dest, dest_str); + _nm_utils_inet4_ntop(gateway, gateway_str); - g_string_append_printf(nm_gstring_add_space_delimiter(str), - "%s/%d %s", - dest_str, - (int) plen, - gateway_str); + nm_str_buf_append_required_delimiter(sbuf, ' '); + nm_str_buf_append_printf(sbuf, "%s/%d %s", dest_str, (int) plen, gateway_str); if (has_classless) { /* RFC 3443: if the DHCP server returns both a Classless Static Routes @@ -649,30 +419,31 @@ lease_parse_routes(NDhcp4ClientLease *lease, nm_ip4_config_add_route( ip4_config, &((const NMPlatformIP4Route){ - .network = dest.s_addr, + .network = dest, .plen = plen, - .gateway = gateway.s_addr, + .gateway = gateway, .rt_source = NM_IP_CONFIG_SOURCE_DHCP, .metric = route_metric, .table_coerced = nm_platform_route_table_coerce(route_table), }), NULL); } + nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, - str->str); + nm_str_buf_get_str(sbuf)); } - r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROUTER, &data, &n_data); - if (!r) { - nm_gstring_prepare(&str); + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROUTER, &l_data, &l_data_len); + if (r == 0) { + nm_str_buf_reset(sbuf); - while (lease_option_next_in_addr(&gateway, &data, &n_data)) { - s = _nm_utils_inet4_ntop(gateway.s_addr, gateway_str); - g_string_append(nm_gstring_add_space_delimiter(str), s); + while (nm_dhcp_lease_data_consume_in_addr(&l_data, &l_data_len, &gateway)) { + nm_str_buf_append_required_delimiter(sbuf, ' '); + nm_str_buf_append(sbuf, _nm_utils_inet4_ntop(gateway, gateway_str)); - if (gateway.s_addr == 0) { + if (gateway == 0) { /* silently skip 0.0.0.0 */ continue; } @@ -697,262 +468,45 @@ lease_parse_routes(NDhcp4ClientLease *lease, ip4_config, &((const NMPlatformIP4Route){ .rt_source = NM_IP_CONFIG_SOURCE_DHCP, - .gateway = gateway.s_addr, + .gateway = gateway, .table_coerced = nm_platform_route_table_coerce(route_table), .metric = m, }), NULL); } + nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_ROUTER, - str->str); + nm_str_buf_get_str(sbuf)); } } static void -lease_parse_mtu(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) -{ - uint16_t mtu; - - if (!lease_get_u16(lease, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, &mtu)) - return; - - if (mtu < 68) - return; - - nm_dhcp_option_add_option_u64(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, - mtu); - nm_ip4_config_set_mtu(ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP); -} - -static void -lease_parse_metered(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) -{ - gboolean metered = FALSE; - uint8_t *data; - size_t n_data; - int r; - - r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, &data, &n_data); - if (r) - metered = FALSE; - else - metered = !!memmem(data, n_data, "ANDROID_METERED", NM_STRLEN("ANDROID_METERED")); - - /* TODO: expose the vendor specific option when present */ - nm_ip4_config_set_metered(ip4_config, metered); -} - -static void -lease_parse_ntps(NDhcp4ClientLease *lease, GHashTable *options) -{ - nm_auto_free_gstring GString *str = NULL; - char addr_str[NM_UTILS_INET_ADDRSTRLEN]; - struct in_addr addr; - uint8_t * data; - size_t n_data; - int r; - - r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_NTP_SERVER, &data, &n_data); - if (r) - return; - - nm_gstring_prepare(&str); - - while (lease_option_next_in_addr(&addr, &data, &n_data)) { - _nm_utils_inet4_ntop(addr.s_addr, addr_str); - g_string_append(nm_gstring_add_space_delimiter(str), addr_str); - } - - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_NTP_SERVER, - str->str); -} - -static void -lease_parse_hostname(NDhcp4ClientLease *lease, GHashTable *options) -{ - nm_auto_free_gstring GString *str = NULL; - uint8_t * data; - size_t n_data; - int r; - - r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_HOST_NAME, &data, &n_data); - if (r) - return; - - str = g_string_new_len((char *) data, n_data); - - if (nm_utils_is_localhost(str->str)) - return; - - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_HOST_NAME, - str->str); -} - -static void -lease_parse_domainname(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) -{ - nm_auto_free_gstring GString *str = NULL; - gs_strfreev char ** domains = NULL; - uint8_t * data; - size_t n_data; - int r; - - r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, &data, &n_data); - if (r) - return; - - str = g_string_new_len((char *) data, n_data); - - /* Multiple domains sometimes stuffed into option 15 "Domain Name". */ - domains = g_strsplit(str->str, " ", 0); - nm_gstring_prepare(&str); - - for (char **d = domains; *d; d++) { - if (nm_utils_is_localhost(*d)) - return; - - g_string_append(nm_gstring_add_space_delimiter(str), *d); - nm_ip4_config_add_domain(ip4_config, *d); - } - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, - str->str); -} - -char ** -nm_dhcp_parse_search_list(guint8 *data, size_t n_data) -{ - GPtrArray *array = NULL; - guint8 * cache = data; - size_t n_cache = 0; - - for (;;) { - nm_auto_free_gstring GString *domain = NULL; - - nm_gstring_prepare(&domain); - - if (!lease_option_print_domain_name(domain, cache, &n_cache, &data, &n_data)) - break; - - if (!array) - array = g_ptr_array_new(); - - g_ptr_array_add(array, g_string_free(domain, FALSE)); - domain = NULL; - } - - if (array) { - g_ptr_array_add(array, NULL); - return (char **) g_ptr_array_free(array, FALSE); - } else - return NULL; -} - -static void lease_parse_search_domains(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) { - nm_auto_free_gstring GString *str = NULL; - uint8_t * data; - size_t n_data; - gs_strfreev char ** domains = NULL; - guint i; - int r; - - r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, &data, &n_data); - if (r) - return; - - domains = nm_dhcp_parse_search_list(data, n_data); - nm_gstring_prepare(&str); - - for (i = 0; domains && domains[i]; i++) { - g_string_append(nm_gstring_add_space_delimiter(str), domains[i]); - nm_ip4_config_add_search(ip4_config, domains[i]); - } - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, - str->str); -} - -static void -lease_parse_root_path(NDhcp4ClientLease *lease, GHashTable *options) -{ - nm_auto_free_gstring GString *str = NULL; - uint8_t * data; - size_t n_data; - int r; - - r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROOT_PATH, &data, &n_data); - if (r) + gs_strfreev char **domains = NULL; + const guint8 * l_data; + gsize l_data_len; + guint i; + int r; + + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, &l_data, &l_data_len); + if (r != 0) return; - str = g_string_new_len((char *) data, n_data); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_ROOT_PATH, - str->str); -} - -static void -lease_parse_wpad(NDhcp4ClientLease *lease, GHashTable *options) -{ - gs_free char *wpad = NULL; - uint8_t * data; - size_t n_data; - int r; + domains = nm_dhcp_lease_data_parse_search_list(l_data, l_data_len); - r = n_dhcp4_client_lease_query(lease, - NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, - &data, - &n_data); - if (r) + if (!domains || !domains[0]) return; - nm_utils_buf_utf8safe_escape((char *) data, n_data, 0, &wpad); - if (wpad == NULL) - wpad = g_strndup((char *) data, n_data); - - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, - wpad); -} - -static void -lease_parse_nis_domain(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options) -{ - gs_free char *str_free = NULL; - const char * str; - uint8_t * data; - size_t n_data; - guint i; - int r; - - r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, &data, &n_data); - if (r) - return; - - for (i = 0; i < n_data; i++) { - if (!nm_is_ascii((char) data[i])) - return; - } + for (i = 0; domains[i]; i++) + nm_ip4_config_add_search(ip4_config, domains[i]); - str = nm_strndup_a(300, (const char *) data, n_data, &str_free); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, - str); - nm_ip4_config_set_nis_domain(ip4_config, str); + nm_dhcp_option_take_option(options, + AF_INET, + NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, + g_strjoinv(" ", domains)); } static void @@ -962,8 +516,8 @@ lease_parse_private_options(NDhcp4ClientLease *lease, GHashTable *options) for (i = NM_DHCP_OPTION_DHCP4_PRIVATE_224; i <= NM_DHCP_OPTION_DHCP4_PRIVATE_254; i++) { gs_free char *option_string = NULL; - guint8 * data; - gsize n_data; + const guint8 *l_data; + gsize l_data_len; int r; /* We manage private options 249 (private classless static route) and 252 (wpad) in a special @@ -973,15 +527,12 @@ lease_parse_private_options(NDhcp4ClientLease *lease, GHashTable *options) NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY)) continue; - r = n_dhcp4_client_lease_query(lease, i, &data, &n_data); + r = _client_lease_query(lease, i, &l_data, &l_data_len); if (r) continue; - option_string = nm_utils_bin2hexstr_full(data, n_data, ':', FALSE, NULL); - nm_dhcp_option_take_option(options, - _nm_dhcp_option_dhcp4_options, - i, - g_steal_pointer(&option_string)); + option_string = nm_utils_bin2hexstr_full(l_data, l_data_len, ':', FALSE, NULL); + nm_dhcp_option_take_option(options, AF_INET, i, g_steal_pointer(&option_string)); } } @@ -995,8 +546,17 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, GHashTable ** out_options, GError ** error) { + nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(0, FALSE); gs_unref_object NMIP4Config *ip4_config = NULL; gs_unref_hashtable GHashTable *options = NULL; + const guint8 * l_data; + gsize l_data_len; + const char * v_str; + guint16 v_u16; + gboolean v_bool; + in_addr_t v_inaddr; + struct in_addr v_inaddr_s; + int r; g_return_val_if_fail(lease != NULL, NULL); @@ -1006,22 +566,145 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, if (!lease_parse_address(lease, ip4_config, options, error)) return NULL; - lease_parse_server_id(lease, ip4_config, options); - lease_parse_broadcast(lease, ip4_config, options); - lease_parse_routes(lease, ip4_config, options, route_table, route_metric); - lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER, options); - lease_parse_domainname(lease, ip4_config, options); + r = n_dhcp4_client_lease_get_server_identifier(lease, &v_inaddr_s); + if (r == 0) { + nm_dhcp_option_add_option_in_addr(options, + AF_INET, + NM_DHCP_OPTION_DHCP4_SERVER_ID, + v_inaddr_s.s_addr); + } + + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_BROADCAST, &l_data, &l_data_len); + if (r == 0 && nm_dhcp_lease_data_parse_in_addr(l_data, l_data_len, &v_inaddr)) { + nm_dhcp_option_add_option_in_addr(options, + AF_INET, + NM_DHCP_OPTION_DHCP4_BROADCAST, + v_inaddr); + } + + lease_parse_routes(lease, ip4_config, options, route_table, route_metric, &sbuf); + + lease_parse_address_list(lease, + ip4_config, + NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER, + options, + &sbuf); + + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, &l_data, &l_data_len); + if (r == 0 && nm_dhcp_lease_data_parse_cstr(l_data, l_data_len, &l_data_len)) { + gs_free const char **domains = NULL; + + nm_str_buf_reset(&sbuf); + nm_str_buf_append_len0(&sbuf, (const char *) l_data, l_data_len); + + /* Multiple domains sometimes stuffed into option 15 "Domain Name". */ + domains = nm_utils_strsplit_set(nm_str_buf_get_str(&sbuf), " "); + + nm_str_buf_reset(&sbuf); + if (domains) { + gsize i; + + for (i = 0; domains[i]; i++) { + gs_free char *s = NULL; + + s = nm_dhcp_lease_data_parse_domain_validate(domains[i]); + if (!s) + continue; + + nm_str_buf_append_required_delimiter(&sbuf, ' '); + nm_str_buf_append(&sbuf, s); + nm_ip4_config_add_domain(ip4_config, s); + } + } + + if (sbuf.len > 0) { + nm_dhcp_option_add_option(options, + AF_INET, + NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, + nm_str_buf_get_str(&sbuf)); + } + } + lease_parse_search_domains(lease, ip4_config, options); - lease_parse_mtu(lease, ip4_config, options); - lease_parse_metered(lease, ip4_config, options); - - lease_parse_hostname(lease, options); - lease_parse_ntps(lease, options); - lease_parse_root_path(lease, options); - lease_parse_wpad(lease, options); - lease_parse_nis_domain(lease, ip4_config, options); - lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NIS_SERVERS, options); - lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER, options); + + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, &l_data, &l_data_len); + if (r == 0 && nm_dhcp_lease_data_parse_mtu(l_data, l_data_len, &v_u16)) { + nm_dhcp_option_add_option_u64(options, AF_INET, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, v_u16); + nm_ip4_config_set_mtu(ip4_config, v_u16, NM_IP_CONFIG_SOURCE_DHCP); + } + + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, &l_data, &l_data_len); + v_bool = + (r == 0) && memmem(l_data, l_data_len, "ANDROID_METERED", NM_STRLEN("ANDROID_METERED")); + nm_ip4_config_set_metered(ip4_config, v_bool); + + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_HOST_NAME, &l_data, &l_data_len); + if (r == 0) { + gs_free char *s = NULL; + + if (nm_dhcp_lease_data_parse_domain(l_data, l_data_len, &s)) { + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_HOST_NAME, s); + } + } + + lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NTP_SERVER, options, &sbuf); + + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROOT_PATH, &l_data, &l_data_len); + if (r == 0 && nm_dhcp_lease_data_parse_cstr(l_data, l_data_len, &l_data_len)) { + /* https://tools.ietf.org/html/rfc2132#section-3.19 + * + * The path is formatted as a character string consisting of + * characters from the NVT ASCII character set. + * + * We still accept any character set and backslash escape it! */ + if (l_data_len == 0) { + /* "Its minimum length is 1." */ + } else { + nm_dhcp_option_add_option_utf8safe_escape(options, + AF_INET, + NM_DHCP_OPTION_DHCP4_ROOT_PATH, + l_data, + l_data_len); + } + } + + r = _client_lease_query(lease, + NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, + &l_data, + &l_data_len); + if (r == 0 && nm_dhcp_lease_data_parse_cstr(l_data, l_data_len, &l_data_len)) { + /* https://tools.ietf.org/html/draft-ietf-wrec-wpad-01#section-4.4.1 + * + * We reject NUL characters inside the string (except trailing NULs). + * Otherwise, we allow any encoding and backslash-escape the result to + * UTF-8. */ + nm_dhcp_option_add_option_utf8safe_escape(options, + AF_INET, + NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, + l_data, + l_data_len); + } + + r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, &l_data, &l_data_len); + if (r == 0 && nm_dhcp_lease_data_parse_cstr(l_data, l_data_len, &l_data_len)) { + gs_free char *to_free = NULL; + + /* https://tools.ietf.org/html/rfc2132#section-8.1 */ + + v_str = nm_utils_buf_utf8safe_escape((char *) l_data, l_data_len, 0, &to_free); + + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, v_str); + nm_ip4_config_set_nis_domain(ip4_config, v_str); + } + + lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NIS_SERVERS, options, &sbuf); + + lease_parse_address_list(lease, + ip4_config, + NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER, + options, + &sbuf); + lease_parse_private_options(lease, options); NM_SET_OUT(out_options, g_steal_pointer(&options)); @@ -1033,25 +716,24 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, static void lease_save(NMDhcpNettools *self, NDhcp4ClientLease *lease, const char *lease_file) { - struct in_addr a_address; - nm_auto_free_gstring GString *new_contents = NULL; - char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + struct in_addr a_address; + nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE); + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; gs_free_error GError *error = NULL; nm_assert(lease); nm_assert(lease_file); - new_contents = g_string_new("# This is private data. Do not parse.\n"); - n_dhcp4_client_lease_get_yiaddr(lease, &a_address); if (a_address.s_addr == INADDR_ANY) return; - g_string_append_printf(new_contents, - "ADDRESS=%s\n", - _nm_utils_inet4_ntop(a_address.s_addr, sbuf)); + nm_str_buf_append(&sbuf, "# This is private data. Do not parse.\n"); + nm_str_buf_append_printf(&sbuf, + "ADDRESS=%s\n", + _nm_utils_inet4_ntop(a_address.s_addr, addr_str)); - if (!g_file_set_contents(lease_file, new_contents->str, -1, &error)) + if (!g_file_set_contents(lease_file, nm_str_buf_get_str_unsafe(&sbuf), sbuf.len, &error)) _LOGW("error saving lease to %s: %s", lease_file, error->message); } @@ -1081,7 +763,7 @@ bound4_handle(NMDhcpNettools *self, NDhcp4ClientLease *lease, gboolean extended) return; } - nm_dhcp_option_add_requests_to_options(options, _nm_dhcp_option_dhcp4_options); + nm_dhcp_option_add_requests_to_options(options, AF_INET); lease_save(self, lease, priv->lease_file); nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), @@ -1159,9 +841,9 @@ dhcp4_event_handle(NMDhcpNettools *self, NDhcp4ClientEvent *event) } static gboolean -dhcp4_event_cb(int fd, GIOCondition condition, gpointer data) +dhcp4_event_cb(int fd, GIOCondition condition, gpointer user_data) { - NMDhcpNettools * self = data; + NMDhcpNettools * self = user_data; NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(self); NDhcp4ClientEvent * event; int r; @@ -1407,7 +1089,7 @@ ip4_start(NMDhcpClient *client, } /* Add requested options */ - for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) { + for (i = 0; i < (int) G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options); i++) { if (_nm_dhcp_option_dhcp4_options[i].include) { nm_assert(_nm_dhcp_option_dhcp4_options[i].option_num <= 255); n_dhcp4_client_probe_config_request_option(config, diff --git a/src/core/dhcp/nm-dhcp-options.c b/src/core/dhcp/nm-dhcp-options.c index 09dd6f363f..3537cd147e 100644 --- a/src/core/dhcp/nm-dhcp-options.c +++ b/src/core/dhcp/nm-dhcp-options.c @@ -7,11 +7,13 @@ #include "nm-dhcp-options.h" -#define REQPREFIX "requested_" +#include "nm-glib-aux/nm-str-buf.h" -#define REQ(_num, _name, _include) \ - { \ - .name = REQPREFIX ""_name, .option_num = _num, .include = _include, \ +/*****************************************************************************/ + +#define REQ(_num, _name, _include) \ + { \ + .name = NM_DHCP_OPTION_REQPREFIX ""_name, .option_num = _num, .include = _include, \ } const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = { @@ -167,8 +169,24 @@ const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = { REQ(NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, "ip_address", FALSE), REQ(NM_DHCP_OPTION_DHCP4_NM_EXPIRY, "expiry", FALSE), REQ(NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, "next_server", FALSE), +}; - {0}}; +static const NMDhcpOption *const _sorted_options_4[G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options)] = { +#define A(idx) (&_nm_dhcp_option_dhcp4_options[(idx)]) + A(0), A(1), A(8), A(18), A(19), A(2), A(20), A(21), A(22), A(23), A(24), A(3), + A(25), A(26), A(4), A(27), A(17), A(28), A(29), A(30), A(31), A(32), A(33), A(34), + A(35), A(5), A(36), A(6), A(37), A(38), A(39), A(40), A(9), A(41), A(42), A(43), + A(44), A(45), A(46), A(10), A(11), A(12), A(47), A(48), A(49), A(50), A(51), A(52), + A(13), A(53), A(54), A(55), A(57), A(58), A(59), A(60), A(61), A(62), A(63), A(64), + A(65), A(66), A(67), A(68), A(69), A(70), A(71), A(72), A(73), A(74), A(75), A(76), + A(77), A(78), A(79), A(80), A(81), A(82), A(83), A(84), A(85), A(86), A(87), A(56), + A(88), A(89), A(90), A(91), A(92), A(93), A(14), A(7), A(94), A(95), A(96), A(97), + A(98), A(99), A(100), A(101), A(102), A(103), A(104), A(105), A(106), A(107), A(108), A(109), + A(110), A(111), A(112), A(113), A(114), A(115), A(116), A(117), A(118), A(119), A(120), A(121), + A(122), A(123), A(124), A(125), A(126), A(127), A(128), A(129), A(130), A(131), A(132), A(133), + A(134), A(15), A(135), A(136), A(16), A(137), A(138), A(139), A(140), A(141), +#undef A +}; const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = { REQ(NM_DHCP_OPTION_DHCP6_CLIENTID, "dhcp6_client_id", FALSE), @@ -194,73 +212,235 @@ const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = { REQ(NM_DHCP_OPTION_DHCP6_NM_RENEW, "renew", FALSE), REQ(NM_DHCP_OPTION_DHCP6_NM_REBIND, "rebind", FALSE), REQ(NM_DHCP_OPTION_DHCP6_NM_IAID, "iaid", FALSE), +}; + +#undef REQ + +static const NMDhcpOption *const _sorted_options_6[G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options)] = { +#define A(idx) (&_nm_dhcp_option_dhcp6_options[(idx)]) + A(0), + A(1), + A(2), + A(3), + A(4), + A(5), + A(6), + A(7), + A(8), + A(9), + A(10), + A(11), + A(12), + A(13), + A(14), + A(15), +#undef A +}; + +/*****************************************************************************/ + +static int +_sorted_options_generate_sort(gconstpointer pa, gconstpointer pb, gpointer user_data) +{ + const NMDhcpOption *const *a = pa; + const NMDhcpOption *const *b = pb; + + NM_CMP_DIRECT((*a)->option_num, (*b)->option_num); + return nm_assert_unreachable_val(0); +} + +static char * +_sorted_options_generate(const NMDhcpOption *base, const NMDhcpOption *const *sorted, guint n) +{ + gs_free const NMDhcpOption **sort2 = NULL; + NMStrBuf sbuf = NM_STR_BUF_INIT(0, FALSE); + guint i; + + sort2 = nm_memdup(sorted, n * sizeof(sorted[0])); + + g_qsort_with_data(sort2, n, sizeof(sort2[0]), _sorted_options_generate_sort, NULL); + + for (i = 0; i < n; i++) { + if (i > 0) + nm_str_buf_append(&sbuf, ", "); + nm_str_buf_append_printf(&sbuf, "A(%d)", (int) (sort2[i] - base)); + } + + return nm_str_buf_finalize(&sbuf, NULL); +} + +_nm_unused static void +_ASSERT_sorted(int IS_IPv4, const NMDhcpOption *const *const sorted, int n) + +{ + const NMDhcpOption *const options = + IS_IPv4 ? _nm_dhcp_option_dhcp4_options : _nm_dhcp_option_dhcp6_options; + int i; + int j; + gs_free char *sorted_msg = NULL; + + for (i = 0; i < n; i++) { + const NMDhcpOption *opt = sorted[i]; + + g_assert(opt); + g_assert(opt >= options); + g_assert(opt < &options[n]); + + for (j = 0; j < i; j++) { + const NMDhcpOption *opt2 = sorted[j]; + + if (opt == opt2) { + g_error("%s:%d: the _sorted_options_%c at [%d] (opt=%u, %s) is duplicated at " + "[%d] (SORT: %s)", + __FILE__, + __LINE__, + IS_IPv4 ? '4' : '6', + i, + opt->option_num, + opt->name, + j, + (sorted_msg = _sorted_options_generate(options, sorted, n))); + } + } - {0}}; + if (i > 0) { + const NMDhcpOption *opt2 = sorted[i - 1]; -const char * -nm_dhcp_option_request_string(const NMDhcpOption *requests, guint option) + if (opt2->option_num >= opt->option_num) { + g_error("%s:%d: the _sorted_options_%c at [%d] (opt=%u, %s) should come before " + "[%d] (opt=%u, %s) (SORT: %s)", + __FILE__, + __LINE__, + IS_IPv4 ? '4' : '6', + i, + opt->option_num, + opt->name, + i - 1, + opt2->option_num, + opt2->name, + (sorted_msg = _sorted_options_generate(options, sorted, n))); + } + } + } +} + +/*****************************************************************************/ + +const NMDhcpOption * +nm_dhcp_option_find(int addr_family, guint option) { - guint i = 0; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMDhcpOption *const *const sorted = IS_IPv4 ? _sorted_options_4 : _sorted_options_6; + const int n = IS_IPv4 ? G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options) + : G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options); + int imin = 0; + int imax = n - 1; + int imid = (n - 1) / 2; + +#if NM_MORE_ASSERTS > 10 + nm_assert(n < G_MAXINT / 2); + if (IS_IPv4 && !NM_MORE_ASSERT_ONCE(10)) { + /* already checked */ + } else if (!IS_IPv4 && !NM_MORE_ASSERT_ONCE(10)) { + /* already checked */ + } else + _ASSERT_sorted(IS_IPv4, sorted, n); +#endif + + for (;;) { + const guint o = sorted[imid]->option_num; - while (requests[i].name) { - if (requests[i].option_num == option) - return requests[i].name + NM_STRLEN(REQPREFIX); - i++; + if (G_UNLIKELY(o == option)) + return sorted[imid]; + + if (o < option) + imin = imid + 1; + else + imax = imid - 1; + + if (G_UNLIKELY(imin > imax)) + break; + + imid = (imin + imax) / 2; } /* Option should always be found */ - nm_assert_not_reached(); - return NULL; + return nm_assert_unreachable_val(NULL); } +/*****************************************************************************/ + void -nm_dhcp_option_take_option(GHashTable * options, - const NMDhcpOption *requests, - guint option, - char * value) +nm_dhcp_option_take_option(GHashTable *options, int addr_family, guint option, char *value) { - nm_assert(options); - nm_assert(requests); + nm_assert_addr_family(addr_family); nm_assert(value); nm_assert(g_utf8_validate(value, -1, NULL)); - g_hash_table_insert(options, (gpointer) nm_dhcp_option_request_string(requests, option), value); + if (!options) { + nm_assert_not_reached(); + g_free(value); + return; + } + + g_hash_table_insert(options, + (gpointer) nm_dhcp_option_request_string(addr_family, option), + value); } void -nm_dhcp_option_add_option(GHashTable * options, - const NMDhcpOption *requests, - guint option, - const char * value) +nm_dhcp_option_add_option(GHashTable *options, int addr_family, guint option, const char *value) { - if (options) - nm_dhcp_option_take_option(options, requests, option, g_strdup(value)); + nm_dhcp_option_take_option(options, addr_family, option, g_strdup(value)); } void -nm_dhcp_option_add_option_u64(GHashTable * options, - const NMDhcpOption *requests, - guint option, - guint64 value) +nm_dhcp_option_add_option_utf8safe_escape(GHashTable * options, + int addr_family, + guint option, + const guint8 *data, + gsize n_data) { - if (options) - nm_dhcp_option_take_option(options, - requests, - option, - g_strdup_printf("%" G_GUINT64_FORMAT, value)); + gs_free char *to_free = NULL; + const char * escaped; + + escaped = nm_utils_buf_utf8safe_escape((char *) data, n_data, 0, &to_free); + nm_dhcp_option_add_option(options, addr_family, option, escaped ?: ""); } void -nm_dhcp_option_add_requests_to_options(GHashTable *options, const NMDhcpOption *requests) +nm_dhcp_option_add_option_u64(GHashTable *options, int addr_family, guint option, guint64 value) { - guint i; + nm_dhcp_option_take_option(options, + addr_family, + option, + g_strdup_printf("%" G_GUINT64_FORMAT, value)); +} - if (!options) - return; +void +nm_dhcp_option_add_option_in_addr(GHashTable *options, + int addr_family, + guint option, + in_addr_t value) +{ + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + nm_dhcp_option_add_option(options, addr_family, option, _nm_utils_inet4_ntop(value, sbuf)); +} + +void +nm_dhcp_option_add_requests_to_options(GHashTable *options, int addr_family) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMDhcpOption *const all_options = + IS_IPv4 ? _nm_dhcp_option_dhcp4_options : _nm_dhcp_option_dhcp6_options; + int n_options = IS_IPv4 ? G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options) + : G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options); + int i; - for (i = 0; requests[i].name; i++) { - if (requests[i].include) - g_hash_table_insert(options, (gpointer) requests[i].name, g_strdup("1")); + for (i = 0; i < n_options; i++) { + if (all_options[i].include) + g_hash_table_insert(options, (gpointer) all_options[i].name, g_strdup("1")); } } diff --git a/src/core/dhcp/nm-dhcp-options.h b/src/core/dhcp/nm-dhcp-options.h index c2a403a4b5..585f118795 100644 --- a/src/core/dhcp/nm-dhcp-options.h +++ b/src/core/dhcp/nm-dhcp-options.h @@ -177,29 +177,50 @@ typedef enum { } NMDhcpOptionDhcp6Options; +#define NM_DHCP_OPTION_REQPREFIX "requested_" + typedef struct { const char *name; uint16_t option_num; bool include; } NMDhcpOption; -extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[]; -extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[]; +extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[142]; +extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[16]; + +static inline const char * +nm_dhcp_option_get_name(const NMDhcpOption *option) +{ + nm_assert(option); + nm_assert(option->name); + nm_assert(NM_STR_HAS_PREFIX(option->name, NM_DHCP_OPTION_REQPREFIX)); + + return &option->name[NM_STRLEN(NM_DHCP_OPTION_REQPREFIX)]; +} + +const NMDhcpOption *nm_dhcp_option_find(int addr_family, guint option); + +static inline const char * +nm_dhcp_option_request_string(int addr_family, guint option) +{ + return nm_dhcp_option_get_name(nm_dhcp_option_find(addr_family, option)); +} -const char *nm_dhcp_option_request_string(const NMDhcpOption *requests, guint option); -void nm_dhcp_option_take_option(GHashTable * options, - const NMDhcpOption *requests, - guint option, - char * value); -void nm_dhcp_option_add_option(GHashTable * options, - const NMDhcpOption *requests, - guint option, - const char * value); -void nm_dhcp_option_add_option_u64(GHashTable * options, - const NMDhcpOption *requests, - guint option, - guint64 value); -void nm_dhcp_option_add_requests_to_options(GHashTable *options, const NMDhcpOption *requests); +void nm_dhcp_option_take_option(GHashTable *options, int addr_family, guint option, char *value); +void +nm_dhcp_option_add_option(GHashTable *options, int addr_family, guint option, const char *value); +void nm_dhcp_option_add_option_utf8safe_escape(GHashTable * options, + int addr_family, + guint option, + const guint8 *data, + gsize n_data); +void nm_dhcp_option_add_option_in_addr(GHashTable *options, + int addr_family, + guint option, + in_addr_t value); +void +nm_dhcp_option_add_option_u64(GHashTable *options, int addr_family, guint option, guint64 value); +void nm_dhcp_option_add_requests_to_options(GHashTable *options, int addr_family); GHashTable *nm_dhcp_option_create_options_dict(void); #endif /* __NM_DHCP_OPTIONS_H__ */ diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c index d558a62628..b92a9073fa 100644 --- a/src/core/dhcp/nm-dhcp-systemd.c +++ b/src/core/dhcp/nm-dhcp-systemd.c @@ -138,32 +138,26 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, options = out_options ? nm_dhcp_option_create_options_dict() : NULL; _nm_utils_inet4_ntop(a_address.s_addr, addr_str); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, - addr_str); + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, addr_str); a_plen = nm_utils_ip4_netmask_to_prefix(a_netmask.s_addr); nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, _nm_utils_inet4_ntop(a_netmask.s_addr, addr_str)); nm_dhcp_option_add_option_u64(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME, a_lifetime); nm_dhcp_option_add_option_u64(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_NM_EXPIRY, (guint64)(ts_time + a_lifetime)); if (sd_dhcp_lease_get_next_server(lease, &a_next_server) == 0) { _nm_utils_inet4_ntop(a_next_server.s_addr, addr_str); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, - addr_str); + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, addr_str); } nm_ip4_config_add_address(ip4_config, @@ -179,18 +173,12 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, if (sd_dhcp_lease_get_server_identifier(lease, &server_id) >= 0) { _nm_utils_inet4_ntop(server_id.s_addr, addr_str); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_SERVER_ID, - addr_str); + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_SERVER_ID, addr_str); } if (sd_dhcp_lease_get_broadcast(lease, &broadcast) >= 0) { _nm_utils_inet4_ntop(broadcast.s_addr, addr_str); - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_BROADCAST, - addr_str); + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_BROADCAST, addr_str); } num = sd_dhcp_lease_get_dns(lease, &addr_list); @@ -208,7 +196,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, nm_ip4_config_add_nameserver(ip4_config, addr_list[i].s_addr); } nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER, str->str); } @@ -221,7 +209,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, nm_ip4_config_add_search(ip4_config, search_domains[i]); } nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, str->str); } @@ -230,10 +218,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, gs_strfreev char **domains = NULL; char ** d; - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, - s); + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, s); /* Multiple domains sometimes stuffed into option 15 "Domain Name". * As systemd escapes such characters, split them at \\032. */ @@ -243,10 +228,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, } if (sd_dhcp_lease_get_hostname(lease, &s) >= 0) { - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_HOST_NAME, - s); + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_HOST_NAME, s); } num = sd_dhcp_lease_get_routes(lease, &routes); @@ -348,12 +330,12 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, if (str_classless && str_classless->len > 0) nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, str_classless->str); if (str_static && str_static->len > 0) nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, str_static->str); } @@ -400,17 +382,11 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, }), NULL); } - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_ROUTER, - str->str); + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_ROUTER, str->str); } if (sd_dhcp_lease_get_mtu(lease, &mtu) >= 0 && mtu) { - nm_dhcp_option_add_option_u64(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, - mtu); + nm_dhcp_option_add_option_u64(options, AF_INET, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, mtu); nm_ip4_config_set_mtu(ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP); } @@ -421,38 +397,29 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, _nm_utils_inet4_ntop(addr_list[i].s_addr, addr_str); g_string_append(nm_gstring_add_space_delimiter(str), addr_str); } - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_NTP_SERVER, - str->str); + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NTP_SERVER, str->str); } if (sd_dhcp_lease_get_root_path(lease, &s) >= 0) { - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_ROOT_PATH, - s); + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_ROOT_PATH, s); } if (sd_dhcp_lease_get_t1(lease, &renewal) >= 0) { nm_dhcp_option_add_option_u64(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME, renewal); } if (sd_dhcp_lease_get_t2(lease, &rebinding) >= 0) { nm_dhcp_option_add_option_u64(options, - _nm_dhcp_option_dhcp4_options, + AF_INET, NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME, rebinding); } if (sd_dhcp_lease_get_timezone(lease, &s) >= 0) { - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp4_options, - NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE, - s); + nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE, s); } if (sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len) >= 0) @@ -473,10 +440,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx, g_free(option_string); continue; } - nm_dhcp_option_take_option(options, - _nm_dhcp_option_dhcp4_options, - private_options[i].code, - option_string); + nm_dhcp_option_take_option(options, AF_INET, private_options[i].code, option_string); } } NM_SET_OUT(out_options, g_steal_pointer(&options)); @@ -518,7 +482,7 @@ bound4_handle(NMDhcpSystemd *self, gboolean extended) return; } - nm_dhcp_option_add_requests_to_options(options, _nm_dhcp_option_dhcp4_options); + nm_dhcp_option_add_requests_to_options(options, AF_INET); dhcp_lease_save(lease, priv->lease_file); nm_dhcp_client_set_state(NM_DHCP_CLIENT(self), @@ -702,7 +666,7 @@ ip4_start(NMDhcpClient *client, } /* Add requested options */ - for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) { + for (i = 0; i < (int) G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options); i++) { if (_nm_dhcp_option_dhcp4_options[i].include) { nm_assert(_nm_dhcp_option_dhcp4_options[i].option_num <= 255); r = sd_dhcp_client_set_request_option(sd_client, @@ -821,10 +785,7 @@ lease_to_ip6_config(NMDedupMultiIndex *multi_idx, g_string_append(nm_gstring_add_space_delimiter(str), addr_str); }; if (str->len) - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp6_options, - NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS, - str->str); + nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS, str->str); if (!info_only && nm_ip6_config_get_num_addresses(ip6_config) == 0) { g_set_error_literal(error, @@ -842,10 +803,7 @@ lease_to_ip6_config(NMDedupMultiIndex *multi_idx, g_string_append(nm_gstring_add_space_delimiter(str), addr_str); nm_ip6_config_add_nameserver(ip6_config, &dns[i]); } - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp6_options, - NM_DHCP_OPTION_DHCP6_DNS_SERVERS, - str->str); + nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_DNS_SERVERS, str->str); } num = sd_dhcp6_lease_get_domains(lease, &domains); @@ -855,17 +813,11 @@ lease_to_ip6_config(NMDedupMultiIndex *multi_idx, g_string_append(nm_gstring_add_space_delimiter(str), domains[i]); nm_ip6_config_add_search(ip6_config, domains[i]); } - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp6_options, - NM_DHCP_OPTION_DHCP6_DOMAIN_LIST, - str->str); + nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_DOMAIN_LIST, str->str); } if (sd_dhcp6_lease_get_fqdn(lease, &s) >= 0) { - nm_dhcp_option_add_option(options, - _nm_dhcp_option_dhcp6_options, - NM_DHCP_OPTION_DHCP6_FQDN, - s); + nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_FQDN, s); } NM_SET_OUT(out_options, g_steal_pointer(&options)); @@ -1020,7 +972,7 @@ ip6_start(NMDhcpClient * client, } /* Add requested options */ - for (i = 0; _nm_dhcp_option_dhcp6_options[i].name; i++) { + for (i = 0; i < (int) G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options); i++) { if (_nm_dhcp_option_dhcp6_options[i].include) { r = sd_dhcp6_client_set_request_option(sd_client, _nm_dhcp_option_dhcp6_options[i].option_num); diff --git a/src/core/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c index a6555bce24..646411e201 100644 --- a/src/core/dhcp/nm-dhcp-utils.c +++ b/src/core/dhcp/nm-dhcp-utils.c @@ -8,7 +8,10 @@ #include <unistd.h> #include <arpa/inet.h> +#include "nm-std-aux/unaligned.h" #include "nm-glib-aux/nm-dedup-multi.h" +#include "nm-glib-aux/nm-str-buf.h" +#include "systemd/nm-sd-utils-shared.h" #include "nm-dhcp-utils.h" #include "nm-utils.h" @@ -834,3 +837,285 @@ nm_dhcp_utils_get_dhcp6_event_id(GHashTable *lease) return g_strdup_printf("%s|%s", iaid, start); } + +/*****************************************************************************/ + +gboolean +nm_dhcp_lease_data_parse_u16(const guint8 *data, gsize n_data, uint16_t *out_val) +{ + if (n_data != 2) + return FALSE; + + *out_val = unaligned_read_be16(data); + return TRUE; +} + +gboolean +nm_dhcp_lease_data_parse_mtu(const guint8 *data, gsize n_data, uint16_t *out_val) +{ + uint16_t mtu; + + if (!nm_dhcp_lease_data_parse_u16(data, n_data, &mtu)) + return FALSE; + + if (mtu < 68) { + /* https://tools.ietf.org/html/rfc2132#section-5.1: + * + * The minimum legal value for the MTU is 68. */ + return FALSE; + } + + *out_val = mtu; + return TRUE; +} + +gboolean +nm_dhcp_lease_data_parse_cstr(const guint8 *data, gsize n_data, gsize *out_new_len) +{ + /* WARNING: this function only validates that the string does not contain + * NUL characters (and ignores trailing NULs). It does not check character + * encoding! */ + + while (n_data > 0 && data[n_data - 1] == '\0') + n_data--; + + if (n_data > 0) { + if (memchr(data, n_data, '\0')) { + /* we accept trailing NUL, but none in between. + * + * https://tools.ietf.org/html/rfc2132#section-2 + * https://github.com/systemd/systemd/issues/1337 */ + return FALSE; + } + } + + NM_SET_OUT(out_new_len, n_data); + return TRUE; +} + +char * +nm_dhcp_lease_data_parse_domain_validate(const char *str) +{ + gs_free char *s = NULL; + + s = nm_sd_dns_name_normalize(str); + if (!s) + return NULL; + + if (nm_str_is_empty(s) || (s[0] == '.' && s[1] == '\0')) { + /* root domains are not allowed. */ + return NULL; + } + + if (nm_utils_is_localhost(s)) + return NULL; + + if (!g_utf8_validate(s, -1, NULL)) { + /* the result must be valid UTF-8. */ + return NULL; + } + + return g_steal_pointer(&s); +} + +gboolean +nm_dhcp_lease_data_parse_domain(const guint8 *data, gsize n_data, char **out_val) +{ + gs_free char *str1_free = NULL; + const char * str1; + gs_free char *s = NULL; + + /* this is mostly the same as systemd's lease_parse_domain(). */ + + if (!nm_dhcp_lease_data_parse_cstr(data, n_data, &n_data)) + return FALSE; + + if (n_data == 0) { + /* empty domains are rejected. See + * https://tools.ietf.org/html/rfc2132#section-3.14 + * https://tools.ietf.org/html/rfc2132#section-3.17 + * + * Its minimum length is 1. + * + * Note that this is *after* we potentially stripped trailing NULs. + */ + return FALSE; + } + + str1 = nm_strndup_a(300, (char *) data, n_data, &str1_free); + + s = nm_dhcp_lease_data_parse_domain_validate(str1); + if (!s) + return FALSE; + + *out_val = g_steal_pointer(&s); + return TRUE; +} + +gboolean +nm_dhcp_lease_data_parse_in_addr(const guint8 *data, gsize n_data, in_addr_t *out_val) +{ + /* - option 1, https://tools.ietf.org/html/rfc2132#section-3.3 + * - option 28, https://tools.ietf.org/html/rfc2132#section-5.3 + */ + + if (n_data != 4) + return FALSE; + + *out_val = unaligned_read_ne32(data); + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +lease_option_print_label(NMStrBuf *sbuf, size_t n_label, const uint8_t **datap, size_t *n_datap) +{ + gsize i; + + for (i = 0; i < n_label; ++i) { + uint8_t c = 0; + + if (!nm_dhcp_lease_data_consume(datap, n_datap, &c, sizeof(c))) + return FALSE; + + switch (c) { + case 'a' ... 'z': + case 'A' ... 'Z': + case '0' ... '9': + case '-': + case '_': + nm_str_buf_append_c(sbuf, c); + break; + case '.': + case '\\': + nm_str_buf_append_c2(sbuf, '\\', c); + break; + default: + nm_str_buf_append_printf(sbuf, "\\%3d", c); + } + } + + return TRUE; +} + +static char * +lease_option_print_domain_name(const uint8_t * cache, + size_t * n_cachep, + const uint8_t **datap, + size_t * n_datap) +{ + nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_40, FALSE); + const uint8_t * domain; + size_t n_domain; + size_t n_cache = *n_cachep; + const uint8_t ** domainp = datap; + size_t * n_domainp = n_datap; + gboolean first = TRUE; + uint8_t c; + + /* + * We are given two adjacent memory regions. The @cache contains alreday parsed + * domain names, and the @datap contains the remaining data to parse. + * + * A domain name is formed from a sequence of labels. Each label start with + * a length byte, where the two most significant bits are unset. A zero-length + * label indicates the end of the domain name. + * + * Alternatively, a label can be followed by an offset (indicated by the two + * most significant bits being set in the next byte that is read). The offset + * is an offset into the cache, where the next label of the domain name can + * be found. + * + * Note, that each time a jump to an offset is performed, the size of the + * cache shrinks, so this is guaranteed to terminate. + */ + if (cache + n_cache != *datap) + return NULL; + + for (;;) { + if (!nm_dhcp_lease_data_consume(domainp, n_domainp, &c, sizeof(c))) + return NULL; + + switch (c & 0xC0) { + case 0x00: /* label length */ + { + size_t n_label = c; + + if (n_label == 0) { + /* + * We reached the final label of the domain name. Adjust + * the cache to include the consumed data, and return. + */ + *n_cachep = *datap - cache; + return nm_str_buf_finalize(&sbuf, NULL); + } + + if (!first) + nm_str_buf_append_c(&sbuf, '.'); + else + first = FALSE; + + if (!lease_option_print_label(&sbuf, n_label, domainp, n_domainp)) + return NULL; + + break; + } + case 0xC0: /* back pointer */ + { + size_t offset = (c & 0x3F) << 16; + + /* + * The offset is given as two bytes (in big endian), where the + * two high bits are masked out. + */ + + if (!nm_dhcp_lease_data_consume(domainp, n_domainp, &c, sizeof(c))) + return NULL; + + offset += c; + + if (offset >= n_cache) + return NULL; + + domain = cache + offset; + n_domain = n_cache - offset; + n_cache = offset; + + domainp = &domain; + n_domainp = &n_domain; + + break; + } + default: + return NULL; + } + } +} + +char ** +nm_dhcp_lease_data_parse_search_list(const guint8 *data, gsize n_data) +{ + GPtrArray * array = NULL; + const guint8 *cache = data; + gsize n_cache = 0; + + for (;;) { + gs_free char *s = NULL; + + s = lease_option_print_domain_name(cache, &n_cache, &data, &n_data); + if (!s) + break; + + if (!array) + array = g_ptr_array_new(); + + g_ptr_array_add(array, g_steal_pointer(&s)); + } + + if (!array) + return NULL; + + g_ptr_array_add(array, NULL); + return (char **) g_ptr_array_free(array, FALSE); +} diff --git a/src/core/dhcp/nm-dhcp-utils.h b/src/core/dhcp/nm-dhcp-utils.h index f5bc53a215..69715f90fe 100644 --- a/src/core/dhcp/nm-dhcp-utils.h +++ b/src/core/dhcp/nm-dhcp-utils.h @@ -36,8 +36,35 @@ gboolean nm_dhcp_utils_get_leasefile_path(int addr_family, const char *uuid, char ** out_leasefile_path); -char **nm_dhcp_parse_search_list(guint8 *data, size_t n_data); - char *nm_dhcp_utils_get_dhcp6_event_id(GHashTable *lease); +/*****************************************************************************/ + +static inline gboolean +nm_dhcp_lease_data_consume(const uint8_t **datap, size_t *n_datap, void *out, size_t n_out) +{ + if (*n_datap < n_out) + return FALSE; + + memcpy(out, *datap, n_out); + *datap += n_out; + *n_datap -= n_out; + return TRUE; +} + +static inline gboolean +nm_dhcp_lease_data_consume_in_addr(const uint8_t **datap, size_t *n_datap, in_addr_t *addrp) +{ + return nm_dhcp_lease_data_consume(datap, n_datap, addrp, sizeof(struct in_addr)); +} + +char *nm_dhcp_lease_data_parse_domain_validate(const char *str); + +gboolean nm_dhcp_lease_data_parse_u16(const guint8 *data, gsize n_data, guint16 *out_val); +gboolean nm_dhcp_lease_data_parse_mtu(const guint8 *data, gsize n_data, guint16 *out_val); +gboolean nm_dhcp_lease_data_parse_cstr(const guint8 *data, gsize n_data, gsize *out_new_len); +gboolean nm_dhcp_lease_data_parse_domain(const guint8 *data, gsize n_data, char **out_val); +gboolean nm_dhcp_lease_data_parse_in_addr(const guint8 *data, gsize n_data, in_addr_t *out_val); +char ** nm_dhcp_lease_data_parse_search_list(const guint8 *data, gsize n_data); + #endif /* __NETWORKMANAGER_DHCP_UTILS_H__ */ diff --git a/src/core/dhcp/tests/test-dhcp-utils.c b/src/core/dhcp/tests/test-dhcp-utils.c index 02fe640660..9b54e2cd02 100644 --- a/src/core/dhcp/tests/test-dhcp-utils.c +++ b/src/core/dhcp/tests/test-dhcp-utils.c @@ -13,10 +13,13 @@ #include "nm-utils.h" #include "dhcp/nm-dhcp-utils.h" +#include "dhcp/nm-dhcp-options.h" #include "platform/nm-platform.h" #include "nm-test-utils-core.h" +/*****************************************************************************/ + static NMIP4Config * _ip4_config_from_options(int ifindex, const char *iface, GHashTable *options, guint32 route_metric) { @@ -202,7 +205,7 @@ test_parse_search_list(void) char ** domains; data = (guint8[]){0x05, 'l', 'o', 'c', 'a', 'l', 0x00}; - domains = nm_dhcp_parse_search_list(data, 7); + domains = nm_dhcp_lease_data_parse_search_list(data, 7); g_assert(domains); g_assert_cmpint(g_strv_length(domains), ==, 1); g_assert_cmpstr(domains[0], ==, "local"); @@ -211,7 +214,7 @@ test_parse_search_list(void) data = (guint8[]){0x04, 't', 'e', 's', 't', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, 0xc0, 0x05, 0x03, 'a', 'b', 'c', 0xc0, 0x0d, 0x06, 'f', 'o', 'o', 'b', 'a', 'r', 0x00}; - domains = nm_dhcp_parse_search_list(data, 34); + domains = nm_dhcp_lease_data_parse_search_list(data, 34); g_assert(domains); g_assert_cmpint(g_strv_length(domains), ==, 4); g_assert_cmpstr(domains[0], ==, "test.example.com"); @@ -226,7 +229,7 @@ test_parse_search_list(void) 'a', 'd', }; - domains = nm_dhcp_parse_search_list(data, 4); + domains = nm_dhcp_lease_data_parse_search_list(data, 4); g_assert(!domains); data = (guint8[]){ @@ -241,7 +244,7 @@ test_parse_search_list(void) 'a', 'd', }; - domains = nm_dhcp_parse_search_list(data, 10); + domains = nm_dhcp_lease_data_parse_search_list(data, 10); g_assert(domains); g_assert_cmpint(g_strv_length(domains), ==, 1); g_assert_cmpstr(domains[0], ==, "okay"); @@ -740,6 +743,46 @@ test_client_id_from_string(void) COMPARE_ID(endcolon, TRUE, endcolon, strlen(endcolon)); } +/*****************************************************************************/ + +static void +test_dhcp_opt_list(gconstpointer test_data) +{ + const gboolean IS_IPv4 = (GPOINTER_TO_INT(test_data) == 0); + const int addr_family = IS_IPv4 ? AF_INET : AF_INET6; + const NMDhcpOption *const options = + IS_IPv4 ? _nm_dhcp_option_dhcp4_options : _nm_dhcp_option_dhcp6_options; + const guint n = (IS_IPv4 ? G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options) + : G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options)); + guint i; + guint j; + + g_assert(options); + g_assert(n > 0); + + for (i = 0; i < n; i++) { + const NMDhcpOption *const opt = &options[i]; + + g_assert_cmpstr(opt->name, !=, NULL); + g_assert(NM_STR_HAS_PREFIX(opt->name, NM_DHCP_OPTION_REQPREFIX)); + + for (j = 0; j < i; j++) { + const NMDhcpOption *const opt2 = &options[j]; + + g_assert_cmpstr(opt->name, !=, opt2->name); + g_assert_cmpint(opt->option_num, !=, opt2->option_num); + } + } + + for (i = 0; i < n; i++) { + const NMDhcpOption *const opt = &options[i]; + + g_assert(opt == nm_dhcp_option_find(addr_family, opt->option_num)); + } +} + +/*****************************************************************************/ + NMTST_DEFINE(); int @@ -776,6 +819,8 @@ main(int argc, char **argv) g_test_add_func("/dhcp/client-id-from-string", test_client_id_from_string); g_test_add_func("/dhcp/vendor-option-metered", test_vendor_option_metered); g_test_add_func("/dhcp/parse-search-list", test_parse_search_list); + g_test_add_data_func("/dhcp/test_dhcp_opt_list/IPv4", GINT_TO_POINTER(0), test_dhcp_opt_list); + g_test_add_data_func("/dhcp/test_dhcp_opt_list/IPv6", GINT_TO_POINTER(1), test_dhcp_opt_list); return g_test_run(); } diff --git a/src/core/ndisc/nm-lndp-ndisc.c b/src/core/ndisc/nm-lndp-ndisc.c index d846411e0b..f773478f51 100644 --- a/src/core/ndisc/nm-lndp-ndisc.c +++ b/src/core/ndisc/nm-lndp-ndisc.c @@ -474,7 +474,7 @@ dns_servers_done: gsize padding; gsize len; - nm_str_buf_reset(&sbuf, NULL); + nm_str_buf_reset(&sbuf); for (i = 0; i < rdata->dns_domains->len; i++) { const NMNDiscDNSDomain *dns_domain = |