Blame modules/proxy/mod_proxy_wstunnel.c

Packit 90a5c9
/* Licensed to the Apache Software Foundation (ASF) under one or more
Packit 90a5c9
 * contributor license agreements.  See the NOTICE file distributed with
Packit 90a5c9
 * this work for additional information regarding copyright ownership.
Packit 90a5c9
 * The ASF licenses this file to You under the Apache License, Version 2.0
Packit 90a5c9
 * (the "License"); you may not use this file except in compliance with
Packit 90a5c9
 * the License.  You may obtain a copy of the License at
Packit 90a5c9
 *
Packit 90a5c9
 *     http://www.apache.org/licenses/LICENSE-2.0
Packit 90a5c9
 *
Packit 90a5c9
 * Unless required by applicable law or agreed to in writing, software
Packit 90a5c9
 * distributed under the License is distributed on an "AS IS" BASIS,
Packit 90a5c9
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Packit 90a5c9
 * See the License for the specific language governing permissions and
Packit 90a5c9
 * limitations under the License.
Packit 90a5c9
 */
Packit 90a5c9
Packit 90a5c9
#include "mod_proxy.h"
Packit 90a5c9
Packit 90a5c9
module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * Canonicalise http-like URLs.
Packit 90a5c9
 * scheme is the scheme for the URL
Packit 90a5c9
 * url is the URL starting with the first '/'
Packit 90a5c9
 * def_port is the default port for this scheme.
Packit 90a5c9
 */
Packit 90a5c9
static int proxy_wstunnel_canon(request_rec *r, char *url)
Packit 90a5c9
{
Packit 90a5c9
    char *host, *path, sport[7];
Packit 90a5c9
    char *search = NULL;
Packit 90a5c9
    const char *err;
Packit 90a5c9
    char *scheme;
Packit 90a5c9
    apr_port_t port, def_port;
Packit 90a5c9
Packit 90a5c9
    /* ap_port_of_scheme() */
Packit 90a5c9
    if (strncasecmp(url, "ws:", 3) == 0) {
Packit 90a5c9
        url += 3;
Packit 90a5c9
        scheme = "ws:";
Packit 90a5c9
        def_port = apr_uri_port_of_scheme("http");
Packit 90a5c9
    }
Packit 90a5c9
    else if (strncasecmp(url, "wss:", 4) == 0) {
Packit 90a5c9
        url += 4;
Packit 90a5c9
        scheme = "wss:";
Packit 90a5c9
        def_port = apr_uri_port_of_scheme("https");
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        return DECLINED;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    port = def_port;
Packit 90a5c9
    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url);
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * do syntactic check.
Packit 90a5c9
     * We break the URL into host, port, path, search
Packit 90a5c9
     */
Packit 90a5c9
    err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
Packit 90a5c9
    if (err) {
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02439) "error parsing URL %s: %s",
Packit 90a5c9
                      url, err);
Packit 90a5c9
        return HTTP_BAD_REQUEST;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * now parse path/search args, according to rfc1738:
Packit 90a5c9
     * process the path. With proxy-nocanon set (by
Packit 90a5c9
     * mod_proxy) we use the raw, unparsed uri
Packit 90a5c9
     */
Packit 90a5c9
    if (apr_table_get(r->notes, "proxy-nocanon")) {
Packit 90a5c9
        path = url;   /* this is the raw path */
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
Packit 90a5c9
                                 r->proxyreq);
Packit 90a5c9
        search = r->args;
Packit 90a5c9
    }
Packit 90a5c9
    if (path == NULL)
Packit 90a5c9
        return HTTP_BAD_REQUEST;
Packit 90a5c9
Packit 90a5c9
    apr_snprintf(sport, sizeof(sport), ":%d", port);
Packit 90a5c9
Packit 90a5c9
    if (ap_strchr_c(host, ':')) {
Packit 90a5c9
        /* if literal IPv6 address */
Packit 90a5c9
        host = apr_pstrcat(r->pool, "[", host, "]", NULL);
Packit 90a5c9
    }
Packit 90a5c9
    r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "//", host, sport,
Packit 90a5c9
                              "/", path, (search) ? "?" : "",
Packit 90a5c9
                              (search) ? search : "", NULL);
Packit 90a5c9
    return OK;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * process the request and write the response.
Packit 90a5c9
 */
Packit 90a5c9
static int proxy_wstunnel_request(apr_pool_t *p, request_rec *r,
Packit 90a5c9
                                proxy_conn_rec *conn,
Packit 90a5c9
                                proxy_worker *worker,
Packit 90a5c9
                                proxy_server_conf *conf,
Packit 90a5c9
                                apr_uri_t *uri,
Packit 90a5c9
                                char *url, char *server_portstr)
Packit 90a5c9
{
Packit 90a5c9
    apr_status_t rv;
Packit 90a5c9
    apr_pollset_t *pollset;
Packit 90a5c9
    apr_pollfd_t pollfd;
Packit 90a5c9
    const apr_pollfd_t *signalled;
Packit 90a5c9
    apr_int32_t pollcnt, pi;
Packit 90a5c9
    apr_int16_t pollevent;
Packit 90a5c9
    conn_rec *c = r->connection;
Packit 90a5c9
    apr_socket_t *sock = conn->sock;
Packit 90a5c9
    conn_rec *backconn = conn->connection;
Packit 90a5c9
    char *buf;
Packit 90a5c9
    apr_bucket_brigade *header_brigade;
Packit 90a5c9
    apr_bucket *e;
Packit 90a5c9
    char *old_cl_val = NULL;
Packit 90a5c9
    char *old_te_val = NULL;
Packit 90a5c9
    apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
Packit 90a5c9
    apr_socket_t *client_socket = ap_get_conn_socket(c);
Packit 90a5c9
    int done = 0, replied = 0;
Packit 90a5c9
    const char *upgrade_method = *worker->s->upgrade ? worker->s->upgrade : "WebSocket";
Packit 90a5c9
Packit 90a5c9
    header_brigade = apr_brigade_create(p, backconn->bucket_alloc);
Packit 90a5c9
Packit 90a5c9
    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "sending request");
Packit 90a5c9
Packit 90a5c9
    rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, conn,
Packit 90a5c9
                                 worker, conf, uri, url, server_portstr,
Packit 90a5c9
                                 &old_cl_val, &old_te_val);
Packit 90a5c9
    if (rv != OK) {
Packit 90a5c9
        return rv;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    if (ap_cstr_casecmp(upgrade_method, "NONE") == 0) {
Packit 90a5c9
        buf = apr_pstrdup(p, "Upgrade: WebSocket" CRLF "Connection: Upgrade" CRLF CRLF);
Packit 90a5c9
    } else if (ap_cstr_casecmp(upgrade_method, "ANY") == 0) {
Packit 90a5c9
        const char *upgrade;
Packit 90a5c9
        upgrade = apr_table_get(r->headers_in, "Upgrade");
Packit 90a5c9
        buf = apr_pstrcat(p, "Upgrade: ", upgrade, CRLF "Connection: Upgrade" CRLF CRLF, NULL);
Packit 90a5c9
    } else {
Packit 90a5c9
        buf = apr_pstrcat(p, "Upgrade: ", upgrade_method, CRLF "Connection: Upgrade" CRLF CRLF, NULL);
Packit 90a5c9
    }
Packit 90a5c9
    ap_xlate_proto_to_ascii(buf, strlen(buf));
Packit 90a5c9
    e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
Packit 90a5c9
    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
Packit 90a5c9
Packit 90a5c9
    if ((rv = ap_proxy_pass_brigade(backconn->bucket_alloc, r, conn, backconn,
Packit 90a5c9
                                    header_brigade, 1)) != OK)
Packit 90a5c9
        return rv;
Packit 90a5c9
Packit 90a5c9
    apr_brigade_cleanup(header_brigade);
Packit 90a5c9
Packit 90a5c9
    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()");
Packit 90a5c9
Packit 90a5c9
    if ((rv = apr_pollset_create(&pollset, 2, p, 0)) != APR_SUCCESS) {
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02443)
Packit 90a5c9
                      "error apr_pollset_create()");
Packit 90a5c9
        return HTTP_INTERNAL_SERVER_ERROR;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
#if 0
Packit 90a5c9
    apr_socket_opt_set(sock, APR_SO_NONBLOCK, 1);
Packit 90a5c9
    apr_socket_opt_set(sock, APR_SO_KEEPALIVE, 1);
Packit 90a5c9
    apr_socket_opt_set(client_socket, APR_SO_NONBLOCK, 1);
Packit 90a5c9
    apr_socket_opt_set(client_socket, APR_SO_KEEPALIVE, 1);
Packit 90a5c9
#endif
Packit 90a5c9
Packit 90a5c9
    pollfd.p = p;
Packit 90a5c9
    pollfd.desc_type = APR_POLL_SOCKET;
Packit 90a5c9
    pollfd.reqevents = APR_POLLIN | APR_POLLHUP;
Packit 90a5c9
    pollfd.desc.s = sock;
Packit 90a5c9
    pollfd.client_data = NULL;
Packit 90a5c9
    apr_pollset_add(pollset, &pollfd);
Packit 90a5c9
Packit 90a5c9
    pollfd.desc.s = client_socket;
Packit 90a5c9
    apr_pollset_add(pollset, &pollfd);
Packit 90a5c9
Packit 90a5c9
    ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
Packit 90a5c9
Packit 90a5c9
    r->output_filters = c->output_filters;
Packit 90a5c9
    r->proto_output_filters = c->output_filters;
Packit 90a5c9
    r->input_filters = c->input_filters;
Packit 90a5c9
    r->proto_input_filters = c->input_filters;
Packit 90a5c9
Packit 90a5c9
    /* This handler should take care of the entire connection; make it so that
Packit 90a5c9
     * nothing else is attempted on the connection after returning. */
Packit 90a5c9
    c->keepalive = AP_CONN_CLOSE;
Packit 90a5c9
Packit 90a5c9
    do { /* Loop until done (one side closes the connection, or an error) */
Packit 90a5c9
        rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled);
Packit 90a5c9
        if (rv != APR_SUCCESS) {
Packit 90a5c9
            if (APR_STATUS_IS_EINTR(rv)) {
Packit 90a5c9
                continue;
Packit 90a5c9
            }
Packit 90a5c9
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02444) "error apr_poll()");
Packit 90a5c9
            return HTTP_INTERNAL_SERVER_ERROR;
Packit 90a5c9
        }
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02445)
Packit 90a5c9
                      "woke from poll(), i=%d", pollcnt);
Packit 90a5c9
Packit 90a5c9
        for (pi = 0; pi < pollcnt; pi++) {
Packit 90a5c9
            const apr_pollfd_t *cur = &signalled[pi];
Packit 90a5c9
Packit 90a5c9
            if (cur->desc.s == sock) {
Packit 90a5c9
                pollevent = cur->rtnevents;
Packit 90a5c9
                if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02446)
Packit 90a5c9
                                  "sock was readable");
Packit 90a5c9
                    done |= ap_proxy_transfer_between_connections(r, backconn,
Packit 90a5c9
                                                                  c,
Packit 90a5c9
                                                                  header_brigade,
Packit 90a5c9
                                                                  bb, "sock",
Packit 90a5c9
                                                                  &replied,
Packit 90a5c9
                                                                  AP_IOBUFSIZE,
Packit 90a5c9
                                                                  0)
Packit 90a5c9
                                                                 != APR_SUCCESS;
Packit 90a5c9
                }
Packit 90a5c9
                else if (pollevent & APR_POLLERR) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02447)
Packit 90a5c9
                            "error on backconn");
Packit 90a5c9
                    backconn->aborted = 1;
Packit 90a5c9
                    done = 1;
Packit 90a5c9
                }
Packit 90a5c9
                else { 
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02605)
Packit 90a5c9
                            "unknown event on backconn %d", pollevent);
Packit 90a5c9
                    done = 1;
Packit 90a5c9
                }
Packit 90a5c9
            }
Packit 90a5c9
            else if (cur->desc.s == client_socket) {
Packit 90a5c9
                pollevent = cur->rtnevents;
Packit 90a5c9
                if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02448)
Packit 90a5c9
                                  "client was readable");
Packit 90a5c9
                    done |= ap_proxy_transfer_between_connections(r, c,
Packit 90a5c9
                                                                  backconn, bb,
Packit 90a5c9
                                                                  header_brigade,
Packit 90a5c9
                                                                  "client",
Packit 90a5c9
                                                                  NULL,
Packit 90a5c9
                                                                  AP_IOBUFSIZE,
Packit 90a5c9
                                                                  0)
Packit 90a5c9
                                                                 != APR_SUCCESS;
Packit 90a5c9
                }
Packit 90a5c9
                else if (pollevent & APR_POLLERR) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02607)
Packit 90a5c9
                            "error on client conn");
Packit 90a5c9
                    c->aborted = 1;
Packit 90a5c9
                    done = 1;
Packit 90a5c9
                }
Packit 90a5c9
                else { 
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02606)
Packit 90a5c9
                            "unknown event on client conn %d", pollevent);
Packit 90a5c9
                    done = 1;
Packit 90a5c9
                }
Packit 90a5c9
            }
Packit 90a5c9
            else {
Packit 90a5c9
                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02449)
Packit 90a5c9
                              "unknown socket in pollset");
Packit 90a5c9
                done = 1;
Packit 90a5c9
            }
Packit 90a5c9
Packit 90a5c9
        }
Packit 90a5c9
    } while (!done);
Packit 90a5c9
Packit 90a5c9
    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
Packit 90a5c9
                  "finished with poll() - cleaning up");
Packit 90a5c9
Packit 90a5c9
    if (!replied) {
Packit 90a5c9
        return HTTP_BAD_GATEWAY;
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        return OK;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    return OK;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 */
Packit 90a5c9
static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
Packit 90a5c9
                             proxy_server_conf *conf,
Packit 90a5c9
                             char *url, const char *proxyname,
Packit 90a5c9
                             apr_port_t proxyport)
Packit 90a5c9
{
Packit 90a5c9
    int status;
Packit 90a5c9
    char server_portstr[32];
Packit 90a5c9
    proxy_conn_rec *backend = NULL;
Packit 90a5c9
    char *scheme;
Packit 90a5c9
    apr_pool_t *p = r->pool;
Packit f02de7
    char *locurl = url;
Packit 90a5c9
    apr_uri_t *uri;
Packit 90a5c9
    int is_ssl = 0;
Packit 90a5c9
    const char *upgrade_method = *worker->s->upgrade ? worker->s->upgrade : "WebSocket";
Packit 90a5c9
Packit 90a5c9
    if (strncasecmp(url, "wss:", 4) == 0) {
Packit 90a5c9
        scheme = "WSS";
Packit 90a5c9
        is_ssl = 1;
Packit 90a5c9
    }
Packit 90a5c9
    else if (strncasecmp(url, "ws:", 3) == 0) {
Packit 90a5c9
        scheme = "WS";
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02450) "declining URL %s", url);
Packit 90a5c9
        return DECLINED;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    if (ap_cstr_casecmp(upgrade_method, "NONE") != 0) {
Packit 90a5c9
        const char *upgrade;
Packit 90a5c9
        upgrade = apr_table_get(r->headers_in, "Upgrade");
Packit 90a5c9
        if (!upgrade || (ap_cstr_casecmp(upgrade, upgrade_method) != 0 &&
Packit 90a5c9
            ap_cstr_casecmp(upgrade_method, "ANY") !=0)) {
Packit 90a5c9
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02900)
Packit 90a5c9
                          "declining URL %s  (not %s, Upgrade: header is %s)", 
Packit 90a5c9
                          url, upgrade_method, upgrade ? upgrade : "missing");
Packit 90a5c9
            return DECLINED;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    uri = apr_palloc(p, sizeof(*uri));
Packit 90a5c9
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02451) "serving URL %s", url);
Packit 90a5c9
Packit 90a5c9
    /* create space for state information */
Packit f02de7
    status = ap_proxy_acquire_connection(scheme, &backend, worker, r->server);
Packit 90a5c9
    if (status != OK) {
Packit f02de7
        goto cleanup;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    backend->is_ssl = is_ssl;
Packit 90a5c9
    backend->close = 0;
Packit 90a5c9
Packit f02de7
    /* Step One: Determine Who To Connect To */
Packit f02de7
    status = ap_proxy_determine_connection(p, r, conf, worker, backend,
Packit f02de7
                                           uri, &locurl, proxyname, proxyport,
Packit f02de7
                                           server_portstr,
Packit f02de7
                                           sizeof(server_portstr));
Packit f02de7
    
Packit f02de7
    if (status != OK) {
Packit f02de7
        goto cleanup;
Packit f02de7
    }
Packit f02de7
    
Packit f02de7
    /* Step Two: Make the Connection */
Packit f02de7
    if (ap_proxy_connect_backend(scheme, backend, worker, r->server)) {
Packit f02de7
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02452)
Packit f02de7
                      "failed to make connection to backend: %s",
Packit f02de7
                      backend->hostname);
Packit f02de7
        status = HTTP_SERVICE_UNAVAILABLE;
Packit f02de7
        goto cleanup;
Packit f02de7
    }
Packit 90a5c9
Packit f02de7
    /* Step Three: Create conn_rec */
Packit f02de7
    /* keep it because of */
Packit f02de7
    /* https://github.com/apache/httpd/commit/313d5ee40f390da1a6ee2c2752864ad3aad0a1c3 */
Packit f02de7
    status = ap_proxy_connection_create_ex(scheme, backend, r);
Packit f02de7
    if (status != OK) {
Packit f02de7
        goto cleanup;
Packit 90a5c9
    }
Packit f02de7
    
Packit f02de7
    /* Step Four: Process the Request */
Packit f02de7
    status = proxy_wstunnel_request(p, r, backend, worker, conf, uri, locurl,
Packit f02de7
                                  server_portstr);
Packit 90a5c9
Packit f02de7
cleanup:
Packit 90a5c9
    /* Do not close the socket */
Packit f02de7
    if (backend) {
Packit f02de7
        backend->close = 1;
Packit f02de7
        ap_proxy_release_connection(scheme, backend, r->server);
Packit f02de7
    }
Packit 90a5c9
    return status;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void ap_proxy_http_register_hook(apr_pool_t *p)
Packit 90a5c9
{
Packit 90a5c9
    proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, NULL, APR_HOOK_FIRST);
Packit 90a5c9
    proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, NULL, APR_HOOK_FIRST);
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
AP_DECLARE_MODULE(proxy_wstunnel) = {
Packit 90a5c9
    STANDARD20_MODULE_STUFF,
Packit 90a5c9
    NULL,                       /* create per-directory config structure */
Packit 90a5c9
    NULL,                       /* merge per-directory config structures */
Packit 90a5c9
    NULL,                       /* create per-server config structure */
Packit 90a5c9
    NULL,                       /* merge per-server config structures */
Packit 90a5c9
    NULL,                       /* command apr_table_t */
Packit 90a5c9
    ap_proxy_http_register_hook /* register hooks */
Packit 90a5c9
};