File serving over the Internet, for one

For personal notes and development, there’s a lot to be said for using a shared network file system. Dropbox has a collection of notification and sharing features that don’t naturally fit development use (where many files are intermediate products of a build process and not particularly interesting at any point of their lifecycle). In this post, I outline the steps to build a single server-to-multiple client file service configuration that might be suitable for text editing and relatively small software builds. (You always want one or more big servers with fast I/O for large builds.) Technologies used are Wireguard, MUNGE, and 9P; examples are given for Ubuntu.

Dropbox is great for managing files shared with others, and also convenient for some forms of archiving.

The goal is to set up a network share that can be used from various systems. In my initial deployment there will be a stationary workstation and a laptop. Both are running current Ubuntu. The server is running current Ubuntu as well.

In most situations, my network connectivity—even tethered—is better that what we had in university and at work. (MPK17 received hundred megabit Ethernet long after most of the other buildings on campus.) That means, as long as we can put up with some latency, a network file service might be sufficient.

Our network transport is a Wireguard link to the server from each client. This choice means that the authentication and file operations are encrypted as they pass between client and server.

We have set up Wireguard using dsnet for ease of administration. dsnet is a lightweight command line utility for managing a Wireguard server and its clients; in particular, it manages the IP address assignment for the clients. Follow the dsnet author’s instructional post for the initial configuration.

$ sudo apt install wireguard
$ wget https://github.com/naggie/dsnet/releases/download/v0.1/dsnet-linux-amd64
$ sudo cp dsnet-linux-amd64 /usr/local/bin/dsnet
$ sudo dsnet init
[ edit /etc/dsnetconfig.json as appropriate ]
$ sudo dsnet up

dsnet's add subcommand can be used to generate the configuration for a new client.

$ sudo dsnet add glowy > dsnet-glowy.conf

(glowy is a Hades Canyon NUC with an illuminated logo on its top.)

Copy this configuration to the appropriate workstation. You’ll also need the IP address of the server’s Wireguard interface; we’ll refer to this as SERVERADDR below.

On Ubuntu, wg-quick requires resolvconf. One possible workaround is:

$ sudo ln -s /usr/bin/resolvectl /usr/local/bin/resolvconf

On that workstation, test the configuration using

$ sudo apt install wireguard
$ wg-quick up ./dsnet-glowy.conf

Both authentication and file service will use the Wireguard link for transport. The link is not configured to take all traffic from the client, although that is certainly possible.

Authentication for file service is Munge, which will securely communicate the user ID (UID) and group ID (GID) making each request. Your UIDs (and GIDs) should match across systems.

Munge reminds me somewhat of NIS, in that a shared secret is used to allow a group of machines to trust one another’s user and group IDs. Munge has superior security characteristics to NIS, benefiting from delivering a smaller set of features and using, well, cryptography.

On the server and each client install Munge.

$ sudo apt install munge

The postinstall script for this package creates a weak key to keep the installation time low, so on the server generate a better one:

$ sudo create-munge-key -f -r

You need to place a copy of this key in /etc/munge/munge.key on each client system. This file needs to be owned by the munge user and group. Updating the key will require it to be distributed to each participating system, client and server. (And restarting the service as well, once the key is in place.)

Finally, we use 9P for network file service. We are using the diod implementation.

On the server, we create a /sync directory, under which we will collect the directories we plan to share.

$ apt install diod
$ mkdir /sync/$USER

Then add /sync/$USER to /etc/diod.conf

You can invoke diod directly, with logging off and authentication on:

$ sudo diod -f

(There are command line options for debugging and for deactivating authentication.)

Finally set DIOD_ENABLE to true in /etc/default/diod

$ sudo vim /etc/default/diod

and ensure that the diod service starts successfully.

On the client, create a target directory for the mount

$ mkdir ~/sync

And then use diodmount to make the mount:

$ apt install diod
$ sudo diodmount -n $SERVERADDR:/sync/$USER ~/sync
$ ls ~/sync

Since this mount was the first time I had used 9P, I spent a brief period of time copying around ~80MB files, just to get a feel for the performance.

$ time cp $EIGHTY_MB_FILE /tmp
cp $EIGHTY_MB_FILE /tmp  0.00s user 0.14s system 66% cpu 0.222 total
$ time cp $EIGHTY_MB_FILE ~/sync
cp $EIGHTY_MB_FILE sync  0.00s user 0.23s system 0% cpu 2:38.76 total
$ stat $EIGHTY_MB_FILE
  File: $EIGHTY_MB_FILE
  Size: 81224528  	Blocks: 158648     IO Block: 4096   regular file
Device: 802h/2050d	Inode: 37748762    Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1001/   $USER)   Gid: (   50/   staff)
Access: 2020-10-27 18:05:41.399117077 -0700
Modify: 2019-06-06 06:58:17.680609774 -0700
Change: 2020-10-27 15:26:29.596553909 -0700
 Birth: 2019-06-06 06:58:13.608637639 -0700

That’s an effective rate of ~511 kilobytes/second. I have a typical Comcast home cable link, so there’s a maximum of ~6 megabits/second available.

Linux has FS-Cache, a built-in caching subsystem for network file systems. We can enable caching for 9P by using the cache mount option:

$ sudo umount ~/sync
$ sudo diodmount -n $SERVERADDR:/sync/$USER ~/sync -o cache=fscache

If we rerun our copy operation, we see

$ time cp $EIGHTY_MB_FILE ~/sync
cp $EIGHTY_MB_FILE sync  0.01s user 0.18s system 1% cpu 16.538 total

Which is an effective speed of ~4911 kilobytes/second. I suspect most of this speedup is just making the operations asynchronous from the perspective of our cp(1) invocation.

There isn’t much recent work on 9P performance. Van Hensbergen and Minnich (2005) showed that 9P was generally competitive with NFS on a variety of workloads.

In an attempt to make detaching and reattaching the mount simple, I wrote a brief script, named toggle-sync. You’ll need to make sure HOME, SERVERADDR, and USER are defined.

#!/bin/bash -p

hostname=$(uname -n)
mountpoint=$HOME/sync
server=$SERVERADDR
serverdir=/sync/$USER

if ! which wg-quick; then
    echo "may need apt install wireguard"
    exit 1
fi

if ! which diodmount; then
    echo "may need apt install diod"
    exit 1
fi

if ! pgrep -af munged; then
    echo "munged not running; may need apt install munge and completed key configuration"
fi

if ip addr | grep -q "dsnet-$hostname"; then
    echo "dsnet-$hostname wireguard link present"
else
    wg-quick up "./dsnet-$hostname.conf"
fi

if mount | grep -q /home/sch/sync; then
    echo "$mountpoint mounted; unmounting"
    sudo umount "$mountpoint"
else
    echo "$mountpoint not mounted; mounting"
    sudo diodmount -n "$server:$serverdir" "$mountpoint" -o cache=fscache
fi

So far this setup has been working well for notes and small files. This post (and its Hugo build) were written (and invoked) from ~/sync. It’s nice to be able to switch machines without losing my working state.