BTRFS dual-boot wankery: Arch+Ubuntu+Grub on one filesystem with shared user folders, independent snapshots, and mixed compression

Prelude

tl;dr? Skip to “Two operating systems on BTRFS”.

Having not been home for over eight months, my hatred towards laptops has subsided somewhat and I decided to buy one. It came with Windows 7 Pro pre-installed, and I decided to give it a chance. Two days later, I had to wait over two hours for the system to boot due to this monstrosity:

F*ck windows

I can’t seriously depend on an operating system that incorporates a denial-of-service attack as part of its core design. Additionally, it had managed to mass over 8GB of “queued error reports” over the two day period, so I decided to squash the Windows partition to the end of the disk and put a proper operating system on.

Axiom of choice


I intended to install one Linux distribution on the computer, but found that there were already three partitions on the (MBR-tabled) disk. I googled to see if GRUB can be safely installed to the same partition as the OS since adding two partitions would require an extended partition – which can screw up booting in some circumstances. Through this searching, I got into dirty details of Linux booting and filesystems.

The title of this section is a reference to the Banach-Tarski paradox, as I started with one Linux, dismantled it, and ended up with two Linuxes. I couldn’t decide between Arch Linux or Ubuntu, and I figured that if I could sort out the partition-count problem then I could install both distributions. Solving the partition-count problem is what motivated me to finally try BTRFS.

BTRFS


While Windows and NTFS have slowly been crawling along with half-baked technological advances a decade after everyone else, the rest of the filesystem world has been having a party – IBM’s JFS, Sun’s ZFS, SGI’s XFS, and Linux’s ExtN have been interbreeding and following academic research to produce some powerful features.

BTRFS (backed by Oracle) has one particularly interesting feature, subvolumes. In very simple (and inaccurate) terms, a subvolume is like a filesystem within a filesystem – the BTRFS filesystem can contain multiple subvolumes, and they can also contain subvolumes. Each subvolume can be snapshotted independently of others, can have a different RAID configuration, and can have different (transparent) compression.

The snapshots are copy-on-write, so they occupy (almost) no extra space until a file is modified. This allows regular snapshots to be taken without a linear increase in disk-usage, at the expense of write-times when a snapshotted file is later modified.

Typical home-PC usage of BTRFS


The average home-user experimenting with BTRFS will have an ext2 /boot partition, and a BTRFS structure like one of the following:

[code]
subvolume path => mount point
@ => / (high/slow compression)
@home => /home (low/fast compression)
[/code]

Two operating systems on BTRFS


In order to share user folders between two Linux installations, I opted for this subvolume configuration:

[code]
@ (root subvolume, not mounted)
@/snapshots (snapshots folder, not a subvolume)
@/arch => / (Root filesystem for Arch Linux)
@/ubuntu => / (Root filesystem for Ubuntu Linux)
@/home => /home (Users’ data)
@/root => /root (Root’s data)
@/boot => /boot (GRUB + useful ISOs)
[/code]

The operating system subvolumes are compressed using slow, high compression (zlib), while the root/home subvolumes use fast, lower compression (lzo). Fast/slow refers to write times – the read times for both are very high. Such compression can increase read performance, since less data needs to be read from the disk.

The boot subvolume is not compressed, since GRUB may have issues with compression. There is very little to be gained from compressing it anyway, since it is tiny and the large files are generally initial ramdisks, which are typically gzip-compressed anyway (note: some people use bzip2 or lzma/xz, but those people probably also complain about long boot times).

The key difference between this setup and typical ones is that:

  1. We have two filesystem roots (/) – one for each distribution
  2. The bootloader is on the BTRFS volume too
  3. We have a folder for snapshots

Installing Ubuntu


I was tempted to say that as per usual for dual-boot, you should install the inferior operating system first. Ubuntu seems to have improved a hell of a lot since I last used it though and I quite like Ubuntu now, so I leave that comment purely for the trolling value.

Creating the BTRFS filesystem, creating the subvolumes, then installing the operating systems may seem like all there is to it, but the Ubuntu installer has other ideas. It supports installing to BTRFS, but does not allow you to specify the subvolumes to install to.

After using GParted (in the Ubuntu live environment) to squash the Windows partition to the end of the disk, I ran the Ubuntu installer, created a new BTRFS partition (labelled “Linux”) in the free space and installed to it.

Once the installer is complete, mount the BTRFS filesystem:

[code]
mkdir /mnt/bt

# Replace Linux with the label you gave the filesystem, or
# use the common /dev/sdXY path or a UUID.
mount -L Linux /mnt/bt -o rw,subvol=/

cd /mnt/bt

# Unset the default subvolume
btrfs subvolume set-default . /

# Rename Ubuntu’s subvolume
mv @ ubuntu

# Create a subvolume for /boot
btrfs subvolume create boot

# Move the bootloader files to the boot subvolume
mv ubuntu/boot/* boot/

# Create the other subvolumes
btrfs subvolume create home
btrfs subvolume create root
[/code]

Configure fstab

[code]
LABEL=Linux / btrfs rw,compress=zlib,subvol=ubuntu,errors=remount-ro 0 0
LABEL=Linux /home btrfs rw,compress=lzo,subvol=home,errors=remount-ro 0 0
LABEL=Linux /root btrfs rw,compress=lzo,subvol=root,errors=remount-ro 0 0
LABEL=Linux /boot btrfs rw,compress=no,subvol=boot,errors=remount-ro 0 0
[/code]

Configure grub


When you generate /boot/grub/grub.cfg, grub-mkrelpath is used to generate paths to the initial ramdisks and kernels which GRUB is to invoke. grub-mkrelpath does not understand BTRFS subvolumes though, and will create incorrect paths if your boot subvolume is not at “/boot” within the BTRFS root subvolume – resulting in an unbootable installation. To fix this, hook grub-mkrelpath (or use /boot/ for boot, as I do).

[code]
cd /mnt/bt/ubuntu/usr/bin
# Rename the real grub-mkrelpath
mv grub-mkrelpath{,-real}

# Symlink grub-mkrelpath to our hook
ln -s grub-mkrelpath{-hook,}

# Create the hook (I use vim)
nano grub-mkrelpath-hook
[/code]

grub-mkrelpath-hook:

[code]
#!/bin/bash

if [[ $1 =~ boot ]]; then
echo "$1" | sed -e "s/boot/your-boot-path-goes-here/"
else
grub-mkrelpath-real "$@"
fi
[/code]

A more robust option would be to modify /etc/grub.d/10_linux instead.

You will also need to have grub tell the kernel which subvolume to mount as the filesystem root. This is achieved via the rootflags parameter. This could be set via /etc/default/grub, although I prefer to use a manually-typed grub.cfg, so I don’t use the above hook or the following GRUB_CMDLINE_LINUX. This works for me as I don’t need kernel updates for the Ubuntu system, so I use aptitude to hold the kernel packages at their current version. If I do update the kernel, I will also manually modify grub.cfg to use the newer kernel and initramfs.

[code]
GRUB_CMDLINE_LINUX="rootflags=subvol=ubuntu"
[/code]

Installing Arch


I won’t go through every detail of installing Arch since their is an entire Wiki on the subject, but key things to note:

  • Do not install grub – Ubuntu is already managing the grub bootloader. Or alternatively, chroot into Ubuntu, uninstall grub, then install it again from Arch
  • Remember to bootstrap a text editor, network connectivity tools, and btrfs-progs

Mounting the installation target:

[code]
mkdir /mnt/bt
mount -L Linux /mnt/bt -o rw,subvol=arch,compress=zlib
mount -L Linux /mnt/bt/boot -o rw,subvol=boot,compress=no
mount -L Linux /mnt/bt/root -o rw,subvol=root,compress=lzo
mount -L Linux /mnt/bt/home -o rw,subvol=home,compress=lzo
arch-chroot /mnt/bt
[/code]

fstab:
Much like the Ubuntu fstab, but for the filesystem root (/), change the “subvol” parameter’s value from ‘ubuntu’ to ‘arch’.

Snapshot


When both operating systems are installed and working, take read-only snapshots of them and of the bootloader:

[code]
cd /mnt/bt
mkdir snapshots
btrfs subvolume snapshot -r arch snapshots/arch-base
btrfs subvolume snapshot -r ubuntu snapshots/ubuntu-base
btrfs subvolume snapshot -r boot snapshots/boot-base
[/code]

You can browse these snapshots like any directory or subvolume. You can view files in them, copy a file out and into the working subvolume, or create a writable shapshot of them and replace your working subvolume with that (to roll back).

Accessing one installation from the other


In the extremely likely event that you break one installation, you can chroot into it from another. I will refer to each subvolume (e.g. arch, ubuntu) as red and blue, since these instructions work either way round.

[code]
mkdir /mnt/bt
mount -L Linux /mnt/bt -o rw,subvol=red
cd /mnt/bt
for F in proc sys run tmp dev dev/pts; do mount –bind /$F $F; done
chroot . /usr/bin/bash
mount -a
[/code]

If you want to access the internet from within the chroot (e.g. for aptitude / pacman), you may need to set DNS server(s):

[code]
echo 8.8.4.4 >> /etc/resolv.conf
[/code]

Generating grub.cfg


The smart thing to do would be to add a script in /etc/grub.d which looks for Arch and Ubuntu kernels in /boot, then finds the matching initramfs/initrd and generates the corresponding grub menu item.

I would post the script except I was lazy and haven’t written one yet. Instead, I hard-coded the grub entries to files in /boot/grub/custom/{arch,ubuntu} and added a script to /etc/grub.d/10_custom which simply writes the hard-coded menu entries to grub.cfg (i.e. “cat /boot/grub/custom/*”).

EDIT: I prefer a hand-written grub over an automatically-produced one now, as I don’t like having my boot loader depend on the dumb guesswork of a
bunch of scripts.

Here are examples of custom grub entries for use with BTRFS:

/boot/grub/custom/10_arch

[code]
menuentry ‘Arch’ {
recordfail
load_video
insmod gzio
insmod part_msdos
insmod btrfs
search –no-floppy –label Linux –set=root
linux /boot/vmlinuz-linux root=LABEL=Linux rw rootflags=subvol=arch quiet splash vt.handoff=1
initrd /boot/initramfs-linux.img
}

menuentry ‘Arch (fallback)’ {
recordfail
load_video
insmod gzio
insmod part_msdos
insmod btrfs
search –no-floppy –label Linux –set=root
linux /boot/vmlinuz-linux root=LABEL=Linux rw recovery nomodeset rootflags=subvol=arch
initrd /boot/initramfs-linux-fallback.img
}
[/code]

/boot/grub/custom/20_ubuntu

[code]
menuentry ‘Ubuntu’ {
recordfail
load_video
insmod gzio
insmod part_msdos
insmod btrfs
search –no-floppy –label Linux –set=root
linux /boot/vmlinuz-3.13.0-39-generic root=LABEL=Linux ro rootflags=subvol=ubuntu quiet splash vt.handoff=1
initrd /boot/initrd.img-3.13.0-39-generic
}

menuentry ‘Ubuntu (fallback)’ {
recordfail
load_video
insmod gzio
insmod part_msdos
insmod btrfs
search –no-floppy –label Linux –set=root
linux /boot/vmlinuz-3.13.0-39-generic root=LABEL=Linux ro recovery nomodeset rootflags=subvol=ubuntu
initrd /boot/initrd.img-3.13.0-39-generic
}
[/code]

If you use these, remember to set the kernel versions in the Ubuntu entries, and the kernel flavour (e.g. -lts, -arch, -pf) in the Arch entries.

Automatic daily snapshots


Backups are great when you remember to take them. I don’t remember to take them. Thankfully, systemd provides a really nice way to create services and scheduled tasks. For distributions that still use the old crontab+sysv crap, you can get the same functionality with that but it’s just more messy.

[code]
/etc/systemd/system/autosnap.service
———————————-
[Unit]
Description=Snapshot important BTRFS subvolumes
After=basic.target mnt-bt.mount

[Service]
Type=oneshot
ExecStart=/usr/bin/bash /mnt/bt/create-snapshot.sh "auto"
[/code]

[code]
/etc/systemd/system/autosnap.timer
———————————-
[Unit]
Description=Periodically snapshot important BTRFS subvolumes

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
[/code]

[code]
/mnt/bt/create-snapshot.sh
————————–
#!/bin/bash
set -euo pipefail

declare -a subvols=(arch ubuntu boot home root)
declare fs_root="$(dirname "$0")"
declare target="snapshots/$(date -uIdate)$(if (( $# )); then printf — "-%s" "$@"; fi)"

cd "${fs_root}"

declare -i errors=0

for subvol in "${subvols[@]}"; do
if [ -d "${target}/${subvol}" ]; then
echo "Snapshot of ${subvol} at ${target}/ already exists"
errors=errors+1
elif ! btrfs subvolume snapshot -r "${subvol}" "${target}/"; then
echo "Failed to snapshot ${subvol} to ${target}/"
errors=errors+1
fi
done

exit ${errors}
[/code]

This causes read-only snapshots of the listed subvolumes (subvols array) to be created once per day in the (btrfs)/snapshots/yyyy-mm-dd-auto folder. You can manually trigger snapshotting too by calling:

[code]
/mnt/bt/create-snapshot.sh pre kernel upgrade
[/code]

which would snapshot the given subvolumes to (btrfs)/snapshots/yyyy-mm-dd-pre-kernel-upgrade.

Snapshot serialization


Taking snapshots is great for if you (or some software) screws up the operating system or deleted a bunch of files, but it won’t protect against a disk failure or theft. For the former, BTRFS supports RAID1/10/5/6 (and can do some cool per-subvolume stuff with it too). For the former and for the latter, BTRFS can serialize snapshots and produce “diffs” of them also.

Serialize a BTRFS subvolume


We make an initial backup of our system in January, by taking a snapshot, then sending the snapshot to our backup disk:

[code]
### Duplicate a BTRFS subvolume to another BTRFS disk (e.g. a USB disk)
# 1. Create read-only snapshot of subvolume to send
btrfs subvolume snapshot -r /mnt/bt/home /mnt/bt/snapshots/home-january
# 2. Serialize subvolume, pipe to deserialize it onto another BTRFS filesystem
btrfs send /mnt/bt/home | btrfs receive /mnt/usb/backups
[/code]

Now if we screw things up locally, we can plug the USB disk in and copy files from the January snapshot that is on it (or copy the entier snapshot over if we want to roll back).

Serialize changes to a BTRFS subvolume since a snapshot (incremental backup)


We make a differential backup of our system in February, by taking a snapshot relative to the January snapshot, then sending the incremental snapshot to our backup disk (which must have a copy of the previous snapshot already on it).

[code]
### Serialize a diff to another disk (e.g. USB disk)
# 1. Create read-only snapshot of current state
btrfs subvolume snapshot -r /mnt/bt/home /mnt/bt/snapshots/home-february
# 2. Serialize diff, deserialize it to other BTRFS filesystem
btrfs send -p /mnt/bt/snapshots/home-january /mnt/bt/snapshots/home-february | \
btrfs receive /mnt/usb/backups
[/code]

Now we can roll back to this snapshot, or to the January one. We can pull files from either snapshot too, so if we find that some problem occured before the Feburay backup was taken, we can resolve it via the January backup instead. Since the snapshots are incremental, disk usage is really efficient, even if we have many snapshots.

Send BTRFS snapshot via network/internet


If the network is reliable, or has some layer that handles network problems, this is trivial:

[code]
# Send a snapshot over the network
btrfs send /mnt/bt/snapshots/home-january | \
ssh user@my-server.tld ‘btrfs receive /mnt/backups’
# Send a differential snapshot over the network
btrfs send -p /mnt/snapshots/home-january /mnt/snapshots/home-february | \
ssh user@my-server.tld ‘btrfs receive /mnt/backups’
[/code]

TODO: Document de-duplication (cron job running “bedup dedup”)
TODO: Migrating to a RAID-0(sys)+RAID-1(data) setup, once I have two SSDs in this laptop instead of one 5400rpm HDD…

The infamous tar-pipe

Bulk copying

Copying files on or between Linux/Unix machines is considerably nicer than on their Windows counterparts.  Recursive copy with the built-in command (cp) is available by addition of a single flag, where Windows often requires a separate program (xcopy) and additional flags to achieve the same task.  Copying over networks is a breeze with SCP, where Windows would complain about the shell not supporting UNCs.  Alternatively, you would have to map network shares to drive letters first, and keep track of which letters are what shares.

Of course, on Windows you can drag/drop the files in a GUI much like on Linux, but the moment you go away for a coffee will be the moment that Windows freezes the operation and pops up an annoying dialog asking you if you’re sure that you want to copy some of the files.  Then half an hour later when you return, the copy is still only ten seconds in…

On the Linux front, sometimes we want to customize things a bit:

  • error handling (fail or continue?)
  • symbolic link handling (reference or duplicate?)
  • hard link handling (reference or duplicate?)
  • metadata (copy or ignore?)
  • permissions (copy or ignore?)
  • sparse files (preserve or fill?)
  • filesystem boundaries (recurse or skip?)

Additionally, copying many small files over SCP can take a very long time; SCP performs best with large files. Rather than re-invent the wheel with a whole new file copy & networking program, we can do much better with the tools that we already have, thanks to the modular and interoperable nature of software built upon the Unix philosophy.

Most (or maybe all) of these problems can be solved with rsync, but rsync is not available in all environments (e.g. managed servers, overpriced Microsoft crap).

Tar examples


A simple and highly customizable way to read a load of files is provided by the tape backup utility tar. You can tell it how to handle the various intricacies listed above and it will then recursively read a load of files and write them in a single stream to its output or to a file.

[code]
Common tar options:
-c combine files into an archive
-x extract files from archive
-f <file> set archive filename (default is standard input/output)
-t list names of files in archive
-z, -j, -J use gzip / bzip2 / lzma (de)compression
-v list names of files processed
-C <path> set current working directory to this path before proceeding
[/code]
[code language=”bash”]tar -cf output.tar file1 file2 …[/code]
[code language=”bash”]tar -xf input.tar[/code]

By writing to the standard output, we can pass this archive through a stream compressor, e.g. gzip, bzip2.

[code language=”bash”]
tar -c file1 file2 … | gzip -c > output.tar.gz
[/code]

As this is a common use of tar, the most common compressors can also be specified as flags to tar rather than via a pipeline:

Archive and compress:

[code language=”bash”]
tar -czf output.tar.bz2 file1 file2 …
tar -cjf output.tar.bz2 file1 file2 …
tar -cJf output.tar.xz file1 file2 …
[/code]

Decompress and extract

[code language=”bash”]
tar -xzf input.tar.bz2
tar -xjf input.tar.bz2
tar -xJf input.tar.xz
[/code]

Tar streams can be transferred over networks to a destination computer, where a second tar instance is run. This second one receives the archive stream from the first tar instance and extracts the files onto the destination computer.  This usage of two tar instances over a pipeline has resulted in the technique being nicknamed the “tar-pipe”.

Where network speed is the bottleneck, tar can be instructed to (de)compress the streams on the fly, and offers a choice of codecs.  Note that due to the pipelined nature of this operation, any other streaming (de)compressors can also be used even if not supported by tar.

Tar-pipe examples

In its simplest form, to copy one folder tree to another:

[code language=”bash”]tar -C source/ -c . | tar -C dest/ -x[/code]

One could specify the -h parameter for the left-side tar, to have it follow symbolic links and build a link-free copy of the source in the destination, e.g. for sharing the tree with Windows users.

To copy the files over a network, simply wrap the second tar in an SSH call:

[code language=”bash”]tar -C source/ -c . | ssh user@host ‘tar -C dest/ -x'[/code]

To copy from a remote machine, put the first tar in an SSH call instead:

[code language=”bash”]ssh user@host ‘tar -C source/ -c .’ | tar -C dest/ -x[/code]

SSH provides authentication and encryption, so this form can be used over insecure networks such as the internet.  The SCP utility uses SSH internally. SSH can also provide transparent compression, but the options provided by tar will generally be more useful.

Fast but insecure alternative: netcat


A lightweight and insecure alternative would be to use netcat, which should only be used on secure private networks:

[code language=”bash”]
# On the source machine
tar -C source/ -c *.log | nc host port
[/code]
[code language=”bash”]
# On the target machine
nc -l -p port | tar -C dest/ -x
[/code]

This lightweight form is useful on ultra-low-end hardware such as the Raspberry Pi. It is considerably less robust than the SSH tar-pipe, and is also very insecure.

Compressed tar-pipe


If the network is slow then data compression can easily be used with the tar-pipe:

[code language=”bash”]
# z = gzip (high speed)
# j = bzip2 (compromise)
# J = xz (high compression)

# example, using bzip2 (why would anyone use bzip2 vs choice of xz/gzip nowadays?)
tar -C source/ -cj . | ssh user@host ‘tar -C dest/ -xj’
[/code]

To use a (de)compressor of your choice, provided it is installed on both machines:

[code language=”bash”]
tar -C source/ -c . | my-compressor | ssh user@host ‘my-decompressor | tar -C dest/ -x’
[/code]

You could, for example, use a parallel implementation of a common compressor such as pigz / pbzip2 / pxz, in order to speed things up a bit.

Tar also has a command-line parameter for specifying the compressor/decompresser, provided it follows a certain set of rules.

The choice of (de)compressor and compressor settings depends on the available processing power, RAM, and network bandwidth. Copying between two modern i7 desktops over 1gig ethernet, gzip compression should suffice. On a fast connection, heavy compression (e.g. xz -9e) will create a bottleneck. For a 100mbit ethernet connection or a USB2 connection, bzip2 or xz (levels 1-3) might give better performance. On a Raspberry Pi, a bzip2 tar-pipe might end up being slower (due to CPU bottleneck) than an uncompressed tar-pipe (limited by network bandwidth).

A niche use example of tar+compression

I originally wrote this while solving a somewhat unrelated problem. From in Estonia I can remotely power on my home PC in the UK via a Raspberry Pi Wake-On-LAN server with Dynamic DNS, then I can use port backwarding to access the UK PC. In order to transfer a large amount of data (~1TB) from the UK PC to Estonia, the fastest method (by far) was to use Sneakernet: i.e. copy the data to a USB disk, then have that disk posted to Estonia.

A friend back home plugged the USB disk in, which contained a couple of hundred gigabytes of his own files (which he wanted to send me), but the disk was formatted using Microsoft’s crappy FAT32 file system. After copying a load of small files to the disk, it became very slow to use then while trying to copy “large” files (only >4GB), it failed completely.  I recall Bill Gates once said that we’d never need more than 640kB or RAM – well apparently, he thought that a 4GB file-size limit would also be futureproof…  FAT32 also didn’t support symbolic links, and although Microsoft’s recent versions of NTFS do, their own programs still often fail miserably when handing symbolic links to executable files.

To solve this I wanted to reformat the disk as Ext4, but keep the files that were on it. The only disk in my home PC with enough free space for the files already on the USB disk was very slow (damn Eco-friendly 5400rpm disk!), so moving the files from the USB disk to this one would take a long time. Hence, I used the left half of a tar-pipe with a parallel-gzip (pigz) compressor to copy the data from the USB disk to the very slow disk.

By compressing the data on the fly before storing it, I could fit somewhat more source data into the measly 20MB/s write speed of the slow disk, getting an effective write speed of around 50MB/s – saturating the link from the USB disk, which was one bottleneck that couldn’t be avoided.

After that was complete, I blitzed and re-formatted the USB disk as Ext4, then ran the right-half of the tar-pipe to extract the data from the slow disk back to the USB disk and resumed copying “To Estonia” files to the disk.

Reverse-tunnel SSH through NAT: Remote-access a computer that is behind a NAT router/firewall without manually forwarding ports on the router

Using port-forwarding over an SSH connection allows one to set up a SSH reverse-proxy server, allowing SSH access to a machine that cannot receive incoming connections (e.g. behind NAT firewall/router) by proxying them through a machine which can (e.g. a cheap VPS)

Problem: Dynamic IP, NAT and a bad router

While travelling around Europe (and now, living in Estonia), I occasionally want to connect to my home PC in order to backup photos to it from my camera, or to copy music from it to my phone. Unfortunately my PC is at the family house (now that I don’t have a UK house) and they have a crappy ISP router which “forgets” its settings every time the ISP pushes some new pointless stealth update to it.  The dynamic IP problems arising from the cheap ISP are easily resolved via my Dynamic DNS package, however that isn’t even necessary for the solution in this article.

Reverse port forwarding

This means that my PC in England essentially cannot receive connections from the outside world. However it can make connections. This is where “port backwarding” or rather “reverse port forwarding” comes in.  In typical port forwarding over tunnels, you connect to some other device and messages sent to some port on your end are forwarded down the tunnel to the other end.  In a reverse port forward, you connect to some other device and instruct it to forward packets back down the tunnel to you.

When my PC is powered on, a service loads which creates a tunnel to one of my cloud servers. On the remote end of this tunnel, a port on the server is forwarded back down the tunnel to my home PC. Therefore, connections made to that port on the server are forwarded down the tunnel to my home PC. Hence, my home PC can receive connections from the outside world, as long as they are passed through the tunnel that it created to the outside world. This is similar to a VPN, but much simpler and also quick and easy to configure.  In order to prevent me from having to enter a password for SSH login every time the service starts, I use public key authentication*.  If security is not an issue, you can also create port forwarding (reverse and forward) via netcat and pipes.

* I actually use this between ALL my secured PCs and servers, and disable password login.  This vastly reduces the risk of Chinese botnets getting into my networks.  The server hosting this blog (and others) gets on average three failed login attempts per second from China, plus some from other parts of the world too.  An IPTables rule-set restricting connection initiation rates on certain ports also hardens security against these bots somewhat.

Remote wake-on-lan without VPN or DDNS

First, to power my PC on remotely, I issue a command to one of my cheap cloud servers. A service running on a Raspberry Pi in the England house creates a connection to my cloud server.  The cloud server uses this connection to notify the Pi when a “power on” command is received by the server.  The Pi uses Wake-on-lan to power my PC on – much cheaper and simpler than the 1980’s power-relay method.  My PC powers on and the “port backwarding” service starts.  This method does not require a VPN or my Dynamic DNS service.  Nor is my old remote wake-on-lan interface needed any more.

The code

The service on my home PC runs the following:
[code language=”bash”]ssh -nNTR remoteIntf:remotePort:localHost:localPort remoteUser@remoteHost[/code]
This creates an SSH tunnel between localHost and remoteHost, and forwards any packets bound for remoteIntf:remotePort on remoteHost to localHost:localPort. Using this, we can forward ports from the remote host to any host/port on our local network. In this case, the machine creating the tunnel is also the target for the port forwarding, so we have:
[code language=”bash”]ssh -nNTR localhost:9000:localhost:22 server-user@cloud-server[/code]
which listens for packets via local loopback, port 9000 on the remote host, and forwards them over the tunnel to local loopback, port 22 (SSH port) on the home PC.

I could of course use this on the Pi instead, to tunnel from Pi to server, forward from server throuh Pi to PC:
[code language=”bash”]ssh -nNTR localhost:9000:home-pc:22 server-user@cloud-server[/code]
But I see no advantage in forwarding through the Pi, and it will definitely slow things down a little. Also if I decide to reboot the Pi then I would lose my connection to the home PC via this method.

Using the first method (service on home PC initiates reverse forwarding), to create an SSH connection to my home PC, I simply log onto my cloud server via SSH/Putty and issue:
[code language=”bash”]ssh home-user@localhost -p 9000[/code]
which creates an SSH connection through local loopback to port 9000 – which is forwarded over the previously-created tunnel to my home PC’s SSH port (22).

If I bind the tunnel to the public IP of my server, then I can connect to my home PC from my SSH client here, rather than logging into my server first.

This has security risks, so to enable it you must first set the following in /etc/ssh/sshd_config:
[code language=”bash”]GatewayPorts=yes[/code]
Service on target PC:
[code language=”bash”]ssh -nNTR cloud-server:9000:localhost:22 cloud-user@cloud-server[/code]
Connection from other PC/phone/tablet:
[code language=”bash”]ssh home-user@cloud-server -p 9000[/code]
Obviously, change the user-names and server names/ports to your own preferred values, I use home-user/cloud-user/cloud-server/9000 as examples here.