Previous Page
Next Page

4.8. gtunnel

Sometimes, we want to build a type of tunnel that is not supported by the operating system, or we need to have finer control of the tunnel parameters than the operating system provides. In these cases, it is useful to be able to do our own encapsulation of packets as they leave the TCP/IP stack. In principle, we could do this by modifying the stack itself, but doing so has several disadvantages. First, it requires an intimate knowledge of the operating system's kernel, its data structures, and its support routines. More serious, unless the operating system's vendor can be persuaded to incorporate the changes into the base system, the modifications become a maintenance problem because they have to be reapplied with every new release of the operating system.

Fortunately, many operating systemsincluding FreeBSD, Linux, and Solarisprovide a facility that allows us to do the encapsulation easily in a user-space program. Our examples will use the FreeBSD tunnel driver, but Linux and Solaris offer essentially identical services through their TUN/TAP drivers. We refer to all these facilities as "tunnel drivers."

The tunnel drivers appear to the TCP/IP stack to be device drivers for a network interface device, such as an Ethernet card. Instead of encapsulating packets from the TCP/IP in, say, an Ethernet frame and passing the result to a physical device, the tunnel drivers deliver them to a user-space program, which performs further processing on the packet and then delivers it to the appropriate output device. Similarly, the user-space program can pass packets to the tunnel driver for delivery back up the TCP/IP stack.

A typical use of a tunnel driver is illustrated in Figure 4.63, which shows a user application talking to the TCP/IP stack in the normal way. The packets that result move down the stack to the tunnel driver, which delivers them to a user-space program, labeled gtunnel, that performs further processing and delivers them to the output device.

Figure 4.63. gtunnel and the Tunnel Driver


A typical use of the tunnel driver is illustrated by the FreeBSD PPP implementation. Rather than implement PPP in the kernel, as most other systems do, FreeBSD provides PPP functionality with a normal user-space application program called pppd. The pppd program communicates with the TCP/IP stack through the tunnel driver and with the outside world, typically, through a serial port. Thus, pppd encapsulates IP packets from the stack in PPP frames and delivers them to a serial port for transmission to the remote system. PPP frames from the remote system arrive at the serial port and are read by pppd, which strips off the PPP framing and delivers the resulting IP packet to the TCP/IP stack through the tunnel driver.

Building a gtunnel.c Skeleton

We can write our own programs that provide whatever processing and encapsulation are needed to implement a particular type of tunnel. To make this easier, we use the gtunnel.c skeleton shown in Figure 4.64. By providing the startup, inbound, and outbound functions, we can flesh gtunnel out to a complete tunnel implementation.

Figure 4.64. gtunnel Skeleton


As we see from Figure 4.64, our skeleton provides merely the main function. We build a complete tunnel implementation by providing a startup function (line 12) to perform any necessary initialization and set up communications with our peer, as well as outbound (line 30) and inbound (line 32) functions to handle packets from the stack and our peer.

The main function

1

We include the etcp.h header file, which contains prototypes for the error function, the definition for the INIT macro, and various other headers files needed for networking applications.

410

These lines contain the read masks for the select call, the file descriptors for the input, output, and tun devices, and a working variable for return codes.

112

The INIT macro sets the program name for use by the error function. After INIT, we call the startup function to perform any required initialization. We'll see an example of this function shortly. We pass the argc and argv variables to startup so that it can examine the program's command line parameters, and it returns file descriptors for the inbound and outbound functions. We might think that one file descriptor would suffice for both, but as we will see in Chapter 7, we sometimes want to talk to another process and therefore need a pair of pipes.

1315

We open the tunnel driver. When we read from the driver, we are reading an IP packet from the stack. When we write to the driver, we are writing an IP packet to the stack.

1619

Next, we perform the normal setup for the select call. We are interested in read events from the tunnel driver and our external peer.

20

We enter the program's event loop on this line. We loop, waiting for input from the stack or our peer, process the input, and pass it on.

2228

We ask select to notify us when input is available from either the tunnel driver or our peer. Any errors from select are noted, but we continue processing.

2930

When we get input from the stack, we call outbound to read, process, and forward the packet to our peer.

3132

When we get input from our peer, we call inbound to read, process, and forward the packet up the TCP/IP stack.


Building an IP-in-IP Tunnel with gtunnel

To illustrate the use of gtunnel, let's build an IP-in-IP tunnel like the one in Section 4.2, using the network configuration shown in Figure 4.65. The IP-in-IP tunnel will be built between the 192.168.1.1 interface on bsd and the 192.168.2.1 interface on laptop, with both interfaces assigned to the tunnel driver. The packets will actually follow the shaded path through the usual 172.30.0.0/24 network.

Figure 4.65. Network Diagram for an IP-in-IP Tunnel


We begin fleshing out gtunnel by including extra header files (Figure 4.66) that we will need and by adding a define and global definition to the start of gtunnel.c. We will call our new file ipip.c.

Figure 4.66. Header Files for ipip.c


4

Our SOCKADDR define is simply a convenience macro to cast the sockaddr_in structures to the generic sockaddr structure expected by the BSD networking API.

5

We also define the global remote variable to hold the IP address of our peer.

In the startup function (Figure 4.67), we merely set our peer's IP address and allocate a socket for our communications with the peer. Our program will be invoked as

ipip peer_name_or_address

Figure 4.67. The startup Function



The startup Function

1012

We begin by allocating a socket. Because we want our outer IP packets to have a protocol type of IP-in-IP (4), we set the socket type to SOCK_RAW and the protocol type to IPPROTO_IPIP.

1321

Next, we fill in remote with our peer's IP address. If the address is given as a numerical address, the inet_aton function will convert it to a 32-bit integer and store it in remote. Otherwise, we use gethostbyname to look up our peer's address.

22

Finally, we set both the file descriptors to the socket.
When the select call returns with a read event on the tun device, we call outbound (Figure 4.68) to process the packet.

Figure 4.68. The outbound Function



The outbound Function

2830

We begin by reading the packet from the tunnel driver. Assuming that we're running on bsd, this will be an IP packet with a source address of 192.168.1.1 and a destination address of 192.168.2.1.

3134

We process this packet by simply sending it to our peer. As a result of the call to sendto, the TCP/IP stack will add an outer IP header with a source address of 172.30.0.1 and a destination address of 172.30.0.6, again assuming that we are running on bsd.

The inbound function (Figure 4.69) is only slightly more complicated. Because we specified SOCK_RAW for our socket, the packet will come to us with the outer IP header still in place. We must remove this header before sending the inner IP packet up the stack to the user application.

Figure 4.69. The inbound Function



The inbound Function

4145

First, we read the packet from our peer as we normally would in any TCP/IP program. This packet will have the format shown in Figure 4.4.

4647

We strip off the outer IP header by first setting ip to point at the outer packet and then incrementing the pointer by the number of bytes in the outer header. After line 47, ip points to the inner IP header.

4951

We send the entire inner IP packet up the stack by writing it to the tun device. Notice that we get the size of the packet from the ip_len field of the IP header.

We can test our IP-in-IP tunnel by configuring the tun interfaces and starting ipip on bsd and laptop. For example, on bsd, we configure the tunnel interface using the normal ifconfig command and then start ipip:

bsd# ifconfig tun0 192.168.1.1 192.168.2.1 up
bsd# ./ipip laptop

In another window on bsd, we test our tunnel by pinging the remote interface:

$ ping 192.168.2.1
PING 192.168.2.1 (192.168.2.1): 56 data bytes
64 bytes from 192.168.2.1: icmp_seq=0 ttl=64
 time=0.466 ms
64 bytes from 192.168.2.1: icmp_seq=1 ttl=64
 time=1.464 ms
64 bytes from 192.168.2.1: icmp_seq=2 ttl=64
 time=0.454 ms
64 bytes from 192.168.2.1: icmp_seq=3 ttl=64
 time=0.523 ms
64 bytes from 192.168.2.1: icmp_seq=4 ttl=64
 time=0.405 ms
^C
--- 192.168.2.1 ping statistics ---
5 packets transmitted, 5 packets received, 0%
 packet loss
round-trip min/avg/max/stddev = 0.405/0.662/1.464
/0.403 ms


If we examine the tcpdump capture of one of these pings, we see the expected encapsulation. Except for the addresses, it is identical to that from Section 4.2 (the outer IP header is set in boldface):

1   16:47:06.482086 172.30.0.1 > 172.30.0.6:
    192.168.1.1 > 192.168.2.1: icmp: echo request (ipip-proto-4)
1.1    4500 0068 17db 0000 4004 0a74 ac1e 0001    E..h....@..t....
1.2    ac1e 0006 4500 0054 17da 0000 4001 de7c    ....E..T....@..|
1.3    c0a8 0101 c0a8 0201 0800 b033 463e 0000    ...........3F>..
1.4    4aef 3041 945a 0700 0809 0a0b 0c0d 0e0f    J.0A.Z..........
1.5    1011 1213 1415 1617 1819 1a1b 1c1d 1e1f    ................
1.6    2021 2223 2425 2627 2829 2a2b 2c2d 2e2f    .!"#$%&'()*+,-./
1.7    3031 3233 3435 3637                        01234567

We've seen that by adding as little as 52 lines of code to our gtunnel skeleton, we can build a functional tunnel. Obviously, we could improve our tunnel by including code to track the tunnel health, to worry about MTUs, to provide diagnostics and other features, and so on (see Exercises 4.6 and 4.7, for example). The point is that gtunnel provides an infrastructure on which we can build arbitrarily complex tunnels.


Previous Page
Next Page