bin/60443: ftp ASCII transfers are too slow

"[email protected] via gnats" <[email protected]>
Newsgroups gmane.os.netbsd.bugs
Message-ID <[email protected]>
>Number:         60443
>Category:       bin
>Synopsis:       ftp ASCII transfers are too slow
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    bin-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Jul 12 08:00:00 +0000 2026
>Originator:     RVP
>Release:        NetBSD/amd64 11.99.6
>Organization:
>Environment:
NetBSD/amd64 11.99.6
>Description:
FTP ASCII transfers seem to interact badly with Nagle's algorithm
because ASCII xfers rely on libc's BUFSIZ (1KB) internal buffer.

If you write 1KB at a time (relatively slowly because you're doing
getc(3)/putc(3) to locale '\n'), then it looks the 200ms Nagle timeout
comes into play.

At that's what it seems to me from looking at the tcpdump packet times.
>How-To-Repeat:
1. Create a big text file.
   dd if=/dev/urandom bs=1m count=100 | sed -n l > in.txt

2. Run ftpd.
   /usr/libexec/ftpd -Ddl

3. Run tcpdump to capture packet times.

4. Get or Put the file--active or passive mode don't matter.
   Reduce `xferbuf' to 8192 to make the xfer crawl real quick.
   Also turn `progress' off. so that interrupts don't confound things.
>Fix:
You need 2 patches. One for ftp(1) and a similar one for ftpd(8).

---START patch ftp(1)---
diff -urN usr.bin/ftp.orig/ftp.c usr.bin/ftp/ftp.c
--- usr.bin/ftp.orig/ftp.c	2026-02-07 03:55:18.973125260 +0000
+++ usr.bin/ftp/ftp.c	2026-02-08 15:51:14.124404272 +0000
@@ -1728,6 +1728,23 @@
 	return (1);
 }
 
+static FILE *
+ftp_fdopen(int fd, const char *lmode)
+{
+	enum { BSIZ = XFERBUFMIN * 4 };	/* 4K (any >MTU) */
+	FILE *fp;
+	void* buf;
+
+	if ((fp = fdopen(fd, lmode)) == NULL)
+		return NULL;
+	if ((buf = ftp_malloc(BSIZ)) == NULL) {
+		fclose(fp);
+		return NULL;	
+	}
+	(void)setvbuf(fp, buf, _IOFBF, BSIZ);
+	return fp;
+}
+
 FILE *
 dataconn(const char *lmode)
 {
@@ -1738,7 +1755,7 @@
 	socklen_t	fromlen;
 
 	if (passivemode)	/* passive data connection */
-		return (fdopen(data, lmode));
+		return (ftp_fdopen(data, lmode));
 
 				/* active mode data connection */
 
@@ -1798,7 +1815,7 @@
 		}
 	}
 #endif
-	return (fdopen(data, lmode));
+	return (ftp_fdopen(data, lmode));
 
  dataconn_failed:
 	(void)close(data);
---END patch ftp(1)---

---START patch ftpd(8)---
diff -urN libexec/ftpd.orig/ftpd.c libexec/ftpd/ftpd.c
--- libexec/ftpd.orig/ftpd.c	2025-06-28 04:00:41.032045996 +0000
+++ libexec/ftpd/ftpd.c	2026-02-08 15:35:04.378214731 +0000
@@ -1917,6 +1917,23 @@
 }
 
 static FILE *
+ftp_fdopen(int fd, const char *lmode)
+{
+	enum { BSIZ = FTP_BUFLEN * 8 };	/* 4KB (any >MTU) */
+	FILE *fp;
+	void* buf;
+
+	if ((fp = fdopen(fd, lmode)) == NULL)
+		return NULL;
+	if ((buf = malloc(BSIZ)) == NULL) {
+		fclose(fp);
+		return NULL;	
+	}
+	(void)setvbuf(fp, buf, _IOFBF, BSIZ);
+	return fp;
+}
+
+static FILE *
 getdatasock(const char *fmode)
 {
 	int		on, s, t, tries;
@@ -1924,7 +1941,7 @@
 
 	on = 1;
 	if (data >= 0)
-		return (fdopen(data, fmode));
+		return (ftp_fdopen(data, fmode));
 	if (! dropprivs) {
 		if (seteuid((uid_t)0) < 0) {
 			syslog(LOG_NOTICE, "getdatasock: can't seteuid 0: %m");
@@ -1979,7 +1996,7 @@
 			syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
 	}
 #endif
-	return (fdopen(s, fmode));
+	return (ftp_fdopen(s, fmode));
  bad:
 		/* Return the real value of errno (close may change it) */
 	t = errno;
@@ -2044,13 +2061,13 @@
 #endif
 		reply(150, "Opening %s mode data connection for '%s'%s.",
 		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
-		return (fdopen(pdata, fmode));
+		return (ftp_fdopen(pdata, fmode));
 	}
 	if (data >= 0) {
 		reply(125, "Using existing data connection for '%s'%s.",
 		    name, sizebuf);
 		usedefault = 1;
-		return (fdopen(data, fmode));
+		return (ftp_fdopen(data, fmode));
 	}
 	if (usedefault)
 		data_dest = his_addr;
---END patch ftpd(8)---
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.