blob: c79b2c4efedcb0e3f80b33fdd92958c112840097 [file] [log] [blame]
/**
* @file dnsdisc.c DNS Discovery of a STUN Server
*
* Copyright (C) 2010 Creytiv.com
*/
#include <string.h>
#include <re_types.h>
#include <re_fmt.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_list.h>
#include <re_sa.h>
#include <re_dns.h>
#include <re_stun.h>
#define DEBUG_MODULE "dnsdisc"
#define DEBUG_LEVEL 5
#include <re_dbg.h>
/** DNS Query */
struct stun_dns {
char domain[256]; /**< Cached domain name */
stun_dns_h *dnsh; /**< DNS Response handler */
void *arg; /**< Handler argument */
struct sa srv; /**< Resolved server address */
struct dnsc *dnsc; /**< DNS Client */
struct dns_query *dq; /**< Current DNS query */
int af; /**< Preferred Address family*/
uint16_t port; /**< Default Port */
};
const char *stun_proto_udp = "udp"; /**< UDP Protocol */
const char *stun_proto_tcp = "tcp"; /**< TCP Protocol */
const char *stun_usage_binding = "stun"; /**< Binding usage */
const char *stuns_usage_binding = "stuns"; /**< Binding usage TLS */
const char *stun_usage_relay = "turn";
const char *stuns_usage_relay = "turns";
const char *stun_usage_behavior = "stun-behavior";
const char *stuns_usage_behavior = "stun-behaviors";
static void resolved(const struct stun_dns *dns, int err)
{
stun_dns_h *dnsh = dns->dnsh;
void *dnsh_arg = dns->arg;
DEBUG_INFO("resolved: %J (%m)\n", &dns->srv, err);
dnsh(err, &dns->srv, dnsh_arg);
}
static void a_handler(int err, const struct dnshdr *hdr, struct list *ansl,
struct list *authl, struct list *addl, void *arg)
{
struct stun_dns *dns = arg;
struct dnsrr *rr;
(void)hdr;
(void)authl;
(void)addl;
/* Find A answers */
rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_A, DNS_CLASS_IN, false);
if (!rr) {
err = err ? err : EDESTADDRREQ;
goto out;
}
sa_set_in(&dns->srv, rr->rdata.a.addr, sa_port(&dns->srv));
DEBUG_INFO("A answer: %j\n", &dns->srv);
out:
resolved(dns, err);
}
#ifdef HAVE_INET6
static void aaaa_handler(int err, const struct dnshdr *hdr, struct list *ansl,
struct list *authl, struct list *addl, void *arg)
{
struct stun_dns *dns = arg;
struct dnsrr *rr;
(void)hdr;
(void)authl;
(void)addl;
/* Find A answers */
rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_AAAA, DNS_CLASS_IN, false);
if (!rr) {
err = err ? err : EDESTADDRREQ;
goto out;
}
sa_set_in6(&dns->srv, rr->rdata.aaaa.addr, sa_port(&dns->srv));
DEBUG_INFO("AAAA answer: %j\n", &dns->srv);
out:
resolved(dns, err);
}
#endif
static int a_or_aaaa_query(struct stun_dns *dns, const char *name)
{
dns->dq = mem_deref(dns->dq);
switch (dns->af) {
case AF_INET:
return dnsc_query(&dns->dq, dns->dnsc, name, DNS_TYPE_A,
DNS_CLASS_IN, true, a_handler, dns);
#ifdef HAVE_INET6
case AF_INET6:
return dnsc_query(&dns->dq, dns->dnsc, name, DNS_TYPE_AAAA,
DNS_CLASS_IN, true, aaaa_handler, dns);
#endif
default:
return EAFNOSUPPORT;
}
}
static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
struct list *authl, struct list *addl, void *arg)
{
struct stun_dns *dns = arg;
struct dnsrr *rr, *arr;
(void)hdr;
(void)authl;
dns_rrlist_sort(ansl, DNS_TYPE_SRV, (size_t)dns->arg);
/* Find SRV answers */
rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_SRV, DNS_CLASS_IN, false);
if (!rr) {
DEBUG_INFO("no SRV entry, trying A lookup on \"%s\"\n",
dns->domain);
sa_set_in(&dns->srv, 0, dns->port);
err = a_or_aaaa_query(dns, dns->domain);
if (err)
goto out;
return;
}
DEBUG_INFO("SRV answer: %s:%u\n", rr->rdata.srv.target,
rr->rdata.srv.port);
/* Look for Additional information */
switch (dns->af) {
case AF_INET:
arr = dns_rrlist_find(addl, rr->rdata.srv.target,
DNS_TYPE_A, DNS_CLASS_IN, true);
if (arr) {
sa_set_in(&dns->srv, arr->rdata.a.addr,
rr->rdata.srv.port);
DEBUG_INFO("additional A: %j\n", &dns->srv);
goto out;
}
break;
#ifdef HAVE_INET6
case AF_INET6:
arr = dns_rrlist_find(addl, rr->rdata.srv.target,
DNS_TYPE_AAAA, DNS_CLASS_IN, true);
if (arr) {
sa_set_in6(&dns->srv, arr->rdata.aaaa.addr,
rr->rdata.srv.port);
DEBUG_INFO("additional AAAA: %j\n", &dns->srv);
goto out;
}
break;
#endif
}
sa_set_in(&dns->srv, 0, rr->rdata.srv.port);
err = a_or_aaaa_query(dns, rr->rdata.srv.target);
if (err) {
DEBUG_WARNING("SRV: A lookup failed (%m)\n", err);
goto out;
}
DEBUG_INFO("SRV handler: doing A/AAAA lookup..\n");
return;
out:
resolved(dns, err);
}
static void dnsdisc_destructor(void *data)
{
struct stun_dns *dns = data;
mem_deref(dns->dq);
}
/**
* Do a DNS Discovery of a STUN Server
*
* @param dnsp Pointer to allocated DNS Discovery object
* @param dnsc DNS Client
* @param service Name of service to discover (e.g. "stun")
* @param proto Transport protocol (e.g. "udp")
* @param af Preferred Address Family
* @param domain Domain name or IP address of STUN server
* @param port Port number (if 0 do SRV lookup)
* @param dnsh DNS Response handler
* @param arg Handler argument
*
* @return 0 if success, otherwise errorcode
*/
int stun_server_discover(struct stun_dns **dnsp, struct dnsc *dnsc,
const char *service, const char *proto,
int af, const char *domain, uint16_t port,
stun_dns_h *dnsh, void *arg)
{
struct stun_dns *dns;
int err;
if (!dnsp || !service || !proto || !domain || !domain[0] || !dnsh)
return EINVAL;
dns = mem_zalloc(sizeof(*dns), dnsdisc_destructor);
if (!dns)
return ENOMEM;
dns->port = service[strlen(service)-1] == 's' ? STUNS_PORT : STUN_PORT;
dns->dnsh = dnsh;
dns->arg = arg;
dns->dnsc = dnsc;
dns->af = af;
/* Numeric IP address - no lookup */
if (0 == sa_set_str(&dns->srv, domain, port ? port : dns->port)) {
DEBUG_INFO("IP (%s)\n", domain);
resolved(dns, 0);
err = 0;
goto out; /* free now */
}
/* Port specified - use AAAA or A lookup */
else if (port) {
sa_set_in(&dns->srv, 0, port);
DEBUG_INFO("resolving A query: (%s)\n", domain);
err = a_or_aaaa_query(dns, domain);
if (err) {
DEBUG_WARNING("%s: A/AAAA lookup failed (%m)\n",
domain, err);
goto out;
}
}
/* SRV lookup */
else {
char q[256];
str_ncpy(dns->domain, domain, sizeof(dns->domain));
(void)re_snprintf(q, sizeof(q), "_%s._%s.%s", service, proto,
domain);
DEBUG_INFO("resolving SRV query: (%s)\n", q);
err = dnsc_query(&dns->dq, dnsc, q, DNS_TYPE_SRV, DNS_CLASS_IN,
true, srv_handler, dns);
if (err) {
DEBUG_WARNING("%s: SRV lookup failed (%m)\n", q, err);
goto out;
}
}
*dnsp = dns;
return 0;
out:
mem_deref(dns);
return err;
}