Previous Page
Next Page

6.6. The stunnel Program

One problem with the examples we have looked at so far is that the applications are all SSL-aware. That is, they have support for SSL compiled into the application. We would like a way to use SSL to secure applications that know nothing of SSL.

Fortunately, there is software that does exactly that: stunnel. The stunnel program provides a secure tunnel between two applications that are not SSL-aware or between two applications only one of which is SSL-aware. The program is often used, for instance, to secure POP3, IMAP, NNTP, or SMTP servers without changing the code of the servers themselves.

The stunnel program depends on the OpenSSL library to provide its SSL functionality. Its role is to provide a convenient tunnel endpoint for applications that have no SSL capability of their own. This is illustrated in Figure 6.24, which shows stunnel connecting (A) two SSL-unaware applications and (B) an SSL-unaware application with an application that is SSL-aware.

Figure 6.24. Using stunnel to Connect Applications


Using stunnel

Let's try some concrete examples of the two situations shown in Figure 6.24. For our SSL-unaware client, we'll use netcat. For our SSL-aware server, we'll use the SSL echo server that we developed in the previous section. For our SSL-unaware server, we'll use a shell script that implements a simple echo server (Figure 6.25).

Figure 6.25. A Simple Echo Server


To form an SSL tunnel between netcat and echoit, we need two stunnel processesone for each end of the tunnel, just as in Figure 6.24 (A). Let's start with the server end of the tunnel. We use the stunnel configuration file shown in Figure 6.26.

Figure 6.26. The Server's stunnel Configuration File


3

The cert parameter points to the server's certificate. The server will send this certificate to the client in the Certificate handshake.

58

These lines establish a chroot jail for stunnel to run in. Everything that stunnel needs to run must be in this jail, because nothing outside the /var/tmp/stunnel subdirectory can be accessed by stunnel or any applications it calls.

1011

The ciphers parameter lists the ciphers that the server will support. We have also specified the DONT_INSERT_EMPTY_FRAGMENTS option to prevent the server from including empty fragments in the application records.

1316

These lines define a service and tell stunnel how to handle requests for it. The [echo] names the service and has no real significance other than to serve as a delimiter between services and as a tag for logging diagnostics. The accept parameter tells stunnel to listen for requests for the echo service on TCP port 5001. The exec parameter tells stunnel to exec echoit when there is a connection on port 5001. Notice that the path for echoit is relative to the jail. Because echoit takes no arguments, the execargs parameter lists only arg0, the application name.


The configuration file for the client stunnel is similar, as shown in Figure 6.27.

Figure 6.27. The Client's stunnel Configuration File


89

Instead of specifying a certificate to send as we did in stunnel.server, we request that stunnel verify the server's certificate, using the root certificate (rootcert.pem) from our mini-CA, Text CA.

13

We inform stunnel that this invocation will be playing the client's role.

1523

We define three services: echo-r for remote echo, echo-l for local echo, and sslecho. The [echo-r] service accepts connections from an SSL-unaware application on TCP port 5000 and connects to its peer stunnel running on host linux on port 5001. The [echo-l] service is similar except that it accepts connections on port 5002 and connects to an stunnel running on the same machine.


The utility of this last service is limited, of course. Local services like this are useful for testing before moving the server application to a remote machine.

We will use the [sslecho] service to connect to our sslecho server on linux, by connecting to port 5003 on the local machine.

We now have everything we need to try the two situations in Figure 6.24. First, we'll use stunnel to provide a secure connection from a netcat session on bsd to the echoit script on linux. We begin by starting stunnel on both machines. On bsd, we start the client side:

bsd# stunnel stunnel.client

On linux, we start the server side. We also start sslecho, for our next demonstration:

linux:# stunnel stunnel.server
linux:# sslecho

In another window on bsd, we use netcat to connect to the client side of the tunnel. We type two lines and note that they are echoed back to us by echoit:

bsd:~
$ nc -v localhost 5000
localhost [127.0.0.1] 5000 (commplex-main) open
Hello echoit on linux!
Hello echoit on linux!
bye
bye
^C punt!
bsd:~

Although not shown here, an ssldump TRace taken during the session shows the two stunnels connecting as usual and passing the application data.

Using the same client stunnel invocation, we can also demonstrate an SSL-unaware application talking to one that is SSL-aware:

bsd:~
$ nc -v localhost 5003
localhost [127.0.0.1] 5003 (?) open
Hello from bsd
Hello from bsd
^C punt!
bsd:~

Notice that this is essentially the same as the echoit demonstration except that we connect to local port 5003. That causes the client stunnel on bsd to connect directly to the sslecho program running on linux.

Building a VPN with stunnel

SSL is nominally a protocol between two applications, and the examples we have explored so far all reflect that. It is possible, however, to use stunnel to connect two networks. To make things clear, Figure 6.28 shows a portion of our test network. We are going to connect the 192.168.123.0/24 network segment with the 192.168.122.0/24 network segment by establishing a PPP tunnel over SSL between hosts bsd and linux. We have labeled the PPP connection "virtual" because the actual flow of the PPP data is through the SSL tunnel over the 172.30.0.0/24 network segment, as shown by the shaded path.

Figure 6.28. Tunneling PPP over SSL


To do this, we'll tunnel the PPP connection through stunnel in a way similar to Figure 6.24 (A). First, we add a new service to our stunnel.client configuration file:

[sslvpn]
accept = 5010 # local stunnel is listening here
connect = linux:5011 # remote stunnel is listening here

Rather than use the same server configuration file, we simplify things a bit by not arranging for a chroot jail. Our new stunnel server configuration file is as shown in Figure 6.29.

Figure 6.29. The stunnel Configuration File for the VPN Server


38

These lines are just like those in stunnel.server, except that we don't create the chroot jail.

1013

We define our new service. When the peer stunnel on bsd connects to port 5011, we will exec the PPP daemon, pppd. The notty parameter tells pppd not to use a serial port, as it normally would, but to communicate with its peer PPP process through STDIN and STDOUT.


The pppd daemon does this by opening a pseudoterminal (see [Stevens 1992]), which allows the rest of the daemon to believe that it is communicating over a serial port.

The 10.0.0.4:10.0.0.1 tells pppd that the local interface for the PPP connection will have address 10.0.0.4 and that the remote interface's address is 10.0.0.1.

This is all the configuration that is necessary on linux. On bsd, however, things are more complicated. Most UNIX implementations implement PPP in the kernel, as Linux does. FreeBSD, however, uses a user-mode PPP process called ppp. The TCP/IP stack is connected to this process by the tunnel driver, just as gtunnel is. The ppp program is very flexible: It can handle ISDN, PPPoE, NAT, proxy ARP, and several other protocols in addition to PPP. This flexibility requires the use of configuration files, which complicates our side of the VPN a little. In a manner reminiscent of the stunnel configuration file, one can define multiple ppp services and then refer to them by name. For simplicity, we define only the sslvpn service, although the same configuration file could have services for any of the protocols supported by ppp. Our ppp configuration file is given in Figure 6.30.

Figure 6.30. The ppp Configuration File


2

This line merely sets the logging level. Here, we are requesting a great deal of information, which is handy for debugging but is probably overkill in a production environment.

48

These are the parameters for our PPP session. Line 5 tells ppp to communicate over TCP port 5010 on the local host rather than the usual serial port. The set dial command is used to tell ppp to initiate a session with its peer and to specify the dialing chat script. Because we won't be dialing anything, we do not specify an argument. By setting the timeout to 0, we disable the idle timeout and keep the PPP connection up even if there is no traffic. Finally, the set ifaddr command sets the addresses of the local and peer interfaces.


There is one more configuration file to deal withthe ppp.linkup script (Figure 6.31). This file is executed by ppp when the PPP link comes up. We will use this script to add a route to our peer network. We could do this by hand, as we will on the linux side, but the script is a handy way of adding the route automatically.

Figure 6.31. Adding a Route When the Link Comes Up


Linux offers a similar capability with the ip-up script. The details of how it works depend on which of the various Linux distributions is in use. We will add the route manually on linux to avoid bogging down in these details and to make explicit the need to add the route.

The HISADDR in line 2 will be replaced by the peer's interface address. In this case, we already know it, of course, but sometimes it's not known until the link comes up.

We now have everything in place to establish our VPN and try it out. First, we start stunnel on linux, where it will listen for a connection from its peer stunnel on bsd:

linux:# /usr/local/sbin/stunnel stunnel.vpn

Next, we start stunnel and ppp on bsd. When ppp starts, it connects to stunnel, which establishes the SSL connection with its peer on linux. When the stunnel on linux sees the connect, it starts the pppd daemon:

bsd# stunnel stunnel.client
bsd# ppp -background sslvpn
Working in background mode
Using interface: tun0
PPP enabled

We see from the PPP enabled that the PPP tunnel has been established. Although nothing in the screen capture shows it, this PPP tunnel is being carried inside the SSL tunnel, an example of tunneling an interface-layer protocol inside a transport-layer protocol.

We can now test the tunnel. On linux, we do this by adding a route to the 192.168.123.0/24 network and pinging bsd on its 192.168.123.1 interface:

linux: # route add -net 192.168.123.0/24 gw 10.0.0.1
linux: # ping 192.168.123.1
PING 192.168.123.1 (192.168.123.1) from 10.0.0.4 :
    56(84) bytes of data.
64 bytes from 192.168.123.1: icmp_seq=1 ttl=64 time=2.64 ms
64 bytes from 192.168.123.1: icmp_seq=2 ttl=64 time=2.48 ms
64 bytes from 192.168.123.1: icmp_seq=3 ttl=64 time=2.47 ms
64 bytes from 192.168.123.1: icmp_seq=4 ttl=64 time=2.42 ms
...

On bsd, we do the same except that we don't need to set the route, because it was already supplied by the ppp.linkup file:

bsd# ping 192.168.122.1
PING 192.168.122.1 (192.168.122.1): 56 data bytes
64 bytes from 192.168.122.1: icmp_seq=0 ttl=64 time=7.063 ms
64 bytes from 192.168.122.1: icmp_seq=1 ttl=64 time=2.354 ms
64 bytes from 192.168.122.1: icmp_seq=2 ttl=64 time=2.283 ms
64 bytes from 192.168.122.1: icmp_seq=3 ttl=64 time=2.339 ms
...

It is worth considering what the data in our tunnel looks like. As we noted, we are tunneling an interface-layer protocol, which in turn carries higher-layer protocols, inside a transport-layer protocol. Figure 6.32 shows the packet layout for the pings on our VPN.

Figure 6.32. SSL VPN Packet Encapsulation


Here is one ping captured by ssldump during the test of our VPN. The first two bytes are the PPP header: The 7e is a flag byte that separates PPP frames, and the 21 is the PPP protocol number indicating that this PPP frame contains an IP packet. The next 20 bytes, beginning with the 45, are the IP header. The following byte, in boldface, is the start of the ICMP packet. Notice that it is 08 (echo request) in the first SSL record, and 00 (echo reply) in the second. The last 3 bytes are the PPP trailer comprising a 2-byte CRC and another flag byte.

1 40 72.1196 (11.9504)  S>CV3.0(112)  application_data
  ---------------------------------------------------------------
  7e 21 45 00 00 54 72 7b 40 00 40 01 82 80 0a 00    ~!E..Tr{@.@.....
  00 04 c0 a8 7b 01 08 00 56 21 63 11 01 00 3c 5a    ....{...V!c...<Z
  90 3e 7a 31 0c 00 08 09 0a 0b 0c 0d 0e 0f 10 11    .>z1............
  12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21    .............. !
  22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31    "#$%&'()*+,-./01
  32 33 34 35 36 37 92 51 7e                         234567.Q~
  ---------------------------------------------------------------
1 41 72.1209 (0.0013)  C>SV3.0(112)  application_data
  ---------------------------------------------------------------
  7e 21 45 00 00 54 36 fa 40 00 40 01 be 01 c0 a8    ~!E..T6.@.@.....
  7b 01 0a 00 00 04 00 00 5e 21 63 11 01 00 3c 5a    {.......^!c...<Z
  90 3e 7a 31 0c 00 08 09 0a 0b 0c 0d 0e 0f 10 11    .>z1............
  12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21    .............. !
  22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31    "#$%&'()*+,-./01
  32 33 34 35 36 37 94 7a 7e                         234567.z~
  ---------------------------------------------------------------

As shown in Figure 6.32, these packets are 169 bytes, excluding the Ethernet framing, compared to 84 bytes for a normal ping packet. All VPNs have some overhead, of course, but here we have doubled the packet size. Even in the best case of a full-size Ethernet frame, we have about 85 bytes out of 1,500 devoted to VPN overhead.

Ethernet is not the only link-layer protocol, of course, and some of the others have larger MTUs. For these protocols, the size overhead is not as onerous. Nevertheless, Ethernet is, at the time of this writing, by far the most common.

The real overhead of our tunnel is not size, however, but time. Notice that the ping times are about 2.32.4 milliseconds. Recall that these times were measured on a LAN, not on the Internet. For comparison, here is a ping between bsd and linux that does not go through the tunnel:

bsd# ping linux
PING linux.jcs.local (172.30.0.4): 56 data bytes
64 bytes from 172.30.0.4: icmp_seq=0 ttl=64 time=0.186 ms
64 bytes from 172.30.0.4: icmp_seq=1 ttl=64 time=0.214 ms
64 bytes from 172.30.0.4: icmp_seq=2 ttl=64 time=0.214 ms
64 bytes from 172.30.0.4: icmp_seq=3 ttl=64 time=0.212 ms
...

We see that the ping times in the tunnel are an order of magnitude greater than those that don't go through the tunnel. It's not difficult to understand the difference. In addition to the normal IP/ICMP processing, we have the SSL/stunnel overhead, including the expensive encryption, and the overhead of the PPP processes.

In one sense, we tested our VPN in a way that is most favorable to it. That's because the tunnel was running on a LAN (see Tip 12 of ETCP), and we weren't running TCP over it. If we had established our tunnel between two machines on the Internet and made a TCP connection between them over the tunnel, we would have seen even poorer performance: We would have been tunneling one TCP connection over another, and the retransmission strategies of the two TCPs would have interfered with each other. In general, it's not a good idea to tunnel one reliable protocol inside another, because both are trying to provide reliability, and they can get in each other's way.


Previous Page
Next Page