nfsserve

repository·main·Indexed 20 days ago

https://github.com/huggingface/nfsserve

A functional implementation of an NFSv3 server in Rust (version 0.11.0). It serves as a user-mode filesystem API that allows developers to create cross-platform filesystems by implementing the vfs::NFSFileSystem trait. The library includes modules for XDR encoding/decoding, core NFS protocol logic, TCP transport, and Portmapper service functionality.

Tokens
11K
Snippets
44
Records
53
Agent score
70%

What's inside nfsserve

  1. How NFS object addressing works

    main

    In this NFSv3 implementation, every filesystem object (directory, file, or symlink) is addressed in two ways:

    1. fileid3: A 64-bit integer equivalent to an inode number.
    2. nfs_fh3: A variable-length opaque object (up to 64 bytes).

    The Lifecycle of an Access:

    1. Mounting: The client uses the MOUNT protocol to request a handle for the root directory (e.g., MNT("/")).
    2. Traversal: To access a nested file (e.g., dir/a.txt), the client must first have the handle for dir/. It then calls LOOKUP(directory_handle, "a.txt") to receive the nfs_fh3 for the target file.
    3. Access: Once the client has the nfs_fh3, it uses that handle for subsequent operations.

    Why use nfs_fh3 instead of just fileid3?

    • Extended Metadata: It allows the server to cache more information than a 64-bit ID allows (e.g., volume identifiers).
    • Cache Invalidation: The handle can include a token unique to the server's current session. If the server restarts, old handles become invalid, signaling the client to clear its caches.
  2. Mount the NFS server on Windows

    main

    On Windows, you must use a Pro edition (Home edition does not include the NFS client). Use the mount.exe command to mount the server to a drive letter (e.g., X:):

    mount.exe -o anon,nolock,mtype=soft,fileaccess=6,casesensitive,lang=ansi,rsize=128,wsize=128,timeout=60,retry=2 \\127.0.0.1\\ X:
  3. Run the demo NFS server

    main

    To run the included demo example, which hosts an NFS server on localhost:11111, use the following commands:

    1. Build the example with the demo feature enabled:
      cargo build --example demo --features demo
    2. Execute the resulting binary:
      ./target/debug/examples/demo

    Note that the demo filesystem is writable.

    cargo build --example demo --features demo
    ./target/debug/examples/demo
  4. Mount the NFS server on Linux

    main

    On Linux, you can mount the running NFS server using the mount.nfs command. You may need sudo privileges. The following command uses specific options to match the demo server's configuration (port 11111, TCP, NFSv3):

    mkdir demo
    sudo mount.nfs -o user,noacl,nolock,vers=3,tcp,wsize=1048576,rsize=131072,actimeo=120,port=11111,mountport=11111 localhost:/ demo
    mkdir demo
    mount.nfs -o user,noacl,nolock,vers=3,tcp,wsize=1048576,rsize=131072,actimeo=120,port=11111,mountport=11111 localhost:/ demo
  5. NFSv3 Mount Protocol Procedures

    main

    The nfsserve implementation follows the RFC 1813 Appendix I specification for the MOUNT program (program number 100005). The following procedures are supported to manage NFSv3 mount requests:

    • MOUNTPROC3_NULL (0): Returns a success reply.
    • MOUNTPROC3_MNT (1): Performs a mount operation for a specific dirpath.
    • MOUNTPROC3_DUMP (2): Returns a list of exported file systems.
    • MOUNTPROC3_UMNT (3): Unmounts a specific dirpath.
    • MOUNTPROC3_UMNTALL (4): Unmounts all currently mounted file systems.
    • MOUNTPROC3_EXPORT (5): Returns details about exported file systems and allowed client groups.
  6. How the MOUNTPROC3_MNT procedure works

    main

    The mountproc3_mnt procedure handles requests to mount a directory.

    1. It deserializes the requested dirpath from the input.
    2. It validates the path against the context.export_name. If the path does not start with the configured export name, it returns MNT3ERR_NOENT.
    3. It attempts to resolve the path to a file ID using context.vfs.path_to_id.
    4. If successful, it returns a mountres3_ok structure containing:
      • fhandle: The file handle for the requested path.
      • auth_flavors: A list of supported authentication flavors (currently supports AUTH_NULL and AUTH_UNIX).
    5. If the path is not found, it returns MNT3ERR_NOENT.

    Upon a successful mount, a signal is sent through context.mount_signal to notify the system of the new mount.

  7. Understand RPC message types and structure

    main

    All RPC messages are encapsulated in the rpc_msg struct, which contains a transaction identifier (xid) and a rpc_body. The rpc_body is a discriminated union that can be either a CALL or a REPLY.

    Call Body (call_body)

    Used by clients to initiate a procedure. It includes:

    • rpcvers: Must be 2.
    • prog: Remote program number.
    • vers: Remote program version number.
    • proc: Remote procedure number.
    • cred: opaque_auth (credentials).
    • verf: opaque_auth (verifier).
  8. Understand RPC authentication flavors and opaque_auth

    main

    Authentication in the RPC protocol is handled via the opaque_auth structure. This structure consists of an auth_flavor (which determines how the body should be interpreted) and an opaque body of bytes.

    Supported auth_flavor values include:

    • AUTH_NULL: No authentication.
    • AUTH_UNIX: Unix-based authentication (using auth_unix structure).
    • AUTH_SHORT: Short authentication.
    • AUTH_DES: DES authentication.

    The auth_unix structure contains:

    • stamp: u32
    • machinename: Vec<u8>
    • uid: u32
    • gid: u32
    • gids: Vec<u32>
    // Example of the structure of an opaque_auth
    let auth = opaque_auth {
        flavor: auth_flavor::AUTH_NULL,
        body: Vec::new(),
    };
  9. Implement the `vfs::NFSFileSystem` trait

    main

    To use nfsserve to build a custom filesystem, you must implement the vfs::NFSFileSystem trait.

    Key requirements for implementation:

    • Object Identification: You must be able to associate every filesystem object (files and directories) with a unique 64-bit ID (fileid3).
    • Directory Listing: Implement directory traversal, noting that pagination requirements can make this part complex.
    • Handles: You must support nfs_fh3 (file handles), which are variable-length opaque objects (up to 64 bytes) used by clients to access objects after an initial LOOKUP.

    Refer to demos/demos.rs for a concrete implementation example and bin/main.rs to see how to initialize and start the service.

  10. Handle NFS RPC calls with handle_nfs

    main

    The handle_nfs function is the primary entrypoint for processing NFSv3 RPC calls. It validates the requested NFS version against the server's supported version (nfs::VERSION) and dispatches the call to the appropriate NFS procedure handler based on the NFSProgram enum. If the version is incorrect, it returns a program mismatch reply. If the procedure is unimplemented, it returns a procedure unavailable reply.

    pub async fn handle_nfs(
        xid: u32,
        call: call_body,
        input: &mut impl Read,
        output: &mut impl Write,
        context: &RPCContext,
    ) -> Result<(), anyhow::Error>
  11. NFSv3 WRITE operation

    main

    The nfsproc3_write function handles the NFSv3 WRITE RPC call. It writes data to a file identified by a file handle at a specific offset.

    Key behaviors:

    • Requires VFSCapabilities::ReadWrite capabilities; otherwise, returns NFS3ERR_ROFS.
    • Performs a sanity check to ensure the length of the provided data matches the count argument; if they mismatch, it returns a garbage arguments reply.
    • Returns Write Cache Coherence (WCC) data containing both before and after attributes to help clients track changes.
    pub async fn nfsproc3_write(
        xid: u32,
        input: &mut impl Read,
        output: &mut impl Write,
        context: &RPCContext,
    ) -> Result<(), anyhow::Error>