diff --git a/Docs/manual.html b/Docs/manual.html
new file mode 100644
index 0000000..a4ae0b3
--- /dev/null
+++ b/Docs/manual.html
@@ -0,0 +1,885 @@
+
+
+L2TPNS Manual
+
+
+
+L2TPNS Manual
+
+ - Overview
+ - Installation
+ - Configuration
+ - Controlling the process
+ - Command-Line Interface
+ - Throttling
+ - Interception
+ - Authentication
+ - Plugins
+ - Walled Garden
+ - Clustering
+ - Performance
+
+Overview
+L2TPNS is half of a complete L2TP implementation. It supports only the
+LNS side of the connection.
+
+L2TP (Layer 2 Tunneling Protocol) is designed to allow any layer 2
+protocol (e.g. Ethernet, PPP) to be tunneled over an IP connection. L2TPNS
+implements PPP over L2TP only.
+
+There are a couple of other L2TP imlementations, of which l2tpd is probably the
+most popular. l2tpd also will handle being either end of a tunnel, and
+is a lot more configurable than L2TPNS. However, due to the way it works,
+it is nowhere near as scalable.
+
+L2TPNS uses the TUN/TAP interface provided by the Linux kernel to receive
+and send packets. Using some packet manipulation it doesn't require a
+single interface per connection, as l2tpd does.
+
+This allows it to scale extremely well to very high loads and very high
+numbers of connections.
+
+It also has a plugin architecture which allows custom code to be run
+during processing. An example of this is in the walled garden module
+included.
+
+
+Documentation is not my best skill. If you find any problems
+with this document, or if you wish to contribute, please email david@dparrish.com.
+
+
Installation
+Requirements
+
+
+- Linux kernel version 2.4 or above, with the Tun/Tap interface either
+compiled in, or as a module.
+
+- libcli 1.5 or greater.
You can get this from http://sourceforge.net/projects/libcli
+
+- The iproute2 user-space tools. These are used for throttling,
+so if you don't want to throttle then this is not required.
You
+may also need to patch tc and the kernel to include HTB
+support. You can find the relevant patches and instructions at http://luxik.cdi.cz/~devik/qos/htb/.
+
+
+
+Compile
+
+You can generally get away with just running make from the source
+directory. This will compile the daemon, associated tools and any modules
+shipped with the distribution.
+
+
Install
+
+After you have successfully compiled everything, run make
+install to install it. By default, the binaries are installed into
+/usr/sbin, the configuration into /etc/l2tpns, and the
+modules into /usr/lib/l2tpns.
+
+You will definately need to edit the configuration file before you start.
+See the Configuration section for more information.
+
+You should also create the appropriate iptables chains if you want to use
+throttling or walled garden.
+
+# Create the walled garden stuff
+iptables -t nat -N l2tpns
+iptables -t nat -F l2tpns
+iptables -t nat -A PREROUTING -j l2tpns
+iptables -t nat -A l2tpns -j garden_users
+# Create the throttling stuff
+iptables -t mangle -N l2tpns
+iptables -t mangle -F l2tpns
+iptables -t mangle -N throttle
+iptables -t mangle -F throttle
+iptables -t mangle -A PREROUTING -j l2tpns
+iptables -t mangle -A l2tpns -j throttle
+
+
+Running it
+
+You only need to run /usr/sbin/l2tpns as root to start it. It does
+not detach to a daemon process, so you should perhaps run it from init.
+
+By default there is no log destination set, so all log messages will go to
+stdout.
+
+
Configuration
+
+All configuration of the software is done from the files installed into
+/etc/l2tpns.
+
+l2tpns.cfg
+
+This is the main configuration file for L2TPNS. The format of the file is a
+list of commands that can be run through the command-line interface. This
+file can also be written directly by the L2TPNS process if a user runs the
+write memory command, so any comments will be lost. However if your
+policy is not to write the config by the program, then feel free to comment
+the file with a # at the beginning of the line.
+
+A list of the possible configuration directives follows. Each of these
+should be set by a line like:
+
+set configstring "value"
+set ipaddress 192.168.1.1
+set boolean true
+
+
+
+- debug (int)
+Sets the level of messages that will be written to the log file. The value
+should be between 0 and 5, with 0 being no debugging, and 5 being the
+highest. A rough description of the levels is:
+
+ - Critical Errors - Things are probably broken
+ - Errors - Things might have gone wrong, but probably will recover
+ - Warnings - Just in case you care what is not quite perfect
+ - Information - Parameters of control packets
+ - Calls - For tracing the execution of the code
+ - Packets - Everything, including a hex dump of all packets processed... probably twice
+
+Note that the higher you set the debugging level, the slower the program
+will run. Also, at level 5 a LOT of information will be logged. This should
+only ever be used for working out why it doesn't work at all.
+
+
+
+- log_file (string)
+This will be where all logging and debugging information is written
+to. This can be either a filename, such as /var/log/l2tpns, or
+the special magic string syslog:facility, where facility
+is any one of the syslog logging facilities, such as local5.
+
+
+
+- l2tp_secret (string)
+This sets the string that L2TPNS will use for authenticating tunnel request.
+This must be the same as the LAC, or authentication will fail. This will
+only actually be used if the LAC requests authentication.
+
+
+
+- primary_dns (ip address)
+Whenever a PPP connection is established, DNS servers will be sent to the
+user, both a primary and a secondary. If either is set to 0.0.0.0, then that
+one will not be sent.
+This sets the first DNS entry that will be sent.
+
+
+
+- secondary_dns (ip address)
+See primary_dns.
+
+
+
+- snoop_host (ip address)
+Whenever a user is intercepted, a copy of their traffic will be sent to this
+IP address, using the port specified by snoop_port. Each packet
+will be sent as UDP.
+
+
+
+- snoop_port (int)
+See snoop_host.
+
+
+
+- primary_radius (ip address)
+This sets the primary radius server used for both authentication and
+accounting. If this server does not respond, then the secondary radius
+server will be used.
+
+
+
+- secondary_radius (ip address)
+See primary_radius.
+
+
+
+- radius_accounting (boolean)
+If set to true, then radius accounting packets will be sent. This means that
+a Start record will be sent when the session is successfully authenticated,
+and a Stop record will be sent when the session is closed.
+
+
+
+- radius_secret (string)
+This secret will be used in all radius queries. If this is not set then
+radius queries will fail.
+
+
+
+- bind_address (ip address)
+When the tun interface is created, it is assigned the address specified
+here. If no address is given, 1.1.1.1 is used.
+If an address is given here, then packets containing user traffic should be
+routed via this address, otherwise the primary address of the machine.
+This is set automatically by the cluster master when taking over a failed
+machine.
+
+
+
+- cluster_master (ip address)
+This sets the address of the cluster master. See the Clustering
+section for more information on configuring a cluster.
+
+
+
+- throttle_speed (int)
+Sets the speed (in kbits/s) which sessions will be limited to. If this is
+set to 0, then throttling will not be used at all. Note: You can set this by
+the CLI, but changes will not affect currently connected users.
+
+
+
+- dump_speed (boolean)
+If set to true, then the current bandwidth utilization will be logged every
+second. Even if this is disabled, you can see this information by running
+the uptime command on the CLI.
+
+
+
+- setuid (int)
+After starting up and binding the interface, change UID to this. This
+doesn't work properly.
+
+
+
+- accounting_dir (string)
+If set to a directory, then every 5 minutes the current usage for every
+connected use will be dumped to a file. Each file dumped begins with a
+header, where each line is prefixed by #. Following the header is a single
+line for every connected user, fields separated by a space.
+The fields are username, ip, qos, uptxoctets, downrxoctets. The qos
+field is 1 if a standard user, and 2 if the user is throttled.
+
+
+
+
+- save_state (boolean)
+If set to true, a state file will be dumped to disk when the process dies.
+This will be restored on startup, loading all active tunnels and sessions.
+
+
+
+
+
+l2tpns.users
+
+This file's sole purpose is to manage access to the command-line
+interface. If this file doesn't exist, then anyone who can get to port
+23 will be allowed access without a username / password.
+
+If this is not what you want, then create this file and put in it a list of
+username / password pairs, separated by a :. e.g.:
+
+
+user.1:randompassword
+fred:bhPe4rD1ME8.s
+bob:SP2RHKl3Q3qo6
+
+
+Keep in mind that the password should be in clear-text. There is no user
+privilege distinction, so anyone on this list will have full control of the
+system.
+
+
l2tpns.ip_pool
+
+This file is used to configure the IP address pool which user addresses are
+assigned from. This file should contain either an IP address or a IP mask
+per line. e.g.:
+
+
+192.168.1.1
+192.168.1.2
+192.168.1.3
+192.168.4.0/24
+172.16.0.0/16
+10.0.0.0/8
+
+
+Keep in mind that L2TPNS can only handle 65535 connections per process, so
+don't put more than 65535 IP addresses in the configuration file. They will
+be wasted.
+
+Controlling the process
+
+A running L2TPNS process can be controlled in a number of ways. The primary
+method of control is by the Command-Line Interface (CLI).
+
+You can also remotely send commands to modules via the nsctl client
+provided. This currently only works with the walled garden module, but
+modification is trivial to support other modules.
+
+Also, there are a number of signals that L2TPNS understands and takes action
+when it receives them.
+
+
Command-Line Interface
+
+You can access the command line interface by telnet'ing to port 23. There is
+no IP address restriction, so it's a good idea to firewall this port off
+from anyone who doesn't need access to it. See l2tpns.users for information
+on restricting access based on a username and password.
+
+The CLI gives you real-time control over almost everything in
+the process. The interface is designed to look like a CISCO
+device, and supports things like command history, line editing and
+context sensitive help. This is provided by linking with the libcli library.
+
+After you have connected to the telnet port (and perhaps logged in), you
+will be presented with a prompt
l2tpns>
+
+You can type help to get a list of all possible commands, but this
+list could be quite long. A brief overview of the more important commands
+follows:
+
+
+- show session
+Without specifying a session ID, this will list all tunnels currently
+connected. If you specify a session ID, you will be given all information on
+a single tunnel. Note that the full session list can be around 185 columns
+wide, so you should probably use a wide terminal to see the list
+properly.
+The columns listed in the overview are:
+
+ | SID | Session ID |
+ | TID | Tunnel ID - Use with show tunnel tid |
+ | Username | The username given in the PPP
+ authentication. If this is *, then LCP authentication has not
+ completed. |
+ | IP | The IP address given to the session. If
+ this is 0.0.0.0, LCP negotiation has not completed. |
+ | I | Intercept - Y or N depending on whether the
+ session is being snooped. See snoop. |
+ | T | Throttled - Y or N if the session is
+ currently throttled. See throttle. |
+ | G | Walled Garden - Y or N if the user is
+ trapped in the walled garden. This field is present even if the
+ garden module is not loaded. |
+ | opened | The number of seconds since the
+ session started |
+ | downloaded | Number of bytes downloaded by the user |
+ | uploaded | Number of bytes uploaded by the user |
+ | idle | The number of seconds since traffic was
+ detected on the session |
+ | LAC | The IP address of the LAC the session is
+ connected to. |
+ | CLI | The Calling-Line-Identification field
+ provided during the session setup. This field is generated by the
+ LAC. |
+
+
+
+
+- show tunnel
+This will show all the open tunnels in a summary, or detail on a single
+tunnel if you give a tunnel id.
+The columns listed in the overview are:
+
+ | TID | Tunnel ID |
+ | Hostname | The hostname for the tunnel as
+ provided by the LAC. This has no relation to DNS, it is just
+ a text field. |
+ | IP | The IP address of the LAC |
+ | State | Tunnel state - Free, Open, Dieing,
+ Opening |
+ | Sessions | The number of open sessions on the
+ tunnel |
+
+
+
+
+- show pool
+Displays the current IP address pool allocation. This will only display
+addresses that are in use, or are reserved for re-allocation to a
+disconnected user.
+If an address is not currently in use, but has been used, then in the User
+column the username will be shown in square brackets, followed by the time
+since the address was used:
+
+IP Address Used Session User
+192.168.100.6 N [joe.user] 1548s
+
+
+
+
+- show radius
+Show a summary of the in-use radius sessions. This list should not be very
+long, as radius sessions should be cleaned up as soon as they are used. The
+columns listed are:
+
+ | Radius | The ID of the radius request. This is
+ sent in the packet to the radius server for identification. |
+ | State | The state of the request - WAIT, CHAP,
+ AUTH, IPCP, START, STOP, NULL. |
+ | Session | The session ID that this radius
+ request is associated with |
+ | Retry | If a response does not appear to the
+ request, it will retry at this time. This is a unix timestamp. |
+ | Try | Retry count. The radius request is
+ discarded after 3 retries. |
+
+
+
+
+- show running-config
+This will list the current running configuration. This is in a format that
+can either be pasted into the configuration file, or run directly at the
+command line.
+
+
+
+- show counters
+Internally, counters are kept of key values, such as bytes and packets
+transferred, as well as function call counters. This function displays all
+these counters, and is probably only useful for debugging.
+You can reset these counters by running clear counters.
+
+
+
+- write memory
+This will write the current running configuration to the config file
+l2tpns.cfg, which will be run on a restart.
+
+
+
+- snoop
+You must specify a username, which will be intercepted for the current
+session. Specify no snoop username to disable interception for the
+current session.
+If you want interception to be permanent, you will have to modify the radius
+response for the user. See Interception.
+
+
+
+- throttle
+You must specify a username, which will be throttled for the current
+session. Specify no throttle username to disable throttling for the
+current session.
+If you want throttling to be permanent, you will have to modify the radius
+response for the user. See Throttling.
+
+
+
+- drop session
+This will cleanly disconnect a session. You must specify a session id, which
+you can get from show session. This will send a disconnect message
+to the remote end.
+
+
+
+- drop tunnel
+This will cleanly disconnect a tunnel, as well as all sessions on that
+tunnel. It will send a disconnect message for each session individually, and
+after 10 seconds it will send a tunnel disconnect message.
+
+
+
+- load plugin
+Load a plugin. You must specify the plugin name, and it will search in
+/usr/lib/l2tpns for plugin.so. You can unload a loaded plugin with
+remove plugin.
+
+
+
+- set
+Set a configuration variable. You must specify the variable name, and the
+value. If the value contains any spaces, you should quote the value with
+double quotes (").
+
+
+
+- uptime
+This will show how long the L2TPNS process has been running, and the current
+bandwidth utilization:
+
+17:10:35 up 8 days, 2212 users, load average: 0.21, 0.17, 0.16
+Bandwidth: UDP-ETH:6/6 ETH-UDP:13/13 TOTAL:37.6 IN:3033 OUT:2569
+
+The bandwidth line contains 4 sets of values.
+UDP-ETH is the current bandwidth going from the LAC to the ethernet
+(user uploads), in mbits/sec.
+ETH-UDP is the current bandwidth going from ethernet to the LAC (user
+downloads).
+TOTAL is the total aggregate bandwidth in mbits/s.
+IN and OUT are packets/per-second going between UDP-ETH and ETH-UDP.
+
+These counters are updated every second.
+
+
+
+
+nsctl
+
+nsctl was implemented (badly) to allow messages to be passed to modules.
+
+You must pass at least 2 parameters: host and command. The
+host is the address of the L2TPNS server which you want to send the message
+to.
+Command can currently be either garden or ungarden. With
+both of these commands, you must give a session ID as the 3rd parameter.
+This will activate or deactivate the walled garden for a session
+temporarily.
+
+
Signals
+
+While the process is running, you can send it a few different signals, using
+the kill command.
+
+killall -HUP l2tpns
+
+
+The signals understood are:
+
+- SIGHUP - Reload the config from disk
+- SIGTERM / SIGINT - Shut down for a restart. This will dump the current
+state to disk (if save_state is set to true). Upon restart, the
+process will read this saved state to resume active sessions.
+This is really useful when doing an upgrade, as the code can change without
+dropping any users. However, if the internal structures such as
+sessiont or tunnelt change, then this saved state file
+will not reload, and none of the sessions will be recreated. This is bad.
+If these structures do change, you should kill the server with SIGQUIT,
+which won't dump the state.
+- SIGQUIT - Shut down cleanly. This will send a disconnect message for
+every active session and tunnel before shutting down. This is a good idea
+when upgrading the code, as no sessions will be left with the remote end
+thinking they are open.
+
+
+Throttling
+
+L2TPNS contains support for slowing down user sessions to whatever speed you
+desire. You must first enable the global setting throttle_speed
+before this will be activated.
+
+If you wish a session to be throttled permanently, you should set the
+Vendor-Specific radius value Cisco-Avpair="throttle=yes", which
+will be handled by the autothrottle module.
+
+Otherwise, you can enable and disable throttling an active session using
+the throttle CLI command.
+
+Throttling is actually performed using a combination of iptables and tc.
+First, a HTB bucket is created using tc (unless one is already created and
+unused).
+Secondly, an iptables rule is inserted into the throttle chanin in the
+mangle table so all packets destined for the user's IP address go into the
+HTB.
+
+You can check the packets being throttled using the tc command. Find the HTB
+handle by doing show session id in the CLI, next to the Filter
+Bucket tag. Then at the shell prompt, you can run:
+
+tc -s class ls dev tun0 | grep -A3 1:870
+class htb 1:870 root prio 0 rate 28Kbit ceil 28Kbit burst 15Kb cburst 1634b
+ Sent 27042557 bytes 41464 pkts (dropped 1876, overlimits 0)
+ lended: 41471 borrowed: 0 giants: 0
+ tokens: 3490743 ctokens: 353601
+
+
+Interception
+
+You may have to deal with legal requirements to be able to intercept a
+user's traffic at any time. L2TPNS allows you to begin and end interception
+on the fly, as well as at authentication time.
+
+When a user is being intercepted, a copy of every packet they send and
+receive will be sent wrapped in a UDP packet to the IP address and port set
+in the snoop_host and snoop_port configuration
+variables.
+
+The UDP packet contains just the raw IP frame, with no extra headers.
+
+To enable interception on a connected user, use the snoop username
+and no snoop username CLI commands. These will enable interception
+immediately.
+
+If you wish the user to be intercepted whenever they reconnect, you will
+need to modify the radius response to include the Vendor-Specific value
+Cisco-Avpair="intercept=yes". For this feature to be enabled,
+you need to have the autosnoop module loaded.
+
+
Authentication
+
+Whenever a session connects, it is not fully set up until authentication is
+completed. The remote end must send a PPP CHAP or PPP PAP authentication
+request to L2TPNS.
+
+This request is sent to the radius server, which will hopefully respond with
+Auth-Accept or Auth-Reject.
+
+If Auth-Accept is received, the session is set up and an IP address is
+assigned. The radius server can include a Framed-IP-Address field in the
+reply, and that address will be assigned to the client. It can also include
+specific DNS servers, and a Framed-Route if that is required.
+
+If Auth-Reject is received, then the client is sent a PPP AUTHNAK packet,
+at which point they should disconnect. The exception to this is when the
+walled garden module is loaded, in which case the user still receives the
+PPP AUTHACK, but their session is flagged as being a garden'd user, and they
+should not receive any service.
+
+The radius reply can also contain a Vendor-Specific attribute called
+Cisco-Avpair. This field is a freeform text field that most CISCO
+devices understand to contain configuration instructions for the session. In
+the case of L2TPNS it is expected to be of the form
+
+key=value,key2=value2,key3=value3,keyn=value
+
+
+Each key-value pair is separated and passed to any modules loaded. The
+autosnoop and autothrottle understand the keys
+intercept and throttle respectively. For example, to have
+a user who is to be throttled and intercepted, the Cisco-Avpair value should
+contain:
+
+intercept=yes,throttle=yes
+
+
+Plugins
+
+So as to make L2TPNS as flexible as possible (I know the core code is pretty
+difficult to understand), it includes a plugin API, which you can use to
+hook into certain events.
+
+There are a few example modules included - autosnoop, autothrottle and
+garden.
+
+When an event happens that has a hook, L2TPNS looks for a predefined
+function name in every loaded module, and runs them in the order the modules
+were loaded.
+
+The function should return PLUGIN_RET_OK if it is all OK. If it returns
+PLUGIN_RET_STOP, then it is assumed to have worked, but that no further
+modules should be run for this event.
+A return of PLUGIN_RET_ERROR means that this module failed, and
+no further processing should be done for this event. Use this with care.
+
+Every event function called takes a specific structure named
+param_event, which varies in content with each event. The
+function name for each event will be plugin_event,
+so for the event timer, the function declaration should look like:
+
+int plugin_timer(struct param_timer *data);
+
+
+A list of the available events follows, with a list of all the fields in the
+supplied structure:
+
+
+ | Event | Description | Parameters |
+ | pre_auth |
+ This is called after a radius response has been
+ received, but before it has been processed by the
+ code. This will allow you to modify the response in
+ some way.
+ |
+
+
+ - t - Tunnel ID
+ - s - Session ID
+ - username
+ - password
+ - protocol (0xC023 for PAP, 0xC223 for CHAP)
+ - continue_auth - Set to 0 to stop processing authentication modules
+
+ |
+
+ | post_auth |
+ This is called after a radius response has been
+ received, and the basic checks have been performed. This
+ is what the garden module uses to force authentication
+ to be accepted.
+ |
+
+
+ - t - Tunnel ID
+ - s - Session ID
+ - username
+ - auth_allowed - This is already set to true or
+ false depending on whether authentication has been
+ allowed so far. You can set this to 1 or 0 to force
+ allow or disallow authentication
+ - protocol (0xC023 for PAP, 0xC223 for CHAP)
+
+ |
+
+ | packet_rx |
+ This is called whenever a session receives a
+ packet. Use this sparingly, as this will
+ seriously slow down the system.
+ |
+
+
+ - t - Tunnel ID
+ - s - Session ID
+ - buf - The raw packet data
+ - len - The length of buf
+
+ |
+
+ | packet_tx |
+ This is called whenever a session sends a
+ packet. Use this sparingly, as this will
+ seriously slow down the system.
+ |
+
+
+ - t - Tunnel ID
+ - s - Session ID
+ - buf - The raw packet data
+ - len - The length of buf
+
+ |
+
+ | timer |
+ This is run every second, no matter what is happening.
+ This is called from a signal handler, so make sure anything
+ you do is reentrant.
+ |
+
+
+ - time_now - The current unix timestamp
+
+ |
+
+ | new_session |
+ This is called after a session is fully set up. The
+ session is now ready to handle traffic.
+ |
+
+
+ - t - Tunnel ID
+ - s - Session ID
+
+ |
+
+ | kill_session |
+ This is called when a session is about to be shut down.
+ This may be called multiple times for the same session.
+ |
+
+
+ - t - Tunnel ID
+ - s - Session ID
+
+ |
+
+ | radius_response |
+ This is called whenever a radius response includes a
+ Cisco-Avpair value. The value is split up into
+ key=value pairs, and each is processed through all
+ modules.
+ |
+
+
+ - t - Tunnel ID
+ - s - Session ID
+ - key
+ - value
+
+ |
+
+ | control |
+ This is called in whenever a nsctl packet is received.
+ This should handle the packet and form a response if
+ required.
+ |
+
+
+ - buf - The raw packet data
+ - l - The raw packet data length
+ - source_ip - Where the request came from
+ - source_port - Where the request came from
+ - response - Allocate a buffer and put your response in here
+ - response_length - Length of response
+ - send_response - true or false whether a response
+ should be sent. If you set this to true, you must
+ allocate a response buffer.
+ - type - Type of request (see nsctl.c)
+ - id - ID of request
+ - data - I'm really not sure
+ - data_length - Length of data
+
+ |
+
+
+ |
+
+Walled Garden
+
+Walled Garden is implemented so that you can provide perhaps limited service
+to sessions that incorrectly authenticate.
+
+Whenever a session provides incorrect authentication, and the
+radius server responds with Auth-Reject, the walled garden module
+(if loaded) will force authentication to succeed, but set the flag
+garden in the session structure, and adds an iptables rule to
+the garden_users chain to force all packets for the session's IP
+address to traverse the garden chain.
+
+This doesn't just work. To set this all up, you will need to create
+2 iptables chains on the nat table - garden and garden_users.
+
+iptables -t nat -N garden
+iptables -t nat -F garden
+iptables -t nat -N garden_users
+iptables -t nat -F garden_users
+
+
+You should add rules to the garden chain to limit user's traffic. For
+example, to force all traffic except DNS to be forwarded to 192.168.1.1, add
+these entries to your firewall startup script:
+
+iptables -t nat -A garden -p tcp --dport ! 53 -j DNAT --to 192.168.1.1
+iptables -t nat -A garden -p udp --dport ! 53 -j DNAT --to 192.168.1.1
+
+
+L2TPNS will add entries to the garden_users chain as appropriate.
+
+You can check the amount of traffic being captured using the following
+command:
+
+iptables -t nat -L garden -nvx
+
+
+Clustering
+
+Clustering is currently broken. But here's how it's supposed to work.
+
+
Performance
+
+Performance is great.
+
+I'd like to include some pretty graphs here that show a linear performance
+increase, with no impact by number of connected sessions.
+
+That's really what it looks like.
+
+
+David Parrish
+david@dparrsih.com
+
+