Adventures with DD-WRT
Part 5: Installing Optware & Avahi (Zeroconf)


42px-Avahi-logo.svg

Last time, I laid out my motivations of why I want Zeroconf on the router. Now let's actually do it. I will use Avahi because there are prebuilt packages available for it.

I actually tried two different ways, but the first way didn't work.

With the first way, I noticed there was a direct Avahi package available from the OpenWRT and I was under the impression from the documentation that DD-WRT had support for OpenWRT packages. Unfortunately when I actually tried starting the Avahi-daemon, the process failed to daemonize and would abort. I could not figure out why this happened. So I went to Optware.


So in DD-WRT, there is a significant amount of documentation devoted to another packaging system called Optware. It looks like it belongs to yet another Linux distribution and has been co-opted by these home router firmware distros like DD-WRT and Tomato. But in retrospect, based on how comparatively large and bloated the packages are with respect to the amount of ROM available on these typical routers, I think Optware targets slightly beefier systems. In that, I am rather disappointed because it turns out that it would be impossible to fit Optware on the remaining amount of space I have in JFFS. External storage, thus USB, is an absolute must for a device like mine. (I am somewhat relieved I didn't buy the Linksys WRT54GS.)

[[RAW HTML]]

So the documentation on DD-WRT/Optware is not quite what I hoped. Basically there is an old document that you are discouraged from reading. And there is another document called "Optware, the Right Way". The thing I don't like about the latter document is that it only tells you how to get the packages they want on your system. They have a prewritten script that grabs and installs nearly 20 different services and takes 10-20 minutes to download. They also install a huge amount of stuff, most of which I am not interested in which basically makes their approach bloatware to me. And none of the packages are Avahi or the ones I personally designated as must-haves. And though I haven't tried it, the documentation doesn't give any sense that their approach is generalizable to the packages I want to install.


So I am going to not follow this document. Thus, I will probably be doing "Optware, the Wrong Way". So corrections are welcome.


I pretty much follow their old standard guide found here. I already more or less described "Option B: Preparing USB drive for /opt" in my earlier sections, so we jump right to Installing ipkg-opt and uclibc-opt.

You are going to need to telnet or ssh into the router.


To grab a magical Optware install script, run:

wget http://www.3iii.dk/linux/optware/optware-install-ddwrt.sh -O - | tr -d '\r' > /tmp/optware-install.sh

then run it:

sh /tmp/optware-install.sh


After this, I found that the rest of the documentation I didn't need to follow.


However, I do make one change. Because I am worried about flash drive wear, I do not want the /opt/var directory to be on my flash drive. So instead, I create a symlink to /tmp on the router. This directory happens to be completely in RAM so there is no flash to wear out. But note that when the device reboots, everything in that directory is lost. But typically things in /var aren't critical to keep around. So I don't care if I lose this data.


So if /opt/var exists, remove it:

rmdir /opt/var

(If that fails because something is inside it, you can do rm -rf /opt/var)

Then I just create the symlink:

ln -s /tmp/var /opt/var


Installing Avahi

Now we are ready to install Avahi using the Optware tools. To do this, we can use the ipkg-opt tool, specifying the avahi package that is in Optware. The tool will automatically install any dependencies Avahi relies on.

/opt/bin/ipkg-opt --tmp-dir /tmp install avahi

The full console output looked like the following for me:

root@AsusWireless:/# /opt/bin/ipkg-opt --tmp-dir /tmp install avahi Installing avahi (0.6.24-2) to /opt/... Downloading http://ipkg.nslu2-linux.org/feeds/optware/ddwrt/cross/stable/avahi_0.6.24-2_mipsel.ipk Installing expat (2.0.1-1) to /opt/... Downloading http://ipkg.nslu2-linux.org/feeds/optware/ddwrt/cross/stable/expat_2.0.1-1_mipsel.ipk Installing libdaemon (0.13-1) to /opt/... Downloading http://ipkg.nslu2-linux.org/feeds/optware/ddwrt/cross/stable/libdaemon_0.13-1_mipsel.ipk Installing dbus (1.2.16-1) to /opt/... Downloading http://ipkg.nslu2-linux.org/feeds/optware/ddwrt/cross/stable/dbus_1.2.16-1_mipsel.ipk Installing adduser (1.10.3-1) to /opt/... Downloading http://ipkg.nslu2-linux.org/feeds/optware/ddwrt/cross/stable/adduser_1.10.3-1_mipsel.ipk Configuring adduser update-alternatives: Linking //opt/bin/su to /opt/bin/adduser-su Configuring avahi Configuring dbus No messagebus user found, creating it... done Configuring expat Configuring libdaemon Successfully terminated.

So the package installed, but there are some problems. The first problem is that the dbus package is misbuilt which causes the dbus daemon to seg fault on start. Avahi requires this service to work, so this won't do. Fortunately, somebody posted a fixed version found in this thread here.

The poster says the the -PIE flag must be specified to build dbus correctly.

The file name is: dbus_1.2.16-2_mipsel.ipk.zip

I ended up downloading it to my desktop so I could unzip it first:

unzip dbus_1.2.16-2_mipsel.ipk.zip


And then transferring it via ssh to the router (make sure ssh is enabled in your router's settings)

scp dbus_1.2.16-2_mipsel.ipk root@192.168.1.1:/opt


I originally tried force overwriting the package with the new one, but it turns out that it didn't work because it kept crashing. I think the command silently failed. I spent many hours on this where only in a final last ditch effort I completely removed the package and then reinstalled it which did work.

So to remove the package:

/opt/bin/ipkg-opt remove -force-depends dbus

Then to install:

/opt/bin/ipkg-opt install /opt/dbus_1.2.14-2_mipsel.ipk


Now there additional minor problems. Optware fails to give you an init.d startup style script. Optware doesn't set the required user and group accounts needed by dbus and Avahi.


So I figured all this out by trial and error. I might be wrong, but this seems to work.


If you recall several parts ago, I had a startup script in /jffs/etc/config. We are going to continue using it.


The script is named start_optware_drive.startup

And last time I mentioned I had the line:

mount -f -r -o noatime,remount -t ext3 /dev/scsi/host0/bus0/target0/lun0/part1 /opt

(Note for debugging/development, you may want to remove the -r (readonly) switch.)


So next we are going to add the correct users and groups. Optware added the tools adduser and addgroup so we could use those, but I found they weren't giving the exact style of entry I wanted even with all the switches, so I do it manually. Instead I just direct add entries to the /tmp/etc/group and /tmp/etc/passwd  which seem to be mapped to /etc/group and /etc/passwd. Since /etc is kind of a read-only situation, we modify /tmp/etc. Notice these changes get wiped every reboot, which is why we put this in the startup script to be recreated every time. 

echo "netdev:x:1:" >> /tmp/etc/group echo "avahi:x:2:" >> /tmp/etc/group echo "avahi:x:2:2:avahi daemon:/opt/sbin/avahi-daemon:/bin/false" >> /tmp/etc/passwd


Finally, I am going to add a little loop to start all scripts that start with S in /opt/etc/init.d (which is a common startup convention you see in many Linux distros...sorry no Launchd here yet).

for f in /opt/etc/init.d/S* ; do [ -x $f ] && $f start done


If you look in /opt/etc/init.d, you will see Optware added a script called S20dbus. This starts the dbus service. But inexplicably, there is no script to start Avahi. Avahi needs to start after dbus, so we will write our own script called S21avahi-daemon.

#!/bin/sh EXE=avahi-daemon BIN=/opt/sbin/$EXE OPTIONS="-D" RUN_D=/var/run/$EXE case $1 in  start)   mkdir -p $RUN_D   $BIN $OPTIONS   ;;  stop)   $BIN -k   ;;  reload)   if [ -f $RUN_D/pid ]; then    $BIN -r;   else    mkdir -p $RUN_D;    $BIN $OPTIONS;   fi   ;;  restart)   if [ -f $RUN_D/pid ]; then  $BIN -k   fi   mkdir -p $RUN_D   $BIN $OPTIONS   ;;  *)   echo "usage: $0 (start|stop|reload|restart)"   exit 1 esac exit $?

Most of the complexity in the script is just to detect whether Avahi is currently running or not and then do the right thing. We look for a process id number that is written in /var/run to detect the state and act accordingly. The difference between reload and restart is that reload uses the Avahi built-in mechanism to reload while restart will kill and start the process explicitly. I found that reload didn't always work for me which is why I added restart.

If you are wondering about the path /var/run, recall we symlinked /opt/var/run to go to /tmp/var/run. And notice that /tmp/var/run and /var/run are aliases of each other like /tmp/etc and /etc. 


Avahi Runaway CPU Load Problem

So at this point, if you were to start the services, Avahi would work. (Yea!) But I discovered one more annoying problem. If you reboot the router, when Avahi comes up, Avahi sucks up most of the CPU power of the router. The CPU load seemed to float around 79%-96% for me. There is something wrong. If you kill it and start it up again (restart), then CPU usage is normal (about 0%).

After spending many hours debugging this, I discovered that starting Avahi in the startup script would always lead to the runaway CPU usage. But if I started it elsewhere, the CPU usage would be normal. This remained true even if I placed a long pause (sleep) in the startup script before starting Avahi. I also tried killing Avahi and restarting it in the startup script with very long pauses in between and even spawning these things in background processes so the startup script would technically end before Avahi started.

I initially speculated it had something to do with the router's firewall being brought up after Avahi started causing some kind of problem, but my sleep and backgrounding experiments make me rethink that hypothesis.

So giving up on finding the reason, I settled for a workaround. DD-WRT also has rules about running scripts when different events happen. I utilize .wanup scripts which are run when the WAN port is up and after the firewall is set. In my script, I kill and restart the Avahi process. There is a downside that .wanup scripts may be invoked many times so the Avahi process may be getting killed and restarted needlessly, but I probably won't notice this.

I continue to leave the initial Avahi startup in the .startup script because I am worried about the case where the WAN port never goes up and .wanup is never run. Since the most critical time I may need Zeroconf is when I move and setup the device on a new network, I may not have anything plugged into the WAN port and I don't know if the WAN related scripts will be executed. I still want to be able to find the device. I'm willing to live with the runaway CPU usage in this case since it will be only temporary as I reconfigure the router.

So in /jffs/etc/config, I create a new file called restart_avahi.wanup and it contains:

#!/bin/sh

opt/etc/init.d/S21avahi-daemon restart


Make the file executable:

chmod +x /jffs/etc/config/restart_avahi.wanup


Note that I use restart instead of reload because using Avahi's built-in reload mechanism fails to resolve the CPU utilization problem. Only by killing the process and restarting it does the CPU utilization return to normal.


So now we have Zeroconf working on the router! We can now find this router by name. I called my router "AsusWireless" in my DD-WRT settings via the web-gui. So from any Zeroconf enabled machine, I can now connect to the router using the name "AsusWireless.local" (case-insensitive).

So in my web browser on my desktop, I can enter the address:

http://AsusWireless.local


For from my terminal, I can telnet or ssh to the device using the name, e.g.:

telnet AsusWireless.local

TelnetZeroconf


But we went to all this trouble to install Zeroconf/Avahi, so let's really take advantage of it. I should be able to browse for services so I don't need to remember what I named the router. Let's fix that next.


Setting Up Service Advertising with Avahi

Zeroconf allows services (not to be confused with devices) to be advertised so clients of those services can know about them. By services, I mean things like web servers, telnet services, ssh, ftp, printing, etc. Note that I wouldn't advertise by router box "AsusWireless", but I would advertise the web servicee (for the GUI) running on my router box. The distinction between services and the device is important difference. For example, my ftp client doesn't care that my box runs a web-gui. If my box isn't serving ftp, my ftp client shouldn't confuse me by showing AsusWireless in a list of devices that are available on the network. So it only really makes sense to advertise services.

The Avahi package should have installed some configuration files to /opt/etc/avahi. There is also a subdirectory in here called services with a couple of example scripts. I believe ssh and sftp are the two scripts.


The ssh one is fine for me so I will leave it alone, but we don't have sftp on this router. I could delete it, but instead I will just hijack it as a template for a different service. I will rename that script to telnet.service and modify it to look like the following:

<service-group>   <name replace-wildcards="yes">DD-WRT Telnet on %h</name>   <service>     <type>_telnet._tcp</type>     <port>23</port>   </service> </service-group>


Changing the type field to telnet will allow telnet clients to find it. The standard telnet port is 23 which is what the router is using. You can spruce up the name field to be more descriptive. See the following screenshot from the Mac OS X Terminal telnet browser. (You will need to restart Avahi before the changes take effect.)

TelnetBrowser


Next, I will create another file for the Web GUI: webgui.service.

It is the same idea as before. Note that if I had https support in my firmware and I was running it, I could also advertise the https version too. But unfortunately, the DD-WRT Mini firmware doesn't come with https support, so I comment out that section below:

<service-group>   <name replace-wildcards="yes">DD-WRT Control Panel on %h</name>   <service>     <type>_http._tcp</type>     <port>80</port>   </service> <!-- not available in mini firmware --> <!--   <service>     <type>_https._tcp</type>     <port>443</port>   </service> --> </service-group>

SafariBonjour

But let's not stop here.


Advertising Services on Behalf of Other Devices

A long time ago, I wrote an entry entitled Advertising a legacy network printer with Bonjour (with a little help from launchd). It's time to revisit that.

In that article, I had an old network printer that predated Zeroconf that I wanted advertised. I dedicated a system already running Bonjour to advertise the printer on its behalf since it couldn't do that itself. That way any visitors that use my network can easily find the printer and say, for example, print out the boarding pass for their airplane flight to take with them.

Well, most machines I run Bonjour on are considerably more expensive and consume a lot more power than this router. Since this router always needs to be on for me, I consider this a terrific device to move those advertising duties to. And now that we are this deep into setting up Avahi on this router, this will be a trivial change.

To get started, I just create a new file which I call Lexmark.service to contain the information for my Lexmark printer.

<service-group>   <name replace-wildcards="yes">Lexmark Optra S 1625 Laser Printer</name>   <service>     <type>_printer._tcp</type>     <host-name>Lexmark.local</host-name>     <port>515</port>     <txt-record>product=(Lexmark Optra S 1625 Laser Printer)</txt-record>     <txt-record>pdl=application/postscript,application/vnd.hp-PCLXL</txt-record>     <txt-record>Color=F</txt-record>     <txt-record>Collate=T</txt-record>     <txt-record>Staple=F</txt-record>   </service> </service-group>


Notice that I set the host-name field to Lexmark.local. So how does Avahi know about the name Lexmark.local and the IP address behind it? Well, it doesn't...yet. There is one more configuration file we need to change. In /opt/etc/avahi, there is a file called hosts.

This file lets you map a static IP address to a hostname of your choosing for Zeroconf purposes. So I add this line to the hosts file:

192.168.0.115 Lexmark.local

Now Avahi knows that Lexmark.local maps to that IP address. If you restart Avahi, everything should now just work.


BonjourPrinter


Now I just remembered that somebody once read my article on using Bonjour to advertise my Lexmark printer. They had the same printer and it inspired them to do the same using Avahi. Peter Ortner wrote up his findings here. He has a much more elaborate Avahi services file. So I encourage you to read his writeup.


For those of you that are connecting a printer through the router's USB port, you should be able to apply this general idea and technique so you can advertise your USB printer. I haven't tried this, but you probably won't need to touch the hosts file and can just get away with using the router's hostname.


Finally, you may have noticed from an earlier screenshot, that there is a web service for RuckusWireless being advertised. Alas, I wish it were true that the device was advertising itself, but the Ruckus engineers fell short here. So I decided to advertise it like I did my printer. This won't help me in the case where I move the Ruckus off my network, but since I had to change its default IP address to co-exist on my current network, I never remember what IP address I assigned, so Zeroconf is still helpful to me in finding it while attached to my current network.

So in /opt/etc/avahi/hosts, I add the entry:

192.168.0.220 RuckusWireless.local


And I add the file /opt/etc/avahi/services/RuckusWireless.service containing:

<service-group>   <name replace-wildcards="yes">RuckusWirless</name>   <service>     <type>_http._tcp</type>     <host-name>RuckusWireless.local</host-name>     <port>80</port>   </service>   <service>     <type>_https._tcp</type>     <host-name>RuckusWireless.local</host-name>     <port>443</port>   </service> </service-group>

(They do have https, so I include it here.)

So now reboot the router or run:

/opt/etc/init.d/S21avahi-daemon restart


Avahi should now be advertising all the services we just added. That was a lot of stuff to do, but it now all works!


Thoughts & Future Directions:

Despite getting this all working, I am slightly disappointed at how much disk space Avahi needs due to all the dependencies brought in via Optware. Optware is big to begin with. Then on top of that, dbus is surprisingly big. I am wondering if Avahi was built directly against the built-in DD-WRT libraries, and if the dbus dependency could be eliminated or at least shrunk, then would the resulting footprint be significantly smaller. I would like to see this shrunk enough such that all firmware distributions have no excuse to exclude Zeroconf as a built-in feature.

For those who might think that Zeroconf must be big, I will point out a device called SitePlayer which is a serial port to ethernet adaptor. SitePlayer once boasted the world's smallest Zeroconf implementation at around 800 bytes. Granted that it's probably been stripped of some of the features I demonstrated like XML configuration files to advertise services on other devices, but I think this shows that the size need not be bloated.

Another alternative would be to try compiling Apple's Bonjour implementation for DD-WRT. Apple supposedly supports other platforms besides OS X and Windows. I have successfully compiled and run Bonjour on a desktop Linux and Sun Solaris. It would be interesting to see if Bonjour is more streamlined.

In addition, I am interested in seeing Bonjour on the router because of a new feature they introduced called Bonjour Sleep Proxy. Basically the idea is that if a service (e.g. ssh, printing, web server, etc) is on a computer/device that is put to sleep, a sleep proxy will continue to advertise those services on its behalf. When somebody tries to use the service, the sleep proxy will wake up the computer/device and allow things to continue as if the computer/device had never been asleep. 

This would be a wonderful thing to have on a router such as this. It is low power and always on, so it makes a brilliant candidate to be a sleep proxy server. So far, I haven't found anything about the Avahi people or any other projects implementing this themselves.

I probably won't be doing any of these things in the foreseeable future. (It took me 3 years of thinking about it, before I actually tried DD-WRT.) So I hope some of you try it and document your efforts.


Next time we will look at improving UPnP support and adding NAT-PMP support on our DD-WRT router.

Links:

Part 4: Zeroconf should be available on all network devices, including routers (previous)

Part 6: UPnP and NAT-PMP (next)


Copyright © PlayControl Software, LLC / Eric Wing