crossroads

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

commit d4d4a0cf5b98a1b5a0e9430f35144e9970330163
Author: finwo <finwo@pm.me>
Date:   Sat,  3 Jan 2026 18:49:42 +0100

1.00

Diffstat:
A.gitignore | 1+
AChangeLog | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AMakefile | 40++++++++++++++++++++++++++++++++++++++++
Adoc/ANNOUNCEMENT | 19+++++++++++++++++++
Adoc/Makefile | 13+++++++++++++
Adoc/benchmarking.yo | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/compiling.yo | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/config.yo | 581+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/crossroads.html | 1928+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/crossroads.man | 1971+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/crossroads.pdf | 0
Adoc/crossroads.yo | 38++++++++++++++++++++++++++++++++++++++
Adoc/defs.yo | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/impatient.yo | 42++++++++++++++++++++++++++++++++++++++++++
Adoc/intro.yo | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/tips.yo | 547+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/using.yo | 38++++++++++++++++++++++++++++++++++++++
Aetc/Makefile.def | 45+++++++++++++++++++++++++++++++++++++++++++++
Aetc/Makefile.help | 15+++++++++++++++
Asrc/Makefile | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/allocreporter.c | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backendavailable.c | 28++++++++++++++++++++++++++++
Asrc/backendconnect.c | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/choosebackend.c | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/configtest.c | 6++++++
Asrc/copysockets.c | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/crossroads.h | 266+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/deallocreporter.c | 33+++++++++++++++++++++++++++++++++
Asrc/error.c | 13+++++++++++++
Asrc/forktcpservicer.c | 38++++++++++++++++++++++++++++++++++++++
Asrc/http09received.c | 13+++++++++++++
Asrc/httpbeyondheaders.c | 29+++++++++++++++++++++++++++++
Asrc/httpcomplete.c | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/httperror.c | 16++++++++++++++++
Asrc/httpfindcookie.c | 25+++++++++++++++++++++++++
Asrc/httpheadersdone.c | 24++++++++++++++++++++++++
Asrc/httpheaderval.c | 38++++++++++++++++++++++++++++++++++++++
Asrc/httpread.c | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/httpserve.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Asrc/httpserversocket.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/httpsetstickycookie.c | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/httpwrite.c | 13+++++++++++++
Asrc/initsockaddr.c | 13+++++++++++++
Asrc/interrupt.c | 29+++++++++++++++++++++++++++++
Asrc/ishexdigit.c | 10++++++++++
Asrc/lexer.l | 267+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lockreporter.c | 24++++++++++++++++++++++++
Asrc/main.c | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/makesocket.c | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/markactivity.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/msg.c | 16++++++++++++++++
Asrc/msgdumpbuf.c | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/parser.y | 834+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/restart.c | 38++++++++++++++++++++++++++++++++++++++
Asrc/runservice.c | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sample.conf | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sampleconf.c | 7+++++++
Asrc/serve.c | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/servesockets.c | 11+++++++++++
Asrc/setprogramtitle.c | 37+++++++++++++++++++++++++++++++++++++
Asrc/showservices.c | 17+++++++++++++++++
Asrc/showstatus.c | 38++++++++++++++++++++++++++++++++++++++
Asrc/stagetostring.c | 16++++++++++++++++
Asrc/statetostring.c | 5+++++
Asrc/stopdaemon.c | 28++++++++++++++++++++++++++++
Asrc/stringtostate.c | 10++++++++++
Asrc/strprintf.c | 8++++++++
Asrc/strvprintf.c | 27+++++++++++++++++++++++++++
Asrc/sysrun.c | 8++++++++
Asrc/tcpserve.c | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tellservice.c | 35+++++++++++++++++++++++++++++++++++
Asrc/thruputlog.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/trafficlog.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/unlockreporter.c | 17+++++++++++++++++
Asrc/usage.c | 8++++++++
Asrc/usage.txt | 38++++++++++++++++++++++++++++++++++++++
Asrc/wakeuphandler.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/warning.c | 12++++++++++++
Asrc/writelog.c | 9+++++++++
Asrc/xmalloc.c | 9+++++++++
Asrc/xrealloc.c | 12++++++++++++
Asrc/xstrcat.c | 14++++++++++++++
Asrc/xstrdup.c | 11+++++++++++
Atools/c-conf | 487+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/e-txt2c | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/e-ver | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/gettools | 20++++++++++++++++++++
Atools/makedist | 44++++++++++++++++++++++++++++++++++++++++++++
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: &nbsp;(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'.&nbsp;(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.&nbsp;(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&gt;&lt;/td&gt;.. &lt;/tr&gt;.&lt;/table&gt;.&lt;/td&gt;&lt;td class... +6 0000594 0.946356 B smallboxdownl"&gt;Download&lt;/td&gt;.. &lt;td class... +7 0000594 0.961102 B td&gt;&lt;td class="smallboxodd" valign="top"&gt;&lt;... +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 ----&gt;----&gt;----&gt;---&gt;*crossroads ====&gt;====&gt;====&gt; + \ + back end + / +client ----&lt;----&lt;----&lt;---&lt; crossroads ====&lt;====&lt;====&lt; + +</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&nbsp;(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&gt; cd /etc/rc.d/rc3.d +root&gt; ln -s /etc/init.d/crossroads S99crossroads +root&gt; 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"); +