From 0f5854ac4133aceb20b90ced63350c050525b1c5 Mon Sep 17 00:00:00 2001
From: Jan Vcelak <jan.vcelak@nic.cz>
Date: Thu, 24 Sep 2015 20:36:33 +0200
Subject: [PATCH] tests: add EDNS Client Subnet new API

---
 tests/.gitignore              |   1 +
 tests/Makefile.am             |   1 +
 tests/libknot/test_edns.c     | 113 --------------
 tests/libknot/test_edns_ecs.c | 270 ++++++++++++++++++++++++++++++++++
 4 files changed, 272 insertions(+), 113 deletions(-)
 create mode 100644 tests/libknot/test_edns_ecs.c

diff --git a/tests/.gitignore b/tests/.gitignore
index 9eea8c4893..93aa897f9b 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -24,6 +24,7 @@
 /libknot/test_descriptor
 /libknot/test_dname
 /libknot/test_edns
+/libknot/test_edns_ecs
 /libknot/test_lookup
 /libknot/test_pkt
 /libknot/test_rdata
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7c55ff71c1..9c6f594212 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -34,6 +34,7 @@ check_PROGRAMS += \
 	libknot/test_descriptor		\
 	libknot/test_dname		\
 	libknot/test_edns		\
+	libknot/test_edns_ecs		\
 	libknot/test_lookup		\
 	libknot/test_pkt		\
 	libknot/test_rdata		\
diff --git a/tests/libknot/test_edns.c b/tests/libknot/test_edns.c
index f9221bac37..2abd9ec2e1 100644
--- a/tests/libknot/test_edns.c
+++ b/tests/libknot/test_edns.c
@@ -714,118 +714,6 @@ static void test_unique(void)
 	knot_rrset_clear(&opt_rr, NULL);
 }
 
-static void test_client_subnet(void)
-{
-	int ret;
-	knot_addr_family_t family;
-	uint8_t  addr[IPV6_PREFIXLEN / 8] = { 0 };
-	uint16_t addr_len = sizeof(addr);
-	uint8_t  src_mask, dst_mask;
-	uint8_t  data[KNOT_EDNS_MAX_OPTION_CLIENT_SUBNET] = { 0 };
-	uint16_t data_len = sizeof(data);
-
-	/* Create IPv4 subnet - src mask 32  */
-	family = KNOT_ADDR_FAMILY_IPV4;
-	data_len = sizeof(data);
-	addr_len = 4;
-	memcpy(&addr, "\xFF\xFF\xFF\xFF", 4);
-	src_mask = 32;
-	dst_mask = 32;
-	ret = knot_edns_client_subnet_create(family, addr, addr_len, src_mask,
-                                             dst_mask, data, &data_len);
-	ok(ret == KNOT_EOK, "EDNS-client-subnet: create (src mask 32)");
-	ok(data_len == 8, "EDNS-client-subnet: create (cmp out length)");
-	ok(memcmp(data, "\x00\x01\x20\x20\xFF\xFF\xFF\xFF", 8) == 0,
-           "EDNS-client-subnet: create (cmp out)");
-
-	/* Create IPv4 subnet - src mask 31  */
-	data_len = sizeof(data);
-	src_mask = 31;
-	ret = knot_edns_client_subnet_create(family, addr, addr_len, src_mask,
-                                             dst_mask, data, &data_len);
-	ok(ret == KNOT_EOK, "EDNS-client-subnet: create (src mask 31)");
-	ok(data_len == 8, "EDNS-client-subnet: create (cmp out length)");
-	ok(memcmp(data, "\x00\x01\x1F\x20\xFF\xFF\xFF\xFE", 8) == 0,
-           "EDNS-client-subnet: create (cmp out)");
-
-	/* Create IPv4 subnet - src mask 7  */
-	data_len = sizeof(data);
-	src_mask = 7;
-	ret = knot_edns_client_subnet_create(family, addr, addr_len, src_mask,
-                                             dst_mask, data, &data_len);
-	ok(ret == KNOT_EOK, "EDNS-client-subnet: create (src mask 7)");
-	ok(data_len == 5, "EDNS-client-subnet: create (cmp out length)");
-	ok(memcmp(data, "\x00\x01\x07\x20\xFE", 5) == 0,
-           "EDNS-client-subnet: create (cmp out)");
-
-	/* Create IPv4 subnet - src mask 0  */
-	data_len = sizeof(data);
-	src_mask = 0;
-	ret = knot_edns_client_subnet_create(family, addr, addr_len, src_mask,
-                                             dst_mask, data, &data_len);
-	ok(ret == KNOT_EOK, "EDNS-client-subnet: create (src mask 0)");
-	ok(data_len == 4, "EDNS-client-subnet: create (cmp out length)");
-	ok(memcmp(data, "\x00\x01\x00\x20", 0) == 0,
-           "EDNS-client-subnet: create (cmp out)");
-
-	/* Create IPv6 subnet - src mask 128  */
-	data_len = sizeof(data);
-	family = KNOT_ADDR_FAMILY_IPV6;
-	addr_len = 16;
-	memcpy(&addr, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 16);
-	src_mask = 128;
-	dst_mask = 128;
-	ret = knot_edns_client_subnet_create(family, addr, addr_len, src_mask,
-                                             dst_mask, data, &data_len);
-	ok(ret == KNOT_EOK, "EDNS-client-subnet: create (src mask 128)");
-	ok(data_len == 20, "EDNS-client-subnet: create (cmp out length)");
-	ok(memcmp(data, "\x00\x02\x80\x80\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
-           20) == 0, "EDNS-client-subnet: create (cmp out)");
-
-	/* Create IPv6 subnet - src mask 1  */
-	data_len = sizeof(data);
-	family = KNOT_ADDR_FAMILY_IPV6;
-	addr_len = 1;
-	memcpy(&addr, "\xFF", 1);
-	src_mask = 1;
-	ret = knot_edns_client_subnet_create(family, addr, addr_len, src_mask,
-                                             dst_mask, data, &data_len);
-	ok(ret == KNOT_EOK, "EDNS-client-subnet: create (src mask 1)");
-	ok(data_len == 5, "EDNS-client-subnet: create (cmp out length)");
-	ok(memcmp(data, "\x00\x02\x01\x80\x80",
-           5) == 0, "EDNS-client-subnet: create (cmp out)");
-
-	/* Parse IPv4 subnet - src mask 31  */
-	memcpy(&data, "\x00\x01\x1F\x20\xFF\xFF\xFF\xFE", 8);
-	data_len = 8;
-	addr_len = sizeof(addr);
-	ret = knot_edns_client_subnet_parse(data, data_len, &family, addr,
-                                            &addr_len, &src_mask, &dst_mask);
-	ok(ret == KNOT_EOK, "EDNS-client-subnet: parse (src mask 31)");
-	ok(family == KNOT_ADDR_FAMILY_IPV4,
-           "EDNS-client-subnet: parse (cmp family)");
-	ok(src_mask == 31, "EDNS-client-subnet: parse (cmp src mask)");
-	ok(dst_mask == 32, "EDNS-client-subnet: parse (cmp dst mask)");
-	ok(addr_len == 4, "EDNS-client-subnet: parse (cmp addr length)");
-	ok(memcmp(addr, "\xFF\xFF\xFF\xFE", 4) == 0,
-           "EDNS-client-subnet: parse (cmp addr)");
-
-	/* Parse IPv6 subnet - src mask 1  */
-	memcpy(&data, "\x00\x02\x01\x80\x80", 5);
-	data_len = 5;
-	addr_len = sizeof(addr);
-	ret = knot_edns_client_subnet_parse(data, data_len, &family, addr,
-                                            &addr_len, &src_mask, &dst_mask);
-	ok(ret == KNOT_EOK, "EDNS-client-subnet: parse (src mask 1)");
-	ok(family == KNOT_ADDR_FAMILY_IPV6,
-           "EDNS-client-subnet: parse (cmp family)");
-	ok(src_mask == 1, "EDNS-client-subnet: parse (cmp src mask)");
-	ok(dst_mask == 128, "EDNS-client-subnet: parse (cmp dst mask)");
-	ok(addr_len == 1, "EDNS-client-subnet: parse (cmp addr length)");
-	ok(memcmp(addr, "\x80", 1) == 0,
-           "EDNS-client-subnet: parse (cmp addr)");
-}
-
 static void test_alignment(void)
 {
 	int ret;
@@ -861,7 +749,6 @@ int main(int argc, char *argv[])
 	test_getters(&opt_rr);
 	test_remove();
 	test_unique();
-	test_client_subnet();
 	test_alignment();
 
 	knot_rrset_clear(&opt_rr, NULL);
diff --git a/tests/libknot/test_edns_ecs.c b/tests/libknot/test_edns_ecs.c
new file mode 100644
index 0000000000..27e6c9af6c
--- /dev/null
+++ b/tests/libknot/test_edns_ecs.c
@@ -0,0 +1,270 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <tap/basic.h>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netdb.h>
+
+#include "contrib/sockaddr.h"
+#include "libknot/errcode.h"
+#include "libknot/rrtype/opt.h"
+
+#define GARBAGE_BYTE 0xdb
+
+static void test_size(void)
+{
+	struct test {
+		const char *msg;
+		size_t expected;
+		knot_edns_client_subnet_t ecs;
+	};
+
+	static struct test const TESTS[] = {
+		// invalid
+		{ "zero family",             0, { 0 } },
+		{ "zero family & source",    0, { 0,  1 } },
+		{ "unknown family",          0, { 42, 0 } },
+		{ "unknown family & source", 0, { 42, 1 } },
+		// IPv4 bit ops
+		{ "IPv4, zero source",         4, { 1 } },
+		{ "IPv4, 7 bits in last byte", 7, { 1, 23 } },
+		{ "IPv4, 8 bits in last byte", 7, { 1, 24 } },
+		{ "IPv4, 1 bit in last byte",  8, { 1, 25 } },
+		// IPv6 bit ops
+		{ "IPv6, zero source",         4,  { 2 } },
+		{ "IPv6, 7 bits in last byte", 19, { 2, 113 } },
+		{ "IPv6, 8 bits in last byte", 19, { 2, 120 } },
+		{ "IPv6, 1 bit in last byte",  20, { 2, 121 } },
+		// sources
+		{ "IPv4, source < max", 8, { 1, 31 } },
+		{ "IPv4, source = max", 8, { 1, 32 } },
+		{ "IPv4, source > max", 0, { 1, 33 } },
+		// scopes
+		{ "IPv6, scope < source", 12, { 2, 64, 48 } },
+		{ "IPv6, scope = source", 12, { 2, 64, 64 } },
+		{ "IPv6, scope > source", 0,  { 2, 64, 65 } },
+		{ NULL }
+	};
+
+	is_int(0, knot_edns_client_subnet_size(NULL), "%s: null", __func__);
+
+	for (struct test const *t = TESTS; t->msg != NULL; t++) {
+		int r = knot_edns_client_subnet_size(&t->ecs);
+		is_int(t->expected, r, "%s: %s", __func__, t->msg);
+	}
+}
+
+struct test_io {
+	const char *msg;
+	int expected;
+	size_t option_len;
+	const char *option;
+	knot_edns_client_subnet_t ecs;
+};
+
+static void test_write(void)
+{
+	static struct test_io const TESTS[] = {
+		// invalid
+		{ "unset family",   KNOT_EINVAL, 0, NULL, { 0 } },
+		{ "invalid family", KNOT_EINVAL, 0, NULL, { 3 } },
+		{ "small buffer",   KNOT_ESPACE, 4, NULL, { 1, 1 } },
+		// IPv4 prefix
+		{ "IPv4, zero source",   KNOT_EOK,    4, "\x00\x01\x00\x00",                 { 1 } },
+		{ "IPv4, 7 bits in LSB", KNOT_EOK,    6, "\x00\x01\x0f\x00\xff\xfe",         { 1, 15,  0, "\xff\xff\xff\xff" } },
+		{ "IPv4, 8 bits in LSB", KNOT_EOK,    6, "\x00\x01\x10\x00\xff\xff",         { 1, 16,  0, "\xff\xff\xff\xff" } },
+		{ "IPv4, 1 bit in LSB",  KNOT_EOK,    7, "\x00\x01\x11\x00\xff\xff\x80",     { 1, 17,  0, "\xff\xff\xff\xff" } },
+		{ "IPv4, source = max",  KNOT_EOK,    8, "\x00\x01\x20\x00\xaa\xbb\xcc\xdd", { 1, 32,  0, "\xaa\xbb\xcc\xdd" } },
+		{ "IPv4, source > max",  KNOT_EINVAL, 0, NULL,                               { 2, 129 } },
+		// IPv6 scope
+		{ "IPv6, scope < source", KNOT_EOK,    6, "\x00\x02\x10\x0e\xff\xff", { 2, 16, 14, "\xff\xff\xff\xff" } },
+		{ "IPv6, scope = source", KNOT_EOK,    6, "\x00\x02\x08\x08\xff",     { 2, 8,  8,  "\xff\xff\xff\xff" } },
+		{ "IPv6, scope > source", KNOT_EINVAL, 0, NULL,                       { 1, 8,  9 } },
+		// other
+		{ "larger buffer", KNOT_EOK, 7, "\x00\x01\x10\x0e\xff\xff\x00", { 1, 16, 14, "\xff\xff\xff\xff" } },
+		{ NULL }
+	};
+
+	for (struct test_io const *t = TESTS; t->msg != NULL; t++) {
+		uint8_t option[64];
+		assert(sizeof(option) >= t->option_len);
+		memset(option, GARBAGE_BYTE, sizeof(option));
+
+		int r = knot_edns_client_subnet_write(option, t->option_len, &t->ecs);
+		ok(r == t->expected &&
+		   (t->expected != KNOT_EOK || memcmp(option, t->option, t->option_len) == 0),
+		   "%s: %s", __func__, t->msg);
+	}
+}
+
+static void test_parse(void)
+{
+	static struct test_io const TESTS[] = {
+		// invalid
+		{ "null",              KNOT_EINVAL, 0, NULL },
+		{ "empty buffer",      KNOT_EMALF,  0, "" },
+		{ "incomplete header", KNOT_EMALF,  3, "\x00\x01\x00" },
+		{ "incomplete source", KNOT_EMALF,  5, "\x00\x0a\x00\x00\xff\xff" },
+		{ "zero family",       KNOT_EMALF,  4, "\x00\x00\x00\x00" },
+		{ "unknown family",    KNOT_EMALF,  4, "\x00\x03\x00\x00" },
+		// IPv4 prefix
+		{ "IPv4, zero source",   KNOT_EOK,   4, "\x00\x01\x00\x00",                 { 1 } },
+		{ "IPv4, 7 bits in LSB", KNOT_EOK,   6, "\x00\x01\x0f\x00\xff\xfe",         { 1, 15, 0, "\xff\xfe" } },
+		{ "IPv4, 9 bits in LSB", KNOT_EOK,   6, "\x00\x01\x10\x00\xff\xff",         { 1, 16, 0, "\xff\xff" } },
+		{ "IPv4, 1 bit in LSB",  KNOT_EOK,   7, "\x00\x01\x11\x00\xff\xff\x80",     { 1, 17, 0, "\xff\xff\x80" } },
+		{ "IPv4, source = max",  KNOT_EOK,   8, "\x00\x01\x20\x00\xaa\xbb\xcc\xdd", { 1, 32, 0, "\xaa\xbb\xcc\xdd" } },
+		{ "IPv4, dirty source",  KNOT_EOK,   8, "\x00\x01\x0b\x00\xff\xff\xff\xff", { 1, 11, 0, "\xff\xe0" } },
+		{ "IPv4, source > max",  KNOT_EMALF, 9, "\x00\x01\x21\x00\xaa\xbb\xcc\xdd\xee" },
+		// IPv6 scope
+		{ "IPv6 scope < source", KNOT_EOK,   5, "\x00\x02\x07\x05\xff", { 2, 7, 5, "\xfe" } },
+		{ "IPv6 scope = source", KNOT_EOK,   5, "\x00\x02\x06\x06\xff", { 2, 6, 6, "\xfc" } },
+		{ "IPv6 scope > source", KNOT_EMALF, 5, "\x00\x02\x06\x07\xff" },
+		// extra buffer size
+		{ "extra space", KNOT_EOK, 6, "\x00\x01\x00\x00\xff\x00", { 1 } },
+		{ "extra space", KNOT_EOK, 6, "\x00\x01\x01\x00\xff\x00", { 1, 1, 0, "\x80" } },
+		{ NULL }
+	};
+
+	for (struct test_io const *t = TESTS; t->msg != NULL; t++) {
+		knot_edns_client_subnet_t ecs = { 0 };
+		memset(&ecs, GARBAGE_BYTE, sizeof(ecs));
+
+		int r = knot_edns_client_subnet_parse(&ecs, (uint8_t *)t->option, t->option_len);
+		ok(r == t->expected &&
+		   (t->expected != KNOT_EOK || memcmp(&ecs, &t->ecs, sizeof(ecs)) == 0),
+		   "%s: %s", __func__, t->msg);
+	}
+}
+
+static struct sockaddr_storage addr_init(const char *addr)
+{
+	struct sockaddr_storage sa = { 0 };
+
+	struct addrinfo hints = { .ai_flags = AI_NUMERICHOST };
+	struct addrinfo *info = NULL;
+	int r = getaddrinfo(addr, NULL, &hints, &info);
+	assert(r == 0);
+	memcpy(&sa, info->ai_addr, info->ai_addrlen);
+	freeaddrinfo(info);
+
+	return sa;
+}
+
+static void test_set_address(void)
+{
+	int r;
+	knot_edns_client_subnet_t ecs = { 0 };
+	struct sockaddr_storage ss = { 0 };
+
+	r = knot_edns_client_subnet_set_addr(NULL, &ss);
+	is_int(KNOT_EINVAL, r, "%s: missing ECS", __func__);
+
+	r = knot_edns_client_subnet_set_addr(&ecs, NULL);
+	is_int(KNOT_EINVAL, r, "%s: missing address", __func__);
+
+	memset(&ecs, GARBAGE_BYTE, sizeof(ecs));
+	ss = addr_init("198.51.100.42");
+	assert(ss.ss_family == AF_INET);
+	const uint8_t raw4[4] = { 198, 51, 100, 42 };
+
+	r = knot_edns_client_subnet_set_addr(&ecs, &ss);
+	ok(r == KNOT_EOK &&
+	   ecs.family == 1 && ecs.source_len == 32 && ecs.scope_len == 0 &&
+	   memcmp(ecs.address, raw4, sizeof(raw4)) == 0,
+	   "%s: IPv4", __func__);
+
+	memset(&ecs, GARBAGE_BYTE, sizeof(ecs));
+	ss = addr_init("2001:db8::dead:beef");
+	assert(ss.ss_family == AF_INET6);
+	const uint8_t raw6[16] = "\x20\x01\x0d\xb8\x00\x00\x00\x00"
+	                         "\x00\x00\x00\x00\xde\xad\xbe\xef";
+	r = knot_edns_client_subnet_set_addr(&ecs, &ss);
+	ok(r == KNOT_EOK &&
+	   ecs.family == 2 && ecs.source_len == 128 && ecs.scope_len == 0 &&
+	   memcmp(ecs.address, raw6, sizeof(raw6)) == 0,
+	   "%s: IPv6", __func__);
+
+	const struct sockaddr_storage ss_unix = { .ss_family = AF_UNIX };
+	r = knot_edns_client_subnet_set_addr(&ecs, &ss_unix);
+	is_int(KNOT_ENOTSUP, r, "%s: UNIX not supported", __func__);
+}
+
+static bool sockaddr_eq(const struct sockaddr_storage *a, const struct sockaddr_storage *b)
+{
+	return sockaddr_cmp((struct sockaddr *)a, (struct sockaddr *)b) == 0;
+}
+
+static void test_get_address(void)
+{
+	struct test {
+		const char *msg;
+		int expected;
+		const char *addr_str;
+		knot_edns_client_subnet_t ecs;
+	};
+
+	static struct test const TESTS[] = {
+		// invalid
+		{ "unset family",   KNOT_ENOTSUP, NULL, { 0 } },
+		{ "unknown family", KNOT_ENOTSUP, NULL, { 3 } },
+		// zero source
+		{ "IPv4, any", KNOT_EOK, "0.0.0.0", { 1 } },
+		{ "IPv6, any", KNOT_EOK, "::0"    , { 2 } },
+		// IPv4
+		{ "IPv4, 7 bits in LSB", KNOT_EOK, "198.50.0.0",   { 1, 15, 0, "\xc6\x33\xff\xff" } },
+		{ "IPv4, 8 bits in LSB", KNOT_EOK, "198.51.0.0",   { 1, 16, 0, "\xc6\x33\xff\xff" } },
+		{ "IPv4, 1 bit in LSB",  KNOT_EOK, "198.51.128.0", { 1, 17, 0, "\xc6\x33\xff\xff" } },
+		{ "IPv4, source = max",  KNOT_EOK, "198.51.128.1", { 1, 32, 0, "\xc6\x33\x80\x01" } },
+		// IPv6
+		{ "IPv6, 7 bits in LSB", KNOT_EOK, "2001:db8:200::", { 2, 39,  0, "\x20\x01\x0d\xb8\x03\xff" } },
+		{ "IPv6, 8 bits in LSB", KNOT_EOK, "2001:db8:100::", { 2, 40,  0, "\x20\x01\x0d\xb8\x01\xff" } },
+		{ "IPv6, 1 bit in LSB",  KNOT_EOK, "2001:db8:180::", { 2, 41,  0, "\x20\x01\x0d\xb8\x01\xff" } },
+		{ "IPv6, source = max",  KNOT_EOK, "2001:db8::1",    { 2, 128, 0, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" } },
+		{ NULL }
+	};
+
+	for (struct test const *t = TESTS; t->msg != NULL; t++) {
+		struct sockaddr_storage result = { 0 };
+		int r = knot_edns_client_subnet_get_addr(&result, &t->ecs);
+		bool valid = false;
+
+		if (t->expected == KNOT_EOK) {
+			struct sockaddr_storage addr = addr_init(t->addr_str);
+			assert(addr.ss_family != AF_UNSPEC);
+			valid = (r == t->expected && sockaddr_eq(&result, &addr));
+		} else {
+			valid = (r == t->expected);
+		}
+
+		ok(valid, "%s: %s", __func__, t->msg);
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	plan_lazy();
+
+	test_size();
+	test_write();
+	test_parse();
+	test_set_address();
+	test_get_address();
+
+	return 0;
+}
-- 
GitLab