Chapter 4. Networking

Hacks #45-53

There was once a time when a network admin was a person who spent all of his time trying to figure out how to make machines talk to each other over a network. It seems that lately, much of a network admin’s time is spent trying to figure out how to restrict access to their machines via the network, thus keeping out the undesirables while still allowing legitimate traffic to pass through.

Fortunately, the netfilter firewall in Linux provides a very flexible interface to the kernel’s networking decisions. Using the iptables command, you can create firewall rules that let you create a rich and very flexible access policy. It can not only match packets based on port, interface and MAC addresses, but also on data contained within the packet and even by the rate that packets are received. This information can be used to help weed out all sorts of attempted attacks, from port floods to virii.

But locking users out isn’t nearly as much fun as connecting users together. After all the whole point of a computer network is to allow people to communicate with each other! We’ll take a look at some more unusual methods for controlling the flow of network traffic, from the remote port forwarding to various forms of IP tunnelling. By the time we’ve explored IP encapsulation and user space tunnels like vtun, we’ll see how it is possible to build networks on top of the Internet that behave in all sorts of unexpected and surprisingly useful ways.

Creating a Firewall from the Command Line of any Server

You don’t have to have a dedicated firewall to benefit from using iptables

The netfilter firewall (available in Linux 2.4 and later) allows for very flexible firewall manipulation from the command line. Using iptables can take a while to get used to, but it allows for a very expressive syntax that lets you create complex (and hopefully useful ;) firewall rules.

Even if your machine isn’t a “real” firewall (that is, it only has one network interface and isn’t protecting other machines) the filter functionality can be very useful. Suppose you want to allow telnet access to this machine (just in case something happens to ssh or its libraries) but don’t want to permit it from just anywhere on the Net. You could use a tcpwrapper (by populating /etc/hosts.allow and /etc/hosts.deny, and setting up /etc/inetd.conf appropriately). Or, you could use iptables with a line like this:

iptables -A INPUT -t filter -s ! 208.201.239.36 -p tcp --dport 23 -j DROP

Generally, most people want to permit unrestricted access from trusted hosts, block all access from known problem hosts, and allow something in between for everyone else. Here is one method for using a whitelist, blacklist, and restricted port policy simultaneously.

#!/bin/sh
#
# A simple firewall initialization script
#
WHITELIST=/usr/local/etc/whitelist.txt
BLACKLIST=/usr/local/etc/blacklist.txt
ALLOWED="22 25 80 443"

#
# Drop all existing filter rules
#
iptables -F

#
# First, run through $WHITELIST, accepting all traffic from the hosts and networks
# contained therein.
#
for x in `grep -v ^# $WHITELIST | awk '{print $1}'`; do 
echo "Permitting $x..."
iptables -A INPUT -t filter -s $x -j ACCEPT
done

#
# Now run through $BLACKLIST, dropping all traffic from the hosts and networks
# contained therein.
#
for x in `grep -v ^# $BLACKLIST | awk '{print $1}'`; do 
echo "Blocking $x..."
iptables -A INPUT -t filter -s $x -j DROP
done

#
# Next, the permitted ports: What will we accept from hosts not appearing 
# on the blacklist?
#
for port in $ALLOWED; do 
echo "Accepting port $port..."
iptables -A INPUT -t filter -p tcp --dport $port -j ACCEPT
done

#
# Finally, unless it's mentioned above, and it's an inbound startup request,
# just drop it.
#
iptables -A INPUT -t filter -p tcp --syn -j DROP

Be sure to specify all of the ports you’d like to include in the $ALLOWED variable at the top of the script. If you forget to include 22, you won’t be able to ssh into the box!

The /usr/local/etc/blacklist.txt file is populated with IP addresses, host names, and networks like this:

1.2.3.4 # Portscanned on 8/15/02
7.8.9.0/24 # Who knows what evil lurks therein
r00tb0y.script-kiddie.coop # $0 s0rR33 u 31337 h4x0r!

Likewise, /usr/local/etc/whitelist.txt contains the “good guys” that should be permitted no matter what the other rules specify:

11.22.33.44 # My workstation
208.201.239.0/26 # the local network

Since we’re only grabbing lines that don’t start with #, you can comment out an entire line if you need to. The next time you run the script, any commented entries will be ignored. We run an iptables -F at the beginning to flush all existing filter entries, so you can simply run the script again when you make changes to blacklist.txt, whitelist.txt, or the ports specified in $ALLOWED.

Also note that this script only allows for TCP connections. If you need to also support UDP, ICMP, or some other protocol, run another pass just like the $ALLOWED for loop, but include your additional ports and protocols (passing -p udp or -p icmp to iptables, for example).

Be careful about using whitelists. Any IPs or networks appearing on this list will be permitted to access all ports on your machine. In some circumstances, a clever miscreant may be able to send forged packets apparently originating from one of those IPs, if they can find out ahead of time (or logically deduce) what IPs appear on your whitelist. This kind of attack is difficult to perform, but it is possible. If you are particularly paranoid, you might only allow whitelist addresses from networks that aren’t routable over the Internet but are used on your internal network.

It is extremely useful to have console access while working with new firewall rules (you can’t lock yourself out of the console with iptables!) If you get confused about where you are when working with iptables, remember that you can always list out all rules with iptables -L, and start over by issuing iptables -F. If iptables -L seems to hang, try iptables -L -n to show the rules without doing any DNS resolution — your rules might accidentally be prohibiting DNS requests.

You can do a lot with simple filtering, but there’s much more to iptables than just the filter target.

See also:

Simple IP Masquerading

Set up NAT on your gateway in ten seconds

If you have a private network that needs to share an Internet connection with one IP address, you’ll want to use IP Masquerading on your gateway machine. Luckily, with iptables this is a simple two-liner:

# echo "1" > /proc/sys/net/ipv4/ip_forward
# iptables -t nat -A POSTROUTING -o $EXT_IFACE -j MASQUERADE

where $EXT_IFACE is the outside interface of your gateway. Now any machines that reside on a network on any of the other interfaces in your gateway will be able to “get out” to the Internet. As far as the Net is concerned, all traffic originates from your gateway’s external IP address.

There was a time when one had to worry about miscreants on the external network sending forged packets to your gateway, claiming to be originating from the internal network. These packets would obligingly be masqueraded by the kernel, and leave your network as if they were legitimate traffic. This made it possible for anyone to launch attacks that apparently originated from your network, making very bad times for the hapless gateway owner.

I say that this was a problem, because recent kernels give you a free firewall rule to deal with exactly this problem, called rp_filter . With rp_filter enabled, if a packet that arrives on an interface has a source address that doesn’t match the corresponding routing table entry, it is dropped. This effectively prevents IP spoofing and allows simple (and safe) masquerading with the example above.

In the very unlikely event that rp_filter is causing problems for you, you can deactivate it very easily:

# echo "0" > /proc/sys/net/ipv4/conf/all/rp_filter

See also:

iptables Tips & Tricks

Make your firewall do far more than filter packets with iptables

iptables is the next generation of firewall software for the netfilter project. It provides all of the functionality of its predecessor, ipchains, in addition to support for stateful firewalling. iptables also supports a framework for extending its capabilities with loadable modules. Here are a few tricks you can use with the base distribution of iptables, as well as some of the extensible modules available for iptables.

For these examples, we’ll assume that the following environment variables are already set:

$EXT_IFACE

The external (public) interface of the firewall

$INT_IFACE

The inside (private) interface of the firewall

$DEST_IP

The ultimate requested destination of this packet

You can use iptables to limit new inbound TCP packets to prevent a Denial of Service attack. This is accomplished with the following rules:

# Create syn-flood chain for detecting Denial of Service attacks
iptables -t nat -N syn-flood

# Limit 12 connections per second (burst to 24)
iptables -t nat -A syn-flood -m limit --limit 12/s --limit-burst 24 \
  -j RETURN
iptables -t nat -A syn-flood -j DROP

# Check for DoS attack
iptables -t nat -A PREROUTING -i $EXT_IFACE -d $DEST_IP -p tcp --syn \
  -j syn-flood

These rules limit new inbound TCP connections (packets with SYN bit set) to 12 per second after 24 connections per second have been seen.

Using iptables, a transparent Squid proxy can be set up. This will transparently cache and log all outbound HTTP requests to the Internet. It requires no modification to the user’s browser and is useful for blocking unwanted content. This is accomplished with the following iptables rule at the top of the PREROUTING chain:

# Setup transparent Squid proxy for internal network
#
# For details on setting up Squid, see:
# http://www.linuxdoc.org/HOWTO/mini/TransparentProxy.html
#
iptables -t nat -A PREROUTING -i $INT_IFACE -p tcp --dport 80 \
  -j REDIRECT --to-port 3128

This rule redirects outgoing requests on TCP port 80 to a Squid proxy running on TCP port 3128 on the firewall.

Arbitrary TCP flags can be matched with iptables. This means you can block XMAS-tree (all flags set) and NULL packets with the following rules:

# DROP XMAS & NULL TCP packets
iptables -t nat -A PREROUTING -p tcp --tcp-flags ALL ALL -j DROP
iptables -t nat -A PREROUTING -p tcp --tcp-flags ALL NONE -j DROP

Advanced iptables Features

iptables has introduced several advanced firewall features that are available by patching the Linux kernel. These patches can be obtained from http://www.netfilter.org/ by downloading the patch-o-matic version corresponding to the iptables version you are using. Patch-o-matic patches are iptables patches that are not yet available in the mainstream Linux kernel. Some of the patches are experimental and should be used with caution.

Using the experimental netfilter psd patch, iptables can detect and block inbound port scans with the following rule:

# DROP inbound port scans
iptables -t nat -A PREROUTING -i $EXT_IFACE -d $DEST_IP -m psd -j DROP

Using the experimental netfilter iplimit patch, iptables can limit the number of connections received from a particular IP address with the following rule:

# DROP packets from hosts with more than 16 active connections
iptables -t nat -A PREROUTING -i $EXT_IFACE -p tcp --syn -d $DEST_IP -m 
iplimit --iplimit-above 16 -j DROP

One of the most powerful netfilter patches allows you to match packets based on their content. The experimental string-matching patch allows you to filter out packets that match a certain string. This is helpful to filter out the CodeRed or Nimda viruses before they hit your web server. The following rules achieve this:

# DROP HTTP packets related to CodeRed and Nimda viruses silently
iptables -t filter -A INPUT -i $EXT_IFACE -p tcp -d $DEST_IP --dport http \
  -m string --string "/default.ida?" -j DROP
iptables -t filter -A INPUT -i $EXT_IFACE -p tcp -d $DEST_IP --dport http \
  -m string --string ".exe?/c+dir" -j DROP
iptables -t filter -A INPUT -i $EXT_IFACE -p tcp -d $DEST_IP --dport http \
  -m string --string ".exe?/c+tftp" -j DROP

Port forwarding is now native to iptables . The nat table uses a feature called Destination NAT in the PREROUTING chain to accomplish this. The following rule can be used to port forward HTTP requests to a system (10.0.0.3) on the internal network:

# Use DNAT to port forward http
iptables -t nat -A PREROUTING ! -i $INT_IFACE -p tcp --destination-port \
  80 -j DNAT --to 10.0.0.3:80

You can also port forward UDP packets. If you port forward traffic for a particular port, you do not need to have a corresponding rule in the INPUT chain to accept inbound connections on that port. This will only work if the destination is on a network on a locally attached interface (that is, not to destinations on foreign networks). Take a look at tools like rinetd ([Hack #48]) or nportredird if you need traffic to forward to remote networks.

If you port forward your HTTP requests to an internal host, you can filter out the CodeRed virus in the FORWARD chain with this rule:

iptables -t filter -A FORWARD -p tcp --dport http \
  -m string --string "/default.ida?" -j DROP

Using iptables can be challenging at first, but its flexibility makes it a tremendously useful tool. If you ever get stuck while developing your rule set (and you will), remember that your two best friends are iptables -L -n and tcpdump (maybe followed by a quick session with ethereal).

See also:

Forwarding TCP Ports to Arbitrary Machines

Make non-local services appear to come from local ports

As we saw in [Hack #47], it is simple to forward TCP and UDP ports from a firewall to internal hosts using iptables. But what if you need to forward traffic from arbitrary addresses to a machine that isn’t even on your network? Try an application layer port forwarder, like rinetd .

This simple bit of code is a couple of years old but is small, efficient, and perfect for just this sort of problem. Unpack the archive and simply run make, and you’ll be presented with a tiny rinetd binary that will let you forward TCP ports to your heart’s content. Unfortunately, UDP ports aren’t supported by rinetd.

The configuration file is dead simple:

[Source Address] [Source Port] [Destination Address] [Destination Port]

Each port to be forwarded is specified on a separate line. The source and destination addresses can be either host names or IP addresses, and an IP address of 0.0.0.0 binds rinetd to every available local IP:

0.0.0.0 80 some.othersite.gov 80
216.218.203.211 25 123.45.67.89 25
0.0.0.0 5353 my.shellserver.us 22

Save the file to /etc/rinetd.conf, and copy rinetd to somewhere handy (like /usr/local/sbin/, for example.) Then start it by simply running rinetd.

The first example forwards all web traffic destined for any local address to some.othersite.gov. Note that this will only work if there isn’t another process (like Apache) already bound to local port 80.

The next forwards inbound SMTP traffic destined for 216.218.203.211 to the mail server at 123.45.67.89 (but doesn’t interfere with any SMTP agents bound to other local IPs). The final example will forward any inbound traffic on port 5353 to the ssh server on my.shellserver.us. These all work without NAT or any special kernel configuration. Simply run rinetd, and it daemonizes and starts listening on the ports you have specified.

This utility can really help ease the transition when renumbering or physically relocating servers, as services can appear to remain up on the original IP (even though they are actually coming from another network entirely). rinetd doesn’t even need to run as root, if you’re only binding to ports higher than 1024. There are also extensive options for providing access control and keeping logs. This tiny tool is well worth having handy for when TCP port indirection is called.

Using Custom Chains in iptables

Keep your firewall rules under control with custom chains

By default, the iptables filter table consists of three chains: INPUT, FORWARD, and OUTPUT. You can add as many custom chains as you like to help simplify managing large rule sets. Custom chains behave just as built-in chains, introducing logic that must be passed before the ultimate fate of a packet is determined.

To create a new chain, use the -N switch:

root@mouse:~# iptables -N fun-filter

You can see which chains are defined at any time with the standard -L switch:

root@mouse:~# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination 

Chain FORWARD (policy ACCEPT)
target prot opt source destination 

Chain OUTPUT (policy ACCEPT)
target prot opt source destination 

Chain fun-filter (0 references)
target prot opt source destination

In order to make use of your custom chain, you’ll have to jump to it from somewhere. Let’s add a jump to the fun-filter chain we’ve just created straight from the INPUT chain:

root@mouse:~# iptables -t filter -A INPUT -j fun-filter

Now your custom chain can grow to any sort of complexity you like. For example, you may want to match packets based on the source MAC address:

root@mouse:~# iptables -A fun-filter -m mac  -- mac-source 11:22:33:aa:bb:cc \
  -j ACCEPT
root@mouse:~# iptables -A fun-filter -m mac  -- mac-source de:ad:be:ef:00:42 \
  -j ACCEPT
root@mouse:~# iptables -A fun-filter -m mac  -- mac-source 00:22:44:fa:ca:de
  -j REJECT  -- reject-with icmp-host-unreachable
root@mouse:~# iptables -A fun-filter -j RETURN

The RETURN jump at the end of the table makes processing resume back in the chain that called this one (in this case, back in the INPUT chain). Again, show what all of your tables look like with the -L switch:

root@mouse:~# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination 
fun-filter all -- anywhere anywhere 

Chain FORWARD (policy ACCEPT)
target prot opt source destination 

Chain OUTPUT (policy ACCEPT)
target prot opt source destination 

Chain fun-filter (0 references)
target prot opt source destination 
ACCEPT all -- anywhere anywhere MAC 11:22:33:AA:BB:CC 
ACCEPT all -- anywhere anywhere MAC DE:AD:BE:EF:00:42 
REJECT all -- anywhere anywhere MAC 00:22:44:FA:CA:DE reject-with icmp-host-
unreachable 
RETURN all -- anywhere anywhere

You can jump into any number of custom defined chains and even jump between them. This helps to isolate rules that you’re developing from the standard system policy rules, and enable and disable them easily. If you want to stop using your custom chain temporarily, you can simply delete the jump from the INPUT chain (rather than flushing the entire custom chain):

root@mouse:~# iptables -t filter -D INPUT -j fun-filter

If you decide to delete your custom chain, use -X:

root@mouse:~# iptables -X fun-filter

Note that there can be no references to your custom chain if you try to delete it; use -F to flush the chain first if there are still rules referring to your chain.

When properly managed, even the most complex iptables rulesets can be easily read, if you use intuitively named custom chains.

See also:

Tunneling: IPIP Encapsulation

IP tunneling with the Linux IPIP driver

If you have never worked with IP tunneling before, you might want to take a look at the Advanced Router HOWTO before continuing. Essentially, an IP tunnel is much like a VPN, except that not every IP tunnel involves encryption. A machine that is “tunneled” into another network has a virtual interface configured with an IP address that isn’t local, but exists on a remote network. Usually, all (or most) network traffic is routed down this tunnel, so remote clients appear to exist on the network services, or more generally, to connect to any two private networks together using the Internet to carry the tunnel traffic.

If you want to perform simple IP-within-IP tunneling between two machines, you might want to try IPIP. It is probably the simplest tunnel protocol available and will also work with *BSD, Solaris, and even Windows. Note that IPIP is simply a tunneling protocol and does not involve any sort of encryption. It is also only capable of tunneling unicast packets; if you need to tunnel multicast traffic, take a look at GRE tunneling in [Hack #51].

Before we rush right into our first tunnel, you’ll need a copy of the advanced routing tools (specifically the ip utility). You can get the latest authoritative copy at ftp://ftp.inr.ac.ru/ip-routing/. Be warned, the advanced routing tools aren’t especially friendly, but they allow you to manipulate nearly any facet of the Linux networking engine.

Assume that you have two private networks (10.42.1.0/24 and 10.42.2.0/24) and that these networks both have direct Internet connectively via a Linux router at each network. The “real” IP address of the first network router is 240.101.83.2, and the “real” IP of the second router is 251.4.92.217. This isn’t very difficult, so let’s jump right in.

First, load the kernel module on both routers:

# modprobe ipip

Next, on the first network’s router (on the 10.42.1.0/24 network), do the following:

# ip tunnel add mytun mode ipip remote 251.4.92.217 \  
local 240.101.83.2 ttl 255
# ifconfig mytun 10.42.1.1
# route add -net 10.42.2.0/24 dev mytun

And on the second network’s router (on the 10.42.2.0/24), reciprocate:

# ip tunnel add mytun mode ipip remote 240.101.83.2 \  
local 251.4.92.217 ttl 255
# ifconfig tun10 10.42.2.1
# route add -net 10.42.1.0/24 dev mytun

Naturally, you can give the interface a more meaningful name than mytun if you like. From the first network’s router, you should be able to ping 10.42.2.1, and from the second network router, you should be able to ping 10.42.1.1. Likewise, every machine on the 10.42.1.0/24 network should be able to route to every machine on the 10.42.2.0/24 network, just as if the Interent weren’t even there.

If you’re running a Linux 2.2x kernel, you’re in luck: here’s a shortcut that you can use to avoid having to use the Advanced Router tools package at all. After loading the module, try these commands instead:

# ifconfig tun10 10.42.1.1 pointopoint 251.4.92.217
# route add -net 10.42.2.0/24 dev tun10

And on the second network’s router (on the 10.42.2.0/24):

# ifconfig tun10 10.42.2.1 pointopoint 240.101.83.2
# route add -net 10.42.1.0/24 dev tun10

That’s all there is to it.

If you can ping the opposite router but other machines on the network don’t seem to be able to pass traffic beyond the router, make sure that both routers are configured to forward packets between interfaces:

# echo "1" > /proc/sys/net/ipv4/ip_forward

If you need to reach networks beyond 10.42.1.0 and 10.42.2.0, simply add additional route add -net... lines. There is no configuration needed on any of your network hosts, as long as they have a default route to their respective router (which they definitely should, since it is their router, after all).

To bring the tunnel down: On both routers, bring down the interface and delete it, if you like:

# ifconfig mytun down
# ip tunnel del mytun

(or, in Linux 2.2):

# ifconfig tun10 down

The kernel will very politely clean up your routing table for you when the interface goes away.

See also:

Tunneling: GRE Encapsulation

IP tunnels with Generic Routing Encapsulation

GRE stands for Generic Routing Encapsulation. Like IPIP tunneling (see [Hack #50]), GRE is an unencrypted encapsulation protocol. The main advantage of using GRE instead of IPIP are that it supports multicast packets and that it will also inter operate with Cisco routers.

Just as with the IPIP tunneling hack, we’ll assume that you have two private networks (10.42.1.0/24 and 10.42.2.0/24) and that these networks both have direct Internet connectivity via a Linux router at each network. The “real” IP address of the first network router is 240.101.83.2, and the “real” IP of the second router is 251.4.92.217.

Again, as with IPIP tunneling ([Hack #50]), you will need a copy of the advanced routing tools package (there is no shortcut for GRE tunnels in Linux 2.2 that I’ve been able to find). Once you have the iproute2 package installed, we’ll begin by loading the GRE kernel module on both routers:

# modprobe ip_gre

On the first network’s router, set up a new tunnel device:

# ip tunnel add gre0 mode gre remote 251.4.92.217 local 240.101.83.2 ttl 255
# ip addr add 10.42.1.254 dev gre0
# ip link set gre0 up

Note that you can call the device anything you like; gre0 is just an example. Also, that 10.42.1.254 address can be any available address on the first network, but shouldn’t be 10.42.1.1 (the IP already bound to its internal interface). Now, add your network routes via the new tunnel interface:

# ip route add 10.42.2.0/24 dev gre0

The first network is finished. Now for the second:

# ip tunnel add gre0 mode gre remote 240.101.83.2 local 251.4.92.217 ttl 255
# ip addr add 10.42.2.254 dev gre0
# ip link set gre0 up
# ip route add 10.42.1.0/24 dev gre0

Again, the 10.42.2.254 address can be any available address on the second network. Feel free to add as many ip route add . . . dev gre0 commands as you need.

That’s it! You should now be able to pass packets between the two networks as if the Internet didn’t exist. A traceroute from the first network should show just a couple of hops to any host in the second network (although you’ll probably notice a fair bit of latency when crossing the 10.42.2.254 hop, unless you’re really well connected). If you’re having trouble, check the notes in the IPIP example and don’t panic. Your best friend when debugging new network configurations is probably a packet sniffer like tcpdump or ethereal. Running a tcpdump 'proto \icmp' on both routers while pinging will give you a very detailed overview of what’s going on.

To bring the tunnel down, run this on both routers:

# ip link set gre0 down
# ip tunnel del gre0

See also:

Using vtun over ssh to Circumvent NAT

Connect two networks together using vtun and a single ssh connection

vtun is a user space tunnel server, allowing entire networks to be tunneled to each other using the tun universal tunnel kernel driver. Connections can be made directly over IP or even over PPP or serial. this technique can be particularly useful when general network access is restricted by an intervening firewall, as all IP traffic will be encryted and forwarded over a single TCP port (that is, over a single ssh connection).

The procedure described below will allow a host with a private IP address (10.42.4.6) to bring up a new tunnel interface with a real, live routed IP address (208.201.239.33) that works as expected, as if the private network weren’t even there. We’ll do this by bringing up the tunnel, dropping the default route, then adding a new default route via the other end of the tunnel.

To begin with, here is the (pre-tunneled) configuration of the network.

root@client:~# ifconfig eth2
eth2 Link encap:Ethernet HWaddr 00:02:2D:2A:27:EA 
inet addr:10.42.3.2 Bcast:10.42.3.63 Mask:255.255.255.192
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:662 errors:0 dropped:0 overruns:0 frame:0
TX packets:733 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:100 
RX bytes:105616 (103.1 Kb) TX bytes:74259 (72.5 Kb)
Interrupt:3 Base address:0x100 

root@client:~# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
10.42.3.0 * 255.255.255.192 U 0 0 0 eth2
loopback * 255.0.0.0 U 0 0 0 lo
default 10.42.3.1 0.0.0.0 UG 0 0 0 eth2

As you can see, our local network is 10.42.3.0/26, our IP is 10.42.3.2, and our default gateway is 10.42.3.1. This gateway provides network address translation (NAT) to the internet. Here’s what our path looks like to yahoo.com:

root@client:~# traceroute -n yahoo.com
traceroute to yahoo.com (64.58.79.230), 30 hops max, 40 byte packets
1 10.42.3.1 2.848 ms 2.304 ms 2.915 ms
2 209.204.179.1 16.654 ms 16.052 ms 19.224 ms
3 208.201.224.194 20.112 ms 20.863 ms 18.238 ms
4 208.201.224.5 213.466 ms 338.259 ms 357.7 ms
5 206.24.221.217 20.743 ms 23.504 ms 24.192 ms
6 206.24.210.62 22.379 ms 30.948 ms 54.475 ms
7 206.24.226.104 94.263 ms 94.192 ms 91.825 ms
8 206.24.238.61 97.107 ms 91.005 ms 91.133 ms
9 206.24.238.26 95.443 ms 98.846 ms 100.055 ms
10 216.109.66.7 92.133 ms 97.419 ms 94.22 ms
11 216.33.98.19 99.491 ms 94.661 ms 100.002 ms
12 216.35.210.126 97.945 ms 93.608 ms 95.347 ms
13 64.58.77.41 98.607 ms 99.588 ms 97.816 ms

In this example, we’ll be connecting to a tunnel server on the Internet at 208.201.239.5. It has two spare live IP addresses (208.201.239.32 and 208.201.239.33) to be used for tunneling. We’ll refer to that machine as the server, and our local machine as the client.

Now, let’s get the tunnel running. To begin with, load the tun driver on both machines:

# modprobe tun

It is worth noting that the tun driver will sometimes fail if the kernel version on the server and client don’t match. For best results, use a recent kernel (and the same version, e.g., 2.4.19) on both machines.

On the server machine, install this file to /usr/local/etc/vtund.conf :

options {
port 5000;
ifconfig /sbin/ifconfig;
route /sbin/route;
syslog auth;
}

default {
compress no;
speed 0;
}

home {
type tun;
proto tcp;
stat yes;
keepalive yes;

pass sHHH; # Password is REQUIRED.

up {
ifconfig "%% 208.201.239.32 pointopoint 208.201.239.33";

program /sbin/arp "-Ds 208.201.239.33 %% pub";
program /sbin/arp "-Ds 208.201.239.33 eth0 pub";

route "add -net 10.42.0.0/16 gw 208.201.239.33";
};

down {
program /sbin/arp "-d 208.201.239.33 -i %%";
program /sbin/arp "-d 208.201.239.33 -i eth0";

route "del -net 10.42.0.0/16 gw 208.201.239.33";
};
}

and launch the vtund server with this command:

root@server:~# vtund -s

Now, you’ll need a vtund.conf for the client side. Try this one, again in /usr/local/etc/vtund.conf:

options {
port 5000;
ifconfig /sbin/ifconfig;
route /sbin/route;
}

default {
compress no;
speed 0;
}

home {
type tun;
proto tcp;
keepalive yes;

pass sHHH; # Password is REQUIRED.

up {
ifconfig "%% 208.201.239.33 pointopoint 208.201.239.32 arp";

route "add 208.201.239.5 gw 10.42.3.1";
route "del default";
route "add default gw 208.201.239.32";

};

down {
route "del default";
route "del 208.201.239.5 gw 10.42.3.1";
route "add default gw 10.42.3.1";
};
}

Finally, run this command on the client:

root@client:~# vtund -p home server

Presto! You now not only have a tunnel up between client and server, but have added a new default route via the other end of the tunnel. Take a look at what happens when we traceroute to yahoo.com with the tunnel in place:

root@client:~# traceroute -n yahoo.com
traceroute to yahoo.com (64.58.79.230), 30 hops max, 40 byte packets
1 208.201.239.32 24.368 ms 28.019 ms 19.114 ms
2 208.201.239.1 21.677 ms 22.644 ms 23.489 ms
3 208.201.224.194 20.41 ms 22.997 ms 23.788 ms
4 208.201.224.5 26.496 ms 23.8 ms 25.752 ms
5 206.24.221.217 26.174 ms 28.077 ms 26.344 ms
6 206.24.210.62 26.484 ms 27.851 ms 25.015 ms
7 206.24.226.103 104.22 ms 114.278 ms 108.575 ms
8 206.24.238.57 99.978 ms 99.028 ms 100.976 ms
9 206.24.238.26 103.749 ms 101.416 ms 101.09 ms
10 216.109.66.132 102.426 ms 104.222 ms 98.675 ms
11 216.33.98.19 99.985 ms 99.618 ms 103.827 ms
12 216.35.210.126 104.075 ms 103.247 ms 106.398 ms
13 64.58.77.41 107.219 ms 106.285 ms 101.169 ms

This means that any server processes running on client are now fully available to the Internet, at IP address 208.201.239.33. This has happened all without making a single change (e.g., port forwarding) on the gateway 10.42.3.1.

Here’s what the new tunnel interface looks like on the client:

root@client:~# ifconfig tun0
tun0 Link encap:Point-to-Point Protocol 
inet addr:208.201.239.33 P-t-P:208.201.239.32 Mask:255.255.255.255
UP POINTOPOINT RUNNING MULTICAST MTU:1500 Metric:1
RX packets:39 errors:0 dropped:0 overruns:0 frame:0
TX packets:39 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:10 
RX bytes:2220 (2.1 Kb) TX bytes:1560 (1.5 Kb)

and here’s the updated routing table. Note that we still need to keep a host route to the tunnel server’s IP address via our old default gateway, otherwise the tunnel traffic couldn’t get out:

root@client:~# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
208.201.239.5 10.42.3.1 255.255.255.255 UGH 0 0 0 eth2
208.201.239.32 * 255.255.255.255 UH 0 0 0 tun0
10.42.3.0 * 255.255.255.192 U 0 0 0 eth2
10.42.4.0 * 255.255.255.192 U 0 0 0 eth0
loopback * 255.0.0.0 U 0 0 0 lo
default 208.201.239.32 0.0.0.0 UG 0 0 0 tun0

To bring down the tunnel, simply kill the vtund process on the client. This will restore all network settings back to their original state.

This method works fine, if you trust vtun to use strong encryption and to be free from remote exploits. Personally, I don’t think you can be too paranoid when it comes to machines connected to the Internet. To use vtun over ssh (and therefore rely on the strong authentication and encryption that ssh provides) simply forward port 5000 on client to the same port on server. Give this a try:

root@client:~# ssh -f -N -c blowfish -C -L5000:localhost:5000 server
root@client:~# vtund -p home localhost
root@client:~# traceroute -n yahoo.com
traceroute to yahoo.com (64.58.79.230), 30 hops max, 40 byte packets
1 208.201.239.32 24.715 ms 31.713 ms 29.519 ms
2 208.201.239.1 28.389 ms 36.247 ms 28.879 ms
3 208.201.224.194 48.777 ms 28.602 ms 44.024 ms
4 208.201.224.5 38.788 ms 35.608 ms 35.72 ms
5 206.24.221.217 37.729 ms 38.821 ms 43.489 ms
6 206.24.210.62 39.577 ms 43.784 ms 34.711 ms
7 206.24.226.103 110.761 ms 111.246 ms 117.15 ms
8 206.24.238.57 112.569 ms 113.2 ms 111.773 ms
9 206.24.238.26 111.466 ms 123.051 ms 118.58 ms
10 216.109.66.132 113.79 ms 119.143 ms 109.934 ms
11 216.33.98.19 111.948 ms 117.959 ms 122.269 ms
12 216.35.210.126 113.472 ms 111.129 ms 118.079 ms
13 64.58.77.41 110.923 ms 110.733 ms 115.22 ms

In order to discourage connections to vtund on port 5000 of the server, add a netfilter rule to drop connections from the outside world:

root@server:~# iptables -A INPUT -t filter -i eth0 -p tcp  -- dport 5000 -j DROP

This will allow local connections to get through (since they use loopback), and therefore require an ssh tunnel to server before accepting a connection.

As you can see, this can be an extremely handy tool to have around. In addition to giving live IP addresses to machines behind a NAT, you can effectively connect any two networks together if you can obtain a single ssh connection between them (originating from either direction).

If your head is swimming from the vtund.conf configuration previously, or if you’re terminally lazy and don’t want to figure out what to change when setting up your own client’s vtund.conf, take a look at the Automatic vtund.conf generator, in [Hack #53].

Notes:

  • The session name (home in the above example) must match on the client AND the server sides, or you’ll get an ambiguous “server disconnected” message.

  • The same goes for the password field in the vtund.conf on both sides. It must be present AND match on both sides, or the connection won’t work.

  • If you’re having trouble connecting, make sure you’re using the same kernel version on both sides, and that the server is up and running (try telnet server 5000 from the client side to verify that the server is happy).

  • Try the direct method first, then get ssh working once you are happy with your vtund.conf settings.

  • If you’re still having trouble, check /etc/syslog.conf to see where your auth facility messages are going, and watch that log on both the client and server when trying to connect.

See also:

Automatic vtund.conf Generator

Generate a vtund.conf on the fly to match changing network conditions

If you’ve just come from [Hack #51], then this script will generate a working vtund.conf for the client side automatically.

If you haven’t read [Hack #51] (or if you’ve never used vtun), then go back and read it before attempting to grok this bit of Perl. Essentially, it attempts to take the guesswork out of changing the routing table around on the client side by auto-detecting the default gateway and building the vtund.conf accordingly.

To configure the script, take a look at the Configuration section. The first line of $Config contains the addresses, port, and secret that we used in “Tunneling: GRE Encapsulation” [Hack #51] . The second is there simply as an example of how to add more.

To run the script, either call it as vtundconf home, or set $TunnelName to the one to which you want to default. Or better yet, make symlinks to the script like this:

# ln -s vtundconf home
# ln -s vtundconf tunnel2

Then generate the appropriate vtund.conf by calling the symlink directly:

# vtundconf home > /usr/local/etc/vtund.conf

You might be wondering why anyone would go to all of the trouble to make a script to generate a vtund.conf in the first place. Once you get the settings right, you’ll never have to change them, right?

Well, usually that is the case. But consider the case of a Linux laptop that uses many different networks in the course of the day (say a DSL line at home, Ethernet at work, and maybe a wireless connection at the local coffee shop). By running vtund.conf once at each location, you will have a working configuration instantly, even if your IP and gateway is assigned by DHCP. This makes it very easy to get up and running quickly with a live, routable IP address, regardless of the local network topology.

Incidentally, vtund and vtund.conf currently runs great on Linux, FreeBSD, OS X, Solaris, and a few others.

Listing: vtundconf

#!/usr/bin/perl -w

# vtund wrapper in need of a better name.
#
# (c)2002 Schuyler Erle & Rob Flickenger
#
################ CONFIGURATION

# If TunnelName is blank, the wrapper will look at @ARGV or $0.
#
# Config is TunnelName, LocalIP, RemoteIP, TunnelHost, TunnelPort, Secret
#
my $TunnelName = ""; 
my $Config = q{
home 208.201.239.33 208.201.239.32 208.201.239.5 5000 sHHH
tunnel2 10.0.1.100 10.0.1.1 192.168.1.4 6001 foobar
};

################ MAIN PROGRAM BEGINS HERE

use POSIX 'tmpnam';
use IO::File;
use File::Basename;
use strict;

# Where to find things...
#
$ENV{PATH} = "/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin";
my $IP_Match = '((?:\d{1,3}\.){3}\d{1,3})'; # match xxx.xxx.xxx.xxx
my $Ifconfig = "ifconfig -a";
my $Netstat = "netstat -rn";
my $Vtund = "/bin/echo";
my $Debug = 1;

# Load the template from the data section.
#
my $template = join( "", <DATA> );

# Open a temp file -- adapted from Perl Cookbook, 1st Ed., sec. 7.5.
#
my ( $file, $name ) = ("", "");
$name = tmpnam(  ) until $file = IO::File->new( $name, O_RDWR|O_CREAT|O_EXCL );
END { unlink( $name ) or warn "Can't remove temporary file $name!\n"; }

# If no TunnelName is specified, use the first thing on the command line,
# or if there isn't one, the basename of the script.
# This allows users to symlink different tunnel names to the same script.
#
$TunnelName ||= shift(@ARGV) || basename($0);
die "Can't determine tunnel config to use!\n" unless $TunnelName;

# Parse config.
#
my ($LocalIP, $RemoteIP, $TunnelHost, $TunnelPort, $Secret);
for (split(/\r*\n+/, $Config)) {
my ($conf, @vars) = grep( $_ ne "", split( /\s+/ ));
next if not $conf or $conf =~ /^\s*#/o; # skip blank lines, comments
if ($conf eq $TunnelName) {
($LocalIP, $RemoteIP, $TunnelHost, $TunnelPort, $Secret) = @vars;
last;
}
}

die "Can't determine configuration for TunnelName '$TunnelName'!\n"
unless $RemoteIP and $TunnelHost and $TunnelPort;

# Find the default gateway.
#
my ( $GatewayIP, $ExternalDevice );

for (qx{ $Netstat }) {
# In both Linux and BSD, the gateway is the next thing on the line,
# and the interface is the last.
#
if ( /^(?:0.0.0.0|default)\s+(\S+)\s+.*?(\S+)\s*$/o ) {
$GatewayIP = $1;
$ExternalDevice = $2;
last;
}
}

die "Can't determine default gateway!\n" unless $GatewayIP and $ExternalDevice;

# Figure out the LocalIP and LocalNetwork.
#
my ( $LocalNetwork );
my ( $iface, $addr, $up, $network, $mask ) = "";

sub compute_netmask {
($addr, $mask) = @_;
# We have to mask $addr with $mask because linux /sbin/route
# complains if the network address doesn't match the netmask.
#
my @ip = split( /\./, $addr );
my @mask = split( /\./, $mask );
$ip[$_] = ($ip[$_] + 0) & ($mask[$_] + 0) for (0..$#ip);
$addr = join(".", @ip);
return $addr;
}

for (qx{ $Ifconfig }) {
last unless defined $_;

# If we got a new device, stash the previous one (if any).
if ( /^([^\s:]+)/o ) {
if ( $iface eq $ExternalDevice and $network and $up ) {
$LocalNetwork = $network;
last;
}
$iface = $1;
$up = 0;
}

# Get the network mask for the current interface.
if ( /addr:$IP_Match.*?mask:$IP_Match/io ) {
# Linux style ifconfig.
compute_netmask($1, $2);
$network = "$addr netmask $mask";
} elsif ( /inet $IP_Match.*?mask 0x([a-f0-9]{8})/io ) {
# BSD style ifconfig.
($addr, $mask) = ($1, $2);
$mask = join(".", map( hex $_, $mask =~ /(..)/gs )); 
compute_netmask($addr, $mask);
$network = "$addr/$mask";
}

# Ignore interfaces that are loopback devices or aren't up.
$iface = "" if /\bLOOPBACK\b/o;
$up++ if /\bUP\b/o;
}

die "Can't determine local IP address!\n" unless $LocalIP and $LocalNetwork;

# Set OS dependent variables.
#
my ( $GW, $NET, $PTP );
if ( $^O eq "linux" ) {
$GW = "gw"; $PTP = "pointopoint"; $NET = "-net";
} else {
$GW = $PTP = $NET = "";
}

# Parse the config template.
#
$template =~ s/(\$\w+)/$1/gee;

# Write the temp file and execute vtund.
#
if ($Debug) {
print $template;
} else {
print $file $template;
close $file;
system("$Vtund $name");
}

__DATA_  _

options {
port $TunnelPort;
ifconfig /sbin/ifconfig;
route /sbin/route;
}

default {
compress no;
speed 0;
}

$TunnelName { # 'mytunnel' should really be `basename $0` or some such
# for automagic config selection
type tun;
proto tcp;
keepalive yes;

pass $Secret;

up {
ifconfig "%% $LocalIP $PTP $RemoteIP arp";
route "add $TunnelHost $GW $GatewayIP";
route "delete default";
route "add default $GW $RemoteIP";
route "add $NET $LocalNetwork $GW $GatewayIP";
};

down {
ifconfig "%% down";
route "delete default";
route "delete $TunnelHost $GW $GatewayIP";
route "delete $NET $LocalNetwork";
route "add default $GW $GatewayIP";
};
}

Get Linux Server Hacks now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.