781b3e5efc
There is not need to reassemble the order of blocks. Per RFC 1350, server must wait for the ACK, before sending next block. Data packets can be served immediately without putting them to priority queue. Logic to handle incoming packet is this: - if packet block id equal to expected block id, then process the packet, - if packet block id is less than expected - this is retransmit of old packet, then ACK it and drop the packet, - if packet block id is more than expected - that shouldn't happen, just drop the packet. It makes the tftp receive path code simpler, smaller and faster. As a benefit, this change fixes CID# 73624 and CID# 96690, caused by following while loop: while (cmp_block (grub_be_to_cpu16 (tftph->u.data.block), data->block + 1) == 0) where tftph pointer is not moving from one iteration to another, causing to serve same packet again. Luckily, double serving didn't happen due to data->block++ during the first iteration. Fixes: CID 73624, CID 96690 Signed-off-by: Alexey Makhalov <amakhalov@vmware.com> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
467 lines
11 KiB
C
467 lines
11 KiB
C
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2010,2011 Free Software Foundation, Inc.
|
|
*
|
|
* GRUB is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GRUB is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <grub/misc.h>
|
|
#include <grub/net/udp.h>
|
|
#include <grub/net/ip.h>
|
|
#include <grub/net/ethernet.h>
|
|
#include <grub/net/netbuff.h>
|
|
#include <grub/net.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/dl.h>
|
|
#include <grub/file.h>
|
|
#include <grub/i18n.h>
|
|
|
|
GRUB_MOD_LICENSE ("GPLv3+");
|
|
|
|
/* IP port for the MTFTP server used for Intel's PXE */
|
|
enum
|
|
{
|
|
MTFTP_SERVER_PORT = 75,
|
|
MTFTP_CLIENT_PORT = 76,
|
|
/* IP port for the TFTP server */
|
|
TFTP_SERVER_PORT = 69
|
|
};
|
|
|
|
enum
|
|
{
|
|
TFTP_DEFAULTSIZE_PACKET = 512,
|
|
};
|
|
|
|
enum
|
|
{
|
|
TFTP_CODE_EOF = 1,
|
|
TFTP_CODE_MORE = 2,
|
|
TFTP_CODE_ERROR = 3,
|
|
TFTP_CODE_BOOT = 4,
|
|
TFTP_CODE_CFG = 5
|
|
};
|
|
|
|
enum
|
|
{
|
|
TFTP_RRQ = 1,
|
|
TFTP_WRQ = 2,
|
|
TFTP_DATA = 3,
|
|
TFTP_ACK = 4,
|
|
TFTP_ERROR = 5,
|
|
TFTP_OACK = 6
|
|
};
|
|
|
|
enum
|
|
{
|
|
TFTP_EUNDEF = 0, /* not defined */
|
|
TFTP_ENOTFOUND = 1, /* file not found */
|
|
TFTP_EACCESS = 2, /* access violation */
|
|
TFTP_ENOSPACE = 3, /* disk full or allocation exceeded */
|
|
TFTP_EBADOP = 4, /* illegal TFTP operation */
|
|
TFTP_EBADID = 5, /* unknown transfer ID */
|
|
TFTP_EEXISTS = 6, /* file already exists */
|
|
TFTP_ENOUSER = 7 /* no such user */
|
|
};
|
|
|
|
struct tftphdr {
|
|
grub_uint16_t opcode;
|
|
union {
|
|
grub_int8_t rrq[TFTP_DEFAULTSIZE_PACKET];
|
|
struct {
|
|
grub_uint16_t block;
|
|
grub_int8_t download[0];
|
|
} data;
|
|
struct {
|
|
grub_uint16_t block;
|
|
} ack;
|
|
struct {
|
|
grub_uint16_t errcode;
|
|
grub_int8_t errmsg[TFTP_DEFAULTSIZE_PACKET];
|
|
} err;
|
|
struct {
|
|
grub_int8_t data[TFTP_DEFAULTSIZE_PACKET+2];
|
|
} oack;
|
|
} u;
|
|
} GRUB_PACKED ;
|
|
|
|
|
|
typedef struct tftp_data
|
|
{
|
|
grub_uint64_t file_size;
|
|
grub_uint64_t block;
|
|
grub_uint32_t block_size;
|
|
grub_uint64_t ack_sent;
|
|
int have_oack;
|
|
struct grub_error_saved save_err;
|
|
grub_net_udp_socket_t sock;
|
|
} *tftp_data_t;
|
|
|
|
static grub_err_t
|
|
ack (tftp_data_t data, grub_uint64_t block)
|
|
{
|
|
struct tftphdr *tftph_ack;
|
|
grub_uint8_t nbdata[512];
|
|
struct grub_net_buff nb_ack;
|
|
grub_err_t err;
|
|
|
|
nb_ack.head = nbdata;
|
|
nb_ack.end = nbdata + sizeof (nbdata);
|
|
grub_netbuff_clear (&nb_ack);
|
|
grub_netbuff_reserve (&nb_ack, 512);
|
|
err = grub_netbuff_push (&nb_ack, sizeof (tftph_ack->opcode)
|
|
+ sizeof (tftph_ack->u.ack.block));
|
|
if (err)
|
|
return err;
|
|
|
|
tftph_ack = (struct tftphdr *) nb_ack.data;
|
|
tftph_ack->opcode = grub_cpu_to_be16_compile_time (TFTP_ACK);
|
|
tftph_ack->u.ack.block = grub_cpu_to_be16 (block);
|
|
|
|
err = grub_net_send_udp_packet (data->sock, &nb_ack);
|
|
if (err)
|
|
return err;
|
|
data->ack_sent = block;
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
tftp_receive (grub_net_udp_socket_t sock __attribute__ ((unused)),
|
|
struct grub_net_buff *nb,
|
|
void *f)
|
|
{
|
|
grub_file_t file = f;
|
|
struct tftphdr *tftph = (void *) nb->data;
|
|
tftp_data_t data = file->data;
|
|
grub_err_t err;
|
|
grub_uint8_t *ptr;
|
|
|
|
if (nb->tail - nb->data < (grub_ssize_t) sizeof (tftph->opcode))
|
|
{
|
|
grub_dprintf ("tftp", "TFTP packet too small\n");
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
tftph = (struct tftphdr *) nb->data;
|
|
switch (grub_be_to_cpu16 (tftph->opcode))
|
|
{
|
|
case TFTP_OACK:
|
|
data->block_size = TFTP_DEFAULTSIZE_PACKET;
|
|
data->have_oack = 1;
|
|
for (ptr = nb->data + sizeof (tftph->opcode); ptr < nb->tail;)
|
|
{
|
|
if (grub_memcmp (ptr, "tsize\0", sizeof ("tsize\0") - 1) == 0)
|
|
data->file_size = grub_strtoul ((char *) ptr + sizeof ("tsize\0")
|
|
- 1, 0, 0);
|
|
if (grub_memcmp (ptr, "blksize\0", sizeof ("blksize\0") - 1) == 0)
|
|
data->block_size = grub_strtoul ((char *) ptr + sizeof ("blksize\0")
|
|
- 1, 0, 0);
|
|
while (ptr < nb->tail && *ptr)
|
|
ptr++;
|
|
ptr++;
|
|
}
|
|
data->block = 0;
|
|
grub_netbuff_free (nb);
|
|
err = ack (data, 0);
|
|
grub_error_save (&data->save_err);
|
|
return GRUB_ERR_NONE;
|
|
case TFTP_DATA:
|
|
if (nb->tail - nb->data < (grub_ssize_t) (sizeof (tftph->opcode)
|
|
+ sizeof (tftph->u.data.block)))
|
|
{
|
|
grub_dprintf ("tftp", "TFTP packet too small\n");
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
/* Ack old/retransmitted block. */
|
|
if (grub_be_to_cpu16 (tftph->u.data.block) < data->block + 1)
|
|
ack (data, grub_be_to_cpu16 (tftph->u.data.block));
|
|
/* Ignore unexpected block. */
|
|
else if (grub_be_to_cpu16 (tftph->u.data.block) > data->block + 1)
|
|
grub_dprintf ("tftp", "TFTP unexpected block # %d\n", tftph->u.data.block);
|
|
else
|
|
{
|
|
unsigned size;
|
|
|
|
if (file->device->net->packs.count < 50)
|
|
{
|
|
err = ack (data, data->block + 1);
|
|
if (err)
|
|
return err;
|
|
}
|
|
else
|
|
file->device->net->stall = 1;
|
|
|
|
err = grub_netbuff_pull (nb, sizeof (tftph->opcode) +
|
|
sizeof (tftph->u.data.block));
|
|
if (err)
|
|
return err;
|
|
size = nb->tail - nb->data;
|
|
|
|
data->block++;
|
|
if (size < data->block_size)
|
|
{
|
|
if (data->ack_sent < data->block)
|
|
ack (data, data->block);
|
|
file->device->net->eof = 1;
|
|
file->device->net->stall = 1;
|
|
grub_net_udp_close (data->sock);
|
|
data->sock = NULL;
|
|
}
|
|
/*
|
|
* Prevent garbage in broken cards. Is it still necessary
|
|
* given that IP implementation has been fixed?
|
|
*/
|
|
if (size > data->block_size)
|
|
{
|
|
err = grub_netbuff_unput (nb, size - data->block_size);
|
|
if (err)
|
|
return err;
|
|
}
|
|
/* If there is data, puts packet in socket list. */
|
|
if ((nb->tail - nb->data) > 0)
|
|
{
|
|
grub_net_put_packet (&file->device->net->packs, nb);
|
|
/* Do not free nb. */
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
}
|
|
grub_netbuff_free (nb);
|
|
return GRUB_ERR_NONE;
|
|
case TFTP_ERROR:
|
|
data->have_oack = 1;
|
|
grub_netbuff_free (nb);
|
|
grub_error (GRUB_ERR_IO, (char *) tftph->u.err.errmsg);
|
|
grub_error_save (&data->save_err);
|
|
return GRUB_ERR_NONE;
|
|
default:
|
|
grub_netbuff_free (nb);
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a normalized copy of the filename. Compress any string of consecutive
|
|
* forward slashes to a single forward slash.
|
|
*/
|
|
static void
|
|
grub_normalize_filename (char *normalized, const char *filename)
|
|
{
|
|
char *dest = normalized;
|
|
const char *src = filename;
|
|
|
|
while (*src != '\0')
|
|
{
|
|
if (src[0] == '/' && src[1] == '/')
|
|
src++;
|
|
else
|
|
*dest++ = *src++;
|
|
}
|
|
*dest = '\0';
|
|
}
|
|
|
|
static grub_err_t
|
|
tftp_open (struct grub_file *file, const char *filename)
|
|
{
|
|
struct tftphdr *tftph;
|
|
char *rrq;
|
|
int i;
|
|
int rrqlen;
|
|
int hdrlen;
|
|
grub_uint8_t open_data[1500];
|
|
struct grub_net_buff nb;
|
|
tftp_data_t data;
|
|
grub_err_t err;
|
|
grub_uint8_t *nbd;
|
|
grub_net_network_level_address_t addr;
|
|
|
|
data = grub_zalloc (sizeof (*data));
|
|
if (!data)
|
|
return grub_errno;
|
|
|
|
nb.head = open_data;
|
|
nb.end = open_data + sizeof (open_data);
|
|
grub_netbuff_clear (&nb);
|
|
|
|
grub_netbuff_reserve (&nb, 1500);
|
|
err = grub_netbuff_push (&nb, sizeof (*tftph));
|
|
if (err)
|
|
{
|
|
grub_free (data);
|
|
return err;
|
|
}
|
|
|
|
tftph = (struct tftphdr *) nb.data;
|
|
|
|
rrq = (char *) tftph->u.rrq;
|
|
rrqlen = 0;
|
|
|
|
tftph->opcode = grub_cpu_to_be16_compile_time (TFTP_RRQ);
|
|
|
|
/*
|
|
* Copy and normalize the filename to work-around issues on some TFTP
|
|
* servers when file names are being matched for remapping.
|
|
*/
|
|
grub_normalize_filename (rrq, filename);
|
|
rrqlen += grub_strlen (rrq) + 1;
|
|
rrq += grub_strlen (rrq) + 1;
|
|
|
|
grub_strcpy (rrq, "octet");
|
|
rrqlen += grub_strlen ("octet") + 1;
|
|
rrq += grub_strlen ("octet") + 1;
|
|
|
|
grub_strcpy (rrq, "blksize");
|
|
rrqlen += grub_strlen ("blksize") + 1;
|
|
rrq += grub_strlen ("blksize") + 1;
|
|
|
|
grub_strcpy (rrq, "1024");
|
|
rrqlen += grub_strlen ("1024") + 1;
|
|
rrq += grub_strlen ("1024") + 1;
|
|
|
|
grub_strcpy (rrq, "tsize");
|
|
rrqlen += grub_strlen ("tsize") + 1;
|
|
rrq += grub_strlen ("tsize") + 1;
|
|
|
|
grub_strcpy (rrq, "0");
|
|
rrqlen += grub_strlen ("0") + 1;
|
|
rrq += grub_strlen ("0") + 1;
|
|
hdrlen = sizeof (tftph->opcode) + rrqlen;
|
|
|
|
err = grub_netbuff_unput (&nb, nb.tail - (nb.data + hdrlen));
|
|
if (err)
|
|
{
|
|
grub_free (data);
|
|
return err;
|
|
}
|
|
|
|
file->not_easily_seekable = 1;
|
|
file->data = data;
|
|
|
|
err = grub_net_resolve_address (file->device->net->server, &addr);
|
|
if (err)
|
|
{
|
|
grub_free (data);
|
|
return err;
|
|
}
|
|
|
|
data->sock = grub_net_udp_open (addr,
|
|
TFTP_SERVER_PORT, tftp_receive,
|
|
file);
|
|
if (!data->sock)
|
|
{
|
|
grub_free (data);
|
|
return grub_errno;
|
|
}
|
|
|
|
/* Receive OACK packet. */
|
|
nbd = nb.data;
|
|
for (i = 0; i < GRUB_NET_TRIES; i++)
|
|
{
|
|
nb.data = nbd;
|
|
err = grub_net_send_udp_packet (data->sock, &nb);
|
|
if (err)
|
|
{
|
|
grub_net_udp_close (data->sock);
|
|
grub_free (data);
|
|
return err;
|
|
}
|
|
grub_net_poll_cards (GRUB_NET_INTERVAL + (i * GRUB_NET_INTERVAL_ADDITION),
|
|
&data->have_oack);
|
|
if (data->have_oack)
|
|
break;
|
|
}
|
|
|
|
if (!data->have_oack)
|
|
grub_error (GRUB_ERR_TIMEOUT, N_("time out opening `%s'"), filename);
|
|
else
|
|
grub_error_load (&data->save_err);
|
|
if (grub_errno)
|
|
{
|
|
grub_net_udp_close (data->sock);
|
|
grub_free (data);
|
|
return grub_errno;
|
|
}
|
|
|
|
file->size = data->file_size;
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
tftp_close (struct grub_file *file)
|
|
{
|
|
tftp_data_t data = file->data;
|
|
|
|
if (data->sock)
|
|
{
|
|
grub_uint8_t nbdata[512];
|
|
grub_err_t err;
|
|
struct grub_net_buff nb_err;
|
|
struct tftphdr *tftph;
|
|
|
|
nb_err.head = nbdata;
|
|
nb_err.end = nbdata + sizeof (nbdata);
|
|
|
|
grub_netbuff_clear (&nb_err);
|
|
grub_netbuff_reserve (&nb_err, 512);
|
|
err = grub_netbuff_push (&nb_err, sizeof (tftph->opcode)
|
|
+ sizeof (tftph->u.err.errcode)
|
|
+ sizeof ("closed"));
|
|
if (!err)
|
|
{
|
|
tftph = (struct tftphdr *) nb_err.data;
|
|
tftph->opcode = grub_cpu_to_be16_compile_time (TFTP_ERROR);
|
|
tftph->u.err.errcode = grub_cpu_to_be16_compile_time (TFTP_EUNDEF);
|
|
grub_memcpy (tftph->u.err.errmsg, "closed", sizeof ("closed"));
|
|
|
|
err = grub_net_send_udp_packet (data->sock, &nb_err);
|
|
}
|
|
if (err)
|
|
grub_print_error ();
|
|
grub_net_udp_close (data->sock);
|
|
}
|
|
grub_free (data);
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
tftp_packets_pulled (struct grub_file *file)
|
|
{
|
|
tftp_data_t data = file->data;
|
|
if (file->device->net->packs.count >= 50)
|
|
return 0;
|
|
|
|
if (!file->device->net->eof)
|
|
file->device->net->stall = 0;
|
|
if (data->ack_sent >= data->block)
|
|
return 0;
|
|
return ack (data, data->block);
|
|
}
|
|
|
|
static struct grub_net_app_protocol grub_tftp_protocol =
|
|
{
|
|
.name = "tftp",
|
|
.open = tftp_open,
|
|
.close = tftp_close,
|
|
.packets_pulled = tftp_packets_pulled
|
|
};
|
|
|
|
GRUB_MOD_INIT (tftp)
|
|
{
|
|
grub_net_app_level_register (&grub_tftp_protocol);
|
|
}
|
|
|
|
GRUB_MOD_FINI (tftp)
|
|
{
|
|
grub_net_app_level_unregister (&grub_tftp_protocol);
|
|
}
|