]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: h2: parse Extended CONNECT request to htx
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 11 Dec 2020 16:53:09 +0000 (17:53 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Thu, 28 Jan 2021 15:37:14 +0000 (16:37 +0100)
Support for the rfc 8441 Bootstraping WebSockets with HTTP/2

Convert an Extended CONNECT HTTP/2 request into a htx representation.
The htx message uses the GET method with an Upgrade header field to be
fully compatible with the equivalent HTTP/1.1 Upgrade mechanism.

The Extended CONNECT is of the following form :

:method = CONNECT
:protocol = websocket
:scheme = https
:path = /chat
:authority = server.example.com

The new pseudo-header :protocol has been defined and is used to identify
an Extended CONNECT method. Contrary to standard CONNECT, Extended
CONNECT must have :scheme, :path and :authority defined.

include/haproxy/h2.h
src/h2.c

index 6bf9df9ccee0d32db10016579764b83337b1a54f..34ad4de345924477ec9d2408aaafabeb5c210a62 100644 (file)
@@ -50,6 +50,7 @@ enum {
        H2_PHDR_IDX_SCHM = 4, /* :scheme    = 6..7  */
        H2_PHDR_IDX_STAT = 5, /* :status    = 8..14 */
        H2_PHDR_IDX_HOST = 6, /* Host, never returned, just a place-holder */
+       H2_PHDR_IDX_PROT = 7, /* :protocol from rfc 8441 Extended Connect */
        H2_PHDR_NUM_ENTRIES   /* must be last */
 };
 
@@ -64,6 +65,7 @@ enum {
        H2_PHDR_FND_SCHM = 1 << H2_PHDR_IDX_SCHM,
        H2_PHDR_FND_STAT = 1 << H2_PHDR_IDX_STAT,
        H2_PHDR_FND_HOST = 1 << H2_PHDR_IDX_HOST,
+       H2_PHDR_FND_PROT = 1 << H2_PHDR_IDX_PROT,
 };
 
 /* frame types, from the standard */
@@ -305,6 +307,7 @@ static inline int h2_str_to_phdr(const struct ist str)
                else if (isteq(str, ist(":scheme")))    return H2_PHDR_IDX_SCHM;
                else if (isteq(str, ist(":status")))    return H2_PHDR_IDX_STAT;
                else if (isteq(str, ist(":authority"))) return H2_PHDR_IDX_AUTH;
+               else if (isteq(str, ist(":protocol")))  return H2_PHDR_IDX_PROT;
 
                /* all other names starting with ':' */
                return -1;
index 9154bd50d3b59bb4a5b2d4ada89d8e6a43d3f01e..de71203b2f30eed94c40b4e8b5fa1abd9814cb90 100644 (file)
--- a/src/h2.c
+++ b/src/h2.c
@@ -173,28 +173,64 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon
  */
 static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr, struct htx *htx, unsigned int *msgf)
 {
-       struct ist uri;
+       struct ist uri, meth_sl;
        unsigned int flags = HTX_SL_F_NONE;
        struct htx_sl *sl;
        size_t i;
 
        if ((fields & H2_PHDR_FND_METH) && isteq(phdr[H2_PHDR_IDX_METH], ist("CONNECT"))) {
-               /* RFC 7540 #8.2.6 regarding CONNECT: ":scheme" and ":path"
-                * MUST be omitted ; ":authority" contains the host and port
-                * to connect to.
-                */
-               if (fields & H2_PHDR_FND_SCHM) {
-                       /* scheme not allowed */
-                       goto fail;
-               }
-               else if (fields & H2_PHDR_FND_PATH) {
-                       /* path not allowed */
-                       goto fail;
+               if (fields & H2_PHDR_FND_PROT) {
+                       /* rfc 8441 Extended Connect Protocol
+                        * #4 :scheme and :path must be present, as well as
+                        * :authority like all h2 requests
+                        */
+                       if (!(fields & H2_PHDR_FND_SCHM)) {
+                               /* missing scheme */
+                               goto fail;
+                       }
+                       else if (!(fields & H2_PHDR_FND_PATH)) {
+                               /* missing path */
+                               goto fail;
+                       }
+                       else if (!(fields & H2_PHDR_FND_AUTH)) {
+                               /* missing authority */
+                               goto fail;
+                       }
+
+                       flags |= HTX_SL_F_HAS_SCHM;
+                       if (isteqi(phdr[H2_PHDR_IDX_SCHM], ist("http")))
+                               flags |= HTX_SL_F_SCHM_HTTP;
+                       else if (isteqi(phdr[H2_PHDR_IDX_SCHM], ist("https")))
+                               flags |= HTX_SL_F_SCHM_HTTPS;
+
+                       meth_sl = ist("GET");
+
+                       *msgf |= H2_MSGF_EXT_CONNECT;
+                       /* no ES on the HEADERS frame but no body either for
+                        * Extended CONNECT */
+                       *msgf &= ~H2_MSGF_BODY;
                }
-               else if (!(fields & H2_PHDR_FND_AUTH)) {
-                       /* missing authority */
-                       goto fail;
+               else {
+                       /* RFC 7540 #8.2.6 regarding CONNECT: ":scheme" and ":path"
+                        * MUST be omitted ; ":authority" contains the host and port
+                        * to connect to.
+                        */
+                       if (fields & H2_PHDR_FND_SCHM) {
+                               /* scheme not allowed */
+                               goto fail;
+                       }
+                       else if (fields & H2_PHDR_FND_PATH) {
+                               /* path not allowed */
+                               goto fail;
+                       }
+                       else if (!(fields & H2_PHDR_FND_AUTH)) {
+                               /* missing authority */
+                               goto fail;
+                       }
+
+                       meth_sl = phdr[H2_PHDR_IDX_METH];
                }
+
                *msgf |= H2_MSGF_BODY_TUNNEL;
        }
        else if ((fields & (H2_PHDR_FND_METH|H2_PHDR_FND_SCHM|H2_PHDR_FND_PATH)) !=
@@ -230,6 +266,8 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr,
                        flags |= HTX_SL_F_SCHM_HTTP;
                else if (isteqi(phdr[H2_PHDR_IDX_SCHM], ist("https")))
                        flags |= HTX_SL_F_SCHM_HTTPS;
+
+               meth_sl = phdr[H2_PHDR_IDX_METH];
        }
 
        if (!(flags & HTX_SL_F_HAS_SCHM)) {
@@ -289,11 +327,11 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr,
        flags |= HTX_SL_F_VER_11;    // V2 in fact
        flags |= HTX_SL_F_XFER_LEN;  // xfer len always known with H2
 
-       sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, phdr[H2_PHDR_IDX_METH], uri, ist("HTTP/2.0"));
+       sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth_sl, uri, ist("HTTP/2.0"));
        if (!sl)
                goto fail;
 
-       sl->info.req.meth = find_http_meth(phdr[H2_PHDR_IDX_METH].ptr, phdr[H2_PHDR_IDX_METH].len);
+       sl->info.req.meth = find_http_meth(meth_sl.ptr, meth_sl.len);
        if (sl->info.req.meth == HTTP_METH_HEAD)
                *msgf |= H2_MSGF_BODYLESS_RSP;
        return sl;
@@ -457,6 +495,14 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms
                htx->flags |= HTX_FL_EOM;
        }
 
+       if (*msgf & H2_MSGF_EXT_CONNECT) {
+               if (!htx_add_header(htx, ist("upgrade"), phdr_val[H2_PHDR_IDX_PROT]))
+                       goto fail;
+               if (!htx_add_header(htx, ist("connection"), ist("upgrade")))
+                       goto fail;
+               sl_flags |= HTX_SL_F_CONN_UPG;
+       }
+
        /* update the start line with last detected header info */
        sl->flags |= sl_flags;