Skip to content

Conversation

ViRb3
Copy link

@ViRb3 ViRb3 commented Oct 2, 2025

Hello!

First thing's first, I want to clarify that this issue was only experienced on macOS. I don't know if Windows is also affected.

Over the past years, I've been observing intermittent instances of 426 Connection closed; transfer aborted. across various FTP servers (TLS and not). Recently, I tried to use Cyberduck to transfer files to my Nintendo Switch where I host a FTP server using Sphaira or FTPd. There I realized that the same intermittent issue now has more of a 50% chance to happen on every single file, rendering Cyberduck basically useless. I tried a variety of other clients and they never experienced such issues, so I just abandoned Cyberduck for this purpose.

However, today, I decided to dig into this properly. For some context, I am connecting over plain FTP, passive mode, over LAN. I noticed that the 426 occurs just at the end of a file transfer (e.g. 10GB file would transfer fine for minutes and then fails at 100%). I sniffed the packets with WireShark and noticed something interesting - Cyberduck was sometimes closing the socket right after the data is sent, before the server ACKs, resulting in RST. Here are dumps of a working and non-working case:

TCP logs
Failing:

1682	5.670201	192.168.1.125	192.168.1.224	FTP	161	Request: STOR TEST_FILE
1683	5.681870	192.168.1.224	192.168.1.125	FTP	77	Response: 150 Ready
1684	5.682013	192.168.1.125	192.168.1.224	TCP	66	50896 → 5000 [ACK] Seq=1386 Ack=1551 Win=131712 Len=0 TSval=3498841635 TSecr=1336843510
1685	5.683191	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
1686	5.683194	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
1687	5.683195	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
1688	5.683196	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
1689	5.683197	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
1690	5.683197	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
1691	5.683198	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
1692	5.683199	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
1693	5.683199	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
1694	5.683200	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
1695	5.684527	192.168.1.125	192.168.1.224	TCP	54	50913 → 44429 [RST, ACK] Seq=14481 Ack=1 Win=131776 Len=0
1696	5.687690	192.168.1.224	192.168.1.125	TCP	66	44429 → 50913 [ACK] Seq=1 Ack=2897 Win=62592 Len=0 TSval=2252654860 TSecr=2684224083
1697	5.687692	192.168.1.224	192.168.1.125	TCP	66	44429 → 50913 [ACK] Seq=1 Ack=5793 Win=59712 Len=0 TSval=2252654860 TSecr=2684224083
1698	5.687795	192.168.1.125	192.168.1.224	TCP	54	50913 → 44429 [RST] Seq=2897 Win=0 Len=0
1699	5.687816	192.168.1.125	192.168.1.224	TCP	54	50913 → 44429 [RST] Seq=5793 Win=0 Len=0
1700	5.688362	192.168.1.224	192.168.1.125	TCP	66	44429 → 50913 [ACK] Seq=1 Ack=8689 Win=64064 Len=0 TSval=2252654861 TSecr=2684224083
1701	5.688442	192.168.1.125	192.168.1.224	TCP	54	50913 → 44429 [RST] Seq=8689 Win=0 Len=0
1702	5.688865	192.168.1.224	192.168.1.125	TCP	66	44429 → 50913 [ACK] Seq=1 Ack=11585 Win=61184 Len=0 TSval=2252654861 TSecr=2684224083
1703	5.688866	192.168.1.224	192.168.1.125	TCP	66	44429 → 50913 [ACK] Seq=1 Ack=14481 Win=58240 Len=0 TSval=2252654861 TSecr=2684224083
1704	5.688949	192.168.1.125	192.168.1.224	TCP	54	50913 → 44429 [RST] Seq=11585 Win=0 Len=0
1705	5.689002	192.168.1.125	192.168.1.224	TCP	54	50913 → 44429 [RST] Seq=14481 Win=0 Len=0
1706	5.689473	192.168.1.224	192.168.1.125	FTP	94	Response: 426 Data connection failed
1707	5.689645	192.168.1.125	192.168.1.224	TCP	66	50896 → 5000 [ACK] Seq=1386 Ack=1579 Win=131712 Len=0 TSval=3498841642 TSecr=1336843516
1738	15.874697	192.168.1.224	192.168.1.125	TCP	66	5000 → 50836 [FIN, ACK] Seq=1 Ack=1 Win=16403 Len=0 TSval=1648215039 TSecr=3465173659
1739	15.874699	192.168.1.224	192.168.1.125	TCP	66	5000 → 50836 [RST, ACK] Seq=2 Ack=1 Win=16403 Len=0 TSval=1648215040 TSecr=3465173659
1740	15.874821	192.168.1.125	192.168.1.224	TCP	66	50836 → 5000 [ACK] Seq=1 Ack=2 Win=2058 Len=0 TSval=3465233565 TSecr=1648215039
1741	15.878489	192.168.1.224	192.168.1.125	TCP	54	5000 → 50836 [RST] Seq=2 Win=0 Len=0

Working:

218	4.607323	192.168.1.125	192.168.1.224	FTP	161	Request: STOR TEST_FILE
219	4.636985	192.168.1.224	192.168.1.125	FTP	77	Response: 150 Ready
220	4.637143	192.168.1.125	192.168.1.224	TCP	66	50976 → 5000 [ACK] Seq=390 Ack=843 Win=131712 Len=0 TSval=3644498421 TSecr=1805593578
221	4.638042	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
222	4.638045	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
223	4.638046	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
224	4.638047	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
225	4.638048	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
226	4.638049	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
227	4.638050	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
228	4.638051	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
229	4.638052	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
230	4.638053	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
232	4.642722	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=2897 Win=62592 Len=0 TSval=2609959962 TSecr=747574150
233	4.642724	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=5793 Win=59712 Len=0 TSval=2609959962 TSecr=747574150
234	4.642725	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=8689 Win=56832 Len=0 TSval=2609959962 TSecr=747574150
235	4.642837	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
236	4.642839	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
237	4.642839	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
238	4.642840	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
239	4.642890	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
240	4.642891	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
241	4.642891	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
242	4.642892	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
243	4.642921	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
244	4.642922	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
245	4.642922	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
246	4.642923	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
247	4.643444	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=11585 Win=62592 Len=0 TSval=2609959963 TSecr=747574150
249	4.643448	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=14481 Win=59712 Len=0 TSval=2609959963 TSecr=747574150
250	4.643541	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
251	4.643542	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
252	4.643543	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
253	4.643543	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
254	4.643576	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
255	4.643577	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
256	4.643577	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
257	4.643578	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
259	4.650031	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=17377 Win=62592 Len=0 TSval=2609959967 TSecr=747574156
260	4.650033	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=20273 Win=59712 Len=0 TSval=2609959967 TSecr=747574156
261	4.650034	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=23169 Win=56832 Len=0 TSval=2609959967 TSecr=747574156
262	4.650036	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=26065 Win=53952 Len=0 TSval=2609959967 TSecr=747574156
263	4.650037	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=28961 Win=51008 Len=0 TSval=2609959969 TSecr=747574156
264	4.650038	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=31857 Win=48128 Len=0 TSval=2609959969 TSecr=747574156
265	4.650039	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=34753 Win=45248 Len=0 TSval=2609959969 TSecr=747574156
266	4.650040	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=37649 Win=42368 Len=0 TSval=2609959969 TSecr=747574156
267	4.650041	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=40545 Win=39424 Len=0 TSval=2609959969 TSecr=747574156
268	4.650043	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=43441 Win=36544 Len=0 TSval=2609959969 TSecr=747574156
269	4.650044	192.168.1.224	192.168.1.125	TCP	66	[TCP Window Update] 32325 → 50984 [ACK] Seq=1 Ack=43441 Win=65536 Len=0 TSval=2609959969 TSecr=747574156
270	4.650193	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
271	4.650195	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
272	4.650196	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
273	4.650197	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
274	4.650233	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
275	4.650234	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
276	4.650235	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
277	4.650236	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
278	4.650249	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
279	4.650250	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
280	4.650251	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
281	4.650252	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
282	4.650267	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
283	4.650268	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
284	4.650269	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
285	4.650270	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
286	4.650287	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
287	4.650288	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
288	4.650289	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
289	4.650289	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
290	4.650306	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
291	4.650307	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
292	4.650307	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
293	4.650308	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
294	4.650318	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
295	4.650364	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
296	4.650365	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
297	4.650366	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
298	4.650366	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
299	4.650367	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
300	4.650367	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
301	4.650368	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
302	4.650368	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
303	4.650369	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
304	4.650369	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
305	4.650370	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
306	4.650371	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
307	4.650371	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
308	4.650372	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
309	4.650373	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
310	4.654428	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=46337 Win=62592 Len=0 TSval=2609959974 TSecr=747574163
311	4.654558	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
312	4.654561	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
313	4.654561	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
314	4.654562	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
315	4.657214	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=49233 Win=59712 Len=0 TSval=2609959974 TSecr=747574163
316	4.657216	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=52129 Win=56832 Len=0 TSval=2609959974 TSecr=747574163
317	4.657217	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=55025 Win=53952 Len=0 TSval=2609959974 TSecr=747574163
318	4.657364	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
319	4.659475	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=57921 Win=62592 Len=0 TSval=2609959975 TSecr=747574163
320	4.659476	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=60817 Win=59712 Len=0 TSval=2609959975 TSecr=747574163
321	4.659477	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=63713 Win=56832 Len=0 TSval=2609959975 TSecr=747574163
322	4.659478	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=66609 Win=53952 Len=0 TSval=2609959975 TSecr=747574163
323	4.659479	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=69505 Win=51008 Len=0 TSval=2609959976 TSecr=747574163
324	4.659480	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=72401 Win=48128 Len=0 TSval=2609959976 TSecr=747574163
325	4.659482	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=75297 Win=45248 Len=0 TSval=2609959977 TSecr=747574163
326	4.659483	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=78193 Win=42368 Len=0 TSval=2609959977 TSecr=747574163
327	4.659484	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=81089 Win=39424 Len=0 TSval=2609959977 TSecr=747574163
328	4.659485	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=83985 Win=36544 Len=0 TSval=2609959977 TSecr=747574163
329	4.659487	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=86881 Win=33664 Len=0 TSval=2609959977 TSecr=747574163
330	4.659488	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=89777 Win=30784 Len=0 TSval=2609959977 TSecr=747574163
331	4.659489	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=92673 Win=27840 Len=0 TSval=2609959977 TSecr=747574163
332	4.659490	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=95569 Win=24960 Len=0 TSval=2609959978 TSecr=747574163
333	4.659491	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=98465 Win=22080 Len=0 TSval=2609959978 TSecr=747574163
334	4.659492	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=101361 Win=19200 Len=0 TSval=2609959978 TSecr=747574163
335	4.659493	192.168.1.224	192.168.1.125	TCP	66	[TCP Window Update] 32325 → 50984 [ACK] Seq=1 Ack=101361 Win=65536 Len=0 TSval=2609959978 TSecr=747574163
336	4.659495	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=104257 Win=62592 Len=0 TSval=2609959979 TSecr=747574168
337	4.659496	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=107153 Win=59712 Len=0 TSval=2609959979 TSecr=747574168
338	4.659692	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
339	4.659695	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
340	4.659696	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
341	4.659697	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
342	4.659699	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
343	4.659700	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
344	4.659701	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
345	4.659702	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
346	4.659846	192.168.1.125	192.168.1.224	FTP-DATA	1514	FTP Data: 1448 bytes (PASV) (STOR TEST_FILE)
347	4.659887	192.168.1.125	192.168.1.224	FTP-DATA	449	FTP Data: 383 bytes (PASV) (STOR TEST_FILE)
348	4.665757	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=110049 Win=64064 Len=0 TSval=2609959984 TSecr=747574170
349	4.665759	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=112945 Win=61184 Len=0 TSval=2609959984 TSecr=747574173
350	4.665760	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=115841 Win=58240 Len=0 TSval=2609959984 TSecr=747574173
351	4.665762	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=118737 Win=55360 Len=0 TSval=2609959984 TSecr=747574173
352	4.665763	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=121633 Win=52480 Len=0 TSval=2609959985 TSecr=747574173
353	4.665764	192.168.1.224	192.168.1.125	TCP	66	32325 → 50984 [ACK] Seq=1 Ack=122017 Win=52096 Len=0 TSval=2609959985 TSecr=747574173
354	4.665766	192.168.1.224	192.168.1.125	FTP	74	Response: 226 OK

The fix in this PR definitely doesn't follow proper OOP - it's merely a PoC - but I think it gets the idea right. We half-close the socket (output only), signaling to the FTP server that we're done here. Then the server ACKs and responds with "226 OK", and only then we full-close the socket (input as well).

I re-ran my previously failing test cases a few hundred times, and all worked fine with this PR! The only caveat is, with this PR transfers to one of the FTP servers has a small delay between a file upload being complete, and a new file transfer starting. I compared with other FTP clients (Forklift on macOS) and there was no such delay. On the other FTP server, neither Cyberduck nor Forklift have this delay.

@ViRb3 ViRb3 requested a review from a team as a code owner October 2, 2025 12:07
@ViRb3 ViRb3 changed the title Fix race condition in FTP close that causes intermittent 426 Connection closed [WIP] Fix race condition in FTP close that causes intermittent 426 Connection closed Oct 2, 2025
Copy link

cla-assistant bot commented Oct 2, 2025

CLA assistant check
All committers have signed the CLA.

@ViRb3 ViRb3 marked this pull request as draft October 2, 2025 12:07
@ViRb3
Copy link
Author

ViRb3 commented Oct 2, 2025

I just ran a test with Cyberduck on Windows - on one of the FTP servers, there are NO issues - hundreds of transfers are fast and don't cause 426. On the other server, the first file just hangs at 100% forever. Other clients (e.g. WinSCP) work fine for both servers. This is likely a separate issue though, so let's ignore it for now.

@dkocher dkocher added the ftp FTP Protocol Implementation label Oct 2, 2025
@ViRb3
Copy link
Author

ViRb3 commented Oct 2, 2025

I'm trying to compile the Windows version with this PR to see if it happens to resolve the hanging issue. However, I'm getting errors on pulling the NuGet dependencies from nuget.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
    <add key="gh-iterate-ch" value="https://nuget.pkg.github.com/iterate-ch/index.json" protocolVersion="3" />
    <add key="gh-ikvmnet" value="https://nuget.pkg.github.com/ikvmnet/index.json" protocolVersion="3" />
  </packageSources>
  <packageSourceMapping>
    <packageSource key="nuget.org">
      <package pattern="IKVM.ByteCode" />
      <package pattern="*" />
    </packageSource>
    <packageSource key="gh-ikvmnet">
      <package pattern="IKVM*" />
    </packageSource>
    <packageSource key="gh-iterate-ch">
      <package pattern="iterate-ch.*" />
    </packageSource>
  </packageSourceMapping>
</configuration>

It gives me 401, and if I put my GH token, it gives 403.

@AliveDevil
Copy link
Contributor

Sorry for the unclear instructions, I'll update the building-part of the readme. In the meantime, make sure to use a "Legacy/Classic" personal access token with at least "read:packages" permissions, then in the Cyberduck repo root use Terminal commands

dotnet nuget update source gh-ikvmnet -u "YourUsername" -p "YourPat"
dotnet nuget update source gh-iterate-ch -u "YourUsername" -p "YourPat"

After that, building with mvn verify should work.
Not sure whether how a "Fine Grained" PAT or typo would reveal itself in the API response.

@ViRb3
Copy link
Author

ViRb3 commented Oct 2, 2025

@AliveDevil thanks, that worked. Two more notes:

  • Needed to use Java 21 instead of Java 17 specified in the guide, otherwise I was getting class version mismatch errors.
  • choco install wixtoolset -y was mandatory, unlike what the guide says, and it also installed fine on Windows 11.

Sadly, even with the changes from this PR applied, the transfer still hangs on one of the FTP servers on Windows. However, I looked at its WireShark dump, and this issue seems directly connected to the one with macOS.

The problem on Windows is that it sometimes doesn't send FIN with the last FTP-DATA packet, so the server naturally waits for more data. The client on the other side is waiting for server response, so it hangs.

I believe that my fix from this PR should have worked here too, but perhaps due to the Windows networking stack and/or IKVM cross-compilation, it has no effect and/or different behavior.

I collected TCP dumps of various FTP clients that work properly, both on macOS and Windows, and they all shared the same flow:

  1. Client sends last data packet with FIN flag to signal EOF. At this point, our output stream is closed
  2. Server continues to ACK data packets, until it reaches the last one and sees its FIN. The server then responds with FIN, ACK, and its output stream is also closed
  3. Client ACKs the server's FIN and only then closes the data socket
  4. Server responds with transfer status (226) on the control socket

To be honest, there might be a better way to achieve a correct flow here. Could this perhaps be a bug somewhere outside of the read/write close() functions? Maybe a race with when data is drained vs. when close is called? I'm not familiar with Cyberduck's codebase. Sadly, as things stand now, Cyberduck's FTP implementation seems very broken on both Mac and Windows.

@ViRb3
Copy link
Author

ViRb3 commented Oct 3, 2025

Good news, I came up with a proper implementation that fixes both Windows and macOS across all the clients I tested. Additionally, the macOS delay side effect is now gone. Will polish it up and update this PR. Ultimately, this turned out to be a bug in Apache Common NET's implementation of FTPClient - more specifically, their socket handling.

@ViRb3
Copy link
Author

ViRb3 commented Oct 3, 2025

Force-pushed the new fix. To summarize, the issue was in Apache Commons NET's SocketOutputStream.close() method, which is called by their FTPClient and by your code in at least FTPReadFeature and FTPWriteFeature:

public class SocketOutputStream extends FilterOutputStream {
    private final Socket socket;

    public SocketOutputStream(Socket socket, OutputStream stream) {
        super(stream);
        this.socket = socket;
    }

    public void close() throws IOException {
        super.close();
        this.socket.close();
    }

    public void write(byte[] buffer, int offset, int length) throws IOException {
        this.out.write(buffer, offset, length);
    }
}

The close() assumes that there is no data in transit and no server ACKs are expected. If the ACKs arrive after the socket is closed, they result in RSTs. On Windows, likely due to the different networking stack and/or IKVM, a FIN is sometimes not sent as a part of super.close(). Maybe the two closures right after each other result in a race within the networking stack?

There are two ways I could think of fixing this - either ensure connections are finalized in all instances where SocketOutputStream.close() is called, or wrap the socket and enforce proper shutdown sequence upon calling its close().

I wasn't sure just how many places would need changing for the first approach, and whether it's possible at all since at least some functions of Apache's FTPClient perform the closure on their own, so you have no control over them. For example, FTPClient.storeFile.

So I opted to wrap the socket instead - since we have a nice hook in the beginning of the connection, the new socket will be used throughout Cyberduck and Apache's code, and anywhere it is closed, proper shutdown will be enforced.

I went a bit heavy with the comments in the code given the low-level and quirky nature. If something doesn't make sense, please let me know. I was also unsure about the socket timeout part, but I will leave a comment in the PR for us to discuss it.

As mentioned in my previous comment, this fix works with all of my FTP servers with no side effects. However, it's worth noting that I only tested plaintext FTP over LAN. I haven't tested FTP+TLS, SFTP, Active mode, etc. I don't expect there to be issues, but you might want to check any more exotic configurations on your end.

@ViRb3 ViRb3 changed the title [WIP] Fix race condition in FTP close that causes intermittent 426 Connection closed [WIP] Fix race conditions in FTP socket closure that cause intermittent errors Oct 3, 2025
@ViRb3 ViRb3 changed the title [WIP] Fix race conditions in FTP socket closure that cause intermittent errors Fix race conditions in FTP socket closure that cause intermittent errors Oct 3, 2025
@ViRb3
Copy link
Author

ViRb3 commented Oct 3, 2025

Alright, with the latest changes, I'm happy to announce that Cyberduck works on all of my test FTP servers, both Windows and macOS, with no performance regressions. I stress-tested all of them with 1,000 small files back-to-back, and there were no issues. What's more, the performance of Cyberduck is significantly better than all other FTP clients that I tested, in both multiple small files and a single large file.

I think the PR is now ready to merge, at least from my view. Please let me know if you have any questions.

@ViRb3 ViRb3 marked this pull request as ready for review October 3, 2025 21:07
finally {
log.debug("Closing socket {}", delegate);
// Work around macOS quirk where Java NIO's SocketDispatcher.close0() has a 1,000ms delay
CompletableFuture.runAsync(() -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running in a background thread and missing any error looks dangerious to me as we will not report any error when closing the stream fails which may indicate that the file is not properly persisted on the remote server.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fully agree. I tried really hard to find a workaround, but couldn't come up with one. Having 1 whole second delay between each file upload is really bad when you upload lots of small files, and it causes Cyberduck to be upwards of 10x slower than competitor FTP clients. If you have any suggestion here, I'm more than happy to take it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I don't think this failing specifically could result in data loss. By the time we've reached here we already flushed our data, closed our stream, and waited for the server to close its stream. So, I don't think failing here could ever result in data loss.

The only case of data loss should occur if "Failed to shutdown output for socket" is hit. Maybe we can change that to throw instead of logging and discarding.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are probably right there is no risk here in the real world. But it should probably not be in the finally block as it does not need to be executed when above code fails with I/O.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that close quirk on macOS specific to server implementations?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is. I really have no explanation why, but it's 100% consistent. Even if IO fails, don't we want to make sure the socket is closed though? Otherwise we might leak it. I'm not sure how Java deals with this specifically.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean do you see this delay regardless of what server implementation you connect to? If it shows only when connected to a specific server implementation we would not want to implement a workaround.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I agree that we will want to close the socket regardless of any previous error.

Copy link
Author

@ViRb3 ViRb3 Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I only see this delay with one specific FTP server. In that server, it happens 100% of the time. I looked at the packet dump and the server returns immediately - the delay is purely on client side with no pending packets. I can look again if there's something that might be triggering it, but no other FTP client has a delay on macOS, so I'm inclined to believe it's a bug that can happen even to other servers.

@dkocher
Copy link
Contributor

dkocher commented Oct 5, 2025

Can you comment what server implementations you tested this changeset with to avoid regressions.

@dkocher
Copy link
Contributor

dkocher commented Oct 5, 2025

Found related documentation in Orderly Versus Abortive Connection Release in Java 1

  • Always consume data from a socket before closing it. This will help avoid the second scenario described above.
  • Use shutdownOutput(). This method has the same effect as close() in that a FIN is sent to indicate that this peer has finished sending, but it is still possible to read from the socket until such time as the remote peer sends a FIN and end-of-file is read from the stream. Then the socket can be closed with Socket.close().

Footnotes

  1. https://docs.oracle.com/javase/8/docs/technotes/guides/net/articles/connection_release.html

@ViRb3
Copy link
Author

ViRb3 commented Oct 5, 2025

Can you comment what server implementations you tested this changeset with to avoid regressions.

I tested with the following FTP servers:

However, I just noticed a peculiar issue exclusively with FileZilla Server - there's insane read amplification. I confirmed that us draining the input on download operations actually triggers the server to re-send the file again and again. I am thinking since we already wrap the input/output streams, we could track how many bytes they've processed. And when we close, if the output bytes > 0, only then we do full close. Does that make sense to you? Only caveat is that if a socket is used for both input and output, and draining the input starts re-sending, we can't avoid this. But at least in FTP, this should not be the case - data sockets are either in only or out only.

@ViRb3
Copy link
Author

ViRb3 commented Oct 5, 2025

Pushed my fix as described above. Seems to work everywhere now, and no more read amplification.

@dkocher
Copy link
Contributor

dkocher commented Oct 6, 2025

Can you comment what server implementations you tested this changeset with to avoid regressions.

We will want to test interoperability with common FTP servers in use such as

  • ProFTPD
  • Pure-FTPd
  • vsftpd

The embedded integration tests run against Apache FTP Server.

Comment on lines +163 to +174
public synchronized InputStream getInputStream() throws IOException {
if(inputStreamWrapper == null) {
inputStreamWrapper = new ProxyInputStream(delegate.getInputStream()) {
@Override
public void close() throws IOException {
// super.close() will call delegate.close(), so override it with ours instead
FTPSocket.this.close();
}
};
}
return inputStreamWrapper;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see we are required to override the implementation here if it should not apply when no data is sent to the server.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because if the socket was used for output but the input stream is closed, that would bypass proper closure.

finally {
log.debug("Closing socket {}", delegate);
// Work around macOS quirk where Java NIO's SocketDispatcher.close0() has a 1,000ms delay
CompletableFuture.runAsync(() -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean do you see this delay regardless of what server implementation you connect to? If it shows only when connected to a specific server implementation we would not want to implement a workaround.

finally {
log.debug("Closing socket {}", delegate);
// Work around macOS quirk where Java NIO's SocketDispatcher.close0() has a 1,000ms delay
CompletableFuture.runAsync(() -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I agree that we will want to close the socket regardless of any previous error.

Comment on lines +52 to +55
private final AtomicInteger outputBytesCount = new AtomicInteger(0);

private InputStream inputStreamWrapper;
private OutputStream outputStreamWrapper;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If counting bytes is really required then reuse org.apache.commons.io.output.CountingOutputStream.

bytesRead++;
}
if(bytesRead > 0) {
log.debug("Drained {} bytes from socket {}", bytesRead, delegate);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log with warn level as unexpected.

* 2. Drain input to wait for ACKs until we receive server FIN
* 3. Close the socket to release resources
*/
public class FTPSocket extends Socket {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extend from socket that delegates only to proxy implementation.

Suggested change
public class FTPSocket extends Socket {
public class FTPSocket extends DelegatingSocket {
package ch.cyberduck.core.socket;

/*
 * Copyright (c) 2002-2025 iterate GmbH. All rights reserved.
 * https://cyberduck.io/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;

public class DelegatingSocket extends Socket {
    protected final Socket delegate;

    public DelegatingSocket(final Socket delegate) {
        this.delegate = delegate;
    }

    @Override
    public void bind(final SocketAddress bindpoint) throws IOException {
        delegate.bind(bindpoint);
    }

    @Override
    public synchronized void close() throws IOException {
        delegate.close();
    }

    @Override
    public void connect(final SocketAddress endpoint) throws IOException {
        delegate.connect(endpoint);
    }

    @Override
    public void connect(final SocketAddress endpoint, final int timeout) throws IOException {
        delegate.connect(endpoint, timeout);
    }

    @Override
    public SocketChannel getChannel() {
        return delegate.getChannel();
    }

    @Override
    public InetAddress getInetAddress() {
        return delegate.getInetAddress();
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return delegate.getInputStream();
    }

    @Override
    public boolean getKeepAlive() throws SocketException {
        return delegate.getKeepAlive();
    }

    @Override
    public InetAddress getLocalAddress() {
        return delegate.getLocalAddress();
    }

    @Override
    public int getLocalPort() {
        return delegate.getLocalPort();
    }

    @Override
    public SocketAddress getLocalSocketAddress() {
        return delegate.getLocalSocketAddress();
    }

    @Override
    public boolean getOOBInline() throws SocketException {
        return delegate.getOOBInline();
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        return delegate.getOutputStream();
    }

    @Override
    public int getPort() {
        return delegate.getPort();
    }

    @Override
    public synchronized int getReceiveBufferSize() throws SocketException {
        return delegate.getReceiveBufferSize();
    }

    @Override
    public SocketAddress getRemoteSocketAddress() {
        return delegate.getRemoteSocketAddress();
    }

    @Override
    public boolean getReuseAddress() throws SocketException {
        return delegate.getReuseAddress();
    }

    @Override
    public synchronized int getSendBufferSize() throws SocketException {
        return delegate.getSendBufferSize();
    }

    @Override
    public int getSoLinger() throws SocketException {
        return delegate.getSoLinger();
    }

    @Override
    public synchronized int getSoTimeout() throws SocketException {
        return delegate.getSoTimeout();
    }

    @Override
    public boolean getTcpNoDelay() throws SocketException {
        return delegate.getTcpNoDelay();
    }

    @Override
    public int getTrafficClass() throws SocketException {
        return delegate.getTrafficClass();
    }

    @Override
    public boolean isBound() {
        return delegate.isBound();
    }

    @Override
    public boolean isClosed() {
        return delegate.isClosed();
    }

    @Override
    public boolean isConnected() {
        return delegate.isConnected();
    }

    @Override
    public boolean isInputShutdown() {
        return delegate.isInputShutdown();
    }

    @Override
    public boolean isOutputShutdown() {
        return delegate.isOutputShutdown();
    }

    @Override
    public void sendUrgentData(final int data) throws IOException {
        delegate.sendUrgentData(data);
    }

    @Override
    public void setKeepAlive(final boolean on) throws SocketException {
        delegate.setKeepAlive(on);
    }

    @Override
    public void setOOBInline(final boolean on) throws SocketException {
        delegate.setOOBInline(on);
    }

    @Override
    public void setPerformancePreferences(final int connectionTime, final int latency, final int bandwidth) {
        delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
    }

    @Override
    public synchronized void setReceiveBufferSize(final int size) throws SocketException {
        delegate.setReceiveBufferSize(size);
    }

    @Override
    public void setReuseAddress(final boolean on) throws SocketException {
        delegate.setReuseAddress(on);
    }

    @Override
    public synchronized void setSendBufferSize(final int size) throws SocketException {
        delegate.setSendBufferSize(size);
    }

    @Override
    public void setSoLinger(final boolean on, final int linger) throws SocketException {
        delegate.setSoLinger(on, linger);
    }

    @Override
    public synchronized void setSoTimeout(final int timeout) throws SocketException {
        delegate.setSoTimeout(timeout);
    }

    @Override
    public void setTcpNoDelay(final boolean on) throws SocketException {
        delegate.setTcpNoDelay(on);
    }

    @Override
    public void setTrafficClass(final int tc) throws SocketException {
        delegate.setTrafficClass(tc);
    }

    @Override
    public void shutdownInput() throws IOException {
        delegate.shutdownInput();
    }

    @Override
    public void shutdownOutput() throws IOException {
        delegate.shutdownOutput();
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("DelegatingSocket{");
        sb.append("delegate=").append(delegate);
        sb.append('}');
        return sb.toString();
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ftp FTP Protocol Implementation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants