shithub: riscv

Download patch

ref: a3f3e31b204821b31891c91dbb4266a03c82bf0b
parent: eed90aa0ad7ac012d9f1abe638074156654d81ef
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Jun 10 18:08:57 EDT 2018

dhcp6d: add minimal stateless DHCPv6 server for network boot and DNS configuration

--- a/sys/man/8/dhcpd
+++ b/sys/man/8/dhcpd
@@ -1,6 +1,6 @@
 .TH DHCPD 8
 .SH NAME
-dhcpd, dhcpleases, rarpd, tftpd \- Internet booting
+dhcpd, dhcp6d, dhcpleases, rarpd, tftpd \- Internet booting
 .SH SYNOPSIS
 .PP
 .B ip/dhcpd
@@ -20,6 +20,13 @@
 .PP
 .B ip/dhcpleases
 .PP
+.B ip/dhcp6d
+.RB [ -d ]
+.RB [ -f
+.IR ndb-file ]
+.RB [ -x
+.IR netmtpt ]
+.PP
 .B ip/rarpd
 .RB [ -d ]
 .RB [ -e
@@ -37,7 +44,8 @@
 These programs support booting over the Internet.
 They should all be run on the same server to
 allow other systems to be booted.
-.I Dhcpd
+.IR Dhcpd ,
+.I dhcp6d
 and
 .I tftpd
 are used to boot everything;
@@ -220,6 +228,10 @@
 .I secs
 as the minimum lease time for static addresses.
 .PD
+.PP
+.I Dhcp6d
+provides DHCPv6 service for IPv6 clients. Only network boot and
+DNS parameters are supported.
 .PP
 .I Dhcpleases
 prints out the currently valid DHCP leases found in the
--- /dev/null
+++ b/sys/src/cmd/ip/dhcp6d.c
@@ -1,0 +1,561 @@
+/* minimal stateless DHCPv6 server for network boot */
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <ndb.h>
+
+enum {
+	Eaddrlen = 6,
+};
+
+typedef struct Req Req;
+struct Req
+{
+	int		tra;
+
+	Udphdr		*udp;
+	Ipifc		*ifc;
+
+	uchar		mac[Eaddrlen];
+	uchar		ips[IPaddrlen*8];
+	int		nips;
+
+	Ndb		*db;
+	Ndbtuple	*t;
+
+	struct {
+		int	t;
+		uchar	*p;
+		uchar	*e;
+	} req;
+
+	struct {
+		int	t;
+		uchar	*p;
+		uchar	*e;
+	} resp;
+};
+
+typedef struct Otab Otab;
+struct Otab
+{
+	int	t;
+	int	(*f)(uchar *, int, Otab*, Req*);
+	char	*q[3];
+	int	done;
+};
+
+static Otab otab[];
+static Ipifc *ipifcs;
+static ulong starttime;
+static int debug;
+
+static uchar v6loopback[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 1
+};
+
+/*
+ * open ndbfile as db if not already open.  also check for stale data
+ * and reload as needed.
+ */
+static Ndb *
+opendb(char *ndbfile)
+{
+	static Ndb *db;
+	/* check no more often than once every minute */
+	if(db == nil)
+		db = ndbopen(ndbfile);
+	else if (ndbchanged(db))
+		ndbreopen(db);
+	return db;
+}
+
+static Ipifc*
+findifc(char *net, uchar ip[IPaddrlen])
+{
+	Ipifc *ifc;
+	Iplifc *lifc;
+
+	ipifcs = readipifc(net, ipifcs, -1);
+	for(ifc = ipifcs; ifc != nil; ifc = ifc->next)
+		for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next)
+			if(ipcmp(lifc->ip, ip) == 0)
+				return ifc;
+
+	return nil;
+}
+
+static int
+openlisten(char *net)
+{
+	int fd, cfd;
+	char data[128], devdir[40];
+	Ipifc *ifc;
+
+	sprint(data, "%s/udp!*!dhcp6s", net);
+	cfd = announce(data, devdir);
+	if(cfd < 0)
+		sysfatal("can't announce: %r");
+	if(fprint(cfd, "headers") < 0)
+		sysfatal("can't set header mode: %r");
+
+	ipifcs = readipifc(net, ipifcs, -1);
+	for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+		if(ifc->lifc == nil)
+			continue;
+		if(strcmp(ifc->dev, "/dev/null") == 0)
+			continue;
+		if(fprint(cfd, "addmulti %I ff02::1:2", ifc->lifc->ip) < 0)
+			fprint(2, "can't add interface %s: %r", ifc->dev);
+	}
+
+	sprint(data, "%s/data", devdir);
+	fd = open(data, ORDWR);
+	if(fd < 0)
+		sysfatal("open udp data: %r");
+
+	return fd;
+}
+
+static uchar*
+gettlv(int x, int *plen, uchar *p, uchar *e)
+{
+	int t;
+	int l;
+
+	if(plen != nil)
+		*plen = 0;
+	while(p+4 <= e){
+		t = p[0]<<8 | p[1];
+		l = p[2]<<8 | p[3];
+		if(p+4+l > e)
+			break;
+		if(t == x){
+			if(plen != nil)
+				*plen = l;
+			return p+4;
+		}
+		p += l+4;
+	}
+	return nil;
+}
+
+static int
+getv6ips(uchar *ip, int n, Ndbtuple *t, char *attr)
+{
+	int r = 0;
+
+	if(n < IPaddrlen)
+		return 0;
+	if(*attr == '@')
+		attr++;
+	for(; t != nil; t = t->entry){
+		if(strcmp(t->attr, attr) != 0)
+			continue;
+		if(parseip(ip, t->val) == -1)
+			continue;
+		if(isv4(ip))
+			continue;
+		ip += IPaddrlen;
+		r += IPaddrlen;
+		if(r >= n)
+			break;
+	}
+	return r;
+}
+
+static int
+lookupips(uchar *ip, int n, Ndb *db, uchar mac[Eaddrlen])
+{
+	Ndbtuple *t;
+	Ndbs s;
+	char val[256], *attr;
+	int r;
+
+	/*
+	 *  use hardware address to find an ip address
+	 */
+	attr = "ether";
+	snprint(val, sizeof val, "%E", mac);
+
+	t = ndbsearch(db, &s, attr, val);
+	r = 0;
+	while(t != nil){
+		r += getv6ips(ip + r, n - r, t, "ip");
+		ndbfree(t);
+		if(r >= n)
+			break;
+		t = ndbsnext(&s, attr, val);
+	}
+	return r;
+}
+
+static void
+clearotab(void)
+{
+	Otab *o;
+
+	for(o = otab; o->t != 0; o++)
+		o->done = 0;
+}
+
+static Otab*
+findotab(int t)
+{
+	Otab *o;
+
+	for(o = otab; o->t != 0; o++)
+		if(o->t == t)
+			return o;
+	return nil;
+}
+
+static int
+addoption(Req *r, int t)
+{
+	Otab *o;
+	int n;
+
+	if(r->resp.p+4 > r->resp.e)
+		return -1;
+	o = findotab(t);
+	if(o == nil || o->f == nil || o->done)
+		return -1;
+	o->done = 1;
+	n = (*o->f)(r->resp.p+4, r->resp.e - (r->resp.p+4), o, r);
+	if(n < 0 || r->resp.p+4+n > r->resp.e)
+		return -1;
+	r->resp.p[0] = t>>8, r->resp.p[1] = t;
+	r->resp.p[2] = n>>8, r->resp.p[3] = n;
+	if(debug) fprint(2, "%d(%.*H)\n", t, n, r->resp.p+4);
+	r->resp.p += 4+n;
+	return n;
+}
+
+static void
+usage(void)
+{
+	fprint(2, "%s [-d]  [-f ndbfile] [-x netmtpt]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *ndbfile = nil;
+	char *net = "/net";
+	uchar ibuf[4096], obuf[4096];
+	Req r[1];
+	int fd, n, i;
+
+	fmtinstall('H', encodefmt);
+	fmtinstall('I', eipfmt);
+	fmtinstall('E', eipfmt);
+
+	ARGBEGIN {
+	case 'd':
+		debug++;
+		break;
+	case 'f':
+		ndbfile = EARGF(usage());
+		break;
+	case 'x':
+		net = EARGF(usage());
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	starttime = time(nil) - 946681200UL;
+
+	if(opendb(ndbfile) == nil)
+		sysfatal("opendb: %r");
+
+	fd = openlisten(net);
+
+	/* put process in background */
+	if(!debug)
+	switch(rfork(RFNOTEG|RFPROC|RFFDG)) {
+	default:
+		exits(nil);
+	case -1:
+		sysfatal("fork: %r");
+	case 0:
+		break;
+	}
+
+	while((n = read(fd, ibuf, sizeof(ibuf))) > Udphdrsize+4){
+		r->req.p = ibuf+Udphdrsize;
+		r->req.e = ibuf+n;
+
+		memmove(obuf, ibuf, Udphdrsize);
+		r->udp = (Udphdr*)obuf;
+		r->resp.p = obuf+Udphdrsize;
+		r->resp.e = &obuf[sizeof(obuf)];
+
+		r->tra = r->req.p[1]<<16 | r->req.p[2]<<8 | r->req.p[3];
+		r->req.t = r->req.p[0];
+
+		if((r->ifc = findifc(net, r->udp->ifcaddr)) == nil)
+			continue;
+
+		if(debug)
+		fprint(2, "%I->%I(%s) typ=%d tra=%x\n",
+			r->udp->raddr, r->udp->laddr, r->ifc->dev,
+			r->req.t, r->tra);
+
+		switch(r->req.t){
+		default:
+			continue;
+		case 1:		/* solicit */
+			r->resp.t = 2;	/* advertise */
+			break;
+		case 3:		/* request */
+		case 11:	/* information request */
+			r->resp.t = 7;	/* reply */
+			break;
+		}
+		r->resp.p[0] = r->resp.t;
+		r->resp.p[1] = r->tra>>16;
+		r->resp.p[2] = r->tra>>8;
+		r->resp.p[3] = r->tra;
+
+		r->req.p += 4;
+		r->resp.p += 4;
+
+		r->t = nil;
+
+		clearotab();
+
+		/* Server Identifier */
+		if(addoption(r, 2) < 0)
+			continue;
+
+		/* Client Identifier */
+		if(addoption(r, 1) < 0)
+			continue;
+
+		/* Lookup taret ip addresses */
+		if((r->db = opendb(ndbfile)) == nil)
+			continue;
+		r->nips = lookupips(r->ips, sizeof(r->ips), r->db, r->mac)/IPaddrlen;
+		if(debug){
+			for(i=0; i<r->nips; i++)
+				fprint(2, "ip=%I\n", r->ips+i*IPaddrlen);
+		}
+
+		addoption(r, 3);
+		addoption(r, 6);
+
+		write(fd, obuf, r->resp.p-obuf);
+		if(debug) fprint(2, "\n");
+	}
+
+	exits(nil);
+}
+
+static int
+oclientid(uchar *w, int n, Otab*, Req *r)
+{
+	int len;
+	uchar *p;
+
+	if((p = gettlv(1, &len, r->req.p, r->req.e)) == nil)
+		return -1;
+	if(len < 4+4+Eaddrlen || n < len)
+		return -1;
+	memmove(r->mac, p+len-Eaddrlen, Eaddrlen);
+	memmove(w, p, len);
+
+	return len;
+}
+
+static int
+oserverid(uchar *w, int n, Otab*, Req *r)
+{
+	if(n < 4+4+Eaddrlen)
+		return -1;
+	w[0] = 0, w[1] = 1;	/* duid type: link layer address + time*/
+	w[2] = 0, w[3] = 1;	/* hw type: ethernet */
+	w += 4;
+	w[0] = starttime>>24;
+	w[1] = starttime>>16;
+	w[2] = starttime>>8;
+	w[3] = starttime;
+	w += 4;
+	myetheraddr(w, r->ifc->dev);
+	return 4+4+Eaddrlen;
+}
+
+static int
+oiana(uchar *w, int n, Otab*, Req *r)
+{
+	int i, len;
+	uchar *p;
+
+	p = gettlv(3, &len, r->req.p, r->req.e);
+	if(p == nil || len < 3*4)
+		return -1;
+
+	len = 3*4 + (4+IPaddrlen+2*4)*r->nips;
+	if(n < len)
+		return -1;
+
+	memmove(w, p, 3*4);
+	w += 3*4;
+
+	for(i = 0; i < r->nips; i++){
+		w[0] = 0, w[1] = 5;
+		w[2] = 0, w[3] = IPaddrlen+2*4;
+		w += 4;
+
+		memmove(w, r->ips + i*IPaddrlen, IPaddrlen);
+		w += IPaddrlen;
+
+		memset(w, 255, 2*4);
+		w += 2*4;
+	}
+
+	return len;
+}
+
+static Ndbtuple*
+lookup(Req *r, char *av[], int ac)
+{
+	Ndbtuple *t;
+	char *s;
+
+	if(ac <= 0)
+		return nil;
+
+	t = nil;
+	if(r->nips > 0){
+		int i;
+
+		/* use the target ip's to lookup info if any */
+		for(i=0; i<r->nips; i++){
+			s = smprint("%I", &r->ips[i*IPaddrlen]);
+			t = ndbconcatenate(t, ndbipinfo(r->db, "ip", s, av, ac));
+			free(s);
+		}
+	} else {
+		Iplifc *lifc;
+
+		/* use the ipv6 networks on the interface */
+		for(lifc=r->ifc->lifc; lifc!=nil; lifc=lifc->next){
+			if(isv4(lifc->ip)
+			|| ipcmp(lifc->ip, v6loopback) == 0
+			|| ISIPV6LINKLOCAL(lifc->ip))
+				continue;
+			s = smprint("%I", lifc->net);
+			t = ndbconcatenate(t, ndbipinfo(r->db, "ip", s, av, ac));
+			free(s);
+		}
+	}
+	return t;
+}
+
+static int
+oro(uchar*, int, Otab *o, Req *r)
+{
+	uchar *p;
+	char *av[100];
+	int i, j, l, ac;
+	Ndbtuple *t;
+
+	p = gettlv(6, &l, r->req.p, r->req.e);
+	if(p == nil || l < 2)
+		return -1;
+
+	ac = 0;
+	for(i=0; i<l; i+=2){
+		if((o = findotab(p[i]>>8 | p[i+1])) == nil || o->done)
+			continue;
+		for(j=0; j<3 && o->q[j]!=nil && ac<nelem(av); j++)
+			av[ac++] = o->q[j];
+	}
+
+	r->t = lookup(r, av, ac);
+
+	if(debug){
+		fprint(2, "ndb(");
+		for(t = r->t; t != nil; t = t->entry){
+			fprint(2, "%s=%s ", t->attr, t->val);
+			if(t->entry != nil && t->entry != t->line)
+				fprint(2, "\n");
+		}
+		fprint(2, ")\n");
+	}
+
+	/* process the options */
+	for(i=0; i<l; i+=2)
+		addoption(r, p[i]>>8 | p[i+1]);
+
+	ndbfree(r->t);
+	r->t = nil;
+
+	return -1;
+}
+
+static int
+oservers(uchar *w, int n, Otab *o, Req *r)
+{
+	return getv6ips(w, n, r->t, o->q[0]);
+}
+
+static int
+odomainlist(uchar *w, int n, Otab *o, Req *q)
+{
+	Ndbtuple *t;
+	int l, r;
+	char *s;
+
+	r = 0;
+	for(t = q->t; t != nil; t = t->entry){
+		if(strcmp(t->attr, o->q[0]) != 0)
+			continue;
+		for(s = t->val; *s != 0; s++){
+			for(l = 0; *s != 0 && *s != '.'; l++)
+				s++;
+			if(r+1+l > n)
+				return -1;
+			w[r++] = l;
+			memmove(w+r, s-l, l);
+			r += l;
+		}
+		if(r >= n)
+			return -1;
+		w[r++] = 0;
+	}
+	return r;
+}
+
+static int
+obootfileurl(uchar *w, int n, Otab *, Req *q)
+{
+	uchar ip[IPaddrlen];
+	Ndbtuple *bootf;
+
+	if((bootf = ndbfindattr(q->t, q->t, "bootf")) == nil)
+		return -1;
+	if(strstr(bootf->val, "://") != nil)
+		return snprint((char*)w, n, "%s", bootf->val);
+	else if(getv6ips(ip, sizeof(ip), q->t, "tftp"))
+		return snprint((char*)w, n, "tftp://[%I]/%s", ip, bootf->val);
+	return -1;
+}
+
+static Otab otab[] = {
+	{  1, oclientid, },
+	{  2, oserverid, },
+	{  3, oiana, },
+	{  6, oro, },
+	{ 23, oservers, "@dns" },
+	{ 24, odomainlist, "dnsdomain" },
+	{ 59, obootfileurl, "bootf", "@tftp", },
+	{ 0 },
+};
--- a/sys/src/cmd/ip/mkfile
+++ b/sys/src/cmd/ip/mkfile
@@ -2,6 +2,7 @@
 
 TARG = 	6in4\
 	ayiya\
+	dhcp6d\
 	dhcpclient\
 	ftpd\
 	gping\