Why would you want a Bonjour Gateway? Because you have more than 1 network and you want to have Bonjour devices be able to talk to each other even though they aren’t on the same network.

For example, at work, you have Apple TVs that are on one network, and guests that connect to a different network and they need to be able to present using the Apple TV. Why do I keep saying Bonjour instead of mDNS? They are, for the purposes of this article, equivalent, and I have to pick one. Since its more likely that people of an Apple bent will be interested in this, I’m using the Apple terminology.

Prerequisites

When building your machine (physical or virtual), you will either need 2 NICs (network interface card), or the ability to use a trunk port and multiple VLANs. If you choose the latter, don’t forget to adjust these settings as you go.

FreeBSD Install

Standard FreeBSD install. Used pkg to install the following:

accessibility/atk
converters/libiconv
databases/gdbm
devel/dbus
devel/dbus-glib
devel/gettext-runtime
devel/gettext-tools
devel/glib20
devel/gobject-introspection
devel/icu
devel/libdaemon
devel/libdevq
devel/libedit
devel/libevent2
devel/libffi
devel/libglade2
devel/libpciaccess
devel/libpthread-stubs
devel/llvm37
devel/pcre
devel/pkgconf
dns/libidn
editors/vim
emulators/open-vm-tools-nox11
emulators/tpm-emulator
graphics/cairo
graphics/gbm
graphics/gdk-pixbuf2
graphics/graphite2
graphics/gtk-update-icon-cache
graphics/jasper
graphics/jbigkit
graphics/jpeg-turbo
graphics/libEGL
graphics/libGL
graphics/libdrm
graphics/libglapi
graphics/png
graphics/tiff
lang/perl5.20
lang/python
lang/python2
lang/python27
math/gmp
misc/hicolor-icon-theme
misc/pciids
misc/shared-mime-info
net/avahi
net/avahi-app
net/avahi-autoipd
net/avahi-gtk
net/avahi-header
net/libdnet
ports-mgmt/dialog4ports
ports-mgmt/pkg
print/cups
print/freetype2
print/harfbuzz
print/indexinfo
print/libpaper
security/ca_root_nss
security/gnutls
security/libtasn1
security/nettle
security/p11-kit
security/sudo
security/trousers
sysutils/gnome_subr
sysutils/tmux
textproc/expat2
textproc/libxml2
textproc/xmlcatmgr
x11-fonts/dejavu
x11-fonts/encodings
x11-fonts/font-bh-ttf
x11-fonts/font-misc-ethiopic
x11-fonts/font-misc-meltho
x11-fonts/font-util
x11-fonts/fontconfig
x11-fonts/libXft
x11-fonts/libfontenc
x11-fonts/mkfontdir
x11-fonts/mkfontscale
x11-fonts/xorg-fonts-truetype
x11-toolkits/gtk20
x11-toolkits/libXt
x11-toolkits/pango
x11/compositeproto
x11/damageproto
x11/dri2proto
x11/fixesproto
x11/glproto
x11/inputproto
x11/kbproto
x11/libICE
x11/libSM
x11/libX11
x11/libXau
x11/libXcomposite
x11/libXcursor
x11/libXdamage
x11/libXdmcp
x11/libXext
x11/libXfixes
x11/libXi
x11/libXinerama
x11/libXrandr
x11/libXrender
x11/libXv
x11/libXvMC
x11/libXxf86vm
x11/libxcb
x11/libxshmfence
x11/pixman
x11/randrproto
x11/renderproto
x11/videoproto
x11/xcb-util
x11/xcb-util-renderutil
x11/xextproto
x11/xf86vidmodeproto
x11/xineramaproto
x11/xproto

rc.conf settings:

hostname="bonjour-bridge.cmhome"
ifconfig_em0="DHCP"
ifconfig_em1="inet 198.18.9.10/24"
ifconfig_em0_ipv6="inet6 accept_rtadv"
ifconfig_em1_ipv6="inet6 fc00:101:ca7:9::10/64"
sshd_enable="YES"
ntpd_enable="YES"
ntpd_sync_on_start="YES"
dumpdev="AUTO"
vmware_guest_vmblock_enable="YES"
vmware_guest_vmhgfs_enable="YES"
vmware_guest_vmmemctl_enable="YES"
vmware_guest_vmxnet_enable="YES"
vmware_guestd_enable="YES"
avahi_daemon_enable="YES"
dbus_enable="YES"
pf_enable="YES"
pflog_enable="YES"

I picked a static IP on one side, and left the other DHCP. Doesn’t really matter how you do it. Could be static on both sides, probably don’t want to do DHCP on both sides unless you ensure you don’t pick up 2 default routes.

Note: If you aren’t in a virtual machine, skip the open-vm-tools-nox11 pkg, and the vmware-* sysrc knobs.

Configuring Avahi

Literally, the only line I changed in avahi-daemon.conf was (uncomment):

enable-reflector=no

Enable Avahi (and DBUS):

sysrc avahi_daemon_enable=YES
sysrc dbus_enable=YES

Start the services:

service avahi-daemon start
service dbus start

You should see something like the following, indicating the process are running:

[louisk@bonjour-bridge avahi 34 ]$ ps ax | egrep 'dbus|avahi'
 670  -  Is     0:00.00 /usr/local/bin/dbus-daemon --system
 674  -  S      0:03.21 avahi-daemon: running [bonjour-bridge.local] (avahi-dae
[louisk@bonjour-bridge avahi 35 ]$

Configuring PF

I wanted to block essentially everything but Bonjour (mDNS).

#
# Based on Calomel.org pf.conf
#
################ FreeBSD pf.conf ##########################
# Required order: options, normalization, queueing, translation, filtering.
# Note: translation rules are first match while filter rules are last match.
################ Macros ###################################

### Interfaces ###
 ExtIf ="em0"
 IntIf ="em1"

### Queues, States and Types ###
 IcmpPing ="icmp-type 8 code 0"
 SshQueue ="(ssh_bulk, ssh_login)"
 SynState ="flags S/UAPRSF synproxy state"
 TcpState ="flags S/UAPRSF modulate state"
 UdpState ="keep state"

### Ports ###
 AntiScanPort="{23:79, 6000:8000}"
 BlockNoLogPort="{17500,445,136:139}"

### Stateful Tracking Options (STO) ###
 OpenSTO     ="(max 500, source-track rule, max-src-conn 10, max-src-nodes 256)"
 SshSTO      ="(max  10, source-track rule, max-src-conn 10, max-src-nodes  10, max-src-conn-rate 5/3,  overload <BLOCKTEMP> flush global)"
 WebSTO      ="(max 100, source-track rule, max-src-conn 10, max-src-nodes  10, max-src-conn-rate 5/10, overload <BLOCKTEMP> flush global)"
 AntiScanSTO ="(max  10, source-track rule, max-src-conn  1, max-src-nodes  10, max-src-conn-rate 1/60, overload <BLACKTEMP> flush global)"

### Tables ###
 table <BLOCKTEMP> counters

################ Options ######################################################
### Misc Options
 set skip on lo
 set debug urgent
 set block-policy drop
 set loginterface $ExtIf
 set loginterface $IntIf
 set state-policy if-bound
 set fingerprints "/etc/pf.os"
 set ruleset-optimization none

### Timeout Options
 set optimization normal
 set timeout { tcp.closing 60, tcp.established 7200}

################ Normalization ###############################################
# set-tos 0x1c is Maximize-Reliability + Minimize-Delay + Maximize-Throughput
scrub out log on $ExtIf all random-id min-ttl 15 set-tos 0x1c fragment reassemble
scrub out log on $IntIf all random-id min-ttl 15 set-tos 0x1c fragment reassemble

################ Filtering ###################################################
# Rules are best (closest) match. Notice we optimized the rules so external
# interface parsing is first followed by the internal interface.

### Things to block silently
 block drop in quick      on $ExtIf inet proto tcp from any         to any	port $BlockNoLogPort
 block drop in quick      on $ExtIf inet proto udp from any         to any	port $BlockNoLogPort

### $ExtIf block abusive hosts in temp tables
 block drop in  log quick on $ExtIf                from <BLOCKTEMP> to any
 block drop in  log quick on $IntIf                from <BLOCKTEMP> to any

### $ExtIf default block with drop
 block drop in  log       on $ExtIf
 block drop in  log       on $IntIf

### $ExtIf $IntIf inbound
# DHCP
 pass in       on $ExtIf inet   proto tcp   from any         to any         port 67:68         $UdpState $OpenSTO
 pass in       on $ExtIf inet   proto udp   from any         to any         port 67:68         $UdpState $OpenSTO
 pass in       on $ExtIf inet   proto tcp   from !($ExtIf)   to 224.0.0.251 port 5353:5354     $UdpState $OpenSTO
 pass in       on $ExtIf inet   proto udp   from !($ExtIf)   to 224.0.0.251 port 5353:5354     $UdpState $OpenSTO
 pass in       on $IntIf inet   proto tcp   from !($IntIf)   to 224.0.0.251 port 5353:5354     $UdpState $OpenSTO
 pass in       on $IntIf inet   proto udp   from !($IntIf)   to 224.0.0.251 port 5353:5354     $UdpState $OpenSTO
 pass in       on $ExtIf inet   proto tcp   from !($ExtIf)   to ($ExtIf)    port ssh           $TcpState $SshSTO
 pass in       on $ExtIf inet6  proto tcp   from !($ExtIf)   to ($ExtIf)    port ssh           $TcpState $SshSTO
 pass in       on $ExtIf inet   proto icmp  from !($ExtIf)   to any                            $UdpState $OpenSTO
 pass in       on $IntIf inet   proto icmp  from !($IntIf)   to any                            $UdpState $OpenSTO
 pass in       on $ExtIf inet6  proto icmp6 from !($ExtIf)   to any                            $UdpState $OpenSTO
 pass in       on $IntIf inet6  proto icmp6 from !($IntIf)   to any                            $UdpState $OpenSTO
 pass in log   on $ExtIf inet   proto tcp   from any         to any         port $AntiScanPort $SynState $AntiScanSTO
 pass in log   on $IntIf inet   proto tcp   from any         to any         port $AntiScanPort $SynState $AntiScanSTO
 pass in log   on $ExtIf inet6  proto tcp   from any         to any         port $AntiScanPort $SynState $AntiScanSTO
 pass in log   on $IntIf inet6  proto tcp   from any         to any         port $AntiScanPort $SynState $AntiScanSTO

### $ExtIf $IntIf outbound
# DHCP
 pass out      on $ExtIf inet   proto tcp   from any         to any         port 67:68         $UdpState $OpenSTO
 pass out      on $ExtIf inet   proto udp   from any         to any         port 67:68         $UdpState $OpenSTO
# MDNS
 pass out      on $ExtIf inet   proto tcp   from ($ExtIf)    to 224.0.0.251 port 5353:5354     $UdpState $OpenSTO
 pass out      on $ExtIf inet   proto udp   from ($ExtIf)    to 224.0.0.251 port 5353:5354     $UdpState $OpenSTO
 pass out      on $IntIf inet   proto tcp   from ($ExtIf)    to 224.0.0.251 port 5353:5354     $UdpState $OpenSTO
 pass out      on $IntIf inet   proto udp   from ($ExtIf)    to 224.0.0.251 port 5353:5354     $UdpState $OpenSTO

 pass out      on $ExtIf inet   proto tcp   from ($ExtIf)    to any                            $TcpState $OpenSTO
 pass out      on $IntIf inet   proto tcp   from ($ExtIf)    to any                            $TcpState $OpenSTO
 pass out      on $ExtIf inet6  proto tcp   from ($ExtIf)    to any                            $TcpState $OpenSTO
 pass out      on $IntIf inet6  proto tcp   from ($ExtIf)    to any                            $TcpState $OpenSTO
 pass out      on $ExtIf inet   proto udp   from ($ExtIf)    to any                            $UdpState $OpenSTO
 pass out      on $IntIf inet   proto udp   from ($ExtIf)    to any                            $UdpState $OpenSTO
 pass out      on $ExtIf inet6  proto udp   from ($ExtIf)    to any                            $UdpState $OpenSTO
 pass out      on $IntIf inet6  proto udp   from ($ExtIf)    to any                            $UdpState $OpenSTO
 pass out      on $ExtIf inet   proto icmp  from ($ExtIf)    to any                            $UdpState $OpenSTO
 pass out      on $IntIf inet   proto icmp  from ($ExtIf)    to any                            $UdpState $OpenSTO
 pass out      on $ExtIf inet6  proto icmp6 from ($ExtIf)    to any                            $UdpState $OpenSTO
 pass out      on $IntIf inet6  proto icmp6 from ($ExtIf)    to any                            $UdpState $OpenSTO

############# END of FreeBSD pf.conf #######################

Enable pf and pflog with:

sysrc pf_enable=YES
sysrc pflog_enable=YES

Start the services (starting pf will kill any remote sessions such as ssh, but if things are configured properly, you will not be locked out, just existing sessions are terminated):

service pflog start
service pf start

You can check that pf is running by doing:

pfctl -s rules

You should see output like:

[louisk@bonjour-bridge avahi 35 ]$ sudo pfctl -s rules
scrub out log on em0 all random-id min-ttl 15 set-tos 0x1c fragment reassemble
scrub out log on em1 all random-id min-ttl 15 set-tos 0x1c fragment reassemble
block drop in quick on em0 inet proto tcp from any to any port = 17500
block drop in quick on em0 inet proto tcp from any to any port = microsoft-ds
block drop in quick on em0 inet proto tcp from any to any port 136:139
block drop in quick on em0 inet proto udp from any to any port = 17500
block drop in quick on em0 inet proto udp from any to any port = microsoft-ds
block drop in quick on em0 inet proto udp from any to any port 136:139
block drop in log quick on em0 from <BLOCKTEMP> to any
block drop in log quick on em1 from <BLOCKTEMP> to any
block drop in log on em0 all
block drop in log on em1 all
pass in on em0 inet proto tcp from any to any port 67:68 flags S/SA keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass in on em0 inet proto udp from any to any port 67:68 keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass in on em0 inet proto tcp from ! (em0) to 224.0.0.251 port 5353:5354 flags S/SA keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass in on em0 inet proto udp from ! (em0) to 224.0.0.251 port 5353:5354 keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass in on em1 inet proto tcp from ! (em1) to 224.0.0.251 port 5353:5354 flags S/SA keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass in on em1 inet proto udp from ! (em1) to 224.0.0.251 port 5353:5354 keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass in on em0 inet proto tcp from ! (em0) to (em0) port = ssh flags S/FSRPAU modulate state (max 10, source-track rule, max-src-conn 10, max-src-conn-rate 5/3, max-src-nodes 10, overload <BLOCKTEMP> flush global, if-bound, adaptive.start 6, adaptive.end 12, src.track 3)
pass in on em0 inet6 proto tcp from ! (em0) to (em0) port = ssh flags S/FSRPAU modulate state (max 10, source-track rule, max-src-conn 10, max-src-conn-rate 5/3, max-src-nodes 10, overload <BLOCKTEMP> flush global, if-bound, adaptive.start 6, adaptive.end 12, src.track 3)
pass in on em0 inet proto icmp from ! (em0) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass in on em1 inet proto icmp from ! (em1) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass in on em0 inet6 proto ipv6-icmp from ! (em0) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass in on em1 inet6 proto ipv6-icmp from ! (em1) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass in log on em0 inet proto tcp from any to any port 23:79 flags S/FSRPAU synproxy state (max 10, source-track rule, max-src-conn 1, max-src-conn-rate 1/60, max-src-nodes 10, overload <BLACKTEMP> flush global, if-bound, adaptive.start 6, adaptive.end 12, src.track 60)
pass in log on em0 inet proto tcp from any to any port 6000:8000 flags S/FSRPAU synproxy state (max 10, source-track rule, max-src-conn 1, max-src-conn-rate 1/60, max-src-nodes 10, overload <BLACKTEMP> flush global, if-bound, adaptive.start 6, adaptive.end 12, src.track 60)
pass in log on em1 inet proto tcp from any to any port 23:79 flags S/FSRPAU synproxy state (max 10, source-track rule, max-src-conn 1, max-src-conn-rate 1/60, max-src-nodes 10, overload <BLACKTEMP> flush global, if-bound, adaptive.start 6, adaptive.end 12, src.track 60)
pass in log on em1 inet proto tcp from any to any port 6000:8000 flags S/FSRPAU synproxy state (max 10, source-track rule, max-src-conn 1, max-src-conn-rate 1/60, max-src-nodes 10, overload <BLACKTEMP> flush global, if-bound, adaptive.start 6, adaptive.end 12, src.track 60)
pass in log on em0 inet6 proto tcp from any to any port 23:79 flags S/FSRPAU synproxy state (max 10, source-track rule, max-src-conn 1, max-src-conn-rate 1/60, max-src-nodes 10, overload <BLACKTEMP> flush global, if-bound, adaptive.start 6, adaptive.end 12, src.track 60)
pass in log on em0 inet6 proto tcp from any to any port 6000:8000 flags S/FSRPAU synproxy state (max 10, source-track rule, max-src-conn 1, max-src-conn-rate 1/60, max-src-nodes 10, overload <BLACKTEMP> flush global, if-bound, adaptive.start 6, adaptive.end 12, src.track 60)
pass in log on em1 inet6 proto tcp from any to any port 23:79 flags S/FSRPAU synproxy state (max 10, source-track rule, max-src-conn 1, max-src-conn-rate 1/60, max-src-nodes 10, overload <BLACKTEMP> flush global, if-bound, adaptive.start 6, adaptive.end 12, src.track 60)
pass in log on em1 inet6 proto tcp from any to any port 6000:8000 flags S/FSRPAU synproxy state (max 10, source-track rule, max-src-conn 1, max-src-conn-rate 1/60, max-src-nodes 10, overload <BLACKTEMP> flush global, if-bound, adaptive.start 6, adaptive.end 12, src.track 60)
pass out on em0 inet proto tcp from any to any port 67:68 flags S/SA keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em0 inet proto udp from any to any port 67:68 keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em0 inet proto tcp from (em0) to 224.0.0.251 port 5353:5354 flags S/SA keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em0 inet proto udp from (em0) to 224.0.0.251 port 5353:5354 keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em1 inet proto tcp from (em0) to 224.0.0.251 port 5353:5354 flags S/SA keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em1 inet proto udp from (em0) to 224.0.0.251 port 5353:5354 keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em0 inet proto tcp from (em0) to any flags S/FSRPAU modulate state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em1 inet proto tcp from (em0) to any flags S/FSRPAU modulate state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em0 inet6 proto tcp from (em0) to any flags S/FSRPAU modulate state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em1 inet6 proto tcp from (em0) to any flags S/FSRPAU modulate state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em0 inet proto udp from (em0) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em1 inet proto udp from (em0) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em0 inet6 proto udp from (em0) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em1 inet6 proto udp from (em0) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em0 inet proto icmp from (em0) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em1 inet proto icmp from (em0) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em0 inet6 proto ipv6-icmp from (em0) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
pass out on em1 inet6 proto ipv6-icmp from (em0) to any keep state (max 500, source-track rule, max-src-conn 10, max-src-nodes 256, if-bound, adaptive.start 300, adaptive.end 600)
[louisk@bonjour-bridge avahi 36 ]$

If you need to debug the firewall rules, you can run tcpdump against pflog0 and see whats getting blocked.

Summary

You should now be able to have your (as an example) Apple TV on one network and your phone/computer on another network and be able to use AirPlay. The Bonjour Gateway will block incoming traffic that isn’t part of mDNS, while allowing your airplay to function as expected.

Footnotes and References