-
Notifications
You must be signed in to change notification settings - Fork 103
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
Fix #658: Add possibility to configure error response behaviour. #848
Changes from 1 commit
7041f40
9056461
aceb4ad
4c5f17a
27bfd26
d7052cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,8 @@ | |
|
||
#include "sync_socket.h" | ||
|
||
extern unsigned short tfw_block_action_flags; | ||
|
||
#define RESP_BUF_LEN 128 | ||
static DEFINE_PER_CPU(char[RESP_BUF_LEN], g_buf); | ||
int ghprio; /* GFSM hook priority. */ | ||
|
@@ -713,6 +715,34 @@ 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) | ||
{ | ||
switch(status) { | ||
case 403: | ||
tfw_http_send_403(req, reason); | ||
break; | ||
case 404: | ||
tfw_http_send_404(req, reason); | ||
break; | ||
case 500: | ||
tfw_http_send_500(req, reason); | ||
break; | ||
case 502: | ||
tfw_http_send_502(req, reason); | ||
break; | ||
case 504: | ||
tfw_http_send_504(req, reason); | ||
break; | ||
default: | ||
TFW_WARN("Unexpected response error code: [%d]\n", | ||
status); | ||
tfw_http_send_500(req, reason); | ||
break; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment #658 (comment) isn't addressed. Please load files named like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected: configurable custom error pages added. |
||
|
||
/* | ||
* Forwarding of requests to a back end server is run under a lock | ||
* on the server connection's forwarding queue. It's performed as | ||
|
@@ -735,25 +765,8 @@ tfw_http_req_zap_error(struct list_head *equeue) | |
|
||
list_for_each_entry_safe(req, tmp, equeue, fwd_list) { | ||
list_del_init(&req->fwd_list); | ||
switch(req->httperr.status) { | ||
case 404: | ||
tfw_http_send_404(req, req->httperr.reason); | ||
break; | ||
case 500: | ||
tfw_http_send_500(req, req->httperr.reason); | ||
break; | ||
case 502: | ||
tfw_http_send_502(req, req->httperr.reason); | ||
break; | ||
case 504: | ||
tfw_http_send_504(req, req->httperr.reason); | ||
break; | ||
default: | ||
TFW_WARN("Unexpected response error code: [%d]\n", | ||
req->httperr.status); | ||
tfw_http_send_500(req, req->httperr.reason); | ||
break; | ||
} | ||
tfw_http_error_resp_switch(req, req->httperr.status, | ||
req->httperr.reason); | ||
TFW_INC_STAT_BH(clnt.msgs_otherr); | ||
} | ||
} | ||
|
@@ -1789,7 +1802,9 @@ __tfw_http_resp_fwd(TfwCliConn *cli_conn, struct list_head *ret_queue) | |
list_for_each_entry_safe(req, tmp, ret_queue, msg.seq_list) { | ||
BUG_ON(!req->resp); | ||
tfw_http_resp_init_ss_flags((TfwHttpResp *)req->resp, req); | ||
if (tfw_cli_conn_send(cli_conn, (TfwMsg *)req->resp)) { | ||
if (tfw_cli_conn_send(cli_conn, (TfwMsg *)req->resp) || | ||
(TFW_CONN_TYPE(cli_conn) & Conn_Suspected && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is HTTP layer. I believe it's sufficient to check the flag for the HTTP layer. It's unnecessary to check flags in both layers as they both are set at the same time. Moreover, it breaks the whole idea of layers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @keshonok thank you for review! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected. |
||
req->flags & TFW_HTTP_SUSPECTED)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please place opening curly brace at next line for multi-line conditions, otherwise the complex statement is hard to read. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected. |
||
ss_close_sync(cli_conn->sk, true); | ||
return; | ||
} | ||
|
@@ -2058,6 +2073,69 @@ tfw_http_req_set_context(TfwHttpReq *req) | |
return !req->vhost; | ||
} | ||
|
||
/* | ||
* Function defines logging and response behaviour during | ||
* detection of malformed or malicious messages. Can be | ||
* called from both - client and server connection contexts. | ||
* | ||
* NOTE: @mark must be set only for client connection context | ||
* because in this case we must mark client connection in | ||
* special manner to delay its closing until transmission | ||
* of error response will be finished. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? If we find in a server connection context that a request is malicious (e.g. the call in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected: added the ability to mark a request and connection for closing from the server context. |
||
*/ | ||
static inline void | ||
tfw_http_error_resp_and_log(bool reply, bool nolog, TfwHttpReq *req, | ||
unsigned short code, const char *msg, bool mark) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Frankly, I think this is way too many arguments. Perhaps, this can be done in a different way? Please see the comments below to the functions that call this one. The most confusing thing here is that the notion of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected. |
||
{ | ||
TfwCliConn *cli_conn = (TfwCliConn *)req->conn; | ||
if (reply) { | ||
if (mark) { | ||
BUG_ON(req->flags & TFW_HTTP_SUSPECTED || | ||
TFW_CONN_TYPE(cli_conn) & Conn_Suspected); | ||
TFW_CONN_TYPE(cli_conn) |= Conn_Suspected; | ||
req->flags |= TFW_HTTP_SUSPECTED; | ||
tfw_connection_unlink_msg(req->conn); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ...and, yes, we can call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected. |
||
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_error_resp_switch(req, code, msg); | ||
} | ||
else { | ||
spin_lock(&cli_conn->seq_qlock); | ||
if (unlikely(!list_empty(&req->msg.seq_list))) | ||
list_del_init(&req->msg.seq_list); | ||
spin_unlock(&cli_conn->seq_qlock); | ||
tfw_http_conn_msg_free((TfwHttpMsg *)req); | ||
} | ||
if (!nolog) | ||
TFW_WARN("Error response: %s, msg=%p conn=%p\n", | ||
msg, req, req->conn); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to print client's IP address - it's important information for security logs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected. |
||
} | ||
|
||
/** | ||
* Defines behaviour for malformed messages depending on configuration | ||
* settings: sending response error messages and logging. | ||
*/ | ||
static void | ||
tfw_http_drop(TfwHttpReq *req, unsigned short code, const char *msg, bool mark) | ||
{ | ||
bool reply = tfw_block_action_flags & TFW_BLOCK_ACTION_ERROR_REPLY; | ||
bool nolog = tfw_block_action_flags & TFW_BLOCK_ACTION_ERROR_NOLOG; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know this is a common practice to use such boolean variables just to handle flags, but we don't like so long names ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected. |
||
tfw_http_error_resp_and_log(reply, nolog, req, code, msg, mark); | ||
} | ||
|
||
/** | ||
* Do the same as 'tfw_http_drop' function, but for malicious messages. | ||
*/ | ||
static void | ||
tfw_http_block(TfwHttpReq *req, unsigned short code, const char *msg, bool mark) | ||
{ | ||
bool reply = tfw_block_action_flags & TFW_BLOCK_ACTION_ATTACK_REPLY; | ||
bool nolog = tfw_block_action_flags & TFW_BLOCK_ACTION_ATTACK_NOLOG; | ||
tfw_http_error_resp_and_log(reply, nolog, req, code, msg, mark); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe these functions should be inlined. Also, I believe it would be better if you had separate versions for client and server contexts, and that may help to reduce the number of arguments. Plus there are other consideration that point in that direction, most significant of which is that the request should be put on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @keshonok thank you for the notes! The modern compiler doesn't treat There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no sense to split expressions like |
||
|
||
/** | ||
* @return zero on success and negative value otherwise. | ||
* TODO enter the function depending on current GFSM state. | ||
|
@@ -2112,16 +2190,19 @@ tfw_http_req_process(TfwConn *conn, struct sk_buff *skb, unsigned int off) | |
BUG(); | ||
case TFW_BLOCK: | ||
TFW_DBG2("Block invalid HTTP request\n"); | ||
tfw_http_conn_msg_free((TfwHttpMsg *)req); | ||
TFW_INC_STAT_BH(clnt.msgs_parserr); | ||
tfw_http_drop(req, 403, "failed to" | ||
" parse request", true); | ||
return TFW_BLOCK; | ||
case TFW_POSTPONE: | ||
r = tfw_gfsm_move(&conn->state, | ||
TFW_HTTP_FSM_REQ_CHUNK, skb, off); | ||
TFW_DBG3("TFW_HTTP_FSM_REQ_CHUNK return code %d\n", r); | ||
if (r == TFW_BLOCK) { | ||
tfw_http_conn_msg_free((TfwHttpMsg *)req); | ||
TFW_INC_STAT_BH(clnt.msgs_filtout); | ||
tfw_http_block(req, 403, "postponed" | ||
" request has been" | ||
" filtered out", true); | ||
return TFW_BLOCK; | ||
} | ||
/* | ||
|
@@ -2145,8 +2226,9 @@ tfw_http_req_process(TfwConn *conn, struct sk_buff *skb, unsigned int off) | |
TFW_DBG3("TFW_HTTP_FSM_REQ_MSG return code %d\n", r); | ||
/* Don't accept any following requests from the peer. */ | ||
if (r == TFW_BLOCK) { | ||
tfw_http_conn_msg_free((TfwHttpMsg *)req); | ||
TFW_INC_STAT_BH(clnt.msgs_filtout); | ||
tfw_http_block(req, 403, "parsed request" | ||
" has been filtered out", true); | ||
return TFW_BLOCK; | ||
} | ||
|
||
|
@@ -2159,7 +2241,9 @@ tfw_http_req_process(TfwConn *conn, struct sk_buff *skb, unsigned int off) | |
|
||
/* Assign the right Vhost for this request. */ | ||
if (tfw_http_req_set_context(req)) { | ||
tfw_http_conn_msg_free((TfwHttpMsg *)req); | ||
TFW_INC_STAT_BH(clnt.msgs_otherr); | ||
tfw_http_drop(req, 500, "cannot find" | ||
"Vhost for request", true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's been stated that special drop/block processing is for the cases where a request either malformed or malicious (i.e. filtered out). This particular case is more of an internal error nature (or maybe even a configuration). So why There are still regular There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But in the opposite case - if we don't send error response - the client will see only hang pages. |
||
return TFW_BLOCK; | ||
} | ||
|
||
|
@@ -2216,8 +2300,9 @@ tfw_http_req_process(TfwConn *conn, struct sk_buff *skb, unsigned int off) | |
*/ | ||
TFW_WARN("Not enough memory to create" | ||
" a request sibling\n"); | ||
tfw_http_conn_msg_free((TfwHttpMsg *)req); | ||
TFW_INC_STAT_BH(clnt.msgs_otherr); | ||
tfw_http_drop(req, 500, "cannot create" | ||
" sibling request", true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, this seems like an internal error. Why the change to UPDATE: Yes, I guess we do, as otherwise we break the pairing of responses to requests. The other question still remains. |
||
return TFW_BLOCK; | ||
} | ||
} | ||
|
@@ -2414,7 +2499,7 @@ tfw_http_resp_gfsm(TfwHttpMsg *hmresp, struct sk_buff *skb, unsigned int off) | |
return TFW_BLOCK; | ||
} | ||
|
||
tfw_http_send_502(req, "response dropped: filtered out"); | ||
tfw_http_block(req, 502, "response blocked: filtered out", false); | ||
tfw_http_conn_msg_free(hmresp); | ||
TFW_INC_STAT_BH(serv.msgs_filtout); | ||
return r; | ||
|
@@ -2510,6 +2595,7 @@ tfw_http_resp_process(TfwConn *conn, struct sk_buff *skb, unsigned int off) | |
unsigned int skb_len = skb->len; | ||
TfwHttpReq *bad_req; | ||
TfwHttpMsg *hmresp; | ||
bool filtout = false; | ||
|
||
BUG_ON(!conn->msg); | ||
BUG_ON(data_off >= skb_len); | ||
|
@@ -2570,6 +2656,7 @@ tfw_http_resp_process(TfwConn *conn, struct sk_buff *skb, unsigned int off) | |
TFW_DBG3("TFW_HTTP_FSM_RESP_CHUNK return code %d\n", r); | ||
if (r == TFW_BLOCK) { | ||
TFW_INC_STAT_BH(serv.msgs_filtout); | ||
filtout = true; | ||
goto bad_msg; | ||
} | ||
/* | ||
|
@@ -2658,8 +2745,16 @@ tfw_http_resp_process(TfwConn *conn, struct sk_buff *skb, unsigned int off) | |
return r; | ||
bad_msg: | ||
bad_req = tfw_http_popreq(hmresp); | ||
if (bad_req) | ||
tfw_http_send_500(bad_req, "response dropped: processing error"); | ||
if (bad_req) { | ||
if (filtout) | ||
tfw_http_block(bad_req, 502, | ||
"response blocked:" | ||
" filtered out", false); | ||
else | ||
tfw_http_drop(bad_req, 500, | ||
"response dropped:" | ||
" processing error", false); | ||
} | ||
tfw_http_conn_msg_free(hmresp); | ||
return r; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -290,6 +290,7 @@ typedef struct { | |
/* URI has form http://authority/path, not just /path */ | ||
#define TFW_HTTP_URI_FULL 0x000400 | ||
#define TFW_HTTP_NON_IDEMP 0x000800 | ||
#define TFW_HTTP_SUSPECTED 0x001000 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't agree: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I was trying to say was that in my opinion there's nothing that is So I didn't understand what they are suspected of? Suspicion is when we don't know what it is yet, it's either this or that, and there are further actions to come up with more detailed knowledge. But it doesn't work that way in the code. Again, the decision had been made at the time this flag is set, with the HTTP error response code and the error message. There's nothing that is suspected. Just the opposite, everything is pretty clear and definite. Just a thought. :-) |
||
|
||
/* Response flags */ | ||
#define TFW_HTTP_VOID_BODY 0x010000 /* Resp has no body */ | ||
|
@@ -474,6 +475,12 @@ typedef struct { | |
#define FOR_EACH_HDR_FIELD_FROM(pos, end, msg, soff) \ | ||
__FOR_EACH_HDR_FIELD(pos, end, msg, soff, (msg)->h_tbl->off) | ||
|
||
/* Bit flags for block action behaviour. */ | ||
#define TFW_BLOCK_ACTION_ERROR_REPLY 0x0001 | ||
#define TFW_BLOCK_ACTION_ATTACK_REPLY 0x0002 | ||
#define TFW_BLOCK_ACTION_ERROR_NOLOG 0x0004 | ||
#define TFW_BLOCK_ACTION_ATTACK_NOLOG 0x0008 | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The flags are somewhat misleading: they're essentially single bit flag, so, given that there is no comments, a one can assume that they can ORed together, but that's not true. Probably they should be split to |
||
/* Get current timestamp in secs. */ | ||
static inline time_t | ||
tfw_current_timestamp(void) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -746,6 +746,11 @@ ss_tcp_process_skb(struct sock *sk, struct sk_buff *skb, int *processed) | |
*/ | ||
BUG_ON(conn == NULL); | ||
|
||
if (SS_CONN_TYPE(sk) & Conn_Suspected) { | ||
__kfree_skb(skb); | ||
continue; | ||
} | ||
|
||
r = SS_CALL(connection_recv, conn, skb, offset); | ||
|
||
if (r < 0) { | ||
|
@@ -858,10 +863,13 @@ ss_tcp_data_ready(struct sock *sk) | |
TFW_ERR("error data in socket %p\n", sk); | ||
} | ||
else if (!skb_queue_empty(&sk->sk_receive_queue)) { | ||
if (ss_tcp_process_data(sk)) { | ||
if (ss_tcp_process_data(sk) && | ||
!(SS_CONN_TYPE(sk) & Conn_Suspected)) { | ||
/* | ||
* Drop connection in case of internal errors, | ||
* banned packets, or FIN in the received packet. | ||
* banned packets, or FIN in the received packet, | ||
* and only if we don't wait for sending the error | ||
* response to client (Conn_Suspected bit set). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Considering the comment below for the name of the flag (like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected. |
||
* | ||
* ss_linkerror() is responsible for calling | ||
* application layer connection closing callback. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure that it's a good idea to refer this way to a configuration variable defined elsewhere. Perhaps, it's better to define a set of inline functions that would allow to access the contents of the variable.