From patchwork Sat Sep 21 03:43:46 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mikhail Kshevetskiy X-Patchwork-Id: 1988107 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=iopsys.eu header.i=@iopsys.eu header.a=rsa-sha256 header.s=selector2 header.b=jbWLPUk4; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) Received: from phobos.denx.de (phobos.denx.de [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4X9ZsT4hvMz1y2P for ; Sat, 21 Sep 2024 13:45:33 +1000 (AEST) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id CDEDD88B27; Sat, 21 Sep 2024 05:44:19 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=reject dis=none) header.from=iopsys.eu Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=pass (2048-bit key; unprotected) header.d=iopsys.eu header.i=@iopsys.eu header.b="jbWLPUk4"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id 39A5488B0C; Sat, 21 Sep 2024 05:44:18 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-1.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS,SPF_HELO_PASS, SPF_PASS autolearn=no autolearn_force=no version=3.4.2 Received: from EUR05-DB8-obe.outbound.protection.outlook.com (mail-db8eur05on20701.outbound.protection.outlook.com [IPv6:2a01:111:f403:2614::701]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id 855958863E for ; Sat, 21 Sep 2024 05:44:14 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=reject dis=none) header.from=iopsys.eu Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=mikhail.kshevetskiy@genexis.eu ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=dnhwod9ksY4+YK63JLTvV/fYdNaMcNuIwkww5q+BYj78hzsC46akjdJw7iWPyA5D+xlyslgTd10p53axca/KCPkyk/bWbsGmmuCYu/K0QxXtna6ZxP+YShFKVJzmmDSk7myKS3vS7Sz2rPFGf/3++S6R4zLQbWQblzmkQnNs5trV/BYtG/jjoOApswqmEY1Q4mwVCLceHIjF0xRlNE9t/FMr8DhFr4oRvfxb4SMk9m+uz6+m8tTbQ5DKVmC4avJgLPOki9i7qrn0pabR3U+fZ8QMMPlaZXIyQyPWvrQACZHu+oBabQgPfPm640mR50gYVoghAytY30dlL8Xce++Tiw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=TOrjz9Z2LfB7yZP8j8ck1xOOGkW5I+jODebUEPYL+Wo=; b=uXNbhS9rjzgzU88JSYKqEDT2zwg2apWQ8vAXSk68cexe/AyzO4eJKjPowWaenlEsZgOfqzEGpKMhKARiTyHWu6T47I8L5yBHhXNOn8vaqQ+lBu8wY64g7TFQXsaxrndP/SUgwgN2vwp8tWEmvSpBdQ+T4wrSgq8NbRsNeNaB0hMFzdKJ7K5z7DJf9CM2ToHRKcICv8hV+So+h1rqoCSQpyDhmPuMSeYB6keIxlI+/CyyQUE9g+fccl2WTwfL5qLNPAHUUHFDZDWme04o6ZiAv+r21r0/AtIN8Q75SFBg31mbHJOBSCm5Q76vA/jtAgleFjgvUVTgz351rn+MB9zVZQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=genexis.eu; dmarc=pass action=none header.from=iopsys.eu; dkim=pass header.d=iopsys.eu; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iopsys.eu; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=TOrjz9Z2LfB7yZP8j8ck1xOOGkW5I+jODebUEPYL+Wo=; b=jbWLPUk4UsO51w3d28qlWQXJa5SnXGY1V/wcQIH1bhxs1kxVkUR2LVF+CvX4wfH09YWsNB6Frwl07M9pg9jO/PbnXeA1FtzZ7ZoWBs9deyPBpS7cO4xahYDhOSKo0itMhkZHrfcNbdDNHmGSgoGHr3a/0efrlPoCfmKBmeRQFKEfsUkpQgxptNsRXUmJc9aCKo4CTuivFeHbVyEIPOB+8icYBk2S7+iUcoh/+JGlXtB48wscPGYIlrBeOl8zigvlAh96KhDJPgEHwt6lY9WJce+6lNhfjHZ/i4zUgXLGq5U5QKpl0OmSoyOlFBO0EXD3phwy3IsKbpHPQNs2/3K8Gg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=iopsys.eu; Received: from GV2PR08MB8121.eurprd08.prod.outlook.com (2603:10a6:150:7d::22) by DU0PR08MB9418.eurprd08.prod.outlook.com (2603:10a6:10:421::17) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8005.7; Sat, 21 Sep 2024 03:44:10 +0000 Received: from GV2PR08MB8121.eurprd08.prod.outlook.com ([fe80::4cd3:da80:2532:daa0]) by GV2PR08MB8121.eurprd08.prod.outlook.com ([fe80::4cd3:da80:2532:daa0%2]) with mapi id 15.20.8005.010; Sat, 21 Sep 2024 03:44:10 +0000 From: Mikhail Kshevetskiy To: Simon Glass , Tom Rini , Joe Hershberger , Ramon Fried , Mattijs Korpershoek , Mikhail Kshevetskiy , AKASHI Takahiro , Heinrich Schuchardt , Michal Simek , Anand Moon , Marek Vasut , Ilias Apalodimas , Masahisa Kojima , Sean Anderson , Baruch Siach , Yasuharu Shibata , Richard Weinberger , u-boot@lists.denx.de Subject: [PATCH v7 06/13] net/tcp: improve tcp framework, use better state machine Date: Sat, 21 Sep 2024 06:43:46 +0300 Message-ID: <20240921034353.1298452-7-mikhail.kshevetskiy@iopsys.eu> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20240921034353.1298452-1-mikhail.kshevetskiy@iopsys.eu> References: <20240921034353.1298452-1-mikhail.kshevetskiy@iopsys.eu> X-ClientProxiedBy: GV2PEPF00004595.SWEP280.PROD.OUTLOOK.COM (2603:10a6:158:401::440) To GV2PR08MB8121.eurprd08.prod.outlook.com (2603:10a6:150:7d::22) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: GV2PR08MB8121:EE_|DU0PR08MB9418:EE_ X-MS-Office365-Filtering-Correlation-Id: 88b7092c-d415-4cb3-d9c0-08dcd9efa71d X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|7416014|52116014|376014|1800799024|366016|38350700014|921020; X-Microsoft-Antispam-Message-Info: D93LWmdhiYHslCBarbSoQRfTtrqHeI76hzMTl19yHR9IGyLodxMA1qA4yRUjMFnKia7TlzH8d/S5XKogXNnaJmk5hyYTtu+JpQgjNXK/fxKtaMe3HW9+xuwRWNIb/3k128UMFvZpv9eEK1Jr5lflLLfofcC+4pxu2UaTMIjNyanJ5G3nO+HU8O8ePNWN9pZ9FBfcxuYnA/ZZMjRIe/evbzChXKHKmPnqJxJm7OzLj7M3Ds2B9PmFdRKR8yohRdajfVTgi6mAoCaPk5XIEnyWFcIn7HmBnyhbqCz3CIyKbTw8vrmdjYvvsozGsYFysl7tbkD79bNqTXZefXrcxESf7Z33c5wQl07Ya5E3927pO64mf7eigUR/A5TnCYaJRiFdyTFjTryPNQZnm5vdXEhfZJAOVGc/O9RsZ84fhhGUfzMWx2+Dz9ouy2F20x1qmvMw3/9NynnFroELaTj/pQOI/mHiM+vxKXxS3TjiSSV9HcWwLF/dgWzWYtWjbq5RhhGJuMRGZVbJsCPzujGUfx2hEg6bw2JlwkbPKrv58Miu+dAwqustLpLvAexBVrfhbEs5tVvddrOtSycm+KbET6B9J1IZ3MN3g3/Nn7K5XQ2xW6BtNRumiRowZytB6k+1gKz1c++LUGyfMSbSJ7c107gjqgtzFU9Mmob6gtPlPW0Dz2hxLL1Pd0yWngA0XHqawRuoWnVoU8l9MSGt7fV0kjM9rbBXOePvAMI07nQxB4ouVjpCSrRiF6N8q1CQ7fqU8j2j89IiMNZ2knFpetwSOqmFvB7Yvzy+UzE5FMhsr/GFi28i3tZ0jOOHAVUXsyiHAb026ujaTuq85VTrYYPKXz6vQp3KDuNn+yc+N2oedRxg6iNy0e/Yy4v+QlQUZclyL/i89zph9HgvWKVz1trzk5feQMwItJxMfGQBS/7t0DSYlvk9Z7JCM7CdRvNUJovHzDXWgZlgNv1i2FqxMI0fwTHS72jgpujo3nda+1Zw/i2RSLR/wBgghjespHCDz2R4fpCvYv5bTbD7By9HQC/ADSR9VeQmb7ewdxfsImJD+TEZ7cbDehf4malyEq4Evq/5Az3cEbwGm3haL+Cs4CVAJlaVWSTasnkD04YqLZBXC8boZeQlYFOjTqyJmy64Yt7F94k/qWrY14Y0kKR5wVpfCuE9U86O6oRQV4uzbdTQ0blli+XVn4xRGO3t6PlqcpFST/0aO57hiB3bKQh2IWcM/veJtedve3bPHZKQjaLJ/B17SjMTjpjQkbPjwfO5RMsj/bB5eQLPfXd/iJwJxJ44VeYUa3IXRuIxJ1Vjfu/9NLeeXkvU9Zj0SrfK0lUPXiSvXP0yU+OUP3M6U0ceoyCctw6ql+c1qiBsdlMNiZlI6hCjJEA= X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:GV2PR08MB8121.eurprd08.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(7416014)(52116014)(376014)(1800799024)(366016)(38350700014)(921020); DIR:OUT; SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: yuw4IF5reMo0uKC5zLuFmt25G9TaOv9guQqag6FurU9cBxPIAumnLjrgMCrQjeIDXJinmDxk/x7slMksuNzZB1jAyT3KNukcNqazcHDtYM3fhBxASkRqYYjsSzDdikHv5wc9tYAkCdzVuUDdVrC5RfqkcqLWHntmO9casUGXjaCATvtvOPFBRX6f7G8TKwJE/pOIxBbNxT1rPHTR89ZgZ/9Gl6UEvggBoerCfR181pLCD3qwpeW/Gxdb+4nDDrkr6+ZIfJxd8gpTs3J/2FD7Ccp7cUg4vlDeSQamN2kbGCh8K+kuRvmcXYKT3fiSvQTLzhGRTLlFtub1fi3i/sBDkdhkRN6T8LdKX4v2DzviTpQlQUgXZUaKm+f6gZDGHbcrRGqBVdfslvghJuJsuTHwA9wWN0Yhl2fTps90eR3FHZe1ath1ny59ARXz2kN9x+KW/q/KEok82ISM0P6RnWWLZcVOtO1NdxHivGXuwmX7wR4lL+2IK95AVoweRmTDHnFL45oaH3A+L+tzfHkjt6gf4gB0KjcrYQIKTia9c8OAraeBkFgpDBP2HaACTZraPlAQt19SLQlu9K1MJ6ofxmr4kqN2bwsq/xxiOZ41ySMeTSMmsS/r1d0OVs0eT9vKA6jdN25WsveZ3fJ5yUDS15iAc4m3f9zZeyorlGzkZF74KNJ4DNGODAg0RbxAUTyG2XA4+JPOafwxxF1IpeO/N6+s+NyMjqddgHJ1QrEktaKsu48xeV3Rjd17eCW2kBy9PqCs9VXL1rCXg9AnzWOZFhhtxUYXwKTqeo0itFd3b7gC0UMXcyqGGeTUEBZ6GtQmRVZyqaeQOZGN0b1jiTkMCf2J3DEvH/vxqcS60EEahYi9jbDGl7gnw/Gul5qEfFbNKJv+V/zzgs9je9Vq/LOBLSLsL3bYM5LeCs0XuINP+yEx31rycJYET0OkRU3KOsJh+wE8a7/7fbzmcaEy8AMWHS2aeWHBHXak5OaMs3dtVPVJseLONEk/bnuVt3iocFuAUXIDXjv5q4CwyDV2k446JgDcB1fV7Rkiy/kzScPFC9g2kkhf8FHLpptZhVaEIkSVPVq3qLkd6mDecRMgM1b9bNW0dGOEJIBReg37fGig6bJCTrHv/RQwl3dgglA0gzffLFEDsl4aolA66vfE7UOM23O7L9RJma7XIg6hTcSVHA1SOTupMNHhLcPvdGTf6JCvcLfCRG9sMK79s/kL3Cqu3P0uxActeLItKASQtirb+oMcnHQNPxZ5zTT/9r1GZ50j7TWAC6R6hhjrpbywsmrB0QxN9C8udmY0aIwlLxwxahGHTYGCTXobNLEL8IyfEkx94NleWMpOy64c7a0th9VCsq9ncaZDomxquOwbJJeqXE/jv36grQ/x4QXeJmsWzYVDxs+tq3UiR22WtonmFldLCdp1XuyNvHxPnvI9gBbGIPRAYqTXBBcGHDcV2aWXe4cRoOC0iVRd6jqtnSWcX/bg49vOIwYUjUAn2C5CjWcN9jfFopqhLqP2UO2SwJy0fsksPjjaOsYz33e2L461RqZz3Y86wdWAhuMzMRuZGqCwhRcVhz7W73PGW54gmGqRMIOCqcfHfb3ekh39o8iwdZ1hOPZRpjKDeGJjoVbCh+HzncL8I40= X-OriginatorOrg: iopsys.eu X-MS-Exchange-CrossTenant-Network-Message-Id: 88b7092c-d415-4cb3-d9c0-08dcd9efa71d X-MS-Exchange-CrossTenant-AuthSource: GV2PR08MB8121.eurprd08.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 21 Sep 2024 03:44:10.6032 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 8d891be1-7bce-4216-9a99-bee9de02ba58 X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: e3yUulvdC58zjmAjUjIpZEe5hsoMoWf4Umhl5BwKxevZHrVUHoBX6ltowa9b2zXTEmSBgdt4ICxCw/PkKQH7rAPVeWCZ/IergZTIhoXCo7Q= X-MS-Exchange-Transport-CrossTenantHeadersStamped: DU0PR08MB9418 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.39 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de X-Virus-Status: Clean 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 Reviewed-by: Simon Glass --- include/net/tcp.h | 179 ++++++++-- include/net/wget.h | 8 - net/fastboot_tcp.c | 193 +++++----- net/net.c | 4 + net/tcp.c | 853 ++++++++++++++++++++++++++++++++++----------- net/wget.c | 464 +++++++----------------- 6 files changed, 1022 insertions(+), 679 deletions(-) diff --git a/include/net/tcp.h b/include/net/tcp.h index 0694af9d5b1..0b18475645d 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,20 @@ 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 + */ +enum tcp_status { + TCP_ERR_OK = 0, + TCP_ERR_TOUT, + TCP_ERR_RST, }; /** @@ -283,51 +297,150 @@ 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. + * 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. + * 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) + * + * @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); + u32 (*rx)(struct tcp_stream *tcp, u32 rx_offs, void *buf, u32 len); + u32 (*tx)(struct tcp_stream *tcp, u32 tx_offs, void *buf, u32 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; + + 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 +464,37 @@ 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); +enum tcp_state tcp_stream_get_state(struct tcp_stream *tcp); +enum tcp_status tcp_stream_get_status(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 + +/* + * 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..101d598012e 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 u32 tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, u32 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 u32 tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, u32 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 b33ea59a9fa..86182cc504e 100644 --- a/net/net.c +++ b/net/net.c @@ -646,6 +646,9 @@ restart: * errors that may have happened. */ eth_rx(); +#if defined(CONFIG_PROT_TCP) + tcp_streams_poll(); +#endif /* * Abort if ctrl-c was pressed. @@ -952,6 +955,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 7e445eaffd6..07c57d3ea8b 100644 --- a/net/tcp.c +++ b/net/tcp.c @@ -24,9 +24,26 @@ #include #include -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,15 +115,70 @@ static void tcp_stream_set_state(struct tcp_stream *tcp, tcp->state = new_state; } +/** + * 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) +{ + return tcp->status; +} + +/** + * 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_state new_status) +{ + tcp->status = new_status; +} + +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->time_last_rx = get_timer(0); +} + +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) { - incoming_filter = NULL; - tcp_stream.state = TCP_CLOSED; + 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_set_incoming_filter(tcp_incoming_filter *filter) +void tcp_stream_set_on_create_handler(int (*on_create)(struct tcp_stream *)) { - incoming_filter = filter; + tcp_stream_on_create = on_create; } static struct tcp_stream *tcp_stream_add(struct in_addr rhost, @@ -100,15 +186,14 @@ static struct tcp_stream *tcp_stream_add(struct in_addr rhost, { struct tcp_stream *tcp = &tcp_stream; - if (tcp->state != TCP_CLOSED) + 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; - 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; } @@ -122,30 +207,205 @@ 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) { - 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; + 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) +{ + 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; + + 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 */ + tcp->tx(tcp, tcp->retry_tx_offs, ptr, tcp->retry_tx_len); + } + 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) +{ + uchar *ptr; + u32 tx_offs, tx_len, 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) + 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 +500,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 +559,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 +577,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 +626,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 +745,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 +760,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 +781,333 @@ 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) +{ + 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) { - 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; + u32 tmp_len, 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) + 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; + + tcp_flags = b->ip.hdr.tcp_flags; - switch (tcp->state) { +// 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->time_last_rx = get_timer(0); + + 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->time_last_rx = get_timer(0); + + /* 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->time_last_rx = get_timer(0); + + 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 +1119,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 +1162,58 @@ 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_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; + + tcp = tcp_stream_add(rhost, rport, random_port()); + if (!tcp) + return NULL; - /* 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->iss = tcp_get_start_seq(); + tcp->snd_una = tcp->iss; + tcp->snd_nxt = tcp->iss + 1; - tcp_activity_count++; - if (tcp_activity_count > TCP_ACTIVITY) { - puts("| "); - tcp_activity_count = 0; - } + tcp_stream_set_state(tcp, TCP_SYN_SENT); + tcp_send_packet_with_retry(tcp, TCP_SYN, tcp->snd_una, 0, 0); - 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); + return tcp; +} - (*tcp_packet_handler) (tcp, (uchar *)b + pkt_len - payload_len, - tcp_seq_num, tcp_ack_num, tcp_action, - payload_len); +void tcp_stream_reset(struct tcp_stream *tcp) +{ + if (tcp->state == TCP_CLOSED) + return; - } 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); - } + 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 ad5db21e97e..74df1378e5d 100644 --- a/net/wget.c +++ b/net/wget.c @@ -21,48 +21,26 @@ DECLARE_GLOBAL_DATA_PTR; /* The default, change with environment variable 'httpdstp' */ #define SERVER_PORT 80 +#define HASHES_PER_LINE 65 + +#define HTTP_MAX_HDR_LEN 2048 + static const char bootfile1[] = "GET "; static const char bootfile3[] = " HTTP/1.0\r\n\r\n"; static const char http_eom[] = "\r\n\r\n"; -static const char http_ok[] = "200"; -static const char content_len[] = "Content-Length"; +static const char http_ok[] = " 200 "; +static const char content_len[] = "Content-Length:"; static const char linefeed[] = "\r\n"; -static int wget_timeout_count; -struct tcp_stream *tcp; - -struct pkt_qd { - uchar *pkt; - unsigned int tcp_seq_num; - unsigned int len; -}; - -/* - * This is a control structure for out of order packets received. - * The actual packet bufers are in the kernel space, and are - * expected to be overwritten by the downloaded image. - */ -#define PKTQ_SZ (PKTBUFSRX / 4) -static struct pkt_qd pkt_q[PKTQ_SZ]; -static int pkt_q_idx; +static struct in_addr web_server_ip; +static unsigned int server_port; static unsigned long content_length; static unsigned int packets; - -static unsigned int initial_data_seq_num; -static unsigned int next_data_seq_num; - -static enum wget_state current_wget_state; +static u32 http_hdr_size; +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 */ - static ulong wget_load_size; /** @@ -95,7 +73,6 @@ static int wget_init_load_size(void) 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 (IS_ENABLED(CONFIG_LMB)) { @@ -116,329 +93,153 @@ 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(void) { - 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; - - memcpy(offset, &bootfile1, strlen(bootfile1)); - offset += strlen(bootfile1); - - memcpy(offset, image_url, strlen(image_url)); - offset += strlen(image_url); - - memcpy(offset, &bootfile3, strlen(bootfile3)); - offset += strlen(bootfile3); - net_send_tcp_packet((offset - ptr), tcp->rhost, tcp->rport, tcp->lport, - TCP_PUSH, tcp_seq_num, tcp_ack_num); - current_wget_state = WGET_CONNECTED; - break; - case WGET_CONNECTED: - case WGET_TRANSFERRING: - case WGET_TRANSFERRED: - net_send_tcp_packet(0, tcp->rhost, tcp->rport, tcp->lport, action, - tcp_seq_num, tcp_ack_num); - break; - } -} + int cnt; -static void wget_send(u8 action, unsigned int tcp_seq_num, - unsigned int tcp_ack_num, int len) -{ - retry_action = action; - retry_tcp_ack_num = tcp_ack_num; - retry_tcp_seq_num = tcp_seq_num; - retry_len = len; + if (content_length != -1) { + if (net_boot_file_size > content_length) + content_length = net_boot_file_size; - wget_send_stored(); + 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"); + } } -void wget_fail(char *error_message, unsigned int tcp_seq_num, - unsigned int tcp_ack_num, u8 action) +static void tcp_stream_on_closed(struct tcp_stream *tcp) { - printf("wget: Transfer Fail - %s\n", error_message); - net_set_timeout_handler(0, NULL); - wget_send(action, tcp_seq_num, tcp_ack_num, 0); + if (tcp->status != TCP_ERR_OK) + wget_loop_state = NETLOOP_FAIL; + + if (wget_loop_state != NETLOOP_SUCCESS) + printf("\nwget: Transfer Fail, TCP status - %d\n", tcp->status); + else + printf("\nPackets received %d, Transfer Successful\n", packets); + net_set_state(wget_loop_state); } -void wget_success(u8 action, unsigned int tcp_seq_num, - unsigned int tcp_ack_num, int len, int packets) +static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes) { - printf("Packets received %d, Transfer Successful\n", packets); - wget_send(action, tcp_seq_num, tcp_ack_num, len); -} + char *pos, *tail; + uchar saved, *ptr; + int i; -/* - * Interfaces of U-BOOT - */ -static void wget_timeout_handler(void) -{ - if (++wget_timeout_count > WGET_RETRY_COUNT) { - puts("\nRetry count exceeded; starting again\n"); - wget_send(TCP_RST, 0, 0, 0); - net_start_again(); - } else { - puts("T "); - net_set_timeout_handler(wget_timeout + - WGET_TIMEOUT * wget_timeout_count, - wget_timeout_handler); - wget_send_stored(); + if (http_hdr_size) { + net_boot_file_size = rx_bytes - http_hdr_size; + show_block_marker(); + return; } -} - -#define PKT_QUEUE_OFFSET 0x20000 -#define PKT_QUEUE_PACKET_SIZE 0x800 -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; + ptr = map_sysmem(image_load_addr, rx_bytes + 1); - pkt[len] = '\0'; - pos = strstr((char *)pkt, http_eom); + saved = ptr[rx_bytes]; + ptr[rx_bytes] = '\0'; + pos = strstr((char *)ptr, http_eom); + ptr[rx_bytes] = saved; 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((phys_addr_t)pkt_in_q, len); - memcpy(ptr1, pkt, len); - unmap_sysmem(ptr1); - - pkt_q[pkt_q_idx].pkt = pkt_in_q; - pkt_q[pkt_q_idx].tcp_seq_num = tcp_seq_num; - pkt_q[pkt_q_idx].len = len; - pkt_q_idx++; - - if (pkt_q_idx >= PKTQ_SZ) { - printf("wget: Fatal error, queue overrun!\n"); - net_set_state(NETLOOP_FAIL); + if (rx_bytes < HTTP_MAX_HDR_LEN && + tcp->state == TCP_ESTABLISHED) + goto end; + + printf("ERROR: misssed HTTP header\n"); + tcp_stream_close(tcp); + wget_loop_state = NETLOOP_FAIL; + goto end; + } - 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; - - if (strstr((char *)pkt, http_ok) == 0) { - 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: Connctd pkt %p hlen %x\n", - pkt, hlen); - - pos = strstr((char *)pkt, content_len); - if (!pos) { - content_length = -1; - } else { - pos += sizeof(content_len) + 2; - strict_strtoul(pos, 10, &content_length); - debug_cond(DEBUG_WGET, - "wget: Connected Len %lu\n", - content_length); - } - - 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; - } - } + http_hdr_size = pos - (char *)ptr + strlen(http_eom); + *pos = '\0'; + + pos = strstr((char *)ptr, linefeed); + if (pos > 0) + i = pos - (char *)ptr; + else + i = http_hdr_size - strlen(http_eom); + printf("%.*s\n", i, ptr); + + if (!strstr((char *)ptr, http_ok)) { + 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: Connected Pkt %p hlen %x\n", - pkt, hlen); - - for (i = 0; i < pkt_q_idx; i++) { - int err; - - ptr1 = map_sysmem( - (phys_addr_t)(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: Connctd 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; - } - } - } + debug_cond(DEBUG_WGET, "wget: Connctd pkt %p hlen %x\n", + ptr, http_hdr_size); + + pos = strstr((char *)ptr, content_len); + if (!pos) { + content_length = -1; + } else { + pos += strlen(content_len) + 1; + content_length = simple_strtoul(pos, &tail, 10); + if (*tail != '\r' && *tail != '\n' && *tail != '\0') + content_length = -1; + printf("%s %d\n", content_len, (int)content_length); } - wget_send(action, tcp_seq_num, tcp_ack_num, len); + + net_boot_file_size = rx_bytes - http_hdr_size; + memmove(ptr, ptr + http_hdr_size, net_boot_file_size); + +end: + unmap_sysmem(ptr); + + wget_loop_state = NETLOOP_SUCCESS; } -/** - * 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 u32 tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, u32 len) { - enum tcp_state wget_tcp_state = tcp_stream_get_state(tcp); - - net_set_timeout_handler(wget_timeout, wget_timeout_handler); packets++; + store_block(buf, rx_offs - http_hdr_size, len); - 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); - } - } - break; - case WGET_CONNECTED: - debug_cond(DEBUG_WGET, "wget: Connected seq=%u, len=%x\n", - tcp_seq_num, len); - if (!len) { - wget_fail("Image not found, no data returned\n", - tcp_seq_num, tcp_ack_num, action); - } else { - wget_connected(pkt, tcp_seq_num, action, tcp_ack_num, len); - } - break; - case WGET_TRANSFERRING: - debug_cond(DEBUG_WGET, - "wget: Transferring, seq=%x, ack=%x,len=%x\n", - tcp_seq_num, tcp_ack_num, len); - - if (next_data_seq_num != tcp_seq_num) { - debug_cond(DEBUG_WGET, "wget: seq=%x packet was lost\n", next_data_seq_num); - return; - } - next_data_seq_num = tcp_seq_num + len; + return len; +} - if (store_block(pkt, tcp_seq_num - initial_data_seq_num, len) != 0) { - wget_fail("wget: store error\n", - tcp_seq_num, tcp_ack_num, action); - net_set_state(NETLOOP_FAIL); - return; - } +static u32 tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, u32 maxlen) +{ + int 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); - break; - } + if (tx_offs) + return 0; + + ret = snprintf(buf, maxlen, "%s%s%s", bootfile1, image_url, bootfile3); + + return ret; +} + +static int tcp_stream_on_create(struct tcp_stream *tcp) +{ + if (tcp->rhost.s_addr != web_server_ip.s_addr || + tcp->rport != server_port) + return 0; + + tcp->max_retry_count = WGET_RETRY_COUNT; + tcp->initial_timeout = WGET_TIMEOUT; + tcp->on_closed = tcp_stream_on_closed; + tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update; + tcp->rx = tcp_stream_rx; + tcp->tx = tcp_stream_tx; + + return 1; } #define BLOCKSIZE 512 void wget_start(void) { - struct in_addr web_server_ip; - unsigned int server_port; + struct tcp_stream *tcp; image_url = strchr(net_boot_file_name, ':'); if (image_url > 0) { @@ -486,12 +287,6 @@ void wget_start(void) } } - 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 @@ -500,14 +295,21 @@ void wget_start(void) memset(net_server_ethaddr, 0, 6); + packets = 0; + net_boot_file_size = 0; + http_hdr_size = 0; + wget_tsize_num_hash = 0; + wget_loop_state = NETLOOP_FAIL; + 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 == NULL) { + 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); } #if (IS_ENABLED(CONFIG_CMD_DNS))