diff --git a/fw/connection.h b/fw/connection.h index 60a4b1d2ef..419c3767b0 100644 --- a/fw/connection.h +++ b/fw/connection.h @@ -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) @@ -100,7 +108,7 @@ enum { struct sock *sk; \ void (*destructor)(void *); -typedef struct TfwConn { +typedef struct tfw_conn_t { TFW_CONN_COMMON; } TfwConn; @@ -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 }; /** @@ -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. */ diff --git a/fw/gfsm.h b/fw/gfsm.h index 5894684aec..e13b1f9643 100644 --- a/fw/gfsm.h +++ b/fw/gfsm.h @@ -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, @@ -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); diff --git a/fw/http.c b/fw/http.c index 290176116b..042c943620 100644 --- a/fw/http.c +++ b/fw/http.c @@ -2339,7 +2339,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)) { @@ -5931,6 +5930,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; @@ -5953,6 +5953,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); @@ -6098,6 +6099,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. diff --git a/fw/http_frame.h b/fw/http_frame.h index f28c18432e..e3a9651e8e 100644 --- a/fw/http_frame.h +++ b/fw/http_frame.h @@ -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); diff --git a/fw/http_sched_hash.c b/fw/http_sched_hash.c index 8e329503de..20d03cce97 100644 --- a/fw/http_sched_hash.c +++ b/fw/http_sched_hash.c @@ -129,6 +129,7 @@ __is_conn_suitable(TfwSrvConn *conn, bool hmonitor) { return (hmonitor || !tfw_srv_suspended((TfwServer *)conn->peer)) && !tfw_srv_conn_restricted(conn) + && !tfw_srv_conn_unscheduled(conn) && !tfw_srv_conn_busy(conn) && !tfw_srv_conn_queue_full(conn) && tfw_srv_conn_get_if_live(conn); @@ -333,6 +334,8 @@ tfw_sched_hash_add_conns(TfwServer *srv, TfwHashConnList *cl, size_t *seed, list_for_each_entry(conn, &srv->conn_list, list) { unsigned long hash; + if (tfw_srv_conn_unscheduled(conn)) + continue; do { hash = __hash_64(srv_hash ^ *seed); *seed += seed_inc; @@ -432,6 +435,30 @@ tfw_sched_hash_del_srv(TfwServer *srv) call_rcu(&cl->rcu, tfw_sched_hash_put_srv_data); } +static int +tfw_sched_hash_upd_srv(TfwServer *srv) +{ + size_t size, seed, seed_inc = 0; + TfwHashConnList *cl = rcu_dereference_bh_check(srv->sched_data, 1); + TfwHashConnList *cl_copy; + + seed = get_random_long(); + seed_inc = get_random_int(); + + size = sizeof(TfwHashConnList) + srv->conn_n * sizeof(TfwHashConn); + if (!(cl_copy = kzalloc(size, GFP_ATOMIC))) + return -ENOMEM; + + tfw_sched_hash_add_conns(srv, cl_copy, &seed, seed_inc); + + rcu_assign_pointer(srv->sched_data, cl_copy); + + if (cl) + call_rcu(&cl->rcu, tfw_sched_hash_put_srv_data); + + return 0; +} + static TfwScheduler tfw_sched_hash = { .name = "hash", .list = LIST_HEAD_INIT(tfw_sched_hash.list), @@ -439,6 +466,7 @@ static TfwScheduler tfw_sched_hash = { .del_grp = tfw_sched_hash_del_grp, .add_srv = tfw_sched_hash_add_srv, .del_srv = tfw_sched_hash_del_srv, + .upd_srv = tfw_sched_hash_upd_srv, .sched_sg_conn = tfw_sched_hash_get_sg_conn, .sched_srv_conn = tfw_sched_hash_get_srv_conn, }; diff --git a/fw/http_sched_ratio.c b/fw/http_sched_ratio.c index fb1fbc64c7..a3cc081f6d 100644 --- a/fw/http_sched_ratio.c +++ b/fw/http_sched_ratio.c @@ -30,6 +30,12 @@ #define TFW_SCHED_RATIO_INTVL (HZ / 20) /* The timer periodicity. */ +typedef struct { + struct rcu_head rcu; + size_t conn_n; + TfwSrvConn *conns[0]; +} TfwRatioSrvConnList; + /** * Individual upstream server descriptor. * @@ -38,17 +44,15 @@ * * @rcu - RCU control structure. * @srv - pointer to server structure. - * @conn - list of pointers to server connection structures. + * @cl - pointer to list of pointers to server connection structures. * @counter - monotonic counter for choosing the next connection. - * @conn_n - number of connections to server. * @seq - current sequence number for APM stats. */ typedef struct { struct rcu_head rcu; TfwServer *srv; - TfwSrvConn **conn; + TfwRatioSrvConnList *cl; atomic64_t counter; - size_t conn_n; unsigned int seq; } TfwRatioSrvDesc; @@ -827,12 +831,15 @@ static inline TfwSrvConn * __sched_srv(TfwRatioSrvDesc *srvdesc, int skipnip, int *nipconn) { size_t ci; + TfwRatioSrvConnList *cl = rcu_dereference_bh_check(srvdesc->cl, 1); - for (ci = 0; ci < srvdesc->conn_n; ++ci) { + rcu_read_lock_bh(); + for (ci = 0; ci < cl->conn_n; ++ci) { unsigned long idxval = atomic64_inc_return(&srvdesc->counter); - TfwSrvConn *srv_conn = srvdesc->conn[idxval % srvdesc->conn_n]; + TfwSrvConn *srv_conn = cl->conns[idxval % cl->conn_n]; if (unlikely(tfw_srv_conn_restricted(srv_conn) + || tfw_srv_conn_unscheduled(srv_conn) || tfw_srv_conn_busy(srv_conn) || tfw_srv_conn_queue_full(srv_conn))) continue; @@ -841,9 +848,12 @@ __sched_srv(TfwRatioSrvDesc *srvdesc, int skipnip, int *nipconn) ++(*nipconn); continue; } - if (likely(tfw_srv_conn_get_if_live(srv_conn))) + if (likely(tfw_srv_conn_get_if_live(srv_conn))) { + rcu_read_unlock_bh(); return srv_conn; + } } + rcu_read_unlock_bh(); return NULL; } @@ -981,7 +991,7 @@ tfw_sched_ratio_cleanup(TfwRatio *ratio) /* Data that is shared between pool entries. */ for (si = 0; si < ratio->srv_n; ++si) - kfree(ratio->srvdesc[si].conn); + kfree(ratio->srvdesc[si].cl); kfree(ratio->hstdata); kfree(ratio->rtodata); @@ -1039,28 +1049,32 @@ static int tfw_sched_ratio_srvdesc_setup_srv(TfwServer *srv, TfwRatioSrvDesc *srvdesc) { size_t size, ci = 0; - TfwSrvConn **conn, *srv_conn; + TfwSrvConn *srv_conn; + TfwRatioSrvConnList *cl; - size = sizeof(TfwSrvConn *) * srv->conn_n; - if (!(srvdesc->conn = kzalloc(size, GFP_KERNEL))) + size = sizeof(TfwRatioSrvConnList) + sizeof(TfwSrvConn *) * srv->conn_n; + if (!(cl = kzalloc(size, GFP_KERNEL))) return -ENOMEM; - conn = srvdesc->conn; list_for_each_entry(srv_conn, &srv->conn_list, list) { + if (tfw_srv_conn_unscheduled(srv_conn)) + continue; if (unlikely(ci++ == srv->conn_n)) goto err; - *conn++ = srv_conn; + + cl->conns[ci-1] = srv_conn; } if (unlikely(ci != srv->conn_n)) goto err; - srvdesc->conn_n = srv->conn_n; + cl->conn_n = ci; srvdesc->srv = srv; atomic64_set(&srvdesc->counter, 0); + rcu_assign_pointer(srvdesc->cl, cl); return 0; err: - kfree(srvdesc->conn); + kfree(srvdesc->cl); return -EINVAL; } @@ -1081,7 +1095,7 @@ tfw_sched_ratio_srvdesc_setup(TfwSrvGroup *sg, TfwRatio *ratio) int r; size_t si = 0; TfwServer *srv; - TfwRatioSrvDesc *srvdesc = ratio->srvdesc; + TfwRatioSrvDesc *srvdesc = rcu_dereference_bh_check(ratio->srvdesc, 1); list_for_each_entry(srv, &sg->srv_list, list) { if (unlikely((si++ == sg->srv_n) || !srv->conn_n @@ -1277,10 +1291,17 @@ static void tfw_sched_ratio_put_srv_data(struct rcu_head *rcu) { TfwRatioSrvDesc *srvdesc = container_of(rcu, TfwRatioSrvDesc, rcu); - kfree(srvdesc->conn); + kfree(srvdesc->cl); kfree(srvdesc); } +static void +tfw_sched_ratio_put_conn_data(struct rcu_head *rcu) +{ + TfwRatioSrvConnList *cl = container_of(rcu, TfwRatioSrvConnList, rcu); + kfree(cl); +} + static void tfw_sched_ratio_del_srv(TfwServer *srv) { @@ -1291,6 +1312,40 @@ tfw_sched_ratio_del_srv(TfwServer *srv) call_rcu(&srvdesc->rcu, tfw_sched_ratio_put_srv_data); } +static int +tfw_sched_ratio_upd_srv(TfwServer *srv) +{ + TfwRatioSrvDesc *srvdesc = rcu_dereference_bh_check(srv->sched_data, 1); + size_t size, ci = 0; + TfwRatioSrvConnList *cl_copy; + TfwRatioSrvConnList *cl = rcu_dereference_bh_check(srvdesc->cl, 1); + TfwSrvConn *srv_conn; + + size = sizeof(TfwRatioSrvConnList) + sizeof(TfwSrvConn *) * srv->conn_n; + if (!(cl_copy = kzalloc(size, GFP_ATOMIC))) + return -ENOMEM; + + list_for_each_entry(srv_conn, &srv->conn_list, list) { + if (tfw_srv_conn_unscheduled(srv_conn)) + continue; + if (unlikely(ci++ == srv->conn_n)) + goto err; + cl_copy->conns[ci-1] = srv_conn; + } + if (unlikely(ci != srv->conn_n)) + goto err; + cl->conn_n = ci; + rcu_assign_pointer(srvdesc->cl, cl_copy); + + if (srvdesc) + call_rcu(&cl->rcu, tfw_sched_ratio_put_conn_data); + + return 0; +err: + kfree(cl_copy); + return -EINVAL; +} + static TfwScheduler tfw_sched_ratio = { .name = "ratio", .list = LIST_HEAD_INIT(tfw_sched_ratio.list), @@ -1298,6 +1353,7 @@ static TfwScheduler tfw_sched_ratio = { .del_grp = tfw_sched_ratio_del_grp, .add_srv = tfw_sched_ratio_add_srv, .del_srv = tfw_sched_ratio_del_srv, + .upd_srv = tfw_sched_ratio_upd_srv, .sched_sg_conn = tfw_sched_ratio_sched_sg_conn, .sched_srv_conn = tfw_sched_ratio_sched_srv_conn, }; diff --git a/fw/http_sess.c b/fw/http_sess.c index 67d2c180d9..4e30810058 100644 --- a/fw/http_sess.c +++ b/fw/http_sess.c @@ -1304,6 +1304,7 @@ __try_conn(TfwMsg *msg, TfwSrvConn *srv_conn) return NULL; if (!tfw_srv_conn_restricted(srv_conn) + && !tfw_srv_conn_unscheduled(srv_conn) && !tfw_srv_conn_busy(srv_conn) && !tfw_srv_conn_queue_full(srv_conn) && !tfw_srv_conn_hasnip(srv_conn) @@ -1400,7 +1401,7 @@ tfw_http_sess_get_srv_conn(TfwMsg *msg) } /* * In unlikely but possible situations the same session will be tried - * on multiple cpus, use locking to guarantee theat the srv_conn + * on multiple cpus, use locking to guarantee that the srv_conn * will point to the same server for all of them, or requests from the * same session might be forwarded to different servers. */ diff --git a/fw/sched.c b/fw/sched.c index 5b9232124d..3f2f3a1d90 100644 --- a/fw/sched.c +++ b/fw/sched.c @@ -78,4 +78,3 @@ tfw_sched_unregister(TfwScheduler *sched) BUG_ON(list_empty(&sched->list)); list_del_init(&sched->list); } - diff --git a/fw/server.c b/fw/server.c index 391a32a9a7..322a96b7f5 100644 --- a/fw/server.c +++ b/fw/server.c @@ -46,7 +46,7 @@ static atomic64_t act_sg_n = ATOMIC64_INIT(0); * On the TempestaFW start or live reconfiguration a new configuration is * parsed and all server groups declared in the configuration are added into * sg_hash_reconfig. If the configuration is valid sg_hash_reconfig replaces - * sg_hash by tfw_sg_apply_reconfig(). Otherwize reconfig list is cleared by + * sg_hash by tfw_sg_apply_reconfig(). Otherwise reconfig list is cleared by * sock_srv.c * * The same server group instance may be listed in both sg_hash and diff --git a/fw/server.h b/fw/server.h index e1d002d9f0..d9bfa632a5 100644 --- a/fw/server.h +++ b/fw/server.h @@ -163,6 +163,9 @@ typedef struct { * Called in process context at re-configuration time. * @del_srv - delete single server added via add_srv. * Called in SoftIRQ context. + * @upd_srv - update (add or delete connections) single server. + * Called in SoftIRQ context. + * * @sched_sg_conn - virtual method. Schedule a request to a server from * given server group. Returns a server connection; * @sched_srv_conn - schedule a request to the given server. @@ -187,6 +190,7 @@ struct tfw_scheduler_t { void (*del_grp)(TfwSrvGroup *sg); int (*add_srv)(TfwServer *srv); void (*del_srv)(TfwServer *srv); + int (*upd_srv)(TfwServer *srv); TfwSrvConn *(*sched_sg_conn)(TfwMsg *msg, TfwSrvGroup *sg); TfwSrvConn *(*sched_srv_conn)(TfwMsg *msg, TfwServer *srv); }; @@ -340,4 +344,9 @@ TfwScheduler *tfw_sched_lookup(const char *name); int tfw_sched_register(TfwScheduler *sched); void tfw_sched_unregister(TfwScheduler *sched); +/* Public sock_srv routings */ +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); + #endif /* __SERVER_H__ */ diff --git a/fw/sock_srv.c b/fw/sock_srv.c index 41bd878649..b7be2080e8 100644 --- a/fw/sock_srv.c +++ b/fw/sock_srv.c @@ -64,7 +64,7 @@ * a socket is created first, and then there's a period of time while * a connection is being established. * - * TfwSrvConn{} instance goes though the following periods of life: + * TfwSrvConn{} instance goes through the following periods of life: * - First, a TfwSrvConn{} instance is allocated and set up with * data from configuration file. * - When a server socket is created, the TfwSrvConn{} instance @@ -217,7 +217,7 @@ tfw_srv_conn_release(TfwSrvConn *srv_conn) * Initiate a non-blocking connect attempt. * Returns immediately without waiting until a connection is established. */ -static void +void tfw_sock_srv_connect_try(TfwSrvConn *srv_conn) { int r; @@ -528,7 +528,7 @@ tfw_sock_srv_disconnect(TfwConn *conn) * Get reference to server and mark the connection as active, which means * that server must be put during connection release procedure. */ -static inline void +inline void tfw_sock_srv_conn_activate(TfwServer *srv, TfwSrvConn *srv_conn) { tfw_server_get(srv); @@ -631,7 +631,7 @@ tfw_srv_conn_free(TfwSrvConn *srv_conn) kmem_cache_free(tfw_srv_conn_cache, srv_conn); } -static TfwSrvConn * +TfwSrvConn * tfw_sock_srv_new_conn(TfwServer *srv) { TfwSrvConn *srv_conn; diff --git a/fw/t/unit/test_hpack.c b/fw/t/unit/test_hpack.c index 54a8adb48a..e254c44e06 100644 --- a/fw/t/unit/test_hpack.c +++ b/fw/t/unit/test_hpack.c @@ -23,6 +23,21 @@ #include "http_frame.c" #include "http.c" +void tfw_sock_srv_connect_try(TfwSrvConn *srv_conn) +{ + (void)srv_conn; +} +void tfw_sock_srv_conn_activate(TfwServer *srv, TfwSrvConn *srv_conn) +{ + (void)srv; + (void)srv_conn; +} +TfwSrvConn *tfw_sock_srv_new_conn(TfwServer *srv) +{ + (void)srv; + return NULL; +} + #include "test.h" #include "helpers.h" #include "tfw_str_helper.h"