]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: tcp-act: add parameter rst-ttl to silent-drop
authorMathias Weiersmueller <mathias.weiersmueller@cyberheads.ch>
Fri, 18 Nov 2022 23:07:56 +0000 (00:07 +0100)
committerWilly Tarreau <w@1wt.eu>
Sat, 19 Nov 2022 03:53:47 +0000 (04:53 +0100)
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.

doc/configuration.txt
src/tcp_act.c

index 1eb87fc3b85b3fb9ec88aacd1186ff1a78d47fef..2a951c3e76b34f9d4cf74793c05a95149bf17fbf 100644 (file)
@@ -6443,7 +6443,7 @@ http-request <action> [options...] [ { if | unless } <condition> ]
     - 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-fmt(<var-name>[,<cond> ...]) <fmt> [ { if | unless } <condi
     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 @@ http-response set-var-fmt(<var-name>[,<cond> ...]) <fmt> [ { if | unless } <cond
   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 @@ tcp-request connection set-var-fmt(<var-name>[,<cond> ...]) <fmt> [ { if | unles
   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 @@ tcp-request content set-var-fmt(<var-name>[,<cond> ...]) <fmt> [ { if | unless }
   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 @@ tcp-request session set-var-fmt(<var-name>[,<cond> ...]) <fmt> [ { if | unless }
   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 @@ tcp-response content set-var-fmt(<var-name>[,<cond> ...]) <fmt> [ { if | unless
   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
index f31c9c3b921ab2107736bc25678d673890ca5252..142e1ba09e684fdb011366f05eebb754703fee86 100644 (file)
@@ -261,11 +261,25 @@ static enum act_return tcp_action_req_set_dst_port(struct act_rule *rule, struct
        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 @@ static enum act_return tcp_exec_action_silent_drop(struct act_rule *rule, struct
        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 @@ static enum act_parse_ret tcp_parse_set_tos(const char **args, int *cur_arg, str
 #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;
 }