美文网首页NFS
和满哥抓包学NFSv3 (RFC1813)

和满哥抓包学NFSv3 (RFC1813)

作者: robot_test_boy | 来源:发表于2020-12-26 23:45 被阅读0次

    Network File System(NFS)协议是SUN公司设计的,NFS就是网络上的文件系统。NFS自1984年面世以来,已经流行30年。理论上它适用于任何操作系统,不过因为种种原因,一般只在 Linux/UNIX 环境中存在。

    无论SUN的命运如何多舛,NFS始终处乱不惊,这么多年来只出过3个版本,即1984年的NFSv2、1995年的NFSv3和2000年的NFSv4。目前,大多数NFS环境都还是NFSv3,本文介绍的也是这个版本。NFSv2还在极少数环境中运行(只在日本见到过),可以想象这些环境有多老了。而NFSv4因为深受CIFS影响,实施过程相对复杂,所以普及速度较慢。

    如何深入学习NFS协议呢?其实所有权威资料都可以在RFC 1813中找到,不过这些文档读起来就像面对一张冷冰冰的面孔,令人望而却步。《鸟哥的Linux私房菜》中对NFS的介绍虽称得上友好,但美中不足的是不够深入,出了问题也不知道如何排查。

    ps:不是所有问题都要抓包,如何定位问题的方法很多,本文仅介绍抓包方式。

    抓包实践

    NFS客户端和文件服务器的IP分别是10.32.106.159和10.32.106.62。在运行挂载命令(mount)时抓了包,然后用“portmap||mount||nfs”进行过滤。

    # mount 10.32.106.62:/code /tmp/code

    从上图中的Info一栏可以看到,Wireshark已经提供了详细的解析。

    包号112和113:

    客户端:“我想连接你的NFS进程,应该用哪个端口呀?”

    服务器:“我的NFS端口是2049。”

    包号123和124:

    客户端:“那我试一下NFS进程能否连上。”

    服务器:“收到了,能连上。”

    包号128和129:

    客户端:“我想连接你的mount服务,应该用哪个端口呀?”

    服务器:“我的mount的端口号是1234。”

    包号132和133:

    客户端:“那我试一下mount进程能否连上。”

    服务器:“收到了,能连上。”

    包号134和135:

    客户端:“我要挂载/code共享目录。”

    服务器:“你的请求被批准了。以后请用file handle 0x2cc9be18 来访问本目录。”

    包号140和141:

    客户端:“我试一下NFS进程能否连上。”

    服务器:“收到了,能连上。”

    包号143和144:

    客户端:“我想看看这个文件系统的属性。”

    服务器:“给,都在这里。”

    包号145和146:

    客户端:“我想看看这个文件系统的属性。”

    服务器:“给,都在这里。”

    以上便是NFS挂载的全过程。细节之处很多,所以在没有Wireshark的情况下很难排错,经常不得不盲目地检查每一个环节,比如先用rpcinfo命令获得服务器上的端口列表,再用 Telnet命令逐个试探。即使这样也只能检查几个关键进程能否连上,排查范围非常有限。

    看到portmap请求没有得到回复,可以考虑防火墙对111端口的拦截;如果发现mount请求被服务器拒绝了,就应该检查该共享目录的访问控制。

    既然说到访问控制,我们就来看看NFS在安全方面的机制,包括对客户端的访问控制和对用户的权限控制。NFS对客户端的访问控制是通过IP地址实现的。创建共享目录时可以指定哪些IP允许读写,哪些IP只允许读,还有哪些IP连挂载都不允许。

    问题1:客户端A上的用户admin在/code目录里新建一个文件,该文件的owner正常显示为admin。但是在客户端B上查看该文件时,owner却变成nasadmin。

    上图的Credentials信息可知,用户在创建文件时并没有使用admin这个用户名,而是用了admin的UID501来代表自己的身份(用户名与UID的对应关系是由客户端的 /etc/passwd决定的)。也就是说NFS协议是只认UID不认用户名的。当admin通过客户端A创建了一个文件,其UID 501就会被写到文件里,成为owner信息。

    而当客户端B上的用户查看该文件属性时,看到的其实也是“UID:501”。但是因为客户端B上的/etc/passwd文件和客户端A上的不一样,其UID501对应的用户名叫nasadmin,所以文件的owner就显示为nasadmin了。

    为了防止这类问题,建议用户名和UID的关系在每台客户端上都保持一致。

    问题2: NFS 是如何读写文件的?不同mount参数有什么作用?如何针对性地进行性能调优?

    以读取文件abc.txt的过程为例

    # cat /tmp/code/abc.txt

    包号2和3:

    客户端:“我可以进入0x2cc9be18(也就是/code的file handle)吗?”

    服务器:“你的请求被接受了,进来吧。”

    包号5和6:

    客户端:“我想看看这个目录里的文件及其file handle。”

    服务器:“文件名及file handle的信息在这里。其中abc.txt的file handle是0x531352e1。”

    包号8和9:

    客户端:“0x531352e1(也就是abc.txt)的文件属性是什么?“

    服务器:“权限、uid、gid, 文件大小等信息都给你。”

    包号11和12:

    客户端:“我可以打开0x531352e1(也就是abc.txt)吗?”

    服务器:“你的请求被允许了。你有读、写、执行等权限。”

    包号13、14、152、292:

    客户端:“从0x531352e1的偏移量为0处(即从abc.txt的开头位置)读131072字节。”

    客户端:“从0x531352e1的偏移量为131072处(即接着上一个请求读完的位置)再读131072字节。”

    服务器:“给你131072字节。”

    服务器:“再给你131072字节。”(继续读,直到读完整个文件。)

    NFS完成了文件的读取过程。从最后几个包可见,Linux客户端读NFS共享文件时是多个 READ Call 连续发出去的(本例中是连续两个)。这个方式跟Windows XP读CIFS共享文件有所不同。Windows XP不会连续发READ Call,而是先发一个Call,等收到Reply后再发下一个。

    除了读文件的方式,每个READ Call请求多少数据也会影响性能。这台Linux默认每次读131072字节,实验室里还有默认每次读32768字节的客户端。在高性能环境中,要手动指定一个比较大的值。我的Isilon实验室中,常常要调到512KB。这个值可以在mount时通过rsize参数来定义,比如 “mount -o  rsize=524288 10.32.106.62:/code/tmp/code”。

    问题3:NFS 是如何写写文件的?不同mount参数有什么作用?如何针对性地进行性能调优?

    # cp abc.txt code/abc.txt

    包号1和2:

    客户端:“我可以进入0x2cc9be18(即/code目录)吗?”

    服务器:“你的请求被接受了,进来吧。”

    包号4和5

    客户端:“请问这里有叫abc.txt的文件么?”

    服务器:“没有。”

    包号6和7

    客户端:“那我想创建一个叫abc.txt的文件。”

    服务器:“没问题,这个文件的file handle是0x531352e1。”

    包号69、104、130、190

    客户端:“从0x531352e1的偏移量为0处(即 abc.txt的文件开头)写131072字节。”

    服务器:“第/个131072字节写好了。”

    客户端:“从0x531352e1的偏移量为131072处(即接着上一个写完的位置)再写131072字节。”

    客户端:“从0x531352e1的偏移量为262144处(即接着上一个写完的位置)再写131072字节。”

    (继续写,直到写完整个文件。)

    包号306和307

    客户端:“我刚才往0x531352e1(也就是abc.txt)写的数据都存盘了吗?”

    服务器:“都存好了。”

    包号308和309:

    客户端:“那我看看0x531352e1(也就是abc.txt)的文件属性。”

    服务器:“文件的权限、uid、gid、文件大小等信息都给你。”

    这个例子的写操作也是多个WRITE Call连续发出去的,这是因为我们在挂载时没有指定任何参数,所以使用了默认的async写方式。和async相对应的是sync方式。假如mount时使用了sync参数(见图29),客户端会先发送一个WRITE Call,等收到Reply后再发下一个Call,也就是说WRITE Call和WRITE Reply是交替出现的。除此之外,还有什么办法在包里看出一个写操作是async还是sync呢?答案就是每个WRITE Call上的“UNSTABLE”和“FILE_SYNC”标志,前者表示async,后者表示sync。下图显示了用sync参数后的网络包。

    从上图中不仅可以看到FILE_SYNC标志,还可以看到WRITE Call和WRITE Reply 是交替出现的(也就是说没有连续的 Call)。不难想象,每个 WRITE Call写多少数据也是影响写性能的重要因素,我们可以在mount时用wsize参数来指定每次应该写多少。不过在有些客户端上启用sync参数之后,无论wsize定义成多少都会被强制为 4KB,从而导致写性能非常差。那为什么还有人用 sync 方式呢?答案是有些特殊的应用要求服务器收到sync的写请求之后,一定要等到存盘才能回复 WRITE Reply,sync 操作正符合了这个需求。由此我们也可以推出COMMIT对于sync写操作是没有必要的。

    非常值得一提的是,经常有人在mount时使用noac参数,然后发现读写性能都有问题。而根据RFC的说明,noac只是让客户端不缓存文件属性而已,为什么会影响性能呢?光看文档也许永远发现不了原因。抓个包吧,Wireshark会告诉我们答案。

    本章读书笔记来自林沛满的《Wireshark网络分析就这么简单》

    相关文章

      网友评论

        本文标题:和满哥抓包学NFSv3 (RFC1813)

        本文链接:https://www.haomeiwen.com/subject/wcuinktx.html