diff options
Diffstat (limited to 'pjsip/src/pjsip/sip_resolve.c')
-rw-r--r-- | pjsip/src/pjsip/sip_resolve.c | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/pjsip/src/pjsip/sip_resolve.c b/pjsip/src/pjsip/sip_resolve.c new file mode 100644 index 0000000..f5efc1e --- /dev/null +++ b/pjsip/src/pjsip/sip_resolve.c @@ -0,0 +1,520 @@ +/* $Id: sip_resolve.c 4108 2012-04-27 01:32:12Z bennylp $ */ +/* + * 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 <pjsip/sip_resolve.h> +#include <pjsip/sip_transport.h> +#include <pjsip/sip_errno.h> +#include <pjlib-util/errno.h> +#include <pjlib-util/srv_resolver.h> +#include <pj/addr_resolv.h> +#include <pj/array.h> +#include <pj/assert.h> +#include <pj/ctype.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/rand.h> +#include <pj/string.h> + + +#define THIS_FILE "sip_resolve.c" + +#define ADDR_MAX_COUNT 8 + +struct naptr_target +{ + pj_str_t res_type; /**< e.g. "_sip._udp" */ + pj_str_t name; /**< Domain name. */ + pjsip_transport_type_e type; /**< Transport type. */ + unsigned order; /**< Order */ + unsigned pref; /**< Preference. */ +}; + +struct query +{ + char *objname; + + pj_dns_type query_type; + void *token; + pjsip_resolver_callback *cb; + pj_dns_async_query *object; + pj_status_t last_error; + + /* Original request: */ + struct { + pjsip_host_info target; + unsigned def_port; + } req; + + /* NAPTR records: */ + unsigned naptr_cnt; + struct naptr_target naptr[8]; +}; + + +struct pjsip_resolver_t +{ + pj_dns_resolver *res; +}; + + +static void srv_resolver_cb(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec); +static void dns_a_callback(void *user_data, + pj_status_t status, + pj_dns_parsed_packet *response); + + +/* + * Public API to create the resolver. + */ +PJ_DEF(pj_status_t) pjsip_resolver_create( pj_pool_t *pool, + pjsip_resolver_t **p_res) +{ + pjsip_resolver_t *resolver; + + PJ_ASSERT_RETURN(pool && p_res, PJ_EINVAL); + resolver = PJ_POOL_ZALLOC_T(pool, pjsip_resolver_t); + *p_res = resolver; + + return PJ_SUCCESS; +} + + +/* + * Public API to set the DNS resolver instance for the SIP resolver. + */ +PJ_DEF(pj_status_t) pjsip_resolver_set_resolver(pjsip_resolver_t *res, + pj_dns_resolver *dns_res) +{ +#if PJSIP_HAS_RESOLVER + res->res = dns_res; + return PJ_SUCCESS; +#else + PJ_UNUSED_ARG(res); + PJ_UNUSED_ARG(dns_res); + pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)"); + return PJ_EINVALIDOP; +#endif +} + + +/* + * Public API to get the internal DNS resolver. + */ +PJ_DEF(pj_dns_resolver*) pjsip_resolver_get_resolver(pjsip_resolver_t *res) +{ + return res->res; +} + + +/* + * Public API to create destroy the resolver + */ +PJ_DEF(void) pjsip_resolver_destroy(pjsip_resolver_t *resolver) +{ + if (resolver->res) { +#if PJSIP_HAS_RESOLVER + pj_dns_resolver_destroy(resolver->res, PJ_FALSE); +#endif + resolver->res = NULL; + } +} + +/* + * Internal: + * determine if an address is a valid IP address, and if it is, + * return the IP version (4 or 6). + */ +static int get_ip_addr_ver(const pj_str_t *host) +{ + pj_in_addr dummy; + pj_in6_addr dummy6; + + /* First check with inet_aton() */ + if (pj_inet_aton(host, &dummy) > 0) + return 4; + + /* Then check if this is an IPv6 address */ + if (pj_inet_pton(pj_AF_INET6(), host, &dummy6) == PJ_SUCCESS) + return 6; + + /* Not an IP address */ + return 0; +} + + +/* + * This is the main function for performing server resolution. + */ +PJ_DEF(void) pjsip_resolve( pjsip_resolver_t *resolver, + pj_pool_t *pool, + const pjsip_host_info *target, + void *token, + pjsip_resolver_callback *cb) +{ + pjsip_server_addresses svr_addr; + pj_status_t status = PJ_SUCCESS; + int ip_addr_ver; + struct query *query; + pjsip_transport_type_e type = target->type; + + /* Is it IP address or hostname? And if it's an IP, which version? */ + ip_addr_ver = get_ip_addr_ver(&target->addr.host); + + /* Set the transport type if not explicitly specified. + * RFC 3263 section 4.1 specify rules to set up this. + */ + if (type == PJSIP_TRANSPORT_UNSPECIFIED) { + if (ip_addr_ver || (target->addr.port != 0)) { +#if PJ_HAS_TCP + if (target->flag & PJSIP_TRANSPORT_SECURE) + { + type = PJSIP_TRANSPORT_TLS; + } else if (target->flag & PJSIP_TRANSPORT_RELIABLE) + { + type = PJSIP_TRANSPORT_TCP; + } else +#endif + { + type = PJSIP_TRANSPORT_UDP; + } + } else { + /* No type or explicit port is specified, and the address is + * not IP address. + * In this case, full NAPTR resolution must be performed. + * But we don't support it (yet). + */ +#if PJ_HAS_TCP + if (target->flag & PJSIP_TRANSPORT_SECURE) + { + type = PJSIP_TRANSPORT_TLS; + } else if (target->flag & PJSIP_TRANSPORT_RELIABLE) + { + type = PJSIP_TRANSPORT_TCP; + } else +#endif + { + type = PJSIP_TRANSPORT_UDP; + } + } + + /* Add IPv6 flag for IPv6 address */ + if (ip_addr_ver == 6) + type = (pjsip_transport_type_e)((int)type + PJSIP_TRANSPORT_IPV6); + } + + + /* If target is an IP address, or if resolver is not configured, + * we can just finish the resolution now using pj_gethostbyname() + */ + if (ip_addr_ver || resolver->res == NULL) { + char addr_str[PJ_INET6_ADDRSTRLEN+10]; + pj_uint16_t srv_port; + + if (ip_addr_ver != 0) { + /* Target is an IP address, no need to resolve */ + if (ip_addr_ver == 4) { + pj_sockaddr_init(pj_AF_INET(), &svr_addr.entry[0].addr, + NULL, 0); + pj_inet_aton(&target->addr.host, + &svr_addr.entry[0].addr.ipv4.sin_addr); + } else { + pj_sockaddr_init(pj_AF_INET6(), &svr_addr.entry[0].addr, + NULL, 0); + pj_inet_pton(pj_AF_INET6(), &target->addr.host, + &svr_addr.entry[0].addr.ipv6.sin6_addr); + } + } else { + pj_addrinfo ai; + unsigned count; + int af; + + PJ_LOG(5,(THIS_FILE, + "DNS resolver not available, target '%.*s:%d' type=%s " + "will be resolved with getaddrinfo()", + target->addr.host.slen, + target->addr.host.ptr, + target->addr.port, + pjsip_transport_get_type_name(target->type))); + + if (type & PJSIP_TRANSPORT_IPV6) { + af = pj_AF_INET6(); + } else { + af = pj_AF_INET(); + } + + /* Resolve */ + count = 1; + status = pj_getaddrinfo(af, &target->addr.host, &count, &ai); + if (status != PJ_SUCCESS) { + /* "Normalize" error to PJ_ERESOLVE. This is a special error + * because it will be translated to SIP status 502 by + * sip_transaction.c + */ + status = PJ_ERESOLVE; + goto on_error; + } + + svr_addr.entry[0].addr.addr.sa_family = (pj_uint16_t)af; + pj_memcpy(&svr_addr.entry[0].addr, &ai.ai_addr, + sizeof(pj_sockaddr)); + } + + /* Set the port number */ + if (target->addr.port == 0) { + srv_port = (pj_uint16_t) + pjsip_transport_get_default_port_for_type(type); + } else { + srv_port = (pj_uint16_t)target->addr.port; + } + pj_sockaddr_set_port(&svr_addr.entry[0].addr, srv_port); + + /* Call the callback. */ + PJ_LOG(5,(THIS_FILE, + "Target '%.*s:%d' type=%s resolved to " + "'%s' type=%s (%s)", + (int)target->addr.host.slen, + target->addr.host.ptr, + target->addr.port, + pjsip_transport_get_type_name(target->type), + pj_sockaddr_print(&svr_addr.entry[0].addr, addr_str, + sizeof(addr_str), 3), + pjsip_transport_get_type_name(type), + pjsip_transport_get_type_desc(type))); + svr_addr.count = 1; + svr_addr.entry[0].priority = 0; + svr_addr.entry[0].weight = 0; + svr_addr.entry[0].type = type; + svr_addr.entry[0].addr_len = pj_sockaddr_get_len(&svr_addr.entry[0].addr); + (*cb)(status, token, &svr_addr); + + /* Done. */ + return; + } + + /* Target is not an IP address so we need to resolve it. */ +#if PJSIP_HAS_RESOLVER + + /* Build the query state */ + query = PJ_POOL_ZALLOC_T(pool, struct query); + query->objname = THIS_FILE; + query->token = token; + query->cb = cb; + query->req.target = *target; + pj_strdup(pool, &query->req.target.addr.host, &target->addr.host); + + /* If port is not specified, start with SRV resolution + * (should be with NAPTR, but we'll do that later) + */ + PJ_TODO(SUPPORT_DNS_NAPTR); + + /* Build dummy NAPTR entry */ + query->naptr_cnt = 1; + pj_bzero(&query->naptr[0], sizeof(query->naptr[0])); + query->naptr[0].order = 0; + query->naptr[0].pref = 0; + query->naptr[0].type = type; + pj_strdup(pool, &query->naptr[0].name, &target->addr.host); + + + /* Start DNS SRV or A resolution, depending on whether port is specified */ + if (target->addr.port == 0) { + query->query_type = PJ_DNS_TYPE_SRV; + + query->req.def_port = 5060; + + if (type == PJSIP_TRANSPORT_TLS) { + query->naptr[0].res_type = pj_str("_sips._tcp."); + query->req.def_port = 5061; + } else if (type == PJSIP_TRANSPORT_TCP) + query->naptr[0].res_type = pj_str("_sip._tcp."); + else if (type == PJSIP_TRANSPORT_UDP) + query->naptr[0].res_type = pj_str("_sip._udp."); + else { + pj_assert(!"Unknown transport type"); + query->naptr[0].res_type = pj_str("_sip._udp."); + + } + + } else { + /* Otherwise if port is specified, start with A (or AAAA) host + * resolution + */ + query->query_type = PJ_DNS_TYPE_A; + query->naptr[0].res_type.slen = 0; + query->req.def_port = target->addr.port; + } + + /* Start the asynchronous query */ + PJ_LOG(5, (query->objname, + "Starting async DNS %s query: target=%.*s%.*s, transport=%s, " + "port=%d", + pj_dns_get_type_name(query->query_type), + (int)query->naptr[0].res_type.slen, + query->naptr[0].res_type.ptr, + (int)query->naptr[0].name.slen, query->naptr[0].name.ptr, + pjsip_transport_get_type_name(target->type), + target->addr.port)); + + if (query->query_type == PJ_DNS_TYPE_SRV) { + + status = pj_dns_srv_resolve(&query->naptr[0].name, + &query->naptr[0].res_type, + query->req.def_port, pool, resolver->res, + PJ_TRUE, query, &srv_resolver_cb, NULL); + + } else if (query->query_type == PJ_DNS_TYPE_A) { + + status = pj_dns_resolver_start_query(resolver->res, + &query->naptr[0].name, + PJ_DNS_TYPE_A, 0, + &dns_a_callback, + query, &query->object); + + } else { + pj_assert(!"Unexpected"); + status = PJ_EBUG; + } + + if (status != PJ_SUCCESS) + goto on_error; + + return; + +#else /* PJSIP_HAS_RESOLVER */ + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(query); + PJ_UNUSED_ARG(srv_name); +#endif /* PJSIP_HAS_RESOLVER */ + +on_error: + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + PJ_LOG(4,(THIS_FILE, "Failed to resolve '%.*s'. Err=%d (%s)", + (int)target->addr.host.slen, + target->addr.host.ptr, + status, + pj_strerror(status,errmsg,sizeof(errmsg)).ptr)); + (*cb)(status, token, NULL); + return; + } +} + +#if PJSIP_HAS_RESOLVER + +/* + * This callback is called when target is resolved with DNS A query. + */ +static void dns_a_callback(void *user_data, + pj_status_t status, + pj_dns_parsed_packet *pkt) +{ + struct query *query = (struct query*) user_data; + pjsip_server_addresses srv; + pj_dns_a_record rec; + unsigned i; + + rec.addr_count = 0; + + /* Parse the response */ + if (status == PJ_SUCCESS) { + status = pj_dns_parse_a_response(pkt, &rec); + } + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + /* Log error */ + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(query->objname, "DNS A record resolution failed: %s", + errmsg)); + + /* Call the callback */ + (*query->cb)(status, query->token, NULL); + return; + } + + /* Build server addresses and call callback */ + srv.count = 0; + for (i=0; i<rec.addr_count; ++i) { + srv.entry[srv.count].type = query->naptr[0].type; + srv.entry[srv.count].priority = 0; + srv.entry[srv.count].weight = 0; + srv.entry[srv.count].addr_len = sizeof(pj_sockaddr_in); + pj_sockaddr_in_init(&srv.entry[srv.count].addr.ipv4, + 0, (pj_uint16_t)query->req.def_port); + srv.entry[srv.count].addr.ipv4.sin_addr.s_addr = + rec.addr[i].s_addr; + + ++srv.count; + } + + /* Call the callback */ + (*query->cb)(PJ_SUCCESS, query->token, &srv); +} + + +/* Callback to be called by DNS SRV resolution */ +static void srv_resolver_cb(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + struct query *query = (struct query*) user_data; + pjsip_server_addresses srv; + unsigned i; + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + /* Log error */ + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(query->objname, "DNS A record resolution failed: %s", + errmsg)); + + /* Call the callback */ + (*query->cb)(status, query->token, NULL); + return; + } + + /* Build server addresses and call callback */ + srv.count = 0; + for (i=0; i<rec->count; ++i) { + unsigned j; + + for (j=0; j<rec->entry[i].server.addr_count; ++j) { + srv.entry[srv.count].type = query->naptr[0].type; + srv.entry[srv.count].priority = rec->entry[i].priority; + srv.entry[srv.count].weight = rec->entry[i].weight; + srv.entry[srv.count].addr_len = sizeof(pj_sockaddr_in); + pj_sockaddr_in_init(&srv.entry[srv.count].addr.ipv4, + 0, (pj_uint16_t)rec->entry[i].port); + srv.entry[srv.count].addr.ipv4.sin_addr.s_addr = + rec->entry[i].server.addr[j].s_addr; + + ++srv.count; + } + } + + /* Call the callback */ + (*query->cb)(PJ_SUCCESS, query->token, &srv); +} + +#endif /* PJSIP_HAS_RESOLVER */ + |