Alexander Bluhm([email protected]) on 2019.03.04 17:44:08 +0100:
> On Sat, Mar 02, 2019 at 12:13:20AM +0100, Sebastian Benoit wrote:
> > --- usr.sbin/relayd/parse.y
> > +++ usr.sbin/relayd/parse.y
> > @@ -176,6 +176,7 @@ typedef struct {
> >  %token     TO ROUTER RTLABEL TRANSPARENT TRAP UPDATES URL VIRTUAL WITH TTL 
> > RTABLE
> >  %token     MATCH PARAMS RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE 
> > PASSWORD ECDHE
> >  %token     EDH TICKETS CONNECTION CONNECTIONS ERRORS STATE CHANGES CHECKS
> > +%token  WEBSOCKETS
> >  %token     <v.string>      STRING
> >  %token  <v.number> NUMBER
> >  %type      <v.string>      hostname interface table value optstring
> 
> Here are some tab vs space incosistencies.

fixed.
 
> > +                           if (strcasecmp("Connection", key) == 0 &&
> > +                               strcasecmp("Upgrade", value) == 0)
> > +                                   priv->http_upgrade_req |=
> > +                                       HTTP_UPGRADE_CONN;
> 
> Would the name HTTP_CONNECTION_UPGRADE be better?  Perhaps we want
> to do something spcific with connection keep-alive or close some
> day.

changed.

> > @@ -422,6 +445,28 @@ relay_read_http(struct bufferevent *bev, void *arg)
> >                     return;
> >             }
> >  
> > +           /* HTTP 101 Switching Protocols */
> > +           if (cre->dir == RELAY_DIR_REQUEST) {
> > +                   if (!(proto->httpflags & HTTPFLAG_WEBSOCKETS) &&
> > +                       (priv->http_upgrade_req &
> > +                       HTTP_UPGRADE_WEBSOCKET)) {
> 
> Should we check for both upgrade and websocket?

i am now checking:
1. 'Upgrade: websocket' hdr AND NOT "http { websockets }" --> 403
2. 'Upgrade: websocket' hdr AND NO 'Connection: upgrade' hdr --> 400
3. 'Upgrade: websocket' hdr AND method NOT "GET" --> 405

> > +                           relay_abort_http(con, 403,
> > +                               "Forbidden", 0);
> 
> A more specific error message like "Websocket Forbidden" would make
> debugging much easier.

done in all cases

> The RFC says it must be a GET request.  We should check at least
> this.  If we check more, an attacker can create less dubious states.

thx, I was looking for something like that and could not find it.
Where?

Implemented.

> > +                           return;
> > +                   }
> > +           } else if (cre->dir == RELAY_DIR_RESPONSE &&
> > +               desc->http_status == 101) {
> > +                   if ((priv->http_upgrade_req &
> > +                       (HTTP_UPGRADE_CONN | HTTP_UPGRADE_WEBSOCKET)) &&
> 
> Both flags must be present.

oops. fixed.

> 
> > +                       proto->httpflags & HTTPFLAG_WEBSOCKETS) {
> > +                           cre->dst->toread = TOREAD_UNLIMITED;
> > +                           cre->dst->bev->readcb = relay_read;
> > +                   }  else  {
> > +                           relay_abort_http(con, 502, "Bad Gateway", 0);
> 
> Could we have a more specific message like "Bad Websocket Gateway"?
> 
> > +                           return;
> > +                   }
> > +           }
> > +
> >             switch (desc->http_method) {
> >             case HTTP_METHOD_CONNECT:
> >                     /* Data stream */
> 
> bluhm
> 

(relayd_101Switching_policy4.diff)

diff --git usr.sbin/relayd/http.h usr.sbin/relayd/http.h
index 052bc0ce326..135ca5bbcb7 100644
--- usr.sbin/relayd/http.h
+++ usr.sbin/relayd/http.h
@@ -251,4 +251,10 @@ struct http_descriptor {
        struct kvtree            http_headers;
 };
 
+struct relay_http_priv {
+#define HTTP_CONNECTION_UPGRADE        0x01
+#define HTTP_UPGRADE_WEBSOCKET 0x02
+       int                      http_upgrade_req;
+};
+
 #endif /* HTTP_H */
diff --git usr.sbin/relayd/parse.y usr.sbin/relayd/parse.y
index 9875973fd80..66a568d5e62 100644
--- usr.sbin/relayd/parse.y
+++ usr.sbin/relayd/parse.y
@@ -176,6 +176,7 @@ typedef struct {
 %token TO ROUTER RTLABEL TRANSPARENT TRAP UPDATES URL VIRTUAL WITH TTL RTABLE
 %token MATCH PARAMS RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE PASSWORD ECDHE
 %token EDH TICKETS CONNECTION CONNECTIONS ERRORS STATE CHANGES CHECKS
+%token WEBSOCKETS
 %token <v.string>      STRING
 %token  <v.number>     NUMBER
 %type  <v.string>      hostname interface table value optstring
@@ -1064,8 +1065,20 @@ protoptsl        : ssltls tlsflags
                | ssltls '{' tlsflags_l '}'
                | TCP tcpflags
                | TCP '{' tcpflags_l '}'
-               | HTTP httpflags
-               | HTTP '{' httpflags_l '}'
+               | HTTP {
+                       if (proto->type != RELAY_PROTO_HTTP) {
+                               yyerror("can set http options only for "
+                                   "http protocol");
+                               YYERROR;
+                       }
+               } httpflags
+               | HTTP  {
+                       if (proto->type != RELAY_PROTO_HTTP) {
+                               yyerror("can set http options only for "
+                                   "http protocol");
+                               YYERROR;
+                       }
+               } '{' httpflags_l '}'
                | RETURN ERROR opteflags        { proto->flags |= F_RETURN; }
                | RETURN ERROR '{' eflags_l '}' { proto->flags |= F_RETURN; }
                | filterrule
@@ -1078,17 +1091,14 @@ httpflags_l     : httpflags comma httpflags_l
                ;
 
 httpflags      : HEADERLEN NUMBER      {
-                       if (proto->type != RELAY_PROTO_HTTP) {
-                               yyerror("can set http options only for "
-                                   "http protocol");
-                               YYERROR;
-                       }
                        if ($2 < 0 || $2 > RELAY_MAXHEADERLENGTH) {
                                yyerror("invalid headerlen: %d", $2);
                                YYERROR;
                        }
                        proto->httpheaderlen = $2;
                }
+               | WEBSOCKETS    { proto->httpflags |= HTTPFLAG_WEBSOCKETS; }
+               | NO WEBSOCKETS { proto->httpflags &= ~HTTPFLAG_WEBSOCKETS; }
                ;
 
 tcpflags_l     : tcpflags comma tcpflags_l
@@ -2338,6 +2348,7 @@ lookup(char *s)
                { "url",                URL },
                { "value",              VALUE },
                { "virtual",            VIRTUAL },
+               { "websockets",         WEBSOCKETS },
                { "with",               WITH }
        };
        const struct keywords   *p;
diff --git usr.sbin/relayd/relay.c usr.sbin/relayd/relay.c
index 739c9226b65..bf662b9a1df 100644
--- usr.sbin/relayd/relay.c
+++ usr.sbin/relayd/relay.c
@@ -1410,7 +1410,13 @@ relay_session(struct rsession *con)
                return;
        }
 
-       if (rlay->rl_proto->type != RELAY_PROTO_HTTP) {
+       if (rlay->rl_proto->type == RELAY_PROTO_HTTP) {
+               if (relay_http_priv_init(con) == -1) {
+                       relay_close(con,
+                           "failed to allocate http session data", 1);
+                       return;
+               }
+       } else {
                if (rlay->rl_conf.fwdmode == FWD_TRANS)
                        relay_bindanyreq(con, 0, IPPROTO_TCP);
                else if (relay_connect(con) == -1) {
diff --git usr.sbin/relayd/relay_http.c usr.sbin/relayd/relay_http.c
index a9d27bfe605..e431d2b8f44 100644
--- usr.sbin/relayd/relay_http.c
+++ usr.sbin/relayd/relay_http.c
@@ -109,6 +109,17 @@ relay_http_init(struct relay *rlay)
        relay_calc_skip_steps(&rlay->rl_proto->rules);
 }
 
+int
+relay_http_priv_init(struct rsession *con)
+{
+       struct relay_http_priv  *p;
+       if ((p = calloc(1, sizeof(struct relay_http_priv))) == NULL)
+               return (-1);
+
+       con->se_priv = p;
+       return (0);
+}
+
 int
 relay_httpdesc_init(struct ctl_relay_event *cre)
 {
@@ -152,6 +163,7 @@ relay_read_http(struct bufferevent *bev, void *arg)
        struct relay            *rlay = con->se_relay;
        struct protocol         *proto = rlay->rl_proto;
        struct evbuffer         *src = EVBUFFER_INPUT(bev);
+       struct relay_http_priv  *priv = con->se_priv;
        char                    *line = NULL, *key, *value;
        char                    *urlproto, *host, *path;
        int                      action, unique, ret;
@@ -386,6 +398,17 @@ relay_read_http(struct bufferevent *bev, void *arg)
                        unique = 0;
 
                if (cre->line != 1) {
+                       if (cre->dir == RELAY_DIR_REQUEST) {
+                               if (strcasecmp("Connection", key) == 0 &&
+                                   strcasecmp("Upgrade", value) == 0)
+                                       priv->http_upgrade_req |=
+                                           HTTP_CONNECTION_UPGRADE;
+                               if (strcasecmp("Upgrade", key) == 0 &&
+                                   strcasecmp("websocket", value) == 0)
+                                       priv->http_upgrade_req |=
+                                           HTTP_UPGRADE_WEBSOCKET;
+                       }
+
                        if ((hdr = kv_add(&desc->http_headers, key,
                            value, unique)) == NULL) {
                                relay_abort_http(con, 400,
@@ -422,6 +445,43 @@ relay_read_http(struct bufferevent *bev, void *arg)
                        return;
                }
 
+               /* HTTP 101 Switching Protocols */
+               if (cre->dir == RELAY_DIR_REQUEST) {
+                       if ((priv->http_upgrade_req & HTTP_UPGRADE_WEBSOCKET) &&
+                           !(proto->httpflags & HTTPFLAG_WEBSOCKETS)) {
+                               relay_abort_http(con, 403,
+                                   "Websocket Forbidden", 0);
+                               return;
+                       }
+                       if ((priv->http_upgrade_req & HTTP_UPGRADE_WEBSOCKET) &&
+                           !(priv->http_upgrade_req & HTTP_CONNECTION_UPGRADE))
+                       {
+                               relay_abort_http(con, 400,
+                                   "Bad Websocket Request", 0);
+                               return;
+                       }
+                       if ((priv->http_upgrade_req & HTTP_UPGRADE_WEBSOCKET) &&
+                           (desc->http_method != HTTP_METHOD_GET)) {
+                               relay_abort_http(con, 405,
+                                   "Websocket Method Not Allowed", 0);
+                               return;
+                       }
+               } else if (cre->dir == RELAY_DIR_RESPONSE &&
+                   desc->http_status == 101) {
+                       if (((priv->http_upgrade_req &
+                           (HTTP_CONNECTION_UPGRADE | HTTP_UPGRADE_WEBSOCKET))
+                           ==
+                           (HTTP_CONNECTION_UPGRADE | HTTP_UPGRADE_WEBSOCKET))
+                           && proto->httpflags & HTTPFLAG_WEBSOCKETS) {
+                               cre->dst->toread = TOREAD_UNLIMITED;
+                               cre->dst->bev->readcb = relay_read;
+                       }  else  {
+                               relay_abort_http(con, 502,
+                                   "Bad Websocket Gateway", 0);
+                               return;
+                       }
+               }
+
                switch (desc->http_method) {
                case HTTP_METHOD_CONNECT:
                        /* Data stream */
diff --git usr.sbin/relayd/relayd.conf.5 usr.sbin/relayd/relayd.conf.5
index d43ffa06627..5a651784060 100644
--- usr.sbin/relayd/relayd.conf.5
+++ usr.sbin/relayd/relayd.conf.5
@@ -1006,6 +1006,10 @@ Valid options are:
 .It Ic headerlen Ar number
 Set the maximum size of all HTTP headers in bytes.
 The default value is 8192 and it is limited to a maximum of 131072.
+.It Ic websockets
+Allow connection upgrade to websocket protocol.
+The default is
+.Ic no websockets .
 .El
 .El
 .Sh FILTER RULES
diff --git usr.sbin/relayd/relayd.h usr.sbin/relayd/relayd.h
index fe55c3a8478..121aa5bd13e 100644
--- usr.sbin/relayd/relayd.h
+++ usr.sbin/relayd/relayd.h
@@ -702,6 +702,8 @@ struct relay_ticket_key {
 };
 #define        TLS_SESSION_LIFETIME    (2 * 3600)
 
+#define HTTPFLAG_WEBSOCKETS    0x01
+
 struct protocol {
        objid_t                  id;
        u_int32_t                flags;
@@ -711,6 +713,7 @@ struct protocol {
        u_int8_t                 tcpipttl;
        u_int8_t                 tcpipminttl;
        size_t                   httpheaderlen;
+       int                      httpflags;
        u_int8_t                 tlsflags;
        char                     tlsciphers[768];
        char                     tlsdhparams[128];
@@ -1227,6 +1230,7 @@ const char
        *relay_httpmethod_byid(u_int);
 const char
        *relay_httperror_byid(u_int);
+int     relay_http_priv_init(struct rsession *);
 int     relay_httpdesc_init(struct ctl_relay_event *);
 ssize_t         relay_http_time(time_t, char *, size_t);
 

Reply via email to