Skip to content
Feb 4 19

Cross compiling Rust on Mac OS for an ARM Linux router

by sigmaris

Wanting to compile a small program I’d written in Rust to run on my home router, I found this guide to cross compilation of Rust code. The router is a Netgear R7000 with an ARM processor, running FreshTomato, a distribution of Linux for ARM and MIPS architecture consumer routers. The top of that guide shows an example of installing the cross-compilation toolchain for ARM on Ubuntu, but it required some work to adapt to Mac OS High Sierra, my desktop environment.

The guide suggests rustup can be used to install extra cross compilation targets. I already have rustup which I’ve used to install Rust for Mac OS and keep it up-to-date, so that’s handy. So I ran “rustup target list” to list all the installable targets:

(many more x86, mips, powerpc and x86_64 targets)

That’s a lot of possible targets. It looks like in the ARM space, there’s AArch64, arm-unknown, armebv7r, armv5te and armv7(r?) architectures of various variants. So, let’s google to see what kind of CPU the router has.

According to the OpenWRT wiki it’s a Broadcom BCM4709A0. So, what kind of architecture is that? Googling for “BCM4709A0” brought me to Wikidevi, which says it’s an ARM Cortex-A9. Looking at Wikipedia for the Cortex-A9 tells me:

  • It’s a 32-bit architecture, so not AArch64
  • It’s an ARMv7-A architecture

So I’d guess one of the armv7 targets is the best one. It’s probably not armv7-apple-ios or armv7-linux-androideabi, since this isn’t an iOS or Android OS. That leaves armv7-unknown-linux-gnueabihf, armv7-unknown-linux-musleabihf, armv7r-none-eabi and armv7r-none-eabihf. I know the router runs Linux, so let’s try the first two. I installed the armv7-unknown-linux-gnueabihf target with:

rustup target add armv7-unknown-linux-gnueabihf

OK, let’s try compiling a “hello world” Rust application with that target:

cargo build --target=armv7-unknown-linux-gnueabihf

That failed with a message “error: linking with cc failed: exit code: 1” and then a note showing the entire cc command, and a note saying:

= note: clang: warning: argument unused during compilation: '-pie' [-Wunused-command-line-argument]
           ld: unknown option: --as-needed
           clang: error: linker command failed with exit code 1 (use -v to see invocation)

OK, so I guess this is clang giving that error. Clang is the native C compiler for Mac OS, but I expect it can’t link an ARM executable in the way Rust wants. So, it looks like we need a linker for ARM. Reading the guide seems to suggest that Rust doesn’t have its own linker for Linux targets – it uses the linker from a C toolchain, for example the GNU C compiler. So we need to install a C toolchain targeting ARM.

My first stop when looking to install open source tools on Mac OS is Homebrew, and indeed there’s a formula on there for arm-linux-gnueabihf-binutils – it looks like that could be what we need to get a linker targeting ARM Linux. So let’s install that with:

brew install arm-linux-gnueabihf-binutils

That installs a set of tools named arm-linux-gnueabihf-addr2line, arm-linux-gnueabihf-ar and so on. I know the linker is normally invoked as “ld”, and cross-compilation toolchains by convention prefix their tool names with the target name, so the ARM Linux linker should be arm-linux-gnueabihf-ld. I know from the guide that this needs to go in ~/.cargo/config in a section like this:

linker = "arm-linux-gnueabihf-gcc"

But the Homebrew formula didn’t install arm-linux-gnueabihf-gcc – it only has arm-linux-gnueabihf-ld. Well, let’s try that instead, so the config is:

linker = "arm-linux-gnueabihf-ld"

OK, let’s try compiling again…

   Compiling rust-sandbox v0.1.0 (/Users/hugh/Source/rust-sandbox)

error: linking with `arm-linux-gnueabihf-ld` failed: exit code: 1
  (...long command removed...)
  = note: arm-linux-gnueabihf-ld: cannot find -ldl
          arm-linux-gnueabihf-ld: cannot find -lrt
          arm-linux-gnueabihf-ld: cannot find -lpthread
          arm-linux-gnueabihf-ld: cannot find -lgcc_s
          arm-linux-gnueabihf-ld: cannot find -lc
          arm-linux-gnueabihf-ld: cannot find -lm
          arm-linux-gnueabihf-ld: cannot find -lrt
          arm-linux-gnueabihf-ld: cannot find -lpthread
          arm-linux-gnueabihf-ld: cannot find -lutil
          arm-linux-gnueabihf-ld: cannot find -lutil

This is more promising, but it looks like the linker can’t find all of those libraries to link with. Those look like parts of the GNU C library and other system libraries for Linux, which the Homebrew package arm-linux-gnueabihf-binutils doesn’t seem to include. These would normally be installed on a Linux system, but on Mac OS we don’t have them.

It seemed like I might need to install a more complete Linux toolchain that includes those libraries, but before trying that, let’s look at the other Rust target – armv7-unknown-linux-musleabihf. The “musl” in the name refers to the musl C library, a small C library that can be statically linked with Rust programs instead of the GNU C library. This sounds promising as it removes the need to link against libpthread, etc, which we had problems with earlier.

Let’s put the same linker configuration in ~/.cargo/config for the armv7-unknown-linux-musleabihf target:

linker = "arm-linux-gnueabihf-ld"

And try compiling our Rust program with this target:

cargo build --target=armv7-unknown-linux-musleabihf
   Compiling rust-sandbox v0.1.0 (/Users/hugh/Source/rust-sandbox)
    Finished dev [unoptimized + debuginfo] target(s) in 1.12s

It built, so let’s copy the executable to the router:

scp target/armv7-unknown-linux-musleabihf/debug/rust-sandbox router:/tmp/

And then SSH on to the router and run it:

$ ssh
 Welcome to the Netgear R7000 [TomatoUSB]
 (output trimmed)

root@router:/tmp/home/root# /tmp/rust-sandbox
Hello, world!

Great, it works! Using the musl C library and statically linking everything is somewhat less optimal than linking against the C library that’s already installed on Linux, as it means the built executable size is larger, but it’s good enough for a simple Rust program.

Jan 5 19

Make certbot Let’s Encrypt certificates readable by Debian ssl-cert group

by sigmaris

On Debian, there’s a group named ssl-cert which grants access to TLS certificates and private keys, so that services that don’t run as the root user can still use TLS certificates. For example, the PostgreSQL Debian package installs PostgreSQL to run as a user named postgres, which is a member of the ssl-cert group, and so it can use certificates and private keys in /etc/ssl.

The certbot Let’s Encrypt client, by default, makes the certificates and private keys it installs only readable by the root user. There is an open issue against certbot, requesting that on Debian, certbot should follow the Debian standard of making the certificates and keys readable by the ssl-cert group as well. In the meantime, until that issue is resolved, the ownership can be set by a post-hook which will be run by certbot after obtaining or renewing a certificate.

To do that, add this line to your certbot configuration file, i.e. /etc/letsencrypt/cli.ini:

post-hook = chmod 0640 /etc/letsencrypt/archive/*/privkey*.pem && chmod g+rx /etc/letsencrypt/live /etc/letsencrypt/archive && chown -R root:ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive
Apr 21 18

Encoding DNS URI records for DNSMASQ

by sigmaris

Dnsmasq can be configured to add various types of records like SRV, PTR, and NAPTR to its internal DNS server by various directives in its configuration file. But what if there’s a less common type of DNS record that you want to serve, which dnsmasq doesn’t have a specific configuration directive to handle?

Handily, dnsmasq also supports serving arbitrary DNS resource records using the dns-rr option. However you have to supply the binary value of the response encoded in hexadecimal. Here’s an example of how to do this for a URI record with Python. The URI record type is described in RFC 7553 which describes the binary value of the response (“wire format”) as:

The RDATA for a URI RR consists of a 2-octet Priority field, a 2-octet Weight field, and a variable-length Target field.

Priority and Weight are unsigned integers in network byte order.

The remaining data in the RDATA contains the Target field. The Target field contains the URI as a sequence of octets (without the enclosing double-quote characters used in the presentation format).

So, we need to encode the priority as 2 bytes, then the weight as 2 bytes, then the URI itself. We can use Python’s struct module for encoding the integers, and the binascii module to encode the strings as hex:

import binascii
import struct
record_name = b''
record_type = b'256'  # Assigned type number for URI records
priority = 10
weight = 5
value = b''
    b'dns-rr=' + record_name,
    # The ! here uses network byte order, followed by two 'H's for unsigned 2-byte values.
    binascii.hexlify(struct.pack('!HH', priority, weight))
    + binascii.hexlify(value)

This will output:,256,000a0005687474703a2f2f7777772e6578616d706c652e636f6d2f

which can be copied straight into dnsmasq’s configuration file.

Dec 12 16

A handy reference list of GSS-API mechanism OIDs

by sigmaris

This is more for my own reference, but might be useful for others.

1.2.840.113554.1.2.2 – Kerberos v5 – RFC 1964
1.2.840.48018.1.2.2 – Kerberos V5 (incorrect, used by old Windows versions) – SPNEGO – RFC 4178 – IAKERB – draft-ietf-kitten-iakerb-03 – NTLM SSP – Heimdal:lib/gssapi/oid.txt – SCRAM-SHA-1 – RFC 5802 – SCRAM-SHA-256 – RFC 7677* – GSS-EAP (arc) – RFC 7055 – PKU2U – draft-zhu-pku2u-09 – SPKM-1 – RFC 2025 – SPKM-2 – RFC 2025 – SPKM-3 – RFC 2847 – LIPKEY – RFC 2847
1.2.752.43.14.2 – NETLOGON – Heimdal:lib/gssapi/oid.txt

Jun 9 12

Home IPv6 Tunnel

by sigmaris

The news of World IPv6 Launch Day last week prompted me to properly set up IPv6 for my home network. Unfortunately my ISP doesn’t offer native IPv6 connectivity — not many consumer ISPs do — but fortunately there are free services which will provide an IPv6 ‘tunnel’ over the IPv4 internet, to the wider IPv6 world. I signed up to Hurricane Electric’s service at and was soon up and running. Hurricane Electric will issue you a /64 block of IPv6 addresses, which you can allocate as you see fit. You need to set up one system to handle your end of the tunnel, and route all traffic destined for the wider IPv6 internet over the tunnel. I already had a Linux server running that was handling the local end, but since I’ve recently acquired an Asus RT-N16 router running DD-WRT, I decided to enable IPv6 support on it and use it as the tunnel endpoint, so it can serve as the default gateway for both IPv4 and IPv6 on my LAN.

To do this you need to be running one of the DD-WRT builds with IPv6 support, I am using the “mega” build which includes pretty much everything. As well as enabling IPv6 support in the DD-WRT admin web interface, you need to set up a custom script to bring up the tunnel when the router starts up. The script I used is below – you need to replace the following:

the User ID shown on the page when you log in
the MD5 hash of your password
the Tunnel ID of your tunnel
the IPv4 address of the remote end
the IPv6 address of the local end
one IPv6 address, out of your routed IPv6 /64, allocation that you want to use for your router on your LAN
insmod ipv6
WANIP=$(nvram get wan_ipaddr);
wget -O - ''
ip tunnel add he-ipv6 mode sit remote (remotev4) local $WANIP ttl 255
ip link set he-ipv6 up
ip -6 addr add (localv6) dev he-ipv6
ip -6 addr add (lanv6) dev br0
ip -6 route add ::/0 dev he-ipv6
echo 1 > /proc/sys/net/ipv6/conf/all/forwarding
radvd -C /tmp/radvd.conf

This image shows where to find some of the information in your page:

The setup script needs to be saved, as a custom startup script, in the Administration -) Commands section of the DD-WRT web interface.

I had also enabled radvd in the DD-WRT web interface to advertise the gateway to the wider IPv6 world on my LAN, but for me it failed to start on boot, so I added the radvd -C /tmp/radvd.conf line to make sure it starts after the tunnel is configured. The configuration file used for radvd is below, replace (routedv6) with your routed IPv6 /64 allocation, including the /64, and paste it into the box for radvd config in the DD-WRT web interface:

interface br0
   AdvSendAdvert on;
   prefix (routedv6)
       AdvOnLink on;
       AdvAutonomous on;

This setup means that on the other computers on the LAN (all Macs), I can set “Configure IPv6: Automatically” in the Network Preferences and IPv6 connectivity just works. The other machines on the LAN will configure themselves based on the information radvd sends out, using stateless autoconfiguration. However, this ease of use comes with a security risk…

read more…

Feb 20 11

Two-factor authentication with Mac OS X and OpenSC part 2

by sigmaris

This is the second part of the guide to smartcard-based authentication on Mac OS X. In this part of the guide, I’m going to assume that following Part 1, you have installed OpenSC, initialised your smartcard, and loaded or generated some certificates and private keys onto it. Now I’m going to show you how to use the card for actual authentication.

read more…

Nov 13 10

Two-factor authentication with Mac OS X and OpenSC part 1

by sigmaris

Interested in using a smartcard for secure two-factor authentication on OS X? What about E-mail signing and encryption, SSH key authentication, and more? All of these applications are possible, using the built-in smartcard support in OS X and open source software. What follows is the first part of a guide to using smartcards on OS X, using software from the OpenSC Project.

read more…

Apr 5 10

A Service for unmounting volumes in OS X

by sigmaris

A little while ago I had a problem. My phone is a Sony Ericsson K800i, with 64MB of internal storage and a slot for Memory Stick expansion storage. I was using it as an MP3 player, and so had an 8GB memory stick in there to store MP3s.

When I connected it to my Mac in USB drive mode to transfer files, it appeared in the Finder as two storage devices, one for the internal storage and one for the memory stick. When I’d finished transferring files I would click Eject on the memory stick’s device to safely remove it. Then I’d do the same on the internal storage’s device, but this would eventually fail and report an I/O error. The phone’s screen would show that the USB connection had ended, but one of the devices would still show in the Finder. If I then unplugged the USB cable the Finder would tell me that data might have been lost since I didn’t eject the storage device properly.

I eventually found a workaround – go into the Disk Utility app, and Unmount (not Eject) one device, then Eject the other. The order didn’t seem to matter, as the Eject would cause both devices to disappear. However this required me to launch Disk Utility to do the unmounting every time I had to unplug the phone from the Mac.

I started to get fed up of the extra time and clicking involved, so I decided to write a single-function Service to do the unmounting. Check it out here.

Feb 18 10

Student Tech Meetup talk

by sigmaris

On Wednesday, I gave a talk at the Student Techmeetup here in Edinburgh on DS homebrew development. As promised, I’ve uploaded the slides from that talk as a PDF here, with links to a few tutorials and resources included in them, along with some notes.

Jan 16 10

emitSMS plugin update

by sigmaris

The emitSMS plugin has been updated with a small new feature – it will now strip out dashes and spaces from your phone numbers in Address Book before sending them to the phone, as most phones don’t seem to accept numbers in this format. Grab the new version here.