skip to Main Content

Wide Area Bonjour – Airprint over Layer 3 VPN

What is Airprint and how does it work?

Airprint is Apple’s voodoo magic allowing iOS & macOS devices to auto discover printers on a user’s local network. It makes use of their implementation of zero-configuration networking – Bonjour.

Bonjour utilizes both the mDNS (Multicast DNS) and DNS-SD protocol. Multicast DNS provides the ability to perform DNS-like operations on a local network without the need of a local name server.  It relies on broadcasting multicast packets, these are packets that are ‘broadcast’ to all devices in a network. DNS-SD adds the support needed to discover network services over DNS.

Okay, so what is Wide Area Bonjour?

Wide Area Bonjour, also known as wide-area DNS-SD, allows iOS/macOS clients to search for, and find, Airprint printers or Airplay compatible devices such as Apple TVs outside of the devices broadcast domain.

A great example of this is a Layer 3 (TUN) VPN server inside a network where an Airprint capable printer resides. When a client connects to the VPN, the device cannot find the printer, as it is not receiving any broadcast traffic from the remote network as it is outside of the printer’s broadcast domain.

When an iOS/macOS client searches for printers in the network, it also determines if there are any wide-area DNS-SD services that it needs to search for. It does this by prepending “lb._dns-sd._udp” to the default DNS domain and then querying for PTR records with that name. E.g. lb._dns-sd._udp.example.com. This is a really neat feature that avoids the need to proxy or repeat mDNS traffic across the tunnel.

Recently, a client came to me in search of a long term remote printing solution. The client had a Brother laser printer which had no remote printing feature other than the soon to be deprecated Google Cloud Print. They also needed to be able to print from their iPad.

I proposed a Raspberry Pi 3 situated inside the network running Ubuntu Server 20.04, serving as an OpenVPN server but also as a BIND name server to serve the DNS-SD records.

How I did it – Step by Step

Setting up OpenVPN

I used this handy script to setup the VPN server. It saves a lot of hassle by creating all of the client certificates and configurations for you, it also defaults to the fast & secure AES-128-GCM cipher, rather than the outdated OpenVPN default of BF-CBC.

After you run this script, it will put your user configuration in your user’s home directory. Be sure to transfer this configuration to your client devices securely, as this configuration contains the certificates.

We will need to add a few things to the generated server configuration. We need to push our DNS server so that the clients can lookup our DNS-SD records. By default, our VPN server will use 10.8.0.0/24 as the client network. The DNS server will run on the same host as OpenVPN so we will push 10.8.0.1.

Edit /etc/openvpn/server.conf and add:

push "dhcp-option DNS 10.8.0.1"

It is also recommended that you push the search domain that you will set records in later. In my case the client is a landlord of a pub, The Wonston Arms, so I am setting the domain as wonston.arms:

push "dhcp-option DOMAIN wonston.arms"

The clients also need to be able to access the remote network, so we need to push a route. In my case, the remote network where the printer resided was 192.168.1.0/24.

Add this line:

push "route 192.168.1.0 255.255.255.0"

By default, this script creates a systemd service that adds the iptables rules on boot. This isn’t preferable in my case, so I installed iptables-persistent which will do the same thing:

sudo apt install iptables-persistent

and when it prompts if you would like to save the current rules, select Yes. This will save the rules set by the script into /etc/iptables/rules.v4.

I then deleted the service created by the script

sudo rm /etc/systemd/system/iptables-openvpn.service

and reloaded systemd.

sudo systemctl daemon-reload

Finally, I restarted the OpenVPN server

sudo systemctl restart openvpn

Now you’ll be able to connect a client to the VPN and ping your printer’s IP address, but iOS/macOS clients will not be able to see it in Airprint.

Setting up BIND

Okay, so it would help to understand what is going on here before we get started.

When you open the Airprint dialog on an iOS device, it sends some queries to its local name server. Here it is in action from a tcpdump:

20:06:45.772174 IP (tos 0x0, ttl 255, id 50095, offset 0, flags [none], proto UDP (17), length 86)
10.8.0.3.62719 > 10.8.0.1.53: 29536+ PTR? lb._dns-sd._udp.0.0.168.192.in-addr.arpa. (58)
20:06:45.773277 IP (tos 0x0, ttl 255, id 6145, offset 0, flags [none], proto UDP (17), length 83)
10.8.0.3.49457 > 10.8.0.1.53: 12549+ PTR? lb._dns-sd._udp.0.0.8.10.in-addr.arpa. (55)
20:06:45.773631 IP (tos 0x0, ttl 255, id 20444, offset 0, flags [none], proto UDP (17), length 74)
10.8.0.3.60716 > 10.8.0.1.53: 8163+ PTR? lb._dns-sd._udp.wonston.arms. (46)

As you can see, it is asking for PTR records of lb._dns-sd._udp.0.0.168.192.in-addr.arpa, lb._dns-sd._udp.0.0.8.10.in-addr.arpa and lb._dns-sd._udp.wonston.arms.

0.0.8.10 is from our VPNs network, and wonston.arms is the search domain we are pushing. It is prepending lb._dns-sd._udp onto the default search domain. If we did not push a search domain, it would simply lookup only the first two, and would still work provided those PTR records were set in the 0.0.8.10 or 0.0.168.192 zone.

Let’s install bind

sudo apt install bind9

Firstly, we will edit /etc/bind/named.conf.options. This file defines options such the forwarders to use and which networks are trusted for recursion. We trust our VPN network 10.8.0.0/24 and forward requests to the Cloudflare resolvers.

acl "trusted" {
        10.8.0.0/24;
};
options {
        directory "/var/cache/bind";
        forwarders {
                1.1.1.1;
                1.0.0.1;
         };
        dnssec-validation auto;
        recursion yes;
        allow-recursion { trusted; };
        listen-on { 10.8.0.1; };
        allow-transfer { none; };
};

Next, edit /etc/bind/named.conf.local. This is where we will define our zones. If you are pushing your search domain you will only need that domains zone – so in my case wonston.arms. I’m going to do both anyway.

zone "wonston.arms" {
        type master;
        file "/etc/bind/zones/db.wonston.arms";
};
zone "8.10.in-addr.arpa" {
    type master;
    file "/etc/bind/zones/db.10.8";
};

I then created the zones directory

sudo mkdir /etc/bind/zones

I created the necessary zone files as I set above

sudo touch /etc/bind/zones/db.wonston.arms
sudo touch /etc/bind/zones/db.10.8

and a file where we will set any printer’s records in

sudo touch /etc/bind/zones/airprint

Before I set the records in the zone files, I needed the dns-sd records in zone file format. This can be easily obtained by running the dns-sd utility on a device inside the printer’s network. The utility is packaged with Bonjour Print Services on Windows, so I installed that. It is installed by default on macOS.

On a device with the dns-sd utility, I ran:

dns-sd -Z _ipp._tcp,_universal

Leave this running until you see an output similar to below, but with your printers name and IP etc. Note that this did not provide any results for me on Windows, so I ended up needing to use a Mac.

@                                               PTR     The\032Wonston\032Arms
The\032Wonston\032Arms                          SRV     0 0 631 BRN3C2AF4E1D5F9.local.
The\032Wonston\032Arms                          TXT     "txtvers=1" "qtotal=1" "pdl=application/octet-stream,image/urf,image/pwg-raster" "rp=ipp/print" "note=" "ty=Brother MFC-L3750CDW" "product=(Brother MFC-L3750CDW)" "adminurl=http://192.168.1.251/net/net/airprint.html" "priority=25" "usb_MFG=Brother" "usb_MDL=MFC-L3750CDW" "usb_CMD=PJL,PCL,PCLXL,URF" "Color=T" "Copies=T" "Duplex=T" "Scan=T" "PaperCustom=T" "Binary=T" "Transparent=T" "TBCP=F" "URF=SRGB24,W8,CP1,IS1-4,MT1-3-4-5-8-11,OB10,PQ4-5,RS600,DM1" "UUID=e3248000-80ce-11db-8000-3c2af405afe9" "print_wfds=T"

I took note of this for the zone file.

I edited the wonston.arms zone, /etc/bind/zones/db.wonston.arms:

$TTL    604800
@       IN      SOA     ubuntu.wonston.arms. root.wonston.arms. (
                              1        ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL
;
        IN      NS      ubuntu.wonston.arms.
ubuntu  IN      A       10.8.0.1
BRN3C2AF4E1D5F9 IN      A       192.168.1.251
printer         IN      CNAME   BRN3C2AF4E1D5F9.wonston.arms.
lb._dns-sd._udp  IN      PTR     wonston.arms.
b._dns-sd._udp   IN      PTR     wonston.arms.
$INCLUDE /etc/bind/zones/airprint _ipp._tcp

As you can see, I set a few records for the RPi and printer, and then for those lb._dns-sd._udp records. The b._dns-sd._udp record is legacy but it might be good to include it anyway. I then include the airprint file.

Inside /etc/bind/zones/airprint:

@                                               PTR     The\032Wonston\032Arms
_universal._sub                                 PTR     The\032Wonston\032Arms
The\032Wonston\032Arms                          SRV     0 0 631 BRN3C2AF4E1D5F9.wonston.arms.
The\032Wonston\032Arms                          TXT     "txtvers=1" "qtotal=1" "pdl=application/octet-stream,image/urf,image/pwg-raster" "rp=ipp/print" "note=" "ty=Brother MFC-L3750CDW" "product=(Brother MFC-L3750CDW)" "adminurl=http://192.168.1.251/net/net/airprint.html" "priority=25" "usb_MFG=Brother" "usb_MDL=MFC-L3750CDW" "usb_CMD=PJL,PCL,PCLXL,URF" "Color=T" "Copies=T" "Duplex=T" "Scan=T" "PaperCustom=T" "Binary=T" "Transparent=T" "TBCP=F" "URF=SRGB24,W8,CP1,IS1-4,MT1-3-4-5-8-11,OB10,PQ4-5,RS600,DM1" "UUID=e3248000-80ce-11db-8000-3c2af405afe9" "print_wfds=T"

Note 2 things. I have added in the _universal._sub record, this is necessary as AirPrint devices don’t browse for all IPP printers, only for the subset of IPP printers that support Universal Raster Format (URF). I have also changed the FQDN of the printer to one that the client device can resolve (a record that was set above in the wonston.arms zone file above).

And optionally set up the reverse 0.0.8.10 zone

/etc/bind/zones/db.10.8:

$TTL    604800
@       IN      SOA     ubuntu.wonston.arms. root.wonston.arms. (
                              1         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL
;


; name servers - NS records
        IN      NS      ubuntu.wonston.arms.
lb._dns-sd._udp.0.0.8.10.in-addr.arpa.  IN      PTR     wonston.arms.
b._dns-sd._udp.0.0.8.10.in-addr.arpa.   IN      PTR     wonston.arms.

It is important to note – any time you make a change to a zone file and subsequently restart bind, you must increase the serial for the change to be read.

Restart bind

sudo systemctl restart bind9

Check the status of bind to ensure your configuration will be read

sudo systemctl status bind9

If all is okay, give it a go!

Hopefully this might help someone out who faces a similar challenge. I found documentation on this very vague, especially for anyone who has never used bind before. I’m new to bind, so excuse any incorrect or unnecessary configuration!

 

dnsmasq

If you prefer to use dnsmasq, like I ultimately did, you can format the above zone file like so in dnsmasq.conf:

ptr-record=b._dns-sd._udp.wonston.arms.,wonston.arms.
ptr-record=lb._dns-sd._udp.wonston.arms.,wonston.arms.
ptr-record=_ipp._tcp.wonston.arms.,TheWonstonArms._ipp._tcp.wonston.arms.
ptr-record=_universal._sub._ipp._tcp.wonston.arms.,TheWonstonArms._ipp._tcp.wonston.arms.
srv-host=TheWonstonArms._ipp._tcp.wonston.arms.,BRW405BD87DB78C.wonston.arms.,631,0,0
txt-record=TheWonstonArms._ipp._tcp.wonston.arms.,"txtvers=1","qtotal=1","pdl=application/octet-stream,image/urf,image/pwg-raster","rp=ipp/print","note=","ty=Brother MFC-L3750CDW","product=(Brother MFC-L3750CDW)","adminurl=http://10.10.1.175/net/net/airprint.html","priority=25","usb_MFG=Brother","usb_MDL=MFC-L3750CDW","usb_CMD=PJL,PCL,PCLXL,URF","Color=T","Copies=T","Duplex=T","Scan=T","PaperCustom=T","Binary=T","Transparent=T","TBCP=F","URF=SRGB24,W8,CP1,IS1-4,MT1-3-4-5-8-11,OB10,PQ4-5,RS600,DM1","UUID=e3248000-80ce-11db-8000-3c2af405afe9","print_wfds=T"

This Post Has 0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top