2 个版本
0.10.2 | 2024 年 4 月 3 日 |
---|---|
0.10.1 | 2023 年 6 月 5 日 |
#183 在 文件系统
383 每月下载次数
160KB
3.5K SLoC
Rust NFSv3 服务器
这是 Rust 中一个不完整但功能非常强大的 NFSv3 服务器实现。
为什么?你可能想知道。
我想实现一个真正的跨平台用户模式文件系统挂载。哪个协议几乎每个操作系统都支持?NFS。
为什么不使用 FUSE 呢?
- FUSE 在 Mac 和 Windows 上对用户来说很麻烦(需要驱动程序)。
- 为远程文件系统构建 FUSE 驱动程序需要很多注意。
- NFS 客户端对于慢响应或可能永远不会响应的服务器有很多历史性的健壮性。
操作系统在缓存 NFS 方面相当出色。对于元数据或数据,都有已建立的缓存淘汰原则。
因此,这是一个类似 FUSE 的用户模式文件系统 API,它基本上通过创建一个你可以挂载的本地主机 NFSv3 服务器来工作。
这用于 pyxet 和 xet-core,以提供 xet mount
功能,允许你在任何地方挂载多 TB 的 Xethub 仓库。
运行演示
要运行 demofs,它将在本地主机:11111 上托管一个 NFS 服务器
cargo build --example demo --features demo
./target/debug/examples/demo
要挂载。在 Linux 上(可能需要 sudo 权限)
mkdir demo
mount.nfs -o user,noacl,nolock,vers=3,tcp,wsize=1048576,rsize=131072,actimeo=120,port=11111,mountport=11111 localhost:/ demo
在 Mac 上
mkdir demo
mount_nfs -o nolocks,vers=3,tcp,rsize=131072,actimeo=120,port=11111,mountport=11111 localhost:/ demo
在 Windows 上(需要专业版,因为家庭版没有 NFS 客户端)
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:
请注意,演示文件系统是 可写 的。
使用方法
你只需要实现 vfs::NFSFileSystem trait。参见 demofs.rs 以获取示例,并查看 bin/main.rs 以了解如何实际启动服务。接口通常不难实现;主要要求将每个文件系统对象(目录/文件)与一个 64 位 ID 关联。由于分页要求,目录列表可能有点复杂。
待办事项和寻求贡献者
- 改进文档
- 在Mount协议和NFS协议中还有更多的事情需要实现。有一些消息回复为"不可用"。例如,我们实现了
READDIR_PLUS
但没有实现READDIR
,这通常没问题,但是Windows坚持总是先尝试READDIR。链接创建也不受支持。 nfs_handlers.rs
中的RPC消息处理还有很多需要改进的地方。响应序列化非常手动。一些清理工作会很好。- Windows的挂载"大概"可以工作(仅在Windows 11 Pro和NFS服务器上),但是因为各种未实现的API而打印出大量垃圾。Windows 11似乎不断尝试使用非常旧的NFS协议进行轮询。
- 许多性能优化。
- 也许可以从xet-core中引入挂载命令,这样用户就不需要记住上面的
-o
咒语。 - 也许可以做一个SMB3实现,这样我们就可以在Windows Home版上工作。
- NFSv4有一些写性能优化,这将非常不错。但是,协议实现起来比较复杂,因为它是有状态的。
相关RFC
- XDR是消息格式:RFC 1014。 https://datatracker.ietf.org/doc/html/rfc1014
- SUN RPC是RPC线格式:RFC 1057 https://datatracker.ietf.org/doc/html/rfc1057
- NFS在RFC 1813 https://datatracker.ietf.org/doc/html/rfc1813
- NFS挂载协议在RFC 1813附录I。 https://datatracker.ietf.org/doc/html/rfc1813#appendix-I
- PortMapper在RFC 1057附录A https://datatracker.ietf.org/doc/html/rfc1057#appendix-A
基本源布局
- context.rs:一个包含连接信息、VFS信息等的内容传递连接上下文对象。
- xdr.rs:XDR结构的序列化和反序列化例程。
- tcp.rs:主要的TCP处理入口点。
- rpcwire.rs:从TCP套接字读取和写入RPC消息,并根据需要将外部的RPC消息解码重定向到NFS/Mount/Portmapper实现。
- rpc.rs:RPC调用和回复的结构。所有都是XDR编码的。
- portmap.rs/portmap_handlers.rs:Portmapper协议所需的XDR结构和Portmapper RPC处理程序。
- mount.rs/mount_handlers.rs:Mount协议所需的XDR结构和Mount RPC处理程序。
- nfs.rs/nfs_handlers.rs:NFS协议所需的XDR结构和NFS RPC处理程序。
过多不必要的细节
消息的基本工作方式是
- 我们从TCP流中读取一系列片段(一个4字节长度头和随后的一堆字节)
- 我们将片段组装成一个记录
- 记录是SUN RPC消息类型。
- 一个消息告诉我们三件信息,
- RPC程序(表示协议"类"的整数。例如,NFS协议是100003,Portmapper协议是100000)。
- RPC程序的版本(例如,3 = NFSv3,4 = NFSv4等)
- 调用的方法(调用哪个NFS方法)(例如,参见nfs.rs顶部的注释中的列表)
- 继续解码消息将给出方法的参数
- 然后我们取方法响应,将其包装在记录中,然后返回。
Portmapper
首先,让我们将端口映射器的问题解决掉。这是一个非常古老的机制,现在已经很少使用了。端口映射器是一个在端口111上运行的守护进程。当NFS或其他RPC服务启动时,它们会向端口映射器服务注册它们所监听的端口(例如NFS在2049端口)。然后,当另一台机器想要连接到NFS时,它们首先会询问端口映射器111,询问NFS监听哪个端口,然后连接到返回的端口。
我们并不严格需要实现此协议,因为现在几乎不再使用它(例如,NFSv4不使用端口映射器)。如果指定了-o port
和-o mountport
,Linux和Mac的内置NFS客户端也不需要它。但是,这对于调试和测试很有用,因为libnfs似乎需要端口映射器,但它却将端口硬编码为111。我将源代码修改为将其改为12000以进行测试,并实现了PMAPPROC_GETPORT
方法,以便我可以使用libnfs进行测试。
NFS基础知识
NFS的工作方式是每个文件系统对象(目录/文件/符号链接)都有两种可以被寻址的方式
fileid3: u64
. 一个64位整数。相当于inode号。nfs_fh3
:一个最多64字节长的可变不透明对象。
基本上,每当客户端尝试访问有关对象的任何信息时,它都需要一个nfs_fh3
。nfs_fh3
的目的有两个
- 允许服务器在句柄中缓存可能超过64位的附加查询信息。例如,如果服务器有多个在不同磁盘卷上的导出,我可能需要更多的位来标识磁盘卷。
- 允许客户端识别服务器是否“重启”,因此客户端必须清除所有缓存。
nfs_fh3
句柄应包含一个在NFS服务器首次启动时唯一的令牌,这样服务器就可以检查句柄是否仍然有效。如果服务器已重启,则所有先前的句柄都将“过期”,并且对它们的任何使用都应触发句柄过期错误,通知客户端清除所有缓存。
然而,获取文件nfs_fh3
的唯一方式是通过目录遍历。即存在一个查找方法LOOKUP(目录's句柄, 文件名in 目录)
,它返回文件名的句柄。
例如,要获取文件“dir/a.txt”的句柄,我首先需要“dir/”目录的句柄,然后查询LOOKUP(句柄, ""a.txt")
。
那么问题来了,我如何获取第一个句柄呢?这就是MOUNT协议要解决的问题。
挂载
MOUNT协议提供了一组“导出”(在最简单的情况下,即“/”),客户端将请求MNT("/"),这将返回根目录的句柄。
通常服务器可以维护一个可以查询的挂载列表,并且客户端也可以执行UMNT(卸载)。但在我们这个案例中,我们只实现了MNT和EXPORT,这已经足够了。NFS客户端通常忽略UMNT的返回消息,因为客户端在UMNT失败时实际上什么也不能做。因此,我们的挂载协议实现是完全无状态的。
NFS
NFS协议本身非常简单,大部分烦恼都是由于XDR消息格式处理引起的(特别是可选的列表等)。
令人高兴的是,NFS的设计是完全无状态的。主要是坐下来实现所有需要的方法,并对客户端进行测试。
依赖项
~4–13MB
~146K SLoC