From 16e015635c1c76604572acc4d780572d451977ac Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Tue, 9 Aug 2016 16:46:18 +0200 Subject: [PATCH] MINOR: tcp: add dst_is_local and src_is_local It is sometimes needed in application server environments to easily tell if a source is local to the machine or a remote one, without necessarily knowing all the local addresses (dhcp, vrrp, etc). Similarly in transparent proxy configurations it is sometimes desired to tell the difference between local and remote destination addresses. This patch adds two new sample fetch functions for this : dst_is_local : boolean Returns true if the destination address of the incoming connection is local to the system, or false if the address doesn't exist on the system, meaning that it was intercepted in transparent mode. It can be useful to apply certain rules by default to forwarded traffic and other rules to the traffic targetting the real address of the machine. For example the stats page could be delivered only on this address, or SSH access could be locally redirected. Please note that the check involves a few system calls, so it's better to do it only once per connection. src_is_local : boolean Returns true if the source address of the incoming connection is local to the system, or false if the address doesn't exist on the system, meaning that it comes from a remote machine. Note that UNIX addresses are considered local. It can be useful to apply certain access restrictions based on where the client comes from (eg: require auth or https for remote machines). Please note that the check involves a few system calls, so it's better to do it only once per connection. --- doc/configuration.txt | 19 +++++++++++++++++ include/common/standard.h | 12 +++++++++++ src/proto_tcp.c | 44 +++++++++++++++++++++++++++++++++++++++ src/standard.c | 43 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+) diff --git a/doc/configuration.txt b/doc/configuration.txt index 2bfd314e3c..430b0ca880 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -12733,6 +12733,16 @@ dst_conn : integer different limits to different listening ports or addresses. See also the "fe_conn" and "be_conn" fetches. +dst_is_local : boolean + Returns true if the destination address of the incoming connection is local + to the system, or false if the address doesn't exist on the system, meaning + that it was intercepted in transparent mode. It can be useful to apply + certain rules by default to forwarded traffic and other rules to the traffic + targetting the real address of the machine. For example the stats page could + be delivered only on this address, or SSH access could be locally redirected. + Please note that the check involves a few system calls, so it's better to do + it only once per connection. + dst_port : integer Returns an integer value corresponding to the destination TCP port of the connection on the client side, which is the port the client connected to. @@ -13076,6 +13086,15 @@ src_inc_gpc0([]) : integer acl kill src_inc_gpc0 gt 0 tcp-request connection reject if abuse kill +src_is_local : boolean + Returns true if the source address of the incoming connection is local to the + system, or false if the address doesn't exist on the system, meaning that it + comes from a remote machine. Note that UNIX addresses are considered local. + It can be useful to apply certain access restrictions based on where the + client comes from (eg: require auth or https for remote machines). Please + note that the check involves a few system calls, so it's better to do it only + once per connection. + src_kbytes_in([
]) : integer Returns the total amount of data received from the incoming connection's source address in the current proxy's stick-table or in the designated diff --git a/include/common/standard.h b/include/common/standard.h index 63f0345250..5afaad20fb 100644 --- a/include/common/standard.h +++ b/include/common/standard.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #ifndef LLONG_MAX @@ -358,6 +359,17 @@ int addr_to_str(struct sockaddr_storage *addr, char *str, int size); */ int port_to_str(struct sockaddr_storage *addr, char *str, int size); +/* check if the given address is local to the system or not. It will return + * -1 when it's not possible to know, 0 when the address is not local, 1 when + * it is. We don't want to iterate over all interfaces for this (and it is not + * portable). So instead we try to bind in UDP to this address on a free non + * privileged port and to connect to the same address, port 0 (connect doesn't + * care). If it succeeds, we own the address. Note that non-inet addresses are + * considered local since they're most likely AF_UNIX. + */ +int addr_is_local(const struct netns_entry *ns, + const struct sockaddr_storage *orig); + /* will try to encode the string replacing all characters tagged in * with the hexadecimal representation of their ASCII-code (2 digits) * prefixed by , and will store the result between (included) diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 717ba289c3..f7610dfb28 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -2299,6 +2299,48 @@ smp_fetch_dst(const struct arg *args, struct sample *smp, const char *kw, void * return 1; } +/* check if the destination address of the front connection is local to the + * system or if it was intercepted. + */ +int smp_fetch_dst_is_local(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct connection *conn = objt_conn(smp->sess->origin); + struct listener *li = smp->sess->listener; + + if (!conn) + return 0; + + conn_get_to_addr(conn); + if (!(conn->flags & CO_FL_ADDR_TO_SET)) + return 0; + + smp->data.type = SMP_T_BOOL; + smp->flags = 0; + smp->data.u.sint = addr_is_local(li->netns, &conn->addr.to); + return smp->data.u.sint >= 0; +} + +/* check if the source address of the front connection is local to the system + * or not. + */ +int smp_fetch_src_is_local(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct connection *conn = objt_conn(smp->sess->origin); + struct listener *li = smp->sess->listener; + + if (!conn) + return 0; + + conn_get_from_addr(conn); + if (!(conn->flags & CO_FL_ADDR_FROM_SET)) + return 0; + + smp->data.type = SMP_T_BOOL; + smp->flags = 0; + smp->data.u.sint = addr_is_local(li->netns, &conn->addr.from); + return smp->data.u.sint >= 0; +} + /* set temp integer to the frontend connexion's destination port */ static int smp_fetch_dport(const struct arg *args, struct sample *smp, const char *kw, void *private) @@ -2620,8 +2662,10 @@ static struct acl_kw_list acl_kws = {ILH, { */ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { { "dst", smp_fetch_dst, 0, NULL, SMP_T_IPV4, SMP_USE_L4CLI }, + { "dst_is_local", smp_fetch_dst_is_local, 0, NULL, SMP_T_BOOL, SMP_USE_L4CLI }, { "dst_port", smp_fetch_dport, 0, NULL, SMP_T_SINT, SMP_USE_L4CLI }, { "src", smp_fetch_src, 0, NULL, SMP_T_IPV4, SMP_USE_L4CLI }, + { "src_is_local", smp_fetch_src_is_local, 0, NULL, SMP_T_BOOL, SMP_USE_L4CLI }, { "src_port", smp_fetch_sport, 0, NULL, SMP_T_SINT, SMP_USE_L4CLI }, #ifdef TCP_INFO { "fc_rtt", smp_fetch_fc_rtt, ARG1(0,STR), NULL, SMP_T_SINT, SMP_USE_L4CLI }, diff --git a/src/standard.c b/src/standard.c index f002573d75..c2d16896d1 100644 --- a/src/standard.c +++ b/src/standard.c @@ -11,12 +11,14 @@ */ #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -1406,6 +1408,47 @@ int port_to_str(struct sockaddr_storage *addr, char *str, int size) return addr->ss_family; } +/* check if the given address is local to the system or not. It will return + * -1 when it's not possible to know, 0 when the address is not local, 1 when + * it is. We don't want to iterate over all interfaces for this (and it is not + * portable). So instead we try to bind in UDP to this address on a free non + * privileged port and to connect to the same address, port 0 (connect doesn't + * care). If it succeeds, we own the address. Note that non-inet addresses are + * considered local since they're most likely AF_UNIX. + */ +int addr_is_local(const struct netns_entry *ns, + const struct sockaddr_storage *orig) +{ + struct sockaddr_storage addr; + int result; + int fd; + + if (!is_inet_addr(orig)) + return 1; + + memcpy(&addr, orig, sizeof(addr)); + set_host_port(&addr, 0); + + fd = my_socketat(ns, addr.ss_family, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + return -1; + + result = -1; + if (bind(fd, (struct sockaddr *)&addr, get_addr_len(&addr)) == 0) { + if (connect(fd, (struct sockaddr *)&addr, get_addr_len(&addr)) == -1) + result = 0; // fail, non-local address + else + result = 1; // success, local address + } + else { + if (errno == EADDRNOTAVAIL) + result = 0; // definitely not local :-) + } + close(fd); + + return result; +} + /* will try to encode the string replacing all characters tagged in * with the hexadecimal representation of their ASCII-code (2 digits) * prefixed by , and will store the result between (included)