With the release of the Linux sources for the Linksys WRT54G/GS series of routers came a number of modified firmwares to extend functionality in various ways. Each firmware was 99% stock sources and 1% added functionality, and each firmware attempted to cater to a certain market segment with what functionality they provided. The downsides were two fold, one - it was often difficult to find a firmware with the combination of functionality desired (leading to forks and yet more custom firmwares) and two - all the firmwares were based on the original Linksys sources which were far behind mainstream linux development.
OpenWrt takes a different route, instead of starting out with the Linksys sources, the development started with a clean slate. Piece by piece software was added to bring the functionality back to that of the stock firmware, using the most recent versions available. What makes OpenWrt really unique though is the fact it employs a writable filesystem so the firmware is nolonger a static compilation of software but can instead be dynamically adjusted to fit the particular needs of the situation. In short, the device is turned into a mini linux PC with OpenWrt acting as the distribution, complete with almost all traditional linux commands and a package management system for easily loading on extra software and features.
Everything is stored on a single flash chip. The basic flash layout is as follows -
[bootloader][firmware][nvram]There's no actual partition table as such, the names and locations are simply hardcoded.
When the device boots, it runs a bootloader, either PMON or CFE; both are identical as far as the user is concerned. It's the responsibility of this bootloader to perform basic system initialization along with validating and loading the actual firmware; think of it as the BIOS of the device before control is passed over to the opperating system. If the firmware fails to pass the CRC check, then the bootloader will presume that the firmware is corrupted and will wait for a new firmware to be uploaded via the network.
The nvram partition is used as a storage mechanism, in here you'll find a number of configuration values stored as 'name=value' pairs. There are acutally two nvram locations, the partition identified above, and a smaller copy embeded into the bootloader. The nvram embeded into the bootloader is used as a set of default values if the actual nvram partition can't be found.
The firmware itself starts immediately after the bootloader and is allowed to take all the space up until the start of nvram, although in practice it rarely does. As a result, there's often a few megabytes of unused space:
[bootloader][firmware][unused/jffs2][nvram]OpenWrt makes use of this otherwise unused space by converting it into a writable jffs2 formatted filesystem. The reason for not simply extending the filesystem within the firmware is an artifact of the filesystem being writable; if the filesystem were contained within the firmware then any changes to the filesystem would require the the firmware CRC to be updated or else the firmware would fail to pass the booloader's CRC check.
[trx header][kernel][filesystem]The trx header provides information on the basic structure of the firmware:
struct trx_header {
unsigned magic; /* "HDR0" */
unsigned len; /* Length of file including header */
unsigned crc32; /* 32-bit CRC from flag_version to end of file */
unsigned flag_version; /* 0:15 flags, 16:31 version */
unsigned offsets[3]; /* Offsets of partitions from start of header */
};
At the start of the trx is the signature "HDR0" to identify it as a trx, this is followed by information such as the length of the trx file itself, the CRC, flags for what features are supported and offsets for up to 3 partitions contained within the trx. (It's this CRC which is used to verify the firmware before reflashing and again by the bootloader to check for corruption.)
Although the firmware is stored in flash as a trx file, the firmware itself is generally downloaded/distributed as a bin file, which adds the following header before the trx data:
struct code_header {
char magic[4]; /* wrt54g: "W54G", wrt54gs: "W54S" */
char res1[4];
char fwdate[3];
char fwvern[3];
char id[4]; /* "UND0" */
char hw_ver; /* 4702 chips: 0, 4712 chips: 1 */
unsigned short flags;
unsigned char res2[10];
}; /* 32 bytes */
The purpose of the bin header is to provide the firmware version and identify the model the firmware was intended for. If the file begins with the string "W54G" then the firmware is intended for a on a wrt54g, if it starts with "W54S" then the firmware is intented for a wrt54gs. The bin header is automatically stripped off by the bootloader and the upgrade webpage in the linksys firmware; it is not stripped off automatically by OpenWrt's mtd commands.
| 4M FLASH/16M RAM | 8M FLASH/32M RAM | Revision notes |
|---|---|---|
| WRT54G v1.0 | - | Initial release, 125Mhz, AMD flash, removable minipci wireless card, PMON bootloader |
| WRT54G v1.1 | - | The flash chips where switched to intel, integrated wireless |
| WRT54G v2.0 | WRT54GS v1.0 | 200Mhz CPU, CFE bootloader, introduction of GS models |
| WRT54G v2.2 | WRT54GS v1.1 | Ethernet switch changed from ADM6996 to Broadcom Roboswitch |
| WRT54G v3.0 | WRT54GS v2.0 | Two additional leds and a new button |
The packaging doesn't always indicate the revision number; the best way to determine the revision is by looking at the bottom of the router. All linksys routers have a silver and black sticker with the linksys logo and the words "Model no." followed by the model name and optionally a version number. Models that don't have a version number are v1.0; the version number was ommited because at that time there was only one version.
Specifically, on bootup, the kernel boots using the squashfs filesystem, instead of following typical linux rules, the "init" application is not the first program loaded but rather the second; the first thing the system does is executes /etc/preinit from the squashfs partition. Preinit gets executed via the kernel's "init=/etc/preinit" commandline option, it's job is to switch the root from squashfs to jffs2, using pivot_root before finally passing control to init. The squashfs partition is moved from root to "/rom" and jffs2 takes over as the root filesystem in what can best be described as a "poor man's overlay filesystem"; each file on the jffs2 partition starts out as a symlink to the squashfs filesystem on /rom.
The reason for this preinit setup is that the preinit script becomes one of the only things that's executed directly from the squashfs filesystem; everything else, including init is executed from the jffs2 partition and can easily be modified to suit the needs of the system, providing the illusion of a fully writable system. The problem and at the same time one of the major advantages to the squashfs version is that the squahsfs filesystem itself isn't writable. To make a change to the filesystem you first need to delete the symlink to the /rom (squashfs) file from the now root jffs2 partition, and recreate the file. Nothing short of a reflash will change the file on the squashfs partition, so if you replace a 13k file, the squashfs copy of the file is still using up 13k of space and another 13k is used by the newly created file on the jffs2 root partition.
The advantage comes from the fact that because the squashfs partition can't be modified, there's always a pristine copy of the base filesystem. To explain this concept better, what would happen if the preinit script didn't perform the call to pivot_root and instead left the squashfs filesystem as the root? This is a condition we refer to as "failsafe mode", because it allows you to boot into the system and recover a corrupt jffs2 partition. Failsafe mode can be triggered by rebooting, waiting for the DMZ led on the front of the router to light and then pressing and holding the reset button for just over a second.
As mentioned earlier in the flash section, the jffs2 partition can't be stored as part of the firmware or any changes to the filesystem would alter the firmware's CRC and it would be seen as a corrupt firmware. So how do we create and populate the jffs2 filesystem in the first place?
The answer to how the jffs2 layout works can only be described as the very definition of a hack -- the jffs2 filesystem is part of the firmware, but only up util the first bootup. Once booted, the command "jffs2root --move" is executed, causing the program to alter the flash, modifying the firmware's trx headers so the firmware is now only the kernel, making the jffs2 partition external. Since this trick depends on modifying the firmware, an additional reboot is required before the system is in a usable state; the first time it boots the firmware is modified, a reboot is then required and the system is usable from the second boot.
The more recent revelation was that it didn't matter what type of filesystem was embeded into the original firmware, that the same trick could be used to convert a squashfs install into a jffs2 only system. Once the jffs2 partition nolonger Had any more remaining symlinks to the squashfs filesystem on /rom, you could run the jffs2root utility again and it would repartition, allowing the jffs2 partition to take over the space previously held by the squashfs partition -- as long as the new partition contained some vaild jffs2 data, the invalid squashfs data was ignored.
The trick to enabling the boot_wait setting is the Ping.asp page found under the administration/diagnostics page of the web administration. Early versions of the firmware would simply plug the value entered on the webpage into a "ping %s" command and execute it. As the firmwares progressed they attempted to fix the bug by slowly adding more and more restrictions to the characters which could be entered into the fields; eventually the bug was fixed by replacing the system() calls with an execve() call which prevented the arguments from being evaluated by the shell. The last firmware revisions which featured a usable Ping.asp bug were 3.01.3 for the WRT54G models (all revisions) and 3.37.2 for the WRT54GS models (all revisions). If you call this a security hole or not depends on your point of view; it's certainly a flaw in their code, but it requires the administrator password to be able to access the page.
Here's the code behind the Ping.asp page on the 3.37.2 firmware:
char *ip = nvram_safe_get("ping_ip");
if(!check_wan_link(0))
buf_to_file(PING_TMP, "Network is unreachable\n");
else if(strchr(ip, ' ') || strchr(ip, '`') || strstr(ip, PING_TMP))
// Fix Ping.asp bug, user can execute command ping in Ping.asp
buf_to_file(PING_TMP, "Invalid IP Address or Domain Name\n");
else if(nvram_invmatch("ping_times","") && nvram_invmatch("ping_ip","")){
char cmd[80];
snprintf(cmd, sizeof(cmd), "ping -c %s -f %s %s &", nvram_safe_get("ping_times"), PING_TMP, ip);
printf("cmd=[%s]\n",cmd);
eval("killall", "ping");
unlink(PING_TMP);
system(cmd);
}
The ping command is only executed if the WAN interface has an ip address and the ip address to ping doesn't contain any of the following: (space) (tilde) "/tmp/ping.log". There's also a length restriction on the HTML that only allows 31 characters to be entered into the IP field.
With that in mind, make sure the WAN interface has an ip address and ping the following "ip addresses" in sequential order to enable the boot_wait variable:
;cp${IFS}*/*/nvram${IFS}/tmp/n
;*/n${IFS}set${IFS}boot_wait=on
;*/n${IFS}commit
;*/n${IFS}show>tmp/ping.log
Note that these commands make use of a number of tricks to keep the total length under 31 characters and avoid any of the above pitfalls. The actual commands executed are:
cp /usr/sbin/nvram /tmp/n /tmp/n set boot_wait=on /tmp/n commit /tmp/n show > tmp/ping.logThe Ping.asp page is coded to display "/tmp/ping.log" as the ping results so only the last command will actually produce any output. The output from the "nvram show" command will include all variables set in nvram, the only one of particular interest here is "boot_wait=on"; if you don't see that try the commands again.
Uploading the OpenWrt firmware can be done in a number of ways, either via the webpage or the previously enabled boot_wait. It's recommended that you use boot_wait and upload the firmware via tftp so you aren't at a loss of what to do should the firmware fail to boot.
A sample session might look like this:
tftp 192.168.1.1 tftp> binary tftp> rexmt 1 tftp> timeout 60 tftp> trace Packet tracing on. tftp> put openwrt-wrt54g-squashfs.bin
mtd -r write firmware.trx linuxThis will write the file "firmware.trx" directly to the "linux" partition, and if successful it will automatically reboot. As for getting the firmware.trx onto the filesystem, popular choices include wget, scp and network mounted drives.
The linux partition is the entire area from the end of the bootloader to the start of nvram, the write command will take the file specified and write it to the start of the linux partition; it will not erase the partition and it won't write any more data than that contained in firmware.trx; no other partitions (including nvram) are touched. This means, that if you're using the squashfs version, that the jffs2 data generally remains intact (provided the new firmware isn't large enough to overwrite the jffs2 data). If you're using the jffs2 version of the firmware, the old jffs2 data will be lost, requiring packages to be reinstalled.
dd if=/dev/mtd/1 of=/tmp/firmware.trxThis will create a firmware.trx containing the firmware and the jffs2 filesystem located after the firmware. This can then be written back to the router (or another router of a similar flash size) using the mtd write command above.
Trying 192.168.1.41...
Connected to 192.168.1.41.
Escape character is '^]'.
=== IMPORTANT ============================
Use 'passwd' to set your login password
this will disable telnet and enable SSH
------------------------------------------
BusyBox v1.00 (2005.05.30-01:57+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.
_______ ________ __
| |.-----.-----.-----.| | | |.----.| |_
| - || _ | -__| || | | || _|| _|
|_______|| __|_____|__|__||________||__| |____|
|__| W I R E L E S S F R E E D O M
root@OpenWrt:/#
By default there's no password. It's strongly suggested that you set a root password, which as noted in the login banner will disable telnet and enable SSH.
Almost all standard linux commands will work, however most of the commands are actually compact versions provided by busybox and may not support all commandline switches.
Access to the nvram partition is done via the nvram utility:
| Command | Description |
|---|---|
| nvram show | Shows all variables stored in nvram |
| nvram get [variable] | Returns the contents of [variable] |
| nvram set [variable]=[value] | Assigns [value] to [variable] |
| nvram unset [variable] | Removes [variable] from nvram |
| nvram commit | Saves the nvram cache back to the flash |
It's important to note, that with the exception of the commit command, all nvram commands work with a memory cached copy of the nvram data and do not alter the data stored on the flash chip. Any changes not commited to the flash will be lost on a reboot as the cache is repopulated with the values stored on flash.
| Variable | Description |
|---|---|
| [type]_ifname | The name of the interface used for [type] |
| [type]_ifnames | If [type]_ifname is a bridge (br0-br99) then this variable contains the interfaces to be added to the bridge |
| [type]_proto | This variable determines how the interface is to be configured:
dhcp - Attempt a dhcp lease to determine ip, netmask, dns, gateway static - manual configuration |
| [type]_ipaddr | The ip address to assign to the interface or to request via dhcp |
| [type]_netmask | The netmask associated with the interface |
| [type]_dns | The dns server to be used |
| [type]_gateway | The default gateway |
| [type]_stp | Enable spanning tree on bridges |
| [type]_hwaddr | MAC address to be used for the interface |
Here [type] refers to "lan" for the lan/wifi bridge, and "wan" for the connection to the internet. Configuration using the above values is done by the /etc/init.d/S40network script, using the "ifup [type]" command; the lan interface is handled by "ifup lan" and the wan interface is handled by "ifup wan". Note that [type] itself has no value; the networking script defaults to configuring the lan and wan, but there's no reason why you couldn't follow the above pattern with [type] as "foo" and perform "ifup foo" to configure foo_ifname.
The default /etc/init.d/S40network script:
#!/bin/sh
. /etc/functions.sh
case "$1" in
start|restart)
ifup lan
ifup wan
ifup wifi
wifi up
for route in $(nvram get static_route); do {
eval "set $(echo $route | sed 's/:/ /g')"
$DEBUG route add -net $1 netmask $2 gw $3 metric $4 dev $5
} done
;;
esac
To see what the ifup script actually does, there's a DEBUG mode:
DEBUG=echo ifup lanThis will cause ifup to only print the commands that would normally be executed to configure the interface.
| Variable | Value |
|---|---|
| wl0_mode | One of the following:
ap - wireless access point sta - wireless client wet - wireless bridge (note: buggy) |
| wl0_infra | Mode to be used for sta/wet
0 - adhoc (point to point) 1 - infrastructure (connect to an ap) |
| wl0_ssid | Network name (default "linksys") |
| wl0_closed | Broadcast SSID in beacons
0 - broadcast ssid 1 - hide SSID (broadcast a beacon with null SSID field) |
| wl0_radio | Enable/Disable radio
0 - wireless is disabled 1 - wireless is enabled |
| wl0_bcn | Beacon period (transmission interval, default 100) |
| wl0_frameburst | Allow framebursts
on - allow frameburst off - don't allow frameburst |
| wl0_afterburner | Enable afterburner/speedbooster extensions (note: buggy)
on - afterburner is enabled off - afterburner is disabled |
Configuration is done by the /sbin/wifi wrapper. The command "wifi up" is used in /etc/init.d/S40network to enable the wireless layer.
To get into failsafe mode on a WRT54G or WRT54GS, you need to press and hold the reset button when the DMZ led at the front of the router lights at bootup.
The actual code responsible for the failsafe check looks like this: (taken from preinit)
echo 0x01 > /proc/sys/diag
sleep 1
if [ $(cat /proc/sys/reset) = 1 ] || [ "$(/usr/sbin/nvram get failsafe)" = 1 ]; then
export FAILSAFE=true
while :; do { echo $(((X=(X+1)%8)%2)) > /proc/sys/diag; sleep $((X==0)); } done &
fi
The first thing preinit does is turn on the DMZ led through /proc/sys/diag, the system then pauses for one second to allow the user to notice the led and press the button, at the end of the one second delay the system checks if the reset button is currently held, which triggers failsafe. Additionally, failsafe can also be triggered by setting the nvram variable "failsafe" to "1", allowing failsafe to be used without physical access to the device.
Once in failsafe mode, the DMZ led will flash continuously and the FAILSAFE variable is set, triggering failsafe mode in various scripts.
nvram () {
case $1 in
get) eval "echo \${NVRAM_$2:-\$(command nvram get $2)}";;
*) command nvram $*;;
esac
}
The purpose of these lines is to allow /etc/nvram.overrides to set variables which take priority over the actual nvram variables. The above code creates a shell wrapper around the nvram application, allowing it to intercept get commands; if a shell environment variable exists it will be returned, otherwise the actual nvram command will be run.
This allows the /etc/nvram.overrides script to set NVRAM_lan_ipaddr=192.168.1.1, providing a known ip address for failsafe mode without erasing the user's existing configuration. The user is then able to fix or experiment with the configuration without having to redo the configuration each time a mistake is made. The user is then able to fix or experiment with the configuration without having to redo the configuration each time a mistake is made.
| Command | Description |
|---|---|
| ipkg update | Download a list of packages available |
| ipkg list | View the list of packages |
| ipkg install dropbear | Install the dropbear package |
| ipkg remove dropbear | Remove the dropbear package |
The list of repositories that ipkg searches for packages is in /etc/ipkg.conf. Each repository is entered in the following format:
src [name] [url]Links to various repositories and the packages they contain can be found at http://tracker.openwrt.org/