crossroads

Git mirror of https://crossroads.e-tunity.com/
git clone git://git.finwo.net/app/crossroads
Log | Files | Refs | LICENSE

commit f76cdc37c38a3fa6ff316b44507bbdaa12cd0fd1
parent 02d94599519b5c28a3fedc2bbb7a81b28eb6fccf
Author: finwo <finwo@pm.me>
Date:   Sat,  3 Jan 2026 19:35:43 +0100

2.10

Diffstat:
MChangeLog | 14++++++++++++++
MMakefile | 2+-
Mdoc/xr.odt | 0
Mdoc/xr.pdf | 0
Atest/sampleconf.xml | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Mxr/DispatchAlgorithms/algorithm/algorithm | 4+++-
Mxr/DispatchAlgorithms/external/external | 3++-
Mxr/DispatchAlgorithms/external/target.cc | 10++++++----
Mxr/DispatchAlgorithms/firstactive/firstactive | 3++-
Mxr/DispatchAlgorithms/firstactive/target.cc | 11++++++-----
Mxr/DispatchAlgorithms/hashedip/hashedip | 3++-
Mxr/DispatchAlgorithms/hashedip/target.cc | 11++++++-----
Mxr/DispatchAlgorithms/leastconn/leastconn | 3++-
Mxr/DispatchAlgorithms/leastconn/target.cc | 33++++++++++++++++++++++++---------
Mxr/DispatchAlgorithms/roundrobin/roundrobin | 3++-
Mxr/DispatchAlgorithms/roundrobin/target.cc | 60++++++++++++++++++++++++++++++++++++------------------------
Mxr/DispatchAlgorithms/storedip/storedip | 3++-
Mxr/DispatchAlgorithms/storedip/target.cc | 11++++++-----
Mxr/backend/backend | 6++++--
Mxr/backend/connect.cc | 18+++++++++++++-----
Mxr/backenddef/backenddef | 23++++++++++++++++++-----
Mxr/backenddef/backenddef1.cc | 17+++++++++++++++--
Axr/backenddef/hostmatch.cc | 11+++++++++++
Axr/backenddef/weight.cc | 16++++++++++++++++
Axr/backendvector/backendvector | 17+++++++++++++++++
Mxr/config/config | 2++
Mxr/config/config1.cc | 1+
Mxr/config/parsecmdline.cc | 1+
Mxr/config/setbackend.cc | 9++++++---
Mxr/etc/usage.txt | 5+++--
Mxr/httpdispatcher/dispatch.cc | 80++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mxr/httpdispatcher/handle.cc | 2++
Mxr/httpdispatcher/httpdispatcher | 1+
Axr/httpdispatcher/senderrorpage.cc | 18++++++++++++++++++
Mxr/sys/sys | 1+
Mxr/tcpdispatcher/dispatch.cc | 23++++++++++++++++-------
Mxr/tcpdispatcher/tcpdispatcher | 23++++++++++++++---------
Mxr/tcpdispatcher/tcpdispatcher1.cc | 2+-
Mxrctl/xrctl | 27++++++++++++++++++++++++++-
39 files changed, 410 insertions(+), 120 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,17 @@ + +2.10 [KK 2008-09-02] +- Bugfix in host match mode. When a back end doesn't match anything, + then the client isn't served. (Previously the dispatcher would fall + back to least-connections over all known back ends.) +- In HTTP mode, a 502 error gets returned to the client when + dispatching fails or when back end processing goes haywire. There is + just one error page, 502 error header, 0 bytes content length. +- Implemented back end weights for least-connections dispatching. +- Bugfix in Backend::connect(): Socket gets closed when connecting fails. + +2.09 [KK 2008-09-01] +--host-match code implemented + 2.08 [KK 2008-08-31] I'd forgotten to include the 'P' into the set of allowed flags (--prefix-timestamp would work, -P not). Fixed. diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ # Top-level Makefile for XR # ------------------------- -VER = 2.08 +VER = 2.10 BINDIR = /usr/sbin TAR = /tmp/crossroads-$(VER).tar.gz AUTHOR = Karel Kubat <karel@kubat.nl> diff --git a/doc/xr.odt b/doc/xr.odt Binary files differ. diff --git a/doc/xr.pdf b/doc/xr.pdf Binary files differ. diff --git a/test/sampleconf.xml b/test/sampleconf.xml @@ -0,0 +1,53 @@ +<!-- Sample XR Configuration. + Just doodling around here for an XML format for the config. + No relevance (yet). --> + +<configuration> + + <!-- Global settings, applicable to all services --> + + <piddir>/var/run</piddir> + <uselogger>true</uselogger> + <logdir>/var/log</logdir> + <maxlogsize>100000</maxlogsize> + <path> + <dir>/bin</dir> + <dir>/sbin</dir> + <dir>/usr/bin</dir> + <dir>/usr/sbin</dir> + <dir>/usr/local/bin</dir> + <dir>/usr/local/sbin</dir> + <dir>/opt/local/bin</dir> + <dir>/opt/local/sbin</dir> + </path> + + <!-- Service descriptors --> + + <service name="web"> + <!-- Multi-host balancing. "www.onesite.org" or anything matching + "onesite" goes to the 10.1.1 back ends. Anything matching + "othersite" goes to the 10.1.9 back ends. --> + <server>http:0:81</server> + <dispatchmode>least-connections</dispatchmode> + <backends hostmatch="onesite"> + <backend> + <address>10.1.1.1:80</address> + <weight>5</weight> + </backend> + <backend> + <address>10.1.1.2:80</address> + <maxconnections>10</maxconnections> + </backend> + </backends> + <backends hostmatch="othersite"> + <backend> + <address>10.1.9.1:80</address> + </backend> + <backend> + <address>10.1.9.2:80</address> + </backend> + </backends> + <verbose>true</verbose> + </service> + +</configuration> diff --git a/xr/DispatchAlgorithms/algorithm/algorithm b/xr/DispatchAlgorithms/algorithm/algorithm @@ -2,11 +2,13 @@ #define _ALGORITHM_ #include "ThreadsAndMutexes/thread/thread" +#include "backendvector/backendvector" class Algorithm: public Thread { public: virtual ~Algorithm(); - virtual int target(struct in_addr clientip) = 0; + virtual unsigned target(struct in_addr clientip, + BackendVector const &targetlist) = 0; }; #endif diff --git a/xr/DispatchAlgorithms/external/external b/xr/DispatchAlgorithms/external/external @@ -9,7 +9,8 @@ class External: public Algorithm { public: - int target(struct in_addr clientip); + unsigned target(struct in_addr clientip, + BackendVector const &targetlist); }; #endif diff --git a/xr/DispatchAlgorithms/external/target.cc b/xr/DispatchAlgorithms/external/target.cc @@ -1,6 +1,8 @@ #include "external" -int External::target(struct in_addr clientip) { +unsigned External::target(struct in_addr clientip, + BackendVector const &targetlist) { + // Prepare command to run ostringstream o; o << config.externalalgorithm() << ' ' << balancer.nbackends(); @@ -14,8 +16,8 @@ int External::target(struct in_addr clientip) { if (! (f = popen (o.str().c_str(), "r")) ) throw static_cast<Error>("Cannot start '") + o.str() + "': " + strerror(errno); - int i; - if (fscanf (f, "%d", &i) < 1) + unsigned i; + if (fscanf (f, "%u", &i) < 1) throw static_cast<Error>("External algorithm '") + o.str() + "' did not reply with a number"; @@ -23,7 +25,7 @@ int External::target(struct in_addr clientip) { n << i; msg ("External algorithm says: " + n.str() + "\n"); - if (i < 0 || i >= (int)balancer.nbackends()) + if (i >= balancer.nbackends()) throw static_cast<Error>("External algorithm '") + o.str() + "': answer " + n.str() + " out of bounds"; if (pclose (f)) diff --git a/xr/DispatchAlgorithms/firstactive/firstactive b/xr/DispatchAlgorithms/firstactive/firstactive @@ -8,7 +8,8 @@ class Firstactive: public Algorithm { public: - int target(struct in_addr clientip); + unsigned target(struct in_addr clientip, + BackendVector const &targetlist); }; #endif diff --git a/xr/DispatchAlgorithms/firstactive/target.cc b/xr/DispatchAlgorithms/firstactive/target.cc @@ -1,9 +1,10 @@ #include "firstactive" -int Firstactive::target(struct in_addr clientip) { - for (unsigned i = 0; i < balancer.nbackends(); i++) - if (balancer.backend(i).available()) - return ( (int) i); - throw static_cast<Error>("First-active algorithm: no available back ends"); +unsigned Firstactive::target(struct in_addr clientip, + BackendVector const &targetlist) { + if (targetlist.size() == 0) + throw static_cast<Error>("First-active algorithm: " + "no available back ends"); + return (targetlist[0]); } diff --git a/xr/DispatchAlgorithms/hashedip/hashedip b/xr/DispatchAlgorithms/hashedip/hashedip @@ -10,7 +10,8 @@ class HashedIp: public Algorithm { public: - int target(struct in_addr clientip); + unsigned target(struct in_addr clientip, + BackendVector const &targetlist); }; #endif diff --git a/xr/DispatchAlgorithms/hashedip/target.cc b/xr/DispatchAlgorithms/hashedip/target.cc @@ -1,15 +1,16 @@ #include "hashedip" -int HashedIp::target(struct in_addr clientip) { +unsigned HashedIp::target(struct in_addr clientip, + BackendVector const &targetlist) { // Hash the client's IP into an index unsigned h = 0; for (char *cp = (char*)&clientip; unsigned(cp - (char*)&clientip) < sizeof(struct in_addr); cp++) { h += *cp; - h %= balancer.nbackends(); - } - int index = int(h); + h %= targetlist.size(); + } + unsigned index = targetlist[h]; if (config.verbose()) { ostringstream o; @@ -31,7 +32,7 @@ int HashedIp::target(struct in_addr clientip) { balancer.backend(index).description() + " unavailable, " "falling back to least-connections\n"); Leastconn l; - index = l.target(clientip); + index = l.target(clientip, targetlist); } } diff --git a/xr/DispatchAlgorithms/leastconn/leastconn b/xr/DispatchAlgorithms/leastconn/leastconn @@ -8,7 +8,8 @@ class Leastconn: public Algorithm { public: - int target(struct in_addr clientip); + unsigned target(struct in_addr clientip, + BackendVector const &targetlist); }; #endif diff --git a/xr/DispatchAlgorithms/leastconn/target.cc b/xr/DispatchAlgorithms/leastconn/target.cc @@ -1,21 +1,36 @@ #include "leastconn" -int Leastconn::target(struct in_addr clientip) { +unsigned Leastconn::target(struct in_addr clientip, + BackendVector const &targetlist) { bool found = false; - unsigned nconn; - int t; + unsigned nconn, t; - for (unsigned i = 0; i < balancer.nbackends(); i++) { - if (!balancer.backend(i).available()) + for (unsigned i = 0; i < targetlist.size(); i++) { + if (! balancer.backend(targetlist[i]).available()) continue; - if (!found || - balancer.backend(i).connections() < nconn) { - nconn = balancer.backend(i).connections(); - t = i; + unsigned weighted_conn = + balancer.backend(targetlist[i]).connections() * + balancer.backend(targetlist[i]).adjustedweight(); + if (config.verbose()) { + ostringstream o; + o << "connections " + << balancer.backend(targetlist[i]).connections() + << ", adjusted weight " + << balancer.backend(targetlist[i]).adjustedweight() + << ", weighted connections " + << weighted_conn; + msg ("Back end " + balancer.backend(targetlist[i]).description() + + ": " + o.str() + "\n"); + } + + if (!found || weighted_conn < nconn) { + t = targetlist[i]; + nconn = balancer.backend(t).connections(); found = true; } } + if (!found) throw static_cast<Error> ("Least-connections algorithm: no available back ends"); diff --git a/xr/DispatchAlgorithms/roundrobin/roundrobin b/xr/DispatchAlgorithms/roundrobin/roundrobin @@ -8,7 +8,8 @@ class Roundrobin: public Algorithm { public: - int target(struct in_addr clientip); + unsigned target(struct in_addr clientip, + BackendVector const &targetlist); }; #endif diff --git a/xr/DispatchAlgorithms/roundrobin/target.cc b/xr/DispatchAlgorithms/roundrobin/target.cc @@ -1,31 +1,43 @@ #include "roundrobin" -int Roundrobin::target(struct in_addr clientip) { - static int last = -1; - int t = last + 1; - - if (last == -1) { - // Initiaizer code - lock(&last); - last = 0; - unlock(&last); - } +unsigned Roundrobin::target(struct in_addr clientip, + BackendVector const &targetlist) { + // No back ends? Don't even try. + if (targetlist.size() == 0) + throw static_cast<Error>("Round robin dispatcher: no backends\n"); + + static int prev_run_index = -1; - while (1) { - if (balancer.backend(t).available()) { - lock(&last); - last = t; - unlock(&last); - return (t); + unsigned first_try_val; + bool first_try_set = false; + while (true) { + // See where we will start + unsigned cur_val; + + if (prev_run_index == -1) { + cur_val = 0; + } else { + if (! first_try_set) { + first_try_set = true; + first_try_val = prev_run_index; + } + cur_val = prev_run_index + 1; + cur_val %= targetlist.size(); + if (cur_val == first_try_val) + throw static_cast<Error>("Round robin dispatcher: " + "no backends\n"); } - t++; - t %= balancer.nbackends(); - lock (&last); - bool came_around = t == last; - unlock (&last); - if (came_around) - throw static_cast<Error> - ("Round-robin algorithm: no available back ends"); + + // Store for next time 'round + lock (&prev_run_index); + prev_run_index = cur_val; + unlock (&prev_run_index); + + if (! balancer.backend(targetlist[cur_val]).available()) + continue; + + // Done. + return (targetlist[cur_val]); } } diff --git a/xr/DispatchAlgorithms/storedip/storedip b/xr/DispatchAlgorithms/storedip/storedip @@ -10,7 +10,8 @@ class StoredIp: public Algorithm { public: - int target(struct in_addr clientip); + unsigned target(struct in_addr clientip, + BackendVector const &targetlist); }; #endif diff --git a/xr/DispatchAlgorithms/storedip/target.cc b/xr/DispatchAlgorithms/storedip/target.cc @@ -15,8 +15,9 @@ struct ClientDataCmp { static map<struct in_addr, ClientData, ClientDataCmp> store; -int StoredIp::target(struct in_addr clientip) { - int target; +unsigned StoredIp::target(struct in_addr clientip, + BackendVector const &targetlist) { + unsigned target; time_t now = time(0); // Is the client already known in the map, and not timed out? @@ -46,20 +47,20 @@ int StoredIp::target(struct in_addr clientip) { balancer.backend(target).description() + " unavailable, falling back to least-connections\n"); Leastconn l; - target = l.target(clientip); + target = l.target(clientip, targetlist); } } } else { // Not recent anymore msg ("Visit too long ago, re-dispatching with least-connections\n"); Leastconn l; - target = l.target(clientip); + target = l.target(clientip, targetlist); } } else { // Historical target unknown, fetch new one msg ("New visit from " + (string)inet_ntoa(clientip) + "\n"); Leastconn l; - target = l.target(clientip); + target = l.target(clientip, targetlist); } // Update the info. diff --git a/xr/backend/backend b/xr/backend/backend @@ -24,10 +24,12 @@ public: bool live() const { return (islive); }; int sock() const { return (clsocket); } - string server() const { return (bdef.server()); } + string const &server() const { return (bdef.server()); } int port() const { return (bdef.port()); } unsigned maxconn() const { return (bdef.maxconn()); } - string hostmatch() const { return (bdef.hostmatch()); } + string const &hostmatch() const { return (bdef.hostmatch()); } + regex_t const &hostregex() const { return (bdef.hostregex()); } + unsigned adjustedweight() const { return (bdef.adjustedweight()); } unsigned connections() const { return (nconn); } double bytesserved() const { return (bytes_served); } unsigned clientsserved() const { return (totconn); } diff --git a/xr/backend/connect.cc b/xr/backend/connect.cc @@ -11,9 +11,11 @@ bool Backend::connect() { // Resolve hostname, prepare binding struct hostent *hostaddr; - if (! (hostaddr = gethostbyname(bdef.server().c_str())) ) + if (! (hostaddr = gethostbyname(bdef.server().c_str())) ) { + socketclose (clsocket); throw static_cast<Error>("Failed to resolve backend host '") + bdef.server(); + } struct sockaddr_in backendaddr; backendaddr.sin_family = AF_INET; backendaddr.sin_port = htons(bdef.port()); @@ -23,11 +25,15 @@ bool Backend::connect() { // Client socket goes into nonblocking mode, so we can connect // and enforce a timeout later. int flags; - if ( (flags = fcntl (clsocket, F_GETFL, 0)) == -1 ) + if ( (flags = fcntl (clsocket, F_GETFL, 0)) == -1 ) { + socketclose (clsocket); throw static_cast<Error>("Failed to get fd flags: ") + strerror(errno); - if (fcntl (clsocket, F_SETFL, flags | O_NONBLOCK) == -1) + } + if (fcntl (clsocket, F_SETFL, flags | O_NONBLOCK) == -1) { throw static_cast<Error>("Failed to fd in nonblocking mode: ") + strerror(errno); + socketclose (clsocket); + } // Do the connect int conres = ::connect (clsocket, (struct sockaddr *)&backendaddr, @@ -35,10 +41,12 @@ bool Backend::connect() { int conerrno = errno; // Put socket again in blocking mode. - if (fcntl (clsocket, F_SETFL, flags) == -1) + if (fcntl (clsocket, F_SETFL, flags) == -1) { + socketclose (clsocket); throw static_cast<Error>("Failed to put fd in blocking mode: ") + strerror(errno); - + } + // Check on the outcome of the connect if (!conres || conerrno == EINPROGRESS) { // Wait for socket to go writable. diff --git a/xr/backenddef/backenddef b/xr/backenddef/backenddef @@ -8,22 +8,35 @@ using namespace std; class BackendDef { public: - BackendDef(): srv(""), prt(-1), max(0), host_match("") {} - BackendDef (string s, string p, string m = ""); +BackendDef(): srv(""), prt(-1), max(0), host_match(""), wt(1) {} + BackendDef (string s, string p, string m = "", string w = "1"); void server(string s) { srv = s; } - string server() const { return (srv); } + string const &server() const { return (srv); } + void port (int p) { prt = p; } int port() const { return (prt); } + unsigned maxconn() const { return (max); } - void hostmatch(string s) { host_match = s; } - string hostmatch() const { return (host_match); } + void maxconn (unsigned m) { max = m; } + + unsigned weight() const { return wt; } + void weight (unsigned w); + unsigned adjustedweight() const { return min_wt + max_wt - wt; } + + void hostmatch(string s); + string const &hostmatch() const { return (host_match); } + regex_t const &hostregex() const { return (host_regex); } private: string srv; int prt; unsigned max; string host_match; + regex_t host_regex; + unsigned wt; + static unsigned min_wt, max_wt; + static bool minmax_wt_set; }; #endif diff --git a/xr/backenddef/backenddef1.cc b/xr/backenddef/backenddef1.cc @@ -1,7 +1,12 @@ #include "backenddef" -BackendDef::BackendDef (string server, string port, string maxclients) : - srv(server), prt(-1), max(0), host_match("") { +unsigned BackendDef::min_wt; +unsigned BackendDef::max_wt; +bool BackendDef::minmax_wt_set = false; + +BackendDef::BackendDef (string server, string port, + string maxclients, string w) : + srv(server), prt(-1), max(0), host_match(""), wt(1) { if (sscanf (port.c_str(), "%d", &prt) < 1) throw static_cast<Error>("Bad backend port specifier: '") + port + @@ -10,4 +15,12 @@ BackendDef::BackendDef (string server, string port, string maxclients) : sscanf (maxclients.c_str(), "%u", &max) < 1) throw static_cast<Error>("Bad maximum connections specifier: '") + maxclients + "' is not a number"; + + unsigned ww; + if (sscanf(w.c_str(), "%u", &ww) < 1) + throw static_cast<Error>("Bad backend weight specifier: '") + w + + "' is not a number"; + if (ww < 1) + throw static_cast<Error>("Weights less than 1 are not supported"); + weight(ww); } diff --git a/xr/backenddef/hostmatch.cc b/xr/backenddef/hostmatch.cc @@ -0,0 +1,11 @@ +#include "backenddef" + +void BackendDef::hostmatch (string s) { + host_match = s; + if (host_match != "") { + if (regcomp (&host_regex, s.c_str(), + REG_EXTENDED | REG_ICASE | REG_NOSUB)) + throw static_cast<Error>("Host match specifier '") + + s + "' isn't a valid regular expression"; + } +} diff --git a/xr/backenddef/weight.cc b/xr/backenddef/weight.cc @@ -0,0 +1,16 @@ +#include "backenddef" + +void BackendDef::weight(unsigned w) { + wt = w; + + if (!minmax_wt_set) { + min_wt = w; + max_wt = w; + minmax_wt_set = true; + } else { + if (min_wt < w) + min_wt = w; + if (max_wt > w) + max_wt = w; + } +} diff --git a/xr/backendvector/backendvector b/xr/backendvector/backendvector @@ -0,0 +1,17 @@ +#ifndef _BACKENDVECTOR_ +#define _BACKENDVECTOR_ + +class BackendVector { +public: + BackendVector(): vec(), is_set(false) {}; + bool isset() const { return is_set; } + void isset(bool i) { is_set = i; } + void add (unsigned nr) { vec.push_back(nr); } + unsigned size() const { return vec.size(); } + unsigned operator[] (unsigned index) const { return vec[index]; } +private: + vector<unsigned> vec; + bool is_set; +}; + +#endif diff --git a/xr/config/config b/xr/config/config @@ -48,6 +48,7 @@ public: void fastclose (bool f) { fast_close = f; } int ipstoretimeout() const { return (ipstore_timeout); } void ipstoretimeout(int t) { ipstore_timeout = t; } + bool hostmatchused() const { return (hostmatch_used); } struct in_addr allow(unsigned n) const { return (allowlist[n]); @@ -96,6 +97,7 @@ private: static vector<struct in_addr> denylist; static bool fast_close; static int ipstore_timeout; + static bool hostmatch_used; }; extern Config config; diff --git a/xr/config/config1.cc b/xr/config/config1.cc @@ -25,6 +25,7 @@ vector<struct in_addr> Config::allowlist; vector<struct in_addr> Config::denylist; bool Config::fast_close = false; int Config::ipstore_timeout; +bool Config::hostmatch_used = false; Config::Config () { } diff --git a/xr/config/parsecmdline.cc b/xr/config/parsecmdline.cc @@ -98,6 +98,7 @@ void Config::parsecmdline (int ac, char **av) { break; case 'M': current_hostmatch = optarg; + hostmatch_used = true; break; case 'm': max_conn = (unsigned)setinteger (optarg); diff --git a/xr/config/setbackend.cc b/xr/config/setbackend.cc @@ -11,15 +11,18 @@ void Config::setbackend (string str, string host) { } if (str.length() > 0) parts.push_back (str); - if (parts.size() < 2 || parts.size() > 3) + if (parts.size() < 2 || parts.size() > 4) throw static_cast<Error>("Bad back end specifier in '-b") + str + - "', expected: SERVER:PORT or SERVER:PORT:MAXCONNECTIONS"; + "', expected: SERVER:PORT or SERVER:PORT:MAXCONNECTIONS or " + "SERVER:PORT:MAXCONNECTIONS:WEIGHT"; BackendDef *bdp; if (parts.size() == 2) bdp = new BackendDef(parts[0], parts[1]); - else + else if (parts.size() == 3) bdp = new BackendDef(parts[0], parts[1], parts[2]); + else if (parts.size() == 4) + bdp = new BackendDef(parts[0], parts[1], parts[2], parts[3]); bdp->hostmatch(host); blist.push_back (*bdp); delete bdp; diff --git a/xr/etc/usage.txt b/xr/etc/usage.txt @@ -8,10 +8,11 @@ may not exist on your platform): would allow the class B network 192.168.*.* -A MASK, --deny-from MASK Deny clients that match MASK. - -b ADDRESS:PORT[:MAX], --backend ADDRESS:PORT[:MAX] + -b ADDRESS:PORT[:MAX[:WEIGHT]], --backend ADDRESS:PORT[:MAX[:WEIGHT]] Specifies a back end, use multiple -b... to specify several back ends. At least one -b... must be given. Specifier MAX is optional: when given, - defines the maximum connections for the back end. + defines the maximum connections for the back end. WEIGHT is optional: + when given, specifies the weight (bigger means better server, default 1). -b SIZE, --buffer-size SIZE Sets the network buffer size, default is 2048 (in bytes) -C, --close-sockets-fast diff --git a/xr/httpdispatcher/dispatch.cc b/xr/httpdispatcher/dispatch.cc @@ -2,35 +2,69 @@ void HttpDispatcher::dispatch() { unsigned stickytarget; + string host_header = ""; - // Get the client's request. May need for cookie inspection. - if (!getclientrequest()) - throw static_cast<Error>("Didn't receive a valid client request.\n"); + // Try to dispatch. Since we're in HTTP mode, we must return an + // error page when dispatching fails. - // Dispatch as a normal backend if sticky HTTP is off, or if the - // sticky target is badly specified. - if (!config.stickyhttp() || - sscanf (clientrequest.cookievalue ("XRTarget").c_str(), - "%d", &stickytarget) < 1 || - stickytarget >= balancer.nbackends()) { - issticky(false); - TcpDispatcher::dispatch(); - } else { - // Got a sticky target. Try to connect. If that fails, fallback - // to non-sticky dispatching. - targetbackend(stickytarget); - Backend tb = balancer.backend(stickytarget); - msg ("Sticky HTTP request for " + tb.description() + "\n"); - if (! tb.connect()) { - balancer.backend(stickytarget).live(false); - msg ("Failed to connect to back end " + tb.description() + - ", trying to dispatch to other\n"); + try { + + // Get the client's request. May need for cookie inspection or for the + // host header. + if (!getclientrequest()) + throw static_cast<Error>("Didn't receive a valid " + "client request.\n"); + if (config.hostmatchused()) { + host_header = clientrequest.headerval ("Host"); + msg ("Will try to dispatch request host '" + host_header + "'\n"); + + // We need to build tcpdispatcher's target list now! + // Construct locally and poke into TcpDispatcher. + msg ("Creating host-based target list for the HTTP dispatcher\n"); + BackendVector v; + v.isset(true); + for (unsigned i = 0; i < balancer.nbackends(); i++) { + if ( (balancer.backend(i).available()) && + (!regexec (&(balancer.backend(i).hostregex()), + host_header.c_str(), 0, 0, 0)) ) { + v.add(i); + msg (" Candidate target: " + + balancer.backend(i).description() + "\n"); + } + } + targetlist(v); + } + + // Dispatch as a normal backend if sticky HTTP is off, or if the + // sticky target is badly specified. + if (!config.stickyhttp() || + sscanf (clientrequest.cookievalue ("XRTarget").c_str(), + "%d", &stickytarget) < 1 || + stickytarget >= balancer.nbackends()) { issticky(false); TcpDispatcher::dispatch(); } else { - backendfd(tb.sock()); - issticky(true); + // Got a sticky target. Try to connect. If that fails, fallback + // to non-sticky dispatching. + targetbackend(stickytarget); + Backend tb = balancer.backend(stickytarget); + msg ("Sticky HTTP request for " + tb.description() + "\n"); + if (! tb.connect()) { + balancer.backend(stickytarget).live(false); + msg ("Failed to connect to back end " + tb.description() + + ", trying to dispatch to other\n"); + issticky(false); + TcpDispatcher::dispatch(); + } else { + backendfd(tb.sock()); + issticky(true); + } } + + } catch (Error const &e) { + senderrorpage(); + throw e; } + } diff --git a/xr/httpdispatcher/handle.cc b/xr/httpdispatcher/handle.cc @@ -20,12 +20,14 @@ void HttpDispatcher::handle() { // Send to server if (!sendclientrequest() ) { msg ("Failed to send client request to back end, not processing\n"); + senderrorpage(); return; } // Get server's response if (!getserverresponse()) { msg ("Failed to receive a valid back end response, not processing\n"); + senderrorpage(); return; } diff --git a/xr/httpdispatcher/httpdispatcher b/xr/httpdispatcher/httpdispatcher @@ -20,6 +20,7 @@ private: bool sendclientrequest(); bool getserverresponse(); bool sendserverresponse(); + void senderrorpage(); Httpbuffer clientrequest, serverresponse; bool is_sticky; diff --git a/xr/httpdispatcher/senderrorpage.cc b/xr/httpdispatcher/senderrorpage.cc @@ -0,0 +1,18 @@ +#include "httpdispatcher" + +#define ERRORSTR \ + "HTTP/1.0 502 Internal Server Error\r\n" \ + "Content-Length: 0\r\n" \ + "\r\n" + +void HttpDispatcher::senderrorpage() { + msg ("Sending error page to client.\n"); + try { + Fdset set (config.client_timeout()); + set.add (clientfd()); + if (set.writeable() == clientfd()) + writechunk (clientfd(), ERRORSTR, sizeof(ERRORSTR)); + } catch (Error const &e) { + } +} + diff --git a/xr/sys/sys b/xr/sys/sys @@ -12,6 +12,7 @@ #include <getopt.h> #endif #include <netdb.h> +#include <regex.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> diff --git a/xr/tcpdispatcher/dispatch.cc b/xr/tcpdispatcher/dispatch.cc @@ -1,12 +1,24 @@ #include "tcpdispatcher" void TcpDispatcher::dispatch() { - Backend tb; bool connected = false; + // Build up the target list, if not yet done so. The HTTP dispatcher + // might've created it already for host-based matching (in which case + // we won't bother here). + if (! target_list.isset()) { + msg ("Creating target list for the TCP dispatcher\n"); + for (unsigned i = 0; i < balancer.nbackends(); i++) + if (balancer.backend(i).available()) { + target_list.add(i); + msg (" Candidate target: " + + balancer.backend(i).description() + "\n"); + } + } + while (!connected) { - target_backend = algorithm->target(clientip()); - tb = balancer.backend(target_backend); + target_backend = algorithm->target(clientip(), target_list); + Backend tb = balancer.backend(target_backend); if (!tb.connect()) { balancer.backend(target_backend).live(false); msg ("Failed to connect to back end " + tb.description() + @@ -15,10 +27,7 @@ void TcpDispatcher::dispatch() { connected = true; backendfd(tb.sock()); - ostringstream o; - o << target_backend; - msg ("Dispatching client to back end " + o.str() + " " + - tb.description() + "\n"); + msg ("Dispatching client to back end " + tb.description() + "\n"); break; } diff --git a/xr/tcpdispatcher/tcpdispatcher b/xr/tcpdispatcher/tcpdispatcher @@ -5,6 +5,7 @@ #include "balancer/balancer" #include "config/config" #include "ThreadsAndMutexes/thread/thread" +#include "backendvector/backendvector" // Dispatching algorithm workers #include "DispatchAlgorithms/algorithm/algorithm" @@ -24,17 +25,20 @@ public: virtual void execute(); virtual void dispatch(); + void dispatch (string host); virtual void handle(); - int targetbackend() const { return target_backend; } - void targetbackend(int t) { target_backend = t; } - struct in_addr clientip() const { return client_ip; } - int clientfd() const { return client_fd; } - void clientfd(int c) { client_fd = c; } - int backendfd() const { return backend_fd; } - void backendfd(int b) { backend_fd = b; } - char const *databuf() const { return data_buf; } - unsigned databufsize() const { return data_bufsz; } + int targetbackend() const { return target_backend; } + void targetbackend(int t) { target_backend = t; } + struct in_addr clientip() const { return client_ip; } + int clientfd() const { return client_fd; } + void clientfd(int c) { client_fd = c; } + int backendfd() const { return backend_fd; } + void backendfd(int b) { backend_fd = b; } + char const *databuf() const { return data_buf; } + unsigned databufsize() const { return data_bufsz; } + BackendVector const &targetlist() const { return target_list; } + void targetlist (BackendVector t) { target_list = t; } unsigned readchunk (int src); unsigned writechunk (int dst, char const *b, unsigned blen); @@ -46,6 +50,7 @@ private: char *data_buf; unsigned data_bufsz; Algorithm *algorithm; + BackendVector target_list; }; #endif diff --git a/xr/tcpdispatcher/tcpdispatcher1.cc b/xr/tcpdispatcher/tcpdispatcher1.cc @@ -2,7 +2,7 @@ TcpDispatcher::TcpDispatcher(int cfd, struct in_addr cip): Thread(), client_ip(cip), target_backend(-1), client_fd(cfd), - backend_fd(-1), data_bufsz(0) { + backend_fd(-1), data_bufsz(0), target_list() { // Set up a data buffer for network transfers data_buf = new char[config.buffersize()]; diff --git a/xrctl/xrctl b/xrctl/xrctl @@ -35,6 +35,9 @@ my @bindirs = qw(/bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin # primary key into hash %services is a self-chosen name. xrctl will supply # --prefix-timestamp when logging to a bare file (when logger isn't used). # Also xrctl will supply --pidfile for run control purposes. +# The strings like --server are passed plaintext to the XR invocation. The +# only exception is --host-match - in that case, there's a more complex +# configuration that also states all back ends. # See the examples below to help you model your favorite dispatcher. my %services = ( @@ -74,6 +77,20 @@ my %services = '--close-sockets-fast' => undef, }, + # Multi-hosting two websites. Site "www.onesite.org" has two back ends + # in the 10.1.1 series, "www.othersite.org" has two back ends in the + # 10.1.9 series. Note that the server mode must be http to use this. + 'webfour' => + { '--server' => [ qw(http:0:82) ], + '--host-match' => { 'onesite' => [ qw(10.1.1.1:80 + 10.1.1.2:80) ], + 'othersite' => [ qw(10.1.9.1:80 + 10.1.9.2:80) ], + }, + '--verbose' => undef, + # Other options as --allow-from etc. can also be added here + }, + # An SSH session balancer on port 2222. We set the client time out # to 2 hours. Requests are balanced to server1, server2 and server3, # all to port 22. @@ -364,7 +381,15 @@ sub xrcommand ($) { push (@ret, "--prefix-timestamp") if (! $use_logger or ! findbin('logger')); for my $o (sort (keys (%opts))) { - if (! $opts{$o}) { + if ($o eq '--host-match') { + my %def = %{ $opts{$o} }; + for my $host (sort (keys (%def))) { + push (@ret, '--host-match', $host); + for my $b(@{ $def{$host} }) { + push (@ret, '--backend', $b); + } + } + } elsif (! $opts{$o}) { push (@ret, $o); } else { my @args = @{ $opts{$o} };