To Kill -9 a Drone, Part 2

July 10, 2015

This post first appeared on the Big Nerd Ranch blog.

Last time, we took a tour of TTYs and serial connections to shell into the Linux-powered Parrot Rolling Spider quadcopter. This time, we’ll telnet in over Bluetooth.

Before stumbling upon the USB method for shelling into my drone, I noticed that my drone didn’t shut down after 60 seconds if a Bluetooth device was connected. A fleeting glance at /etc/init.d/rcS revealed calls to telnetd and BLEproxy.

telnet opens a shell session over TCP/IP, usually over a WiFi or Ethernet connection. However, the Rolling Spider is not equipped with WiFi, so why the references to networking? I hypothesized that a lot of the mini-drone's software is based on the WiFi-equipped AR.Drone, which receives flight commands over UDP/TCP.

To keep the software reusable on the Rolling Spider, BLEproxy might set up a bnep network interface, which would allow me to control to the Rolling Spider on 192.168.1.1, just like the AR.Drone. To that end, I'll establish a PAN network with the drone so that I can open UDP/TCP connections through the bnep interface. Since telnetd is listening on port 23, I should be able shell to the drone over Bluetooth.

But first things first: I need a computer with Bluetooth set up.

Bluetooth on OS X

Like most of my colleagues, I use a Mac. The BSD flavor of UNIX is great for day-to-day development, but the tooling differs drastically from a Linux stack.

Bluetooth is no exception: on Mac, there are few (i.e., zilch) terminal utilities for working with OS X’s Bluetooth stack. Linux, however, is well-stocked with open-source tools to configure Bluetooth and network interfaces, thanks to the BlueZ stack. The generous assemblage of blog and forum posts is especially crucial for wading through nondescript errors.

Linux VM #1: Debian

Rather than build a Linux computer from scratch, I’ll run a virtual machine with Virtual Box. Still, it takes a couple hours to spin up the machine, install the OS and install guest additions. Maybe some scripts could save me some time?

Enter Vagrant: it makes spinning up a VM a snap by downloading and setting up common VM configurations for you. Most folks use it to set up a consistent staging environment, but it also works great for quickly setting up a Linux box with shell access.

There are plenty of VM templates to choose from; for entirely arbitrary reasons, I decide to spin up a Debian 7.6 machine:

$ vagrant init chef/debian-7.6
$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Box 'chef/debian-7.6' could not be found. Attempting to find and install...
    default: Box Provider: virtualbox
    default: Box Version: >= 0
==> default: Loading metadata for box 'chef/debian-7.6'
    default: URL: https://atlas.hashicorp.com/chef/debian-7.6
==> default: Adding box 'chef/debian-7.6' (v1.0.0) for provider: virtualbox
    default: Downloading: https://atlas.hashicorp.com/chef/boxes/debian-7.6/versions/1.0.0/providers/virtualbox.box
==> default: Successfully added box 'chef/debian-7.6' (v1.0.0) for 'virtualbox'!
==> default: Importing base box 'chef/debian-7.6'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'chef/debian-7.6' is up to date...
==> default: Setting the name of the VM: drone_default_1435939856993_21528
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 => 2222 (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default:
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default:
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if its present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Mounting shared folders...

$ vagrant halt
==> default: Attempting graceful shutdown of VM...

Three minutes later, I have a ready-to-go Debian instance. Next, I need to give the VM access to my MacBook's internal Bluetooth card. Guest Additions need to be installed for the VM to recognize various hardware devices, but this Debian VM template already has it installed. So I simply need to enable the USB 2.0 controller and add my Bluetooth card:

VirtualBox USB Settings

Just to make sure the hardware settings work, I start up the VM directly in VirtualBox rather than using Vagrant's CLI. It boots into a shell, so I log in with username vagrant and password vagrant. Now for the hardware: my Bluetooth card shows up in the list of available USB Devices:

VirtualBox USB Devices

Time to mount my Bluetooth USB device to the Debian VM:

VirtualBox Cannot Attach Bluetooth

Reclaiming Bluetooth

Uh oh! For the virtual machine to use Bluetooth, it needs to have the device all to itself, but the host OS is using it. Turning off Bluetooth in OS X won't suffice: I need to kill -9 the blued process on my Mac, but it regenerates itself every time. For blued to really die, it needs to be unloaded with launchctl:

$ sudo launchctl unload /System/Library/LaunchDaemons/com.apple.blued.plist

That's still not quite enough: some kernel extensions (KEXT) are holding on. Let's search all the loaded KEXTs for those relating to Bluetooth:

$ kextstat -l | awk '{gsub(/^ +| +$/,"")}1' |  tr -s ' ' | cut -f6 -d ' ' | grep Bluetooth
com.apple.iokit.IOBluetoothFamily
com.apple.iokit.IOBluetoothHostControllerUSBTransport
com.apple.iokit.BroadcomBluetoothHostControllerUSBTransport
com.apple.iokit.IOBluetoothSerialManager

The third item BroadcomBluetoothHostControllerUSBTransport looks pertinent, maybe unloading it will free up the Bluetooth device:

$ sudo kextunload -b com.apple.iokit.BroadcomBluetoothHostControllerUSBTransport

Success! Now when we try to connect the Bluetooth controller to Virtual Box, it doesn't give any errors. Let's shut down the VM and start it up from Vagrant now:

$ vagrant up
[ lots of output ]
$ vagrant ssh
vagrant@packer-debian-7:~$

Just to make sure, I'll use lsusb to see if the Linux VM recognizes my Bluetooth controller:

$ sudo apt-get update
$ sudo apt-get install usbutils
$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 002 Device 002: ID 05ac:8286 Apple, Inc.

The output is stripped of pleasantries, but that last device has Apple's manufacturer ID (05ac), so it must be the Bluetooth controller. I'm ready to install the BlueZ stack:

$ sudo apt-get install bluez bluez-utils bluez-compat

Next up, I need to tell Linux what Bluetooth device to use.

$ hciconfig
Can't open HCI socket.: Address family not supported by protocol

What’s going on? The Linux kernel is super modular and made of many low-level modules. Most device drivers (like USB and Ethernet) are just Linux modules, including Bluetooth. So, does our Linux kernel have a Bluetooth module?

$ lsmod | grep bluetooth
[ nothing :-( ]

Nope! Debian 7.6's version of the Linux kernel doesn’t support the Bluetooth module.

Linux VM #2: Debian Again

Time for an upgrade: I'll rinse and repeat the setup steps with a more recent Debian:

$ vagrant init chef/debian-7.8
$ vagrant up
$ vagrant halt

After running the rest of the commands, I can double check kernel support:

$ lsmod | grep bluetooth
bluetooth             119455  1 btusb
rfkill                 19012  1 bluetooth
crc16                  12343  2 ext4,bluetooth

That looks much more promising! This kernel is built with the Bluetooth module. Let's try hcitool:

$ hcitool dev
Devices:
        hci0    28:CF:E9:21:6C:1A

Great! Linux recognizes my Bluetooth device. I need to set up the device on every reboot:

$ sudo hciconfig hci0 up

Now I should be able to scan for Bluetooth Low Energy devices. Turn my little drone on, and...

$ sudo hcitool lescan
LE Scan ...
9E:08:00:AC:0B:19 RS_R112233
9E:08:00:AC:0B:19 (unknown)

It's alive, but all is not well. When I try to connect to the drone with sudo hcitool lecc or gatttool, it fails with a Host is down (112) message. Maybe BLE support in my old version of BlueZ is buggy?

BlueZ is currently version 5.30, but why is Debian 7.8 bundled with BlueZ 4? The BlueZ 5 API diverges drastically from BlueZ 4, so it takes a fair amount of effort to upgrade apps. Coupled with the shockingly poor documentation, very few Linux distributions have successfully upgraded to BlueZ 5.

I initially built BlueZ 5.30 from source, but a few binaries are missing, so I'll install BlueZ 5.23 from the Debian Unstable package source.

As part of major refactors, BlueZ 5 directs all commands to the bluetoothd daemon over D-Bus. To send commands from the terminal, I can run bluetoothctl:

$ sudo bluetoothctl
[bluetooth]#

I get a REPL, but it just hangs. Maybe bluetoothd isn't listening?

$ ps aux | grep bluetoothd
[ nothing :( ]

Doh, bluetoothd isn't even running! I'll run it myself in no-detach mode:

$ sudo bluetoothd -n
bluetoothd[4440]: Bluetooth daemon 5.14
bluetoothd[4440]: Failed to access management interface
bluetoothd[4440]: Adapter handling initialization failed

$ uname -r
3.2.0-4-amd64

How embarrassing—our Linux kernel is still too old! BlueZ 5 requires a kernel version >= 3.4 to support the new Bluetooth Management kernel interface, and >= 3.5 for BLE support!

Maybe it's time for a third Linux distribution?

Linux VM #3: Ubuntu

This time, I'll pick a recent distribution: Ubuntu 14.04 LTS. It ships with version 3.13 of the Linux kernel. After repeating all the setup commands I ran on the Debian distros, things seem to be in order:

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 002: ID 05ac:8286 Apple, Inc. Bluetooth Host Controller
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

The BlueZ version for Ubuntu is still too old, but thankfully another package source has the latest builds. I'll add it to my apt-get sources:

$ echo "deb http://ppa.launchpad.net/vidplace7/bluez5/ubuntu trusty main" >> /etc/apt/sources.list
$ sudo apt-get install bluez
$ bluetoothctl -v
5.30

I'm definitely running BlueZ 5.30, time to see if bluetoothd started up:

$ sudo bluetoothctl
[NEW] Controller 28:CF:E9:21:6C:1A vagrant [default]
[bluetooth]#

Looks like I have working a Bluetooth setup!

PANning for Networks

With Bluetooth working correctly on my VM, I am ready to set up a PAN with my drone. BlueZ 4 includes a utility called pand that does just this, but in BlueZ 5 I have to code up my own script. Since everything happens over D-Bus, I can write a Python script to initiate a PAN. Thankfully, Mike Kazantsev has already written that script.

$ wget https://github.com/mk-fg/fgtk/raw/master/bt-pan
$ chmod +x bt-pan
$ sudo apt-get install python-dbus

The bt-pan script is a modified version of the test-network script bundled with BlueZ 5. It instructs the bluetoothd to create a new network interface called bnep0. I need to grab the MAC address of my drone, then feed that into the script:

$ sudo hcitool lescan
LE Scan ...
9E:08:00:AC:0B:19 RS_R112233
9E:08:00:AC:0B:19 (unknown)

$ sudo ./bt-pan -u panu client 9E:08:00:AC:0B:19
Traceback (most recent call last):
  File "./bt-pan", line 207, in <module>
    if __name__ == '__main__': sys.exit(main())
  File "./bt-pan", line 180, in main
    try: iface = net.Connect(opts.uuid)
  File "/usr/lib/python2.7/dist-packages/dbus/proxies.py", line 145, in __call__
    **keywords)
  File "/usr/lib/python2.7/dist-packages/dbus/connection.py", line 651, in call_blocking
    message, timeout)
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.UnknownMethod: Method "Connect" with signature "s" on interface "org.bluez.Network1" doesn't exist

A D-Bus issue? After all that work, it turns out that the PAN interface Network1 is not available. This isn't a spurious failure: Bluetooth Low Energy doesn't support the PAN profile, so Network1 is never exposed on the D-Bus interface. Only regular Bluetooth supports PAN!

Next time...

To my disappointment, we aren't quite to the point of telneting into the drone. But never fear! In Part 3, we will backtrack to another clue in /etc/init.d/rcS to complete our journey of wirelessly shelling into the mini-drone.

Jonathan Lee Martin

Jonathan is an educator, writer and international speaker. He guides developers — from career switchers to senior developers at Fortune 100 companies — through their journey into web development.