summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2018-10-26 17:32:59 +0200
committerThomas Haller <thaller@redhat.com>2018-11-13 19:09:33 +0100
commit187d356198182749c8d2f1ea240f505ccfcfe6da (patch)
tree9898e7e4db4b5565affbe97a52bbc38199a6019c
parent50121ee0286f00986406cd1b654c32dbed800946 (diff)
downloadNetworkManager-187d356198182749c8d2f1ea240f505ccfcfe6da.tar.gz
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.
-rw-r--r--src/systemd/nm-sd-utils.c46
-rw-r--r--src/systemd/nm-sd-utils.h9
-rw-r--r--src/tests/test-general.c98
3 files changed, 152 insertions, 1 deletions
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 51377c530d..9882734c5f 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-general.c
@@ -22,6 +22,8 @@
#include <string.h>
#include <errno.h>
+#include <net/if.h>
+#include <byteswap.h>
/* need math.h for isinf() and INFINITY. No need to link with -lm */
#include <math.h>
@@ -1954,6 +1956,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
@@ -2006,6 +2101,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 ();
}