James Kuszmaul | 82f6c04 | 2021-01-17 11:30:16 -0800 | [diff] [blame^] | 1 | /** |
| 2 | * @file dnsdisc.c DNS Discovery of a STUN Server |
| 3 | * |
| 4 | * Copyright (C) 2010 Creytiv.com |
| 5 | */ |
| 6 | #include <string.h> |
| 7 | #include <re_types.h> |
| 8 | #include <re_fmt.h> |
| 9 | #include <re_mem.h> |
| 10 | #include <re_mbuf.h> |
| 11 | #include <re_list.h> |
| 12 | #include <re_sa.h> |
| 13 | #include <re_dns.h> |
| 14 | #include <re_stun.h> |
| 15 | |
| 16 | |
| 17 | #define DEBUG_MODULE "dnsdisc" |
| 18 | #define DEBUG_LEVEL 5 |
| 19 | #include <re_dbg.h> |
| 20 | |
| 21 | |
| 22 | /** DNS Query */ |
| 23 | struct stun_dns { |
| 24 | char domain[256]; /**< Cached domain name */ |
| 25 | stun_dns_h *dnsh; /**< DNS Response handler */ |
| 26 | void *arg; /**< Handler argument */ |
| 27 | struct sa srv; /**< Resolved server address */ |
| 28 | struct dnsc *dnsc; /**< DNS Client */ |
| 29 | struct dns_query *dq; /**< Current DNS query */ |
| 30 | int af; /**< Preferred Address family*/ |
| 31 | uint16_t port; /**< Default Port */ |
| 32 | }; |
| 33 | |
| 34 | const char *stun_proto_udp = "udp"; /**< UDP Protocol */ |
| 35 | const char *stun_proto_tcp = "tcp"; /**< TCP Protocol */ |
| 36 | |
| 37 | const char *stun_usage_binding = "stun"; /**< Binding usage */ |
| 38 | const char *stuns_usage_binding = "stuns"; /**< Binding usage TLS */ |
| 39 | const char *stun_usage_relay = "turn"; |
| 40 | const char *stuns_usage_relay = "turns"; |
| 41 | const char *stun_usage_behavior = "stun-behavior"; |
| 42 | const char *stuns_usage_behavior = "stun-behaviors"; |
| 43 | |
| 44 | |
| 45 | static void resolved(const struct stun_dns *dns, int err) |
| 46 | { |
| 47 | stun_dns_h *dnsh = dns->dnsh; |
| 48 | void *dnsh_arg = dns->arg; |
| 49 | |
| 50 | DEBUG_INFO("resolved: %J (%m)\n", &dns->srv, err); |
| 51 | |
| 52 | dnsh(err, &dns->srv, dnsh_arg); |
| 53 | } |
| 54 | |
| 55 | |
| 56 | static void a_handler(int err, const struct dnshdr *hdr, struct list *ansl, |
| 57 | struct list *authl, struct list *addl, void *arg) |
| 58 | { |
| 59 | struct stun_dns *dns = arg; |
| 60 | struct dnsrr *rr; |
| 61 | |
| 62 | (void)hdr; |
| 63 | (void)authl; |
| 64 | (void)addl; |
| 65 | |
| 66 | /* Find A answers */ |
| 67 | rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_A, DNS_CLASS_IN, false); |
| 68 | if (!rr) { |
| 69 | err = err ? err : EDESTADDRREQ; |
| 70 | goto out; |
| 71 | } |
| 72 | |
| 73 | sa_set_in(&dns->srv, rr->rdata.a.addr, sa_port(&dns->srv)); |
| 74 | |
| 75 | DEBUG_INFO("A answer: %j\n", &dns->srv); |
| 76 | |
| 77 | out: |
| 78 | resolved(dns, err); |
| 79 | } |
| 80 | |
| 81 | |
| 82 | #ifdef HAVE_INET6 |
| 83 | static void aaaa_handler(int err, const struct dnshdr *hdr, struct list *ansl, |
| 84 | struct list *authl, struct list *addl, void *arg) |
| 85 | { |
| 86 | struct stun_dns *dns = arg; |
| 87 | struct dnsrr *rr; |
| 88 | |
| 89 | (void)hdr; |
| 90 | (void)authl; |
| 91 | (void)addl; |
| 92 | |
| 93 | /* Find A answers */ |
| 94 | rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_AAAA, DNS_CLASS_IN, false); |
| 95 | if (!rr) { |
| 96 | err = err ? err : EDESTADDRREQ; |
| 97 | goto out; |
| 98 | } |
| 99 | |
| 100 | sa_set_in6(&dns->srv, rr->rdata.aaaa.addr, sa_port(&dns->srv)); |
| 101 | |
| 102 | DEBUG_INFO("AAAA answer: %j\n", &dns->srv); |
| 103 | |
| 104 | out: |
| 105 | resolved(dns, err); |
| 106 | } |
| 107 | #endif |
| 108 | |
| 109 | |
| 110 | static int a_or_aaaa_query(struct stun_dns *dns, const char *name) |
| 111 | { |
| 112 | dns->dq = mem_deref(dns->dq); |
| 113 | |
| 114 | switch (dns->af) { |
| 115 | |
| 116 | case AF_INET: |
| 117 | return dnsc_query(&dns->dq, dns->dnsc, name, DNS_TYPE_A, |
| 118 | DNS_CLASS_IN, true, a_handler, dns); |
| 119 | |
| 120 | #ifdef HAVE_INET6 |
| 121 | case AF_INET6: |
| 122 | return dnsc_query(&dns->dq, dns->dnsc, name, DNS_TYPE_AAAA, |
| 123 | DNS_CLASS_IN, true, aaaa_handler, dns); |
| 124 | #endif |
| 125 | |
| 126 | default: |
| 127 | return EAFNOSUPPORT; |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | |
| 132 | static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl, |
| 133 | struct list *authl, struct list *addl, void *arg) |
| 134 | { |
| 135 | struct stun_dns *dns = arg; |
| 136 | struct dnsrr *rr, *arr; |
| 137 | |
| 138 | (void)hdr; |
| 139 | (void)authl; |
| 140 | |
| 141 | dns_rrlist_sort(ansl, DNS_TYPE_SRV, (size_t)dns->arg); |
| 142 | |
| 143 | /* Find SRV answers */ |
| 144 | rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_SRV, DNS_CLASS_IN, false); |
| 145 | if (!rr) { |
| 146 | DEBUG_INFO("no SRV entry, trying A lookup on \"%s\"\n", |
| 147 | dns->domain); |
| 148 | |
| 149 | sa_set_in(&dns->srv, 0, dns->port); |
| 150 | |
| 151 | err = a_or_aaaa_query(dns, dns->domain); |
| 152 | if (err) |
| 153 | goto out; |
| 154 | |
| 155 | return; |
| 156 | } |
| 157 | |
| 158 | DEBUG_INFO("SRV answer: %s:%u\n", rr->rdata.srv.target, |
| 159 | rr->rdata.srv.port); |
| 160 | |
| 161 | /* Look for Additional information */ |
| 162 | switch (dns->af) { |
| 163 | |
| 164 | case AF_INET: |
| 165 | arr = dns_rrlist_find(addl, rr->rdata.srv.target, |
| 166 | DNS_TYPE_A, DNS_CLASS_IN, true); |
| 167 | if (arr) { |
| 168 | sa_set_in(&dns->srv, arr->rdata.a.addr, |
| 169 | rr->rdata.srv.port); |
| 170 | DEBUG_INFO("additional A: %j\n", &dns->srv); |
| 171 | goto out; |
| 172 | } |
| 173 | break; |
| 174 | |
| 175 | #ifdef HAVE_INET6 |
| 176 | case AF_INET6: |
| 177 | arr = dns_rrlist_find(addl, rr->rdata.srv.target, |
| 178 | DNS_TYPE_AAAA, DNS_CLASS_IN, true); |
| 179 | if (arr) { |
| 180 | sa_set_in6(&dns->srv, arr->rdata.aaaa.addr, |
| 181 | rr->rdata.srv.port); |
| 182 | DEBUG_INFO("additional AAAA: %j\n", &dns->srv); |
| 183 | goto out; |
| 184 | } |
| 185 | break; |
| 186 | #endif |
| 187 | } |
| 188 | |
| 189 | sa_set_in(&dns->srv, 0, rr->rdata.srv.port); |
| 190 | |
| 191 | err = a_or_aaaa_query(dns, rr->rdata.srv.target); |
| 192 | if (err) { |
| 193 | DEBUG_WARNING("SRV: A lookup failed (%m)\n", err); |
| 194 | goto out; |
| 195 | } |
| 196 | |
| 197 | DEBUG_INFO("SRV handler: doing A/AAAA lookup..\n"); |
| 198 | |
| 199 | return; |
| 200 | |
| 201 | out: |
| 202 | resolved(dns, err); |
| 203 | } |
| 204 | |
| 205 | |
| 206 | static void dnsdisc_destructor(void *data) |
| 207 | { |
| 208 | struct stun_dns *dns = data; |
| 209 | |
| 210 | mem_deref(dns->dq); |
| 211 | } |
| 212 | |
| 213 | |
| 214 | /** |
| 215 | * Do a DNS Discovery of a STUN Server |
| 216 | * |
| 217 | * @param dnsp Pointer to allocated DNS Discovery object |
| 218 | * @param dnsc DNS Client |
| 219 | * @param service Name of service to discover (e.g. "stun") |
| 220 | * @param proto Transport protocol (e.g. "udp") |
| 221 | * @param af Preferred Address Family |
| 222 | * @param domain Domain name or IP address of STUN server |
| 223 | * @param port Port number (if 0 do SRV lookup) |
| 224 | * @param dnsh DNS Response handler |
| 225 | * @param arg Handler argument |
| 226 | * |
| 227 | * @return 0 if success, otherwise errorcode |
| 228 | */ |
| 229 | int stun_server_discover(struct stun_dns **dnsp, struct dnsc *dnsc, |
| 230 | const char *service, const char *proto, |
| 231 | int af, const char *domain, uint16_t port, |
| 232 | stun_dns_h *dnsh, void *arg) |
| 233 | { |
| 234 | struct stun_dns *dns; |
| 235 | int err; |
| 236 | |
| 237 | if (!dnsp || !service || !proto || !domain || !domain[0] || !dnsh) |
| 238 | return EINVAL; |
| 239 | |
| 240 | dns = mem_zalloc(sizeof(*dns), dnsdisc_destructor); |
| 241 | if (!dns) |
| 242 | return ENOMEM; |
| 243 | |
| 244 | dns->port = service[strlen(service)-1] == 's' ? STUNS_PORT : STUN_PORT; |
| 245 | dns->dnsh = dnsh; |
| 246 | dns->arg = arg; |
| 247 | dns->dnsc = dnsc; |
| 248 | dns->af = af; |
| 249 | |
| 250 | /* Numeric IP address - no lookup */ |
| 251 | if (0 == sa_set_str(&dns->srv, domain, port ? port : dns->port)) { |
| 252 | |
| 253 | DEBUG_INFO("IP (%s)\n", domain); |
| 254 | |
| 255 | resolved(dns, 0); |
| 256 | err = 0; |
| 257 | goto out; /* free now */ |
| 258 | } |
| 259 | /* Port specified - use AAAA or A lookup */ |
| 260 | else if (port) { |
| 261 | sa_set_in(&dns->srv, 0, port); |
| 262 | DEBUG_INFO("resolving A query: (%s)\n", domain); |
| 263 | |
| 264 | err = a_or_aaaa_query(dns, domain); |
| 265 | if (err) { |
| 266 | DEBUG_WARNING("%s: A/AAAA lookup failed (%m)\n", |
| 267 | domain, err); |
| 268 | goto out; |
| 269 | } |
| 270 | } |
| 271 | /* SRV lookup */ |
| 272 | else { |
| 273 | char q[256]; |
| 274 | str_ncpy(dns->domain, domain, sizeof(dns->domain)); |
| 275 | (void)re_snprintf(q, sizeof(q), "_%s._%s.%s", service, proto, |
| 276 | domain); |
| 277 | DEBUG_INFO("resolving SRV query: (%s)\n", q); |
| 278 | err = dnsc_query(&dns->dq, dnsc, q, DNS_TYPE_SRV, DNS_CLASS_IN, |
| 279 | true, srv_handler, dns); |
| 280 | if (err) { |
| 281 | DEBUG_WARNING("%s: SRV lookup failed (%m)\n", q, err); |
| 282 | goto out; |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | *dnsp = dns; |
| 287 | |
| 288 | return 0; |
| 289 | |
| 290 | out: |
| 291 | mem_deref(dns); |
| 292 | return err; |
| 293 | } |