]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: tcp: add dst_is_local and src_is_local
authorWilly Tarreau <w@1wt.eu>
Tue, 9 Aug 2016 14:46:18 +0000 (16:46 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 9 Aug 2016 14:50:08 +0000 (16:50 +0200)
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
include/common/standard.h
src/proto_tcp.c
src/standard.c

index 2bfd314e3cf43d44205d43677fe4c1f11cb237ab..430b0ca8801ca73f1ebe1640bd7c3cd672cebd45 100644 (file)
@@ -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([<table>]) : 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([<table>]) : 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
index 63f0345250fe568de91262f367f30f62f1f78ed6..5afaad20fb0a8cd41846cce002a4897bf1a508f6 100644 (file)
@@ -33,6 +33,7 @@
 #include <arpa/inet.h>
 #include <common/chunk.h>
 #include <common/config.h>
+#include <common/namespace.h>
 #include <eb32tree.h>
 
 #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 <string> replacing all characters tagged in
  * <map> with the hexadecimal representation of their ASCII-code (2 digits)
  * prefixed by <escape>, and will store the result between <start> (included)
index 717ba289c3e16fa958b443eedfaf9cb18adac350..f7610dfb288119c78c5721574151e8086f84db20 100644 (file)
@@ -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 },
index f002573d7589d5752c860d19f559889f90b131eb..c2d16896d159bbf0a07f9cd31c5299ee83e215e4 100644 (file)
  */
 
 #include <ctype.h>
+#include <errno.h>
 #include <netdb.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
+#include <unistd.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <netinet/in.h>
@@ -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 <string> replacing all characters tagged in
  * <map> with the hexadecimal representation of their ASCII-code (2 digits)
  * prefixed by <escape>, and will store the result between <start> (included)