commit 3651f75185e9babc65a5e61ad297e2d34c2ae8d4
parent 0680b47c11e05fd36cd924768d0da75f6b3da487
Author: finwo <finwo@pm.me>
Date: Sat, 3 Jan 2026 19:35:02 +0100
2.04
Diffstat:
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($) {