62、深入探究lkcd与TCP/IP栈调试

深入探究lkcd与TCP/IP栈调试

1. lkcd源与补丁

lkcd源可从sourceforge.net获取。kerntypes是内核数据结构的数据库,在构建lkcd时生成。kerntypes的路径和系统映射文件是lcrash的参数。以下是启动lcrash程序的相关命令:
- 在内核崩溃转储上启动lcrash程序:

lcrash kerntypes core - file system.map
  • 在实时系统上使用lcrash:
lcrash kerntypes /dev/mem system.map

默认生成的kerntypes可能不包含所有内核子系统数据结构的存根。SG开发了一个工具来生成所有内核数据类型的存根。需要在调试模式下构建内核,并运行dwarfextract二进制文件来构建kerntypes文件,具体操作如下:

dwarfextract - p vmlinux kerntypes

使用 dwarfextract - c - C 输入需要添加到kerntypes的模块。kerntypes随lkcdutils的7.0.1 - 27版本提供,位于lkcdu - tils/dwarf/dwarfdump目录下。

此外,还需要使用帧指针选项配置内核,并使用lkcd补丁构建内核。对于内核2.4,补丁可在 http://lkcd.sourceforge.net/ 找到。lcrash的用户文档可在 lkcd.sourceforge.net/doc/lcrash.pdf 查看,完整信息可在 http://www.faqs.org/docs/Linux - HOWTO/Linux - Crash - HOWTO.html 获取。

2. 访问套接字

当应用程序打开TCP套接字时,可按以下步骤访问内核中的套接字结构:
1. 启动lcrash程序 :按照前面提到的方法启动lcrash程序,运行一个打开TCP套接字(INET_STREAM)的简单应用程序,这里要查找内核中套接字的程序是 client_do_nothing
2. 找到进程的task_struct对象 :在lcrash命令行界面运行 ps 命令,识别内核中的进程。
3. 找到进程的文件表 task_struct 对象的 files 字段指向文件表,该文件表是 files_struct 类型的对象。使用 print 命令获取文件表的地址,然后使用给定地址转储 files_struct 对象。
4. 识别套接字文件描述符 files_struct fd 字段是一个指向 file 对象的指针数组,每个打开的文件对应一个指针。转储 fd 地址的10个32位字,前三个条目分别指向标准输入、标准输出和标准错误,由于程序只打开了一个套接字,第四个条目应对应该套接字。
5. 检查inode对象 :第四个条目是指向 file 对象的指针,通过 file 对象的 f_dentry 字段访问文件的 dentry 对象,再通过 dentry 对象的 d_inode 字段获取 inode 对象的地址。检查 inode i_sock 字段,若该字段已设置,则可确定第四个条目对应打开的套接字。
6. 找到套接字对象 :由于 inode 是VFS为任何类型文件提供的通用接口, u 是Linux支持的所有特定文件类型对象的联合。对于套接字 inode inode 联合 u 中有一个套接字对象,由 inode 联合的 socket_i 字段指向,转储该套接字对象。

此时已到达BSD套接字对象,BSD套接字对象的 sk 字段指向特定协议的套接字,该特定协议的套接字负责执行特定协议的操作,管理连接的特定协议状态和数据。

3. 查看接收套接字缓冲区

应用程序以18字节的块接收数据“ I got your message ”,且未发出 recv() 系统调用来从套接字的接收缓冲区读取数据,因此可以查看套接字的接收缓冲区。
由于应用程序未读取套接字上的数据,所有套接字缓冲区都会堆积在套接字接收队列上。可以看到48个套接字缓冲区在接收队列中排队,这些缓冲区通过 sk_buff_head 对象的 next prev 字段链接。从接收队列中取出第一个缓冲区,当缓冲区在套接字的接收队列中排队时,协议头已被剥离,因此缓冲区( sk_buff )的 data 字段将指向TCP有效负载。访问 data 字段的指针,转储该指针指向位置的18字节数据,可以看到缓冲区包含“ I got the message ”。

4. sk_buff中的路由信息

每个在栈中向上传输的网络缓冲区在路由后都包含路由信息,这些信息包含了路由的所有相关信息。传入的数据包可能需要转发,此时,关于传出接口的所有信息以及路由的其他信息都会与缓冲区本身进行缓存。这些信息可通过 sk_buff dst 字段获取,该字段类型为 dst_entry 。获取 sk_buff 中缓存的路由信息的地址,该地址有一个指向 net_device 对象的指针,指向传出接口,通过 dev 字段获取该指针,然后打印接口名称进行交叉检查,结果显示接口为eth0。

5. 查看发送套接字缓冲区

当向套接字写入数据时,数据首先进入套接字发送缓冲区,然后从发送缓冲区传输。这有很多原因,例如即使无法立即传输数据,也可能需要对其进行排队,并且需要对已传输的段进行排队,直到收到确认(ACK)。一旦数据被确认,就会从发送套接字缓冲区中移除。

以下是查看发送套接字缓冲区的实验步骤:
1. 建立实验环境 :客户端和服务器程序在局域网内的两个不同主机上运行,应用程序以1 mss大小的小块写入数据,每次写入时用下一个字母填充1 mss的缓冲区。
2. 找到套接字 :按照前面访问套接字的方法找到连接的套接字。
3. 查看发送队列 :由于局域网中没有拥塞且数据传输速率高,数据包一旦在套接字的发送队列中排队就会立即传输,数据段一旦被确认就会从发送队列中移除。为了能够查看发送缓冲区,将接收端从网络中断开一段时间,此时数据包不会被确认,可以轻松查看套接字发送缓冲区。
4. 分析发送队列情况 :转储发送队列( sk → write_queue ),可以看到有两个数据包在发送队列中排队。此时,发送头指向要传输的下一个数据包,它应该指向 sk → write_queue prev 指向的段,因为 sk → write_queue next 字段指向的第一个段已经被传输,并且由于重传定时器触发,已经进行了重传。
5. 再次实验 :再次将接收端从网络中断开,发现套接字的发送缓冲区中有两个段。检查这些段的内容,由于此时还没有构建头,缓冲区的 data 字段指向数据的起始位置。由于应用程序以1 mss的块写入数据,段中没有数据重叠,第一个段包含全是 k 的数据,第二个段包含全是 j 的数据。

6. TCP分段单元

在应用程序发送数据进行传输且发送队列尾部存在部分段的情况下,TCP分段单元会尝试将其组成一个完整的段(即1 mss段)。实验与前面查看发送套接字缓冲区的实验类似,不同之处在于应用程序每次发送的数据块更小,每次在接收端从网络中断开时写入18字节的数据。

实验步骤如下:
1. 找到套接字 :按照前面的方法找到连接的套接字。
2. 查看发送队列 :可以看到发送队列中有两个段, sk→ write_queue next 字段指向的第一个段已经被传输,并且由于超时进行了重传,该段只包含18字节的数据。发送队列中下一个缓冲区的长度为342字节。
3. 分析缓冲区数据 :检查缓冲区中的数据,发现第一个缓冲区包含“ I got the message ”数据,第二个缓冲区多次追加了相同的数据。由于应用程序每次写入18字节的数据,TCP的分段单元会将数据追加到发送队列尾部的部分缓冲区中,而不是为每次写入创建新的段。当另一端连接到网络时,可以看到这两个段被传输,后续的所有段都只包含18字节的数据,因为它们一旦排队就会被传输。

7. 发送拥塞窗口和ssthresh

通过以下两个简单实验来检查发送数据时拥塞窗口随确认(ACK)的变化情况:
1. 实验一:间隔1秒发送一个数据段
- 操作步骤 :应用程序以1 mss的数据量,每隔1秒发送一个数据段。
- 实验结果 :观察到拥塞窗口保持恒定为2。
- 原因分析 :拥塞窗口只有在网络在任何时间点都以满容量使用时才会增加。在这种情况下,应用程序只有在收到第一个数据块的确认后才会发送下一个数据块,因此数据传输速率不足以使网络饱和。
2. 实验二:突发发送20个全尺寸段
- 操作步骤 :应用程序以突发方式发送20个全尺寸段,并且每隔10秒重复一次。
- 实验结果 :应用程序发送第一个突发数据后,拥塞窗口增加到8,而预期会有更高的值。
- 原因分析 :这是由于累积确认(ACK)的原因,接收器可能会为4、3和2个数据段发送累积确认,不确定具体情况。而且在确认到达时,套接字的发送队列中可能没有准备好数据,因为应用程序可能在发送完20个全尺寸数据块的完整突发之前就被调度出去了。可以尝试编写一个小程序,一次性发送20 mss的大数据块,这样可能会在数据完全传输结束时得到更高的拥塞窗口值。

8. 重传和路由

进行了一个简单的实验来检查连接的重传次数和路由信息之间的关系:
1. 建立连接并中断网络 :建立正常的TCP连接,然后将对等端从网络中断开,应用程序继续发送数据。由于是在局域网中,重传超时(RTO)会更短。
2. 检查重传情况 :使用lcrash检查时,重传次数达到10次,但仍然没有收到确认。此时,连接的路由对于套接字已经消失。
3. 检查路由有效性 :在重传定时器回调例程中,调用 tcp_write_timeout() 来检查是否需要检查连接的路由。首先检查重传次数是否超过 sysctl_tcp_retries1 ,如果超过,则需要检查连接的路由是否有效,调用 dst_negative_advice() 来更新连接的路由( sk → dst_cache )。如果重传次数超过 sysctl_tcp_retries2 ,则需要关闭连接。通过 fsyms lcrash 命令检查这两个控制参数的值,发现重传次数已经超过 sysctl_tcp_retries1 (值为3),检查连接的路由,发现由于对等端不在网络中,目的地不可达,路由无效,因此通过调用 ipv4_negative_advice() 将套接字的路由缓存置为NULL。

9. 查看连接队列和SYN队列

监听套接字有两个队列:接受队列和SYN队列。新请求会在SYN队列中排队,一旦建立连接,就会从SYN队列中出队并在接受队列中排队。接受队列中可以排队的请求数量由 listen() 系统调用的 backlog 参数定义,默认值为5。

以下是查看连接队列和SYN队列的实验步骤:
1. 编写并运行服务器程序 :编写一个简单的服务器程序 server_do_nothing ,在运行lcrash的机器上运行该程序,使用 listen() 系统调用将接受队列的长度设置为1。
2. 发送连接请求 :从网络中的另一台机器向该监听套接字发送多个连接请求。
3. 获取监听套接字 :获取监听套接字,其状态为未连接。
4. 检查队列情况 :监听套接字的接受队列由 tcp_opt 对象的 accept_queue 字段指向,SYN队列由 tcp_listen_opt 对象的 syn_table 字段指向。检查监听套接字的 tcp_listen_opt 对象,发现SYN队列中有9个请求排队,且这些请求都不是新请求,这意味着SYN队列中的所有请求都至少重传了一次SYN - ACK。这种情况可能有两种原因:
- SYN - ACK没有得到确认。
- 接受队列被部分连接(三次TCP握手尚未完成)填满。

通过以上实验和分析,我们对lkcd的使用以及TCP/IP栈的调试有了更深入的了解,包括如何访问内核中的套接字结构、查看接收和发送缓冲区、分析TCP分段单元的行为、研究拥塞窗口的变化以及重传和路由的关系,还有连接队列和SYN队列的工作机制。这些知识对于理解和调试TCP/IP网络通信非常有帮助。

深入探究lkcd与TCP/IP栈调试

10. 总结与流程梳理

为了更清晰地理解整个调试过程,我们将前面的内容进行流程梳理,以下是几个关键操作的流程图:

graph TD;
    A[启动lcrash程序] --> B[找到进程的task_struct对象];
    B --> C[找到进程的文件表];
    C --> D[识别套接字文件描述符];
    D --> E[检查inode对象];
    E --> F[找到套接字对象];

这个流程图展示了访问内核中套接字结构的主要步骤。

另外,查看发送套接字缓冲区的流程如下:

graph TD;
    A[建立实验环境] --> B[找到套接字];
    B --> C[查看发送队列];
    C --> D[分析发送队列情况];
    D --> E[再次实验];

同时,我们可以将前面的实验和操作总结成一个表格,方便对比和理解:

实验内容 操作步骤 关键结果
访问套接字 1. 启动lcrash程序;2. 找到进程的task_struct对象;3. 找到进程的文件表;4. 识别套接字文件描述符;5. 检查inode对象;6. 找到套接字对象 到达BSD套接字对象,其sk字段指向特定协议的套接字
查看接收套接字缓冲区 1. 应用程序接收数据;2. 查看接收队列;3. 取出第一个缓冲区;4. 访问data字段指针并转储数据 缓冲区包含“ I got the message ”
查看发送套接字缓冲区 1. 建立实验环境;2. 找到套接字;3. 查看发送队列;4. 分析发送队列情况;5. 再次实验 发送队列中有数据包排队,数据段按规则处理
TCP分段单元 1. 找到套接字;2. 查看发送队列;3. 分析缓冲区数据 分段单元将数据追加到部分缓冲区,后续段按规则传输
发送拥塞窗口和ssthresh 实验一:间隔1秒发送一个数据段;实验二:突发发送20个全尺寸段 实验一拥塞窗口恒定为2,实验二拥塞窗口增加到8
重传和路由 1. 建立连接并中断网络;2. 检查重传情况;3. 检查路由有效性 重传次数超过阈值,路由无效时更新缓存
查看连接队列和SYN队列 1. 编写并运行服务器程序;2. 发送连接请求;3. 获取监听套接字;4. 检查队列情况 SYN队列有请求排队,可能因SYN - ACK未确认或接受队列满
11. 实际应用中的注意事项

在实际使用lkcd进行TCP/IP栈调试时,有以下几点需要注意:
- lkcd源和补丁
- 确保从正确的源(sourceforge.net)获取lkcd源,并且按照要求配置内核和应用补丁,特别是对于内核2.4,要从指定的网址获取补丁。
- 生成kerntypes文件时,要注意使用正确的命令和参数,根据实际情况添加需要的模块。
- 访问内核数据结构
- 在使用lcrash访问内核数据结构时,要确保对每个步骤的理解,特别是在识别套接字文件描述符和检查inode对象时,要仔细检查相关字段的值。
- 对于不同的内核版本,数据结构的定义和字段可能会有所不同,需要根据实际情况进行调整。
- 实验环境
- 在进行各种实验时,要确保实验环境的稳定性和可控性,例如在查看发送套接字缓冲区的实验中,要注意网络连接的断开和恢复时间,避免影响实验结果。
- 对于拥塞窗口和重传等实验,要考虑网络环境的影响,不同的网络条件可能会导致不同的实验结果。

12. 拓展思考

虽然我们通过前面的实验和分析对lkcd和TCP/IP栈调试有了一定的了解,但在实际应用中还有很多可以拓展和深入研究的地方:
- 优化调试过程 :可以探索如何进一步优化lkcd的使用,减少调试时间和提高调试效率,例如开发更自动化的调试脚本。
- 复杂网络场景 :前面的实验主要是在相对简单的局域网环境中进行的,对于更复杂的网络场景,如广域网、无线网络等,需要进一步研究如何应用这些调试方法。
- 与其他工具结合 :可以考虑将lkcd与其他网络调试工具结合使用,以获取更全面的网络信息,提高调试的准确性。

通过对lkcd和TCP/IP栈调试的深入研究和实践,我们可以更好地理解和解决网络通信中出现的问题,提高网络系统的稳定性和性能。希望以上内容能为大家在网络调试方面提供一些有价值的参考。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值