gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/lisafs/README.md (about)

     1  # LInux SAndbox FileSystem (LISAFS) Protocol
     2  
     3  ## Overview
     4  
     5  LISAFS stands for Linux Sandbox File System. It is a protocol that can be used
     6  by sandboxed (untrusted) applications to communicate with a trusted RPC server.
     7  The untrusted client can make RPCs to perform file system operations on the
     8  server.
     9  
    10  ### Inspiration
    11  
    12  LISAFS was mainly inspired by gVisor’s need for such a protocol. Historically,
    13  gVisor used a custom extension of the 9P2000.L protocol to talk to gofer
    14  processes. 9P proved to be chatty in certain situations, inducing a lot of RPCs.
    15  The overhead associated with a round trip to the server seemed to be
    16  deteriorating performance. LISAFS aims to be more economical.
    17  
    18  ## Background
    19  
    20  This protocol aims to safely expose filesystem resources over a connection
    21  between an untrusted client and a trusted server. Usually these filesystem
    22  resources are exposed by path-based APIs (e.g. Linux’s path based syscalls).
    23  However, such path based operations are susceptible to symlink-based attacks.
    24  Because path based operations require re-walking to the file on the server; a
    25  malicious client might be able to trick the server into walking on a malicious
    26  symlink on the path.
    27  
    28  Hence, LISAFS focuses on providing an API which is file-descriptor based (e.g.
    29  Linux’s FD-based syscalls). LISAFS provides various FD abstractions over the
    30  protocol which can be opened by the client and used to perform filesystem
    31  operations. Filesystem operations happen **on** these FD abstractions. Because
    32  of that, these FD abstractions on the server do not need to perform rewalks.
    33  They can simply reuse the host FD or whatever resource is attached to that file.
    34  RPCs in lisafs are operations on these FD abstractions.
    35  
    36  ## Concepts
    37  
    38  ### Server
    39  
    40  A LISAFS server is an agent that serves one file system tree that may be
    41  accessed/mutated via RPCs by LISAFS clients. The server is a trusted process.
    42  For security reasons, the server must assume that the client can be potentially
    43  compromised and act maliciously.
    44  
    45  #### Concurrency
    46  
    47  The server must execute file system operations under appropriate concurrency
    48  constraints to prevent a malicious client from tricking the server into walking
    49  on hazardous symlinks and escaping the filesystem tree being served. To provide
    50  such concurrency guarantees for each node in the file system tree, the server
    51  must maintain the file system tree in memory with synchronization primitives for
    52  each node. Server must provide the following concurrency guarantees:
    53  
    54  -   None: Provides no guarantees; any operation could be concurrently happening
    55      on this node. This can be provided to operations that don’t require touching
    56      the file system tree at all.
    57  -   Read: Guarantees to be exclusive of any write operation on this node or
    58      global operation. But this may be executed concurrently with other read
    59      operations occurring on this node.
    60  -   Write: Guarantees to be exclusive of any read or write operation occurring
    61      on this node or any global operation.
    62  -   Global: Guarantees to be exclusive of any read, write or global operation
    63      across all nodes.
    64  
    65  Some things that follow:
    66  
    67  -   Read guarantee on a node `A` also guarantees that the client can not
    68      invalidate any node on the path from root to `A`. To invalidate a node, the
    69      client must delete it. To delete any intermediate node (directory) up until
    70      `A`, the client must first delete all children of that directory including
    71      `A`, which is impossible because that requires a write guarantee on `A`.
    72  -   If there are two clients accessing independent file system *subtrees* from
    73      the same server, it might be beneficial to configure them to use different
    74      server objects (maybe in the same agent process itself) to avoid unnecessary
    75      synchronization overheads caused by server-wide locking.
    76  
    77  ### Client
    78  
    79  A LISAFS client is an untrusted process which can potentially be malicious. The
    80  client is considered to be running alongside an untrusted workload in a sandbox
    81  environment. As part of defense in depth strategy, it is assumed that the
    82  sandbox environment can be compromised due to security vulnerabilities. And so
    83  the client should be treated as a potentially malicious entity. The client is
    84  immutably associated with a server and a file system tree on the server that it
    85  can access.
    86  
    87  ### Connection
    88  
    89  A connection is a session established between a client and a server. The
    90  connection can only be started using the socket communicator (see below). See
    91  “Setup & Configuration” section to see how the initial communicator can be set
    92  up.
    93  
    94  #### File Descriptor
    95  
    96  Linux provides file system resources via either path-based syscalls or FD-based
    97  syscalls. LISAFS attempts to emulate Linux's FD-based file system syscalls.
    98  Path-based syscalls are slower because they have to rewalk the host kernel’s
    99  dentry tree and they are also susceptible to symlink based attacks. So LISAFS
   100  provides various FD abstractions to perform various file system operations –
   101  very similar to the FD-based syscalls. Each FD abstraction is identified by an
   102  FDID. FDIDs are local to the connection they belong to. An FDID is defined by a
   103  uint64 integer. The server implementation does not have to keep track of free
   104  FDIDs to reuse – which requires additional memory. Instead the server can
   105  naively increment a counter to eternity to generate new FDIDs. uint64 is large
   106  enough.
   107  
   108  ### Communicator
   109  
   110  A communicator is a communication pathway between the server and client. The
   111  client can send messages on a communicator and expect a response from the server
   112  on that communicator. A connection starts off with just 1 socket communicator.
   113  The client can request to create more communicators by making certain RPCs over
   114  existing communicators. The server may choose to deny additional communicators
   115  after some arbitrary limit has been reached.
   116  
   117  A communicator may also be capable of sending open file descriptors to the peer
   118  endpoint. This can be done with SCM_RIGHTS ancillary messages over a socket.
   119  Hence forth, this is called “donating an FD”. FD donation of course is an
   120  inter-process mechanism and can not be done if the client and server are on
   121  different hosts. But donating FDs enables clients to make staggering
   122  optimizations for IO-intensive workloads and avoid a lot of RPC round-trips and
   123  buffer management overheads. The usage of the donated FDs can be monitored using
   124  seccomp filters around the client.
   125  
   126  Each communicator has a header which contains metadata. The communicator header
   127  format is immutable. To enhance/update a communicator header, a new communicator
   128  must be created which uses the new header. A new RPC must be introduced that
   129  sets up such a communicator. The communicator header may optionally contain the
   130  payload length (the size of the message in bytes). The client/server may use
   131  this information to do more efficient buffer management and limit the number of
   132  message bytes to read. However, this information is redundant. The size of the
   133  message in bytes is either predetermined or can be inferred while deserializing.
   134  See “Wire Format” section for more details. If the payload length in header and
   135  the message's manifest size disagree, the message size can be used (or an error
   136  can be returned).
   137  
   138  #### Socket Communicator
   139  
   140  A socket communicator uses a unix domain socket pair. The client and server own
   141  one end each. The header looks like this:
   142  
   143  ```
   144  type sockHeader struct {
   145      payloadLen uint32
   146      message    uint16
   147      _          uint16 // Need to make struct packed.
   148  }
   149  ```
   150  
   151  The socket communicator is only capable of making synchronous RPCs. It can not
   152  be used to make multiple RPCs concurrently. One client thread should acquire
   153  this socket communicator, send a request to the server, block until the response
   154  is received and only then release it. An alternative approach would be to add a
   155  RPC tag in the header and release the communicator after sending a request. The
   156  response from the server would contain the same tag and the client can do
   157  book-keeping and pass the response to the appropriate thread. That would allow
   158  for asynchronous RPCs. If need be, an asynchronous socket communicator can be
   159  introduced as a new communicator.
   160  
   161  #### Flipcall Channel Communicator
   162  
   163  The channel communicator is inspired from gVisor’s flipcall package
   164  (`pkg/flipcall`) which “implements a protocol providing Fast Local Interprocess
   165  Procedure Calls between mutually-distrusting processes”. In this communicator,
   166  both ends (server and client) own a flipcall endpoint. Flipcall endpoints can
   167  “switch” to the other endpoint synchronously and yield control, hence enabling
   168  synchronous RPCs. For this reason, channel communicators can not accommodate
   169  asynchronous RPCs.
   170  
   171  This communicator uses a shared memory region between both flipcall endpoints
   172  into which messages are written. Accessing the memory region is faster than
   173  communicating over a socket which involves making a syscall and passing a buffer
   174  to the kernel which is copied over to the other process. Due to the memory
   175  region being shared between mutually-distrusting processes, the server must be
   176  cautious that a malicious client might concurrently corrupt the memory while the
   177  server is reading it.
   178  
   179  The header looks like this:
   180  
   181  ```
   182  type channelHeader struct {
   183      // flipcall header
   184      connState uint32
   185      dataLen   uint32
   186      reserved  uint64
   187      // channel header
   188      message   uint16
   189      numFDs    uint8
   190      _         uint8 // Need to make struct packed.
   191  }
   192  ```
   193  
   194  #### RPC Overhead
   195  
   196  Making an RPC is associated with some RPC overhead which is independent of the
   197  RPC itself. The socket and channel communicator both suffer from scheduling
   198  overhead. The host kernel has to schedule the server’s communicator thread
   199  before the server can process the request. Similarly, after the server responds,
   200  the client’s receiving thread needs to be scheduled again. Using FUTEX_SWAP in
   201  flipcall reduces this overhead. There may be other overheads associated with the
   202  exact mechanism of making the switch.
   203  
   204  ### Control FD
   205  
   206  A control FD is an FD abstraction which is used to perform certain file
   207  operations on a file. It can only be created by performing `Walk` operations
   208  from a directory Control FD. The Walk operation returns a Control FD for each
   209  path component to the client. The Control FD is then immutably associated with
   210  that file node. The client can then use it to perform operations on the
   211  associated file. It is a rather unusual concept. It is not an inode, because
   212  multiple control FDs are allowed to exist on the same file. It is not a file
   213  descriptor because it is not tied to any access mode, i.e. a control FD can
   214  change the underlying host FD’s access mode based on the operation being
   215  performed.
   216  
   217  A control FD can modify or traverse the filesystem tree. For example, it
   218  supports operations like Mkdir, Walk, Unlink and Rename. A control FD is the
   219  most fundamental FD abstraction, which can be used to create other FD
   220  abstractions as detailed below.
   221  
   222  ### Open FD
   223  
   224  An Open FD represents an open file descriptor on the protocol. It resonates
   225  closely with a Linux file descriptor. It is associated with the access mode it
   226  was opened with. Its operations are not allowed to modify or traverse the
   227  filesystem tree. It can only be created by performing `Open` operations on a
   228  Control FD (more details later in “RPC Methods” section). An Open FD is also
   229  immutably associated with the Control FD it was opened on.
   230  
   231  ### Bound Socket FD
   232  
   233  A Bound Socket FD represents a `socket(2)` FD on the protocol which has been
   234  `bind(2)`-ed. Operations like `Listen` and `Accept` can be performed on such FDs
   235  to accept connections from the host and donate the accepted FDs to the client.
   236  It can only be created by performing `Bind` operation on a Control FD (more
   237  details later in “RPC Methods” section). A Bound Socket FD is also immutably
   238  associated with a Control FD on the bound socket file.
   239  
   240  ## Setup & Configuration
   241  
   242  The sandbox configuration should define the servers and their sandboxed clients.
   243  The client:server relationship can be many:1. The configuration must also define
   244  the path in the server at which each client is mounted. The mount path is
   245  defined via trusted configuration, as opposed to being defined by an RPC from a
   246  potentially compromised client.
   247  
   248  The sandbox owner should use this configuration to create `socketpair(2)`s for
   249  each client/server combination. The sandbox owner should then start the sandbox
   250  process (which contains the clients) and the server processes. The sandbox owner
   251  should then donate the socket FDs and their configuration appropriately. The
   252  server should have information about each socket FD’s corresponding client –
   253  what path the client is mounted on, what kinds of RPCs are permissible. The
   254  server can then spawn threads running the socket communicator that block on
   255  reading from these sockets.
   256  
   257  For example, gVisor’s runsc, which is an OCI compatible runtime, passes the OCI
   258  runtime spec to its gofer. It additionally passes one end of all the
   259  `socketpair(2)`s to the gofer. The gofer then reads all the mount points from
   260  the runtime spec and uses the sockets to start connections serving those mount
   261  paths.
   262  
   263  ## RPC Methods
   264  
   265  -   All RPC methods must be non-blocking on the server.
   266  -   RPC methods were designed to minimize the number of roundtrips between
   267      client and server to reduce RPC overhead (see section “RPC Overhead” above)
   268      incurred by the client.
   269  -   The request and response message structs are defined in this package by the
   270      names defined below.
   271  -   Each RPC is defined by a Message ID (MID). When the server receives a MID,
   272      it should read the Request struct associated with that MID. Similarly the
   273      client should read the Response struct associated with that MID.
   274  -   MID is a uint16 integer. The first 256 MIDs [0,255] are reserved for
   275      standard LISAFS protocol. Proprietary extensions of LISAFS can use the
   276      remainder of the available range.
   277  
   278  MID | Message      | Request         | Response                                                           | Description
   279  --- | ------------ | --------------- | ------------------------------------------------------------------ | -----------
   280  0   | Error        | N/A             | ErrorResp                                                          | Returned from server to indicate error while handling RPC. If Error is returned, the failed RPC should have no side effects. Any intermediate changes made should be rolled back. This is a response-only message. ErrorResp.errno should be interpreted as a Linux error code.
   281  1   | Mount        |                 | MountResp<br><br>Optionally donates: \[mountPointHostFD\]          | Mount establishes a connection. MountResp.root is a Control FD for the mountpoint, which becomes the root for this connection. The location of the connection’s mountpoint on the server is predetermined as per sandbox configuration. Clients can not request to mount the connection at a certain path, unlike mount(2). MountResp.maxMessageSize dictates the maximum message size the server can tolerate across all communicators. This limit does not include the communicator’s header size. MountResp.supportedMs contains all the MIDs that the server supports. Clients can use this information for checking feature support. The server must provide a read concurrency guarantee on the root node during this operation.
   282  2   | Channel      |                 | ChannelResp<br><br>Donates: \[dataFD, fdSock\]                     | Channel sets up a new communicator based on a shared memory region between the client and server. dataFD is the host FD for the shared memory file. fdSock is a host socket FD that the server will use to donate FDs over this channel. ChannelResp’s dataOffset and dataLength describe the shared memory file region owned by this channel. No concurrency guarantees are needed. ENOMEM is returned to indicate that the server hit the max channels limit.
   283  3   | FStat        | StatReq         | [struct statx](https://man7.org/linux/man-pages/man2/statx.2.html) | Fstat is analogous to fstat(2). It returns struct statx for the file represented by StatReq.fd. FStat may be called on a Control FD or Open FD. The server must provide a read concurrency guarantee on the file node during this operation.
   284  4   | SetStat      | SetStatReq      | SetStatResp                                                        | SetStat does not correspond to any particular syscall. It serves the purpose of fchmod(2), fchown(2), ftruncate(2) and futimesat(2) in one message. This enables client-side optimizations where the client is able to change multiple attributes in 1 RPC. It must be called on Control FDs only. One instance where this is helpful is in overlayfs implementation which requires changing multiple attributes at the same time. The failure of setting one attribute does not terminate the entire operation. SetStatResp.failureMask should be interpreted as stx\_mask and indicates all attributes that failed to be modified. In case failureMask != 0, SetStatResp.failiureErrno indicates any one of the failure errnos. The server must provide a write concurrency guarantee on the file node during this operation.
   285  5   | Walk         | WalkReq         | WalkResp                                                           | Walk walks multiple path components described by WalkReq.path starting from Control FD WalkReq.dirFD. The walk must terminate if a path component is a symlink or a path component does not exist and return all the inodes walked so far. The reason for premature termination of walk is indicated via WalkResp.status. Symlinks can not be walked on the server. The client must Readlink the symlink and rewalk its target + the remaining path. The server must provide a read concurrency guarantee on the directory node being walked and should protect against renames during the entire walk.
   286  6   | WalkStat     | WalkReq         | WalkStatResp                                                       | WalkStat is similar to Walk, except that it only returns the statx results for the path components. It does not return a Control FD for each path component. Additionally, if the first element of WalkReq.path is an empty string, WalkStat also returns the statx results for WalkReq.dirFD. This is useful in scenarios where the client already has the Control FDs for a path but just needs statx results to revalidate its state. The server must provide a read concurrency guarantee on the directory node being walked and should protect against renames during the entire walk.
   287  7   | OpenAt       | OpenAtReq       | OpenAtResp<br><br>Optionally donates: \[openHostFD\]               | OpenAt is analogous to openat(2). It creates an Open FD on the Control FD OpenAtReq.fd using OpenAtReq.flags. The server may donate a host FD opened with the same flags. The client can directly make syscalls on this FD, instead of making RPCs as an optimization. The server must provide a read concurrency guarantee on the file node during this operation.
   288  8   | OpenCreateAt | OpenCreateAtReq | OpenCreateAtResp<br><br>Optionally donates: \[openHostFD\]         | OpenCreateAt is analogous to openat(2) with flags that include O\_CREAT
   289  9   | Close        | CloseReq        |                                                                    | Close is analogous to calling close(2) on multiple FDs. CloseReq.fds accepts an array of FDIDs. The server drops the client’s reference on the FD and stops tracking it. Future calls to the same FDID will return EBADF. However, this may not necessarily release the FD’s resources as other references might be held as per reference model. No concurrency guarantees are needed.
   290  10  | FSync        | FsyncReq        |                                                                    | FSync is analogous to calling fsync(2) on multiple FDs. FsyncReq.fds accepts an array of FDIDs. The errors from syncing the FDs are ignored. The server must provide a read concurrency guarantee on the file node during this operation.
   291  11  | PWrite       | PWriteReq       | PWriteResp                                                         | PWrite is analogous to pwrite(2). PWriteReq.fd must be an Open FD. Fields in PWriteReq are similar to pwrite(2) arguments. PWriteResp.count is the number of bytes written to the file. The server must provide a write concurrency guarantee on the file node during this operation.
   292  12  | PRead        | PReadReq        | PReadResp                                                          | PRead is analogous to pread(2). PReadReq.fd must be an Open FD. Fields in PReadReq are similar to pread(2) arguments. PReadResp contains a buffer with the bytes read. The server must provide a read concurrency guarantee on the file node during this operation.
   293  13  | MkdirAt      | MkdirAtReq      | MkdirAtResp                                                        | MkdirAt is analogous to mkdirat(2). It additionally allows the client to set the UID and GID for the newly created directory. MkdirAtReq.dirFD must be a Control FD for the directory inside which the new directory named MkdirAtReq.name will be created. It returns the new directory’s Inode. The server must provide a write concurrency guarantee on the directory node during this operation.
   294  14  | MknodAt      | MknodAtReq      | MknodAtResp                                                        | MknodAt is analogous to mknodat(2). It additionally allows the client to set the UID and GID for the newly created file. MknodAtReq.dirFD must be a Control FD for the directory inside which the new file named MknodAtReq.name will be created. It returns the new file’s Inode. The server must provide a write concurrency guarantee on the directory node during this operation.
   295  15  | SymlinkAt    | SymlinkAtReq    | SymlinkAtResp                                                      | SymlinkAt is analogous to symlinkat(2). It additionally allows the client to set the UID and GID for the newly created symlink. SymlinkAtReq.dirFD must be a Control FD for the directory inside which the new symlink named SymlinkAtReq.name is created. The symlink file contains SymlinkAtReq.target. It returns the new symlink’s Inode. The server must provide a write concurrency guarantee on the directory node during this operation.
   296  16  | LinkAt       | LinkAtReq       | LinkAtResp                                                         | LinkAt is analogous to linkat(2) except it does not accept any flags. In Linux, AT\_SYMLINK\_FOLLOW can be specified in flags but following symlinks on the server is not allowed in lisafs. LinkAtReq.dirFD must be a Control FD for the directory inside which the hard link named LinkAtReq.name is created. This hard link is an existing file identified by LinkAtReq.target. It returns the link’s Inode. The server must provide a write concurrency guarantee on the directory node during this operation.
   297  17  | FStatFS      | FStatFSReq      | StatFS                                                             | FStatFS is analogous to fstatfs(2). It returns information about the mounted file system in which FStatFSReq.fd is located. FStatFSReq.fd must be a Control FD. The server must provide a read concurrency guarantee on the file node during this operation.
   298  18  | FAllocate    | FAllocateReq    |                                                                    | FAllocate is analogous to fallocate(2). Fields in FAllocateReq correspond to arguments of fallocate(2). FAllocateReq.fd must be an Open FD. The server must provide a write concurrency guarantee on the file node during this operation.
   299  19  | ReadLinkAt   | ReadLinkAtReq   | ReadLinkAtResp                                                     | ReadLinkAt is analogous to readlinkat(2) except, it does not perform any path traversal. ReadLinkAtReq.fd must be a Control FD on a symlink file. It returns the contents of the symbolic link. The server must provide a read concurrency guarantee on the file node during this operation.
   300  20  | Flush        | FlushReq        |                                                                    | Flush may be called before Close on an Open FD. It cleans up the file state. Its behavior is implementation specific. The server must provide a read concurrency guarantee on the file node during this operation.
   301  21  | Connect      | ConnectReq      | Donates: \[sockFD\]                                                | Connect is analogous to calling socket(2) and then connect(2) on that socket FD. The socket FD is created using socket(AF\_UNIX, ConnectReq.sockType, 0). ConnectReq.fd must be a Control FD on a socket file that is connect(2)-ed to. On success, the socket FD is donated. The server must provide a read concurrency guarantee on the file node during this operation.
   302  22  | UnlinkAt     | UnlinkAtReq     |                                                                    | UnlinkAt is analogous to unlinkat(2). Fields in UnlinkAtReq are similar to unlinkat(2) arguments. UnlinkAtReq.dirFD must be a Control FD for the directory inside which the child named UnlinkAtReq.name is to be unlinked. The server must provide a write concurrency guarantee on the directory and to-be-deleted file node during this operation.
   303  23  | RenameAt     | RenameAtReq     |                                                                    | RenameAt is analogous to renameat(2). Fields in RenameAtReq are similar to renameat(2) arguments. RenameAtReq.oldDir and RenameAtReq.newDir must be Control FDs on the old directory and new directory respectively. The file named RenameAtReq.oldName inside old directory is renamed into new directory with the name RenameAtReq.newName. The server must provide global concurrency guarantee during this operation.
   304  24  | Getdents64   | Getdents64Req   | Getdents64Resp                                                     | Getdents64 is analogous to getdents64(2). Fields in Getdents64Req are similar to getdents64(2) arguments. Getdents64Req.dirFD should be a Open FD on a directory. It returns an array of directory entries (Dirent). Each dirent also contains the dev\_t for the entry inode. This operation advances the open directory FD’s offset. The server must provide a read concurrency guarantee on the file node during this operation.
   305  25  | FGetXattr    | FGetXattrReq    | FGetXattrResp                                                      | FGetXattr is analogous to fgetxattr(2). Fields in FGetXattrReq are similar to fgetxattr(2) arguments. It must be invoked on a Control FD. The server must provide a read concurrency guarantee on the file node during this operation.
   306  26  | FSetXattr    | FSetXattrReq    |                                                                    | FSetXattr is analogous to fsetxattr(2). Fields in FSetXattrReq are similar to fsetxattr(2) arguments. It must be invoked on a Control FD. The server must provide a write concurrency guarantee on the file node during this operation.
   307  27  | FListXattr   | FListXattrReq   | FListXattrResp                                                     | FListXattr is analogous to flistxattr(2). Fields in FListXattrReq are similar to flistxattr(2) arguments. It must be invoked on a Control FD. The server must provide a read concurrency guarantee on the file node during this operation.
   308  28  | FRemoveXattr | FRemoveXattrReq |                                                                    | FRemoveXattr is analogous to fremovexattr(2). Fields in FRemoveXattrReq are similar to fremovexattr(2) arguments. It must be invoked on a Control FD. The server must provide a write concurrency guarantee on the file node during this operation.
   309  29  | BindAt       | BindAtReq       | BindAtResp<br>Donates: \[sockFD\]                                  | BindAt is analogous to calling socket(2) and then bind(2) on that socket FD with a path. The path which is binded to is the host path of the directory represented by the control FD BindAtReq.DirFD + ‘/’ + BindAtReq.Name. The socket FD is created using socket(AF\_UNIX, BindAtReq.sockType, 0). It additionally allows the client to set the UID and GID for the newly created socket. On success, the socket FD is donated to the client. The client may use this donated socket FD to poll for notifications. The client may listen(2) and accept(2) from the FD if syscall filters permit. There are other RPCs to perform those operations. On success a Bound Socket FD is also returned along with an Inode for the newly created socket file. The server must provide a write concurrency guarantee on the directory node during this operation.
   310  30  | Listen       | ListenReq       |                                                                    | Listen is analogous to calling listen(2) on the host socket FD represented by the Bound Socket FD ListenReq.fd with backlog ListenReq.backlog. The server must provide a read concurrency guarantee on the socket node during this operation.
   311  31  | Accept       | AcceptReq       | AcceptResp<br>Donates: \[connFD\]                                  | Accept is analogous to calling accept(2) on the host socket FD represented by the Bound Socket FD AcceptReq.fd. On success, Accept donates the connection FD which was accepted and also returns the peer address as a string in AcceptResp.peerAddr. The server may choose to protect the peer address by returning an empty string. Accept must not block. The server must provide a read concurrency guarantee on the socket node during this operation.
   312  
   313  ### Chunking
   314  
   315  Some IO based RPCs like PRead and PWrite may be limited by the maximum message
   316  size limit imposed by the connection (see Mount RPC). If more data is required
   317  to be read/written than one Read/Write RPC can accommodate, then the client
   318  should introduce logic to read/write in chunks and provide synchronization as
   319  required. The optimal chunk size should utilize the entire message size limit.
   320  
   321  ## Wire Format
   322  
   323  LISAFS manually serializes and deserializes data structures to and from the
   324  wire. This is simple and fast. 9P has successfully implemented and used such a
   325  technique without issues for many years now.
   326  
   327  Each message has the following format: `[Communicator Header][Message bytes]`
   328  The structs representing various messages and headers are of two kinds:
   329  
   330  -   Statically sized: The size and composition of such a struct can be
   331      determined at compile time. For example, `lisafs.Inode` has a static size.
   332      These structs are serialized onto the wire exactly how Golang would
   333      represent them in memory. This kind of serialization was inspired by
   334      gVisor’s go_marshal library. go_marshal autogenerates Golang code to
   335      serialize and deserialize statically sized structs using Golang’s unsafe
   336      package. The generated code just performs a memmove(3) from the struct’s
   337      memory address to the wire, or the other way round. This method avoids
   338      runtime reflection, which is slow. An alternative is to generate
   339      field-by-field serialization implementations, which is also slower than a
   340      memmove(3).
   341  -   Dynamically sized: The size of such a struct can only be determined at
   342      runtime. This usually means that the struct contains a string or an array.
   343      Such structs use field-by-field serialization and deserialization
   344      implementations, just like 9P does. The dynamic fields like strings and
   345      arrays are preceded by some metadata indicating their size. For example,
   346      `lisafs.SizedString` is a dynamically sized struct.