commit ab42faeecb9f5c97fee4f81a0a94c855675d55d4 parent 159a2b25ec8fc144a766d38c1bfab6dd9b788750 Author: finwo <finwo@pm.me> Date: Sat, 3 Jan 2026 19:35:48 +0100 2.12 Diffstat:
44 files changed, 736 insertions(+), 208 deletions(-)
diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,15 @@ +2.12 [KK 2008-09-10] +- Small code changes for g++ v3.x backward-compatibility support. + (Thanks Simon M.) +- Web interface: layout enhanced, more modification options +- Code cleanup, duplications removed +- Network sends treat ignore some errno's and retry (see + sys/fdwrite.cc) +- Web interface retries binding to its socket (incase a previous + instance hasn't terminated yet) +- Web interface returns an HTTP error page (status 500 only, no + content) during errors + 2.11 [KK 2008-09-04] - Bugfix in "first-active" dispatch mode. Previously XR would gobble up fd's when no back end was available. diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ # Top-level Makefile for XR # ------------------------- -VER = 2.11 +VER = 2.12 BINDIR = /usr/sbin TAR = /tmp/crossroads-$(VER).tar.gz AUTHOR = Karel Kubat <karel@kubat.nl> diff --git a/xr/backend/available.cc b/xr/backend/available.cc @@ -4,7 +4,7 @@ bool Backend::available() const { if (config.debug()) { ostringstream o; o << (islive ? "alive" : "dead") << ", " - << connections() << " connections, " << ", of " + << connections() << " connections of " << maxconn() << " max"; debugmsg ("Backend " + description() + ": " + o.str() + "\n"); } diff --git a/xr/backend/backend b/xr/backend/backend @@ -29,6 +29,7 @@ public: unsigned maxconn() const { return (bdef.maxconn()); } void maxconn (unsigned m) { bdef.maxconn(m); } string const &hostmatch() const { return (bdef.hostmatch()); } + void hostmatch(string const &s) { bdef.hostmatch(s); } regex_t const &hostregex() const { return (bdef.hostregex()); } unsigned weight() const { return (bdef.weight()); } void weight (unsigned w) { bdef.weight(w); } diff --git a/xr/backend/connect.cc b/xr/backend/connect.cc @@ -3,6 +3,8 @@ bool Backend::connect() { // Assume the backend is dead islive = false; + + debugmsg ("Connecting to back end " + description() + "\n"); // Create client socket if ( (clsocket = socket (PF_INET, SOCK_STREAM, 0)) < 0 ) @@ -30,9 +32,9 @@ bool Backend::connect() { throw static_cast<Error>("Failed to get fd flags: ") + strerror(errno); } if (fcntl (clsocket, F_SETFL, flags | O_NONBLOCK) == -1) { + socketclose (clsocket); throw static_cast<Error>("Failed to fd in nonblocking mode: ") + strerror(errno); - socketclose (clsocket); } // Do the connect @@ -56,13 +58,15 @@ bool Backend::connect() { islive = true; } - /* - msg << "Back end " << bdef.server() << ":" << bdef.port(); - if (islive) - msg << " is live on socket " << clsocket << "\n"; - else - msg << " is NOT live\n"; - */ + if (config.debug()) { + ostringstream o; + o << "Back end " << description() << " is "; + if (islive) + o << "alive on socket " << clsocket; + else + o << "NOT ALIVE"; + debugmsg (o.str() + "\n"); + } return (islive); } diff --git a/xr/backenddef/backenddef b/xr/backenddef/backenddef @@ -24,7 +24,7 @@ BackendDef(): srv(""), prt(-1), max(0), host_match(""), wt(1) {} void weight (unsigned w); unsigned adjustedweight() const { return min_wt + max_wt - wt; } - void hostmatch(string s); + void hostmatch(string const &s); string const &hostmatch() const { return (host_match); } regex_t const &hostregex() const { return (host_regex); } diff --git a/xr/backenddef/hostmatch.cc b/xr/backenddef/hostmatch.cc @@ -1,11 +1,11 @@ #include "backenddef" -void BackendDef::hostmatch (string s) { +void BackendDef::hostmatch (string const &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"; - } + if (host_match == "") + host_match = "."; + if (regcomp (&host_regex, host_match.c_str(), + REG_EXTENDED | REG_ICASE | REG_NOSUB)) + throw static_cast<Error>("Host match specifier '") + + host_match + "' isn't a valid regular expression"; } diff --git a/xr/config/addserverheader.cc b/xr/config/addserverheader.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::addserverheader (string const &s) { + Mutex::lock (&serverheaders); + serverheaders.push_back (s); + Mutex::unlock (&serverheaders); +} diff --git a/xr/config/addxforwardedfor.cc b/xr/config/addxforwardedfor.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::addxforwardedfor(bool b) { + Mutex::lock (&add_x_forwarded_for); + add_x_forwarded_for = b; + Mutex::unlock (&add_x_forwarded_for); +} diff --git a/xr/config/addxrversion.cc b/xr/config/addxrversion.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::addxrversion(bool b) { + Mutex::lock (&add_xr_version); + add_xr_version = b; + Mutex::unlock (&add_xr_version); +} diff --git a/xr/config/buffersize.cc b/xr/config/buffersize.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::buffersize(unsigned b) { + Mutex::lock (&bufsize); + bufsize = b; + Mutex::unlock (&bufsize); +} diff --git a/xr/config/changeserverheader.cc b/xr/config/changeserverheader.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::changeserverheader (unsigned i, string const &s) { + Mutex::lock (&serverheaders); + serverheaders[i] = s; + Mutex::unlock (&serverheaders); +} diff --git a/xr/config/config b/xr/config/config @@ -1,11 +1,12 @@ #ifndef _CMDLINE_ #define _CMDLINE_ -#include "../sys/sys" -#include "../backenddef/backenddef" -#include "../servertype/servertype" -#include "../dispatchmode/dispatchmode" -#include "../error/error" +#include "sys/sys" +#include "backenddef/backenddef" +#include "servertype/servertype" +#include "dispatchmode/dispatchmode" +#include "error/error" +#include "ThreadsAndMutexes/mutex/mutex" using namespace std; @@ -18,44 +19,64 @@ public: // Accessors bool verbose() const { return (verbose_flag); } + bool debug() const { return (debug_flag); } + Servertype::Type stype() const { return (styp.type()); } string stypestr() const { return (styp.typestr()); } string sipaddr() const { return (sip); } int sport() const { return (lport); } + int backends() const { return (blist.size()); } + int client_timeout() const { return (c_timeout); } int backend_timeout() const { return (b_timeout); } - int wakeupsec() const { return (wakeup); } + + int wakeupsec() const { return (wakeup); } int checkupsec() const { return (checkup); } - unsigned buffersize() const { return (bufsize); } - void buffersize (unsigned b) { bufsize = b; } + + unsigned buffersize() const { return (bufsize); } + void buffersize (unsigned b); + bool foregroundmode() const { return (foreground_mode); } + bool addxrversion() const { return (add_xr_version); } - bool debug() const { return (debug_flag); } + void addxrversion (bool b); + bool addxforwardedfor() const { return (add_x_forwarded_for); } + void addxforwardedfor (bool b); + bool stickyhttp() const { return (sticky_http); } + void stickyhttp(bool b); + unsigned maxconn() const { return (max_conn); } - void maxconn (unsigned m) const { max_conn = m; } + void maxconn (unsigned m); + string externalalgorithm() const { return (external_algorithm); } + string pidfile() const { return (pid_file); } - void pidfile (string p) { pid_file = p; } + void pidfile (string const &p); + bool prefixtimestamp() const { return (prefix_timestamp); } - void prefixtimestamp (bool p) { prefix_timestamp = p; } - void addserverheader (string s) { serverheaders.push_back(s); } - unsigned nserverheaders() const { return (serverheaders.size()); } - string serverheader (unsigned n) { return (serverheaders[n]); } - unsigned nallow() const { return (allowlist.size()); } - unsigned ndeny() const { return (denylist.size()); } + void prefixtimestamp (bool p); + 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; } - bool hostmatchused() const { return (hostmatch_used); } + void fastclose (bool f); + bool usewebinterface() const { return use_webinterface; } string webinterfaceip() const { return webinterface_ip; } int webinterfaceport() const { return webinterface_port; } - struct in_addr allow(unsigned n) const { + unsigned nserverheaders() const { return (serverheaders.size()); } + string serverheader (unsigned n) { return (serverheaders[n]); } + void addserverheader (string const &s); + void removeserverheader (unsigned i); + void changeserverheader (unsigned i, string const &s); + + unsigned nallow() const { return (allowlist.size()); } + unsigned ndeny() const { return (denylist.size()); } + int ipstoretimeout() const { return (ipstore_timeout); } + void ipstoretimeout(int t); + struct in_addr allow(unsigned n) const { return (allowlist[n]); } struct in_addr deny(unsigned n) const { @@ -68,6 +89,9 @@ public: Dispatchmode::Mode dispatchmode() const { return (dmode.mode()); } + string dispatchmodestr() const { + return (dmode.modestr()); + } private: void setbackend (string s, string hostmatch); @@ -103,7 +127,6 @@ private: static vector<struct in_addr> denylist; static bool fast_close; static int ipstore_timeout; - static bool hostmatch_used; static bool use_webinterface; static string webinterface_ip; static int webinterface_port; diff --git a/xr/config/config1.cc b/xr/config/config1.cc @@ -25,7 +25,6 @@ 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; bool Config::use_webinterface = false; string Config::webinterface_ip; int Config::webinterface_port; diff --git a/xr/config/fastclose.cc b/xr/config/fastclose.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::fastclose(bool b) { + Mutex::lock (&fast_close); + fast_close = b; + Mutex::unlock (&fast_close); +} diff --git a/xr/config/ipstoretimeout.cc b/xr/config/ipstoretimeout.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::ipstoretimeout (int t) { + Mutex::lock (&ipstore_timeout); + ipstore_timeout = t; + Mutex::unlock (&ipstore_timeout); +} diff --git a/xr/config/maxconn.cc b/xr/config/maxconn.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::maxconn (unsigned m) { + Mutex::lock (&max_conn); + max_conn = m; + Mutex::unlock (&max_conn); +} diff --git a/xr/config/parsecmdline.cc b/xr/config/parsecmdline.cc @@ -99,7 +99,6 @@ 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/pidfile.cc b/xr/config/pidfile.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::pidfile (string const &p) { + Mutex::lock (&pid_file); + pid_file = p; + Mutex::unlock (&pid_file); +} diff --git a/xr/config/prefixtimestamp.cc b/xr/config/prefixtimestamp.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::prefixtimestamp(bool b) { + Mutex::lock (&prefix_timestamp); + prefix_timestamp = b; + Mutex::unlock (&prefix_timestamp); +} diff --git a/xr/config/removeserverheader.cc b/xr/config/removeserverheader.cc @@ -0,0 +1,8 @@ +#include "config" + +void Config::removeserverheader (unsigned i) { + Mutex::lock (&serverheaders); + serverheaders.erase (serverheaders.begin() + i, + serverheaders.begin() + i + 1); + Mutex::unlock (&serverheaders); +} diff --git a/xr/config/stickyhttp.cc b/xr/config/stickyhttp.cc @@ -0,0 +1,7 @@ +#include "config" + +void Config::stickyhttp (bool b) { + Mutex::lock (&sticky_http); + sticky_http = b; + Mutex::unlock (&sticky_http); +} diff --git a/xr/dispatchmode/dispatchmode b/xr/dispatchmode/dispatchmode @@ -1,7 +1,7 @@ #ifndef _DISPATCHMODE_ #define _DISPATCHMODE_ -#include "../sys/sys" +#include "sys/sys" using namespace std; @@ -24,6 +24,7 @@ public: void mode (Mode m) { mymode = m; } Mode mode() const { return (mymode); } + string modestr() const; private: Mode mymode; diff --git a/xr/dispatchmode/modestr.cc b/xr/dispatchmode/modestr.cc @@ -0,0 +1,29 @@ +#include "dispatchmode" +#include "config/config" + +string Dispatchmode::modestr() const { + ostringstream o; + + switch (mode()) { + case m_leastconn: + return ("least-connections"); + case m_roundrobin: + return ("round-robin"); + case m_firstactive: + return ("first-available"); + case m_external: + return (static_cast<string>("external:") + config.externalalgorithm()); + case m_strict_hashed_ip: + return ("strict-hashed-ip"); + case m_lax_hashed_ip: + return ("lax-hashed-ip"); + case m_strict_stored_ip: + o << "strict-stored-ip:" << config.ipstoretimeout(); + return (o.str()); + case m_lax_stored_ip: + o << "lax-stored-ip:" << config.ipstoretimeout(); + return (o.str()); + default: + return ("unknown"); + } +} diff --git a/xr/etc/status.xslt b/xr/etc/status.xslt @@ -19,23 +19,29 @@ } td { font-family: Verdana,Helvetica; - font-size: 10pt; + font-size: 8pt; + background-color: #ffff99; } } input { + font-family: Verdana,Helvetica; font-size: 8pt; + background-color: #ffff99; } } .server { background-color: #f3f399; } .backend { background-color: #f3f099; } - .info { font-size: 8pt; background-color: #ffff99; } .footer { color: gray; } </style> <script type="text/javascript"> function goto(uri, input) { - var el = document.getElementById(input); - if (el) { - var value = el.value; - if (value != "") - document.location = uri + value; + if (input == '') + document.location = uri; + else { + var el = document.getElementById(input); + if (el) { + var value = el.value; + if (value != "") + document.location = uri + value; + } } } </script> @@ -56,99 +62,226 @@ <xsl:template match="/status/server"> <tr> - <td class="server" colspan="3"> + <td class="server" colspan="4"> <b>Server <xsl:value-of select="address"/> </b> </td> </tr> <tr> - <td class="info"> Type </td> - <td class="info" colspan="3"> <xsl:value-of select="type"/> </td> + <td>Type</td> + <td colspan="3"> <xsl:value-of select="type"/> </td> + </tr> + <tr> + <td>Timeouts</td> + <td>client: <xsl:value-of select="clienttimeout"/></td> + <td colspan="2">backend: <xsl:value-of select="backendtimeout"/></td> + </tr> + <tr> + <td>Checks</td> + <td>wakeups</td> + <td colspan="2"> + <xsl:choose> + <xsl:when test="checks/wakeupinterval = 0"> + off + </xsl:when> + <xsl:otherwise> + each <xsl:value-of select="checks/wakeupinterval"/> sec. + </xsl:otherwise> + </xsl:choose> + </td> + </tr> + <tr> + <td></td> + <td>checkups</td> + <td colspan="2"> + <xsl:choose> + <xsl:when test="checks/checkupinterval = 0"> + off + </xsl:when> + <xsl:otherwise> + each <xsl:value-of select="checks/checkupinterval"/> sec. + </xsl:otherwise> + </xsl:choose> + </td> + </tr> + <tr> + <td>Dispatch mode</td> + <td colspan="3"> <xsl:value-of select="dispatchmode"/> </td> + </tr> + <tr> + <td>Network buffer size</td> + <td colspan="2">bytes</td> + <td> + <input type="text" size="8" name="serverbufsz" id="serverbufsz" + value="{buffersize}" + onchange="goto('/server/buffersize/', 'serverbufsz');"/> + </td> </tr> <tr> - <td class="info"> Max. connections </td> - <td class="info"> + <td> Max. connections </td> + <td colspan="2"> <xsl:choose> <xsl:when test="maxconnections = 0"> unlimited </xsl:when> <xsl:otherwise> - <xsl:value-of select="maxconnections"/> + maximum value (0 for unlimited) </xsl:otherwise> </xsl:choose> </td> <td> - <input type="text" size="5" name="setservermaxcon" - id="setservermaxcon" value="" + <input type="text" size="8" name="setservermaxcon" class="input" + id="setservermaxcon" value="{maxconnections}" onchange="goto('/server/maxconnections/', 'setservermaxcon');"/> </td> </tr> + <xsl:if test="/status/server/type = 'http'"> + <xsl:apply-templates select="/status/server/http"/> + </xsl:if> </xsl:template> <xsl:template match="/status/backend"> - <tr> <td colspan="3"></td></tr> + <tr> <td colspan="4"><hr/></td></tr> <tr> - <td class="backend" colspan="3"> + <td class="backend" colspan="4"> <b> Back end <xsl:value-of select="address"/> </b> </td> </tr> <tr> - <td class="info">Weight</td> - <td class="info"><xsl:value-of select="weight"/></td> + <td><b>State</b></td> + <td>health</td> + <td colspan="2"> + <xsl:value-of select="live"/>, + <xsl:value-of select="available"/> + </td> + </tr> + <tr> + <td></td> + <td>connections</td> + <td colspan="2"><xsl:value-of select="connections"/></td> + </tr> + <tr> + <td></td> + <td>served</td> + <td colspan="2"> + <xsl:value-of select="bytesserved"/> bytes, + <xsl:value-of select="clientsserved"/> clients + </td> + </tr> + <tr> + <td><b>Options</b></td> + <td colspan="2">weight</td> <td> - <input type="text" size="5" name="setbackendweight{nr}" - id="setbackendweight{nr}" value="" + <input type="text" size="8" name="setbackendweight{nr}" + id="setbackendweight{nr}" value="{weight}" onchange="goto('/backend/{nr}/weight/', 'setbackendweight{nr}');"/> </td> </tr> <tr> - <td class="info">Max. connections</td> - <td class="info"> + <td></td> + <td>max. connections</td> + <td> <xsl:choose> <xsl:when test="maxconnections = 0"> unlimited </xsl:when> <xsl:otherwise> - <xsl:value-of select="maxconnections"/> + maximum value (0 for unlimited) </xsl:otherwise> </xsl:choose> </td> <td> - <input type="text" size="5" name="setmaxconnections{nr}" - id="setmaxconnections{nr}" value="" - onchange="goto('/backend/{nr}/maxconnections/', 'setmaxconnections{nr}');"/> + <input type="text" size="8" name="setbackendmaxcon{nr}" class="input" + id="setbackendmaxcon{nr}" value="{maxconnections}" + onchange="goto('/backend/{nr}/maxconnections/', 'setbackendmaxcon{nr}');"/> </td> </tr> - <xsl:if test="/server/type = http"> + <xsl:if test="/status/server/type = 'http'"> <tr> - <td class="info">Host match</td> - <td class="info"><xsl:value-of select="hostmatch"/></td> - <td class="info"></td> + <td></td> + <td>host match</td> + <td> + <xsl:choose> + <xsl:when test="hostmatch = '.'"> + any host request + </xsl:when> + <xsl:otherwise> + (. for any host) + </xsl:otherwise> + </xsl:choose> + </td> + <td> + <input type="text" size="8" name="sethostmatch{nr}" + id="sethostmatch{nr}" value="{hostmatch}" + onchange="goto('/backend/{nr}/hostmatch/', 'sethostmatch{nr}');"/> + </td> </tr> </xsl:if> +</xsl:template> + +<xsl:template match="/status/server/http"> <tr> - <td class="info">Health state</td> - <td class="info"><xsl:value-of select="live"/></td> - <td class="info"></td> - </tr> - <tr> - <td class="info">Availability</td> - <td class="info"><xsl:value-of select="available"/></td> - <td class="info"></td> - </tr> - <tr> - <td class="info">Connections</td> - <td class="info"><xsl:value-of select="connections"/></td> - <td class="info"></td> + <td>HTTP Goodies</td> + <td colspan="2">add X-Forwarded-For</td> + <td> + <xsl:choose> + <xsl:when test="addxforwardedfor = 0"> + <select onchange="goto('/server/addxforwardedfor/on', '');"> + <option value="yes">yes</option> + <option value="no" selected="1">no</option> + </select> + </xsl:when> + <xsl:otherwise> + <select onchange="goto('/server/addxforwardedfor/off', '');"> + <option value="yes" selected="1">yes</option> + <option value="no">no</option> + </select> + </xsl:otherwise> + </xsl:choose> + </td> </tr> <tr> - <td class="info">Bytes served</td> - <td class="info"><xsl:value-of select="bytesserved"/></td> - <td class="info"></td> + <td></td> + <td colspan="2">sticky HTTP</td> + <td> + <xsl:choose> + <xsl:when test="stickyhttp = 0"> + <select onchange="goto('/server/stickyhttp/on', '');"> + <option value="yes">yes</option> + <option value="no" selected="1">no</option> + </select> + </xsl:when> + <xsl:otherwise> + <select onchange="goto('/server/stickyhttp/off', '');"> + <option value="yes" selected="1">yes</option> + <option value="no">no</option> + </select> + </xsl:otherwise> + </xsl:choose> + </td> </tr> + <xsl:apply-templates select="/status/server/http/serverheaders"/> +</xsl:template> + +<xsl:template match="/status/server/http/serverheaders"> + <xsl:for-each select="serverheader"> + <tr> + <td></td> + <td colspan="2">server header (. to delete)</td> + <td> + <input type="text" size="8" name="serverheader{nr}" + id="serverheader{nr}" value="{header}" + onchange="goto('/server/changeheader/{nr}/', 'serverheader{nr}');"/> + </td> + </tr> + </xsl:for-each> <tr> - <td class="info">Clients served</td> - <td class="info"><xsl:value-of select="clientsserved"/></td> - <td class="info"></td> + <td></td> + <td colspan="2">new server header</td> + <td> + <input type="text" size="8" name="newserverheader" + id="newserverheader" + onchange="goto('/server/newheader/', 'newserverheader');"/> + </td> </tr> </xsl:template> diff --git a/xr/httpbuffer/requestmethod.cc b/xr/httpbuffer/requestmethod.cc @@ -3,8 +3,10 @@ Httpbuffer::RequestMethod Httpbuffer::requestmethod() const { string first = firstline(); debugmsg ("First line of http buffer: '" + first + "'\n"); - - if (!first.compare (0, 3, "GET")) + + // GCC 3.x doesn't support string.compare(start,len,otherstring) + // so let's just get a substring and compare to the target + if (first.substr(0, 3) == static_cast<string>("GET")) return (m_get); return (m_other); diff --git a/xr/httpdispatcher/dispatch.cc b/xr/httpdispatcher/dispatch.cc @@ -14,7 +14,17 @@ void HttpDispatcher::dispatch() { if (!getclientrequest()) throw static_cast<Error>("Didn't receive a valid " "client request.\n"); - if (config.hostmatchused()) { + + // See if hostmatching is used. This is true when a backend matches against + // a non-dot host. + bool hostmatchused = false; + for (unsigned i = 0; i < balancer.nbackends(); i++) + if (balancer.backend(i).hostmatch() != ".") { + hostmatchused = true; + break; + } + // Build new target list if host matching applies. + if (hostmatchused) { host_header = clientrequest.headerval ("Host"); msg ("Will try to dispatch request host '" + host_header + "'\n"); diff --git a/xr/httpdispatcher/handle.cc b/xr/httpdispatcher/handle.cc @@ -18,15 +18,17 @@ void HttpDispatcher::handle() { clientrequest.addheader (config.serverheader(n)); // Send to server - if (!sendclientrequest() ) { - msg ("Failed to send client request to back end, not processing\n"); + try { + sendclientrequest(); + } catch (Error const &e) { + msg (static_cast<string>(e.what()) + "\n"); senderrorpage(); return; } // Get server's response if (!getserverresponse()) { - msg ("Failed to receive a valid back end response, not processing\n"); + msg ("Failed to get server response\n"); senderrorpage(); return; } @@ -41,7 +43,9 @@ void HttpDispatcher::handle() { } // Flush server buffer to the client - writechunk (clientfd(), serverresponse.data(), serverresponse.size()); + fdwrite (clientfd(), config.client_timeout(), + serverresponse.data(), serverresponse.size()); + balancer.backend(targetbackend()).addbytes(serverresponse.size()); // Then switch to the TCP copy/thru protocol. TcpDispatcher::handle(); diff --git a/xr/httpdispatcher/httpdispatcher b/xr/httpdispatcher/httpdispatcher @@ -17,9 +17,9 @@ public: private: private: bool getclientrequest(); - bool sendclientrequest(); + void sendclientrequest(); bool getserverresponse(); - bool sendserverresponse(); + void sendserverresponse(); void senderrorpage(); Httpbuffer clientrequest, serverresponse; diff --git a/xr/httpdispatcher/sendclientrequest.cc b/xr/httpdispatcher/sendclientrequest.cc @@ -1,10 +1,9 @@ #include "httpdispatcher" -bool HttpDispatcher::sendclientrequest() { - Fdset set (config.backend_timeout()); - set.add (backendfd()); - return ( (set.writeable() == backendfd()) && - (writechunk (backendfd(), - clientrequest.data(), clientrequest.size()) == - clientrequest.size()) ); +void HttpDispatcher::sendclientrequest() { + msg ("Sending client's request '" + clientrequest.firstline() + + "' to back end\n"); + fdwrite (backendfd(), config.backend_timeout(), + clientrequest.data(), clientrequest.size()); + balancer.backend(targetbackend()).addbytes(clientrequest.size()); } diff --git a/xr/httpdispatcher/senderrorpage.cc b/xr/httpdispatcher/senderrorpage.cc @@ -8,11 +8,9 @@ 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)); + fdwrite (clientfd(), config.client_timeout(), + ERRORSTR, sizeof(ERRORSTR)); } catch (Error const &e) { + cerr << e.what() << " (while sending error page)\n"; } } - diff --git a/xr/httpdispatcher/sendserverresponse.cc b/xr/httpdispatcher/sendserverresponse.cc @@ -1,12 +1,9 @@ #include "httpdispatcher" -bool HttpDispatcher::sendserverresponse() { +void HttpDispatcher::sendserverresponse() { msg ("Sending server response '" + serverresponse.firstline() + "' to client.\n"); - Fdset set (config.client_timeout()); - set.add (clientfd()); - return ( (set.writeable() == clientfd()) && - (writechunk (clientfd(), - serverresponse.data(), serverresponse.size()) == - serverresponse.size()) ); + fdwrite (clientfd(), config.client_timeout(), + serverresponse.data(), serverresponse.size()); + balancer.backend(targetbackend()).addbytes(clientrequest.size()); } diff --git a/xr/sys/fdwrite.cc b/xr/sys/fdwrite.cc @@ -0,0 +1,45 @@ +#include "sys" +#include "fdset/fdset" + +void fdwrite (int fd, int timeout, char const *buf, unsigned buflen) { + if (config.debug()) { + ostringstream o; + o << "About to write " << buflen << " bytes to fd " << fd + << ", timeout " << timeout << "\n"; + debugmsg (o.str()); + } + + unsigned totwritten = 0; + while (totwritten < buflen) { + // Wait for the socket to become writeable. + if (timeout) { + Fdset set (timeout); + set.add (fd); + if (set.writeable() != fd) + throw static_cast<Error>("Fd ") + fd + + " failed to become writable within " + timeout + " sec"; + } + + // Push bytes + ssize_t nwritten; + if (fd < 4) + nwritten = write (fd, buf + totwritten, buflen - nwritten); + else + nwritten = send (fd, buf + totwritten, buflen - nwritten, 0); + + if (config.debug()) { + ostringstream o; + o << "Sent " << nwritten << " bytes to fd " << fd << "\n"; + debugmsg (o.str()); + } + + // EINVAL / EINPROGRESS errors are handled as: retry + // If any bytes were written, we're ok + if (nwritten >= 1) + totwritten += nwritten; + else if (errno != EINVAL && errno != EINPROGRESS) + throw static_cast<Error>("Write/send failed: errno=") + + errno + ", " + strerror(errno) + ", result=" + nwritten; + } +} + diff --git a/xr/sys/socketclose.cc b/xr/sys/socketclose.cc @@ -2,6 +2,12 @@ #include "../config/config" void socketclose (int fd) { + if (config.debug()) { + ostringstream o; + o << fd; + debugmsg ("Closing socket " + o.str() + "\n"); + } + if (config.fastclose()) { struct linger l; l.l_onoff = 1; diff --git a/xr/sys/sys b/xr/sys/sys @@ -52,6 +52,7 @@ string timestamp(time_t s = 0); bool ipmatch (struct in_addr addr, struct in_addr mask); void socketclose (int fd); vector<string> str2parts (string const &s, char sep); +void fdwrite (int fd, int timeout, char const *buf, unsigned buflen); #ifndef HAVE_INET_ATON int inet_aton (char const *name, struct in_addr *addr); diff --git a/xr/tcpdispatcher/dispatch.cc b/xr/tcpdispatcher/dispatch.cc @@ -16,6 +16,8 @@ void TcpDispatcher::dispatch() { } } + // Call the dispatch algorithm until we can connect, + // or until the algorithm is out of back ends (throws exception). while (!connected) { target_backend = algorithm->target(clientip(), target_list); Backend tb = balancer.backend(target_backend); @@ -27,7 +29,10 @@ void TcpDispatcher::dispatch() { connected = true; backendfd(tb.sock()); - msg ("Dispatching client to back end " + tb.description() + "\n"); + ostringstream o; + o << tb.sock(); + msg ("Will dispatch client to back end " + tb.description() + + " on fd " + o.str() + "\n"); break; } diff --git a/xr/tcpdispatcher/execute.cc b/xr/tcpdispatcher/execute.cc @@ -16,7 +16,7 @@ void TcpDispatcher::execute() { ostringstream co; co << clientfd(); ostringstream bo; - bo << balancer.backend(target_backend).sock(); + bo << backendfd(); msg ("Dispatching client fd " + co.str() + " to " + balancer.backend(target_backend).description() + ", fd " + bo.str() + "\n"); diff --git a/xr/tcpdispatcher/handle.cc b/xr/tcpdispatcher/handle.cc @@ -1,15 +1,37 @@ #include "tcpdispatcher" void TcpDispatcher::handle() { + if (config.debug()) { + ostringstream o; + o << "TCP dispatcher: About to shuttle between client fd " + << clientfd() << " and backend fd " << backendfd() << "\n"; + debugmsg (o.str()); + } + while (1) { Fdset readset (config.client_timeout()); readset.add (clientfd()); readset.add (backendfd()); int s; - if ((s = readset.readable()) < 0 || !readchunk(s)) + if ((s = readset.readable()) < 0) + break; + + if (config.debug()) { + ostringstream o; + o << s; + debugmsg ("Data waiting on fd " + o.str() + "\n"); + } + + if (!readchunk (s == clientfd() ? clientfd(): backendfd())) break; - writechunk (s == clientfd() ? backendfd() : clientfd(), - databuf(), databufsize()); + + // fdwrite (1, 0, databuf(), databufsize()); + + fdwrite (s == clientfd() ? backendfd() : clientfd(), + s == clientfd() ? config.backend_timeout() : + config.client_timeout(), + databuf(), databufsize()); + balancer.backend(target_backend).addbytes(databufsize()); } } diff --git a/xr/tcpdispatcher/tcpdispatcher b/xr/tcpdispatcher/tcpdispatcher @@ -41,7 +41,6 @@ public: void targetlist (BackendVector t) { target_list = t; } unsigned readchunk (int src); - unsigned writechunk (int dst, char const *b, unsigned blen); private: string printable (char ch) const; diff --git a/xr/tcpdispatcher/writechunk.cc b/xr/tcpdispatcher/writechunk.cc @@ -1,29 +0,0 @@ -#include "tcpdispatcher" - -unsigned TcpDispatcher::writechunk (int dst, char const *b, unsigned blen) { - // Client fd 0 (stdin) gets written to stdout. - if (!dst) - dst = 1; - - unsigned totwritten = 0; - while (totwritten < blen) { - ssize_t nwritten = write (dst, (void*) (b + totwritten), - blen - totwritten); - if (nwritten < 1) - throw static_cast<Error>("Write failed"); - - balancer.backend(targetbackend()).addbytes (nwritten); - - if (config.debug()) { - ostringstream o; - o << "Sent " << nwritten << " bytes to fd " << dst << ": "; - for (unsigned i = 0; i < (unsigned) nwritten; i++) - o << printable(b[totwritten + i]); - o << "\n"; - debugmsg (o.str()); - } - - totwritten += nwritten; - } - return (totwritten); -} diff --git a/xr/webinterface/answer.cc b/xr/webinterface/answer.cc @@ -1,43 +1,190 @@ #include "webinterface" +static unsigned str2uns (string const &s, string const &desc) { + unsigned ret; + + if (sscanf (s.c_str(), "%u", &ret) < 1) + throw static_cast<Error>("Bad ") + desc; + return (ret); +} + +static unsigned backendindex (string const &s) { + unsigned ret; + + ret = str2uns (s, "back end index"); + if (ret >= balancer.nbackends()) + throw static_cast<Error>("Back end index out of range"); + return (ret); +} + +static unsigned headerindex (string const &s) { + unsigned ret; + + ret = str2uns (s, "header index"); + if (ret >= config.nserverheaders()) + throw static_cast<Error>("Server header index out of range"); + return (ret); +} + +bool str2bool (string const &s, string const &desc) { + int i; + bool ret; + + if (sscanf (s.c_str(), "%d", &i) > 0) + ret = (i != 0); + else if (s == "on") + ret = true; + else if (s == "off") + ret = false; + else + throw static_cast<Error>("Bad ") + desc + " switch '" + s + "'"; + + return (ret); +} + +string decode (string const &s) { + string ret; + + for (char const *cp = s.c_str(); cp && *cp;) { + if (*cp == '%') { + int v; + cp++; + if (sscanf (cp, "%2x", &v)) { + ret += static_cast<char>(v); + cp += 2; + }else { + ret += '%'; + } + } else { + ret += *cp; + cp++; + } + } + + // debugmsg ("Decoded: '" + s + "' into '" + ret + "'\n"); + + return (ret); +} + void Webinterface::answer(Httpbuffer req) { if (req.requestmethod() != Httpbuffer::m_get) throw static_cast<Error>("Only request method GET supported"); string uri = req.requesturi(); - if (uri == "/") + + // Status overview + if (uri == "/") { answer_status(); - else if (uri == "/xslt") + return; + } + + // XSLT request + if (uri == "/xslt") { answer_xslt(); - else { - vector<string> parts = str2parts (uri, '/'); - unsigned ind, num; - - if (parts.size() == 3 && - parts[0] == "server" && parts[1] == "maxconnections" && - sscanf(parts[2].c_str(), "%u", &num) > 0) { - // /server/maxconnections/NUMBER - config.maxconn(num); - answer_status(); - } else if (parts.size() == 4 && - parts[0] == "backend" && - sscanf(parts[1].c_str(), "%u", &ind) > 0 && - ind < balancer.nbackends() && - parts[2] == "weight" && - sscanf(parts[3].c_str(), "%u", &num)) { - // /backend/NR/weight/NUMBER - balancer.backend(ind).weight(num); - answer_status(); - } else if (parts.size() == 4 && - parts[0] == "backend" && - sscanf(parts[1].c_str(), "%u", &ind) > 0 && - ind < balancer.nbackends() && - parts[2] == "maxconnections" && - sscanf(parts[3].c_str(), "%u", &num)) { - // /backend/NR/maxconnections/NUMBER - balancer.backend(ind).maxconn(num); - answer_status(); - } else - throw static_cast<Error>("No action for URI '") + uri; + return; + } + + vector<string> parts = str2parts (uri, '/'); + for (unsigned i = 0; i < parts.size(); i++) + parts[i] = decode(parts[i]); + + // server/buffersize/VALUE + if (parts.size() == 3 && + parts[0] == "server" && parts[1] == "buffersize") { + unsigned sz = str2uns (parts[2], "buffer size"); + if (sz < 1) + throw static_cast<Error>("Buffer size may not be less than 1"); + config.buffersize(sz); + answer_status(); + return; + } + + // /server/maxconnections/NUMBER + if (parts.size() == 3 && + parts[0] == "server" && parts[1] == "maxconnections") { + unsigned num = str2uns (parts[2], "server weight"); + config.maxconn(num); + answer_status(); + return; + } + + // /server/addxrversion/BOOLEAN + if (parts.size() == 3 && + parts[0] == "server" && parts[1] == "addxrversion") { + config.addxrversion (str2bool (parts[2], "addxrversion")); + answer_status(); + return; + } + + // /server/addxforwardedfor/BOOLEAN + if (parts.size() == 3 && + parts[0] == "server" && parts[1] == "addxforwardedfor") { + config.addxforwardedfor (str2bool (parts[2], "addxforwardedfor")); + answer_status(); + return; + } + + // /server/stickyhttp/BOOLEAN + if (parts.size() == 3 && + parts[0] == "server" && parts[1] == "stickyhttp") { + config.stickyhttp (str2bool(parts[2], "stickyhttp")); + answer_status(); + return; + } + + // /server/newheader/NEWHEADER + if ( (parts.size() == 2 || parts.size() == 3) && + (parts[0] == "server" && parts[1] == "newheader") ) { + if (parts.size() == 3) + config.addserverheader(parts[2]); + answer_status(); + return; } + + // /server/changeheader/NR + // /server/changeheader/NR/VALUE + if ( (parts.size() == 3 || parts.size() == 4) && + (parts[0] == "server" && parts[1] == "changeheader") ) { + unsigned ind = headerindex(parts[2]); + if (parts.size() == 3) + config.removeserverheader(ind); + else + config.changeserverheader(ind, parts[3]); + answer_status(); + return; + } + + // /backend/NR/weight/NUMBER + if (parts.size() == 4 && + parts[0] == "backend" && parts[2] == "weight") { + unsigned ind = backendindex(parts[1]); + unsigned num = str2uns (parts[3], "back end weight"); + if (num < 1) + throw static_cast<Error>("Weight may not be less than 1"); + balancer.backend(ind).weight(num); + answer_status(); + return; + } + + // /backend/NR/maxconnections/NUMBER + if (parts.size() == 4 && + parts[0] == "backend" && parts[2] == "maxconnections") { + unsigned ind = backendindex(parts[1]); + unsigned num = str2uns (parts[3], "back end maxconnections"); + balancer.backend(ind).maxconn(num); + answer_status(); + return; + } + + // /backend/NR/hostmatch/EXPRESSION + // /backend/NR/hostmatch + if ( (parts.size() == 3 || parts.size() == 4) && + (parts[0] == "backend" && parts[2] == "hostmatch") ) { + unsigned ind = backendindex(parts[1]); + balancer.backend(ind).hostmatch(parts.size() == 3 ? "" : parts[3]); + answer_status(); + return; + } + + throw static_cast<Error>("No action for URI '") + uri + "'"; } diff --git a/xr/webinterface/answerblob.cc b/xr/webinterface/answerblob.cc @@ -10,18 +10,5 @@ void Webinterface::answer_blob (string const &blob) { "Content-Length: " + cl.str() + "\r\n" "\r\n" + blob; - - unsigned totwritten = 0; - unsigned towrite = resp.size(); - while (totwritten < towrite) { - Fdset set (config.client_timeout()); - set.add (cfd); - if (set.writeable() == cfd) { - ssize_t nwritten = write (cfd, resp.c_str() + totwritten, - towrite - totwritten); - if (nwritten < 1) - throw static_cast<Error>("Write failed"); - totwritten += nwritten; - } - } + fdwrite (cfd, config.client_timeout(), resp.c_str(), resp.size()); } diff --git a/xr/webinterface/answerstatus.cc b/xr/webinterface/answerstatus.cc @@ -14,7 +14,31 @@ void Webinterface::answer_status() { " <server>\n" " <address>" << config.sipaddr() << ":" << config.sport() << "</address>\n" " <type>" << config.stypestr() << "</type>\n" + " <dispatchmode>" << config.dispatchmodestr() << "</dispatchmode>\n" " <maxconnections>" << config.maxconn() << "</maxconnections>\n" + " <clienttimeout>" << config.client_timeout() << "</clienttimeout>\n" + " <backendtimeout>" << config.backend_timeout() << "</backendtimeout>\n" + " <buffersize>" << config.buffersize() << "</buffersize>\n" + " <checks>\n" + " <wakeupinterval>" << config.wakeupsec() << "</wakeupinterval>\n" + " <checkupinterval>" << config.checkupsec() << "</checkupinterval>\n" + " </checks>\n" + " <http>\n" + " <addxrversion>" << config.addxrversion() << "</addxrversion>\n" + " <addxforwardedfor>" << config.addxforwardedfor() << "</addxforwardedfor>\n" + " <stickyhttp>" << config.stickyhttp() << "</stickyhttp>\n" + " <serverheaders>\n" + ; + for (unsigned i = 0; i < config.nserverheaders(); i++) + o << + " <serverheader>\n" + " <nr>" << i << "</nr>\n" + " <header>" << config.serverheader(i) << "</header>\n" + " </serverheader>\n" + ; + o << + " </serverheaders>\n" + " </http>\n" " </server>\n" ; @@ -30,10 +54,11 @@ void Webinterface::answer_status() { " <connections>" << balancer.backend(i).connections() << "</connections>\n" " <bytesserved>" << balancer.backend(i).bytesserved() << "</bytesserved>\n" " <clientsserved>" << balancer.backend(i).clientsserved() << "</clientsserved>\n" + " <hostmatch>" << balancer.backend(i).hostmatch() << "</hostmatch>\n" " </backend>\n" ; o << - "</status>\n"; + "</status>\n\n"; answer_blob (o.str()); } diff --git a/xr/webinterface/execute.cc b/xr/webinterface/execute.cc @@ -1,11 +1,26 @@ #include "webinterface" void Webinterface::execute() { - int sfd = serversocket (config.webinterfaceip(), config.webinterfaceport(), - "web interface"); + int sfd; + + // Create the server socket, or retry infinitely. + while (true) { + try { + msg ("Starting web interface\n"); + sfd = serversocket (config.webinterfaceip(), + config.webinterfaceport(), + "web interface"); + } catch (Error const &e) { + cerr << e.what() << "(webinterface, retrying in a sec)\n"; + sleep (1); + continue; + } + break; + } + ostringstream o; o << sfd; - msg ("Web interface listening on socket " + o.str() + "\n"); + msg ("Web interface started on socket " + o.str() + "\n"); while (!balancer.terminate()) { try { @@ -20,8 +35,17 @@ void Webinterface::execute() { socketclose(cfd); } } catch (Error const &e) { + cerr << e.what() << " (webinterface)\n"; + string err = static_cast<string> + ("HTTP/1.0 500 Server Error\r\n") + + "X-Reason: " + e.what() + "\r\n" + "Content-Length: 0\r\n" + "\r\n"; + try { + fdwrite(cfd, config.client_timeout(), err.c_str(), err.size()); + } catch (...) { + } socketclose(cfd); - cerr << "Webinterface: " << e.what() << "\n"; } } msg ("Web interface stopping.\n");