diff options
Diffstat (limited to 'pjlib/src/pj/sock_common.c')
-rw-r--r-- | pjlib/src/pj/sock_common.c | 1196 |
1 files changed, 1196 insertions, 0 deletions
diff --git a/pjlib/src/pj/sock_common.c b/pjlib/src/pj/sock_common.c new file mode 100644 index 0000000..dd2ef6e --- /dev/null +++ b/pjlib/src/pj/sock_common.c @@ -0,0 +1,1196 @@ +/* $Id: sock_common.c 3841 2011-10-24 09:28:13Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#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> +#include <pj/addr_resolv.h> +#include <pj/string.h> +#include <pj/compat/socket.h> + +#if 0 + /* Enable some tracing */ + #include <pj/log.h> + #define THIS_FILE "sock_common.c" + #define TRACE_(arg) PJ_LOG(4,arg) +#else + #define TRACE_(arg) +#endif + + +/* + * Convert address string with numbers and dots to binary IP address. + */ +PJ_DEF(pj_in_addr) pj_inet_addr(const pj_str_t *cp) +{ + pj_in_addr addr; + + pj_inet_aton(cp, &addr); + return addr; +} + +/* + * Convert address string with numbers and dots to binary IP address. + */ +PJ_DEF(pj_in_addr) pj_inet_addr2(const char *cp) +{ + pj_str_t str = pj_str((char*)cp); + return pj_inet_addr(&str); +} + +/* + * Get text representation. + */ +PJ_DEF(char*) pj_inet_ntop2( int af, const void *src, + char *dst, int size) +{ + pj_status_t status; + + status = pj_inet_ntop(af, src, dst, size); + return (status==PJ_SUCCESS)? dst : NULL; +} + +/* + * Print socket address. + */ +PJ_DEF(char*) pj_sockaddr_print( const pj_sockaddr_t *addr, + char *buf, int size, + unsigned flags) +{ + enum { + WITH_PORT = 1, + WITH_BRACKETS = 2 + }; + + char txt[PJ_INET6_ADDRSTRLEN]; + char port[32]; + const pj_addr_hdr *h = (const pj_addr_hdr*)addr; + char *bquote, *equote; + pj_status_t status; + + status = pj_inet_ntop(h->sa_family, pj_sockaddr_get_addr(addr), + txt, sizeof(txt)); + if (status != PJ_SUCCESS) + return ""; + + if (h->sa_family != PJ_AF_INET6 || (flags & WITH_BRACKETS)==0) { + bquote = ""; equote = ""; + } else { + bquote = "["; equote = "]"; + } + + if (flags & WITH_PORT) { + pj_ansi_snprintf(port, sizeof(port), ":%d", + pj_sockaddr_get_port(addr)); + } else { + port[0] = '\0'; + } + + pj_ansi_snprintf(buf, size, "%s%s%s%s", + bquote, txt, equote, port); + + return buf; +} + +/* + * Set the IP address of an IP socket address from string address, + * with resolving the host if necessary. The string address may be in a + * standard numbers and dots notation or may be a hostname. If hostname + * is specified, then the function will resolve the host into the IP + * address. + */ +PJ_DEF(pj_status_t) pj_sockaddr_in_set_str_addr( pj_sockaddr_in *addr, + const pj_str_t *str_addr) +{ + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(!str_addr || str_addr->slen < PJ_MAX_HOSTNAME, + (addr->sin_addr.s_addr=PJ_INADDR_NONE, PJ_EINVAL)); + + PJ_SOCKADDR_RESET_LEN(addr); + addr->sin_family = AF_INET; + pj_bzero(addr->sin_zero, sizeof(addr->sin_zero)); + + if (str_addr && str_addr->slen) { + addr->sin_addr = pj_inet_addr(str_addr); + if (addr->sin_addr.s_addr == PJ_INADDR_NONE) { + pj_hostent he; + pj_status_t rc; + + rc = pj_gethostbyname(str_addr, &he); + if (rc == 0) { + addr->sin_addr.s_addr = *(pj_uint32_t*)he.h_addr; + } else { + addr->sin_addr.s_addr = PJ_INADDR_NONE; + return rc; + } + } + + } else { + addr->sin_addr.s_addr = 0; + } + + return PJ_SUCCESS; +} + +/* Set address from a name */ +PJ_DEF(pj_status_t) pj_sockaddr_set_str_addr(int af, + pj_sockaddr *addr, + const pj_str_t *str_addr) +{ + pj_status_t status; + + if (af == PJ_AF_INET) { + return pj_sockaddr_in_set_str_addr(&addr->ipv4, str_addr); + } + + PJ_ASSERT_RETURN(af==PJ_AF_INET6, PJ_EAFNOTSUP); + + /* IPv6 specific */ + + addr->ipv6.sin6_family = PJ_AF_INET6; + PJ_SOCKADDR_RESET_LEN(addr); + + if (str_addr && str_addr->slen) { + status = pj_inet_pton(PJ_AF_INET6, str_addr, &addr->ipv6.sin6_addr); + if (status != PJ_SUCCESS) { + pj_addrinfo ai; + unsigned count = 1; + + status = pj_getaddrinfo(PJ_AF_INET6, str_addr, &count, &ai); + if (status==PJ_SUCCESS) { + pj_memcpy(&addr->ipv6.sin6_addr, &ai.ai_addr.ipv6.sin6_addr, + sizeof(pj_sockaddr_in6)); + } + } + } else { + status = PJ_SUCCESS; + } + + return status; +} + +/* + * Set the IP address and port of an IP socket address. + * The string address may be in a standard numbers and dots notation or + * may be a hostname. If hostname is specified, then the function will + * resolve the host into the IP address. + */ +PJ_DEF(pj_status_t) pj_sockaddr_in_init( pj_sockaddr_in *addr, + const pj_str_t *str_addr, + pj_uint16_t port) +{ + PJ_ASSERT_RETURN(addr, (addr->sin_addr.s_addr=PJ_INADDR_NONE, PJ_EINVAL)); + + PJ_SOCKADDR_RESET_LEN(addr); + addr->sin_family = PJ_AF_INET; + pj_bzero(addr->sin_zero, sizeof(addr->sin_zero)); + pj_sockaddr_in_set_port(addr, port); + return pj_sockaddr_in_set_str_addr(addr, str_addr); +} + +/* + * Initialize IP socket address based on the address and port info. + */ +PJ_DEF(pj_status_t) pj_sockaddr_init(int af, + pj_sockaddr *addr, + const pj_str_t *cp, + pj_uint16_t port) +{ + pj_status_t status; + + if (af == PJ_AF_INET) { + return pj_sockaddr_in_init(&addr->ipv4, cp, port); + } + + /* IPv6 specific */ + PJ_ASSERT_RETURN(af==PJ_AF_INET6, PJ_EAFNOTSUP); + + pj_bzero(addr, sizeof(pj_sockaddr_in6)); + addr->addr.sa_family = PJ_AF_INET6; + + status = pj_sockaddr_set_str_addr(af, addr, cp); + if (status != PJ_SUCCESS) + return status; + + addr->ipv6.sin6_port = pj_htons(port); + return PJ_SUCCESS; +} + +/* + * Compare two socket addresses. + */ +PJ_DEF(int) pj_sockaddr_cmp( const pj_sockaddr_t *addr1, + const pj_sockaddr_t *addr2) +{ + const pj_sockaddr *a1 = (const pj_sockaddr*) addr1; + const pj_sockaddr *a2 = (const pj_sockaddr*) addr2; + int port1, port2; + int result; + + /* Compare address family */ + if (a1->addr.sa_family < a2->addr.sa_family) + return -1; + else if (a1->addr.sa_family > a2->addr.sa_family) + return 1; + + /* Compare addresses */ + result = pj_memcmp(pj_sockaddr_get_addr(a1), + pj_sockaddr_get_addr(a2), + pj_sockaddr_get_addr_len(a1)); + if (result != 0) + return result; + + /* Compare port number */ + port1 = pj_sockaddr_get_port(a1); + port2 = pj_sockaddr_get_port(a2); + + if (port1 < port2) + return -1; + else if (port1 > port2) + return 1; + + /* TODO: + * Do we need to compare flow label and scope id in IPv6? + */ + + /* Looks equal */ + return 0; +} + +/* + * Get first IP address associated with the hostname. + */ +PJ_DEF(pj_in_addr) pj_gethostaddr(void) +{ + pj_sockaddr_in addr; + const pj_str_t *hostname = pj_gethostname(); + + pj_sockaddr_in_set_str_addr(&addr, hostname); + return addr.sin_addr; +} + +/* + * Get port number of a pj_sockaddr_in + */ +PJ_DEF(pj_uint16_t) pj_sockaddr_in_get_port(const pj_sockaddr_in *addr) +{ + return pj_ntohs(addr->sin_port); +} + +/* + * Get the address part + */ +PJ_DEF(void*) pj_sockaddr_get_addr(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr*)addr; + + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || + a->addr.sa_family == PJ_AF_INET6, NULL); + + if (a->addr.sa_family == PJ_AF_INET6) + return (void*) &a->ipv6.sin6_addr; + else + return (void*) &a->ipv4.sin_addr; +} + +/* + * Check if sockaddr contains a non-zero address + */ +PJ_DEF(pj_bool_t) pj_sockaddr_has_addr(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr*)addr; + + /* It's probably not wise to raise assertion here if + * the address doesn't contain a valid address family, and + * just return PJ_FALSE instead. + * + * The reason is because application may need to distinguish + * these three conditions with sockaddr: + * a) sockaddr is not initialized. This is by convention + * indicated by sa_family==0. + * b) sockaddr is initialized with zero address. This is + * indicated with the address field having zero address. + * c) sockaddr is initialized with valid address/port. + * + * If we enable this assertion, then application will loose + * the capability to specify condition a), since it will be + * forced to always initialize sockaddr (even with zero address). + * This may break some parts of upper layer libraries. + */ + //PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || + // a->addr.sa_family == PJ_AF_INET6, PJ_FALSE); + + if (a->addr.sa_family!=PJ_AF_INET && a->addr.sa_family!=PJ_AF_INET6) { + return PJ_FALSE; + } else if (a->addr.sa_family == PJ_AF_INET6) { + pj_uint8_t zero[24]; + pj_bzero(zero, sizeof(zero)); + return pj_memcmp(a->ipv6.sin6_addr.s6_addr, zero, + sizeof(pj_in6_addr)) != 0; + } else + return a->ipv4.sin_addr.s_addr != PJ_INADDR_ANY; +} + +/* + * Get port number + */ +PJ_DEF(pj_uint16_t) pj_sockaddr_get_port(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr*) addr; + + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || + a->addr.sa_family == PJ_AF_INET6, (pj_uint16_t)0xFFFF); + + return pj_ntohs((pj_uint16_t)(a->addr.sa_family == PJ_AF_INET6 ? + a->ipv6.sin6_port : a->ipv4.sin_port)); +} + +/* + * Get the length of the address part. + */ +PJ_DEF(unsigned) pj_sockaddr_get_addr_len(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr*) addr; + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || + a->addr.sa_family == PJ_AF_INET6, 0); + return a->addr.sa_family == PJ_AF_INET6 ? + sizeof(pj_in6_addr) : sizeof(pj_in_addr); +} + +/* + * Get socket address length. + */ +PJ_DEF(unsigned) pj_sockaddr_get_len(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr*) addr; + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || + a->addr.sa_family == PJ_AF_INET6, 0); + return a->addr.sa_family == PJ_AF_INET6 ? + sizeof(pj_sockaddr_in6) : sizeof(pj_sockaddr_in); +} + +/* + * Copy only the address part (sin_addr/sin6_addr) of a socket address. + */ +PJ_DEF(void) pj_sockaddr_copy_addr( pj_sockaddr *dst, + const pj_sockaddr *src) +{ + /* Destination sockaddr might not be initialized */ + const char *srcbuf = (char*)pj_sockaddr_get_addr(src); + char *dstbuf = ((char*)dst) + (srcbuf - (char*)src); + pj_memcpy(dstbuf, srcbuf, pj_sockaddr_get_addr_len(src)); +} + +/* + * Copy socket address. + */ +PJ_DEF(void) pj_sockaddr_cp(pj_sockaddr_t *dst, const pj_sockaddr_t *src) +{ + pj_memcpy(dst, src, pj_sockaddr_get_len(src)); +} + +/* + * Set port number of pj_sockaddr_in + */ +PJ_DEF(void) pj_sockaddr_in_set_port(pj_sockaddr_in *addr, + pj_uint16_t hostport) +{ + addr->sin_port = pj_htons(hostport); +} + +/* + * Set port number of pj_sockaddr + */ +PJ_DEF(pj_status_t) pj_sockaddr_set_port(pj_sockaddr *addr, + pj_uint16_t hostport) +{ + int af = addr->addr.sa_family; + + PJ_ASSERT_RETURN(af==PJ_AF_INET || af==PJ_AF_INET6, PJ_EINVAL); + + if (af == PJ_AF_INET6) + addr->ipv6.sin6_port = pj_htons(hostport); + else + addr->ipv4.sin_port = pj_htons(hostport); + + return PJ_SUCCESS; +} + +/* + * Get IPv4 address + */ +PJ_DEF(pj_in_addr) pj_sockaddr_in_get_addr(const pj_sockaddr_in *addr) +{ + pj_in_addr in_addr; + in_addr.s_addr = pj_ntohl(addr->sin_addr.s_addr); + return in_addr; +} + +/* + * Set IPv4 address + */ +PJ_DEF(void) pj_sockaddr_in_set_addr(pj_sockaddr_in *addr, + pj_uint32_t hostaddr) +{ + addr->sin_addr.s_addr = pj_htonl(hostaddr); +} + +/* + * Parse address + */ +PJ_DEF(pj_status_t) pj_sockaddr_parse2(int af, unsigned options, + const pj_str_t *str, + pj_str_t *p_hostpart, + pj_uint16_t *p_port, + int *raf) +{ + const char *end = str->ptr + str->slen; + const char *last_colon_pos = NULL; + unsigned colon_cnt = 0; + const char *p; + + PJ_ASSERT_RETURN((af==PJ_AF_INET || af==PJ_AF_INET6 || af==PJ_AF_UNSPEC) && + options==0 && + str!=NULL, PJ_EINVAL); + + /* Special handling for empty input */ + if (str->slen==0 || str->ptr==NULL) { + if (p_hostpart) + p_hostpart->slen = 0; + if (p_port) + *p_port = 0; + if (raf) + *raf = PJ_AF_INET; + return PJ_SUCCESS; + } + + /* Count the colon and get the last colon */ + for (p=str->ptr; p!=end; ++p) { + if (*p == ':') { + ++colon_cnt; + last_colon_pos = p; + } + } + + /* Deduce address family if it's not given */ + if (af == PJ_AF_UNSPEC) { + if (colon_cnt > 1) + af = PJ_AF_INET6; + else + af = PJ_AF_INET; + } else if (af == PJ_AF_INET && colon_cnt > 1) + return PJ_EINVAL; + + if (raf) + *raf = af; + + 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 hostpart; + unsigned long port; + + hostpart.ptr = (char*)str->ptr; + + if (last_colon_pos) { + pj_str_t port_part; + int i; + + hostpart.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 { + hostpart.slen = str->slen; + port = 0; + } + + if (p_hostpart) + *p_hostpart = hostpart; + if (p_port) + *p_port = (pj_uint16_t)port; + + return PJ_SUCCESS; + + } else if (af == PJ_AF_INET6) { + + /* Parse as IPv6. 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 hostpart, port_part; + + if (*str->ptr == '[') { + char *end_bracket; + int i; + unsigned long port; + + if (last_colon_pos == NULL) + return PJ_EINVAL; + + end_bracket = pj_strchr(str, ']'); + if (end_bracket == NULL) + return PJ_EINVAL; + + hostpart.ptr = (char*)str->ptr + 1; + hostpart.slen = end_bracket - hostpart.ptr; + + 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; + + if (p_hostpart) + *p_hostpart = hostpart; + if (p_port) + *p_port = (pj_uint16_t)port; + + return PJ_SUCCESS; + + } else { + /* Treat everything as part of the IPv6 IP address */ + if (p_hostpart) + *p_hostpart = *str; + if (p_port) + *p_port = 0; + + return PJ_SUCCESS; + } + + } else { + return PJ_EAFNOTSUP; + } + +} + +/* + * Parse address + */ +PJ_DEF(pj_status_t) pj_sockaddr_parse( int af, unsigned options, + const pj_str_t *str, + pj_sockaddr *addr) +{ + pj_str_t hostpart; + pj_uint16_t port; + pj_status_t status; + + 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); + + status = pj_sockaddr_parse2(af, options, str, &hostpart, &port, &af); + if (status != PJ_SUCCESS) + return status; + +#if !defined(PJ_HAS_IPV6) || !PJ_HAS_IPV6 + if (af==PJ_AF_INET6) + return PJ_EIPV6NOTSUP; +#endif + + status = pj_sockaddr_init(af, addr, &hostpart, port); +#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 + if (status != PJ_SUCCESS && af == PJ_AF_INET6) { + /* Parsing does not yield valid address. Try to treat the last + * portion after the colon as port number. + */ + const char *last_colon_pos=NULL, *p; + const char *end = str->ptr + str->slen; + unsigned long long_port; + pj_str_t port_part; + int i; + + /* Parse as IPv6:port */ + for (p=str->ptr; p!=end; ++p) { + if (*p == ':') + last_colon_pos = p; + } + + if (last_colon_pos == NULL) + return status; + + hostpart.ptr = (char*)str->ptr; + hostpart.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 status; + } + long_port = pj_strtoul(&port_part); + if (long_port > 65535) + return status; + + port = (pj_uint16_t)long_port; + + status = pj_sockaddr_init(PJ_AF_INET6, addr, &hostpart, port); + } +#endif + + return status; +} + +/* Resolve the IP address of local machine */ +PJ_DEF(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr) +{ + unsigned i, count, cand_cnt; + enum { + CAND_CNT = 8, + + /* Weighting to be applied to found addresses */ + WEIGHT_HOSTNAME = 1, /* hostname IP is not always valid! */ + WEIGHT_DEF_ROUTE = 2, + WEIGHT_INTERFACE = 1, + WEIGHT_LOOPBACK = -5, + WEIGHT_LINK_LOCAL = -4, + WEIGHT_DISABLED = -50, + + MIN_WEIGHT = WEIGHT_DISABLED+1 /* minimum weight to use */ + }; + /* candidates: */ + pj_sockaddr cand_addr[CAND_CNT]; + int cand_weight[CAND_CNT]; + int selected_cand; + char strip[PJ_INET6_ADDRSTRLEN+10]; + /* Special IPv4 addresses. */ + struct spec_ipv4_t + { + pj_uint32_t addr; + pj_uint32_t mask; + int weight; + } spec_ipv4[] = + { + /* 127.0.0.0/8, loopback addr will be used if there is no other + * addresses. + */ + { 0x7f000000, 0xFF000000, WEIGHT_LOOPBACK }, + + /* 0.0.0.0/8, special IP that doesn't seem to be practically useful */ + { 0x00000000, 0xFF000000, WEIGHT_DISABLED }, + + /* 169.254.0.0/16, a zeroconf/link-local address, which has higher + * priority than loopback and will be used if there is no other + * valid addresses. + */ + { 0xa9fe0000, 0xFFFF0000, WEIGHT_LINK_LOCAL } + }; + /* Special IPv6 addresses */ + struct spec_ipv6_t + { + pj_uint8_t addr[16]; + pj_uint8_t mask[16]; + int weight; + } spec_ipv6[] = + { + /* Loopback address, ::1/128 */ + { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, + {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, + WEIGHT_LOOPBACK + }, + + /* Link local, fe80::/10 */ + { {0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0xff,0xc0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + WEIGHT_LINK_LOCAL + }, + + /* Disabled, ::/128 */ + { {0x0,0x0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + { 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, + WEIGHT_DISABLED + } + }; + pj_addrinfo ai; + pj_status_t status; + + /* May not be used if TRACE_ is disabled */ + PJ_UNUSED_ARG(strip); + +#ifdef _MSC_VER + /* Get rid of "uninitialized he variable" with MS compilers */ + pj_bzero(&ai, sizeof(ai)); +#endif + + cand_cnt = 0; + pj_bzero(cand_addr, sizeof(cand_addr)); + pj_bzero(cand_weight, sizeof(cand_weight)); + for (i=0; i<PJ_ARRAY_SIZE(cand_addr); ++i) { + cand_addr[i].addr.sa_family = (pj_uint16_t)af; + PJ_SOCKADDR_RESET_LEN(&cand_addr[i]); + } + + addr->addr.sa_family = (pj_uint16_t)af; + PJ_SOCKADDR_RESET_LEN(addr); + +#if !defined(PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION) || \ + PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION == 0 + /* Get hostname's IP address */ + count = 1; + status = pj_getaddrinfo(af, pj_gethostname(), &count, &ai); + if (status == PJ_SUCCESS) { + pj_assert(ai.ai_addr.addr.sa_family == (pj_uint16_t)af); + pj_sockaddr_copy_addr(&cand_addr[cand_cnt], &ai.ai_addr); + pj_sockaddr_set_port(&cand_addr[cand_cnt], 0); + cand_weight[cand_cnt] += WEIGHT_HOSTNAME; + ++cand_cnt; + + TRACE_((THIS_FILE, "hostname IP is %s", + pj_sockaddr_print(&ai.ai_addr, strip, sizeof(strip), 0))); + } +#else + PJ_UNUSED_ARG(ai); + PJ_UNUSED_ARG(count); +#endif + + /* Get default interface (interface for default route) */ + if (cand_cnt < PJ_ARRAY_SIZE(cand_addr)) { + status = pj_getdefaultipinterface(af, addr); + if (status == PJ_SUCCESS) { + TRACE_((THIS_FILE, "default IP is %s", + pj_sockaddr_print(addr, strip, sizeof(strip), 0))); + + pj_sockaddr_set_port(addr, 0); + for (i=0; i<cand_cnt; ++i) { + if (pj_sockaddr_cmp(&cand_addr[i], addr)==0) + break; + } + + cand_weight[i] += WEIGHT_DEF_ROUTE; + if (i >= cand_cnt) { + pj_sockaddr_copy_addr(&cand_addr[i], addr); + ++cand_cnt; + } + } + } + + + /* Enumerate IP interfaces */ + if (cand_cnt < PJ_ARRAY_SIZE(cand_addr)) { + unsigned start_if = cand_cnt; + unsigned count = PJ_ARRAY_SIZE(cand_addr) - start_if; + + status = pj_enum_ip_interface(af, &count, &cand_addr[start_if]); + if (status == PJ_SUCCESS && count) { + /* Clear the port number */ + for (i=0; i<count; ++i) + pj_sockaddr_set_port(&cand_addr[start_if+i], 0); + + /* For each candidate that we found so far (that is the hostname + * address and default interface address, check if they're found + * in the interface list. If found, add the weight, and if not, + * decrease the weight. + */ + for (i=0; i<cand_cnt; ++i) { + unsigned j; + for (j=0; j<count; ++j) { + if (pj_sockaddr_cmp(&cand_addr[i], + &cand_addr[start_if+j])==0) + break; + } + + if (j == count) { + /* Not found */ + cand_weight[i] -= WEIGHT_INTERFACE; + } else { + cand_weight[i] += WEIGHT_INTERFACE; + } + } + + /* Add remaining interface to candidate list. */ + for (i=0; i<count; ++i) { + unsigned j; + for (j=0; j<cand_cnt; ++j) { + if (pj_sockaddr_cmp(&cand_addr[start_if+i], + &cand_addr[j])==0) + break; + } + + if (j == cand_cnt) { + pj_sockaddr_copy_addr(&cand_addr[cand_cnt], + &cand_addr[start_if+i]); + cand_weight[cand_cnt] += WEIGHT_INTERFACE; + ++cand_cnt; + } + } + } + } + + /* Apply weight adjustment for special IPv4/IPv6 addresses + * See http://trac.pjsip.org/repos/ticket/1046 + */ + if (af == PJ_AF_INET) { + for (i=0; i<cand_cnt; ++i) { + unsigned j; + for (j=0; j<PJ_ARRAY_SIZE(spec_ipv4); ++j) { + pj_uint32_t a = pj_ntohl(cand_addr[i].ipv4.sin_addr.s_addr); + pj_uint32_t pa = spec_ipv4[j].addr; + pj_uint32_t pm = spec_ipv4[j].mask; + + if ((a & pm) == pa) { + cand_weight[i] += spec_ipv4[j].weight; + break; + } + } + } + } else if (af == PJ_AF_INET6) { + for (i=0; i<PJ_ARRAY_SIZE(spec_ipv6); ++i) { + unsigned j; + for (j=0; j<cand_cnt; ++j) { + pj_uint8_t *a = cand_addr[j].ipv6.sin6_addr.s6_addr; + pj_uint8_t am[16]; + pj_uint8_t *pa = spec_ipv6[i].addr; + pj_uint8_t *pm = spec_ipv6[i].mask; + unsigned k; + + for (k=0; k<16; ++k) { + am[k] = (pj_uint8_t)((a[k] & pm[k]) & 0xFF); + } + + if (pj_memcmp(am, pa, 16)==0) { + cand_weight[j] += spec_ipv6[i].weight; + } + } + } + } else { + return PJ_EAFNOTSUP; + } + + /* Enumerate candidates to get the best IP address to choose */ + selected_cand = -1; + for (i=0; i<cand_cnt; ++i) { + TRACE_((THIS_FILE, "Checking candidate IP %s, weight=%d", + pj_sockaddr_print(&cand_addr[i], strip, sizeof(strip), 0), + cand_weight[i])); + + if (cand_weight[i] < MIN_WEIGHT) { + continue; + } + + if (selected_cand == -1) + selected_cand = i; + else if (cand_weight[i] > cand_weight[selected_cand]) + selected_cand = i; + } + + /* If else fails, returns loopback interface as the last resort */ + if (selected_cand == -1) { + if (af==PJ_AF_INET) { + addr->ipv4.sin_addr.s_addr = pj_htonl (0x7f000001); + } else { + pj_in6_addr *s6_addr; + + s6_addr = (pj_in6_addr*) pj_sockaddr_get_addr(addr); + pj_bzero(s6_addr, sizeof(pj_in6_addr)); + s6_addr->s6_addr[15] = 1; + } + TRACE_((THIS_FILE, "Loopback IP %s returned", + pj_sockaddr_print(addr, strip, sizeof(strip), 0))); + } else { + pj_sockaddr_copy_addr(addr, &cand_addr[selected_cand]); + TRACE_((THIS_FILE, "Candidate %s selected", + pj_sockaddr_print(addr, strip, sizeof(strip), 0))); + } + + return PJ_SUCCESS; +} + +/* Get the default IP interface */ +PJ_DEF(pj_status_t) pj_getdefaultipinterface(int af, pj_sockaddr *addr) +{ + pj_sock_t fd; + pj_str_t cp; + pj_sockaddr a; + int len; + pj_uint8_t zero[64]; + pj_status_t status; + + addr->addr.sa_family = (pj_uint16_t)af; + + status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &fd); + if (status != PJ_SUCCESS) { + return status; + } + + if (af == PJ_AF_INET) { + cp = pj_str("1.1.1.1"); + } else { + cp = pj_str("1::1"); + } + status = pj_sockaddr_init(af, &a, &cp, 53); + if (status != PJ_SUCCESS) { + pj_sock_close(fd); + return status; + } + + status = pj_sock_connect(fd, &a, pj_sockaddr_get_len(&a)); + if (status != PJ_SUCCESS) { + pj_sock_close(fd); + return status; + } + + len = sizeof(a); + status = pj_sock_getsockname(fd, &a, &len); + if (status != PJ_SUCCESS) { + pj_sock_close(fd); + return status; + } + + pj_sock_close(fd); + + /* Check that the address returned is not zero */ + pj_bzero(zero, sizeof(zero)); + if (pj_memcmp(pj_sockaddr_get_addr(&a), zero, + pj_sockaddr_get_addr_len(&a))==0) + { + return PJ_ENOTFOUND; + } + + pj_sockaddr_copy_addr(addr, &a); + + /* Success */ + return PJ_SUCCESS; +} + + +/* Only need to implement these in DLL build */ +#if defined(PJ_DLL) + +PJ_DEF(pj_uint16_t) pj_AF_UNSPEC(void) +{ + return PJ_AF_UNSPEC; +} + +PJ_DEF(pj_uint16_t) pj_AF_UNIX(void) +{ + return PJ_AF_UNIX; +} + +PJ_DEF(pj_uint16_t) pj_AF_INET(void) +{ + return PJ_AF_INET; +} + +PJ_DEF(pj_uint16_t) pj_AF_INET6(void) +{ + return PJ_AF_INET6; +} + +PJ_DEF(pj_uint16_t) pj_AF_PACKET(void) +{ + return PJ_AF_PACKET; +} + +PJ_DEF(pj_uint16_t) pj_AF_IRDA(void) +{ + return PJ_AF_IRDA; +} + +PJ_DEF(int) pj_SOCK_STREAM(void) +{ + return PJ_SOCK_STREAM; +} + +PJ_DEF(int) pj_SOCK_DGRAM(void) +{ + return PJ_SOCK_DGRAM; +} + +PJ_DEF(int) pj_SOCK_RAW(void) +{ + return PJ_SOCK_RAW; +} + +PJ_DEF(int) pj_SOCK_RDM(void) +{ + return PJ_SOCK_RDM; +} + +PJ_DEF(pj_uint16_t) pj_SOL_SOCKET(void) +{ + return PJ_SOL_SOCKET; +} + +PJ_DEF(pj_uint16_t) pj_SOL_IP(void) +{ + return PJ_SOL_IP; +} + +PJ_DEF(pj_uint16_t) pj_SOL_TCP(void) +{ + return PJ_SOL_TCP; +} + +PJ_DEF(pj_uint16_t) pj_SOL_UDP(void) +{ + return PJ_SOL_UDP; +} + +PJ_DEF(pj_uint16_t) pj_SOL_IPV6(void) +{ + return PJ_SOL_IPV6; +} + +PJ_DEF(int) pj_IP_TOS(void) +{ + return PJ_IP_TOS; +} + +PJ_DEF(int) pj_IPTOS_LOWDELAY(void) +{ + return PJ_IPTOS_LOWDELAY; +} + +PJ_DEF(int) pj_IPTOS_THROUGHPUT(void) +{ + return PJ_IPTOS_THROUGHPUT; +} + +PJ_DEF(int) pj_IPTOS_RELIABILITY(void) +{ + return PJ_IPTOS_RELIABILITY; +} + +PJ_DEF(int) pj_IPTOS_MINCOST(void) +{ + return PJ_IPTOS_MINCOST; +} + +PJ_DEF(pj_uint16_t) pj_SO_TYPE(void) +{ + return PJ_SO_TYPE; +} + +PJ_DEF(pj_uint16_t) pj_SO_RCVBUF(void) +{ + return PJ_SO_RCVBUF; +} + +PJ_DEF(pj_uint16_t) pj_SO_SNDBUF(void) +{ + return PJ_SO_SNDBUF; +} + +PJ_DEF(pj_uint16_t) pj_TCP_NODELAY(void) +{ + return PJ_TCP_NODELAY; +} + +PJ_DEF(pj_uint16_t) pj_SO_REUSEADDR(void) +{ + return PJ_SO_REUSEADDR; +} + +PJ_DEF(pj_uint16_t) pj_SO_NOSIGPIPE(void) +{ + return PJ_SO_NOSIGPIPE; +} + +PJ_DEF(pj_uint16_t) pj_SO_PRIORITY(void) +{ + return PJ_SO_PRIORITY; +} + +PJ_DEF(pj_uint16_t) pj_IP_MULTICAST_IF(void) +{ + return PJ_IP_MULTICAST_IF; +} + +PJ_DEF(pj_uint16_t) pj_IP_MULTICAST_TTL(void) +{ + return PJ_IP_MULTICAST_TTL; +} + +PJ_DEF(pj_uint16_t) pj_IP_MULTICAST_LOOP(void) +{ + return PJ_IP_MULTICAST_LOOP; +} + +PJ_DEF(pj_uint16_t) pj_IP_ADD_MEMBERSHIP(void) +{ + return PJ_IP_ADD_MEMBERSHIP; +} + +PJ_DEF(pj_uint16_t) pj_IP_DROP_MEMBERSHIP(void) +{ + return PJ_IP_DROP_MEMBERSHIP; +} + +PJ_DEF(int) pj_MSG_OOB(void) +{ + return PJ_MSG_OOB; +} + +PJ_DEF(int) pj_MSG_PEEK(void) +{ + return PJ_MSG_PEEK; +} + +PJ_DEF(int) pj_MSG_DONTROUTE(void) +{ + return PJ_MSG_DONTROUTE; +} + +#endif /* PJ_DLL */ + |