]> granicus.if.org Git - curl/commitdiff
http2: read pending frames (including GOAWAY) in connection-check
authorDaniel Stenberg <daniel@haxx.se>
Mon, 19 Mar 2018 15:28:05 +0000 (16:28 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 21 Mar 2018 23:09:15 +0000 (00:09 +0100)
If a connection has received a GOAWAY frame while not being used, the
function now reads frames off the connection before trying to reuse it
to avoid reusing connections the server has told us not to use.

Reported-by: Alex Baines
Fixes #1967
Closes #2402

lib/http2.c

index c15630eca2868b15ff78e7d336b21c0d3fad8902..6758f9554dfcf79ede2b042e70e1ab4f44e8d0ad 100644 (file)
@@ -41,6 +41,7 @@
 #include "curl_memory.h"
 #include "memdebug.h"
 
+#define H2_BUFSIZE 32768
 #define MIN(x,y) ((x)<(y)?(x):(y))
 
 #if (NGHTTP2_VERSION_NUM < 0x010000)
 #define H2BUGF(x) do { } WHILE_FALSE
 #endif
 
+
+static ssize_t http2_recv(struct connectdata *conn, int sockindex,
+                          char *mem, size_t len, CURLcode *err);
+static bool http2_connisdead(struct connectdata *conn);
+static int h2_session_send(struct Curl_easy *data,
+                           nghttp2_session *h2);
+static int h2_process_pending_input(struct connectdata *conn,
+                                    struct http_conn *httpc,
+                                    CURLcode *err);
+
 /*
  * Curl_http2_init_state() is called when the easy handle is created and
  * allows for HTTP/2 specific init of state.
@@ -164,29 +175,51 @@ static CURLcode http2_disconnect(struct connectdata *conn,
  * Instead, if it is readable, run Curl_connalive() to peek at the socket
  * and distinguish between closed and data.
  */
-static bool http2_connisdead(struct connectdata *check)
+static bool http2_connisdead(struct connectdata *conn)
 {
   int sval;
-  bool ret_val = TRUE;
+  bool dead = TRUE;
 
-  sval = SOCKET_READABLE(check->sock[FIRSTSOCKET], 0);
+  if(conn->bits.close)
+    return TRUE;
+
+  sval = SOCKET_READABLE(conn->sock[FIRSTSOCKET], 0);
   if(sval == 0) {
     /* timeout */
-    ret_val = FALSE;
+    dead = FALSE;
   }
   else if(sval & CURL_CSELECT_ERR) {
     /* socket is in an error state */
-    ret_val = TRUE;
+    dead = TRUE;
   }
   else if(sval & CURL_CSELECT_IN) {
     /* readable with no error. could still be closed */
-    ret_val = !Curl_connalive(check);
+    dead = !Curl_connalive(conn);
+    if(!dead) {
+      /* This happens before we've sent off a request and the connection is
+         not in use by any other thransfer, there shouldn't be any data here,
+         only "protocol frames" */
+      CURLcode result;
+      struct http_conn *httpc = &conn->proto.httpc;
+      ssize_t nread = ((Curl_recv *)httpc->recv_underlying)(
+        conn, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, &result);
+      if(nread != -1) {
+        infof(conn->data,
+              "%d bytes stray data read before trying h2 connection\n",
+              (int)nread);
+        httpc->nread_inbuf = 0;
+        httpc->inbuflen = nread;
+        (void)h2_process_pending_input(conn, httpc, &result);
+      }
+      else
+        /* the read failed so let's say this is dead anyway */
+        dead = TRUE;
+    }
   }
 
-  return ret_val;
+  return dead;
 }
 
-
 static unsigned int http2_conncheck(struct connectdata *check,
                                     unsigned int checks_to_perform)
 {
@@ -1039,8 +1072,6 @@ static ssize_t data_source_read_callback(nghttp2_session *session,
   return nread;
 }
 
-#define H2_BUFSIZE 32768
-
 #ifdef NGHTTP2_HAS_ERROR_CALLBACK
 static int error_callback(nghttp2_session *session,
                           const char *msg,
@@ -1227,9 +1258,6 @@ static int should_close_session(struct http_conn *httpc)
     !nghttp2_session_want_write(httpc->h2);
 }
 
-static int h2_session_send(struct Curl_easy *data,
-                           nghttp2_session *h2);
-
 /*
  * h2_process_pending_input() processes pending input left in
  * httpc->inbuf.  Then, call h2_session_send() to send pending data.