diff --git a/src/systemd/nm-sd-utils.c b/src/systemd/nm-sd-utils.c index c6c4c12328..12741e680e 100644 --- a/src/systemd/nm-sd-utils.c +++ b/src/systemd/nm-sd-utils.c @@ -26,6 +26,7 @@ #include "path-util.h" #include "sd-id128.h" +#include "dhcp-identifier.h" /*****************************************************************************/ @@ -59,3 +60,48 @@ 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); +} diff --git a/src/systemd/nm-sd-utils.h b/src/systemd/nm-sd-utils.h index fb41f9fe57..55c5a590fc 100644 --- a/src/systemd/nm-sd-utils.h +++ b/src/systemd/nm-sd-utils.h @@ -33,5 +33,12 @@ struct _NMUuid; struct _NMUuid *nm_sd_utils_id128_get_machine (struct _NMUuid *out_uuid); -#endif /* __NM_SD_UTILS_H__ */ +/*****************************************************************************/ +GBytes *nm_sd_utils_generate_default_dhcp_client_id (int ifindex, + const guint8 *mac_addr, + gsize mac_addr_len); + +/*****************************************************************************/ + +#endif /* __NM_SD_UTILS_H__ */ diff --git a/src/tests/test-general.c b/src/tests/test-general.c index 3eaf475a84..a3ba7c1184 100644 --- a/src/tests/test-general.c +++ b/src/tests/test-general.c @@ -22,6 +22,8 @@ #include #include +#include +#include /* need math.h for isinf() and INFINITY. No need to link with -lm */ #include @@ -1935,6 +1937,99 @@ test_machine_id_read (void) /*****************************************************************************/ +static void +test_nm_sd_utils_generate_default_dhcp_client_id (gconstpointer 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; + gint64 u64; + const guint8 *cid; + const NMUuid *machine_id; + + /* see whether IFINDEX exists. */ + if (if_indextoname (IFINDEX, ifname_buf)) { + ifname = ifname_buf; + has_ifindex = TRUE; + } else { + ifname = "lo"; + has_ifindex = FALSE; + } + + /* 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); + + /* 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); + + /* 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)); + + 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_not_reached (); + } + + 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)); +} + +/*****************************************************************************/ + NMTST_DEFINE (); int @@ -1985,6 +2080,9 @@ 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); + return g_test_run (); }