diff options
| author | Michael Biebl <biebl@debian.org> | 2018-06-17 14:54:10 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2018-06-17 14:54:10 +0200 |
| commit | 069cb5c3a525ebcc19cc2927964258acaca87b13 (patch) | |
| tree | 66ac6b21a44d630c0fc281c0df327239d491226a /src/devices/nm-device.c | |
| parent | 04bc9e1cd3544445d883ad29ea108c1645c8e7b7 (diff) | |
New upstream version 1.11.90 upstream/1.11.90
Diffstat (limited to 'src/devices/nm-device.c')
| -rw-r--r-- | src/devices/nm-device.c | 427 |
1 files changed, 369 insertions, 58 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 9a40a0dd..9df41410 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -37,8 +37,11 @@ #include <linux/if_arp.h> #include <linux/rtnetlink.h> #include <linux/pkt_sched.h> +#include <uuid/uuid.h> #include "nm-utils/nm-dedup-multi.h" +#include "nm-utils/nm-random-utils.h" +#include "nm-utils/unaligned.h" #include "nm-common-macros.h" #include "nm-device-private.h" @@ -6830,11 +6833,12 @@ dhcp4_grace_period_expired (gpointer user_data) } static void -dhcp4_fail (NMDevice *self, gboolean timeout) +dhcp4_fail (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - _LOGD (LOGD_DHCP4, "DHCPv4 failed%s", timeout ? " (timeout)" : ""); + _LOGD (LOGD_DHCP4, "DHCPv4 failed (ip_state %s)", + _ip_state_to_string (priv->ip4_state)); /* Keep client running if there are static addresses configured * on the interface. @@ -6848,7 +6852,7 @@ dhcp4_fail (NMDevice *self, gboolean timeout) * configuration. */ if ( !priv->dhcp4.was_active - && (timeout || priv->ip4_state == IP_CONF)) { + && priv->ip4_state == IP_CONF) { dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE); nm_device_activate_schedule_ip4_config_timeout (self); return; @@ -6911,7 +6915,7 @@ dhcp4_state_changed (NMDhcpClient *client, case NM_DHCP_STATE_BOUND: if (!ip4_config) { _LOGW (LOGD_DHCP4, "failed to get IPv4 config in response to DHCP event."); - dhcp4_fail (self, FALSE); + dhcp4_fail (self); break; } @@ -6950,11 +6954,11 @@ dhcp4_state_changed (NMDhcpClient *client, if (dhcp4_lease_change (self, ip4_config)) nm_device_update_metered (self); else - dhcp4_fail (self, FALSE); + dhcp4_fail (self); } break; case NM_DHCP_STATE_TIMEOUT: - dhcp4_fail (self, TRUE); + dhcp4_fail (self); break; case NM_DHCP_STATE_EXPIRE: /* Ignore expiry before we even have a lease (NAK, old lease, etc) */ @@ -6963,7 +6967,7 @@ dhcp4_state_changed (NMDhcpClient *client, /* fall through */ case NM_DHCP_STATE_DONE: case NM_DHCP_STATE_FAIL: - dhcp4_fail (self, FALSE); + dhcp4_fail (self); break; default: break; @@ -7014,13 +7018,32 @@ get_dhcp_timeout (NMDevice *self, int addr_family) } static GBytes * -dhcp4_get_client_id (NMDevice *self, NMConnection *connection) +dhcp4_get_client_id_mac (const guint8 *hwaddr /* ETH_ALEN bytes */) +{ + guint8 *client_id_buf; + guint8 hwaddr_type = ARPHRD_ETHER; + + client_id_buf = g_malloc (ETH_ALEN + 1); + client_id_buf[0] = hwaddr_type; + memcpy (&client_id_buf[1], hwaddr, ETH_ALEN); + return g_bytes_new_take (client_id_buf, ETH_ALEN + 1); +} + +static GBytes * +dhcp4_get_client_id (NMDevice *self, + NMConnection *connection, + GBytes *hwaddr) { NMSettingIPConfig *s_ip4; const char *client_id; gs_free char *client_id_default = NULL; guint8 *client_id_buf; - gboolean is_mac; + const char *fail_reason; + guint8 hwaddr_bin_buf[NM_UTILS_HWADDR_LEN_MAX]; + const guint8 *hwaddr_bin; + gsize hwaddr_len; + GBytes *result; + gs_free char *logstr1 = NULL; s_ip4 = nm_connection_get_setting_ip4_config (connection); client_id = nm_setting_ip4_config_get_dhcp_client_id (NM_SETTING_IP4_CONFIG (s_ip4)); @@ -7028,42 +7051,54 @@ dhcp4_get_client_id (NMDevice *self, NMConnection *connection) if (!client_id) { client_id_default = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA, "ipv4.dhcp-client-id", self); - if (client_id_default && client_id_default[0]) + if (client_id_default && client_id_default[0]) { + /* a non-empty client-id is always valid, see nm_dhcp_utils_client_id_string_to_bytes(). */ client_id = client_id_default; + } } - if (!client_id) + if (!client_id) { + _LOGD (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4, + "ipv4.dhcp-client-id: no explicity client-id configured"); return NULL; + } - if ( (is_mac = nm_streq (client_id, "mac")) - || nm_streq (client_id, "perm-mac")) { - const char *hwaddr; - char addr_buf[NM_UTILS_HWADDR_LEN_MAX]; - gsize addr_len; - guint8 addr_type; - - hwaddr = is_mac - ? nm_device_get_hw_address (self) - : nm_device_get_permanent_hw_address (self); - if (!hwaddr) - return NULL; + if (nm_streq (client_id, "mac")) { + if (!hwaddr) { + fail_reason = "failed to get current MAC address"; + goto out_fail; + } + + hwaddr_bin = g_bytes_get_data (hwaddr, &hwaddr_len); + if (hwaddr_len != ETH_ALEN) { + fail_reason = "MAC address is not ethernet"; + goto out_fail; + } + + result = dhcp4_get_client_id_mac (hwaddr_bin); + goto out_good; + } + + if (nm_streq (client_id, "perm-mac")) { + const char *hwaddr_str; + + hwaddr_str = nm_device_get_permanent_hw_address (self); + if (!hwaddr_str) { + fail_reason = "failed to get permanent MAC address"; + goto out_fail; + } - if (!_nm_utils_hwaddr_aton (hwaddr, addr_buf, sizeof (addr_buf), &addr_len)) + if (!_nm_utils_hwaddr_aton (hwaddr_str, hwaddr_bin_buf, sizeof (hwaddr_bin_buf), &hwaddr_len)) g_return_val_if_reached (NULL); - switch (addr_len) { - case ETH_ALEN: - addr_type = ARPHRD_ETHER; - break; - default: + if (hwaddr_len != ETH_ALEN) { /* unsupported type. */ - return NULL; + fail_reason = "MAC address is not ethernet"; + goto out_fail; } - client_id_buf = g_malloc (addr_len + 1); - client_id_buf[0] = addr_type; - memcpy (&client_id_buf[1], addr_buf, addr_len); - return g_bytes_new_take (client_id_buf, addr_len + 1); + result = dhcp4_get_client_id_mac (hwaddr_bin_buf); + goto out_good; } if (nm_streq (client_id, "stable")) { @@ -7099,10 +7134,30 @@ dhcp4_get_client_id (NMDevice *self, NMConnection *connection) client_id_buf = g_malloc (1 + 15); client_id_buf[0] = 0; memcpy (&client_id_buf[1], buf, 15); - return g_bytes_new_take (client_id_buf, 1 + 15); + result = g_bytes_new_take (client_id_buf, 1 + 15); + goto out_good; } - return nm_dhcp_utils_client_id_string_to_bytes (client_id); + result = nm_dhcp_utils_client_id_string_to_bytes (client_id); + goto out_good; + +out_fail: + nm_assert (fail_reason); + _LOGW (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4, + "ipv4.dhcp-client-id: failure to generate client id (%s). Use random client id", + fail_reason); + client_id_buf = g_malloc (1 + 15); + client_id_buf[0] = 0; + nm_utils_random_bytes (&client_id_buf[1], 15); + result = g_bytes_new_take (client_id_buf, 1 + 15); + +out_good: + nm_assert (result); + _LOGD (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4, + "ipv4.dhcp-client-id: use \"%s\" client ID: %s", + client_id, + (logstr1 = nm_dhcp_utils_duid_to_string (result))); + return result; } static NMActStageReturn @@ -7126,7 +7181,7 @@ dhcp4_start (NMDevice *self) hwaddr = nm_platform_link_get_address_as_bytes (nm_device_get_platform (self), nm_device_get_ip_ifindex (self)); - client_id = dhcp4_get_client_id (self, connection); + client_id = dhcp4_get_client_id (self, connection, hwaddr); g_warn_if_fail (priv->dhcp4.client == NULL); priv->dhcp4.client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (), @@ -7678,12 +7733,260 @@ dhcp6_prefix_delegated (NMDhcpClient *client, g_signal_emit (self, signals[IP6_PREFIX_DELEGATED], 0, prefix); } +/* RFC 3315 defines the epoch for the DUID-LLT time field on Jan 1st 2000. */ +#define EPOCH_DATETIME_200001010000 946684800 + +static GBytes * +generate_duid_llt (const guint8 *hwaddr /* ETH_ALEN bytes */, + gint64 time) +{ + GByteArray *duid_arr; + const guint16 duid_type = htons (1); + const guint16 hw_type = htons (ARPHRD_ETHER); + const guint32 duid_time = htonl (NM_MAX (0, time - EPOCH_DATETIME_200001010000)); + + duid_arr = g_byte_array_sized_new (2 + 4 + 2 + ETH_ALEN); + + g_byte_array_append (duid_arr, (const guint8 *) &duid_type, 2); + g_byte_array_append (duid_arr, (const guint8 *) &hw_type, 2); + g_byte_array_append (duid_arr, (const guint8 *) &duid_time, 4); + g_byte_array_append (duid_arr, hwaddr, ETH_ALEN); + + return g_byte_array_free_to_bytes (duid_arr); +} + +static GBytes * +generate_duid_ll (const guint8 *hwaddr /* ETH_ALEN bytes */) +{ + GByteArray *duid_arr; + const guint16 duid_type = htons (3); + const guint16 hw_type = htons (ARPHRD_ETHER); + + duid_arr = g_byte_array_sized_new (2 + 2 + ETH_ALEN); + + g_byte_array_append (duid_arr, (const guint8 *) &duid_type, 2); + g_byte_array_append (duid_arr, (const guint8 *) &hw_type, 2); + g_byte_array_append (duid_arr, hwaddr, ETH_ALEN); + + return g_byte_array_free_to_bytes (duid_arr); +} + +static GBytes * +generate_duid_uuid (guint8 *data, gsize data_len) +{ + const guint16 duid_type = g_htons (4); + const int DUID_SIZE = 18; + guint8 *duid_buffer; + + nm_assert (data); + nm_assert (data_len >= 16); + + /* Generate a DHCP Unique Identifier for DHCPv6 using the + * DUID-UUID method (see RFC 6355 section 4). Format is: + * + * u16: type (DUID-UUID = 4) + * u8[16]: UUID bytes + */ + duid_buffer = g_malloc (DUID_SIZE); + + G_STATIC_ASSERT_EXPR (sizeof (duid_type) == 2); + memcpy (&duid_buffer[0], &duid_type, 2); + + /* UUID is 128 bits, we just take the first 128 bits + * (regardless of data size) as the DUID-UUID. + */ + memcpy (&duid_buffer[2], data, 16); + + return g_bytes_new_take (duid_buffer, DUID_SIZE); +} + +static GBytes * +generate_duid_from_machine_id (void) +{ + gs_free const char *machine_id_s = NULL; + uuid_t uuid; + GChecksum *sum; + guint8 sha256_digest[32]; + gsize len = sizeof (sha256_digest); + static GBytes *global_duid = NULL; + + if (global_duid) + return g_bytes_ref (global_duid); + + machine_id_s = nm_utils_machine_id_read (); + if (!nm_utils_machine_id_parse (machine_id_s, uuid)) + return NULL; + + /* Hash the machine ID so it's not leaked to the network */ + sum = g_checksum_new (G_CHECKSUM_SHA256); + g_checksum_update (sum, (const guchar *) &uuid, sizeof (uuid)); + g_checksum_get_digest (sum, sha256_digest, &len); + g_checksum_free (sum); + + global_duid = generate_duid_uuid (sha256_digest, len); + return g_bytes_ref (global_duid); +} + +static GBytes * +dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, NMDhcpDuidEnforce *out_enforce) +{ + NMSettingIPConfig *s_ip6; + const char *duid; + gs_free char *duid_default = NULL; + const char *duid_error; + GBytes *duid_out; + guint8 sha256_digest[32]; + gsize len = sizeof (sha256_digest); + NMDhcpDuidEnforce duid_enforce = NM_DHCP_DUID_ENFORCE_ALWAYS; + gs_free char *logstr1 = NULL; + + s_ip6 = nm_connection_get_setting_ip6_config (connection); + duid = nm_setting_ip6_config_get_dhcp_duid (NM_SETTING_IP6_CONFIG (s_ip6)); + + if (!duid) { + duid_default = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA, + "ipv6.dhcp-duid", self); + duid = duid_default; + if (!duid) + duid = "lease"; + } + + if (nm_streq (duid, "lease")) { + duid_enforce = NM_DHCP_DUID_ENFORCE_NEVER; + duid_out = generate_duid_from_machine_id (); + if (!duid_out) { + duid_error = "failure to read machine-id"; + goto out_fail; + } + goto out_good; + } + + if (!_nm_utils_dhcp_duid_valid (duid, &duid_out)) { + duid_error = "invalid duid"; + goto out_fail; + } + + if (duid_out) + goto out_good; + + if (NM_IN_STRSET (duid, "ll", "llt")) { + if (!hwaddr) { + duid_error = "missing link-layer address"; + goto out_fail; + } + if (g_bytes_get_size (hwaddr) != ETH_ALEN) { + duid_error = "unsupported link-layer address"; + goto out_fail; + } + + if (nm_streq (duid, "ll")) { + duid_out = generate_duid_ll (g_bytes_get_data (hwaddr, NULL)); + } else { + gint64 time; + + time = nm_utils_secret_key_get_timestamp (); + if (!time) { + duid_error = "cannot retrieve the secret key timestamp"; + goto out_fail; + } + + duid_out = generate_duid_llt (g_bytes_get_data (hwaddr, NULL), time); + } + + goto out_good; + } + + if (NM_IN_STRSET (duid, "stable-ll", "stable-llt", "stable-uuid")) { + NMUtilsStableType stable_type; + const char *stable_id = NULL; + guint32 salted_header; + GChecksum *sum; + const guint8 *secret_key; + gsize secret_key_len; + + stable_id = _get_stable_id (self, connection, &stable_type); + if (!stable_id) + g_return_val_if_reached (NULL); + + salted_header = htonl (670531087 + stable_type); + + nm_utils_secret_key_get (&secret_key, &secret_key_len); + + sum = g_checksum_new (G_CHECKSUM_SHA256); + + g_checksum_update (sum, (const guchar *) &salted_header, sizeof (salted_header)); + g_checksum_update (sum, (const guchar *) stable_id, -1); + g_checksum_update (sum, (const guchar *) secret_key, secret_key_len); + + g_checksum_get_digest (sum, sha256_digest, &len); + g_checksum_free (sum); + + if (nm_streq (duid, "stable-ll")) { + duid_out = generate_duid_ll (sha256_digest); + } else if (nm_streq (duid, "stable-llt")) { + gint64 time; + +#define EPOCH_DATETIME_THREE_YEARS (356 * 24 * 3600 * 3) + + /* We want a variable time between the secret_key timestamp and three years + * before. Let's compute the time (in seconds) from 0 to 3 years; then we'll + * subtract it from the secret_key timestamp. + */ + time = nm_utils_secret_key_get_timestamp (); + if (!time) { + duid_error = "cannot retrieve the secret key timestamp"; + goto out_fail; + } + /* don't use too old timestamps. They cannot be expressed in DUID-LLT and + * would all be truncated to zero. */ + time = NM_MAX (time, EPOCH_DATETIME_200001010000 + EPOCH_DATETIME_THREE_YEARS); + time -= (unaligned_read_be32 (&sha256_digest[ETH_ALEN]) % EPOCH_DATETIME_THREE_YEARS); + + duid_out = generate_duid_llt (sha256_digest, time); + } else { + nm_assert (nm_streq (duid, "stable-uuid")); + duid_out = generate_duid_uuid (sha256_digest, len); + } + + goto out_good; + } + + g_return_val_if_reached (NULL); + +out_fail: + nm_assert (!duid_out && duid_error); + { + guint8 uuid[16]; + + _LOGW (LOGD_IP6 | LOGD_DHCP6, + "ipv6.dhcp-duid: failure to generate %s DUID: %s. Fallback to random DUID-UUID.", + duid, duid_error); + + nm_utils_random_bytes (uuid, sizeof (uuid)); + duid_out = generate_duid_uuid (uuid, sizeof (uuid)); + } + +out_good: + nm_assert (duid_out); + _LOGD (LOGD_IP6 | LOGD_DHCP6, + "ipv6.dhcp-duid: generate %s DUID '%s' (%s)", + duid, + (logstr1 = nm_dhcp_utils_duid_to_string (duid_out)), + (duid_enforce == NM_DHCP_DUID_ENFORCE_ALWAYS) ? "enforcing" : "fallback"); + + NM_SET_OUT (out_enforce, duid_enforce); + return duid_out; +} + static gboolean dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMSettingIPConfig *s_ip6; gs_unref_bytes GBytes *hwaddr = NULL; + gs_unref_bytes GBytes *duid = NULL; + NMDhcpDuidEnforce enforce_duid = NM_DHCP_DUID_ENFORCE_NEVER; + const NMPlatformIP6Address *ll_addr = NULL; g_assert (connection); @@ -7704,6 +8007,7 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection) hwaddr = nm_platform_link_get_address_as_bytes (nm_device_get_platform (self), nm_device_get_ip_ifindex (self)); + duid = dhcp6_get_duid (self, connection, hwaddr, &enforce_duid); priv->dhcp6.client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (), nm_device_get_multi_index (self), nm_device_get_ip_iface (self), @@ -7715,6 +8019,8 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection) nm_device_get_route_metric (self, AF_INET6), nm_setting_ip_config_get_dhcp_send_hostname (s_ip6), nm_setting_ip_config_get_dhcp_hostname (s_ip6), + duid, + enforce_duid, get_dhcp_timeout (self, AF_INET6), priv->dhcp_anycast_address, (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE, @@ -8139,8 +8445,10 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config) if (ifindex <= 0) return; - if (nm_device_sys_iface_state_is_external_or_assume (self)) { - /* for assumed connections we don't tamper with the MTU. */ + if ( !nm_device_get_applied_connection (self) + || nm_device_sys_iface_state_is_external_or_assume (self)) { + /* we don't tamper with the MTU of disconnected and + * external/assumed devices. */ return; } @@ -9886,43 +10194,45 @@ _cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type) gboolean _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name, - GError **error, const char **argv) + GError **error, const char **whitelist) { - guint found_keys = 0; + guint found_whitelisted_keys = 0; guint i; nm_assert (hash && g_hash_table_size (hash) > 0); - nm_assert (argv && argv[0]); + nm_assert (whitelist && whitelist[0]); #if NM_MORE_ASSERTS > 10 - /* Assert that the keys are unique. */ + /* Require whitelist to only contain unique keys. */ { gs_unref_hashtable GHashTable *check_dups = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL); - for (i = 0; argv[i]; i++) { - if (!g_hash_table_add (check_dups, (char *) argv[i])) + for (i = 0; whitelist[i]; i++) { + if (!g_hash_table_add (check_dups, (char *) whitelist[i])) nm_assert (FALSE); } nm_assert (g_hash_table_size (check_dups) > 0); } #endif - for (i = 0; argv[i]; i++) { - if (g_hash_table_contains (hash, argv[i])) - found_keys++; + for (i = 0; whitelist[i]; i++) { + if (g_hash_table_contains (hash, whitelist[i])) + found_whitelisted_keys++; } - if (found_keys != g_hash_table_size (hash)) { + if (found_whitelisted_keys == g_hash_table_size (hash)) { + /* Good, there are only whitelisted keys in the hash. */ + return TRUE; + } + + if (error) { GHashTableIter iter; const char *k = NULL; const char *first_invalid_key = NULL; - if (!error) - return FALSE; - g_hash_table_iter_init (&iter, hash); while (g_hash_table_iter_next (&iter, (gpointer *) &k, NULL)) { - if (nm_utils_strv_find_first ((char **) argv, -1, k) < 0) { + if (nm_utils_strv_find_first ((char **) whitelist, -1, k) < 0) { first_invalid_key = k; break; } @@ -9942,10 +10252,9 @@ _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name, first_invalid_key); } g_return_val_if_fail (first_invalid_key, FALSE); - return FALSE; } - return TRUE; + return FALSE; } void @@ -10088,9 +10397,11 @@ can_reapply_change (NMDevice *self, const char *setting_name, NM_SETTING_CONNECTION_METERED, NM_SETTING_CONNECTION_LLDP); } else if (NM_IN_STRSET (setting_name, - NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP6_CONFIG_SETTING_NAME, NM_SETTING_PROXY_SETTING_NAME)) { + return TRUE; + } else if (NM_IN_STRSET (setting_name, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_SETTING_NAME)) { if (g_hash_table_contains (diffs, NM_SETTING_IP_CONFIG_ROUTE_TABLE)) { /* changing the route-table setting is complicated, because it affects * how we sync the routes. Don't support changing it without full @@ -10221,7 +10532,7 @@ check_and_reapply_connection (NMDevice *self, if (diffs) { NMConnection *connection_clean = connection; - gs_free NMConnection *connection_clean_free = NULL; + gs_unref_object NMConnection *connection_clean_free = NULL; { NMSettingConnection *s_con_a, *s_con_n; |