diff options
Diffstat (limited to 'src/proto')
| -rw-r--r-- | src/proto/http.c | 206 | ||||
| -rw-r--r-- | src/proto/nfs.c | 610 | ||||
| -rw-r--r-- | src/proto/slam.c | 541 | ||||
| -rw-r--r-- | src/proto/tftm.c | 491 |
4 files changed, 1848 insertions, 0 deletions
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("<DUP>\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 <tlegge@rogers.com> +* +* 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("<REXMT>\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("<DUP>\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 */ |
