net/tcp: improve tcp framework, use better state machine

Changes:
 * Fix initial send sequence always zero issue
 * Use state machine close to RFC 9293. This should make TCP
   transfers more reliable (now we can upload a huge array
   of data from the board to external server)
 * Improve TCP framework a lot. This should make tcp client
   code much more simple.
 * rewrite wget with new tcp stack
 * rewrite fastboot_tcp with new tcp stack

It's quite hard to fix the initial send sequence (ISS) issue
with the separate patch. A naive attempt to fix an issue
inside the tcp_set_tcp_header() function will break tcp packet
retransmit logic in wget and other clients.

Example:
  Wget stores tcp_seq_num value before tcp_set_tcp_header() will
  be called and (on failure) retransmit the packet with the stored
  tcp_seq_num value. Thus:
    * the same ISS must allways be used (current case)
    * or tcp clients needs to generate a proper ISS when
      required.

A proper ISS fix will require a big redesing comparable with
a this one.

Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
Reviewed-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Mikhail Kshevetskiy 2024-12-28 13:46:32 +03:00 committed by Tom Rini
parent 82bf3aafa6
commit bf962de97c
6 changed files with 1159 additions and 729 deletions

View file

@ -265,6 +265,7 @@ union tcp_build_pkt {
* @TCP_CLOSING: Rec FIN, sent FIN, ACK waiting for ACK
* @TCP_FIN_WAIT_1: Sent FIN waiting for response
* @TCP_FIN_WAIT_2: Rec ACK from FIN sent, waiting for FIN
* @TCP_LAST_ACK: Waiting for ACK of the connection termination
*/
enum tcp_state {
TCP_CLOSED,
@ -274,7 +275,22 @@ enum tcp_state {
TCP_CLOSE_WAIT,
TCP_CLOSING,
TCP_FIN_WAIT_1,
TCP_FIN_WAIT_2
TCP_FIN_WAIT_2,
TCP_LAST_ACK,
};
/**
* enum tcp_status - TCP stream status for connection
* @TCP_ERR_OK: no rx/tx errors
* @TCP_ERR_TOUT: rx/tx timeout happened
* @TCP_ERR_RST: connection was reset
* @TCP_ERR_IO: input/output error
*/
enum tcp_status {
TCP_ERR_OK = 0,
TCP_ERR_TOUT,
TCP_ERR_RST,
TCP_ERR_IO
};
/**
@ -283,51 +299,156 @@ enum tcp_state {
* @rport: Remote port, host byte order
* @lport: Local port, host byte order
*
* @priv: User private data (not used by tcp module)
*
* @max_retry_count: Maximum retransmit attempts (default 3)
* @initial_timeout: Timeout from initial TX to reTX (default 2 sec)
* @rx_inactiv_timeout: Maximum time from last rx till connection drop
* (default 30 sec)
*
* @on_closed: User callback, called just before destroying TCP stream
* @on_established: User callback, called when TCP stream enters
* TCP_ESTABLISHED state
* @on_rcv_nxt_update: User callback, called when all data in the segment
* [0..rx_bytes - 1] was received
* @on_snd_una_update: User callback, called when all data in the segment
* [0..tx_bytes - 1] were transferred and acknowledged
* @rx: User callback, called on receive of segment
* [rx_offs..rx_offs+len-1]. If NULL -- all incoming data
* will be ignored. User SHOULD store the segment and
* return the number of accepted bytes or negative value
* on error.
* WARNING: Previous segmengs may not be received yet
* @tx: User callback, called on transmit/retransmit of segment
* [tx_offs..tx_offs+maxlen-1]. If NULL -- no data will
* be transmitted. User SHOULD fill provided buffer and
* return the number of bytes in the buffer or negative
* value on error.
* WARNING: do not use tcp_stream_close() from this
* callback (it will break stream). Better use
* on_snd_una_update() callback for such purposes.
*
* @time_last_rx: Arrival time of last valid incoming package (ticks)
* @time_start: Timeout start time (ticks)
* @time_delta: Timeout duration (ticks)
* @time_handler Timeout handler for a stream
*
* @state: TCP connection state
* @status: TCP stream status (OK or ERR)
* @rx_packets: total number of received packets
* @tx_packets: total number of transmitted packets
*
* @fin_rx: Non-zero if TCP_FIN was received
* @fin_rx_seq: TCP sequence of rx FIN bit
* @fin_tx: Non-zero if TCP_FIN was sent (or planned to send)
* @fin_tx_seq: TCP sequence of tx FIN bit
*
* @iss: Initial send sequence number
* @snd_una: Send unacknowledged
* @snd_nxt: Send next
* @snd_wnd: Send window (in bytes)
* @snd_wl1: Segment sequence number used for last window update
* @snd_wl2: Segment acknowledgment number used for last window update
*
* @irs: Initial receive sequence number
* @rcv_nxt: Receive next
* @rcv_wnd: Receive window (in bytes)
*
* @loc_timestamp: Local timestamp
* @rmt_timestamp: Remote timestamp
*
* @rmt_win_scale: Remote window scale factor
*
* @lost: Used for SACK
*
* @retry_cnt: Number of retry attempts remaining. Only SYN, FIN
* or DATA segments are tried to retransmit.
* @retry_timeout: Current retry timeout (ms)
* @retry_action: TCP flags used for sending
* @retry_seq_num: TCP sequence for retransmit
* retry_tx_len: Number of data to transmit
* @retry_tx_offs: Position in the TX stream
*/
struct tcp_stream {
struct in_addr rhost;
u16 rport;
u16 lport;
/* TCP connection state */
void *priv;
int max_retry_count;
int initial_timeout;
int rx_inactiv_timeout;
void (*on_closed)(struct tcp_stream *tcp);
void (*on_established)(struct tcp_stream *tcp);
void (*on_rcv_nxt_update)(struct tcp_stream *tcp, u32 rx_bytes);
void (*on_snd_una_update)(struct tcp_stream *tcp, u32 tx_bytes);
int (*rx)(struct tcp_stream *tcp, u32 rx_offs, void *buf, int len);
int (*tx)(struct tcp_stream *tcp, u32 tx_offs, void *buf, int maxlen);
ulong time_last_rx;
ulong time_start;
ulong time_delta;
void (*time_handler)(struct tcp_stream *tcp);
enum tcp_state state;
enum tcp_status status;
u32 rx_packets;
u32 tx_packets;
int fin_rx;
u32 fin_rx_seq;
int fin_tx;
u32 fin_tx_seq;
u32 iss;
u32 snd_una;
u32 snd_nxt;
u32 snd_wnd;
u32 snd_wl1;
u32 snd_wl2;
u32 irs;
u32 rcv_nxt;
u32 rcv_wnd;
/* TCP option timestamp */
u32 loc_timestamp;
u32 rmt_timestamp;
/* TCP window scale */
u8 rmt_win_scale;
/* TCP sliding window control used to request re-TX */
struct tcp_sack_v lost;
/* used for data retransmission */
int retry_cnt;
int retry_timeout;
u8 retry_action;
u32 retry_seq_num;
u32 retry_tx_len;
u32 retry_tx_offs;
};
void tcp_init(void);
typedef int tcp_incoming_filter(struct in_addr rhost,
u16 rport, u16 sport);
/*
* This function sets user callback used to accept/drop incoming
* connections. Callback should:
* This function sets user callback called on TCP stream creation.
* Callback should:
* + Check TCP stream endpoint and make connection verdict
* - return non-zero value to accept connection
* - return zero to drop connection
* + Setup TCP stream callbacks like: on_closed(), on_established(),
* n_rcv_nxt_update(), on_snd_una_update(), rx() and tx().
* + Setup other stream related data
*
* WARNING: If callback is NOT defined, all incoming connections
* will be dropped.
* WARNING: User MUST setup TCP stream on_create handler. Without it
* no connection (including outgoung) will be created.
*/
void tcp_set_incoming_filter(tcp_incoming_filter *filter);
void tcp_stream_set_on_create_handler(int (*on_create)(struct tcp_stream *));
/*
* tcp_stream_get -- Get or create TCP stream
@ -351,28 +472,52 @@ struct tcp_stream *tcp_stream_get(int is_new, struct in_addr rhost,
*/
struct tcp_stream *tcp_stream_connect(struct in_addr rhost, u16 rport);
enum tcp_state tcp_stream_get_state(struct tcp_stream *tcp);
/*
* tcp_stream_put -- Return stream to a TCP subsystem. Subsystem will
* check stream and destroy it (if stream was already
* closed). Otherwize no stream change will happen.
* @tcp: TCP stream to put
*/
void tcp_stream_put(struct tcp_stream *tcp);
/*
* tcp_stream_restart_rx_timer -- Restart RX inactivity timer. Usually there
* is no needs to call this function. Timer
* will be restarted on receiving of any valid
* tcp packet belonging to a stream.
*
* This function may be used to prevent connection
* break in the following case:
* - u-boot is busy with very long data processing
* - remote side waits for u-boot reply
*
* @tcp: TCP stream to put
*/
void tcp_stream_restart_rx_timer(struct tcp_stream *tcp);
enum tcp_state tcp_stream_get_state(struct tcp_stream *tcp);
enum tcp_status tcp_stream_get_status(struct tcp_stream *tcp);
/*
* tcp_stream_rx_offs(),
* tcp_stream_tx_offs() -- Returns offset of first unacknowledged byte
* in receive/transmit stream correspondingly.
* The result is NOT affected by sin/fin flags.
* @tcp: TCP stream
*/
u32 tcp_stream_rx_offs(struct tcp_stream *tcp);
u32 tcp_stream_tx_offs(struct tcp_stream *tcp);
/* reset tcp stream */
void tcp_stream_reset(struct tcp_stream *tcp);
/* force TCP stream closing, do NOT use from tcp->tx callback */
void tcp_stream_close(struct tcp_stream *tcp);
void tcp_streams_poll(void);
int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len,
u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
/**
* rxhand_tcp() - An incoming packet handler.
* @tcp: TCP stream
* @pkt: pointer to the application packet
* @dport: destination TCP port
* @sip: source IP address
* @sport: source TCP port
* @tcp_seq_num: TCP sequential number
* @tcp_ack_num: TCP acknowledgment number
* @action: TCP action (SYN, ACK, FIN, etc)
* @len: packet length
*/
typedef void rxhand_tcp(struct tcp_stream *tcp, uchar *pkt,
u32 tcp_seq_num, u32 tcp_ack_num,
u8 action, unsigned int len);
void tcp_set_tcp_handler(rxhand_tcp *f);
void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int len);
u16 tcp_set_pseudo_header(uchar *pkt, struct in_addr src, struct in_addr dest,

View file

@ -8,14 +8,6 @@
*/
void wget_start(void);
enum wget_state {
WGET_CLOSED,
WGET_CONNECTING,
WGET_CONNECTED,
WGET_TRANSFERRING,
WGET_TRANSFERRED
};
#define DEBUG_WGET 0 /* Set to 1 for debug messages */
#define WGET_RETRY_COUNT 30
#define WGET_TIMEOUT 2000UL

View file

@ -10,140 +10,109 @@
#define FASTBOOT_TCP_PORT 5554
static char command[FASTBOOT_COMMAND_LEN];
static char response[FASTBOOT_RESPONSE_LEN];
static const unsigned short handshake_length = 4;
static const uchar *handshake = "FB01";
static u32 curr_tcp_seq_num;
static u32 curr_tcp_ack_num;
static unsigned int curr_request_len;
static enum fastboot_tcp_state {
FASTBOOT_CLOSED,
FASTBOOT_CONNECTED,
FASTBOOT_DISCONNECTING
} state = FASTBOOT_CLOSED;
static char rxbuf[sizeof(u64) + FASTBOOT_COMMAND_LEN + 1];
static char txbuf[sizeof(u64) + FASTBOOT_RESPONSE_LEN + 1];
static void fastboot_tcp_answer(struct tcp_stream *tcp, u8 action,
unsigned int len)
static u32 data_read;
static u32 tx_last_offs, tx_last_len;
static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes)
{
const u32 response_seq_num = curr_tcp_ack_num;
const u32 response_ack_num = curr_tcp_seq_num +
(curr_request_len > 0 ? curr_request_len : 1);
u64 cmd_size;
__be64 len_be;
char saved;
int fastboot_command_id, len;
net_send_tcp_packet(len, tcp->rhost, tcp->rport, tcp->lport,
action, response_seq_num, response_ack_num);
}
static void fastboot_tcp_reset(struct tcp_stream *tcp)
{
fastboot_tcp_answer(tcp, TCP_RST, 0);
state = FASTBOOT_CLOSED;
}
static void fastboot_tcp_send_packet(struct tcp_stream *tcp, u8 action,
const uchar *data, unsigned int len)
{
uchar *pkt = net_get_async_tx_pkt_buf();
memset(pkt, '\0', PKTSIZE);
pkt += net_eth_hdr_size() + IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2;
memcpy(pkt, data, len);
fastboot_tcp_answer(tcp, action, len);
memset(pkt, '\0', PKTSIZE);
}
static void fastboot_tcp_send_message(struct tcp_stream *tcp,
const char *message, unsigned int len)
{
__be64 len_be = __cpu_to_be64(len);
uchar *pkt = net_get_async_tx_pkt_buf();
memset(pkt, '\0', PKTSIZE);
pkt += net_eth_hdr_size() + IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2;
// Put first 8 bytes as a big endian message length
memcpy(pkt, &len_be, 8);
pkt += 8;
memcpy(pkt, message, len);
fastboot_tcp_answer(tcp, TCP_ACK | TCP_PUSH, len + 8);
memset(pkt, '\0', PKTSIZE);
}
static void fastboot_tcp_handler_ipv4(struct tcp_stream *tcp, uchar *pkt,
u32 tcp_seq_num, u32 tcp_ack_num,
u8 action, unsigned int len)
{
int fastboot_command_id;
u64 command_size;
u8 tcp_fin = action & TCP_FIN;
u8 tcp_push = action & TCP_PUSH;
curr_tcp_seq_num = tcp_seq_num;
curr_tcp_ack_num = tcp_ack_num;
curr_request_len = len;
switch (state) {
case FASTBOOT_CLOSED:
if (tcp_push) {
if (len != handshake_length ||
strlen(pkt) != handshake_length ||
memcmp(pkt, handshake, handshake_length) != 0) {
fastboot_tcp_reset(tcp);
break;
}
fastboot_tcp_send_packet(tcp, TCP_ACK | TCP_PUSH,
handshake, handshake_length);
state = FASTBOOT_CONNECTED;
if (!data_read && rx_bytes >= handshake_length) {
if (memcmp(rxbuf, handshake, handshake_length)) {
printf("fastboot: bad handshake\n");
tcp_stream_close(tcp);
return;
}
break;
case FASTBOOT_CONNECTED:
if (tcp_fin) {
fastboot_tcp_answer(tcp, TCP_FIN | TCP_ACK, 0);
state = FASTBOOT_DISCONNECTING;
break;
}
if (tcp_push) {
// First 8 bytes is big endian message length
command_size = __be64_to_cpu(*(u64 *)pkt);
len -= 8;
pkt += 8;
// Only single packet messages are supported ATM
if (strlen(pkt) != command_size) {
fastboot_tcp_reset(tcp);
break;
}
strlcpy(command, pkt, len + 1);
fastboot_command_id = fastboot_handle_command(command, response);
fastboot_tcp_send_message(tcp, response, strlen(response));
fastboot_handle_boot(fastboot_command_id,
strncmp("OKAY", response, 4) == 0);
}
break;
case FASTBOOT_DISCONNECTING:
if (tcp_push)
state = FASTBOOT_CLOSED;
break;
tx_last_offs = 0;
tx_last_len = handshake_length;
memcpy(txbuf, handshake, handshake_length);
data_read += handshake_length;
rx_bytes -= handshake_length;
if (rx_bytes > 0)
memmove(rxbuf, rxbuf + handshake_length, rx_bytes);
return;
}
memset(command, 0, FASTBOOT_COMMAND_LEN);
memset(response, 0, FASTBOOT_RESPONSE_LEN);
curr_tcp_seq_num = 0;
curr_tcp_ack_num = 0;
curr_request_len = 0;
if (rx_bytes < sizeof(u64))
return;
memcpy(&cmd_size, rxbuf, sizeof(u64));
cmd_size = __be64_to_cpu(cmd_size);
if (rx_bytes < sizeof(u64) + cmd_size)
return;
saved = rxbuf[sizeof(u64) + cmd_size];
rxbuf[sizeof(u64) + cmd_size] = '\0';
fastboot_command_id = fastboot_handle_command(rxbuf + sizeof(u64),
txbuf + sizeof(u64));
fastboot_handle_boot(fastboot_command_id,
strncmp("OKAY", txbuf + sizeof(u64), 4) != 0);
rxbuf[sizeof(u64) + cmd_size] = saved;
len = strlen(txbuf + sizeof(u64));
len_be = __cpu_to_be64(len);
memcpy(txbuf, &len_be, sizeof(u64));
tx_last_offs += tx_last_len;
tx_last_len = len + sizeof(u64);
data_read += sizeof(u64) + cmd_size;
rx_bytes -= sizeof(u64) + cmd_size;
if (rx_bytes > 0)
memmove(rxbuf, rxbuf + sizeof(u64) + cmd_size, rx_bytes);
}
static int incoming_filter(struct in_addr rhost, u16 rport, u16 lport)
static int tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, int len)
{
return (lport == FASTBOOT_TCP_PORT);
memcpy(rxbuf + rx_offs - data_read, buf, len);
return len;
}
static int tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, int maxlen)
{
/* by design: tx_offs >= tx_last_offs */
if (tx_offs >= tx_last_offs + tx_last_len)
return 0;
maxlen = tx_last_offs + tx_last_len - tx_offs;
memcpy(buf, txbuf + (tx_offs - tx_last_offs), maxlen);
return maxlen;
}
static int tcp_stream_on_create(struct tcp_stream *tcp)
{
if (tcp->lport != FASTBOOT_TCP_PORT)
return 0;
data_read = 0;
tx_last_offs = 0;
tx_last_len = 0;
tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update;
tcp->rx = tcp_stream_rx;
tcp->tx = tcp_stream_tx;
return 1;
}
void fastboot_tcp_start_server(void)
{
memset(net_server_ethaddr, 0, 6);
tcp_stream_set_on_create_handler(tcp_stream_on_create);
printf("Using %s device\n", eth_get_name());
printf("Listening for fastboot command on tcp %pI4\n", &net_ip);
tcp_set_incoming_filter(incoming_filter);
tcp_set_tcp_handler(fastboot_tcp_handler_ipv4);
}

View file

@ -652,6 +652,9 @@ restart:
* errors that may have happened.
*/
eth_rx();
#if defined(CONFIG_PROT_TCP)
tcp_streams_poll();
#endif
/*
* Abort if ctrl-c was pressed.
@ -961,6 +964,7 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, int dport, int sport,
+ tcp_set_tcp_header(tcp, pkt + eth_hdr_size,
payload_len, action, tcp_seq_num,
tcp_ack_num);
tcp_stream_put(tcp);
break;
#endif
default:

921
net/tcp.c

File diff suppressed because it is too large Load diff

View file

@ -22,48 +22,26 @@ DECLARE_GLOBAL_DATA_PTR;
/* The default, change with environment variable 'httpdstp' */
#define SERVER_PORT 80
static const char bootfileGET[] = "GET ";
static const char bootfileHEAD[] = "HEAD ";
static const char bootfile3[] = " HTTP/1.0\r\n\r\n";
#define HASHES_PER_LINE 65
#define HTTP_MAX_HDR_LEN 2048
#define HTTP_STATUS_BAD 0
#define HTTP_STATUS_OK 200
static const char http_proto[] = "HTTP/1.0";
static const char http_eom[] = "\r\n\r\n";
static const char content_len[] = "Content-Length";
static const char content_len[] = "Content-Length:";
static const char linefeed[] = "\r\n";
static int wget_timeout_count;
struct tcp_stream *tcp;
struct pkt_qd {
uchar *pkt;
unsigned int tcp_seq_num;
unsigned int len;
};
/*
* This is a control structure for out of order packets received.
* The actual packet bufers are in the kernel space, and are
* expected to be overwritten by the downloaded image.
*/
#define PKTQ_SZ (PKTBUFSRX / 4)
static struct pkt_qd pkt_q[PKTQ_SZ];
static int pkt_q_idx;
static struct in_addr web_server_ip;
static unsigned int server_port;
static unsigned long content_length;
static unsigned int packets;
static unsigned int initial_data_seq_num;
static unsigned int next_data_seq_num;
static enum wget_state current_wget_state;
static u32 http_hdr_size, max_rx_pos;
static int wget_tsize_num_hash;
static char *image_url;
static unsigned int wget_timeout = WGET_TIMEOUT;
static enum net_loop_state wget_loop_state;
/* Timeout retry parameters */
static u8 retry_action; /* actions for TCP retry */
static unsigned int retry_tcp_ack_num; /* TCP retry acknowledge number*/
static unsigned int retry_tcp_seq_num; /* TCP retry sequence number */
static int retry_len; /* TCP retry length */
/**
* store_block() - store block in memory
* @src: source of data
@ -73,7 +51,6 @@ static int retry_len; /* TCP retry length */
static inline int store_block(uchar *src, unsigned int offset, unsigned int len)
{
ulong store_addr = image_load_addr + offset;
ulong newsize = offset + len;
uchar *ptr;
if (CONFIG_IS_ENABLED(LMB) && wget_info->set_bootdev) {
@ -89,372 +66,217 @@ static inline int store_block(uchar *src, unsigned int offset, unsigned int len)
memcpy(ptr, src, len);
unmap_sysmem(ptr);
if (net_boot_file_size < (offset + len))
net_boot_file_size = newsize;
return 0;
}
/**
* wget_send_stored() - wget response dispatcher
*
* WARNING, This, and only this, is the place in wget.c where
* SEQUENCE NUMBERS are swapped between incoming (RX)
* and outgoing (TX).
* Procedure wget_handler() is correct for RX traffic.
*/
static void wget_send_stored(void)
static void show_block_marker(u32 packets)
{
u8 action = retry_action;
int len = retry_len;
unsigned int tcp_ack_num = retry_tcp_seq_num + (len == 0 ? 1 : len);
unsigned int tcp_seq_num = retry_tcp_ack_num;
uchar *ptr, *offset;
int cnt;
switch (current_wget_state) {
case WGET_CLOSED:
debug_cond(DEBUG_WGET, "wget: send SYN\n");
current_wget_state = WGET_CONNECTING;
net_send_tcp_packet(0, tcp->rhost, tcp->rport, tcp->lport, action,
tcp_seq_num, tcp_ack_num);
packets = 0;
break;
case WGET_CONNECTING:
pkt_q_idx = 0;
net_send_tcp_packet(0, tcp->rhost, tcp->rport, tcp->lport, action,
tcp_seq_num, tcp_ack_num);
if (content_length != -1) {
if (net_boot_file_size > content_length)
content_length = net_boot_file_size;
ptr = net_tx_packet + net_eth_hdr_size() +
IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2;
offset = ptr;
switch (wget_info->method) {
case WGET_HTTP_METHOD_HEAD:
memcpy(offset, &bootfileHEAD, strlen(bootfileHEAD));
offset += strlen(bootfileHEAD);
break;
case WGET_HTTP_METHOD_GET:
default:
memcpy(offset, &bootfileGET, strlen(bootfileGET));
offset += strlen(bootfileGET);
break;
cnt = net_boot_file_size * 50 / content_length;
while (wget_tsize_num_hash < cnt) {
putc('#');
wget_tsize_num_hash++;
}
memcpy(offset, image_url, strlen(image_url));
offset += strlen(image_url);
memcpy(offset, &bootfile3, strlen(bootfile3));
offset += strlen(bootfile3);
net_send_tcp_packet((offset - ptr), tcp->rhost, tcp->rport, tcp->lport,
TCP_PUSH, tcp_seq_num, tcp_ack_num);
current_wget_state = WGET_CONNECTED;
break;
case WGET_CONNECTED:
case WGET_TRANSFERRING:
case WGET_TRANSFERRED:
net_send_tcp_packet(0, tcp->rhost, tcp->rport, tcp->lport, action,
tcp_seq_num, tcp_ack_num);
break;
}
}
static void wget_send(u8 action, unsigned int tcp_seq_num,
unsigned int tcp_ack_num, int len)
{
retry_action = action;
retry_tcp_ack_num = tcp_ack_num;
retry_tcp_seq_num = tcp_seq_num;
retry_len = len;
wget_send_stored();
}
void wget_fail(char *error_message, unsigned int tcp_seq_num,
unsigned int tcp_ack_num, u8 action)
{
printf("wget: Transfer Fail - %s\n", error_message);
net_set_timeout_handler(0, NULL);
wget_send(action, tcp_seq_num, tcp_ack_num, 0);
}
/*
* Interfaces of U-BOOT
*/
static void wget_timeout_handler(void)
{
if (++wget_timeout_count > WGET_RETRY_COUNT) {
puts("\nRetry count exceeded; starting again\n");
wget_send(TCP_RST, 0, 0, 0);
net_start_again();
} else {
puts("T ");
net_set_timeout_handler(wget_timeout +
WGET_TIMEOUT * wget_timeout_count,
wget_timeout_handler);
wget_send_stored();
if ((packets % 10) == 0)
putc('#');
else if (((packets + 1) % (10 * HASHES_PER_LINE)) == 0)
puts("\n");
}
}
#define PKT_QUEUE_OFFSET 0x20000
#define PKT_QUEUE_PACKET_SIZE 0x800
static void wget_fill_info(const uchar *pkt, int hlen)
static void tcp_stream_on_closed(struct tcp_stream *tcp)
{
const char *first_space;
const char *second_space;
char *pos, *end;
if (tcp->status != TCP_ERR_OK)
wget_loop_state = NETLOOP_FAIL;
if (wget_info->headers) {
if (hlen < MAX_HTTP_HEADERS_SIZE)
strncpy(wget_info->headers, pkt, hlen);
else
hlen = 0;
wget_info->headers[hlen] = 0;
}
//Get status code
first_space = strchr(pkt, ' ');
if (!first_space) {
wget_info->status_code = -1;
net_set_state(wget_loop_state);
if (wget_loop_state != NETLOOP_SUCCESS) {
net_boot_file_size = 0;
if (wget_info->status_code == HTTP_STATUS_OK) {
wget_info->status_code = HTTP_STATUS_BAD;
wget_info->hdr_cont_len = 0;
if (wget_info->headers)
wget_info->headers[0] = 0;
}
printf("\nwget: Transfer Fail, TCP status - %d\n", tcp->status);
return;
}
second_space = strchr(first_space + 1, ' ');
if (!second_space) {
wget_info->status_code = -1;
printf("\nPackets received %d, Transfer Successful\n", tcp->rx_packets);
wget_info->file_size = net_boot_file_size;
if (wget_info->method == WGET_HTTP_METHOD_GET && wget_info->set_bootdev) {
efi_set_bootdev("Http", NULL, image_url,
map_sysmem(image_load_addr, 0),
net_boot_file_size);
env_set_hex("filesize", net_boot_file_size);
}
}
static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes)
{
char *pos, *tail;
uchar saved, *ptr;
int reply_len;
if (http_hdr_size) {
net_boot_file_size = rx_bytes - http_hdr_size;
show_block_marker(tcp->rx_packets);
return;
}
wget_info->status_code = (u32)simple_strtoul(first_space + 1, &end, 10);
ptr = map_sysmem(image_load_addr, rx_bytes + 1);
if (second_space != end)
wget_info->status_code = -1;
saved = ptr[rx_bytes];
ptr[rx_bytes] = '\0';
pos = strstr((char *)ptr, http_eom);
ptr[rx_bytes] = saved;
pos = strstr((char *)pkt, content_len);
if (!pos) {
if (rx_bytes < HTTP_MAX_HDR_LEN &&
tcp->state == TCP_ESTABLISHED)
goto end;
printf("ERROR: misssed HTTP header\n");
tcp_stream_close(tcp);
goto end;
}
http_hdr_size = pos - (char *)ptr + strlen(http_eom);
*pos = '\0';
if (wget_info->headers && http_hdr_size < MAX_HTTP_HEADERS_SIZE)
strcpy(wget_info->headers, ptr);
/* check for HTTP proto */
if (strncasecmp((char *)ptr, "HTTP/", 5)) {
debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer "
"(no HTTP Status Line found)\n");
tcp_stream_close(tcp);
goto end;
}
/* get HTTP reply len */
pos = strstr((char *)ptr, linefeed);
if (pos)
reply_len = pos - (char *)ptr;
else
reply_len = http_hdr_size - strlen(http_eom);
pos = strchr((char *)ptr, ' ');
if (!pos || pos - (char *)ptr > reply_len) {
debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer "
"(no HTTP Status Code found)\n");
tcp_stream_close(tcp);
goto end;
}
wget_info->status_code = (u32)simple_strtoul(pos + 1, &tail, 10);
if (tail == pos + 1 || *tail != ' ') {
debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer "
"(bad HTTP Status Code)\n");
tcp_stream_close(tcp);
goto end;
}
debug_cond(DEBUG_WGET,
"wget: HTTP Status Code %d\n", wget_info->status_code);
if (wget_info->status_code != HTTP_STATUS_OK) {
debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer\n");
tcp_stream_close(tcp);
goto end;
}
debug_cond(DEBUG_WGET, "wget: Connctd pkt %p hlen %x\n",
ptr, http_hdr_size);
content_length = -1;
pos = strstr((char *)ptr, content_len);
if (pos) {
pos += sizeof(content_len) + 1;
pos += strlen(content_len) + 1;
while (*pos == ' ')
pos++;
content_length = simple_strtoul(pos, &end, 10);
content_length = simple_strtoul(pos, &tail, 10);
if (*tail != '\r' && *tail != '\n' && *tail != '\0')
content_length = -1;
}
if (content_length >= 0) {
debug_cond(DEBUG_WGET,
"wget: Connected Len %lu\n",
content_length);
wget_info->hdr_cont_len = content_length;
}
net_boot_file_size = rx_bytes - http_hdr_size;
memmove(ptr, ptr + http_hdr_size, max_rx_pos + 1 - http_hdr_size);
wget_loop_state = NETLOOP_SUCCESS;
end:
unmap_sysmem(ptr);
}
static void wget_connected(uchar *pkt, unsigned int tcp_seq_num,
u8 action, unsigned int tcp_ack_num, unsigned int len)
static int tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, int len)
{
uchar *pkt_in_q;
char *pos;
int hlen, i;
uchar *ptr1;
if ((max_rx_pos == (u32)(-1)) || (max_rx_pos < rx_offs + len - 1))
max_rx_pos = rx_offs + len - 1;
pkt[len] = '\0';
pos = strstr((char *)pkt, http_eom);
store_block(buf, rx_offs - http_hdr_size, len);
if (!pos) {
debug_cond(DEBUG_WGET,
"wget: Connected, data before Header %p\n", pkt);
pkt_in_q = (void *)image_load_addr + PKT_QUEUE_OFFSET +
(pkt_q_idx * PKT_QUEUE_PACKET_SIZE);
ptr1 = map_sysmem((ulong)pkt_in_q, len);
memcpy(ptr1, pkt, len);
unmap_sysmem(ptr1);
pkt_q[pkt_q_idx].pkt = pkt_in_q;
pkt_q[pkt_q_idx].tcp_seq_num = tcp_seq_num;
pkt_q[pkt_q_idx].len = len;
pkt_q_idx++;
if (pkt_q_idx >= PKTQ_SZ) {
printf("wget: Fatal error, queue overrun!\n");
net_set_state(NETLOOP_FAIL);
return;
}
} else {
debug_cond(DEBUG_WGET, "wget: Connected HTTP Header %p\n", pkt);
/* sizeof(http_eom) - 1 is the string length of (http_eom) */
hlen = pos - (char *)pkt + sizeof(http_eom) - 1;
pos = strstr((char *)pkt, linefeed);
if (pos > 0)
i = pos - (char *)pkt;
else
i = hlen;
printf("%.*s", i, pkt);
current_wget_state = WGET_TRANSFERRING;
initial_data_seq_num = tcp_seq_num + hlen;
next_data_seq_num = tcp_seq_num + len;
wget_fill_info(pkt, hlen);
debug_cond(DEBUG_WGET,
"wget: HTTP Status Code %d\n", wget_info->status_code);
if (wget_info->status_code != 200) {
debug_cond(DEBUG_WGET,
"wget: Connected Bad Xfer\n");
wget_loop_state = NETLOOP_FAIL;
wget_send(action, tcp_seq_num, tcp_ack_num, len);
} else {
debug_cond(DEBUG_WGET,
"wget: Connected Pkt %p hlen %x\n",
pkt, hlen);
net_boot_file_size = 0;
if (len > hlen) {
if (store_block(pkt + hlen, 0, len - hlen) != 0) {
wget_loop_state = NETLOOP_FAIL;
wget_fail("wget: store error\n", tcp_seq_num, tcp_ack_num, action);
net_set_state(NETLOOP_FAIL);
return;
}
}
for (i = 0; i < pkt_q_idx; i++) {
int err;
ptr1 = map_sysmem((ulong)pkt_q[i].pkt,
pkt_q[i].len);
err = store_block(ptr1,
pkt_q[i].tcp_seq_num -
initial_data_seq_num,
pkt_q[i].len);
unmap_sysmem(ptr1);
debug_cond(DEBUG_WGET,
"wget: Conncted pkt Q %p len %x\n",
pkt_q[i].pkt, pkt_q[i].len);
if (err) {
wget_loop_state = NETLOOP_FAIL;
wget_fail("wget: store error\n", tcp_seq_num, tcp_ack_num, action);
net_set_state(NETLOOP_FAIL);
return;
}
}
}
}
wget_send(action, tcp_seq_num, tcp_ack_num, len);
return len;
}
/**
* wget_handler() - TCP handler of wget
* @tcp: TCP stream
* @pkt: pointer to the application packet
* @tcp_seq_num: TCP sequential number
* @tcp_ack_num: TCP acknowledgment number
* @action: TCP action (SYN, ACK, FIN, etc)
* @len: packet length
*
* In the "application push" invocation, the TCP header with all
* its information is pointed to by the packet pointer.
*/
static void wget_handler(struct tcp_stream *tcp, uchar *pkt,
u32 tcp_seq_num, u32 tcp_ack_num,
u8 action, unsigned int len)
static int tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, int maxlen)
{
enum tcp_state wget_tcp_state = tcp_stream_get_state(tcp);
int ret;
const char *method;
net_set_timeout_handler(wget_timeout, wget_timeout_handler);
packets++;
if (tx_offs)
return 0;
switch (current_wget_state) {
case WGET_CLOSED:
debug_cond(DEBUG_WGET, "wget: Handler: Error!, State wrong\n");
switch (wget_info->method) {
case WGET_HTTP_METHOD_HEAD:
method = "HEAD";
break;
case WGET_CONNECTING:
debug_cond(DEBUG_WGET,
"wget: Connecting In len=%x, Seq=%u, Ack=%u\n",
len, tcp_seq_num, tcp_ack_num);
if (!len) {
if (wget_tcp_state == TCP_ESTABLISHED) {
debug_cond(DEBUG_WGET,
"wget: Cting, send, len=%x\n", len);
wget_send(action, tcp_seq_num, tcp_ack_num,
len);
} else {
printf("%.*s", len, pkt);
wget_fail("wget: Handler Connected Fail\n",
tcp_seq_num, tcp_ack_num, action);
}
}
break;
case WGET_CONNECTED:
debug_cond(DEBUG_WGET, "wget: Connected seq=%u, len=%x\n",
tcp_seq_num, len);
if (!len) {
wget_fail("Image not found, no data returned\n",
tcp_seq_num, tcp_ack_num, action);
} else {
wget_connected(pkt, tcp_seq_num, action, tcp_ack_num, len);
}
break;
case WGET_TRANSFERRING:
debug_cond(DEBUG_WGET,
"wget: Transferring, seq=%x, ack=%x,len=%x\n",
tcp_seq_num, tcp_ack_num, len);
if (next_data_seq_num != tcp_seq_num) {
debug_cond(DEBUG_WGET, "wget: seq=%x packet was lost\n", next_data_seq_num);
return;
}
next_data_seq_num = tcp_seq_num + len;
if (store_block(pkt, tcp_seq_num - initial_data_seq_num, len) != 0) {
wget_fail("wget: store error\n",
tcp_seq_num, tcp_ack_num, action);
net_set_state(NETLOOP_FAIL);
return;
}
switch (wget_tcp_state) {
case TCP_FIN_WAIT_2:
wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num, len);
fallthrough;
case TCP_SYN_SENT:
case TCP_SYN_RECEIVED:
case TCP_CLOSING:
case TCP_FIN_WAIT_1:
case TCP_CLOSED:
net_set_state(NETLOOP_FAIL);
break;
case TCP_ESTABLISHED:
wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num,
len);
wget_loop_state = NETLOOP_SUCCESS;
break;
case TCP_CLOSE_WAIT: /* End of transfer */
current_wget_state = WGET_TRANSFERRED;
wget_send(action | TCP_ACK | TCP_FIN,
tcp_seq_num, tcp_ack_num, len);
break;
}
break;
case WGET_TRANSFERRED:
printf("Packets received %d, Transfer Successful\n", packets);
net_set_state(wget_loop_state);
wget_info->file_size = net_boot_file_size;
if (wget_info->method == WGET_HTTP_METHOD_GET && wget_info->set_bootdev) {
efi_set_bootdev("Http", NULL, image_url,
map_sysmem(image_load_addr, 0),
net_boot_file_size);
env_set_hex("filesize", net_boot_file_size);
}
case WGET_HTTP_METHOD_GET:
default:
method = "GET";
break;
}
ret = snprintf(buf, maxlen, "%s %s %s\r\n\r\n",
method, image_url, http_proto);
return ret;
}
static int tcp_stream_on_create(struct tcp_stream *tcp)
{
if (tcp->rhost.s_addr != web_server_ip.s_addr ||
tcp->rport != server_port)
return 0;
tcp->max_retry_count = WGET_RETRY_COUNT;
tcp->initial_timeout = WGET_TIMEOUT;
tcp->on_closed = tcp_stream_on_closed;
tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update;
tcp->rx = tcp_stream_rx;
tcp->tx = tcp_stream_tx;
return 1;
}
#define BLOCKSIZE 512
void wget_start(void)
{
struct in_addr web_server_ip;
unsigned int server_port;
struct tcp_stream *tcp;
if (!wget_info)
wget_info = &default_wget_info;
@ -496,12 +318,6 @@ void wget_start(void)
debug_cond(DEBUG_WGET,
"\nwget:Load address: 0x%lx\nLoading: *\b", image_load_addr);
net_set_timeout_handler(wget_timeout, wget_timeout_handler);
tcp_set_tcp_handler(wget_handler);
wget_timeout_count = 0;
current_wget_state = WGET_CLOSED;
/*
* Zero out server ether to force arp resolution in case
* the server ip for the previous u-boot command, for example dns
@ -510,14 +326,27 @@ void wget_start(void)
memset(net_server_ethaddr, 0, 6);
max_rx_pos = (u32)(-1);
net_boot_file_size = 0;
http_hdr_size = 0;
wget_tsize_num_hash = 0;
wget_loop_state = NETLOOP_FAIL;
wget_info->status_code = HTTP_STATUS_BAD;
wget_info->file_size = 0;
wget_info->hdr_cont_len = 0;
if (wget_info->headers)
wget_info->headers[0] = 0;
server_port = env_get_ulong("httpdstp", 10, SERVER_PORT) & 0xffff;
tcp_stream_set_on_create_handler(tcp_stream_on_create);
tcp = tcp_stream_connect(web_server_ip, server_port);
if (!tcp) {
printf("No free tcp streams\n");
net_set_state(NETLOOP_FAIL);
return;
}
wget_send(TCP_SYN, 0, 0, 0);
tcp_stream_put(tcp);
}
int wget_do_request(ulong dst_addr, char *uri)