Index: head/stand/libsa/ip.c =================================================================== --- head/stand/libsa/ip.c (revision 329263) +++ head/stand/libsa/ip.c (revision 329264) @@ -1,423 +1,428 @@ /* * Copyright (c) 1992 Regents of the University of California. * All rights reserved. * * This software was developed by the Computer Systems Engineering group * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and * contributed to Berkeley. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * The send and receive functions were originally implemented in udp.c and * moved here. Also it is likely some more cleanup can be done, especially * once we will implement the support for tcp. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "stand.h" #include "net.h" typedef STAILQ_HEAD(ipqueue, ip_queue) ip_queue_t; struct ip_queue { void *ipq_pkt; struct ip *ipq_hdr; STAILQ_ENTRY(ip_queue) ipq_next; }; /* * Fragment re-assembly queue. */ struct ip_reasm { struct in_addr ip_src; struct in_addr ip_dst; uint16_t ip_id; uint8_t ip_proto; uint8_t ip_ttl; size_t ip_total_size; ip_queue_t ip_queue; void *ip_pkt; struct ip *ip_hdr; STAILQ_ENTRY(ip_reasm) ip_next; }; STAILQ_HEAD(ire_list, ip_reasm) ire_list = STAILQ_HEAD_INITIALIZER(ire_list); /* Caller must leave room for ethernet and ip headers in front!! */ ssize_t sendip(struct iodesc *d, void *pkt, size_t len, uint8_t proto) { ssize_t cc; struct ip *ip; u_char *ea; #ifdef NET_DEBUG if (debug) { printf("sendip: proto: %x d=%p called.\n", proto, (void *)d); if (d) { printf("saddr: %s:%d", inet_ntoa(d->myip), ntohs(d->myport)); printf(" daddr: %s:%d\n", inet_ntoa(d->destip), ntohs(d->destport)); } } #endif ip = (struct ip *)pkt - 1; len += sizeof(*ip); bzero(ip, sizeof(*ip)); ip->ip_v = IPVERSION; /* half-char */ ip->ip_hl = sizeof(*ip) >> 2; /* half-char */ ip->ip_len = htons(len); ip->ip_p = proto; /* char */ ip->ip_ttl = IPDEFTTL; /* char */ ip->ip_src = d->myip; ip->ip_dst = d->destip; ip->ip_sum = in_cksum(ip, sizeof(*ip)); /* short, but special */ if (ip->ip_dst.s_addr == INADDR_BROADCAST || ip->ip_src.s_addr == 0 || netmask == 0 || SAMENET(ip->ip_src, ip->ip_dst, netmask)) ea = arpwhohas(d, ip->ip_dst); else ea = arpwhohas(d, gateip); cc = sendether(d, ip, len, ea, ETHERTYPE_IP); if (cc == -1) return (-1); if (cc != len) panic("sendip: bad write (%zd != %zd)", cc, len); return (cc - sizeof(*ip)); } static void ip_reasm_free(struct ip_reasm *ipr) { struct ip_queue *ipq; while ((ipq = STAILQ_FIRST(&ipr->ip_queue)) != NULL) { STAILQ_REMOVE_HEAD(&ipr->ip_queue, ipq_next); free(ipq->ipq_pkt); free(ipq); } free(ipr->ip_pkt); free(ipr); } static int ip_reasm_add(struct ip_reasm *ipr, void *pkt, struct ip *ip) { struct ip_queue *ipq, *prev, *p; if ((ipq = calloc(1, sizeof (*ipq))) == NULL) return (1); ipq->ipq_pkt = pkt; ipq->ipq_hdr = ip; prev = NULL; STAILQ_FOREACH(p, &ipr->ip_queue, ipq_next) { if ((ntohs(p->ipq_hdr->ip_off) & IP_OFFMASK) < (ntohs(ip->ip_off) & IP_OFFMASK)) { prev = p; continue; } if (prev == NULL) break; STAILQ_INSERT_AFTER(&ipr->ip_queue, prev, ipq, ipq_next); return (0); } STAILQ_INSERT_HEAD(&ipr->ip_queue, ipq, ipq_next); return (0); } /* * Receive a IP packet and validate it is for us. */ static ssize_t readipv4(struct iodesc *d, void **pkt, void **payload, time_t tleft, uint8_t proto) { ssize_t n; size_t hlen; struct ether_header *eh; struct ip *ip; struct udphdr *uh; uint16_t etype; /* host order */ char *ptr; struct ip_reasm *ipr; struct ip_queue *ipq, *last; #ifdef NET_DEBUG if (debug) printf("readip: called\n"); #endif ip = NULL; ptr = NULL; n = readether(d, (void **)&ptr, (void **)&ip, tleft, &etype); if (n == -1 || n < sizeof(*ip) + sizeof(*uh)) { free(ptr); return (-1); } /* Ethernet address checks now in readether() */ /* Need to respond to ARP requests. */ if (etype == ETHERTYPE_ARP) { struct arphdr *ah = (void *)ip; if (ah->ar_op == htons(ARPOP_REQUEST)) { /* Send ARP reply */ arp_reply(d, ah); } free(ptr); errno = EAGAIN; /* Call me again. */ return (-1); } if (etype != ETHERTYPE_IP) { #ifdef NET_DEBUG if (debug) printf("readip: not IP. ether_type=%x\n", etype); #endif free(ptr); return (-1); } /* Check ip header */ if (ip->ip_v != IPVERSION || /* half char */ ip->ip_p != proto) { #ifdef NET_DEBUG if (debug) { printf("readip: IP version or proto. ip_v=%d ip_p=%d\n", ip->ip_v, ip->ip_p); } #endif free(ptr); return (-1); } hlen = ip->ip_hl << 2; if (hlen < sizeof(*ip) || in_cksum(ip, hlen) != 0) { #ifdef NET_DEBUG if (debug) printf("readip: short hdr or bad cksum.\n"); #endif free(ptr); return (-1); } if (n < ntohs(ip->ip_len)) { #ifdef NET_DEBUG if (debug) printf("readip: bad length %d < %d.\n", (int)n, ntohs(ip->ip_len)); #endif free(ptr); return (-1); } if (d->myip.s_addr && ip->ip_dst.s_addr != d->myip.s_addr) { #ifdef NET_DEBUG if (debug) { printf("readip: bad saddr %s != ", inet_ntoa(d->myip)); printf("%s\n", inet_ntoa(ip->ip_dst)); } #endif free(ptr); return (-1); } /* Unfragmented packet. */ if ((ntohs(ip->ip_off) & IP_MF) == 0 && (ntohs(ip->ip_off) & IP_OFFMASK) == 0) { uh = (struct udphdr *)((uintptr_t)ip + sizeof (*ip)); /* If there were ip options, make them go away */ if (hlen != sizeof(*ip)) { bcopy(((u_char *)ip) + hlen, uh, uh->uh_ulen - hlen); ip->ip_len = htons(sizeof(*ip)); n -= hlen - sizeof(*ip); } n = (n > (ntohs(ip->ip_len) - sizeof(*ip))) ? ntohs(ip->ip_len) - sizeof(*ip) : n; *pkt = ptr; *payload = (void *)((uintptr_t)ip + sizeof(*ip)); return (n); } STAILQ_FOREACH(ipr, &ire_list, ip_next) { if (ipr->ip_src.s_addr == ip->ip_src.s_addr && ipr->ip_dst.s_addr == ip->ip_dst.s_addr && ipr->ip_id == ip->ip_id && ipr->ip_proto == ip->ip_p) break; } /* Allocate new reassembly entry */ if (ipr == NULL) { if ((ipr = calloc(1, sizeof (*ipr))) == NULL) { free(ptr); return (-1); } ipr->ip_src = ip->ip_src; ipr->ip_dst = ip->ip_dst; ipr->ip_id = ip->ip_id; ipr->ip_proto = ip->ip_p; ipr->ip_ttl = MAXTTL; STAILQ_INIT(&ipr->ip_queue); STAILQ_INSERT_TAIL(&ire_list, ipr, ip_next); } if (ip_reasm_add(ipr, ptr, ip) != 0) { STAILQ_REMOVE(&ire_list, ipr, ip_reasm, ip_next); free(ipr); free(ptr); return (-1); } if ((ntohs(ip->ip_off) & IP_MF) == 0) { ipr->ip_total_size = (8 * (ntohs(ip->ip_off) & IP_OFFMASK)); ipr->ip_total_size += n + sizeof (*ip); ipr->ip_total_size += sizeof (struct ether_header); ipr->ip_pkt = malloc(ipr->ip_total_size + 2); if (ipr->ip_pkt == NULL) { STAILQ_REMOVE(&ire_list, ipr, ip_reasm, ip_next); ip_reasm_free(ipr); return (-1); } } /* * If we do not have re-assembly buffer ipr->ip_pkt, we are still * missing fragments, so just restart the read. */ if (ipr->ip_pkt == NULL) { errno = EAGAIN; return (-1); } /* * Walk the packet list in reassembly queue, if we got all the * fragments, build the packet. */ n = 0; last = NULL; STAILQ_FOREACH(ipq, &ipr->ip_queue, ipq_next) { if ((ntohs(ipq->ipq_hdr->ip_off) & IP_OFFMASK) != n / 8) { STAILQ_REMOVE(&ire_list, ipr, ip_reasm, ip_next); ip_reasm_free(ipr); return (-1); } n += ntohs(ipq->ipq_hdr->ip_len) - (ipq->ipq_hdr->ip_hl << 2); last = ipq; } if ((ntohs(last->ipq_hdr->ip_off) & IP_MF) != 0) { errno = EAGAIN; return (-1); } ipq = STAILQ_FIRST(&ipr->ip_queue); /* Fabricate ethernet header */ eh = (struct ether_header *)((uintptr_t)ipr->ip_pkt + 2); bcopy((void *)((uintptr_t)ipq->ipq_pkt + 2), eh, sizeof (*eh)); /* Fabricate IP header */ ipr->ip_hdr = (struct ip *)((uintptr_t)eh + sizeof (*eh)); bcopy(ipq->ipq_hdr, ipr->ip_hdr, sizeof (*ipr->ip_hdr)); ipr->ip_hdr->ip_hl = sizeof (*ipr->ip_hdr) >> 2; ipr->ip_hdr->ip_len = htons(n); ipr->ip_hdr->ip_sum = 0; ipr->ip_hdr->ip_sum = in_cksum(ipr->ip_hdr, sizeof (*ipr->ip_hdr)); n = 0; ptr = (char *)((uintptr_t)ipr->ip_hdr + sizeof (*ipr->ip_hdr)); STAILQ_FOREACH(ipq, &ipr->ip_queue, ipq_next) { char *data; size_t len; hlen = ipq->ipq_hdr->ip_hl << 2; len = ntohs(ipq->ipq_hdr->ip_len) - hlen; data = (char *)((uintptr_t)ipq->ipq_hdr + hlen); bcopy(data, ptr + n, len); n += len; } *pkt = ipr->ip_pkt; ipr->ip_pkt = NULL; /* Avoid free from ip_reasm_free() */ *payload = ptr; /* Clean up the reassembly list */ while ((ipr = STAILQ_FIRST(&ire_list)) != NULL) { STAILQ_REMOVE_HEAD(&ire_list, ip_next); ip_reasm_free(ipr); } return (n); } /* * Receive a IP packet. */ ssize_t readip(struct iodesc *d, void **pkt, void **payload, time_t tleft, uint8_t proto) { time_t t; ssize_t ret = -1; t = getsecs(); while ((getsecs() - t) < tleft) { errno = 0; ret = readipv4(d, pkt, payload, tleft, proto); + if (ret >= 0) + return (ret); + /* Bubble up the error if it wasn't successful */ if (errno != EAGAIN) - break; + return (-1); } - return (ret); + /* We've exhausted tleft; timeout */ + errno = ETIMEDOUT; + return (-1); } Index: head/stand/libsa/net.c =================================================================== --- head/stand/libsa/net.c (revision 329263) +++ head/stand/libsa/net.c (revision 329264) @@ -1,283 +1,283 @@ /* $NetBSD: net.c,v 1.20 1997/12/26 22:41:30 scottr Exp $ */ /* * Copyright (c) 1992 Regents of the University of California. * All rights reserved. * * This software was developed by the Computer Systems Engineering group * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and * contributed to Berkeley. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#) Header: net.c,v 1.9 93/08/06 19:32:15 leres Exp (LBL) */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "stand.h" #include "net.h" /* * Send a packet and wait for a reply, with exponential backoff. * * The send routine must return the actual number of bytes written, * or -1 on error. * * The receive routine can indicate success by returning the number of * bytes read; it can return 0 to indicate EOF; it can return -1 with a * non-zero errno to indicate failure; finally, it can return -1 with a * zero errno to indicate it isn't done yet. */ ssize_t sendrecv(struct iodesc *d, ssize_t (*sproc)(struct iodesc *, void *, size_t), void *sbuf, size_t ssize, ssize_t (*rproc)(struct iodesc *, void **, void **, time_t), void **pkt, void **payload) { ssize_t cc; time_t t, tmo, tlast; long tleft; #ifdef NET_DEBUG if (debug) printf("sendrecv: called\n"); #endif tmo = MINTMO; tlast = 0; tleft = 0; t = getsecs(); for (;;) { if (tleft <= 0) { if (tmo >= MAXTMO) { errno = ETIMEDOUT; return -1; } cc = (*sproc)(d, sbuf, ssize); if (cc != -1 && cc < ssize) panic("sendrecv: short write! (%zd < %zd)", cc, ssize); tleft = tmo; tmo += MINTMO; if (tmo > MAXTMO) tmo = MAXTMO; if (cc == -1) { /* Error on transmit; wait before retrying */ while ((getsecs() - t) < tmo) ; tleft = 0; continue; } tlast = t; } /* Try to get a packet and process it. */ cc = (*rproc)(d, pkt, payload, tleft); /* Return on data, EOF or real error. */ - if (cc != -1 || errno != 0) + if (cc != -1 || (errno != 0 && errno != ETIMEDOUT)) return (cc); /* Timed out or didn't get the packet we're waiting for */ t = getsecs(); tleft -= t - tlast; tlast = t; } } /* * Like inet_addr() in the C library, but we only accept base-10. * Return values are in network order. */ n_long inet_addr(char *cp) { u_long val; int n; char c; u_int parts[4]; u_int *pp = parts; for (;;) { /* * Collect number up to ``.''. * Values are specified as for C: * 0x=hex, 0=octal, other=decimal. */ val = 0; while ((c = *cp) != '\0') { if (c >= '0' && c <= '9') { val = (val * 10) + (c - '0'); cp++; continue; } break; } if (*cp == '.') { /* * Internet format: * a.b.c.d * a.b.c (with c treated as 16-bits) * a.b (with b treated as 24 bits) */ if (pp >= parts + 3 || val > 0xff) goto bad; *pp++ = val, cp++; } else break; } /* * Check for trailing characters. */ if (*cp != '\0') goto bad; /* * Concoct the address according to * the number of parts specified. */ n = pp - parts + 1; switch (n) { case 1: /* a -- 32 bits */ break; case 2: /* a.b -- 8.24 bits */ if (val > 0xffffff) goto bad; val |= parts[0] << 24; break; case 3: /* a.b.c -- 8.8.16 bits */ if (val > 0xffff) goto bad; val |= (parts[0] << 24) | (parts[1] << 16); break; case 4: /* a.b.c.d -- 8.8.8.8 bits */ if (val > 0xff) goto bad; val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8); break; } return (htonl(val)); bad: return (htonl(INADDR_NONE)); } char * inet_ntoa(struct in_addr ia) { return (intoa(ia.s_addr)); } /* Similar to inet_ntoa() */ char * intoa(n_long addr) { char *cp; u_int byte; int n; static char buf[17]; /* strlen(".255.255.255.255") + 1 */ addr = ntohl(addr); cp = &buf[sizeof buf]; *--cp = '\0'; n = 4; do { byte = addr & 0xff; *--cp = byte % 10 + '0'; byte /= 10; if (byte > 0) { *--cp = byte % 10 + '0'; byte /= 10; if (byte > 0) *--cp = byte + '0'; } *--cp = '.'; addr >>= 8; } while (--n > 0); return (cp+1); } static char * number(char *s, int *n) { for (*n = 0; isdigit(*s); s++) *n = (*n * 10) + *s - '0'; return s; } n_long ip_convertaddr(char *p) { #define IP_ANYADDR 0 n_long addr = 0, n; if (p == (char *)0 || *p == '\0') return IP_ANYADDR; p = number(p, &n); addr |= (n << 24) & 0xff000000; if (*p == '\0' || *p++ != '.') return IP_ANYADDR; p = number(p, &n); addr |= (n << 16) & 0xff0000; if (*p == '\0' || *p++ != '.') return IP_ANYADDR; p = number(p, &n); addr |= (n << 8) & 0xff00; if (*p == '\0' || *p++ != '.') return IP_ANYADDR; p = number(p, &n); addr |= n & 0xff; if (*p != '\0') return IP_ANYADDR; return htonl(addr); } Index: head/stand/libsa/tftp.c =================================================================== --- head/stand/libsa/tftp.c (revision 329263) +++ head/stand/libsa/tftp.c (revision 329264) @@ -1,785 +1,791 @@ /* $NetBSD: tftp.c,v 1.4 1997/09/17 16:57:07 drochner Exp $ */ /* * Copyright (c) 1996 * Matthias Drochner. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed for the NetBSD Project * by Matthias Drochner. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Simple TFTP implementation for libsa. * Assumes: * - socket descriptor (int) at open_file->f_devdata * - server host IP in global servip * Restrictions: * - read only * - lseek only with SEEK_SET or SEEK_CUR * - no big time differences between transfers ( #include #include #include #include #include #include #include "stand.h" #include "net.h" #include "netif.h" #include "tftp.h" struct tftp_handle; static int tftp_open(const char *path, struct open_file *f); static int tftp_close(struct open_file *f); static int tftp_parse_oack(struct tftp_handle *h, char *buf, size_t len); static int tftp_read(struct open_file *f, void *buf, size_t size, size_t *resid); static int tftp_write(struct open_file *f, void *buf, size_t size, size_t *resid); static off_t tftp_seek(struct open_file *f, off_t offset, int where); static int tftp_set_blksize(struct tftp_handle *h, const char *str); static int tftp_stat(struct open_file *f, struct stat *sb); static ssize_t sendrecv_tftp(struct tftp_handle *h, ssize_t (*sproc)(struct iodesc *, void *, size_t), void *sbuf, size_t ssize, ssize_t (*rproc)(struct tftp_handle *h, void **, void **, time_t, unsigned short *), void **, void **, unsigned short *rtype); struct fs_ops tftp_fsops = { "tftp", tftp_open, tftp_close, tftp_read, tftp_write, tftp_seek, tftp_stat, null_readdir }; extern struct in_addr servip; static int tftpport = 2000; static int is_open = 0; /* * The legacy TFTP_BLKSIZE value was SEGSIZE(512). * TFTP_REQUESTED_BLKSIZE of 1428 is (Ethernet MTU, less the TFTP, UDP and * IP header lengths). */ #define TFTP_REQUESTED_BLKSIZE 1428 /* * Choose a blksize big enough so we can test with Ethernet * Jumbo frames in the future. */ #define TFTP_MAX_BLKSIZE 9008 struct tftp_handle { struct iodesc *iodesc; int currblock; /* contents of lastdata */ int islastblock; /* flag */ int validsize; int off; char *path; /* saved for re-requests */ unsigned int tftp_blksize; unsigned long tftp_tsize; void *pkt; struct tftphdr *tftp_hdr; }; #define TFTP_MAX_ERRCODE EOPTNEG static const int tftperrors[TFTP_MAX_ERRCODE + 1] = { 0, /* ??? */ ENOENT, EPERM, ENOSPC, EINVAL, /* ??? */ EINVAL, /* ??? */ EEXIST, EINVAL, /* ??? */ EINVAL, /* Option negotiation failed. */ }; static int tftp_getnextblock(struct tftp_handle *h); /* send error message back. */ static void tftp_senderr(struct tftp_handle *h, u_short errcode, const char *msg) { struct { u_char header[HEADER_SIZE]; struct tftphdr t; u_char space[63]; /* +1 from t */ } __packed __aligned(4) wbuf; char *wtail; int len; len = strlen(msg); if (len > sizeof(wbuf.space)) len = sizeof(wbuf.space); wbuf.t.th_opcode = htons((u_short) ERROR); wbuf.t.th_code = htons(errcode); wtail = wbuf.t.th_msg; bcopy(msg, wtail, len); wtail[len] = '\0'; wtail += len + 1; sendudp(h->iodesc, &wbuf.t, wtail - (char *) &wbuf.t); } static void tftp_sendack(struct tftp_handle *h) { struct { u_char header[HEADER_SIZE]; struct tftphdr t; } __packed __aligned(4) wbuf; char *wtail; wbuf.t.th_opcode = htons((u_short) ACK); wtail = (char *) &wbuf.t.th_block; wbuf.t.th_block = htons((u_short) h->currblock); wtail += 2; sendudp(h->iodesc, &wbuf.t, wtail - (char *) &wbuf.t); } static ssize_t recvtftp(struct tftp_handle *h, void **pkt, void **payload, time_t tleft, unsigned short *rtype) { struct iodesc *d = h->iodesc; struct tftphdr *t; void *ptr = NULL; ssize_t len; errno = 0; len = readudp(d, &ptr, (void **)&t, tleft); if (len < 4) { free(ptr); return (-1); } *rtype = ntohs(t->th_opcode); switch (ntohs(t->th_opcode)) { case DATA: { int got; if (htons(t->th_block) != (u_short) d->xid) { /* * Expected block? */ free(ptr); return (-1); } if (d->xid == 1) { /* * First data packet from new port. */ struct udphdr *uh; uh = (struct udphdr *) t - 1; d->destport = uh->uh_sport; } /* else check uh_sport has not changed??? */ got = len - (t->th_data - (char *)t); *pkt = ptr; *payload = t; return (got); } case ERROR: if ((unsigned) ntohs(t->th_code) > TFTP_MAX_ERRCODE) { printf("illegal tftp error %d\n", ntohs(t->th_code)); errno = EIO; } else { #ifdef TFTP_DEBUG printf("tftp-error %d\n", ntohs(t->th_code)); #endif errno = tftperrors[ntohs(t->th_code)]; } free(ptr); return (-1); case OACK: { struct udphdr *uh; int tftp_oack_len; /* * Unexpected OACK. TFTP transfer already in progress. * Drop the pkt. */ if (d->xid != 1) { free(ptr); return (-1); } /* * Remember which port this OACK came from, because we need * to send the ACK or errors back to it. */ uh = (struct udphdr *) t - 1; d->destport = uh->uh_sport; /* Parse options ACK-ed by the server. */ tftp_oack_len = len - sizeof(t->th_opcode); if (tftp_parse_oack(h, t->th_u.tu_stuff, tftp_oack_len) != 0) { tftp_senderr(h, EOPTNEG, "Malformed OACK"); errno = EIO; free(ptr); return (-1); } *pkt = ptr; *payload = t; return (0); } default: #ifdef TFTP_DEBUG printf("tftp type %d not handled\n", ntohs(t->th_opcode)); #endif free(ptr); return (-1); } } /* send request, expect first block (or error) */ static int tftp_makereq(struct tftp_handle *h) { struct { u_char header[HEADER_SIZE]; struct tftphdr t; u_char space[FNAME_SIZE + 6]; } __packed __aligned(4) wbuf; char *wtail; int l; ssize_t res; void *pkt; struct tftphdr *t; char *tftp_blksize = NULL; int blksize_l; unsigned short rtype = 0; /* * Allow overriding default TFTP block size by setting * a tftp.blksize environment variable. */ if ((tftp_blksize = getenv("tftp.blksize")) != NULL) { tftp_set_blksize(h, tftp_blksize); } wbuf.t.th_opcode = htons((u_short) RRQ); wtail = wbuf.t.th_stuff; l = strlen(h->path); #ifdef TFTP_PREPEND_PATH if (l > FNAME_SIZE - (sizeof(TFTP_PREPEND_PATH) - 1)) return (ENAMETOOLONG); bcopy(TFTP_PREPEND_PATH, wtail, sizeof(TFTP_PREPEND_PATH) - 1); wtail += sizeof(TFTP_PREPEND_PATH) - 1; #else if (l > FNAME_SIZE) return (ENAMETOOLONG); #endif bcopy(h->path, wtail, l + 1); wtail += l + 1; bcopy("octet", wtail, 6); wtail += 6; bcopy("blksize", wtail, 8); wtail += 8; blksize_l = sprintf(wtail, "%d", h->tftp_blksize); wtail += blksize_l + 1; bcopy("tsize", wtail, 6); wtail += 6; bcopy("0", wtail, 2); wtail += 2; /* h->iodesc->myport = htons(--tftpport); */ h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff)); h->iodesc->destport = htons(IPPORT_TFTP); h->iodesc->xid = 1; /* expected block */ h->currblock = 0; h->islastblock = 0; h->validsize = 0; pkt = NULL; res = sendrecv_tftp(h, &sendudp, &wbuf.t, wtail - (char *) &wbuf.t, &recvtftp, &pkt, (void **)&t, &rtype); if (res == -1) { free(pkt); return (errno); } free(h->pkt); h->pkt = pkt; h->tftp_hdr = t; if (rtype == OACK) return (tftp_getnextblock(h)); /* Server ignored our blksize request, revert to TFTP default. */ h->tftp_blksize = SEGSIZE; switch (rtype) { case DATA: { h->currblock = 1; h->validsize = res; h->islastblock = 0; if (res < h->tftp_blksize) { h->islastblock = 1; /* very short file */ tftp_sendack(h); } return (0); } case ERROR: default: return (errno); } } /* ack block, expect next */ static int tftp_getnextblock(struct tftp_handle *h) { struct { u_char header[HEADER_SIZE]; struct tftphdr t; } __packed __aligned(4) wbuf; char *wtail; int res; void *pkt; struct tftphdr *t; unsigned short rtype = 0; wbuf.t.th_opcode = htons((u_short) ACK); wtail = (char *) &wbuf.t.th_block; wbuf.t.th_block = htons((u_short) h->currblock); wtail += 2; h->iodesc->xid = h->currblock + 1; /* expected block */ pkt = NULL; res = sendrecv_tftp(h, &sendudp, &wbuf.t, wtail - (char *) &wbuf.t, &recvtftp, &pkt, (void **)&t, &rtype); if (res == -1) { /* 0 is OK! */ free(pkt); return (errno); } free(h->pkt); h->pkt = pkt; h->tftp_hdr = t; h->currblock++; h->validsize = res; if (res < h->tftp_blksize) h->islastblock = 1; /* EOF */ if (h->islastblock == 1) { /* Send an ACK for the last block */ wbuf.t.th_block = htons((u_short) h->currblock); sendudp(h->iodesc, &wbuf.t, wtail - (char *)&wbuf.t); } return (0); } static int tftp_open(const char *path, struct open_file *f) { struct tftp_handle *tftpfile; struct iodesc *io; int res; size_t pathsize; const char *extraslash; if (netproto != NET_TFTP) return (EINVAL); if (f->f_dev->dv_type != DEVT_NET) return (EINVAL); if (is_open) return (EBUSY); tftpfile = (struct tftp_handle *) malloc(sizeof(*tftpfile)); if (!tftpfile) return (ENOMEM); memset(tftpfile, 0, sizeof(*tftpfile)); tftpfile->tftp_blksize = TFTP_REQUESTED_BLKSIZE; tftpfile->iodesc = io = socktodesc(*(int *) (f->f_devdata)); if (io == NULL) return (EINVAL); io->destip = servip; tftpfile->off = 0; pathsize = (strlen(rootpath) + 1 + strlen(path) + 1) * sizeof(char); tftpfile->path = malloc(pathsize); if (tftpfile->path == NULL) { free(tftpfile); return(ENOMEM); } if (rootpath[strlen(rootpath) - 1] == '/' || path[0] == '/') extraslash = ""; else extraslash = "/"; res = snprintf(tftpfile->path, pathsize, "%s%s%s", rootpath, extraslash, path); if (res < 0 || res > pathsize) { free(tftpfile->path); free(tftpfile); return(ENOMEM); } res = tftp_makereq(tftpfile); if (res) { free(tftpfile->path); free(tftpfile->pkt); free(tftpfile); return (res); } f->f_fsdata = (void *) tftpfile; is_open = 1; return (0); } static int tftp_read(struct open_file *f, void *addr, size_t size, size_t *resid /* out */) { struct tftp_handle *tftpfile; tftpfile = (struct tftp_handle *) f->f_fsdata; while (size > 0) { int needblock, count; twiddle(32); needblock = tftpfile->off / tftpfile->tftp_blksize + 1; if (tftpfile->currblock > needblock) { /* seek backwards */ tftp_senderr(tftpfile, 0, "No error: read aborted"); tftp_makereq(tftpfile); /* no error check, it worked * for open */ } while (tftpfile->currblock < needblock) { int res; res = tftp_getnextblock(tftpfile); if (res) { /* no answer */ #ifdef TFTP_DEBUG printf("tftp: read error\n"); #endif return (res); } if (tftpfile->islastblock) break; } if (tftpfile->currblock == needblock) { int offinblock, inbuffer; offinblock = tftpfile->off % tftpfile->tftp_blksize; inbuffer = tftpfile->validsize - offinblock; if (inbuffer < 0) { #ifdef TFTP_DEBUG printf("tftp: invalid offset %d\n", tftpfile->off); #endif return (EINVAL); } count = (size < inbuffer ? size : inbuffer); bcopy(tftpfile->tftp_hdr->th_data + offinblock, addr, count); addr = (char *)addr + count; tftpfile->off += count; size -= count; if ((tftpfile->islastblock) && (count == inbuffer)) break; /* EOF */ } else { #ifdef TFTP_DEBUG printf("tftp: block %d not found\n", needblock); #endif return (EINVAL); } } if (resid) *resid = size; return (0); } static int tftp_close(struct open_file *f) { struct tftp_handle *tftpfile; tftpfile = (struct tftp_handle *) f->f_fsdata; /* let it time out ... */ if (tftpfile) { free(tftpfile->path); free(tftpfile->pkt); free(tftpfile); } is_open = 0; return (0); } static int tftp_write(struct open_file *f __unused, void *start __unused, size_t size __unused, size_t *resid __unused /* out */) { return (EROFS); } static int tftp_stat(struct open_file *f, struct stat *sb) { struct tftp_handle *tftpfile; tftpfile = (struct tftp_handle *) f->f_fsdata; sb->st_mode = 0444 | S_IFREG; sb->st_nlink = 1; sb->st_uid = 0; sb->st_gid = 0; sb->st_size = (off_t) tftpfile->tftp_tsize; return (0); } static off_t tftp_seek(struct open_file *f, off_t offset, int where) { struct tftp_handle *tftpfile; tftpfile = (struct tftp_handle *) f->f_fsdata; switch (where) { case SEEK_SET: tftpfile->off = offset; break; case SEEK_CUR: tftpfile->off += offset; break; default: errno = EOFFSET; return (-1); } return (tftpfile->off); } static ssize_t sendrecv_tftp(struct tftp_handle *h, ssize_t (*sproc)(struct iodesc *, void *, size_t), void *sbuf, size_t ssize, ssize_t (*rproc)(struct tftp_handle *, void **, void **, time_t, unsigned short *), void **pkt, void **payload, unsigned short *rtype) { struct iodesc *d = h->iodesc; ssize_t cc; time_t t, t1, tleft; #ifdef TFTP_DEBUG if (debug) printf("sendrecv: called\n"); #endif tleft = MINTMO; t = t1 = getsecs(); for (;;) { if ((getsecs() - t) > MAXTMO) { errno = ETIMEDOUT; return -1; } cc = (*sproc)(d, sbuf, ssize); if (cc != -1 && cc < ssize) panic("sendrecv: short write! (%zd < %zu)", cc, ssize); if (cc == -1) { /* Error on transmit; wait before retrying */ while ((getsecs() - t1) < tleft); + t1 = getsecs(); continue; } + t = t1 = getsecs(); recvnext: + if ((getsecs() - t) > MAXTMO) { + errno = ETIMEDOUT; + return -1; + } /* Try to get a packet and process it. */ cc = (*rproc)(h, pkt, payload, tleft, rtype); /* Return on data, EOF or real error. */ - if (cc != -1 || errno != 0) + if (cc != -1 || (errno != 0 && errno != ETIMEDOUT)) return (cc); if ((getsecs() - t1) < tleft) { goto recvnext; } /* Timed out or didn't get the packet we're waiting for */ tleft += MINTMO; if (tleft > (2 * MINTMO)) { tleft = (2 * MINTMO); } t1 = getsecs(); } } static int tftp_set_blksize(struct tftp_handle *h, const char *str) { char *endptr; int new_blksize; int ret = 0; if (h == NULL || str == NULL) return (ret); new_blksize = (unsigned int)strtol(str, &endptr, 0); /* * Only accept blksize value if it is numeric. * RFC2348 specifies that acceptable values are 8-65464. * Let's choose a limit less than MAXRSPACE. */ if (*endptr == '\0' && new_blksize >= 8 && new_blksize <= TFTP_MAX_BLKSIZE) { h->tftp_blksize = new_blksize; ret = 1; } return (ret); } /* * In RFC2347, the TFTP Option Acknowledgement package (OACK) * is used to acknowledge a client's option negotiation request. * The format of an OACK packet is: * +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ * | opc | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 | * +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ * * opc * The opcode field contains a 6, for Option Acknowledgment. * * opt1 * The first option acknowledgment, copied from the original * request. * * value1 * The acknowledged value associated with the first option. If * and how this value may differ from the original request is * detailed in the specification for the option. * * optN, valueN * The final option/value acknowledgment pair. */ static int tftp_parse_oack(struct tftp_handle *h, char *buf, size_t len) { /* * We parse the OACK strings into an array * of name-value pairs. */ char *tftp_options[128] = { 0 }; char *val = buf; int i = 0; int option_idx = 0; int blksize_is_set = 0; int tsize = 0; unsigned int orig_blksize; while (option_idx < 128 && i < len) { if (buf[i] == '\0') { if (&buf[i] > val) { tftp_options[option_idx] = val; val = &buf[i] + 1; ++option_idx; } } ++i; } /* Save the block size we requested for sanity check later. */ orig_blksize = h->tftp_blksize; /* * Parse individual TFTP options. * * "blksize" is specified in RFC2348. * * "tsize" is specified in RFC2349. */ for (i = 0; i < option_idx; i += 2) { if (strcasecmp(tftp_options[i], "blksize") == 0) { if (i + 1 < option_idx) blksize_is_set = tftp_set_blksize(h, tftp_options[i + 1]); } else if (strcasecmp(tftp_options[i], "tsize") == 0) { if (i + 1 < option_idx) tsize = strtol(tftp_options[i + 1], (char **)NULL, 10); if (tsize != 0) h->tftp_tsize = tsize; } else { /* Do not allow any options we did not expect to be ACKed. */ printf("unexpected tftp option '%s'\n", tftp_options[i]); return (-1); } } if (!blksize_is_set) { /* * If TFTP blksize was not set, try defaulting * to the legacy TFTP blksize of SEGSIZE(512) */ h->tftp_blksize = SEGSIZE; } else if (h->tftp_blksize > orig_blksize) { /* * Server should not be proposing block sizes that * exceed what we said we can handle. */ printf("unexpected blksize %u\n", h->tftp_blksize); return (-1); } #ifdef TFTP_DEBUG printf("tftp_blksize: %u\n", h->tftp_blksize); printf("tftp_tsize: %lu\n", h->tftp_tsize); #endif return 0; }