A little over one year ago, I purchased a Seagate Dockstar on woot.com. Everything about it was irresistible. The price, $25. The efficiency, a 7 watt draw. The power, a 1.2ghz ARM. I hoped to turn it into a firewall router to replace my aging, under-powered WRT54G.

One year later, I’ve rolled an operating system and written several times about my little ARM box, and it lived up to every hope I had.

Operating System

The firmware in NAND was a non-starter, incapable of much beyond serving files and connecting to the PogoPlug service.

Plugbox Linux, now known as Arch Linux ARM, already existed, but Arch has never been a favourite environment of mine.

Dissatisfied, I turned toward my go-to OS, FreeBSD. The wiki proclaimed support for Marvell Kirkwood, so I cracked open the Dockstar, added a serial console connection, and set to work. A week later, I released patches for FreeBSD 8.1 to make it play nice on the little white device. I used it lightly for a few months determining what worked what didn’t, fixing things along the way. Those changes paved the way for an extremely solid 8.2 release earlier this year.

As a Router

I don’t demand much of a home router, just 100% uptime and reliability along with firewalling, IPv4 NAT, IPv6, NTP, DNS, DHCP, and uPNP.

The first glaring limitation of the Dockstar is its single LAN port, correctable with a USB ethernet controller. I have two 10/100 controllers, one USB 1.1 and one USB 2.0, supported by aue and axe, respectively. USB 1.1 negates 100Mbit operation of the aue controller, because in practice it has trouble reaching 10Mbit.

In this example, ue0 is my USB WAN interface, and mge0 is the gigabit LAN interface. I’ve chosen 172.16.0.0/12 as my private IPv4 address range.

We have cable Internet through Comcast, so the WAN link is configured as DHCP. I use SYNCDHCP, which halts the boot process until ue0 gets an IP. PF will fail to initialize at boot otherwise.

/etc/rc.conf ifconfig_ue0="SYNCDHCP" ifconfig_mge0="inet 172.16.0.1 netmask 255.255.255.0"

IPv6 connectivity

My ISP doesn’t offer IPv6 service, but it’s possible to get connected through a tunnel broker such as Hurricane Electric or SixXS. Either service provides a tunnel to get IPv6 traffic over an IPv4-only WAN link as well as a subnet of IPv6 addresses for machines on the LAN. I went with SixXS, because their AICCU daemon updates my tunnel endpoint address automatically if my IPv4 WAN IP changes. Just enable IPv6, assign the LAN interface a v6 address from the assigned subnet, create a gif device, set it as UP at boot, and let AICCU do the rest:

pkg_add -r sixxs-aiccu

/etc/rc.conf ipv6_enable="YES" ipv6_gateway_enable="YES" ipv6_ifconfig_mge0="2001:4830:1632::210:75ff:fe1a:a4f1 prefixlen 64" gif_interfaces="gif0" gifconfig_gif0="up" sixxs_aiccu_enable="YES"

/usr/local/etc/aiccu.conf # AICCU Configuration # Login information (defaults: none) username NAR-SIXXS/T56021 password likeIwouldgivethisout # Protocol and server to use for setting up the tunnel (defaults: none) #protocol <tic|tsp|l2tp> protocol tic server tic.sixxs.net # Interface names to use (default: aiccu) # ipv6_interface is the name of the interface that will be used as a tunnel interface. # On *BSD the ipv6_interface should be set to gifX (eg gif0) for proto-41 tunnels # or tunX (eg tun0) for AYIYA tunnels. ipv6_interface gif0 # The tunnel_id to use (default: none) # (only required when there are multiple tunnels in the list) tunnel_id T56021 # Be verbose? (default: false) verbose false # Daemonize? (default: true) # Set to false if you want to see any output # When true output goes to syslog # # WARNING: never run AICCU from DaemonTools or a similar automated # 'restart' tool/script. When AICCU does not start, it has a reason # not to start which it gives on either the stdout or in the (sys)log # file. The TIC server *will* automatically disable accounts which # are detected to run in this mode. # daemonize true # Automatic Login and Tunnel activation? automatic true # Require TLS? # When set to true, if TLS is not supported on the server # the TIC transaction will fail. # When set to false, it will try a starttls, when that is # not supported it will continue. # In any case if AICCU is build with TLS support it will # try to do a 'starttls' to the TIC server to see if that # is supported. requiretls false

PF

PF is a stateful packet filter imported from OpenBSD. In my setup, it acts as NAT for IPv4 clients and as firewall for both IPv4 and IPv6 clients.

ftp-proxy

FTP is troublesome. To make it work through PF, enable ftp-proxy (part on the base system, no Port installation needed) and add anchors for it to the rdr and filter sections of pf.conf, as shown below.

/etc/rc.conf ftpproxy_enable="YES"

UPnP

UPnP allows services inside the LAN to automatically configure port forwards for themselves. It’s a handy way to set up things like P2P applications and games consoles. My home network is a pretty relaxed environment, but don’t enable UPnP if you’re not keen on letting things punch arbitrary holes in your firewall.

Otherwise, grab miniupnpd from Ports or from my pre-built package:

pkg_add -r miniupnpd

And add anchors for it to the rdr and filter sections of pf.conf, as shown, then edit miniupnpd.conf to suit your environment.

/usr/local/etc/miniupnpd.conf # WAN network interface ext_ifname=ue0 # if the WAN interface has several IP addresses, you # can specify the one to use below #ext_ip= # there can be multiple listening ips for receiving SSDP traffic. # the 1st IP is also used for UPnP Soap traffic. listening_ip=172.16.0.1 # port for HTTP (descriptions and SOAP) traffic. set 0 for autoselect. port=5555 # path to the unix socket used to communicate with MiniSSDPd # If running, MiniSSDPd will manage M-SEARCH answering. # default is /var/run/minissdpd.sock #minissdpdsocket=/var/run/minissdpd.sock # enable NAT-PMP support (default is no) enable_natpmp=yes # lease file location #lease_file=/var/log/upnp.leases # bitrates reported by daemon in bits per second bitrate_up=12582912 bitrate_down=3145728 # "secure" mode : when enabled, UPnP client are allowed to add mappings only # to their IP. (default is yes) secure_mode=yes # default presentation url is http address on port 80 #presentation_url= # report system uptime instead of daemon uptime system_uptime=yes # unused rules cleaning. # never remove any rule before this threshold for the number # of redirections is exceeded. default to 20 #clean_ruleset_threshold=10 # clean process work interval in seconds. default to 0 (disabled). # a 600 seconds (10 minutes) interval makes sense clean_ruleset_interval=600 # notify interval in seconds default is 30 seconds. #notify_interval=240 # log packets in pf #packet_log=no # ALTQ queue in pf # filter rules must be used for this to be used. # compile with PF_ENABLE_FILTER_RULES (see config.h file) queue=upnp # uuid : generated by the install a new one can be created with # uuidgen uuid=1008e7e5-9f48-12df-abef-0010751aa4f1 # UPnP permission rules # (allow|deny) (external port range) ip/mask (internal port range) # one port in the range. # ip/mask format must be nn.nn.nn.nn/nn allow 1024-65535 172.16.0.0/12 1024-65535 deny 0-65535 0.0.0.0/0 0-65535

/etc/rc.conf miniupnpd_enable="YES"

pf.conf

pf.conf is PF’s configuration file. It holds rules that define how the firewall behaves, such as what ports or services are blocked or allowed and what interfaces get NAT.

Instead of explaining it line-by-line, here’s a well-commented working example from my firewall:

/etc/pf.conf #Define variables for our interfaces ext_if="ue0" int_if="mge0" lo_if="lo0" 6_if="gif0" #Define some IPv4 hosts variables to forward to #my desktop Emi and my housemate's NAS wut Emi = "172.16.0.40" wut = "172.16.0.103" #Blocked packets are silently dropped set block-policy drop #Enable order-checking in this ruleset set require-order yes #Fingerprints file for passive operating system fingerprinting set fingerprints "/etc/pf.os" #Log this interface (if using pflog) set loginterface $ext_if #Scrub (normalize) incoming packets scrub in all #NAT everything except $ext_id. Useful if you have multiple interfaces #such as LAN and WLAN #nat on $ext_if inet from ! ($ext_if) to any -> ($ext_if) # -OR- #NAT $int_if nat on $ext_if inet from $int_if:network to any -> ($ext_if) #FTP-proxy nat-anchor "ftp-proxy/*" rdr-anchor "ftp-proxy/*" rdr pass proto tcp from any to any port ftp -> 127.0.0.1 port 8021 #UPNP rdr-anchor "miniupnpd" #httpd on aloe rdr on $ext_if proto tcp from any to any port http -> $lo_if #SSH on Emi rdr on $ext_if proto tcp from any to $ext_if port 220 -> $Emi port 22 #Subsonic on Emi rdr on $ext_if proto tcp from any to $ext_if port 8180 -> $Emi #Minecraft on Emi rdr on $ext_if proto tcp from any to $ext_if port 25565 -> $Emi #OpenTTD on Emi rdr on $ext_if proto {tcp, udp} from any to $ext_if port 3978:3979 -> $Emi #Subsonic on wut rdr on $ext_if proto tcp from any to $ext_if port 10099 -> $wut port 8180 #SSH on wut rdr on $ext_if proto tcp from any to $ext_if port 221 -> $wut port 22 # Filtering: the implicit first two rules are pass in all pass out all # block all incoming packets but allow ssh, pass all outgoing tcp and udp # connections and keep state, logging blocked packets. block in log all #nmap block in log quick on $ext_if inet proto tcp from any to any flags FUP/FUP #ftp-proxy anchor "ftp-proxy/*" #UPNP anchor "miniupnpd" #icmp, ping etc pass in on $ext_if proto icmp all pass in on $ext_if proto icmp6 all pass in on $6_if proto icmp6 all #httpd on aloe pass in on $ext_if proto tcp from any to any port http pass in on $6_if proto tcp from any to any port http #SSH on Emi pass in on $ext_if proto tcp from any to any port 220 pass in on $6_if proto tcp from any to any port 220 #Subsonic on Emi pass in on $ext_if proto tcp from any to any port 8180 #Minecraft on Emi pass in on $ext_if proto tcp from any to any port 25565 #OpenTTD on Emi pass in on $ext_if proto tcp from any to any port 3978:3979 #Subsonic on wut pass in on $ext_if proto tcp from any to any port 10099 #SSH on wut pass in on $ext_if proto tcp from any to any port 221 #allow anything outbound pass out on $ext_if proto { tcp, udp, icmp } all keep state #allow anything from LAN pass in on $int_if all pass on $lo_if all

/etc/rc.conf pf_enable="YES" gateway_enable="YES"

IPv4 DHCP

Grab isc-dhcp41-server. It will be used to hand out IPv4 addresses on the network.

pkg_add -r isc-dhcp41-server

Generate a DNSSEC key

Using the dnssec-keygen utility, generate a key that DHCPD will use to authenticate with named for dynamic DNS. That means when a machine gets an address from DHCPD, the daemon will update named’s zone files with forward and reverse DNS for the new host. You can name the key anything you please. I called mine dhcp-ddns.

dnssec-keygen -a hmac-md5 -b 512 -n USER dhcp-ddns

It will spit out two key files. Grab the Base64-encoded key from either file. It will look like a random string of characters.

In this example there are two defined DNS zones. One forward, aloe.cooltrainer.org., and one reverse, 0.16.172-in-addr.arpa.

My dhcpd.conf has some defined hosts. These systems can use DHCP for ease of configuration but will always get the same address.

/usr/local/etc/dhcpd.conf default-lease-time 600; max-lease-time 7200; ddns-update-style interim; update-static-leases on; authoritative; option domain-name "aloe.cooltrainer.org"; #Normally, include DNSSEC key to prevent exposing it in git #include "/usr/local/etc/dhcp-ddns.key"; #For this example, though, a fake key: key dhcp-ddns { algorithm HMAC-MD5.SIG-ALG.REG.INT; secret /YH97F5944576ghgjjkNMSOuu+4NaIlx/ZiJhbhmfgiuy8945ghMmH850xDtSByZtpmVPg6oDfJehG/AnA==; } zone aloe.cooltrainer.org. { primary 127.0.0.1; key dhcp-ddns; } zone 0.16.172.in-addr.arpa. { primary 127.0.0.1; key dhcp-ddns; } subnet 172.16.0.0 netmask 255.255.255.0 { option domain-name-servers 172.16.0.1; range 172.16.0.150 172.16.0.250; option routers 172.16.0.1; option time-servers 172.16.0.1; option subnet-mask 255.255.255.0; } group { option domain-name "aloe.cooltrainer.org"; ddns-domainname "aloe.cooltrainer.org"; host emi { hardware ethernet 00:1D:7D:AA:FA:3D; fixed-address 172.16.0.40; option host-name "emi"; ddns-hostname "emi"; } host minamo { hardware ethernet 00:1B:21:3C:FB:C5; fixed-address 172.16.0.45; option host-name "minamo"; ddns-hostname "minamo"; } host wut { hardware ethernet 00:1B:21:51:AB:97; fixed-address 172.16.0.103; option host-name "wut"; ddns-hostname "wut"; } host jetdirect { hardware ethernet 00:01:e6:46:ca:ce; fixed-address 172.16.0.30; option host-name "jetdirect"; ddns-hostname "jetdirect"; } host espresso { hardware ethernet 38:e7:d8:7f:7c:82; fixed-address 172.16.0.140; option host-name "espresso"; ddns-hostname "espresso"; } }

/etc/rc.conf dhcpd_enable="YES" dhcpd_ifaces="mge0"

DNS

BIND named is part of the base system, version 9.6 as of FreeBSD 8.2. Most of my configuration file is based on the examples from the FreeBSD handbook with the addition of my DDNS key, my two zones, and some upstream DNS servers (Comcast, Google) to forward and cache.

Named is run jailed by default, so its configuration file can be found in /var/named/etc/namedb/ .

/var/named/etc/namedb/named.conf options { directory "/etc/namedb/working"; pid-file "/var/run/named/pid"; dump-file "/var/dump/named_dump.db"; statistics-file "/var/stats/named.stats"; listen-on { 127.0.0.1; 172.16.0.1; }; listen-on-v6 { ::1; 2001:4830:1632::210:75ff:fe1a:a4f1; }; // These zones are already covered by the empty zones listed below. // If you remove the related empty zones below, comment these lines out. disable-empty-zone "255.255.255.255.IN-ADDR.ARPA"; disable-empty-zone "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA"; disable-empty-zone "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA"; forwarders { //Comcast Atlanta Primary 68.87.68.162; //Comcast Atlanta Secondary 68.87.74.162; //Comcast Atlanta v6 Primary 2001:558:100A:4:68:87:68:162; //comcast Atlanta v6 Secondary 2001:558:1012:6:68:87:74:162; //Google Primary 8.8.8.8; //Google Secondary 8.8.4.4; //Google v6 Primary 2001:4860:4860::8888; //Google v6 Secondary 2001:4860:4860::8844; }; forward only; }; zone "." { type hint; file "/etc/namedb/named.root"; }; /* Serving the following zones locally will prevent any queries for these zones leaving your network and going to the root name servers. This has two significant advantages: 1. Faster local resolution for your users 2. No spurious traffic will be sent from your network to the roots */ // RFC 1912 (and BCP 32 for localhost) zone "localhost" { type master; file "/etc/namedb/master/localhost-forward.db"; }; zone "127.in-addr.arpa" { type master; file "/etc/namedb/master/localhost-reverse.db"; }; zone "255.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // RFC 1912-style zone for IPv6 localhost address zone "0.ip6.arpa" { type master; file "/etc/namedb/master/localhost-reverse.db"; }; // "This" Network (RFCs 1912 and 3330) zone "0.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // Private Use Networks (RFC 1918) zone "10.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "16.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "17.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "18.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "19.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "20.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "21.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "22.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "23.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "24.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "25.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "26.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "27.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "28.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "29.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "30.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "31.172.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "168.192.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // Link-local/APIPA (RFCs 3330 and 3927) zone "254.169.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // TEST-NET-[1-3] for Documentation (RFC 5737) zone "2.0.192.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "100.51.198.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "113.0.203.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // IPv6 Range for Documentation (RFC 3849) zone "0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // Domain Names for Documentation and Testing (BCP 32) zone "test" { type master; file "/etc/namedb/master/empty.db"; }; zone "example" { type master; file "/etc/namedb/master/empty.db"; }; zone "invalid" { type master; file "/etc/namedb/master/empty.db"; }; zone "example.com" { type master; file "/etc/namedb/master/empty.db"; }; zone "example.net" { type master; file "/etc/namedb/master/empty.db"; }; zone "example.org" { type master; file "/etc/namedb/master/empty.db"; }; // Router Benchmark Testing (RFC 3330) zone "18.198.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "19.198.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // IANA Reserved - Old Class E Space zone "240.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "241.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "242.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "243.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "244.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "245.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "246.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "247.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "248.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "249.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "250.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "251.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "252.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "253.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "254.in-addr.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // IPv6 Unassigned Addresses (RFC 4291) zone "1.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "3.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "4.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "5.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "6.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "7.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "8.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "9.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "a.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "b.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "c.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "d.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "e.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "0.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "1.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "2.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "3.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "4.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "5.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "6.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "7.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "8.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "9.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "a.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "b.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "0.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "1.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "2.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "3.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "4.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "5.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "6.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "7.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // IPv6 ULA (RFC 4193) zone "c.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "d.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // IPv6 Link Local (RFC 4291) zone "8.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "9.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "a.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "b.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // IPv6 Deprecated Site-Local Addresses (RFC 3879) zone "c.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "d.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "e.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; zone "f.e.f.ip6.arpa" { type master; file "/etc/namedb/master/empty.db"; }; // IP6.INT is Deprecated (RFC 4159) zone "ip6.int" { type master; file "/etc/namedb/master/empty.db"; }; # Normally, don't expose our DDNS key in git include "/etc/namedb/rndc.key"; #include "/etc/namedb/dhcp-ddns.key"; #For this example, though, a fake key: key dhcp-ddns { algorithm hmac-md5; secret "/YH97F5944576ghgjjkNMSOuu+4NaIlx/ZiJhbhmfgiuy8945ghM mH850xDtSByZtpmVPg6oDfJehG/AnA=="; }; controls { inet * allow { 127.0.0.1; ::1; } keys { "rndc-key"; "dhcp-ddns"; }; }; acl clients { 172.16.0.0/12; 2001:4830:1632::/64; 127.0.0.0/8; ::1/128; }; zone "aloe.cooltrainer.org." { type master; file "/etc/namedb/dynamic/aloe.cooltrainer.org.conf"; allow-update { key dhcp-ddns; }; notify no; }; zone "0.16.172.in-addr.arpa." { type master; file "/etc/namedb/dynamic/0.16.172.in-addr.arpa"; allow-update { key dhcp-ddns; }; notify no; }; #Log to /var/named/var/log/named.log #logging { # channel log_file { file "/var/log/named.log" size 10M; # severity debug 3 ; }; # category queries { log_file; }; # category xfer-in { log_file; }; # category xfer-out { log_file; }; # category default { log_file; }; #};

Once dhcpd and named are both configured, DHCP clients should get forward and reverse DNS records.

$ORIGIN 0.16.172.in-addr.arpa. 1 PTR aloe.cooltrainer.org. $TTL 300 ; 5 minutes 140 PTR espresso.aloe.cooltrainer.org. 146 PTR asholette.aloe.cooltrainer.org. 166 PTR Eliott-Desktop.aloe.cooltrainer.org. $TTL 3600 ; 1 hour 30 PTR jetdirect.aloe.cooltrainer.org. $TTL 600 ; 10 minutes 40 PTR emi.aloe.cooltrainer.org.

$ORIGIN aloe.cooltrainer.org. $TTL 300 ; 5 minutes android-e27f9bc6e9c5fb24 A 172.16.0.140 TXT "006e8238a52a03576df0c055e018bc2e12" asholette A 172.16.0.146 TXT "319708bac638ca09e7aff3e696ad654c0c" Eliott-Desktop A 172.16.0.166 TXT "00a2411b8e29027733b2220817956cd8a0" $TTL 600 ; 10 minutes emi A 172.16.0.40 $TTL 300 ; 5 minutes espresso A 172.16.0.140 TXT "006e8238a52a03576df0c055e018bc2e12" $TTL 3600 ; 1 hour JetDirect A 172.16.0.30 TXT "003f54fdfc7dd203d6a1573fde3a487cf2"

Using DNS this way simplifies home networking. One can set a mount point for a hostname, for example, so the actual IP address doesn’t matter and can change.

[nicole@emi#etc]host emi emi.aloe.cooltrainer.org has address 172.16.0.40 [nicole@emi#etc]host 172.16.0.40 40.0.16.172.in-addr.arpa domain name pointer emi.aloe.cooltrainer.org.

/etc/rc.conf named_enable="YES"

Stateless IPv6 autoconfiguration (SLAAC)

Now, we need a way to hand out IPv6 addresses from our subnet to clients on the LAN. The easiest method is rtadvd, the router advertisement daemon, inherited in BSD from the KAME project.

rtadvd is very simple to configure. The only piece of required information is your IPv6 subnet, 2001:4830:1632::/64 in my case.

/etc/rtadvd.conf mge0:\ :addrs#1:addr="2001:4830:1632::":prefixlen#64:

/etc/rc.conf rtadvd_enable="YES" rtadvd_interfaces="mge0"

Stateful IPv6 autoconfiguration (DHCPv6)

SLAAC is very easy to set up and use, but it can’t interact with named like DHCP can. Luckily, ISC-DHCP supports DHCPv6 as of version 4.0. I’ve yet to set this up, but I will update this section of this post when I do.

Network Time

I prefer OpenBSD’s OpenNTPD to the ntpd in FreeBSD’s base system. Install it.

pkg_add -r openntpd

Its configuration, then, is incredibly straightforward. It listens on localhost and on mge0. I use servers from the pool.ntp.org project.

The -s argument allows for a large time correction with openntpd instead of using ntpdate or rdate. This is important on the Dockstar, because it has no real time clock hardware and needs a large time jump at boot.

/usr/local/etc/ntpd.conf # Addresses to listen on (ntpd does not listen by default) #listen on * listen on 127.0.0.1 listen on ::1 listen on 172.16.0.1 listen on 2001:4830:1632::210:75ff:fe1a:a4f1 # sync to a single server #server ntp.example.org # use a random selection of NTP Pool Time Servers # see http://support.ntp.org/bin/view/Servers/NTPPoolServers servers pool.ntp.org

/etc/rc.conf openntpd_enable="YES" openntpd_flags="-s"

Inadyn

I have aloe.cooltrainer.org delegated to afraid.org’s freedns service to chase my dynamic IPv4 WAN address.

;; AUTHORITY SECTION: aloe.cooltrainer.org. 86400 IN NS ns3.afraid.org. aloe.cooltrainer.org. 86400 IN NS ns4.afraid.org. aloe.cooltrainer.org. 86400 IN NS ns1.afraid.org. aloe.cooltrainer.org. 86400 IN NS ns2.afraid.org.

Inadyn is a small utility to inform afraid.org when our IP changes.

pkg_add -r inadyn

/usr/local/etc/inadyn.conf --dyndns_system default@freedns.afraid.org --alias aloe.cooltrainer.org --dyndns_server_name freedns.afraid.org --dyndns_server_url /dynamic/update.php?blahblahuseyourown --update_period_sec 400 --verbose 0 --background

/etc/rc.conf inadyn_enable="YES"

Wireless

My old WRT54G was re-purposed as a wireless access point for this project, using a BrainSlayer build of DD-WRT r14929 (dd-wrt.v24_nokaid_generic.bin) that supports IPv6. It’s configured with a static IP for itself and forwards DHCP and SLAAC advertisements to wireless clients with the configuration described here.

Wrap-up

Everything on the LAN should now receive an IPv4 and an IPv6 address, get network time, get cached DNS, and have no trouble opening ports through the IPv4 NAT.

Here’s a sample of the new machine’s throughout and packet-pushing performance while running several dozen well-seeded torrents at once, generated with pfstat. Only IPv4 is shown, since there weren’t enough fellow IPv6 hosts to make the test worthwhile. Our connection is rated for 12Mbit down, 3 up, hence the hard cutoff near 12Mbit/s.

It didn’t let me down!

My config files for Aloe are stored in Aloe’s GitHub repo. Check them out if you need an example!