You are on page 1of 9

Building a filtering bridge with DragonFlyBSD

A filtering bridge transparently filters packets in a network. This has its advantages and disadvantages.
Disadvantages include: getting the bridge up (the handbook is outdated on this), active FTP, directions.
I have spent many days getting my bridge up & running. Thought I would share the information with
others in order to help avoiding the hassle I had to face

Materials and Methods


I used a dual PII/400 system with 2 Planet-ENW-9607 /sk(4)/ NICs and the machine was running
DragonflyBSD 1.6.1.

Setting up bridging
To enable bridging and PF support on DragonFly you have two options: load the required kernel
modules or compile them directly into the kernel. I chose the latter, because ALTQ support ha to be
compiled into the kernel anyway in order to use traffic shaping (and I had to recompile for SMP as well
:P). Here is my kernel config (only the parts that differ from GENERIC.):
machine
i386
cpu
I686_CPU
ident
SMP
# Bridging support
pseudo-device bridge
# ALTQ
options
ALTQ
#alternate queueing
options
ALTQ_CBQ
#class based queueing
options
ALTQ_RED
#random early detection
options
ALTQ_RIO
#triple red for diffserv (needs RED)
options
ALTQ_HFSC
#hierarchical fair service curve
options
ALTQ_PRIQ
#priority queue
options
ALTQ_NOPCC
#don't use processor cycle counter
options
ALTQ_DEBUG
#for debugging
# PF
device
pf
device
pfsync
device
pflog
# Symmetric Multiprocessing support
options
SMP
# Symmetric MultiProcessor Kernel
options
APIC_IO
# Symmetric (APIC) I/O

After compiling, installing and rebooting I got bridging support. If you don't need/want to compile a
kernel you can simply open (or create) * /boot/loader.conf* and add these lines:
if_bridge_load="YES"
pf_load="YES"
pflog_load="YES"

and if you don't want to reboot, you can load the modules realtime: # kldload pf.ko # kldload pflog.ko #
kldload if_bridge.ko Well, we have bridging support now, we have to create an interface that represents
the bridge and tell which NICs belong to it.
Edit /etc/rc.conf and include: # Replace x.x.x.x with the desired IP. ifconfig_sk0="inet x.x.x.x netmask
255.255.255.0" ifconfig_sk1="up" cloned_interfaces="bridge0" ifconfig_bridge0="addm sk0 addm sk1
up" pf_enable="YES" pflog_enable="YES" There is no need to have IPs in a bridge, but it is generally
a good idea to have one interface with an IP address assigned, and if we would like to do filtering, it is
necessary (well, for me at least. :P) It is also good because you can SSH into the machine or run it as a
DHCP server, caching DNS server etc. as well (I run dhcpd on it, but won't cover that now, there are
many great howto's out there). That's it, we have a running bridge with PF. Now let's go and configure
filtering!

Configuring the firewall


I will not cover every PF option under the sun, go figure yourself, the PF FAQ is great, although it
lacks some details that I will partially cover. I insist on readingg how lists, macros and filtering rules
work before reading on. Be aware of DragonFly's PF version being the same as OpenBSD 3.6's one!
First off, one needs to understand that a bridge is symmetrical from the aspect of traffic, hence it's a
good idea to filter on only one interface. In my practice, the interface having the IP usually resides
LAN-side, while the IPless one does on the WAN-side:
int_if=sk0
ext_if=sk1

On other interfaces we just pass everything:


pass quick on { $int_if, lo0, bridge0} all

OK, we do the usual filtering, all good, then we get the good ol' active FTP problem. Well, since we
have no new-wave ftp-proxy in the base system, nor does ftpsesame work on DragonFly, we'll have to
stick with the ftp-proxy that comes with inetd. We enable it in /etc/inetd.conf (uncommenting the last
line in the default config):
ftp-proxy

stream

tcp

nowait

root

/usr/libexec/ftp-proxy

ftp-proxy

and (re)start inetd:


# rcrestart inetd

It will listen on any interfaces by default, so you may want to limit access to port 8021 in PF. Now we
set up PF to forward FTP traffic from the LAN to the ftp-proxy. On a router, this can be done with a
simple traffic redirection - we redirect traffic from the external interface to the internal interface (even
ftp-proxy needs an IP to listen on :-P):
# ftp-proxy S01E01
# Do you remember? ($int_if) always gives you the IP of that inerface, you can use
it like (rl0) as well.

rdr on $ext_if inet proto tcp from to any port ftp -> ($int_if) port 8021 but given that the bridge
operates in Layer 2, we just have rewritten this packet, nothing more, it will leave the bridge and
disappear ( rdr * directives never work on a bridge!*). However, we can actually route it to another
interface, and since the packet has just been changed to be destined to hit the IP of that interface, we
have successfully done a forwarding, on a bridge it requires these two steps, though. We use the routeto keyword for this (and if you can NOT find anything about it in the PF FAQ, then you are right, there
are a few words on it in the pf.conf(5) manpage, though):
# ftp-proxy S01E02
pass in log quick on $ext_if route-to ( lo0 127.0.0.1 ) proto tcp from <intnet> to
($int_if) port 8021

We actually route it to the loopback interface, I am unsure why it has to be done this way. (FIXME)
Well, all we have to do now to allow FTPDATA connections from port 20 of the ftpserver to our
machine's IP. This can easily be achieved as ftp-proxy has a dedicated user called proxy, and we have
uid/gid filtering in pf (another poorly documented feature):
# ftp-proxy S01E03 a. k. a. season finale
pass in log quick on $ext_if inet proto tcp from port 20 to ($int_if) user proxy
keep state

and we will have working active FTP behind our bridge.

How to port to DragonFly


Preparing the workspace
To port software with pkgsrc, two approaches are possible:

Add new packages in pkgsrc.


Modify an existing package.

In both cases, the source code of the package must be extracted into a work directory before starting the
porting work. When a source file of the original application is modified, its original will be stored with
the extension . orig. The utility mkpatches, that we'll see later in this document, will use the original
files as a basis to create patches. Please note that the version with the extension .orig must always be
that of the original distribution package.

Add a new package


The page Creating a new pkgsrc package from scratch gives the main steps required to add a new
application to the pkgsrc collection.This tutorial is very well done and summarizes the main steps
required to create a new package.

Modify an existing package


Suppose you would want to modify the application foo/bar from the pkgsrc collection. You then start
by visiting its dedicated directory in pkgsrc, and by running the following command:
cd /usr/pkgsrc/foo/bar
bmake patch

This will restore the changes previously made, which will be a good starting point for the porting. The
bmake command could also be used without options to attempt an immediate first compilation. But you
might have to change some files first, like the GNU scripts for example.
Suppose that the bar application of the collection is version 1.0. Let's go into the newly extracted
source directory located right here[1]:
cd /usr/pkgobj/bootstrap/work/pkgsrc/foo/bar/work/bar-1.0

Voil, your ready! The porting of the source code can now begin.

Working with GNU developper tools


If the application to port uses GNU tools like autoconf, GNU scripts shall be the first thing to look at.
We'll first make a brief introduction to these tools. Then we will cover some modifications required to
adapt their scripts to DragonFly.

Introduction to GNU tools


GNU provides a series of very popular tools which aims at helping C and C++ cross-platform
application development. These tools are Autoconf, Automake and Libtool. They can operate
independently, but they can also be highly integrated.
Their use in common might not be necessarily easy to master. The GNU Autobook (see the References
section), available online for free, can help you find your way with it.
Briefly, the following files are defined by the developers:

Makefile.am and config.h (automake), to generate the Makefile.in files.


configure.ac (autoconf), to generate the configure script.

The aclocal tool generates the file aclocal.m4, which contains dependencies for the configure script. It
is usually run before other tools. The autoconf generated configure script will in turn generate the
makefiles from other generated files, like Makefile.in. So, there are several levels of file generation.
The first level generation is called bootstrapping (see the Bootstrap section). This process generates a
set of files and directories right in the current build directory. For example, in DragonFly 2.8, the files
generated by default are:
autom4te.cache/
.deps/
configure
missing
Makefile.in
install-sh
depcomp
config.sub
config.guess
aclocal.m4

When porting an application, it is usually not necessary to regenerate these files and directories. You
simply need to adjust the Makefile and configure files. But occasionally, you'll have no other choice.
The following sections attempt to give you some leads to follow.

Editing configure, config.guess and config.sub


When the script configure is run, it creates settings suitable for the host platform. In cases where
DragonFly is not on the list of supported systems, we'll need to add it.
By default, the command uname -m is used to detect the host platform. In our case, it will return the
DragonFly label. The host_os variable will be defined after this value and will take the form
dragonflyX (where X is the version of the host system, ex. 2.8). The configure script then uses this
information to define OSARCH. A good place to start the port would be to add the following
definition:
case "${host_os}" in
[]
dragonfly*)
OSARCH=DragonFly
;;

The host_os variable is normally generated by running config.guess. It can also be explicitly defined by
using the build option of the configure script.
Then you can continue by searching through the script to locate:

Other occurrences of case "${host_os}".


Occurrences of the BSD string.
Changes made to other BSD systems, like NetBSD for example. If the software has already
been brought to one of these systems, and it will often be the case, this will allow you to
quickly find many of the modifications required for DragonFly. Of course this is not to take
for granted.

Ideally, you would have to review the entire configure script to make sure that all necessary changes
were made. Some will prefer to go by trial and error, by first changing what's obvious and then actively
testing the results.

The config.guess file may also need to be reviewed. But the latest versions of this script already include
DragonFly in its supported systems. Running it on DragonFly 2.8, for example, returns i386-unknowndragonfly2.8.
The config.sub file contains a set of subroutines used by the configure script. If this file is not a
symlink, it may also need to be changed. But the DragonFly system is already included by default in it,
but possibly lightly implemented.
One special thing to consider in the review of GNU scripts for DragonFly is that some configuration
scripts tries to collect all BSD like systems like this:
case "$uname" in
*BSD*)
[]
esac

In our case, uname will contain the string DragonFly, without the BSD suffix. This pattern should be
searched to explicitly add DragonFly after the sequence:
case "$uname" in
*BSD*|DragonFly)
[]
esac

You might want to solve this problem by forcing variables like host_os and OSARCH to
DragonflyBSD. But it may be more difficult than simply searching for occurrences of *BSD and
applying the necessary changes.

Regenerate GNU scripts


There may be cases where you would want to regenerate the GNU scripts rather than just port them.
Some basic knowledge are required to perform this task. For those who do not have this knowledge, the
GNU Autobook is an excellent reference for it. We'll briefly cover the topic here, by giving some basic
information and clarifying some difficulties.

Autoconf compatibility
As mentioned before, autoconf is used to regenerate the configure script. Operating systems are not
bundled with the same version of it. We must therefore see to use the correct version of GNU tools if
we want to regenerate completelly an application's build scripts.

The makefile.am file


This file contains macros needed to generate the Makefile.in files, which in turn generates the
makefiles. The automake tool defines a syntax of its own. It can generate complex and portable
makefiles from a small makefile.am script.

The configure.ac or configure.in file


This file contains macros necessary to generate the configure script. These are m4 macros, which
generates a shell script when you run autoconf. Required changes to this file are similar to those
proposed for the configure script itself.

Bootstrap
The procedure aiming at regenerating all the GNU build files is called bootstrap. Some ports are
bundled with a script of this name. It runs all the required tools in the correct order. The typical case
will define the following sequence:

aclocal \
&& automake --gnu --add-missing \
&& autoconf

Compiling with FreeBSD as build target


As DragonFly is a FreeBSD fork, many are building it's ports by using the FreeBSD build target.
Although it sometimes works well, like for PostgreSQL, this is risky because compile compatibility
with FreeBSD is not among DragonFly's goals. You must therefore expect that this shortcut is less and
less usable over time, or can possibly generate badly optimized binaries.
In cases where this method still works, they may still be some changes required in GNU scripts. As
mentioned earlier, scripts sometime tries to catch all the BSD variants by using the *BSD string. In
these cases, without minimal changes, the final results might not entirely be what was anticipated for
FreeBSD.

Working with imake and xmkmf


Arguably, imake and xmkmf could be seen as the old building tools for those wishing to develop
portable applications. These tools were designed for the venerable X Window System and its
applications, themselves brought to many operating systems, including UNIX, the BSD family,
GNU/Linux, AIX, Solaris and Windows. This section will cover briefly these build tools, because
they have been gradually discarded in favor of the GNU build tools.
X Window normally comes with a lib/X11/config directory, which contains macro files dedicated to
xmkmf. This directory contains files with extensions *.cf, *.tmpl and *.rules.

*.cf files contains settings specific to it's hosts. X Window comes normally with a
DragonFly.cf file.
*. rules files defines macros that implement the rules that can be used in Imakefile files.
*. tmpl files contains templates that defines compile options.

As for GNU automake, these macros are used to generate makefiles for the operating system that hosts
the X Window System. The normal xmkmf's procedure is to define Imakefile files, then to build the
makefiles by running xmkmf. This tool will in turn call imake, which is a C preprocessor interface for
the make tool. The tool xmkmf will invoke imake with the appropriate arguments for DragonFly.
When xmkmf is executed with the -a option, it automatically runs make Makefiles, make includes and
make depends.
Here's a simple Imakefile example:
BINDIR = /usr/local/bin
.SUFFIXES: cpp
CXXEXTRA_INCLUDES = -I. -I.. -I/usr/pkg/include
LOCAL_LIBRARIES = -L/usr/pkg/lib
EXTRA_LIBRARIES = $(XMLIB) $(XTOOLLIB) $(XLIB) $(XMULIB) $(XPMLIB)
CXXDEBUGFLAGS = -O2 -g
HEADERS = bar.h
SRCS = bar.cpp
OBJS = bar.o
ComplexCplusplusProgramTarget(bar)

Note the similarity of Imakefile with makefiles. You can see the Imakefile as makefiles with macros.

In the example above, the ComplexCplusplusProgramTarget macro defines a rule to generate the C++
"bar" binary, according to the parameters that precedes it. Notably, the parameter .SUFFIXES indicates
that the source files will have the extension .cpp.
In DragonFly, imake and xmkmf are available by installing the package devel/imake. For more
information, links are given in the References section.

Editing the code


To tailor a program to DragonFly, you can either use the definition used to identify the DragonFly
system or use the definitions generated by the GNU tools.

The DragonFly definition


Idealy, an application's code would be reviewed in full to ensure that it uses the right functions and
algorithms for DragonFly.
In fact, the method mostly used is probably to try to compile, to correct the code when it does not, and
to test the final results. When the code is adapted to DragonFly, it may look like this:
#if defined(__DragonFly__)
/* code specific to DragonFly */
#elif
/* code for other systems */
#endif

An initial change might be to scan the definitions for the other BSD variants, as FreeBSD or NetBSD.
We often see this kind of syntax in the code tree:
#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) ||
defined(__Darwin__) || defined(__DragonFly__)
#include <fcntl.h>
#include <net/route.h>
#endif

GNU Tools definition


Porting code can focus on identifying available functions rather than on identifying host systems. A
typical case would be the bcopy function, normally used instead of strcpy on BSD derivatives. Rather
than referring to the host system, the code might look like this (from the GNU Autobook[2]):
#if !HAVE_STRCPY
# if HAVE_BCOPY
#
define strcpy(dest, src)
# else /* !HAVE_BCOPY */
error no strcpy or bcopy
# endif /* HAVE_BCOPY */
#endif /* HAVE_STRCPY */

bcopy (src, dest, 1 + strlen (src))

This guarantees a form of compatibility between systems at the code level. Unfortunately, this
approach is moderately used, which generates more work for porters.

The BSD4.4 Heritage


Many developers are unaware that a BSD definition was created for BSD4.4 and it's derivatives, as
DragonFly, FreeBSD and NetBSD. This definition is used like this (from pkgsrc developer's guide[3]):
#include <sys/param.h>
#if (defined(BSD) && BSD >= 199306)
/* BSD-specific code goes here */
#else
/* non-BSD-specific code goes here */

#endif

The NetBSD group, which maintains pkgsrc, recommends the use of this definition. In fact, it is rarely
used. In the specific case of DragonFly, this definition no longer exists. Therefore it must not be used.

Patching for pkgsrc


Ideally, a port to DragonFly shall always be at the project level. But realistically, it is faster and easier
to modify applications at the pkgsrc level.
pkgsrc contains the patches and some basic information needed to build a package in the package
directory (by default /usr/pkgsrc/<category>/<package>).
The pkgtools/pkgdiff suite of tools helps with creating and updating patches to a package. Extract
the source code into the work directory (by default
/usr/pkgobj/bootstrap/work/pkgsrc/<category>/<package>/work/) by invoking
bmake patch

from the package directory. This fetches the source code if necessary, extracts it and applies any
existing pkgsrc patch, saving the unpatched files with a .orig extension in the work directory.
To create a new patch, save a copy of the original file with that same .orig extension. If it exists, just
keep it do not overwrite or change .orig files or your patches will not apply later on! You may
choose to use pkgvi from the pkgdiff suite to automate this.
You can preview the patches using pkgdiff <file>. To generate all patches invoke
mkpatches

from the package directory (not the work directory!) The new patches will be saved in the patches/
directory along with backups of the previous patchset. When you are content with the generated
patches commit them and update the patch checksums:
mkpatches -c
bmake makepatchsum

You may also revert to the old patches by calling mkpatches -r.
Now clean up and try to rebuild your package:
bmake clean
bmake

If you have any other changes to add, you can remove the package again and repeat these steps.

Submitting a package in pkgsrc


If you plan to port a program to DragonFly and wish to share your work, pkgsrc is ideal for you. The
first thing to do is to inquire whether the application is already in the collection. If not, you're free to
add your work and be registered as a port maintainer.
This section will attempt to give you some minimal guidance on submitting changes in the pkgsrc
collection. This information is incomplete, please consult the page Submitting and Commiting carefully
before submitting anything for real.
A source code package can be submitted with the gtk-send-pr package (pr=Problem Report), or by
visiting the page NetBSD Problem Report. The indications given by the pkgsrc developer's guide in
connection with this tool are summarized here:

In the form of the problem report, the category should be pkg, the synopsis should include the
package name and version number, and the description field should contain a short description of your
package (contents of the
T variable or D S
file are
). The uuencoded package data
should go into the fix field. http www.netbsd.org docs pkgsrc submit.html submitting-yourpackage
It is also possible to import new packages in pkgsrc-wip. See http://pkgsrc-wip.sourceforge.net/ for
more information.