diff options
-rw-r--r-- | Changelog | 1 | ||||
-rw-r--r-- | Makefile.target | 2 | ||||
-rw-r--r-- | slirp/slirp.h | 1 | ||||
-rw-r--r-- | slirp/tftp.c | 339 | ||||
-rw-r--r-- | slirp/tftp.h | 34 | ||||
-rw-r--r-- | slirp/udp.c | 8 | ||||
-rw-r--r-- | vl.c | 11 |
7 files changed, 395 insertions, 1 deletions
@@ -8,6 +8,7 @@ version 0.6.1: transparent decompression - VMware 3 and 4 read-only disk image support (untested) - Support for up to 4 serial ports + - TFTP server support (Magnus Damm) version 0.6.0: diff --git a/Makefile.target b/Makefile.target index 6d5849071..5ac50dc09 100644 --- a/Makefile.target +++ b/Makefile.target @@ -264,7 +264,7 @@ ifdef CONFIG_SLIRP DEFINES+=-I$(SRC_PATH)/slirp SLIRP_OBJS=cksum.o if.o ip_icmp.o ip_input.o ip_output.o \ slirp.o mbuf.o misc.o sbuf.o socket.o tcp_input.o tcp_output.o \ -tcp_subr.o tcp_timer.o udp.o bootp.o debug.o +tcp_subr.o tcp_timer.o udp.o bootp.o debug.o tftp.o VL_OBJS+=$(addprefix slirp/, $(SLIRP_OBJS)) endif diff --git a/slirp/slirp.h b/slirp/slirp.h index f5c93c5ee..b52044a65 100644 --- a/slirp/slirp.h +++ b/slirp/slirp.h @@ -210,6 +210,7 @@ int inet_aton _P((const char *cp, struct in_addr *ia)); #endif #include "bootp.h" +#include "tftp.h" #include "libslirp.h" extern struct ttys *ttys_unit[MAX_INTERFACES]; diff --git a/slirp/tftp.c b/slirp/tftp.c new file mode 100644 index 000000000..1bcc70fa5 --- /dev/null +++ b/slirp/tftp.c @@ -0,0 +1,339 @@ +/* + * tftp.c - a simple, read-only tftp server for qemu + * + * Copyright (c) 2004 Magnus Damm <damm@opensource.se> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <slirp.h> + +struct tftp_session { + int in_use; + unsigned char filename[TFTP_FILENAME_MAX]; + + struct in_addr client_ip; + u_int16_t client_port; + + struct timeval timestamp; +}; + +struct tftp_session tftp_sessions[TFTP_SESSIONS_MAX]; + +char *tftp_prefix; + +static void tftp_session_update(struct tftp_session *spt) +{ + gettimeofday(&spt->timestamp, 0); + spt->in_use = 1; +} + +static void tftp_session_terminate(struct tftp_session *spt) +{ + spt->in_use = 0; +} + +static int tftp_session_allocate(struct tftp_t *tp) +{ + struct tftp_session *spt; + struct timeval tv; + int k; + + gettimeofday(&tv, 0); + + for (k = 0; k < TFTP_SESSIONS_MAX; k++) { + spt = &tftp_sessions[k]; + + if (!spt->in_use) { + goto found; + } + + /* sessions time out after 5 inactive seconds */ + + if (tv.tv_sec > (spt->timestamp.tv_sec + 5)) { + goto found; + } + } + + return -1; + + found: + memset(spt, 0, sizeof(*spt)); + memcpy(&spt->client_ip, &tp->ip.ip_src, sizeof(spt->client_ip)); + spt->client_port = tp->udp.uh_sport; + + tftp_session_update(spt); + + return k; +} + +static int tftp_session_find(struct tftp_t *tp) +{ + struct tftp_session *spt; + int k; + + for (k = 0; k < TFTP_SESSIONS_MAX; k++) { + spt = &tftp_sessions[k]; + + if (spt->in_use) { + if (!memcmp(&spt->client_ip, &tp->ip.ip_src, sizeof(spt->client_ip))) { + if (spt->client_port == tp->udp.uh_sport) { + return k; + } + } + } + } + + return -1; +} + +static int tftp_read_data(struct tftp_session *spt, u_int16_t block_nr, + u_int8_t *buf, int len) +{ + int fd; + int bytes_read = 0; + + fd = open(spt->filename, O_RDONLY); + + if (fd < 0) { + return -1; + } + + if (len) { + lseek(fd, block_nr * 512, SEEK_SET); + + bytes_read = read(fd, buf, len); + } + + close(fd); + + return bytes_read; +} + +static int tftp_send_error(struct tftp_session *spt, + u_int16_t errorcode, const char *msg, + struct tftp_t *recv_tp) +{ + struct sockaddr_in saddr, daddr; + struct mbuf *m; + struct tftp_t *tp; + int nobytes; + + m = m_get(); + + if (!m) { + return -1; + } + + memset(m->m_data, 0, m->m_size); + + m->m_data += if_maxlinkhdr; + tp = (void *)m->m_data; + m->m_data += sizeof(struct udpiphdr); + + tp->tp_op = htons(TFTP_ERROR); + tp->x.tp_error.tp_error_code = htons(errorcode); + strcpy(tp->x.tp_error.tp_msg, msg); + + saddr.sin_addr = recv_tp->ip.ip_dst; + saddr.sin_port = recv_tp->udp.uh_dport; + + daddr.sin_addr = spt->client_ip; + daddr.sin_port = spt->client_port; + + nobytes = 2; + + m->m_len = sizeof(struct tftp_t) - 514 + 3 + strlen(msg) - + sizeof(struct ip) - sizeof(struct udphdr); + + udp_output2(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); + + tftp_session_terminate(spt); + + return 0; +} + +static int tftp_send_data(struct tftp_session *spt, + u_int16_t block_nr, + struct tftp_t *recv_tp) +{ + struct sockaddr_in saddr, daddr; + struct mbuf *m; + struct tftp_t *tp; + int nobytes; + + if (block_nr < 1) { + return -1; + } + + m = m_get(); + + if (!m) { + return -1; + } + + memset(m->m_data, 0, m->m_size); + + m->m_data += if_maxlinkhdr; + tp = (void *)m->m_data; + m->m_data += sizeof(struct udpiphdr); + + tp->tp_op = htons(TFTP_DATA); + tp->x.tp_data.tp_block_nr = htons(block_nr); + + saddr.sin_addr = recv_tp->ip.ip_dst; + saddr.sin_port = recv_tp->udp.uh_dport; + + daddr.sin_addr = spt->client_ip; + daddr.sin_port = spt->client_port; + + nobytes = tftp_read_data(spt, block_nr - 1, tp->x.tp_data.tp_buf, 512); + + if (nobytes < 0) { + m_free(m); + + /* send "file not found" error back */ + + tftp_send_error(spt, 1, "File not found", tp); + + return -1; + } + + m->m_len = sizeof(struct tftp_t) - (512 - nobytes) - + sizeof(struct ip) - sizeof(struct udphdr); + + udp_output2(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); + + if (nobytes == 512) { + tftp_session_update(spt); + } + else { + tftp_session_terminate(spt); + } + + return 0; +} + +static void tftp_handle_rrq(struct tftp_t *tp, int pktlen) +{ + struct tftp_session *spt; + int s, k, n; + u_int8_t *src, *dst; + + s = tftp_session_allocate(tp); + + if (s < 0) { + return; + } + + spt = &tftp_sessions[s]; + + src = tp->x.tp_buf; + dst = spt->filename; + n = pktlen - ((uint8_t *)&tp->x.tp_buf[0] - (uint8_t *)tp); + + /* get name */ + + for (k = 0; k < n; k++) { + if (k < TFTP_FILENAME_MAX) { + dst[k] = src[k]; + } + else { + return; + } + + if (src[k] == '\0') { + break; + } + } + + if (k >= n) { + return; + } + + k++; + + /* check mode */ + if ((n - k) < 6) { + return; + } + + if (memcmp(&src[k], "octet\0", 6) != 0) { + tftp_send_error(spt, 4, "Unsupported transfer mode", tp); + return; + } + + /* do sanity checks on the filename */ + + if ((spt->filename[0] != '/') + || (spt->filename[strlen(spt->filename) - 1] == '/') + || strstr(spt->filename, "/../")) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + /* only allow exported prefixes */ + + if (!tftp_prefix + || (strncmp(spt->filename, tftp_prefix, strlen(tftp_prefix)) != 0)) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + /* check if the file exists */ + + if (tftp_read_data(spt, 0, spt->filename, 0) < 0) { + tftp_send_error(spt, 1, "File not found", tp); + return; + } + + tftp_send_data(spt, 1, tp); +} + +static void tftp_handle_ack(struct tftp_t *tp, int pktlen) +{ + int s; + + s = tftp_session_find(tp); + + if (s < 0) { + return; + } + + if (tftp_send_data(&tftp_sessions[s], + ntohs(tp->x.tp_data.tp_block_nr) + 1, + tp) < 0) { + return; + } +} + +void tftp_input(struct mbuf *m) +{ + struct tftp_t *tp = (struct tftp_t *)m->m_data; + + switch(ntohs(tp->tp_op)) { + case TFTP_RRQ: + tftp_handle_rrq(tp, m->m_len); + break; + + case TFTP_ACK: + tftp_handle_ack(tp, m->m_len); + break; + } +} diff --git a/slirp/tftp.h b/slirp/tftp.h new file mode 100644 index 000000000..3ee666a15 --- /dev/null +++ b/slirp/tftp.h @@ -0,0 +1,34 @@ +/* tftp defines */ + +#define TFTP_SESSIONS_MAX 3 + +#define TFTP_SERVER 69 + +#define TFTP_RRQ 1 +#define TFTP_WRQ 2 +#define TFTP_DATA 3 +#define TFTP_ACK 4 +#define TFTP_ERROR 5 + +#define TFTP_FILENAME_MAX 512 + +struct tftp_t { + struct ip ip; + struct udphdr udp; + u_int16_t tp_op; + union { + struct { + u_int16_t tp_block_nr; + u_int8_t tp_buf[512]; + } tp_data; + struct { + u_int16_t tp_error_code; + u_int8_t tp_msg[512]; + } tp_error; + u_int8_t tp_buf[512 + 2]; + } x; +}; + +extern char *tftp_prefix; + +void tftp_input(struct mbuf *m); diff --git a/slirp/udp.c b/slirp/udp.c index 67a05090f..2dd51a39f 100644 --- a/slirp/udp.c +++ b/slirp/udp.c @@ -153,6 +153,14 @@ udp_input(m, iphlen) goto bad; } + /* + * handle TFTP + */ + if (ntohs(uh->uh_dport) == TFTP_SERVER) { + tftp_input(m); + goto bad; + } + /* * Locate pcb for datagram. */ @@ -2334,6 +2334,7 @@ void help(void) "-tun-fd fd use this fd as already opened tap/tun interface\n" #ifdef CONFIG_SLIRP "-user-net use user mode network stack [default if no tap/tun script]\n" + "-tftp prefix allow tftp access to files starting with prefix [only with -user-net enabled]\n" #endif "-dummy-net use dummy network stack\n" "\n" @@ -2408,6 +2409,7 @@ enum { QEMU_OPTION_n, QEMU_OPTION_tun_fd, QEMU_OPTION_user_net, + QEMU_OPTION_tftp, QEMU_OPTION_dummy_net, QEMU_OPTION_kernel, @@ -2460,6 +2462,7 @@ const QEMUOption qemu_options[] = { { "tun-fd", HAS_ARG, QEMU_OPTION_tun_fd }, #ifdef CONFIG_SLIRP { "user-net", 0, QEMU_OPTION_user_net }, + { "tftp", HAS_ARG, QEMU_OPTION_tftp }, #endif { "dummy-net", 0, QEMU_OPTION_dummy_net }, @@ -2751,9 +2754,17 @@ int main(int argc, char **argv) } } break; +#ifdef CONFIG_SLIRP + case QEMU_OPTION_tftp: + { + extern const char *tftp_prefix; + tftp_prefix = optarg; + } + break; case QEMU_OPTION_user_net: net_if_type = NET_IF_USER; break; +#endif case QEMU_OPTION_dummy_net: net_if_type = NET_IF_DUMMY; break; |