petrbalvin a9b8039ef7
Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
feat: full NFSv4.2 server and client in pure Go
Assisted-by: GLM 5.3 Flash
2026-09-21 18:51:17 +02:00

NFSv4.2 server and client in pure Go

One project, two programs: nfsd serves a local directory tree over NFSv4.2, and nfs speaks to any NFSv4.2 server from the command line. Both are written against RFC 8881 and RFC 7862 and speak minor version 2 only, with no portmapper, no mountd and no separate locking protocol: NFSv4 carries all of it on one TCP port.

Status: feature complete against the scope in docs/ARCHITECTURE.md. The full surface is implemented: stateless operations, sessions, locking, delegations, the optional operations of RFC 7862, extended attributes, pNFS with five layout families, directory delegations, Kerberos (RPCSEC_GSS and GSSv3) and RPC-with-TLS, all in pure Go. The server is verified end to end against the Linux kernel NFSv4.2 client: mount -t nfs4 mounts the tree, and reads, writes, creates, removals and attribute ownership behave through it for root and non root callers alike. The same holds for the FreeBSD kernel client on amd64: mount_nfs mounts the tree and the same battery passes.

Platforms

Linux on amd64, arm64, loong64 and riscv64; FreeBSD, OpenBSD and NetBSD on amd64 and arm64; darwin on arm64. The stack is pure Go whose one dependency outside the standard library is interpres, the TOML reader of the server configuration, itself pure Go, so other platforms stay within reach without a rewrite. All three BSDs are verified live on amd64: FreeBSD through its kernel client mounting the tree over NFSv4.2, OpenBSD and NetBSD with both ends of the project running inside the system and across to the Linux server, because their kernel clients speak only NFSv3, which sits outside the project's NFSv4.2 scope. The arm64 assets ship cross compiled. Extended attributes and the sparse operations answer not supported on OpenBSD and NetBSD, whose local backends have no system interface an arbitrary attribute name could use. The darwin target builds from the same tree and ships cross compiled; it carries no runtime testing, and extended attributes and the sparse operations answer not supported there.

Interoperability

NFS is a protocol between independent implementations, and real clients carry their own reading of the specification. During development and testing this server was exercised against the most widely deployed NFS client, the Linux kernel client, and that client deviates from RFC 8881: it presents delegation state that no live server granted and never recovers it, so a server that answers the specification's NFS4ERR_BAD_STATEID without a tolerance layer holds the client in an endless retry loop. On the most widespread platform of all, strict conformance alone produces incompatibility.

The answer here is a tolerance layer confined to documented seams, each one named in the code with the deviation it absorbs, and a wire that is otherwise held strictly to RFC 8881 and RFC 7862. Between this server and this client the protocol runs with no deviation at all: every tolerance path sits dormant, because both ends speak exactly as the specification is written. The pair is the strict reference of the stack, an implementation that keeps behaviour and validity consistent across platforms and a baseline any other implementation can be measured against.

Features

  • XDR codec: the primitive encoding of RFC 4506, with append style encoding and bounds checked decoding
  • ONC RPC: record marking per RFC 5531 with fragment reassembly, the call and reply headers, and AUTH_SYS credentials
  • NFSv4.2 wire vocabulary: operation and error numbers, the attribute table, and the COMPOUND procedure codec
  • Stateless server core: PUTROOTFH, PUTFH, SAVEFH, RESTOREFH, GETFH, LOOKUP, GETATTR, ACCESS, READ, READDIR, WRITE, CREATE, REMOVE, RENAME, SETATTR, LINK, READLINK, COMMIT, SECINFO and SECINFO_NO_NAME over a virtual filesystem
  • Byte range locking: LOCK, LOCKT and LOCKU with per-owner conflict detection and range splitting on unlock
  • Lease and client lifecycle: OPEN_DOWNGRADE, DESTROY_CLIENTID and RECLAIM_COMPLETE with the RFC-mandated second-answer rejection
  • OPEN delegations: read and write delegations granted on the only open of a file, recalled over the back channel on a conflicting open
  • Back channel: the client demultiplexes callback calls from replies on the same connection and answers them
  • Sessions: the RFC 8881 slot table with at-most-once execution, a reply cache per slot, and client reboot detection
  • OPEN and CLOSE: real stateids, share reservations enforced across opens of the same file, and regular file creation through the unchecked and guarded forms
  • Local backend: one local directory tree served behind the nfsfs interface, with ino based file handles and a read write half that creates directories, symlinks, fifos, sockets and device nodes and writes file data
  • nfsd: the server binary, with version reporting and a clean shutdown
  • nfs: the client command: ls, cat, put and stat against a running server, plus the version report
  • pNFS: the metadata server role with flexfiles, files, block, objects and SCSI layout bodies over one emulated device, GETDEVICEINFO and GETDEVICELIST
  • Extended attributes: GETXATTR, SETXATTR, LISTXATTR and REMOVEXATTR end to end over the user namespace of the local backend
  • Optional operations of RFC 7862: SEEK, ALLOCATE, DEALLOCATE, IO_ADVISE, READ_PLUS, WRITE_SAME, COPY, CLONE, COPY_NOTIFY, OFFLOAD_CANCEL, OFFLOAD_STATUS, LAYOUTERROR and LAYOUTSTATS
  • Migration and referrals: the fs_locations and fs_locations_info attributes with NFS4ERR_MOVED stubs
  • Named attributes: OPENATTR with create, lookup, read, write and remove over the synthetic directory
  • Directory delegations: GET_DIR_DELEGATION with CB_NOTIFY on create, rename and remove, and CB_NOTIFY_LOCK when a denied lock frees
  • Kerberos: RPCSEC_GSS with krb5, krb5i and krb5p: the AES profiles of RFC 3961/3962 and the tokens of RFC 4121 in pure Go, plus the version three credential of RFC 7861 with assertion binding
  • RPC-with-TLS: the AUTH_TLS probe and in place connection upgrade of RFC 9289
  • nfsclient: the client package behind the nfs command; it is also the second oracle against the server

Install

From source:

git clone https://sourcedock.dev/petrbalvin/nfs.git
cd nfs
just build

The binaries land in bin/nfsd and bin/nfs.

Quick start

./bin/nfsd -version
mkdir -p /srv/demo && echo "ahoj" > /srv/demo/hello.txt
./bin/nfsd -export /srv/demo -addr 127.0.0.1:2049
./bin/nfs -addr 127.0.0.1:2049 ls
./bin/nfs -addr 127.0.0.1:2049 cat /hello.txt
./bin/nfs -addr 127.0.0.1:2049 put README.md /readme.md

The server refuses to start without an export, serves the tree read and write on the address given, enforcing the permissions of the files against the identity each client presents, and exits on SIGINT and SIGTERM; clients retry through their session replay caches, so a stopped server costs no state. The client speaks to any NFSv4.2 server on the address given, this one included.

Development

just build   # build
just test    # the test suite
just fmt     # format

See docs/DEVELOPMENT.md for the full workflow, and CONTRIBUTING.md for how to contribute.

Documentation

Licence

MIT. See LICENSE.

Copyright © 2026 Petr Balvín

S
Description
Full NFSv4.2 server and client in pure Go: sessions, locking, delegations, pNFS, Kerberos and RPC-with-TLS on one TCP port; standard library only, verified against the Linux kernel client.
Readme MIT
352 KiB
v1.0.0
Latest
2026-09-21 16:51:17 +00:00
Languages
Go 99.6%
Just 0.4%