commit 5c6aaa5c2c76faffd499a2aa7fb6d31988d16a45
parent 3bb3099779ff845dc1e34c7112bcf08bbef357ff
Author: finwo <finwo@pm.me>
Date: Sat, 3 Jan 2026 19:10:59 +0100
1.12
Diffstat:
84 files changed, 2336 insertions(+), 1240 deletions(-)
diff --git a/ChangeLog b/ChangeLog
@@ -1,6 +1,43 @@
ChangeLog for Crossroads
------------------------------------------------------------------------------
+1.12 [KK 2006-09-13] Small changes in wakeup handler. Timeout handling
+ in HTTP service processing slightly changed.
+ [KK 2006-09-15] Added checks whether both client and server want
+ persisting HTTP connections (see http_serve()). Header
+ Proxy-Connection: close is inserted if the connections should get
+ closed. Rewrote network to- and fro- copying: net_copy() now
+ replaces net_read() + net_write().
+ [KK 2006-09-26] Promoted to STABLE.
+
+1.11 [KK 2006-09-12] HTTP type services now analyze not only the first
+ request/response pair to apply header magic; Subsequent pairs are
+ also analyzed.
+
+1.10 [KK 2006-09-11] Small speedup in header reading. Commandline
+ option "restart" will now act as "start" if no Crossroads is
+ found running. Rewrote chunked transfer-encoding transmission in
+ HTTP type services. Messaging now shows the type of message with
+ a prefix: ERROR, WARNING or INFO.
+
+1.09 [KK 2006-09-05] Small changes for HP-UX port. Crossroads should
+ now compile on HP-UX 11.23 / ia64. (Thanks, Bernd Krumboeck):
+ - gcc selected if both gcc and cc are present (c-conf 1.04)
+ - conditional compilation of vsyslog() function
+ [KK 2006-09-06] Neater support of streamed HTTP transfer
+ (eg. video); data is sent instantly to the client.
+
+1.08 [KK 2006-08-28] Next development (trunk) version.
+ [KK 2006-08-29] Implemented net_read() / net_write() so that
+ socket reads and writes are centralized, with their logging and
+ error handling. HTTP transfer buffers are now 'unsigned
+ char'. Shell script test/runtest added to drive sanity checks.
+ [KK 2006-08-31] Rewrote HTTPP message parsing (http_msg_*()).
+ Added directives 6 header modification directives. Lots of code
+ cleanups. The HTTP 502 error text (proxy error) is now in a
+ separate text file. Byte and second counts of 'crossroads status'
+ are now correctly displayed. Documentation updated.
+
1.07 [KK 2006-07-25] Next development (trunk) version. Directive
'insertrealip' implemented. Docs updated. While creating docs,
leading tabs are now expanded (needed for PDF documentation).
diff --git a/doc/ANNOUNCEMENT b/doc/ANNOUNCEMENT
@@ -1,19 +0,0 @@
-Announcing: Crossroads V0.26
-
-Crossroads is a load balance and fail over utility for TCP based
-services. It is a daemon program running in user space, and features
-extensive configurability, polling of back ends using 'wakeup calls',
-detailed status reporting, 'hooks' for special actions when backend
-calls fail, and much more. Crossroads is service-independent: it is
-usable for HTTP(S), SSH, SMTP, DNS, etc. In the case of HTTP
-balancing, Crossroads can provide 'session stickiness' for back-end
-processes that need sessions, but aren't session-aware of other back-ends.
-
-For more information, please visit:
-- http://public.e-tunity.com/crossroads/crossroads.html
- (description, extensive documentation, installation guide)
-- http://public.e-tunity.com/crossroads/crossroads-latest.tar.gz
- (tarball source)
-- http://reshmeat.net/projects/crossr/ (Freshmeat project page)
-
-Feedback is appreciated!
diff --git a/doc/Makefile b/doc/Makefile
@@ -1,7 +1,7 @@
include ../etc/Makefile.def
foo:
- perl ../tools/untab *.yo
+ ../tools/untab *.yo
yo2html -dVER=$(VER) crossroads
yo2man -dVER=$(VER) crossroads
yo2pdf -dVER=$(VER) crossroads
diff --git a/doc/compiling.yo b/doc/compiling.yo
@@ -62,16 +62,16 @@ itemization(
destination directory for your PDF manuals;
it() Optionally, tt(cp doc/crossroads.man)
- em(manualdirectory)tt(/man1/crossroads.1), where
- em(manualdirectory) is e.g. tt(/usr/man),
- tt(/usr/share), tt(/usr/local/man),
- tt(/usr/local/share). Any possibility is valid, as
- long as em(manualdirectory) has a subdirectory
- tt(man1/);
+ em(manualdirectory)tt(/crossroads.1), where
+ em(manualdirectory) is e.g. tt(/usr/man/man1),
+ tt(/usr/share/man1), tt(/usr/local/man/man1),
+ tt(/usr/local/share/man1). Any possibility is valid, as
+ long as em(manualdirectory) is one of the directories
+ where manual pages are stored;
it() If your manual page system supports compressed
manual pages, then you can save some space with
- tt(gzip) em(manualdirectory)tt(/man1/crossroads.1).)
+ tt(gzip) em(manualdirectory)tt(/crossroads.1).)
)
diff --git a/doc/config.yo b/doc/config.yo
@@ -310,7 +310,13 @@ service myservice {
Each service definition must have at least one backend
definition. There may be more (and probably will, if you want
balancing and fail over) as long as the backend names differ.
-The statements in the backend definition blocks are:
+The statements in the backend definition blocks are described in the
+following sections.
+
+subsubsect(General Backend Directives)
+
+The following directives are used in all types of services (tt(any) or
+tt(http)). HTTP-specific directives are shown in section ref(httpdirectives).
startdit()
@@ -395,69 +401,8 @@ startdit()
it() Syntax: tt(decay) em(number) tt(;)
it() where em(number) is a percentage that decreases the back
data when other back ends are hit
- it() Default: 0, meaning that no decay is applied to usage statistics.)
-
- dit(HTTP cookie handling:) When the service type is tt(http),
- then backends may define HTTP cookies to identify sessions. There
- are two directives to accomplish this: tt(stickycookie) and
- tt(insertcookie).
-
- The directive tt(insertcookie) is used to insert a cookie into the
- HTTP stream when crossroads assigns a back end to a new session.
-
- itemization(
- it() Syntax: tt(insertcookie) em(cookiestring) tt(;)
- it() Default: none)
-
- The directive tt(stickycookie) is used by crossroads to determine
- whether a HTTP request is part of an established session. If so, the
- request is sent to the appropriate back end, without balancing
- considerations.
-
- itemization(
- it() Syntax: tt(stickycookie) em(cookiestring) tt(;)
- it() Default: none)
-
- It should be obvious that the em(cookiestrings) in the two above
- directives should match: the cookie that's initially inserted by
- tt(insertcookie) must be found in a subsequent connection by
- tt(stickycookie), otherwise the session won't stick. Here's a small
- example:
-
- verb(\
-service www {
- port 80;
- type http;
- backend one {
- server 10.0.0.1:80;
- stickycookie BalancerID=a;
- insertcookie "BalancerID=a; Path=/";
- }
- backend two {
- server 10.0.0.2:80;
- stickycookie BalancerID=b;
- insertcookie "BalancerID=b; Path=/";
- }
-})
-
- Note also the quoting of the cookiestring in the tt(insertcookie)
- directive. Without it, the semicolon in the string would break the
- directive.
-
- dit(Passing of real IP address:) When the service type is
- tt(http), the directive tt(insertrealip) can be used to force
- Crossroads to insert the client's real IP address as an HTTP
- header. The back end may then inspect the header.
-
- itemization(
- it() Syntax: tt(insertrealip) em(setting) tt(;)
- it() where em(setting) is tt(true), tt(yes) or tt(on) to turn
- on this feature; or tt(false), tt(no) or tt(off) to turn it
- off.
- it() Default: tt(off))
-
- When turned on, the client's IP address is stored in an HTTP
- header named tt(XR-Real-IP).
+ it() Default: 0, meaning that no decay is applied to usage
+ statistics.)
dit(Event triggers:) As special 'hooks' for actions, two triggers
are available: tt(onfailure) and tt(onsuccess). The argument to
@@ -591,7 +536,166 @@ client ----<----<----<---< crossroads ====<====<====<
it() Syntax: tt(throughputlog) em(filename) tt(;)
it() There is no default. Without this directive, the
throughput is not logged.)
+enddit()
+
+subsubsect(HTTP-related Backend Directives) label(httpdirectives)
+
+The following directives are specific for HTTP-type services; i.e.,
+services with a specification tt(type http).
+
+It is inevitable that when Crossroads handles services of tt(type
+http), more processing is necessary. Crossroads has to unpack the TCP
+payload in order to do its header magic; which leads to performance
+impact.
+
+startdit()
+
+ dit(Session Stickiness:) The directive tt(stickycookie) em(value)
+ causes Crossroads to unpack clients' requests, to check for
+ em(value) in the cookies. When found, the message is routed to the
+ back end having the appropriate tt(stickycookie) directive.
+
+ E.g., consider the following configuration:
+
+ verb(\
+service ... {
+ ...
+ backend one {
+ ...
+ stickycookie "BalancerID=first";
+ }
+ backend two {
+ ...
+ stickycookie "BalancerID=second";
+ }
+})
+
+ When clients' messages contain cookies named tt(BalancerID) with
+ the value tt(first), then such messages are routed to backend
+ tt(one). When the value is tt(second) then they are routed to the
+ backend tt(two).
+
+ There are basically to provide such cookies to a browser. First, a
+ back end can insert such a cookie into the HTTP response. E.g.,
+ the webserver of back end tt(one) might insert a cookie named
+ tt(BalancerID), having value tt(first).
+ Second, Crossroads can insert such cookies using a carefully
+ crafted directive tt(addclientheader). See below.
+
+ dit(Header modification:) Crossroads understands the following
+ header modification directives: tt(addclientheader),
+ tt(appendclientheader), tt(setclientheader), tt(addserverheader),
+ tt(appendserverheader), tt(setserverheader).
+
+ The directive names always consist of
+ em(Action)em(Destination)tt(header), where:
+
+ itemization(
+ it() The action is tt(add), tt(append) or tt(insert).
+
+ itemization(
+ it() Action tt(add) adds a header, even when headers with
+ the same name already are present in an HTTP
+ message. Adding headers is useful for e.g. tt(Set-Cookie)
+ headers; a message may contain several of such headers.
+
+ it() Action tt(append) adds a header if it isn't present
+ yet in an HTTP message. If such a header is already
+ present, then the value is appended to the pre-existing
+ header. This is useful for e.g. tt(Via) headers. Imagine
+ an HTTP message with a header tt(Via: someproxy). Then the
+ directive tt(appendclientheader "Via: crossroads") will
+ rewrite the header to tt(Via: someproxy; crossroads).
+
+ it() Action tt(set) overwrites headers with the same
+ name; or adds a new header if no pre-existing is found.
+ This is useful for e.g. tt(Host) headers.)
+
+ it() The destination is one of tt(client) or tt(server). When
+ the destination is tt(server), then Crossroads will apply such
+ directives to HTTP messages that originate from the browser
+ and are being forwarded to back ends. When the destination is
+ tt(client), then Crossroads will apply such directives to
+ backend responses that are shuttled to the browser.)
+
+ The syntax of the directives is e.g. tt(addclientheader
+ "X-Processed-By: Crossroads";). The directives expect one
+ argument; a string, consisting of a header name, a colon, and a
+ header value. The directive ends with a semicolon.
+
+ The header value may contain one of the following formatting
+ directives:
+
+ itemization(
+ it() tt(%r) is expanded to the real IP address of a client;
+ it() tt(%t) is expanded to a timestamp of the local time;
+ it() tt(%T) is expanded to a timestamp of Greenwich Mean Time;
+ it() tt(%v) is expanded to the Crossroads version;
+ it() tt(%)em(x) (where em(x) is any other character) is
+ expanded to em(x). E.g., tt(%%) is a literal % sign.)
+
+ dit(Common Uses)
+
+ The following examples show common uses of header modifications.
+ description(
+ dit(Enforcing session stickiness:) By combining
+ tt(stickycookie) and tt(addclientheader), HTTP session
+ stickiness is enforced. Consider the following configuration:
+
+ verb(\
+service ... {
+ ...
+ backend one {
+ ...
+ addclientheader "Set-Cookie: BalancerID=first; path=/";
+ stickycookie "BalancerID=first";
+ }
+ backend two {
+ ...
+ addclientheader "Set-Cookie: BalancerID=second; path=/";
+ stickycookie "BalancerID=second";
+ }
+})
+
+ The first request of an HTTP session is balanced to either
+ backend tt(one) or tt(two). The server response is enriched
+ using tt(addclientheader) with an appropriate cookie. A
+ subsequent request from the same browser now has that cookie
+ in place; and is therefore sent to the same back end where the
+ its predecessors went.
+
+ dit(Hiding the server software version:) Many servers
+ (e.g. Apache) advertize their version, as in tt(Server: Apache
+ 1.27). This potentially provides information to attackers. The
+ following configuration hides such information:
+
+ verb(\
+service ... {
+ ...
+ backend one {
+ ...
+ setclientheader "Server: WWW-Server";
+ }
+})
+
+ dit(Informing the server of the clients' IP address:) Since
+ Crossroads sits 'in the middle' between a client and a back
+ end, the back end perceives Crossroads as its client. The
+ following sends the true clients' IP address to the server, in
+ a header tt(X-Real-IP):
+
+ verb(\
+service ... {
+ ...
+ backend one {
+ ...
+ setserverheader "X-Real-IP: %r";
+ }
+})
+
+ )
+
enddit()
diff --git a/doc/crossroads.html b/doc/crossroads.html
@@ -1,12 +1,12 @@
<a name="defs.yo"></a><html><head>
-<title>Crossroads 1.07</title>
+<title>Crossroads 1.12</title>
<link rel="stylesheet" type="text/css" href="http://www.e-tunity.com/css/yodl.css">
<link rel="stylesheet" type="text/css" href="http://www.e-tunity.com/css/yodl.css">
<link rev="made" href="mailto:info@e-tunity.com">
</head>
<body>
<hr>
-<h1>Crossroads 1.07</h1>
+<h1>Crossroads 1.12</h1>
<h2>Karel Kubat</h2>
<h2>e-tunity</h2><h2>2005, 2006, ff.</h2>
@@ -18,7 +18,8 @@
'hooks' for special actions when backend calls fail, and much
more. Crossroads is service-independent: it is usable for
HTTP/HTTPS, SSH, SMTP, DNS, etc. In the case of HTTP
- balancing, Crossroads can provide 'session stickiness' for
+ balancing, Crossroads can modify HTTP headers, e.g. to
+ provide 'session stickiness' for
back-end processes that need sessions, but aren't
session-aware of other back-ends.</em></blockquote>
@@ -31,71 +32,77 @@
<dt><a href="#l3">1.2: Copyright and Disclaimer</a></dt>
<dt><a href="#l4">1.3: Terminology</a></dt>
<dt><a href="#l5">1.4: Porting issues for pre-0.26 installations</a></dt>
+<dt><a href="#l6">1.5: Porting issues for pre-1.08 installations</a></dt>
</dl>
-<dt><h3><a href="#l6">2: Installation for the impatient</a></h3></dt>
-<dt><h3><a href="#l7">3: Using Crossroads</a></h3></dt>
+<dt><h3><a href="#l7">2: Installation for the impatient</a></h3></dt>
+<dt><h3><a href="#l8">3: Using Crossroads</a></h3></dt>
<dl>
-<dt><a href="#l8">3.1: Logging-related options</a></dt>
+<dt><a href="#l9">3.1: General Commandline Syntax</a></dt>
+<dt><a href="#l10">3.2: Logging-related options</a></dt>
</dl>
-<dt><h3><a href="#l9">4: The configuration</a></h3></dt>
+<dt><h3><a href="#l11">4: The configuration</a></h3></dt>
<dl>
-<dt><a href="#l10">4.1: General language elements</a></dt>
+<dt><a href="#l12">4.1: General language elements</a></dt>
<dl>
-<dt><a href="#l11">4.1.1: Empty lines and comments</a></dt>
-<dt><a href="#l12">4.1.2: Keywords, numbers, identifiers, generic strings</a></dt>
+<dt><a href="#l13">4.1.1: Empty lines and comments</a></dt>
+<dt><a href="#l14">4.1.2: Keywords, numbers, identifiers, generic strings</a></dt>
+</dl>
+<dt><a href="#l15">4.2: Service definitions</a></dt>
+<dt><a href="#l16">4.3: Backend definitions</a></dt>
+<dl>
+<dt><a href="#l17">4.3.1: General Backend Directives</a></dt>
+<dt><a href="#l18">4.3.2: HTTP-related Backend Directives</a></dt>
</dl>
-<dt><a href="#l13">4.2: Service definitions</a></dt>
-<dt><a href="#l14">4.3: Backend definitions</a></dt>
</dl>
-<dt><h3><a href="#l15">5: Tips, Tricks and Remarks</a></h3></dt>
+<dt><h3><a href="#l19">5: Tips, Tricks and Remarks</a></h3></dt>
<dl>
-<dt><a href="#l16">5.1: How back ends are selected in load balancing</a></dt>
+<dt><a href="#l20">5.1: How back ends are selected in load balancing</a></dt>
<dl>
-<dt><a href="#l17">5.1.1: Bysize, byduration or byconnections?</a></dt>
-<dt><a href="#l18">5.1.2: Averaging size and duration</a></dt>
-<dt><a href="#l19">5.1.3: Specifying decays</a></dt>
-<dt><a href="#l20">5.1.4: Adjusting the weights</a></dt>
-<dt><a href="#l21">5.1.5: Throttling the number of concurrent connections</a></dt>
+<dt><a href="#l21">5.1.1: Bysize, byduration or byconnections?</a></dt>
+<dt><a href="#l22">5.1.2: Averaging size and duration</a></dt>
+<dt><a href="#l23">5.1.3: Specifying decays</a></dt>
+<dt><a href="#l24">5.1.4: Adjusting the weights</a></dt>
+<dt><a href="#l25">5.1.5: Throttling the number of concurrent connections</a></dt>
</dl>
-<dt><a href="#l22">5.2: HTTP Session Stickiness</a></dt>
+<dt><a href="#l26">5.2: HTTP Session Stickiness</a></dt>
<dl>
-<dt><a href="#l23">5.2.1: Don't use stickiness!</a></dt>
-<dt><a href="#l24">5.2.2: But if you must..</a></dt>
+<dt><a href="#l27">5.2.1: Don't use stickiness!</a></dt>
+<dt><a href="#l28">5.2.2: But if you must..</a></dt>
</dl>
-<dt><a href="#l25">5.3: Passing the client's IP address</a></dt>
+<dt><a href="#l29">5.3: Passing the client's IP address</a></dt>
<dl>
-<dt><a href="#l26">5.3.1: Sample Crossroads configuration</a></dt>
-<dt><a href="#l27">5.3.2: Sample Apache configuration</a></dt>
+<dt><a href="#l30">5.3.1: Sample Crossroads configuration</a></dt>
+<dt><a href="#l31">5.3.2: Sample Apache configuration</a></dt>
</dl>
-<dt><a href="#l28">5.4: Configuration examples</a></dt>
+<dt><a href="#l32">5.4: Configuration examples</a></dt>
<dl>
-<dt><a href="#l29">5.4.1: A load balancer for three webserver back ends</a></dt>
-<dt><a href="#l30">5.4.2: An HTTP forwarder when travelling</a></dt>
-<dt><a href="#l31">5.4.3: SSH login with enforced idle logout</a></dt>
+<dt><a href="#l33">5.4.1: A load balancer for three webserver back ends</a></dt>
+<dt><a href="#l34">5.4.2: An HTTP forwarder when travelling</a></dt>
+<dt><a href="#l35">5.4.3: SSH login with enforced idle logout</a></dt>
</dl>
</dl>
-<dt><h3><a href="#l32">6: Benchmarking</a></h3></dt>
+<dt><h3><a href="#l36">6: Benchmarking</a></h3></dt>
<dl>
-<dt><a href="#l33">6.1: Benchmark 1: Accessing a proxy via crossroads or directly</a></dt>
+<dt><a href="#l37">6.1: Benchmark 1: Accessing a proxy via crossroads or directly</a></dt>
<dl>
-<dt><a href="#l34">6.1.1: Results</a></dt>
-<dt><a href="#l35">6.1.2: Discussion</a></dt>
+<dt><a href="#l38">6.1.1: Results</a></dt>
+<dt><a href="#l39">6.1.2: Discussion</a></dt>
</dl>
-<dt><a href="#l36">6.2: Benchmark 2: Crossroads versus Linux Virtual Server (LVS)</a></dt>
+<dt><a href="#l40">6.2: Benchmark 2: Crossroads versus Linux Virtual Server (LVS)</a></dt>
<dl>
-<dt><a href="#l37">6.2.1: Environment</a></dt>
-<dt><a href="#l38">6.2.2: Tests and results</a></dt>
+<dt><a href="#l41">6.2.1: Environment</a></dt>
+<dt><a href="#l42">6.2.2: Tests and results</a></dt>
</dl>
</dl>
-<dt><h3><a href="#l39">7: Compiling and Installing</a></h3></dt>
+<dt><h3><a href="#l43">7: Compiling and Installing</a></h3></dt>
<dl>
-<dt><a href="#l40">7.1: Prerequisites</a></dt>
-<dt><a href="#l41">7.2: Compiling and installing</a></dt>
-<dt><a href="#l42">7.3: Configuring crossroads</a></dt>
-<dt><a href="#l43">7.4: A boot script</a></dt>
+<dt><a href="#l44">7.1: Prerequisites</a></dt>
+<dt><a href="#l45">7.2: Compiling and installing</a></dt>
+<dt><a href="#l46">7.3: Configuring crossroads</a></dt>
+<dt><a href="#l47">7.4: A boot script</a></dt>
<dl>
-<dt><a href="#l44">7.4.1: SysV Style Startup</a></dt>
-<dt><a href="#l45">7.4.2: BSD Style Startup</a></dt>
+<dt><a href="#l48">7.4.1: SysV Style Startup</a></dt>
+<dt><a href="#l49">7.4.2: BSD Style Startup</a></dt>
</dl>
<p><hr><p>
@@ -233,6 +240,10 @@ using the terms here in a very strict sense.)
screens. E.g., a website registration dialog may involve 3
screens that when called from the same browser,
form a logical group of some sort.
+ <p><dt><strong>Headers</strong><dd> or <strong>header lines</strong> are specific parts of an HTTP
+ message. Crossroads has directives to add or modify
+ headers that are part of the request that a browser sends
+ to server, or those that are part of the server.
<p><dt><strong>Session stickiness</strong><dd> means that when a browser starts an
HTTP dialog, the balancer makes sure that it 'sticks' to
the same back end (i.e., subsequent requests from the
@@ -276,6 +287,27 @@ Therefore when converting configuration files to the new syntax,
TCP connection.)
<p>
<a name="l6"></a>
+<h3>1.5: Porting issues for pre-1.08 installations</h3>
+<p>
+As of version 1.08, the following directives no longer are
+ supported:
+<p>
+<ul>
+ <li> <code>insertstickycookie</code> was replaced by the more generic
+ directive <code>addclientheader</code>. E.g., instead of <br>
+ <code>insertstickycookie "XRID=100; Path=/";</code> <br>
+ the syntax is now <br>
+ <code>addclientheader "Set-Cookie: XRID=100; Path=/";</code>
+<p>
+<li> <code>insertrealip</code> was replaced by the more generic
+ directive <code>setserverheader</code>. E.g., instead of <br>
+ <code>insertrealip on;</code> <br>
+ the syntax is now <br>
+ <code>setserverheader "XR-Real-IP: %r";</code> <br>
+ This incidentally also makes it possible to change the header
+ name (here: <code>XR-Real-IP</code>).</ul>
+<p>
+<a name="l7"></a>
<h2>2: Installation for the impatient</h2>
<a name="impatient"></a>
For the impatient, here's the very-quick-but-very-superficial recipy
@@ -345,7 +377,7 @@ That's off course assuming that you want to balance HTTP on
status</code>.
</ul>
<p>
-<a name="l7"></a>
+<a name="l8"></a>
<h2>3: Using Crossroads</h2>
<a name="using"></a>Crossroads is started from the commandline, and highly depends on
<code>/etc/crossroads.conf</code> (the default configuration file). It
@@ -354,6 +386,9 @@ configuration file). The actual usage information is always obtained
by typing <code>crossroads</code> without any arguments. Crossroads then
displays the allowed arguments.
<p>
+<a name="l9"></a>
+<h3>3.1: General Commandline Syntax</h3>
+<p>
This section shows the most basic usage. As said above, start
<code>crossroads</code> without arguments to view the full listing of options.
<p>
@@ -387,8 +422,8 @@ This section shows the most basic usage. As said above, start
configuration <code>/etc/crossroads.conf</code>.
</ul>
<p>
-<a name="l8"></a>
-<h3>3.1: Logging-related options</h3>
+<a name="l10"></a>
+<h3>3.2: Logging-related options</h3>
<p>
Two 'flags' of Crossroads are specifically logging-related. This
section elaborates on these flags.
@@ -430,7 +465,7 @@ That instructs <code>syslogd</code> to send <code>LOG_LOCAL7</code> requests to
<li> Finally, monitor <code>/var/log/crossroads.log</code> for Crossroads'
messages.</ul>
<p>
-<a name="l9"></a>
+<a name="l11"></a>
<h2>4: The configuration</h2>
<a name="config"></a>The configuration that crossroads uses is normally stored in the file
<code>/etc/crossroads.conf</code>. This location can be overruled using the
@@ -439,13 +474,13 @@ command line flag <code>-c</code>.
This section explains the syntax of the configuration file, and what
all settings do.
<p>
-<a name="l10"></a>
+<a name="l12"></a>
<h3>4.1: General language elements</h3>
<p>
This section describes the general elements of the crossroads
configuration language.
<p>
-<a name="l11"></a>
+<a name="l13"></a>
<strong>4.1.1: Empty lines and comments</strong>
<p>
Empty lines are of course allowed in the
@@ -463,7 +498,7 @@ best'. (I favor C or C++ comment. My favorite editor <em>emacs</em>
can be put in <code>cmode</code> and nicely highlight what's comment and what's
not. And as a bonus it will auto-indent the configuration!)
<p>
-<a name="l12"></a>
+<a name="l14"></a>
<strong>4.1.2: Keywords, numbers, identifiers, generic strings</strong>
<p>
In a configuration file, statements are identified by <em>keywords</em>,
@@ -498,7 +533,7 @@ Finally, an argument can be a 'boolean' value. Crossroads knows
<code>true</code>, <code>yes</code> and <code>on</code> all mean the same and can be used
interchangeably; as can the keywords <code>false</code>, <code>no</code> and <code>off</code>.
<p>
-<a name="l13"></a>
+<a name="l15"></a>
<h3>4.2: Service definitions</h3>
<p>
Service definitions are blocks in the configuration file that
@@ -721,7 +756,7 @@ is exceeded.
<p>
</dl>
<p>
-<a name="l14"></a>
+<a name="l16"></a>
<h3>4.3: Backend definitions</h3>
<p>
Inside the service definitions as are described in the previous
@@ -749,7 +784,14 @@ service myservice {
Each service definition must have at least one backend
definition. There may be more (and probably will, if you want
balancing and fail over) as long as the backend names differ.
-The statements in the backend definition blocks are:
+The statements in the backend definition blocks are described in the
+following sections.
+<p>
+<a name="l17"></a>
+<strong>4.3.1: General Backend Directives</strong>
+<p>
+The following directives are used in all types of services (<code>any</code> or
+<code>http</code>). HTTP-specific directives are shown in section <a href="crossroads.html#httpdirectives">4.3.2</a>.
<p>
<dl>
<p>
@@ -834,71 +876,8 @@ This means that when a given back end is hit, then its usage data
<li> Syntax: <code>decay</code> <em>number</em> <code>;</code>
<li> where <em>number</em> is a percentage that decreases the back
data when other back ends are hit
- <li> Default: 0, meaning that no decay is applied to usage statistics.</ul>
-<p>
-<p><dt><strong>HTTP cookie handling:</strong><dd> When the service type is <code>http</code>,
- then backends may define HTTP cookies to identify sessions. There
- are two directives to accomplish this: <code>stickycookie</code> and
- <code>insertcookie</code>.
-<p>
-The directive <code>insertcookie</code> is used to insert a cookie into the
- HTTP stream when crossroads assigns a back end to a new session.
-<p>
-<ul>
- <li> Syntax: <code>insertcookie</code> <em>cookiestring</em> <code>;</code>
- <li> Default: none</ul>
-<p>
-The directive <code>stickycookie</code> is used by crossroads to determine
- whether a HTTP request is part of an established session. If so, the
- request is sent to the appropriate back end, without balancing
- considerations.
-<p>
-<ul>
- <li> Syntax: <code>stickycookie</code> <em>cookiestring</em> <code>;</code>
- <li> Default: none</ul>
-<p>
-It should be obvious that the <em>cookiestrings</em> in the two above
- directives should match: the cookie that's initially inserted by
- <code>insertcookie</code> must be found in a subsequent connection by
- <code>stickycookie</code>, otherwise the session won't stick. Here's a small
- example:
-<p>
-<pre>
-service www {
- port 80;
- type http;
- backend one {
- server 10.0.0.1:80;
- stickycookie BalancerID=a;
- insertcookie "BalancerID=a; Path=/";
- }
- backend two {
- server 10.0.0.2:80;
- stickycookie BalancerID=b;
- insertcookie "BalancerID=b; Path=/";
- }
-}
-</pre>
-
-<p>
-Note also the quoting of the cookiestring in the <code>insertcookie</code>
- directive. Without it, the semicolon in the string would break the
- directive.
-<p>
-<p><dt><strong>Passing of real IP address:</strong><dd> When the service type is
- <code>http</code>, the directive <code>insertrealip</code> can be used to force
- Crossroads to insert the client's real IP address as an HTTP
- header. The back end may then inspect the header.
-<p>
-<ul>
- <li> Syntax: <code>insertrealip</code> <em>setting</em> <code>;</code>
- <li> where <em>setting</em> is <code>true</code>, <code>yes</code> or <code>on</code> to turn
- on this feature; or <code>false</code>, <code>no</code> or <code>off</code> to turn it
- off.
- <li> Default: <code>off</code></ul>
-<p>
-When turned on, the client's IP address is stored in an HTTP
- header named <code>XR-Real-IP</code>.
+ <li> Default: 0, meaning that no decay is applied to usage
+ statistics.</ul>
<p>
<p><dt><strong>Event triggers:</strong><dd> As special 'hooks' for actions, two triggers
are available: <code>onfailure</code> and <code>onsuccess</code>. The argument to
@@ -1040,16 +1019,184 @@ Summarizing, the throughput times of a client-back end connection
<li> Syntax: <code>throughputlog</code> <em>filename</em> <code>;</code>
<li> There is no default. Without this directive, the
throughput is not logged.</ul>
+</dl>
+<p>
+<a name="l18"></a>
+<strong>4.3.2: HTTP-related Backend Directives</strong> <a name="httpdirectives"></a>
+<p>
+The following directives are specific for HTTP-type services; i.e.,
+services with a specification <code>type http</code>.
+<p>
+It is inevitable that when Crossroads handles services of <code>type
+http</code>, more processing is necessary. Crossroads has to unpack the TCP
+payload in order to do its header magic; which leads to performance
+impact.
+<p>
+<dl>
+<p>
+<p><dt><strong>Session Stickiness:</strong><dd> The directive <code>stickycookie</code> <em>value</em>
+ causes Crossroads to unpack clients' requests, to check for
+ <em>value</em> in the cookies. When found, the message is routed to the
+ back end having the appropriate <code>stickycookie</code> directive.
+<p>
+E.g., consider the following configuration:
+<p>
+<pre>
+service ... {
+ ...
+ backend one {
+ ...
+ stickycookie "BalancerID=first";
+ }
+ backend two {
+ ...
+ stickycookie "BalancerID=second";
+ }
+}
+</pre>
+
+<p>
+When clients' messages contain cookies named <code>BalancerID</code> with
+ the value <code>first</code>, then such messages are routed to backend
+ <code>one</code>. When the value is <code>second</code> then they are routed to the
+ backend <code>two</code>.
+<p>
+There are basically to provide such cookies to a browser. First, a
+ back end can insert such a cookie into the HTTP response. E.g.,
+ the webserver of back end <code>one</code> might insert a cookie named
+ <code>BalancerID</code>, having value <code>first</code>.
+ Second, Crossroads can insert such cookies using a carefully
+ crafted directive <code>addclientheader</code>. See below.
+<p>
+<p><dt><strong>Header modification:</strong><dd> Crossroads understands the following
+ header modification directives: <code>addclientheader</code>,
+ <code>appendclientheader</code>, <code>setclientheader</code>, <code>addserverheader</code>,
+ <code>appendserverheader</code>, <code>setserverheader</code>.
+<p>
+The directive names always consist of
+ <em>Action</em><em>Destination</em><code>header</code>, where:
+<p>
+<ul>
+ <li> The action is <code>add</code>, <code>append</code> or <code>insert</code>.
+<p>
+<ul>
+ <li> Action <code>add</code> adds a header, even when headers with
+ the same name already are present in an HTTP
+ message. Adding headers is useful for e.g. <code>Set-Cookie</code>
+ headers; a message may contain several of such headers.
+<p>
+<li> Action <code>append</code> adds a header if it isn't present
+ yet in an HTTP message. If such a header is already
+ present, then the value is appended to the pre-existing
+ header. This is useful for e.g. <code>Via</code> headers. Imagine
+ an HTTP message with a header <code>Via: someproxy</code>. Then the
+ directive <code>appendclientheader "Via: crossroads"</code> will
+ rewrite the header to <code>Via: someproxy; crossroads</code>.
+<p>
+<li> Action <code>set</code> overwrites headers with the same
+ name; or adds a new header if no pre-existing is found.
+ This is useful for e.g. <code>Host</code> headers.</ul>
+<p>
+<li> The destination is one of <code>client</code> or <code>server</code>. When
+ the destination is <code>server</code>, then Crossroads will apply such
+ directives to HTTP messages that originate from the browser
+ and are being forwarded to back ends. When the destination is
+ <code>client</code>, then Crossroads will apply such directives to
+ backend responses that are shuttled to the browser.</ul>
+<p>
+The syntax of the directives is e.g. <code>addclientheader
+ "X-Processed-By: Crossroads";</code>. The directives expect one
+ argument; a string, consisting of a header name, a colon, and a
+ header value. The directive ends with a semicolon.
+<p>
+The header value may contain one of the following formatting
+ directives:
+<p>
+<ul>
+ <li> <code>%r</code> is expanded to the real IP address of a client;
+ <li> <code>%t</code> is expanded to a timestamp of the local time;
+ <li> <code>%T</code> is expanded to a timestamp of Greenwich Mean Time;
+ <li> <code>%v</code> is expanded to the Crossroads version;
+ <li> <code>%</code><em>x</em> (where <em>x</em> is any other character) is
+ expanded to <em>x</em>. E.g., <code>%%</code> is a literal % sign.</ul>
+<p>
+<p><dt><strong>Common Uses</strong><dd>
+<p>
+The following examples show common uses of header modifications.
+<p>
+<dl>
+ <p><dt><strong>Enforcing session stickiness:</strong><dd> By combining
+ <code>stickycookie</code> and <code>addclientheader</code>, HTTP session
+ stickiness is enforced. Consider the following configuration:
+<p>
+<pre>
+service ... {
+ ...
+ backend one {
+ ...
+ addclientheader "Set-Cookie: BalancerID=first; path=/";
+ stickycookie "BalancerID=first";
+ }
+ backend two {
+ ...
+ addclientheader "Set-Cookie: BalancerID=second; path=/";
+ stickycookie "BalancerID=second";
+ }
+}
+</pre>
+
+<p>
+The first request of an HTTP session is balanced to either
+ backend <code>one</code> or <code>two</code>. The server response is enriched
+ using <code>addclientheader</code> with an appropriate cookie. A
+ subsequent request from the same browser now has that cookie
+ in place; and is therefore sent to the same back end where the
+ its predecessors went.
+<p>
+<p><dt><strong>Hiding the server software version:</strong><dd> Many servers
+ (e.g. Apache) advertize their version, as in <code>Server: Apache
+ 1.27</code>. This potentially provides information to attackers. The
+ following configuration hides such information:
+<p>
+<pre>
+service ... {
+ ...
+ backend one {
+ ...
+ setclientheader "Server: WWW-Server";
+ }
+}
+</pre>
+
+<p>
+<p><dt><strong>Informing the server of the clients' IP address:</strong><dd> Since
+ Crossroads sits 'in the middle' between a client and a back
+ end, the back end perceives Crossroads as its client. The
+ following sends the true clients' IP address to the server, in
+ a header <code>X-Real-IP</code>:
+<p>
+<pre>
+service ... {
+ ...
+ backend one {
+ ...
+ setserverheader "X-Real-IP: %r";
+ }
+}
+</pre>
+
<p>
</dl>
<p>
-<a name="l15"></a>
+</dl>
+<p>
+<a name="l19"></a>
<h2>5: Tips, Tricks and Remarks</h2>
<a name="tips"></a>The following sections elaborate on the directives as described in
section <a href="crossroads.html#config">4</a> to illustrate how crossroads works and to help you
achieve the "optimal" balancing configuration.
<p>
-<a name="l16"></a>
+<a name="l20"></a>
<h3>5.1: How back ends are selected in load balancing</h3><a name="howselected"></a>
<p>
In order to tune your load balancing, you'll need to understand how
@@ -1058,7 +1205,7 @@ section we'll focus on the dispatching modes <code>bysize</code>, <code>bydurati
and <code>byconnections</code> only. The other dispatching types are
self-explanatory.
<p>
-<a name="l17"></a>
+<a name="l21"></a>
<strong>5.1.1: Bysize, byduration or byconnections?</strong>
<p>
As stated before, crossroads doesn't know 'what a service does' and
@@ -1108,7 +1255,7 @@ E.g., consider a database connection. What's
<p>
</ul>
<p>
-<a name="l18"></a>
+<a name="l22"></a>
<strong>5.1.2: Averaging size and duration</strong>
<p>
The configuration statement <code>dispatchmode bysize</code> or <code>byduration</code>
@@ -1129,7 +1276,7 @@ In contrast, when e.g. <code>over 3</code> is in effect, then a sudden load
does show up -- because it highly contributes to the average of three
connections.
<p>
-<a name="l19"></a>
+<a name="l23"></a>
<strong>5.1.3: Specifying decays</strong>
<p>
Decays are also only relevant when crossroads computes the 'next best
@@ -1183,7 +1330,7 @@ service soap {
</pre>
<p>
-<a name="l20"></a>
+<a name="l24"></a>
<strong>5.1.4: Adjusting the weights</strong>
<p>
The back end modifier <code>weight</code> is useful in situations where your
@@ -1237,7 +1384,7 @@ both A and B crash). Note also that A's usage data decay much faster
than B's and C's: we're assuming that this big server recovers quicker
than its smaller siblings.
<p>
-<a name="l21"></a>
+<a name="l25"></a>
<strong>5.1.5: Throttling the number of concurrent connections</strong>
<p>
If you suspect that your service may occasionally receive 'spikes' of
@@ -1267,7 +1414,7 @@ too much, a situation may occur where that back end is about to be
hit. A <code>maxconnections</code> statement on the level of that back may then
protect it.
<p>
-<a name="l22"></a>
+<a name="l26"></a>
<h3>5.2: HTTP Session Stickiness</h3>
<p>
This section focuses on HTTP session stickiness. This term refers to
@@ -1276,7 +1423,7 @@ a backend farm always to the same back end. In other words: once a
back end is selected by the balancer, it will remain the back end of
choice, even for subsequent connections.
<p>
-<a name="l23"></a>
+<a name="l27"></a>
<strong>5.2.1: Don't use stickiness!</strong>
<p>
The rule of thumb as far as the balancer is concerned, is: <strong>Do not
@@ -1287,7 +1434,11 @@ session stickiness hampers failover, balancing and performance:
<li> Failover is hampered because during the session,
the balancer has to assign new connections to the same back
end that was selected at the start of a session. If the back
- end suddenly goes 'down', then the session will crash.
+ end suddenly goes 'down', then the session will most likely
+ crash. (Actually, when a back end becomes unreachable in the
+ middle of a session, Crossroads will assign a new back end to
+ that session. This will most likely result in a malfunction
+ of the underlying application.)
<li> Balancing is hampered because at the start of the session,
the balancer has selected the next-best back end. But during
the session, that back end may well become overloaded. The
@@ -1304,7 +1455,7 @@ that all PHP applications have access to these data. Application
servers such as Websphere can be configured to replicate session data
between nodes.
<p>
-<a name="l24"></a>
+<a name="l28"></a>
<strong>5.2.2: But if you must..</strong>
<p>
However, if you <strong>must</strong> use session stickiness, then proceed as
@@ -1314,7 +1465,7 @@ follows:
<li> At the level of a <code>service</code> description, set the type to
<code>http</code>.
<li> At the level of each back end description, configure the
- <code>stickycookie</code> and a <code>insertcookie</code> directives.</ul>
+ <code>stickycookie</code> and a <code>addclientheader</code> directives.</ul>
<p>
Once crossroads sees that, it will examine each HTTP message that it
shuttles between client and back end:
@@ -1343,23 +1494,23 @@ service www {
backend one {
server 10.1.1.100:80;
stickycookie XRID=100;
- insertcookie "XRID=100; Path=/";
+ addclientheader "Set-Cookie: XRID=100; Path=/";
}
backend two {
server 10.1.1.101:80;
stickycookie XRID=101;
- insertcookie "XRID=101; Path=/";
+ addclientheader "Set-Cookie: XRID=101; Path=/";
}
}
</pre>
<p>
Note how the cookie names and values in the directives
-<code>stickycookie</code> and <code>insertcookie</code> match. That is obviously a
+<code>stickycookie</code> and <code>addclientheader</code> match. That is obviously a
prerequisite for stickiness.
<p>
-<a name="l25"></a>
+<a name="l29"></a>
<h3>5.3: Passing the client's IP address</h3>
<p>
Since Crossroads just shuttles bytes to and fro, meta-information of
@@ -1375,16 +1526,19 @@ configuration must state the following:
<p>
<ul>
<li> The service type must be <code>http</code>, and not <code>any</code>;
- <li> In the back end definition, a statement <code>insertrealip on;</code>
- must occur.</ul>
+ <li> In the back end definition, the following statement must
+ occur: <br>
+ <code>addserverheader "X-Real-IP: %r";</code> <br>
+ You are of course free to choose the header name; the here
+ used <code>X-Real-IP</code> is a common name for this purpose.</ul>
<p>
After this, HTTP traffic that arrives at the back ends has a new
-header: <code>XR-Real-IP</code>, holding the client's IP address.
+header: <code>X-Real-IP</code>, holding the client's IP address.
<strong>Note that</strong> once the type is set to <code>http</code>, Crossroads'
performance will be hampered -- all passing messages will have to be
unpacked and analyzed.
<p>
-<a name="l26"></a>
+<a name="l30"></a>
<strong>5.3.1: Sample Crossroads configuration</strong>
<p>
The below sample configuration shows two HTTP back ends that receive
@@ -1400,21 +1554,21 @@ service www {
backend one {
server 10.1.1.100:80;
- insertrealip on;
+ addserverheader "X-Real-IP: %r";
}
backend two {
server 10.1.1.200:80;
- insertrealip on;
+ addserverheader "X-Real-IP: %r";
}
}
</pre>
<p>
-<a name="l27"></a>
+<a name="l31"></a>
<strong>5.3.2: Sample Apache configuration</strong>
<p>
-The method by which each back end analyzes the header <code>XR-Real-IP</code>
+The method by which each back end analyzes the header <code>X-Real-IP</code>
will obviously be different per server implementations. However, a
common method with the Apache webserver is to log the client's IP
address into the access log.
@@ -1434,23 +1588,23 @@ file <code>logs/access_log</code>, using the previously defined format
<code>common</code>.
<p>
Furtunately, Apache's <code>LogFormat</code> allows one to log contents of
-headers. By replacing the <code>%h</code> with <code>%{XR-Real-IP}i</code>, the desired
+headers. By replacing the <code>%h</code> with <code>%{X-Real-IP}i</code>, the desired
information is sent to the log. Therefore, normally you can simply
redefine the <code>common</code> format to
<p>
<pre>
-LogFormat "%{XR-Real-IP}i %l %u %t %D \"%r\" %>s %b" common
+LogFormat "%{X-Real-IP}i %l %u %t %D \"%r\" %>s %b" common
</pre>
<p>
-<a name="l28"></a>
+<a name="l32"></a>
<h3>5.4: Configuration examples</h3>
<p>
As a general hint, use <code>crossroads sampleconf</code> to view the most
up-to-date examples of configurations. The description below shows a
few examples too.
<p>
-<a name="l29"></a>
+<a name="l33"></a>
<strong>5.4.1: A load balancer for three webserver back ends</strong>
<p>
The following configuration example binds crossroads to port 80 of the
@@ -1579,7 +1733,7 @@ service www {
</pre>
<p>
-<a name="l30"></a>
+<a name="l34"></a>
<strong>5.4.2: An HTTP forwarder when travelling</strong>
<p>
As another example, here's my <code>crossroads.conf</code> that I use on my
@@ -1668,7 +1822,7 @@ and <code>LocalSquid</code> are both active, then <code>crossroads tell httpprox
sshtunnel down</code> will 'take down' the back end <code>SshTunnel</code> -- and
will automatically cause crossroads to switch to <code>LocalSquid</code>.
<p>
-<a name="l31"></a>
+<a name="l35"></a>
<strong>5.4.3: SSH login with enforced idle logout</strong>
<p>
The following example shows how crossroads 'throttles' SSH
@@ -1694,13 +1848,13 @@ service Ssh {
</pre>
<p>
-<a name="l32"></a>
+<a name="l36"></a>
<h2>6: Benchmarking</h2>
<a name="benchmarking"></a>This section shows how crossroads affects the
transmitting of HTML data when used as an intermediate 'station'
through which all data travels.
<p>
-<a name="l33"></a>
+<a name="l37"></a>
<h3>6.1: Benchmark 1: Accessing a proxy via crossroads or directly</h3>
<p>
The benchmark was run on a system where the following was varied:
@@ -1728,7 +1882,7 @@ service HttpProxy {
</pre>
<p>
-<a name="l34"></a>
+<a name="l38"></a>
<strong>6.1.1: Results</strong>
<p>
The results of this test are that crossroads causes a negligible
@@ -1751,7 +1905,7 @@ sys 0m0.230s
</pre>
<p>
-<a name="l35"></a>
+<a name="l39"></a>
<strong>6.1.2: Discussion</strong>
<p>
The above shown results are quite favorable to crossroads. However,
@@ -1783,7 +1937,7 @@ seldom in the real world:
back end). Again, this processing time will weigh much heavier
than the multiple read/writes.</ul>
<p>
-<a name="l36"></a>
+<a name="l40"></a>
<h3>6.2: Benchmark 2: Crossroads versus Linux Virtual Server (LVS)</h3>
<p>
LVS is a kernel-based balancer that acts like a masquerading
@@ -1797,7 +1951,7 @@ LVS isn't aware of downtime of back ends (unless one implements an
external heartbeat). Also, crossroads offers more complex balancing
than LVS.
<p>
-<a name="l37"></a>
+<a name="l41"></a>
<strong>6.2.1: Environment</strong>
<p>
On the balancer, LVS was run on port 80, its forwarding set up for two
@@ -1828,7 +1982,7 @@ service http {
</pre>
<p>
-<a name="l38"></a>
+<a name="l42"></a>
<strong>6.2.2: Tests and results</strong>
<p>
In the first test, ports 80 and 81 on the balancer were 'bombed' with
@@ -1907,9 +2061,9 @@ are shown in the below table:
Again, the results show that crossroads performs just as effectively
as LVS, even with large data chunks!
<p>
-<a name="l39"></a>
+<a name="l43"></a>
<h2>7: Compiling and Installing</h2>
-<a name="compiling"></a><a name="l40"></a>
+<a name="compiling"></a><a name="l44"></a>
<h3>7.1: Prerequisites</h3>
<p>
The creation of crossroads requires:
@@ -1929,7 +2083,7 @@ Basically a Linux or Apple MacOSX box will do nicely once you make
sure that <code>bison</code> and <code>flex</code> are installed. To compile and install
crossroads, follow these steps.
<p>
-<a name="l41"></a>
+<a name="l45"></a>
<h3>7.2: Compiling and installing</h3>
<p>
<ul>
@@ -1974,20 +2128,20 @@ crossroads, follow these steps.
destination directory for your PDF manuals;
<p>
<li> Optionally, <code>cp doc/crossroads.man</code>
- <em>manualdirectory</em><code>/man1/crossroads.1</code>, where
- <em>manualdirectory</em> is e.g. <code>/usr/man</code>,
- <code>/usr/share</code>, <code>/usr/local/man</code>,
- <code>/usr/local/share</code>. Any possibility is valid, as
- long as <em>manualdirectory</em> has a subdirectory
- <code>man1/</code>;
+ <em>manualdirectory</em><code>/crossroads.1</code>, where
+ <em>manualdirectory</em> is e.g. <code>/usr/man/man1</code>,
+ <code>/usr/share/man1</code>, <code>/usr/local/man/man1</code>,
+ <code>/usr/local/share/man1</code>. Any possibility is valid, as
+ long as <em>manualdirectory</em> is one of the directories
+ where manual pages are stored;
<p>
<li> If your manual page system supports compressed
manual pages, then you can save some space with
- <code>gzip</code> <em>manualdirectory</em><code>/man1/crossroads.1</code>.</ul>
+ <code>gzip</code> <em>manualdirectory</em><code>/crossroads.1</code>.</ul>
<p>
</ul>
<p>
-<a name="l42"></a>
+<a name="l46"></a>
<h3>7.3: Configuring crossroads</h3>
<p>
Now that the binary is available on your system, you need to create a
@@ -2036,13 +2190,13 @@ which crossroads daemons are running. Finally, the tailing of
<code>/var/log/messages</code> shows what's going on -- especially if you have
<code>verbosity true</code> statements in the configuration.
<p>
-<a name="l43"></a>
+<a name="l47"></a>
<h3>7.4: A boot script</h3>
<p>
Finally, you may want to create a boot-time startup script. The exact
procedure depends on the used Unix flavor.
<p>
-<a name="l44"></a>
+<a name="l48"></a>
<strong>7.4.1: SysV Style Startup</strong>
<p>
On SysV style systems, there's a startup script directory
@@ -2084,7 +2238,7 @@ If your runlevel is 5, then the right <code>cd</code> command is to
<code>/etc/rc.d/rc5.d</code>. Alternatively, you can create the
symlinks in both runlevel directories.</ul>
<p>
-<a name="l45"></a>
+<a name="l49"></a>
<strong>7.4.2: BSD Style Startup</strong>
<p>
On BSD style systems, daemons are booted directly from <code>/etc/rc</code> and
diff --git a/doc/crossroads.man b/doc/crossroads.man
@@ -1,6 +1,6 @@
-.TH "Crossroads 1\&.07" "2005, 2006, ff\&."
+.TH "Crossroads 1\&.12" "2005, 2006, ff\&."
.PP
-.SH "Crossroads 1\&.07"
+.SH "Crossroads 1\&.12"
.SH "Karel Kubat"
.SH "e-tunity"
.SH "2005, 2006, ff\&."
@@ -151,6 +151,11 @@ An HTTP session may even span several
screens\&. E\&.g\&., a website registration dialog may involve 3
screens that when called from the same browser,
form a logical group of some sort\&.
+.IP "Headers"
+or \fBheader lines\fP are specific parts of an HTTP
+message\&. Crossroads has directives to add or modify
+headers that are part of the request that a browser sends
+to server, or those that are part of the server\&.
.IP "Session stickiness"
means that when a browser starts an
HTTP dialog, the balancer makes sure that it \&'sticks\&' to
@@ -200,6 +205,36 @@ multiple TCP connections, and the term
TCP connection\&.)
.PP
+.SH "1\&.5: Porting issues for pre-1\&.08 installations"
+
+.PP
+As of version 1\&.08, the following directives no longer are
+supported:
+.PP
+.IP o
+\f(CWinsertstickycookie\fP was replaced by the more generic
+directive \f(CWaddclientheader\fP\&. E\&.g\&., instead of
+.br
+\f(CWinsertstickycookie "XRID=100; Path=/";\fP
+.br
+the syntax is now
+.br
+\f(CWaddclientheader "Set-Cookie: XRID=100; Path=/";\fP
+.IP
+.IP o
+\f(CWinsertrealip\fP was replaced by the more generic
+directive \f(CWsetserverheader\fP\&. E\&.g\&., instead of
+.br
+\f(CWinsertrealip on;\fP
+.br
+the syntax is now
+.br
+\f(CWsetserverheader "XR-Real-IP: %r";\fP
+.br
+This incidentally also makes it possible to change the header
+name (here: \f(CWXR-Real-IP\fP)\&.
+.PP
+
.SH "2: Installation for the impatient"
For the impatient, here\&'s the very-quick-but-very-superficial recipy
for getting crossroads up and running:
@@ -287,6 +322,10 @@ configuration file)\&. The actual usage information is always obtained
by typing \f(CWcrossroads\fP without any arguments\&. Crossroads then
displays the allowed arguments\&.
.PP
+
+.SH "3\&.1: General Commandline Syntax"
+
+.PP
This section shows the most basic usage\&. As said above, start
\f(CWcrossroads\fP without arguments to view the full listing of options\&.
.PP
@@ -327,7 +366,7 @@ configuration \f(CW/etc/crossroads\&.conf\fP\&.
.PP
-.SH "3\&.1: Logging-related options"
+.SH "3\&.2: Logging-related options"
.PP
Two \&'flags\&' of Crossroads are specifically logging-related\&. This
@@ -728,7 +767,15 @@ service myservice {
Each service definition must have at least one backend
definition\&. There may be more (and probably will, if you want
balancing and fail over) as long as the backend names differ\&.
-The statements in the backend definition blocks are:
+The statements in the backend definition blocks are described in the
+following sections\&.
+.PP
+
+.SH "4\&.3\&.1: General Backend Directives"
+
+.PP
+The following directives are used in all types of services (\f(CWany\fP or
+\f(CWhttp\fP)\&. HTTP-specific directives are shown in section ??\&.
.PP
.IP "Server:"
Each back end must be identified by the network name
@@ -828,77 +875,8 @@ Syntax: \f(CWdecay\fP \fInumber\fP \f(CW;\fP
where \fInumber\fP is a percentage that decreases the back
data when other back ends are hit
.IP o
-Default: 0, meaning that no decay is applied to usage statistics\&.
-.IP
-.IP "HTTP cookie handling:"
-When the service type is \f(CWhttp\fP,
-then backends may define HTTP cookies to identify sessions\&. There
-are two directives to accomplish this: \f(CWstickycookie\fP and
-\f(CWinsertcookie\fP\&.
-.IP
-The directive \f(CWinsertcookie\fP is used to insert a cookie into the
-HTTP stream when crossroads assigns a back end to a new session\&.
-.IP
-.IP o
-Syntax: \f(CWinsertcookie\fP \fIcookiestring\fP \f(CW;\fP
-.IP o
-Default: none
-.IP
-The directive \f(CWstickycookie\fP is used by crossroads to determine
-whether a HTTP request is part of an established session\&. If so, the
-request is sent to the appropriate back end, without balancing
-considerations\&.
-.IP
-.IP o
-Syntax: \f(CWstickycookie\fP \fIcookiestring\fP \f(CW;\fP
-.IP o
-Default: none
-.IP
-It should be obvious that the \fIcookiestrings\fP in the two above
-directives should match: the cookie that\&'s initially inserted by
-\f(CWinsertcookie\fP must be found in a subsequent connection by
-\f(CWstickycookie\fP, otherwise the session won\&'t stick\&. Here\&'s a small
-example:
-.IP
-.nf
-service www {
- port 80;
- type http;
- backend one {
- server 10\&.0\&.0\&.1:80;
- stickycookie BalancerID=a;
- insertcookie "BalancerID=a; Path=/";
- }
- backend two {
- server 10\&.0\&.0\&.2:80;
- stickycookie BalancerID=b;
- insertcookie "BalancerID=b; Path=/";
- }
-}
-.fi
-
-.IP
-Note also the quoting of the cookiestring in the \f(CWinsertcookie\fP
-directive\&. Without it, the semicolon in the string would break the
-directive\&.
-.IP
-.IP "Passing of real IP address:"
-When the service type is
-\f(CWhttp\fP, the directive \f(CWinsertrealip\fP can be used to force
-Crossroads to insert the client\&'s real IP address as an HTTP
-header\&. The back end may then inspect the header\&.
-.IP
-.IP o
-Syntax: \f(CWinsertrealip\fP \fIsetting\fP \f(CW;\fP
-.IP o
-where \fIsetting\fP is \f(CWtrue\fP, \f(CWyes\fP or \f(CWon\fP to turn
-on this feature; or \f(CWfalse\fP, \f(CWno\fP or \f(CWoff\fP to turn it
-off\&.
-.IP o
-Default: \f(CWoff\fP
-.IP
-When turned on, the client\&'s IP address is stored in an HTTP
-header named \f(CWXR-Real-IP\fP\&.
+Default: 0, meaning that no decay is applied to usage
+statistics\&.
.IP
.IP "Event triggers:"
As special \&'hooks\&' for actions, two triggers
@@ -1052,6 +1030,183 @@ Syntax: \f(CWthroughputlog\fP \fIfilename\fP \f(CW;\fP
.IP o
There is no default\&. Without this directive, the
throughput is not logged\&.
+
+.PP
+
+.SH "4\&.3\&.2: HTTP-related Backend Directives"
+
+.PP
+The following directives are specific for HTTP-type services; i\&.e\&.,
+services with a specification \f(CWtype http\fP\&.
+.PP
+It is inevitable that when Crossroads handles services of \f(CWtype
+http\fP, more processing is necessary\&. Crossroads has to unpack the TCP
+payload in order to do its header magic; which leads to performance
+impact\&.
+.PP
+.IP "Session Stickiness:"
+The directive \f(CWstickycookie\fP \fIvalue\fP
+causes Crossroads to unpack clients\&' requests, to check for
+\fIvalue\fP in the cookies\&. When found, the message is routed to the
+back end having the appropriate \f(CWstickycookie\fP directive\&.
+.IP
+E\&.g\&., consider the following configuration:
+.IP
+.nf
+service \&.\&.\&. {
+ \&.\&.\&.
+ backend one {
+ \&.\&.\&.
+ stickycookie "BalancerID=first";
+ }
+ backend two {
+ \&.\&.\&.
+ stickycookie "BalancerID=second";
+ }
+}
+.fi
+
+.IP
+When clients\&' messages contain cookies named \f(CWBalancerID\fP with
+the value \f(CWfirst\fP, then such messages are routed to backend
+\f(CWone\fP\&. When the value is \f(CWsecond\fP then they are routed to the
+backend \f(CWtwo\fP\&.
+.IP
+There are basically to provide such cookies to a browser\&. First, a
+back end can insert such a cookie into the HTTP response\&. E\&.g\&.,
+the webserver of back end \f(CWone\fP might insert a cookie named
+\f(CWBalancerID\fP, having value \f(CWfirst\fP\&.
+Second, Crossroads can insert such cookies using a carefully
+crafted directive \f(CWaddclientheader\fP\&. See below\&.
+.IP
+.IP "Header modification:"
+Crossroads understands the following
+header modification directives: \f(CWaddclientheader\fP,
+\f(CWappendclientheader\fP, \f(CWsetclientheader\fP, \f(CWaddserverheader\fP,
+\f(CWappendserverheader\fP, \f(CWsetserverheader\fP\&.
+.IP
+The directive names always consist of
+\fIAction\fP\fIDestination\fP\f(CWheader\fP, where:
+.IP
+.IP o
+The action is \f(CWadd\fP, \f(CWappend\fP or \f(CWinsert\fP\&.
+.IP
+.IP o
+Action \f(CWadd\fP adds a header, even when headers with
+the same name already are present in an HTTP
+message\&. Adding headers is useful for e\&.g\&. \f(CWSet-Cookie\fP
+headers; a message may contain several of such headers\&.
+.IP
+.IP o
+Action \f(CWappend\fP adds a header if it isn\&'t present
+yet in an HTTP message\&. If such a header is already
+present, then the value is appended to the pre-existing
+header\&. This is useful for e\&.g\&. \f(CWVia\fP headers\&. Imagine
+an HTTP message with a header \f(CWVia: someproxy\fP\&. Then the
+directive \f(CWappendclientheader "Via: crossroads"\fP will
+rewrite the header to \f(CWVia: someproxy; crossroads\fP\&.
+.IP
+.IP o
+Action \f(CWset\fP overwrites headers with the same
+name; or adds a new header if no pre-existing is found\&.
+This is useful for e\&.g\&. \f(CWHost\fP headers\&.
+.IP
+.IP o
+The destination is one of \f(CWclient\fP or \f(CWserver\fP\&. When
+the destination is \f(CWserver\fP, then Crossroads will apply such
+directives to HTTP messages that originate from the browser
+and are being forwarded to back ends\&. When the destination is
+\f(CWclient\fP, then Crossroads will apply such directives to
+backend responses that are shuttled to the browser\&.
+.IP
+The syntax of the directives is e\&.g\&. \f(CWaddclientheader
+"X-Processed-By: Crossroads";\fP\&. The directives expect one
+argument; a string, consisting of a header name, a colon, and a
+header value\&. The directive ends with a semicolon\&.
+.IP
+The header value may contain one of the following formatting
+directives:
+.IP
+.IP o
+\f(CW%r\fP is expanded to the real IP address of a client;
+.IP o
+\f(CW%t\fP is expanded to a timestamp of the local time;
+.IP o
+\f(CW%T\fP is expanded to a timestamp of Greenwich Mean Time;
+.IP o
+\f(CW%v\fP is expanded to the Crossroads version;
+.IP o
+\f(CW%\fP\fIx\fP (where \fIx\fP is any other character) is
+expanded to \fIx\fP\&. E\&.g\&., \f(CW%%\fP is a literal % sign\&.
+.IP
+.IP "Common Uses"
+
+.IP
+The following examples show common uses of header modifications\&.
+.IP
+.IP "Enforcing session stickiness:"
+By combining
+\f(CWstickycookie\fP and \f(CWaddclientheader\fP, HTTP session
+stickiness is enforced\&. Consider the following configuration:
+.IP
+.nf
+service \&.\&.\&. {
+ \&.\&.\&.
+ backend one {
+ \&.\&.\&.
+ addclientheader "Set-Cookie: BalancerID=first; path=/";
+ stickycookie "BalancerID=first";
+ }
+ backend two {
+ \&.\&.\&.
+ addclientheader "Set-Cookie: BalancerID=second; path=/";
+ stickycookie "BalancerID=second";
+ }
+}
+.fi
+
+.IP
+The first request of an HTTP session is balanced to either
+backend \f(CWone\fP or \f(CWtwo\fP\&. The server response is enriched
+using \f(CWaddclientheader\fP with an appropriate cookie\&. A
+subsequent request from the same browser now has that cookie
+in place; and is therefore sent to the same back end where the
+its predecessors went\&.
+.IP
+.IP "Hiding the server software version:"
+Many servers
+(e\&.g\&. Apache) advertize their version, as in \f(CWServer: Apache
+1\&.27\fP\&. This potentially provides information to attackers\&. The
+following configuration hides such information:
+.IP
+.nf
+service \&.\&.\&. {
+ \&.\&.\&.
+ backend one {
+ \&.\&.\&.
+ setclientheader "Server: WWW-Server";
+ }
+}
+.fi
+
+.IP
+.IP "Informing the server of the clients\&' IP address:"
+Since
+Crossroads sits \&'in the middle\&' between a client and a back
+end, the back end perceives Crossroads as its client\&. The
+following sends the true clients\&' IP address to the server, in
+a header \f(CWX-Real-IP\fP:
+.IP
+.nf
+service \&.\&.\&. {
+ \&.\&.\&.
+ backend one {
+ \&.\&.\&.
+ setserverheader "X-Real-IP: %r";
+ }
+}
+.fi
+
.IP
.SH "5: Tips, Tricks and Remarks"
@@ -1309,7 +1464,11 @@ session stickiness hampers failover, balancing and performance:
Failover is hampered because during the session,
the balancer has to assign new connections to the same back
end that was selected at the start of a session\&. If the back
-end suddenly goes \&'down\&', then the session will crash\&.
+end suddenly goes \&'down\&', then the session will most likely
+crash\&. (Actually, when a back end becomes unreachable in the
+middle of a session, Crossroads will assign a new back end to
+that session\&. This will most likely result in a malfunction
+of the underlying application\&.)
.IP o
Balancing is hampered because at the start of the session,
the balancer has selected the next-best back end\&. But during
@@ -1340,7 +1499,7 @@ At the level of a \f(CWservice\fP description, set the type to
\f(CWhttp\fP\&.
.IP o
At the level of each back end description, configure the
-\f(CWstickycookie\fP and a \f(CWinsertcookie\fP directives\&.
+\f(CWstickycookie\fP and a \f(CWaddclientheader\fP directives\&.
.PP
Once crossroads sees that, it will examine each HTTP message that it
shuttles between client and back end:
@@ -1370,20 +1529,20 @@ service www {
backend one {
server 10\&.1\&.1\&.100:80;
stickycookie XRID=100;
- insertcookie "XRID=100; Path=/";
+ addclientheader "Set-Cookie: XRID=100; Path=/";
}
backend two {
server 10\&.1\&.1\&.101:80;
stickycookie XRID=101;
- insertcookie "XRID=101; Path=/";
+ addclientheader "Set-Cookie: XRID=101; Path=/";
}
}
.fi
.PP
Note how the cookie names and values in the directives
-\f(CWstickycookie\fP and \f(CWinsertcookie\fP match\&. That is obviously a
+\f(CWstickycookie\fP and \f(CWaddclientheader\fP match\&. That is obviously a
prerequisite for stickiness\&.
.PP
@@ -1404,11 +1563,16 @@ configuration must state the following:
.IP o
The service type must be \f(CWhttp\fP, and not \f(CWany\fP;
.IP o
-In the back end definition, a statement \f(CWinsertrealip on;\fP
-must occur\&.
+In the back end definition, the following statement must
+occur:
+.br
+\f(CWaddserverheader "X-Real-IP: %r";\fP
+.br
+You are of course free to choose the header name; the here
+used \f(CWX-Real-IP\fP is a common name for this purpose\&.
.PP
After this, HTTP traffic that arrives at the back ends has a new
-header: \f(CWXR-Real-IP\fP, holding the client\&'s IP address\&.
+header: \f(CWX-Real-IP\fP, holding the client\&'s IP address\&.
\fBNote that\fP once the type is set to \f(CWhttp\fP, Crossroads\&'
performance will be hampered -- all passing messages will have to be
unpacked and analyzed\&.
@@ -1430,12 +1594,12 @@ service www {
backend one {
server 10\&.1\&.1\&.100:80;
- insertrealip on;
+ addserverheader "X-Real-IP: %r";
}
backend two {
server 10\&.1\&.1\&.200:80;
- insertrealip on;
+ addserverheader "X-Real-IP: %r";
}
}
.fi
@@ -1445,7 +1609,7 @@ service www {
.SH "5\&.3\&.2: Sample Apache configuration"
.PP
-The method by which each back end analyzes the header \f(CWXR-Real-IP\fP
+The method by which each back end analyzes the header \f(CWX-Real-IP\fP
will obviously be different per server implementations\&. However, a
common method with the Apache webserver is to log the client\&'s IP
address into the access log\&.
@@ -1465,12 +1629,12 @@ file \f(CWlogs/access_log\fP, using the previously defined format
\f(CWcommon\fP\&.
.PP
Furtunately, Apache\&'s \f(CWLogFormat\fP allows one to log contents of
-headers\&. By replacing the \f(CW%h\fP with \f(CW%{XR-Real-IP}i\fP, the desired
+headers\&. By replacing the \f(CW%h\fP with \f(CW%{X-Real-IP}i\fP, the desired
information is sent to the log\&. Therefore, normally you can simply
redefine the \f(CWcommon\fP format to
.PP
.nf
-LogFormat "%{XR-Real-IP}i %l %u %t %D \e"%r\e" %>s %b" common
+LogFormat "%{X-Real-IP}i %l %u %t %D \e"%r\e" %>s %b" common
.fi
.PP
@@ -2024,17 +2188,17 @@ destination directory for your PDF manuals;
.IP
.IP o
Optionally, \f(CWcp doc/crossroads\&.man\fP
-\fImanualdirectory\fP\f(CW/man1/crossroads\&.1\fP, where
-\fImanualdirectory\fP is e\&.g\&. \f(CW/usr/man\fP,
-\f(CW/usr/share\fP, \f(CW/usr/local/man\fP,
-\f(CW/usr/local/share\fP\&. Any possibility is valid, as
-long as \fImanualdirectory\fP has a subdirectory
-\f(CWman1/\fP;
+\fImanualdirectory\fP\f(CW/crossroads\&.1\fP, where
+\fImanualdirectory\fP is e\&.g\&. \f(CW/usr/man/man1\fP,
+\f(CW/usr/share/man1\fP, \f(CW/usr/local/man/man1\fP,
+\f(CW/usr/local/share/man1\fP\&. Any possibility is valid, as
+long as \fImanualdirectory\fP is one of the directories
+where manual pages are stored;
.IP
.IP o
If your manual page system supports compressed
manual pages, then you can save some space with
-\f(CWgzip\fP \fImanualdirectory\fP\f(CW/man1/crossroads\&.1\fP\&.
+\f(CWgzip\fP \fImanualdirectory\fP\f(CW/crossroads\&.1\fP\&.
.IP
.SH "7\&.3: Configuring crossroads"
diff --git a/doc/crossroads.pdf b/doc/crossroads.pdf
Binary files differ.
diff --git a/doc/crossroads.yo b/doc/crossroads.yo
@@ -8,7 +8,8 @@ abstract(Crossroads is a load balance and fail over utility for TCP
'hooks' for special actions when backend calls fail, and much
more. Crossroads is service-independent: it is usable for
HTTP/HTTPS, SSH, SMTP, DNS, etc. In the case of HTTP
- balancing, Crossroads can provide 'session stickiness' for
+ balancing, Crossroads can modify HTTP headers, e.g. to
+ provide 'session stickiness' for
back-end processes that need sessions, but aren't
session-aware of other back-ends.)
article(Crossroads VER())
diff --git a/doc/intro.yo b/doc/intro.yo
@@ -127,6 +127,10 @@ description(
screens. E.g., a website registration dialog may involve 3
screens that when called from the same browser,
form a logical group of some sort.
+ dit(Headers) or bf(header lines) are specific parts of an HTTP
+ message. Crossroads has directives to add or modify
+ headers that are part of the request that a browser sends
+ to server, or those that are part of the server.
dit(Session stickiness) means that when a browser starts an
HTTP dialog, the balancer makes sure that it 'sticks' to
the same back end (i.e., subsequent requests from the
@@ -167,4 +171,24 @@ subsect(Porting issues for pre-0.26 installations)
multiple TCP connections, and the term
em(session) is used strictly in that sense -- and no longer for a
TCP connection.)
-
-\ No newline at end of file
+
+subsect(Porting issues for pre-1.08 installations)
+
+ As of version 1.08, the following directives no longer are
+ supported:
+
+ itemization(
+ it() tt(insertstickycookie) was replaced by the more generic
+ directive tt(addclientheader). E.g., instead of nl()
+ tt(insertstickycookie "XRID=100; Path=/";) nl()
+ the syntax is now nl()
+ tt(addclientheader "Set-Cookie: XRID=100; Path=/";)
+
+ it() tt(insertrealip) was replaced by the more generic
+ directive tt(setserverheader). E.g., instead of nl()
+ tt(insertrealip on;) nl()
+ the syntax is now nl()
+ tt(setserverheader "XR-Real-IP: %r";) nl()
+ This incidentally also makes it possible to change the header
+ name (here: tt(XR-Real-IP)).)
+
+\ No newline at end of file
diff --git a/doc/tips.yo b/doc/tips.yo
@@ -233,7 +233,11 @@ itemization(
it() Failover is hampered because during the session,
the balancer has to assign new connections to the same back
end that was selected at the start of a session. If the back
- end suddenly goes 'down', then the session will crash.
+ end suddenly goes 'down', then the session will most likely
+ crash. (Actually, when a back end becomes unreachable in the
+ middle of a session, Crossroads will assign a new back end to
+ that session. This will most likely result in a malfunction
+ of the underlying application.)
it() Balancing is hampered because at the start of the session,
the balancer has selected the next-best back end. But during
the session, that back end may well become overloaded. The
@@ -259,7 +263,7 @@ itemization(
it() At the level of a tt(service) description, set the type to
tt(http).
it() At the level of each back end description, configure the
- tt(stickycookie) and a tt(insertcookie) directives.)
+ tt(stickycookie) and a tt(addclientheader) directives.)
Once crossroads sees that, it will examine each HTTP message that it
shuttles between client and back end:
@@ -288,18 +292,18 @@ service www {
backend one {
server 10.1.1.100:80;
stickycookie XRID=100;
- insertcookie "XRID=100; Path=/";
+ addclientheader "Set-Cookie: XRID=100; Path=/";
}
backend two {
server 10.1.1.101:80;
stickycookie XRID=101;
- insertcookie "XRID=101; Path=/";
+ addclientheader "Set-Cookie: XRID=101; Path=/";
}
})
Note how the cookie names and values in the directives
-tt(stickycookie) and tt(insertcookie) match. That is obviously a
+tt(stickycookie) and tt(addclientheader) match. That is obviously a
prerequisite for stickiness.
@@ -318,11 +322,14 @@ configuration must state the following:
itemization(
it() The service type must be tt(http), and not tt(any);
- it() In the back end definition, a statement tt(insertrealip on;)
- must occur.)
+ it() In the back end definition, the following statement must
+ occur: nl()
+ tt(addserverheader "X-Real-IP: %r";) nl()
+ You are of course free to choose the header name; the here
+ used tt(X-Real-IP) is a common name for this purpose.)
After this, HTTP traffic that arrives at the back ends has a new
-header: tt(XR-Real-IP), holding the client's IP address.
+header: tt(X-Real-IP), holding the client's IP address.
bf(Note that) once the type is set to tt(http), Crossroads'
performance will be hampered -- all passing messages will have to be
unpacked and analyzed.
@@ -341,19 +348,19 @@ service www {
backend one {
server 10.1.1.100:80;
- insertrealip on;
+ addserverheader "X-Real-IP: %r";
}
backend two {
server 10.1.1.200:80;
- insertrealip on;
+ addserverheader "X-Real-IP: %r";
}
})
subsubsect(Sample Apache configuration)
-The method by which each back end analyzes the header tt(XR-Real-IP)
+The method by which each back end analyzes the header tt(X-Real-IP)
will obviously be different per server implementations. However, a
common method with the Apache webserver is to log the client's IP
address into the access log.
@@ -371,12 +378,12 @@ file tt(logs/access_log), using the previously defined format
tt(common).
Furtunately, Apache's tt(LogFormat) allows one to log contents of
-headers. By replacing the tt(%h) with tt(%{XR-Real-IP}i), the desired
+headers. By replacing the tt(%h) with tt(%{X-Real-IP}i), the desired
information is sent to the log. Therefore, normally you can simply
redefine the tt(common) format to
verb(\
-LogFormat "%{XR-Real-IP}i %l %u %t %D \"%r\" %>s %b" common)
+LogFormat "%{X-Real-IP}i %l %u %t %D \"%r\" %>s %b" common)
subsect(Configuration examples)
diff --git a/doc/using.yo b/doc/using.yo
@@ -5,6 +5,8 @@ configuration file). The actual usage information is always obtained
by typing tt(crossroads) without any arguments. Crossroads then
displays the allowed arguments.
+subsect(General Commandline Syntax)
+
This section shows the most basic usage. As said above, start
tt(crossroads) without arguments to view the full listing of options.
diff --git a/etc/Makefile.def b/etc/Makefile.def
@@ -3,7 +3,7 @@
# Versioning. This defines the overall version ID and must match the topmost
# entry in the ChangeLog.
-VER = 1.07
+VER = 1.12
# Default config
DEFAULT_CONF = /etc/crossroads.conf
@@ -25,6 +25,9 @@ SLEEP_TIME = 10
# (as they should be) then keep this number small.
CONNECT_TIMEOUT = 2
+# The size of buffers for the copying of data between sockets.
+TCP_BUFSZ = 10240
+
# The directory where the crossroads program will be installed. Note that
# the docs aren't installed by default; you have to do that by hand.
# For the bindir, we like to use EBINDIR, but supply a name if EBINDIR
diff --git a/src/Makefile b/src/Makefile
@@ -17,11 +17,17 @@ samplerun: all
-./crossroads -c sample.conf stop
./crossroads -c sample.conf start
-textconv: usage.h sampleconf.h
+textconv: usage.h sampleconf.h proxyerror.h
usage.h: usage.txt
- ../tools/e-txt2c USAGETEXT <usage.txt | expand >usage.h
+ ../tools/untab usage.txt
+ ../tools/e-txt2c USAGETEXT <usage.txt >usage.h
sampleconf.h: sample.conf
- ../tools/e-txt2c SAMPLECONF <sample.conf | expand >sampleconf.h
+ ../tools/untab sample.conf
+ ../tools/e-txt2c SAMPLECONF <sample.conf >sampleconf.h
+ ../tools/untab sampleconf.h
+proxyerror.h: proxyerror.txt
+ ../tools/untab proxyerror.txt
+ ../tools/e-txt2c ERRORTEXT <proxyerror.txt >proxyerror.h
grammar: parser.c lexer.c
lexer.c: lexer.l
@@ -39,12 +45,14 @@ DEFS = -DDEFAULT_CONF=\"$(DEFAULT_CONF)\" -DMAX_BACKEND=$(MAX_BACKEND) \
-DSHM_MASK=$(SHM_MASK) -DSLEEP_TIME=$(SLEEP_TIME) \
-DCONNECT_TIMEOUT=$(CONNECT_TIMEOUT) -DVER=\"$(VER)\" \
-DSET_PROC_TITLE_BY_ARGV=$(SET_PROC_TITLE_BY_ARGV) \
+ -DTCP_BUFSZ=$(TCP_BUFSZ) \
$(shell ../tools/c-conf ifheader malloc.h HAVE_MALLOC_H) \
$(shell ../tools/c-conf ifheader stdint.h HAVE_STDINT_H) \
$(shell ../tools/c-conf libfunction flock HAVE_FLOCK) \
$(shell ../tools/c-conf libfunction lockf HAVE_LOCKF) \
$(shell ../tools/c-conf libfunction strlcat HAVE_STRLCAT) \
- $(shell ../tools/c-conf libfunction sranddev HAVE_SRANDDEV)
+ $(shell ../tools/c-conf libfunction sranddev HAVE_SRANDDEV) \
+ $(shell ../tools/c-conf libfunction vsyslog HAVE_VSYSLOG)
LIBS = $(shell ../tools/c-conf lib ucb nsl pthread socket 2>/dev/null)
@@ -66,7 +74,7 @@ libcrossroads.a: $(OBJ)
distclean: clean
clean:
rm -f $(OBJ) lexer.c parser.c parser.h libcrossroads.a crossroads \
- usage.h sampleconf.h
+ usage.h sampleconf.h proxyerror.h
# Extra deps:
usage.o: usage.c usage.h ../etc/Makefile.def
diff --git a/src/ansistamp.c b/src/ansistamp.c
@@ -1,14 +1,25 @@
#include "crossroads.h"
-char *ansistamp () {
+char *ansistamp (TmType t) {
static char buf[80];
time_t now;
struct tm *tmp;
+ double hrs_off;
time (&now);
- tmp = localtime (&now);
- snprintf (buf, sizeof(buf), "%4.4d-%2.2d-%2.2d/%2.2d:%2.2d:%2.2d",
- tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
- tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
+ if (t == tm_localtime) {
+ tmp = localtime (&now);
+ hrs_off = tmp->tm_gmtoff / 3600;
+ snprintf (buf, sizeof(buf),
+ "%4.4d-%2.2d-%2.2d/%2.2d:%2.2d:%2.2d(%.1f,%s)",
+ tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
+ tmp->tm_hour, tmp->tm_min, tmp->tm_sec,
+ hrs_off, tmp->tm_zone ? tmp->tm_zone : "UNK");
+ } else {
+ tmp = gmtime (&now);
+ snprintf (buf, sizeof(buf), "%4.4d-%2.2d-%2.2d/%2.2d:%2.2d:%2.2d",
+ tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
+ tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
+ }
return (buf);
}
diff --git a/src/backendconnect.c b/src/backendconnect.c
@@ -5,38 +5,42 @@ static int timeout;
static void alarmhandler (int sig) {
alarm(0);
- msg ("Backend %s: connect timed out (%d sec)",
- activeservice->backend[current_backend].name, CONNECT_TIMEOUT);
longjmp (jmpbuf, 1);
}
-int backend_connect (int is_waking) {
+int backend_connect () {
int backend_sock;
struct sockaddr_in servername;
- if ( (backend_sock = socket (PF_INET, SOCK_STREAM, 0)) < 0 )
- error ("Failed to create socket for backend communication: %s",
- strerror(errno));
-
+ if ( (backend_sock = socket (PF_INET, SOCK_STREAM, 0)) < 0 ) {
+ if (program_stage != stage_retrying) {
+ error ("Service %s: failed to create socket "
+ "for backend communication: %s",
+ activeservice->name, strerror(errno));
+ } else {
+ warning ("Service %s: failed to create socket "
+ "for backend communication: %s",
+ activeservice->name, strerror(errno));
+ return (-1);
+ }
+ }
+
if (init_sockaddr (&servername,
activeservice->backend[current_backend].server,
activeservice->backend[current_backend].port)) {
/* The hostname is unusable.. */
- if (is_waking)
- msg ("Unknown host %s",
+ warning ("Service %s: unknown host %s",
+ activeservice->name,
activeservice->backend[current_backend].server);
- else {
- warning ("Unknown host %s",
- activeservice->backend[current_backend].server);
- mark_activity (0, 0, st_unavailable);
- }
- return (-1);
+ mark_activity (0, 0, st_unavailable);
+ return (-2);
}
signal (SIGALRM, alarmhandler);
timeout = 0;
alarm (CONNECT_TIMEOUT);
+
if (!setjmp (jmpbuf)) {
/* First time around, try connecting */
if (connect (backend_sock, (struct sockaddr *) &servername,
@@ -45,39 +49,36 @@ int backend_connect (int is_waking) {
* Either connect() has failed (connection refused) or it got
* interrupted by the SIGALRM
*/
+ close (backend_sock);
alarm(0);
mark_activity (0, 0, st_unavailable);
if (timeout) {
- if (is_waking)
- msg ("Server %s: not usable due to timeout",
- activeservice->backend[current_backend].server);
- else
- warning ("Server %s: not usable due to timeout",
+ if (program_stage != stage_retrying)
+ warning ("Service %s: server %s not usable due to timeout",
+ activeservice->name,
activeservice->backend[current_backend].server);
- }
- else {
- msg ("Server %s:%d: cannot connect: %s",
- activeservice->backend[current_backend].server,
- activeservice->backend[current_backend].port,
- strerror(errno));
+ } else {
+ if (program_stage != stage_retrying)
+ warning ("Service %s: server %s:%d: cannot connect: %s",
+ activeservice->name,
+ activeservice->backend[current_backend].server,
+ activeservice->backend[current_backend].port,
+ strerror(errno));
mark_activity (0, 0, st_unavailable);
}
- close (backend_sock);
- backend_sock = -2;
+ return (-3);
}
alarm(0);
} else {
/* Got here because of longjmp from the signal handler,
* this MUST be a timeout
*/
+ close (backend_sock);
mark_activity (0, 0, st_unavailable);
- if (is_waking)
- msg ("Server %s: not usable due to timeout",
- activeservice->backend[current_backend].server);
- else
- warning ("Server %s: not usable due to timeout",
+ if (program_stage != stage_retrying)
+ warning ("Server %s not usable due to timeout",
activeservice->backend[current_backend].server);
- backend_sock = -3;
+ return (-4);
}
signal (SIGALRM, SIG_DFL);
diff --git a/src/choosebackend.c b/src/choosebackend.c
@@ -13,7 +13,8 @@ void choose_backend () {
/* Check that we're allowed to accept at all. */
if (activeservice->maxconnections &&
servicereport->nclients >= activeservice->maxconnections) {
- warning ("Max clients %d exceeded",
+ warning ("Service %s: max clients %d exceeded",
+ activeservice->name,
activeservice->maxconnections);
current_backend = -1;
return;
@@ -30,8 +31,10 @@ void choose_backend () {
(activeservice->backend[i].maxconnections == 0 ||
activeservice->backend[i].maxconnections >
servicereport->backendstate[i].nclients)) {
- msg ("Candidate back end: %d (max clients %d, active %d, "
+ msg ("Service %s: "
+ "candidate back end: %d (max clients %d, active %d, "
"state %s)",
+ activeservice->name,
i, activeservice->backend[i].maxconnections,
servicereport->backendstate[i].nclients,
state_to_string(servicereport->backendstate[i].avail));
@@ -51,7 +54,8 @@ void choose_backend () {
if (nbackends == 1) {
current_backend = backends[0];
servicereport->last_backend = current_backend;
- msg ("Only 1 backend to select (%d)", current_backend);
+ msg ("Service %s: only 1 backend to select (%d)",
+ activeservice->name, current_backend);
return;
}
@@ -66,26 +70,28 @@ void choose_backend () {
i %= nbackends;
current_backend = backends[i];
servicereport->last_backend = current_backend;
- msg ("Chosen backend (roundrobin): %d",
- current_backend);
+ msg ("Service %s: chosen backend (roundrobin): %d",
+ activeservice->name, current_backend);
return;
}
/* None found.. try the first one (run to momma). */
current_backend = backends[0];
servicereport->last_backend = current_backend;
- msg ("Chosen backend (roundrobin, without historical info): %d",
- current_backend);
+ msg ("Service %s: "
+ "chosen backend (roundrobin, without historical info): %d",
+ activeservice->name, current_backend);
return;
case ds_random:
/* Re-randomize. */
# ifdef HAVE_SRANDDEV
sranddev();
- msg ("Randomier seeded with randdev");
+ msg ("Service %s: randomier seeded with randdev", activeservice->name);
# else
gettimeofday (&tv, 0);
srand ((unsigned) tv.tv_usec);
- msg ("Randomizer seeded with %u", (unsigned) tv.tv_usec);
+ msg ("Service %s: randomizer seeded with %u",
+ activeservice->name, (unsigned) tv.tv_usec);
# endif
/* First of all let's see if all the weights are the same. */
@@ -168,15 +174,17 @@ void choose_backend () {
current_backend = backends[sel_weights[i]];
servicereport->last_backend = current_backend;
free (sel_weights);
- msg ("Chosen backend (weighted random): %d at index %d",
- current_backend, i);
+ msg ("Service %s: "
+ "chosen backend (weighted random): %d at index %d",
+ activeservice->name, current_backend, i);
return;
}
/* ELSE: Choose a random one of the availables. */
current_backend = backends[rand() % nbackends];
servicereport->last_backend = current_backend;
- msg ("Chosen backend (flat-weight random): %d", current_backend);
+ msg ("Service %s: chosen backend (flat-weight random): %d",
+ activeservice->name, current_backend);
return;
case ds_bysize:
@@ -193,8 +201,10 @@ void choose_backend () {
else
values[i] = servicereport->backendstate[backends[i]].nbytes *
activeservice->backend[backends[i]].weight;
- msg ("By size weighing: backend %d has weight %d, value %u"
- " (bytes=%u, avgbytes=%u)",
+ msg ("Service %s: "
+ "by size weighing backend %d has weight %d, value %u"
+ " (bytes=%u, avgbytes=%u)",
+ activeservice->name,
backends[i],
activeservice->backend[backends[i]].weight,
(unsigned) values[i],
@@ -208,10 +218,12 @@ void choose_backend () {
nbest = values[i];
current_backend = backends[i];
servicereport->last_backend = current_backend;
- msg ("By size weighing: best so far is %d (value %g)",
- current_backend, nbest);
+ msg ("Service %s: "
+ "by size weighing: best so far is %d (value %g)",
+ activeservice->name, current_backend, nbest);
}
- msg ("Chosen backend (by size): %d", current_backend);
+ msg ("Service %s: chosen backend (by size): %d",
+ activeservice->name, current_backend);
return;
case ds_byduration:
@@ -230,8 +242,9 @@ void choose_backend () {
values[i] = servicereport->backendstate[backends[i]].nsec *
1000000 *
activeservice->backend[backends[i]].weight;
- msg ("By duration weighing: backend %d has value %lu"
+ msg ("Service %s: By duration weighing: backend %d has value %lu"
" (sec=%g, avgsec=%g, weight=%d)",
+ activeservice->name,
backends[i], values[i],
servicereport->backendstate[backends[i]].nsec,
servicereport->backendstate[backends[i]].avg_nsec,
@@ -243,17 +256,20 @@ void choose_backend () {
nbest = values[i];
current_backend = backends[i];
servicereport->last_backend = current_backend;
- msg ("By duration weighing: best so far is %d (value %g)",
- current_backend, nbest);
+ msg ("Service %s: "
+ "by duration weighing: best so far is %d (value %g)",
+ activeservice->name, current_backend, nbest);
}
- msg ("Chosen backend (by duration): %d", current_backend);
+ msg ("Service %s: chosen backend (by duration): %d",
+ activeservice->name, current_backend);
return;
case ds_byorder:
/* Get the first available back end in line. */
current_backend = backends[0];
servicereport->last_backend = current_backend;
- msg ("Chosen backend (by order): %d", current_backend);
+ msg ("Service %s: chosen backend (by order): %d",
+ activeservice->name, current_backend);
return;
case ds_byconnections:
@@ -262,8 +278,9 @@ void choose_backend () {
values[i] =
servicereport->backendstate[backends[i]].nclients *
activeservice->backend[backends[i]].weight;
- msg ("By connections weighing: backend %d has value %u",
- backends[i], values[i]);
+ msg ("Service %s: "
+ "by connections weighing: backend %d has value %u",
+ activeservice->name, backends[i], values[i]);
}
/* Find the minimum in the values array. */
for (i = 0; i < nbackends; i++)
@@ -271,15 +288,17 @@ void choose_backend () {
nclients = values[i];
current_backend = backends[i];
servicereport->last_backend = current_backend;
- msg ("By connections weighing: best so far is %d (value %u)",
- current_backend, nclients);
+ msg ("Service %s: "
+ "by connections weighing: best so far is %d (value %u)",
+ activeservice->name, current_backend, nclients);
}
- msg ("Chosen backend (by connections): %d", current_backend);
+ msg ("Service %s: chosen backend (by connections): %d",
+ activeservice->name, current_backend);
return;
default:
/* Internal fry.. */
- error ("Internal error: unhandled dispatch type %d",
- activeservice->dispatchtype);
+ error ("Service %s: internal error: unhandled dispatch type %d",
+ activeservice->name, activeservice->dispatchtype);
}
}
diff --git a/src/copysockets.c b/src/copysockets.c
@@ -1,165 +1,11 @@
#include "crossroads.h"
-void copysockets (int clientsock, int serversock, struct sockaddr_in *client) {
- int src_fd, dst_fd, nfd;
- int nread, nwritten, totwritten;
- unsigned char buf[10240];
- char *src_str, *dst_str;
- fd_set readset, exceptset;
- struct timeval tv, *tvp, start_tv;
- unsigned long long microsec;
-
- /* Promote the verbosity of the backend */
- flag_verbose = activeservice->backend[current_backend].verbosity;
-
- /* Get starting time */
- gettimeofday (&start_tv, 0);
- lock_reporter();
- servicereport->nclients++;
- servicereport->backendstate[current_backend].nclients++;
- unlock_reporter();
-
- /* Cpio back and forth. */
- log_activity_start (client);
-
- while (1) {
- FD_ZERO (&readset);
- FD_SET (clientsock, &readset);
- FD_SET (serversock, &readset);
-
- FD_ZERO (&exceptset);
- FD_SET (clientsock, &exceptset);
- FD_SET (serversock, &exceptset);
-
- if (activeservice->connectiontimeout) {
- tv.tv_sec = activeservice->connectiontimeout;
- tv.tv_usec = 0;
- tvp = &tv;
- } else
- tvp = 0;
-
- /* Wait for anything to happen */
- nfd = select (FD_SETSIZE, &readset, 0, &exceptset, tvp);
-
- /* Update the intermediate activity as far as the elapsed time
- * is concerned. */
- gettimeofday (&tv, 0);
- microsec = ( (tv.tv_sec * 1000000 + tv.tv_usec) -
- (start_tv.tv_sec * 1000000 + start_tv.tv_usec) );
-
- gettimeofday (&start_tv, 0);
- mark_activity (0, microsec / 1000000, st_intermediate);
- // msg ("Spent another %g microsecs in this connection.", microsec);
-
- if (nfd < 1) {
- /* We're done here. */
- lock_reporter();
- servicereport->nclients--;
- servicereport->backendstate[current_backend].nclients--;
- unlock_reporter();
- log_activity_end (client);
-
- if (! nfd) {
- /* This must be caused by a timeout in select(), otherwise
- * the read() below would have returned 0. */
- mark_activity (0, 0, st_available);
- error ("Service %s: Timeout (backend %s) after %d secs",
- activeservice->name,
- activeservice->backend[current_backend].name,
- activeservice->connectiontimeout);
- } else {
- /* nfd < 0: This is an error condition.
- * select() has failed! */
- error ("Service %s: Failed to wait for network input: %s",
- activeservice->name,
- strerror(errno));
- }
- }
-
- /* Check for exceptions. A backend-side exception will be reported
- * as a failure.
- */
- if (FD_ISSET(clientsock, &exceptset)) {
- lock_reporter();
- servicereport->nclients--;
- servicereport->backendstate[current_backend].nclients--;
- unlock_reporter();
- log_activity_end (client);
- error ("Exception on client connection");
- }
- if (FD_ISSET(serversock, &exceptset)) {
- lock_reporter();
- servicereport->nclients--;
- servicereport->backendstate[current_backend].nclients--;
- unlock_reporter();
- mark_activity (0, 0, st_unavailable);
- log_activity_end (client);
- error ("Exception on backend connection");
- }
-
- /* Determine which to read and which to write. */
- if (FD_ISSET(clientsock, &readset)) {
- src_fd = clientsock;
- dst_fd = serversock;
- src_str = "client";
- dst_str = "backend";
- } else if (FD_ISSET(serversock, &readset)) {
- src_fd = serversock;
- dst_fd = clientsock;
- src_str = "backend";
- dst_str = "client";
- }
-
- /* Read source socket. */
- if (! (nread = read (src_fd, buf, sizeof(buf))) ) {
- lock_reporter();
- servicereport->nclients--;
- servicereport->backendstate[current_backend].nclients--;
- unlock_reporter();
- mark_activity (0, 0, st_available);
- close (serversock);
- close (clientsock);
- log_activity_end (client);
-
- /* We're done. */
- msg ("Successful TCP stop, exiting servicer %d", getpid());
- exit (0);
- } else if (nread < 0) {
- if (src_fd == serversock)
- mark_activity (0, 0, st_unavailable);
- lock_reporter();
- servicereport->nclients--;
- servicereport->backendstate[current_backend].nclients--;
- unlock_reporter();
- log_activity_end (client);
- error ("Read error on connection from %s", src_str);
- }
-
- /* Update the intermediate activity as far as the read bytes
- * are concerned. Update traffic / thruput logs. */
- // msg ("Read %d bytes from %s", nread, src_str);
- mark_activity (nread, 0, st_intermediate);
- trafficlog (buf, nread, src_fd == clientsock);
- thruputlog (buf, nread, src_fd == clientsock);
-
- /* Write in chunks to dest socket. */
- totwritten = 0;
- while (totwritten < nread) {
- if ( (nwritten = write (dst_fd,
- buf + totwritten,
- nread - totwritten)) < 1 ) {
- if (dst_fd == serversock)
- mark_activity (0, 0, st_unavailable);
- lock_reporter();
- servicereport->nclients--;
- servicereport->backendstate[current_backend].nclients--;
- unlock_reporter();
- log_activity_end (client);
- error ("Write error when copying to %s", dst_str);
- }
- // msg ("Wrote another %d bytes to %s in this connection",
- // nwritten, dst_str);
- totwritten += nwritten;
- }
- }
+void copysockets (int clientsock, int serversock) {
+ unsigned char buf[TCP_BUFSZ];
+
+ /* Copy between client and server. Function net_copy() will
+ * stop itself when either an error is seen, or when no more
+ * data are there. */
+ while (1)
+ net_copy (clientsock, serversock, TCP_BUFSZ, buf, 1);
}
diff --git a/src/crossroads.h b/src/crossroads.h
@@ -72,8 +72,12 @@ typedef enum { /* Config parsing related */
cf_thruspec,
cf_bindspec,
cf_stickycookiespec,
- cf_insertcookiespec,
- cf_insertrealipspec,
+ cf_setclientheaderspec,
+ cf_addclientheaderspec,
+ cf_appendclientheaderspec,
+ cf_setserverheaderspec,
+ cf_addserverheaderspec,
+ cf_appendserverheaderspec,
} Conftype;
typedef union { /* Integer of string value */
@@ -118,8 +122,18 @@ typedef struct { /* Backend description */
char *thruputfile; /* .. traffic throughput file */
int maxconnections; /* .. max # of allowed connections */
char *stickycookie; /* .. cookie selector */
- char *insertcookie; /* .. cookie ID to insert */
- int insertrealip; /* .. insert real IP (yes/no) */
+ char **addclientheader; /* .. client hdrs to ADD */
+ int naddclientheader; /* .. table size */
+ char **setclientheader; /* .. client hdrs to SET */
+ int nsetclientheader; /* .. table size */
+ char **appendclientheader; /* .. client hdrs to APPEND */
+ int nappendclientheader; /* .. table size */
+ char **addserverheader; /* .. server hdrs to ADD */
+ int naddserverheader; /* .. table size */
+ char **setserverheader; /* .. server hdrs to SET */
+ int nsetserverheader; /* .. table size */
+ char **appendserverheader; /* .. server hdrs to APPEND */
+ int nappendserverheader;
} Backend;
typedef struct { /* Service description */
@@ -176,12 +190,35 @@ typedef enum { /* Stage of the program: */
stage_retrying, /* .. waking up a backend */
} Programstage; /* Update stagetostring.c when */
/* modifying! */
+
+typedef struct { /* An HTTP Message */
+ char **header; /* Headers table, excluding first */
+ int nheader; /* Headers table size */
+} HttpHeader;
+
+typedef enum { /* Time representation format */
+ tm_localtime, /* .. local */
+ tm_gmtime, /* .. UTC */
+} TmType;
+
+typedef enum { /* TCP copying direction */
+ dir_client_to_server,
+ dir_server_to_client,
+} CopyDirection;
+
+typedef enum { /* HTTP version */
+ con_unknown,
+ con_keepalive,
+ con_close,
+} HttpConnectionType;
+
/* Globals. */
#ifndef EXTERN
#define EXTERN extern
#endif
EXTERN Service *activeservice; /* target service of a daemon */
+EXTERN char *client_ip; /* connected client */
EXTERN char *config_file; /* config to parse */
EXTERN int current_backend; /* of a given service */
EXTERN int daemonized; /* are we forked off yet */
@@ -209,49 +246,55 @@ EXTERN char *yytext; /* lexical buffer */
/* Functions. */
extern void alloc_reporter (Service *active, int first);
-extern char *ansistamp (void);
+extern char *ansistamp (TmType t);
extern int backend_available (void);
-extern int backend_connect (int is_waking);
+extern int backend_connect (void);
extern void choose_backend (void);
-extern void copysockets (int clientsock, int serversock,
- struct sockaddr_in *client);
+extern void copysockets (int clientsock, int serversock);
extern void configtest (int ac, char **av);
+extern void decr_client_count (void);
extern void dealloc_reporter (Service *s);
extern void error (char const *fmt, ...);
extern int fork_tcp_servicer (int to_backend);
-extern int http_09_received (unsigned char const *buf);
-extern unsigned char const *http_beyondheaders (unsigned char const *buf,
- int buflen);
-extern int http_complete (unsigned char const *buf, int buflen);
+extern void http_copy (HttpHeader *h, int src_sock, int dst_sock,
+ CopyDirection dir);
extern void http_error (int clientsock);
-extern int http_findcookie (unsigned char const *buf, char const *cookie);
-extern char *http_headerval (unsigned char const *buf, char const *label);
-extern int http_headers_done (unsigned char const *buf);
-extern void http_insert_header (unsigned char **bufp, int *buflenp,
- char const *header);
-extern void http_read (int sock, int isclient, unsigned char **bufp,
- int *buflen);
-extern void http_serve (int clientsock, struct sockaddr_in *client);
-extern int http_serversocket (unsigned char const *buf,
- int *is_continuation);
-extern void http_set_realip_header (unsigned char **buf, int *buflen,
- struct sockaddr_in *client);
-extern void http_set_sticky_cookie (unsigned char **buf, int *buflen);
-extern int http_write (int sock, unsigned char const *buf, int buflen);
+extern char *http_expand_header (char const *h);
+extern void http_header_addheader (HttpHeader *m, char const *hdr);
+extern void http_header_appendheader (HttpHeader *m, char const *hdr);
+extern HttpConnectionType http_header_connectiontype (HttpHeader *h);
+extern void http_header_free (HttpHeader *msg);
+extern int http_header_hascookie (HttpHeader *m, char const *cookie);
+extern double http_header_httpver (HttpHeader *h);
+extern unsigned char const *http_header_val (HttpHeader *m, char const *hdr);
+extern HttpHeader *http_header_new (void);
+extern void http_header_read (HttpHeader *m, int srcsock, int dstsock,
+ CopyDirection dir);
+extern void http_header_setheader (HttpHeader *m, char const *hdr);
+extern void http_header_write (HttpHeader *m, int sock, int isclient);
+extern void http_serve (int clientsock);
+extern int http_serversocket (HttpHeader *m, int *is_continuation);
+extern int http_write (int sock, int isclient, unsigned char const *buf,
+ unsigned buflen);
+extern void incr_client_count (void);
extern int init_sockaddr (struct sockaddr_in *name,
const char *hostname, int port);
extern void interrupt (int sig);
-extern int ishexdigit (char c);
extern void lock_reporter (void);
-extern void log_activity_start (struct sockaddr_in *client);
-extern void log_activity_end (struct sockaddr_in *client);
-extern void log_activity_continuation (struct sockaddr_in *client);
+extern void log_activity_any (char const *action);
+extern void log_activity_start (void);
+extern void log_activity_end (void);
+extern void log_activity_continuation (void);
extern int make_socket (int port, char const *ipaddr);
extern void mark_activity (unsigned long long nbytes,
double nsec,
Backendavail newstate);
extern void msg (char const *fmt, ...);
extern void msgdumpbuf (unsigned char const *buf, int buflen);
+extern unsigned net_copy (int clientsock, int serversock, unsigned maxbytes,
+ unsigned char *buf, int write_too);
+extern int net_write (int sock, unsigned char const *buf, unsigned len,
+ int is_client);
extern void restart (int ac, char **av);
extern void runservice (void);
extern void sample_conf (int ac, char **av);
@@ -268,20 +311,26 @@ extern char *str_vprintf (char const *fmt, va_list args);
extern void sysrun (char const *cmd);
extern void tcpserve (int sock);
extern void tell_service (int ac, char **av);
-extern void thruputlog (unsigned char const *buf, int len, int isclient);
-extern void trafficlog (unsigned char const *buf, int len, int isclient);
+extern void thruputlog (unsigned char const *buf, int len, CopyDirection dir);
+extern void trafficlog (unsigned char const *buf, int len, CopyDirection dir);
extern void unlock_reporter (void);
extern void usage (void);
extern void wakeup_handler (void);
extern void warning (char const *fmt, ...);
-extern void writelog (int prio, char const *fmt, va_list args);
+extern void writelog (int prio, char const *fmt, ...);
extern int yyparse (void);
extern void *xmalloc (unsigned sz);
extern void *xrealloc (void *what, unsigned sz);
extern char *xstrcat (char *what, char const *rest);
+extern char *xstrcatch (char *what, char ch);
extern char *xstrdup (char const *what);
/* strlcat() if it's missing on this system */
#ifndef HAVE_STRLCAT
extern size_t strlcat (char *dst, char const *src, size_t size);
#endif
+
+/* vsyslog() if it's missing on this system */
+#ifndef HAVE_VSYSLOG
+extern void vsyslog (int fac, char const *fmt, va_list args);
+#endif
diff --git a/src/decrclientcount.c b/src/decrclientcount.c
@@ -0,0 +1,8 @@
+#include "crossroads.h"
+
+void decr_client_count () {
+ lock_reporter();
+ servicereport->nclients--;
+ servicereport->backendstate[current_backend].nclients--;
+ unlock_reporter();
+}
diff --git a/src/error.c b/src/error.c
@@ -2,12 +2,14 @@
void error (char const *fmt, ...) {
va_list args;
+ char *str;
va_start (args, fmt);
- if (!daemonized) {
- vfprintf (stderr, fmt, args);
- fprintf (stderr, "\n");
- } else
- writelog (LOG_ERR, fmt, args);
+ str = str_vprintf (fmt, args);
+ if (!daemonized)
+ fprintf (stderr, "ERROR: %s\n", str);
+ else
+ writelog (LOG_ERR, "ERROR: %s", str);
+ free (str);
exit (1);
}
diff --git a/src/forktcpservicer.c b/src/forktcpservicer.c
@@ -5,10 +5,12 @@ int fork_tcp_servicer (int to_backend) {
if ( (pid = fork()) < 0 )
/* Fork failure */
- error ("Failed to fork: %s", strerror(errno));
+ error ("Service %s: failed to fork: %s",
+ activeservice->name, strerror(errno));
else if (pid) {
/* Parent branch */
- msg ("Service communications will be handled by pid %d", pid);
+ msg ("Service %s: communications will be handled by pid %d",
+ activeservice->name, pid);
return (pid);
} else {
/* Child branch: here we will serve the socket pair.
@@ -20,14 +22,13 @@ int fork_tcp_servicer (int to_backend) {
program_stage = stage_serving;
if (to_backend > 0)
- set_program_title ("servicing %s to %s",
+ set_program_title ("Service %s: serving, back end is %s",
activeservice->name,
- activeservice->backend
- [current_backend].name);
+ activeservice->backend [current_backend].name);
else
- set_program_title ("servicing %s",
+ set_program_title ("Service %s: serving",
activeservice->name);
- return (0);
+ return (0);
}
/* To satisfy the prototype... */
diff --git a/src/http09received.c b/src/http09received.c
@@ -1,13 +0,0 @@
-#include "crossroads.h"
-
-int http_09_received (unsigned char const *buf) {
- if ( (!strncmp ( (char const *) buf, "GET", 3) ||
- !strncmp ( (char const *) buf, "POST", 4) ||
- !strncmp ( (char const *) buf, "HEAD", 4)) &&
- !strstr ( (char const *) buf, "HTTP/1") ) {
- msg ("Got HTTP/0.9 protocol in '%s'", buf);
- return (1);
- }
- msg ("Buffer is not HTTP/0.9: '%s'", buf);
- return (0);
-}
diff --git a/src/httpbeyondheaders.c b/src/httpbeyondheaders.c
@@ -1,29 +0,0 @@
-#include "crossroads.h"
-
-unsigned char const *http_beyondheaders (unsigned char const *buf,
- int buflen) {
- unsigned char const *cp;
-
- msg ("Getting beyond-headers block (%d bytes) from '%s'",
- buflen, buf);
- for (cp = (unsigned char const *) strchr ( (char const *) buf, '\n');
- cp && *cp;
- cp = (unsigned char const *) strchr ( (char const *) cp, '\n')) {
- cp++;
- if (*cp == '\r')
- cp++;
- if (*cp == '\n') {
- cp++;
- if (cp - buf < buflen) {
- msg ("Beyond headers block at '%s'", cp);
- return (cp);
- }
- else {
- msg ("No beyond-headers block present");
- return (0);
- }
- }
- }
-
- return (0);
-}
diff --git a/src/httpcomplete.c b/src/httpcomplete.c
@@ -1,71 +0,0 @@
-#include "crossroads.h"
-
-static unsigned char const *skipcr (unsigned char const *buf) {
- if (*buf == '\r' && *(buf + 1) == '\n')
- buf += 2;
- else if (*buf == '\n')
- buf++;
- return (buf);
-}
-
-int http_complete (unsigned char const *buf, int buflen) {
- char *val;
- unsigned char const *body;
- int len, chunked, chunk, bodylen;
-
- msg ("Verifying HTTP buffer (%d bytes) '%s'", buflen, buf);
- if ( (val = http_headerval (buf, "content-length")) ) {
- len = atoi (val);
- free (val);
- msg ("Expecting content-length %d", len);
- if (! (body = http_beyondheaders (buf, buflen)) ) {
- msg ("Body not yet received..");
- return (0);
- }
- bodylen = buflen - (body - buf);
- if (bodylen >= len) {
- msg ("Content-length %d satisfied (got %d bytes), done",
- len, bodylen);
- return (1);
- }
- msg ("Content-length %d not yet received (only %d)", len, bodylen);
- return (0);
- }
-
- if ( (val = http_headerval (buf, "transfer-encoding")) ) {
- msg ("Analyzing transfer-encoding '%s'", val);
- chunked = !strcasecmp (val, "chunked");
- if (!chunked)
- error ("Cannot handle tranfer-encoding '%s'", val);
- free (val);
- if (! (body = http_beyondheaders (buf, buflen)) )
- return (0);
- while (1) {
- msg ("Verifying chunk, now at '%s'", body);
- if (! sscanf ( (char const *) body, "%x", &chunk)) {
- msg ("No chunk size in '%s'", body);
- msgdumpbuf (buf, buflen);
- return (0);
- }
- msg ("Chunk size: %d (0x%x)", chunk, chunk);
- if (! chunk)
- return (1);
- while (ishexdigit (*body))
- body++;
- body = skipcr (body);
- bodylen = buflen - (body - buf);
- if (bodylen < chunk) {
- msg ("Incomplete chunk, should have %d bytes "
- "(only %d) at '%s'",
- chunk, bodylen, body);
- // For debugging purposes: extra dump of the full buffer
- // msgdumpbuf (buf, buflen);
- return (0);
- }
- body += chunk;
- body = skipcr (body);
- }
- }
-
- return (http_headers_done (buf));
-}
diff --git a/src/httpcopy.c b/src/httpcopy.c
@@ -0,0 +1,71 @@
+#include "crossroads.h"
+
+static void copy (int src, int dst, int dir, unsigned tot) {
+ unsigned ncopied = 0;
+ unsigned char buf[TCP_BUFSZ];
+
+ while (ncopied < tot) {
+ if (dir == dir_client_to_server)
+ ncopied += net_copy (src, dst, TCP_BUFSZ, buf, 1);
+ else
+ ncopied += net_copy (dst, src, TCP_BUFSZ, buf, 1);
+ }
+}
+
+unsigned getchunk (int sock, int dest, int isclient) {
+ unsigned ret = 0;
+ unsigned char ch;
+ char *buf = 0;
+
+ /* We match <hex-chunksize> <any-mush> [\r] \n
+ * while copying all to the other socket, and return the
+ * chunk size */
+ while (1) {
+ net_copy (sock, dest, 1, &ch, 1);
+
+ if (ch == '\n') {
+ sscanf (buf, "%x", &ret);
+ free (buf);
+ return (ret);
+ }
+ else
+ buf = xstrcatch (buf, ch);
+ }
+}
+
+void http_copy (HttpHeader *h, int src, int dst, CopyDirection dir) {
+ unsigned char const *mode;
+ unsigned nbytes;
+
+ msg ("Service %s: copying HTTP body from %s",
+ activeservice->name,
+ dir == dir_client_to_server ?
+ "client to server" : "server to client");
+
+ /* Check in what 'mode' we are.
+ * We know 3 modes:
+ * - Content-Length is supplied: we copy that # of bytes
+ * - Transfer-Encoding is chunked: we copy chunks
+ * - None of the above: cpio back/forth until one stops
+ */
+
+ if ( (mode = http_header_val (h, "content-length")) &&
+ mode ) {
+ nbytes = atoi ( (char const *) mode );
+ msg ("Service %s: copying %u bytes of content from %s",
+ activeservice->name, nbytes,
+ dir == dir_client_to_server ?
+ "client to server" : "server to client");
+ copy (src, dst, dir, nbytes);
+ }
+ else if ( (mode = http_header_val (h, "transfer-encoding"))
+ && mode ) {
+ if (strncasecmp ( (char const *) mode, "chunked", 7))
+ error ("Service %s: can't handle transfer-encoding '%s'",
+ activeservice->name, mode);
+ while ( (nbytes = getchunk(src, dst, dir == dir_client_to_server)) )
+ /* Copy the chunk, then copy \r\n */
+ copy (src, dst, dir, nbytes + 2);
+ } else
+ msg ("Service %s: no body to send", activeservice->name);
+}
diff --git a/src/httperror.c b/src/httperror.c
@@ -1,16 +1,12 @@
#include "crossroads.h"
-
-char *str =
-"<h1>Internal Server Error</h1>\n"
-"Your request could not be fulfilled at this time.\n"
-"Please try again later.\n";
+#include "proxyerror.h"
void http_error (int clientsock) {
char *buf = str_printf ("HTTP/1.0 502 Internal Server Error\r\n"
"Content-Length: %d\r\n"
"\r\n"
- "%s",
- strlen(str), str);
+ ERRORTEXT,
+ strlen(ERRORTEXT));
write (clientsock, buf, strlen(buf));
error ("No back end could be selected. Client received error page.");
}
diff --git a/src/httpexpandheader.c b/src/httpexpandheader.c
@@ -0,0 +1,41 @@
+#include "crossroads.h"
+
+char *http_expand_header (char const *h) {
+ char *ret = 0;
+ char const *cp;
+
+ for (cp = h; cp && *cp; cp++) {
+ if (*cp == '%') {
+ switch (*(cp + 1)) {
+ case 'r':
+ ret = xstrcat (ret, client_ip);
+ cp++;
+ break;
+ case 't':
+ ret = xstrcat (ret, ansistamp (tm_localtime));
+ cp++;
+ break;
+ case 'T':
+ ret = xstrcat (ret, ansistamp (tm_gmtime));
+ cp++;
+ break;
+ case 'v':
+ ret = xstrcat (ret, VER);
+ cp++;
+ break;
+ default:
+ if (*(cp + 1)) {
+ ret = xstrcatch (ret, *(cp + 1));
+ cp++;
+ }
+ break;
+ }
+ } else {
+ ret = xstrcatch (ret, *cp);
+ }
+ }
+
+ return (ret);
+}
+
+
diff --git a/src/httpfindcookie.c b/src/httpfindcookie.c
@@ -1,25 +0,0 @@
-#include "crossroads.h"
-
-int http_findcookie (unsigned char const *buf, char const *cookie) {
- char *val, *cp;
-
- if (!cookie)
- return (0);
- if (! (val = http_headerval (buf, "cookie")) ) {
- msg ("No cookies in '%s'", buf);
- return (0);
- }
-
- for (cp = val; cp && *cp; cp = strchr (cp, ';')) {
- while (*cp == ';' || *cp == ' ')
- cp++;
- if (!strncmp (cp, cookie, strlen (cookie))) {
- msg ("Found cookie '%s' in '%s'", cookie, val);
- free (val);
- return (1);
- }
- }
- msg ("Cookie '%s' not present in '%s'", cookie, val);
- free (val);
- return (0);
-}
diff --git a/src/httpheaderaddheader.c b/src/httpheaderaddheader.c
@@ -0,0 +1,10 @@
+#include "crossroads.h"
+
+void http_header_addheader (HttpHeader *m, char const *h) {
+ char *exp;
+
+ exp = http_expand_header (h);
+ msg ("Service %s: adding header '%s'", activeservice->name, exp);
+ m->header = xrealloc (m->header, (m->nheader + 1) * sizeof(char*));
+ m->header[m->nheader++] = exp;
+}
diff --git a/src/httpheaderappendheader.c b/src/httpheaderappendheader.c
@@ -0,0 +1,37 @@
+#include "crossroads.h"
+
+void http_header_appendheader (HttpHeader *m, char const *h) {
+ char *hname, *cp, *exp;
+ int i;
+
+ hname = xstrdup (h);
+ if (! (cp = strchr (hname, ':')) ) {
+ free (hname);
+ return;
+ }
+ *cp = 0;
+ cp++;
+
+ while (isspace (*cp) || *cp == ':')
+ cp++;
+ exp = http_expand_header (cp);
+
+ for (i = 0; i < m->nheader; i++) {
+ if (!m->header[i] || !*m->header[i])
+ continue;
+ // msg ("Append header: comparing '%s' to '%s'", hname, m->header[i]);
+ if (!strncasecmp (m->header[i], hname, strlen(hname))) {
+ m->header[i] = xstrcat (m->header[i], "; ");
+ m->header[i] = xstrcat (m->header[i], exp);
+ /* msg ("Service %s: appending (after existing) header, now '%s'",
+ activeservice->name, m->header[i]); */
+ free (hname);
+ return;
+ }
+ }
+
+ /* msg ("Service %s: appending (setting) header '%s'",
+ activeservice->name, h); */
+
+ http_header_addheader (m, h);
+}
diff --git a/src/httpheaderconnectiontype.c b/src/httpheaderconnectiontype.c
@@ -0,0 +1,26 @@
+#include "crossroads.h"
+
+HttpConnectionType http_header_connectiontype (HttpHeader *h) {
+ unsigned char const *val;
+
+ if (! (val = http_header_val (h, "proxy-connection")) )
+ val = http_header_val (h, "connection");
+
+ if (!val) {
+ msg ("Service %s: Cannot determine connection type, "
+ "no such header", activeservice->name);
+ return (con_unknown);
+ }
+ if (!strncasecmp ((char const *)val, "close", 5)) {
+ msg ("Service %s: Connection-Type is CLOSE",
+ activeservice->name);
+ return (con_close);
+ } else if (!strncasecmp ((char const *)val, "keep-alive", 10)) {
+ msg ("Service %s: Connection-Type is KEEP-ALIVE",
+ activeservice->name);
+ return (con_keepalive);
+ }
+ warning ("Service %s: Unsupported Connection-Type '%s'",
+ activeservice->name, val);
+ return (con_unknown);
+}
diff --git a/src/httpheaderfree.c b/src/httpheaderfree.c
@@ -0,0 +1,10 @@
+#include "crossroads.h"
+
+void http_header_free (HttpHeader *m) {
+ int i;
+
+ if (!m)
+ return;
+ for (i = 0; i < m->nheader; i++)
+ free (m->header[i]);
+}
diff --git a/src/httpheaderhascookie.c b/src/httpheaderhascookie.c
@@ -0,0 +1,29 @@
+#include "crossroads.h"
+
+int http_header_hascookie (HttpHeader *m, char const *cookie) {
+ unsigned char const *val;
+ char *cp, *buf;
+
+ if (!cookie)
+ return (0);
+ if (! (val = http_header_val (m, "cookie")) ) {
+ msg ("Service %s: no cookies in HTTP message", activeservice->name);
+ return (0);
+ }
+
+ buf = xstrdup ((char const *) val);
+ for (cp = buf; cp && *cp; cp = strchr (cp, ';')) {
+ while (*cp == ';' || *cp == ' ')
+ cp++;
+ if (!strncmp (cp, cookie, strlen (cookie))) {
+ msg ("Service %s: found cookie '%s' in '%s'",
+ activeservice->name, cookie, buf);
+ free (buf);
+ return (1);
+ }
+ }
+ msg ("Service %s: cookie '%s' not present in '%s'",
+ activeservice->name, cookie, buf);
+ free (buf);
+ return (0);
+}
diff --git a/src/httpheaderhttpver.c b/src/httpheaderhttpver.c
@@ -0,0 +1,31 @@
+#include "crossroads.h"
+
+double http_header_httpver (HttpHeader *h) {
+ double ret;
+
+ /* We need at least a header line */
+ if (h->nheader < 1) {
+ warning ("Service %s: cannot get HTTP version, no headers",
+ activeservice->name);
+ return (0.9);
+ }
+
+ /* We need HTTP/1.? length */
+ if (strlen (h->header[0]) < 8) {
+ warning ("Service %s: cannot get HTTP version, header line '%s'",
+ activeservice->name, h->header[0]);
+ return (0.9);
+ }
+
+ /* If the line starts with HTTP then it's a server line.
+ * Otherwise it's a client line. */
+ if (!strncmp (h->header[0], "HTTP/", 5))
+ ret = atof (h->header[0] + 5);
+ else
+ ret = atof (h->header[0] + strlen(h->header[0]) - 3);
+
+ msg ("Service %s: HTTP version is %g (headerline %s)",
+ activeservice->name, ret, h->header[0]);
+ return (ret);
+}
+
diff --git a/src/httpheadernew.c b/src/httpheadernew.c
@@ -0,0 +1,11 @@
+#include "crossroads.h"
+
+HttpHeader *http_header_new () {
+ HttpHeader *ret;
+
+ /* Alloc & zero out */
+ ret = xmalloc (sizeof (HttpHeader));
+ memset (ret, 0, sizeof(HttpHeader));
+
+ return (ret);
+}
diff --git a/src/httpheaderread.c b/src/httpheaderread.c
@@ -0,0 +1,49 @@
+#include "crossroads.h"
+
+// #define DEBUG
+
+#ifdef DEBUG
+static void debughdr (HttpHeader *h) {
+ int i;
+ msg ("http header read: %d headers so far", h->nheader);
+ for (i = 0; i < h->nheader; i++)
+ msg ("http header read: header[%d] '%s'",
+ i, h->header[i]);
+}
+
+#define SHOWHEADERS(h) debughdr((h))
+#else
+#define SHOWHEADERS(h)
+#endif
+
+void http_header_read (HttpHeader *h, int srcsock, int dstsock,
+ CopyDirection dir) {
+ unsigned char ch;
+ int last_is_nl = 0, at_start = 1;
+
+ msg ("Service %s: reading HTTP headers from %s",
+ activeservice->name,
+ dir == dir_client_to_server ? "client" : "server");
+ while (1) {
+ // SHOWHEADERS(h);
+ net_copy (srcsock, dstsock, 1, &ch, 0);
+ if (ch == '\r')
+ continue;
+ if (ch == '\n') {
+ if (last_is_nl && h->nheader)
+ break;
+ last_is_nl++;
+ continue;
+ }
+ if (at_start || last_is_nl) {
+ h->header = xrealloc (h->header, (h->nheader + 1) * sizeof(char*));
+ h->header[h->nheader] = 0;
+ h->nheader++;
+ at_start = 0;
+ }
+ h->header[h->nheader - 1] = xstrcatch (h->header[h->nheader - 1], ch); last_is_nl = 0;
+ // SHOWHEADERS(h);
+ }
+
+ SHOWHEADERS(h);
+}
diff --git a/src/httpheadersdone.c b/src/httpheadersdone.c
@@ -1,24 +0,0 @@
-#include "crossroads.h"
-
-int http_headers_done (unsigned char const *buf) {
- char const *cp;
-
- if (http_09_received (buf))
- return (1);
-
- for (cp = strchr ( (char const *) buf, '\n');
- cp && *cp;
- cp = strchr (cp, '\n')) {
- cp++;
- if (*cp == '\r')
- cp++;
- if (*cp == '\n') {
- msg ("HTTP headers all compete in '%s'", buf);
- return (1);
- }
- }
-
- msg ("HTTP headers not (yet) complete in '%s'", buf);
- return (0);
-}
-
diff --git a/src/httpheadersetheader.c b/src/httpheadersetheader.c
@@ -0,0 +1,32 @@
+#include "crossroads.h"
+
+void http_header_setheader (HttpHeader *m, char const *h) {
+ int i;
+ char *hname, *cp, *exp;
+
+ hname = xstrdup (h);
+ if (! (cp = strchr (hname, ':')) ) {
+ free (hname);
+ return;
+ }
+ *cp = 0;
+
+ exp = http_expand_header (h);
+ for (i = 0; i < m->nheader; i++) {
+ if (!m->header[i] || !*m->header[i])
+ continue;
+ if (!strncasecmp (m->header[i], hname, strlen(hname))) {
+ free (m->header[i]);
+ msg ("Service %s: setting (replacing) header '%s'",
+ activeservice->name, exp);
+ m->header[i] = exp;
+ free (hname);
+ return;
+ }
+ }
+
+ msg ("Service %s: setting (adding) header '%s'", activeservice->name, exp);
+ m->header = xrealloc (m->header, (m->nheader + 1) * sizeof(char*));
+ m->header[m->nheader++] = exp;
+ free (hname);
+}
diff --git a/src/httpheaderval.c b/src/httpheaderval.c
@@ -1,38 +1,22 @@
#include "crossroads.h"
-char *http_headerval (unsigned char const *buf, char const *label) {
- char const
- *cp = (char const *) buf,
- *end;
- char *ret;
+unsigned char const *http_header_val (HttpHeader *m, char const *what) {
+ unsigned char const *ret;
- msg ("Searching for header '%s' in '%s'", label, buf);
- while (cp && *cp) {
- // msg ("Now at '%s'", cp);
- if (! strncasecmp (cp, label, strlen(label)) ) {
- cp += strlen(label);
- while (isspace (*cp) || *cp == ':')
- cp++;
- if (! (end = strchr (cp, '\r')) )
- end = strchr (cp, '\n');
- if (! end)
- return (0);
- ret = xmalloc (end - cp + 1);
- strncpy (ret, cp, end - cp);
- ret[end - cp] = 0;
- msg ("Got header value: '%s'", ret);
- return (ret);
+ int i;
+
+ for (i = 0; i < m->nheader; i++) {
+ // msg ("Scanning header %s for %s", m->header[i], what);
+ if (m->header[i] && !strncasecmp (m->header[i], what, strlen(what))) {
+ ret = (unsigned char const *) m->header[i] + strlen(what);
+ if (*ret == ':') {
+ ret++;
+ while (isspace (*ret))
+ ret++;
+ return (ret);
+ }
}
- if (! (cp = strchr (cp, '\n')) )
- return (0);
- cp++;
- if (*cp == '\r')
- cp++;
- if (*cp == '\n')
- return (0);
}
return (0);
}
-
-
diff --git a/src/httpheaderwrite.c b/src/httpheaderwrite.c
@@ -0,0 +1,19 @@
+#include "crossroads.h"
+
+void http_header_write (HttpHeader *m, int sock, int isclient) {
+ int i;
+
+ msg ("Service %s: Sending HTTP headers to %s",
+ activeservice->name, isclient ? "client" : "server");
+
+ for (i = 0; i < m->nheader; i++) {
+ if (! m->header[i] || ! *m->header[i])
+ continue;
+ msg ("Service %s: sending header %d: '%s'",
+ activeservice->name, i, m->header[i]);
+ http_write (sock, isclient, (unsigned char const *) m->header[i],
+ strlen(m->header[i]));
+ http_write (sock, isclient, (unsigned char const *) "\r\n", 2);
+ }
+ http_write (sock, isclient, (unsigned char const *) "\r\n", 2);
+}
diff --git a/src/httpinsertheader.c b/src/httpinsertheader.c
@@ -1,6 +1,6 @@
#include "crossroads.h"
-void http_insert_header (unsigned char **bufp, int *buflen,
+void http_insert_header (unsigned char **bufp, unsigned *buflen,
char const *header) {
unsigned char const *buf, *cp;
unsigned char *newbuf;
diff --git a/src/httpread.c b/src/httpread.c
@@ -1,115 +0,0 @@
-#include "crossroads.h"
-
-void http_read (int sock, int isclient, unsigned char **bufp, int *buflen) {
- char buf[10241];
- struct timeval tv, *tvp, start_tv;
- fd_set readset;
- int nfd, nread;
- unsigned long long microsec;
-
- msg ("Reading HTTP message from socket %d", sock);
-
- /* Reset initial return buffer */
- *bufp = 0;
- *buflen = 0;
-
- /* Promote the verbosity of the backend */
- flag_verbose = activeservice->backend[current_backend].verbosity;
-
- /* Get starting time, update # clients (if this is a backend read) */
- gettimeofday (&start_tv, 0);
- lock_reporter();
- servicereport->nclients++;
- if (!isclient)
- servicereport->backendstate[current_backend].nclients++;
- unlock_reporter();
-
- while (1) {
- FD_ZERO (&readset);
- FD_SET (sock, &readset);
- if (activeservice->connectiontimeout) {
- tv.tv_sec = activeservice->connectiontimeout;
- tv.tv_usec = 0;
- tvp = &tv;
- } else
- tvp = 0;
-
- /* Wait for input */
- nfd = select (FD_SETSIZE, &readset, 0, 0, tvp);
-
- /* Update the intermediate activity as far as the elapsed time
- * is concerned. */
- gettimeofday (&tv, 0);
- microsec = ( (tv.tv_sec * 1000000 + tv.tv_usec) -
- (start_tv.tv_sec * 1000000 + start_tv.tv_usec) );
- gettimeofday (&start_tv, 0);
- if (!isclient)
- mark_activity (0, microsec / 1000000, st_intermediate);
- /* msg ("Spent another %g microsecs in this connection.",
- microsec); */
-
- if (nfd < 1) {
- /* Done, with error */
- lock_reporter();
- servicereport->nclients--;
- if (!isclient)
- servicereport->backendstate[current_backend].nclients--;
- unlock_reporter();
-
- if (nfd < 0)
- error ("Failed to wait for HTTP input: %s", strerror(errno));
- else
- error ("HTTP wait on socket %d interrupted", sock);
- }
-
- /* Got a live socket. */
- nread = read (sock, buf, sizeof(buf) - 1);
- if (nread < 1) {
- lock_reporter();
- servicereport->nclients--;
- if (!isclient)
- servicereport->backendstate[current_backend].nclients--;
- unlock_reporter();
- if (nread < 0)
- warning ("Failed to read HTTP input: %s", strerror(errno));
- else
- warning ("Premature end of HTTP message, no bytes received");
-
- /* If we have a buffer so far, let the caller have it.
- * Otherwise we're done. */
- if (*buflen)
- return;
- exit (0);
- }
-
- /* Got a good buffer from the network.
- * We ASCII-Z terminate the buffer. That's only for display
- * purposes in logging; internally not relevant. */
- buf[nread] = 0;
-
- /* Update the intermediate activity as far as the read bytes
- * are concerned. Update traffic / thruput logs. */
- // msg ("Read %d bytes from socket %d", nread, sock);
- if (!isclient)
- mark_activity (nread, 0, st_intermediate);
- trafficlog ( (unsigned char *) buf, nread, isclient);
- thruputlog ( (unsigned char *) buf, nread, isclient);
-
-
- /* Got a succesful read. Add to buffer, update stats. */
- *bufp = xrealloc (*bufp, *buflen + nread + 1);
- memcpy (*bufp + *buflen, buf, nread + 1);
- *buflen += nread;
-
- if (http_complete (*bufp, *buflen)) {
- lock_reporter();
- servicereport->nclients--;
- if (!isclient)
- servicereport->backendstate[current_backend].nclients--;
- unlock_reporter();
- msg ("HTTP read: complete message (%d bytes) '%s'",
- *buflen, *bufp);
- return;
- }
- }
-}
diff --git a/src/httpserve.c b/src/httpserve.c
@@ -1,64 +1,145 @@
#include "crossroads.h"
-void http_serve (int clientsock, struct sockaddr_in *client) {
- unsigned char *firstbuf, *secondbuf;
- int serversock, buflen, is_continuation, is_ready;
+void http_serve (int clientsock) {
+ int serversock = -1, is_continuation, i;
+ HttpHeader *clientreq, *serverresp;
+ int client_persisting = 1, server_persisting = 1;
/* Fork off a servicer if we're in daemon mode. */
if (fork_tcp_servicer(-1))
return;
- /* Get the initial client request, update logs. */
- http_read (clientsock, 1, &firstbuf, &buflen);
+ /*
+ * Here's the logic of the HTTP services.
+ * - Get the client's request;
+ * - If we're not yet connected to a back end: determine the back
+ * end and connect to it
+ * - Apply all server-directed headers
+ * - Send client's request to the server
+ * - Read back the server's response
+ * - Apply all client-directed headers
+ * - Send the server's response to the client
+ * - Redo from start; this might be a Keep-Alive connection
+ */
- /* Determine the back, maybe a fresh one, or a session continuation. */
- if ( (serversock = http_serversocket (firstbuf, &is_continuation)) < 1 )
- http_error (clientsock);
+ while (1) {
+ /* Get the initial client headers */
+ clientreq = http_header_new();
+ http_header_read (clientreq, clientsock, serversock,
+ dir_client_to_server);
+
+ /* Determine the back end if necessary. */
+ if (serversock == -1) {
+ if ( (serversock = http_serversocket (clientreq,
+ &is_continuation)) < 1 )
+ http_error (clientsock);
+ incr_client_count();
+ if (is_continuation)
+ log_activity_continuation();
+ else
+ log_activity_start ();
+ }
+
+ /* Put all server-directed headers in place. */
+ for (i = 0;
+ i < activeservice->backend[current_backend].naddserverheader;
+ i++)
+ http_header_addheader (clientreq,
+ activeservice->backend[current_backend].addserverheader[i]);
+ for (i = 0;
+ i < activeservice->backend[current_backend].nsetserverheader;
+ i++)
+ http_header_setheader (clientreq,
+ activeservice->backend[current_backend].setserverheader[i]);
+ for (i = 0;
+ i < activeservice->backend[current_backend].nappendserverheader;
+ i++)
+ http_header_appendheader (clientreq,
+ activeservice->backend[current_backend].appendserverheader[i]);
- /* Stick in a real-IP header if required. */
- msg ("pasting - current_backend: %d, insertrealip: %d",
- current_backend,
- activeservice->backend[current_backend].insertrealip);
- if (activeservice->backend[current_backend].insertrealip)
- http_set_realip_header (&firstbuf, &buflen, client);
+ /* See RFC2616. We're actually a proxy here. Clients that talk
+ * HTTP/1.0 MUST be treated as single shot connection clients, even
+ * when they specify Connection: Keep-Alive. */
+ if (http_header_httpver (clientreq) < 1.1) {
+ msg ("Service %s: Client talks HTTP < 1.1, forcing closing "
+ "connections", activeservice->name);
+ http_header_setheader (clientreq, "Connection: close");
+ http_header_setheader (clientreq, "Proxy-Connection: close");
+ client_persisting = 0;
+ } else if (http_header_connectiontype (clientreq) == con_close) {
+ msg ("Service %s: Client asks for connection closing",
+ activeservice->name);
+ client_persisting = 0;
+ }
- /* Log what's going on. We only log the continuation here,
- * because copysockets below will handle the start/end. */
- if (is_continuation)
- log_activity_continuation (client);
+ /* Send client's headers to the back end. */
+ http_header_write (clientreq, serversock, 0);
+
+ /* Copy body from client to server */
+ http_copy (clientreq, clientsock, serversock, dir_client_to_server);
+
+ /* Get server response. */
+ serverresp = http_header_new();
+ http_header_read (serverresp, serversock, clientsock,
+ dir_server_to_client);
- if (http_write (serversock, firstbuf, buflen)) {
- mark_activity (0, 0, st_unavailable);
- error ("Failed to send data to backend '%s'",
- activeservice->backend[current_backend].name);
- }
- free (firstbuf);
+ /* Put all client-directed headers in place. */
+ for (i = 0;
+ i < activeservice->backend[current_backend].naddclientheader;
+ i++)
+ http_header_addheader (serverresp,
+ activeservice->backend[current_backend].addclientheader[i]);
+ for (i = 0;
+ i < activeservice->backend[current_backend].nsetclientheader;
+ i++)
+ http_header_setheader (serverresp,
+ activeservice->backend[current_backend].setclientheader[i]);
+ for (i = 0;
+ i < activeservice->backend[current_backend].nappendclientheader;
+ i++)
+ http_header_appendheader (serverresp,
+ activeservice->backend[current_backend].appendclientheader[i]);
- /* If we have a sticky cookie to insert:
- * Get the server response, stick in a session cookie, send it to the
- * client. */
- is_ready = 0;
- if (activeservice->backend[current_backend].insertcookie) {
- msg ("Getting server response");
- http_read (serversock, 0, &secondbuf, &buflen);
- http_set_sticky_cookie (&secondbuf, &buflen);
- if (http_write (clientsock, secondbuf, buflen))
- error ("Failed to send data to client");
- if (http_complete (secondbuf, buflen)) {
- mark_activity (0, 0, st_available);
- is_ready = 1;
+ /* If the server wants to close the connection, then we must
+ * inform the client. */
+ if (client_persisting) {
+ if (http_header_httpver (serverresp) < 1.1) {
+ msg ("Service %s: Server talks HTTP < 1.1, forcing closing "
+ "connections", activeservice->name);
+ http_header_setheader (serverresp, "Connection: close");
+ http_header_setheader (serverresp, "Proxy-Connection: close");
+ server_persisting = 0;
+ } else if (http_header_connectiontype (serverresp) == con_close) {
+ msg ("Service %s: Server asks for connection closing",
+ activeservice->name);
+ http_header_setheader (serverresp, "Connection: close");
+ http_header_setheader (serverresp, "Proxy-Connection: close");
+ server_persisting = 0;
+ }
}
- free (secondbuf);
+
+ /* Send server headers to the client. */
+ http_header_write (serverresp, clientsock, 1);
+
+ /* Copy body from server to client */
+ http_copy (serverresp, serversock, clientsock, dir_server_to_client);
+
+ /* Free up info from this request/response chat. */
+
+ http_header_free (serverresp);
+ http_header_free (clientreq);
+
+ /* Should we loop around? */
+ if (!client_persisting || !server_persisting)
+ break;
+
+ msg ("Service %s: Analyzing following requests..",
+ activeservice->name);
}
- /* Now we might have to piggyback to and fro -- tho only if the
- * HTTP chat wasn't done yet. */
- if (!is_ready)
- copysockets (clientsock, serversock, client);
-
- /* Normal shutdown: TCP traffic done. */
- msg ("Normal HTTP stop.");
- close (serversock);
- close (clientsock);
- exit (0);
+ /* Copy through all that arrives on back end or client.
+ * This doesn't return... */
+ msg ("Service %s: HTTP service now TCP copying",
+ activeservice->name);
+ copysockets (clientsock, serversock);
}
diff --git a/src/httpserversocket.c b/src/httpserversocket.c
@@ -1,9 +1,10 @@
#include "crossroads.h"
-int http_serversocket (unsigned char const *buf, int *is_continuation) {
+int http_serversocket (HttpHeader *m, int *is_continuation) {
int i, sock;
- msg ("Searching for back end for HTTP request.");
+ msg ("Service %s: searching for back end for HTTP request.",
+ activeservice->name);
/* Try to find a sticky cookie in the request. */
for (i = 0; i < activeservice->nbackend; i++) {
@@ -13,8 +14,10 @@ int http_serversocket (unsigned char const *buf, int *is_continuation) {
activeservice->backend[i].maxconnections <=
servicereport->backendstate[i].nclients)
continue;
- if (http_findcookie (buf, activeservice->backend[i].stickycookie)) {
- msg ("HTTP backend %d selected due to cookie '%s'",
+ if (http_header_hascookie (m,
+ activeservice->backend[i].stickycookie)) {
+ msg ("Service %s: HTTP backend %d selected due to cookie '%s'",
+ activeservice->name,
i,
activeservice->backend[i].stickycookie);
@@ -22,7 +25,7 @@ int http_serversocket (unsigned char const *buf, int *is_continuation) {
* If it doesn't succeed, then we'll do a failover to
* any other back end. */
current_backend = i;
- if ( (sock = backend_connect (0)) >= 0 ) {
+ if ( (sock = backend_connect ()) >= 0 ) {
*is_continuation = 1;
return (sock);
}
@@ -35,7 +38,9 @@ int http_serversocket (unsigned char const *buf, int *is_continuation) {
while (1) {
/* No back end? Nogo. */
if (! backend_available()) {
- msg ("Out of back ends while scanning for HTTP back end");
+ msg ("Service %s: "
+ "out of back ends while scanning for HTTP back end",
+ activeservice->name);
return (-1);
}
@@ -46,8 +51,9 @@ int http_serversocket (unsigned char const *buf, int *is_continuation) {
return (-2);
}
- msg ("Trying back end %d for new HTTP connection", current_backend);
- if ( (sock = backend_connect (0)) >= 0 ) {
+ msg ("Service %s: trying back end %d for new HTTP connection",
+ activeservice->name, current_backend);
+ if ( (sock = backend_connect ()) >= 0 ) {
servicereport->backendstate[current_backend].sessions++;
*is_continuation = 0;
return (sock);
diff --git a/src/httpsetrealipheader.c b/src/httpsetrealipheader.c
@@ -1,10 +0,0 @@
-#include "crossroads.h"
-
-void http_set_realip_header (unsigned char **bufp, int *buflenp,
- struct sockaddr_in *client) {
- char *header;
-
- header = str_printf ("XR-Real-IP: %s", inet_ntoa (client->sin_addr));
- http_insert_header (bufp, buflenp, header);
- free (header);
-}
diff --git a/src/httpsetstickycookie.c b/src/httpsetstickycookie.c
@@ -1,10 +0,0 @@
-#include "crossroads.h"
-
-void http_set_sticky_cookie (unsigned char **bufp, int *buflenp) {
- char *header;
-
- header = str_printf ("Set-Cookie: %s",
- activeservice->backend[current_backend].insertcookie);
- http_insert_header (bufp, buflenp, header);
- free (header);
-}
diff --git a/src/httpwrite.c b/src/httpwrite.c
@@ -1,12 +1,12 @@
#include "crossroads.h"
-int http_write (int sock, unsigned char const *buf, int buflen) {
+int http_write (int sock, int isclient, unsigned char const *buf,
+ unsigned buflen) {
int nwritten, totwritten = 0;
while (totwritten < buflen) {
- nwritten = write (sock, buf + totwritten, buflen - totwritten);
- if (nwritten < buflen - totwritten)
- return (1);
+ nwritten = net_write (sock, buf + totwritten, buflen - totwritten,
+ isclient);
totwritten += nwritten;
}
return (0);
diff --git a/src/incrclientcount.c b/src/incrclientcount.c
@@ -0,0 +1,9 @@
+#include "crossroads.h"
+
+void incr_client_count () {
+ lock_reporter();
+ servicereport->nclients++;
+ if (current_backend >= 0)
+ servicereport->backendstate[current_backend].nclients++;
+ unlock_reporter();
+}
diff --git a/src/initsockaddr.c b/src/initsockaddr.c
@@ -3,7 +3,10 @@
int init_sockaddr (struct sockaddr_in *name,
const char *hostname, int port) {
struct hostent *hostinfo;
-
+
+ if (program_stage != stage_retrying)
+ msg ("Service %s: trying to connect to backend %s:%d",
+ activeservice->name, hostname, port);
name->sin_family = AF_INET;
name->sin_port = htons (port);
if (! (hostinfo = gethostbyname (hostname)) )
diff --git a/src/ishexdigit.c b/src/ishexdigit.c
@@ -1,10 +0,0 @@
-#include "crossroads.h"
-
-int ishexdigit (char c) {
- if ( (c >= '0' && c <= '9') ||
- (c >= 'a' && c <= 'f') ||
- (c >= 'A' && c <= 'F') )
- return (1);
-
- return (0);
-}
diff --git a/src/lexer.l b/src/lexer.l
@@ -189,17 +189,48 @@ stickycookie {
laststring = 0;
return (STICKYCOOKIE);
}
-insertcookie {
- lmsg ("insertcookie");
+addclientheader {
+ lmsg ("addclientheader");
BEGIN (stringstate);
free (laststring);
laststring = 0;
- return (INSERTCOOKIE);
+ return (ADDCLIENTHEADER);
+ }
+setclientheader {
+ lmsg ("setclientheader");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (SETCLIENTHEADER);
+ }
+appendclientheader {
+ lmsg ("appendclientheader");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (APPENDCLIENTHEADER);
+ }
+addserverheader {
+ lmsg ("addserverheader");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (ADDSERVERHEADER);
+ }
+setserverheader {
+ lmsg ("setserverheader");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (SETSERVERHEADER);
+ }
+appendserverheader {
+ lmsg ("appendserverheader");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (APPENDSERVERHEADER);
}
-insertrealip {
- lmsg ("insertrealip");
- return (INSERTREALIP);
- }
on|true|yes {
lmsg ("on");
return (ON);
diff --git a/src/logactivityany.c b/src/logactivityany.c
@@ -0,0 +1,11 @@
+#include "crossroads.h"
+
+void log_activity_any (char const *action) {
+ if (!log_activity)
+ return;
+ if (!logstarted++)
+ openlog ("crossroads", LOG_PID, log_facility);
+ syslog (LOG_NOTICE, "%s %s %s from %s to %s",
+ ansistamp(tm_localtime), action, activeservice->name,
+ client_ip, activeservice->backend[current_backend].server);
+}
diff --git a/src/logactivitycontinuation.c b/src/logactivitycontinuation.c
@@ -1,12 +1,5 @@
#include "crossroads.h"
-void log_activity_continuation (struct sockaddr_in *client) {
- if (!log_activity)
- return;
- if (!logstarted++)
- openlog ("crossroads", LOG_PID, log_facility);
- syslog (LOG_NOTICE, "%s continuing %s from %s to %s",
- ansistamp(), activeservice->name,
- inet_ntoa (client->sin_addr),
- activeservice->backend[current_backend].server);
+void log_activity_continuation () {
+ log_activity_any ("continuing");
}
diff --git a/src/logactivityend.c b/src/logactivityend.c
@@ -1,12 +1,5 @@
#include "crossroads.h"
-void log_activity_end (struct sockaddr_in *client) {
- if (!log_activity)
- return;
- if (!logstarted++)
- openlog ("crossroads", LOG_PID, log_facility);
- syslog (LOG_NOTICE, "%s ending %s from %s to %s",
- ansistamp(), activeservice->name,
- inet_ntoa (client->sin_addr),
- activeservice->backend[current_backend].server);
+void log_activity_end () {
+ log_activity_any ("ending");
}
diff --git a/src/logactivitystart.c b/src/logactivitystart.c
@@ -1,12 +1,5 @@
#include "crossroads.h"
-void log_activity_start (struct sockaddr_in *client) {
- if (!log_activity)
- return;
- if (!logstarted++)
- openlog ("crossroads", LOG_PID, log_facility);
- syslog (LOG_NOTICE, "%s starting %s from %s to %s",
- ansistamp(), activeservice->name,
- inet_ntoa (client->sin_addr),
- activeservice->backend[current_backend].server);
+void log_activity_start () {
+ log_activity_any ("starting");
}
diff --git a/src/makesocket.c b/src/makesocket.c
@@ -39,8 +39,8 @@ int make_socket (int port, char const *ipaddr) {
/* Finallly, bind the socket. */
if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) {
- warning ("Service %s: cannot bind socket to port %d: %s",
- activeservice->name, port, strerror(errno));
+ warning ("Cannot bind socket to port %d: %s",
+ port, strerror(errno));
close (sock);
return (-3);
}
diff --git a/src/markactivity.c b/src/markactivity.c
@@ -7,8 +7,9 @@ void mark_activity (unsigned long long nbytes, double nsec,
unsigned multiplier;
int i;
- /* If we had a signal somewhere, don't update the values. */
- if (interrupted)
+ /* If we had a signal somewhere, don't update the values.
+ * If we don't have a back end yet, don't update the values. */
+ if (interrupted || current_backend == -1)
return;
/* Update values. */
@@ -73,8 +74,8 @@ void mark_activity (unsigned long long nbytes, double nsec,
unlock_reporter();
/* Give some feedback (though not during intermediate updates) */
- if (newstate != st_intermediate)
- msg ("Updated stats for service %s / backend %d (%s): "
+ if (newstate != st_intermediate && program_stage != stage_retrying)
+ msg ("Service %s: updated stats for backend %d (%s): "
"hits=%lu, "
"fails=%lu, secs=%g, avgsecs=%g, "
"bytes=%llu, avgbytes=%lu, state=%s",
diff --git a/src/msg.c b/src/msg.c
@@ -2,15 +2,16 @@
void msg (char const *fmt, ...) {
va_list args;
-
+ char *str;
+
if (!flag_verbose)
return;
va_start (args, fmt);
- if (!daemonized) {
- vfprintf (stderr, fmt, args);
- fprintf (stderr, "\n");
- } else
- writelog (LOG_NOTICE, fmt, args);
+ str = str_vprintf (fmt, args);
+ if (!daemonized)
+ fprintf (stderr, "INFO: %s\n", str);
+ else
+ writelog (LOG_NOTICE, "INFO: %s", str);
va_end (args);
}
diff --git a/src/netcopy.c b/src/netcopy.c
@@ -0,0 +1,121 @@
+#include "crossroads.h"
+
+unsigned net_copy (int cl, int sr, unsigned max, unsigned char *buf,
+ int write_too) {
+ fd_set readset, exset;
+ struct timeval tv, *tvp, tv1, tv2;
+ int nfd;
+ unsigned nread, nwritten, totwritten;
+ CopyDirection dir;
+ double microsec;
+
+ /*
+ msg ("Service %s: Network copy between sockets %d and %d (%u bytes max), "
+ "write flag: %d",
+ activeservice->name, cl, sr, max, write_too);
+ */
+
+ /* Prepare select() sets */
+ FD_ZERO (&readset);
+ FD_ZERO (&exset);
+
+ FD_SET (cl, &readset);
+ FD_SET (cl, &exset);
+ if (sr > 0) {
+ FD_SET (sr, &readset);
+ FD_SET (sr, &exset);
+ }
+
+ /* Prepare timout state */
+ if (activeservice->connectiontimeout) {
+ tv.tv_sec = activeservice->connectiontimeout;
+ tv.tv_usec = 0;
+ tvp = &tv;
+ } else
+ tvp = 0;
+
+ /* Wait for a socket to become readable */
+ gettimeofday (&tv1, 0);
+ nfd = select (FD_SETSIZE, &readset, 0, &exset, tvp);
+ if (nfd < 1) {
+ decr_client_count();
+ log_activity_end();
+ mark_activity (0, 0, st_available);
+ error ("Service %s: network copy stopped after timeout",
+ activeservice->name);
+ }
+
+ /* Check for exceptions. */
+ if (FD_ISSET (cl, &exset)) {
+ decr_client_count();
+ log_activity_end();
+ error ("Service %s: client exception", activeservice->name);
+ }
+ if (sr > 0 && FD_ISSET (sr, &exset)) {
+ decr_client_count();
+ log_activity_end();
+ mark_activity (0, 0, st_unavailable);
+ error ("Service %s: server exception", activeservice->name);
+ }
+
+ /* Do the read. */
+ if (sr > 0 && FD_ISSET (sr, &readset))
+ dir = dir_server_to_client;
+ else
+ dir = dir_client_to_server;
+
+ nread = read (dir == dir_client_to_server ? cl : sr,
+ buf,
+ max);
+ if (nread < 1) {
+ if (nread < 0) {
+ if (dir == dir_server_to_client)
+ mark_activity (0, 0, st_unavailable);
+ error ("Service %s: read error when getting data from %s",
+ activeservice->name,
+ dir == dir_client_to_server ? "client" : "server");
+ } else {
+ msg ("Service %s: %s signals end of data",
+ activeservice->name,
+ dir == dir_client_to_server ? "client" : "server");
+ exit (0);
+ }
+ }
+
+ /* Update thruput / traffic logs. */
+ trafficlog (buf, nread, dir);
+ thruputlog (buf, nread, dir);
+
+ /* Write to dest. */
+ if (write_too) {
+ totwritten = 0;
+ while (totwritten < nread) {
+ nwritten = write (dir == dir_client_to_server ? sr : cl,
+ buf + totwritten,
+ nread - totwritten);
+ if (nwritten < 1) {
+ if (dir == dir_client_to_server)
+ mark_activity (0, 0, st_unavailable);
+ decr_client_count();
+ log_activity_end();
+ error ("Service %s: write error when sending data to %s",
+ dir == dir_client_to_server ? "server" : "client");
+ }
+ totwritten += nwritten;
+ }
+ }
+
+ gettimeofday (&tv2, 0);
+ microsec = ( (tv2.tv_sec * 1000000 + tv2.tv_usec) -
+ (tv1.tv_sec * 1000000 + tv1.tv_usec) );
+ /*
+ msg ("Service %s: shuttled %u bytes in %g sec",
+ activeservice->name,
+ totwritten, microsec / 1000000);
+ */
+
+ mark_activity (totwritten, microsec / 1000000, st_intermediate);
+
+ return (nread);
+}
+
diff --git a/src/netwrite.c b/src/netwrite.c
@@ -0,0 +1,83 @@
+#include "crossroads.h"
+
+int net_write (int sock, unsigned char const *buf, unsigned buflen,
+ int is_client) {
+ int ret, nfd;
+ struct timeval tv, tv1, tv2, *tvp;
+ double microsec;
+ fd_set fdset, exset;
+
+ /* Start timer */
+ gettimeofday (&tv1, 0);
+
+ /* Prepare the write. */
+ FD_ZERO (&fdset);
+ FD_ZERO (&exset);
+ FD_SET (sock, &fdset);
+ FD_SET (sock, &exset);
+ if (activeservice->connectiontimeout) {
+ tv.tv_sec = activeservice->connectiontimeout;
+ tv.tv_usec = 0;
+ tvp = &tv;
+ } else
+ tvp = 0;
+ if ( (nfd = select (FD_SETSIZE, 0, &fdset, &exset, tvp)) < 1) {
+ if (!nfd)
+ warning ("Service %s: Timout while writing %s on socket %d",
+ activeservice->name,
+ is_client ? "client" : "server", sock);
+ else
+ warning ("Service %s: Failed to wait for network of %s "
+ "on socket %d",
+ activeservice->name,
+ is_client ? "client" : "server", sock);
+
+ decr_client_count();
+ log_activity_end();
+ if (!is_client)
+ mark_activity (0, 0, st_unavailable);
+ exit (0);
+ }
+
+ if (FD_ISSET (sock, &exset)) {
+ decr_client_count();
+ log_activity_end();
+ error ("Service %s: exception on %s network",
+ is_client ? "client" : "server");
+ }
+
+ /* We can write! */
+ ret = write (sock, buf, buflen);
+
+ /*
+ msg ("Network write (status %d): tried to send %u bytes to %s",
+ ret, buflen, is_client ? "client" : "server");
+ */
+
+ /* Check for finished / errors */
+ if (ret < 1) {
+ if (ret < 0 && !is_client)
+ mark_activity (0, 0, st_unavailable);
+ decr_client_count();
+ log_activity_end ();
+ if (ret < 0)
+ error ("Service %s: write error when copying to %s",
+ activeservice->name, is_client ? "client" : "server");
+ }
+
+ /* Update activity. */
+ gettimeofday (&tv2, 0);
+ microsec = ( (tv2.tv_sec * 1000000 + tv2.tv_usec) -
+ (tv1.tv_sec * 1000000 + tv1.tv_usec) );
+ mark_activity (ret, microsec / 1000000, st_intermediate);
+
+ /*
+ msg ("Service %s: sent %u bytes to %s (%-10s...)",
+ activeservice->name, ret, is_client ? "client" : "server", buf);
+ */
+
+ /* Signal caller how many bytes were written. */
+ return (ret);
+}
+
+
diff --git a/src/parser.y b/src/parser.y
@@ -80,7 +80,8 @@ static Service cur_service; /* Storage for a handled service */
ONSUCCESS ONFAILURE STRING BACKLOG RANDOM BYDURATION BYSIZE
BYCONNECTIONS CONNECTIONTIMEOUT MAXCONNECTIONS BYORDER TRAFFICLOG
OVER DECAY BINDTO THROUGHPUTLOG TYPE ANY HTTP
- STICKYCOOKIE INSERTCOOKIE INSERTREALIP
+ STICKYCOOKIE ADDCLIENTHEADER SETCLIENTHEADER APPENDCLIENTHEADER
+ ADDSERVERHEADER SETSERVERHEADER APPENDSERVERHEADER
%%
/* Config file grammar rules */
@@ -259,27 +260,84 @@ servicebody:
pimsg ("backend max clients: ", $1.set[i].v.ival);
cur_backend.maxconnections = $1.set[i].v.ival;
break;
- case cf_insertrealipspec:
- pimsg ("backend real IP to insert:",
- $1.set[i].v.ival);
- cur_backend.insertrealip = $1.set[i].v.ival;
- break;
- case cf_insertcookiespec:
- psmsg ("backend cookie to insert:",
- $1.set[i].v.sval);
- cur_backend.insertcookie = $1.set[i].v.sval;
- break;
case cf_stickycookiespec:
psmsg ("backend sticky cookie:",
$1.set[i].v.sval);
cur_backend.stickycookie = $1.set[i].v.sval;
break;
+ case cf_addclientheaderspec:
+ psmsg ("client header to add:",
+ $1.set[i].v.sval);
+ cur_backend.addclientheader =
+ xrealloc (cur_backend.addclientheader,
+ (cur_backend.naddclientheader + 1) *
+ sizeof(char*));
+ cur_backend.addclientheader
+ [cur_backend.naddclientheader++] =
+ $1.set[i].v.sval;
+ break;
+ case cf_setclientheaderspec:
+ psmsg ("client header to set:",
+ $1.set[i].v.sval);
+ cur_backend.setclientheader =
+ xrealloc (cur_backend.setclientheader,
+ (cur_backend.nsetclientheader + 1) *
+ sizeof(char*));
+ cur_backend.setclientheader
+ [cur_backend.nsetclientheader++] =
+ $1.set[i].v.sval;
+ break;
+ case cf_appendclientheaderspec:
+ psmsg ("client header to append:",
+ $1.set[i].v.sval);
+ cur_backend.appendclientheader =
+ xrealloc (cur_backend.appendclientheader,
+ (cur_backend.nappendclientheader + 1) *
+ sizeof(char*));
+ cur_backend.appendclientheader
+ [cur_backend.nappendclientheader++] =
+ $1.set[i].v.sval;
+ break;
+ case cf_addserverheaderspec:
+ psmsg ("server header to add:",
+ $1.set[i].v.sval);
+ cur_backend.addserverheader =
+ xrealloc (cur_backend.addserverheader,
+ (cur_backend.naddserverheader + 1) *
+ sizeof(char*));
+ cur_backend.addserverheader
+ [cur_backend.naddserverheader++] =
+ $1.set[i].v.sval;
+ break;
+ case cf_setserverheaderspec:
+ psmsg ("server header to set:",
+ $1.set[i].v.sval);
+ cur_backend.setserverheader =
+ xrealloc (cur_backend.setserverheader,
+ (cur_backend.nsetserverheader + 1) *
+ sizeof(char*));
+ cur_backend.setserverheader
+ [cur_backend.nsetserverheader++] =
+ $1.set[i].v.sval;
+ break;
+ case cf_appendserverheaderspec:
+ psmsg ("server header to append:",
+ $1.set[i].v.sval);
+ cur_backend.appendserverheader =
+ xrealloc (cur_backend.appendserverheader,
+ (cur_backend.nappendserverheader + 1) *
+ sizeof(char*));
+ cur_backend.appendserverheader
+ [cur_backend.nappendserverheader++] =
+ $1.set[i].v.sval;
+ break;
default:
error ("Internal jam, unhandled type %d "
"in backend specification",
$1.set[i].cf);
}
free ($1.set);
+
/* Verify the backend block, supply defaults,
* And so on.
*/
@@ -292,7 +350,7 @@ servicebody:
/* Add to the list. */
cur_service.backend = xrealloc (cur_service.backend,
++cur_service.nbackend *
- sizeof(Service));
+ sizeof(Backend));
cur_service.backend[cur_service.nbackend - 1] =
cur_backend;
pimsg ("this was backend defintion", cur_service.nbackend);
@@ -534,7 +592,7 @@ backendblock:
backendname:
backendname_expected
IDENTIFIER {
- psmsg ("backend name:", laststr);
+ psmsg ("backend name:", yytext);
cur_backend.name = xstrdup (yytext);
}
;
@@ -616,13 +674,33 @@ backendstatement:
$$ = $1;
}
|
- insertcookiestatement {
- psmsg ("backend cookie insertion:", $1.set[0].v.sval);
+ addclientheaderstatement {
+ psmsg ("addclientheader:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+|
+ setclientheaderstatement {
+ psmsg ("setclientheader:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+|
+ appendclientheaderstatement {
+ psmsg ("appendclientheader:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+|
+ addserverheaderstatement {
+ psmsg ("addserverheader:", $1.set[0].v.sval);
$$ = $1;
}
|
- insertrealipstatement {
- pimsg ("insert real ip:", $1.set[0].v.ival);
+ setserverheaderstatement {
+ psmsg ("setserverheader:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+|
+ appendserverheaderstatement {
+ psmsg ("appendserverheader:", $1.set[0].v.sval);
$$ = $1;
}
;
@@ -748,46 +826,106 @@ stickycookiestatement:
}
;
-insertcookiestatement:
- INSERTCOOKIE
- cookiespecifier
+cookiespecifier:
+ cookie_expected
+ STRING {
+ setlaststr (laststring);
+ free (laststring);
+ laststring = 0;
+ }
+;
+
+addclientheaderstatement:
+ ADDCLIENTHEADER
+ headerstring
semicol {
- psmsg ("insertcookie statement:", laststr);
+ psmsg ("addclientheader statement:", laststr);
$$.n = 1;
- $$.set = xmalloc (sizeof (Confset));
- $$.set[0].cf = cf_insertcookiespec;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_addclientheaderspec;
$$.set[0].v.sval = xstrdup (laststr);
}
;
-insertrealipstatement:
- INSERTREALIP
- onoff_expected
- onoff
+setclientheaderstatement:
+ SETCLIENTHEADER
+ headerstring
semicol {
- pimsg ("insert real IP statement: ", lastnr);
+ psmsg ("setclientheader statement:", laststr);
$$.n = 1;
$$.set = xmalloc (sizeof(Confset));
- $$.set[0].cf = cf_insertrealipspec;
- $$.set[0].v.ival = lastnr;
+ $$.set[0].cf = cf_setclientheaderspec;
+ $$.set[0].v.sval = xstrdup (laststr);
}
;
+appendclientheaderstatement:
+ APPENDCLIENTHEADER
+ headerstring
+ semicol {
+ psmsg ("appendclientheader statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_appendclientheaderspec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
-cookiespecifier:
- cookie_expected
+addserverheaderstatement:
+ ADDSERVERHEADER
+ headerstring
+ semicol {
+ psmsg ("addserverheader statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_addserverheaderspec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
+
+setserverheaderstatement:
+ SETSERVERHEADER
+ headerstring
+ semicol {
+ psmsg ("setserverheader statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_setserverheaderspec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
+
+appendserverheaderstatement:
+ APPENDSERVERHEADER
+ headerstring
+ semicol {
+ psmsg ("appendserverheader statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_appendserverheaderspec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
+
+headerstring:
+ headerstring_expected
STRING {
- setlaststr (laststring);
- free (laststring);
- laststring = 0;
+ setlaststr (laststring);
+ free (laststring);
+ laststring = 0;
}
;
+headerstring_expected: {
+ yyerrmsg = "HTTP header specifier expected";
+}
+;
+
cookie_expected: {
yyerrmsg = "cookie specifier expected";
}
+;
-;
number_expected: {
yyerrmsg = "number expected";
}
diff --git a/src/proxyerror.txt b/src/proxyerror.txt
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>Internal Server Error</title>
+ </head>
+ <body>
+ <h1>Internal Server Error</h1>
+ Your request could not be fulfilled at this time.<br>
+ Please try again later.
+ </body>
+</html>
diff --git a/src/restart.c b/src/restart.c
@@ -13,13 +13,10 @@ void restart (int ac, char **av) {
msg ("Restart: stopping using '%s'", cmd);
if ( (ret = system (cmd)) )
- error ("Failed to stop services (cmd: %s).\n"
- "Crossroads may be in an unstable state, fix by hand!",
- cmd);
-
- /* Wait a few secs to allow the listening socket to free up.
- * sleep (1);
- */
+ warning ("Failed to stop services (cmd: %s).\n"
+ "Attempting to start services, "
+ "but Crossroads may be unstable!\n",
+ cmd);
/* Now run the 'start' action */
org_argv[org_argc - 1] = "start";
diff --git a/src/showstatus.c b/src/showstatus.c
@@ -17,7 +17,7 @@ static char *timestr (double nsec) {
buf = xstrcat (buf, tmp);
free (tmp);
}
- tmp = str_printf ("%us", (unsigned) nsec);
+ tmp = str_printf ("%.2f", nsec);
buf = xstrcat (buf, tmp);
free (tmp);
@@ -37,7 +37,8 @@ static char *bytestr (unsigned long long nbytes) {
else if (nbytes > 1024)
buf = str_printf ("%.2fKb", (double) nbytes / 1024);
else
- buf = str_printf ("%lub", nbytes);
+ buf = str_printf ("%llub", nbytes);
+
return (buf);
}
diff --git a/src/tcpserve.c b/src/tcpserve.c
@@ -54,13 +54,17 @@ void tcpserve (int server_sock) {
error ("Service %s: failure while accepting on server socket: %s",
activeservice->name, strerror(errno));
- msg ("Connect on service %s from %s",
- activeservice->name,
- inet_ntoa (clientname.sin_addr));
+ /* Store client IP, it's used for lots of logging. */
+ client_ip = inet_ntoa (clientname.sin_addr);
+ msg ("Service %s: connection from %s, socket %d",
+ activeservice->name, client_ip, new);
+
+ /* Backend yet to be defined. */
+ current_backend = -1;
/* Incase of a http type service: handle separately. */
if (activeservice->type == type_http) {
- http_serve (new, &clientname);
+ http_serve (new);
close (new);
continue;
}
@@ -97,7 +101,7 @@ void tcpserve (int server_sock) {
/* Connect to the backend. If this fails then we'll sleep
* and re-enter the while(1) loop instead of returning.
* In the loop we may need to decide to return after all. */
- if ( (backend_sock = backend_connect(0)) < 0 ) {
+ if ( (backend_sock = backend_connect()) < 0 ) {
sysrun (activeservice->backend[current_backend].onfailure);
warning ("Service %s: failed to connect to server %s:%d",
activeservice->name,
@@ -115,16 +119,13 @@ void tcpserve (int server_sock) {
close (backend_sock);
break;
}
-
- copysockets (new, backend_sock, &clientname);
- /* If we got here then it's because copysockets()
- * didn't exit(). Only possible in foreground mode.
- * We just close the TCP sockets and go to the next select/accept.
- */
- close (new);
- close (backend_sock);
- break;
+ /* Child branch: piggyback to and fro.
+ * This one never returns. */
+ log_activity_start ();
+ incr_client_count ();
+ flag_verbose = activeservice->backend[current_backend].verbosity;
+ copysockets (new, backend_sock);
}
}
}
diff --git a/src/thruputlog.c b/src/thruputlog.c
@@ -1,13 +1,14 @@
#include "crossroads.h"
-void thruputlog (unsigned char const *buf, int len, int isclient) {
+void thruputlog (unsigned char const *buf, int len, CopyDirection dir) {
struct timeval tv;
static double d_start = 0;
double d_now;
int i;
FILE *f;
- if (! activeservice->backend[current_backend].thruputfile)
+ if (current_backend < 0 ||
+ ! activeservice->backend[current_backend].thruputfile)
return;
/* Initialize timer if necessary. Get current time. */
@@ -32,21 +33,20 @@ void thruputlog (unsigned char const *buf, int len, int isclient) {
return;
}
+#if defined HAVE_FLOCK
+ flock (fileno(f), LOCK_EX);
+#elif defined HAVE_LOCKF
+ lockf (fileno(f), F_LOCK, 0);
+#endif
+
/* Report the activity. */
fprintf (f, "%7.7d %15f %c ",
getpid(),
(d_now - d_start) / 1000000,
- isclient ? 'C' : 'B');
+ dir == dir_client_to_server ? 'C' : 'B');
for (i = 0; i < 100 && i < len; i++)
fputc (isprint (buf[i]) ? buf[i] : '.', f);
fputc ('\n', f);
-
-#if defined HAVE_FLOCK
- flock (fileno(f), LOCK_EX);
-#elif defined HAVE_LOCKF
- lockf (fileno(f), F_LOCK, 0);
-#endif
-
#if defined HAVE_FLOCK
flock (fileno(f), LOCK_UN);
diff --git a/src/trafficlog.c b/src/trafficlog.c
@@ -1,11 +1,12 @@
#include "crossroads.h"
-void trafficlog (unsigned char const *buf, int len, int isclient) {
+void trafficlog (unsigned char const *buf, int len, CopyDirection dir) {
int i, j, n = 0;
char disp[17];
FILE *f;
- if (! activeservice->backend[current_backend].dumpfile)
+ if (current_backend < 0 ||
+ ! activeservice->backend[current_backend].dumpfile)
return;
if ( (! (f = fopen (activeservice->backend[current_backend].dumpfile,
@@ -26,7 +27,8 @@ void trafficlog (unsigned char const *buf, int len, int isclient) {
for (i = 0; i < len; i++) {
if (! n)
- fprintf (f, "%c %4.4x ", isclient ? 'C' : 'B', i);
+ fprintf (f, "%4.4d %c %4.4x ", getpid(),
+ dir == dir_client_to_server ? 'C' : 'B', i);
fprintf (f, " %2.2x", buf[i]);
disp[n] = isprint(buf[i]) ? buf[i] : '.';
diff --git a/src/vsyslog.c b/src/vsyslog.c
@@ -0,0 +1,11 @@
+#include "crossroads.h"
+
+#ifndef HAVE_VSYSLOG
+void vsyslog (int pri, char const *fmt, va_list args) {
+ char *msg;
+
+ msg = str_vprintf (fmt, args);
+ syslog (pri, "%s", msg);
+ free (msg);
+}
+#endif
diff --git a/src/wakeuphandler.c b/src/wakeuphandler.c
@@ -18,37 +18,32 @@ void wakeup_handler () {
/* Now do our stuff. */
for (current_backend = 0;
current_backend < activeservice->nbackend;
- current_backend++)
- if (servicereport->backendstate[current_backend].avail ==
- st_unavailable) {
- if (activeservice->dispatchtype != ds_byorder)
- msg ("Wakeup service %s: trying to wake up backend %s",
- activeservice->name,
- activeservice->backend[current_backend].name);
+ current_backend++) {
- lock_reporter();
- servicereport->backendstate[current_backend].avail = st_waking;
- unlock_reporter();
-
- if ( (sock = backend_connect(1) ) >= 0) {
- warning ("Backend %s of service %s has woken up",
- activeservice->backend[current_backend].name,
- activeservice->name);
- lock_reporter();
- servicereport->backendstate[current_backend].avail =
- st_available;
- unlock_reporter();
- close (sock);
- } else {
- if (activeservice->dispatchtype != ds_byorder)
- msg ("Backend %s of service %s is still unavailable",
- activeservice->backend[current_backend].name,
- activeservice->name);
- lock_reporter();
- servicereport->backendstate[current_backend].avail =
- st_unavailable;
- unlock_reporter();
- }
- }
+ /* Only loop through unavailable back ends. */
+ if (servicereport->backendstate[current_backend].avail !=
+ st_unavailable)
+ continue;
+
+ /* Mark state as WAKING */
+ lock_reporter();
+ servicereport->backendstate[current_backend].avail = st_waking;
+ unlock_reporter();
+
+ /* Try to TCP connect */
+ sock = backend_connect();
+ close (sock);
+
+ /* Set state accordingly */
+ if (sock >= 0)
+ warning ("Backend %s of service %s has woken up",
+ activeservice->backend[current_backend].name,
+ activeservice->name);
+
+ lock_reporter();
+ servicereport->backendstate[current_backend].avail =
+ sock >= 0 ? st_available : st_unavailable;
+ unlock_reporter();
+ }
}
}
diff --git a/src/warning.c b/src/warning.c
@@ -2,11 +2,13 @@
void warning (char const *fmt, ...) {
va_list args;
+ char *str;
va_start (args, fmt);
- if (!daemonized) {
- vfprintf (stderr, fmt, args);
- fprintf (stderr, "\n");
- } else
- writelog (LOG_ERR, fmt, args);
+ str = str_vprintf (fmt, args);
+ if (!daemonized)
+ fprintf (stderr, "WARNING: %s\n", str);
+ else
+ writelog (LOG_ERR, "WARNING: %s", str);
+ free (str);
}
diff --git a/src/writelog.c b/src/writelog.c
@@ -1,6 +1,9 @@
#include "crossroads.h"
-void writelog (int prio, char const *fmt, va_list args) {
+void writelog (int prio, char const *fmt, ...) {
+ va_list args;
+
+ va_start (args, fmt);
if (!logstarted++)
openlog ("crossroads", LOG_PID, log_facility);
vsyslog (prio, fmt, args);
diff --git a/src/xstrcatch.c b/src/xstrcatch.c
@@ -0,0 +1,9 @@
+#include "crossroads.h"
+
+char *xstrcatch (char *what, char ch) {
+ char buf[2];
+
+ buf[0] = ch;
+ buf[1] = 0;
+ return (xstrcat (what, buf));
+}
diff --git a/test/client b/test/client
@@ -3,22 +3,22 @@
use strict;
use Socket;
-if ($#ARGV != 1) {
+if ($#ARGV != 2) {
die <<"ENDUSAGE";
-Usage: client host port
+Usage: client ntimes host port
ENDUSAGE
}
-my ($host, $port) = @ARGV;
+my ($ntimes, $host, $port) = @ARGV;
-print ("Starting client loop to query $host:$port.. press ^C to stop\n");
+print ("Starting client loop to query $host:$port..\n");
select (undef, undef, undef, 0.2);
my %hits = (a => 0, b => 0, c => 0, unknown => 0, errors => 0);
-while (1) {
+for my $n (1..$ntimes) {
open (my $if, "wget -vSO- http://$host:$port 2>&1 |")
or die ("Can't start wget: $!\n");
my ($a, $b, $c, $unk, $err);
@@ -51,5 +51,5 @@ while (1) {
}
print ("\n");
- select (undef, undef, undef, 0.2);
+ # select (undef, undef, undef, 0.2);
};
diff --git a/test/runtest b/test/runtest
@@ -0,0 +1,62 @@
+#!/usr/bin/perl
+
+use strict;
+
+my $tests = <<'ENDTEST';
+ ../src/crossroads -c t1.conf start
+ ./server 12 10000 &
+ ./client 200 localhost 10001
+ sleep 5
+ ../src/crossroads -c t1.conf status
+ ../src/crossroads -c t1.conf stop
+
+ ../src/crossroads -c t2.conf start
+ ./server 12 10000 &
+ ./client 200 localhost 10001
+ sleep 5
+ ../src/crossroads -c t2.conf status
+ ../src/crossroads -c t2.conf stop
+
+ ../src/crossroads -c t3.conf start
+ time ssh -p23 root@localhost; true
+ ../src/crossroads -c t3.conf status
+ ../src/crossroads -c t3.conf stop
+
+ ../src/crossroads -c t4.conf start
+ ./server 12 10000 &
+ ./client 200 localhost 10001
+ sleep 5
+ ../src/crossroads -c t4.conf status
+ ../src/crossroads -c t4.conf stop
+
+ ../src/crossroads -c t6.conf start
+ ./server 12 10000 &
+ ./client 200 localhost 10001
+ sleep 5
+ ../src/crossroads -c t6.conf status
+ ../src/crossroads -c t6.conf stop
+
+ENDTEST
+
+sub runtest {
+ for my $cmd (@_) {
+ $cmd =~ s/^ *//;
+ print STDERR ("runtest: [$cmd]\n");
+ system ($cmd) and die ("runtest: [$cmd] has failed\n");
+ }
+ print ("Press ENTER for next test\n");
+ <STDIN>;
+}
+
+# Main
+my @cmds = ();
+for my $s (split (/\n/, $tests)) {
+ if ($s ne '') {
+ push (@cmds, $s);
+ } else {
+ if ($#cmds > -1) {
+ runtest (@cmds);
+ @cmds = ();
+ }
+ }
+}
diff --git a/test/server b/test/server
@@ -6,7 +6,7 @@ use POSIX;
# Verbose messaging
sub msg {
- print STDERR ("server: ", @_);
+ # print STDERR ("server: ", @_);
}
# Child process catcher
@@ -87,13 +87,14 @@ sub serve ($) {
}
# Main starts here
-if ($#ARGV != 0) {
+if ($#ARGV != 1) {
die <<"ENDUSAGE";
-Usage: server portnr
+Usage: server nsec portnr
ENDUSAGE
}
+alarm (shift (@ARGV));
serve (shift (@ARGV));
diff --git a/test/t1.conf b/test/t1.conf
@@ -2,7 +2,6 @@ service test {
port 10001;
type http;
bindto any;
- revivinginterval 5;
dispatchmode bysize;
connectiontimeout 600;
verbosity on;
@@ -13,7 +12,7 @@ service test {
weight 2;
decay 5;
stickycookie BalancerID=Backend_A;
- insertcookie "BalancerID=Backend_A; Path=/";
+ addclientheader "Set-Cookie: BalancerID=Backend_A; Path=/";
}
backend b {
@@ -22,7 +21,7 @@ service test {
weight 2;
decay 5;
stickycookie BalancerID=Backend_B;
- insertcookie "BalancerID=Backend_B; Path=/";
+ addclientheader "Set-Cookie: BalancerID=Backend_B; Path=/";
}
backend c {
@@ -31,6 +30,6 @@ service test {
weight 3;
decay 5;
stickycookie BalancerID=Backend_C;
- insertcookie "BalancerID=Backend_C; Path=/";
+ addclientheader "Set-Cookie: BalancerID=Backend_C; Path=/";
}
}
diff --git a/test/t2.conf b/test/t2.conf
@@ -13,7 +13,7 @@ service test {
port 10000;
weight 2;
stickycookie BalancerID=Backend_A;
- insertcookie "BalancerID=Backend_A; Path=/";
+ addclientheader "Set-Cookie: BalancerID=Backend_A; Path=/";
}
backend b {
@@ -22,7 +22,7 @@ service test {
port 10000;
weight 3;
stickycookie BalancerID=Backend_B;
- insertcookie "BalancerID=Backend_B; Path=/";
+ addclientheader "Set-Cookie: BalancerID=Backend_B; Path=/";
}
backend c {
@@ -31,6 +31,6 @@ service test {
port 10000;
weight 3;
stickycookie BalancerID=Backend_C;
- insertcookie "BalancerID=Backend_C; Path=/";
+ addclientheader "Set-Cookie: BalancerID=Backend_C; Path=/";
}
}
diff --git a/test/t3.conf b/test/t3.conf
@@ -1,10 +1,10 @@
/* Another testing scenario.
* This one forwards port 23 to localhost:22 (sshd), and logs out
- * after 1 minute of inactivity. */
+ * after 10 secs of inactivity. */
service ssh {
port 23;
- connectiontimeout 60;
+ connectiontimeout 10;
verbosity true;
backend a {
verbosity true;
diff --git a/test/t4.conf b/test/t4.conf
@@ -11,8 +11,8 @@ service test {
port 10000;
verbosity on;
stickycookie BalancerID=Backend_A;
- insertcookie "BalancerID=Backend_A; Path=/";
- insertrealip on;
+ addclientheader "Set-Cookie: BalancerID=Backend_A; Path=/";
+ addserverheader "X-Real-IP: %r";
}
backend b {
@@ -20,8 +20,8 @@ service test {
port 10000;
verbosity on;
stickycookie BalancerID=Backend_B;
- insertcookie "BalancerID=Backend_B; Path=/";
- insertrealip on;
+ addclientheader "Set-Cookie: BalancerID=Backend_B; Path=/";
+ addserverheader "X-Real-IP: %r";
}
backend c {
@@ -29,7 +29,7 @@ service test {
port 10000;
verbosity on;
stickycookie BalancerID=Backend_C;
- insertcookie "BalancerID=Backend_C; Path=/";
- insertrealip on;
+ addclientheader "Set-Cookie: BalancerID=Backend_C; Path=/";
+ addserverheader "X-Real-IP: %r";
}
}
diff --git a/test/t5.conf b/test/t5.conf
@@ -0,0 +1,31 @@
+service test {
+ port 10001;
+ type http;
+ bindto any;
+ revivinginterval 5;
+ dispatchmode bysize;
+ connectiontimeout 600;
+ verbosity on;
+
+ backend a {
+ server localhost;
+ port 10000;
+ weight 2;
+ decay 5;
+ }
+
+ backend b {
+ server localhost;
+ port 10000;
+ weight 2;
+ decay 5;
+ }
+
+ backend c {
+ server localhost;
+ port 10000;
+ weight 3;
+ decay 5;
+
+ }
+}
diff --git a/test/t6.conf b/test/t6.conf
@@ -0,0 +1,44 @@
+service test {
+ port 10001;
+ type http;
+ bindto any;
+ dispatchmode bysize;
+ connectiontimeout 600;
+ verbosity on;
+
+ backend a {
+ server localhost;
+ port 10000;
+ weight 2;
+ decay 5;
+ stickycookie BalancerID=Backend_A;
+ addclientheader "Set-Cookie: BalancerID=Backend_A; Path=/";
+ setclientheader "Host: undisclosed-webserver";
+ setclientheader "X-Timestamp: %t";
+ setserverheader "X-Real-IP: %r";
+ }
+
+ backend b {
+ server localhost;
+ port 10000;
+ weight 2;
+ decay 5;
+ stickycookie BalancerID=Backend_B;
+ setclientheader "Host: undisclosed-webserver";
+ addclientheader "Set-Cookie: BalancerID=Backend_B; Path=/";
+ setclientheader "X-Timestamp: %t";
+ setserverheader "X-Real-IP: %r";
+ }
+
+ backend c {
+ server localhost;
+ port 10000;
+ weight 3;
+ decay 5;
+ stickycookie BalancerID=Backend_C;
+ setclientheader "Host: undisclosed-webserver";
+ addclientheader "Set-Cookie: BalancerID=Backend_C; Path=/";
+ setclientheader "X-Timestamp: %t";
+ setserverheader "X-Real-IP: %r";
+ }
+}
diff --git a/tools/c-conf b/tools/c-conf
@@ -4,7 +4,9 @@ use strict;
use Getopt::Std;
# Globals
-my $VER = "1.03";
+my $VER = "1.04";
+# 1.04 [KK 2006-09-05] C-compilers: gcc/g++ get selected first, instead of
+# cc/c++. Helps HP-UX ports. [Thanks, Bernd Krumboeck.]
# 1.03 [KK 2006-07-19] 'subfiles' keeps track of visited dirs incase of
# recursion. Testing is now by inode, used to be by name.
# 1.02 [KK 2006-06-01] 'findbin' searches for .exe too now, for Cygwin support
@@ -22,8 +24,8 @@ my @def_libdirs = ('/usr/lib',
'/usr/ucblib',
"$ENV{HOME}/lib",
);
-my @c_compilers = ('cc', 'gcc');
-my @cpp_compilers = ('c++', 'g++');
+my @c_compilers = ('gcc', 'cc');
+my @cpp_compilers = ('g++', 'c++');
# Globals
my %opts;
diff --git a/tools/untab b/tools/untab
@@ -29,3 +29,4 @@ for my $in (@ARGV) {
rename ($out, $in) or die ("Can't rename $out to $in: $!\n");
}
}
+