UNIX 网络协议的深度分析
网络已经是无处不在,很多时候我们都会利用网络与不同主机进行通信,包括网络内部和外部的。大多数情况下这不会遇到问题,但是有时您需要仔细检查您的网络以查明问题原因。
仔细检查网络流量内容的原因有很多。其中第一个原因是您可能正在调试一个现有的网络应用,或者您正在开发一个应用,而您想要监控通过您的网络的流量。第二个原因是需要识别可能耗尽网络带宽和资源的流量。对于前一种情况,您可能已经知道协议的内容了,但是您希望能够更深入地了解正在传输的实际数据,例如,在使用 Web 服务时。对于后一种情况,确定数据包的内容需要了解正在使用的协议的一些扩展知识。
在 TCP/IP 和 UDP/IP 的通信中,最主要的元素是用于确定主机和端口号的 IP 地址。端口号用于提供额外的通信通道,这样您才能够在两个主机之间实现多个连接。其中端口定义还有一些标准。例如,端口 25 是专用于电子邮件(SMTP)传输,而大多数网站都是运行在端口 80(HTTP)上的。这些规范可以使程序之间通过一个熟悉的通道进行通信,这与您选择电话或传真号的道理是一样的。
虽然有这样一些规范,但是您实际想使用哪些端口是没有任何限制或约束的。事实上,大多数情况下一些破坏性网络应用和一些安全性方法会故意使用非标准端口。例如,有些应用会通过将一个标准端口用于不同的协议而隐藏内容,如在 端口 25 上使用 HTTP 协议。此外,有时某些应用也会使用与标准不同的端口,这样端口的用途就明显了(如,将端口 99 用于 HTTP),或者将特定的协议流量封装到另一个协议中。最后的方法实际上是网络通道和虚拟私有网络(v*n)所使用的方法。
不管网络流量原因和复杂性,第一个步骤都会开始记录数据。
如果您希望记录网络原始数据,以便自己检查这些信息,那么您可以使用许多不同的工具。大多数的网络嗅探器也能够解码和解密特定的数据包内容,这能够帮助您研究一个已知协议的内容。
在 Solaris 上,您可以使用 snoop 工具,而在 AIX 上,您可以使用 iptrace 工具。您也可以尝试使用跨平台的 tcpdump 工具,它支持大多数的 UNIX 和 Linux 操作系统。这些工具能够帮您捕捉和解码数据包,通常也能为您执行大多数的协议分析。注意,现代交换机不会将 Ethernet 数据包发送到每一个端口上,这通常会限制您从当前主机获取的信息量。许多现代交换机具有一个管理端口,它通常带有与这种监控完全相同的所有数据包的副本。
解码网络传输最复杂的是网络数据包中信息的级别。此外,大部分信息也会经过二进制编码后再发送,从网络捕捉完全原始的数据包需要进行大量的操作才能捕捉您需要的数据。通过使用实现某些处理的工具,您可以简化解码网络数据的过程。
例如,在一个 Ethernet 上查看一个典型的 TCP/IP 协议,您将会发现网络中传输的数据包括:
- Ethernet 数据包头,包括 Ethernet 来源和目标地址,数据包大小和 Ethernet 数据包类型。
- IP 报头,由 IP 寻址(来源和目标),协议标识和 IP 标记。您也会得到关于分片和数据包顺序的信息。
- TCP 报头,它包含端口上的信息、隐含的协议、标记和顺序编号。
即使有这些信息,我们仍然无法了解实际内容。在 TCP(或 UDP)协议之下还有额外的协议,标准数据协议(包括 HTTP、SMTP 和 FTP),或者封装性协议,如 Remote Procedure Call (RPC) 和 RPC 的子类型,如 NFS。
通常这些工具必须使用协议和/或端口号来确定正在传输的内容。所以,如果流量是通过非标准端口传输的,那么这个些信息可能无法正确解码。
本文之前提到的许多网络嗅探工具都提供了不同级别的协议解码,它们是通过检查端口和内容来确定所使用的协议实现的。
例如,snoop 和 tcpdump 都提供了关于 UDP 和 TCP 上不同协议的不同级别的详细信息。例如,在 snoop 中,您可以获得从顶级协议到所传输的单个数据块的关于 NFS 操作的详细信息。例如,您可以通过指定监控 RPC 使用 NFS 协议来实现对 NFS 流量的监控:$ snoop -v rpc nfs
。
它能输出非常详细的数据包信息,并且它们应该进行更紧密的观察。清单 1 提供了 Ethernet 报头数据。
清单 1. Ethernet 报头数据
ETHER: ----- Ether Header ----- ETHER: ETHER: Packet 64 arrived at 16:14:41.79434 ETHER: Packet size = 238 bytes ETHER: Destination = 0:1a:ee:1:1:c0, ETHER: Source = 0:21:28:3c:c0:61, ETHER: Ethertype = 0800 (IP) ETHER: |
这里的输出表明 Ethernet 数据包包含了 IP 数据、全部数据包大小和时间,以及数据包的目标和来源地址。
清单 2 显示的是 IP 报头。其中除了协议和来源/目标地址信息,其余许多 IP 数据是没有用的。
清单 2. IP 报头
IP: ----- IP Header ----- IP: IP: Version = 4 IP: Header length = 20 bytes IP: Type of service = 0x00 IP: xxx. .... = 0 (precedence) IP: ...0 .... = normal delay IP: .... 0... = normal throughput IP: .... .0.. = normal reliability IP: .... ..0. = not ECN capable transport IP: .... ...0 = no ECN congestion experienced IP: Total length = 224 bytes IP: Identification = 27460 IP: Flags = 0x4 IP: .1.. .... = do not fragment IP: ..0. .... = last fragment IP: Fragment offset = 0 bytes IP: Time to live = 64 seconds/hops IP: Protocol = 6 (TCP) IP: Header checksum = 4d11 IP: Source address = 192.168.0.112, tiger.mcslp.pri IP: Destination address = 192.168.0.2, bear.mcslp.pri IP: No options IP: |
在 清单 3 中,您可以看到 TCP 报头。同样,这些信息中通常只有来源和目标端口号才是有用的,因为这些信息将可用于确定预期的协议,或者提供给您可用于更进一步观察这个端口所传输流量的信息。
清单 3. TCP 报头
TCP: ----- TCP Header ----- TCP: TCP: Source port = 2049 TCP: Destination port = 889 (Sun RPC) TCP: Sequence number = 2834727685 TCP: Acknowledgement number = 2654368001 TCP: Data offset = 32 bytes TCP: Flags = 0x18 TCP: 0... .... = No ECN congestion window reduced TCP: .0.. .... = No ECN echo TCP: ..0. .... = No urgent pointer TCP: ...1 .... = Acknowledgement TCP: .... 1... = Push TCP: .... .0.. = No reset TCP: .... ..0. = No Syn TCP: .... ...0 = No Fin TCP: Window = 32806 TCP: Checksum = 0x4852 TCP: Urgent pointer = 0 TCP: Options: (12 bytes) TCP: - No operation TCP: - No operation TCP: - TS Val = 34449495, TS Echo = 253458642 TCP: |
清单 4 的倒数第二部分显示的是 RPC 报头数据。
清单 4. RPC 报头数据
RPC: ----- SUN RPC Header ----- RPC: RPC: Record Mark: last fragment, length = 168 RPC: Transaction id = 3041181596 RPC: Type = 1 (Reply) RPC: This is a reply to frame 63 RPC: Status = 0 (Accepted) RPC: Verifier : Flavor = 0 (None), len = 0 bytes RPC: Accept status = 0 (Success) RPC: |
最后,清单 5 提供了 NFS 数据包内容,包括权限(文件模式)、文件大小、拥有者和其他信息。在这里,这个 NFS 操作请求是进行文件系统统计(这等同于 ls 操作结果),因此这就是详细信息。
清单 5. NFS 数据包内容
NFS: ----- Sun NFS ----- NFS: NFS: Proc = 18 (Get filesystem statistics) NFS: Status = 0 (OK) NFS: Post-operation attributes: NFS: File type = 2 (Directory) NFS: Mode = 0777 NFS: Setuid = 0, Setgid = 0, Sticky = 0 NFS: Owner's permissions = rwx NFS: Group's permissions = rwx NFS: Other's permissions = rwx NFS: Link count = 24, User ID = 502, Group ID = 10 NFS: File size = 29, Used = 2560 NFS: Special: Major = 4294967295, Minor = 4294967295 NFS: File system id = 781684113418, File id = 4304616 NFS: Last access time = 28-Feb-10 15:49:51.042953989 GMT NFS: Modification time = 25-Feb-10 09:39:07.965422590 GMT NFS: Attribute change time = 25-Feb-10 09:39:07.965422590 GMT NFS: NFS: Total space = 759567510016 bytes NFS: Available space = 659048374272 bytes NFS: Available space - this user = 659048374272 bytes NFS: Total file slots = 1288161604 NFS: Available file slots = 1287203856 NFS: Available file slots - this user = 1287203856 NFS: Invariant time = 0 sec NFS: |
在这里,我们可以看到所查询的文件事实上是一个目录(见文件类型行)。虽然我们没有得到文件的实际路径,但是我们可以通过使用 Find 命令来查询 inode 号对应的文件/路径来查找上面提到的目录(见 清单 6)。
清单 6. 查询 inode 号对应的文件
$ find /scratch -xdev -inum 4304616 /scratch/installed/mysql-6.0.11 |
如果您准备分辨流量,那么使用这些工具的最佳方法是首先运行它们,收集尽可能多的数据,然后手动检查数据内容,找出您的网络本来不应该出现的数据项。
一旦您识别出了可疑流量,您就可以开始在命令行上添加参数以便关注于流量细节。例如,您可以使用 清单 7 所示的命令之一,规定只显示特定主机的流量。
清单 7. 规定只显示一个特定主机的流量
$ snoop host 192.168.0.2 $ tcpdump host 192.168.0.2 |
要进一步限制,您可以限制协议信息的端口:$ snoop host 192.168.0.2 and port 25
。
处理来自 tcpdump 的另一个方法是将原始网络数据包数据保存到一个文件中,然后处理这个文件以便查找和解码出您想要的信息。
有许多使用不同语言编写的模块具有读取和解码 tcpdump 和 snoop 捕捉的数据的功能。例如,有两个用 Perl 编写的模块:Net::SnoopLog(针对 snoop)和 Net::TcpDumpLog(针对 tcpdump)。它们将读取原始数据内容。这些模块的基本接口是相同的。
要开始处理,您首先需要使用 snoop 或 tcpdump 将数据写到一个文件,而创建一个通过网络的数据包的二进制记录。对于本例,我们将使用 tcpdump 和 Net::TcpDumpLog 模块:$ tcpdump -w packets.raw
。
收集网络数据完成后,您就可以开始处理网络数据内容以查找您想要的信息。Net::TcpDumpLog 会解析 tcpdump 所保存的原始网络数据。因为这些数据是以原始的二进制格式保存的,解析这些信息就需要处理这个二进制数据。为了方便起见,您可以使用另一组模块 NetPacket::* 来解码原始数据。
例如,清单 8 显示的是一个打印所有数据包的 IP 地址信息的简单脚本。
清单 8. 打印所有数据包的 IP 地址的简单脚本
use Net::TcpDumpLog; use NetPacket::Ethernet; use NetPacket::IP; my $log = Net::TcpDumpLog->new(); $log->read("packets.raw"); foreach my $index ($log->indexes) { my $packet = $log->data($index); my $ethernet = NetPacket::Ethernet->decode($packet); if ($ethernet->{type} == 0x0800) { my $ip = NetPacket::IP->decode($ethernet->{data}); printf(" %s to %s protocol %s \n", $ip->{src_ip},$ip->{dest_ip},$ip->{proto}); } } |
第一部分是提取每一个数据包。Net::TcpDumpLog
模块会将每一个数据包序列化,这样我们就能够通过数据包 ID 读取每一个数据包。然后 data()
函数就会返回整个数据包的原始数据。
通过 snoop 的输出,我们必须从原始网络数据包信息中提取每一个数据块。所以在本例中,我们首先需要从原始网络数据包中提取 Ethernet 数据包,包括数据有效负载。而 NetPacket::Ethernet
模块能够帮我们执行这个操作。
因为我们寻找的是 IP 数据包,所以我们能通过检查 Ethernet 数据包类型来检查 IP 数据包。IP 数据包的 ID 为 0x0800。
然后,NetPacket::IP
模块会被用于从 Ethernet 数据包的数据负载中提取出 IP 信息。这个模块能提供来源 IP、目标 IP 和协议信息等等,然后我们可以打印出这些信息。
通过使用这个基本的框架,您就能够执行不依赖于 tcpdump 或 snoop 的自动化解决方案的更复杂查询和解码。例如,如果您怀疑 HTTP 流量是通过一个非标准端口传输的(如,不是端口 80),那么您可以使用 清单 9 中的脚本在怀疑的主机 IP 的非 80 端口上检查 HTTP 数据包。
清单 9. 在非 80 端口上检查 HTTP 数据包
use Net::TcpDumpLog; use NetPacket::Ethernet; use NetPacket::IP; use NetPacket::TCP; my $log = Net::TcpDumpLog->new(); $log->read("packets.raw"); foreach my $index ($log->indexes) { my $packet = $log->data($index); my $ethernet = NetPacket::Ethernet->decode($packet); if ($ethernet->{type} == 0x0800) { my $ip = NetPacket::IP->decode($ethernet->{data}); if ($ip->{src_ip} eq '192.168.0.2') { if ($ip->{proto} == 6) { my $tcp = NetPacket::TCP->decode($ip->{data}); if (($tcp->{src_port} != 80) && ($tcp->{data} =~ m/HTTP/)) { print("Found HTTP traffic on non-port 80\n"); printf("%s (port: %d) to %s (port: %d)\n%s\n", $ip->{src_ip}, $tcp->{src_port}, $ip->{dest_ip}, $tcp->{dest_port}, $tcp->{data}); } } } } } |
在一个示例数据包集上运行上面的脚本会返回如 清单 10 所示的结果。
清单 10. 在一个示例数据包集上运行这个脚本
$ perl http-non80.pl Found HTTP traffic on non-port 80 192.168.0.2 (port: 39280) to 168.143.162.100 (port: 80) GET /statuses/user_timeline.json HTTP/1.1 Found HTTP traffic on non-port 80 192.168.0.2 (port: 39282) to 168.143.162.100 (port: 80) GET /statuses/friends_timeline.json HTTP/1 |
在这种特定的情况下,我们发现主机的流量是通向一个外部网站(Twitter)。
显然,在这个例子中我们处理的是原始数据,但是您可以使用相同的基本结构进行解码,也可以使用任何公开或私有协议结构的格式的数据。如果您正在使用这个方法使用或开发一个协议,并且您知道协议格式,您就能够提取和监控正在传输的数据。
虽然,正如之前提到的,诸如 tcpdump、iptrace 和 snoop 等工具都提供了基本的网络分析和解码功能,但是还有一些基于 GUI 的工具使这个过程更简单。Wireshark 就是其中一个工具,它支持大量的网络协议解码和分析。
Wireshark 的最主要优点之一是您可以捕捉一段时间内的数据包(如 tcpdump 一样),然后基于不同的协议、端口和其他数据交互地分析和过滤内容。Wireshark 也支持大量的协议解码器,这使您能够检查每分钟内数据包和会话的详细内容。
您可以看到 Wireshark 的简单截图显示了所有类型的所有数据包,如 图 1 所示。这个窗口分成三个主要部分:过滤的数据包列表、解码的协议明细和 HEX/ASCII 格式的原始数据包数据。
图 1. Wireshark 界面
作为 Wireshark 工具所提供的一个级别的信息和解码的例子,在撰写本文时我注意网络中的一个 MySQL 服务器返回了一些错误数据包。
为了专注于内容,我首先对输出应用了 MySQL 过滤。您可以通过在 Filter 输入框中输入一个表达式(类似于 tcpdump、snoop 或 iptract)。或者,您可以单击 Expression 按钮,然后从内置的列表的中选择一种过滤。您可以在 图 2 中看到一组示例过滤。一旦您选择了某个过滤,单击 Apply 就可以过滤数据包列表。
图 2. 选择一个 Wireshark 过滤
通过过滤 MySQL 协议,我能够确定错误的数据包。MySQL 协议会传到一个带有错误信息的特殊数据包类型。在这里,错误 1242 表示查询操作失败,由于子查询有问题。您可以通过展开 Wireshark 窗口的 MySQL 协议部分查看 MySQL 协议的内容,如 图 3 所示。
图 3. 检查一个 MySQL 错误数据包
在这里我们可以看到错误的明细。通过跟踪之前的 ‘Request Query’ 数据包,我们就可能确定出导致产生错误响应的查询(图 4)、
图 4. 导致产生错误响应的 MySQL 查询
通过分析数据包,我能够识别之前并没有注意的错误代码,并且能够指出这个错误及导致发生问题的查询。
Wireshark 支持广泛的协议和过滤器,您可以用它们获得详细的信息。它的另一个常见用途是监控详细协议的确切内容,如 Web 服务。图 5 显示了一个来自 SOAP 请求用于记录状态信息的详细(且结构清晰的)信息。
图 5. 查看 SOAP Web 服务请求的详细信息
这些详细信息在调试您使用的任何网络协议时是非常有用的。
Wireshark 的另一个有用的特性是它能够处理即时信息,并且它能够记录信息以备将来过滤和处理。这表示您可以使用它监控某个时期的可疑流量,然后您可以在恰当的时候分析这些信息,以查明您的网络中发生了什么操作。
对于通过您的 UNIX 网络的信息进行协议分析可能是一个复杂的过程。但是,通过使用一些简单而广泛使用的工具,您可以解码和检查您的流量的详细信息,包括来源和目标等基本信息,以及具体的协议和传输的数据。
如本文所述,通过使用诸如 tcpdump、snoop 或 iptrace 的工具,您可以在命令行上提取大量的数据。通过使用诸如 Wireshark 的工具,您可以更一地步地了解更大范围的协议和内容的更多详细信息。对于自定义的协议和结构,您可以使用 Perl 提取原始数据,并获得您需要的所有信息。