Commit 781b3e5efc (tftp: Do not use priority queue) caused a regression
when fetching files over TFTP whose size is bigger than 65535 * block size.
grub> linux /images/pxeboot/vmlinuz
grub> echo $?
0
grub> initrd /images/pxeboot/initrd.img
error: timeout reading '/images/pxeboot/initrd.img'.
grub> echo $?
28
It is caused by the block number counter being a 16-bit field, which leads
to a maximum file size of ((1 << 16) - 1) * block size. Because GRUB sets
the block size to 1024 octets (by using the TFTP Blocksize Option from RFC
2348 [0]), the maximum file size that can be transferred is 67107840 bytes.
The TFTP PROTOCOL (REVISION 2) RFC 1350 [1] does not mention what a client
should do when a file size is bigger than the maximum, but most TFTP hosts
support the block number counter to be rolled over. That is, acking a data
packet with a block number of 0 is taken as if the 65356th block was acked.
It was working before because the block counter roll-over was happening due
an overflow. But that got fixed by the mentioned commit, which led to the
regression when attempting to fetch files larger than the maximum size.
To allow TFTP file transfers of unlimited size again, re-introduce a block
counter roll-over so the data packets are acked preventing the timeouts.
[0]: https://tools.ietf.org/html/rfc2348
[1]: https://tools.ietf.org/html/rfc1350
Fixes: 781b3e5efc (tftp: Do not use priority queue)
Suggested-by: Peter Jones <pjones@redhat.com>
Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
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>
Some TFTP servers do not handle multiple consecutive slashes correctly.
This patch avoids sending TFTP requests with non-normalized paths.
Signed-off-by: Lenny Szubowicz <lszubowi@redhat.com>
Signed-off-by: Mark Salter <msalter@redhat.com>
Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
* grub-core/net/net.c (receive_packets): Decrease the stop to 10
packets but stop only if stop condition is satisfied.
(grub_net_fs_read_real): Call packets_pulled after real read. Use
`stall' instead of `eof' as stop condition.
* grub-core/net/http.c (parse_line): Set `stall' on EOF.
(http_err): Likewise.
* grub-core/net/tftp.c (ack): Replace the first argument with data
instead of socket.
(tftp_receive): Stall if too many packets are in wait queue.
(tftp_packets_pulled): New function.
(grub_tftp_protocol): Set packets_pulled.
* include/grub/net.h (grub_net_packets): New field count.
(grub_net_put_packet): Increment count.
(grub_net_remove_packet): Likewise.
(grub_net_app_protocol): New field `packets_pulled'.
(grub_net): New field `stall'.
* include/grub/net.h (grub_net_poll_cards): New argument stop_condition.
All users updated.
* grub-core/net/arp.c (have_pending): New var.
(pending_req): Likewise.
(grub_net_arp_send_request): Fill pending_req and use have_pending as
stop indicator.
(grub_net_arp_receive): Set have_pending.
* grub-core/net/dns.c (recv_data): New field stop.
(recv_hook): Set stop.
(grub_net_dns_lookup): Init stop and use as stop condition.
* grub-core/net/http.c (http_establish): Use headers_recv as stop
condition.
* grub-core/net/net.c (grub_net_poll_cards): New argument
stop_condition. Stop when it goes true.
* grub-core/net/tcp.c (grub_net_tcp_open): Use `established' as stop
indicator.
* grub-core/net/tftp.c (tftp_open): Use `have_oack' as stop indicator.