Apache HTTPD
h2_ws.c
Go to the documentation of this file.
1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <assert.h>
18
19#include "apr.h"
20#include "apr_strings.h"
21#include "apr_lib.h"
22#include "apr_sha1.h"
23#include "apr_strmatch.h"
24
25#include <ap_mmn.h>
26
27#include <httpd.h>
28#include <http_core.h>
29#include <http_connection.h>
30#include <http_protocol.h>
31#include <http_request.h>
32#include <http_log.h>
33#include <http_ssl.h>
34#include <http_vhost.h>
35#include <util_filter.h>
36#include <ap_mpm.h>
37
38#include "h2_private.h"
39#include "h2_config.h"
40#include "h2_conn_ctx.h"
41#include "h2_headers.h"
42#include "h2_request.h"
43#include "h2_ws.h"
44
45#if H2_USE_WEBSOCKETS
46
47#include "apr_encode.h" /* H2_USE_WEBSOCKETS is conditional on APR 1.6+ */
48
50
51struct ws_filter_ctx {
52 const char *ws_accept_base64;
53 int has_final_response;
54 int override_body;
55};
56
61static const char *gen_ws_accept(conn_rec *c, const char *key_base64)
62{
64 const char ws_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
66
68 apr_sha1_update(&sha1_ctx, key_base64, (unsigned int)strlen(key_base64));
69 apr_sha1_update(&sha1_ctx, ws_guid, (unsigned int)strlen(ws_guid));
71
72 return apr_pencode_base64_binary(c->pool, dgst, sizeof(dgst),
74}
75
77 conn_rec *c2, int no_body)
78{
81 unsigned char key_raw[16];
82 const char *key_base64, *accept_base64;
83 struct ws_filter_ctx *ws_ctx;
84 apr_status_t rv;
85
86 if (!conn_ctx || !req->protocol || strcmp("websocket", req->protocol))
87 return req;
88
89 if (ap_cstr_casecmp("CONNECT", req->method)) {
91 "h2_c2(%s-%d): websocket request with method %s",
92 conn_ctx->id, conn_ctx->stream_id, req->method);
93 return req;
94 }
95 if (!req->scheme) {
97 "h2_c2(%s-%d): websocket CONNECT without :scheme",
98 conn_ctx->id, conn_ctx->stream_id);
99 return req;
100 }
101 if (!req->path) {
103 "h2_c2(%s-%d): websocket CONNECT without :path",
104 conn_ctx->id, conn_ctx->stream_id);
105 return req;
106 }
107
109 "h2_c2(%s-%d): websocket CONNECT for %s",
110 conn_ctx->id, conn_ctx->stream_id, req->path);
111 /* Transform the HTTP/2 extended CONNECT to an internal GET using
112 * the HTTP/1.1 version of websocket connection setup. */
113 wsreq = h2_request_clone(c2->pool, req);
114 wsreq->method = "GET";
115 wsreq->protocol = NULL;
116 apr_table_set(wsreq->headers, "Upgrade", "websocket");
117 apr_table_add(wsreq->headers, "Connection", "Upgrade");
118 /* add Sec-WebSocket-Key header */
120 if (rv != APR_SUCCESS) {
122 "error generating secret");
123 return NULL;
124 }
127 apr_table_set(wsreq->headers, "Sec-WebSocket-Key", key_base64);
128 /* This is now the request to process internally */
129
130 /* When this request gets processed and delivers a 101 response,
131 * we expect it to carry a "Sec-WebSocket-Accept" header with
132 * exactly the following value, as per RFC 6455. */
134 /* Add an output filter that intercepts generated responses:
135 * - if a valid WebSocket negotiation happens, transform the
136 * 101 response to a 200
137 * - if a 2xx response happens, that does not pass the Accept test,
138 * return a 502 indicating that the URI seems not support the websocket
139 * protocol (RFC 8441 does not define this, but it seems the best
140 * choice)
141 * - if a 3xx, 4xx or 5xx response happens, forward this unchanged.
142 */
143 ws_ctx = apr_pcalloc(c2->pool, sizeof(*ws_ctx));
144 ws_ctx->ws_accept_base64 = accept_base64;
145 /* insert our filter just before the C2 core filter */
147 ap_add_output_filter("H2_C2_WS_OUT", ws_ctx, NULL, c2);
148 ap_add_output_filter("H2_C2_NET_OUT", NULL, NULL, c2);
149 /* Mark the connection as being an Upgrade, with some special handling
150 * since the request needs an EOS, without the stream being closed */
151 conn_ctx->is_upgrade = 1;
152
153 return wsreq;
154}
155
157 apr_table_t *headers, apr_table_t *notes)
158{
160
161 ap_assert(headers);
162 nheaders = apr_table_clone(c2->pool, headers);
163 apr_table_unset(nheaders, "Connection");
164 apr_table_unset(nheaders, "Upgrade");
165 apr_table_unset(nheaders, "Sec-WebSocket-Accept");
166 nnotes = notes? apr_table_clone(c2->pool, notes) :
167 apr_table_make(c2->pool, 10);
168#if AP_HAS_RESPONSE_BUCKETS
170 c2->pool, c2->bucket_alloc);
171#else
174 nnotes, 0, c2->pool));
175#endif
176}
177
179 apr_table_t *notes)
180{
182
183 nheaders = apr_table_make(c2->pool, 10);
184 apr_table_setn(nheaders, "Content-Length", "0");
185 nnotes = notes? apr_table_clone(c2->pool, notes) :
186 apr_table_make(c2->pool, 10);
187#if AP_HAS_RESPONSE_BUCKETS
189 c2->pool, c2->bucket_alloc);
190#else
193 nnotes, 0, c2->pool));
194#endif
195}
196
199{
200#if AP_HAS_RESPONSE_BUCKETS
201 ap_bucket_response *resp = b->data;
202#else /* AP_HAS_RESPONSE_BUCKETS */
204#endif /* !AP_HAS_RESPONSE_BUCKETS */
206 int is_final = 0;
207 int override_body = 0;
208
209 if (ws_ctx->has_final_response) {
210 /* already did, nop */
211 return;
212 }
213
215 "h2_c2(%s-%d): H2_C2_WS_OUT inspecting response %d",
216 conn_ctx->id, conn_ctx->stream_id, resp->status);
217 if (resp->status == HTTP_SWITCHING_PROTOCOLS) {
218 /* The resource agreed to switch protocol. But this is only valid
219 * if it send back the correct Sec-WebSocket-Accept header value */
220 const char *hd = apr_table_get(resp->headers, "Sec-WebSocket-Accept");
221 if (hd && !strcmp(ws_ctx->ws_accept_base64, hd)) {
223 "h2_c2(%s-%d): websocket CONNECT, valid 101 Upgrade"
224 ", converting to 200 response",
225 conn_ctx->id, conn_ctx->stream_id);
226 b_override = make_valid_resp(c2, HTTP_OK, resp->headers, resp->notes);
227 is_final = 1;
228 }
229 else {
230 if (!hd) {
231 /* This points to someone being confused */
233 "h2_c2(%s-%d): websocket CONNECT, got 101 response "
234 "without Sec-WebSocket-Accept header",
235 conn_ctx->id, conn_ctx->stream_id);
236 }
237 else {
238 /* This points to a bug, either in our WebSockets negotiation
239 * or in the request processings implementation of WebSockets */
241 "h2_c2(%s-%d): websocket CONNECT, 101 response "
242 "with 'Sec-WebSocket-Accept: %s' but expected %s",
243 conn_ctx->id, conn_ctx->stream_id, hd,
244 ws_ctx->ws_accept_base64);
245 }
248 }
249 }
250 else if (resp->status < 200) {
251 /* other intermediate response, pass through */
252 }
253 else if (resp->status < 300) {
254 /* Failure, we might be talking to a plain http resource */
256 "h2_c2(%s-%d): websocket CONNECT, invalid response %d",
257 conn_ctx->id, conn_ctx->stream_id, resp->status);
260 }
261 else {
262 /* error response, pass through. */
263 ws_ctx->has_final_response = 1;
264 }
265
266 if (b_override) {
269 b = b_override;
270 }
271 if (override_body) {
273 ws_ctx->override_body = 1;
274 }
275 if (is_final) {
276 ws_ctx->has_final_response = 1;
277 conn_ctx->has_final_response = 1;
278 }
279}
280
282{
283 struct ws_filter_ctx *ws_ctx = f->ctx;
285 apr_bucket *b, *bnext;
286
288 if (ws_ctx->override_body) {
289 /* We have overridden the original response and also its body.
290 * If this filter is called again, we signal a hard abort to
291 * allow processing to terminate at the earliest. */
292 f->c->aborted = 1;
293 return APR_ECONNABORTED;
294 }
295
296 /* Inspect the brigade, looking for RESPONSE/HEADER buckets.
297 * Remember, this filter is only active for client websocket CONNECT
298 * requests that we translated to an internal GET with websocket
299 * headers.
300 * We inspect the repsone to see if the internal resource actually
301 * agrees to talk websocket or is "just" a normal HTTP resource that
302 * ignored the websocket request headers. */
303 for (b = APR_BRIGADE_FIRST(bb);
304 b != APR_BRIGADE_SENTINEL(bb);
305 b = bnext)
306 {
309#if AP_HAS_RESPONSE_BUCKETS
311#else
312 if (H2_BUCKET_IS_HEADERS(b)) {
313#endif /* !AP_HAS_RESPONSE_BUCKETS */
315 continue;
316 }
317 }
318 else if (ws_ctx->override_body) {
320 }
321 }
322 return ap_pass_brigade(f->next, bb);
323}
324
325static int ws_post_read(request_rec *r)
326{
327
328 if (r->connection->master) {
330 if (conn_ctx && conn_ctx->is_upgrade &&
333 }
334 }
335 return DECLINED;
336}
337
338void h2_ws_register_hooks(void)
339{
344}
345
346#else /* H2_USE_WEBSOCKETS */
347
349 conn_rec *c2, int no_body)
350{
351 (void)c2;
352 (void)no_body;
353 /* no rewriting */
354 return req;
355}
356
358{
359 /* NOP */
360}
361
362#endif /* H2_USE_WEBSOCKETS (else part) */
Module Magic Number.
Apache Multi-Processing Module library.
APR-UTIL Encoding.
APR general purpose library routines.
APR-UTIL SHA1 library.
#define APR_SHA1_DIGESTSIZE
Definition apr_sha1.h:39
APR Strings library.
APR-UTIL string matching routines.
request_rec * r
#define DECLINED
Definition httpd.h:457
apr_status_t ap_pass_brigade(ap_filter_t *filter, apr_bucket_brigade *bucket)
apr_status_t ap_remove_output_filter_byhandle(ap_filter_t *next, const char *handle)
ap_filter_rec_t * ap_register_output_filter(const char *name, ap_out_filter_func filter_func, ap_init_filter_func filter_init, ap_filter_type ftype)
ap_filter_t * ap_add_output_filter(const char *name, void *ctx, request_rec *r, conn_rec *c)
@ AP_FTYPE_NETWORK
#define APLOGNO(n)
Definition http_log.h:117
#define APLOG_ERR
Definition http_log.h:67
#define ap_log_error
Definition http_log.h:370
#define ap_log_cerror
Definition http_log.h:498
#define APLOG_MARK
Definition http_log.h:283
#define APLOG_WARNING
Definition http_log.h:68
#define APLOG_CRIT
Definition http_log.h:66
#define APLOG_TRACE2
Definition http_log.h:73
#define APLOG_TRACE1
Definition http_log.h:72
void ap_hook_post_read_request(ap_HOOK_post_read_request_t *pf, const char *const *aszPre, const char *const *aszSucc, int nOrder)
Definition protocol.c:2585
#define APR_ECONNABORTED
Definition apr_errno.h:769
apr_file_t * f
#define APR_BUCKET_INSERT_AFTER(a, b)
#define APR_BUCKET_IS_METADATA(e)
#define APR_BUCKET_NEXT(e)
#define APR_BRIGADE_SENTINEL(b)
#define apr_bucket_delete(e)
#define APR_BRIGADE_FIRST(b)
#define APR_BUCKET_INSERT_BEFORE(a, b)
#define APR_ENCODE_NONE
Definition apr_encode.h:110
#define APR_HOOK_MIDDLE
Definition apr_hooks.h:303
#define HTTP_OK
Definition httpd.h:490
#define HTTP_BAD_GATEWAY
Definition httpd.h:537
#define HTTP_SWITCHING_PROTOCOLS
Definition httpd.h:488
#define HTTP_NOT_IMPLEMENTED
Definition httpd.h:536
int ap_cstr_casecmp(const char *s1, const char *s2)
Definition util.c:3542
#define ap_assert(exp)
Definition httpd.h:2271
apr_size_t size
const char int apr_pool_t * pool
Definition apr_cstr.h:84
#define APR_SUCCESS
Definition apr_errno.h:225
int apr_status_t
Definition apr_errno.h:44
apr_vformatter_buff_t * c
Definition apr_lib.h:175
apr_pool_t * b
Definition apr_pools.h:529
#define apr_pcalloc(p, size)
Definition apr_pools.h:465
int int status
int h2_config_sgeti(server_rec *s, h2_config_var_t var)
Definition h2_config.c:506
@ H2_CONF_WEBSOCKETS
Definition h2_config.h:48
#define h2_conn_ctx_get(c)
Definition h2_conn_ctx.h:78
h2_headers * h2_bucket_headers_get(apr_bucket *b)
Definition h2_headers.c:85
h2_headers * h2_headers_create(int status, const apr_table_t *headers_in, const apr_table_t *notes, apr_off_t raw_bytes, apr_pool_t *pool)
Definition h2_headers.c:119
apr_bucket * h2_bucket_headers_create(apr_bucket_alloc_t *list, h2_headers *r)
Definition h2_headers.c:73
#define H2_BUCKET_IS_HEADERS(e)
Definition h2_headers.h:37
h2_request * h2_request_clone(apr_pool_t *p, const h2_request *src)
Definition h2_request.c:216
const h2_request * h2_ws_rewrite_request(const h2_request *req, conn_rec *c2, int no_body)
Definition h2_ws.c:348
void h2_ws_register_hooks(void)
Definition h2_ws.c:357
Apache connection library.
CORE HTTP Daemon.
Apache Logging library.
HTTP protocol handling.
Apache Request library.
SSL protocol handling.
Virtual Host package.
HTTP Daemon routines.
return NULL
Definition mod_so.c:359
This structure is used for recording information about the registered filters. It associates a name w...
The representation of a filter chain.
Structure to store things which are per connection.
Definition httpd.h:1152
apr_pool_t * pool
Definition httpd.h:1154
struct ap_filter_t * output_filters
Definition httpd.h:1197
struct apr_bucket_alloc_t * bucket_alloc
Definition httpd.h:1201
conn_rec * master
Definition httpd.h:1248
const char * method
Definition h2.h:170
const char * scheme
Definition h2.h:171
const char * path
Definition h2.h:173
const char * protocol
Definition h2.h:174
A structure that represents the current request.
Definition httpd.h:845
conn_rec * connection
Definition httpd.h:849
server_rec * server
Definition httpd.h:851
Apache filter library.