[PATCH v15 00/11] net: tcp: improve tcp support in legacy stack

Legacy TCP stack is bad. Here are some of the known issues: * tcp packet from other connection can break a current one * tcp send sequence always starts from zero * bad tcp options processing * strange assumptions on packet size for selective acknowledge * tcp interface assumes one of the two scenarios: - data downloading from remote host to a board - request-response exchange with a small packets so it's not possible to upload large amount of data from the board to remote host. * wget test generate bad tcp stream, test should fail but it passes instead
This series of patches fixes all of the above issues.
The benefits: * A lot of bug was fixed * Better and more reliable TCP state machine * Tcp clients becomes smaller/simpler * Data uploading was fixed (now it's possible to transmit a huge amount of data from the board to remote host)
Modification was verified with * firmware downloading via u-boot wget command * fastboot over tcp * netcat linux client using test netcat implementation (not included to this patch series) * Firefox/Chrome/Edge using test web-server implementation (not included to this patch series)
Changes v2: * cover letter was added * some patches were removed
Changes v3: * better cover letter
Changes v4: * fix bug in debug output * add more comments * code style fixes
Changes v5: * old patches were occasionally sent with v4 * add back web-server patches * fix bug in debug output * add more comments * code style fixes
Changes v6: * fix the wget test * improve description of "simplify tcp header filling code" patch
Changes v7: * fix include ordering * improve option descriptions * fix a lot of extra brackets and comparisons against NULL / 0 * add empty lines before final returns * fixed a bug with zero size httpd uploads
Changes v8: * [tcp] add function to restart rx inactivity timeout. This may help in the cases where remote peer waits for u-boot data, but u-boot is busy with long executing action * [httpd] add function returning underlying tcp stream from request id. This (with the above change) allows avoid connection break for the long processing post requests.
Changes v9: * rebase all changes on top of origin/next * update test/cmd/wget patch description (bad tcp ack/seq issue was already fixed in origin/next) * tcp: add rx/tx packet counters to a tcp_stream structure * wget: use tcp rx packet counter instead of own one * netcat: use tcp rx/tx packet counters instead of own one
Changes v10: * netcat: fix bug appeared after rx/tx packet counter addition * rebased on top of v2024.10
Changes v11: * fix spelling in cover letter
Changes v12: * rebase on top of master branch (3df6145db0e) * slightly change tcp->rx() & tcp->tx() callback declarations, this allow us to terminate a connection if something goes wrong in rx/tx paths. * set net_boot_file_size variable in httpd_upload * set filesize env variable to a proper value netcat and httpd_upload
Changes v13: * rebase on top of master branch (8573ea41058) * remove netcat support * remove example httpd server implementation
Changes v14: * fix issues found by github CI
Changes v15: * Rebase on top of next branch (5947cd76acd) * Add support of HTTP HEAD request to wget command. Thus it should work the same way as original wget from the next branch. Seems this is needed for efi http booting. * Parse HTTP status line in wget the same way as implemented in original wget from the next branch. Seems this is needed for efi http booting. * Fix bogus server response with an actual response from the HTTP server in test/cmd/wget. This makes a test more correct and beautifull. * Some debug messages were improved * Minor patches clenup
Mikhail Kshevetskiy (11): net/tcp: fix TCP options processing net/tcp: fix selective acknowledge net/tcp: put connection specific data into a tcp_stream structure net/tcp: add connection info to tcp_stream structure net/tcp: rename ack_edge and seq_init to more common rcv_nxt and irs net/tcp: improve tcp framework, use better state machine test/cmd/wget: fix the test test/cmd/wget: replace bogus response with an actual response from the HTTP server net/tcp: simplify tcp header filling code net/tcp: define a fallback value for rcv_wnd size net/net: fix include ordering
arch/sandbox/include/asm/eth.h | 4 + include/net-legacy.h | 5 +- include/net/tcp.h | 257 ++++++- include/net/wget.h | 8 - net/fastboot_tcp.c | 193 ++--- net/net.c | 45 +- net/tcp.c | 1297 ++++++++++++++++++++++---------- net/wget.c | 551 +++++--------- test/cmd/wget.c | 81 +- 9 files changed, 1504 insertions(+), 937 deletions(-)

Current TCP code may miss an option if TCP_O_NOP option was used before it for proper aligning.
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu Reviewed-by: Simon Glass sjg@chromium.org --- net/tcp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/net/tcp.c b/net/tcp.c index b0cc8a1fe3e..3e3118de450 100644 --- a/net/tcp.c +++ b/net/tcp.c @@ -475,7 +475,7 @@ void tcp_parse_options(uchar *o, int o_len) * NOPs are options with a zero length, and thus are special. * All other options have length fields. */ - for (p = o; p < (o + o_len); p = p + p[1]) { + for (p = o; p < (o + o_len); ) { if (!p[1]) return; /* Finished processing options */
@@ -490,12 +490,14 @@ void tcp_parse_options(uchar *o, int o_len) case TCP_O_TS: tsopt = (struct tcp_t_opt *)p; rmt_timestamp = tsopt->t_snd; - return; + break; }
/* Process optional NOPs */ if (p[0] == TCP_O_NOP) p++; + else + p += p[1]; } }

Current code assume that all (except last) packets are of the same size. This is definitely wrong. Replace SACK code with a new one, that does not rely on this assumption. Also this code uses less memory.
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu Reviewed-by: Simon Glass sjg@chromium.org --- net/tcp.c | 200 +++++++++++++++++++++++------------------------------- 1 file changed, 86 insertions(+), 114 deletions(-)
diff --git a/net/tcp.c b/net/tcp.c index 3e3118de450..724536cb352 100644 --- a/net/tcp.c +++ b/net/tcp.c @@ -38,21 +38,6 @@ static u32 tcp_ack_edge;
static int tcp_activity_count;
-/* - * Search for TCP_SACK and review the comments before the code section - * TCP_SACK is the number of packets at the front of the stream - */ - -enum pkt_state {PKT, NOPKT}; -struct sack_r { - struct sack_edges se; - enum pkt_state st; -}; - -static struct sack_r edge_a[TCP_SACK]; -static unsigned int sack_idx; -static unsigned int prev_len; - /* * TCP lengths are stored as a rounded up number of 32 bit words. * Add 3 to length round up, rounded, then divided into the @@ -69,6 +54,11 @@ static enum tcp_state current_tcp_state; /* Current TCP RX packet handler */ static rxhand_tcp *tcp_packet_handler;
+static inline s32 tcp_seq_cmp(u32 a, u32 b) +{ + return (s32)(a - b); +} + /** * tcp_get_tcp_state() - get current TCP state * @@ -267,6 +257,7 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, action = TCP_FIN; current_tcp_state = TCP_FIN_WAIT_1; } else { + tcp_lost.len = TCP_OPT_LEN_2; current_tcp_state = TCP_SYN_SENT; } break; @@ -353,6 +344,20 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, return pkt_hdr_len; }
+static void tcp_update_ack_edge(void) +{ + if (tcp_seq_cmp(tcp_ack_edge, tcp_lost.hill[0].l) >= 0) { + tcp_ack_edge = tcp_lost.hill[0].r; + + memmove(&tcp_lost.hill[0], &tcp_lost.hill[1], + (TCP_SACK_HILLS - 1) * sizeof(struct sack_edges)); + + tcp_lost.len -= TCP_OPT_LEN_8; + tcp_lost.hill[TCP_SACK_HILLS - 1].l = TCP_O_NOP; + tcp_lost.hill[TCP_SACK_HILLS - 1].r = TCP_O_NOP; + } +} + /** * tcp_hole() - Selective Acknowledgment (Essential for fast stream transfer) * @tcp_seq_num: TCP sequence start number @@ -360,106 +365,79 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, */ void tcp_hole(u32 tcp_seq_num, u32 len) { - u32 idx_sack, sack_in; - u32 sack_end = TCP_SACK - 1; - u32 hill = 0; - enum pkt_state expect = PKT; - u32 seq = tcp_seq_num - tcp_seq_init; - u32 hol_l = tcp_ack_edge - tcp_seq_init; - u32 hol_r = 0; - - /* Place new seq number in correct place in receive array */ - if (prev_len == 0) - prev_len = len; - - idx_sack = sack_idx + ((tcp_seq_num - tcp_ack_edge) / prev_len); - if (idx_sack < TCP_SACK) { - edge_a[idx_sack].se.l = tcp_seq_num; - edge_a[idx_sack].se.r = tcp_seq_num + len; - edge_a[idx_sack].st = PKT; + int i, j, cnt, cnt_move;
- /* - * The fin (last) packet is not the same length as data - * packets, and if it's length is recorded and used for - * array index calculation, calculation breaks. - */ - if (prev_len < len) - prev_len = len; - } + cnt = (tcp_lost.len - TCP_OPT_LEN_2) / TCP_OPT_LEN_8; + for (i = 0; i < cnt; i++) { + if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num) < 0) + continue; + if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num + len) > 0) + break;
- debug_cond(DEBUG_DEV_PKT, - "TCP 1 seq %d, edg %d, len %d, sack_idx %d, sack_end %d\n", - seq, hol_l, len, sack_idx, sack_end); + if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) > 0) + tcp_lost.hill[i].l = tcp_seq_num; + if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) < 0) { + len += tcp_seq_num - tcp_lost.hill[i].l; + tcp_seq_num = tcp_lost.hill[i].l; + } + if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num + len) >= 0) { + tcp_update_ack_edge(); + return; + }
- /* Right edge of contiguous stream, is the left edge of first hill */ - hol_l = tcp_seq_num - tcp_seq_init; - hol_r = hol_l + len; + /* check overlapping with next hills */ + cnt_move = 0; + tcp_lost.hill[i].r = tcp_seq_num + len; + for (j = i + 1; j < cnt; j++) { + if (tcp_seq_cmp(tcp_lost.hill[j].l, tcp_lost.hill[i].r) > 0) + break;
- if (IS_ENABLED(CONFIG_PROT_TCP_SACK)) - tcp_lost.len = TCP_OPT_LEN_2; + tcp_lost.hill[i].r = tcp_lost.hill[j].r; + cnt_move++; + }
- debug_cond(DEBUG_DEV_PKT, - "TCP 1 in %d, seq %d, pkt_l %d, pkt_r %d, sack_idx %d, sack_end %d\n", - idx_sack, seq, hol_l, hol_r, sack_idx, sack_end); - - for (sack_in = sack_idx; sack_in < sack_end && hill < TCP_SACK_HILLS; - sack_in++) { - switch (expect) { - case NOPKT: - switch (edge_a[sack_in].st) { - case NOPKT: - debug_cond(DEBUG_INT_STATE, "N"); - break; - case PKT: - debug_cond(DEBUG_INT_STATE, "n"); - if (IS_ENABLED(CONFIG_PROT_TCP_SACK)) { - tcp_lost.hill[hill].l = - edge_a[sack_in].se.l; - tcp_lost.hill[hill].r = - edge_a[sack_in].se.r; - } - expect = PKT; - break; - } - break; - case PKT: - switch (edge_a[sack_in].st) { - case NOPKT: - debug_cond(DEBUG_INT_STATE, "p"); - if (sack_in > sack_idx && - hill < TCP_SACK_HILLS) { - hill++; - if (IS_ENABLED(CONFIG_PROT_TCP_SACK)) - tcp_lost.len += TCP_OPT_LEN_8; - } - expect = NOPKT; - break; - case PKT: - debug_cond(DEBUG_INT_STATE, "P"); - - if (tcp_ack_edge == edge_a[sack_in].se.l) { - tcp_ack_edge = edge_a[sack_in].se.r; - edge_a[sack_in].st = NOPKT; - sack_idx++; - } else { - if (IS_ENABLED(CONFIG_PROT_TCP_SACK) && - hill < TCP_SACK_HILLS) - tcp_lost.hill[hill].r = - edge_a[sack_in].se.r; - if (IS_ENABLED(CONFIG_PROT_TCP_SACK) && - sack_in == sack_end - 1) - tcp_lost.hill[hill].r = - edge_a[sack_in].se.r; - } - break; + if (cnt_move > 0) { + if (cnt > i + cnt_move + 1) + memmove(&tcp_lost.hill[i + 1], + &tcp_lost.hill[i + cnt_move + 1], + cnt_move * sizeof(struct sack_edges)); + + cnt -= cnt_move; + tcp_lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8; + for (j = cnt; j < TCP_SACK_HILLS; j++) { + tcp_lost.hill[j].l = TCP_O_NOP; + tcp_lost.hill[j].r = TCP_O_NOP; } - break; } + + tcp_update_ack_edge(); + return; } - debug_cond(DEBUG_INT_STATE, "\n"); - if (!IS_ENABLED(CONFIG_PROT_TCP_SACK) || tcp_lost.len <= TCP_OPT_LEN_2) - sack_idx = 0; -} + + if (i == TCP_SACK_HILLS) { + tcp_update_ack_edge(); + return; + } + + if (cnt < TCP_SACK_HILLS) { + cnt_move = cnt - i; + cnt++; + } else { + cnt = TCP_SACK_HILLS; + cnt_move = TCP_SACK_HILLS - i; + } + + if (cnt_move > 0) + memmove(&tcp_lost.hill[i + 1], + &tcp_lost.hill[i], + cnt_move * sizeof(struct sack_edges)); + + tcp_lost.hill[i].l = tcp_seq_num; + tcp_lost.hill[i].r = tcp_seq_num + len; + tcp_lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8; + + tcp_update_ack_edge(); +};
/** * tcp_parse_options() - parsing TCP options @@ -509,7 +487,6 @@ static u8 tcp_state_machine(u8 tcp_flags, u32 tcp_seq_num, int payload_len) u8 tcp_push = tcp_flags & TCP_PUSH; u8 tcp_ack = tcp_flags & TCP_ACK; u8 action = TCP_DATA; - int i;
/* * tcp_flags are examined to determine TX action in a given state @@ -536,6 +513,7 @@ static u8 tcp_state_machine(u8 tcp_flags, u32 tcp_seq_num, int payload_len) action = TCP_SYN | TCP_ACK; tcp_seq_init = tcp_seq_num; tcp_ack_edge = tcp_seq_num + 1; + tcp_lost.len = TCP_OPT_LEN_2; current_tcp_state = TCP_SYN_RECEIVED; } else if (tcp_ack || tcp_fin) { action = TCP_DATA; @@ -552,13 +530,7 @@ static u8 tcp_state_machine(u8 tcp_flags, u32 tcp_seq_num, int payload_len) action |= TCP_ACK; tcp_seq_init = tcp_seq_num; tcp_ack_edge = tcp_seq_num + 1; - sack_idx = 0; - edge_a[sack_idx].se.l = tcp_ack_edge; - edge_a[sack_idx].se.r = tcp_ack_edge; - prev_len = 0; current_tcp_state = TCP_ESTABLISHED; - for (i = 0; i < TCP_SACK; i++) - edge_a[i].st = NOPKT;
if (tcp_syn && tcp_ack) action |= TCP_PUSH;

no functional changes
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu Reviewed-by: Simon Glass sjg@chromium.org --- include/net/tcp.h | 37 +++++++- net/net.c | 11 ++- net/tcp.c | 231 +++++++++++++++++++++++----------------------- net/wget.c | 3 +- 4 files changed, 163 insertions(+), 119 deletions(-)
diff --git a/include/net/tcp.h b/include/net/tcp.h index c29d4ce24a7..14aee64cb1c 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -277,9 +277,40 @@ enum tcp_state { TCP_FIN_WAIT_2 };
-enum tcp_state tcp_get_tcp_state(void); -void tcp_set_tcp_state(enum tcp_state new_state); -int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, +/** + * struct tcp_stream - TCP data stream structure + * + * @state: TCP connection state + * + * @seq_init: Initial receive sequence number + * @ack_edge: Receive next + * + * @loc_timestamp: Local timestamp + * @rmt_timestamp: Remote timestamp + * + * @lost: Used for SACK + */ +struct tcp_stream { + /* TCP connection state */ + enum tcp_state state; + + u32 seq_init; + u32 ack_edge; + + /* TCP option timestamp */ + u32 loc_timestamp; + u32 rmt_timestamp; + + /* TCP sliding window control used to request re-TX */ + struct tcp_sack_v lost; +}; + +struct tcp_stream *tcp_stream_get(void); + +enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp); +void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state); +int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, + int sport, int payload_len, u8 action, u32 tcp_seq_num, u32 tcp_ack_num);
/** diff --git a/net/net.c b/net/net.c index ca35704f661..e12ac55c401 100644 --- a/net/net.c +++ b/net/net.c @@ -420,7 +420,7 @@ int net_init(void) /* Only need to setup buffer pointers once. */ first_call = 0; if (IS_ENABLED(CONFIG_PROT_TCP)) - tcp_set_tcp_state(TCP_CLOSED); + tcp_set_tcp_state(tcp_stream_get(), TCP_CLOSED); }
return net_init_loop(); @@ -924,6 +924,9 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, int dport, int sport, uchar *pkt; int eth_hdr_size; int pkt_hdr_size; +#if defined(CONFIG_PROT_TCP) + struct tcp_stream *tcp; +#endif
/* make sure the net_tx_packet is initialized (net_init() was called) */ assert(net_tx_packet != NULL); @@ -950,8 +953,12 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, int dport, int sport, break; #if defined(CONFIG_PROT_TCP) case IPPROTO_TCP: + tcp = tcp_stream_get(); + if (!tcp) + return -EINVAL; + pkt_hdr_size = eth_hdr_size - + tcp_set_tcp_header(pkt + eth_hdr_size, dport, sport, + + tcp_set_tcp_header(tcp, pkt + eth_hdr_size, dport, sport, payload_len, action, tcp_seq_num, tcp_ack_num); break; diff --git a/net/tcp.c b/net/tcp.c index 724536cb352..8eeb43b8796 100644 --- a/net/tcp.c +++ b/net/tcp.c @@ -24,19 +24,8 @@ #include <net.h> #include <net/tcp.h>
-/* - * TCP sliding window control used by us to request re-TX - */ -static struct tcp_sack_v tcp_lost; - -/* TCP option timestamp */ -static u32 loc_timestamp; -static u32 rmt_timestamp; - -static u32 tcp_seq_init; -static u32 tcp_ack_edge; - static int tcp_activity_count; +static struct tcp_stream tcp_stream;
/* * TCP lengths are stored as a rounded up number of 32 bit words. @@ -48,9 +37,6 @@ static int tcp_activity_count; #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4) #define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
-/* TCP connection state */ -static enum tcp_state current_tcp_state; - /* Current TCP RX packet handler */ static rxhand_tcp *tcp_packet_handler;
@@ -60,22 +46,30 @@ static inline s32 tcp_seq_cmp(u32 a, u32 b) }
/** - * tcp_get_tcp_state() - get current TCP state + * tcp_get_tcp_state() - get TCP stream state + * @tcp: tcp stream * - * Return: Current TCP state + * Return: TCP stream state */ -enum tcp_state tcp_get_tcp_state(void) +enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp) { - return current_tcp_state; + return tcp->state; }
/** - * tcp_set_tcp_state() - set current TCP state + * tcp_set_tcp_state() - set TCP stream state + * @tcp: tcp stream * @new_state: new TCP state */ -void tcp_set_tcp_state(enum tcp_state new_state) +void tcp_set_tcp_state(struct tcp_stream *tcp, + enum tcp_state new_state) { - current_tcp_state = new_state; + tcp->state = new_state; +} + +struct tcp_stream *tcp_stream_get(void) +{ + return &tcp_stream; }
static void dummy_handler(uchar *pkt, u16 dport, @@ -138,29 +132,30 @@ u16 tcp_set_pseudo_header(uchar *pkt, struct in_addr src, struct in_addr dest,
/** * net_set_ack_options() - set TCP options in acknowledge packets + * @tcp: tcp stream * @b: the packet * * Return: TCP header length */ -int net_set_ack_options(union tcp_build_pkt *b) +int net_set_ack_options(struct tcp_stream *tcp, union tcp_build_pkt *b) { b->sack.hdr.tcp_hlen = SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
b->sack.t_opt.kind = TCP_O_TS; b->sack.t_opt.len = TCP_OPT_LEN_A; - b->sack.t_opt.t_snd = htons(loc_timestamp); - b->sack.t_opt.t_rcv = rmt_timestamp; + b->sack.t_opt.t_snd = htons(tcp->loc_timestamp); + b->sack.t_opt.t_rcv = tcp->rmt_timestamp; b->sack.sack_v.kind = TCP_1_NOP; b->sack.sack_v.len = 0;
if (IS_ENABLED(CONFIG_PROT_TCP_SACK)) { - if (tcp_lost.len > TCP_OPT_LEN_2) { + if (tcp->lost.len > TCP_OPT_LEN_2) { debug_cond(DEBUG_DEV_PKT, "TCP ack opt lost.len %x\n", - tcp_lost.len); - b->sack.sack_v.len = tcp_lost.len; + tcp->lost.len); + b->sack.sack_v.len = tcp->lost.len; b->sack.sack_v.kind = TCP_V_SACK; - b->sack.sack_v.hill[0].l = htonl(tcp_lost.hill[0].l); - b->sack.sack_v.hill[0].r = htonl(tcp_lost.hill[0].r); + b->sack.sack_v.hill[0].l = htonl(tcp->lost.hill[0].l); + b->sack.sack_v.hill[0].r = htonl(tcp->lost.hill[0].r);
/* * These SACK structures are initialized with NOPs to @@ -168,17 +163,17 @@ int net_set_ack_options(union tcp_build_pkt *b) * SACK structures used for both header padding and * internally. */ - b->sack.sack_v.hill[1].l = htonl(tcp_lost.hill[1].l); - b->sack.sack_v.hill[1].r = htonl(tcp_lost.hill[1].r); - b->sack.sack_v.hill[2].l = htonl(tcp_lost.hill[2].l); - b->sack.sack_v.hill[2].r = htonl(tcp_lost.hill[2].r); + b->sack.sack_v.hill[1].l = htonl(tcp->lost.hill[1].l); + b->sack.sack_v.hill[1].r = htonl(tcp->lost.hill[1].r); + b->sack.sack_v.hill[2].l = htonl(tcp->lost.hill[2].l); + b->sack.sack_v.hill[2].r = htonl(tcp->lost.hill[2].r); b->sack.sack_v.hill[3].l = TCP_O_NOP; b->sack.sack_v.hill[3].r = TCP_O_NOP; }
b->sack.hdr.tcp_hlen = SHIFT_TO_TCPHDRLEN_FIELD(ROUND_TCPHDR_LEN(TCP_HDR_SIZE + TCP_TSOPT_SIZE + - tcp_lost.len)); + tcp->lost.len)); } else { b->sack.sack_v.kind = 0; b->sack.hdr.tcp_hlen = SHIFT_TO_TCPHDRLEN_FIELD(ROUND_TCPHDR_LEN(TCP_HDR_SIZE + @@ -194,13 +189,14 @@ int net_set_ack_options(union tcp_build_pkt *b) }
/** - * net_set_ack_options() - set TCP options in SYN packets + * net_set_syn_options() - set TCP options in SYN packets + * @tcp: tcp stream * @b: the packet */ -void net_set_syn_options(union tcp_build_pkt *b) +void net_set_syn_options(struct tcp_stream *tcp, union tcp_build_pkt *b) { if (IS_ENABLED(CONFIG_PROT_TCP_SACK)) - tcp_lost.len = 0; + tcp->lost.len = 0;
b->ip.hdr.tcp_hlen = 0xa0;
@@ -219,14 +215,15 @@ void net_set_syn_options(union tcp_build_pkt *b) } b->ip.t_opt.kind = TCP_O_TS; b->ip.t_opt.len = TCP_OPT_LEN_A; - loc_timestamp = get_ticks(); - rmt_timestamp = 0; + tcp->loc_timestamp = get_ticks(); + tcp->rmt_timestamp = 0; b->ip.t_opt.t_snd = 0; b->ip.t_opt.t_rcv = 0; b->ip.end = TCP_O_END; }
-int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, +int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, + int sport, int payload_len, u8 action, u32 tcp_seq_num, u32 tcp_ack_num) { union tcp_build_pkt *b = (union tcp_build_pkt *)pkt; @@ -249,21 +246,21 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num); tcp_activity_count = 0; - net_set_syn_options(b); + net_set_syn_options(tcp, b); tcp_seq_num = 0; tcp_ack_num = 0; pkt_hdr_len = IP_TCP_O_SIZE; - if (current_tcp_state == TCP_SYN_SENT) { /* Too many SYNs */ + if (tcp->state == TCP_SYN_SENT) { /* Too many SYNs */ action = TCP_FIN; - current_tcp_state = TCP_FIN_WAIT_1; + tcp->state = TCP_FIN_WAIT_1; } else { - tcp_lost.len = TCP_OPT_LEN_2; - current_tcp_state = TCP_SYN_SENT; + tcp->lost.len = TCP_OPT_LEN_2; + tcp->state = TCP_SYN_SENT; } break; case TCP_SYN | TCP_ACK: case TCP_ACK: - pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(b); + pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b); b->ip.hdr.tcp_flags = action; debug_cond(DEBUG_DEV_PKT, "TCP Hdr:ACK (%pI4, %pI4, s=%u, a=%u, A=%x)\n", @@ -276,20 +273,20 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num); payload_len = 0; pkt_hdr_len = IP_TCP_HDR_SIZE; - current_tcp_state = TCP_FIN_WAIT_1; + tcp->state = TCP_FIN_WAIT_1; break; case TCP_RST | TCP_ACK: case TCP_RST: debug_cond(DEBUG_DEV_PKT, "TCP Hdr:RST (%pI4, %pI4, s=%u, a=%u)\n", &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num); - current_tcp_state = TCP_CLOSED; + tcp->state = TCP_CLOSED; break; /* Notify connection closing */ case (TCP_FIN | TCP_ACK): case (TCP_FIN | TCP_ACK | TCP_PUSH): - if (current_tcp_state == TCP_CLOSE_WAIT) - current_tcp_state = TCP_CLOSING; + if (tcp->state == TCP_CLOSE_WAIT) + tcp->state = TCP_CLOSING;
debug_cond(DEBUG_DEV_PKT, "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%u, a=%u, A=%x)\n", @@ -297,7 +294,7 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, tcp_seq_num, tcp_ack_num, action); fallthrough; default: - pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(b); + pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b); b->ip.hdr.tcp_flags = action | TCP_PUSH | TCP_ACK; debug_cond(DEBUG_DEV_PKT, "TCP Hdr:dft (%pI4, %pI4, s=%u, a=%u, A=%x)\n", @@ -308,9 +305,9 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, pkt_len = pkt_hdr_len + payload_len; tcp_len = pkt_len - IP_HDR_SIZE;
- tcp_ack_edge = tcp_ack_num; + tcp->ack_edge = tcp_ack_num; /* TCP Header */ - b->ip.hdr.tcp_ack = htonl(tcp_ack_edge); + b->ip.hdr.tcp_ack = htonl(tcp->ack_edge); b->ip.hdr.tcp_src = htons(sport); b->ip.hdr.tcp_dst = htons(dport); b->ip.hdr.tcp_seq = htonl(tcp_seq_num); @@ -344,78 +341,79 @@ int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, return pkt_hdr_len; }
-static void tcp_update_ack_edge(void) +static void tcp_update_ack_edge(struct tcp_stream *tcp) { - if (tcp_seq_cmp(tcp_ack_edge, tcp_lost.hill[0].l) >= 0) { - tcp_ack_edge = tcp_lost.hill[0].r; + if (tcp_seq_cmp(tcp->ack_edge, tcp->lost.hill[0].l) >= 0) { + tcp->ack_edge = tcp->lost.hill[0].r;
- memmove(&tcp_lost.hill[0], &tcp_lost.hill[1], + memmove(&tcp->lost.hill[0], &tcp->lost.hill[1], (TCP_SACK_HILLS - 1) * sizeof(struct sack_edges));
- tcp_lost.len -= TCP_OPT_LEN_8; - tcp_lost.hill[TCP_SACK_HILLS - 1].l = TCP_O_NOP; - tcp_lost.hill[TCP_SACK_HILLS - 1].r = TCP_O_NOP; + tcp->lost.len -= TCP_OPT_LEN_8; + tcp->lost.hill[TCP_SACK_HILLS - 1].l = TCP_O_NOP; + tcp->lost.hill[TCP_SACK_HILLS - 1].r = TCP_O_NOP; } }
/** * tcp_hole() - Selective Acknowledgment (Essential for fast stream transfer) + * @tcp: tcp stream * @tcp_seq_num: TCP sequence start number * @len: the length of sequence numbers */ -void tcp_hole(u32 tcp_seq_num, u32 len) +void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 len) { int i, j, cnt, cnt_move;
- cnt = (tcp_lost.len - TCP_OPT_LEN_2) / TCP_OPT_LEN_8; + cnt = (tcp->lost.len - TCP_OPT_LEN_2) / TCP_OPT_LEN_8; for (i = 0; i < cnt; i++) { - if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num) < 0) + if (tcp_seq_cmp(tcp->lost.hill[i].r, tcp_seq_num) < 0) continue; - if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num + len) > 0) + if (tcp_seq_cmp(tcp->lost.hill[i].l, tcp_seq_num + len) > 0) break;
- if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) > 0) - tcp_lost.hill[i].l = tcp_seq_num; - if (tcp_seq_cmp(tcp_lost.hill[i].l, tcp_seq_num) < 0) { - len += tcp_seq_num - tcp_lost.hill[i].l; - tcp_seq_num = tcp_lost.hill[i].l; + if (tcp_seq_cmp(tcp->lost.hill[i].l, tcp_seq_num) > 0) + tcp->lost.hill[i].l = tcp_seq_num; + if (tcp_seq_cmp(tcp->lost.hill[i].l, tcp_seq_num) < 0) { + len += tcp_seq_num - tcp->lost.hill[i].l; + tcp_seq_num = tcp->lost.hill[i].l; } - if (tcp_seq_cmp(tcp_lost.hill[i].r, tcp_seq_num + len) >= 0) { - tcp_update_ack_edge(); + if (tcp_seq_cmp(tcp->lost.hill[i].r, tcp_seq_num + len) >= 0) { + tcp_update_ack_edge(tcp); return; }
/* check overlapping with next hills */ cnt_move = 0; - tcp_lost.hill[i].r = tcp_seq_num + len; + tcp->lost.hill[i].r = tcp_seq_num + len; for (j = i + 1; j < cnt; j++) { - if (tcp_seq_cmp(tcp_lost.hill[j].l, tcp_lost.hill[i].r) > 0) + if (tcp_seq_cmp(tcp->lost.hill[j].l, tcp->lost.hill[i].r) > 0) break;
- tcp_lost.hill[i].r = tcp_lost.hill[j].r; + tcp->lost.hill[i].r = tcp->lost.hill[j].r; cnt_move++; }
if (cnt_move > 0) { if (cnt > i + cnt_move + 1) - memmove(&tcp_lost.hill[i + 1], - &tcp_lost.hill[i + cnt_move + 1], + memmove(&tcp->lost.hill[i + 1], + &tcp->lost.hill[i + cnt_move + 1], cnt_move * sizeof(struct sack_edges));
cnt -= cnt_move; - tcp_lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8; + tcp->lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8; for (j = cnt; j < TCP_SACK_HILLS; j++) { - tcp_lost.hill[j].l = TCP_O_NOP; - tcp_lost.hill[j].r = TCP_O_NOP; + tcp->lost.hill[j].l = TCP_O_NOP; + tcp->lost.hill[j].r = TCP_O_NOP; } }
- tcp_update_ack_edge(); + tcp_update_ack_edge(tcp); return; }
if (i == TCP_SACK_HILLS) { - tcp_update_ack_edge(); + tcp_update_ack_edge(tcp); return; }
@@ -428,23 +426,24 @@ void tcp_hole(u32 tcp_seq_num, u32 len) }
if (cnt_move > 0) - memmove(&tcp_lost.hill[i + 1], - &tcp_lost.hill[i], + memmove(&tcp->lost.hill[i + 1], + &tcp->lost.hill[i], cnt_move * sizeof(struct sack_edges));
- tcp_lost.hill[i].l = tcp_seq_num; - tcp_lost.hill[i].r = tcp_seq_num + len; - tcp_lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8; + tcp->lost.hill[i].l = tcp_seq_num; + tcp->lost.hill[i].r = tcp_seq_num + len; + tcp->lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8;
- tcp_update_ack_edge(); + tcp_update_ack_edge(tcp); };
/** * tcp_parse_options() - parsing TCP options + * @tcp: tcp stream * @o: pointer to the option field. * @o_len: length of the option field. */ -void tcp_parse_options(uchar *o, int o_len) +void tcp_parse_options(struct tcp_stream *tcp, uchar *o, int o_len) { struct tcp_t_opt *tsopt; uchar *p = o; @@ -467,7 +466,7 @@ void tcp_parse_options(uchar *o, int o_len) break; case TCP_O_TS: tsopt = (struct tcp_t_opt *)p; - rmt_timestamp = tsopt->t_snd; + tcp->rmt_timestamp = tsopt->t_snd; break; }
@@ -479,7 +478,8 @@ void tcp_parse_options(uchar *o, int o_len) } }
-static u8 tcp_state_machine(u8 tcp_flags, u32 tcp_seq_num, int payload_len) +static u8 tcp_state_machine(struct tcp_stream *tcp, u8 tcp_flags, + u32 tcp_seq_num, int payload_len) { u8 tcp_fin = tcp_flags & TCP_FIN; u8 tcp_syn = tcp_flags & TCP_SYN; @@ -500,21 +500,21 @@ static u8 tcp_state_machine(u8 tcp_flags, u32 tcp_seq_num, int payload_len) debug_cond(DEBUG_INT_STATE, "TCP STATE ENTRY %x\n", action); if (tcp_rst) { action = TCP_DATA; - current_tcp_state = TCP_CLOSED; + tcp->state = TCP_CLOSED; net_set_state(NETLOOP_FAIL); debug_cond(DEBUG_INT_STATE, "TCP Reset %x\n", tcp_flags); return TCP_RST; }
- switch (current_tcp_state) { + switch (tcp->state) { case TCP_CLOSED: debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags); if (tcp_syn) { action = TCP_SYN | TCP_ACK; - tcp_seq_init = tcp_seq_num; - tcp_ack_edge = tcp_seq_num + 1; - tcp_lost.len = TCP_OPT_LEN_2; - current_tcp_state = TCP_SYN_RECEIVED; + tcp->seq_init = tcp_seq_num; + tcp->ack_edge = tcp_seq_num + 1; + tcp->lost.len = TCP_OPT_LEN_2; + tcp->state = TCP_SYN_RECEIVED; } else if (tcp_ack || tcp_fin) { action = TCP_DATA; } @@ -525,12 +525,12 @@ static u8 tcp_state_machine(u8 tcp_flags, u32 tcp_seq_num, int payload_len) tcp_flags, tcp_seq_num); if (tcp_fin) { action = action | TCP_PUSH; - current_tcp_state = TCP_CLOSE_WAIT; + tcp->state = TCP_CLOSE_WAIT; } else if (tcp_ack || (tcp_syn && tcp_ack)) { action |= TCP_ACK; - tcp_seq_init = tcp_seq_num; - tcp_ack_edge = tcp_seq_num + 1; - current_tcp_state = TCP_ESTABLISHED; + tcp->seq_init = tcp_seq_num; + tcp->ack_edge = tcp_seq_num + 1; + tcp->state = TCP_ESTABLISHED;
if (tcp_syn && tcp_ack) action |= TCP_PUSH; @@ -541,15 +541,15 @@ static u8 tcp_state_machine(u8 tcp_flags, u32 tcp_seq_num, int payload_len) case TCP_ESTABLISHED: debug_cond(DEBUG_INT_STATE, "TCP_ESTABLISHED %x\n", tcp_flags); if (payload_len > 0) { - tcp_hole(tcp_seq_num, payload_len); + tcp_hole(tcp, tcp_seq_num, payload_len); tcp_fin = TCP_DATA; /* cause standalone FIN */ }
if ((tcp_fin) && (!IS_ENABLED(CONFIG_PROT_TCP_SACK) || - tcp_lost.len <= TCP_OPT_LEN_2)) { + tcp->lost.len <= TCP_OPT_LEN_2)) { action = action | TCP_FIN | TCP_PUSH | TCP_ACK; - current_tcp_state = TCP_CLOSE_WAIT; + tcp->state = TCP_CLOSE_WAIT; } else if (tcp_ack) { action = TCP_DATA; } @@ -567,7 +567,7 @@ static u8 tcp_state_machine(u8 tcp_flags, u32 tcp_seq_num, int payload_len) debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_2 (%x)\n", tcp_flags); if (tcp_ack) { action = TCP_PUSH | TCP_ACK; - current_tcp_state = TCP_CLOSED; + tcp->state = TCP_CLOSED; puts("\n"); } else if (tcp_syn) { action = TCP_DATA; @@ -578,20 +578,20 @@ static u8 tcp_state_machine(u8 tcp_flags, u32 tcp_seq_num, int payload_len) case TCP_FIN_WAIT_1: debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags); if (tcp_fin) { - tcp_ack_edge++; + tcp->ack_edge++; action = TCP_ACK | TCP_FIN; - current_tcp_state = TCP_FIN_WAIT_2; + tcp->state = TCP_FIN_WAIT_2; } if (tcp_syn) action = TCP_RST; if (tcp_ack) - current_tcp_state = TCP_CLOSED; + tcp->state = TCP_CLOSED; break; case TCP_CLOSING: debug_cond(DEBUG_INT_STATE, "TCP_CLOSING (%x)\n", tcp_flags); if (tcp_ack) { action = TCP_PUSH; - current_tcp_state = TCP_CLOSED; + tcp->state = TCP_CLOSED; puts("\n"); } else if (tcp_syn) { action = TCP_RST; @@ -615,6 +615,7 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) u8 tcp_action = TCP_DATA; u32 tcp_seq_num, tcp_ack_num; int tcp_hdr_len, payload_len; + struct tcp_stream *tcp;
/* Verify IP header */ debug_cond(DEBUG_DEV_PKT, @@ -643,11 +644,15 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) return; }
+ tcp = tcp_stream_get(); + if (!tcp) + return; + tcp_hdr_len = GET_TCP_HDR_LEN_IN_BYTES(b->ip.hdr.tcp_hlen); payload_len = tcp_len - tcp_hdr_len;
if (tcp_hdr_len > TCP_HDR_SIZE) - tcp_parse_options((uchar *)b + IP_TCP_HDR_SIZE, + tcp_parse_options(tcp, (uchar *)b + IP_TCP_HDR_SIZE, tcp_hdr_len - TCP_HDR_SIZE); /* * Incoming sequence and ack numbers are server's view of the numbers. @@ -657,7 +662,7 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) tcp_ack_num = ntohl(b->ip.hdr.tcp_ack);
/* Packets are not ordered. Send to app as received. */ - tcp_action = tcp_state_machine(b->ip.hdr.tcp_flags, + tcp_action = tcp_state_machine(tcp, b->ip.hdr.tcp_flags, tcp_seq_num, payload_len);
tcp_activity_count++; @@ -678,7 +683,7 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) } else if (tcp_action != TCP_DATA) { debug_cond(DEBUG_DEV_PKT, "TCP Action (action=%x,Seq=%u,Ack=%u,Pay=%d)\n", - tcp_action, tcp_ack_num, tcp_ack_edge, payload_len); + tcp_action, tcp_ack_num, tcp->ack_edge, payload_len);
/* * Warning: Incoming Ack & Seq sequence numbers are transposed @@ -687,6 +692,6 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) net_send_tcp_packet(0, ntohs(b->ip.hdr.tcp_src), ntohs(b->ip.hdr.tcp_dst), (tcp_action & (~TCP_PUSH)), - tcp_ack_num, tcp_ack_edge); + tcp_ack_num, tcp->ack_edge); } } diff --git a/net/wget.c b/net/wget.c index d338eaf4ef3..0213e3ff51d 100644 --- a/net/wget.c +++ b/net/wget.c @@ -365,7 +365,8 @@ static void wget_handler(uchar *pkt, u16 dport, u32 tcp_seq_num, u32 tcp_ack_num, u8 action, unsigned int len) { - enum tcp_state wget_tcp_state = tcp_get_tcp_state(); + struct tcp_stream *tcp = tcp_stream_get(); + enum tcp_state wget_tcp_state = tcp_get_tcp_state(tcp);
net_set_timeout_handler(wget_timeout, wget_timeout_handler); packets++;

Changes: * Avoid use net_server_ip in tcp code, use tcp_stream data instead * Ignore packets from other connections if connection already created. This prevents us from connection break caused by other tcp stream.
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu Reviewed-by: Simon Glass sjg@chromium.org --- include/net-legacy.h | 5 +- include/net/tcp.h | 57 ++++++++++++++++--- net/fastboot_tcp.c | 50 +++++++++-------- net/net.c | 12 ++-- net/tcp.c | 129 +++++++++++++++++++++++++++++++++---------- net/wget.c | 52 ++++++----------- 6 files changed, 203 insertions(+), 102 deletions(-)
diff --git a/include/net-legacy.h b/include/net-legacy.h index 1f62ebff51d..bc0f0cde9fe 100644 --- a/include/net-legacy.h +++ b/include/net-legacy.h @@ -416,6 +416,7 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, int dport, int sport, /** * net_send_tcp_packet() - Transmit TCP packet. * @payload_len: length of payload + * @dhost: Destination host * @dport: Destination TCP port * @sport: Source TCP port * @action: TCP action to be performed @@ -424,8 +425,8 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, int dport, int sport, * * Return: 0 on success, other value on failure */ -int net_send_tcp_packet(int payload_len, int dport, int sport, u8 action, - u32 tcp_seq_num, u32 tcp_ack_num); +int net_send_tcp_packet(int payload_len, struct in_addr dhost, int dport, + int sport, u8 action, u32 tcp_seq_num, u32 tcp_ack_num); int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport, int sport, int payload_len);
diff --git a/include/net/tcp.h b/include/net/tcp.h index 14aee64cb1c..f224d0cae2f 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -279,6 +279,9 @@ enum tcp_state {
/** * struct tcp_stream - TCP data stream structure + * @rhost: Remote host, network byte order + * @rport: Remote port, host byte order + * @lport: Local port, host byte order * * @state: TCP connection state * @@ -291,6 +294,10 @@ enum tcp_state { * @lost: Used for SACK */ struct tcp_stream { + struct in_addr rhost; + u16 rport; + u16 lport; + /* TCP connection state */ enum tcp_state state;
@@ -305,16 +312,53 @@ struct tcp_stream { struct tcp_sack_v lost; };
-struct tcp_stream *tcp_stream_get(void); +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: + * + Check TCP stream endpoint and make connection verdict + * - return non-zero value to accept connection + * - return zero to drop connection + * + * WARNING: If callback is NOT defined, all incoming connections + * will be dropped. + */ +void tcp_set_incoming_filter(tcp_incoming_filter *filter); + +/* + * tcp_stream_get -- Get or create TCP stream + * @is_new: if non-zero and no stream found, then create a new one + * @rhost: Remote host, network byte order + * @rport: Remote port, host byte order + * @lport: Local port, host byte order + * + * Returns: TCP stream structure or NULL (if not found/created) + */ +struct tcp_stream *tcp_stream_get(int is_new, struct in_addr rhost, + u16 rport, u16 lport); + +/* + * tcp_stream_connect -- Create new TCP stream for remote connection. + * @rhost: Remote host, network byte order + * @rport: Remote port, host byte order + * + * Returns: TCP new stream structure or NULL (if not created). + * Random local port will be used. + */ +struct tcp_stream *tcp_stream_connect(struct in_addr rhost, u16 rport); + +enum tcp_state tcp_stream_get_state(struct tcp_stream *tcp);
-enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp); -void tcp_set_tcp_state(struct tcp_stream *tcp, enum tcp_state new_state); -int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, - int sport, int payload_len, +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 @@ -324,8 +368,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, * @action: TCP action (SYN, ACK, FIN, etc) * @len: packet length */ -typedef void rxhand_tcp(uchar *pkt, u16 dport, - struct in_addr sip, u16 sport, +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); diff --git a/net/fastboot_tcp.c b/net/fastboot_tcp.c index d1fccbc7238..4d34fdc5a45 100644 --- a/net/fastboot_tcp.c +++ b/net/fastboot_tcp.c @@ -8,14 +8,14 @@ #include <net/fastboot_tcp.h> #include <net/tcp.h>
-static char command[FASTBOOT_COMMAND_LEN] = {0}; -static char response[FASTBOOT_RESPONSE_LEN] = {0}; +#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 u16 curr_sport; -static u16 curr_dport; static u32 curr_tcp_seq_num; static u32 curr_tcp_ack_num; static unsigned int curr_request_len; @@ -25,34 +25,37 @@ static enum fastboot_tcp_state { FASTBOOT_DISCONNECTING } state = FASTBOOT_CLOSED;
-static void fastboot_tcp_answer(u8 action, unsigned int len) +static void fastboot_tcp_answer(struct tcp_stream *tcp, u8 action, + unsigned int len) { 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);
- net_send_tcp_packet(len, htons(curr_sport), htons(curr_dport), + net_send_tcp_packet(len, tcp->rhost, tcp->rport, tcp->lport, action, response_seq_num, response_ack_num); }
-static void fastboot_tcp_reset(void) +static void fastboot_tcp_reset(struct tcp_stream *tcp) { - fastboot_tcp_answer(TCP_RST, 0); + fastboot_tcp_answer(tcp, TCP_RST, 0); state = FASTBOOT_CLOSED; }
-static void fastboot_tcp_send_packet(u8 action, const uchar *data, unsigned int len) +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(action, len); + fastboot_tcp_answer(tcp, action, len); memset(pkt, '\0', PKTSIZE); }
-static void fastboot_tcp_send_message(const char *message, unsigned int len) +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(); @@ -63,12 +66,11 @@ static void fastboot_tcp_send_message(const char *message, unsigned int len) memcpy(pkt, &len_be, 8); pkt += 8; memcpy(pkt, message, len); - fastboot_tcp_answer(TCP_ACK | TCP_PUSH, len + 8); + fastboot_tcp_answer(tcp, TCP_ACK | TCP_PUSH, len + 8); memset(pkt, '\0', PKTSIZE); }
-static void fastboot_tcp_handler_ipv4(uchar *pkt, u16 dport, - struct in_addr sip, u16 sport, +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) { @@ -77,8 +79,6 @@ static void fastboot_tcp_handler_ipv4(uchar *pkt, u16 dport, u8 tcp_fin = action & TCP_FIN; u8 tcp_push = action & TCP_PUSH;
- curr_sport = sport; - curr_dport = dport; curr_tcp_seq_num = tcp_seq_num; curr_tcp_ack_num = tcp_ack_num; curr_request_len = len; @@ -89,17 +89,17 @@ static void fastboot_tcp_handler_ipv4(uchar *pkt, u16 dport, if (len != handshake_length || strlen(pkt) != handshake_length || memcmp(pkt, handshake, handshake_length) != 0) { - fastboot_tcp_reset(); + fastboot_tcp_reset(tcp); break; } - fastboot_tcp_send_packet(TCP_ACK | TCP_PUSH, + 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_FIN | TCP_ACK, 0); + fastboot_tcp_answer(tcp, TCP_FIN | TCP_ACK, 0); state = FASTBOOT_DISCONNECTING; break; } @@ -111,12 +111,12 @@ static void fastboot_tcp_handler_ipv4(uchar *pkt, u16 dport,
// Only single packet messages are supported ATM if (strlen(pkt) != command_size) { - fastboot_tcp_reset(); + fastboot_tcp_reset(tcp); break; } strlcpy(command, pkt, len + 1); fastboot_command_id = fastboot_handle_command(command, response); - fastboot_tcp_send_message(response, strlen(response)); + fastboot_tcp_send_message(tcp, response, strlen(response)); fastboot_handle_boot(fastboot_command_id, strncmp("OKAY", response, 4) == 0); } @@ -129,17 +129,21 @@ static void fastboot_tcp_handler_ipv4(uchar *pkt, u16 dport,
memset(command, 0, FASTBOOT_COMMAND_LEN); memset(response, 0, FASTBOOT_RESPONSE_LEN); - curr_sport = 0; - curr_dport = 0; curr_tcp_seq_num = 0; curr_tcp_ack_num = 0; curr_request_len = 0; }
+static int incoming_filter(struct in_addr rhost, u16 rport, u16 lport) +{ + return (lport == FASTBOOT_TCP_PORT); +} + void fastboot_tcp_start_server(void) { 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); } diff --git a/net/net.c b/net/net.c index e12ac55c401..62d1c5b1e82 100644 --- a/net/net.c +++ b/net/net.c @@ -420,7 +420,7 @@ int net_init(void) /* Only need to setup buffer pointers once. */ first_call = 0; if (IS_ENABLED(CONFIG_PROT_TCP)) - tcp_set_tcp_state(tcp_stream_get(), TCP_CLOSED); + tcp_init(); }
return net_init_loop(); @@ -908,10 +908,10 @@ int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport, int sport, }
#if defined(CONFIG_PROT_TCP) -int net_send_tcp_packet(int payload_len, int dport, int sport, u8 action, - u32 tcp_seq_num, u32 tcp_ack_num) +int net_send_tcp_packet(int payload_len, struct in_addr dhost, int dport, + int sport, u8 action, u32 tcp_seq_num, u32 tcp_ack_num) { - return net_send_ip_packet(net_server_ethaddr, net_server_ip, dport, + return net_send_ip_packet(net_server_ethaddr, dhost, dport, sport, payload_len, IPPROTO_TCP, action, tcp_seq_num, tcp_ack_num); } @@ -953,12 +953,12 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, int dport, int sport, break; #if defined(CONFIG_PROT_TCP) case IPPROTO_TCP: - tcp = tcp_stream_get(); + tcp = tcp_stream_get(0, dest, dport, sport); if (!tcp) return -EINVAL;
pkt_hdr_size = eth_hdr_size - + tcp_set_tcp_header(tcp, pkt + eth_hdr_size, dport, sport, + + tcp_set_tcp_header(tcp, pkt + eth_hdr_size, payload_len, action, tcp_seq_num, tcp_ack_num); break; diff --git a/net/tcp.c b/net/tcp.c index 8eeb43b8796..db708f0a2a5 100644 --- a/net/tcp.c +++ b/net/tcp.c @@ -26,6 +26,7 @@
static int tcp_activity_count; static struct tcp_stream tcp_stream; +static tcp_incoming_filter *incoming_filter;
/* * TCP lengths are stored as a rounded up number of 32 bit words. @@ -40,40 +41,95 @@ static struct tcp_stream tcp_stream; /* Current TCP RX packet handler */ static rxhand_tcp *tcp_packet_handler;
+#define RANDOM_PORT_START 1024 +#define RANDOM_PORT_RANGE 0x4000 + +/** + * random_port() - make port a little random (1024-17407) + * + * Return: random port number from 1024 to 17407 + * + * This keeps the math somewhat trivial to compute, and seems to work with + * all supported protocols/clients/servers + */ +static uint random_port(void) +{ + return RANDOM_PORT_START + (get_timer(0) % RANDOM_PORT_RANGE); +} + static inline s32 tcp_seq_cmp(u32 a, u32 b) { return (s32)(a - b); }
/** - * tcp_get_tcp_state() - get TCP stream state + * tcp_stream_get_state() - get TCP stream state * @tcp: tcp stream * * Return: TCP stream state */ -enum tcp_state tcp_get_tcp_state(struct tcp_stream *tcp) +enum tcp_state tcp_stream_get_state(struct tcp_stream *tcp) { return tcp->state; }
/** - * tcp_set_tcp_state() - set TCP stream state + * tcp_stream_set_state() - set TCP stream state * @tcp: tcp stream * @new_state: new TCP state */ -void tcp_set_tcp_state(struct tcp_stream *tcp, - enum tcp_state new_state) +static void tcp_stream_set_state(struct tcp_stream *tcp, + enum tcp_state new_state) { tcp->state = new_state; }
-struct tcp_stream *tcp_stream_get(void) +void tcp_init(void) +{ + incoming_filter = NULL; + tcp_stream.state = TCP_CLOSED; +} + +void tcp_set_incoming_filter(tcp_incoming_filter *filter) +{ + incoming_filter = filter; +} + +static struct tcp_stream *tcp_stream_add(struct in_addr rhost, + u16 rport, u16 lport) +{ + struct tcp_stream *tcp = &tcp_stream; + + if (tcp->state != TCP_CLOSED) + return NULL; + + memset(tcp, 0, sizeof(struct tcp_stream)); + tcp->rhost.s_addr = rhost.s_addr; + tcp->rport = rport; + tcp->lport = lport; + tcp->state = TCP_CLOSED; + tcp->lost.len = TCP_OPT_LEN_2; + return tcp; +} + +struct tcp_stream *tcp_stream_get(int is_new, struct in_addr rhost, + u16 rport, u16 lport) { - return &tcp_stream; + struct tcp_stream *tcp = &tcp_stream; + + if (tcp->rhost.s_addr == rhost.s_addr && + tcp->rport == rport && + tcp->lport == lport) + return tcp; + + if (!is_new || !incoming_filter || + !incoming_filter(rhost, rport, lport)) + return NULL; + + return tcp_stream_add(rhost, rport, lport); }
-static void dummy_handler(uchar *pkt, u16 dport, - struct in_addr sip, u16 sport, +static void dummy_handler(struct tcp_stream *tcp, uchar *pkt, u32 tcp_seq_num, u32 tcp_ack_num, u8 action, unsigned int len) { @@ -222,8 +278,7 @@ void net_set_syn_options(struct tcp_stream *tcp, union tcp_build_pkt *b) b->ip.end = TCP_O_END; }
-int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, - int sport, int payload_len, +int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len, u8 action, u32 tcp_seq_num, u32 tcp_ack_num) { union tcp_build_pkt *b = (union tcp_build_pkt *)pkt; @@ -243,7 +298,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, case TCP_SYN: debug_cond(DEBUG_DEV_PKT, "TCP Hdr:SYN (%pI4, %pI4, sq=%u, ak=%u)\n", - &net_server_ip, &net_ip, + &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num); tcp_activity_count = 0; net_set_syn_options(tcp, b); @@ -264,13 +319,13 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, b->ip.hdr.tcp_flags = action; debug_cond(DEBUG_DEV_PKT, "TCP Hdr:ACK (%pI4, %pI4, s=%u, a=%u, A=%x)\n", - &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num, + &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num, action); break; case TCP_FIN: debug_cond(DEBUG_DEV_PKT, "TCP Hdr:FIN (%pI4, %pI4, s=%u, a=%u)\n", - &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num); + &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num); payload_len = 0; pkt_hdr_len = IP_TCP_HDR_SIZE; tcp->state = TCP_FIN_WAIT_1; @@ -279,7 +334,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, case TCP_RST: debug_cond(DEBUG_DEV_PKT, "TCP Hdr:RST (%pI4, %pI4, s=%u, a=%u)\n", - &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num); + &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num); tcp->state = TCP_CLOSED; break; /* Notify connection closing */ @@ -290,7 +345,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport,
debug_cond(DEBUG_DEV_PKT, "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%u, a=%u, A=%x)\n", - &net_server_ip, &net_ip, + &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num, action); fallthrough; default: @@ -298,7 +353,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, b->ip.hdr.tcp_flags = action | TCP_PUSH | TCP_ACK; debug_cond(DEBUG_DEV_PKT, "TCP Hdr:dft (%pI4, %pI4, s=%u, a=%u, A=%x)\n", - &net_server_ip, &net_ip, + &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num, action); }
@@ -308,8 +363,8 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, tcp->ack_edge = tcp_ack_num; /* TCP Header */ b->ip.hdr.tcp_ack = htonl(tcp->ack_edge); - b->ip.hdr.tcp_src = htons(sport); - b->ip.hdr.tcp_dst = htons(dport); + b->ip.hdr.tcp_src = htons(tcp->lport); + b->ip.hdr.tcp_dst = htons(tcp->rport); b->ip.hdr.tcp_seq = htonl(tcp_seq_num);
/* @@ -332,10 +387,10 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int dport, b->ip.hdr.tcp_xsum = 0; b->ip.hdr.tcp_ugr = 0;
- b->ip.hdr.tcp_xsum = tcp_set_pseudo_header(pkt, net_ip, net_server_ip, + b->ip.hdr.tcp_xsum = tcp_set_pseudo_header(pkt, net_ip, tcp->rhost, tcp_len, pkt_len);
- net_set_ip_header((uchar *)&b->ip, net_server_ip, net_ip, + net_set_ip_header((uchar *)&b->ip, tcp->rhost, net_ip, pkt_len, IPPROTO_TCP);
return pkt_hdr_len; @@ -616,19 +671,26 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) u32 tcp_seq_num, tcp_ack_num; int tcp_hdr_len, payload_len; struct tcp_stream *tcp; + struct in_addr src;
/* Verify IP header */ debug_cond(DEBUG_DEV_PKT, "TCP RX in RX Sum (to=%pI4, from=%pI4, len=%d)\n", &b->ip.hdr.ip_src, &b->ip.hdr.ip_dst, pkt_len);
- b->ip.hdr.ip_src = net_server_ip; + /* + * src IP address will be destroyed by TCP checksum verification + * algorithm (see tcp_set_pseudo_header()), so remember it before + * it was garbaged. + */ + src.s_addr = b->ip.hdr.ip_src.s_addr; + b->ip.hdr.ip_dst = net_ip; b->ip.hdr.ip_sum = 0; if (tcp_rx_xsum != compute_ip_checksum(b, IP_HDR_SIZE)) { debug_cond(DEBUG_DEV_PKT, "TCP RX IP xSum Error (%pI4, =%pI4, len=%d)\n", - &net_ip, &net_server_ip, pkt_len); + &net_ip, &src, pkt_len); return; }
@@ -640,11 +702,14 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) pkt_len)) { debug_cond(DEBUG_DEV_PKT, "TCP RX TCP xSum Error (%pI4, %pI4, len=%d)\n", - &net_ip, &net_server_ip, tcp_len); + &net_ip, &src, tcp_len); return; }
- tcp = tcp_stream_get(); + tcp = tcp_stream_get(b->ip.hdr.tcp_flags & TCP_SYN, + src, + ntohs(b->ip.hdr.tcp_src), + ntohs(b->ip.hdr.tcp_dst)); if (!tcp) return;
@@ -676,9 +741,9 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) "TCP Notify (action=%x, Seq=%u,Ack=%u,Pay%d)\n", tcp_action, tcp_seq_num, tcp_ack_num, payload_len);
- (*tcp_packet_handler) ((uchar *)b + pkt_len - payload_len, b->ip.hdr.tcp_dst, - b->ip.hdr.ip_src, b->ip.hdr.tcp_src, tcp_seq_num, - tcp_ack_num, tcp_action, payload_len); + (*tcp_packet_handler) (tcp, (uchar *)b + pkt_len - payload_len, + tcp_seq_num, tcp_ack_num, tcp_action, + payload_len);
} else if (tcp_action != TCP_DATA) { debug_cond(DEBUG_DEV_PKT, @@ -689,9 +754,13 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) * Warning: Incoming Ack & Seq sequence numbers are transposed * here to outgoing Seq & Ack sequence numbers */ - net_send_tcp_packet(0, ntohs(b->ip.hdr.tcp_src), - ntohs(b->ip.hdr.tcp_dst), + net_send_tcp_packet(0, tcp->rhost, tcp->rport, tcp->lport, (tcp_action & (~TCP_PUSH)), tcp_ack_num, tcp->ack_edge); } } + +struct tcp_stream *tcp_stream_connect(struct in_addr rhost, u16 rport) +{ + return tcp_stream_add(rhost, rport, random_port()); +} diff --git a/net/wget.c b/net/wget.c index 0213e3ff51d..a175533ada9 100644 --- a/net/wget.c +++ b/net/wget.c @@ -28,9 +28,8 @@ static const char bootfile3[] = " HTTP/1.0\r\n\r\n"; static const char http_eom[] = "\r\n\r\n"; static const char content_len[] = "Content-Length"; static const char linefeed[] = "\r\n"; -static struct in_addr web_server_ip; -static int our_port; static int wget_timeout_count; +struct tcp_stream *tcp;
struct pkt_qd { uchar *pkt; @@ -110,22 +109,19 @@ static void wget_send_stored(void) 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; - unsigned int server_port; uchar *ptr, *offset;
- server_port = env_get_ulong("httpdstp", 10, SERVER_PORT) & 0xffff; - 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, server_port, our_port, action, + 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, server_port, our_port, action, + net_send_tcp_packet(0, tcp->rhost, tcp->rport, tcp->lport, action, tcp_seq_num, tcp_ack_num);
ptr = net_tx_packet + net_eth_hdr_size() + @@ -149,14 +145,14 @@ static void wget_send_stored(void)
memcpy(offset, &bootfile3, strlen(bootfile3)); offset += strlen(bootfile3); - net_send_tcp_packet((offset - ptr), server_port, our_port, + 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, server_port, our_port, action, + net_send_tcp_packet(0, tcp->rhost, tcp->rport, tcp->lport, action, tcp_seq_num, tcp_ack_num); break; } @@ -348,10 +344,8 @@ static void wget_connected(uchar *pkt, unsigned int tcp_seq_num,
/** * wget_handler() - TCP handler of wget + * @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) @@ -360,13 +354,11 @@ static void wget_connected(uchar *pkt, unsigned int tcp_seq_num, * In the "application push" invocation, the TCP header with all * its information is pointed to by the packet pointer. */ -static void wget_handler(uchar *pkt, u16 dport, - struct in_addr sip, u16 sport, +static void wget_handler(struct tcp_stream *tcp, uchar *pkt, u32 tcp_seq_num, u32 tcp_ack_num, u8 action, unsigned int len) { - struct tcp_stream *tcp = tcp_stream_get(); - enum tcp_state wget_tcp_state = tcp_get_tcp_state(tcp); + enum tcp_state wget_tcp_state = tcp_stream_get_state(tcp);
net_set_timeout_handler(wget_timeout, wget_timeout_handler); packets++; @@ -457,26 +449,13 @@ static void wget_handler(uchar *pkt, u16 dport, } }
-#define RANDOM_PORT_START 1024 -#define RANDOM_PORT_RANGE 0x4000 - -/** - * random_port() - make port a little random (1024-17407) - * - * Return: random port number from 1024 to 17407 - * - * This keeps the math somewhat trivial to compute, and seems to work with - * all supported protocols/clients/servers - */ -static unsigned int random_port(void) -{ - return RANDOM_PORT_START + (get_timer(0) % RANDOM_PORT_RANGE); -} - #define BLOCKSIZE 512
void wget_start(void) { + struct in_addr web_server_ip; + unsigned int server_port; + if (!wget_info) wget_info = &default_wget_info;
@@ -523,8 +502,6 @@ void wget_start(void) wget_timeout_count = 0; current_wget_state = WGET_CLOSED;
- our_port = random_port(); - /* * Zero out server ether to force arp resolution in case * the server ip for the previous u-boot command, for example dns @@ -533,6 +510,13 @@ void wget_start(void)
memset(net_server_ethaddr, 0, 6);
+ server_port = env_get_ulong("httpdstp", 10, SERVER_PORT) & 0xffff; + tcp = tcp_stream_connect(web_server_ip, server_port); + if (!tcp) { + net_set_state(NETLOOP_FAIL); + return; + } + wget_send(TCP_SYN, 0, 0, 0); }

Use the names from RFC 9293
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu Reviewed-by: Simon Glass sjg@chromium.org --- include/net/tcp.h | 8 ++++---- net/tcp.c | 32 ++++++++++++++++---------------- 2 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/include/net/tcp.h b/include/net/tcp.h index f224d0cae2f..0694af9d5b1 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -285,8 +285,8 @@ enum tcp_state { * * @state: TCP connection state * - * @seq_init: Initial receive sequence number - * @ack_edge: Receive next + * @irs: Initial receive sequence number + * @rcv_nxt: Receive next * * @loc_timestamp: Local timestamp * @rmt_timestamp: Remote timestamp @@ -301,8 +301,8 @@ struct tcp_stream { /* TCP connection state */ enum tcp_state state;
- u32 seq_init; - u32 ack_edge; + u32 irs; + u32 rcv_nxt;
/* TCP option timestamp */ u32 loc_timestamp; diff --git a/net/tcp.c b/net/tcp.c index db708f0a2a5..7c713a5ca56 100644 --- a/net/tcp.c +++ b/net/tcp.c @@ -360,9 +360,9 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len, pkt_len = pkt_hdr_len + payload_len; tcp_len = pkt_len - IP_HDR_SIZE;
- tcp->ack_edge = tcp_ack_num; + tcp->rcv_nxt = tcp_ack_num; /* TCP Header */ - b->ip.hdr.tcp_ack = htonl(tcp->ack_edge); + b->ip.hdr.tcp_ack = htonl(tcp->rcv_nxt); b->ip.hdr.tcp_src = htons(tcp->lport); b->ip.hdr.tcp_dst = htons(tcp->rport); b->ip.hdr.tcp_seq = htonl(tcp_seq_num); @@ -396,10 +396,10 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len, return pkt_hdr_len; }
-static void tcp_update_ack_edge(struct tcp_stream *tcp) +static void tcp_update_rcv_nxt(struct tcp_stream *tcp) { - if (tcp_seq_cmp(tcp->ack_edge, tcp->lost.hill[0].l) >= 0) { - tcp->ack_edge = tcp->lost.hill[0].r; + if (tcp_seq_cmp(tcp->rcv_nxt, tcp->lost.hill[0].l) >= 0) { + tcp->rcv_nxt = tcp->lost.hill[0].r;
memmove(&tcp->lost.hill[0], &tcp->lost.hill[1], (TCP_SACK_HILLS - 1) * sizeof(struct sack_edges)); @@ -434,7 +434,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 len) tcp_seq_num = tcp->lost.hill[i].l; } if (tcp_seq_cmp(tcp->lost.hill[i].r, tcp_seq_num + len) >= 0) { - tcp_update_ack_edge(tcp); + tcp_update_rcv_nxt(tcp); return; }
@@ -463,12 +463,12 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 len) } }
- tcp_update_ack_edge(tcp); + tcp_update_rcv_nxt(tcp); return; }
if (i == TCP_SACK_HILLS) { - tcp_update_ack_edge(tcp); + tcp_update_rcv_nxt(tcp); return; }
@@ -489,7 +489,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 len) tcp->lost.hill[i].r = tcp_seq_num + len; tcp->lost.len = TCP_OPT_LEN_2 + cnt * TCP_OPT_LEN_8;
- tcp_update_ack_edge(tcp); + tcp_update_rcv_nxt(tcp); };
/** @@ -566,8 +566,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 tcp_flags, debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags); if (tcp_syn) { action = TCP_SYN | TCP_ACK; - tcp->seq_init = tcp_seq_num; - tcp->ack_edge = tcp_seq_num + 1; + tcp->irs = tcp_seq_num; + tcp->rcv_nxt = tcp_seq_num + 1; tcp->lost.len = TCP_OPT_LEN_2; tcp->state = TCP_SYN_RECEIVED; } else if (tcp_ack || tcp_fin) { @@ -583,8 +583,8 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 tcp_flags, tcp->state = TCP_CLOSE_WAIT; } else if (tcp_ack || (tcp_syn && tcp_ack)) { action |= TCP_ACK; - tcp->seq_init = tcp_seq_num; - tcp->ack_edge = tcp_seq_num + 1; + tcp->irs = tcp_seq_num; + tcp->rcv_nxt = tcp_seq_num + 1; tcp->state = TCP_ESTABLISHED;
if (tcp_syn && tcp_ack) @@ -633,7 +633,7 @@ static u8 tcp_state_machine(struct tcp_stream *tcp, u8 tcp_flags, case TCP_FIN_WAIT_1: debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags); if (tcp_fin) { - tcp->ack_edge++; + tcp->rcv_nxt++; action = TCP_ACK | TCP_FIN; tcp->state = TCP_FIN_WAIT_2; } @@ -748,7 +748,7 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) } else if (tcp_action != TCP_DATA) { debug_cond(DEBUG_DEV_PKT, "TCP Action (action=%x,Seq=%u,Ack=%u,Pay=%d)\n", - tcp_action, tcp_ack_num, tcp->ack_edge, payload_len); + tcp_action, tcp_ack_num, tcp->rcv_nxt, payload_len);
/* * Warning: Incoming Ack & Seq sequence numbers are transposed @@ -756,7 +756,7 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) */ net_send_tcp_packet(0, tcp->rhost, tcp->rport, tcp->lport, (tcp_action & (~TCP_PUSH)), - tcp_ack_num, tcp->ack_edge); + tcp_ack_num, tcp->rcv_nxt); } }

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 --- include/net/tcp.h | 201 ++++++++-- include/net/wget.h | 8 - net/fastboot_tcp.c | 193 +++++----- net/net.c | 4 + net/tcp.c | 891 +++++++++++++++++++++++++++++++++++---------- net/wget.c | 530 +++++++++------------------ 6 files changed, 1128 insertions(+), 699 deletions(-)
diff --git a/include/net/tcp.h b/include/net/tcp.h index 0694af9d5b1..5022fa9dc1b 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -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,27 +472,51 @@ 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);
-int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len, - u8 action, u32 tcp_seq_num, u32 tcp_ack_num); +/* + * 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);
-/** - * 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 +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 */ -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); +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);
void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int len);
diff --git a/include/net/wget.h b/include/net/wget.h index 6714f7ea573..9a423b30414 100644 --- a/include/net/wget.h +++ b/include/net/wget.h @@ -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 diff --git a/net/fastboot_tcp.c b/net/fastboot_tcp.c index 4d34fdc5a45..3ea25c997fc 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 void fastboot_tcp_answer(struct tcp_stream *tcp, u8 action, - unsigned int len) -{ - 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); +static char rxbuf[sizeof(u64) + FASTBOOT_COMMAND_LEN + 1]; +static char txbuf[sizeof(u64) + FASTBOOT_RESPONSE_LEN + 1];
- net_send_tcp_packet(len, tcp->rhost, tcp->rport, tcp->lport, - action, response_seq_num, response_ack_num); -} +static u32 data_read; +static u32 tx_last_offs, tx_last_len;
-static void fastboot_tcp_reset(struct tcp_stream *tcp) +static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes) { - fastboot_tcp_answer(tcp, TCP_RST, 0); - state = FASTBOOT_CLOSED; -} + u64 cmd_size; + __be64 len_be; + char saved; + int fastboot_command_id, len; + + 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_send_packet(struct tcp_stream *tcp, u8 action, - const uchar *data, unsigned int len) -{ - uchar *pkt = net_get_async_tx_pkt_buf(); + 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(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; + + 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 void fastboot_tcp_send_message(struct tcp_stream *tcp, - const char *message, unsigned int len) +static int tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, 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); + memcpy(rxbuf + rx_offs - data_read, buf, len); + + return len; }
-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_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, int maxlen) { - 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; - } - 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; - } + /* by design: tx_offs >= tx_last_offs */ + if (tx_offs >= tx_last_offs + tx_last_len) + return 0;
- 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; + maxlen = tx_last_offs + tx_last_len - tx_offs; + memcpy(buf, txbuf + (tx_offs - tx_last_offs), maxlen); + + 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); } diff --git a/net/net.c b/net/net.c index 62d1c5b1e82..fd634405e8a 100644 --- a/net/net.c +++ b/net/net.c @@ -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: diff --git a/net/tcp.c b/net/tcp.c index 7c713a5ca56..a2941951834 100644 --- a/net/tcp.c +++ b/net/tcp.c @@ -24,9 +24,26 @@ #include <net.h> #include <net/tcp.h>
-static int tcp_activity_count; +/* + * The start sequence number increment for the two sequently created + * connections within the same timer tick. This number must be: + * - prime (to increase the time before the same number will be generated) + * - larger than typical MTU (to avoid similar numbers for two sequently + * created connections) + */ +#define TCP_START_SEQ_INC 2153 /* just large prime number */ + +#define TCP_SEND_RETRY 3 +#define TCP_SEND_TIMEOUT 2000UL +#define TCP_RX_INACTIVE_TIMEOUT 30000UL +#define TCP_RCV_WND_SIZE (PKTBUFSRX * TCP_MSS) + +#define TCP_PACKET_OK 0 +#define TCP_PACKET_DROP 1 + static struct tcp_stream tcp_stream; -static tcp_incoming_filter *incoming_filter; + +static int (*tcp_stream_on_create)(struct tcp_stream *tcp);
/* * TCP lengths are stored as a rounded up number of 32 bit words. @@ -35,12 +52,10 @@ static tcp_incoming_filter *incoming_filter; */ #define LEN_B_TO_DW(x) ((x) >> 2) #define ROUND_TCPHDR_LEN(x) (LEN_B_TO_DW((x) + 3)) +#define ROUND_TCPHDR_BYTES(x) (((x) + 3) & ~3) #define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4) #define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
-/* Current TCP RX packet handler */ -static rxhand_tcp *tcp_packet_handler; - #define RANDOM_PORT_START 1024 #define RANDOM_PORT_RANGE 0x4000
@@ -62,6 +77,22 @@ static inline s32 tcp_seq_cmp(u32 a, u32 b) return (s32)(a - b); }
+static inline u32 tcp_get_start_seq(void) +{ + static u32 tcp_seq_inc; + u32 tcp_seq; + + tcp_seq = (get_timer(0) & 0xffffffff) + tcp_seq_inc; + tcp_seq_inc += TCP_START_SEQ_INC; + + return tcp_seq; +} + +static inline ulong msec_to_ticks(ulong msec) +{ + return msec * CONFIG_SYS_HZ / 1000; +} + /** * tcp_stream_get_state() - get TCP stream state * @tcp: tcp stream @@ -84,31 +115,90 @@ static void tcp_stream_set_state(struct tcp_stream *tcp, tcp->state = new_state; }
-void tcp_init(void) +/** + * tcp_stream_get_status() - get TCP stream status + * @tcp: tcp stream + * + * Return: TCP stream status + */ +enum tcp_status tcp_stream_get_status(struct tcp_stream *tcp) { - incoming_filter = NULL; - tcp_stream.state = TCP_CLOSED; + return tcp->status; }
-void tcp_set_incoming_filter(tcp_incoming_filter *filter) +/** + * tcp_stream_set_status() - set TCP stream state + * @tcp: tcp stream + * @new_satus: new TCP stream status + */ +static void tcp_stream_set_status(struct tcp_stream *tcp, + enum tcp_status new_status) { - incoming_filter = filter; + tcp->status = new_status; }
-static struct tcp_stream *tcp_stream_add(struct in_addr rhost, - u16 rport, u16 lport) +void tcp_stream_restart_rx_timer(struct tcp_stream *tcp) { - struct tcp_stream *tcp = &tcp_stream; - - if (tcp->state != TCP_CLOSED) - return NULL; + tcp->time_last_rx = get_timer(0); +}
+static void tcp_stream_init(struct tcp_stream *tcp, + struct in_addr rhost, u16 rport, u16 lport) +{ memset(tcp, 0, sizeof(struct tcp_stream)); tcp->rhost.s_addr = rhost.s_addr; tcp->rport = rport; tcp->lport = lport; tcp->state = TCP_CLOSED; tcp->lost.len = TCP_OPT_LEN_2; + tcp->rcv_wnd = TCP_RCV_WND_SIZE; + tcp->max_retry_count = TCP_SEND_RETRY; + tcp->initial_timeout = TCP_SEND_TIMEOUT; + tcp->rx_inactiv_timeout = TCP_RX_INACTIVE_TIMEOUT; + tcp_stream_restart_rx_timer(tcp); +} + +static void tcp_stream_destroy(struct tcp_stream *tcp) +{ + if (tcp->on_closed) + tcp->on_closed(tcp); + memset(tcp, 0, sizeof(struct tcp_stream)); +} + +void tcp_init(void) +{ + static int initialized; + struct tcp_stream *tcp = &tcp_stream; + + tcp_stream_on_create = NULL; + if (!initialized) { + initialized = 1; + memset(tcp, 0, sizeof(struct tcp_stream)); + } + + tcp_stream_set_state(tcp, TCP_CLOSED); + tcp_stream_set_status(tcp, TCP_ERR_RST); + tcp_stream_destroy(tcp); +} + +void tcp_stream_set_on_create_handler(int (*on_create)(struct tcp_stream *)) +{ + tcp_stream_on_create = on_create; +} + +static struct tcp_stream *tcp_stream_add(struct in_addr rhost, + u16 rport, u16 lport) +{ + struct tcp_stream *tcp = &tcp_stream; + + if (!tcp_stream_on_create || + tcp->state != TCP_CLOSED) + return NULL; + + tcp_stream_init(tcp, rhost, rport, lport); + if (!tcp_stream_on_create(tcp)) + return NULL; + return tcp; }
@@ -122,30 +212,226 @@ struct tcp_stream *tcp_stream_get(int is_new, struct in_addr rhost, tcp->lport == lport) return tcp;
- if (!is_new || !incoming_filter || - !incoming_filter(rhost, rport, lport)) - return NULL; + return is_new ? tcp_stream_add(rhost, rport, lport) : NULL; +}
- return tcp_stream_add(rhost, rport, lport); +void tcp_stream_put(struct tcp_stream *tcp) +{ + if (tcp->state == TCP_CLOSED) + tcp_stream_destroy(tcp); }
-static void dummy_handler(struct tcp_stream *tcp, uchar *pkt, - u32 tcp_seq_num, u32 tcp_ack_num, - u8 action, unsigned int len) +u32 tcp_stream_rx_offs(struct tcp_stream *tcp) { + u32 ret; + + switch (tcp->state) { + case TCP_CLOSED: + case TCP_SYN_SENT: + case TCP_SYN_RECEIVED: + return 0; + default: + break; + } + + ret = tcp->rcv_nxt - tcp->irs - 1; + if (tcp->fin_rx && tcp->rcv_nxt == tcp->fin_rx_seq) + ret--; + + return ret; }
-/** - * tcp_set_tcp_handler() - set a handler to receive data - * @f: handler - */ -void tcp_set_tcp_handler(rxhand_tcp *f) +u32 tcp_stream_tx_offs(struct tcp_stream *tcp) +{ + u32 ret; + + switch (tcp->state) { + case TCP_CLOSED: + case TCP_SYN_SENT: + case TCP_SYN_RECEIVED: + return 0; + default: + break; + } + + ret = tcp->snd_una - tcp->iss - 1; + if (tcp->fin_tx && tcp->snd_una == tcp->fin_tx_seq + 1) + ret--; + + return ret; +} + +static void tcp_stream_set_time_handler(struct tcp_stream *tcp, ulong msec, + void (*handler)(struct tcp_stream *)) +{ + if (!msec) { + tcp->time_handler = NULL; + return; + } + + tcp->time_handler = handler; + tcp->time_start = get_timer(0); + tcp->time_delta = msec_to_ticks(msec); +} + +static void tcp_send_packet(struct tcp_stream *tcp, u8 action, + u32 tcp_seq_num, u32 tcp_ack_num, u32 tx_len) +{ + tcp->tx_packets++; + net_send_tcp_packet(tx_len, tcp->rhost, tcp->rport, + tcp->lport, action, tcp_seq_num, + tcp_ack_num); +} + +static void tcp_send_repeat(struct tcp_stream *tcp) +{ + uchar *ptr; + u32 tcp_opts_size; + int ret; + + if (!tcp->retry_cnt) { + puts("\nTCP: send retry counter exceeded\n"); + tcp_send_packet(tcp, TCP_RST, tcp->retry_seq_num, + tcp->rcv_nxt, 0); + tcp_stream_set_status(tcp, TCP_ERR_TOUT); + tcp_stream_set_state(tcp, TCP_CLOSED); + tcp_stream_destroy(tcp); + return; + } + tcp->retry_cnt--; + tcp->retry_timeout += tcp->initial_timeout; + + if (tcp->retry_tx_len > 0) { + tcp_opts_size = ROUND_TCPHDR_BYTES(TCP_TSOPT_SIZE + + tcp->lost.len); + ptr = net_tx_packet + net_eth_hdr_size() + + IP_TCP_HDR_SIZE + tcp_opts_size; + + if (tcp->retry_tx_len > TCP_MSS - tcp_opts_size) + tcp->retry_tx_len = TCP_MSS - tcp_opts_size; + + /* refill packet data */ + ret = tcp->tx(tcp, tcp->retry_tx_offs, ptr, tcp->retry_tx_len); + if (ret < 0) { + puts("\nTCP: send failure\n"); + tcp_send_packet(tcp, TCP_RST, tcp->retry_seq_num, + tcp->rcv_nxt, 0); + tcp_stream_set_status(tcp, TCP_ERR_IO); + tcp_stream_set_state(tcp, TCP_CLOSED); + tcp_stream_destroy(tcp); + return; + } + } + tcp_send_packet(tcp, tcp->retry_action, tcp->retry_seq_num, + tcp->rcv_nxt, tcp->retry_tx_len); + + tcp_stream_set_time_handler(tcp, tcp->retry_timeout, tcp_send_repeat); +} + +static void tcp_send_packet_with_retry(struct tcp_stream *tcp, u8 action, + u32 tcp_seq_num, u32 tx_len, u32 tx_offs) +{ + tcp->retry_cnt = tcp->max_retry_count; + tcp->retry_timeout = tcp->initial_timeout; + tcp->retry_action = action; + tcp->retry_seq_num = tcp_seq_num; + tcp->retry_tx_len = tx_len; + tcp->retry_tx_offs = tx_offs; + + tcp_send_packet(tcp, action, tcp_seq_num, tcp->rcv_nxt, tx_len); + tcp_stream_set_time_handler(tcp, tcp->retry_timeout, tcp_send_repeat); +} + +static inline u8 tcp_stream_fin_needed(struct tcp_stream *tcp, u32 tcp_seq_num) +{ + return (tcp->fin_tx && (tcp_seq_num == tcp->fin_tx_seq)) ? TCP_FIN : 0; +} + +static void tcp_steam_tx_try(struct tcp_stream *tcp) { - debug_cond(DEBUG_INT_STATE, "--- net_loop TCP handler set (%p)\n", f); - if (!f) - tcp_packet_handler = dummy_handler; - else - tcp_packet_handler = f; + uchar *ptr; + int tx_len; + u32 tx_offs, tcp_opts_size; + + if (tcp->state != TCP_ESTABLISHED || + tcp->time_handler || + !tcp->tx) + return; + + tcp_opts_size = ROUND_TCPHDR_BYTES(TCP_TSOPT_SIZE + tcp->lost.len); + tx_len = TCP_MSS - tcp_opts_size; + if (tcp->fin_tx) { + /* do not try to send beyonds FIN packet limits */ + if (tcp_seq_cmp(tcp->snd_una, tcp->fin_tx_seq) >= 0) + return; + + tx_len = tcp->fin_tx_seq - tcp->snd_una; + if (tx_len > TCP_MSS - tcp_opts_size) + tx_len = TCP_MSS - tcp_opts_size; + } + + tx_offs = tcp_stream_tx_offs(tcp); + ptr = net_tx_packet + net_eth_hdr_size() + + IP_TCP_HDR_SIZE + tcp_opts_size; + + /* fill packet data and adjust size */ + tx_len = tcp->tx(tcp, tx_offs, ptr, tx_len); + if (tx_len < 0) { + puts("\nTCP: send failure\n"); + tcp_send_packet(tcp, TCP_RST, tcp->retry_seq_num, + tcp->rcv_nxt, 0); + tcp_stream_set_status(tcp, TCP_ERR_IO); + tcp_stream_set_state(tcp, TCP_CLOSED); + tcp_stream_destroy(tcp); + return; + } + if (!tx_len) + return; + + if (tcp_seq_cmp(tcp->snd_una + tx_len, tcp->snd_nxt) > 0) + tcp->snd_nxt = tcp->snd_una + tx_len; + + tcp_send_packet_with_retry(tcp, TCP_ACK | TCP_PUSH, + tcp->snd_una, tx_len, tx_offs); +} + +static void tcp_stream_poll(struct tcp_stream *tcp, ulong time) +{ + ulong delta; + void (*handler)(struct tcp_stream *tcp); + + if (tcp->state == TCP_CLOSED) + return; + + /* handle rx inactivity timeout */ + delta = msec_to_ticks(tcp->rx_inactiv_timeout); + if (time - tcp->time_last_rx >= delta) { + puts("\nTCP: rx inactivity timeout exceeded\n"); + tcp_stream_reset(tcp); + tcp_stream_set_status(tcp, TCP_ERR_TOUT); + tcp_stream_destroy(tcp); + return; + } + + /* handle retransmit timeout */ + if (tcp->time_handler && + time - tcp->time_start >= tcp->time_delta) { + handler = tcp->time_handler; + tcp->time_handler = NULL; + handler(tcp); + } + + tcp_steam_tx_try(tcp); +} + +void tcp_streams_poll(void) +{ + ulong time; + struct tcp_stream *tcp; + + time = get_timer(0); + tcp = &tcp_stream; + tcp_stream_poll(tcp, time); }
/** @@ -240,7 +526,6 @@ int net_set_ack_options(struct tcp_stream *tcp, union tcp_build_pkt *b) * This returns the actual rounded up length of the * TCP header to add to the total packet length */ - return GET_TCP_HDR_LEN_IN_BYTES(b->sack.hdr.tcp_hlen); }
@@ -300,18 +585,8 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len, "TCP Hdr:SYN (%pI4, %pI4, sq=%u, ak=%u)\n", &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num); - tcp_activity_count = 0; net_set_syn_options(tcp, b); - tcp_seq_num = 0; - tcp_ack_num = 0; pkt_hdr_len = IP_TCP_O_SIZE; - if (tcp->state == TCP_SYN_SENT) { /* Too many SYNs */ - action = TCP_FIN; - tcp->state = TCP_FIN_WAIT_1; - } else { - tcp->lost.len = TCP_OPT_LEN_2; - tcp->state = TCP_SYN_SENT; - } break; case TCP_SYN | TCP_ACK: case TCP_ACK: @@ -328,21 +603,16 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len, &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num); payload_len = 0; pkt_hdr_len = IP_TCP_HDR_SIZE; - tcp->state = TCP_FIN_WAIT_1; break; case TCP_RST | TCP_ACK: case TCP_RST: debug_cond(DEBUG_DEV_PKT, "TCP Hdr:RST (%pI4, %pI4, s=%u, a=%u)\n", &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num); - tcp->state = TCP_CLOSED; break; /* Notify connection closing */ case (TCP_FIN | TCP_ACK): case (TCP_FIN | TCP_ACK | TCP_PUSH): - if (tcp->state == TCP_CLOSE_WAIT) - tcp->state = TCP_CLOSING; - debug_cond(DEBUG_DEV_PKT, "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%u, a=%u, A=%x)\n", &tcp->rhost, &net_ip, @@ -382,7 +652,7 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len, * it is, then the u-boot tftp or nfs kernel netboot should be * considered. */ - b->ip.hdr.tcp_win = htons(PKTBUFSRX * TCP_MSS >> TCP_SCALE); + b->ip.hdr.tcp_win = htons(tcp->rcv_wnd >> TCP_SCALE);
b->ip.hdr.tcp_xsum = 0; b->ip.hdr.tcp_ugr = 0; @@ -501,6 +771,7 @@ void tcp_hole(struct tcp_stream *tcp, u32 tcp_seq_num, u32 len) void tcp_parse_options(struct tcp_stream *tcp, uchar *o, int o_len) { struct tcp_t_opt *tsopt; + struct tcp_scale *wsopt; uchar *p = o;
/* @@ -515,10 +786,13 @@ void tcp_parse_options(struct tcp_stream *tcp, uchar *o, int o_len) case TCP_O_END: return; case TCP_O_MSS: - case TCP_O_SCL: case TCP_P_SACK: case TCP_V_SACK: break; + case TCP_O_SCL: + wsopt = (struct tcp_scale *)p; + tcp->rmt_win_scale = wsopt->scale; + break; case TCP_O_TS: tsopt = (struct tcp_t_opt *)p; tcp->rmt_timestamp = tsopt->t_snd; @@ -533,129 +807,344 @@ void tcp_parse_options(struct tcp_stream *tcp, uchar *o, int o_len) } }
-static u8 tcp_state_machine(struct tcp_stream *tcp, u8 tcp_flags, - u32 tcp_seq_num, int payload_len) +static int tcp_seg_in_wnd(struct tcp_stream *tcp, + u32 tcp_seq_num, int payload_len) { - u8 tcp_fin = tcp_flags & TCP_FIN; - u8 tcp_syn = tcp_flags & TCP_SYN; - u8 tcp_rst = tcp_flags & TCP_RST; - u8 tcp_push = tcp_flags & TCP_PUSH; - u8 tcp_ack = tcp_flags & TCP_ACK; - u8 action = TCP_DATA; + if (!payload_len && !tcp->rcv_wnd) { + if (tcp_seq_num == tcp->rcv_nxt) + return 1; + } + if (!payload_len && tcp->rcv_wnd > 0) { + if (tcp_seq_cmp(tcp->rcv_nxt, tcp_seq_num) <= 0 && + tcp_seq_cmp(tcp_seq_num, tcp->rcv_nxt + tcp->rcv_wnd) < 0) + return 1; + } + if (payload_len > 0 && tcp->rcv_wnd > 0) { + if (tcp_seq_cmp(tcp->rcv_nxt, tcp_seq_num) <= 0 && + tcp_seq_cmp(tcp_seq_num, tcp->rcv_nxt + tcp->rcv_wnd) < 0) + return 1; + tcp_seq_num += payload_len - 1; + if (tcp_seq_cmp(tcp->rcv_nxt, tcp_seq_num) <= 0 && + tcp_seq_cmp(tcp_seq_num, tcp->rcv_nxt + tcp->rcv_wnd) < 0) + return 1; + } + + return 0; +} + +static int tcp_rx_check_ack_num(struct tcp_stream *tcp, u32 tcp_seq_num, + u32 tcp_ack_num, u32 tcp_win_size) +{ + u32 old_offs, new_offs; + u8 action; + + switch (tcp->state) { + case TCP_SYN_RECEIVED: + if (tcp_seq_cmp(tcp->snd_una, tcp_ack_num) >= 0 || + tcp_seq_cmp(tcp_ack_num, tcp->snd_nxt) > 0) { + // segment acknowledgment is not acceptable + tcp_send_packet(tcp, TCP_RST, tcp_ack_num, 0, 0); + return TCP_PACKET_DROP; + }
+ tcp_stream_set_state(tcp, TCP_ESTABLISHED); + tcp->snd_wnd = tcp_win_size; + tcp->snd_wl1 = tcp_seq_num; + tcp->snd_wl2 = tcp_ack_num; + + if (tcp->on_established) + tcp->on_established(tcp); + + fallthrough; + + case TCP_ESTABLISHED: + case TCP_FIN_WAIT_1: + case TCP_FIN_WAIT_2: + case TCP_CLOSE_WAIT: + case TCP_CLOSING: + if (tcp_seq_cmp(tcp_ack_num, tcp->snd_nxt) > 0) { + // ACK acks something not yet sent + action = tcp_stream_fin_needed(tcp, tcp->snd_una) | TCP_ACK; + tcp_send_packet(tcp, action, tcp->snd_una, tcp->rcv_nxt, 0); + return TCP_PACKET_DROP; + } + + if (tcp_seq_cmp(tcp->snd_una, tcp_ack_num) < 0) { + old_offs = tcp_stream_tx_offs(tcp); + tcp->snd_una = tcp_ack_num; + new_offs = tcp_stream_tx_offs(tcp); + if (tcp->time_handler && + tcp_seq_cmp(tcp->snd_una, tcp->retry_seq_num) > 0) { + tcp_stream_set_time_handler(tcp, 0, NULL); + } + if (tcp->on_snd_una_update && + old_offs != new_offs) + tcp->on_snd_una_update(tcp, new_offs); + } + + if (tcp_seq_cmp(tcp->snd_una, tcp_ack_num) <= 0) { + if (tcp_seq_cmp(tcp->snd_wl1, tcp_seq_num) < 0 || + (tcp->snd_wl1 == tcp_seq_num && + tcp_seq_cmp(tcp->snd_wl2, tcp_seq_num) <= 0)) { + tcp->snd_wnd = tcp_win_size; + tcp->snd_wl1 = tcp_seq_num; + tcp->snd_wl2 = tcp_ack_num; + } + } + + if (tcp->state == TCP_FIN_WAIT_1) { + if (tcp->snd_una == tcp->snd_nxt) + tcp_stream_set_state(tcp, TCP_FIN_WAIT_2); + } + + if (tcp->state == TCP_CLOSING) { + if (tcp->snd_una == tcp->snd_nxt) + tcp_stream_set_state(tcp, TCP_CLOSED); + } + return TCP_PACKET_OK; + + case TCP_LAST_ACK: + if (tcp_ack_num == tcp->snd_nxt) + tcp_stream_set_state(tcp, TCP_CLOSED); + return TCP_PACKET_OK; + + default: + return TCP_PACKET_DROP; + } +} + +static int tcp_rx_user_data(struct tcp_stream *tcp, u32 tcp_seq_num, + char *buf, int len) +{ + int tmp_len; + u32 buf_offs, old_offs, new_offs; + u8 action; + + if (!len) + return TCP_PACKET_OK; + + switch (tcp->state) { + case TCP_ESTABLISHED: + case TCP_FIN_WAIT_1: + case TCP_FIN_WAIT_2: + break; + default: + return TCP_PACKET_DROP; + } + + tmp_len = len; + old_offs = tcp_stream_rx_offs(tcp); + buf_offs = tcp_seq_num - tcp->irs - 1; + if (tcp->rx) { + tmp_len = tcp->rx(tcp, buf_offs, buf, len); + if (tmp_len < 0) { + puts("\nTCP: receive failure\n"); + tcp_send_packet(tcp, TCP_RST, tcp->snd_una, + tcp->rcv_nxt, 0); + tcp_stream_set_status(tcp, TCP_ERR_IO); + tcp_stream_set_state(tcp, TCP_CLOSED); + tcp_stream_destroy(tcp); + return TCP_PACKET_DROP; + } + } + if (tmp_len) + tcp_hole(tcp, tcp_seq_num, tmp_len); + + new_offs = tcp_stream_rx_offs(tcp); + if (tcp->on_rcv_nxt_update && old_offs != new_offs) + tcp->on_rcv_nxt_update(tcp, new_offs); + + action = tcp_stream_fin_needed(tcp, tcp->snd_una) | TCP_ACK; + tcp_send_packet(tcp, action, tcp->snd_una, tcp->rcv_nxt, 0); + + return TCP_PACKET_OK; +} + +void tcp_rx_state_machine(struct tcp_stream *tcp, + union tcp_build_pkt *b, unsigned int pkt_len) +{ + int tcp_len = pkt_len - IP_HDR_SIZE; + u32 tcp_seq_num, tcp_ack_num, tcp_win_size; + int tcp_hdr_len, payload_len; + u8 tcp_flags, action; + + tcp_hdr_len = GET_TCP_HDR_LEN_IN_BYTES(b->ip.hdr.tcp_hlen); + payload_len = tcp_len - tcp_hdr_len; + + if (tcp_hdr_len > TCP_HDR_SIZE) + tcp_parse_options(tcp, (uchar *)b + IP_TCP_HDR_SIZE, + tcp_hdr_len - TCP_HDR_SIZE); /* - * tcp_flags are examined to determine TX action in a given state - * tcp_push is interpreted to mean "inform the app" - * urg, ece, cer and nonce flags are not supported. - * - * exe and crw are use to signal and confirm knowledge of congestion. - * This TCP only sends a file request and acks. If it generates - * congestion, the network is broken. + * Incoming sequence and ack numbers are server's view of the numbers. + * The app must swap the numbers when responding. */ - debug_cond(DEBUG_INT_STATE, "TCP STATE ENTRY %x\n", action); - if (tcp_rst) { - action = TCP_DATA; - tcp->state = TCP_CLOSED; - net_set_state(NETLOOP_FAIL); - debug_cond(DEBUG_INT_STATE, "TCP Reset %x\n", tcp_flags); - return TCP_RST; - } + tcp_seq_num = ntohl(b->ip.hdr.tcp_seq); + tcp_ack_num = ntohl(b->ip.hdr.tcp_ack); + tcp_win_size = ntohs(b->ip.hdr.tcp_win) << tcp->rmt_win_scale;
- switch (tcp->state) { + tcp_flags = b->ip.hdr.tcp_flags; + +// printf("pkt: seq=%d, ack=%d, flags=%x, len=%d\n", +// tcp_seq_num - tcp->irs, tcp_ack_num - tcp->iss, tcp_flags, pkt_len); +// printf("tcp: rcv_nxt=%d, snd_una=%d, snd_nxt=%d\n\n", +// tcp->rcv_nxt - tcp->irs, tcp->snd_una - tcp->iss, tcp->snd_nxt - tcp->iss); + + switch (tcp->state) { case TCP_CLOSED: - debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags); - if (tcp_syn) { - action = TCP_SYN | TCP_ACK; - tcp->irs = tcp_seq_num; - tcp->rcv_nxt = tcp_seq_num + 1; - tcp->lost.len = TCP_OPT_LEN_2; - tcp->state = TCP_SYN_RECEIVED; - } else if (tcp_ack || tcp_fin) { - action = TCP_DATA; + if (tcp_flags & TCP_RST) + return; + + if (tcp_flags & TCP_ACK) { + tcp_send_packet(tcp, TCP_RST, tcp_ack_num, 0, 0); + return; } - break; - case TCP_SYN_RECEIVED: + + if (!(tcp_flags & TCP_SYN)) + return; + + tcp->irs = tcp_seq_num; + tcp->rcv_nxt = tcp->irs + 1; + + tcp->iss = tcp_get_start_seq(); + tcp->snd_una = tcp->iss; + tcp->snd_nxt = tcp->iss + 1; + tcp->snd_wnd = tcp_win_size; + + tcp_stream_restart_rx_timer(tcp); + + tcp_stream_set_state(tcp, TCP_SYN_RECEIVED); + tcp_send_packet_with_retry(tcp, TCP_SYN | TCP_ACK, + tcp->iss, 0, 0); + return; + case TCP_SYN_SENT: - debug_cond(DEBUG_INT_STATE, "TCP_SYN_SENT | TCP_SYN_RECEIVED %x, %u\n", - tcp_flags, tcp_seq_num); - if (tcp_fin) { - action = action | TCP_PUSH; - tcp->state = TCP_CLOSE_WAIT; - } else if (tcp_ack || (tcp_syn && tcp_ack)) { - action |= TCP_ACK; - tcp->irs = tcp_seq_num; - tcp->rcv_nxt = tcp_seq_num + 1; - tcp->state = TCP_ESTABLISHED; - - if (tcp_syn && tcp_ack) - action |= TCP_PUSH; - } else { - action = TCP_DATA; + if (!(tcp_flags & TCP_ACK)) + return; + + if (tcp_seq_cmp(tcp_ack_num, tcp->iss) <= 0 || + tcp_seq_cmp(tcp_ack_num, tcp->snd_nxt) > 0) { + if (!(tcp_flags & TCP_RST)) + tcp_send_packet(tcp, TCP_RST, tcp_ack_num, 0, 0); + return; } - break; + + if (tcp_flags & TCP_RST) { + tcp_stream_set_status(tcp, TCP_ERR_RST); + tcp_stream_set_state(tcp, TCP_CLOSED); + return; + } + + if (!(tcp_flags & TCP_SYN)) + return; + + /* stop retransmit of SYN */ + tcp_stream_set_time_handler(tcp, 0, NULL); + + tcp->irs = tcp_seq_num; + tcp->rcv_nxt = tcp->irs + 1; + tcp->snd_una = tcp_ack_num; + + tcp_stream_restart_rx_timer(tcp); + + /* our SYN has been ACKed */ + tcp_stream_set_state(tcp, TCP_ESTABLISHED); + + if (tcp->on_established) + tcp->on_established(tcp); + + action = tcp_stream_fin_needed(tcp, tcp->snd_una) | TCP_ACK; + tcp_send_packet(tcp, action, tcp->snd_una, tcp->rcv_nxt, 0); + tcp_rx_user_data(tcp, tcp_seq_num, + ((char *)b) + pkt_len - payload_len, + payload_len); + return; + + case TCP_SYN_RECEIVED: case TCP_ESTABLISHED: - debug_cond(DEBUG_INT_STATE, "TCP_ESTABLISHED %x\n", tcp_flags); - if (payload_len > 0) { - tcp_hole(tcp, tcp_seq_num, payload_len); - tcp_fin = TCP_DATA; /* cause standalone FIN */ + case TCP_FIN_WAIT_1: + case TCP_FIN_WAIT_2: + case TCP_CLOSE_WAIT: + case TCP_CLOSING: + case TCP_LAST_ACK: + if (!tcp_seg_in_wnd(tcp, tcp_seq_num, payload_len)) { + if (tcp_flags & TCP_RST) + return; + action = tcp_stream_fin_needed(tcp, tcp->snd_una) | TCP_ACK; + tcp_send_packet(tcp, action, tcp->snd_una, tcp->rcv_nxt, 0); + return; }
- if ((tcp_fin) && - (!IS_ENABLED(CONFIG_PROT_TCP_SACK) || - tcp->lost.len <= TCP_OPT_LEN_2)) { - action = action | TCP_FIN | TCP_PUSH | TCP_ACK; - tcp->state = TCP_CLOSE_WAIT; - } else if (tcp_ack) { - action = TCP_DATA; + tcp_stream_restart_rx_timer(tcp); + + if (tcp_flags & TCP_RST) { + tcp_stream_set_status(tcp, TCP_ERR_RST); + tcp_stream_set_state(tcp, TCP_CLOSED); + return; }
- if (tcp_syn) - action = TCP_ACK + TCP_RST; - else if (tcp_push) - action = action | TCP_PUSH; - break; - case TCP_CLOSE_WAIT: - debug_cond(DEBUG_INT_STATE, "TCP_CLOSE_WAIT (%x)\n", tcp_flags); - action = TCP_DATA; - break; - case TCP_FIN_WAIT_2: - debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_2 (%x)\n", tcp_flags); - if (tcp_ack) { - action = TCP_PUSH | TCP_ACK; - tcp->state = TCP_CLOSED; - puts("\n"); - } else if (tcp_syn) { - action = TCP_DATA; - } else if (tcp_fin) { - action = TCP_DATA; + if (tcp_flags & TCP_SYN) { + tcp_send_packet(tcp, TCP_RST, tcp_ack_num, 0, 0); + tcp_stream_set_status(tcp, TCP_ERR_RST); + tcp_stream_set_state(tcp, TCP_CLOSED); + return; } - break; - case TCP_FIN_WAIT_1: - debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags); - if (tcp_fin) { - tcp->rcv_nxt++; - action = TCP_ACK | TCP_FIN; - tcp->state = TCP_FIN_WAIT_2; + + if (!(tcp_flags & TCP_ACK)) + return; + + if (tcp_rx_check_ack_num(tcp, tcp_seq_num, tcp_ack_num, + tcp_win_size) == TCP_PACKET_DROP) { + return; } - if (tcp_syn) - action = TCP_RST; - if (tcp_ack) - tcp->state = TCP_CLOSED; - break; - case TCP_CLOSING: - debug_cond(DEBUG_INT_STATE, "TCP_CLOSING (%x)\n", tcp_flags); - if (tcp_ack) { - action = TCP_PUSH; - tcp->state = TCP_CLOSED; - puts("\n"); - } else if (tcp_syn) { - action = TCP_RST; - } else if (tcp_fin) { - action = TCP_DATA; + + if (tcp_rx_user_data(tcp, tcp_seq_num, + ((char *)b) + pkt_len - payload_len, + payload_len) == TCP_PACKET_DROP) { + return; + } + + if (tcp_flags & TCP_FIN) { + tcp->fin_rx = 1; + tcp->fin_rx_seq = tcp_seq_num + payload_len + 1; + tcp_hole(tcp, tcp_seq_num + payload_len, 1); + action = tcp_stream_fin_needed(tcp, tcp->snd_una) | TCP_ACK; + tcp_send_packet(tcp, action, tcp->snd_una, tcp->rcv_nxt, 0); + } + + if (tcp->fin_rx && + tcp->fin_rx_seq == tcp->rcv_nxt) { + /* all rx data were processed */ + switch (tcp->state) { + case TCP_ESTABLISHED: + tcp_stream_set_state(tcp, TCP_LAST_ACK); + tcp_send_packet_with_retry(tcp, TCP_ACK | TCP_FIN, + tcp->snd_nxt, 0, 0); + tcp->snd_nxt++; + break; + + case TCP_FIN_WAIT_1: + if (tcp_ack_num == tcp->snd_nxt) + tcp_stream_set_state(tcp, TCP_CLOSED); + else + tcp_stream_set_state(tcp, TCP_CLOSING); + break; + + case TCP_FIN_WAIT_2: + tcp_stream_set_state(tcp, TCP_CLOSED); + break; + + default: + break; + } + } + + if (tcp->state == TCP_FIN_WAIT_1 && + tcp_stream_fin_needed(tcp, tcp->snd_una)) { + /* all tx data were acknowledged */ + tcp_send_packet_with_retry(tcp, TCP_ACK | TCP_FIN, + tcp->snd_una, 0, 0); } - break; } - return action; }
/** @@ -667,9 +1156,6 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) { int tcp_len = pkt_len - IP_HDR_SIZE; u16 tcp_rx_xsum = b->ip.hdr.ip_sum; - u8 tcp_action = TCP_DATA; - u32 tcp_seq_num, tcp_ack_num; - int tcp_hdr_len, payload_len; struct tcp_stream *tcp; struct in_addr src;
@@ -713,54 +1199,59 @@ void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len) if (!tcp) return;
- tcp_hdr_len = GET_TCP_HDR_LEN_IN_BYTES(b->ip.hdr.tcp_hlen); - payload_len = tcp_len - tcp_hdr_len; + tcp->rx_packets++; + tcp_rx_state_machine(tcp, b, pkt_len); + tcp_stream_put(tcp); +}
- if (tcp_hdr_len > TCP_HDR_SIZE) - tcp_parse_options(tcp, (uchar *)b + IP_TCP_HDR_SIZE, - tcp_hdr_len - TCP_HDR_SIZE); - /* - * Incoming sequence and ack numbers are server's view of the numbers. - * The app must swap the numbers when responding. - */ - tcp_seq_num = ntohl(b->ip.hdr.tcp_seq); - tcp_ack_num = ntohl(b->ip.hdr.tcp_ack); +struct tcp_stream *tcp_stream_connect(struct in_addr rhost, u16 rport) +{ + struct tcp_stream *tcp;
- /* Packets are not ordered. Send to app as received. */ - tcp_action = tcp_state_machine(tcp, b->ip.hdr.tcp_flags, - tcp_seq_num, payload_len); + tcp = tcp_stream_add(rhost, rport, random_port()); + if (!tcp) + return NULL;
- tcp_activity_count++; - if (tcp_activity_count > TCP_ACTIVITY) { - puts("| "); - tcp_activity_count = 0; - } + tcp->iss = tcp_get_start_seq(); + tcp->snd_una = tcp->iss; + tcp->snd_nxt = tcp->iss + 1;
- if ((tcp_action & TCP_PUSH) || payload_len > 0) { - debug_cond(DEBUG_DEV_PKT, - "TCP Notify (action=%x, Seq=%u,Ack=%u,Pay%d)\n", - tcp_action, tcp_seq_num, tcp_ack_num, payload_len); + tcp_stream_set_state(tcp, TCP_SYN_SENT); + tcp_send_packet_with_retry(tcp, TCP_SYN, tcp->snd_una, 0, 0);
- (*tcp_packet_handler) (tcp, (uchar *)b + pkt_len - payload_len, - tcp_seq_num, tcp_ack_num, tcp_action, - payload_len); + return tcp; +}
- } else if (tcp_action != TCP_DATA) { - debug_cond(DEBUG_DEV_PKT, - "TCP Action (action=%x,Seq=%u,Ack=%u,Pay=%d)\n", - tcp_action, tcp_ack_num, tcp->rcv_nxt, payload_len); - - /* - * Warning: Incoming Ack & Seq sequence numbers are transposed - * here to outgoing Seq & Ack sequence numbers - */ - net_send_tcp_packet(0, tcp->rhost, tcp->rport, tcp->lport, - (tcp_action & (~TCP_PUSH)), - tcp_ack_num, tcp->rcv_nxt); - } +void tcp_stream_reset(struct tcp_stream *tcp) +{ + if (tcp->state == TCP_CLOSED) + return; + + tcp_stream_set_time_handler(tcp, 0, NULL); + tcp_send_packet(tcp, TCP_RST, tcp->snd_una, 0, 0); + tcp_stream_set_status(tcp, TCP_ERR_RST); + tcp_stream_set_state(tcp, TCP_CLOSED); }
-struct tcp_stream *tcp_stream_connect(struct in_addr rhost, u16 rport) +void tcp_stream_close(struct tcp_stream *tcp) { - return tcp_stream_add(rhost, rport, random_port()); + switch (tcp->state) { + case TCP_SYN_SENT: + tcp_stream_reset(tcp); + break; + case TCP_SYN_RECEIVED: + case TCP_ESTABLISHED: + tcp->fin_tx = 1; + tcp->fin_tx_seq = tcp->snd_nxt; + if (tcp_stream_fin_needed(tcp, tcp->snd_una)) { + /* all tx data were acknowledged */ + tcp_send_packet_with_retry(tcp, TCP_ACK | TCP_FIN, + tcp->snd_una, 0, 0); + } + tcp_stream_set_state(tcp, TCP_FIN_WAIT_1); + tcp->snd_nxt++; + break; + default: + break; + } } diff --git a/net/wget.c b/net/wget.c index a175533ada9..088796172e7 100644 --- a/net/wget.c +++ b/net/wget.c @@ -22,48 +22,23 @@ 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"; -static const char http_eom[] = "\r\n\r\n"; -static const char content_len[] = "Content-Length"; -static const char linefeed[] = "\r\n"; -static int wget_timeout_count; -struct tcp_stream *tcp; +#define HASHES_PER_LINE 65
-struct pkt_qd { - uchar *pkt; - unsigned int tcp_seq_num; - unsigned int len; -}; +#define HTTP_MAX_HDR_LEN 2048
-/* - * 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 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 linefeed[] = "\r\n"; +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 +48,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 +63,221 @@ 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; - - 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); - - 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; - } + int cnt;
- memcpy(offset, image_url, strlen(image_url)); - offset += strlen(image_url); + if (content_length != -1) { + if (net_boot_file_size > content_length) + content_length = net_boot_file_size;
- 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; + cnt = net_boot_file_size * 50 / content_length; + while (wget_tsize_num_hash < cnt) { + putc('#'); + wget_tsize_num_hash++; + } + } else { + if ((packets % 10) == 0) + putc('#'); + else if (((packets + 1) % (10 * HASHES_PER_LINE)) == 0) + puts("\n"); } }
-static void wget_send(u8 action, unsigned int tcp_seq_num, - unsigned int tcp_ack_num, int len) +static void tcp_stream_on_closed(struct tcp_stream *tcp) { - retry_action = action; - retry_tcp_ack_num = tcp_ack_num; - retry_tcp_seq_num = tcp_seq_num; - retry_len = len; + if (tcp->status != TCP_ERR_OK) + wget_loop_state = NETLOOP_FAIL;
- wget_send_stored(); -} + net_set_state(wget_loop_state); + if (wget_loop_state != NETLOOP_SUCCESS) { + net_boot_file_size = 0; + printf("\nwget: Transfer Fail, TCP status - %d\n", tcp->status); + return; + }
-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); + 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); + } }
-/* - * Interfaces of U-BOOT - */ -static void wget_timeout_handler(void) +static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes) { - 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(); + 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; } -}
-#define PKT_QUEUE_OFFSET 0x20000 -#define PKT_QUEUE_PACKET_SIZE 0x800 + ptr = map_sysmem(image_load_addr, rx_bytes + 1);
-static void wget_fill_info(const uchar *pkt, int hlen) -{ - const char *first_space; - const char *second_space; - char *pos, *end; - - if (wget_info->headers) { - if (hlen < MAX_HTTP_HEADERS_SIZE) - strncpy(wget_info->headers, pkt, hlen); - else - hlen = 0; - wget_info->headers[hlen] = 0; - } + saved = ptr[rx_bytes]; + ptr[rx_bytes] = '\0'; + pos = strstr((char *)ptr, http_eom); + ptr[rx_bytes] = saved; + + if (!pos) { + if (rx_bytes < HTTP_MAX_HDR_LEN && + tcp->state == TCP_ESTABLISHED) + goto end;
- //Get status code - first_space = strchr(pkt, ' '); - if (!first_space) { + printf("ERROR: misssed HTTP header\n"); + tcp_stream_close(tcp); + wget_loop_state = NETLOOP_FAIL; wget_info->status_code = -1; - return; + goto end; }
- second_space = strchr(first_space + 1, ' '); - if (!second_space) { + 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); + wget_loop_state = NETLOOP_FAIL; wget_info->status_code = -1; - return; + goto end; }
- wget_info->status_code = (u32)simple_strtoul(first_space + 1, &end, 10); + /* 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); + wget_loop_state = NETLOOP_FAIL; + wget_info->status_code = -1; + goto end; + }
- if (second_space != 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); + wget_loop_state = NETLOOP_FAIL; wget_info->status_code = -1; + goto end; + }
- pos = strstr((char *)pkt, content_len); + 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"); + tcp_stream_close(tcp); + wget_loop_state = NETLOOP_FAIL; + 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; } -} - -static void wget_connected(uchar *pkt, unsigned int tcp_seq_num, - u8 action, unsigned int tcp_ack_num, unsigned int len) -{ - uchar *pkt_in_q; - char *pos; - int hlen, i; - uchar *ptr1;
- pkt[len] = '\0'; - pos = strstr((char *)pkt, http_eom); + net_boot_file_size = rx_bytes - http_hdr_size; + memmove(ptr, ptr + http_hdr_size, max_rx_pos + 1 - http_hdr_size);
- 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); +end: + unmap_sysmem(ptr);
- 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++; + wget_loop_state = NETLOOP_SUCCESS; +}
- if (pkt_q_idx >= PKTQ_SZ) { - printf("wget: Fatal error, queue overrun!\n"); - net_set_state(NETLOOP_FAIL); +static int tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, int len) +{ + if ((max_rx_pos == (u32)(-1)) || (max_rx_pos < rx_offs + len - 1)) + max_rx_pos = rx_offs + len - 1;
- 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); + store_block(buf, rx_offs - http_hdr_size, len);
- 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"); - 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); - } - } + switch (wget_info->method) { + case WGET_HTTP_METHOD_HEAD: + method = "HEAD"; 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); - } + case WGET_HTTP_METHOD_GET: + default: + method = "GET"; 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; + ret = snprintf(buf, maxlen, "%s %s %s\r\n\r\n", + method, image_url, http_proto);
- 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; - } + return ret; +}
- 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); - } - break; - } +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 +319,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 +327,25 @@ 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 = -1; + 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)

Changes: * update to new tcp stack * fix zero values for ISS and IRS issue (see RFC 9293)
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu Reviewed-by: Simon Glass sjg@chromium.org --- arch/sandbox/include/asm/eth.h | 4 +++ test/cmd/wget.c | 58 ++++++++++++++++------------------ 2 files changed, 31 insertions(+), 31 deletions(-)
diff --git a/arch/sandbox/include/asm/eth.h b/arch/sandbox/include/asm/eth.h index f042a5f3b92..083a7371a3f 100644 --- a/arch/sandbox/include/asm/eth.h +++ b/arch/sandbox/include/asm/eth.h @@ -77,6 +77,8 @@ typedef int sandbox_eth_tx_hand_f(struct udevice *dev, void *pkt, * fake_host_hwaddr - MAC address of mocked machine * fake_host_ipaddr - IP address of mocked machine * disabled - Will not respond + * irs - tcp initial receive sequence + * iss - tcp initial send sequence * recv_packet_buffer - buffers of the packet returned as received * recv_packet_length - lengths of the packet returned as received * recv_packets - number of packets returned @@ -87,6 +89,8 @@ struct eth_sandbox_priv { uchar fake_host_hwaddr[ARP_HLEN]; struct in_addr fake_host_ipaddr; bool disabled; + u32 irs; + u32 iss; uchar * recv_packet_buffer[PKTBUFSRX]; int recv_packet_length[PKTBUFSRX]; int recv_packets; diff --git a/test/cmd/wget.c b/test/cmd/wget.c index 7570c065a10..d51650d5854 100644 --- a/test/cmd/wget.c +++ b/test/cmd/wget.c @@ -25,8 +25,7 @@
#define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4) #define LEN_B_TO_DW(x) ((x) >> 2) - -int net_set_ack_options(union tcp_build_pkt *b); +#define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
static int sb_arp_handler(struct udevice *dev, void *packet, unsigned int len) @@ -64,12 +63,14 @@ static int sb_syn_handler(struct udevice *dev, void *packet, eth_send = (void *)priv->recv_packet_buffer[priv->recv_packets]; memcpy(eth_send->et_dest, eth->et_src, ARP_HLEN); memcpy(eth_send->et_src, priv->fake_host_hwaddr, ARP_HLEN); + priv->irs = ntohl(tcp->tcp_seq); + priv->iss = ~priv->irs; /* just to differ from irs */ eth_send->et_protlen = htons(PROT_IP); tcp_send = (void *)eth_send + ETHER_HDR_SIZE; tcp_send->tcp_src = tcp->tcp_dst; tcp_send->tcp_dst = tcp->tcp_src; - tcp_send->tcp_seq = htonl(0); - tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1); + tcp_send->tcp_seq = htonl(priv->iss); + tcp_send->tcp_ack = htonl(priv->irs + 1); tcp_send->tcp_hlen = SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE)); tcp_send->tcp_flags = TCP_SYN | TCP_ACK; tcp_send->tcp_win = htons(PKTBUFSRX * TCP_MSS >> TCP_SCALE); @@ -104,13 +105,11 @@ static int sb_ack_handler(struct udevice *dev, void *packet, void *data; int pkt_len; int payload_len = 0; + u32 tcp_seq, tcp_ack; + int tcp_data_len; const char *payload1 = "HTTP/1.1 200 OK\r\n" "Content-Length: 30\r\n\r\n\r\n" "<html><body>Hi</body></html>\r\n"; - union tcp_build_pkt *b = (union tcp_build_pkt *)tcp; - const int recv_payload_len = len - net_set_ack_options(b) - IP_HDR_SIZE - ETHER_HDR_SIZE; - static int next_seq; - const int bottom_payload_len = 10;
/* Don't allow the buffer to overrun */ if (priv->recv_packets >= PKTBUFSRX) @@ -125,35 +124,32 @@ static int sb_ack_handler(struct udevice *dev, void *packet, tcp_send->tcp_dst = tcp->tcp_src; data = (void *)tcp_send + IP_TCP_HDR_SIZE;
- if (ntohl(tcp->tcp_seq) == 1 && ntohl(tcp->tcp_ack) == 1 && recv_payload_len == 0) { - // ignore ACK for three-way handshaking - return 0; - } else if (ntohl(tcp->tcp_seq) == 1 && ntohl(tcp->tcp_ack) == 1) { - // recv HTTP request message and reply top half data - tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack)); - tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + recv_payload_len); + tcp_seq = ntohl(tcp->tcp_seq) - priv->irs; + tcp_ack = ntohl(tcp->tcp_ack) - priv->iss; + tcp_data_len = len - ETHER_HDR_SIZE - IP_HDR_SIZE - GET_TCP_HDR_LEN_IN_BYTES(tcp->tcp_hlen);
- payload_len = strlen(payload1) - bottom_payload_len; - memcpy(data, payload1, payload_len); - tcp_send->tcp_flags = TCP_ACK; + if (tcp->tcp_flags & TCP_FIN) + tcp_data_len++;
- next_seq = ntohl(tcp_send->tcp_seq) + payload_len; - } else if (ntohl(tcp->tcp_ack) == next_seq) { - // reply bottom half data - const int top_payload_len = strlen(payload1) - bottom_payload_len; + tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack)); + tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + tcp_data_len);
- tcp_send->tcp_seq = htonl(next_seq); - tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + recv_payload_len); + if (tcp_seq == 1 && tcp_ack == 1) { + if (tcp_data_len == 0) { + /* no data, wait for GET request */ + return -1; + }
- payload_len = bottom_payload_len; - memcpy(data, payload1 + top_payload_len, payload_len); + /* reply to GET request */ + payload_len = strlen(payload1); + memcpy(data, payload1, payload_len); tcp_send->tcp_flags = TCP_ACK; - } else { - // close connection - tcp_send->tcp_seq = htonl(ntohl(tcp->tcp_ack)); - tcp_send->tcp_ack = htonl(ntohl(tcp->tcp_seq) + 1); + } else if (tcp_ack == 1 + strlen(payload1)) { payload_len = 0; tcp_send->tcp_flags = TCP_ACK | TCP_FIN; + } else if (tcp_ack == 2 + strlen(payload1)) { + payload_len = 0; + tcp_send->tcp_flags = TCP_ACK; }
tcp_send->tcp_hlen = SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE)); @@ -217,7 +213,7 @@ static int net_test_wget(struct unit_test_state *uts) env_set("ethrotate", "no"); env_set("loadaddr", "0x20000"); ut_assertok(run_command("wget ${loadaddr} 1.1.2.2:/index.html", 0)); - ut_assert_nextline("HTTP/1.1 200 OK"); + ut_assert_nextline_empty(); ut_assert_nextline("Packets received 5, Transfer Successful"); ut_assert_nextline("Bytes transferred = 32 (20 hex)");

According to HTTP/1.0 standard the HTTP reply consist of * Status Line + CRLF * Zero or more Response Header Fields (each ended with CRLF) * CRLF on new line (Response Header Fields end marker) * Optional Entity Body.
Thus in response headers we state: Content-Length = 30 but actual transferred file data is: "\r\n<html><body>Hi</body></html>\r\n". This is 32 bytes of data.
So we get and check for correctness 32 bytes of data, but * The response we are used is incorrect, real server will set Content-Length to 32. * default_wget_info->hdr_cont_len will be set to wrong value 30 (used for efi http booting).
Fix an issue by: * replace bogus response with an actual response from the HTTP server * format response to show HTTP response structure * recalculate md5sum as transferred file data has been changed.
The server response was captured with the commands
echo -ne "<html><body>Hi</body></html>\n" > ~/public_html/test.html echo -ne "GET /~${USER}/test.html HTTP/1.0\r\n\r\n" | netcat localhost 80 >reply.txt
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu --- test/cmd/wget.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/test/cmd/wget.c b/test/cmd/wget.c index d51650d5854..b020d20ad70 100644 --- a/test/cmd/wget.c +++ b/test/cmd/wget.c @@ -107,9 +107,22 @@ static int sb_ack_handler(struct udevice *dev, void *packet, int payload_len = 0; u32 tcp_seq, tcp_ack; int tcp_data_len; - const char *payload1 = "HTTP/1.1 200 OK\r\n" - "Content-Length: 30\r\n\r\n\r\n" - "<html><body>Hi</body></html>\r\n"; + const char *payload1 = + /* response status line */ + "HTTP/1.1 200 OK\r\n" + /* response header fields */ + "Date: Mon, 23 Dec 2024 05:18:23 GMT\r\n" + "Server: Apache/2.4.62 (Debian)\r\n" + "Last-Modified: Mon, 23 Dec 2024 05:04:50 GMT\r\n" + "ETag: "1d-629e8efb09e7b"\r\n" + "Accept-Ranges: bytes\r\n" + "Content-Length: 29\r\n" + "Connection: close\r\n" + "Content-Type: text/html\r\n" + /* response header fields end marker */ + "\r\n" + /* file data (for HTTP GET requests) */ + "<html><body>Hi</body></html>\n";
/* Don't allow the buffer to overrun */ if (priv->recv_packets >= PKTBUFSRX) @@ -136,11 +149,11 @@ static int sb_ack_handler(struct udevice *dev, void *packet,
if (tcp_seq == 1 && tcp_ack == 1) { if (tcp_data_len == 0) { - /* no data, wait for GET request */ + /* no data, wait for GET/HEAD request */ return -1; }
- /* reply to GET request */ + /* reply to GET/HEAD request */ payload_len = strlen(payload1); memcpy(data, payload1, payload_len); tcp_send->tcp_flags = TCP_ACK; @@ -215,12 +228,12 @@ static int net_test_wget(struct unit_test_state *uts) ut_assertok(run_command("wget ${loadaddr} 1.1.2.2:/index.html", 0)); ut_assert_nextline_empty(); ut_assert_nextline("Packets received 5, Transfer Successful"); - ut_assert_nextline("Bytes transferred = 32 (20 hex)"); + ut_assert_nextline("Bytes transferred = 29 (1d hex)");
sandbox_eth_set_tx_handler(0, NULL);
run_command("md5sum ${loadaddr} ${filesize}", 0); - ut_assert_nextline("md5 for 00020000 ... 0002001f ==> 234af48e94b0085060249ecb5942ab57"); + ut_assert_nextline("md5 for 00020000 ... 0002001c ==> 847d5e7320a27462e90bc1ed75eb8cd8"); ut_assert_console_end();
env_set("ethact", prev_ethact);

This patch: * remove useless code, * use a special function for pretty printing of tcp flags, * simplify the code
The behavior should not be changed.
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu Reviewed-by: Simon Glass sjg@chromium.org --- net/tcp.c | 66 +++++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 33 deletions(-)
diff --git a/net/tcp.c b/net/tcp.c index a2941951834..2600f74b265 100644 --- a/net/tcp.c +++ b/net/tcp.c @@ -563,10 +563,33 @@ void net_set_syn_options(struct tcp_stream *tcp, union tcp_build_pkt *b) b->ip.end = TCP_O_END; }
+const char *tcpflags_to_str(char tcpflags, char *buf, int size) +{ + int i; + static const struct { + int bit; + const char *name; + } desc[] = {{TCP_RST, "RST"}, {TCP_SYN, "SYN"}, {TCP_PUSH, "PSH"}, + {TCP_FIN, "FIN"}, {TCP_ACK, "ACK"}}; + + *buf = '\0'; + for (i = 0; i < ARRAY_SIZE(desc); i++) { + if (!(tcpflags & desc[i].bit)) + continue; + + if (*buf) + strlcat(buf, ",", size); + strlcat(buf, desc[i].name, size); + } + + return buf; +} + int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len, u8 action, u32 tcp_seq_num, u32 tcp_ack_num) { union tcp_build_pkt *b = (union tcp_build_pkt *)pkt; + char buf[24]; int pkt_hdr_len; int pkt_len; int tcp_len; @@ -576,55 +599,32 @@ int tcp_set_tcp_header(struct tcp_stream *tcp, uchar *pkt, int payload_len, * 4 bits reserved options */ b->ip.hdr.tcp_flags = action; - pkt_hdr_len = IP_TCP_HDR_SIZE; b->ip.hdr.tcp_hlen = SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
switch (action) { case TCP_SYN: debug_cond(DEBUG_DEV_PKT, - "TCP Hdr:SYN (%pI4, %pI4, sq=%u, ak=%u)\n", - &tcp->rhost, &net_ip, - tcp_seq_num, tcp_ack_num); + "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n", + tcpflags_to_str(action, buf, sizeof(buf)), + &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num); net_set_syn_options(tcp, b); pkt_hdr_len = IP_TCP_O_SIZE; break; - case TCP_SYN | TCP_ACK: - case TCP_ACK: - pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b); - b->ip.hdr.tcp_flags = action; - debug_cond(DEBUG_DEV_PKT, - "TCP Hdr:ACK (%pI4, %pI4, s=%u, a=%u, A=%x)\n", - &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num, - action); - break; - case TCP_FIN: - debug_cond(DEBUG_DEV_PKT, - "TCP Hdr:FIN (%pI4, %pI4, s=%u, a=%u)\n", - &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num); - payload_len = 0; - pkt_hdr_len = IP_TCP_HDR_SIZE; - break; case TCP_RST | TCP_ACK: case TCP_RST: debug_cond(DEBUG_DEV_PKT, - "TCP Hdr:RST (%pI4, %pI4, s=%u, a=%u)\n", + "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n", + tcpflags_to_str(action, buf, sizeof(buf)), &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num); + pkt_hdr_len = IP_TCP_HDR_SIZE; break; - /* Notify connection closing */ - case (TCP_FIN | TCP_ACK): - case (TCP_FIN | TCP_ACK | TCP_PUSH): - debug_cond(DEBUG_DEV_PKT, - "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%u, a=%u, A=%x)\n", - &tcp->rhost, &net_ip, - tcp_seq_num, tcp_ack_num, action); - fallthrough; default: pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(tcp, b); - b->ip.hdr.tcp_flags = action | TCP_PUSH | TCP_ACK; debug_cond(DEBUG_DEV_PKT, - "TCP Hdr:dft (%pI4, %pI4, s=%u, a=%u, A=%x)\n", - &tcp->rhost, &net_ip, - tcp_seq_num, tcp_ack_num, action); + "TCP Hdr:%s (%pI4, %pI4, s=%u, a=%u)\n", + tcpflags_to_str(action, buf, sizeof(buf)), + &tcp->rhost, &net_ip, tcp_seq_num, tcp_ack_num); + break; }
pkt_len = pkt_hdr_len + payload_len;

Some driver implements it's own network packet pool, so PKTBUFSRX is zero. This results in zero-size TCP receive window, so data transfer doesn't work. Avoid it by setting a reasonable fallback value.
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu Reviewed-by: Simon Glass sjg@chromium.org --- net/tcp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/net/tcp.c b/net/tcp.c index 2600f74b265..2635727f47d 100644 --- a/net/tcp.c +++ b/net/tcp.c @@ -36,7 +36,11 @@ #define TCP_SEND_RETRY 3 #define TCP_SEND_TIMEOUT 2000UL #define TCP_RX_INACTIVE_TIMEOUT 30000UL -#define TCP_RCV_WND_SIZE (PKTBUFSRX * TCP_MSS) +#if PKTBUFSRX != 0 + #define TCP_RCV_WND_SIZE (PKTBUFSRX * TCP_MSS) +#else + #define TCP_RCV_WND_SIZE (4 * TCP_MSS) +#endif
#define TCP_PACKET_OK 0 #define TCP_PACKET_DROP 1

fix include ordering to follow https://docs.u-boot.org/en/latest/develop/codingstyle.html#include-files
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu Reviewed-by: Simon Glass sjg@chromium.org --- net/net.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/net/net.c b/net/net.c index fd634405e8a..1828f1cca36 100644 --- a/net/net.c +++ b/net/net.c @@ -89,41 +89,43 @@ #include <image.h> #include <led.h> #include <log.h> +#if defined(CONFIG_LED_STATUS) +#include <miiphy.h> +#endif #include <net.h> #include <net6.h> #include <ndisc.h> +#if defined(CONFIG_LED_STATUS) +#include <status_led.h> +#endif +#include <watchdog.h> +#include <linux/compiler.h> #include <net/fastboot_udp.h> #include <net/fastboot_tcp.h> -#include <net/tftp.h> #include <net/ncsi.h> #if defined(CONFIG_CMD_PCAP) #include <net/pcap.h> #endif -#include <net/udp.h> -#if defined(CONFIG_LED_STATUS) -#include <miiphy.h> -#include <status_led.h> -#endif -#include <watchdog.h> -#include <linux/compiler.h> -#include <test/test.h> #include <net/tcp.h> +#include <net/tftp.h> +#include <net/udp.h> #include <net/wget.h> +#include <test/test.h> #include "arp.h" #include "bootp.h" #include "cdp.h" +#include "dhcpv6.h" #if defined(CONFIG_CMD_DNS) #include "dns.h" #endif #include "link_local.h" +#include "net_rand.h" #include "nfs.h" #include "ping.h" #include "rarp.h" #if defined(CONFIG_CMD_WOL) #include "wol.h" #endif -#include "dhcpv6.h" -#include "net_rand.h"
/** BOOTP EXTENTIONS **/

On Mon, Dec 23, 2024 at 01:58:41PM +0300, Mikhail Kshevetskiy wrote:
Legacy TCP stack is bad. Here are some of the known issues:
- tcp packet from other connection can break a current one
- tcp send sequence always starts from zero
- bad tcp options processing
- strange assumptions on packet size for selective acknowledge
- tcp interface assumes one of the two scenarios:
so it's not possible to upload large amount of data from the board to remote host.
- data downloading from remote host to a board
- request-response exchange with a small packets
- wget test generate bad tcp stream, test should fail but it passes instead
This series of patches fixes all of the above issues.
The benefits:
- A lot of bug was fixed
- Better and more reliable TCP state machine
- Tcp clients becomes smaller/simpler
- Data uploading was fixed (now it's possible to transmit a huge amount of data from the board to remote host)
Modification was verified with
- firmware downloading via u-boot wget command
- fastboot over tcp
- netcat linux client using test netcat implementation (not included to this patch series)
- Firefox/Chrome/Edge using test web-server implementation (not included to this patch series)
This fails on the EFI self test on qemu-arm64. You can see this with "bootefi selftest" on the platform, and the excerpt from the full failure log at: https://source.denx.de/u-boot/u-boot/-/jobs/984858 https://dev.azure.com/u-boot/u-boot/_build/results?buildId=10224&view=lo... is: Setting up 'http protocol' succeeded Executing 'http protocol' 93.184.215.14 Packets received 5, Transfer Successful lib/efi_selftest/efi_selftest_http.c(216): ERROR: File not found lib/efi_selftest/efi_selftest.c(114): ERROR: Executing 'http protocol' failed Tearing down 'http protocol'

Is there a way to run this test normally? I tried to run it on x86_64
* make sandbox64_defconfig * enable CMD_BOOTEFI_SELFTEST configuration * run u-boot with "sudo ./u-boot -D"
type the following commands: => setenv autoload no; => setenv ethrotate no; => setenv ethact host_enp1s0; => dhcp; => setenv efi_selftest "http protocol" => bootefi selftest;
but DNS functionality does not work. I tried direct DNS querying with the same result
=> dns example.com UDP wrong checksum 00000001 0000e946 Timeout dns lookup of example.com failed, check setup
But wget, ping and some other commands works normally
=> ping 142.250.74.14 Using host_enp1s0 device host 142.250.74.14 is alive
Lets go back to a test. As I can see this test should not work et all. Current wget implementation requires "default server configuration" being set on target server. Unfortunately "http://example.com/" does NOT set default server configuration.
Thus wget open 93.184.215.14 server, then send the following request GET / HTTP/1.0\r\n \r\n Please note linux wget utility will send the following request GET / HTTP/1.0\r\n HOST: example.com\r\n \r\n (some meaningless headers were omitted).
As result in first case we tried to open default server. It's not configured, so we will get 404 error (Not Found). In the second case "example.com" server will be queried, so we get normal HTTP response.
I can patch wget, so it will send HOST header. Unfortunately it will require configured DNS. What do you think about it?
PS: Anyway, I found a bug (incorrect error handling) in my wget code, so I will send v16 anyway.
Mikhail Kshevetskiy
On 28.12.2024 00:00, Tom Rini wrote:
On Mon, Dec 23, 2024 at 01:58:41PM +0300, Mikhail Kshevetskiy wrote:
Legacy TCP stack is bad. Here are some of the known issues:
- tcp packet from other connection can break a current one
- tcp send sequence always starts from zero
- bad tcp options processing
- strange assumptions on packet size for selective acknowledge
- tcp interface assumes one of the two scenarios:
so it's not possible to upload large amount of data from the board to remote host.
- data downloading from remote host to a board
- request-response exchange with a small packets
- wget test generate bad tcp stream, test should fail but it passes instead
This series of patches fixes all of the above issues.
The benefits:
- A lot of bug was fixed
- Better and more reliable TCP state machine
- Tcp clients becomes smaller/simpler
- Data uploading was fixed (now it's possible to transmit a huge amount of data from the board to remote host)
Modification was verified with
- firmware downloading via u-boot wget command
- fastboot over tcp
- netcat linux client using test netcat implementation (not included to this patch series)
- Firefox/Chrome/Edge using test web-server implementation (not included to this patch series)
This fails on the EFI self test on qemu-arm64. You can see this with "bootefi selftest" on the platform, and the excerpt from the full failure log at: https://source.denx.de/u-boot/u-boot/-/jobs/984858 https://dev.azure.com/u-boot/u-boot/_build/results?buildId=10224&view=lo... is: Setting up 'http protocol' succeeded Executing 'http protocol' 93.184.215.14 Packets received 5, Transfer Successful lib/efi_selftest/efi_selftest_http.c(216): ERROR: File not found lib/efi_selftest/efi_selftest.c(114): ERROR: Executing 'http protocol' failed Tearing down 'http protocol'

more notes about lib/efi_selftest/efi_selftest_http 1) test does not send HEAD request as stated in the lib/efi_selftest/efi_selftest_http.c file header. It send GET request in the line 168 and read the response twice (line 203 and line 244). It looks a bit wrong to read the response twice from the same request.
2) I replace "http://example.com/" with my router page "http://10.0.14.10/index.html" to avoid DNS issue (discussed in the previous mail), but sandbox/sandbox64 produce SIGABRT during a test run.
kl@laska:~/econet/bootloader/u-boot-current$ sudo gdb ./u-boot GNU gdb (Debian 15.2-1) 15.2 Copyright (C) 2024 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: https://www.gnu.org/software/gdb/bugs/. Find the GDB manual and other documentation resources online at: http://www.gnu.org/software/gdb/documentation/.
For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./u-boot... (gdb) run -D Starting program: /home/kl/econet/bootloader/u-boot-current/u-boot -D [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Bloblist at b000 not found (err=-2)
U-Boot 2025.01-rc5-00387-gc390d0882c37-dirty (Dec 28 2024 - 12:19:19 +0300)
Reset Status: COLD Model: sandbox DRAM: 256 MiB
Warning: host_lo MAC addresses don't match: Address in ROM is 12:f0:b0:f5:1b:36 Address in environment is 02:00:11:22:33:44 Core: 114 devices, 60 uclasses, devicetree: board, universal payload active WDT: Started alarm-wdt with servicing every 1000ms (5s timeout) NAND: 0 MiB MMC: Loading Environment from nowhere... OK Warning: device tree node '/config/environment' not found In: serial Out: serial,vidconsole Err: serial,vidconsole Model: sandbox Net: eth0: host_lo, eth1: host_enp1s0, eth2: host_wlp2s0, eth3: host_enx00e04c6801f6 Error: eth@10002000 No valid MAC address found.
Hit any key to stop autoboot: 0 => setenv autoload no;setenv ethrotate no;setenv ethact host_enp1s0; dhcp BOOTP broadcast 1 BOOTP broadcast 2 BOOTP broadcast 3 BOOTP broadcast 4 DHCP client bound to address 10.0.0.146 (3407 ms) => setenv efi_selftest "http protocol" => bootefi selftest efi_var_to_file() Cannot persist EFI variables without system partition
Testing EFI API implementation
Selected test: 'http protocol'
Setting up 'http protocol' HTTP Service Binding: child created successfully Setting up 'http protocol' succeeded
Executing 'http protocol' phys_to_virt: Cannot map sandbox address 18bc1010 (SDRAM from 0 to 10000000)
Program received signal SIGABRT, Aborted. __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44 warning: 44 ./nptl/pthread_kill.c: No such file or directory (gdb) bt #0 __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44 #1 0x00007ffff7e2acef in __pthread_kill_internal (threadid=<optimized out>, signo=6) at ./nptl/pthread_kill.c:78 #2 0x00007ffff7dd6c42 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26 #3 0x00007ffff7dbf4f0 in __GI_abort () at ./stdlib/abort.c:79 #4 0x000055555559d450 in os_abort () at arch/sandbox/cpu/os.c:1054 #5 phys_to_virt (paddr=<optimized out>) at arch/sandbox/cpu/cpu.c:121 #6 phys_to_virt (paddr=<optimized out>) at arch/sandbox/cpu/cpu.c:96 #7 0x0000555555791444 in map_physmem.constprop.0 (paddr=paddr@entry=414978064, len=<optimized out>, len@entry=36, flags=0) at arch/sandbox/cpu/cpu.c:184 #8 0x00005555556d1587 in map_sysmem (paddr=414978064, len=36) at ./arch/sandbox/include/asm/io.h:225 #9 store_block (src=0x555555964462 <net_pkt_buf+1858> "HTTP/1.0 200 OK\r\nConnection: close\r\n", offset=<optimized out>, len=36) at net/wget.c:65 #10 tcp_stream_rx (tcp=<optimized out>, rx_offs=<optimized out>, buf=0x555555964462 <net_pkt_buf+1858>, len=36) at net/wget.c:230 #11 0x00005555556d4e3c in tcp_rx_user_data (tcp=tcp@entry=0x55555595e700 <tcp_stream.lto_priv>, tcp_seq_num=tcp_seq_num@entry=3457615271, buf=buf@entry=0x555555964462 <net_pkt_buf+1858> "HTTP/1.0 200 OK\r\nConnection: close\r\n", len=len@entry=36) at net/tcp.c:943 #12 0x00005555556d630d in tcp_rx_state_machine (tcp=0x55555595e700 <tcp_stream.lto_priv>, b=0x55555596442e <net_pkt_buf+1806>, pkt_len=88) at net/tcp.c:1104 #13 rxhand_tcp_f (b=0x55555596442e <net_pkt_buf+1806>, pkt_len=88) at net/tcp.c:1207 #14 net_process_received_packet (in_packet=<optimized out>, len=<optimized out>) at net/net.c:1411 #15 eth_rx () at net/eth-uclass.c:459 #16 0x00005555556d7121 in net_loop (protocol=protocol@entry=WGET) at net/net.c:656 #17 0x00005555556d7b87 in wget_do_request (dst_addr=414978064, uri=<optimized out>) at net/wget.c:413 #18 0x00005555556875cb in wget_request (info=0x555555940e20 <efi_wget_info>, dst_addr=4166818, uri=0x7fffffffd618 "http://10.0.14.10/index.html") at net/net-common.c:26 #19 efi_net_do_request (url=0x7fffffffd618 "http://10.0.14.10/index.html", method=HTTP_METHOD_GET, buffer=0x19c6b980, status_code=0x19c6b998, file_size=0x19c6b988, headers_buffer=0x19c7b3a8 "") at lib/efi_loader/efi_net.c:1320 #20 efi_http_request (this=0x19c6b940, token=0x7fffffffdb70) at lib/efi_loader/efi_http.c:242 #21 0x000055555568f3ec in execute () at lib/efi_selftest/efi_selftest_http.c:168 #22 0x0000555555681687 in execute (test=0x5555559225f0 <_u_boot_list_2_efi_unit_test_2_http>, failures=0x7fffffffdd08) at lib/efi_selftest/efi_selftest.c:112 #23 efi_st_do_tests (testname=0x19c47df0, phase=1, steps=7, failures=0x7fffffffdd08) at lib/efi_selftest/efi_selftest.c:229 #24 0x00005555555bdce6 in efi_selftest (systab=0x55555592ec20 <systab>, image_handle=0x19c6b7a0, systab=0x55555592ec20 <systab>) at lib/efi_selftest/efi_selftest.c:313 #25 do_efi_selftest () at cmd/bootefi.c:109 #26 do_bootefi (cmdtp=<optimized out>, flag=<optimized out>, argc=<optimized out>, argv=<optimized out>) at cmd/bootefi.c:176 #27 0x00005555555f2bb6 in cmd_call (cmdtp=0x5555559168b8 <_u_boot_list_2_cmd_2_bootefi>, flag=0, argc=2, argv=0x19c58a20, repeatable=0x7fffffffdd84) at common/command.c:582 #28 cmd_process (flag=<optimized out>, argc=2, argv=0x19c58a20, repeatable=0x555555948c68 <flag_repeat>, ticks=0x0) at common/command.c:637 #29 0x00005555555ebdf6 in run_pipe_real (pi=0x19c586a0) at common/cli_hush.c:1672 #30 run_list_real (pi=pi@entry=0x19c586a0) at common/cli_hush.c:1868 #31 0x00005555555ec100 in run_list (pi=0x19c586a0) at common/cli_hush.c:2017 #32 parse_stream_outer (inp=inp@entry=0x7fffffffdf28, flag=flag@entry=2) at common/cli_hush.c:3207 #33 0x00005555555ed848 in parse_file_outer () at common/cli_hush.c:3299 #34 cli_loop () at common/cli.c:302 #35 main_loop () at common/main.c:82 --Type <RET> for more, q to quit, c to continue without paging--Quit
The incorrect image loading address comes to wget from the test or efi subsystem, thus we get SIGABRT on map_sysmem() function call.
Mikhail Kshevetskiy
On 28.12.2024 00:00, Tom Rini wrote:
On Mon, Dec 23, 2024 at 01:58:41PM +0300, Mikhail Kshevetskiy wrote:
Legacy TCP stack is bad. Here are some of the known issues:
- tcp packet from other connection can break a current one
- tcp send sequence always starts from zero
- bad tcp options processing
- strange assumptions on packet size for selective acknowledge
- tcp interface assumes one of the two scenarios:
so it's not possible to upload large amount of data from the board to remote host.
- data downloading from remote host to a board
- request-response exchange with a small packets
- wget test generate bad tcp stream, test should fail but it passes instead
This series of patches fixes all of the above issues.
The benefits:
- A lot of bug was fixed
- Better and more reliable TCP state machine
- Tcp clients becomes smaller/simpler
- Data uploading was fixed (now it's possible to transmit a huge amount of data from the board to remote host)
Modification was verified with
- firmware downloading via u-boot wget command
- fastboot over tcp
- netcat linux client using test netcat implementation (not included to this patch series)
- Firefox/Chrome/Edge using test web-server implementation (not included to this patch series)
This fails on the EFI self test on qemu-arm64. You can see this with "bootefi selftest" on the platform, and the excerpt from the full failure log at: https://source.denx.de/u-boot/u-boot/-/jobs/984858 https://dev.azure.com/u-boot/u-boot/_build/results?buildId=10224&view=lo... is: Setting up 'http protocol' succeeded Executing 'http protocol' 93.184.215.14 Packets received 5, Transfer Successful lib/efi_selftest/efi_selftest_http.c(216): ERROR: File not found lib/efi_selftest/efi_selftest.c(114): ERROR: Executing 'http protocol' failed Tearing down 'http protocol'
participants (2)
-
Mikhail Kshevetskiy
-
Tom Rini