From 85d9eae44ef5e2c48fcbefe36fb7bdfcb54c9f75 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 1 May 2005 14:04:11 +0000 Subject: Moved protocols to proto/ --- src/core/nfs.c | 610 -------------------------------------------------- src/core/proto_http.c | 206 ----------------- src/core/proto_slam.c | 541 -------------------------------------------- src/core/proto_tftm.c | 491 ---------------------------------------- src/proto/http.c | 206 +++++++++++++++++ src/proto/nfs.c | 610 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/proto/slam.c | 541 ++++++++++++++++++++++++++++++++++++++++++++ src/proto/tftm.c | 491 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1848 insertions(+), 1848 deletions(-) delete mode 100644 src/core/nfs.c delete mode 100644 src/core/proto_http.c delete mode 100644 src/core/proto_slam.c delete mode 100644 src/core/proto_tftm.c create mode 100644 src/proto/http.c create mode 100644 src/proto/nfs.c create mode 100644 src/proto/slam.c create mode 100644 src/proto/tftm.c diff --git a/src/core/nfs.c b/src/core/nfs.c deleted file mode 100644 index cbda6ab7b..000000000 --- a/src/core/nfs.c +++ /dev/null @@ -1,610 +0,0 @@ -#ifdef DOWNLOAD_PROTO_NFS - -#include "etherboot.h" -#include "nic.h" - -/* NOTE: the NFS code is heavily inspired by the NetBSD netboot code (read: - * large portions are copied verbatim) as distributed in OSKit 0.97. A few - * changes were necessary to adapt the code to Etherboot and to fix several - * inconsistencies. Also the RPC message preparation is done "by hand" to - * avoid adding netsprintf() which I find hard to understand and use. */ - -/* NOTE 2: Etherboot does not care about things beyond the kernel image, so - * it loads the kernel image off the boot server (ARP_SERVER) and does not - * access the client root disk (root-path in dhcpd.conf), which would use - * ARP_ROOTSERVER. The root disk is something the operating system we are - * about to load needs to use. This is different from the OSKit 0.97 logic. */ - -/* NOTE 3: Symlink handling introduced by Anselm M Hoffmeister, 2003-July-14 - * If a symlink is encountered, it is followed as far as possible (recursion - * possible, maximum 16 steps). There is no clearing of ".."'s inside the - * path, so please DON'T DO THAT. thx. */ - -#define START_OPORT 700 /* mountd usually insists on secure ports */ -#define OPORT_SWEEP 200 /* make sure we don't leave secure range */ - -static int oport = START_OPORT; -static int mount_port = -1; -static int nfs_port = -1; -static int fs_mounted = 0; -static unsigned long rpc_id; - -/************************************************************************** -RPC_INIT - set up the ID counter to something fairly random -**************************************************************************/ -void rpc_init(void) -{ - unsigned long t; - - t = currticks(); - rpc_id = t ^ (t << 8) ^ (t << 16); -} - - -/************************************************************************** -RPC_PRINTERROR - Print a low level RPC error message -**************************************************************************/ -static void rpc_printerror(struct rpc_t *rpc) -{ - if (rpc->u.reply.rstatus || rpc->u.reply.verifier || - rpc->u.reply.astatus) { - /* rpc_printerror() is called for any RPC related error, - * suppress output if no low level RPC error happened. */ - printf("RPC error: (%d,%d,%d)\n", ntohl(rpc->u.reply.rstatus), - ntohl(rpc->u.reply.verifier), - ntohl(rpc->u.reply.astatus)); - } -} - -/************************************************************************** -AWAIT_RPC - Wait for an rpc packet -**************************************************************************/ -static int await_rpc(int ival, void *ptr, - unsigned short ptype, struct iphdr *ip, struct udphdr *udp) -{ - struct rpc_t *rpc; - if (!udp) - return 0; - if (arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr) - return 0; - if (ntohs(udp->dest) != ival) - return 0; - if (nic.packetlen < ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr) + 8) - return 0; - rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; - if (*(unsigned long *)ptr != ntohl(rpc->u.reply.id)) - return 0; - if (MSG_REPLY != ntohl(rpc->u.reply.type)) - return 0; - return 1; -} - -/************************************************************************** -RPC_LOOKUP - Lookup RPC Port numbers -**************************************************************************/ -static int rpc_lookup(int addr, int prog, int ver, int sport) -{ - struct rpc_t buf, *rpc; - unsigned long id; - int retries; - long *p; - - id = rpc_id++; - buf.u.call.id = htonl(id); - buf.u.call.type = htonl(MSG_CALL); - buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ - buf.u.call.prog = htonl(PROG_PORTMAP); - buf.u.call.vers = htonl(2); /* portmapper is version 2 */ - buf.u.call.proc = htonl(PORTMAP_GETPORT); - p = (long *)buf.u.call.data; - *p++ = 0; *p++ = 0; /* auth credential */ - *p++ = 0; *p++ = 0; /* auth verifier */ - *p++ = htonl(prog); - *p++ = htonl(ver); - *p++ = htonl(IP_UDP); - *p++ = 0; - for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { - long timeout; - udp_transmit(arptable[addr].ipaddr.s_addr, sport, SUNRPC_PORT, - (char *)p - (char *)&buf, &buf); - timeout = rfc2131_sleep_interval(TIMEOUT, retries); - if (await_reply(await_rpc, sport, &id, timeout)) { - rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; - if (rpc->u.reply.rstatus || rpc->u.reply.verifier || - rpc->u.reply.astatus) { - rpc_printerror(rpc); - return -1; - } else { - return ntohl(rpc->u.reply.data[0]); - } - } - } - return -1; -} - -/************************************************************************** -RPC_ADD_CREDENTIALS - Add RPC authentication/verifier entries -**************************************************************************/ -static long *rpc_add_credentials(long *p) -{ - int hl; - - /* Here's the executive summary on authentication requirements of the - * various NFS server implementations: Linux accepts both AUTH_NONE - * and AUTH_UNIX authentication (also accepts an empty hostname field - * in the AUTH_UNIX scheme). *BSD refuses AUTH_NONE, but accepts - * AUTH_UNIX (also accepts an empty hostname field in the AUTH_UNIX - * scheme). To be safe, use AUTH_UNIX and pass the hostname if we have - * it (if the BOOTP/DHCP reply didn't give one, just use an empty - * hostname). */ - - hl = (hostnamelen + 3) & ~3; - - /* Provide an AUTH_UNIX credential. */ - *p++ = htonl(1); /* AUTH_UNIX */ - *p++ = htonl(hl+20); /* auth length */ - *p++ = htonl(0); /* stamp */ - *p++ = htonl(hostnamelen); /* hostname string */ - if (hostnamelen & 3) { - *(p + hostnamelen / 4) = 0; /* add zero padding */ - } - memcpy(p, hostname, hostnamelen); - p += hl / 4; - *p++ = 0; /* uid */ - *p++ = 0; /* gid */ - *p++ = 0; /* auxiliary gid list */ - - /* Provide an AUTH_NONE verifier. */ - *p++ = 0; /* AUTH_NONE */ - *p++ = 0; /* auth length */ - - return p; -} - -/************************************************************************** -NFS_PRINTERROR - Print a NFS error message -**************************************************************************/ -static void nfs_printerror(int err) -{ - switch (-err) { - case NFSERR_PERM: - printf("Not owner\n"); - break; - case NFSERR_NOENT: - printf("No such file or directory\n"); - break; - case NFSERR_ACCES: - printf("Permission denied\n"); - break; - case NFSERR_ISDIR: - printf("Directory given where filename expected\n"); - break; - case NFSERR_INVAL: - printf("Invalid filehandle\n"); - break; // INVAL is not defined in NFSv2, some NFS-servers - // seem to use it in answers to v2 nevertheless. - case 9998: - printf("low-level RPC failure (parameter decoding problem?)\n"); - break; - case 9999: - printf("low-level RPC failure (authentication problem?)\n"); - break; - default: - printf("Unknown NFS error %d\n", -err); - } -} - -/************************************************************************** -NFS_MOUNT - Mount an NFS Filesystem -**************************************************************************/ -static int nfs_mount(int server, int port, char *path, char *fh, int sport) -{ - struct rpc_t buf, *rpc; - unsigned long id; - int retries; - long *p; - int pathlen = strlen(path); - - id = rpc_id++; - buf.u.call.id = htonl(id); - buf.u.call.type = htonl(MSG_CALL); - buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ - buf.u.call.prog = htonl(PROG_MOUNT); - buf.u.call.vers = htonl(1); /* mountd is version 1 */ - buf.u.call.proc = htonl(MOUNT_ADDENTRY); - p = rpc_add_credentials((long *)buf.u.call.data); - *p++ = htonl(pathlen); - if (pathlen & 3) { - *(p + pathlen / 4) = 0; /* add zero padding */ - } - memcpy(p, path, pathlen); - p += (pathlen + 3) / 4; - for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { - long timeout; - udp_transmit(arptable[server].ipaddr.s_addr, sport, port, - (char *)p - (char *)&buf, &buf); - timeout = rfc2131_sleep_interval(TIMEOUT, retries); - if (await_reply(await_rpc, sport, &id, timeout)) { - rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; - if (rpc->u.reply.rstatus || rpc->u.reply.verifier || - rpc->u.reply.astatus || rpc->u.reply.data[0]) { - rpc_printerror(rpc); - if (rpc->u.reply.rstatus) { - /* RPC failed, no verifier, data[0] */ - return -9999; - } - if (rpc->u.reply.astatus) { - /* RPC couldn't decode parameters */ - return -9998; - } - return -ntohl(rpc->u.reply.data[0]); - } else { - fs_mounted = 1; - memcpy(fh, rpc->u.reply.data + 1, NFS_FHSIZE); - return 0; - } - } - } - return -1; -} - -/************************************************************************** -NFS_UMOUNTALL - Unmount all our NFS Filesystems on the Server -**************************************************************************/ -void nfs_umountall(int server) -{ - struct rpc_t buf, *rpc; - unsigned long id; - int retries; - long *p; - - if (!arptable[server].ipaddr.s_addr) { - /* Haven't sent a single UDP packet to this server */ - return; - } - if ((mount_port == -1) || (!fs_mounted)) { - /* Nothing mounted, nothing to umount */ - return; - } - id = rpc_id++; - buf.u.call.id = htonl(id); - buf.u.call.type = htonl(MSG_CALL); - buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ - buf.u.call.prog = htonl(PROG_MOUNT); - buf.u.call.vers = htonl(1); /* mountd is version 1 */ - buf.u.call.proc = htonl(MOUNT_UMOUNTALL); - p = rpc_add_credentials((long *)buf.u.call.data); - for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { - long timeout = rfc2131_sleep_interval(TIMEOUT, retries); - udp_transmit(arptable[server].ipaddr.s_addr, oport, mount_port, - (char *)p - (char *)&buf, &buf); - if (await_reply(await_rpc, oport, &id, timeout)) { - rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; - if (rpc->u.reply.rstatus || rpc->u.reply.verifier || - rpc->u.reply.astatus) { - rpc_printerror(rpc); - } - fs_mounted = 0; - return; - } - } -} -/*************************************************************************** - * NFS_READLINK (AH 2003-07-14) - * This procedure is called when read of the first block fails - - * this probably happens when it's a directory or a symlink - * In case of successful readlink(), the dirname is manipulated, - * so that inside the nfs() function a recursion can be done. - **************************************************************************/ -static int nfs_readlink(int server, int port, char *fh, char *path, char *nfh, - int sport) -{ - struct rpc_t buf, *rpc; - unsigned long id; - long *p; - int retries; - int pathlen = strlen(path); - - id = rpc_id++; - buf.u.call.id = htonl(id); - buf.u.call.type = htonl(MSG_CALL); - buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ - buf.u.call.prog = htonl(PROG_NFS); - buf.u.call.vers = htonl(2); /* nfsd is version 2 */ - buf.u.call.proc = htonl(NFS_READLINK); - p = rpc_add_credentials((long *)buf.u.call.data); - memcpy(p, nfh, NFS_FHSIZE); - p += (NFS_FHSIZE / 4); - for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { - long timeout = rfc2131_sleep_interval(TIMEOUT, retries); - udp_transmit(arptable[server].ipaddr.s_addr, sport, port, - (char *)p - (char *)&buf, &buf); - if (await_reply(await_rpc, sport, &id, timeout)) { - rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; - if (rpc->u.reply.rstatus || rpc->u.reply.verifier || - rpc->u.reply.astatus || rpc->u.reply.data[0]) { - rpc_printerror(rpc); - if (rpc->u.reply.rstatus) { - /* RPC failed, no verifier, data[0] */ - return -9999; - } - if (rpc->u.reply.astatus) { - /* RPC couldn't decode parameters */ - return -9998; - } - return -ntohl(rpc->u.reply.data[0]); - } else { - // It *is* a link. - // If it's a relative link, append everything to dirname, filename TOO! - retries = strlen ( (char *)(&(rpc->u.reply.data[2]) )); - if ( *((char *)(&(rpc->u.reply.data[2]))) != '/' ) { - path[pathlen++] = '/'; - while ( ( retries + pathlen ) > 298 ) { - retries--; - } - if ( retries > 0 ) { - memcpy(path + pathlen, &(rpc->u.reply.data[2]), retries + 1); - } else { retries = 0; } - path[pathlen + retries] = 0; - } else { - // Else make it the only path. - if ( retries > 298 ) { retries = 298; } - memcpy ( path, &(rpc->u.reply.data[2]), retries + 1 ); - path[retries] = 0; - } - return 0; - } - } - } - return -1; -} -/************************************************************************** -NFS_LOOKUP - Lookup Pathname -**************************************************************************/ -static int nfs_lookup(int server, int port, char *fh, char *path, char *nfh, - int sport) -{ - struct rpc_t buf, *rpc; - unsigned long id; - long *p; - int retries; - int pathlen = strlen(path); - - id = rpc_id++; - buf.u.call.id = htonl(id); - buf.u.call.type = htonl(MSG_CALL); - buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ - buf.u.call.prog = htonl(PROG_NFS); - buf.u.call.vers = htonl(2); /* nfsd is version 2 */ - buf.u.call.proc = htonl(NFS_LOOKUP); - p = rpc_add_credentials((long *)buf.u.call.data); - memcpy(p, fh, NFS_FHSIZE); - p += (NFS_FHSIZE / 4); - *p++ = htonl(pathlen); - if (pathlen & 3) { - *(p + pathlen / 4) = 0; /* add zero padding */ - } - memcpy(p, path, pathlen); - p += (pathlen + 3) / 4; - for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { - long timeout = rfc2131_sleep_interval(TIMEOUT, retries); - udp_transmit(arptable[server].ipaddr.s_addr, sport, port, - (char *)p - (char *)&buf, &buf); - if (await_reply(await_rpc, sport, &id, timeout)) { - rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; - if (rpc->u.reply.rstatus || rpc->u.reply.verifier || - rpc->u.reply.astatus || rpc->u.reply.data[0]) { - rpc_printerror(rpc); - if (rpc->u.reply.rstatus) { - /* RPC failed, no verifier, data[0] */ - return -9999; - } - if (rpc->u.reply.astatus) { - /* RPC couldn't decode parameters */ - return -9998; - } - return -ntohl(rpc->u.reply.data[0]); - } else { - memcpy(nfh, rpc->u.reply.data + 1, NFS_FHSIZE); - return 0; - } - } - } - return -1; -} - -/************************************************************************** -NFS_READ - Read File on NFS Server -**************************************************************************/ -static int nfs_read(int server, int port, char *fh, int offset, int len, - int sport) -{ - struct rpc_t buf, *rpc; - unsigned long id; - int retries; - long *p; - - static int tokens=0; - /* - * Try to implement something similar to a window protocol in - * terms of response to losses. On successful receive, increment - * the number of tokens by 1 (cap at 256). On failure, halve it. - * When the number of tokens is >= 2, use a very short timeout. - */ - - id = rpc_id++; - buf.u.call.id = htonl(id); - buf.u.call.type = htonl(MSG_CALL); - buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ - buf.u.call.prog = htonl(PROG_NFS); - buf.u.call.vers = htonl(2); /* nfsd is version 2 */ - buf.u.call.proc = htonl(NFS_READ); - p = rpc_add_credentials((long *)buf.u.call.data); - memcpy(p, fh, NFS_FHSIZE); - p += NFS_FHSIZE / 4; - *p++ = htonl(offset); - *p++ = htonl(len); - *p++ = 0; /* unused parameter */ - for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { - long timeout = rfc2131_sleep_interval(TIMEOUT, retries); - if (tokens >= 2) - timeout = TICKS_PER_SEC/2; - - udp_transmit(arptable[server].ipaddr.s_addr, sport, port, - (char *)p - (char *)&buf, &buf); - if (await_reply(await_rpc, sport, &id, timeout)) { - if (tokens < 256) - tokens++; - rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; - if (rpc->u.reply.rstatus || rpc->u.reply.verifier || - rpc->u.reply.astatus || rpc->u.reply.data[0]) { - rpc_printerror(rpc); - if (rpc->u.reply.rstatus) { - /* RPC failed, no verifier, data[0] */ - return -9999; - } - if (rpc->u.reply.astatus) { - /* RPC couldn't decode parameters */ - return -9998; - } - return -ntohl(rpc->u.reply.data[0]); - } else { - return 0; - } - } else - tokens >>= 1; - } - return -1; -} - -/************************************************************************** -NFS - Download extended BOOTP data, or kernel image from NFS server -**************************************************************************/ -int nfs(const char *name, int (*fnc)(unsigned char *, unsigned int, unsigned int, int)) -{ - static int recursion = 0; - int sport; - int err, namelen = strlen(name); - char dirname[300], *fname; - char dirfh[NFS_FHSIZE]; /* file handle of directory */ - char filefh[NFS_FHSIZE]; /* file handle of kernel image */ - unsigned int block; - int rlen, size, offs, len; - struct rpc_t *rpc; - - rx_qdrain(); - - sport = oport++; - if (oport > START_OPORT+OPORT_SWEEP) { - oport = START_OPORT; - } - if ( name != dirname ) { - memcpy(dirname, name, namelen + 1); - } - recursion = 0; -nfssymlink: - if ( recursion > NFS_MAXLINKDEPTH ) { - printf ( "\nRecursion: More than %d symlinks followed. Abort.\n", NFS_MAXLINKDEPTH ); - return 0; - } - recursion++; - fname = dirname + (namelen - 1); - while (fname >= dirname) { - if (*fname == '/') { - *fname = '\0'; - fname++; - break; - } - fname--; - } - if (fname < dirname) { - printf("can't parse file name %s\n", name); - return 0; - } - - if (mount_port == -1) { - mount_port = rpc_lookup(ARP_SERVER, PROG_MOUNT, 1, sport); - } - if (nfs_port == -1) { - nfs_port = rpc_lookup(ARP_SERVER, PROG_NFS, 2, sport); - } - if (nfs_port == -1 || mount_port == -1) { - printf("can't get nfs/mount ports from portmapper\n"); - return 0; - } - - - err = nfs_mount(ARP_SERVER, mount_port, dirname, dirfh, sport); - if (err) { - printf("mounting %s: ", dirname); - nfs_printerror(err); - /* just to be sure... */ - nfs_umountall(ARP_SERVER); - return 0; - } - - err = nfs_lookup(ARP_SERVER, nfs_port, dirfh, fname, filefh, sport); - if (err) { - printf("looking up %s: ", fname); - nfs_printerror(err); - nfs_umountall(ARP_SERVER); - return 0; - } - - offs = 0; - block = 1; /* blocks are numbered starting from 1 */ - size = -1; /* will be set properly with the first reply */ - len = NFS_READ_SIZE; /* first request is always full size */ - do { - err = nfs_read(ARP_SERVER, nfs_port, filefh, offs, len, sport); - if ((err <= -NFSERR_ISDIR)&&(err >= -NFSERR_INVAL) && (offs == 0)) { - // An error occured. NFS servers tend to sending - // errors 21 / 22 when symlink instead of real file - // is requested. So check if it's a symlink! - block = nfs_readlink(ARP_SERVER, nfs_port, dirfh, dirname, - filefh, sport); - if ( 0 == block ) { - printf("\nLoading symlink:%s ..",dirname); - goto nfssymlink; - } - nfs_printerror(err); - nfs_umountall(ARP_SERVER); - return 0; - } - if (err) { - printf("reading at offset %d: ", offs); - nfs_printerror(err); - nfs_umountall(ARP_SERVER); - return 0; - } - - rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; - - /* size must be found out early to allow EOF detection */ - if (size == -1) { - size = ntohl(rpc->u.reply.data[6]); - } - rlen = ntohl(rpc->u.reply.data[18]); - if (rlen > len) { - rlen = len; /* shouldn't happen... */ - } - - err = fnc((char *)&rpc->u.reply.data[19], block, rlen, - (offs+rlen == size)); - if (err <= 0) { - nfs_umountall(ARP_SERVER); - return err; - } - - block++; - offs += rlen; - /* last request is done with matching requested read size */ - if (size-offs < NFS_READ_SIZE) { - len = size-offs; - } - } while (len != 0); - /* len == 0 means that all the file has been read */ - return 1; -} - -#endif /* DOWNLOAD_PROTO_NFS */ diff --git a/src/core/proto_http.c b/src/core/proto_http.c deleted file mode 100644 index f2dc9dd18..000000000 --- a/src/core/proto_http.c +++ /dev/null @@ -1,206 +0,0 @@ -#include "etherboot.h" -#include "http.h" - -#ifdef DOWNLOAD_PROTO_HTTP - -/* The block size is currently chosen to be 512 bytes. This means, we can - allocate the receive buffer on the stack, but it results in a noticeable - performance penalty. - This is what needs to be done in order to increase the block size: - - size negotiation needs to be implemented in TCP - - the buffer needs to be allocated on the heap - - path MTU discovery needs to be implemented -*/ /***/ /* FIXME */ -#define BLOCKSIZE TFTP_DEFAULTSIZE_PACKET - -/************************************************************************** -SEND_TCP_CALLBACK - Send data using TCP -**************************************************************************/ -struct send_recv_state { - int (*fnc)(unsigned char *data, int block, int len, int eof); - char *send_buffer; - char *recv_buffer; - int send_length; - int recv_length; - int bytes_sent; - int block; - int bytes_received; - enum { RESULT_CODE, HEADER, DATA, ERROR, MOVED } recv_state; - int rc; - char location[MAX_URL+1]; -}; - -static int send_tcp_request(int length, void *buffer, void *ptr) { - struct send_recv_state *state = (struct send_recv_state *)ptr; - - if (length > state->send_length - state->bytes_sent) - length = state->send_length - state->bytes_sent; - memcpy(buffer, state->send_buffer + state->bytes_sent, length); - state->bytes_sent += length; - return (length); -} - -/************************************************************************** -RECV_TCP_CALLBACK - Receive data using TCP -**************************************************************************/ -static int recv_tcp_request(int length, const void *buffer, void *ptr) { - struct send_recv_state *state = (struct send_recv_state *)ptr; - - /* Assume that the lines in an HTTP header do not straddle a packet */ - /* boundary. This is probably a reasonable assumption */ - if (state->recv_state == RESULT_CODE) { - while (length > 0) { - /* Find HTTP result code */ - if (*(const char *)buffer == ' ') { - const char *ptr = ((const char *)buffer) + 1; - int rc = strtoul(ptr, &ptr, 10); - if (ptr >= (const char *)buffer + length) { - state->recv_state = ERROR; - return 0; - } - state->rc = rc; - state->recv_state = HEADER; - goto header; - } - ++(const char *)buffer; - length--; - } - state->recv_state = ERROR; - return 0; - } - if (state->recv_state == HEADER) { - header: while (length > 0) { - /* Check for HTTP redirect */ - if (state->rc >= 300 && state->rc < 400 && - !memcmp(buffer, "Location: ", 10)) { - char *ptr = state->location; - int i; - memcpy(ptr, buffer + 10, MAX_URL); - for (i = 0; i < MAX_URL && *ptr > ' '; - i++, ptr++); - *ptr = '\000'; - state->recv_state = MOVED; - return 1; - } - /* Find beginning of line */ - while (length > 0) { - length--; - if (*((const char *)buffer)++ == '\n') - break; - } - /* Check for end of header */ - if (length >= 2 && !memcmp(buffer, "\r\n", 2)) { - state->recv_state = DATA; - buffer += 2; - length -= 2; - break; - } - } - } - if (state->recv_state == DATA) { - state->bytes_received += length; - while (length > 0) { - int copy_length = BLOCKSIZE - state->recv_length; - if (copy_length > length) - copy_length = length; - memcpy(state->recv_buffer + state->recv_length, - buffer, copy_length); - if ((state->recv_length += copy_length) == BLOCKSIZE) { - if (!state->fnc(state->recv_buffer, - ++state->block, BLOCKSIZE, 0)) - return 0; - state->recv_length = 0; - } - length -= copy_length; - buffer += copy_length; - } - } - return 1; -} - -/************************************************************************** -HTTP_GET - Get data using HTTP -**************************************************************************/ -int http(const char *url, - int (*fnc)(unsigned char *, unsigned int, unsigned int, int)) { - static const char GET[] = "GET /%s HTTP/1.0\r\n\r\n"; - static char recv_buffer[BLOCKSIZE]; - in_addr destip; - int port; - int length; - struct send_recv_state state; - - state.fnc = fnc; - state.rc = -1; - state.block = 0; - state.recv_buffer = recv_buffer; - length = strlen(url); - if (length <= MAX_URL) { - memcpy(state.location, url, length+1); - destip = arptable[ARP_SERVER].ipaddr; - port = url_port; - if (port == -1) - port = 80; - goto first_time; - - do { - state.rc = -1; - state.block = 0; - url = state.location; - if (memcmp("http://", url, 7)) - break; - url += 7; - length = inet_aton(url, &destip); - if (!length) { - /* As we do not have support for DNS, assume*/ - /* that HTTP redirects always point to the */ - /* same machine */ - if (state.recv_state == MOVED) { - while (*url && - *url != ':' && *url != '/') url++; - } else { - break; - } - } - if (*(url += length) == ':') { - port = strtoul(url, &url, 10); - } else { - port = 80; - } - if (!*url) - url = "/"; - if (*url != '/') - break; - url++; - - first_time: - length = strlen(url); - state.send_length = sizeof(GET) - 3 + length; - - { char buf[state.send_length + 1]; - sprintf(state.send_buffer = buf, GET, url); - state.bytes_sent = 0; - - state.bytes_received = 0; - state.recv_state = RESULT_CODE; - - state.recv_length = 0; - tcp_transaction(destip.s_addr, 80, &state, - send_tcp_request, recv_tcp_request); - } - } while (state.recv_state == MOVED); - } else { - memcpy(state.location, url, MAX_URL); - state.location[MAX_URL] = '\000'; - } - - if (state.rc == 200) { - return fnc(recv_buffer, ++state.block, state.recv_length, 1); - } else { - printf("Failed to download %s (rc = %d)\n", - state.location, state.rc); - return 0; - } -} - -#endif /* DOWNLOAD_PROTO_HTTP */ diff --git a/src/core/proto_slam.c b/src/core/proto_slam.c deleted file mode 100644 index 135384a9c..000000000 --- a/src/core/proto_slam.c +++ /dev/null @@ -1,541 +0,0 @@ -#ifdef DOWNLOAD_PROTO_SLAM -#include "etherboot.h" -#include "nic.h" - -#define SLAM_PORT 10000 -#define SLAM_MULTICAST_IP ((239<<24)|(255<<16)|(1<<8)|(1<<0)) -#define SLAM_MULTICAST_PORT 10000 -#define SLAM_LOCAL_PORT 10000 - -/* Set the timeout intervals to at least 1 second so - * on a 100Mbit ethernet can receive 10000 packets - * in one second. - * - * The only case that is likely to trigger all of the nodes - * firing a nack packet is a slow server. The odds of this - * happening could be reduced being slightly smarter and utilizing - * the multicast channels for nacks. But that only improves the odds - * it doesn't improve the worst case. So unless this proves to be - * a common case having the control data going unicast should increase - * the odds of the data not being dropped. - * - * When doing exponential backoff we increase just the timeout - * interval and not the base to optimize for throughput. This is only - * expected to happen when the server is down. So having some nodes - * pinging immediately should get the transmission restarted quickly after a - * server restart. The host nic won't be to baddly swamped because of - * the random distribution of the nodes. - * - */ -#define SLAM_INITIAL_MIN_TIMEOUT (TICKS_PER_SEC/3) -#define SLAM_INITIAL_TIMEOUT_INTERVAL (TICKS_PER_SEC) -#define SLAM_BASE_MIN_TIMEOUT (2*TICKS_PER_SEC) -#define SLAM_BASE_TIMEOUT_INTERVAL (4*TICKS_PER_SEC) -#define SLAM_BACKOFF_LIMIT 5 -#define SLAM_MAX_RETRIES 20 - -/*** Packets Formats *** - * Data Packet: - * transaction - * total bytes - * block size - * packet # - * data - * - * Status Request Packet - * transaction - * total bytes - * block size - * - * Status Packet - * received packets - * requested packets - * received packets - * requested packets - * ... - * received packets - * requested packtes - * 0 - */ - -#define MAX_HDR (7 + 7 + 7) /* transaction, total size, block size */ -#define MIN_HDR (1 + 1 + 1) /* transactino, total size, block size */ - -#define MAX_SLAM_REQUEST MAX_HDR -#define MIN_SLAM_REQUEST MIN_HDR - -#define MIN_SLAM_DATA (MIN_HDR + 1) - -static struct slam_nack { - struct iphdr ip; - struct udphdr udp; - unsigned char data[ETH_MAX_MTU - - (sizeof(struct iphdr) + sizeof(struct udphdr))]; -} nack; - -struct slam_state { - unsigned char hdr[MAX_HDR]; - unsigned long hdr_len; - unsigned long block_size; - unsigned long total_bytes; - unsigned long total_packets; - - unsigned long received_packets; - - unsigned char *image; - unsigned char *bitmap; -} state; - - -static void init_slam_state(void) -{ - state.hdr_len = sizeof(state.hdr); - memset(state.hdr, 0, state.hdr_len); - state.block_size = 0; - state.total_packets = 0; - - state.received_packets = 0; - - state.image = 0; - state.bitmap = 0; -} - -struct slam_info { - in_addr server_ip; - in_addr multicast_ip; - in_addr local_ip; - uint16_t server_port; - uint16_t multicast_port; - uint16_t local_port; - int (*fnc)(unsigned char *, unsigned int, unsigned int, int); - int sent_nack; -}; - -#define SLAM_TIMEOUT 0 -#define SLAM_REQUEST 1 -#define SLAM_DATA 2 -static int await_slam(int ival __unused, void *ptr, - unsigned short ptype __unused, struct iphdr *ip, struct udphdr *udp) -{ - struct slam_info *info = ptr; - if (!udp) { - return 0; - } - /* I can receive two kinds of packets here, a multicast data packet, - * or a unicast request for information - */ - /* Check for a data request packet */ - if ((ip->dest.s_addr == arptable[ARP_CLIENT].ipaddr.s_addr) && - (ntohs(udp->dest) == info->local_port) && - (nic.packetlen >= - ETH_HLEN + - sizeof(struct iphdr) + - sizeof(struct udphdr) + - MIN_SLAM_REQUEST)) { - return SLAM_REQUEST; - } - /* Check for a multicast data packet */ - if ((ip->dest.s_addr == info->multicast_ip.s_addr) && - (ntohs(udp->dest) == info->multicast_port) && - (nic.packetlen >= - ETH_HLEN + - sizeof(struct iphdr) + - sizeof(struct udphdr) + - MIN_SLAM_DATA)) { - return SLAM_DATA; - } -#if 0 - printf("#"); - printf("dest: %@ port: %d len: %d\n", - ip->dest.s_addr, ntohs(udp->dest), nic.packetlen); -#endif - return 0; - -} - -static int slam_encode( - unsigned char **ptr, unsigned char *end, unsigned long value) -{ - unsigned char *data = *ptr; - int bytes; - bytes = sizeof(value); - while ((bytes > 0) && ((0xff & (value >> ((bytes -1)<<3))) == 0)) { - bytes--; - } - if (bytes <= 0) { - bytes = 1; - } - if (data + bytes >= end) { - return -1; - } - if ((0xe0 & (value >> ((bytes -1)<<3))) == 0) { - /* packed together */ - *data = (bytes << 5) | (value >> ((bytes -1)<<3)); - } else { - bytes++; - *data = (bytes << 5); - } - bytes--; - data++; - while(bytes) { - *(data++) = 0xff & (value >> ((bytes -1)<<3)); - bytes--; - } - *ptr = data; - return 0; -} - -static int slam_skip(unsigned char **ptr, unsigned char *end) -{ - int bytes; - if (*ptr >= end) { - return -1; - } - bytes = ((**ptr) >> 5) & 7; - if (bytes == 0) { - return -1; - } - if (*ptr + bytes >= end) { - return -1; - } - (*ptr) += bytes; - return 0; - -} - -static unsigned long slam_decode(unsigned char **ptr, unsigned char *end, int *err) -{ - unsigned long value; - unsigned bytes; - if (*ptr >= end) { - *err = -1; - } - bytes = ((**ptr) >> 5) & 7; - if ((bytes == 0) || (bytes > sizeof(unsigned long))) { - *err = -1; - return 0; - } - if ((*ptr) + bytes >= end) { - *err = -1; - } - value = (**ptr) & 0x1f; - bytes--; - (*ptr)++; - while(bytes) { - value <<= 8; - value |= **ptr; - (*ptr)++; - bytes--; - } - return value; -} - - -static long slam_sleep_interval(int exp) -{ - long range; - long divisor; - long interval; - range = SLAM_BASE_TIMEOUT_INTERVAL; - if (exp < 0) { - divisor = RAND_MAX/SLAM_INITIAL_TIMEOUT_INTERVAL; - } else { - if (exp > SLAM_BACKOFF_LIMIT) - exp = SLAM_BACKOFF_LIMIT; - divisor = RAND_MAX/(range << exp); - } - interval = random()/divisor; - if (exp < 0) { - interval += SLAM_INITIAL_MIN_TIMEOUT; - } else { - interval += SLAM_BASE_MIN_TIMEOUT; - } - return interval; -} - - -static unsigned char *reinit_slam_state( - unsigned char *header, unsigned char *end) -{ - unsigned long total_bytes; - unsigned long block_size; - - unsigned long bitmap_len; - unsigned long max_packet_len; - unsigned char *data; - int err; - -#if 0 - printf("reinit\n"); -#endif - data = header; - - state.hdr_len = 0; - err = slam_skip(&data, end); /* transaction id */ - total_bytes = slam_decode(&data, end, &err); - block_size = slam_decode(&data, end, &err); - if (err) { - printf("ALERT: slam size out of range\n"); - return 0; - } - state.block_size = block_size; - state.total_bytes = total_bytes; - state.total_packets = (total_bytes + block_size - 1)/block_size; - state.hdr_len = data - header; - state.received_packets = 0; - - data = state.hdr; - slam_encode(&data, &state.hdr[sizeof(state.hdr)], state.total_packets); - max_packet_len = data - state.hdr; - memcpy(state.hdr, header, state.hdr_len); - -#if 0 - printf("block_size: %ld\n", block_size); - printf("total_bytes: %ld\n", total_bytes); - printf("total_packets: %ld\n", state.total_packets); - printf("hdr_len: %ld\n", state.hdr_len); - printf("max_packet_len: %ld\n", max_packet_len); -#endif - - if (state.block_size > ETH_MAX_MTU - ( - sizeof(struct iphdr) + sizeof(struct udphdr) + - state.hdr_len + max_packet_len)) { - printf("ALERT: slam blocksize to large\n"); - return 0; - } - if (state.bitmap) { - forget(state.bitmap); - } - bitmap_len = (state.total_packets + 1 + 7)/8; - state.bitmap = allot(bitmap_len); - state.image = allot(total_bytes); - if ((unsigned long)state.image < 1024*1024) { - printf("ALERT: slam filesize to large for available memory\n"); - return 0; - } - memset(state.bitmap, 0, bitmap_len); - - return header + state.hdr_len; -} - -static int slam_recv_data(unsigned char *data) -{ - unsigned long packet; - unsigned long data_len; - int err; - struct udphdr *udp; - udp = (struct udphdr *)&nic.packet[ETH_HLEN + sizeof(struct iphdr)]; - err = 0; - packet = slam_decode(&data, &nic.packet[nic.packetlen], &err); - if (err || (packet > state.total_packets)) { - printf("ALERT: Invalid packet number\n"); - return 0; - } - /* Compute the expected data length */ - if (packet != state.total_packets -1) { - data_len = state.block_size; - } else { - data_len = state.total_bytes % state.block_size; - } - /* If the packet size is wrong drop the packet and then continue */ - if (ntohs(udp->len) != (data_len + (data - (unsigned char*)udp))) { - printf("ALERT: udp packet is not the correct size\n"); - return 1; - } - if (nic.packetlen < data_len + (data - nic.packet)) { - printf("ALERT: Ethernet packet shorter than data_len\n"); - return 1; - } - if (data_len > state.block_size) { - data_len = state.block_size; - } - if (((state.bitmap[packet >> 3] >> (packet & 7)) & 1) == 0) { - /* Non duplicate packet */ - state.bitmap[packet >> 3] |= (1 << (packet & 7)); - memcpy(state.image + (packet*state.block_size), data, data_len); - state.received_packets++; - } else { -#ifdef MDEBUG - printf("\n"); -#endif - } - return 1; -} - -static void transmit_nack(unsigned char *ptr, struct slam_info *info) -{ - int nack_len; - /* Ensure the packet is null terminated */ - *ptr++ = 0; - nack_len = ptr - (unsigned char *)&nack; - build_udp_hdr(info->server_ip.s_addr, - info->local_port, info->server_port, 1, nack_len, &nack); - ip_transmit(nack_len, &nack); -#if defined(MDEBUG) && 0 - printf("Sent NACK to %@ bytes: %d have:%ld/%ld\n", - info->server_ip, nack_len, - state.received_packets, state.total_packets); -#endif -} - -static void slam_send_nack(struct slam_info *info) -{ - unsigned char *ptr, *end; - /* Either I timed out or I was explicitly - * asked for a request packet - */ - ptr = &nack.data[0]; - /* Reserve space for the trailling null */ - end = &nack.data[sizeof(nack.data) -1]; - if (!state.bitmap) { - slam_encode(&ptr, end, 0); - slam_encode(&ptr, end, 1); - } - else { - /* Walk the bitmap */ - unsigned long i; - unsigned long len; - unsigned long max; - int value; - int last; - /* Compute the last bit and store an inverted trailer */ - max = state.total_packets; - value = ((state.bitmap[(max -1) >> 3] >> ((max -1) & 7) ) & 1); - value = !value; - state.bitmap[max >> 3] &= ~(1 << (max & 7)); - state.bitmap[max >> 3] |= value << (max & 7); - - len = 0; - last = 1; /* Start with the received packets */ - for(i = 0; i <= max; i++) { - value = (state.bitmap[i>>3] >> (i & 7)) & 1; - if (value == last) { - len++; - } else { - if (slam_encode(&ptr, end, len)) - break; - last = value; - len = 1; - } - } - } - info->sent_nack = 1; - transmit_nack(ptr, info); -} - -static void slam_send_disconnect(struct slam_info *info) -{ - if (info->sent_nack) { - /* A disconnect is a packet with just the null terminator */ - transmit_nack(&nack.data[0], info); - } - info->sent_nack = 0; -} - - -static int proto_slam(struct slam_info *info) -{ - int retry; - long timeout; - - init_slam_state(); - - retry = -1; - rx_qdrain(); - /* Arp for my server */ - if (arptable[ARP_SERVER].ipaddr.s_addr != info->server_ip.s_addr) { - arptable[ARP_SERVER].ipaddr.s_addr = info->server_ip.s_addr; - memset(arptable[ARP_SERVER].node, 0, ETH_ALEN); - } - /* If I'm running over multicast join the multicast group */ - join_group(IGMP_SERVER, info->multicast_ip.s_addr); - for(;;) { - unsigned char *header; - unsigned char *data; - int type; - header = data = 0; - - timeout = slam_sleep_interval(retry); - type = await_reply(await_slam, 0, info, timeout); - /* Compute the timeout for next time */ - if (type == SLAM_TIMEOUT) { - /* If I timeouted recompute the next timeout */ - if (retry++ > SLAM_MAX_RETRIES) { - return 0; - } - } else { - retry = 0; - } - if ((type == SLAM_DATA) || (type == SLAM_REQUEST)) { - /* Check the incomming packet and reinit the data - * structures if necessary. - */ - header = &nic.packet[ETH_HLEN + - sizeof(struct iphdr) + sizeof(struct udphdr)]; - data = header + state.hdr_len; - if (memcmp(state.hdr, header, state.hdr_len) != 0) { - /* Something is fishy reset the transaction */ - data = reinit_slam_state(header, &nic.packet[nic.packetlen]); - if (!data) { - return 0; - } - } - } - if (type == SLAM_DATA) { - if (!slam_recv_data(data)) { - return 0; - } - if (state.received_packets == state.total_packets) { - /* We are done get out */ - break; - } - } - if ((type == SLAM_TIMEOUT) || (type == SLAM_REQUEST)) { - /* Either I timed out or I was explicitly - * asked by a request packet - */ - slam_send_nack(info); - } - } - slam_send_disconnect(info); - - /* Leave the multicast group */ - leave_group(IGMP_SERVER); - /* FIXME don't overwrite myself */ - /* load file to correct location */ - return info->fnc(state.image, 1, state.total_bytes, 1); -} - - -int url_slam(const char *name, int (*fnc)(unsigned char *, unsigned int, unsigned int, int)) -{ - struct slam_info info; - /* Set the defaults */ - info.server_ip.s_addr = arptable[ARP_SERVER].ipaddr.s_addr; - info.server_port = SLAM_PORT; - info.multicast_ip.s_addr = htonl(SLAM_MULTICAST_IP); - info.multicast_port = SLAM_MULTICAST_PORT; - info.local_ip.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr; - info.local_port = SLAM_LOCAL_PORT; - info.fnc = fnc; - info.sent_nack = 0; - /* Now parse the url */ - if (url_port != -1) { - info.server_port = url_port; - } - if (name[0]) { - /* multicast ip */ - name += inet_aton(name, &info.multicast_ip); - if (name[0] == ':') { - name++; - info.multicast_port = strtoul(name, &name, 10); - } - } - if (name[0]) { - printf("\nBad url\n"); - return 0; - } - return proto_slam(&info); -} - -#endif /* DOWNLOAD_PROTO_SLAM */ diff --git a/src/core/proto_tftm.c b/src/core/proto_tftm.c deleted file mode 100644 index 8040fc445..000000000 --- a/src/core/proto_tftm.c +++ /dev/null @@ -1,491 +0,0 @@ -/************************************************************************** -* -* proto_tftm.c -- Etherboot Multicast TFTP -* Written 2003-2003 by Timothy Legge -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -* -* This code is based on the DOWNLOAD_PROTO_TFTM section of -* Etherboot 5.3 core/nic.c and: -* -* Anselm Martin Hoffmeister's previous proto_tftm.c multicast work -* Eric Biederman's proto_slam.c -* -* $Revision$ -* $Author$ -* $Date$ -* -* REVISION HISTORY: -* ================ -* 09-07-2003 timlegge Release Version, Capable of Multicast Booting -* 08-30-2003 timlegge Initial version, Assumes consecutive blocks -* -* Indent Options: indent -kr -i8 -***************************************************************************/ - -#ifdef DOWNLOAD_PROTO_TFTM -#include "etherboot.h" -#include "nic.h" - -//#define TFTM_DEBUG -#ifdef TFTM_DEBUG -#define debug(x) printf x -#else -#define debug(x) -#endif -struct tftm_info { - in_addr server_ip; - in_addr multicast_ip; - in_addr local_ip; - uint16_t server_port; - uint16_t multicast_port; - uint16_t local_port; - int (*fnc) (unsigned char *, unsigned int, unsigned int, int); - int sent_nack; - const char *name; /* Filename */ -}; - -struct tftm_state { - unsigned long block_size; - unsigned long total_bytes; - unsigned long total_packets; - char ismaster; - unsigned long received_packets; - unsigned char *image; - unsigned char *bitmap; - char recvd_oack; -} state; - -#define TFTM_PORT 1758 -#define TFTM_MIN_PACKET 1024 - - -int opt_get_multicast(struct tftp_t *tr, unsigned short *len, - unsigned long *filesize, struct tftm_info *info); - -static int await_tftm(int ival, void *ptr, unsigned short ptype __unused, - struct iphdr *ip, struct udphdr *udp) -{ - struct tftm_info *info = ptr; - - /* Check for Unicast data being received */ - if (ip->dest.s_addr == arptable[ARP_CLIENT].ipaddr.s_addr) { - if (!udp) { - return 0; - } - if (arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr) - return 0; - if (ntohs(udp->dest) != ival) - return 0; - - return 1; /* Unicast Data Received */ - } - - /* Also check for Multicast data being received */ - if ((ip->dest.s_addr == info->multicast_ip.s_addr) && - (ntohs(udp->dest) == info->multicast_port) && - (nic.packetlen >= ETH_HLEN + sizeof(struct iphdr) + - sizeof(struct udphdr))) { - return 1; /* Multicast data received */ - } - return 0; -} - -int proto_tftm(struct tftm_info *info) -{ - int retry = 0; - static unsigned short iport = 2000; - unsigned short oport = 0; - unsigned short len, block = 0, prevblock = 0; - struct tftp_t *tr; - struct tftpreq_t tp; - unsigned long filesize = 0; - - state.image = 0; - state.bitmap = 0; - - rx_qdrain(); - - /* Warning: the following assumes the layout of bootp_t. - But that's fixed by the IP, UDP and BOOTP specs. */ - - /* Send a tftm-request to the server */ - tp.opcode = htons(TFTP_RRQ); /* Const for "\0x0" "\0x1" =^= ReadReQuest */ - len = - sizeof(tp.ip) + sizeof(tp.udp) + sizeof(tp.opcode) + - sprintf((char *) tp.u.rrq, - "%s%coctet%cmulticast%c%cblksize%c%d%ctsize%c", - info->name, 0, 0, 0, 0, 0, TFTM_MIN_PACKET, 0, 0) + 1; - - if (!udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, ++iport, - TFTM_PORT, len, &tp)) - return (0); - - /* loop to listen for packets and to receive the file */ - for (;;) { - long timeout; -#ifdef CONGESTED - timeout = - rfc2131_sleep_interval(block ? TFTP_REXMT : TIMEOUT, - retry); -#else - timeout = rfc2131_sleep_interval(TIMEOUT, retry); -#endif - /* Calls the await_reply function in nic.c which in turn calls - await_tftm (1st parameter) as above */ - if (!await_reply(await_tftm, iport, info, timeout)) { - if (!block && retry++ < MAX_TFTP_RETRIES) { /* maybe initial request was lost */ - if (!udp_transmit - (arptable[ARP_SERVER].ipaddr.s_addr, - ++iport, TFTM_PORT, len, &tp)) - return (0); - continue; - } -#ifdef CONGESTED - if (block && ((retry += TFTP_REXMT) < TFTP_TIMEOUT)) { /* we resend our last ack */ -#ifdef MDEBUG - printf("\n"); -#endif - debug(("Timed out receiving file")); - len = - sizeof(tp.ip) + sizeof(tp.udp) + - sizeof(tp.opcode) + - sprintf((char *) tp.u.rrq, - "%s%coctet%cmulticast%c%cblksize%c%d%ctsize%c", - info->name, 0, 0, 0, 0, 0, - TFTM_MIN_PACKET, 0, 0) + 1; - - udp_transmit - (arptable[ARP_SERVER].ipaddr.s_addr, - ++iport, TFTM_PORT, len, &tp); - continue; - } -#endif - break; /* timeout */ - } - - tr = (struct tftp_t *) &nic.packet[ETH_HLEN]; - - if (tr->opcode == ntohs(TFTP_ERROR)) { - printf("TFTP error %d (%s)\n", - ntohs(tr->u.err.errcode), tr->u.err.errmsg); - break; - } - - if (tr->opcode == ntohs(TFTP_OACK)) { - int i = - opt_get_multicast(tr, &len, &filesize, info); - - if (i == 0 || (i != 7 && !state.recvd_oack)) { /* Multicast unsupported */ - /* Transmit an error message to the server to end the transmission */ - printf - ("TFTM-Server doesn't understand options [blksize tsize multicast]\n"); - tp.opcode = htons(TFTP_ERROR); - tp.u.err.errcode = 8; - /* - * Warning: the following assumes the layout of bootp_t. - * But that's fixed by the IP, UDP and BOOTP specs. - */ - len = - sizeof(tp.ip) + sizeof(tp.udp) + - sizeof(tp.opcode) + - sizeof(tp.u.err.errcode) + - /* - * Normally bad form to omit the format string, but in this case - * the string we are copying from is fixed. sprintf is just being - * used as a strcpy and strlen. - */ - sprintf((char *) tp.u.err.errmsg, - "RFC2090 error") + 1; - udp_transmit(arptable[ARP_SERVER].ipaddr. - s_addr, iport, - ntohs(tr->udp.src), len, &tp); - block = tp.u.ack.block = 0; /* this ensures, that */ - /* the packet does not get */ - /* processed as data! */ - return (0); - } else { - unsigned long bitmap_len; - /* */ - if (!state.recvd_oack) { - - state.total_packets = - 1 + (filesize - - (filesize % - state.block_size)) / - state.block_size; - bitmap_len = - (state.total_packets + 7) / 8; - if (!state.image) { - state.bitmap = - allot(bitmap_len); - state.image = - allot(filesize); - - if ((unsigned long) state. - image < 1024 * 1024) { - printf - ("ALERT: tftp filesize to large for available memory\n"); - return 0; - } - memset(state.bitmap, 0, - bitmap_len); - } - /* If I'm running over multicast join the multicast group */ - join_group(IGMP_SERVER, - info->multicast_ip. - s_addr); - } - state.recvd_oack = 1; - } - - - - } else if (tr->opcode == htons(TFTP_DATA)) { - unsigned long data_len; - unsigned char *data; - struct udphdr *udp; - udp = - (struct udphdr *) &nic.packet[ETH_HLEN + - sizeof(struct - iphdr)]; - len = - ntohs(tr->udp.len) - sizeof(struct udphdr) - 4; - data = - nic.packet + ETH_HLEN + sizeof(struct iphdr) + - sizeof(struct udphdr) + 4; - - if (len > TFTM_MIN_PACKET) /* shouldn't happen */ - continue; /* ignore it */ - - block = ntohs(tp.u.ack.block = tr->u.data.block); - - if (block > state.total_packets) { - printf("ALERT: Invalid packet number\n"); - continue; - } - - /* Compute the expected data length */ - if (block != state.total_packets) { - data_len = state.block_size; - } else { - data_len = filesize % state.block_size; - } - /* If the packet size is wrong drop the packet and then continue */ - if (ntohs(udp->len) != - (data_len + (data - (unsigned char *) udp))) { - printf - ("ALERT: udp packet is not the correct size: %d\n", - block); - continue; - } - if (nic.packetlen < data_len + (data - nic.packet)) { - printf - ("ALERT: Ethernet packet shorter than data_len: %d\n", - block); - continue; - } - - if (data_len > state.block_size) { - data_len = state.block_size; - } - if (((state. - bitmap[block >> 3] >> (block & 7)) & 1) == - 0) { - /* Non duplicate packet */ - state.bitmap[block >> 3] |= - (1 << (block & 7)); - memcpy(state.image + - ((block - 1) * state.block_size), - data, data_len); - state.received_packets++; - } else { - -/* printf("\n"); */ - } - } - - else { /* neither TFTP_OACK, TFTP_DATA nor TFTP_ERROR */ - break; - } - - if (state.received_packets <= state.total_packets) { - unsigned long b; - unsigned long len; - unsigned long max; - int value; - int last; - - /* Compute the last bit and store an inverted trailer */ - max = state.total_packets + 1; - value = - ((state. - bitmap[(max - 1) >> 3] >> ((max - - 1) & 7)) & 1); - value = !value; - state.bitmap[max >> 3] &= ~(1 << (max & 7)); - state.bitmap[max >> 3] |= value << (max & 7); - - len = 0; - last = 0; /* Start with the received packets */ - for (b = 1; b <= max; b++) { - value = - (state.bitmap[b >> 3] >> (b & 7)) & 1; - - if (value == 0) { - tp.u.ack.block = htons(b - 1); /* Acknowledge the previous block */ - break; - } - } - } - if (state.ismaster) { - tp.opcode = htons(TFTP_ACK); - oport = ntohs(tr->udp.src); - udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, iport, oport, TFTP_MIN_PACKET, &tp); /* ack */ - } - if (state.received_packets == state.total_packets) { - /* If the client is finished and not the master, - * ack the last packet */ - if (!state.ismaster) { - tp.opcode = htons(TFTP_ACK); - /* Ack Last packet to end xfer */ - tp.u.ack.block = htons(state.total_packets); - oport = ntohs(tr->udp.src); - udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, iport, oport, TFTP_MIN_PACKET, &tp); /* ack */ - } - /* We are done get out */ - forget(state.bitmap); - break; - } - - if ((unsigned short) (block - prevblock) != 1) { - /* Retransmission or OACK, don't process via callback - * and don't change the value of prevblock. */ - continue; - } - - prevblock = block; - retry = 0; /* It's the right place to zero the timer? */ - - } - /* Leave the multicast group */ - leave_group(IGMP_SERVER); - return info->fnc(state.image, 1, filesize, 1); -} - -int url_tftm(const char *name, - int (*fnc) (unsigned char *, unsigned int, unsigned int, int)) -{ - - int ret; - struct tftm_info info; - - /* Set the defaults */ - info.server_ip.s_addr = arptable[ARP_SERVER].ipaddr.s_addr; - info.server_port = TFTM_PORT; - info.local_ip.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr; - info.local_port = TFTM_PORT; /* Does not matter. So take tftm port too. */ - info.multicast_ip.s_addr = info.local_ip.s_addr; - info.multicast_port = TFTM_PORT; - info.fnc = fnc; - state.ismaster = 0; - info.name = name; - - state.block_size = 0; - state.total_bytes = 0; - state.total_packets = 0; - state.received_packets = 0; - state.image = 0; - state.bitmap = 0; - state.recvd_oack = 0; - - if (name[0] != '/') { - /* server ip given, so use it */ - name += inet_aton(info.name, &info.server_ip); - /* No way to specify another port for now */ - } - if (name[0] != '/') { - printf("Bad tftm-URI: [%s]\n", info.name); - return 0; - } - - ret = proto_tftm(&info); - - return ret; -} - -/****************************** -* Parse the multicast options -*******************************/ -int opt_get_multicast(struct tftp_t *tr, unsigned short *len, - unsigned long *filesize, struct tftm_info *info) -{ - const char *p = tr->u.oack.data, *e = 0; - int i = 0; - *len = ntohs(tr->udp.len) - sizeof(struct udphdr) - 2; - if (*len > TFTM_MIN_PACKET) - return -1; - e = p + *len; - - while (*p != '\0' && p < e) { - if (!strcasecmp("tsize", p)) { - p += 6; - if ((*filesize = strtoul(p, &p, 10)) > 0) - i |= 4; - debug(("\n")); - debug(("tsize=%d\n", *filesize)); - while (p < e && *p) - p++; - if (p < e) - p++; - } else if (!strcasecmp("blksize", p)) { - i |= 2; - p += 8; - state.block_size = strtoul(p, &p, 10); - if (state.block_size != TFTM_MIN_PACKET) { - printf - ("TFTM-Server rejected required transfer blocksize %d\n", - TFTM_MIN_PACKET); - return 0; - } - debug(("blksize=%d\n", state.block_size)); - while (p < e && *p) - p++; - if (p < e) - p++; - } else if (!strncmp(p, "multicast", 10)) { - i |= 1; - p += 10; - debug(("multicast options: %s\n", p)); - p += 1 + inet_aton(p, &info->multicast_ip); - debug(("multicast ip = %@\n", info->multicast_ip)); - info->multicast_port = strtoul(p, &p, 10); - ++p; - debug(("multicast port = %d\n", - info->multicast_port)); - state.ismaster = (*p == '1' ? 1 : 0); - debug(("multicast ismaster = %d\n", - state.ismaster)); - while (p < e && *p) - p++; - if (p < e) - p++; - } - } - if (p > e) - return 0; - return i; -} -#endif /* DOWNLOAD_PROTO_TFTP */ diff --git a/src/proto/http.c b/src/proto/http.c new file mode 100644 index 000000000..f2dc9dd18 --- /dev/null +++ b/src/proto/http.c @@ -0,0 +1,206 @@ +#include "etherboot.h" +#include "http.h" + +#ifdef DOWNLOAD_PROTO_HTTP + +/* The block size is currently chosen to be 512 bytes. This means, we can + allocate the receive buffer on the stack, but it results in a noticeable + performance penalty. + This is what needs to be done in order to increase the block size: + - size negotiation needs to be implemented in TCP + - the buffer needs to be allocated on the heap + - path MTU discovery needs to be implemented +*/ /***/ /* FIXME */ +#define BLOCKSIZE TFTP_DEFAULTSIZE_PACKET + +/************************************************************************** +SEND_TCP_CALLBACK - Send data using TCP +**************************************************************************/ +struct send_recv_state { + int (*fnc)(unsigned char *data, int block, int len, int eof); + char *send_buffer; + char *recv_buffer; + int send_length; + int recv_length; + int bytes_sent; + int block; + int bytes_received; + enum { RESULT_CODE, HEADER, DATA, ERROR, MOVED } recv_state; + int rc; + char location[MAX_URL+1]; +}; + +static int send_tcp_request(int length, void *buffer, void *ptr) { + struct send_recv_state *state = (struct send_recv_state *)ptr; + + if (length > state->send_length - state->bytes_sent) + length = state->send_length - state->bytes_sent; + memcpy(buffer, state->send_buffer + state->bytes_sent, length); + state->bytes_sent += length; + return (length); +} + +/************************************************************************** +RECV_TCP_CALLBACK - Receive data using TCP +**************************************************************************/ +static int recv_tcp_request(int length, const void *buffer, void *ptr) { + struct send_recv_state *state = (struct send_recv_state *)ptr; + + /* Assume that the lines in an HTTP header do not straddle a packet */ + /* boundary. This is probably a reasonable assumption */ + if (state->recv_state == RESULT_CODE) { + while (length > 0) { + /* Find HTTP result code */ + if (*(const char *)buffer == ' ') { + const char *ptr = ((const char *)buffer) + 1; + int rc = strtoul(ptr, &ptr, 10); + if (ptr >= (const char *)buffer + length) { + state->recv_state = ERROR; + return 0; + } + state->rc = rc; + state->recv_state = HEADER; + goto header; + } + ++(const char *)buffer; + length--; + } + state->recv_state = ERROR; + return 0; + } + if (state->recv_state == HEADER) { + header: while (length > 0) { + /* Check for HTTP redirect */ + if (state->rc >= 300 && state->rc < 400 && + !memcmp(buffer, "Location: ", 10)) { + char *ptr = state->location; + int i; + memcpy(ptr, buffer + 10, MAX_URL); + for (i = 0; i < MAX_URL && *ptr > ' '; + i++, ptr++); + *ptr = '\000'; + state->recv_state = MOVED; + return 1; + } + /* Find beginning of line */ + while (length > 0) { + length--; + if (*((const char *)buffer)++ == '\n') + break; + } + /* Check for end of header */ + if (length >= 2 && !memcmp(buffer, "\r\n", 2)) { + state->recv_state = DATA; + buffer += 2; + length -= 2; + break; + } + } + } + if (state->recv_state == DATA) { + state->bytes_received += length; + while (length > 0) { + int copy_length = BLOCKSIZE - state->recv_length; + if (copy_length > length) + copy_length = length; + memcpy(state->recv_buffer + state->recv_length, + buffer, copy_length); + if ((state->recv_length += copy_length) == BLOCKSIZE) { + if (!state->fnc(state->recv_buffer, + ++state->block, BLOCKSIZE, 0)) + return 0; + state->recv_length = 0; + } + length -= copy_length; + buffer += copy_length; + } + } + return 1; +} + +/************************************************************************** +HTTP_GET - Get data using HTTP +**************************************************************************/ +int http(const char *url, + int (*fnc)(unsigned char *, unsigned int, unsigned int, int)) { + static const char GET[] = "GET /%s HTTP/1.0\r\n\r\n"; + static char recv_buffer[BLOCKSIZE]; + in_addr destip; + int port; + int length; + struct send_recv_state state; + + state.fnc = fnc; + state.rc = -1; + state.block = 0; + state.recv_buffer = recv_buffer; + length = strlen(url); + if (length <= MAX_URL) { + memcpy(state.location, url, length+1); + destip = arptable[ARP_SERVER].ipaddr; + port = url_port; + if (port == -1) + port = 80; + goto first_time; + + do { + state.rc = -1; + state.block = 0; + url = state.location; + if (memcmp("http://", url, 7)) + break; + url += 7; + length = inet_aton(url, &destip); + if (!length) { + /* As we do not have support for DNS, assume*/ + /* that HTTP redirects always point to the */ + /* same machine */ + if (state.recv_state == MOVED) { + while (*url && + *url != ':' && *url != '/') url++; + } else { + break; + } + } + if (*(url += length) == ':') { + port = strtoul(url, &url, 10); + } else { + port = 80; + } + if (!*url) + url = "/"; + if (*url != '/') + break; + url++; + + first_time: + length = strlen(url); + state.send_length = sizeof(GET) - 3 + length; + + { char buf[state.send_length + 1]; + sprintf(state.send_buffer = buf, GET, url); + state.bytes_sent = 0; + + state.bytes_received = 0; + state.recv_state = RESULT_CODE; + + state.recv_length = 0; + tcp_transaction(destip.s_addr, 80, &state, + send_tcp_request, recv_tcp_request); + } + } while (state.recv_state == MOVED); + } else { + memcpy(state.location, url, MAX_URL); + state.location[MAX_URL] = '\000'; + } + + if (state.rc == 200) { + return fnc(recv_buffer, ++state.block, state.recv_length, 1); + } else { + printf("Failed to download %s (rc = %d)\n", + state.location, state.rc); + return 0; + } +} + +#endif /* DOWNLOAD_PROTO_HTTP */ diff --git a/src/proto/nfs.c b/src/proto/nfs.c new file mode 100644 index 000000000..cbda6ab7b --- /dev/null +++ b/src/proto/nfs.c @@ -0,0 +1,610 @@ +#ifdef DOWNLOAD_PROTO_NFS + +#include "etherboot.h" +#include "nic.h" + +/* NOTE: the NFS code is heavily inspired by the NetBSD netboot code (read: + * large portions are copied verbatim) as distributed in OSKit 0.97. A few + * changes were necessary to adapt the code to Etherboot and to fix several + * inconsistencies. Also the RPC message preparation is done "by hand" to + * avoid adding netsprintf() which I find hard to understand and use. */ + +/* NOTE 2: Etherboot does not care about things beyond the kernel image, so + * it loads the kernel image off the boot server (ARP_SERVER) and does not + * access the client root disk (root-path in dhcpd.conf), which would use + * ARP_ROOTSERVER. The root disk is something the operating system we are + * about to load needs to use. This is different from the OSKit 0.97 logic. */ + +/* NOTE 3: Symlink handling introduced by Anselm M Hoffmeister, 2003-July-14 + * If a symlink is encountered, it is followed as far as possible (recursion + * possible, maximum 16 steps). There is no clearing of ".."'s inside the + * path, so please DON'T DO THAT. thx. */ + +#define START_OPORT 700 /* mountd usually insists on secure ports */ +#define OPORT_SWEEP 200 /* make sure we don't leave secure range */ + +static int oport = START_OPORT; +static int mount_port = -1; +static int nfs_port = -1; +static int fs_mounted = 0; +static unsigned long rpc_id; + +/************************************************************************** +RPC_INIT - set up the ID counter to something fairly random +**************************************************************************/ +void rpc_init(void) +{ + unsigned long t; + + t = currticks(); + rpc_id = t ^ (t << 8) ^ (t << 16); +} + + +/************************************************************************** +RPC_PRINTERROR - Print a low level RPC error message +**************************************************************************/ +static void rpc_printerror(struct rpc_t *rpc) +{ + if (rpc->u.reply.rstatus || rpc->u.reply.verifier || + rpc->u.reply.astatus) { + /* rpc_printerror() is called for any RPC related error, + * suppress output if no low level RPC error happened. */ + printf("RPC error: (%d,%d,%d)\n", ntohl(rpc->u.reply.rstatus), + ntohl(rpc->u.reply.verifier), + ntohl(rpc->u.reply.astatus)); + } +} + +/************************************************************************** +AWAIT_RPC - Wait for an rpc packet +**************************************************************************/ +static int await_rpc(int ival, void *ptr, + unsigned short ptype, struct iphdr *ip, struct udphdr *udp) +{ + struct rpc_t *rpc; + if (!udp) + return 0; + if (arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr) + return 0; + if (ntohs(udp->dest) != ival) + return 0; + if (nic.packetlen < ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr) + 8) + return 0; + rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; + if (*(unsigned long *)ptr != ntohl(rpc->u.reply.id)) + return 0; + if (MSG_REPLY != ntohl(rpc->u.reply.type)) + return 0; + return 1; +} + +/************************************************************************** +RPC_LOOKUP - Lookup RPC Port numbers +**************************************************************************/ +static int rpc_lookup(int addr, int prog, int ver, int sport) +{ + struct rpc_t buf, *rpc; + unsigned long id; + int retries; + long *p; + + id = rpc_id++; + buf.u.call.id = htonl(id); + buf.u.call.type = htonl(MSG_CALL); + buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ + buf.u.call.prog = htonl(PROG_PORTMAP); + buf.u.call.vers = htonl(2); /* portmapper is version 2 */ + buf.u.call.proc = htonl(PORTMAP_GETPORT); + p = (long *)buf.u.call.data; + *p++ = 0; *p++ = 0; /* auth credential */ + *p++ = 0; *p++ = 0; /* auth verifier */ + *p++ = htonl(prog); + *p++ = htonl(ver); + *p++ = htonl(IP_UDP); + *p++ = 0; + for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { + long timeout; + udp_transmit(arptable[addr].ipaddr.s_addr, sport, SUNRPC_PORT, + (char *)p - (char *)&buf, &buf); + timeout = rfc2131_sleep_interval(TIMEOUT, retries); + if (await_reply(await_rpc, sport, &id, timeout)) { + rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; + if (rpc->u.reply.rstatus || rpc->u.reply.verifier || + rpc->u.reply.astatus) { + rpc_printerror(rpc); + return -1; + } else { + return ntohl(rpc->u.reply.data[0]); + } + } + } + return -1; +} + +/************************************************************************** +RPC_ADD_CREDENTIALS - Add RPC authentication/verifier entries +**************************************************************************/ +static long *rpc_add_credentials(long *p) +{ + int hl; + + /* Here's the executive summary on authentication requirements of the + * various NFS server implementations: Linux accepts both AUTH_NONE + * and AUTH_UNIX authentication (also accepts an empty hostname field + * in the AUTH_UNIX scheme). *BSD refuses AUTH_NONE, but accepts + * AUTH_UNIX (also accepts an empty hostname field in the AUTH_UNIX + * scheme). To be safe, use AUTH_UNIX and pass the hostname if we have + * it (if the BOOTP/DHCP reply didn't give one, just use an empty + * hostname). */ + + hl = (hostnamelen + 3) & ~3; + + /* Provide an AUTH_UNIX credential. */ + *p++ = htonl(1); /* AUTH_UNIX */ + *p++ = htonl(hl+20); /* auth length */ + *p++ = htonl(0); /* stamp */ + *p++ = htonl(hostnamelen); /* hostname string */ + if (hostnamelen & 3) { + *(p + hostnamelen / 4) = 0; /* add zero padding */ + } + memcpy(p, hostname, hostnamelen); + p += hl / 4; + *p++ = 0; /* uid */ + *p++ = 0; /* gid */ + *p++ = 0; /* auxiliary gid list */ + + /* Provide an AUTH_NONE verifier. */ + *p++ = 0; /* AUTH_NONE */ + *p++ = 0; /* auth length */ + + return p; +} + +/************************************************************************** +NFS_PRINTERROR - Print a NFS error message +**************************************************************************/ +static void nfs_printerror(int err) +{ + switch (-err) { + case NFSERR_PERM: + printf("Not owner\n"); + break; + case NFSERR_NOENT: + printf("No such file or directory\n"); + break; + case NFSERR_ACCES: + printf("Permission denied\n"); + break; + case NFSERR_ISDIR: + printf("Directory given where filename expected\n"); + break; + case NFSERR_INVAL: + printf("Invalid filehandle\n"); + break; // INVAL is not defined in NFSv2, some NFS-servers + // seem to use it in answers to v2 nevertheless. + case 9998: + printf("low-level RPC failure (parameter decoding problem?)\n"); + break; + case 9999: + printf("low-level RPC failure (authentication problem?)\n"); + break; + default: + printf("Unknown NFS error %d\n", -err); + } +} + +/************************************************************************** +NFS_MOUNT - Mount an NFS Filesystem +**************************************************************************/ +static int nfs_mount(int server, int port, char *path, char *fh, int sport) +{ + struct rpc_t buf, *rpc; + unsigned long id; + int retries; + long *p; + int pathlen = strlen(path); + + id = rpc_id++; + buf.u.call.id = htonl(id); + buf.u.call.type = htonl(MSG_CALL); + buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ + buf.u.call.prog = htonl(PROG_MOUNT); + buf.u.call.vers = htonl(1); /* mountd is version 1 */ + buf.u.call.proc = htonl(MOUNT_ADDENTRY); + p = rpc_add_credentials((long *)buf.u.call.data); + *p++ = htonl(pathlen); + if (pathlen & 3) { + *(p + pathlen / 4) = 0; /* add zero padding */ + } + memcpy(p, path, pathlen); + p += (pathlen + 3) / 4; + for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { + long timeout; + udp_transmit(arptable[server].ipaddr.s_addr, sport, port, + (char *)p - (char *)&buf, &buf); + timeout = rfc2131_sleep_interval(TIMEOUT, retries); + if (await_reply(await_rpc, sport, &id, timeout)) { + rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; + if (rpc->u.reply.rstatus || rpc->u.reply.verifier || + rpc->u.reply.astatus || rpc->u.reply.data[0]) { + rpc_printerror(rpc); + if (rpc->u.reply.rstatus) { + /* RPC failed, no verifier, data[0] */ + return -9999; + } + if (rpc->u.reply.astatus) { + /* RPC couldn't decode parameters */ + return -9998; + } + return -ntohl(rpc->u.reply.data[0]); + } else { + fs_mounted = 1; + memcpy(fh, rpc->u.reply.data + 1, NFS_FHSIZE); + return 0; + } + } + } + return -1; +} + +/************************************************************************** +NFS_UMOUNTALL - Unmount all our NFS Filesystems on the Server +**************************************************************************/ +void nfs_umountall(int server) +{ + struct rpc_t buf, *rpc; + unsigned long id; + int retries; + long *p; + + if (!arptable[server].ipaddr.s_addr) { + /* Haven't sent a single UDP packet to this server */ + return; + } + if ((mount_port == -1) || (!fs_mounted)) { + /* Nothing mounted, nothing to umount */ + return; + } + id = rpc_id++; + buf.u.call.id = htonl(id); + buf.u.call.type = htonl(MSG_CALL); + buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ + buf.u.call.prog = htonl(PROG_MOUNT); + buf.u.call.vers = htonl(1); /* mountd is version 1 */ + buf.u.call.proc = htonl(MOUNT_UMOUNTALL); + p = rpc_add_credentials((long *)buf.u.call.data); + for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { + long timeout = rfc2131_sleep_interval(TIMEOUT, retries); + udp_transmit(arptable[server].ipaddr.s_addr, oport, mount_port, + (char *)p - (char *)&buf, &buf); + if (await_reply(await_rpc, oport, &id, timeout)) { + rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; + if (rpc->u.reply.rstatus || rpc->u.reply.verifier || + rpc->u.reply.astatus) { + rpc_printerror(rpc); + } + fs_mounted = 0; + return; + } + } +} +/*************************************************************************** + * NFS_READLINK (AH 2003-07-14) + * This procedure is called when read of the first block fails - + * this probably happens when it's a directory or a symlink + * In case of successful readlink(), the dirname is manipulated, + * so that inside the nfs() function a recursion can be done. + **************************************************************************/ +static int nfs_readlink(int server, int port, char *fh, char *path, char *nfh, + int sport) +{ + struct rpc_t buf, *rpc; + unsigned long id; + long *p; + int retries; + int pathlen = strlen(path); + + id = rpc_id++; + buf.u.call.id = htonl(id); + buf.u.call.type = htonl(MSG_CALL); + buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ + buf.u.call.prog = htonl(PROG_NFS); + buf.u.call.vers = htonl(2); /* nfsd is version 2 */ + buf.u.call.proc = htonl(NFS_READLINK); + p = rpc_add_credentials((long *)buf.u.call.data); + memcpy(p, nfh, NFS_FHSIZE); + p += (NFS_FHSIZE / 4); + for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { + long timeout = rfc2131_sleep_interval(TIMEOUT, retries); + udp_transmit(arptable[server].ipaddr.s_addr, sport, port, + (char *)p - (char *)&buf, &buf); + if (await_reply(await_rpc, sport, &id, timeout)) { + rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; + if (rpc->u.reply.rstatus || rpc->u.reply.verifier || + rpc->u.reply.astatus || rpc->u.reply.data[0]) { + rpc_printerror(rpc); + if (rpc->u.reply.rstatus) { + /* RPC failed, no verifier, data[0] */ + return -9999; + } + if (rpc->u.reply.astatus) { + /* RPC couldn't decode parameters */ + return -9998; + } + return -ntohl(rpc->u.reply.data[0]); + } else { + // It *is* a link. + // If it's a relative link, append everything to dirname, filename TOO! + retries = strlen ( (char *)(&(rpc->u.reply.data[2]) )); + if ( *((char *)(&(rpc->u.reply.data[2]))) != '/' ) { + path[pathlen++] = '/'; + while ( ( retries + pathlen ) > 298 ) { + retries--; + } + if ( retries > 0 ) { + memcpy(path + pathlen, &(rpc->u.reply.data[2]), retries + 1); + } else { retries = 0; } + path[pathlen + retries] = 0; + } else { + // Else make it the only path. + if ( retries > 298 ) { retries = 298; } + memcpy ( path, &(rpc->u.reply.data[2]), retries + 1 ); + path[retries] = 0; + } + return 0; + } + } + } + return -1; +} +/************************************************************************** +NFS_LOOKUP - Lookup Pathname +**************************************************************************/ +static int nfs_lookup(int server, int port, char *fh, char *path, char *nfh, + int sport) +{ + struct rpc_t buf, *rpc; + unsigned long id; + long *p; + int retries; + int pathlen = strlen(path); + + id = rpc_id++; + buf.u.call.id = htonl(id); + buf.u.call.type = htonl(MSG_CALL); + buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ + buf.u.call.prog = htonl(PROG_NFS); + buf.u.call.vers = htonl(2); /* nfsd is version 2 */ + buf.u.call.proc = htonl(NFS_LOOKUP); + p = rpc_add_credentials((long *)buf.u.call.data); + memcpy(p, fh, NFS_FHSIZE); + p += (NFS_FHSIZE / 4); + *p++ = htonl(pathlen); + if (pathlen & 3) { + *(p + pathlen / 4) = 0; /* add zero padding */ + } + memcpy(p, path, pathlen); + p += (pathlen + 3) / 4; + for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { + long timeout = rfc2131_sleep_interval(TIMEOUT, retries); + udp_transmit(arptable[server].ipaddr.s_addr, sport, port, + (char *)p - (char *)&buf, &buf); + if (await_reply(await_rpc, sport, &id, timeout)) { + rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; + if (rpc->u.reply.rstatus || rpc->u.reply.verifier || + rpc->u.reply.astatus || rpc->u.reply.data[0]) { + rpc_printerror(rpc); + if (rpc->u.reply.rstatus) { + /* RPC failed, no verifier, data[0] */ + return -9999; + } + if (rpc->u.reply.astatus) { + /* RPC couldn't decode parameters */ + return -9998; + } + return -ntohl(rpc->u.reply.data[0]); + } else { + memcpy(nfh, rpc->u.reply.data + 1, NFS_FHSIZE); + return 0; + } + } + } + return -1; +} + +/************************************************************************** +NFS_READ - Read File on NFS Server +**************************************************************************/ +static int nfs_read(int server, int port, char *fh, int offset, int len, + int sport) +{ + struct rpc_t buf, *rpc; + unsigned long id; + int retries; + long *p; + + static int tokens=0; + /* + * Try to implement something similar to a window protocol in + * terms of response to losses. On successful receive, increment + * the number of tokens by 1 (cap at 256). On failure, halve it. + * When the number of tokens is >= 2, use a very short timeout. + */ + + id = rpc_id++; + buf.u.call.id = htonl(id); + buf.u.call.type = htonl(MSG_CALL); + buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ + buf.u.call.prog = htonl(PROG_NFS); + buf.u.call.vers = htonl(2); /* nfsd is version 2 */ + buf.u.call.proc = htonl(NFS_READ); + p = rpc_add_credentials((long *)buf.u.call.data); + memcpy(p, fh, NFS_FHSIZE); + p += NFS_FHSIZE / 4; + *p++ = htonl(offset); + *p++ = htonl(len); + *p++ = 0; /* unused parameter */ + for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { + long timeout = rfc2131_sleep_interval(TIMEOUT, retries); + if (tokens >= 2) + timeout = TICKS_PER_SEC/2; + + udp_transmit(arptable[server].ipaddr.s_addr, sport, port, + (char *)p - (char *)&buf, &buf); + if (await_reply(await_rpc, sport, &id, timeout)) { + if (tokens < 256) + tokens++; + rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; + if (rpc->u.reply.rstatus || rpc->u.reply.verifier || + rpc->u.reply.astatus || rpc->u.reply.data[0]) { + rpc_printerror(rpc); + if (rpc->u.reply.rstatus) { + /* RPC failed, no verifier, data[0] */ + return -9999; + } + if (rpc->u.reply.astatus) { + /* RPC couldn't decode parameters */ + return -9998; + } + return -ntohl(rpc->u.reply.data[0]); + } else { + return 0; + } + } else + tokens >>= 1; + } + return -1; +} + +/************************************************************************** +NFS - Download extended BOOTP data, or kernel image from NFS server +**************************************************************************/ +int nfs(const char *name, int (*fnc)(unsigned char *, unsigned int, unsigned int, int)) +{ + static int recursion = 0; + int sport; + int err, namelen = strlen(name); + char dirname[300], *fname; + char dirfh[NFS_FHSIZE]; /* file handle of directory */ + char filefh[NFS_FHSIZE]; /* file handle of kernel image */ + unsigned int block; + int rlen, size, offs, len; + struct rpc_t *rpc; + + rx_qdrain(); + + sport = oport++; + if (oport > START_OPORT+OPORT_SWEEP) { + oport = START_OPORT; + } + if ( name != dirname ) { + memcpy(dirname, name, namelen + 1); + } + recursion = 0; +nfssymlink: + if ( recursion > NFS_MAXLINKDEPTH ) { + printf ( "\nRecursion: More than %d symlinks followed. Abort.\n", NFS_MAXLINKDEPTH ); + return 0; + } + recursion++; + fname = dirname + (namelen - 1); + while (fname >= dirname) { + if (*fname == '/') { + *fname = '\0'; + fname++; + break; + } + fname--; + } + if (fname < dirname) { + printf("can't parse file name %s\n", name); + return 0; + } + + if (mount_port == -1) { + mount_port = rpc_lookup(ARP_SERVER, PROG_MOUNT, 1, sport); + } + if (nfs_port == -1) { + nfs_port = rpc_lookup(ARP_SERVER, PROG_NFS, 2, sport); + } + if (nfs_port == -1 || mount_port == -1) { + printf("can't get nfs/mount ports from portmapper\n"); + return 0; + } + + + err = nfs_mount(ARP_SERVER, mount_port, dirname, dirfh, sport); + if (err) { + printf("mounting %s: ", dirname); + nfs_printerror(err); + /* just to be sure... */ + nfs_umountall(ARP_SERVER); + return 0; + } + + err = nfs_lookup(ARP_SERVER, nfs_port, dirfh, fname, filefh, sport); + if (err) { + printf("looking up %s: ", fname); + nfs_printerror(err); + nfs_umountall(ARP_SERVER); + return 0; + } + + offs = 0; + block = 1; /* blocks are numbered starting from 1 */ + size = -1; /* will be set properly with the first reply */ + len = NFS_READ_SIZE; /* first request is always full size */ + do { + err = nfs_read(ARP_SERVER, nfs_port, filefh, offs, len, sport); + if ((err <= -NFSERR_ISDIR)&&(err >= -NFSERR_INVAL) && (offs == 0)) { + // An error occured. NFS servers tend to sending + // errors 21 / 22 when symlink instead of real file + // is requested. So check if it's a symlink! + block = nfs_readlink(ARP_SERVER, nfs_port, dirfh, dirname, + filefh, sport); + if ( 0 == block ) { + printf("\nLoading symlink:%s ..",dirname); + goto nfssymlink; + } + nfs_printerror(err); + nfs_umountall(ARP_SERVER); + return 0; + } + if (err) { + printf("reading at offset %d: ", offs); + nfs_printerror(err); + nfs_umountall(ARP_SERVER); + return 0; + } + + rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; + + /* size must be found out early to allow EOF detection */ + if (size == -1) { + size = ntohl(rpc->u.reply.data[6]); + } + rlen = ntohl(rpc->u.reply.data[18]); + if (rlen > len) { + rlen = len; /* shouldn't happen... */ + } + + err = fnc((char *)&rpc->u.reply.data[19], block, rlen, + (offs+rlen == size)); + if (err <= 0) { + nfs_umountall(ARP_SERVER); + return err; + } + + block++; + offs += rlen; + /* last request is done with matching requested read size */ + if (size-offs < NFS_READ_SIZE) { + len = size-offs; + } + } while (len != 0); + /* len == 0 means that all the file has been read */ + return 1; +} + +#endif /* DOWNLOAD_PROTO_NFS */ diff --git a/src/proto/slam.c b/src/proto/slam.c new file mode 100644 index 000000000..135384a9c --- /dev/null +++ b/src/proto/slam.c @@ -0,0 +1,541 @@ +#ifdef DOWNLOAD_PROTO_SLAM +#include "etherboot.h" +#include "nic.h" + +#define SLAM_PORT 10000 +#define SLAM_MULTICAST_IP ((239<<24)|(255<<16)|(1<<8)|(1<<0)) +#define SLAM_MULTICAST_PORT 10000 +#define SLAM_LOCAL_PORT 10000 + +/* Set the timeout intervals to at least 1 second so + * on a 100Mbit ethernet can receive 10000 packets + * in one second. + * + * The only case that is likely to trigger all of the nodes + * firing a nack packet is a slow server. The odds of this + * happening could be reduced being slightly smarter and utilizing + * the multicast channels for nacks. But that only improves the odds + * it doesn't improve the worst case. So unless this proves to be + * a common case having the control data going unicast should increase + * the odds of the data not being dropped. + * + * When doing exponential backoff we increase just the timeout + * interval and not the base to optimize for throughput. This is only + * expected to happen when the server is down. So having some nodes + * pinging immediately should get the transmission restarted quickly after a + * server restart. The host nic won't be to baddly swamped because of + * the random distribution of the nodes. + * + */ +#define SLAM_INITIAL_MIN_TIMEOUT (TICKS_PER_SEC/3) +#define SLAM_INITIAL_TIMEOUT_INTERVAL (TICKS_PER_SEC) +#define SLAM_BASE_MIN_TIMEOUT (2*TICKS_PER_SEC) +#define SLAM_BASE_TIMEOUT_INTERVAL (4*TICKS_PER_SEC) +#define SLAM_BACKOFF_LIMIT 5 +#define SLAM_MAX_RETRIES 20 + +/*** Packets Formats *** + * Data Packet: + * transaction + * total bytes + * block size + * packet # + * data + * + * Status Request Packet + * transaction + * total bytes + * block size + * + * Status Packet + * received packets + * requested packets + * received packets + * requested packets + * ... + * received packets + * requested packtes + * 0 + */ + +#define MAX_HDR (7 + 7 + 7) /* transaction, total size, block size */ +#define MIN_HDR (1 + 1 + 1) /* transactino, total size, block size */ + +#define MAX_SLAM_REQUEST MAX_HDR +#define MIN_SLAM_REQUEST MIN_HDR + +#define MIN_SLAM_DATA (MIN_HDR + 1) + +static struct slam_nack { + struct iphdr ip; + struct udphdr udp; + unsigned char data[ETH_MAX_MTU - + (sizeof(struct iphdr) + sizeof(struct udphdr))]; +} nack; + +struct slam_state { + unsigned char hdr[MAX_HDR]; + unsigned long hdr_len; + unsigned long block_size; + unsigned long total_bytes; + unsigned long total_packets; + + unsigned long received_packets; + + unsigned char *image; + unsigned char *bitmap; +} state; + + +static void init_slam_state(void) +{ + state.hdr_len = sizeof(state.hdr); + memset(state.hdr, 0, state.hdr_len); + state.block_size = 0; + state.total_packets = 0; + + state.received_packets = 0; + + state.image = 0; + state.bitmap = 0; +} + +struct slam_info { + in_addr server_ip; + in_addr multicast_ip; + in_addr local_ip; + uint16_t server_port; + uint16_t multicast_port; + uint16_t local_port; + int (*fnc)(unsigned char *, unsigned int, unsigned int, int); + int sent_nack; +}; + +#define SLAM_TIMEOUT 0 +#define SLAM_REQUEST 1 +#define SLAM_DATA 2 +static int await_slam(int ival __unused, void *ptr, + unsigned short ptype __unused, struct iphdr *ip, struct udphdr *udp) +{ + struct slam_info *info = ptr; + if (!udp) { + return 0; + } + /* I can receive two kinds of packets here, a multicast data packet, + * or a unicast request for information + */ + /* Check for a data request packet */ + if ((ip->dest.s_addr == arptable[ARP_CLIENT].ipaddr.s_addr) && + (ntohs(udp->dest) == info->local_port) && + (nic.packetlen >= + ETH_HLEN + + sizeof(struct iphdr) + + sizeof(struct udphdr) + + MIN_SLAM_REQUEST)) { + return SLAM_REQUEST; + } + /* Check for a multicast data packet */ + if ((ip->dest.s_addr == info->multicast_ip.s_addr) && + (ntohs(udp->dest) == info->multicast_port) && + (nic.packetlen >= + ETH_HLEN + + sizeof(struct iphdr) + + sizeof(struct udphdr) + + MIN_SLAM_DATA)) { + return SLAM_DATA; + } +#if 0 + printf("#"); + printf("dest: %@ port: %d len: %d\n", + ip->dest.s_addr, ntohs(udp->dest), nic.packetlen); +#endif + return 0; + +} + +static int slam_encode( + unsigned char **ptr, unsigned char *end, unsigned long value) +{ + unsigned char *data = *ptr; + int bytes; + bytes = sizeof(value); + while ((bytes > 0) && ((0xff & (value >> ((bytes -1)<<3))) == 0)) { + bytes--; + } + if (bytes <= 0) { + bytes = 1; + } + if (data + bytes >= end) { + return -1; + } + if ((0xe0 & (value >> ((bytes -1)<<3))) == 0) { + /* packed together */ + *data = (bytes << 5) | (value >> ((bytes -1)<<3)); + } else { + bytes++; + *data = (bytes << 5); + } + bytes--; + data++; + while(bytes) { + *(data++) = 0xff & (value >> ((bytes -1)<<3)); + bytes--; + } + *ptr = data; + return 0; +} + +static int slam_skip(unsigned char **ptr, unsigned char *end) +{ + int bytes; + if (*ptr >= end) { + return -1; + } + bytes = ((**ptr) >> 5) & 7; + if (bytes == 0) { + return -1; + } + if (*ptr + bytes >= end) { + return -1; + } + (*ptr) += bytes; + return 0; + +} + +static unsigned long slam_decode(unsigned char **ptr, unsigned char *end, int *err) +{ + unsigned long value; + unsigned bytes; + if (*ptr >= end) { + *err = -1; + } + bytes = ((**ptr) >> 5) & 7; + if ((bytes == 0) || (bytes > sizeof(unsigned long))) { + *err = -1; + return 0; + } + if ((*ptr) + bytes >= end) { + *err = -1; + } + value = (**ptr) & 0x1f; + bytes--; + (*ptr)++; + while(bytes) { + value <<= 8; + value |= **ptr; + (*ptr)++; + bytes--; + } + return value; +} + + +static long slam_sleep_interval(int exp) +{ + long range; + long divisor; + long interval; + range = SLAM_BASE_TIMEOUT_INTERVAL; + if (exp < 0) { + divisor = RAND_MAX/SLAM_INITIAL_TIMEOUT_INTERVAL; + } else { + if (exp > SLAM_BACKOFF_LIMIT) + exp = SLAM_BACKOFF_LIMIT; + divisor = RAND_MAX/(range << exp); + } + interval = random()/divisor; + if (exp < 0) { + interval += SLAM_INITIAL_MIN_TIMEOUT; + } else { + interval += SLAM_BASE_MIN_TIMEOUT; + } + return interval; +} + + +static unsigned char *reinit_slam_state( + unsigned char *header, unsigned char *end) +{ + unsigned long total_bytes; + unsigned long block_size; + + unsigned long bitmap_len; + unsigned long max_packet_len; + unsigned char *data; + int err; + +#if 0 + printf("reinit\n"); +#endif + data = header; + + state.hdr_len = 0; + err = slam_skip(&data, end); /* transaction id */ + total_bytes = slam_decode(&data, end, &err); + block_size = slam_decode(&data, end, &err); + if (err) { + printf("ALERT: slam size out of range\n"); + return 0; + } + state.block_size = block_size; + state.total_bytes = total_bytes; + state.total_packets = (total_bytes + block_size - 1)/block_size; + state.hdr_len = data - header; + state.received_packets = 0; + + data = state.hdr; + slam_encode(&data, &state.hdr[sizeof(state.hdr)], state.total_packets); + max_packet_len = data - state.hdr; + memcpy(state.hdr, header, state.hdr_len); + +#if 0 + printf("block_size: %ld\n", block_size); + printf("total_bytes: %ld\n", total_bytes); + printf("total_packets: %ld\n", state.total_packets); + printf("hdr_len: %ld\n", state.hdr_len); + printf("max_packet_len: %ld\n", max_packet_len); +#endif + + if (state.block_size > ETH_MAX_MTU - ( + sizeof(struct iphdr) + sizeof(struct udphdr) + + state.hdr_len + max_packet_len)) { + printf("ALERT: slam blocksize to large\n"); + return 0; + } + if (state.bitmap) { + forget(state.bitmap); + } + bitmap_len = (state.total_packets + 1 + 7)/8; + state.bitmap = allot(bitmap_len); + state.image = allot(total_bytes); + if ((unsigned long)state.image < 1024*1024) { + printf("ALERT: slam filesize to large for available memory\n"); + return 0; + } + memset(state.bitmap, 0, bitmap_len); + + return header + state.hdr_len; +} + +static int slam_recv_data(unsigned char *data) +{ + unsigned long packet; + unsigned long data_len; + int err; + struct udphdr *udp; + udp = (struct udphdr *)&nic.packet[ETH_HLEN + sizeof(struct iphdr)]; + err = 0; + packet = slam_decode(&data, &nic.packet[nic.packetlen], &err); + if (err || (packet > state.total_packets)) { + printf("ALERT: Invalid packet number\n"); + return 0; + } + /* Compute the expected data length */ + if (packet != state.total_packets -1) { + data_len = state.block_size; + } else { + data_len = state.total_bytes % state.block_size; + } + /* If the packet size is wrong drop the packet and then continue */ + if (ntohs(udp->len) != (data_len + (data - (unsigned char*)udp))) { + printf("ALERT: udp packet is not the correct size\n"); + return 1; + } + if (nic.packetlen < data_len + (data - nic.packet)) { + printf("ALERT: Ethernet packet shorter than data_len\n"); + return 1; + } + if (data_len > state.block_size) { + data_len = state.block_size; + } + if (((state.bitmap[packet >> 3] >> (packet & 7)) & 1) == 0) { + /* Non duplicate packet */ + state.bitmap[packet >> 3] |= (1 << (packet & 7)); + memcpy(state.image + (packet*state.block_size), data, data_len); + state.received_packets++; + } else { +#ifdef MDEBUG + printf("\n"); +#endif + } + return 1; +} + +static void transmit_nack(unsigned char *ptr, struct slam_info *info) +{ + int nack_len; + /* Ensure the packet is null terminated */ + *ptr++ = 0; + nack_len = ptr - (unsigned char *)&nack; + build_udp_hdr(info->server_ip.s_addr, + info->local_port, info->server_port, 1, nack_len, &nack); + ip_transmit(nack_len, &nack); +#if defined(MDEBUG) && 0 + printf("Sent NACK to %@ bytes: %d have:%ld/%ld\n", + info->server_ip, nack_len, + state.received_packets, state.total_packets); +#endif +} + +static void slam_send_nack(struct slam_info *info) +{ + unsigned char *ptr, *end; + /* Either I timed out or I was explicitly + * asked for a request packet + */ + ptr = &nack.data[0]; + /* Reserve space for the trailling null */ + end = &nack.data[sizeof(nack.data) -1]; + if (!state.bitmap) { + slam_encode(&ptr, end, 0); + slam_encode(&ptr, end, 1); + } + else { + /* Walk the bitmap */ + unsigned long i; + unsigned long len; + unsigned long max; + int value; + int last; + /* Compute the last bit and store an inverted trailer */ + max = state.total_packets; + value = ((state.bitmap[(max -1) >> 3] >> ((max -1) & 7) ) & 1); + value = !value; + state.bitmap[max >> 3] &= ~(1 << (max & 7)); + state.bitmap[max >> 3] |= value << (max & 7); + + len = 0; + last = 1; /* Start with the received packets */ + for(i = 0; i <= max; i++) { + value = (state.bitmap[i>>3] >> (i & 7)) & 1; + if (value == last) { + len++; + } else { + if (slam_encode(&ptr, end, len)) + break; + last = value; + len = 1; + } + } + } + info->sent_nack = 1; + transmit_nack(ptr, info); +} + +static void slam_send_disconnect(struct slam_info *info) +{ + if (info->sent_nack) { + /* A disconnect is a packet with just the null terminator */ + transmit_nack(&nack.data[0], info); + } + info->sent_nack = 0; +} + + +static int proto_slam(struct slam_info *info) +{ + int retry; + long timeout; + + init_slam_state(); + + retry = -1; + rx_qdrain(); + /* Arp for my server */ + if (arptable[ARP_SERVER].ipaddr.s_addr != info->server_ip.s_addr) { + arptable[ARP_SERVER].ipaddr.s_addr = info->server_ip.s_addr; + memset(arptable[ARP_SERVER].node, 0, ETH_ALEN); + } + /* If I'm running over multicast join the multicast group */ + join_group(IGMP_SERVER, info->multicast_ip.s_addr); + for(;;) { + unsigned char *header; + unsigned char *data; + int type; + header = data = 0; + + timeout = slam_sleep_interval(retry); + type = await_reply(await_slam, 0, info, timeout); + /* Compute the timeout for next time */ + if (type == SLAM_TIMEOUT) { + /* If I timeouted recompute the next timeout */ + if (retry++ > SLAM_MAX_RETRIES) { + return 0; + } + } else { + retry = 0; + } + if ((type == SLAM_DATA) || (type == SLAM_REQUEST)) { + /* Check the incomming packet and reinit the data + * structures if necessary. + */ + header = &nic.packet[ETH_HLEN + + sizeof(struct iphdr) + sizeof(struct udphdr)]; + data = header + state.hdr_len; + if (memcmp(state.hdr, header, state.hdr_len) != 0) { + /* Something is fishy reset the transaction */ + data = reinit_slam_state(header, &nic.packet[nic.packetlen]); + if (!data) { + return 0; + } + } + } + if (type == SLAM_DATA) { + if (!slam_recv_data(data)) { + return 0; + } + if (state.received_packets == state.total_packets) { + /* We are done get out */ + break; + } + } + if ((type == SLAM_TIMEOUT) || (type == SLAM_REQUEST)) { + /* Either I timed out or I was explicitly + * asked by a request packet + */ + slam_send_nack(info); + } + } + slam_send_disconnect(info); + + /* Leave the multicast group */ + leave_group(IGMP_SERVER); + /* FIXME don't overwrite myself */ + /* load file to correct location */ + return info->fnc(state.image, 1, state.total_bytes, 1); +} + + +int url_slam(const char *name, int (*fnc)(unsigned char *, unsigned int, unsigned int, int)) +{ + struct slam_info info; + /* Set the defaults */ + info.server_ip.s_addr = arptable[ARP_SERVER].ipaddr.s_addr; + info.server_port = SLAM_PORT; + info.multicast_ip.s_addr = htonl(SLAM_MULTICAST_IP); + info.multicast_port = SLAM_MULTICAST_PORT; + info.local_ip.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr; + info.local_port = SLAM_LOCAL_PORT; + info.fnc = fnc; + info.sent_nack = 0; + /* Now parse the url */ + if (url_port != -1) { + info.server_port = url_port; + } + if (name[0]) { + /* multicast ip */ + name += inet_aton(name, &info.multicast_ip); + if (name[0] == ':') { + name++; + info.multicast_port = strtoul(name, &name, 10); + } + } + if (name[0]) { + printf("\nBad url\n"); + return 0; + } + return proto_slam(&info); +} + +#endif /* DOWNLOAD_PROTO_SLAM */ diff --git a/src/proto/tftm.c b/src/proto/tftm.c new file mode 100644 index 000000000..8040fc445 --- /dev/null +++ b/src/proto/tftm.c @@ -0,0 +1,491 @@ +/************************************************************************** +* +* proto_tftm.c -- Etherboot Multicast TFTP +* Written 2003-2003 by Timothy Legge +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* This code is based on the DOWNLOAD_PROTO_TFTM section of +* Etherboot 5.3 core/nic.c and: +* +* Anselm Martin Hoffmeister's previous proto_tftm.c multicast work +* Eric Biederman's proto_slam.c +* +* $Revision$ +* $Author$ +* $Date$ +* +* REVISION HISTORY: +* ================ +* 09-07-2003 timlegge Release Version, Capable of Multicast Booting +* 08-30-2003 timlegge Initial version, Assumes consecutive blocks +* +* Indent Options: indent -kr -i8 +***************************************************************************/ + +#ifdef DOWNLOAD_PROTO_TFTM +#include "etherboot.h" +#include "nic.h" + +//#define TFTM_DEBUG +#ifdef TFTM_DEBUG +#define debug(x) printf x +#else +#define debug(x) +#endif +struct tftm_info { + in_addr server_ip; + in_addr multicast_ip; + in_addr local_ip; + uint16_t server_port; + uint16_t multicast_port; + uint16_t local_port; + int (*fnc) (unsigned char *, unsigned int, unsigned int, int); + int sent_nack; + const char *name; /* Filename */ +}; + +struct tftm_state { + unsigned long block_size; + unsigned long total_bytes; + unsigned long total_packets; + char ismaster; + unsigned long received_packets; + unsigned char *image; + unsigned char *bitmap; + char recvd_oack; +} state; + +#define TFTM_PORT 1758 +#define TFTM_MIN_PACKET 1024 + + +int opt_get_multicast(struct tftp_t *tr, unsigned short *len, + unsigned long *filesize, struct tftm_info *info); + +static int await_tftm(int ival, void *ptr, unsigned short ptype __unused, + struct iphdr *ip, struct udphdr *udp) +{ + struct tftm_info *info = ptr; + + /* Check for Unicast data being received */ + if (ip->dest.s_addr == arptable[ARP_CLIENT].ipaddr.s_addr) { + if (!udp) { + return 0; + } + if (arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr) + return 0; + if (ntohs(udp->dest) != ival) + return 0; + + return 1; /* Unicast Data Received */ + } + + /* Also check for Multicast data being received */ + if ((ip->dest.s_addr == info->multicast_ip.s_addr) && + (ntohs(udp->dest) == info->multicast_port) && + (nic.packetlen >= ETH_HLEN + sizeof(struct iphdr) + + sizeof(struct udphdr))) { + return 1; /* Multicast data received */ + } + return 0; +} + +int proto_tftm(struct tftm_info *info) +{ + int retry = 0; + static unsigned short iport = 2000; + unsigned short oport = 0; + unsigned short len, block = 0, prevblock = 0; + struct tftp_t *tr; + struct tftpreq_t tp; + unsigned long filesize = 0; + + state.image = 0; + state.bitmap = 0; + + rx_qdrain(); + + /* Warning: the following assumes the layout of bootp_t. + But that's fixed by the IP, UDP and BOOTP specs. */ + + /* Send a tftm-request to the server */ + tp.opcode = htons(TFTP_RRQ); /* Const for "\0x0" "\0x1" =^= ReadReQuest */ + len = + sizeof(tp.ip) + sizeof(tp.udp) + sizeof(tp.opcode) + + sprintf((char *) tp.u.rrq, + "%s%coctet%cmulticast%c%cblksize%c%d%ctsize%c", + info->name, 0, 0, 0, 0, 0, TFTM_MIN_PACKET, 0, 0) + 1; + + if (!udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, ++iport, + TFTM_PORT, len, &tp)) + return (0); + + /* loop to listen for packets and to receive the file */ + for (;;) { + long timeout; +#ifdef CONGESTED + timeout = + rfc2131_sleep_interval(block ? TFTP_REXMT : TIMEOUT, + retry); +#else + timeout = rfc2131_sleep_interval(TIMEOUT, retry); +#endif + /* Calls the await_reply function in nic.c which in turn calls + await_tftm (1st parameter) as above */ + if (!await_reply(await_tftm, iport, info, timeout)) { + if (!block && retry++ < MAX_TFTP_RETRIES) { /* maybe initial request was lost */ + if (!udp_transmit + (arptable[ARP_SERVER].ipaddr.s_addr, + ++iport, TFTM_PORT, len, &tp)) + return (0); + continue; + } +#ifdef CONGESTED + if (block && ((retry += TFTP_REXMT) < TFTP_TIMEOUT)) { /* we resend our last ack */ +#ifdef MDEBUG + printf("\n"); +#endif + debug(("Timed out receiving file")); + len = + sizeof(tp.ip) + sizeof(tp.udp) + + sizeof(tp.opcode) + + sprintf((char *) tp.u.rrq, + "%s%coctet%cmulticast%c%cblksize%c%d%ctsize%c", + info->name, 0, 0, 0, 0, 0, + TFTM_MIN_PACKET, 0, 0) + 1; + + udp_transmit + (arptable[ARP_SERVER].ipaddr.s_addr, + ++iport, TFTM_PORT, len, &tp); + continue; + } +#endif + break; /* timeout */ + } + + tr = (struct tftp_t *) &nic.packet[ETH_HLEN]; + + if (tr->opcode == ntohs(TFTP_ERROR)) { + printf("TFTP error %d (%s)\n", + ntohs(tr->u.err.errcode), tr->u.err.errmsg); + break; + } + + if (tr->opcode == ntohs(TFTP_OACK)) { + int i = + opt_get_multicast(tr, &len, &filesize, info); + + if (i == 0 || (i != 7 && !state.recvd_oack)) { /* Multicast unsupported */ + /* Transmit an error message to the server to end the transmission */ + printf + ("TFTM-Server doesn't understand options [blksize tsize multicast]\n"); + tp.opcode = htons(TFTP_ERROR); + tp.u.err.errcode = 8; + /* + * Warning: the following assumes the layout of bootp_t. + * But that's fixed by the IP, UDP and BOOTP specs. + */ + len = + sizeof(tp.ip) + sizeof(tp.udp) + + sizeof(tp.opcode) + + sizeof(tp.u.err.errcode) + + /* + * Normally bad form to omit the format string, but in this case + * the string we are copying from is fixed. sprintf is just being + * used as a strcpy and strlen. + */ + sprintf((char *) tp.u.err.errmsg, + "RFC2090 error") + 1; + udp_transmit(arptable[ARP_SERVER].ipaddr. + s_addr, iport, + ntohs(tr->udp.src), len, &tp); + block = tp.u.ack.block = 0; /* this ensures, that */ + /* the packet does not get */ + /* processed as data! */ + return (0); + } else { + unsigned long bitmap_len; + /* */ + if (!state.recvd_oack) { + + state.total_packets = + 1 + (filesize - + (filesize % + state.block_size)) / + state.block_size; + bitmap_len = + (state.total_packets + 7) / 8; + if (!state.image) { + state.bitmap = + allot(bitmap_len); + state.image = + allot(filesize); + + if ((unsigned long) state. + image < 1024 * 1024) { + printf + ("ALERT: tftp filesize to large for available memory\n"); + return 0; + } + memset(state.bitmap, 0, + bitmap_len); + } + /* If I'm running over multicast join the multicast group */ + join_group(IGMP_SERVER, + info->multicast_ip. + s_addr); + } + state.recvd_oack = 1; + } + + + + } else if (tr->opcode == htons(TFTP_DATA)) { + unsigned long data_len; + unsigned char *data; + struct udphdr *udp; + udp = + (struct udphdr *) &nic.packet[ETH_HLEN + + sizeof(struct + iphdr)]; + len = + ntohs(tr->udp.len) - sizeof(struct udphdr) - 4; + data = + nic.packet + ETH_HLEN + sizeof(struct iphdr) + + sizeof(struct udphdr) + 4; + + if (len > TFTM_MIN_PACKET) /* shouldn't happen */ + continue; /* ignore it */ + + block = ntohs(tp.u.ack.block = tr->u.data.block); + + if (block > state.total_packets) { + printf("ALERT: Invalid packet number\n"); + continue; + } + + /* Compute the expected data length */ + if (block != state.total_packets) { + data_len = state.block_size; + } else { + data_len = filesize % state.block_size; + } + /* If the packet size is wrong drop the packet and then continue */ + if (ntohs(udp->len) != + (data_len + (data - (unsigned char *) udp))) { + printf + ("ALERT: udp packet is not the correct size: %d\n", + block); + continue; + } + if (nic.packetlen < data_len + (data - nic.packet)) { + printf + ("ALERT: Ethernet packet shorter than data_len: %d\n", + block); + continue; + } + + if (data_len > state.block_size) { + data_len = state.block_size; + } + if (((state. + bitmap[block >> 3] >> (block & 7)) & 1) == + 0) { + /* Non duplicate packet */ + state.bitmap[block >> 3] |= + (1 << (block & 7)); + memcpy(state.image + + ((block - 1) * state.block_size), + data, data_len); + state.received_packets++; + } else { + +/* printf("\n"); */ + } + } + + else { /* neither TFTP_OACK, TFTP_DATA nor TFTP_ERROR */ + break; + } + + if (state.received_packets <= state.total_packets) { + unsigned long b; + unsigned long len; + unsigned long max; + int value; + int last; + + /* Compute the last bit and store an inverted trailer */ + max = state.total_packets + 1; + value = + ((state. + bitmap[(max - 1) >> 3] >> ((max - + 1) & 7)) & 1); + value = !value; + state.bitmap[max >> 3] &= ~(1 << (max & 7)); + state.bitmap[max >> 3] |= value << (max & 7); + + len = 0; + last = 0; /* Start with the received packets */ + for (b = 1; b <= max; b++) { + value = + (state.bitmap[b >> 3] >> (b & 7)) & 1; + + if (value == 0) { + tp.u.ack.block = htons(b - 1); /* Acknowledge the previous block */ + break; + } + } + } + if (state.ismaster) { + tp.opcode = htons(TFTP_ACK); + oport = ntohs(tr->udp.src); + udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, iport, oport, TFTP_MIN_PACKET, &tp); /* ack */ + } + if (state.received_packets == state.total_packets) { + /* If the client is finished and not the master, + * ack the last packet */ + if (!state.ismaster) { + tp.opcode = htons(TFTP_ACK); + /* Ack Last packet to end xfer */ + tp.u.ack.block = htons(state.total_packets); + oport = ntohs(tr->udp.src); + udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, iport, oport, TFTP_MIN_PACKET, &tp); /* ack */ + } + /* We are done get out */ + forget(state.bitmap); + break; + } + + if ((unsigned short) (block - prevblock) != 1) { + /* Retransmission or OACK, don't process via callback + * and don't change the value of prevblock. */ + continue; + } + + prevblock = block; + retry = 0; /* It's the right place to zero the timer? */ + + } + /* Leave the multicast group */ + leave_group(IGMP_SERVER); + return info->fnc(state.image, 1, filesize, 1); +} + +int url_tftm(const char *name, + int (*fnc) (unsigned char *, unsigned int, unsigned int, int)) +{ + + int ret; + struct tftm_info info; + + /* Set the defaults */ + info.server_ip.s_addr = arptable[ARP_SERVER].ipaddr.s_addr; + info.server_port = TFTM_PORT; + info.local_ip.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr; + info.local_port = TFTM_PORT; /* Does not matter. So take tftm port too. */ + info.multicast_ip.s_addr = info.local_ip.s_addr; + info.multicast_port = TFTM_PORT; + info.fnc = fnc; + state.ismaster = 0; + info.name = name; + + state.block_size = 0; + state.total_bytes = 0; + state.total_packets = 0; + state.received_packets = 0; + state.image = 0; + state.bitmap = 0; + state.recvd_oack = 0; + + if (name[0] != '/') { + /* server ip given, so use it */ + name += inet_aton(info.name, &info.server_ip); + /* No way to specify another port for now */ + } + if (name[0] != '/') { + printf("Bad tftm-URI: [%s]\n", info.name); + return 0; + } + + ret = proto_tftm(&info); + + return ret; +} + +/****************************** +* Parse the multicast options +*******************************/ +int opt_get_multicast(struct tftp_t *tr, unsigned short *len, + unsigned long *filesize, struct tftm_info *info) +{ + const char *p = tr->u.oack.data, *e = 0; + int i = 0; + *len = ntohs(tr->udp.len) - sizeof(struct udphdr) - 2; + if (*len > TFTM_MIN_PACKET) + return -1; + e = p + *len; + + while (*p != '\0' && p < e) { + if (!strcasecmp("tsize", p)) { + p += 6; + if ((*filesize = strtoul(p, &p, 10)) > 0) + i |= 4; + debug(("\n")); + debug(("tsize=%d\n", *filesize)); + while (p < e && *p) + p++; + if (p < e) + p++; + } else if (!strcasecmp("blksize", p)) { + i |= 2; + p += 8; + state.block_size = strtoul(p, &p, 10); + if (state.block_size != TFTM_MIN_PACKET) { + printf + ("TFTM-Server rejected required transfer blocksize %d\n", + TFTM_MIN_PACKET); + return 0; + } + debug(("blksize=%d\n", state.block_size)); + while (p < e && *p) + p++; + if (p < e) + p++; + } else if (!strncmp(p, "multicast", 10)) { + i |= 1; + p += 10; + debug(("multicast options: %s\n", p)); + p += 1 + inet_aton(p, &info->multicast_ip); + debug(("multicast ip = %@\n", info->multicast_ip)); + info->multicast_port = strtoul(p, &p, 10); + ++p; + debug(("multicast port = %d\n", + info->multicast_port)); + state.ismaster = (*p == '1' ? 1 : 0); + debug(("multicast ismaster = %d\n", + state.ismaster)); + while (p < e && *p) + p++; + if (p < e) + p++; + } + } + if (p > e) + return 0; + return i; +} +#endif /* DOWNLOAD_PROTO_TFTP */ -- cgit v1.2.3-55-g7522