From: Mathias Weiersmueller Date: Fri, 18 Nov 2022 23:07:56 +0000 (+0100) Subject: MEDIUM: tcp-act: add parameter rst-ttl to silent-drop X-Git-Tag: v2.7-dev10~53 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d9b7174d999feba6236577e1073b93d012932703;p=thirdparty%2Fhaproxy.git MEDIUM: tcp-act: add parameter rst-ttl to silent-drop The silent-drop action was extended with an additional optional parameter, [rst-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 1eb87fc3b8..2a951c3e76 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -6443,7 +6443,7 @@ http-request [options...] [ { if | unless } ] - set-var([, ...]) - set-var-fmt([, ...]) - send-spoe-group - - silent-drop + - silent-drop [ rst-ttl ] - strict-mode { on | off } - tarpit [ { status | deny_status } ] ... - track-sc0 [table ] @@ -7398,24 +7398,27 @@ http-request set-var-fmt([, ...]) [ { if | unless } ] +http-request silent-drop [ rst-ttl ] [ { if | unless } ] - 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 } ] @@ -7849,7 +7852,7 @@ http-response set-var-fmt([, ...]) [ { if | unless } ] +http-response silent-drop [ rst-ttl ] [ { if | unless } ] 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([, ...]) [ { 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 } ] +tcp-request connection silent-drop [ rst-ttl ] [ { if | unless } ] 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([, ...]) [ { 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 } ] +tcp-request content silent-drop [ rst-ttl ] [ { if | unless } ] 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([, ...]) [ { 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 } ] +tcp-request session silent-drop [ rst-ttl ] [ { if | unless } ] 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([, ...]) [ { 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 } ] +tcp-response content silent-drop [ rst-ttl ] [ { if | unless } ] 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 f31c9c3b92..142e1ba09e 100644 --- a/src/tcp_act.c +++ b/src/tcp_act.c @@ -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; }