slirp / vnet: added support for multiple TFTP sessions

slirp: fixed minimum eth packet size for builtin replies
This commit is contained in:
Volker Ruppert 2013-09-27 11:47:59 +00:00
parent 01c0f07e4e
commit 4b59e6b884
4 changed files with 152 additions and 104 deletions

View File

@ -171,7 +171,7 @@ private:
dhcp_cfg_t dhcp;
tftp_data_t tftp;
char tftp_rootdir[BX_PATHNAME_LEN];
int rx_timer_index;
unsigned netdev_speed;
@ -256,9 +256,7 @@ bx_slirp_pktmover_c::bx_slirp_pktmover_c(const char *netif,
}
}
strcpy(this->tftp.rootdir, netif);
this->tftp.tid = 0;
this->tftp.write = 0;
strcpy(this->tftp_rootdir, netif);
Bit32u status = this->rxstat(this->netdev) & BX_NETDEV_SPEED;
this->netdev_speed = (status == BX_NETDEV_1GBIT) ? 1000 :
(status == BX_NETDEV_100MBIT) ? 100 : 10;
@ -380,7 +378,7 @@ bx_bool bx_slirp_pktmover_c::handle_ipv4(const Bit8u *buf, unsigned len)
if (udp_targetport == 67) { // BOOTP
udp_reply_size = process_dhcp(netdev, &l4pkt[8], l4pkt_len-8, &reply_buffer[42], &dhcp);
} else {
udp_reply_size = process_tftp(netdev, &l4pkt[8], l4pkt_len-8, udp_sourceport, &reply_buffer[42], &tftp);
udp_reply_size = process_tftp(netdev, &l4pkt[8], l4pkt_len-8, udp_sourceport, &reply_buffer[42], tftp_rootdir);
}
if (udp_reply_size > 0) {
pending_reply_size = udp_reply_size + 42;
@ -457,6 +455,10 @@ void bx_slirp_pktmover_c::prepare_builtin_reply(unsigned type)
memcpy(ethhdr->dst_mac_addr, dhcp.guest_macaddr, ETHERNET_MAC_ADDR_LEN);
memcpy(ethhdr->src_mac_addr, dhcp.host_macaddr, ETHERNET_MAC_ADDR_LEN);
ethhdr->type = htons(type);
if (pending_reply_size < 60) {
memset(&reply_buffer[pending_reply_size], 0, 60 - pending_reply_size);
pending_reply_size = 60;
}
rx_time = (64 + 96 + 4 * 8 + pending_reply_size * 8) / this->netdev_speed;
bx_pc_system.activate_timer(this->rx_timer_index, this->tx_time + rx_time + 100, 0);
}

View File

@ -160,7 +160,7 @@ private:
dhcp_cfg_t dhcp;
tftp_data_t tftp;
char tftp_rootdir[BX_PATHNAME_LEN];
struct {
unsigned ipprotocol;
@ -217,9 +217,7 @@ void bx_vnet_pktmover_c::pktmover_init(
this->netdev = dev;
this->rxh = rxh;
this->rxstat = rxstat;
strcpy(this->tftp.rootdir, netif);
this->tftp.tid = 0;
this->tftp.write = 0;
strcpy(this->tftp_rootdir, netif);
memcpy(&dhcp.host_macaddr[0], macaddr, 6);
memcpy(&dhcp.guest_macaddr[0], macaddr, 6);
@ -716,7 +714,7 @@ void bx_vnet_pktmover_c::udpipv4_tftp_handler_ns(
Bit8u replybuf[TFTP_BUFFER_SIZE + 4];
int len;
len = process_tftp(netdev, data, data_len, sourceport, replybuf, &tftp);
len = process_tftp(netdev, data, data_len, sourceport, replybuf, tftp_rootdir);
if (len > 0) {
host_to_guest_udpipv4_packet(sourceport, targetport, replybuf, len);
}

View File

@ -754,51 +754,95 @@ int process_dhcp(bx_devmodel_c *netdev, const Bit8u *data, unsigned data_len, Bi
return opts_len;
}
int tftp_send_error(Bit8u *buffer, unsigned code, const char *msg, tftp_data_t *tftp)
// TFTP support
tftp_session_t *tftp_sessions = NULL;
tftp_session_t *tftp_new_session(Bit16u req_tid, bx_bool mode, const char *tpath, const char *tname)
{
tftp_session_t *s = new tftp_session_t;
s->tid = req_tid;
s->write = mode;
s->next = tftp_sessions;
tftp_sessions = s;
if ((strlen(tname) > 0) && ((strlen(tpath) + strlen(tname)) < BX_PATHNAME_LEN)) {
sprintf(s->filename, "%s/%s", tpath, tname);
} else {
s->filename[0] = 0;
}
return s;
}
tftp_session_t *tftp_find_session(Bit16u tid)
{
tftp_session_t *s = tftp_sessions;
while (s != NULL) {
if (s->tid != tid)
s = s->next;
else
break;
}
return s;
}
void tftp_remove_session(tftp_session_t *s)
{
tftp_session_t *last;
if (tftp_sessions == s) {
tftp_sessions = s->next;
} else {
last = tftp_sessions;
while (last != NULL) {
if (last->next != s)
last = last->next;
else
break;
}
if (last) {
last->next = s->next;
}
}
delete s;
}
int tftp_send_error(Bit8u *buffer, unsigned code, const char *msg, tftp_session_t *s)
{
put_net2(buffer, TFTP_ERROR);
put_net2(buffer + 2, code);
strcpy((char*)buffer + 4, msg);
tftp->tid = 0;
if (s != NULL) {
tftp_remove_session(s);
}
return (strlen(msg) + 5);
}
int tftp_send_data(Bit8u *buffer, unsigned block_nr, tftp_data_t *tftp)
int tftp_send_data(Bit8u *buffer, unsigned block_nr, tftp_session_t *s)
{
char path[BX_PATHNAME_LEN];
char msg[BX_PATHNAME_LEN];
int rd;
if (strlen(tftp->filename) == 0) {
return tftp_send_error(buffer, 1, "File not found", tftp);
}
if ((strlen(tftp->rootdir) + strlen(tftp->filename)) > BX_PATHNAME_LEN) {
return tftp_send_error(buffer, 1, "Path name too long", tftp);
}
sprintf(path, "%s/%s", tftp->rootdir, tftp->filename);
FILE *fp = fopen(path, "rb");
FILE *fp = fopen(s->filename, "rb");
if (!fp) {
sprintf(msg, "File not found: %s", tftp->filename);
return tftp_send_error(buffer, 1, msg, tftp);
sprintf(msg, "File not found: %s", s->filename);
return tftp_send_error(buffer, 1, msg, s);
}
if (fseek(fp, (block_nr - 1) * TFTP_BUFFER_SIZE, SEEK_SET) < 0) {
return tftp_send_error(buffer, 3, "Block not seekable", tftp);
return tftp_send_error(buffer, 3, "Block not seekable", s);
}
rd = fread(buffer + 4, 1, TFTP_BUFFER_SIZE, fp);
fclose(fp);
if (rd < 0) {
return tftp_send_error(buffer, 3, "Block not readable", tftp);
return tftp_send_error(buffer, 3, "Block not readable", s);
}
put_net2(buffer, TFTP_DATA);
put_net2(buffer + 2, block_nr);
if (rd < TFTP_BUFFER_SIZE) {
tftp->tid = 0;
tftp_remove_session(s);
}
return (rd + 4);
}
@ -827,37 +871,20 @@ int tftp_send_optack(Bit8u *buffer, size_t tsize_option, unsigned blksize_option
return (p - buffer);
}
// duplicate the part of tftp_send_data() that constructs the filename
// but ignore errors since tftp_send_data() will respond for us
static size_t get_file_size(bx_devmodel_c *netdev, const char *tpath, const char *tname)
int process_tftp(bx_devmodel_c *netdev, const Bit8u *data, unsigned data_len, Bit16u req_tid, Bit8u *reply, const char *tftp_rootdir)
{
struct stat stbuf;
char path[BX_PATHNAME_LEN];
if (strlen(tname) == 0)
return 0;
if ((strlen(tpath) + strlen(tname)) > BX_PATHNAME_LEN)
return 0;
sprintf(path, "%s/%s", tpath, tname);
if (stat(path, &stbuf) < 0)
return 0;
BX_INFO(("tftp filesize: %lu", (unsigned long)stbuf.st_size));
return (size_t)stbuf.st_size;
}
int process_tftp(bx_devmodel_c *netdev, const Bit8u *data, unsigned data_len, Bit16u req_tid, Bit8u *reply, tftp_data_t *tftp)
{
char path[BX_PATHNAME_LEN];
FILE *fp;
unsigned block_nr;
unsigned tftp_len;
tftp_session_t *s;
s = tftp_find_session(req_tid);
switch (get_net2(data)) {
case TFTP_RRQ:
if (tftp->tid == 0) {
{
if (s != NULL) {
tftp_remove_session(s);
}
strncpy((char*)reply, (const char*)data + 2, data_len - 2);
reply[data_len - 4] = 0;
@ -885,15 +912,23 @@ int process_tftp(bx_devmodel_c *netdev, const Bit8u *data, unsigned data_len, Bi
}
}
if (!octet_option) {
return tftp_send_error(reply, 4, "Unsupported transfer mode", tftp);
return tftp_send_error(reply, 4, "Unsupported transfer mode", NULL);
}
}
strcpy(tftp->filename, (char*)reply);
BX_INFO(("tftp req: %s", tftp->filename));
s = tftp_new_session(req_tid, 0, tftp_rootdir, (const char*)reply);
if (strlen(s->filename) == 0) {
return tftp_send_error(reply, 1, "Illegal file name", s);
}
if (tsize_option) {
tsize_option = get_file_size(netdev, tftp->rootdir, tftp->filename);
struct stat stbuf;
if (stat(s->filename, &stbuf) < 0) {
tsize_option = 0;
} else {
tsize_option = (size_t)stbuf.st_size;
}
if (tsize_option > 0) {
BX_INFO(("tftp filesize: %lu", (unsigned long)tsize_option));
// if tsize requested and file exists, send optack and return
// optack ack will pick up where we leave off here.
// if blksize_option is less than TFTP_BUFFER_SIZE should
@ -904,15 +939,15 @@ int process_tftp(bx_devmodel_c *netdev, const Bit8u *data, unsigned data_len, Bi
if (blksize_option) {
BX_INFO(("tftp req: blksize (val = %d) unused", blksize_option));
}
tftp->tid = req_tid;
tftp->write = 0;
return tftp_send_data(reply, 1, tftp);
} else {
return tftp_send_error(reply, 4, "Illegal request", tftp);
return tftp_send_data(reply, 1, s);
}
break;
case TFTP_WRQ:
if (tftp->tid == 0) {
{
if (s != NULL) {
tftp_remove_session(s);
}
strncpy((char*)reply, (const char*)data + 2, data_len - 2);
reply[data_len - 4] = 0;
@ -920,63 +955,76 @@ int process_tftp(bx_devmodel_c *netdev, const Bit8u *data, unsigned data_len, Bi
if (strlen((char*)reply) < data_len - 2) {
const char *mode = (const char*)data + 2 + strlen((char*)reply) + 1;
if (memcmp(mode, "octet\0", 6) != 0) {
return tftp_send_error(reply, 4, "Unsupported transfer mode", tftp);
return tftp_send_error(reply, 4, "Unsupported transfer mode", NULL);
}
}
strcpy(tftp->filename, (char*)reply);
sprintf(path, "%s/%s", tftp->rootdir, tftp->filename);
fp = fopen(path, "rb");
s = tftp_new_session(req_tid, 1, tftp_rootdir, (const char*)reply);
if (strlen(s->filename) == 0) {
return tftp_send_error(reply, 1, "Illegal file name", s);
}
fp = fopen(s->filename, "rb");
if (fp) {
fclose(fp);
return tftp_send_error(reply, 6, "File exists", tftp);
return tftp_send_error(reply, 6, "File exists", s);
}
fp = fopen(path, "wb");
fp = fopen(s->filename, "wb");
if (!fp) {
return tftp_send_error(reply, 2, "Access violation", tftp);
return tftp_send_error(reply, 2, "Access violation", s);
}
fclose(fp);
tftp->tid = req_tid;
tftp->write = 1;
return tftp_send_ack(reply, 0);
} else {
return tftp_send_error(reply, 4, "Illegal request", tftp);
}
break;
case TFTP_DATA:
if ((tftp->tid == req_tid) && (tftp->write == 1)) {
block_nr = get_net2(data + 2);
strncpy((char*)reply, (const char*)data + 4, data_len - 4);
tftp_len = data_len - 4;
reply[tftp_len] = 0;
if (tftp_len <= 512) {
sprintf(path, "%s/%s", tftp->rootdir, tftp->filename);
fp = fopen(path, "ab");
if (!fp) {
return tftp_send_error(reply, 2, "Access violation", tftp);
if (s != NULL) {
if (s->write == 1) {
block_nr = get_net2(data + 2);
strncpy((char*)reply, (const char*)data + 4, data_len - 4);
tftp_len = data_len - 4;
reply[tftp_len] = 0;
if (tftp_len <= 512) {
fp = fopen(s->filename, "ab");
if (!fp) {
return tftp_send_error(reply, 2, "Access violation", s);
}
if (fseek(fp, (block_nr - 1) * TFTP_BUFFER_SIZE, SEEK_SET) < 0) {
return tftp_send_error(reply, 3, "Block not seekable", s);
}
fwrite(reply, 1, tftp_len, fp);
fclose(fp);
if (tftp_len < 512) {
tftp_remove_session(s);
}
return tftp_send_ack(reply, block_nr);
} else {
return tftp_send_error(reply, 4, "Illegal request", s);
}
if (fseek(fp, (block_nr - 1) * TFTP_BUFFER_SIZE, SEEK_SET) < 0) {
return tftp_send_error(reply, 3, "Block not seekable", tftp);
}
fwrite(reply, 1, tftp_len, fp);
fclose(fp);
if (tftp_len < 512) {
tftp->tid = 0;
}
return tftp_send_ack(reply, block_nr);
} else {
return tftp_send_error(reply, 4, "Illegal request", tftp);
return tftp_send_error(reply, 4, "Illegal request", s);
}
} else {
return tftp_send_error(reply, 4, "Illegal request", tftp);
return tftp_send_error(reply, 4, "Unknown transfer ID", s);
}
break;
case TFTP_ACK:
return tftp_send_data(reply, get_net2(data + 2) + 1, tftp);
case TFTP_ERROR:
// silently ignore error packets
if (s != NULL) {
if (s->write == 0) {
return tftp_send_data(reply, get_net2(data + 2) + 1, s);
} else {
return tftp_send_error(reply, 4, "Illegal request", s);
}
}
break;
case TFTP_ERROR:
if (s != NULL) {
tftp_remove_session(s);
}
break;
default:
BX_ERROR(("TFTP unknown opt %d", get_net2(data)));
}

View File

@ -131,12 +131,12 @@ typedef struct {
int max_guest_ipv4addr;
} dhcp_cfg_t;
typedef struct {
typedef struct tftp_session {
char filename[BX_PATHNAME_LEN];
char rootdir[BX_PATHNAME_LEN];
bx_bool write;
Bit16u tid;
} tftp_data_t;
bx_bool write;
struct tftp_session *next;
} tftp_session_t;
static const Bit8u broadcast_macaddr[6] = {0xff,0xff,0xff,0xff,0xff,0xff};
@ -174,7 +174,7 @@ BX_CPP_INLINE void put_net4(Bit8u *buf,Bit32u data)
Bit16u ip_checksum(const Bit8u *buf, unsigned buf_len);
int process_arp(bx_devmodel_c *netdev, const Bit8u *data, unsigned data_len, Bit8u *reply, dhcp_cfg_t *dhcp);
int process_dhcp(bx_devmodel_c *netdev, const Bit8u *data, unsigned data_len, Bit8u *reply, dhcp_cfg_t *dhcp);
int process_tftp(bx_devmodel_c *netdev, const Bit8u *data, unsigned data_len, Bit16u req_tid, Bit8u *reply, tftp_data_t *tftp);
int process_tftp(bx_devmodel_c *netdev, const Bit8u *data, unsigned data_len, Bit16u req_tid, Bit8u *reply, const char *tftp_rootdir);
//
// The eth_pktmover class is used by ethernet chip emulations