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
Assisted-by: GLM 5.3 Flash
171 lines
7.6 KiB
Markdown
171 lines
7.6 KiB
Markdown
# 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:
|
|
|
|
```sh
|
|
git clone https://sourcedock.dev/petrbalvin/nfs.git
|
|
cd nfs
|
|
just build
|
|
```
|
|
|
|
The binaries land in `bin/nfsd` and `bin/nfs`.
|
|
|
|
## Quick start
|
|
|
|
```sh
|
|
./bin/nfsd -version
|
|
mkdir -p /srv/demo && echo "ahoj" > /srv/demo/hello.txt
|
|
./bin/nfsd -export /srv/demo -addr 127.0.0.1:2049
|
|
```
|
|
|
|
```sh
|
|
./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
|
|
|
|
```sh
|
|
just build # build
|
|
just test # the test suite
|
|
just fmt # format
|
|
```
|
|
|
|
See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for the full workflow, and
|
|
[CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute.
|
|
|
|
## Documentation
|
|
|
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow
|
|
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md): prerequisites and recipes
|
|
- [docs/CLI.md](docs/CLI.md): the command line reference
|
|
- [man/nfsd.1](man/nfsd.1) and [man/nfs.1](man/nfs.1): the manpages of the
|
|
two commands
|
|
|
|
## Licence
|
|
|
|
MIT. See [LICENSE](LICENSE).
|
|
|
|
Copyright © 2026 [Petr Balvín](https://petrbalvin.org)
|