commit d4d4a0cf5b98a1b5a0e9430f35144e9970330163
Author: finwo <finwo@pm.me>
Date: Sat, 3 Jan 2026 18:49:42 +0100
1.00
Diffstat:
88 files changed, 10688 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+*.tar.gz
diff --git a/ChangeLog b/ChangeLog
@@ -0,0 +1,159 @@
+ChangeLog for Crossroads
+------------------------------------------------------------------------------
+
+1.00 [KK 2006-05-05] This version is identical to 0.33, except that
+ crossroads will now be hosted in a publicly accessible SVN
+ repository at svn://svn.e-tunity.com/crossroads.
+
+0.33 [KK 2006-05-05] Fixed small bug in http_findcookie() concerning
+ routing cookies matching.
+
+0.32 [KK 2006-05-02] Fixed set_program_title() to have a separate
+ argv[0] argument, so that 'killall' e.a. match 'crossroads'.
+
+0.31 [KK 2006-04-06] Slight change of functionality in
+ http_serversocket(). Fail over implemented for existing HTTP
+ sessions. Furthermore: small code optimizations.
+
+0.30 [KK 2006-03-17] Optimization in http_serversocket() routine.
+
+0.29 [KK 2006-03-14]
+ - Small bugfix in chunked transfer handling (added: ishexdigit())
+
+0.28 [KK 2006-03-13]
+ - Chunked transfer handling improved.
+ - Flag -V implemented.
+
+0.27 [KK 2006-03-10]
+ - Small changes in HTTP handling (better support for proxies as
+ back ends).
+ - Verbosity of HTTP handling somewhat throttled.
+
+0.26 [KK 2006-02-27]
+ - Backend definitions now support HOSTNAME:PORT in a 'server'
+ specifier.
+ - Status overview compressed to fewer lines.
+ - Documentation updated and refined.
+ - Service types introduced, defaulting to 'any'.
+ - New service 'stickyhttp'. New directives 'stickycookie' and
+ 'insertcookie' to make HTTP sessions.. well.. stick.
+ - Old keywords 'sessiontimeout' and 'bysessions' renamed to
+ 'connectiontimeout' and 'byconnections'.
+
+0.25 [KK 2006-02-08] Program stages are displayed as strings in
+ debug/error messages (see stagetostring.c).
+
+0.24 [KK 2006-01-20] Fixed display of high-ascii chars in the dumplog.
+
+0.23 [KK 2006-01-03] Fixed potential race condition when creating
+ network sockets over and over. Now a short sleeptime is inserted
+ between the retries, so that e.g. new file descriptors become
+ available. Fixed potential fd leak.
+
+0.22 [KK 2005-12-12] Portability issues fixed for Solaris builds
+ (thanks, Lasse Osterild, for pointing this out and fixing
+ it). Version ID added to the docs.
+
+0.21 [KK 2005-12-07] Implemented the 'configtest' command line
+ argument. Configuration file parsing improved (no more
+ shift/reduce conflicts, better error checks). Docs updated.
+
+0.20 [KK 2005-12-06] Changes in the dispatching machine. Added a
+ "dispatchmode bysessions", which selects the back end with the
+ least active clients (adjusted for weights). All durations are
+ now logged with millisecond accuracy, the "dispatchmode
+ byduration" uses this. (All this due to Ray Pittigher's requests
+ to load-balance MySQL RDBMS back ends ;-)
+
+0.19 [KK 2005-11-29] Cosmetic fix of debug messages. Typo fix in the
+ documentation (thanks, Marc Uebel for pointing this one out).
+ Fixed the status updater: updates now occur DURING sessions, not
+ just at the closing. That makes balancing by duration or by size
+ more precise.
+
+0.18 [KK 2005-11-28] Fixed #ifdef/#if clausing of
+ SET_PROC_TITLE_BY_ARGV. (Thanks to Ray Pittigher for pointing
+ this out, Linux on Redhat AS4 was too critical to grok this code.)
+
+0.17 [KK 2005-11-23] Maintenance-related release. Messages are now logged with
+ priority LOG_NOTICE (which ensures debug-information on default
+ Mac OSX). When the creation of a listening socket fails,
+ crossroads will retry after X sleep-seconds (this prevents 'port
+ steals'). Stability of the wakeup handler improved.
+
+0.16 [KK 2005-11-18] Documentation updated for the throughputlog statement.
+
+0.15 [KK 2005-11-17] Bugfix in time calculation of thruputlog().
+
+0.14 [KK 2005-11-16] Statement 'dumptraffic' renamed to 'trafficlog'.
+ Implemented the 'throughputlog' statement.
+
+0.13 [KK 2005-11-09] Implemented support for either flock(), lockf()
+ or none of the above; whichever is available. Added #defines for
+ flock()-related calls and for others, incase your Unix lacks them
+ (thanks to Patrick Debois & Jan Vanhaecht, a Solaris port
+ effort). Added linkage flags for the libs: ucb, nsl, pthread,
+ socket (thanks also to Patrick Debois & Jan Vanhaecht). Added a
+ maxclients statement at the level of back end definitions (thanks
+ to Martin Lonkwitz for suggesting this).
+
+0.12 [KK 2005-11-03] Minor documentation update.
+
+0.11 [KK 2005-10-28] Docs updated. Makefiles somewhat cleaned
+ up. Fixed a 'make install' related issue (thanks go to Johnatan
+ Cua or pointing this one out).
+
+0.10 [KK 2005-10-26] Minor bugfix. The nr. of sessions in the report
+ would not get decreased upon timeout of a session (thanks go to
+ Martin Lonkwitz for pointing this one out).
+
+0.09 [KK 2005-10-18] Warnings of backend connections are avoided in
+ the wakeup handler. This prevents too much messaging in the
+ system log. Implemented commandline action 'restart'. Docs updated.
+
+0.08 [KK 2005-10-13] Minor build changes; port to Mac OSX Darwin.
+
+0.07 [KK 2005-09-15] Small change of verbose messages. Verbose
+ messages now go to stderr (instead of stdout). Added shmctl()
+ calls to make sure that we're the rightful owner of the shared
+ memory segment. Fixed checking of the return value of shmat()
+ for 64bit systems [Thanks, Ray Pittigher, for helping me find
+ this bug].
+
+0.06 [KK 2005-09-14] Improved error messaging during the parsing of
+ the configuration. The onfailure or onsuccess triggers now also
+ run during an initial connect; this used to be only during
+ interrupted sessions. Improved the handling of new connections
+ when no back ends are available: in that case, the client's
+ connection gets denied (instead of accepted and closed).
+ Increased the default sleep time (pause when no back ends are
+ available) to 10 secs; edit etc/Makefile.def if you want to
+ change this. Implemented the statement 'bindto ip-address' in
+ service configurations; this can be used in situations where
+ crossroads must only listen to a specific IP address.
+
+0.05 [KK 2005-09-12] Changed the wakeup handler to generate less
+ messages to syslog. Hearing that a back end still hasn't woken up
+ is not very relevant.
+ Moved release from alpha to beta.
+
+0.04 [KK 2005-07-18] Added manual page. Internal: update of project to
+ svn (instead of CVS). Implemented flag -u to set the effective
+ uid of running services. More documentation updates.
+
+0.03 [KK 2005-07-15] Fixed potential race condition in mark_activity()
+ to set back end states. Implemented set_program_title() so that
+ 'ps ax | grep crossroads' shows what's going on. Change in
+ show_status(), the output is more condensed. Implemented "over N"
+ in dispatchmode, and "weight N" in backend
+ configurations. Rewrote the backend selection algorithm. Then of
+ course lots of changes in the docs.
+
+0.02 [KK 2005-07-11] Maintenance stuff: fixed minor things in the
+ docs, updated timeout message in backend_connect(), fixed minor bug in
+ wakeup_handler(), 'tell' argument now matches services and back
+ ends without regard to casing. More maintenance in the 'upload'
+ target: a crossroads-latest.tar.gz is created to facilitate easy
+ downloads from public.e-tunity.com.
+
+0.01 [KK 2005-07-06] First release, alpha.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,40 @@
+# Central Makefile for crossroads
+# -------------------------------
+
+include etc/Makefile.def
+
+foo:
+ @cat etc/Makefile.help
+
+local:
+ $(MAKE) -C src
+
+install:
+ $(MAKE) -C src install
+
+documentation:
+ $(MAKE) -C doc
+
+clean:
+ $(MAKE) -C src clean
+ $(MAKE) -C doc clean
+ find . -type f -name '*~' -exec rm {} \;
+
+distclean:
+ $(MAKE) -C src distclean
+ $(MAKE) -C doc distclean
+
+dist: documentation
+ tools/e-ver ChangeLog $(VER)
+ tools/makedist $(VER)
+
+# This is only for uploads to public.e-tunity.com, to be done by
+# me ([KK 2005-09-12])
+UPLOADHOST ?= public.e-tunity.com
+upload: dist
+ upload-scp public.e-tunity.com/data/crossroads \
+ ChangeLog doc/*.html doc/*.pdf /tmp/crossroads-$(VER).tar.gz
+ upload-ssh 'echo $(VER) > public.e-tunity.com/data/crossroads/VERSION'
+ upload-ssh 'cd public.e-tunity.com/data/crossroads && \
+ rm -f crossroads-latest.tar.gz && \
+ ln -s crossroads-$(VER).tar.gz crossroads-latest.tar.gz'
diff --git a/doc/ANNOUNCEMENT b/doc/ANNOUNCEMENT
@@ -0,0 +1,19 @@
+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
@@ -0,0 +1,13 @@
+include ../etc/Makefile.def
+
+foo:
+ yo2html -dVER=$(VER) crossroads
+ yo2man -dVER=$(VER) crossroads
+ yo2pdf -dVER=$(VER) crossroads
+ $(MAKE) clean
+
+clean:
+ rm -f *.aux *.latex *.log *.toc
+
+distclean: clean
+ rm -f *.html *.pdf *.man
diff --git a/doc/benchmarking.yo b/doc/benchmarking.yo
@@ -0,0 +1,170 @@
+This section shows how crossroads affects the
+transmitting of HTML data when used as an intermediate 'station'
+through which all data travels.
+
+
+subsect(Benchmark 1: Accessing a proxy via crossroads or directly)
+
+The benchmark was run on a system where the following was varied:
+
+enumeration(
+ eit() A website was recursively spidered through a local squid
+ proxy. The spidering was repeated 10 times, the total was recorded.
+
+ eit() Crossroads was placed in front of the squid proxy, and
+ the website was again recursively spidered. Again, the
+ spidering was repeated 10 times and the total was recorded.)
+
+The crossroads configuration of the second alternative is shown below:
+
+verb(\
+service HttpProxy {
+ port 8080;
+ verbosity on;
+ backend LocalSquid {
+ server 127.0.0.1;
+ port 3128;
+ verbosity on;
+ }
+})
+
+
+subsubsect(Results)
+
+The results of this test are that crossroads causes a negligible
+delay, if it is statistically relevant at all. Without crossroads, the
+timing results are:
+
+verb(\
+real 0m8.146s
+user 0m0.130s
+sys 0m0.253s)
+
+When using crossroads as a middle station, the results are:
+
+verb(\
+real 0m9.481s
+user 0m0.141s
+sys 0m0.230s)
+
+
+subsubsect(Discussion)
+
+The above shown results are quite favorable to crossroads. However,
+one should know that situations will exist where crossroads leans
+towards the 'worst case' scenario, causing up to 50%
+delay.
+
+E.g., imagine a test where a tt(wget) command retrieves a
+HTML document from an Apache server on tt(localhost). Now we have
+(almost) no overhead due to network throttling, hostname lookups and
+so on. When this test would be run either with or without crossroads
+in between, then theoretically, crossroads would cause a much larger
+delay, because it has to read from the server, and then write the same
+information to tt(wget). Each read/write occurs twice when crossroads
+sits in between.
+
+This worst case scenario will however (fortunately) occur only very
+seldom in the real world:
+
+itemization(
+ it() Normally network issues, such as the above mentioned host
+ name lookups or throughput restrictions, will add
+ significantly to the duration of a request. The 'twice as
+ many' read/writes caused by crossroads are then relatively
+ irrelevant.
+
+ it() Normally a significant amount of time will be spent in a
+ back end, due to processing (e.g., when calling a servlet on a
+ back end). Again, this processing time will weigh much heavier
+ than the multiple read/writes.)
+
+
+subsect(Benchmark 2: Crossroads versus Linux Virtual Server (LVS))
+
+LVS is a kernel-based balancer that acts like a masquerading
+firewall: TCP packets that arrive at the balancer are sent to one of
+the configured back ends. LVS has the advantage over crossroads that
+there is no stop-and-go in the transmission; in contrast, crossroads
+needs to send data via an internal buffer. Crossroads has the
+advantage that it offers instantaneous failover because it tries to
+contact the back end for upon each new TCP connection; in contrast,
+LVS isn't aware of downtime of back ends (unless one implements an
+external heartbeat). Also, crossroads offers more complex balancing
+than LVS.
+
+subsubsect(Environment)
+
+On the balancer, LVS was run on port 80, its forwarding set up for two
+equally weighted back ends, using tt(ipvsadm):
+
+verb(\
+ipvsadm -a -t 192.168.1.250:http -r 10.1.1.100:http -m -w 1
+ipvsadm -a -t 192.168.1.250:http -r 10.1.1.101:http -m -w 1)
+
+Crossroads was run on port 81. The configuration file is shown below:
+
+verb(\
+service http {
+ port 81;
+ dispatchmode roundrobin;
+ revivinginterval 5;
+ backend one {
+ server 10.1.1.100;
+ port 80;
+ }
+ backend two {
+ server 10.1.1.101;
+ port 80;
+ }
+})
+
+subsubsect(Tests and results)
+
+In the first test, ports 80 and 81 on the balancer were 'bombed' with
+50 concurrent clients, each requesting a small page 50 times. The
+following timings where measured:
+
+itemization(
+ it() How long it takes to establish a connection;
+ it() How long it takes to retrieve the page.)
+
+The results of this test were:
+
+itemization(
+ it() On average, each client took 0.12 seconds to connect
+ to LVS, and each page was retrieved in 0.14 seconds;
+ it() On average, each client took 0.11 seconds to connect to
+ crossroads, and each page was retrieved in 0.13 seconds.)
+
+In this setup there seems to be no difference between the performance
+of LVS and crossroads!
+
+In a second test, the size of the retrieved page was varied from 2.000
+to 2.000.000 bytes. This test was taken to see whether crossroads would
+show performance degradation when transferring larger amounts of data.
+
+For each page size, 30 concurrent clients were started, that retrieved
+the page 50 times. Again, the connect times and processing times where
+recorded.
+
+The results of the total time (connect time + retrieval time)
+are shown in the below table:
+
+table(3)(rrr)(
+ rowline()
+ row(
+ cell(bf(Bytes)) cell(bf(LVS timing)) cell(bf(Crossroads timing)))
+ row(
+ cell(2000) cell(0.130741688) cell(0.12739582))
+ row(
+ cell(20000) cell(0.490916224) cell(0.50376901))
+ row(
+ cell(200000) cell(3.799440328) cell(4.33125273))
+ row(
+ cell(2000000) cell(45.25090855) cell(45.9600728))
+ rowline())
+
+Again, the results show that crossroads performs just as effectively
+as LVS, even with large data chunks!
+
+\ No newline at end of file
diff --git a/doc/compiling.yo b/doc/compiling.yo
@@ -0,0 +1,175 @@
+subsect(Prerequisites)
+
+The creation of crossroads requires:
+
+itemization(
+ it() Standard Unix tools, such as tt(sed), tt(awk), tt(Perl)
+ (5.00 or better);
+
+ it() A POSIX-compliant C compiler;
+
+ it() The grammar generation tools tt(bison) and tt(flex);
+
+ it() Support for SYSV IPC, networking and so on.
+)
+
+Basically a Linux or Apple MacOSX box will do nicely once you make
+sure that tt(bison) and tt(flex) are installed. To compile and install
+crossroads, follow these steps.
+
+
+subsect(Compiling and installing)
+
+itemization(
+ it() Obtain the source distribution. It can be found on
+ lurl(http://public.e-tunity.com). The distribution comes as an
+ archive tt(crossroads-)em(X.YY)tt(.tar.gz), where em(X.YY) is
+ a version number.
+
+ it() Unpack the archive in a sources directory using tt(tar
+ xzf crossroads-)em(X.YY)tt(.tar.gz). The contents spill into a
+ subdirectory tt(crossroads-)em(X.YY/).
+
+ it() Change-dir into the directory.
+
+ it() Next, edit tt(etc/Makefile.def) and verify that all
+ compilation settings are to your likings. The settings are
+ explained in the file. bf(Note that) the default distribution
+ of tt(Makefile.def) is suited for Linux or Apple MacOSX
+ systems. On other Unices, or on non-Unix systems, you must
+ particularly pay attention to tt(SET_PROC_TITLE_BY...). When
+ in doubt, comment out all tt(SET_PROC_TITLE...)
+ settings. Crossroads will work nevertheless, but it won't show
+ nice titles in tt(ps) listings. Also there's a macro
+ tt(EXTRA_LIBS) to add linkage flags (an example for a Solaris
+ build is included).
+
+ it() Now crossroads is ready for compilation. Do a tt(make
+ local) followed by tt(make install). The latter step may have
+ to be done by the user tt(root) if the tt(BINDIR) setting of
+ tt(etc/Makefile.def) points to a root-owned directory.
+
+ it() The documentation doesn't install in this process. If you
+ want to install the documentation, then proceed as follows:
+
+ itemization(
+ it() Optionally, tt(cp doc/crossroads.html)
+ em(htmldirectory/); where em(htmldirectory) is the destination
+ directory for your HTML manuals;
+
+ it() Optionally, tt(cp doc/crossroads.pdf)
+ em(pdfdirectory/); where em(pdfdirectory) is the
+ 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/);
+
+ 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).)
+
+)
+
+
+subsect(Configuring crossroads)
+
+Now that the binary is available on your system, you need to create a
+suitable tt(/etc/crossroads.conf). Use this manual or the output of
+tt(crossroads samplconf) to get started.
+
+Once you have the configuration ready, start crossroads with
+tt(crossroads start). Test the availability of your services and back
+ends. Monitor how crossroads is doing with:
+
+itemization(
+ it() In one terminal, run the script:
+ verb(\
+while [ 1 ] ; do
+ tput clear
+ crossroads status
+ sleep 3
+done)
+
+ bf(Note) that depending on your system you might need
+ tt(sleep 3s), i.e., with an tt(s) appended.
+
+ it() In another terminal, run:
+ verb(\
+while [ 1 ] ; do
+ tput clear
+ ps ax | grep crossroads | grep -v grep
+ sleep 3
+done)
+
+ bf(Note) that depending on your system you might need
+ tt(ps -ef) instead of tt(ps ax).
+
+ it() In yet another terminal, run tt(tail -f
+ /var/log/messages) (supply the appropriate system log file if
+ tt(/var/log/messages) doesn't work for you).)
+
+Now thoroughly test the availability of your back ends through
+crossroads. The status display will show an updated view of which back
+ends are selected and how busy they are. The process list will show
+which crossroads daemons are running. Finally, the tailing of
+tt(/var/log/messages) shows what's going on -- especially if you have
+tt(verbosity true) statements in the configuration.
+
+
+subsect(A boot script)
+
+Finally, you may want to create a boot-time startup script. The exact
+procedure depends on the used Unix flavor.
+
+subsubsect(SysV Style Startup)
+
+On SysV style systems, there's a startup script directory
+tt(/etc/init.d) where bootscripts for all utilities are located.
+You may have the tt(chkconfig) utility to automate the task of
+inserting scripts into the boot sequence, but
+otherwise the steps will resemble the following.
+
+itemization(
+ it() Create a script tt(crossroads) in tt(/etc/init.d) similar to the
+ following:
+
+verb(\
+#!/bin/sh
+/usr/local/bin/crossroads -v $@)
+
+ The stated directory tt(/usr/local/bin) must correspond with
+ the installation path. The flag tt(-v) causes the startup to
+ be more 'verbose'. However, once daemonized, the verbosity is
+ controlled by the appropriate statements in the configuration.
+
+ it() Determine your 'runlevel': usually 3 when your system is
+ running in text-mode only, or 5 when you are a graphical
+ interface. If your runlevel is 3, then:
+
+verb(\
+root> cd /etc/rc.d/rc3.d
+root> ln -s /etc/init.d/crossroads S99crossroads
+root> ln -s /etc/init.d/crossroads K99crossroads)
+
+ This creates startup (tt(S*)) and stop (tt(K*)) links that
+ will be run when the system enters or leaves a given runlevel.
+
+ If your runlevel is 5, then the right tt(cd) command is to
+ tt(/etc/rc.d/rc5.d). Alternatively, you can create the
+ symlinks in both runlevel directories.)
+
+subsubsect(BSD Style Startup)
+
+On BSD style systems, daemons are booted directly from tt(/etc/rc) and
+related scripts. Incase you have a file tt(/etc/rc.local), edit it,
+and add the statement:
+
+verb(/usr/local/bin/crossroads start)
+
+If your BSD system lacks tt(/etc/rc.local), then you may need to start
+Crossroads from tt(/etc/rc). Your mileage may vary.
diff --git a/doc/config.yo b/doc/config.yo
@@ -0,0 +1,581 @@
+The configuration that crossroads uses is normally stored in the file
+tt(/etc/crossroads.conf). This location can be overruled using the
+command line flag tt(-c).
+
+This section explains the syntax of the configuration file, and what
+all settings do.
+
+subsect(General language elements)
+
+This section describes the general elements of the crossroads
+configuration language.
+
+
+subsubsect(Empty lines and comments)
+
+Empty lines are of course allowed in the
+configuration. Crossroads recognizes three formats of comment:
+
+itemization(
+ it() C-style, between tt(/*) and tt(*/),
+ it() C++-style, starting with tt(//) and ending with the end
+ of the text line;
+ it() Shell-style, starting with tt(#) and ending with the end
+ of the text line.)
+
+Simply choose your favorite editor and use the comment that 'looks
+best'.footnote(I favor C or C++ comment. My favorite editor em(emacs)
+can be put in tt(cmode) and nicely highlight what's comment and what's
+not. And as a bonus it will auto-indent the configuration!)
+
+
+subsubsect(Keywords, numbers, identifiers, generic strings)
+
+In a configuration file, statements are identified by em(keywords),
+such as tt(service), tt(verbosity). These are reserved words.
+
+Many keywords require an em(identifier) as the argument. E.g, a
+service has a unique name, which must start with a letter or
+underscore, followed by zero or more letters, underscores, or
+digits. Therefore, in the statement tt(service myservice), the keyword is
+tt(service) and the identifier is tt(myservice).
+
+Other keywords require a numeric argument. Crossroads knows only
+non-negative integer numbers, as in tt(port 8000). Here, tt(port) is
+the keyword and tt(8000) is the number.
+
+Yet other keywords require 'generic strings', such as hostname
+specifications or system commands. Such generic strings contain any
+characters (including white space) up to the terminating statement
+character tt(;). If a string must contain a semicolon, then it must
+be enclosed in single or double quotes:
+
+itemization(
+ it() tt(This is a string;) is a string that starts at tt(T)
+ and ends with tt(g)
+ it() tt("This is a string";) is the same, the double quotes
+ are not necessary
+ it() tt("This is ; a string";) has double quotes to protect
+ the inner ;)
+
+Finally, an argument can be a 'boolean' value. Crossroads knows
+tt(true), tt(false), tt(yes), tt(no), tt(on), tt(off). The keywords
+tt(true), tt(yes) and tt(on) all mean the same and can be used
+interchangeably; as can the keywords tt(false), tt(no) and tt(off).
+
+
+subsect(Service definitions)
+
+Service definitions are blocks in the configuration file that
+state what is for each service. A service definition starts with
+tt(service), followed by a unique identifier, and by statements in
+tt({) and tt(}). For example:
+
+verb(\
+// Definition of service 'www':
+service www {
+ ...
+ ... // statements that define the
+ ... // service named 'www'
+ ...
+})
+
+The configuration file can contain many service blocks, as long as the
+identifying names differ. The following list shows possible
+statements. Each statement must end with a semicolon, except for the
+tt(backend) statement, which has is own block (more on this later).
+
+startdit()
+
+dit(The type statement) defines how crossroads handles the stated
+service. There are currently two types: tt(any) and
+tt(stickyhttp). The type tt(any) means that crossroads doesn't
+interpret the contents of a TCP stream, but only distributes streams
+over back ends. The type tt(stickyhttp) means that crossroads has to
+analyze what's in the messages, does magical HTTP cookie tricks, and
+so on -- to ensure that multiple connections are treated as one
+session.
+
+Unless you really need sticky HTTP sessions, use the type tt(any) (the
+default), even for HTTP protocols.
+
+itemization(
+ it() Syntax: tt(type) em(typespecifier) tt(;)
+ it() Where em(typespecifier) is tt(any) or tt(stickyhttp)
+ it() Default: tt(any))
+
+dit(The port statement) defines to which TCP port a service
+'listens'. E.g. tt(port 8000) says that this service will accept
+connections on port 8000.
+
+itemization(
+ it() Syntax: tt(port) em(number) tt(;)
+ it() There is no default. This is a required setting.)
+
+dit(The bindto statement) is used in situations where crossroads
+should only listen to the stated port at a given IP address. E.g.,
+tt(bindto 127.0.0.1) causes crossroads to 'bind' the service only to
+the local IP address. Network connections from other hosts won't be
+serviced. By default, crossroads binds a service to all presently
+active IP addresses at the invoking host.
+
+itemization(
+ it() Syntax: tt(bindto) em(ip-address) tt(;)
+ it() where em(ip-address) is a numeric IP address, such as
+ tt(192.168.1.45), or the keyword tt(any)
+ it() Default: tt(any))
+
+dit(Verbosity statements) come in two forms: tt(verbosity on) or
+tt(verbosity off). When 'on', log messages to tt(/var/log/messages)
+are generated that show what's going on.footnote(Actually, the
+messages go to tt(syslog(3)), using facility tt(LOG_DAEMON) and
+priority tt(LOG_INFO). In most (Linux) cases this will mean: output to
+tt(/var/log/messages). On Mac OSX the messages go to
+tt(/var/log/system.log).) The keyword tt(verbose) is an alias for
+tt(verbosity).
+
+itemization(
+ it() Syntax: tt(verbosity) em(setting) tt(;)
+ it() Or: tt(verbose) em(setting) tt(;)
+ it() where em(setting) is tt(true), tt(yes) or tt(on) to turn
+ verbosity on; or tt(false), tt(no), tt(off) to turn it off.
+ it() Default: tt(off).)
+
+dit(The dispatch mode) controls how crossroads selects a back end from
+a list of active back ends. The below text shows the bare
+syntax. See section ref(howselected) for a textual explanation.
+
+The syntax is:
+
+itemization(
+ it() tt(dispatchmode roundrobin): Simply the 'next in line' is
+ chosen. E.g, when 3 back ends are active, then the usage
+ series is 1, 2, 3, 1, 2, 3, and so on.
+
+ Roundrobin dispatching is the default method, when no
+ tt(dispatchmode) statement occurs.
+
+ it() tt(dispatchmode random): Random selection. Probably only
+ for stress testing.
+
+ it() tt(dispatchmode bysize [ over) em(connections) tt(]):
+ The next back end is the one
+ that has transferred the least number of bytes. This
+ selection mechanism assumes that the more bytes, the heavier
+ the load.
+
+ The modifier tt(over) em(connections) is optional. (The square
+ brackets shown above are not part of the statement but
+ indicate optionality.) When given,
+ the load is computed as an average of the last stated number of
+ connections. When this modifier is absent, then the load is
+ computed over all connections since startup.
+
+ it() tt(dispatchmode byduration [ over) em(connections) tt(]):
+ The next back end is the one
+ that served connections for the shortest time. This mechanism
+ assumes that the longer the connection, the heavier the load.
+
+ it() tt(dispatchmode byconnections): The next back end is the one
+ with the least active connections. This mechanism assumes that
+ each connection to a back end represents load. It is usable
+ for e.g. database connections.
+
+ it() tt(dispatchmode byorder): The first back end is selected
+ every time, unless it's unavailable. In that case the second
+ is taken, and so on.)
+
+The selection algorithm is only used when clients are serviced that
+aren't part of a sticky HTTP session. This is the case during:
+
+itemization(
+ it() all client requests of a service type tt(any);
+ it() new sessions of a service type tt(stickyhttp).)
+
+When tt(stickyhttp) is in effect and a session is underway, then the
+previously used back end is always selected -- regardless of
+dispatching mode.
+
+Your 'right' dispatch mode will depend on the type of service. Given
+the fact that crossroads doesn't know (and doesn't care) how to
+estimate load from a network traffic stream, you have to choose an
+appropriate dispatch mode to optimize load balancing. In most cases,
+tt(roundrobin) or tt(byconnections) will do the job just fine.
+
+dit(A reviving interval definition) is needed when crossroads
+determines that a back end is temporarily unavailable. This will
+happen when:
+
+itemization(
+ it() The back end cannot be reached (network connection
+ fails);
+ it() The network connection to the back end suddenly dies.)
+
+An example of the definition is tt(revivinginterval 10). When this
+reviving interval is given, crossroads will check each 10 seconds
+whether unavailable back ends have woken up yet. A back end is
+considered awake when a network connection to that back end can
+succesfully be established.
+
+itemization(
+ it() Syntax: tt(revivinginterval) em(number) tt(;)
+ it() Default: 0, meaning no revivals will occur.)
+
+dit(The maximum number of connections) is specified using
+tt(maxconnections). There is one argument; the number of concurrent
+established connections that may be active within one service.
+
+'Throttling' the number of connections is a way of preventing Denial of
+Service (DOS) attacks. Without a limit, numerous network connections
+may spawn so many server instances, that the service ultimately breaks
+down and becomes unavailable.
+
+itemization(
+ it() Syntax: tt(maxconnections) em(number) tt(;)
+ it() Default: 0, meaning that all client connections will be
+ accepted.)
+
+dit(The TCP back log size) is a number that controls how many
+'waiting' network connections may be queued, before a client simply
+cannot connect. The syntax is e.g. tt(backlog 5) to cause crossroads
+to have 5 waiting connections for 1 active connection.
+The backlog queue shouldn't be too
+high, or clients will experience timeouts before they can actually
+connect. The queue shouldn't be too small either, because clients
+would be simply rejected. Your mileage may vary.
+
+itemization(
+ it() Syntax: tt(backlog) em(number) tt(;)
+ it() Default: zero, which takes the operating system's default
+ value for socket back log size.)
+
+dit(Reporting based: the shared memory key.) Different crossroad
+invocations must 'know' of each others activity. E.g, tt(crossroad
+status) must be able to get to the actual state information of all
+running services. This is internally implemented through shared
+memory, which is reserved using a key.
+
+Normally crossroads will supply a shared memory key, based on the
+service port and bitwise or-ed with a magic number. In situations
+where this conflicts with existing keys (of other programs, having
+their own keys), you may supply a chosen value.
+
+The syntax is e.g. tt(shmkey 123456). The actual key value doesn't
+matter much, as long as it's unique and as long as each invocation of
+crossroads uses it.
+
+itemization(
+ it() Syntax: tt(shmkey) em(number) tt(;)
+ it() Default: 0, which means that crossroads will 'guess' its
+ own key, based on TCP port and a magic number.)
+
+dit(Connection timeouts:) Sometimes, clients simply won't close a network
+connection which leads to unnecessary resource usage. To avoid this,
+one might state e.g. tt(connectiontimeout 300). This instructs crossroads to
+consider a connection where nothing has happened for 300 seconds as
+'finished'. Crossroads will terminate the connection when this timeout
+is exceeded.
+
+itemization(
+ it() Syntax: tt(connectiontimeout) em(number) tt(;)
+ it() Default: 0, meaning that crossroads will not try to
+ determine timeouts.)
+
+enddit()
+
+
+subsect(Backend definitions)
+
+Inside the service definitions as are described in the previous
+section, em(backend definitions) must also occur. Backend definitions
+are started by the keyword tt(backend), followed by an identifier
+(the back end name) , and statements inside tt({) and tt(}):
+
+verb(\
+service myservice {
+ ...
+ ... // statements that define the
+ ... // service named 'myservice'
+ ...
+
+ backend mybackend {
+ ...
+ ... // statements that define the
+ ... // backend named 'mybackend'
+ ...
+ }
+})
+
+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:
+
+startdit()
+
+ dit(Server:) Each back end must be identified by the network name
+ (server name) where it is located. For example: tt(server
+ 10.1.1.23), or tt(server web.mydomain.org). A TCP port specifier
+ can follow the server name, as in tt(server web.mydomain.org:80).
+
+ itemization(
+ it() Syntax: tt(server) em(servername) tt(;)
+ it() Or: tt(server) em(servername)tt(:)em(port) tt(;)
+ it() There is no default. This is a required setting.)
+
+ dit(Port:) When the tt(server) specifier doesn't include a TCP
+ port, then this statement is used to define the port at which the
+ back end expects its traffic. There is one argument, the (numeric)
+ port number.
+
+ itemization(
+ it() Syntax: tt(port) em(number) tt(;)
+ it() There is no default. The port must be defined either in
+ the tt(server) setting or using the tt(port) specifier.)
+
+ dit(Verbosity:) Similar to tt(service) specifications, a
+ tt(backend) can have its own verbosity (tt(on) or tt(off)). When
+ tt(on), traffic to and fro this back end is reported.
+
+ itemization(
+ it() Syntax: tt(verbosity) em(setting) tt(;)
+ it() Or: tt(verbose) em(setting) tt(;)
+ it() where em(setting) is tt(true), tt(yes) or tt(on) to turn
+ verbosity on; or tt(false), tt(no), tt(off) to turn it off.
+ it() Default: tt(off).)
+
+ dit(Maxconnections:) This setting states how many concurrent connections
+ a back end connection may accept. Note that there is also a
+ tt(maxconnections) statement for the overall service description.
+
+ The difference is that a tt(maxconnections) statement at the level of
+ a service description avoids too many hits from the outside (DOS
+ prevention). A tt(maxconnections) statement at the level of a back end
+ description makes sure that this particular back end doesn't get
+ overloaded.
+
+ itemization(
+ it() Syntax: tt(maxconnections) em(number) tt(;)
+ it() where em(number) is the maximum number of concurrent
+ client connections.
+ it() Default: 0, meaning that there is no limit.)
+
+ dit(Weight:) To influence how backends are selected by size or by
+ duration, a backend can specify its 'weight' in the process. The
+ higher the weight, the less likely a back end will be chosen. The
+ default is 1.
+
+ The weighing mechanism only applies to the dispatch modes
+ tt(byconnections), tt(bysize) and tt(byduration).
+ The weight is in fact a multiplier. E.g., if backend A has
+ tt(weight 2) and backend B has tt(weight 1), then backend B will
+ be selected all the time, until its usage parameter is twice as
+ large as the parameter of A.
+
+ itemization(
+ it() Syntax: tt(weight) em(number) tt(;)
+ it() Default: 1)
+
+ dit(Decay:) To make sure that a 'spike' of activity doesn't
+ influence the perceived load of a back end forever, you may
+ specify a certain decay. E.g, the statement tt(decay 10) makes
+ sure that the load that crossroads computes for this back end (be
+ it in seconds or in bytes) is decreased by 10% each time that
+ bf(an other) back end is hit. Decays are not applied to the count
+ of concurrent connections.
+
+ This means that when a given back end is hit, then its usage data
+ of the transferred bytes and the connection duration are updated
+ using the actual number of bytes and actual duration. However,
+ when a different back end is hit, then the usage data are
+ decreased by the specified decay.
+
+ itemization(
+ 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(stickyhttp),
+ then backends should 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 stickyhttp;
+ 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(Event triggers:) As special 'hooks' for actions, two triggers
+ are available: tt(onfailure) and tt(onsuccess). The argument to
+ the triggers is a system command that is executed when a connection
+ with the back end either fails or succeeds.
+
+ itemization(
+ it() Syntax: tt(onfailure) em(commandline) tt(;) and
+ tt(onsuccess) em(commandline) tt(;)
+ it() There is no default.)
+
+ dit(Debugging and Performance aids:) Incase the traffic between
+ client and backend
+ must be debugged, the statement tt(trafficlog) em(filename) can
+ be issued. This causes the traffic to be dumped in hexadecimal
+ format to the stated filename.
+
+ Traffic sent by the client is prefixed by a bf(C), traffic sent by
+ the back end is prefixed by a bf(B). Below is a sample traffic
+ dump of a browser trying to get a HTML page. The server replies
+ that the page was not modified.
+
+ verb(\
+C 0000 47 45 54 20 68 74 74 70 3a 2f 2f 77 77 77 2e 63 GET http://www.c
+C 0010 73 2e 68 65 6c 73 69 6e 6b 69 2e 66 69 2f 6c 69 s.helsinki.fi/li
+C 0020 6e 75 78 2f 6c 69 6e 75 78 2d 6b 65 72 6e 65 6c nux/linux-kernel
+C 0030 2f 32 30 30 31 2d 34 37 2f 30 34 31 37 2e 68 74 /2001-47/0417.ht
+C 0040 6d 6c 20 48 54 54 50 2f 31 2e 31 0d 0a 43 6f 6e ml HTTP/1.1..Con
+C 0050 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73 65 0d 0a nection: close..
+.
+. etcetera
+.
+B 0000 48 54 54 50 2f 31 2e 30 20 33 30 34 20 4e 6f 74 HTTP/1.0 304 Not
+B 0010 20 4d 6f 64 69 66 69 65 64 0d 0a 44 61 74 65 3a Modified..Date:
+B 0020 20 54 75 65 2c 20 31 32 20 4a 75 6c 20 32 30 30 Tue, 12 Jul 200
+B 0030 35 20 30 39 3a 34 39 3a 34 37 20 47 4d 54 0d 0a 5 09:49:47 GMT..
+B 0040 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 Content-Type: te
+B 0050 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 65 74 xt/html; charset
+.
+. etcetera
+.)
+
+ Turning on traffic dumps will em(significantly)
+ slow down crossroads.
+
+ itemization(
+ it() Syntax: tt(trafficlog) em(filename) tt(;)
+ it() There is no default. Without this directive, traffic is
+ not logged.)
+
+ Besides tt(trafficlog), there is also a directive
+ tt(throughputlog). This directive also takes one argument, a
+ filename. The file is appended, and the following information is
+ logged:
+
+ itemization(
+ it() The process ID of the crossroads image that serves the
+ TCP connection;
+ it() The time of the request, in seconds and microseconds
+ since start of the run;
+ it() A bf(C) when the request originated at the client, or
+ bf(B) when the request originated at the back end;
+ it() The first 100 bytes of the request.)
+
+ As an example, consider the following (the lines are shortened for
+ brevity and prefixed by line numbers for clarity):
+
+ verb(
+1 0000594 0.000001 C GET http://public.e-tunity.com/index.html...
+2 0000594 0.173713 B HTTP/1.0 200 OK..Date: Fri, 18 Nov 2005 0...
+3 0000594 0.278125 B width="100" bgcolor="#e0e0e0" valign="to...
+4 0000595 0.000001 C GET http://public.e-tunity.com/css/style/...
+5 0000594 0.944339 B /a></td>.. </tr>.</table>.</td><td class...
+6 0000594 0.946356 B smallboxdownl">Download</td>.. <td class...
+7 0000594 0.961102 B td><td class="smallboxodd" valign="top"><...
+8 0000595 0.698215 B HTTP/1.0 304 Not Modified..Date: Fri, 18 ...)
+
+ This tells us that:
+
+ itemization(
+ it() Line 1: PID 594 served a request that originated at
+ the client. The corresponding time is (almost) 0 seconds,
+ so this is really the start of the run.
+ it() Line 2: A back end replied 0.17 seconds later, and
+ 0.28 seconds later, it was still replying (this is the
+ third line, again a bf(B)-type transmission).
+ it() Line 4: PID 595 served a request that originated
+ at the client. Again, the corresponding time is (almost)
+ 0 seconds, since this is the first conversation part of
+ this connection.
+ it() Lines 5 to 7: This is the continuation of line 2. Line 7
+ is the last line of the bf(B) series (not visible from
+ the example, but trust me, it is), so that we may
+ conclude that it took the back end 0.96 seconds to serve
+ the file tt(index.html) requested in line 1.
+ it() Line 8: This is the answer to the client's request of
+ line 4 (you can tell by the process ID number).
+ So the back end took 0.68 seconds to confirm that
+ the stylesheet requested in line 4 wasn't modified.)
+
+ It is also worth while remembering that the start time of a bf(C)
+ request is the time that crossroads sees the activity. Any latency
+ between the true client and crossroads is obviously not
+ included. This is illustrated by the below simple ASCII art:
+
+ verb(
+client ---->---->---->--->*crossroads ====>====>====>
+ \
+ back end
+ /
+client ----<----<----<---< crossroads ====<====<====<
+)
+
+ This simple picture shows a typical HTTP request that originates
+ at a client, travels to crossroads, and is relayed via the back
+ end. The bf(C) entry in a throughput log is the time when
+ crossroads sees the request, indicated by an asterisk. The bf(B)
+ entries are the times that it takes the back end to answer,
+ indicated by tt(===) style lines. Therefore, the true roundtrip
+ time will be longer than the number of seconds that are logged in
+ the throughput log: the latency between client and crossroads
+ isn't included in that measurement.
+
+ Summarizing, the throughput times of a client-back end connection
+ can be analyzed using the directive tt(throughputlog). In a
+ real-world analysis, you'd probably want to write up a script to
+ analyze the output and to compute round trip times. Such scripts
+ are not (yet) included in Crossroads.
+
+ itemization(
+ it() Syntax: tt(throughputlog) em(filename) tt(;)
+ it() There is no default. Without this directive, the
+ throughput is not logged.)
+
+enddit()
+
+
diff --git a/doc/crossroads.html b/doc/crossroads.html
@@ -0,0 +1,1928 @@
+<a name="defs.yo"></a><html><head>
+<title>Crossroads 0.33</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 0.33</h1>
+<h2>Karel Kubat</h2>
+
+<h2>e-tunity</h2><h2>2005, 2006, ff.</h2>
+
+<blockquote><em>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.</em></blockquote>
+
+<h1>Table of Contents</h1>
+<dl>
+<dl>
+<dt><h3><a href="#l1">1: Introduction</a></h3></dt>
+<dl>
+<dt><a href="#l2">1.1: Obtaining Crossroads</a></dt>
+<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>
+</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="#l8">4: The configuration</a></h3></dt>
+<dl>
+<dt><a href="#l9">4.1: General language elements</a></dt>
+<dl>
+<dt><a href="#l10">4.1.1: Empty lines and comments</a></dt>
+<dt><a href="#l11">4.1.2: Keywords, numbers, identifiers, generic strings</a></dt>
+</dl>
+<dt><a href="#l12">4.2: Service definitions</a></dt>
+<dt><a href="#l13">4.3: Backend definitions</a></dt>
+</dl>
+<dt><h3><a href="#l14">5: Tips, Tricks and Remarks</a></h3></dt>
+<dl>
+<dt><a href="#l15">5.1: How back ends are selected in load balancing</a></dt>
+<dl>
+<dt><a href="#l16">5.1.1: Bysize, byduration or byconnections?</a></dt>
+<dt><a href="#l17">5.1.2: Averaging size and duration</a></dt>
+<dt><a href="#l18">5.1.3: Specifying decays</a></dt>
+<dt><a href="#l19">5.1.4: Adjusting the weights</a></dt>
+<dt><a href="#l20">5.1.5: Throttling the number of concurrent connections</a></dt>
+</dl>
+<dt><a href="#l21">5.2: HTTP Session Stickiness</a></dt>
+<dl>
+<dt><a href="#l22">5.2.1: Don't use stickiness!</a></dt>
+<dt><a href="#l23">5.2.2: But if you must..</a></dt>
+</dl>
+<dt><a href="#l24">5.3: Configuration examples</a></dt>
+<dl>
+<dt><a href="#l25">5.3.1: A load balancer for three webserver back ends</a></dt>
+<dt><a href="#l26">5.3.2: An HTTP forwarder when travelling</a></dt>
+<dt><a href="#l27">5.3.3: SSH login with enforced idle logout</a></dt>
+</dl>
+</dl>
+<dt><h3><a href="#l28">6: Benchmarking</a></h3></dt>
+<dl>
+<dt><a href="#l29">6.1: Benchmark 1: Accessing a proxy via crossroads or directly</a></dt>
+<dl>
+<dt><a href="#l30">6.1.1: Results</a></dt>
+<dt><a href="#l31">6.1.2: Discussion</a></dt>
+</dl>
+<dt><a href="#l32">6.2: Benchmark 2: Crossroads versus Linux Virtual Server (LVS)</a></dt>
+<dl>
+<dt><a href="#l33">6.2.1: Environment</a></dt>
+<dt><a href="#l34">6.2.2: Tests and results</a></dt>
+</dl>
+</dl>
+<dt><h3><a href="#l35">7: Compiling and Installing</a></h3></dt>
+<dl>
+<dt><a href="#l36">7.1: Prerequisites</a></dt>
+<dt><a href="#l37">7.2: Compiling and installing</a></dt>
+<dt><a href="#l38">7.3: Configuring crossroads</a></dt>
+<dt><a href="#l39">7.4: A boot script</a></dt>
+<dl>
+<dt><a href="#l40">7.4.1: SysV Style Startup</a></dt>
+<dt><a href="#l41">7.4.2: BSD Style Startup</a></dt>
+</dl>
+
+<p><hr><p>
+<p>
+<a name="l1"></a>
+<h2>1: Introduction</h2>
+<a name="intro"></a>Crossroads is a daemon that basically accepts TCP connections
+at preconfigured ports, and given a list of 'back ends'
+distributes each incoming connection to one of the back ends,
+so that a client request is
+served. Additionally, crossroads maintains an internal
+administration of the back end connectivity: if a back end isn't
+usable, then the client request is handled using another back
+end. Crossroads will then periodically check whether a previously not
+usable back end has come to life yet. Also, crossroads can select
+back ends by estimating the load, so that balancing is achieved.
+<p>
+Using this approach, crossroads serves as load balancer and fail over
+utility. Crossroads will very likely not be as reliable as
+hardware based balancers, since it always will require a server to
+run on. This server, in turn, may become a new Single Point of
+Failure (SPOS). However, in situations where cost efficiency is an issue,
+crossroads may be a good choice. Furthermore, crossroads can be
+deployed in situations where a hardware based balancing already
+exists and augmenting service reliability is needed. Or, crossroads may be
+run off a diskless system, which again improves reliability of the
+underlying hardware.
+<p>
+This document describes how to use crossroads, how to configure it
+in order to increase the reliability of your systems, and how to
+compile the program from its sources. This document is
+also available in <a href="crossroads.pdf">PDF</a> format.
+<p>
+<a name="l2"></a>
+<h3>1.1: Obtaining Crossroads</h3>
+<p>
+As quick reference, here are some important URL's for Crossroads:
+<p>
+<ul>
+ <li> <a href="http://public.e-tunity.com">http://public.e-tunity.com</a> is the site that serves
+ Crossroads and other packages. You can browse this at leisure
+ for documentation, sources, and so on.
+<p>
+<li>
+ <a href="http://public.e-tunity.com/crossroads/crossroads-latest.tar.gz">http://public.e-tunity.com/crossroads/crossroads-latest.tar.gz</a> is
+ the 'latest' distribution archive. (Older versions aren't
+ stored. If you really need an old version, contact me at
+ <a href="mailto:karel@e-tunity.com">karel@e-tunity.com</a>.)
+<p>
+<li>
+ <a href="http://public.e-tunity.com/crossroads/crossroads.html">http://public.e-tunity.com/crossroads/crossroads.html</a> is
+ the documentation in HTML format (this text). Substitute
+ <em>.pdf</em> for <em>.html</em> to get the documentation in PDF format.</ul>
+<p>
+<a name="l3"></a>
+<h3>1.2: Copyright and Disclaimer</h3>
+<p>
+Crossroads is distributed as-is, without assumptions of fitness
+or usability. You are free to use crossroads to your
+liking. It's free, and as with everything that's free: there's
+also no warranty.
+<p>
+You are allowed to make modifications to the source code of
+crossroads, and you are allowed to (re)distribute crossroads, as
+long as you include this text, all sources, and if applicable: all
+your modifications, with each distribution.
+<p>
+While you are allowed to make any and all changes to the sources,
+I would appreciate hearing about them. If the changes concern new
+functionality or bugfixes, then I'll include them in a next
+release, stating full credits.
+<p>
+<a name="l4"></a>
+<h3>1.3: Terminology</h3>
+<p>
+Throughout this document, the following terms are used: (Many
+more meanings of the terms will exist -- yes, I am aware of that. I'm
+using the terms here in a very strict sense.)
+<p>
+<dl>
+ <p><dt><strong>A client</strong><dd> is a process that initiates a network connection
+ to get contact with some service.
+ <p><dt><strong>A service</strong><dd> or <strong>server process</strong> or <strong>listener</strong>
+ is a central application
+ that accepts network connections from clients and sevices
+ them.
+ <p><dt><strong>Back ends</strong><dd> are locations where crossroads looks in
+ order to service its clients. Crossroads sits 'in between'
+ and does its tricks. Therefore, as far as the back ends
+ are concerned, crossroads behaves like a client. As far as
+ the true client is concerned, crossroads behaves like the
+ service. The communication is however transparent: neither
+ client nor back end are aware of the middle position of
+ crossroads.
+ <p><dt><strong>A connection</strong><dd> is a network conversation between client and service,
+ where data are transferred to and fro. As
+ far as crossroads is concerned, success means that a
+ connection can be established without errors on
+ the network level. Crossroads isn't aware of service
+ pecularities. E.g., when a webserver answers <code>HTTP/1.0
+ 500 Server Error</code> then crossroads will see this as a
+ succesful connection, though the user behind a browser may
+ think otherwise.
+ <p><dt><strong>Back end selection algorithms</strong><dd> are methods by which
+ crossroads determines which back end it will talk to
+ next. Crossroads has a number of built-in algorithms,
+ which may be configured per service.
+ <p><dt><strong>Back end states</strong><dd> are the statusses of each back end that
+ is known to crossroads. A back end may be available,
+ (temporarily) unavailble or truly down. When a back end is
+ temporarily unavailable, then crossroads will periodically
+ check whether the back end has come to life yet (that is,
+ if configured so).
+ <p><dt><strong>A spike</strong><dd> is a sudden increase in activity, leading to
+ extra load on a given service. When crossroads is in
+ effect and when the spike occurs in one connection,
+ then obviously the spike will also appear at one
+ of the back ends. However, crossroads will see the spike
+ and will make sure that a subsequent request goes to an
+ other back end. In contrast, when several connections
+ arrive simultaneously and cause a spike, then crossroads
+ will be able to distribute the connections over several
+ back ends, thereby 'flattening out' the increase.
+ <p><dt><strong>Load balancing</strong><dd> means that incoming client requests are
+ distributed over more than just one back end (which wouldn't be the
+ case if you wouldn't be running crossroads). Enabling load
+ balancing is nothing more than duplicating services over
+ more than one back end, and having something (in this
+ case: crossroads) distribute the requests, so that per
+ back end the load doesn't get too high.
+ <p><dt><strong>An HTTP session</strong><dd> is a series of separate network connections
+ that originate from one browser. E.g., to fill the display
+ with text and images, the browser hits a website several times.
+ 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.
+ <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
+ browser are forced to go to the same back end, instead of
+ being balanced to other ones).
+ <p><dt><strong>Back end usage</strong><dd> is measured by crossroads in order to be
+ able to determine back end selection. Crossroads stores
+ information about the number of active connections, the
+ transferred bytes and
+ about the connection duration. These numbers can be used to
+ estimate which back end is the least used -- and
+ therefore, presumably, the best candidate for a new
+ request.
+ <p><dt><strong>Fail over</strong><dd> is almost always used when load balancing is in
+ effect. The distributor of client requests (crossroads of
+ course) can also monitor back ends, so that incase a back
+ end is 'down', it is no longer accessed.
+ <p><dt><strong>Service downtime</strong><dd> normally occurs when a service is
+ switched off. Downtime is obviously avoided when fail over
+ is in effect: a back end can be taken out of service in a
+ controlled manner, without any client noticing it.
+</dl>
+<p>
+<a name="l5"></a>
+<h3>1.4: Porting issues for pre-0.26 installations</h3>
+<p>
+As of version 0.26 the syntax of the configuration file has
+ changed. In particular:
+<p>
+<ul>
+ <li> The keyword <code>maxconnections</code> is now used instead of
+ <code>maxclients</code>;
+ <li> The keyword <code>connectiontimeout</code> is now used instead of
+ <code>sessiontimeout</code>.</ul>
+<p>
+Therefore when converting configuration files to the new syntax,
+ the above keywords must be changed. (The reason for these changes
+ is that 0.26 introduces <em>sticky HTTP sessions</em> that span
+ multiple TCP connections, and the term
+ <em>session</em> is used strictly in that sense -- and no longer for a
+ TCP connection.)
+<p>
+<a name="l6"></a>
+<h2>2: Installation for the impatient</h2>
+<a name="impatient"></a>
+For the impatient, here's the very-quick-but-very-superficial recipy
+for getting crossroads up and running:
+<p>
+<ul>
+<p>
+<li> Obtain the crossroads source archive. Change-dir to a
+ 'sources' directory on your system and unpack the archive.
+<p>
+<li> Change-dir into the created directory <code>crossroads/</code>.
+<p>
+<li> Type <code>make install</code>. This installs the crossroads
+ binary into <code>/usr/local/bin/</code>. If the compilation doesn't
+ work on your system, check <code>etc/Makefile.def</code> for hints.
+<p>
+<li> Create a file <code>/etc/crossroads.conf</code>. In it state
+ something like:
+<p>
+<pre>
+service www {
+ port 80;
+ revivinginterval 15;
+ backend one {
+ server 10.1.1.100:80;
+ }
+ backend two {
+ server 10.1.1.101:80;
+ }
+}
+</pre>
+
+<p>
+Of course, make sure that you have webservers running on
+ 10.1.1.100 and 10.1.1.101.
+<p>
+<li> Type <code>crossroads start</code>.
+<p>
+<li> Surf to the machine where crossroads is running. You will
+ see the pages served by the back ends 10.1.1.100 or
+ 10.1.1.101.
+<p>
+<li> To monitor the status of crossroads, type <code>crossroads
+ status</code>.
+</ul>
+<p>
+<a name="l7"></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
+supports a number of flags (e.g., to overrule the location of the
+configuration file). The actual usage information is always obtained
+by typing <code>crossroads</code> without any arguments. Crossroads then
+displays the allowed arguments.
+<p>
+This section shows the basic usage.
+<p>
+<ul>
+ <li> <code>crossroads start</code> and <code>crossroads stop</code> are typical
+ actions that are run from system startup scripts. The
+ meaning is self-explanatory.
+ <li> <code>crossroads restart</code> is a combination of the former
+ two. Beware that a restart may cause discontinuity in
+ service; it is just a shorthand for typing the 'stop' and
+ 'start' actions after one another.
+ <li> <code>crossroad status</code> reports on each running
+ service. Per service, the state of each back end is
+ reported.
+ <li> <code>crossroads tell</code> <em>service backend state</em> is a
+ command line way of telling crossroads that a given back
+ end, of a given service, is in a given state. Normally
+ crossroads maintains state information itself, but by
+ using <code>crossroads tell</code>, a back end can be e.g. taken
+ 'off line' for servicing.
+ <li> <code>crossroads configtest</code> tells you whether the
+ configuration is syntactially correct.
+ <li> <code>crossroads services</code> reports on the configured
+ services. In contrast to <code>crossroads status</code>, this
+ option only shows what's configured -- not what's up and
+ running. Therefore, <code>crossroads services</code> doesn't
+ report on back end states.
+ <li> <code>crossroads sampleconf</code> shows a sample configuration on
+ screen. A good way of quicky viewing the configuration
+ file syntax, or of getting a start for your own
+ configuration <code>/etc/crossroads.conf</code>.
+</ul>
+<p>
+<a name="l8"></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
+command line flag <code>-c</code>.
+<p>
+This section explains the syntax of the configuration file, and what
+all settings do.
+<p>
+<a name="l9"></a>
+<h3>4.1: General language elements</h3>
+<p>
+This section describes the general elements of the crossroads
+configuration language.
+<p>
+<a name="l10"></a>
+<strong>4.1.1: Empty lines and comments</strong>
+<p>
+Empty lines are of course allowed in the
+configuration. Crossroads recognizes three formats of comment:
+<p>
+<ul>
+ <li> C-style, between <code>/*</code> and <code>*/</code>,
+ <li> C++-style, starting with <code>//</code> and ending with the end
+ of the text line;
+ <li> Shell-style, starting with <code>#</code> and ending with the end
+ of the text line.</ul>
+<p>
+Simply choose your favorite editor and use the comment that 'looks
+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="l11"></a>
+<strong>4.1.2: Keywords, numbers, identifiers, generic strings</strong>
+<p>
+In a configuration file, statements are identified by <em>keywords</em>,
+such as <code>service</code>, <code>verbosity</code>. These are reserved words.
+<p>
+Many keywords require an <em>identifier</em> as the argument. E.g, a
+service has a unique name, which must start with a letter or
+underscore, followed by zero or more letters, underscores, or
+digits. Therefore, in the statement <code>service myservice</code>, the keyword is
+<code>service</code> and the identifier is <code>myservice</code>.
+<p>
+Other keywords require a numeric argument. Crossroads knows only
+non-negative integer numbers, as in <code>port 8000</code>. Here, <code>port</code> is
+the keyword and <code>8000</code> is the number.
+<p>
+Yet other keywords require 'generic strings', such as hostname
+specifications or system commands. Such generic strings contain any
+characters (including white space) up to the terminating statement
+character <code>;</code>. If a string must contain a semicolon, then it must
+be enclosed in single or double quotes:
+<p>
+<ul>
+ <li> <code>This is a string;</code> is a string that starts at <code>T</code>
+ and ends with <code>g</code>
+ <li> <code>"This is a string";</code> is the same, the double quotes
+ are not necessary
+ <li> <code>"This is ; a string";</code> has double quotes to protect
+ the inner ;</ul>
+<p>
+Finally, an argument can be a 'boolean' value. Crossroads knows
+<code>true</code>, <code>false</code>, <code>yes</code>, <code>no</code>, <code>on</code>, <code>off</code>. The keywords
+<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="l12"></a>
+<h3>4.2: Service definitions</h3>
+<p>
+Service definitions are blocks in the configuration file that
+state what is for each service. A service definition starts with
+<code>service</code>, followed by a unique identifier, and by statements in
+<code>{</code> and <code>}</code>. For example:
+<p>
+<pre>
+// Definition of service 'www':
+service www {
+ ...
+ ... // statements that define the
+ ... // service named 'www'
+ ...
+}
+</pre>
+
+<p>
+The configuration file can contain many service blocks, as long as the
+identifying names differ. The following list shows possible
+statements. Each statement must end with a semicolon, except for the
+<code>backend</code> statement, which has is own block (more on this later).
+<p>
+<dl>
+<p>
+<p><dt><strong>The type statement</strong><dd> defines how crossroads handles the stated
+service. There are currently two types: <code>any</code> and
+<code>stickyhttp</code>. The type <code>any</code> means that crossroads doesn't
+interpret the contents of a TCP stream, but only distributes streams
+over back ends. The type <code>stickyhttp</code> means that crossroads has to
+analyze what's in the messages, does magical HTTP cookie tricks, and
+so on -- to ensure that multiple connections are treated as one
+session.
+<p>
+Unless you really need sticky HTTP sessions, use the type <code>any</code> (the
+default), even for HTTP protocols.
+<p>
+<ul>
+ <li> Syntax: <code>type</code> <em>typespecifier</em> <code>;</code>
+ <li> Where <em>typespecifier</em> is <code>any</code> or <code>stickyhttp</code>
+ <li> Default: <code>any</code></ul>
+<p>
+<p><dt><strong>The port statement</strong><dd> defines to which TCP port a service
+'listens'. E.g. <code>port 8000</code> says that this service will accept
+connections on port 8000.
+<p>
+<ul>
+ <li> Syntax: <code>port</code> <em>number</em> <code>;</code>
+ <li> There is no default. This is a required setting.</ul>
+<p>
+<p><dt><strong>The bindto statement</strong><dd> is used in situations where crossroads
+should only listen to the stated port at a given IP address. E.g.,
+<code>bindto 127.0.0.1</code> causes crossroads to 'bind' the service only to
+the local IP address. Network connections from other hosts won't be
+serviced. By default, crossroads binds a service to all presently
+active IP addresses at the invoking host.
+<p>
+<ul>
+ <li> Syntax: <code>bindto</code> <em>ip-address</em> <code>;</code>
+ <li> where <em>ip-address</em> is a numeric IP address, such as
+ <code>192.168.1.45</code>, or the keyword <code>any</code>
+ <li> Default: <code>any</code></ul>
+<p>
+<p><dt><strong>Verbosity statements</strong><dd> come in two forms: <code>verbosity on</code> or
+<code>verbosity off</code>. When 'on', log messages to <code>/var/log/messages</code>
+are generated that show what's going on. (Actually, the
+messages go to <code>syslog(3)</code>, using facility <code>LOG_DAEMON</code> and
+priority <code>LOG_INFO</code>. In most (Linux) cases this will mean: output to
+<code>/var/log/messages</code>. On Mac OSX the messages go to
+<code>/var/log/system.log</code>.) The keyword <code>verbose</code> is an alias for
+<code>verbosity</code>.
+<p>
+<ul>
+ <li> Syntax: <code>verbosity</code> <em>setting</em> <code>;</code>
+ <li> Or: <code>verbose</code> <em>setting</em> <code>;</code>
+ <li> where <em>setting</em> is <code>true</code>, <code>yes</code> or <code>on</code> to turn
+ verbosity on; or <code>false</code>, <code>no</code>, <code>off</code> to turn it off.
+ <li> Default: <code>off</code>.</ul>
+<p>
+<p><dt><strong>The dispatch mode</strong><dd> controls how crossroads selects a back end from
+a list of active back ends. The below text shows the bare
+syntax. See section <a href="crossroads.html#howselected">5.1</a> for a textual explanation.
+<p>
+The syntax is:
+<p>
+<ul>
+ <li> <code>dispatchmode roundrobin</code>: Simply the 'next in line' is
+ chosen. E.g, when 3 back ends are active, then the usage
+ series is 1, 2, 3, 1, 2, 3, and so on.
+<p>
+Roundrobin dispatching is the default method, when no
+ <code>dispatchmode</code> statement occurs.
+<p>
+<li> <code>dispatchmode random</code>: Random selection. Probably only
+ for stress testing.
+<p>
+<li> <code>dispatchmode bysize [ over</code> <em>connections</em> <code>]</code>:
+ The next back end is the one
+ that has transferred the least number of bytes. This
+ selection mechanism assumes that the more bytes, the heavier
+ the load.
+<p>
+The modifier <code>over</code> <em>connections</em> is optional. (The square
+ brackets shown above are not part of the statement but
+ indicate optionality.) When given,
+ the load is computed as an average of the last stated number of
+ connections. When this modifier is absent, then the load is
+ computed over all connections since startup.
+<p>
+<li> <code>dispatchmode byduration [ over</code> <em>connections</em> <code>]</code>:
+ The next back end is the one
+ that served connections for the shortest time. This mechanism
+ assumes that the longer the connection, the heavier the load.
+<p>
+<li> <code>dispatchmode byconnections</code>: The next back end is the one
+ with the least active connections. This mechanism assumes that
+ each connection to a back end represents load. It is usable
+ for e.g. database connections.
+<p>
+<li> <code>dispatchmode byorder</code>: The first back end is selected
+ every time, unless it's unavailable. In that case the second
+ is taken, and so on.</ul>
+<p>
+The selection algorithm is only used when clients are serviced that
+aren't part of a sticky HTTP session. This is the case during:
+<p>
+<ul>
+ <li> all client requests of a service type <code>any</code>;
+ <li> new sessions of a service type <code>stickyhttp</code>.</ul>
+<p>
+When <code>stickyhttp</code> is in effect and a session is underway, then the
+previously used back end is always selected -- regardless of
+dispatching mode.
+<p>
+Your 'right' dispatch mode will depend on the type of service. Given
+the fact that crossroads doesn't know (and doesn't care) how to
+estimate load from a network traffic stream, you have to choose an
+appropriate dispatch mode to optimize load balancing. In most cases,
+<code>roundrobin</code> or <code>byconnections</code> will do the job just fine.
+<p>
+<p><dt><strong>A reviving interval definition</strong><dd> is needed when crossroads
+determines that a back end is temporarily unavailable. This will
+happen when:
+<p>
+<ul>
+ <li> The back end cannot be reached (network connection
+ fails);
+ <li> The network connection to the back end suddenly dies.</ul>
+<p>
+An example of the definition is <code>revivinginterval 10</code>. When this
+reviving interval is given, crossroads will check each 10 seconds
+whether unavailable back ends have woken up yet. A back end is
+considered awake when a network connection to that back end can
+succesfully be established.
+<p>
+<ul>
+ <li> Syntax: <code>revivinginterval</code> <em>number</em> <code>;</code>
+ <li> Default: 0, meaning no revivals will occur.</ul>
+<p>
+<p><dt><strong>The maximum number of connections</strong><dd> is specified using
+<code>maxconnections</code>. There is one argument; the number of concurrent
+established connections that may be active within one service.
+<p>
+'Throttling' the number of connections is a way of preventing Denial of
+Service (DOS) attacks. Without a limit, numerous network connections
+may spawn so many server instances, that the service ultimately breaks
+down and becomes unavailable.
+<p>
+<ul>
+ <li> Syntax: <code>maxconnections</code> <em>number</em> <code>;</code>
+ <li> Default: 0, meaning that all client connections will be
+ accepted.</ul>
+<p>
+<p><dt><strong>The TCP back log size</strong><dd> is a number that controls how many
+'waiting' network connections may be queued, before a client simply
+cannot connect. The syntax is e.g. <code>backlog 5</code> to cause crossroads
+to have 5 waiting connections for 1 active connection.
+The backlog queue shouldn't be too
+high, or clients will experience timeouts before they can actually
+connect. The queue shouldn't be too small either, because clients
+would be simply rejected. Your mileage may vary.
+<p>
+<ul>
+ <li> Syntax: <code>backlog</code> <em>number</em> <code>;</code>
+ <li> Default: zero, which takes the operating system's default
+ value for socket back log size.</ul>
+<p>
+<p><dt><strong>Reporting based: the shared memory key.</strong><dd> Different crossroad
+invocations must 'know' of each others activity. E.g, <code>crossroad
+status</code> must be able to get to the actual state information of all
+running services. This is internally implemented through shared
+memory, which is reserved using a key.
+<p>
+Normally crossroads will supply a shared memory key, based on the
+service port and bitwise or-ed with a magic number. In situations
+where this conflicts with existing keys (of other programs, having
+their own keys), you may supply a chosen value.
+<p>
+The syntax is e.g. <code>shmkey 123456</code>. The actual key value doesn't
+matter much, as long as it's unique and as long as each invocation of
+crossroads uses it.
+<p>
+<ul>
+ <li> Syntax: <code>shmkey</code> <em>number</em> <code>;</code>
+ <li> Default: 0, which means that crossroads will 'guess' its
+ own key, based on TCP port and a magic number.</ul>
+<p>
+<p><dt><strong>Connection timeouts:</strong><dd> Sometimes, clients simply won't close a network
+connection which leads to unnecessary resource usage. To avoid this,
+one might state e.g. <code>connectiontimeout 300</code>. This instructs crossroads to
+consider a connection where nothing has happened for 300 seconds as
+'finished'. Crossroads will terminate the connection when this timeout
+is exceeded.
+<p>
+<ul>
+ <li> Syntax: <code>connectiontimeout</code> <em>number</em> <code>;</code>
+ <li> Default: 0, meaning that crossroads will not try to
+ determine timeouts.</ul>
+<p>
+</dl>
+<p>
+<a name="l13"></a>
+<h3>4.3: Backend definitions</h3>
+<p>
+Inside the service definitions as are described in the previous
+section, <em>backend definitions</em> must also occur. Backend definitions
+are started by the keyword <code>backend</code>, followed by an identifier
+(the back end name) , and statements inside <code>{</code> and <code>}</code>:
+<p>
+<pre>
+service myservice {
+ ...
+ ... // statements that define the
+ ... // service named 'myservice'
+ ...
+
+ backend mybackend {
+ ...
+ ... // statements that define the
+ ... // backend named 'mybackend'
+ ...
+ }
+}
+</pre>
+
+<p>
+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:
+<p>
+<dl>
+<p>
+<p><dt><strong>Server:</strong><dd> Each back end must be identified by the network name
+ (server name) where it is located. For example: <code>server
+ 10.1.1.23</code>, or <code>server web.mydomain.org</code>. A TCP port specifier
+ can follow the server name, as in <code>server web.mydomain.org:80</code>.
+<p>
+<ul>
+ <li> Syntax: <code>server</code> <em>servername</em> <code>;</code>
+ <li> Or: <code>server</code> <em>servername</em><code>:</code><em>port</em> <code>;</code>
+ <li> There is no default. This is a required setting.</ul>
+<p>
+<p><dt><strong>Port:</strong><dd> When the <code>server</code> specifier doesn't include a TCP
+ port, then this statement is used to define the port at which the
+ back end expects its traffic. There is one argument, the (numeric)
+ port number.
+<p>
+<ul>
+ <li> Syntax: <code>port</code> <em>number</em> <code>;</code>
+ <li> There is no default. The port must be defined either in
+ the <code>server</code> setting or using the <code>port</code> specifier.</ul>
+<p>
+<p><dt><strong>Verbosity:</strong><dd> Similar to <code>service</code> specifications, a
+ <code>backend</code> can have its own verbosity (<code>on</code> or <code>off</code>). When
+ <code>on</code>, traffic to and fro this back end is reported.
+<p>
+<ul>
+ <li> Syntax: <code>verbosity</code> <em>setting</em> <code>;</code>
+ <li> Or: <code>verbose</code> <em>setting</em> <code>;</code>
+ <li> where <em>setting</em> is <code>true</code>, <code>yes</code> or <code>on</code> to turn
+ verbosity on; or <code>false</code>, <code>no</code>, <code>off</code> to turn it off.
+ <li> Default: <code>off</code>.</ul>
+<p>
+<p><dt><strong>Maxconnections:</strong><dd> This setting states how many concurrent connections
+ a back end connection may accept. Note that there is also a
+ <code>maxconnections</code> statement for the overall service description.
+<p>
+The difference is that a <code>maxconnections</code> statement at the level of
+ a service description avoids too many hits from the outside (DOS
+ prevention). A <code>maxconnections</code> statement at the level of a back end
+ description makes sure that this particular back end doesn't get
+ overloaded.
+<p>
+<ul>
+ <li> Syntax: <code>maxconnections</code> <em>number</em> <code>;</code>
+ <li> where <em>number</em> is the maximum number of concurrent
+ client connections.
+ <li> Default: 0, meaning that there is no limit.</ul>
+<p>
+<p><dt><strong>Weight:</strong><dd> To influence how backends are selected by size or by
+ duration, a backend can specify its 'weight' in the process. The
+ higher the weight, the less likely a back end will be chosen. The
+ default is 1.
+<p>
+The weighing mechanism only applies to the dispatch modes
+ <code>byconnections</code>, <code>bysize</code> and <code>byduration</code>.
+ The weight is in fact a multiplier. E.g., if backend A has
+ <code>weight 2</code> and backend B has <code>weight 1</code>, then backend B will
+ be selected all the time, until its usage parameter is twice as
+ large as the parameter of A.
+<p>
+<ul>
+ <li> Syntax: <code>weight</code> <em>number</em> <code>;</code>
+ <li> Default: 1</ul>
+<p>
+<p><dt><strong>Decay:</strong><dd> To make sure that a 'spike' of activity doesn't
+ influence the perceived load of a back end forever, you may
+ specify a certain decay. E.g, the statement <code>decay 10</code> makes
+ sure that the load that crossroads computes for this back end (be
+ it in seconds or in bytes) is decreased by 10% each time that
+ <strong>an other</strong> back end is hit. Decays are not applied to the count
+ of concurrent connections.
+<p>
+This means that when a given back end is hit, then its usage data
+ of the transferred bytes and the connection duration are updated
+ using the actual number of bytes and actual duration. However,
+ when a different back end is hit, then the usage data are
+ decreased by the specified decay.
+<p>
+<ul>
+ <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>stickyhttp</code>,
+ then backends should 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 stickyhttp;
+ 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>Event triggers:</strong><dd> As special 'hooks' for actions, two triggers
+ are available: <code>onfailure</code> and <code>onsuccess</code>. The argument to
+ the triggers is a system command that is executed when a connection
+ with the back end either fails or succeeds.
+<p>
+<ul>
+ <li> Syntax: <code>onfailure</code> <em>commandline</em> <code>;</code> and
+ <code>onsuccess</code> <em>commandline</em> <code>;</code>
+ <li> There is no default.</ul>
+<p>
+<p><dt><strong>Debugging and Performance aids:</strong><dd> Incase the traffic between
+ client and backend
+ must be debugged, the statement <code>trafficlog</code> <em>filename</em> can
+ be issued. This causes the traffic to be dumped in hexadecimal
+ format to the stated filename.
+<p>
+Traffic sent by the client is prefixed by a <strong>C</strong>, traffic sent by
+ the back end is prefixed by a <strong>B</strong>. Below is a sample traffic
+ dump of a browser trying to get a HTML page. The server replies
+ that the page was not modified.
+<p>
+<pre>
+C 0000 47 45 54 20 68 74 74 70 3a 2f 2f 77 77 77 2e 63 GET http://www.c
+C 0010 73 2e 68 65 6c 73 69 6e 6b 69 2e 66 69 2f 6c 69 s.helsinki.fi/li
+C 0020 6e 75 78 2f 6c 69 6e 75 78 2d 6b 65 72 6e 65 6c nux/linux-kernel
+C 0030 2f 32 30 30 31 2d 34 37 2f 30 34 31 37 2e 68 74 /2001-47/0417.ht
+C 0040 6d 6c 20 48 54 54 50 2f 31 2e 31 0d 0a 43 6f 6e ml HTTP/1.1..Con
+C 0050 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73 65 0d 0a nection: close..
+.
+. etcetera
+.
+B 0000 48 54 54 50 2f 31 2e 30 20 33 30 34 20 4e 6f 74 HTTP/1.0 304 Not
+B 0010 20 4d 6f 64 69 66 69 65 64 0d 0a 44 61 74 65 3a Modified..Date:
+B 0020 20 54 75 65 2c 20 31 32 20 4a 75 6c 20 32 30 30 Tue, 12 Jul 200
+B 0030 35 20 30 39 3a 34 39 3a 34 37 20 47 4d 54 0d 0a 5 09:49:47 GMT..
+B 0040 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 Content-Type: te
+B 0050 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 65 74 xt/html; charset
+.
+. etcetera
+.
+</pre>
+
+<p>
+Turning on traffic dumps will <em>significantly</em>
+ slow down crossroads.
+<p>
+<ul>
+ <li> Syntax: <code>trafficlog</code> <em>filename</em> <code>;</code>
+ <li> There is no default. Without this directive, traffic is
+ not logged.</ul>
+<p>
+Besides <code>trafficlog</code>, there is also a directive
+ <code>throughputlog</code>. This directive also takes one argument, a
+ filename. The file is appended, and the following information is
+ logged:
+<p>
+<ul>
+ <li> The process ID of the crossroads image that serves the
+ TCP connection;
+ <li> The time of the request, in seconds and microseconds
+ since start of the run;
+ <li> A <strong>C</strong> when the request originated at the client, or
+ <strong>B</strong> when the request originated at the back end;
+ <li> The first 100 bytes of the request.</ul>
+<p>
+As an example, consider the following (the lines are shortened for
+ brevity and prefixed by line numbers for clarity):
+<p>
+<pre>
+
+1 0000594 0.000001 C GET http://public.e-tunity.com/index.html...
+2 0000594 0.173713 B HTTP/1.0 200 OK..Date: Fri, 18 Nov 2005 0...
+3 0000594 0.278125 B width="100" bgcolor="#e0e0e0" valign="to...
+4 0000595 0.000001 C GET http://public.e-tunity.com/css/style/...
+5 0000594 0.944339 B /a></td>.. </tr>.</table>.</td><td class...
+6 0000594 0.946356 B smallboxdownl">Download</td>.. <td class...
+7 0000594 0.961102 B td><td class="smallboxodd" valign="top"><...
+8 0000595 0.698215 B HTTP/1.0 304 Not Modified..Date: Fri, 18 ...
+</pre>
+
+<p>
+This tells us that:
+<p>
+<ul>
+ <li> Line 1: PID 594 served a request that originated at
+ the client. The corresponding time is (almost) 0 seconds,
+ so this is really the start of the run.
+ <li> Line 2: A back end replied 0.17 seconds later, and
+ 0.28 seconds later, it was still replying (this is the
+ third line, again a <strong>B</strong>-type transmission).
+ <li> Line 4: PID 595 served a request that originated
+ at the client. Again, the corresponding time is (almost)
+ 0 seconds, since this is the first conversation part of
+ this connection.
+ <li> Lines 5 to 7: This is the continuation of line 2. Line 7
+ is the last line of the <strong>B</strong> series (not visible from
+ the example, but trust me, it is), so that we may
+ conclude that it took the back end 0.96 seconds to serve
+ the file <code>index.html</code> requested in line 1.
+ <li> Line 8: This is the answer to the client's request of
+ line 4 (you can tell by the process ID number).
+ So the back end took 0.68 seconds to confirm that
+ the stylesheet requested in line 4 wasn't modified.</ul>
+<p>
+It is also worth while remembering that the start time of a <strong>C</strong>
+ request is the time that crossroads sees the activity. Any latency
+ between the true client and crossroads is obviously not
+ included. This is illustrated by the below simple ASCII art:
+<p>
+<pre>
+
+client ---->---->---->--->*crossroads ====>====>====>
+ \
+ back end
+ /
+client ----<----<----<---< crossroads ====<====<====<
+
+</pre>
+
+<p>
+This simple picture shows a typical HTTP request that originates
+ at a client, travels to crossroads, and is relayed via the back
+ end. The <strong>C</strong> entry in a throughput log is the time when
+ crossroads sees the request, indicated by an asterisk. The <strong>B</strong>
+ entries are the times that it takes the back end to answer,
+ indicated by <code>===</code> style lines. Therefore, the true roundtrip
+ time will be longer than the number of seconds that are logged in
+ the throughput log: the latency between client and crossroads
+ isn't included in that measurement.
+<p>
+Summarizing, the throughput times of a client-back end connection
+ can be analyzed using the directive <code>throughputlog</code>. In a
+ real-world analysis, you'd probably want to write up a script to
+ analyze the output and to compute round trip times. Such scripts
+ are not (yet) included in Crossroads.
+<p>
+<ul>
+ <li> Syntax: <code>throughputlog</code> <em>filename</em> <code>;</code>
+ <li> There is no default. Without this directive, the
+ throughput is not logged.</ul>
+<p>
+</dl>
+<p>
+<a name="l14"></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="l15"></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
+crossroads computes usage, how weighing works, and so on. In this
+section we'll focus on the dispatching modes <code>bysize</code>, <code>byduration</code>
+and <code>byconnections</code> only. The other dispatching types are
+self-explanatory.
+<p>
+<a name="l16"></a>
+<strong>5.1.1: Bysize, byduration or byconnections?</strong>
+<p>
+As stated before, crossroads doesn't know 'what a service does' and
+how to judge whether a given back end is very busy or not. You
+must therefore give the right hints:
+<p>
+<ul>
+ <li> In general, a service which is CPU bound, will be more
+ busy when it takes longer to process a request. The dispatch
+ mode <code>byduration</code> is appropriate here.
+<p>
+<li> In contrast, a service which is filesystem bound, will be
+ more busy when more data are transferred. The dispatch mode
+ <code>bysize</code> is apppropriate.
+<p>
+<li> The dispatch mode <code>byduration</code> can also be used when
+ network latency is an issue. E.g., if your balancer has back
+ ends that are geograpically distributed, then <code>byduration</code>
+ would be a good way to select best available back ends.
+<p>
+<li> Furthermore it is noteworthy that <code>dispatchmode
+ byduration</code> is not usable for interactive processes such as
+ SSH logins. Idle time of a
+ login adds to the duration, while causing (almost) no
+ load. Mode <code>byduration</code> should only be used for automated
+ processes that don't wait for user interaction (e.g., SOAP
+ calls and other HTTP requests).
+<p>
+<li> As a last remark, the dispatching mode <code>byconnections</code> can
+ be used if you don't have other clues for load
+ estimations.
+<p>
+E.g., consider a database connection. What's
+ heavier on the back end, time-consuming connections, or connections
+ where loads of bytes are transferred? Well, that depends. A
+ tough <code>select</code> query that joins multiple tables can be very
+ heavy on the back end, though the response set can be quite
+ small - and hence the number of
+ transferred bytes. That would suggest
+ dispatching by duration. However, <code>byduration</code>
+ balancing doesn't respresent the true world, when interactive
+ connections can occur where users have an idle TCP connection to
+ the database:
+ this consumes time, but no bytes (see the SSH login example
+ above). In this case, the dispatch mode <code>byconnections</code> may be
+ your best bet.
+<p>
+</ul>
+<p>
+<a name="l17"></a>
+<strong>5.1.2: Averaging size and duration</strong>
+<p>
+The configuration statement <code>dispatchmode bysize</code> or <code>byduration</code>
+allows an optional modifier <code>over</code> <em>number</em>, where the stated
+number represents a connection count. When this modifier is present, then
+crossroads will use a moving average over the last <em>n</em> connections to
+compute duration and size figures.
+<p>
+In the real world you'll always want this modifier. E.g., consider two
+back ends that are running for years now, and one of them is suddenly
+overloaded and very busy (it experiences a 'spike' in activity).
+When the <code>over</code> modifier is absent, then
+the sudden load will hardly show up in the usage figures -- it will
+flatten out due to the large usage figures already stored in the years
+of service.
+<p>
+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="l18"></a>
+<strong>5.1.3: Specifying decays</strong>
+<p>
+Decays are also only relevant when crossroads computes the 'next best
+back end' by size (bytes) or duration (seconds). E.g., imagine two
+back ends A and B, both averaged over say 3 connections.
+<p>
+Now when back end A is suddenly hit by a spike,
+its average would go up accordingly. But the back end would never
+again be used, unless B also received a similar spike, because A's
+'usage data' over its last three connections would forever be larger than
+B's data.
+<p>
+For that reason, you should in real situations probably always
+specify a decay, so that the backend selection algorithm recovers from
+spikes. Note that the usage data of the back end where a decay is
+specified, decay when <strong>other</strong> back ends are hit. The decay parameter
+is like specifying how fast your body regenerates when someone else
+does the work.
+<p>
+The below configuration illustrates this:
+<p>
+<pre>
+/* Definition of the service */
+service soap {
+ /* Local TCP port */
+ port 8080;
+
+ /* We'll select back ends by the processing
+ * duration
+ */
+ dispatchmode byduration over 3;
+
+ /* First back end: */
+ backend A {
+ /* Back end IP address and port */
+ server 10.1.1.1:8080;
+
+ /* When this back end is NOT hit because
+ * the other one was less busy, then the
+ * usage parameters decay 10% per connection
+ */
+ decay 10;
+ }
+
+ /* Second back end: */
+ backend B {
+ server 10.1.1.2:8080;
+ decay 10;
+ }
+}
+</pre>
+
+<p>
+<a name="l19"></a>
+<strong>5.1.4: Adjusting the weights</strong>
+<p>
+The back end modifier <code>weight</code> is useful in situations where your
+back ends differ in respect to performance. E.g,. your back ends may
+be geographically distributed, and you know that a given back end is
+difficult to reach and often experiences network lag.
+<p>
+Or you may have
+one primary back end, a system with a fast CPU and enough memory, and a
+small fall-back back end, with a slow CPU and short on memory. In that
+case you know in advance that the second back end should be used only
+rarely. Most requests should go to the big server, up to a certain load.
+<p>
+In such cases you will know in advance that the best performing back ends
+should be selected the most often. Here's where the <code>weight</code>
+statement comes in: you can simply increase the weight of the back
+ends with the least performance, so that they are selected less
+frequently.
+<p>
+E.g., consider the following configuration:
+<p>
+<pre>
+service soap {
+ port 8080;
+ dispatchmode byduration over 3;
+ backend A {
+ server 10.1.1.1:8080;
+ decay 20;
+ }
+ backend B {
+ server 10.1.1.2:8080;
+ weight 2;
+ decay 10;
+ }
+ backend C {
+ server 10.1.1.3:8080;
+ weight 4;
+ decay 5;
+ }
+}
+</pre>
+
+<p>
+This will cause crossroads to select back ends by the processing time,
+averaging over the last three connections. However, backend B will kick
+in only when its usage is half of the usage of A (back end B is
+probably only half as fast as A). Backend C will kick in only when its
+usage is a quarter of the usage of A, which is half of the usage of B
+(back end C is probably very weak, and just a fall-back system incase
+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="l20"></a>
+<strong>5.1.5: Throttling the number of concurrent connections</strong>
+<p>
+If you suspect that your service may occasionally receive 'spikes' of
+activity (which you should always assume), then it might be a
+good idea to protect your service by specifying a maximum number of
+concurrent connections. This protection can be specified on two levels:
+<p>
+<dl>
+ <p><dt><strong>On the service level</strong><dd> a statement like <code>maxconnections
+ 100;</code> states that the service as a whole will never
+ service more than 100 concurrent connections. This means that
+ all your back ends and the crossroads balancer itself
+ will be protected from being overloaded.
+ <p><dt><strong>On the back end level</strong><dd> a statement like <code>maxconnections 10;</code>
+ states that this particular back end will never have more
+ than 10 concurrent connections; regardless of the overall
+ setting on the service level. This means that this
+ particular back end will be protected from being
+ overloaded (regardless of what other back ends may
+ experience).</dl>
+<p>
+The <code>maxconnections</code> statement, combined with a back end selection
+algorithm, allows very fine granularity. The <code>maxconnections</code> statement
+on the back end level is like a hand brake: even when you specify a
+back end algorithm that would protect a given back end from being used
+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="l21"></a>
+<h3>5.2: HTTP Session Stickiness</h3>
+<p>
+This section focuses on HTTP session stickiness. This term refers to
+the ability of a balancer to route a conversation between browser and
+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="l22"></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
+use HTTP session stickiness unless you really have to.</strong> Enabling
+session stickiness hampers failover, balancing and performance:
+<p>
+<ul>
+ <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.
+ <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
+ balancer however must continue to send the requests there.
+ <li> Performance is hampered because crossroads needs to 'unpack'
+ messages as they are passed to and fro. That's because
+ crossroads needs to check the HTTP headers in the messages
+ for persistence cookies.</ul>
+<p>
+There is a number of measures that you can take to avoid using session
+stickiness. E.g., session data can be 'shared' between web back
+ends. PHP offers functionality to store session data in a database, so
+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="l23"></a>
+<strong>5.2.2: But if you must..</strong>
+<p>
+However, if you <strong>must</strong> use session stickiness, then proceed as
+follows:
+<p>
+<ul>
+ <li> At the level of a <code>service</code> description, set the type to
+ <code>stickyhttp</code>.
+ <li> At the level of each back end description, configure the
+ <code>stickycookie</code> and a <code>insertcookie</code> directives.</ul>
+<p>
+Once crossroads sees that, it will examine each HTTP message that it
+shuttles between client and back end:
+<p>
+<ul>
+ <li> If there is no persistence cookie in the HTTP headers of a
+ client's request, then the message must be the first one and
+ a new session should be established.
+ Crossroads selects an appropriate back
+ end, sends the message to that back end, catches the reply,
+ and inserts a <code>Set-Cookie</code> directive.
+ <li> If there is a persistence cookie in the HTTP headers of a
+ client's request, then the request is part of an already
+ established session. Crossroads analyzes the cookie and
+ forwards the request to the appropriate back end.</ul>
+<p>
+Below is a short example of a configuration.
+<p>
+<pre>
+service www {
+ port 80;
+ type stickyhttp;
+ revivinginterval 15;
+ dispatchmode byconnections;
+
+ backend one {
+ server 10.1.1.100:80;
+ stickycookie XRID=100;
+ insertcookie "XRID=100; Path=/";
+ }
+
+ backend two {
+ server 10.1.1.101:80;
+ stickycookie XRID=101;
+ insertcookie "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
+prerequisite for stickiness.
+<p>
+<a name="l24"></a>
+<h3>5.3: 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="l25"></a>
+<strong>5.3.1: A load balancer for three webserver back ends</strong>
+<p>
+The following configuration example binds crossroads to port 80 of the
+current server, and distributes the load over three back ends. This
+configuration shows most of the possible settings.
+<p>
+<pre>
+service www {
+ /* We don't need session stickyness. */
+ type any;
+
+ /* Port on which we'll listen in this service: required. */
+ port 8000;
+
+ /* What IP address should this service listen? Default is 'any'.
+ * Alternatively you can state an explicit IP address, such as
+ * 127.0.0.1; that would bind the service only to 'localhost'. */
+ bindto any;
+
+ /* Verbose reporting or not. Default is off. */
+ verbosity on;
+
+ /* Dispatching mode, or: How to select a back end for an incoming
+ * request. Possible values:
+ * roundrobin: just the next back end in line
+ * random: like roundrobin, but at random to make things more
+ * confusing. Probably only good for testing.
+ * bysize: The backend that transferred the least nr of bytes
+ * is the next in line. As a modifier you can say e.g.
+ * bysize over 10, meaning that the 10 last connections will
+ * be used to compute the transfer size, instead of all
+ * transfers.
+ * byduration: The backend that was active for the shortest time
+ * is the next in line. As a modifier you can say e.g.
+ * byduration of 10 to compute over the last 10 connections.
+ * byconnections: The back end with the least active connections
+ * is the next ine line.
+ * byorder: The first available back end is always taken.
+ */
+ dispatchmode byduration over 5;
+
+ /* Interval at which we'll check whether a temporarily unavailable
+ * backend has woken up.
+ */
+ revivinginterval 5;
+
+ /* TCP backlog of connections. Default is 0 (no backlog, one
+ * connection may be active).
+ */
+ backlog 5;
+
+ /* For status reporting: a shared memory key. Default is the same
+ * as the port number, OR-ed by a magic number.
+ */
+ shmkey 8000;
+
+ /* This controls when crossroads should consider a connection as
+ * finished even when the TCP sockets weren't closed. This is to
+ * avoid hanging connections that don't do anything. NOTE THAT when
+ * crossroads cuts off a connection due to timeout exceed, this is
+ * not marked as a failure, but as a success. Default is 0: no timeout.
+ */
+ connectiontimeout 300;
+
+ /* The max number of allowed client connections. When present, connections
+ * won't be accepted if the max is about to be exceeded. When
+ * absent, all connections will be accepted, which might be misused
+ * for a DOS attack.
+ */
+ maxconnections 300;
+
+ /* Now let's define a couple of back ends. Number 1: */
+ backend www_backend_1 {
+ /* The server and its port, the minimum configuration. */
+ server httpserver1;
+ port 9010;
+ /* The 'decay' of usage data of this back end. Only relevant
+ * when the whole service has 'dispatchmode bysize' or
+ * 'byduration'. The number is a percentage by which the usage
+ * parameter is decreased upon each connection of an other back
+ * end.
+ */
+ decay 10;
+
+ /* To see what's happening in /var/log/messages: */
+ verbosity on;
+ }
+
+ /* The second one: */
+ backend www_backend_2 {
+ /* Server and port */
+ server httpserver2;
+ port 9011;
+
+ /* Verbosity of reporting when this back end is active */
+ verbosity on;
+
+ /* Decay */
+ decay 10;
+
+ /* This back end is twice as weak as the first one */
+ weight 2;
+
+ /* Event triggers for system commands upon succesful activation
+ * and upon failure.
+ */
+ onsuccess echo 'success on backend 2' | mail root;
+ onfailure echo 'failure on backend 2' | mail root;
+ }
+
+ /* And yet another one.. this time we will dump the traffic
+ * to a trace file. Furthermore we don't want more than 10 concurrent
+ * connections here. Note that there's also a total maxconnections for the
+ * whole service.
+ */
+ backend www_backend_3 {
+ server httpserver3;
+ verbosity on;
+ port 9000;
+ verbosity on;
+ decay 10;
+ trafficlog /tmp/backend.3.log;
+ maxconnections 10;
+ }
+}
+</pre>
+
+<p>
+<a name="l26"></a>
+<strong>5.3.2: An HTTP forwarder when travelling</strong>
+<p>
+As another example, here's my <code>crossroads.conf</code> that I use on my
+Unix laptop. The problem that I face is that I need many HTTP proxy
+configurations (at home, at customers' sites and so on) but I'm too
+lazy to reconfigure browsers all the time.
+<p>
+Here's how it used to be before crossroads:
+<p>
+<ul>
+ <li> At home, I would surf through a squid proxy on my local
+ machine. The browser proxy setting is then
+ <code>http://localhost:3128</code>.
+<p>
+<li> Sometimes I start up an SSH tunnel to our offices. The
+ tunnel has a local port 3129, and connects to a squid proxy on
+ our e-tunity server. Hence, the browser proxy is then
+ <code>http://localhost:3129</code>.
+<p>
+<li> At a customer's location I need the proxy
+ <code>http://10.120.34.113:8080</code>, because they have configured it
+ so.
+<p>
+<li> And in yet other instances, I use a HTTP diagnostic tool
+ <a href="http://www.xk72.com/charles">Charles</a>
+ that sits between browser and website and shows me
+ what's happening. I run charles on my own machine and it
+ listens to port 8888, behaving like a proxy. The browser
+ configuration for the proxy is then
+ <code>http://localhost:8888</code>.</ul>
+<p>
+Here's how it works with a crossroads configuration:
+<p>
+<ul>
+ <li> I have configured my browsers to use
+ <code>http://localhost:8080</code> as the proxy. For all situations.
+<p>
+<li> I use the following crossroads configuration, and let
+ crossroads figure out which proxy backend works, and which
+ doesn't. Note two particularities:
+<p>
+<ul>
+ <li> The statement <code>dispatchmode byorder</code>. This
+ makes sure that once crossroads determines which
+ backend works, it will stick to it. This usage of
+ crossroads doesn't need to balance over more than one
+ back end.
+<p>
+<li> The statement <code>bindto 127.0.0.1</code> makes sure
+ that requests from other interfaces than loopback
+ won't get serviced.</ul>
+<p>
+<pre>
+service HttpProxy {
+ port 8080;
+ bindto 127.0.0.1;
+ verbosity on;
+ dispatchmode byorder;
+ revivinginterval 15;
+
+ backend Charles {
+ server localhost:8888;
+ verbosity on;
+ }
+
+ backend CustomerProxy {
+ server 10.120.34.113:8080;
+ verbosity on;
+ }
+
+ backend SshTunnel {
+ server localhost:3129;
+ }
+
+ backend LocalSquid {
+ server localhost:3128;
+ }
+}
+</pre>
+</ul>
+<p>
+As a final note, the commandline argument <code>tell</code> can be used to
+influence crossroad's own detection mechanism of back end availability
+detection. E.g., if in the above example the back ends <code>SshTunnel</code>
+and <code>LocalSquid</code> are both active, then <code>crossroads tell httpproxy
+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="l27"></a>
+<strong>5.3.3: SSH login with enforced idle logout</strong>
+<p>
+The following example shows how crossroads 'throttles' SSH
+logins. Connections are accepted on port
+22 (the normal SSH port) and forwarded to the actual SSH daemon
+which is running on port 2222.
+<p>
+Note the usage of the
+<code>connectiontimeout</code> directive. This makes sure that users are logged
+out after 10 minutes of inactivity. Note also the <code>maxconnections</code>
+setting, this makes sure that no more than 10 concurrent logins occur.
+<p>
+<pre>
+service Ssh {
+ port 22;
+ backlog 5;
+ maxconnections 10;
+ connectiontimeout 600;
+ backend TrueSshDaemon {
+ server localhost:2222;
+ }
+}
+</pre>
+
+<p>
+<a name="l28"></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="l29"></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:
+<p>
+<ol>
+ <li> A website was recursively spidered through a local squid
+ proxy. The spidering was repeated 10 times, the total was recorded.
+<p>
+<li> Crossroads was placed in front of the squid proxy, and
+ the website was again recursively spidered. Again, the
+ spidering was repeated 10 times and the total was recorded.</ol>
+<p>
+The crossroads configuration of the second alternative is shown below:
+<p>
+<pre>
+service HttpProxy {
+ port 8080;
+ verbosity on;
+ backend LocalSquid {
+ server 127.0.0.1;
+ port 3128;
+ verbosity on;
+ }
+}
+</pre>
+
+<p>
+<a name="l30"></a>
+<strong>6.1.1: Results</strong>
+<p>
+The results of this test are that crossroads causes a negligible
+delay, if it is statistically relevant at all. Without crossroads, the
+timing results are:
+<p>
+<pre>
+real 0m8.146s
+user 0m0.130s
+sys 0m0.253s
+</pre>
+
+<p>
+When using crossroads as a middle station, the results are:
+<p>
+<pre>
+real 0m9.481s
+user 0m0.141s
+sys 0m0.230s
+</pre>
+
+<p>
+<a name="l31"></a>
+<strong>6.1.2: Discussion</strong>
+<p>
+The above shown results are quite favorable to crossroads. However,
+one should know that situations will exist where crossroads leans
+towards the 'worst case' scenario, causing up to 50%
+delay.
+<p>
+E.g., imagine a test where a <code>wget</code> command retrieves a
+HTML document from an Apache server on <code>localhost</code>. Now we have
+(almost) no overhead due to network throttling, hostname lookups and
+so on. When this test would be run either with or without crossroads
+in between, then theoretically, crossroads would cause a much larger
+delay, because it has to read from the server, and then write the same
+information to <code>wget</code>. Each read/write occurs twice when crossroads
+sits in between.
+<p>
+This worst case scenario will however (fortunately) occur only very
+seldom in the real world:
+<p>
+<ul>
+ <li> Normally network issues, such as the above mentioned host
+ name lookups or throughput restrictions, will add
+ significantly to the duration of a request. The 'twice as
+ many' read/writes caused by crossroads are then relatively
+ irrelevant.
+<p>
+<li> Normally a significant amount of time will be spent in a
+ back end, due to processing (e.g., when calling a servlet on a
+ back end). Again, this processing time will weigh much heavier
+ than the multiple read/writes.</ul>
+<p>
+<a name="l32"></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
+firewall: TCP packets that arrive at the balancer are sent to one of
+the configured back ends. LVS has the advantage over crossroads that
+there is no stop-and-go in the transmission; in contrast, crossroads
+needs to send data via an internal buffer. Crossroads has the
+advantage that it offers instantaneous failover because it tries to
+contact the back end for upon each new TCP connection; in contrast,
+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="l33"></a>
+<strong>6.2.1: Environment</strong>
+<p>
+On the balancer, LVS was run on port 80, its forwarding set up for two
+equally weighted back ends, using <code>ipvsadm</code>:
+<p>
+<pre>
+ipvsadm -a -t 192.168.1.250:http -r 10.1.1.100:http -m -w 1
+ipvsadm -a -t 192.168.1.250:http -r 10.1.1.101:http -m -w 1
+</pre>
+
+<p>
+Crossroads was run on port 81. The configuration file is shown below:
+<p>
+<pre>
+service http {
+ port 81;
+ dispatchmode roundrobin;
+ revivinginterval 5;
+ backend one {
+ server 10.1.1.100;
+ port 80;
+ }
+ backend two {
+ server 10.1.1.101;
+ port 80;
+ }
+}
+</pre>
+
+<p>
+<a name="l34"></a>
+<strong>6.2.2: Tests and results</strong>
+<p>
+In the first test, ports 80 and 81 on the balancer were 'bombed' with
+50 concurrent clients, each requesting a small page 50 times. The
+following timings where measured:
+<p>
+<ul>
+ <li> How long it takes to establish a connection;
+ <li> How long it takes to retrieve the page.</ul>
+<p>
+The results of this test were:
+<p>
+<ul>
+ <li> On average, each client took 0.12 seconds to connect
+ to LVS, and each page was retrieved in 0.14 seconds;
+ <li> On average, each client took 0.11 seconds to connect to
+ crossroads, and each page was retrieved in 0.13 seconds.</ul>
+<p>
+In this setup there seems to be no difference between the performance
+of LVS and crossroads!
+<p>
+In a second test, the size of the retrieved page was varied from 2.000
+to 2.000.000 bytes. This test was taken to see whether crossroads would
+show performance degradation when transferring larger amounts of data.
+<p>
+For each page size, 30 concurrent clients were started, that retrieved
+the page 50 times. Again, the connect times and processing times where
+recorded.
+<p>
+The results of the total time (connect time + retrieval time)
+are shown in the below table:
+<p>
+<table>
+
+ <td colspan=3><hr></td>
+
+
+<tr>
+
+ <td> <strong>Bytes</strong></td> <td> <strong>LVS timing</strong></td> <td> <strong>Crossroads timing</strong></td>
+
+</tr>
+
+
+<tr>
+
+ <td> 2000</td> <td> 0.130741688</td> <td> 0.12739582</td>
+
+</tr>
+
+
+<tr>
+
+ <td> 20000</td> <td> 0.490916224</td> <td> 0.50376901</td>
+
+</tr>
+
+
+<tr>
+
+ <td> 200000</td> <td> 3.799440328</td> <td> 4.33125273</td>
+
+</tr>
+
+
+<tr>
+
+ <td> 2000000</td> <td> 45.25090855</td> <td> 45.9600728</td>
+
+</tr>
+
+ <td colspan=3><hr></td>
+
+</table>
+<p>
+Again, the results show that crossroads performs just as effectively
+as LVS, even with large data chunks!
+<p>
+<a name="l35"></a>
+<h2>7: Compiling and Installing</h2>
+<a name="compiling"></a><a name="l36"></a>
+<h3>7.1: Prerequisites</h3>
+<p>
+The creation of crossroads requires:
+<p>
+<ul>
+ <li> Standard Unix tools, such as <code>sed</code>, <code>awk</code>, <code>Perl</code>
+ (5.00 or better);
+<p>
+<li> A POSIX-compliant C compiler;
+<p>
+<li> The grammar generation tools <code>bison</code> and <code>flex</code>;
+<p>
+<li> Support for SYSV IPC, networking and so on.
+</ul>
+<p>
+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="l37"></a>
+<h3>7.2: Compiling and installing</h3>
+<p>
+<ul>
+ <li> Obtain the source distribution. It can be found on
+ <a href="http://public.e-tunity.com">http://public.e-tunity.com</a>. The distribution comes as an
+ archive <code>crossroads-</code><em>X.YY</em><code>.tar.gz</code>, where <em>X.YY</em> is
+ a version number.
+<p>
+<li> Unpack the archive in a sources directory using <code>tar
+ xzf crossroads-</code><em>X.YY</em><code>.tar.gz</code>. The contents spill into a
+ subdirectory <code>crossroads-</code><em>X.YY/</em>.
+<p>
+<li> Change-dir into the directory.
+<p>
+<li> Next, edit <code>etc/Makefile.def</code> and verify that all
+ compilation settings are to your likings. The settings are
+ explained in the file. <strong>Note that</strong> the default distribution
+ of <code>Makefile.def</code> is suited for Linux or Apple MacOSX
+ systems. On other Unices, or on non-Unix systems, you must
+ particularly pay attention to <code>SET_PROC_TITLE_BY...</code>. When
+ in doubt, comment out all <code>SET_PROC_TITLE...</code>
+ settings. Crossroads will work nevertheless, but it won't show
+ nice titles in <code>ps</code> listings. Also there's a macro
+ <code>EXTRA_LIBS</code> to add linkage flags (an example for a Solaris
+ build is included).
+<p>
+<li> Now crossroads is ready for compilation. Do a <code>make
+ local</code> followed by <code>make install</code>. The latter step may have
+ to be done by the user <code>root</code> if the <code>BINDIR</code> setting of
+ <code>etc/Makefile.def</code> points to a root-owned directory.
+<p>
+<li> The documentation doesn't install in this process. If you
+ want to install the documentation, then proceed as follows:
+<p>
+<ul>
+ <li> Optionally, <code>cp doc/crossroads.html</code>
+ <em>htmldirectory/</em>; where <em>htmldirectory</em> is the destination
+ directory for your HTML manuals;
+<p>
+<li> Optionally, <code>cp doc/crossroads.pdf</code>
+ <em>pdfdirectory/</em>; where <em>pdfdirectory</em> is the
+ 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>;
+<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>
+<p>
+</ul>
+<p>
+<a name="l38"></a>
+<h3>7.3: Configuring crossroads</h3>
+<p>
+Now that the binary is available on your system, you need to create a
+suitable <code>/etc/crossroads.conf</code>. Use this manual or the output of
+<code>crossroads samplconf</code> to get started.
+<p>
+Once you have the configuration ready, start crossroads with
+<code>crossroads start</code>. Test the availability of your services and back
+ends. Monitor how crossroads is doing with:
+<p>
+<ul>
+ <li> In one terminal, run the script:
+ <pre>
+while [ 1 ] ; do
+ tput clear
+ crossroads status
+ sleep 3
+done
+</pre>
+
+<p>
+<strong>Note</strong> that depending on your system you might need
+ <code>sleep 3s</code>, i.e., with an <code>s</code> appended.
+<p>
+<li> In another terminal, run:
+ <pre>
+while [ 1 ] ; do
+ tput clear
+ ps ax | grep crossroads | grep -v grep
+ sleep 3
+done
+</pre>
+
+<p>
+<strong>Note</strong> that depending on your system you might need
+ <code>ps -ef</code> instead of <code>ps ax</code>.
+<p>
+<li> In yet another terminal, run <code>tail -f
+ /var/log/messages</code> (supply the appropriate system log file if
+ <code>/var/log/messages</code> doesn't work for you).</ul>
+<p>
+Now thoroughly test the availability of your back ends through
+crossroads. The status display will show an updated view of which back
+ends are selected and how busy they are. The process list will show
+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="l39"></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="l40"></a>
+<strong>7.4.1: SysV Style Startup</strong>
+<p>
+On SysV style systems, there's a startup script directory
+<code>/etc/init.d</code> where bootscripts for all utilities are located.
+You may have the <code>chkconfig</code> utility to automate the task of
+inserting scripts into the boot sequence, but
+otherwise the steps will resemble the following.
+<p>
+<ul>
+ <li> Create a script <code>crossroads</code> in <code>/etc/init.d</code> similar to the
+ following:
+<p>
+<pre>
+#!/bin/sh
+/usr/local/bin/crossroads -v $@
+</pre>
+
+<p>
+The stated directory <code>/usr/local/bin</code> must correspond with
+ the installation path. The flag <code>-v</code> causes the startup to
+ be more 'verbose'. However, once daemonized, the verbosity is
+ controlled by the appropriate statements in the configuration.
+<p>
+<li> Determine your 'runlevel': usually 3 when your system is
+ running in text-mode only, or 5 when you are a graphical
+ interface. If your runlevel is 3, then:
+<p>
+<pre>
+root> cd /etc/rc.d/rc3.d
+root> ln -s /etc/init.d/crossroads S99crossroads
+root> ln -s /etc/init.d/crossroads K99crossroads
+</pre>
+
+<p>
+This creates startup (<code>S*</code>) and stop (<code>K*</code>) links that
+ will be run when the system enters or leaves a given runlevel.
+<p>
+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="l41"></a>
+<strong>7.4.2: BSD Style Startup</strong>
+<p>
+On BSD style systems, daemons are booted directly from <code>/etc/rc</code> and
+related scripts. Incase you have a file <code>/etc/rc.local</code>, edit it,
+and add the statement:
+<p>
+<pre>
+/usr/local/bin/crossroads start
+</pre>
+
+<p>
+If your BSD system lacks <code>/etc/rc.local</code>, then you may need to start
+Crossroads from <code>/etc/rc</code>. Your mileage may vary.
+<p>
+</body>
+</html>
diff --git a/doc/crossroads.man b/doc/crossroads.man
@@ -0,0 +1,1971 @@
+.TH "Crossroads 0\&.33" "2005, 2006, ff\&."
+.PP
+.SH "Crossroads 0\&.33"
+.SH "Karel Kubat"
+.SH "e-tunity"
+.SH "2005, 2006, ff\&."
+
+.PP
+
+.SH "1: Introduction"
+Crossroads is a daemon that basically accepts TCP connections
+at preconfigured ports, and given a list of \&'back ends\&'
+distributes each incoming connection to one of the back ends,
+so that a client request is
+served\&. Additionally, crossroads maintains an internal
+administration of the back end connectivity: if a back end isn\&'t
+usable, then the client request is handled using another back
+end\&. Crossroads will then periodically check whether a previously not
+usable back end has come to life yet\&. Also, crossroads can select
+back ends by estimating the load, so that balancing is achieved\&.
+.PP
+Using this approach, crossroads serves as load balancer and fail over
+utility\&. Crossroads will very likely not be as reliable as
+hardware based balancers, since it always will require a server to
+run on\&. This server, in turn, may become a new Single Point of
+Failure (SPOS)\&. However, in situations where cost efficiency is an issue,
+crossroads may be a good choice\&. Furthermore, crossroads can be
+deployed in situations where a hardware based balancing already
+exists and augmenting service reliability is needed\&. Or, crossroads may be
+run off a diskless system, which again improves reliability of the
+underlying hardware\&.
+.PP
+This document describes how to use crossroads, how to configure it
+in order to increase the reliability of your systems, and how to
+compile the program from its sources\&.
+.PP
+
+.SH "1\&.1: Obtaining Crossroads"
+
+.PP
+As quick reference, here are some important URL\&'s for Crossroads:
+.PP
+.IP o
+http://public\&.e-tunity\&.com is the site that serves
+Crossroads and other packages\&. You can browse this at leisure
+for documentation, sources, and so on\&.
+.IP
+.IP o
+http://public\&.e-tunity\&.com/crossroads/crossroads-latest\&.tar\&.gz is
+the \&'latest\&' distribution archive\&. (Older versions aren\&'t
+stored\&. If you really need an old version, contact me at
+karel@e-tunity\&.com\&.)
+.IP
+.IP o
+http://public\&.e-tunity\&.com/crossroads/crossroads\&.html is
+the documentation in HTML format (this text)\&. Substitute
+\fI\&.pdf\fP for \fI\&.html\fP to get the documentation in PDF format\&.
+.PP
+
+.SH "1\&.2: Copyright and Disclaimer"
+
+.PP
+Crossroads is distributed as-is, without assumptions of fitness
+or usability\&. You are free to use crossroads to your
+liking\&. It\&'s free, and as with everything that\&'s free: there\&'s
+also no warranty\&.
+.PP
+You are allowed to make modifications to the source code of
+crossroads, and you are allowed to (re)distribute crossroads, as
+long as you include this text, all sources, and if applicable: all
+your modifications, with each distribution\&.
+.PP
+While you are allowed to make any and all changes to the sources,
+I would appreciate hearing about them\&. If the changes concern new
+functionality or bugfixes, then I\&'ll include them in a next
+release, stating full credits\&.
+.PP
+
+.SH "1\&.3: Terminology"
+
+.PP
+Throughout this document, the following terms are used: \e (Many
+more meanings of the terms will exist -- yes, I am aware of that\&. I\&'m
+using the terms here in a very strict sense\&.)
+.PP
+.IP "A client"
+is a process that initiates a network connection
+to get contact with some service\&.
+.IP "A service"
+or \fBserver process\fP or \fBlistener\fP
+is a central application
+that accepts network connections from clients and sevices
+them\&.
+.IP "Back ends"
+are locations where crossroads looks in
+order to service its clients\&. Crossroads sits \&'in between\&'
+and does its tricks\&. Therefore, as far as the back ends
+are concerned, crossroads behaves like a client\&. As far as
+the true client is concerned, crossroads behaves like the
+service\&. The communication is however transparent: neither
+client nor back end are aware of the middle position of
+crossroads\&.
+.IP "A connection"
+is a network conversation between client and service,
+where data are transferred to and fro\&. As
+far as crossroads is concerned, success means that a
+connection can be established without errors on
+the network level\&. Crossroads isn\&'t aware of service
+pecularities\&. E\&.g\&., when a webserver answers \f(CWHTTP/1\&.0
+500 Server Error\fP then crossroads will see this as a
+succesful connection, though the user behind a browser may
+think otherwise\&.
+.IP "Back end selection algorithms"
+are methods by which
+crossroads determines which back end it will talk to
+next\&. Crossroads has a number of built-in algorithms,
+which may be configured per service\&.
+.IP "Back end states"
+are the statusses of each back end that
+is known to crossroads\&. A back end may be available,
+(temporarily) unavailble or truly down\&. When a back end is
+temporarily unavailable, then crossroads will periodically
+check whether the back end has come to life yet (that is,
+if configured so)\&.
+.IP "A spike"
+is a sudden increase in activity, leading to
+extra load on a given service\&. When crossroads is in
+effect and when the spike occurs in one connection,
+then obviously the spike will also appear at one
+of the back ends\&. However, crossroads will see the spike
+and will make sure that a subsequent request goes to an
+other back end\&. In contrast, when several connections
+arrive simultaneously and cause a spike, then crossroads
+will be able to distribute the connections over several
+back ends, thereby \&'flattening out\&' the increase\&.
+.IP "Load balancing"
+means that incoming client requests are
+distributed over more than just one back end (which wouldn\&'t be the
+case if you wouldn\&'t be running crossroads)\&. Enabling load
+balancing is nothing more than duplicating services over
+more than one back end, and having something (in this
+case: crossroads) distribute the requests, so that per
+back end the load doesn\&'t get too high\&.
+.IP "An HTTP session"
+is a series of separate network connections
+that originate from one browser\&. E\&.g\&., to fill the display
+with text and images, the browser hits a website several times\&.
+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 "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
+browser are forced to go to the same back end, instead of
+being balanced to other ones)\&.
+.IP "Back end usage"
+is measured by crossroads in order to be
+able to determine back end selection\&. Crossroads stores
+information about the number of active connections, the
+transferred bytes and
+about the connection duration\&. These numbers can be used to
+estimate which back end is the least used -- and
+therefore, presumably, the best candidate for a new
+request\&.
+.IP "Fail over"
+is almost always used when load balancing is in
+effect\&. The distributor of client requests (crossroads of
+course) can also monitor back ends, so that incase a back
+end is \&'down\&', it is no longer accessed\&.
+.IP "Service downtime"
+normally occurs when a service is
+switched off\&. Downtime is obviously avoided when fail over
+is in effect: a back end can be taken out of service in a
+controlled manner, without any client noticing it\&.
+
+.PP
+
+.SH "1\&.4: Porting issues for pre-0\&.26 installations"
+
+.PP
+As of version 0\&.26 the syntax of the configuration file has
+changed\&. In particular:
+.PP
+.IP o
+The keyword \f(CWmaxconnections\fP is now used instead of
+\f(CWmaxclients\fP;
+.IP o
+The keyword \f(CWconnectiontimeout\fP is now used instead of
+\f(CWsessiontimeout\fP\&.
+.PP
+Therefore when converting configuration files to the new syntax,
+the above keywords must be changed\&. (The reason for these changes
+is that 0\&.26 introduces \fIsticky HTTP sessions\fP that span
+multiple TCP connections, and the term
+\fIsession\fP is used strictly in that sense -- and no longer for a
+TCP connection\&.)
+.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:
+.PP
+.IP o
+Obtain the crossroads source archive\&. Change-dir to a
+\&'sources\&' directory on your system and unpack the archive\&.
+.IP
+.IP o
+Change-dir into the created directory \f(CWcrossroads/\fP\&.
+.IP
+.IP o
+Type \f(CWmake install\fP\&. This installs the crossroads
+binary into \f(CW/usr/local/bin/\fP\&. If the compilation doesn\&'t
+work on your system, check \f(CWetc/Makefile\&.def\fP for hints\&.
+.IP
+.IP o
+Create a file \f(CW/etc/crossroads\&.conf\fP\&. In it state
+something like:
+.IP
+.nf
+service www {
+ port 80;
+ revivinginterval 15;
+ backend one {
+ server 10\&.1\&.1\&.100:80;
+ }
+ backend two {
+ server 10\&.1\&.1\&.101:80;
+ }
+}
+.fi
+
+.IP
+Of course, make sure that you have webservers running on
+10\&.1\&.1\&.100 and 10\&.1\&.1\&.101\&.
+.IP
+.IP o
+Type \f(CWcrossroads start\fP\&.
+.IP
+.IP o
+Surf to the machine where crossroads is running\&. You will
+see the pages served by the back ends 10\&.1\&.1\&.100 or
+10\&.1\&.1\&.101\&.
+.IP
+.IP o
+To monitor the status of crossroads, type \f(CWcrossroads
+status\fP\&.
+
+.PP
+
+.SH "3: Using Crossroads"
+Crossroads is started from the commandline, and highly depends on
+\f(CW/etc/crossroads\&.conf\fP (the default configuration file)\&. It
+supports a number of flags (e\&.g\&., to overrule the location of the
+configuration file)\&. The actual usage information is always obtained
+by typing \f(CWcrossroads\fP without any arguments\&. Crossroads then
+displays the allowed arguments\&.
+.PP
+This section shows the basic usage\&.
+.PP
+.IP o
+\f(CWcrossroads start\fP and \f(CWcrossroads stop\fP are typical
+actions that are run from system startup scripts\&. The
+meaning is self-explanatory\&.
+.IP o
+\f(CWcrossroads restart\fP is a combination of the former
+two\&. Beware that a restart may cause discontinuity in
+service; it is just a shorthand for typing the \&'stop\&' and
+\&'start\&' actions after one another\&.
+.IP o
+\f(CWcrossroad status\fP reports on each running
+service\&. Per service, the state of each back end is
+reported\&.
+.IP o
+\f(CWcrossroads tell\fP \fIservice backend state\fP is a
+command line way of telling crossroads that a given back
+end, of a given service, is in a given state\&. Normally
+crossroads maintains state information itself, but by
+using \f(CWcrossroads tell\fP, a back end can be e\&.g\&. taken
+\&'off line\&' for servicing\&.
+.IP o
+\f(CWcrossroads configtest\fP tells you whether the
+configuration is syntactially correct\&.
+.IP o
+\f(CWcrossroads services\fP reports on the configured
+services\&. In contrast to \f(CWcrossroads status\fP, this
+option only shows what\&'s configured -- not what\&'s up and
+running\&. Therefore, \f(CWcrossroads services\fP doesn\&'t
+report on back end states\&.
+.IP o
+\f(CWcrossroads sampleconf\fP shows a sample configuration on
+screen\&. A good way of quicky viewing the configuration
+file syntax, or of getting a start for your own
+configuration \f(CW/etc/crossroads\&.conf\fP\&.
+
+.PP
+
+.SH "4: The configuration"
+The configuration that crossroads uses is normally stored in the file
+\f(CW/etc/crossroads\&.conf\fP\&. This location can be overruled using the
+command line flag \f(CW-c\fP\&.
+.PP
+This section explains the syntax of the configuration file, and what
+all settings do\&.
+.PP
+
+.SH "4\&.1: General language elements"
+
+.PP
+This section describes the general elements of the crossroads
+configuration language\&.
+.PP
+
+.SH "4\&.1\&.1: Empty lines and comments"
+
+.PP
+Empty lines are of course allowed in the
+configuration\&. Crossroads recognizes three formats of comment:
+.PP
+.IP o
+C-style, between \f(CW/*\fP and \f(CW*/\fP,
+.IP o
+C++-style, starting with \f(CW//\fP and ending with the end
+of the text line;
+.IP o
+Shell-style, starting with \f(CW#\fP and ending with the end
+of the text line\&.
+.PP
+Simply choose your favorite editor and use the comment that \&'looks
+best\&'\&.\e (I favor C or C++ comment\&. My favorite editor \fIemacs\fP
+can be put in \f(CWcmode\fP and nicely highlight what\&'s comment and what\&'s
+not\&. And as a bonus it will auto-indent the configuration!)
+.PP
+
+.SH "4\&.1\&.2: Keywords, numbers, identifiers, generic strings"
+
+.PP
+In a configuration file, statements are identified by \fIkeywords\fP,
+such as \f(CWservice\fP, \f(CWverbosity\fP\&. These are reserved words\&.
+.PP
+Many keywords require an \fIidentifier\fP as the argument\&. E\&.g, a
+service has a unique name, which must start with a letter or
+underscore, followed by zero or more letters, underscores, or
+digits\&. Therefore, in the statement \f(CWservice myservice\fP, the keyword is
+\f(CWservice\fP and the identifier is \f(CWmyservice\fP\&.
+.PP
+Other keywords require a numeric argument\&. Crossroads knows only
+non-negative integer numbers, as in \f(CWport 8000\fP\&. Here, \f(CWport\fP is
+the keyword and \f(CW8000\fP is the number\&.
+.PP
+Yet other keywords require \&'generic strings\&', such as hostname
+specifications or system commands\&. Such generic strings contain any
+characters (including white space) up to the terminating statement
+character \f(CW;\fP\&. If a string must contain a semicolon, then it must
+be enclosed in single or double quotes:
+.PP
+.IP o
+\f(CWThis is a string;\fP is a string that starts at \f(CWT\fP
+and ends with \f(CWg\fP
+.IP o
+\f(CW"This is a string";\fP is the same, the double quotes
+are not necessary
+.IP o
+\f(CW"This is ; a string";\fP has double quotes to protect
+the inner ;
+.PP
+Finally, an argument can be a \&'boolean\&' value\&. Crossroads knows
+\f(CWtrue\fP, \f(CWfalse\fP, \f(CWyes\fP, \f(CWno\fP, \f(CWon\fP, \f(CWoff\fP\&. The keywords
+\f(CWtrue\fP, \f(CWyes\fP and \f(CWon\fP all mean the same and can be used
+interchangeably; as can the keywords \f(CWfalse\fP, \f(CWno\fP and \f(CWoff\fP\&.
+.PP
+
+.SH "4\&.2: Service definitions"
+
+.PP
+Service definitions are blocks in the configuration file that
+state what is for each service\&. A service definition starts with
+\f(CWservice\fP, followed by a unique identifier, and by statements in
+\f(CW{\fP and \f(CW}\fP\&. For example:
+.PP
+.nf
+// Definition of service \&'www\&':
+service www {
+ \&.\&.\&.
+ \&.\&.\&. // statements that define the
+ \&.\&.\&. // service named \&'www\&'
+ \&.\&.\&.
+}
+.fi
+
+.PP
+The configuration file can contain many service blocks, as long as the
+identifying names differ\&. The following list shows possible
+statements\&. Each statement must end with a semicolon, except for the
+\f(CWbackend\fP statement, which has is own block (more on this later)\&.
+.PP
+.IP "The type statement"
+defines how crossroads handles the stated
+service\&. There are currently two types: \f(CWany\fP and
+\f(CWstickyhttp\fP\&. The type \f(CWany\fP means that crossroads doesn\&'t
+interpret the contents of a TCP stream, but only distributes streams
+over back ends\&. The type \f(CWstickyhttp\fP means that crossroads has to
+analyze what\&'s in the messages, does magical HTTP cookie tricks, and
+so on -- to ensure that multiple connections are treated as one
+session\&.
+.IP
+Unless you really need sticky HTTP sessions, use the type \f(CWany\fP (the
+default), even for HTTP protocols\&.
+.IP
+.IP o
+Syntax: \f(CWtype\fP \fItypespecifier\fP \f(CW;\fP
+.IP o
+Where \fItypespecifier\fP is \f(CWany\fP or \f(CWstickyhttp\fP
+.IP o
+Default: \f(CWany\fP
+.IP
+.IP "The port statement"
+defines to which TCP port a service
+\&'listens\&'\&. E\&.g\&. \f(CWport 8000\fP says that this service will accept
+connections on port 8000\&.
+.IP
+.IP o
+Syntax: \f(CWport\fP \fInumber\fP \f(CW;\fP
+.IP o
+There is no default\&. This is a required setting\&.
+.IP
+.IP "The bindto statement"
+is used in situations where crossroads
+should only listen to the stated port at a given IP address\&. E\&.g\&.,
+\f(CWbindto 127\&.0\&.0\&.1\fP causes crossroads to \&'bind\&' the service only to
+the local IP address\&. Network connections from other hosts won\&'t be
+serviced\&. By default, crossroads binds a service to all presently
+active IP addresses at the invoking host\&.
+.IP
+.IP o
+Syntax: \f(CWbindto\fP \fIip-address\fP \f(CW;\fP
+.IP o
+where \fIip-address\fP is a numeric IP address, such as
+\f(CW192\&.168\&.1\&.45\fP, or the keyword \f(CWany\fP
+.IP o
+Default: \f(CWany\fP
+.IP
+.IP "Verbosity statements"
+come in two forms: \f(CWverbosity on\fP or
+\f(CWverbosity off\fP\&. When \&'on\&', log messages to \f(CW/var/log/messages\fP
+are generated that show what\&'s going on\&.\e (Actually, the
+messages go to \f(CWsyslog(3)\fP, using facility \f(CWLOG_DAEMON\fP and
+priority \f(CWLOG_INFO\fP\&. In most (Linux) cases this will mean: output to
+\f(CW/var/log/messages\fP\&. On Mac OSX the messages go to
+\f(CW/var/log/system\&.log\fP\&.) The keyword \f(CWverbose\fP is an alias for
+\f(CWverbosity\fP\&.
+.IP
+.IP o
+Syntax: \f(CWverbosity\fP \fIsetting\fP \f(CW;\fP
+.IP o
+Or: \f(CWverbose\fP \fIsetting\fP \f(CW;\fP
+.IP o
+where \fIsetting\fP is \f(CWtrue\fP, \f(CWyes\fP or \f(CWon\fP to turn
+verbosity on; or \f(CWfalse\fP, \f(CWno\fP, \f(CWoff\fP to turn it off\&.
+.IP o
+Default: \f(CWoff\fP\&.
+.IP
+.IP "The dispatch mode"
+ controls how crossroads selects a back end from
+a list of active back ends\&. The below text shows the bare
+syntax\&. See section ?? for a textual explanation\&.
+.IP
+The syntax is:
+.IP
+.IP o
+\f(CWdispatchmode roundrobin\fP: Simply the \&'next in line\&' is
+chosen\&. E\&.g, when 3 back ends are active, then the usage
+series is 1, 2, 3, 1, 2, 3, and so on\&.
+.IP
+Roundrobin dispatching is the default method, when no
+\f(CWdispatchmode\fP statement occurs\&.
+.IP
+.IP o
+\f(CWdispatchmode random\fP: Random selection\&. Probably only
+for stress testing\&.
+.IP
+.IP o
+\f(CWdispatchmode bysize [ over\fP \fIconnections\fP \f(CW]\fP:
+The next back end is the one
+that has transferred the least number of bytes\&. This
+selection mechanism assumes that the more bytes, the heavier
+the load\&.
+.IP
+The modifier \f(CWover\fP \fIconnections\fP is optional\&. (The square
+brackets shown above are not part of the statement but
+indicate optionality\&.) When given,
+the load is computed as an average of the last stated number of
+connections\&. When this modifier is absent, then the load is
+computed over all connections since startup\&.
+.IP
+.IP o
+\f(CWdispatchmode byduration [ over\fP \fIconnections\fP \f(CW]\fP:
+The next back end is the one
+that served connections for the shortest time\&. This mechanism
+assumes that the longer the connection, the heavier the load\&.
+.IP
+.IP o
+\f(CWdispatchmode byconnections\fP: The next back end is the one
+with the least active connections\&. This mechanism assumes that
+each connection to a back end represents load\&. It is usable
+for e\&.g\&. database connections\&.
+.IP
+.IP o
+\f(CWdispatchmode byorder\fP: The first back end is selected
+every time, unless it\&'s unavailable\&. In that case the second
+is taken, and so on\&.
+.IP
+The selection algorithm is only used when clients are serviced that
+aren\&'t part of a sticky HTTP session\&. This is the case during:
+.IP
+.IP o
+all client requests of a service type \f(CWany\fP;
+.IP o
+new sessions of a service type \f(CWstickyhttp\fP\&.
+.IP
+When \f(CWstickyhttp\fP is in effect and a session is underway, then the
+previously used back end is always selected -- regardless of
+dispatching mode\&.
+.IP
+Your \&'right\&' dispatch mode will depend on the type of service\&. Given
+the fact that crossroads doesn\&'t know (and doesn\&'t care) how to
+estimate load from a network traffic stream, you have to choose an
+appropriate dispatch mode to optimize load balancing\&. In most cases,
+\f(CWroundrobin\fP or \f(CWbyconnections\fP will do the job just fine\&.
+.IP
+.IP "A reviving interval definition"
+is needed when crossroads
+determines that a back end is temporarily unavailable\&. This will
+happen when:
+.IP
+.IP o
+The back end cannot be reached (network connection
+fails);
+.IP o
+The network connection to the back end suddenly dies\&.
+.IP
+An example of the definition is \f(CWrevivinginterval 10\fP\&. When this
+reviving interval is given, crossroads will check each 10 seconds
+whether unavailable back ends have woken up yet\&. A back end is
+considered awake when a network connection to that back end can
+succesfully be established\&.
+.IP
+.IP o
+Syntax: \f(CWrevivinginterval\fP \fInumber\fP \f(CW;\fP
+.IP o
+Default: 0, meaning no revivals will occur\&.
+.IP
+.IP "The maximum number of connections"
+is specified using
+\f(CWmaxconnections\fP\&. There is one argument; the number of concurrent
+established connections that may be active within one service\&.
+.IP
+\&'Throttling\&' the number of connections is a way of preventing Denial of
+Service (DOS) attacks\&. Without a limit, numerous network connections
+may spawn so many server instances, that the service ultimately breaks
+down and becomes unavailable\&.
+.IP
+.IP o
+Syntax: \f(CWmaxconnections\fP \fInumber\fP \f(CW;\fP
+.IP o
+Default: 0, meaning that all client connections will be
+accepted\&.
+.IP
+.IP "The TCP back log size"
+is a number that controls how many
+\&'waiting\&' network connections may be queued, before a client simply
+cannot connect\&. The syntax is e\&.g\&. \f(CWbacklog 5\fP to cause crossroads
+to have 5 waiting connections for 1 active connection\&.
+The backlog queue shouldn\&'t be too
+high, or clients will experience timeouts before they can actually
+connect\&. The queue shouldn\&'t be too small either, because clients
+would be simply rejected\&. Your mileage may vary\&.
+.IP
+.IP o
+Syntax: \f(CWbacklog\fP \fInumber\fP \f(CW;\fP
+.IP o
+Default: zero, which takes the operating system\&'s default
+value for socket back log size\&.
+.IP
+.IP "Reporting based: the shared memory key\&."
+Different crossroad
+invocations must \&'know\&' of each others activity\&. E\&.g, \f(CWcrossroad
+status\fP must be able to get to the actual state information of all
+running services\&. This is internally implemented through shared
+memory, which is reserved using a key\&.
+.IP
+Normally crossroads will supply a shared memory key, based on the
+service port and bitwise or-ed with a magic number\&. In situations
+where this conflicts with existing keys (of other programs, having
+their own keys), you may supply a chosen value\&.
+.IP
+The syntax is e\&.g\&. \f(CWshmkey 123456\fP\&. The actual key value doesn\&'t
+matter much, as long as it\&'s unique and as long as each invocation of
+crossroads uses it\&.
+.IP
+.IP o
+Syntax: \f(CWshmkey\fP \fInumber\fP \f(CW;\fP
+.IP o
+Default: 0, which means that crossroads will \&'guess\&' its
+own key, based on TCP port and a magic number\&.
+.IP
+.IP "Connection timeouts:"
+Sometimes, clients simply won\&'t close a network
+connection which leads to unnecessary resource usage\&. To avoid this,
+one might state e\&.g\&. \f(CWconnectiontimeout 300\fP\&. This instructs crossroads to
+consider a connection where nothing has happened for 300 seconds as
+\&'finished\&'\&. Crossroads will terminate the connection when this timeout
+is exceeded\&.
+.IP
+.IP o
+Syntax: \f(CWconnectiontimeout\fP \fInumber\fP \f(CW;\fP
+.IP o
+Default: 0, meaning that crossroads will not try to
+determine timeouts\&.
+.IP
+
+.SH "4\&.3: Backend definitions"
+
+.PP
+Inside the service definitions as are described in the previous
+section, \fIbackend definitions\fP must also occur\&. Backend definitions
+are started by the keyword \f(CWbackend\fP, followed by an identifier
+(the back end name) , and statements inside \f(CW{\fP and \f(CW}\fP:
+.PP
+.nf
+service myservice {
+ \&.\&.\&.
+ \&.\&.\&. // statements that define the
+ \&.\&.\&. // service named \&'myservice\&'
+ \&.\&.\&.
+
+ backend mybackend {
+ \&.\&.\&.
+ \&.\&.\&. // statements that define the
+ \&.\&.\&. // backend named \&'mybackend\&'
+ \&.\&.\&.
+ }
+}
+.fi
+
+.PP
+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:
+.PP
+.IP "Server:"
+Each back end must be identified by the network name
+(server name) where it is located\&. For example: \f(CWserver
+10\&.1\&.1\&.23\fP, or \f(CWserver web\&.mydomain\&.org\fP\&. A TCP port specifier
+can follow the server name, as in \f(CWserver web\&.mydomain\&.org:80\fP\&.
+.IP
+.IP o
+Syntax: \f(CWserver\fP \fIservername\fP \f(CW;\fP
+.IP o
+Or: \f(CWserver\fP \fIservername\fP\f(CW:\fP\fIport\fP \f(CW;\fP
+.IP o
+There is no default\&. This is a required setting\&.
+.IP
+.IP "Port:"
+When the \f(CWserver\fP specifier doesn\&'t include a TCP
+port, then this statement is used to define the port at which the
+back end expects its traffic\&. There is one argument, the (numeric)
+port number\&.
+.IP
+.IP o
+Syntax: \f(CWport\fP \fInumber\fP \f(CW;\fP
+.IP o
+There is no default\&. The port must be defined either in
+the \f(CWserver\fP setting or using the \f(CWport\fP specifier\&.
+.IP
+.IP "Verbosity:"
+Similar to \f(CWservice\fP specifications, a
+\f(CWbackend\fP can have its own verbosity (\f(CWon\fP or \f(CWoff\fP)\&. When
+\f(CWon\fP, traffic to and fro this back end is reported\&.
+.IP
+.IP o
+Syntax: \f(CWverbosity\fP \fIsetting\fP \f(CW;\fP
+.IP o
+Or: \f(CWverbose\fP \fIsetting\fP \f(CW;\fP
+.IP o
+where \fIsetting\fP is \f(CWtrue\fP, \f(CWyes\fP or \f(CWon\fP to turn
+verbosity on; or \f(CWfalse\fP, \f(CWno\fP, \f(CWoff\fP to turn it off\&.
+.IP o
+Default: \f(CWoff\fP\&.
+.IP
+.IP "Maxconnections:"
+This setting states how many concurrent connections
+a back end connection may accept\&. Note that there is also a
+\f(CWmaxconnections\fP statement for the overall service description\&.
+.IP
+The difference is that a \f(CWmaxconnections\fP statement at the level of
+a service description avoids too many hits from the outside (DOS
+prevention)\&. A \f(CWmaxconnections\fP statement at the level of a back end
+description makes sure that this particular back end doesn\&'t get
+overloaded\&.
+.IP
+.IP o
+Syntax: \f(CWmaxconnections\fP \fInumber\fP \f(CW;\fP
+.IP o
+where \fInumber\fP is the maximum number of concurrent
+client connections\&.
+.IP o
+Default: 0, meaning that there is no limit\&.
+.IP
+.IP "Weight:"
+To influence how backends are selected by size or by
+duration, a backend can specify its \&'weight\&' in the process\&. The
+higher the weight, the less likely a back end will be chosen\&. The
+default is 1\&.
+.IP
+The weighing mechanism only applies to the dispatch modes
+\f(CWbyconnections\fP, \f(CWbysize\fP and \f(CWbyduration\fP\&.
+The weight is in fact a multiplier\&. E\&.g\&., if backend A has
+\f(CWweight 2\fP and backend B has \f(CWweight 1\fP, then backend B will
+be selected all the time, until its usage parameter is twice as
+large as the parameter of A\&.
+.IP
+.IP o
+Syntax: \f(CWweight\fP \fInumber\fP \f(CW;\fP
+.IP o
+Default: 1
+.IP
+.IP "Decay:"
+To make sure that a \&'spike\&' of activity doesn\&'t
+influence the perceived load of a back end forever, you may
+specify a certain decay\&. E\&.g, the statement \f(CWdecay 10\fP makes
+sure that the load that crossroads computes for this back end (be
+it in seconds or in bytes) is decreased by 10% each time that
+\fBan other\fP back end is hit\&. Decays are not applied to the count
+of concurrent connections\&.
+.IP
+This means that when a given back end is hit, then its usage data
+of the transferred bytes and the connection duration are updated
+using the actual number of bytes and actual duration\&. However,
+when a different back end is hit, then the usage data are
+decreased by the specified decay\&.
+.IP
+.IP o
+Syntax: \f(CWdecay\fP \fInumber\fP \f(CW;\fP
+.IP o
+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(CWstickyhttp\fP,
+then backends should 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 stickyhttp;
+ 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 "Event triggers:"
+As special \&'hooks\&' for actions, two triggers
+are available: \f(CWonfailure\fP and \f(CWonsuccess\fP\&. The argument to
+the triggers is a system command that is executed when a connection
+with the back end either fails or succeeds\&.
+.IP
+.IP o
+Syntax: \f(CWonfailure\fP \fIcommandline\fP \f(CW;\fP and
+\f(CWonsuccess\fP \fIcommandline\fP \f(CW;\fP
+.IP o
+There is no default\&.
+.IP
+.IP "Debugging and Performance aids:"
+Incase the traffic between
+client and backend
+must be debugged, the statement \f(CWtrafficlog\fP \fIfilename\fP can
+be issued\&. This causes the traffic to be dumped in hexadecimal
+format to the stated filename\&.
+.IP
+Traffic sent by the client is prefixed by a \fBC\fP, traffic sent by
+the back end is prefixed by a \fBB\fP\&. Below is a sample traffic
+dump of a browser trying to get a HTML page\&. The server replies
+that the page was not modified\&.
+.IP
+.nf
+C 0000 47 45 54 20 68 74 74 70 3a 2f 2f 77 77 77 2e 63 GET http://www\&.c
+C 0010 73 2e 68 65 6c 73 69 6e 6b 69 2e 66 69 2f 6c 69 s\&.helsinki\&.fi/li
+C 0020 6e 75 78 2f 6c 69 6e 75 78 2d 6b 65 72 6e 65 6c nux/linux-kernel
+C 0030 2f 32 30 30 31 2d 34 37 2f 30 34 31 37 2e 68 74 /2001-47/0417\&.ht
+C 0040 6d 6c 20 48 54 54 50 2f 31 2e 31 0d 0a 43 6f 6e ml HTTP/1\&.1\&.\&.Con
+C 0050 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73 65 0d 0a nection: close\&.\&.
+\&.
+\&. etcetera
+\&.
+B 0000 48 54 54 50 2f 31 2e 30 20 33 30 34 20 4e 6f 74 HTTP/1\&.0 304 Not
+B 0010 20 4d 6f 64 69 66 69 65 64 0d 0a 44 61 74 65 3a Modified\&.\&.Date:
+B 0020 20 54 75 65 2c 20 31 32 20 4a 75 6c 20 32 30 30 Tue, 12 Jul 200
+B 0030 35 20 30 39 3a 34 39 3a 34 37 20 47 4d 54 0d 0a 5 09:49:47 GMT\&.\&.
+B 0040 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 Content-Type: te
+B 0050 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 65 74 xt/html; charset
+\&.
+\&. etcetera
+\&.
+.fi
+
+.IP
+Turning on traffic dumps will \fIsignificantly\fP
+slow down crossroads\&.
+.IP
+.IP o
+Syntax: \f(CWtrafficlog\fP \fIfilename\fP \f(CW;\fP
+.IP o
+There is no default\&. Without this directive, traffic is
+not logged\&.
+.IP
+Besides \f(CWtrafficlog\fP, there is also a directive
+\f(CWthroughputlog\fP\&. This directive also takes one argument, a
+filename\&. The file is appended, and the following information is
+logged:
+.IP
+.IP o
+The process ID of the crossroads image that serves the
+TCP connection;
+.IP o
+The time of the request, in seconds and microseconds
+since start of the run;
+.IP o
+A \fBC\fP when the request originated at the client, or
+\fBB\fP when the request originated at the back end;
+.IP o
+The first 100 bytes of the request\&.
+.IP
+As an example, consider the following (the lines are shortened for
+brevity and prefixed by line numbers for clarity):
+.IP
+.nf
+
+1 0000594 0\&.000001 C GET http://public\&.e-tunity\&.com/index\&.html\&.\&.\&.
+2 0000594 0\&.173713 B HTTP/1\&.0 200 OK\&.\&.Date: Fri, 18 Nov 2005 0\&.\&.\&.
+3 0000594 0\&.278125 B width="100" bgcolor="#e0e0e0" valign="to\&.\&.\&.
+4 0000595 0\&.000001 C GET http://public\&.e-tunity\&.com/css/style/\&.\&.\&.
+5 0000594 0\&.944339 B /a></td>\&.\&. </tr>\&.</table>\&.</td><td class\&.\&.\&.
+6 0000594 0\&.946356 B smallboxdownl">Download</td>\&.\&. <td class\&.\&.\&.
+7 0000594 0\&.961102 B td><td class="smallboxodd" valign="top"><\&.\&.\&.
+8 0000595 0\&.698215 B HTTP/1\&.0 304 Not Modified\&.\&.Date: Fri, 18 \&.\&.\&.
+.fi
+
+.IP
+This tells us that:
+.IP
+.IP o
+Line 1: PID 594 served a request that originated at
+the client\&. The corresponding time is (almost) 0 seconds,
+so this is really the start of the run\&.
+.IP o
+Line 2: A back end replied 0\&.17 seconds later, and
+0\&.28 seconds later, it was still replying (this is the
+third line, again a \fBB\fP-type transmission)\&.
+.IP o
+Line 4: PID 595 served a request that originated
+at the client\&. Again, the corresponding time is (almost)
+0 seconds, since this is the first conversation part of
+this connection\&.
+.IP o
+Lines 5 to 7: This is the continuation of line 2\&. Line 7
+is the last line of the \fBB\fP series (not visible from
+the example, but trust me, it is), so that we may
+conclude that it took the back end 0\&.96 seconds to serve
+the file \f(CWindex\&.html\fP requested in line 1\&.
+.IP o
+Line 8: This is the answer to the client\&'s request of
+line 4 (you can tell by the process ID number)\&.
+So the back end took 0\&.68 seconds to confirm that
+the stylesheet requested in line 4 wasn\&'t modified\&.
+.IP
+It is also worth while remembering that the start time of a \fBC\fP
+request is the time that crossroads sees the activity\&. Any latency
+between the true client and crossroads is obviously not
+included\&. This is illustrated by the below simple ASCII art:
+.IP
+.nf
+
+client ---->---->---->--->*crossroads ====>====>====>
+ \e
+ back end
+ /
+client ----<----<----<---< crossroads ====<====<====<
+
+.fi
+
+.IP
+This simple picture shows a typical HTTP request that originates
+at a client, travels to crossroads, and is relayed via the back
+end\&. The \fBC\fP entry in a throughput log is the time when
+crossroads sees the request, indicated by an asterisk\&. The \fBB\fP
+entries are the times that it takes the back end to answer,
+indicated by \f(CW===\fP style lines\&. Therefore, the true roundtrip
+time will be longer than the number of seconds that are logged in
+the throughput log: the latency between client and crossroads
+isn\&'t included in that measurement\&.
+.IP
+Summarizing, the throughput times of a client-back end connection
+can be analyzed using the directive \f(CWthroughputlog\fP\&. In a
+real-world analysis, you\&'d probably want to write up a script to
+analyze the output and to compute round trip times\&. Such scripts
+are not (yet) included in Crossroads\&.
+.IP
+.IP o
+Syntax: \f(CWthroughputlog\fP \fIfilename\fP \f(CW;\fP
+.IP o
+There is no default\&. Without this directive, the
+throughput is not logged\&.
+.IP
+
+.SH "5: Tips, Tricks and Remarks"
+
+The following sections elaborate on the directives as described in
+section ?? to illustrate how crossroads works and to help you
+achieve the "optimal" balancing configuration\&.
+.PP
+
+.SH "5\&.1: How back ends are selected in load balancing"
+
+.PP
+In order to tune your load balancing, you\&'ll need to understand how
+crossroads computes usage, how weighing works, and so on\&. In this
+section we\&'ll focus on the dispatching modes \f(CWbysize\fP, \f(CWbyduration\fP
+and \f(CWbyconnections\fP only\&. The other dispatching types are
+self-explanatory\&.
+.PP
+
+.SH "5\&.1\&.1: Bysize, byduration or byconnections?"
+
+.PP
+As stated before, crossroads doesn\&'t know \&'what a service does\&' and
+how to judge whether a given back end is very busy or not\&. You
+must therefore give the right hints:
+.PP
+.IP o
+In general, a service which is CPU bound, will be more
+busy when it takes longer to process a request\&. The dispatch
+mode \f(CWbyduration\fP is appropriate here\&.
+.IP
+.IP o
+In contrast, a service which is filesystem bound, will be
+more busy when more data are transferred\&. The dispatch mode
+\f(CWbysize\fP is apppropriate\&.
+.IP
+.IP o
+The dispatch mode \f(CWbyduration\fP can also be used when
+network latency is an issue\&. E\&.g\&., if your balancer has back
+ends that are geograpically distributed, then \f(CWbyduration\fP
+would be a good way to select best available back ends\&.
+.IP
+.IP o
+Furthermore it is noteworthy that \f(CWdispatchmode
+byduration\fP is not usable for interactive processes such as
+SSH logins\&. Idle time of a
+login adds to the duration, while causing (almost) no
+load\&. Mode \f(CWbyduration\fP should only be used for automated
+processes that don\&'t wait for user interaction (e\&.g\&., SOAP
+calls and other HTTP requests)\&.
+.IP
+.IP o
+As a last remark, the dispatching mode \f(CWbyconnections\fP can
+be used if you don\&'t have other clues for load
+estimations\&.
+.IP
+E\&.g\&., consider a database connection\&. What\&'s
+heavier on the back end, time-consuming connections, or connections
+where loads of bytes are transferred? Well, that depends\&. A
+tough \f(CWselect\fP query that joins multiple tables can be very
+heavy on the back end, though the response set can be quite
+small - and hence the number of
+transferred bytes\&. That would suggest
+dispatching by duration\&. However, \f(CWbyduration\fP
+balancing doesn\&'t respresent the true world, when interactive
+connections can occur where users have an idle TCP connection to
+the database:
+this consumes time, but no bytes (see the SSH login example
+above)\&. In this case, the dispatch mode \f(CWbyconnections\fP may be
+your best bet\&.
+.IP
+
+.SH "5\&.1\&.2: Averaging size and duration"
+
+.PP
+The configuration statement \f(CWdispatchmode bysize\fP or \f(CWbyduration\fP
+allows an optional modifier \f(CWover\fP \fInumber\fP, where the stated
+number represents a connection count\&. When this modifier is present, then
+crossroads will use a moving average over the last \fIn\fP connections to
+compute duration and size figures\&.
+.PP
+In the real world you\&'ll always want this modifier\&. E\&.g\&., consider two
+back ends that are running for years now, and one of them is suddenly
+overloaded and very busy (it experiences a \&'spike\&' in activity)\&.
+When the \f(CWover\fP modifier is absent, then
+the sudden load will hardly show up in the usage figures -- it will
+flatten out due to the large usage figures already stored in the years
+of service\&.
+.PP
+In contrast, when e\&.g\&. \f(CWover 3\fP is in effect, then a sudden load
+does show up -- because it highly contributes to the average of three
+connections\&.
+.PP
+
+.SH "5\&.1\&.3: Specifying decays"
+
+.PP
+Decays are also only relevant when crossroads computes the \&'next best
+back end\&' by size (bytes) or duration (seconds)\&. E\&.g\&., imagine two
+back ends A and B, both averaged over say 3 connections\&.
+.PP
+Now when back end A is suddenly hit by a spike,
+its average would go up accordingly\&. But the back end would never
+again be used, unless B also received a similar spike, because A\&'s
+\&'usage data\&' over its last three connections would forever be larger than
+B\&'s data\&.
+.PP
+For that reason, you should in real situations probably always
+specify a decay, so that the backend selection algorithm recovers from
+spikes\&. Note that the usage data of the back end where a decay is
+specified, decay when \fBother\fP back ends are hit\&. The decay parameter
+is like specifying how fast your body regenerates when someone else
+does the work\&.
+.PP
+The below configuration illustrates this:
+.PP
+.nf
+/* Definition of the service */
+service soap {
+ /* Local TCP port */
+ port 8080;
+
+ /* We\&'ll select back ends by the processing
+ * duration
+ */
+ dispatchmode byduration over 3;
+
+ /* First back end: */
+ backend A {
+ /* Back end IP address and port */
+ server 10\&.1\&.1\&.1:8080;
+
+ /* When this back end is NOT hit because
+ * the other one was less busy, then the
+ * usage parameters decay 10% per connection
+ */
+ decay 10;
+ }
+
+ /* Second back end: */
+ backend B {
+ server 10\&.1\&.1\&.2:8080;
+ decay 10;
+ }
+}
+.fi
+
+.PP
+
+.SH "5\&.1\&.4: Adjusting the weights"
+
+.PP
+The back end modifier \f(CWweight\fP is useful in situations where your
+back ends differ in respect to performance\&. E\&.g,\&. your back ends may
+be geographically distributed, and you know that a given back end is
+difficult to reach and often experiences network lag\&.
+.PP
+Or you may have
+one primary back end, a system with a fast CPU and enough memory, and a
+small fall-back back end, with a slow CPU and short on memory\&. In that
+case you know in advance that the second back end should be used only
+rarely\&. Most requests should go to the big server, up to a certain load\&.
+.PP
+In such cases you will know in advance that the best performing back ends
+should be selected the most often\&. Here\&'s where the \f(CWweight\fP
+statement comes in: you can simply increase the weight of the back
+ends with the least performance, so that they are selected less
+frequently\&.
+.PP
+E\&.g\&., consider the following configuration:
+.PP
+.nf
+service soap {
+ port 8080;
+ dispatchmode byduration over 3;
+ backend A {
+ server 10\&.1\&.1\&.1:8080;
+ decay 20;
+ }
+ backend B {
+ server 10\&.1\&.1\&.2:8080;
+ weight 2;
+ decay 10;
+ }
+ backend C {
+ server 10\&.1\&.1\&.3:8080;
+ weight 4;
+ decay 5;
+ }
+}
+.fi
+
+.PP
+This will cause crossroads to select back ends by the processing time,
+averaging over the last three connections\&. However, backend B will kick
+in only when its usage is half of the usage of A (back end B is
+probably only half as fast as A)\&. Backend C will kick in only when its
+usage is a quarter of the usage of A, which is half of the usage of B
+(back end C is probably very weak, and just a fall-back system incase
+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\&.
+.PP
+
+.SH "5\&.1\&.5: Throttling the number of concurrent connections"
+
+.PP
+If you suspect that your service may occasionally receive \&'spikes\&' of
+activity\e (which you should always assume), then it might be a
+good idea to protect your service by specifying a maximum number of
+concurrent connections\&. This protection can be specified on two levels:
+.PP
+.IP "On the service level"
+a statement like \f(CWmaxconnections
+100;\fP states that the service as a whole will never
+service more than 100 concurrent connections\&. This means that
+all your back ends and the crossroads balancer itself
+will be protected from being overloaded\&.
+.IP "On the back end level"
+a statement like \f(CWmaxconnections 10;\fP
+states that this particular back end will never have more
+than 10 concurrent connections; regardless of the overall
+setting on the service level\&. This means that this
+particular back end will be protected from being
+overloaded (regardless of what other back ends may
+experience)\&.
+.PP
+The \f(CWmaxconnections\fP statement, combined with a back end selection
+algorithm, allows very fine granularity\&. The \f(CWmaxconnections\fP statement
+on the back end level is like a hand brake: even when you specify a
+back end algorithm that would protect a given back end from being used
+too much, a situation may occur where that back end is about to be
+hit\&. A \f(CWmaxconnections\fP statement on the level of that back may then
+protect it\&.
+.PP
+
+.SH "5\&.2: HTTP Session Stickiness"
+
+.PP
+This section focuses on HTTP session stickiness\&. This term refers to
+the ability of a balancer to route a conversation between browser and
+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\&.
+.PP
+
+.SH "5\&.2\&.1: Don\&'t use stickiness!"
+
+.PP
+The rule of thumb as far as the balancer is concerned, is: \fBDo not
+use HTTP session stickiness unless you really have to\&.\fP Enabling
+session stickiness hampers failover, balancing and performance:
+.PP
+.IP o
+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\&.
+.IP o
+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
+balancer however must continue to send the requests there\&.
+.IP o
+Performance is hampered because crossroads needs to \&'unpack\&'
+messages as they are passed to and fro\&. That\&'s because
+crossroads needs to check the HTTP headers in the messages
+for persistence cookies\&.
+.PP
+There is a number of measures that you can take to avoid using session
+stickiness\&. E\&.g\&., session data can be \&'shared\&' between web back
+ends\&. PHP offers functionality to store session data in a database, so
+that all PHP applications have access to these data\&. Application
+servers such as Websphere can be configured to replicate session data
+between nodes\&.
+.PP
+
+.SH "5\&.2\&.2: But if you must\&.\&."
+
+.PP
+However, if you \fBmust\fP use session stickiness, then proceed as
+follows:
+.PP
+.IP o
+At the level of a \f(CWservice\fP description, set the type to
+\f(CWstickyhttp\fP\&.
+.IP o
+At the level of each back end description, configure the
+\f(CWstickycookie\fP and a \f(CWinsertcookie\fP directives\&.
+.PP
+Once crossroads sees that, it will examine each HTTP message that it
+shuttles between client and back end:
+.PP
+.IP o
+If there is no persistence cookie in the HTTP headers of a
+client\&'s request, then the message must be the first one and
+a new session should be established\&.
+Crossroads selects an appropriate back
+end, sends the message to that back end, catches the reply,
+and inserts a \f(CWSet-Cookie\fP directive\&.
+.IP o
+If there is a persistence cookie in the HTTP headers of a
+client\&'s request, then the request is part of an already
+established session\&. Crossroads analyzes the cookie and
+forwards the request to the appropriate back end\&.
+.PP
+Below is a short example of a configuration\&.
+.PP
+.nf
+service www {
+ port 80;
+ type stickyhttp;
+ revivinginterval 15;
+ dispatchmode byconnections;
+
+ backend one {
+ server 10\&.1\&.1\&.100:80;
+ stickycookie XRID=100;
+ insertcookie "XRID=100; Path=/";
+ }
+
+ backend two {
+ server 10\&.1\&.1\&.101:80;
+ stickycookie XRID=101;
+ insertcookie "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
+prerequisite for stickiness\&.
+.PP
+
+.SH "5\&.3: Configuration examples"
+
+.PP
+As a general hint, use \f(CWcrossroads sampleconf\fP to view the most
+up-to-date examples of configurations\&. The description below shows a
+few examples too\&.
+.PP
+
+.SH "5\&.3\&.1: A load balancer for three webserver back ends"
+
+.PP
+The following configuration example binds crossroads to port 80 of the
+current server, and distributes the load over three back ends\&. This
+configuration shows most of the possible settings\&.
+.PP
+.nf
+service www {
+ /* We don\&'t need session stickyness\&. */
+ type any;
+
+ /* Port on which we\&'ll listen in this service: required\&. */
+ port 8000;
+
+ /* What IP address should this service listen? Default is \&'any\&'\&.
+ * Alternatively you can state an explicit IP address, such as
+ * 127\&.0\&.0\&.1; that would bind the service only to \&'localhost\&'\&. */
+ bindto any;
+
+ /* Verbose reporting or not\&. Default is off\&. */
+ verbosity on;
+
+ /* Dispatching mode, or: How to select a back end for an incoming
+ * request\&. Possible values:
+ * roundrobin: just the next back end in line
+ * random: like roundrobin, but at random to make things more
+ * confusing\&. Probably only good for testing\&.
+ * bysize: The backend that transferred the least nr of bytes
+ * is the next in line\&. As a modifier you can say e\&.g\&.
+ * bysize over 10, meaning that the 10 last connections will
+ * be used to compute the transfer size, instead of all
+ * transfers\&.
+ * byduration: The backend that was active for the shortest time
+ * is the next in line\&. As a modifier you can say e\&.g\&.
+ * byduration of 10 to compute over the last 10 connections\&.
+ * byconnections: The back end with the least active connections
+ * is the next ine line\&.
+ * byorder: The first available back end is always taken\&.
+ */
+ dispatchmode byduration over 5;
+
+ /* Interval at which we\&'ll check whether a temporarily unavailable
+ * backend has woken up\&.
+ */
+ revivinginterval 5;
+
+ /* TCP backlog of connections\&. Default is 0 (no backlog, one
+ * connection may be active)\&.
+ */
+ backlog 5;
+
+ /* For status reporting: a shared memory key\&. Default is the same
+ * as the port number, OR-ed by a magic number\&.
+ */
+ shmkey 8000;
+
+ /* This controls when crossroads should consider a connection as
+ * finished even when the TCP sockets weren\&'t closed\&. This is to
+ * avoid hanging connections that don\&'t do anything\&. NOTE THAT when
+ * crossroads cuts off a connection due to timeout exceed, this is
+ * not marked as a failure, but as a success\&. Default is 0: no timeout\&.
+ */
+ connectiontimeout 300;
+
+ /* The max number of allowed client connections\&. When present, connections
+ * won\&'t be accepted if the max is about to be exceeded\&. When
+ * absent, all connections will be accepted, which might be misused
+ * for a DOS attack\&.
+ */
+ maxconnections 300;
+
+ /* Now let\&'s define a couple of back ends\&. Number 1: */
+ backend www_backend_1 {
+ /* The server and its port, the minimum configuration\&. */
+ server httpserver1;
+ port 9010;
+ /* The \&'decay\&' of usage data of this back end\&. Only relevant
+ * when the whole service has \&'dispatchmode bysize\&' or
+ * \&'byduration\&'\&. The number is a percentage by which the usage
+ * parameter is decreased upon each connection of an other back
+ * end\&.
+ */
+ decay 10;
+
+ /* To see what\&'s happening in /var/log/messages: */
+ verbosity on;
+ }
+
+ /* The second one: */
+ backend www_backend_2 {
+ /* Server and port */
+ server httpserver2;
+ port 9011;
+
+ /* Verbosity of reporting when this back end is active */
+ verbosity on;
+
+ /* Decay */
+ decay 10;
+
+ /* This back end is twice as weak as the first one */
+ weight 2;
+
+ /* Event triggers for system commands upon succesful activation
+ * and upon failure\&.
+ */
+ onsuccess echo \&'success on backend 2\&' | mail root;
+ onfailure echo \&'failure on backend 2\&' | mail root;
+ }
+
+ /* And yet another one\&.\&. this time we will dump the traffic
+ * to a trace file\&. Furthermore we don\&'t want more than 10 concurrent
+ * connections here\&. Note that there\&'s also a total maxconnections for the
+ * whole service\&.
+ */
+ backend www_backend_3 {
+ server httpserver3;
+ verbosity on;
+ port 9000;
+ verbosity on;
+ decay 10;
+ trafficlog /tmp/backend\&.3\&.log;
+ maxconnections 10;
+ }
+}
+.fi
+
+.PP
+
+.SH "5\&.3\&.2: An HTTP forwarder when travelling"
+
+.PP
+As another example, here\&'s my \f(CWcrossroads\&.conf\fP that I use on my
+Unix laptop\&. The problem that I face is that I need many HTTP proxy
+configurations (at home, at customers\&' sites and so on) but I\&'m too
+lazy to reconfigure browsers all the time\&.
+.PP
+Here\&'s how it used to be before crossroads:
+.PP
+.IP o
+At home, I would surf through a squid proxy on my local
+machine\&. The browser proxy setting is then
+\f(CWhttp://localhost:3128\fP\&.
+.IP
+.IP o
+Sometimes I start up an SSH tunnel to our offices\&. The
+tunnel has a local port 3129, and connects to a squid proxy on
+our e-tunity server\&. Hence, the browser proxy is then
+\f(CWhttp://localhost:3129\fP\&.
+.IP
+.IP o
+At a customer\&'s location I need the proxy
+\f(CWhttp://10\&.120\&.34\&.113:8080\fP, because they have configured it
+so\&.
+.IP
+.IP o
+And in yet other instances, I use a HTTP diagnostic tool
+Charles
+that sits between browser and website and shows me
+what\&'s happening\&. I run charles on my own machine and it
+listens to port 8888, behaving like a proxy\&. The browser
+configuration for the proxy is then
+\f(CWhttp://localhost:8888\fP\&.
+.PP
+Here\&'s how it works with a crossroads configuration:
+.PP
+.IP o
+I have configured my browsers to use
+\f(CWhttp://localhost:8080\fP as the proxy\&. For all situations\&.
+.IP
+.IP o
+I use the following crossroads configuration, and let
+crossroads figure out which proxy backend works, and which
+doesn\&'t\&. Note two particularities:
+.IP
+.IP o
+The statement \f(CWdispatchmode byorder\fP\&. This
+makes sure that once crossroads determines which
+backend works, it will stick to it\&. This usage of
+crossroads doesn\&'t need to balance over more than one
+back end\&.
+.IP
+.IP o
+The statement \f(CWbindto 127\&.0\&.0\&.1\fP makes sure
+that requests from other interfaces than loopback
+won\&'t get serviced\&.
+.IP
+.nf
+service HttpProxy {
+ port 8080;
+ bindto 127\&.0\&.0\&.1;
+ verbosity on;
+ dispatchmode byorder;
+ revivinginterval 15;
+
+ backend Charles {
+ server localhost:8888;
+ verbosity on;
+ }
+
+ backend CustomerProxy {
+ server 10\&.120\&.34\&.113:8080;
+ verbosity on;
+ }
+
+ backend SshTunnel {
+ server localhost:3129;
+ }
+
+ backend LocalSquid {
+ server localhost:3128;
+ }
+}
+.fi
+
+.PP
+As a final note, the commandline argument \f(CWtell\fP can be used to
+influence crossroad\&'s own detection mechanism of back end availability
+detection\&. E\&.g\&., if in the above example the back ends \f(CWSshTunnel\fP
+and \f(CWLocalSquid\fP are both active, then \f(CWcrossroads tell httpproxy
+sshtunnel down\fP will \&'take down\&' the back end \f(CWSshTunnel\fP -- and
+will automatically cause crossroads to switch to \f(CWLocalSquid\fP\&.
+.PP
+
+.SH "5\&.3\&.3: SSH login with enforced idle logout"
+
+.PP
+The following example shows how crossroads \&'throttles\&' SSH
+logins\&. Connections are accepted on port
+22 (the normal SSH port) and forwarded to the actual SSH daemon
+which is running on port 2222\&.
+.PP
+Note the usage of the
+\f(CWconnectiontimeout\fP directive\&. This makes sure that users are logged
+out after 10 minutes of inactivity\&. Note also the \f(CWmaxconnections\fP
+setting, this makes sure that no more than 10 concurrent logins occur\&.
+.PP
+.nf
+service Ssh {
+ port 22;
+ backlog 5;
+ maxconnections 10;
+ connectiontimeout 600;
+ backend TrueSshDaemon {
+ server localhost:2222;
+ }
+}
+.fi
+
+.PP
+
+.SH "6: Benchmarking"
+This section shows how crossroads affects the
+transmitting of HTML data when used as an intermediate \&'station\&'
+through which all data travels\&.
+.PP
+
+.SH "6\&.1: Benchmark 1: Accessing a proxy via crossroads or directly"
+
+.PP
+The benchmark was run on a system where the following was varied:
+.PP
+.IP 1\&.
+A website was recursively spidered through a local squid
+proxy\&. The spidering was repeated 10 times, the total was recorded\&.
+.IP
+.IP 2\&.
+Crossroads was placed in front of the squid proxy, and
+the website was again recursively spidered\&. Again, the
+spidering was repeated 10 times and the total was recorded\&.
+.PP
+The crossroads configuration of the second alternative is shown below:
+.PP
+.nf
+service HttpProxy {
+ port 8080;
+ verbosity on;
+ backend LocalSquid {
+ server 127\&.0\&.0\&.1;
+ port 3128;
+ verbosity on;
+ }
+}
+.fi
+
+.PP
+
+.SH "6\&.1\&.1: Results"
+
+.PP
+The results of this test are that crossroads causes a negligible
+delay, if it is statistically relevant at all\&. Without crossroads, the
+timing results are:
+.PP
+.nf
+real 0m8\&.146s
+user 0m0\&.130s
+sys 0m0\&.253s
+.fi
+
+.PP
+When using crossroads as a middle station, the results are:
+.PP
+.nf
+real 0m9\&.481s
+user 0m0\&.141s
+sys 0m0\&.230s
+.fi
+
+.PP
+
+.SH "6\&.1\&.2: Discussion"
+
+.PP
+The above shown results are quite favorable to crossroads\&. However,
+one should know that situations will exist where crossroads leans
+towards the \&'worst case\&' scenario, causing up to 50%
+delay\&.
+.PP
+E\&.g\&., imagine a test where a \f(CWwget\fP command retrieves a
+HTML document from an Apache server on \f(CWlocalhost\fP\&. Now we have
+(almost) no overhead due to network throttling, hostname lookups and
+so on\&. When this test would be run either with or without crossroads
+in between, then theoretically, crossroads would cause a much larger
+delay, because it has to read from the server, and then write the same
+information to \f(CWwget\fP\&. Each read/write occurs twice when crossroads
+sits in between\&.
+.PP
+This worst case scenario will however (fortunately) occur only very
+seldom in the real world:
+.PP
+.IP o
+Normally network issues, such as the above mentioned host
+name lookups or throughput restrictions, will add
+significantly to the duration of a request\&. The \&'twice as
+many\&' read/writes caused by crossroads are then relatively
+irrelevant\&.
+.IP
+.IP o
+Normally a significant amount of time will be spent in a
+back end, due to processing (e\&.g\&., when calling a servlet on a
+back end)\&. Again, this processing time will weigh much heavier
+than the multiple read/writes\&.
+.PP
+
+.SH "6\&.2: Benchmark 2: Crossroads versus Linux Virtual Server (LVS)"
+
+.PP
+LVS is a kernel-based balancer that acts like a masquerading
+firewall: TCP packets that arrive at the balancer are sent to one of
+the configured back ends\&. LVS has the advantage over crossroads that
+there is no stop-and-go in the transmission; in contrast, crossroads
+needs to send data via an internal buffer\&. Crossroads has the
+advantage that it offers instantaneous failover because it tries to
+contact the back end for upon each new TCP connection; in contrast,
+LVS isn\&'t aware of downtime of back ends (unless one implements an
+external heartbeat)\&. Also, crossroads offers more complex balancing
+than LVS\&.
+.PP
+
+.SH "6\&.2\&.1: Environment"
+
+.PP
+On the balancer, LVS was run on port 80, its forwarding set up for two
+equally weighted back ends, using \f(CWipvsadm\fP:
+.PP
+.nf
+ipvsadm -a -t 192\&.168\&.1\&.250:http -r 10\&.1\&.1\&.100:http -m -w 1
+ipvsadm -a -t 192\&.168\&.1\&.250:http -r 10\&.1\&.1\&.101:http -m -w 1
+.fi
+
+.PP
+Crossroads was run on port 81\&. The configuration file is shown below:
+.PP
+.nf
+service http {
+ port 81;
+ dispatchmode roundrobin;
+ revivinginterval 5;
+ backend one {
+ server 10\&.1\&.1\&.100;
+ port 80;
+ }
+ backend two {
+ server 10\&.1\&.1\&.101;
+ port 80;
+ }
+}
+.fi
+
+.PP
+
+.SH "6\&.2\&.2: Tests and results"
+
+.PP
+In the first test, ports 80 and 81 on the balancer were \&'bombed\&' with
+50 concurrent clients, each requesting a small page 50 times\&. The
+following timings where measured:
+.PP
+.IP o
+How long it takes to establish a connection;
+.IP o
+How long it takes to retrieve the page\&.
+.PP
+The results of this test were:
+.PP
+.IP o
+On average, each client took 0\&.12 seconds to connect
+to LVS, and each page was retrieved in 0\&.14 seconds;
+.IP o
+On average, each client took 0\&.11 seconds to connect to
+crossroads, and each page was retrieved in 0\&.13 seconds\&.
+.PP
+In this setup there seems to be no difference between the performance
+of LVS and crossroads!
+.PP
+In a second test, the size of the retrieved page was varied from 2\&.000
+to 2\&.000\&.000 bytes\&. This test was taken to see whether crossroads would
+show performance degradation when transferring larger amounts of data\&.
+.PP
+For each page size, 30 concurrent clients were started, that retrieved
+the page 50 times\&. Again, the connect times and processing times where
+recorded\&.
+.PP
+The results of the total time (connect time + retrieval time)
+are shown in the below table:
+.PP
+.TS
+ tab(~);
+
+
+
+
+
+
+
+
+
+
+
+
+---
+rrr
+rrr
+rrr
+rrr
+rrr
+---
+c.
+\fBBytes\fP~\fBLVS timing\fP~\fBCrossroads timing\fP
+2000~0\&.130741688~0\&.12739582
+20000~0\&.490916224~0\&.50376901
+200000~3\&.799440328~4\&.33125273
+2000000~45\&.25090855~45\&.9600728
+.TE
+
+.PP
+Again, the results show that crossroads performs just as effectively
+as LVS, even with large data chunks!
+.PP
+
+.SH "7: Compiling and Installing"
+
+
+.SH "7\&.1: Prerequisites"
+
+.PP
+The creation of crossroads requires:
+.PP
+.IP o
+Standard Unix tools, such as \f(CWsed\fP, \f(CWawk\fP, \f(CWPerl\fP
+(5\&.00 or better);
+.IP
+.IP o
+A POSIX-compliant C compiler;
+.IP
+.IP o
+The grammar generation tools \f(CWbison\fP and \f(CWflex\fP;
+.IP
+.IP o
+Support for SYSV IPC, networking and so on\&.
+
+.PP
+Basically a Linux or Apple MacOSX box will do nicely once you make
+sure that \f(CWbison\fP and \f(CWflex\fP are installed\&. To compile and install
+crossroads, follow these steps\&.
+.PP
+
+.SH "7\&.2: Compiling and installing"
+
+.PP
+.IP o
+Obtain the source distribution\&. It can be found on
+http://public\&.e-tunity\&.com\&. The distribution comes as an
+archive \f(CWcrossroads-\fP\fIX\&.YY\fP\f(CW\&.tar\&.gz\fP, where \fIX\&.YY\fP is
+a version number\&.
+.IP
+.IP o
+Unpack the archive in a sources directory using \f(CWtar
+xzf crossroads-\fP\fIX\&.YY\fP\f(CW\&.tar\&.gz\fP\&. The contents spill into a
+subdirectory \f(CWcrossroads-\fP\fIX\&.YY/\fP\&.
+.IP
+.IP o
+Change-dir into the directory\&.
+.IP
+.IP o
+Next, edit \f(CWetc/Makefile\&.def\fP and verify that all
+compilation settings are to your likings\&. The settings are
+explained in the file\&. \fBNote that\fP the default distribution
+of \f(CWMakefile\&.def\fP is suited for Linux or Apple MacOSX
+systems\&. On other Unices, or on non-Unix systems, you must
+particularly pay attention to \f(CWSET_PROC_TITLE_BY\&.\&.\&.\fP\&. When
+in doubt, comment out all \f(CWSET_PROC_TITLE\&.\&.\&.\fP
+settings\&. Crossroads will work nevertheless, but it won\&'t show
+nice titles in \f(CWps\fP listings\&. Also there\&'s a macro
+\f(CWEXTRA_LIBS\fP to add linkage flags (an example for a Solaris
+build is included)\&.
+.IP
+.IP o
+Now crossroads is ready for compilation\&. Do a \f(CWmake
+local\fP followed by \f(CWmake install\fP\&. The latter step may have
+to be done by the user \f(CWroot\fP if the \f(CWBINDIR\fP setting of
+\f(CWetc/Makefile\&.def\fP points to a root-owned directory\&.
+.IP
+.IP o
+The documentation doesn\&'t install in this process\&. If you
+want to install the documentation, then proceed as follows:
+.IP
+.IP o
+Optionally, \f(CWcp doc/crossroads\&.html\fP
+\fIhtmldirectory/\fP; where \fIhtmldirectory\fP is the destination
+directory for your HTML manuals;
+.IP
+.IP o
+Optionally, \f(CWcp doc/crossroads\&.pdf\fP
+\fIpdfdirectory/\fP; where \fIpdfdirectory\fP is the
+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;
+.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\&.
+.IP
+
+.SH "7\&.3: Configuring crossroads"
+
+.PP
+Now that the binary is available on your system, you need to create a
+suitable \f(CW/etc/crossroads\&.conf\fP\&. Use this manual or the output of
+\f(CWcrossroads samplconf\fP to get started\&.
+.PP
+Once you have the configuration ready, start crossroads with
+\f(CWcrossroads start\fP\&. Test the availability of your services and back
+ends\&. Monitor how crossroads is doing with:
+.PP
+.IP o
+In one terminal, run the script:
+.nf
+while [ 1 ] ; do
+ tput clear
+ crossroads status
+ sleep 3
+done
+.fi
+
+.IP
+\fBNote\fP that depending on your system you might need
+\f(CWsleep 3s\fP, i\&.e\&., with an \f(CWs\fP appended\&.
+.IP
+.IP o
+In another terminal, run:
+.nf
+while [ 1 ] ; do
+ tput clear
+ ps ax | grep crossroads | grep -v grep
+ sleep 3
+done
+.fi
+
+.IP
+\fBNote\fP that depending on your system you might need
+\f(CWps -ef\fP instead of \f(CWps ax\fP\&.
+.IP
+.IP o
+In yet another terminal, run \f(CWtail -f
+/var/log/messages\fP (supply the appropriate system log file if
+\f(CW/var/log/messages\fP doesn\&'t work for you)\&.
+.PP
+Now thoroughly test the availability of your back ends through
+crossroads\&. The status display will show an updated view of which back
+ends are selected and how busy they are\&. The process list will show
+which crossroads daemons are running\&. Finally, the tailing of
+\f(CW/var/log/messages\fP shows what\&'s going on -- especially if you have
+\f(CWverbosity true\fP statements in the configuration\&.
+.PP
+
+.SH "7\&.4: A boot script"
+
+.PP
+Finally, you may want to create a boot-time startup script\&. The exact
+procedure depends on the used Unix flavor\&.
+.PP
+
+.SH "7\&.4\&.1: SysV Style Startup"
+
+.PP
+On SysV style systems, there\&'s a startup script directory
+\f(CW/etc/init\&.d\fP where bootscripts for all utilities are located\&.
+You may have the \f(CWchkconfig\fP utility to automate the task of
+inserting scripts into the boot sequence, but
+otherwise the steps will resemble the following\&.
+.PP
+.IP o
+Create a script \f(CWcrossroads\fP in \f(CW/etc/init\&.d\fP similar to the
+following:
+.IP
+.nf
+#!/bin/sh
+/usr/local/bin/crossroads -v $@
+.fi
+
+.IP
+The stated directory \f(CW/usr/local/bin\fP must correspond with
+the installation path\&. The flag \f(CW-v\fP causes the startup to
+be more \&'verbose\&'\&. However, once daemonized, the verbosity is
+controlled by the appropriate statements in the configuration\&.
+.IP
+.IP o
+Determine your \&'runlevel\&': usually 3 when your system is
+running in text-mode only, or 5 when you are a graphical
+interface\&. If your runlevel is 3, then:
+.IP
+.nf
+root> cd /etc/rc\&.d/rc3\&.d
+root> ln -s /etc/init\&.d/crossroads S99crossroads
+root> ln -s /etc/init\&.d/crossroads K99crossroads
+.fi
+
+.IP
+This creates startup (\f(CWS*\fP) and stop (\f(CWK*\fP) links that
+will be run when the system enters or leaves a given runlevel\&.
+.IP
+If your runlevel is 5, then the right \f(CWcd\fP command is to
+\f(CW/etc/rc\&.d/rc5\&.d\fP\&. Alternatively, you can create the
+symlinks in both runlevel directories\&.
+.PP
+
+.SH "7\&.4\&.2: BSD Style Startup"
+
+.PP
+On BSD style systems, daemons are booted directly from \f(CW/etc/rc\fP and
+related scripts\&. Incase you have a file \f(CW/etc/rc\&.local\fP, edit it,
+and add the statement:
+.PP
+.nf
+/usr/local/bin/crossroads start
+.fi
+
+.PP
+If your BSD system lacks \f(CW/etc/rc\&.local\fP, then you may need to start
+Crossroads from \f(CW/etc/rc\fP\&. Your mileage may vary\&.
+.PP
diff --git a/doc/crossroads.pdf b/doc/crossroads.pdf
Binary files differ.
diff --git a/doc/crossroads.yo b/doc/crossroads.yo
@@ -0,0 +1,38 @@
+tocclearpage()
+titleclearpage()
+includefile(defs.yo)
+abstract(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.)
+article(Crossroads VER())
+ (Karel Kubat)
+ (2005, 2006, ff.)
+
+sect(Introduction)
+includefile(intro)
+
+sect(Installation for the impatient)
+includefile(impatient)
+
+sect(Using Crossroads)
+includefile(using)
+
+sect(The configuration)
+includefile(config)
+
+sect(Tips, Tricks and Remarks)
+includefile(tips)
+
+sect(Benchmarking)
+includefile(benchmarking)
+
+sect(Compiling and Installing)
+includefile(compiling)
+
diff --git a/doc/defs.yo b/doc/defs.yo
@@ -0,0 +1,84 @@
+COMMENT(
+
+ Local Yodl Macros
+ =================
+
+Here's a sample "local config" file that gets installed into
+/usr/e/share/yodl. You can use this as an example to cook your
+own. E.g., you'll want to a use different affiliation, mailto, and so
+on.
+
+Also, the shown latexlayoutcmds() probably won't be what you like to
+see... Good luck!
+-- [KK 2004-11-17]
+
+)
+
+COMMENT(General document modifiers)
+htmlstylesheet(http://www.e-tunity.com/css/yodl.css)
+affiliation(e-tunity)
+mailto(info@e-tunity.com)
+latexlayoutcmds(
+ \usepackage{a4wide}
+ \usepackage{fancyhdr}
+ \usepackage{graphicx}
+ \usepackage{moreverb}
+ \usepackage{palatino}
+ \setlength{\textheight}{21cm}
+ \setlength{\parindent}{0pt}
+ \setlength{\parskip}{1ex plus 0.5ex minus 0.2ex}
+ \renewcommand{\headrulewidth}{.6pt}
+ \renewcommand{\footrulewidth}{.6pt}
+ \fancypagestyle{plain}{
+ \fancyhf{}
+ \fancyhead[LE,RO]{}
+ \fancyhead[LO]{\small\slshape \leftmark}
+ \fancyhead[RE]{\small\slshape \rightmark}
+ \fancyfoot[LE,RO]{\small\thepage}
+ %\fancyfoot[RE,LO]{\small\textbf{e-tunity}}
+ }
+ \AtBeginDocument{\pagestyle{plain}})
+setfigureext(.png)
+sethtmlfigureext(.png)
+
+COMMENT(pngfig (basename-without-extension) (caption) (label)
+ A-la figure() but includes the true png. We can do that
+ because we use pdflatex.)
+
+redef(pngfig)(3)(\
+ whenhtml(figure(ARG1)(ARG2)(ARG3))\
+ whenlatex(\
+ latexcommand(\begin{figure}[htbp])\
+ latexcommand(\centerline{)\
+ latexcommand(\includegraphics[scale=.5]{)\
+ ARG1.png+\
+ latexcommand(}})\
+ latexcommand(\caption{\small )ARG2+latexcommand(})\
+ latexcommand(\label{)ARG3+latexcommand(})\
+ latexcommand(\end{figure}))
+ whenman(verb(
+ =====================================================
+ Insert figure ARG1 about here
+ ARG2
+ =====================================================)\
+ label(ARG3)))
+
+COMMENT(listing(text)
+ A-la verb() but nicely formatted in LaTeX.)
+
+redef(listing)(1)(\
+ whenhtml(verb(ARG1))\
+ whenlatex(\
+ PUSHCHARTABLE()
+ latexcommand(\begin{listing}{1})\
+ XXnl()\
+ NOEXPAND(ARG1)\
+ XXnl()\
+ latexcommand(\end{listing})
+ POPCHARTABLE())\
+ whenman(\
+ XXroffcmd(.nf)()()()\
+ INTERNALINDEX(verb on)\
+ NOEXPAND(ARG1)\
+ INTERNALINDEX(verb off)\
+ XXroffcmd(.fi)()()()))
diff --git a/doc/impatient.yo b/doc/impatient.yo
@@ -0,0 +1,42 @@
+
+For the impatient, here's the very-quick-but-very-superficial recipy
+for getting crossroads up and running:
+
+itemization(
+
+ it() Obtain the crossroads source archive. Change-dir to a
+ 'sources' directory on your system and unpack the archive.
+
+ it() Change-dir into the created directory tt(crossroads/).
+
+ it() Type tt(make install). This installs the crossroads
+ binary into tt(/usr/local/bin/). If the compilation doesn't
+ work on your system, check tt(etc/Makefile.def) for hints.
+
+ it() Create a file tt(/etc/crossroads.conf). In it state
+ something like:
+
+ verb(\
+service www {
+ port 80;
+ revivinginterval 15;
+ backend one {
+ server 10.1.1.100:80;
+ }
+ backend two {
+ server 10.1.1.101:80;
+ }
+})
+
+ Of course, make sure that you have webservers running on
+ 10.1.1.100 and 10.1.1.101.
+
+ it() Type tt(crossroads start).
+
+ it() Surf to the machine where crossroads is running. You will
+ see the pages served by the back ends 10.1.1.100 or
+ 10.1.1.101.
+
+ it() To monitor the status of crossroads, type tt(crossroads
+ status).
+)
diff --git a/doc/intro.yo b/doc/intro.yo
@@ -0,0 +1,169 @@
+Crossroads is a daemon that basically accepts TCP connections
+at preconfigured ports, and given a list of 'back ends'
+distributes each incoming connection to one of the back ends,
+so that a client request is
+served. Additionally, crossroads maintains an internal
+administration of the back end connectivity: if a back end isn't
+usable, then the client request is handled using another back
+end. Crossroads will then periodically check whether a previously not
+usable back end has come to life yet. Also, crossroads can select
+back ends by estimating the load, so that balancing is achieved.
+
+Using this approach, crossroads serves as load balancer and fail over
+utility. Crossroads will very likely not be as reliable as
+hardware based balancers, since it always will require a server to
+run on. This server, in turn, may become a new Single Point of
+Failure (SPOS). However, in situations where cost efficiency is an issue,
+crossroads may be a good choice. Furthermore, crossroads can be
+deployed in situations where a hardware based balancing already
+exists and augmenting service reliability is needed. Or, crossroads may be
+run off a diskless system, which again improves reliability of the
+underlying hardware.
+
+This document describes how to use crossroads, how to configure it
+in order to increase the reliability of your systems, and how to
+compile the program from its sources. whenhtml(This document is
+also available in url(PDF)(crossroads.pdf) format.)
+
+subsect(Obtaining Crossroads)
+
+As quick reference, here are some important URL's for Crossroads:
+
+itemization(
+ it() lurl(http://public.e-tunity.com) is the site that serves
+ Crossroads and other packages. You can browse this at leisure
+ for documentation, sources, and so on.
+
+ it()
+ lurl(http://public.e-tunity.com/crossroads/crossroads-latest.tar.gz) is
+ the 'latest' distribution archive. (Older versions aren't
+ stored. If you really need an old version, contact me at
+ url(karel@e-tunity.com)(mailto:karel@e-tunity.com).)
+
+ it()
+ lurl(http://public.e-tunity.com/crossroads/crossroads.html) is
+ the documentation in HTML format (this text). Substitute
+ em(.pdf) for em(.html) to get the documentation in PDF format.)
+
+
+subsect(Copyright and Disclaimer)
+
+Crossroads is distributed as-is, without assumptions of fitness
+or usability. You are free to use crossroads to your
+liking. It's free, and as with everything that's free: there's
+also no warranty.
+
+You are allowed to make modifications to the source code of
+crossroads, and you are allowed to (re)distribute crossroads, as
+long as you include this text, all sources, and if applicable: all
+your modifications, with each distribution.
+
+While you are allowed to make any and all changes to the sources,
+I would appreciate hearing about them. If the changes concern new
+functionality or bugfixes, then I'll include them in a next
+release, stating full credits.
+
+subsect(Terminology)
+
+Throughout this document, the following terms are used: footnote(Many
+more meanings of the terms will exist -- yes, I am aware of that. I'm
+using the terms here in a very strict sense.)
+
+description(
+ dit(A client) is a process that initiates a network connection
+ to get contact with some service.
+ dit(A service) or bf(server process) or bf(listener)
+ is a central application
+ that accepts network connections from clients and sevices
+ them.
+ dit(Back ends) are locations where crossroads looks in
+ order to service its clients. Crossroads sits 'in between'
+ and does its tricks. Therefore, as far as the back ends
+ are concerned, crossroads behaves like a client. As far as
+ the true client is concerned, crossroads behaves like the
+ service. The communication is however transparent: neither
+ client nor back end are aware of the middle position of
+ crossroads.
+ dit(A connection) is a network conversation between client and service,
+ where data are transferred to and fro. As
+ far as crossroads is concerned, success means that a
+ connection can be established without errors on
+ the network level. Crossroads isn't aware of service
+ pecularities. E.g., when a webserver answers tt(HTTP/1.0
+ 500 Server Error) then crossroads will see this as a
+ succesful connection, though the user behind a browser may
+ think otherwise.
+ dit(Back end selection algorithms) are methods by which
+ crossroads determines which back end it will talk to
+ next. Crossroads has a number of built-in algorithms,
+ which may be configured per service.
+ dit(Back end states) are the statusses of each back end that
+ is known to crossroads. A back end may be available,
+ (temporarily) unavailble or truly down. When a back end is
+ temporarily unavailable, then crossroads will periodically
+ check whether the back end has come to life yet (that is,
+ if configured so).
+ dit(A spike) is a sudden increase in activity, leading to
+ extra load on a given service. When crossroads is in
+ effect and when the spike occurs in one connection,
+ then obviously the spike will also appear at one
+ of the back ends. However, crossroads will see the spike
+ and will make sure that a subsequent request goes to an
+ other back end. In contrast, when several connections
+ arrive simultaneously and cause a spike, then crossroads
+ will be able to distribute the connections over several
+ back ends, thereby 'flattening out' the increase.
+ dit(Load balancing) means that incoming client requests are
+ distributed over more than just one back end (which wouldn't be the
+ case if you wouldn't be running crossroads). Enabling load
+ balancing is nothing more than duplicating services over
+ more than one back end, and having something (in this
+ case: crossroads) distribute the requests, so that per
+ back end the load doesn't get too high.
+ dit(An HTTP session) is a series of separate network connections
+ that originate from one browser. E.g., to fill the display
+ with text and images, the browser hits a website several times.
+ 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.
+ 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
+ browser are forced to go to the same back end, instead of
+ being balanced to other ones).
+ dit(Back end usage) is measured by crossroads in order to be
+ able to determine back end selection. Crossroads stores
+ information about the number of active connections, the
+ transferred bytes and
+ about the connection duration. These numbers can be used to
+ estimate which back end is the least used -- and
+ therefore, presumably, the best candidate for a new
+ request.
+ dit(Fail over) is almost always used when load balancing is in
+ effect. The distributor of client requests (crossroads of
+ course) can also monitor back ends, so that incase a back
+ end is 'down', it is no longer accessed.
+ dit(Service downtime) normally occurs when a service is
+ switched off. Downtime is obviously avoided when fail over
+ is in effect: a back end can be taken out of service in a
+ controlled manner, without any client noticing it.
+)
+
+subsect(Porting issues for pre-0.26 installations)
+
+ As of version 0.26 the syntax of the configuration file has
+ changed. In particular:
+
+ itemization(
+ it() The keyword tt(maxconnections) is now used instead of
+ tt(maxclients);
+ it() The keyword tt(connectiontimeout) is now used instead of
+ tt(sessiontimeout).)
+
+ Therefore when converting configuration files to the new syntax,
+ the above keywords must be changed. (The reason for these changes
+ is that 0.26 introduces em(sticky HTTP sessions) that span
+ 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
diff --git a/doc/tips.yo b/doc/tips.yo
@@ -0,0 +1,547 @@
+The following sections elaborate on the directives as described in
+section ref(config) to illustrate how crossroads works and to help you
+achieve the "optimal" balancing configuration.
+
+subsect(How back ends are selected in load balancing)label(howselected)
+
+In order to tune your load balancing, you'll need to understand how
+crossroads computes usage, how weighing works, and so on. In this
+section we'll focus on the dispatching modes tt(bysize), tt(byduration)
+and tt(byconnections) only. The other dispatching types are
+self-explanatory.
+
+
+subsubsect(Bysize, byduration or byconnections?)
+
+As stated before, crossroads doesn't know 'what a service does' and
+how to judge whether a given back end is very busy or not. You
+must therefore give the right hints:
+
+itemization(
+ it() In general, a service which is CPU bound, will be more
+ busy when it takes longer to process a request. The dispatch
+ mode tt(byduration) is appropriate here.
+
+ it() In contrast, a service which is filesystem bound, will be
+ more busy when more data are transferred. The dispatch mode
+ tt(bysize) is apppropriate.
+
+ it() The dispatch mode tt(byduration) can also be used when
+ network latency is an issue. E.g., if your balancer has back
+ ends that are geograpically distributed, then tt(byduration)
+ would be a good way to select best available back ends.
+
+ it() Furthermore it is noteworthy that tt(dispatchmode
+ byduration) is not usable for interactive processes such as
+ SSH logins. Idle time of a
+ login adds to the duration, while causing (almost) no
+ load. Mode tt(byduration) should only be used for automated
+ processes that don't wait for user interaction (e.g., SOAP
+ calls and other HTTP requests).
+
+ it() As a last remark, the dispatching mode tt(byconnections) can
+ be used if you don't have other clues for load
+ estimations.
+
+ E.g., consider a database connection. What's
+ heavier on the back end, time-consuming connections, or connections
+ where loads of bytes are transferred? Well, that depends. A
+ tough tt(select) query that joins multiple tables can be very
+ heavy on the back end, though the response set can be quite
+ small - and hence the number of
+ transferred bytes. That would suggest
+ dispatching by duration. However, tt(byduration)
+ balancing doesn't respresent the true world, when interactive
+ connections can occur where users have an idle TCP connection to
+ the database:
+ this consumes time, but no bytes (see the SSH login example
+ above). In this case, the dispatch mode tt(byconnections) may be
+ your best bet.
+
+ )
+
+
+subsubsect(Averaging size and duration)
+
+The configuration statement tt(dispatchmode bysize) or tt(byduration)
+allows an optional modifier tt(over) em(number), where the stated
+number represents a connection count. When this modifier is present, then
+crossroads will use a moving average over the last em(n) connections to
+compute duration and size figures.
+
+In the real world you'll always want this modifier. E.g., consider two
+back ends that are running for years now, and one of them is suddenly
+overloaded and very busy (it experiences a 'spike' in activity).
+When the tt(over) modifier is absent, then
+the sudden load will hardly show up in the usage figures -- it will
+flatten out due to the large usage figures already stored in the years
+of service.
+
+In contrast, when e.g. tt(over 3) is in effect, then a sudden load
+does show up -- because it highly contributes to the average of three
+connections.
+
+
+subsubsect(Specifying decays)
+
+Decays are also only relevant when crossroads computes the 'next best
+back end' by size (bytes) or duration (seconds). E.g., imagine two
+back ends A and B, both averaged over say 3 connections.
+
+Now when back end A is suddenly hit by a spike,
+its average would go up accordingly. But the back end would never
+again be used, unless B also received a similar spike, because A's
+'usage data' over its last three connections would forever be larger than
+B's data.
+
+For that reason, you should in real situations probably always
+specify a decay, so that the backend selection algorithm recovers from
+spikes. Note that the usage data of the back end where a decay is
+specified, decay when bf(other) back ends are hit. The decay parameter
+is like specifying how fast your body regenerates when someone else
+does the work.
+
+The below configuration illustrates this:
+
+verb(\
+/* Definition of the service */
+service soap {
+ /* Local TCP port */
+ port 8080;
+
+ /* We'll select back ends by the processing
+ * duration
+ */
+ dispatchmode byduration over 3;
+
+ /* First back end: */
+ backend A {
+ /* Back end IP address and port */
+ server 10.1.1.1:8080;
+
+ /* When this back end is NOT hit because
+ * the other one was less busy, then the
+ * usage parameters decay 10% per connection
+ */
+ decay 10;
+ }
+
+ /* Second back end: */
+ backend B {
+ server 10.1.1.2:8080;
+ decay 10;
+ }
+})
+
+subsubsect(Adjusting the weights)
+
+The back end modifier tt(weight) is useful in situations where your
+back ends differ in respect to performance. E.g,. your back ends may
+be geographically distributed, and you know that a given back end is
+difficult to reach and often experiences network lag.
+
+Or you may have
+one primary back end, a system with a fast CPU and enough memory, and a
+small fall-back back end, with a slow CPU and short on memory. In that
+case you know in advance that the second back end should be used only
+rarely. Most requests should go to the big server, up to a certain load.
+
+In such cases you will know in advance that the best performing back ends
+should be selected the most often. Here's where the tt(weight)
+statement comes in: you can simply increase the weight of the back
+ends with the least performance, so that they are selected less
+frequently.
+
+E.g., consider the following configuration:
+
+verb(\
+service soap {
+ port 8080;
+ dispatchmode byduration over 3;
+ backend A {
+ server 10.1.1.1:8080;
+ decay 20;
+ }
+ backend B {
+ server 10.1.1.2:8080;
+ weight 2;
+ decay 10;
+ }
+ backend C {
+ server 10.1.1.3:8080;
+ weight 4;
+ decay 5;
+ }
+})
+
+This will cause crossroads to select back ends by the processing time,
+averaging over the last three connections. However, backend B will kick
+in only when its usage is half of the usage of A (back end B is
+probably only half as fast as A). Backend C will kick in only when its
+usage is a quarter of the usage of A, which is half of the usage of B
+(back end C is probably very weak, and just a fall-back system incase
+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.
+
+
+subsubsect(Throttling the number of concurrent connections)
+
+If you suspect that your service may occasionally receive 'spikes' of
+activity+footnote(which you should always assume), then it might be a
+good idea to protect your service by specifying a maximum number of
+concurrent connections. This protection can be specified on two levels:
+
+description(
+ dit(On the service level) a statement like tt(maxconnections
+ 100;) states that the service as a whole will never
+ service more than 100 concurrent connections. This means that
+ all your back ends and the crossroads balancer itself
+ will be protected from being overloaded.
+ dit(On the back end level) a statement like tt(maxconnections 10;)
+ states that this particular back end will never have more
+ than 10 concurrent connections; regardless of the overall
+ setting on the service level. This means that this
+ particular back end will be protected from being
+ overloaded (regardless of what other back ends may
+ experience).)
+
+The tt(maxconnections) statement, combined with a back end selection
+algorithm, allows very fine granularity. The tt(maxconnections) statement
+on the back end level is like a hand brake: even when you specify a
+back end algorithm that would protect a given back end from being used
+too much, a situation may occur where that back end is about to be
+hit. A tt(maxconnections) statement on the level of that back may then
+protect it.
+
+
+subsect(HTTP Session Stickiness)
+
+This section focuses on HTTP session stickiness. This term refers to
+the ability of a balancer to route a conversation between browser and
+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.
+
+subsubsect(Don't use stickiness!)
+
+The rule of thumb as far as the balancer is concerned, is: bf(Do not
+use HTTP session stickiness unless you really have to.) Enabling
+session stickiness hampers failover, balancing and performance:
+
+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.
+ 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
+ balancer however must continue to send the requests there.
+ it() Performance is hampered because crossroads needs to 'unpack'
+ messages as they are passed to and fro. That's because
+ crossroads needs to check the HTTP headers in the messages
+ for persistence cookies.)
+
+There is a number of measures that you can take to avoid using session
+stickiness. E.g., session data can be 'shared' between web back
+ends. PHP offers functionality to store session data in a database, so
+that all PHP applications have access to these data. Application
+servers such as Websphere can be configured to replicate session data
+between nodes.
+
+subsubsect(But if you must..)
+
+However, if you bf(must) use session stickiness, then proceed as
+follows:
+
+itemization(
+ it() At the level of a tt(service) description, set the type to
+ tt(stickyhttp).
+ it() At the level of each back end description, configure the
+ tt(stickycookie) and a tt(insertcookie) directives.)
+
+Once crossroads sees that, it will examine each HTTP message that it
+shuttles between client and back end:
+
+itemization(
+ it() If there is no persistence cookie in the HTTP headers of a
+ client's request, then the message must be the first one and
+ a new session should be established.
+ Crossroads selects an appropriate back
+ end, sends the message to that back end, catches the reply,
+ and inserts a tt(Set-Cookie) directive.
+ it() If there is a persistence cookie in the HTTP headers of a
+ client's request, then the request is part of an already
+ established session. Crossroads analyzes the cookie and
+ forwards the request to the appropriate back end.)
+
+Below is a short example of a configuration.
+
+verb(\
+service www {
+ port 80;
+ type stickyhttp;
+ revivinginterval 15;
+ dispatchmode byconnections;
+
+ backend one {
+ server 10.1.1.100:80;
+ stickycookie XRID=100;
+ insertcookie "XRID=100; Path=/";
+ }
+
+ backend two {
+ server 10.1.1.101:80;
+ stickycookie XRID=101;
+ insertcookie "XRID=101; Path=/";
+ }
+})
+
+Note how the cookie names and values in the directives
+tt(stickycookie) and tt(insertcookie) match. That is obviously a
+prerequisite for stickiness.
+
+
+subsect(Configuration examples)
+
+As a general hint, use tt(crossroads sampleconf) to view the most
+up-to-date examples of configurations. The description below shows a
+few examples too.
+
+
+subsubsect(A load balancer for three webserver back ends)
+
+The following configuration example binds crossroads to port 80 of the
+current server, and distributes the load over three back ends. This
+configuration shows most of the possible settings.
+
+verb(\
+service www {
+ /* We don't need session stickyness. */
+ type any;
+
+ /* Port on which we'll listen in this service: required. */
+ port 8000;
+
+ /* What IP address should this service listen? Default is 'any'.
+ * Alternatively you can state an explicit IP address, such as
+ * 127.0.0.1; that would bind the service only to 'localhost'. */
+ bindto any;
+
+ /* Verbose reporting or not. Default is off. */
+ verbosity on;
+
+ /* Dispatching mode, or: How to select a back end for an incoming
+ * request. Possible values:
+ * roundrobin: just the next back end in line
+ * random: like roundrobin, but at random to make things more
+ * confusing. Probably only good for testing.
+ * bysize: The backend that transferred the least nr of bytes
+ * is the next in line. As a modifier you can say e.g.
+ * bysize over 10, meaning that the 10 last connections will
+ * be used to compute the transfer size, instead of all
+ * transfers.
+ * byduration: The backend that was active for the shortest time
+ * is the next in line. As a modifier you can say e.g.
+ * byduration of 10 to compute over the last 10 connections.
+ * byconnections: The back end with the least active connections
+ * is the next ine line.
+ * byorder: The first available back end is always taken.
+ */
+ dispatchmode byduration over 5;
+
+ /* Interval at which we'll check whether a temporarily unavailable
+ * backend has woken up.
+ */
+ revivinginterval 5;
+
+ /* TCP backlog of connections. Default is 0 (no backlog, one
+ * connection may be active).
+ */
+ backlog 5;
+
+ /* For status reporting: a shared memory key. Default is the same
+ * as the port number, OR-ed by a magic number.
+ */
+ shmkey 8000;
+
+ /* This controls when crossroads should consider a connection as
+ * finished even when the TCP sockets weren't closed. This is to
+ * avoid hanging connections that don't do anything. NOTE THAT when
+ * crossroads cuts off a connection due to timeout exceed, this is
+ * not marked as a failure, but as a success. Default is 0: no timeout.
+ */
+ connectiontimeout 300;
+
+ /* The max number of allowed client connections. When present, connections
+ * won't be accepted if the max is about to be exceeded. When
+ * absent, all connections will be accepted, which might be misused
+ * for a DOS attack.
+ */
+ maxconnections 300;
+
+ /* Now let's define a couple of back ends. Number 1: */
+ backend www_backend_1 {
+ /* The server and its port, the minimum configuration. */
+ server httpserver1;
+ port 9010;
+ /* The 'decay' of usage data of this back end. Only relevant
+ * when the whole service has 'dispatchmode bysize' or
+ * 'byduration'. The number is a percentage by which the usage
+ * parameter is decreased upon each connection of an other back
+ * end.
+ */
+ decay 10;
+
+ /* To see what's happening in /var/log/messages: */
+ verbosity on;
+ }
+
+ /* The second one: */
+ backend www_backend_2 {
+ /* Server and port */
+ server httpserver2;
+ port 9011;
+
+ /* Verbosity of reporting when this back end is active */
+ verbosity on;
+
+ /* Decay */
+ decay 10;
+
+ /* This back end is twice as weak as the first one */
+ weight 2;
+
+ /* Event triggers for system commands upon succesful activation
+ * and upon failure.
+ */
+ onsuccess echo 'success on backend 2' | mail root;
+ onfailure echo 'failure on backend 2' | mail root;
+ }
+
+ /* And yet another one.. this time we will dump the traffic
+ * to a trace file. Furthermore we don't want more than 10 concurrent
+ * connections here. Note that there's also a total maxconnections for the
+ * whole service.
+ */
+ backend www_backend_3 {
+ server httpserver3;
+ verbosity on;
+ port 9000;
+ verbosity on;
+ decay 10;
+ trafficlog /tmp/backend.3.log;
+ maxconnections 10;
+ }
+})
+
+subsubsect(An HTTP forwarder when travelling)
+
+As another example, here's my tt(crossroads.conf) that I use on my
+Unix laptop. The problem that I face is that I need many HTTP proxy
+configurations (at home, at customers' sites and so on) but I'm too
+lazy to reconfigure browsers all the time.
+
+Here's how it used to be before crossroads:
+
+itemization(
+ it() At home, I would surf through a squid proxy on my local
+ machine. The browser proxy setting is then
+ tt(http://localhost:3128).
+
+ it() Sometimes I start up an SSH tunnel to our offices. The
+ tunnel has a local port 3129, and connects to a squid proxy on
+ our e-tunity server. Hence, the browser proxy is then
+ tt(http://localhost:3129).
+
+ it() At a customer's location I need the proxy
+ tt(http://10.120.34.113:8080), because they have configured it
+ so.
+
+ it() And in yet other instances, I use a HTTP diagnostic tool
+ url(Charles)(http://www.xk72.com/charles)
+ that sits between browser and website and shows me
+ what's happening. I run charles on my own machine and it
+ listens to port 8888, behaving like a proxy. The browser
+ configuration for the proxy is then
+ tt(http://localhost:8888).)
+
+Here's how it works with a crossroads configuration:
+
+itemization(
+ it() I have configured my browsers to use
+ tt(http://localhost:8080) as the proxy. For all situations.
+
+ it() I use the following crossroads configuration, and let
+ crossroads figure out which proxy backend works, and which
+ doesn't. Note two particularities:
+
+ itemization(
+ it() The statement tt(dispatchmode byorder). This
+ makes sure that once crossroads determines which
+ backend works, it will stick to it. This usage of
+ crossroads doesn't need to balance over more than one
+ back end.
+
+ it() The statement tt(bindto 127.0.0.1) makes sure
+ that requests from other interfaces than loopback
+ won't get serviced.)
+
+verb(\
+service HttpProxy {
+ port 8080;
+ bindto 127.0.0.1;
+ verbosity on;
+ dispatchmode byorder;
+ revivinginterval 15;
+
+ backend Charles {
+ server localhost:8888;
+ verbosity on;
+ }
+
+ backend CustomerProxy {
+ server 10.120.34.113:8080;
+ verbosity on;
+ }
+
+ backend SshTunnel {
+ server localhost:3129;
+ }
+
+ backend LocalSquid {
+ server localhost:3128;
+ }
+}))
+
+As a final note, the commandline argument tt(tell) can be used to
+influence crossroad's own detection mechanism of back end availability
+detection. E.g., if in the above example the back ends tt(SshTunnel)
+and tt(LocalSquid) are both active, then tt(crossroads tell httpproxy
+sshtunnel down) will 'take down' the back end tt(SshTunnel) -- and
+will automatically cause crossroads to switch to tt(LocalSquid).
+
+
+subsubsect(SSH login with enforced idle logout)
+
+The following example shows how crossroads 'throttles' SSH
+logins. Connections are accepted on port
+22 (the normal SSH port) and forwarded to the actual SSH daemon
+which is running on port 2222.
+
+Note the usage of the
+tt(connectiontimeout) directive. This makes sure that users are logged
+out after 10 minutes of inactivity. Note also the tt(maxconnections)
+setting, this makes sure that no more than 10 concurrent logins occur.
+
+verb(\
+service Ssh {
+ port 22;
+ backlog 5;
+ maxconnections 10;
+ connectiontimeout 600;
+ backend TrueSshDaemon {
+ server localhost:2222;
+ }
+})
diff --git a/doc/using.yo b/doc/using.yo
@@ -0,0 +1,38 @@
+Crossroads is started from the commandline, and highly depends on
+tt(/etc/crossroads.conf) (the default configuration file). It
+supports a number of flags (e.g., to overrule the location of the
+configuration file). The actual usage information is always obtained
+by typing tt(crossroads) without any arguments. Crossroads then
+displays the allowed arguments.
+
+This section shows the basic usage.
+
+itemization(
+ it() tt(crossroads start) and tt(crossroads stop) are typical
+ actions that are run from system startup scripts. The
+ meaning is self-explanatory.
+ it() tt(crossroads restart) is a combination of the former
+ two. Beware that a restart may cause discontinuity in
+ service; it is just a shorthand for typing the 'stop' and
+ 'start' actions after one another.
+ it() tt(crossroad status) reports on each running
+ service. Per service, the state of each back end is
+ reported.
+ it() tt(crossroads tell) em(service backend state) is a
+ command line way of telling crossroads that a given back
+ end, of a given service, is in a given state. Normally
+ crossroads maintains state information itself, but by
+ using tt(crossroads tell), a back end can be e.g. taken
+ 'off line' for servicing.
+ it() tt(crossroads configtest) tells you whether the
+ configuration is syntactially correct.
+ it() tt(crossroads services) reports on the configured
+ services. In contrast to tt(crossroads status), this
+ option only shows what's configured -- not what's up and
+ running. Therefore, tt(crossroads services) doesn't
+ report on back end states.
+ it() tt(crossroads sampleconf) shows a sample configuration on
+ screen. A good way of quicky viewing the configuration
+ file syntax, or of getting a start for your own
+ configuration tt(/etc/crossroads.conf).
+)
diff --git a/etc/Makefile.def b/etc/Makefile.def
@@ -0,0 +1,45 @@
+# Definitions for the making of....
+# crossroads.
+
+# Versioning. This defines the overall version ID and must match the topmost
+# entry in the ChangeLog.
+VER = 1.00
+
+# Default config
+DEFAULT_CONF = /etc/crossroads.conf
+
+# The max nr of backends of a given service. This is a fixed number,
+# because it defines the size of the shared memory that crossroads claims.
+MAX_BACKEND = 50
+
+# Magic mask for shared memory keys.
+SHM_MASK = 0x19081962
+
+# Sleep time (sec). When we detect that there are too many clients, or
+# no back ends, then we'll wait during this period.
+SLEEP_TIME = 10
+
+# How long before an attempt to connect to a back end fails (secs)?
+# This is NOT the sessiontimeout specifier of the config file; it affects
+# trying to connect to a back end. If your back ends are nearby
+# (as they should be) then keep this number small.
+CONNECT_TIMEOUT = 2
+
+# 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
+# isn't set.
+ifeq ($(strip $(EBINDIR)),)
+BINDIR ?= /usr/local/bin
+else
+BINDIR ?= $(EBINDIR)
+endif
+
+# If you're on Linux, then one can set the program title for 'ps' output
+# by modifying argv directly. If you're on Linux or if your system supports
+# this too, then define as value 1. Otherwise set to zero.
+SET_PROC_TITLE_BY_ARGV = 1
+
+# Uncomment if you're on Solaris and don't want to fiddle with
+# LD_LIBRARY_PATH or crle (yuk!)
+# EXTRALIBS = -R/usr/ucblib
diff --git a/etc/Makefile.help b/etc/Makefile.help
@@ -0,0 +1,15 @@
+============================================================================
+ The Making Of.... Crossroads.
+============================================================================
+Make what? Choose:
+ make local Local program construction; src/crossroads will
+ be built
+ make install Locally built program is installed, see
+ etc/Makefile.def for the destination location
+ make documentation Creates documentation under doc/. Requires Yodl
+ and pdflatex.
+ make clean Cleanup of stale files
+ make distclean Even cleaner still
+ make dist Creates a distribution (e-tunity only)
+ make upload Uploads to our public site (e-tunity only)
+============================================================================
diff --git a/src/Makefile b/src/Makefile
@@ -0,0 +1,71 @@
+include ../etc/Makefile.def
+
+all:
+ ../tools/gettools e-ver e-txt2c c-conf
+ ../tools/e-ver ../ChangeLog $(VER)
+ $(MAKE) textconv
+ $(MAKE) grammar
+ $(MAKE) objects
+ $(MAKE) crossroads
+
+install: all $(BINDIR)/crossroads
+
+$(BINDIR)/crossroads: crossroads
+ install -s crossroads $(BINDIR)
+
+samplerun: all
+ -./crossroads -c sample.conf stop
+ ./crossroads -c sample.conf start
+
+textconv: usage.h sampleconf.h
+usage.h: usage.txt
+ ../tools/e-txt2c USAGETEXT <usage.txt | expand >usage.h
+sampleconf.h: sample.conf
+ ../tools/e-txt2c SAMPLECONF <sample.conf | expand >sampleconf.h
+
+grammar: parser.c lexer.c
+lexer.c: lexer.l
+ flex -o$@ $<
+parser.c: parser.y
+ bison -d -o $@ $<
+
+SRC = $(wildcard *.c)
+OBJ = $(patsubst %.c, %.o, $(SRC))
+objects: $(OBJ)
+
+CC = $(shell ../tools/c-conf c-compiler)
+
+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) \
+ $(shell ../tools/c-conf ifheader malloc.h HAVE_MALLOC_H) \
+ $(shell ../tools/c-conf libfunction flock HAVE_FLOCK) \
+ $(shell ../tools/c-conf libfunction lockf HAVE_LOCKF)
+
+LIBS = $(shell ../tools/c-conf lib ucb nsl pthread socket 2>/dev/null)
+
+parser.o: parser.c
+ $(CC) $(DEFS) -c -g $<
+
+lexer.o: lexer.c
+ $(CC) $(DEFS) -c -g $<
+
+%.o: %.c
+ $(CC) $(DEFS) -c -g -Wall $<
+
+crossroads: libcrossroads.a
+ $(CC) $(EXTRALIBS) -g -L. -lcrossroads $(LIBS) -o $@
+
+libcrossroads.a: $(OBJ)
+ ar rs libcrossroads.a $(OBJ)
+
+distclean: clean
+clean:
+ rm -f $(OBJ) lexer.c parser.c parser.h libcrossroads.a crossroads \
+ usage.h sampleconf.h
+
+# Extra deps:
+usage.o: usage.c usage.h ../etc/Makefile.def
+main.o: main.c crossroads.h ../etc/Makefile.def
+sampleconf.o: sampleconf.c sampleconf.h
diff --git a/src/allocreporter.c b/src/allocreporter.c
@@ -0,0 +1,75 @@
+#include "crossroads.h"
+
+void alloc_reporter (Service *s, int first) {
+ int shmid;
+ struct shmid_ds shmbuf;
+
+ if (flag_foreground) {
+ /* Fake it! */
+ msg ("Obtaining reporting memory for service %s "
+ "(in memory segment, NOT in shared memory "
+ "since we're in debug mode)",
+ s->name);
+ servicereport = xmalloc (sizeof(Servicereport));
+ memset (servicereport, 0, sizeof(Servicereport));
+ return;
+ }
+
+ /* Get an ID to work on, depending on the create mode.
+ Get a corresponding semaphore too.
+ */
+ msg ("Obtaining reporting memory for service %s (key %d)",
+ s->name, s->shmkey);
+
+ if (first) {
+ /* First time 'round. We're allocating for the first time here. */
+ if ( (shmid = shmget (s->shmkey, sizeof(Servicereport),
+ 0644 | IPC_CREAT) ) >= 0)
+ msg ("Created new shm at id %d", shmid);
+ else
+ error ("Cannot create shared memory for service %s (key %d: %s)",
+ s->name, s->shmkey, strerror(errno));
+
+ /* Make sure that we're the owner of the segment. */
+ if (shmctl (shmid, IPC_STAT, &shmbuf) == -1)
+ error ("Failed to get status of shared memory: %s",
+ strerror(errno));
+ shmbuf.shm_perm.uid = getuid();
+ shmbuf.shm_perm.gid = getgid();
+ if (shmctl (shmid, IPC_SET, &shmbuf) == -1)
+ error ("Failed to set status of shared memory: %s",
+ strerror(errno));
+
+ if ( (semid = semget (s->shmkey, 1, 0644 | IPC_CREAT)) >= 0 )
+ msg ("Created new sem at id %d", semid);
+ else
+ error ("Cannot create semaphore for service %s (key %d: %s)",
+ s->name, s->shmkey, strerror(errno));
+ } else {
+ /* Second time 'round. We're getting a handle on previously
+ * created shared memory and semaphores. */
+ if ( (shmid = shmget (s->shmkey, sizeof(Servicereport),
+ 0644) ) >= 0)
+ msg ("Got existing shm at id %d", shmid);
+ else
+ error ("Cannot get shared memory for service %s, "
+ "key %d: %s. "
+ "Maybe crossroads isn't running, or the configuration "
+ "has changed.",
+ s->name, s->shmkey, strerror(errno));
+ if ( (semid = semget (s->shmkey, 1, 0644)) >= 0 )
+ msg ("Got existing sem at id %d", semid);
+ else
+ error ("Cannot get semaphore for service %s (key %d: %s)",
+ s->name, s->shmkey, strerror(errno));
+ }
+
+ /* Attach to our memory segment. */
+ if ( ((servicereport = shmat (shmid, 0, 0))) == (void *) -1 )
+ error ("Cannot attach shared memory for service %s: %s",
+ s->name, strerror(errno));
+
+ /* Zero it out if this is the first usage. */
+ if (first)
+ memset (servicereport, 0, sizeof(Servicereport));
+}
diff --git a/src/backendavailable.c b/src/backendavailable.c
@@ -0,0 +1,28 @@
+#include "crossroads.h"
+
+int backend_available () {
+ int i;
+
+ /* Just see if all back ends are flagged down. In that case
+ * we return 0.
+ * We won't actually connect here with a back end, tcp_serve()
+ * will do that later on.
+ */
+
+ for (i = 0; i < activeservice->nbackend; i++) {
+ /* Backend i is available when:
+ * (a) The state indicates availability;
+ * (b) Either, there is no max to the active connections, or
+ * the # of connections is below the max.
+ */
+ if (servicereport->backendstate[i].avail == st_available &&
+ (activeservice->backend[i].maxconnections == 0 ||
+ activeservice->backend[i].maxconnections >
+ servicereport->backendstate[i].nclients))
+ msg ("Found (at least) one available back end");
+ return (1);
+ }
+
+ msg ("Found no available back end!");
+ return (0);
+}
diff --git a/src/backendconnect.c b/src/backendconnect.c
@@ -0,0 +1,85 @@
+#include "crossroads.h"
+
+static jmp_buf jmpbuf;
+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_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 (init_sockaddr (&servername,
+ activeservice->backend[current_backend].server,
+ activeservice->backend[current_backend].port)) {
+ /* The hostname is unusable.. */
+ if (is_waking)
+ msg ("Unknown host %s",
+ activeservice->backend[current_backend].server);
+ else {
+ warning ("Unknown host %s",
+ activeservice->backend[current_backend].server);
+ mark_activity (0, 0, st_unavailable);
+ }
+ return (-1);
+ }
+
+ signal (SIGALRM, alarmhandler);
+ timeout = 0;
+ alarm (CONNECT_TIMEOUT);
+ if (!setjmp (jmpbuf)) {
+ /* First time around, try connecting */
+ if (connect (backend_sock, (struct sockaddr *) &servername,
+ sizeof(servername)) < 0) {
+ /* This backend is unusable..
+ * Either connect() has failed (connection refused) or it got
+ * interrupted by the SIGALRM
+ */
+ 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",
+ 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));
+ mark_activity (0, 0, st_unavailable);
+ }
+ close (backend_sock);
+ backend_sock = -2;
+ }
+ alarm(0);
+ } else {
+ /* Got here because of longjmp from the signal handler,
+ * this MUST be a timeout
+ */
+ 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",
+ activeservice->backend[current_backend].server);
+ backend_sock = -3;
+ }
+
+ signal (SIGALRM, SIG_DFL);
+ return (backend_sock);
+}
diff --git a/src/choosebackend.c b/src/choosebackend.c
@@ -0,0 +1,184 @@
+#include "crossroads.h"
+
+void choose_backend () {
+ int backends[MAX_BACKEND], nbackends = 0, i;
+ unsigned long long values[MAX_BACKEND];
+ unsigned long long nbest;
+ unsigned nclients;
+ int target_set = 0;
+
+ /* Check that we're allowed to accept at all. */
+ if (activeservice->maxconnections &&
+ servicereport->nclients >= activeservice->maxconnections) {
+ warning ("Max clients %d exceeded",
+ activeservice->maxconnections);
+ current_backend = -1;
+ return;
+ }
+
+ /* Populate the array of selectable backends. */
+ for (i = 0; i < activeservice->nbackend; i++) {
+ /* Backend i is a candidate if:
+ * (a) it is available, AND
+ * (b) there's no max of connections, or the actual nr of connections
+ * is below max
+ */
+ if (servicereport->backendstate[i].avail == st_available &&
+ (activeservice->backend[i].maxconnections == 0 ||
+ activeservice->backend[i].maxconnections >
+ servicereport->backendstate[i].nclients)) {
+ msg ("Candidate back end: %d (max clients %d, active %d, "
+ "state %s)",
+ i, activeservice->backend[i].maxconnections,
+ servicereport->backendstate[i].nclients,
+ state_to_string(servicereport->backendstate[i].avail));
+ backends[nbackends++] = i;
+ }
+ }
+
+ /* No backends? Nogo. */
+ if (!nbackends) {
+ current_backend = -1;
+ warning ("No active backends to select!");
+ return;
+ }
+
+ /* Only one backend? Then it's always the first one,
+ * whatever you do.
+ */
+ if (nbackends == 1) {
+ current_backend = backends[0];
+ servicereport->last_backend = current_backend;
+ msg ("Only 1 backend to select (%d)", current_backend);
+ return;
+ }
+
+ /* More than 1 backend available.. make a wise choice. */
+ switch (activeservice->dispatchtype) {
+
+ case ds_roundrobin:
+ /* Find the currently used backend, go one forward. */
+ for (i = 0; i < nbackends; i++)
+ if (backends[i] == servicereport->last_backend) {
+ i++;
+ i %= nbackends;
+ current_backend = backends[i];
+ servicereport->last_backend = current_backend;
+ msg ("Chosen backend (roundrobin): %d", current_backend);
+ return;
+ }
+ /* None found.. try the first one (run to momma). */
+ current_backend = backends[0];
+ servicereport->last_backend = current_backend;
+ return;
+
+ case ds_random:
+ /* Choose a random one of the availables. */
+ current_backend = backends[rand() % nbackends];
+ servicereport->last_backend = current_backend;
+ msg ("Chosen backend (random): %d", current_backend);
+ return;
+
+ case ds_bysize:
+ /* Fill the values */
+ for (i = 0; i < nbackends; i++) {
+ /* When dispatch over #-connections is in place, then we
+ * compute using the averaged value. However if the averaged
+ * value is 0 (not yet computed), we take the true value. */
+ if (activeservice->dispatchover &&
+ servicereport->backendstate[backends[i]].avg_nbytes)
+ values[i] =
+ servicereport->backendstate[backends[i]].avg_nbytes *
+ servicereport->backendstate[backends[i]].avg_nbytes *
+ activeservice->backend[backends[i]].weight;
+ else
+ values[i] = servicereport->backendstate[backends[i]].nbytes *
+ activeservice->backend[backends[i]].weight;
+ msg ("By size weighing: backend %d has value %lu"
+ " (bytes=%lu, avgbytes=%lu, weight=%d)",
+ backends[i], values[i],
+ servicereport->backendstate[backends[i]].nbytes,
+ servicereport->backendstate[backends[i]].avg_nbytes,
+ activeservice->backend[backends[i]].weight);
+ }
+ /* Get the backend that has transported the least bytes */
+ for (i = 0; i < nbackends; i++)
+ if (!target_set++ || values[i] < nbest) {
+ 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 ("Chosen backend (by size): %d", current_backend);
+ return;
+
+ case ds_byduration:
+ /* Fill the values */
+ for (i = 0; i < nbackends; i++) {
+ /* When dispatch over #-connections is in place, then we
+ * compute using the averaged value. However if the averaged
+ * value is 0 (not yet computed), we take the true value. */
+ if (activeservice->dispatchover &&
+ servicereport->backendstate[backends[i]].avg_nsec)
+ values[i] =
+ servicereport->backendstate[backends[i]].avg_nsec *
+ 1000000 *
+ activeservice->backend[backends[i]].weight;
+ else
+ values[i] = servicereport->backendstate[backends[i]].nsec *
+ 1000000 *
+ activeservice->backend[backends[i]].weight;
+ msg ("By duration weighing: backend %d has value %lu"
+ " (sec=%g, avgsec=%g, weight=%d)",
+ backends[i], values[i],
+ servicereport->backendstate[backends[i]].nsec,
+ servicereport->backendstate[backends[i]].avg_nsec,
+ activeservice->backend[backends[i]].weight);
+ }
+ /* Get the backend that has transported the least connection secs */
+ for (i = 0; i < nbackends; i++)
+ if (!target_set++ || values[i] < nbest) {
+ 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 ("Chosen backend (by duration): %d", 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);
+ return;
+
+ case ds_byconnections:
+ /* Get the weighing values. */
+ for (i = 0; i < nbackends; i++) {
+ 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]);
+ }
+ /* Find the minimum in the values array. */
+ for (i = 0; i < nbackends; i++)
+ if (!target_set++ || values[i] < nclients) {
+ 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 ("Chosen backend (by connections): %d", current_backend);
+ return;
+
+ default:
+ /* Internal fry.. */
+ error ("Internal error: unhandled dispatch type %d",
+ activeservice->dispatchtype);
+ }
+}
diff --git a/src/configtest.c b/src/configtest.c
@@ -0,0 +1,6 @@
+#include "crossroads.h"
+
+void configtest (int ac, char **av) {
+ /* If we got this far, after reading the config: */
+ printf ("Configuration '%s' OK.\n", config_file);
+}
diff --git a/src/copysockets.c b/src/copysockets.c
@@ -0,0 +1,165 @@
+#include "crossroads.h"
+
+void copysockets (int clientsock, int serversock) {
+ 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. */
+ 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();
+
+ if (! nfd) {
+ /* This must be caused by a timeout in select(), otherwise
+ * the read() below would have returned 0. */
+ warning ("Timout in service %s (backend %s) after %d secs",
+ activeservice->name,
+ activeservice->backend[current_backend].name,
+ activeservice->connectiontimeout);
+ mark_activity (0, 0, st_available);
+ error ("Timout in service %s (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 ("Failed to wait for network input: %s",
+ 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();
+ 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);
+ 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);
+
+ /* In forked mode, we're done. In foreground mode, RTC. */
+ if (flag_foreground) {
+ msg ("Successful TCP stop, returning to caller");
+ return;
+ } else {
+ 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();
+ 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();
+ error ("Write error when copying to %s", dst_str);
+ }
+ // msg ("Wrote another %d bytes to %s in this connection",
+ // nwritten, dst_str);
+ totwritten += nwritten;
+ }
+ }
+}
diff --git a/src/crossroads.h b/src/crossroads.h
@@ -0,0 +1,266 @@
+/* Includes. */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#include <math.h>
+#include <pwd.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/file.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/times.h>
+#include <sys/types.h>
+
+/* flock() macros, incase they aren't present on your system. */
+#ifndef LOCK_SH
+#define LOCK_SH 1
+#endif
+#ifndef LOCK_EX
+#define LOCK_EX 2
+#endif
+#ifndef LOCK_NB
+#define LOCK_NB 4
+#endif
+#ifndef LOCK_UN
+#define LOCK_UN 8
+#endif
+
+/* inet_addr() macros, incase they aren't present on your system. */
+#ifndef INADDR_NONE
+#define INADDR_NONE ((unsigned long) -1)
+#endif
+
+/* Types. */
+typedef enum { /* Config parsing related */
+ cf_portspec,
+ cf_verbosityspec,
+ cf_backendspec,
+ cf_serverspec,
+ cf_dispatchspec,
+ cf_revivespec,
+ cf_shmkeyspec,
+ cf_weightspec,
+ cf_decayspec,
+ cf_onsuccspec,
+ cf_onfailspec,
+ cf_connectiontimeoutspec,
+ cf_maxconnectionsspec,
+ cf_typespec,
+ cf_dumpspec,
+ cf_thruspec,
+ cf_bindspec,
+ cf_stickycookiespec,
+ cf_insertcookiespec,
+} Conftype;
+
+typedef union { /* Integer of string value */
+ int ival; /* passed around by the parser */
+ char *sval;
+} IntOrString;
+
+typedef struct {
+ Conftype cf;
+ IntOrString v;
+} Confset;
+
+typedef struct {
+ int n;
+ Confset *set;
+} Confsets;
+
+typedef enum { /* Dispatching types */
+ ds_roundrobin,
+ ds_random,
+ ds_bysize,
+ ds_byduration,
+ ds_byorder,
+ ds_byconnections,
+} Dispatchtype;
+
+typedef enum { /* Service types */
+ type_any,
+ type_http,
+} Servicetype;
+
+typedef struct { /* Backend description */
+ char *name; /* .. back end identifier */
+ int port; /* .. TCP port */
+ char *server; /* .. server name */
+ int verbosity; /* .. debugging on/off */
+ int weight; /* .. weight in backend selection */
+ int decay; /* .. decay in % */
+ char *onsuccess; /* .. system() on success */
+ char *onfailure; /* .. or on failure */
+ char *dumpfile; /* .. traffic dump file */
+ char *thruputfile; /* .. traffic throughput file */
+ int maxconnections; /* .. max # of allowed connections */
+ char *stickycookie; /* .. cookie selector */
+ char *insertcookie; /* .. cookie ID to insert */
+} Backend;
+
+typedef struct { /* Service description */
+ char *name; /* .. service name */
+ char *bind; /* .. address to bind to */
+ unsigned port; /* .. listening port */
+ unsigned verbosity; /* .. message generation */
+ Dispatchtype dispatchtype; /* .. backend selection method */
+ unsigned dispatchover; /* .. selection over # connections */
+ unsigned rev_interval; /* .. dead backend revival interval */
+ unsigned backlog; /* .. # pending TCP connections */
+ unsigned shmkey; /* .. key for SysV IPC */
+ unsigned connectiontimeout; /* .. # secs for timeout handling */
+ unsigned maxconnections; /* .. max # of allowed connections */
+ Servicetype type; /* .. type of the service */
+ Backend *backend; /* .. the back ends */
+ int nbackend; /* .. size of backend array */
+} Service;
+
+typedef enum { /* Backend availability */
+ st_available, /* .. all OK */
+ st_unavailable, /* .. temporarily unavailable */
+ st_down, /* .. permanently unavailable */\
+ st_waking, /* .. scheduled for wake up */
+ st_intermediate, /* .. bogus, for mark_activity() */
+ st_unknown, /* .. no idea (terminator) */
+} Backendavail;
+
+typedef struct { /* Backend state */
+ Backendavail avail; /* .. availability */
+ unsigned long totuses; /* .. times hit */
+ unsigned long failures; /* .. times failed */
+ unsigned long long nbytes; /* .. transferred bytes */
+ unsigned long avg_nbytes; /* .. averaged over # connections */
+ double nsec; /* .. connection durations */
+ double avg_nsec; /* .. averaged over # connections */
+ unsigned nclients; /* .. active clients */
+} Backendstate;
+
+typedef struct { /* Service reporting (shmem) */
+ int pid; /* .. PID of service daemon */
+ int rev_pid; /* .. PID of revival handler */
+ int nclients; /* .. active clients */
+ int last_backend; /* .. last used back end */
+ Backendstate /* .. states of the back ends */
+ backendstate[MAX_BACKEND];
+} Servicereport;
+
+typedef enum { /* Stage of the program: */
+ stage_main, /* .. commandline */
+ stage_waiting, /* .. waiting for connections */
+ stage_serving, /* .. servicing a connection */
+ stage_retrying, /* .. waking up a backend */
+} Programstage; /* Update stagetostring.c when */
+ /* modifying! */
+/* Globals. */
+#ifndef EXTERN
+#define EXTERN extern
+#endif
+
+EXTERN Service *activeservice; /* target service of a daemon */
+EXTERN char *config_file; /* config to parse */
+EXTERN int current_backend; /* of a given service */
+EXTERN int daemonized; /* are we forked off yet */
+EXTERN int flag_foreground; /* flag: don't daemonize */
+EXTERN int flag_verbose; /* flag: verbosity */
+EXTERN int iflag_present; /* flag: -i present */
+EXTERN int interrupted; /* got a signal? */
+EXTERN char *laststring; /* semantic lexer value */
+EXTERN int nservice; /* # service descriptions */
+EXTERN int org_argc; /* original argc */
+EXTERN char **org_argv; /* and original argv */
+EXTERN Programstage program_stage; /* stage of the program */
+EXTERN int relevant_sigs[]; /* relevant signals */
+EXTERN int semid; /* semaphore ID */
+EXTERN Service *service; /* service descriptions */
+EXTERN Servicereport *servicereport; /* reporter in shared mem */
+EXTERN char *state_to_string_map[]; /* backend states as strings */
+EXTERN int uid; /* UID to assume */
+EXTERN FILE *yyin; /* config file handle */
+EXTERN int yylineno; /* input line number */
+EXTERN char *yyerrmsg; /* parsing error msg */
+EXTERN char *yytext; /* lexical buffer */
+
+/* Functions. */
+extern void alloc_reporter (Service *active, int first);
+extern int backend_available (void);
+extern int backend_connect (int is_waking);
+extern void choose_backend (void);
+extern void copysockets (int clientsock, int serversock);
+extern void configtest (int ac, char **av);
+extern void dealloc_reporter (Service *s);
+extern void error (char const *fmt, ...);
+extern int fork_tcp_servicer (int to_backend);
+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_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 int http_09_received (unsigned char const *buf);
+extern void http_read (int sock, int isclient, unsigned char **bufp,
+ int *buflen);
+extern void http_serve (int clientsock);
+extern int http_serversocket (unsigned char const *buf);
+extern void http_set_sticky_cookie (unsigned char **buf, int *buflen);
+extern int http_write (int sock, unsigned char const *buf, int buflen);
+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 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 void restart (int ac, char **av);
+extern void runservice (void);
+extern void sample_conf (int ac, char **av);
+extern void serve (int ac, char **av);
+extern void servesockets (int clientsock, int backendsock);
+extern void set_program_title (char const *fmt, ...);
+extern void show_services (int ac, char **av);
+extern void show_status (int ac, char **av);
+extern char *stage_to_string (Programstage stage);
+extern char *state_to_string (Backendavail avail);
+extern Backendavail string_to_state (char const *str);
+extern void stop_daemon (int ac, char **av);
+extern char *str_printf (char const *fmt, ...);
+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 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 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 *xstrdup (char const *what);
diff --git a/src/deallocreporter.c b/src/deallocreporter.c
@@ -0,0 +1,33 @@
+#include "crossroads.h"
+
+void dealloc_reporter (Service *s) {
+ int shmid;
+
+ msg ("Freeing reporting memory for service %s (key %d)",
+ s->name, s->shmkey);
+
+ /* Get the shared memory ID. If this fails and we're in stage 0,
+ * then ignore the error. The true handler will have already released it.
+ */
+ if ( (shmid = shmget (s->shmkey, sizeof(Servicereport),
+ 0644) ) >= 0)
+ msg ("Got existing shm at id %d", shmid);
+ else if (program_stage != stage_main)
+ error ("Cannot get shared memory for service %s (key %d: %s)",
+ s->name, s->shmkey, strerror(errno));
+ else
+ return;
+
+ /* Mark to be released. If this fails and if we're stage 0,
+ * then ignore.
+ */
+ if (shmctl (shmid, IPC_RMID, 0) < 0 && program_stage != stage_main)
+ error ("Cannot mark shared memory as destructable: %s "
+ "NOTE: Root will need to 'ipcrm -m %d' and 'ipcrm -s %d' "
+ "to clean up!",
+ strerror(errno), shmid, semid);
+ if (semctl (semid, 0, IPC_RMID) < 0 && program_stage != stage_main)
+ error ("Cannot remove semaphore: %s "
+ "NOTE: Root will need to 'ipcrm -s %d' to clean up!",
+ semid);
+}
diff --git a/src/error.c b/src/error.c
@@ -0,0 +1,13 @@
+#include "crossroads.h"
+
+void error (char const *fmt, ...) {
+ va_list args;
+
+ va_start (args, fmt);
+ if (!daemonized) {
+ vfprintf (stderr, fmt, args);
+ fprintf (stderr, "\n");
+ } else
+ writelog (LOG_ERR, fmt, args);
+ exit (1);
+}
diff --git a/src/forktcpservicer.c b/src/forktcpservicer.c
@@ -0,0 +1,38 @@
+#include "crossroads.h"
+
+int fork_tcp_servicer (int to_backend) {
+ int pid, i;
+
+ if (flag_foreground)
+ return (0);
+
+ if ( (pid = fork()) < 0 )
+ /* Fork failure */
+ error ("Failed to fork: %s", strerror(errno));
+ else if (pid) {
+ /* Parent branch */
+ msg ("Service communications will be handled by pid %d", pid);
+ return (pid);
+ } else {
+ /* Child branch: here we will serve the socket pair.
+ * We want to catch interrupts, but not die. */
+ for (i = 0; relevant_sigs[i]; i++)
+ signal (relevant_sigs[i], interrupt);
+
+ /* Forked for the second time... */
+ program_stage = stage_serving;
+
+ if (to_backend > 0)
+ set_program_title ("servicing %s to %s",
+ activeservice->name,
+ activeservice->backend
+ [current_backend].name);
+ else
+ set_program_title ("servicing %s",
+ activeservice->name);
+ return (0);
+ }
+
+ /* To satisfy the prototype... */
+ return (0);
+}
diff --git a/src/http09received.c b/src/http09received.c
@@ -0,0 +1,13 @@
+#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
@@ -0,0 +1,29 @@
+#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
@@ -0,0 +1,71 @@
+#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/httperror.c b/src/httperror.c
@@ -0,0 +1,16 @@
+#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";
+
+void http_error (int clientsock) {
+ char *buf = str_printf ("HTTP/1.x 502 Internal Server Error\r\n"
+ "Content-Length: %d\r\n"
+ "\r\n"
+ "%s",
+ strlen(str), str);
+ write (clientsock, buf, strlen(buf));
+ error ("No back end could be selected. Client received error page.");
+}
diff --git a/src/httpfindcookie.c b/src/httpfindcookie.c
@@ -0,0 +1,25 @@
+#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/httpheadersdone.c b/src/httpheadersdone.c
@@ -0,0 +1,24 @@
+#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/httpheaderval.c b/src/httpheaderval.c
@@ -0,0 +1,38 @@
+#include "crossroads.h"
+
+char *http_headerval (unsigned char const *buf, char const *label) {
+ char const
+ *cp = (char const *) buf,
+ *end;
+ char *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);
+ }
+ if (! (cp = strchr (cp, '\n')) )
+ return (0);
+ cp++;
+ if (*cp == '\r')
+ cp++;
+ if (*cp == '\n')
+ return (0);
+ }
+
+ return (0);
+}
+
+
diff --git a/src/httpread.c b/src/httpread.c
@@ -0,0 +1,109 @@
+#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 */
+ gettimeofday (&start_tv, 0);
+ lock_reporter();
+ servicereport->nclients++;
+ 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);
+ 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--;
+ 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--;
+ servicereport->backendstate[current_backend].nclients--;
+ unlock_reporter();
+ mark_activity (0, 0, st_available);
+ 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);
+ 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--;
+ servicereport->backendstate[current_backend].nclients--;
+ unlock_reporter();
+ mark_activity (0, 0, st_available);
+ msg ("HTTP read: complete message (%d bytes) '%s'",
+ *buflen, *bufp);
+ return;
+ }
+ }
+}
diff --git a/src/httpserve.c b/src/httpserve.c
@@ -0,0 +1,46 @@
+#include "crossroads.h"
+
+void http_serve (int clientsock) {
+ unsigned char *firstbuf, *secondbuf;
+ int serversock, buflen;
+
+ /* 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);
+
+ /* Determine the back end and send the initial client request. */
+ if ( (serversock = http_serversocket (firstbuf)) < 1 )
+ http_error (clientsock);
+ 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);
+
+ /* If we have a sticky cookie to insert:
+ * Get the server response, stick in the cookie, send it to the
+ * client. */
+ 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");
+ free (secondbuf);
+ }
+
+ /* Now we have a client and server socket, possibly with a
+ * session cookie in the chat. Piggyback to & fro. */
+ copysockets (clientsock, serversock);
+
+ /* Normal shutdown: TCP traffic done. */
+ msg ("Normal HTTP stop.");
+ close (serversock);
+ close (clientsock);
+ if (! flag_foreground)
+ exit (0);
+}
diff --git a/src/httpserversocket.c b/src/httpserversocket.c
@@ -0,0 +1,50 @@
+#include "crossroads.h"
+
+int http_serversocket (unsigned char const *buf) {
+ msg ("Searching for back end for HTTP request.");
+ int i, sock;
+
+ /* Try to find a sticky cookie in the request. */
+ for (i = 0; i < activeservice->nbackend; i++) {
+ if (servicereport->backendstate[i].avail != st_available)
+ continue;
+ if (activeservice->backend[i].maxconnections > 0 &&
+ 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'",
+ i,
+ activeservice->backend[i].stickycookie);
+
+ /* Got the target back end. Try to connect to it.
+ * 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 )
+ return (sock);
+ }
+ }
+
+ /* We got here because this is the first time 'round or because
+ * a previously established back end went dead in the middle of a
+ * session. So: Loop 'till we find a back end, or until we fail. */
+ while (1) {
+ /* No back end? Nogo. */
+ if (! backend_available()) {
+ msg ("Out of back ends while scanning for HTTP back end");
+ return (-1);
+ }
+
+ /* Try one. */
+ choose_backend();
+ if (current_backend < 0) {
+ warning ("No back end for request.");
+ return (-2);
+ }
+
+ msg ("Trying back end %d for new HTTP connection", current_backend);
+ if ( (sock = backend_connect (0)) >= 0 )
+ return (sock);
+ }
+}
diff --git a/src/httpsetstickycookie.c b/src/httpsetstickycookie.c
@@ -0,0 +1,51 @@
+#include "crossroads.h"
+
+void http_set_sticky_cookie (unsigned char **bufp, int *buflen) {
+ char
+ *cookieval = activeservice->backend[current_backend].insertcookie,
+ *instruction;
+ unsigned char const *buf, *cp;
+ unsigned char *newbuf;
+ int crseen = 0, atend = 0;
+
+ cp = buf = *bufp;
+ msg ("Pasting cookie '%s' in '%s'", cookieval, buf);
+ while (1) {
+ if (*cp == '\n')
+ cp++;
+ if (! (cp = (unsigned char const *)
+ strchr ( (char const *) cp, '\n')) ) {
+ msg ("Failed to find end of headers while inserting cookie");
+ return;
+ }
+ cp++;
+
+ if (*cp == '\r') {
+ crseen++;
+ if (*(cp + 1) == '\n')
+ atend++;
+ } else if (*cp == '\n')
+ atend++;
+
+ if (atend) {
+ instruction = str_printf ("Set-Cookie: %s%s",
+ cookieval,
+ crseen ? "\r\n" : "\n");
+
+ newbuf = xmalloc (*buflen + strlen(instruction) + 1);
+
+ memcpy (newbuf, buf, cp - buf);
+ memcpy (newbuf + (cp - buf), instruction, strlen(instruction));
+ memcpy (newbuf + (cp - buf) + strlen(instruction),
+ cp, *buflen - (cp - buf));
+
+ msg ("Modified buffer: '%s'", newbuf);
+
+ free (*bufp);
+ *bufp = newbuf;
+ *buflen += strlen(instruction);
+
+ return;
+ }
+ }
+}
diff --git a/src/httpwrite.c b/src/httpwrite.c
@@ -0,0 +1,13 @@
+#include "crossroads.h"
+
+int http_write (int sock, unsigned char const *buf, int buflen) {
+ int nwritten, totwritten = 0;
+
+ while (totwritten < buflen) {
+ nwritten = write (sock, buf + totwritten, buflen - totwritten);
+ if (nwritten < buflen - totwritten)
+ return (1);
+ totwritten += nwritten;
+ }
+ return (0);
+}
diff --git a/src/initsockaddr.c b/src/initsockaddr.c
@@ -0,0 +1,13 @@
+#include "crossroads.h"
+
+int init_sockaddr (struct sockaddr_in *name,
+ const char *hostname, int port) {
+ struct hostent *hostinfo;
+
+ name->sin_family = AF_INET;
+ name->sin_port = htons (port);
+ if (! (hostinfo = gethostbyname (hostname)) )
+ return (1);
+ name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
+ return (0);
+}
diff --git a/src/interrupt.c b/src/interrupt.c
@@ -0,0 +1,29 @@
+#include "crossroads.h"
+
+/* Signal handler */
+void interrupt (int sig) {
+ interrupted++;
+
+ switch (program_stage) {
+ case stage_waiting:
+ warning ("LISTENER: "
+ "Caught signal %d, freeing reporter and stopping",
+ sig);
+ dealloc_reporter (activeservice);
+ exit (1);
+ case stage_serving:
+ warning ("CONNECTION SERVICER: "
+ "Caught signal %d, will stop after servicing connection",
+ sig);
+ break;
+ case stage_retrying:
+ warning ("WAKEUP HANDER: "
+ " Caught signal %d, will stop waking up backend and exit",
+ sig);
+ exit (1);
+ default:
+ msg ("Caught signal %d in program stage %s", sig,
+ stage_to_string (program_stage));
+ exit (1);
+ }
+}
diff --git a/src/ishexdigit.c b/src/ishexdigit.c
@@ -0,0 +1,10 @@
+#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
@@ -0,0 +1,267 @@
+%{
+#include "crossroads.h"
+#include "parser.h"
+
+/* Required by flex */
+static int yywrap () {
+ return (1);
+}
+
+/* Lexer debugging related */
+// #define LEXER_DEBUG
+#ifdef LEXER_DEBUG
+ static void lmsg (char const *x) {
+ printf ("L: %s\n", x);
+ }
+ static void llmsg (char const *x, char const *y) {
+ printf ("L: %s %s\n", x, y);
+ }
+#else
+# define lmsg(x)
+# define llmsg(x,y)
+#endif
+%}
+
+%x stringstate commentstate
+
+%%
+
+#.*\n {
+ lmsg ("comment");
+ yylineno++;
+ }
+\/\/.*\n {
+ lmsg ("comment");
+ yylineno++;
+ }
+service {
+ lmsg ("service");
+ return (SERVICE);
+ }
+bindto {
+ lmsg ("bindto");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (BINDTO);
+ }
+port {
+ lmsg ("port");
+ return (PORT);
+ }
+over {
+ lmsg ("over");
+ return (OVER);
+ }
+shmkey {
+ lmsg ("shmkey");
+ return (SHMKEY);
+ }
+backend {
+ lmsg ("backend");
+ return (BACKEND);
+ }
+backlog {
+ lmsg ("backlog");
+ return (BACKLOG);
+ }
+(verbosity)|(verbose) {
+ lmsg ("verbosity");
+ return (VERBOSITY);
+ }
+connectiontimeout {
+ lmsg ("connectiontimeout");
+ return (CONNECTIONTIMEOUT);
+ }
+maxconnections {
+ lmsg ("maxconnections");
+ return (MAXCONNECTIONS);
+ }
+weight {
+ lmsg ("weight");
+ return (WEIGHT);
+ }
+decay {
+ lmsg ("decay");
+ return (DECAY);
+ }
+server {
+ lmsg ("server");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (SERVER);
+ }
+dispatchmode {
+ lmsg ("dispatchmode");
+ return (DISPATCHMODE);
+ }
+roundrobin {
+ lmsg ("roundrobin");
+ return (ROUNDROBIN);
+ }
+random {
+ lmsg ("random");
+ return (RANDOM);
+ }
+byduration {
+ lmsg ("byduration");
+ return (BYDURATION);
+ }
+bysize {
+ lmsg ("bysize");
+ return (BYSIZE);
+ }
+byorder {
+ lmsg ("byorder");
+ return (BYORDER);
+ }
+byconnections {
+ lmsg ("byconnections");
+ return (BYCONNECTIONS);
+ }
+revivinginterval {
+ lmsg ("revivinginterval");
+ return (REVIVINGINTERVAL);
+ }
+type {
+ lmsg ("type");
+ return (TYPE);
+ }
+any {
+ lmsg ("any");
+ return (ANY);
+ }
+stickyhttp {
+ lmsg ("sticky http");
+ return (STICKYHTTP);
+ }
+throughputlog {
+ lmsg ("throughputlog");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (THROUGHPUTLOG);
+ }
+trafficlog {
+ lmsg ("trafficlog");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (TRAFFICLOG);
+ }
+dumptraffic {
+ lmsg ("dumptraffic");
+ warning ("The 'dumptraffic' statement "
+ "is obsolete.\n"
+ "You should change to "
+ "'trafficlog'.\n");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (TRAFFICLOG);
+ }
+onsuccess {
+ lmsg ("onsuccess");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (ONSUCCESS);
+ }
+onfailure {
+ lmsg ("onfailure");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (ONFAILURE);
+ }
+stickycookie {
+ lmsg ("stickycookie");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (STICKYCOOKIE);
+ }
+insertcookie {
+ lmsg ("insertcookie");
+ BEGIN (stringstate);
+ free (laststring);
+ laststring = 0;
+ return (INSERTCOOKIE);
+ }
+on|true|yes {
+ lmsg ("on");
+ return (ON);
+ }
+off|false|no {
+ lmsg ("off");
+ return (OFF);
+ }
+[a-zA-Z_][a-zA-Z0-9_]* {
+ llmsg ("identifier", yytext);
+ return (IDENTIFIER);
+ }
+[0-9]+ {
+ llmsg ("number", yytext);
+ return (NUMBER);
+ }
+[ \t]+ {
+ lmsg ("space(s)");
+ }
+\n {
+ lmsg ("newline");
+ yylineno++;
+ }
+. {
+ llmsg ("lone char", yytext);
+ return (*yytext);
+ }
+\/\* {
+ lmsg ("C-comment starts");
+ BEGIN(commentstate);
+ }
+<commentstate>\*\/ {
+ lmsg ("C-comment ends");
+ BEGIN(0);
+ }
+<commentstate>\n {
+ yylineno++;
+ }
+<commentstate>. ;
+
+<stringstate>\"[^\"]*\" {
+ llmsg ("string part", yytext);
+ laststring = xstrcat (laststring,
+ yytext + 1);
+ laststring[strlen(laststring) - 1] = 0;
+ }
+<stringstate>\'[^\']*\' {
+ llmsg ("string part", yytext);
+ laststring = xstrcat (laststring,
+ yytext + 1);
+ laststring[strlen(laststring) - 1] = 0;
+ }
+<stringstate>; {
+ BEGIN (0);
+ unput (';');
+ llmsg ("string", laststring);
+ return (STRING);
+ }
+<stringstate>" " {
+ if (laststring) {
+ laststring = xstrcat (laststring,
+ yytext);
+ llmsg ("string part", yytext);
+ }
+ }
+<stringstate>. {
+ llmsg ("string part", yytext);
+ laststring = xstrcat (laststring,
+ yytext);
+ }
+<stringstate>\n {
+ lmsg ("string part: ERROR - newline");
+ error ("Parse eror at line %d: "
+ "unterminated string, ';' "
+ "expected\n", yylineno + 1);
+ }
diff --git a/src/lockreporter.c b/src/lockreporter.c
@@ -0,0 +1,24 @@
+#include "crossroads.h"
+
+void lock_reporter() {
+ struct sembuf buf[] = {
+ {
+ 0, /* semaphore number */
+ 0, /* wait for zero */
+ 0, /* no special flags */
+ },
+ {
+ 0, /* semaphore number */
+ 1, /* add 1 to the value */
+ 0 /* no special flags */
+ }
+ };
+
+ if (flag_foreground)
+ return;
+
+ /* msg ("Locking reporter memory"); */
+ if (semop (semid, buf, 2) < 0)
+ error ("Failed to lock reporter memory (stage %s): %s",
+ stage_to_string (program_stage), strerror(errno));
+}
diff --git a/src/main.c b/src/main.c
@@ -0,0 +1,109 @@
+
+#define EXTERN
+#include "crossroads.h"
+
+/* Explicit initializations. */
+
+char *state_to_string_map [] = { /* backend states as strings */
+ "available",
+ "UNAVAILABLE",
+ "DOWN",
+ "waking",
+ "intermediate update",
+ "unknown",
+ 0,
+};
+
+int relevant_sigs[] = { /* signals relevant to this app */
+ SIGHUP, /* either caught or ignored, */
+ SIGINT, /* depending on the stage */
+ SIGPIPE,
+ SIGTERM,
+ 0,
+};
+
+typedef struct {
+ char *arg; /* cmdline argument */
+ int narg; /* required remaining args */
+ int needconf; /* configuration file required? */
+ void (*handler)(int ac, char **av); /* action handler */
+} Handler;
+
+int main (int argc, char **argv) {
+ int opt, i;
+ static Handler handler[] = {
+ // Argument Nr.args Needconf Handler function
+ { "sampleconf", 0, 0, sample_conf },
+ { "services", 0, 1, show_services },
+ { "status", 0, 1, show_status },
+ { "stop", 0, 1, stop_daemon },
+ { "start", 0, 1, serve },
+ { "restart", 0, 1, restart },
+ { "tell", 3, 1, tell_service },
+ { "configtest", 0, 1, configtest },
+ };
+ struct passwd *passwd;
+
+ /* Remember original ac/av/ep */
+ org_argc = argc;
+ org_argv = argv;
+
+ /* Parse options. */
+ config_file = DEFAULT_CONF;
+ while ( (opt = getopt (argc, argv, "?c:fhvi:u:V")) > 0 )
+ switch (opt) {
+ case 'c':
+ config_file = optarg;
+ break;
+ case 'f':
+ flag_foreground++;
+ break;
+ case 'v':
+ flag_verbose++;
+ break;
+ case 'i':
+ iflag_present++;
+ break;
+ case 'u':
+ if (! (passwd = getpwnam (optarg)) )
+ error ("Cannot fetch information for user '%s': %s",
+ optarg, strerror(errno));
+ uid = passwd->pw_uid;
+ break;
+ case 'V':
+ puts (VER);
+ exit (0);
+ case '?':
+ case 'h':
+ default:
+ usage ();
+ }
+
+ /* We need at last one argument: the action */
+ if (optind >= argc)
+ usage();
+
+ /* Act on the argument. */
+ for (i = 0; i < sizeof(handler) / sizeof(Handler); i++) {
+ /* Match the handler action string with argv, and match the
+ * required remaining arguments. If they match..
+ * scan the configuration (if needed) and GO!
+ */
+ if (!strcmp (argv[optind], handler[i].arg) &&
+ handler[i].narg == argc - optind - 1) {
+ if (handler[i].needconf) {
+ msg ("Parsing configuration %s", config_file);
+ if (! (yyin = fopen (config_file, "r")) )
+ error ("Cannot read configuration %s: %s",
+ config_file, strerror(errno));
+ yyparse();
+ fclose (yyin);
+ }
+ handler[i].handler (argc - optind, argv + optind + 1);
+ return (0);
+ }
+ }
+
+ usage();
+ return (1);
+}
diff --git a/src/makesocket.c b/src/makesocket.c
@@ -0,0 +1,44 @@
+
+#include "crossroads.h"
+
+int make_socket (int port, char const *ipaddr) {
+ int sock, val = 1;
+ struct sockaddr_in name;
+
+ /* Create the socket. */
+ if ( (sock = socket (PF_INET, SOCK_STREAM, 0)) < 0) {
+ warning ("Cannot create network socket: %s", strerror(errno));
+ return (-1);
+ }
+ if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val))) {
+ warning ("Cannot set socket options: %s", strerror(errno));
+ close (sock);
+ return (-2);
+ }
+
+ /* Give the socket a name. */
+ name.sin_family = AF_INET;
+ name.sin_port = htons (port);
+
+ /* Propagate 'bindto' if given. */
+ if (strcasecmp (ipaddr, "any")) {
+ msg ("Creating socket for IP address '%s'", ipaddr);
+ if ( (name.sin_addr.s_addr = inet_addr (ipaddr)) == INADDR_NONE ) {
+ close (sock);
+ error ("Cannot convert address '%s' to network bytes",
+ ipaddr);
+ }
+ } else {
+ msg ("Creating socket for all interfaces");
+ name.sin_addr.s_addr = htonl (INADDR_ANY);
+ }
+
+ /* Finallly, bind the socket. */
+ if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) {
+ warning ("Cannot bind socket to port %d: %s", port, strerror(errno));
+ close (sock);
+ return (-3);
+ }
+
+ return (sock);
+}
diff --git a/src/markactivity.c b/src/markactivity.c
@@ -0,0 +1,99 @@
+#include "crossroads.h"
+
+void mark_activity (unsigned long long nbytes, double nsec,
+ Backendavail newstate) {
+ unsigned long long llval;
+ double dlval;
+ unsigned multiplier;
+ int i;
+
+ /* If we had a signal somewhere, don't update the values. */
+ if (interrupted)
+ return;
+
+ /* Update values. */
+ lock_reporter();
+
+ /* Handle the decays of all other back ends */
+ for (i = 0; i < activeservice->nbackend; i++) {
+ if (i != current_backend &&
+ activeservice->backend[i].decay) {
+ if (activeservice->dispatchover) {
+ servicereport->backendstate[i].avg_nbytes *=
+ (100 - activeservice->backend[i].decay);
+ servicereport->backendstate[i].avg_nbytes /= 100;
+ servicereport->backendstate[i].avg_nsec *=
+ (100 - activeservice->backend[i].decay);
+ servicereport->backendstate[i].avg_nsec /= 100;
+ } else {
+ servicereport->backendstate[i].nbytes *=
+ (100 - activeservice->backend[i].decay);
+ servicereport->backendstate[i].nbytes /= 100;
+ servicereport->backendstate[i].nsec *=
+ (100 - activeservice->backend[i].decay);
+ servicereport->backendstate[i].nsec /= 100;
+ }
+ }
+ }
+
+ /* Update average seconds and bytes. */
+ if (activeservice->dispatchover) {
+ multiplier = activeservice->dispatchover - 1;
+ if (servicereport->backendstate[current_backend].totuses < multiplier)
+ multiplier = servicereport->backendstate[current_backend].totuses;
+
+ dlval = servicereport->backendstate[current_backend].avg_nsec;
+ dlval *= multiplier;
+ dlval += nsec;
+ dlval /= (multiplier + 1);
+ servicereport->backendstate[current_backend].avg_nsec = dlval;
+
+ llval = servicereport->backendstate[current_backend].avg_nbytes;
+ llval *= multiplier;
+ llval += nbytes;
+ llval /= (multiplier + 1);
+ servicereport->backendstate[current_backend].avg_nbytes = llval;
+ }
+
+ /* Update total secs / bytes. */
+ servicereport->backendstate[current_backend].nsec += nsec;
+ servicereport->backendstate[current_backend].nbytes += nbytes;
+ if (newstate == st_unavailable)
+ servicereport->backendstate[current_backend].failures++;
+
+ /* Set the state, unless it's already marked for wakeup or brought down.
+ * Do this only for 'final' states, not for intermediate. */
+ if (newstate != st_intermediate) {
+ if (servicereport->backendstate[current_backend].avail != st_waking &&
+ servicereport->backendstate[current_backend].avail != st_down)
+ servicereport->backendstate[current_backend].avail = newstate;
+ servicereport->backendstate[current_backend].totuses++;
+ }
+
+ /* Don't update servicereport beyond this point! */
+ unlock_reporter();
+
+ /* Give some feedback (though not during intermediate updates) */
+ if (newstate != st_intermediate)
+ msg ("Updated stats for service %s / backend %d (%s): "
+ "hits=%lu, "
+ "fails=%lu, secs=%g, avgsecs=%g, "
+ "bytes=%llu, avgbytes=%lu, state=%s",
+ activeservice->name,
+ current_backend, activeservice->backend[current_backend].name,
+ servicereport->backendstate[current_backend].totuses,
+ servicereport->backendstate[current_backend].failures,
+ servicereport->backendstate[current_backend].nsec,
+ servicereport->backendstate[current_backend].avg_nsec,
+ servicereport->backendstate[current_backend].nbytes,
+ servicereport->backendstate[current_backend].avg_nbytes,
+ state_to_string
+ (servicereport->backendstate[current_backend].avail));
+
+ /* At the end of a request, check the onsuccess/onfailure handlers.
+ * We only do this for the 'final' states. */
+ if (newstate == st_available)
+ sysrun (activeservice->backend[current_backend].onsuccess);
+ else if (newstate == st_unavailable)
+ sysrun (activeservice->backend[current_backend].onfailure);
+}
diff --git a/src/msg.c b/src/msg.c
@@ -0,0 +1,16 @@
+#include "crossroads.h"
+
+void msg (char const *fmt, ...) {
+ va_list args;
+
+ if (!flag_verbose)
+ return;
+ va_start (args, fmt);
+ if (!daemonized) {
+ vfprintf (stderr, fmt, args);
+ fprintf (stderr, "\n");
+ } else
+ writelog (LOG_NOTICE, fmt, args);
+
+ va_end (args);
+}
diff --git a/src/msgdumpbuf.c b/src/msgdumpbuf.c
@@ -0,0 +1,43 @@
+#include "crossroads.h"
+
+void msgdumpbuf (unsigned char const *buf, int buflen) {
+ char
+ hexbuf[80] = { 0 },
+ disp[17] = { 0 },
+ tmp[5];
+ int i, j, n = 0;
+
+ if (! flag_verbose)
+ return;
+
+ for (i = 0; i < buflen; i++) {
+ if (! n)
+ sprintf (hexbuf, "%8.8x ", i);
+
+ sprintf (tmp, " %2.2x", buf[i]);
+ strcat (hexbuf, tmp);
+
+ disp[n] = isprint (buf[i]) ? buf[i] : '.';
+ disp[n + 1] = 0;
+
+ if (++n == 16) {
+ strcat (hexbuf, " ");
+ strcat (hexbuf, disp);
+ msg ("Message hexdump: %s", hexbuf);
+ n = 0;
+ disp[0] = 0;
+ hexbuf[0] = 0;
+ }
+ }
+
+ if (n < 16 - 1) {
+ for (j = n; j < 16; j++)
+ strcat (hexbuf, " ");
+ strcat (hexbuf, " ");
+ strcat (hexbuf, disp);
+ msg ("Message hex end: %s", hexbuf);
+ }
+ else
+ msg ("Message hex ends");
+}
+
diff --git a/src/parser.y b/src/parser.y
@@ -0,0 +1,834 @@
+/* Parser of crossroads configuration files */
+
+%{
+/* Prologue */
+#include "crossroads.h"
+
+#define YYSTYPE Confsets
+
+/* Parser debugging related */
+// #define PARSER_DEBUG
+#ifdef PARSER_DEBUG
+ static void pmsg (char const *x) {
+ printf ("P: %s\n", x);
+ }
+ static void psmsg (char const *x, char const *y) {
+ printf ("P: %s %s\n", x, y);
+ }
+ static void pimsg(char const *x, int y) {
+ printf ("P: %s %d\n", x, y);
+ }
+#else
+# define pmsg(x)
+# define psmsg(x,y)
+# define pimsg(x,y)
+#endif
+
+/* Error handler for yyparse() */
+static int yyerror (char *msg) {
+ error ("Parse error at line %d, '%s': %s",
+ yylineno + 1, yytext, yyerrmsg);
+}
+
+/* Store an encountered string */
+static char *laststr;
+static void setlaststr (char const *what) {
+ free (laststr);
+ laststr = xstrdup (what);
+}
+
+/* Store an encountered number */
+static int lastnr;
+static void setlastnr (char const *what) {
+ lastnr = atoi (what);
+}
+
+/* Store an encountered 'over' number */
+static int lastovernr;
+static void setlastovernr (char const *what) {
+ lastovernr = atoi(what);
+}
+
+/* Get the server part from HOSTNAME:PORT in allocated memory */
+static char *serverpart (char const *what) {
+ char *ret = xstrdup (what);
+ char *cp;
+
+ if ( (cp = strchr (ret, ':')) )
+ *cp = 0;
+ return (ret);
+}
+
+/* Get the port part from HOSTNAME:PORT */
+static int portpart (char const *what) {
+ char *cp;
+
+ if ( (cp = strchr (what, ':')) )
+ return (atoi (cp + 1));
+ return (0);
+}
+
+/* Temp vars */
+static int i; /* Loop counter */
+static Backend cur_backend; /* Storage for a handled backend */
+static Service cur_service; /* Storage for a handled service */
+%}
+
+/* Declarations */
+%token SERVICE IDENTIFIER PORT NUMBER BACKEND VERBOSITY SERVER
+ ON OFF DISPATCHMODE ROUNDROBIN REVIVINGINTERVAL SHMKEY WEIGHT
+ ONSUCCESS ONFAILURE STRING BACKLOG RANDOM BYDURATION BYSIZE
+ BYCONNECTIONS CONNECTIONTIMEOUT MAXCONNECTIONS BYORDER TRAFFICLOG
+ OVER DECAY BINDTO THROUGHPUTLOG TYPE ANY STICKYHTTP
+ STICKYCOOKIE INSERTCOOKIE
+
+%%
+/* Config file grammar rules */
+
+input:
+ element
+ input
+|
+ element
+;
+
+element:
+ service
+ servicename
+ '{'
+ servicestatements
+ '}' {
+ /* Verify the service description, supply defaults
+ * and so on.
+ */
+ if (!cur_service.port)
+ error ("Service %s lacks a port",
+ cur_service.name);
+ if (!cur_service.nbackend)
+ error ("Service %s lacks back ends",
+ cur_service.name);
+
+ if (!cur_service.shmkey)
+ cur_service.shmkey = cur_service.port | SHM_MASK;
+
+ if (!cur_service.bind)
+ cur_service.bind = "any";
+
+ /* Add to the list. */
+ service = xrealloc (service, ++nservice * sizeof(Service));
+ service[nservice - 1] = cur_service;
+ memset (&cur_service, 0, sizeof(Service));
+ }
+;
+
+service:
+ service_expected
+ SERVICE
+;
+
+servicename:
+ servicename_expected
+ IDENTIFIER {
+ psmsg ("service:", yytext);
+ cur_service.name = xstrdup(yytext);
+ }
+;
+
+servicestatements:
+ servicestatements
+ servicestatement
+|
+ servicestatement
+;
+
+servicestatement:
+ servicebody_expected
+ servicebody
+;
+
+servicebody:
+ portstatement {
+ pimsg ("sevice port:", $1.set[0].v.ival);
+ cur_service.port = $1.set[0].v.ival;
+ free ($1.set);
+ }
+|
+ bindstatement {
+ psmsg ("service binding:", $1.set[0].v.sval);
+ cur_service.bind = $1.set[0].v.sval;
+ free ($1.set);
+ }
+|
+ verbositystatement {
+ pimsg ("service verbosity:", $1.set[0].v.ival);
+ cur_service.verbosity = $1.set[0].v.ival;
+ free ($1.set);
+ }
+|
+ dispatchmodestatement {
+ pimsg ("service dispatch mode:", $1.set[0].v.ival);
+ pimsg ("service dispatch over:", lastovernr);
+ cur_service.dispatchtype = $1.set[0].v.ival;
+ cur_service.dispatchover = lastovernr;
+ free ($1.set);
+ }
+|
+ revivingintervalstatement {
+ pimsg ("service revival interval:", $1.set[0].v.ival);
+ cur_service.rev_interval = $1.set[0].v.ival;
+ free ($1.set);
+ }
+|
+ backlogstatement {
+ pimsg ("service backlog:", $1.set[0].v.ival);
+ cur_service.backlog = $1.set[0].v.ival;
+ free ($1.set);
+ }
+|
+ shmkeystatement {
+ pimsg ("service shmkey:", $1.set[0].v.ival);
+ cur_service.shmkey = $1.set[0].v.ival;
+ free ($1.set);
+ }
+|
+ connectiontimeoutstatement {
+ pimsg ("connection timout:", $1.set[0].v.ival);
+ cur_service.connectiontimeout = $1.set[0].v.ival;
+ free ($1.set);
+ }
+|
+ maxconnectionsstatement {
+ pimsg ("max clients in service:", $1.set[0].v.ival);
+ cur_service.maxconnections = $1.set[0].v.ival;
+ free ($1.set);
+ }
+|
+ typestatement {
+ pimsg ("service type: ", $1.set[0].v.ival);
+ cur_service.type = $1.set[0].v.ival;
+ free ($1.set);
+ }
+|
+ backendblock {
+ pimsg ("converting backend statements, count is", $1.n);
+ for (i = 0; i < $1.n; i++)
+ switch ($1.set[i].cf) {
+ case cf_portspec:
+ pimsg ("backend block port:", $1.set[i].v.ival);
+ cur_backend.port = $1.set[i].v.ival;
+ break;
+ case cf_serverspec:
+ psmsg ("backend block server:", $1.set[i].v.sval);
+ cur_backend.server = serverpart ($1.set[i].v.sval);
+ cur_backend.port = portpart ($1.set[i].v.sval);
+ free ($1.set[i].v.sval);
+ break;
+ case cf_verbosityspec:
+ pimsg ("backend block verbosity:", $1.set[i].v.ival);
+ cur_backend.verbosity = $1.set[i].v.ival;
+ break;
+ case cf_onsuccspec:
+ psmsg ("backend block onsuccess:", $1.set[i].v.sval);
+ cur_backend.onsuccess = $1.set[i].v.sval;
+ break;
+ case cf_onfailspec:
+ psmsg ("backend block onfailure:", $1.set[i].v.sval);
+ cur_backend.onfailure = $1.set[i].v.sval;
+ break;
+ case cf_dumpspec:
+ psmsg ("backend trafficlog:", $1.set[i].v.sval);
+ cur_backend.dumpfile = $1.set[i].v.sval;
+ break;
+ case cf_thruspec:
+ psmsg ("backend throughputlog:", $1.set[i].v.sval);
+ cur_backend.thruputfile = $1.set[i].v.sval;
+ break;
+ case cf_weightspec:
+ pimsg ("backend weight:", $1.set[i].v.ival);
+ cur_backend.weight = $1.set[i].v.ival;
+ break;
+ case cf_decayspec:
+ pimsg ("backend decay:", $1.set[i].v.ival);
+ if ($1.set[i].v.ival >= 100)
+ error ("Decay specifier %d must be a percentage, "
+ "never more than 99",
+ $1.set[i].v.ival);
+ cur_backend.decay = $1.set[i].v.ival;
+ break;
+ case cf_maxconnectionsspec:
+ pimsg ("backend max clients: ", $1.set[i].v.ival);
+ cur_backend.maxconnections = $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;
+ 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.
+ */
+ if (!cur_service.port)
+ error ("Back end %s lacks port",
+ cur_service.port);
+ if (!cur_backend.weight)
+ cur_backend.weight = 1;
+
+ /* Add to the list. */
+ cur_service.backend = xrealloc (cur_service.backend,
+ ++cur_service.nbackend *
+ sizeof(Service));
+ cur_service.backend[cur_service.nbackend - 1] =
+ cur_backend;
+ pimsg ("this was backend defintion", cur_service.nbackend);
+ memset (&cur_backend, 0, sizeof(cur_backend));
+ }
+;
+
+portstatement:
+ PORT
+ number
+ semicol {
+ pimsg ("port statement:", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_portspec;
+ $$.set[0].v.ival = lastnr;
+ }
+;
+
+bindstatement:
+ BINDTO
+ ipaddress
+ semicol {
+ psmsg ("bindto statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_bindspec;
+ $$.set[0].v.sval = xstrdup(laststr);
+ }
+;
+
+ipaddress:
+ ipaddress_expected
+ STRING {
+ setlaststr (laststring);
+ free (laststring);
+ laststring = 0;
+ }
+;
+
+number:
+ number_expected
+ NUMBER {
+ setlastnr (yytext);
+ }
+;
+
+semicol:
+ semicol_expected
+ ';'
+;
+
+verbositystatement:
+ VERBOSITY
+ onoff_expected
+ onoff
+ semicol {
+ pimsg ("verbosity statement:", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_verbosityspec;
+ $$.set[0].v.ival = lastnr;
+ }
+;
+
+onoff:
+ ON {
+ lastnr = 1;
+ }
+|
+ OFF {
+ lastnr = 0;
+ }
+;
+
+dispatchmodestatement:
+ DISPATCHMODE
+ dispatchmethod
+ opt_over
+ semicol {
+ pimsg ("dispatch mode statement:", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_dispatchspec;
+ $$.set[0].v.ival = lastnr;
+
+ if (lastovernr &&
+ (lastnr != ds_bysize && lastnr != ds_byduration))
+ error ("Service '%s': this dispatch mode "
+ "doesn't support 'over <connections>'",
+ cur_service.name);
+ }
+;
+
+opt_over:
+ OVER
+ overnumber
+|
+ /* empty */ {
+ lastovernr = 0;
+ }
+;
+
+overnumber:
+ number_expected
+ NUMBER {
+ setlastovernr (yytext);
+ }
+;
+
+dispatchmethod:
+ dispatchmethod_expected
+ dispatchmethodspec
+;
+
+dispatchmethodspec:
+ ROUNDROBIN {
+ lastnr = ds_roundrobin;
+ }
+|
+ RANDOM {
+ lastnr = ds_random;
+ }
+|
+ BYDURATION {
+ lastnr = ds_byduration;
+ }
+|
+ BYSIZE {
+ lastnr = ds_bysize;
+ }
+|
+ BYORDER {
+ lastnr = ds_byorder;
+ }
+|
+ BYCONNECTIONS {
+ lastnr = ds_byconnections;
+ }
+;
+
+revivingintervalstatement:
+ REVIVINGINTERVAL
+ number
+ semicol {
+ pimsg ("reviving interval statement:", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_revivespec;
+ $$.set[0].v.ival = lastnr;
+ }
+;
+
+backlogstatement:
+ BACKLOG
+ number
+ semicol {
+ pimsg ("backlog statement:", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_revivespec;
+ $$.set[0].v.ival = lastnr;
+ }
+;
+
+shmkeystatement:
+ SHMKEY
+ number
+ semicol {
+ pimsg ("shmkey statement:", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_shmkeyspec;
+ $$.set[0].v.ival = lastnr;
+ }
+;
+
+connectiontimeoutstatement:
+ CONNECTIONTIMEOUT
+ number
+ semicol {
+ pimsg ("connection timeout statement:", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_connectiontimeoutspec;
+ $$.set[0].v.ival = lastnr;
+ }
+;
+
+maxconnectionsstatement:
+ MAXCONNECTIONS
+ number
+ semicol {
+ pimsg ("max clients statement (service):", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_maxconnectionsspec;
+ $$.set[0].v.ival = lastnr;
+ }
+;
+
+typestatement:
+ TYPE
+ typespec
+ semicol {
+ pimsg ("service type:", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof(Confset));
+ $$.set[0].cf = cf_typespec;
+ $$.set[0].v.ival = lastnr;
+ }
+
+typespec:
+ type_expected
+ typespecifier
+ ;
+
+typespecifier:
+ ANY {
+ lastnr = type_any;
+ }
+|
+ STICKYHTTP {
+ lastnr = type_http;
+ }
+;
+
+backendblock:
+ BACKEND
+ backendname
+ '{'
+ backenddefinitions
+ '}' {
+ $$ = $4;
+ }
+;
+
+backendname:
+ backendname_expected
+ IDENTIFIER {
+ psmsg ("backend name:", laststr);
+ cur_backend.name = xstrdup (yytext);
+ }
+;
+
+backenddefinitions:
+ backenddefinitions
+ backenddefinition {
+ $1.n++;
+ $1.set = xrealloc ($1.set, $1.n * sizeof(Confset));
+ $1.set[$1.n - 1] = $2.set[0];
+ $$ = $1;
+ }
+|
+ backenddefinition {
+ $$ = $1;
+ }
+;
+
+backenddefinition:
+ backendstatement_expected
+ backendstatement {
+ $$ = $2;
+ }
+;
+
+backendstatement:
+ serverstatement {
+ psmsg ("backend server:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+|
+ portstatement {
+ pimsg ("backend port:", $1.set[0].v.ival);
+ $$ = $1;
+ }
+|
+ verbositystatement {
+ pimsg ("backend verbosity:", $1.set[0].v.ival);
+ $$ = $1;
+ }
+|
+ onsuccessstatement {
+ psmsg ("backend onsuccess:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+|
+ onfailurestatement {
+ psmsg ("backend onfailure:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+|
+ dumptrafficstatement {
+ psmsg ("backend trafficlog:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+|
+ throughputstatement {
+ psmsg ("backend trafficlog:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+|
+ weightstatement {
+ pimsg ("backend weight:", $1.set[0].v.ival);
+ $$ = $1;
+ }
+|
+ decaystatement {
+ pimsg ("backend decay:", $1.set[0].v.ival);
+ $$ = $1;
+ }
+|
+ maxconnectionsstatement {
+ pimsg ("backend maxconnections:", $1.set[0].v.ival);
+ $$ = $1;
+ }
+|
+ stickycookiestatement {
+ psmsg ("backend sticky cookie:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+|
+ insertcookiestatement {
+ psmsg ("backend cookie insertion:", $1.set[0].v.sval);
+ $$ = $1;
+ }
+;
+
+serverstatement:
+ SERVER
+ serveraddress_expected
+ serveraddress
+ semicol {
+ psmsg ("server statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof (Confset));
+ $$.set[0].cf = cf_serverspec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
+
+weightstatement:
+ WEIGHT
+ number
+ semicol {
+ pimsg ("weight statement", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof (Confset));
+ $$.set[0].cf = cf_weightspec;
+ $$.set[0].v.ival = lastnr;
+ }
+;
+
+decaystatement:
+ DECAY
+ number
+ semicol {
+ pimsg ("decay statement", lastnr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof (Confset));
+ $$.set[0].cf = cf_decayspec;
+ $$.set[0].v.ival = lastnr;
+ }
+;
+
+serveraddress:
+ STRING {
+ setlaststr (laststring);
+ }
+;
+
+onsuccessstatement:
+ ONSUCCESS
+ commandline
+ semicol {
+ psmsg ("onsuccess statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof (Confset));
+ $$.set[0].cf = cf_onsuccspec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
+
+onfailurestatement:
+ ONFAILURE
+ commandline
+ semicol {
+ psmsg ("onfailure statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof (Confset));
+ $$.set[0].cf = cf_onfailspec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
+
+dumptrafficstatement:
+ TRAFFICLOG
+ filename
+ semicol {
+ psmsg ("trafficlog statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof (Confset));
+ $$.set[0].cf = cf_dumpspec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
+
+throughputstatement:
+ THROUGHPUTLOG
+ filename
+ semicol {
+ psmsg ("throughputlog statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof (Confset));
+ $$.set[0].cf = cf_thruspec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
+
+commandline:
+ commandline_expected
+ STRING {
+ setlaststr (laststring);
+ free (laststring);
+ laststring = 0;
+ }
+;
+
+filename:
+ filename_expected
+ STRING {
+ setlaststr (laststring);
+ free (laststring);
+ laststring = 0;
+ }
+;
+
+stickycookiestatement:
+ STICKYCOOKIE
+ cookiespecifier
+ semicol {
+ psmsg ("insertcookie statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof (Confset));
+ $$.set[0].cf = cf_stickycookiespec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
+
+insertcookiestatement:
+ INSERTCOOKIE
+ cookiespecifier
+ semicol {
+ psmsg ("insertcookie statement:", laststr);
+ $$.n = 1;
+ $$.set = xmalloc (sizeof (Confset));
+ $$.set[0].cf = cf_insertcookiespec;
+ $$.set[0].v.sval = xstrdup (laststr);
+ }
+;
+
+cookiespecifier:
+ cookie_expected
+ STRING {
+ setlaststr (laststring);
+ free (laststring);
+ laststring = 0;
+ }
+;
+
+cookie_expected: {
+ yyerrmsg = "cookie specifier expected";
+ }
+
+;
+number_expected: {
+ yyerrmsg = "number expected";
+ }
+;
+
+serveraddress_expected: {
+ yyerrmsg = "hostname or IP address expected";
+ }
+;
+
+service_expected: {
+ yyerrmsg = "'service' expected";
+ }
+;
+
+backendstatement_expected: {
+ yyerrmsg = "backend definition statement expected";
+ }
+;
+
+servicebody_expected: {
+ yyerrmsg = "service body statement expected";
+ }
+;
+
+semicol_expected: {
+ yyerrmsg = "semicolon (;) expected";
+ }
+;
+
+onoff_expected: {
+ yyerrmsg = "'on' or 'off' expetcted";
+ }
+;
+
+dispatchmethod_expected: {
+ yyerrmsg = "dispatch method expected";
+ }
+;
+
+commandline_expected: {
+ yyerrmsg = "command line expected";
+ }
+;
+
+filename_expected: {
+ yyerrmsg = "file name expected";
+ }
+;
+
+servicename_expected: {
+ yyerrmsg = "service name (identifier) expected";
+ }
+;
+
+backendname_expected: {
+ yyerrmsg = "backend name (identifier) expected";
+ }
+;
+
+ipaddress_expected: {
+ yyerrmsg = "IP address or 'any' expected";
+ }
+;
+
+type_expected: {
+ yyerrmsg = "Service type expected ('any', 'stickyhttp', ...)";
+ }
+;
diff --git a/src/restart.c b/src/restart.c
@@ -0,0 +1,38 @@
+#include "crossroads.h"
+
+void restart (int ac, char **av) {
+ char *cmd = 0;
+ int i, ret;
+
+ /* First run the 'stop' action. */
+ for (i = 0; i < org_argc - 1; i++) {
+ cmd = xstrcat (cmd, org_argv[i]);
+ cmd = xstrcat (cmd, " ");
+ }
+ cmd = xstrcat (cmd, "stop");
+
+ 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);
+
+ /* Now run the 'start' action */
+ org_argv[org_argc - 1] = "start";
+ free (cmd);
+ cmd = 0;
+ for (i = 0; i < org_argc; i++) {
+ cmd = xstrcat (cmd, org_argv[i]);
+ if (i < org_argc - 1)
+ cmd = xstrcat (cmd, " ");
+ }
+ msg ("Restart: starting using '%s'", cmd);
+
+ // execvp (org_argv[0], org_argv);
+ if ( (ret = system (cmd)) )
+ error ("Failed to start services (cmd: %s).\n"
+ "Crossroads is not running, fix by hand!");
+}
diff --git a/src/runservice.c b/src/runservice.c
@@ -0,0 +1,95 @@
+#include "crossroads.h"
+
+void runservice () {
+ int listen_sock, pid;
+
+ msg ("Service: %s (on port %d)",
+ activeservice->name, activeservice->port);
+
+ /* Allocate the shared mem segment. */
+ alloc_reporter(activeservice, 1);
+
+ /* Now if required, go into the background. */
+ if (!flag_foreground) {
+
+ if ( (pid = fork()) < 0 ) {
+ /* Fork failed */
+ error ("Fork failure: %s", strerror(errno));
+ } else if (pid) {
+ /* Parent branch */
+ msg ("Service %s detached as PID %d",
+ activeservice->name, pid);
+ return;
+ } else {
+ /* Child branch */
+ if (uid) {
+ if (seteuid(uid))
+ error ("Cannot assume effective uid %d: %s",
+ uid, strerror(errno));
+ }
+
+ set_program_title ("service %s listening", activeservice->name);
+ close (0);
+ close (1);
+ close (2);
+ daemonized++;
+ if ( (open ("/dev/null", O_RDONLY) < 0) ||
+ (open ("/dev/null", O_WRONLY) < 0) ||
+ (open ("/dev/null", O_WRONLY) < 0) )
+ error ("Failed to reopen stdin/out/err on /dev/null");
+ if (setsid() < 0)
+ error ("Failed to become seesion leader");
+
+ /* Store our pid, so that 'crossroads stop' may kill us. */
+ lock_reporter();
+ servicereport->pid = getpid();
+ unlock_reporter();
+
+ /* Promote verbosity. */
+ flag_verbose = activeservice->verbosity;
+
+ /* We've forked for the first time */
+ program_stage = stage_waiting;
+ }
+ }
+
+ /* In 'forever' mode, we create a server-side socket and ask tcpserve()
+ * to service it. tcpserve() will return to us if there are no back
+ * ends to service the request. In that case, we close the listening
+ * socket (so that new clients get denied), take a nap, and redo.
+ * Of course we only start listening if we have back ends at all...
+ */
+ while (1) {
+ if (backend_available()) {
+ /* Create the socket, bind to port. */
+ if ( (listen_sock = make_socket (activeservice->port,
+ activeservice->bind)) < 0 ) {
+ warning ("Listening socket creation failed.. will retry\n");
+
+ /* We wait here for some time, to let the system regain
+ * equilibrium. Something's really afoot, so let's not retry
+ * right away. */
+ sleep (SLEEP_TIME);
+
+ continue;
+ }
+ msg ("Server-side network socket: %d", listen_sock);
+
+ /* Start serving the port! */
+ tcpserve (listen_sock);
+
+ /* tcpserve() returned -- must be because this service is out
+ * of back ends */
+ if (shutdown (listen_sock, SHUT_RDWR))
+ error ("Failed to shut down server-side socket %d: %s",
+ listen_sock, strerror(errno));
+ if (close (listen_sock))
+ error ("Failed to close server-side socket %d: %s",
+ listen_sock, strerror(errno));
+
+ }
+
+ msg ("Taking a nap...");
+ sleep (SLEEP_TIME);
+ }
+}
diff --git a/src/sample.conf b/src/sample.conf
@@ -0,0 +1,184 @@
+/*
+ * Sample configuration for crossroads
+ * Default location of this is /etc/crossroads.conf, unless overruled by -c.
+ */
+
+# Empty lines are allowed, # shell-style comment too.
+// And C++ style comment as well.
+
+/* Service www: TCP activity on port 8000 gets distributed
+ * over a couple of webserver back ends.
+ */
+service www {
+
+ /* Port on which we'll listen in this service: required. */
+ port 8000;
+
+ /* What IP address should this service listen? Default is 'any'.
+ * Alternatively you can state an explicit IP address, such as
+ * 127.0.0.1; that would bind the service only to 'localhost'. */
+ bindto any;
+
+ /* Verbose reporting or not. Default is off. */
+ verbosity on;
+
+ /* Service type: 'http' or 'any'. With 'http' you can make sessions
+ * "stick" to a once selected back end. If you don't need stickiness,
+ * use 'any' which is the default. */
+ type any;
+
+ /* Dispatching mode, or: How to select a back end for an incoming
+ * request. Possible values:
+ * roundrobin: just the next back end in line
+ * random: like roundrobin, but at random to make things more
+ * confusing. Probably only good for testing.
+ * bysize: The backend that transferred the least nr of bytes
+ * is the next in line. As a modifier you can say e.g.
+ * bysize over 10, meaning that the 10 last connections will
+ * be used to compute the transfer size, instead of all
+ * transfers.
+ * byduration: The backend that was active for the shortest time
+ * is the next in line. As a modifier you can say e.g.
+ * byduration of 10 to compute over the last 10 connections.
+ * byconnections: The back end with the least number of active
+ * connections is the next in line.
+ * byorder: The first available back end is always taken.
+ */
+ dispatchmode byduration over 5;
+
+ /* Interval at which we'll check whether a temporarily unavailable
+ * backend has woken up.
+ */
+ revivinginterval 5;
+
+ /* TCP backlog of connections. Default is 0 (no backlog, one
+ * connection may be active).
+ */
+ backlog 5;
+
+ /* For status reporting: a shared memory key. Default is the same
+ * as the port number, OR-ed by a magic number.
+ */
+ shmkey 8000;
+
+ /* This controls when crossroads should consider a connection as
+ * finished even when the TCP sockets weren't closed. This is to
+ * avoid hanging connections that don't do anything. NOTE THAT when
+ * crossroads cuts off a connection due to timeout exceed, this is
+ * not marked as a failure, but as a success. Default is 0: no timeout.
+ */
+ connectiontimeout 300;
+
+ /* The max number of allowed client connections. When present, connections
+ * won't be accepted if the max is about to be exceeded. When
+ * absent, all connections will be accepted, which might be misused
+ * for a DOS attack.
+ */
+ maxconnections 300;
+
+ /* Now let's define a couple of back ends. Number 1: */
+ backend www_backend_1 {
+ /* The server and its port, the minimum configuration. */
+ server httpserver1;
+ port 9010;
+
+ /* The 'decay' of usage data of this back end. Only relevant
+ * when the whole service has 'dispatchmode bysize' or
+ * 'byduration'. The number is a percentage by which the usage
+ * parameter is decreased upon each connection of an other back
+ * end.
+ */
+ decay 10;
+
+ /* To see what's happening in /var/log/messages: */
+ verbosity on;
+ }
+
+ /* The second one. For this back end we want to see the response
+ * times, so we specify a throughput log. */
+ backend www_backend_2 {
+ /* Server and port. You can use a shorthand for both host and port: */
+ server httpserver2:9011;
+
+ /* Verbosity of reporting when this back end is active */
+ verbosity on;
+
+ /* Decay */
+ decay 10;
+
+ /* Performance related */
+ throughputlog /tmp/backend.2.perflog;
+
+ /* Event triggers for system commands upon succesful activation
+ * and upon failure.
+ */
+ onsuccess echo 'success on backend 2' | mail root;
+ onfailure echo 'failure on backend 2' | mail root;
+ }
+
+ /* And yet another one.. this time we will dump the traffic
+ * to a trace file. Furthermore we don't want more than 10 concurrent
+ * connections here. Note that there's also a total maxconnections for the
+ * whole service.
+ */
+ backend www_backend_3 {
+ server httpserver3:9000;
+ verbosity on;
+ decay 10;
+ trafficlog /tmp/backend.3.log;
+ maxconnections 10;
+ }
+}
+
+/* Another example: SSH login failover. */
+service ssh {
+ port 2222; // Incoming port
+ type any; // Generic TCP service
+ verbosity on; // Let's be verbose
+ shmkey 2222; // Shared memory related
+ connectiontimeout 30; // Auto-logout after 30 secs
+ dispatchmode bysize over 1; // Least bytes = least active,
+ // only last connection matters
+ maxconnections 1; // 1 concurrent login
+
+ backend ssh_1 { // First back end
+ server sshserver1:22;
+ verbosity on;
+ onfailure echo 'SSH failure on sshserver1' | mail root;
+ }
+
+ backend ssh_2 { // Second back end
+ server sshserver2:22;
+ verbosity on;
+ onfailure echo 'SSH failure on sshserver2' | mail root;
+ }
+}
+
+/* Yet another example: sticky HTTP for an application that's hosted on
+ * 3 back ends, but the each of the apps isn't session-aware of the others.
+ * As for the balancing we'll simply round-robin it.
+ */
+service balancedapp {
+ port 8001;
+ type stickyhttp;
+ backend app1 {
+ server appserver1:8080;
+ /* If 'BalancerID=a' is already in the browser's request, then
+ * the request automatically goes to this backend */
+ stickycookie BalancerID=a;
+ /* The server return is enriched with the following cookie
+ * instruction (note that this matches 'stickycookie' above) */
+ insertcookie "BalancerID=a; Path=/";
+ }
+ backend app2 {
+ server appserver2:8080;
+ stickycookie BalancerID=b;
+ insertcookie "BalancerID=b; Path=/";
+ }
+ backend app3 {
+ server appserver3:8080;
+ stickycookie BalancerID=c;
+ insertcookie "BalancerID=c; Path=/";
+ }
+}
+
diff --git a/src/sampleconf.c b/src/sampleconf.c
@@ -0,0 +1,7 @@
+#include "crossroads.h"
+#include "sampleconf.h"
+
+void sample_conf (int ac, char **av) {
+ printf ("%s\n", SAMPLECONF);
+ exit (0);
+}
diff --git a/src/serve.c b/src/serve.c
@@ -0,0 +1,44 @@
+#include "crossroads.h"
+
+void serve (int ac, char **av) {
+ int i;
+
+ msg ("Starting services");
+
+ /* Here's a dirty hack for Linux systems. We're going to get
+ * daemonized (unless -f was given) and we'll want to change the
+ * program name as it appears in the ps list.
+ * So we re-exec ourselves with a dummy -i flag that creates
+ * space on the commanedline.
+ */
+# if SET_PROC_TITLE_BY_ARGV == 1
+ if (! flag_foreground && ! iflag_present) {
+ char **argv = xmalloc ( (org_argc + 2) * sizeof(char*));
+ int i;
+
+ argv[0] = org_argv[0];
+ argv[1] = "-ixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ argv[org_argc + 1] = 0;
+
+ for (i = 1; i < org_argc; i++)
+ argv[i + 1] = org_argv[i];
+ argv[org_argc + 1] = 0;
+
+ execv (argv[0], argv);
+ execvp (argv[0], argv);
+ error ("Failed to re-exec program: %s",
+ strerror(errno));
+ }
+# endif
+
+ for (i = 0; i < nservice; i++) {
+ /* With -f flag, don't run second and next services. */
+ if (i && flag_foreground)
+ break;
+
+ /* Start the service. */
+ activeservice = service + i;
+ runservice ();
+ }
+}
diff --git a/src/servesockets.c b/src/servesockets.c
@@ -0,0 +1,11 @@
+#include "crossroads.h"
+
+void servesockets (int clientsock, int backendsock) {
+
+ /* Fork off a servicer if we're in daemon mode. */
+ if (fork_tcp_servicer(current_backend))
+ return;
+
+ /* Copy between sockets. */
+ copysockets (clientsock, backendsock);
+}
diff --git a/src/setprogramtitle.c b/src/setprogramtitle.c
@@ -0,0 +1,37 @@
+#include "crossroads.h"
+
+void set_program_title (char const *fmt, ...) {
+# if SET_PROC_TITLE_BY_ARGV == 1
+
+ static int orglen;
+ char *title, *name = "crossroads";
+ int i, j, k;
+ va_list args;
+
+ /* Make the title */
+ va_start (args, fmt);
+ title = str_vprintf (fmt, args);
+ va_end (args);
+
+ /* Reset old argv, count max length */
+ if (! orglen)
+ for (i = 0; i < org_argc; i++)
+ orglen += (strlen(org_argv[i]) + 1);
+
+ /* Paste in our program name first */
+ for (i = 0; i <= strlen(name) && i < orglen; i++)
+ org_argv[0][i] = name[i];
+
+ /* Paste in the new title */
+ for (j = 0; j < strlen(title) && i + j < orglen; j++)
+ org_argv[0][i + j] = title[j];
+
+ /* Reset remainder of old cmd line */
+ for (k = i + j; k < orglen; k++)
+ org_argv[0][k] = 0;
+
+ /* Free up */
+ free (title);
+
+# endif /* SET_PROC_TITLE_BY_ARGV */
+}
diff --git a/src/showservices.c b/src/showservices.c
@@ -0,0 +1,17 @@
+#include "crossroads.h"
+
+void show_services (int ac, char **av) {
+ int i, j;
+
+ for (i = 0; i < nservice; i++) {
+ printf ("Service %s (on port %d) %d backends\n",
+ service[i].name, service[i].port, service[i].nbackend);
+ for (j = 0; j < service[i].nbackend; j++)
+ printf (" Backend %s (on host %s, port %d)\n",
+ service[i].backend[j].name,
+ service[i].backend[j].server,
+ service[i].backend[j].port);
+ }
+
+ exit (nservice == 0);
+}
diff --git a/src/showstatus.c b/src/showstatus.c
@@ -0,0 +1,38 @@
+#include "crossroads.h"
+
+void show_status (int ac, char **av) {
+ int i, j;
+
+ for (i = 0; i < nservice; i++) {
+ if (i)
+ putchar ('\n');
+ alloc_reporter (service + i, 0);
+ msg ("Reporting service %s (pid = %d)",
+ service[i].name, servicereport->pid);
+ printf ("Service : %s, %d connections, last backend %d\n",
+ service[i].name, servicereport->nclients,
+ servicereport->last_backend);
+ for (j = 0; j < service[i].nbackend; j++) {
+ printf (" Backend %2d : %s is %s, %u connections\n"
+ " Stats : %lu failed in %lu connections,"
+ " usage %gs, %llub",
+ j,
+ service[i].backend[j].name,
+ state_to_string (servicereport->backendstate[j].avail),
+ servicereport->backendstate[j].nclients,
+ servicereport->backendstate[j].failures,
+ servicereport->backendstate[j].totuses,
+ servicereport->backendstate[j].nsec,
+ servicereport->backendstate[j].nbytes);
+ if (service[i].dispatchover)
+ printf (" avg %gs, %lub",
+ servicereport->backendstate[j].avg_nsec,
+ servicereport->backendstate[j].avg_nbytes);
+ putchar ('\n');
+ }
+
+ if (shmdt (servicereport) < 0)
+ error ("Failure releasing reporter memory: %s",
+ strerror(errno));
+ }
+}
diff --git a/src/stagetostring.c b/src/stagetostring.c
@@ -0,0 +1,16 @@
+#include "crossroads.h"
+
+char *stage_to_string (Programstage stage) {
+ switch (stage) {
+ case stage_main:
+ return ("main");
+ case stage_waiting:
+ return ("waiting");
+ case stage_serving:
+ return ("serving");
+ case stage_retrying:
+ return ("retrying");
+ default:
+ return ("unknown");
+ }
+}
diff --git a/src/statetostring.c b/src/statetostring.c
@@ -0,0 +1,5 @@
+#include "crossroads.h"
+
+char *state_to_string (Backendavail avail) {
+ return (state_to_string_map[avail]);
+}
diff --git a/src/stopdaemon.c b/src/stopdaemon.c
@@ -0,0 +1,28 @@
+#include "crossroads.h"
+
+void stop_daemon (int ac, char **av) {
+ int i, ret = 0;
+
+ for (i = 0; i < nservice; i++) {
+ alloc_reporter (service + i, 0);
+ msg ("Stopping service %s (daemon pid %d, wakeup handler pid %d)",
+ service[i].name, servicereport->pid, servicereport->rev_pid);
+ if (kill (servicereport->pid, SIGINT)) {
+ warning ("Failed to stop service %s (%s)",
+ service[i].name, strerror(errno));
+ ret++;
+ }
+ if (servicereport->rev_pid && kill (servicereport->rev_pid, SIGINT)) {
+ warning ("Failed to stop wakeup handler of service %s (%s)",
+ service[i].name, strerror(errno));
+ ret++;
+ }
+
+ /* Note: We run the deallocator of shmem here anyeay, though the
+ * signalled listener does it too.. just to make sure..
+ */
+ dealloc_reporter (service + i);
+ }
+
+ exit (ret);
+}
diff --git a/src/stringtostate.c b/src/stringtostate.c
@@ -0,0 +1,10 @@
+#include "crossroads.h"
+
+Backendavail string_to_state (char const *str) {
+ int i;
+
+ for (i = 0; state_to_string_map[i]; i++)
+ if (!strcasecmp (str, state_to_string_map[i]))
+ return (i);
+ return (st_unknown);
+}
diff --git a/src/strprintf.c b/src/strprintf.c
@@ -0,0 +1,8 @@
+#include "crossroads.h"
+
+char *str_printf (char const *fmt, ...) {
+ va_list args;
+
+ va_start (args, fmt);
+ return (str_vprintf (fmt, args));
+}
diff --git a/src/strvprintf.c b/src/strvprintf.c
@@ -0,0 +1,27 @@
+#include "crossroads.h"
+
+#define STR_BLOCK 512
+
+char *str_vprintf (char const *fmt, va_list arguments) {
+ int size = STR_BLOCK; /* initial size guess */
+ char *buffer = xmalloc (size); /* initial buffer */
+ int nchars; /* return value of vsnprintf */
+
+ while (1) { /* try to make string */
+ nchars = vsnprintf (buffer, size, fmt, arguments);
+
+ /* if this worked, return string */
+ if (nchars > -1 && nchars < size)
+ return (buffer);
+
+ /* try again with more space */
+ if (nchars > -1)
+ size = nchars + 1;
+ else
+ size += STR_BLOCK;
+
+ buffer = xrealloc (buffer, size);
+ }
+
+ return (0); /* to satisfy prototype */
+}
diff --git a/src/sysrun.c b/src/sysrun.c
@@ -0,0 +1,8 @@
+#include "crossroads.h"
+
+void sysrun (char const *cmd) {
+ if (!cmd)
+ return;
+ msg ("Running command: '%s'", cmd);
+ system (cmd);
+}
diff --git a/src/tcpserve.c b/src/tcpserve.c
@@ -0,0 +1,124 @@
+#include "crossroads.h"
+
+void tcpserve (int server_sock) {
+ fd_set set;
+ int backend_sock, new, size, i, pid;
+ struct sockaddr_in clientname;
+ static int wakeup_started = 0;
+
+ /* Set up our signal handlers. */
+ if (!flag_foreground && !wakeup_started++) {
+ /* Interruption signals */
+ for (i = 0; relevant_sigs[i]; i++)
+ signal (relevant_sigs[i], interrupt);
+ /* Set wakeup handler for the wakeup calls. */
+ if (activeservice->rev_interval) {
+ if ( (pid = fork()) < 0 )
+ error ("Fork failed: %s", strerror(errno));
+ else if (!pid) {
+ set_program_title ("wakeup for %s", activeservice->name);
+ wakeup_handler();
+ } else {
+ msg ("Started wakeup handler at pid %d", pid);
+ servicereport->rev_pid = pid;
+ }
+ }
+ }
+
+ msg ("Awaiting activity on port %d (socket fd %d)",
+ activeservice->port, server_sock);
+
+ if (listen (server_sock, activeservice->backlog + 1) < 0)
+ error ("Failed to listen to server_socket: %s", strerror(errno));
+
+ /* Avoid zombies. */
+ signal (SIGCHLD, SIG_IGN);
+
+ /* We're a service now. Never return, never exit
+ * (unless something is REALLY wrong).
+ */
+ while (1) {
+ /* Block until we get a connection. */
+ FD_ZERO (&set);
+ FD_SET (server_sock, &set);
+ if (select (FD_SETSIZE, &set, 0, 0, 0) < 0)
+ error ("Error while waiting for activity: %d (%s)",
+ errno, strerror(errno));
+
+ /* Accept the client-side connection. */
+ size = sizeof(clientname);
+ if ( (new = accept (server_sock, (struct sockaddr *) &clientname,
+ (socklen_t *) &size)) < 0 )
+ error ("Failure while accepting on server socket: %s",
+ strerror(errno));
+
+ msg ("Connect on service %s from %s",
+ activeservice->name,
+ inet_ntoa (clientname.sin_addr));
+
+ /* Incase of a http type service: handle separately. */
+ if (activeservice->type == type_http) {
+ http_serve (new);
+ close (new);
+ continue;
+ }
+
+ /* This is an 'any' service type.
+ * Leave it alone if there are no back ends or if we exceed
+ * the max allowed clients. IN THAT CASE WE RETURN so that
+ * runservice() may close the listener socket, sleep, and
+ * create a new one.
+ */
+ if (! backend_available()) {
+ warning ("No back ends available");
+ close (new);
+ return;
+ }
+
+ /* Retry back ends until we succeed. */
+ while (1) {
+ choose_backend();
+ if (current_backend < 0) {
+ /* No back ends available now. NOTE: we return so that
+ * runservice() will retry in some time. */
+ close (new);
+ return;
+ }
+
+ /* Show what's happening. */
+ msg ("Trying back end %s, port %d",
+ activeservice->backend[current_backend].server,
+ activeservice->backend[current_backend].port);
+
+ /* 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 ) {
+ sysrun (activeservice->backend[current_backend].onfailure);
+ warning ("Failed to connect to server %s:%d",
+ activeservice->backend[current_backend].server,
+ activeservice->backend[current_backend].port);
+ continue;
+ }
+
+ /* Got a live one! */
+ if (fork_tcp_servicer (current_backend)) {
+ /* We're the parent branch here. Close sockets so that
+ * we don't run out of file descriptors, and loop into
+ * the next select/accept run. */
+ close (new);
+ close (backend_sock);
+ break;
+ }
+ copysockets (new, backend_sock);
+
+ /* 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;
+ }
+ }
+}
diff --git a/src/tellservice.c b/src/tellservice.c
@@ -0,0 +1,35 @@
+#include "crossroads.h"
+
+void tell_service (int ac, char **av) {
+ Service *target_service = 0;
+ int target_backend = -1, i;
+ Backendavail avail;
+
+
+ for (i = 0; i < nservice; i++)
+ if (!strcasecmp (service[i].name, av[0])) {
+ target_service = service + i;
+ break;
+ }
+ if (!target_service)
+ error ("Service '%s' isn't known. "
+ "Maybe the configuration has changed?", av[0]);
+
+ for (i = 0; i < target_service->nbackend; i++)
+ if (!strcasecmp (target_service->backend[i].name, av[1])) {
+ target_backend = i;
+ break;
+ }
+ if (target_backend == -1)
+ error ("Backend '%s' of service '%s' isn't known. "
+ "Maybe the configuration has changed?", av[1], av[0]);
+
+ if ( (avail = string_to_state (av[2])) == st_unknown )
+ error ("Incorrect state '%s'.", av[2]);
+
+ alloc_reporter (target_service, 0);
+ servicereport->backendstate[target_backend].avail = avail;
+ msg ("Marked backend %s of service %s as %s.",
+ av[1], av[0], av[2]);
+}
+
diff --git a/src/thruputlog.c b/src/thruputlog.c
@@ -0,0 +1,58 @@
+#include "crossroads.h"
+
+void thruputlog (unsigned char const *buf, int len, int isclient) {
+ struct timeval tv;
+ static double d_start = 0;
+ double d_now;
+ int i;
+ FILE *f;
+
+ if (! activeservice->backend[current_backend].thruputfile)
+ return;
+
+ /* Initialize timer if necessary. Get current time. */
+ if (! d_start) {
+ if (gettimeofday (&tv, 0))
+ error ("Failed to get the time of day: %s\n",
+ strerror(errno));
+ d_start = tv.tv_sec * 1000000 + tv.tv_usec;
+ }
+ if (gettimeofday (&tv, 0))
+ error ("Failed to get the time of day: %s\n", strerror(errno));
+ d_now = tv.tv_sec * 1000000 + tv.tv_usec;
+
+ /* Get a handle on the reporting log. */
+ if ( (! (f = fopen (activeservice->backend[current_backend].thruputfile,
+ "a"))) &&
+ (! (f = fopen (activeservice->backend[current_backend].thruputfile,
+ "w"))) ) {
+ warning ("Cannot write %s: %s",
+ activeservice->backend[current_backend].thruputfile,
+ strerror(errno));
+ return;
+ }
+
+ /* Report the activity. */
+ fprintf (f, "%7.7d %15f %c ",
+ getpid(),
+ (d_now - d_start) / 1000000,
+ isclient ? '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);
+#elif defined HAVE_LOCKF
+ lockf (fileno(f), F_ULOCK, 0);
+#endif
+
+ fclose (f);
+}
diff --git a/src/trafficlog.c b/src/trafficlog.c
@@ -0,0 +1,58 @@
+#include "crossroads.h"
+
+void trafficlog (unsigned char const *buf, int len, int isclient) {
+ int i, j, n = 0;
+ char disp[17];
+ FILE *f;
+
+ if (! activeservice->backend[current_backend].dumpfile)
+ return;
+
+ if ( (! (f = fopen (activeservice->backend[current_backend].dumpfile,
+ "a"))) &&
+ (! (f = fopen (activeservice->backend[current_backend].dumpfile,
+ "w"))) ) {
+ warning ("Cannot write %s: %s",
+ activeservice->backend[current_backend].dumpfile,
+ strerror(errno));
+ return;
+ }
+
+#if defined HAVE_FLOCK
+ flock (fileno(f), LOCK_EX);
+#elif defined HAVE_LOCKF
+ lockf (fileno(f), F_LOCK, 0);
+#endif
+
+ for (i = 0; i < len; i++) {
+ if (! n)
+ fprintf (f, "%c %4.4x ", isclient ? 'C' : 'B', i);
+ fprintf (f, " %2.2x", buf[i]);
+ disp[n] = isprint(buf[i]) ? buf[i] : '.';
+
+ n++;
+
+ if (n == 16) {
+ fputc (' ', f);
+ for (j = 0; j < n; j++)
+ fputc (disp[j], f);
+ fputc ('\n', f);
+ n = 0;
+ }
+ }
+
+ for (j = n; j < 16; j++)
+ fprintf (f, " ");
+ fputc (' ', f);
+ for (j = 0; j < n; j++)
+ fputc (disp[j], f);
+ fputc ('\n', f);
+
+#if defined HAVE_FLOCK
+ flock (fileno(f), LOCK_UN);
+#elif defined HAVE_LOCKF
+ lockf (fileno(f), F_ULOCK, 0);
+#endif
+
+ fclose (f);
+}
diff --git a/src/unlockreporter.c b/src/unlockreporter.c
@@ -0,0 +1,17 @@
+#include "crossroads.h"
+
+void unlock_reporter() {
+ struct sembuf buf = {
+ 0, /* semaphore number */
+ -1, /* subtract 1 */
+ 0, /* no special flags */
+ };
+
+ if (flag_foreground)
+ return;
+
+ /* msg ("UnLocking reporter memory"); */
+ if (semop (semid, &buf, 1) < 0)
+ error ("Failed to unlock reporter memory (stage %s): %s",
+ stage_to_string (program_stage), strerror(errno));
+}
diff --git a/src/usage.c b/src/usage.c
@@ -0,0 +1,8 @@
+
+#include "crossroads.h"
+#include "usage.h"
+
+void usage () {
+ fprintf (stderr, "\n" USAGETEXT "\n", VER, DEFAULT_CONF);
+ exit (1);
+}
diff --git a/src/usage.txt b/src/usage.txt
@@ -0,0 +1,38 @@
+This is Crossroads %s, a load balancer and fail-over utility for TCP.
+Copyright (c) Karel Kubat / e-tunity 2005/2006 ff. All rights reserved.
+For information, contact <info@e-tunity.com> or see <http://www.e-tunity.com>.
+For distributions and updates, visit <http://public.e-tunity.com>.
+
+Usage:
+ ** Controlling the daemon: **
+ crossroads [flags] start: start all services
+ crossroads [flags] status: show the active services and their status
+ crossroads [flags] stop: stop all services
+ crossroads [flags] restart: stop and then start
+ crossroads [flags] tell SERVICE BACKEND STATE: mark the named
+ BACKEND of the SERVICE with the STATE ('available', 'unavailable'
+ or 'down'). Use the action 'services' to determine SERVICE and
+ BACKEND names.
+ ** Configuration related: **
+ crossroads [flags] configtest: verify that the config is OK
+ crossroads [flags] services: show the configured services
+ crossroads [flags] sampleconf: display a sample configuration
+
+Supported flags:
+ -c CONFIG: Uses the named configuration, instead of the default
+ %s
+ -f: With '(re)start', causes crossroads to stay in the foreground
+ instead of daemonizing. Useful for debugging with -v. No
+ 'status' reporting is possible, no wakeup handlers will run.
+ -u USER: With '(re)start', causes the daemons to run under the
+ permissions of the stated user. Unused when combined with -f.
+ -v: Enables verbosity upon startup and, when used with -f, in the
+ services. Normally the services verbosity is controlled via
+ 'verbosity' statements in the configuration.
+ -V: Shows the version ID and stops.
+ -?, -h: Shows this message.
+
+NOTE: Stopping, restarting and status reporting depends on the
+configuration that was in use when crossroads was started. Therefore,
+before adding or removing services in the configuration, 'stop' the
+running services first.
diff --git a/src/wakeuphandler.c b/src/wakeuphandler.c
@@ -0,0 +1,54 @@
+#include "crossroads.h"
+
+void wakeup_handler () {
+ int sock, i;
+
+ /* Set the stage and any signals that we want. */
+ program_stage = stage_retrying;
+ for (i = 0; relevant_sigs[i]; i++)
+ signal (relevant_sigs[i], interrupt);
+
+ /* Forever, until a signal kills us, or Sol explodes, causing
+ * massive electromagnetic pulses that fry my CPU.
+ */
+ while (1) {
+ /* Wait for the alarm to go off. */
+ sleep (activeservice->rev_interval);
+
+ /* 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);
+
+ 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();
+ }
+ }
+ }
+}
diff --git a/src/warning.c b/src/warning.c
@@ -0,0 +1,12 @@
+#include "crossroads.h"
+
+void warning (char const *fmt, ...) {
+ va_list args;
+
+ va_start (args, fmt);
+ if (!daemonized) {
+ vfprintf (stderr, fmt, args);
+ fprintf (stderr, "\n");
+ } else
+ writelog (LOG_ERR, fmt, args);
+}
diff --git a/src/writelog.c b/src/writelog.c
@@ -0,0 +1,9 @@
+#include "crossroads.h"
+
+void writelog (int prio, char const *fmt, va_list args) {
+ static int logstarted;
+
+ if (!logstarted++)
+ openlog ("crossroads", LOG_PID, LOG_DAEMON);
+ vsyslog (prio, fmt, args);
+}
diff --git a/src/xmalloc.c b/src/xmalloc.c
@@ -0,0 +1,9 @@
+#include "crossroads.h"
+
+void *xmalloc (unsigned sz) {
+ void *ret;
+
+ if (! (ret = malloc (sz)) )
+ error ("Out of memory (while allocating %u bytes)", sz);
+ return (ret);
+}
diff --git a/src/xrealloc.c b/src/xrealloc.c
@@ -0,0 +1,12 @@
+#include "crossroads.h"
+
+void *xrealloc (void *what, unsigned sz) {
+ if (! sz) {
+ free (what);
+ return (0);
+ }
+ if (! (what = realloc (what, sz)) )
+ error ("Out of memory (while increasing block to %u bytes)", sz);
+ return (what);
+}
+
diff --git a/src/xstrcat.c b/src/xstrcat.c
@@ -0,0 +1,14 @@
+#include "crossroads.h"
+
+char *xstrcat (char *what, char const *rest) {
+ if (!what || !*what)
+ return (xstrdup (rest));
+ if (!rest || !*rest)
+ return (what);
+
+ if (! (what = realloc (what, strlen(what) + strlen(rest) + 1)) )
+ error ("Out of memory (while adding '%s' to '%s'",
+ rest, what);
+ strcat (what, rest);
+ return (what);
+}
diff --git a/src/xstrdup.c b/src/xstrdup.c
@@ -0,0 +1,11 @@
+#include "crossroads.h"
+
+char *xstrdup (char const *what) {
+ char *ret;
+
+ if (!what || !*what)
+ return (0);
+ if (! (ret = strdup (what)) )
+ error ("Out of memory (while copying '%s')", what);
+ return (ret);
+}
diff --git a/tools/c-conf b/tools/c-conf
@@ -0,0 +1,487 @@
+#!/usr/bin/perl
+
+use strict;
+use Getopt::Std;
+
+# Globals
+my $VER = "1.01";
+# 1.01 [KK 2005-09-29] Implemented context-sensitive help via -h.
+# Action 'header' implemented.
+# 1.00 [KK 2005-09-28] First version
+
+# Configuration
+my @def_headerdirs = ('/usr/include',
+ '/usr/local/include',
+ "$ENV{HOME}/include",
+ );
+my @def_libdirs = ('/usr/lib',
+ '/usr/local/lib',
+ '/usr/ucblib',
+ "$ENV{HOME}/lib",
+ );
+my @c_compilers = ('cc', 'gcc');
+my @cpp_compilers = ('c++', 'g++');
+
+# Globals
+my %opts;
+my $base;
+my @warnings;
+my $printed;
+my @headerdirs;
+my @libdirs;
+
+# Show usage and croak
+sub usage {
+ die <<"ENDUSAGE"
+
+This is c-conf, the C compilation configuration helper V$VER
+Copyright (c) e-tunity. Contact <info\@e-tunity.com> for information.
+
+Usage:
+ $base [flags] header FILE.H [FILE.H...] Searches for directories
+ containing the named header(s), returns appropriate -I flags.
+ $base [flags] headerdir DIR [DIR...]: Searches for directory
+ containing headers, returns appropriate -I flag.
+ $base [flags] ifheader FILE.H DEFINE Searches for the named header.
+ If found, a compilation flag -DDEFINE is returned, indicating that
+ the header is found.
+ $base [flags] libfunction FUNC DEFINE Creates a small program that
+ tries to use FUNC. If this succeeds, a -DDEFINE=1 flag is returned.
+ $base [flags] lib NAME [NAME...]: Searches for libNAME.{a,so,...},
+ returns appropriate -L and -l flags.
+ $base [flags] so-name LIB: Returns filename of a shared-object for LIB
+ $base [flags] so-cflags: Returns flags to compile for shared-object
+ ready objects.
+ $base [flags] so-lflags: Returns flags to produce a shared-object
+ library
+ $base [flags] c-compiler: Returns absolute path to C compiler
+ $base [flags] c++-compiler: Returns absolute path to C++ compiler
+
+Optional flags:
+ -h: to show short help for an action, e.g. try '$base -h so-name'
+ -v: to show verbose messages
+ -I DIR[,DIR..]: to add DIR(s) to the searchpath for headers, default
+ searchpath is @headerdirs
+ -L DIR[,DIR..]: to add DIR(s) to the searchpath for libraries, default
+ searchpath is @libdirs
+
+Meaningful output is returned on stdout. Verbose messages, warnings and
+errors go to stderr.
+
+ENDUSAGE
+}
+
+# Issue a warning
+sub warning {
+ push (@warnings, "@_");
+}
+
+# Show a message
+sub msg {
+ return unless ($opts{v});
+ print STDERR ("$base: ", @_);
+}
+
+# Show help info if -h was given
+sub checkhelp {
+ return unless ($opts{h});
+ print STDERR (@_);
+ exit (1);
+}
+
+# Basename / dirname of a file.
+sub basename ($) {
+ my $name = shift;
+ $name =~ s{.*/}{};
+ return ($name);
+}
+sub dirname ($) {
+ my $name = shift;
+ return (undef) unless ($name =~ /\//);
+ $name =~ s{/[^/]$}{};
+ return ($name);
+}
+
+# Get the uname.
+sub uname() {
+ my $ret = `uname`;
+ chomp ($ret);
+ msg ("uname: $ret\n");
+ return ($ret);
+}
+
+# Find a binary along the path.
+sub findbin ($) {
+ my $bin = shift;
+ msg ("Looking for executable '$bin'\n");
+ foreach my $d (split (/:/, $ENV{PATH})) {
+ if (-x "$d/$bin") {
+ msg ("Found as '$d/$bin'\n");
+ return ("$d/$bin");
+ }
+ }
+ msg ("Not found!\n");
+}
+
+# Recursively determine the files under a given dir.
+my %_dir_visited;
+sub subfiles ($$$) {
+ my ($dir, $mask, $recursive) = @_;
+
+ %_dir_visited = () unless ($recursive);
+ if ($_dir_visited{$dir}) {
+ msg ("Path '$dir' was already visited\n");
+ return (undef);
+ }
+ my ($dev, $ino) = stat($dir);
+ $_dir_visited{$dir} = sprintf ("%d-%d", $dev, $ino);
+
+ msg ("Scanning for '$mask' under '$dir'\n");
+ if (! -d $dir) {
+ msg ("Scan path ends, '$dir' is not an accessible directory\n");
+ return (undef);
+ }
+
+ my @ret = ();
+ foreach my $f (glob ("$dir/$mask")) {
+ if (-f $f) {
+ push (@ret, $f);
+ msg ("Found a hit as '$f'\n");
+ }
+ msg ("Hits so far: ", $#ret + 1, "\n") if ($#ret > -1);
+ }
+ foreach my $d (glob ("$dir/*")) {
+ next unless (-d $d);
+ msg ("Recursing from '$dir' into '$d'\n");
+ my @subret = subfiles ("$d", $mask, 1);
+ my $added = 0;
+ foreach my $f (@subret) {
+ if (-f $f) {
+ push (@ret, $f);
+ $added++;
+ }
+ }
+ msg ("Added ", $added, " hits from '$d'\n") if ($added);
+ }
+ if ($#ret > -1) {
+ msg ("Found ", $#ret + 1, " entries matching '$mask' under '$dir'\n");
+ return (@ret);
+ } else {
+ # msg ("No entries matching '$mask' under '$dir' found\n");
+ return (undef);
+ }
+}
+
+# Output stuff
+sub output {
+ print (' ') if ($printed++);
+ print (@_);
+}
+
+# Find a header, output a define if found.
+sub if_header {
+ checkhelp <<"ENDHELP";
+'ifheader' tries to find a header file in the 'include' directories.
+When found, a define-flag for the C compiler is returned.
+E.g.: $base ifheader malloc.h HAVE_MALLOC_H (may return -DHAVE_MALLOC_H)
+Use in a Makefile as in:
+CFLAGS = $(CFLAGS) $(shell c-conf ifheader malloc.h HAVE_MALLOC_H
+Then in a C source as:
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+ENDHELP
+
+ usage() if ($#_ != 1);
+
+ my ($h, $def) = @_;
+ msg ("Looking for '$h'\n");
+
+ foreach my $d (@headerdirs) {
+ if (-f "$d/$h") {
+ output ("-D$def");
+ return;
+ }
+ }
+}
+
+# Find a header
+sub header {
+ checkhelp <<"ENDHELP";
+'header' locates one or more C headers in the 'include' directories.
+E.g.: $base header e-lib.h stdio.h (may return -I/usr/include -I/usr/e/include)
+Use in a Makefile as in:
+CFLAGS = -C -Wall \$(shell c-conf header e-lib.h)
+Then in a C source as:
+#include <e-lib.h>
+ENDHELP
+
+ usage() if ($#_ == -1);
+ foreach my $h (@_) {
+ msg ("Looking for '$h'\n");
+ my $found = 0;
+ foreach my $d (@headerdirs) {
+ msg ("Trying '$d/$h'\n");
+ if (-f "$d/$h") {
+ $found++;
+ msg ("Found\n");
+ output ("-I$d");
+ last;
+ }
+ }
+ warning ("Failed to locate header '$h' in @headerdirs\n")
+ unless ($found);
+ }
+}
+
+# Find a header directory
+sub headerdir {
+ checkhelp <<"ENDHELP";
+'headerdir' locates directories under which (in steps) C headers are
+E.g.: $base headerdir libxml2 (may return '-I/usr/include/libxml2')
+Use in a Makefile as in:
+CFLAGS = -C -Wall \$(shell c-conf headerdir libxml2)
+Then in a C source as:
+#include <libxml/xpath.h>
+ENDHELP
+
+ usage() if ($#_ == -1);
+ foreach my $headerdir (@_) {
+ msg ("Looking for header dir '$headerdir'\n");
+ my $found = 0;
+ foreach my $d (@headerdirs) {
+ msg ("Trying as '$d/$headerdir'\n");
+ my $target = "$d/$headerdir";
+ if (subfiles ($target, '*.h', 0)) {
+ output ("-I$target");
+ $found++;
+ }
+ }
+ warning ("Header dir '$headerdir' not found\n")
+ unless ($found);
+ }
+}
+
+# Find a library
+sub lib {
+ checkhelp <<"ENDHELP";
+'lib' generates the linkage flags for a given library name. The name
+is bare, without 'lib' and '.so' and the like.
+E.g.: $base lib xml2 (may return '-L/usr/lib -lxml2')
+Use in a Makefile as in:
+LDFLAGS = \$(shell c-conf lib xml2)
+ENDHELP
+
+ usage() if ($#_ == -1);
+ foreach my $lib (@_) {
+ msg ("Looking for lib '$lib'\n");
+ my $found = 0;
+ foreach my $d (@libdirs) {
+ msg ("Trying under '$d'\n");
+ my $hit = (subfiles ($d, "lib$lib.*", 0))[0];
+ if ($hit) {
+ msg ("Found as '$hit'\n");
+ $found++;
+ $hit =~ s{/[^/]*$}{};
+ output ("-L$hit -l$lib");
+ }
+ }
+ warning ("Library '$lib' not found\n")
+ unless ($found);
+ }
+}
+
+# Compilation flags to make a so-ready object.
+sub so_cflags {
+ checkhelp <<"ENDHELP";
+'so-cflags' returns the compilation flags that are necessary when
+building objects for a shared library.
+E.g.: $base so-cflags (may return '-fPIC')
+Use in a Makefile as in:
+CFLAGS = -c -g -Wall $(shell c-conf so-cflags)
+ENDHELP
+
+ usage() if ($#_ > -1);
+ if (uname() eq 'Darwin') {
+ output ('-fPIC');
+ } else {
+ output ('-fpic');
+ }
+}
+
+# Linkage flags to make an so.
+sub so_lflags {
+ checkhelp << "ENDHELP";
+'so-lflags' returns the linkage flags that are necessary when
+combining objects into a shared library.
+E.g.: $base so-lflags (may return '-dynamiclib -Wl,-single_module')
+Use in a Makefile as in:
+MY_SO = \$(shell c-conf s-name my)
+\$(MY_SO): *.o
+ \$(CC) -o \$(MY_SO) \$(shell c-conf so-lflags) *.o
+ENDHELP
+
+ usage() if ($#_ > -1);
+ my $lib = shift;
+
+ if (uname() eq 'Darwin') {
+ output ("-dynamiclib -Wl,-single_module");
+ } else {
+ output ("-shared");
+ }
+}
+
+# Get the C compiler
+sub c_compiler {
+ checkhelp <<"ENDHELP";
+'c-compiler' tries to find a C compiler and returns its (bare) name.
+E.g.: $base c-compiler
+ -> gcc
+ENDHELP
+
+ usage() if ($#_ > -1);
+ foreach my $c (@c_compilers) {
+ if (findbin ($c)) {
+ output ($c);
+ return;
+ }
+ }
+ warning ("No C compiler found\n");
+}
+
+# Get the C++ compiler
+sub cpp_compiler {
+ checkhelp <<"ENDHELP";
+'c++-compiler' tries to find a C++ compiler and returns its (bare) name.
+E.g.: $base c++-compiler
+ -> g++
+ENDHELP
+
+ usage() if ($#_ > -1);
+ foreach my $c (@cpp_compilers) {
+ if (findbin ($c)) {
+ output ($c);
+ return;
+ }
+ }
+ warning ("No C++ compiler found\n");
+}
+
+# Get the name for an SO.
+sub so_name {
+ checkhelp <<"ENDHELP";
+'so-name' returns the filename of a shared library, based on the LIB
+argument.
+E.g.: $base so-name test
+ -> libtest.so
+ENDHELP
+
+ usage() if ($#_ != 0);
+ my $name = shift;
+
+ my $dir = dirname ($name);
+ my $base = basename ($name);
+
+ my $dest;
+ if (uname() eq 'Darwin') {
+ $dest = "lib$base.dylib";
+ } else {
+ $dest = "lib$base.so";
+ }
+
+ if ($dir ne '') {
+ output ("$dir/$dest");
+ } else {
+ output ("$dest");
+ }
+}
+
+# Check that a libfunction is present.
+sub libfunction {
+ checkhelp <<"ENDHELP";
+'libfunction' checks whether a library function is present. There are
+two arguments: the function to check, and a define to output when the
+function is found. The output is a -D flag for the compiler commandline.
+E.g.: $base libfunction printf HAVE_PRINTF
+ -> -DHAVE_PRINTF=1
+ $base libfunction foo_bar HAVE_FOOBAR
+ -> (nothing)
+ENDHELP
+
+ usage() if ($#_ != 1);
+ my ($func, $def) = @_;
+
+ # Create a temp .c file.
+ my $src = "/tmp/$$.c";
+ my $dst = "/tmp/$$.out";
+ open (my $of, ">$src")
+ or die ("Cannot write $src: $!\n");
+ print $of ("main () {\n",
+ " void $func (void);\n",
+ " $func();\n",
+ "}\n");
+ close ($of);
+
+ my $cc;
+ foreach my $c (@c_compilers) {
+ if (findbin ($c)) {
+ $cc = $c;
+ last;
+ }
+ }
+ die ("Failed to locate C compiler\n") if ($cc eq '');
+ my $ret = system ("$cc -o $dst $src >/dev/null 2>&1");
+ unlink ($src, $dst);
+
+ output ("-D$def=1") if ($ret == 0);
+}
+
+# Main starts here
+
+$base = $0;
+$base =~ s{.*/}{};
+usage () unless (getopts ('vhI:L:', \%opts));
+foreach my $d (split (/,/, $opts{L})) {
+ push (@libdirs, $d);
+}
+foreach my $d (split (/,/, $opts{I})) {
+ push (@headerdirs, $d);
+}
+
+push (@libdirs, @def_libdirs);
+push (@headerdirs, @def_headerdirs);
+my $action = shift (@ARGV);
+
+if ($action eq 'header') {
+ header (@ARGV);
+} elsif ($action eq 'headerdir') {
+ headerdir (@ARGV);
+} elsif ($action eq 'lib') {
+ lib (@ARGV);
+} elsif ($action eq 'so-cflags') {
+ so_cflags (@ARGV);
+} elsif ($action eq 'so-lflags') {
+ so_lflags (@ARGV);
+} elsif ($action eq 'c-compiler') {
+ c_compiler(@ARGV);
+} elsif ($action eq 'c++-compiler') {
+ cpp_compiler(@ARGV);
+} elsif ($action eq 'so-name') {
+ so_name (@ARGV);
+} elsif ($action eq 'ifheader') {
+ if_header (@ARGV);
+} elsif ($action eq 'libfunction') {
+ libfunction (@ARGV);
+} else {
+ usage ();
+}
+
+print ("\n") if ($printed);
+if ($#warnings > -1) {
+ foreach my $w (@warnings) {
+ print STDERR ("$base WARNING: $w");
+ }
+ exit (1);
+}
+exit (0);
+
diff --git a/tools/e-txt2c b/tools/e-txt2c
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+VER=1.04
+
+# ChangeLog:
+# 1.04 Call to e-copyright removed.
+# 1.03 -V shows more info
+# 1.02 In the text file, ' is changed to \' and \ is changed to \\
+# 1.01 -V flag implemented
+# 1.00 First version
+
+# e-txt2c: development tool
+# just write up your usage in a file "usage.txt", then run
+# e-txt2c DEFINENAME < usage.txt > usage.h
+# then include usage.h in your program and refer to define DEFINENAME
+# to address the string
+
+# check args
+if [ "$1" = "-V" ] ; then
+ echo $VER TXT to C-string Translator
+ exit 0
+fi
+
+if [ -z "$1" ] ; then
+ cat << ENDUSAGE 1>&2
+Usage: e-txt2c DEFINENAME <input.txt >output.h
+E.g., when writing up your usage() function, just put your text in usage.txt.
+Then run "e-txt2c USAGETEXT <usage.txt >usage.h" and include usage.h. Refer
+to your text using the define symbol USAGETEXT. Typically this is a rule
+in a Makefile, as in:
+ # my "main.c" includes "usage.h", so it is dependent on that
+ main.o: main.c usage.h
+
+ # how to make usage.h
+ usage.h: usage.txt
+ e-txt2c USAGETEXT <usage.txt >usage.h
+
+ENDUSAGE
+ exit 1
+fi
+
+cat << ENDWARNING
+
+/*
+ * Warning: this file is auto-generated from an input file by e-txt2c.
+ * Don't modify this, your changes will be overwritten (probably during
+ * the next "make").
+ * To change this text, find the sources, edit the text input, and
+ * re-make the program.
+ */
+
+ENDWARNING
+
+echo "#define $1 \\"
+
+sed \
+ -e 's:\\:\\\\:g' \
+ -e "s:':\\\':g" \
+ -e 's:":\\":g' \
+ -e 's:^: ":' \
+ -e 's:$:\\n" \\:'
+
+echo ' ""'
diff --git a/tools/e-ver b/tools/e-ver
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+
+use strict;
+use Getopt::Std;
+
+# ChangeLog
+# 1.03 Revamp to Perl. Support for RPM's spec files.
+# 1.02 -V shows more info.
+# 1.01 -V flag implemented
+# 1.00 First version
+
+my $VER=1.03;
+my %opts;
+
+# Show usage and stop.
+sub usage () {
+ system ("e-copyright 'Version ID Checker' $VER");
+ die ("Usage: e-ver file(s) VERSION\n",
+ "The files are checked for ChangeLog-style entries or RPM-style\n",
+ "entries. The stated versions must match VERSION.\n\n");
+}
+
+# Check a version in a file.
+sub checkversion ($$) {
+ my $f = shift;
+ my $v = shift;
+
+ open (IF, $f) or die ("e-ver: cannot read $f: $!\n");
+ while (<IF>) {
+ chomp ();
+ if (/^[0-9]/) {
+ # ChangeLog style entry.
+ my $curver = sprintf ("%g", $_);
+ die ("e-ver: ChangeLog-style version $curver is not the\n",
+ " required $v. You should probably update $f\n")
+ if ($curver != $v);
+ close (IF);
+ return;
+ }
+
+ if (/^Version: */) {
+ s/^Version: *//;
+ my $curver = sprintf ("%g", $_);
+ die ("e-ver: RPM-spec style version $curver is not the\n",
+ " required $v. You should probably update $f.\n")
+ if ($curver != $v);
+ close (IF);
+ return;
+ }
+ }
+
+ close (IF);
+ die ("e-ver: failed to find version ID in $f, cannot check\n");
+}
+
+# Main starts here.
+usage() unless (getopts ("V", \%opts));
+if ($opts{V}) {
+ print ("$VER Version ID Checker\n");
+ exit (0);
+}
+usage() if ($#ARGV < 1);
+
+my $version_to_check = pop (@ARGV);
+foreach my $f (@ARGV) {
+ die ("e-ver: no such file $f\n") unless (-f $f);
+ checkversion ($f, $version_to_check);
+}
diff --git a/tools/gettools b/tools/gettools
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use strict;
+
+foreach my $f (@ARGV) {
+ my $srcfound = 0;
+ my ($src, $dst);
+ foreach my $srcdir ("$ENV{EBASE}/bin", "$ENV{EBASE}/etc") {
+ $src = "$srcdir/$f";
+ next unless (-f $src);
+ $dst = "../tools/$f";
+
+ if (! -f $dst or
+ (stat($src))[9] > (stat($dst))[9]) {
+ print ("gettools: $src -> $dst\n");
+ system ("cp $src $dst") == 0
+ or die ("gettools: copying of '$src' to '$dst' failed\n");
+ }
+ }
+}
diff --git a/tools/makedist b/tools/makedist
@@ -0,0 +1,44 @@
+#!/usr/bin/perl
+
+use strict;
+
+sub run ($) {
+ my $cmd = shift;
+ print ("RUN> $cmd\n");
+ system ($cmd) == 0
+ or die ("Command [$cmd] failed\n");
+}
+
+my $ver = $ARGV[0]
+ or die ("Version argument required.\n",
+ "You can only run this from 'make dist'!\n");
+
+print ("Making crossroads distribution.. version is $ver\n");
+
+my $distdir = "/tmp/crossroads-$ver";
+if (-d $distdir) {
+ print ("Removing old dist dir $distdir\n");
+ run ("rm -r $distdir");
+}
+print ("Preparing for distro (make documentation, make clean)\n");
+run ("make documentation");
+run ("make clean");
+mkdir ($distdir)
+ or die ("Cannot makedir $distdir: $!\n");
+
+print ("Copying all...\n");
+run ("cp -r * $distdir");
+print ("Removing CVS entries in dist dir\n");
+run ("find $distdir -type d -name CVS -exec rm -rf {} \\; || true");
+print ("Removing SVN entries in dist dir\n");
+run ("find $distdir -type d -name .svn -exec rm -rf {} \\; || true");
+
+print ("Making archive...\n");
+chdir ("/tmp")
+ or die ("Hm.. /tmp is gone\n");
+run ("tar czf crossroads-$ver.tar.gz crossroads-$ver");
+
+print ("----------------------------------------------------------\n",
+ " Distro archive is now in /tmp/crossroads-$ver.tar.gz\n",
+ "----------------------------------------------------------\n");
+