July 15, 2015

Looking under the skin of SoundMate M1 (Airmusic NW11)

Looking under the skin of SoundMate M1 (Airmusic NW11)

In this article I'll do a bit of digging under the skin and do some reverse engineering of SoundMate M1 audio streaming device.

OpenWrt is a not your average embedded OS, it is fully featured Linux with hundreds of packages which you can install with ease. Actually I'm puzzled how come we have just started to see consumer products use OpenWrt.

telnet to device, and use "root" for username and "ifconfig" for password.

# free -m
             total         used         free       shared      buffers
Mem:         62192        27584        34608            0         1376
-/+ buffers:              26208        35984
Swap:            0            0            0

So this device had 64Mb of ram, and more than half of it is free.

# df -h

Filesystem                Size      Used Available Use% Mounted on
rootfs                    1.0M     64.0K    976.0K   6% /
/dev/root                 5.9M      5.9M         0 100% /rom
tmpfs                    30.4M    288.0K     30.1M   1% /tmp
tmpfs                   512.0K         0    512.0K   0% /dev
/dev/mtdblock3            1.0M     64.0K    976.0K   6% /overlay
overlayfs:/overlay        1.0M     64.0K    976.0K   6% /

By looking at free drive space we see that there is 976K of free space for any additional tools or scripts.

# ls -1 /etc/rc.d/
K90network
K98boot
K99umount
S05defconfig
S10boot
S20fstab
S39usb
S40network
S42wanlan
S45dnsmasq
S50cron
S50lighttpd
S50telnet
S50wifi_save
S60dbus
S61avahi-daemon
S69shairport
S90firewall
S95done
S97watchdog
S98sysntpd
S99sysctl

Listing of services shows that this device is using all Open source tools, main tool that enables music streaming is shairport which emulates Apple Airplay service.

# cat /etc/config/network 

config 'interface' 'loopback'
        option 'ifname' 'lo'
        option 'proto' 'static'
        option 'ipaddr' '127.0.0.1'
        option 'netmask' '255.0.0.0'

config 'interface' 'lan'
        option 'type' 'bridge'
        option 'proto' 'static'
        option 'ipaddr' '192.168.222.254'
        option 'netmask' '255.255.255.0'

config 'interface' 'wan'
        option 'ifname' 'eth0'
        option 'proto' 'dhcp'
        option 'hostname' 'SoundMate'
        option 'workmode' '1'

config 'interface' 'wwan'
        option 'ifname' 'wlan1'
        option 'proto' 'dhcp'
        option 'hostname' 'SoundMate'

It is strange to see eth0 port configured when this device is missing RJ45 connector.

# cat /etc/config/wireless 

config 'wifi-device' 'radio0'
        option 'type' 'mac80211'
        option 'hwmode' '11ng'
        option 'noscan' '1'
        list 'ht_capab' 'SHORT-GI-20'
        list 'ht_capab' 'SHORT-GI-40'
        list 'ht_capab' 'RX-STBC1'
        list 'ht_capab' 'DSSS_CCK-40'
        option 'macaddr' '88:a0:cc:87:0a:35'
        option 'channel' '11'
        option 'htmode' 'HT40-'

config 'wifi-iface'
        option 'device' 'radio0'
        option 'network' 'lan'
        option 'mode' 'ap'
        option 'wpa_group_rekey' '0'
        option 'wpa_pair_rekey' '0'
        option 'wpa_master_rekey' '0'
        option 'wds' '1'
        option 'encryption' 'mixed-psk+ccmp'
        option 'ssid' 'SoundMate'
        option 'key' 'password'

config 'wifi-iface'
        option 'device' 'radio0'
        option 'network' 'wwan'
        option 'mode' 'sta'
        option 'wds' '0'
        option 'disabled' '0'
        option 'ssid' 'Home Wifi'
        option 'encryption' 'psk2+ccmp'
        option 'key' 'Password'

Wifi options need a bit more investigating.

# ps 
PID USER       VSZ STAT COMMAND
1 root      1520 S    init
2 root         0 SW   [kthreadd]
3 root         0 SW   [ksoftirqd/0]
4 root         0 SW   [kworker/0:0]
5 root         0 RW   [kworker/u:0]
6 root         0 SW   [rcu_kthread]
7 root         0 SW<  [khelper]
8 root         0 RW   [kworker/u:1]
55 root         0 SW   [sync_supers]
57 root         0 SW   [bdi-default]
59 root         0 SW<  [kblockd]
87 root         0 SW   [kswapd0]
137 root         0 SW   [fsnotify_mark]
151 root         0 SW<  [ar71xx-spi]
164 root         0 SW   [mtdblock0]
169 root         0 SW   [mtdblock1]
174 root         0 DW   [mtdblock2]
179 root         0 SW   [mtdblock3]
184 root         0 SW   [mtdblock4]
189 root         0 SW   [mtdblock5]
233 root         0 SW   [kworker/0:1]
387 root         0 SW<  [ipolldevd]
415 root         0 SWN  [jffs2_gcd_mtd3]
417 root         0 SW   [flush-mtd-unmap]
432 root      1552 S    {rcS} /bin/sh /etc/init.d/rcS S boot
433 root      1520 S    /sbin/getty 115200 ttyATH0
434 root      1552 S    {rcS} /bin/sh /etc/init.d/rcS S boot
435 root      1520 S    logger -s -p 6 -t sysinit
465 root         0 SW<  [cfg80211]
483 root         0 SW   [khubd]
500 root         0 SW<  [rpciod]
515 root         0 SW<  [nfsiod]
567 root       736 S    /sbin/hotplug2 --override --persistent --set-worker /lib/hotplug2/worker_fork.so --set-rules-file /etc/hotplug2.rules --max-children 1
783 root         0 RW   [kworker/0:2]
846 root      1408 S    hostapd -P /var/run/wifi-phy0.pid -B /var/run/hostapd-phy0.conf
1029 root      1424 S    wpa_supplicant -B -P /var/run/wifi-wlan1.pid -D nl80211 -i wlan1 -c /var/run/wpa_supplicant-wlan1.conf -H /var/run/hostapd-phy0/wlan0
1086 root       912 S    wanlan
1146 nobody     864 S    /usr/sbin/dnsmasq -K -D -y -Z -b -E -s lan -S /lan/ -l /tmp/dhcp.leases -r /tmp/resolv.conf.auto --rebind-localhost-ok --dhcp-range=lan,192.168.222.1,192.168.222.24,255
1227 root      1520 S    udhcpc -t 0 -i eth0 -H SoundMate -b -p /var/run/dhcp-eth0.pid -O rootpath -R
1415 root      1536 S    udhcpc -t 0 -i wlan1 -H SoundMate -b -p /var/run/dhcp-wlan1.pid -O rootpath -R
1478 root      3904 S    lighttpd -f /etc/lighttpd/lighttpd.conf
1481 root      1520 S    telnetd -l /bin/login
1483 root      1120 S    wifi_save
1500 root      1632 S    /usr/sbin/dbus-daemon --system
1508 root      1536 S    -ash
1515 nobody    2144 S    avahi-daemon: running [SoundMate.local]
1557 root      3856 S    /bin/newshair -a MusicBox -m external-avahi -d
1560 root      1552 S    avahi-publish-service E3A17DD59D85@MusicBox _raop._tcp 5002 tp=UDP sm=false ek=1 et=0,1 cn=0,1 ch=2 ss=16 sr=44100 vn=3 txtvers=1 da=true md=0,1,2 pw=false
1579 root      1568 S    {S95done} /bin/sh /etc/rc.common /etc/rc.d/S95done boot
1584 root       896 S    /usr/sbin/updatefw
1645 root       800 D    nrender --friendly-name MusicBox --ip-address 192.168.1.XXX --uuid SM-mac=88a0cc87XXXX
1647 root       912 S    checknren
1650 root      1568 S    {S95done} /bin/sh /etc/rc.common /etc/rc.d/S95done boot
1651 root      1504 D    awk BEGIN{FS="[ \t]+:[ \t]"} /machine/ {print $2} /proc/cpuinfo
1658 root      1520 S    /bin/sh -c uci commit
1659 root       928 D    uci commit
1660 root      1520 D    sh -c ps -w |grep friendly-name | grep -v grep|wc -l
1661 root      1520 R    ps -w
1662 root      1520 S    [grep]
1663 root         0 Z    [grep]
1664 root      1520 R    ps

I'm not sure what nsrender does, does it complement shairtunes or is it a separate tool.

# cat /etc/rc.local

# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.

date -s "2013-01-01 08:00:00"
[ -d /tmp/root ] && {
    ismount=$(mount | grep -c disk)
    if [ "$ismount" -ne "1" ]; then
        for device in $(ls /dev | grep sd); do
            n=$(echo $device | sed -e "s/sd//")
            m=$n
            q=$(echo $n | sed -e "s/[a-z]//")
            if [ -z "$q" ]; then
                q=${m}0
                m=$q
            fi
            n=$(expr substr "$n" 1 1)
            mkdir -p /tmp/mnt/disk-$m
            ntfs-3g -o nls=utf8 /dev/$device /tmp/mnt/disk-$m
            ismount=$(mount | grep -c disk-$m)
            if [ "$ismount" -ne "1" ]; then
                mount -o fmask=000,dmask=000,iocharset=utf8 /dev/$device /tmp/mnt/disk-$m
                ismount=$(mount | grep -c disk-$m)
                if [ "$ismount" -ne "1" ]; then
                    mount -t exfat -o fmask=000,dmask=000,iocharset=utf8 /dev/$device /tmp/mnt/disk-$m
                    ismount=$(mount | grep -c disk-$m)
                    if [ "$ismount" -ne "1" ]; then
                        rm -r /tmp/mnt/disk-${m}
                    fi
                fi
            fi
        done
            
    fi
}
/usr/sbin/updatefw &
brctl addif br-lan eth2
insmod /lib/modules/2.6.39.4/ar7240_i2s.ko
mknod /dev/i2s c 253 0
nrestart
checknren &
exit 0

rc.local script has few interesting thing, first part with if statement is to mount external usb devices. updatefw is obviously firmware upgrade tool which look if new firmware is put on usb and then it updates current firmware. ar7240_i2s kernel driver provides support for audio DAC that is connected over i2s bus to AR9331 SoC.