| /** |
| * @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; |
| } |