Home Router on SmartOS

A SmartOS zone can serve quite nicely as a NATing router for a home network. It works incredibly well on the HP N54L Microserver in concert with other VMs.

Note: This guide has been heavily influenced by the SmartOS wiki article on NAT using Etherstubs.

In addition to the SmartOS NAT guide, we will be installing several other services on our router, namely dnsmasq for DNS and DHCP service, as well as optionally haproxy for TCP port forwarding.

Identify your interfaces

Most home routers have two network interfaces, internal and external. We're going to do the same. Our internal network interface is the one we're going to connect to our local network, and the one we will be NATing packets for. Our external network interface should be the one directly connected to the wild Internet (or a cable modem), but shouldn't yet have anything configured on.

There are a few different ways this can be done in SmartOS.

The easy way

The simplest configuration is to have two different physical network interfaces in the global zone: one dedicated for admin and one for external. For simplicity's sake, we will be using the admin network as our internal network.

For this approach, we will need to correctly identify and tag each interface. Lets check them out on the global zone:

[root@gz ~]# dladm show-phys -m
LINK         SLOT     ADDRESS            INUSE CLIENT
e1000g0      primary  00:11:22:33:44:55  yes  e1000g0
bge0         primary  66:77:88:99:aa:bb   no  bge0

In the above example, e1000g0 is the admin interface while bge0 will become our external interface. Check the global zone configuration to confirm that admin is 00:11:22:33:44:55 and add a directive to indicate that external is 66:77:88:99:aa:bb.

/usbkey/config

admin_nic=00:11:22:33:44:55
...
external_nic=66:77:88:99:aa:bb
...

If you don't want to reboot, use nictagadm to update the running system with the new tag.

[root@gz ~]# nictagadm add external 66:77:88:99:aa:bb

In the zone configuration below, you should use the following to describe the network interfaces:

...
  "nics": [
    {
      "nic_tag": "external",
      "interface": "net0",
      "ips": ["dhcp"],
      "primary": true,
      "allow_ip_spoofing": "true"
    },
    {
      "nic_tag": "admin",
      "interface": "net1",
      "ips": ["192.168.0.1/24"],
      "allow_dhcp_spoofing": "true",
      "allow_ip_spoofing": "true"
    }
  ],
...

The minimalist way

If you have only one physical network interface in the global zone (and a VLAN capable switch), you can still use SmartOS as a home router. In this case we will use the untagged admin interface as the internal network and VLAN ID 2 packets as the external interface.

This approach takes no reconfiguration of the global zone, however you will need to configure your switch to route external frames appropriately, which is beyond the scope of this post.

In the zone configuration below, you should use the following to describe the network interfaces:

...
  "nics": [
    {
      "nic_tag": "admin",
      "vlan_id": 2,
      "interface": "net0",
      "ips": ["dhcp"],
      "primary": true,
      "allow_ip_spoofing": "true"
    },
    {
      "nic_tag": "admin",
      "interface": "net1",
      "ips": ["192.168.0.1/24"],
      "allow_dhcp_spoofing": "true",
      "allow_ip_spoofing": "true"
    }
  ],
...

The awesome way

While both of the above configurations are completely valid ways to configure the network, they're both lacking in the performance department. Think about it: If you've got two or more network interfaces, why haven't you aggregated them yet?

This configuration requires VLAN and LACP capable switching fabric, which isn't common in the home. The changes to the global zone configuration are as follows:

/usbkey/config:

...
aggr0_aggr=00:11:22:33:44:55,66:77:88:99:aa:bb
aggr0_lacp_mode=active

admin_nic=aggr0
...

While you certainly can manually configure this as well, I recommend rebooting the global zone to apply this.

Since you now effectively have one network interface, follow the minimalist way instructions above.

Configure Zone

Note: In the following vm manifests the default admin network is shared with the site's internal network. This could be a bad idea if you're using any KVM VMs, as by default, there is no authentication protecting VNC access to the console of the KVM instance. This is a non-issue if you're exclusively using Solaris Zones or LX Branded Zones.

Specifying the same MAC address as your last router increases the likelihood of getting the same IP address from your residential ISP's DHCP service. If you would like the same IP address, I recommend setting the MAC address to match that of your previous router. If not, I recommend removing the line entirely (#This line).

Notice: If you decided to use one of the alternate network configurations discussed above, change out the nics block below with the one relevant for your configuration.

router.json

{
  "alias": "router",
  "hostname": "router",
  "brand": "joyent",
  "image_uuid": "088b97b0-e1a1-11e5-b895-9baa2086eb33",
  "max_physical_memory": 256,
  "quota": 10,
  "nics": [
    {
      "nic_tag": "external",
      "interface": "net0",
      "mac": "b0:b0:b0:b0:b0:b0",  # This line
      "ips": ["dhcp"],
      "primary": true,
      "allow_ip_spoofing": "true"
    },
    {
      "nic_tag": "admin",
      "interface": "net1",
      "ips": ["192.168.0.1/24"],
      "allow_dhcp_spoofing": "true",
      "allow_ip_spoofing": "true"
    }
  ],
  "resolvers": [ "8.8.8.8", "8.8.4.4" ]
}

Create the router VM and login to it.

[root@00-11-22-33-44-55 ~]# vmadm create -f router.json
Successfully created VM 01234567-89ab-cdef-0123-456789abcdef
[root@00-11-22-33-44-55 ~]# zlogin 01234567-89ab-cdef-0123-456789abcdef

Configure Routing

Edit /etc/ipf/ipnat.conf so that it will NAT IPv4 packets routed through the vm. I recommend uncommenting the rdr line if you're also planning on providing services through haproxy.

map net0 192.168.0.0/24 -> 0/32 proxy port ftp ftp/tcp
map net0 192.168.0.0/24 -> 0/32 portmap tcp/udp auto
map net0 192.168.0.0/24 -> 0/32

# Uncomment the following line if you want the router's external IP address accessible from the internal network
# rdr net1 <public ip>/32 -> 192.168.0.1

Turn on IPv4 forwarding & enable ipfilter

[root@router ~]# routeadm -u -e ipv4-forwarding
[root@router ~]# svcadm enable ipfilter

Verify that NAT is working properly

[root@router ~]# ipnat -l
List of active MAP/Redirect filters:
map net0 192.168.0.0/24 -> 0.0.0.0/32 proxy port ftp ftp/tcp
map net0 192.168.0.0/24 -> 0.0.0.0/32 portmap tcp/udp auto
map net0 192.168.0.0/24 -> 0.0.0.0/32

List of active sessions:

Install dnsmasq and haproxy

[root@router ~]# pkgin in dnsmasq haproxy
reading local summary...
processing local summary...
...
calculating dependencies... done.

nothing to upgrade.
2 packages to be installed (3003K to download, 7790K to install):

dnsmasq-2.75 haproxy-1.6.2

proceed ? [Y/n] y
downloading packages...
...

Configure dnsmasq

Edit /opt/local/etc/dnsmasq.conf. Below are the highlights from the config file.

# Never forward plain names (without a dot or domain part)
domain-needed

# Never forward addresses in the non-routed address spaces.
bogus-priv

# We only service our internal network
interface=net1

# 200 ip addresses means lots of friends can come over
dhcp-range=192.168.0.50,192.168.0.250,6h

# Use /etc/ethers as a static allocation map
read-ethers

# Set the NTP time server addresses to 192.168.0.9 and 172.16.0.9
dhcp-option=option:ntp-server,192.168.0.9,172.16.0.9

# This should be the authoritative DHCP server on the network
dhcp-authoritative

# Cache of looked up domain names, don't go too crazy here
cache-size=1200  

Create a directory at /var/cache and enable dnsmasq.

[root@router ~]# mkdir /var/cache
[root@router ~]# svcadm enable dnsmasq

Configure haproxy

While this package is only necessary if you're providing what should be publicly available services from your internal network, it's still useful to have on hand. In our example, we're going to use haproxy on our router to forward traffic to another VM on our internal network that's running nginx.

Create /opt/local/etc/haproxy.cfg

global  
  log localhost local0
  log localhost local1 notice
  maxconn 4096
  uid 99
  gid 99
  daemon

defaults  
  log global
  mode http
  option httplog
  option dontlognull
  option redispatch
  retries 3
  maxconn 1024
  timeout connect 60s
  timeout client 60s
  timeout server 60s

listen http  
  bind *:80
  option forwardfor
  balance roundrobin
  server http01 192.168.0.10

Enable haproxy.

[root@router ~]# svcadm enable haproxy

Disable SSH Logins (optional)

Due to the elevated position of this VM in the network, I disable direct ssh access to it, instead preferring to access it through zlogin from the global zone.

[root@router ~]# svcadm disable ssh

Conclusion

In my next blog post, I will be describing how to establish a tunnel to Hurricane Electric's tunnelbroker.net, and provide ipv6 connectivity into the network.