/* * 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 void udpsend(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 void nak(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 int tuplematch(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 int udppayload(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 int chanlen(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 int udprecv(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 void ack(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 int optval(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 int tftpread1st(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 int tftpread(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; default: print("tftpread: unexpected pkt type recv'd\n"); nak(oe, a, 0, "unexpected pkt type recv'd", 0); return -1; case Tftp_DATA: break; } blockno = (tftp->header[2]<<8)|tftp->header[3]; if(blockno <= tftpblockno){ if(Debug) print("tftpread: blkno %d <= %d\n", blockno, tftpblockno); continue; } if(blockno == tftpblockno+1) { tftpblockno++; if(len < dlen) /* last packet? send final ack */ ack(oe, a, tftpblockno); return len-Tftphdrsz; } print("tftpread: block error: %d, expected %d\n", blockno, tftpblockno+1); } return -1; } /* * broadcast a bootp request for file. stash any answer in rep. */ static int bootpbcast(Openeth *oe, char *file, Bootp *rep) { Bootp req; int i; uchar *ea; char name[128], *filename, *sysname; static char zeroes[IPaddrlen]; oe->filename[0] = '\0'; if (Debug) if (file == nil) print("bootpopen: %s...", oe->ethname); else print("bootpopen: %s!%s...", oe->ethname, file); if((ea = etheraddr(oe)) == nil){ print("bad ether %s\n", oe->ethname); return -1; } filename = nil; sysname = 0; if(file && *file){ strncpy(name, file, sizeof name); if(filename = strchr(name, '!')){ sysname = name; *filename++ = 0; } else filename = name; } /* * form a bootp request packet */ memset(&req, 0, sizeof(req)); req.op = Bootrequest; req.htype = 1; /* ethernet */ req.hlen = Eaddrlen; /* ethernet */ memmove(req.chaddr, ea, Eaddrlen); req.flags[0] = 0x80; /* request broadcast reply */ if(filename != nil) { strncpy(req.file, filename, sizeof(req.file)); strncpy(oe->filename, filename, sizeof oe->filename); } if(sysname != nil) /* if server name given, supply it */ strncpy(req.sname, sysname, sizeof(req.sname)); if (memcmp(myaddr.ip, zeroes, sizeof myaddr.ip) == 0) ipmove(myaddr.ip, IPv4bcast); /* didn't know my ip yet */ myaddr.port = BPportsrc; memmove(myea, ea, Eaddrlen); /* send to 255.255.255.255!67 */ ipmove(bootpserv.ip, IPv4bcast); bootpserv.port = BPportdst; /* * send it until we get a matching answer */ memset(rep, 0, sizeof *rep); for(i = 10; i > 0; i--) { req.xid[0] = i; /* try different xids */ udpsend(oe, &bootpserv, &req, sizeof(req)); if(udprecv(oe, &bootpserv, rep, sizeof(*rep)) <= 0) continue; if(memcmp(req.chaddr, rep->chaddr, Eaddrlen) != 0) continue; if(rep->htype != 1 || rep->hlen != Eaddrlen) continue; if(sysname == 0 || strcmp(sysname, rep->sname) == 0) break; } if(i <= 0) { if (file == nil) print("bootp on %s timed out\n", oe->ethname); else print("bootp on %s for %s timed out\n", oe->ethname, file); return -1; } return 0; } /* * request file via tftp from server named in rep. * initial data packet will be stashed in tftpb. */ static int tftpopen(Openeth *oe, char *file, Bootp *rep) { char *filename; char buf[128]; static uchar ipv4noaddr[IPv4addrlen]; if (tftpconnect(oe, rep) < 0) return Err; /* * read file from tftp server in bootp answer */ filename = oe->filename; if (file) filename = file; if(filename == 0 || *filename == 0){ if(strcmp(rep->file, "/386/9boot") == 0 || strcmp(rep->file, "/386/9pxeload") == 0) { print("won't load another boot loader (%s)\n", rep->file); return -1; /* avoid infinite loop */ } filename = rep->file; } print("\n"); if(rep->sname[0] != '\0') print("%s ", rep->sname); v4tov6(myaddr.ip, rep->yiaddr); myaddr.port = tftpport; if (equivip4(rep->siaddr, ipv4noaddr)) { /* no server address? */ getstr("tftp server IP address", buf, sizeof buf, TFTPDEF, 0); v4parseip(rep->siaddr, buf); } v4tov6(tftpserv.ip, rep->siaddr); tftpserv.port = TFTPport; if (tftpb == nil) tftpb = malloc(sizeof *tftpb); print("(%V!%d): %s ", rep->siaddr, tftpserv.port, filename); return tftpread1st(oe, &tftpserv, filename, tftpb); } /* load the kernel in file via tftp on oe */ int tftpboot(Openeth *oe, char *file, Bootp *rep, Boot *b) { int n; /* file must exist, else it's an error */ if((n = tftpopen(oe, file, rep)) < 0) return n; progress = 0; /* no more dots; we're on a roll now */ print(" "); /* after "sys (ip!port): kernel ..." */ while(bootpass(b, tftpb->data, n) == MORE){ n = tftpread(oe, &tftpserv, tftpb, segsize); if(n < segsize) break; } if(0 < n && n < segsize) /* got to end of file */ bootpass(b, tftpb->data, n); else nak(oe, &tftpserv, 3, "ok", 0); /* tftpclose to abort transfer */ bootpass(b, nil, 0); /* boot if possible */ return Err; } /* leave the channel to /net/ipifc/clone open */ static int binddevip(Openeth *oe) { Chan *icc; char buf[32]; if (waserror()) { print("binddevip: can't bind ether %s: %s\n", oe->netethname, up->errstr); nexterror(); } /* get a new ip interface */ oe->ifcctl = icc = namecopen("/net/ipifc/clone", ORDWR); if(icc == nil) error("can't open /net/ipifc/clone"); /* * specify medium as ethernet, bind the interface to it. * this should trigger chandial of types 0x800, 0x806 and 0x86dd. */ snprint(buf, sizeof buf, "bind ether %s", oe->netethname); devtab[icc->type]->write(icc, buf, strlen(buf), 0); /* bind ether %s */ poperror(); return 0; } /* set the default route */ static int adddefroute(char *, uchar *gaddr) { char buf[64]; Chan *rc; rc = nil; if (waserror()) { if (rc) cclose(rc); return -1; } rc = enamecopen("/net/iproute", ORDWR); if(isv4(gaddr)) snprint(buf, sizeof buf, "add 0 0 %I", gaddr); else snprint(buf, sizeof buf, "add :: /0 %I", gaddr); devtab[rc->type]->write(rc, buf, strlen(buf), 0); poperror(); cclose(rc); return 0; } static int validip(uchar *ip) { return ipcmp(ip, IPnoaddr) != 0 && ipcmp(ip, v4prefix) != 0; } static int openetherdev(Openeth *oe) { int n; char num[16]; Chan *c; static char promisc[] = "promiscuous"; if (chdir(oe->netethname) < 0) return -1; /* out of ethers */ oe->ethctl = nil; if (waserror()) { print("error opening /net/ether%d/0/ctl: %s\n", oe->ctlrno, up->errstr); if (oe->ethctl) { cclose(oe->ethctl); oe->ethctl = nil; } chdir("/"); /* don't hold conv. open */ return -1; } oe->ethctl = c = namecopen("0/ctl", ORDWR); /* should be ipv4 */ if (c == nil) { /* read clone file to make conversation 0 since not present */ oe->ethctl = c = enamecopen("clone", ORDWR); n = devtab[c->type]->read(c, num, sizeof num - 1, 0); if (n < 0) print("no %s/clone: %s\n", oe->netethname, up->errstr); else { num[n] = 0; print("%s/clone returned %s\n", oe->netethname, num); } } /* shouldn't be needed to read bootp (broadcast) reply */ devtab[c->type]->write(c, promisc, sizeof promisc-1, 0); poperror(); chdir("/"); /* leave oe->ethctl open to keep promiscuous mode on */ return 0; } /* add a logical interface to the ip stack */ int minip4cfg(Openeth *oe) { int n; char buf[64]; n = snprint(buf, sizeof buf, "add %I", IPnoaddr); devtab[oe->ifcctl->type]->write(oe->ifcctl, buf, n, 0); /* add %I */ openetherdev(oe); return 0; } /* remove the :: address added by minip4cfg */ int unminip4cfg(Openeth *oe) { int n; char buf[64]; n = snprint(buf, sizeof buf, "remove %I /128", IPnoaddr); if (waserror()) { print("failed write to ifc: %s: %s\n", buf, up->errstr); return -1; } devtab[oe->ifcctl->type]->write(oe->ifcctl, buf, n, 0); /* remove %I */ cclose(oe->ethctl); /* turn promiscuous mode off */ oe->ethctl = nil; poperror(); return 0; } /* * parse p, looking for option `op'. if non-nil, np points to minimum length. * return nil if option is too small, else ptr to opt, and * store actual length via np if non-nil. */ uchar* optget(uchar *p, int op, int *np) { int len, code; while ((code = *p++) != OBend) { if(code == OBpad) continue; len = *p++; if(code != op) { p += len; continue; } if(np != nil){ if(*np > len) return 0; *np = len; } return p; } return 0; } int optgetaddr(uchar *p, int op, uchar *ip) { int len; len = 4; p = optget(p, op, &len); if(p == nil) return 0; v4tov6(ip, p); return 1; } int beprimary = 1; /* add a logical interface to the ip stack */ int ip4cfg(Openeth *oe, Bootp *rep) { int n; uchar gaddr[IPaddrlen], v6mask[IPaddrlen]; uchar v4mask[IPv4addrlen]; char buf[64]; static uchar zeroes[4]; v4tov6(gaddr, rep->yiaddr); if(!validip(gaddr)) return -1; /* dig subnet mask, if any, out of options. if none, guess. */ if(optgetaddr(rep->optdata, OBmask, v6mask)) { v6tov4(v4mask, v6mask); n = snprint(buf, sizeof buf, "add %V %M", rep->yiaddr, v4mask); } else n = snprint(buf, sizeof buf, "add %V 255.255.255.0", rep->yiaddr); devtab[oe->ifcctl->type]->write(oe->ifcctl, buf, n, 0); v4tov6(gaddr, rep->giaddr); if(beprimary==1 && validip(gaddr) && !equivip4(rep->giaddr, zeroes)) adddefroute("/net", gaddr); return 0; } static int openudp(Openeth *oe) { int n; char buf[16]; Chan *cc; /* read clone file for conversation number */ if (waserror()) panic("openudp: can't open /net/udp/clone"); cc = enamecopen("/net/udp/clone", ORDWR); oe->udpctl = cc; n = devtab[cc->type]->read(cc, buf, sizeof buf - 1, 0); poperror(); buf[n] = '\0'; return atoi(buf); } static void initbind(Openeth *oe) { char buf[8]; if (waserror()) { print("error while binding: %s\n", up->errstr); return; } snprint(buf, sizeof buf, "#I%d", oe->ctlrno); bind(buf, "/net", MAFTER); snprint(buf, sizeof buf, "#l%d", oe->ctlrno); bind(buf, "/net", MAFTER); binddevip(oe); poperror(); } static void closeudp(Openeth *oe) { if (oe->udpctl) { cclose(oe->udpctl); oe->udpctl = nil; } if (oe->udpdata) { cclose(oe->udpdata); oe->udpdata = nil; } } static int announce(Openeth *oe, char *port) { int udpconv; char buf[32]; static char hdrs[] = "headers"; while (waserror()) { print("can't announce udp!*!%s: %s\n", port, up->errstr); closeudp(oe); nexterror(); } udpconv = openudp(oe); if (udpconv < 0) panic("can't open udp conversation: %s", up->errstr); /* headers is only effective after a udp announce */ snprint(buf, sizeof buf, "announce %s", port); devtab[oe->udpctl->type]->write(oe->udpctl, buf, strlen(buf), 0); devtab[oe->udpctl->type]->write(oe->udpctl, hdrs, sizeof hdrs - 1, 0); poperror(); /* now okay to open the data file */ snprint(buf, sizeof buf, "/net/udp/%d/data", udpconv); /* * we must use create, not open, to get Conv->rq and ->wq * allocated by udpcreate. */ oe->udpdata = enameccreate(buf, ORDWR); cclose(oe->udpctl); oe->udpctl = nil; return udpconv; } static long tftprdfile(Openeth *oe, int openread, void* va, long len) { int n; char *p, *v; n = openread; /* have read this many bytes already into tftpb->data */ p = v = va; len--; /* leave room for NUL */ while(n > 0) { if((p-v)+n > len) n = len - (p-v); memmove(p, tftpb->data, n); p += n; *p = 0; if(n != segsize) break; if((n = tftpread(oe, &tftpserv, tftpb, segsize)) < 0) return n; } return p-v; } static int tftpconnect(Openeth *oe, Bootp *rep) { char num[16], dialstr[64]; if (waserror()) { print("can't dial: %s\n", up->errstr); return -1; } closeudp(oe); tftpphase = 1; tftpport = 5000 + nrand(20480); snprint(num, sizeof num, "%d", tftpport); if (Tftpusehdrs) announce(oe, num); else { snprint(dialstr, sizeof dialstr, "/net/udp!%V!%d", rep->siaddr, TFTPport); oe->udpdata = chandial(dialstr, num, nil, nil); oe->udpctl = nil; } poperror(); return 0; } static int setipcfg(Openeth *oe, Bootp *rep) { int r; tftpphase = 0; progress = 1; /* /net/iproute is unpopulated here; add at least broadcast */ minip4cfg(oe); announce(oe, "68"); r = bootpbcast(oe, nil, rep); closeudp(oe); unminip4cfg(oe); if(r < 0) return -1; ip4cfg(oe, rep); if (Debug) print("got & set ip config\n"); return 0; } /* * use bootp answer (rep) to open cfgpxe. * reads first pkt of cfgpxe into tftpb->data. */ static int rdcfgpxe(Openeth *oe, Bootp *rep, char *cfgpxe) { int n; char *ini; /* cfgpxe is optional */ n = tftpopen(oe, cfgpxe, rep); if (n < 0) return n; if (Debug) print("\opened %s\n", cfgpxe); ini = smalloc(2*BOOTARGSLEN); /* starts by copying data from tftpb->data into ini */ n = tftprdfile(oe, n, ini, 2*BOOTARGSLEN); if (n < 0) { print("error reading %s\n", cfgpxe); free(ini); return n; } print(" read %d bytes", n); /* * take note of plan9.ini contents. consumes ini to make config vars, * thus we can't free ini. */ dotini(ini); return Ok; } /* * break kp->bootfile into kp->edev & kp->bootfile, * copy any args for new kernel to low memory. */ static int parsebootfile(Kernname *kp) { char *p; p = strchr(kp->bootfile, '!'); if (p != nil) { *p++ = '\0'; kp->edev = kp->bootfile; kp->bootfile = nil; kstrdup(&kp->bootfile, p); if (strncmp(kp->edev, ethernm, sizeof ethernm - 1) != 0) { print("bad ether device %s\n", kp->edev); return Err; } } /* pass any arguments to kernels that expect them */ strecpy(BOOTLINE, BOOTLINE+BOOTLINELEN, kp->bootfile); p = strchr(kp->bootfile, ' '); if(p != nil) *p = '\0'; return Ok; } static int getkernname(Openeth *oe, Bootp *rep, Kernname *kp) { int n; char *p; char cfgpxe[32], buf[64]; if (kp->bootfile) { /* i think returning here is a bad idea */ // print("getkernname: already have bootfile %s\n", // kp->bootfile); free(kp->bootfile); // return Ok; } kp->edev = kp->bootfile = nil; i8250console(); /* configure serial port with defaults */ /* use our mac address instead of relying on a bootp answer. */ snprint(cfgpxe, sizeof cfgpxe, "/cfg/pxe/%E", myea); n = rdcfgpxe(oe, rep, cfgpxe); switch (n) { case Ok: p = getconf("bootfile"); if (p) kstrdup(&kp->bootfile, p); if (kp->bootfile == nil) askbootfile(buf, sizeof buf, &kp->bootfile, Promptsecs, "ether0!/386/9pccpu"); if (strcmp(kp->bootfile, "manual") == 0) askbootfile(buf, sizeof buf, &kp->bootfile, 0, ""); break; case Err: print("\nfailed.\n"); return n; case Nonexist: askbootfile(buf, sizeof buf, &kp->bootfile, 0, ""); break; } return parsebootfile(kp); } static void unbinddevip(Openeth *oe) { Chan *icc; static char unbind[] = "unbind"; icc = oe->ifcctl; if (icc) { devtab[icc->type]->write(icc, unbind, sizeof unbind - 1, 0); cclose(icc); oe->ifcctl = nil; } } /* * phase 1: get our ip (v4) configuration via bootp, set new ip configuration. * phase 2: load /cfg/pxe, parse it, extract kernel filename. * phase 3: load kernel and jump to it. */ static int tftpload(Openeth *oe, Kernname *kp) { int r, n; char buf[64]; Bootp rep; Boot boot; r = -1; if(waserror()) { print("tftpload: %s\n", up->errstr); closeudp(oe); unbinddevip(oe); return r; } memset(&rep, 0, sizeof rep); if (setipcfg(oe, &rep) < 0) error("can't set ip config"); n = getkernname(oe, &rep, kp); if (n < 0) { r = n; /* pass reason back to caller */ USED(r); nexterror(); } do { if (kp->edev && oe->ctlrno != strtol(kp->edev + sizeof ethernm - 1, 0, 10)){ /* user specified an ether & it's not this one; next! */ r = Ok; USED(r); nexterror(); } memset(&boot, 0, sizeof boot); boot.state = INITKERNEL; r = tftpboot(oe, kp->bootfile, &rep, &boot); /* we failed or bootfile asked for another ether */ if (r == Nonexist) do { askbootfile(buf, sizeof buf, &kp->bootfile, 0, ""); } while (parsebootfile(kp) != Ok); } while (r == Nonexist); poperror(); closeudp(oe); unbinddevip(oe); return r; } static int etherload(int eth, Kernname *kp) { int r; Openeth *oe; print("pxe on ether%d ", eth); oe = smalloc(sizeof *oe); memset(oe, 0, sizeof *oe); oe->ctlrno = eth; snprint(oe->ethname, sizeof oe->ethname, "ether%d", oe->ctlrno); snprint(oe->netethname, sizeof oe->netethname, "/net/ether%d", oe->ctlrno); initbind(oe); r = tftpload(oe, kp); /* failed to boot; keep going */ unmount(nil, "/net"); return r; } static int attacheth(int neth) { char num[4]; Chan *cc; cc = nil; if (waserror()) { /* no more interfaces */ if (cc) cclose(cc); return -1; } snprint(num, sizeof num, "%d", neth); cc = etherattach(num); if (cc) cclose(cc); poperror(); return cc == nil? -1: 0; } void bootloadproc(void *) { int eth, neth, needattach; Kernname kernnm; srand(TK2MS(m->ticks)); /* for local port numbers */ nrand(20480); /* 1st # is always 0; toss it */ kernnm.edev = kernnm.bootfile = nil; while(waserror()) { print("%s\n", up->errstr); tsleep(&up->sleep, return0, 0, 30*1000); } neth = MaxEther; needattach = 1; for (;;) { /* try each interface in turn: first get /cfg/pxe file */ for (eth = 0; eth < neth && kernnm.edev == nil; eth++) { if (needattach && attacheth(eth) < 0) break; etherload(eth, &kernnm); } if (needattach) { neth = eth; needattach = 0; if (neth == 0) print("no ethernet interfaces found\n"); } if (kernnm.edev != nil) { eth = strtol(kernnm.edev + sizeof ethernm - 1, 0, 10); etherload(eth, &kernnm); } /* * couldn't boot on any ether. don't give up; * perhaps the boot servers are down, so try again later. */ print("failed to boot via pxe; will try again.\n"); tsleep(&up->sleep, return0, 0, 15*1000); } }