diff options
author | Benny Prijono <bennylp@teluu.com> | 2009-06-04 15:11:25 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2009-06-04 15:11:25 +0000 |
commit | 3accbfaad96e2be2dfb5f5d4fd3625b3d7b1f6ac (patch) | |
tree | 6a9ea9fe58a5ac39ffc5739047d95c95409a09b8 | |
parent | 7d34bc703776dd5ad0f2cccd8ebb396c3de273ff (diff) |
Ticket #878: New PJLIB API to parse socket address string
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2743 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r-- | pjlib/include/pj/sock.h | 51 | ||||
-rw-r--r-- | pjlib/src/pj/sock_common.c | 186 | ||||
-rw-r--r-- | pjlib/src/pjlib-test/sock.c | 159 |
3 files changed, 396 insertions, 0 deletions
diff --git a/pjlib/include/pj/sock.h b/pjlib/include/pj/sock.h index 44413263..b8ddb02d 100644 --- a/pjlib/include/pj/sock.h +++ b/pjlib/include/pj/sock.h @@ -940,6 +940,57 @@ PJ_DECL(pj_status_t) pj_sockaddr_set_port(pj_sockaddr *addr, PJ_DECL(void) pj_sockaddr_in_set_port(pj_sockaddr_in *addr, pj_uint16_t hostport); +/** + * Parse string containing IP address and optional port into socket address, + * possibly also with address family detection. This function supports both + * IPv4 and IPv6 parsing, however IPv6 parsing may only be done if IPv6 is + * enabled during compilation. + * + * This function supports parsing several formats. Sample IPv4 inputs and + * their default results:: + * - "10.0.0.1:80": address 10.0.0.1 and port 80. + * - "10.0.0.1": address 10.0.0.1 and port zero. + * - "10.0.0.1:": address 10.0.0.1 and port zero. + * - "10.0.0.1:0": address 10.0.0.1 and port zero. + * - ":80": address 0.0.0.0 and port 80. + * - ":": address 0.0.0.0 and port 0. + * - "localhost": address 127.0.0.1 and port 0. + * - "localhost:": address 127.0.0.1 and port 0. + * - "localhost:80": address 127.0.0.1 and port 80. + * + * Sample IPv6 inputs and their default results: + * - "[fec0::01]:80": address fec0::01 and port 80 + * - "[fec0::01]": address fec0::01 and port 0 + * - "[fec0::01]:": address fec0::01 and port 0 + * - "[fec0::01]:0": address fec0::01 and port 0 + * - "fec0::01": address fec0::01 and port 0 + * - "fec0::01:80": address fec0::01:80 and port 0 + * - "::": address zero (::) and port 0 + * - "[::]": address zero (::) and port 0 + * - "[::]:": address zero (::) and port 0 + * - ":::": address zero (::) and port 0 + * - "[::]:80": address zero (::) and port 0 + * - ":::80": address zero (::) and port 80 + * + * Note: when the IPv6 socket address contains port number, the IP + * part of the socket address should be enclosed with square brackets, + * otherwise the port number will be included as part of the IP address + * (see "fec0::01:80" example above). + * + * @param af Optionally specify the address family to be used. If the + * address family is to be deducted from the input, specify + * pj_AF_UNSPEC() here. + * @param options Additional options to assist the parsing, must be zero + * for now. + * @param str The input string to be parsed. + * @param addr Pointer to store the result. + * + * @return PJ_SUCCESS if the parsing is successful. + */ +PJ_DECL(pj_status_t) pj_sockaddr_parse(int af, unsigned options, + const pj_str_t *str, + pj_sockaddr *addr); + /***************************************************************************** * * HOST NAME AND ADDRESS. 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; |