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);