summaryrefslogtreecommitdiffstats
path: root/src/proto
diff options
context:
space:
mode:
authorMichael Brown2005-05-01 16:04:11 +0200
committerMichael Brown2005-05-01 16:04:11 +0200
commit85d9eae44ef5e2c48fcbefe36fb7bdfcb54c9f75 (patch)
tree00a1194278710abe5021f8bf00236ca41b14d087 /src/proto
parentAbort immediately if no nameserver is present. (diff)
downloadipxe-85d9eae44ef5e2c48fcbefe36fb7bdfcb54c9f75.tar.gz
ipxe-85d9eae44ef5e2c48fcbefe36fb7bdfcb54c9f75.tar.xz
ipxe-85d9eae44ef5e2c48fcbefe36fb7bdfcb54c9f75.zip
Moved protocols to proto/
Diffstat (limited to 'src/proto')
-rw-r--r--src/proto/http.c206
-rw-r--r--src/proto/nfs.c610
-rw-r--r--src/proto/slam.c541
-rw-r--r--src/proto/tftm.c491
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 */