05|15 Deploying code with packages

Startups iterate quickly. As a devops professional (apparently that's what they call me now), I've found that the ability to turn over code deploys, new services, and configuration changes very fast is critical to keeping your customers (the engineering team) happy. This process is generally referred to as Continuous Integration or CI. A lot of new tools have popped up over the last few years to get code from VCS to prod servers faster along the lines of Capistrano, Fabric, and... probably some other stuff. These tools are great for creating quick-and-dirty, reproducible-ish deployments without a ton of thought. However, this approach is completely wrong.

There is quite a bit of prior art in the area of code deployment in the form of operating system packages. Everything on a fresh install of a server probably came from a system package. Kernels, compilers, editors, network utilities, were all packaged by the OS maintainers and installed by an automated system. Your code is not special, so why treat it's deployment specially?

The problem with OS packaging is that it's a bit of an arcane art. Systems like RPM, Apt, and Ebuild are fraught with outdated documentation, links to dead blogs, buried mailing list posts, and "just ask the dude on IRC, he's pretty cool". But you can't ignore that all of these systems have essentially solved every type of code deployment problem you're likely to face, and as lazy devops/sysadmin people, we hate reinventing the wheel, so here we go.

For the sake of brevity, sanity, and inanity, I'm going to assume that you're deploying a Python web app in a Git repo and deploying to a recent Debian or Ubuntu system. Our goal is to be able to install a package using Puppet, Chef, CFengine, or something else that puts all of your code in a standard location every time, on every server.

Ok, the basics. Debian packages are .deb files. A .deb file is basically a compressed tar archive containing a built binary of your app with some extra metadata about versioning and dependencies. The dpkg-deb utility allows us to examine and unpack .deb files. Take a look at an existing package's metadata with dpkg-deb -I /var/cache/apt/archives/something_1.2.3-1.deb. Anytime you apt-get install a package, it caches the downloaded archive in /var/cache/apt/archives. If you've installed a lot of things, you'll have a lot of .deb files here. Feel free to poke around, take a look at man dpkg-deb for more options and tools for playing with .deb archives.

So, why can't we just pack up our source directory into a .deb file and call it a day? That'll get our files onto the server, and it'll work the same every time, right? Sure, but then you'd be skipping a lot of the really useful and important parts of the Debian packaging system. As a rule, nothing makes it into upstream Debian unless it can be built from a special type of package called a source package. Source packages provide all of the instructions, dependencies, and versioning information required for the build system to generate a .deb for (theoretically) any architecture or distribution using Debian-style packaging from nothing but source code.

Great, so how do you create a source package? First, you'll need some tools.

sudo apt-get install devscripts pbuilder git-buildpackage dh-make apt-utils

Ok, so what are these things? pbuilder will create a chroot jail (google it, I'll wait) around a completely clean and sterile, minimal Debian install, and build your package inside the jail using a bunch of tools, rather than directly on your system with god-knows-what installed on it. This ensures that your final built package is reproducible, that some dude in Siberia that wants to hack on your code or port it to a different system gets the same resulting binaries that you did. git-buildpackage wraps around pbuilder to provide intelligent management of package sources within a git repository, and dh-make generates boilerplate for new source packages.

Let's start packaging. This is a Python app, so we'll package it the standard Python way, using distutils or setuptools. A simplistic setup.py is enough to get started: http://docs.python.org/distutils/introduction.html#a-simple-example

The Debian maintainers have spent a LOT of time figuring out the best way to get Python code onto a system in sys.path without resorting to patching every upstream package drastically. All you need to do to get a source package is to add a debian/ directory at the root of your repository. Of course, there are already several tools that will setup the boilerplate debian/ for you, the most common of which is called dh_make. There are other similar tools specific to different languages, feel free to Google them.

~/myfirstwebapp-0.1 $ export DEBEMAIL="jeremy@example.com"
~/myfirstwebapp-0.1 $ export DEBFULLNAME="Jeremy Grosser"
~/myfirstwebapp-0.1 $ dh_make --createorig
~/myfirstwebapp-0.1 $ rm debian/*.{ex,EX}

First, we set a couple environment variables to tell the scripts what to fill in for the maintainer and author fields. You probably want to just put this in your .bashrc. dh_make is kinda old and janky, so your source directory has to be named - or it'll whine about it. You also have to pass --createorig to generate an .orig.tar.gz (more on this later), which is pretty irrelevant when using git-buildpackage, but again we do it to keep dh_make from complaining. When dh_make asks you what kind of package to create, just type "s" and press enter for now, the other types are designed for different packaging scenarios, like kernel modules. You may want to try CDBS if you're packaging an Ant library or some other standardish build scripts. We also deleted a bunch of the example files that dh_make created because for simple packages they're completely unnecessary. Feel free to browse around the examples if you've got more complicated projects in mind, they are well commented.

At this point, you have something vaguely resembling this:

~/myfirstwebapp-0.1 $ ls -1 debian/
changelog
compat
control
copyright
docs
README.Debian
README.source
rules
source

Of these, changelog, compat, control, and rules are absolutely required. The rest are technically optional, but you probably don't want to get rid of them.

debian/changelog is exactly what it sounds like, a changelog of what you've done with this source package. Each section of this file represents a package version. Note that all of the spacing and formatting in this file is very specific and your build will break if you change spaces to tabs, for example. Let's take a closer look at the version number: (0.1-1). Version numbers in packages hold a lot of meaning to the system. The first part 0.1 is the upstream package version. If you had downloaded this package from some website, this is the upstream author's version. The second part -1 is the Debian version. Occasionally it's useful to be able to revise the packaging metadata or build scripts for a package before the upstream maintainer has tagged a new release. In those cases, this number simply gets incremented. There are a lot of different ways to represent a version here, they're all enumerated in the completely difficult to comprehend Debian Policy Manual, section 5.6.12 (http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version). All you really need to know is that a version with a "-" in it came from an upstream source (eg. you didn't write it), and anything that's just a version number like "2.5.1" with no "-" part, is known as a native package. In most cases, you'll wanna stick with a non-native package and keep the debian version separate from the upstream version.

debian/compat basically just tells apt that you're using a not ancient spec for package layout. You will probably never need to change this.

debian/control provides a lot of the metadata that's used for managing how your package appears to the user and the rest of the system. Let's dive a bit deeper...

Source: myfirstwebapp
Section: unknown
Priority: extra
Maintainer: Jeremy Grosser <jeremy@example.com>
Build-Depends: debhelper (>= 8.0.0)
Standards-Version: 3.9.2
Homepage: <insert the upstream URL, if relevant>
#Vcs-Git: git://git.debian.org/collab-maint/myfirstwebapp.git
#Vcs-Browser: http://git.debian.org/?p=collab-maint/myfirstwebapp.git;a=summary

Package: myfirstwebapp
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: <insert up to 60 chars description>
 <insert long description, indented with spaces>

As you can see, there are two sections here. The first one describes the source package and the dependencies needed to build it, where the second one describes the resulting binary package and dependencies required to install it. Let me repeat that: there are two dependency lists, one for building and one for installing. The rest of the fields here are fairly self-explanatory and some are even optional (but highly recommended). There are a lot of different things that you can specify in the control file, there's a whole section dedicated to it in the Debian Policy Manual. http://www.debian.org/doc/debian-policy/ch-controlfields.html

So what belongs in your Build-Depends list? debhelper (>= 8.0.0) is listed by default, because you'll basically always want it. Debhelper gives you all kinds of useful shortcuts and anything prefixed with dh_ probably comes from here. Yeah, you want that, it makes your life easier. We're building a python package, so let's add... python! Amazing how that works. If your package had C extensions, you'd also want to include python-all-dev in this list. If you're using setuptools, you'll need that too, it's called python-setuptools. Ideally you'd also specify a minimum version number for each of these things (except python, but that's a whole can of worms, just leave it without a version). I tend to leave explicit versions off of the Build-Depends while getting a new package working, then go back and add them later, once I know exactly what I need and what's available.

In the binary package section, we don't need to change a lot. Unless you're building C extensions or have some other dependency on x86 architecture, change that to "all", which causes the build system to create one binary package that can be installed on any system, like a home router with an ARM chip, for example. If you left this as "any", you'd have to rebuild the binary for each target architecture.

It's worth noting, your binary package name does not have to be the same as your source package name. It's actually fairly common for a source package to be something like "sqlalchemy" that creates a binary named "python-sqlalchemy". In the case of Python packages, the convention is that libraries and module are prefixed with "python-" where services and tools that just happen to be written in Python aren't prefixed.

A single source package can define several binary packages. For example, the "sqlalchemy" source package creates a "python-sqlalchemy-doc" binary, so that you can install the docs separately. Multiple binary packages are outside the scope of this document.

At this point, you should have something like this:

Source: myfirstwebapp
Section: misc
Priority: extra
Maintainer: Jeremy Grosser <jeremy@example.com>
Build-Depends: debhelper (>= 8.0.0), python
Standards-Version: 3.9.2
Homepage: http://example.com/dist/myfirstwebapp-latest.tar.gz
Vcs-Git: https://github.com/synack/myfirstwebapp.git
Vcs-Browser: https://github.com/synack/myfirstwebapp

Package: myfirstwebapp
Architecture: all
Depends: ${shlibs:Depends}, ${misc:Depends}, python-bottle, python-jinja2
Description: My first web app!
 There are many like it, but this one is mine. Accessories sold separately, batteries not included.

debian/copyright lists out who maintains this code, who wrote it, who owns the rights to it, how they've licensed it, etc, etc. Technically you don't need it, but somebody somewhere on the internet cares about this sort of thing. If you ignore it, prepare for an onslaught of trolls.

debian/docs is simply a list of documentation files that come with your package. They'll get installed somewhere under /usr/share/doc and nobody will ever read them before emailing you. How sad. In most cases, you'll just put something like "README.txt" in here and call it a day.

debian/README.Debian and debian/README.source are where you'd put any notes specific to the packaging of this application. If you don't have anything interesting to say, just delete them.

debian/rules is a Makefile that needs to support a bunch of different targets like "build" and "install". This is where the real work of the package comes in. The build target needs to do any sort of compiling, linking, and whatnot to turn your code into something useful, then the install target comes along and lays it all out under debian/tmp/ as close to the FHS standard (http://www.pathname.com/fhs/) as possible. The good news is that you don't have to write any of that! Debhelper is here to... uh... help! The example rules file generated by dh_make is basically all you need:

#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.

# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1

%:
    dh $@

With one slight addition, this will work perfectly for us. Change that last line to dh --with=python2 $@ and you're done. This tells debhelper that there's a setup.py in the root of your source package and it behaves as expected, just like that.

debian/source/format hints to the build system whether this is a native or non-native package.

Native: 1.0

Non-native: 3.0 (quilt)

quilt is a patching system that allows your debian packaging to apply patches to the upstream tarball before building it. We're not using it here, but you have to say that anyway. If you need to edit something from upstream and you can't get a patch submitted there, you can create a debian/patches directory with a bunch of patch files in it, and the build scripts will take care of getting that all squared away during the build. If you need this, go read the quilt manpages.

That's all. You now have a basic Debian source package. As we're managing this repo with git-buildpackage, there are some conventions for where to commit it. The ideal case is that you have an upstream branch and a master branch, where upstream is simply a copy of the latest upstream release, and master has the debian/ directory added to it. When a new upstream release comes out, you merge it into the upstream branch, then merge that to master, update debian/changelog (using git-dch or debchange), make sure the dependencies in debian/control are still accurate, and commit to master. This whole process becomes quite a bit easier if the upstream package maintainer also uses git, or if you use a tool like git-import-orig to pull in new release tarballs. It's good practice to also tag each upstream release, as well as your debian package releases. git-dch and git-import-orig will do this for you, given the right arguments.

We're going to be setting up a brand new pbuilder environment, which means building a new tarball that represents a clean debian install for our architecture and distribution. Every time a build runs, it'll untar this into a chroot jail and run the build there.

pbuilder create

This command will take a while and download all of the packages necessary to build a minimal debian system using a tool called debootstrap. If you wanted to build for a different architecture or distribution than the one you're currently runnning, you'd edit /etc/pbuilderrc or ~/.pbuilderrc to point it at the right mirror, dist, tarball location, build result location, etc, etc. The pbuilderrc(5) lists all of the options you can use there. For now, let's just stick with the defaults.

sudo git-buildpackage --git-builder='pdebuild --debsign-k jeremy@example.com --auto-debsign'

This kicks off the building of our package, using the sources in the current working directory. It needs root permissions in order to create the chroot jail. chroot is a privileged syscall. It goes through a lot of different stages, roughly:

  1. Runs debian/rules clean to make sure our source directory isn't dirty
  2. Create a source package consisting of a .dsc, .debian.tar.gz, .orig.tar.gz, and .changes files from our upstream and master branches.
  3. Setup a new chroot jail and untar the base tarball into it
  4. Create and install a virtual package that installs all of our Build-Depends into the jail
  5. Copies our source package into the jail
  6. Runs debuild inside the jail, which calls dpkg-buildpackage. The manpages for these scripts tell you more about what they do. All you need to know is that they parse debian/control and debian/changelog, call your rules targets and spit out a .deb
  7. Signs the resulting package with your GPG key, identified by the debsign-k argument to pdebuild. You may be prompted to enter your key's passphrase at this point.

If you're lucky, you waited a while, saw a lot of text scroll by, and the build finished successfully. Your built package is now in /var/cache/pbuilder/result

$ ls -1 /var/cache/pbuilder/result/myfirstwebapp_0.1*
myfirstwebapp_0.1-1_all.deb
myfirstwebapp_0.1-1_amd64.changes
myfirstwebapp_0.1-1.debian.tar.gz
myfirstwebapp_0.1-1.dsc
myfirstwebapp_0.1.orig.tar.gz

Pretty awesome, right? Now you just need a Debian repository to upload it to.

A Debian package repository is nothing more than a bunch of files arranged in a specific way, with a files like Packages and Release that tell apt what versions of what packages are available and where to find the .deb files to download. You can browse around most official Debian distributions to get a feel for how a large repo is laid out: http://ftp.us.debian.org/debian/. For our purposes, let's keep it a bit simpler. We're just going to generate a Packages and Release file in the same directory as our binaries.

mkdir -p /var/www/mybuntu
cd /var/www/mybuntu
cp /var/cache/pbuilder/result/myfirstwebapp_0.1* .
apt-ftparchive packages >Packages
apt-ftparchive release >Release
gpg --yes --abs -u jeremy@example.com -o Release.gpg Release

You'll need to run this (everything except the mkdir -p part) every time you add a new package to the repository. It's probably a good idea to write a script so you don't forget all that stuff. apt-ftparchive supports much more complicated layouts and configuration files, I encourage you to read the manpages for that so you can do clever things like manage multiple distributions (separating production and development packages is really handy).

Now you just need to add this apt repository to your clients. Edit /etc/apt/sources.list.d/mybuntu.list on some other server or even your repository machine, if so inclined.

$ cat /etc/apt/sources.list.d/mybuntu.list
deb http://packages.example.com/mybuntu/ ./
$ sudo apt-get update
$ sudo apt-get install myfirstwebapp

Ta da! Your webapp is now installed in the python path! Any scripts you defined in your setup.py are in /usr/bin, and everything is happy and reproducible. You can build new releases of your package, put them in the apt repository and now they're available to all of your servers. Combine this with a config management tool like Puppet where you can do things like ensure => latest and your code gets updated on every puppet run. Need to roll back code because you broke everything? apt-get install myfirstwebapp=0.1-1 or just remove it completely: apt-get remove myfirstwebapp

These tools fit quite nicely with existing CI tools like Jenkins, allowing you to do automated testing, building, and deployment of a package every time somebody commits to master.

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.