crossroads

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

commit 18ca72abe3c1e23f6448d970a84e8b0843d3b7e1
parent 2c92a238e2fe0a66b074434dc725437a6f133d07
Author: finwo <finwo@pm.me>
Date:   Sat,  3 Jan 2026 19:37:28 +0100

2.48

Diffstat:
MChangeLog | 5+++++
MMakefile | 2+-
Mdoc/xr.odt | 0
Mdoc/xr.pdf | 0
Mdoc/xrctl.xml.5 | 11+++++++++++
Atest/onend | 3+++
Atest/onstart | 5+++++
Mxr/Dispatchers/httpdispatcher/dispatch.cc | 9++++++---
Mxr/Dispatchers/tcpdispatcher/execute.cc | 38++++++++++++++++++++++++++++++++++++--
Mxr/config/config | 28++++++++++++++++++++--------
Mxr/config/config1.cc | 2++
Mxr/config/parsecmdline.cc | 10+++++++++-
Mxr/etc/Makefile.class | 2+-
Mxr/etc/status.xslt | 18++++++++++++++++++
Mxr/etc/usage.txt | 6++++++
Mxr/httpbuffer/cookievalue.cc | 11+++++++----
Mxr/httpbuffer/httpbuffer | 1+
Axr/httpbuffer/paramvalue.cc | 21+++++++++++++++++++++
Mxr/mstr/mstr | 5+++++
Mxr/webinterface/answer.cc | 18++++++++++++++++++
Mxr/webinterface/answerstatus.cc | 2++
Mxrctl/xrctl | 2++
22 files changed, 179 insertions(+), 20 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,8 @@ +2.48 [KK 2009-03-26] +- Implemented onstart/onend hooks (flags -z, -Z). +- Sticky HTTP mode inspects the URI (parameter XRTarget) when no + sticky cookie is present. + 2.47 [KK 2009-03-04] - Stored-ip dispatching was enhanced to "anticipate" reconnects from previously seen clients. The number of anticipated connections is taken diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ # Top-level Makefile for XR # ------------------------- -VER = 2.47 +VER = 2.48 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 @@ -118,6 +118,17 @@ distributed with the sources for a full description. closing connections are in TIME_WAIT state, use: --> <closesocketsfast>yes</closesocketsfast> + <!-- XR can run specific scripts when client activity starts or + ends. When given, the scripts are run with the arguments: + the client IP, and the back end (server:port). A very + simple script /where/ever/activitystart might e.g. do: + echo Client $1 is going to back end $2 >> /tmp/activity.log + A very simple script /where/ever/activityend might e.g. do: + echo Client $1 is done with back end $2 >> /tmp/activity.log + --> + <onstart>/where/ever/activitystart</onstart> + <onend>/where/ever/activityend</onend> + <!-- Access restrictions: we allow from two IP ranges, and deny from one IP address. The overall results:will be: - Access will be allowed from 10.*.*.* diff --git a/test/onend b/test/onend @@ -0,0 +1,3 @@ +#!/bin/sh + +echo Client $1 was handled by $2 and is now stopping >> /tmp/activity.log diff --git a/test/onstart b/test/onstart @@ -0,0 +1,5 @@ +#!/bin/sh + +echo Client $1 will be handled by $2 >> /tmp/activity.log + + diff --git a/xr/Dispatchers/httpdispatcher/dispatch.cc b/xr/Dispatchers/httpdispatcher/dispatch.cc @@ -48,12 +48,14 @@ void HttpDispatcher::dispatch() { } targetlist(v); } - + // Dispatch as a normal backend if sticky HTTP is off, or if the // sticky target is badly specified. if (!config.stickyhttp() || - sscanf (buf.cookievalue ("XRTarget").c_str(), - "%d", &stickytarget) < 1 || + (sscanf (buf.cookievalue ("XRTarget").c_str(), + "%d", &stickytarget) < 1 && + sscanf (buf.paramvalue ("XRTarget").c_str(), + "%d", &stickytarget) < 1) || stickytarget >= balancer.nbackends()) { issticky(false); TcpDispatcher::dispatch(); @@ -74,6 +76,7 @@ void HttpDispatcher::dispatch() { issticky(true); } } + } catch (Error const &e) { senderrorpage(e.what()); diff --git a/xr/Dispatchers/tcpdispatcher/execute.cc b/xr/Dispatchers/tcpdispatcher/execute.cc @@ -1,5 +1,24 @@ #include "tcpdispatcher" +static void sysrun(string const &s) { + int ret = system(s.c_str()); + if (ret == -1) { + warnmsg(Mstr("Failed to start command: ") + s + "\n"); + return; + } + if (WIFEXITED(ret)) { + int stat = WEXITSTATUS(ret); + if (stat) + warnmsg(Mstr("Command" ) + s + + Mstr(" exited with status ") + Mstr(stat) + "\n"); + else + msg(Mstr("Command ") + s + + Mstr(" terminated normally.\n")); + return; + } + warnmsg(Mstr("Command ") + s + Mstr(" failed miserably!\n")); +} + void TcpDispatcher::execute() { Threadlist::clientfd(clientfd()); @@ -25,11 +44,19 @@ void TcpDispatcher::execute() { (Mstr(" to ") + balancer.backend(targetbackend()).description()) + (Mstr(", fd ") + backendfd()) + "\n"); + Threadlist::desc("Serving"); Threadlist::backend(targetbackend()); Threadlist::backendfd(backendfd()); balancer.backend(targetbackend()).startconnection(); + if (config.onstart().length()) { + ostringstream o; + o << config.onstart() << ' ' << clientipstr() << ' ' + << balancer.backend(targetbackend()).description(); + msg (Mstr("Running onstart script: ") + o.str() + "\n"); + sysrun(o.str()); + } try { handle(); @@ -39,11 +66,18 @@ void TcpDispatcher::execute() { Mutex::unlock(&cerr); } - balancer.backend(targetbackend()).endconnection(); - socketclose (clientfd()); socketclose (backendfd()); + balancer.backend(targetbackend()).endconnection(); + if (config.onend().length()) { + ostringstream o; + o << config.onend() << ' ' << clientipstr() << ' ' + << balancer.backend(targetbackend()).description(); + msg (Mstr("Running onend script: ") + o.str() + "\n"); + sysrun(o.str()); + } + msg ((Mstr("Done dispatching to back end fd ") + backendfd()) + (Mstr(" at ") + balancer.backend(targetbackend()).description()) + "\n"); diff --git a/xr/config/config b/xr/config/config @@ -30,7 +30,7 @@ public: Servertype::Type stype() const { return (styp.type()); } void stype(string const &s) { styp.type(s); } string stypestr() const { return (styp.typestr()); } - string sipaddr() const { return (sip); } + string const &sipaddr() const { return (sip); } int sport() const { return (lport); } int backends() const { return (blist.size()); } @@ -67,9 +67,11 @@ public: unsigned maxconn() const { return (max_conn); } void maxconn (unsigned m); - string externalalgorithm() const { return (external_algorithm); } + string const &externalalgorithm() const { + return (external_algorithm); + } - string pidfile() const { return (pid_file); } + string const &pidfile() const { return (pid_file); } void pidfile (string const &p); bool prefixtimestamp() const { return (prefix_timestamp); } @@ -79,16 +81,20 @@ public: void fastclose (bool f); bool usewebinterface() const { return use_webinterface; } - string webinterfaceip() const { return webinterface_ip; } + string const &webinterfaceip() const { + return webinterface_ip; + } int webinterfaceport() const { return webinterface_port; } unsigned nserverheaders() const { return (serverheaders.size()); } - string serverheader (unsigned n) { return (serverheaders[n]); } + string const &serverheader (unsigned n) { + return (serverheaders[n]); + } void addserverheader (string const &s); void removeserverheader (unsigned i); void changeserverheader (unsigned i, string const &s); - string dumpdir() const { return (dump_dir); } + string const &dumpdir() const { return (dump_dir); } void dumpdir (string s) { dump_dir = s; } unsigned softmaxconnrate() const { return soft_maxconnrate; } @@ -131,19 +137,24 @@ public: return (dmode.modestr()); } - string softmaxconnexcess() const { + string const &softmaxconnexcess() const { return soft_maxconn_excess_prog; } void softmaxconnexcess(string const &s) { soft_maxconn_excess_prog = s; } - string hardmaxconnexcess() const { + string const &hardmaxconnexcess() const { return hard_maxconn_excess_prog; } void hardmaxconnexcess(string const &s) { hard_maxconn_excess_prog = s; } + void onstart(string s) { on_start = s; } + string const &onstart() const { return on_start; } + void onend(string s) { on_end = s; } + string const &onend() const { return on_end; } + private: void setbackend (string const &s, string const &hostmatch, @@ -191,6 +202,7 @@ private: static string soft_maxconn_excess_prog; static string hard_maxconn_excess_prog; static unsigned dns_cache_timeout; + static string on_start, on_end; }; extern Config config; diff --git a/xr/config/config1.cc b/xr/config/config1.cc @@ -38,6 +38,8 @@ unsigned Config::quit_after = 0; string Config::soft_maxconn_excess_prog = ""; string Config::hard_maxconn_excess_prog = ""; unsigned Config::dns_cache_timeout = 3600; +string Config::on_start = ""; +string Config::on_end = ""; Config::Config () { } diff --git a/xr/config/parsecmdline.cc b/xr/config/parsecmdline.cc @@ -16,7 +16,7 @@ void Config::parsecmdline (int ac, char **av) { throw Error("Bad command line '" + cmdline + "'\n" + USAGE); # define OPTSTRING "?a:A:B:b:c:CDd:E:e:fF:g:hH:Il:" \ - "m:M:nPp:Q:r:R:Ss:t:T:u:U:vVW:w:xX" + "m:M:nPp:Q:r:R:Ss:t:T:u:U:vVW:w:xXz:Z:" # ifdef HAVE_GETOPT_LONG static struct option longopts[] = { { "allow-from", required_argument, 0, 'a' }, @@ -56,6 +56,8 @@ void Config::parsecmdline (int ac, char **av) { { "web-interface", required_argument, 0, 'W' }, { "add-xr-version", no_argument, 0, 'X' }, { "add-x-forwarded-for", no_argument, 0, 'x' }, + { "onstart", required_argument, 0, 'z' }, + { "onend", required_argument, 0, 'Z' }, { 0, 0, 0, 0 } }; # endif @@ -227,6 +229,12 @@ void Config::parsecmdline (int ac, char **av) { case 'x': add_x_forwarded_for = true; break; + case 'z': + onstart(optarg); + break; + case 'Z': + onend(optarg); + break; default: throw Error("Unknown flag, try 'xr -h' for usage"); break; diff --git a/xr/etc/Makefile.class b/xr/etc/Makefile.class @@ -15,4 +15,4 @@ $(BASE)/xr/$(BUILDDIR)/$(DIR)_%.o: %.cc -DCONF_OPTFLAGS='"$(CONF_OPTFLAGS)"' $(CONF_STRNSTR) \ $(CONF_GETOPT) $(CONF_GETOPT_LONG) $(CONF_INET_ATON) \ -I$(BASE)/xr \ - -c -g -Wall -o $@ $< + -c -g -Wall -Werror -o $@ $< diff --git a/xr/etc/status.xslt b/xr/etc/status.xslt @@ -411,6 +411,24 @@ </td> </tr> <tr> + <td>Activity scripts</td> + <td>Onstart command</td> + <td colspan="2" align="right"> + <input type="text" size="30" name="onstart" id="onstart" + value="{onstart}" + onchange="goto('/server/onstart/', 'onstart');"/> + </td> + </tr> + <tr> + <td></td> + <td>Onend command</td> + <td colspan="2" align="right"> + <input type="text" size="30" name="onend" id="onend" + value="{onend}" + onchange="goto('/server/onend/', 'onend');"/> + </td> + </tr> + <tr> <td>Network buffer size</td> <td colspan="2">bytes</td> <td> diff --git a/xr/etc/usage.txt b/xr/etc/usage.txt @@ -154,6 +154,12 @@ may not exist on your platform): -x, --add-x-forwarded-for Adds X-Forwarded-For with external IP address to back end streams in HTTP messages. + -z CMD, --onstart CMD + Runs CMD just before connecting a client to a back end. The arguments + to the command are: the client's IP address, and the back end address. + -Z CMD, --onend CMD + Runs CMD after termination of a client. For the arguments of CMD see -z. + XR's messages are sent to stderr. Invoke XR daemons using something like "xr -b ... [other flags] 2>&1 | logger &", or use xrctl. diff --git a/xr/httpbuffer/cookievalue.cc b/xr/httpbuffer/cookievalue.cc @@ -4,16 +4,19 @@ string Httpbuffer::cookievalue (string c) { PROFILE("Httpheader::cookievalue"); string cval = headerval ("Cookie"); - size_t pos; + string match = c; + if (match[match.size() - 1] != '=') + match += '='; string ret = ""; - if ( (pos = cval.find (c)) != string::npos) { - pos++; - pos += c.size(); + size_t pos = cval.find(match); + if (pos != string::npos) { + pos += match.size(); for (char ch = cval[pos]; pos < cval.size() && ch != ';' && ch != ',' && ch; ch = cval[++pos]) { ret += ch; } } + msg(Mstr("Cookie value '") + c + Mstr("' : '") + ret + "'\n"); return (ret); } diff --git a/xr/httpbuffer/httpbuffer b/xr/httpbuffer/httpbuffer @@ -34,6 +34,7 @@ public: void replaceheader (string const &h); string cookievalue (string var); + string paramvalue(string var); RequestMethod requestmethod(); diff --git a/xr/httpbuffer/paramvalue.cc b/xr/httpbuffer/paramvalue.cc @@ -0,0 +1,21 @@ +#include "httpbuffer" + +string Httpbuffer::paramvalue (string c) { + PROFILE("Httpheader::paramvalue"); + + string uri = requesturi(); + string match = c; + if (match[match.size() - 1] != '=') + match += '='; + string ret = ""; + size_t pos = uri.find(match); + if (pos != string::npos) { + pos += match.size(); + for (char ch = uri[pos]; + pos < uri.size() && ch != '&' && ch != '?'; + ch = uri[++pos]) + ret += ch; + } + msg(Mstr("Param value '") + c + Mstr("' : '") + ret + "'\n"); + return ret; +} diff --git a/xr/mstr/mstr b/xr/mstr/mstr @@ -7,6 +7,11 @@ class Mstr: public string { public: Mstr(string s): string(s) {} Mstr(char const *s): string(s) {} + Mstr(int i) { + ostringstream o; + o << i; + *this = o.str(); + } Mstr const &operator+ (int i) { ostringstream o; o << i; diff --git a/xr/webinterface/answer.cc b/xr/webinterface/answer.cc @@ -397,6 +397,24 @@ void Webinterface::answer(Httpbuffer req) { return; } + // /server/onstart/ + // /server/onstart/PROGRAM + if (parts.size() == 3 && + parts[0] == "server" && parts[1] == "onstart") { + config.onstart(parts[2]); + answer_status(); + return; + } + + // /server/onend/ + // /server/onend/PROGRAM + if (parts.size() == 3 && + parts[0] == "server" && parts[1] == "onend") { + config.onend(parts[2]); + answer_status(); + return; + } + // /server/addbackend/IP:PORT if (parts.size() == 3 && parts[0] == "server" && parts[1] == "addbackend") { diff --git a/xr/webinterface/answerstatus.cc b/xr/webinterface/answerstatus.cc @@ -25,6 +25,8 @@ void Webinterface::answer_status() { " <dnscachetimeout>" << config.dnscachetimeout() << "</dnscachetimeout>\n" " <buffersize>" << config.buffersize() << "</buffersize>\n" " <closesocketsfast>" << config.fastclose() << "</closesocketsfast>\n" + " <onstart>" << config.onstart() << "</onstart>\n" + " <onend>" << config.onend() << "</onend>\n" " <checks>\n" " <wakeupinterval>" << config.wakeupsec() << "</wakeupinterval>\n" " <checkupinterval>" << config.checkupsec() << "</checkupinterval>\n" diff --git a/xrctl/xrctl b/xrctl/xrctl @@ -474,6 +474,8 @@ sub xr_cmdarr { $default_softmaxconnexcess), flag($ss, '--dns-cache-timeout', 'dnscachetimeout', $default_dnscachetimeout), + flag($ss, '--onstart', 'onstart'), + flag($ss, '--onend', 'onend'), flag($ss, '--log-traffic-dir', 'logtrafficdir', '')); for my $k (sort (keys (%boolflags))) { push (@cmd, $boolflags{$k}) if (istrue($ss->data($k)));