Get Firefox!

DSL Router for FreeBSD with MPD and PF

Jan Stocker <Jan.Stocker@t-online.de>


This article gives you a short overview how to setup a DSL router with FreeBSD 5 using MPD and PF.


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.


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.

        load tonlinedsl

        new -i ng0 tonlinedsl PPPoE
        set iface addrs
        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
        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.

        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.

/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:


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,


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


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


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


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


table <badhost> const {,,, \
             ,,, \
             ,,, \
# hosts that can use this system as a gateway
table <allowhost> const {}

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
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


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.



(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