[MEDIUM] add non-local bind to connect() on Linux

Using some Linux kernel patches which add the IP_TRANSPARENT
SOL_IP option , it is possible to bind to a non-local address
on without having resort to any sort of NAT, thus causing no
performance degradation.

This is by far faster and cleaner than the previous CTTPROXY
method. The code has been slightly changed in order to remain
compatible with CTTPROXY as a fallback for the new method when
it does not work.

It is not needed anymore to specify the outgoing source address
for connect, it can remain 0.0.0.0.
This commit is contained in:
Willy Tarreau 2008-01-13 16:31:17 +01:00
parent b1e52e8c44
commit 5b6995c31b
2 changed files with 142 additions and 70 deletions

View File

@ -1113,6 +1113,87 @@ int assign_server_and_queue(struct session *s)
}
}
/* Binds ipv4 address <local> to socket <fd>, unless <flags> is set, in which
* case we try to bind <remote>. <flags> is a 2-bit field consisting of :
* - 0 : ignore remote address (may even be a NULL pointer)
* - 1 : use provided address
* - 2 : use provided port
* - 3 : use both
*
* The function supports multiple foreign binding methods :
* - linux_tproxy: we directly bind to the foreign address
* - cttproxy: we bind to a local address then nat.
* The second one can be used as a fallback for the first one.
* This function returns 0 when everything's OK, 1 if it could not bind, to the
* local address, 2 if it could not bind to the foreign address.
*/
static int bind_ipv4(int fd, int flags, struct sockaddr_in *local, struct sockaddr_in *remote)
{
struct sockaddr_in bind_addr;
int foreign_ok = 0;
int ret;
#ifdef CONFIG_HAP_LINUX_TPROXY
static int ip_transp_working = 1;
if (flags && ip_transp_working) {
if (setsockopt(fd, SOL_IP, IP_TRANSPARENT, (char *) &one, sizeof(one)) == 0)
foreign_ok = 1;
else
ip_transp_working = 0;
}
#endif
if (flags) {
memset(&bind_addr, 0, sizeof(bind_addr));
if (flags & 1)
bind_addr.sin_addr = remote->sin_addr;
if (flags & 2)
bind_addr.sin_port = remote->sin_port;
}
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one));
if (foreign_ok) {
ret = bind(fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr));
if (ret < 0)
return 2;
}
else {
ret = bind(fd, (struct sockaddr *)local, sizeof(*local));
if (ret < 0)
return 1;
}
if (!flags)
return 0;
#ifdef CONFIG_HAP_CTTPROXY
if (!foreign_ok) {
struct in_tproxy itp1, itp2;
memset(&itp1, 0, sizeof(itp1));
itp1.op = TPROXY_ASSIGN;
itp1.v.addr.faddr = bind_addr.sin_addr;
itp1.v.addr.fport = bind_addr.sin_port;
/* set connect flag on socket */
itp2.op = TPROXY_FLAGS;
itp2.v.flags = ITP_CONNECT | ITP_ONCE;
if (setsockopt(fd, SOL_IP, IP_TPROXY, &itp1, sizeof(itp1)) != -1 &&
setsockopt(fd, SOL_IP, IP_TPROXY, &itp2, sizeof(itp2)) != -1) {
foreign_ok = 1;
}
}
#endif
if (!foreign_ok) {
/* we could not bind to a foreign address */
close(fd);
return 2;
}
return 0;
}
/*
* This function initiates a connection to the server assigned to this session
@ -1189,99 +1270,84 @@ int connect_server(struct session *s)
* - proxy-specific next
*/
if (s->srv != NULL && s->srv->state & SRV_BIND_SRC) {
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one));
if (bind(fd, (struct sockaddr *)&s->srv->source_addr, sizeof(s->srv->source_addr)) == -1) {
Alert("Cannot bind to source address before connect() for server %s/%s. Aborting.\n",
s->be->id, s->srv->id);
close(fd);
send_log(s->be, LOG_EMERG,
"Cannot bind to source address before connect() for server %s/%s.\n",
s->be->id, s->srv->id);
return SN_ERR_RESOURCE;
}
#ifdef CONFIG_HAP_CTTPROXY
if (s->srv->state & SRV_TPROXY_MASK) {
struct in_tproxy itp1, itp2;
memset(&itp1, 0, sizeof(itp1));
struct sockaddr_in *remote = NULL;
int ret, flags = 0;
itp1.op = TPROXY_ASSIGN;
if (s->srv->state & SRV_TPROXY_MASK) {
switch (s->srv->state & SRV_TPROXY_MASK) {
case SRV_TPROXY_ADDR:
itp1.v.addr.faddr = s->srv->tproxy_addr.sin_addr;
itp1.v.addr.fport = s->srv->tproxy_addr.sin_port;
remote = (struct sockaddr_in *)&s->srv->tproxy_addr;
flags = 3;
break;
case SRV_TPROXY_CLI:
itp1.v.addr.fport = ((struct sockaddr_in *)&s->cli_addr)->sin_port;
flags |= 2;
/* fall through */
case SRV_TPROXY_CIP:
/* FIXME: what can we do if the client connects in IPv6 ? */
itp1.v.addr.faddr = ((struct sockaddr_in *)&s->cli_addr)->sin_addr;
flags |= 1;
remote = (struct sockaddr_in *)&s->cli_addr;
break;
}
}
/* set connect flag on socket */
itp2.op = TPROXY_FLAGS;
itp2.v.flags = ITP_CONNECT | ITP_ONCE;
if (setsockopt(fd, SOL_IP, IP_TPROXY, &itp1, sizeof(itp1)) == -1 ||
setsockopt(fd, SOL_IP, IP_TPROXY, &itp2, sizeof(itp2)) == -1) {
ret = bind_ipv4(fd, flags, &s->srv->source_addr, remote);
if (ret) {
close(fd);
if (ret == 1) {
Alert("Cannot bind to source address before connect() for server %s/%s. Aborting.\n",
s->be->id, s->srv->id);
send_log(s->be, LOG_EMERG,
"Cannot bind to source address before connect() for server %s/%s.\n",
s->be->id, s->srv->id);
} else {
Alert("Cannot bind to tproxy source address before connect() for server %s/%s. Aborting.\n",
s->be->id, s->srv->id);
close(fd);
send_log(s->be, LOG_EMERG,
"Cannot bind to tproxy source address before connect() for server %s/%s.\n",
s->be->id, s->srv->id);
return SN_ERR_RESOURCE;
}
}
#endif
}
else if (s->be->options & PR_O_BIND_SRC) {
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one));
if (bind(fd, (struct sockaddr *)&s->be->source_addr, sizeof(s->be->source_addr)) == -1) {
Alert("Cannot bind to source address before connect() for proxy %s. Aborting.\n", s->be->id);
close(fd);
send_log(s->be, LOG_EMERG,
"Cannot bind to source address before connect() for proxy %s.\n",
s->be->id);
return SN_ERR_RESOURCE;
}
#ifdef CONFIG_HAP_CTTPROXY
if (s->be->options & PR_O_TPXY_MASK) {
struct in_tproxy itp1, itp2;
memset(&itp1, 0, sizeof(itp1));
}
else if (s->be->options & PR_O_BIND_SRC) {
struct sockaddr_in *remote = NULL;
int ret, flags = 0;
itp1.op = TPROXY_ASSIGN;
if (s->be->options & PR_O_TPXY_MASK) {
switch (s->be->options & PR_O_TPXY_MASK) {
case PR_O_TPXY_ADDR:
itp1.v.addr.faddr = s->be->tproxy_addr.sin_addr;
itp1.v.addr.fport = s->be->tproxy_addr.sin_port;
remote = (struct sockaddr_in *)&s->be->tproxy_addr;
flags = 3;
break;
case PR_O_TPXY_CLI:
itp1.v.addr.fport = ((struct sockaddr_in *)&s->cli_addr)->sin_port;
flags |= 2;
/* fall through */
case PR_O_TPXY_CIP:
/* FIXME: what can we do if the client connects in IPv6 ? */
itp1.v.addr.faddr = ((struct sockaddr_in *)&s->cli_addr)->sin_addr;
flags |= 1;
remote = (struct sockaddr_in *)&s->cli_addr;
break;
}
}
/* set connect flag on socket */
itp2.op = TPROXY_FLAGS;
itp2.v.flags = ITP_CONNECT | ITP_ONCE;
if (setsockopt(fd, SOL_IP, IP_TPROXY, &itp1, sizeof(itp1)) == -1 ||
setsockopt(fd, SOL_IP, IP_TPROXY, &itp2, sizeof(itp2)) == -1) {
ret = bind_ipv4(fd, flags, &s->srv->source_addr, remote);
if (ret) {
close(fd);
if (ret == 1) {
Alert("Cannot bind to source address before connect() for proxy %s. Aborting.\n",
s->be->id);
send_log(s->be, LOG_EMERG,
"Cannot bind to source address before connect() for proxy %s.\n",
s->be->id);
} else {
Alert("Cannot bind to tproxy source address before connect() for proxy %s. Aborting.\n",
s->be->id);
close(fd);
send_log(s->be, LOG_EMERG,
"Cannot bind to tproxy source address before connect() for proxy %s.\n",
s->be->id);
return SN_ERR_RESOURCE;
}
return SN_ERR_RESOURCE;
}
#endif
}
if ((connect(fd, (struct sockaddr *)&s->srv_addr, sizeof(s->srv_addr)) == -1) &&

View File

@ -1615,7 +1615,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
}
else if (!strcmp(args[cur_arg], "source")) { /* address to which we bind when connecting */
if (!*args[cur_arg + 1]) {
#ifdef CONFIG_HAP_CTTPROXY
#if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY)
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], and optional '%s' <addr> as argument.\n",
file, linenum, "source", "usesrc");
#else
@ -1628,12 +1628,14 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
newsrv->source_addr = *str2sa(args[cur_arg + 1]);
cur_arg += 2;
if (!strcmp(args[cur_arg], "usesrc")) { /* address to use outside */
#ifdef CONFIG_HAP_CTTPROXY
#if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY)
#if !defined(CONFIG_HAP_LINUX_TPROXY)
if (newsrv->source_addr.sin_addr.s_addr == INADDR_ANY) {
Alert("parsing [%s:%d] : '%s' requires an explicit '%s' address.\n",
file, linenum, "usesrc", "source");
return -1;
}
#endif
if (!*args[cur_arg + 1]) {
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], 'client', or 'clientip' as argument.\n",
file, linenum, "usesrc");
@ -1647,22 +1649,23 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
newsrv->state |= SRV_TPROXY_ADDR;
newsrv->tproxy_addr = *str2sa(args[cur_arg + 1]);
}
global.last_checks |= LSTCHK_CTTPROXY | LSTCHK_NETADM;
global.last_checks |= LSTCHK_NETADM;
#if !defined(CONFIG_HAP_LINUX_TPROXY)
global.last_checks |= LSTCHK_CTTPROXY;
#endif
cur_arg += 2;
#else /* no CTTPROXY support */
Alert("parsing [%s:%d] : '%s' not allowed here because support for cttproxy was not compiled in.\n",
#else /* no TPROXY support */
Alert("parsing [%s:%d] : '%s' not allowed here because support for TPROXY was not compiled in.\n",
file, linenum, "usesrc");
return -1;
#endif
}
}
#ifdef CONFIG_HAP_CTTPROXY
else if (!strcmp(args[cur_arg], "usesrc")) { /* address to use outside: needs "source" first */
Alert("parsing [%s:%d] : '%s' only allowed after a '%s' statement.\n",
file, linenum, "usesrc", "source");
return -1;
}
#endif
else {
Alert("parsing [%s:%d] : server %s only supports options 'backup', 'cookie', 'check', 'inter', 'rise', 'fall', 'addr', 'port', 'source', 'minconn', 'maxconn', 'maxqueue', 'slowstart' and 'weight'.\n",
file, linenum, newsrv->id);
@ -1774,7 +1777,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
return 0;
if (!*args[1]) {
#ifdef CONFIG_HAP_CTTPROXY
#if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY)
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], and optional '%s' <addr> as argument.\n",
file, linenum, "source", "usesrc");
#else
@ -1787,12 +1790,14 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
curproxy->source_addr = *str2sa(args[1]);
curproxy->options |= PR_O_BIND_SRC;
if (!strcmp(args[2], "usesrc")) { /* address to use outside */
#ifdef CONFIG_HAP_CTTPROXY
#if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY)
#if !defined(CONFIG_HAP_LINUX_TPROXY)
if (curproxy->source_addr.sin_addr.s_addr == INADDR_ANY) {
Alert("parsing [%s:%d] : '%s' requires an explicit 'source' address.\n",
file, linenum, "usesrc");
return -1;
}
#endif
if (!*args[3]) {
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], 'client', or 'clientip' as argument.\n",
file, linenum, "usesrc");
@ -1807,21 +1812,22 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
curproxy->options |= PR_O_TPXY_ADDR;
curproxy->tproxy_addr = *str2sa(args[3]);
}
global.last_checks |= LSTCHK_CTTPROXY | LSTCHK_NETADM;
#else /* no CTTPROXY support */
Alert("parsing [%s:%d] : '%s' not allowed here because support for cttproxy was not compiled in.\n",
global.last_checks |= LSTCHK_NETADM;
#if !defined(CONFIG_HAP_LINUX_TPROXY)
global.last_checks |= LSTCHK_CTTPROXY;
#endif
#else /* no TPROXY support */
Alert("parsing [%s:%d] : '%s' not allowed here because support for TPROXY was not compiled in.\n",
file, linenum, "usesrc");
return -1;
#endif
}
}
#ifdef CONFIG_HAP_CTTPROXY
else if (!strcmp(args[0], "usesrc")) { /* address to use outside: needs "source" first */
Alert("parsing [%s:%d] : '%s' only allowed after a '%s' statement.\n",
file, linenum, "usesrc", "source");
return -1;
}
#endif
else if (!strcmp(args[0], "cliexp") || !strcmp(args[0], "reqrep")) { /* replace request header from a regex */
regex_t *preg;
if (curproxy == &defproxy) {