diff --git a/Makefile b/Makefile index 2a2a21ff9..65e305f26 100644 --- a/Makefile +++ b/Makefile @@ -867,7 +867,7 @@ OBJS = src/cfgparse.o src/proto_http.o src/stats.o src/server.o src/stream.o \ src/regex.o src/queue.o src/frontend.o src/arg.o src/proto_uxst.o \ src/raw_sock.o src/lb_chash.o src/lb_fwlc.o src/lb_fwrr.o \ src/lb_fas.o src/applet.o src/hdr_idx.o src/ev_select.o src/hash.o \ - src/lb_map.o src/base64.o src/sha1.o src/protocol.o src/h1.o \ + src/lb_map.o src/base64.o src/sha1.o src/protocol.o src/h1.o src/h2.o \ src/action.o src/hathreads.o src/mux_pt.o src/cache.o src/shctx.o EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o $(EBTREE_DIR)/eb32sctree.o \ diff --git a/include/common/h2.h b/include/common/h2.h index 0521b11a9..65c5ab1c8 100644 --- a/include/common/h2.h +++ b/include/common/h2.h @@ -5,27 +5,67 @@ * Copyright (C) 2000-2017 Willy Tarreau - w@1wt.eu * Copyright (C) 2017 HAProxy Technologies * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation, version 2.1 - * exclusively. + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef _COMMON_H2_H #define _COMMON_H2_H #include +#include +#include +/* indexes of most important pseudo headers can be simplified to an almost + * linear array by dividing the index by 2 for all values from 1 to 9, and + * caping to 4 for values up to 14 ; thus it fits in a single 24-bit array + * shifted by 3 times the index value/2, or a 32-bit array shifted by 4x. + * Don't change these values, they are assumed by hpack_idx_to_phdr(). There + * is an entry for the Host header field which is not a pseudo-header but + * needs to be tracked as we should only use :authority if it's absent. + */ +enum { + H2_PHDR_IDX_NONE = 0, + H2_PHDR_IDX_AUTH = 1, /* :authority = 1 */ + H2_PHDR_IDX_METH = 2, /* :method = 2..3 */ + H2_PHDR_IDX_PATH = 3, /* :path = 4..5 */ + 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_NUM_ENTRIES /* must be last */ +}; + +/* bit fields indicating the pseudo-headers found. It also covers the HOST + * header field as well as any non-pseudo-header field (NONE). + */ +enum { + H2_PHDR_FND_NONE = 1 << H2_PHDR_IDX_NONE, /* found a regular header */ + H2_PHDR_FND_AUTH = 1 << H2_PHDR_IDX_AUTH, + H2_PHDR_FND_METH = 1 << H2_PHDR_IDX_METH, + H2_PHDR_FND_PATH = 1 << H2_PHDR_IDX_PATH, + 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, +}; + /* frame types, from the standard */ enum h2_ft { H2_FT_DATA = 0x00, // RFC7540 #6.1 @@ -104,6 +144,11 @@ enum h2_err { "\x54\x50\x2f\x32\x2e\x30\x0d\x0a" \ "\x0d\x0a\x53\x4d\x0d\x0a\x0d\x0a" + +/* various protocol processing functions */ + +int h2_make_h1_request(struct http_hdr *list, char *out, int osize); + /* * Some helpful debugging functions. */ @@ -125,6 +170,27 @@ static inline const char *h2_ft_str(int type) } } +/* returns the pseudo-header corresponds to among H2_PHDR_IDX_*, 0 if not a + * pseudo-header, or -1 if not a valid pseudo-header. + */ +static inline int h2_str_to_phdr(const struct ist str) +{ + if (*str.ptr == ':') { + if (isteq(str, ist(":path"))) return H2_PHDR_IDX_PATH; + else if (isteq(str, ist(":method"))) return H2_PHDR_IDX_METH; + 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; + + /* all other names starting with ':' */ + return -1; + } + + /* not a pseudo header */ + return 0; +} + + #endif /* _COMMON_H2_H */ /* diff --git a/src/h2.c b/src/h2.c new file mode 100644 index 000000000..28518a6bf --- /dev/null +++ b/src/h2.c @@ -0,0 +1,226 @@ +/* + * HTTP/2 protocol processing + * + * Copyright 2017 Willy Tarreau + * Copyright (C) 2017 HAProxy Technologies + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + + +/* Prepare the request line into <*ptr> (stopping at ) from pseudo headers + * stored in . indicates what was found so far. This should be + * called once at the detection of the first general header field or at the end + * of the request if no general header field was found yet. Returns 0 on success + * or a negative error code on failure. + */ +static int h2_prepare_h1_reqline(uint32_t fields, struct ist *phdr, char **ptr, char *end) +{ + char *out = *ptr; + int uri_idx = H2_PHDR_IDX_PATH; + + 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; + } + else if (!(fields & H2_PHDR_FND_AUTH)) { + /* missing authority */ + goto fail; + } + // otherwise OK ; let's use the authority instead of the URI + uri_idx = H2_PHDR_IDX_AUTH; + } + else if ((fields & (H2_PHDR_FND_METH|H2_PHDR_FND_SCHM|H2_PHDR_FND_PATH)) != + (H2_PHDR_FND_METH|H2_PHDR_FND_SCHM|H2_PHDR_FND_PATH)) { + /* RFC 7540 #8.1.2.3 : all requests MUST include exactly one + * valid value for the ":method", ":scheme" and ":path" phdr + * unless it is a CONNECT request. + */ + if (!(fields & H2_PHDR_FND_METH)) { + /* missing method */ + goto fail; + } + else if (!(fields & H2_PHDR_FND_SCHM)) { + /* missing scheme */ + goto fail; + } + else { + /* missing path */ + goto fail; + } + } + + if (out + phdr[uri_idx].len + 1 + phdr[uri_idx].len + 11 > end) { + /* too large */ + goto fail; + } + + memcpy(out, phdr[H2_PHDR_IDX_METH].ptr, phdr[H2_PHDR_IDX_METH].len); + out += phdr[H2_PHDR_IDX_METH].len; + *(out++) = ' '; + + memcpy(out, phdr[uri_idx].ptr, phdr[uri_idx].len); + out += phdr[uri_idx].len; + memcpy(out, " HTTP/1.1\r\n", 11); + out += 11; + + *ptr = out; + return 0; + fail: + return -1; +} + +/* Takes an H2 request present in the headers list terminated by a name + * being and emits the equivalent HTTP/1.1 request according to the + * rules documented in RFC7540 #8.1.2. The output contents are emitted in + * for a max of bytes, and the amount of bytes emitted is returned. In + * case of error, a negative error code is returned. + * + * The headers list must be composed of : + * - n.name != NULL, n.len > 0 : literal header name + * - n.name == NULL, n.len > 0 : indexed pseudo header name number + * among H2_PHDR_IDX_* + * - n.name ignored, n.len == 0 : end of list + * - in all cases except the end of list, v.name and v.len must designate a + * valid value. + */ +int h2_make_h1_request(struct http_hdr *list, char *out, int osize) +{ + struct ist phdr_val[H2_PHDR_NUM_ENTRIES]; + char *out_end = out + osize; + uint32_t fields; /* bit mask of H2_PHDR_FND_* */ + uint32_t idx; + int phdr; + int ret; + + fields = 0; + for (idx = 0; list[idx].n.len != 0; idx++) { + if (!list[idx].n.ptr) { + /* this is an indexed pseudo-header */ + phdr = list[idx].n.len; + } + else { + /* this can be any type of header */ + phdr = h2_str_to_phdr(list[idx].n); + } + + if (phdr > 0 && phdr < H2_PHDR_NUM_ENTRIES) { + /* insert a pseudo header by its index (in phdr) and value (in value) */ + if (fields & ((1 << phdr) | H2_PHDR_FND_NONE)) { + if (fields & H2_PHDR_FND_NONE) { + /* pseudo header field after regular headers */ + goto fail; + } + else { + /* repeated pseudo header field */ + goto fail; + } + } + fields |= 1 << phdr; + phdr_val[phdr] = list[idx].v; + continue; + } + else if (phdr != 0) { + /* invalid pseudo header -- should never happen here */ + goto fail; + } + + /* regular header field in (name,value) */ + if (!(fields & H2_PHDR_FND_NONE)) { + /* no more pseudo-headers, time to build the request line */ + ret = h2_prepare_h1_reqline(fields, phdr_val, &out, out_end); + if (ret != 0) + goto leave; + fields |= H2_PHDR_FND_NONE; + } + + if (isteq(list[idx].n, ist("host"))) + fields |= H2_PHDR_FND_HOST; + + if (out + list[idx].n.len + 2 + list[idx].v.len + 2 > out_end) { + /* too large */ + goto fail; + } + + /* copy "name: value" */ + memcpy(out, list[idx].n.ptr, list[idx].n.len); + out += list[idx].n.len; + *(out++) = ':'; + *(out++) = ' '; + + memcpy(out, list[idx].v.ptr, list[idx].v.len); + out += list[idx].v.len; + *(out++) = '\r'; + *(out++) = '\n'; + } + + /* Let's dump the request now if not yet emitted. */ + if (!(fields & H2_PHDR_FND_NONE)) { + ret = h2_prepare_h1_reqline(fields, phdr_val, &out, out_end); + if (ret != 0) + goto leave; + } + + /* complete with missing Host if needed */ + if ((fields & (H2_PHDR_FND_HOST|H2_PHDR_FND_AUTH)) == H2_PHDR_FND_AUTH) { + /* missing Host field, use :authority instead */ + if (out + 6 + phdr_val[H2_PHDR_IDX_AUTH].len + 2 > out_end) { + /* too large */ + goto fail; + } + + memcpy(out, "host: ", 6); + memcpy(out + 6, phdr_val[H2_PHDR_IDX_AUTH].ptr, phdr_val[H2_PHDR_IDX_AUTH].len); + out += 6 + phdr_val[H2_PHDR_IDX_AUTH].len; + *(out++) = '\r'; + *(out++) = '\n'; + } + + /* And finish */ + if (out + 2 > out_end) { + /* too large */ + goto fail; + } + + *(out++) = '\r'; + *(out++) = '\n'; + ret = out + osize - out_end; + leave: + return ret; + + fail: + return -1; +}