Customizing the Asus WL700gE NAS Router



Forward

This page is *very* old and outdated! If you're looking for the latest custom firmware for the WL700gE Asus router, then look here:
http://wl700g.homelinux.net/drupal/?q=node/134

Introduction

The WL700gE is an all in one NAS, wireless and wired router.   My primary objective for this unit was to replace my always-on, power hungry, noisy file / print / http / e-mail server and firewall.  I knew from the beginning that tweaking the firmware and installing additional packages would be necessary.  I also wanted the internal HDD to spin down when not in use.  With the help of a developing hacker community at http://www.wl700g.info, I've made some very good progress costomizing my router.

For those that don't care about the details of what I've done and just want an image to reflash, jump down to the bottom of the page.  Don't forget to add an rc.local script, and create a "/shares/MYVOLUME1/opt/etc" directory or you won't notice any difference!

Rebuilding the firmware

Rebuilding the firmware was relatively straight forward.  The hardest part was assembling all of the items. 

I started with an default installation of Ubuntu linux running under a VMWare virtual machine.  I had no particular reason for picking Ubuntu other than it is quick and easy to install.  You can download both Ubuntu and VMWare Server for free.

You'll have to add a number of packages to the default Ubuntu installation: gcc-4.0, binutils, make, rcs, and libc6-dev.

Tweaking the firmware

Note: For brevity, all paths listed below are relative to ./wl700g/nasoc/src/apps, henceforth referred to as $(apps).

Step #1, Building the original distribution

For a concise overview, please read this HOW-TO posting.  I've added a few additional comments below.
  1. The source can be found here at Asus. This is a monster 400Meg+ zip file.
  2. Regarding step #5 in the HOW-TO posting, I think it's easier to symlink mkcramfs to mkfs.cramfs than to edit all of the makefiles.
  3. Don't run the build as root, otherwise ez-ipupdate will try to install binaries onto your system you'd probably rather not have.
  4. In $(apps)/ez-ipupdate, modify the "DESTDIR=" line in the Makefile with "DESTDIR=$(INSTALLDIR)".  This will keep ez-ipupdate debug host binaries constrained within the build tree and allow a normal user to perform the build.
If your build succeeds, proceed to the following steps.

Step #2, Adding support for executing a custom startup script (rc.local)

The default firmware executes a shell script called /apps/bin/rcex at both startup and shutdown.  Quite a number of rcex files are included in the firmware distribution.  The one that matters is actually in $(apps)/tarfiles/exinstall.tar.gz.  To modify the rcex file:
  1. cd to $(apps)/tarfiles/exinstall.tar.gz
  2. Untar the exinstall.tar.gz file (tar -xvzf exinstall.tar.gz)
  3. cd to ./exinstall/apps/bin
  4. Modify rcex as necessary.  I added the following to the bottom of mine:
    # Run rc.local.  Prefer the one out of reach by the file
    # sharing applications.  If not available, look in the web
    # folder on the internal HDD.

    if [ -x /shares/${pool}/rc.local ]; then
     /shares/${pool}/rc.local $1 &
    else
     if [ -e /shares/${pool}/${share}/web/rc.local ]; then
      /bin/sh /shares/${pool}/${share}/web/rc.local $1 &
     else
      echo Found no rc.local to run!
     fi
    fi


  5. cd back to $(apps)/tarfiles
  6. Recreate the tarball (tar -cvzf exinstall.tar.gz exinstall)
Notice the ${pool} and ${share} macros.  On most systems these expand out to MYVOLUME1 and MYSHARE1 respectively.  However, it appears these locations may not be fixed so I followed the conventions found in other scripts and source code and used the values stored in nvram.

Here's a link to my final $(apps)/tarfiles/exinstall/apps/bin/rcex script containing the items described in step #1.

Step #3, Adding additional support files to /etc

Like the rcex file, there are a number of etc directories sprinkled throughout the build tree.  Also like the rcex file, none of them actually matter!  Turns out /etc is built by the $(apps)/misc/rootprep.sh shell script so any changes you want reflected on your target will have to be added to this script.

The mount options of the filesystems on the internal HDD need to be changed to allow the HDD to spin down.  To make remounting easier, it is helpful to have a functional /etc/fstab on the router.  Additionally, acquiring shell access via ssh or telnet requires a functional /etc/shells.  Other files like /etc/passwd, are already configured by the default Asus script. 

I added the following lines to by rootprep.sh build script to point /etc/fstab and /etc/shells into /tmp:

ln -sf /tmp/fstab etc/fstab
ln -sf /tmp/shells etc/shells

Since /tmp is a ramfs, files or symlinks can be written at at any time without reflashing the router.  I decided to symlink the /tmp/fstab and /tmp/shells files to the real files (which permanently live on the HDD in /opt/etc) at boot-time via rc.local.

Step #4, Paving the way for ipkg packages

After trying many packages from the various mipsel package repositories, I finally settled on the "optware" distribution.  Optware packages are explicitly configured to live entirely within /opt (hence the name optware) which results in minimal interference with, and dependence on, the default Asus firmware. 

Within the optware collection, I chose the "oleg" group of packages because it has pre-packaged uClibc and ipkg packages.  The ddwrt collection has nearly the same content and would probably work just as well (though I haven't tried).

Since /opt must be mounted from the HDD,  I need to create a mount point for it in the root filesystem on the router.  To do this, I added the following line to the same rootprep.sh script as described in Step #3:

mkdir -p opt

Here's a link to my final $(apps)/misc/rootprep.sh script containing the items described in steps #3 and #4.

Additionally, the Asus busybox version of "wget" had to be modified slightly due to chunked HTTP transfer encoded downloads aborting early.  We need a working wget to initially download .ipk packages.  To work around the problem, simply replace the "1.1" part of the HTTP request with "1.0" in $(apps)/busybox/networking/wget.c.  Servers will not perform chunked HTTP transfers to 1.0 clients.

Step #5, Fixing the entropy problem

Dropbear (SSH) requires a good source of randomness.  The traditional source for this randomness is /dev/random.  To make /dev/random more secure, the random number generator gathers randomness (otherwise known as entropy) from select kernel device drivers that are highly influenced by their natural, and inherently random, surroundings (i.e. keyboards, HDD transfer times, network interrupt timing, etc.). 

When /dev/random runs out of entropy, it blocks until more becomes available.  The net effect to the end user (me) is the inability to ssh into my router or generate the necessary keys to configure my ssh server.  An alternatate device, /dev/urandom, does not have this blocking behavior when the entropy pool runs out.  It simply runs in a "degraded" mode which is more than adequate for my ssh login needs.

My testing indicates that the router only gathers entropy from the HDD.  Since my HDD spends most of its time spun down, the little entropy it generates depletes quickly. 

The usual solution for entropy starved devices is to delete /dev/random and symlink /dev/random to /dev/urandom.  Unfortunately, the WL700gE has been built with devfs support which means we cannot do this.  To work around the problem, I replaced both instances of the &random_fops struct pointers with &urandom_fops struct pointers in ./wl700g/nasoc/src/linux/linux/drivers/char/mem.c.

If you know of a more elegant solution to this problem, that doesn't require any kernel hacking, please let me know.

Step #6, enabling login support

The default busybox build does not have login enabled.  Login is needed fore more secure telnet access.  Enabling it was simple:
    1. Disable the config rewrite nonsense in $(apps)/Makefile by commenting out the line immediately under the "busybox:" target label.
    2. Enable login by editing $(apps)/busybox/.config and changing "# CONFIG_LOGIN is not set" line to "CONFIG_LOGIN=y"
    3. Keep the defaults when the busybox config script queries for them.
Utelnetd will automagically detect the presence of /bin/login and and use it.

Step #7, Building  a new binary

Repeat step #1, and flash $(apps)/mipsel/WL700gE_1.0.4.6.nas into your router (see below).

Installing the new firmware image

Step #1, Flashing the new firmware

Flashing the firmware is pretty easy.  I followed the flashing HOW-TO on the wl700g.info forum.  These directions are very good.  Much better than anything provided by Asus. 

In addition to the HOW-TO, I've made the following observations:
  1. Steps 2,3,4: I've found that simply powering down the box, holding the ez-setup button on the back, and powering back on is sufficient under most circumstances to get the box into a flashable state.  Be sure to let go of the ez-setup button as soon as the ready light turns on.  After that, you have about 30 seconds before the router times out and boots normally.
  2. Step 8,9,10: The firmware transfer for me was *much* faster than 1 minute.  It only took about 5 seconds to transfer on 100Mbit wired ethernet and 10 seconds to complete flashing and rebuilding the HDD filesystem. The router automatically reboots once the update is complete.
Step #2, Loading rc.local

To customize the router startup, an rc.local file *MUST* be put onto the router.  From the factory default state, with no telnet access, adding rc.local takes two steps.  The first step allows the script to be executed from a location where it's easy to drop it onto the router.  Unfortunately, this location is rather insecure.  The second step secures the script from the outside world by relocating rc.local to an internal location not accessable via standard file sharing.
    1. Grab the starter rc.local script.  The script must have proper unix-style line terminators to execute on the router so don't edit it with braindead windoze editors like notepad.
    2. Copy rc.local into /shares/MYVOLUME1/MYSHARE1/web using one of the many file transfer methods.  I've tested the ftp, cifs (samba), and nfs interfaces. 
    3. Reboot your router.  You now have unsecured telnet access from the local LAN interface.
    4. Telnet to your router.  Move rc.local from /shares/MYVOLUME1/MYSHARE1/web to /shares/MYVOLUME1, and change its permissions to 755. (cd /shares/MYVOLUME1; mv MYSHARE1/web/rc.local .; chmod 755 rc.local).
    5. Step #4 helps close the security hole opened by step #2.  The wide-open telnet access is still a problem, but at least it's constrained to the internal LAN interface.  This hole is easily closed after installing some optware packages.
For those of you who installed my previous firmware image, please note rc.local has moved from /shares/MYVOLUME1/MYSHARE1 to /shares/MYVOLUME1.  If you don't move your existing rc.local to the new location prior to reflashing, you won't be able to get back into your router without starting from step #1!

Step #3, Installing optware packages
    1. Telnet to your router and login as root.
    2. Create and manually mount /shares/MYVOLUME1/opt on /opt (rc.local will do this automatically once /shares/MYVOLUME1/opt is created)
    3. Manually install uClibc, busybox, wget, and ipkg from the optware repository:
    4. Add /opt/bin to the beginning your path (to override Asus binaries of the same name)
    5. Add the following line to /opt/etc/ipkg.conf with the newly installed busybox version of vi: "src optware http://ipkg.nslu2-linux.org/feeds/optware/oleg/cross/stable".  Here's a copy of mine.
    6. Run "ipkg update"
    7. For completeness, re-run "ipkg install" for uclibc, busybox, ipkg, and wget to fully install them.  You may have to use the -force-overwrite flag.
    8. You can now add packages as necessary and start them via rc.local

Step #4, Closing the telnet hole

With the default config, root access via telnet will be wide open with no password protection.  Any user accounts added by the web interface will be password protected with /bin/sh as their default shell.  Either manually edit /etc/passwd and use an existing user account password as the root password, or install dropbear and disable telnet altogether (which is what I did).

Step #5, Making a better system
    1. Install the bash package for a better shell
    2. Install the sudo package so you don't have to login as root to perform administrative functions (you can't login as root via ssh anyway)
    3. Give yourself supreme permissions in /opt/etc/sudoers
    4. Add /opt/bin/bash to /etc/shells (echo "/opt/bin/bash" >> /etc/shells)
    5. Add a non-root user account via the web interface
    6. Give yourself a home directory (mkdir -p /opt/home/<username>; chown <username> /opt/home/<username>
    7. Modify /etc/passed to make /opt/bin/bash your login shell and /opt/home/<username> your home directory (vi /etc/passwd).  See here for a non-functional sample.
    8. Optionally symlink your user account directory into /shares/MYVOLUME1/MYSHARE1 if you want it accessable via cifs (Samba).  This trick won't work for ftp, nfs or the web interface.  You'll have to make your home directory in /shares/MYVOLUME1/MYSHARE1 if you want full access from all methods. (cd /shares/MYVOLUME1/MYSHARE1; ln -s /opt/home/<username>)
    9. Within your .profile, add /opt/bin and /opt/lib to your PATH and LD_LIBRARY_PATH environment variables
    10. Install dropbear and start it (manually at first, then via rc.local), see the last rc.local file below.
    11. Disable telnet by removing it from rc.local to completely eliminate any chance of root logins
    12. Install ncurses, ncursesw, and termcap packages for better terminal support. You may need to use the -force-overwrite option when installing ncurses.
    13. Install man and manpages packages

Example rc.local files

  1. This rc.local script starts an unprotected telnet daemon on the local interface, creates a functional fstab, and sets up the environment for installing and executing optware packages.
  2. In addition to #1, this rc.local script spins down the internal HDD.  You'll have to rename this file to rc.local and create a /shares/MYVOLUME1/opt/etc directory in order for the HDD spin-down to work properly (cd /shares/MYVOLUME1; mkdir -p opt/etc).
  3. In addition to #2, this rc.local script does a lot more (it's intended for demonstration only!  You certainly won't want to use it as-is.).  You'll have to rename this file to rc.local and it also requires /shares/MYVOLUME1/opt/etc being present.

Additional things on my to-do list (in order of importance)

  1. Figure out why I need to go to the status web page before samba print sharing works.  I know this page spawns /bin/printd, but manually starting printd by itself doesn't work.  I need to better understand what /bin/printd does and how it's supposed to be called so I can start it from rc.local.
  2. Figure out why ksoftirqd is consuming 100% of the CPU (and determine whether or not it is negatively impacting system performance)

Thoughts and other misc items

Binary images

This is my second binary image for the WL700gE containing the modifications described above.  It's a significant improvement over my first release.  This release is also based off of the 1.0.4.6 distribution provided by Asus.  I plan to eventually release a patchset against the original source from Asus.  However, for now, everything I changed is described above.

Due to server storage limitations, I can't provide older images so please keep copies if you want to go back...

Download the 18M file from http://home.comcast.net/~kfurge/WL700gE_1.0.4.6_kc_02a.nas.