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>
diff --git a/net/fastboot_tcp.c b/net/fastboot_tcp.c
index 4d34fdc..3ea25c9 100644
--- a/net/fastboot_tcp.c
+++ b/net/fastboot_tcp.c
@@ -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);
-}
+ if (!data_read && rx_bytes >= handshake_length) {
+ if (memcmp(rxbuf, handshake, handshake_length)) {
+ printf("fastboot: bad handshake\n");
+ tcp_stream_close(tcp);
+ return;
+ }
-static void fastboot_tcp_reset(struct tcp_stream *tcp)
-{
- fastboot_tcp_answer(tcp, TCP_RST, 0);
- state = FASTBOOT_CLOSED;
-}
+ tx_last_offs = 0;
+ tx_last_len = handshake_length;
+ memcpy(txbuf, handshake, handshake_length);
-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();
+ data_read += handshake_length;
+ rx_bytes -= handshake_length;
+ if (rx_bytes > 0)
+ memmove(rxbuf, rxbuf + handshake_length, rx_bytes);
+ return;
+ }
- 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);
-}
+ if (rx_bytes < sizeof(u64))
+ return;
-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();
+ 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));
- 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);
+ 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 void fastboot_tcp_handler_ipv4(struct tcp_stream *tcp, uchar *pkt,
- u32 tcp_seq_num, u32 tcp_ack_num,
- u8 action, unsigned int len)
+static int tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, int len)
{
- int fastboot_command_id;
- u64 command_size;
- u8 tcp_fin = action & TCP_FIN;
- u8 tcp_push = action & TCP_PUSH;
+ memcpy(rxbuf + rx_offs - data_read, buf, len);
- curr_tcp_seq_num = tcp_seq_num;
- curr_tcp_ack_num = tcp_ack_num;
- curr_request_len = len;
+ return 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;
- }
- 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;
+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;
- // 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;
- }
+ maxlen = tx_last_offs + tx_last_len - tx_offs;
+ memcpy(buf, txbuf + (tx_offs - tx_last_offs), maxlen);
- 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;
+ return maxlen;
}
-static int incoming_filter(struct in_addr rhost, u16 rport, u16 lport)
+static int tcp_stream_on_create(struct tcp_stream *tcp)
{
- return (lport == FASTBOOT_TCP_PORT);
+ 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);
}