diff --git a/doc/configuration.txt b/doc/configuration.txt index 2bfd314e3..430b0ca88 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 63f034525..5afaad20f 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 717ba289c..f7610dfb2 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 f002573d7..c2d16896d 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)