DSL Router for FreeBSD with MPD and PF
Jan Stocker <Jan.Stocker@t-online.de>
Abstract
This article gives you a short overview how to setup a DSL router with FreeBSD 5 using MPD and PF.
Introduction
At home I am using multiple computers sharing one
Internet connection, like many other people. Starting with FreeBSD and
ISDN, I used I4B directly over the sppp
device. Some years
later I switched to DSL using ppp. I was not very happy with the
firewall capabilities implemented in userland ppp
. You
can only have 20 rules and you must order your rules by numbering
them. This makes it difficult to add a rule at a specific position.
I tried to use IP Firewall (IPFW) but I was not able to get the NAT thing running together with ppp. The most praised firewall at that time was IP Filter (IPF) but using that did not work, either. I could not find anyone on irc or any mailing list who was able to help me. So I stopped that attempt for a long time.
Some time later I read about a comparison of PPP over Ethernet (PPPoE) performance on FreeBSD and Linux boxes (http://www.jraitala.net/comp/articles/2002/pppoe/). The result was that FreeBSD consumed 100% CPU which I could not believe, because my 486-DX2/66 did a good job. After that post, the mailing lists filled up with comments about using MPD, a ppp replacement doing the network routing in kernel mode but configuration in userland: context switching takes time, so ppp was slow where MPD is faster. The author added another test series for FreeBSD with MPD.
That was around the time I really wanted to setup a new
server, but I had not time for it for nearly 2 years. In the meantime,
some guys took OpenBSD's standard firewall, pf
and ported it to
FreeBSD, where it has been part of the base system for a few months.
I think this firewall is easy to configure and has a mighty ruleset.
The time was right to try it all out.
MPD
The homepage of MPD (http://www.dellroad.org/mpd/index) describes the application as this:
MPD is a multi-link PPP daemon for FreeBSD based on netgraph(4). It is designed to be both fast and flexible. It handles configuration and negotiation in user land, while routing all data packets strictly in the kernel.
The configuration is very similar to ppp, but the documentation is not very well structured and misses some how-tos, in my opinion.
First install this software from the ports
cd /usr/ports/net/mpd && make install clean
Now we need to configure MPD. All configuration files will be in /usr/local/etc/mpd directory. This directory contains a sample file for each of the three needed files; you can make a copy of them or write them from scratch. I included my files here with some comments.
The main configuration is stored in
mpd.conf
. The section "default" is loaded by MPD
automatically. I want my section "tonlinedsl" to be loaded at start,
so I added a load statement in the default section. The first line
creates a new interface ng0 on the link PPPoE. This line is defined in
the file "mpd.links", which I will describe later.
For PPPoE, it is essential to set the MRU size to 1492 bytes. You can find the command "set iface upscript" at the end of the file. This statement makes sure that the given mpd_dsl.linkup script is called after the connection is established. This is similar to the ppp.linkup file for ppp. PF is initialized before MPD starts and before MPD creates the ng0 network interface, so this script will configure PF for this interface.
default: load tonlinedsl tonlinedsl: new -i ng0 tonlinedsl PPPoE set iface addrs 10.2.1.1 10.2.1.2 set iface route default set iface disable on-demand set iface idle 0 set bundle disable multilink set bundle authname "username" set link no acfcomp protocomp set link disable pap chap set link accept chap set link mtu 1492 set link keep-alive 10 60 set ipcp yes vjcomp set ipcp ranges 0.0.0.0/0 0.0.0.0/0 set iface up-script /usr/local/etc/mpd/mpd_dsl.linkup open iface
You can see that mpd.conf contains the username but no password for the login. For security reasons all passwords are stored in "mpd.secrets". Make sure this file is not readable worldwide. It only contains pairs of strings: the username and the password separated by spaces.
username password
As I mentioned before, you need to define the link to use. This is done in the separate file "mpd.links". I use a link called PPPoE with the protocol "pppoe" and my NIC de0. This is only a 10 Mbit card, but fast enough for my DSL line.
PPPoE: set link type pppoe set pppoe iface de0 set pppoe service "123" set pppoe disable incoming set pppoe enable originate
To include a running firewall using PF, I added a script calling pfctl to initialize the rules. This script flushes everything PF has in its rule tables and re-reads everything from the standard configuration file. If you are using PF on other devices than this ng0 interface, please note that all states for other interfaces are also flushed. You must alter this script in such a case.
#!/bin/sh /sbin/pfctl -Fa -e -f /etc/pf.conf
MPD is configured but not added to the startup sequence. Please make a copy from /usr/local/etc/rc.d/mdp.sh.sample and name it mpd.sh, also give it execute rights.
You can now have a look what MPD is doing by starting it:
mpd
I got an error message containing these lines:
[PPPoE] exec: /sbin/ifconfig de0 up [PPPoE] Cannot send a netgraph message: de0::No such file or directory
So what happened? I looked at my NIC, manually ran ifconfig, checked for the device and so on. The result was: the netgraph module for ethernet access was not loaded. I do not know why MPD is not loading this automatically if needed, but the fact is, it does not.
You can either add a line to "/boot/loader.conf" and reboot your system,
ng_ether_load="YES"
or add these lines to the mpd.sh start script:
if ! /sbin/kldstat | grep -w ng_ether.ko >/dev/null; then /sbin/kldload ng_ether fi
PF
The MPD daemon has now been installed correctly and we need to configure PF.
To be able to use PF as a module you have to build a new kernel with the following two options added.
options RANDOM_IP_ID options PFIL_HOOKS
On my system, PF is integrated in the base distribution. If you are using 5.2.1 or older you have to install it from the port system (/usr/ports/security/pf). Please note: starting procedure and file location may be different.
To add PF to the startup system, you only need to add
pf_enable="YES" pf_flags="-d"
This loads the PF module to the memory but disables its functionality. The above mentioned script "mpd_dsl.linkup" enables the filter with its option "-e". If you do not disable PF here, your /var/log/messages will contain errors about the missing ng0 interface.
If you have not already done this, add IP routing to your FreeBSD system in rc.conf
gateway_enable="YES"
The file "/etc/pf.conf" contains all rules for PF. I found a good example for it under https://solarflux.org/pf/pf.conf.pyun-fbsd50. My pf.conf is nearly the same but I have a different internal ip net. I do not want any peer-to-peer network routed to an internal client, but I want to have ssh and IMAP access to my router from the outside world.
# PF rule set for MPD under FreeBSD ext_if="ng0" int_if="xl0" INTERNAL="10.1.1.0/24" table <badhost> const {0.0.0.0/8, 169.254.0.0/16, 192.0.2.0/24, \ 224.0.0.0/4, 240.0.0.0/4, 10.0.0.0/8, \ 172.16.0.0/12, 192.168.0.0/16, 255.255.255.255, \ 127.0.0.1/8} # # hosts that can use this system as a gateway # table <allowhost> const {10.1.1.0/24} set loginterface $ext_if # Clean up fragmented and abnormal packets, defeat NAT detection too # max-mss is needed due to MPD's poor MSS handling scrub in all scrub out all random-id max-mss 1440 # # NAT section # nat on $ext_if inet from $INTERNAL to any -> ($ext_if) # Remember default rule for non-matching packets are passed!!! block out log on $ext_if all block in log on $ext_if all block return-rst out log on $ext_if proto tcp all block return-rst in log on $ext_if proto tcp all block return-icmp out log on $ext_if proto udp all block return-icmp in log on $ext_if proto udp all # allow lo0 interface packet pass in quick on lo0 all pass out quick on lo0 all # allow internal network traffic pass in on $int_if from any to <allowhost> pass out on $int_if from <allowhost> to any # # block spoofing attack # block in quick log on $ext_if from <badhost> to any # # block nmap's fingerprinting attempt(FIN, URG, PSH) block in quick on $ext_if inet proto tcp from any to any flags FUP/FUP # # block MSN(messenger.hotmail.com) block out log quick proto tcp from any to 207.46.104.20 block out log quick proto tcp from any to any port 1863 # Create states pass in log on $ext_if inet proto tcp from any to any port 993 keep state pass in log on $ext_if inet proto tcp from any to any port 22 keep state pass out log on $ext_if inet proto tcp all flags S/SA keep state pass out log on $ext_if inet proto {udp, icmp} all keep state
Conclusion
MPD and PF are two good and stable programs, the use of which results in a good router system. MPD is fast and flexible, and gives me the option to create a VPN server. PF is, in my opinion, the best structured packet filter for FreeBSD, and with ALTQ support it extends my planned integration of a public WLAN AP.
Links
- FreeBSD PF port - http://pf4freebsd.love2party.net
- PF FAQ - http://www.openbsd.org/faq/pf
- PF tutorials and links - https://solarflux.org/pf
- MPD homepage - http://www.dellroad.org/mpd/index
- PPPoE performance - http://www.jraitala.net/comp/articles/2002/pppoe
Copyright
(C) 2004
Jan Stocker
Redistribution of this text is permitted as long as the copyright is
retained and all text altering is marked by author and date.
Version 1.0 - 2004-06-06
Version 1.1 - 2004-06-18 Fixed some typos