From ed4780553fd05bb8ed8a29462698090482be3393 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Tue, 26 Apr 2022 20:53:35 +0200 Subject: [PATCH] Fix TCP address with options connect-timeout and retry --- CHANGES | 6 ++++++ test.sh | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ xio-socket.c | 18 +++++++++++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 584d936..a0ede59 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,12 @@ Corrections: Reason was not handling EAGAIN on recvmsg(). Thanks to Jamie McQuillan for reporting this issue. + Address TCP with options connect-timeout and retry terminated + immediately when a connection attempt failed on network error or + connection refused. + Test: TCP_TIMEOUT_RETRY + Thanks to Kamil Holubicki for reporting this issue. + Porting: OpenSSL, at least 1.1 on Ubuntu, crashed with SIGSEGV under certain conditions: client connection to server with certificate with empty diff --git a/test.sh b/test.sh index c671bcc..e1c6b83 100755 --- a/test.sh +++ b/test.sh @@ -15403,6 +15403,60 @@ directory gopen orphaned gopen " + +# Test TCP with options connect-timeout and retry. +# Up to 1.7.4.3 this terminated immediately on connection refused +NAME=TCP_TIMEOUT_RETRY +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%tcp%*|*%socket%*|*%$NAME%*) +TEST="$NAME: TCP with options connect-timeout and retry" +# In background run a delayed echo server +# In foreground start TCP with connect-timeout and retry. On first attempt the +# server is not listening; when socat makes a second attempt that succeeds, the +# bug is absent and the test succeeded. +if ! eval $NUMCOND; then :; else +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD0="sleep 1 && $TRACE $SOCAT $opts TCP-L:$PORT,reuseaddr PIPE" +CMD1="$TRACE $SOCAT $opts - TCP:$LOCALHOST:$PORT,connect-timeout=2,retry=1,interval=2" +printf "test $F_n $TEST... " $N +eval "$CMD0" >/dev/null 2>"${te}0" & +pid0=$! +echo "$da" |$CMD1 >"${tf}1" 2>"${te}1" +rc1=$? +kill $pid0 2>/dev/null; wait +if [ $rc1 -ne 0 ]; then + $PRINTF "$FAILED\n" + echo "$CMD0 &" >&2 + cat "${te}0" >&2 + echo "$CMD1" >&2 + cat "${te}1" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +elif echo "$da" |diff - "${tf}1" >$tdiff; then + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then + echo "$CMD0 &" >&2 + echo "$CMD1" >&2 + fi + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0 &" >&2 + cat "${te}0" >&2 + echo "$CMD1" >&2 + cat "${te}1" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) + # end of common tests ################################################################################## diff --git a/xio-socket.c b/xio-socket.c index 15d4a22..7d89874 100644 --- a/xio-socket.c +++ b/xio-socket.c @@ -903,6 +903,8 @@ int _xioopen_connect(struct single *xfd, union sockaddr_union *us, size_t uslen, xfd->para.socket.connect_timeout.tv_usec != 0) { struct timeval timeout; struct pollfd writefd; + int err; + socklen_t errlen = sizeof(err); int result; Info4("connect(%d, %s, "F_Zd"): %s", @@ -938,7 +940,21 @@ int _xioopen_connect(struct single *xfd, union sockaddr_union *us, size_t uslen, #endif return STAT_RETRYLATER; } - /* otherwise OK */ + /* otherwise OK or network error */ + result = Getsockopt(xfd->fd, SOL_SOCKET, SO_ERROR, &err, &errlen); + if (result != 0) { + Msg2(level, "getsockopt(%d, SOL_SOCKET, SO_ERROR, ...): %s", + xfd->fd, strerror(err)); + return STAT_RETRYLATER; + } + Debug2("getsockopt(%d, SOL_SOCKET, SO_ERROR, { %d }) -> 0", + xfd->fd, err); + if (err != 0) { + Msg4(level, "connect(%d, %s, "F_Zd"): %s", + xfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)), + themlen, strerror(err)); + return STAT_RETRYLATER; + } Fcntl_l(xfd->fd, F_SETFL, fcntl_flags); } else { Warn4("connect(%d, %s, "F_Zd"): %s",