diff --git a/tempesta_fw/cache.c b/tempesta_fw/cache.c index a9dc0828e..c8026ac4d 100644 --- a/tempesta_fw/cache.c +++ b/tempesta_fw/cache.c @@ -689,7 +689,8 @@ tfw_handle_validation_req(TfwHttpReq *req, TfwCacheEntry *ce) || (req->method == TFW_HTTP_METH_HEAD)) __send_304(req, ce); else - tfw_http_send_412(req); + HTTP_SEND_RESP(req, 412, "request validatin:" + " precondition failed"); return false; } @@ -1397,14 +1398,14 @@ tfw_cache_purge_method(TfwHttpReq *req) /* Deny PURGE requests by default. */ if (!(cache_cfg.cache && vhost->cache_purge && vhost->cache_purge_acl)) { - tfw_http_send_403(req, "purge: not configured"); + HTTP_SEND_RESP(req, 403, "purge: not configured"); return; } /* Accept requests from configured hosts only. */ ss_getpeername(req->conn->sk, &saddr); if (!tfw_capuacl_match(vhost, &saddr)) { - tfw_http_send_403(req, "purge: ACL violation"); + HTTP_SEND_RESP(req, 403, "purge: ACL violation"); return; } @@ -1414,14 +1415,14 @@ tfw_cache_purge_method(TfwHttpReq *req) ret = tfw_cache_purge_invalidate(req); break; default: - tfw_http_send_403(req, "purge: invalid option"); + HTTP_SEND_RESP(req, 403, "purge: invalid option"); return; } if (ret) - tfw_http_send_404(req, "purge: processing error"); + HTTP_SEND_RESP(req, 404, "purge: processing error"); else - tfw_http_send_200(req); + HTTP_SEND_RESP(req, 200, "purge: success"); } /** @@ -1606,7 +1607,7 @@ cache_req_process_node(TfwHttpReq *req, tfw_http_cache_cb_t action) resp->flags |= TFW_HTTP_RESP_STALE; out: if (!resp && (req->cache_ctl.flags & TFW_HTTP_CC_OIFCACHED)) - tfw_http_send_504(req, "resource not cached"); + HTTP_SEND_RESP(req, 504, "resource not cached"); else /* * TODO: RFC 7234 4.3.2: Extend preconditional request headers diff --git a/tempesta_fw/http.c b/tempesta_fw/http.c index ac61437af..191380d74 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -34,12 +34,12 @@ #include "sync_socket.h" -extern unsigned short tfw_blk_flags; - #define RESP_BUF_LEN 128 static DEFINE_PER_CPU(char[RESP_BUF_LEN], g_buf); int ghprio; /* GFSM hook priority. */ +extern unsigned short tfw_blk_flags; + #define S_CRLFCRLF "\r\n\r\n" #define S_HTTP "http://" @@ -68,6 +68,120 @@ int ghprio; /* GFSM hook priority. */ #define S_H_CONN_KA S_F_CONNECTION S_V_CONN_KA S_CRLFCRLF #define S_H_CONN_CLOSE S_F_CONNECTION S_V_CONN_CLOSE S_CRLFCRLF +#define S_200_PART_01 S_200 S_CRLF S_F_DATE +#define S_200_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF +#define S_403_PART_01 S_403 S_CRLF S_F_DATE +#define S_403_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF +#define S_404_PART_01 S_404 S_CRLF S_F_DATE +#define S_404_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF +#define S_412_PART_01 S_412 S_CRLF S_F_DATE +#define S_412_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF +#define S_500_PART_01 S_500 S_CRLF S_F_DATE +#define S_500_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF +#define S_502_PART_01 S_502 S_CRLF S_F_DATE +#define S_502_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF +#define S_504_PART_01 S_504 S_CRLF S_F_DATE +#define S_504_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF + +/* + * Array with predefined response data + */ +static TfwStr http_predef_resps[RESP_NUM] = { + [RESP_200] = { + .ptr = (TfwStr []){ + { .ptr = S_200_PART_01, .len = SLEN(S_200_PART_01) }, + { .ptr = NULL, .len = SLEN(S_V_DATE) }, + { .ptr = S_200_PART_02, .len = SLEN(S_200_PART_02) }, + { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, + { .ptr = NULL, .len = 0 }, + }, + .len = SLEN(S_200_PART_01 S_V_DATE S_200_PART_02 S_CRLF), + .flags = 4 << TFW_STR_CN_SHIFT + }, + [RESP_403] = { + .ptr = (TfwStr []){ + { .ptr = S_403_PART_01, .len = SLEN(S_403_PART_01) }, + { .ptr = NULL, .len = SLEN(S_V_DATE) }, + { .ptr = S_403_PART_02, .len = SLEN(S_403_PART_02) }, + { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, + { .ptr = NULL, .len = 0 }, + }, + .len = SLEN(S_403_PART_01 S_V_DATE S_403_PART_02 S_CRLF), + .flags = 4 << TFW_STR_CN_SHIFT + }, + [RESP_404] = { + .ptr = (TfwStr []){ + { .ptr = S_404_PART_01, .len = SLEN(S_404_PART_01) }, + { .ptr = NULL, .len = SLEN(S_V_DATE) }, + { .ptr = S_404_PART_02, .len = SLEN(S_404_PART_02) }, + { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, + { .ptr = NULL, .len = 0 }, + }, + .len = SLEN(S_404_PART_01 S_V_DATE S_404_PART_02 S_CRLF), + .flags = 4 << TFW_STR_CN_SHIFT + }, + [RESP_412] = { + .ptr = (TfwStr []){ + { .ptr = S_412_PART_01, .len = SLEN(S_412_PART_01) }, + { .ptr = NULL, .len = SLEN(S_V_DATE) }, + { .ptr = S_412_PART_02, .len = SLEN(S_412_PART_02) }, + { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, + { .ptr = NULL, .len = 0 }, + }, + .len = SLEN(S_412_PART_01 S_V_DATE S_412_PART_02 S_CRLF), + .flags = 4 << TFW_STR_CN_SHIFT + }, + [RESP_500] = { + .ptr = (TfwStr []){ + { .ptr = S_500_PART_01, .len = SLEN(S_500_PART_01) }, + { .ptr = NULL, .len = SLEN(S_V_DATE) }, + { .ptr = S_500_PART_02, .len = SLEN(S_500_PART_02) }, + { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, + { .ptr = NULL, .len = 0 }, + }, + .len = SLEN(S_500_PART_01 S_V_DATE S_500_PART_02 S_CRLF), + .flags = 4 << TFW_STR_CN_SHIFT + }, + [RESP_502] = { + .ptr = (TfwStr []){ + { .ptr = S_502_PART_01, .len = SLEN(S_502_PART_01) }, + { .ptr = NULL, .len = SLEN(S_V_DATE) }, + { .ptr = S_502_PART_02, .len = SLEN(S_502_PART_02) }, + { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, + { .ptr = NULL, .len = 0 }, + }, + .len = SLEN(S_502_PART_01 S_V_DATE S_502_PART_02 S_CRLF), + .flags = 4 << TFW_STR_CN_SHIFT + }, + [RESP_504] = { + .ptr = (TfwStr []){ + { .ptr = S_504_PART_01, .len = SLEN(S_504_PART_01) }, + { .ptr = NULL, .len = SLEN(S_V_DATE) }, + { .ptr = S_504_PART_02, .len = SLEN(S_504_PART_02) }, + { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, + { .ptr = NULL, .len = 0 }, + }, + .len = SLEN(S_504_PART_01 S_V_DATE S_504_PART_02 S_CRLF), + .flags = 4 << TFW_STR_CN_SHIFT + } +}; + +static TfwStr http_4xx_resp_body = { + .ptr = (TfwStr []){ + { .ptr = NULL, .len = 0 }, + { .ptr = NULL, .len = 0 }, + }, + .len = 0, +}; + +static TfwStr http_5xx_resp_body = { + .ptr = (TfwStr []){ + { .ptr = NULL, .len = 0 }, + { .ptr = NULL, .len = 0 }, + }, + .len = 0, +}; + /* * Prepare current date in the format required for HTTP "Date:" * header field. See RFC 2616 section 3.3. @@ -319,13 +433,17 @@ tfw_http_resp_build_error(TfwHttpReq *req) * client connection should be closed, because response-request * pairing for pipelined requests is violated. * - * NOTE: This function expects that the last chunk of @msg is CRLF. + * NOTE: This function expects the predefined order of chunks in @msg: + * the fourth chunk must be CRLF. */ -static void -tfw_http_send_resp(TfwHttpReq *req, TfwStr *msg, const TfwStr *date) +void +tfw_http_send_resp(TfwHttpReq *req, resp_code_t code) { + TfwStr *msg = &http_predef_resps[code]; + TfwStr *date = __TFW_STR_CH(msg, 1); + TfwStr *crlf = __TFW_STR_CH(msg, 3); int conn_flag = req->flags & __TFW_HTTP_CONN_MASK; - TfwStr *crlf = __TFW_STR_CH(msg, TFW_STR_CHUNKN(msg) - 1); + unsigned long init_len; TfwHttpMsg *hmresp; TfwMsgIter it; @@ -338,6 +456,7 @@ tfw_http_send_resp(TfwHttpReq *req, TfwStr *msg, const TfwStr *date) crlf->ptr = S_H_CONN_CLOSE; crlf->len = SLEN(S_H_CONN_CLOSE); } + init_len = msg->len; msg->len += crlf->len - crlf_len; } @@ -346,190 +465,28 @@ tfw_http_send_resp(TfwHttpReq *req, TfwStr *msg, const TfwStr *date) if (tfw_http_msg_setup(hmresp, &it, msg->len)) goto err_setup; + date->ptr = *this_cpu_ptr(&g_buf); tfw_http_prep_date(date->ptr); + if (tfw_http_msg_write(&it, hmresp, msg)) goto err_setup; tfw_http_resp_fwd(req, (TfwHttpResp *)hmresp); + goto done; - return; err_setup: TFW_DBG2("%s: Response message allocation error: conn=[%p]\n", __func__, req->conn); tfw_http_msg_free(hmresp); err_create: tfw_http_resp_build_error(req); -} - -#define S_200_PART_01 S_200 S_CRLF S_F_DATE -#define S_200_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF -/* - * HTTP 200 response: Success. - */ -void -tfw_http_send_200(TfwHttpReq *req) -{ - TfwStr rh = { - .ptr = (TfwStr []){ - { .ptr = S_200_PART_01, .len = SLEN(S_200_PART_01) }, - { .ptr = *this_cpu_ptr(&g_buf), .len = SLEN(S_V_DATE) }, - { .ptr = S_200_PART_02, .len = SLEN(S_200_PART_02) }, - { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, - }, - .len = SLEN(S_200_PART_01 S_V_DATE S_200_PART_02 S_CRLF), - .flags = 4 << TFW_STR_CN_SHIFT - }; - - TFW_DBG("Send HTTP 200 response\n"); - - tfw_http_send_resp(req, &rh, __TFW_STR_CH(&rh, 1)); -} - -#define S_403_PART_01 S_403 S_CRLF S_F_DATE -#define S_403_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF -/* - * HTTP 403 response: Access is forbidden. - */ -void -tfw_http_send_403(TfwHttpReq *req, const char *reason) -{ - TfwStr rh = { - .ptr = (TfwStr []){ - { .ptr = S_403_PART_01, .len = SLEN(S_403_PART_01) }, - { .ptr = *this_cpu_ptr(&g_buf), .len = SLEN(S_V_DATE) }, - { .ptr = S_403_PART_02, .len = SLEN(S_403_PART_02) }, - { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, - }, - .len = SLEN(S_403_PART_01 S_V_DATE S_403_PART_02 S_CRLF), - .flags = 4 << TFW_STR_CN_SHIFT - }; - - TFW_DBG("Send HTTP 403 response: %s\n", reason); - - tfw_http_send_resp(req, &rh, __TFW_STR_CH(&rh, 1)); -} - -#define S_404_PART_01 S_404 S_CRLF S_F_DATE -#define S_404_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF -/* - * HTTP 404 response: Tempesta is unable to find the requested data. - */ -void -tfw_http_send_404(TfwHttpReq *req, const char *reason) -{ - TfwStr rh = { - .ptr = (TfwStr []){ - { .ptr = S_404_PART_01, .len = SLEN(S_404_PART_01) }, - { .ptr = *this_cpu_ptr(&g_buf), .len = SLEN(S_V_DATE) }, - { .ptr = S_404_PART_02, .len = SLEN(S_404_PART_02) }, - { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, - }, - .len = SLEN(S_404_PART_01 S_V_DATE S_404_PART_02 S_CRLF), - .flags = 4 << TFW_STR_CN_SHIFT - }; - - TFW_DBG("Send HTTP 404 response: %s\n", reason); - - tfw_http_send_resp(req, &rh, __TFW_STR_CH(&rh, 1)); -} - -#define S_412_PART_01 S_412 S_CRLF S_F_DATE -#define S_412_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF -/* - * HTTP 412 response: Preconditional headers are evaluated to false. - */ -void -tfw_http_send_412(TfwHttpReq *req) -{ - TfwStr rh = { - .ptr = (TfwStr []){ - { .ptr = S_412_PART_01, .len = SLEN(S_412_PART_01) }, - { .ptr = *this_cpu_ptr(&g_buf), .len = SLEN(S_V_DATE) }, - { .ptr = S_412_PART_02, .len = SLEN(S_412_PART_02) }, - { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, - }, - .len = SLEN(S_412_PART_01 S_V_DATE S_412_PART_02 S_CRLF), - .flags = 4 << TFW_STR_CN_SHIFT - }; - - TFW_DBG("Send HTTP 412 response\n"); - - tfw_http_send_resp(req, &rh, __TFW_STR_CH(&rh, 1)); -} - -#define S_500_PART_01 S_500 S_CRLF S_F_DATE -#define S_500_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF -/* - * HTTP 500 response: there was an internal error while forwarding - * the request to a server. - */ -static void -tfw_http_send_500(TfwHttpReq *req, const char *reason) -{ - TfwStr rh = { - .ptr = (TfwStr []){ - { .ptr = S_500_PART_01, .len = SLEN(S_500_PART_01) }, - { .ptr = *this_cpu_ptr(&g_buf), .len = SLEN(S_V_DATE) }, - { .ptr = S_500_PART_02, .len = SLEN(S_500_PART_02) }, - { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, - }, - .len = SLEN(S_500_PART_01 S_V_DATE S_500_PART_02 S_CRLF), - .flags = 4 << TFW_STR_CN_SHIFT - }; - - TFW_DBG("Send HTTP 500 response: %s\n", reason); - - tfw_http_send_resp(req, &rh, __TFW_STR_CH(&rh, 1)); -} - -#define S_502_PART_01 S_502 S_CRLF S_F_DATE -#define S_502_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF -/* - * HTTP 502 response: Tempesta is unable to forward the request to - * the designated server. - */ -void -tfw_http_send_502(TfwHttpReq *req, const char *reason) -{ - TfwStr rh = { - .ptr = (TfwStr []){ - { .ptr = S_502_PART_01, .len = SLEN(S_502_PART_01) }, - { .ptr = *this_cpu_ptr(&g_buf), .len = SLEN(S_V_DATE) }, - { .ptr = S_502_PART_02, .len = SLEN(S_502_PART_02) }, - { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, - }, - .len = SLEN(S_502_PART_01 S_V_DATE S_502_PART_02 S_CRLF), - .flags = 4 << TFW_STR_CN_SHIFT - }; - - TFW_DBG("Send HTTP 502 response: %s\n", reason); - - tfw_http_send_resp(req, &rh, __TFW_STR_CH(&rh, 1)); -} - -#define S_504_PART_01 S_504 S_CRLF S_F_DATE -#define S_504_PART_02 S_CRLF S_F_CONTENT_LENGTH "0" S_CRLF -/* - * HTTP 504 response: did not receive a timely response from - * the designated server. - */ -void -tfw_http_send_504(TfwHttpReq *req, const char *reason) -{ - TfwStr rh = { - .ptr = (TfwStr []){ - { .ptr = S_504_PART_01, .len = SLEN(S_504_PART_01) }, - { .ptr = *this_cpu_ptr(&g_buf), .len = SLEN(S_V_DATE) }, - { .ptr = S_504_PART_02, .len = SLEN(S_504_PART_02) }, - { .ptr = S_CRLF, .len = SLEN(S_CRLF) }, - }, - .len = SLEN(S_504_PART_01 S_V_DATE S_504_PART_02 S_CRLF), - .flags = 4 << TFW_STR_CN_SHIFT - }; - - TFW_DBG("Send HTTP 504 response: %s\n", reason); - - tfw_http_send_resp(req, &rh, __TFW_STR_CH(&rh, 1)); +done: + date->ptr = NULL; + if (conn_flag) { + crlf->ptr = S_CRLF; + crlf->len = SLEN(S_CRLF); + msg->len = init_len; + } } /* @@ -725,34 +682,42 @@ tfw_http_req_error(TfwSrvConn *srv_conn, TfwHttpReq *req, __tfw_http_req_error(req, equeue, status, reason); } -static inline void -tfw_http_error_resp_switch(TfwHttpReq *req, unsigned short status, - const char *reason) +static inline resp_code_t +tfw_http_enum_resp_code(int status) { switch(status) { + case 200: + return RESP_200; case 403: - tfw_http_send_403(req, reason); - break; + return RESP_403; case 404: - tfw_http_send_404(req, reason); - break; + return RESP_404; + case 412: + return RESP_412; case 500: - tfw_http_send_500(req, reason); - break; + return RESP_500; case 502: - tfw_http_send_502(req, reason); - break; + return RESP_502; case 504: - tfw_http_send_504(req, reason); - break; + return RESP_504; default: - TFW_WARN("Unexpected response error code: [%d]\n", - status); - tfw_http_send_500(req, reason); - break; + return RESP_NUM; } } +static inline void +tfw_http_error_resp_switch(TfwHttpReq *req, int status, const char *reason) +{ + resp_code_t code = tfw_http_enum_resp_code(status); + if (code == RESP_NUM) { + TFW_WARN("Unexpected response error code: [%d]\n", status); + tfw_http_send_resp(req, RESP_500); + return; + } + + tfw_http_send_resp(req, code); +} + /* * Forwarding of requests to a back end server is run under a lock * on the server connection's forwarding queue. It's performed as @@ -1927,9 +1892,9 @@ static void tfw_http_req_cache_service(TfwHttpReq *req, TfwHttpResp *resp) { if (tfw_http_adjust_resp(resp, req)) { - tfw_http_send_500(req, "response dropped: processing error"); - tfw_http_conn_msg_free((TfwHttpMsg *)resp); + HTTP_SEND_RESP(req, 500, "response dropped: processing error"); TFW_INC_STAT_BH(clnt.msgs_otherr); + tfw_http_conn_msg_free((TfwHttpMsg *)resp); return; } tfw_http_resp_fwd(req, resp); @@ -1996,11 +1961,11 @@ tfw_http_req_cache_cb(TfwHttpReq *req, TfwHttpResp *resp) goto conn_put; send_502: - tfw_http_send_502(req, "request dropped: processing error"); + HTTP_SEND_RESP(req, 502, "request dropped: processing error"); TFW_INC_STAT_BH(clnt.msgs_otherr); return; send_500: - tfw_http_send_500(req, "request dropped: processing error"); + HTTP_SEND_RESP(req, 500, "request dropped: processing error"); TFW_INC_STAT_BH(clnt.msgs_otherr); conn_put: tfw_srv_conn_put(srv_conn); @@ -2084,12 +2049,11 @@ tfw_http_req_set_context(TfwHttpReq *req) } static inline void -tfw_http_req_mark_error(TfwHttpReq *req, unsigned short code, - const char *msg) +tfw_http_req_mark_error(TfwHttpReq *req, int status, const char *msg) { TFW_CONN_TYPE(req->conn) |= Conn_OnHold; req->flags |= TFW_HTTP_SUSPECTED; - tfw_http_error_resp_switch(req, code, msg); + tfw_http_error_resp_switch(req, status, msg); } /** @@ -2100,7 +2064,7 @@ tfw_http_req_mark_error(TfwHttpReq *req, unsigned short code, */ static void tfw_http_cli_error_resp_and_log(bool reply, bool nolog, TfwHttpReq *req, - unsigned short code, const char *msg) + int status, const char *msg) { if (reply) { TfwCliConn *cli_conn = (TfwCliConn *)req->conn; @@ -2108,7 +2072,7 @@ tfw_http_cli_error_resp_and_log(bool reply, bool nolog, TfwHttpReq *req, spin_lock(&cli_conn->seq_qlock); list_add_tail(&req->msg.seq_list, &cli_conn->seq_queue); spin_unlock(&cli_conn->seq_qlock); - tfw_http_req_mark_error(req, code, msg); + tfw_http_req_mark_error(req, status, msg); } else tfw_http_conn_req_clean(req); @@ -2119,10 +2083,10 @@ tfw_http_cli_error_resp_and_log(bool reply, bool nolog, TfwHttpReq *req, static void tfw_http_srv_error_resp_and_log(bool reply, bool nolog, TfwHttpReq *req, - unsigned short code, const char *msg) + int status, const char *msg) { if (reply) - tfw_http_req_mark_error(req, code, msg); + tfw_http_req_mark_error(req, status, msg); else tfw_http_conn_req_clean(req); @@ -2141,35 +2105,43 @@ tfw_http_srv_error_resp_and_log(bool reply, bool nolog, TfwHttpReq *req, * and tfw_srv_client_block() - only from server connection context. */ static inline void -tfw_client_drop(TfwHttpReq *req, unsigned short code, const char *msg) +tfw_client_drop(TfwHttpReq *req, int status, const char *msg) { - tfw_http_cli_error_resp_and_log(tfw_blk_flags & TFW_BLK_ERR_REPLY, - tfw_blk_flags & TFW_BLK_ERR_NOLOG, - req, code, msg); + tfw_http_cli_error_resp_and_log(tfw_blk_flags & + TFW_BLK_ERR_REPLY, + tfw_blk_flags & + TFW_BLK_ERR_NOLOG, + req, status, msg); } static inline void -tfw_client_block(TfwHttpReq *req, unsigned short code, const char *msg) +tfw_client_block(TfwHttpReq *req, int status, const char *msg) { - tfw_http_cli_error_resp_and_log(tfw_blk_flags & TFW_BLK_ATT_REPLY, - tfw_blk_flags & TFW_BLK_ATT_NOLOG, - req, code, msg); + tfw_http_cli_error_resp_and_log(tfw_blk_flags & + TFW_BLK_ATT_REPLY, + tfw_blk_flags & + TFW_BLK_ATT_NOLOG, + req, status, msg); } static inline void -tfw_srv_client_drop(TfwHttpReq *req, unsigned short code, const char *msg) +tfw_srv_client_drop(TfwHttpReq *req, int status, const char *msg) { - tfw_http_srv_error_resp_and_log(tfw_blk_flags & TFW_BLK_ERR_REPLY, - tfw_blk_flags & TFW_BLK_ERR_NOLOG, - req, code, msg); + tfw_http_srv_error_resp_and_log(tfw_blk_flags & + TFW_BLK_ERR_REPLY, + tfw_blk_flags & + TFW_BLK_ERR_NOLOG, + req, status, msg); } static inline void -tfw_srv_client_block(TfwHttpReq *req, unsigned short code, const char *msg) +tfw_srv_client_block(TfwHttpReq *req, int status, const char *msg) { - tfw_http_srv_error_resp_and_log(tfw_blk_flags & TFW_BLK_ATT_REPLY, - tfw_blk_flags & TFW_BLK_ATT_NOLOG, - req, code, msg); + tfw_http_srv_error_resp_and_log(tfw_blk_flags & + TFW_BLK_ATT_REPLY, + tfw_blk_flags & + TFW_BLK_ATT_NOLOG, + req, status, msg); } /** @@ -2364,8 +2336,8 @@ tfw_http_req_process(TfwConn *conn, struct sk_buff *skb, unsigned int off) * Otherwise we lose the reference to it and get a leak. */ if (tfw_cache_process(req, NULL, tfw_http_req_cache_cb)) { - tfw_http_send_500(req, "request dropped:" - " processing error"); + HTTP_SEND_RESP(req, 500, "request dropped:" + " processing error"); TFW_INC_STAT_BH(clnt.msgs_otherr); return TFW_PASS; } @@ -2418,9 +2390,9 @@ tfw_http_resp_cache_cb(TfwHttpReq *req, TfwHttpResp *resp) * inter-node data transfers. (see tfw_http_req_cache_cb()) */ if (tfw_http_adjust_resp(resp, req)) { - tfw_http_send_500(req, "response dropped: processing error"); - tfw_http_conn_msg_free((TfwHttpMsg *)resp); + HTTP_SEND_RESP(req, 500, "response dropped: processing error"); TFW_INC_STAT_BH(serv.msgs_otherr); + tfw_http_conn_msg_free((TfwHttpMsg *)resp); return; } /* @@ -2587,7 +2559,7 @@ tfw_http_resp_cache(TfwHttpMsg *hmresp) if (tfw_cache_process(req, (TfwHttpResp *)hmresp, tfw_http_resp_cache_cb)) { - tfw_http_send_500(req, "response dropped: processing error"); + HTTP_SEND_RESP(req, 500, "response dropped: processing error"); tfw_http_conn_msg_free(hmresp); TFW_INC_STAT_BH(serv.msgs_otherr); /* Proceed with processing of the next response. */ @@ -2819,6 +2791,192 @@ tfw_http_msg_process(void *conn, struct sk_buff *skb, unsigned int off) : tfw_http_resp_process(c, skb, off); } +/* Macros specific to *_set_body() functions. */ +#define __TFW_STR_SET_BODY() \ + msg->len += l_size - clen_str->len + b_size - body_str->len; \ + body_str->ptr = new_body; \ + body_str->len = b_size; \ + clen_str->ptr = new_length; \ + clen_str->len = l_size; + +static void +tfw_http_set_body(resp_code_t code, char *new_length, size_t l_size, + char *new_body, size_t b_size) +{ + TfwStr *msg = &http_predef_resps[code]; + TfwStr *clen_str = __TFW_STR_CH(msg, 2); + TfwStr *body_str = __TFW_STR_CH(msg, 4); + + void *prev_clen_ptr = NULL; + void *prev_body_ptr = body_str->ptr; + if (prev_body_ptr) + prev_clen_ptr = clen_str->ptr; + + __TFW_STR_SET_BODY(); + + if (!prev_body_ptr) { + TFW_STR_CHUNKN_ADD(msg, 1); + return; + } + + BUG_ON(!prev_clen_ptr); + if (prev_body_ptr != __TFW_STR_CH(&http_4xx_resp_body, 1)->ptr && + prev_body_ptr != __TFW_STR_CH(&http_5xx_resp_body, 1)->ptr) + { + kfree(prev_body_ptr); + kfree(prev_clen_ptr); + } +} + +static int +tfw_http_set_common_body(int status_code, char *new_length, size_t l_size, + char *new_body, size_t b_size) +{ + TfwStr *msg; + resp_code_t i, begin, end; + TfwStr *clen_str; + TfwStr *body_str; + void *prev_clen_ptr = NULL; + void *prev_body_ptr = NULL; + + switch(status_code) { + case HTTP_STATUS_4XX: + begin = RESP_4XX_BEGIN; + end = RESP_4XX_END; + msg = &http_4xx_resp_body; + break; + case HTTP_STATUS_5XX: + begin = RESP_5XX_BEGIN; + end = RESP_5XX_END; + msg = &http_5xx_resp_body; + break; + default: + TFW_ERR("undefined HTTP status group: [%d]\n", status_code); + return -EINVAL; + } + + clen_str = __TFW_STR_CH(msg, 0); + body_str = __TFW_STR_CH(msg, 1); + prev_body_ptr = body_str->ptr; + + if (prev_body_ptr) + prev_clen_ptr = clen_str->ptr; + + __TFW_STR_SET_BODY(); + + for (i = begin; i < end; ++i) { + TfwStr *body_str = + __TFW_STR_CH(&http_predef_resps[i], 4); + if (!body_str->ptr || + body_str->ptr == prev_body_ptr) + { + TfwStr *msg = &http_predef_resps[i]; + TfwStr *clen_str = __TFW_STR_CH(msg, 2); + __TFW_STR_SET_BODY(); + __TFW_STR_CHUNKN_SET(msg, 5); + } + } + + if (!prev_body_ptr) { + BUG_ON(prev_clen_ptr); + return 0; + } + + BUG_ON(!prev_clen_ptr); + kfree(prev_body_ptr); + kfree(prev_clen_ptr); + + return 0; +} + +/** + * Set message body for predefined response with corresponding code. + */ +int +tfw_http_config_resp_body(int status_code, const char *src_body, size_t b_size) +{ + resp_code_t code; + size_t digs_count, l_size; + char *new_length, *new_body; + char buff[TFW_ULTOA_BUF_SIZ] = {0}; + + if (!(digs_count = tfw_ultoa(b_size, buff, TFW_ULTOA_BUF_SIZ))) { + TFW_ERR("too small buffer for Content-Length header\n"); + return -E2BIG; + } + + l_size = 2*SLEN(S_CRLF) + SLEN(S_F_CONTENT_LENGTH) + digs_count; + new_length = kmalloc(l_size + 1, GFP_KERNEL); + if (!new_length) { + TFW_ERR("can't allocate memory for Content-Length header\n"); + return -ENOMEM; + } + snprintf(new_length, l_size + 1, "%s%s%s%s", + S_CRLF, S_F_CONTENT_LENGTH , buff, S_CRLF); + + new_body = kmalloc(b_size, GFP_KERNEL); + if (!new_body) { + TFW_ERR("cannot allocate memory for message body\n"); + kfree(new_length); + return -ENOMEM; + } + memcpy(new_body, src_body, b_size); + + if (status_code == HTTP_STATUS_4XX || status_code == HTTP_STATUS_5XX) { + tfw_http_set_common_body(status_code, new_length, + l_size, new_body, b_size); + return 0; + } + + code = tfw_http_enum_resp_code(status_code); + if (code == RESP_NUM) { + TFW_ERR_NL("Unexpected status code: [%d]\n", + status_code); + return -EINVAL; + } + + tfw_http_set_body(code, new_length, l_size, new_body, b_size); + + return 0; +} + +/** + * Delete all dynamically allocated message bodies for predefined + * responses (for the cleanup case during shutdown). + */ +void +tfw_http_del_resp_bodies(void) +{ + TfwStr *clen_str_4xx = __TFW_STR_CH(&http_4xx_resp_body, 0); + TfwStr *body_str_4xx = __TFW_STR_CH(&http_4xx_resp_body, 1); + TfwStr *clen_str_5xx = __TFW_STR_CH(&http_5xx_resp_body, 0); + TfwStr *body_str_5xx = __TFW_STR_CH(&http_5xx_resp_body, 1); + resp_code_t i; + for (i = 0; i < RESP_NUM; ++i) { + TfwStr *body_str = __TFW_STR_CH(&http_predef_resps[i], 4); + if (body_str->ptr) { + TfwStr *clen_str = + __TFW_STR_CH(&http_predef_resps[i], 2); + if (body_str->ptr != body_str_4xx->ptr && + body_str->ptr != body_str_5xx->ptr) + { + kfree(body_str->ptr); + kfree(clen_str->ptr); + } + } + } + if (body_str_4xx->ptr) { + BUG_ON(!clen_str_4xx->ptr); + kfree(body_str_4xx->ptr); + kfree(clen_str_4xx->ptr); + } + if (body_str_5xx->ptr) { + BUG_ON(!clen_str_5xx->ptr); + kfree(body_str_5xx->ptr); + kfree(clen_str_5xx->ptr); + } +} + /** * Calculate the key of an HTTP request by hashing URI and Host header values. */ diff --git a/tempesta_fw/http.h b/tempesta_fw/http.h index 7a74b1f98..189477788 100644 --- a/tempesta_fw/http.h +++ b/tempesta_fw/http.h @@ -481,6 +481,33 @@ typedef struct { #define TFW_BLK_ERR_NOLOG 0x0004 #define TFW_BLK_ATT_NOLOG 0x0008 +/* HTTP codes enumeration for predefined responses */ +typedef enum { + RESP_200, + RESP_4XX_BEGIN, + RESP_403 = RESP_4XX_BEGIN, + RESP_404, + RESP_412, + RESP_4XX_END, + RESP_5XX_BEGIN = RESP_4XX_END, + RESP_500 = RESP_5XX_BEGIN, + RESP_502, + RESP_504, + RESP_5XX_END, + RESP_NUM = RESP_5XX_END +} resp_code_t; + +#define HTTP_SEND_RESP(req, code, reason) \ +do { \ + tfw_http_send_resp(req, RESP_##code); \ + TFW_DBG("Send HTTP %s response: %s\n", #code, reason); \ +} while (0) + +enum { + HTTP_STATUS_4XX = 4, + HTTP_STATUS_5XX +}; + /* Get current timestamp in secs. */ static inline time_t tfw_current_timestamp(void) @@ -509,15 +536,14 @@ void tfw_http_resp_build_error(TfwHttpReq *req); /* * Functions to send an HTTP error response to a client. */ -void tfw_http_send_200(TfwHttpReq *req); int tfw_http_prep_302(TfwHttpMsg *resp, TfwHttpReq *req, TfwStr *cookie); int tfw_http_prep_304(TfwHttpMsg *resp, TfwHttpReq *req, void *msg_it, size_t hdrs_size); -void tfw_http_send_403(TfwHttpReq *req, const char *reason); -void tfw_http_send_404(TfwHttpReq *req, const char *reason); -void tfw_http_send_412(TfwHttpReq *req); -void tfw_http_send_502(TfwHttpReq *req, const char *reason); -void tfw_http_send_504(TfwHttpReq *req, const char *reason); +void tfw_http_send_resp(TfwHttpReq *req, resp_code_t code); + +/* Configuration stage functions */ +int tfw_http_config_resp_body(int code, const char *body, size_t b_size); +void tfw_http_del_resp_bodies(void); /* * Functions to create SKBs with data stream. diff --git a/tempesta_fw/sock_clnt.c b/tempesta_fw/sock_clnt.c index c20f9ab05..9aa69f437 100644 --- a/tempesta_fw/sock_clnt.c +++ b/tempesta_fw/sock_clnt.c @@ -20,6 +20,9 @@ * this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include + #include "cfg.h" #include "classifier.h" #include "client.h" @@ -655,6 +658,85 @@ tfw_sock_clnt_cfg_handle_block_action(TfwCfgSpec *cs, TfwCfgEntry *ce) return 0; } +static int +tfw_cfg_parse_http_status(const char *status, int *out) +{ + int i; + for (i = 0; status[i]; ++i) { + if (isdigit(status[i])) + continue; + + if (i == 1 && status[i] == '*' && !status[i+1]) { + /* + * For status groups only two-character + * sequences with first digit are + * acceptable (e.g. 4* or 5*). + */ + if (status[0] == '4') { + *out = HTTP_STATUS_4XX; + return 0; + } + if (status[0] == '5') { + *out = HTTP_STATUS_5XX; + return 0; + } + } + return -EINVAL; + } + /* + * For simple HTTP status value only + * three-digit numbers are acceptable + * currently. + */ + if (i != 3) + return -EINVAL; + + return kstrtoint(status, 10, out); +} + +static int +tfw_sock_clnt_cfg_handle_resp_body(TfwCfgSpec *cs, TfwCfgEntry *ce) +{ + char *body_data; + size_t body_size; + int code, ret = 0; + + if (tfw_cfg_check_val_n(ce, 2)) + return -EINVAL; + + if (ce->attr_n) { + TFW_ERR_NL("Unexpected attributes\n"); + return -EINVAL; + } + + if (tfw_cfg_parse_http_status(ce->vals[0], &code)) + { + TFW_ERR_NL("Unable to parse 'response_body' value: '%s'\n", + ce->vals[0] + ? ce->vals[0] + : "No value specified"); + return -EINVAL; + } + + body_data = tfw_cfg_read_file(ce->vals[1], &body_size); + if (!body_data) { + TFW_ERR_NL("Cannot read file with error response: '%s'\n", + ce->vals[1]); + return -EINVAL; + } + + ret = tfw_http_config_resp_body(code, body_data, body_size - 1); + vfree(body_data); + + return ret; +} + +static void +tfw_sock_clnt_cfg_cleanup_resp_body(TfwCfgSpec *cs) +{ + tfw_http_del_resp_bodies(); +} + static void tfw_sock_clnt_cfg_cleanup_listen(TfwCfgSpec *cs) { @@ -687,6 +769,14 @@ TfwCfgMod tfw_sock_clnt_cfg_mod = { .allow_repeat = true, .allow_none = true }, + { + "response_body", + NULL, + tfw_sock_clnt_cfg_handle_resp_body, + .allow_repeat = true, + .allow_none = true, + .cleanup = tfw_sock_clnt_cfg_cleanup_resp_body + }, {} } }; diff --git a/tempesta_fw/t/unit/test_http_sticky.c b/tempesta_fw/t/unit/test_http_sticky.c index 419fa9e0e..9241c099c 100644 --- a/tempesta_fw/t/unit/test_http_sticky.c +++ b/tempesta_fw/t/unit/test_http_sticky.c @@ -296,7 +296,7 @@ TEST(http_sticky, sending_502) StickyVal sv = { .ts = 1 }; EXPECT_EQ(__sticky_calc(mock.req, &sv), 0); - tfw_http_send_502(mock.req, "sticky calculation"); + HTTP_SEND_RESP(mock.req, 502, "sticky calculation"); /* HTTP 502 response have no Set-Cookie header */ EXPECT_TRUE(mock.tfw_connection_send_was_called);