diff options
Diffstat (limited to 'clients/net-snk/app/netlib/tftp.c')
-rw-r--r-- | clients/net-snk/app/netlib/tftp.c | 658 |
1 files changed, 658 insertions, 0 deletions
diff --git a/clients/net-snk/app/netlib/tftp.c b/clients/net-snk/app/netlib/tftp.c new file mode 100644 index 0000000..7536770 --- /dev/null +++ b/clients/net-snk/app/netlib/tftp.c @@ -0,0 +1,658 @@ +/****************************************************************************** + * Copyright (c) 2004, 2007 IBM Corporation + * All rights reserved. + * This program and the accompanying materials + * are made available under the terms of the BSD License + * which accompanies this distribution, and is available at + * http://www.opensource.org/licenses/bsd-license.php + * + * Contributors: + * IBM Corporation - initial implementation + *****************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/socket.h> + +#include <netlib/netlib.h> +#include <netlib/icmp.h> + +//#define __DEBUG__ + +#define BUFFER_LEN 2048 +#define ACK_BUFFER_LEN 256 +#define READ_BUFFER_LEN 256 + +#define ENOTFOUND 1 +#define EACCESS 2 +#define EBADOP 4 +#define EBADID 5 +#define ENOUSER 7 +//#define EUNDEF 0 +//#define ENOSPACE 3 +//#define EEXISTS 6 + +#define RRQ 1 +#define WRQ 2 +#define DATA 3 +#define ACK 4 +#define ERROR 5 +#define OACK 6 + +/** + * dump_package - Prints a package. + * + * @package: package which is to print + * @len: length of the package + */ +#ifdef __DEBUG__ + +static void +dump_package(unsigned char *buffer, unsigned int len) +{ + int i; + + for (i = 1; i <= len; i++) { + printf("%02x%02x ", buffer[i - 1], buffer[i]); + i++; + if ((i % 16) == 0) + printf("\n"); + } + printf("\n"); +} +#endif + +/* UDP header checksum calculation */ + +static unsigned short +checksum(unsigned short *packet, int words, unsigned short *pseudo_ip) +{ + int i; + unsigned long checksum; + for (checksum = 0; words > 0; words--) + checksum += *packet++; + if (pseudo_ip) { + for (i = 0; i < 6; i++) + checksum += *pseudo_ip++; + } + checksum = (checksum >> 16) + (checksum & 0xffff); + checksum += (checksum >> 16); + return ~checksum; +} + + +/** + * send_rrq - Sends a read request package. + * + * @client: client IPv4 address (e.g. 127.0.0.1) + * @server: server IPv4 address (e.g. 127.0.0.1) + * @filename: name of the file which should be downloaded + */ +static void +send_rrq(int boot_device, filename_ip_t * fn_ip) +{ + int i; + unsigned char mode[] = "octet"; + unsigned char packet[READ_BUFFER_LEN]; + char *ptr; + struct ethhdr *ethh; + struct iphdr *ip; + struct udphdr *udph; + struct tftphdr *tftp; + struct pseudo_iphdr piph = { 0 }; + + memset(packet, 0, READ_BUFFER_LEN); + + ethh = (struct ethhdr *) packet; + + memcpy(ethh->src_mac, fn_ip->own_mac, 6); + memcpy(ethh->dest_mac, fn_ip->server_mac, 6); + ethh->type = htons(ETHERTYPE_IP); + + ip = (struct iphdr *) ((unsigned char *) ethh + sizeof(struct ethhdr)); + ip->ip_hlv = 0x45; + ip->ip_tos = 0x00; + ip->ip_len = sizeof(struct iphdr) + sizeof(struct udphdr) + + strlen((char *) fn_ip->filename) + strlen((char *) mode) + 4 + + strlen("blksize") + strlen("1432") + 2; + ip->ip_id = 0x0; + ip->ip_off = 0x0000; + ip->ip_ttl = 60; + ip->ip_p = 17; + ip->ip_src = fn_ip->own_ip; + ip->ip_dst = fn_ip->server_ip; + ip->ip_sum = 0; + + udph = (struct udphdr *) (ip + 1); + udph->uh_sport = htons(2001); + udph->uh_dport = htons(69); + udph->uh_ulen = htons(sizeof(struct udphdr) + + strlen((char *) fn_ip->filename) + strlen((char *) mode) + 4 + + strlen("blksize") + strlen("1432") + 2); + + tftp = (struct tftphdr *) (udph + 1); + tftp->th_opcode = htons(RRQ); + + ptr = (char *) &tftp->th_data; + memcpy(ptr, fn_ip->filename, strlen((char *) fn_ip->filename) + 1); + + ptr += strlen((char *) fn_ip->filename) + 1; + memcpy(ptr, mode, strlen((char *) mode) + 1); + + ptr += strlen((char *) mode) + 1; + memcpy(ptr, "blksize", strlen("blksize") + 1); + + ptr += strlen("blksize") + 1; + memcpy(ptr, "1432", strlen("1432") + 1); + + piph.ip_src = ip->ip_src; + piph.ip_dst = ip->ip_dst; + piph.ip_p = ip->ip_p; + piph.ip_ulen = udph->uh_ulen; + + udph->uh_sum = 0; + udph->uh_sum = + checksum((unsigned short *) udph, udph->uh_ulen >> 1, + (unsigned short *) &piph); + + ip->ip_sum = + checksum((unsigned short *) ip, sizeof(struct iphdr) >> 1, 0); + i = send(boot_device, packet, ip->ip_len + sizeof(struct ethhdr), 0); +#ifdef __DEBUG__ + printf("tftp RRQ %d bytes transmitted over socket.\n", i); +#endif + return; +} + +/** + * send_ack - Sends a acknowlege package. + * + * @boot_device: + * @fn_ip: + * @blckno: block number + */ +static void +send_ack(int boot_device, filename_ip_t * fn_ip, + int blckno, unsigned short dport) +{ + int i; + unsigned char packet[ACK_BUFFER_LEN]; + struct ethhdr *ethh; + struct iphdr *ip; + struct udphdr *udph; + struct tftphdr *tftp; + struct pseudo_iphdr piph = { 0 }; + + memset(packet, 0, ACK_BUFFER_LEN); + + ethh = (struct ethhdr *) packet; + memcpy(ethh->src_mac, fn_ip->own_mac, 6); + memcpy(ethh->dest_mac, fn_ip->server_mac, 6); + ethh->type = htons(ETHERTYPE_IP); + + ip = (struct iphdr *) ((unsigned char *) ethh + sizeof(struct ethhdr)); + ip->ip_hlv = 0x45; + ip->ip_tos = 0x00; + ip->ip_len = sizeof(struct iphdr) + sizeof(struct udphdr) + 4; + ip->ip_id = 0; + ip->ip_off = 0x0000; + ip->ip_ttl = 60; + ip->ip_p = 17; + ip->ip_src = fn_ip->own_ip; + ip->ip_dst = fn_ip->server_ip; + + ip->ip_sum = 0; + + udph = (struct udphdr *) (ip + 1); + udph->uh_sport = htons(2001); + udph->uh_dport = htons(dport); + udph->uh_ulen = htons(sizeof(struct udphdr) + 4); + + tftp = (struct tftphdr *) (udph + 1); + tftp->th_opcode = htons(ACK); + tftp->th_data = htons(blckno); + + piph.ip_src = ip->ip_src; + piph.ip_dst = ip->ip_dst; + piph.ip_p = ip->ip_p; + piph.ip_ulen = udph->uh_ulen; + + udph->uh_sum = 0; + udph->uh_sum = + checksum((unsigned short *) udph, udph->uh_ulen >> 1, + (unsigned short *) &piph); + + ip->ip_sum = + checksum((unsigned short *) ip, sizeof(struct iphdr) >> 1, 0); + + i = send(boot_device, packet, ip->ip_len + sizeof(struct ethhdr), 0); + +#ifdef __DEBUG__ + printf("tftp ACK %d bytes transmitted over socket.\n", i); +#endif + + return; +} + +/** + * send_error - Sends an error package. + * + * @boot_device: socket handle + * @fn_ip: some OSI CEP-IDs + * @error_code: Used sub code for error packet + * @dport: UDP destination port + */ +static void +send_error(int boot_device, filename_ip_t * fn_ip, + int error_code, unsigned short dport) +{ + int i; + unsigned char packet[256]; + struct ethhdr *ethh; + struct iphdr *ip; + struct udphdr *udph; + struct tftphdr *tftp; + struct pseudo_iphdr piph = { 0 }; + + memset(packet, 0, 256); + + ethh = (struct ethhdr *) packet; + memcpy(ethh->src_mac, fn_ip->own_mac, 6); + memcpy(ethh->dest_mac, fn_ip->server_mac, 6); + ethh->type = htons(ETHERTYPE_IP); + + ip = (struct iphdr *) ((unsigned char *) ethh + sizeof(struct ethhdr)); + ip->ip_hlv = 0x45; + ip->ip_tos = 0x00; + ip->ip_len = sizeof(struct iphdr) + sizeof(struct udphdr) + 5; + ip->ip_id = 0; + ip->ip_off = 0x0000; + ip->ip_ttl = 60; + ip->ip_p = 17; + ip->ip_src = fn_ip->own_ip; + ip->ip_dst = fn_ip->server_ip; + + ip->ip_sum = 0; + + udph = (struct udphdr *) (ip + 1); + udph->uh_sport = htons(2001); + udph->uh_dport = htons(dport); + udph->uh_ulen = htons(sizeof(struct udphdr) + 5); + + tftp = (struct tftphdr *) (udph + 1); + tftp->th_opcode = htons(ERROR); + tftp->th_data = htons(error_code); + ((char *) &tftp->th_data)[2] = 0; + + piph.ip_src = ip->ip_src; + piph.ip_dst = ip->ip_dst; + piph.ip_p = ip->ip_p; + piph.ip_ulen = udph->uh_ulen; + + udph->uh_sum = 0; + udph->uh_sum = + checksum((unsigned short *) udph, udph->uh_ulen >> 1, + (unsigned short *) &piph); + + ip->ip_sum = + checksum((unsigned short *) ip, sizeof(struct iphdr) >> 1, 0); + + i = send(boot_device, packet, ip->ip_len + sizeof(struct ethhdr), 0); + +#ifdef __DEBUG__ + printf("tftp ERROR %d bytes transmitted over socket.\n", i); +#endif + + return; +} + + +static int +send_arp_reply(int boot_device, filename_ip_t * fn_ip) +{ + int i; + unsigned int packetsize = sizeof(struct ethhdr) + sizeof(struct arphdr); + unsigned char packet[packetsize]; + struct ethhdr *ethh; + struct arphdr *arph; + + ethh = (struct ethhdr *) packet; + arph = (struct arphdr *) ((void *) ethh + sizeof(struct ethhdr)); + + memset(packet, 0, packetsize); + + memcpy(ethh->src_mac, fn_ip->own_mac, 6); + memcpy(ethh->dest_mac, fn_ip->server_mac, 6); + ethh->type = htons(ETHERTYPE_ARP); + + arph->hw_type = 1; + arph->proto_type = 0x800; + arph->hw_len = 6; + arph->proto_len = 4; + + memcpy(arph->src_mac, fn_ip->own_mac, 6); + arph->src_ip = fn_ip->own_ip; + + arph->dest_ip = fn_ip->server_ip; + + arph->opcode = 2; +#ifdef __DEBUG__ + printf("send arp reply\n"); +#endif +#if 0 + printf("Sending packet\n"); + printf("Packet is "); + for (i = 0; i < packetsize; i++) + printf(" %2.2x", packet[i]); + printf(".\n"); +#endif + + i = send(boot_device, packet, packetsize, 0); + return i; +} + +static void +print_progress(int urgent, int received_bytes) +{ + static unsigned int i = 1; + static int first = -1; + static int last_bytes = 0; + char buffer[100]; + char *ptr; + + // 1MB steps or 0x400 times or urgent + if(((received_bytes - last_bytes) >> 20) > 0 + || (i & 0x3FF) == 0 || urgent) { + if(!first) { + sprintf(buffer, "%d KBytes", (last_bytes >> 10)); + for(ptr = buffer; *ptr != 0; ++ptr) + *ptr = '\b'; + printf(buffer); + } + printf("%d KBytes", (received_bytes >> 10)); + i = 1; + first = 0; + last_bytes = received_bytes; + } + ++i; +} + +/** + * get_blksize tries to extract the blksize from the OACK package + * the TFTP returned. From RFC 1782 + * The OACK packet has the following format: + * + * +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ + * | opc | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 | + * +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ + * + * @param buffer the network packet + * @param len the length of the network packet + * @return the blocksize the server supports or 0 for error + */ +static int +get_blksize(unsigned char *buffer, unsigned int len) +{ + unsigned char *orig = buffer; + /* skip all headers until tftp has been reached */ + buffer += sizeof(struct ethhdr); + buffer += sizeof(struct iphdr); + buffer += sizeof(struct udphdr); + /* skip opc */ + buffer += 2; + while (buffer < orig + len) { + if (!memcmp(buffer, "blksize", strlen("blksize") + 1)) + return (unsigned short) strtoul((char *) (buffer + + strlen("blksize") + 1), + (char **) NULL, 10); + else { + /* skip the option name */ + buffer = (unsigned char *) strchr((char *) buffer, 0); + if (!buffer) + return 0; + buffer++; + /* skip the option value */ + buffer = (unsigned char *) strchr((char *) buffer, 0); + if (!buffer) + return 0; + buffer++; + } + } + return 0; +} + +/** + * this prints out some status characters + * \|-/ for each packet received + * A for an arp packet + * I for an ICMP packet + * #+* for different unexpected TFTP packets (not very good) + */ +static int +the_real_tftp(int boot_device, filename_ip_t * fn_ip, unsigned char *buffer, + int len, unsigned int retries, tftp_err_t * tftp_err) +{ + int i, j = 0; + int received_len = 0; + struct ethhdr *ethh; + struct arphdr *arph; + + struct iphdr *ip; + struct udphdr *udph; + struct tftphdr *tftp; + struct icmphdr *icmp; + + unsigned char packet[BUFFER_LEN]; + short port_number = -1; + unsigned short block = 0; + unsigned short blocksize = 512; + int lost_packets = 0; + + tftp_err->bad_tftp_packets = 0; + tftp_err->no_packets = 0; + + send_rrq(boot_device, fn_ip); + + printf(" Receiving data: "); + print_progress(-1, 0); + + set_timer(TICKS_SEC); + while (j++ < 0x100000) { + /* bad_tftp_packets are counted whenever we receive a TFTP packet + * which was not expected; if this gets larger than 'retries' + * we just exit */ + if (tftp_err->bad_tftp_packets > retries) { + return -40; + } + /* no_packets counts the times we have returned from recv() without + * any packet received; if this gets larger than 'retries' + * we also just exit */ + if (tftp_err->no_packets > retries) { + return -41; + } + /* don't wait longer than 0.5 seconds for packet to be recevied */ + do { + i = recv(boot_device, packet, BUFFER_LEN, 0); + if (i != 0) + break; + } while (get_timer() > 0); + + /* no packet received; no processing */ + if (i == 0) { + /* the server doesn't seem to retry let's help out a bit */ + if (tftp_err->no_packets > 4 && port_number != -1 + && block > 1) + send_ack(boot_device, fn_ip, block, + port_number); + tftp_err->no_packets++; + set_timer(TICKS_SEC); + continue; + } +#ifndef __DEBUG__ + print_progress(0, received_len); +#endif + ethh = (struct ethhdr *) packet; + arph = + (struct arphdr *) ((void *) ethh + sizeof(struct ethhdr)); + ip = (struct iphdr *) (packet + sizeof(struct ethhdr)); + udph = (struct udphdr *) ((void *) ip + sizeof(struct iphdr)); + tftp = + (struct tftphdr *) ((void *) udph + sizeof(struct udphdr)); + icmp = (struct icmphdr *) ((void *) ip + sizeof(struct iphdr)); + + if (memcmp(ethh->dest_mac, fn_ip->own_mac, 6) == 0) { + set_timer(TICKS_SEC); + tftp_err->no_packets = 0; + } + + if (ethh->type == htons(ETHERTYPE_ARP) && + arph->hw_type == 1 && + arph->proto_type == 0x800 && arph->opcode == 1) { + /* let's see if the arp request asks for our IP address + * else we will not answer */ + if (fn_ip->own_ip == arph->dest_ip) { +#ifdef __DEBUG__ + printf("\bA "); +#endif + send_arp_reply(boot_device, fn_ip); + } + continue; + } + + /* check if packet is an ICMP packet */ + if (ip->ip_p == PROTO_ICMP) { +#ifdef __DEBUG__ + printf("\bI "); +#endif + i = handle_icmp(icmp); + if (i) + return i; + } + + /* only IPv4 UDP packets we want */ + if (ip->ip_hlv != 0x45 || ip->ip_p != 0x11) { +#ifdef __DEBUG__ + printf("Unknown packet %x %x %x %x %x \n", ethh->type, + ip->ip_hlv, ip->ip_p, ip->ip_dst, fn_ip->own_ip); +#endif + continue; + } + + /* we only want packets for our own IP and broadcast UDP packets + * there will be probably never be a broadcast UDP TFTP packet + * but the RFC talks about it (crazy RFC) */ + if (!(ip->ip_dst == fn_ip->own_ip || ip->ip_dst == 0xFFFFFFFF)) + continue; +#ifdef __DEBUG__ + dump_package(packet, i); +#endif + + port_number = udph->uh_sport; + if (tftp->th_opcode == htons(OACK)) { + /* an OACK means that the server answers our blocksize request */ + blocksize = get_blksize(packet, i); + if (!blocksize || blocksize > 1432) { + send_error(boot_device, fn_ip, 8, port_number); + return -8; + } + send_ack(boot_device, fn_ip, 0, port_number); + } else if (tftp->th_opcode == htons(ACK)) { + /* an ACK means that the server did not answers + * our blocksize request, therefore we will set the blocksize + * to the default value of 512 */ + blocksize = 512; + send_ack(boot_device, fn_ip, 0, port_number); + } else if ((unsigned char) tftp->th_opcode == ERROR) { +#ifdef __DEBUG__ + printf("tftp->th_opcode : %x\n", tftp->th_opcode); + printf("tftp->th_data : %x\n", tftp->th_data); +#endif + if ((unsigned char) tftp->th_data == ENOTFOUND) /* 1 */ + return -3; // ERROR: file not found + else if ((unsigned char) tftp->th_data == EACCESS) /* 2 */ + return -4; // ERROR: access violation + else if ((unsigned char) tftp->th_data == EBADOP) /* 4 */ + return -5; // ERROR: illegal TFTP operation + else if ((unsigned char) tftp->th_data == EBADID) /* 5 */ + return -6; // ERROR: unknown transfer ID + else if ((unsigned char) tftp->th_data == ENOUSER) /* 7 */ + return -7; // ERROR: no such user + return -1; // ERROR: unknown error + } else if (tftp->th_opcode == DATA) { + /* DATA PACKAGE */ + if (tftp->th_data == block + 1) + block++; + else if (tftp->th_data == block) { +#ifdef __DEBUG__ + printf + ("\nTFTP: Received block %x, expected block was %x\n", + tftp->th_data, block + 1); + printf("\b+ "); +#endif + send_ack(boot_device, fn_ip, tftp->th_data, + port_number); + lost_packets++; + tftp_err->bad_tftp_packets++; + continue; + } else if (tftp->th_data < block) { +#ifdef __DEBUG__ + printf + ("\nTFTP: Received block %x, expected block was %x\n", + tftp->th_data, block + 1); + printf("\b* "); +#endif + /* This means that an old data packet appears (again); + * this happens sometimes if we don't answer fast enough + * and a timeout is generated on the server side; + * as we already have this packet we just ignore it */ + tftp_err->bad_tftp_packets++; + continue; + } else { + tftp_err->blocks_missed = block + 1; + tftp_err->blocks_received = tftp->th_data; + return -42; + } + + tftp_err->bad_tftp_packets = 0; + /* check if our buffer is large enough */ + if (received_len + udph->uh_ulen - 12 > len) + return -2; + memcpy(buffer + received_len, &tftp->th_data + 1, + udph->uh_ulen - 12); + send_ack(boot_device, fn_ip, tftp->th_data, + port_number); + received_len += udph->uh_ulen - 12; + /* Last packet reached if the payload of the UDP packet + * is smaller than blocksize + 12 + * 12 = UDP header (8) + 4 bytes TFTP payload */ + if (udph->uh_ulen < blocksize + 12) + break; + /* 0xffff is the highest block number possible + * see the TFTP RFCs */ + if (block >= 0xffff) + return -9; + } else { +#ifdef __DEBUG__ + printf("Unknown packet %x\n", tftp->th_opcode); + printf("\b# "); +#endif + tftp_err->bad_tftp_packets++; + continue; + } + } + print_progress(-1, received_len); + printf("\n"); + if (lost_packets) + printf("Lost ACK packets: %d\n", lost_packets); + return received_len; +} + +int +tftp(int boot_device, filename_ip_t * fn_ip, unsigned char *buffer, int len, + unsigned int retries, tftp_err_t * tftp_err) +{ + return the_real_tftp(boot_device, fn_ip, buffer, len, retries, + tftp_err); +} |