summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2009-06-04 15:11:25 +0000
committerBenny Prijono <bennylp@teluu.com>2009-06-04 15:11:25 +0000
commit3accbfaad96e2be2dfb5f5d4fd3625b3d7b1f6ac (patch)
tree6a9ea9fe58a5ac39ffc5739047d95c95409a09b8
parent7d34bc703776dd5ad0f2cccd8ebb396c3de273ff (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.h51
-rw-r--r--pjlib/src/pj/sock_common.c186
-rw-r--r--pjlib/src/pjlib-test/sock.c159
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;