MEDIUM: tcp: add new tcp action "silent-drop"

This stops the evaluation of the rules and makes the client-facing
connection suddenly disappear using a system-dependant way that tries
to prevent the client from being notified. The effect it then that the
client still sees an established connection while there's none on
HAProxy. The purpose is to achieve a comparable effect to "tarpit"
except that it doesn't use any local resource at all on the machine
running HAProxy. It can resist much higher loads than "tarpit", and
slow down stronger attackers. It is important to undestand the impact
of using this mechanism. All stateful equipments placed between the
client and HAProxy (firewalls, proxies, load balancers) will also keep
the established connection for a long time and may suffer from this
action. On modern Linux systems running with enough privileges, the
TCP_REPAIR socket option is used to block the emission of a TCP
reset. On other systems, the socket's TTL is reduced to 1 so that the
TCP reset doesn't pass the first router, though it's still delivered to
local networks.
This commit is contained in:
Willy Tarreau 2015-08-24 01:43:45 +02:00
parent c870bfd0e9
commit 2d392c2c2f
2 changed files with 181 additions and 1 deletions

View File

@ -3409,6 +3409,7 @@ http-request { allow | deny | tarpit | auth [realm <realm>] | redirect <rule> |
{ track-sc0 | track-sc1 | track-sc2 } <key> [table <table>] |
sc-inc-gpc0(<sc-id>) |
sc-set-gpt0(<sc-id>) <int> |
silent-drop |
lua <function name>
}
[ { if | unless } <condition> ]
@ -3441,7 +3442,8 @@ http-request { allow | deny | tarpit | auth [realm <realm>] | redirect <rule> |
efficient against very dumb robots, and will significantly reduce the
load on firewalls compared to a "deny" rule. But when facing "correctly"
developed robots, it can make things worse by forcing haproxy and the
front firewall to support insane number of concurrent connections.
front firewall to support insane number of concurrent connections. See
also the "silent-drop" action below.
- "auth" : this stops the evaluation of the rules and immediately responds
with an HTTP 401 or 407 error code to invite the user to present a valid
@ -3727,6 +3729,23 @@ http-request { allow | deny | tarpit | auth [realm <realm>] | redirect <rule> |
When set-src is successful, the source port is set to 0.
- "silent-drop" : this stops the evaluation of the rules and makes the
client-facing connection suddenly disappear using a system-dependant way
that tries to prevent the client from being notified. The effect it then
that the client still sees an established connection while there's none
on HAProxy. The purpose is to achieve a comparable effect to "tarpit"
except that it doesn't use any local resource at all on the machine
running HAProxy. It can resist much higher loads than "tarpit", and slow
down stronger attackers. It is important to undestand the impact of using
this mechanism. All stateful equipments placed between the client and
HAProxy (firewalls, proxies, load balancers) will also keep the
established connection for a long time and may suffer from this action.
On modern Linux systems running with enough privileges, the TCP_REPAIR
socket option is used to block the emission of a TCP reset. On other
systems, the socket's TTL is reduced to 1 so that the TCP reset doesn't
pass the first router, though it's still delivered to local networks. Do
not use it unless you fully understand how it works.
There is no limit to the number of http-request statements per instance.
It is important to know that http-request rules are processed very early in
@ -3796,6 +3815,7 @@ http-response { allow | deny | add-header <name> <fmt> | set-nice <nice> |
set-var(<var-name>) <expr> |
sc-inc-gpc0(<sc-id>) |
sc-set-gpt0(<sc-id>) <int> |
silent-drop |
lua <function name>
}
[ { if | unless } <condition> ]
@ -4014,6 +4034,23 @@ http-response { allow | deny | add-header <name> <fmt> | set-nice <nice> |
designated by <sc-id>. If an error occurs, this action silently fails and
the actions evaluation continues.
- "silent-drop" : this stops the evaluation of the rules and makes the
client-facing connection suddenly disappear using a system-dependant way
that tries to prevent the client from being notified. The effect it then
that the client still sees an established connection while there's none
on HAProxy. The purpose is to achieve a comparable effect to "tarpit"
except that it doesn't use any local resource at all on the machine
running HAProxy. It can resist much higher loads than "tarpit", and slow
down stronger attackers. It is important to undestand the impact of using
this mechanism. All stateful equipments placed between the client and
HAProxy (firewalls, proxies, load balancers) will also keep the
established connection for a long time and may suffer from this action.
On modern Linux systems running with enough privileges, the TCP_REPAIR
socket option is used to block the emission of a TCP reset. On other
systems, the socket's TTL is reduced to 1 so that the TCP reset doesn't
pass the first router, though it's still delivered to local networks. Do
not use it unless you fully understand how it works.
There is no limit to the number of http-response statements per instance.
It is important to know that http-response rules are processed very early in
@ -8516,6 +8553,24 @@ tcp-request connection <action> [{if | unless} <condition>]
an error occurs, this action silently fails and the actions evaluation
continues.
- "silent-drop" :
This stops the evaluation of the rules and makes the client-facing
connection suddenly disappear using a system-dependant way that tries
to prevent the client from being notified. The effect it then that the
client still sees an established connection while there's none on
HAProxy. The purpose is to achieve a comparable effect to "tarpit"
except that it doesn't use any local resource at all on the machine
running HAProxy. It can resist much higher loads than "tarpit", and
slow down stronger attackers. It is important to undestand the impact
of using this mechanism. All stateful equipments placed between the
client and HAProxy (firewalls, proxies, load balancers) will also keep
the established connection for a long time and may suffer from this
action. On modern Linux systems running with enough privileges, the
TCP_REPAIR socket option is used to block the emission of a TCP
reset. On other systems, the socket's TTL is reduced to 1 so that the
TCP reset doesn't pass the first router, though it's still delivered to
local networks. Do not use it unless you fully understand how it works.
Note that the "if/unless" condition is optional. If no condition is set on
the action, it is simply performed unconditionally. That can be useful for
"track-sc*" actions as well as for changing the default action to a reject.
@ -8588,6 +8643,7 @@ tcp-request content <action> [{if | unless} <condition>]
- set-gpt0(<sc-id>) <int>
- lua <function>
- set-var(<var-name>) <expr>
- silent-drop
They have the same meaning as their counter-parts in "tcp-request connection"
so please refer to that section for a complete description.
@ -8808,6 +8864,24 @@ tcp-response content <action> [{if | unless} <condition>]
an error occurs, this action silently fails and the actions evaluation
continues.
- "silent-drop" :
This stops the evaluation of the rules and makes the client-facing
connection suddenly disappear using a system-dependant way that tries
to prevent the client from being notified. The effect it then that the
client still sees an established connection while there's none on
HAProxy. The purpose is to achieve a comparable effect to "tarpit"
except that it doesn't use any local resource at all on the machine
running HAProxy. It can resist much higher loads than "tarpit", and
slow down stronger attackers. It is important to undestand the impact
of using this mechanism. All stateful equipments placed between the
client and HAProxy (firewalls, proxies, load balancers) will also keep
the established connection for a long time and may suffer from this
action. On modern Linux systems running with enough privileges, the
TCP_REPAIR socket option is used to block the emission of a TCP
reset. On other systems, the socket's TTL is reduced to 1 so that the
TCP reset doesn't pass the first router, though it's still delivered to
local networks. Do not use it unless you fully understand how it works.
Note that the "if/unless" condition is optional. If no condition is set on
the action, it is simply performed unconditionally. That can be useful for
for changing the default action to a reject.

View File

@ -25,6 +25,8 @@
#include <sys/un.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <common/cfgparse.h>
#include <common/compat.h>
@ -38,6 +40,7 @@
#include <types/global.h>
#include <types/capture.h>
#include <types/server.h>
#include <types/connection.h>
#include <proto/acl.h>
#include <proto/action.h>
@ -49,6 +52,7 @@
#include <proto/log.h>
#include <proto/port_range.h>
#include <proto/protocol.h>
#include <proto/proto_http.h>
#include <proto/proto_tcp.h>
#include <proto/proxy.h>
#include <proto/sample.h>
@ -1416,6 +1420,65 @@ int tcp_exec_req_rules(struct session *sess)
return result;
}
/* Executes the "silent-drop" action. May be called from {tcp,http}{request,response} */
static enum act_return tcp_exec_action_silent_drop(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *strm, int flags)
{
struct connection *conn = objt_conn(sess->origin);
if (!conn)
goto out;
if (!conn_ctrl_ready(conn))
goto out;
conn_sock_drain(conn);
#ifdef TCP_QUICKACK
/* re-enable quickack if it was disabled to ack all data and avoid
* retransmits from the client that might trigger a real reset.
*/
setsockopt(conn->t.sock.fd, SOL_TCP, TCP_QUICKACK, &one, sizeof(one));
#endif
/* lingering must absolutely be disabled so that we don't send a
* shutdown(), this is critical to the TCP_REPAIR trick. When no stream
* is present, returning with ERR will cause lingering to be disabled.
*/
if (strm)
strm->si[0].flags |= SI_FL_NOLINGER;
#ifdef TCP_REPAIR
if (setsockopt(conn->t.sock.fd, SOL_TCP, TCP_REPAIR, &one, sizeof(one)) == 0) {
/* socket will be quiet now */
goto out;
}
#endif
/* either TCP_REPAIR is not defined or it failed (eg: permissions).
* Let's fall back on the TTL trick, though it only works for routed
* network and has no effect on local net.
*/
#ifdef IP_TTL
setsockopt(conn->t.sock.fd, SOL_IP, IP_TTL, &one, sizeof(one));
#endif
out:
/* kill the stream if any */
if (strm) {
channel_abort(&strm->req);
channel_abort(&strm->res);
strm->req.analysers = 0;
strm->res.analysers = 0;
strm->be->be_counters.denied_req++;
if (!(strm->flags & SF_ERR_MASK))
strm->flags |= SF_ERR_PRXCOND;
if (!(strm->flags & SF_FINST_MASK))
strm->flags |= SF_FINST_R;
}
sess->fe->fe_counters.denied_req++;
if (sess->listener->counters)
sess->listener->counters->denied_req++;
return ACT_RET_STOP;
}
/* Parse a tcp-response rule. Return a negative value in case of failure */
static int tcp_parse_response_rule(char **args, int arg, int section_type,
struct proxy *curpx, struct proxy *defpx,
@ -1953,6 +2016,17 @@ static int tcp_parse_tcp_req(char **args, int section_type, struct proxy *curpx,
return -1;
}
/* Parse a "silent-drop" action. It takes no argument. It returns ACT_RET_PRS_OK on
* success, ACT_RET_PRS_ERR on error.
*/
static enum act_parse_ret tcp_parse_silent_drop(const char **args, int *orig_arg, struct proxy *px,
struct act_rule *rule, char **err)
{
rule->action = ACT_CUSTOM;
rule->action_ptr = tcp_exec_action_silent_drop;
return ACT_RET_PRS_OK;
}
/************************************************************************/
/* All supported sample fetch functions must be declared here */
@ -2299,6 +2373,33 @@ static struct bind_kw_list bind_kws = { "TCP", { }, {
{ NULL, NULL, 0 },
}};
static struct action_kw_list tcp_req_conn_actions = {ILH, {
{ "silent-drop", tcp_parse_silent_drop },
{ /* END */ }
}};
static struct action_kw_list tcp_req_cont_actions = {ILH, {
{ "silent-drop", tcp_parse_silent_drop },
{ /* END */ }
}};
static struct action_kw_list tcp_res_cont_actions = {ILH, {
{ "silent-drop", tcp_parse_silent_drop },
{ /* END */ }
}};
static struct action_kw_list http_req_actions = {ILH, {
{ "silent-drop", tcp_parse_silent_drop },
{ /* END */ }
}};
static struct action_kw_list http_res_actions = {ILH, {
{ "silent-drop", tcp_parse_silent_drop },
{ /* END */ }
}};
__attribute__((constructor))
static void __tcp_protocol_init(void)
{
@ -2308,6 +2409,11 @@ static void __tcp_protocol_init(void)
cfg_register_keywords(&cfg_kws);
acl_register_keywords(&acl_kws);
bind_register_keywords(&bind_kws);
tcp_req_conn_keywords_register(&tcp_req_conn_actions);
tcp_req_cont_keywords_register(&tcp_req_cont_actions);
tcp_res_cont_keywords_register(&tcp_res_cont_actions);
http_req_keywords_register(&http_req_actions);
http_res_keywords_register(&http_res_actions);
}