]> granicus.if.org Git - apache/blob - modules/proxy/mod_proxy_wstunnel.c
reverting r1735174 as http/1.1 just terminated before response
[apache] / modules / proxy / mod_proxy_wstunnel.c
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 "mod_proxy.h"
18 #include "ap_mpm.h"
19
20 module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;
21
22 typedef struct {
23     signed char is_async;
24     apr_time_t idle_timeout;
25     apr_time_t async_delay;
26 } proxyws_dir_conf;
27
28 typedef struct ws_baton_t {
29     request_rec *r;
30     proxy_conn_rec *proxy_connrec;
31     apr_socket_t *server_soc;
32     apr_socket_t *client_soc;
33     apr_pollset_t *pollset;
34     apr_bucket_brigade *bb_i;
35     apr_bucket_brigade *bb_o;
36     apr_pool_t *subpool;        /* cleared before each suspend, destroyed when request ends */
37     char *scheme;               /* required to release the proxy connection */
38 } ws_baton_t;
39
40 static void proxy_wstunnel_callback(void *b);
41
42 static int proxy_wstunnel_pump(ws_baton_t *baton, apr_time_t timeout, int try_async) {
43     request_rec *r = baton->r;
44     conn_rec *c = r->connection;
45     proxy_conn_rec *conn = baton->proxy_connrec;
46     apr_socket_t *sock = conn->sock;
47     conn_rec *backconn = conn->connection;
48     const apr_pollfd_t *signalled;
49     apr_int32_t pollcnt, pi;
50     apr_int16_t pollevent;
51     apr_pollset_t *pollset = baton->pollset;
52     apr_socket_t *client_socket = baton->client_soc;
53     apr_status_t rv;
54     apr_bucket_brigade *bb_i = baton->bb_i;
55     apr_bucket_brigade *bb_o = baton->bb_o;
56     int done = 0, replied = 0;
57
58     do { 
59         rv = apr_pollset_poll(pollset, timeout, &pollcnt, &signalled);
60         if (rv != APR_SUCCESS) {
61             if (APR_STATUS_IS_EINTR(rv)) {
62                 continue;
63             }
64             else if (APR_STATUS_IS_TIMEUP(rv)) { 
65                 if (try_async) { 
66                     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02542) "Attempting to go async");
67                     return SUSPENDED;
68                 }
69                 else { 
70                     return HTTP_REQUEST_TIME_OUT;
71                 }
72             }
73             else { 
74                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02444) "error apr_poll()");
75                 return HTTP_INTERNAL_SERVER_ERROR;
76             }
77         }
78
79         ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02445)
80                 "woke from poll(), i=%d", pollcnt);
81
82         for (pi = 0; pi < pollcnt; pi++) {
83             const apr_pollfd_t *cur = &signalled[pi];
84
85             if (cur->desc.s == sock) {
86                 pollevent = cur->rtnevents;
87                 if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
88                     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02446)
89                             "sock was readable");
90                     done |= ap_proxy_transfer_between_connections(r, backconn,
91                                                                   c, bb_i, bb_o,
92                                                                   "sock", NULL,
93                                                                   AP_IOBUFSIZE,
94                                                                   0)
95                                                                  != APR_SUCCESS;
96                 }
97                 else if (pollevent & APR_POLLERR) {
98                     ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02447)
99                             "error on backconn");
100                     backconn->aborted = 1;
101                     done = 1;
102                 }
103                 else { 
104                     ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02605)
105                             "unknown event on backconn %d", pollevent);
106                     done = 1;
107                 }
108             }
109             else if (cur->desc.s == client_socket) {
110                 pollevent = cur->rtnevents;
111                 if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
112                     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02448)
113                             "client was readable");
114                     done |= ap_proxy_transfer_between_connections(r, c, backconn,
115                                                                   bb_o, bb_i,
116                                                                   "client",
117                                                                   &replied,
118                                                                   AP_IOBUFSIZE,
119                                                                   0)
120                                                                  != APR_SUCCESS;
121                 }
122                 else if (pollevent & APR_POLLERR) {
123                     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02607)
124                             "error on client conn");
125                     c->aborted = 1;
126                     done = 1;
127                 }
128                 else { 
129                     ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02606)
130                             "unknown event on client conn %d", pollevent);
131                     done = 1;
132                 }
133             }
134             else {
135                 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02449)
136                         "unknown socket in pollset");
137                 done = 1;
138             }
139
140         }
141     } while (!done);
142
143     ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
144             "finished with poll() - cleaning up");
145
146     if (!replied) {
147         return HTTP_BAD_GATEWAY;
148     }
149     else {
150         return OK;
151     }
152 }
153
154 static void proxy_wstunnel_finish(ws_baton_t *baton) { 
155     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, baton->r, "proxy_wstunnel_finish");
156     baton->proxy_connrec->close = 1; /* new handshake expected on each back-conn */
157     baton->r->connection->keepalive = AP_CONN_CLOSE;
158     ap_proxy_release_connection(baton->scheme, baton->proxy_connrec, baton->r->server);
159     ap_finalize_request_protocol(baton->r);
160     ap_lingering_close(baton->r->connection);
161     apr_socket_close(baton->client_soc);
162     ap_mpm_resume_suspended(baton->r->connection);
163     ap_process_request_after_handler(baton->r); /* don't touch baton or r after here */
164 }
165
166 /* If neither socket becomes readable in the specified timeout,
167  * this callback will kill the request.  We do not have to worry about
168  * having a cancel and a IO both queued.
169  */
170 static void proxy_wstunnel_cancel_callback(void *b)
171
172     ws_baton_t *baton = (ws_baton_t*)b;
173     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, baton->r, "proxy_wstunnel_cancel_callback, IO timed out");
174     proxy_wstunnel_finish(baton);
175     return;
176 }
177
178 /* Invoked by the event loop when data is ready on either end. 
179  *  Pump both ends until they'd block and then start over again 
180  *  We don't need the invoke_mtx, since we never put multiple callback events
181  *  in the queue.
182  */
183 static void proxy_wstunnel_callback(void *b) { 
184     int status;
185     apr_socket_t *sockets[3] = {NULL, NULL, NULL};
186     ws_baton_t *baton = (ws_baton_t*)b;
187     proxyws_dir_conf *dconf = ap_get_module_config(baton->r->per_dir_config, &proxy_wstunnel_module);
188     apr_pool_clear(baton->subpool);
189     status = proxy_wstunnel_pump(baton, dconf->async_delay, dconf->is_async);
190     if (status == SUSPENDED) {
191         sockets[0] = baton->client_soc;
192         sockets[1] = baton->server_soc;
193         ap_mpm_register_socket_callback_timeout(sockets, baton->subpool, 1, 
194             proxy_wstunnel_callback, 
195             proxy_wstunnel_cancel_callback, 
196             baton, 
197             dconf->idle_timeout);
198         ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, baton->r, "proxy_wstunnel_callback suspend");
199     }
200     else { 
201         proxy_wstunnel_finish(baton);
202     }
203 }
204
205
206 /*
207  * Canonicalise http-like URLs.
208  * scheme is the scheme for the URL
209  * url is the URL starting with the first '/'
210  * def_port is the default port for this scheme.
211  */
212 static int proxy_wstunnel_canon(request_rec *r, char *url)
213 {
214     char *host, *path, sport[7];
215     char *search = NULL;
216     const char *err;
217     char *scheme;
218     apr_port_t port, def_port;
219
220     /* ap_port_of_scheme() */
221     if (ap_casecmpstrn(url, "ws:", 3) == 0) {
222         url += 3;
223         scheme = "ws:";
224         def_port = apr_uri_port_of_scheme("http");
225     }
226     else if (ap_casecmpstrn(url, "wss:", 4) == 0) {
227         url += 4;
228         scheme = "wss:";
229         def_port = apr_uri_port_of_scheme("https");
230     }
231     else {
232         return DECLINED;
233     }
234
235     port = def_port;
236     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url);
237
238     /*
239      * do syntactic check.
240      * We break the URL into host, port, path, search
241      */
242     err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
243     if (err) {
244         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02439) "error parsing URL %s: %s",
245                       url, err);
246         return HTTP_BAD_REQUEST;
247     }
248
249     /*
250      * now parse path/search args, according to rfc1738:
251      * process the path. With proxy-nocanon set (by
252      * mod_proxy) we use the raw, unparsed uri
253      */
254     if (apr_table_get(r->notes, "proxy-nocanon")) {
255         path = url;   /* this is the raw path */
256     }
257     else {
258         path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
259                                  r->proxyreq);
260         search = r->args;
261     }
262     if (path == NULL)
263         return HTTP_BAD_REQUEST;
264
265     apr_snprintf(sport, sizeof(sport), ":%d", port);
266
267     if (ap_strchr_c(host, ':')) {
268         /* if literal IPv6 address */
269         host = apr_pstrcat(r->pool, "[", host, "]", NULL);
270     }
271     r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "//", host, sport,
272                               "/", path, (search) ? "?" : "",
273                               (search) ? search : "", NULL);
274     return OK;
275 }
276
277 /*
278  * process the request and write the response.
279  */
280 static int proxy_wstunnel_request(apr_pool_t *p, request_rec *r,
281                                 proxy_conn_rec *conn,
282                                 proxy_worker *worker,
283                                 proxy_server_conf *conf,
284                                 apr_uri_t *uri,
285                                 char *url, char *server_portstr, char *scheme)
286 {
287     apr_status_t rv;
288     apr_pollset_t *pollset;
289     apr_pollfd_t pollfd;
290     conn_rec *c = r->connection;
291     apr_socket_t *sock = conn->sock;
292     conn_rec *backconn = conn->connection;
293     char *buf;
294     apr_bucket_brigade *header_brigade;
295     apr_bucket *e;
296     char *old_cl_val = NULL;
297     char *old_te_val = NULL;
298     apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
299     apr_socket_t *client_socket = ap_get_conn_socket(c);
300     ws_baton_t *baton = apr_pcalloc(r->pool, sizeof(ws_baton_t));
301     apr_socket_t *sockets[3] = {NULL, NULL, NULL};
302     int status;
303     proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_wstunnel_module);
304
305     header_brigade = apr_brigade_create(p, backconn->bucket_alloc);
306
307     ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "sending request");
308
309     rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, conn,
310                                  worker, conf, uri, url, server_portstr,
311                                  &old_cl_val, &old_te_val);
312     if (rv != OK) {
313         return rv;
314     }
315
316     buf = apr_pstrdup(p, "Upgrade: WebSocket" CRLF "Connection: Upgrade" CRLF CRLF);
317     ap_xlate_proto_to_ascii(buf, strlen(buf));
318     e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
319     APR_BRIGADE_INSERT_TAIL(header_brigade, e);
320
321     if ((rv = ap_proxy_pass_brigade(backconn->bucket_alloc, r, conn, backconn,
322                                     header_brigade, 1)) != OK)
323         return rv;
324
325     apr_brigade_cleanup(header_brigade);
326
327     ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()");
328
329     if ((rv = apr_pollset_create(&pollset, 2, p, 0)) != APR_SUCCESS) {
330         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02443)
331                       "error apr_pollset_create()");
332         return HTTP_INTERNAL_SERVER_ERROR;
333     }
334
335 #if 0
336     apr_socket_opt_set(sock, APR_SO_NONBLOCK, 1);
337     apr_socket_opt_set(sock, APR_SO_KEEPALIVE, 1);
338     apr_socket_opt_set(client_socket, APR_SO_NONBLOCK, 1);
339     apr_socket_opt_set(client_socket, APR_SO_KEEPALIVE, 1);
340 #endif
341
342     pollfd.p = p;
343     pollfd.desc_type = APR_POLL_SOCKET;
344     pollfd.reqevents = APR_POLLIN | APR_POLLHUP;
345     pollfd.desc.s = sock;
346     pollfd.client_data = NULL;
347     apr_pollset_add(pollset, &pollfd);
348
349     pollfd.desc.s = client_socket;
350     apr_pollset_add(pollset, &pollfd);
351
352     ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
353
354     r->output_filters = c->output_filters;
355     r->proto_output_filters = c->output_filters;
356     r->input_filters = c->input_filters;
357     r->proto_input_filters = c->input_filters;
358
359     /* This handler should take care of the entire connection; make it so that
360      * nothing else is attempted on the connection after returning. */
361     c->keepalive = AP_CONN_CLOSE;
362
363     baton->r = r;
364     baton->pollset = pollset;
365     baton->client_soc = client_socket;
366     baton->server_soc = sock;
367     baton->proxy_connrec = conn;
368     baton->bb_o = bb;
369     baton->bb_i = header_brigade;
370     baton->scheme = scheme;
371     apr_pool_create(&baton->subpool, r->pool);
372
373     if (!dconf->is_async) { 
374         status = proxy_wstunnel_pump(baton, dconf->idle_timeout, dconf->is_async);
375     }  
376     else { 
377         status = proxy_wstunnel_pump(baton, dconf->async_delay, dconf->is_async); 
378         apr_pool_clear(baton->subpool);
379         if (status == SUSPENDED) {
380             sockets[0] = baton->client_soc;
381             sockets[1] = baton->server_soc;
382             rv = ap_mpm_register_socket_callback_timeout(sockets, baton->subpool, 1, 
383                          proxy_wstunnel_callback, 
384                          proxy_wstunnel_cancel_callback, 
385                          baton, 
386                          dconf->idle_timeout);
387             if (rv == APR_SUCCESS) { 
388                 return SUSPENDED;
389             }
390             else if (APR_STATUS_IS_ENOTIMPL(rv)) { 
391                 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02544) "No async support");
392                 status = proxy_wstunnel_pump(baton, dconf->idle_timeout, 0); /* force no async */
393             }
394             else { 
395                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
396                               APLOGNO(02543) "error creating websockets tunnel");
397                 return HTTP_INTERNAL_SERVER_ERROR;
398             }
399         }
400     }
401
402     if (status != OK) { 
403         /* Avoid sending error pages down an upgraded connection */
404         if (status != HTTP_REQUEST_TIME_OUT) {
405             r->status = status;
406         }
407         status = OK;
408     }
409     return status;
410 }    
411     
412 /*
413  */
414 static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
415                              proxy_server_conf *conf,
416                              char *url, const char *proxyname,
417                              apr_port_t proxyport)
418 {
419     int status;
420     char server_portstr[32];
421     proxy_conn_rec *backend = NULL;
422     const char *upgrade;
423     char *scheme;
424     conn_rec *c = r->connection;
425     apr_pool_t *p = r->pool;
426     char *locurl = url;
427     apr_uri_t *uri;
428     int is_ssl = 0;
429
430     if (ap_casecmpstrn(url, "wss:", 4) == 0) {
431         scheme = "WSS";
432         is_ssl = 1;
433     }
434     else if (ap_casecmpstrn(url, "ws:", 3) == 0) {
435         scheme = "WS";
436     }
437     else {
438         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02450) "declining URL %s", url);
439         return DECLINED;
440     }
441
442     upgrade = apr_table_get(r->headers_in, "Upgrade");
443     if (!upgrade || ap_casecmpstr(upgrade, "WebSocket") != 0) {
444         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02900)
445                       "declining URL %s  (not WebSocket)", url);
446         return DECLINED;
447     }
448
449     uri = apr_palloc(p, sizeof(*uri));
450     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02451) "serving URL %s", url);
451
452     /* create space for state information */
453     status = ap_proxy_acquire_connection(scheme, &backend, worker, r->server);
454     if (status != OK) {
455         goto cleanup;
456     }
457
458     backend->is_ssl = is_ssl;
459     backend->close = 0;
460
461     /* Step One: Determine Who To Connect To */
462     status = ap_proxy_determine_connection(p, r, conf, worker, backend,
463                                            uri, &locurl, proxyname, proxyport,
464                                            server_portstr,
465                                            sizeof(server_portstr));
466     if (status != OK) {
467         goto cleanup;
468     }
469
470     /* Step Two: Make the Connection */
471     if (ap_proxy_connect_backend(scheme, backend, worker, r->server)) {
472         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02452)
473                       "failed to make connection to backend: %s",
474                       backend->hostname);
475         status = HTTP_SERVICE_UNAVAILABLE;
476         goto cleanup;
477     }
478
479     /* Step Three: Create conn_rec */
480     if (!backend->connection) {
481         status = ap_proxy_connection_create(scheme, backend, c, r->server);
482         if (status  != OK) {
483             goto cleanup;
484         }
485     }
486
487     /* Step Three: Process the Request */
488     status = proxy_wstunnel_request(p, r, backend, worker, conf, uri, locurl,
489                                   server_portstr, scheme);
490
491 cleanup:
492     /* Do not close the socket */
493     if (backend && status != SUSPENDED) { 
494         backend->close = 1;
495         ap_proxy_release_connection(scheme, backend, r->server);
496     }
497     return status;
498 }
499
500 static void *create_proxyws_dir_config(apr_pool_t *p, char *dummy)
501 {
502     proxyws_dir_conf *new =
503         (proxyws_dir_conf *) apr_pcalloc(p, sizeof(proxyws_dir_conf));
504
505     new->idle_timeout = -1; /* no timeout */
506
507     return (void *) new;
508 }
509
510 static const char * proxyws_set_idle(cmd_parms *cmd, void *conf, const char *val)
511 {
512     proxyws_dir_conf *dconf = conf;
513     if (ap_timeout_parameter_parse(val, &(dconf->idle_timeout), "s") != APR_SUCCESS)
514         return "ProxyWebsocketIdleTimeout timeout has wrong format";
515     return NULL;
516 }
517 static const char * proxyws_set_aysnch_delay(cmd_parms *cmd, void *conf, const char *val)
518 {
519     proxyws_dir_conf *dconf = conf;
520     if (ap_timeout_parameter_parse(val, &(dconf->async_delay), "s") != APR_SUCCESS)
521         return "ProxyWebsocketAsyncDelay timeout has wrong format";
522     return NULL;
523 }
524
525 static const command_rec ws_proxy_cmds[] =
526 {
527     AP_INIT_FLAG("ProxyWebsocketAsync", ap_set_flag_slot_char, (void*)APR_OFFSETOF(proxyws_dir_conf, is_async), 
528                  RSRC_CONF|ACCESS_CONF,
529                  "on if idle websockets connections should be monitored asyncronously"),
530
531     AP_INIT_TAKE1("ProxyWebsocketIdleTimeout", proxyws_set_idle, NULL, RSRC_CONF|ACCESS_CONF,
532                  "timeout for activity in either direction, unlimited by default"),
533
534     AP_INIT_TAKE1("ProxyWebsocketAsyncDelay", proxyws_set_aysnch_delay, NULL, RSRC_CONF|ACCESS_CONF,
535                  "amount of time to poll before going asyncronous"),
536     {NULL}
537 };
538
539 static void ap_proxy_http_register_hook(apr_pool_t *p)
540 {
541     proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, NULL, APR_HOOK_FIRST);
542     proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, NULL, APR_HOOK_FIRST);
543 }
544
545 AP_DECLARE_MODULE(proxy_wstunnel) = {
546     STANDARD20_MODULE_STUFF,
547     create_proxyws_dir_config,  /* create per-directory config structure */
548     NULL,                       /* merge per-directory config structures */
549     NULL,                       /* create per-server config structure */
550     NULL,                       /* merge per-server config structures */
551     ws_proxy_cmds,              /* command apr_table_t */
552     ap_proxy_http_register_hook /* register hooks */
553 };