Using 9front as a home router
When people discuss Plan 9 it is usually mentioned that Plan 9 is a real file orientated design but I think its is sometimes hard to conceptulize the benefits of this from an outside perspective. To assist with this I wanted to discuss how I have my 9front box at home configured to act as my home router, and show how it's done.
My hardware setup is an old DELL OEM that I've jammed a 4 port ethernet pcie card in to. I use this for both the LAN and uplink since the on board ethernet port is not gigabit. Nothing fancy, just some hardware I had lying around.
Preface
Before we get in to the weeds here we're going to need to know our tools. In Plan 9 the kernel exposes a number of functionality through kernel filesystems, think stuff like Linux's /sys/. However unlike Linux Plan 9 exposes multiple roots for different subsections that the user may bind(1) in to their namespace at will. These kernel devices can be access by accessing a path that starts with '#' and is followed be a single rune identifier. For example the IP stack is accessed via '#I' and disks (/dev/sd*)
are exposed via '#S'.
Much like 9p filesystems a "mount argument" may also be supplied when accessing these devices, this is usually used to acces a specific instance of a device. For example '#l0' refers to ethernet card 0, '#l1' refers to ethernet card 1 and so on. The ip device ('#I') also allows for a integer argument to specify which IP stack you would like to access, which can be used to setup multiple disjoint IP stacks.
Setup
For making this router happen we'll create a /cfg/$sysname/cpurc
script, which will run at startup.
# Place IP stack 1 on /net.alt, this will be our "outside" IP stack
bind '#I1' /net.alt
# Place ethernet card 0 within that outside IP stack
# This is just organizational, not binding it to the IP stack yet
bind -a '#l0' /net.alt
# Create a ethernet bridge and add it to our "inside" IP stack
bind -a '#B' /net
# Add all of our internal ports to our "inside" IP stack
bind -a '#l2' /net
bind -a '#l3' /net
bind -a '#l4' /net
# Bind the interfaces to the bridge
echo 'bind ether port1 0 /net/ether2' >/net/bridge0/ctl
echo 'bind ether port2 0 /net/ether3' >/net/bridge0/ctl
echo 'bind ether port3 0 /net/ether4' >/net/bridge0/ctl
x=/net.alt
o=/net
# Create a virtual IP interface for both the outside and inside IP stack
# We open the clone file, and the kernel will then give us the
# id of the new created interface.
<$x/ipifc/clone {
# Read the new interface number
xi=$x/ipifc/`{read}
# Write in to the ctl file of the newly created interface to configure it
>$xi/ctl {
# This is a packet interface
echo bind pkt
# Our ip is 192.168.69.3/24 and we only allow remote connections from 192.168.69.2
echo add 192.168.69.3 255.255.255.255 192.168.69.2
# Route packets to others
echo iprouting 1
# Now create a new interface on the inside IP stack
<$o/ipifc/clone {
oi=$o/ipifc/`{read}
>$oi/ctl {
# Hook up this device to the outside IP stack device
echo bind netdev $xi/data
# Our ip is 192.168.69.2/24 and we only allow remote connections from 192.168.69.3
echo add 192.168.69.2 255.255.255.0 192.168.69.3
echo iprouting 1
}
}
}
}
# Configure our route table for both the inside and outside IP stacks
# Arguments are: target mask nexthop interface(addressed by IP)
echo add 192.168.168.0 255.255.255.0 192.168.69.2 192.168.69.3 > $x/iproute
echo add 0.0.0.0 /96 192.168.69.3 192.168.69.2 > $o/iproute
# Do DHCP on the external interface. -x tells us which
# IP stack to use. -t tells us that we are doing NAT
# and to configure the route table as such. NAT is implemented
# as just a route table flag.
ip/ipconfig -x /net.alt -t ether /net.alt/ether0
# Configure a static IP on our internal interface, which will
# act as a gateway for our internal network.
ip/ipconfig ether /net/ether2 192.168.168.209 255.255.255.0
# Start dhcpd on our internal network, our DHCP range is 192.168.168.50-192.168.168.150
/bin/ip/dhcpd 192.168.168.50 100
It is worth noting that ip/ipconfig is mostly a convenience and has no magic under the hood, it itself is just talking to files in /net. This allows us to pass different /net's via -x as we like.
That's all folks. I've been using this for about a year now and haven't had any problems with it.
Further Reading
You can read more about the specific kernel devices within section 3 of the 9front manual. Some of the ones we used today: ether bridge ip.
9front also has a network database (NDB) that is used to infer systems and their ip addresses (among other things) but was omitted to keep a focus here if you want to read more about it, look at ndb(6) and the wiki