Running Tailscale on a Mango* Router

*Mango GL-MT300N-V2 specifically

GLiNet Mango MT300N-V2

Background

I recently picked up a Mango Travel Router by GLi.Net from Amazon . As of the time of publishing, these are being sold in the UK for around Β£25 - quite the deal, considering its feature set.

Refer to the GL.iNet website for more techy details.

The router was purchased as a method to connect to the internet while we travel and/or move - as we’re likely to have none when the latter actually occurs, at least for a short while at least.

The router itself comes pre-installed with OpenWRT , which makes it a very capable device - as long as you have space available, of course.

Which brings me to my first recommendation - grab yourself a Sandisk USB flash to increase the storage capacity, as you’ll need it if you’re following these instructions. Any USB flash will do (if you already have some), my preference is the Fit as it doesn’t protrude too much.

Assumptions

These instructions assume that you have a modicum of technical skills, and are comfortable in the CLI. You should only really follow this guide if you have the confidence as you can, and likely will break your new router.

Router recovery will likely require a reflash of the device. Consider yourself warned!

Also assumed is that your router has network connectivity, and can successfully download files from the internet.

####Initial Router Setup and Update
Follow the setup guide for your new router - include the latest firmware update, so that you start from the best possible position. Ensure that you create the root users password.

Next, check that you can connect to the new router via SSH.

$ ssh root@<xxx.xxx.xxx.xxx>

BusyBox v1.30.1 () built-in shell (ash)

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 OpenWrt 19.07.7, r11306-c4a6851c72
 -----------------------------------------------------
root@ROUTER:~#

Run the upgrade command to get the latest file list:

root@ROUTER:~# opkg update

Downloading https://fw.gl-inet.com/releases/v19.07.7/packages-3.0/	ramips/packages/Packages.gz  
Updated list of available packages in /var/opkg-lists/glinet_packages
Downloading https://fw.gl-inet.com/releases/v19.07.7/packages-3.0/ramips/glinet/Packages.gz
Updated list of available packages in /var/opkg-lists/glinet_private
Downloading https://fw.gl-inet.com/releases/v19.07.7/kmod-3.0/ramips/mt76x8/Packages.gz
Updated list of available packages in /var/opkg-lists/glinet_kmod

You will need curl, ca-bundle and kmod-tun installed first:

root@ROUTER:~# opkg install curl ca-bundle kmod-tun

Some or all of these packages may already be installed - running the above command will not hurt.

If you haven’t already, go to the Tailscale Home Page and create yourself an account, and then log in.

Double check the routers' architecture

root@ROUTER:~# cat /etc/openwrt_release

DISTRIB_ID='OpenWrt'
DISTRIB_RELEASE='19.07.7'
DISTRIB_REVISION='r11306-c4a6851c72'
DISTRIB_TARGET='ramips/mt76x8'
DISTRIB_ARCH='mipsel_24kc'
DISTRIB_DESCRIPTION='OpenWrt 19.07.7 r11306-c4a6851c72'
DISTRIB_TAINTS='busybox override'

‘DISTRIB_ARCH=’ shows this as a mipsle_24kc device, so we’ll be looking for the mipsle static binary later.

By default, the router uses ‘vi’ as its editor. Nano is somewhat friendlier, and allows you to paste directly into the console, so we’ll install it now

root@ROUTER:~# opkg install nano

Package nano (5.5-1) installed in root is up to date.  

Install USB Tools and Drivers

root@ROUTER:~# opkg install kmod-usb-storage kmod-usb-storage-uas usbutils

Package kmod-usb-storage (4.14.221-1) installed in root is up to date.
Package kmod-usb-storage-uas (4.14.221-1) installed in root is up to date.
Package usbutils (007-10) installed in root is up to date.

root@ROUTER:~# lsusb -t

/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=ohci-platform/1p, 12M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Mass Storage, Driver=usb-storage, 480M

Confirm the available partitions/devices

root@ROUTER:~# opkg install block-mount

Package block-mount (2020-05-12-84269037-1) installed in root is up to date.  

root@ROUTER:~# ls -la /dev/sd*

brw-------    1 root     root        8,   0 Jan  1  1970 /dev/sda
brw-------    1 root     root        8,   1 Jan  1  1970 /dev/sda1

root@ROUTER:~# block info | grep "/dev/sd"

/dev/sda1: UUID="YOUR-UUID-HERE" VERSION="1.0" MOUNT="/mnt/sda1" TYPE="ext4"  

Install the required disk utilities

root@ROUTER:~# opkg install gdisk e2fsprogs kmod-fs-ext4

Installing gdisk (1.0.4-2) to root...
Downloading https://fw.gl-inet.com/releases/v19.07.7/packages-3.0/ramips/packages/gdisk_1.0.4-2_mipsel_24kc.ipk
Installing uclibcxx (0.2.5-3) to root...
Downloading https://fw.gl-inet.com/releases/v19.07.7/packages-3.0/ramips/packages/uclibcxx_0.2.5-3_mipsel_24kc.ipk
Configuring uclibcxx.
Configuring gdisk.
Package e2fsprogs (1.44.5-2) installed in root is up to date.
Package kmod-fs-ext4 (4.14.221-1) installed in root is up to date.

Out with the old and in with the new - creating the EXT4 partition on USB storage

Use gdisk to delete the current partition (which is likely to be FAT32), and create a new one (see the gdisk man page for more help with this). Mark the partition as Linux Filesystem (use code 8300 here).

Example output:

root@ROUTER:~# gdisk /dev/**your device**

GPT fdisk (gdisk) version 1.0.4

Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present

Found valid GPT with protective MBR; using GPT.

Command (? for help): p
Disk /dev/sda: 60088320 sectors, 28.7 GiB
Model:  SanDisk 3.2Gen1
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): YOUR-DISK-IDENTIFIER-HERE
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 60088286
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number  Start (sector)    End (sector)  Size       Code  Name
1	2048		  60088286      28.7 GiB   8300  Linux filesystem

Command (? for help): w

Once saved (press ‘w’ to write changes and exit, or ‘q’ to quit without saving changes) , create the filesystem:

root@ROUTER:~# mkfs.ext4 /dev/sda1

Mounting the newly created partition

Create the /mnt/sda1 folder as the mount point, if it doesn’t already exist

root@ROUTER:~# mkdir -p /mnt/sda1

root@ROUTER:~# blkid

/dev/mtdblock5: TYPE="squashfs"
/dev/sda1: UUID="YOUR-UUID-HERE" TYPE="ext4" PARTLABEL="Linux filesystem" PARTUUID="YOUR-PARTUUID-HERE"  

Set the partition so that it will automount

root@ROUTER:~# block detect | uci import fstab

root@ROUTER:~# uci set fstab.@mount[-1].enabled='1'

root@ROUTER:~# uci commit fstab

Reboot and check that everything is as expected - ensure that the /dev/sda1 folder is mounted.

root@ROUTER:~# uci show fstab

fstab.@global[0]=global
fstab.@global[0].anon_swap='0'
fstab.@global[0].anon_mount='0'
fstab.@global[0].auto_swap='1'
fstab.@global[0].auto_mount='1'
fstab.@global[0].delay_root='5'
fstab.@global[0].check_fs='0'
fstab.@mount[0]=mount
fstab.@mount[0].target='/mnt/sda1'
fstab.@mount[0].uuid='YOUR-UUID-HERE'
fstab.@mount[0].enabled='1'  

Now you create the tailscale folder inside what will be the /opt folder later - we also create a state folder to save authentication between restarts

root@ROUTER:~# mkdir -p /mnt/sda1/opt/tailscale/state

Note: busybox may not support 'mkdir -p', in which case, create each directory individually

Create an /opt folder in the root directory

root@ROUTER:~# mkdir /opt

Create the symbolic link between these two folders

root@ROUTER:~# ln -s /mnt/sda1/opt /opt

Now we have the space, it’s time to install Tailscale

I also create an ‘archive’ folder to store the tailscale download file - keeps things tidy - it’s your choice, but is recommended.

root@ROUTER:~# mkdir /opt/tailscale/archive

Download the Tailscale Stable static binaries for the mipsle device (adjust the path if you didn’t create the archive folder).

root@ROUTER:~# curl -O https://pkgs.tailscale.com/stable/**latest version**_mipsle.tgz --output /opt/tailscale/archive/.

Unpack the archive (adjusting the archive name as required)

root@ROUTER:~# tar x -zvf /opt/tailscale/archive/**latest version**_mipsle.tgz -C /opt/

Move the files into the correct folder (adjusting folder name as required)

root@ROUTER:~# mv /opt/**latest version**_mipsle/* /opt/tailscale/.

Delete the now empty folder (adjusting folder name as required)

root@ROUTER:~# rm -r /opt/**latest version**_mipsle

The /opt/tailscale folder should now look like this:

root@ROUTER:~# ls -la /opt/tailscale

drwxr-xr-x    5 root     root          4096 Jul 28 16:10 .
drwxr-xr-x    4 root     root          4096 Jul 28 11:50 ..
drwxr-xr-x    2 root     root          4096 Jul 28 16:01 archive
drwxr-xr-x    2 root     root          4096 Jul 28 15:35 state
drwxr-xr-x    2 root     root          4096 Jul 28 12:56 systemd
-rwxr-xr-x    1 root     root      10866857 Jul 28 02:56 tailscale
-rwxr-xr-x    1 root     root      16612381 Jul 28 02:56 tailscaled

Create the symbolic links to the tailscale application and daemon files

root@ROUTER:~# ln -s /opt/tailscale/tailscale /usr/sbin/tailscale
root@ROUTER:~# ln -s /opt/tailscale/tailscaled /usr/sbin/tailscaled

Create the service that controls Tailscale

Create the Tailscale service in /etc/init.d (using nano which you installed earlier)

root@ROUTER:~# nano /etc/init.d/tailscale

Paste the following script into the editor

 1#!/bin/sh /etc/rc.common
 2
 3# Copyright 2020 Google LLC.
 4# SPDX-License-Identifier: Apache-2.0
 5
 6USE_PROCD=1
 7START=80
 8
 9start_service() {
10  local state_file
11  local port
12  local std_err std_out
13
14  config_load tailscale
15  config_get_bool std_out "settings" log_stdout 1
16  config_get_bool std_err "settings" log_stderr 1
17  config_get port "settings" port 41641
18  config_get state_file "settings" state_file /opt/tailscale/state/tailscaled.state
19
20  /usr/sbin/tailscaled --cleanup
21
22  procd_open_instance 
23  procd_set_param command /usr/sbin/tailscaled
24
25  # Set the port to listen on for incoming VPN packets.
26  # Remote nodes will automatically be informed about the new port number,
27  # but you might want to configure this in order to set external firewall
28  # settings.
29  procd_append_param command --port "$port"
30  
31  # OpenWRT /var is a symlink to /tmp, so write persistent state elsewhere.
32  procd_append_param command --state "$state_file"
33
34  procd_set_param respawn
35  procd_set_param stdout "$std_out"
36  procd_set_param stderr "$std_err"
37
38  procd_close_instance
39}
40
41stop_service() {
42  /usr/sbin/tailscaled --cleanup
43}
44

Exit nano using CTRL+X, ensuring that you save the file on exit. Now to make the script executable

root@ROUTER:~# chmod +x /etc/init.d/tailscale

(Optional) You can create a config file for the service in /etc/config which can override the built in service settings

root@ROUTER:~# nano /etc/config/tailscale

1config settings 'settings'
2        option log_stderr '1'
3        option log_stdout '1'
4        option port '41641'
5        option state_file '/opt/tailscale/state/tailscaled.state'

Tailscale has now been installed. Time to test that our work is functioning correctly…

root@ROUTER:~# cd /etc/init.d
root@ROUTER:~# service tailscale start

You should see some output relevant to your installation. As long as there are no serious errors (you may see a couple relating to logs that you may safely ignore) then you can bring Tailscale VPN online with the following command

Authenticating Tailscale for the first time

root@ROUTER:~# tailscale up

This will display some text on the screen, along with a URL to visit - at this point, you should already have an account with Tailscale , and have logged into their website. Visit the URL from the console to authenticate your router.

Now when you visit the Tailscale admin console , you should see your router added - note that the router here has ‘No Expiry’ set already - you can do this later by selecting the ellipsis (Β·Β·Β·) beside the device, and choosing Disable Key Expiry:

Machines  

[All | External]			[Search machines...			]  

MACHINE					IP			OS	LAST SEEN  
hello					100.101.102.103		Linux	Connected  
services@tailscale.com						1.12.1
[External][No expiry]

router					xxx.xxx.xxx.xxx		Linux	Connected
your@email.address						1.12.1
[No expiry]  

To check that the network is available, run the following command (use CTRL+C to cancel)

root@ROUTER:~# tailscale ping hello.ipn.dev

pong from hello (100.101.102.103) via DERP(fra) in 659ms
pong from hello (100.101.102.103) via DERP(fra) in 54ms
pong from hello (100.101.102.103) via DERP(fra) in 101ms
pong from hello (100.101.102.103) via DERP(fra) in 54ms
pong from hello (100.101.102.103) via DERP(fra) in 54ms
^Ccontext canceled

Congratulations! You now have Tailscale installed and working correctly. However, it won’t start automatically yet, so there are two more commands that you need to enter before you reboot your router.

root@ROUTER:~# cd /etc/init.d
root@ROUTER:~# service tailscale enable

Reboot the router, then retest the ping using the command above. If it still works as before (and there’s no reason why it shouldn’t) then pat yourself on the back for a job well done - it’s been a trek! ;)

References

@PeeKay69 - Twitter
OpenWRT Home Page
OpenWRT Storage Page
Tailscale Admin Console

Thanks and Credits

This guide is an original work, with additional information included. However, as I used several sites as reference to get this functioning correctly, I wanted to credit their work also.

Will Angley : “How I set up Tailscale on my WiFi router”
OpenWRT Packages : Adaptation of the service/init and /etc/config/tailscale config files
Pat Regan : Pointed me in Wills direction - I originally used Pats service method prior to creating this better (IMHO) version
Tailscale : Let’s not forget Tailscale without whom I would never have embarked on this mission in the first place

Cristiaan Diels for additional proof reading and error spotting - thanks!