From: Willy Tarreau Date: Sun, 12 Nov 2006 22:57:19 +0000 (+0100) Subject: [MAJOR] support for source binding via cttproxy X-Git-Tag: v1.3.4~49 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=77074d548b1149c2727a0a080f8356c2a08c1991;p=thirdparty%2Fhaproxy.git [MAJOR] support for source binding via cttproxy Using the cttproxy kernel patch, it's possible to bind to any source address. It is highly recommended to use the 03-natdel patch with the other ones. A new keyword appears as a complement to the "source" keyword : "usesrc". The source address is mandatory and must be valid on the interface which will see the packets. The "usesrc" option supports "client" (for full client_ip:client_port spoofing), "client_ip" (for client_ip spoofing) and any 'IP[:port]' combination to pretend to be another machine. Right now, the source binding is missing from server health-checks if set to another address. It must be implemented (think restricted firewalls). The doc is still missing too. --- diff --git a/Makefile b/Makefile index 50be7d0c70..1eef960aef 100644 --- a/Makefile +++ b/Makefile @@ -92,14 +92,60 @@ ADDLIB = # set some defines when needed. # Known ones are -DENABLE_POLL, -DENABLE_EPOLL, and -DUSE_MY_EPOLL # - use -DTPROXY to compile with transparent proxy support. +# - use -DCONFIG_HAP_CTTPROXY to enable full transparent proxy support DEFINE = -DTPROXY + +#### build options + +# do not change this one, enable USE_* variables instead. +OPTIONS = + +ifneq ($(USE_CTTPROXY),) +OPTIONS += -DCONFIG_HAP_CTTPROXY +endif + +ifneq ($(USE_TPROXY),) +OPTIONS += -DTPROXY +endif + +ifneq ($(USE_POLL),) +OPTIONS += -DENABLE_POLL +endif + +ifneq ($(USE_EPOLL),) +OPTIONS += -DENABLE_EPOLL +endif + +ifneq ($(USE_MY_EPOLL),) +OPTIONS += -DUSE_MY_EPOLL +endif + +ifneq ($(USE_NETFILTER),) +OPTIONS += -DNETFILTER +endif + +ifneq ($(USE_EPOLL_WORKAROUND),) +OPTIONS += -DEPOLL_CTL_MOD_WORKAROUND +endif + +ifneq ($(USE_GETSOCKNAME),) +OPTIONS += -DUSE_GETSOCKNAME +endif + +ifneq ($(USE_REGPARM),) +OPTIONS += -DCONFIG_HAP_USE_REGPARM +endif + +#### end of build options + + # global options TARGET_OPTS=$(COPTS.$(TARGET)) REGEX_OPTS=$(COPTS.$(REGEX)) CPU_OPTS=$(COPTS.$(CPU)) -COPTS=-Iinclude $(ADDINC) $(CPU_OPTS) $(TARGET_OPTS) $(REGEX_OPTS) $(SMALL_OPTS) $(DEFINE) +COPTS=-Iinclude $(ADDINC) $(CPU_OPTS) $(TARGET_OPTS) $(REGEX_OPTS) $(SMALL_OPTS) $(DEFINE) $(OPTIONS) LIBS=$(LIBS.$(TARGET)) $(LIBS.$(REGEX)) $(ADDLIB) CFLAGS = -Wall $(COPTS) $(DEBUG) diff --git a/include/types/backend.h b/include/types/backend.h index a6a393a51d..f70a3707cf 100644 --- a/include/types/backend.h +++ b/include/types/backend.h @@ -53,6 +53,12 @@ #define PR_O_ABRT_CLOSE 0x00800000 /* immediately abort request when client closes */ #define PR_O_SSL3_CHK 0x01000000 /* use SSLv3 CLIENT_HELLO packets for server health */ +#define PR_O_TPXY_ADDR 0x02000000 /* bind to this non-local address when connect()ing */ +#define PR_O_TPXY_CIP 0x04000000 /* bind to the client's IP address when connect()ing */ +#define PR_O_TPXY_CLI 0x06000000 /* bind to the client's IP+port when connect()ing */ +#define PR_O_TPXY_MASK 0x06000000 /* bind to a non-local address when connect()ing */ + + #endif /* _TYPES_BACKEND_H */ /* diff --git a/include/types/global.h b/include/types/global.h index 681ac05820..40c6c997ab 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -38,22 +38,27 @@ #define MODE_STARTING 128 #define MODE_FOREGROUND 256 +/* list of last checks to perform, depending on config options */ +#define LSTCHK_CAP_BIND 0x00000001 /* check that we can bind to any port */ +#define LSTCHK_CTTPROXY 0x00000002 /* check that tproxy is enabled */ +#define LSTCHK_NETADM 0x00000004 /* check that we have CAP_NET_ADMIN */ /* FIXME : this will have to be redefined correctly */ struct global { - int uid; - int gid; - int nbproc; - int maxconn; - int maxsock; /* max # of sockets */ - int rlimit_nofile; /* default ulimit-n value : 0=unset */ - int rlimit_memmax; /* default ulimit-d in megs value : 0=unset */ - int mode; - char *chroot; - char *pidfile; - int logfac1, logfac2; - int loglev1, loglev2; - struct sockaddr_in logsrv1, logsrv2; + int uid; + int gid; + int nbproc; + int maxconn; + int maxsock; /* max # of sockets */ + int rlimit_nofile; /* default ulimit-n value : 0=unset */ + int rlimit_memmax; /* default ulimit-d in megs value : 0=unset */ + int mode; + int last_checks; + char *chroot; + char *pidfile; + int logfac1, logfac2; + int loglev1, loglev2; + struct sockaddr_in logsrv1, logsrv2; }; extern struct global global; diff --git a/include/types/proxy.h b/include/types/proxy.h index f3304e63b6..8523a694a4 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -102,6 +102,9 @@ struct proxy { int options; /* PR_O_REDISP, PR_O_TRANSP, ... */ int mode; /* mode = PR_MODE_TCP, PR_MODE_HTTP or PR_MODE_HEALTH */ struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */ +#ifdef CONFIG_HAP_CTTPROXY + struct sockaddr_in tproxy_addr; /* non-local address we want to bind to for connect() */ +#endif struct proxy *next; struct sockaddr_in logsrv1, logsrv2; /* 2 syslog servers */ signed char logfac1, logfac2; /* log facility for both servers. -1 = disabled */ diff --git a/include/types/server.h b/include/types/server.h index 37b8af15f1..70e1f8fdb6 100644 --- a/include/types/server.h +++ b/include/types/server.h @@ -35,11 +35,16 @@ /* server flags */ -#define SRV_RUNNING 1 /* the server is UP */ -#define SRV_BACKUP 2 /* this server is a backup server */ -#define SRV_MAPPORTS 4 /* this server uses mapped ports */ -#define SRV_BIND_SRC 8 /* this server uses a specific source address */ -#define SRV_CHECKED 16 /* this server needs to be checked */ +#define SRV_RUNNING 0x0001 /* the server is UP */ +#define SRV_BACKUP 0x0002 /* this server is a backup server */ +#define SRV_MAPPORTS 0x0004 /* this server uses mapped ports */ +#define SRV_BIND_SRC 0x0008 /* this server uses a specific source address */ +#define SRV_CHECKED 0x0010 /* this server needs to be checked */ + +#define SRV_TPROXY_ADDR 0x0020 /* bind to this non-local address to reach this server */ +#define SRV_TPROXY_CIP 0x0040 /* bind to the client's IP address to reach this server */ +#define SRV_TPROXY_CLI 0x0060 /* bind to the client's IP+port to reach this server */ +#define SRV_TPROXY_MASK 0x0060 /* bind to a non-local address to reach this server */ /* function which act on servers need to return various errors */ #define SRV_STATUS_OK 0 /* everything is OK. */ @@ -60,6 +65,9 @@ struct server { struct task *queue_mgt; /* the task associated to the queue processing */ struct sockaddr_in addr; /* the address to connect to */ struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */ +#ifdef CONFIG_HAP_CTTPROXY + struct sockaddr_in tproxy_addr; /* non-local address we want to bind to for connect() */ +#endif short check_port; /* the port to use for the health checks */ int health; /* 0->rise-1 = bad; rise->rise+fall-1 = good */ int rise, fall; /* time in iterations */ diff --git a/src/backend.c b/src/backend.c index 2884ea539f..1813d34dee 100644 --- a/src/backend.c +++ b/src/backend.c @@ -35,6 +35,9 @@ #include #include +#ifdef CONFIG_HAP_CTTPROXY +#include +#endif /* * This function recounts the number of usable active and backup servers for @@ -384,6 +387,42 @@ int connect_server(struct session *s) s->proxy->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)); + + itp1.op = TPROXY_ASSIGN; + 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; + break; + case SRV_TPROXY_CLI: + itp1.v.addr.fport = ((struct sockaddr_in *)&s->cli_addr)->sin_port; + /* 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; + 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) { + Alert("Cannot bind to tproxy source address before connect() for server %s/%s. Aborting.\n", + s->proxy->id, s->srv->id); + close(fd); + send_log(s->proxy, LOG_EMERG, + "Cannot bind to tproxy source address before connect() for server %s/%s.\n", + s->proxy->id, s->srv->id); + return SN_ERR_RESOURCE; + } + } +#endif } else if (s->proxy->options & PR_O_BIND_SRC) { setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); @@ -395,6 +434,42 @@ int connect_server(struct session *s) s->proxy->id, s->srv->id); return SN_ERR_RESOURCE; } +#ifdef CONFIG_HAP_CTTPROXY + if (s->proxy->options & PR_O_TPXY_MASK) { + struct in_tproxy itp1, itp2; + memset(&itp1, 0, sizeof(itp1)); + + itp1.op = TPROXY_ASSIGN; + switch (s->proxy->options & PR_O_TPXY_MASK) { + case PR_O_TPXY_ADDR: + itp1.v.addr.faddr = s->srv->tproxy_addr.sin_addr; + itp1.v.addr.fport = s->srv->tproxy_addr.sin_port; + break; + case PR_O_TPXY_CLI: + itp1.v.addr.fport = ((struct sockaddr_in *)&s->cli_addr)->sin_port; + /* 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; + 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) { + Alert("Cannot bind to tproxy source address before connect() for proxy %s. Aborting.\n", + s->proxy->id); + close(fd); + send_log(s->proxy, LOG_EMERG, + "Cannot bind to tproxy source address before connect() for server %s/%s.\n", + s->proxy->id, s->srv->id); + return SN_ERR_RESOURCE; + } + } +#endif } if ((connect(fd, (struct sockaddr *)&s->srv_addr, sizeof(s->srv_addr)) == -1) && diff --git a/src/cfgparse.c b/src/cfgparse.c index 034a2f11bf..9592247612 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -1153,6 +1153,30 @@ int cfg_parse_listen(const char *file, int linenum, char **args) newsrv->state |= SRV_BIND_SRC; newsrv->source_addr = *str2sa(args[cur_arg + 1]); cur_arg += 2; +#ifdef CONFIG_HAP_CTTPROXY + if (!strcmp(args[cur_arg], "usesrc")) { /* address to use outside */ + if (newsrv->source_addr.sin_addr.s_addr == INADDR_ANY) { + Alert("parsing [%s:%d] : '%s' requires an explicit 'source' address.\n", + file, linenum, "usesrc"); + return -1; + } + if (!*args[cur_arg + 1]) { + Alert("parsing [%s:%d] : '%s' expects [:], 'client', or 'clientip' as argument.\n", + file, linenum, "usesrc"); + return -1; + } + if (!strcmp(args[cur_arg + 1], "client")) { + newsrv->state |= SRV_TPROXY_CLI; + } else if (!strcmp(args[cur_arg + 1], "clientip")) { + newsrv->state |= SRV_TPROXY_CIP; + } else { + newsrv->state |= SRV_TPROXY_ADDR; + newsrv->tproxy_addr = *str2sa(args[cur_arg + 1]); + } + global.last_checks |= LSTCHK_CTTPROXY | LSTCHK_NETADM; + cur_arg += 2; + } +#endif } else { Alert("parsing [%s:%d] : server %s only supports options 'backup', 'cookie', 'check', 'inter', 'rise', 'fall', 'port', 'source', 'minconn', 'maxconn' and 'weight'.\n", @@ -1241,6 +1265,30 @@ int cfg_parse_listen(const char *file, int linenum, char **args) curproxy->source_addr = *str2sa(args[1]); curproxy->options |= PR_O_BIND_SRC; +#ifdef CONFIG_HAP_CTTPROXY + if (!strcmp(args[2], "usesrc")) { /* address to use outside */ + 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; + } + if (!*args[3]) { + Alert("parsing [%s:%d] : '%s' expects [:], 'client', or 'clientip' as argument.\n", + file, linenum, "usesrc"); + return -1; + } + + if (!strcmp(args[3], "client")) { + curproxy->options |= PR_O_TPXY_CLI; + } else if (!strcmp(args[3], "clientip")) { + curproxy->options |= PR_O_TPXY_CIP; + } else { + curproxy->options |= PR_O_TPXY_ADDR; + curproxy->tproxy_addr = *str2sa(args[3]); + } + global.last_checks |= LSTCHK_CTTPROXY | LSTCHK_NETADM; + } +#endif } else if (!strcmp(args[0], "cliexp") || !strcmp(args[0], "reqrep")) { /* replace request header from a regex */ regex_t *preg;