1308 lines · 28.2 KB · C
RawDownload
/*  * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * 9boot - load next kernel via pxe (bootp, tftp) and start it * * intel says that pxe can only load into the bottom 640K, * and intel's boot agent takes 128K, leaving only 512K for 9boot. * * some of this code is from the old 9load's bootp.c. */#include	"u.h"#include	"../port/lib.h"#include	"mem.h"#include	"dat.h"#include	"fns.h"#include	"io.h"#include	"ureg.h"#include	"pool.h"#include	"../port/netif.h"#include	"etherif.h"#include	"../ip/ip.h"#include	"pxe.h" #define TFTPDEF "135.104.9.6"	/* IP of default tftp server */ enum {	Tftpusehdrs =	0,	/* flag: use announce+headers for tftp? */	Debug =		0, 	Tftphdrsz =	4,	/*	 * this can be bigger than the ether mtu and	 * will work due to ip fragmentation, at least on v4.	 */	Prefsegsize =	1400,	Maxsegsize =	2048,	Bufsz =		Maxsegsize + 2, 	Ok =		0,	Err =		-1,	Nonexist =	-2,}; typedef struct Ethaddr Ethaddr;typedef struct Kernname Kernname;typedef struct Openeth Openeth;typedef struct Tftp Tftp; struct Tftp {	uchar	header[Tftphdrsz];	uchar	data[Maxsegsize];}; struct Kernname {	char	*edev;	char	*bootfile;}; struct Openeth {	/* names */	int	ctlrno;	char	ethname[16];	/* ether%d */	char	netethname[32];	/* /net/ether%d */	char	filename[128];	/* from bootp, for tftp */ 	Chan	*ifcctl;	/* /net/ipifc/clone */	Chan	*ethctl;	/* /net/etherN/0/ctl, for promiscuous mode */ 	/* udp connection */	Chan	*udpctl;	Chan	*udpdata;	Pxenetaddr *netaddr;	int	rxactive;}; struct Ethaddr {		/* communication with sleep procs */	Openeth	*oe;	Pxenetaddr *a;}; static char ethernm[] = "ether";static uchar myea[Eaddrlen];static Pxenetaddr myaddr;		/* actually, local ip addr & port */ /* * there can be at most one concurrent tftp session until we move these * variables into Openeth or some other struct (Tftpstate). */static ushort tftpport;static int tftpblockno;static int tftpphase;static int progress;static int segsize;static Tftp *tftpb;static Pxenetaddr tftpserv;		/* actually, remote ip addr & port */static Pxenetaddr bootpserv; static int	tftpconnect(Openeth *, Bootp *); uchar *etheraddr(Openeth *oe){	int n;	char name[32], buf[32];	static uchar ea[Eaddrlen]; 	memset(ea, 0, sizeof ea);	snprint(name, sizeof name, "#l%d/ether%d/addr", oe->ctlrno, oe->ctlrno);	n = readfile(name, buf, sizeof buf - 1);	if (n < 0)		return ea;	buf[n] = '\0';	parseether(ea, buf);	return ea;} static voidudpsend(Openeth *oe, Pxenetaddr *a, void *data, int dlen){	int n;	uchar *buf;	Chan *c;	Etherpkt pkt;	Udphdr *uh; 	buf = data;	if (dlen > sizeof pkt)		panic("udpsend: packet too big"); 	oe->netaddr = a;	/*	 * add Plan 9 UDP pseudo-headers	 */	if (!tftpphase || Tftpusehdrs) {		memset(&pkt, 0, sizeof pkt);		uh = (Udphdr*)&pkt;		memmove(uh + 1, data, dlen);		USED(buf);		buf = (uchar *)uh;		dlen += sizeof *uh;		if (dlen > sizeof pkt)			panic("udpsend: packet too big"); 		ipmove(uh->laddr, myaddr.ip);		hnputs(uh->lport, myaddr.port);		ipmove(uh->raddr, a->ip);		hnputs(uh->rport, a->port);		if(Debug)			print("udpsend %I!%d -> %I!%d ", uh->laddr,				nhgets(uh->lport), uh->raddr, nhgets(uh->rport));	}	if (waserror()) {		iprint("udp write error\n");		return;			/* send another req later */	}	c = oe->udpdata;	assert(oe->udpdata != nil);	n = devtab[c->type]->write(c, buf, dlen, c->offset);	poperror();	c->offset += n;	if (n != dlen)		print("udpsend: wrote %d/%d\n", n, dlen);	else if (progress)		print(".");} static voidnak(Openeth *oe, Pxenetaddr *a, int code, char *msg, int report){	char buf[4 + 32]; 	buf[0] = 0;	buf[1] = Tftp_ERROR;	buf[2] = 0;	buf[3] = code;	strncpy(buf+4, msg, sizeof buf - 4 - 1);	udpsend(oe, a, buf, 4 + strlen(buf+4) + 1);	if(report)		print("\ntftp: error(%d): %s\n", code, msg);} /* a is the source address we're looking for */static inttuplematch(Pxenetaddr *a, Udphdr *h){	int port;	uchar *ip; 	if (tftpphase && !Tftpusehdrs)		return 1;	/*	 * we're using udp headers mode, because we're still doing bootp,	 * or we are doing tftp and we chose to use headers mode.	 */	port = a->port;	ip = a->ip;	/*	 * we're accepting any src port or it's from the port we want, and	 * it's from the ip we want or we sent to a broadcast address, and	 * it's for us or it's a broadcast.	 */	return (port == 0 || nhgets(h->rport) == port) &&		(equivip6(h->raddr, ip) || equivip6(ip, IPv4bcast)) &&		(equivip6(h->laddr, myaddr.ip) || equivip6(h->laddr, IPv4bcast));} /* extract UDP payload into data and set a */static intudppayload(Udphdr *h, int len, Pxenetaddr *a, uchar *data, int dlen){	if(Debug)		print("udprecv %I!%d to %I!%d...\n",			h->raddr, nhgets(h->rport), h->laddr, nhgets(h->lport)); 	if(a->port != 0 && nhgets(h->rport) != a->port) {		if(Debug)			print("udpport %ux not %ux\n", nhgets(h->rport), a->port);		return -1;	} 	if(!equivip6(a->ip, IPv4bcast) && !equivip6(a->ip, h->raddr)) {		if(Debug)			print("bad ip %I not %I\n", h->raddr, a->ip);		return -1;	} 	len -= sizeof *h;		/* don't count pseudo-headers */	if(len > dlen) {		print("udp packet too big: %d > %d; from addr %I\n",			len, dlen, h->raddr);		return -1;	}	memmove(data, h + 1, len);	/* skip pseudo-headers */ 	/* set a from remote address */	ipmove(a->ip, h->raddr);	a->port = nhgets(h->rport);	return len;} static intchanlen(Chan *ch){	int len;	Dir *dp; 	dp = dirchstat(ch);	if (dp == nil)		return -1;	len = dp->length;		/* qlen(cv->rq) in devip */	free(dp);	return len;} static intudprecv(Openeth *oe, Pxenetaddr *a, void *data, int dlen){	int len, buflen, chlen;	ulong timo, now;	char *buf;	Chan *c;	Etherpkt pkt; 	oe->netaddr = a;	/* timo is frequency of tftp ack and broadcast bootp retransmission */	if(oe->rxactive == 0)		timo = 1000;	else		timo = Timeout;	now = TK2MS(m->ticks);	timo += now;			/* deadline */ 	c = oe->udpdata;	spllo();			/* paranoia */	do {		/*		 * wait for data to arrive or time-out.		 * alarms only work for user procs, so we poll to avoid getting		 * stuck in ipread.		 */		for (chlen = chanlen(c); chlen == 0 && now < timo;		     chlen = chanlen(c)) {			/* briefly give somebody else a chance to run */			tsleep(&up->sleep, return0, 0, 0);			now = TK2MS(m->ticks);		}		if (chlen <= 0) {			print("T");			return -1;		/* timed out */		} 		while (waserror()) {			print("read err: %s\n", up->errstr);			tsleep(&up->sleep, return0, 0, 1000);		} 		/*		 * using Plan 9 UDP pseudo-headers?		 */		if (tftpphase && !Tftpusehdrs) {			buf = data;	/* read directly in caller's buffer */			buflen = dlen;		} else {			buf = (char *)&pkt;  /* read pkt with hdrs */			buflen = sizeof pkt;		}		/* devtab[c->type]->read calls ipread */		len = devtab[c->type]->read(c, buf, buflen, c->offset);		poperror(); 		if (len <= 0)			return len;		c->offset += len;	} while (!tuplematch(oe->netaddr, (Udphdr *)buf)); 	/*	 * using Plan 9 UDP pseudo-headers? extract payload into caller's buf.	 */	if (!tftpphase || Tftpusehdrs)		len = udppayload((Udphdr *)&pkt, len, a, data, dlen);	if (len >= 0)		oe->rxactive = 1;	return len;} static voidack(Openeth *oe, Pxenetaddr *a, int blkno){	char buf[4]; 	buf[0] = 0;	buf[1] = Tftp_ACK;	buf[2] = blkno>>8;	buf[3] = blkno;	udpsend(oe, a, buf, sizeof buf);} static char *skipwd(char *wd){	while (*wd != '\0')		wd++;	return wd + 1;		/* skip terminating NUL */} static intoptval(char *opt, char *pkt, int len){	char *wd, *ep, *p; 	ep = pkt + len;	for (p = pkt; p < ep && *p != '\0'; p = skipwd(wd)) {		wd = skipwd(p);		if (cistrcmp(p, opt) == 0)			return strtol(wd, 0, 10);	}	return -1;} /* * send a tftp read request to `a' for name.  if we get a data packet back, * ack it and stash it in tftp for later. * * format of a request packet, from the RFC: * *          2 bytes     string    1 byte     string   1 byte *          ------------------------------------------------ *         | Opcode |  Filename  |   0  |    Mode    |   0  | *          ------------------------------------------------ */static inttftpread1st(Openeth *oe, Pxenetaddr *a, char *name, Tftp *tftp){	int i, n, len, rlen, oport, sendack;	static char *buf; 	if (buf == nil)		buf = malloc(Bufsz);	buf[0] = 0;	buf[1] = Tftp_READ;	len = 2 + snprint(buf+2, Bufsz - 2, "%s", name) + 1;	len += snprint(buf+len, Bufsz - len, "octet") + 1;	len += snprint(buf+len, Bufsz - len, "blksize") + 1; /* option */	len += snprint(buf+len, Bufsz - len, "%d", Prefsegsize) + 1; 	/*	 * keep sending the same packet until we get an answer.	 */	if (Debug)		print("tftpread1st %s\n", name);	oe->netaddr = a;	/*	 * the first packet or two sent seem to get dropped,	 * so use a shorter time-out on the first packet.	 */	oe->rxactive = 0;	oport = a->port;	tftpblockno = 0;	segsize = Defsegsize;	sendack = 0;	for(i = 0; i < 10; i++){		a->port = oport;		if (sendack)			ack(oe, a, tftpblockno);		else			udpsend(oe, a, buf, len);	/* tftp read name */ 		if((rlen = udprecv(oe, a, tftp, sizeof(Tftp))) < Tftphdrsz)			continue;		/* runt or time-out */ 		switch((tftp->header[0]<<8)|tftp->header[1]){ 		case Tftp_ERROR:			if(strstr((char *)tftp->data, "does not exist") != nil){				print("%s\n", (char*)tftp->data);				return Nonexist;			}			print("tftpread1st: error (%d): %s\n",				(tftp->header[2]<<8)|tftp->header[3], (char*)tftp->data);			return Err; 		case Tftp_OACK:			n = optval("blksize", (char *)tftp->header+2, rlen-2);			if (n <= 0) {				nak(oe, a, 0, "bad blksize option value", 0);				return Err;			}			segsize = n;			/* no bytes stashed in tftp.data */			i = 0;			sendack = 1;			break; 		case Tftp_DATA:			tftpblockno = 1;			len = (tftp->header[2]<<8)|tftp->header[3];			if(len != tftpblockno){				print("tftpread1st: block error: %d\n", len);				nak(oe, a, 1, "block error", 0);				return Err;			}			rlen -= Tftphdrsz;			if(rlen < segsize)				/* ACK now, in case we don't later */				ack(oe, a, tftpblockno);			return rlen; 		default:			print("tftpread1st: unexpected pkt type recv'd\n");			nak(oe, a, 0, "unexpected pkt type recv'd", 0);			return Err;		}	} 	print("tftpread1st: failed to connect to server (%I!%d)\n", a->ip, oport);	return Err;} static inttftpread(Openeth *oe, Pxenetaddr *a, Tftp *tftp, int dlen){	int try, blockno, len; 	dlen += Tftphdrsz; 	/*	 * keep sending ACKs until we get an answer.	 */	for(try = 0; try < 10; try++) {		ack(oe, a, tftpblockno); 		len = udprecv(oe, a, tftp, dlen);		/*		 * NB: not `<='; just a header is legal and happens when		 * file being read is a multiple of segsize bytes long.		 */		if(len < Tftphdrsz){			if(Debug)				print("tftpread: too short %d <= %d\n",					len, Tftphdrsz);			continue;		}		switch((tftp->header[0]<<8)|tftp->header[1]){		case Tftp_ERROR:			print("tftpread: error (blk %d): %s\n",				(tftp->header[2]<<8)|tftp->header[3],				(char*)tftp->data);			nak(oe, a, 0, "error pkt recv'd", 0);			return -1;		case Tftp_OACK:			print("tftpread: oack pkt recv'd too late\n");			nak(oe, a, 0, "oack pkt recv'd too late", 0);			return -1;

Lines 1–500 of 1308.