From 3accbfaad96e2be2dfb5f5d4fd3625b3d7b1f6ac Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Thu, 4 Jun 2009 15:11:25 +0000 Subject: 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 --- pjlib/include/pj/sock.h | 51 ++++++++++++ pjlib/src/pj/sock_common.c | 186 ++++++++++++++++++++++++++++++++++++++++++++ pjlib/src/pjlib-test/sock.c | 159 +++++++++++++++++++++++++++++++++++++ 3 files changed, 396 insertions(+) (limited to 'pjlib') 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 #include +#include #include #include #include @@ -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 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 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 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