There are several low power boards on the market which qualify for a Unix-based router. For example the Soekris Netxxxx series, the WRAP or ALIX boards from PC Engines, or most of the VIA EPIA products, when adding a second ethernet adapter. Also the Efika 5k2 from bPlan is a very interesting candidate. There are certainly a lot more.
Why not just buy a standard router with a proprietary OS? Those will be perfect for most of the people of course. But a BSD or Linux or OpenWRT based router gives you the possibility to control everything, when you are willed to invest the time. They can act as a full-grown server and allow you to log-in into your home LAN from whereever you are, for example.
In this example I will install NetBSD onto a Soekris Net4501. A Compact Flash card of 128 MB is sufficient for a base system. But the more the better, in case you want to install additional programs.
First of all, a Compact Flash card reader is highly recommended. You could theoretically install via PXEBoot/NFS, but I won't discuss that here. Also I assume a second NetBSD system, of any kind, to plug in the card reader and to connect to the Soekris console via nullmodem cable. It should be switched to 9600 bps, which is standard for the NetBSD boot loader (Net4501 defaults to 19200 bps). After that you can connect to the board as simply as typing tip dty0. The card reader will show up in kernel messages as:
umass0: Generic Card Reader, rev 2.00/93.21, addr 2 umass0: using SCSI over Bulk-Only scsibus0 at umass0: 2 targets, 3 luns per target sd0 at scsibus0 target 0 lun 0:disk removable sd0: fabricating a geometry sd0: 122 MB, 122 cyl, 64 head, 32 sec, 512 bytes/sect x 250880 sectors
This means there are 250880 sectors of 512 bytes on the card. We will create an image, filled with zeroes, of exactly that size.
# dd if=/dev/zero of=/tmp/arwen.img bs=512 count=250880
The image is attached to a vnode disk device, which allows us to treat it virtually as a real disk device. Using an image instead of writing to the card directly will give us some speed advantage during the installation. Writing to CF is very slow.
# vnconfig vnd0 /tmp/arwen.img
Install an active MBR partition, which extends over the whole disk.
# fdisk -ua0 vnd0 fdisk: primary partition table invalid, no magic in sector 0 Disk: /dev/rvnd0d NetBSD disklabel disk geometry: cylinders: 122, heads: 64, sectors/track: 32 (2048 sectors/cylinder) total sectors: 250880 BIOS disk geometry: cylinders: 122, heads: 64, sectors/track: 32 (2048 sectors/cylinder) total sectors: 250880 Do you want to change our idea of what BIOS thinks? [n] Partition 0:The data for partition 0 is: sysid: [0..255 default: 169] start: [0..123cyl default: 32, 0cyl, 0MB] size: [0..122cyl default: 250848, 122cyl, 122MB] bootmenu: [] Do you want to change the active partition? [n] y Choosing 4 will make no partition active. active partition: [0..4 default: 0] 0 Are you happy with this choice? [n] y We haven't written the MBR back to disk yet. This is your last chance. Partition table: 0: NetBSD (sysid 169) start 32, size 250848 (122 MB, Cyls 0-122/32/1), Active 1: 2: 3: Bootselector disabled. Should we write new partition table? [n] y
Install a NetBSD diskabel and create an 'a' partition to hold the root file system. A 'b' partition for swap space is not recommended (who wants to swap to CF?). Partition 'd' will contain the whole disk, as usual for the i386 architecture (this is 'c' for others). Remember that the partition table is edited in vi mode.
# disklabel -e -I vnd0 # /dev/rvnd0d: type: vnd disk: vnd label: fictitious flags: bytes/sector: 512 sectors/track: 32 tracks/cylinder: 64 sectors/cylinder: 2048 cylinders: 122 total sectors: 250880 rpm: 3600 interleave: 1 trackskew: 0 cylinderskew: 0 headswitch: 0 # microseconds track-to-track seek: 0 # microseconds drivedata: 0 5 partitions: # size offset fstype [fsize bsize cpg/sgs] a: 250848 32 4.2BSD 0 0 0 # (Cyl. 0*- 122*) c: 250848 32 unused 0 0 # (Cyl. 0*- 122*) d: 250880 0 unused 0 0 # (Cyl. 0 - 122*)
Now we can create the file system in the root partition, /dev/vnd0a. Option -B sets the byte-order to little-endian for Soekris, in case you make the image on a big-endian system. -m 0 sets the amount of reserved disk space to 0% and the -o option instructs the filesystem to minimize space fragmentation. You can omit the last two options when dealing with larger CF cards in the GB range.
# newfs -B le -m 0 -o space vnd0a /dev/rvnd0a: 122.5MB (250848 sectors) block size 8192, fragment size 1024 using 4 cylinder groups of 30.62MB, 3920 blks, 7552 inodes. super-block backups (for fsck_ffs -b #) at: 32, 62752, 125472, 188192,
Mount the file system and extract the base and etc sets of the selected NetBSD release. For larger cards you can add man and text.
# mount /dev/vnd0a /mnt # cd /mnt # tar xzfp /home/frank/i386sets_3.1/base.tgz # tar xzfp /home/frank/i386sets_3.1/etc.tgz
For the Soekris, you would want to make a cusomized kernel, which has support for the special features of the board and is as small as possible to avoid wasting any byte of the valuable 64 MB RAM. There is already a NET4501 config file in src/sys/arch/i386/conf, which you may use as a start. NetBSD 4.x or higher is recommended to get access to the board's GPIO and watchdog timers.
[...] # link ARWEN/netbsd ld -T ../../../../arch/i386/conf/kern.ldscript -Ttext c0100000 -e start -X -o netbsd ${SYSTEM_OBJ} ${EXTRA_OBJ} vers.o text data bss dec hex filename 1310906 27684 181844 1520434 173332 netbsd # cp /home/frank/netbsd/3.1/src/sys/arch/i386/compile/ARWEN/netbsd /mnt/
Copy the second level i386 boot loader to the image file system.
# cp /usr/mdec/boot /mnt/
Install the first level boot loader, with console set to serial at 9600 bps. Note that since NetBSD 4 you have to patch the binary with installboot -e -o console=com0,speed=9600 mybootxx_ffsv1 first, before installing it. /usr/mdec is only valid when working on an i386 system with the same release. Otherwise you have to take the boot loaders from src/obj/destdir.i386/usr/mdec in your source tree.
# installboot -v -o console=com0,speed=9600 /dev/rvnd0a /usr/mdec/bootxx_ffsv1 File system: /dev/rvnd0a File system type: ffs (blocksize 8192, needswap 0) Primary bootstrap: /usr/mdec/bootxx_ffsv1 Preserving 51 (0x33) bytes of the BPB
Create the /etc/fstab. The root file system wd0a (the CF appears as standard IDE on the Soekris) has got the noatime attribute to prevent constant updates of the access-time in the file system. softdep for improved performance. Additionally we create a memory file system with a maximum of 32 MB, which holds all the dynamic data under /var.
/dev/wd0a / ffs rw,noatime,softdep 1 1 varfs /var mfs rw,nodev,nosuid,-s=32m 0 0
You might consider to make your root file system read-only, but I decided against it, because I still want to be able to transfer files to my router and to install new software. I did my best to limit write access to the CF medium to a minimum, and I'm confident that a modern card will survive that for a very long time.
An archive of the initial /var file system is stored in /etc and extracted on system startup. After having done that everything below /var can be deleted. Only the mount-point should persist.
# cd /mnt/var # tar czf /mnt/etc/var.tar.gz . # rm -rf *
Therefore we have to modify the /etc/rc.d/mountcritlocal start script to fill the memory file system after it has been mounted. The following diff file shows the differences to apply.
--- /tmp/mountcritlocal 2008-06-28 21:41:31.000000000 +0200 +++ /mnt/etc/rc.d/mountcritlocal 2008-06-28 21:42:23.000000000 +0200 @@ -19,6 +19,8 @@ # This usually includes /var. # mount_critical_filesystems local + echo "Constructing /var." + (cd /var && tar xzpf /etc/var.tar.gz) # clean up left-over files. # this could include the cleanup of lock files and /var/run, etc.
Tune some kernel settings in /etc/sysctl.conf to improve router's networking.
[...] # Consider interface's MTU when calculating MSS net.inet.tcp.mss_ifmtu=1 # Connect to hosts with bad setup net.inet.tcp.rfc1323=0
It is recommended to configure /etc/namedb.conf and /etc/namedb/ to run the DNS service on your router. This allows all the hosts in your LAN to just use the router's IP as default gateway and DNS entry. Read the DNS chapter of the NetBSD guide for more information how to set up your name service.
/etc/resolv.conf defines my own domain, and a familiar one I want to automatically search as well. The nameserver is set to the IP of the router.
domain owl.de search owl.de hasenbraten.de nameserver 192.168.0.250
Don't forget your host name in /etc/hosts.
192.168.0.250 arwen.owl.de arwen
Enable IPFilter, IPNAT, name server, ifwatchd and SSH in /etc/rc.conf. Declare that we don't want swap space or kernel core dumps after a reboot. The first ethernet interface, sip0, is set up for the LAN. The second is pppoe0, which is configured via /etc/ifconfig.pppoe0.
[...] hostname="arwen.owl.de" auto_ifconfig=NO net_interfaces="sip0 pppoe0" ifconfig_sip0="inet 192.168.0.250 media auto up" ipfilter=YES ipnat=YES ifwatchd=YES named=YES sshd=YES no_swap=YES savecore=NO
Create /etc/ifconfig.pppoe0 to configure your DSL connection via PPPOE. In the case of the Soekris Net4501 the second ethernet interface, called sip1, is attached to the pppoe0 pseudo device. sip0 was reserved for the LAN connection. You have to supply pppoectl with the authentication protocol (pap or chap), login-name and password.
create ! /sbin/ifconfig sip1 up ! /sbin/pppoectl -e sip1 $int ! /sbin/pppoectl $int myauthproto=pap myauthname=xxxxxxxxxxxxxxxxx#@t-online.de myauthsecret=xxxxxx hisauthproto=none 0.0.0.0 0.0.0.1 netmask 255.255.255.255 up
I'm also adding the following lines to /etc/daily.local to make sure that I disconnect at a defined time during the night. In Germany most providers force a disconnect after 24h at the latest. With this method I am sure that the disconnect won't happen during the day. Also useful to synchronize your clock with a time server (ntpdate).
#!/bin/sh /usr/sbin/ntpdate -s ntp1.ptb.de ntp2.ptb.de /bin/sleep 3 /sbin/ifconfig pppoe0 down /bin/sleep 300 /sbin/pppoectl pppoe0 myauthproto=pap myauthname=xxxxxxxxxxxxxxxxx#@t-online.de myauthsecret=xxxxxx hisauthproto=none /bin/sleep 3 /sbin/ifconfig pppoe0 0.0.0.0 0.0.0.1 netmask 255.255.255.255 up
Create ip-up and ip-down scripts in /etc/ppp (also create the directory when missing), which are called by the ifwatchd daemon whenever the PPP connection goes down or up. In the up case we can set the new default route and delete it again in the down case. Optionally ip-up may be used to refresh the IP for your host registered at DynDNS or similar services. I'm using InaDyn, which is, for example, available at Source Forge.
/etc/ppp/ip-up:#!/bin/sh /sbin/route add default $5 /usr/local/bin/inadyn-advanced -u username -p password -a myhost.dyndns.org --iterations 1/etc/ppp/ip-down:
#!/bin/sh /sbin/route delete default $5
For the IPFilter I use a restrictive configuration which blocks everything from extern except icmp and the tcp ports 22 (ssh), 80 (http) and 113 (ident). You may need others. Note that sip0 is the name of the ethernet interface connected to your LAN, in case you want to use the rules on another system. Copy the following to /etc/ipf.conf:
# block corrupt or dangerous packets block in log quick from any to any with ipopts block in log quick proto tcp from any to any with short block in log quick from any to any with frag block in log quick from any to any with opt lsrr block in log quick from any to any with opt ssrr # allow loopback pass out quick on lo0 from any to any pass in quick on lo0 from any to any # allow LAN pass out quick on sip0 from any to any pass in quick on sip0 from any to any # block packets from reserved addresses block in log body quick on pppoe0 from 192.168.0.0/16 to any block in log body quick on pppoe0 from 172.16.0.0/12 to any block in log body quick on pppoe0 from 10.0.0.0/8 to any block in log body quick on pppoe0 from 127.0.0.0/8 to any block in log body quick on pppoe0 from 0.0.0.0/8 to any block in log body quick on pppoe0 from 169.254.0.0/16 to any block in log body quick on pppoe0 from 192.0.2.0/24 to any block in log body quick on pppoe0 from 204.152.64.0/23 to any block in log body quick on pppoe0 from 224.0.0.0/3 to any block out log body quick on pppoe0 from any to 192.168.0.0/16 block out log body quick on pppoe0 from any to 172.16.0.0/12 block out log body quick on pppoe0 from any to 10.0.0.0/8 block out log body quick on pppoe0 from any to 127.0.0.0/8 block out log body quick on pppoe0 from any to 0.0.0.0/8 block out log body quick on pppoe0 from any to 169.254.0.0/16 block out log body quick on pppoe0 from any to 192.0.2.0/24 block out log body quick on pppoe0 from any to 204.152.64.0/23 block out log body quick on pppoe0 from any to 224.0.0.0/3 # allow ping, ident, http and ssh from extern pass in log quick on pppoe0 proto icmp from any to any icmp-type echo keep state pass in log quick on pppoe0 proto tcp from any to any port = 113 flags S keep state keep frags pass in log quick on pppoe0 proto tcp from any to any port = 80 flags S keep state keep frags pass in log quick on pppoe0 proto tcp from any to any port = 22 flags S keep state keep frags # keep state of all connection from internal to external pass out quick on pppoe0 proto tcp from any to any flags S keep state keep frags pass out quick on pppoe0 proto udp from any to any keep state keep frags pass out quick on pppoe0 proto icmp from any to any keep state keep frags # block the rest block in log quick from any to any block out log quick from any to any
Create /etc/ipnat.conf for Network Address Translation, needed to allow multiple systems in your LAN to access the internet via the router. My LAN has the address 192.168.0, which you might want to change.
map pppoe0 192.168.0.0/24 -> 0/32 proxy port ftp ftp/tcp mssclamp 1412 map pppoe0 192.168.0.0/24 -> 0/32 portmap tcp/udp auto mssclamp 1412 map pppoe0 192.168.0.0/24 -> 0/32 mssclamp 1412
Set the time zone for your location.
# rm /mnt/etc/localtime # ln -s /usr/share/zoneinfo/Europe/Berlin /mnt/etc/localtime
The /dev file system is automatically mounted as a memory file system by the init process, when it is empty. /dev/MAKEDEV init will be called to create the device nodes. This is exactly what we need, but unfortunately some important devices for IPNat and IPFilter are missing in the init case. So we have to modify the script. Additionally I like to have the pci* devices and some more pseudo terminals to log in.
--- /dev/MAKEDEV 2006-11-08 18:11:56.000000000 +0100 +++ /mnt/dev/MAKEDEV 2008-06-28 00:57:33.000000000 +0200 @@ -218,8 +218,9 @@ case $i in # As of 2005-03-15, the "init" case must not create more than 1024 entries. +# Modified for Soekris. init) - makedev std wscons wt0 fd0 fd1 wd0 wd1 wd2 wd3 sd0 sd1 sd2 sd3 sd4 + makedev std wscons wt0 fd0 fd1 wd0 wd1 sd0 sd1 makedev tty0 tty1 makedev st0 st1 ch0 cd0 cd1 mcd0 vnd0 makedev bpf @@ -232,6 +233,8 @@ makedev xbd0 xbd1 xencons makedev usbs makedev ipty + makedev ipl pf crypto systrace + makedev pci0 pci1 makedev local ;; @@ -960,8 +963,12 @@ ipty) mkdev ttyp0 c 5 0 666 mkdev ttyp1 c 5 1 666 + mkdev ttyp2 c 5 2 666 + mkdev ttyp3 c 5 3 666 mkdev ptyp0 c 6 0 666 mkdev ptyp1 c 6 1 666 + mkdev ptyp2 c 6 2 666 + mkdev ptyp3 c 6 3 666 ;; ptm)
For NetBSD 4 we would also like to include gpio0 and sysmon for the watchdog timers.
--- /tmp/MAKEDEV 2008-06-29 12:21:59.000000000 +0200 +++ dev/MAKEDEV 2008-06-29 12:23:46.000000000 +0200 @@ -457,8 +457,7 @@ # As of 2005-03-15, the "init" case must not create more than 1024 entries. init) makedev std wscons wt0 fd0 fd1 - makedev wd0 wd1 wd2 wd3 wd4 wd5 wd6 wd7 - makedev sd0 sd1 sd2 sd3 sd4 + makedev wd0 wd1 sd0 sd1 makedev tty0 tty1 makedev st0 st1 ch0 cd0 cd1 mcd0 vnd0 makedev bpf @@ -471,6 +470,7 @@ makedev xbd0 xbd1 xencons makedev usbs makedev ipty + makedev ipl pf crypto sysmon pci0 pci1 gpio0 makedev local ;; @@ -1289,8 +1289,12 @@ ipty) mkdev ttyp0 c 5 0 666 mkdev ttyp1 c 5 1 666 + mkdev ttyp2 c 5 2 666 + mkdev ttyp3 c 5 3 666 mkdev ptyp0 c 6 0 666 mkdev ptyp1 c 6 1 666 + mkdev ptyp2 c 6 2 666 + mkdev ptyp3 c 6 3 666 ;; ptm)
We are done working on the image. Unmount the file system and detach our virtual disk.
# umount /mnt # vnconfig -u vnd0
Write the image onto the Compact Flash card. This can take a while, especially with bigger cards. Remember to use /dev/sd0c when running on non-i386 hosts.
# dd if=arwen.img of=/dev/sd0d bs=4k
Now we can try to boot up our freshly installed NetBSD router for the first time and perform some final configurations directly on the live system. First action would be to set your root password with passwd and create a user:
# useradd -m username # passwd username # vipw
When installing packages from pkgsrc the default package database path of /var/db/pkg is a problem, because the contents of /var is volatile now. So we set an environment variable in the rc-script for all shells we want to use. In .shrc it would be setenv PKG_DBDIR /usr/pkg/db and in .bashrc is looks like export PKG_DBDIR=/usr/pkg/db. This makes sure that new packages are registered under /usr/pkg/db.
export PKG_DBDIR=/usr/pkg/db
To avoid writing of a history when using the more (or less) command, we have to add this to .bashrc:
export LESSHISTFILE=-
The same problem exists with the command line history of the Bash. So we also add:
HISTFILE=/dev/null HISTFILESIZE=0 HISTSIZE=100
When the DNS service starts up, it will probably complain about a missing rndc.key. Go to /etc and create it with rndc-confgen -a -r keyboard. The last option is required because you have to provide the entropy by typing on your keyboard. Otherwise you can wait for a very long time for the command to finish.
# cd /etc # rndc-confgen -a -r keyboard