Michael Chang 8abbafa493 net/tcp: Fix TCP port number reused on reboot
GRUB's TCP stack assigns source ports for outgoing connections starting
at 21550 and increments sequentially by 1 (e.g., 21550, 21551, ...).
While this generally works, it can lead to failures if the system
reboots rapidly and reuses the same source port too soon.

This issue was observed on powerpc-ieee1275 platforms using CAS (Client
Architecture Support) reboot. In such cases, loading the initrd over
HTTP may fail with connection timeouts. Packet captures show the failed
connections are flagged as "TCP Port Number Reused" by Wireshark.

The root cause is that GRUB reuses the same port shortly after reboot,
while the server may still be tracking the previous connection in
TIME_WAIT. This can result in the server rejecting the connection
attempt or responding with a stale ACK or RST, leading to handshake
failure.

This patch fixes the issue by introducing a time based source port
selection strategy. Instead of always starting from port 21550, GRUB now
computes an initial base port based on the current RTC time, divided
into 5 minute windows. The purpose of this time based strategy is to
ensure that GRUB avoids reusing the same source port within a 5 minute
window, thereby preventing collisions with stale server side connection
tracking that could interfere with a new TCP handshake.

A step size of 8 ensures that the same port will not be reused across
reboots unless GRUB opens more than 8 TCP connections per second on
average, something that is highly unlikely. In typical usage, a GRUB
boot cycle lasts about 15 seconds and may open fewer than 100
connections total, well below the reuse threshold. This makes the
approach robust against short reboot intervals while keeping the logic
simple and deterministic.

Signed-off-by: Michael Chang <mchang@suse.com>
Reviewed-by: Sudhakar Kuppusamy <sudhakar@linux.ibm.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
2025-10-11 15:36:53 +02:00

1063 lines
27 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/net.h>
#include <grub/net/ip.h>
#include <grub/net/tcp.h>
#include <grub/net/netbuff.h>
#include <grub/time.h>
#include <grub/priority_queue.h>
#include <grub/datetime.h>
#define TCP_SYN_RETRANSMISSION_TIMEOUT GRUB_NET_INTERVAL
#define TCP_SYN_RETRANSMISSION_COUNT GRUB_NET_TRIES
#define TCP_RETRANSMISSION_TIMEOUT GRUB_NET_INTERVAL
#define TCP_RETRANSMISSION_COUNT GRUB_NET_TRIES
struct unacked
{
struct unacked *next;
struct unacked **prev;
struct grub_net_buff *nb;
grub_uint64_t last_try;
int try_count;
};
enum
{
TCP_FIN = 0x1,
TCP_SYN = 0x2,
TCP_RST = 0x4,
TCP_PUSH = 0x8,
TCP_ACK = 0x10,
TCP_URG = 0x20,
};
struct grub_net_tcp_socket
{
struct grub_net_tcp_socket *next;
struct grub_net_tcp_socket **prev;
int established;
int i_closed;
int they_closed;
int in_port;
int out_port;
int errors;
int they_reseted;
int i_reseted;
int i_stall;
grub_uint32_t my_start_seq;
grub_uint32_t my_cur_seq;
grub_uint32_t their_start_seq;
grub_uint32_t their_cur_seq;
grub_uint16_t my_window;
struct unacked *unack_first;
struct unacked *unack_last;
grub_err_t (*recv_hook) (grub_net_tcp_socket_t sock, struct grub_net_buff *nb,
void *recv);
void (*error_hook) (grub_net_tcp_socket_t sock, void *recv);
void (*fin_hook) (grub_net_tcp_socket_t sock, void *recv);
void *hook_data;
grub_net_network_level_address_t out_nla;
grub_net_link_level_address_t ll_target_addr;
struct grub_net_network_level_interface *inf;
grub_net_packets_t packs;
grub_priority_queue_t pq;
};
struct grub_net_tcp_listen
{
struct grub_net_tcp_listen *next;
struct grub_net_tcp_listen **prev;
grub_uint16_t port;
const struct grub_net_network_level_interface *inf;
grub_err_t (*listen_hook) (grub_net_tcp_listen_t listen,
grub_net_tcp_socket_t sock,
void *data);
void *hook_data;
};
struct tcphdr
{
grub_uint16_t src;
grub_uint16_t dst;
grub_uint32_t seqnr;
grub_uint32_t ack;
grub_uint16_t flags;
grub_uint16_t window;
grub_uint16_t checksum;
grub_uint16_t urgent;
} GRUB_PACKED;
struct tcp_pseudohdr
{
grub_uint32_t src;
grub_uint32_t dst;
grub_uint8_t zero;
grub_uint8_t proto;
grub_uint16_t tcp_length;
} GRUB_PACKED;
struct tcp6_pseudohdr
{
grub_uint64_t src[2];
grub_uint64_t dst[2];
grub_uint32_t tcp_length;
grub_uint8_t zero[3];
grub_uint8_t proto;
} GRUB_PACKED;
static struct grub_net_tcp_socket *tcp_sockets;
static struct grub_net_tcp_listen *tcp_listens;
#define FOR_TCP_SOCKETS(var) FOR_LIST_ELEMENTS (var, tcp_sockets)
#define FOR_TCP_LISTENS(var) FOR_LIST_ELEMENTS (var, tcp_listens)
grub_net_tcp_listen_t
grub_net_tcp_listen (grub_uint16_t port,
const struct grub_net_network_level_interface *inf,
grub_err_t (*listen_hook) (grub_net_tcp_listen_t listen,
grub_net_tcp_socket_t sock,
void *data),
void *hook_data)
{
grub_net_tcp_listen_t ret;
ret = grub_malloc (sizeof (*ret));
if (!ret)
return NULL;
ret->listen_hook = listen_hook;
ret->hook_data = hook_data;
ret->port = port;
ret->inf = inf;
grub_list_push (GRUB_AS_LIST_P (&tcp_listens), GRUB_AS_LIST (ret));
return ret;
}
void
grub_net_tcp_stop_listen (grub_net_tcp_listen_t listen)
{
grub_list_remove (GRUB_AS_LIST (listen));
}
static inline void
tcp_socket_register (grub_net_tcp_socket_t sock)
{
grub_list_push (GRUB_AS_LIST_P (&tcp_sockets),
GRUB_AS_LIST (sock));
}
static void
error (grub_net_tcp_socket_t sock)
{
struct unacked *unack, *next;
if (sock->error_hook)
sock->error_hook (sock, sock->hook_data);
for (unack = sock->unack_first; unack; unack = next)
{
next = unack->next;
grub_netbuff_free (unack->nb);
grub_free (unack);
}
sock->unack_first = NULL;
sock->unack_last = NULL;
}
static grub_err_t
tcp_send (struct grub_net_buff *nb, grub_net_tcp_socket_t socket)
{
grub_err_t err;
grub_uint8_t *nbd;
struct unacked *unack;
struct tcphdr *tcph;
grub_size_t size;
tcph = (struct tcphdr *) nb->data;
tcph->seqnr = grub_cpu_to_be32 (socket->my_cur_seq);
size = (nb->tail - nb->data - (grub_be_to_cpu16 (tcph->flags) >> 12) * 4);
if (grub_be_to_cpu16 (tcph->flags) & TCP_FIN)
size++;
socket->my_cur_seq += size;
tcph->src = grub_cpu_to_be16 (socket->in_port);
tcph->dst = grub_cpu_to_be16 (socket->out_port);
tcph->checksum = 0;
tcph->checksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_TCP,
&socket->inf->address,
&socket->out_nla);
nbd = nb->data;
if (size)
{
unack = grub_malloc (sizeof (*unack));
if (!unack)
return grub_errno;
unack->next = NULL;
unack->nb = nb;
unack->try_count = 1;
unack->last_try = grub_get_time_ms ();
if (!socket->unack_last)
socket->unack_first = socket->unack_last = unack;
else
socket->unack_last->next = unack;
}
err = grub_net_send_ip_packet (socket->inf, &(socket->out_nla),
&(socket->ll_target_addr), nb,
GRUB_NET_IP_TCP);
if (err)
return err;
nb->data = nbd;
if (!size)
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
void
grub_net_tcp_close (grub_net_tcp_socket_t sock,
int discard_received)
{
struct grub_net_buff *nb_fin;
struct tcphdr *tcph_fin;
grub_err_t err;
if (discard_received != GRUB_NET_TCP_CONTINUE_RECEIVING)
{
sock->recv_hook = NULL;
sock->error_hook = NULL;
sock->fin_hook = NULL;
}
if (discard_received == GRUB_NET_TCP_ABORT)
sock->i_reseted = 1;
if (sock->i_closed)
return;
sock->i_closed = 1;
nb_fin = grub_netbuff_alloc (sizeof (*tcph_fin)
+ GRUB_NET_OUR_MAX_IP_HEADER_SIZE
+ GRUB_NET_MAX_LINK_HEADER_SIZE);
if (!nb_fin)
return;
err = grub_netbuff_reserve (nb_fin, GRUB_NET_OUR_MAX_IP_HEADER_SIZE
+ GRUB_NET_MAX_LINK_HEADER_SIZE);
if (err)
{
grub_netbuff_free (nb_fin);
grub_dprintf ("net", "error closing socket\n");
grub_errno = GRUB_ERR_NONE;
return;
}
err = grub_netbuff_put (nb_fin, sizeof (*tcph_fin));
if (err)
{
grub_netbuff_free (nb_fin);
grub_dprintf ("net", "error closing socket\n");
grub_errno = GRUB_ERR_NONE;
return;
}
tcph_fin = (void *) nb_fin->data;
tcph_fin->ack = grub_cpu_to_be32 (sock->their_cur_seq);
tcph_fin->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_FIN
| TCP_ACK);
tcph_fin->window = grub_cpu_to_be16_compile_time (0);
tcph_fin->urgent = 0;
err = tcp_send (nb_fin, sock);
if (err)
{
grub_netbuff_free (nb_fin);
grub_dprintf ("net", "error closing socket\n");
grub_errno = GRUB_ERR_NONE;
}
return;
}
static void
ack_real (grub_net_tcp_socket_t sock, int res)
{
struct grub_net_buff *nb_ack;
struct tcphdr *tcph_ack;
grub_err_t err;
nb_ack = grub_netbuff_alloc (sizeof (*tcph_ack) + 128);
if (!nb_ack)
return;
err = grub_netbuff_reserve (nb_ack, 128);
if (err)
{
grub_netbuff_free (nb_ack);
grub_dprintf ("net", "error closing socket\n");
grub_errno = GRUB_ERR_NONE;
return;
}
err = grub_netbuff_put (nb_ack, sizeof (*tcph_ack));
if (err)
{
grub_netbuff_free (nb_ack);
grub_dprintf ("net", "error closing socket\n");
grub_errno = GRUB_ERR_NONE;
return;
}
tcph_ack = (void *) nb_ack->data;
if (res)
{
tcph_ack->ack = grub_cpu_to_be32_compile_time (0);
tcph_ack->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_RST);
tcph_ack->window = grub_cpu_to_be16_compile_time (0);
}
else
{
tcph_ack->ack = grub_cpu_to_be32 (sock->their_cur_seq);
tcph_ack->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_ACK);
tcph_ack->window = !sock->i_stall ? grub_cpu_to_be16 (sock->my_window)
: 0;
}
tcph_ack->urgent = 0;
tcph_ack->src = grub_cpu_to_be16 (sock->in_port);
tcph_ack->dst = grub_cpu_to_be16 (sock->out_port);
err = tcp_send (nb_ack, sock);
if (err)
{
grub_dprintf ("net", "error acking socket\n");
grub_errno = GRUB_ERR_NONE;
}
}
static void
ack (grub_net_tcp_socket_t sock)
{
ack_real (sock, 0);
}
static void
reset (grub_net_tcp_socket_t sock)
{
ack_real (sock, 1);
}
void
grub_net_tcp_retransmit (void)
{
grub_net_tcp_socket_t sock;
grub_uint64_t ctime = 0, limit_time = 0;
if (tcp_sockets != NULL)
{
ctime = grub_get_time_ms ();
limit_time = ctime - TCP_RETRANSMISSION_TIMEOUT;
}
FOR_TCP_SOCKETS (sock)
{
struct unacked *unack;
for (unack = sock->unack_first; unack; unack = unack->next)
{
struct tcphdr *tcph;
grub_uint8_t *nbd;
grub_err_t err;
if (unack->last_try > limit_time)
continue;
if (unack->try_count > TCP_RETRANSMISSION_COUNT)
{
error (sock);
break;
}
unack->try_count++;
unack->last_try = ctime;
nbd = unack->nb->data;
tcph = (struct tcphdr *) nbd;
if ((tcph->flags & grub_cpu_to_be16_compile_time (TCP_ACK))
&& tcph->ack != grub_cpu_to_be32 (sock->their_cur_seq))
{
tcph->checksum = 0;
tcph->checksum = grub_net_ip_transport_checksum (unack->nb,
GRUB_NET_IP_TCP,
&sock->inf->address,
&sock->out_nla);
}
err = grub_net_send_ip_packet (sock->inf, &(sock->out_nla),
&(sock->ll_target_addr), unack->nb,
GRUB_NET_IP_TCP);
unack->nb->data = nbd;
if (err)
{
grub_dprintf ("net", "TCP retransmit failed: %s\n", grub_errmsg);
grub_errno = GRUB_ERR_NONE;
}
}
}
}
grub_uint16_t
grub_net_ip_transport_checksum (struct grub_net_buff *nb,
grub_uint16_t proto,
const grub_net_network_level_address_t *src,
const grub_net_network_level_address_t *dst)
{
grub_uint16_t a, b = 0;
grub_uint32_t c;
a = ~grub_be_to_cpu16 (grub_net_ip_chksum ((void *) nb->data,
nb->tail - nb->data));
switch (dst->type)
{
case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4:
{
struct tcp_pseudohdr ph;
ph.src = src->ipv4;
ph.dst = dst->ipv4;
ph.zero = 0;
ph.tcp_length = grub_cpu_to_be16 (nb->tail - nb->data);
ph.proto = proto;
b = ~grub_be_to_cpu16 (grub_net_ip_chksum ((void *) &ph, sizeof (ph)));
break;
}
case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6:
{
struct tcp6_pseudohdr ph;
grub_memcpy (ph.src, src->ipv6, sizeof (ph.src));
grub_memcpy (ph.dst, dst->ipv6, sizeof (ph.dst));
grub_memset (ph.zero, 0, sizeof (ph.zero));
ph.tcp_length = grub_cpu_to_be32 (nb->tail - nb->data);
ph.proto = proto;
b = ~grub_be_to_cpu16 (grub_net_ip_chksum ((void *) &ph, sizeof (ph)));
break;
}
case GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV:
b = 0;
break;
}
c = (grub_uint32_t) a + (grub_uint32_t) b;
if (c >= 0xffff)
c -= 0xffff;
return grub_cpu_to_be16 (~c);
}
/* FIXME: overflow. */
static int
cmp (const void *a__, const void *b__)
{
struct grub_net_buff *a_ = *(struct grub_net_buff **) a__;
struct grub_net_buff *b_ = *(struct grub_net_buff **) b__;
struct tcphdr *a = (struct tcphdr *) a_->data;
struct tcphdr *b = (struct tcphdr *) b_->data;
/* We want the first elements to be on top. */
if (grub_be_to_cpu32 (a->seqnr) < grub_be_to_cpu32 (b->seqnr))
return +1;
if (grub_be_to_cpu32 (a->seqnr) > grub_be_to_cpu32 (b->seqnr))
return -1;
return 0;
}
static void
destroy_pq (grub_net_tcp_socket_t sock)
{
struct grub_net_buff **nb_p;
while ((nb_p = grub_priority_queue_top (sock->pq)))
{
grub_netbuff_free (*nb_p);
grub_priority_queue_pop (sock->pq);
}
grub_priority_queue_destroy (sock->pq);
}
grub_err_t
grub_net_tcp_accept (grub_net_tcp_socket_t sock,
grub_err_t (*recv_hook) (grub_net_tcp_socket_t sock,
struct grub_net_buff *nb,
void *data),
void (*error_hook) (grub_net_tcp_socket_t sock,
void *data),
void (*fin_hook) (grub_net_tcp_socket_t sock,
void *data),
void *hook_data)
{
struct grub_net_buff *nb_ack;
struct tcphdr *tcph;
grub_err_t err;
grub_net_network_level_address_t gateway;
struct grub_net_network_level_interface *inf;
sock->recv_hook = recv_hook;
sock->error_hook = error_hook;
sock->fin_hook = fin_hook;
sock->hook_data = hook_data;
err = grub_net_route_address (sock->out_nla, &gateway, &inf);
if (err)
return err;
err = grub_net_link_layer_resolve (sock->inf, &gateway, &(sock->ll_target_addr));
if (err)
return err;
nb_ack = grub_netbuff_alloc (sizeof (*tcph)
+ GRUB_NET_OUR_MAX_IP_HEADER_SIZE
+ GRUB_NET_MAX_LINK_HEADER_SIZE);
if (!nb_ack)
return grub_errno;
err = grub_netbuff_reserve (nb_ack, GRUB_NET_OUR_MAX_IP_HEADER_SIZE
+ GRUB_NET_MAX_LINK_HEADER_SIZE);
if (err)
{
grub_netbuff_free (nb_ack);
return err;
}
err = grub_netbuff_put (nb_ack, sizeof (*tcph));
if (err)
{
grub_netbuff_free (nb_ack);
return err;
}
tcph = (void *) nb_ack->data;
tcph->ack = grub_cpu_to_be32 (sock->their_cur_seq);
tcph->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_SYN | TCP_ACK);
tcph->window = grub_cpu_to_be16 (sock->my_window);
tcph->urgent = 0;
sock->established = 1;
tcp_socket_register (sock);
err = tcp_send (nb_ack, sock);
if (err)
return err;
sock->my_cur_seq++;
return GRUB_ERR_NONE;
}
/*
* Derive a time-based source port to avoid reusing the same port across
* reboots. This helps prevent failures caused by server side TCP state
* (e.g. TIME_WAIT) from interfering with new connections using the same socket.
*
* The base port starts at 21550 and increments every second by 8 across
* a 5 minute window (300 seconds), giving 2400 possible distinct base ports
* per window. In typical GRUB usage, the number of connections per boot is
* small, so reuse is effectively avoided.
*/
static grub_uint16_t
get_initial_base_port (void)
{
grub_err_t err;
struct grub_datetime date;
grub_int64_t t = 0;
grub_uint64_t r = 0;
err = grub_get_datetime (&date);
if (err != GRUB_ERR_NONE || !grub_datetime2unixtime (&date, &t))
{
grub_errno = GRUB_ERR_NONE;
return 21550;
}
grub_divmod64 (t, 300, &r);
return 21550 + (r << 3);
}
grub_net_tcp_socket_t
grub_net_tcp_open (char *server,
grub_uint16_t out_port,
grub_err_t (*recv_hook) (grub_net_tcp_socket_t sock,
struct grub_net_buff *nb,
void *data),
void (*error_hook) (grub_net_tcp_socket_t sock,
void *data),
void (*fin_hook) (grub_net_tcp_socket_t sock,
void *data),
void *hook_data)
{
grub_err_t err;
grub_net_network_level_address_t addr;
struct grub_net_network_level_interface *inf;
grub_net_network_level_address_t gateway;
grub_net_tcp_socket_t socket;
static grub_uint16_t in_port;
struct grub_net_buff *nb;
struct tcphdr *tcph;
int i;
grub_uint8_t *nbd;
grub_net_link_level_address_t ll_target_addr;
if (!in_port)
{
in_port = get_initial_base_port ();
grub_dprintf ("net", "base port: %d\n", in_port);
}
err = grub_net_resolve_address (server, &addr);
if (err)
return NULL;
if (addr.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4
&& addr.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6)
{
grub_error (GRUB_ERR_BUG, "not an IP address");
return NULL;
}
err = grub_net_route_address (addr, &gateway, &inf);
if (err)
return NULL;
err = grub_net_link_layer_resolve (inf, &gateway, &ll_target_addr);
if (err)
return NULL;
socket = grub_zalloc (sizeof (*socket));
if (socket == NULL)
return NULL;
socket->out_port = out_port;
socket->inf = inf;
socket->out_nla = addr;
socket->ll_target_addr = ll_target_addr;
socket->in_port = in_port++;
socket->recv_hook = recv_hook;
socket->error_hook = error_hook;
socket->fin_hook = fin_hook;
socket->hook_data = hook_data;
nb = grub_netbuff_alloc (sizeof (*tcph) + 128);
if (!nb)
{
grub_free (socket);
return NULL;
}
err = grub_netbuff_reserve (nb, 128);
if (err)
{
grub_free (socket);
grub_netbuff_free (nb);
return NULL;
}
err = grub_netbuff_put (nb, sizeof (*tcph));
if (err)
{
grub_free (socket);
grub_netbuff_free (nb);
return NULL;
}
socket->pq = grub_priority_queue_new (sizeof (struct grub_net_buff *), cmp);
if (!socket->pq)
{
grub_free (socket);
grub_netbuff_free (nb);
return NULL;
}
tcph = (void *) nb->data;
socket->my_start_seq = grub_get_time_ms ();
socket->my_cur_seq = socket->my_start_seq + 1;
socket->my_window = 8192;
tcph->seqnr = grub_cpu_to_be32 (socket->my_start_seq);
tcph->ack = grub_cpu_to_be32_compile_time (0);
tcph->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_SYN);
tcph->window = grub_cpu_to_be16 (socket->my_window);
tcph->urgent = 0;
tcph->src = grub_cpu_to_be16 (socket->in_port);
tcph->dst = grub_cpu_to_be16 (socket->out_port);
tcph->checksum = 0;
tcph->checksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_TCP,
&socket->inf->address,
&socket->out_nla);
tcp_socket_register (socket);
nbd = nb->data;
for (i = 0; i < TCP_SYN_RETRANSMISSION_COUNT; i++)
{
int j;
nb->data = nbd;
err = grub_net_send_ip_packet (socket->inf, &(socket->out_nla),
&(socket->ll_target_addr), nb,
GRUB_NET_IP_TCP);
if (err)
{
grub_list_remove (GRUB_AS_LIST (socket));
grub_free (socket);
grub_netbuff_free (nb);
return NULL;
}
for (j = 0; (j < TCP_SYN_RETRANSMISSION_TIMEOUT / 50
&& !socket->established); j++)
grub_net_poll_cards (50, &socket->established);
if (socket->established)
break;
}
if (!socket->established)
{
grub_list_remove (GRUB_AS_LIST (socket));
if (socket->they_reseted)
grub_error (GRUB_ERR_NET_PORT_CLOSED,
N_("connection refused"));
else
grub_error (GRUB_ERR_NET_NO_ANSWER,
N_("connection timeout"));
grub_netbuff_free (nb);
destroy_pq (socket);
grub_free (socket);
return NULL;
}
grub_netbuff_free (nb);
return socket;
}
grub_err_t
grub_net_send_tcp_packet (const grub_net_tcp_socket_t socket,
struct grub_net_buff *nb, int push)
{
struct tcphdr *tcph;
grub_err_t err;
grub_ssize_t fraglen;
COMPILE_TIME_ASSERT (sizeof (struct tcphdr) == GRUB_NET_TCP_HEADER_SIZE);
if (socket->out_nla.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4)
fraglen = (socket->inf->card->mtu - GRUB_NET_OUR_IPV4_HEADER_SIZE
- sizeof (*tcph));
else
fraglen = 1280 - GRUB_NET_OUR_IPV6_HEADER_SIZE;
while (nb->tail - nb->data > fraglen)
{
struct grub_net_buff *nb2;
nb2 = grub_netbuff_alloc (fraglen + sizeof (*tcph)
+ GRUB_NET_OUR_MAX_IP_HEADER_SIZE
+ GRUB_NET_MAX_LINK_HEADER_SIZE);
if (!nb2)
return grub_errno;
err = grub_netbuff_reserve (nb2, GRUB_NET_MAX_LINK_HEADER_SIZE
+ GRUB_NET_OUR_MAX_IP_HEADER_SIZE);
if (err)
return err;
err = grub_netbuff_put (nb2, sizeof (*tcph));
if (err)
return err;
tcph = (struct tcphdr *) nb2->data;
tcph->ack = grub_cpu_to_be32 (socket->their_cur_seq);
tcph->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_ACK);
tcph->window = !socket->i_stall ? grub_cpu_to_be16 (socket->my_window)
: 0;
tcph->urgent = 0;
err = grub_netbuff_put (nb2, fraglen);
if (err)
return err;
grub_memcpy (tcph + 1, nb->data, fraglen);
err = grub_netbuff_pull (nb, fraglen);
if (err)
return err;
err = tcp_send (nb2, socket);
if (err)
return err;
}
err = grub_netbuff_push (nb, sizeof (*tcph));
if (err)
return err;
tcph = (struct tcphdr *) nb->data;
tcph->ack = grub_cpu_to_be32 (socket->their_cur_seq);
tcph->flags = (grub_cpu_to_be16_compile_time ((5 << 12) | TCP_ACK)
| (push ? grub_cpu_to_be16_compile_time (TCP_PUSH) : 0));
tcph->window = !socket->i_stall ? grub_cpu_to_be16 (socket->my_window) : 0;
tcph->urgent = 0;
return tcp_send (nb, socket);
}
grub_err_t
grub_net_recv_tcp_packet (struct grub_net_buff *nb,
struct grub_net_network_level_interface *inf,
const grub_net_network_level_address_t *source)
{
struct tcphdr *tcph;
grub_net_tcp_socket_t sock;
grub_err_t err;
/* Ignore broadcast. */
if (!inf)
{
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
tcph = (struct tcphdr *) nb->data;
if ((grub_be_to_cpu16 (tcph->flags) >> 12) < 5)
{
grub_dprintf ("net", "TCP header too short: %u\n",
grub_be_to_cpu16 (tcph->flags) >> 12);
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
if (nb->tail - nb->data < (grub_ssize_t) ((grub_be_to_cpu16 (tcph->flags)
>> 12) * sizeof (grub_uint32_t)))
{
grub_dprintf ("net", "TCP packet too short: %" PRIuGRUB_SIZE "\n",
(grub_size_t) (nb->tail - nb->data));
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
FOR_TCP_SOCKETS (sock)
{
if (!(grub_be_to_cpu16 (tcph->dst) == sock->in_port
&& grub_be_to_cpu16 (tcph->src) == sock->out_port
&& inf == sock->inf
&& grub_net_addr_cmp (source, &sock->out_nla) == 0))
continue;
if (tcph->checksum)
{
grub_uint16_t chk, expected;
chk = tcph->checksum;
tcph->checksum = 0;
expected = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_TCP,
&sock->out_nla,
&sock->inf->address);
if (expected != chk)
{
grub_dprintf ("net", "Invalid TCP checksum. "
"Expected %x, got %x\n",
grub_be_to_cpu16 (expected),
grub_be_to_cpu16 (chk));
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
tcph->checksum = chk;
}
if ((grub_be_to_cpu16 (tcph->flags) & TCP_SYN)
&& (grub_be_to_cpu16 (tcph->flags) & TCP_ACK)
&& !sock->established)
{
sock->their_start_seq = grub_be_to_cpu32 (tcph->seqnr);
sock->their_cur_seq = sock->their_start_seq + 1;
sock->established = 1;
}
if (grub_be_to_cpu16 (tcph->flags) & TCP_RST)
{
sock->they_reseted = 1;
error (sock);
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
if (grub_be_to_cpu16 (tcph->flags) & TCP_ACK)
{
struct unacked *unack, *next;
grub_uint32_t acked = grub_be_to_cpu32 (tcph->ack);
for (unack = sock->unack_first; unack; unack = next)
{
grub_uint32_t seqnr;
struct tcphdr *unack_tcph;
next = unack->next;
seqnr = grub_be_to_cpu32 (((struct tcphdr *) unack->nb->data)
->seqnr);
unack_tcph = (struct tcphdr *) unack->nb->data;
seqnr += (unack->nb->tail - unack->nb->data
- (grub_be_to_cpu16 (unack_tcph->flags) >> 12) * 4);
if (grub_be_to_cpu16 (unack_tcph->flags) & TCP_FIN)
seqnr++;
if (seqnr > acked)
break;
grub_netbuff_free (unack->nb);
grub_free (unack);
}
sock->unack_first = unack;
if (!sock->unack_first)
sock->unack_last = NULL;
}
if (grub_be_to_cpu32 (tcph->seqnr) < sock->their_cur_seq)
{
ack (sock);
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
if (sock->i_reseted && (nb->tail - nb->data
- (grub_be_to_cpu16 (tcph->flags)
>> 12) * sizeof (grub_uint32_t)) > 0)
{
reset (sock);
}
err = grub_priority_queue_push (sock->pq, &nb);
if (err)
{
grub_netbuff_free (nb);
return err;
}
{
struct grub_net_buff **nb_top_p, *nb_top;
int do_ack = 0;
int just_closed = 0;
while (1)
{
nb_top_p = grub_priority_queue_top (sock->pq);
if (!nb_top_p)
return GRUB_ERR_NONE;
nb_top = *nb_top_p;
tcph = (struct tcphdr *) nb_top->data;
if (grub_be_to_cpu32 (tcph->seqnr) >= sock->their_cur_seq)
break;
grub_netbuff_free (nb_top);
grub_priority_queue_pop (sock->pq);
}
if (grub_be_to_cpu32 (tcph->seqnr) != sock->their_cur_seq)
{
ack (sock);
return GRUB_ERR_NONE;
}
while (1)
{
nb_top_p = grub_priority_queue_top (sock->pq);
if (!nb_top_p)
break;
nb_top = *nb_top_p;
tcph = (struct tcphdr *) nb_top->data;
if (grub_be_to_cpu32 (tcph->seqnr) != sock->their_cur_seq)
break;
grub_priority_queue_pop (sock->pq);
err = grub_netbuff_pull (nb_top, (grub_be_to_cpu16 (tcph->flags)
>> 12) * sizeof (grub_uint32_t));
if (err)
{
grub_netbuff_free (nb_top);
return err;
}
sock->their_cur_seq += (nb_top->tail - nb_top->data);
if (grub_be_to_cpu16 (tcph->flags) & TCP_FIN)
{
sock->they_closed = 1;
just_closed = 1;
sock->their_cur_seq++;
do_ack = 1;
}
/* If there is data, puts packet in socket list. */
if ((nb_top->tail - nb_top->data) > 0)
{
grub_net_put_packet (&sock->packs, nb_top);
do_ack = 1;
}
else
grub_netbuff_free (nb_top);
}
if (do_ack)
ack (sock);
while (sock->packs.first)
{
nb = sock->packs.first->nb;
if (sock->recv_hook)
sock->recv_hook (sock, sock->packs.first->nb, sock->hook_data);
else
grub_netbuff_free (nb);
grub_net_remove_packet (sock->packs.first);
}
if (sock->fin_hook && just_closed)
sock->fin_hook (sock, sock->hook_data);
}
return GRUB_ERR_NONE;
}
if (grub_be_to_cpu16 (tcph->flags) & TCP_SYN)
{
grub_net_tcp_listen_t listen;
FOR_TCP_LISTENS (listen)
{
if (!(grub_be_to_cpu16 (tcph->dst) == listen->port
&& (inf == listen->inf || listen->inf == NULL)))
continue;
sock = grub_zalloc (sizeof (*sock));
if (sock == NULL)
return grub_errno;
sock->out_port = grub_be_to_cpu16 (tcph->src);
sock->in_port = grub_be_to_cpu16 (tcph->dst);
sock->inf = inf;
sock->out_nla = *source;
sock->their_start_seq = grub_be_to_cpu32 (tcph->seqnr);
sock->their_cur_seq = sock->their_start_seq + 1;
sock->my_cur_seq = sock->my_start_seq = grub_get_time_ms ();
sock->my_window = 8192;
sock->pq = grub_priority_queue_new (sizeof (struct grub_net_buff *),
cmp);
if (!sock->pq)
{
grub_free (sock);
grub_netbuff_free (nb);
return grub_errno;
}
err = listen->listen_hook (listen, sock, listen->hook_data);
grub_netbuff_free (nb);
return err;
}
}
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
void
grub_net_tcp_stall (grub_net_tcp_socket_t sock)
{
if (sock->i_stall)
return;
sock->i_stall = 1;
ack (sock);
}
void
grub_net_tcp_unstall (grub_net_tcp_socket_t sock)
{
if (!sock->i_stall)
return;
sock->i_stall = 0;
ack (sock);
}