crossroads

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

commit 3651f75185e9babc65a5e61ad297e2d34c2ae8d4
parent 0680b47c11e05fd36cd924768d0da75f6b3da487
Author: finwo <finwo@pm.me>
Date:   Sat,  3 Jan 2026 19:35:02 +0100

2.04

Diffstat:
MChangeLog | 6++++++
MMakefile | 2+-
Mdoc/xr.odt | 0
Mdoc/xr.pdf | 0
Mxr/config/config | 3+++
Mxr/config/config1.cc | 1+
Mxr/config/parsecmdline.cc | 12+++++++++++-
Mxr/config/setdispatcmode.cc | 14+++++++++++++-
Mxr/dispatchmode/dispatchmode | 2++
Mxr/etc/usage.txt | 25+++++++++++++++++--------
Axr/storedip/storedip | 16++++++++++++++++
Axr/storedip/target.cc | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mxr/sys/sys | 2+-
Mxr/sys/timestamp.cc | 10++++++++--
Mxr/tcpdispatcher/tcpdispatcher | 3+++
Mxr/tcpdispatcher/tcpdispatcher1.cc | 4++++
Mxr/thread/thread | 2+-
Mxr/thread/thread1.cc | 1+
Mxrctl/xrctl | 24++++++++++++++++++++++--
19 files changed, 194 insertions(+), 17 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,9 @@ +2.04 [KK 2008-08-11] +Mutex in Thread class is now a static. +Implemented dispatching algorithm "stored clent ip", in the variants +strict and lax. +Flag -n / --tryout implemented. Implemented in xrctl. + 2.03 [KK 2008-08-10] Updated docs regarding the mailing list. Fixed verbose display upon accepting a client ("current back end diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ # Top-level Makefile for XR # ------------------------- -VER = 2.03 +VER = 2.04 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/xr/config/config b/xr/config/config @@ -46,6 +46,8 @@ public: unsigned ndeny() const { return (denylist.size()); } bool fastclose() const { return (fast_close); } void fastclose (bool f) { fast_close = f; } + int ipstoretimeout() const { return (ipstore_timeout); } + void ipstoretimeout(int t) { ipstore_timeout = t; } struct in_addr allow(unsigned n) const { return (allowlist[n]); @@ -93,6 +95,7 @@ private: static vector<struct in_addr> allowlist; static vector<struct in_addr> denylist; static bool fast_close; + static int ipstore_timeout; }; extern Config config; diff --git a/xr/config/config1.cc b/xr/config/config1.cc @@ -24,6 +24,7 @@ vector<string> Config::serverheaders; vector<struct in_addr> Config::allowlist; vector<struct in_addr> Config::denylist; bool Config::fast_close = false; +int Config::ipstore_timeout; Config::Config () { } diff --git a/xr/config/parsecmdline.cc b/xr/config/parsecmdline.cc @@ -28,6 +28,7 @@ void Config::parsecmdline (int ac, char **av) { { "help", no_argument, 0, 'h' }, { "add-server-header", required_argument, 0, 'H' }, { "max-connections", required_argument, 0, 'm' }, + { "tryout", no_argument, 0, 'n' }, { "prefix-timestamp", no_argument, 0, 'P' }, { "pidfile", required_argument, 0, 'p' }, { "server", required_argument, 0, 's' }, @@ -44,7 +45,9 @@ void Config::parsecmdline (int ac, char **av) { int opt; bool backend_set = false; - while ( (opt = getopt_long (ac, av, "?a:A:B:b:c:Dd:fhH:m:p:Ss:t:T:vVw:xX", + bool tryout = false; + while ( (opt = getopt_long (ac, av, + "?a:A:B:b:c:Dd:fhH:m:np:Ss:t:T:vVw:xX", longopts, 0)) > 0) { switch (opt) { case 'a': @@ -85,6 +88,9 @@ void Config::parsecmdline (int ac, char **av) { case 'm': max_conn = (unsigned)setinteger (optarg); break; + case 'n': + tryout = true; + break; case 'P': prefix_timestamp = true; break; @@ -135,6 +141,10 @@ void Config::parsecmdline (int ac, char **av) { "No backend defined, use '-b...' at least once, " "or try 'xr -h' for usage"); + // In tryout mode, stop now. + if (tryout) + exit (0); + msg ("+--------------------------------------------+\n"); msg ("| Welcome to xr V" VER " |\n"); msg ("| Copyright (c) Karel Kubat <karel@kubat.nl> |\n"); diff --git a/xr/config/setdispatcmode.cc b/xr/config/setdispatcmode.cc @@ -17,7 +17,19 @@ void Config::setdispatchmode (string s) { dmode.mode (Dispatchmode::m_leastconn); else if (s == "r" || s == "round-robin") dmode.mode (Dispatchmode::m_roundrobin); - else + else if (s.substr(0, 2) == "s:") { + dmode.mode (Dispatchmode::m_strict_stored_ip); + ipstoretimeout(setinteger(s.substr(2))); + } else if (s.substr(0, 17) == "strict-stored-ip:") { + dmode.mode (Dispatchmode::m_strict_stored_ip); + ipstoretimeout(setinteger(s.substr(17))); + } else if (s.substr(0, 2) == "S:") { + dmode.mode (Dispatchmode::m_lax_stored_ip); + ipstoretimeout(setinteger(s.substr(2))); + } else if (s.substr(0, 14) == "lax-stored-ip:") { + dmode.mode (Dispatchmode::m_strict_stored_ip); + ipstoretimeout(setinteger(s.substr(14))); + } else throw ((Error) "Bad dispatch mode -d" + s); if (dmode.mode() == Dispatchmode::m_external && diff --git a/xr/dispatchmode/dispatchmode b/xr/dispatchmode/dispatchmode @@ -15,6 +15,8 @@ public: m_external, m_strict_hashed_ip, m_lax_hashed_ip, + m_strict_stored_ip, + m_lax_stored_ip, }; Dispatchmode() : mymode(m_leastconn) { diff --git a/xr/etc/usage.txt b/xr/etc/usage.txt @@ -21,14 +21,21 @@ Usage: xr [flags], where the flags may be: Sets debugging on, more verbosity on top of -v -d METHOD, --dispatch-mode METHOD Defines how to dispatch over back ends, the method may be: - f, first-active - first live back end gets all traffic - e:EXT, external:EXT - external program EXT is queried - h, strict-hashed-ip - client IP is hashed to determine a back end, - client is denied when back end is down - H, lax-hashed-ip - client IP is hashed, fallback to least - connections when target back end is down - l, least-connections - back end with least TCP connections is taken - r, round-robin - back ends take turns + f, first-available - first live back end gets all traffic + e:EXT, external:EXT - external program EXT is queried + h, strict-hashed-ip - client IP is hashed to determine a back + end, client is denied when back end is down + H, lax-hashed-ip - client IP is hashed, fallback to least- + connections when target back end is down + l, least-connections - back end with least connections is taken + r, round-robin - back ends take turns + s:SEC, strict-stored-ip:SEC - if client connected before within SEC + seconds, then the same backend is used. + Client is denied if that backend is down. + Else a new is found by least-connections. + S:SEC, lax-stored-ip:SEC - same as strict-stored-ip, but falls back + to least-connections when a previously + used back end is down Default method is l (least-connections). When external mode is selected, program EXT is started with arguments <nbackends> <b0> <b0-availability> <b0-connections> (b0 repeated for all back ends). Here <b0> is the back @@ -45,6 +52,8 @@ Usage: xr [flags], where the flags may be: -m MAX, --max-connections MAX Sets the maximum number of connections to the balancer. Default is 0, no maximum. + -n, --tryout + Validates all flags and stops; does not start the balancer. -P, --prefix-timestamp Messages (verbose, debug, error etc.) are prefixed with a time stamp. -p FILE, --pidfile FILE diff --git a/xr/storedip/storedip b/xr/storedip/storedip @@ -0,0 +1,16 @@ +#ifndef _STOREDIP_ +#define _STOREDIP_ + +#include "../sys/sys" +#include "../error/error" +#include "../algorithm/algorithm" +#include "../balancer/balancer" +#include "../config/config" +#include "../leastconn/leastconn" + +class StoredIp: public Algorithm { +public: + int target(struct in_addr clientip); +}; + +#endif diff --git a/xr/storedip/target.cc b/xr/storedip/target.cc @@ -0,0 +1,84 @@ +#include "storedip" + +struct ClientData { + int targetbackend; + time_t lastaccess; +}; + +struct ClientDataCmp { + bool operator() (struct in_addr a, struct in_addr b) const { + long la = *((long*)&a); + long lb = *((long*)&b); + return (la - lb) < 0; + } +}; + +static map<struct in_addr, ClientData, ClientDataCmp> store; + +int StoredIp::target(struct in_addr clientip) { + int target; + time_t now = time(0); + + // Is the client already known in the map, and not timed out? + map<struct in_addr, ClientData, ClientDataCmp>::iterator hit = + store.find(clientip); + if (store.count(clientip) > 0) { + time_t diff = now - store[clientip].lastaccess; + ostringstream o; + o << diff; + msg ("Client IP " + string(inet_ntoa(clientip)) + + " last visited on " + timestamp(store[clientip].lastaccess) + + ", " + o.str() + " sec ago, and went to " + + balancer.backend(store[clientip].targetbackend).description() + + "\n"); + if (diff <= config.ipstoretimeout()) { + // Recent 'nuff + target = store[clientip].targetbackend; + if (! balancer.backend(target).available()) { + // Historical target down - get new one if in lax mode + if (config.dispatchmode() == Dispatchmode::m_strict_stored_ip) + throw ((Error) "Stored-IP algorithm: target back end " + + balancer.backend(target).description() + + "unavailable"); + else { + msg ("Stored IP algorithm: target back end " + + balancer.backend(target).description() + + " unavailable, falling back to least-connections\n"); + Leastconn l; + target = l.target(clientip); + } + } + } else { + // Not recent anymore + msg ("Visit too long ago, re-dispatching with least-connections\n"); + Leastconn l; + target = l.target(clientip); + } + } else { + // Historical target unknown, fetch new one + msg ("New visit from " + (string)inet_ntoa(clientip) + "\n"); + Leastconn l; + target = l.target(clientip); + } + + // Update the info. + ClientData entry = {target, now}; + store[clientip] = entry; + + // Weed out store + map<struct in_addr, ClientData, ClientDataCmp>::iterator + iter = store.begin(); + while (iter != store.end()) { + if (config.debug()) + debugmsg ("Stored-IP: " + + (string)inet_ntoa((*iter).first) + " visited on " + + timestamp((*iter).second.lastaccess) + "\n"); + if (now - ((*iter).second.lastaccess) > config.ipstoretimeout()) + store.erase(iter); + else + iter++; + } + + // Return target to caller + return (target); +} diff --git a/xr/sys/sys b/xr/sys/sys @@ -38,7 +38,7 @@ void msg (string const &s); void debugmsg (string const &s); void reportmsg (string const &s); int serversocket (string addr, int port, string description); -string timestamp(); +string timestamp(time_t s = 0); bool ipmatch (struct in_addr addr, struct in_addr mask); void socketclose (int fd); diff --git a/xr/sys/timestamp.cc b/xr/sys/timestamp.cc @@ -1,8 +1,14 @@ #include "sys" -string timestamp() { +string timestamp(time_t s) { struct timeval tv; - gettimeofday (&tv, 0); + + if (! s) + gettimeofday (&tv, 0); + else { + tv.tv_sec = s; + tv.tv_usec = 0; + } struct tm *tmp = localtime(&tv.tv_sec); diff --git a/xr/tcpdispatcher/tcpdispatcher b/xr/tcpdispatcher/tcpdispatcher @@ -5,12 +5,15 @@ #include "../balancer/balancer" #include "../config/config" #include "../thread/thread" + +// Dispatching algorithm workers #include "../algorithm/algorithm" #include "../roundrobin/roundrobin" #include "../firstactive/firstactive" #include "../leastconn/leastconn" #include "../external/external" #include "../hashedip/hashedip" +#include "../storedip/storedip" class TcpDispatcher: public Thread { public: diff --git a/xr/tcpdispatcher/tcpdispatcher1.cc b/xr/tcpdispatcher/tcpdispatcher1.cc @@ -22,6 +22,10 @@ TcpDispatcher::TcpDispatcher(int cfd, struct in_addr cip): case Dispatchmode::m_lax_hashed_ip: algorithm = new HashedIp; break; + case Dispatchmode::m_strict_stored_ip: + case Dispatchmode::m_lax_stored_ip: + algorithm = new StoredIp; + break; case Dispatchmode::m_leastconn: default: algorithm = new Leastconn; diff --git a/xr/thread/thread b/xr/thread/thread @@ -16,7 +16,7 @@ public: void unlock(); virtual void execute(); private: - pthread_mutex_t thread_mutex; + static pthread_mutex_t thread_mutex; }; #endif diff --git a/xr/thread/thread1.cc b/xr/thread/thread1.cc @@ -1,5 +1,6 @@ #include "thread" +pthread_mutex_t Thread::thread_mutex; Thread::Thread() { int res; diff --git a/xrctl/xrctl b/xrctl/xrctl @@ -96,14 +96,14 @@ my %services = # in, this reaches some proxy - or localhost:3128, which is a local squid. # I configure my browser to use localhost:8080 as proxy, and don't have # to reconfigure browsers anymore. Note the dispatch method which is - # first-active, the first downstream proxy that works is OK for me. + # first-available, the first downstream proxy that works is OK for me. # Note also that the server type is TCP, I don't need HTTP goodies. # Also the server listens to 127.0.0.1 - only for localhost usage. 'proxy' => { '--server' => [ qw(tcp:127.0.0.1:8080) ], '--backend' => [ qw(10.120.114.2:8080 192.168.1.250:80 localhost:3128) ], - '--dispatch-mode' => [ qw(first-active) ], + '--dispatch-mode' => [ qw(first-available) ], '--verbose' => undef, }, @@ -137,6 +137,9 @@ if ($#ARGV == -1) { } } +# Verify the configuration +verifyconf (@ARGV); + # Take appropriate action if ($action eq 'list') { list(@ARGV); @@ -309,6 +312,23 @@ sub rotate { } } +# Verify a configuration +# ---------------------- +sub verifyconf { + for my $s (@_) { + my @p = xrcommand($s); + my $cmd = "$p[0] -n"; + for my $i (2..$#p) { + $cmd .= " '$p[$i]'"; + } + if (system ($cmd)) { + die ("xrctl: Configuration of service '$s' probably bad\n", + "Testing command was:\n", + " $cmd\n"); + } + } +} + # Get the status of one balancer service # -------------------------------------- sub getstatus($) {