Changeset 2743 for pjproject/trunk


Ignore:
Timestamp:
Jun 4, 2009 3:11:25 PM (15 years ago)
Author:
bennylp
Message:

Ticket #878: New PJLIB API to parse socket address string

Location:
pjproject/trunk/pjlib
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • pjproject/trunk/pjlib/include/pj/sock.h

    r2394 r2743  
    941941                                      pj_uint16_t hostport); 
    942942 
     943/** 
     944 * Parse string containing IP address and optional port into socket address, 
     945 * possibly also with address family detection. This function supports both 
     946 * IPv4 and IPv6 parsing, however IPv6 parsing may only be done if IPv6 is 
     947 * enabled during compilation. 
     948 * 
     949 * This function supports parsing several formats. Sample IPv4 inputs and 
     950 * their default results:: 
     951 *  - "10.0.0.1:80": address 10.0.0.1 and port 80. 
     952 *  - "10.0.0.1": address 10.0.0.1 and port zero. 
     953 *  - "10.0.0.1:": address 10.0.0.1 and port zero. 
     954 *  - "10.0.0.1:0": address 10.0.0.1 and port zero. 
     955 *  - ":80": address 0.0.0.0 and port 80. 
     956 *  - ":": address 0.0.0.0 and port 0. 
     957 *  - "localhost": address 127.0.0.1 and port 0. 
     958 *  - "localhost:": address 127.0.0.1 and port 0. 
     959 *  - "localhost:80": address 127.0.0.1 and port 80. 
     960 * 
     961 * Sample IPv6 inputs and their default results: 
     962 *  - "[fec0::01]:80": address fec0::01 and port 80 
     963 *  - "[fec0::01]": address fec0::01 and port 0 
     964 *  - "[fec0::01]:": address fec0::01 and port 0 
     965 *  - "[fec0::01]:0": address fec0::01 and port 0 
     966 *  - "fec0::01": address fec0::01 and port 0 
     967 *  - "fec0::01:80": address fec0::01:80 and port 0 
     968 *  - "::": address zero (::) and port 0 
     969 *  - "[::]": address zero (::) and port 0 
     970 *  - "[::]:": address zero (::) and port 0 
     971 *  - ":::": address zero (::) and port 0 
     972 *  - "[::]:80": address zero (::) and port 0 
     973 *  - ":::80": address zero (::) and port 80 
     974 * 
     975 * Note: when the IPv6 socket address contains port number, the IP  
     976 * part of the socket address should be enclosed with square brackets,  
     977 * otherwise the port number will be included as part of the IP address 
     978 * (see "fec0::01:80" example above). 
     979 * 
     980 * @param af        Optionally specify the address family to be used. If the 
     981 *                  address family is to be deducted from the input, specify 
     982 *                  pj_AF_UNSPEC() here. 
     983 * @param options   Additional options to assist the parsing, must be zero 
     984 *                  for now. 
     985 * @param str       The input string to be parsed. 
     986 * @param addr      Pointer to store the result. 
     987 * 
     988 * @return          PJ_SUCCESS if the parsing is successful. 
     989 */ 
     990PJ_DECL(pj_status_t) pj_sockaddr_parse(int af, unsigned options, 
     991                                       const pj_str_t *str, 
     992                                       pj_sockaddr *addr); 
     993 
    943994/***************************************************************************** 
    944995 * 
  • pjproject/trunk/pjlib/src/pj/sock_common.c

    r2656 r2743  
    2020#include <pj/sock.h> 
    2121#include <pj/assert.h> 
     22#include <pj/ctype.h> 
    2223#include <pj/errno.h> 
    2324#include <pj/ip_helper.h> 
     
    455456} 
    456457 
     458/* 
     459 * Parse address 
     460 */ 
     461PJ_DEF(pj_status_t) pj_sockaddr_parse( int af, unsigned options, 
     462                                       const pj_str_t *str, 
     463                                       pj_sockaddr *addr) 
     464{ 
     465    const char *end = str->ptr + str->slen; 
     466    const char *last_colon_pos = NULL; 
     467 
     468    PJ_ASSERT_RETURN(addr, PJ_EINVAL); 
     469    PJ_ASSERT_RETURN(af==PJ_AF_UNSPEC || 
     470                     af==PJ_AF_INET || 
     471                     af==PJ_AF_INET6, PJ_EINVAL); 
     472    PJ_ASSERT_RETURN(options == 0, PJ_EINVAL); 
     473 
     474    /* Deduce address family if it's not given */ 
     475    if (af == PJ_AF_UNSPEC) { 
     476        unsigned colon_cnt = 0; 
     477        const char *p; 
     478 
     479        /* Can't accept NULL or empty input if address family is unknown */ 
     480        PJ_ASSERT_RETURN(str && str->slen, PJ_EINVAL); 
     481 
     482        for (p=str->ptr; p!=end; ++p) { 
     483            if (*p == ':') { 
     484                ++colon_cnt; 
     485                last_colon_pos = p; 
     486            } 
     487        } 
     488 
     489        if (colon_cnt > 1) 
     490            af = PJ_AF_INET6; 
     491        else 
     492            af = PJ_AF_INET; 
     493    } else { 
     494        /* Input may be NULL or empty as long as address family is given */ 
     495        if (str == NULL || str->slen == 0) 
     496            return pj_sockaddr_init(af, addr, NULL, 0); 
     497    } 
     498 
     499    if (af == PJ_AF_INET) { 
     500        /* Parse as IPv4. Supported formats: 
     501         *  - "10.0.0.1:80" 
     502         *  - "10.0.0.1" 
     503         *  - "10.0.0.1:" 
     504         *  - ":80" 
     505         *  - ":" 
     506         */ 
     507        pj_str_t ip_part; 
     508        unsigned long port; 
     509 
     510        if (last_colon_pos == NULL) 
     511            last_colon_pos = pj_strchr(str, ':'); 
     512         
     513        ip_part.ptr = (char*)str->ptr; 
     514 
     515        if (last_colon_pos) { 
     516            pj_str_t port_part; 
     517            int i; 
     518 
     519            ip_part.slen = last_colon_pos - str->ptr; 
     520 
     521            port_part.ptr = (char*)last_colon_pos + 1; 
     522            port_part.slen = end - port_part.ptr; 
     523 
     524            /* Make sure port number is valid */ 
     525            for (i=0; i<port_part.slen; ++i) { 
     526                if (!pj_isdigit(port_part.ptr[i])) 
     527                    return PJ_EINVAL; 
     528            } 
     529            port = pj_strtoul(&port_part); 
     530            if (port > 65535) 
     531                return PJ_EINVAL; 
     532        } else { 
     533            ip_part.slen = str->slen; 
     534            port = 0; 
     535        } 
     536 
     537        return pj_sockaddr_in_init(&addr->ipv4, &ip_part, (pj_uint16_t)port); 
     538    } 
     539#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 
     540    else if (af == PJ_AF_INET6) { 
     541        /* Parse as IPv4. Supported formats: 
     542         *  - "fe::01:80"  ==> note: port number is zero in this case, not 80! 
     543         *  - "[fe::01]:80" 
     544         *  - "fe::01" 
     545         *  - "fe::01:" 
     546         *  - "[fe::01]" 
     547         *  - "[fe::01]:" 
     548         *  - "[::]:80" 
     549         *  - ":::80" 
     550         *  - "[::]" 
     551         *  - "[::]:" 
     552         *  - ":::" 
     553         *  - "::" 
     554         */ 
     555        pj_str_t ip_part, port_part; 
     556 
     557        if (*str->ptr == '[') { 
     558            char *end_bracket = pj_strchr(str, ']'); 
     559            int i; 
     560            unsigned long port; 
     561 
     562            if (end_bracket == NULL) 
     563                return PJ_EINVAL; 
     564 
     565            ip_part.ptr = (char*)str->ptr + 1; 
     566            ip_part.slen = end_bracket - ip_part.ptr; 
     567 
     568            if (last_colon_pos == NULL) { 
     569                const char *p; 
     570                for (p=str->ptr; p!=end; ++p) { 
     571                    if (*p == ':') 
     572                        last_colon_pos = p; 
     573                } 
     574            } 
     575 
     576            if (last_colon_pos == NULL) 
     577                return PJ_EINVAL; 
     578 
     579            if (last_colon_pos < end_bracket) { 
     580                port_part.ptr = NULL; 
     581                port_part.slen = 0; 
     582            } else { 
     583                port_part.ptr = (char*)last_colon_pos + 1; 
     584                port_part.slen = end - port_part.ptr; 
     585            } 
     586 
     587            /* Make sure port number is valid */ 
     588            for (i=0; i<port_part.slen; ++i) { 
     589                if (!pj_isdigit(port_part.ptr[i])) 
     590                    return PJ_EINVAL; 
     591            } 
     592            port = pj_strtoul(&port_part); 
     593            if (port > 65535) 
     594                return PJ_EINVAL; 
     595 
     596            return pj_sockaddr_init(PJ_AF_INET6, addr, &ip_part,  
     597                                    (pj_uint16_t)port); 
     598        } else { 
     599            int i; 
     600            unsigned long port; 
     601 
     602            /* First lets try to parse everything as IPv6 address */ 
     603            if (pj_sockaddr_init(PJ_AF_INET6, addr, str, 0)==PJ_SUCCESS) 
     604                return PJ_SUCCESS; 
     605 
     606            /* Parse as IPv6:port */ 
     607            if (last_colon_pos == NULL) { 
     608                const char *p; 
     609                for (p=str->ptr; p!=end; ++p) { 
     610                    if (*p == ':') 
     611                        last_colon_pos = p; 
     612                } 
     613            } 
     614 
     615            if (last_colon_pos == NULL) 
     616                return PJ_EINVAL; 
     617 
     618            ip_part.ptr = (char*)str->ptr; 
     619            ip_part.slen = last_colon_pos - str->ptr; 
     620 
     621            port_part.ptr = (char*)last_colon_pos + 1; 
     622            port_part.slen = end - port_part.ptr; 
     623 
     624            /* Make sure port number is valid */ 
     625            for (i=0; i<port_part.slen; ++i) { 
     626                if (!pj_isdigit(port_part.ptr[i])) 
     627                    return PJ_EINVAL; 
     628            } 
     629            port = pj_strtoul(&port_part); 
     630            if (port > 65535) 
     631                return PJ_EINVAL; 
     632 
     633            return pj_sockaddr_init(PJ_AF_INET6, addr, &ip_part,  
     634                                    (pj_uint16_t)port); 
     635        } 
     636    } 
     637#endif 
     638    else { 
     639        return PJ_EIPV6NOTSUP; 
     640    } 
     641} 
     642 
    457643static pj_bool_t is_usable_ip(const pj_sockaddr *addr) 
    458644{ 
  • pjproject/trunk/pjlib/src/pjlib-test/sock.c

    r2394 r2743  
    168168    /* pj_gethostaddr() */ 
    169169 
     170 
     171    return 0; 
     172} 
     173 
     174static int parse_test(void) 
     175{ 
     176#define IPv4    1 
     177#define IPv6    2 
     178 
     179    struct test_t { 
     180        const char  *input; 
     181        int          result_af; 
     182        const char  *result_ip; 
     183        pj_uint16_t  result_port; 
     184    }; 
     185    struct test_t valid_tests[] =  
     186    { 
     187        /* IPv4 */ 
     188        { "10.0.0.1:80", IPv4, "10.0.0.1", 80}, 
     189        { "10.0.0.1", IPv4, "10.0.0.1", 0}, 
     190        { "10.0.0.1:", IPv4, "10.0.0.1", 0}, 
     191        { "10.0.0.1:0", IPv4, "10.0.0.1", 0}, 
     192        { ":80", IPv4, "0.0.0.0", 80}, 
     193        { ":", IPv4, "0.0.0.0", 0}, 
     194        { "localhost", IPv4, "127.0.0.1", 0}, 
     195        { "localhost:", IPv4, "127.0.0.1", 0}, 
     196        { "localhost:80", IPv4, "127.0.0.1", 80}, 
     197 
     198#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 
     199        { "fe::01:80", IPv6, "fe::01:80", 0}, 
     200        { "[fe::01]:80", IPv6, "fe::01", 80}, 
     201        { "fe::01", IPv6, "fe::01", 0}, 
     202        { "[fe::01]", IPv6, "fe::01", 0}, 
     203        { "fe::01:", IPv6, "fe::01", 0}, 
     204        { "[fe::01]:", IPv6, "fe::01", 0}, 
     205        { "::", IPv6, "::0", 0}, 
     206        { "[::]", IPv6, "::", 0}, 
     207        { ":::", IPv6, "::", 0}, 
     208        { "[::]:", IPv6, "::", 0}, 
     209        { ":::80", IPv6, "::", 80}, 
     210        { "[::]:80", IPv6, "::", 80}, 
     211#endif 
     212    }; 
     213    struct test_t invalid_tests[] =  
     214    { 
     215        /* IPv4 */ 
     216        { "10.0.0.1:abcd", IPv4},   /* port not numeric */ 
     217        { "10.0.0.1:-1", IPv4},     /* port contains illegal character */ 
     218        { "10.0.0.1:123456", IPv4}, /* port too big     */ 
     219        { "1.2.3.4.5:80", IPv4},    /* invalid IP */ 
     220 
     221#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 
     222        { "[fe::01]:abcd", IPv6},   /* port not numeric */ 
     223        { "[fe::01]:-1", IPv6},     /* port contains illegal character */ 
     224        { "[fe::01]:123456", IPv6}, /* port too big     */ 
     225        { "fe::01:02::03:04:80", IPv6},     /* invalid IP */ 
     226        { "[fe::01:02::03:04]:80", IPv6},   /* invalid IP */ 
     227        { "[fe:01", IPv6},          /* Unterminated bracket */ 
     228#endif 
     229    }; 
     230 
     231    unsigned i; 
     232 
     233    PJ_LOG(3,("test", "...IP address parsing")); 
     234 
     235    for (i=0; i<PJ_ARRAY_SIZE(valid_tests); ++i) { 
     236        pj_status_t status; 
     237        pj_str_t input; 
     238        pj_sockaddr addr, result; 
     239 
     240        switch (valid_tests[i].result_af) { 
     241        case IPv4: 
     242            valid_tests[i].result_af = PJ_AF_INET; 
     243            break; 
     244        case IPv6: 
     245            valid_tests[i].result_af = PJ_AF_INET6; 
     246            break; 
     247        default: 
     248            pj_assert(!"Invalid AF!"); 
     249            continue; 
     250        } 
     251 
     252        /* Try parsing with PJ_AF_UNSPEC */ 
     253        status = pj_sockaddr_parse(PJ_AF_UNSPEC, 0,  
     254                                   pj_cstr(&input, valid_tests[i].input),  
     255                                   &addr); 
     256        if (status != PJ_SUCCESS) { 
     257            PJ_LOG(1,("test", ".... failed when parsing %s",  
     258                      valid_tests[i].input)); 
     259            return -10; 
     260        } 
     261 
     262        /* Build the correct result */ 
     263        status = pj_sockaddr_init(valid_tests[i].result_af, 
     264                                  &result, 
     265                                  pj_cstr(&input, valid_tests[i].result_ip),  
     266                                  valid_tests[i].result_port); 
     267        if (status != PJ_SUCCESS) { 
     268            PJ_LOG(1,("test", ".... error building IP address %s",  
     269                      valid_tests[i].input)); 
     270            return -30; 
     271        } 
     272 
     273        /* Compare the result */ 
     274        if (pj_sockaddr_cmp(&addr, &result) != 0) { 
     275            PJ_LOG(1,("test", ".... parsed result mismatched for %s",  
     276                      valid_tests[i].input)); 
     277            return -40; 
     278        } 
     279 
     280        /* Parse again with the specified af */ 
     281        status = pj_sockaddr_parse(valid_tests[i].result_af, 0,  
     282                                   pj_cstr(&input, valid_tests[i].input),  
     283                                   &addr); 
     284        if (status != PJ_SUCCESS) { 
     285            PJ_LOG(1,("test", ".... failed when parsing %s",  
     286                      valid_tests[i].input)); 
     287            return -50; 
     288        } 
     289 
     290        /* Compare the result again */ 
     291        if (pj_sockaddr_cmp(&addr, &result) != 0) { 
     292            PJ_LOG(1,("test", ".... parsed result mismatched for %s",  
     293                      valid_tests[i].input)); 
     294            return -60; 
     295        } 
     296    } 
     297 
     298    for (i=0; i<PJ_ARRAY_SIZE(invalid_tests); ++i) { 
     299        pj_status_t status; 
     300        pj_str_t input; 
     301        pj_sockaddr addr; 
     302 
     303        switch (invalid_tests[i].result_af) { 
     304        case IPv4: 
     305            invalid_tests[i].result_af = PJ_AF_INET; 
     306            break; 
     307        case IPv6: 
     308            invalid_tests[i].result_af = PJ_AF_INET6; 
     309            break; 
     310        default: 
     311            pj_assert(!"Invalid AF!"); 
     312            continue; 
     313        } 
     314 
     315        /* Try parsing with PJ_AF_UNSPEC */ 
     316        status = pj_sockaddr_parse(PJ_AF_UNSPEC, 0,  
     317                                   pj_cstr(&input, invalid_tests[i].input),  
     318                                   &addr); 
     319        if (status == PJ_SUCCESS) { 
     320            PJ_LOG(1,("test", ".... expecting failure when parsing %s",  
     321                      invalid_tests[i].input)); 
     322            return -100; 
     323        } 
     324    } 
    170325 
    171326    return 0; 
     
    577732        return rc; 
    578733 
     734    rc = parse_test(); 
     735    if (rc != 0) 
     736        return rc; 
     737 
    579738    rc = gethostbyname_test(); 
    580739    if (rc != 0) 
Note: See TracChangeset for help on using the changeset viewer.