Problems with custom OpenWRT images

During this week, I have been playing extensively with the OpenWRT whiterussian Linux distribution for embedded devices, like the Linksys WRT54G wireless routers.

My initial plan was to build a custom firmware for the Linksys WRT54G wireless router in order to enable some functionality that is disabled by default — like busybox su applet or shadow passwords — and disable some other features that come enabled by default — like USB or PCMCIA.

I followed the instructions outlined in OpenWrt Buildroot, but had only partial success.

First, I downloaded whiterussian build sources from SVN:

$ svn co https://svn.openwrt.org/openwrt/branches/whiterussian/
$ cd whiterussian/openwrt
$ make menuconfig
$ make

Next, I uploaded the resulting bin/openwrt-wrt54g-squashfs.bin image to the Linksys router:

$ scp bin/openwrt-wrt54g-squashfs.bin root@linksys:/tmp

Then flashed the new image:

$ ssh root@linksys
# cd /tmp
# dd if=openwrt-wrt54g-squashfs.bin of=flash.trx bs=32 skip=1
# mtd -r write flash.trx linux

mtd flashed the new image and rebooted the router — as instructed per the -r command-line flag. I could telnet into the Linksys box, but something strange was going on: instead of seeing a bunch of symbolic links under /etc/init.d pointing to /rom/etc/init.d, I only found plain text, executable script files.

While looking around, I could see the JFFS2 partition was mounted under /jffs. I ran firstboot by hand and that seemed to create the JFFS2 filesystem layout, which mostly consists of symbolic links to files in the SquashFS volume (mounted under /rom). Rebooting the Linksys router left an apparently useable system that mostly looked like the normal OpenWRT whiterussian images I can download from http://downloads.openwrt.org/whiterussian/rc5/, but still not identical.

Why doing a custom build of OpenWRT produces a flash image that is different from the ones available for download from the official site, and one that fails to run firstboot on the first run after reflashing the router’s memory?

Does anyone have dealt with building custom OpenWRT images before? And what’s more, does anyone had full success in flashing and using them?

Linksys, OpenWRT and multiple VLANs

The Cisco Linksys WRT54G/GS/GL is made up of a six-port configurable switch, a standard Ethernet controller (usually a Broadcom controller named eth0) and a Wireless controller (usually a Broadcom controller named eth1).

The following diagram tries to illustrate the different components that made up the Cisco Linksys and how are they interconnected:

                                            Linksys rear
 Trunk    Internet    1     2     3     4   port number
  ---        ---     ---   ---   ---   ---
  |5|        |4|     |3|   |2|   |1|   |0|  switch port number
  ---        ---     ---   ---   ---   ---
  |           |       |                 |
  |         vlan1     |----- vlan0 -----|
  |
  | Miniswitch
  ----------------------------------------
  | Linux
  |
  |           ---- vlan0 -> LAN
  |           |
  |----- eth0 -
              |
              ---- vlan1 -> Internet/WAN

The standard Ethernet controller is attached to the sixth port (port #5) of the switch and is configured as a 802.1q VLAN trunk port. This allows running several VLANs using a single connection to the switch.

By default, OpenWRT configures two per-VLAN network interfaces:

  • vlan0:

    stands on the VLAN0 (the Local Area Network which comprises the four ports labeled as 1, 2, 3 and 4 at the rear of the box).

  • vlan1:

    stands on the WAN network (the port labeled Internet at the rear of the box).

The VLAN configuration is controlled using NVRAM variables. The variable labeled vlan0ports defines which switch ports are assigned onto the VLAN0, while vlan1ports defines which switch ports are assigned onto the VLAN1.

This is the default NVRAM configuration:

nvram set vlan0ports="3 2 1 0 5*"
nvram set vlan0hwname=et0
nvram set vlan1ports="4 5"
nvram set vlan1hwname=et0
  • vlan0ports:

    states that ports #3, #2, #1 and #0 (the ports labeled as 1, 2, 3 and 4 at the rear of the box) are assigned onto VLAN0. Additionally, port #5 is also assigned onto VLAN0.

    The asterisk sitting besides the 5 means VLAN0 is the default, native VLAN for this port, so any untagged traffic is considered to belong to VLAN0.

  • vlan1ports:

    states that port #4 (the port labeled as Internet at the rear of the box) is assigned onto VLAN1. Additionally, port #5 is also assigned onto VLAN1 since it’s a trunk port.

    The lack of an asterisk means VLAN1 is not the default, native VLAN for this port.

NOTE: vlannhwname needs to have a value assigned to it, even when it’s value is never used by the init scripts. This value is usually et0.

NOTE: Care must be exercised as ports numbers are zero-based, as illustrated before, and the sixth-port (port #5) must be assigned to every VLAN, since it is an VLAN trunk port.

The following code snippet from /etc/init.d/S10boot shows how the init script tells the switch which ports are onto which VLANs:

# configure the switch based on nvram
[ -d /proc/switch/eth0 ] &ports)"
    [ -z "$vp" -o -z "$(nvram get vlan${nr}hwname)" ] || {
        echo "$vp" > /proc/switch/eth0/vlan/$nr/ports
    }
  done
}

We can also see that up to sixteen VLANs are supported by the switch.

Custom VLANs

The Linksys and OpenWRT combination is so flexible that we can configure additional VLANs. In fact, I was looking to add an additional administrative VLAN (VLAN2) granting me full access to the box while I could restrict access from the LAN and WAN to the minimum — for example, by using additional firewall rules.

This is depicted in the following figure:

                                            Linksys rear
 Trunk    Internet    1     2     3     4   port number
  ---        ---     ---   ---   ---   ---
  |5|        |4|     |3|   |2|   |1|   |0|  switch port number
  ---        ---     ---   ---   ---   ---
  |           |       |     |           |
  |         vlan1   vlan2   |-- vlan0 --|
  |
  | Linksys
  ----------------------------------------
  | Linux
  |
  |           ---- vlan0 -> LAN
  |           |
  |----- eth0 ---- vlan1 -> Internet/WAN
              |
              ---- vlan2 -> Administrative VLAN

To achieve this configuration, we need to remove port #3 (labeled as 1 at the rear of the box) from VLAN0 and assign it onto VLAN2. We also need to add port #5 to the VLAN2 since it is the VLAN trunk port used to carry the traffic from the switch to Linux through the standard Ethernet controller:

nvram set vlan0ports="2 1 0 5*"
nvram set vlan0hwname=et0
nvram set vlan1ports="4 5"
nvram set vlan1hwname=et0
nvram set vlan2ports="3 5"
nvram set vlan2hwname=et0

I’ve defined three custom NVRAM variables that will get used by an additional init script to configure the VLAN2 as an administrative VLAN, granting full access to the box:

  • adm_ifname:

    defines the Linux network interface name assigned to the administrative VLAN, in the form of vlann, where n is the VLAN number.

  • adm_ipaddr:

    defines the IP address for the administrative interface.

  • adm_netmask:

    defines the network mask for the administrative interface.

For example:

nvram set adm_ifname=vlan2
nvram set adm_ipaddr=192.168.0.100
nvram set adm_netmask=255.255.0.0

I’ve also coded up an additional init script, named /etc/init.d/S41network, used to bring up the administrative interface. I’ve decided not to fiddle with /etc/init.d/S40network to avoid breaking things and having problems during upgrades.

These are the contents of /etc/init.d/S41network:

#!/bin/sh
IFNAME=$(nvram get adm_ifname)
VLAN=${IFNAME##vlan}
IPADDR=$(nvram get adm_ipaddr)
NETMASK=$(nvram get adm_netmask)
vconfig add eth0 $VLAN
ifconfig vlan${VLAN} up ${IPADDR} netmask ${NETMASK}

Testing

To test this custom configuration, I recommend disabling the firewall, my removing the executable permission bit from /etc/init.d/S45firewall and /etc/init.d/S41network just to prevent being locked out from the box in case problems arise.

Firewalling

I’ve also replaced the firewalling init script, /etc/init.d/S45firewall, with my own version. This allows for a fine-grained and thighter configuration.

Since the box will act as a routing firewall, and since it has 3 VLANs, I wanted to apply the following policy:

  • Any traffic coming from or going to the administrative VLAN (VLAN2) is allowed:

    This rule allows administering the box from a computer attached to the VLAN2, while blocking administrative access from other VLANs.

  • Incoming ICMP Echo Requests and ICMP Time Exceeded control messages are allowed from any interface:

    This rule allows certain ICMP control messages to reach the box. ICMP Echo Request is needed in order for the box to respond to ping and ICMP Time Exceeded (TTL) so we don’t break the PMTU discovery algorithm.

  • Any other incoming traffic from the LAN is rejected:

    This rule rejects any other traffic which does not match previous rules. Traffic is explicitly rejected, so we avoid having clients blocked waiting for an RST TCP segment.

  • Any other incoming traffic from the WAN is dropped:

    This rule silently drops any traffic coming from the WAN which does not match any previous rule. This will make external scan attacks much slower.

  • Local DNS queries coming from the local box going to configured DNS servers are allowed:

    This rule allows the local machine to resolve DNS queries sent against configured DNS servers (those configured in the wan_dns NVRAM variable). This is rarely needed, but the ipkg command requires a working DNS name resolution.

  • HTTP traffic from the local machine to the WAN is allowed:

    This rule allows upgrading and installing packages using the ipkg command.

  • Outgoing ICMP Echo Requests and ICMP Time Exceeded control messages are allowed from any interface:

    This rule allows certain ICMP control messages to depart from the box. ICMP Echo Request is needed in order for the box to invoke ping and ICMP Time Exceeded (TTL) so we don’t break the PMTU discovery algorithm.

  • Forwarding SSH/NX traffic coming from WAN to the designated SSH/NX server in the LAN:

    This rule allows accesing the SSH/NX traffic from the WAN. In addition, I apply SNAT to make IP datagrams appear to come from the firewall box since I have multiple DSL links.

  • Forwarding HTTP and HTTP/S traffic coming from the LAN targeted to the WAN:

    This rule allows using HTTP and HTTP/S services from the LAN.

  • DNS queries coming from the LAN going to configured DNS servers are allowed:

    This rule allows the machines in the LAN to resolve DNS queries sent against configured DNS servers (those configured in the wan_dns NVRAM variable).

  • Forwarding ICMP Echo Requests coming from the LAN to the WAN:

    This allows pinging external hosts from the LAN. ICMP Time Exceeded, however, is not forwarded, since the firewall sits in the middle between the LAN and the WAN (and I do use SNAT and DNAT).

Here is the complete /etc/init.d/S45firewall script:

#!/bin/sh
IPTABLES=/usr/sbin/iptables
FW_INET_IFACE=$(nvram get wan_ifname)
FW_INET_IP=$(nvram get wan_ipaddr)
FW_PRIVATE_IFACE=$(nvram get lan_ifname)
FW_PRIVATE_IP=$(nvram get lan_ipaddr)
FW_ADM_IFACE=$(nvram get adm_ifname)
NX_IP=10.200.0.10

$IPTABLES -F
$IPTABLES -t nat -F

# Configure SNAT/DNAT/MASQUERADE
$IPTABLES -t nat -A PREROUTING -i ${FW_INET_IFACE} -p tcp 
                               -d ${FW_INET_IP} --dport 179 
                               -j DNAT --to-destination ${NX_IP}:22
$IPTABLES -t nat -A POSTROUTING -o ${FW_PRIVATE_IFACE} -p tcp 
                                -d ${NX_IP} --dport 22 
                                -j SNAT --to-source ${FW_PRIVATE_IP}
$IPTABLES -t nat -A POSTROUTING -o ${FW_INET_IFACE} -j MASQUERADE

# Configure input firewall filtering:
# Allow:
#   - Traffic flowing from the loopback interface
#   - Traffic coming from the administrative VLAN
#   - ICMP Echo Request coming from WAN
#   - ICMP Time Exceeded (TTL) coming from WAN
#   - Traffic from an already established or related connection
# Block:
#   - Any traffic coming from the WAN
# Reject:
#   - Any other traffic coming from the LAN
$IPTABLES -A INPUT -i lo -j ACCEPT
$IPTABLES -A INPUT -i ${FW_ADM_IFACE} -j ACCEPT
$IPTABLES -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
$IPTABLES -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
$IPTABLES -A INPUT -i ${FW_INET_IFACE} -j DROP
$IPTABLES -A INPUT -j REJECT

# Configure output firewall filtering:
# Allow:
#   - Traffic flowing to the loopback interface
#   - HTTP traffic
#   - ICMP Echo Request going to WAN
#   - ICMP Time Exceeded (TTL) going to WAN
#   - DNS queries to configured WAN name servers
#   - Traffic from an already established or related connection
# Reject:
#   - Any other traffic
$IPTABLES -A OUTPUT -o lo -j ACCEPT
$IPTABLES -A OUTPUT -o ${FW_INET_IFACE} -p tcp -m tcp 
                     --dport 80 -m state --state NEW -j ACCEPT
$IPTABLES -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
$IPTABLES -A OUTPUT -p icmp --icmp-type time-exceeded -j ACCEPT
for ns in $(nvram get wan_dns); do
        $IPTABLES -A OUTPUT -o ${FW_INET_IFACE} -p udp -m udp 
                            -d "$ns" --dport 53 -j ACCEPT
        $IPTABLES -A OUTPUT -o ${FW_INET_IFACE} -p tcp -m tcp 
                            -d "$ns" --dport 53 -j ACCEPT
done
$IPTABLES -A OUTPUT -j REJECT

# Configure forward firewall filtering:
# Allow:
#   - Incoming SSH/NX traffic -> the filtering takes place after the
#     PREROUTING chain has been processed and, since DNAT has been already
#     being performed, the traffic is filtered accordingly to its final
#     destination (the SSH/NX server)
#   - Outgoing DNS queries to configured WAN name servers
#   - Outgoing HTTP and HTTP/S traffic
#   - ICMP Echo Request coming from LAN going to WAN
#   - Trafic from an already established or related connection
# Drop:
#   - Any other traffic
$IPTABLES -A FORWARD -i ${FW_INET_IFACE} -o ${FW_PRIVATE_IFACE} -p tcp -m tcp 
                     -d ${NX_IP} --dport 22 -m state --state NEW -j ACCEPT
$IPTABLES -A FORWARD -i ${FW_PRIVATE_IFACE} -o ${FW_INET_IFACE} -p tcp -m tcp 
                     --dport 80 -m state --state NEW -j ACCEPT
$IPTABLES -A FORWARD -i ${FW_PRIVATE_IFACE} -o ${FW_INET_IFACE} -p tcp -m tcp 
                     --dport 443 -m state --state NEW -j ACCEPT
$IPTABLES -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A FORWARD -i ${FW_PRIVATE_IFACE} -o ${FW_INET_IFACE} 
                     -p icmp --icmp-type echo-request -j ACCEPT
for ns in $(nvram get wan_dns); do
        $IPTABLES -A FORWARD -i ${FW_PRIVATE_IFACE} -o ${FW_INET_IFACE} 
                             -p udp -m udp -d "$ns" --dport 53 -j ACCEPT
        $IPTABLES -A FORWARD -i ${FW_PRIVATE_IFACE} -o ${FW_INET_IFACE} 
                             -p tcp -m tcp -d "$ns" --dport 53 -j ACCEPT
done
$IPTABLES -A FORWARD -j DROP

Integrated DHCP and DNS services using OpenWRT

dnsmasq offers a lightweight, functional and integrated DHCP and DNS service. Using it on OpenWRT brings up and embedded, flexible DNS service, with a very small footprint, for small or home offices.

dnsmasq acts as a caching DNS server and DHCP server. It reserves a DNS domain, called the local DNS domain and usually being .lan, for local name resolution. When queried for an A RR inside the local DNS domain, dnsmasq looks at file /etc/hosts for a match. If one is found, its corresponding IP is returned as the query result. When queried for a PTR RR, it looks into file /etc/hosts for a match by IP and, if one is found, its correspoding hostname, qualified with the local DNS domain, is returned. Thus, /etc/hosts behaves much like a DNS master zone file.

Also, if the DHCP server funcionality of dnsmasq is enabled, when a query under the local DNS domain fails (no record is found in /etc/hosts), it will try to resolve the query from the DHCP lease database.

The DHCP lease database is usually stored at /tmp/dhcp.leases. Its format is pretty simple: it’s a text file, where each line represents an active DHCP lease. Each line is made up of five fields:

  1. Time of lease expiration

    In epoch time (seconds since 1970). States when the lease will expire. Most DHCP clients will try to renew the lease when it reaches 80% of its valid lifetime.

  2. Client MAC address

    The MAC address corresponding to the client to which the lease belongs.

  3. Leased IP address

    A valid IP address, taken from the DHCP pool, which is actually and currently leased to the client whose MAC address is listed in the previous field.

  4. Client hostname

    If known, holds the unqualified host name of the client machine. Else, an asterisk is stored here.

  5. Client ID

    Simon Kelley defines it as:

    The client-ID is used as the computer’s unique-ID in preference to the MAC address, if it’s available. Some DHCP clients provide it, and some don’t. The ones that do normally derive it from the MAC address unless explicity configured, but it could be something like a serial number, which would protect a computer from losing its identify if the network interface were replaced.

    If not know, an asterisk is stored here.

A sample DHCP database lease:

# cat /tmp/dhcp.leases
1147729862 00:16:3e:3b:56:f1 192.168.0.11 rhel *
1147725355 00:0c:29:09:3d:58 192.168.0.10 rhel-devel *

In this case, there are two active DHCP leases, one for client rhel, another one for rhel-devel.

OpenWRT uses a rc.d script stored at /etc/init.d/S50dnsmasq which, for a squashfs firmware is a symbolic link to /rom/etc/init.d/S50dnsmasq. This rc.d script tries to configure the dnsmasq daemon using NVRAM variables, which helps a lot when reflashing. However, I have found more convenient to the use the traditional /etc/dnsmasq.conf file instead.

Replacing the OpenWRT rc.d script with a custom one, in order to leverage dnsmasq.conf, is as simple as removing /etc/init.d/S50dnsmasq and invoking the dnsmasq daemon directly:

rm -f /etc/init.d/S50dnsmasq
cat > /etc/init.d/S50dnsmasq < < EOF
#/bin/sh
/usr/sbin/dnsmasq
EOF

Here is a sample of a /etc/dnsmasq.conf file I use on my Linksys WRT54G router running OpenWRT White Russian RC5:

# filter what we send upstream
domain-needed
bogus-priv
filterwin2k
localise-queries

# allow /etc/hosts and dhcp lookups via *.lan
local=/lan/
domain=lan
expand-hosts

# enable dhcp (start,end,netmask,leasetime)
dhcp-authoritative
dhcp-range=10.0.0.10,10.0.0.100,255.255.255.128,12h
dhcp-leasefile=/tmp/dhcp.leases

# use /etc/ethers for static hosts; same format as --dhcp-host
# [hwaddr] [ipaddr]
read-ethers

# other useful options:
# default route(s): dhcp-option=3,192.168.1.1,192.168.1.2
#    dns server(s): dhcp-option=6,192.168.1.1,192.168.1.2
dhcp-option=3,10.0.0.126
dhcp-option=6,10.0.0.122

OpenWRT and Time zone

By default, an OpenWRT router operates in the UTC (Universal Time Coordinate) time zone.

OpenWRT stores the time zone inside a file named /etc/TZ. If this file is missing or empty, OpenWRT assumes the local time equals UTC time. The format is pretty strange, and has the following syntax:

syntax ::= GMT

offset ::= (+|-)

hour ::= [ 00 - 12 ]

The is the value you must add to or substract from the local time to get the UTC time. This offset will be positive if the local time zone is west of the Prime Meridian and negative if it is east. For example, TZ must be set to GMT-1 for Madrid, which is GMT+1:

# echo GMT-1 > /etc/TZ

UPDATED: The syntax for the TZ variable is documented here.

Following this document, the correct contents for file /etc/TZ when located in Madrid would be:

CET-1CEST-2,M3.5.0/2,M10.5.0/3

Thanks Kiko!

QoS with OpenWRT

I use the following script for my Linksys WRT54GS Wireless router running OpenWRT White Russian -RC4 to setup a QoS firewall that uses Hierarchical Token Bucket (HTB) and Stochastic Fair Queueing (SFQ) to classify the traffic in three cathegories:

  1. Interactive, high priority traffic:
    This class is used for DNS traffic and SSH traffic.
  2. Interactive, normal priority traffic:
    This class is used for HTTP and HTTP/S traffic.
  3. Low priority traffic:
    This class is used for traffic which doesn’t fit any of the previous classes.

Each class is also subclassed with Stochastic Fair Queueing (SFQ) to distribute traffic utilization among the same class evenly.

To make the script run every time the router is powered up, save the script as /etc/init.d/S41qos and turn the executable bit on it.

#!/bin/ash # Executables GREP=/bin/grep INSMOD=/sbin/insmod TC=/usr/sbin/tc DEV=vlan1 # Load kernel modules $GREP -q ^sch_htb /proc/modules || $INSMOD /lib/modules/`uname -r`/sch_htb.o $GREP -q ^sch_sfq /proc/modules || $INSMOD /lib/modules/`uname -r`/sch_sfq.o $GREP -q ^cls_u32 /proc/modules || $INSMOD /lib/modules/`uname -r`/cls_u32.o # Hierarchical Token Bucket (HTB) $TC qdisc add dev $DEV root handle 1: htb default 30 $TC class add dev $DEV parent 1: classid 1:1 htb rate 1mbit burst 20k cburst 20k # HTB Classes $TC class add dev $DEV parent 1:1 classid 1:10 htb rate 768kbit ceil 1mbit burst 15k cburst 15k $TC class add dev $DEV parent 1:1 classid 1:20 htb rate 256kbit ceil 1mbit burst 20k cburst 20k $TC class add dev $DEV parent 1:1 classid 1:30 htb rate 128kbit ceil 512kbit burst 5k cburst 5k $TC qdisc add dev $DEV parent 1:10 handle 10: sfq perturb 10 $TC qdisc add dev $DEV parent 1:20 handle 20: sfq perturb 10 $TC qdisc add dev $DEV parent 1:30 handle 30: sfq perturb 10 # Filters $TC filter add dev $DEV protocol ip parent 1:0 prio 1 u32 match ip dport 53 0xffff flowid 1:10 $TC filter add dev $DEV protocol ip parent 1:0 prio 2 u32 match ip dport 22 0xffff flowid 1:10 $TC filter add dev $DEV protocol ip parent 1:0 prio 10 u32 match ip dport 80 0xffff flowid 1:20 $TC filter add dev $DEV protocol ip parent 1:0 prio 10 u32 match ip dport 443 0xffff flowid 1:20

Controlling the WRT54G/GS leds

The Cisco/Linksys WRT54G/GS router has two leds just beneath the Cisco Systems logo. One is a white led, while the other is an amber led. It is possible to turn them on or off using the GPIO pins on the mainboard.

  • GPIO #3 controls the amber led beneath the Cisco Systems logo:

    Disabling GPIO #3 turns on the amber led.
    Enabling GPIO #3 turns off the amber led.

  • GPIO #2 controls the white led beneath the Cisco Systems logo:

    Disabling GPIO #2 turns on the white led.
    Enabling GPIO #2 turns off the white led.

  • GPIO #7 controls the DMZ led:

    Disabling GPIO #7 turns on the DMZ led.
    Enabling GPIO #7 turns off the DMZ led.

To control those GPIO pins with the OpenWRT firmware, download gpio.tar.gz or gpio.tar.gz and install the gpio binary into /usr/bin:

# cd /tmp
# wget http://openwrt.org/downloads/gpio.tar.gz
# tar -zxf gpio.tar.gz
# mv gpio /usr/bin
# rm /tmp/gpio.*

Use “/usr/bin/gpio disable n” to disable GPIO #n, or use “/usr/bin/gpio enable n” to enable GPIO #n.

Additionally, I customized the /etc/init.d/S99done script in order to turn on the white led under the Cisco Systems logo once the system booted:

# rm /etc/init.d/S99done
# cat > /etc/init.d/S99done <  #!/bin/sh
> /rom/etc/init.d/S99done
> /usr/bin/gpio disable 2
> EOF
# chmod +x /etc/init.d/S99done

Remote logging with Linksys WRT54G

Enabling remote syslog logging with Linksys WRT54G and OpenWRT White Russian RC3 is as simple as storing the IP of the remote syslog server into the log_ipaddr NVRAM variable:

nvram set log_ipaddr=A.B.C.D
nvram commit
reboot

log_ipaddr is used by /etc/init.d/rcS startup script to launch a local syslog daemon with option “-R “:

#!/bin/sh
syslog_ip=$(nvram get log_ipaddr)
ipcalc -s "$syslog_ip" || syslog_ip=""
syslogd -C 16 ${syslog_ip:+-L -R $syslog_ip}
klogd
...

Adding static routes to the Linksys WRT54G

Static routes are stored in a NVRAM variable called static_route. This NVRAM variable is a blank-delimited list of static route entries. Each entry has the following format:

SUBNET_ADDRESS:SUBNET_MASK:GATEWAY:METRIC:INTERFACE
  • SUBNET_ADDRESS is the IP subnet address, calculated by applying the netmask to the gateway address, for example.
  • SUBNET_MASK is the subnet mask used to disguise which part, of a given IP address, corresponds to the subnet and which part correspond to the host.
  • GATEWAY is the IP address of the gateway used to reach the subnet.
  • METRIC is the route metric. Routes to the same destination with lower metrics are preferred over those with higher metrics. So, this is usually set to 1 for single-path routes.
  • INTERFACE is the interface name used to reach the GATEWAY. Usually it is not needed, as this can be disguised from the current routing table, but the WRT54G implementation of static routes requires this. It is usually vlan0 for LAN-reachable gateways.

During the next boot, the /etc/init.d/S40network initscript will read the static_route NVRAM variable and will inject static entries into the routing table derived from it.

Linksys WRT54G (cont.)

I have been playing around with the WRT54G a little bit more.

Instead of acting as a Wireless AP and forwarding traffic to the Internet via the WAN port, I have configured it to forward packets coming from the LAN (via any of the 4-LAN ports) to the Internet via the Wireless interface. The WRT54G will associate with an existing Wireless AP and will use it as the gateway to forward traffic to the Internet.

These are the steps I followed to configure WRT54G in such a way:

  1. Break the bridge between wired and wireless interfaces:

    In its default configuration, the WRT54G router is configured to bridge together the wired (vlan0) and wireless (eth1) interfaces. Since the WRT54G is being configured to route between the wired interface (LAN) and the wireless interface (Internet), this bridge must be disabled:

    # nvram set lan_ifname=vlan0

    Setting lan_ifname to br0 will bridge together the interfaces specified by lan_ifnames under a bridge interface named br0. Setting lan_ifname to vlan0 means no bridge will get built up, and that LAN traffic will flow through and from the vlan0 interface.

    NOTE: vlan0 represents the 4-LAN ports of the WRT54G, vlan1 the WAN port, and eth1 the Wireless interface, by the way.

    # nvram set lan_ifnames=vlan0

    This variable defines the interfaces that will get bridged together into br0. Since only one interface is listed, and lan_ifname is not set to br0, no bridge will be built.

  2. Configure WRT54G as a Wireless client:

    The Wireless interface will behave like a WAN port. Thus, I have used the wan_* NVRAM set of variables to configure it. I run the following commands from inside a router shell:

    # nvram set wl0_mode=sta

    This configures the WRT54G to act as a Wireless client, instead of a Wireless AP.

    # nvram set wl0_ssid=

    Configures the ESSID of the Wireless LAN the WRT54G will try to associate with.

    # nvram set wl0_wep=enabled

    WEP authentication is required in order to associate to the Wireless AP.

    # nvram set wl0_key=1

    Use the first WEP key, of the four available WEP key slots.

    # nvram set wl0_key1=

    This fills in the first WEP key slot with the correct key.

    # nvram set wan_proto=static

    Use static IP configuration (no DHCP).

    # nvram set wan_ipaddr=

    Configures the IP address for the wireless interface (acting as the WAN port).

    # nvram set wan_netmask=

    Sets the network mask for the wireless interface (acting as the WAN port).

    # nvram set wan_ifname=eth1

    We tell the initscripts that the Wireless interface will act as the WAN port (outside, firewalled, Internet connection).

    # nvram set wan_gateway=

    Defines the gateway that will be used as the default route to reach the Internet.

    # nvram set wan_hostname=

    Sets the router’s FQDN, like linksys.local or openwrt.example.com.

    # nvram set wan_dns=

    Sets the DNS name server used to resolve names (this is optional, since the router does not have to perform name resolution).

  3. Reconfigure the WRT54G hardware MAC address:

    Sometimes, when the Wireless AP is using MAC filtering, it may be necessary to change the hardwareMAC address of the WRT54G wirelss interface. This can be done using the following command:

    # nvram set il0macaddr=

    However, it is recommended to keep a copy of the original MAC address. My WRT54G router has a sticker in its bottom listing the hardware MAC address for the wired interfaces, but no sticker for the wireless one. Anyways, the MAC address for the wireless interface is the result of adding 0x02 to the last byte of the wired interface MAC address. Thus, if the wired MAC address is 00:11:22:33:44:55, the wireless MAC address is 00:11:22:33:44:57.

  4. Check the wireless interface is properly configured and working:

    The simplest way is to save changes into NVRAM and reboot to check everything is working is to save the changes to NVRAM and reboot:

    # nvram commit
    # reboot

    After rebooting, use the iwconfig command to check if the wireless interface has been able to associate with the Wireless AP:

    # iwconfig eth1
    eth1      IEEE 802.11-DS  ESSID: 
              Mode:Managed  Frequency:2.412Ghz  Access Point: AA:BB:CC:DD:EE:FF
              Tx-Power:31 dBm
              RTS thr=2347 B   Fragment thr=2346B
              Encryption key:XXXX-XXXX-XX

    where AA:BB:CC:DD:EE:FF is the MAC address of the Wireless AP.

  5. Harden the firewall:

    Since the WRT54G will be directly exposed to the outside by means of the Wireless connection, it is important to properly harden the firewall:

    # nvram set wan_ifname=eth1

    Although we are not using the WAN interface, the firewall initscript (S45firewall), by default, blocks all incoming traffic coming from the interface listed in the wan_ifname NVRAM variable. Since we want to block all the traffic coming from the Wireless interface, we want to block all traffic coming from eth1.

    Next, tweak the firewall initscript:

    # rm /etc/init.d/S45firewall
    # cp /rom/etc/init.d/S45firewall /etc/init.d/S45firewall
    # vi /etc/init.d/S45firewall

    From the /etc/init.d/S45firewall file, comment out the following lines:

    iptables -A INPUT -p icmp -j ACCEPT
    iptables -A INPUT -p gre -j ACCEPT
    iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset
    iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable

    This will drop any incoming traffic not related to an already established flow (either TCP session, or UDP/ICMP datagrams).

Linksys WRT54G

Today, I bought a Linksys WRT54G V3.1 Wireless Router from my usual local store. Although it uses a heavily customized Linux version internally, it is quited limited. For instance, it does not allow remote administration via SSH, only through a nice Web interface. So, after playing a little bit with it, I decided to install OpenWRT onto it. At the time of this writing, the only stable, binary release was WhiteRussian -RC2 from July, 19th.

Installing OpenWRT is a little bit tricky. Before being able to flash the OpenWRT firmware, you are instructed to set a NVRAM variable named boot_wait to on since this will introduce a three second delay, just after the router is powered up, for the administrator to upload a new firmware image via TFTP. Also, enabling boot_wait makes troubleshooting and recovery a little bit easier.

The problem is that changing this NVRAM variable is not easy, since the router’s Web interface does not offer that ability. Instead, as instructed by the OpenWRT documentation Wiki, I exploited a bug in the Ping.asp page of the Web interface which allows injecting shell-code to the router. Shortly, the shell code makes a copy of the file /usr/sbin/nvram into /tmp/n and then subsequently uses it to run the following commands:

/tmp/n set boot_wait=on
/tmp/n commit

The next step is launcing a TFTP client on a workstation attached to one of the four LAN ports of the WRT54G and have it prepared to upload the firmware binary image to the router at its factory-default address 192.168.1.1.

The OpenWRT download page offers firmware binary images for several hardware platforms in two formats:

  1. SquashFS:

    SquashFS is the preferred one, since it’s the most mature and allows for an easier recovery in case the routers filesystem gets corrupted.

    The SquashFS firmware is composed of two parts: the combination of a SquashFS ROM filesystem and a JFFS2 writeable flash filesystem. The SquashFS ROM is mounted at /root while JFFS2 is used as / containing a lot of symbolic links to files located under /root (that is, in the ROM).

  2. JFFS2:

    JFFS2 uses JFFS2 filesystem entirely. Thus, the whole filesystem is writeable, which can lead to accidental corruption. Recovery is a more difficult, since critical configuration or binary files could get destroyed or corrupted.

So, I chose the SquashFS firmware. I fired up my TFTP client and configured it to auto-retransmit the firmware file continuously, for 60 seconds, to the router’s IP (which, by default, is 192.168.1.1):

tftp 192.168.1.1
tftp> binary
tftp> rexmt 1
tftp> timeout 60
tftp> trace
Packet tracing on
tftp> put openwrt-wrt54g-squashfs.bin

Once the router is powered up, it will wait for three seconds for the firmware to be uploaded, so there is a small time window for this to succeed. In fact, it took me twice to get the firmware downloaded properly to the WRT54G. The router rebooted itself, set the DMZ led on for a while, then set it off, which means the router is ready. Once the firmware was uploaded, the router rebooted itself into OpenWRT, a pure Linux box.

OpenWRT gives so much freedom when compared to the original firmware. By default, it bridges wired LAN and wireless traffic together, while still having a WAN interface, but it can be easily reconfigured to act as a three-leg router acting as Wireless AP, or it can be reconfigured so the Wireless interface behaves as a client instead.

I’m still playing with it, but I have a lot of expectations about it.