VPN access to USB SMB Server on OpenWRT
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.
$ ssh [email protected]
To use a USB device with OpenWRT, it needs to be configured with ext4
file system.
$ 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.
$ 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.
$ 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
.
$ DEVICE="/dev/sda1"
$ mkfs.ext4 -L extroot ${DEVICE}
Now, we configure the selected partition as new overlay via fstab
.
$ 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.
$ 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.
$ 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.
$ mkdir /overlay/share
$ chmod a+rwX /overlay/share
Now login to Luci web portal and navigate to Services -> Network share
and configure as follows.
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.
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.
$ vi /etc/passwd
Append at the end of file the following line.
newuser:*:1000:65534:newuser:/var:/bin/false
Replace the two newuser
placeholders with a username of your choice.
Create password for your newuser
.
$ smbpasswd -a newuser
Then restart the Samba4 service.
$ 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.
$ 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.
$ 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.
$ 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:
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:
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.
$ 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
.
$ 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.
$ 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.
$ /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.
$ /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!