crossroads

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

commit f0cb14bcd2b19ee5dd0abbc223c9d25581c8b4c1
parent e08f730be61b2d2c337b15ef201d6d327759fce8
Author: finwo <finwo@pm.me>
Date:   Sat,  3 Jan 2026 19:37:49 +0100

2.57

Diffstat:
MChangeLog | 12++++++++++++
MMakefile | 2+-
Mdoc/xr.odt | 0
Mdoc/xr.pdf | 0
Mdoc/xrctl.xml.5 | 6++++++
Atest/multi-ip.xml | 46++++++++++++++++++++++++++++++++++++++++++++++
Mxr/DispatchAlgorithms/leastconn/target.cc | 35++++++++++++++++++++++++++++-------
Mxr/DispatchAlgorithms/storedip/target.cc | 25++++++++++++++++++++++++-
Mxr/balancer/serve.cc | 7+++++++
Mxr/config/config | 7+++++++
Mxr/config/config1.cc | 5+++--
Mxr/config/parsecmdline.cc | 6+++++-
Mxr/etc/usage.txt | 13++++++++-----
Mxr/fdset/wait.cc | 20+++++++++++++++++---
Mxr/ipstore/anticipated.cc | 10++++++++--
Axr/ipstore/clearoldest.cc | 36++++++++++++++++++++++++++++++++++++
Axr/ipstore/dump.cc | 18++++++++++++++++++
Mxr/ipstore/ipstore | 4++++
Mxr/ipstore/target.cc | 26++------------------------
Axr/ipstore/weed.cc | 29+++++++++++++++++++++++++++++
Mxr/webinterface/answerstatus.cc | 1+
Mxrctl/xrctl | 22+++++++++++++++++-----
22 files changed, 279 insertions(+), 51 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,15 @@ +2.57 [KK 2009-09-14] +- Output of "xrctl status" colorized when a service is not running, + thanks Frederik D. for the suggestion and the code! +- Small cleanup of xrctl's usage information. + +2.56 [KK 2009-05-26] +- Bugfix in cleaning of IPStore map +- Implemented flag --remove-reservations (xrctl: tag: removereservations) +- Timeouts adjusted: all read time outs set to 30, client-write set to + 5, backend-write set to 3 +- Implemented tag prefixtimestamp in system block to force timestamping. + 2.55 [KK 2009-05-13] - Implemented connection error counting of back ends. - Select-handling revised: atomic readability and writeability checks, diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ # Top-level Makefile for XR # ------------------------- -VER = 2.55 +VER = 2.57 PREFIX = $(DESTDIR)/usr BINDIR = $(PREFIX)/sbin MANDIR = $(PREFIX)/share/man 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/doc/xrctl.xml.5 b/doc/xrctl.xml.5 @@ -45,6 +45,9 @@ distributed with the sources for a full description. <maxlogsize>100000</maxlogsize> - How many history logs to keep? <loghistory>10</loghistory> --> + <!-- To force log line timestamping, add: + <prefixtimestamp>true</prefixtimestamp> + Or turn it off with value "false". --> </system> <!-- Service descriptions: This section defines all balancing @@ -151,6 +154,9 @@ distributed with the sources for a full description. <denyfrom>192.168.1.100</denyfrom> </acl> + <!-- For a nonstandard buffer size (default is 2k), use: --> + <buffersize>4096</buffersize> + <dosprotection> <!-- Here is some basic DOS protection. Connections from IP's are counted over timeinterval seconds (here: 2 sec). When a diff --git a/test/multi-ip.xml b/test/multi-ip.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + + <system> + <uselogger>true</uselogger> + <logger>clpipe /var/log/xr.clog</logger> + <path>/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/opt/local/bin:/opt/local/sbin</path> + </system> + + <service> + <name>first</name> + <server> + <type>tcp</type> + <address>192.168.2.5:10000</address> + <dispatchmode>first-available</dispatchmode> + <webinterface>192.168.2.5:10001</webinterface> + <verbose>yes</verbose> + <debug>yes</debug> + </server> + <backend> + <address>www.google.nl:80</address> + </backend> + <backend> + <address>www.thedailywtf.com:80</address> + </backend> + </service> + + <service> + <name>second</name> + <server> + <type>tcp</type> + <address>172.16.89.1:10000</address> + <dispatchmode>first-available</dispatchmode> + <webinterface>172.16.89.1:10001</webinterface> + <verbose>yes</verbose> + <debug>yes</debug> + </server> + <backend> + <address>kubat.nl:80</address> + </backend> + <backend> + <address>slashdot.org:80</address> + </backend> + </service> + +</configuration> diff --git a/xr/DispatchAlgorithms/leastconn/target.cc b/xr/DispatchAlgorithms/leastconn/target.cc @@ -5,25 +5,46 @@ unsigned Leastconn::target(struct in_addr clientip, PROFILE("Leastconn::target"); msg (Mstr("Starting least-connections dispatcher\n")); + + if (config.debug()) { + ostringstream o; + o << "Back end target list:"; + for (unsigned i = 0; i < targetlist.size(); i++) + o << ' ' << targetlist[i]; + o << '\n'; + _debugmsg(o.str()); + } bool found = false; unsigned best_weighted = 0, t = 0; for (unsigned i = 0; i < targetlist.size(); i++) { - if (! balancer.backend(targetlist[i]).available()) + if (! balancer.backend(targetlist[i]).available()) { + debugmsg(Mstr("Back end ") + + Mstr(balancer.backend(targetlist[i]).description()) + + " is NOT available\n"); continue; - unsigned this_weight = - (balancer.backend(targetlist[i]).connections() + - IPStore::anticipated(targetlist[i])) * - balancer.backend(targetlist[i]).adjustedweight(); + } + unsigned connections = balancer.backend(targetlist[i]).connections(); + unsigned anticipated = IPStore::anticipated(targetlist[i]); + unsigned adjweight = balancer.backend(targetlist[i]).adjustedweight(); + unsigned this_weight = (connections + anticipated) * adjweight; + ostringstream o; + + if (config.debug()) + o << "Back end " << balancer.backend(targetlist[i]).description() + << ": connections=" << connections << ", anticipated=" << anticipated + << ", adjweight=" << adjweight << ", thisweight=" << this_weight; + if (!found || this_weight < best_weighted) { t = targetlist[i]; best_weighted = this_weight; found = true; - } + debugmsg(Mstr(o.str()) + Mstr(" is best so far\n")); + } else + debugmsg(Mstr(o.str()) + Mstr(" skipped, got a better one\n")); } - if (!found) throw Error("Least-connections algorithm: no available back ends"); return (t); diff --git a/xr/DispatchAlgorithms/storedip/target.cc b/xr/DispatchAlgorithms/storedip/target.cc @@ -29,5 +29,28 @@ unsigned StoredIp::target(struct in_addr clientip, // their preferred back end is down (and we're in lax mode ofc). // Treat as new connection. Leastconn l; - return l.target(clientip, targetlist); + + if (!config.removereservations()) + return l.target(clientip, targetlist); + else { + BackendVector tlist = targetlist; + while (true) { + try { + return l.target(clientip, tlist); + } catch (...) { + // We're out of back ends and need to clear up the IP store to retry. + // We give it a sec, remove the oldest entry, and rebuild the + // target list for the least-connections dispatch algorithm. + warnmsg("Out of back ends, releasing oldest client entry and retrying\n"); + IPStore::clearoldest(); + sleep(1); + BackendVector newlist; + for (unsigned int i = 0; i < balancer.nbackends(); i++) + if (balancer.backend(i).available()) + newlist.add(i); + tlist = newlist; + } + } + } } + diff --git a/xr/balancer/serve.cc b/xr/balancer/serve.cc @@ -3,6 +3,8 @@ #include "Dispatchers/httpdispatcher/httpdispatcher" #include "Dispatchers/udpdispatcher/udpdispatcher" +// #define SHOWDEBUG + void Balancer::serve() { int clsock = -1; @@ -143,6 +145,11 @@ void Balancer::serve() { _debugmsg (Mstr("Allocation boundary at dispatcher start: ") + mem + "\n"); } + #ifdef SHOWDEBUG + void *mem = malloc(16); + free (mem); + cout << "XR allocation at dispatcher start: " << mem << '\n'; + #endif d->start(); } else { diff --git a/xr/config/config b/xr/config/config @@ -158,6 +158,12 @@ public: string dispatchmodestr() const { return (dmode.modestr()); } + bool removereservations() const { + return remove_reservations; + } + void removereservations(bool b) { + remove_reservations = b; + } string const &softmaxconnexcess() const { return soft_maxconn_excess_prog; @@ -227,6 +233,7 @@ private: static string hard_maxconn_excess_prog; static unsigned dns_cache_timeout; static string on_start, on_end, on_fail; + static bool remove_reservations; }; extern Config config; diff --git a/xr/config/config1.cc b/xr/config/config1.cc @@ -7,8 +7,8 @@ string Config::sip = "0"; vector<BackendDef> Config::blist; Dispatchmode Config::dmode; unsigned Config::c_timeout = 30; -unsigned Config::b_timeout = 3; -unsigned Config::c_write_timeout = 30; +unsigned Config::c_write_timeout = 5; +unsigned Config::b_timeout = 30; unsigned Config::b_write_timeout = 3; unsigned Config::wakeup = 5; unsigned Config::checkup = 0; @@ -43,6 +43,7 @@ unsigned Config::dns_cache_timeout = 3600; string Config::on_start = ""; string Config::on_end = ""; string Config::on_fail = ""; +bool Config::remove_reservations = false; Config::Config () { } diff --git a/xr/config/parsecmdline.cc b/xr/config/parsecmdline.cc @@ -15,7 +15,7 @@ void Config::parsecmdline (int ac, char **av) { if (ac == 1) throw Error("Bad command line '" + cmdline + "'\n" + USAGE); -# define OPTSTRING "?a:A:B:b:c:CDd:E:e:fF:g:hH:Il:" \ +# define OPTSTRING "?a:A:B:b:c:CDd:E:e:fF:Gg:hH:Il:" \ "m:M:nPp:Q:r:R:Ss:t:T:u:U:vVW:w:xXy:z:Z:" # ifdef HAVE_GETOPT_LONG static struct option longopts[] = { @@ -31,6 +31,7 @@ void Config::parsecmdline (int ac, char **av) { { "soft-maxconn-excess", required_argument, 0, 'e' }, { "dns-cache-timeout", required_argument, 0, 'F' }, { "foreground", no_argument, 0, 'f' }, + { "remove-reservations", no_argument, 0, 'G' }, { "backend-check", required_argument, 0, 'g' }, { "help", no_argument, 0, 'h' }, { "add-server-header", required_argument, 0, 'H' }, @@ -115,6 +116,9 @@ void Config::parsecmdline (int ac, char **av) { case 'f': foreground_mode = true; break; + case 'G': + removereservations(true); + break; case 'g': current_backendcheck.parse(optarg); break; diff --git a/xr/etc/usage.txt b/xr/etc/usage.txt @@ -76,6 +76,9 @@ may not exist on your platform): The default behavior is a TCP connect, to the back end's IP, at the back end's port. Use "-g connect::" to reset previous flags to the default. + -G, --remove-reservations + In stored-ip algorithms, outstanding reservations for expected + clients are removed when no more back ends are available. -h, -?, --help This text. -H HDR, --add-server-header HDR @@ -123,13 +126,13 @@ may not exist on your platform): is 0, XR will listen to stdin (inetd-mode, not available for udp). Default: tcp:0:10000 (TCP balancing, on all interfaces, via port 10000). -t SEC, --backend-timeout SEC - Defines network timeouts for back ends, default 3 sec. Use 0 to - prevent timing out. SEC is the timeout for reads and writes. - Use -t RSEC:WSEC to specify separate timeouts for reads and writes. + Defines network read timeouts for back ends, default 30 sec. Use 0 to + prevent timing out. Use -t RSEC:WSEC to specify separate timeouts + for reads and writes, default 30:3. -T SEC, --client-timeout SEC - Defines network timeouts for clients, default 30 sec. Use 0 to + Defines network read timeouts for clients, default 30 sec. Use 0 to prevent timing out. Use -T RSEC:WSEC to specify separate - timeouts for reads and writes. + timeouts for reads and writes, default 30:5 -u USEC, --defer-time USEC If a connection is going to be deferred due to hitting the "soft" rate (see --soft-maxconnrate), then this option sets how long the deferral diff --git a/xr/fdset/wait.cc b/xr/fdset/wait.cc @@ -53,13 +53,27 @@ void Fdset::wait(bool wait_read, bool wait_write) { throw Error(o.str()); } - // More debugging + // More debugging: What has become readable, what has become + // writeable, also state if no change was seen if (config.debug()) { + bool statechanged = false; for (unsigned int i = 0; i < FD_SETSIZE; i++) { - if (FD_ISSET(i, &readset)) + if (FD_ISSET(i, &readset)) { _debugmsg(Mstr("Fd ") + Mstr(i) + " is readable\n"); - if (FD_ISSET(i, &writeset)) + statechanged = true; + } + if (FD_ISSET(i, &writeset)) { _debugmsg(Mstr("Fd ") + Mstr(i) + " is writeable\n"); + statechanged = true; + } } + if (!statechanged) { + ostringstream o; + o << "Select timeout: neither of the fd's "; + for (unsigned int i = 0; i < set.size(); i++) + o << set[i] << ' '; + o << "has shown activity in " << tsec << " sec\n"; + _debugmsg(o.str()); + } } } diff --git a/xr/ipstore/anticipated.cc b/xr/ipstore/anticipated.cc @@ -3,7 +3,11 @@ unsigned IPStore::anticipated(unsigned b) { if (!onoff || b >= balancer.nbackends()) return 0; - + + // Weed store for decisions later + weed(); + + // Get number of anticipated clients for given back end unsigned ret = 0; Mutex::lock(&store); @@ -13,7 +17,9 @@ unsigned IPStore::anticipated(unsigned b) { if ((*iter).second.targetbackend == (int)b) ret++; Mutex::unlock(&store); - + + debugmsg(Mstr("Anticipated connections for back end ") + Mstr(b) + ": " + + Mstr(ret) + "\n"); return ret; } diff --git a/xr/ipstore/clearoldest.cc b/xr/ipstore/clearoldest.cc @@ -0,0 +1,36 @@ +#include "ipstore" + +void IPStore::clearoldest() { + time_t oldest_time = time(0) + 100; + StoreMap::iterator oldest_entry; + bool found = false; + + Mutex::lock(&store); + + dump(); + + // Find oldest entry. + for (StoreMap::iterator iter = store.begin(); + iter != store.end(); + iter++) { + if ((*iter).second.lastaccess < oldest_time) { + oldest_time = (*iter).second.lastaccess; + oldest_entry = iter; + found = true; + } + } + + // Kill it if we got it. + if (found) { + if (config.debug()) { + Timestamp tm((*oldest_entry).second.lastaccess); + _debugmsg(Mstr("Erasing oldest IP store entry: ") + + Mstr(inet_ntoa(oldest_entry->first)) + " on " + + tm.desc() + "\n"); + } + store.erase(oldest_entry); + } + + Mutex::unlock(&store); + dump(); +} diff --git a/xr/ipstore/dump.cc b/xr/ipstore/dump.cc @@ -0,0 +1,18 @@ +#include "ipstore" + +void IPStore::dump() { + if (!config.debug()) + return; + + _debugmsg(Mstr("IPStore dump:\n")); + for (StoreMap::iterator iter = store.begin(); + iter != store.end(); + iter++) { + Timestamp tm((*iter).second.lastaccess); + ostringstream o; + o << "Client IP " << inet_ntoa(iter->first) << " on " + << tm.desc() << " to back end " << (*iter).second.targetbackend + << '\n'; + _debugmsg(o.str()); + } +} diff --git a/xr/ipstore/ipstore b/xr/ipstore/ipstore @@ -29,11 +29,15 @@ public: static void activity(struct in_addr clientip, unsigned curbackend); static unsigned anticipated(unsigned bckend); static void clear(struct in_addr clientip); + static void clearoldest(); static void on() { onoff = true; } static void off() { onoff = false; } private: + static void dump(); + static void weed(); + static StoreMap store; static bool onoff; }; diff --git a/xr/ipstore/target.cc b/xr/ipstore/target.cc @@ -4,30 +4,8 @@ IPStore::StoreMap IPStore::store; bool IPStore::onoff = false; int IPStore::target(struct in_addr clientip) { - time_t now = time(0); - - // Weed out store. Done first, because the store should be up to date - // for some decisions below. - bool done = false; - Mutex::lock(&store); - while (!done) { - done = true; - for (StoreMap::iterator iter = store.begin(); - iter != store.end(); - iter++) { - if (now - ((*iter).second.lastaccess) > config.ipstoretimeout()) { - if (config.debug()) { - done = false; - Timestamp tm((*iter).second.lastaccess); - debugmsg (Mstr(inet_ntoa(iter->first)) + - Mstr(" visited on ") + tm.desc() + ", erasing\n"); - } - store.erase(iter); - break; - } - } - } - Mutex::unlock(&store); + // Weed out the store, for decisions later + weed(); // Let's see if we know the client. if (store.count(clientip) > 0) { diff --git a/xr/ipstore/weed.cc b/xr/ipstore/weed.cc @@ -0,0 +1,29 @@ +#include "ipstore" + +void IPStore::weed() { + time_t now = time(0); + bool done = false; + + Mutex::lock(&store); + + while (!done) { + done = true; + for (StoreMap::iterator iter = store.begin(); + iter != store.end(); + iter++) { + if (now - ((*iter).second.lastaccess) > config.ipstoretimeout()) { + if (config.debug()) { + Timestamp tm((*iter).second.lastaccess); + _debugmsg (Mstr("Stale entry: ") + Mstr(inet_ntoa(iter->first)) + + Mstr(" visited on ") + tm.desc() + + ", erasing\n"); + } + done = false; + store.erase(iter); + break; + } + } + } + + Mutex::unlock(&store); +} diff --git a/xr/webinterface/answerstatus.cc b/xr/webinterface/answerstatus.cc @@ -23,6 +23,7 @@ void Webinterface::answer_status() { " <backendreadtimeout>" << config.backend_read_timeout() << "</backendreadtimeout>\n" " <backendwritetimeout>" << config.backend_write_timeout() << "</backendwritetimeout>\n" " <dispatchmode>" << config.dispatchmodestr() << "</dispatchmode>\n" + " <removereservations>" << config.removereservations() << "</removereservations>\n" " <webinterface>" << config.webinterfaceip() << ':' << config.webinterfaceport() << "</webinterface>\n" " <dnscachetimeout>" << config.dnscachetimeout() << "</dnscachetimeout>\n" " <buffersize>" << config.buffersize() << "</buffersize>\n" diff --git a/xrctl/xrctl b/xrctl/xrctl @@ -1,6 +1,7 @@ #!/usr/bin/perl use strict; use Getopt::Std; +use Term::ANSIColor qw(:constants); # Versioning my $VER = "__VER__"; @@ -11,6 +12,7 @@ my $VER = "__VER__"; # Default configuration file to read and default logging facility my $default_conf = '/etc/xrctl.xml'; my $default_logger = 'logger'; +my $default_prefixtimestamp = undef; # Default settings, must match xr's defaults my $default_dispatchmode = 'least-connections'; @@ -57,7 +59,7 @@ my $sysblock = $xp->data('system'); if ($sysblock ne '') { my $sysxp = new XMLParser($xp->data('system')); for my $tag qw(pscmd logger uselogger logdir - maxlogsize loghistory path) { + maxlogsize loghistory path prefixtimestamp) { $sysconf{$tag} = $sysxp->data($tag); msg("System config $tag: $sysconf{$tag}\n") if ($sysconf{$tag} ne ''); } @@ -78,6 +80,15 @@ if ($sysblock ne '') { } } msg ("PS command: $sysconf{pscmd}\n"); + + if ($sysconf{prefixtimestamp}) { + $default_prefixtimestamp = 1 if istrue($sysconf{prefixtimestamp}); + } else { + $default_prefixtimestamp = 1 + if (!istrue($sysconf{uselogger}) or !find_bin('logger')); + } + msg ("Log lines will be prefixed with a timestamp\n") + if ($default_prefixtimestamp); } # Load up the service names. @@ -227,7 +238,7 @@ sub cmd_killstart { sub cmd_status { for my $s (@_) { print ("Service $s: "); - print ("not ") unless (is_running($s)); + print (BOLD, RED, "not ", RESET) unless (is_running($s)); print ("running\n"); } } @@ -325,7 +336,7 @@ Flags are: -v increases verbosity -c CONFIG specifies the configuration, default $default_conf Actions are: - configtest builds invocations from the configuration file and validates them + configtest validates the configuration list shows the xr command line start starts the service(s) if they are not yet running stop gracefully stops the service(s) if they are running @@ -472,7 +483,7 @@ sub xr_cmdarr { my @cmd; push (@cmd, "xr-$service"); push (@cmd, '--prefix-timestamp') - if (!istrue($sysconf{uselogger}) or !find_bin('logger')); + if ($default_prefixtimestamp); # Fetch the <service> block for this service my $sp = xml_serviceparser($service) @@ -490,7 +501,8 @@ sub xr_cmdarr { # Flags that should go on the command line if the bool-tag is true my %boolflags = (closesocketsfast => '--close-sockets-fast', verbose => '--verbose', - debug => '--debug'); + debug => '--debug', + removereservations => '--remove-reservations'); # Handle general flags and boolflags push (@cmd,