Networking

Common Lisp web servers

Recently at work my manager asked me to create a server solution that was both, “Fun, and easy for me to get things running quickly in.” Well, to me that’s a roundabout way of saying Common Lisp, so I started looking at possible solutions based on that platform. The solution will run as a webserver in a potentially high-demand scenario, so I figured it would pay to compare the options available to me.

Unfortunately, I did not have equal platforms on which to create these test results. If this were my only task this week, I would have setup a dedicated Linux machine and created identical tests for each setup described below. Because I do not have this luxury, some of the results may be due solely to where and how I ran them. So please take these numbers with a huge grain of salt, since you may see very different results in your own environment.

First, I wanted to test Apache serving static files, to get a feel for what Lisp is up against. On my MacBook Pro, running OS X 10.5.1 and Apache 2.2.6, Apache is able to serve a simple HTML file at roughly 4000 requests per sec. This was determined by repeatedly running ApacheBench, using 1000 accesses and 10 concurrent threads.

Next I tried Hunchentoot, serving the exact same file. I’m using SBCL 1.0.12.6 on Intel OS X (32-bit), with threading enabled. Note that threading is not officially supported on this platform, and was something I had to explicitly enable during the build process. What you get from MacPorts will not have threading enabled. It could be this that led to some of the results I saw.

Anyway, Hunchentoot in this scenario serves between 250-400 pages per second. Honestly, I’m not sure if that’s good or bad for a pure-Common Lisp implementation, but I did find myself a bit disheartened. Also, Hunchentoot on OS X does not handle worker thread exhaustion very well. I’m certain that part of this is due to the preliminary thread support on OS X, but then again, the same tests has troubles on Linux as well (see below). Using 1 concurrent thread in ApacheBench, Hunchentoot does fine. At 5 threads it does fine most of the time. At 10 threads, Hunchentoot has a tendency to drop requests, causing ApacheBench to stop working altogether. I had to try several times in a row before I could get all 1000 page loads to complete.

Next I tried Portable AllegroServe with the same SBCL version. I used paserve 1.2.47-vbz-0.1.4, a version custom-fitted for use with SBCL. The numbers here were a bit more promising: on average, 1000 pages/sec. The real advantage, though, is that listener thread exhaustion only causes AllegroServe to wait until another thread is available. I was able to run ApacheBench with 1000 concurrent connections and AllegroServe didn’t bat an eye.

Since I don’t have mod_lisp running on my OS X box, the next tests all used Linux virtual machines running under VMware Fusion. I used 64-bit CentOS 5 (x86-64) for all tests.

The Apache baseline is much slower in this case, as expected. Now Apache serves direct files at ~1200 pages/sec. Hunchentoot running behind mod_lisp serves pages at around 190 pages/sec. The nice thing, though, is that threading problems are non-existent. What’s strange, however, is that if I increase the number of concurrent access, performance actually goes up! At 100 concurrent accesses (as opposed to 10), Hunchentoot behind mod_lisp is able to serve pages at around 330 pages/sec. I have no idea why this would be the case. Also, I’m not doing static file tests this time, but using the dynamically generated default page for Hunchentoot.

If I access Hunchentoot in this setting directly, it doesn’t break off connections the way it does on OS X, but it does stall them. As a result, with 10 concurrent accesses, it serves up data at around 30-50 pages/sec. So using mod_lisp is definitely a huge win. Also, at 100 concurrent accesses, I was able to send the SBCL process into a 100% CPU burn, from which it took over a minute to recover — even after aborting ApacheBench. At 5 concurrent access, there was less contention and it served data at around 200 pages/sec.

For the last scenario, I switched to another Lisp implementation altogether, this time to Armed Bear Common Lisp. I figured I would try running a Java servlet under Tomcat, thereby leveraging the stability and great threading support I’ve come to expect from the Java VM. Also, I can easily integrate with other Java code we have in house, which is sure to become a requirement if the project succeeds. So I created another 64-bit CentOS 5 virtual machine and loaded Tomcat5 using JPackage. I also built my own tomcat-native package, so I could have Tomcat running as fast as possible. This setup used Sun’s JDK5 JVM, Tomcat 5.5, and Armed Bear version 0.0.10.

Rendering a dynamic Lisp page, the Tomcat solution sees speeds on average of 330 pages/sec, in the same scenario where Hunchentoot behind mod_lisp offers 30-50 pages/sec (due to thread contention). Also, concurrency with the Tomcat servlet is no problem at all; with 100 concurrent accesses, speed is simply not affected. Even at 1000 concurrent accesses, nothing really changes. This could be due to smart caching on Tomcat’s part, or just to great thread handling.

I did not try AllegroServe on Linux, because I think running on the Java VM will be smart for other reasons not relating to Common Lisp. I was a bit disheartened by Hunchentoot’s performance and threading behavior, but at the same was quite pleasantly surprised by Armed Bear. I do hope the author of ABCL continues to put effort into his project; and I’m crossing my fingers that basing code on a 0.0.10 release won’t end up being a huge mistake. If it does become a problem, though, I should be able to switch over to Groovy pretty quickly, without changing any of the framework at all. It’s just that using Common Lisp would make this task such a joy.

|

Script of the week: redirect

This week’s script of the week is so simple, it doesn’t really deserve to be called a script. But since it’s highly useful and comes as a surprise to many people that it can be done so easily, here it is.

The purpose of this script is to create momentary TCP routes. TCP routing is also called Layer 4 routing. That is, one machine momentarily serves as a transparent gateway between two TCP ports on two other machines. The advantages to layer 4 routing are:

  1. It’s “port to port” (you aren’t opening up subnets to each other, or even whole machines).
  2. It doesn’t require complicated routing tables entries, or IP forwarding, or NAT.
  3. It can be done entirely in user space. No strange kernel drivers required!

Here’s an example: Let’s say you use a web server sitting on a private network which you access over VPN. You can see the server just fine by typing it’s address in your web browser. One day, however, you find a bug on the server, but it only happen on that server like for your friend — who knows about such servers — to see what’s happening, but you obviously can’t grant him access to your secured network.

What would be really cool is if your friend could connect to your machine instead, and have your machine transparently proxy the connection into the VPN and over to that web server. It would also proxy responses back, so that from your friend’s point of view: your machine becomes the web server for as long as you keep the link up.

Here’s the command to do this, assuming I expose port 8080 on my machine for my friend to connect to, and I’m linking him to port 80 on the VPN’s web server:

$ tcpserver <MY-PUBLIC-IP> 8080 nc <VPN-WEB-SERVER-IP> 80

Did I mention that this doesn’t even require root privileges to work?

Note: If you have the socat utility installed, things get even simpler. In that case, the above command is just this:

$ socat tcp-listen:8080 tcp:<VPN-WEB-SERVER-IP>:80

Now you have a transparent route from port 8080 on your machine to your secured web server. After your friend is done checking things out, just cancel the command and the tunnel is destroyed. This is the best way I can think of to temporary and easily create transparent tunnels into otherwise inaccessible networks.

For this scriptlet to work, you’ll need ucspi-tcp installed (for the tcpserver command), and netcat, which comes pre-installed on OS X 10.5.

|

Hunchentoot: Persisting across reboots

In my earlier article on running Hunchentoot behind Apache, I mentioned that it would not be very difficult to have Common Lisp persist your runtime state across a system reboot. Well, after a bit of work, I now have that support available. I’ve revised the article to reflect these changes, so please read there for more information!

|

A quick Hunchentoot primer

I wrote yesterday about setting up Hunchentoot, a Common Lisp web server running behind Apache, for rendering dynamic web pages in Lisp. What I neglected to mention was how one goes about coding such pages. Fortunately, that's the easiest part of all, so I wanted to provide a very short primer on getting your first Lisp web pages up and running. Read More...
|

Running Common Lisp behind Apache

It's hard for me to think of a more ideal platform for web design than Common Lisp. Imagine having a system that runs indefinitely — with the ability to "snapshot" its running state and restore exactly where you left off after reboot — where updates can be applied live at a functional-level granularity, from anywhere. Oh, and let's not forget the remote debugging and inspection capabilities! And I thought ASP.NET was nice.

Where Lisp lacks today is primarily in easy to use, pre-packaged services. One of these is getting it to run behind Apache, which although easy to do, took a bit of figuring out from several different web pages. Hopefully I can share how simple it is to get such a system running, so you can try out this highly understated environment for yourself. Read More...
|

Life and times of a TCP packet

Have you ever wondered how data reaches your computer from all over the world as you browse the Internet? You may have heard of TCP/IP, but what exactly is it doing to reach that single Web server over in France, all the way from the United States? How does the information reach you?

Today's article examines how a single connection works, from my computer on the island of Grenada to another computer sitting in southern France — in this case, the website of my favorite soap company, Marius Fabre. Read More...
|

Serving up Mercurial using mod_python

The following article resulted from several hours of battling with SELinux and Apache, attempting to find some way of serving up my Mercurial repository (now at http://hg.newartisans.com) over HTTP. Now I'm happy to bring you the fruits of that research, even though I'm still getting errors from Mercurial when trying to push (I'm using ssh at the moment). More to come on that front later... Read More...
|

Using Archiveopteryx on the Mac

I recently discovered an IMAP mail storage server called Archiveopteryx, which is able to store virtually unlimited amounts of e-mail in a PostgreSQL database. I can't say enough good things about it. My article describes how to get up and running with Archiveopteryx on a Mac OS X or a Linux machine. I use it for keeping my private mail on my MacBook Pro laptop. Read More...
|

OpenSSH connection mastering

I just discovered a very cool feature of SSH today: control mastering. It lets you multiplex a single ssh connection so you don't have to open multiple TCP connections to the remote host; instead, all your SSH/SCP commands "share" the initial connection. This speeds up subsequent connections to the same host, and also means you don't have to enter your password more than once for hosts who don't know your public key yet. I use this feature to implement a script for setting up new remote accounts. Read More...
|

How to administer OpenVPN

Today's article describes how to administrate OpenVPN on a Debian GNU/Linux server. It does not cover installing a new OpenVPN service from scratch, since that is already covered in the official OpenVPN 2.0 HOWTO. In particular, this document covers:

  1. Logging in via OpenSSH to administrate the system.
  2. Creating X.509 certificates for new OpenVPN users.
  3. Installing the OpenVPN client on a user's machine.
  4. Re-configuring OpenVPN and restarting the daemon.
  5. Re-installing OpenVPN on a new Debian GNU/Linux server, in case the old server dies or is compromised.
If you haven't installed OpenVPN on your server yet, please visit the official HOWTO and complete the steps there. Then you can return to this document. I originally wrote this to show co-administrators how to work with an already-running OpenVPN installation. Read More...
|

Neat tricks with iptables

The past few months have seen me digging deep into the world of TCP/IP and firewalls. It has been a fascinating journey into packet queueing and TCP headers, three-way handshakes and ICMP broadcasts.

The result of this research has been the ongoing creation of a firewall to protect my laptop against open networks, and my Internet server from port scanning and DoS attacks. I'm pretty certain I haven't even scratched the surface yet, but I have found some settings to protect against the most common attacks. In this article I summarize the major pieces of my new firewall, and the logic behind it. Read More...
|

Using local DNS caching

I’ve found a very easy way to use local DNS caching on the Mac: simply setup Internet sharing from one device you don’t use, to another you don’t use. Since I almost always get my Internet access through wireless, I’ve setup my system to share my Fireware port to anyone connected to my Ethernet port.

Although this sharing setup doesn’t do any sharing, what it does do is to cause OS/X to run a local DHCP server and a local DNS server. This local DNS server takes its nameserver addresses from your current Internet configuration (in my case, wireless), so everything is automatically setup to cache DNS from the nameservers you’re actually using.

The only thing necessary to do is to change /etc/resolv.conf to point at your new local nameserver. Using the Network Preferences Pane, find out what your local Ethernet address is (to use my example; you’ll have to find the IP of the interface you’re sharing from). Now edit /etc/resolv.conf so it looks something like this:

nameserver 192.168.2.1

In my case, my local Ethernet interface gets set to 192.168.2.1 when I’m using sharing. You may need to setup a cron job (check out the utility CronniX) in order for your resolv.conf to get overwritten each time with this setting.

Now site back and enjoy the added speed of cached DNS! This is especially helpful on connections that drop packets a lot, since I find a great number of the “pauses” in my Internet usage all relate to lagged out DNS lookups.

|
© 2008 John Wiegley