VPN access to USB SMB Server on OpenWRT
Tags: openwrt, smb, tailscale, vpn
Published: 2023-04-07 14:36:52

## Introduction

This post deals with accessing an SMB server on a USB mounted to your home router via Tailscale VPN. The purpose is to have a shared folder on your home network which stores files that can be accessed and be modified by devices on your home network, thus reducing dependency on third party cloud storage services. In contrast to cloud storage services, your files stay on your home network and need not be transferred over to a remote third party server. In addition, if you're not connected to your home network, but still require access to the shared files, it is possible to do so by connecting via a VPN to your home network.  

This post is divided into three parts: Mounting a USB drive to a OpenWRT router, installing SMB server for file sharing and installing Tailscale as a VPN service. 

The prerequisites are: a router running OpenWRT as its operating system, and a USB port should be available on the router. My current setup is running OpenWRT v22.03.3 on a TP-Link Archer A9 AC1900 v6. The SMB (Server Message Block) server will run on Samba4. Alternatively, the SMB server can be installed and run on a Raspberry Pi or a NAS, however they are out of scope of this article as they require additional hardware. Here, the SMB server runs directly on the router itself and stores files on a USB mounted to the router. Tailscale VPN is chosen as a VPN service as my OpenWRT router sits behind a primary router which does not allow port forwarding. Alternatively, if you're running OpenWRT on a primary router, OpenVPN or WireGuard could be used as a VPN server.

## Mounting USB drive to OpenWRT

Make sure to have the USB drive formatted to `FAT32` format. Then plug it in to the router and create an ssh connection to the router. 

```bash
$ ssh root@192.168.1.1
```

To use a USB device with OpenWRT, it needs to be configured with `ext4` file system. 

```bash
$ opkg update
$ opkg install block-mount \
kmod-fs-ext4 e2fsprogs parted \
kmod-usb-storage kmod-usb-uhci \
kmod-usb2 kmod-usb3 \
kmod-usb-core kmod-usb-storage
$ parted -s /dev/sda -- mklabel gpt mkpart extroot 2048s -2048s
```

We now configure OpenWRT to use the USB to expand its root file system. OpenWRT will split the internal storage into `rootfs` and `rootfs_data` partitions, which are merged together into a single writable `overlay` file system. `rootfs` is a read only partition mounted on `/rom`. `rootfs_data` is a writable partition mounted on `/overlay`. And `overlay` is a hybrid of these two file systems mounted on `/`, where the objects that appear in this file system do not always appear to belong to the same file system. Mostly, an object accessed in this union will be indistinguishable
from accessing the corresponding object from the original file systems.

We now configure the file system tables to mount `rootfs_data` in another directory in case you need to access the original root overlay to change `extroot` settings. `extroot` works by setting another overlay partition in the external storage device, and during boot this new overlay partition will be mounted over the internal storage's overlay partition. This allows easy fallback in case the external storage device is removed, as your device will still have its own overlay partition and thus will load all configuration from there.

```bash
$ DEVICE="$(sed -n -e "/\s\/overlay\s.*$/s///p" /etc/mtab)"
$ uci -q delete fstab.rwm
$ uci set fstab.rwm="mount"
$ uci set fstab.rwm.device="${DEVICE}"
$ uci set fstab.rwm.target="/rwm"
$ uci commit fstab
```

Check block partitions using the following command.

```bash
$ block info
/dev/mtdblock8: UUID="9fd43c61-c3f2c38f-13440ce7-53f0d42d" VERSION="4.0" MOUNT="/rom" TYPE="squashfs"
/dev/mtdblock9: MOUNT="/rwm" TYPE="jffs2"
/dev/sda1: UUID="fdacc9f1-0e0e-45ab-acee-9cb9cc8d7d49" LABEL="extroot" VERSION="1.0" MOUNT="/overlay" TYPE="ext4"
```
The `mtdblock*` are the devices in internal flash memory, and /dev/sda1 is the partition on the USB flash drive.
We now create an `ext4` file system on the first partition of the USB device – `/dev/sda1`.

```bash
$ DEVICE="/dev/sda1"
$ mkfs.ext4 -L extroot ${DEVICE}
```

Now, we configure the selected partition as new overlay via `fstab`.

```bash
$ eval $(block info ${DEVICE} | grep -o -e "UUID=\S*")
$ uci -q delete fstab.overlay
$ uci set fstab.overlay="mount"
$ uci set fstab.overlay.uuid="${UUID}"
$ uci set fstab.overlay.target="/overlay"
$ uci commit fstab
```

We now transfer the content of the current overlay to the external drive and reboot the device to apply changes.

```bash
$ mount ${DEVICE} /mnt
$ tar -C /overlay -cvf - . | tar -C /mnt -xf -
$ reboot
```

To test if it worked as expected, after the reboot, login to your Luci web portal and navigate to `System -> Mount Points`. This should show the USB partition mounted as `overlay`. And if you navigate to `System -> Software`, it should show you the additional free space of the overlay partition from the USB drive. 

## File sharing with Samba4

Install the following dependencies to start a Samba4 server.

```bash
$ opkg update
$ opkg install kmod-usb-extras block-mount \
ntfs-3g samba4-server luci-app-samba4
```

Create a `share/` folder. This will be the folder that will be shared on the network. Also, change permissions of the folder so that any user can read and write to the folder. 

```bash
$ mkdir /overlay/share
$ chmod a+rwX /overlay/share
```

Now login to Luci web portal and navigate to `Services -> Network share` and configure as follows.

```yml
Interface: lan
Workgroup: WORKGROUP
Enable macOS compatible shares: Check (Optional)
```
Note: **Never expose SMB to the Internet. SMB servers should only be accessed over a VPN.**

On the `Shared directories` section, add a directory with the following configurations.

```yaml
Name: share
Path: /overlay/share
Browsable: True
Allow guests: True
```
Save and apply changes. Now we need to add new SMB users. Open `/etc/passwd` on the router.

```bash
$ vi /etc/passwd
```
Append at the end of file the following line.

```yaml
newuser:*:1000:65534:newuser:/var:/bin/false
```

Replace the two `newuser` placeholders with a username of your choice. 

Create password for your `newuser`.

```bash
$ smbpasswd -a newuser
```
Then restart the Samba4 service.

```bash
$ service samba4 restart
```

Again, login to Luci web portal and navigate to `Services -> Network share`. On the `Shared directories` section, on the `Allowed Users` field, enter the username you selected earlier, uncheck `Allow guests`, then save and apply.

SMB server is now up and running. You can test this by connecting through another device on the same network. The URL should be `smb://192.168.1.1/share` (for Windows `\\192.168.1.1\share`). Then authenticate with the earlier defined username and password.

## Install Tailscale on OpenWRT

Install the following dependencies for Tailscale.

```bash
$ opkg update
$ okpg install tailscale iptables-nft
```
Now log in to Tailscale using the following command. Copy the login URL into a browser and use SSO to log in to Tailscale. Then register this router as one of your devices. Also, download and install Tailscale app on a second device, as Tailscale requires at least two devices to be registered to communicate. 

```bash
$ tailscale up
```

We need to advertise our local routes for other devices to be able to connect to our local network. You can define the IPv4 address space to be advertised to your devices. Additionally, we can also use this router as an exit node for VPN traffic. If you want split tunneling of your traffic and only require access to the local area network, you can leave out the exit node flag.

```bash
$ tailscale up \
--advertise-routes=192.168.0.0/16 \
--advertise-exit-node --accept-routes \
--netfilter-mode=off
```  

Additionally, you need to enable Subnet route and exit node to be allowed on the Tailscale web portal. For this, navigate to the Tailscale web portal. Select `Edit route settings` on OpenWRT, and enable `Subnet routes` and `Exit node` options. Also, you can disable key expiry for OpenWRT router on the web portal so that you need not login time and again via the router. 

To use OpenWRT router as an exit node, further configurations should be changed in OpenWRT. Log in to Luci web portal and navigate to `Network -> Firewall -> General Settings -> Add` and set the following configurations:

```yaml
General:
    Name: Tailscale
    Input: Accept
    Output: Accept
    Forward: Accept
    Masquerading: True
    MSS clamping: True
    Covered Networks: Unspecified
    Allow forward to destination zones: Lan, Wan
    Allow forward from source zones: Lan, Wan
Advanced:
    Covered devices: tailscale0
``` 

Navigate to `Network -> Firewall -> NAT Rules -> Add` and set the following configurations:

```yml
General:
    Name: Tailscale 
    Protocol: Any 
    Outbound zone: Any zone 
    Source address: Any 
    Destination address: Any 
    Action: MASQUERADE
Advanced:
    Outbound device: tailscale0
```

Tailscale is now configured. Now you should be able to connect to the SMB server via the Tailscale VPN. Tailscale is available as a graphical user app for Windows, Mac, Android, and iOS. To connect to Tailscale from Ubuntu, use the following commands.

```bash
$ sudo apt update
$ sudo apt install tailscale
$ #Connect to tailscale and use exit node
$ sudo tailscale up --accept-routes --exit-node=openwrt
$ #Connect to tailscale with split tunneling
$ sudo tailscale up --accept-routes
$ #Disconnect tailscale
$ sudo tailscale down
```
To start Tailscale automatically on router startup, the Tailscale up command needs to be configured in `/etc/init.d`. 

```bash
$ cat > /etc/init.d/tailscalevpn<< EOF
#!/bin/sh /etc/rc.common
# Script to toggle tailscale up and down
 
START=80
 
start() {
 
    tailscale up --advertise-routes=192.168.0.0/16 --advertise-exit-node --accept-routes --netfilter-mode=off
 
}
 
stop() {
 
    tailscale down
 
}
EOF
```

The first line of a `init.d` script must be `#!/bin/sh /etc/rc.common`. `START=80` determines at which point in the `init sequence` this script gets executed. This means that the file will be symlinked as `/etc/rc.d/S80tailscalevpn`. It will then start after the init scripts with `START=79 and below`, but before `START=81 and above`. The `start()` command will be run when it is called with 'start' as its parameter (`/etc/init.d/tailscalevpn start`). Likewise, the `stop()` command will be run when it is called with 'stop' as its parameter (`/etc/init.d/tailscalevpn stop`).

Make sure the file is executable.

```bash
$ chmod +x /etc/init.d/tailscalevpn
```

Enable your script. In order to automatically start the init script on boot, it must be enabled. This will create one or more symlinks in `/etc/rc.d/` which automatically execute at boot time and shutdown time. This makes the application behave as a system service, by starting when the device boots and stopping at shutdown, as configured in the init.d script. If you need to disable the script, use the `disable` command.

```bash
$ /etc/init.d/tailscalevpn enable
```
Your script should now have a symlink in `/etc/rc.d`. At boot time, the `init.d` daemon just starts executing scripts it finds in `/etc/rc.d` according to their file names. In this case, our filename will be `S80tailscalevpn` as described above and will start at the 80th position.

In order to confirm that your script is enabled in `init.d`, run the following command. If the output of the script is `on`, it should be enabled.

```bash
$ /etc/init.d/tailscalevpn enabled && echo on
on
```

That is how you can use a USB on a OpenWRT router as a file sharing SMB server and access it via a VPN. Note that SMB is not optimal for streaming media over VPN. It is good for document access over a VPN. However, streaming within the LAN itself is performant. For streaming over VPN, other services such as Plex server should be considered. Apart from SMB, NFS (Network File Share) can also be used to share files on the LAN. 

Happy Hacking!