Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade connection to websocket and provision new connection #1584

Closed
wants to merge 10 commits into from
23 changes: 21 additions & 2 deletions fw/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ enum {
/* HTTPS */
Conn_HttpsClnt = Conn_Clnt | TFW_FSM_HTTPS,
Conn_HttpsSrv = Conn_Srv | TFW_FSM_HTTPS,

/* Websocket plain */
Conn_WsClnt = Conn_HttpClnt | TFW_FSM_WS,
Conn_WsSrv = Conn_HttpSrv | TFW_FSM_WS,

/* Websocket secure */
Conn_WssClnt = Conn_HttpsClnt | TFW_FSM_WS,
Conn_WssSrv = Conn_HttpsSrv | TFW_FSM_WS,
};

#define TFW_CONN_TYPE2IDX(t) TFW_FSM_TYPE(t)
Expand Down Expand Up @@ -100,7 +108,7 @@ enum {
struct sock *sk; \
void (*destructor)(void *);

typedef struct TfwConn {
typedef struct tfw_conn_t {
TFW_CONN_COMMON;
} TfwConn;

Expand Down Expand Up @@ -199,7 +207,9 @@ enum {
/* Connection is in use or at least scheduled to be established. */
TFW_CONN_B_ACTIVE,
/* Connection is disconnected and stopped. */
TFW_CONN_B_STOPPED
TFW_CONN_B_STOPPED,
/* Mark connection as unavailable to schedulers */
TFW_CONN_B_UNSCHED
};

/**
Expand Down Expand Up @@ -297,6 +307,15 @@ tfw_srv_conn_restricted(TfwSrvConn *srv_conn)
return test_bit(TFW_CONN_B_RESEND, &srv_conn->flags);
}

/*
* Connection is unavailable to scheduler and may be removed from it
*/
static inline bool
tfw_srv_conn_unscheduled(TfwSrvConn *srv_conn)
{
return test_bit(TFW_CONN_B_UNSCHED, &srv_conn->flags);
}

/*
* Tell if a connection has non-idempotent requests.
*/
Expand Down
4 changes: 3 additions & 1 deletion fw/gfsm.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ enum {
/* Protocols */
TFW_FSM_HTTP,
TFW_FSM_HTTPS,
/* Not really a FSM */
TFW_FSM_WS,

/* Security rules enforcement. */
TFW_FSM_FRANG_REQ,
Expand Down Expand Up @@ -181,7 +183,7 @@ typedef struct {
& ((TFW_GFSM_FSM_MASK << TFW_GFSM_FSM_SHIFT) \
| TFW_GFSM_STATE_MASK))

typedef struct TfwConn TfwConn;
typedef struct tfw_conn_t TfwConn;
typedef int (*tfw_gfsm_handler_t)(TfwConn *conn, TfwFsmData *data);

void tfw_gfsm_state_init(TfwGState *st, void *obj, int st0);
Expand Down
105 changes: 100 additions & 5 deletions fw/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@
#include "sync_socket.h"
#include "lib/common.h"

void tfw_sock_srv_connect_try(TfwSrvConn *srv_conn);
void tfw_sock_srv_conn_activate(TfwServer *srv, TfwSrvConn *srv_conn);
TfwSrvConn *tfw_sock_srv_new_conn(TfwServer *srv);

#define S_H2_METHOD ":method"
#define S_H2_SCHEME ":scheme"
#define S_H2_AUTH ":authority"
Expand Down Expand Up @@ -2339,7 +2343,6 @@ static int
tfw_http_conn_init(TfwConn *conn)
{
T_DBG2("%s: conn=[%p]\n", __func__, conn);

if (TFW_CONN_TYPE(conn) & Conn_Srv) {
TfwSrvConn *srv_conn = (TfwSrvConn *)conn;
if (!list_empty(&srv_conn->fwd_queue)) {
Expand Down Expand Up @@ -2572,6 +2575,33 @@ tfw_http_set_hdr_date(TfwHttpMsg *hm)
return r;
}

/*
* Add 'Upgrade:' header for websocket upgrade messages
*/
static int
tfw_http_set_hdr_upgrade(TfwHttpMsg *hm, bool is_resp)
{
int r = 0;

if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags)) {
if (is_resp && ((TfwHttpResp *)hm)->status == 101
&& test_bit(TFW_HTTP_B_UPGRADE_EXTRA, hm->flags))
{
T_ERR("Unable to add uncompliant Upgrade: header "
"to msg [%p]\n", hm);
return -EINVAL;
}
r = tfw_http_msg_hdr_xfrm(hm, "upgrade", SLEN("upgrade"),
"websocket", SLEN("websocket"),
TFW_HTTP_HDR_UPGRADE, 0);
if (r)
T_ERR("Unable to add Upgrade: header to msg [%p]\n", hm);
else
T_DBG2("Added Upgrade: header to msg [%p]\n", hm);
}
return r;
}

/*
* Expand HTTP response with 'Date:' header field.
*/
Expand Down Expand Up @@ -2677,23 +2707,40 @@ tfw_http_expand_hbh(TfwHttpResp *resp, unsigned short status)
static int
tfw_http_set_hdr_connection(TfwHttpMsg *hm, unsigned long conn_flg)
{
int r;
BUILD_BUG_ON(BIT_WORD(__TFW_HTTP_MSG_M_CONN) != 0);
if (((hm->flags[0] & __TFW_HTTP_MSG_M_CONN) == conn_flg)
&& (!TFW_STR_EMPTY(&hm->h_tbl->tbl[TFW_HTTP_HDR_CONNECTION]))
&& !test_bit(TFW_HTTP_B_CONN_EXTRA, hm->flags))
&& !test_bit(TFW_HTTP_B_CONN_EXTRA, hm->flags)
&& !test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags))
{
return 0;
}

switch (conn_flg) {
case BIT(TFW_HTTP_B_CONN_CLOSE):
return TFW_HTTP_MSG_HDR_XFRM(hm, "Connection", "close",
TFW_HTTP_HDR_CONNECTION, 0);
case BIT(TFW_HTTP_B_CONN_KA):
return TFW_HTTP_MSG_HDR_XFRM(hm, "Connection", "keep-alive",
TFW_HTTP_HDR_CONNECTION, 0);
r = TFW_HTTP_MSG_HDR_XFRM(hm, "Connection", "keep-alive",
TFW_HTTP_HDR_CONNECTION, 0);
break;
default:
return TFW_HTTP_MSG_HDR_DEL(hm, "Connection",
r = TFW_HTTP_MSG_HDR_DEL(hm, "Connection",
TFW_HTTP_HDR_CONNECTION);
}

if (r < 0)
return r;

if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags)
&& test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags))
{
r = TFW_HTTP_MSG_HDR_XFRM(hm, "Connection", "upgrade",
TFW_HTTP_HDR_CONNECTION, 1);
}

return r;
}

/**
Expand Down Expand Up @@ -3072,6 +3119,10 @@ tfw_h1_adjust_req(TfwHttpReq *req)
if (r < 0)
return r;

r = tfw_http_set_hdr_upgrade(hm, false);
if (r < 0)
return r;

r = tfw_h1_set_loc_hdrs(hm, false, false);
if (r < 0)
return r;
Expand Down Expand Up @@ -3642,6 +3693,10 @@ tfw_http_adjust_resp(TfwHttpResp *resp)
if (r < 0)
return r;

r = tfw_http_set_hdr_upgrade(hm, true);
if (r < 0)
return r;

r = tfw_http_set_hdr_keep_alive(hm, conn_flg);
if (r < 0)
return r;
Expand Down Expand Up @@ -5854,6 +5909,7 @@ tfw_http_resp_process(TfwConn *conn, TfwStream *stream, struct sk_buff *skb)
unsigned int chunks_unused, parsed;
TfwHttpReq *bad_req;
TfwHttpMsg *hmresp, *hmsib;
TfwHttpResp *resp;
TfwFsmData data_up;
bool conn_stop, filtout = false;

Expand All @@ -5876,6 +5932,7 @@ tfw_http_resp_process(TfwConn *conn, TfwStream *stream, struct sk_buff *skb)
parsed = 0;
hmsib = NULL;
hmresp = (TfwHttpMsg *)stream->msg;
resp = (TfwHttpResp *)hmresp;

r = ss_skb_process(skb, tfw_http_parse_resp, hmresp, &chunks_unused,
&parsed);
Expand Down Expand Up @@ -6021,6 +6078,44 @@ tfw_http_resp_process(TfwConn *conn, TfwStream *stream, struct sk_buff *skb)
r = TFW_PASS;
goto next_resp;
}

/*
* Upgrade client and server connection to websocket, remove it
* from scheduler and provision new connection.
*
* TODO #755: set existent client and server connection to Conn_Ws*
* when websocket proxing protocol will be implemented
*/
if (unlikely(test_bit(TFW_HTTP_B_CONN_UPGRADE, hmresp->flags)
&& test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hmresp->flags)
&& resp->status == 101))
{
TfwServer *srv = (TfwServer *)resp->conn->peer;
TfwSrvConn *srv_conn;

/* Cannot proceed with upgrade websocket due to error
* in creation of new http connection. While it will not be
* inherently erroneous to upgrade existing connection, but
* we would pay for it with essentially dropping connection with
* server. Better just drop upgrade request and
* reestablish connection.
*/
if (!(srv_conn = tfw_sock_srv_new_conn(srv))) {
tfw_http_conn_error_log(conn, "Can't create new "
"connection for websocket"
" upgrade response");
return TFW_BLOCK;
}

set_bit(TFW_CONN_B_UNSCHED,
&((TfwSrvConn *)hmresp->conn)->flags);

tfw_sock_srv_conn_activate(srv, srv_conn);
tfw_sock_srv_connect_try(srv_conn);

srv->sg->sched->upd_srv(srv);
}

/*
* Pass the response to cache for further processing.
* In the end, the response is sent on to the client.
Expand Down
16 changes: 11 additions & 5 deletions fw/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ typedef struct {
* Http headers table.
*
* Singular headers (in terms of RFC 7230 3.2.2) go first to protect header
* repetition attacks. See __hdr_is_singular() and don't forget to
* update the static headers array when add a new singular header here.
* If the new header is hop-by-hop (must not be forwarded and cached by Tempesta)
* it must be listed in tfw_http_init_parser_req()/tfw_http_init_parser_resp()
* for unconditionally hop-by-hop header or in __parse_connection() otherwize.
* repetition attacks. See __hdr_is_singular() and don't forget to update the
* static headers array when add a new singular header here. If the new header
* is hop-by-hop (must not be forwarded and cached by Tempesta) it must be
* listed in tfw_http_init_parser_req()/tfw_http_init_parser_resp()
* for unconditionally hop-by-hop header or in __parse_connection() otherwise.
* If the header is end-to-end it must be listed in __hbh_parser_add_data().
*
* Note: don't forget to update __http_msg_hdr_val() and
Expand Down Expand Up @@ -212,6 +212,7 @@ typedef enum {
TFW_HTTP_HDR_X_FORWARDED_FOR,
TFW_HTTP_HDR_KEEP_ALIVE,
TFW_HTTP_HDR_TRANSFER_ENCODING,
TFW_HTTP_HDR_UPGRADE,

/* Start of list of generic (raw) headers. */
TFW_HTTP_HDR_RAW,
Expand Down Expand Up @@ -239,7 +240,12 @@ enum {
*/
TFW_HTTP_B_CONN_CLOSE = TFW_HTTP_FLAGS_COMMON,
TFW_HTTP_B_CONN_KA,
TFW_HTTP_B_CONN_UPGRADE,
TFW_HTTP_B_CONN_EXTRA,
/* Message is a websocket upgrade request */
TFW_HTTP_B_UPGRADE_WEBSOCKET,
/* Message upgrade header contains extra fields */
TFW_HTTP_B_UPGRADE_EXTRA,
/* Chunked is last transfer encoding. */
TFW_HTTP_B_CHUNKED,
/* Chunked in the middle of applied transfer encodings. */
Expand Down
2 changes: 1 addition & 1 deletion fw/http_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ typedef struct {
unsigned char data_off;
} TfwH2Ctx;

typedef struct TfwConn TfwConn;
typedef struct tfw_conn_t TfwConn;

int tfw_h2_init(void);
void tfw_h2_cleanup(void);
Expand Down
50 changes: 50 additions & 0 deletions fw/http_limits.c
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,49 @@ frang_http_methods_override(const TfwHttpReq *req, FrangAcc *ra,
return TFW_PASS;
}

static int
frang_http_upgrade_websocket(const TfwHttpReq *req, FrangAcc *ra,
FrangVhostCfg *f_cfg)
{
BUG_ON(!req);

switch (req->version) {
/*
* TODO upgrade websocket checks for h2 as described in RFC8441
*/
case TFW_HTTP_VER_20:
break;
/*
* Tempesta FW MUST block requests with Upgrade header but without
* upgrade option in Connection header. Tempesta FW MUST ignore
* Upgrade header for HTTP version less then HTTP/1.1.
* See RFC7230#section-6.1.
*/
case TFW_HTTP_VER_11:
case TFW_HTTP_VER_10:
case TFW_HTTP_VER_09:
if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, req->flags)
&& !test_bit(TFW_HTTP_B_CONN_UPGRADE, req->flags))
{
frang_msg("upgrade request without connection option",
&FRANG_ACC2CLI(ra)->addr, ": protocol: %s\n",
"websocket");
return TFW_BLOCK;
}
if (req->version == TFW_HTTP_VER_10
|| req->version == TFW_HTTP_VER_09)
{
clear_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET,
((TfwHttpReq *)req)->flags);
}
break;
default:
return TFW_BLOCK;
}

return TFW_PASS;
}

static int
frang_http_ct_check(const TfwHttpReq *req, FrangAcc *ra, FrangCtVals *ct_vals)
{
Expand Down Expand Up @@ -1189,6 +1232,13 @@ frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data,
if (f_cfg->http_ct_required || f_cfg->http_ct_vals)
r = frang_http_ct_check(req, ra, f_cfg->http_ct_vals);

/* Do checks for websocket upgrade */
if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, req->flags)
&& (r = frang_http_upgrade_websocket(req, ra, f_cfg)))
{
T_FSM_EXIT();
}

__FRANG_FSM_MOVE(Frang_Req_Body_Start);
}

Expand Down
4 changes: 4 additions & 0 deletions fw/http_msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ tfw_http_msg_resp_spec_hid(const TfwStr *hdr)
TfwStrDefV("transfer-encoding:",TFW_HTTP_HDR_TRANSFER_ENCODING),
TfwStrDefV("x-forwarded-for:", TFW_HTTP_HDR_X_FORWARDED_FOR),
TfwStrDefV("x-tempesta-cache:", TFW_HTTP_HDR_X_TEMPESTA_CACHE),
TfwStrDefV("upgrade:", TFW_HTTP_HDR_UPGRADE),
};

BUILD_BUG_ON(ARRAY_SIZE(resp_hdrs) !=
Expand Down Expand Up @@ -182,6 +183,7 @@ tfw_http_msg_req_spec_hid(const TfwStr *hdr)
TfwStrDefV("user-agent:", TFW_HTTP_HDR_USER_AGENT),
TfwStrDefV("x-forwarded-for:", TFW_HTTP_HDR_X_FORWARDED_FOR),
TfwStrDefV("x-tempesta-cache:", TFW_HTTP_HDR_X_TEMPESTA_CACHE),
TfwStrDefV("upgrade:", TFW_HTTP_HDR_UPGRADE),
};

BUILD_BUG_ON(ARRAY_SIZE(req_hdrs) !=
Expand Down Expand Up @@ -215,6 +217,7 @@ __http_msg_hdr_val(TfwStr *hdr, unsigned id, TfwStr *val, bool client)
[TFW_HTTP_HDR_SET_COOKIE] = SLEN("Set-Cookie:"),
[TFW_HTTP_HDR_ETAG] = SLEN("ETag:"),
[TFW_HTTP_HDR_REFERER] = SLEN("Referer:"),
[TFW_HTTP_HDR_UPGRADE] = SLEN("Upgrade:"),
},
(unsigned char []) {
[TFW_HTTP_HDR_HOST] = SLEN("Host:"),
Expand All @@ -229,6 +232,7 @@ __http_msg_hdr_val(TfwStr *hdr, unsigned id, TfwStr *val, bool client)
[TFW_HTTP_HDR_COOKIE] = SLEN("Cookie:"),
[TFW_HTTP_HDR_IF_NONE_MATCH] = SLEN("If-None-Match:"),
[TFW_HTTP_HDR_REFERER] = SLEN("Referer:"),
[TFW_HTTP_HDR_UPGRADE] = SLEN("Upgrade:"),
},
};
TfwStr *c, *end;
Expand Down
Loading