/* This software was written by Dirk Engling It is considered beerware. Prost. Skol. Cheers or whatever. $id$ */ /* System */ #include #include /* Opentracker */ #include "ot_ip.h" #include "test.h" static char text[64]; static const char *formatted(const char *ip) { text[ip_format(text, ip)] = 0; return text; } /* ------------------------------------------------------------------ IPv4 */ /* ------------------------------------------------------- RFC 5952 output */ static void case_format_rfc5952(void) { test_case("4.1 leading zeros in a group are suppressed"); CHECK_STR(formatted("\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"), "2001:db8::1"); test_case("4.2.1 the longest run of zero groups becomes ::"); CHECK_STR(formatted("\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01"), "2001:db8::2:1"); test_case("4.2.2 a single zero group is never shortened"); CHECK_STR(formatted("\x20\x01\x0d\xb8\x00\x00\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01"), "2001:db8:0:1:1:1:1:1"); test_case("4.2.1 a longer run wins over an earlier shorter one"); CHECK_STR(formatted("\x20\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01"), "2001:0:0:1::1"); test_case("4.2.3 of two runs of equal length the first is shortened"); CHECK_STR(formatted("\x20\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x01"), "2001::1:0:0:1:1"); test_case("4.3 hexadecimal digits are lower case"); CHECK_STR(formatted("\xab\xcd\xef\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"), "abcd:ef01::1"); test_case("the unspecified and loopback addresses"); CHECK_STR(formatted("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), "::"); CHECK_STR(formatted("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"), "::1"); test_case("a full address with no zero group"); CHECK_STR(formatted("\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88"), "1111:2222:3333:4444:5555:6666:7777:8888"); } static void case_format_trailing_zero_group(void) { /* libowfat's formatter looked two bytes past the address to decide this one, so a peer address ending in a zero group came out as "::" whenever the bytes that happened to follow were not themselves zero. In ot_stats.c those bytes are the peer's port. */ const char ip[] = "\x20\x01\x0d\xb8\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x00"; test_case("a single trailing zero group is written out, not shortened"); CHECK_STR(formatted(ip), "2001:db8:1:2:3:4:5:0"); test_case("and nothing beyond the sixteen bytes is consulted"); { char padded[20]; memcpy(padded, ip, 16); memset(padded + 16, 0xff, 4); /* a port and flags, as a peer record has */ CHECK_STR(formatted(padded), "2001:db8:1:2:3:4:5:0"); memset(padded + 16, 0x00, 4); CHECK_STR(formatted(padded), "2001:db8:1:2:3:4:5:0"); } } static void case_v4mapped(void) { const char mapped[] = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x0a\x00\x00\x01"; const char unmapped[] = "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"; test_case("a v4 mapped address is recognised"); CHECK(ip_is_v4mapped(mapped)); CHECK_INT(ip_is_v4mapped(unmapped), 0); /* The tracker has only ever shown the plain quad, so that is the one form the formatter produces; "::ffff:10.0.0.1" is not written anywhere */ test_case("a mapped address is written as the plain dotted quad"); CHECK_STR(formatted(mapped), "10.0.0.1"); CHECK_STR(formatted("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00"), "0.0.0.0"); CHECK_STR(formatted("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff"), "255.255.255.255"); test_case("anything else follows RFC 5952"); CHECK_STR(formatted(unmapped), "2001:db8::1"); } /* -------------------------------------------------------- RFC 4291 input */ static void case_scan_forms(void) { char ip[16]; test_case("2.2 form 1, every group spelled out"); CHECK_INT(ip_scan("1111:2222:3333:4444:5555:6666:7777:8888", ip), 39); CHECK_MEM(ip, "\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88", 16); test_case("2.2 form 2, zero groups abbreviated"); CHECK_INT(ip_scan("2001:db8::1", ip), 11); CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); CHECK_INT(ip_scan("::1", ip), 3); CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); CHECK_INT(ip_scan("::", ip), 2); CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16); CHECK_INT(ip_scan("1::", ip), 3); CHECK_MEM(ip, "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16); test_case("2.2 form 3, a dotted quad in the last two groups"); CHECK_INT(ip_scan("::ffff:1.2.3.4", ip), 14); CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x01\x02\x03\x04", 16); CHECK_INT(ip_scan("1:2:3:4:5:6:1.2.3.4", ip), 19); CHECK_MEM(ip, "\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x01\x02\x03\x04", 16); test_case("upper case input is accepted"); CHECK_INT(ip_scan("2001:DB8::1", ip), 11); CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); } static void case_scan_accepts_bare_ipv4(void) { char ip[16]; /* Every address opentracker holds is sixteen bytes, so an operator writing a plain IPv4 address in the config has to land as the v4 mapped form */ test_case("a bare IPv4 address is stored v4 mapped"); CHECK_INT(ip_scan("10.0.0.1", ip), 8); CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x0a\x00\x00\x01", 16); CHECK(ip_is_v4mapped(ip)); } static void case_scan_reports_length(void) { char ip[16]; /* The config parser continues from where the address ended, which is how "address:port" and "address/bits" are read */ test_case("scanning stops at the character after the address"); CHECK_INT(ip_scan("10.0.0.1:6969", ip), 8); CHECK_INT(ip_scan("2001:db8::1/64", ip), 11); CHECK_INT(ip_scan("::1]", ip), 3); CHECK_INT(ip_scan("1:2:3:4:5:6:7:8 trailing", ip), 15); } static void case_scan_rejects(void) { char ip[16]; test_case("an empty or junk string is refused"); CHECK_INT(ip_scan("", ip), 0); CHECK_INT(ip_scan("x", ip), 0); CHECK_INT(ip_scan(":", ip), 0); CHECK_INT(ip_scan(":1:2", ip), 0); test_case("too few groups without :: are refused"); CHECK_INT(ip_scan("1:2:3:4:5:6:7", ip), 0); CHECK_INT(ip_scan("2001:db8:1", ip), 0); /* libowfat accepted a prefix of each of these and carried on. Taking only part of a malformed address is worse than refusing it, especially where the text comes from a config file or an X-Forwarded-For header. */ test_case("a second :: is refused rather than truncated"); CHECK_INT(ip_scan("1::2::3", ip), 0); CHECK_INT(ip_scan("::1::", ip), 0); CHECK_INT(ip_scan("::::", ip), 0); test_case("more than eight groups is refused"); CHECK_INT(ip_scan("1:2:3:4:5:6:7:8:9", ip), 0); test_case("a group of more than four digits is refused, not truncated"); CHECK_INT(ip_scan("12345::1", ip), 0); CHECK_INT(ip_scan("fffff::1", ip), 0); } static void case_round_trip(void) { /* Whatever the formatter writes, the scanner has to read back */ static const char *const samples[] = {"::", "::1", "1::", "2001:db8::1", "2001:db8:1:2:3:4:5:0", "1111:2222:3333:4444:5555:6666:7777:8888", "2001::1:0:0:1", "abcd:ef01::1", NULL}; char ip[16], again[16]; size_t i; test_case("every formatted address scans back to the same bytes"); for (i = 0; samples[i]; ++i) { size_t n = ip_scan(samples[i], ip); if (n != strlen(samples[i])) { test_failf(__FILE__, __LINE__, "\"%s\" consumed %zu of %zu", samples[i], n, strlen(samples[i])); return; } text[ip_format(text, ip)] = 0; if (strcmp(text, samples[i])) { test_failf(__FILE__, __LINE__, "\"%s\" formatted back as \"%s\"", samples[i], text); return; } if (!ip_scan(text, again) || memcmp(ip, again, 16)) { test_failf(__FILE__, __LINE__, "\"%s\" did not survive a second pass", samples[i]); return; } } ++test_checks; } void test_ip(void) { case_format_rfc5952(); case_format_trailing_zero_group(); case_v4mapped(); case_scan_forms(); case_scan_accepts_bare_ipv4(); case_scan_reports_length(); case_scan_rejects(); case_round_trip(); }