From 49531e45821bbebebbd097337da906042c3e964c Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Tue, 26 Oct 2021 15:28:36 +1100 Subject: [PATCH] Handle HTTP/1.1 pipelined requests Check to see whether there are outstanding requests in the httpd receive buffer after sending the response, and if so, process them. Test that pipelined requests are handled by sending multiple minimal HTTP/1.1 using netcat (nc) and checking that we get back the same number of responses. --- bin/tests/system/conf.sh.in | 3 ++ bin/tests/system/statistics/tests.sh | 8 ++++-- bin/tests/system/statschannel/clean.sh | 1 + bin/tests/system/statschannel/tests.sh | 21 ++++++++++++++ configure.ac | 7 +++++ lib/isc/httpd.c | 40 +++++++++++++++++++++----- 6 files changed, 71 insertions(+), 9 deletions(-) diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in index 06aaad39c2..ff6c6e4b67 100644 --- a/bin/tests/system/conf.sh.in +++ b/bin/tests/system/conf.sh.in @@ -112,6 +112,9 @@ SHELL=@SHELL@ # CURL will be empty if no program was found by configure CURL=@CURL@ +# NC will be empty if no program was found by configure +NC=@NC@ + # XMLLINT will be empty if no program was found by configure XMLLINT=@XMLLINT@ diff --git a/bin/tests/system/statistics/tests.sh b/bin/tests/system/statistics/tests.sh index 4cb8008180..7e0190c604 100644 --- a/bin/tests/system/statistics/tests.sh +++ b/bin/tests/system/statistics/tests.sh @@ -175,8 +175,12 @@ echo_i "checking bind9.xsl vs xml ($n)" if $FEATURETEST --have-libxml2 && [ -x "${CURL}" ] && [ -x "${XSLTPROC}" ] ; then $DIGCMD +notcp +recurse @10.53.0.3 soa . > dig.out.test$n.1 2>&1 $DIGCMD +notcp +recurse @10.53.0.3 soa example > dig.out.test$n.2 2>&1 - ${CURL} http://10.53.0.3:${EXTRAPORT1}/xml/v3 > curl.out.${n}.xml 2>/dev/null || ret=1 - ${CURL} http://10.53.0.3:${EXTRAPORT1}/bind9.xsl > curl.out.${n}.xsl 2>/dev/null || ret=1 + # check multiple requests over the same socket + time1=$($PERL -e 'print time(), "\n";') + ${CURL} --http1.1 -o curl.out.${n}.xml http://10.53.0.3:${EXTRAPORT1}/xml/v3 \ + -o curl.out.${n}.xsl http://10.53.0.3:${EXTRAPORT1}/bind9.xsl 2>/dev/null || ret=1 + time2=$($PERL -e 'print time(), "\n";') + test $((time2 - time1)) -lt 5 || ret=1 ${DIFF} ${TOP_SRCDIR}/bin/named/bind9.xsl curl.out.${n}.xsl || ret=1 ${XSLTPROC} curl.out.${n}.xsl - < curl.out.${n}.xml > xsltproc.out.${n} 2>/dev/null || ret=1 cp curl.out.${n}.xml stats.xml.out || ret=1 diff --git a/bin/tests/system/statschannel/clean.sh b/bin/tests/system/statschannel/clean.sh index 2e13b1a9a5..03cb1bb87d 100644 --- a/bin/tests/system/statschannel/clean.sh +++ b/bin/tests/system/statschannel/clean.sh @@ -28,3 +28,4 @@ rm -f xml.*mem json.*mem rm -f xml.*stats json.*stats rm -f zones zones.out.* zones.json.* zones.xml.* zones.expect.* rm -rf ./__pycache__ +rm -f nc.out* diff --git a/bin/tests/system/statschannel/tests.sh b/bin/tests/system/statschannel/tests.sh index bbbc8a0c97..ce145b7c3e 100644 --- a/bin/tests/system/statschannel/tests.sh +++ b/bin/tests/system/statschannel/tests.sh @@ -374,5 +374,26 @@ if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` n=`expr $n + 1` +if [ -x "${NC}" ] ; then + echo_i "Check HTTP/1.1 pipelined requests are handled ($n)" + ret=0 + ${NC} 10.53.0.3 ${EXTRAPORT1} << EOF > nc.out$n || ret=1 +GET /xml/v3/status HTTP/1.1 +Host: 10.53.0.3:${EXTRAPORT1} + +GET /xml/v3/status HTTP/1.1 +Host: 10.53.0.3:${EXTRAPORT1} +Connection: close + +EOF + lines=$(grep "^HTTP/1.1" nc.out$n | wc -l) + test $lines = 2 || ret=1 + if [ $ret != 0 ]; then echo_i "failed"; fi + status=`expr $status + $ret` + n=`expr $n + 1` +else + echo_i "skipping test as nc not found" +fi + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/configure.ac b/configure.ac index fa1dbae162..17ca8eb60c 100644 --- a/configure.ac +++ b/configure.ac @@ -1256,6 +1256,13 @@ AC_CONFIG_FILES([doc/doxygen/doxygen-input-filter], AC_PATH_PROG(CURL, curl, curl) AC_SUBST(CURL) +# +# Look for nc +# + +AC_PATH_PROGS(NC, nc, nc) +AC_SUBST(NC) + # # IDN support using libidn2 # diff --git a/lib/isc/httpd.c b/lib/isc/httpd.c index 9596b8e0af..0787da8c18 100644 --- a/lib/isc/httpd.c +++ b/lib/isc/httpd.c @@ -403,11 +403,14 @@ process_request(isc_httpd_t *httpd, isc_region_t *region, size_t *buflen) { len = limit; } - memmove(httpd->recvbuf + httpd->recvlen, region->base, len); - httpd->recvlen += len; - httpd->recvbuf[httpd->recvlen] = 0; - *buflen = httpd->recvlen; + if (len > 0U) { + memmove(httpd->recvbuf + httpd->recvlen, region->base, len); + httpd->recvlen += len; + httpd->recvbuf[httpd->recvlen] = 0; + isc_region_consume(region, len); + } httpd->headers = NULL; + *buflen = httpd->recvlen; /* * If we don't find a blank line in our buffer, return that we need @@ -858,10 +861,22 @@ httpd_request(isc_nmhandle_t *handle, isc_result_t eresult, goto cleanup_readhandle; } - result = process_request(httpd, region, &buflen); + result = process_request( + httpd, region == NULL ? &(isc_region_t){ NULL, 0 } : region, + &buflen); if (result == ISC_R_NOTFOUND) { if (buflen < HTTP_RECVLEN - 1) { - /* don't unref, keep reading */ + if (region != NULL) { + /* don't unref, keep reading */ + return; + } + /* + * We have been called from httpd_senddone + * and we need to resume reading. Detach + * readhandle before resuming. + */ + isc_nmhandle_detach(&httpd->readhandle); + isc_nm_resumeread(handle); return; } goto cleanup_readhandle; @@ -950,6 +965,7 @@ httpd_request(isc_nmhandle_t *handle, isc_result_t eresult, * the response headers and store the result in httpd->sendbuffer. */ isc_buffer_dup(mgr->mctx, &httpd->sendbuffer, &httpd->headerbuffer); + isc_buffer_clear(&httpd->headerbuffer); isc_buffer_setautorealloc(httpd->sendbuffer, true); databuffer = (is_compressed ? &httpd->compbuffer : &httpd->bodybuffer); isc_buffer_usedregion(databuffer, &r); @@ -965,6 +981,7 @@ httpd_request(isc_nmhandle_t *handle, isc_result_t eresult, } httpd->recvlen -= httpd->consume; httpd->consume = 0; + httpd->recvbuf[httpd->recvlen] = 0; } /* @@ -1150,7 +1167,16 @@ httpd_senddone(isc_nmhandle_t *handle, isc_result_t result, void *arg) { } httpd->state = RECV; - isc_nm_resumeread(handle); + if (httpd->recvlen != 0) { + /* + * Outstanding requests still exist, start processing + * them. + */ + isc_nmhandle_attach(handle, &httpd->readhandle); + httpd_request(handle, ISC_R_SUCCESS, NULL, httpd->mgr); + } else { + isc_nm_resumeread(handle); + } } isc_result_t