#include "include.h" #include "ip.h" #include "fs.h" #define INIPATHLEN 64 typedef struct Pxether Pxether; static struct Pxether { Fs fs; char ini[INIPATHLEN]; } *pxether; extern int debug; extern int debugload; extern char *persist; uchar broadcast[Eaddrlen] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; static ushort tftpport = 5000; static int Id = 1; static int ctlrinuse; static Netaddr myaddr; static Netaddr server; typedef struct { uchar header[4]; uchar data[Segsize]; } Tftp; static Tftp *tftpbp; static void hnputs(uchar *ptr, ushort val) { ptr[0] = val>>8; ptr[1] = val; } static void hnputl(uchar *ptr, ulong val) { ptr[0] = val>>24; ptr[1] = val>>16; ptr[2] = val>>8; ptr[3] = val; } static ulong nhgetl(uchar *ptr) { return ((ptr[0]<<24) | (ptr[1]<<16) | (ptr[2]<<8) | ptr[3]); } static ushort nhgets(uchar *ptr) { return ((ptr[0]<<8) | ptr[1]); } static short endian = 1; static char* aendian = (char*)&endian; #define LITTLE *aendian static ushort ptcl_csum(void *a, int len) { uchar *addr; ulong t1, t2; ulong losum, hisum, mdsum, x; addr = a; losum = 0; hisum = 0; mdsum = 0; x = 0; if((ulong)addr & 1) { if(len) { hisum += addr[0]; len--; addr++; } x = 1; } while(len >= 16) { t1 = *(ushort*)(addr+0); t2 = *(ushort*)(addr+2); mdsum += t1; t1 = *(ushort*)(addr+4); mdsum += t2; t2 = *(ushort*)(addr+6); mdsum += t1; t1 = *(ushort*)(addr+8); mdsum += t2; t2 = *(ushort*)(addr+10); mdsum += t1; t1 = *(ushort*)(addr+12); mdsum += t2; t2 = *(ushort*)(addr+14); mdsum += t1; mdsum += t2; len -= 16; addr += 16; } while(len >= 2) { mdsum += *(ushort*)addr; len -= 2; addr += 2; } if(x) { if(len) losum += addr[0]; if(LITTLE) losum += mdsum; else hisum += mdsum; } else { if(len) hisum += addr[0]; if(LITTLE) hisum += mdsum; else losum += mdsum; } losum += hisum >> 8; losum += (hisum & 0xff) << 8; while(hisum = losum>>16) losum = hisum + (losum & 0xffff); return ~losum; } static ushort ip_csum(uchar *addr) { int len; ulong sum = 0; len = (addr[0]&0xf)<<2; while(len > 0) { sum += addr[0]<<8 | addr[1] ; len -= 2; addr += 2; } sum = (sum & 0xffff) + (sum >> 16); sum = (sum & 0xffff) + (sum >> 16); return (sum^0xffff); } static void udpsend(int ctlrno, Netaddr *a, void *data, int dlen) { Udphdr *uh; Etherhdr *ip; Etherpkt pkt; int len, ptcllen; uh = (Udphdr*)&pkt; memset(uh, 0, sizeof(Etherpkt)); memmove(uh->udpcksum+sizeof(uh->udpcksum), data, dlen); /* * UDP portion */ ptcllen = dlen + (UDP_HDRSIZE-UDP_PHDRSIZE); uh->ttl = 0; uh->udpproto = IP_UDPPROTO; uh->frag[0] = 0; uh->frag[1] = 0; hnputs(uh->udpplen, ptcllen); hnputl(uh->udpsrc, myaddr.ip); hnputs(uh->udpsport, myaddr.port); hnputl(uh->udpdst, a->ip); hnputs(uh->udpdport, a->port); hnputs(uh->udplen, ptcllen); uh->udpcksum[0] = 0; uh->udpcksum[1] = 0; dlen = (dlen+1)&~1; hnputs(uh->udpcksum, ptcl_csum(&uh->ttl, dlen+UDP_HDRSIZE)); /* * IP portion */ ip = (Etherhdr*)&pkt; len = UDP_EHSIZE+UDP_HDRSIZE+dlen; /* non-descriptive names */ ip->vihl = IP_VER|IP_HLEN; ip->tos = 0; ip->ttl = 255; hnputs(ip->length, len-ETHER_HDR); hnputs(ip->id, Id++); ip->frag[0] = 0; ip->frag[1] = 0; ip->cksum[0] = 0; ip->cksum[1] = 0; hnputs(ip->cksum, ip_csum(&ip->vihl)); /* * Ethernet MAC portion */ hnputs(ip->type, ET_IP); memmove(ip->d, a->ea, sizeof(ip->d)); if(debug) { print("udpsend "); } /* * if packet is too short, make it longer rather than relying * on ethernet interface or lower layers to pad it. */ if (len < ETHERMINTU) len = ETHERMINTU; ethertxpkt(ctlrno, &pkt, len, Timeout); } static void nak(int ctlrno, Netaddr *a, int code, char *msg, int report) { int n; char buf[128]; buf[0] = 0; buf[1] = Tftp_ERROR; buf[2] = 0; buf[3] = code; strecpy(buf+4, buf + sizeof buf, msg); n = strlen(msg) + 4 + 1; udpsend(ctlrno, a, buf, n); if(report) print("\ntftp: error(%d): %s\n", code, msg); } void dump(void *vaddr, int words) { ulong *addr; addr = vaddr; while (words-- > 0) print("%.8lux%c", *addr++, words % 8 == 0? '\n': ' '); } static int udprecv(int ctlrno, Netaddr *a, void *data, int dlen) { int n, len; ushort csm; Udphdr *h; ulong addr, timo; Etherpkt pkt; static int rxactive; if(rxactive == 0) timo = 1000; else timo = Timeout; timo += TK2MS(m->ticks); while(timo > TK2MS(m->ticks)){ n = etherrxpkt(ctlrno, &pkt, timo - TK2MS(m->ticks)); if(n <= 0) continue; h = (Udphdr*)&pkt; if(debug) print("udprecv %E to %E...\n", h->s, h->d); if(nhgets(h->type) != ET_IP) { if(debug) print("not ip..."); continue; } if(ip_csum(&h->vihl)) { print("ip chksum error\n"); continue; } if(h->vihl != (IP_VER|IP_HLEN)) { print("ip bad vers/hlen\n"); continue; } if(h->udpproto != IP_UDPPROTO) { if(debug) print("not udp (%d)...", h->udpproto); continue; } if(debug) print("okay udp..."); h->ttl = 0; len = nhgets(h->udplen); hnputs(h->udpplen, len); if(nhgets(h->udpcksum)) { csm = ptcl_csum(&h->ttl, len+UDP_PHDRSIZE); if(csm != 0) { print("udp chksum error csum #%4ux len %d\n", csm, n); break; } } if(a->port != 0 && nhgets(h->udpsport) != a->port) { if(debug) print("udpport %ux not %ux\n", nhgets(h->udpsport), a->port); continue; } addr = nhgetl(h->udpsrc); if(a->ip != Bcastip && a->ip != addr) { if(debug) print("bad ip %lux not %lux\n", addr, a->ip); continue; } len -= UDP_HDRSIZE-UDP_PHDRSIZE; if(len > dlen) { print("udp: packet too big: %d > %d; from addr %E\n", len, dlen, h->udpsrc); continue; } memmove(data, h->udpcksum+sizeof(h->udpcksum), len); a->ip = addr; a->port = nhgets(h->udpsport); memmove(a->ea, pkt.s, sizeof(a->ea)); rxactive = 1; return len; } return 0; } static int tftpblockno; /* * format of a request packet, from the RFC: * 2 bytes string 1 byte string 1 byte ------------------------------------------------ | Opcode | Filename | 0 | Mode | 0 | ------------------------------------------------ */ static int tftpopen(int ctlrno, Netaddr *a, char *name, Tftp *tftp, int op) { int i, len, rlen, oport; char *end; static char buf[Segsize+2]; /* reduce stack use */ ctlrinuse = ctlrno; buf[0] = 0; buf[1] = op; end = seprint(buf+2, buf + sizeof buf, "%s", name) + 1; end = seprint(end, buf + sizeof buf, "octet") + 1; len = end - buf; oport = a->port; for(i = 0; i < 5; i++){ a->port = oport; udpsend(ctlrno, a, buf, len); a->port = 0; if((rlen = udprecv(ctlrno, a, tftp, sizeof(Tftp))) < sizeof(tftp->header)) continue; switch((tftp->header[0]<<8)|tftp->header[1]){ case Tftp_ERROR: print("tftpopen: error (%d): %s\n", (tftp->header[2]<<8)|tftp->header[3], (char*)tftp->data); return -1; case Tftp_DATA: /* this should only happen when opening to read */ tftpblockno = 1; len = (tftp->header[2]<<8)|tftp->header[3]; if(len != tftpblockno){ print("tftpopen: block error: %d\n", len); nak(ctlrno, a, 1, "block error", 0); return -1; } rlen -= sizeof(tftp->header); if(rlen < Segsize){ /* ACK last block now, in case we don't later */ buf[0] = 0; buf[1] = Tftp_ACK; buf[2] = tftpblockno>>8; buf[3] = tftpblockno; udpsend(ctlrno, a, buf, sizeof(tftp->header)); } return rlen; case Tftp_ACK: /* this should only happen when opening to write */ len = (tftp->header[2]<<8)|tftp->header[3]; if(len != 0){ print("tftpopen: block # error: %d != 0\n", len); nak(ctlrno, a, 1, "block # error", 0); return -1; } return 0; } } print("tftpopen: failed to connect to server\n"); return -1; } static int tftpread(int ctlrno, Netaddr *a, Tftp *tftp, int dlen) { uchar buf[4]; int try, blockno, len; dlen += sizeof(tftp->header); for(try = 0; try < 10; try++) { buf[0] = 0; buf[1] = Tftp_ACK; buf[2] = tftpblockno>>8; buf[3] = tftpblockno; udpsend(ctlrno, a, buf, sizeof(buf)); len = udprecv(ctlrno, a, tftp, dlen); if(len <= sizeof(tftp->header)){ if(debug) print("tftpread: too short %d <= %d\n", len, sizeof(tftp->header)); continue; } 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 */ tftpblockno++; buf[0] = 0; buf[1] = Tftp_ACK; buf[2] = tftpblockno>>8; buf[3] = tftpblockno; udpsend(ctlrno, a, buf, sizeof(buf)); } return len-sizeof(tftp->header); } print("tftpread: block error: %d, expected %d\n", blockno, tftpblockno+1); } return -1; } static int bootpopen(int ctlrno, char *file, Bootp *rep, int dotftpopen) { Bootp req; int i, n; uchar *ea; char name[128], *filename, *sysname; if (debugload) print("bootpopen: ether%d!%s...", ctlrno, file); if((ea = etheraddr(ctlrno)) == 0){ print("invalid ctlrno %d\n", ctlrno); return -1; } filename = 0; sysname = 0; if(file && *file){ strecpy(name, name + sizeof name, file); if(filename = strchr(name, '!')){ sysname = name; *filename++ = 0; } else filename = name; } memset(&req, 0, sizeof(req)); req.op = Bootrequest; req.htype = 1; /* ethernet */ req.hlen = Eaddrlen; /* ethernet */ memmove(req.chaddr, ea, Eaddrlen); if(filename != nil) strncpy(req.file, filename, sizeof(req.file)); if(sysname != nil) strncpy(req.sname, sysname, sizeof(req.sname)); myaddr.ip = 0; myaddr.port = BPportsrc; memmove(myaddr.ea, ea, Eaddrlen); etherrxflush(ctlrno); for(i = 0; i < 10; i++) { server.ip = Bcastip; server.port = BPportdst; memmove(server.ea, broadcast, sizeof(server.ea)); udpsend(ctlrno, &server, &req, sizeof(req)); if(udprecv(ctlrno, &server, rep, sizeof(*rep)) <= 0) continue; if(memcmp(req.chaddr, rep->chaddr, Eaddrlen)) continue; if(rep->htype != 1 || rep->hlen != Eaddrlen) continue; if(sysname == 0 || strcmp(sysname, rep->sname) == 0) break; } if(i >= 10) { print("bootp on ether%d for %s timed out\n", ctlrno, file); return -1; } if(!dotftpopen) return 0; if(filename == 0 || *filename == 0){ if(strcmp(rep->file, "/386/9pxeload") == 0) return -1; filename = rep->file; } if(rep->sname[0] != '\0') print("%s ", rep->sname); print("(%d.%d.%d.%d!%d): %s\n", rep->siaddr[0], rep->siaddr[1], rep->siaddr[2], rep->siaddr[3], server.port, filename); myaddr.ip = nhgetl(rep->yiaddr); myaddr.port = tftpport++; server.ip = nhgetl(rep->siaddr); server.port = TFTPport; if((n = tftpopen(ctlrno, &server, filename, tftpbp, Tftp_READ)) < 0) return -1; return n; } int bootpboot(int ctlrno, char *file, Boot *b) { int n; Bootp rep; if (tftpbp == nil) tftpbp = malloc(sizeof *tftpbp); if((n = bootpopen(ctlrno, file, &rep, 1)) < 0) return -1; while(bootpass(b, tftpbp->data, n) == MORE){ n = tftpread(ctlrno, &server, tftpbp, sizeof(tftpbp->data)); if(n < sizeof(tftpbp->data)) break; } if(0 < n && n < sizeof(tftpbp->data)) /* got to end of file */ bootpass(b, tftpbp->data, n); else nak(ctlrno, &server, 3, "ok", 0); /* tftpclose to abort transfer */ bootpass(b, nil, 0); /* boot if possible */ return -1; } static vlong pxediskseek(Fs*, vlong) { return -1; } static long pxediskread(Fs*, void*, long) { return -1; } static long pxeread(File* f, void* va, long len) { int n; Bootp rep; char *p, *v; if (pxether == nil) pxether = malloc(MaxEther * sizeof *pxether); if((n = bootpopen(f->fs->dev, pxether[f->fs->dev].ini, &rep, 1)) < 0) return -1; p = v = va; while(n > 0) { if((p-v)+n > len) n = len - (p-v); memmove(p, tftpbp->data, n); p += n; if(n != Segsize) break; if((n = tftpread(f->fs->dev, &server, tftpbp, sizeof(tftpbp->data))) < 0) return -1; } return p-v; } static int pxewalk(File* f, char* name) { Bootp rep; char *ini; switch(f->walked){ default: return -1; case 0: if(strcmp(name, "cfg") == 0){ f->walked = 1; return 1; } break; case 1: if(strcmp(name, "pxe") == 0){ f->walked = 2; return 1; } break; case 2: if(strcmp(name, "%E") != 0) break; f->walked = 3; if(bootpopen(f->fs->dev, nil, &rep, 0) < 0) return 0; if (pxether == nil) pxether = malloc(MaxEther * sizeof *pxether); ini = pxether[f->fs->dev].ini; /* use our mac address instead of relying on a bootp answer */ seprint(ini, ini+INIPATHLEN, "/cfg/pxe/%E", (uchar *)myaddr.ea); f->path = ini; return 1; } return 0; } void* pxegetfspart(int ctlrno, char* part, int) { Fs *fs; if(!pxe || strcmp(part, "*") != 0 || ctlrno >= MaxEther || iniread && getconf("*pxeini") != nil) return nil; if (pxether == nil) pxether = malloc(MaxEther * sizeof *pxether); fs = &pxether[ctlrno].fs; fs->dev = ctlrno; fs->diskread = pxediskread; fs->diskseek = pxediskseek; fs->read = pxeread; fs->walk = pxewalk; fs->root.fs = fs; fs->root.walked = 0; return fs; } /* * tftp upload, for memory dumps and the like. */ void sendfile(int ctlrno, Netaddr *a, void *p, int len, char *name) { int ackblock, block, ret, rexmit, rlen, n, txtry, rxl; short op; char *mem, *emem; static uchar ack[1024], buf[Segsize+4]; /* reduce stack use */ block = 0; rexmit = 0; n = 0; mem = p; emem = mem + len; for(txtry = 0; txtry < 10;) { if(rexmit == 0) { block++; buf[0] = 0; buf[1] = Tftp_DATA; buf[2] = block>>8; buf[3] = block; if (mem < emem) { if (emem - mem < Segsize) n = emem - mem; else n = Segsize; memmove(buf+4, mem, n); mem += n; } else n = 0; txtry = 0; } else { print("rexmit %d bytes to %s:%d\n", 4+n, name, block); txtry++; } /* write buf to network */ udpsend(ctlrno, a, buf, 4+n); ret = 4+n; for(rxl = 0; rxl < 10; rxl++) { rexmit = 0; /* read ack from network */ memset(ack, 0, 32); rlen = udprecv(ctlrno, a, ack, sizeof ack); if(rlen < sizeof tftpbp->header) { rexmit = 1; print("reply too small\n"); break; } op = ack[0]<<8 | ack[1]; if(op == Tftp_ERROR) { print("sendfile: tftp error\n"); break; } if (op != Tftp_ACK) { print("expected ACK!\n"); continue; } ackblock = ack[2]<<8 | ack[3]; if(ackblock == block) break; else if(ackblock == 0xffff) { rexmit = 1; break; } else print("ack not for last block sent, " "awaiting another\n"); } if (rxl >= 10) print("never got ack for block %d\n", block); if(ret != Segsize+4 && rexmit == 0) { print(" done.\n"); break; } if (0 && rexmit == 0) print("."); } if (txtry >= 5) print("too many rexmits\n"); } int tftpupload(char *name, void *p, int len) { int n; if (tftpbp == nil) tftpbp = malloc(sizeof *tftpbp); /* assume myaddr and server are still set from downloading */ myaddr.port = tftpport++; server.port = TFTPport; n = tftpopen(ctlrinuse, &server, name, tftpbp, Tftp_WRITE); if(n < 0) return -1; sendfile(ctlrinuse, &server, p, len, name); return 0; }