[MEDIUM] add ability to connect to a server from an IP found in a header

Using get_ip_from_hdr2() we can look for occurrence #X or #-X and
extract the IP it contains. This is typically designed for use with
the X-Forwarded-For header.

Using "usesrc hdr_ip(name,occ)", it becomes possible to use the IP address
found in <name>, and possibly specify occurrence number <occ>, as the
source to connect to a server. This is possible both in a server and in
a backend's source statement. This is typically used to use the source
IP previously set by a upstream proxy.
This commit is contained in:
Willy Tarreau 2009-09-07 11:51:47 +02:00
parent 090466c91a
commit bce7088275
8 changed files with 217 additions and 3 deletions

View File

@ -4127,6 +4127,7 @@ server <name> <address>[:port] [param*]
source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | client | clientip } ]
source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | hdr_ip(<hdr>[,<occ>]) } ]
source <addr>[:<port>] [interface <name>]
Set the source address for outgoing connections
May be used in sections : defaults | frontend | listen | backend
@ -4155,6 +4156,29 @@ source <addr>[:<port>] [interface <name>]
The default value of zero means the system will select a free
port.
<hdr> is the name of a HTTP header in which to fetch the IP to bind to.
This is the name of a comma-separated header list which can
contain multiple IP addresses. By default, the last occurrence is
used. This is designed to work with the X-Forwarded-For header
and to automatically bind to the the client's IP address as seen
by previous proxy, typically Stunnel. In order to use another
occurrence from the last one, please see the <occ> parameter
below. When the header (or occurrence) is not found, no binding
is performed so that the proxy's default IP address is used. Also
keep in mind that the header name is case insensitive, as for any
HTTP header.
<occ> is the occurrence number of a value to be used in a multi-value
header. This is to be used in conjunction with "hdr_ip(<hdr>)",
in order to specificy which occurrence to use for the source IP
address. Positive values indicate a position from the first
occurrence, 1 being the first one. Negative values indicate
positions relative to the last one, -1 being the last one. This
is helpful for situations where an X-Forwarded-For header is set
at the entry point of an infrastructure and must be used several
proxy layers away. When this value is not specified, -1 is
assumed. Passing a zero here disables the feature.
<name> is an optional interface name to which to bind to for outgoing
traffic. On systems supporting this features (currently, only
Linux), this allows one to bind all traffic to the server to
@ -4230,6 +4254,11 @@ source <addr>[:<port>] [interface <name>]
# with Tproxy version 4.
source 0.0.0.0 usesrc clientip
backend transparent_http
# Connect to the servers using the client's IP as seen by previous
# proxy.
source 0.0.0.0 usesrc hdr_ip(x-forwarded-for,-1)
See also : the "source" server option in section 5, the Tproxy patches for
the Linux kernel on www.balabit.com, the "bind" keyword.
@ -5576,6 +5605,7 @@ slowstart <start_time_in_ms>
Supported in default-server: Yes
source <addr>[:<pl>[-<ph>]] [usesrc { <addr2>[:<port2>] | client | clientip } ]
source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | hdr_ip(<hdr>[,<occ>]) } ]
source <addr>[:<pl>[-<ph>]] [interface <name>] ...
The "source" parameter sets the source address which will be used when
connecting to the server. It follows the exact same parameters and principle

View File

@ -63,6 +63,11 @@
#define MAX_HTTP_HDR ((BUFSIZE+79)/80)
#endif
// max # of headers in history when looking for header #-X
#ifndef MAX_HDR_HISTORY
#define MAX_HDR_HISTORY 10
#endif
// max # of loops we can perform around a read() which succeeds.
// It's very frequent that the system returns a few TCP segments at a time.
#ifndef MAX_READ_POLL_LOOPS

View File

@ -92,6 +92,8 @@ void http_return_srv_error(struct session *s, struct stream_interface *si);
void http_capture_bad_message(struct error_snapshot *es, struct session *s,
struct buffer *buf, struct http_msg *msg,
struct proxy *other_end);
unsigned int get_ip_from_hdr2(struct http_msg *msg, const char *hname, int hlen,
struct hdr_idx *idx, int occ);
void http_init_txn(struct session *s);
void http_end_txn(struct session *s);

View File

@ -249,6 +249,9 @@ struct proxy {
struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */
#if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY)
struct sockaddr_in tproxy_addr; /* non-local address we want to bind to for connect() */
char *bind_hdr_name; /* bind to this header name if defined */
int bind_hdr_len; /* length of the name of the header above */
int bind_hdr_occ; /* occurrence number of header above: >0 = from first, <0 = from end, 0=disabled */
#endif
int iface_len; /* bind interface name length */
char *iface_name; /* bind interface name or NULL */

View File

@ -106,6 +106,9 @@ struct server {
struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */
#if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY)
struct sockaddr_in tproxy_addr; /* non-local address we want to bind to for connect() */
char *bind_hdr_name; /* bind to this header name if defined */
int bind_hdr_len; /* length of the name of the header above */
int bind_hdr_occ; /* occurrence number of header above: >0 = from first, <0 = from end, 0=disabled */
#endif
int iface_len; /* bind interface name length */
char *iface_name; /* bind interface name or NULL */

View File

@ -837,8 +837,19 @@ static void assign_tproxy_address(struct session *s)
/* FIXME: what can we do if the client connects in IPv6 ? */
s->from_addr = *(struct sockaddr_in *)&s->cli_addr;
break;
case SRV_TPROXY_DYN:
if (s->srv->bind_hdr_occ) {
/* bind to the IP in a header */
s->from_addr.sin_port = 0;
s->from_addr.sin_addr.s_addr = htonl(get_ip_from_hdr2(&s->txn.req,
s->srv->bind_hdr_name,
s->srv->bind_hdr_len,
&s->txn.hdr_idx,
s->srv->bind_hdr_occ));
}
break;
default:
s->from_addr = *(struct sockaddr_in *)0;
memset(&s->from_addr, 0, sizeof(s->from_addr));
}
}
else if (s->be->options & PR_O_BIND_SRC) {
@ -851,8 +862,19 @@ static void assign_tproxy_address(struct session *s)
/* FIXME: what can we do if the client connects in IPv6 ? */
s->from_addr = *(struct sockaddr_in *)&s->cli_addr;
break;
case PR_O_TPXY_DYN:
if (s->be->bind_hdr_occ) {
/* bind to the IP in a header */
s->from_addr.sin_port = 0;
s->from_addr.sin_addr.s_addr = htonl(get_ip_from_hdr2(&s->txn.req,
s->be->bind_hdr_name,
s->be->bind_hdr_len,
&s->txn.hdr_idx,
s->be->bind_hdr_occ));
}
break;
default:
s->from_addr = *(struct sockaddr_in *)0;
memset(&s->from_addr, 0, sizeof(s->from_addr));
}
}
#endif

View File

@ -3451,15 +3451,56 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
}
#endif
if (!*args[cur_arg + 1]) {
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], 'client', or 'clientip' as argument.\n",
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], 'client', 'clientip', or 'hdr_ip(name,#)' as argument.\n",
file, linenum, "usesrc");
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
if (!strcmp(args[cur_arg + 1], "client")) {
newsrv->state &= ~SRV_TPROXY_MASK;
newsrv->state |= SRV_TPROXY_CLI;
} else if (!strcmp(args[cur_arg + 1], "clientip")) {
newsrv->state &= ~SRV_TPROXY_MASK;
newsrv->state |= SRV_TPROXY_CIP;
} else if (!strncmp(args[cur_arg + 1], "hdr_ip(", 7)) {
char *name, *end;
name = args[cur_arg+1] + 7;
while (isspace(*name))
name++;
end = name;
while (*end && !isspace(*end) && *end != ',' && *end != ')')
end++;
newsrv->state &= ~SRV_TPROXY_MASK;
newsrv->state |= SRV_TPROXY_DYN;
newsrv->bind_hdr_name = calloc(1, end - name + 1);
newsrv->bind_hdr_len = end - name;
memcpy(newsrv->bind_hdr_name, name, end - name);
newsrv->bind_hdr_name[end-name] = '\0';
newsrv->bind_hdr_occ = -1;
/* now look for an occurrence number */
while (isspace(*end))
end++;
if (*end == ',') {
end++;
name = end;
if (*end == '-')
end++;
while (isdigit(*end))
end++;
newsrv->bind_hdr_occ = strl2ic(name, end-name);
}
if (newsrv->bind_hdr_occ < -MAX_HDR_HISTORY) {
Alert("parsing [%s:%d] : usesrc hdr_ip(name,num) does not support negative"
" occurrences values smaller than %d.\n",
file, linenum, MAX_HDR_HISTORY);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
} else {
struct sockaddr_in *sk = str2sa(args[cur_arg + 1]);
if (!sk) {
@ -3725,9 +3766,50 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
}
if (!strcmp(args[cur_arg + 1], "client")) {
curproxy->options &= ~PR_O_TPXY_MASK;
curproxy->options |= PR_O_TPXY_CLI;
} else if (!strcmp(args[cur_arg + 1], "clientip")) {
curproxy->options &= ~PR_O_TPXY_MASK;
curproxy->options |= PR_O_TPXY_CIP;
} else if (!strncmp(args[cur_arg + 1], "hdr_ip(", 7)) {
char *name, *end;
name = args[cur_arg+1] + 7;
while (isspace(*name))
name++;
end = name;
while (*end && !isspace(*end) && *end != ',' && *end != ')')
end++;
curproxy->options &= ~PR_O_TPXY_MASK;
curproxy->options |= PR_O_TPXY_DYN;
curproxy->bind_hdr_name = calloc(1, end - name + 1);
curproxy->bind_hdr_len = end - name;
memcpy(curproxy->bind_hdr_name, name, end - name);
curproxy->bind_hdr_name[end-name] = '\0';
curproxy->bind_hdr_occ = -1;
/* now look for an occurrence number */
while (isspace(*end))
end++;
if (*end == ',') {
end++;
name = end;
if (*end == '-')
end++;
while (isdigit(*end))
end++;
curproxy->bind_hdr_occ = strl2ic(name, end-name);
}
if (curproxy->bind_hdr_occ < -MAX_HDR_HISTORY) {
Alert("parsing [%s:%d] : usesrc hdr_ip(name,num) does not support negative"
" occurrences values smaller than %d.\n",
file, linenum, MAX_HDR_HISTORY);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
} else {
struct sockaddr_in *sk = str2sa(args[cur_arg + 1]);
if (!sk) {
@ -5051,6 +5133,13 @@ int check_config_validity()
curproxy->options2 &= ~cfg_opts2[optnum].val;
}
}
if (curproxy->bind_hdr_occ) {
curproxy->bind_hdr_occ = 0;
Warning("config : %s '%s' : ignoring use of header %s as source IP in non-HTTP mode.\n",
proxy_type_str(curproxy), curproxy->id, curproxy->bind_hdr_name);
err_code |= ERR_WARN;
}
}
/*
@ -5074,6 +5163,13 @@ int check_config_validity()
proxy_type_str(curproxy), curproxy->id);
cfgerr++;
}
if (curproxy->mode != PR_MODE_HTTP && newsrv->bind_hdr_occ) {
newsrv->bind_hdr_occ = 0;
Warning("config : %s '%s' : server %s cannot use header %s as source IP in non-HTTP mode.\n",
proxy_type_str(curproxy), curproxy->id, newsrv->id, newsrv->bind_hdr_name);
err_code |= ERR_WARN;
}
newsrv = newsrv->next;
}

View File

@ -6534,6 +6534,59 @@ void http_capture_bad_message(struct error_snapshot *es, struct session *s,
es->src = s->cli_addr;
}
/* return the IP address pointed to by occurrence <occ> of header <hname> in
* HTTP message <msg> indexed in <idx>. If <occ> is strictly positive, the
* occurrence number corresponding to this value is returned. If <occ> is
* strictly negative, the occurrence number before the end corresponding to
* this value is returned. If <occ> is null, any value is returned, so it is
* not recommended to use it that way. Negative occurrences are limited to
* a small value because it is required to keep them in memory while scanning.
* IP address 0.0.0.0 is returned if no match is found.
*/
unsigned int get_ip_from_hdr2(struct http_msg *msg, const char *hname, int hlen, struct hdr_idx *idx, int occ)
{
struct hdr_ctx ctx;
unsigned int hdr_hist[MAX_HDR_HISTORY];
unsigned int hist_ptr;
int found = 0;
ctx.idx = 0;
if (occ >= 0) {
while (http_find_header2(hname, hlen, msg->sol, idx, &ctx)) {
occ--;
if (occ <= 0) {
found = 1;
break;
}
}
if (!found)
return 0;
return inetaddr_host_lim(ctx.line+ctx.val, ctx.line+ctx.val+ctx.vlen);
}
/* negative occurrence, we scan all the list then walk back */
if (-occ > MAX_HDR_HISTORY)
return 0;
hist_ptr = 0;
hdr_hist[hist_ptr] = 0;
while (http_find_header2(hname, hlen, msg->sol, idx, &ctx)) {
hdr_hist[hist_ptr++] = inetaddr_host_lim(ctx.line+ctx.val, ctx.line+ctx.val+ctx.vlen);
if (hist_ptr >= MAX_HDR_HISTORY)
hist_ptr = 0;
found++;
}
if (-occ > found)
return 0;
/* OK now we have the last occurrence in [hist_ptr-1], and we need to
* find occurrence -occ, so we have to check [hist_ptr+occ].
*/
hist_ptr += occ;
if (hist_ptr >= MAX_HDR_HISTORY)
hist_ptr -= MAX_HDR_HISTORY;
return hdr_hist[hist_ptr];
}
/*
* Print a debug line with a header
*/