diff options
author | Mark Johnston <markj@FreeBSD.org> | 2024-11-03 14:43:38 +0000 |
---|---|---|
committer | Mark Johnston <markj@FreeBSD.org> | 2024-11-03 16:47:38 +0000 |
commit | 79c342aaf86feb4efbd15383f54e4fe7bdc9da7b (patch) | |
tree | ae6d8ef6410dcac511d913efd1f9eb73539acb1a | |
parent | 2b8c3a05e0a63f3ffd6ba0eeca08a97578ec3a38 (diff) |
tftpd: Address flaky tests
The tftpd tests all follow the same pattern:
1. open a UDP socket,
2. fork a child to exec tftpd, which subsequently handles requests on
the socket,
3. use a client socket to send some message to the tftpd daemon.
However, tftpd's first action is to mark its socket as non-blocking and
then read a request from it. If no data is present in the socket, tftpd
exits immediately with an error. So, there is a race; we often see
tftpd test timeouts when running tests in parallel. These timeouts also
arise periodically in CI runs.
One solution is to restructure each test to create the server socket,
then write the request to the client socket, then fork tftpd. This
closes the race. However, this involves a lot of churn.
This patch fixes the problem a different way, by adding a new -b flag to
tftpd which makes it block to read the initial request. Each test is
modified to use -b, closing the race.
Reviewed by: imp, asomers
MFC after: 2 weeks
Differential Revision: https://reviews.freebsd.org/D47404
-rw-r--r-- | libexec/tftpd/tests/functional.c | 2 | ||||
-rw-r--r-- | libexec/tftpd/tftpd.8 | 14 | ||||
-rw-r--r-- | libexec/tftpd/tftpd.c | 19 |
3 files changed, 25 insertions, 10 deletions
diff --git a/libexec/tftpd/tests/functional.c b/libexec/tftpd/tests/functional.c index 54ace29d718c..791aa9190a2f 100644 --- a/libexec/tftpd/tests/functional.c +++ b/libexec/tftpd/tests/functional.c @@ -323,6 +323,7 @@ setup(struct sockaddr_storage *to, uint16_t idx) { int client_s, server_s, pid, argv_idx; char execname[] = "/usr/libexec/tftpd"; + char b_flag_str[] = "-b"; char s_flag_str[] = "-s"; char w_flag_str[] = "-w"; char pwd[MAXPATHLEN]; @@ -382,6 +383,7 @@ setup(struct sockaddr_storage *to, uint16_t idx) bzero(argv, sizeof(argv)); argv[0] = execname; argv_idx = 1; + argv[argv_idx++] = b_flag_str; if (w_flag) argv[argv_idx++] = w_flag_str; if (s_flag) diff --git a/libexec/tftpd/tftpd.8 b/libexec/tftpd/tftpd.8 index 558f7bdf809a..0118198da53d 100644 --- a/libexec/tftpd/tftpd.8 +++ b/libexec/tftpd/tftpd.8 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 8, 2024 +.Dd November 3, 2024 .Dt TFTPD 8 .Os .Sh NAME @@ -33,7 +33,7 @@ .Nd Internet Trivial File Transfer Protocol server .Sh SYNOPSIS .Nm tftpd -.Op Fl CcdlnoSw +.Op Fl bCcdlnoSw .Op Fl F Ar strftime-format .Op Fl s Ar directory .Op Fl U Ar umask @@ -119,6 +119,16 @@ option is specified. .Pp The options are: .Bl -tag -width Ds +.It Fl b +By default, +.Nm +expects an initial message to be available on its input socket. +If no data is available, the server exits immediately. +If +.Fl b +is specified, +.Nm +will block waiting for the initial message. .It Fl c Changes the default root directory of a connecting host via .Xr chroot 2 diff --git a/libexec/tftpd/tftpd.c b/libexec/tftpd/tftpd.c index a51fb4742985..f8f9bd549a2e 100644 --- a/libexec/tftpd/tftpd.c +++ b/libexec/tftpd/tftpd.c @@ -121,15 +121,18 @@ main(int argc, char *argv[]) struct passwd *nobody; const char *chuser = "nobody"; char recvbuffer[MAXPKTSIZE]; - int allow_ro = 1, allow_wo = 1, on = 1; + int allow_ro = 1, allow_wo = 1, block = 0, on = 1; pid_t pid; tzset(); /* syslog in localtime */ acting_as_client = 0; tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); - while ((ch = getopt(argc, argv, "cCd::F:lnoOp:s:Su:U:wW")) != -1) { + while ((ch = getopt(argc, argv, "bcCd::F:lnoOp:s:Su:U:wW")) != -1) { switch (ch) { + case 'b': + block = 1; + break; case 'c': ipchroot = 1; break; @@ -213,14 +216,9 @@ main(int argc, char *argv[]) umask(mask); - if (ioctl(0, FIONBIO, &on) < 0) { - tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); - exit(1); - } - /* Find out who we are talking to and what we are going to do */ peerlen = sizeof(peer_sock); - n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, + n = recvfrom(0, recvbuffer, MAXPKTSIZE, block ? 0 : MSG_DONTWAIT, (struct sockaddr *)&peer_sock, &peerlen); if (n < 0) { tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno)); @@ -234,6 +232,11 @@ main(int argc, char *argv[]) exit(1); } + if (ioctl(0, FIONBIO, &on) < 0) { + tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); + exit(1); + } + /* * Now that we have read the message out of the UDP * socket, we fork and exit. Thus, inetd will go back |