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?

Advertisements

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
...