06|18 Hacking the RXS-3211

After seeing Hack-a-Day’s post about a cheap IP webcam that runs Linux I couldn’t resist the urge to dig into yet another embedded system. I don’t really have a definite need for something like this, but thought it sounded like a neat piece of hardware to hack around with, so why not?

When I first plugged the camera in, I didn’t see it grab an IP over DHCP and went digging for the Manual (PDF). It boots up as 192.168.2.3 with a webserver on port 80. Default username and password: admin/1234.

After changing the IP to a more reasonable subnet, it functions as a nifty little IP webcam providing either a MJPEG or MPEG4 stream over RTSP. Not bad for $40.

Cracking the case open was a bit difficult, until I realized there was a screw hidden under the label on the back.

Time for hacking! I’ve found at least two potential ways of getting a shell on this thing, with several more theoretically possible. The easiest approach is the one that Hack-a-Day mentioned: wire up a serial console to some empty pads on the board and rock on. There are four pads on the back side of the device (the side without the camera sensor). In the picture below, from left to right: 3.3v, RX, TX, GND. Please excuse my horrible soldering job.

RXS-3211 serial header

For the sake of simplicity, I connected this header up to my Bus Pirate and configured the port as 115200 8e1.


Bus Pirate v3a
Firmware v5.10 (r559) Bootloader v4.1
DEVID:0x0447 REVID:0x3043 (24FJ64GA002 B5)

http://dangerousprototypes.com

HiZ>m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. LCD
9. DIO
x. exit(without change)

(1)>3
Set serial port speed: (bps)
1. 300
2. 1200
3. 2400
4. 4800
5. 9600
6. 19200
7. 38400
8. 57600
9. 115200
10. BRG raw value

(1)>9
Data bits and parity:
1. 8, NONE *default
2. 8, EVEN
3. 8, ODD
4. 9, NONE
(1)>2
Stop bits:
1. 1 *default
2. 2
(1)>1
Receive polarity:
1. Idle 1 *default
2. Idle 0
(1)>1
Select output type:
1. Open drain (H=Hi-Z, L=GND)
2. Normal (H=3.3V, L=GND)

(1)>2
Ready
UART>(1)
UART bridge
Reset to exit
Are you sure? y

Assuming everything’s wired up correctly, you should now be able to watch the board boot up and interact with it on the console.

Welcome to Proboot 1.0
Prolific Technology Inc. 2003-2006

[General]
Version : 1.0.39
Date : 2009/10/26
CPU : ARM FA526
MemSize : 32 MB
Cache : vIbrSLWCaM
IP : 0.0.0.0
MAC : 00:00:00:00:00:00

[OnBoard NOR Flash]
Type : PPI-AMD
ID : 00a8
Sectors : 71
Size : 4MB

[Memery Layout]
Stack : 0x00400000 - 0x003c0000
Shadow : 0x00600000 - 0x006a4f68
Pages : 0x00004000 - 0x00008000
Buffer : 0x00700000 - 0x00800000
Tags : 0x00002000 - 0x00004000

script 0x19802000 8192 2
Running script (Ctrl-C to cancel)

echo "loading compressed kernel"
loading compressed kernel
load 0x20000 0x220000 786432
echo "execute kernel"
execute kernel
exec 0x220000 "root=/dev/norblock/disc0/disc rootfstype=squashfs nor=b:0x320000@0xe0000,c:8192@0x4000,c:40960@0x6000 prolific.keypad=GPIO_IN(11);GPIO_OE(5,6);"
Uncompressing Linux......................................... done, booting the kernel.
Linux version 2.4.19-pl1029 (root@neo) (gcc version 3.3.4) #219 ?| 7?? 29 10:55:30 CST 2010
CPU: Faraday FA526id(wb) revision 1
ICache:16KB enabled, DCache:16KB enabled, BTB support, IDLE support
Machine: Prolific ARM9v4 - PL1029
Prolific arm arch version 1.0.12
On node 0 totalpages: 8192
zone(0): 8192 pages.
zone(1): 0 pages.
zone(2): 0 pages.
Kernel command line: root=/dev/norblock/disc0/disc rootfstype=squashfs nor=b:0x320000@0xe0000,c:8192@0x4000,c:40960@0x6000 prolific.keypad=GPIO_IN(11);GPIO_OE(5,6);
plser console driver v2.0.0
Calibrating delay loop... 147.56 BogoMIPS
Memory: 32MB = 32MB total
Memory: 30948KB available (1064K code, 297K data, 64K init)
Dentry cache hash table entries: 4096 (order: 3, 32768 bytes)
Inode cache hash table entries: 2048 (order: 2, 16384 bytes)
Mount-cache hash table entries: 512 (order: 0, 4096 bytes)
Buffer-cache hash table entries: 1024 (order: 0, 4096 bytes)
Page-cache hash table entries: 8192 (order: 3, 32768 bytes)
POSIX conformance testing by UNIFIX
PCI: Probing PCI hardware on host bus 0.
Linux NET4.0 for Linux 2.4
Based upon Swansea University Computer Society NET3.039
Initializing RT netlink socket
Prolific addr driver v0.0.4
tts/%d0 at MEM 0x1b000400 (irq = 2) is a PLSER
Starting kswapd
devfs: v1.12a (20020514) Richard Gooch (rgooch@atnf.csiro.au)
devfs: boot_options: 0x1
squashfs: version 3.1 (2006/08/19) Phillip Lougher
i2c-core.o: i2c core module version 2.8.1 (20031005)
i2c-dev.o: i2c /dev entries driver module version 2.8.1 (20031005)
Prolific i2c algorithm module v1.2
Initialize Prolific I2C adapter module v1.2.1
found i2c adapter at 0xd9440000 irq 17. Data tranfer clock is 100000Hz
i2c-proc.o version 2.8.1 (20031005)
pty: 256 Unix98 ptys configured
PL-1029 NOR flash driver, version 0.8.4
NOR flash type: ppi-amd 8x8 64x63
NOR: PPI
NOR flash id = 0xa8
nor interrupt 30 registered
Partition check:
norblocka: unknown partition table
RAMDISK driver initialized: 16 RAM disks of 2048K size 1024 blocksize
PPP generic driver version 2.4.2
PPP Deflate Compression module registered
PPP BSD Compression module registered
usb.c: registered new driver usbdevfs
usb.c: registered new driver hub
usb-ohci-pci.c: usb-00:05.0, PCI device 180d:2300
usb-ohci.c: USB OHCI at membase 0xd8400000, IRQ 9
usb.c: new USB bus registered, assigned bus number 1
usb.c: ### @@@ usb_set_address

usb.c: >>> usb_get_device_descriptor

usb.c: >>> usb_get_configuration

Product: USB OHCI Root Hub
SerialNumber: d8400000
hub.c: USB hub found
hub.c: 4 ports detected
Prolific Real-Time Clock Driver version 1.0.0 (2003-04-02)
Prolific keypad driver v1.0.8
PL UART driver version 1.1.0-1 (2007-02-15)
ov7670.o version 1.0.0.0 (20070316) [ fine tune ]
OV7670AttachAdapter() be called
OV7670Detect() be called
Set 166 registers value
NET4: Linux TCP/IP 1.0 for NET4.0
IP Protocols: ICMP, UDP, TCP
IP: routing cache hash table of 512 buckets, 4Kbytes
TCP: Hash tables configured (established 2048 bind 4096)
NET4: Unix domain sockets 1.0/SMP for Linux NET4.0.
NET4: Ethernet Bridge 008 for NET4.0
Fast Floating Point Emulator V0.9 (c) Peter Teichmann.
VFS: Mounted root (squashfs filesystem) readonly.
Mounted devfs on /dev
Freeing init memory: 64K
pl serial only support even parity
MAC address: 00:1F:1F:CE:C8:25
MAC address: 00:1F:1F:CE:C8:25
init started: BusyBox v1.01 (2009.10.29-06:28+0000) multi-call binary
init started: BusyBox v1.01 (2009.10.29-06:28+0000) multi-call binary
Starting pid 10, console /dev/tts/0: '/etc/rc.d/rcS'
F: applet not found
Enable alignment-fixup function...
$Starting tmpfs:---->

$Starting encoder driver module: ---->
Using /lib/modules/plmedia.o
plmedia version 1.3.0
Hello Grabber!
[Grab] : Preallocation memory space 2097152 bytes
Hello Encoder!
Hello PLMD!
Warning: loading plmedia will taint the kernel: non-GPL license - PLGRAB
See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Using /lib/modules/crypt.o
aes interrupt 12 registered
ic3010_ctrl_init...
Using /lib/modules/ic3005_ctrl.o
Warning: loading ic3005_ctrl will taint the kernel: no license
See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Using /lib/modules/rt3070sta.o
Warning: loading rt3070sta will taint the kernel: no license
See http://www.tux.org/lkml/#export-tainted for information about tainted modules
rtusb init --->
usb.c: registered new driver rt2870
SIOCGIFFLAGS: No such device
Using /lib/modules/sundance.o
sundance.c:v1.30b 7-9-2007 Written by Donald Becker

http://www.scyld.com/network/sundance.html

eth0: IC Plus IP100A Fast Ethernet Adapter at 0xc28bf000, 00:00:00:00:00:00, IRQ 5.
eth0: MII PHY found at address 0, status 0x7849 advertising 01e1.

Starting enet...

****************************************************************
Initializing ipc between Spook and Enet....: Success
****************************************************************

: Success
total enet structure = 5693 (163D) checksum:(enet:532F) (flash:532F)

default MAC address: 00 1F 1F CE C8 25
eth0 Link down, 7849.
neo: netdev_open Reset PHY
neo: netdev_open Reset PHY 2
eth0: Promiscuous mode enabled.
device eth0 entered promiscuous mode
Starting pibr0: port 1(eth0) entering learning state
d 48,br0: port 1(eth0) entering forwarding state
br0: topology change detected, propagating
console /dev/tts/0: '/bin/ash'

BusyBox v1.01 (2009.10.29-06:28+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

/ # NTP thread start.. PID:52
udpsock port: 4322
httpd->port:80

link_status->wlg_ifindex: 0
link_status->lan_ifindex: 2
enet_watchdog_init
eNET main task started
Init eth0: Promiscuous mode enabled.
random number generator with value: 505
System startbr0: port 1(eth0) entering disabled state
up [ v1.0 (Jul 29 2010 10:55:12) ]
enet_watchdog_start
klogd thread started
Set gateway (change the routing table)
add 255.255.255.255/255.255.255.255 to routing table for broadcast
embedded av stream server started PID:58

Starting mpeg4 pipe server for Spook
Waiting for a connection from spook... (video)
video device thread started PID:60
motion detect thread started PID:61
audio device thread started PID:62
embedded av stream controller started 63
SO_SNDBUF:131070 SO_SNDLOWAT:1
set audio capture properties
start audio capture ok
stop video capture
stop video device.....
set video capture properties
Enter normal VGA mode...
set webpush fps to 30
set video capture properties done
set motion detection properties
set motion detection properties done
start video capture
success to open digits.bmp file

Set 166 registers value
Opened /dev/pl_grab successfully
uDenominator:30 uNumerator:1
******************************************************
MPEG4 encoding source: 640 X 480
MPEG4 encoding format: 640 X 480
MPEG4 bitrate: 0
MPEG4 FPS: 30
MPEG4 VBR nQuant: 5
Opened /dev/pl_enc successfully (MPEG4)
******************************************************
skip MPEG4 encoder!! Using MD profile instead
Jan 1 00:00:12 spook[65]: listening on control socket /tmp/spook.sock
Jan 1 00:00:12 spook[65]: unable to open /tmp/spook.conf: No such file or directory
killall: spook: no process killed
generate new spook.conf... (MPEG4)
******************************************************
MJPEG encoding source: 640 X 480
MJPEG encoding format: 640 X 480
MJPEG quality: 4
MJPEG FPS: 30
Opened /dev/pl_enc successfully (MJPEG)
******************************************************
******************************************************
MD encoding source: 640 X 480
MD encoding format: 640 X 480
MD FPS: 30
MD VBR nQuant: 5
Opened /dev/pl_enc successfully (MD)
******************************************************
start video capture ok
httpd thread started PID:69
mmap Y[0]
enet agentd started PID:73
This board has no wireless supported.
encode VOS header...
enet
00 00 01 B0 08 00 00 01 B5 89 13 00 00 01 00 00 00 01 20 00 C4 88 80 0F 51 40 43 C1 46 3F 00 00
first read video at 13:54
mmap Y[2]
eth0: Promiscuous mode enabled.
eth0 Link down, 7849.
neo: netdev_open Reset PHY
neo: netdev_open Reset PHY 2
eth0: Promiscuous mode enabled.
eth0: Promiscuous mode enabled.
br0: port 1(eth0) entering learning state
eth0: Promiscuous mode enabled.
br0: port 1(eth0) entering forwarding state
br0: topology change detected, propagating
E-mail schedule system started (PID:51)
FTP schedule system started (PID:50)
mmap Y[1]
Jan 1 00:00:15 spook[78]: listening on control socket /tmp/spook.sock
Jan 1 00:00:15 spook[78]: listening on tcp port 554
Jan 1 00:00:15 spook[78]: pl1029-video: start_block
Jan 1 00:00:15 spook[78]: pl1029-video: set_device
Jan 1 00:00:15 spook[78]: pl1029-video: set_framesize
Jan 1 00:00:15 spook[78]: pl1029-video: set_bitrate
Jan 1 00:00:15 spook[78]: pl1029-video: set_format
Jan 1 00:00:15 spook[78]: pl1029-video: set_framerate_num
Jan 1 00:00:15 spook[78]: pl1029-video: set_output
Jan 1 00:00:15 spook[78]: pl1029-video: end_block
Jan 1 00:00:15 spook[78]: pl1029-video: device: /tmp/mpeg4Stream
Jan 1 00:00:15 spook[78]: pl1029-video: format: 100
Jan 1 00:00:15 spook[78]: pl1029-video: bitrate: 2048
Jan 1 00:00:15 spook[78]: pl1029-video: width: 640
Jan 1 00:00:15 spook[78]: pl1029-video: height: 480
Jan 1 00:00:15 spook[78]: pl1029-video: fps: eth0: Promiscuous mode enabled.
30
Jan 1br0: port 1(eth0) entering disabled state
00:00:15 spook[78]: pl1029-video: gopsize: 30eth0: Promiscuous mode enabled.
eth0 Link down, 7849.
neo: netdev_open Reset PHY
neo: netdev_open Reset PHY 2
eth0: Promiscuous mode enabled.

Jan 1 00:00:eth0: Promiscuous mode enabled.
br0: port 1(eth0) entering learning state
eth0: Promiscuous mode enabled.
15 spbr0: port 1(eth0) entering forwarding state
br0: topology change detected, propagating
ook[78]: pl1029-video: quality: 12
Accepted a new connection from spook... (video)
Jan 1 00:0br0: port 1(eth0) entering disabled state
0:15 spook[78]: pl1029-video: capture_loop
=============== reinit br0: port 1(eth0) entering learning state
libbrbr0: port 1(eth0) entering forwarding state
br0: topology change detected, propagating
idge ================ ...
Jan 1 00Inconsistent of vop_type (i-vop)!!
:00:15 spook[78]: pl1029-video: set_running
Jan 1 00:00:17 spook[78]: waiting for VOS header...
Set gateway (change the routing table)
add 255.255.255.255/255.255.255.255 to routing table for broadcast
encode VOS header...
enet
00 00 01 B0 08 00 00 01 B5 89 13 00 00 01 00 00 00 01 20 00 C4 88 80 0F 51 40 43 C1 46 3F 00 00
Jan 1 00:00:17 spook[78]: pl1029-video: set_running
Terminated
generate new spook.conf... (MPEG4)
send mpeg4 data to spook pipe failed. aborted..
Waiting for a connection from spook... (video)
Inconsistent of vop_type (i-vop)!!
Jan 1 00:00:18 spook[86]: listening on control socket /tmp/spook.sock
Jan 1 00:00:18 spook[86]: listening on tcp port 554
Jan 1 00:00:18 spook[86]: pl1029-video: start_block
Jan 1 00:00:18 spook[86]: pl1029-video: set_device
Jan 1 00:00:18 spook[86]: pl1029-video: set_framesize
Jan 1 00:00:18 spook[86]: pl1029-video: set_bitrate
Jan 1 00:00:18 spook[86]: pl1029-video: set_format
Jan 1 00:00:18 spook[86]: pl1029-video: set_framerate_num
Jan 1 00:00:18 spook[86]: pl1029-video: set_output
Jan 1 00:00:18 spook[86]: pl1029-video: end_block
Jan 1 00:00:18 spook[86]: pl1029-video: device: /tmp/mpeg4Stream
Jan 1 00:00:18 spook[86]: pl1029-video: format: 100
Jan 1 00:00:18 spook[86]: pl1029-video: bitrate: 2048
Jan 1 00:00:18 spook[86]: pl1029-video: width: 640
Jan 1 00:00:18 spook[86]: pl1029-video: height: 480
Jan 1 00:00:18 spook[86]: pl1029-video: fps: 30
Jan 1 00:00:18 spook[86]: pl1029-video: gopsize: 30
Jan 1 00:00:18 spook[86]: pl1029-video: quality: 12
Accepted a new connection from spook... (video)
Jan 1 00:00:18 spook[86]: pl1029-video: set_running
Jan 1 00:00:18 spook[86]: pl1029-video: capture_loop
Jan 1 00:00:18 spook[86]: waiting for VOS header...
Inconsistent of vop_type (i-vop)!!
encode VOS header...
enet
00 00 01 B0 08 00 00 01 B5 89 13 00 00 01 00 00 00 01 20 00 C4 88 80 0F 51 40 43 C1 46 3F 00 00
Jan 1 00:00:19 spook[86]: pl1029-video: set_running
Inconsistent of vop_type (i-vop)!!
enet link_status thread started PID:81
eth0: Promiscuous mode enabled.
Network initial status: WLAN
WLAN was disabled! link status still was LAN
/ #

At this point, you can poke around a bit. The system has a relatively minimal busybox config installed with two processes making up the webcam interface. “/bin/spook” and “/sbin/enet”. Taking a step back, the web server sends a Server header in it’s responses identifying it as a GoAhead web server. This is a small web server designed to be embedded into another process, in this case, spook. The GoAhead web server has a long list of vulnerabilities attributed to it, including a couple good remote buffer overflows. For someone so inclined, this might provide an alternate method of hacking the RXS-3211 without modifying the hardware.

Getting back to the software itself, I noticed some strange behavior if I attempted to use any program to communicate with the network other than spook. After examining dmesg and thinking carefully about how such a low-powered device can provide streaming video at 30fps, I can make an educated guess about how the system works. When the snoop process starts, it maps a shared memory section with the enet process. snoop instructs the hardware to capture frames, encode them as either MPEG4 or MJPEG and write the result into the shared memory area. The enet process then instructs the ethernet hardware to copy the data out of shared memory and send it out to the network. Assuming that this hypothesis is correct, then doing *anything* that attempts to send data to the network while the enet process is copying data directly to the ethernet chip’s buffers will cause the whole network stack to lock up and break all network connectivity. This makes it exceedingly difficult to copy files off the device or interact with it over the network in anything other than the prescribed manner.

Faced with this setback, I started looking toward the bootloader. Astute readers will have noticed that the bootloader pauses for a couple seconds at startup, prompting for Ctrl-C on the serial console. Doing so drops you into a “proboot>” prompt.


proboot> credit
We fight for the honor of our company.
Prolific bootloader - Proboot - Professional bootloader
by Jun Chen, Hugo Leu, Jedy Wei, CC Yen 2003-2006

It seems to be a fairly sane bootloader with some nice options like loading and dumping images over tftp. Unfortunately, proboot does not recognize the PL1023′s network hardware and expects a Realtek 8139 to be connected. I suspect that this is what the extra set of pads on the I/O board are for: an RTL8139 chip and RJ-45 jack for interacting with the bootloader. What’s left on the board is likely a relic of the development cycle.

Faced with spotty network access in userspace and no viable network interface in the bootloader, I decided to dump out the firmware image to the serial console using the “memdump” command. memdump takes a memory address and length and reads the whole thing out in the common hex/ascii dump format. While this format is intended more for human consumption than machines, I was able to turn it into a binary dump by parsing my terminal emulator’s output log with a few Python scripts.

Based on the default boot script, I was able to deduce the locations of the kernel and three sections of the NOR flash, one of which appears to be the root filesystem… I assume the other two flash sections store non-volatile configuration that is preserved between reboots.

Now that I have access to the firmware, I plan to modify the /etc/rc.d/rcS script in the rootfs to not start spook so that I’ll have a chance to interact with the device over the network unhindered. Eventually, I’d like to build an entirely new rootfs image with some sort of support for writing to flash, currently the system is read-only.

Other observations:

  • The reset button does nothing
  • There are configuration and firmware files for at least three different wireless chipsets, implying that the same software may be running on other IP webcams with better hardware.
  • Without networked access to the bootloader, I’ll have to figure out what sort of “Firmware upgrade” image the webserver expects to be uploaded. Rosewill has not released any upgrade image for this device, so I don’t have a whole lot to go on.


/ # cat /proc/cpuinfo
Processor : Faraday FA526id(wb) rev 1 (v4l)
BogoMIPS : 147.56
Features : swp half

Hardware : Prolific ARM9v4 - PL1029
Revision : 0000
Serial : 0000000000000000
/ # cat /proc/gpio
GPIO_0: Disabled.
GPIO_1: Disabled.
GPIO_2: Disabled.
GPIO_3: Disabled.
GPIO_4: Disabled.
GPIO_5: Disabled.
GPIO_6: Disabled.
GPIO_7: Disabled.
GPIO_8: Disabled.
GPIO_9: Disabled.
GPIO_10: Disabled.
GPIO_11: key = '0x00'
GPIO_12: Disabled.
GPIO_13: Disabled.
GPIO_14: Disabled.
GPIO_15: Disabled.
/ # cat /proc/hardware
ROM Ver: 1970/1/1

[Routing Detail]
Source: 24MHz
clk range: 12M~24MHz
multiply_on: pllm
pllm_range: 100M~300MHz
pllout = xclk_in * 8/1 (n/m)
mem src = independ
pci_bridge = enabled

[Divider and Register]
cfg_fdiv: 0 cfg_hdiv: 1 cfg_pdiv: 1
cfg_ediv: 5 cfg_ddiv: 1 cfg_udiv: 3
External PCI: enabled
PCI Deskew: enabled (20M~100MHz)
PCI Drive: 4mA
ccr: 3a95c848
ccr3: 00000111

[Clock Detail]
XCLK_IN: 24000 kHz
PLL_OUT: 192000 kHz
CPU clock: 192000 kHz
MEM clock: 96000 kHz
PCI clock: 96000 kHz
EPCI clock: 32000 kHz
DEV clock: 96000 kHz
USB clock: 48000 kHz

[Device Clock Detail]
NAND: 0 kHz
I2C: 24000 kHz
AC97: 96000 kHz
GPIO: 48000 kHz
IDE1: 0 kHz
SD: 0 kHz
GPIO2: 96000 kHz
UART: 48000 kHz
MS: 0 kHz
JMP4: 96000 kHz
SPI: 12000 kHz
AES: 96000 kHz
NOR: 48000 kHz
/ # cat /proc/pl_grab
Grabbed Frames : 9405
Output Frames : 9405
Dropped Frames : 0
Grabbed fps : 28.67
Output fps : 28.67
Time Stamp Unit : 90000
Last Frame Squence : 9404
Buffer Allocated Size : 524288
Buffer Actual Size : 460800
Buffer Number : 3
Overflow Times : 1
Free List Empty Times : 33
Ready List Empty Times : 0
Free List Size : 1
Ready List Size : 0
Grabbing Index : 1
Frame Interval : 3000
Is grabbing : 1
Grab Mode : Streaming
Using Catched Frame : Off
Preallocated space : 2097152
Used preallocation : 1388544
FG_CNFG : 0x000F0049
FG_STRIDE : 0x00A000A0
FG_HCROP : 0x01400000
FG_VCROP : 0x01E00000

03|22 Advice for wannabe startup employees

Via @bswatson on Twitter:

@synack Can you make any recommendations for devs interested in getting in early at a startup?

Right now, the demand for high quality engineers who are willing and able to work for a startup is very strong… Not a day goes by that I don’t see tweets, emails, and random conversations that start with “Anybody know a good X engineer?” where X is the latest must-have technology. However, sometimes it is neither simple or prudent to approach strangers and shove your resume in their face. Here are few things you can do to make your search for an exciting work environment a bit easier.

Don’t trust recruiters

Specifically, recruiting firms and contract recruiters that have no loyalty or stake in the company they’re recruiting for. I’m not saying that these people don’t represent amazing companies, but I’ve found that their motivations are mainly focused on getting the commission.

Example: A firm in San Francisco asked me to come in for an interview. After keeping me waiting for over an hour, they spent five minutes reading my resume, then showed me two potential positions that I was completely unqualified for and uninterested in. I didn’t hear from them again until several months later, when the very same recruiter had no prior knowledge of me or the positions I had already rejected. I’ve had very similar experiences with firms like this at least three times.

Bottom line: This process turned out to be a massive waste of time that could’ve been spent chasing down job leads that I had already vetted.

Meetups are great

“They say it’s all about who you know, and I know crackle!”

-Mitch Hedberg
Startup and tech events, like the regularly scheduled GitHub meetups, are potentially the best place to meet founders, employees, investors, and just plain cool people. With an open mind and some booze money, you can learn an amazing amount about what local startups are working on and what sorts of problems they’re encountering. Stick around long enough, and somebody’s sure to say something like “We’re looking for a good JavaScript/iPhone/Android guru, know anybody?” Even if you don’t, remember who’s looking for what. If you can help out with another startup’s recruiting, that karma can go a long way in your own job search.

Social media is your friend

Yes, it’s a buzzword, but that doesn’t make it irrelevant. Networks like Twitter and Facebook have a ton of power in finding startups that are doing cool things. Figure out who the thought leaders in your areas of expertise are, follow them, and maybe even start a conversation. Keep an eye on who and what they’re talking about… Sooner or later, you’ll see a new company or idea pop up. Find out if there’s an IRC channel and get friendly with it’s residents. If nothing else, you’ll probably learn something.

Write code

This one seems to trip a lot of people up. If you’re looking for a job as a coder, write some code and open source it! I think Eric S. Raymond put it best,

“Every good work of software starts by scratching a developer’s personal itch.”

That is, write something that helps you. A database to organize your music library, a driver for some obscure piece of hardware, a parser for freely available weather data, etc. Find something that’s interesting to you and do it. Don’t be afraid to experiment, this is one of the only places where you can write anything you want, any way you want. Use bleeding edge tools, libraries, and services and get familiar with them. Eventually, you’ll meet somebody that uses the same combination of tools and needs a developer to maintain them.

Do your own thing

If you’re having trouble finding a company that feels like a good fit for you, you might have the curse of the entrepreneur. If you’re one of these poor souls with the insatiable urge to work long, thankless hours to make your idea fly, I bow to you. Most of the above still applies in this case, but rather than poking and prodding people for a job, you’ll be asking for their opinion on your idea. Build a prototype as fast as possible and show it to some people. Listen to them. Iterate. Share it with more people. If it’s a good idea, it’ll snowball into something amazing. If not, pray that you grasp the concept of failure before you lose all of your hair. Lather, rinse, repeat.

11|16 Using TCP sockets in Python

It seems like there are a lot of people confused about how to make correct use of the BSD sockets interface without having data disappear in the middle of a session. For building servers, you can make use of the SocketServer module to hide some of the annoyances, but for clients you've either got to deal with sockets or use a third party library, which never ends up being as fun as it sounds.

We'll start with a simple example... Open a socket, send a string, then close it.

from socket import socket

sock = socket()
sock.connect(('1.2.3.4', 1234))
sock.send('Hello world!\n')
sock.close()

This looks like a pretty simple example, but it exemplifies the pitfalls of sockets programming fairly well. The first problem that becomes apparent is that while the socket() constructor defaults to AF_INET, SOCK_STREAM (IPv4 TCP socket) arguments now, this may not be true in the future. The most likely change would be AF_INET6 for IPv6 support. While this call probably won't fail on an IPv6 native system, the IP address passed to the connect method would end up being in the wrong format. The simple solution is to pass DNS names rather than IP addresses and ensure that you catch any exceptions that might arise from nonexistant names.

This leads to the next problem: exception handling. The socket constructor could throw an exception if the system encounters an "out of FDs" condition. While this is fairly uncommon, you'll want to catch it to make your code more robust. The connect method could throw an exception for a number of reasons. If the IP or hostname you pass in cannot be parsed or resolved, or if all available source ports have been exhausted (extremely unlikely, requires at least 65536 established connections). The send method could fail if the connection has been closed or interrupted in some manner, although it's more likely that it simply wouldn't send data. Finally, the close method will only fail if the socket has already been closed. In our example above, there's no other way the socket object could be closed, so we really don't need to worry much about that. Additionally, this line may even be extraneous because Python closes sockets upon garbage collection. But it's best to include it for correctness.

Sending data can be a bit tricky, especially if you're trying to send a lot of it. The socket's send() method is not guaranteed to send all of the data you pass it. Instead, it returns the number of bytes that were actually sent and expects your application to handle retransmission of the unsent portion. However, Python provides a convenience method called sendall() that makes sure all of your data is sent before returning. The right way:

sock.sendall('Hello world\n')

If, for some reason, you need to know exactly what bytes were sent, even under an error condition, you can also do it the old-fashioned way:

buffer = 'Hello world\n'
while buffer:
	bytes = sock.send(buffer)
	buffer = buffer[bytes:]

This approach can be a bit inefficient memory-wise, but will allow you to access the unsent portion of buffer if sock.send throws an exception.

Time to try receiving some data! There are many ways to handle incoming data, depending on your application's needs. For now, I'll illustrate how you might handle a common line-based protocol.

def readlines(sock, recv_buffer=4096, delim='\n'):
	buffer = ''
	data = True
	while data:
		data = sock.recv(recv_buffer)
		buffer += data

		while buffer.find(delim) != -1:
			line, buffer = buffer.split('\n', 1)
			yield line
	return

This method will continuously call recv() until the connection is closed, and supply a generator interface to the caller so that implementing your protocol becomes as simple as:

for line in readlines(sock):
	# Do something with a line of data

The socket object also provides a makefile() method that returns a file object based on the socket's file descriptor, allowing implementations like this:

for line in sock.makefile('r'):
	# Do something with a line of data

The makefile approach is likely more efficient and will perform better, but may not be as compatible on multiple platforms and does not give you as much control over the recv buffer size and delimiter.

Unfortunately, recv() is a blocking call. If you'd like your program to continue doing other things while waiting for data, you have several choices: call setblocking(False) on your socket, causing recv to throw an exception if there's no data available, or use the select module to occasionally poll the socket for data, or make use of platform-specific calls like epoll and kqueue, or just handle sockets in a separate thread or process. It all depends on what your application needs. Let's take a look at a simple select() call, as most of the other methods are variants of this.

from select import select
from socket import socket

sock = socket()
sock.connect(('1.2.3.4', 1234))

while True:
	readable, writable, exceptional = select([sock], [], [], timeout=0)
	if readable:
		data = sock.recv(4096)
		# Handle the incoming data
	# Continue other tasks here

In this case, we're only using select to check the status of a single socket, but as you can see, it supports polling lists of sockets for readable, writable, and exception states. The return value is a three item tuple, containing any sockets that were found to be in the given states. Once the data is cleared from a socket, you can continue to perform other tasks in your application, eventually looping around to the select() call to poll for more data. This is an extremely efficient way to handle a large number of connections and use often used in implementing servers in order to avoid having to spawn new processes or threads for each connection, further consuming system resources.

I hope this post has cleared up some of the confusion around sockets programming. If you have questions or comments, don't be afraid to let me know.

11|03 Using scapy without root privileges

I've been working on some scripts that interact with SNMP devices and after much research and frustration settled on the fact that all of the SNMP libraries available in Python can do everything. The NetSNMP bindings are incomplete, the PySNMP API changes every few months, and both are poorly documented. For these reasons, I started using Scapy to create and parse SNMP packets.

It's not terribly difficult to get some useful code with scapy and if you take a close look, the scapy source often provides concrete examples of what you're trying to do. For the purposes of this article, let's try writing an SNMP walk script.

from scapy import *

def walk(ip, community, oid_prefix):
    nextoid = oid_prefix
	while nextoid.startswith(oid_prefix):
		p = IP(dst=ip)/UDP(sport=RandShort())/SNMP(community=community, PDU=SNMPnext(varbindlist=[SNMPvarbind(oid=nextoid)]))
		r = sr1(p)
		oid = r[SNMPvarbind].oid.val
		if oid.startswith(oid_prefix):
			yield (oid, r[SNMPvarbind].value)
		else:
			break
		nextoid = oid

This is mostly just a re-implementation of scapy's builtin snmpwalk() method, except that it doesn't traverse the tree infinitely. For the most part, this code works. However, it requires root privileges to run because scapy uses raw sockets to send packets and pcap to receive them. Clearly this is not ideal.

The simplest workaround I've found (with help from Jordan) is to create a regular socket object in python, then use scapy to generate and parse the payloads.

from scapy import *
from socket import socket, AF_INET, SOCK_DGRAM

def walk(ip, community, oid_prefix):
	sock = socket(AF_INET, SOCK_DGRAM)
	sock.connect((ip, 161))

	nextoid = oid_prefix
	while nextoid.startswith(oid_prefix):
		p = SNMP(community=community, PDU=SNMPnext(varbindlist=[SNMPvarbind(oid=nextoid)]))
		buf = str(p)
		while buf:
			bytes = sock.send(buf)
			buf = buf[bytes:]

		r = SNMP(sock.recv(4096))
		oid = r[SNMPvarbind].oid.val
		if oid.startswith(oid_prefix):
			yield (oid, r[SNMPvarbind].value)
		else:
			break

By using the standard BSD sockets approach, you can avoid the requirement to have root privileges by giving up control of the IP and transport layers to the OS. This should work equally well for SOCK_STREAM (TCP) and SOCK_DGRAM (UDP) socket types. This approach appears to be much faster than scapy's native pcap based method, because pcap isn't parsing every packet on the interface to find the responses.

07|13 Implementing HTTP Live Streaming

Apple's recently released HTTP Live Streaming provides a clear, concise method for streaming audio and video content to browsers and mobile devices without the need for proprietary formats or browser plugins (I'm looking at you, Adobe). Instead, Apple's spec relies upon already common and relatively open formats.

The reference for implementing this functionality is Apple's HTTP Live Streaming Overview. Please read this document before continuing. I'll wait.

Right, so the jist is that you need to serve an m3u8 playlist containing metadata and a pointer to a URL containing a valid MPEG2 transport stream. As my preferred language is Python and the general convention for creating web services in Python is to use WSGI (PEP 333) we'll start with a basic WSGI application.

from flup.server.fcgi import WSGIServer
from threading import Thread
from socket import socket
from select import select
from Queue import Queue
import re

class LiveHTTPServer(object):
	def __init__(self):
		self.urls = [
			('^/stream.m3u8$', self.playlist),
			('^/stream.ts$', self.stream),
		]
		self.urls = [(re.compile(pattern), func) for pattern, func in urls]
		self.queues = []

	def __call__(self, environ, start_response):
		for pattern, func in self.urls:
			match = pattern.match(environ['PATH_INFO'])
			if match:
				return func(start_response, match)
		start_response('404 Not Found', [('Content-type', 'text/plain')])
		return ['404 Not Found']

So, there's not too much magic here. When the server object is instantiated, it compiles a list of regular expressions and maps them to instance methods. The WSGI server will call __call__ which attempts to match each of the regexes on the path of the request, calling the associated method if matched or sending a 404 response if not. Note that Ian Bicking's WebOb library is a much simpler way to perform a lot of these tasks, but doesn't provide an easy way to send chunked responses, which are necessary for the MPEG2 transport stream. Just ignore all the extra imports for now, we'll get to them in a minute.

	def playlist(self, start_response, match):
		start_response('200 OK', [('Content-type', 'application/x-mpegURL')])
		return ['''#EXTM3U
#EXTINF:10,
http://video.example.org/stream.ts
#EXT-X-ENDLIST''']

This method implementes the /stream.m3u8 response as required my Apple's spec. The M3U standard says that the EXTINF attribute should have a value of -1 for ongoing streams or those of unknown length, but the iPhone rejected the playlist given anything other than a positive integer in this field.

	def stream(self, start_response, match):
		start_response('200 OK', [('Content-type', 'video/MP2T')])
		q = Queue()
		self.queues.append(q)
		while True:
			try:
				yield q.get()
			except:
				if q in self.queues:
					self.queues.remove(q)
				return

This is where the tricky part actually happens. We create a Queue that will be filled with the MPEG2 data from another thread and start blocking on it, passing the data as a chunked response as soon as it's available. If anything goes wrong (eg. client disconnect) then we remove this stream's queue from the list and return. If this server were to handle a large number of clients, we might want to set a max queue size to avoid filling up memory with data destined for a slow or unresponsive client. It might also be useful to perform some locking on the queues list, to avoid contention between threads. I'll leave that as an exercise for the reader.

def input_loop(app):
	sock = socket()
	sock.bind(('', 9999))
	sock.listen(1)
	while True:
		print 'Waiting for input stream'
		sd, addr = sock.accept()
		print 'Accepted input stream from', addr
		data = True
		while data:
			readable = select([sd], [], [], 0.1)[0]
			for s in readable:
				data = s.recv(1024)
				if not data:
					break
				for q in app.queues:
					q.put(data)
		print 'Lost input stream from', addr

This method serves as the feeder for all of the client queues. It listens for a single connection on port 9999 and puts any received data into all available client queues. If the feeder stream is lost, it will go back to waiting for a new connection.

if __name__ == '__main__':
	app = LiveHTTPServer()
	server = WSGIServer(app, bindAddress=('', 9998))

	t1 = Thread(target=input_loop, args=[app])
	t1.setDaemon(True)
	t1.start()

	server.run()

Finally we tie it all together by instantiating the WSGI application and server and starting a separate thread for the input loop. The flup.server.fcgi.WSGIServer class will act as a FastCGI server that can act as a backend for any number of web servers. If you'd rather not use FastCGI, you should be able to drop in any other WSGI server as long as it supports multiple concurrent requests, otherwise any client after the first will just block waiting for the transport stream.

For my application, I used gstreamer to connect to the input socket and provide an MPEG2 transport stream. This is trivial to do using gst-launch assuming you've got the proper plugins installed.

gst-launch alsasrc device=hw:0,4 ! ffenc_libmp3lame ! ffmux_mpegts ! tcpclientsink host=video.example.org port=9999