/* $Id: dns.C,v 1.17 2001/11/20 01:23:43 dm Exp $ */ /* * * Copyright (C) 1998 David Mazieres (dm@uun.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, 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 * */ /* Asynchronous resolver. This code is easy to understand if and only * if you also have a copy of RFC 1035. You can get this by anonymous * ftp from, among other places, ftp.internic.net:/rfc/rfc1035.txt. */ #include "dnsparse.h" #include "arena.h" #include "vec.h" #include "ihash.h" #include "list.h" #include "backoff.h" INITFN(dns_start); #define NSPORT NAMESERVER_PORT #define QBSIZE 512 class dnsreq { static u_int16_t lastid; // Last request id static int ns_idx; static sockaddr_in srvaddr; // IP address of name server static int sock; // UDP socket static bool tcplock; static int last_good_idx; static time_t last_resp; static void tcpcb (int, int); static void resend (dnsreq *r) { r->xmit (); } static u_int16_t genid (); dnsreq (); virtual void readreply (dnsparse *) = 0; void fail (int); void start (bool); int ntries; int srchno; protected: int error; public: static void setsock (bool); static void pktready (); u_int16_t id; // DNS query ID str basename; // Name for which to search str name; // Name for which to query u_short type; // Type of query (T_A, T_PTR, etc.) ihash_entry hlink; // Per-id hash table link tmoq_entry tlink; // Retransmit queue link dnsreq (str, u_int16_t, bool search = false); virtual ~dnsreq (); bool xmit (int = 0); void timeout (); }; u_int16_t dnsreq::lastid; int dnsreq::ns_idx; sockaddr_in dnsreq::srvaddr; int dnsreq::sock = -1; bool dnsreq::tcplock; int dnsreq::last_good_idx; time_t dnsreq::last_resp; static ihash reqtab; static tmoq reqtoq; u_int16_t dnsreq::genid () { #ifndef HAVE_ARC4RANDOM static u_short dns_lastid; return ++dns_lastid; #else /* HAVE_ARC4RANDOM */ return arc4random () & 0xffff; #endif /* HAVE_ARC4RANDOM */ } void dnsreq::fail (int err) { if (!error) error = err; readreply (NULL); delete this; } void dnsreq::start (bool again) { if (again && (srchno < 0 || !_res.dnsrch[srchno])) { fail (NXDOMAIN); return; } if (again) { reqtab.remove (this); reqtoq.remove (this); } if (srchno >= 0) name = strbuf ("%s.%s", basename.cstr (), _res.dnsrch[srchno++]); id = genid (); reqtab.insert (this); reqtoq.start (this); } dnsreq::dnsreq (str n, u_int16_t t, bool search) : ntries (0), error (0), type (t) { if (!search || strchr (n, '.')) { srchno = -1; basename = NULL; name = n; } else { srchno = 0; basename = n; name = NULL; } start (false); } dnsreq::~dnsreq () { reqtab.remove (this); reqtoq.remove (this); } //* Send a query to the nameserver */ bool dnsreq::xmit (int retry) { u_char qb[QBSIZE]; HEADER *const h = (HEADER *) qb; int n; n = res_mkquery (QUERY, name, C_IN, type, NULL, 0, NULL, qb, sizeof (qb)); if (n < 0) return false; h->id = htons (id); /* XXX - Why bother with htons? */ if (sendto (sock, reinterpret_cast (qb), n, 0, reinterpret_cast (&srvaddr), sizeof (srvaddr)) < 0) { error = ARERR_CANTSEND; return false; } error = 0; return true; } void dnsreq::timeout () { if (timenow - last_resp > 60) { if (++ntries < _res.nscount) { reqtab.remove (this); reqtoq.remove (this); setsock (true); start (false); return; } setsock (true); } fail (ARERR_TIMEOUT); } void dnsreq::pktready () { u_char qb[QBSIZE]; ssize_t n; for (;;) { sockaddr_in sin; socklen_t sinlen = sizeof (sin); bzero (&sin, sizeof (sin)); n = recvfrom (sock, reinterpret_cast (qb), sizeof (qb), 0, reinterpret_cast (&sin), &sinlen); if (n < 0) break; if (!addreq (reinterpret_cast (&sin), reinterpret_cast (&srvaddr), sizeof (sin))) continue; last_resp = timenow; last_good_idx = ns_idx; dnsparse reply (qb, n); question q; if (!reply.qparse (&q)) continue; dnsreq *r; for (r = reqtab[ntohs (reply.hdr->id)]; r && (r->type != q.q_type || strcasecmp (r->name, q.q_name)); r = reqtab.nextkeq (r)) ; if (!r) continue; if (reply.error && !r->error) r->error = reply.error; if (r->error == NXDOMAIN) { r->error = 0; r->start (true); } else { r->readreply (r->error ? NULL : &reply); delete r; } } } /* Try setting up a TCP connection to name servers, to make sure they * are really running named. (This is really a kludge because we * can't see ICMP port unreachable packets without using connected UDP * sockets, and we can't use connected UDP sockets without failing * when laptops change their IP addresses.) */ void dnsreq::tcpcb (int idx, int fd) { tcplock = false; if (fd >= 0) { close (fd); last_good_idx = ns_idx; return; } else if (idx == ns_idx && !last_resp) setsock (true); } void dnsreq::setsock (bool bump) { if (sock < 0) { #if 0 /* We use a reserved port here to prevent port-capture problems. * (An unprivileged user can steal packets destined to an INADDR_ANY * socket by binding to the same port on the more-specific interface * address. */ if (suidsafe ()) sock = inetsocket_resvport (SOCK_DGRAM); else sock = inetsocket (SOCK_DGRAM); #else /* It seems many Linux distros automatically firewall all UDP * traffic to reserved ports without even keeping state. That * means reserved UDP ports essentially don't work. Oh well. */ sock = inetsocket (SOCK_DGRAM); #endif if (sock < 0) fatal ("dnsreq::setsock: inetsocket: %s\n", strerror (errno)); close_on_exec (sock); make_async (sock); fdcb (sock, selread, wrap (pktready)); } if (bump && _res.nscount) { ns_idx = (ns_idx + 1) % _res.nscount; warn ("changing nameserver to %s\n", inet_ntoa (_res.nsaddr_list[ns_idx].sin_addr)); } srvaddr = _res.nsaddr_list[ns_idx]; if (!srvaddr.sin_addr.s_addr) // XXX srvaddr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); last_resp = 0; reqtab.traverse (wrap (resend)); if (!tcplock && (!bump || ns_idx != last_good_idx)) { tcplock = true; tcpconnect (srvaddr.sin_addr, ntohs (srvaddr.sin_port), wrap (tcpcb, ns_idx)); } } class dnsreq_a : public dnsreq { bool checkaddr; // Non-zero when arr_addr must be checked in_addr addr; // Adress of inverse queries (for checking) cbhent cb; // Callback for hostbyname/addr dnsreq_a (); public: dnsreq_a (str n, cbhent c, bool s = false) : dnsreq (n, T_A, s), checkaddr (false), cb (c) {} dnsreq_a (str n, cbhent c, const in_addr &a) : dnsreq (n, T_A), checkaddr (true), addr (a), cb (c) {} void readreply (dnsparse *); }; void dnsreq_a::readreply (dnsparse *reply) { ptr h; if (!error) { if (!(h = reply->tohostent ())) error = reply->error; else if (checkaddr) { char **ap; for (ap = h->h_addr_list; *ap && *(in_addr *) *ap != addr; ap++) ; if (!*ap) { h = NULL; error = ARERR_PTRSPOOF; } } } (*cb) (h, error); } void dns_hostbyname (str name, cbhent cb, bool search, bool addrok) { if (addrok) { in_addr addr; if (inet_aton (name.cstr (), &addr)) { ptr h = refcounted::alloc (sizeof (*h) + 3 * sizeof (void *) + sizeof (addr) + strlen (name) + 1); h->h_aliases = (char **) &h[1]; h->h_addrtype = AF_INET; h->h_length = sizeof (addr); h->h_addr_list = &h->h_aliases[1]; h->h_aliases[0] = NULL; h->h_addr_list[0] = (char *) &h->h_addr_list[2]; h->h_addr_list[1] = NULL; *(struct in_addr *) h->h_addr_list[0] = addr; h->h_name = (char *) h->h_addr_list[0] + sizeof (addr); strcpy ((char *) h->h_name, name); (*cb) (h, 0); return; } } vNew dnsreq_a (name, cb, search); } class dnsreq_ptr : public dnsreq { in_addr addr; cbhent cb; // Callback for hostbyname/addr dnsreq_ptr (); public: static str inaddr_arpa (in_addr); dnsreq_ptr (in_addr a, cbhent c) : dnsreq (inaddr_arpa (a), T_PTR), addr (a), cb (c) {} void readreply (dnsparse *); }; str dnsreq_ptr::inaddr_arpa (in_addr addr) { u_char *a = reinterpret_cast (&addr); return strbuf ("%d.%d.%d.%d.in-addr.arpa", a[3], a[2], a[1], a[0]); } void dnsreq_ptr::readreply (dnsparse *reply) { if (!error) { const u_char *cp = reply->getanp (); for (u_int i = 0; i < reply->ancount; i++) { resrec rr; if (!reply->rrparse (&cp, &rr)) break; if (rr.rr_type == T_PTR && rr.rr_class == C_IN) { vNew dnsreq_a (rr.rr_ptr, cb, addr); return; } } } if (!error && !(error = reply->error)) error = ARERR_NXREC; (*cb) (NULL, error); } void dns_hostbyaddr (in_addr addr, cbhent cb) { vNew dnsreq_ptr (addr, cb); } static void dns_start () { if (!(_res.options & RES_INIT)) res_init (); dnsreq::setsock (false); } const char * dns_strerror (int no) { switch (no) { case NOERROR: return "no error"; case FORMERR: return "DNS format error"; case SERVFAIL: return "name server failure"; case NXDOMAIN: return "non-existent domain name"; case NOTIMP: return "unimplemented DNS request"; case REFUSED: return "DNS query refused"; case ARERR_NXREC: return "no DNS records of appropriate type"; case ARERR_TIMEOUT: return "name lookup timed out"; case ARERR_PTRSPOOF: return "incorrect PTR record"; case ARERR_BADRESP: return "malformed DNS reply"; case ARERR_CANTSEND: return "cannot send to name server"; case ARERR_REQINVAL: return "malformed domain name"; case ARERR_CNAMELOOP: return "CNAME records form loop"; default: return "unknown DNS error"; } } int dns_tmperr (int no) { switch (no) { case SERVFAIL: case ARERR_TIMEOUT: case ARERR_CANTSEND: case ARERR_BADRESP: return 1; default: return 0; } }