xref: /plan9/sys/src/9/pcboot/pxeload.c (revision aec4d1fc5ec801f618be50d4bf00171c58a8c2db)
1 /*
2  * 9boot - load next kernel via pxe (bootp, tftp) and start it
3  *
4  * intel says that pxe can only load into the bottom 640K,
5  * and intel's boot agent takes 128K, leaving only 512K for 9boot.
6  *
7  * some of this code is from the old 9load's bootp.c.
8  */
9 #include	"u.h"
10 #include	"../port/lib.h"
11 #include	"mem.h"
12 #include	"dat.h"
13 #include	"fns.h"
14 #include	"io.h"
15 #include	"ureg.h"
16 #include	"pool.h"
17 #include	"../port/netif.h"
18 #include	"etherif.h"
19 #include	"../ip/ip.h"
20 #include	"pxe.h"
21 
22 #define TFTPDEF "135.104.9.6"	/* IP of default tftp server */
23 
24 enum {
25 	Tftpusehdrs =	0,	/* flag: use announce+headers for tftp? */
26 	Debug =		0,
27 
28 	Tftphdrsz =	4,
29 	/*
30 	 * this can be bigger than the ether mtu and
31 	 * will work due to ip fragmentation, at least on v4.
32 	 */
33 	Prefsegsize =	1400,
34 	Maxsegsize =	2048,
35 	Bufsz =		Maxsegsize + 2,
36 
37 	Ok =		0,
38 	Err =		-1,
39 	Nonexist =	-2,
40 };
41 
42 typedef struct Ethaddr Ethaddr;
43 typedef struct Kernname Kernname;
44 typedef struct Openeth Openeth;
45 typedef struct Tftp Tftp;
46 
47 struct Tftp {
48 	uchar	header[Tftphdrsz];
49 	uchar	data[Maxsegsize];
50 };
51 
52 struct Kernname {
53 	char	*edev;
54 	char	*bootfile;
55 };
56 
57 struct Openeth {
58 	/* names */
59 	int	ctlrno;
60 	char	ethname[16];	/* ether%d */
61 	char	netethname[32];	/* /net/ether%d */
62 	char	filename[128];	/* from bootp, for tftp */
63 
64 	Chan	*ifcctl;	/* /net/ipifc/clone */
65 	Chan	*ethctl;	/* /net/etherN/0/ctl, for promiscuous mode */
66 
67 	/* udp connection */
68 	Chan	*udpctl;
69 	Chan	*udpdata;
70 	Pxenetaddr *netaddr;
71 	int	rxactive;
72 };
73 
74 struct Ethaddr {		/* communication with sleep procs */
75 	Openeth	*oe;
76 	Pxenetaddr *a;
77 };
78 
79 static char ethernm[] = "ether";
80 static uchar myea[Eaddrlen];
81 static Pxenetaddr myaddr;		/* actually, local ip addr & port */
82 
83 /*
84  * there can be at most one concurrent tftp session until we move these
85  * variables into Openeth or some other struct (Tftpstate).
86  */
87 static ushort tftpport;
88 static int tftpblockno;
89 static int tftpphase;
90 static int progress;
91 static int segsize;
92 static Tftp *tftpb;
93 static Pxenetaddr tftpserv;		/* actually, remote ip addr & port */
94 static Pxenetaddr bootpserv;
95 
96 static int	tftpconnect(Openeth *, Bootp *);
97 
98 uchar *
etheraddr(Openeth * oe)99 etheraddr(Openeth *oe)
100 {
101 	int n;
102 	char name[32], buf[32];
103 	static uchar ea[Eaddrlen];
104 
105 	memset(ea, 0, sizeof ea);
106 	snprint(name, sizeof name, "#l%d/ether%d/addr", oe->ctlrno, oe->ctlrno);
107 	n = readfile(name, buf, sizeof buf - 1);
108 	if (n < 0)
109 		return ea;
110 	buf[n] = '\0';
111 	parseether(ea, buf);
112 	return ea;
113 }
114 
115 static void
udpsend(Openeth * oe,Pxenetaddr * a,void * data,int dlen)116 udpsend(Openeth *oe, Pxenetaddr *a, void *data, int dlen)
117 {
118 	int n;
119 	uchar *buf;
120 	Chan *c;
121 	Etherpkt pkt;
122 	Udphdr *uh;
123 
124 	buf = data;
125 	if (dlen > sizeof pkt)
126 		panic("udpsend: packet too big");
127 
128 	oe->netaddr = a;
129 	/*
130 	 * add Plan 9 UDP pseudo-headers
131 	 */
132 	if (!tftpphase || Tftpusehdrs) {
133 		memset(&pkt, 0, sizeof pkt);
134 		uh = (Udphdr*)&pkt;
135 		memmove(uh + 1, data, dlen);
136 		USED(buf);
137 		buf = (uchar *)uh;
138 		dlen += sizeof *uh;
139 		if (dlen > sizeof pkt)
140 			panic("udpsend: packet too big");
141 
142 		ipmove(uh->laddr, myaddr.ip);
143 		hnputs(uh->lport, myaddr.port);
144 		ipmove(uh->raddr, a->ip);
145 		hnputs(uh->rport, a->port);
146 		if(Debug)
147 			print("udpsend %I!%d -> %I!%d ", uh->laddr,
148 				nhgets(uh->lport), uh->raddr, nhgets(uh->rport));
149 	}
150 	if (waserror()) {
151 		iprint("udp write error\n");
152 		return;			/* send another req later */
153 	}
154 	c = oe->udpdata;
155 	assert(oe->udpdata != nil);
156 	n = devtab[c->type]->write(c, buf, dlen, c->offset);
157 	poperror();
158 	c->offset += n;
159 	if (n != dlen)
160 		print("udpsend: wrote %d/%d\n", n, dlen);
161 	else if (progress)
162 		print(".");
163 }
164 
165 static void
nak(Openeth * oe,Pxenetaddr * a,int code,char * msg,int report)166 nak(Openeth *oe, Pxenetaddr *a, int code, char *msg, int report)
167 {
168 	char buf[4 + 32];
169 
170 	buf[0] = 0;
171 	buf[1] = Tftp_ERROR;
172 	buf[2] = 0;
173 	buf[3] = code;
174 	strncpy(buf+4, msg, sizeof buf - 4 - 1);
175 	udpsend(oe, a, buf, 4 + strlen(buf+4) + 1);
176 	if(report)
177 		print("\ntftp: error(%d): %s\n", code, msg);
178 }
179 
180 /* a is the source address we're looking for */
181 static int
tuplematch(Pxenetaddr * a,Udphdr * h)182 tuplematch(Pxenetaddr *a, Udphdr *h)
183 {
184 	int port;
185 	uchar *ip;
186 
187 	if (tftpphase && !Tftpusehdrs)
188 		return 1;
189 	/*
190 	 * we're using udp headers mode, because we're still doing bootp,
191 	 * or we are doing tftp and we chose to use headers mode.
192 	 */
193 	port = a->port;
194 	ip = a->ip;
195 	/*
196 	 * we're accepting any src port or it's from the port we want, and
197 	 * it's from the ip we want or we sent to a broadcast address, and
198 	 * it's for us or it's a broadcast.
199 	 */
200 	return (port == 0 || nhgets(h->rport) == port) &&
201 		(equivip6(h->raddr, ip) || equivip6(ip, IPv4bcast)) &&
202 		(equivip6(h->laddr, myaddr.ip) || equivip6(h->laddr, IPv4bcast));
203 }
204 
205 /* extract UDP payload into data and set a */
206 static int
udppayload(Udphdr * h,int len,Pxenetaddr * a,uchar * data,int dlen)207 udppayload(Udphdr *h, int len, Pxenetaddr *a, uchar *data, int dlen)
208 {
209 	if(Debug)
210 		print("udprecv %I!%d to %I!%d...\n",
211 			h->raddr, nhgets(h->rport), h->laddr, nhgets(h->lport));
212 
213 	if(a->port != 0 && nhgets(h->rport) != a->port) {
214 		if(Debug)
215 			print("udpport %ux not %ux\n", nhgets(h->rport), a->port);
216 		return -1;
217 	}
218 
219 	if(!equivip6(a->ip, IPv4bcast) && !equivip6(a->ip, h->raddr)) {
220 		if(Debug)
221 			print("bad ip %I not %I\n", h->raddr, a->ip);
222 		return -1;
223 	}
224 
225 	len -= sizeof *h;		/* don't count pseudo-headers */
226 	if(len > dlen) {
227 		print("udp packet too big: %d > %d; from addr %I\n",
228 			len, dlen, h->raddr);
229 		return -1;
230 	}
231 	memmove(data, h + 1, len);	/* skip pseudo-headers */
232 
233 	/* set a from remote address */
234 	ipmove(a->ip, h->raddr);
235 	a->port = nhgets(h->rport);
236 	return len;
237 }
238 
239 static int
chanlen(Chan * ch)240 chanlen(Chan *ch)
241 {
242 	int len;
243 	Dir *dp;
244 
245 	dp = dirchstat(ch);
246 	if (dp == nil)
247 		return -1;
248 	len = dp->length;		/* qlen(cv->rq) in devip */
249 	free(dp);
250 	return len;
251 }
252 
253 static int
udprecv(Openeth * oe,Pxenetaddr * a,void * data,int dlen)254 udprecv(Openeth *oe, Pxenetaddr *a, void *data, int dlen)
255 {
256 	int len, buflen, chlen;
257 	ulong timo, now;
258 	char *buf;
259 	Chan *c;
260 	Etherpkt pkt;
261 
262 	oe->netaddr = a;
263 	/* timo is frequency of tftp ack and broadcast bootp retransmission */
264 	if(oe->rxactive == 0)
265 		timo = 1000;
266 	else
267 		timo = Timeout;
268 	now = TK2MS(m->ticks);
269 	timo += now;			/* deadline */
270 
271 	c = oe->udpdata;
272 	spllo();			/* paranoia */
273 	do {
274 		/*
275 		 * wait for data to arrive or time-out.
276 		 * alarms only work for user procs, so we poll to avoid getting
277 		 * stuck in ipread.
278 		 */
279 		for (chlen = chanlen(c); chlen == 0 && now < timo;
280 		     chlen = chanlen(c)) {
281 			/* briefly give somebody else a chance to run */
282 			tsleep(&up->sleep, return0, 0, 0);
283 			now = TK2MS(m->ticks);
284 		}
285 		if (chlen <= 0) {
286 			print("T");
287 			return -1;		/* timed out */
288 		}
289 
290 		while (waserror()) {
291 			print("read err: %s\n", up->errstr);
292 			tsleep(&up->sleep, return0, 0, 1000);
293 		}
294 
295 		/*
296 		 * using Plan 9 UDP pseudo-headers?
297 		 */
298 		if (tftpphase && !Tftpusehdrs) {
299 			buf = data;	/* read directly in caller's buffer */
300 			buflen = dlen;
301 		} else {
302 			buf = (char *)&pkt;  /* read pkt with hdrs */
303 			buflen = sizeof pkt;
304 		}
305 		/* devtab[c->type]->read calls ipread */
306 		len = devtab[c->type]->read(c, buf, buflen, c->offset);
307 		poperror();
308 
309 		if (len <= 0)
310 			return len;
311 		c->offset += len;
312 	} while (!tuplematch(oe->netaddr, (Udphdr *)buf));
313 
314 	/*
315 	 * using Plan 9 UDP pseudo-headers? extract payload into caller's buf.
316 	 */
317 	if (!tftpphase || Tftpusehdrs)
318 		len = udppayload((Udphdr *)&pkt, len, a, data, dlen);
319 	if (len >= 0)
320 		oe->rxactive = 1;
321 	return len;
322 }
323 
324 static void
ack(Openeth * oe,Pxenetaddr * a,int blkno)325 ack(Openeth *oe, Pxenetaddr *a, int blkno)
326 {
327 	char buf[4];
328 
329 	buf[0] = 0;
330 	buf[1] = Tftp_ACK;
331 	buf[2] = blkno>>8;
332 	buf[3] = blkno;
333 	udpsend(oe, a, buf, sizeof buf);
334 }
335 
336 static char *
skipwd(char * wd)337 skipwd(char *wd)
338 {
339 	while (*wd != '\0')
340 		wd++;
341 	return wd + 1;		/* skip terminating NUL */
342 }
343 
344 static int
optval(char * opt,char * pkt,int len)345 optval(char *opt, char *pkt, int len)
346 {
347 	char *wd, *ep, *p;
348 
349 	ep = pkt + len;
350 	for (p = pkt; p < ep && *p != '\0'; p = skipwd(wd)) {
351 		wd = skipwd(p);
352 		if (cistrcmp(p, opt) == 0)
353 			return strtol(wd, 0, 10);
354 	}
355 	return -1;
356 }
357 
358 /*
359  * send a tftp read request to `a' for name.  if we get a data packet back,
360  * ack it and stash it in tftp for later.
361  *
362  * format of a request packet, from the RFC:
363  *
364  *          2 bytes     string    1 byte     string   1 byte
365  *          ------------------------------------------------
366  *         | Opcode |  Filename  |   0  |    Mode    |   0  |
367  *          ------------------------------------------------
368  */
369 static int
tftpread1st(Openeth * oe,Pxenetaddr * a,char * name,Tftp * tftp)370 tftpread1st(Openeth *oe, Pxenetaddr *a, char *name, Tftp *tftp)
371 {
372 	int i, n, len, rlen, oport, sendack;
373 	static char *buf;
374 
375 	if (buf == nil)
376 		buf = malloc(Bufsz);
377 	buf[0] = 0;
378 	buf[1] = Tftp_READ;
379 	len = 2 + snprint(buf+2, Bufsz - 2, "%s", name) + 1;
380 	len += snprint(buf+len, Bufsz - len, "octet") + 1;
381 	len += snprint(buf+len, Bufsz - len, "blksize") + 1; /* option */
382 	len += snprint(buf+len, Bufsz - len, "%d", Prefsegsize) + 1;
383 
384 	/*
385 	 * keep sending the same packet until we get an answer.
386 	 */
387 	if (Debug)
388 		print("tftpread1st %s\n", name);
389 	oe->netaddr = a;
390 	/*
391 	 * the first packet or two sent seem to get dropped,
392 	 * so use a shorter time-out on the first packet.
393 	 */
394 	oe->rxactive = 0;
395 	oport = a->port;
396 	tftpblockno = 0;
397 	segsize = Defsegsize;
398 	sendack = 0;
399 	for(i = 0; i < 10; i++){
400 		a->port = oport;
401 		if (sendack)
402 			ack(oe, a, tftpblockno);
403 		else
404 			udpsend(oe, a, buf, len);	/* tftp read name */
405 
406 		if((rlen = udprecv(oe, a, tftp, sizeof(Tftp))) < Tftphdrsz)
407 			continue;		/* runt or time-out */
408 
409 		switch((tftp->header[0]<<8)|tftp->header[1]){
410 
411 		case Tftp_ERROR:
412 			if(strstr((char *)tftp->data, "does not exist") != nil){
413 				print("%s\n", (char*)tftp->data);
414 				return Nonexist;
415 			}
416 			print("tftpread1st: error (%d): %s\n",
417 				(tftp->header[2]<<8)|tftp->header[3], (char*)tftp->data);
418 			return Err;
419 
420 		case Tftp_OACK:
421 			n = optval("blksize", (char *)tftp->header+2, rlen-2);
422 			if (n <= 0) {
423 				nak(oe, a, 0, "bad blksize option value", 0);
424 				return Err;
425 			}
426 			segsize = n;
427 			/* no bytes stashed in tftp.data */
428 			i = 0;
429 			sendack = 1;
430 			break;
431 
432 		case Tftp_DATA:
433 			tftpblockno = 1;
434 			len = (tftp->header[2]<<8)|tftp->header[3];
435 			if(len != tftpblockno){
436 				print("tftpread1st: block error: %d\n", len);
437 				nak(oe, a, 1, "block error", 0);
438 				return Err;
439 			}
440 			rlen -= Tftphdrsz;
441 			if(rlen < segsize)
442 				/* ACK now, in case we don't later */
443 				ack(oe, a, tftpblockno);
444 			return rlen;
445 
446 		default:
447 			print("tftpread1st: unexpected pkt type recv'd\n");
448 			nak(oe, a, 0, "unexpected pkt type recv'd", 0);
449 			return Err;
450 		}
451 	}
452 
453 	print("tftpread1st: failed to connect to server (%I!%d)\n", a->ip, oport);
454 	return Err;
455 }
456 
457 static int
tftpread(Openeth * oe,Pxenetaddr * a,Tftp * tftp,int dlen)458 tftpread(Openeth *oe, Pxenetaddr *a, Tftp *tftp, int dlen)
459 {
460 	int try, blockno, len;
461 
462 	dlen += Tftphdrsz;
463 
464 	/*
465 	 * keep sending ACKs until we get an answer.
466 	 */
467 	for(try = 0; try < 10; try++) {
468 		ack(oe, a, tftpblockno);
469 
470 		len = udprecv(oe, a, tftp, dlen);
471 		/*
472 		 * NB: not `<='; just a header is legal and happens when
473 		 * file being read is a multiple of segsize bytes long.
474 		 */
475 		if(len < Tftphdrsz){
476 			if(Debug)
477 				print("tftpread: too short %d <= %d\n",
478 					len, Tftphdrsz);
479 			continue;
480 		}
481 		switch((tftp->header[0]<<8)|tftp->header[1]){
482 		case Tftp_ERROR:
483 			print("tftpread: error (blk %d): %s\n",
484 				(tftp->header[2]<<8)|tftp->header[3],
485 				(char*)tftp->data);
486 			nak(oe, a, 0, "error pkt recv'd", 0);
487 			return -1;
488 		case Tftp_OACK:
489 			print("tftpread: oack pkt recv'd too late\n");
490 			nak(oe, a, 0, "oack pkt recv'd too late", 0);
491 			return -1;
492 		default:
493 			print("tftpread: unexpected pkt type recv'd\n");
494 			nak(oe, a, 0, "unexpected pkt type recv'd", 0);
495 			return -1;
496 		case Tftp_DATA:
497 			break;
498 		}
499 		blockno = (tftp->header[2]<<8)|tftp->header[3];
500 		if(blockno <= tftpblockno){
501 			if(Debug)
502 				print("tftpread: blkno %d <= %d\n",
503 					blockno, tftpblockno);
504 			continue;
505 		}
506 
507 		if(blockno == tftpblockno+1) {
508 			tftpblockno++;
509 			if(len < dlen)	/* last packet? send final ack */
510 				ack(oe, a, tftpblockno);
511 			return len-Tftphdrsz;
512 		}
513 		print("tftpread: block error: %d, expected %d\n",
514 			blockno, tftpblockno+1);
515 	}
516 
517 	return -1;
518 }
519 
520 /*
521  * broadcast a bootp request for file.  stash any answer in rep.
522  */
523 static int
bootpbcast(Openeth * oe,char * file,Bootp * rep)524 bootpbcast(Openeth *oe, char *file, Bootp *rep)
525 {
526 	Bootp req;
527 	int i;
528 	uchar *ea;
529 	char name[128], *filename, *sysname;
530 	static char zeroes[IPaddrlen];
531 
532 	oe->filename[0] = '\0';
533 	if (Debug)
534 		if (file == nil)
535 			print("bootpopen: %s...", oe->ethname);
536 		else
537 			print("bootpopen: %s!%s...", oe->ethname, file);
538 	if((ea = etheraddr(oe)) == nil){
539 		print("bad ether %s\n", oe->ethname);
540 		return -1;
541 	}
542 
543 	filename = nil;
544 	sysname = 0;
545 	if(file && *file){
546 		strncpy(name, file, sizeof name);
547 		if(filename = strchr(name, '!')){
548 			sysname = name;
549 			*filename++ = 0;
550 		}
551 		else
552 			filename = name;
553 	}
554 
555 	/*
556 	 * form a bootp request packet
557 	 */
558 	memset(&req, 0, sizeof(req));
559 	req.op = Bootrequest;
560 	req.htype = 1;			/* ethernet */
561 	req.hlen = Eaddrlen;		/* ethernet */
562 	memmove(req.chaddr, ea, Eaddrlen);
563 	req.flags[0] = 0x80;		/* request broadcast reply */
564 	if(filename != nil) {
565 		strncpy(req.file, filename, sizeof(req.file));
566 		strncpy(oe->filename, filename, sizeof oe->filename);
567 	}
568 	if(sysname != nil)		/* if server name given, supply it */
569 		strncpy(req.sname, sysname, sizeof(req.sname));
570 
571 	if (memcmp(myaddr.ip, zeroes, sizeof myaddr.ip) == 0)
572 		ipmove(myaddr.ip, IPv4bcast);	/* didn't know my ip yet */
573 	myaddr.port = BPportsrc;
574 	memmove(myea, ea, Eaddrlen);
575 
576 	/* send to 255.255.255.255!67 */
577 	ipmove(bootpserv.ip, IPv4bcast);
578 	bootpserv.port = BPportdst;
579 
580 	/*
581 	 * send it until we get a matching answer
582 	 */
583 	memset(rep, 0, sizeof *rep);
584 	for(i = 10; i > 0; i--) {
585 		req.xid[0] = i;			/* try different xids */
586 		udpsend(oe, &bootpserv, &req, sizeof(req));
587 
588 		if(udprecv(oe, &bootpserv, rep, sizeof(*rep)) <= 0)
589 			continue;
590 		if(memcmp(req.chaddr, rep->chaddr, Eaddrlen) != 0)
591 			continue;
592 		if(rep->htype != 1 || rep->hlen != Eaddrlen)
593 			continue;
594 		if(sysname == 0 || strcmp(sysname, rep->sname) == 0)
595 			break;
596 	}
597 	if(i <= 0) {
598 		if (file == nil)
599 			print("bootp on %s timed out\n", oe->ethname);
600 		else
601 			print("bootp on %s for %s timed out\n", oe->ethname, file);
602 		return -1;
603 	}
604 	return 0;
605 }
606 
607 /*
608  * request file via tftp from server named in rep.
609  * initial data packet will be stashed in tftpb.
610  */
611 static int
tftpopen(Openeth * oe,char * file,Bootp * rep)612 tftpopen(Openeth *oe, char *file, Bootp *rep)
613 {
614 	char *filename;
615 	char buf[128];
616 	static uchar ipv4noaddr[IPv4addrlen];
617 
618 	if (tftpconnect(oe, rep) < 0)
619 		return Err;
620 
621 	/*
622 	 * read file from tftp server in bootp answer
623 	 */
624 	filename = oe->filename;
625 	if (file)
626 		filename = file;
627 	if(filename == 0 || *filename == 0){
628 		if(strcmp(rep->file, "/386/9boot") == 0 ||
629 		   strcmp(rep->file, "/386/9pxeload") == 0) {
630 			print("won't load another boot loader (%s)\n", rep->file);
631 			return -1;		/* avoid infinite loop */
632 		}
633 		filename = rep->file;
634 	}
635 
636 	print("\n");
637 	if(rep->sname[0] != '\0')
638 		print("%s ", rep->sname);
639 
640 	v4tov6(myaddr.ip, rep->yiaddr);
641 	myaddr.port = tftpport;
642 	if (equivip4(rep->siaddr, ipv4noaddr)) { /* no server address? */
643 		getstr("tftp server IP address", buf, sizeof buf, TFTPDEF, 0);
644 		v4parseip(rep->siaddr, buf);
645 	}
646 	v4tov6(tftpserv.ip, rep->siaddr);
647 	tftpserv.port = TFTPport;
648 	if (tftpb == nil)
649 		tftpb = malloc(sizeof *tftpb);
650 
651 	print("(%V!%d): %s ", rep->siaddr, tftpserv.port, filename);
652 
653 	return tftpread1st(oe, &tftpserv, filename, tftpb);
654 }
655 
656 /* load the kernel in file via tftp on oe */
657 int
tftpboot(Openeth * oe,char * file,Bootp * rep,Boot * b)658 tftpboot(Openeth *oe, char *file, Bootp *rep, Boot *b)
659 {
660 	int n;
661 
662 	/* file must exist, else it's an error */
663 	if((n = tftpopen(oe, file, rep)) < 0)
664 		return n;
665 
666 	progress = 0;			/* no more dots; we're on a roll now */
667 	print(" ");			/* after "sys (ip!port): kernel ..." */
668 	while(bootpass(b, tftpb->data, n) == MORE){
669 		n = tftpread(oe, &tftpserv, tftpb, segsize);
670 		if(n < segsize)
671 			break;
672 	}
673 	if(0 < n && n < segsize)	/* got to end of file */
674 		bootpass(b, tftpb->data, n);
675 	else
676 		nak(oe, &tftpserv, 3, "ok", 0);	/* tftpclose to abort transfer */
677 	bootpass(b, nil, 0);	/* boot if possible */
678 	return Err;
679 }
680 
681 /* leave the channel to /net/ipifc/clone open */
682 static int
binddevip(Openeth * oe)683 binddevip(Openeth *oe)
684 {
685 	Chan *icc;
686 	char buf[32];
687 
688 	if (waserror()) {
689 		print("binddevip: can't bind ether %s: %s\n",
690 			oe->netethname, up->errstr);
691 		nexterror();
692 	}
693 	/* get a new ip interface */
694 	oe->ifcctl = icc = namecopen("/net/ipifc/clone", ORDWR);
695 	if(icc == nil)
696 		error("can't open /net/ipifc/clone");
697 
698 	/*
699 	 * specify medium as ethernet, bind the interface to it.
700 	 * this should trigger chandial of types 0x800, 0x806 and 0x86dd.
701 	 */
702 	snprint(buf, sizeof buf, "bind ether %s", oe->netethname);
703 	devtab[icc->type]->write(icc, buf, strlen(buf), 0);  /* bind ether %s */
704 	poperror();
705 	return 0;
706 }
707 
708 /* set the default route */
709 static int
adddefroute(char *,uchar * gaddr)710 adddefroute(char *, uchar *gaddr)
711 {
712 	char buf[64];
713 	Chan *rc;
714 
715 	rc = nil;
716 	if (waserror()) {
717 		if (rc)
718 			cclose(rc);
719 		return -1;
720 	}
721 	rc = enamecopen("/net/iproute", ORDWR);
722 
723 	if(isv4(gaddr))
724 		snprint(buf, sizeof buf, "add 0 0 %I", gaddr);
725 	else
726 		snprint(buf, sizeof buf, "add :: /0 %I", gaddr);
727 	devtab[rc->type]->write(rc, buf, strlen(buf), 0);
728 	poperror();
729 	cclose(rc);
730 	return 0;
731 }
732 
733 static int
validip(uchar * ip)734 validip(uchar *ip)
735 {
736 	return ipcmp(ip, IPnoaddr) != 0 && ipcmp(ip, v4prefix) != 0;
737 }
738 
739 static int
openetherdev(Openeth * oe)740 openetherdev(Openeth *oe)
741 {
742 	int n;
743 	char num[16];
744 	Chan *c;
745 	static char promisc[] = "promiscuous";
746 
747 	if (chdir(oe->netethname) < 0)
748 		return -1;			/* out of ethers */
749 
750 	oe->ethctl = nil;
751 	if (waserror()) {
752 		print("error opening /net/ether%d/0/ctl: %s\n",
753 			oe->ctlrno, up->errstr);
754 		if (oe->ethctl) {
755 			cclose(oe->ethctl);
756 			oe->ethctl = nil;
757 		}
758 		chdir("/");			/* don't hold conv. open */
759 		return -1;
760 	}
761 	oe->ethctl = c = namecopen("0/ctl", ORDWR);	/* should be ipv4 */
762 	if (c == nil) {
763 		/* read clone file to make conversation 0 since not present */
764 		oe->ethctl = c = enamecopen("clone", ORDWR);
765 		n = devtab[c->type]->read(c, num, sizeof num - 1, 0);
766 		if (n < 0)
767 			print("no %s/clone: %s\n", oe->netethname, up->errstr);
768 		else {
769 			num[n] = 0;
770 			print("%s/clone returned %s\n", oe->netethname, num);
771 		}
772 	}
773 	/* shouldn't be needed to read bootp (broadcast) reply */
774 	devtab[c->type]->write(c, promisc, sizeof promisc-1, 0);
775 	poperror();
776 	chdir("/");
777 	/* leave oe->ethctl open to keep promiscuous mode on */
778 	return 0;
779 }
780 
781 /* add a logical interface to the ip stack */
782 int
minip4cfg(Openeth * oe)783 minip4cfg(Openeth *oe)
784 {
785 	int n;
786 	char buf[64];
787 
788 	n = snprint(buf, sizeof buf, "add %I", IPnoaddr);
789 	devtab[oe->ifcctl->type]->write(oe->ifcctl, buf, n, 0);	/* add %I */
790 
791 	openetherdev(oe);
792 	return 0;
793 }
794 
795 /* remove the :: address added by minip4cfg */
796 int
unminip4cfg(Openeth * oe)797 unminip4cfg(Openeth *oe)
798 {
799 	int n;
800 	char buf[64];
801 
802 	n = snprint(buf, sizeof buf, "remove %I /128", IPnoaddr);
803 	if (waserror()) {
804 		print("failed write to ifc: %s: %s\n", buf, up->errstr);
805 		return -1;
806 	}
807 	devtab[oe->ifcctl->type]->write(oe->ifcctl, buf, n, 0);	/* remove %I */
808 	cclose(oe->ethctl);		/* turn promiscuous mode off */
809 	oe->ethctl = nil;
810 	poperror();
811 	return 0;
812 }
813 
814 /*
815  * parse p, looking for option `op'.  if non-nil, np points to minimum length.
816  * return nil if option is too small, else ptr to opt, and
817  * store actual length via np if non-nil.
818  */
819 uchar*
optget(uchar * p,int op,int * np)820 optget(uchar *p, int op, int *np)
821 {
822 	int len, code;
823 
824 	while ((code = *p++) != OBend) {
825 		if(code == OBpad)
826 			continue;
827 		len = *p++;
828 		if(code != op) {
829 			p += len;
830 			continue;
831 		}
832 		if(np != nil){
833 			if(*np > len)
834 				return 0;
835 			*np = len;
836 		}
837 		return p;
838 	}
839 	return 0;
840 }
841 
842 int
optgetaddr(uchar * p,int op,uchar * ip)843 optgetaddr(uchar *p, int op, uchar *ip)
844 {
845 	int len;
846 
847 	len = 4;
848 	p = optget(p, op, &len);
849 	if(p == nil)
850 		return 0;
851 	v4tov6(ip, p);
852 	return 1;
853 }
854 
855 int beprimary = 1;
856 
857 /* add a logical interface to the ip stack */
858 int
ip4cfg(Openeth * oe,Bootp * rep)859 ip4cfg(Openeth *oe, Bootp *rep)
860 {
861 	int n;
862 	uchar gaddr[IPaddrlen], v6mask[IPaddrlen];
863 	uchar v4mask[IPv4addrlen];
864 	char buf[64];
865 	static uchar zeroes[4];
866 
867 	v4tov6(gaddr, rep->yiaddr);
868 	if(!validip(gaddr))
869 		return -1;
870 
871 	/* dig subnet mask, if any, out of options.  if none, guess. */
872 	if(optgetaddr(rep->optdata, OBmask, v6mask)) {
873 		v6tov4(v4mask, v6mask);
874 		n = snprint(buf, sizeof buf, "add %V %M", rep->yiaddr, v4mask);
875 	} else
876 		n = snprint(buf, sizeof buf, "add %V 255.255.255.0", rep->yiaddr);
877 
878 	devtab[oe->ifcctl->type]->write(oe->ifcctl, buf, n, 0);
879 
880 	v4tov6(gaddr, rep->giaddr);
881 	if(beprimary==1 && validip(gaddr) && !equivip4(rep->giaddr, zeroes))
882 		adddefroute("/net", gaddr);
883 	return 0;
884 }
885 
886 static int
openudp(Openeth * oe)887 openudp(Openeth *oe)
888 {
889 	int n;
890 	char buf[16];
891 	Chan *cc;
892 
893 	/* read clone file for conversation number */
894 	if (waserror())
895 		panic("openudp: can't open /net/udp/clone");
896 	cc = enamecopen("/net/udp/clone", ORDWR);
897 	oe->udpctl = cc;
898 	n = devtab[cc->type]->read(cc, buf, sizeof buf - 1, 0);
899 	poperror();
900 	buf[n] = '\0';
901 	return atoi(buf);
902 }
903 
904 static void
initbind(Openeth * oe)905 initbind(Openeth *oe)
906 {
907 	char buf[8];
908 
909 	if (waserror()) {
910 		print("error while binding: %s\n", up->errstr);
911 		return;
912 	}
913 	snprint(buf, sizeof buf, "#I%d", oe->ctlrno);
914 	bind(buf, "/net", MAFTER);
915 	snprint(buf, sizeof buf, "#l%d", oe->ctlrno);
916 	bind(buf, "/net", MAFTER);
917 	binddevip(oe);
918 	poperror();
919 }
920 
921 static void
closeudp(Openeth * oe)922 closeudp(Openeth *oe)
923 {
924 	if (oe->udpctl) {
925 		cclose(oe->udpctl);
926 		oe->udpctl = nil;
927 	}
928 	if (oe->udpdata) {
929 		cclose(oe->udpdata);
930 		oe->udpdata = nil;
931 	}
932 }
933 
934 static int
announce(Openeth * oe,char * port)935 announce(Openeth *oe, char *port)
936 {
937 	int udpconv;
938 	char buf[32];
939 	static char hdrs[] = "headers";
940 
941 	while (waserror()) {
942 		print("can't announce udp!*!%s: %s\n", port, up->errstr);
943 		closeudp(oe);
944 		nexterror();
945 	}
946 	udpconv = openudp(oe);
947 	if (udpconv < 0)
948 		panic("can't open udp conversation: %s", up->errstr);
949 
950 	/* headers is only effective after a udp announce */
951 	snprint(buf, sizeof buf, "announce %s", port);
952 	devtab[oe->udpctl->type]->write(oe->udpctl, buf, strlen(buf), 0);
953 	devtab[oe->udpctl->type]->write(oe->udpctl, hdrs, sizeof hdrs - 1, 0);
954 	poperror();
955 
956 	/* now okay to open the data file */
957 	snprint(buf, sizeof buf, "/net/udp/%d/data", udpconv);
958 	/*
959 	 * we must use create, not open, to get Conv->rq and ->wq
960 	 * allocated by udpcreate.
961 	 */
962 	oe->udpdata = enameccreate(buf, ORDWR);
963 	cclose(oe->udpctl);
964 	oe->udpctl = nil;
965 	return udpconv;
966 }
967 
968 static long
tftprdfile(Openeth * oe,int openread,void * va,long len)969 tftprdfile(Openeth *oe, int openread, void* va, long len)
970 {
971 	int n;
972 	char *p, *v;
973 
974 	n = openread;	/* have read this many bytes already into tftpb->data */
975 	p = v = va;
976 	len--;				/* leave room for NUL */
977 	while(n > 0) {
978 		if((p-v)+n > len)
979 			n = len - (p-v);
980 		memmove(p, tftpb->data, n);
981 		p += n;
982 		*p = 0;
983 		if(n != segsize)
984 			break;
985 
986 		if((n = tftpread(oe, &tftpserv, tftpb, segsize)) < 0)
987 			return n;
988 	}
989 	return p-v;
990 }
991 
992 static int
tftpconnect(Openeth * oe,Bootp * rep)993 tftpconnect(Openeth *oe, Bootp *rep)
994 {
995 	char num[16], dialstr[64];
996 
997 	if (waserror()) {
998 		print("can't dial: %s\n", up->errstr);
999 		return -1;
1000 	}
1001 	closeudp(oe);
1002 
1003 	tftpphase = 1;
1004 	tftpport = 5000 + nrand(20480);
1005 	snprint(num, sizeof num, "%d", tftpport);
1006 	if (Tftpusehdrs)
1007 		announce(oe, num);
1008 	else {
1009 		snprint(dialstr, sizeof dialstr, "/net/udp!%V!%d",
1010 			rep->siaddr, TFTPport);
1011 		oe->udpdata = chandial(dialstr, num, nil, nil);
1012 		oe->udpctl = nil;
1013 	}
1014 	poperror();
1015 	return 0;
1016 }
1017 
1018 static int
setipcfg(Openeth * oe,Bootp * rep)1019 setipcfg(Openeth *oe, Bootp *rep)
1020 {
1021 	int r;
1022 
1023 	tftpphase = 0;
1024 	progress = 1;
1025 
1026 	/* /net/iproute is unpopulated here; add at least broadcast */
1027 	minip4cfg(oe);
1028 	announce(oe, "68");
1029 	r = bootpbcast(oe, nil, rep);
1030 	closeudp(oe);
1031 	unminip4cfg(oe);
1032 	if(r < 0)
1033 		return -1;
1034 
1035 	ip4cfg(oe, rep);
1036 	if (Debug)
1037 		print("got & set ip config\n");
1038 	return 0;
1039 }
1040 
1041 /*
1042  * use bootp answer (rep) to open cfgpxe.
1043  * reads first pkt of cfgpxe into tftpb->data.
1044  */
1045 static int
rdcfgpxe(Openeth * oe,Bootp * rep,char * cfgpxe)1046 rdcfgpxe(Openeth *oe, Bootp *rep, char *cfgpxe)
1047 {
1048 	int n;
1049 	char *ini;
1050 
1051 	/* cfgpxe is optional */
1052 	n = tftpopen(oe, cfgpxe, rep);
1053 	if (n < 0)
1054 		return n;
1055 	if (Debug)
1056 		print("\opened %s\n", cfgpxe);
1057 
1058 	ini = smalloc(2*BOOTARGSLEN);
1059 	/* starts by copying data from tftpb->data into ini */
1060 	n = tftprdfile(oe, n, ini, 2*BOOTARGSLEN);
1061 	if (n < 0) {
1062 		print("error reading %s\n", cfgpxe);
1063 		free(ini);
1064 		return n;
1065 	}
1066 	print(" read %d bytes", n);
1067 
1068 	/*
1069 	 * take note of plan9.ini contents.  consumes ini to make config vars,
1070 	 * thus we can't free ini.
1071 	 */
1072 	dotini(ini);
1073 	return Ok;
1074 }
1075 
1076 /*
1077  * break kp->bootfile into kp->edev & kp->bootfile,
1078  * copy any args for new kernel to low memory.
1079  */
1080 static int
parsebootfile(Kernname * kp)1081 parsebootfile(Kernname *kp)
1082 {
1083 	char *p;
1084 
1085 	p = strchr(kp->bootfile, '!');
1086 	if (p != nil) {
1087 		*p++ = '\0';
1088 		kp->edev = kp->bootfile;
1089 		kp->bootfile = nil;
1090 		kstrdup(&kp->bootfile, p);
1091 		if (strncmp(kp->edev, ethernm, sizeof ethernm - 1) != 0) {
1092 			print("bad ether device %s\n", kp->edev);
1093 			return Err;
1094 		}
1095 	}
1096 
1097 	/* pass any arguments to kernels that expect them */
1098 	strecpy(BOOTLINE, BOOTLINE+BOOTLINELEN, kp->bootfile);
1099 	p = strchr(kp->bootfile, ' ');
1100 	if(p != nil)
1101 		*p = '\0';
1102 	return Ok;
1103 }
1104 
1105 static int
getkernname(Openeth * oe,Bootp * rep,Kernname * kp)1106 getkernname(Openeth *oe, Bootp *rep, Kernname *kp)
1107 {
1108 	int n;
1109 	char *p;
1110 	char cfgpxe[32], buf[64];
1111 
1112 	if (kp->bootfile) {
1113 		/* i think returning here is a bad idea */
1114 		// print("getkernname: already have bootfile %s\n",
1115 		//	kp->bootfile);
1116 		free(kp->bootfile);
1117 		// return Ok;
1118 	}
1119 	kp->edev = kp->bootfile = nil;
1120 	i8250console();		/* configure serial port with defaults */
1121 
1122 	/* use our mac address instead of relying on a bootp answer. */
1123 	snprint(cfgpxe, sizeof cfgpxe, "/cfg/pxe/%E", myea);
1124 	n = rdcfgpxe(oe, rep, cfgpxe);
1125 	switch (n) {
1126 	case Ok:
1127 		p = getconf("bootfile");
1128 		if (p)
1129 			kstrdup(&kp->bootfile, p);
1130 		if (kp->bootfile == nil)
1131 			askbootfile(buf, sizeof buf, &kp->bootfile, Promptsecs,
1132 				"ether0!/386/9pccpu");
1133 		if (strcmp(kp->bootfile, "manual") == 0)
1134 			askbootfile(buf, sizeof buf, &kp->bootfile, 0, "");
1135 		break;
1136 	case Err:
1137 		print("\nfailed.\n");
1138 		return n;
1139 	case Nonexist:
1140 		askbootfile(buf, sizeof buf, &kp->bootfile, 0, "");
1141 		break;
1142 	}
1143 	return parsebootfile(kp);
1144 }
1145 
1146 static void
unbinddevip(Openeth * oe)1147 unbinddevip(Openeth *oe)
1148 {
1149 	Chan *icc;
1150 	static char unbind[] = "unbind";
1151 
1152 	icc = oe->ifcctl;
1153 	if (icc) {
1154 		devtab[icc->type]->write(icc, unbind, sizeof unbind - 1, 0);
1155 		cclose(icc);
1156 		oe->ifcctl = nil;
1157 	}
1158 }
1159 
1160 /*
1161  * phase 1: get our ip (v4) configuration via bootp, set new ip configuration.
1162  * phase 2: load /cfg/pxe, parse it, extract kernel filename.
1163  * phase 3: load kernel and jump to it.
1164  */
1165 static int
tftpload(Openeth * oe,Kernname * kp)1166 tftpload(Openeth *oe, Kernname *kp)
1167 {
1168 	int r, n;
1169 	char buf[64];
1170 	Bootp rep;
1171 	Boot boot;
1172 
1173 	r = -1;
1174 	if(waserror()) {
1175 		print("tftpload: %s\n", up->errstr);
1176 		closeudp(oe);
1177 		unbinddevip(oe);
1178 		return r;
1179 	}
1180 
1181 	memset(&rep, 0, sizeof rep);
1182 	if (setipcfg(oe, &rep) < 0)
1183 		error("can't set ip config");
1184 
1185 	n = getkernname(oe, &rep, kp);
1186 	if (n < 0) {
1187 		r = n;			/* pass reason back to caller */
1188 		USED(r);
1189 		nexterror();
1190 	}
1191 	do {
1192 		if (kp->edev &&
1193 		    oe->ctlrno != strtol(kp->edev + sizeof ethernm - 1, 0, 10)){
1194 			/* user specified an ether & it's not this one; next! */
1195 			r = Ok;
1196 			USED(r);
1197 			nexterror();
1198 		}
1199 
1200 		memset(&boot, 0, sizeof boot);
1201 		boot.state = INITKERNEL;
1202 		r = tftpboot(oe, kp->bootfile, &rep, &boot);
1203 
1204 		/* we failed or bootfile asked for another ether */
1205 		if (r == Nonexist)
1206 			do {
1207 				askbootfile(buf, sizeof buf, &kp->bootfile, 0, "");
1208 			} while (parsebootfile(kp) != Ok);
1209 	} while (r == Nonexist);
1210 
1211 	poperror();
1212 	closeudp(oe);
1213 	unbinddevip(oe);
1214 	return r;
1215 }
1216 
1217 static int
etherload(int eth,Kernname * kp)1218 etherload(int eth, Kernname *kp)
1219 {
1220 	int r;
1221 	Openeth *oe;
1222 
1223 	print("pxe on ether%d ", eth);
1224 	oe = smalloc(sizeof *oe);
1225 	memset(oe, 0, sizeof *oe);
1226 	oe->ctlrno = eth;
1227 	snprint(oe->ethname, sizeof oe->ethname, "ether%d", oe->ctlrno);
1228 	snprint(oe->netethname, sizeof oe->netethname, "/net/ether%d",
1229 		oe->ctlrno);
1230 	initbind(oe);
1231 
1232 	r = tftpload(oe, kp);
1233 
1234 	/* failed to boot; keep going */
1235 	unmount(nil, "/net");
1236 	return r;
1237 }
1238 
1239 static int
attacheth(int neth)1240 attacheth(int neth)
1241 {
1242 	char num[4];
1243 	Chan *cc;
1244 
1245 	cc = nil;
1246 	if (waserror()) {		/* no more interfaces */
1247 		if (cc)
1248 			cclose(cc);
1249 		return -1;
1250 	}
1251 	snprint(num, sizeof num, "%d", neth);
1252 	cc = etherattach(num);
1253 	if (cc)
1254 		cclose(cc);
1255 	poperror();
1256 	return cc == nil? -1: 0;
1257 }
1258 
1259 void
bootloadproc(void *)1260 bootloadproc(void *)
1261 {
1262 	int eth, neth, needattach;
1263 	Kernname kernnm;
1264 
1265 	srand(TK2MS(m->ticks));			/* for local port numbers */
1266 	nrand(20480);				/* 1st # is always 0; toss it */
1267 	kernnm.edev = kernnm.bootfile = nil;
1268 
1269 	while(waserror()) {
1270 		print("%s\n", up->errstr);
1271 		tsleep(&up->sleep, return0, 0, 30*1000);
1272 	}
1273 	neth = MaxEther;
1274 	needattach = 1;
1275 	for (;;) {
1276 		/* try each interface in turn: first get /cfg/pxe file */
1277 		for (eth = 0; eth < neth && kernnm.edev == nil; eth++) {
1278 			if (needattach && attacheth(eth) < 0)
1279 				break;
1280 			etherload(eth, &kernnm);
1281 		}
1282 		if (needattach) {
1283 			neth = eth;
1284 			needattach = 0;
1285 			if (neth == 0)
1286 				print("no ethernet interfaces found\n");
1287 		}
1288 		if (kernnm.edev != nil) {
1289 			eth = strtol(kernnm.edev + sizeof ethernm - 1, 0, 10);
1290 			etherload(eth, &kernnm);
1291 		}
1292 		/*
1293 		 * couldn't boot on any ether.  don't give up;
1294 		 * perhaps the boot servers are down, so try again later.
1295 		 */
1296 		print("failed to boot via pxe; will try again.\n");
1297 		tsleep(&up->sleep, return0, 0, 15*1000);
1298 	}
1299 }
1300