diff options
Diffstat (limited to 'pjlib/src')
-rw-r--r-- | pjlib/src/pj/sock_common.c | 186 | ||||
-rw-r--r-- | pjlib/src/pjlib-test/sock.c | 159 |
2 files changed, 345 insertions, 0 deletions
diff --git a/pjlib/src/pj/sock_common.c b/pjlib/src/pj/sock_common.c index 76ddbad7..0317fc66 100644 --- a/pjlib/src/pj/sock_common.c +++ b/pjlib/src/pj/sock_common.c @@ -19,6 +19,7 @@ */ #include <pj/sock.h> #include <pj/assert.h> +#include <pj/ctype.h> #include <pj/errno.h> #include <pj/ip_helper.h> #include <pj/os.h> @@ -454,6 +455,191 @@ PJ_DEF(void) pj_sockaddr_in_set_addr(pj_sockaddr_in *addr, addr->sin_addr.s_addr = pj_htonl(hostaddr); } +/* + * Parse address + */ +PJ_DEF(pj_status_t) pj_sockaddr_parse( int af, unsigned options, + const pj_str_t *str, + pj_sockaddr *addr) +{ + const char *end = str->ptr + str->slen; + const char *last_colon_pos = NULL; + + PJ_ASSERT_RETURN(addr, PJ_EINVAL); + PJ_ASSERT_RETURN(af==PJ_AF_UNSPEC || + af==PJ_AF_INET || + af==PJ_AF_INET6, PJ_EINVAL); + PJ_ASSERT_RETURN(options == 0, PJ_EINVAL); + + /* Deduce address family if it's not given */ + if (af == PJ_AF_UNSPEC) { + unsigned colon_cnt = 0; + const char *p; + + /* Can't accept NULL or empty input if address family is unknown */ + PJ_ASSERT_RETURN(str && str->slen, PJ_EINVAL); + + for (p=str->ptr; p!=end; ++p) { + if (*p == ':') { + ++colon_cnt; + last_colon_pos = p; + } + } + + if (colon_cnt > 1) + af = PJ_AF_INET6; + else + af = PJ_AF_INET; + } else { + /* Input may be NULL or empty as long as address family is given */ + if (str == NULL || str->slen == 0) + return pj_sockaddr_init(af, addr, NULL, 0); + } + + if (af == PJ_AF_INET) { + /* Parse as IPv4. Supported formats: + * - "10.0.0.1:80" + * - "10.0.0.1" + * - "10.0.0.1:" + * - ":80" + * - ":" + */ + pj_str_t ip_part; + unsigned long port; + + if (last_colon_pos == NULL) + last_colon_pos = pj_strchr(str, ':'); + + ip_part.ptr = (char*)str->ptr; + + if (last_colon_pos) { + pj_str_t port_part; + int i; + + ip_part.slen = last_colon_pos - str->ptr; + + port_part.ptr = (char*)last_colon_pos + 1; + port_part.slen = end - port_part.ptr; + + /* Make sure port number is valid */ + for (i=0; i<port_part.slen; ++i) { + if (!pj_isdigit(port_part.ptr[i])) + return PJ_EINVAL; + } + port = pj_strtoul(&port_part); + if (port > 65535) + return PJ_EINVAL; + } else { + ip_part.slen = str->slen; + port = 0; + } + + return pj_sockaddr_in_init(&addr->ipv4, &ip_part, (pj_uint16_t)port); + } +#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 + else if (af == PJ_AF_INET6) { + /* Parse as IPv4. Supported formats: + * - "fe::01:80" ==> note: port number is zero in this case, not 80! + * - "[fe::01]:80" + * - "fe::01" + * - "fe::01:" + * - "[fe::01]" + * - "[fe::01]:" + * - "[::]:80" + * - ":::80" + * - "[::]" + * - "[::]:" + * - ":::" + * - "::" + */ + pj_str_t ip_part, port_part; + + if (*str->ptr == '[') { + char *end_bracket = pj_strchr(str, ']'); + int i; + unsigned long port; + + if (end_bracket == NULL) + return PJ_EINVAL; + + ip_part.ptr = (char*)str->ptr + 1; + ip_part.slen = end_bracket - ip_part.ptr; + + if (last_colon_pos == NULL) { + const char *p; + for (p=str->ptr; p!=end; ++p) { + if (*p == ':') + last_colon_pos = p; + } + } + + if (last_colon_pos == NULL) + return PJ_EINVAL; + + if (last_colon_pos < end_bracket) { + port_part.ptr = NULL; + port_part.slen = 0; + } else { + port_part.ptr = (char*)last_colon_pos + 1; + port_part.slen = end - port_part.ptr; + } + + /* Make sure port number is valid */ + for (i=0; i<port_part.slen; ++i) { + if (!pj_isdigit(port_part.ptr[i])) + return PJ_EINVAL; + } + port = pj_strtoul(&port_part); + if (port > 65535) + return PJ_EINVAL; + + return pj_sockaddr_init(PJ_AF_INET6, addr, &ip_part, + (pj_uint16_t)port); + } else { + int i; + unsigned long port; + + /* First lets try to parse everything as IPv6 address */ + if (pj_sockaddr_init(PJ_AF_INET6, addr, str, 0)==PJ_SUCCESS) + return PJ_SUCCESS; + + /* Parse as IPv6:port */ + if (last_colon_pos == NULL) { + const char *p; + for (p=str->ptr; p!=end; ++p) { + if (*p == ':') + last_colon_pos = p; + } + } + + if (last_colon_pos == NULL) + return PJ_EINVAL; + + ip_part.ptr = (char*)str->ptr; + ip_part.slen = last_colon_pos - str->ptr; + + port_part.ptr = (char*)last_colon_pos + 1; + port_part.slen = end - port_part.ptr; + + /* Make sure port number is valid */ + for (i=0; i<port_part.slen; ++i) { + if (!pj_isdigit(port_part.ptr[i])) + return PJ_EINVAL; + } + port = pj_strtoul(&port_part); + if (port > 65535) + return PJ_EINVAL; + + return pj_sockaddr_init(PJ_AF_INET6, addr, &ip_part, + (pj_uint16_t)port); + } + } +#endif + else { + return PJ_EIPV6NOTSUP; + } +} + static pj_bool_t is_usable_ip(const pj_sockaddr *addr) { if (addr->addr.sa_family==PJ_AF_INET) { diff --git a/pjlib/src/pjlib-test/sock.c b/pjlib/src/pjlib-test/sock.c index 60273437..f81c7e02 100644 --- a/pjlib/src/pjlib-test/sock.c +++ b/pjlib/src/pjlib-test/sock.c @@ -171,6 +171,161 @@ static int format_test(void) return 0; } +static int parse_test(void) +{ +#define IPv4 1 +#define IPv6 2 + + struct test_t { + const char *input; + int result_af; + const char *result_ip; + pj_uint16_t result_port; + }; + struct test_t valid_tests[] = + { + /* IPv4 */ + { "10.0.0.1:80", IPv4, "10.0.0.1", 80}, + { "10.0.0.1", IPv4, "10.0.0.1", 0}, + { "10.0.0.1:", IPv4, "10.0.0.1", 0}, + { "10.0.0.1:0", IPv4, "10.0.0.1", 0}, + { ":80", IPv4, "0.0.0.0", 80}, + { ":", IPv4, "0.0.0.0", 0}, + { "localhost", IPv4, "127.0.0.1", 0}, + { "localhost:", IPv4, "127.0.0.1", 0}, + { "localhost:80", IPv4, "127.0.0.1", 80}, + +#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 + { "fe::01:80", IPv6, "fe::01:80", 0}, + { "[fe::01]:80", IPv6, "fe::01", 80}, + { "fe::01", IPv6, "fe::01", 0}, + { "[fe::01]", IPv6, "fe::01", 0}, + { "fe::01:", IPv6, "fe::01", 0}, + { "[fe::01]:", IPv6, "fe::01", 0}, + { "::", IPv6, "::0", 0}, + { "[::]", IPv6, "::", 0}, + { ":::", IPv6, "::", 0}, + { "[::]:", IPv6, "::", 0}, + { ":::80", IPv6, "::", 80}, + { "[::]:80", IPv6, "::", 80}, +#endif + }; + struct test_t invalid_tests[] = + { + /* IPv4 */ + { "10.0.0.1:abcd", IPv4}, /* port not numeric */ + { "10.0.0.1:-1", IPv4}, /* port contains illegal character */ + { "10.0.0.1:123456", IPv4}, /* port too big */ + { "1.2.3.4.5:80", IPv4}, /* invalid IP */ + +#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 + { "[fe::01]:abcd", IPv6}, /* port not numeric */ + { "[fe::01]:-1", IPv6}, /* port contains illegal character */ + { "[fe::01]:123456", IPv6}, /* port too big */ + { "fe::01:02::03:04:80", IPv6}, /* invalid IP */ + { "[fe::01:02::03:04]:80", IPv6}, /* invalid IP */ + { "[fe:01", IPv6}, /* Unterminated bracket */ +#endif + }; + + unsigned i; + + PJ_LOG(3,("test", "...IP address parsing")); + + for (i=0; i<PJ_ARRAY_SIZE(valid_tests); ++i) { + pj_status_t status; + pj_str_t input; + pj_sockaddr addr, result; + + switch (valid_tests[i].result_af) { + case IPv4: + valid_tests[i].result_af = PJ_AF_INET; + break; + case IPv6: + valid_tests[i].result_af = PJ_AF_INET6; + break; + default: + pj_assert(!"Invalid AF!"); + continue; + } + + /* Try parsing with PJ_AF_UNSPEC */ + status = pj_sockaddr_parse(PJ_AF_UNSPEC, 0, + pj_cstr(&input, valid_tests[i].input), + &addr); + if (status != PJ_SUCCESS) { + PJ_LOG(1,("test", ".... failed when parsing %s", + valid_tests[i].input)); + return -10; + } + + /* Build the correct result */ + status = pj_sockaddr_init(valid_tests[i].result_af, + &result, + pj_cstr(&input, valid_tests[i].result_ip), + valid_tests[i].result_port); + if (status != PJ_SUCCESS) { + PJ_LOG(1,("test", ".... error building IP address %s", + valid_tests[i].input)); + return -30; + } + + /* Compare the result */ + if (pj_sockaddr_cmp(&addr, &result) != 0) { + PJ_LOG(1,("test", ".... parsed result mismatched for %s", + valid_tests[i].input)); + return -40; + } + + /* Parse again with the specified af */ + status = pj_sockaddr_parse(valid_tests[i].result_af, 0, + pj_cstr(&input, valid_tests[i].input), + &addr); + if (status != PJ_SUCCESS) { + PJ_LOG(1,("test", ".... failed when parsing %s", + valid_tests[i].input)); + return -50; + } + + /* Compare the result again */ + if (pj_sockaddr_cmp(&addr, &result) != 0) { + PJ_LOG(1,("test", ".... parsed result mismatched for %s", + valid_tests[i].input)); + return -60; + } + } + + for (i=0; i<PJ_ARRAY_SIZE(invalid_tests); ++i) { + pj_status_t status; + pj_str_t input; + pj_sockaddr addr; + + switch (invalid_tests[i].result_af) { + case IPv4: + invalid_tests[i].result_af = PJ_AF_INET; + break; + case IPv6: + invalid_tests[i].result_af = PJ_AF_INET6; + break; + default: + pj_assert(!"Invalid AF!"); + continue; + } + + /* Try parsing with PJ_AF_UNSPEC */ + status = pj_sockaddr_parse(PJ_AF_UNSPEC, 0, + pj_cstr(&input, invalid_tests[i].input), + &addr); + if (status == PJ_SUCCESS) { + PJ_LOG(1,("test", ".... expecting failure when parsing %s", + invalid_tests[i].input)); + return -100; + } + } + + return 0; +} + static int simple_sock_test(void) { int types[2]; @@ -576,6 +731,10 @@ int sock_test() if (rc != 0) return rc; + rc = parse_test(); + if (rc != 0) + return rc; + rc = gethostbyname_test(); if (rc != 0) return rc; |