mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-05-04 19:58:12 +02:00
dhcp: reimplement node-specific DHCP client-id generation from systemd
Our internal DHCP client (from systemd) defaults to a particular client ID. It is currently exposed as nm_sd_utils_generate_default_dhcp_client_id() and is based on the systemd implementation. One problem with that is, that it internally looks up the interface name with if_indextoname() and reads /etc/machine-id. Both makes it harder for testing. Another problem is, that this way of generating the client-id is currently limited to internal client. Why? If you use dhclient plugin, you may still want to use the same algorithm. Also, there is no explict "ipv4.dhcp-client-id" mode to select this client-id (so that it could be used in combination with "dhclient" plugin). As such, this code will be useful also aside systemd DHCP plugin. Hence, the function should not be obviously tied to systemd code. The implementation is simple enough, and since we already have a unit-test, refactor the code to our own implementation.
This commit is contained in:
parent
187d356198
commit
a55795772a
5 changed files with 172 additions and 130 deletions
|
|
@ -30,6 +30,7 @@
|
|||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <resolv.h>
|
||||
#include <byteswap.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
|
|
@ -39,6 +40,7 @@
|
|||
|
||||
#include "nm-utils/nm-random-utils.h"
|
||||
#include "nm-utils/nm-io-utils.h"
|
||||
#include "nm-utils/unaligned.h"
|
||||
#include "nm-utils.h"
|
||||
#include "nm-core-internal.h"
|
||||
#include "nm-setting-connection.h"
|
||||
|
|
@ -3285,6 +3287,101 @@ nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
|
|||
|
||||
/*****************************************************************************/
|
||||
|
||||
/**
|
||||
* nm_utils_dhcp_client_id_systemd_node_specific_full:
|
||||
* @legacy_unstable_byteorder: historically, the code would generate a iaid
|
||||
* dependent on host endianness. This is undesirable, if backward compatibility
|
||||
* are not a concern, generate stable endianness.
|
||||
* @interface_id: a binary identifer that is hashed into the DUID.
|
||||
* Comonly this is the interface-name, but it may be the MAC address.
|
||||
* @interface_id_len: the length of @interface_id.
|
||||
* @machine_id: the binary identifier for the machine. It is hashed
|
||||
* into the DUID. It commonly is /etc/machine-id (parsed in binary as NMUuid).
|
||||
* @machine_id_len: the length of the @machine_id.
|
||||
*
|
||||
* Systemd's sd_dhcp_client generates a default client ID (type 255, node-specific,
|
||||
* RFC 4361) if no explicit client-id is set. This function duplicates that
|
||||
* implementation and exposes it as (internal) API.
|
||||
*
|
||||
* Returns: a %GBytes of generated client-id. This function cannot fail.
|
||||
*/
|
||||
GBytes *
|
||||
nm_utils_dhcp_client_id_systemd_node_specific_full (gboolean legacy_unstable_byteorder,
|
||||
const guint8 *interface_id,
|
||||
gsize interface_id_len,
|
||||
const guint8 *machine_id,
|
||||
gsize machine_id_len)
|
||||
{
|
||||
const guint8 HASH_KEY[16] = { 0x80, 0x11, 0x8c, 0xc2, 0xfe, 0x4a, 0x03, 0xee, 0x3e, 0xd6, 0x0c, 0x6f, 0x36, 0x39, 0x14, 0x09 };
|
||||
const guint16 DUID_TYPE_EN = 2;
|
||||
const guint32 SYSTEMD_PEN = 43793;
|
||||
struct _nm_packed {
|
||||
guint8 type;
|
||||
guint32 iaid;
|
||||
struct _nm_packed {
|
||||
guint16 type;
|
||||
union {
|
||||
struct _nm_packed {
|
||||
/* DUID_TYPE_EN */
|
||||
guint32 pen;
|
||||
uint8_t id[8];
|
||||
} en;
|
||||
};
|
||||
} duid;
|
||||
} *client_id;
|
||||
guint64 u64;
|
||||
guint32 u32;
|
||||
|
||||
g_return_val_if_fail (interface_id, NULL);
|
||||
g_return_val_if_fail (interface_id_len > 0, NULL);
|
||||
g_return_val_if_fail (machine_id, NULL);
|
||||
g_return_val_if_fail (machine_id_len > 0, NULL);
|
||||
|
||||
client_id = g_malloc (sizeof (*client_id));
|
||||
|
||||
client_id->type = 255;
|
||||
|
||||
u64 = c_siphash_hash (HASH_KEY, interface_id, interface_id_len);
|
||||
u32 = (u64 & 0xffffffffu) ^ (u64 >> 32);
|
||||
if (legacy_unstable_byteorder) {
|
||||
/* original systemd code dhcp_identifier_set_iaid() generates the iaid
|
||||
* in native endianness. Do that too, to preserve compatibility
|
||||
* (https://github.com/systemd/systemd/pull/10614). */
|
||||
u32 = bswap_32 (u32);
|
||||
} else {
|
||||
/* generate fixed byteorder, in a way that on little endian systems
|
||||
* the values agree. Meaning: legacy behavior is identical to this
|
||||
* on little endian. */
|
||||
u32 = be32toh (u32);
|
||||
}
|
||||
unaligned_write_ne32 (&client_id->iaid, u32);
|
||||
|
||||
unaligned_write_be16 (&client_id->duid.type, DUID_TYPE_EN);
|
||||
|
||||
unaligned_write_be32 (&client_id->duid.en.pen, SYSTEMD_PEN);
|
||||
|
||||
u64 = htole64 (c_siphash_hash (HASH_KEY, machine_id, machine_id_len));
|
||||
memcpy(client_id->duid.en.id, &u64, sizeof (client_id->duid.en.id));
|
||||
|
||||
G_STATIC_ASSERT_EXPR (sizeof (*client_id) == 19);
|
||||
return g_bytes_new_take (client_id, 19);
|
||||
}
|
||||
|
||||
GBytes *
|
||||
nm_utils_dhcp_client_id_systemd_node_specific (gboolean legacy_unstable_byteorder,
|
||||
const char *ifname)
|
||||
{
|
||||
g_return_val_if_fail (ifname && ifname[0], NULL);
|
||||
|
||||
return nm_utils_dhcp_client_id_systemd_node_specific_full (legacy_unstable_byteorder,
|
||||
(const guint8 *) ifname,
|
||||
strlen (ifname),
|
||||
(const guint8 *) nm_utils_machine_id_bin (),
|
||||
sizeof (NMUuid));
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/**
|
||||
* nm_utils_setpgid:
|
||||
* @unused: unused
|
||||
|
|
|
|||
|
|
@ -373,6 +373,19 @@ char *nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
|
|||
const char *current_mac_address,
|
||||
const char *generate_mac_address_mask);
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
GBytes *nm_utils_dhcp_client_id_systemd_node_specific_full (gboolean legacy_unstable_byteorder,
|
||||
const guint8 *interface_id,
|
||||
gsize interface_id_len,
|
||||
const guint8 *machine_id,
|
||||
gsize machine_id_len);
|
||||
|
||||
GBytes *nm_utils_dhcp_client_id_systemd_node_specific (gboolean legacy_unstable_byteorder,
|
||||
const char *ifname);
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
void nm_utils_array_remove_at_indexes (GArray *array, const guint *indexes_to_delete, gsize len);
|
||||
|
||||
void nm_utils_setpgid (gpointer unused);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
|
||||
#include "path-util.h"
|
||||
#include "sd-id128.h"
|
||||
#include "dhcp-identifier.h"
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
|
|
@ -60,48 +59,3 @@ nm_sd_utils_id128_get_machine (NMUuid *out_uuid)
|
|||
return NULL;
|
||||
return out_uuid;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/**
|
||||
* nm_sd_utils_generate_default_dhcp_client_id:
|
||||
* @ifindex: the interface ifindex
|
||||
* @mac: the MAC address
|
||||
* @mac_addr_len: the length of MAC address.
|
||||
*
|
||||
* Systemd's sd_dhcp_client generates a default client ID (type 255, node-specific,
|
||||
* RFC 4361) if no explicit client-id is set. This function duplicates that
|
||||
* implementation and exposes it as (internal) API.
|
||||
*
|
||||
* Returns: a %GBytes of generated client-id, or %NULL on failure.
|
||||
*/
|
||||
GBytes *
|
||||
nm_sd_utils_generate_default_dhcp_client_id (int ifindex,
|
||||
const guint8 *mac_addr,
|
||||
gsize mac_addr_len)
|
||||
{
|
||||
struct _nm_packed {
|
||||
guint8 type;
|
||||
guint32 iaid;
|
||||
struct duid duid;
|
||||
} client_id;
|
||||
int r;
|
||||
gsize duid_len;
|
||||
|
||||
g_return_val_if_fail (ifindex > 0, NULL);
|
||||
g_return_val_if_fail (mac_addr, NULL);
|
||||
g_return_val_if_fail (mac_addr_len > 0, NULL);
|
||||
|
||||
client_id.type = 255;
|
||||
|
||||
r = dhcp_identifier_set_iaid (ifindex, (guint8 *) mac_addr, mac_addr_len, &client_id.iaid);
|
||||
if (r < 0)
|
||||
return NULL;
|
||||
|
||||
r = dhcp_identifier_set_duid_en (&client_id.duid, &duid_len);
|
||||
if (r < 0)
|
||||
return NULL;
|
||||
|
||||
return g_bytes_new (&client_id,
|
||||
G_STRUCT_OFFSET (typeof (client_id), duid) + duid_len);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,10 +35,4 @@ struct _NMUuid *nm_sd_utils_id128_get_machine (struct _NMUuid *out_uuid);
|
|||
|
||||
/*****************************************************************************/
|
||||
|
||||
GBytes *nm_sd_utils_generate_default_dhcp_client_id (int ifindex,
|
||||
const guint8 *mac_addr,
|
||||
gsize mac_addr_len);
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
#endif /* __NM_SD_UTILS_H__ */
|
||||
|
|
|
|||
|
|
@ -1957,94 +1957,78 @@ test_machine_id_read (void)
|
|||
/*****************************************************************************/
|
||||
|
||||
static void
|
||||
test_nm_sd_utils_generate_default_dhcp_client_id (gconstpointer test_data)
|
||||
test_nm_utils_dhcp_client_id_systemd_node_specific (gconstpointer test_data)
|
||||
{
|
||||
const int TEST_IDX = GPOINTER_TO_INT (test_data);
|
||||
const guint8 HASH_KEY[16] = { 0x80, 0x11, 0x8c, 0xc2, 0xfe, 0x4a, 0x03, 0xee, 0x3e, 0xd6, 0x0c, 0x6f, 0x36, 0x39, 0x14, 0x09 };
|
||||
/* We run the test twice with two ifindexes.
|
||||
*
|
||||
* One is "1", which we expect to exist and having a name "lo".
|
||||
* The other is a random number, which we expect not to exist.
|
||||
*
|
||||
* Regardless of whether the ifindex actually exists, the tests are
|
||||
* supposed to pass. However, when our expectations are not met, we
|
||||
* silently miss test cases. */
|
||||
const int IFINDEX = GPOINTER_TO_INT (test_data)
|
||||
? 1
|
||||
: (int) (nmtst_get_rand_int () % 10000);
|
||||
const guint8 mac_addr[ETH_ALEN] = { 0x20, 0xaf, 0x51, 0x42, 0x29, 0x05 };
|
||||
const guint16 duid_type_en = htons (2);
|
||||
const guint32 systemd_pen = htonl (43793);
|
||||
guint32 iaid_mac;
|
||||
guint32 iaid_ifname;
|
||||
gs_unref_bytes GBytes *client_id = NULL;
|
||||
char ifname_buf[IFNAMSIZ];
|
||||
const char *ifname;
|
||||
gboolean has_ifindex;
|
||||
const struct {
|
||||
NMUuid machine_id;
|
||||
const char *ifname;
|
||||
guint64 ifname_hash_1;
|
||||
guint32 iaid_ifname;
|
||||
guint64 duid_id;
|
||||
} d_array[] = {
|
||||
[0] = {
|
||||
.machine_id = { 0xcb, 0xc2, 0x2e, 0x47, 0x41, 0x8e, 0x40, 0x2a, 0xa7, 0xb3, 0x0d, 0xea, 0x92, 0x83, 0x94, 0xef },
|
||||
.ifname = "lo",
|
||||
.ifname_hash_1 = 0x7297085c2b12c911llu,
|
||||
.iaid_ifname = htobe32 (0x5985c14du),
|
||||
.duid_id = htobe64 (0x3d769bb2c14d29e1u),
|
||||
},
|
||||
[1] = {
|
||||
.machine_id = { 0x11, 0x4e, 0xb4, 0xda, 0xd3, 0x22, 0x4a, 0xff, 0x9f, 0xc3, 0x30, 0x83, 0x38, 0xa0, 0xeb, 0xb7 },
|
||||
.ifname = "eth0",
|
||||
.ifname_hash_1 = 0x9e1cb083b54cd7b6llu,
|
||||
.iaid_ifname = htobe32 (0x2b506735u),
|
||||
.duid_id = htobe64 (0x551572e0f2a2a10fu),
|
||||
},
|
||||
};
|
||||
int i;
|
||||
typeof (d_array[0]) *d = &d_array[TEST_IDX];
|
||||
gint64 u64;
|
||||
const guint8 *cid;
|
||||
const NMUuid *machine_id;
|
||||
gint32 u32;
|
||||
|
||||
/* see whether IFINDEX exists. */
|
||||
if (if_indextoname (IFINDEX, ifname_buf)) {
|
||||
ifname = ifname_buf;
|
||||
has_ifindex = TRUE;
|
||||
} else {
|
||||
ifname = "lo";
|
||||
has_ifindex = FALSE;
|
||||
}
|
||||
/* the test already hard-codes the expected values iaid_ifname and duid_id
|
||||
* above. Still, redo the steps to derive them from the ifname/machine-id
|
||||
* and double check. */
|
||||
u64 = c_siphash_hash (HASH_KEY, (const guint8 *) d->ifname, strlen (d->ifname));
|
||||
g_assert_cmpint (u64, ==, d->ifname_hash_1);
|
||||
u32 = be32toh ((u64 & 0xffffffffu) ^ (u64 >> 32));
|
||||
g_assert_cmpint (u32, ==, d->iaid_ifname);
|
||||
|
||||
/* generate the iaid based on the ifname and assert for expected
|
||||
* values.
|
||||
*
|
||||
* We often expect that the interface name is "lo". Hence, assert
|
||||
* for the expected hash values explicitly.
|
||||
*
|
||||
* Note that the iaid generated by dhcp_identifier_set_iaid() is
|
||||
* in native endianness (https://github.com/systemd/systemd/pull/10614). */
|
||||
u64 = c_siphash_hash (HASH_KEY, (const guint8 *) ifname, strlen (ifname));
|
||||
if (nm_streq (ifname, "lo"))
|
||||
g_assert_cmpint (u64, ==, 0x7297085c2b12c911llu);
|
||||
iaid_ifname = bswap_32 ((u64 & 0xffffffffu) ^ (u64 >> 32));
|
||||
if (nm_streq (ifname, "lo"))
|
||||
g_assert_cmpint (iaid_ifname, ==, 0x4dc18559u);
|
||||
u64 = htole64 (c_siphash_hash (HASH_KEY, (const guint8 *) &d->machine_id, sizeof (d->machine_id)));
|
||||
g_assert_cmpint (u64, ==, d->duid_id);
|
||||
|
||||
/* generate the iaid based on the hard-coded MAC address */
|
||||
u64 = c_siphash_hash (HASH_KEY, mac_addr, sizeof (mac_addr));
|
||||
g_assert_cmpint (u64, ==, 0x1f3d1d8d15de49dcllu);
|
||||
iaid_mac = bswap_32 ((u64 & 0xffffffffu) ^ (u64 >> 32));
|
||||
g_assert_cmpint (iaid_mac, ==, 0x5154e30au);
|
||||
for (i = 0; i < 2; i++) {
|
||||
const gboolean legacy_unstable_byteorder = (i != 0);
|
||||
gs_unref_bytes GBytes *client_id = NULL;
|
||||
const guint8 *cid;
|
||||
guint32 iaid = d->iaid_ifname;
|
||||
|
||||
/* as it is, nm_sd_utils_generate_default_dhcp_client_id() resolves the ifname (based on the
|
||||
* ifindex) and loads /etc/machine-id. Maybe the code should be refactored, to accept
|
||||
* such external input as arguments (to ease testing).
|
||||
*
|
||||
* Instead, we just duplicate the steps here, which are don't internally by the
|
||||
* function. Hey, it's a test. Let's re-implement what the code does here. */
|
||||
client_id = nm_sd_utils_generate_default_dhcp_client_id (IFINDEX, mac_addr, sizeof (mac_addr));
|
||||
client_id = nm_utils_dhcp_client_id_systemd_node_specific_full (legacy_unstable_byteorder,
|
||||
(const guint8 *) d->ifname,
|
||||
strlen (d->ifname),
|
||||
(const guint8 *) &d->machine_id,
|
||||
sizeof (d->machine_id));
|
||||
|
||||
if (!client_id) {
|
||||
/* the only reason why this can fail, is because /etc/machine-id is invalid. */
|
||||
if (!g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS)) {
|
||||
g_test_skip ("no /etc/machine-id");
|
||||
return;
|
||||
g_assert (client_id);
|
||||
g_assert_cmpint (g_bytes_get_size (client_id), ==, 19);
|
||||
cid = g_bytes_get_data (client_id, NULL);
|
||||
g_assert_cmpint (cid[0], ==, 255);
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
if (legacy_unstable_byteorder) {
|
||||
/* on non-little endian, the legacy behavior is to have the bytes
|
||||
* swapped. */
|
||||
iaid = bswap_32 (iaid);
|
||||
}
|
||||
g_assert_not_reached ();
|
||||
#endif
|
||||
g_assert_cmpmem (&cid[1], 4, &iaid, sizeof (iaid));
|
||||
g_assert_cmpmem (&cid[5], 2, &duid_type_en, sizeof (duid_type_en));
|
||||
g_assert_cmpmem (&cid[7], 4, &systemd_pen, sizeof (systemd_pen));
|
||||
g_assert_cmpmem (&cid[11], 8, &d->duid_id, sizeof (d->duid_id));
|
||||
}
|
||||
|
||||
g_assert_cmpint (g_bytes_get_size (client_id), ==, 19);
|
||||
cid = g_bytes_get_data (client_id, NULL);
|
||||
|
||||
g_assert_cmpint (cid[0], ==, 255);
|
||||
if (has_ifindex)
|
||||
g_assert_cmpmem (&cid[1], 4, &iaid_ifname, sizeof (iaid_ifname));
|
||||
else
|
||||
g_assert_cmpmem (&cid[1], 4, &iaid_mac, sizeof (iaid_mac));
|
||||
g_assert_cmpmem (&cid[5], 2, &duid_type_en, sizeof (duid_type_en));
|
||||
g_assert_cmpmem (&cid[7], 4, &systemd_pen, sizeof (systemd_pen));
|
||||
|
||||
machine_id = nm_utils_machine_id_bin ();
|
||||
u64 = htole64 (c_siphash_hash (HASH_KEY, (const guint8 *) machine_id, sizeof (*machine_id)));
|
||||
g_assert_cmpmem (&cid[11], 8, &u64, sizeof (u64));
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
|
@ -2101,8 +2085,8 @@ main (int argc, char **argv)
|
|||
|
||||
g_test_add_func ("/general/test_dns_create_resolv_conf", test_dns_create_resolv_conf);
|
||||
|
||||
g_test_add_data_func ("/general/nm_sd_utils_generate_default_dhcp_client_id/lo", GINT_TO_POINTER (TRUE), test_nm_sd_utils_generate_default_dhcp_client_id);
|
||||
g_test_add_data_func ("/general/nm_sd_utils_generate_default_dhcp_client_id/rnd", GINT_TO_POINTER (FALSE), test_nm_sd_utils_generate_default_dhcp_client_id);
|
||||
g_test_add_data_func ("/general/nm_utils_dhcp_client_id_systemd_node_specific/0", GINT_TO_POINTER (0), test_nm_utils_dhcp_client_id_systemd_node_specific);
|
||||
g_test_add_data_func ("/general/nm_utils_dhcp_client_id_systemd_node_specific/1", GINT_TO_POINTER (1), test_nm_utils_dhcp_client_id_systemd_node_specific);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue