TUN/TAP with Routing
5. Enable proxy arp in both directions between the UML instance and the rest of the network.
Thoughts on Security
At this point, the machinations of the uml_net helper should make sense. To recap, let’s add another interface to the instance and let uml_net set it up for us:
host% uml_mconsole debian config eth1=tuntap,,,192.168.0.252 OK
Configuring the new device in the instance shows us this:
UML# ifconfig eth1 192.168.0.251 up * modprobe tun
* ifconfig tap0 192.168.0.252 netmask 255.255.255.255 up * bash -c echo 1 > /proc/sys/net/ipv4/ip_forward
* route add -host 192.168.0.251 dev tap0
* bash -c echo 1 > /proc/sys/net/ipv4/conf/tap0/proxy_arp * arp -Ds 192.168.0.251 jeffs-uml pub
* arp -Ds 192.168.0.251 eth1 pub
Here we can see the helper doing just about everything we just finished doing by hand. The one thing that’s missing is actually creating the TUN/TAP device. uml_net does that itself, without invoking an out- side utility, so that doesn’t show up in the list of commands it runs on our behalf.
Aside from knowing how to configure the host in order to support a networked UML instance, this is also important for understanding
130 Chapter 7 UML Networking in Depth
the security implications of what we have done and for customizing this setup for a particular environment.
What uml_net does is not secure against a nasty root user inside the instance. Consider what would happen if the UML user decided to configure the UML eth0 with the same IP address as your local name server. uml_net would set up proxy arp to direct name requests to the UML instance. The real name server would still be there getting requests, but some requests would be redirected to the UML instance. With a name server in the UML instance providing bogus responses, this could easily be a real security problem. For this reason, uml_net should not be used in a serious UML establishment. Its purpose is to make UML networking easy to set up for the casual UML user. For any more serious uses of UML, the host should be configured according to the local needs, security and otherwise.
What we just did by hand isn’t that bad because we set the route to the instance and proxy arp according to the IP address we expected it to use. If root inside our UML instance decides to use a different IP address, such as that of our local name server, it will see no traffic. The host will only arp on behalf of the IP we expect it to use, and the route is only good for that IP. All other traffic will go elsewhere.
A nasty root user can still send out packets purporting to be from other hosts, but since it can’t receive any responses to them, it would have to make blind attacks. As I discussed earlier, this is unlikely to enable any successful attacks on its own, but it does remove a layer of protection that might prove useful if another exploit on the host allows the attacker to see the local network traffic.
So, it is probably advisable to filter out any unexpected network traffic at the iptables level. First, let’s see that the UML instance can send out packets that pretend to be from some other host. As usual for this discussion, these will be pings, but they could just as easily be any other protocol.
UML# ifconfig eth0 192.168.0.100 up UML# ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3): 56 data bytes
--- 192.168.0.3 ping statistics ---
4 packets transmitted, 0 packets received, 100% packet loss
Here I am pretending to be 192.168.0.100, which we will consider to be an important host on the local network. Watching the jeffs-uml device on the host shows this:
Manually Setting Up Networking 131
host# tcpdump -i jeffs-uml -l -n
tcpdump: verbose output suppressed, use -v or -vv for full \ protocol decode
listening on jeffs-uml, link-type EN10MB (Ethernet), capture \ size 96 bytes
20:20:34.978090 arp who-has 192.168.0.3 tell 192.168.0.100 20:20:35.506878 arp reply 192.168.0.3 is-at ae:42:d1:20:37:e5 20:20:35.508062 IP 192.168.0.100 > 192.168.0.3: icmp 64: echo \ request seq 0
We can see those faked packets reaching the host. Looking at the host’s interface to the rest of the network, we can see they are reaching the local network:
tcpdump -i eth1 -l -n
tcpdump: verbose output suppressed, use -v or -vv for full \ protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size \ 96 bytes
20:23:30.741482 IP 192.168.0.100 > 192.168.0.3: icmp 64: echo \ request seq 0
20:23:30.744305 arp who-has 192.168.0.100 tell 192.168.0.3
Notice that arp request. It will be answered correctly, so the ping responses will go to the actual host that legitimately owns 192.168.0.100, which is not expecting them. That host will discard them, so they will cause no harm except for some wasted network bandwidth and CPU cycles. However, it would be preferable for those packets not to reach the network or the host in the first place. This can be done as follows:
host# iptables -A FORWARD -i jeffs-uml -s \! 192.168.0.253 -j \ DROP
Warning: wierd character in interface `jeffs-uml' (No aliases, \ :, ! or *).
iptables is apparently complaining about the dash in the interface name, but it does create the rule, as we can see here:
host# iptables -L
Chain FORWARD (policy ACCEPT)
target prot opt source destination DROP all -- !192.168.0.253 anywhere
Chain INPUT (policy ACCEPT)
target prot opt source destination Chain OUTPUT (policy ACCEPT)
132 Chapter 7 UML Networking in Depth
So, we have just told iptables to discard any packet it sees that:
☞
Is supposed to be forwarded☞
Enters the host through the jeffs-uml interface☞
Has a source address other than 192.168.0.253After creating this firewall rule, you should be able to rerun the previ- ous ping and tcpdump will show that those packets are not reaching the outside network.
At this point, we have a reasonably secure setup. As originally configured, the UML instance couldn’t see any traffic not intended for it. With the new firewall rule, the rest of the network will see only traf- fic from the instance that originates from the IP address assigned to it. A possible enhancement to this is to log any attempts to use an unau- thorized IP address so that the host administrator is aware of any such attempts and can take any necessary action.
You could also block any packets from coming in to the UML instance with an incorrect IP address. This shouldn’t happen because the proxy arp we have set up shouldn’t attract any packets for IP addresses that don’t belong somehow to the host, and any such packets that do reach the host won’t be routed to the UML instance. However, an explicit rule to prevent this might be a good addition to a layered security model. In the event of a malfunction or compromise of this con- figuration, such a rule could end up being the one thing standing in the way of a UML instance seeing traffic that it shouldn’t. This rule would look like this:
host# iptables -A FORWARD -o jeffs-uml -d \! 192.168.0.253 -j \ DROP
Warning: wierd character in interface `jeffs-uml' (No aliases, \ :, ! or *).
Access to the Outside Network
We still have a bit of work to do, as we have demonstrated access only to the local network, using IP addresses rather than more convenient host names. So, we need to provide the UML instance with a name service. For a single instance, the easiest thing to do is copy it from the host:
host# cat > /etc/resolv.conf
; generated by /sbin/dhclient-script search user-mode-linux.org
Manually Setting Up Networking 133
I cut the contents of the host’s /etc/resolv.conf and pasted them into the UML. You should do the same on your own machine, as my resolv.conf will almost certainly not work for you.
We also need a default route, which hasn’t been necessary for the limited testing we’ve done so far but is needed for almost anything else:
UML# route add default gw 192.168.0.254
I normally use the IP address of the host end of the TUN/TAP device as the default gateway.
If you still have the unauthorized IP address assigned to your instance’s eth0, reassign the original address:
ifconfig eth0 192.168.0.253
Now we should have name service:
UML# host 192.168.0.3
Name: laptop.user-mode-linux.org Address: 192.168.0.3
That’s a local name—let’s check for a remote one:
UML# host www.user-mode-linux.org
www.user-mode-linux.org A 66.59.111.166
Now let’s try pinging it, to see if we have network access to the outside world:
UML# ping www.user-mode-linux.org
PING www.user-mode-linux.org (66.59.111.166): 56 data bytes 64 bytes from 66.59.111.166: icmp_seq=0 ttl=52 time=487.2 ms 64 bytes from 66.59.111.166: icmp_seq=1 ttl=52 time=37.8 ms 64 bytes from 66.59.111.166: icmp_seq=2 ttl=52 time=36.0 ms 64 bytes from 66.59.111.166: icmp_seq=3 ttl=52 time=73.0 ms
--- www.user-mode-linux.org ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss round-trip min/avg/max = 36.0/158.5/487.2 ms
Copying /etc/resolv.conf from the host and setting the default route by hand works but is not the right thing to do. The real way to do these is with DHCP. The reason this won’t work here is the same reason that ARP didn’t work—the UML is on a different Ether- net strand than the rest of the network, and DHCP, being an Ethernet broadcast protocol, doesn’t cross Ethernet broadcast domain boundaries.
134 Chapter 7 UML Networking in Depth
DHCP through a TUN/TAP Device
Some tools work around the DHCP problem by forwarding DHCP requests from one Ethernet domain to another and relaying whatever replies come back. One such tool is dhcp-fwd. It needs to be installed on the host and configured. It has a fairly scary-looking default config file. You need to specify the interface from which client requests will come and the interface from which server responses will come.
In the default config file, these are eth2 and eth1, respectively. On my machine, the client interface is jeffs-uml and the server interface is eth1. So, a global replace of eth2 with jeffs-uml, and leaving eth1 alone, is sufficient to get a working dhcp-fwd.
Let’s get a clean start by unplugging the UML eth0 and plugging it back in. First we need to bring the interface down:
UML# ifconfig eth0 down
Then, on the host, remove the device:
host% uml_mconsole debian remove eth0 OK
Now, let’s plug it back in:
host% uml_mconsole debian config eth0=tuntap,,fe:fd:c0:a8:00:fd,\ 192.168.0.254
OK
Notice that we have a new parameter to this command. We are specifying a hardware MAC address for the interface. We never did this before because the UML network driver automatically generates one when it is assigned an IP address for the first time. It is important that these be unique. Physical Ethernet cards have a unique MAC burned into their hardware or firmware. It’s tougher for a virtual interface to get a unique identity. It’s also important for its IP address to be unique, and I have taken advantage of this in order to generate a unique MAC address for a UML’s Ethernet device.
When the administrator provides an IP address, which is very likely to be unique on the local network, to a UML Ethernet device, the driver uses that as part of the MAC address it assigns to the device. The first two bytes of the MAC will be 0xFE and 0xFD, which is a pri- vate Ethernet range. The next four bytes are the IP address. If the IP address is unique on the network, the MAC will be, too.
When configuring the interface with DHCP, the MAC is needed before the DHCP server can assign the IP. Thus, we need to assign the
Manually Setting Up Networking 135
MAC on the command line or when plugging the device into a running UML instance.
There is another case where you may need to supply a MAC on the UML command line, which I will discuss in greater detail later in this chapter. That is when the distribution you are using brings the inter- face up before giving it an IP address. In this case, the driver can’t sup- ply the MAC after the fact, when the interface is already up, so it must be provided ahead of time, on the command line.
Now, assuming the dhcp-fwd service has been started on the host, dhclient will work inside UML:
UML# dhclient eth0
Internet Software Consortium DHCP Client 2.0pl5
Copyright 1995, 1996, 1997, 1998, 1999 The Internet Software \ Consortium.
All rights reserved.
Please contribute if you find this software useful.
For info, please visit http://www.isc.org/dhcp-contrib.html
Listening on LPF/eth0/fe:fd:c0:a8:00:fd Sending on LPF/eth0/fe:fd:c0:a8:00:fd Sending on Socket/fallback/fallback-net DHCPREQUEST on eth0 to 255.255.255.255 port 67 DHCPACK from 192.168.0.254
bound to 192.168.0.9 -- renewal in 21600 seconds.
Final Testing
At this point, we have full access to the outside network. There is still one thing that could go wrong. Ping packets are relatively small; in some situations small packets will be unmolested but large packets, contained in full-size Ethernet frames, will be lost. To check this, we can copy in a large file:
UML# wget http://www.kernel.org/pub/linux/kernel/v2.6/\ linux-2.6.12.3.tar.bz2
--01:35:56-- http://www.kernel.org/pub/linux/kernel/v2.6/\ linux-2.6.12.3.tar.bz2 => `linux-2.6.12.3.tar.bz2' Resolving www.kernel.org... 204.152.191.37, 204.152.191.5
Connecting to www.kernel.org[204.152.191.37]:80... connected. HTTP request sent, awaiting response... 200 OK
Length: 37,500,159 [application/x-bzip2] 100%[====================================>] 37,500,159 \ 87.25K/s ETA 00:00 01:43:04 (85.92 KB/s) - `linux-2.6.12.3.tar.bz2' saved \ [37500159/37500159]
136 Chapter 7 UML Networking in Depth
Copying in a full Linux kernel tarball is a pretty good test, and in this case, it’s fine. If this does nothing for you, it’s likely that there’s a problem with large packets. If so, you need to lower the Maximal Transfer Unit (MTU) of the UML’s eth0:
UML# ifconfig eth0 mtu 1400
You can determine the exact value by experiment. Lower it until large transfers start working.
The cases where I’ve seen this involved a PPPoE connection to the outside world. PPPoE usually means a DSL connection, and I’ve seen UML connectivity problems when the host was my DSL gateway. Lower- ing the MTU to 1400 made the network fully functional. In fact, the MTU for a PPPoE connection is 1492, so lowering it to 1400 was overkill.
Bridging
As mentioned at the start of this chapter, there are two ways to config- ure a host to give a UML access to the outside world. We just explored one of them. The alternative, bridging, doesn’t require the host to route packets to and from the UML, and so doesn’t require new routes to be created or proxy arp to be configured. With bridging, the TUN/TAP device used by the UML instance is combined with the host’s physical Ethernet device into a sort of virtual switch. The bridge interface for- wards Ethernet frames from one interface to another based on their destination MAC addresses. This effectively merges the broadcast domains associated with the bridged interfaces. Since this caused DHCP and arp to not work when we were doing IP forwarding, bridg- ing provides a neat solution to these problems.
If you currently have an active UML network, you should shut it down before continuing:
UML# ifconfig eth0 down
Then, on the host, remove the device:
host% uml_mconsole debian remove eth0 OK
Bring down and remove the TUN/TAP interface, which will delete the route and one side of the proxy arp, and delete the other side of the proxy arp:
Manually Setting Up Networking 137
host# ifconfig jeffs-uml down host% tunctl -d jeffs-uml Set 'jeffs-uml' nonpersistent
host# arp -i jeffs-uml -d 192.168.0.253 pub
Now, with everything cleaned up, we can start from scratch:
host% tunctl -u jdike -t jeffs-uml
Let’s start setting up bridging. The idea is that a new interface will provide the host with network access to the outside world. The two interfaces we are currently using, eth0 and jeffs-uml, will be added to this new interface. The bridge device will forward frames from one interface to the other as needed, so that both eth0 and jeffs-uml will see traffic that’s intended for them (or that needs to be sent to the local network, in the case of eth0).
The first step is to create the device using the brctl utility, which is in the bridge-utilities package of your favorite distribution:
host# brctl addbr uml-bridge
In the spirit of giving interfaces meaningful names, I’ve called this one uml-bridge.
Now we want to add the two existing interfaces to it. For the phys- ical interface, choose a wired Ethernet—for some reason, wireless interfaces don’t seem to work in bridges. The virtual interface will be the jeffs-uml TUN/TAP interface.
We need to do some configuration to make it usable:
host# ifconfig jeffs-uml 0.0.0.0 up
These interfaces can’t have their own IP addresses, so we have to clear the one on eth0. This is a step you want to think about carefully. If you are logged in to the host remotely, this will likely kill your ses- sion and any network access you have to it. If the host has two network interfaces, and you know that your session and all other network activ- ity you care about is traveling over the other, then it should be safe to remove the IP address from this one:
host# ifconfig eth0 0.0.0.0
We can now add the two interfaces to the bridge:
host# brctl addif uml-bridge jeffs-uml host# brctl addif uml-bridge eth0
138 Chapter 7 UML Networking in Depth
And then we can look at our work:
host# brctl show
bridge name bridge id STP enabled \ interfaces
uml-bridge 8000.0012f04be1fa no \ eth0
\ jeffs-uml
At this point, the bridge configuration is done and we need to bring it up as a new network interface:
host# dhclient uml-bridge
Internet Systems Consortium DHCP Client V3.0.2 Copyright 2004 Internet Systems Consortium. All rights reserved.
For info, please visit http://www.isc.org/products/DHCP