diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/mslookup/Makefile.am | 20 | ||||
-rw-r--r-- | tests/mslookup/mdns_test.c | 602 | ||||
-rw-r--r-- | tests/mslookup/mdns_test.err | 336 | ||||
-rw-r--r-- | tests/mslookup/mslookup_client_mdns_test.c | 255 | ||||
-rw-r--r-- | tests/mslookup/mslookup_client_mdns_test.err | 14 | ||||
-rw-r--r-- | tests/testsuite.at | 12 |
6 files changed, 1239 insertions, 0 deletions
diff --git a/tests/mslookup/Makefile.am b/tests/mslookup/Makefile.am index 71602a3..ebf2add 100644 --- a/tests/mslookup/Makefile.am +++ b/tests/mslookup/Makefile.am @@ -16,11 +16,15 @@ AM_LDFLAGS = \ $(NULL) EXTRA_DIST = \ + mdns_test.err \ + mslookup_client_mdns_test.err \ mslookup_client_test.err \ mslookup_test.err \ $(NULL) check_PROGRAMS = \ + mdns_test \ + mslookup_client_mdns_test \ mslookup_client_test \ mslookup_test \ $(NULL) @@ -41,6 +45,22 @@ mslookup_client_test_LDADD = \ $(LIBOSMOGSM_LIBS) \ $(NULL) +mslookup_client_mdns_test_SOURCES = \ + mslookup_client_mdns_test.c \ + $(NULL) +mslookup_client_mdns_test_LDADD = \ + $(top_builddir)/src/mslookup/libosmo-mslookup.la \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) + +mdns_test_SOURCES = \ + mdns_test.c \ + $(NULL) +mdns_test_LDADD = \ + $(top_builddir)/src/mslookup/libosmo-mslookup.la \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) + .PHONY: update_exp update_exp: for i in $(check_PROGRAMS); do \ diff --git a/tests/mslookup/mdns_test.c b/tests/mslookup/mdns_test.c new file mode 100644 index 0000000..8a60e85 --- /dev/null +++ b/tests/mslookup/mdns_test.c @@ -0,0 +1,602 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <osmocom/core/application.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/mslookup/mdns_rfc.h> +#include <osmocom/mslookup/mdns_msg.h> + +struct qname_enc_dec_test { + const char *domain; + const char *qname; + size_t qname_max_len; /* default: strlen(qname) + 1 */ +}; + +static const struct qname_enc_dec_test qname_enc_dec_test_data[] = { + { + /* OK: typical mslookup domain */ + .domain = "hlr.1234567.imsi", + .qname = "\x03" "hlr" "\x07" "1234567" "\x04" "imsi", + }, + { + /* Wrong format: double dot */ + .domain = "hlr..imsi", + .qname = NULL, + }, + { + /* Wrong format: double dot */ + .domain = "hlr", + .qname = "\x03hlr\0\x03imsi", + }, + { + /* Wrong format: dot at end */ + .domain = "hlr.", + .qname = NULL, + }, + { + /* Wrong format: dot at start */ + .domain = ".hlr", + .qname = NULL, + }, + { + /* Wrong format: empty */ + .domain = "", + .qname = NULL, + }, + { + /* OK: maximum length */ + .domain = + "123456789." "123456789." "123456789." "123456789." "123456789." + "123456789." "123456789." "123456789." "123456789." "123456789." + "123456789." "123456789." "123456789." "123456789." "123456789." + "123456789." "123456789." "123456789." "123456789." "123456789." + "123456789." "123456789." "123456789." "123456789." "123456789." + "12345" + , + .qname = + "\t123456789\t123456789\t123456789\t123456789\t123456789" + "\t123456789\t123456789\t123456789\t123456789\t123456789" + "\t123456789\t123456789\t123456789\t123456789\t123456789" + "\t123456789\t123456789\t123456789\t123456789\t123456789" + "\t123456789\t123456789\t123456789\t123456789\t123456789" + "\x05" "12345" + }, + { + /* Error: too long domain */ + .domain = + "123456789." "123456789." "123456789." "123456789." "123456789." + "123456789." "123456789." "123456789." "123456789." "123456789." + "123456789." "123456789." "123456789." "123456789." "123456789." + "123456789." "123456789." "123456789." "123456789." "123456789." + "123456789." "123456789." "123456789." "123456789." "123456789." + "12345toolong" + , + .qname = NULL, + }, + { + /* Error: too long qname */ + .domain = NULL, + .qname = + "\t123456789\t123456789\t123456789\t123456789\t123456789" + "\t123456789\t123456789\t123456789\t123456789\t123456789" + "\t123456789\t123456789\t123456789\t123456789\t123456789" + "\t123456789\t123456789\t123456789\t123456789\t123456789" + "\t123456789\t123456789\t123456789\t123456789\t123456789" + "\t123456789\t123456789\t123456789\t123456789\t123456789" + }, + { + /* Error: wrong token length in qname */ + .domain = NULL, + .qname = "\x03" "hlr" "\x07" "1234567" "\x05" "imsi", + }, + { + /* Error: wrong token length in qname */ + .domain = NULL, + .qname = "\x02" "hlr" "\x07" "1234567" "\x04" "imsi", + }, + { + /* Wrong format: token length at end of qname */ + .domain = NULL, + .qname = "\x03hlr\x03", + }, + { + /* Error: overflow in label length */ + .domain = NULL, + .qname = "\x03" "hlr" "\x07" "1234567" "\x04" "imsi", + .qname_max_len = 17, + }, +}; + +void test_enc_dec_rfc_qname(void *ctx) +{ + char quote_buf[300]; + int i; + + fprintf(stderr, "-- %s --\n", __func__); + + for (i = 0; i < ARRAY_SIZE(qname_enc_dec_test_data); i++) { + const struct qname_enc_dec_test *t = &qname_enc_dec_test_data[i]; + char *res; + + if (t->domain) { + fprintf(stderr, "domain: %s\n", osmo_quote_str_buf2(quote_buf, sizeof(quote_buf), t->domain, -1)); + fprintf(stderr, "exp: %s\n", osmo_quote_str_buf2(quote_buf, sizeof(quote_buf), t->qname, -1)); + res = osmo_mdns_rfc_qname_encode(ctx, t->domain); + fprintf(stderr, "res: %s\n", osmo_quote_str_buf2(quote_buf, sizeof(quote_buf), res, -1)); + if (t->qname == res || (t->qname && res && strcmp(t->qname, res) == 0)) + fprintf(stderr, "=> OK\n"); + else + fprintf(stderr, "=> ERROR\n"); + if (res) + talloc_free(res); + fprintf(stderr, "\n"); + } + + if (t->qname) { + size_t qname_max_len = t->qname_max_len; + if (qname_max_len) + fprintf(stderr, "qname_max_len: %lu\n", qname_max_len); + else + qname_max_len = strlen(t->qname) + 1; + + fprintf(stderr, "qname: %s\n", osmo_quote_str_buf2(quote_buf, sizeof(quote_buf), t->qname, -1)); + fprintf(stderr, "exp: %s\n", osmo_quote_str_buf2(quote_buf, sizeof(quote_buf), t->domain, -1)); + res = osmo_mdns_rfc_qname_decode(ctx, t->qname, qname_max_len); + fprintf(stderr, "res: %s\n", osmo_quote_str_buf2(quote_buf, sizeof(quote_buf), res, -1)); + if (t->domain == res || (t->domain && res && strcmp(t->domain, res) == 0)) + fprintf(stderr, "=> OK\n"); + else + fprintf(stderr, "=> ERROR\n"); + if (res) + talloc_free(res); + fprintf(stderr, "\n"); + } + } +} + +#define PRINT_HDR(hdr, name) \ + fprintf(stderr, "header %s:\n" \ + ".id = %i\n" \ + ".qr = %i\n" \ + ".opcode = %x\n" \ + ".aa = %i\n" \ + ".tc = %i\n" \ + ".rd = %i\n" \ + ".ra = %i\n" \ + ".z = %x\n" \ + ".rcode = %x\n" \ + ".qdcount = %u\n" \ + ".ancount = %u\n" \ + ".nscount = %u\n" \ + ".arcount = %u\n", \ + name, hdr.id, hdr.qr, hdr.opcode, hdr.aa, hdr.tc, hdr.rd, hdr.ra, hdr.z, hdr.rcode, hdr.qdcount, \ + hdr.ancount, hdr.nscount, hdr.arcount) + +static const struct osmo_mdns_rfc_header header_enc_dec_test_data[] = { + { + /* Typical use case for mslookup */ + .id = 1337, + .qdcount = 1, + }, + { + /* Fill out everything */ + .id = 42, + .qr = 1, + .opcode = 0x02, + .aa = 1, + .tc = 1, + .rd = 1, + .ra = 1, + .z = 0x02, + .rcode = 0x03, + .qdcount = 1234, + .ancount = 1111, + .nscount = 2222, + .arcount = 3333, + }, +}; + +void test_enc_dec_rfc_header() +{ + int i; + + fprintf(stderr, "-- %s --\n", __func__); + for (i = 0; i< ARRAY_SIZE(header_enc_dec_test_data); i++) { + const struct osmo_mdns_rfc_header in = header_enc_dec_test_data[i]; + struct osmo_mdns_rfc_header out = {0}; + struct msgb *msg = msgb_alloc(4096, "dns_test"); + + PRINT_HDR(in, "in"); + osmo_mdns_rfc_header_encode(msg, &in); + fprintf(stderr, "encoded: %s\n", osmo_hexdump(msgb_data(msg), msgb_length(msg))); + assert(osmo_mdns_rfc_header_decode(msgb_data(msg), msgb_length(msg), &out) == 0); + PRINT_HDR(out, "out"); + + fprintf(stderr, "in (hexdump): %s\n", osmo_hexdump((unsigned char *)&in, sizeof(in))); + fprintf(stderr, "out (hexdump): %s\n", osmo_hexdump((unsigned char *)&out, sizeof(out))); + assert(memcmp(&in, &out, sizeof(in)) == 0); + + fprintf(stderr, "=> OK\n\n"); + msgb_free(msg); + } +} + +void test_enc_dec_rfc_header_einval() +{ + struct osmo_mdns_rfc_header out = {0}; + struct msgb *msg = msgb_alloc(4096, "dns_test"); + fprintf(stderr, "-- %s --\n", __func__); + + assert(osmo_mdns_rfc_header_decode(msgb_data(msg), 11, &out) == -EINVAL); + fprintf(stderr, "=> OK\n\n"); + + msgb_free(msg); +} + +#define PRINT_QST(qst, name) \ + fprintf(stderr, "question %s:\n" \ + ".domain = %s\n" \ + ".qtype = %i\n" \ + ".qclass = %i\n", \ + name, (qst)->domain, (qst)->qtype, (qst)->qclass) + +static const struct osmo_mdns_rfc_question question_enc_dec_test_data[] = { + { + .domain = "hlr.1234567.imsi", + .qtype = OSMO_MDNS_RFC_RECORD_TYPE_ALL, + .qclass = OSMO_MDNS_RFC_CLASS_IN, + }, + { + .domain = "hlr.1234567.imsi", + .qtype = OSMO_MDNS_RFC_RECORD_TYPE_A, + .qclass = OSMO_MDNS_RFC_CLASS_ALL, + }, + { + .domain = "hlr.1234567.imsi", + .qtype = OSMO_MDNS_RFC_RECORD_TYPE_AAAA, + .qclass = OSMO_MDNS_RFC_CLASS_ALL, + }, +}; + +void test_enc_dec_rfc_question(void *ctx) +{ + int i; + + fprintf(stderr, "-- %s --\n", __func__); + for (i = 0; i< ARRAY_SIZE(question_enc_dec_test_data); i++) { + const struct osmo_mdns_rfc_question in = question_enc_dec_test_data[i]; + struct osmo_mdns_rfc_question *out; + struct msgb *msg = msgb_alloc(4096, "dns_test"); + + PRINT_QST(&in, "in"); + assert(osmo_mdns_rfc_question_encode(ctx, msg, &in) == 0); + fprintf(stderr, "encoded: %s\n", osmo_hexdump(msgb_data(msg), msgb_length(msg))); + out = osmo_mdns_rfc_question_decode(ctx, msgb_data(msg), msgb_length(msg)); + assert(out); + PRINT_QST(out, "out"); + + if (strcmp(in.domain, out->domain) != 0) + fprintf(stderr, "=> ERROR: domain does not match\n"); + else if (in.qtype != out->qtype) + fprintf(stderr, "=> ERROR: qtype does not match\n"); + else if (in.qclass != out->qclass) + fprintf(stderr, "=> ERROR: qclass does not match\n"); + else + fprintf(stderr, "=> OK\n"); + + fprintf(stderr, "\n"); + msgb_free(msg); + talloc_free(out); + } +} + +void test_enc_dec_rfc_question_null(void *ctx) +{ + uint8_t data[5] = {0}; + + fprintf(stderr, "-- %s --\n", __func__); + assert(osmo_mdns_rfc_question_decode(ctx, data, sizeof(data)) == NULL); + fprintf(stderr, "=> OK\n\n"); +} + +#define PRINT_REC(rec, name) \ + fprintf(stderr, "question %s:\n" \ + ".domain = %s\n" \ + ".type = %i\n" \ + ".class = %i\n" \ + ".ttl = %i\n" \ + ".rdlength = %i\n" \ + ".rdata = %s\n", \ + name, (rec)->domain, (rec)->type, (rec)->class, (rec)->ttl, (rec)->rdlength, \ + osmo_quote_str((char *)(rec)->rdata, (rec)->rdlength)) + +static const struct osmo_mdns_rfc_record record_enc_dec_test_data[] = { + { + .domain = "hlr.1234567.imsi", + .type = OSMO_MDNS_RFC_RECORD_TYPE_A, + .class = OSMO_MDNS_RFC_CLASS_IN, + .ttl = 1234, + .rdlength = 9, + .rdata = (uint8_t *)"10.42.2.1", + }, +}; + +void test_enc_dec_rfc_record(void *ctx) +{ + int i; + + fprintf(stderr, "-- %s --\n", __func__); + for (i=0; i< ARRAY_SIZE(record_enc_dec_test_data); i++) { + const struct osmo_mdns_rfc_record in = record_enc_dec_test_data[i]; + struct osmo_mdns_rfc_record *out; + struct msgb *msg = msgb_alloc(4096, "dns_test"); + size_t record_len; + + PRINT_REC(&in, "in"); + assert(osmo_mdns_rfc_record_encode(ctx, msg, &in) == 0); + fprintf(stderr, "encoded: %s\n", osmo_hexdump(msgb_data(msg), msgb_length(msg))); + out = osmo_mdns_rfc_record_decode(ctx, msgb_data(msg), msgb_length(msg), &record_len); + fprintf(stderr, "record_len: %lu\n", record_len); + assert(out); + PRINT_REC(out, "out"); + + if (strcmp(in.domain, out->domain) != 0) + fprintf(stderr, "=> ERROR: domain does not match\n"); + else if (in.type != out->type) + fprintf(stderr, "=> ERROR: type does not match\n"); + else if (in.class != out->class) + fprintf(stderr, "=> ERROR: class does not match\n"); + else if (in.ttl != out->ttl) + fprintf(stderr, "=> ERROR: ttl does not match\n"); + else if (in.rdlength != out->rdlength) + fprintf(stderr, "=> ERROR: rdlength does not match\n"); + else if (memcmp(in.rdata, out->rdata, in.rdlength) != 0) + fprintf(stderr, "=> ERROR: rdata does not match\n"); + else + fprintf(stderr, "=> OK\n"); + + fprintf(stderr, "\n"); + msgb_free(msg); + talloc_free(out); + } +} + +static uint8_t ip_v4_n[] = {23, 42, 47, 11}; +static uint8_t ip_v6_n[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00}; + + +enum test_records { + RECORD_NONE, + RECORD_A, + RECORD_AAAA, + RECORD_TXT_AGE, + RECORD_TXT_PORT_444, + RECORD_TXT_PORT_666, + RECORD_TXT_INVALID_KEY, + RECORD_TXT_INVALID_NO_KEY_VALUE, + RECORD_INVALID, +}; +struct result_from_answer_test { + const char *desc; + const enum test_records records[5]; + bool error; + const struct osmo_mslookup_result res; +}; + +static void test_result_from_answer(void *ctx) +{ + void *print_ctx = talloc_named_const(ctx, 0, __func__); + struct osmo_sockaddr_str test_host_v4 = {.af = AF_INET, .port=444, .ip = "23.42.47.11"}; + struct osmo_sockaddr_str test_host_v6 = {.af = AF_INET6, .port=666, + .ip = "1122:3344:5566:7788:99aa:bbcc:ddee:ff00"}; + struct osmo_mslookup_result test_result_v4 = {.rc = OSMO_MSLOOKUP_RC_RESULT, .age = 3, + .host_v4 = test_host_v4}; + struct osmo_mslookup_result test_result_v6 = {.rc = OSMO_MSLOOKUP_RC_RESULT, .age = 3, + .host_v6 = test_host_v6}; + struct osmo_mslookup_result test_result_v4_v6 = {.rc = OSMO_MSLOOKUP_RC_RESULT, .age = 3, + .host_v4 = test_host_v4, .host_v6 = test_host_v6}; + struct result_from_answer_test result_from_answer_data[] = { + { + .desc = "IPv4", + .records = {RECORD_TXT_AGE, RECORD_A, RECORD_TXT_PORT_444}, + .res = test_result_v4 + }, + { + .desc = "IPv6", + .records = {RECORD_TXT_AGE, RECORD_AAAA, RECORD_TXT_PORT_666}, + .res = test_result_v6 + }, + { + .desc = "IPv4 + IPv6", + .records = {RECORD_TXT_AGE, RECORD_A, RECORD_TXT_PORT_444, RECORD_AAAA, RECORD_TXT_PORT_666}, + .res = test_result_v4_v6 + }, + { + .desc = "A twice", + .records = {RECORD_TXT_AGE, RECORD_A, RECORD_TXT_PORT_444, RECORD_A}, + .error = true + }, + { + .desc = "AAAA twice", + .records = {RECORD_TXT_AGE, RECORD_AAAA, RECORD_TXT_PORT_444, RECORD_AAAA}, + .error = true + }, + { + .desc = "invalid TXT: no key/value pair", + .records = {RECORD_TXT_AGE, RECORD_AAAA, RECORD_TXT_INVALID_NO_KEY_VALUE}, + .error = true + }, + { + .desc = "age twice", + .records = {RECORD_TXT_AGE, RECORD_TXT_AGE}, + .error = true + }, + { + .desc = "port as first record", + .records = {RECORD_TXT_PORT_444}, + .error = true + }, + { + .desc = "port without previous ip record", + .records = {RECORD_TXT_AGE, RECORD_TXT_PORT_444}, + .error = true + }, + { + .desc = "invalid TXT: invalid key", + .records = {RECORD_TXT_AGE, RECORD_AAAA, RECORD_TXT_INVALID_KEY}, + .error = true + }, + { + .desc = "unexpected record type", + .records = {RECORD_TXT_AGE, RECORD_INVALID}, + .error = true + }, + { + .desc = "missing record: age", + .records = {RECORD_A, RECORD_TXT_PORT_444}, + .error = true + }, + { + .desc = "missing record: port for ipv4", + .records = {RECORD_TXT_AGE, RECORD_A}, + .error = true + }, + { + .desc = "missing record: port for ipv4 #2", + .records = {RECORD_TXT_AGE, RECORD_AAAA, RECORD_TXT_PORT_666, RECORD_A}, + .error = true + }, + }; + int i = 0; + int j = 0; + + fprintf(stderr, "-- %s --\n", __func__); + for (i = 0; i < ARRAY_SIZE(result_from_answer_data); i++) { + struct result_from_answer_test *t = &result_from_answer_data[i]; + struct osmo_mdns_msg_answer ans = {0}; + struct osmo_mslookup_result res = {0}; + void *ctx_test = talloc_named_const(ctx, 0, t->desc); + bool is_error; + + fprintf(stderr, "---\n"); + fprintf(stderr, "test: %s\n", t->desc); + fprintf(stderr, "error: %s\n", t->error ? "true" : "false"); + fprintf(stderr, "records:\n"); + /* Build records list */ + INIT_LLIST_HEAD(&ans.records); + for (j = 0; j < ARRAY_SIZE(t->records); j++) { + struct osmo_mdns_record *rec = NULL; + + switch (t->records[j]) { + case RECORD_NONE: + break; + case RECORD_A: + fprintf(stderr, "- A 42.42.42.42\n"); + rec = talloc_zero(ctx_test, struct osmo_mdns_record); + rec->type = OSMO_MDNS_RFC_RECORD_TYPE_A; + rec->data = ip_v4_n; + rec->length = sizeof(ip_v4_n); + break; + case RECORD_AAAA: + fprintf(stderr, "- AAAA 1122:3344:5566:7788:99aa:bbcc:ddee:ff00\n"); + rec = talloc_zero(ctx_test, struct osmo_mdns_record); + rec->type = OSMO_MDNS_RFC_RECORD_TYPE_AAAA; + rec->data = ip_v6_n; + rec->length = sizeof(ip_v6_n); + break; + case RECORD_TXT_AGE: + fprintf(stderr, "- TXT age=3\n"); + rec = osmo_mdns_record_txt_keyval_encode(ctx_test, "age", "3"); + break; + case RECORD_TXT_PORT_444: + fprintf(stderr, "- TXT port=444\n"); + rec = osmo_mdns_record_txt_keyval_encode(ctx_test, "port", "444"); + break; + case RECORD_TXT_PORT_666: + fprintf(stderr, "- TXT port=666\n"); + rec = osmo_mdns_record_txt_keyval_encode(ctx_test, "port", "666"); + break; + case RECORD_TXT_INVALID_KEY: + fprintf(stderr, "- TXT hello=world\n"); + rec = osmo_mdns_record_txt_keyval_encode(ctx_test, "hello", "world"); + break; + case RECORD_TXT_INVALID_NO_KEY_VALUE: + fprintf(stderr, "- TXT 12345\n"); + rec = osmo_mdns_record_txt_keyval_encode(ctx_test, "12", "45"); + rec->data[3] = '3'; + break; + case RECORD_INVALID: + fprintf(stderr, "- (invalid)\n"); + rec = talloc_zero(ctx, struct osmo_mdns_record); + rec->type = OSMO_MDNS_RFC_RECORD_TYPE_UNKNOWN; + break; + } + + if (rec) + llist_add_tail(&rec->list, &ans.records); + } + + /* Verify output */ + is_error = (osmo_mdns_result_from_answer(&res, &ans) != 0); + if (t->error != is_error) { + fprintf(stderr, "got %s\n", is_error ? "error" : "no error"); + OSMO_ASSERT(false); + } + if (!t->error) { + fprintf(stderr, "exp: %s\n", osmo_mslookup_result_name_c(print_ctx, NULL, &t->res)); + fprintf(stderr, "res: %s\n", osmo_mslookup_result_name_c(print_ctx, NULL, &res)); + OSMO_ASSERT(t->res.rc == res.rc); + OSMO_ASSERT(!osmo_sockaddr_str_cmp(&t->res.host_v4, &res.host_v4)); + OSMO_ASSERT(!osmo_sockaddr_str_cmp(&t->res.host_v6, &res.host_v6)); + OSMO_ASSERT(t->res.age == res.age); + OSMO_ASSERT(t->res.last == res.last); + } + + talloc_free(ctx_test); + fprintf(stderr, "=> OK\n"); + } +} + +int main() +{ + void *ctx = talloc_named_const(NULL, 0, "main"); + osmo_init_logging2(ctx, NULL); + + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_level(osmo_stderr_target, 1); + log_set_print_category(osmo_stderr_target, 1); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + + test_enc_dec_rfc_qname(ctx); + test_enc_dec_rfc_header(); + test_enc_dec_rfc_header_einval(); + test_enc_dec_rfc_question(ctx); + test_enc_dec_rfc_question_null(ctx); + test_enc_dec_rfc_record(ctx); + + test_result_from_answer(ctx); + + return 0; +} diff --git a/tests/mslookup/mdns_test.err b/tests/mslookup/mdns_test.err new file mode 100644 index 0000000..51e5afe --- /dev/null +++ b/tests/mslookup/mdns_test.err @@ -0,0 +1,336 @@ +-- test_enc_dec_rfc_qname -- +domain: "hlr.1234567.imsi" +exp: "\3hlr\a1234567\4imsi" +res: "\3hlr\a1234567\4imsi" +=> OK + +qname: "\3hlr\a1234567\4imsi" +exp: "hlr.1234567.imsi" +res: "hlr.1234567.imsi" +=> OK + +domain: "hlr..imsi" +exp: NULL +res: NULL +=> OK + +domain: "hlr" +exp: "\3hlr" +res: "\3hlr" +=> OK + +qname: "\3hlr" +exp: "hlr" +res: "hlr" +=> OK + +domain: "hlr." +exp: NULL +res: NULL +=> OK + +domain: ".hlr" +exp: NULL +res: NULL +=> OK + +domain: "" +exp: NULL +res: NULL +=> OK + +domain: "123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.12345" +exp: "\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\512345" +res: "\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\512345" +=> OK + +qname: "\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\512345" +exp: "123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.12345" +res: "123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.12345" +=> OK + +domain: "123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.12345toolong" +exp: NULL +res: NULL +=> OK + +qname: "\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\t123456789\ +exp: NULL +res: NULL +=> OK + +qname: "\3hlr\a1234567\5imsi" +exp: NULL +res: NULL +=> OK + +qname: "\2hlr\a1234567\4imsi" +exp: NULL +res: NULL +=> OK + +qname: "\3hlr\3" +exp: NULL +res: NULL +=> OK + +qname_max_len: 17 +qname: "\3hlr\a1234567\4imsi" +exp: NULL +res: NULL +=> OK + +-- test_enc_dec_rfc_header -- +header in: +.id = 1337 +.qr = 0 +.opcode = 0 +.aa = 0 +.tc = 0 +.rd = 0 +.ra = 0 +.z = 0 +.rcode = 0 +.qdcount = 1 +.ancount = 0 +.nscount = 0 +.arcount = 0 +encoded: 05 39 00 00 00 01 00 00 00 00 00 00 +header out: +.id = 1337 +.qr = 0 +.opcode = 0 +.aa = 0 +.tc = 0 +.rd = 0 +.ra = 0 +.z = 0 +.rcode = 0 +.qdcount = 1 +.ancount = 0 +.nscount = 0 +.arcount = 0 +in (hexdump): 39 05 00 00 01 00 00 00 00 00 00 00 +out (hexdump): 39 05 00 00 01 00 00 00 00 00 00 00 +=> OK + +header in: +.id = 42 +.qr = 1 +.opcode = 2 +.aa = 1 +.tc = 1 +.rd = 1 +.ra = 1 +.z = 2 +.rcode = 3 +.qdcount = 1234 +.ancount = 1111 +.nscount = 2222 +.arcount = 3333 +encoded: 00 2a 97 a3 04 d2 04 57 08 ae 0d 05 +header out: +.id = 42 +.qr = 1 +.opcode = 2 +.aa = 1 +.tc = 1 +.rd = 1 +.ra = 1 +.z = 2 +.rcode = 3 +.qdcount = 1234 +.ancount = 1111 +.nscount = 2222 +.arcount = 3333 +in (hexdump): 2a 00 97 a3 d2 04 57 04 ae 08 05 0d +out (hexdump): 2a 00 97 a3 d2 04 57 04 ae 08 05 0d +=> OK + +-- test_enc_dec_rfc_header_einval -- +=> OK + +-- test_enc_dec_rfc_question -- +question in: +.domain = hlr.1234567.imsi +.qtype = 255 +.qclass = 1 +encoded: 03 68 6c 72 07 31 32 33 34 35 36 37 04 69 6d 73 69 00 00 ff 00 01 +question out: +.domain = hlr.1234567.imsi +.qtype = 255 +.qclass = 1 +=> OK + +question in: +.domain = hlr.1234567.imsi +.qtype = 1 +.qclass = 255 +encoded: 03 68 6c 72 07 31 32 33 34 35 36 37 04 69 6d 73 69 00 00 01 00 ff +question out: +.domain = hlr.1234567.imsi +.qtype = 1 +.qclass = 255 +=> OK + +question in: +.domain = hlr.1234567.imsi +.qtype = 28 +.qclass = 255 +encoded: 03 68 6c 72 07 31 32 33 34 35 36 37 04 69 6d 73 69 00 00 1c 00 ff +question out: +.domain = hlr.1234567.imsi +.qtype = 28 +.qclass = 255 +=> OK + +-- test_enc_dec_rfc_question_null -- +=> OK + +-- test_enc_dec_rfc_record -- +question in: +.domain = hlr.1234567.imsi +.type = 1 +.class = 1 +.ttl = 1234 +.rdlength = 9 +.rdata = "10.42.2.1" +encoded: 03 68 6c 72 07 31 32 33 34 35 36 37 04 69 6d 73 69 00 00 01 00 01 00 00 04 d2 00 09 31 30 2e 34 32 2e 32 2e 31 +record_len: 37 +question out: +.domain = hlr.1234567.imsi +.type = 1 +.class = 1 +.ttl = 1234 +.rdlength = 9 +.rdata = "10.42.2.1" +=> OK + +-- test_result_from_answer -- +--- +test: IPv4 +error: false +records: +- TXT age=3 +- A 42.42.42.42 +- TXT port=444 +exp: -> ipv4: 23.42.47.11:444 (age=3) (not-last) +res: -> ipv4: 23.42.47.11:444 (age=3) (not-last) +=> OK +--- +test: IPv6 +error: false +records: +- TXT age=3 +- AAAA 1122:3344:5566:7788:99aa:bbcc:ddee:ff00 +- TXT port=666 +exp: -> ipv6: [1122:3344:5566:7788:99aa:bbcc:ddee:ff00]:666 (age=3) (not-last) +res: -> ipv6: [1122:3344:5566:7788:99aa:bbcc:ddee:ff00]:666 (age=3) (not-last) +=> OK +--- +test: IPv4 + IPv6 +error: false +records: +- TXT age=3 +- A 42.42.42.42 +- TXT port=444 +- AAAA 1122:3344:5566:7788:99aa:bbcc:ddee:ff00 +- TXT port=666 +exp: -> ipv4: 23.42.47.11:444 -> ipv6: [1122:3344:5566:7788:99aa:bbcc:ddee:ff00]:666 (age=3) (not-last) +res: -> ipv4: 23.42.47.11:444 -> ipv6: [1122:3344:5566:7788:99aa:bbcc:ddee:ff00]:666 (age=3) (not-last) +=> OK +--- +test: A twice +error: true +records: +- TXT age=3 +- A 42.42.42.42 +- TXT port=444 +- A 42.42.42.42 +DLGLOBAL ERROR 'A' record found twice in mDNS answer +=> OK +--- +test: AAAA twice +error: true +records: +- TXT age=3 +- AAAA 1122:3344:5566:7788:99aa:bbcc:ddee:ff00 +- TXT port=444 +- AAAA 1122:3344:5566:7788:99aa:bbcc:ddee:ff00 +DLGLOBAL ERROR 'AAAA' record found twice in mDNS answer +=> OK +--- +test: invalid TXT: no key/value pair +error: true +records: +- TXT age=3 +- AAAA 1122:3344:5566:7788:99aa:bbcc:ddee:ff00 +- TXT 12345 +DLGLOBAL ERROR failed to decode txt record +=> OK +--- +test: age twice +error: true +records: +- TXT age=3 +- TXT age=3 +DLGLOBAL ERROR duplicate 'TXT' record for 'age' +=> OK +--- +test: port as first record +error: true +records: +- TXT port=444 +DLGLOBAL ERROR 'TXT' record for 'port' without previous 'A' or 'AAAA' record +=> OK +--- +test: port without previous ip record +error: true +records: +- TXT age=3 +- TXT port=444 +DLGLOBAL ERROR 'TXT' record for 'port' without previous 'A' or 'AAAA' record +=> OK +--- +test: invalid TXT: invalid key +error: true +records: +- TXT age=3 +- AAAA 1122:3344:5566:7788:99aa:bbcc:ddee:ff00 +- TXT hello=world +DLGLOBAL ERROR unexpected key 'hello' in TXT record +=> OK +--- +test: unexpected record type +error: true +records: +- TXT age=3 +- (invalid) +DLGLOBAL ERROR unexpected record type +=> OK +--- +test: missing record: age +error: true +records: +- A 42.42.42.42 +- TXT port=444 +DLGLOBAL ERROR missing resource records in mDNS answer +=> OK +--- +test: missing record: port for ipv4 +error: true +records: +- TXT age=3 +- A 42.42.42.42 +DLGLOBAL ERROR missing resource records in mDNS answer +=> OK +--- +test: missing record: port for ipv4 #2 +error: true +records: +- TXT age=3 +- AAAA 1122:3344:5566:7788:99aa:bbcc:ddee:ff00 +- TXT port=666 +- A 42.42.42.42 +DLGLOBAL ERROR missing resource records in mDNS answer +=> OK diff --git a/tests/mslookup/mslookup_client_mdns_test.c b/tests/mslookup/mslookup_client_mdns_test.c new file mode 100644 index 0000000..f33ef98 --- /dev/null +++ b/tests/mslookup/mslookup_client_mdns_test.c @@ -0,0 +1,255 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <assert.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <osmocom/core/select.h> +#include <osmocom/core/application.h> +#include <osmocom/hlr/logging.h> +#include <osmocom/mslookup/mslookup.h> +#include <osmocom/mslookup/mslookup_client.h> +#include <osmocom/mslookup/mslookup_client_mdns.h> +#include <osmocom/mslookup/mdns.h> +#include <osmocom/mslookup/mdns_sock.h> + +void *ctx = NULL; + +#define TEST_IP OSMO_MSLOOKUP_MDNS_IP4 +#define TEST_PORT OSMO_MSLOOKUP_MDNS_PORT +#define TEST_DOMAIN_SUFFIX "mslookup_client_mdns_test.dgsm.osmocom.org" + +/* + * Test server (emulates the mDNS server in OsmoHLR) and client + */ +struct osmo_mdns_sock *server_mc; + + +static void server_reply(struct osmo_mslookup_query *query, uint16_t packet_id) +{ + struct osmo_mslookup_result result = {0}; + struct msgb *msg; + + result.rc = OSMO_MSLOOKUP_RC_RESULT; + result.age = 3; + osmo_sockaddr_str_from_str(&result.host_v4, "42.42.42.42", 444); + osmo_sockaddr_str_from_str(&result.host_v6, "1122:3344:5566:7788:99aa:bbcc:ddee:ff00", 666); + + msg = osmo_mdns_result_encode(ctx, packet_id, query, &result, TEST_DOMAIN_SUFFIX); + OSMO_ASSERT(msg); + OSMO_ASSERT(osmo_mdns_sock_send(server_mc, msg) == 0); +} + +static int server_recv(struct osmo_fd *osmo_fd, unsigned int what) +{ + int n; + uint8_t buffer[1024]; + uint16_t packet_id; + struct osmo_mslookup_query *query; + + fprintf(stderr, "%s\n", __func__); + + /* Parse the message and print it */ + n = read(osmo_fd->fd, buffer, sizeof(buffer)); + OSMO_ASSERT(n >= 0); + + query = osmo_mdns_query_decode(ctx, buffer, n, &packet_id, TEST_DOMAIN_SUFFIX); + if (!query) + return -1; /* server receiving own answer is expected */ + + fprintf(stderr, "received request\n"); + server_reply(query, packet_id); + talloc_free(query); + return n; +} + +static void server_init() +{ + fprintf(stderr, "%s\n", __func__); + server_mc = osmo_mdns_sock_init(ctx, TEST_IP, TEST_PORT, server_recv, NULL, 0); + OSMO_ASSERT(server_mc); +} + +static void server_stop() +{ + fprintf(stderr, "%s\n", __func__); + OSMO_ASSERT(server_mc); + osmo_mdns_sock_cleanup(server_mc); + server_mc = NULL; +} + +struct osmo_mslookup_client* client; +struct osmo_mslookup_client_method* client_method; + +static void client_init() +{ + fprintf(stderr, "%s\n", __func__); + client = osmo_mslookup_client_new(ctx); + OSMO_ASSERT(client); + client_method = osmo_mslookup_client_add_mdns(client, TEST_IP, TEST_PORT, 1337, TEST_DOMAIN_SUFFIX); + OSMO_ASSERT(client_method); +} + +static void client_recv(struct osmo_mslookup_client *client, uint32_t request_handle, + const struct osmo_mslookup_query *query, const struct osmo_mslookup_result *result) +{ + char buf[256]; + fprintf(stderr, "%s\n", __func__); + fprintf(stderr, "client_recv(): %s\n", osmo_mslookup_result_name_b(buf, sizeof(buf), query, result)); + + osmo_mslookup_client_request_cancel(client, request_handle); +} + +static void client_query() +{ + struct osmo_mslookup_id id = {.type = OSMO_MSLOOKUP_ID_IMSI, + .imsi = "123456789012345"}; + const struct osmo_mslookup_query query = { + .service = "gsup.hlr", + .id = id, + }; + struct osmo_mslookup_query_handling handling = { + .result_timeout_milliseconds = 2000, + .result_cb = client_recv, + }; + + fprintf(stderr, "%s\n", __func__); + osmo_mslookup_client_request(client, &query, &handling); +} + +static void client_stop() +{ + fprintf(stderr, "%s\n", __func__); + osmo_mslookup_client_free(client); + client = NULL; +} +const struct timeval fake_time_start_time = { 0, 0 }; + +#define fake_time_passes(secs, usecs) do \ +{ \ + struct timeval diff; \ + osmo_gettimeofday_override_add(secs, usecs); \ + osmo_clock_override_add(CLOCK_MONOTONIC, secs, usecs * 1000); \ + timersub(&osmo_gettimeofday_override_time, &fake_time_start_time, &diff); \ + LOGP(DMSLOOKUP, LOGL_DEBUG, "Total time passed: %d.%06d s\n", \ + (int)diff.tv_sec, (int)diff.tv_usec); \ + osmo_timers_prepare(); \ + osmo_timers_update(); \ +} while (0) + +static void fake_time_start() +{ + struct timespec *clock_override; + + osmo_gettimeofday_override_time = fake_time_start_time; + osmo_gettimeofday_override = true; + clock_override = osmo_clock_override_gettimespec(CLOCK_MONOTONIC); + OSMO_ASSERT(clock_override); + clock_override->tv_sec = fake_time_start_time.tv_sec; + clock_override->tv_nsec = fake_time_start_time.tv_usec * 1000; + osmo_clock_override_enable(CLOCK_MONOTONIC, true); + fake_time_passes(0, 0); +} +static void test_server_client() +{ + fprintf(stderr, "-- %s --\n", __func__); + server_init(); + client_init(); + client_query(); + + /* Let the server receive the query and indirectly call server_recv(). As side effect of using the same IP and + * port, the client will also receive its own question. The client will dismiss its own question, as it is just + * looking for answers. */ + OSMO_ASSERT(osmo_select_main_ctx(1) == 1); + + /* Let the mslookup client receive the answer (also same side effect as above). It does not call the callback + * (client_recv()) just yet, because it is waiting for the best result within two seconds. */ + OSMO_ASSERT(osmo_select_main_ctx(1) == 1); + + /* Time flies by, client_recv() gets called. */ + fake_time_passes(5, 0); + + server_stop(); + client_stop(); +} + +bool is_multicast_enabled() +{ + bool ret = true; + struct addrinfo *ai; + int sock; + struct addrinfo hints = {0}; + struct ip_mreq multicast_req = {0}; + in_addr_t iface = INADDR_ANY; + + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = (AI_PASSIVE | AI_NUMERICHOST); + assert(getaddrinfo("239.192.23.42", "4266", &hints, &ai) == 0); + + sock = socket(ai->ai_family, ai->ai_socktype, 0); + assert(sock != -1); + assert(setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char*)&iface, sizeof(iface)) != -1); + + memcpy(&multicast_req.imr_multiaddr, &((struct sockaddr_in*)(ai->ai_addr))->sin_addr, + sizeof(multicast_req.imr_multiaddr)); + multicast_req.imr_interface.s_addr = htonl(INADDR_ANY); + + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&multicast_req, sizeof(multicast_req)) == -1) + ret = false; + + freeaddrinfo(ai); + return ret; +} + +/* + * Run all tests + */ +int main() +{ + if (!is_multicast_enabled()) { + fprintf(stderr, "WARNING: multicast is disabled, skipping the test! (OS#4361)"); + return 77; + } + + talloc_enable_null_tracking(); + ctx = talloc_named_const(NULL, 0, "main"); + osmo_init_logging2(ctx, NULL); + + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_level(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 0); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + log_set_category_filter(osmo_stderr_target, DMSLOOKUP, true, LOGL_DEBUG); + + fake_time_start(); + + test_server_client(); + + log_fini(); + + OSMO_ASSERT(talloc_total_blocks(ctx) == 1); + talloc_free(ctx); + OSMO_ASSERT(talloc_total_blocks(NULL) == 1); + talloc_disable_null_tracking(); + + return 0; +} diff --git a/tests/mslookup/mslookup_client_mdns_test.err b/tests/mslookup/mslookup_client_mdns_test.err new file mode 100644 index 0000000..b4ea269 --- /dev/null +++ b/tests/mslookup/mslookup_client_mdns_test.err @@ -0,0 +1,14 @@ +Total time passed: 0.000000 s +-- test_server_client -- +server_init +client_init +client_query +sending mDNS query: gsup.hlr.123456789012345.imsi +server_recv +received request +server_recv +client_recv +client_recv(): gsup.hlr.123456789012345.imsi -> ipv4: 42.42.42.42:444 -> ipv6: [1122:3344:5566:7788:99aa:bbcc:ddee:ff00]:666 (age=3) (not-last) +Total time passed: 5.000000 s +server_stop +client_stop diff --git a/tests/testsuite.at b/tests/testsuite.at index 39df7aa..827e9f8 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -40,6 +40,12 @@ cat $abs_srcdir/db_upgrade/db_upgrade_test.err > experr AT_CHECK([$abs_srcdir/db_upgrade/db_upgrade_test.sh $abs_srcdir/db_upgrade $abs_builddir/db_upgrade], [], [expout], [experr]) AT_CLEANUP +AT_SETUP([mdns]) +AT_KEYWORDS([mdns]) +cat $abs_srcdir/mslookup/mdns_test.err > experr +AT_CHECK([$abs_top_builddir/tests/mslookup/mdns_test], [0], [ignore], [experr]) +AT_CLEANUP + AT_SETUP([mslookup]) AT_KEYWORDS([mslookup]) cat $abs_srcdir/mslookup/mslookup_test.err > experr @@ -51,3 +57,9 @@ AT_KEYWORDS([mslookup_client]) cat $abs_srcdir/mslookup/mslookup_client_test.err > experr AT_CHECK([$abs_top_builddir/tests/mslookup/mslookup_client_test], [0], [ignore], [experr]) AT_CLEANUP + +AT_SETUP([mslookup_client_mdns]) +AT_KEYWORDS([mslookup_client_mdns]) +cat $abs_srcdir/mslookup/mslookup_client_mdns_test.err > experr +AT_CHECK([$abs_top_builddir/tests/mslookup/mslookup_client_mdns_test], [0], [ignore], [experr]) +AT_CLEANUP |