diff --git a/haproxy.c b/haproxy.c index 0a935c0a1..113d0b5f2 100644 --- a/haproxy.c +++ b/haproxy.c @@ -332,7 +332,6 @@ int strlcpy2(char *dst, const char *src, int size) { #define PR_O_COOK_PFX 0x00000020 /* rewrite all cookies by prefixing the right serverid */ #define PR_O_COOK_ANY (PR_O_COOK_RW | PR_O_COOK_IND | PR_O_COOK_INS | PR_O_COOK_PFX) #define PR_O_BALANCE_RR 0x00000040 /* balance in round-robin mode */ -#define PR_O_BALANCE (PR_O_BALANCE_RR) #define PR_O_KEEPALIVE 0x00000080 /* follow keep-alive sessions */ #define PR_O_FWDFOR 0x00000100 /* insert x-forwarded-for with client address */ #define PR_O_BIND_SRC 0x00000200 /* bind to a specific source address when connect()ing */ @@ -348,6 +347,8 @@ int strlcpy2(char *dst, const char *src, int size) { #define PR_O_TCP_SRV_KA 0x00080000 /* enable TCP keep-alive on server-side sessions */ #define PR_O_USE_ALL_BK 0x00100000 /* load-balance between backup servers */ #define PR_O_FORCE_CLO 0x00200000 /* enforce the connection close immediately after server response */ +#define PR_O_BALANCE_SH 0x00400000 /* balance on source IP hash */ +#define PR_O_BALANCE (PR_O_BALANCE_RR | PR_O_BALANCE_SH) /* various session flags */ #define SN_DIRECT 0x00000001 /* connection made on the server matching the client cookie */ @@ -1861,6 +1862,69 @@ static inline struct server *get_server_rr(struct proxy *px) { } +/* + * This function tries to find a running server for the proxy following + * the source hash method. Depending on the number of active/backup servers, + * it will either look for active servers, or for backup servers. + * If any server is found, it will be returned. If no valid server is found, + * NULL is returned. + */ +static inline struct server *get_server_sh(struct proxy *px, char *addr, int len) { + struct server *srv; + + if (px->srv_act) { + unsigned int h, l; + + l = h = 0; + if (px->srv_act > 1) { + while ((l + sizeof (int)) <= len) { + h ^= ntohl(*(unsigned int *)(&addr[l])); + l += sizeof (int); + } + h %= px->srv_act; + } + + for (srv = px->srv; srv; srv = srv->next) { + if ((srv->state & (SRV_RUNNING | SRV_BACKUP)) == SRV_RUNNING) { + if (!h) + return srv; + h--; + } + } + /* note that theorically we should not get there */ + } + + if (px->srv_bck) { + unsigned int h, l; + + /* By default, we look for the first backup server if all others are + * DOWN. But in some cases, it may be desirable to load-balance across + * all backup servers. + */ + l = h = 0; + if (px->srv_bck > 1 && px->options & PR_O_USE_ALL_BK) { + while ((l + sizeof (int)) <= len) { + h ^= ntohl(*(unsigned int *)(&addr[l])); + l += sizeof (int); + } + h %= px->srv_bck; + } + + for (srv = px->srv; srv; srv = srv->next) { + if (srv->state & SRV_RUNNING) { + if (!h) + return srv; + h--; + } + } + /* note that theorically we should not get there */ + } + + /* if we get there, it means there are no available servers at all */ + return NULL; +} + + /* * This function initiates a connection to the current server (s->srv) if (s->direct) * is set, or to the dispatch server if (s->direct) is 0. @@ -1884,6 +1948,7 @@ int connect_server(struct session *s) { s->srv_addr = s->srv->addr; } else if (s->proxy->options & PR_O_BALANCE) { + /* Ensure that srv will not be NULL */ if (!s->proxy->srv_act && !s->proxy->srv_bck) return SN_ERR_SRVTO; @@ -1891,7 +1956,23 @@ int connect_server(struct session *s) { struct server *srv; srv = get_server_rr(s->proxy); - /* srv cannot be NULL */ + s->srv_addr = srv->addr; + s->srv = srv; + } + else if (s->proxy->options & PR_O_BALANCE_SH) { + struct server *srv; + int len; + + if (s->cli_addr.ss_family == AF_INET) + len = 4; + else if (s->cli_addr.ss_family == AF_INET6) + len = 16; + else /* unknown IP family */ + return SN_ERR_INTERNAL; + + srv = get_server_sh(s->proxy, + (void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr, + len); s->srv_addr = srv->addr; s->srv = srv; } @@ -6846,8 +6927,11 @@ int cfg_parse_listen(char *file, int linenum, char **args) { if (!strcmp(args[1], "roundrobin")) { curproxy->options |= PR_O_BALANCE_RR; } + else if (!strcmp(args[1], "source")) { + curproxy->options |= PR_O_BALANCE_SH; + } else { - Alert("parsing [%s:%d] : '%s' only supports 'roundrobin' option.\n", file, linenum, args[0]); + Alert("parsing [%s:%d] : '%s' only supports 'roundrobin' and 'source' options.\n", file, linenum, args[0]); return -1; } } diff --git a/tests/active-sh.cfg b/tests/active-sh.cfg new file mode 100644 index 000000000..20aa29315 --- /dev/null +++ b/tests/active-sh.cfg @@ -0,0 +1,29 @@ +# This is a test configuration. +# It must load-balance across active servers. Check local apache logs to +# verify : +# +# tail /var/log/apache/access_log + + +global + maxconn 100 + +listen sample1 + mode http + option httplog + option dontlognull + retries 1 + redispatch + contimeout 1000 + clitimeout 5000 + srvtimeout 5000 + maxconn 40000 + bind :8081 + balance source + server srv1 127.0.0.1:80 cookie s1 check port 80 inter 1000 fall 1 + server srv2 127.0.0.2:80 cookie s2 check port 80 inter 1000 fall 1 + server srv3 127.0.0.3:80 cookie s3 check port 80 inter 1000 fall 1 + #server srv4 127.0.0.4:80 cookie s4 check port 80 inter 1000 fall 1 + option httpclose + errorloc 503 /503 + diff --git a/tests/backup-all-sh.cfg b/tests/backup-all-sh.cfg new file mode 100644 index 000000000..e89e5565d --- /dev/null +++ b/tests/backup-all-sh.cfg @@ -0,0 +1,35 @@ +# This is a test configuration. +# It must load-balance across backup servers depending on the souce hash. +# Check local apache logs to verify : +# +# tail /var/log/apache/access_log + + +global + maxconn 100 + +listen sample1 + mode http + option httplog + option dontlognull + retries 1 + redispatch + contimeout 1000 + clitimeout 5000 + srvtimeout 5000 + maxconn 40000 + bind :8081 + balance source + option allbackups + server srv1 127.0.0.1:80 cookie s1 check port 81 inter 1000 fall 1 + server srv2 127.0.0.2:80 cookie s2 check port 81 inter 1000 fall 1 + server srv3 127.0.0.3:80 cookie s3 check port 81 inter 1000 fall 1 + server srv4 127.0.0.4:80 cookie s4 check port 81 inter 1000 fall 1 + server bck1 127.0.1.1:80 cookie b1 check port 80 inter 1000 fall 1 backup + server bck2 127.0.1.2:80 cookie b2 check port 80 inter 1000 fall 1 backup + server bck3 127.0.1.3:80 cookie b3 check port 80 inter 1000 fall 1 backup + server bck4 127.0.1.4:80 cookie b4 check port 80 inter 1000 fall 1 backup + server bck5 127.0.1.5:80 cookie b4 check port 80 inter 1000 fall 1 backup + option httpclose + errorloc 503 /503 + diff --git a/tests/backup-second-sh.cfg b/tests/backup-second-sh.cfg new file mode 100644 index 000000000..084d2a48d --- /dev/null +++ b/tests/backup-second-sh.cfg @@ -0,0 +1,34 @@ +# This is a test configuration. +# It must use only the first backup server. Check local apache logs to +# verify : +# +# tail /var/log/apache/access_log + + +global + maxconn 100 + +listen sample1 + mode http + option httplog + option dontlognull + retries 1 + redispatch + contimeout 1000 + clitimeout 5000 + srvtimeout 5000 + maxconn 40000 + bind :8081 + balance source + server srv1 127.0.0.1:80 cookie s1 check port 81 inter 1000 fall 1 + server srv2 127.0.0.2:80 cookie s2 check port 81 inter 1000 fall 1 + server srv3 127.0.0.3:80 cookie s3 check port 81 inter 1000 fall 1 + server srv4 127.0.0.4:80 cookie s4 check port 81 inter 1000 fall 1 + server bck1 127.0.1.1:80 cookie b1 check port 81 inter 1000 fall 1 backup + server bck2 127.0.1.2:80 cookie b2 check port 80 inter 1000 fall 1 backup + server bck3 127.0.1.3:80 cookie b3 check port 80 inter 1000 fall 1 backup + server bck4 127.0.1.4:80 cookie b4 check port 80 inter 1000 fall 1 backup + server bck5 127.0.1.5:80 cookie b5 check port 80 inter 1000 fall 1 backup + option httpclose + errorloc 503 /503 +