xref: /inferno-os/appl/cmd/ip/bootpd.b (revision 62d7827bc358c000db9ff48fe61bd28ac352a884)
1implement Bootpd;
2
3#
4# to do:
5#	DHCP
6#
7
8include "sys.m";
9	sys: Sys;
10
11include "draw.m";
12
13include "bufio.m";
14	bufio: Bufio;
15	Iobuf: import bufio;
16
17include "attrdb.m";
18	attrdb: Attrdb;
19	Attr, Db, Dbentry, Tuples: import attrdb;
20
21include "dial.m";
22	dial: Dial;
23
24include "ip.m";
25	ip: IP;
26	IPaddr, Udphdr: import ip;
27
28include "ipattr.m";
29	ipattr: IPattr;
30
31include "ether.m";
32	ether: Ether;
33
34include "arg.m";
35
36Bootpd: module
37{
38	init: fn(nil: ref Draw->Context, argv: list of string);
39};
40
41stderr: ref Sys->FD;
42debug: int;
43sniff: int;
44verbose: int;
45
46siaddr: IPaddr;
47netmask: IPaddr;
48myname: string;
49progname := "bootpd";
50net := "/net";
51ndb: ref Db;
52ndbfile := "/lib/ndb/local";
53mtime := 0;
54testing := 0;
55
56Udphdrsize: con IP->Udphdrlen;
57
58init(nil: ref Draw->Context, args: list of string)
59{
60	sys = load Sys Sys->PATH;
61	stderr = sys->fildes(2);
62	bufio = load Bufio Bufio->PATH;
63	if(bufio == nil)
64		loadfail(Bufio->PATH);
65	attrdb = load Attrdb Attrdb->PATH;
66	if(attrdb == nil)
67		loadfail(Attrdb->PATH);
68	attrdb->init();
69	dial = load Dial Dial->PATH;
70	if(dial == nil)
71		loadfail(Dial->PATH);
72	ip = load IP IP->PATH;
73	if(ip == nil)
74		loadfail(IP->PATH);
75	ip->init();
76	ipattr = load IPattr IPattr->PATH;
77	if(ipattr == nil)
78		loadfail(IPattr->PATH);
79	ipattr->init(attrdb, ip);
80	ether = load Ether Ether->PATH;
81	if(ether == nil)
82		loadfail(Ether->PATH);
83	ether->init();
84
85	verbose = 1;
86	sniff = 0;
87	debug = 0;
88	arg := load Arg Arg->PATH;
89	if(arg == nil)
90		raise "fail: load Arg";
91	arg->init(args);
92	arg->setusage("bootpd [-dsqv] [-f file] [-x network]");
93	progname = arg->progname();
94	while((o := arg->opt()) != 0)
95		case o {
96		'd' =>	debug++;
97		's' =>		sniff = 1; debug = 255;
98		'q' =>	verbose = 0;
99		'v' =>	verbose = 1;
100		'x' =>	net = arg->earg();
101		'f' =>		ndbfile = arg->earg();
102		't' =>		testing = 1; debug = 1; verbose = 1;
103		* =>		arg->usage();
104		}
105	args = arg->argv();
106	if(args != nil)
107		arg->usage();
108	arg = nil;
109
110	sys->pctl(Sys->FORKFD|Sys->FORKNS, nil);
111
112	if(!sniff && (err := dbread()) != nil)
113		error(err);
114
115	myname = sysname();
116	if(myname == nil)
117		error("system name not set");
118	(siaddr, err) = csquery(myname);
119	if(err != nil)
120		error(sys->sprint("can't find IP address for %s: %s", myname, err));
121	if(debug)
122		sys->fprint(stderr, "bootpd: local IP address is %s\n", siaddr.text());
123
124	addr := net+"/udp!*!67";
125	if(testing)
126		addr = net+"/udp!*!499";
127	if(debug)
128		sys->fprint(stderr, "bootpd: announcing %s\n", addr);
129	c := dial->announce(addr);
130	if(c == nil)
131		error(sys->sprint("can't announce %s: %r", addr));
132	if(sys->fprint(c.cfd, "headers") < 0)
133		error(sys->sprint("can't set headers mode: %r"));
134
135	if(debug)
136		sys->fprint(stderr, "bootpd: opening %s/data\n", c.dir);
137	c.dfd = sys->open(c.dir+"/data", sys->ORDWR);
138	if(c.dfd == nil)
139		error(sys->sprint("can't open %s/data: %r", c.dir));
140
141	spawn server(c);
142}
143
144loadfail(s: string)
145{
146	error(sys->sprint("can't load %s: %r", s));
147}
148
149error(s: string)
150{
151	sys->fprint(stderr, "bootpd: %s\n", s);
152	raise "fail:error";
153}
154
155server(c: ref Sys->Connection)
156{
157	buf := array[2048] of byte;
158	badread := 0;
159	for(;;) {
160		n := sys->read(c.dfd, buf, len buf);
161		if(n <0) {
162			if (badread++ > 10)
163				break;
164			continue;
165		}
166		badread = 0;
167		if(n < Udphdrsize) {
168			if(debug)
169				sys->fprint(stderr, "bootpd: short Udphdr: %d bytes\n", n);
170			continue;
171		}
172		hdr := Udphdr.unpack(buf, Udphdrsize);
173		if(debug)
174			sys->fprint(stderr, "bootpd: received request from udp!%s!%d\n", hdr.raddr.text(), hdr.rport);
175		if(n < Udphdrsize+300) {
176			if(debug)
177				sys->fprint(stderr, "bootpd: short request of %d bytes\n", n - Udphdrsize);
178			continue;
179		}
180
181		(bootp, err) := Bootp.unpack(buf[Udphdrsize:]);
182		if(err != nil) {
183			if(debug)
184				sys->fprint(stderr, "bootpd: can't unpack packet: %s\n", err);
185			continue;
186		}
187		if(debug >= 2)
188			sys->fprint(stderr, "bootpd: recvd {%s}\n", bootp.text());
189		if(sniff)
190			continue;
191		if(bootp.htype != 1 || bootp.hlen != 6) {
192			# if it isn't ether, we don't do it
193			if(debug)
194				sys->fprint(stderr, "bootpd: hardware type not ether; ignoring.\n");
195			continue;
196		}
197		if((err = dbread()) != nil) {
198			sys->fprint(stderr,  "bootpd: getreply: dbread failed: %s\n", err);
199			continue;
200		}
201		rec := lookup(bootp);
202		if(rec == nil) {
203			# we can't answer this request
204			if(debug)
205				sys->fprint(stderr, "bootpd: cannot answer request.\n");
206			continue;
207		}
208		if(debug)
209			sys->fprint(stderr, "bootpd: found a matching entry: {%s}\n", rec.text());
210		mkreply(bootp, rec);
211		if(verbose)
212			sys->print("bootpd: %s -> %s %s\n", ether->text(rec.ha), rec.hostname, rec.ip.text());
213		if(debug)
214			sys->fprint(stderr, "bootpd: reply {%s}\n", bootp.text());
215		repl := bootp.pack();
216		if(!testing)
217			arpenter(rec.ip.text(), ether->text(rec.ha));
218		send(hdr, repl);
219	}
220	sys->fprint(stderr, "bootpd: %d read errors: %r\n", badread);
221}
222
223arpenter(ip, ha: string)
224{
225	if(debug)
226		sys->fprint(stderr, "bootpd: arp: %s -> %s\n", ip, ha);
227	fd := sys->open(net+"/arp", Sys->OWRITE);
228	if(fd == nil) {
229		if(debug)
230			sys->fprint(stderr, "bootpd: arp open failed: %r\n");
231		return;
232	}
233	if(sys->fprint(fd, "add %s %s", ip, ha) < 0){
234		if(debug)
235			sys->fprint(stderr, "bootpd: error writing arp: %r\n");
236	}
237}
238
239sysname(): string
240{
241	t := rf("/dev/sysname");
242	if(t != nil)
243		return t;
244	return rf("#e/sysname");
245}
246
247rf(name: string): string
248{
249	fd := sys->open(name, Sys->OREAD);
250	buf := array[Sys->NAMEMAX] of byte;
251	n := sys->read(fd, buf, len buf);
252	if(n <= 0)
253		return nil;
254	return string buf[0:n];
255}
256
257csquery(name: string): (IPaddr, string)
258{
259	siaddr = ip->noaddr;
260	# get a local IP address by translating our sysname with cs(8)
261	csfile := net+"/cs";
262	fd := sys->open(net+"/cs", Sys->ORDWR);
263	if(fd == nil)
264		return (ip->noaddr, sys->sprint("can't open %s/cs: %r", csfile));
265	if(sys->fprint(fd, "net!%s!0", name) < 0)
266		return (ip->noaddr, sys->sprint("can't translate net!%s!0: %r", name));
267	sys->seek(fd, big 0, 0);
268	a := array[1024] of byte;
269	n := sys->read(fd, a, len a);
270	if(n <= 0)
271		return (ip->noaddr, "no result from "+csfile);
272	reply := string a[0:n];
273	(l, addr):= sys->tokenize(reply, " ");
274	if(l != 2)
275		return (ip->noaddr, "bad cs reply format");
276	(l, addr) = sys->tokenize(hd tl addr, "!");
277	if(l < 2)
278		return (ip->noaddr, "bad cs reply format");
279	(ok, ipa) := IPaddr.parse(hd addr);
280	if(ok < 0 || !ipok(siaddr))
281		return (ip->noaddr, "can't parse address: "+hd addr);
282	return (ipa, nil);
283}
284
285Hostinfo: adt {
286	hostname: string;
287
288	ha: array of byte;	# hardware addr
289	ip: IPaddr;		# client IP addr
290	bootf: string;		# boot file path
291	netmask: IPaddr;	# subnet mask
292	ipgw: IPaddr;	# gateway IP addr
293	fs: IPaddr;		# file server IP addr
294	auth: IPaddr;	# authentication server IP addr
295
296	text:	fn(inf: self ref Hostinfo): string;
297};
298
299send(hdr: ref Udphdr, msg: array of byte)
300{
301	replyaddr := net+"/udp!255.255.255.255!68";	# TO DO: gateway
302	if(testing)
303		replyaddr = sys->sprint("udp!%s!%d", hdr.raddr.text(), hdr.rport);
304	lport := "67";
305	if(testing)
306		lport = "499";
307	c := dial->dial(replyaddr, lport);
308	if(c == nil) {
309		sys->fprint(stderr, "bootpd: can't dial %s for reply: %r\n", replyaddr);
310		return;
311	}
312	n := sys->write(c.dfd, msg, len msg);
313	if(n != len msg)
314		sys->fprint(stderr, "bootpd: udp write error: %r\n");
315}
316
317mkreply(bootp: ref Bootp, rec: ref Hostinfo)
318{
319	bootp.op = 2; # boot reply
320	bootp.yiaddr = rec.ip;
321	bootp.siaddr = siaddr;
322	bootp.giaddr = ip->noaddr;
323	bootp.sname = myname;
324	bootp.file = string rec.bootf;
325	bootp.vend = array of byte sys->sprint("p9  %s %s %s %s", rec.netmask.text(), rec.fs.text(), rec.auth.text(), rec.ipgw.text());
326}
327
328dbread(): string
329{
330	if(ndb == nil){
331		ndb = Db.open(ndbfile);
332		if(ndb == nil)
333			return sys->sprint("cannot open %s: %r", ndbfile);
334	}else if(ndb.changed())
335		ndb.reopen();
336	return nil;
337}
338
339ipok(a: IPaddr): int
340{
341	return a.isv4() && !(a.eq(ip->v4noaddr) || a.eq(ip->noaddr) || a.ismulticast());
342}
343
344lookup(bootp: ref Bootp): ref Hostinfo
345{
346	if(ndb == nil)
347		return nil;
348	inf: ref Hostinfo;
349	hwaddr := ether->text(bootp.chaddr);
350	if(ipok(bootp.ciaddr)){
351		# client thinks it knows address; check match with MAC address
352		ipaddr := bootp.ciaddr.text();
353		ptr: ref Attrdb->Dbptr;
354		for(;;){
355			e: ref Dbentry;
356			(e, ptr) = ndb.findbyattr(ptr, "ip", ipaddr, "ether");
357			if(e == nil)
358				break;
359			# TO DO: check result
360			inf = matchandfill(e, "ip", ipaddr, "ether", hwaddr);
361			if(inf != nil)
362				return inf;
363		}
364	}
365	# look up an ip address associated with given MAC address
366	ptr: ref Attrdb->Dbptr;
367	for(;;){
368		e: ref Dbentry;
369		(e, ptr) = ndb.findbyattr(ptr, "ether", hwaddr, "ip");
370		if(e == nil)
371			break;
372		# TO DO: check right net etc.
373		inf = matchandfill(e, "ether", hwaddr, "ip", nil);
374		if(inf != nil)
375			return inf;
376	}
377	return nil;
378}
379
380matchandfill(e: ref Dbentry, attr: string, val: string, rattr: string, rval: string): ref Hostinfo
381{
382	matches := e.findbyattr(attr, val, rattr);
383	for(; matches != nil; matches = tl matches){
384		(line, attrs) := hd matches;
385		for(; attrs != nil; attrs = tl attrs){
386			if(rval == nil || (hd attrs).val == rval){
387				inf := fillup(line, e);
388				if(inf != nil)
389					return inf;
390				break;
391			}
392		}
393	}
394	return nil;
395}
396
397fillup(line: ref Tuples, e: ref Dbentry): ref Hostinfo
398{
399	ok: int;
400	inf := ref Hostinfo;
401	inf.netmask = ip->noaddr;
402	inf.ipgw = ip->noaddr;
403	inf.fs = ip->v4noaddr;
404	inf.auth = ip->v4noaddr;
405	inf.hostname = find(line, e, "sys");
406	s := find(line, e, "ether");
407	if(s != nil)
408		inf.ha = ether->parse(s);
409	s = find(line, e, "ip");
410	if(s == nil)
411		return nil;
412	(ok, inf.ip) = IPaddr.parse(s);
413	if(ok < 0)
414		return nil;
415	(results, err) := ipattr->findnetattrs(ndb, "ip", s, list of{"ipmask", "ipgw", "fs", "FILESERVER", "SIGNER", "auth", "bootf"});
416	if(err != nil)
417		return nil;
418	for(; results != nil; results = tl results){
419		(a, nattrs) := hd results;
420		if(!a.eq(inf.ip))
421			continue;	# different network
422		for(; nattrs != nil; nattrs = tl nattrs){
423			na := hd nattrs;
424			case na.name {
425			"ipmask" =>
426				inf.netmask = takeipmask(na.pairs, inf.netmask);
427			"ipgw" =>
428				inf.ipgw = takeipattr(na.pairs, inf.ipgw);
429			"fs" or "FILESERVER" =>
430				inf.fs = takeipattr(na.pairs, inf.fs);
431			"auth" or "SIGNER" =>
432				inf.auth = takeipattr(na.pairs, inf.auth);
433			"bootf" =>
434				inf.bootf = takeattr(na.pairs, inf.bootf);
435			}
436		}
437	}
438	return inf;
439}
440
441takeattr(pairs: list of ref Attr, s: string): string
442{
443	if(s != nil || pairs == nil)
444		return s;
445	return (hd pairs).val;
446}
447
448takeipattr(pairs: list of ref Attr, a: IPaddr): IPaddr
449{
450	if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr)))
451		return a;
452	(ok, na) := parseip((hd pairs).val);
453	if(ok < 0)
454		return a;
455	return na;
456}
457
458takeipmask(pairs: list of ref Attr, a: IPaddr): IPaddr
459{
460	if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr)))
461		return a;
462	(ok, na) := IPaddr.parsemask((hd pairs).val);
463	if(ok < 0)
464		return a;
465	return na;
466}
467
468findip(line: ref Tuples, e: ref Dbentry, attr: string): (int, IPaddr)
469{
470	s := find(line, e, attr);
471	if(s == nil)
472		return (-1, ip->noaddr);
473	return parseip(s);
474}
475
476parseip(s: string): (int, IPaddr)
477{
478	(ok, a) := IPaddr.parse(s);
479	if(ok < 0){
480		# look it up if it's a system name
481		s = findbyattr("sys", s, "ip");
482		(ok, a) = IPaddr.parse(s);
483	}
484	return (ok, a);
485}
486
487find(line: ref Tuples, e: ref Dbentry, attr: string): string
488{
489	if(line != nil){
490		a := line.find(attr);
491		if(a != nil)
492			return (hd a).val;
493	}
494	if(e != nil){
495		for(matches := e.find(attr); matches != nil; matches = tl matches){
496			(nil, a) := hd matches;
497			if(a != nil)
498				return (hd a).val;
499		}
500	}
501	return nil;
502}
503
504findbyattr(attr: string, val: string, rattr: string): string
505{
506	ptr: ref Attrdb->Dbptr;
507	for(;;){
508		e: ref Dbentry;
509		(e, ptr) = ndb.findbyattr(ptr, attr, val, rattr);
510		if(e == nil)
511			break;
512		rvl := e.find(rattr);
513		if(rvl != nil){
514			(nil, al) := hd rvl;
515			return (hd al).val;
516		}
517	}
518	return nil;
519}
520
521missing(rec: ref Hostinfo): string
522{
523	s := "";
524	if(rec.ha == nil)
525		s += " hardware address";
526	if(rec.ip.eq(ip->noaddr))
527		s += " IP address";
528	if(rec.bootf == nil)
529		s += " bootfile";
530	if(rec.netmask.eq(ip->noaddr))
531		s += " subnet mask";
532	if(rec.ipgw.eq(ip->noaddr))
533		s += " gateway";
534	if(rec.fs.eq(ip->noaddr))
535		s += " file server";
536	if(rec.auth.eq(ip->noaddr))
537		s += " authentication server";
538	if(s != "")
539		return s[1:];
540	return nil;
541}
542
543dtoa(data: array of byte): string
544{
545	if(data == nil)
546		return nil;
547	result: string;
548	for(i:=0; i < len data; i++)
549		result += sys->sprint(".%d", int data[i]);
550	return result[1:];
551}
552
553magic(cookie: array of byte): string
554{
555	if(eqa(cookie, array[] of { byte 'p', byte '9', byte ' ', byte ' ' }))
556		return "plan9";
557	if(eqa(cookie, array[] of { byte 99, byte 130, byte 83, byte 99 }))
558		return "rfc1048";
559	if(eqa(cookie, array[] of { byte 'C', byte 'M', byte 'U', byte 0 }))
560		return "cmu";
561	return dtoa(cookie);
562}
563
564eqa(a1: array of byte, a2: array of byte): int
565{
566	if(len a1 != len a2)
567		return 0;
568	for(i := 0; i < len a1; i++)
569		if(a1[i] != a2[i])
570			return 0;
571	return 1;
572}
573
574Hostinfo.text(rec: self ref Hostinfo): string
575{
576	return sys->sprint("ha=%s ip=%s bf=%s sm=%s gw=%s fs=%s au=%s",
577		ether->text(rec.ha), rec.ip.text(), rec.bootf, rec.netmask.masktext(), rec.ipgw.text(), rec.fs.text(), rec.auth.text());
578}
579
580Bootp: adt
581{
582	op:	int;		# opcode [1]
583	htype:	int;	# hardware type[1]
584	hlen:	int;		# hardware address length [1]
585	hops:	int;	# gateway hops [1]
586	xid:	int;		# random number [4]
587	secs:	int;		# seconds elapsed since client started booting [2]
588	flags:	int;	# flags[2]
589	ciaddr:	IPaddr;	# client ip address (client->server)[4]
590	yiaddr:	IPaddr;	# your ip address (server->client)[4]
591	siaddr:	IPaddr;	# server's ip address [4]
592	giaddr:	IPaddr;	# gateway ip address [4]
593	chaddr:	array of byte;	# client hardware (mac) address [16]
594	sname:	string;	# server host name [64]
595	file:	string;		# boot file name [128]
596	vend:	array of byte;	# vendor-specific [128]
597
598	unpack:	fn(a: array of byte): (ref Bootp, string);
599	pack:	fn(bp: self ref Bootp): array of byte;
600	text:	fn(bp: self ref Bootp): string;
601};
602
603Bootp.unpack(data: array of byte): (ref Bootp, string)
604{
605	if(len data < 300)
606		return (nil, "too short");
607
608	bp := ref Bootp;
609	bp.op = int data[0];
610	bp.htype = int data[1];
611	bp.hlen = int data[2];
612	if(bp.hlen > 16)
613		return (nil, "length error");
614	bp.hops = int data[3];
615	bp.xid = ip->get4(data, 4);
616	bp.secs = ip->get2(data, 8);
617	bp.flags = ip->get2(data, 10);
618	bp.ciaddr = IPaddr.newv4(data[12:16]);
619	bp.yiaddr = IPaddr.newv4(data[16:20]);
620	bp.siaddr = IPaddr.newv4(data[20:24]);
621	bp.giaddr = IPaddr.newv4(data[24:28]);
622	bp.chaddr = data[28:28+bp.hlen];
623	bp.sname = ctostr(data[44:108]);
624	bp.file = ctostr(data[108:236]);
625	bp.vend = data[236:300];
626	return (bp, nil);
627}
628
629Bootp.pack(bp: self ref Bootp): array of byte
630{
631	data := array[364] of { * => byte 0 };
632	data[0] = byte bp.op;
633	data[1] = byte bp.htype;
634	data[2] = byte bp.hlen;
635	data[3] = byte bp.hops;
636	ip->put4(data, 4, bp.xid);
637	ip->put2(data, 8, bp.secs);
638	ip->put2(data, 10, bp.flags);
639	data[12:] = bp.ciaddr.v4();
640	data[16:] = bp.yiaddr.v4();
641	data[20:] = bp.siaddr.v4();
642	data[24:] = bp.giaddr.v4();
643	data[28:] = bp.chaddr;
644	data[44:] = array of byte bp.sname;
645	data[108:] = array of byte bp.file;
646	data[236:] = bp.vend;
647	return data;
648}
649
650ctostr(cstr: array of byte): string
651{
652	for(i:=0; i<len cstr; i++)
653		if(cstr[i] == byte 0)
654			break;
655	return string cstr[0:i];
656}
657
658Bootp.text(bp: self ref Bootp): string
659{
660	s := sys->sprint("op=%d htype=%d hlen=%d hops=%d xid=%ud secs=%ud ciaddr=%s yiaddr=%s",
661		int bp.op, bp.htype, bp.hlen, bp.hops, bp.xid, bp.secs, bp.ciaddr.text(), bp.yiaddr.text());
662	s += sys->sprint(" server=%s gateway=%s hwaddr=%q host=%q file=%q magic=%q",
663		bp.siaddr.text(), bp.giaddr.text(), ether->text(bp.chaddr), bp.sname, bp.file, magic(bp.vend[0:4]));
664	if(magic(bp.vend[0:4]) == "plan9")
665		s += "("+ctostr(bp.vend)+")";
666	return s;
667}
668