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