Quellcode durchsuchen

Restrict HTTP/2 to ALPN

There are different methods to initialize a HTTP/2 connection:
Using HTTP/1.1 upgrades, using HTTP/2 with prior knowledge, or using ALPN.
ALPN requires TLS (HTTPS).

Since many (all?) major browsers support HTTP/2 only for HTTPS (only for ALPN?),
implementing other methods has a much lower priority.
Using only one method makes implementation also much simpler.
bel2125 vor 5 Jahren
Ursprung
Commit
06df330b4a
1 geänderte Dateien mit 92 neuen und 80 gelöschten Zeilen
  1. 92 80
      src/civetweb.c

+ 92 - 80
src/civetweb.c

@@ -1628,6 +1628,7 @@ struct mg_workerTLS {
 	HANDLE pthread_cond_helper_mutex;
 	struct mg_workerTLS *next_waiting_thread;
 #endif
+	const char *alpn_proto;
 #if defined(MG_ALLOW_USING_GET_REQUEST_INFO_FOR_RESPONSE)
 	char txtbuf[4];
 #endif
@@ -2856,8 +2857,15 @@ enum {
 	CONNECTION_TYPE_RESPONSE
 };
 
+enum {
+	PROTOCOL_TYPE_HTTP1 = 0,
+	PROTOCOL_TYPE_WEBSOCKET = 1,
+	PROTOCOL_TYPE_HTTP2 = 2
+};
+
 struct mg_connection {
 	int connection_type; /* see CONNECTION_TYPE_* above */
+	int protocol_type;   /* see PROTOCOL_TYPE_*: 0=http/1.x, 1=ws, 2=http/2 */
 
 	struct mg_request_info request_info;
 	struct mg_response_info response_info;
@@ -2937,13 +2945,6 @@ struct de {
 };
 
 
-#if defined(USE_WEBSOCKET)
-static int is_websocket_protocol(const struct mg_connection *conn);
-#else
-#define is_websocket_protocol(conn) (0)
-#endif
-
-
 #define mg_cry_internal(conn, fmt, ...)                                        \
 	mg_cry_internal_wrap(conn, NULL, __func__, __LINE__, fmt, __VA_ARGS__)
 
@@ -4026,9 +4027,9 @@ get_proto_name(const struct mg_connection *conn)
 
 	const struct mg_request_info *ri = &conn->request_info;
 
-	const char *proto =
-	    (is_websocket_protocol(conn) ? (ri->is_ssl ? "wss" : "ws")
-	                                 : (ri->is_ssl ? "https" : "http"));
+	const char *proto = ((conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)
+	                         ? (ri->is_ssl ? "wss" : "ws")
+	                         : (ri->is_ssl ? "https" : "http"));
 
 	return proto;
 
@@ -7748,7 +7749,7 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
 /* Step 3: Check if it is a websocket request, and modify the document
  * root if required */
 #if defined(USE_WEBSOCKET)
-	*is_websocket_request = is_websocket_protocol(conn);
+	*is_websocket_request = (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET);
 #if !defined(NO_FILES)
 	if (*is_websocket_request && conn->dom_ctx->config[WEBSOCKET_ROOT]) {
 		root = conn->dom_ctx->config[WEBSOCKET_ROOT];
@@ -10806,7 +10807,6 @@ parse_http_request(char *buf, int len, struct mg_request_info *ri)
 {
 	int request_length;
 	int init_skip = 0;
-	int is_http2 = 0;
 
 	/* Reset attributes. DO NOT TOUCH is_ssl, remote_addr,
 	 * remote_port */
@@ -10844,13 +10844,6 @@ parse_http_request(char *buf, int len, struct mg_request_info *ri)
 		return -1;
 	}
 
-#if defined(USE_HTTP2)
-	static int is_http2_header(const char *buf, size_t buflen);
-	if (is_http2_header(buf, request_length)) {
-		is_http2 = 1;
-	}
-#endif
-
 	/* The first word has to be the HTTP method */
 	ri->request_method = buf;
 
@@ -10858,14 +10851,6 @@ parse_http_request(char *buf, int len, struct mg_request_info *ri)
 		return -1;
 	}
 
-	/* Check for a valid http method */
-	if (!is_valid_http_method(ri->request_method)) {
-		/* "PRI" is valid for HTTP/2 connection initialization */
-		if (!is_http2) {
-			return -1;
-		}
-	}
-
 	/* The second word is the URI */
 	ri->request_uri = buf;
 
@@ -13311,10 +13296,17 @@ handle_websocket_request(struct mg_connection *conn,
 		ws_close_handler(conn, cbData);
 	}
 }
+#endif /* !USE_WEBSOCKET */
 
 
+/* Is upgrade request:
+ *   0 = regular HTTP/1.0 or HTTP/1.1 request
+ *   1 = upgrade to websocket
+ *   2 = upgrade to HTTP/2
+ * -1 = upgrade to unknown protocol
+ */
 static int
-is_websocket_protocol(const struct mg_connection *conn)
+should_switch_to_protocol(const struct mg_connection *conn)
 {
 	const char *upgrade, *connection;
 
@@ -13324,35 +13316,37 @@ is_websocket_protocol(const struct mg_connection *conn)
 	 * Upgrade: Websocket
 	 */
 
-	upgrade = mg_get_header(conn, "Upgrade");
-	if (upgrade == NULL) {
-		return 0; /* fail early, don't waste time checking other header
-		           * fields
-		           */
-	}
-	DEBUG_TRACE("Upgrade: %s", upgrade);
-	if (!mg_strcasestr(upgrade, "websocket")) {
-		return 0;
-	}
-
 	connection = mg_get_header(conn, "Connection");
 	if (connection == NULL) {
-		return 0;
+		return PROTOCOL_TYPE_HTTP1;
 	}
 	if (!mg_strcasestr(connection, "upgrade")) {
-		return 0;
+		return PROTOCOL_TYPE_HTTP1;
 	}
 
-	/* The headers "Host", "Sec-WebSocket-Key", "Sec-WebSocket-Protocol" and
-	 * "Sec-WebSocket-Version" are also required.
-	 * Don't check them here, since even an unsupported websocket protocol
-	 * request still IS a websocket request (in contrast to a standard HTTP
-	 * request). It will fail later in handle_websocket_request.
-	 */
+	upgrade = mg_get_header(conn, "Upgrade");
+	if (upgrade == NULL) {
+		/* "Connection: Upgrade" without "Upgrade" Header --> Error */
+		return -1;
+	}
 
-	return 1;
+	/* Upgrade to ... */
+	if (0 != mg_strcasestr(upgrade, "websocket")) {
+		/* The headers "Host", "Sec-WebSocket-Key", "Sec-WebSocket-Protocol" and
+		 * "Sec-WebSocket-Version" are also required.
+		 * Don't check them here, since even an unsupported websocket protocol
+		 * request still IS a websocket request (in contrast to a standard HTTP
+		 * request). It will fail later in handle_websocket_request.
+		 */
+		return PROTOCOL_TYPE_WEBSOCKET; /* Websocket */
+	}
+	if (0 != mg_strcasestr(upgrade, "h2")) {
+		return PROTOCOL_TYPE_HTTP2; /* Websocket */
+	}
+
+	/* Upgrade to another protocol */
+	return -1;
 }
-#endif /* !USE_WEBSOCKET */
 
 
 static int
@@ -14382,7 +14376,7 @@ handle_request(struct mg_connection *conn)
 	/* 5.1. first test, if the request targets the regular http(s)://
 	 * protocol namespace or the websocket ws(s):// protocol namespace.
 	 */
-	is_websocket_request = is_websocket_protocol(conn);
+	is_websocket_request = (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET);
 #if defined(USE_WEBSOCKET)
 	handler_type = is_websocket_request ? WEBSOCKET_HANDLER : REQUEST_HANDLER;
 #else
@@ -16376,9 +16370,19 @@ alpn_select_cb(SSL *ssl,
 	struct mg_domain_context *dom_ctx = (struct mg_domain_context *)arg;
 	unsigned int i, j;
 
+	struct mg_workerTLS *tls =
+	    (struct mg_workerTLS *)pthread_getspecific(sTlsKey);
+
 	(void)ssl;
 	(void)dom_ctx;
 
+
+	if (tls == NULL) {
+		/* Need to store protocol in Thread Local Storage */
+		/* If there is no Thread Local Storage, don't use ALPN */
+		return SSL_TLSEXT_ERR_NOACK;
+	}
+
 	for (j = 0; alpn_proto_order[j] != NULL; j++) {
 		/* check all accepted protocols in this order */
 		const char *alpn_proto = alpn_proto_order[j];
@@ -16387,6 +16391,7 @@ alpn_select_cb(SSL *ssl,
 			if (!memcmp(in + i, alpn_proto, (unsigned char)alpn_proto[0])) {
 				*out = in + i + 1;
 				*outlen = in[i];
+				tls->alpn_proto = alpn_proto;
 				return SSL_TLSEXT_ERR_OK;
 			}
 		}
@@ -18384,7 +18389,6 @@ process_new_connection(struct mg_connection *conn)
 	char ebuf[100];
 	const char *hostend;
 	int reqerr, uri_type;
-	int is_http2 = 0;
 
 #if defined(USE_SERVER_STATS)
 	int mcon = mg_atomic_inc(&(conn->phys_ctx->active_connections));
@@ -18421,16 +18425,9 @@ process_new_connection(struct mg_connection *conn)
 				mg_send_http_error(conn, reqerr, "%s", ebuf);
 			}
 
-#if defined(USE_HTTP2)
-		} else if ((0 == strcmp(ri->http_version, "2.0"))
-		           && (conn->handled_requests == 0)) {
-			/* Initialize */
-			is_http2 = 1;
-			conn->content_len = -1; /* content len is unknown */
-#endif
-
 		} else if (strcmp(ri->http_version, "1.0")
 		           && strcmp(ri->http_version, "1.1")) {
+			/* HTTP/2 is not allowed here */
 			mg_snprintf(conn,
 			            NULL, /* No truncation check for ebuf */
 			            ebuf,
@@ -18440,7 +18437,7 @@ process_new_connection(struct mg_connection *conn)
 			mg_send_http_error(conn, 505, "%s", ebuf);
 		}
 
-		if ((ebuf[0] == '\0') && (!is_http2)) {
+		if (ebuf[0] == '\0') {
 			uri_type = get_uri_type(conn->request_info.request_uri);
 			switch (uri_type) {
 			case 1:
@@ -18480,27 +18477,29 @@ process_new_connection(struct mg_connection *conn)
 #endif
 		}
 
+		if (ebuf[0] != '\0') {
+			conn->protocol_type = -1;
+
+		} else if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) {
+			/* HTTP/1 allows protocol upgrade */
+			conn->protocol_type = should_switch_to_protocol(conn);
+
+			if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) {
+				/* This will occur, if a HTTP/1.1 request should be upgraded
+				 * to HTTP/2 - but not if HTTP/2 is negotiated using ALPN.
+				 * Since most (all?) major browsers only support HTTP/2 using
+				 * ALPN, this is hard to test and very low priority.
+				 * Deactivate it (at least for now).
+				 */
+				conn->protocol_type = PROTOCOL_TYPE_HTTP1;
+			}
+		}
+
 		DEBUG_TRACE("http: %s, error: %s",
 		            (ri->http_version ? ri->http_version : "none"),
 		            (ebuf[0] ? ebuf : "none"));
-#if defined(USE_HTTP2)
-		if (is_http2) {
-			if (!is_valid_http2_primer(conn)) {
-				/* Primer does not match expectation from RFC.
-				 * See https://tools.ietf.org/html/rfc7540#section-3.5 */
-				mg_snprintf(conn,
-				            NULL, /* No truncation check for ebuf */
-				            ebuf,
-				            sizeof(ebuf),
-				            "Invalid HTTP/2 primer");
-				mg_send_http_error(conn, 400, "%s", ebuf);
-			} else {
-				/* Valid HTTP/2 primer received */
-				handle_http2(conn);
-			}
-		} else
-#endif
-		    if (ebuf[0] == '\0') {
+
+		if (ebuf[0] == '\0') {
 			if (conn->request_info.local_uri) {
 
 /* handle request to local server */
@@ -18555,7 +18554,7 @@ process_new_connection(struct mg_connection *conn)
 		                     && ((conn->consumed_content == conn->content_len)
 		                         || ((conn->request_len + conn->content_len)
 		                             <= conn->data_len))))
-		             && (!is_http2);
+		             && (conn->protocol_type == PROTOCOL_TYPE_HTTP1);
 
 		if (keep_alive) {
 			/* Discard all buffered data for this request */
@@ -18822,6 +18821,9 @@ worker_thread_run(struct mg_connection *conn)
 	 * produce_socket() */
 	while (consume_socket(ctx, &conn->client, thread_index)) {
 
+		/* New connections must start with new protocol negotiation */
+		tls.alpn_proto = NULL;
+
 #if defined(USE_SERVER_STATS)
 		conn->conn_close_time = 0;
 #endif
@@ -18865,7 +18867,17 @@ worker_thread_run(struct mg_connection *conn)
 				ssl_get_client_cert_info(conn);
 
 				/* process HTTPS connection */
-				process_new_connection(conn);
+				if (!memcmp(tls.alpn_proto, "\x02h2", 3)) {
+					/* process HTTPS/2 connection */
+					conn->protocol_type = PROTOCOL_TYPE_HTTP2;
+					init_connection(conn);
+#ifdef USE_HTTP2
+					process_new_http2_connection(conn);
+#endif
+				} else {
+					/* process HTTPS/1.x or WEBSOCKET-SECURE connection */
+					process_new_connection(conn);
+				}
 
 				/* Free client certificate info */
 				if (conn->request_info.client_cert) {