2022 Home Router Refresh
With the new hardware up and running, it's time to redeploy new zones. We're going to start with the router zone, and as it's almost 2022, I'm going to call this article my 2022 Home Router Refresh.
This article will overview rebuilding my zone based SmartOS router, and draw from three of my previous ones, specifically Home Router on SmartOS, Hurricane Electric on SmartOS, and Tunnels on SmartOS. If you'd like more of the theory behind why I'm doing what I'm doing, look to those articles, this is going to be much more nuts and bolts.
Lets dive in.
Overview
We will be deploying a router that will be responsible for routing between the isolated private subnets in my new network deployment, as well as providing common home network services such as dynamic host configuration protocol (DHCP), domain name service (DNS), and network address translation (NAT).
In addition, it will be acting as the terminal for a tunnel going out to Hurricane Electric for IPv6 connectivity, as well as IPSec tunnels going to production systems.
Interfaces
The local interfaces that we'll be working with were listed in the previous article, but for simplicity, are the following:
- Public:
net0: nic:admin, vlan_id:5, dhcp, autoconf
- Infrastructure:
net1: nic:admin, vlan_id:1, 172.22.1.0/27, IPv6/64
- Embedded:
net2: nic:admin, vlan_id:2, 172.22.1.32/27, IPv6/64
- Internal:
net3: etherstub:internal, 172.22.1.64/27, IPv6/64
- External:
net4: etherstub:external, 172.22.1.96/27, IPv6/64
- Secure:
net5: nic:admin, vlan_id:3, 172.22.1.128/26, IPv6/64
- Guest:
net6: nic:admin, vlan_id:4, 172.22.1.192/26, IPv6/64
The public interface may end up being directly connected at some point, the 1000BASE-T SFP module I have doesn't play well with the NDC in my compute node, so for now connecting to the public internet through my switch with a dedicated VLAN is how I'm going to proceed forward at this time. Whenever this network switches up to anything faster, there are still two free SFP+ interfaces on the back of the server to handle that interconnect. in the future.
Also, I may end up defining an additional interconnect for the 40GBE interface at the router or experiment with 802.1D bridging via dladm create-bridge
, we will see when I get there.
Additionally, I will be creating the following tunnel interfaces:
- Hurricane Electric:
v4_he0
- Production Tunnels:
v4_ze0, v4_ze1
Note that I tend to use the v4_
prefix when referring to IPv4 based tunnels and the v6_
prefix when referring to IPv6 based tunnels.
Zone Manifest
This is the manifest that I used to create router zone:
{
"image_uuid": "1d05e788-5409-11eb-b12f-037bd7fee4ee",
"brand": "joyent",
"alias": "router",
"hostname": "router",
"dns_domain": "ewellnet",
"cpu_cap": 200,
"max_physical_memory": 128,
"quota": 10,
"resolvers": [ "::1" ],
"nics": [
{
"nic_tag": "admin",
"vlan_id": 5,
"interface": "net0",
"ips": ["dhcp","addrconf"],
"primary": true,
"allow_ip_spoofing": "true"
},
{
"nic_tag": "admin",
"interface": "net1",
"ips": ["172.22.1.1/27","addrconf"],
"allow_dhcp_spoofing": "true",
"allow_ip_spoofing": "true"
},
{
"nic_tag": "admin",
"vlan_id": 2,
"interface": "net2",
"ips": ["172.22.1.33/27","addrconf"],
"allow_dhcp_spoofing": "true",
"allow_ip_spoofing": "true"
},
{
"nic_tag": "internal0",
"interface": "net3",
"ips": ["172.22.1.65/27","addrconf"],
"allow_dhcp_spoofing": "true",
"allow_ip_spoofing": "true"
},
{
"nic_tag": "external0",
"interface": "net4",
"ips": ["172.22.1.97/27","addrconf"],
"allow_dhcp_spoofing": "true",
"allow_ip_spoofing": "true"
},
{
"nic_tag": "admin",
"vlan_id": 3,
"interface": "net5",
"ips": ["172.22.1.129/26","addrconf"],
"allow_dhcp_spoofing": "true",
"allow_ip_spoofing": "true"
},
{
"nic_tag": "admin",
"vlan_id": 4,
"interface": "net6",
"ips": ["172.22.1.193/26","addrconf"],
"allow_dhcp_spoofing": "true",
"allow_ip_spoofing": "true"
}
]
}
Note that I'm setting a resolver of ::1
in the zone manifest simply because that is ultimately what it will be set to, you will want to temporarily alter /etc/resolv.conf
until you have dnsmasq
properly configured.
Creation command and login:
# vmadm create -f router.json
Successfully created VM f8510d07-3852-ebf0-83f3-e850f1ceb5fa
# zlogin f8510d07-3852-ebf0-83f3-e850f1ceb5fa
Configuring NAT & Routing
Edit /etc/ipf/ipnat.conf
so that it will translate IPv4 packets routed from the private networks out onto the internet:
map net0 172.22.1.0/24 -> 0/32 proxy port ftp ftp/tcp
map net0 172.22.1.0/24 -> 0/32 portmap tcp/udp auto
map net0 172.22.1.0/24 -> 0/32
Enable IPFilter and IPv4 forwarding:
# svcadm enable ipfilter
# routeadm -ue ipv4-forwarding
Then lets verify that it's working properly:
# ipnat -l
List of active MAP/Redirect filters:
map net0 172.22.1.0/24 -> 0.0.0.0/32 proxy port ftp ftp/tcp
map net0 172.22.1.0/24 -> 0.0.0.0/32 portmap tcp/udp auto
map net0 172.22.1.0/24 -> 0.0.0.0/32
List of active sessions:
Installing Dnsmasq
I've come to really appreciate dnsmasq for how small and light it is, and yet how generally capable it is in a home network setting. Sure it isn't the fastest, and it doesn't do everything, but it's fantastic just how much it accomplishes while using minimal resources.
Installation and configuration is a breeze:
# pkgin in dnsmasq
And configuration isn't too difficult either:
/opt/local/etc/dnsmasq.conf:
# DNS specific configuration
# Set DNS cache size
cache-size=8192
# Never forward plain names (without a dot or domain part)
domain-needed
# Never forward addresses in the non-routed address spaces.
bogus-priv
# We don't want dnsmasq to poll resolv files for changes, we will manually notify it with a refresh
no-poll
# Upstream resolvers, use this so that /etc/resolv.conf can refer to localhost
resolv-file=/etc/resolv.upstream
# Domain for local services which should never be forwarded to external resolvers
local=/ewellnet/
domain=ewellnet
# Shared configuration
# Interfaces used for DHCP & DNS
interface=net1
interface=net2
interface=net3
interface=net4
interface=net5
interface=net6
# DHCP specific configuration
# DHCP ranges
dhcp-range=172.22.1.2,172.22.1.30,255.255.255.224,6h
dhcp-range=172.22.1.34,172.22.1.62,255.255.255.224,6h
dhcp-range=172.22.1.130,172.22.1.190,255.255.255.192,6h
dhcp-range=172.22.1.194,172.22.1.254,255.255.255.192,6h
# Read /etc/ethers for static DHCP allocations
read-ethers
# Set the NTP time server addresses to the interface's
dhcp-option=option:ntp-server
# This should be the authoritative DHCP server on the network
dhcp-authoritative
For any static assignments, set records in /etc/hosts
and (optionally) /etc/ethers
. In my case, the records are suffixed with .ewellnet
.
Create the directory at /var/cache
and enable the dnsmasq
service:
# mkdir /var/cache
# svcadm enable dnsmasq
Local DHCP and DNS should now be up and running.
Secure SSH
Depending on your disposition, you may want to prevent password based logins through SSH or disable the service all together. I usually opt for the latter for home, though I'm sure that'll bite me in the future some time.
# svcadm disable ssh
Tunnel to Hurricane Electric
At the time of this writing, Ziply Fiber still hasn't rolled out IPv6 connectivity to end users. This is okay though, since we have Hurricane Electric.
Login to tunnelbroker.net to set and acquire your necessary credentials. Since these addresses are global, I will be making the following replacements for the below examples:
- Server IPv4 address:
1.2.3.4
- Client IPv4 address:
5.6.7.8
- Server IPv6 address:
2001:470::1
- Client IPv6 address:
2001:470::2
Run the following commands to bring a Hurricane Electric tunnel up and route default IPv6 traffic over it:
# dladm create-iptun -T ipv4 -a local=5.6.7.8,remote=1.2.3.4 v4_he0
# ipadm create-addr -T static -a local=2001:470::2,remote=2001:470::1 v4_he0/v6
# route -p add -inet6 default 2001:470::1
Test by pinging any well known IPv6 enabled domain:
# ping -A inet6 google.com
google.com is alive
Our router can now access the IPv6 Internet.
Advertise Routed IPv6 over Hurricane Electric
IPv6 connectivity to our router isn't terribly helpful on it's own; we'll want to be able to provide routing to our attached devices.
First up, we'll need to start forwarding IPv6 packets:
# routeadm -ue ipv6-forwarding
While I would normally make use of dnsmasq's IPv6 router advertisement features, the ndp
daemon is normally already running with addrconf
enabled on an interface, so we might as well just use the native tools here, especially since they'll manipulate the routes table on the router for us.
/etc/inet/ndpd.conf:
if net1 AdvSendAdvertisements 1
prefix 2001:470:????::/64 net1
if net2 AdvSendAdvertisements 1
prefix 2001:470:????:1::/64 net2
if net3 AdvSendAdvertisements 1
prefix 2001:470:????:2::/64 net3
if net4 AdvSendAdvertisements 1
prefix 2001:470:????:3::/64 net4
if net5 AdvSendAdvertisements 1
prefix 2001:470:????:4::/64 net5
if net6 AdvSendAdvertisements 1
prefix 2001:470:????:5::/64 net6
And then restart (not refresh) ndp.
# svcadm restart ndp
You should now see functional IPv6 connectivity on every stateless device on your network.
Establish IPSec Policy & Key Exchange
I also run IPSec tunnels to some production zones in a local datacenter. These tunnels will be called ze0
and ze1
. First of all, we're going to need to set the encryption policy, specifically what encryption and authentications will be accepted over these tunnels:
/etc/inet/ipsecinit.conf:
{tunnel v4_ze0 negotiate tunnel}
ipsec {encr_algs aes encr_auth_algs sha512 sa shared}
{tunnel v4_ze1 negotiate tunnel}
ipsec {encr_algs aes encr_auth_algs sha512 sa shared}
After ensuring that the same settings are present on the remote endpoints, I restart ipsec policy:
# svcadm restart ipsec/policy
Next up is to configure IKE. Normally I would be fancy and use public keys, but pre-shared should be fine in this case:
/etc/inet/ike/config:
p1_lifetime_secs 7200
p1_nonce_len 40
p1_xform {
auth_method preshared
oakley_group 5
auth_alg sha512
encr_alg aes
}
p2_pfs 2
{
label "v4_ze0"
local_addr 1.2.3.4
remote_addr 5.6.7.8
p1_xform {
auth_method preshared
oakley_group 5
auth_alg sha512
encr_alg aes
}
p2_pfs 5
}
{
label "v4_ze1"
local_addr 1.2.3.4
remote_addr 5.6.7.9
p1_xform {
auth_method preshared
oakley_group 5
auth_alg sha512
encr_alg aes
}
p2_pfs 5
}
Generate some large random 64-byte keys for each tunnel. These are example keys only, do not use them production:
# openssl rand -hex 128
0f5451e4e0ad601f64dad77b9a83a0214dd4f460e07935936cfc7faf24c90f1c65609aaebc817ea17894748d999c94324d53a50ba18ce62c7683966674c3d50f93d1333440709f92facd1d10bcfde5b9ccc05e20adfa00a14825025068ba29cac04ba75b3b7e909dfddc01bd84eec2d56fa47a151cd447d0342f4194a910be9c
# openssl rand -hex 128
daa7fafa568601172b01591465e6365f90ad41eb6a778e7730f9ae5ebb9e05b9792fe288a1d7fa9950a2659433ad2b2f31087bc8e3c8ebbc80ef1c1d604847371cf4d1298ebb0b7fe4835b8bd13e969d700312a929ec8c83f63760a8a7b7439441b9e1ec27862c4294214df8ca03bd02eb62c9da6980108e27106c566b3ad7ea
Create some preshared secret files with these keys:
/etc/inet/secret/ike.preshared:
{
localidtype IP
localid 1.2.3.4
remoteidtype IP
remoteid 5.6.7.8
key 0f5451e4e0ad601f64dad77b9a83a0214dd4f460e07935936cfc7faf24c90f1c65609aaebc817ea17894748d999c94324d53a50ba18ce62c7683966674c3d50f93d1333440709f92facd1d10bcfde5b9ccc05e20adfa00a14825025068ba29cac04ba75b3b7e909dfddc01bd84eec2d56fa47a151cd447d0342f4194a910be9c
}
{
localidtype IP
localid 1.2.3.4
remoteidtype IP
remoteid 5.6.7.9
key daa7fafa568601172b01591465e6365f90ad41eb6a778e7730f9ae5ebb9e05b9792fe288a1d7fa9950a2659433ad2b2f31087bc8e3c8ebbc80ef1c1d604847371cf4d1298ebb0b7fe4835b8bd13e969d700312a929ec8c83f63760a8a7b7439441b9e1ec27862c4294214df8ca03bd02eb62c9da6980108e27106c566b3ad7ea
}
After the reciprocal configuration has been set on the remote side, enable or restart IKE:
# svcadm enable ipsec/ike
Establish IPSec Tunnels & Routing
With all of our IPSec policy and key exchange work out of the way, we can finally test it by establishing some tunnels. We'll do that with dladm
:
# dladm create-iptun -T ipv4 -a local=1.2.3.4,remote=5.6.7.8 v4_ze0
# dladm create-iptun -T ipv4 -a local=1.2.3.4,remote=5.6.7.9 v4_ze1
Next up is to bring up a local interface for the tunnel, creating addresses will do this implicitly, so we'll just do that.
# ipadm create-addr -T static -a local=172.22.1.1,remote=10.0.0.1 v4_ze0/v4
# ipadm create-addr -T static -a local=172.22.1.1,remote=10.0.0.2 v4_ze1/v4
After performing the reciprocal steps on the remote host, you should be able to ping the remote side through the tunnel interface:
# ping 10.0.0.1
10.0.0.1 is alive
Next up is to set some static routes. Despite plumbing the interface for IPv6, in my case I'll only be actively using IPv4 over it immediately:
# route -p add 10.0.0.0/28 10.0.0.1
# route -p add 10.0.0.0/28 10.0.0.2
You should now be able to access the remote side of the IPSec tunnel through any subnet attached to this router.
Securing Everything
Next up is to ensure that everything is secure, and only communicating with what it should be. We will achieve this by altering the host model and setting up an inclusive firewall.
Normally I would perform this step earlier and then build into a secure environment. That can make troubleshooting quite difficult, so this time I built everything out first with the intention of locking it all down afterwards.
Strong Host Model
It's often a good idea to discard packets that arrive on unexpected interfaces. We can do this easily in SmartOS by changing the hostmodel to strong by using ipadm set-prop
:
# ipadm set-prop -p hostmodel=strong ipv4
# ipadm set-prop -p hostmodel=strong ipv6
This will ensure that any packets that come in through an interface that's doesn't correlate with the interfaces in the routing table are dropped.
Firewall
The next thing to do is to ensure that the router is allowing only the correct traffic flows between subnets using IP Filter. We're going to enact the following policies:
- IPv6 Traffic from the Internet can reach external services
172.22.1.96/27
. - Traffic from infrastructure
172.22.1.0/27
can reach the Internet, internal services172.22.1.64/27
, external services172.22.1.96/27
and the IPSec tunnelsv4_ze0
andv4_ze1
. - Traffic from embedded
172.22.1.32/27
can access internal services172.22.1.64/27
. - Traffic from internal services
172.22.1.64/27
can access the Internet, infrastructure172.22.1.0/27
, embedded172.22.1.32/27
, external services172.22.1.96/27
and secure172.22.1.128/26
. - Traffic from external services
172.22.1.96/27
can reach the Internet. - Traffic from secure
172.22.1.128/26
can reach the Internet, internal services172.22.1.64/27
and external services172.22.1.96/27
. - Traffic from guest
172.22.1.192/26
can reach the Internet and external services172.22.1.96/27
. - Traffic from the work IPSec tunnels
v4_ze0
andv4_ze1
can reach infrastructure172.22.1.0/27
.
Note: For the sake of this list, "The Internet" includes IPv4 connectivity via NAT through net0
and IPv6 connectivity via the v4_he0
Hurricane Electric tunnel. Also the IPSec connection between infrastructure and remote infrastructure will likely be constrained further to only allow for administrative console access to the remote network. Next up is to implement these policies in configuration:
/etc/ipf/ipf.conf:
# Traffic from infrastructure (net1) [172.22.1.0/27]
block in on net1 from any to 172.16.0.0/12
pass in quick on net1 from 172.22.1.0/27 to 172.22.1.64/26
pass in quick on net1 from 172.22.1.0/27 to 172.22.1.0/27
# Traffic from embedded (net2) [172.22.1.32/27]
block in on net2 all
pass in quick on net2 from 172.22.1.32/27 to 172.22.1.64/27
pass in quick on net2 from 172.22.1.32/27 to 172.22.1.32/27
# Traffic from internal (net3) [172.22.1.64/27]
block in on net3 from any to 172.16.0.0/12
pass in quick on net3 from 172.22.1.64/27 to 172.22.1.128/26
pass in quick on net3 from 172.22.1.64/27 to 172.22.1.0/25
# Traffic from external (net4) [172.22.1.96/27]
block in on net4 from any to 172.16.0.0/12
pass in quick on net4 from 172.22.1.96/27 to 172.22.1.96/27
block out on net4 from 172.16.0.0/12 to any
pass out quick on net4 proto tcp from 172.16.0.0/12 to 172.22.1.96/27 flags S keep state
pass out quick on net4 proto udp from 172.16.0.0/12 to 172.22.1.96/27 keep state
pass out quick on net4 proto icmp from 172.16.0.0/12 to 172.22.1.96/27 icmp-type echo keep state
pass out quick on net4 from 172.22.1.96/27 to 172.22.1.96/27
# Traffic from secure (net5) [172.22.1.128/26]
block in on net5 from any to 172.16.0.0/12
pass in quick on net5 from 172.22.1.128/26 to 172.22.1.64/26
pass in quick on net5 from 172.22.1.128/26 to 172.22.1.128/26
# Traffic from guest (net6) [172.22.1.192/26]
block in on net6 from any to 172.16.0.0/12
pass in quick on net6 from 172.22.1.192/26 to 172.22.1.96/27
pass in quick on net6 from 172.22.1.192/26 to 172.22.1.192/26
# Traffic from IPsec tunnels (v4_ze0,v4_ze1) [192.168.255.224/27]
block in on v4_ze0 all
pass in quick on v4_ze0 from 10.0.0.0/27 to 172.22.1.0/27
block in on v4_ze1 all
pass in quick on v4_ze1 from 10.0.0.0/27 to 172.22.1.0/27
/etc/ipf/ipf6.conf:
# Traffic from public (he0) [::/0]
block in on he0 from any to 2001:470::/48
pass in quick on he0 from any to 2001:470:0:3::/64
block out on he0 from 2001:470::/48 to any
pass out quick on he0 from 2001:470:0:3::/64 to any
pass out quick on he0 proto tcp from 2001:470::/48 to any flags S keep state
pass out quick on he0 proto udp from 2001:470::/48 to any keep state
#pass out quick on he0 proto icmp from 2001:470::/48 to any icmp-type echo keep state
# Traffic from infrastructure (net1) [2001:470::/64]
block in on net1 from any to 2001:470::/48
pass in quick on net1 from 2001:470::/64 to 2001:470:0:2::/63
pass in quick on net1 from 2001:470::/64 to 2001:470::/64
# Traffic from embedded (net2) [2001:470:0:1::/64]
block in on net2 all
pass in quick on net2 from 2001:470:0:1::/64 to 2001:470:0:2::/64
pass in quick on net2 from 2001:470:0:1::/64 to 2001:470:0:1::/64
# Traffic from internal (net3) [2001:470:0:2::/64]
block in on net3 from any to 2001:470::/48
pass in quick on net3 from 2001:470:0:2::/64 to 2001:470:0:4::/64
pass in quick on net3 from 2001:470:0:2::/64 to 2001:470::/62
# Traffic from external (net4) [2001:470:0:3::/64]
block in on net4 from any to 2001:470::/48
pass in quick on net4 from 2001:470:0:3::/64 to 2001:470:0:3::/64
block out on net4 from 2001:470::/48 to any
pass out quick on net4 proto tcp from 2001:470::/48 to 2001:470:0:3::/64 flags S keep state
pass out quick on net4 proto udp from 2001:470::/48 to 2001:470:0:3::/64 keep state
#pass out quick on net4 proto icmp from 2001:470::/48 to 2001:470:0:3::/64 icmp-type echo keep state
pass out quick on net4 from 2001:470:0:3::/64 to 2001:470:0:3::/64
# Traffic from secure (net5) [2001:470:0:4::/64]
block in on net5 from any to 2001:470:0::/48
pass in quick on net5 from 2001:470:0:4::/64 to 2001:470:0:2::/63
pass in quick on net5 from 2001:470:0:4::/64 to 2001:470:0:4::/64
# Traffic from guest (net6) [2001:470:0:5::/64]
block in on net6 from any to 2001:470:0::/48
pass in quick on net6 from 2001:470:0:5::/64 to 2001:470:0:3::/64
pass in quick on net6 from 2001:470:0:5::/64 to 2001:470:0:5::/64
# Traffic from IPsec tunnels (v4_ze0,v4_ze1)
block in on v4_ze0 all
block in on v4_ze1 all
Reload ipfilter
to enable those rules:
# svcadm refresh ipfilter
This enables a stateful IPv4 firewall around external to prevent it from accessing the other network segments without preventing them from accessing it, as well, the IPv6 firewall rules enable two stateful layers, one around the external network segment and another one protecting all networks except for the external segment from access.
Please note: Using a firewall like IP Filter severely limits network performance. While I have taken steps above to mitigate the impact by reducing the firewall rules to their simplest forms, I will be investigating further possible steps to take in the future.
Forwarding Traffic
There are some additional exceptions that need to be set for various services running in this network, namely:
- Forward NTP traffic
udp/123
directed to any of the non-routable router addresses to the SmartOS compute node. - Forward Plex traffic
tcp/31400
to the Plex Media Server zone. - I'm sure many more.
These forwarding rules can be added to /etc/ipf/ipnat.conf
along with the map rules we added before:
/etc/ipf/ipnat.conf:
map net0 172.22.1.0/24 -> 0/32 proxy port ftp ftp/tcp
map net0 172.22.1.0/24 -> 0/32 portmap tcp/udp auto
map net0 172.22.1.0/24 -> 0/32
# Plex redirection (public->media)
rdr net0 0.0.0.0/0 port 32400 -> 172.22.1.70 port 32400 tcp
# NTP server redirection (all->hv-1)
rdr net1 172.22.1.1/32 port 123 -> 172.22.1.8 port 123 udp
rdr net2 172.22.1.33/32 port 123 -> 172.22.1.8 port 123 udp
rdr net3 172.22.1.65/32 port 123 -> 172.22.1.8 port 123 udp
rdr net4 172.22.1.97/32 port 123 -> 172.22.1.8 port 123 udp
rdr net5 172.22.1.129/32 port 123 -> 172.22.1.8 port 123 udp
rdr net6 172.22.1.193/32 port 123 -> 172.22.1.8 port 123 udp
IP Filter will need to be refreshed once more:
# svcadm refresh ipfilter
Conclusion
This should be a pretty solid start to a router zone, though I may end up changing some things around, namely:
- Some of the high throughput communication paths may end up requiring some zones to be moved to different networks to reduce the traffic being filtered through IP Filter and hopefully improve network performance.
- Adding IPSec policies for mobile devices such as Laptops and Smart Phones to access network resources away from home.
- Adding QoS for IP telephony traffic.
But for now, this should do.