Linux Router with VPN on a Raspberry Pi
Note this article is a work in progress!
Rationale
This guide demonstrates how to set up a Raspberry Pi as an open source Linux router with a VPN tunnel.
I had decided against re-flashing a consumer router with an embedded firmware like OpenWrt, DD-WRT or Tomato because these devices were not intended to run an alternate firmware, and there is usually not any manufacturer support when doing so. Their hardware designs are also certainly not open at all.
You could choose to use an old x86/amd64 system instead. This may be a more attractive option if you want to route speeds above 30 Mbit/s. You are limited by the fact the USB and Ethernet share the same bus with the Raspberry Pi.
If you want to route speeds above 100 Mbit/s you'll want to make use of hardware encryption like AES-NI. You can also improve performance by not using OpenVPN in AES-CBC mode and instead using OpenSSH with AES-CTR or stunnel with something like ECDHE-RSA-AES256-GCM-SHA384.
Support for devices varies significantly depending on which one you have - sometimes down to the revision of the router. Integrating wireless into the device creates significant driver compatibility issues. Many chipsets (Broadcom, Marvell) for example either require closed source blobs or have a restrictive license.
For wireless a separate access point was purchased Ubiquiti UniFi AP because it contains a Atheros AR9287 which is supported by ath9k and I was keen to avoid blob drivers.
Installation
This guide assumes you're using AlpineLinux from a micro sdcard in ram-disk mode. First format the sdcard as described, stop when you get to the end of the installation guide Format USB stick
Download armhf.rpi.tar.gz archive and extract into FAT32 partition. You will need version 3.2.0 or greater if you have a Raspberry Pi 2.
Modem in full bridge mode
Your modem will need to be configured in "full bridge mode". The method for doing this varies depending on the interface on your device and is out of the scope of this tutorial.
The modem I am using is a Cisco 877 Integrated Services Router. It has no web interface and is controlled over SSH. I have put the configuration file up here because it took me some time to figure out the IOS commands to get the device configured in this way.
When sshing into this device with a recent version of ssh you may need to use this command:
ssh -o HostKeyAlgorithms=ssh-rsa,ssh-dss -o KexAlgorithms=diffie-hellman-group1-sha1 \ -o Ciphers=aes128-cbc,3des-cbc -o MACs=hmac-md5,hmac-sha1 admin@192.168.0.1
As there is a bug here and here with new versions of OpenSSH and Cisco's old sshd.
Configuration of a Cisco 877 ADSL Modem
! version 12.4 no service pad service timestamps debug datetime msec service timestamps log datetime msec service password-encryption ! hostname <HOSTNAME> ! boot-start-marker boot-end-marker ! logging message-counter syslog logging buffered 4096 informational enable secret 5 <SECRET> ! aaa new-model ! ! aaa authentication login default local aaa authentication login local_auth local aaa authorization exec default local none ! ! aaa session-id common ! ! dot11 syslog ip source-route no ip routing ! ! ! ! no ip cef ip domain name <DOMAIN NAME> no ipv6 cef ! multilink bundle-name authenticated ! ! ! username admin privilege 15 secret 5 <SECRET> username USER privilege 15 password 7 <SECRET> ! ! ! archive log config hidekeys ! ! ip ssh version 2 ip scp server enable ! bridge irb ! ! interface ATM0 no ip address no ip route-cache no atm ilmi-keepalive pvc 8/35 encapsulation aal5snap ! bridge-group 1 ! interface FastEthernet0 ! interface FastEthernet1 ! interface FastEthernet2 ! interface FastEthernet3 ! interface Dot11Radio0 no ip address no ip route-cache shutdown speed basic-1.0 basic-2.0 basic-5.5 6.0 9.0 basic-11.0 12.0 18.0 24.0 36.0 48.0 54.0 station-role root ! interface Vlan1 no ip address no ip route-cache bridge-group 1 ! interface BVI1 ip address 192.168.0.1 255.255.255.252 no ip route-cache ! ip default-gateway 192.168.0.2 ip default-network 192.168.0.0 ip forward-protocol nd no ip http server no ip http secure-server ! ! ! ip access-list standard SSH_ACCESS !This is a list of the addresses you want to allow permit <IP> permit <IP> ! ! ! ! ! ! control-plane ! bridge 1 protocol ieee bridge 1 route ip banner login Authorized access only! Disconnect IMMEDIATELY if you are not an authorized user! ! line con 0 no modem enable line aux 0 line vty 0 4 access-class SSH_ACCESS in authorization exec local_author login authentication login_local transport input ssh ! scheduler max-task-time 5000 end
Besides changing the obvious things such as the hostname, domain name, and permitted IPs, you'll also need to verify the ATM0 settings match your ISP's configuration. Specifically: "pvc 8/35" and "encapsulation aal5snap" works for me but may not for you. You will also need to generate the passwords and replace all the instances of <SECRET>.
Generating the passwords
Secret 5 Password
You can generate the hash for the "secret 5" passwords with this OpenSSL command:
openssl passwd -salt `openssl rand -base64 3` -1 "<YOUR PASSWORD>"
Put the output in <SECRET>
Secret 7 Password
To encrypt the secret 7 password you can use this perl script thanks to m00nie.
#!/usr/bin/perl # Cisco (type 7) password tool from www.m00nie.com :D # Will either decrypt a _TYPE 7_ password from a cisco device # or will encrypt a string so that it can be used in a cisco # device. # # I made this code to learn more Perl and just out of interest # about the type 7 "encryption". The decryption code is already # and from a mailing list. The original header from that is below. # # Credits for original code and description hobbit@avian.org, # SPHiXe, .mudge et al. and for John Bashinski <jbash@CISCO.COM> # for Cisco IOS password encryption facts. # # Use for any malice or illegal purposes strictly prohibited! # @xlat = ( 0x64, 0x73, 0x66, 0x64, 0x3b, 0x6b, 0x66, 0x6f, 0x41, 0x2c, 0x2e, 0x69, 0x79, 0x65, 0x77, 0x72, 0x6b, 0x6c, 0x64, 0x4a, 0x4b, 0x44, 0x48, 0x53 , 0x55, 0x42 ); $loop = 0; while ($loop == 0) { print "\n\n***************************************************************\n"; print "* Cisco (type 7) password tool from www.m00nie.com :D *\n"; print "* Use for any malice or illegal purposes strictly prohibited! *\n"; print "***************************************************************\n\n"; print "1. Decrypt a password\n"; print "2. Encrypt plain text\n"; print "3. Quit\n\n"; print "Pick either 1, 2 or 3: "; chomp ($choice = <STDIN>); if ( $choice == 1 ) { decrypt() } elsif ( $choice == 2) { encrypt() } elsif ($choice == 3) { exit } else { print "$choice is not a valid option\n"; } } sub decrypt { print "Enter the encrypted password: "; chomp ($epass = <STDIN>); if (!(length($epass) & 1)) { $ep = $epass; $dpass = ""; ($s, $e) = ($ep =~ /^(..)(.+)/); for ($i = 0; $i < length($e); $i+=2){ $dpass .= sprintf "%c",hex(substr($e,$i,2))^$xlat[$s++]; } } print "\nEncrypted pass was: $epass\n"; print "Decrypted pass is: $dpass\n"; } sub encrypt { print "Enter the string to encrypt:\n"; chomp ($ptext = <STDIN>); $pt = $ptext; $etext = ""; $n = 2; $etext .= sprintf("%.2o", $n); for ($k = 0; $k < length($pt); $k+=1){ $tmp = ord(substr($pt,$k,1))^$xlat[$n++]; $etext .= sprintf("%.2X", $tmp); } print "\nPlain string was: $ptext\n"; print "Encrypted string is: $etext\n"; } # eof
Configuring PPP
Next up we need to configure our router to be able to dial a PPP connection with our modem.
apk add ppp-pppoe
Check that the interface between your router and modem is eth1, or change it. Enter your credentials at the bottom of the file or use /etc/ppp/chap-secrets
/etc/ppp/peers/yourISP
nolog # Try to get the IP address from the ISP noipdefault # Try to get the name server addresses from the ISP usepeerdns # Use this connection as the default route. defaultroute defaultroute-metric 300 # detatch after ppp0 interface is created updetach # Replace previous default route #replacedefaultroute # rp-pppoe plug-in makes PPPoE connection so rp-pppoe package is not needed # Possibly, you may need to change interface according your configuration plugin rp-pppoe.so eth1 # Uncomment if you need on-demand connection #demand # Disconnect after 300 seconds (5 minutes) of idle time. #idle 300 # Hide password from log entries hide-password # Send echo requests lcp-echo-interval 20 lcp-echo-failure 3 # Do not authenticate ISP peer noauth # Control connection consistency persist maxfail 0 # Control MTU size if your ISP does not force it #mtu 1492 # Set your credentials # Alternatively you may use /etc/ppp/pap-secrets or /etc/ppp/chap-secrets files user username@yourISP.tld password <SECRET>
Network
/etc/network/interfaces
Configure your network interfaces:
auto lo iface lo inet loopback # internal interface auto eth0 iface eth0 inet static address 192.168.1.1 netmask 255.255.255.0 # external interface auto eth1 iface eth1 inet static address 192.168.0.2 netmask 255.255.255.252 # internet connection auto ppp0 iface ppp0 inet ppp pre-up ip link set dev eth1 up provider <yourISP> # Make sure this is the same as /etc/ppp/peers/yourISP post-down ip link set dev eth1 down
Basic IPtables firewall with routing
This demonstrates how to set up basic routing with a permissive outgoing firewall. Incoming packets are blocked.
#!/bin/sh iptables -F iptables -t nat -F export WAN=ppp0 export INT_IF=eth0 export EXT_IF=eth1 export WAN_TUNNEL=tun0 # Allows internet access on gateway iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -m conntrack --ctstate INVALID -j DROP # ICMP iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable iptables -A INPUT -p tcp -j REJECT --reject-with tcp-rst iptables -A INPUT -j REJECT --reject-with icmp-proto-unreachable # SSH To Modem iptables -A FORWARD -i ${INT_IF} -o ${EXT_IF} -s 192.168.1.0/24 -d 192.168.0.0/30 -j ACCEPT iptables -A FORWARD -i ${EXT_IF} -o ${INT_IF} -s 192.168.0.0/30 -d 192.168.1.0/24 -j ACCEPT iptables -A FORWARD -i ${INT_IF} -o ${EXT_IF} -s 192.168.1.0/24 -d 192.168.0.0/30 -j ACCEPT iptables -A FORWARD -i ${EXT_IF} -o ${INT_IF} -s 192.168.0.0/30 -d 192.168.1.0/24 -j ACCEPT # SSH To Router iptables -A INPUT -i ${INT_IF} -p tcp -s 192.168.1.0/24 --dport ssh -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT iptables -A INPUT -i ${INT_IF} -p tcp -s 192.168.2.0/24 --dport ssh -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT iptables -A OUTPUT -o ${INT_IF} -p tcp --sport ssh -m conntrack --ctstate ESTABLISHED -j ACCEPT # Accept DNS from LAN (This router runs a DNS forwarder) iptables -A INPUT -i ${INT_IF} -p tcp -s 192.168.1.0/24 --dport 53 -j ACCEPT iptables -A OUTPUT -o ${INT_IF} -p tcp --sport 53 -j ACCEPT iptables -A INPUT -i ${INT_IF} -p udp -s 192.168.1.0/24 --dport 53 -j ACCEPT iptables -A OUTPUT -o ${INT_IF} -p udp --sport 53 -j ACCEPT #Locks down certain services so they only work from the LAN: iptables -I INPUT 1 -i ${INT_IF} -j ACCEPT iptables -I INPUT 1 -i lo -j ACCEPT iptables -A INPUT -p UDP --dport bootps ! -i ${INT_IF} -j REJECT iptables -A INPUT -p UDP --dport domain ! -i ${INT_IF} -j REJECT # Drop TCP / UDP packets to privileged ports iptables -A INPUT -p TCP ! -i ${INT_IF} -d 0/0 --dport 0:1023 -j DROP iptables -A INPUT -p UDP ! -i ${INT_IF} -d 0/0 --dport 0:1023 -j DROP iptables -I FORWARD -i ${INT_IF} -d 192.168.1.0/24 -j DROP iptables -A FORWARD -i ${INT_IF} -s 192.168.1.0/24 -j ACCEPT iptables -A FORWARD -i ${WAN} -d 192.168.1.0/24 -j ACCEPT iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o ${WAN} -j MASQUERADE # Bittorrent forward (before we had a VPN tunnel) iptables -t nat -A PREROUTING -p tcp --dport 6881:6889 -i ${WAN} -j DNAT --to 192.168.2.100 iptables -t nat -A PREROUTING -p udp --dport 6881:6889 -i ${WAN} -j DNAT --to 192.168.2.100 iptables -P INPUT DROP iptables -P OUTPUT ACCEPT iptables -P FORWARD DROP echo 1 > /proc/sys/net/ipv4/ip_forward for f in /proc/sys/net/ipv4/conf/*/rp_filter ; do echo 1 > $f ; done /etc/init.d/iptables save
/etc/sysctl.conf
net.ipv4.ip_forward = 1 net.ipv4.conf.default.rp_filter = 1 kernel.panic = 120 #### ipv4 networking and equivalent ipv6 parameters #### ## TCP SYN cookie protection (default) ## helps protect against SYN flood attacks ## only kicks in when net.ipv4.tcp_max_syn_backlog is reached net.ipv4.tcp_syncookies = 1 ## protect against tcp time-wait assassination hazards ## drop RST packets for sockets in the time-wait state ## (not widely supported outside of linux, but conforms to RFC) net.ipv4.tcp_rfc1337 = 1 ## sets the kernels reverse path filtering mechanism to value 1(on) ## will do source validation of the packet's recieved from all the interfaces on the machine ## protects from attackers that are using ip spoofing methods to do harm net.ipv4.conf.all.rp_filter = 1 net.ipv6.conf.all.rp_filter = 1 ## tcp timestamps ## + protect against wrapping sequence numbers (at gigabit speeds) ## + round trip time calculation implemented in TCP ## - causes extra overhead and allows uptime detection by scanners like nmap ## enable @ gigabit speeds net.ipv4.tcp_timestamps = 0 #net.ipv4.tcp_timestamps = 1 ## log martian packets net.ipv4.conf.all.log_martians = 1 ## ignore echo broadcast requests to prevent being part of smurf attacks (default) net.ipv4.icmp_echo_ignore_broadcasts = 1 ## ignore bogus icmp errors (default) net.ipv4.icmp_ignore_bogus_error_responses = 1 ## send redirects (not a router, disable it) net.ipv4.conf.all.send_redirects = 0 ## ICMP routing redirects (only secure) #net.ipv4.conf.all.secure_redirects = 1 (default) net/ipv4/conf/default/accept_redirects=0 net/ipv4/conf/all/accept_redirects=0 net/ipv6/conf/default/accept_redirects=0 net/ipv6/conf/all/accept_redirects=0 # Disable ipv6 net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 net.ipv6.conf.lo.disable_ipv6 = 1