MEDIUM: tcp-act: add parameter rst-ttl to silent-drop
The silent-drop action was extended with an additional optional parameter,
[rst-ttl <ttl> ], causing HAProxy to send a TCP RST with the specified TTL
towards the client.
With this behaviour, the connection state on your own client-
facing middle-boxes (load balancers, firewalls) will be purged,
but the client will still assume the TCP connection is up because
the TCP RST packet expires before reaching the client.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 1eb87fc..2a951c3 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -6443,7 +6443,7 @@
- set-var(<var-name>[,<cond> ...]) <expr>
- set-var-fmt(<var-name>[,<cond> ...]) <fmt>
- send-spoe-group <engine-name> <group-name>
- - silent-drop
+ - silent-drop [ rst-ttl <ttl> ]
- strict-mode { on | off }
- tarpit [ { status | deny_status } <code>] ...
- track-sc0 <key> [table <table>]
@@ -7398,24 +7398,27 @@
http-request set-var(req.my_var) req.fhdr(user-agent),lower
http-request set-var-fmt(txn.from) %[src]:%[src_port]
-http-request silent-drop [ { if | unless } <condition> ]
+http-request silent-drop [ rst-ttl <ttl> ] [ { if | unless } <condition> ]
- This stops the evaluation of the rules and makes the client-facing connection
- suddenly disappear using a system-dependent 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
- understand the impact of using this mechanism. All stateful equipment 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.
+ This stops the evaluation of the rules and removes the client-facing
+ connection in a configurable way: When called without the rst-ttl argument,
+ we try to prevent sending any FIN or RST packet back to the client by
+ using TCP_REPAIR. If this fails (mainly because of missing privileges),
+ we fall back to sending a RST packet with a TTL of 1.
+
+ The effect is that the client still sees an established connection while
+ there is none on HAProxy, saving resources. However, stateful equipment
+ placed between the HAProxy and the client (firewalls, proxies,
+ load balancers) will also keep the established connection in their
+ session tables.
+
+ The optional rst-ttl changes this behaviour: TCP_REPAIR is not used,
+ and a RST packet with a configurable TTL is sent. When set to a
+ reasonable value, the RST packet travels through your own equipment,
+ deleting the connection in your middle-boxes, but does not arrive at
+ the client. Future packets from the client will then be dropped
+ already by your middle-boxes. These "local RST"s protect your resources,
+ but not the client's. Do not use it unless you fully understand how it works.
http-request strict-mode { on | off } [ { if | unless } <condition> ]
@@ -7849,7 +7852,7 @@
inline. Please refer to "http-request set-var" and "http-request set-var-fmt"
for a complete description.
-http-response silent-drop [ { if | unless } <condition> ]
+http-response silent-drop [ rst-ttl <ttl> ] [ { if | unless } <condition> ]
This stops the evaluation of the rules and makes the client-facing connection
suddenly disappear using a system-dependent way that tries to prevent the
@@ -12650,7 +12653,7 @@
scopes. Please refer to "http-request set-var" and "http-request set-var-fmt"
for a complete description.
-tcp-request connection silent-drop [ { if | unless } <condition> ]
+tcp-request connection silent-drop [ rst-ttl <ttl> ] [ { if | unless } <condition> ]
This stops the evaluation of the rules and makes the client-facing connection
suddenly disappear using a system-dependent way that tries to prevent the
@@ -12972,7 +12975,7 @@
inline. Please refer to "http-request set-var" and "http-request set-var-fmt"
for a complete description.
-tcp-request content silent-drop [ { if | unless } <condition> ]
+tcp-request content silent-drop [ rst-ttl <ttl> ] [ { if | unless } <condition> ]
This stops the evaluation of the rules and makes the client-facing connection
suddenly disappear using a system-dependent way that tries to prevent the
@@ -13230,7 +13233,7 @@
inline. Please refer to "http-request set-var" and "http-request set-var-fmt"
for a complete description.
-tcp-request session silent-drop [ { if | unless } <condition> ]
+tcp-request session silent-drop [ rst-ttl <ttl> ] [ { if | unless } <condition> ]
This stops the evaluation of the rules and makes the client-facing connection
suddenly disappear using a system-dependent way that tries to prevent the
@@ -13400,7 +13403,7 @@
inline. Please refer to "http-request set-var" and "http-request set-var-fmt"
for a complete description.
-tcp-response content silent-drop [ { if | unless } <condition> ]
+tcp-response content silent-drop [ rst-ttl <ttl> ] [ { if | unless } <condition> ]
This stops the evaluation of the rules and makes the client-facing connection
suddenly disappear using a system-dependent way that tries to prevent the
diff --git a/src/tcp_act.c b/src/tcp_act.c
index f31c9c3..142e1ba 100644
--- a/src/tcp_act.c
+++ b/src/tcp_act.c
@@ -261,11 +261,25 @@
return ACT_RET_CONT;
}
-/* Executes the "silent-drop" action. May be called from {tcp,http}{request,response} */
+/* Executes the "silent-drop" action. May be called from {tcp,http}{request,response}.
+ * If rule->arg.act.p[0] is 0, TCP_REPAIR is tried first, with a fallback to
+ * sending a RST with TTL 1 towards the client. If it is [1-255], we will skip
+ * TCP_REPAIR and prepare the socket to send a RST with the requested TTL when
+ * the connection is killed by channel_abort().
+ */
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);
+ unsigned int ttl __maybe_unused = (uintptr_t)rule->arg.act.p[0];
+ char tcp_repair_enabled __maybe_unused;
+
+ if (ttl == 0) {
+ tcp_repair_enabled = 1;
+ ttl = 1;
+ } else {
+ tcp_repair_enabled = 0;
+ }
if (!conn)
goto out;
@@ -298,22 +312,27 @@
HA_ATOMIC_OR(&fdtab[conn->handle.fd].state, FD_LINGER_RISK);
#ifdef TCP_REPAIR
- if (setsockopt(conn->handle.fd, IPPROTO_TCP, TCP_REPAIR, &one, sizeof(one)) == 0) {
+ /* try to put socket in repair mode if sending a RST was not requested by
+ * config. this often fails due to missing permissions (CAP_NET_ADMIN capability)
+ */
+ if (tcp_repair_enabled && (setsockopt(conn->handle.fd, IPPROTO_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.
+
+ /* Either TCP_REPAIR is not defined, it failed (eg: permissions), or was
+ * not executed because a RST with a specific TTL was requested to be sent.
+ * Set the TTL of the client connection before the connection is killed
+ * by channel_abort and a RST packet will be emitted by the TCP/IP stack.
*/
#ifdef IP_TTL
if (conn->src && conn->src->ss_family == AF_INET)
- setsockopt(conn->handle.fd, IPPROTO_IP, IP_TTL, &one, sizeof(one));
+ setsockopt(conn->handle.fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
#endif
#ifdef IPV6_UNICAST_HOPS
if (conn->src && conn->src->ss_family == AF_INET6)
- setsockopt(conn->handle.fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one));
+ setsockopt(conn->handle.fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl));
#endif
out:
/* kill the stream if any */
@@ -480,15 +499,41 @@
#endif
}
-
-/* Parse a "silent-drop" action. It takes no argument. It returns ACT_RET_PRS_OK on
- * success, ACT_RET_PRS_ERR on error.
+/* Parse a "silent-drop" action. It may take 2 optional arguments to define a
+ * "rst-ttl" parameter. 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,
+static enum act_parse_ret tcp_parse_silent_drop(const char **args, int *cur_arg, struct proxy *px,
struct act_rule *rule, char **err)
{
+ unsigned int rst_ttl = 0;
+ char *endp;
+
rule->action = ACT_CUSTOM;
rule->action_ptr = tcp_exec_action_silent_drop;
+
+ if (strcmp(args[*cur_arg], "rst-ttl") == 0) {
+ if (!*args[*cur_arg + 1]) {
+ memprintf(err, "missing rst-ttl value\n");
+ return ACT_RET_PRS_ERR;
+ }
+
+ rst_ttl = (unsigned int)strtoul(args[*cur_arg + 1], &endp, 0);
+
+ if (endp && *endp != '\0') {
+ memprintf(err, "invalid character starting at '%s' (value 1-255 expected)\n",
+ endp);
+ return ACT_RET_PRS_ERR;
+ }
+ if ((rst_ttl == 0) || (rst_ttl > 255) ) {
+ memprintf(err, "valid rst-ttl values are [1-255]\n");
+ return ACT_RET_PRS_ERR;
+ }
+
+ *cur_arg += 2;
+ }
+
+ rule->arg.act.p[0] = (void *)(uintptr_t)rst_ttl;
return ACT_RET_PRS_OK;
}