Advertising a legacy network printer with Bonjour (with a little help from launchd)

logo_bonjour



Rendezvous is truly revolutionary. It's one of the things that is going to have the largest impact on application design over the next couple of years. All the UIs, and all the security models, are going to have to be rethought once all applications are rendezvous-enabled. And that is going to happen.... We're just at the beginning of the second internet revolution, and Rendezvous is a big part of it.     -- Tim O’Reilly, May. 2003




One of my favorite technologies from Apple is Bonjour/Zeroconf. Bonjour (formerly Rendezvous) for those who don’t know is Apple’s implementation of Zeroconf. Zeroconf is a technology developed at Apple (being pushed as an open standard) that in layman terms brings plug & play or “zero configuration” to IP based services.

I won’t go into the details of how it works (local-link addressing, multicast DNS, and Service Discovery) as there are plenty of better resources that already cover this.

But for those looking for a high level explanation of why somebody might care, one good question is, ‘Why do we have so many technologies that do the same thing?’
USB
Firewire
Ethernet
RS-232 (serial port)
Parallel Port

There’s also the wireless side:
Infrared
Bluetooth
802.11 (Wi-Fi)

I have to be a little careful here to distinguish between the medium and the protocol. In theory, all of the mediums above could use the IP protocol. However, in practice IP is mainly used in just ethernet and Wi-Fi. Instead all of the other mediums have opted to also invent their own transport protocols.

By some measures, this is a colossal waste of time and effort. IP has been around for a long time in computer terms. It is well known, with a vast amount of software already written and optimized. By inventing new protocols, you have to write brand new drivers (and install them, ugh), and you also need new software applications to use them. You can’t just take your trusty FTP program and use it on your USB key. At best, somebody needs to write an adaptor so you can use it with you key. (Replace FTP program with Explorer or Finder and USB key with newfangled Bluetooth portable storage device and you might see the picture better.)

On top of that, focusing on just the wired technologies for a moment, and ignoring protocols, why is there an advantage of needing USB, Firewire, and Ethernet all at the same time? They all essentially do the same thing. They are just cables to shove data through. But all the cables are incompatible which seems like a waste of effort and money. Bandwidth-wise, they are all pretty comparable. So why not pick just one? Ethernet is already king in wide area networks. Why not simply reuse the same thing in the local domain?

Well there are a lot of reasons why Ethernet and IP didn’t dominate for consumer devices and we have primarily USB and a lesser extent Firewire. Protocol speed and efficiency probably wasn’t the main issue otherwise Firewire would have won out over USB. Power over the cable is pretty nice which Firewire and more recently USB 2.0 have. Though Power Over Ethernet (PoE) is finally available. But the real death blow to IP based devices in the consumer arena is setup and configuration.

With USB and Firewire, it’s (mostly) plug-and-play. With IP, you worry about non-conflicting IP addresses, subnets, DNS, gateways and most people don’t want to deal with this stuff.

So that’s where Zeroconf comes in. It figured out how to pull together existing technologies in a new way so plug-and-play was achievable with IP based devices. With Zeroconf, you can plug in a device (like a printer), it will get an address if nobody else gives it one, give it a human readable name you can refer to it by, and it will advertise its services so everybody who uses those services can easily find it. In an alternate reality, if Zeroconf and Power Over Ethernet had materialized 10 years earlier, we might have never had/needed USB and Firewire.

There is much beauty in Zeroconf, such as how they built it on top of existing technologies (local-link addressing, multicast DNS), how it works perfectly with existing infrastructure so it won’t conflict, its simplicity, its efficiency, its lightweight implementation, and so forth.



Anyhow, I recently picked up a used network add-on card for my aforementioned Lexmark Optra S printer for about $10. Prior to that, it depended on a parallel port connection. This meant that it was tied to my Windows box through the parallel port and I shared it via Samba. (Mac’s don’t have parallel ports.) But this meant I always needed to keep the machine on if I wanted my printer to always be accessible. I’m hoping that the printer will last a long time to come, but I’ve been worried that they will disappear as standard components soon on PCs. The last computer I built, I recall that it was becoming more difficult to find a motherboard with certain legacy features I still use. So the network card seemed like a good deal and a good plan.

Zeroconf has been so successful, that all network printers made today seem to  ship with Zeroconf. But sadly, this printer and network card predate Zeroconf. Since I setup the printer with a static IP address, I know how to configure everything, but I often forget IP addresses and if I have a guest computer that wants to print, it’s a big waste of time to try to setup the printer on the new machine. So I would really like Zeroconf.

So my solution is to use another machine to advertise the printing service on the printer’s behalf. Zeroconf does not require that the same device be the advertiser. (It’s just usually more convenient to do it that way.) In fact, you can have multiple advertisements by different devices advertise the same exact service as long as the entries don’t conflict.

So the first step is to figure out how to advertise the service. My first instinct was to look at the utility /usr/bin/dns-sd on the Mac. It is a test tool that can be used to advertise services. A quick glance at the man page shows that I can advertise a printer service like so:

dns-sd -R "My Test" _printer._tcp. . 515 pdl=application/postscript

But it doesn’t let me specify an IP address and it’s assumed the service originates on the machine I ran this on.

So I went looking through the Bonjour APIs on my Mac. (Note: There are other Zeroconf implementations with their own APIs too. The Zeroconf implementations are compatible with each other, but the APIs to build code tend to differ.) Apple actually has 3 APIs on the platform, one in Cocoa, one in CoreFoundation, and one at the BSD sockets level. Though I spend a lot of time in Cocoa, for sockets, I’m slightly more familiar with the latter. Furthermore, I like porting my code to other systems when it makes sense and the BSD socket APIs be used more easily on other platforms.

Being slightly more familiar with the latter level, DNSServiceRegister seems to be the place to start to advertise a service. Unfortunately, it doesn’t look like DNSServiceRegister is sufficient because there doesn’t seem to be a way to specify a different IP address. So something else is needed. I haven’t found any tutorials online for this API set, so I decided to download the source code since Apple open sourced their implementation (Apache License).

Looking at the source code for dns-sd, I discovered that the tool supported more options that were not in the man page. And yes, setting up a proxy advertisement was among them (use -P). D’oh. (Yes, I filed another bug report on the documentation (5597207).)

So for the curious, I believe the correct magic incarnation to set this up is:
DNSServiceCreateConnection
DNSServiceRegisterRecord
DNSServiceRegister

where DNSServiceRegisterRecord is the function that will let you setup an alternative IP address.

But since we have a perfectly good working tool in dns-sd, I felt lazy and decided I would just use that instead of writing my own.

So the next trick is to figure out just the right options for dns-sd. With some trial and error, I found this worked for me:

dns-sd -P "Lexmark Optra S 1625" _printer._tcp. local 515 lexmark.local 10.1.1.213 product=(Lexmark Optra S 1625) pdl=application/postscript,application/vnd. hp-PCLXL Color=F Collate=T Staple=F

The string “Lexmark Optra S 1625” is the human readable name that you will see in a browser. When you go to System Preferences and try to add a new printer, that’s the name you will see in the list of Bonjour discovered printers.

_printer._tcp is the service type you are advertising. You can find a short list here: http://developer.apple.com/qa/qa2001/qa1312.html

There are two printer types, lpd/lpr (which is _printer._tcp), and the Internet Printing Protocol or ipp (_ipp._tcp). My printer also predates ipp, so it must be lpd for me. lpd normally runs on port 515.

lexmark.local is the short name. This is the name that you can type into standard tools that use DNS and it will find the printer. For example, if the printer were running a standard telnet service, then I could do:
telnet lexmark.local
instead of
telnet 10.1.1.213

The next part is the IP address the service actually runs on. 

The last part is the TXT record. This gets into areas I’m not well versed in, but in this context, you might simply think of it as a way to advertise metadata for your service. In fact, Apple seems to have something called the Bonjour Printing Specification that standardizes certain keys and values for printers. 

The two most important are:
product=(Lexmark Optra S 1625)
pdl=application/postscript,application/vnd. hp-PCLXL

Apple’s Printer Setup utility in System Preferences seems to use the product string to try to automatically pick the printer driver for you. In Leopard, from my previous blog, you know that the driver is missing. But in Tiger, using this string, the utility correctly finds the correct driver automatically.

The second string seems to act as a fallback if the product’s driver is not found. The ‘pdl=application/postscript’ is the most important part for Macs. Printer Setup sees this and decides to pick generic PostScript as the driver since it could not find a proper Lexmark driver. My printer also understands PCLXL so I added that part too, but in my experiments, Apple doesn’t seem to care about it. In theory, some other printer setup utility might care though.

So running this on the command line advertises the printer. Be warned though, I found another Apple bug that seems to suppress this entry from the printer list if you are on the same machine that is advertising. If you start and stop the service several times, sometimes you see a slight flicker of an entry in the table that immediately disappears. I’m speculating that somebody incorrectly removed the entry from the list because they assumed that maybe you already had it setup on your machine and you were “Sharing” it which means you might get a duplicate entry? I don’t know. It doesn’t make a whole lot of sense to me. Just make sure you use two different machines to test this. (Yes, I filed a bug on this too (5597164).)

Tiger_printer
(Above: Tiger found Lexmark Optra S xxx5 Series Driver)



(Below: Leopard finding no proper Lexmark driver falls back to Generic PostScript)
pastedgraphic_textmedium


So now that we can advertise the printer, it would be nice to automatically advertise this all the time. I don’t want to have to remember to manually run this and leave a terminal window open all the time just for this. There are a lot of different ways to do this. But on Mac OS X, launchd is where it’s at. Launchd will automatically launch the process for us at boot, and it will automatically daemonize the utility for us so we don’t have to change any of the code.

Finding a good launchd tutorial seems to be even harder than finding a good Zeroconf tutorial. But it’s not too bad after you figure it out. Launchd breaks things up into two catagories, Daemons and Agents. I think we are a Daemon in this case since we always want it running in the background.

On OS X, launchd uses scripts in
/System/Library/LaunchDaemons (for Apple only)
/Library/LaunchDaemons

In this case, I don’t think there is a per-user ~/Library/LaunchDaemons (though there is one for LaunchAgents). I think the issue is that it may not make sense to launch all user daemons if they are not logged in, while at the same time, the user might expect their daemons to be launched regardless.

So we need to create a plist to place in /Library/LaunchDaemons that launches dns-sd for us. The plist we need is pretty simple. The longest part is describing all the parameters for dns-sd.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.lexmarkoptras1625.bonjour</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/dns-sd</string>
<string>-P</string>
<string>Lexmark Optra S 1625</string>
<string>_printer._tcp.</string>
<string>local</string>
<string>515</string>
<string>lexmark.local</string>
<string> 10.1.1.213 </string>
<string>product=(Lexmark Optra S 1625)</string>
<string>pdl=application/postscript,application/vnd.hp-PCLXL</string>
<string>Color=F</string>
<string>Collate=T</string>
<string>Staple=F</string>
</array>
<key>LowPriorityIO</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>


The “Label” I just made up. I made it the same as the filename I saved this to.

I added “LowPriorityIO” for good measure, though dns-sd doesn’t take much to begin with.

“RunAtLoad” I found important. I was having problems getting this to actually to start dns-sd on a non-Leopard machine. I’m speculating that launchd was waiting for something to trigger this. On Leopard, it started immediately for me without this key. But for robustness, I added this key and everything worked as expected.

“KeepAlive” is a new Leopard key. I don’t think we need this since dns-sd won’t quit. But if it crashes, I think this will make sure launchd relaunches it. Perhaps the “NetworkState” key, which controls behavior when the network goes up and down, is something that should be considered as well, but I haven’t played with this one.

After saving this file to /Library/LaunchDaemons, all you need to do is start it.

launchctl load /Library/LaunchDaemons/local.lexmarkoptras1625.bonjour
(You might need sudo.)

This should start up the service. Check your printer browser or do a 
ps aux | grep dns-sd
on the command line to see if you see the process with all the options.

If you want to temporarily unload the service from launchd, do
launchctl unload /Library/LaunchDaemons/local.lexmarkoptras1625.bonjour

If you want to permanently unload the service from launchd, add the -w switch
launchctl unload -w /Library/LaunchDaemons/local.lexmarkoptras1625.bonjour

The switch seems to add a “Disabled” key set to true directly to the file which I presume causes launchd to skip the file on the next boot.

So now we’re all done. We have a Zeroconf advertised printer. Now there are a few short-comings with this approach. First, if I turn off the computer doing the advertisement, then nobody will find the printer. One way around this, is to run the advertisement on multiple computers. As stated earlier, you can run multiple advertisements without a problem as long as they don’t conflict in what they are saying. 

A different solution is to find a machine that is always on and connected. While we’re at it, a low power machine might be nice too. An AppleTV would be a good candidate (assuming you do the ssh hack), since it is a relatively low power device that is always on and connected to the network and has both Bonjour and launchd installed. (You will probably need to build/install dns-sd, but that’s easy to do.)

Another problem is if I change the printer’s IP address, I need to remember to change the dns-sd parameters to match. For me, this is a manageable problem as I try not to change IP addresses too often, but if I were using DHCP, this could be a real pain.

In this case, a slightly better solution would be to have this integrated with the DHCP server. For most home users, this is your home router like the Airport Extreme Base Station or Linksys|Netgear|D-Link router. Sadly, unlike the printer industry, Zeroconf adoption on home routers seems non-existent sans Apple. (Though I know Ruckus Wireless is working on Zeroconf support for their stuff. When it comes to just wireless technology, they are tops and you should check them out if you need wireless to go through multiple walls and cut through signal interference of phones and microwave ovens while still getting enough signal quality to stream video.) 

I haven’t bought a new router in a while, so I may be outdated, but last time I checked they still made you read the instructions and type in a IP address into your web browser. I have a really flakey Netgear router that after a firmware update did some kind of name resolution and automatic redirection. The problem with this is that I needed to change the Netgear’s IP address to work with my existing network with static IPs. Their name resolution/redirect was hard coded to the original address so you couldn’t access the router setup after the change without knowing a backdoor URL that doesn’t redirect. For all the wasted effort they spent implementing that, they could have just rolled out Zeroconf instead.

But I digress. As I was saying, it would be neat to see an option to advertise services from the routers. Like how most routers have menus for things like port forwarding/triggering, an additional menu for Zeroconf advertising could be added. Maybe there could be some presets for common services such as printing and you use a series of pop-up buttons to select things. The menu should allow the user to specify MAC addresses so in the case the router is running a DHCP server, it can identify the device and map the correct services to IP addresses to be advertised. IP addresses should also be allowed in the case where you are using static IP addresses on the network.

Apple should consider this for their Airport’s, but I suspect they would probably leave it out because in their world, everybody already does Zeroconf and there is no need for this kind of thing. Too bad that’s not true...yet.

Copyright © PlayControl Software, LLC / Eric Wing