| #if 0 |
| |
| /************************************************************************** |
| * |
| * 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 |
| ***************************************************************************/ |
| |
| /* |
| * IMPORTANT |
| * |
| * This file should be rewritten to avoid the use of a bitmap. Our |
| * buffer routines can cope with being handed blocks in an arbitrary |
| * order, duplicate blocks, etc. This code could be substantially |
| * simplified by taking advantage of these features. |
| * |
| */ |
| |
| #include "etherboot.h" |
| #include "proto.h" |
| #include "nic.h" |
| |
| struct tftm_info { |
| struct sockaddr_in server; |
| struct sockaddr_in local; |
| struct sockaddr_in multicast; |
| 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; |
| struct buffer *buffer; |
| unsigned char *image; |
| unsigned char *bitmap; |
| char recvd_oack; |
| } state; |
| |
| #define TFTM_PORT 1758 |
| #define TFTM_MIN_PACKET 1024 |
| |
| |
| static 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 tcphdr *tcp __unused) |
| { |
| 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.sin_addr.s_addr) && |
| (ntohs(udp->dest) == info->multicast.sin_port) && |
| (nic.packetlen >= ETH_HLEN + sizeof(struct iphdr) + |
| sizeof(struct udphdr))) { |
| return 1; /* Multicast data received */ |
| } |
| return 0; |
| } |
| |
| static 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(info->server.sin_addr.s_addr, ++iport, |
| info->server.sin_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 |
| (info->server.sin_addr.s_addr, ++iport, |
| info->server.sin_port, len, &tp)) |
| return (0); |
| continue; |
| } |
| #ifdef CONGESTED |
| if (block && ((retry += TFTP_REXMT) < TFTP_TIMEOUT)) { /* we resend our last ack */ |
| DBG("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 |
| (info->server.sin_addr.s_addr, |
| ++iport, info->server.sin_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(info->server.sin_addr.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.image = phys_to_virt ( state.buffer->start ); |
| state.bitmap = state.image + filesize; |
| /* We don't yet use the buffer routines; fake it */ |
| state.buffer->fill = filesize; |
| |
| memset(state.bitmap, 0, |
| bitmap_len); |
| } |
| /* If I'm running over multicast join the multicast group */ |
| join_group(IGMP_SERVER, |
| info->multicast.sin_addr.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(info->server.sin_addr.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(info->server.sin_addr.s_addr, |
| iport, oport, |
| TFTP_MIN_PACKET, &tp); /* ack */ |
| } |
| /* We are done get out */ |
| 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 1; |
| } |
| |
| static int url_tftm ( char *url __unused, struct sockaddr_in *server, |
| char *file, struct buffer *buffer ) { |
| |
| int ret; |
| struct tftm_info info; |
| |
| /* Set the defaults */ |
| info.server = *server; |
| info.local.sin_addr.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr; |
| info.local.sin_port = TFTM_PORT; /* Does not matter. */ |
| info.multicast = info.local; |
| state.ismaster = 0; |
| info.name = file; |
| |
| state.block_size = 0; |
| state.total_bytes = 0; |
| state.total_packets = 0; |
| state.received_packets = 0; |
| state.buffer = buffer; |
| state.image = 0; |
| state.bitmap = 0; |
| state.recvd_oack = 0; |
| |
| if (file[0] != '/') { |
| printf("Bad tftm-URI: [%s]\n", file); |
| return 0; |
| } |
| |
| ret = proto_tftm(&info); |
| |
| return ret; |
| } |
| |
| /****************************** |
| * Parse the multicast options |
| *******************************/ |
| static 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; |
| DBG("\n"); |
| DBG("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; |
| } |
| DBG("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; |
| DBG("multicast options: %s\n", p); |
| p += 1 + inet_aton(p, &info->multicast.sin_addr); |
| DBG("multicast ip = %@\n", info->multicast_ip); |
| info->multicast.sin_port = strtoul(p, &p, 10); |
| ++p; |
| DBG("multicast port = %d\n", |
| info->multicast.sin_port); |
| state.ismaster = (*p == '1' ? 1 : 0); |
| DBG("multicast ismaster = %d\n", |
| state.ismaster); |
| while (p < e && *p) |
| p++; |
| if (p < e) |
| p++; |
| } |
| } |
| if (p > e) |
| return 0; |
| return i; |
| } |
| |
| static struct protocol tftm_protocol __protocol = { |
| .name = "x-tftm", |
| .default_port = TFTM_PORT, |
| .load = url_tftm, |
| }; |
| |
| #endif |