From 13bf09fbd9ba7cc4f050f68b523e35258dc43ba4 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 26 Oct 2018 17:32:59 +0200 Subject: [PATCH] dhcp: test systemd's default DHCP client identifier generation Internal DHCP client generates a default client ID. For one, we should ensure that this algorithm does not change without us noticing, for example, when upgrading systemd code. Add a test, that the generation algorithm works as we expect. Also note, that the generation algorithm uses siphash24(). That means, siphash24() implementation also must not change in the future, to ensure the client ID doesn't change. As we patch systemd sources to use shared/c-siphash, this is not obviously the case. Luckily c-siphash and systemd's siphash24 do agree, so all is good. The test is here to ensure that. Also, previously the generation algorithm is not exposed as a function, sd_dhcp_client will just generate a client-id when it needs it. However, later we want to know (and set) the client id before starting DHCP and not leave it unspecified to an implementation detail. This patch only adds a unit-test for the existing DHCP client ID generation to have something for comparison. In the next commit this will change further. (cherry picked from commit 187d356198182749c8d2f1ea240f505ccfcfe6da) --- src/systemd/nm-sd-utils.c | 46 ++++++++++++++++++ src/systemd/nm-sd-utils.h | 9 +++- src/tests/test-general.c | 98 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 1 deletion(-) 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 (); }