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.
This commit is contained in:
Willy Tarreau 2016-08-09 16:46:18 +02:00
parent f0645dce4f
commit 16e015635c
4 changed files with 118 additions and 0 deletions

View 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

View 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)

View 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 },

View File

@ -11,12 +11,14 @@
*/
#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)