diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 1995-10-18 08:53:40 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 1995-10-18 08:53:40 +0000 |
commit | d6583bb2a13f329cf0332ef2570eb8bb8fc0e39c (patch) | |
tree | ece253b876159b39c620e62b6c9b1174642e070e /sys/arch/i386/netboot/tftp.c |
initial import of NetBSD tree
Diffstat (limited to 'sys/arch/i386/netboot/tftp.c')
-rw-r--r-- | sys/arch/i386/netboot/tftp.c | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/sys/arch/i386/netboot/tftp.c b/sys/arch/i386/netboot/tftp.c new file mode 100644 index 00000000000..6725080aa5a --- /dev/null +++ b/sys/arch/i386/netboot/tftp.c @@ -0,0 +1,484 @@ +/* $NetBSD: tftp.c,v 1.3 1994/10/27 04:21:26 cgd Exp $ */ + +/* + * source in this file came from + * the Mach ethernet boot written by Leendert van Doorn. + * + * Trivial File Transfer Protocol (see RFC 783). + * + * Copyright (c) 1992 by Leendert van Doorn + */ + +#include "proto.h" +#include "assert.h" +#include "param.h" +#include "packet.h" +#include "ether.h" +#include "inet.h" +#include "tftp.h" +#include "arp.h" + +ipaddr_t tftp_server; /* IP address of TFTP server */ +ipaddr_t tftp_gateway; +static char tftp_file_name[100]; +static short block; /* current block */ +static int ctid, stid; /* UDP client and server TID (network order) */ + +extern u_long work_area_org; + +/* + * Print IP address in a readable form + */ +void +IpPrintAddr(ipaddr_t addr) { + inetaddr_t ip; + + ip.a = addr; + printf("%d.%d.%d.%d", ip.s.a0, ip.s.a1, ip.s.a2, ip.s.a3); +} + +/* + * Generic TFTP error routine + */ +static void +TftpFail(ipaddr_t fromaddr, ipaddr_t toaddr, char *filename, char *reason) { + printf("Tftp of file '%s' from ", filename); + IpPrintAddr(fromaddr); + printf(" failed, %s\n", reason); +} + +/* + * One complement check sum + */ +static u_short +InChecksum(char *cp, u_long count) { + u_short *sp; + u_long sum, oneword = 0x00010000; + + for (sum = 0, sp = (u_short *)cp, count >>= 1; count--; ) { + sum += *sp++; + if (sum >= oneword) { + /* wrap carry into low bit */ + sum -= oneword; + sum++; + } + } + return ~sum; +} + +/* + * Setup the standard IP header fields for a destination, + * and send packet (possibly using the gateway). + */ +void +IpSend(packet_t *pkt, ipaddr_t dst, ipaddr_t gateway) { + iphdr_t *ip; + u_char edst[ETH_ADDRSIZE]; + static int ipid = 0; +#if TRACE > 0 +DUMP_STRUCT("IpSend: pkt (front)", pkt, 100); +#endif + pkt->pkt_offset -= sizeof(iphdr_t); + pkt->pkt_len += sizeof(iphdr_t); + ip = (iphdr_t *) pkt->pkt_offset; + ip->ip_vhl = (IP_VERSION << 4) | (sizeof(*ip) >> 2); + ip->ip_tos = 0; + ip->ip_len = htons(pkt->pkt_len); + ip->ip_id = ipid++; + ip->ip_off = 0; + ip->ip_ttl = IP_FRAGTTL; + ip->ip_p = IP_PROTO_UDP; + ip->ip_src = ip_myaddr ? ip_myaddr : IP_ANYADDR; + ip->ip_dst = dst; + ip->ip_sum = 0; + ip->ip_sum = InChecksum((char *)ip, sizeof(*ip)); +#if 0 +/* DUMP_STRUCT("pkt (after)", pkt, 100); */ +DUMP_STRUCT("ip", ip, sizeof(iphdr_t)+pkt->pkt_len); +#endif + if (ArpResolve(pkt, gateway ? gateway : dst, edst)) { + EtherSend(pkt, ETHTYPE_IP, edst); + PktRelease(pkt); + } +} + +/* + * States which TFTP can be in + */ +enum TftpPacketStatus { + TFTP_RECD_GOOD_PACKET, + TFTP_RECD_BAD_PACKET, + TFTP_RECD_SERVER_ABORT, +}; + +/* + * Pseudo header to compute UDP checksum + */ +struct pseudoheader { + ipaddr_t ph_src; + ipaddr_t ph_dst; + u_char ph_zero; + u_char ph_prot; + u_short ph_length; +}; + +/* + * Determine whether this IP packet is the TFTP data packet + * we were expecting. When a broadcast TFTP request was made + * we'll set the TFTP server address as well. + */ +static enum TftpPacketStatus +TftpDigestPacket(packet_t *pkt, char *rbuf, u_long *rlen) { + iphdr_t *ip; + udphdr_t *up; + tftphdr_t *tp; + struct pseudoheader ph; + u_short oldsum, sum; + u_short udplength; + + /* check for minimum size tftp packet */ + if (pkt->pkt_len < (sizeof(ethhdr_t) + sizeof(iphdr_t) + + sizeof(udphdr_t) + sizeof(tftphdr_t))) { +#if 0 + printe("TftpDigestPacket: bad packet size %d\n", pkt->pkt_len); +#endif + return TFTP_RECD_BAD_PACKET; + } + + /* IP related checks */ + ip = (iphdr_t *) (pkt->pkt_offset + sizeof(ethhdr_t)); + if (tftp_server != IP_BCASTADDR && ip->ip_src != tftp_server) { +#if 0 + printe("TftpDigestPacket: incorrect ip source address 0x%x\n", ip->ip_src); +#endif + return TFTP_RECD_BAD_PACKET; + } + if (ntohs(ip->ip_len) < + sizeof(iphdr_t) + sizeof(udphdr_t) + sizeof(tftphdr_t)) { +#if 0 + printe("TftpDigestPacket: bad ip length %d\n", ip->ip_len); +#endif + return TFTP_RECD_BAD_PACKET; + } + if (ip->ip_p != IP_PROTO_UDP) { +#if 0 + printe("TftpDigestPacket: wrong ip protocol type 0x%x\n", ip->ip_p); +#endif + return TFTP_RECD_BAD_PACKET; + } + if (ip_myaddr && ip->ip_dst != ip_myaddr) { +#if 0 + printe("TftpDigestPacket: incorrect ip destination address %x\n", ip->ip_dst); +#endif + return TFTP_RECD_BAD_PACKET; + } + + /* UDP related checks */ + up = (udphdr_t *) ((char *)ip + sizeof(iphdr_t)); + if (block && up->uh_sport != stid) { +#if 0 + printe("TftpDigestPacket: wrong udp source port 0x%x\n", up->uh_sport); +#endif + return TFTP_RECD_BAD_PACKET; + } + *rlen = ntohs(up->uh_len) - sizeof(udphdr_t) - sizeof(tftphdr_t); + if (up->uh_dport != ctid) { +#if 0 + printe("TftpDigestPacket: wrong udp destination port 0x%x\n", up->uh_dport); +#endif + return TFTP_RECD_BAD_PACKET; + } + + /* compute UDP checksum if any */ + oldsum = up->uh_sum; + if (oldsum) { + udplength = ntohs(up->uh_len); + /* + * zero the byte past the last data byte because the + * checksum will be over an even number of bytes. + */ + if (udplength & 01) + ((char *)up)[udplength] = '\0'; + + /* set up the pseudo-header */ + ph.ph_src = ip->ip_src; + ph.ph_dst = ip->ip_dst; + ph.ph_zero = 0; + ph.ph_prot = ip->ip_p; + ph.ph_length = htons(udplength); + + up->uh_sum = ~InChecksum((char *)&ph, sizeof(ph)); + sum = InChecksum((char *)up, (u_long)((udplength + 1) & ~1)); + up->uh_sum = oldsum; /* put original back */ + if (oldsum == (u_short) -1) + oldsum = 0; + if (sum != oldsum) { +#if 0 + printe("TftpDigestPacket: Bad checksum %x != %x, length %d from ", + sum, oldsum, udplength); + IpPrintAddr(ip->ip_src); + printe("\n"); +#endif + return TFTP_RECD_BAD_PACKET; + } + } + + /* TFTP related checks */ + tp = (tftphdr_t *) ((char *)up + sizeof(udphdr_t)); + switch (ntohs(tp->th_op)) { + case TFTP_ERROR: + printf("Diagnostic from server: error #%d, %s\n", + ntohs(tp->th_code), &tp->th_msg); + return TFTP_RECD_SERVER_ABORT; + case TFTP_DATA: + break; + default: +#if 0 + printe("TftpDigestPacket: incorrect tftp packet type 0x%x\n", tp->th_op); +#endif + return TFTP_RECD_BAD_PACKET; + } + + /* reject old packets */ + if (ntohs(tp->th_block) != block + 1) { +#if 0 + printe("TftpDigestPacket: bad block no. %d\n", tp->th_block); +#endif + return TFTP_RECD_BAD_PACKET; + } + + /* some TFTP related check */ + if (block == 0) { + stid = up->uh_sport; + /* in case of a broadcast, remember server address */ + if (tftp_server == IP_BCASTADDR) { + tftp_server = ip->ip_src; +#if 0 + printe("Found TFTP server at "); + IpPrintAddr(tftp_server); + printe("\n"); +#endif + } + } + if (stid != up->uh_sport) { +#if 0 + printe("TftpDigestPacket: incorrect udp source port 0x%x\n", up->uh_sport); +#endif + return TFTP_RECD_BAD_PACKET; + } + + bcopy(&tp->th_data, rbuf, *rlen); + + /* advance to next block */ + block++; + return TFTP_RECD_GOOD_PACKET; +} + +enum TftpStatus { + TFTP_SUCCESS, + TFTP_FAILURE, +}; + +static enum TftpStatus +Tftp(char *rbuf, u_long *rlen) { + u_long time, current, timeout; + int retry, quit; + enum TftpStatus rc = TFTP_FAILURE; + + *rlen = 0; + timeout = 4; /* four seconds */ + for (retry=0, quit=0; ++retry < NRETRIES && !quit; ) { + /* + * Send out a TFTP request. On the first block (actually + * zero) we send out a read request. Every other block we + * just acknowledge. + */ + packet_t *pkt; + ethhdr_t *ep; + udphdr_t *up; + tftphdr_t *tp; +#if TRACE > 0 +printe("Tftp: block %d, try #%d\n", block, retry); +#endif + pkt = PktAlloc(sizeof(ethhdr_t) + sizeof(iphdr_t)); + up = (udphdr_t *) pkt->pkt_offset; + tp = (tftphdr_t *) (pkt->pkt_offset + sizeof(udphdr_t)); + if (block == 0) { /* <RRQ> | <filename> | 0 | "octet" | 0 */ + char *cp, *p; + + tp->th_op = htons(TFTP_RRQ); + cp = tp->th_stuff; + for (p = tftp_file_name; *p; ) + *cp++ = *p++; + *cp++ = '\0'; + *cp++ = 'o'; + *cp++ = 'c'; + *cp++ = 't'; + *cp++ = 'e'; + *cp++ = 't'; + *cp++ = '\0'; + pkt->pkt_len = sizeof(udphdr_t) + (cp - (char *)tp); + } else { /* else <ACK> | <block> */ + tp->th_op = htons(TFTP_ACK); + tp->th_block = htons(block); +#if 0 +printe("ack block %x %x\n", tp->th_block, block); +#endif + pkt->pkt_len = sizeof(udphdr_t) + sizeof(tftphdr_t); + } + up->uh_sport = ctid; + up->uh_dport = stid; + up->uh_sum = 0; + up->uh_len = htons(pkt->pkt_len); +#if 0 +DUMP_STRUCT("tftphdr_t", tp, sizeof(tftphdr_t)); +DUMP_STRUCT("udphdr_t", up, sizeof(udphdr_t)); +printe("Tftp: "); +#endif + IpSend(pkt, tftp_server, tftp_gateway); + + /* + * Receive TFTP data or ARP packets + */ + time = timer() + timeout; + do { + pkt = EtherReceive(); + if (pkt) { + static int spin = 0; + ep = (ethhdr_t *) pkt->pkt_offset; +#if 0 +DUMP_STRUCT("ethhdr_t", ep, sizeof(ethhdr_t)); +#endif + switch (ntohs(ep->eth_proto)) { + case ETHTYPE_ARP: + ArpInput(pkt); + break; + case ETHTYPE_IP: + switch (TftpDigestPacket(pkt, rbuf, rlen)) { + case TFTP_RECD_GOOD_PACKET: + if (block % 8 == 0) + printf("%c\b", "-\\|/"[spin++ % 4]); +#if 0 +DUMP_STRUCT("good tftp packet", pkt, 100); +printe("TBD - copy tftp packet #%d, len %d to buffer\n", block, *rlen); +#endif + rc = TFTP_SUCCESS; + quit = 1; + break; + case TFTP_RECD_SERVER_ABORT: + TftpFail(tftp_server, ip_myaddr, tftp_file_name, "aborted by server"); + + rc = TFTP_FAILURE; + quit = 1; + break; + default: + /* for anything else, retry */ +#if 0 +printe("Tftp: bogus IP packet rec'd, still waiting\n"); +#endif + break; + } + break; + default: +#if 0 +printe("Tftp: undesired ethernet packet (type 0x%x) rec'd, still waiting\n", + ep->eth_proto); +#endif + break; + } + PktRelease(pkt); + } + current = timer(); + HandleKbdAttn(); + } while (current < time && !quit); + +#if 0 +/* TBD - move */ + eth_reset(); +#endif + + if (current >= time) + timeout <<= 1; + } + + if (retry > NRETRIES) { + TftpFail(tftp_server, ip_myaddr, tftp_file_name, "timed Out"); + } + return rc; +} + +static int tftp_at_eof = 1; +static u_long tftp_unread_bytes_in_buffer = 0; + +void +SetTftpParms(ipaddr_t server, ipaddr_t gateway, char *file_name) { + block = 0; + strncpy(tftp_file_name, file_name, MAX_FILE_NAME_LEN); + tftp_server = server; + tftp_at_eof = 0; + tftp_unread_bytes_in_buffer = 0; + stid = htons(IP_PORT_TFTP); + ctid = htons(rand()); + printf("Attempting to tftp file '%s'", tftp_file_name); + if (tftp_server != IP_BCASTADDR) { + printf(" from server "); + IpPrintAddr(tftp_server); + } else + printf(" using IP broadcast"); + tftp_gateway = gateway; + if (tftp_gateway) { + printf(" using gateway "); + IpPrintAddr(tftp_gateway); + } + printf("\n"); +} + +u_long +Read(void *result, u_long n_req) { + static u_long bufp = 0; + static char buf[PKT_DATASIZE]; + u_long length; + u_long n_recd = 0; + while (n_req && !tftp_at_eof) { + if (tftp_unread_bytes_in_buffer) { + *((char *)result)++ = buf[bufp++]; + n_req--; + n_recd++; + tftp_unread_bytes_in_buffer--; + } else { + switch (Tftp(buf, &length)) { + case TFTP_SUCCESS: + tftp_unread_bytes_in_buffer = length; + bufp = 0; + if (length < SEGSIZE) + tftp_at_eof = 1; + break; + default: + /* anything else should cause this to abend */ + tftp_unread_bytes_in_buffer = 0; + tftp_at_eof = 1; + break; + } + } + } + return n_recd; +} + +u_long +PhysRead(u_long addr, u_long n_req) { + u_long n_recd = 0; + while (n_req) { + char buf[512]; + u_long nd = n_req<sizeof(buf) ? n_req : sizeof(buf); + u_long nr = Read(buf, nd); + if (nr == 0) { + /* problem, incomplete read */ + break; + } + PhysBcopy(LA(buf), addr, nr); + n_req -= nr; + n_recd += nr; + addr += nr; + } + return n_recd; +} |