Bluetooth tethering with a 2021 Debian

The other day the cage that holds the SIM card for the wireless modem of my Lenovo X240 rotted away. Fixing this (provided that's even reasonable, which I'm not sure about) requires digging quite a bit deeper into the machine than I consider proportional for something I use less than once a month. Plus, there's still my trusty N900 cellphone that I can use for the occasional GSM (or, where still available, UMTS) data connection.

A SIM card cage on a table

The underlying reason for mucking around tethering bluetooth in 2021: the cage for the SIM card in my computer rotted out. And re-attaching it to the mainboard looks like surgery to deep for summer.

So, I revived the ancient scripts I used to use around 2005 with feature phones playing cell modem and tried to adapt them to the N900. Ouch.

Well, full disclosure: I have only hazy notions about what's going on in bluetooth in general, and the Linux tooling for bluetooth I find badly confusing. Hence, rather than reading manpages I started asking duckduckgo for recipes for “bluetooth tethering linux“ or similar. And since what I found was either rather outdated or used various GUI and network management tools I prefer to not have to regularly run, let me write up what I ended up doing to thether my Debian box. Oh: it's using ifupdown and is Debian-specific in that sense, but otherwise I think it's fairly distribution-neutral, contrary to what you might expect after the headline.

The bluetooth part

The basic operation for bluetooth tethering is straightforward: Open a serial-like connection (“rfcomm”) to the phone, then start a pppd on top of it. It's the little details that make this tricky.

The first detail for me is that I have a deep distrust of bluez (and even the bluetooth drivers). I hence keep bluetooth off and blocked most of the time, and before opening any bluetooth connection, I have to manage this bluetooth state, which I'm going to do in a plain shell script. I'm sure there's a very elegant way to do this in systemd, but then I'd say this is a case where the clarity of a shell script is hard to beat.

So, I created a file /usr/local/sbin/bluenet containing this:

# Configure/deconfigure an rfcomm bluetooth connection


case $1 in
    /usr/sbin/rfkill unblock bluetooth
    /sbin/modprobe btusb
    /usr/sbin/service bluetooth start
    /usr/bin/rfcomm bind /dev/rfcomm0  $DEVICE_MAC
    sleep 2 # no idea what I should really be waiting for here, but
      # bluetooth clearly needs some time to shake out
    /usr/bin/rfcomm release /dev/rfcomm0
    /usr/sbin/service bluetooth stop
    /sbin/rmmod btusb
    /usr/sbin/rfkill block bluetooth
    echo "$1 start|stop"
    exit 1

All that you really need if you don't mind having bluetooth on are the two rfcomm command lines; the advantage of having this in a separate script (rather than just hack the rfcomm calls into the ifupdown stanza) is that you can say bluenet stop after a failed connection attempt and don't have to remember what exactly you started and what the stupid rfcomm command line was. Oh: Resist the temptation to keep this script in your home directory; it will be executed as root by ifupdown and should not be writable by your normal user.

To figure out your phone's bluetooth id, I think what people generally use these days is bluetoothctl (and for interactive use, it's fairly nice, if scantily documented). Essentially, you say scan on in there and wait until you see something looking like your phone (you'll have to temporarily make it discoverable for that, of course). While you're in there, run pair <mac>, too – at least for me, that was really straightforward compared to the hoops you had to jump through to pair bluetooth devices in Linux in the mid-2000s.

With this, you should be able to say:

sudo /usr/local/sbin/bluenet start

and then talk to a modem on /dev/rfcomm0. Try it with a terminal programme:

minicom -D /dev/rfcomm0

In minicom, type AT and return, and try again if it doesn't work on the first attempt[1]; the phone should respond with OK. If it does, you're essentially in business. If it doesn't, try rfcomm -a – which should show something like:

rfcomm0: DE:VI:CE:MA:CA:DD:RE channel 1 clean

Oh, and bluetootctl may be your (slightly twisted) friend; in particular info <mac> has helped me figure out problems. I still don't understand why my N900 doesn't show the rfcomm capability in there, though – again, I'm not nearly enough of a bluetooth buff to tell if that's normal in any way.

The PPP part

Assuming, on the other hand, you have your rfcomm connection, the rest is standard pppd fare (which, of course, makes me feel young again). This means you have to give a provider-specific pppd configuration, conventionally in /etc/ppp/peers/bluez (replace bluez with whatever you like), which for me looks like this:

connect "/usr/sbin/chat -v -f /etc/ppp/chat-bluez"

lcp-echo-interval 300
lcp-echo-failure 10

Some of this is a good idea whenever there's not actually a serial port in the game (local, noctsrts), some may break your setup (noauth, though I think that's fairly normal today), and the lcp-echo things I found useful to detect lost connections, which of course are rather common on cellular data. Oh, and then there's the noipv6 that you may object to.

Anyway: you may need to gently adapt your pppd peers file. Use your common sense and browse the pppd man page if you can't help it.

The chat script /etc/ppp/chat-bluez mentioned in the peers file for me (who's using a German E-Netz reseller) looks like this:

'' "ATZ"
OK 'AT+CGDCONT=1,"IP","",""'
OK "ATD*99***1#"

Essentially I'm doing a modem reset (ATZ), and (to accomodate for the swallowed initial characters mentioned above) I'm trying again if the first attempt failed. The ATE1 enables echo mode to help debugging, and then comes the magic in the CGDCONT (“Define packet data protocol (PDP) context”) AT command – you will have to adapt at least the string to your provider's APN (there are public lists of those). The rest you can probably keep as-is; nobody really uses more than one profile (the 1) or a PDP type other than IP (well, there IPV4V6 that might interest you if you've removed the noipv6 statement above), or uses the PDP address in the last argument.

The final dial command (the ATD) is, I think, fairly standard (it says, essentially: Call the PDP context 1 set up in CGDCONT).

All this assumes your provider does not require authentication; I think most don't these days. If they do, say hello to /etc/ppp/pap-secrets and the pppd manpage (you have my sympathy).

Finally, the various components are assembled in a stanza in /etc/network/interfaces:

iface n900 inet ppp
  pre-up /usr/local/sbin/bluenet start
  provider bluez
  post-down /usr/local/sbin/bluenet stop

That's it – your tethered network should now come up with ifup n900, and you can take it down with ifdown n900. If the thing gets stuck, meaning the interface won't come up as far as Debian is concerned, the post-down action will not be run. Just run bluenet stop manually in that case.

Amazing endurance

One thing blew my mind when I did this: A good decade ago, the Nokia N900 didn't come with the profile rfcomm uses enabled (it's called “DUN” there; don't ask me why). It didn't need much to enable it, but you needed to drop some magic file for the upstart init system running in the N900's maemo into some strategic location, and some kind soul had packaged that up. Not expecting much I simply ran apt-get install bluetooth-dun – and it just worked. Whoever still keeps up these ancient maemo repositories: Thanks a lot, and I'm impressed. If we ever meet, remind be to buy you a $BEVERAGE.

[1]In the communication with the N900 with its ancient bluetooth stack, for some reason the first character(s) tend to get swallowed somewhere on the way. I've not tried to ascertain who is to blame; perhaps it's the autoconnection of rfcomm?
Kategorie: edv