Saturday, December 21, 2013

Part IV - The Ultimate Linux Home Router - DHCP and Routing

Here's where we put it all together and finally replace our existing router. You might want to print this out because some of these steps will be done without internet connectivity.

Other things you may need to purchase

If your existing router also provided wireless access and you want that wireless access to remain behind the firewall (strongly recommended!), you'll need to buy a wireless access point. I love the Buffalo line of wireless routers because they run DD-WRT, an open-source, linux based router firmware. It's inexpensive -- $39 on Amazon, and with the routing capabilities disabled, you'll have a 4-port switch with wireless access. If your existing router allowed you to plug more than one device into it (and you're currently doing just that), you'll need to purchase a switch (unless you elected to buy the wireless access point with a switch). Here's the one I purchased

Preparing to replace your existing router - Figuring out the external side of the network

Option 1: Get rid of your existing router. Obviously you can't do this if your existing router is also your cable or DSL modem.
Option 2: Disable your existing cable or DSL modem's routing capability.
Option 3: Leave your existing router in place, disable all firewalling and setup your new router as a DMZ host.
You'll want to pick one of these. The steps for doing this vary depending on your existing hardware. I (currently) use Comcast Business internet and short of purchasing a static IP, I'm stuck with option #3. I've been running this way for some time now and it's not caused any problems other than having to remember I have one added bit of network complexity.

Setting up the External network

If you are using a USB external network adapter and have not yet plugged it in, plug it in (but leave the network cable disconnected).
$ su
(enter your password)
yast
We could have sudo'd that, but we're going to be doing a lot as root to get this ready, so let's live dangerously.
Select Network Devices and Network Settings Select your external network adapter and choose Edit. On my setup, my external USB adapter is eth1. I'll be referring to eth1 a bit in the future as we issue commands from the shell. If yours has a different name (as it does on my other device), remember it and substitute it whenever you see eth1 in a command. If you picked option 3 above, you'll follow the directions for assigning a static, non-routable, IP address. If you picked option 1 or 2, you probably want to leave the external side as DHCP unless you were assigned or pay for a static IP from your ISP.

Getting the existing network settings

Determine the network that your existing router assigns addresses. If you have a windows box behind the firewall, you can drop to a command prompt and type "ipconfig /all". Locate the IP address assigned and the subnet mask to determine the address. In my case, the IP address is 192.168.0.70 and subnet mask is 255.255.255.0 (/24). This means that my existing router's network is 192.168.1.0/24. Write down your DNS settings. Go to your router's configuration and locate the DHCP and LAN settings.

For Option 3 folks, only

Since we're already using an IP address on your router's existing internal network, we need to change the network that the existing router uses. I've switched mine to use 192.168.1.0/24 (192.168.1.x network with a subnet mask of 255.255.255.0) as the network and 192.168.1.1 as the router's address. I've also disabled DHCP since I'll be assigning the external network address statically. Consult your router's manual on how to make these changes, it'll be different for everyone. Oh, and when you make these changes, you're going to disconnect yourself from the internet. Your current PC will have an address on a different network and won't know how to get to the gateway, so before you save your settings, make sure you're positive they're right. If you screw it up, consult your router manual on how to hard reset the router and try again.

Assigning a Static IP

If you have paid for a routable, static IP address from your ISP, find the documentation they gave you to identify the correct DNS, Gateway and IP address and Subnet Mask to assign here. If not, use the values you discovered from the previous section.
Select Statically assigned IP address, put in the address from your ISP or from what you selected above.
Select ubnet Mask, enter the value from your ISP or from what you discovered above.
Select Hostname. Use the same hostname you used during installation, but add a "-ext" to signify that this IP points to the external side of the router.
Select General and tab over to Firewall Zone. Select External Zone. This will cause the firewall to filter all unsolicited traffic that you haven't explicitly allowed. We'll be doing a lot more customization later.
Hit F10 to save these values.
Select Routing.
Under Default IPv4 Gateway, enter the value from your ISP or from what you discovered above. This is the dumb "if there is no other explicitly assigned route, use this one" route.
Put an [X] in the box next to Enable IP Forwarding.
Hit F10 to save these values.
Select Security and Users and then Firewall. Select Masquerading.
Put an [X] in the box next to Masquerade Networks.

DHCP

Run the following:
# zypper in dhcp yast2-dhcp-server dhcp-server
# yast
Select Network Services then DHCP Server.
Select your internal network adapter, choose Select.
Make sure there's an X only next to the internal network adapter.
Select Open Firewall for Selected Interfaces. Choose Next.
On the next screen, choose Domain Name and enter the Active Directory domain name (yourdomain.net).
For Primary Name Server, Secondary Name Server, Default Gateway, and NTP Time Server, enter the IP address of the internet network adapter on your new router. Leave the rest blank.
For IP Address Range, select a range you'd like to assign to the pool. I used 192.168.1.100 - 192.168.1.200.
Under Service Start, select When Booting. Select "Next" and Quit.

Plug it all in

At this point, you need to unplug all of the ethernet cables from your router (If your router has an internet side that is Ethernet, unplug all ethernet cables except that one). Plug your external network adapter into the existing router. Plug the cables that were going to your existing router into a switch and plug that switch into the new router. If you have a relatively simple network and your ISP provided you with a router, your existing network probably looked something like this:
               /--------\
[INTERNET]=====| Router |=======[Computer]
               |  from  |=======[Computer]
               |  your  |=======[Xbox]
               |  ISP   |=======[Wireless Access Point]
               \--------/
It's now going to look something like this.
               /--------\    /---------\   /--------\
[INTERNET]=====| Router |====| Awesome |===| Switch |===[Computer]
               |  from  |    |   new   |   | or WAP |===[Computer]
               |  your  |    |  Linux  |   | with   |===[Xbox]
               |  ISP   |    | Router  |   | switch |===[Roku]
               \--------/    \---------/   \--------/
It's a little more complex. If you purchased a pure wireless access point that isn't a router and doesn't include a switch, you'll plug that into the switch. If you picked a combined unit that is switch and wireless, you'll have something similar to the above.

Refreshing your internal devices

The easiest way is to simply reboot. However, for Windows boxes, you can drop to a Administrator command prompt and type "ipconfig /release" followed by "ipconfig /renew". You should see that you've now been assigned an address from your linux router.

On the off chance you're using an AX88179 USB 3.0 Ethernet Adapter

The kernel module that supports this device is a little flaky, so we're going to install the reference driver. It can be found by searching here. Run the following:
$ su
(enter your password)
# zypper in kernel-default-devel kernel-devel kernel-source
# cd ~
# wget http://www.asix.com.tw/FrootAttach/driver/AX88179_178A_LINUX_DRIVER_v1.8.0_SOURCE.tar.bz2
# tar -xfv AX88179_178A_LINUX_DRIVER_v1.8.0_SOURCE.tar.bz2
# cd AX88179_178A_LINUX_DRIVER_v1.8.0_SOURCE.tar.bz2
# make
# make install

Tuning the Router

By default, Linux isn't tuned for routing large amounts of traffic. Most of the default settings are fine, however, if you use BitTorrent or any P2P, you'll discover right away the limitations of the default settings. Here are some settings I have applied to my router to improve streaming video and applications that create large numbers of connections. Most of these settings were adapted from sources that I googled. Some may be wildly wrong and may show my ignorance of the Linux networking stack.
$ su
(enter your password)
# echo 'ifconfig eth0 txqueuelen 10000' >> /etc/rc.d/after.local
(If your adapter isn't called eth0 and eth1, replace these with what the adapters are actually called)
# echo 'ifconfig eth1 txqueuelen 10000' >> /etc/rc.d/after.local
# nano /etc/sysctl.conf
Add the following to the bottom of the file (adapted primarily from Linux Network Tuning for 2013:
# Increase system file descriptor limit
fs.file-max = 100000

# Discourage Linux from swapping idle processes to disk (default = 60)
vm.swappiness = 10

# Increase ephermeral IP ports
net.ipv4.ip_local_port_range = 10000 65000

# Increase Linux autotuning TCP buffer limits
# Set max to 16MB for 1GE and 32M (33554432) or 54M (56623104) for 10GE
# Don't set tcp_mem itself! Let the kernel scale it based on RAM.
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 16777216
net.core.wmem_default = 16777216
net.core.optmem_max = 40960
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# Make room for more TIME_WAIT sockets due to more clients,
# and allow them to be reused if we run out of sockets
# Also increase the max packet backlog
net.core.netdev_max_backlog = 50000
net.ipv4.tcp_max_syn_backlog = 30000
net.ipv4.tcp_max_tw_buckets = 2000000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 10

# Disable TCP slow start on idle connections
net.ipv4.tcp_slow_start_after_idle = 0

# If your servers talk UDP, also up these limits
net.ipv4.udp_rmem_min = 8192
net.ipv4.udp_wmem_min = 8192

# Log packets with impossible addresses for security
net.ipv4.conf.all.log_martians = 1
Save and Exit.
# nano /etc/security/limits.conf
Add the following to the bottom of the file:
* soft nofile 100000
* hard nofile 100000

Very Optional - Map IRQ of Network Adapter to Specific Core

I was having some network problems related to UDP performance over a double NAT (though I didn't discover that until I did a myriad of packet captures). This was one of the steps I attempted while troubleshooting. It was a shot in the dark, and ultimately I left it out of my configuration. In case you're in the same spot, I thought I'd leave the instructions. After completing this section, you'll add a line to /etc/rc.d/after.local to run the script with the network adapter name and the core of the CPU you wish to tie it to. More comprehensive instructions are available here.
# nano /usr/local/bin/myri-irq-bind.sh
Paste in the following:
#!/bin/sh
#set -x

if [ $# -eq 0 ]; then
   echo "usage: msixbind.sh INTERFACE [CPU#]"
   exit 1;
fi

eth=$1
mask=$2

echo "Binding interface $eth"
pid=`pgrep irqbalance`
   if [ $? -eq 0 ];
   then
       echo "irqbalance is running! Pid = $pid"
       echo "it will undo anything done by this script"
       echo "Please kill it and re-run this script"
       exit
   fi

done=0
i=0
slice=0
start=0
num_slices=`grep "${eth}" /proc/interrupts | wc -l`
while [ $done != 1 ]
do
# one of the following, depending on which version of the driver is installed
   irq_data=`grep "${eth}:slice-${slice}" /proc/interrupts`

   if [ $? != 0 ];
   then
       if [ $i != 0 ];
       then
           exit
       fi
       irq_data=`grep "${eth}" /proc/interrupts`
       if [ $? != 0 ];
       then
           exit
       fi
   fi
   irq=`echo $irq_data |  awk '{print $1 ; }' | sed -e 's/://g'`
   file="/proc/irq/${irq}/smp_affinity"
   printf "Binding slice %2d to CPU %2d: writing mask 0x%08x to $file\n" $slice $mask $mask
   printf "%x" $mask > $file
   i=`expr $i + 1`
   slice=`expr $slice + 1`
   if [ $slice -eq $num_slices ];
   then
       exit
   fi
done
Save and exit.
Run the following:
# chmod 750 /usr/local/bin/myri-irq-bind.sh

No comments: