Showing posts with label tap. Show all posts
Showing posts with label tap. Show all posts

Tuesday, November 22, 2011

SSH TAP tunnels: Using routing instead of bridging...

In the previous post about SSH tunneling, I used bridging functionality within Linux kernel in order to connect remote network with local laptop. But it is also possible to use routing in that case. The reason why you would prefer routing, instead of bridging, is the quantity of traffic that might be flowing from the remote network to your interface. And because you are probably connected with slower link than available bandwidth on the local network itself, that could be a real problem. So, routing could help in this case. Note that you are loosing ability to use some protocols, most notably those that use broadcasting since routing code won't route those packets.

The idea is to use routing in combination with proxy ARP. Basically, everything stays the same except you are not using bridging and you need to setup some basic forwarding features on remote host. So, here we go.

First add an explicit route for your remote host. That way it won't happen that you can not access it because the local network on which remote host is will be accessed in a special way:
ip route add remote_host via default_current_router
In case you don't know IP address of your default router (default_current_router) you can find it out using ip route sh command. And for remote_host you need to use IP address with network mask 32!

Now, log in to remote host using the usual ssh command that creates tap tunnel (for the meaning of parameters and what happens see previous post):
ssh -C -w any -o Tunnel=ethernet root@remotehost
After logging in, on local host add IP address from remote network that you are assigned when you directly connect on the remote network. In our example network this is the address 192.168.0.40/24 (see this previous post).

Now we have one interesting situation which I'm going to describe in some detail.

Start arping command on remote host like this (read this post about arping command):
arping -I tap0 192.168.0.40
Basically, you won't receive response. If you start tcpdump command, you'll notice that ARP requests are arriving, but there are no responses. The reason why there are no responses is because your local machine sees unexpected address in request and so ignores it.

You can turn on logging to see that those are really ignored with this command:
echo 1 > /proc/sys/net/ipv4/conf/tap0/log_martians
and now look into log file (/var/log/messages). You'll notice messages similar to the following ones:
Nov 22 14:37:25 w510 kernel: [147552.433215] martian source a.b.c.d from e.f.g.h, on dev tap0
Nov 22 14:37:25 w510 kernel: [147552.433221] ll header: ff:ff:ff:ff:ff:ff:16:90:4a:17:9d:d1:08:06
As you can see, it's called martian address. :)

Now, you can two options. The first one is to turn off filtering and the second one is to assign IP address to tap0 interface on remote host too. In general I don't suggest that you turn off rp filtering, but since in this case we are turning it off on a special (and restricted) interface I'll go that route, and also to save one IP address. So, on local machine do the following:
echo 1 > /proc/sys/net/ipv4/conf/tap0/rp_filtering
If you now try arping from local machine to remote machine, trying to reach IP address of remote machine, it won't work! You have to turn off rp filtering on remote machine too. So, execute the previous command there also.

One more thing for L2 part to fully work. When local machine asks from some address on local network, via tun0, remote host has to announce itself. For this purpose Proxy ARP is used but it has to be turned on. So, turn it on with the following command:
echo 1 > /proc/sys/net/ipv4/conf/tap0/proxy_arp
If you now try to "arping" any IP address, you'll aways get MAC address of tap0 on remote machine, and that's exactly what we wanted. But, you also need to do that because when machines on remote network search for you, then remote host has to announce himself and relay/forward everything over the tunnel to you.
echo 1 > /proc/sys/net/ipv4/conf/eth0/proxy_arp
I'm assuming that the interface name which attaches remote host to local network is named eth0, and I'm also assuming that you didn't create bridge device and attached eth0 to it (as it was described in the previous post).

Ok, time for L3 part. Everything has to be done on a remote machine. So, on a remote machine, first tell it that 192.168.0.40 is attached to tap0 interface:

ip route add 192.168.0.40/32 dev tap0
Next, allow IP forwarding:
echo 1 > /proc/sys/net/ipv4/ip_forward
And that's it. Only what's left is to automate the whole procedure.

Monday, November 14, 2011

SSH VPNs: Bridged connection to LAN using tap

In the previous post I showed how to create SSH tunnel that ends on a network  layer (i.e. ppp and point-to-point based vpns) and on a link layer (ethernet type). Now I'm continuing with a series of scenarios that do configuration on a network layer (and few on a link layer too).

This is the first scenario in which I want remote host to look like it is directly attached to a local network. The network layout for this scenario is shown in the following figure:

As you can see there is a local network that has address 192.168.0.0/24. On that local network there is a gateway (192.168.0.1) as well as remote server (192.168.0.30) that we are going to use as the end point of a tunnel. What we want to achieve is that our laptop, that is somewhere on the Internet and has IP address 10.2.4.60, behaves as if it is attached directly on a local network and to use IP address 192.168.0.40. This kind of setup when a single remote machine connects to some network remotely via VPN is offten called road-warrior scenario. To accomplish this we are going to use bridging built into the Linux kernel. In the future post I'll describe variant of this setup that uses forwarding (i.e. routing) to achieve the same thing.

Preparation steps

The idea (for this scenario) is to use briging. But since manipulating bridge and active interface might (and very probably will) disconnect you, it is better to first configure brigde with only a single interface, the one that connects remote computer to a local network. In case you are using CentOS (RHEL/Fedora or some derivative) then do the following on your remote server:
  1. Go into directory /etc/sysconfig/network-scripts.
  2. Copy file ifcfg-eth0 into ifcfg-br0 (I'm assuming that your active interface is eth0, if it is something else change the name accordingly). Also make a copy of that file in case you want to revert changes! Remove UUID line if there is any.
  3. Edit file ifcfg-eth0. Add line BRIDGE=br0, remove all lines that specify IP address and related parameters (broadcast, netmask, gateway, DNS, ...). Also be certain that BOOTPROTO parameter is set to none. Remove UUID line if there is any.
  4. Modify ifcfg-br0. Change type from Ethernet into Brige, also change name from eth0 into br0 and finally add line STP=off.
Now, restart machine and see if everything is OK. First, you should be able to connect to the machine, and second, there should be new interface, br0,  which has to have IP address of interface eth0. Interface eth0 itself should work (UP, LOWER_UP flags) and also it must not have IP address attached.

One other thing you have to take care of, firewall. In case you have firewall configured (which you should!), during this test it's best to disable it and enable it later. To disable firewall on CentOS/Fedora (and similar distributions) do the following:
service iptables stop
This will turn off firewall until you turn it on again, or until you restart machine. To turn it off permanently (strongly not advised!) do the following:
chkconfig iptables off
SELinux might get into your way. In case that you at some point receive the following error message:
channel 0: open failed: administratively prohibited: open failed
it's the indication that tap device hasn't been created because of missing privileges. The simplest way is to turn off temporarily SELinux (again, I do not advise to do that in production environment!):
setenforce 0
If, at any point, something doesn't work three things you can use to debug problems. The first one is option -v to ssh command (or -vv) that will make it verbose and it will print out what's happening. The next thing is to look into log files on the remote machine (for sshd messages). Finally, you should know how to use tcpdump tool.

Creating and configuring tunnel

Ok, for a start, open two terminals on a laptop and in each one of them switch to root user (su command). Then, in one of them execute ssh command like this:
ssh -w any -o Tunnel=ethernet remote_machine
This will connect you to the remote machine (after successful authentication) and create two tap devices, one on the local machine and the other on the remote machine. I'll assume that their name is tap0, on both sides. Now, switch temporarily to other terminal and execute the following command:
ip link set tap0 up
tcpdump -nni tap0
The first command will start interface and the second will run tcpdump command. You'll be able to see traffic from remote network as soon as we attach the other part of the tunnel to bridge br0. Options n instruct tcpdump not to print symbolic names of anything, while option i binds tcpdump to interface tap0. When you want to stop tcpdump, use Ctrl+C keys.

Now, on the remote host execute the following commands:
brctl addif br0 tap0
ip link set tap0 up
The first command will add tap device to bridge (more colloquially switch), and the second one will activate the interface. The moment you execute the other command you'll notice that tcpdump command, running in the second windows, starts to print some output. This output is the traffic from the local network, transferred to the laptop. Of course, you'll see traffic if there is any traffic on the local network.

One final step, to add IP address from the local network to laptop, i.e. 192.168.0.40. But before that, we have to add explicit route for remote host or otherwise things will lock up. The laptop will think that remote host is now directly attached, while it is not and nothing will work. So, execute the following command on laptop (terminate tcpdump or open new windows and switch to root):
ip route add 192.168.0.30/32 via your_default_route
Change the string your_default_route to your default router (check what it is using 'ip route sh' command, it is an IP address in the same line as the word default). Finally, we are ready to add IP address, also on laptop, in the terminal where you added explicit route, execute the following command:
ip addr add 192.168.0.40/24 dev tap0
From that moment on, when you communicate with any host on local network this communication will go through the tunnel to the remote host that will send traffic to the local network for you. Hosts on the network 192.168.0.0/24 won't notice that you are actually somewhere else on the Internet.

One final thing. The question which destinations you want to reach via this tunnel. You can select all, or only a subset of destinations. In any case, you use routing to achieve that, i.e. you use the following command:
ip route add destination via gateway_on_local_network
Change destination to whatever you want to access via tunnel. In case you want everything than use word default (it could happen that you first need to remove existing route for default!). Or, you can set network, or IP address. Let's suppose that you want to reach google via local network. In that case find out IP address (or network) fo google and use that instead of the word destionation.

In place of gateway_on_local_network use gateway on the local network. In our case that is 192.168.0.1.

Finally, to tear down connection, just kill ssh.

Automating access

As a last thing, I'll describe how to automate the whole procedure. If you want fully automated solution, then first you have to configure passwordless login for ssh. Then, create two scripts. The first one will be called vpn_start.sh, will be placed on remote machine in a directory /usr/local/sbin and will contain the following lines:
#!/bin/bash
ip link set tap0 up
brctl addif br0 tap0
Lets call the second script also vpn_start.sh and let it be placed in the /usr/local/sbin/ directory. The content of that script should be:
#!/bin/bash
ssh -f -w any -o Tunnel=ethernet remote_machine /usr/local/sbin/vpn_start.sh
ip route add 192.168.0.30/32 via your_default_route
ip addr add 192.168.0.40/24 dev tap0
ip route add destination via gateway_on_local_network
Repeat the last command as many times as necessary and change all the parameters accordingly. Don't forget to make both scripts executable! Now, to run the configuration just execute the script on the laptop:
/usr/local/sbin/vpn_start.sh
And that's it! Of course, those scripts might be a lot fancier, but this will do just good!

About Me

scientist, consultant, security specialist, networking guy, system administrator, philosopher ;)

Blog Archive