From 7d2f0cab63a2223a426c44e13f771abe6a7d7eaf Mon Sep 17 00:00:00 2001 From: Arto Kinnunen Date: Fri, 5 Oct 2018 13:09:29 +0300 Subject: [PATCH] Squashed 'features/frameworks/nanostack-libservice/' changes from 2705b9b..5eb2f3f 5eb2f3f Make stoip6_prefix return a failure state when the core parser fails 901fdbb Add a couple of unit tests validating the more aggressive IPv6 parser de201a3 Make stoip6 return whether the conversion succeed e6ce3a8 Add new function stoip6_prefix (#75) 8ef1930 Revert "Make stoip6 return whether the conversion succeed" (#73) 6a18702 Merge pull request #72 from Taiki-San/ipv6 5efa0ff Add a couple of unit tests validating the more aggressive IPv6 parser 3b2c19f Make stoip6 return whether the conversion succeed git-subtree-dir: features/frameworks/nanostack-libservice git-subtree-split: 5eb2f3f4592aa00915f1bb37ba9cbc109146734d --- mbed-client-libservice/ip6string.h | 18 ++- source/libip6string/stoip6.c | 113 +++++++++++++--- test/libService/unittest/stoip6/main.cpp | 1 + .../libService/unittest/stoip6/stoip6test.cpp | 127 ++++++++++++++---- .../unittest/stubs/ipv6_test_values.h | 1 + 5 files changed, 216 insertions(+), 44 deletions(-) diff --git a/mbed-client-libservice/ip6string.h b/mbed-client-libservice/ip6string.h index bf1f339c76..0f3cec5613 100644 --- a/mbed-client-libservice/ip6string.h +++ b/mbed-client-libservice/ip6string.h @@ -58,9 +58,10 @@ uint_fast8_t ip6_prefix_tos(const void *prefix, uint_fast8_t prefix_len, char *p * * \param ip6addr IPv6 address in string format. * \param len Lenght of ipv6 string, maximum of 41. - * \param dest buffer for address. MUST be 16 bytes. + * \param dest buffer for address. MUST be 16 bytes. Filled with 0 on error. + * \return boolean set to true if conversion succeed, false if it didn't */ -void stoip6(const char *ip6addr, size_t len, void *dest); +bool stoip6(const char *ip6addr, size_t len, void *dest); /** * Find out numeric IPv6 address prefix length. * @@ -69,6 +70,19 @@ void stoip6(const char *ip6addr, size_t len, void *dest); */ unsigned char sipv6_prefixlength(const char *ip6addr); +/** + * Convert numeric IPv6 address string with prefix to a binary. + * + * IPv4 tunneling addresses are not covered. + * + * \param ip6addr IPv6 address in string format. + * \param dest buffer for address. MUST be 16 bytes. + * \param prefix_len_out length of prefix, is set to -1 if no prefix given + * + * \return 0 on success, negative value otherwise. prefix_len_out contains prefix length. + */ +int stoip6_prefix(const char *ip6addr, void *dest, int_fast16_t *prefix_len_out); + #ifdef __cplusplus } #endif diff --git a/source/libip6string/stoip6.c b/source/libip6string/stoip6.c index 8e6f705a54..4b0a4a81d1 100644 --- a/source/libip6string/stoip6.c +++ b/source/libip6string/stoip6.c @@ -20,6 +20,7 @@ #include "ip6string.h" static uint16_t hex(const char *p); +static bool is_hex(char c); /** * Convert numeric IPv6 address string to a binary. @@ -27,8 +28,9 @@ static uint16_t hex(const char *p); * \param ip6addr IPv6 address in string format. * \param len Length of ipv6 string. * \param dest buffer for address. MUST be 16 bytes. + * \return boolean set to true if conversion succeed, false if it didn't */ -void stoip6(const char *ip6addr, size_t len, void *dest) +bool stoip6(const char *ip6addr, size_t len, void *dest) { uint8_t *addr; const char *p, *q; @@ -37,23 +39,45 @@ void stoip6(const char *ip6addr, size_t len, void *dest) addr = dest; if (len > 39) { // Too long, not possible. We do not support IPv4-mapped IPv6 addresses - return; + goto error; } // First go forward the string, until end, noting :: position if any - for (field_no = 0, p = ip6addr; (len > (size_t)(p - ip6addr)) && *p && field_no < 8; p = q + 1) { - q = p; - // Seek for ':' or end - while (*q && (*q != ':')) { - q++; + // We're decrementing `len` as we go forward, and stop when it reaches 0 + for (field_no = 0, p = ip6addr; len && *p; p = q + 1) { + + for (q = p; len && *q && (*q != ':'); len -= 1) { // Seek for ':' or end + if (!is_hex(*q++)) { // There must only be hex characters besides ':' + goto error; + } } - //Convert and write this part, (high-endian AKA network byte order) + + if ((q - p) > 4) { // We can't have more than 4 hex digits per segment + goto error; + } + + if (field_no == 8) { // If the address goes farther than 8 segments + goto error; + } + + // Convert and write this part, (high-endian AKA network byte order) addr = common_write_16_bit(hex(p), addr); field_no++; - //Check if we reached "::" - if ((len > (size_t)(q - ip6addr)) && *q && (q[0] == ':') && (q[1] == ':')) { - coloncolon = field_no; - q++; + + // We handle the colons + if (len) { + // Check if we reached "::" + if (q[0] == ':' && q[1] == ':') { + if (coloncolon != -1) { // We are not supposed to see "::" more than once per address + goto error; + } + coloncolon = field_no; + q++; + len -= 2; + } + else { + len -= 1; + } } } @@ -65,12 +89,19 @@ void stoip6(const char *ip6addr, size_t len, void *dest) addr = dest; memmove(addr + head_size + inserted_size, addr + head_size, tail_size); memset(addr + head_size, 0, inserted_size); - } else if (field_no != 8) { - /* Should really report an error if we didn't get 8 fields */ - memset(addr, 0, 16 - field_no * 2); + } else if (field_no != 8) { // Report an error if we didn't get 8 fields + goto error; } + return true; + +error: + // Fill the output buffer with 0 so we stick to the old failure behavior. + // We are however more agressive and wipe the entire address, and do so more often. + memset(dest, 0, 16); + return false; } -unsigned char sipv6_prefixlength(const char *ip6addr) + +unsigned char sipv6_prefixlength(const char *ip6addr) { char *ptr = strchr(ip6addr, '/'); if (ptr) { @@ -78,6 +109,56 @@ unsigned char sipv6_prefixlength(const char *ip6addr) } return 0; } + +int stoip6_prefix(const char *ip6addr, void *dest, int_fast16_t *prefix_len_out) +{ + size_t addr_len, total_len; + int_fast16_t prefix_length; + + if (prefix_len_out) { + *prefix_len_out = -1; + } + + total_len = addr_len = strlen(ip6addr); + const char *ptr = strchr(ip6addr, '/'); + if (ptr) { + addr_len = ptr - ip6addr; + if (prefix_len_out) { + if (total_len - addr_len > 3) { + /* too many digits in prefix */ + return -1; + } + + prefix_length = strtoul(ptr + 1, 0, 10); + if (prefix_length < 0 || prefix_length > 128) { + /* prefix value illegal */ + return -1; + } + + *prefix_len_out = prefix_length; + } + } + + if (!stoip6(ip6addr, addr_len, dest)) { + /* parser failure */ + return -1; + } + + return 0; +} + +static bool is_hex(char c) +{ + // 'A' (0x41) and 'a' (0x61) are mapped in the ASCII table in such a way that masking the 0x20 bit turn 'a' in 'A' + if ((c & ~0x20) >= 'A' && (c & ~0x20) <= 'F') + return true; + + if (c >= '0' && c <= '9') + return true; + + return false; +} + static uint16_t hex(const char *p) { uint16_t val = 0; diff --git a/test/libService/unittest/stoip6/main.cpp b/test/libService/unittest/stoip6/main.cpp index 6b3cf41d1a..bfd7ec69c5 100644 --- a/test/libService/unittest/stoip6/main.cpp +++ b/test/libService/unittest/stoip6/main.cpp @@ -25,3 +25,4 @@ int main(int ac, char **av) IMPORT_TEST_GROUP(stoip6); IMPORT_TEST_GROUP(stoip6_2); +IMPORT_TEST_GROUP(stoip6_3); diff --git a/test/libService/unittest/stoip6/stoip6test.cpp b/test/libService/unittest/stoip6/stoip6test.cpp index b9f7a5ed87..431a0922dc 100644 --- a/test/libService/unittest/stoip6/stoip6test.cpp +++ b/test/libService/unittest/stoip6/stoip6test.cpp @@ -42,11 +42,10 @@ TEST(stoip6, TooShort) { char *addr = "FFFF:FFFF:"; uint8_t ip[16]; - uint8_t correct[16] = {0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - // This should sto parsing when too short address given, the buffer hoewever is filled to that long - // So basically there is no error handling. We just check that first FFFF:FFFF gets filled and not trash after that. - // Rest should be filled with zeroes - stoip6(addr, strlen(addr), ip); + uint8_t correct[16] = {0}; + // This should stop parsing when too short address given. + // Despite partial parsing, the entire buffer should be filled with zeroes + CHECK(false == stoip6(addr, strlen(addr), ip)); CHECK(0 == memcmp(ip, correct, 16)); } @@ -58,7 +57,7 @@ TEST(stoip6, TooLongString) uint8_t correct[16] = {0}; // This should not fill anything, too long string. // This is basically only validation we do - stoip6(addr, strlen(addr), ip); + CHECK(false == stoip6(addr, strlen(addr), ip)); CHECK(0 == memcmp(ip, correct, 16)); } @@ -66,12 +65,11 @@ TEST(stoip6, TooManyFields) { // String len must be less than 40 char *addr = "FF:FF:FF:FF:FF:FF:FFFF:FFFF:FFFF:FFFF:"; - uint8_t ip[17] = {0}; - uint8_t correct[17] = { 0, 0xff, 0, 0xff, 0, 0xff, 0, 0xff, 0, 0xff, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0}; - // Again.. there is not really any error handling (no return value) - // Just make sure that it does not overflow - stoip6(addr, strlen(addr), ip); - CHECK(0 == memcmp(ip, correct, 17)); // Note, we are checking 17, to make sure one byte after address in not touched. + uint8_t ip[16] = {0}; + uint8_t correct[16] = {0}; + + CHECK(false == stoip6(addr, strlen(addr), ip)); + CHECK(0 == memcmp(ip, correct, 16)); } TEST(stoip6, Prefixlen) @@ -96,13 +94,31 @@ TEST(stoip6, RegressionTestForOffByOne) 0x64, 0x3f, 0xf5, 0x4a, 0xec, 0x29, 0xcd, 0xbb }; - stoip6(sourceTemp, sourceTempLen, ip); - + CHECK(true == stoip6(sourceTemp, sourceTempLen, ip)); CHECK(0 == memcmp(ip, correct, 16)); free(sourceTemp); } +// Test various illegal formats to ensure proper rejection +TEST(stoip6, InvalidAddresses) +{ + uint8_t ip[16]; + uint8_t correct[16] = {0}; + + const char *invalidArray[] = + { + "FFFF:FFFF::FFFF::FFFF", // Two :: + "F:F:F:FqF:F:F:F:F", // Non-hex character + "F:F:F:FFFFF:F:F:F:F" // >4 hex characters in a segment + }; + + for (uint8_t i = 0; i < 3; ++i) { + CHECK(false == stoip6(invalidArray[i], strlen(invalidArray[i]), ip)); + CHECK(0 == memcmp(ip, correct, 16)); + } +} + /***********************************************************/ /* Second test group for the old tests that were once lost */ @@ -161,63 +177,122 @@ TEST_GROUP(stoip6_2) TEST(stoip6_2, test_2_1) { i = 0; - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, strlen(buf))); } TEST(stoip6_2, test_2_2) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, strlen(buf))); } TEST(stoip6_2, test_2_3) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, strlen(buf))); } TEST(stoip6_2, test_2_4) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, strlen(buf))); } TEST(stoip6_2, test_2_5) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, strlen(buf))); } TEST(stoip6_2, test_2_6) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, strlen(buf))); } TEST(stoip6_2, test_2_7) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, strlen(buf))); } TEST(stoip6_2, test_2_8) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, strlen(buf))); } TEST(stoip6_2, test_2_9) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, strlen(buf))); } TEST(stoip6_2, test_2_10) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, 16)); } TEST(stoip6_2, test_2_11) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, 16)); } TEST(stoip6_2, test_2_12) { - stoip6(string_addr[i], strlen(string_addr[i]), buf); + CHECK(true == stoip6(string_addr[i], strlen(string_addr[i]), buf)); CHECK(0 == memcmp(hex_addr[i], buf, 16)); } +/***********************************************************/ +/* Third test group for stoip6_prefix */ + +const char string_prefix_addr[][40] = +{ + "2001:db8::1:0:0:1/64", // 1 + "2001::/60", // 2 + "::1/48", // 3 + "::/00", // 4 + "2002::02/99", // 5 + "2003::03/", // 6 + "2004::04", // 7 + "2005::05/2000", // 8 + "2005::05/-1", // 9 +}; + + +const uint8_t hex_prefix_addr[][16] = +{ + { 0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1 }, // 1 + { 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 2 + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 }, // 3 + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, // 4 + { 0x20, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }, // 5 + { 0x20, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 }, // 6 + { 0x20, 0x04, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4 }, // 7 + { 0x20, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5 }, // 8 + { 0x20, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5 }, // 9 +}; + +const int_fast16_t prefix_len_tbl[] = {64, 60, 48, 0, 99, 0, -1, -1, -1}; +const int prefix_status_tbl[] = {0, 0, 0, 0, 0, 0, 0, -1, -1}; + +TEST_GROUP(stoip6_3) +{ + void setup() { + } + + void teardown() { + } +}; + +TEST(stoip6_3, stoip6_prefix_test) +{ + for (int i = 0; i < 9; i++) { + uint8_t ip[16]; + int_fast16_t prefix_len; + int result; + const char *addr = &string_prefix_addr[i][0]; + + result = stoip6_prefix(addr, ip, &prefix_len); + CHECK(result == prefix_status_tbl[i]); + if (result == 0) { + CHECK(0 == memcmp(ip, &hex_prefix_addr[i][0], 16)); + CHECK(prefix_len == prefix_len_tbl[i]) + } + } +} + diff --git a/test/libService/unittest/stubs/ipv6_test_values.h b/test/libService/unittest/stubs/ipv6_test_values.h index 418c981311..e9ce71f795 100644 --- a/test/libService/unittest/stubs/ipv6_test_values.h +++ b/test/libService/unittest/stubs/ipv6_test_values.h @@ -33,6 +33,7 @@ struct ip6_addresses_and_its_binary_form_t { { "2001:db8::", { 0x20, 0x01, 0xd, 0xb8 }}, { "::aaaa:0:0:1", { 0, 0, 0, 0, 0, 0, 0, 0, 0xaa, 0xaa, 0, 0, 0, 0, 0, 1 }}, { "::1", { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }}, + { "fe80::1", { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}, { "::", { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }}, { "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, {NULL, {0}}