[ 
https://issues.apache.org/jira/browse/NET-739?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18129666#comment-18129666
 ] 

CamilleLM commented on NET-739:
-------------------------------

Dumb fix proposed on 
[NET-743|https://issues.apache.org/jira/browse/NET-743?focusedCommentId=18129663&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-18129663]

> FTPSClient storeFile() fails with "451 Transfer aborted" on TLS 1.3 (with low 
> latency and pure-ftpd)
> ----------------------------------------------------------------------------------------------------
>
>                 Key: NET-739
>                 URL: https://issues.apache.org/jira/browse/NET-739
>             Project: Commons Net
>          Issue Type: Bug
>            Reporter: Reed Bertolotti
>            Priority: Major
>
> *Summary:*
> {{FTPSClient.storeFile()}} intermittently fails to complete file uploads when 
> using *TLSv1.3* against a strict FTP server (Pure-FTPd). The file data is 
> successfully transmitted, but the transfer ends with a {{451 Transfer 
> aborted}} error from the server instead of the expected {{{}226 Transfer 
> complete{}}}.
> This appears to be a timing race condition in the data channel closure 
> sequence. The issue is highly sensitive to network latency: it is 
> reproducible in *low-latency environments* (e.g. FTP client and FTP server in 
> same AWS Same Region), but disappears if network latency is increased (e.g. 
> different AWS regions or adding latency with tc command)
> ----
>  
> *Environment:*
>  * *Library:* Apache Commons Net 3.9.0 (also reproduced on 3.10.0, 3.11.0, 
> 3.12.0)
>  * *Java:* JDK 17
>  * *OS:* Amazon Linux 2023
>  * *Server:* Pure-FTPd 1.0.52 (TLS 1.3 enabled)
>  * 
>  ** Relevant Build Args:
>  * 
>  ** 
>  *** {{--with-tls}} (Standard TLS support)
>  * 
>  ** Relevant Runtime Args:
>  * 
>  ** 
>  *** {{-Y 3}} (Enforce TLS for Control & Data)
>  * 
>  ** 
>  *** {{--tlsciphersuite=HIGH:MEDIUM:!TLSv1:!TLSv1.1:!aNULL}}
> *Comparison:*
> |*Scenario*|*Client/Server Location*|*Protocol*|*Latency*|*Result*|
> |AWS Intra-Region|Same Region (e.g. us-west-2)|TLSv1.3|Low 
> |{color:#de350b}FAILS (451){color}|
> |AWS Inter-Region|Different Regions|TLSv1.3|Not low|WORKS (226)|
> |Artificial Delay|Same Region + {{tc}} delay|TLSv1.3|Not low 
> (artificial)|WORKS (226)|
> |TLS 1.2 Downgrade|Same Region|TLSv1.2|Any (high or low)|WORKS (226)|
> _Note: Attempts to reproduce this using {{localhost}} failed. Reproduction 
> seems to require a public IP / two servers_
> *Reproduction Code (Simplified):*
> {code:java}
> FTPSClient ftpClient = new FTPSClient(false);
> ftpClient.setEnabledProtocols(new String[]{"TLSv1.3"}); // Issue specific to 
> TLS 1.3
> ftpClient.addProtocolCommandListener(new PrintCommandListener(new 
> PrintWriter(System.out), true));
> // Setup
> ftpClient.configure(new FTPClientConfig(FTPClientConfig.SYST_UNIX));
> ftpClient.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager());
> // IMPORTANT: Reproduction requires a real network path (cannot be localhost) 
> String serverPublicIp = "18.98.23.15";
> ftpClient.connect(serverPublicIp, 21);
> ftpClient.login(user, pass);
> ftpClient.execPROT("P");
> ftpClient.enterLocalPassiveMode();
> ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
> // Upload
> // Use a dummy 10KB payload in memory so this is runnable without external 
> files
> try (InputStream input = new ByteArrayInputStream(new byte[10240])) {
>     String remoteFilePath = "privateFileUpload_10KB.txt"; 
>     
>     // Returns false. Server replies with 451.
>     boolean success = ftpClient.storeFile(remoteFilePath, input); 
>     System.out.println("Success status: " + success);
> }{code}
> *Workaround / Additional Context:*
> We found that we could avoid the error by avoiding {{storeFile()}} and 
> manually implementing the transfer using {{{}storeFileStream(){}}}. 
> Specifically, using {{input.transferTo(output)}} inside a try-with-resources 
> block (to ensure {{{}close(){}}}), followed by 
> {{{}completePendingCommand(){}}}, results in a successful transfer (226) in 
> the same TLSv1.3/low latency environment where {{storeFile()}} fails.
> ----
>  
> *Logs:*
> *1. Client Protocol Log (Commons Net):* The client sends the data, but 
> receives a 451 failure immediately after the transfer.
> {code:java}
> ...
> PASV
> 227 Entering Passive Mode (18,98,23,15,218,245)
> STOR //privateFileUpload_10KB.txt
> 150 Accepted data connection
> 451-Transfer aborted
> 451 0.084 seconds (measured here), 119.71 Kbytes per second 
> ...{code}
> *2. Server Log (Pure-FTPd):* The server acknowledges the upload size (10240 
> bytes) but flags the transfer as aborted, likely due to a socket closure race 
> condition.
> {code:java}
> [INFO] TLS: Enabled TLSv1.3 with TLS_AES_256_GCM_SHA384, 256 secret bits 
> cipher
> [DEBUG] 150 Accepted data connection
> [NOTICE] privateFileUpload_10KB.txt uploaded (10240 bytes, 119.71KB/sec)
> [DEBUG] 451-Transfer aborted {code}
> ----
>  
> It appears {{commons-net}} is closing the underlying socket or data stream 
> too aggressively for TLS 1.3 strictness. If {{commons-net}} closes the TCP 
> socket before the server has processed the TLS closure, strict servers like 
> {{pure-ftpd}} interpret this as a premature disconnection ("Transfer 
> aborted").
> Since this works on TLS 1.2 and high-latency connections, it suggests a 
> timing race between the Java client's TCP FIN and the TLS protocol shutdown.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to