blob: fb23b538e73d2b8812990f5aa73721b1784840c6 [file] [log] [blame]
/* $Id$ */
/*
* 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 */