code: plan9front

Download patch

ref: 6b0574e27e6abffb2328be29e2cc9f3e67e2655b
parent: 917d0fa9b42845d2b918d13ab4e73761e866409a
author: Jacob Moody <moody@posixcafe.org>
date: Sat Jan 27 19:01:56 EST 2024

ndb/dns: DoT support

--- a/sys/man/6/ndb
+++ b/sys/man/6/ndb
@@ -232,7 +232,13 @@
 pairs.
 .TP
 .B dns
-a DNS server to use (for DNS and DHCP)
+a DNS server to use for resolving (for DNS and DHCP)
+.TP
+.B dot
+a DNS over TLS server to use for resolving (for DNS).
+If found,
+.B dns
+entries are ignored.
 .TP
 .B ntp
 an NTP server to use (for DHCP)
--- a/sys/man/8/ndb
+++ b/sys/man/8/ndb
@@ -62,6 +62,9 @@
 .B -a
 .I maxage
 ] [
+.B -c
+.I cert.pem
+] [
 .B -f
 .I dbfile
 ] [
@@ -393,11 +396,18 @@
 to complete lookups.
 If present,
 .B /env/DNSSERVER
-must be a space-separated list of such DNS servers' IP addresses,
+or
+.B /env/DOTSERVER
+must be a space-separated list of such DNS (or DoT) servers' IP addresses,
 otherwise optional
 .IR ndb (6)
 .B dns
 attributes name DNS servers to forward queries to.
+Note that when
+.B DOTSERVER
+is specified,
+.B DNSSERVER
+are ignored.
 .TP
 .B -R
 ignore the `recursive' bit on all incoming requests.
@@ -421,6 +431,12 @@
 are given,
 listen on any interface on network mount point
 .IR netmtpt .
+.TP
+.B -c
+When a certificate
+.I cert.pem
+is specified, also listen on TCP port 853 and handle
+DNS requests over TLS.
 .TP
 .B -x
 specifies the mount point of the network.
--- a/sys/src/cmd/ndb/dblookup.c
+++ b/sys/src/cmd/ndb/dblookup.c
@@ -916,7 +916,7 @@
 
 	/* check duplicate ip */
 	for(n = 0; n < i; n++){
-		snprint(buf, sizeof buf, "local#dns#server%d", n);
+		snprint(buf, sizeof buf, "%s#%d", dp->name, n);
 		nsdp = dnlookup(buf, class, 0);
 		if(nsdp == nil)
 			continue;
@@ -931,7 +931,7 @@
 		rrfreelist(rp);
 	}
 
-	snprint(buf, sizeof buf, "local#dns#server%d", i);
+	snprint(buf, sizeof buf, "%s#%d", dp->name, i);
 	nsdp = dnlookup(buf, class, 1);
 
 	/* ns record for name server, make up an impossible name */
@@ -967,6 +967,33 @@
 	RR *nsrp;
 	DN *dp;
 
+	/* try first DoT servers */
+	dp = dnlookup("local#dot#servers", class, 1);
+	nsrp = rrlookup(dp, Tns, NOneg);
+	if(nsrp != nil)
+		return nsrp;
+
+	p = getenv("DOTSERVER");		/* list of ip addresses */
+	if(p != nil && (n = tokenize(p, args, nelem(args))) > 0){
+		for(i = 0; i < n; i++)
+			addlocaldnsserver(dp, class, args[i], i);
+	} else {
+		t = lookupinfo("@dot");		/* @dot=ip1 ... */
+		if(t == nil)
+			return nil;
+		i = 0;
+		for(nt = t; nt != nil; nt = nt->entry){
+			addlocaldnsserver(dp, class, nt->val, i);
+			i++;
+		}
+		ndbfree(t);
+	}
+
+	nsrp = rrlookup(dp, Tns, NOneg);
+	if(nsrp != nil)
+		return nsrp;
+
+	/* try regular local DNS servers */
 	dp = dnlookup("local#dns#servers", class, 1);
 	nsrp = rrlookup(dp, Tns, NOneg);
 	if(nsrp != nil)
--- a/sys/src/cmd/ndb/dnresolve.c
+++ b/sys/src/cmd/ndb/dnresolve.c
@@ -5,6 +5,8 @@
 #include <libc.h>
 #include <ip.h>
 #include <bio.h>
+#include <mp.h>
+#include <libsec.h>
 #include <ndb.h>
 #include "dns.h"
 
@@ -79,7 +81,7 @@
 	return strdup(lp+1);
 }
 
-void
+static void
 rrfreelistptr(RR **rpp)
 {
 	RR *rp;
@@ -267,9 +269,13 @@
 	 */
 	if(cfg.resolver){
 		nsrp = randomize(getdnsservers(class));
-		if(nsrp != nil)
+		if(nsrp != nil){
+			int dot = strncmp(nsrp->owner->name, "local#dot#server", 16) == 0;
 			if(netqueryns(qp, nsrp) > Answnone)
 				return rrlookup(qp->dp, qp->type, OKneg);
+			else if(dot)
+				return nil;	/* do not fall-back for DoT */
+		}
 	}
 
 	/*
@@ -733,7 +739,7 @@
 /*
  *	return non-0 if first list includes second list
  */
-int
+static int
 contains(RR *rp1, RR *rp2)
 {
 	RR *trp1, *trp2;
@@ -753,7 +759,7 @@
 /*
  *  return multicast version if any
  */
-int
+static int
 ipisbm(uchar *ip)
 {
 	if(isv4(ip)){
@@ -1166,16 +1172,31 @@
 	return rv;
 }
 
+enum {
+	Maxfree = 4,
+};
+
+struct {
+	QLock lk;
+	struct {
+		uvlong when;
+		char *dest;
+		int fd;
+	} l[Maxfree];
+} tcpfree;
+
 /*
  * send a query via tcp to a single address
  * and read the answer(s) into mp->an.
  */
 static int
-tcpquery(Query *qp, uchar *pkt, int len, Dest *p, uvlong endms, DNSmsg *mp)
+tcpquery(Query *qp, uchar *pkt, int len, Dest *p, uvlong endms, DNSmsg *mp, int tls)
 {
 	char buf[NETPATHLEN];
-	int fd, rv;
+	int fd, rv, i, retry;
 	long ms;
+	TLSconn conn;
+	Thumbprint *thumb;
 
 	memset(mp, 0, sizeof *mp);
 
@@ -1185,28 +1206,125 @@
 	if(ms > Maxtcpdialtm)
 		ms = Maxtcpdialtm;
 
-	procsetname("tcp query to %I/%s for %s %s", p->a, p->s->name,
+	procsetname("%s query to %I/%s for %s %s", tls ? "tls" : "tcp", p->a, p->s->name,
 		qp->dp->name, rrname(qp->type, buf, sizeof buf));
 
-	snprint(buf, sizeof buf, "%s/tcp!%I!53", mntpt, p->a);
+	snprint(buf, sizeof buf, "%s/tcp!%I!%s", mntpt, p->a, tls ? "853" : "53");
 
+	fd = -1;
+	retry = 0;
+	qlock(&tcpfree.lk);
+	for(i = 0; i < nelem(tcpfree.l); i++){
+		if(tcpfree.l[i].dest == nil || tcpfree.l[i].fd == -1)
+			continue;
+		if(strcmp(tcpfree.l[i].dest, buf) != 0)
+			continue;
+		/* RFC does not specify connection reuse timeout */
+		if(nowms - tcpfree.l[i].when < 5000){
+			fd = tcpfree.l[i].fd;
+			tcpfree.l[i].fd = -1;
+			retry++;
+			break;
+		}
+	}
+	qunlock(&tcpfree.lk);
+	if(fd != -1)
+		goto Found;
+
+Retry:
 	alarm(ms);
 	fd = dial(buf, nil, nil, nil);
-	alarm(0);
-	if (fd < 0) {
+	if(fd < 0){
+		alarm(0);
 		dnslog("%d: can't dial %s for %I/%s: %r",
 			qp->req->id, buf, p->a, p->s->name);
 		return -1;
 	}
+	if(tls){
+		memset(&conn, 0, sizeof conn);
+		rv = tlsClient(fd, &conn);
+		alarm(0);
+		if(rv >= 0){
+			fd = rv;
+			thumb = initThumbprints("/sys/lib/tls/dns", nil, "x509");
+			if(thumb == nil || !okCertificate(conn.cert, conn.certlen, thumb)){
+				dnslog("%d: invalid fingerprint for %s; echo 'x509 %r' >>/sys/lib/tls/dns",
+					qp->req->id, buf);
+				rv = -1;
+			}
+			free(conn.cert);
+			free(conn.sessionID);
+			freeThumbprints(thumb);
+		}
+		if(rv < 0){
+			close(fd);
+			return -1;
+		}
+	} else {
+		alarm(0);
+	}
+
+Found:
 	rv = writenet(qp, Tcp, fd, pkt, len, p);
 	if(rv == 0){
 		timems();	/* account for time dialing and sending */
 		rv = readreply(qp, Tcp, fd, endms, mp, pkt);
 	}
-	close(fd);
+
+	if(rv < 0){
+		close(fd);
+		if(retry){
+			retry = 0;
+			goto Retry;
+		}
+		return rv;
+	}
+
+	qlock(&tcpfree.lk);
+	if(tcpfree.l[nelem(tcpfree.l)-1].dest != nil){
+		close(tcpfree.l[nelem(tcpfree.l)-1].fd);
+		free(tcpfree.l[nelem(tcpfree.l)-1].dest);
+	}
+	memmove(tcpfree.l + 1, tcpfree.l, sizeof(tcpfree.l[0])*(nelem(tcpfree.l)-1));
+	tcpfree.l[0].when = nowms;
+	tcpfree.l[0].fd = fd;
+	tcpfree.l[0].dest = estrdup(buf);
+	qunlock(&tcpfree.lk);
+
 	return rv;
 }
 
+static int
+tlsqueryns(Query *qp, uchar *pkt, int len)
+{
+	Dest dest[Maxdest], *p;
+	int rv, n;
+	uvlong endms;
+	DNSmsg m;
+
+	/* populates dest with v4 and v6 addresses. */
+	n = 0;
+	n = serveraddrs(qp, dest, n, Ta);
+	n = serveraddrs(qp, dest, n, Taaaa);
+	endms = nowms + 500;
+	for(p = dest; p < dest+n; p++){
+		if(tcpquery(qp, pkt, len, p, endms, &m, 1) == 0){
+			/* free or incorporate RRs in m */
+			rv = procansw(qp, p, &m);
+			if(rv > Answnone)
+				return rv;
+		}
+	}
+
+	/* if all servers returned failure, propagate it */
+	qp->dp->respcode = Rserver;
+	for(p = dest; p < dest+n; p++)
+		if(p->code != Rserver)
+			qp->dp->respcode = Rok;
+
+	return Answnone;
+}
+
 /*
  *  query name servers.  fill in pkt with on-the-wire representation of a
  *  DNSmsg derived from qp. if the name server returns a pointer to another
@@ -1213,30 +1331,15 @@
  *  name server, recurse.
  */
 static int
-udpqueryns(Query *qp, int fd, uchar *pkt)
+udpqueryns(Query *qp, int fd, uchar *pkt, int len)
 {
 	Dest dest[Maxdest], *edest, *p, *np;
-	int ndest, replywaits, len, flag, rv, n;
+	int ndest, replywaits, rv, n;
 	uchar srcip[IPaddrlen];
 	char buf[32];
 	uvlong endms;
 	DNSmsg m;
-	RR *rp;
 
-	/* prepare server RR's for incremental lookup */
-	for(rp = qp->nsrp; rp; rp = rp->next)
-		rp->marker = 0;
-
-	/* request recursion only for local/override dns servers */
-	flag = Oquery;
-	if(strncmp(qp->nsrp->owner->name, "local#", 6) == 0
-	|| strncmp(qp->nsrp->owner->name, "override#", 9) == 0)
-		flag |= Frecurse;
-
-	/* pack request into a udp message */
-	qp->id = rand();
-	len = mkreq(qp->dp, qp->type, pkt, flag, qp->id);
-
 	/* no destination yet */
 	edest = dest;
 
@@ -1307,7 +1410,7 @@
 			/* if response was truncated, try tcp */
 			if(m.flags & Ftrunc){
 				freeanswers(&m);
-				if(tcpquery(qp, pkt, len, p, endms, &m) < 0)
+				if(tcpquery(qp, pkt, len, p, endms, &m, 0) < 0)
 					break;	/* failed via tcp too */
 				if(m.flags & Ftrunc){
 					freeanswers(&m);
@@ -1336,27 +1439,44 @@
 	return Answnone;
 }
 
-/*
- * in principle we could use a single descriptor for a udp port
- * to send all queries and receive all the answers to them,
- * but we'd have to sort out the answers by dns-query id.
- */
 static int
-udpquery(Query *qp)
+doquery(Query *qp)
 {
-	int fd, rv;
+	int fd, rv, len, flag;
 	uchar *pkt;
+	RR *rp;
 
 	pkt = emalloc(Maxudp+Udphdrsize);
-	fd = udpport(mntpt);
-	if (fd < 0) {
-		dnslog("%d: can't get udpport for %s query of name %s: %r",
-			qp->req->id, mntpt, qp->dp->name);
-		rv = -1;
-		goto Out;
+	/* prepare server RR's for incremental lookup */
+	for(rp = qp->nsrp; rp; rp = rp->next)
+		rp->marker = 0;
+	/* request recursion only for local/override dns servers */
+	flag = Oquery;
+	if(strncmp(qp->nsrp->owner->name, "local#", 6) == 0
+	|| strncmp(qp->nsrp->owner->name, "override#", 9) == 0)
+		flag |= Frecurse;
+	/* pack request into a udp message */
+	qp->id = rand();
+	len = mkreq(qp->dp, qp->type, pkt, flag, qp->id);
+	if(strncmp(qp->nsrp->owner->name, "local#dot#server", 16) == 0
+	|| strncmp(qp->nsrp->owner->name, "override#dot#server", 16) == 0){
+		rv = tlsqueryns(qp, pkt, len);
+	} else {
+		/*
+		 * in principle we could use a single descriptor for a udp port
+		 * to send all queries and receive all the answers to them,
+		 * but we'd have to sort out the answers by dns-query id.
+		 */
+		fd = udpport(mntpt);
+		if (fd < 0) {
+			dnslog("%d: can't get udpport for %s query of name %s: %r",
+				qp->req->id, mntpt, qp->dp->name);
+			rv = -1;
+			goto Out;
+		}
+		rv = udpqueryns(qp, fd, pkt, len);
+		close(fd);
 	}
-	rv = udpqueryns(qp, fd, pkt);
-	close(fd);
 Out:
 	free(pkt);
 	return rv;
@@ -1383,5 +1503,5 @@
 	if(!qp->req->isslave && strcmp(qp->req->from, "9p") == 0)
 		return Answnone;
 
-	return udpquery(qp);
+	return doquery(qp);
 }
--- a/sys/src/cmd/ndb/dns.c
+++ b/sys/src/cmd/ndb/dns.c
@@ -92,7 +92,7 @@
 void
 usage(void)
 {
-	fprint(2, "usage: %s [-FnrLR] [-a maxage] [-f ndb-file] [-N target] "
+	fprint(2, "usage: %s [-FnrLR] [-a maxage] [-c cert.pem] [-f ndb-file] [-N target] "
 		"[-x netmtpt] [-s [addrs...]]\n", argv0);
 	exits("usage");
 }
@@ -101,10 +101,12 @@
 main(int argc, char *argv[])
 {
 	char ext[Maxpath], servefile[Maxpath];
+	char *cert;
 	Dir *dir;
 
 	setnetmtpt(mntpt, sizeof mntpt, nil);
 	ext[0] = 0;
+	cert = nil;
 	ARGBEGIN{
 	case 'a':
 		maxage = atol(EARGF(usage()));
@@ -141,6 +143,9 @@
 		cfg.serve = 1;		/* serve network */
 		cfg.cachedb = 1;
 		break;
+	case 'c':
+		cert = EARGF(usage());
+		break;
 	case 'x':
 		setnetmtpt(mntpt, sizeof mntpt, EARGF(usage()));
 		setext(ext, sizeof ext, mntpt);
@@ -181,11 +186,15 @@
 	if(cfg.serve){
 		if(argc == 0) {
 			dnudpserver(mntpt, "*");
-			dntcpserver(mntpt, "*");
+			dntcpserver(mntpt, "*", nil);
+			if(cert != nil)
+				dntcpserver(mntpt, "*", cert);
 		} else {
 			while(argc-- > 0){
 				dnudpserver(mntpt, *argv);
-				dntcpserver(mntpt, *argv);
+				dntcpserver(mntpt, *argv, nil);
+				if(cert != nil)
+					dntcpserver(mntpt, *argv, cert);
 				argv++;
 			}
 		}
--- a/sys/src/cmd/ndb/dns.h
+++ b/sys/src/cmd/ndb/dns.h
@@ -522,7 +522,7 @@
 void	dnudpserver(char*, char*);
 
 /* dntcpserver.c */
-void	dntcpserver(char*, char*);
+void	dntcpserver(char*, char*, char*);
 
 /* dnnotify.c */
 void	dnnotify(DNSmsg*, DNSmsg*, Request*);
--- a/sys/src/cmd/ndb/dnsdebug.c
+++ b/sys/src/cmd/ndb/dnsdebug.c
@@ -172,14 +172,18 @@
 {
 	uchar ip[IPaddrlen];
 	DN *nsdp;
-	RR *rp;
+	RR *rp, *ns;
+	char name[64];
 
 	if(servername == nil)
 		return dnsservers(class);
-	if(parseip(ip, servername) == -1){
-		nsdp = idnlookup(servername, class, 1);
+
+	snprint(name, sizeof name, "override#%s#server", servername[0] == '!' ? "dot" : "dns");
+	ns = rralloc(Tns);
+	if(parseip(ip, servername+1) == -1){
+		nsdp = idnlookup(servername+1, class, 1);
 	} else {
-		nsdp = dnlookup("local#dns#server", class, 1);
+		nsdp = dnlookup(name, class, 1);
 		rp = rralloc(isv4(ip) ? Ta : Taaaa);
 		rp->owner = nsdp;
 		rp->ip = ipalookup(ip, class, 1);
@@ -187,10 +191,9 @@
 		rp->ttl = 10*Min;
 		rrattach(rp, Authoritative);
 	}
-	rp = rralloc(Tns);
-	rp->owner = dnlookup("override#dns#servers", class, 1);
-	rp->host = nsdp;
-	return rp;
+	ns->owner = dnlookup(name, class, 1);
+	ns->host = nsdp;
+	return ns;
 }
 
 int
@@ -201,7 +204,7 @@
 		servername = nil;
 		cfg.resolver = 0;
 	}
-	if(server == nil || *server == 0)
+	if(server == nil || server[0] == 0 || server[1] == 0)
 		return 0;
 	servername = estrdup(server);
 	cfg.resolver = 1;
@@ -276,8 +279,8 @@
 	name = type = nil;
 	tmpsrv = 0;
 
-	if(*f[0] == '@') {
-		if(setserver(f[0]+1) < 0)
+	if(*f[0] == '@' || *f[0] == '!') {
+		if(setserver(f[0]) < 0)
 			return;
 
 		switch(n){
@@ -306,5 +309,5 @@
 	doquery(name, type);
 
 	if(tmpsrv)
-		setserver("");
+		setserver("@");
 }
--- a/sys/src/cmd/ndb/dntcpserver.c
+++ b/sys/src/cmd/ndb/dntcpserver.c
@@ -3,6 +3,8 @@
 #include <bio.h>
 #include <ndb.h>
 #include <ip.h>
+#include <mp.h>
+#include <libsec.h>
 #include "dns.h"
 
 enum {
@@ -12,10 +14,10 @@
 static int	readmsg(int, uchar*, int);
 static int	reply(int, uchar *, DNSmsg*, Request*, uchar*);
 static int	dnzone(int, uchar *, DNSmsg*, DNSmsg*, Request*, uchar*);
-static int	tcpannounce(char *mntpt, char *addr, char caller[128]);
+static int	tcpannounce(char *mntpt, char *addr, char caller[128], char *cert);
 
 void
-dntcpserver(char *mntpt, char *addr)
+dntcpserver(char *mntpt, char *addr, char *cert)
 {
 	volatile int fd, len, rcode, rv;
 	volatile long ms;
@@ -40,7 +42,7 @@
 	}
 
 	procsetname("%s: tcp server %s", mntpt, addr);
-	if((fd = tcpannounce(mntpt, addr, caller)) < 0){
+	if((fd = tcpannounce(mntpt, addr, caller, cert)) < 0){
 		warning("can't announce %s on %s: %r", addr, mntpt);
 		_exits(0);
 	}
@@ -259,13 +261,20 @@
 }
 
 static int
-tcpannounce(char *mntpt, char *addr, char caller[128])
+tcpannounce(char *mntpt, char *addr, char caller[128], char *cert)
 {
 	char adir[NETPATHLEN], ldir[NETPATHLEN], buf[128];
 	int acfd, lcfd, dfd, wfd, rfd, procs;
+	PEMChain *chain = nil;
 
+	if(cert != nil){
+		chain = readcertchain(cert);
+		if(chain == nil)
+			return -1;
+	}
+
 	/* announce tcp dns port */
-	snprint(buf, sizeof(buf), "%s/tcp!%s!53", mntpt, addr);
+	snprint(buf, sizeof(buf), "%s/tcp!%s!%s", mntpt, addr, cert == nil ? "53" : "853");
 	acfd = announce(buf, adir);
 	if(acfd < 0)
 		return -1;
@@ -277,7 +286,6 @@
 		close(acfd);
 		return -1;
 	}
-
 	procs = 0;
 	for(;;) {
 		if(procs >= Maxprocs || (procs % 8) == 0){
@@ -314,7 +322,23 @@
 			close(lcfd);
 			if(dfd < 0)
 				_exits(0);
+			if(chain != nil){
+				TLSconn conn;
+				int fd;
 
+				memset(&conn, 0, sizeof conn);
+				conn.cert = emalloc(conn.certlen = chain->pemlen);
+				memmove(conn.cert, chain->pem, conn.certlen);
+				conn.chain = chain->next;
+				fd = tlsServer(dfd, &conn);
+				if(fd < 0){
+					close(dfd);
+					_exits(0);
+				}
+				free(conn.cert);
+				free(conn.sessionID);
+				dfd = fd;
+			}
 			/* get the callers ip!port */
 			memset(caller, 0, 128);
 			snprint(buf, sizeof(buf), "%s/remote", ldir);
@@ -322,7 +346,6 @@
 				read(rfd, caller, 128-1);
 				close(rfd);
 			}
-
 			/* child returns */
 			return dfd;
 		default: