Thursday, August 4, 2016

TOWL - Telemetry over Opportunistic WiFi Links

Premise & Background:

Can you build a "LoJack" style asset tracking capability using open WiFi hotspots?

The proliferation of cheap, lightweight WiFi embedded ("IoT") devices made me wonder.  The WiFi association stack, DHCP client stack, et al. has to be incredibly lightweight and simple to fit in the firmware on, say, an ESP8266.  If you programmed one to scan, find an AP, associate, get an address, and send a single packet - would it be able to do it fast enough to report its location from a moving vehicle?

[Aside: This is actually something I've wondered about for years, but the IoT chips offered a unique and low-cost way to try it.]

"But wait," you say, "there really aren't that many open APs these days.  Most of them are captive or paywall portals, or at least make you agree to some goofy ToS."

Right, but as has been pointed out multiple times by multiple people all the way back to Dan Kaminsky's DNS tunneling talk in 2004 - hotspots diligently resolve DNS queries.  All you have to do is base32 encode the data you want to send in the hostname of a valid DNS record request, and set up a DNS server for a subdomain (under a domain you own) to catch the queries.

If it works, you get near real-time telemetry over opportunistic WiFi links using DNS recursion!  Think: "low cost LoJack with no data subscriptions".
Digistump Oak ESP8266

I used the Digistump Oak variant of the ESP8266 prototype boards.  I like the fact that it can be flashed OTA, which proves to be useful once the device is deployed in something like an automobile.

In my testing, the complete chain from calling connectAP() to getting the DNS query response typically takes between 3 and 6 seconds, which is certainly fast enough from a slow moving vehicle.

Device Output

The device stores the telemetry in a 16-byte struct consisting of:

struct telem {
  uint32_t tstamp;  // GPS time in ctime format
  int32_t lat;      // Latitude * 1000000
  int32_t lon;      // Longitude * 1000000
  uint8_t spd;      // Speed in MPH
  uint8_t sats;     // Number of GPS satellites received
  uint8_t id;       // Arbitrary number for ACK
  uint8_t mode;     // Object state information
};  // 16 bytes total

The fields are largely self-explanatory.  You'll notice that I used an 8-bit number for speed, so the max speed it will report is 255 MPH.  Also, the 32-bit ctime field means it's vulnerable to the Year 2038 Problem.

The field worth explanation is "mode", which is set as follows:

Value |   Meaning
 255  |  Startup position.
 254  |  Live. Transmitted and ACKed in realtime
 1-n  |  Stored position

The first few position observations are always flagged as mode 255, regardless of whether they're able to be transmitted live or not.  (This way you know where the device started.)

The stored position value is simply the number of 10-second intervals since last transmission modulo the MAX_INTERVAL setting.  This is how the unit "downsamples" the stored waypoint resolution if the buffer fills up.

So... how well does it work?

Here's a plot on OpenStreetMaps of me driving to work one morning.  The blue markers are live (mode 254) and the red markers are stored.

Of note are the blue markers along the freeway.  I've regularly observed this device successfully logging the telemetry data at speeds upward of 60 MPH! 

Technical Details


  • Digistump Oak
  • NMEA serial GPS module (e.g. uBlox or MTK)


The software for the Digistump Oak, as well as a PoC DNS server (in python) to log the output, is available here:

Check the README notes under both subdirectories for build dependencies / config notes.


You'll need to host a custom nameserver on the Internet to receive the DNS queries.  You'll also need to control a domain so that you can designate your DNS query receiver as a subdomain. 

E.g. if you own the domain "", you could designate a server to receive the TOWL queries by creating a NS record for "", pointing at the server you intend to run the catcher on. If said server is at IP address, then that record looks something akin to:


Run the PoC code on the designated server. Be sure to configure both the TOWL devices and the server code for the "" domain name. (See README under each directory for instructions.)

Have fun!

gps tracker dns

Wednesday, July 20, 2016

Script to toggle LEDs on the Netgear Nighthawk AC1900 / R7000

I recently purchased the Netgear Nighthawk AC1900 dual-band router, to go along with my new faster Internet connection.

First off, I'll say- I wasn't a fan of Netgear's past home routers, but this one is quite amazing!  Not only can it keep up with my 1Gbps fiber connection, it also seems to have massively improved the wireless situation in my home over my previous Linksys router.

Anyway, enough of that.  One funny thing about this router, in addition to looking like a 1990s stealth aircraft, is that it's covered with SUPER HIGH INTENSITY WHITE LEDs.  They add to the sleek look, for sure, but you can practically read by them in a dark room.  (!)  Not so great if you happen to sleep in the same room with the router.

Thankfully, the Advanced Setup UI provides a mode to turn off all the LEDs (save for the power LED, which I just stuck a piece of gaffers tape over.)

I like the status LEDs, though, so I thought - wouldn't it be nice to be able to programatically toggle them?

I was hoping I could do it with a simple request from curl, but it turns out you need to auth to the setup page and retrieve a timestamped id token, probably as an anti-CSRF measure (albeit not a bulletproof one.)  So, voila - a quick & dirty python script to toggle the LEDs on the nighthawk router.

Python 2.7
Python requests library

Download link:


# Quick and dirty script to toggle the super-bright LEDs on the Netgear
# Nighthawk R7000 router.  Tested on FW V1.0.5.70_1.1.91.  YMMV.

import requests
import sys

# !!!
# IMPORTANT:  Put your actual router password and router IP address below:
# !!!
ROUTERPASS = 'password'

# First, we have to get a valid timestamp from the settings page:
r = requests.get(ROUTERADDR + 'start.htm',
                 auth=(ROUTERADMIN, ROUTERPASS))
r = requests.get(ROUTERADDR + 'LED_settings.htm',
                 auth=(ROUTERADMIN, ROUTERPASS))
o = r.text.find('led_settings.cgi?id=')
if o == -1:
  if r.text.find('multiLogin') > -1:
    print 'Error: Another browser is currently logged into the router.'
    print 'Invalid response from router:'
    print r.text

o2 = r.text[o:].find('"')

# Next, construct the CGI URL with the returned timestamp
led_url = ROUTERADDR + r.text[o:o+o2]

if len(sys.argv) == 2 and sys.argv[1].upper() == 'OFF':
  data = 'Apply=Apply&led_settings=turn_off&led_now=en_blink'
  data = 'Apply=Apply&led_settings=en_blink&led_now=turn_off'

# Send the magic command:
r =, data=data, auth=(ROUTERADMIN, ROUTERPASS))
print 'Status code: %d' % r.status_code
#print r.text

# Avoid "multiLogin" errors:
r = requests.get(ROUTERADDR + 'LGO_logout.htm', auth=(ROUTERADMIN, ROUTERPASS))

Monday, February 8, 2016

Interactive Modemspeed - SSH like it's 1995.

A couple of years ago I created a simple python program to simulate various modem speeds when you piped a command through it.  Easy, fun, but of limited use.

So, I revisited it and made the read portion non-blocking so that it can be used interactively.  The aim is to have a script I can pipe SSH through to make the remote connection act like it's behind an oldschool modem.

$ ssh remotehost | /path/ speed

Substitute /path/ with the location of the script.
Substitute speed with the speed (in bits per second) you want to experience. 

If you omit speed, you get 300 bits per second.   

Did your friend leave a terminal unlocked?  Why not have some fun?

$ function ssh() { /usr/bin/ssh "$@" | /path/interactive-modemspeed 2400 ;}

(Make sure to put the absolute path to the ssh binary so you don't create a bash fork loop.  :-P)

Now they'll wonder why all their new SSH sessions feel like they're on a BBS in 1989.

Here's the code:


import fcntl
import os
import sys
from time import sleep

def modem(text, speed):
  # Assume speed is in bits/sec, and we're running at 8,N,1
  # So, 10 bits per character.  (1 start, 8 char, 1 stop)
  delay = 1.0 / (speed / 10)  # = seconds per character
  for char in text:

if __name__ == "__main__":
   if len(sys.argv) > 1:
     baud = int(sys.argv[1])
     baud = 300
   # open stdin in non-blocking mode so we can read 1 character at a time
   fd = sys.stdin.fileno()
   fl = fcntl.fcntl(fd, fcntl.F_GETFL)
   fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

   while True:
       c =
       modem(c, baud)
     except IOError:
       continue  # Resource temporarily unavailable

Monday, January 18, 2016

Generic $5 128x64 OLED SPI display with Arduino Pro Micro

I bought a couple of ~$5 generic 128x64 graphic OLED displays on eBay to play with.  It took some trial and error to figure out how to get the Arduino Pro Micro to talk to them, and there's lots of different information on the net about doing so (probably owing to the wide variety of implementations of these displays and generic controller chips.)

This particular one uses the SD1306 controller and is wired for a 4-wire SPI interface.

I used the u8glib library to talk to it.  The "gallery" page on the library repo provides lots of examples of people's various combinations to make these work.  This particular one wasn't shown, but by perusing through the different examples I was able to find a combination that worked.

I'm using the SW SPI function of u8glib, so I don't have to use the ARM chip's native SCK or MOSI pins.  I found that this device will accept data without the CS pin connected (pulled low instead), so I left that out to save an output pin.  (You'd want to use CS if you were trying to use the same SW SPI bus to talk to multiple devices.)

I wired it as shown:

Arduino Pin -> OLED pin
  15 ----> SCK
  14 ----> SDA
  16 ----> RES
  10 ----> DC

Then, in the u8glib "Hello, World" example, I used the following definition:

// SW SPI Com: SCK = 15, MOSI = 14, CS = N/C, A0 = 10, RESET = 16
U8GLIB_SSD1306_128X64 u8g(15, 14, U8G_PIN_NONE, 10, 16);  

Note: The "RESET" pin was the crucial one I was missing in my initial attempts, as it's not defined in the SD1306 example configs in the source.  I had even tried tying the RES line high or low to no avail.

With those four lines, though, it works fine!  I'm not sure what the eBay auction page means by "only needs two I/O pins from Arduino."  Maybe they copied that from the I2C version.  :-P

 - K.C.