TCP/IP 详解 (卷一、二、三) 9787111075660

《TCP/IP详解卷1:协议》是一本完整而详细的TCP/IP协议指南。描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者W.Richard Stevens用Lawrence Berkeley实验室的tcpdump程序来捕获不同

432 126 39MB

Chinese Pages 423 [1544] Year 2000

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

TCP/IP 详解 (卷一、二、三)
 9787111075660

Table of contents :
TCP-IP详解卷一:协议
第1 章概述
1.1 引言
1.2 分层
1.3 TCP/IP 的分层
1.4 互联网的地址
1.5 域名系统
1.6 封装
1.7 分用
1.8 客户-服务器模型
1.9 端口号
1.10 标准化过程
1.11 RFC
1.12 标准的简单服务
1.13 互联网
1.14 实现
1.15 应用编程接口
1.16 测试网络
1.17 小结
第2 章链路层
2.1 引言
2.2 以太网和IEEE 802 封装
2.3 尾部封装
2.4 SLIP :串行线路IP
2.5 压缩的SLIP
2.6 PPP :点对点协议
2.7 环回接口
2.8 最大传输单元MTU
2.9 路径MTU
2.10 串行线路吞吐量计算
2.11 小结
第3 章IP :网际协议
3.1 引言
3.2 IP 首部
3.3 IP 路由选择
3.4 子网寻址
3.5 子网掩码
3.6 特殊情况的IP 地址
3.7 一个子网的例子
3.8 ifconfig 命令
3.9 netstat 命令
3.10 IP 的未来
3.11 小结
第4 章ARP :地址解析协议
4.1 引言
4.2 一个例子
4.3 ARP 高速缓存
4.4 ARP 的分组格式
4.5 ARP 举例
4.5.1 一般的例子
4.5.2 对不存在主机的ARP 请求
4.5.3 ARP 高速缓存超时设置
4.6 ARP 代理
4.7 免费ARP
4.8 arp 命令
4.9 小结
第5 章RARP :逆地址解析协议
5.1 引言
5.2 RARP 的分组格式
5.3 RARP 举例
5.4 RARP 服务器的设计
5.4.1 作为用户进程的RARP 服务器
5.4.2 每个网络有多个RARP 服务器
5.5 小结
第6 章ICMP :Internet 控制报文协议
6.1 引言
6.2 ICMP 报文的类型
6.3 ICMP 地址掩码请求与应答
6.4 ICMP 时间戳请求与应答
6.4.1 举例
6.4.2 另一种方法
6.5 ICMP 端口不可达差错
6.6 ICMP 报文的4.4BSD 处理
6.7 小结
第7 章Ping 程序
7.1 引言
7.2 Ping 程序
7.2.1 LAN 输出
7.2.2 WAN 输出
7.2.3 线路SLIP 链接
7.2.4 拨号SLIP 链路
7.3 IP 记录路由选项
7.3.1 通常的例子
7.3.2 异常的输出
7.4 IP 时间戳选项
7.5 小结
第8 章Traceroute 程序
8.1 引言
8.2 Traceroute 程序的操作
8.3 局域网输出
8.4 广域网输出
8.5 IP 源站选路选项
8.5.1 宽松的源站选路的t r a c e r o u t e 程序示例
8.5.2 严格的源站选路的t r a c e r o u t eL
8.5.3 宽松的源站选路t r a c e r o u t e 程序的往返路由
8.6 小结
第9 章IP 选路
9.1 引言
9.2 选路的原理
9.2.1 简单路由表
9.2.2 初始化路由表
9.2.3 较复杂的路由表
9.2.4 没有到达目的地的路由
9.3 ICMP 主机与网络不可达差错
9.4 转发或不转发
9.5 ICMP 重定向差错
9.5.1 一个例子
9.5.2 更多的细节
9.6 ICMP 路由器发现报文
9.6.1 路由器操作
9.6.2 主机操作
9.6.3 实现
9.7 小结
第10 章动态选路协议
10.1 引言
10.2 动态选路
10.3 Unix 选路守护程序
10.4 RIP :选路信息协议
10.4.1 报文格式
10.4.2 正常运行
命令
10.4.3 度量
10.4.4 问题
10.4.5 举例
10.4.6 另一个例子
10.5 RIP 版本2
10.6 OSPF :开放最短路径优先
10.7 BGP :边界网关协议
10.8 CIDR :无类型域间选路
10.9 小结
第11 章UDP :用户数据报协议
11.1 引言
11.2 UDP 首部
11.3 UDP 检验和
11.3.1 tcpdump 输出
11.3.2 一些统计结果
11.4 一个简单的例子
11.5 IP 分片
11.6 ICMP 不可达差错(需要分片)
11.7 用Traceroute 确定路径MTU
11.8 采用UDP 的路径MTU 发现
11.9 UDP 和ARP 之间的交互作用
11.10 最大UDP 数据报长度
11.11 ICMP 源站抑制差错
11.12 UDP 服务器的设计
11.12.1 客户IP 地址及端口号
11.12.2 目的IP 地址
11.12.3 UDP 输入队列
11.12.4 限制本地IP 地址
11.12.5 限制远端IP 地址
11.12.6 每个端口有多个接收者
11.13 小结
第12 章广播和多播
12.1 引言
12.2 广播
12.2.1 受限的广播
12.2.2 指向网络的广播
12.2.3 指向子网的广播
12.2.4 指向所有子网的广播
12.3 广播的例子
12.4 多播
12.4.1 多播组地址
12.4.2 多播组地址到以太网地址的转换
12.4.3 FDDI 和令牌环网络中的多播
12.5 小结
第13 章IGMP :Internet 组管理协议
13.1 引言
13.2 IGMP 报文
13.3 IGMP 协议
13.3.1 加入一个多播组
13.3.2 IGMP 报告和查询
13.3.3 实现细节
13.3.4 生存时间字段
13.3.5 所有主机组
13.4 一个例子
13.5 小结
第14 章DNS :域名系统
14.1 引言
14.2 DNS 基础
14.3 DNS 的报文格式
14.3.1 DNS 查询报文中的问题部分
14.3.2 DNS 响应报文中的资源记录部分
14.4 一个简单的例子
14.5 指针查询
14.5.1 举例
14.5.2 主机名检查
14.6 资源记录
14.7 高速缓存
14.8 用UDP 还是用TCP
14.9 另一个例子
14.10 小结
第15 章TFTP :简单文件传送协议
15.1 引言
15.2 协议
15.3 一个例子
15.4 安全性
15.5 小结
第16 章BOOTP :引导程序协议
16.1 引言
16.2 BOOTP 的分组格式
16.3 一个例子
16.4 BOOTP 服务器的设计
16.5 BOOTP 穿越路由器
16.6 特定厂商信息
16.7 小结
第17 章TCP :传输控制协议
17.1 引言
17.2 TCP 的服务
17.3 TCP 的首部
17.4 小结
第18 章TCP 连接的建立与终止
18.1 引言
18.2 连接的建立与终止
18.2.1 t c p d u m p 的输出
18.2.2 时间系列
18.2.3 建立连接协议
18.2.4 连接终止协议
18.2.5 正常的t c p d u m p 输出
18.3 连接建立的超时
18.3.1 第一次超时时间
18.3.2 服务类型字段
18.4 最大报文段长度
18.5 TCP 的半关闭
18.6 TCP 的状态变迁图
18.6.1 2MSL 等待状态
18.6.2 平静时间的概念
18.6.3 FIN_WAIT_2 状态
18.7 复位报文段
18.7.1 到不存在的端口的连接请求
18.7.2 异常终止一个连接
18.7.3 检测半打开连接
18.8 同时打开
18.9 同时关闭
18.10 TCP 选项
18.11 TCP 服务器的设计
18.11.1 TCP 服务器端口号
18.11.2 限定的本地IP 地址
18.11.3 限定的远端IP 地址
18.11.4 呼入连接请求队列
18.12 小结
第19 章TCP 的交互数据流
19.1 引言
19.2 交互式输入
19.3 经受时延的确认
19.4 Nagle 算法
19.4.1 关闭Nagle 算法
19.4.2 一个例子
19.5 窗口大小通告
19.6 小结
第20 章TCP 的成块数据流
20.1 引言
20.2 正常数据流
20.3 滑动窗口
20.4 窗口大小
20.5 PUSH 标志
20.6 慢启动
20.7 成块数据的吞吐量
20.7.1 带宽时延乘积
20.7.2 拥塞
20.8 紧急方式
20.9 小结
第21 章TCP 的超时与重传
21.1 引言
21.2 超时与重传的简单例子
21.3 往返时间测量
21.4 往返时间RTT 的例子
21.4.1 往返时间RTT 的测量
21.4.2 RTT 估计器的计算
21.4.3 慢启动
21.5 拥塞举例
21.6 拥塞避免算法
21.7 快速重传与快速恢复算法
21.8 拥塞举例(续)
21.9 按每条路由进行度量
21.10 ICMP 的差错
21.11 重新分组
21.12 小结
第22 章TCP 的坚持定时器
22.1 引言
22.2 一个例子
22.3 糊涂窗口综合症
22.4 小结
第23 章TCP 的保活定时器
23.1 引言
23.2 描述
23.3 保活举例
23.3.1 另一端崩溃
23.3.2 另一端崩溃并重新启动
23.3.3 另一端不可达
23.4 小结
第24 章TCP 的未来和性能
24.1 引言
24.2 路径MTU 发现
24.2.1 一个例子
24.2.2 大分组还是小分组
24.3 长肥管道
24.4 窗口扩大选项
24.5 时间戳选项
24.6 PAWS :防止回绕的序号
24.7 T/TCP :为事务用的TCP 扩展
24.8 TCP 的性能
24.9 小结
第25 章SNMP: 简单网络管理协议
25.1 引言
25.2 协议
25.3 管理信息结构
25.4 对象标识符
25.5 管理信息库介绍
25.6 实例标识
25.6.1 简单变量
25.6.2 表格
25.6.3 字典式排序
25.7 一些简单的例子
25.7.1 简单变量
25.7.2 get-next 操作
25.7.3 表格的访问
25.8 管理信息库(续)
25.8.1 s y s t e m 组
25.8.2 interface 组
25.8.3 a t 组
25.8.4 i p 组
25.8.5 i c m p 组
25.8.6 t c p 组
25.9 其他一些例子
25.9.1 接口MTU
25.9.2 路由表
25.10 Trap
25.11 ASN.1 和BER
25.12 SNMPv2
25.13 小结
第26 章Telnet 和Rlogin :远程登录
26.1 引言
26.2 Rlogin 协议
26.2.1 应用进程的启动
26.2.2 流量控制
26.2.3 客户的中断键
26.2.4 窗口大小的改变
26.2.5 服务器到客户的命令
26.2.6 客户到服务器的命令
26.2.7 客户的转义符
26.3 Rlogin 的例子
26.3.1 初始的客户-服务器协议
26.3.2 客户中断键
26.4 Telnet 协议
26.4.1 NVT ASCII
26.4.2 Telnet 命令
26.4.3 选项协商
26.4.4 子选项协商
26.4.5 半双工、一次一字符、一次一行或行方式
26.4.6 同步信号
26.4.7 客户的转义符
26.5 Telnet 举例
26.5.1 单字符方式
26.5.2 行方式
26.5.3 一次一行方式(准行方式)
26.5.4 行方式:客户中断键
26.6 小结
第27 章FTP :文件传送协议
27.1 引言
27.2 FTP 协议
27.2.1 数据表示
27.2.2 FTP 命令
27.2.3 FTP 应答
27.2.4 连接管理
27.3 FTP 的例子
27.3.1 连接管理:临时数据端口
27.3.2 连接管理:默认数据端口
27.3.3 文本文件传输:NVT ASCII 表示还是图像表示
27.3.4 异常中止一个文件的传输:Telnet 同步信号
27.3.5 匿名FTP
27.3.6 来自一个未知IP 地址的匿名FTP
27.4 小结
第28 章SMTP: 简单邮件传送协议
28.1 引言
28.2 SMTP 协议
28.2.1 简单例子
28.2.2 SMTP 命令
28.2.3 信封、首部和正文
28.2.4 中继代理
28.2.5 NVT ASCII
28.2.6 重试间隔
28.3 SMTP 的例子
28.3.1 MX 记录:主机非直接连到Internet
28.3.2 MX 记录:主机出故障
28.3.3 VRFY 和EXPN 命令
28.4 SMTP 的未来
28.4.1 信封的变化:扩充的SMTP
28.4.2 首部变化:非ASCII 字符
28.4.3 正文变化:通用Internet 邮件扩充
28.5 小结
第29 章网络文件系统
29.1 引言
29.2 Sun 远程过程调用
29.3 XDR: 外部数据表示
29.4 端口映射器
29.5 NFS 协议
29.5.1 文件句柄
29.5.2 安装协议
29.5.3 NFS 过程
29.5.4 UDP 还是TCP
29.5.5 TCP 上的NFS
29.6 NFS 实例
29.6.1 简单的例子:读一个文件
29.6.2 简单的例子:创建一个目录
29.6.3 无状态
29.6.4 例子:服务器崩溃
29.6.5 等幂过程
29.7 第3 版的NFS
29.8 小结
第30 章其他的TCP/IP 应用程序
30.1 引言
30.2 Finger 协议
30.3 Whois 协议
30.4 Archie 、WAIS 、Gopher 、Veronica 和WWW
30.4.1 Archie
30.4.2 WAIS
30.4.3 Gopher
30.4.4 Veronica
30.4.5 万维网WWW
30.5 X 窗口系统
30.5.1 Xscope 程序
30.5.2 LBX :低带宽X
30.6 小结
附录A tcpdump 程序
A.1 BSD 分组过滤器
A.2 SunOS 的网络接口分接头
A.3 SVR4 数据链路提供者接口
A.4 tcpdump 的输出
A.5 安全性考虑
A.6 插口排错选项
附录B 计算机时钟
附录C sock 程序
附录D 部分习题的解答
第1 章
第3 章
第4 章
第5 章
第6 章
第7 章
第8 章
第9 章
第10 章
第11 章
第12 章
第13 章
第14 章
第15 章
第16 章
第17 章
第18 章
第19 章
第20 章
第21 章
第22 章
第23 章
第24 章
第25 章
第26 章
第27 章
第28 章
第29 章
第30 章
附录E 配置选项
E.1 BSD/386 版本1.0
E.2 SunOS 4.1.3
E.3 SRV4
E.4 Solaris 2.2
E.5 AIX 3.2.2
E.6 4.4BSD
附录F 可以免费获得的源代码
参考文献
缩略语
TCP-IP详解卷二:实现
第一章 概述
第二章 mbuf:存储器缓存
第三章 接口层
第四章 接口:以太网
第五章 接口:SLIP和环回
第六章 IP编址
第七章 域和协议
第八章 IP:网际协议
第九章 IP选项项处理
第十章 IP的份片与重载
第十一章 ICMP:Internet控制报文协议
第十二章 IP多播
第十三章 IGMP:Internet组管理协议
第十四章 IP多播选路
第十五章 插口层
第十六章 插口I/O
第十七章 插口选项
第十八章 Radix树路由表
第十九章 选路请求和选路消息
第二十章 选路插口
第二十一章 ARP:地址解析协议
第二十二章 协议控制块
第二十三章 UDP:用户数据报协议
第二十四章 TCP:传输控制协议
第二十五章 TCP的定时器
第二十六章 TCP输出
第二十七章 TCP的函数
第二十八章 TCP的输入
第二十九章 TCP的输入(续)
第三十章 TCP的用户需求
第三十一章 BPF:BSD分组过滤程序
第三十二章 原始IP
附录A 部分习题的解答
附录B 源代码的获取
TCP-IP详解卷三:TCP事务协议,HTTP,NNTP和UNIX域协议
001
第一部分TCP 事务协议
第1 章T/TCP 概述
1.1 概述
1.2 UDP 上的客户-服务器
1.3 TCP 上的客户-服务器
1.4 T/TCP 上的客户-服务器
1.5 测试网络
1.6 时间测量程序
1.7 应用
1.8 历史
1.9 实现
1.10 小结
002
第2 章T/TCP 协议
2.1 概述
2.2 T/TCP 中的新T C P 选项
2.3 T/TCP 实现所需变量
2.4 状态变迁图
2.5 T/TCP 的扩展状态
2.6 小结
003
第3 章T/TCP 使用举例
3.1 概述
3.2 客户重新启动
3.3 常规的T / T C P 事务
3.4 服务器收到过时的重复S Y N
3.5 服务器重启动
3.6 请求或应答超出报文段最大长度M S S
3.7 向后兼容性
3.8 小结
004
第4 章T/TCP 协议(续)
4.1 概述
4.2 客户的端口号和T I M E _ WA I T 状态
4.3 设置T I M E _ WA I T 状态的目的
4.4 TIME_WA I T 状态的截断
4.5 利用TA O 跳过三次握手
4.6 小结
005
第5 章T/TCP 协议的实现:插口层
5.1 概述
5.2 常量
5.3 sosend 函数
5.4 小结
006
第6 章T/TCP 的实现:路由表
6.1 概述
6.2 代码介绍
6.3 r a d i x _ n o d e _ h e a d 结构
6.4 r t e n t r y 结构
6.5 r t _ m e t r i c s 结构
6.6 i n _ i n i t h e a d 函数
6.7 i n _ a d d r o u t e 函数
6.8 i n _ m a t r o u t e 函数
6.9 i n _ c l s r o u t e 函数
6.10 i n _ r t q t i m o 函数
6 . 11 i n _ r t q k i l l 函数
6.12 小结
007
第7 章T/TCP 实现:协议控制块
7.1 概述
7.2 i n _ p c b l a d d r 函数
7.3 i n _ p c b c o n n e c t 函数
7.4 小结
008
第8 章T/TCP 的实现:TCP 概要
8.1 概述
8.2 代码介绍
8.3 TCP 的p r o t o s w 结构
8.4 TCP 控制块
8.5 t c p _ i n i t 函数
8.6 t c p _ s l o w t i m o 函数
8.7 小结
009
第9 章T/TCP 的实现:TCP 输出
9.1 概述
9.2 t c p _ o u t p u t 函数
9.2.1 新的自动变量
9.2.2 增加隐藏的状态标志
9.2.3 在S Y N _ S E N T 状态不要重传S Y N
9.2.4 发送器的糊涂窗口避免机制
9.2.5 有R S T 或S Y N 标志时强制发送报文段
9.2.6 发送M S S 选项
9.2.7 是否发送时间戳选项
9.2.8 发送T / T C P 的C C 选项
9.2.9 根据T C P 选项调整数据长度
9.3 小结
010
第10 章T/TCP 实现:TCP 函数
10.1 概述
10.2 t c p _ n e w t c p c b 函数
10.3 t c p _ r t l o o k u p 函数
10.4 t c p _ g e t t a o c a c h e 函数
10.5 重传超时间隔的计算
10.6 t c p _ c l o s e 函数
10.7 t c p _ m s s s e n d 函数
10.8 t c p _ m s s r c v d 函数
10.9 t c p _ d o o p t i o n s 函数
10.10 t c p _ r e a s s 函数
1 0 . 11 小结
011
第11 章T/TCP 实现:TCP 输入
11.1 概述
11.2 预处理
11.3 首部预测
11.4 被动打开的启动
11.5 主动打开的启动
11.6 PAW S :防止序号重复
11.7 ACK 处理
11.8 完成被动打开和同时打开
11.9 ACK 处理(续)
11.10 FIN 处理
11 . 11 小结
012
第12 章T/TCP 实现:TCP 用户请求
12.1 概述
12.2 P R U _ C O N N E C T 请求
12.3 t c p _ c o n n e c t 函数
12.4 P R U _ S E N D 和P R U _ S E ?崤粈᧣䙂㵌별곂ᎁಋ
12.5 t c p _ u s r c l o s e d 函数
12.6 t c p _ s y s c t l 函数
12.7 T/TCP 的前景
12.8 小结
013
第二部分T C P 的其他应用
第13 章HTTP :超文本传送协议
13.1 概述
13.2 HTTP 和H T M L 概述
13.3 HTTP
13.3.1 报文类型:请求与响应
13.3.2 首部字段
13.3.3 响应代码
13.3.4 各种报文头举例
13.3.5 例子:客户程序缓存
13.3.6 例子:服务器重定向
13.4 一个例子
13.5 HTTP 的统计资料
13.6 性能问题
13.7 小结
014
第14 章在HTTP 服务器上找到的分组
14.1 概述
14.2 多个H T T P 服务器
14.3 客户端S Y N 的到达间隔时间
14.4 RT T 的测量
14.5 用l i s t e n 设置入连接队列的容量
14.6 客户端的S Y N 选项
14.7 客户端的S Y N 重传
14.8 域名
14.9 超时的持续探测
14.10 T/TCP 路由表大小的模拟
1 4 . 11 m b u f 的交互
14.12 TCP 的P C B 高速缓存和首部预测
14.13 小结
015
第15 章NNTP :网络新闻传送协议
15.1 概述
15.2 NNTP 协议
15.3 一个简单的新闻客户
15.4 一个复杂的新闻客户
15.5 NNTP 的统计资料
15.6 小结
016
第三部分Unix 域协议
第16 章Unix 域协议:概述
16.1 概述
16.2 用途
16.3 性能
16.4 编码举例
16.5 小结
017
第17 章Unix 域协议:实现
17.1 概述
17.2 代码介绍
17.3 Unix d o m a i n 和p r o t o s w 结构
17.4 Unix 域插口地址结构
17.5 Unix 域协议控制块
17.6 u i p c _ u s r r e q 函数
17.7 P R U _ A T T A C H 请求和u n p _ a t t a c h 函数
17.8 P R U _ D E T A C H 请求和u n p _ d e t a c h 函数
17.9 P R U _ B I N D 请求和u n p _ b i觤䂒趆袆맴
17.10 P R U _ C O N N E C T 请求和u n p _ c o n n e c t 函数
1 7 . 11 P R U _ C O N N E C T 2 请求和u n p _ c o n n e c t 2 函数
17.12 s o c k e t p a i r 系统调用
17.13 p i p e 系统调用
17.14 P R U _ A C C E P T 请求
17.15 P R U _ D I S C O N N E C T 请求和u n p _ d i s c o n n e c t 函数
17.16 P R U _ S H U T D O W N 请求和u n p _ s h u t d o w n 函数
17.17 P R U _ A B O R T 请求和u n p _ ⾽ᬱ㔑쵪娊ㄊ䦦凴揬
17.18 其他各种请求
17.19 小结
018
第18 章Unix 域协议:I/O 和描述符的传递
18.1 概述
18.2 P R U _ S E N D 和P R U _ R C V D 请求
18.3 描述符的传递
18.4 u n p _ i n t e r n a l i z e 函数
18.5 u n p _ e x t e r n a l i z e 函数
18.6 u n p _ d i s c a r d 函数
18.7 u n p _ d i s p o s e 函数
18.8 u n p _ s c a n 函数
18.9 u n p _ g c 函数
18.10 u n p _ m a r k 函数
1 8 . 11 性能(再讨论)
18.12 小结
019
附录A 测量网络时间
A.1 利用P i n g 的RT T 测量
A.2 协议栈测量
A.3 滞后和带宽
020
附录B 编写T/TCP 应用程序
021
参考文献
022
缩略语

Citation preview

下载

第1章 概



1.1 引言 很多不同的厂家生产各种型号的计算机,它们运行完全不同的操作系统,但 T C P / I P协议 族允许它们互相进行通信。这一点很让人感到吃惊,因为它的作用已远远超出了起初的设想。 T C P / I P起源于 6 0年代末美国政府资助的一个分组交换网络研究项目,到 9 0年代已发展成为计 算机之间最常应用的组网形式。它是一个真正的开放系统,因为协议族的定义及其多种实现 可以不用花钱或花很少的钱就可以公开地得到。它成为被称作“全球互联网”或“因特网 (Internet)”的基础,该广域网( WAN)已包含超过100万台遍布世界各地的计算机。 本章主要对 T C P / I P协议族进行概述,其目的是为本书其余章节提供充分的背景知识。如 果读者要从历史的角度了解有关 TCP/IP的早期发展情况,请参考文献 [Lynch 1993]。

1.2 分层 应用层

Telnet、FTP和e-mail等

负责不同的通信功能。一个协议族,比如 T C P / I P,是

运输层

TCP和UDP

一组不同层次上的多个协议的组合。 T C P / I P通常被认

网络层

IP、ICMP和IGMP

链路层

设备驱动程序及接口卡

网络协议通常分不同层次进行开发,每一层分别

为是一个四层协议系统,如图 1-1所示。 每一层负责不同的功能: 1) 链路层,有时也称作数据链路层或网络接口层, 通常包括操作系统中的设备驱动程序和计算机

图1-1 TCP/IP协议族的四个层次

中对应的网络接口卡。它们一起处理与电缆(或其他任何传输媒介)的物理接口细节。 2) 网络层,有时也称作互联网层,处理分组在网络中的活动,例如分组的选路。在 T C P / I P协议族中,网络层协议包括 I P协议(网际协议),I C M P协议(I n t e r n e t互联网控 制报文协议),以及IGMP协议(Internet组管理协议)。 3) 运输层主要为两台主机上的应用程序提供端到端的通信。在 T C P / I P协议族中,有两个 互不相同的传输协议: TCP(传输控制协议)和 UDP(用户数据报协议)。 TCP为两台主机提供高可靠性的数据通信。它所做的工作包括把应用程序交给它的数据分 成合适的小块交给下面的网络层,确认接收到的分组,设置发送最后确认分组的超时时钟 等。由于运输层提供了高可靠性的端到端的通信,因此应用层可以忽略所有这些细节。 而另一方面, U D P则为应用层提供一种非常简单的服务。它只是把称作数据报的分组 从一台主机发送到另一台主机,但并不保证该数据报能到达另一端。任何必需的可靠 性必须由应用层来提供。 这两种运输层协议分别在不同的应用程序中有不同的用途,这一点将在后面看到。 4) 应用层负责处理特定的应用程序细节。几乎各种不同的 T C P / I P实现都会提供下面这些 通用的应用程序:

2

使用TCP/IP详解,卷1:协议

下载

• Telnet 远程登录。 • FTP 文件传输协议。 • SMTP 简单邮件传送协议。 • SNMP 简单网络管理协议。 另外还有许多其他应用,在后面章节中将介绍其中的一部分。 假设在一个局域网( L A N)如以太网中有两台主机,二者都运行 F T P协议,图 1 - 2列出了 该过程所涉及到的所有协议。

应用层

FTP 客户

FTP协议

FTP 服务器

用户进程

处理应用 程序细节

内核

处理通信细节

TCP协议 运输层

IP协议 网络层

链路层

以太网驱 动程序

以太网协议

以太网驱 动程序

以太网

图1-2 局域网上运行FTP的两台主机

这里,我们列举了一个 F T P客户程序和另一个 F T P服务器程序。大多数的网络应用程序都 被设计成客户—服务器模式。服务器为客户提供某种服务,在本例中就是访问服务器所在主 机上的文件。在远程登录应用程序 Telnet中,为客户提供的服务是登录到服务器主机上。 在同一层上,双方都有对应的一个或多个协议进行通信。例如,某个协议允许 T C P层进 行通信,而另一个协议则允许两个 IP层进行通信。 在图1 - 2的右边,我们注意到应用程序通常是一个用户进程,而下三层则一般在(操作系 统)内核中执行。尽管这不是必需的,但通常都是这样处理的,例如 UNIX操作系统。 在图1 - 2中,顶层与下三层之间还有另一个关键的不同之处。应用层关心的是应用程序的 细节,而不是数据在网络中的传输活动。下三层对应用程序一无所知,但它们要处理所有的 通信细节。 在图1 - 2中列举了四种不同层次上的协议。 F T P是一种应用层协议, T C P是一种运输层协 议,I P是一种网络层协议,而以太网协议则应用于链路层上。 T C P / I P协议族是一组不同的协 议组合在一起构成的协议族。尽管通常称该协议族为 T C P / I P,但T C P和I P只是其中的两种协 议而已(该协议族的另一个名字是 Internet协议族(Internet Protocol Suite))。 网络接口层和应用层的目的是很显然的 — 前者处理有关通信媒介的细节(以太网、令牌 环网等),而后者处理某个特定的用户应用程序( F T P、Te l n e t等)。但是,从表面上看,网络 层和运输层之间的区别不那么明显。为什么要把它们划分成两个不同的层次呢?为了理解这 一点,我们必须把视野从单个网络扩展到一组网络。

第1章 概

下载

3

述使用

在8 0年代,网络不断增长的原因之一是大家都意识到只有一台孤立的计算机构成的“孤 岛”没有太大意义,于是就把这些孤立的系统组在一起形成网络。随着这样的发展,到了 9 0 年代,我们又逐渐认识到这种由单个网络构成的新的更大的“岛屿”同样没有太大的意义。 于是,人们又把多个网络连在一起形成一个网络的网络,或称作互连网 ( i n t e r n e t )。一个互连 网就是一组通过相同协议族互连在一起的网络。 构造互连网最简单的方法是把两个或多个网络通过路由器进行连接。它是一种特殊的用 于网络互连的硬件盒。路由器的好处是为不同类型的物理网络提供连接:以太网、令牌环网、 点对点的链接和 FDDI(光纤分布式数据接口)等等。 这些盒子也称作IP路由器(IP Router),但我们这里使用路由器 (Router)这个术语。 从历史上说,这些盒子称作网关( g a t e w a y),在很多 T C P / I P文献中都使用这个术语。 现在网关这个术语只用来表示应用层网关:一个连接两种不同协议族的进程(例如, TCP/IP和IBM的SNA),它为某个特定的应用程序服务(常常是电子邮件或文件传输)。 图1 - 3是一个包含两个网络的互连网:一个以太网和一个令牌环网,通过一个路由器互相 连接。尽管这里是两台主机通过路由器进行通信,实际上以太网中的任何主机都可以与令牌 环网中的任何主机进行通信。 在图 1 - 3中,我们可以划分出端系统( End system )(两边的两台主机)和中间系统 (Intermediate system)(中间的路由器)。应用层和运输层使用端到端( End-to-end)协议。在 图中,只有端系统需要这两层协议。但是,网络层提供的却是逐跳( Ho p - b y - h o p)协议,两 个端系统和每个中间系统都要使用它。 FTP协议

FTP 客户

FTP 服务器

TCP协议 路由器 IP协议

IP协议

以太网驱 动程序

以太网协议

以太网

以太网驱 动程序

令牌环驱 动程序

令牌环协议

令牌环驱 动程序

令 牌 环

图1-3 通过路由器连接的两个网络

在T C P / I P协议族中,网络层 I P提供的是一种不可靠的服务。也就是说,它只是尽可能快 地把分组从源结点送到目的结点,但是并不提供任何可靠性保证。而另一方面, T C P在不可 靠的I P层上提供了一个可靠的运输层。为了提供这种可靠的服务, T C P采用了超时重传、发 送和接收端到端的确认分组等机制。由此可见,运输层和网络层分别负责不同的功能。 从定义上看,一个路由器具有两个或多个网络接口层(因为它连接了两个或多个网络)。

4

使用TCP/IP详解,卷1:协议

下载

任何具有多个接口的系统,英文都称作是多接口的 (multihomed)。一个主机也可以有多个接口, 但一般不称作路由器 , 除非它的功能只是单纯地把分组从一个接口传送到另一个接口。同样, 路由器并不一定指那种在互联网中用来转发分组的特殊硬件盒。大多数的 T C P / I P实现也允许 一个多接口主机来担当路由器的功能,但是主机为此必须进行特殊的配置。在这种情况下, 我们既可以称该系统为主机(当它运行某一应用程序时,如 F T P或Te l n e t),也可以称之为路 由器(当它把分组从一个网络转发到另一个网络时)。在不同的场合下使用不同的术语。 互联网的目的之一是在应用程序中隐藏所有的物理细节。虽然这一点在图 1 - 3由两个网络 组成的互联网中并不很明显,但是应用层不能关心(也不关心)一台主机是在以太网上,而 另一台主机是在令牌环网上,它们通过路由器进行互连。随着增加不同类型的物理网络,可 能会有 2 0个路由器,但应用层仍然是一样的。物理细节的隐藏使得互联网功能非常强大,也 非常有用。 连接网络的另一个途径是使用网桥。网桥是在链路层上对网络进行互连,而路由器则是 在网络层上对网络进行互连。网桥使得多个局域网( L A N)组合在一起,这样对上层来说就 好像是一个局域网。 TCP /IP 倾向于使用路由器而不是网桥来连接网络,因此我们将着重介绍路由器。文献 [Perlman 1992]的第12章对路由器和网桥进行了比较。

1.3 TCP/IP的分层 在TCP/IP协议族中,有很多种协议。图 1-4给出了本书将要讨论的其他协议。

用户 进程

用户 进程

用户 进程

用户 进程

应用层

运输层

网络层

硬件 接口

媒体

图1-4 TCP/IP协议族中不同层次的协议

链路层

第1章 概

下载

5

述使用

TCP和UDP是两种最为著名的运输层协议,二者都使用 IP作为网络层协议。 虽然T C P使用不可靠的 I P服务,但它却提供一种可靠的运输层服务。本书第 1 7~2 2章将 详细讨论 T C P的内部操作细节。然后,我们将介绍一些 T C P的应用,如第 2 6章中的 Te l n e t和 Rlogin、第27章中的FTP以及第28章中的SMTP等。这些应用通常都是用户进程。 U D P为应用程序发送和接收数据报。一个数据报是指从发送方传输到接收方的一个信息 单元(例如,发送方指定的一定字节数的信息)。但是与 T C P不同的是, U D P是不可靠的,它 不能保证数据报能安全无误地到达最终目的。本书第 11章将讨论 U D P,然后在第 1 4章(D N S : 域名系统),第 1 5章( T F T P:简单文件传送协议),以及第 1 6章( BO OT P:引导程序协议) 介绍使用 U D P的应用程序。 S N M P也使用了 U D P协议,但是由于它还要处理许多其他的协议, 因此本书把它留到第 25章再进行讨论。 IP是网络层上的主要协议,同时被 TCP和UDP使用。TCP和UDP的每组数据都通过端系统 和每个中间路由器中的IP层在互联网中进行传输。在图1-4中,我们给出了一个直接访问 IP的应 用程序。这是很少见的,但也是可能的(一些较老的选路协议就是以这种方式来实现的。当然 新的运输层协议也有可能使用这种方式)。第3章主要讨论IP协议,但是为了使内容更加有针对 性,一些细节将留在后面的章节中进行讨论。第 9章和第10章讨论IP如何进行选路。 ICMP是IP协议的附属协议。IP层用它来与其他主机或路由器交换错误报文和其他重要信息。 第6章对ICMP的有关细节进行讨论。尽管ICMP主要被IP使用,但应用程序也有可能访问它。我 们将分析两个流行的诊断工具,Ping和Traceroute(第7章和第8章),它们都使用了ICMP。 IGMP是Internet组管理协议。它用来把一个 UDP数据报多播到多个主机。我们在第 12章中 描述广播(把一个 U D P数据报发送到某个指定网络上的所有主机)和多播的一般特性,然后 在第13章中对IGMP协议本身进行描述。 A R P(地址解析协议)和 R A R P(逆地址解析协议)是某些网络接口(如以太网和令牌环 网)使用的特殊协议,用来转换 I P层和网络接口层使用的地址。我们分别在第 4章和第 5章对 这两种协议进行分析和介绍。

1.4 互联网的地址 互联网上的每个接口必须有一个唯一的 I n t e r n e t地址(也称作 I P地址)。I P地址长 32 bit 。 Internet地址并不采用平面形式的地址空间,如 1、2、3等。IP地址具有一定的结构,五类不同 的互联网地址格式如图 1-5所示。 7位 A类

24位

网 络 号

主 机 号 14位

B类

C类

16位

网 络 号

主 机 号 21位

8位

网 络 号

主 机 号 28位

D类

多 播 组 号 27位

E类

(留 待 后 用)

图1-5 五类互联网地址

6

使用TCP/IP详解,卷1:协议

这些32位的地址通常写成四个十进制的数,其中

下载 类型

每个整数对应一个字节。这种表示方法称作“点分十

到 到 到 到 到

进制表示法( Dotted decimal notation)”。例如,作者 的系统就是一个B类地址,它表示为:140.252.13.33。 区分各类地址的最简单方法是看它的第一个十进 制整数。图 1 - 6列出了各类地址的起止范围,其中第

范 围

图1-6

各类IP地址的范围

一个十进制整数用加黑字体表示。 需要再次指出的是,多接口主机具有多个 IP地址,其中每个接口都对应一个 IP地址。 由于互联网上的每个接口必须有一个唯一的 IP地址,因此必须要有一个管理机构为接入互 联网的网络分配 I P地址。这个管理机构就是互联网络信息中心( Internet Network Information Centre),称作InterNIC。InterNIC只分配网络号。主机号的分配由系统管理员来负责。 Internet注册服务(IP地址和DNS域名)过去由NIC来负责,其网络地址是nic.ddn.mil。 1993年4月1日,InterNIC成立。现在,NIC只负责处理国防数据网的注册请求,所有其他 的Internet用户注册请求均由InterNIC负责处理,其网址是:rs.internic.net。 事实上InterNIC由三部分组成:注册服务(rs.internic.net),目录和数据库服 务(ds.internic.net) ,以及信息服务(is.internic.net)。有关InterNIC的其他 信息参见习题1.8。 有三类I P地址:单播地址(目的为单个主机)、广播地址(目的端为给定网络上的所有主 机)以及多播地址(目的端为同一组内的所有主机)。第12章和第13章将分别讨论广播和多播 的更多细节。 在3 . 4节中,我们在介绍 I P选路以后将进一步介绍子网的概念。图 3 - 9给出了几个特殊的 I P 地址:主机号和网络号为全 0或全1。

1.5 域名系统 尽管通过 I P地址可以识别主机上的网络接口,进而访问主机,但是人们最喜欢使用的还 是主机名。在 T C P / I P领域中,域名系统( D N S)是一个分布的数据库,由它来提供 I P地址和 主机名之间的映射信息。我们在第 14章将详细讨论DNS。 现在,我们必须理解,任何应用程序都可以调用一个标准的库函数来查看给定名字的主机 的IP地址。类似地,系统还提供一个逆函数 —给定主机的IP地址,查看它所对应的主机名。 大多数使用主机名作为参数的应用程序也可以把 I P地址作为参数。例如,在第 4章中当我 们用Telnet进行远程登录时,既可以指定一个主机名,也可以指定一个 IP地址。

1.6 封装 当应用程序用 T C P传送数据时,数据被送入协议栈中,然后逐个通过每一层直到被当作 一串比特流送入网络。其中每一层对收到的数据都要增加一些首部信息(有时还要增加尾部 信息),该过程如图 1 - 7所示。 T C P传给 I P的数据单元称作 T C P报文段或简称为 T C P段(T C P segment)。IP传给网络接口层的数据单元称作 IP数据报(IP datagram)。通过以太网传输的比特 流称作帧(Frame)。

第1章 概

下载

7

述使用

图1 - 7中帧头和帧尾下面所标注的数字是典型以太网帧首部的字节长度。在后面的章节中 我们将详细讨论这些帧头的具体含义。 以太网数据帧的物理特性是其长度必须在 4 6~1 5 0 0字节之间。我们将在 4 . 5节遇到最小长 度的数据帧,在 2.8节中遇到最大长度的数据帧。 所有的Internet标准和大多数有关 TCP/IP的书都使用octet这个术语来表示字节。使 用这个过分雕琢的术语是有历史原因的,因为 TCP/IP的很多工作都是在DEC-10系统上 进行的,但是它并不使用8 bit的字节。由于现在几乎所有的计算机系统都采用8 bit的字 节,因此我们在本书中使用字节(byte)这个术语。 更准确地说,图 1-7中IP和网络接口层之间传送的数据单元应该是分组( packet)。 分组既可以是一个IP数据报,也可以是IP数据报的一个片(fragment)。我们将在11.5节 讨论IP数据报分片的详细情况。 用户数据 应用程序 Appl 首部

用户数据

应用数据

TCP首部

TCP段 IP首部

TCP首部

应用数据

IP数据报 以太网 首部

IP首部

TCP首部

以太网 驱动程序 应用数据

以太网 尾部

以太网

以太网帧 46~1500字节

图1-7 数据进入协议栈时的封装过程

U D P数据与 T C P 数据基本一致。唯一的不同是 U D P传给 I P 的信息单元称作 U D P数据报 (UDP datagram),而且UDP的首部长为 8字节。 回想1 . 3节中的图 1 - 4,由于T C P、U D P、I C M P和I G M P都要向I P传送数据,因此 I P必须在 生成的 I P首部中加入某种标识,以表明数据属于哪一层。为此, I P在首部中存入一个长度为 8 b i t的数值,称作协议域。 1表示为 I C M P协议, 2表示为 I G M P协议, 6表示为 T C P协议, 1 7表 示为UDP协议。 类似地,许多应用程序都可以使用 T C P或U D P来传送数据。运输层协议在生成报文首部 时要存入一个应用程序的标识符。 T C P和U D P都用一个 1 6 b i t的端口号来表示不同的应用程序。 TCP和UDP把源端口号和目的端口号分别存入报文首部中。 网络接口分别要发送和接收 I P、A R P和R A R P数据,因此也必须在以太网的帧首部中加入

8

使用TCP/IP详解,卷1:协议

下载

某种形式的标识,以指明生成数据的网络层协议。为此,以太网的帧首部也有一个 16 bit的帧 类型域。

1.7 分用 当目的主机收到一个以太网数据帧时,数据就开始从协议栈中由底向上升,同时去掉各 层协议加上的报文首部。每层协议盒都要去检查报文首部中的协议标识,以确定接收数据的 上层协议。这个过程称作分用( Demultiplexing),图1-8显示了该过程是如何发生的。 应用程序

应用程序

应用程序

应用程序 根据 T C P或U D P首 部中的端口号进行 分用

根据 I P首部中的协 议值进行分用

根据以太网首部中 的帧类型进行分用 以太网 驱动程序 进入的帧

图1-8 以太网数据帧的分用过程

为协议ICMP和IGMP定位一直是一件很棘手的事情。在图 1-4中,把它们与IP放在 同一层上,那是因为事实上它们是IP的附属协议。但是在这里,我们又把它们放在IP层 的上面,这是因为ICMP和IGMP报文都被封装在IP数据报中。 对于ARP和RARP,我们也遇到类似的难题。在这里把它们放在以太网设备驱动程 序的上方,这是因为它们和 IP数据报一样,都有各自的以太网数据帧类型。但在图 2-4 中,我们又把ARP作为以太网设备驱动程序的一部分,放在 IP层的下面,其原因在逻 辑上是合理的。 这些分层协议盒并不都是完美的。 当进一步描述TCP的细节时,我们将看到协议确实是通过目的端口号、源 IP地址和源端口 号进行解包的。

1.8 客户-服务器模型 大部分网络应用程序在编写时都假设一端是客户,另一端是服务器,其目的是为了让服 务器为客户提供一些特定的服务。 可以将这种服务分为两种类型:重复型或并发型。重复型服务器通过以下步骤进行交互:

第1章 概

下载

9

述使用

I1. 等待一个客户请求的到来。 I2. 处理客户请求。 I3. 发送响应给发送请求的客户。 I4. 返回I1步。 重复型服务器主要的问题发生在 I2状态。在这个时候,它不能为其他客户机提供服务。 相应地,并发型服务器采用以下步骤: C1. 等待一个客户请求的到来。 C2. 启动一个新的服务器来处理这个客户的请求。在这期间可能生成一个新的进程、任务 或线程,并依赖底层操作系统的支持。这个步骤如何进行取决于操作系统。生成的新服务器 对客户的全部请求进行处理。处理结束后,终止这个新服务器。 C3. 返回C1步。 并发服务器的优点在于它是利用生成其他服务器的方法来处理客户的请求。也就是说, 每个客户都有它自己对应的服务器。如果操作系统允许多任务,那么就可以同时为多个客户 服务。 对服务器,而不是对客户进行分类的原因是因为对于一个客户来说,它通常并不能够辨 别自己是与一个重复型服务器或并发型服务器进行对话。 一般来说, T C P服务器是并发的,而 U D P服务器是重复的,但也存在一些例外。我们将 在11 . 1 2节对U D P对其服务器产生的影响进行详细讨论,并在 1 8 . 11节对T C P对其服务器的影响 进行讨论。

1.9 端口号 前面已经指出过, T C P和U D P采用16 bit的端口号来识别应用程序。那么这些端口号是如 何选择的呢? 服务器一般都是通过知名端口号来识别的。例如,对于每个 T C P / I P实现来说, F T P服务 器的T C P端口号都是 2 1,每个Te l n e t服务器的 T C P端口号都是 2 3,每个T F T P (简单文件传送协 议)服务器的 U D P端口号都是 6 9。任何 T C P / I P实现所提供的服务都用知名的 1~1 0 2 3之间的端 口号。这些知名端口号由 I n t e r n e t号分配机构( Internet Assigned Numbers Authority, IANA) 来管理。 到1992年为止,知名端口号介于1~255之间。256~1023之间的端口号通常都是由 Unix系统占用,以提供一些特定的 Unix服务—也就是说,提供一些只有 Unix系统才 有的、而其他操作系统可能不提供的服务。现在IANA管理1~1023之间所有的端口号。 Internet扩展服务与Unix特定服务之间的一个差别就是Telnet和Rlogin。它们二者都 允许通过计算机网络登录到其他主机上。 Telnet是采用端口号为23的TCP/IP标准且几乎 可以在所有操作系统上进行实现。相反,Rlogin最开始时只是为Unix系统设计的(尽管 许多非Unix系统现在也提供该服务),因此在80年代初,它的有名端口号为513。 客户端通常对它所使用的端口号并不关心,只需保证该端口号在本机上是唯一的就可以 了。客户端口号又称作临时端口号(即存在时间很短暂)。这是因为它通常只是在用户运行该 客户程序时才存在,而服务器则只要主机开着的,其服务就运行。 大多数T C P / I P实现给临时端口分配 1 0 2 4~5 0 0 0之间的端口号。大于 5 0 0 0的端口号是为其

10

使用TCP/IP详解,卷1:协议

下载

他服务器预留的( Internet上并不常用的服务 )。我们可以在后面看见许多这样的给临时端口分 配端口号的例子。 Solaris 2.2是一个很有名的例外。通常TCP和UDP的缺省临时端口号从 32768开始。 在E.4节中,我们将详细描述系统管理员如何对配置选项进行修改以改变这些缺省项。 大多数U n i x系统的文件 /e t c / s e r v i c e s都包含了人们熟知的端口号。为了找到 Te l n e t服 务器和域名系统的端口号,可以运行以下语句: 称它使用TCP端口号23 称它使用UDP端口号53和TCP端口号53

保留端口号 U n i x系统有保留端口号的概念。只有具有超级用户特权的进程才允许给它自己分配一个 保留端口号。 这些端口号介于 1~1 0 2 3之间,一些应用程序(如有名的 R l o g i n,2 6 . 2节)将它作为客户 与服务器之间身份认证的一部分。

1.10 标准化过程 究竟是谁控制着 T C P / I P协议族,又是谁在定义新的标准以及其他类似的事情?事实上, 有四个小组在负责 Internet技术。 1) Internet协会(ISOC,Internet Society)是一个推动、支持和促进 Internet不断增长和发 展的专业组织,它把 Internet作为全球研究通信的基础设施。 2) Internet体系结构委员会( IAB,Internet Architecture Board)是一个技术监督和协调的 机构。它由国际上来自不同专业的 1 5个志愿者组成,其职能是负责 I n t e r n e t标准的最后编辑和 技术审核。 IAB隶属于ISOC。 3) Internet工程专门小组(IETF,Internet Engineering Task Force)是一个面向近期标准的组 织,它分为9个领域(应用、寻径和寻址、安全等等)。IETF开发成为Internet标准的规范。为帮 助IETF主席,又成立了Internet工程指导小组(IESG, Internet Engineering Steering Group)。 4) Internet研究专门小组(IRIF,Internet Research Task Force)主要对长远的项目进行研究。 IRTF和IETF都隶属于IAB。文献[Crocker 1993]提供了关于 Internet内部标准化进程更为详 细的信息,同时还介绍了它的早期历史。

1.11 RFC 所有关于 I n t e r n e t的正式标准都以 R F C(Request for Comment)文档出版。另外,大量的 R F C并不是正式的标准,出版的目的只是为了提供信息。 R F C的篇幅从 1页到2 0 0页不等。每 一项都用一个数字来标识,如 RFC 1 122,数字越大说明 RFC的内容越新。 所有的 R F C都可以通过电子邮件或用 F T P从I n t e r n e t上免费获取。如果发送下面这份电子 邮件,就会收到一份获取 RFC的方法清单:

第1章 概

下载

11

述使用

To: [email protected] Subject: getting rfcs help: ways_to_get_rfcs

最新的 R F C索引总是搜索信息的起点。这个索引列出了 R F C被替换或局部更新的时间。 下面是一些重要的 RFC文档: 1) 赋值R F C(Assigned Numbers RFC)列出了所有 I n t e r n e t协议中使用的数字和常数。至 本书出版时为止,最新 R F C的编号是 1340 [Reynolds 和Postel 1992] 。所有著名的 Internet端口号都列在这里。 当这个RFC被更新时(通常每年至少更新一次) ,索引清单会列出RFC 1340被替换的时间。 2) I n t e r n e t正式协议标准,目前是 RFC 1600[Postel 1994]。这个R F C描述了各种I n t e r n e t协 议的标准化现状。每种协议都处于下面几种标准化状态之一:标准、草案标准、提议 标准、实验标准、信息标准和历史标准。另外,对每种协议都有一个要求的层次、必 需的、建议的、可选择的、限制使用的或者不推荐的。 与赋值RFC一样,这个 RFC也定期更新。请随时查看最新版本。 3) 主机需求RFC,1122和1123[Braden 1989a, 1989b]。RFC 1 122针对链路层、网络层和运 输层; RFC 11 2 3针对应用层。这两个 R F C对早期重要的 R F C文档作了大量的纠正和解 释。如果要查看有关协议更详细的细节内容,它们通常是一个入口点。它们列出了协 议中关于“必须”、“应该”、“可以”、“不应该”或者“不能”等特性及其实现细节。 文献[Borman 1993b]提供了有关这两个 RFC的实用内容。RFC 1127[Braden 1989c]对工 作小组开发主机需求 RFC过程中的讨论内容和结论进行了非正式的总结。 4) 路由器需求 RFC,目前正式版是 RFC 1009[Braden and Postel 1987],但一个新版已接近 完成[Almquist 1993]。它与主机需求 RFC类似,但是只单独描述了路由器的需求。

1.12 标准的简单服务 有一些标准的简单服务几乎每种实现都要提供。在本书中我们将使用其中的一些服务程 序,而客户程序通常选择 Te l n e t。图 1 - 9描述了这些服务。从该图可以看出,当使用 T C P和 UDP提供相同的服务时,一般选择相同的端口号。 名 字

TCP端口号 UDP端口号



RFC



echo

7

7

862

服务器返回客户发送的所有内容

discard

9

9

863

服务器丢弃客户发送的所有内容

daytime

13

13

867

服务器以可读形式返回时间和日期

chargen

19

19

864

当客户发送一个数据报时, TCP服务器发 送一串连续的字符流,直到客户中断连接。 UDP服务器发送一个随机长度的数据报

time

37

37

服务器返回一个二进制形式的 32 bit 数,

868

表示从UTC时间1900年1月1日午夜至今的秒 数

图1-9 大多数实现都提供的标准的简单服务

12

使用TCP/IP详解,卷1:协议

下载

如果仔细检查这些标准的简单服务以及其他标准的 TCP/IP服务(如Telnet、FTP、 SMTP等)的端口号时,我们发现它们都是奇数。这是有历史原因的,因为这些端口号 都是从NCP端口号派生出来的(NCP,即网络控制协议,是 ARPANET的运输层协议, 是TCP的前身)。NCP是单工的,不是全双工的,因此每个应用程序需要两个连接,需 预留一对奇数和偶数端口号。当 TCP和UDP成为标准的运输层协议时,每个应用程序 只需要一个端口号,因此就使用了NCP中的奇数。

1.13 互联网 在图1 - 3中,我们列举了一个由两个网络组成的互联网 — 一个以太网和一个令牌环网。 在1.4节和1.9节中,我们讨论了世界范围内的互联网 — Internet,以及集中分配 IP地址的需要 (I n t e r N I C),还讨论了知名端口号( I A N A)。i n t e r n e t这个词第一个字母是否大写决定了它具 有不同的含义。 i n t e r n e t意思是用一个共同的协议族把多个网络连接在一起。而 I n t e r n e t指的是世界范围内 通过TCP/IP互相通信的所有主机集合(超过 100万台)。Internet是一个internet,但internet不等 于Internet。 第一个广泛可用的 TCP/IP版本

1.14 实现 既成事实标准的 T C P / I P软件实现来 自于位于伯克利的加利福尼亚大学的计

TCP性能得到改善

算机系统研究小组。从历史上看,软件 是随同4.x BSD系统(Berkeley Software 慢启动,拥塞避免, 快速重传

D i s t r i b u t i o n)的网络版一起发布的。它 的源代码是许多其他实现的基础。 图1 - 1 0列举了各种 B S D版本发布的 时间,并标注了重要的 T C P / I P特性。列

BSD网络软件1.0版 (1989):Net/1 快速恢复,TCP头部预测, SLIP头部压缩, 路由表修改

在左边的 B S D网络版,其所有的网络源 代码可以公开得到:包括协议本身以及 许多应用程序和工具(如 Telnet和FTP)。

BSD网络软件2.0版 (1991):Net/2

在本书中,我们将使用“伯克利派

多播, 长肥管道修改

生系统”来指 SunOS 4.x 、S V R 4 以及 AIX 3.2等那些基于伯克利源代码开发的 系统。这些系统有很多共同之处,经常 包含相同的错误。 起初关于 I n t e r n e t的很多研究现在仍

又称为Net/3

图1-10 不同的BSD版及其重要的TCP/IP特性

然在伯克利系统中应用 — 新的拥塞控制算法( 2 1 . 7节)、多播( 1 2 . 4节)、“长肥管道”修改 (24.3节)以及其他类似的研究。

1.15 应用编程接口 使用T C P / I P协议的应用程序通常采用两种应用编程接口( A P I):s o c k e t和T L I(运输层接

第1章 概

下载

13

述使用

口:Transport Layer Interface)。前者有时称作“ Berkeley socket”,表明它是从伯克利版发展 而来的。后者起初是由 AT & T开发的,有时称作 X T I(X / O p e n运输层接口),以承认 X / O p e n这 个自己定义标准的国际计算机生产商所做的工作。 XTI实际上是TLI的一个超集。 本书不是一本编程方面的书,但是偶尔会引用一些内容来说明 T C P / I P的特性,不管大多 数的 A P I (s o c k e t)是否提供它们。所有关于 s o c k e t和T L I的编程细节请参阅文献 [ S t e v e n s 1990]。

1.16 测试网络 图1 - 11是本书中所有的例子运行的测试网络。为阅读时参考方便,该图还复制在本书扉 页前的插页中。

路由器 以太网

调制解 调器

(拨号) 调制解 调器

以太网

图1-11 本书中所有例子运行的测试网络,所有的IP地址均从140.252开始编址

在这个图中(作者的子网),大多数的例子都运行在下面四个系统中。图中所有的 I P地址 属于B类地址,网络号为 1 4 0 . 2 5 2。所有的主机名属于 . t u c . n o a o . e d u这个域( n o a o代表 National Optical Astronomy Observatories,t u c代表Tu c s o n)。例如,右下方的系统有一个完 整的名字 : s v r 4 . t u c . n o a o . e d u,其I P地址是: 1 4 0 . 2 5 2 . 1 3 . 3 4。每个方框上方的名称是该 主机运行的操作系统。这一组系统和网络上的主机及路由器运行于不同的 TCP/IP实现。 需要指出的是, n o a o . e d u这个域中的网络和主机要比图 1-11中的多得多。这里列出来的 只是本书中将要用到的系统。 在3 . 4节中,我们将描述这个网络所用到的子网形式。在 4 . 6节中将介绍 s u n与n e t b之间 的拨号SLIP的有关细节。2.4节将详细讨论SLIP。

1.17 小结 本章快速地浏览了TCP/IP协议族,介绍了在后面的章节中将要详细讨论的许多术语和协议。

14

使用TCP/IP详解,卷1:协议

下载

T C P / I P协议族分为四层:链路层、网络层、运输层和应用层,每一层各有不同的责任。 在T C P / I P中,网络层和运输层之间的区别是最为关键的:网络层( I P)提供点到点的服务, 而运输层( TCP和UDP)提供端到端的服务。 一个互联网是网络的网络。构造互联网的共同基石是路由器,它们在 I P层把网络连在一 起。第一个字母大写的 Internet是指分布在世界各地的大型互联网,其中包括 1万多个网络和超 过100万台主机。 在一个互联网上,每个接口都用 I P地址来标识,尽管用户习惯使用主机名而不是 I P地址。 域名系统为主机名和 I P地址之间提供动态的映射。端口号用来标识互相通信的应用程序。服 务器使用知名端口号,而客户使用临时设定的端口号。

习题 1.1 请计算最多有多少个 A类、B类和C类网络号。 1.2 用匿名 F T P(见2 7 . 3节)从主机 n i c . m e r i t . e d u上获取文件 n s f n e t / s t a t i s t i c s / history.netcount。该文件包含在 NSFNET网络上登记的国内和国外的网络数。画一 坐标系,横坐标代表年,纵坐标代表网络总数的对数值。纵坐标的最大值是习题 1 . 1的结 果。如果数据显示一个明显的趋势,请估计按照当前的编址体制推算,何时会用完所有 的网络地址(3.10节讨论解决该难题的建议)。 1.3 获取一份主机需求 RFC拷贝[Braden 1989a],阅读有关应用于 TCP/IP协议族每一层的稳健 性原则。这个原则的参考对象是什么? 1.4 获取一份最新的赋值 R F C拷贝。“quote of the day”协议的有名端口号是什么?哪个 R F C 对该协议进行了定义? 1.5 如果你有一个接入 T C P / I P互联网的主机帐号,它的主 I P地址是多少?这台主机是否接入 了Internet?它是多接口主机吗? 1.6 获取一份RFC 1000的拷贝,了解RFC这个术语从何而来。 1.7 与Internet协会联系, [email protected]或者+1 703 648 9888,了解有关加入的情况。 1.8 用匿名FTP从主机i s . i n t e r n i c . n e t处获取文件 a b o u t - i n t e r n i c / i n f o r m a t i o n about-the-internic。

下载

第2章 链 路 层 2.1 引言 从图1 - 4中可以看出,在 T C P / I P协议族中,链路层主要有三个目的:(1)为I P模块发送和 接收I P数据报;( 2)为A R P模块发送 A R P请求和接收 A R P应答;( 3)为R A R P发送R A R P请 求和接收RARP应答。TCP/IP支持多种不同的链路层协议,这取决于网络所使用的硬件,如以 太网、令牌环网、 FDDI(光纤分布式数据接口)及 RS-232串行线路等。 在本章中,我们将详细讨论以太网链路层协议,两个串行接口链路层协议( SLIP和PPP), 以及大多数实现都包含的环回( l o o p b a c k)驱动程序。以太网和 S L I P是本书中大多数例子使 用的链路层。对 M T U(最大传输单元)进行了介绍,这个概念在本书的后面章节中将多次遇 到。我们还讨论了如何为串行线路选择 MTU。

2.2 以太网和IEEE 802封装 以太网这个术语一般是指数字设备公司( Digital Equipment Corp.)、英特尔公司( I n t e l C o r p .)和X e r o x公司在 1 9 8 2年联合公布的一个标准。它是当今 T C P / I P采用的主要的局域网技 术。它采用一种称作 C S M A / C D的媒体接入方法,其意思是带冲突检测的载波侦听多路接入 (Carrier Sense, Multiple Access with Collision Detection)。它的速率为10 Mb/s,地址为48 bit。 几年后, I E E E(电子电气工程师协会) 8 0 2委员会公布了一个稍有不同的标准集,其中 802.3针对整个 CSMA/C D网络,8 0 2 . 4针对令牌总线网络, 8 0 2 . 5针对令牌环网络。这三者的共 同特性由 802.2标准来定义,那就是 802网络共有的逻辑链路控制( LLC)。不幸的是, 802.2和 8 0 2 . 3定义了一个与以太网不同的帧格式。文献 [Stallings 1987]对所有的 IEEE 802标准进行了 详细的介绍。 在TCP/IP世界中,以太网 IP数据报的封装是在 RFC 894[Hornig 1984]中定义的, IEEE 802 网络的IP数据报封装是在 RFC 1042[Postel and Reynolds 1988]中定义的。主机需求 RFC要求每 台Internet主机都与一个10 Mb/s的以太网电缆相连接: 1) 必须能发送和接收采用 RFC 894(以太网)封装格式的分组。 2) 应该能接收与RFC 894混合的RFC 1042(IEEE 802)封装格式的分组。 3) 也许能够发送采用 RFC 1042格式封装的分组。如果主机能同时发送两种类型的分组数 据,那么发送的分组必须是可以设置的,而且默认条件下必须是 RFC 894分组。 最常使用的封装格式是 RFC 894定义的格式。图 2 - 1显示了两种不同形式的封装格式。图 中每个方框下面的数字是它们的字节长度。 两种帧格式都采用 48 bit(6字节)的目的地址和源地址( 802.3允许使用16 bit的地址,但 一般是48 bit地址)。这就是我们在本书中所称的硬件地址。ARP和RARP协议(第4章和第5章) 对32 bit的IP地址和48 bit的硬件地址进行映射。 接下来的 2个字节在两种帧格式中互不相同。在 8 0 2标准定义的帧格式中,长度字段是指

16

使用TCP/IP详解,卷1:协议

目的地址

源地址

下载 类型

长度

数 据 38~1492

类型 0800

数据报 38~1492

类型 0806

类型 8035

以太网封装 目的地址

源地址

请求/应答

请求/应答

46~1500字节 数 据

类型

46~1500 类型 0800

数据报 46~1500

类型 0806

请求/应答

类型 8035

请求/应答

图2-1 IEEE 802.2/802.3(RFC 1042)和以太网的封装格式(RFC 894)

它后续数据的字节长度,但不包括 C R C检验码。以太网的类型字段定义了后续数据的类型。 在 8 0 2标准定义的帧格式中,类型字段则由后续的子网接入协议(

Sub-network Access

Protocol,SNAP)的首部给出。幸运的是, 802定义的有效长度值与以太网的有效类型值无一 相同,这样,就可以对两种帧格式进行区分。 在以太网帧格式中,类型字段之后就是数据;而在 8 0 2帧格式中,跟随在后面的是 3字节 的802.2 LLC 和5字节的 802.2 SNAP 。目的服务访问点( Destination Service Access Point, D S A P)和源服务访问点( Source Service Access Point, SSAP)的值都设为 0 x a a。Ct r l字段的 值设为3。随后的 3个字节org code都置为0。再接下来的2个字节类型字段和以太网帧格式一样 (其他类型字段值可以参见 RFC 1340 [Reynolds and Postel 1992])。 C R C字段用于帧内后续字节差错的循环冗余码检验(检验和)(它也被称为 F C S或帧检验 序列)。 802.3标准定义的帧和以太网的帧都有最小长度要求。 802.3规定数据部分必须至少为 38字 节,而对于以太网,则要求最少要有 4 6字节。为了保证这一点,必须在不足的空间插入填充 (pad)字节。在开始观察线路上的分组时将遇到这种最小长度的情况。 在本书中,我们在需要的时候将给出以太网的封装格式,因为这是最为常见的封装格式。

17

第2章 链 路 层使用

下载 2.3 尾部封装

RFC 893[Leffler and Karels 1984] 描述了另一种用于以太网的封装格式,称作尾部封装 (trailer encapsulation)。这是一个早期 B S D系统在 DEC VA X机上运行时的试验格式,它通过 调整 I P数据报中字段的次序来提高性能。在以太网数据帧中,开始的那部分是变长的字段 (I P首部和T C P首部)。把它们移到尾部(在 C R C之前),这样当把数据复制到内核时,就可以 把数据帧中的数据部分映射到一个硬件页面,节省内存到内存的复制过程。 T C P数据报的长 度是5 1 2字节的整数倍,正好可以用内核中的页表来处理。两台主机通过协商使用 A R P扩展协 议对数据帧进行尾部封装。这些数据帧需定义不同的以太网帧类型值。 现在,尾部封装已遭到反对,因此我们不对它举任何例子。有兴趣的读者请参阅 RFC 893 以及文献[Leffler et al. 1989]的11.8节。

2.4 SLIP:串行线路IP SLIP的全称是Serial Line IP。它是一种在串行线路上对 IP数据报进行封装的简单形式,在 RFC 1055[Romkey 1988]中有详细描述。 S L I P适用于家庭中每台计算机几乎都有的 R S - 2 3 2串 行端口和高速调制解调器接入 Internet。 下面的规则描述了 SLIP协议定义的帧格式: 1) IP 数据报以一个称作 E N D(0 x c 0)的特殊字符结束。同时,为了防止数据报到来之前 的线路噪声被当成数据报内容,大多数实现在数据报的开始处也传一个 E N D字符(如果有线 路噪声,那么 E N D字符将结束这份错误的报文。这样当前的报文得以正确地传输,而前一个 错误报文交给上层后,会发现其内容毫无意义而被丢弃)。 2) 如果 I P报文中某个字符为 E N D,那么就要连续传输两个字节 0 x d b和0 x d c来取代它。 0xdb这个特殊字符被称作 SLIP的ESC字符,但是它的值与 ASCII码的ESC字符(0x1b)不同。 3) 如果I P报文中某个字符为 S L I P的E S C字符,那么就要连续传输两个字节 0 x d b和0 x d d来 取代它。 图2 - 2中的例子就是含有一个 E N D字符和一个 E S C字符的 I P报文。在这个例子中,在串行 线路上传输的总字节数是原 IP报文长度再加4个字节。 IP数据报

图2-2 SLIP报文的封装

SLIP是一种简单的帧封装方法,还有一些值得一提的缺陷: 1) 每一端必须知道对方的 IP地址。没有办法把本端的 IP地址通知给另一端。 2) 数据帧中没有类型字段(类似于以太网中的类型字段)。如果一条串行线路用于 S L I P, 那么它不能同时使用其他协议。

18

使用TCP/IP详解,卷1:协议

下载

3 ) S L I P没有在数据帧中加上检验和(类似于以太网中的 C R C字段)。如果 S L I P传输的报 文被线路噪声影响而发生错误,只能通过上层协议来发现(另一种方法是,新型的调制解调 器可以检测并纠正错误报文)。这样,上层协议提供某种形式的 C R C就显得很重要。在第 3章 和第1 7章中,我们将看到 I P首部和 T C P首部及其数据始终都有检验和。在第 11章中,将看到 UDP首部及其数据的检验和却是可选的。 尽管存在这些缺点, SLIP仍然是一种广泛使用的协议。 SLIP的历史要追溯到1984年,Rick Adams第一次在4.2BSD系统中实现。尽管它本 身的描述是一种非标准的协议,但是随着调制解调器的速率和可靠性的提高, SLIP越 来越流行。现在,它的许多产品可以公开获得,而且很多厂家都支持这种协议。

2.5 压缩的SLIP 由于串行线路的速率通常较低( 19200 b/s或更低),而且通信经常是交互式的(如 Te l n e t 和Rlogin,二者都使用TCP),因此在 SLIP线路上有许多小的 TCP分组进行交换。为了传送 1个 字节的数据需要 20个字节的 IP首部和20个字节的TCP首部,总数超过 40个字节(19.2节描述了 Rlogin会话过程中,当敲入一个简单命令时这些小报文传输的详细情况)。 既然承认这些性能上的缺陷,于是人们提出一个被称作 C S L I P(即压缩 S L I P)的新协议, 它在RFC 1144[Jacobson 1990a]中被详细描述。 CSLIP一般能把上面的 40个字节压缩到 3或5个 字节。它能在 CSLIP的每一端维持多达 16个TCP连接,并且知道其中每个连接的首部中的某些 字段一般不会发生变化。对于那些发生变化的字段,大多数只是一些小的数字和的改变。这 些被压缩的首部大大地缩短了交互响应时间。 现在大多数的SLIP产品都支持CSLIP。作者所在的子网(参见封面内页)中有两条 SLIP链路,它们均是CSLIP链路。

2.6 PPP:点对点协议 PPP,点对点协议修改了 SLIP协议中的所有缺陷。 PPP包括以下三个部分: 1) 在串行链路上封装 I P数据报的方法。 P P P既支持数据为 8位和无奇偶检验的异步模式 (如大多数计算机上都普遍存在的串行接口),还支持面向比特的同步链接。 2) 建立、配置及测试数据链路的链路控制协议( LCP:Link Control Protocol)。它允许通 信双方进行协商,以确定不同的选项。 3) 针对不同网络层协议的网络控制协议( N C P:Network Control Protocol)体系。当前 R F C定义的网络层有 I P、O S I网络层、 D E C n e t以及A p p l e Ta l k。例如, IP NCP允许双方商定是 否对报文首部进行压缩,类似于 CSLIP(缩写词NCP也可用在TCP的前面)。 RFC 1548[Simpson 1993]描述了报文封装的方法和链路控制协议。 RFC 1332[McGregor 1992]描述了针对 IP的网络控制协议。 P P P数据帧的格式看上去很像 I S O的H D L C(高层数据链路控制)标准。图 2 - 3是P P P数据 帧的格式。 每一帧都以标志字符 0x7e开始和结束。紧接着是一个地址字节,值始终是 0xff,然后是一 个值为0x03的控制字节。

19

第2章 链 路 层使用

下载 标志 地址

控制

协议





标志

最多1500字节 协议

协议

协议

IP数据报

链路控制数据

网络控制数据

图2-3 PPP数据帧的格式

接下来是协议字段,类似于以太网中类型字段的功能。当它的值为 0 x 0 0 2 1时,表示信息 字段是一个 I P数据报;值为 0 x c 0 2 1时,表示信息字段是链路控制数据;值为 0 x 8 0 2 1时,表示 信息字段是网络控制数据。 CRC字段(或FCS,帧检验序列)是一个循环冗余检验码,以检测数据帧中的错误。 由于标志字符的值是 0 x 7 e,因此当该字符出现在信息字段中时, P P P需要对它进行转义。 在同步链路中,该过程是通过一种称作比特填充 (bit stuffing)的硬件技术来完成的 [Tanenbaum 1 9 8 9 ]。在异步链路中,特殊字符 0 x 7 d用作转义字符。当它出现在 P P P数据帧中时,那么紧接 着的字符的第6个比特要取其补码,具体实现过程如下: 1) 当遇到字符 0x7e时,需连续传送两个字符: 0x7d和0x5e,以实现标志字符的转义。 2) 当遇到转义字符 0x7d时,需连续传送两个字符: 0x7d和0x5d,以实现转义字符的转义。 3 ) 默认情况下,如果字符的值小于 0 x 2 0(比如,一个 A S C I I控制字符),一般都要进行转 义。例如,遇到字符 0x01时需连续传送 0x7d和0x21两个字符(这时,第 6个比特取补码后变为 1,而前面两种情况均把它变为 0)。 这样做的原因是防止它们出现在双方主机的串行接口驱动程序或调制解调器中,因为有 时它们会把这些控制字符解释成特殊的含义。另一种可能是用链路控制协议来指定是否需要 对这32个字符中的某一些值进行转义。默认情况下是对所有的 32个字符都进行转义。 与SLIP类似,由于 PPP经常用于低速的串行链路,因此减少每一帧的字节数可以降低应用 程序的交互时延。利用链路控制协议,大多数的产品通过协商可以省略标志符和地址字段, 并且把协议字段由 2个字节减少到 1个字节。如果我们把 P P P的帧格式与前面的 S L I P的帧格式 (图 2 - 2)进行比较会发现, P P P只增加了 3个额外的字节: 1个字节留给协议字段,另 2个给 CRC字段使用。另外,使用 IP网络控制协议,大多数的产品可以通过协商采用 Van Jacobson报 文首部压缩方法(对应于 CSLIP压缩),减小IP和TCP首部长度。 总的来说, PPP比SLIP具有下面这些优点: (1) PPP支持在单根串行线路上运行多种协议, 不只是I P协议;(2) 每一帧都有循环冗余检验; (3) 通信双方可以进行 I P地址的动态协商(使用 I P网络控制协议); (4) 与C S L I P类似,对 T C P和I P报文首部进行压缩; (5) 链路控制协议可以 对多个数据链路选项进行设置。为这些优点付出的代价是在每一帧的首部增加 3个字节,当建 立链路时要发送几帧协商数据,以及更为复杂的实现。 尽管PPP比SLIP有更多的优点,但是现在的 SLIP用户仍然比PPP用户多。随着产品 越来越多,产家也开始逐渐支持PPP,因此最终PPP应该取代SLIP。

20

使用TCP/IP详解,卷1:协议

下载

2.7 环回接口 大多数的产品都支持环回接口( Loopback Interface),以允许运行在同一台主机上的客户 程序和服务器程序通过 T C P / I P进行通信。 A类网络号 1 2 7就是为环回接口预留的。根据惯例, 大多数系统把 I P地址1 2 7 . 0 . 0 . 1分配给这个接口,并命名为 l o c a l h o s t。一个传给环回接口的 I P数 据报不能在任何网络上出现。 我们想象,一旦传输层检测到目的端地址是环回地址时,应该可以省略部分传输层和所 有网络层的逻辑操作。但是大多数的产品还是照样完成传输层和网络层的所有过程,只是当 IP数据报离开网络层时把它返回给自己。 图2-4是环回接口处理 IP数据报的简单过程。

IP输出 函数

IP输入 函数



放入IP输入 队列中

目的IP地址是否与广播 地址或多播地址相同?

放入IP输入 队列中

否 环回驱动程序 是

目的IP地址是否与接 口IP地址相同?

以太网驱 动程序

否,用ARP获 取目的主机的 以太网地址 基于以太网帧 类型进行分用 发送

接收

以太网

图2-4 环回接口处理IP数据报的过程

图中需要指出的关键点是: 1) 传给环回地址(一般是 127.0.0.1)的任何数据均作为 IP输入。 2) 传给广播地址或多播地址的数据报复制一份传给环回接口,然后送到以太网上。这是 因为广播传送和多播传送的定义(第 12章)包含主机本身。 3) 任何传给该主机 IP地址的数据均送到环回接口。 看上去用传输层和 I P层的方法来处理环回数据似乎效率不高,但它简化了设计,因为环 回接口可以被看作是网络层下面的另一个链路层。网络层把一份数据报传送给环回接口,就 像传给其他链路层一样,只不过环回接口把它返回到 IP的输入队列中。 在图2 - 4中,另一个隐含的意思是送给主机本身 I P地址的 I P数据报一般不出现在相应的网 络上。例如,在一个以太网上,分组一般不被传出去然后读回来。某些 B S D以太网的设备驱 动程序的注释说明,许多以太网接口卡不能读回它们自己发送出去的数据。由于一台主机必

21

第2章 链 路 层使用

下载

须处理发送给自己的 IP数据报,因此图 2-4所示的过程是最为简单的处理办法。 4.4BSD系统定义了变量 useloopback,并初始化为1。但是,如果这个变量置为 0, 以太网驱动程序就会把本地分组送到网络,而不是送到环回接口上。它也许不能工作, 这取决于所使用的以太网接口卡和设备驱动程序。

2.8 最大传输单元MTU 正如在图2-1看到的那样,以太网和 802.3对数据帧的长度都有一个限制,其最大值分别是 1 5 0 0和1 4 9 2字节。链路层的这个特性称作 M T U,最大传输单元。不同类型的网络大



多数都有一个上限。

MTU字节

s令牌环(IBM) s令牌环(IEEE 802.5)

如果 I P层有一个数据报要传,而且数 据的长度比链路层的 M T U还大,那么 I P层



超通道

以太网

就需要进行分片( f r a g m e n t a t i o n),把数据 报分成若干片,这样每一片都小于 M T U。 我们将在11.5节讨论IP分片的过程。 图2 - 5列出了一些典型的 M T U值,它们

点对点(低时延)

图2-5 几种常见的最大传输单元(MTU)

摘自RFC 1191[Mogul and Deering 1990]。点到点的链路层(如 SLIP和PPP)的MTU并非指的 是网络媒体的物理特性。相反,它是一个逻辑限制,目的是为交互使用提供足够快的响应时 间。在2.10节中,我们将看到这个限制值是如何计算出来的。 在3.9节中,我们将用 netstat命令打印出网络接口的 MTU。

2.9 路径MTU 当在同一个网络上的两台主机互相进行通信时,该网络的 M T U是非常重要的。但是如果 两台主机之间的通信要通过多个网络,那么每个网络的链路层就可能有不同的 M T U。重要的 不是两台主机所在网络的 M T U的值,重要的是两台通信主机路径中的最小 M T U。它被称作路 径MTU。 两台主机之间的路径 M T U不一定是个常数。它取决于当时所选择的路由。而选路不一定 是对称的(从 A到B的路由可能与从 B到A的路由不同),因此路径MTU在两个方向上不一定是 一致的。 RFC 1191[Mogul and Deering 1990]描述了路径 MTU的发现机制,即在任何时候确定路径 M T U的方法。我们在介绍了 I C M P和I P分片方法以后再来看它是如何操作的。在 11 . 6节中,我 们将看到 I C M P的不可到达错误就采用这种发现方法。在 11 . 7节中,还会看到, t r a c e r o u t e程序 也是用这个方法来确定到达目的节点的路径 M T U。在11 . 8节和2 4 . 2节,将介绍当产品支持路 径MTU的发现方法时, UDP和TCP是如何进行操作的。

2.10 串行线路吞吐量计算 如果线路速率是 9600 b/s,而一个字节有 8 bit,加上一个起始比特和一个停止比特,那么 线路的速率就是 960 B/s(字节/秒)。以这个速率传输一个 1024字节的分组需要 1066 ms。如果

22

使用TCP/IP详解,卷1:协议

下载

用S L I P链接运行一个交互式应用程序,同时还运行另一个应用程序如 F T P发送或接收 1 0 2 4字 节的数据,那么一般来说就必须等待一半的时间( 533 ms)才能把交互式应用程序的分组数 据发送出去。 假定交互分组数据可以在其他“大块”分组数据发送之前被发送出去。大多数的 S L I P实 现确实提供这类服务排队方法,把交互数据放在大块的数据前面。交互通信一般有

Te l n e t、

Rlogin以及FTP的控制部分(用户的命令,而不是数据)。 这种服务排队方法是不完善的。它不能影响已经进入下游(如串行驱动程序)队 列的非交互数据。同时,新型的调制解调器具有很大的缓冲区,因此非交互数据可能 已经进入该缓冲区了。 对于交互应用来说,等待 533 ms是不能接受的。关于人的有关研究表明,交互响应时间 超过1 0 0~200 ms 就被认为是不好的 [Jacobson 1990a]。这是发送一份交互报文出去后,直到 接收到响应信息(通常是出现一个回显字符)为止的往返时间。 把S L I P的M T U缩短到 2 5 6就意味着链路传输一帧最长需要 266 ms ,它的一半是 133 ms (这是一般需要等待的时间)。这样情况会好一些,但仍然不完美。我们选择它的原因(与 6 4 或1 2 8相比)是因为大块数据提供良好的线路利用率(如大文件传输)。假设 C S L I P的报文首 部是5个字节,数据帧总长为 2 6 1个字节, 2 5 6个字节的数据使线路的利用率为 9 8 . 1 %,帧头占 了1 . 9 %,这样的利用率是很不错的。如果把 M T U降到2 5 6以下,那么将降低传输大块数据的 最大吞吐量。 在图2-5列出的MTU值中,点对点链路的 MTU是296个字节。假设数据为 256字节,TCP和 I P首部占 4 0个字节。由于 M T U是I P向链路层查询的结果,因此该值必须包括通常的 T C P和I P 首部。这样就会导致 IP如何进行分片的决策。 IP对于CSLIP的压缩情况一无所知。 我们对平均等待时间的计算(传输最大数据帧所需时间的一半)只适用于 S L I P链路(或 P P P链路)在交互通信和大块数据传输这两种情况下。当只有交互通信时,如果线路速率是 9600 b/s,那么任何方向上的 1字节数据(假设有 5个字节的压缩帧头)往返一次都大约需要 12.5 ms。它比前面提到的 100~200 ms要小得多。需要注意的是,由于帧头从 40个字节压缩到 5个字节,使得1字节数据往返时间从 85 ms 减到12.5 ms。 不幸的是,当使用新型的纠错和压缩调制解调器时,这样的计算就更难了。这些调制解 调器所采用的压缩方法使得在线路上传输的字节数大大减少,但纠错机制又会增加传输的时 间。不过,这些计算是我们进行合理决策的入口点。 在后面的章节中,我们将用这些串行线路吞吐量的计算来验证数据从串行线路上通过的 时间。

2.11 小结 本章讨论了 I n t e r n e t协议族中的最底层协议,链路层协议。我们比较了以太网和

IEEE

8 0 2 . 2 / 8 0 2 . 3的封装格式,以及 S L I P和P P P的封装格式。由于 S L I P和P P P经常用于低速的链路, 二者都提供了压缩不常变化的公共字段的方法。这使交互性能得到提高。 大多数的实现都提供环回接口。访问这个接口可以通过特殊的环回地址,一般为 127.0.0.1。也可以通过发送 IP数据报给主机所拥有的任一 IP地址。当环回数据回到上层的协议 栈中时,它已经过传输层和 IP层完整的处理过程。

下载

23

第2章 链 路 层使用

我们描述了很多链路都具有的一个重要特性, M T U,相关的一个概念是路径 M T U。根据 典型的串行线路 MTU,对SLIP和CSLIP链路的传输时延进行了计算。 本章的内容只覆盖了当今 T C P / I P所采用的部分数据链路公共技术。 T C P / I P成功的原因之 一是它几乎能在任何数据链路技术上运行。

习题 2.1 如果你的系统支持 n e t s t a t( 1 )命令(参见 3 . 9节),那么请用它确定系统上的接口及其 MTU。

下载

第3章 IP:网际协议 3.1 引言 IP是TCP/IP协议族中最为核心的协议。所有的 TCP、UDP、ICMP及IGMP数据都以IP数据 报格式传输(见图 1 - 4)。许多刚开始接触 T C P / I P的人对 I P提供不可靠、无连接的数据报传送 服务感到很奇怪,特别是那些具有 X.25或SNA背景知识的人。 不可靠( u n r e l i a b l e)的意思是它不能保证 I P数据报能成功地到达目的地。 I P仅提供最好 的传输服务。如果发生某种错误时,如某个路由器暂时用完了缓冲区, I P有一个简单的错误 处理算法:丢弃该数据报,然后发送 I C M P消息报给信源端。任何要求的可靠性必须由上层来 提供(如TCP)。 无连接( c o n n e c t i o n l e s s)这个术语的意思是 I P并不维护任何关于后续数据报的状态信息。 每个数据报的处理是相互独立的。这也说明, I P数据报可以不按发送顺序接收。如果一信源 向相同的信宿发送两个连续的数据报(先是 A,然后是 B),每个数据报都是独立地进行路由 选择,可能选择不同的路线,因此 B可能在A到达之前先到达。 在本章,我们将简要介绍 I P首部中的各个字段,讨论 I P路由选择和子网的有关内容。还 要介绍两个有用的命令: i f c o n f i g和n e t s t a t。关于 I P首部中一些字段的细节,将留在以 后使用这些字段的时候再进行讨论。 RFC 791[Postel 1981a]是IP的正式规范文件。

3.2 IP首部 IP数据报的格式如图 3-1所示。普通的IP首部长为20个字节,除非含有选项字段。 4位 版本

4位首部 长度

8位服务类型 (TOS) 3位 标志

16位标识 8位生存时间

16位总长度(字节数) 13位片偏移 16位首部检验和

8位协议 32位源IP地址 32位目的IP地址 选项(如果有)





图3-1 IP数据报格式及首部中的各字段

20字节

25

第3章 IP:网际协议使用

下载

分析图3-1中的首部。最高位在左边,记为 0 bit ;最低位在右边,记为 31 bit。 4个字节的32 bit值以下面的次序传输:首先是 0~7 bit,其次8~15 bit,然后16~23 bit, 最后是24~31 bit。这种传输次序称作 big endian字节序。由于 TCP/IP首部中所有的二进制整数 在网络中传输时都要求以这种次序,因此它又称作网络字节序。以其他形式存储二进制整数 的机器,如 little endian格式,则必须在传输数据之前把首部转换成网络字节序。 目前的协议版本号是 4,因此IP有时也称作 IPv4。3.10节将对一种新版的 IP协议进行讨论。 首部长度指的是首部占 32 bit字的数目,包括任何选项。由于它是一个 4比特字段,因此 首部最长为6 0个字节。在第 8章中,我们将看到这种限制使某些选项如路由记录选项在当今已 没有什么用处。普通 IP数据报(没有任何选择项)字段的值是 5。 服务类型( TO S)字段包括一个 3 bit的优先权子字段(现在已被忽略),4 bit的TO S子字 段和1 bit未用位但必须置 0。4 bit的TO S分别代表:最小时延、最大吞吐量、最高可靠性和最 小费用。4 bit中只能置其中1 bit。如果所有 4 bit均为0,那么就意味着是一般服务。 RFC 1340 [ R e y n o l d s a n d P o s t e l 1 9 9 2 ] 描述了所有的标准应用如何设置这些服务类型。

RFC 1349

[Almquist 1992]对该RFC进行了修正,更为详细地描述了 TOS的特性。 图3 - 2列出了对不同应用建议的 TO S值。在最后一列中给出的是十六进制值,因为这就是 在后面将要看到的 tcpdump命令输出。 应 用 程 序

最小时延

最大吞吐量

最高可靠性

最小费用

16进 制值

控制 数据 任意块数据 命令阶段 数据阶段 UDP查询 TCP查询 区域传输 差错 查询 任何IGP

图3-2 服务类型字段推荐值

Te l n e t和R l o g i n这两个交互应用要求最小的传输时延,因为人们主要用它们来传输少量的 交互数据。另一方面, F T P文件传输则要求有最大的吞吐量。最高可靠性被指明给网络管理 (S N M P)和路由选择协议。用户网络新闻( Usenet news, NNTP )是唯一要求最小费用的应 用。 现在大多数的TCP/IP实现都不支持TOS特性,但是自4.3BSD Reno以后的新版系统都对它 进行了设置。另外,新的路由协议如 OSPF和IS-IS都能根据这些字段的值进行路由决策。 在2.10节中,我们提到 SLIP一般提供基于服务类型的排队方法,允许对交互通信

26

使用TCP/IP详解,卷1:协议

下载

数据在处理大块数据之前进行处理。由于大多数的实现都不使用 TOS字段,因此这种 排队机制由SLIP自己来判断和处理,驱动程序先查看协议字段(确定是否是一个 TCP 段),然后检查TCP信源和信宿的端口号,以判断是否是一个交互服务。一个驱动程序 的注释这样认为,这种“令人厌恶的处理方法”是必需的,因为大多数实现都不允许 应用程序设置TOS字段。 总长度字段是指整个 I P数据报的长度,以字节为单位。利用首部长度字段和总长度字段, 就可以知道 I P数据报中数据内容的起始位置和长度。由于该字段长 1 6比特,所以 I P数据报最 长可达 6 5 5 3 5字节(回忆图 2 - 5,超级通道的 M T U为6 5 5 3 5。它的意思其实不是一个真正的 M T U— 它使用了最长的 I P数据报)。当数据报被分片时,该字段的值也随着变化,这一点将 在11.5节中进一步描述。 尽管可以传送一个长达 6 5 5 3 5字节的 I P数据报,但是大多数的链路层都会对它进行分片。 而且,主机也要求不能接收超过 5 7 6字节的数据报。由于 T C P把用户数据分成若干片,因此一 般来说这个限制不会影响 T C P。在后面的章节中将遇到大量使用 U D P的应用( R I P,T F T P, B O O T P,D N S,以及S N M P),它们都限制用户数据报长度为 5 1 2字节,小于 5 7 6字节。但是, 事实上现在大多数的实现(特别是那些支持网络文件系统 N F S的实现)允许超过 8 1 9 2字节的 IP数据报。 总长度字段是 I P首部中必要的内容,因为一些数据链路(如以太网)需要填充一些数据 以达到最小长度。尽管以太网的最小帧长为 4 6字节(见图 2 - 1),但是 I P数据可能会更短。如 果没有总长度字段,那么 IP层就不知道 46字节中有多少是 IP数据报的内容。 标识字段唯一地标识主机发送的每一份数据报。通常每发送一份报文它的值就会加 1。在 11 . 5节介绍分片和重组时再详细讨论它。同样,在讨论分片时再来分析标志字段和片偏移字 段。 RFC 791 [Postel 1981a]认为标识字段应该由让IP发送数据报的上层来选择。假设有 两个连续的IP数据报,其中一个是由TCP生成的,而另一个是由UDP生成的,那么它们 可能具有相同的标识字段。尽管这也可以照常工作(由重组算法来处理),但是在大多 数从伯克利派生出来的系统中,每发送一个IP数据报,IP层都要把一个内核变量的值加 1,不管交给IP的数据来自哪一层。内核变量的初始值根据系统引导时的时间来设置。 TTL(time-to-live)生存时间字段设置了数据报可以经过的最多路由器数。它指定了数据 报的生存时间。TTL的初始值由源主机设置(通常为 32或64),一旦经过一个处理它的路由器, 它的值就减去 1。当该字段的值为 0时,数据报就被丢弃,并发送 I C M P报文通知源主机。第 8 章我们讨论 Traceroute程序时将再回来讨论该字段。 我们已经在第 1章讨论了协议字段,并在图 1 - 8中示出了它如何被 I P用来对数据报进行分 用。根据它可以识别是哪个协议向 IP传送数据。 首部检验和字段是根据 IP首部计算的检验和码。它不对首部后面的数据进行计算。 ICMP、 IGMP、UDP和TCP在它们各自的首部中均含有同时覆盖首部和数据检验和码。 为了计算一份数据报的 I P检验和,首先把检验和字段置为 0。然后,对首部中每个 16 bit 进行二进制反码求和(整个首部看成是由一串 16 bit的字组成),结果存在检验和字段中。当 收到一份 I P数据报后,同样对首部中每个 16 bit进行二进制反码的求和。由于接收方在计算过

27

第3章 IP:网际协议使用

下载

程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有发生任何差错, 那么接收方计算的结果应该为全 1。如果结果不是全 1(即检验和错误),那么I P就丢弃收到的 数据报。但是不生成差错报文,由上层去发现丢失的数据报并进行重传。 ICMP、IGMP、UDP和TCP都采用相同的检验和算法,尽管 TCP和UDP除了本身的首部和 数据外,在 IP首部中还包含不同的字段。在 RFC 1071[Braden, Borman and Patridge 1988]中有 关于如何计算 I n t e r n e t检验和的实现技术。由于路由器经常只修改 T T L字段(减 1),因此当路 由器转发一份报文时可以增加它的检验和,而不需要对

I P 整个首部进行重新计算。 R F C

1141[Mallory and Kullberg 1990]为此给出了一个很有效的方法。 但是,标准的BSD实现在转发数据报时并不是采用这种增加的办法。 每一份 I P数据报都包含源 I P地址和目的 I P地址。我们在 1 . 4节中说过,它们都是 32 bit 的 值。 最后一个字段是任选项,是数据报中的一个可变长的可选信息。目前,这些任选项定义 如下: • 安全和处理限制(用于军事领域,详细内容参见 RFC 1108[Kent 1991]) • 记录路径(让每个路由器都记下它的 IP地址,见7.3节) • 时间戳(让每个路由器都记下它的 IP地址和时间,见 7.4节) • 宽松的源站选路(为数据报指定一系列必须经过的 IP地址,见8.5节) • 严格的源站选路(与宽松的源站选路类似,但是要求只能经过指定的这些地址,不能 经过其他的地址)。 这些选项很少被使用,并非所有的主机和路由器都支持这些选项。 选项字段一直都是以 32 bit 作为界限,在必要的时候插入值为 0的填充字节。这样就保证 IP首部始终是 32 bit 的整数倍(这是首部长度字段所要求的)。

3.3 IP路由选择 从概念上说, I P路由选择是简单的,特别对于主机来说。如果目的主机与源主机直接相 连(如点对点链路)或都在一个共享网络上(以太网或令牌环网),那么I P数据报就直接送到 目的主机上。否则,主机把数据报发往一默认的路由器上,由路由器来转发该数据报。大多 数的主机都是采用这种简单机制。 在本节和第 9章中,我们将讨论更一般的情况,即 I P层既可以配置成路由器的功能,也可 以配置成主机的功能。当今的大多数多用户系统,包括几乎所有的 U n i x系统,都可以配置成 一个路由器。我们可以为它指定主机和路由器都可以使用的简单路由算法。本质上的区别在 于主机从不把数据报从一个接口转发到另一个接口,而路由器则要转发数据报。内含路由器 功能的主机应该从不转发数据报,除非它被设置成那样。在 9 . 4小节中,我们将进一步讨论配 置的有关问题。 在一般的体制中, IP可以从TCP、UDP、ICMP和IGMP接收数据报(即在本地生成的数据 报)并进行发送,或者从一个网络接口接收数据报(待转发的数据报)并进行发送。 I P层在 内存中有一个路由表。当收到一份数据报并进行发送时,它都要对该表搜索一次。当数据报 来自某个网络接口时, IP首先检查目的IP地址是否为本机的 IP地址之一或者IP广播地址。如果 确实是这样,数据报就被送到由 I P首部协议字段所指定的协议模块进行处理。如果数据报的

28

使用TCP/IP详解,卷1:协议

下载

目的不是这些地址,那么( 1)如果 I P层被设置为路由器的功能,那么就对数据报进行转发 (也就是说,像下面对待发出的数据报一样处理);否则( 2)数据报被丢弃。 路由表中的每一项都包含下面这些信息: • 目的IP地址。它既可以是一个完整的主机地址,也可以是一个网络地址,由该表目中的标 志字段来指定(如下所述)。主机地址有一个非0的主机号(见图1-5),以指定某一特定的 主机,而网络地址中的主机号为0,以指定网络中的所有主机(如以太网,令牌环网)。 • 下一站(或下一跳)路由器( next-hop router)的I P地址,或者有直接连接的网络 I P地 址。下一站路由器是指一个在直接相连网络上的路由器,通过它可以转发数据报。下 一站路由器不是最终的目的,但是它可以把传送给它的数据报转发到最终目的。 • 标志。其中一个标志指明目的 I P地址是网络地址还是主机地址,另一个标志指明下一 站路由器是否为真正的下一站路由器,还是一个直接相连的接口(我们将在

9 . 2节中

详细介绍这些标志)。 • 为数据报的传输指定一个网络接口。 IP路由选择是逐跳地( hop-by-hop)进行的。从这个路由表信息可以看出, IP并不知道到 达任何目的的完整路径(当然,除了那些与主机直接相连的目的)。所有的 I P路由选择只为数 据报传输提供下一站路由器的 I P地址。它假定下一站路由器比发送数据报的主机更接近目的, 而且下一站路由器与该主机是直接相连的。 IP路由选择主要完成以下这些功能: 1) 搜索路由表,寻找能与目的 I P地址完全匹配的表目(网络号和主机号都要匹配)。如果 找到,则把报文发送给该表目指定的下一站路由器或直接连接的网络接口(取决于标 志字段的值)。 2) 搜索路由表,寻找能与目的网络号相匹配的表目。如果找到,则把报文发送给该表目 指定的下一站路由器或直接连接的网络接口(取决于标志字段的值)。目的网络上的所 有主机都可以通过这个表目来处置。例如,一个以太网上的所有主机都是通过这种表 目进行寻径的。 这种搜索网络的匹配方法必须考虑可能的子网掩码。关于这一点我们在下一节中进行 讨论。 3) 搜索路由表,寻找标为“默认( d e f a u l t)”的表目。如果找到,则把报文发送给该表目 指定的下一站路由器。 如果上面这些步骤都没有成功,那么该数据报就不能被传送。如果不能传送的数据报来自 本机,那么一般会向生成数据报的应用程序返回一个“主机不可达”或“网络不可达”的错误。 完整主机地址匹配在网络号匹配之前执行。只有当它们都失败后才选择默认路由。默认 路由,以及下一站路由器发送的 I C M P间接报文(如果我们为数据报选择了错误的默认路由), 是IP路由选择机制中功能强大的特性。我们在第 9章对它们进行讨论。 为一个网络指定一个路由器,而不必为每个主机指定一个路由器,这是 I P路由选择机制 的另一个基本特性。这样做可以极大地缩小路由表的规模,比如 I n t e r n e t上的路由器有只有几 千个表目,而不会是超过 100万个表目。 举例 首先考虑一个简单的例子:我们的主机 b s d i有一个 I P数据报要发送给主机 s u n。双方都在

29

第3章 IP:网际协议使用

下载

同一个以太网上(参见扉页前图)。数据报的传输过程如图 3-3所示。 当IP从某个上层收到这份数据报后,它搜索路由表,发现目的 IP地址(140.252.13.33)在 一个直接相连的网络上(以太网 1 4 0 . 2 5 2 . 1 3 . 0)。于是,在表中找到匹配网络地址(在下一节 中,我们将看到,由于以太网的子网掩码的存在,实际的网络地址是 1 4 0 . 2 5 2 . 1 3 . 3 2,但是这 并不影响这里所讨论的路由选择)。 数据报被送到以太网驱动程序,然后

目的网络= 140.252,13,0

作为一个以太网数据帧被送到 s u n主机上 以太网140.252.13

(见图 2 - 1 )。I P 数据报中的目的地址是 s u n的I P地址( 1 4 0 . 2 5 2 . 1 3 . 3 3),而在链

链路层 IP首 首部 部

路层首部中的目的地址是 48 bit 的s u n主 目的

机的以太网接口地址。这个 48 bit 的以太

目的

网地址是用 A R P协议获得的,我们将在

图3-3 数据报从主机bsdi到sun的传送过程

下一章对此进行描述。

现在来看另一个例子:主机 b s d i有一份I P数据报要传到 f t p . u u . n e t主机上,它的 I P地 址是192.48.96.9。经过的前三个路由器如图 3-4所示。首先,主机 b s d i搜索路由表,但是没有 找到与主机地址或网络地址相匹配的表目,因此只能用默认的表目,把数据报传给下一站路 由器,即主机 s u n。当数据报从 b s d i被传到 s u n主机上以后,目的 I P地址是最终的信宿机地 址(192.48.96.9),但是链路层地址却是 s u n主机的以太网接口地址。这与图 3-3不同,在那里 数据报中的目的 IP地址和目的链路层地址都指的是相同的主机( sun)。 目的以太网=以太网的140.252.1.4 目的IP=192.48.96.9 下一站= 140.252.104.2 (默认)

链路层 IP 首部 首部

以太网,140.252.1 下一站= 140.252.1.4 (默认) 调制解 调器

IP 首部 调制解 调器

下一站= 140.252.13.33 (默认)

目的IP=192.48.96.9 下一站= 140.252.1.183 (默认)

以太网,140.252.13 链路层 IP 首部 首部

目的IP=192.48.96.9 目的以太网=以太网的140.252.13.33

图3-4 从bsdi 到ftp.uu.net

(192.48.96.9)的初始路径

30

使用TCP/IP详解,卷1:协议

下载

当s u n收到数据报后,它发现数据报的目的 I P地址并不是本机的任一地址,而 s u n已被设 置成具有路由器的功能,因此它把数据报进行转发。经过搜索路由表,选用了默认表目。根 据sun的默认表目,它把数据报转发到下一站路由器 n e t b,该路由器的地址是 140.252.1.183。 数据报是经过点对点 S L I P链路被传送的,采用了图 2 - 2所示的最小封装格式。这里,我们没有 给出像以太网链路层数据帧那样的首部,因为在 SLIP链路中没有那样的首部。 当netb收到数据报后,它执行与 s u n主机相同的步骤:数据报的目的地址不是本机地址, 而n e t b也被设置成具有路由器的功能,于是它也对该数据报进行转发。采用的也是默认路由 表目,把数据报送到下一站路由器 g a t e w a y(1 4 0 . 2 5 2 . 1 . 4)。位于以太网 1 4 0 . 2 5 2 . 1上的主机 netb用ARP获得对应于 140.252.1.4的48 bit以太网地址。这个以太网地址就是链路层数据帧头 上的目的地址。 路由器g a t e w a y也执行与前面两个路由器相同的步骤。它的默认路由表目所指定的下一 站路由器 I P地址是 1 4 0 . 2 5 2 . 1 0 4 . 2(我们将在图 8 - 4中证实,使用 Tr a c e r o u t e程序时,它就是 gateway使用的下一站路由器)。 对于这个例子需要指出一些关键点: 1) 该例子中的所有主机和路由器都使用了默认路由。事实上,大多数主机和一些路由器 可以用默认路由来处理任何目的,除非它在本地局域网上。 2) 数据报中的目的 I P地址始终不发生任何变化(在 8 . 5节中,我们将看到,只有使用源路 由选项时,目的 I P地址才有可能被修改,但这种情况很少出现)。所有的路由选择决策都是基 于这个目的 IP地址。 3) 每个链路层可能具有不同的数据帧首部,而且链路层的目的地址(如果有的话)始终 指的是下一站的链路层地址。在例子中,两个以太网封装了含有下一站以太网地址的链路层 首部,但是 SLIP链路没有这样做。以太网地址一般通过 ARP获得。 在第9章,我们在描述了 I C M P之后将再次讨论 I P路由选择问题。我们将看到一些路由表 的例子,以及如何用它们来进行路由决策的。

3.4 子网寻址 现在所有的主机都要求支持子网编址( RFC 950 [Mogul and Postel 1985])。不是把IP地址 看成由单纯的一个网络号和一个主机号组成,而是把主机号再分成一个子网号和一个主机号。 这样做的原因是因为 A类和B类地址为主机号分配了太多的空间,可分别容纳的主机数为 224-2和216-2。事实上,在一个网络中人们并不安排这么多的主机(各类 IP地址的格式如图 1-5 所示)。由于全0或全1的主机号都是无效的,因此我们把总数减去 2。 在InterNIC获得某类IP网络号后,就由当地的系统管理员来进行分配,由他(或她)来决 定是否建立子网,以及分配多少比特给子网号和主机号。例如,这里有一个

B类网络地址

(1 4 0 . 2 5 2),在剩下的 16 bit中,8 bit用于子网号, 8 bit用于主机号,格式如图 3 - 5所示。这样 就允许有254个子网,每个子网可以有 254台主机。 16位 B类

网络号=140.252

8位

8位

子网号

主机号

图3-5 B类地址的一种子网编址

31

第3章 IP:网际协议使用

下载

许多管理员采用自然的划分方法,即把 B类地址中留给主机的 16 bit中的前 8 bit 作为子网 地址,后 8 b i t作为主机号。这样用点分十进制方法表示的 I P地址就可以比较容易确定子网号。 但是,并不要求 A类或B类地址的子网划分都要以字节为划分界限。 大多数的子网例子都是 B类地址。其实,子网还可用于 C类地址,只是它可用的比特数较 少而已。很少出现 A类地址的子网例子是因为 A类地址本身就很少(但是,大多数 A类地址都 是进行子网划分的)。 子网对外部路由器来说隐藏了内部网络组织(一个校园或公司内部)的细节。在我们的 网络例子中,所有的 I P地址都有一个 B类网络号 1 4 0 . 2 5 2。但是其中有超过 3 0个子网,多于 4 0 0 台主机分布在这些子网中。由一台路由器提供了 Internet的接入,如图3-6所示。 在这个图中,我们把大多数的路由器编号为 Rn,n是子网号。我们给出了连接这些子网的 路由器,同时还包括了扉页前图中的九个系统。在图中,以太网用粗线表示,点对点链路用 虚线表示。我们没有画出不同子网中的所有主机。例如,在子网 1 4 0 . 2 5 2 . 3上,就超过 5 0台主 机,而在子网140.252.1上则超过100台主机。 与30个C类地址相比,用一个包含 30个子网的B类地址的好处是,它可以缩小 Internet路由 表的规模。 B类地址1 4 0 . 2 5 2被划分为若干子网的事实对于所有子网以外的 I n t e r n e t路由器都是 透明的。为了到达 I P地址开始部分为 1 4 0 . 2 5 2的主机,外部路由器只需要知道通往 I P 地址 1 4 0 . 2 5 2 . 1 0 4 . 1的路径。这就是说,对于网络 1 4 0 . 2 5 2只需一个路由表目,而如果采用 3 0个C类 地址,则需要 3 0个路由表目。因此,子网划分缩减了路由表的规模(在 1 0 . 8小节中,我们将 介绍一种新技术,即使用 C类地址也可以缩减路由表的规模)。 子网对于子网内部的路由器是不透明的。如图 3 - 6所示,一份来自 I n t e r n e t的数据报到达 g a t e w a y,它的目的地址是 1 4 0 . 2 5 2 . 5 7 . 1。路由器 g a t e w a y需要知道子网号是 5 7,然后把它 送到kpno。同样,kpno必须把数据报送到 R55,最后由R55把它送到R57。

图3-6 网络noao.edu(140.252)中的大多数子网安排

32

使用TCP/IP详解,卷1:协议

下载

3.5 子网掩码 任何主机在引导时进行的部分配置是指定主机 I P地址。大多数系统把 I P地址存在一个磁 盘文件里供引导时读用。在第 5章我们将讨论一个无盘系统如何在引导时获得 IP地址。 除了I P地址以外,主机还需要知道有多少比特用于子网号及多少比特用于主机号。这是 在引导过程中通过子网掩码来确定的。这个掩码是一个 32 bit的值,其中值为 1的比特留给网 络号和子网号,为 0的比特留给主机号。图 3-7是一个B类地址的两种不同的子网掩码格式。第 一个例子是 noao.edu网络采用的子网划分方法,如图 3-5所示,子网号和主机号都是 8 bit宽。 第二个例子是一个 B类地址划分成10 bit的子网号和 6 bit 的主机号。 8位

16位 网络号

B类

8位

子网号

主机号

子网掩码

B类

16位

10位

8位

网络号

子网号

主机号

子网掩码

图3-7 两种不同的B类地址子网掩码的例子

尽管I P地址一般以点分十进制方法表示,但是子网掩码却经常用十六进制来表示,特别 是当界限不是一个字节时,因为子网掩码是一个比特掩码。 给定I P地址和子网掩码以后,主机就可以确定 I P数据报的目的是:( 1)本子网上的主机; (2)本网络中其他子网中的主机;( 3)其他网络上的主机。如果知道本机的 I P地址,那么就知道 它是否为 A类、B类或C类地址(从IP地址的高位可以得知),也就知道网络号和子网号之间的分 界线。而根据子网掩码就可知道子网号与主机号之间的分界线。 举例 假设我们的主机地址是 1 4 0 . 2 5 2 . 1 . 1(一个B类地址),而子网掩码为 2 5 5 . 2 5 5 . 2 5 5 . 0(其中8 bit为子网号, 8 bit 为主机号)。 • 如果目的 I P地址是 1 4 0 . 2 5 2 . 4 . 5,那么我们就知道 B类网络号是相同的( 1 4 0 . 2 5 2),但是 子网号是不同的( 1和4)。用子网掩码在两个 IP地址之间的比较如图 3-8所示。 • 如果目的 I P地址是1 4 0 . 2 5 2 . 1 . 2 2,那么B类网络号还是一样的( 1 4 0 . 2 5 2),而且子网号也 是一样的( 1),但是主机号是不同的。 • 如果目的 I P地址是 1 9 2 . 4 3 . 2 3 5 . 6(一个 C类地址),那么网络号是不同的,因而进一步的 比较就不用再进行了。 B类网络号末尾 16位 B类 子网掩码

网络号相符

指明的子网号末尾

8位

8位

子网号不相等

B类

图3-8 使用子网掩码的两个B类地址之间的比较

33

第3章 IP:网际协议使用

下载

给定两个IP地址和子网掩码后, IP路由选择功能一直进行这样的比较。

3.6 特殊情况的IP地址 经过子网划分的描述,现在介绍 7个特殊的 I P地址,如图 3 - 9所示。在这个图中, 0表示所 有的比特位全为 0;-1表示所有的比特位全为 1;n e t i d、s u b n e t i d和h o s t i d分别表示不为全 0或全 1的对应字段。子网号栏为空表示该地址没有进行子网划分。 IP 地 址 网络号

可 以 为

子网号

主机号

源 端



目的端



0

0

OK

不可能

0

主机号

OK

不可能

网络上的特定主机(参见下面的限制)

127 -1

任何值 -1

OK

OK

环回地址( 2.7节)

不可能

OK

受限的广播(永远不被转发)

netid

-1

不可能

OK

以网络为目的向 netid广播

-1 -1

不可能

OK

以子网为目的向netid、subnetid广播

不可能

OK

以所有子网为目的向 netid广播

netid netid

subnetid -1

网络上的主机(参见下面的限制)

图3-9 特殊情况的IP地址

我们把这个表分成三个部分。表的头两项是特殊的源地址,中间项是特殊的环回地址, 最后四项是广播地址。 表中的头两项,网络号为 0,如主机使用BOOTP协议确定本机 IP地址时只能作为初始化过 程中的源地址出现。 在12.2节中,我们将进一步分析四类广播地址。

3.7 一个子网的例子 这个例子是本文中采用的子网,以及如何使用两个不同的子网掩码。具体安排如图 3 - 1 0 所示。

以太网,子网140.252.1 SLIP子网

以太网,子网140.252.13.32 作者所在子网140.252.13

图3-10 作者所在子网中的主机和网络安排

如果把该图与扉页前图相比,就会发现在图 3 - 1 0中省略了从路由器 s u n到上面的以太网之 间的连接细节,实际上它们之间的连接是拨号 S L I P。这个细节不影响本节中讨论的子网划分

34

使用TCP/IP详解,卷1:协议

下载

问题。我们在4.6节讨论ARP代理时将再回头讨论这个细节。 问题是我们在子网 1 3中有两个分离的网络:一个以太网和一个点对点链路(硬件连接的 S L I P链路)(点对点链接始终会带来问题,因为它一般在两端都需要 I P地址)。将来或许会有 更多的主机和网络,但是为了不让主机跨越不同的网络就得使用不同的子网号。我们的解决 方法是把子网号从 8 bit 扩充到 11 b i t,把主机号从 8 bit 减为 5 bit 。这就叫作变长子网,因为 140.252网络中的大多数子网都采用 8 bit 子网掩码,而我们的子网却采用 11 bit的子网掩码。 RFC 1009[Braden and Postel 1987]允许一个含有子网的网络使用多个子网掩码。新 的路由器需求RFC[Almquist 1993]则要求支持这一功能。 但是,问题在于并不是所有的路由选择协议在交换目的网络时也交换子网掩码。 在第10章中,我们将看到RIP不支持变长子网,RIP第2版和OSPF则支持变长子网。在 我们的例子中不存在这种问题,因为在我的子网中不要求使用RIP协议。 作者子网中的IP地址结构如图3-11所示,11位子网号中的前 8 bit始终是13。在剩下的 3 bit 中,我们用二进制 0 0 1表示以太网, 0 1 0表示点对点 S L I P链路。这个变长子网掩码在 1 4 0 . 2 5 2网 络中不会给其他主机和路由器带来问题 — 只要目的是子网 1 4 0 . 2 5 2 . 1 3的所有数据报都传给路 由器s u n(I P地址是 1 4 0 . 2 5 2 . 1 . 2 9),如图 3 - 11所示。如果 s u n知道子网 1 3中的主机有 11 bit子 网号,那么一切都好办了。 子网号ID

网络号=140.252

B类

5位 主机号ID

11位

16位

8位=13 子网掩码

图3-11 变长子网

140.252.13子网中的所有接口的子网掩码是 255.255.255.224,或0xffffffe0。这表明最右边 的5 bit 留给主机号,左边的 27 bit留给网络号和子网号。 图3-10中所有接口的IP地址和子网掩码的分配情况如图 3-12所示。 主机

IP地址

子网掩码

网络号 子网号

主机号





在子网1上 在作者所在子网上 在以太网上 点对点 点对点 以太网上的广播地址

图3-12 作者子网的IP地址

第1栏标为是“主机”,但是s u n和b s d i也具有路由器的功能,因为它们是多接口的,可 以把分组数据从一个接口转发到另一个接口。 这个表中的最后一行是图 3 - 1 0 中的广播地址 1 4 0 . 2 5 2 . 1 3 . 6 3:它是根据以太网子网号 (1 4 0 . 2 5 2 . 1 3 . 3 2)和图3 - 11中的低5位置1(1 6+8+4+2+1=3 1)得来的(我们在第 1 2章中将 看到,这个地址被称作以子网为目的的广播地址( subnet-directed broadcast address))。

35

第3章 IP:网际协议使用

下载 3.8 ifconfig命令

到目前为止,我们已经讨论了链路层和 I P层,现在可以介绍 T C P / I P对网络接口进行配置 和查询的命令了。 ifconfig(8)命令一般在引导时运行,以配置主机上的每个接口。 由于拨号接口可能会经常接通和挂断(如

S L I P 链路),每次线路接通和挂断时,

ifconfig都必须(以某种方法)运行。这个过程如何完成取决于使用的 SLIP软件。 下面是作者子网接口的有关参数。请把它们与图 3-12的值进行比较。 在所有接口报告的选项

环回接口( 2.7节)被认为是一个网络接口。它是一个 A类地址,没有进行子网划分。 需要注意的是以太网没有采用尾部封装( 2 . 3节),而且可以进行广播,而 S L I P链路是一 个点对点的链接。 SLIP接口的标志 L I N K 0是一个允许压缩 slip的数据(CSLIP,参见2.5节)的配置选项。其 他的选项有 L I N K 1(如果从另一端收到一份压缩报文,就允许采用 C S L I P)和 L I N K 2(所有 外出的ICMP报文都被丢弃)。我们在4.6节中将讨论 SLIP链接的目的地址。 安装指南中的注释对最后这个选项进行了解释:“一般它不应设置,但是由于一些 不当的ping操作,可能会导致吞吐量降到0。” b s d i是另一台路由器。由于- a参数是S u n O S操作系统具有的功能,因此我们必须多次执 行ifconfig,并指定接口名字参数:

这里,我们看到以太网接口( w e 0)的一个新选项: S I M P L E X。这个4 . 4 B S D标志表明接 口不能收到本机传送的数据。在 B S D / 3 8 6中所有的以太网都这样设置。一旦这样设置后,如 果接口发送一帧数据到广播地址,那么就会为本机拷贝一份数据送到环回地址(在 6 . 3小节我 们将举例说明这一点)。 在主机slip中,SLIP接口的设置基本上与上面的bsdi一致,只是两端的IP地址进行了互换: slip % /s b i n / i f c o n f i g s l 0 sl0: flags=1011 inet 140.252.13.65 --> 140.252.13.66 netmask ffffffe0

最后一个接口是主机 s v r 4上的以太网接口。它与前面的以太网接口类似,只是 S V R 4版 的ifconfig没有打印RUNNING标志: svr4 % /u s r / s b i n / i f c o n f i g e m d 0 emd0: flags=23 inet 140.252.13.34 netmask ffffffe0 broadcast 140.252.13.63

36

使用TCP/IP详解,卷1:协议

下载

i f c o n f i g命令一般支持 T C P / I P以外的其他协议族,而且有很多参数。关于这些细节可 以查看系统说明书。

3.9 netstat命令 n e t s t a t( 1 )命令也提供系统上的接口信息。 - i参数将打印出接口信息, - n参数则打印出 IP地址,而不是主机名字。

这个命令打印出每个接口的 M T U、输入分组数、输入错误、输出分组数、输出错误、冲 突以及当前的输出队列长度。 在第9章将用 n e t s t a t命令检查路由表,那时再回头讨论该命令。另外,在第 1 3章将用 它的一个改进版本来查看活动的广播组。

3.10 IP的未来 I P主要存在三个方面的问题。这是 I n t e r n e t在过去几年快速增长所造成的结果(参见习题 1.2)。 1) 超过半数的 B类地址已被分配。根据估计,它们大约在 1995年耗尽。 2) 32 bit的IP地址从长期的Internet增长角度来看,一般是不够用的。 3) 当前的路由结构没有层次结构,属于平面型 (flat)结构,每个网络都需要一个路由表目。 随着网络数目的增长,一个具有多个网络的网站就必须分配多个 C类地址,而不是一个 B类地 址,因此路由表的规模会不断增长。 无类别的域间路由选择 C I D R(Classless Interdomain Routing)提出了一个可以解决第三 个问题的建议,对当前版本的 IP(IP版本4)进行扩充,以适应 21世纪Internet的发展。对此我 们将在10.8节进一步详细介绍。 对新版的 I P,即下一代 I P,经常称作 I P n g,主要有四个方面的建议。 1 9 9 3年5月发行的 IEEE Network (vol.7, no.3) 对前三个建议进行了综述,同时有一篇关于 C I D R的论文。 R F C 1454 [Dixon 1993]对前三个建议进行了比较。 1) SIP,简单Internet协议。它针对当前的 IP提出了一个最小幅度的修改建议,采用 64位地 址和一个不同的首部格式(首部的前 4比特仍然包含协议的版本号,其值不再是 4)。 2) PIP。这个建议也采用了更大的、可变长度的和有层次结构的地址,而且首部格式也不 相同。 3) TUBA , 代 表“ TCP and UDP with Bigger Address ”, 它基 于 OSI 的 C L N P (Connectionless Network Protocol,无连接网络协议),一个与IP类似的OSI协议。它提供大得 多的地址空间:可变长度,可达 20个字节。由于 CLNP是一个现有的协议,而 SIP和PIP只是建 议,因此关于 CLNP的文档已经出现。 RFC 1347[Callon 1992]提供了TUBA的有关细节。文献 [Perlman 1992]的第7章对I P v 4和C L N P进行了比较。许多路由器已经支持 C L N P,但是很少有 主机也提供支持。

下载

37

第3章 IP:网际协议使用

4) TP/IX,由RFC 1475 [Ullmann 1993]对它进行了描述。虽然 SIP采用了64 bit的址址,但 是它还改变了 T C P和U D P的格式:两个协议均为 32 bit的端口号, 64 bit的序列号, 64 bit的确 认号,以及 TCP的32 bit窗口。 前三个建议基本上采用了相同版本的 TCP和UDP作为传输层协议。 由于四个建议只能有一个被选为 I P v 4的替换者,而且在你读到此书时可能已经做出选择, 因此我们对它们不进行过多评论。虽然 C I D R即将实现以解决目前的短期问题,但是 I P v 4后继 者的实现则需要经过许多年。

3.11 小结 本章开始描述了 I P首部的格式,并简要讨论了首部中的各个字段。我们还介绍了 I P路由 选择,并指出主机的路由选择可以非常简单:如果目的主机在直接相连的网络上,那么就把 数据报直接传给目的主机,否则传给默认路由器。 在进行路由选择决策时,主机和路由器都使用路由表。在表中有三种类型的路由:特定 主机型、特定网络型和默认路由型。路由表中的表目具有一定的优先级。在选择路由时,主 机路由优先于网络路由,最后在没有其他可选路由存在时才选择默认路由。 I P路由选择是通过逐跳来实现的。数据报在各站的传输过程中目的 I P地址始终不变,但 是封装和目的链路层地址在每一站都可以改变。大多数的主机和许多路由器对于非本地网络 的数据报都使用默认的下一站路由器。 A类和B类地址一般都要进行子网划分。用于子网号的比特数通过子网掩码来指定。我们 为此举了一个实例来详细说明,即作者所在的子网,并介绍了变长子网的概念。子网的划分 缩小了 I n t e r n e t路由表的规模,因为许多网络经常可以通过单个表目就可以访问了。接口和网 络的有关信息通过 i f c o n f i g和n e t s t a t命令可以获得,包括接口的 I P地址、子网掩码、广 播地址以及 MTU等。 在本章的最后,我们对 Internet协议族潜在的改进建议 — 下一代IP进行了讨论。

习题 3.1 环回地址必须是 127.0.0.1吗? 3.2 在图3-6中指出有两个网络接口的路由器。 3.3 子网号为16 bit 的A类地址与子网号为 8 bit 的B类地址的子网掩码有什么不同? 3.4 阅读RFC 1219 [Tsuchiya 1991],学习分配子网号和主机号的有关推荐技术。 3.5 子网掩码255.255.0.255是否对A类地址有效? 3.6 你认为为什么3.9小节中打印出来的环回接口的 MTU要设置为1536? 3.7 T C P / I P协议族是基于一种数据报的网络技术,即 I P层,其他的协议族则基于面向连接的 网络技术。阅读文献 [Clark 1988],找出数据报网络层提供的三个优点。

下载

第4章 ARP:地址解析协议 4.1 引言 本章我们要讨论的问题是只对 T C P / I P协议簇有意义的 I P地址。数据链路如以太网或令牌 环网都有自己的寻址机制(常常为 48 bit 地址),这是使用数据链路的任何网络层都必须遵从 的。一个网络如以太网可以同时被不同的网络层使用。例如,一组使用 T C P / I P协议的主机和 另一组使用某种 PC网络软件的主机可以共享相同的电缆。 当一台主机把以太网数据帧发送到位于同一局域网上的另一台主机时,是根据 48 bit的以 太网地址来确定目的接口的。设备驱动程序从不检查 IP数据报中的目的 IP地址。 地址解析为这两种不同的地址形式提供映射: 32 bit 的I P

32位Internet地址

地址和数据链路层使用的任何类型的地址。 RFC 826 [Plummer 1982]是ARP规范描述文档。 本章及下一章我们要讨论的两种协议如图 4 - 1所示: A R P 48位以太网地址

(地址解析协议)和 RARP(逆地址解析协议)。 A R P为I P地址到对应的硬件地址之间提供动态映射。我们

图4-1 地址解析协议:ARP

之所以用动态这个词是因为这个过程是自动完成的,一般应用

和RARP

程序用户或系统管理员不必关心。 R A R P是被那些没有磁盘驱动器的系统使用(一般是无盘工作站或 X终端),它需要系统 管理员进行手工设置。我们在第 5章对它进行讨论。

4.2 一个例子 任何时候我们敲入下面这个形式的命令: % ftp bsdi

都会进行以下这些步骤。这些步骤的序号如图 4-2所示。 1) 应用程序FTP客户端调用函数gethostbyname(3)把主机名(bsdi)转换成32 bit的IP地址。 这个函数在DNS(域名系统)中称作解析器,我们将在第 14章对它进行介绍。这个转换 过程或者使用DNS,或者在较小网络中使用一个静态的主机文件(/etc/hosts)。 2) FTP客户端请求 TCP用得到的IP地址建立连接。 3) T C P发送一个连接请求分段到远端的主机,即用上述 I P地址发送一份 I P数据报(在第 18章我们将讨论完成这个过程的细节)。 4) 如果目的主机在本地网络上(如以太网、令牌环网或点对点链接的另一端),那么I P数 据报可以直接送到目的主机上。如果目的主机在一个远程网络上,那么就通过 I P选路 函数来确定位于本地网络上的下一站路由器地址,并让它转发 I P数据报。在这两种情 况下,IP数据报都是被送到位于本地网络上的一台主机或路由器。 5) 假定是一个以太网,那么发送端主机必须把 32 bit的I P地址变换成48 bit的以太网地址。

39

第4章 ARP:地址解析协议使用

下载

从逻辑Internet地址到对应的物理硬件地址需要进行翻译。这就是 ARP的功能。 ARP本来是用于广播网络的,有许多主机或路由器连在同一个网络上。 6) A R P发送一份称作 A R P请求的以太网数据帧给以太网上的每个主机。这个过程称作广 播,如图 4 - 2中的虚线所示。 A R P请求数据帧中包含目的主机的 I P 地址(主机名为 bsdi),其意思是“如果你是这个 IP地址的拥有者,请回答你的硬件地址。” 主机名 主机名 解析器 IP地址

用IP地址建立连接

给IP地址发送IP数据报

以太网 驱动程序 ARP请求(以太网广播)

以太网驱动程序

以太网 驱动程序

图4-2 当用户输入命令“ftp 主机名”时ARP的操作

7) 目的主机的 A R P层收到这份广播报文后,识别出这是发送端在寻问它的 I P地址,于是 发送一个ARP应答。这个 ARP应答包含IP地址及对应的硬件地址。 8) 收到ARP应答后,使 ARP进行请求—应答交换的 IP数据报现在就可以传送了。 9) 发送IP数据报到目的主机。 在A R P背后有一个基本概念,那就是网络接口有一个硬件地址(一个 48 bit的值,标识不 同的以太网或令牌环网络接口)。在硬件层次上进行的数据帧交换必须有正确的接口地址。但 是,T C P / I P有自己的地址: 32 bit的I P地址。知道主机的 I P地址并不能让内核发送一帧数据给 主机。内核(如以太网驱动程序)必须知道目的端的硬件地址才能发送数据。 A R P的功能是 在32 bit 的IP地址和采用不同网络技术的硬件地址之间提供动态映射。 点对点链路不使用 A R P。当设置这些链路时(一般在引导过程进行),必须告知内核链路

40

使用TCP/IP详解,卷1:协议

下载

每一端的IP地址。像以太网地址这样的硬件地址并不涉及。

4.3 ARP高速缓存 A R P高效运行的关键是由于每个主机上都有一个 A R P高速缓存。这个高速缓存存放了最 近I n t e r n e t地址到硬件地址之间的映射记录。高速缓存中每一项的生存时间一般为 2 0分钟,起 始时间从被创建时开始算起。 我们可以用arp(8)命令来检查ARP高速缓存。参数-a的意思是显示高速缓存中所有的内容。 bsdi % a r p - a sun (140.252.13.33) at 8:0:20:3:f6:42 svr4 (140.252.13.34) at 0:0:c0:c2:9b:26

48 bit的以太网地址用 6个十六进制的数来表示,中间以冒号隔开。在 4 . 8小节我们将讨论 arp命令的其他功能。

4.4 ARP的分组格式 在以太网上解析 I P地址时, A R P请求和应答分组的格式如图 4 - 3所示( A R P可以用于其他 类型的网络,可以解析 I P地址以外的地址。紧跟着帧类型字段的前四个字段指定了最后四个 字段的类型和长度)。 硬件地址长度 协议地址长度 以太网 目的地址

以太网 源地址

帧 硬件 协议 类型 类型 类型

以太网首部

发送端 以太网地址

发送端 IP地址

目的以太网 地址

目的 IP地址

28字节ARP请求/应答

图4-3 用于以太网的ARP请求或应答分组格式

以太网报头中的前两个字段是以太网的源地址和目的地址。目的地址为全 1的特殊地址是 广播地址。电缆上的所有以太网接口都要接收广播的数据帧。 两个字节长的以太网帧类型表示后面数据的类型。对于 A R P请求或应答来说,该字段的 值为0x0806。 形容词hardware(硬件)和protocol(协议)用来描述 ARP分组中的各个字段。例如,一个 ARP 请求分组询问协议地址(这里是 IP地址)对应的硬件地址(这里是以太网地址)。 硬件类型字段表示硬件地址的类型。它的值为 1即表示以太网地址。协议类型字段表示要 映射的协议地址类型。它的值为 0 x 0 8 0 0即表示 I P地址。它的值与包含 I P数据报的以太网数据 帧中的类型字段的值相同,这是有意设计的(参见图 2-1)。 接下来的两个 1字节的字段,硬件地址长度和协议地址长度分别指出硬件地址和协议地址 的长度,以字节为单位。对于以太网上 IP地址的ARP请求或应答来说,它们的值分别为 6和4。 操作字段指出四种操作类型,它们是 ARP请求(值为1)、ARP应答(值为 2)、RARP请求 (值为 3)和R A R P应答(值为 4)(我们在第 5章讨论R A R P)。这个字段必需的,因为 A R P请求 和ARP应答的帧类型字段值是相同的。 接下来的四个字段是发送端的硬件地址(在本例中是以太网地址)、发送端的协议地址 (I P地址)、目的端的硬件地址和目的端的协议地址。注意,这里有一些重复信息:在以太网

41

第4章 ARP:地址解析协议使用

下载

的数据帧报头中和 ARP请求数据帧中都有发送端的硬件地址。 对于一个 A R P请求来说,除目的端硬件地址外的所有其他的字段都有填充值。当系统收 到一份目的端为本机的 A R P请求报文后,它就把硬件地址填进去,然后用两个目的端地址分 别替换两个发送端地址,并把操作字段置为 2,最后把它发送回去。

4.5 ARP举例 在本小节中,我们用 t c p d u m p命令来看一看运行像 Telnet这样的普通 TCP工具软件时 ARP 会做些什么。附录 A包含tcpdump命令的其他细节。 4.5.1 一般的例子 为了看清楚 ARP的运作过程,我们执行 telnet命令与无效的服务器连接。 检验ARP高速缓存是空的 连接无效的服务器

键入Ctrl和右括号,使Telnet回到提示符并关闭

当我们在另一个系统( s u n)上运行带有- e选项的 t c p d u m p命令时,显示的是硬件地址 (在我们的例子中是 48 bit 的以太网地址)。 图4 - 4中的t c p d u m p的原始输出如附录 A中的图A - 3所示。由于这是本书第一个 t c p d u m p 输出例子,你应该去查看附录中的原始输出,看看我们作了哪些修改。

图4-4 TCP连接请求产生的ARP请求和应答

我们删除了 t c p d u m p命令输出的最后四行,因为它们是结束连接的信息(我们将在第 1 8 章进行讨论),与这里讨论的内容不相关。 在第1行中,源端主机( b s d i)的硬件地址是 0 : 0 : c 0 : 6 f : 2 d : 4 0。目的端主机的硬件地址是 ff : ff : ff : ff : ff : ff,这是一个以太网广播地址。电缆上的每个以太网接口都要接收这个数据帧并对 它进行处理,如图 4-2所示。 第1行中紧接着的一个输出字段是 a r p,表明帧类型字段的值是 0 x 0 8 0 6,说明此数据帧是 一个ARP请求或回答。 在每行中,单词 a r p或i p后面的值 6 0指的是以太网数据帧的长度。由于 A R P请求或回答

42

使用TCP/IP详解,卷1:协议

下载

的数据帧长都是 42字节(28字节的ARP数据,14字节的以太网帧头),因此,每一帧都必须加 入填充字符以达到以太网的最小长度要求: 60字节。 请参见图 1 - 7,这个最小长度 6 0字节包含 1 4字节的以太网帧头,但是不包括 4个字节的以 太网帧尾。有一些书把最小长度定为 6 4字节,它包括以太网的帧尾。我们在图 1 - 7中把最小长 度定为 4 6字节,是有意不包括 1 4字节的帧首部,因为对应的最大长度( 1 5 0 0字节)指的是 MTU — 最大传输单元(见图 2-5)。我们使用MTU经常是因为它对 IP数据报的长度进行限制, 但一般与最小长度无关。大多数的设备驱动程序或接口卡自动地用填充字符把以太网数据帧 充满到最小长度。第 3,4和5行中的 I P数据报(包含 T C P段)的长度都比最小长度短,因此都 必须填充到 60字节。 第1行中的下一个输出字段 a r p w h o - h a s表示作为 A R P请求的这个数据帧中,目的 I P地 址是s v r 4的地址,发送端的 I P地址是b s d i的地址。 t c p d u m p打印出主机名对应的默认 I P地址 (在4.7节中,我们将用- n选项来查看 ARP请求中真正的IP地址。) 从第 2 行中可以看到,尽管 A R P 请求是广播的,但是 A R P 应答的目的地址却是 b s d i (0:0:c0:6f:2d:40)。ARP应答是直接送到请求端主机的,而是广播的。 tcpdump打印出a r p r e p l 的 y 字样,同时打印出响应者的主机名和硬件地址。 第3行是第一个请求建立连接的 T C P段。它的目的硬件地址是目的主机 (s v r 4)。我们将在 第18章讨论这个段的细节内容。 在每一行中,行号后面的数字表示 tcpdump收到分组的时间(以秒为单位)。除第1行外, 其他每行在括号中还包含了与上一行的时间差异(以秒为单位)。从这个图可以看出,发送 ARP请求与收到 ARP回答之间的延时是 2.2 ms。而在0.7 ms之后发出第一段 TCP报文。在本例 中,用ARP进行动态地址解析的时间小于 3 ms。 最后需要指出的一点,在 t c p d u m p命令输出中,我们没有看到 s v r 4在发出第一段 T C P报 文(第4行)之前发出的 A R P请求。这是因为可能在 s v r 4的A R P高速缓存中已经有 b s d i的表 项。一般情况下,当系统收到 A R P请求或发送 A R P应答时,都要把请求端的硬件地址和 I P地 址存入 A R P高速缓存。在逻辑上可以假设,如果请求端要发送 I P数据报,那么数据报的接收 端将很可能会发送一个应答。 4.5.2 对不存在主机的ARP请求 如果查询的主机已关机或不存在会发生什么情况呢?为此我们指定一个并不存在的 Internet地址 — 根据网络号和子网号所对应的网络确实存在,但是并不存在所指定的主机号。 从图3 - 1 0可以看出,主机号从 3 6到6 2的主机并不存在(主机号为 6 3是广播地址)。这里,我们 用主机号36来举例。 这次是Telnet的一个地址,而不是主机名

在前一个日期输出后76秒 检查ARP高速缓存

tcpdump命令的输出如图 4-5所示。

43

第4章 ARP:地址解析协议使用

下载

图4-5 对不存在主机的ARP请求

这一次,我们没有用- e选项,因为已经知道 ARP请求是在网上广播的。 令人感兴趣的是看到多次进行 A R P请求:第 1次请求发生后 5 . 5秒进行第 2次请求,在 2 4秒 之后又进行第 3次请求(在第 2 1章我们将看到 T C P的超时和重发算法的细节)。t c p d u m p命令 输出的超时限制为 2 9 . 5秒。但是,在 t e l n e t命令使用前后分别用 d a t e命令检查时间,可以 发现Telnet客户端的连接请求似乎在大约 75秒后才放弃。事实上,我们在后面将看到,大多数 的BSD实现把完成 TCP连接请求的时间限制设置为 75秒。 在第18章中,当我们看到建立连接的 TCP报文段序列时,会发现 ARP请求对应于TCP试图 发送的初始 TCPSYN(同步)段。 注意,在线路上始终看不到 TCP的报文段。我们能看到的是 ARP请求。直到 ARP回答返回 时,T C P报文段才可以被发送,因为硬件地址到这时才可能知道。如果我们用过滤模式运行 tcpdump命令,只查看TCP数据,那么将没有任何输出。 4.5.3 ARP高速缓存超时设置 在A R P高速缓存中的表项一般都要设置超时值(在 4 . 8小节中,我们将看到管理员可以用 a r p命令把地址放入高速缓存中而不设置超时值)。从伯克利系统演变而来的系统一般对完整 的表项设置超时值为 2 0分钟,而对不完整的表项设置超时值为 3分钟(在前面的例子中我们已 见过一个不完整的表项,即在以太网上对一个不存在的主机发出 A R P请求。)当这些表项再次 使用时,这些实现一般都把超时值重新设为 20分钟。 Host Requirements RFC表明即使表项正在使用时,超时值也应该启动,但是大多数从伯 克利系统演变而来的系统没有这样做 — 它们每次都是在访问表项时重设超时值。

4.6 ARP代理 如果A R P请求是从一个网络的主机发往另一个网络上的主机,那么连接这两个网络的路 由器就可以回答该请求,这个过程称作委托 A R P或A R P代理(Proxy ARP)。这样可以欺骗发起 A R P请求的发送端,使它误以为路由器就是目的主机,而事实上目的主机是在路由器的“另 一边”。路由器的功能相当于目的主机的代理,把分组从其他主机转发给它。 举例是说明 ARP代理的最好方法。如图 3-10所示,系统s u n与两个以太网相连。但是,我 们也指出过,事实上并不是这样,请把它与封内图 1进行比较。在 s u n和子网1 4 0 . 2 5 2 . 1之间实 际存在一个路由器,就是这个具有 ARP代理功能的路由器使得 sun就好像在子网 140.252.1上一 样。具体安置如图4-6所示,路由器Telebit NetBlazer,取名为netb,在子网和主机sun之间。 当子网140.252.1(称作gemini)上的其他主机有一份 IP数据报要传给地址为 140.252.1.29 的sun时,gemini比较网络号(140.252)和子网号(1),因为它们都是相同的,因而在图 4-6 上面的以太网中发送IP地址140.252.1.29的ARP请求。路由器netb识别出该IP地址属于它的一个 拔号主机,于是把它的以太网接口地址 140.252.1作为硬件地址来回答。主机 gemini通过以太 网发送 I P数据报到 n e t b,n e t b通过拨号 S L I P链路把数据报转发到 s u n。这个过程对于所有

44

使用TCP/IP详解,卷1:协议

下载

140.252.1子网上的主机来说都是透明的,主机sun实际上是在路由器netb后面进行配置的。

ARP请求发给140.252.1.29 以太网,子网140.252.1

ARP应答 Telebit NetBlazer路由器配 置作为s u n的代理ARP 调制解调器

(拨号) 调制解调器

以太网140.252.13

图4-6 ARP代理的例子

如果在主机 g e m i n i上执行a r p命令,经过与主机 s u n通信以后,我们发现在同一个子网 140.252.1上的netb和sun的IP地址映射的硬件地址是相同的。这通常是使用委托 ARP的线索。 gemini % a r p - a 这里是子网 1 4 0 . 2 5 2 . 1上其他主机的输出行 netb (140.252.1.183) at 0:80:ad:3:6a:80 sun (140.252.1.29) at 0:80:ad:3:6a:80

图4 - 6中的另一个需要解释的细节是在路由器 n e t b的下方( S L I P链路)显然缺少一个 I P 地址。为什么在拨号 S L I P链路的两端只拥有一个 I P地址,而在 b s d i和s l i p之间的两端却分 别有一个 I P地址?在 3 . 8小节我们已经指出,用 i f c o n f i g命令可以显示拨号 S L I P链路的目的 地址,它是 1 4 0 . 2 5 2 . 1 . 1 8 3。N e t B l a z e r不需要知道拨号 S L I P链路每一端的 I P地址(这样做会用 更多的I P地址)。相反,它通过分组到达的串行线路接口来确定发送分组的拨号主机,因此对 于连接到路由器的每个拨号主机不需要用唯一的 I P地址。所有的拨号主机使用同一个 I P地址 140.252.1.183作为SLIP链路的目的地址。 A R P代理可以把数据报传送到路由器 s u n上,但是子网 1 4 0 . 2 5 2 . 1 3上的其他主机是如何处 理的呢?选路必须使数据报能到达其他主机。这里需要特殊处理,选路表中的表项必须在网 络1 4 0 . 2 5 2的某个地方制定,使所有数据报的目的端要么是子网 1 4 0 . 2 5 2 . 1 3,要么是子网上的 某个主机,这样都指向路由器 n e t b。而路由器 n e t b知道如何把数据报传到最终的目的端, 即通过路由器sun。 A R P代理也称作混合 A R P(p r o m i s c u o u s A R P)或ARP 出租(ARP hack)。这些名字来自于 A R P代理的其他用途:通过两个物理网络之间的路由器可以互相隐藏物理网络。在这种情况 下,两个物理网络可以使用相同的网络号,只要把中间的路由器设置成一个 A R P代理,以响 应一个网络到另一个网络主机的 A R P请求。这种技术在过去用来隐藏一组在不同物理电缆上 运行旧版 TCP/IP的主机。分开这些旧主机有两个共同的理由,其一是它们不能处理子网划分, 其二是它们使用旧的广播地址(所有比特值为 0的主机号,而不是目前使用的所有比特值为 1

45

第4章 ARP:地址解析协议使用

下载 的主机号)。

4.7 免费ARP 我们可以看到的另一个 A R P特性称作免费 ARP (gratuitous ARP)。它是指主机发送 A R P查 找自己的IP地址。通常,它发生在系统引导期间进行接口配置的时候。 在互联网中,如果我们引导主机 b s d i并在主机 s u n上运行t c p d u m p命令,可以看到如图 4-7所示的分组。

图4-7 免费ARP的例子

(我们用- n选项运行 t c p d u m p命令,打印出点分十进制的地址,而不是主机名)。对于 A R P请求中的各字段来说,发送端的协议地址和目的端的协议地址是一致的:即主机 b s d i的 地址140.252.13.35。另外,以太网报头中的源地址 0:0:c0:6f:2d:40,正如t c p d u m p命令显示的 那样,等于发送端的硬件地址(见图 4-4)。 免费ARP可以有两个方面的作用: 1) 一个主机可以通过它来确定另一个主机是否设置了相同的 IP地址。主机b s d i并不希望 对此请求有一个回答。但是,如果收到一个回答,那么就会在终端日志上产生一个错误消息 “以太网地址: a : b : c : d : e : f发送来重复的 I P地址”。这样就可以警告系统管理员,某个系统有不 正确的设置。 2) 如果发送免费 A R P的主机正好改变了硬件地址(很可能是主机关机了,并换了一块接 口卡,然后重新启动),那么这个分组就可以使其他主机高速缓存中旧的硬件地址进行相应的 更新。一个比较著名的ARP协议事实[Plummer 1982]是,如果主机收到某个IP地址的ARP请求, 而且它已经在接收者的高速缓存中,那么就要用 A R P请求中的发送端硬件地址(如以太网地 址)对高速缓存中相应的内容进行更新。主机接收到任何 A R P请求都要完成这个操作( A R P 请求是在网上广播的,因此每次发送 ARP请求时网络上的所有主机都要这样做)。 文献[Bhide、Elnozahy和Morgan 1991]中有一个应用例子,通过发送含有备份硬件地址和 故障服务器的 I P地址的免费 A R P请求,使得备份文件服务器可以顺利地接替故障服务器进行 工作。这使得所有目的地为故障服务器的报文都被送到备份服务器那里,客户程序不用关心 原来的服务器是否出了故障。 不幸的是,作者却反对这个做法,因为这取决于所有不同类型的客户端都要有正 确的ARP协议实现。他们显然碰到过客户端的ARP协议实现与规范不一致的情况。 通过检查作者所在子网上的所有系统可以发现, SunOS 4.1.3和4.4BSD在引导时都 发送免费ARP,但是SVR4却没有这样做。

4.8 arp命令 我们已经用过这个命令及参数- a来显示A R P高速缓存中的所有内容。这里介绍其他参数 的功能。 超级用户可以用选项- d来删除 A R P高速缓存中的某一项内容(这个命令格式可以在运行

46

使用TCP/IP详解,卷1:协议

下载

一些例子之前使用,以让我们看清楚 ARP的交换过程)。 另外,可以通过选项- s来增加高速缓存中的内容。这个参数需要主机名和以太网地址: 对应于主机名的 I P地址和以太网地址被增加到高速缓存中。新增加的内容是永久性的(比如, 它没有超时值),除非在命令行的末尾附上关键字 temp。 位于命令行末尾的关键字 p u b和- s选项一起,可以使系统起着主机 A R P代理的作用。系统 将回答与主机名对应的 I P地址的 A R P请求,并以指定的以太网地址作为应答。如果广播的地 址是系统本身,那么系统就为指定的主机名起着委托 ARP代理的作用。

4.9 小结 在大多数的 T C P / I P实现中, A R P是一个基础协议,但是它的运行对于应用程序或系统管 理员来说一般是透明的。 A R P高速缓存在它的运行过程中非常关键,我们可以用 a r p命令对高 速缓存进行检查和操作。高速缓存中的每一项内容都有一个定时器,根据它来删除不完整和 完整的表项。arp命令可以显示和修改 ARP高速缓存中的内容。 我们介绍了 A R P的一般操作,同时也介绍了一些特殊的功能:委托 A R P(当路由器对来 自于另一个路由器接口的 A R P请求进行应答时)和免费 A R P(发送自己 I P地址的 A R P请求, 一般发生在引导过程中)。

习题 4.1 当输入命令以生成类似图 4-4那样的输出时,发现本地 ARP快速缓存为空以后,输入命令 bsdi % r s h s v r 4 a r p - a 如果发现目的主机上的 A R P快速缓存也是空的,那将发生什么情况? (该命令将在 s v r 4 主机上运行 arp - a命令)。 4.2 请描述如何判断一个给定主机是否能正确处理接收到的非必要的 ARP请求的方法。 4.3 由于发送一个数据包后 A R P将等待响应,因此 4 . 2节所描述的步骤 7可能会持续一段时间。 你认为ARP将如何处理在这期间收到相同目的 IP地址发来的多个数据包? 4.4 在4.5节的最后,我们指出 Host Requirements RFC和伯克利派生系统在处理活动 ARP表目 的超时时存在差异。那么如果我们在一个由伯克利派生系统的客户端上,试图与一个正 在更换以太网卡而处于关机状态的服务器主机联系,这时会发生什么情况?如果服务器 在引导过程中广播一份免费 ARP,这种情况是否会发生变化?

下载

第5章 RARP:逆地址解析协议 5.1 引言 具有本地磁盘的系统引导时,一般是从磁盘上的配置文件中读取 I P地址。但是无盘机, 如X终端或无盘工作站,则需要采用其他方法来获得 IP地址。 网络上的每个系统都具有唯一的硬件地址,它是由网络接口生产厂家配置的。无盘系统 的R A R P实现过程是从接口卡上读取唯一的硬件地址,然后发送一份 R A R P请求(一帧在网络 上广播的数据),请求某个主机响应该无盘系统的 IP地址(在RARP应答中)。 在概念上这个过程是很简单的,但是实现起来常常比 A R P要困难,其原因在本章后面介 绍。RARP的正式规范是RFC 903 [Finlayson et al. 1984]。

5.2 RARP的分组格式 R A R P分组的格式与 A R P分组基本一致(见图 4 - 3)。它们之间主要的差别是 R A R P请求或 应答的帧类型代码为 0x8035,而且RARP请求的操作代码为 3,应答操作代码为 4。 对应于ARP,RARP请求以广播方式传送,而 RARP应答一般是单播 (unicast)传送的。

5.3 RARP举例 在互联网中,我们可以强制 s u n主机从网络上引导,而不是从本地磁盘引导。如果在主 机bsdi上运行RARP服务程序和 t c p d u m p命令,就可以得到如图 5-1那样的输出。用 -e参数使 得tcpdump命令打印出硬件地址:

图5-1 RARP请求和应答

R A R P请求是广播方式(第 1行),而第 2行的R A R P应答是单播方式。第 2行的输出中 a t sun表示RARP应答包含主机sun的IP地址(140.252.13.33)。 在第3行中,我们可以看到,一旦 s u n收到IP地址,它就发送一个 TFTP读请求( RRQ)给 文件8 C F C 0 D 2 1 . S U N 4 C(T F T P表示简单文件传送协议。我们将在第 1 5章详细介绍)。文件名 中的8个十六进制数字表求主机 s u n的I P地址1 4 0 . 2 5 2 . 1 3 . 3 3。这个I P地址在R A R P应答中返回。 文件名的后缀SUN4C表示被引导系统的类型。 tcpdump在第3行中指出IP数据报的长度是65个字节,而不是一个UDP数据报(实际上是一 个UDP数据报),因为我们运行tcpdump命令时带有- e参数,以查看硬件层的地址。在图5-1中

48

使用TCP/IP详解,卷1:协议

下载

需要指出的另一点是,第2行中的以太网数据帧长度比最小长度还要小(在4.5节中我们说过应该 是60字节) 。其原因是我们在发送该以太网数据帧的系统(bsdi)上运行tcpdump命令。应用程 序rarpd写42字节到BSD分组过滤设备上(其中14字节为以太网数据帧的报头,剩下的28字节是 RARP应答),这就是tcpdump收到的副本。但是以太网设备驱动程序要把这一短帧填充空白字 符以达到最小传输长度(60)。如果我们在另一个系统上运行tcpdump命令,其长度将会是60。 从这个例子可以看出,当无盘系统从 R A R P应答中收到它的 I P地址后,它将发送 T F T P请 求来读取引导映象。在这一点上我们将不再进一步详细讨论无盘系统是如何引导的(第 1 6章 将描述无盘 X终端利用RARP、BOOTP以及TFTP进行引导的过程)。 当网络上没有RARP服务器时,其结果如图5-2所示。每个分组的目的地址都是以太网广播地 址。在who- 后面的以太网地址是目的硬件地址,跟在tell后面的以太网地址是发送端的硬件地址。 请注意重发的频度。第一次重发是在 6 . 5 5秒以后,然后增加到 4 2 . 8 0秒,然后又减到 5 . 3 4 秒和6 . 5 5秒,然后又回到 4 2 . 7 9秒。这种不确定的情况一直继续下去。如果计算一下两次重发 之间的时间间隔,我们发现存在一种双倍的关系:从 5 . 3 4到6 . 5 5是1 . 2 1秒,从 6 . 5 5到8 . 9 7是 2 . 4 2秒,从 8 . 9 7到1 3 . 8 0是4 . 8 3秒,一直这样继续下去。当时间间隔达到某个阈值时(大于 42.80秒),它又重新置为 5.34秒。

图5-2 网络中没有RARP服务器的RARP请求

超时间隔采用这样的递增方法比每次都采用相同值的方法要好。在图 6 - 8中,我们将看到 一种错误的超时重发方法,以及在第 21章中将看到 TCP的超时重发机制。

5.4 RARP服务器的设计 虽然R A R P在概念上很简单,但是一个 R A R P服务器的设计与系统相关而且比较复杂。相 反,提供一个 A R P服务器很简单,通常是 T C P / I P在内核中实现的一部分。由于内核知道 I P地

下载

49

第5章 RARP:逆地址解析协议使用

址和硬件地址,因此当它收到一个询问 I P地址的 A R P请求时,只需用相应的硬件地址来提供 应答就可以了。 5.4.1 作为用户进程的RARP服务器 RARP服务器的复杂性在于,服务器一般要为多个主机(网络上所有的无盘系统)提供硬 件地址到 I P地址的映射。该映射包含在一个磁盘文件中(在 U n i x系统中一般位于 / e t c / e t h e r s目 录中)。由于内核一般不读取和分析磁盘文件,因此 RARP服务器的功能就由用户进程来提供, 而不是作为内核的 TCP/IP实现的一部分。 更为复杂的是, RARP请求是作为一个特殊类型的以太网数据帧来传送的(帧类型字段值 为0x8035,如图2-1所示)。这说明RARP服务器必须能够发送和接收这种类型的以太网数据帧。 在附录 A中,我们描述了 B S D分组过滤器、 S u n的网络接口栓以及 S V R 4数据链路提供者接口 都可用来接收这些数据帧。由于发送和接收这些数据帧与系统有关,因此 RARP服务器的实现 是与系统捆绑在一起的。 5.4.2 每个网络有多个RARP服务器 R A R P服务器实现的一个复杂因素是 R A R P请求是在硬件层上进行广播的,如图 5 - 2所示。 这意味着它们不经过路由器进行转发。为了让无盘系统在 RARP服务器关机的状态下也能引导, 通常在一个网络上(例如一根电缆)要提供多个 RARP服务器。 当服务器的数目增加时(以提供冗余备份),网络流量也随之增加,因为每个服务器对每 个RARP请求都要发送 RARP应答。发送 RARP请求的无盘系统一般采用最先收到的 RARP应答 (对于 A R P,我们从来没有遇到这种情况,因为只有一台主机发送 A R P应答)。另外,还有一 种可能发生的情况是每个 RARP服务器同时应答,这样会增加以太网发生冲突的概率。

5.5 小结 R A R P协议是许多无盘系统在引导时用来获取 I P地址的。 R A R P分组格式基本上与 A R P分 组一致。一个 RARP请求在网络上进行广播,它在分组中标明发送端的硬件地址,以请求相应 IP地址的响应。应答通常是单播传送的。 R A R P带来的问题包括使用链路层广播,这样就阻止大多数路由器转发 R A R P请求,只返 回很少信息:只是系统的 I P地址。在第 1 6章中,我们将看到 B O O T P在无盘系统引导时会返回 更多的信息:IP地址和引导主机的名字等。 虽然R A R P在概念上很简单,但是 R A R P服务器的实现却与系统相关。因此,并不是所有 的TCP/IP实现都提供 RARP服务器。

习题 5.1 RARP需要不同的帧类型字段吗? ARP和RARP都使用相同的值 0x0806吗? 5.2 在一个有多个RARP服务器的网络上,如何防止它们的响应发生冲突?

下载

第6章 ICMP:Internet控制报文协议 6.1 引言 I C M P经常被认为是 I P层的一个组成部分。它传递差错报文以及其他需要注意的信息。 I C M P报文通常被 I P层或更高层协议( T C P或U D P)使用。一些 I C M P报文把差错报文返回给 用户进程。 IP数据报

I C M P报文是在 I P数据报内部被传输的,如 图6-1所示。

IP首部

ICMP 的正式规范参见 RFC 792 [Posterl

ICMP报文

20字节

1981b]。 ICMP报文的格式如图 6-2所示。所有报文的

图6-1 ICMP封装在IP数据报内部

前4个字节都是一样的,但是剩下的其他字节则互不相同。下面我们将逐个介绍各种报文格式。 类型字段可以有 1 5个不同的值,以描述特定类型的 I C M P报文。某些 I C M P报文还使用代 码字段的值来进一步描述不同的条件。 检验和字段覆盖整个 ICMP报文。使用的算法与我们在 3.2节中介绍的IP首部检验和算法相 同。ICMP的检验和是必需的。 8位类型

8位代码

16位检验和

(不同类型和代码有不同的内容)

图6-2 ICMP报文

在本章中,我们将一般地讨论 I C M P报文,并对其中一部分作详细介绍:地址掩码请求和 应答、时间戳请求和应答以及不可达端口。我们将详细介绍第 2 7章P i n g程序所使用的回应请 求和应答报文和第 9章处理IP路由的ICMP报文。

6.2 ICMP报文的类型 各种类型的ICMP报文如图6-3所示,不同类型由报文中的类型字段和代码字段来共同决定。 图中的最后两列表明 I C M P报文是一份查询报文还是一份差错报文。因为对 I C M P差错报 文有时需要作特殊处理,因此我们需要对它们进行区分。例如,在对 I C M P差错报文进行响应 时,永远不会生成另一份 I C M P差错报文(如果没有这个限制规则,可能会遇到一个差错产生 另一个差错的情况,而差错再产生差错,这样会无休止地循环下去)。 当发送一份 ICMP差错报文时,报文始终包含 IP的首部和产生ICMP差错报文的 IP数据报的 前8个字节。这样,接收 I C M P差错报文的模块就会把它与某个特定的协议(根据 I P数据报首

51

第6章 ICMP:Internet控制报文协议使用

下载 类

型 0



码 0

3

4 5

8 9 10 11

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 0 1 2 3 0 0 0 0 1

12

13 14 15 16 17 18

0 1 0 0 0 0 0 0

描 述 回显应答( Ping应答,第7章) 目的不可达: 网络不可达( 9.3节) 主机不可达( 9.3节) 协议不可达 端口不可达( 6.5节) 需要进行分片但设置了不分片比特( 11.6节) 源站选路失败( 8.5节) 目的网络不认识 目的主机不认识 源主机被隔离(作废不用) 目的网络被强制禁止 目的主机被强制禁止 由于服务类型 TOS,网络不可达( 9.3节) 由于服务类型 TOS,主机不可达( 9.3节) 由于过滤,通信被强制禁止 主机越权 优先权中止生效 源端被关闭(基本流控制, 11.11节) 重定向(9.5节): 对网络重定向 对主机重定向 对服务类型和网络重定向 对服务类型和主机重定向 请求回显( Ping请求,第7章) 路由器通告( 9.6节) 路由器请求( 9.6节) 超时: 传输期间生存时间为 0(Traceroute, 第8章) 在数据报组装期间生存时间为 0(11.5节) 参数问题: 坏的IP首部(包括各种差错) 缺少必需的选项 时间戳请求( 6.4节) 时间戳应答( 6.4节) 信息请求(作废不用) 信息应答(作废不用) 地址掩码请求( 6.3节) 地址掩码应答( 6.3节)









• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • •

图6-3 ICMP报文类型

部中的协议字段来判断)和用户进程(根据包含在 I P数据报前 8个字节中的 T C P或U D P报文首 部中的TCP或UDP端口号来判断)联系起来。 6.5节将举例来说明一点。 下面各种情况都不会导致产生 ICMP差错报文: 1) ICMP差错报文(但是, ICMP查询报文可能会产生 ICMP差错报文)。 2) 目的地址是广播地址(见图 3-9)或多播地址( D类地址,见图1-5)的IP数据报。 3) 作为链路层广播的数据报。 4) 不是IP分片的第一片(将在 11.5节介绍分片)。 5) 源地址不是单个主机的数据报。这就是说,源地址不能为零地址、环回地址、广播地 址或多播地址。 这些规则是为了防止过去允许 ICMP差错报文对广播分组响应所带来的广播风暴。

52

使用TCP/IP详解,卷1:协议

下载

6.3 ICMP地址掩码请求与应答 I C M P地址掩码请求用于无盘系统在引导过程中获取自己的子网掩码( 3 . 5节)。系统广播 它的ICMP请求报文(这一过程与无盘系统在引导过程中用 RARP获取IP地址是类似的)。无盘 系统获取子网掩码的另一个方法是 B O O T P协议,我们将在第 1 6章中介绍。 I C M P地址掩码请 求和应答报文的格式如图 6-4所示。 类型(17或18)

代码(0)

检验和

标识符

序列号

12字节

32位子网掩码

图6-4 ICMP地址掩码请求和应答报文

I C M P报文中的标识符和序列号字段由发送端任意选择设定,这些值在应答中将被返回。 这样,发送端就可以把应答与请求进行匹配。 我们可以写一个简单的程序(取名为 i c m p a d d r m a s k),它发送一份 I C M P地址掩码请求报 文,然后打印出所有的应答。由于一般是把请求报文发往广播地址,因此这里我们也这样做。 目的地址( 140.252.13.63)是子网140.252.13.32的广播地址(见图 3-12)。 sun % icmpaddrmask 140.252.13.63 r e c e i v e d m a s k = f f f f f f e 0 , f r o m 1 4 0 . 2 5 2 . 1 3 .来自本机 33 r e c e i v e d m a s k = f f f f f f e 0 , f r o m 1 4 0 . 2 5 2 . 1 3 .来自b 3 5 sdi r e c e i v e d m a s k = f f f f 0 0 0 0 , f r o m 1 4 0 . 2 5 2 . 1 3 .来自s 3 4 vr4

在输出中我们首先注意到的是,从 s v r 4返回的子网掩码是错的。显然,尽管 s v r 4接口 已经设置了正确的子网掩码,但是 S V R 4还是返回了一个普通的 B类地址掩码,就好像子网并 不存在一样。 svr4 % ifconfig emd0 emd0: flags=23 inet 140.252.13.34 netmask ffffffe0 broadcast 140.252.13.63

SVR4处理ICMP地址掩码请求过程存在差错。 我们用t c p d u m p命令来查看主机 b s d i上的情况,输出如图 6 - 5所示。我们用- e选项来查看 硬件地址。

图6-5 发到广播地址的ICMP地址掩码请求

注意,尽管在线路上什么也看不见,但是发送主机 s u n也能接收到 I C M P应答(带有上面 “来自本机”的输出行)。这是广播的一般特性:发送主机也能通过某种内部环回机制收到一 份广播报文拷贝。由于术语“广播”的定义是指局域网上的所有主机,因此它必须包括发送

53

第6章 ICMP:Internet控制报文协议使用

下载

主机在内(参见图 2 - 4,当以太网驱动程序识别出目的地址是广播地址后,它就把分组送到网 络上,同时传一份拷贝到环回接口)。 接下来, b s d i广播应答,而 s v r 4却只把应答传给请求主机。通常,应答地址必须是单 播地址,除非请求端的源 I P地址是 0 . 0 . 0 . 0。本例不属于这种情况,因此,把应答发送到广播 地址是BSD/386的一个内部差错。 RFC规定,除非系统是地址掩码的授权代理,否则它不能发送地址掩码应答(为 了成为授权代理,它必须进行特殊配置,以发送这些应答。参见附录 E)。但是,正如 我们从本例中看到的那样,大多数主机在收到请求时都发送一个应答,甚至有一些主 机还发送差错的应答。 最后一点可以通过下面的例子来说明。我们向本机 I P地址和环回地址分别发送地址掩码 请求: sun % i c m p a d d r m a s k s u n received mask= ff000000, from 140.252.13.33 sun % i c m p a d d r m a s k l o c a l h o s t received mask= ff000000, from 127.0.0.1

上述两种情况下返回的地址掩码对应的都是环回地址,即 A类地址1 2 7 . 0 . 0 . 1。还有,我们 从图 2 - 4可以看到,发送给本机 I P地址的数据报( 1 4 0 . 2 5 2 . 1 2 . 3 3)实际上是送到环回接口。 I C M P地址掩码应答必须是收到请求接口的子网掩码(这是因为多接口主机每个接口有不同的 子网掩码),因此两种情况下地址掩码请求都来自于环回接口。

6.4 ICMP时间戳请求与应答 I C M P时间戳请求允许系统向另一个系统查询当前的时间。返回的建议值是自午夜开始计 算的毫秒数,协调的统一时间( Coordinated Universal Time, UTC )(早期的参考手册认为 U T C是格林尼治时间)。这种 I C M P报文的好处是它提供了毫秒级的分辨率,而利用其他方法 从别的主机获取的时间(如某些 U n i x系统提供的 r d a t e命令)只能提供秒级的分辨率。由于 返回的时间是从午夜开始计算的,因此调用者必须通过其他方法获知当时的日期,这是它的 一个缺陷。 ICMP时间戳请求和应答报文格式如图 6-6所示。

类型(13或14)

代码(0)

检验和

标识符

序列号 发起时间戳 接收时间戳

传送时间戳

图6-6 ICMP时间戳请求和应答报文

20字节

54

使用TCP/IP详解,卷1:协议

下载

请求端填写发起时间戳,然后发送报文。应答系统收到请求报文时填写接收时间戳,在 发送应答时填写发送时间戳。但是,实际上,大多数的实现把后面两个字段都设成相同的值 (提供三个字段的原因是可以让发送方分别计算发送请求的时间和发送应答的时间)。 6.4.1 举例 我们可以写一个简单程序(取名为 i c m p t i m e),给某个主机发送 I C M P时间戳请求,并 打印出返回的应答。它在我们的小互联网上运行结果如下:

程序打印出 I C M P报文中的三个时间戳:发起时间戳( o r i g)、接收时间戳( r e c v)以 及发送时间戳( x m i t)。正如我们在这个例子以及下面的例子中所看到的那样,所有的主机 把接收时间戳和发送时间戳都设成相同的值。 我们还能计算出往返时间(rtt),它的值是收到应答时的时间值减去发送请求时的时间值。 difference的值是接收时间戳值减去发起时间戳值。这些值之间的关系如图 6-7所示。 发起

请求

接收

传送

应答

图6-7 icmptime 程序输出的值之间的关系

如果我们相信RTT的值,并且相信 RTT的一半用于请求报文的传输,另一半用于应答报文 的传输,那么为了使本机时钟与查询主机的时钟一致,本机时钟需要进行调整,调整值是 difference减去RTT的一半。在前面的例子中, bsdi的时钟比sun的时钟要慢 7 ms 和8 ms 。 由于时间戳的值是自午夜开始计算的毫秒数,即 UTC,因此它们的值始终小于 86 400 000 ( 2 4×6 0×6 0×1 0 0 0 )。这些例子都是在下午 4 : 0 0以前运行的,并且在一个比 U T C慢7个小时的 时区,因此它们的值比 82 800 000(2300小时)要大是有道理的。 如果对主机 b s d i重复运行该程序数次,我们发现接收时间戳和发送时间戳的最后一位数 总是0。这是因为该版本的软件( 0.9.4版)只能提供10ms的时间分辨率(说明参见附录 B)。 如果对主机 svr4运行该程序两次,我们发现 SVR4时间戳的最后三位数始终为 0:

由于某种原因, S V R 4在I C M P时间戳中不提供毫秒级的分辨率。这样,对秒以下的时间 差调整将不起任何作用。 如果我们对子网 1 4 0 . 2 5 2 . 1上的其他主机运行该程序,结果表明其中一台主机的时钟与

下载

55

第6章 ICMP:Internet控制报文协议使用

sun相差3.7秒,而另一个主机时钟相差近 75秒:

另一个令人感兴趣的例子是路由器 g a t e w a y(一个 C i s c o路由器)。它表明,当系统返回 一个非标准时间戳值时(不是自午夜开始计算的毫秒数, U T C),它就用 32 bit 时间戳中的高 位来表示。我们的程序证明了一点,在尖括号中打印出了接收和发送的时间戳值(在关闭高 位之后)。另外,不能计算发起时间戳和接收时间戳之间的时间差,因为它们的单位不一致。

如果我们在这台主机上运行该程序数次,会发现时间戳值显然具有毫秒级的分辨率,而 且是从某个起始点开始计算的毫秒数,但是起始点并不是午夜 U T C(例如,可能是从路由器 引导时开始计数的毫秒数)。 作为最后一个例子,我们来比较 s u n主机和另一个已知是准确的系统时钟 — 一个N T P stratum 1服务器(下面我们会更多地讨论 NTP,网络时间协议)。

如果我们把difference的值减去RTT的一半,结果表明sun主机上的时钟要快38.5~51.5 ms。 6.4.2 另一种方法 还可以用另一种方法来获得时间和日期。 1) 在1 . 1 2节中描述了日期服务程序和时间服务程序。前者是以人们可读的格式返回当前 的时间和日期,是一行 ASCII字符。可以用telnet命令来验证这个服务:

前三行是Telnet客户的输出 这是日期时间服务器的输出 这也是Telnet客户的输出

另一方面,时间服务程序返回的是一个 3 2 b i t的二制进数值,表示自 U T C,1 9 0 0年1月1日 午夜起算的秒数。这个程序是以秒为单位提供的日期和时间(前面我们提过的 r d a t e命令 使用的是TCP时间服务程序)。 2) 严格的计时器使用网络时间协议( N T P),该协议在 RFC 1305 中给出了描述 [ M i l l s 1 9 9 2 ]。这个协议采用先进的技术来保证 L A N或WA N上的一组系统的时钟误差在毫秒 级以内。对计算机精确时间感兴趣的读者应该阅读这份 RFC文档。 3) 开放软件基金会( O S F)的分布式计算环境( D C E)定义了分布式时间服务( D T S),

56

使用TCP/IP详解,卷1:协议

下载

它也提供计算机之间的时钟同步。文献 [Rosenberg, Kenney and Fisher 1992]提供了该服 务的其他细节描述。 4) 伯克利大学的 U n i x系统提供守护程序 t i m e d( 8 ),来同步局域网上的系统时钟。不像 NTP和DTS,timed不在广域网范围内工作。

6.5 ICMP端口不可达差错 最后两小节我们来讨论 I C M P查询报文 — 地址掩码和时间戳查询及应答。现在来分析一 种I C M P差错报文,即端口不可达报文,它是 I C M P目的不可到达报文中的一种,以此来看一 看ICMP差错报文中所附加的信息。使用 UDP(见第11章)来查看它。 U D P的规则之一是,如果收到一份 U D P数据报而目的端口与某个正在使用的进程不相符, 那么U D P返回一个 I C M P不可达报文。可以用 T F T P来强制生成一个端口不可达报文( T F T P将 在第15章描述)。 对于 T F T P服务器来说, U D P的公共端口号是 6 9。但是大多数的 T F T P客户程序允许用 connect命令来指定一个不同的端口号。这里,我们就用它来指定 8888端口: 指定主机名和端口号 试图得到一个文件 大约25秒后

c o n n e c t命令首先指定要连接的主机名及其端口号,接着用 g e t命令来取文件。敲入 g e t 命令后,一份 U D P数据报就发送到主机 s v r 4上的8 8 8 8端口。t c p d u m p命令引起的报文交换结果 如图6-8所示。

图6-8 由TFTP产生的ICMP端口不可达差错

在UDP数据报送到 svr4之前,要先发送一份 ARP请求来确定它的硬件地址(第 1行)。接着 返回A R P应答(第 2行),然后才发送 U D P数据报(第 3行)(在t c p d u m p的输出中保留 A R P请 求和应答是为了提醒我们,这些报文交换可能在第一个 I P数据报从一个主机发送到另一个主 机之前是必需的。在本书以后的章节中,如果这些报文与讨论的题目不相关,那么我们将省 略它们)。 一个I C M P端口不可达差错是立刻返回的(第 4行)。但是, T F T P客户程序看上去似乎忽 略了这个 I C M P报文,而在 5秒钟之后又发送了另一份 U D P数据报(第 5行)。在客户程序放弃

57

第6章 ICMP:Internet控制报文协议使用

下载 之前重发了三次。

注意,I C M P报文是在主机之间交换的,而不用目的端口号,而每个 2 0字节的U D P数据报 则是从一个特定端口( 2924)发送到另一个特定端口( 8888)。 跟在每个 U D P后面的数字 2 0指的是 U D P数据报中的数据长度。在这个例子中, 2 0字节包 括T F T P的2个字节的操作代码, 9个字节以空字符结束的文件名 t e m p . f o o,以及 9个字节以 空字符结束的字符串 netascii(TFTP报文的详细格式参见图 15-1)。 如果用- e选项运行同样的例子,我们可以看到每个返回的 I C M P端口不可达报文的完整长 度。这里的长度为 70字节,各字段分配如图 6-9所示。 IP数据报 ICMP报文 ICMP报文的数据部分

以太网首部

IP首部

ICMP首部

产生差错的数据报IP首部

UDP首部

14字节

20字节

8字节

20字节

8字节

图6-9 “UDP端口不可达”例子中返回的ICMP报文

I C M P的一个规则是, I C M P差错报文(参见图 6 - 3的最后一列)必须包括生成该差错报文 的数据报 I P首部(包含任何选项),还必须至少包括跟在该 I P首部后面的前 8个字节。在我们 的例子中,跟在 IP首部后面的前8个字节包含 UDP的首部(见图11-2)。 一个重要的事实是包含在 U D P首部中的内容是源端口号和目的端口号。就是由于目的端 口号( 8 8 8 8)才导致产生了 I C M P端口不可达的差错报文。接收 I C M P的系统可以根据源端口 号(2924)来把差错报文与某个特定的用户进程相关联(在本例中是 TFTP客户程序)。 导致差错的数据报中的 I P 首部要被送回的原因是因为 I P首部中包含了协议字段,使得 I C M P可以知道如何解释后面的 8个字节(在本例中是 U D P首部)。如果我们来查看 T C P首部 (图17-2),可以发现源端口和目的端口被包含在 TCP首部的前8个字节中。 ICMP不可达报文的一般格式如图 6-10所示。

类型(3)

代码(0~15)

检验和 8字节

未用(必须为0)

IP首部(包括选项)+原始IP数据报中数据的前8字节

图6-10 ICMP不可达报文

在图6-3中,我们注意到有16种不同类型的ICMP不可达报文,代码分别从0到15。ICMP端口 不可达差错代码是3。另外,尽管图6-10指出了在ICMP报文中的第二个32 bit字必须为0,但是当 代码为4时(“需要分片但设置了不分片比特”),路径MTU发现机制(2.9节)却允许路由器把外

58

使用TCP/IP详解,卷1:协议

下载

出接口的MTU填在这个32 bit字的低16 bit中。我们在11.6节中给出了一个这种差错的例子。 尽管ICMP规则允许系统返回多于8个字节的产生差错的IP数据报中的数据,但是大多 数从伯克利派生出来的系统只返回

8个字节。 Solaris 2.2 的 i p _ i c m p _ r e t u r n _

data_bytes选项默认条件下返回前64个字节(E.4节) 。 tcpdump时间系列 在本书的后面章节中,我们还要以时间系列的格式给出tcpdump命令的输出,如图6-11所示。

图6-11 发送到无效端口的TFTP请求的时间系列

时间随着向下而递增,在图左边的时间标记与 tcpdump命令的输出是相同的(见图 6 - 8)。 位于图顶部的标记是通信双方的主机名和端口号。需要指出的是,随着页面向下的 y坐标轴与真 正的时间值不是成比例的。当出现一个有意义的时间段时,在本例中是每5秒之间的重发,我 们就在时间系列的两侧作上标记。当UDP或TCP数据正在被传送时,我们用粗线的行来表示。 当I C M P报文返回时,为什么 T F T P客户程序还要继续重发请求呢?这是由于网络编程中 的一个因素,即 B S D系统不把从插口 ( s o c k e t )接收到的 I C M P报文中的 U D P数据通知用户进程, 除非该进程已经发送了一个 c o n n e c t 命令给该插口。标准的 BSD TFTP 客户程序并不发送 connect命令,因此它永远也不会收到 ICMP差错报文的通知。 这里需要注意的另一点是 T F T P客户程序所采用的不太好的超时重传算法。它只是假定 5 秒是足够的,因此每隔 5秒就重传一次,总共需要 2 5秒钟的时间。在后面我们将看到 T C P有一 个较好的超时重发算法。

59

第6章 ICMP:Internet控制报文协议使用

下载

TFTP客户程序所采用的超时重传算法已被 RFC所禁用。不过,在作者所在子网上 的三个系统以及Solaris 2.2仍然在使用它。AIX 3.2.2采用一种指数退避方法来设置超时 值,分别在0、5、15和35秒时重发报文,这正是所推荐的方法。我们将在第 21章更详 细地讨论超时问题。 最后需要指出的是, ICMP报文是在发送UDP数据报3.5 ms后返回的,这与第 7章我们所看 到的Ping应答的往返时间差不多。

6.6 ICMP报文的4.4BSD处理 由于ICMP覆盖的范围很广,从致命差错到信息差错,因此即使在一个给定的系统实现中, 对每个 I C M P报文的处理都是不相同的。图 6 - 1 2的内容与图 6 - 3相同,它显示的是 4 . 4 B S D系统 对每个可能的ICMP报文的处理方法。 类

型 0 3

4 5

8 9 10 11

代 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 0 1 2 3 0 0 0 0 1

12

13 14 15 16 17 18

0 1 0 0 0 0 0 0



描 述 回显应答 目的不可达: 网络不可达 主机不可达 协议不可达 端口不可达 需要进行分片但设置了不分片比特 DF 源站选路失败 目的网络不认识 目的主机不认识 源主机被隔离(作废不用) 目的网络被强制禁止 目的主机被强制禁止 由于服务类型 TOS,网络不可达 由于服务类型 TOS,主机不可达 由于过滤,通信被强制禁止 主机越权 优先权中止生效 源站被抑制 (quench) 重定向 对网络重定向 对主机重定向 对服务类型和网络重定向 对服务类型和主机重定向 回显请求 路由器通告 路由器请求 超时: 传输期间生存时间为 0 在数据报组装期间生存时间为 0 参数问题: 坏的IP首部(包括各种差错) 缺少必需的选项 时间戳请求 时间戳应答 信息请求(作废不用) 信息应答(作废不用) 地址掩码请求 地址掩码应答

图6-12 4.4BSD系统对ICMP报文的处理

处理方法 用户进程 “无路由到达主机” “无路由到达主机” “连接被拒绝” “连接被拒绝” “报文太长” “无路由到达主机” “无路由到达主机” “无路由到达主机” “无路由到达主机” “无路由到达主机” “无路由到达主机” “无路由到达主机” “无路由到达主机” (忽略) (忽略) (忽略) TCP由内核处理, UDP则忽略 内核更新路由表 内核更新路由表 内核更新路由表 内核更新路由表 用户进程 用户进程 用户进程 用户进程 “协议不可用” “协议不可用” 内核产生应答 用户进程 (忽略) 用户进程 内核产生应答 用户进程

60

使用TCP/IP详解,卷1:协议

下载

如果最后一列标明是“内核”,那么 I C M P就由内核来处理。如果最后一列指明是“用户 进程”,那么报文就被传送到所有在内核中登记的用户进程,以读取收到的 I C M P报文。如果 不存在任何这样的用户进程,那么报文就悄悄地被丢弃(这些用户进程还会收到所有其他类 型的I C M P报文的拷贝,虽然它们应该由内核来处理,当然用户进程只有在内核处理以后才能 收到这些报文)。有一些报文完全被忽略。最后,如果最后一列标明的是引号内的一串字符, 那么它就是对应的 U n i x差错。其中一些差错,如 T C P对发送端关闭的处理等,我们将在以后 的章节中对它们进行讨论。

6.7 小结 本章对每个系统都必须包括的 I n t e r n e t控制报文协议进行了讨论。图 6 - 3列出了所有的 ICMP报文类型,其中大多数都将在以后的章节中加以讨论。 我们详细讨论了 I C M P地址掩码请求和应答以及时间戳请求和应答。这些是典型的请求— 应答报文。二者在 I C M P报文中都有标识符和序列号。发送端应用程序在标识字段内存入一个 唯一的数值,以区别于其他进程的应答。序列号字段使得客户程序可以在应答和请求之间进 行匹配。 我们还讨论了 I C M P端口不可达差错,一种常见的 I C M P差错。对返回的 I C M P差错信息进 行了分析:导致差错的 I P数据报的首部及后续 8个字节。这个信息对于 I C M P差错的接收方来 说是必要的,可以更多地了解导致差错的原因。这是因为 T C P和U D P都在它们的首部前 8个字 节中存入源端口号和目的端口号。 最后,我们第一次给出了按时间先后的 tcpdump输出,这种表示方式在本书后面的章节 中会经常用到。

习题 6.1 在6 . 2节的末尾,我们列出了 5种不发送 I C M P差错报文的特殊条件。如果这些条件不满足 而我们又在局域网上向一个似乎不存在的端口号发送一份广播 U D P数据报,这时会发生 什么样的情况? 6.2 阅读RFC [Braden 1989a],注意生成一个 I C M P端口不可达差错是否为“必须”,“应该” 或者“可能”。这些信息所在的页码和章节是多少? 6.3 阅读RFC 1349 [Almquist 1992],看看IP的服务类型字段(见图 3-2)是如何被ICMP设置 的? 6.4 如果你的系统提供 netstat命令,请用它来查看接收和发送的 ICMP报文类型。

下载

第7章 Ping程序 7.1 引言 “p i n g”这个名字源于声纳定位操作。 P i n g程序由Mike Muuss编写,目的是为了测试另一 台主机是否可达。该程序发送一份 I C M P回显请求报文给主机,并等待返回 I C M P回显应答 (图6-3列出了所有的ICMP报文类型)。 一般来说,如果不能 P i n g到某台主机,那么就不能 Te l n e t或者F T P到那台主机。反过来, 如果不能 Te l n e t到某台主机,那么通常可以用 P i n g程序来确定问题出在哪里。 P i n g程序还能测 出到这台主机的往返时间,以表明该主机离我们有“多远”。 在本章中,我们将使用 P i n g程序作为诊断工具来深入剖析 I C M P。P i n g还给我们提供了检 测IP记录路由和时间戳选项的机会。文献 [Stevens 1990]的第11章提供了Ping程序的源代码。 几年前我们还可以作出这样没有限定的断言,如果不能 Ping到某台主机,那么就 不能Telnet或FTP到那台主机。随着Internet安全意识的增强,出现了提供访问控制清单 的路由器和防火墙,那么像这样没有限定的断言就不再成立了。一台主机的可达性可 能不只取决于IP层是否可达,还取决于使用何种协议以及端口号。Ping程序的运行结果 可能显示某台主机不可达,但我们可以用Telnet远程登录到该台主机的25号端口(邮件 服务器)。

7.2 Ping程序 我们称发送回显请求的 p i n g程序为客户,而称被 p i n g的主机为服务器。大多数的 T C P / I P 实现都在内核中直接支持 P i n g服务器 — 这种服务器不是一个用户进程(在第 6章中描述的两 种ICMP查询服务,地址掩码和时间戳请求,也都是直接在内核中进行处理的)。 ICMP回显请求和回显应答报文如图 7-1所示。

类型(0或8)

检验和

代码(0)

8字节 序号

标识符

选项数据

图7-1 ICMP回显请求和回显应答报文格式

对于其他类型的 I C M P查询报文,服务器必须响应标识符和序列号字段。另外,客户发送 的选项数据必须回显,假设客户对这些信息都会感兴趣。

62

使用TCP/IP详解,卷1:协议

下载

U n i x系统在实现 p i n g程序时是把 I C M P报文中的标识符字段置成发送进程的 I D号。这样 即使在同一台主机上同时运行了多个 ping程序实例, ping程序也可以识别出返回的信息。 序列号从 0开始,每发送一次新的回显请求就加 1。p i n g程序打印出返回的每个分组的序 列号,允许我们查看是否有分组丢失、失序或重复。 I P是一种最好的数据报传递服务,因此 这三个条件都有可能发生。 旧版本的 p i n g程序曾经以这种模式运行,即每秒发送一个回显请求,并打印出返回的每 个回显应答。但是,新版本的实现需要加上- s选项才能以这种模式运行。默认情况下,新版 本的p i n g程序只发送一个回显请求。如果收到回显应答,则输出“ host is alive”;否则,在 20秒内没有收到应答就输出“ no answer(没有回答)”。 7.2.1 LAN输出 在局域网上运行 ping程序的结果输出一般有如下格式:

键入中断键来停止显示

当返回 I C M P回显应答时,要打印出序列号和 T T L,并计算往返时间( T T L位于I P首部中 的生存时间字段。当前的 B S D系统中的 p i n g程序每次收到回显应答时都打印出收到的 T T L — 有些系统并不这样做。我们将在第 8章中通过traceroute程序来介绍 TTL的用法)。

从上面的输出中可以看出,回显应答是以发送的次序返回的( 0,1,2等)。 p i n g程序通过在 I C M P报文数据中存放发送请求的时间值来计算往返时间。当应答返回 时,用当前时间减去存放在 I C M P报文中的时间值,即是往返时间。注意,在发送端 b s d i上, 往返时间的计算结果都为 0 ms 。这是因为程序使用的计时器分辨率低的原因。 B S D / 3 8 6版本 0.9.4系统只能提供10 ms级的计时器(在附录 B中有更详细的介绍)。在后面的章节中,当我们 在具有较高分辨率计时器的系统上( S u n)查看t c p d u m p输出时会发现, I C M P回显请求和回 显应答的时间差在 4 ms 以下。 输出的第一行包括目的主机的 I P地址,尽管指定的是它的名字( s v r 4)。这说明名字已 经经过解析器被转换成 I P地址了。我们将在第 1 4章介绍解析器和 D N S。现在,我们发现,如 果敲入 p i n g命令,几秒钟过后会在第 1行打印出 I P地址, D N S就是利用这段时间来确定主机 名所对应的 IP地址。 本例中的tcpdump输出如图7-2所示。 从发送回显请求到收到回显应答,时间间隔始终为 3.7 ms。还可以看到,回显请求大约每 隔1秒钟发送一次。 通常,第 1个往返时间值要比其他的大。这是由于目的端的硬件地址不在 A R P高速缓存中

63

第7章 Ping程序使用

下载

图7-2 在LAN上运行ping程序的结果

的缘故。正如我们在第 4章中看到的那样,在发送第一个回显请求之前要发送一个 A R P请求并 接收ARP应答,这需要花费几毫秒的时间。下面的例子说明了这一点: 保证ARP高速缓存是空的

键入中断键来停止显示

第1个RTT中多出的3 ms很可能就是因为发送 ARP请求和接收 ARP应答所花费的时间。 这个例子运行在 s u n主机上,它提供的是具有微秒级分辨率的计时器,但是 p i n g程序只 能打印出毫秒级的往返时间。在前面运行于 BSD/386 0.9.4版上的例子中,打印出来的往返时 间值为0 ms,这是因为计时器只能提供 10 ms的误差。下面的例子是 BSD/386 1.0版的输出,它 提供的计时器也具有微秒级的分辨率,因此, ping程序的输出结果也具有较高的分辨率。

键入中断键来停止显示

7.2.2 WAN输出 在一个广域网上,结果会有很大的不同。下面的例子是在某个工作日的下午即 I n t e r n e t具

64

使用TCP/IP详解,卷1:协议

下载

有正常通信量时的运行结果:

键入中断来停止显示

这里,序列号为 1、2、3、4、6、10、11、12和13的回显请求或回显应答在某个地方丢失 了。另外,我们注意到往返时间发生了很大的变化(像 5 2 %这样高的分组丢失率是不正常的。 即使是在工作日的下午,对于 Internet来说也是不正常的)。 通过广域网还有可能看到重复的分组(即相同序列号的分组被打印两次或更多次),失序 的分组(序列号为 N + 1的分组在序列号为 N的分组之前被打印)。 7.2.3 线路SLIP链接 让我们再来看看 S L I P链路上的往返时间,因为它们经常运行于低速的异步方式,如 9 6 0 0 b / s或更低。回想我们在 2 . 1 0节计算的串行线路吞吐量。针对这个例子,我们把主机 b s d i和 slip之间的SLIP链路传输速率设置为 1200 b/s。 下面我们可以来估计往返时间。首先,从前面的 P i n g程序输出例子中可以注意到,默认 情况下发送的 I C M P报文有5 6个字节。再加上 2 0个字节的 I P首部和8个字节的 I C M P首部,I P数 据报的总长度为 8 4字节(我们可以运行 t c p d u m p-e命令查看以太网数据帧来验证这一点)。 另外,从2.4节可以知道,至少要增加两个额外的字节:在数据报的开始和结尾加上 END字符。 此外,SLIP帧还有可能再增加一些字节,但这取决于数据报中每个字节的值。对于 1200 b/s这 个速率来说,由于每个字节含有 8 bit 数据、 1 bit 起始位和 1 bit结束位,因此传输速率是每秒 1 2 0个字节,或者说每个字节 8.33 ms。所以我们可以估计需要 1 4 3 3(8 6×8 . 3 3×2)m s(乘2 是因为我们计算的是往返时间)。 下面的输出证实了我们的计算:

(对于 S V R 4来说,如果每秒钟发送一次请求则必须带- s选项)。往返时间大约是 1 . 5秒, 但是程序仍然每间隔 1 秒钟发送一次 I C M P 回显请求。这说明在第 1个回显应答返回之前 (1 . 4 8 0秒时刻)就已经发送了两次回显请求(分别在 0秒和1秒时刻)。这就是为什么总结行指

下载

65

第7章 Ping程序使用

出丢失了一个分组。实际上分组并未丢失,很可能仍然在返回的途中。 我们在第8章讨论traceroute程序时将回头再讨论这种低速的 SLIP链路。 7.2.4 拨号SLIP链路 对于拔号 S L I P链路来说,情况有些变化,因为在链路的两端增加了调制解调器。用在 s u n和n e t b系统之间的调制解调器提供的是 V. 3 2调制方式( 9600 b/s )、V. 4 2错误控制方式 (也称作 L A P - M)以及 V. 4 2 b i s数据压缩方式。这表明我们针对线路链路参数进行的简单计算 不再准确了。 很多因素都有可能影响。调制解调器带来了时延。随着数据的压缩,分组长度可能会减 小,但是由于使用了错误控制协议,分组长度又可能会增加。另外,接收端的调制解调器只 能在验证了循环检验字符(检验和)后才能释放收到的数据。最后,我们还要处理每一端的 计算机异步串行接口,许多操作系统只能在固定的时间间隔内,或者收到若干字符后才去读 这些接口。 作为一个例子,我们在 sun主机上ping主机gemini,输出结果如下:

注意,第 1个RT T不是10 ms的整数倍,但是其他行都是 10 ms的整数倍。如果我们运行该 程序若干次,发现每次结果都是这样(这并不是由 s u n主机上的时钟分辨率造成的结果,因 为根据附录 B中的测试结果可以知道它的时钟能提供毫秒级的分辨率)。 另外还要注意,第 1个RT T要比其他的大,而且依次递减,然后徘徊在 2 8 0~300 ms之间。 我们让它运行 1 ~ 2分钟, RT T一直处于这个范围,不会低于 260 ms。如果我们以 9600 b/s的速 率计算RTT(习题7.2),那么观察到的值应该大约是估计值的 1.5倍。 如果运行 p i n g程序6 0秒钟并计算观察到的 RT T的平均值,我们发现在 V. 4 2和V. 4 2 b i s模式 下平均值为 277 ms(这比上个例子打印出来的平均值要好,因为运行时间较长,这样就把开 始较长的时间平摊了)。如果我们关闭 V.42bis数据压缩方式,平均值为 330 ms。如果我们关闭 V.42错误控制方式(它同时也关闭了 V.42bis数据压缩方式),平均值为 300 ms。这些调制解调 器的参数对 RTT的影响很大,使用错误控制和数据压缩方式似乎效果最好。

7.3 IP记录路由选项 p i n g程序为我们提供了查看 I P记录路由( R R)选项的机会。大多数不同版本的 p i n g程

66

使用TCP/IP详解,卷1:协议

下载

序都提供-R选项,以提供记录路由的功能。它使得 p i n g程序在发送出去的 IP数据报中设置 IP R R选项(该 I P数据报包含 I C M P回显请求报文)。这样,每个处理该数据报的路由器都把它的 I P地址放入选项字段中。当数据报到达目的端时, I P地址清单应该复制到 I C M P回显应答中, 这样返回途中所经过的路由器地址也被加入清单中。当 p i n g程序收到回显应答时,它就打印 出这份IP地址清单。 这个过程听起来简单,但存在一些缺陷。源端主机生成 RR选项,中间路由器对 RR选项的 处理,以及把 I C M P回显请求中的 R R清单复制到 I C M P回显应答中,所有这些都是选项功能。 幸运的是,现在的大多数系统都支持这些选项功能,只是有一些系统不把 I C M P请求中的 I P清 单复制到ICMP应答中。 但是,最大的问题是 I P首部中只有有限的空间来存放 I P地址。我们从图 3 - 1可以看到, I P 首部中的首部长度字段只有 4 bit,因此整个 I P首部最长只能包括 1 5个32 bit长的字(即6 0个字 节)。由于 I P首部固定长度为 2 0字节, R R选项用去 3个字节(下面我们再讨论),这样只剩下 3 7 个字节( 6 0- 2 0 -3 )来存放 I P 地址清单,也就是说只能存放 9个I P 地址。对于早期的 ARPANET来说,9个I P地址似乎是很多了,但是现在看来是非常有限的(在第 8章中,我们将 用Tr a c e r o u t e工具来确定数据报的路由)。除了这些缺点,记录路由选项工作得很好,为详细 查看如何处理IP选项提供了一个机会。 IP数据报中的 RR选项的一般格式如图 7-3所示。 30字节

4字节

4字节

4字节

4字节

图7-3 IP首部中的记录路由选项的一般格式

c o d e是一个字节,指明 I P选项的类型。对于 R R选项来说,它的值为 7。l e n是R R选项总字 节长度,在这种情况下为 3 9(尽管可以为 R R选项设置比最大长度小的长度,但是 p i n g程序 总是提供 3 9字节的选项字段,最多可以记录 9个I P地址。由于 I P首部中留给选项的空间有限, 它一般情况都设置成最大长度)。 p t r称作指针字段。它是一个基于 1的指针,指向存放下一个 I P地址的位置。它的最小值为 4,指向存放第一个 I P地址的位置。随着每个 I P地址存入清单, p t r的值分别为 8,1 2,1 6,最 大到36。当记录下 9个IP地址后,ptr的值为40,表示清单已满。 当路由器(根据定义应该是多穴的)在清单中记录 I P地址时,它应该记录哪个地址呢? 是入口地址还是出口地址?为此, RFC 791 [Postel 1981a]指定路由器记录出口 I P地址。我们 在后面将看到,当原始主机(运行 p i n g程序的主机)收到带有 R R选项的 I C M P回显应答时, 它也要把它的入口 IP地址放入清单中。 7.3.1 通常的例子 我们举一个用 R R选项运行 p i n g程序的例子,在主机 s v r 4上运行p i n g程序到主机 s l i p。 一个中间路由器 (bsdi)将处理这个数据报。下面是 svr4的输出结果:

67

第7章 Ping程序使用

下载

分组所经过的四站如图 7 - 4所示(每个方向各有两站),每一站都把自己的 I P地址加入 R R 清单。

以太网 空表

图7-4 带有记录路由选项的ping程序

路由器bsdi在不同方向上分别加入了不同的 IP地址。它始终是把出口的 IP地址加入清单。 我们还可以看到,当 I C M P回显应答到达原始系统( s v r 4)时,它把自己的入口 I P地址也加入 清单中。 还可以通过运行带有- v选项的 t c p d u m p命令来查看主机 s u n上进行的分组交换(参见 I P 选项)。输出如图 7-5所示。

图7-5 记录路由选项的tcpdump 输出

输出中o p t l e n = 4 0表示在I P首部中有 4 0个字节的选项空间( I P首部长度必须为 4字节的整数 倍)。R R { 3 9 }的意思是记录路由选项已被设置,它的长度字段是 3 9。然后是 9个I P地址,符号 “#”用来标记 R R选项中的 p t r字段所指向的 I P地址。由于我们是在主机 s u n上观察这些分组 (参见图 7 - 4),因此所能看到 I C M P回显请求中的 I P地址清单是空的,而 I C M P回显应答中有 3 个IP地址。我们省略了 tcpdump输出中的其他行,因为它们与图 7-5基本一致。 位于路由信息末尾的标记 EOL表示IP选项“end of list(清单结束)”的值。EOL选项的值 可以为 0。这时表示 3 9个字节的 R R数据位于 I P首部中的 4 0字节空间中。由于在数据报发送之 前空间选项被设置为 0,因此跟在 3 9个字节的 R R数据之后的 0字符就被解释为 E O L。这正是我

68

使用TCP/IP详解,卷1:协议

下载

们所希望的结果。如果在 I P首部中的选项字段中有多个选项,在开始下一个选项之前必须填 入空白字符,另外还可以用另一个值为 1的特殊字符 NOP(“no operation”)。 在图7-5中,SVR4把回显请求中的TTL字段设为32,BSD/386设为255(它打印出 的值为254是因为路由器bsdi已经将其减去1)。新的系统都把ICMP报文中的TTL设为最 大值(255)。 在作者使用的三个 TCP/IP系统中,BSD/386和SVR4都支持记录路由选项。这就是 说,当转发数据报时,它们都能正确地更新RR清单,而且能正确地把接收到的ICMP回 显请求中的RR清单复制到出口ICMP回显应答中。虽然SunOS 4.1.3在转发一个数据报 时能正确更新RR清单,但是不能复制RR清单。Solaris 2.x对这个问题已作了修改。 7.3.2 异常的输出 下面的例子是作者观察到的,把它作为第

9 章讨论 I C M P 间接报文的起点。在子网

140.252.1上ping主机aix(在主机sun上通过拨号 SLIP连接可以访问),并带有记录路由选项。 在slip主机上运行有如下输出结果:

为什么用这个路由器?

我们已经在主机 b s d i上运行过这个例子。现在选择 s l i p来运行它,观察 R R清单中所有 的9个IP地址。 在输出中令人感到疑惑的是,为什么传出的数据报( I C M P回显请求)直接从 n e t b传到 a i x,而返回的数据报( I C M P回显应答)却从 a i x开始经路由器 g a t e w a y再到n e t b?这里 看到的正是下面将要描述的 IP选路的一个特点。数据报经过的路由如图 7-6所示。 问题是a i x不知道要把目的地为子网 140.252.13的IP数据报发到主机 n e t b上。相反,a i x 在它的路由表中有一个默认项,它指明当没有明确某个目的主机的路由时,就把所有的数据 报发往默认项指定的路由器 g a t e w a y。路由器g a t e w a y比子网140.252.1上的任何主机都具备 更强的选路能力(在这个以太网上有超过 1 5 0台主机,每台主机的路由表中都有一个默认项指 向路由器gateway,这样就不用在每台主机上都运行一个选路守护程序)。 这里没有应答的一个问题是为什么 gateway不直接发送 ICMP报文重定向到aix(9.5节), 以更新它的路由表?由于某种原因(很可能是由于数据报产生的重定向是一份 I C M P回显请求 报文),重定向并没有产生。但是如果我们用 Telnet登录到a i x上的daytime服务器, ICMP就会

69

第7章 Ping程序使用

下载

产生重定向,因而它在 a i x上的路由表也随之更新。如果接着执行 p i n g程序并带有记录路由 选项,其路由显示表明数据报从 n e t b到a i x,然后返回n e t b,而不再经过路由器 g a t e w a y。 在9.5节中将更详细地讨论 ICMP重定向的问题。 ping目的主机

以太网

ping客户端

以太网 空表

图7-6 运行带有记录路由选项的ping 程序,显示IP选路的特点

7.4 IP时间戳选项 I P时间戳选项与记录路由选项类似。 I P时间戳选项的格式如图 7 - 7所示(请与图 7 - 3进行比 较)。 40字节 时间戳

时间戳

时间戳

时间戳

4字节

4字节

4字节

4字节

图7-7 IP首部中时间戳选项的一般格式

时间戳选项的代码为 0 x 4 4。其他两个字段 l e n和p t r与记录路由选项相同:选项的总长度 (一般为36或40)和指向下一个可用空间的指针( 5,9,13等)。 接下来的两个字段是 4 bit的值:O F表示溢出字段, F L表示标志字段。时间戳选项的操作 根据标志字段来进行,如图 7-8所示。 标







0

只记录时间戳,正如我们在图 7-7看到的那样

1

每台路由器都记录它的 IP地址和时间戳。在选项列表中只有存放 4对地址和 时间戳的空间

3

发送端对选项列表进行初始化,存放了 4个I P地址和 4个取值为 0的时间戳 值。只有当列表中的下一个 I P地址与当前路由器地址相匹配时,才记录它的 时间戳

图7-8 时间戳选项不同标志字段值的意义

如果路由器由于没有空间而不能增加时间戳选项,那么它将增加溢出字段的值。

70

使用TCP/IP详解,卷1:协议

下载

时间戳的取值一般为自 U T C午夜开始计的毫秒数,与 I C M P时间戳请求和应答相类似。如 果路由器不使用这种格式,它就可以插入任何它使用的时间表示格式,但是必须打开时间戳 中的高位以表明为非标准值。 与我们遇到的记录路由选项所受到的限制相比,时间戳选项遇到情况要更坏一些。如果 我们要同时记录 I P地址和时间戳(标志位为 1),那么就可以同时存入其中的四对值。只记录 时间戳是没有用处的,因为我们没有标明时间戳与路由器之间的对应关系(除非有一个永远 不变的拓扑结构)。标志值取 3会更好一些,因为我们可以插入时间戳的路由器。一个更为基 本的问题是,很可能无法控制任何给定路由器上时间戳的正确性。这使得试图用 I P选项来计 算路由器之间的跳站数是徒劳的。我们将看到(第 8章)t r a c e r o u t e程序可以提供一种更好 的方法来计算路由器之间的跳站数。

7.5 小结 p i n g程序是对两个 T C P / I P系统连通性进行测试的基本工具。它只利用 I C M P回显请求和 回显应答报文,而不用经过传输层( TCP/UDP)。Ping服务器一般在内核中实现 ICMP的功能。 我们分析了在 L A N、WA N以及 S L I P链路(拨号和线路)上运行 p i n g程序的输出结果, 并对串行线路上的 S L I P链路吞吐量进行了计算。我们还讨论并使用了 p i n g程序的I P记录路由 选项。利用该 I P选项,可以看到它是如何频繁使用默认路由的。在第 9章我们将再次回到这个 讨论主题。另外,还讨论了 IP 时间戳选项,但它在实际使用时有所限制。

习题 7.1 请画出7.2节中ping输出的时间线。 7.2 若把b s d i和s l i p主机之间的SLIP链路设置为9600 b/s,请计算这时的 RTT。假定默认的 数据是56字节。 7.3 当前B S D版中的 p i n g程序允许我们为 I C M P报文的数据部分指定一种模式(数据部分的 前8个字节不用来存放模式,因为它要存放发送报文的时间)。如果我们指定的模式为 0xc0,请重新计算上一题中的答案(提示:阅读 2.4节)。 7.4 使用压缩 S L I P(C S L I P,见2 . 5节)是否会影响我们在 7 . 2节中看到的 p i n g输出中的时间 值? 7.5 在图2-4中,ping环回地址与 ping主机以太网地址会出现什么不同?

下载

第8章 Traceroute程序 8.1 引言 由Van Jacobson编写的Traceroute程序是一个能更深入探索 TCP/IP协议的方便可用的工具。 尽管不能保证从源端发往目的端的两份连续的 I P数据报具有相同的路由,但是大多数情况下 是这样的。Traceroute程序可以让我们看到 IP数据报从一台主机传到另一台主机所经过的路由。 Traceroute程序还可以让我们使用 IP源路由选项。 使用手册上说:“程序由Steve Deering提议,由Van Jacobson实现,并由许多其他人 根据C. Philip Wood, Tim Seaver 及Ken Adelman等人提出的令人信服的建议或补充意见 进行调试。”

8.2 Traceroute程序的操作 在7 . 3节中,我们描述了 I P记录路由选项( R R)。为什么不使用这个选项而另外开发一个 新的应用程序?有三个方面的原因。首先,原先并不是所有的路由器都支持记录路由选项, 因此该选项在某些路径上不能使用( Tr a c e r o u t e程序不需要中间路由器具备任何特殊的或可选 的功能)。 其次,记录路由一般是单向的选项。发送端设置了该选项,那么接收端不得不从收到的 IP 首部中提取出所有的信息,然后全部返回给发送端。在 7.3节中,我们看到大多数Ping服务器的 实现(内核中的 ICMP回显应答功能)把接收到的 RR清单返回,但是这样使得记录下来的 IP地 址翻了一番(一来一回)。这样做会受到一些限制,这一点我们在下一段讨论( Traceroute程序 只需要目的端运行一个UDP模块—其他不需要任何特殊的服务器应用程序)。 最后一个原因也是最主要的原因是, I P首部中留给选项的空间有限,不能存放当前大多 数的路径。在 I P首部选项字段中最多只能存放 9个I P地址。在原先的 ARPANET中这是足够的, 但是对现在来说是远远不够的。 Tr a c e r o u t e程序使用 I C M P报文和 I P首部中的 T T L字段(生存周期)。T T L字段是由发送端 初始设置一个 8 bit 字段。推荐的初始值由分配数字 R F C指定,当前值为 6 4。较老版本的系统 经常初始化为 1 5或3 2。我们从第 7章中的一些 p i n g程序例子中可以看出,发送 I C M P回显应答 时经常把TTL设为最大值 255。 每个处理数据报的路由器都需要把 T T L的值减 1或减去数据报在路由器中停留的秒数。由 于大多数的路由器转发数据报的时延都小于 1秒钟,因此 T T L最终成为一个跳站的计数器,所 经过的每个路由器都将其值减 1。 RFC 1009 [Braden and Postel 1987]指出,如果路由器转发数据报的时延超过1秒,那 么它将把TTL值减去所消耗的时间(秒数) 。但很少有路由器这么实现。新的路由器需求 文档RFC [Almquist 1993]为此指定它为可选择功能,允许把TTL看成一个跳站计数器。

72

使用TCP/IP详解,卷1:协议

下载

T T L字段的目的是防止数据报在选路时无休止地在网络中流动。例如,当路由器瘫痪或 者两个路由器之间的连接丢失时,选路协议有时会去检测丢失的路由并一直进行下去。在这 段时间内,数据报可能在循环回路被终止。 T T L字段就是在这些循环传递的数据报上加上一 个生存上限。 当路由器收到一份 IP数据报,如果其 TTL字段是0或1,则路由器不转发该数据报(接收到 这种数据报的目的主机可以将它交给应用程序,这是因为不需要转发该数据报。但是在通常 情况下,系统不应该接收 T T L字段为 0的数据报)。相反,路由器将该数据报丢弃,并给信源 机发一份 I C M P“超时”信息。 Tr a c e r o u t e程序的关键在于包含这份 I C M P信息的I P报文的信源 地址是该路由器的 IP地址。 我们现在可以猜想一下 Traceroute程序的操作过程。它发送一份 TTL字段为1的IP数据报给 目的主机。处理这份数据报的第一个路由器将 T T L值减 1,丢弃该数据报,并发回一份超时 I C M P报文。这样就得到了该路径中的第一个路由器的地址。然后 Tr a c e r o u t e程序发送一份 T T L值为2的数据报,这样我们就可以得到第二个路由器的地址。继续这个过程直至该数据报 到达目的主机。但是目的主机哪怕接收到 T T L值为1的I P数据报,也不会丢弃该数据报并产生 一份超时 I C M P报文,这是因为数据报已经到达其最终目的地。那么我们该如何判断是否已经 到达目的主机了呢? Tr a c e r o u t e程序发送一份 U D P数据报给目的主机,但它选择一个不可能的值作为 U D P端口 号(大于 30 000),使目的主机的任何一个应用程序都不可能使用该端口。因为,当该数据报 到达时,将使目的主机的 U D P模块产生一份“端口不可达”错误(见 6 . 5节)的 I C M P报文。 这样, Tr a c e r o u t e程序所要做的就是区分接收到的 I C M P报文是超时还是端口不可达,以判断 什么时候结束。 Traceroute程序必须可以为发送的数据报设置 TTL字段。并非所有与TCP/IP接口的 程序都支持这项功能,同时并非所有的实现都支持这项能力,但目前大部分系统都支 持这项功能,并可以运行 Traceroute程序。这个程序界面通常要求用户具有超级用户权 限,这意味着它可能需要特殊的权限以在你的主机上运行该程序。

8.3 局域网输出 现在已经做好运行 Tr a c e r o u t e程序并观察其输出的准备了。我们将使用从 s v r 4到s l i p, 经路由器bsdi的简单互联网(见内封面)。bsdi和slip之间是9600 b/s的SLIP链路。

输出的第 1个无标号行给出了目的主机名和其 I P地址,指出t r a c e r o u t e程序最大的 T T L字段 值为30。40字节的数据报包含 20字节IP首部、8字节的UDP首部和12字节的用户数据( 12字节 的用户数据包含每发一个数据报就加 1的序列号,送出 TTL的副本以及发送数据报的时间)。 输出的后面两行以TTL开始,接下来是主机或路由器名以及其IP地址。对于每个TTL值,发 送3份数据报。每接收到一份 ICMP报文,就计算并打印出往返时间。如果在 5秒种内仍未收到3 份数据报的任意一份的响应,则打印一个星号,并发送下一份数据报。在上述输出结果中, TTL字段为1的前3份数据报的ICMP报文分别在20 ms、10 ms和10 ms收到。TTL字段为2的3份数

73

第8章 Traceroute程序使用

下载

据报的ICMP报文则在120 ms后收到。由于TTL字段为2到达最终目的主机,因此程序就此停止。 往返时间是由发送主机的 t r a c e r o u t e程序计算的。它是指从 t r a c e r o u t e程序到该路 由器的总往返时间。如果我们对每段路径的时间感兴趣,可以用 TTL字段为N+1所打印出来的 时间减去TTL字段为N的时间。 图8-1给出了t c p d u m p的运行输出结果。正如我们所预想的那样,第 1个发往 b s d i的探测 数据报的往返时间是 20 ms 、而后面两个数据报往返时间是 10 ms 的原因是发生了一次 A R P交 换。tcpdump结果证实了确实是这种情况。

图8-1 从svr4到slip的traceroute程序示例的tcpdump输出结果

目的主机 U D P端口号最开始设置为 3 3 4 3 5,且每发送一个数据报加 1。可以通过命令行选 项来改变开始的端口号。 U D P数据报包含 1 2个字节的用户数据,我们在前面 t r a c e r o u t e程 序输出的40字节数据报中已经对其进行了描述。 后面 t c p d u m p打印出了 T T L字段为 1的I P数据报的注释 [t t l 1]。当 T T L值为 0或1时, t c p d u m p打印出这条信息,以提示我们数据报中有些不太寻常之处。在这里可以预见到 T T L 值为1;而在其他一些应用程序中,它可以警告我们数据报可能无法到达其最终目的主机。我 们不可能看到路由器传送一个 TTL值为0的数据报,除非发出该数据报的该路由器已经崩溃。 因为b s d i路由器将 T T L值减到 0,因此我们预计它将发回“传送超时”的 I C M P报文。即 使这份被丢弃的 IP报文发送往 slip,路由器也会发回 ICMP报文。 有两种不同的 ICM P“超时”报文(见 6 . 2节的图6 - 3),它们的 I C M P报文中c o d e字段不同。 图8-2给出了这种 ICMP差错报文的格式。

类型

代码(0或1)





和 8字节

未用(必须为0)

IP首部(包括选项)+原始IP数据报中数据的前8字节

图8-2 ICMP超时报文

74

使用TCP/IP详解,卷1:协议

下载

我们所讨论的ICMP报文是在TTL值等于0时产生的,其code字段为0。 主机在组装分片时可能发生超时,这时,它将发送一份“组装报文超时”的

I C M P报文

(我们将在 11.5节讨论分片和组装)。这种差错报文将 code字段置1。 图8 - 1的第9 ~ 1 4行对应于 T T L为2的3份数据报。这 3份报文到达最终目的主机,并产生一 份ICMP端口不可达报文。 计算出S L I P链路的往返时间是很有意义的,就象我们在 7 . 2节中所举的 P i n g例子,将链路 值设置为 1 2 0 0 b / s一样。发送出的 U D P数据报共 4 2个字节,包括 1 2字节的数据、 8字节U D P首 部、2 0字节的I P首部以及(至少) 2字节的S L I P帧(2 . 4节)。但是与 P i n g不一样的是,返回的 数据报大小是变化的。从图 6 - 9可以看出,返回的 I C M P报文包含发生差错的数据报的 I P首部 以及紧随该 I P首部的8字节数据(在 t r a c e r o u t e程序中,即 U D P首部)。这样,总共就是 2 0 + 8 + 20 + 8 + 2,即58字节。在数据速率为 960 b/s的情况下,预计的 RTT就是(42 + 58/960), 即104 ms。这个值与 svr4上所估算出来的 110 ms是吻合的。 图8 - 1中的源端口号( 4 2 8 0 4)看起来有些大。 t r a c e r o u t e程序将其发送的 U D P数据报 的源端口号设置为 U n i x进程号与 3 2 7 6 8 之间的逻辑或值。对于在同一台主机上多次运行 t r a c e r o u t e程序的情况,每个进程都查看 ICMP返回的UDP首部的源端口号,并且只处理那 些对自己发送应答的报文。 关于t r a c e r o u t e程序,还有一些必须指出的事项。首先,并不能保证现在的路由也是 将来所要采用的路由,甚至两份连续的 I P数据报都可能采用不同的路由。如果在运行程序时, 路由发生改变,就会观察到这种变化,这是因为对于一个给定的 T T L,如果其路由发生变化, traceroute程序将打印出新的 IP地址。 第二,不能保证 I C M P报文的路由与 t r a c e r o u t e程序发送的 U D P数据报采用同一路由。 这表明所打印出来的往返时间可能并不能真正体现数据报发出和返回的时间差(如果 U D P数 据报从信源到路由器的时间是 1秒,而I C M P报文用另一条路由返回信源用了 3秒时间,则打印 出来的往返时间是 4秒)。 第三,返回的 I C M P报文中的信源 I P地址是U D P数据报到达的路由器接口的 I P地址。这与 I P记录路由选项( 7 . 3节)不同,记录的 I P地址指的是发送接口地址。由于每个定义的路由器 都有2个或更多的接口,因此,从 A主机到B主机上运行t r a c e r o u t e程序和从 B主机到A主机 上运行 t r a c e r o u t e 程序所得到的结果可能是不同的。事实上,如果我们从 s l i p主机到 svr4上运行traceroute程序,其输出结果变成了:

这次打印出来的 b s d i主机的I P地址是1 4 0 . 2 5 2 . 1 3 . 6 6,对应于 S L I P接口;而上次的地址是 1 4 0 . 2 5 2 . 1 3 . 3 5,是以太网接口地址。由于 t r a c e r o u t e程序同时也打印出与 I P地址相关的主 机名,因而主机名也可能变化(在我们的例子中, bsdi上的两个接口都采用相同的名字)。 考虑图8 - 3的情况。它给出了两个局域网通过一个路由器相连的情况。两个路由器通过一 个点对点的链路相连。如果我们在左边 L A N的一个主机上运行 t r a c e r o u t e程序,那么它将 发现路由器的 I P地址为 i f 1和i f 3。但在另一种情况下,就会发现打印出来的 I P地址为 i f 4和i f 2。 if2和if3有着同样的网络号,而另两个接口则有着不同的网络号。

75

第8章 Traceroute程序使用

下载 网络1

网络3 路由器1

网络2

路由器2

图8-3 traceroute 程序打印出的接口标识

最后,在广域网情况下,如果 t r a c e r o u t e程序的输出是可读的域名形式,而不是 I P地 址形式,那么会更好理解一些。但是由于 t r a c e r o u t e程序接收到 ICMP报文时,它所获得的 唯一信息就是 I P地址,因此,在给定 I P地址的情况下,它做一个“反向域名查看”工作来获 得域名。这就需要路由器或主机的管理员正确配置其反向域名查看功能(并非所有的情况下 都是如此)。我们将在 14.5节描述如何使用 DNS将一个IP地址转换成域名。

8.4 广域网输出 前面所给出的小互联网的输出例子对于查看协议运行过程来说是足够了,但对于像全球 互联网这样的大互联网来说,应用 traceroute程序就需要一些更为实际的东西。 图8-4是从sun主机到NIC (Network Information Center)的情况。

图8-4 从sun 主机到nic.ddn.mil 的traceroute 程序

由于运行的这个例子包含文本,非 D D N站点(如,非军方站点)的 N I C已经从 nic.ddn.mil转移到rs.internic.net,即新的“InterNIC”。 一旦数据报离开tuc.noao.edu网,它们就进入了telcom.arizona.edu网络。然后这些 数据报进入NASA Science Internet,n s n . n a s a . g o v。TTL字段为6和7的路由器位于 JPL (Jet Propulsion Laboratory)上。TTL字段为11所输出的sura.net网络位于Southeastern Universities Research Association Network上。TTL字段为12的域名GSI是Government Systems, Inc., NIC的运营者。 T T L字段为6的第2个RT T(5 9 0)几乎是其他两个 RT T值(2 3 4和2 6 2)的两倍 。它表明 I P 路由的动态变化。在发送主机和这个路由器之间发生了使该数据报速度变慢的事件。同样, 我们不能区分是发出的数据报还是返回的 ICMP差错报文被拦截。 TTL字段为3的第1个RTT探测值(204)比TTL字段为2的第1个探测值( 233)值还小。由

76

使用TCP/IP详解,卷1:协议

下载

于每个打印出来的 RTT值是从发送主机到路由器的总时间,因此这种情况是可能发生的。 图8-5的例子是从 sun主机到作者出版商之间的运行例子。

图8-5 从sun.tuc.noao.edu

主机到aw.com 的traceroute 程序

在这个例子中,数据报离开 t e l c o m . a r i z o n a . e d u网络后就进行了地区性的网络 w e s t n e t . n e t ( T T L字段值为 6和7 )。然后进行了由 Advanced Network & Services 运营的 N S F N E T主干网, t 3 . a n s . n e t,(T 3是对于主干网采用的 45 Mb/s电话线的一般缩写。)最后的网 络是alter.net,即aw.com与互联网的连接点。

8.5 IP源站选路选项 通常I P路由是动态的,即每个路由器都要判断数据报下面该转发到哪个路由器。应用程 序对此不进行控制,而且通常也并不关心路由。它采用类似 Tr a c e r o u t e程序的工具来发现 实际的路由。 源站选路(source routing)的思想是由发送者指定路由。它可以采用以下两种形式: • 严格的源路由选择。发送端指明 IP数据报所必须采用的确切路由。如果一个路由器发现 源路由所指定的下一个路由器不在其直接连接的网络上,那么它就返回一个“源站路 由失败”的 ICMP差错报文。 • 宽松的源站选路。发送端指明了一个数据报经过的 IP地址清单,但是数据报在清单上指 明的任意两个地址之间可以通过其他路由器。 Tr a c e r o u t e程序提供了一个查看源站选路的方法,我们可以在选项中指明源站路由,然后 检查其运行情况。 一些公开的Traceroute程序源代码包中包含指明宽松的源站选路的补丁。但是在标 准版中通常并不包含此项。这些补丁的解释是“ Van Jacobson的原始 Tr a c e r o u t e程序

77

第8章 Traceroute程序使用

下载

(1988年春)支持该特性,但后来因为有人提出会使网关崩溃而将此功能去除。”对于 本章中所给出的例子,作者将这些补丁安装上去,并将它们设置成允许宽松的源站选 路和严格的源站选路。 图8-6给出了源站路由选项的格式。 39字节

4字节

4字节

4字节

4字节

图8-6 IP首部源站路由选项的通用格式

这个格式与我们在图 7 - 3中所示的记录路由选项格式基本一致。不同之处是,对于源站选 路,我们必须在发送 IP数据报前填充IP地址清单;而对于记录路由选项,我们需要为 IP地址清 单分配并清空一些空间,并让路由器填充该清单中的各项。同时,对于源站选路,只要为所 需要的I P地址数分配空间并进行初始化,通常其数量小于 9。而对于记录路由选项来说,必须 尽可能地分配空间,以达到 9个地址。 对于宽松的源站选路来说, c o d e字段的值是 0 x 8 3;而对于严格的源站选路,其值为 0 x 8 9。 len和ptr字段与7.3节中所描述的一样。 源站路由选项的实际称呼为“源站及记录路由” (对于宽松的源站选路和严格的源站选路, 分别用LSRR和SSRR表示),这是因为在数据报沿路由发送过程中,对 IP地址清单进行了更新。 下面是其运行过程: • 发送主机从应用程序接收源站路由清单,将第 1个表项去掉(它是数据报的最终目的地 址),将剩余的项移到 1个项中(如图 8-6所示),并将原来的目的地址作为清单的最后一 项。指针仍然指向清单的第 1项(即,指针的值为 4)。 • 每个处理数据报的路由器检查其是否为数据报的最终地址。如果不是,则正常转发数 据报(在这种情况下,必须指明宽松源站选路,否则就不能接收到该数据报)。 • 如果该路由器是最终目的,且指针不大于路径的长度,那么( 1)由p t r所指定的清单中的 下一个地址就是数据报的最终目的地址;( 2)由外出接口 (outgoing interface)相对应的 I P 地址取代刚才使用的源地址;( 3)指针加 4。 可以用下面这个例子很好地解释上述过程。在图 8 - 7中,我们假设主机 S上的发送应用程 序发送一份数据报给 D,指定源路由为 R1,R2和R3。

图8-7 IP源路由示例

在上图中,#表示指针字段,其值分别是 4、8、1 2和1 6。长度字段恒为 1 5(三个I P地址加 上三个字节首部)。可以看出,每一跳 IP数据报中的目的地址都发生改变。 当一个应用程序接收到由信源指定路由的数据时,在发送应答时,应该读出接收到的路 由值,并提供反向路由。

78

使用TCP/IP详解,卷1:协议

下载

Host Requirements RFC指明,TCP客户必须能指明源站选路,同时, TCP服务器 必须能够接收源站选路,并且对于该 TCP连接的所有报文段都能采用反向路由。如果 T C P服务器下面接收到一个不同的源站选路,那么新的源站路由将取代旧的源站路 由。 8.5.1 宽松的源站选路的traceroute程序示例 使用t r a c e r o u t e程序的- g选项,可以为宽松的源站选路指明一些中间路由器。采用该 选项可以最多指定 8个中间路由器(其个数是 8而不是9的原因是,所使用的编程接口要求最后 的表目是目的主机)。 在图8-4中,去往 NIC,即n i c . d d n . m i l的路由经过 NASA Science Internet。在图8-8中, 我们通过指定路由器 e n s s 1 4 2 . U T . w e s t n e t . n e t (192.31.39.21) 作为中间路由器来强制数 据报通过NSFNET:

图8-8 采用宽松源站选路通过NSFNET到达nic.ddn.mil 的traceroute 程序

在这种情况下,看起来路径中共有 1 6跳,其平均 RT T大约是350 ms。而图8 - 4的通常选路 则只有 1 3跳,其平均 RT T约为322 ms 。默认路径看起来更好一些(在建立路径时,还需要考 虑其他的一些因素。其中一些必须考虑的因素是所包含网络的组织及政治因素)。 前面我们说看起来有 1 6跳,这是因为将其输出结果与前面的通过 N S F N E T(图8 - 5)的示 例比较,发现在本例采用宽松源路由,选择了 3个路由器(这可能是因为路由器对源站选路数 据 报 产 生 I C M P 超 时 差 错 报 文 上 存 在 一 些 差 错 )。 在 n e t b 和 b u t c h 路 由 器 之 间 的 g a t e w a y. t u c . n o a o . e d u 路由器丢失了,同时,位于 G a b b y和e n s s 1 4 2 . U T. w e s t . n e t 之间的 Westgate.Telcom.Arizona.edu和u u - u a . A Z . w e s t n e t . n e t两个路由器也丢失了。在这些丢失的路由 器上可能发生了与接收到宽松的源站选路选项数据报有关的程序问题。实际上,当采用 NSFNET时,信源和 NIC之间的路径有19跳。本章习题8.5继续对这些丢失路由器进行讨论。 同时本例也指出了另一个问题。在命令行,我们必须指定路由器 enss142.UT.westnet.net的 点分十进制IP地址,而不能以其域名代替。这是因为,反向域名解析( 14.5节中描述的通过 IP

79

第8章 Traceroute程序使用

下载

地址返回域名)将域名与 I P地址相关联,但是前向解析(即给出域名返回 I P地址)则无法做 到。在 D N S中,前向映射和反向映射是两个独立的文件,而并非所有的管理者都同时拥有这 两个文件。因此,在一个方向是工作正常而另一个方向却失败的情况并不少见。 还有一种以前没有碰到过的情况是在 T T L字段为 8的情况下,对于第一个 RT T,打印一个 星号。这表明,发生超时,在 5秒内未收到本次探查的应答信号。 将本图与图 8 - 4相比较,还可以得出一个结论,即路由器 n s n - F I X - p e . s u r a . n e t同时 与NSFNET和NASA Science Internet相连。 8.5.2 严格的源站选路的traceroute程序示例 在作者的 t r a c e r o u t e程序版本中,- G选项与前面所描述的- g选项是完全一样的,不 过此时是严格的源站选路而不是宽松的源站选路。我们可以采用这个选项来观察在指明无效 的严格的源站选路时其结果会是什么样的。从图 8 - 5可以看出来,从作者的子网发往 N S F N E T 的数据报的正常路由器顺序是 n e t b,g a t e w a y,b u t c h和g a b b y(为了便于查看,后面所 有的输出结果中,均省略了域名后缀 . t u c . n o a o . e d u和. t e l c o m . a r i z o n a . e d u)。我们 指定了一个严格源路由,使其试图将数据报从 gateway直接发送到 gabby,而省略了butch。 我们可以猜测到其结果会是失败的,正如图 8-9所给出的结果。

图8-9 采用严格源站路由失败的traceroute程序

这里的关键是在于 T T L字段为3的输出行中, RT T后面的 ! S。这表明 t r a c e r o u t e程序接 收到I C M P“源站路由失败”的差错报文:即图 6 - 3中t y p e字段为3,而c o d e字段为5。T T L字段 为3的第二个 RT T 位置的星号表示未收到这次探查的应答信号。这与我们所猜想的一样, gateway不可能直接发送数据报给 gabby,这是因为它们之间没有直接的连接。 T T L字段为 2和3的结果都来自于 g a t e w a y,对于 T T L字段为 2的应答来自 g a t e w a y,是因 为g a t e w a y接收到TTL字段为1的数据报。在它查看到(无效的)严格的源站选路之前,就发 现T T L已过期,因此发送回 I C M P超时报文。 T T L字段等于 3的行,在进入 g a t e w a y时其T T L 字段为 2,因此,它查看严格的源站选路,发现它是无效的,因此发送回 I C M P源站选路失败 的差错报文。 图8 - 1 0给出了与本例相对应的 t c p d u m p输出结果。该输出结果是在 s u n和n e t b之间的 S L I P链路上遇到的。我们必须在 t c p d u m p中指定- v选项以显示出源站路由信息。这样,会 输出一些像数据报 I D这样我们并不需要的结果,我们在给出结果中将这些不需要的结果删除 掉。同样,用SSRR表示“严格的源站及记录路由”。 首先注意到, s u n所发送的每个 U D P数据报的目的地址都是 n e t b ,而不是目的主机 (w e s t g a t e)。这一点可以用图 8 - 7的例子来解释。类似地,- G选项所指定的另外两个路由器 (gateway和gabby)以及最终目(westgate)成为第一跳的SSRR选项。 从这个输出结果中,还可以看出, t r a c e r o u t e程序所采用的定时时间(第 1 5行和1 6行

80

使用TCP/IP详解,卷1:协议

下载

之间的时间差)是 5秒。

图8-10 失败的严格源站选路traceroute 程序的tcpdump 输出结果

8.5.3 宽松的源站选路traceroute程序的往返路由 我们在前面已经说过,从 A到B的路径并不一定与从 B到A的路径完全一样。除非同时在两 个系统中登录并在每个终端上运行 t r a c e r o u t e程序,否则很难发现两条路径是否不同。但 是,采用宽松的源站选路,就可以决定两个方向上的路径。 这里的窍门就在于指定一个宽松的源站路由,该路由的目的端和宽松路径一样,但发送端为 目的主机。例如,在sun主机上,我们可以查看到发往以及来自 bruno.cs.colorado.edu的 结果如图8-11所示。 发出路径( T T L字段为1 ~ 11)的结果与返回路径( T T L字段为11 ~ 2 1)不同,这很好地说 明了在Internet 上,选路可能是不对称的。 该输出同时还说明了我们在图 8-3中所讨论的问题。比较 TTL字段为2和19的输出结果:它 们都是路由器 g a t e w a y . t u c . n o a o . e d u,但两个 I P地址却是不同的。由于 t r a c e r o u t e程 序以进入接口作为其标识,而我们从两条不同的方向经过该路由器,一条是发出路径( T T L 字段为 2),另一条是返回路径( T T L字段为1 9),因此可以猜想到这个结果。通过比较 T T L字 段为3和18、4和17的结果,可以看到同样的结果。

下载

81

第8章 Traceroute程序使用

图8-11 显示非对称路径的traceroute 程序

8.6 小结 在一个T C P / I P网络中, t r a c e r o u t e程序是不可缺少的工具。其操作很简单:开始时发 送一个 T T L字段为 1的U D P数据报,然后将 T T L字段每次加 1,以确定路径中的每个路由器。 每个路由器在丢弃 U D P数据报时都返回一个 I C M P超时报文 2,而最终目的主机则产生一个 ICMP端口不可达的报文。 我们给出了在 L A N和WA N上运行 t r a c e r o u t e程序的例子,并用它来考察 I P源站选路。 我们用宽松的源站选路来检测发往目的主机的路由是否与从目的主机返回的路由一样。

习题 8.1 当IP将接收到的 TTL字段减1,发现它为 0时,将会发生什么结果? 8.2 traceroute程序是如何计算 RTT的?将这种计算 RTT的方法与ping相比较。 8.3 (本习题与下一道习题是基于开发 t r a c e r o u t e程序过程中遇到的实际问题,它们来自 于t r a c e r o u t e程序源代码注释)。假设源主机和目的主机之间有三个路由器( R 1、R 2 和R3),而中间的路由器( R2)在进入TTL字段为1时,将TTL字段减1,但却错误地将该 I P数据报发往下一个路由器。请描述会发生什么结果。在运行 t r a c e r o u t e程序时会看 到什么样的现象? 8.4 同样,假设源主机和目的主机之间有三个路由器。由于目的主机上存在错误,因此,它 总是将进入 T T L值作为外出 I C M P报文的T T L值。请描述这将发生什么结果,你会看到什 么现象。

82

使用TCP/IP详解,卷1:协议

下载

8.5 在图8 - 8运行例子中,我们可以在 s u n和n e t b之间的 S L I P链路上运行 t c p d u m p程序。如 果指定- v选项,就可以看到返回 I C M P报文的 T T L值。这样,我们可以看到进入 n e t b、 b u t c h、G a b b y和e n s s 1 4 2 . U T . w e s t n e t . n e t的T T L值分别为 2 5 5、2 5 3、2 5 2和2 4 9。 这是否为我们判断是否存在丢失路由器提供了额外的信息? 8.6 S u n O S和S V R 4都提供了带- l选项的 p i n g版本,以提供松源选路。手册上说明,该选项 可以与- R选项(指定记录路由选项)一起使用。如果已经进入到这些系统中,请尝试同 时用这两个选项。其结果是什么?如果采用 tcpdump来观测数据报,请描述其过程。 8.7 比较ping和traceroute程序在处理同一台主机上客户的多个实例的不同点。 8.8 比较ping和traceroute程序在计算往返时间上的不同点。 8.9 我们已经说过, t r a c e r o u t e程序选取开始 U D P目的主机端口号为 3 3 4 5 3,每发送一个 数据报将此数加 1。在 1 . 9节中,我们说过暂时端口号通常是 1 0 2 4 ~ 5 0 0 0之间的值,因此 t r a c e r o u t e程序的目的主机端口号不可能是目的主机上所使用的端口号。在 S o l a r i s 2 . 2 系统中的情况也是如此吗?(提示:查看 E.4节) 8.10 RFC 1393 [Malkin 1993b]提出了另一种判断到目的主机路径的方法。请问其优缺点是什 么?

下载

第9章 IP选路 9.1 引言 选路是I P最重要的功能之一。图 9 - 1是I P层处理过程的简单流程。需要进行选路的数据报 可以由本地主机产生,也可以由其他主机产生。在后一种情况下,主机必须配置成一个路由 器,否则通过网络接口接收到的数据报,如果目的地址不是本机就要被丢弃(例如,悄无声 息地被丢弃)。 在图 9 - 1中,我们还描述了一个路由守护程序( d a e m o n),通常这是一个用户进程。在 U n i x系统中,大多数普通的守护程序都是路由程序和网关程序(术语 d a e m o n指的是运行在后 台的进程,它代表整个系统执行某些操作。 d a e m o n一般在系统引导时启动,在系统运行期间 一直存在)。在某个给定主机上运行何种路由协议,如何在相邻路由器上交换选路信息,以及 选路协议是如何工作的,所有这些问题都是非常复杂的,其本身就可以用整本书来加以讨论 (有兴趣的读者可以参考文献 [Perlman 1992]以获得更详细的信息)。在第10章中,我们将简单 讨论动态选路和选路信息协议 RIP(Routing Information Protocol)。在本章中,我们主要的目 的是了解单个IP层如何作出路由决策。 图9 - 1所示的路由表经常被 I P访问(在一个繁忙的主机上,一秒钟内可能要访问几百次), 但是它被路由守护程序更新的频度却要低得多(可能大约 3 0秒种一次)。当接收到 I C M P重定 向,报文时,路由表也要被更新,这一点我们将在 9.5节讨论route命令时加以介绍。在本章中, 我们还将用 netstat命令来显示路由表。 路由守 护程序

route命 令

netstat 命令

根据相邻路由 器更新路由表

UDP

TCP

ICMP 是

路由表

是分组(IP地址 之一或是广 播地址)

IP输出: 计算下一站路 由器(如果需要)

处理IP选项

IP输入队列 IP层 网络接口

图9-1 IP层工作流程

84

使用TCP/IP详解,卷1:协议

下载

9.2 选路的原理 开始讨论 I P选路之前,首先要理解内核是如何维护路由表的。路由表中包含的信息决定 了IP层所做的所有决策。 在3.3节中,我们列出了 IP搜索路由表的几个步骤: 1) 搜索匹配的主机地址; 2) 搜索匹配的网络地址; 3) 搜索默认表项(默认表项一般在路由表中被指定为一个网络表项,其网络号为 0)。 匹配主机地址步骤始终发生在匹配网络地址步骤之前。 I P层进行的选路实际上是一种选路机制,它搜索路由表并决定向哪个网络接口发送分组。 这区别于选路策略,它只是一组决定把哪些路由放入路由表的规则。 I P执行选路机制,而路 由守护程序则一般提供选路策略。 9.2.1 简单路由表 首先来看一看一些典型的主机路由表。在主机svr4上,我们先执行带-r选项的netstat命令 列出路由表,然后以-n选项再次执行该命令,以数字格式打印出IP地址(我们这样做是因为路由 表中的一些表项是网络地址,而不是主机地址。如果没有- n选项,n e t s t a t命令将搜索文件 /etc/networks并列出其中的网络名。这样会与另一种形式的名字—网络名加主机名相混淆) 。

第1行说明,如果目的地是 1 4 0 . 2 5 2 . 1 3 . 6 5(s l i p主机),那么网关(路由器)将把分组转 发给1 4 0 . 2 5 2 . 1 3 . 3 5(b s d i)。这正是我们所期望的,因为主机 s l i p通过S L I P链路与 b s d i相 连接,而bsdi与该主机在同一个以太网上。 对于一个给定的路由器,可以打印出五种不同的标志( flag): U 该路由可以使用。 G 该路由是到一个网关(路由器)。如果没有设置该标志,说明目的地是直接相连的。 H 该路由是到一个主机,也就是说,目的地址是一个完整的主机地址。如果没有设置该 标志,说明该路由是到一个网络,而目的地址是一个网络地址:一个网络号,或者网 络号与子网号的组合。 D 该路由是由重定向报文创建的( 9.5节)。 M 该路由已被重定向报文修改( 9.5节)。 标志G是非常重要的,因为由它区分了间接路由和直接路由(对于直接路由来说是不设置 标志G的)。其区别在于,发往直接路由的分组中不但具有指明目的端的 I P地址,还具有其链 路层地址(见图 3 - 3)。当分组被发往一个间接路由时, I P地址指明的是最终的目的地,但是 链路层地址指明的是网关(即下一站路由器)。我们在图 3 - 4已看到这样的例子。在这个路由 表例子中,有一个间接路由(设置了标志 G),因此采用这一项路由的分组其 I P地址是最终的 目的地(140.252.13.65),但是其链路层地址必须对应于路由器 140.252.13.35。

85

第9章 IP路由选择使用

下载

理解G和H标志之间的区别是很重要的。 G标志区分了直接路由和间接路由,如上所述。 但是H标志表明,目的地址( n e t s t a t命令输出第一行)是一个完整的主机地址。没有设置 H标志说明目的地址是一个网络地址(主机号部分为 0)。当为某个目的 I P地址搜索路由表时, 主机地址项必须与目的地址完全匹配,而网络地址项只需要匹配目的地址的网络号和子网号 就可以了。另外,大多数版本的 n e t s t a t命令首先打印出所有的主机路由表项,然后才是网 络路由表项。 参考记数Refcnt(Reference count)列给出的是正在使用路由的活动进程个数。面向连接 的协议如 T C P在建立连接时要固定路由。如果在主机 s v r 4和s l i p之间建立 Te l n e t连接,可以 看到参考记数值变为 1。建立另一个Telnet连接时,它的值将增加为 2,依此类推。 下一列(“u s e”)显示的是通过该路由发送的分组数。如果我们是这个路由的唯一用户, 那么运行 p i n g程序发送 5个分组后,它的值将变为 5。最后一列( i n t e r f a c e)是本地接口的名 字。 输出的第 2行是环回接口( 2 . 7节),它的名字始终为 l o 0。没有设置 G标志,因为该路由不 是一个网关。 H标志说明目的地址( 127.0.0.1)是一个主机地址,而不是一个网络地址。由于 没有设置G标志,说明这是一个直接路由,网关列给出的是外出 IP地址。 输出的第 3行是默认路由。每个主机都有一个或多个默认路由。这一项表明,如果在表中 没有找到特定的路由,就把分组发送到路由器 1 4 0 . 2 5 2 . 1 3 . 3 3(s u n主机)。这说明当前主机 (s v r 4)利用这一个路由表项就可以通过 I n t e r n e t经路由器 s u n(及其S L I P链路)访问其他的系 统。建立默认路由是一个功能很强的概念。该路由标志( U G)表明它是一个网关,这是我们 所期望的。 这里,我们有意称sun为路由器而不是主机,因为它被当作默认路由器来使用,它 发挥的是IP转发功能,而不是主机功能。 Host Requirements RFC文档特别说明,IP层必须支持多个默认路由。但是,许多实 现系统并不支持这一点。当存在多个默认路由时,一般的技术就成为它们周围的知更 鸟了,例如,Solaris 2.2就是这样做的。 输出中的最后一行是所在的以太网。 H标志没有设置,说明目的地址( 140.252.13.32)是 一个网络地址,其主机地址部分设为 0。事实上,是它的低 5位设为 0(见图 3 - 11)。由于这是 一个直接路由( G标志没有被设置),网关列指出的 IP地址是外出地址。 n e t s t a t命令输出的最后一项还隐含了另一个信息,那就是目的地址( 1 4 0 . 2 5 2 . 1 3 . 3 2) 的子网掩码。如果要把该目的地址与 1 4 0 . 2 5 2 . 1 3 . 3 3进行比较,那么在比较之前首先要把它与 目的地址掩码( 0 x ffffff e 0,3 . 7节)进行逻辑与。由于内核知道每个路由表项对应的接口,而 且每个接口都有一个对应的子网掩码,因此每个路由表项都有一个隐含的子网掩码。 主机路由表的复杂性取决于主机所在网络的拓扑结构。 1) 最简单的(也是最不令人感兴趣的)情况是主机根本没有与任何网络相连。 T C P / I P协 议仍然能用于这样的主机,但是只能与自己本身通信!这种情况下的路由表只包含环回接口 一项。 2) 接下来的情况是主机连在一个局域网上,只能访问局域网上的主机。这时路由表包含 两项:一项是环回接口,另一项是局域网(如以太网)。 3) 如果主机能够通过单个路由器访问其他网络(如 I n t e r n e t)时,那么就要进行下一步。

86

使用TCP/IP详解,卷1:协议

下载

一般情况下增加一个默认表项指向该路由器。 4) 如果要新增其他的特定主机或网络路由,那么就要进行最后一步。在我们的例子中, 到主机slip的路由要通过路由器 bsdi就是这样的例子。 我们根据上述IP操作的步骤使用这个路由表为主机 svr4上的一些分组例子选择路由。 1) 假定目的地址是主机 s u n,1 4 0 . 2 5 2 . 1 3 . 3 3。首先进行主机地址的匹配。路由表中的两 个主机地址表项( s l i p和l o c a l h o s t)均不匹配,接着进行网络地址匹配。这一次匹配成 功,找到表项 1 4 0 . 2 5 2 . 1 3 . 3 2(网络号和子网号都相同),因此使用 e m d 0接口。这是一个直接 路由,因此链路层地址将是目的端的地址。 2) 假定目的地址是主机 s l i p,140.252.13.65。首先在路由表搜索主机地址,并找到一个 匹配地址。这是一个间接路由,因此目的端的 I P地址仍然是 1 4 0 . 2 5 2 . 1 3 . 6 5,但是链路层地址 必须是网关 140.252.13.65的链路层地址,其接口名为 emd0。 3) 这一次我们通过 I n t e r n e t给主机 a w . c o m(1 9 2 . 2 0 7 . 11 7 . 2)发送一份数据报。首先在路 由表中搜索主机地址,失败后进行网络地址匹配。最后成功地找到默认表项。该路由是一个 间接路由,通过网关 140.252.13.33,并使用接口名为 emd0。 4) 在我们最后一个例子中,我们给本机发送一份数据报。有四种方法可以完成这件事, 如用主机名、主机 IP地址、环回名或者环回 IP地址: ftp ftp ftp ftp

svr4 140.252.13.34 localhost 127.0.0.1

在前两种情况下,对路由表的第 2次搜索得到一个匹配的网络地址 1 4 0 . 2 5 2 . 1 3 . 3 2,并把 I P 报文传送给以太网驱动程序。正如图 2 - 4所示的那样, I P报文中的目的地址为本机 I P地址,因 此报文被送给环回驱动程序,然后由驱动程序把报文放入 IP输出队列中。 在后两种情况下,由于指定了环回接口的名字或 I P地址,第一次搜索就找到匹配的主机 地址,因此报文直接被送给环回驱动程序,然后由驱动程序把报文放入 IP输出队列中。 上述四种情况报文都要被送给环回驱动程序,但是采用的两种路由决策是不相同的。 9.2.2 初始化路由表 我们从来没有说过这些路由表是如何被创建的。每当初始化一个接口时(通常是用 i f c o n f i g命令设置接口地址),就为接口自动创建一个直接路由。对于点对点链路和环回接口 来说,路由是到达主机(例如,设置 H标志)。对于广播接口来说,如以太网,路由是到达网 络。 到达主机或网络的路由如果不是直接相连的,那么就必须加入路由表。一个常用的方法 是在系统引导时显式地在初始化文件中运行 route命令。在主机 s v r 4上,我们运行下面两个命 令来添加路由表中的表项: route add default sun 1 route add slip bsdi 1

第3个参数( d e f a u l t和s l i p)代表目的端,第 4个参数代表网关(路由器),最后一个 参数代表路由的度量 (metric)。r o u t e命令在度量值大于 0时要为该路由设置 G标志,否则,当 耗费值为0时就不设置 G标志。

87

第9章 IP路由选择使用

下载

不幸的是,几乎没有系统愿意在启动文件中包含route命令。在4.4BSD和BSD/386 系统中,启动文件是

/ e t c / n e t s t a r t ;在 S V R 4 系统中,启动文件是

/etc/inet/rc.inet;在Solaris 2.x中,启动文件是 /etc/rc2.d/S69inet;在 SunOS 4.1.x中,启动文件是/etc/rc.local;而AIX 3.2.2则使用文件/etc/rc.net。 一些系统允许在某个文件中指定默认的路由器,如 / e t c / d e f a u l t r o u t e r。于是在每 次重新启动系统时都要在路由表中加入该默认项。 初始化路由表的其他方法是运行路由守护程序(第 1 0章)或者用较新的路由器发现协议 (9.6节)。 9.2.3 较复杂的路由表 在我们的子网上,主机 s u n是所有主机的默认路由器,因为它有拨号 S L I P链路连接到 Internet上(参见扉页前图)。

前两项与主机 s v r 4的前两项一致:通过路由器 b s d i到达s l i p的特定主机路由,以及环 回路由。 第3行是新加的。这是一个直接到达主机的路由 (没有设置 G标志,但设置了 H标志),对应 于点对点的链路,即 SLIP接口。如果我们把它与 ifconfig命令的输出进行比较: sun % ifconfig sl0 s l 0 : f l a g s = 1 0 5 1 inet 140.252.1.29 --> 140.252.1.183 netmask ffffff00

可以发现路由表中的目的地址就是点对点链路的另一端 (即路由器n e t b), 网关地址为外出 接口的本地 I P地址( 1 4 0 . 2 5 2 . 1 . 2 9 ) (前面已经说过 , n e t s t a t为直接路由打印出来的网关地址就 是本地接口所用的 IP地址)。 默认的路由表项是一个到达网络的间接路由 (设置了 G标志,但没有设置 H标志),这正是 我们所希望的。网关地址是路由器的地址 ( 1 4 0 . 2 5 2 . 1 . 1 8 3,S L I P链路的另一端 ), 而不是S L I P链 路的本地IP地址(140.252.1.29)。其原因还是因为是间接路由,不是直接路由。 还应该指出的是, n e t s t a t输出的第 3和第4行(接口名为 s l 0)由S L I P软件在启动时创建, 并在关闭时删除 . 9.2.4 没有到达目的地的路由 我们所有的例子都假定对路由表的搜索能找到匹配的表项,即使匹配的是默认项。如果 路由表中没有默认项,而又没有找到匹配项,这时会发生什么情况呢? 结果取决于该 I P数据报是由主机产生的还是被转发的(例如,我们就充当一个路由器)。 如果数据报是由本地主机产生的,那么就给发送该数据报的应用程序返回一个差错,或者是 “主机不可达差错”或者是“网络不可达差错”。如果是被转发的数据报,那么就给原始发送

88

使用TCP/IP详解,卷1:协议

下载

端发送一份 ICMP主机不可达的差错报文。下一节将讨论这种差错。

9.3

ICMP主机与网络不可达差错 当路由器收到一份 I P数据报但又不能转发时,就要发送一份 I C M P“主机不可达”差错报

文(I C M P主机不可达报文的格式如图 6 - 1 0所示)。可以很容易发现,在我们的网络上把接在 路由器 s u n上的拨号 S L I P链路断开,然后试图通过该 S L I P链路发送分组给任何指定 s u n为默 认路由器的主机。 较老版本的 BSD产生一个主机不可达或者网络不可达差错,这取决于目的端是否 处于一个局域子网上。4.4 BSD只产生主机不可达差错。 我们在上一节通过在路由器 s u n上运行n e t s t a t命令可以看到,当接通 S L I P链路启动时 就要在路由表中增加一项使用 S L I P链路的表项,而当断开 S L I P链路时则删除该表项。这说明 当S L I P链路断开时, s u n的路由表中就没有默认项了。但是我们不想改变网络上其他主机的 路由表,即同时删除它们的默认路由。相反,对于 s u n 不能转发的分组,我们对它产生的 ICMP主机不可达差错报文进行计数。 在主机 s v r 4上运行 p i n g程序就可以看到这一点,它在拨号 S L I P链路的另一端(拨号链 路已被断开):

键入中断键停止显示

在主机bsdi上运行tcpdump命令的输出如图 9-2所示。

图9-2 响应ping 命令的ICMP主机不可达报文

当路由器 s u n发现找不到能到达主机 g e m i n i的路由时,它就响应一个主机不可达的回显 请求报文。 如果把SLIP链路接到Internet上,然后试图ping一个与Internet没有连接的IP地址,那么应该会 产生差错。但令人感兴趣的是,我们可以看到在返回差错报文之前,分组要在Internet上传送多远: 该IP地址没有连接到Internet上

从图8-5可以看出,在发现该 IP地址是无效的之前,该分组已通过了 6个路由器。只有当它 到达N S F N E T骨干网的边界时才检测到差错。这说明, 6个路由器之所以能转发分组是因为路 由表中有默认项。只有当分组到达 N S F N E T骨干网时,路由器才能知道每个连接到 I n t e r n e t上 的每个网络的信息。这说明许多路由器只能在局部范围内工作。 参考文献[Ford, Rekhter, and Braun 1993]定义了顶层选路域( top-level routing domain), 由它来维护大多数 I n t e r n e t网站的路由信息,而不使用默认路由。他们指出,在 I n t e r n e t上存在

89

第9章 IP路由选择使用

下载

5个这样的顶层选路域: N S F N E T主干网、商业互联网交换( Commercial Internet Exchange: C I X)、N A S A科学互联网( NASA Science Internet: NSI )、S p r i n t L i n k以及欧洲 I P主干网 (EBONE)。

9.4

转发或不转发 前面我们已经提过几次,一般都假定主机不转发 I P数据报,除非对它们进行特殊配置而

作为路由器使用。如何进行这样的配置呢? 大多数伯克利派生出来的系统都有一个内核变量 i p f o r w a r d i n g,或其他类似的名字 (参见附录E)。一些系统(如 BSD/386和SVR4)只有在该变量值不为 0的情况下才转发数据报。 SunOS 4.1.x允许该变量可以有三个不同的值:- 1表示始终不转发并且始终不改变它的值; 0表 示默认条件下不转发,但是当打开两个或更多个接口时就把该值设为

1;1表示始终转发。

Solaris 2.x把这三个值改为 0(始终不转发)、1(始终转发)和 2(在打开两个或更多个接口时 才转发)。 较早版本的 4 . 2 B S D主机在默认条件下可以转发数据报,这给没有进行正确配置的系统带 来了许多问题。这就是内核选项为什么要设成默认的“始终不转发”的原因,除非系统管理 员进行特殊设置。

9.5

ICMP重定向差错 当I P数据报应该被发送到另一个路由器时,收到数据报的路由器就要发送 I C M P重定向差

错报文给 I P数据报的发送端。这在概念上是很简单的,正如图 9 - 3所示的那样。只有当主机可 以选择路由器发送分组的情况下,我们才可能看到 I C M P重定向报文(回忆我们在图 7 - 6中看 过的例子)。 1) 我们假定主机发送一份 I P数据报给 R 1。这种选路决策经常发生,因为 R 1是该主机的默 认路由。 2) R1收到数据报并且检查它的路由表,发现 R 2是发送该数据报的下一站。当它把数据报 发送给R 2时,R 1检测到它正在发送的接口与数据报到达接口是相同的(即主机和两个路由器 所在的LAN)。这样就给路由器发送重定向报文给原始发送端提供了线索。 3) R1 发送一份ICMP重定向报文给主机,告诉它以后把数据报发送给 R2而不是R1。 主机 (1) IP数据报

(3) ICMP重定向 (2) IP数据报 R1

R2

最终目标

图9-3 ICMP重定向的例子

90

使用TCP/IP详解,卷1:协议

下载

重定向一般用来让具有很少选路信息的主机逐渐建立更完善的路由表。主机启动时路由 表中可以只有一个默认表项(在图 9 - 3所示的例子中,为 R 1或R 2)。一旦默认路由发生差错, 默认路由器将通知它进行重定向,并允许主机对路由表作相应的改动。

I C M P重定向允许

T C P / I P主机在进行选路时不需要具备智能特性,而把所有的智能特性放在路由器端。显然, 在我们的例子中, R1和R2 必须知道有关相连网络的更多拓扑结构的信息,但是连在 LAN上的 所有主机在启动时只需一个默认路由,通过接收重定向报文来逐步学习。 9.5.1 一个例子 可以在我们的网络上观察到 I C M P重定向的操作过程(见封二的图)。尽管在拓扑图中只 画出了三台主机( a i x , s o l a r i s和g e m i n i)和两台路由器( g a t e w a y和n e t b),但是整个 网络有超过 1 5 0台主机和 1 0台另外的路由器。大多数的主机都把 g a t e w a y指定为默认路由器, 因为它提供了Internet的入口。 子网1 4 0 . 2 5 2 . 1上的主机是如何访问作者所在子网(图中底下的四台主机)的呢?首先, 如果在 S L I P链路的一端只有一台主机,那么就要使用代理 A R P(4 . 6节)。这意味着位于拓扑 图顶部的子网(140.252.1)中的主机不需要其他特殊条件就可以访问主机 sun(140.252.1.29)。 位于netb上的代理ARP软件处理这些事情。 但是,当网络位于 S L I P链路的另一端时,就要涉及到选路了。一个办法是让所有的主机 和路由器都知道路由器 n e t b是网络 1 4 0 . 2 5 2 . 1 3的网关。这可以在每个主机的路由表中设置静 态路由,或者在每个主机上运行守护程序来实现。另一个更简单的办法(也是实际采用的方 法)是利用 ICMP重定向报文来实现。 在位于网络顶部的主机 s o l a r i s上运行 p i n g程序到主机 b s d i( 1 4 0 . 2 5 2 . 1 3 . 3 5 )。由于子 网号不相同,代理 A R P不能使用。假定没有安装静态路由,发送的第一个分组将采用到路由 器gateway的默认路由。下面是我们运行 ping程序之前的路由表:

(2 2 4 . 0 . 0 . 0所在的表项是 I P广播地址。我们将在第 1 2章讨论)。如果为 p i n g程序指定- v选项, 可以看到主机接收到的任何 ICMP报文。我们需要指定该选项以观察发送的重定向报文。

键入中断键停止显示

在收到 p i n g程序的第一个响应之前,主机先收到一份来自默认路由器 g a t e w a y发来的

91

第9章 IP路由选择使用

下载

I C M P重定向报文。如果这时查看路由表,就会发现已经插入了一个到主机

b s d i的新路由

(该表项如以下黑体字所示)。

这是我们第一次看到 D标志,表示该路由是被 ICMP重定向报文创建的。 G标志说明这是一份到 达g a t e w a y (n e t b)的间接路由, H标志则说明这是一个主机路由(正如我们期望的那样),而 不是一个网络路由。 由于这是一个被主机重定向报文增加的主机路由,因此它只处理到达主机 b s d i的报文。 如果我们接着访问主机 s v r 4,那么就要产生另一个 I C M P重定向报文,创建另一个主机路由。 类似地,访问主机 s l i p也创建另一个主机路由。位于子网上的三台主机( b s d i , s v r 4和 s l i p)还可以由一个指向路由器 s u n的网络路由来进行处理。但是 I C M P重定向报文创建的 是主机路由,而不是网络路由,这是因为在本例中,产生 I C M P重定向报文的路由器并不知道 位于140.252.13网络上的子网信息。 9.5.2 更多的细节 ICMP重定向报文的格式如图 9-4所示。 (5)类型

(0~3)代码

检验和 8字节

应该使用的路由器IP地址

IP首部(包括选项)+原始IP数据报中的数据前8字节

图9-4 ICMP重定向报文

有四种不同类型的重定向报文,有不同的代码值,如图 9-5所示。 I C M P重定向报文的接收者必须查看三个 I P地址: ( 1 )导致重定向的 I P地址(即 I C M P重定向报文的数据 位于IP数据报的首部); (2)发送重定向报文的路由器 的I P地址(包含重定向信息的 I P数据报中的源地址; (3)应该采用的路由器 IP地址(在ICMP报文中的4~7字

代码





网络重定向 主机重定向 服务类型和网络重定向 服务类型和主机重定向

图9-5 ICMP重定向报文的不同代码值

节)。 关于I C M P重定向报文有很多规则。首先,重定向报文只能由路由器生成,而不能由主机 生成。另外,重定向报文是为主机而不是为路由器使用的。假定路由器和其他一些路由器共 同参与某一种选路协议,则该协议就能消除重定向的需要(这意味着在图 9 - 1中的路由表应该

92

使用TCP/IP详解,卷1:协议

下载

消除或者能被选路守护程序修改,或者能被重定向报文修改,但不能同时被二者修改)。 在4.4BSD系统中,当主机作为路由器使用时,要进行下列检查。在生成 ICMP重定向报文 之前这些条件都要满足。 1) 出接口必须等于入接口。 2) 用于向外传送数据报的路由不能被 ICMP重定向报文创建或修改过,而且不能是路由器 的默认路由。 3) 数据报不能用源站选路来转发。 4) 内核必须配置成可以发送重定向报文。 内核变量取名为ip_sendredirects或其他类似的名字(参见附录E)。大多数当 前的系统(例如BSD、 SunOS 4.1.x、Solaris 2.x 及AIX 3.2.2)在默认条件下都设置该 变量,使系统可以发送重定向报文。其他系统如SVR4则关闭了该项功能。 另外,一台 4.4BSD主机收到 ICMP重定向报文后,在修改路由表之前要作一些检查。这是 为了防止路由器或主机的误操作,以及恶意用户的破坏,导致错误地修改系统路由表。 1) 新的路由器必须直接与网络相连接。 2) 重定向报文必须来自当前到目的地所选择的路由器。 3) 重定向报文不能让主机本身作为路由器。 4) 被修改的路由必须是一个间接路由。 关于重定向最后要指出的是,路由器应该发送的只是对主机的重定向(代码 1或3,如图 9 - 5所示),而不是对网络的重定向。子网的存在使得难于准确指明何时应发送对网络的重定 向而不是对主机的重定向。只当路由器发送了错误的类型时,一些主机才把收到的对网络的 重定向当作对主机的重定向来处理。

9.6

ICMP路由器发现报文 在本章前面已提到过一种初始化路由表的方法,即在配置文件中指定静态路由。这种方

法经常用来设置默认路由。另一种新的方法是利用 ICMP路由器通告和请求报文。 一般认为,主机在引导以后要广播或多播传送一份路由器请求报文。一台或更多台路由 器响应一份路由器通告报文。另外,路由器定期地广播或多播传送它们的路由器通告报文, 允许每个正在监听的主机相应地更新它们的路由表。 RFC 1256 [Deering 1991]确定了这两种ICMP报文的格式。ICMP路由器请求报文的格式如 图9-6所示。ICMP路由器通告报文的格式如图 9-7所示。 路由器在一份报文中可以通告多个地址。地址数指的是报文中所含的地址数。地址项大 小指的是每个路由器地址 32 bit 字的数目,始终为 2。生存期指的是通告地址有效的时间(秒 数)。

类型(10)

代码(0)

检验和 8字节 未用(置为0发送)

图9-6 ICMP路由器请求报文格式

93

第9章 IP路由选择使用

下载 类型(9)

代码(0)

检验和 8字节

地址数

地址项长度(2)

生存时间

路由器地址[1] 优先级[1] 路由器地址[2] 优先级[2]

图9-7 ICMP路由器通告报文格式

接下来是一对或多对 I P地址和优先级。 I P地址必须是发送路由器的某个地址。优先级是 一个有符号的 32 bit整数,指出该 I P地址作为默认路由器地址的优先等级,这是与子网上的其 他路由器相比较而言的。值越大说明优先级越高。优先级为 0 x 8 0 0 0 0 0 0 0说明对应的地址不能 作为默认路由器地址使用,尽管它也包含中通告报文中。优先级的默认值一般为 0。 9.6.1 路由器操作 当路由器启动时,它定期在所有广播或多播传送接口上发送通告报文。准确地说,这些 通告报文不是定期发送的,而是随机传送的,以减小与子网上其他路由器发生冲突的概率。 一般每两次通告间隔 450秒和600秒。一份给定的通告报文默认生命周期是 30分钟。 使用生命周期域的另一个时机是当路由器上的某个接口被关闭时。在这种情况下,路由 器可以在该接口上发送最后一份通告报文,并把生命周期值设为 0。 除了定期发送主动提供的通告报文以外,路由器还要监听来自主机的请求报文,并发送 路由器通告报文以响应这些请求报文。 如果子网上有多台路由器,由系统管理员为每个路由器设置优先等级。例如,主默认路 由器就要比备份路由器具有更高的优先级。 9.6.2 主机操作 主机在引导期间一般发送三份路由器请求报文,每三秒钟发送一次。一旦接收到一个有 效的通告报文,就停止发送请求报文。 主机也监听来自相邻路由器的请求报文。这些通告报文可以改变主机的默认路由器。另 外,如果没有接收到来自当前默认路由器的通告报文,那么默认路由器会超时。 只要有一般的默认路由器,该路由器就会每隔 1 0分钟发送通告报文,报文的生命周期是 30分钟。这说明主机的默认表项是不会超时的,即使错过一份或两份通告报文。 9.6.3 实现 路由器发现报文一般由用户进程(守护程序)创建和处理。这样,在图 9 - 1中就有另一个

94

使用TCP/IP详解,卷1:协议

下载

修改路由表的程序,尽管它只增加或删除默认表项。守护程序必须把它配置成一台路由器或 主机来使用。 这两种ICMP报文是新加的,不是所有的系统都支持它们。在我们的网络中,只有 Solaris 2.x支持这两种报文(in.rdisc守护程序)。尽管RFC建议尽可能用IP多播传送, 但是路由器发现还可以利用广播报文来实现。

9.7 小结 IP路由操作对于运行 TCP/IP的系统来说是最基本的,不管是主机还是路由器。路由表项 的内容很简单,包括: 5 bit标志、目的 I P地址(主机、网络或默认)、下一站路由器的 I P地址 (间接路由)或者本地接口的 I P地址(直接路由)及指向本地接口的指针。主机表项比网络表 项具有更高的优先级,而网络表项比默认项具有更高的优先级。 系统产生的或转发的每份 I P数据报都要搜索路由表,它可以被路由守护程序或 I C M P重定 向报文修改。系统在默认情况下不转发数据报,除非进行特殊的配置。用 r o u t e命令可以进 入静态路由,可以利用新 I C M P路由器发现报文来初始化默认表项,并进行动态修改。主机在 启动时只有一个简单的路由表,它可以被来自默认路由器的 ICMP重定向报文动态修改。 在本章中,我们集中讨论了单个系统是如何利用路由表的。在下一章,我们将讨论路由 器之间是如何交换路由信息的。

习题 9.1 为什么你认为存在两类 ICMP重定向报文 — 网络和主机? 9.2 在9 . 4节开头列出的 s v r 4主机上的路由表中,到主机 s l i p(1 4 0 . 2 5 2 . 1 3 . 6 5)的特定路由 是必需的吗?如果把这一项从路由表中删除会有什么变化? 9.3 考虑有一电缆连接4.2BSD主机和4.3BSD主机。假定网络号是140.1。4.2BSD主机把主机号为 全0的地址识别为广播地址(140.1.0.0),而4.3BSD通常使用全1的主机号(140.1.255.255)发 送广播。另外,4.2BSD主机在默认条件下要尽力转发接收到的数据报,尽管它们只有一个 接口。 请描述当4.2BSD主机收到一份目的地址为 140.1.255.255的IP数据报时会发生什么事。 9.4 继续前一个习题,假定有人在子网 140.1上的某个系统 ARP高速缓存中增加了一项(用 arp 命令)内容,指定 I P地址1 4 0 . 1 . 2 5 5 . 2 5 5对应的以太网地址为全 1(以太网广播地址)。请 描述此时发生的情况。 9.5 检查你所使用的系统上的路由表,并解释每一项内容。

下载

第10章 动态选路协议 10.1 引言 在前面各章中,我们讨论了静态选路。在配置接口时,以默认方式生成路由表项(对于 直接连接的接口),并通过 route命令增加表项(通常从系统自引导程序文件),或是通过 ICMP 重定向生成表项(通常是在默认方式出错的情况下)。 在网络很小,且与其他网络只有单个连接点且没有多余路由时(若主路由失败,可以使 用备用路由),采用这种方法是可行的。如果上述三种情况不能全部满足,通常使用动态选 路。 本章讨论动态选路协议,它用于路由器间的通信。我们主要讨论 R I P,即选路信息协议 (Routing Infromation Protocol),大多数 T C P / I P实现都提供这个应用广泛的协议。然后讨论两 种新的选路协议, O S P F和B G P。本章的最后研究一种名叫无分类域间选路的新的选路技术, 现在Internet上正在开始采用该协议以保持 B类网络的数量。

10.2 动态选路 当相邻路由器之间进行通信,以告知对方每个路由器当前所连接的网络,这时就出现了 动态选路。路由器之间必须采用选路协议进行通信,这样的选路协议有很多种。路由器上有 一个进程称为路由守护程序( routing daemon),它运行选路协议,并与其相邻的一些路由器 进行通信。正如图 9 - 1所示,路由守护程序根据它从相邻路由器接收到的信息,更新内核中的 路由表。 动态选路并不改变我们在 9 . 2节中所描述的内核在 I P层的选路方式。这种选路方式称为选 路机制( routing mechanism)。内核搜索路由表,查找主机路由、网络路由以及默认路由的方 式并没有改变。仅仅是放置到路由表中的信息改变了 — 当路由随时间变化时,路由是由路 由守护程序动态地增加或删除,而不是来自于自引导程序文件中的 route命令。 正如前面所描述的那样,路由守护程序将选路策略( routing policy)加入到系统中,选 择路由并加入到内核的路由表中。如果守护程序发现前往同一信宿存在多条路由,那么它 (以某种方法)将选择最佳路由并加入内核路由表中。如果路由守护程序发现一条链路已经断 开(可能是路由器崩溃或电话线路不好),它可以删除受影响的路由或增加另一条路由以绕过 该问题。 在像I n t e r n e t这样的系统中,目前采用了许多不同的选路协议。 I n t e r n e t是以一组自治系统 (AS,Autonomous System)的方式组织的,每个自治系统通常由单个实体管理。常常将一个公 司或大学校园定义为一个自治系统。 N S F N E T的I n t e r n e t骨干网形成一个自治系统,这是因为 骨干网中的所有路由器都在单个的管理控制之下。 每个自治系统可以选择该自治系统中各个路由器之间的选路协议。这种协议我们称之为 内部网关协议IGP(Interior Gateway Protocol)或域内选路协议(intradomain routing protocol)。

96

使用TCP/IP详解,卷1:协议

下载

最常用的 I G P是选路信息协议 R I P。一种新的 I G P是开放最短路径优先 O S P F(Open Shortest Path First)协议。它意在取代 R I P。另一种 1 9 8 6年在原来 N S F N E T骨干网上使用的较早的 I G P 协议 — HELLO,现在已经不用了。 新的RFC [Almquist 1993]规定,实现任何动态选路协议的路由器必须同时支持 OSPF和RIP,还可以支持其他IGP协议。 外部网关协议 E G P(Exterier Gateway Protocol)或域内选路协议的分隔选路协议用于不 同自治系统之间的路由器。在历史上,(令人容易混淆)改进的 E G P有着一个与它名称相同的 协议:EGP。新EGP是当前在 NSFNET骨干网和一些连接到骨干网的区域性网络上使用的是边 界网关协议 BGP(Border Gateway Protocol)。BGP意在取代EGP。

10.3 Unix选路守护程序 Unix系统上常常运行名为 r o u t e d路由守护程序。几乎在所有的 TCP/IP实现中都提供该程 序。该程序只使用 RIP进行通信,我们将在下一节中讨论该协议。这是一种用于小型到中型网 络中的协议。 另一个程序是 g a t e d。I G P和E G P都支持它。 [Fedor 1998]描述了早期开发的 g a t e d。图 10-1对r o u t e d和两种不同版本的 g a t e d所支持的不同选路协议进行了比较。大多数运行路由 守护程序的系统都可以运行 routed,除非它们需要支持 gated所支持的其他协议。 内部网点协议

守护程序

外部网点协议

routed gated, 版本2 gated, 版本3

图10-1 routed 和gated 所支持的选路协议

我们在下一节中描述 RIP 版本1,1 0 . 5节描述它与 R I P版本2的不同点, 1 0 . 6节描述 O S P F, 10.7节描述BGP。

10.4 RIP:选路信息协议 本节对RIP进行了描述,这是因为它是最广为使用(也是最受攻击)的选路协议。对于 RIP 的正式描述文件是RFC 1058 [Hedrick 1988a],但是该RFC是在该协议实现数年后才出现的。 10.4.1 报文格式 RIP报文包含中在UDP数据报中,如图10-2所示(在第11章中对UDP进行更为详细的描述)。 图1 0 - 3给出了使用 I P地址时的 R I P报文

IP数据报

格式。

UDP数据报

命令字段为1表示请求,2表示应答。还 有两个舍弃不用的命令( 3和4),两个非正 式的命令:轮询( 5)和轮询表项( 6)。请 求表示要求其他系统发送其全部或部分路由

首部

首部

20字节

8字节

RIP报文

图10-2 封装在UDP数据报中的RIP报文

97

第10章 动态选路协议使用

下载 表。应答则包含发送者全部或部分路由表。

版本字段通常为 1,而第2版RIP(10.5节)将此字段设置为 2。 紧跟在后面的 2 0字节指定地址系列( address family)(对于I P地址来说,其值是 2)、I P地 址以及相应的度量。在本节的后面可以看出, RIP的度量是以跳计数的。 采用这种 2 0字节格式的 R I P报文可以通告多达 2 5条路由。上限 2 5是用来保证 R I P报文的总 长度为2 0×25 + 4 = 504,小于5 1 2字节。由于每个报文最多携带 2 5个路由,因此为了发送整 个路由表,经常需要多个报文。 命令

版本

(必须为0)

地址系列(2)

(必须为0) 32位IP地址 (必须为0)

字节

(必须为0) 度量(1-16)

(最多可有24个另外的路由,与前20字节具有相同的格式)

图 10-3

10.4.2 正常运行 让我们来看一下采用RIP协议的routed程序正常运行的结果。RIP常用的UDP端口号是520。 • 初始化:在启动一个路由守护程序时,它先判断启动了哪些接口,并在每个接口上发送 一个请求报文,要求其他路由器发送完整路由表。在点对点链路中,该请求是发送给其 他终点的。如果网络支持广播的话,这种请求是以广播形式发送的。目的 U D P端口号是 520(这是其他路由器的路由守护程序端口号)。 这种请求报文的命令字段为 1,但地址系列字段设置为 0,而度量字段设置为 1 6。这是一 种要求另一端完整路由表的特殊请求报文。 • 接收到请求。如果这个请求是刚才提到的特殊请求,那么路由器就将完整的路由表发送 给请求者。否则,就处理请求中的每一个表项:如果有连接到指明地址的路由,则将度 量设置成我们的值,否则将度量置为 16(度量为16是一种称为“无穷大”的特殊值,它 意味着没有到达目的的路由)。然后发回响应。 • 接收到响应。使响应生效,可能会更新路由表。可能会增加新表项,对已有的表项进行 修改,或是将已有表项删除。 • 定期选路更新。每过 30秒,所有或部分路由器会将其完整路由表发送给相邻路由器。发 送路由表可以是广播形式的(如在以太网上),或是发送给点对点链路的其他终点的。

98

使用TCP/IP详解,卷1:协议

下载

• 触发更新。每当一条路由的度量发生变化时,就对它进行更新。不需要发送完整路由表, 而只需要发送那些发生变化的表项。 每条路由都有与之相关的定时器。如果运行 R I P的系统发现一条路由在 3分钟内未更新, 就将该路由的度量设置成无穷大( 1 6),并标注为删除。这意味着已经在 6个3 0秒更新时间里 没收到通告该路由的路由器的更新了。再过 6 0秒,将从本地路由表中删除该路由,以保证该 路由的失效已被传播开。 10.4.3 度量 R I P所使用的度量是以跳 ( h o p )计算的。所有直接连接接口的跳数为 1。考虑图 1 0 - 4所示的 路由器和网络。画出的 4条虚线是广播 R I P 报文。 路由器 R 1通过发送广播到 N 1通告它与

经过R2到N3的一 条跳数为2的路由

N 2之间的跳数是 1(发送给 N 1的广播中通 告它与N1之间的路由是无用的)。同时也通

经过R1到N1的一 条跳数为2的路由

过发送广播给 N 2通告它与 N 1之间的跳数为 1。同样,R2通告它与 N2的度量为 1,与N3 的度量为1。 如果相邻路由器通告它与其他网络路

图10-4 路由器和网络示例

由的跳数为 1,那么我们与那个网络的度量就是 2,这是因为为了发送报文到该网络,我们必 须经过那个路由器。在我们的例子中, R2到N1的度量是2,与R1到N3的度量一样。 由于每个路由器都发送其路由表给邻站,因此,可以判断在同一个自治系统 A S内到每个 网络的路由。如果在该 A S内从一个路由器到一个网络有多条路由,那么路由器将选择跳数最 小的路由,而忽略其他路由。 跳数的最大值是 1 5,这意味着 R I P只能用在主机间最大跳数值为 1 5的A S内。度量为 1 6表 示到无路由到达该 IP地址。 10.4.4 问题 这种方法看起来很简单,但它有一些缺陷。首先, R I P没有子网地址的概念。例如,如果 标准的B类地址中16 bit的主机号不为0,那么RIP无法区分非零部分是一个子网号,或者是一个 主机地址。有一些实现中通过接收到的RIP信息,来使用接口的网络掩码,而这有可能出错。 其次,在路由器或链路发生故障后,需要很长的一段时间才能稳定下来。这段时间通常 需要几分钟。在这段建立时间里,可能会发生路由环路。在实现 RIP时,必须采用很多微妙的 措施来防止路由环路的出现,并使其尽快建立。 RFC 1058 [Hedrick 1988a]中指出了很多实现 RIP的细节。 采用跳数作为路由度量忽略了其他一些应该考虑的因素。同时,度量最大值为 1 5则限制 了可以使用 RIP的网络的大小。 10.4.5 举例 我们将使用r i p q u e r y程序来查询一些路由器中的路由表,该程序可以从 g a t e d中得到。

99

第10章 动态选路协议使用

下载

r i p q u e r y程序通过发送一个非正式请求(图 1 0 - 3中命令字段为 5的“p o l l”)给路由器,要求 得到其完整的路由表。如果在 5秒内未收到响应,则发送标准的 R I P请求(c o m m a n d字段为 1) (前面提到过的,将地址系列字段置为 0,度量字段置为 1 6的请求,要求其他路由器发送其完 整路由表)。 图1 0 - 5 给出了将从 s u n 主机上查询其路由表的两个路由器。如果在主机

s u n 上执行

ripquery程序,以得到其下一站路由器 netb的选路信息,那么可以得到下面的结果: sun % r i p q u e r y - n n e t b 5 0 4 b y t e s f r o m n e t b ( 1 4 0 . 2 5 2 . 1 . 1 8 3 ) : 第一份报文包含 5 0 4字节 这里删除了许多行 140.252.1.0, metric 1 图1 0 - 5中上面的以太网 140.252.13.0, metric 1 图1 0 - 5中下面的以太网 2 4 4 b y t e s f r o m n e t b ( 1 4 0 . 2 5 2 . 1 . 1 8 3 ) : 第二份报文包含剩下的 2 4 4字节下面删除了许多行

正如我们所猜想的那样, n e t b告诉我们子网的度量为 1。另外,与 n e t b相连的位于机端 的以太网( 1 4 0 . 2 5 2 . 1 . 0)的m e t r i c也是1(- n参数表示 直接打印 I P 地址而不需要去查看其域名)。在本例中, 将n e t b配置成认为所有位于 1 4 0 . 2 5 2 . 1 3子网的主机都与 其 直 接 相 连 — 即, n e t b 并不知道哪些主机真正与 1 4 0 . 2 5 2 . 1 3子网相连。由于与 1 4 0 . 2 5 2 . 1 3子网只有一个 连接点,因此,通告每个主机的度量实际上没有太大意 义。 图1 0 - 6给出了使用 t c p d u m p交换的报文。采用 - i s10选项指定SLIP接口。 第1个请求发出一个 RIP轮询命令(第1行)。这个请 求在 5秒后超时,发出一个常规的 R I P请求(第 2行)。 第1行和第 2行最后的 2 4表示请求报文的长度: 4个字节

图10-5 查询其路由表内容的两个

的R I P首部(包括命令和版本),然后是单个 2 0字节的

路由器netb 和gateway

地址和度量。 第3行是第一个应答报文。该行最后的 2 5表示包含了2 5个地址和度量对,我们在前面已经 计算过,其字节数为 504。这是上面的ripquery程序所打印出来的结果。我们为 tcpdump程 序指定 - s 6 0 0选项,以让它从网络中读取 6 0 0个字节。这样,它可以接收整个 U D P数据报(而 不是报文的前半部),然后打印出RIP响应的内容。该输出结果省略了。

图10-6 运行ripquery 程序的tcpdump 输出结果

第4行是来自路由器的第二个响应报文,它包含后面的 1 2个地址和度量对。可以计算出该 报文的长度为12×20 + 4=244,这正是 ripquery程序所打印出来的结果。 如果越过 n e t b路由器,到 g a t e w a y,那么可以预测到我们子网( 1 4 0 . 2 5 2 . 1 3 . 0)的度量 为2。可以运行下面的命令来进行验证:

100

使用TCP/IP详解,卷1:协议

下载

sun % ripquery -n gateway 504 bytes from gateway (140.252.1.4): 140.252.1.0, metric 1 140.252.13.0, metric 2

这里删除了许多行 图1 0 - 5上面的以太网 图1 0 - 5下面的以太网

这里,位于图 1 0 - 5上面的以太网( 1 4 0 . 2 5 2 . 1 . 0)的度量依然是 1,这是因为该以太网直接 与gateway和netb相连。而我们的子网 140.252.13.0正如预想的一样,其度量为 2。 10.4.6 另一个例子 现在察看以太网上所有非主动请求的RIP更新,以看一看RIP定期给其邻站发送的信息。图10-7 是noao.edu网络的多种排列情况。为了简化,我们不用本文其他地方所采用的路由器表示方式, 而以Rn来代表路由器,其中n是子网号。以虚线表示点对点链路,并给出了这些链路对端的IP地址。

图10-7 noao.edu 140.252的多个网络

在主机s o l a r i s上运行 Solaris 2.x的s n o o p程序,它与 t c p d u m p相类似。我们可以在不 需要超用户权限的条件下运行该程序,但它只捕获广播报文、多播报文以及发送给主机的报 文。图10-8给出了在60秒内所捕获的报文。在这里,我们将大部分正式的主机名以 Rn来表示。 -P标志以非混杂模式捕获报文,- tr打印出相应的时戳,而 u d p

port

5只捕获信源 20

或信宿端口号为 520的UDP数据报。 来自R6、R4、R2、R7、R8和R3的前6个报文,每个报文只通告一个网络。查看这些报文, 可以发现 R 2通告前往 1 4 0 . 2 5 2 . 6 . 0的跳数为 1的一条路由, R 4通告前往 1 4 0 . 2 5 2 . 4 . 0的跳数为 1的 一条路由,等等。 但是,g a t e w a y路由器却通告了 15条路由。我们可以通过运行 s n o o p程序时加上-v参数 来查看R I P报文的全部内容(这个标志输出全部报文的全部内容:以太网首部、 I P首部、U D P 首部以及RIP报文。我们只保留了 RIP信息而删除了其他信息)。图10-9给出了输出结果。

101

第10章 动态选路协议使用

下载

图10-8 solaris 在60秒内所捕获到的RIP广播报文

把这些子网 140.252.1上通告报文经过的路由与图 10-7中的拓扑结构进行比较。 使人迷惑不解的一个问题是为什么图 1 0 - 8输出结果中, R 1 0通告其有 4个网络而在图 1 0 - 7 中显示的只有3个。如果查看带 snoop的RIP报文,就会得到以下通告路由: R I P: A d d r e s s RIP: 140.251.0.0 RIP: 140.252.9.0 RIP: 140.252.10.0 RIP: 140.252.11.0

Metric 16 (not reachable) 1 1 1

前往B类网络140.251的路由是假的,不应该通告它(它属于其他机构而不是 noao.edu)。 删去许多行

图10-9 来自gateway 的RIP响应

图10-8中,对于 R10发送的RIP报文,s n o o p输出“BROADCAST”符号,它表示目的 IP 地址是有限的广播地址 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5(1 2 . 2节),而不是其他路由器用来指向子网的广播地

102

使用TCP/IP详解,卷1:协议

下载

址(140.252.1.255)。

10.5 RIP版本2 RFC 1388 [Malkin 1993a]中对R I P定义进行了扩充,通常称其结果为 R I P - 2。这些扩充并 不改变协议本身,而是利用图 10-3中的一些标注为“必须为 0”的字段来传递一些额外的信息。 如果RIP忽略这些必须为 0的字段,那么, RIP和RIP-2可以互操作。 图10-10重新给出了由RIP-2定义的图。对于 RIP-2来说,其版本字段为 2。 版本(2)

命令(1-6)

路由域 路由标记

地址类(2) 32位IP地址 32位子网掩码 32位下一站IP地位 度量(1-16)

(最多超过24个路由,与前20字节具有相同的格式) 20字节

图10-10 RIP-2报文格式

选路域 (routing domain)是一个选路守护程序的标识符,它指出了这个数据报的所有者。 在一个 U n i x实现中,它可以是选路守护程序的进程号。该域允许管理者在单个路由器上运行 多个RIP实例,每个实例在一个选路域内运行。 选路标记 (routing tag)是为了支持外部网关协议而存在的。它携带着一个 E G P和B G P的自 治系统号。 每个表项的子网掩码应用于相应的 IP地址上。下一站 IP地址指明发往目的 IP地址的报文该 发往哪里。该字段为 0意味着发往目的地址的报文应该发给发送 RIP报文的系统。 R I P - 2提供了一种简单的鉴别机制。可以指定 R I P报文的前 2 0字节表项地址系列为 0 x ffff, 路由标记为 2。表项中的其余 16字节包含一个明文口令。 最后, R I P - 2除了广播(第 1 2章)外,还支持多播。这可以减少不收听 R I P - 2报文的主机 的负载。

10.6 OSPF:开放最短路径优先 O S P F是除 R I P外的另一个内部网关协议。它克服了 R I P的所有限制。 RFC 1247 [Moy 1991]中对第2版OSPF进行了描述。 与采用距离向量的 R I P协议不同的是, O S P F是一个链路状态协议。距离向量的意思是, R I P发送的报文包含一个距离向量(跳数)。每个路由器都根据它所接收到邻站的这些距离向

下载

103

第10章 动态选路协议使用

量来更新自己的路由表。 在一个链路状态协议中,路由器并不与其邻站交换距离信息。它采用的是每个路由器主 动地测试与其邻站相连链路的状态,将这些信息发送给它的其他邻站,而邻站将这些信息在 自治系统中传播出去。每个路由器接收这些链路状态信息,并建立起完整的路由表。 从实际角度来看,二者的不同点是链路状态协议总是比距离向量协议收敛更快。收敛的 意思是在路由发生变化后,例如在路由器关闭或链路出故障后,可以稳定下来。

[Perlman

1992]的9.3节对这两种类型的选路协议的其他方面进行了比较。 O S P F与R I P(以及其他选路协议)的不同点在于, O S P F直接使用 I P。也就是说,它并不 使用UDP或TCP。对于IP首部的protocol字段,OSPF有其自己的值(图 3-1)。 另外,作为一种链路状态协议而不是距离向量协议, OSPF还有着一些优于 RIP的特点。 1) OSPF可以对每个 I P服务类型(图 3 - 2)计算各自的路由集。这意味着对于任何目的, 可以有多个路由表表项,每个表项对应着一个 IP服务类型。 2) 给每个接口指派一个无维数的费用。可以通过吞吐率、往返时间、可靠性或其他性能 来进行指派。可以给每个 IP服务类型指派一个单独的费用。 3) 当对同一个目的地址存在着多个相同费用的路由时,OSPF在这些路由上平均分配流量。 我们称之为流量平衡。 4) OSPF支持子网:子网掩码与每个通告路由相连。这样就允许将一个任何类型的 I P地址 分割成多个不同大小的子网(我们在 3 . 7节中给出了这样的一个例子,称之为变长度子网)。 到一个主机的路由是通过全 1子网掩码进行通告的。默认路由是以 I P地址为 0 . 0 . 0 . 0、网络掩码 为全0进行通告的。 5) 路由器之间的点对点链路不需要每端都有一个 I P地址,我们称之为无编号网络。这样 可以节省IP地址 — 现在非常紧缺的一种资源。 6) 采用了一种简单鉴别机制。可以采用类似于 R I P - 2机制(1 0 . 5节)的方法指定一个明文 口令。 7) OSPF采用多播(第12章),而不是广播形式,以减少不参与 OSPF的系统负载。 随着大部分厂商支持 OSPF,在很多网络中 OSPF将逐步取代 RIP。

10.7 BGP:边界网关协议 B G P是一种不同自治系统的路由器之间进行通信的外部网关协议。 B G P是ARPANET所使 用的老EGP的取代品。 RFC1267 [Lougheed and Rekhter 1991] 对第3版的BGP进行了描述。 RFC 1268 [Rekhter and Gross 1991] 描述了如何在Internet中使用BGP。下面对于 BGP的大 部分描述都来自于这两个 R F C文档。同时, 1 9 9 3年开发第 4版的B G P(见RFC 1467 [To p o l c i c 1993]),以支持我们将在 10.8节描述的CIDR。 B G P系统与其他 B G P系统之间交换网络可到达信息。这些信息包括数据到达这些网络所 必须经过的自治系统 A S中的所有路径。这些信息足以构造一幅自治系统连接图。然后,可以 根据连接图删除选路环,制订选路策略。 首先,我们将一个自治系统中的 I P数据报分成本地流量和通过流量。在自治系统中,本 地流量是起始或终止于该自治系统的流量。也就是说,其信源 I P地址或信宿 I P地址所指定的 主机位于该自治系统中。其他的流量则称为通过流量。在 I n t e r n e t中使用 B G P的一个目的就是

104

使用TCP/IP详解,卷1:协议

下载

减少通过流量。 可以将自治系统分为以下几种类型: 1) 残桩自治系统(stub AS),它与其他自治系统只有单个连接。 stub AS只有本地流量。 2) 多接口自治系统 (multihomed AS),它与其他自治系统有多个连接,但拒绝传送通过流 量。 3) 转送自治系统 (transit AS),它与其他自治系统有多个连接,在一些策略准则之下,它 可以传送本地流量和通过流量。 这样,可以将 I n t e r n e t的总拓扑结构看成是由一些残桩自治系统、多接口自治系统以及转 送自治系统的任意互连。残桩自治系统和多接口自治系统不需要使用 B G P——它们通过运行 EGP在自治系统之间交换可到达信息。 B G P允许使用基于策略的选路。由自治系统管理员制订策略,并通过配置文件将策略指 定给B G P。制订策略并不是协议的一部分,但指定策略允许 B G P实现在存在多个可选路径时 选择路径,并控制信息的重发送。选路策略与政治、安全或经济因素有关。 BGP与RIP和OSPF的不同之处在于 BGP使用TCP作为其传输层协议。两个运行 BGP的系统 之间建立一条 T C P连接,然后交换整个 B G P路由表。从这个时候开始,在路由表发生变化时, 再发送更新信号。 B G P是一个距离向量协议,但是与(通告到目的地址跳数的) R I P不同的是,B G P列举了 到每个目的地址的路由(自治系统到达目的地址的序列号)。这样就排除了一些距离向量协议 的问题。采用16 bit 数字表示自治系统标识。 BGP通过定期发送keepalive报文给其邻站来检测 TCP连接对端的链路或主机失败。两个报 文之间的时间间隔建议值为 3 0秒。应用层的 k e e p a l i v e报文与 T C P的k e e p a l i v e选项(第 2 3章) 是独立的。

10.8 CIDR:无类型域间选路 在第3章中,我们指出了 B类地址的缺乏,因此现在的多个网络站点只能采用多个 C类网 络号,而不采用单个 B类网络号。尽管分配这些 C类地址解决了一个问题( B类地址的缺乏), 但它却带来了另一个问题:每个 C类网络都需要一个路由表表项。无类型域间选路( C I D R) 是一个防止 I n t e r n e t路由表膨胀的方法,它也称为超网( s u p e r n e t t i n g)。在RFC 1518 [Rekher and Li 1993] 和RFC 1519 [Fuller et al. 1993]中对它进行了描述,而 [Ford, Rekhter, and Braun 1993]是它的综述。CIDR有一个Internet Architecture Board’s blessing [Huitema 1993]。RFC 1467 [Topolcic 1993] 对Internet中CIDR的开发状况进行了小结。 C I D R的基本观点是采用一种分配多个 I P地址的方式,使其能够将路由表中的许多表项总 和( s u m m a r i z a t i o n )成更少的数目。例如,如果给单个站点分配 1 6个C类地址,以一种可以用总 和的方式来分配这 1 6个地址,这样,所有这 1 6个地址可以参照 I n t e r n e t上的单个路由表表项。 同时,如果有 8个不同的站点是通过同一个 I n t e r n e t服务提供商的同一个连接点接入 I n t e r n e t的, 且这8个站点分配的8个不同IP地址可以进行总和,那么,对于这 8个站点,在 Internet上,只需 要单个路由表表项。 要使用这种总和,必须满足以下三种特性: 1) 为进行选路要对多个 IP地址进行总和时,这些 IP地址必须具有相同的高位地址比特。

下载

105

第10章 动态选路协议使用

2) 路由表和选路算法必须扩展成根据 32 bit IP地址和32 bit掩码做出选路决策。 3) 必须扩展选路协议使其除了 32 bit地址外,还要有 32 bit掩码。OSPF(10.6节)和RIP-2 (10.5节)都能够携带第 4版BGP所提出的32 bit掩码。 例 如 , RFC 1466 [Gerich 1993] 建 议 欧 洲 新 的 C类 地 址 的 范 围 是 1 9 4 . 0 . 0 . 0~ 195.255.255.255。以16进制表示,这些地址的范围是 0xc2000000~0xc3ffffff。它代表了65536 个不同的 C类网络号,但它们地址的高 7 bit是相同的。在欧洲以外的国家里,可以采用 IP地址 为0xc2000000和32 bit 0xfe000000 (254.0.0.0) 为掩码的单个路由表表项来对所有这些 65536个 C类网络号选路到单个点上。 C类地址的后面各比特位(即在 1 9 4或1 9 5后面各比特)也可以进 行层次分配,例如以国家或服务提供商分配,以允许对在欧洲路由器之间使用除了这 32 bit掩 码的高7 bit 外的其他比特进行概括。 C I D R同时还使用一种技术,使最佳匹配总是最长的匹配:即在 32 bit 掩码中,它具有最 大值。我们继续采用上一段中所用的例子,欧洲的一个服务提供商可能会采用一个与其他欧 洲服务提供商不同的接入点。如果给该提供商分配的地址组是从 194.0.16.0到194.0.31.255 (16 个 C 类网络号 ) ,那么可能只有这些网络的路由表项的

I P地址是 1 9 4 . 0 . 1 6 . 0 ,掩码为

255.255.240.0 (0xfffff000)。发往194.0.22.1地址的数据报将同时与这个路由表表项和其他欧洲 C类地址的表项进行匹配。但是由于掩码 2 5 5 . 2 5 5 . 2 4 0比2 5 4 . 0 . 0 . 0更“长”,因此将采用具有更 长掩码的路由表表项。 “无类型”的意思是现在的选路决策是基于整个 32 bit IP 地址的掩码操作,而不管其 I P地 址是A类、B类或是C类,都没有什么区别。 CIDR最初是针对新的 C类地址提出的。这种变化将使 Internet路由表增长的速度缓慢下来, 但对于现存的选路则没有任何帮助。这是一个短期解决方案。作为一个长期解决方案,如果将 CIDR应用于所有IP地址,并根据各洲边界和服务提供商对已经存在的 IP地址进行重新分配(且 所有现有主机重新进行编址!),那么[Ford, Rekhter, and Braun 1993] 宣称,目前包含10 000网 络表项的路由表将会减少成只有200个表项。

10.9 小结 有两种基本的选路协议,即用于同一自治系统各路由器之间的内部网关协议( IGP)和用 于不同自治系统内路由器通信的外部网关协议( EGP)。 最常用的IGP是路由信息协议( RIP),而OSPF是一个正在得到广泛使用的新 IGP。一种新 近流行的 E G P是边界网关协议( B G P)。在本章中,我们讨论了 R I P及其交换的报文类型。第 2 版R I P是其最近的一个改进版,它支持子网,还有一些其他改进技术。同时也对 O S P F、B G P 和无类型域间选路( C I D R)进行了描述。 C I D R是一种新技术,可以减小 I n t e r n e t路由表的大 小。 你可能还会遇到一些其他的 O S I选路协议。域间选路协议( I D R P)最开始时,是一个为 了使用 O S I地址而不是 I P地址,而进行修改的 B G P版本。Intermediate System to Intermediate System 协议( I S - I S)是O S I的标准 I G P。可以用它来选路 C L N P(无连接网络协议),这是一 种与IP类似的OSI协议。IS-IS和OSPF相似。 动态选路仍然是一个网间互连的研究热点。对使用的选路协议和运行的路由守护程序进 行选择,是一项复杂的工作。 [Perlman 1992]提供了许多细节。

106

使用TCP/IP详解,卷1:协议

下载

习题 10.1 在图10-9中哪些路由是从路由器 kpno进入gateway的? 10.2 假设一个路由器要使用 RIP通告30个路由,这需要一个包含 25条路由和另一个包含 5条路 由的数据报。如果每过一个小时,第一个包含 25条路由的数据报丢失一次,那么其结果 如何? 10.3 OSPF报文格式中有一个检验和字段,而 RIP报文则没有此项,这是为什么? 10.4 像OSPF这样的负载平衡,对于传输层的影响是什么? 10.5 查阅RFC1058 关于实现 RIP的其他资料。在图 10-8中,140.252.1网络的每个路由器只通 告它所提供的路由,而它并不能通过其他路由器的广播中知道任何其他路由。这种技术 的名称是什么? 10.6 在3.4节中,我们说过除了图10-7中所示的8个路由器外,140.252.1子网上还有超过100个主 机。那么这100个主机是如何处理每30秒到达它们的8个广播信息呢(图10-8)?

下载

第11章 UDP:用户数据报协议 11.1 引言 U D P是一个简单的面向数据报的运输层协议:进程的每个输出操作都正好产生一个 U D P 数据报,并组装成一份待发送的 I P数据报。

IP数据报

这与面向流字符的协议不同,如 T C P,应用

UDP数据报

程序产生的全体数据与真正发送的单个 I P数 据报可能没有什么联系。 U D P数据报封装成一份 I P数据报的格式

首部

UDP 首部

20字节

8字节

如图11-1所示。

UDP数据

图11-1 UDP封装

RFC 768 [Postel 1980] 是U D P的正式规 范。

U D P不提供可靠性:它把应用程序传给 I P层的数据发送出去,但是并不保证它们能到达 目的地。由于缺乏可靠性,我们似乎觉得要避免使用 U D P而使用一种可靠协议如 T C P。我们 在第17章讨论完TCP后将再回到这个话题,看看什么样的应用程序可以使用 UDP。 应用程序必须关心 I P数据报的长度。如果它超过网络的 M T U(2 . 8节),那么就要对 I P数 据报进行分片。如果需要,源端到目的端之间的每个网络都要进行分片,并不只是发送端主 机连接第一个网络才这样做(我们在 2 . 9节中已定义了路径 M T U的概念)。在11 . 5节中,我们 将讨论IP分片机制。

11.2 UDP首部 UDP首部的各字段如图 11-2所示。 16位源端口号

16位目的端口号

16位UDP长度

16位UDP检验和

8字节

数据(如果有)

图11-2 UDP首部

端口号表示发送进程和接收进程。在图 1 - 8中,我们画出了 T C P和U D P用目的端口号来分 用来自IP层的数据的过程。由于 IP层已经把IP数据报分配给 TCP或UDP(根据IP首部中协议字 段值),因此T C P端口号由 T C P来查看,而 U D P端口号由 U D P来查看。 T C P端口号与 U D P端口 号是相互独立的。

108

使用TCP/IP详解,卷1:协议

下载

尽管相互独立,如果 TCP和UDP同时提供某种知名服务,两个协议通常选择相同 的端口号。这纯粹是为了使用方便,而不是协议本身的要求。 U D P长度字段指的是 U D P首部和U D P数据的字节长度。该字段的最小值为 8字节(发送一 份0字节的 U D P数据报是 O K)。这个 U D P长度是有冗余的。 I P数据报长度指的是数据报全长 (图3 - 1),因此U D P数据报长度是全长减去 I P首部的长度(该值在首部长度字段中指定,如图 3-1所示)。

11.3 UDP检验和 UDP检验和覆盖UDP首部和UDP数据。回想 IP首部的检验和,它只覆盖 IP的首部 — 并不 覆盖IP数据报中的任何数据。 UDP和TCP在首部中都有覆盖它们首部和数据的检验和。 UDP的检验和是可选的,而 TCP 的检验和是必需的。 尽管 U D P检验和的基本计算方法与我们在 3 . 2节中描述的 I P首部检验和计算方法相类似 (16 bit字的二进制反码和),但是它们之间存在不同的地方。首先, UDP数据报的长度可以为 奇数字节,但是检验和算法是把若干个 16 bit字相加。解决方法是必要时在最后增加填充字节 0,这只是为了检验和的计算(也就是说,可能增加的填充字节不被传送)。 其次, U D P数据报和 T C P段都包含一个 1 2字节长的伪首部,它是为了计算检验和而设置 的。伪首部包含 I P首部一些字段。其目的是让 U D P 两次检查数据是否已经正确到达目的地 (例如, I P没有接受地址不是本主机的数据报,以及 I P没有把应传给另一高层的数据报传给 UDP)。UDP数据报中的伪首部格式如图 11-3所示。

32位源IP地址 UDP伪首部

32位目的IP地址 0

8位协议(17)

16位UDP长度

16位源端口号

16位目的端口号

16位UDP长度

16位UDP检验和

UDP首部

数据

填充字节(0)

图11-3 UDP检验和计算过程中使用的各个字段

在该图中,我们特地举了一个奇数长度的数据报例子,因而在计算检验和时需要加上填 充字节。注意, UDP数据报的长度在检验和计算过程中出现两次。 如果检验和的计算结果为 0,则存入的值为全 1(6 5 5 3 5),这在二进制反码计算中是等效 的。如果传送的检验和为 0,说明发送端没有计算检验和。

109

第11章 UDP:用户数据报协议使用

下载

如果发送端没有计算检验和而接收端检测到检验和有差错,那么 U D P数据报就要被悄悄 地丢弃。不产生任何差错报文(当 IP层检测到IP首部检验和有差错时也这样做)。 U D P检验和是一个端到端的检验和。它由发送端计算,然后由接收端验证。其目的是为 了发现UDP首部和数据在发送端到接收端之间发生的任何改动。 尽管U D P检验和是可选的,但是它们应该总是在用。在 8 0年代,一些计算机产商在默认 条件下关闭 U D P检验和的功能,以提高使用 U D P协议的N F S(Network File System)的速度。 在单个局域网中这可能是可以接受的,但是在数据报通过路由器时,通过对链路层数据帧进 行循环冗余检验(如以太网或令牌环数据帧)可以检测到大多数的差错,导致传输失败。不 管相信与否,路由器中也存在软件和硬件差错,以致于修改数据报中的数据。如果关闭端到 端的U D P检验和功能,那么这些差错在 U D P数据报中就不能被检测出来。另外,一些数据链 路层协议(如SLIP)没有任何形式的数据链路检验和。 Host Requirements RFC声明,UDP检验和选项在默认条件下是打开的。它还声明, 如果发送端已经计算了检验和,那么接收端必须检验接收到的检验和(如接收到检验 和不为0)。但是,许多系统没有遵守这一点,只是在出口检验和选项被打开时才验证 接收到的检验和。 11.3.1 tcpdump输出 很难知道某个特定系统是否打开了 U D P检验和选项。应用程序通常不可能得到接收到的 U D P首部中的检验和。为了得到这一点,作者在 t c p d u m p程序中增加了一个选项,以打印出 接收到的UDP检验和。如果打印出的值为 0,说明发送端没有计算检验和。 测试网络上三个不同系统的输出如图 11 - 4所示(参见封面二)。运行我们自编的 s o c k程序 (附录C),发送一份包含 9个字节数据的UDP数据报给标准回显服务器。

图11-4 tcpdump 输出,观察其他主机是否打开UDP检验和选项

从这里可以看出,三个系统中有两个打开了 UDP检验和选项。 还要注意的是,在这个简单例子中,送出的数据报与收到的数据报具有相同的检验和值 (第3和第4行,第5和第6行)。从图11-3可以看出,两个IP地址进行了交换,正如两个端口号一样。 伪首部和UDP首部中的其他字段都是相同的,就像数据回显一样。这再次表明 UDP检验和(事 实上,TCP/IP协议簇中所有的检验和)是简单的16 bit和。它们检测不出交换两个16 bit的差错。 作者在14.2节中在8个域名服务器中各进行了一次 DNS查询。DNS主要使用UDP, 结果只有两台服务器打开了UDP检验和选项。 11.3.2 一些统计结果 文献[Mogul 1992]提供了在一个繁忙的NFS服务器上所发生的不同检验和差错的统计结果,

110

使用TCP/IP详解,卷1:协议

时间持续了 4 0天。统计数字结果如图 11 - 5所 示。 最后一列是每一行的大概总数,因为以

下载 层



检验和差错数

近似总分组数

以太网

太网和 I P层还使用其他的协议。例如,不是 所有的以太网数据帧都是 I P数据报,至少以 太网还要使用 A R P协议。不是所有的 I P数据

图11-5 检测到不同检验和差错的分组统计结果

报都是UDP或TCP数据,因为 ICMP也用IP传送数据。 注意, T C P发生检验和差错的比例与 U D P相比要高得多。这很可能是因为在该系统中的 TCP连接经常是“远程”连接(经过许多路由器和网桥等中间设备),而UDP一般为本地通信。 从最后一行可以看出,不要完全相信数据链路(如以太网,令牌环等)的 C R C检验。应 该始终打开端到端的检验和功能。而且,如果你的数据很有价值,也不要完全相信

U D P或

TCP的检验和,因为这些都只是简单的检验和,不能检测出所有可能发生的差错。

11.4 一个简单的例子 用我们自己编写的 sock程序生成一些可以通过 tcpdump观察的UDP数据报: bsdi % s o c k - v - u - i - n 4 s v r 4 d i s c a r d connected on 140.252.13.35.1108 to 140.252.13.34.9 bsdi % sock -v -u -i -n4 -w0 svr4 discard connected on 140.252.13.35.1110 to 140.252.13.34.9

第1次执行这个程序时,我们指定 v e r b o s e模式(- v)来观察 e p h e m e r a l端口号,指定 U D P (- u)而不是默认的 T C P,并且指定源模式( - i)来发送数据,而不是读写标准的输入和输 出。- n 4选项指明输出 4份数据报(默认条件下为 1 0 2 4),目的主机为 s v r 4。在1 . 1 2节描述了丢 弃服务。每次写操作的输出长度取默认值 1024。 第2次运行该程序时我们指定 - w 0,意思是写长度为 0的数据报。两个命令的 t c p d u m p输 出结果如图 11-6所示。

图11-6 向一个方向发送UDP数据报时的tcpdump 输出

输出显示有四份 1 0 2 4字节的数据报,接着有四份长度为 0的数据报。每份数据报间隔几毫 秒(输入第 2个命令花了 41秒的时间)。 在发送第 1份数据报之前,发送端和接收端之间没有任何通信(在第 1 7章,我们将看到 T C P在发送数据的第 1个字节之前必须与另一端建立连接)。另外,当收到数据时,接收端没 有任何确认。在这个例子中,发送端并不知道另一端是否已经收到这些数据报。 最后要指出的是,每次运行程序时,源端的 U D P端口号都发生变化。第一次是 11 0 8,然 后是11 0。在1 . 9节我们已经提过,客户程序使用 e p h e m e r a l端口号一般在 1 0 2 4~5 0 0 0之间,正

111

第11章 UDP:用户数据报协议使用

下载 如我们现在看到的这样。

11.5 IP分片 正如我们在 2 . 8节描述的那样,物理网络层一般要限制每次发送数据帧的最大长度。任何 时候I P层接收到一份要发送的 I P数据报时,它要判断向本地哪个接口发送数据(选路),并查 询该接口获得其 M T U。I P把M T U与数据报长度进行比较,如果需要则进行分片。分片可以发 生在原始发送端主机上,也可以发生在中间路由器上。 把一份 I P数据报分片以后,只有到达目的地才进行重新组装(这里的重新组装与其他网 络协议不同,它们要求在下一站就进行进行重新组装,而不是在最终的目的地)。重新组装由 目的端的 I P层来完成,其目的是使分片和重新组装过程对运输层( T C P和U D P)是透明的, 除了某些可能的越级操作外。已经分片过的数据报有可能会再次进行分片(可能不止一次)。 IP首部中包含的数据为分片和重新组装提供了足够的信息。 回忆IP首部(图3-1),下面这些字段用于分片过程。对于发送端发送的每份 IP数据报来说, 其标识字段都包含一个唯一值。该值在数据报分片时被复制到每个片中(我们现在已经看到 这个字段的用途)。标志字段用其中一个比特来表示“更多的片”。除了最后一片外,其他每 个组成数据报的片都要把该比特置 1。片偏移字段指的是该片偏移原始数据报开始处的位置。 另外,当数据报被分片后,每个片的总长度值要改为该片的长度值。 最后,标志字段中有一个比特称作“不分片”位。如果将这一比特置 1,I P将不对数据报 进行分片。相反把数据报丢弃并发送一个 I C M P差错报文(“需要进行分片但设置了不分片比 特”,见图6-3)给起始端。在下一节我们将看到出现这个差错的例子。 当I P数据报被分片后,每一片都成为一个分组,具有自己的 I P首部,并在选择路由时与 其他分组独立。这样,当数据报的这些片到达目的端时有可能会失序,但是在 I P首部中有足 够的信息让接收端能正确组装这些数据报片。 尽管IP分片过程看起来是透明的,但有一点让人不想使用它:即使只丢失一片数据也要重 传整个数据报。为什么会发生这种情况呢?因为 IP层本身没有超时重传的机制——由更高层来 负责超时和重传( TCP有超时和重传机制,但 UDP没有。一些UDP应用程序本身也执行超时和 重传)。当来自TCP报文段的某一片丢失后, TCP在超时后会重发整个 TCP报文段,该报文段对 应于一份IP数据报。没有办法只重传数据报中的一个数据报片。事实上,如果对数据报分片的 是中间路由器,而不是起始端系统,那么起始端系统就无法知道数据报是如何被分片的。就这 个原因,经常要避免分片。文献[Kent and Mogul 1987]对避免分片进行了论述。 使用UDP很容易导致 IP分片(在后面我们将看到, TCP试图避免分片,但对于应用程序来 说几乎不可能强迫 T C P发送一个需要进行分片的长报文段)。我们可以用 s o c k程序来增加数 据报的长度,直到分片发生。在一个以太网上,数据帧的最大长度是 1 5 0 0字节(见图 2 - 1), 其中 1 4 7 2字节留给数据,假定 I P首部为 2 0字节, U D P首部为 8字节。我们分别以数据长度为 1471, 1472, 1473和1474字节运行sock程序。最后两次应该发生分片: bsdi bsdi bsdi bsdi

% sock % sock % sock % sock

-u -u -u -u

-i -i -i -i

-nl -nl -nl -nl

-w1471 -w1472 -w1473 -w1474

svr4 svr4 svr4 svr4

相应的tcpdump输出如图11-7所示。

discard discard discard discard

112

使用TCP/IP详解,卷1:协议

下载

图11-7 观察UDP数据报分片

前两份 U D P数据报(第 1行和第 2行)能装入以太网数据帧,没有被分片。但是对应于写 1473字节的IP数据报长度为 1501,就必须进行分片(第 3行和第4行)。同理,写 1474字节产生 的数据报长度为 1502,它也需要进行分片(第 5行和第6行)。 当IP数据报被分片后, t c p d u m p打印出其他的信息。首先, f r a g 26304(第3行和第4 行)和f r a g 2 6 3 1( 3 第5行和第6行)指的是 IP首部中标识字段的值。 分片信息中的下一个数字,即第 3行中位于冒号和 @号之间的 1 4 8 0,是除I P首部外的片长。 两份数据报第一片的长度均为 1480:UDP首部占8字节,用户数据占 1472字节(加上IP首部的 2 0字节分组长度正好为 1 5 0 0字节)。第1份数据报的第 2片(第4行)只包含1字节数据 — 剩下 的用户数据。第 2份数据报的第2片(第6行)包含剩下的 2字节用户数据。 在分片时,除最后一片外,其他每一片中的数据部分(除 I P首部外的其余部分)必须是 8 字节的整数倍。在本例中, 1480是8的整数倍。 位于@符号后的数字是从数据报开始处计算的片偏移值。两份数据报第 1片的偏移值均为0(第3行 和第5行),第2片的偏移值为1480(第4行和第6行) 。跟在偏移值后面的加号对应于IP首部中3 bit标志 字段中的“更多片”比特。设置这一比特的目的是让接收端知道在什么时候完成所有的分片组装。 最后,注意第 4行和第6行(不是第 1片)省略了协议名( U D P)、源端口号和目的端口号。 协议名是可以打印出来的,因为它在 I P首部并被复制到各个片中。但是,端口号在 U D P首部, 只能在第1片中被发现。 发送的第 3份数据报(用户数据为 1 4 7 3字节)分片情况如图 11 - 8所示。需要重申的是,任 何运输层首部只出现在第 1片数据中。 另外需要解释几个术语: I P数据报是指 I P层端到端的传输单元(在分片之前和重新组装 之后),分组是指在IP层和链路层之间传送的数据单元。一个分组可以是一个完整的 IP数据报, 也可以是IP数据报的一个分片。 IP数据报 IP首部

UDP首部

20字节

8字节

UDP数据(1473字节)

8字节

20字节

IP首部

UDP首部

IP首部 1472字节

20字节 分组

分组

图11-8

UDP分片举例

1字节

113

第11章 UDP:用户数据报协议使用

下载 11.6

ICMP不可达差错(需要分片)

发生I C M P不可达差错的另一种情况是,当路由器收到一份需要分片的数据报,而在 I P首 部又设置了不分片( D F)的标志比特。如果某个程序需要判断到达目的端的路途中最小 M T U 是多少 — 称作路径MTU发现机制( 2.9节),那么这个差错就可以被该程序使用。 这种情况下的 I C M P不可达差错报文格式如图 11 - 9所示。这里的格式与图 6 - 1 0不同,因为 在第2个32 bit字中,16~31 bit可以提供下一站的 MTU,而不再是 0。 检验和

代码(4)

类型(3)

8字节 未用(必须为0)

下一站网络的MTU

IP首部(包括选项)+原始IP数据报中数据的前8字节

图11-9 需要分片但又设置不分片标志比特时的ICMP不可达差错报文格式

如果路由器没有提供这种新的 ICMP差错报文格式,那么下一站的 MTU就设为0。 新版的路由器需求RFC [Almquist 1993]声明,在发生这种ICMP不可达差错时,路 由器必须生成这种新格式的报文。 例子 关于分片作者曾经遇到过一个问题, ICMP差错试图判断从路由器 n e t b到主机s u n之间的 拨号S L I P链路的M T U。我们知道从 s u n到n e t b的链路的 M T U:当S L I P被安装到主机 s u n时, 这是SLIP配置过程中的一部分,加上在 3.9节中已经通过n e t s t a t命令观察过。现在,我们想 从另一个方向来判断它的 MTU(在第25章,将讨论如何用SNMP来判断)。在点到点的链路中, 不要求两个方向的 MTU为相同值。 所采用的技术是在主机 s o l a r i s上运行 p i n g程序到主机 b s d i,增加数据分组长度,直 到看见进入的分组被分片为止。如图 11-10所示。

分片 ICMP回显请求

分片

用tcpdump观察

分片

图11-10 用来判断从netb 到sun 的SLIP链路MTU的系统

在主机 s u n上运行t c p d u m p,观察 S L I P链路,看什么时候发生分片。开始没有观察到分 片,一切都很正常直到 p i n g分组的数据长度从 5 0 0增加到6 0 0字节。可以看到接收到的回显请

114

使用TCP/IP详解,卷1:协议

下载

求(仍然没有分片),但不见回显应答。 为了跟踪下去,也在主机 b s d i上运行 t c p d u m p,观察它接收和发送的报文。输出如图 11-11所示。

图11-11 600字节的IP数据报从solaris 主机ping 到bsdi 主机时的tcpdump 输出

首先,每行中的标记( D F)说明在 I P首部中设置了不分片比特。这意味着 Solaris 2.2 一 般把不分片比特置 1,作为实现路径 MTU发现机制的一部分。 第1行显示的是回显请求通过路由器 n e t b到达s u n主机,没有进行分片,并设置了 D F比 特,因此我们知道还没有达到 netb的SLIP MTU。 接下来,在第2行注意到DF标志被复制到回显应答报文中。这就带来了问题。回显应答与 回显请求报文长度相同(超过 6 0 0字节),但是 s u n外出的 S L I P接口M T U为5 5 2。因此回显应 答需要进行分片,但是 DF标志比特又被设置了。这样, s u n就产生一个 ICMP不可达差错报文 返回给bsdi(报文在bsdi处被丢弃)。 这就是我们在主机 s o l a r i s上没有看到任何回显应答的原因。这些应答永远不能通过 sun。分组的路径如图 11-12所示。

ICMP回显请求

ICMP回显请求

ICMP回显请求

ICMP回显应答 ICMP不可达:需要分片, 但又设置了DF位

图11-12 例子中的分组交换

最后,在图 11 - 11中的第3行和第6行中,m t u = 0表示主机 s u n没有在 I C M P不可达报文中返 回出口 M T U值,如图 11 - 9所示(在 2 5 . 9节中,将重新回到这个问题,用 S N M P判断n e t b上的 SLIP接口MTU值为1500)。

11.7 用Traceroute确定路径MTU 尽管大多数的系统不支持路径 M T U发现功能,但可以很容易地修改 t r a c e r o u t e程序 (第8章),用它来确定路径 M T U。要做的是发送分组,并设置“不分片”标志比特。发送的 第一个分组的长度正好与出口 MTU相等,每次收到 ICMP“不能分片”差错时(在上一节讨论

下载

115

第11章 UDP:用户数据报协议使用

的)就减小分组的长度。如果路由器发送的 ICMP差错报文是新格式,包含出口的 MTU,那么 就用该 M T U值来发送,否则就用下一个最小的 M T U值来发送。正如 RFC 1191 [Mogul and Deering 1990]声明的那样, M T U值的个数是有限的,因此在我们的程序中有一些由近似值构 成的表,取下一个最小 MTU值来发送。 首先,我们尝试判断从主机 sun到主机slip的路径MTU,知道SLIP链路的MTU为296。

在这个例子中,路由器 b s d i没有在 I C M P差错报文中返回出口 M T U ,因此我们选择另一个 M T U近似值。 T T L为2的第1行输出打印的主机名为 b s d i,但这是因为它是返回 I C M P差错报 文的路由器。TTL为2的最后一行正是我们所要找的。 在b s d i上修改ICMP代码使它返回出口 MTU值并不困难,如果那样做并再次运行该程序, 得到如下输出结果:

这时,在找到正确的 M T U值之前,我们不用逐个尝试 8个不同的 M T U值——路由器返回 了正确的MTU值。 全球互联网 作为一个实验,我们多次运行修改以后的 t r a c e r o u t e程序,目的端为世界各地的主机。 可以到达 1 5个国家(包括南极洲),使用了多个跨大西洋和跨太平洋的链路。但是,在这样做 之前,作者所在子网与路由器 n e t b之间的拨号 S L I P链路M T U(见图11 - 1 2)增加到 1 5 0 0,与 以太网相同。 在1 8次运行当中,只有其中 2次发现的路径 M T U小于 1 5 0 0。其中一个跨大西洋的链路 M T U值为 5 7 2 (其近似值甚至在 RFC 1 1 9 1中也没有被列出),而路由器返回的是新格式的 I C M P差错报文。另外一条链路,在日本的两个路由器之间,不能处理 1 5 0 0字节的数据帧,并 且路由器没有返回新格式的 ICMP差错报文。把MTU值设成1006则可以正常工作。 从这个实验可以得出结论,现在许多但不是所有的广域网都可以处理大于 512字节的分组。 利用路径MTU发现机制,应用程序就可以充分利用更大的 MTU来发送报文。

116

使用TCP/IP详解,卷1:协议

下载

11.8 采用UDP的路径MTU发现 下面对使用 U D P的应用程序与路径 M T U发现机制之间的交互作用进行研究。看一看如果 应用程序写了一个对于一些中间链路来说太长的数据报时会发生什么情况。 例子 由于我们所使用的支持路径 MTU发现机制的唯一系统就是 Solaris 2.x,因此,将采用它作 为源站发送一份 650字节数据报经slip。由于slip主机位于MTU为296的SLIP链路后,因此, 任何长于 268字节(296-20-8)且“不分片”比特置为 1的UDP数据都会使 b s d i路由器产生 ICMP“不能分片”差错报文。图 11-13给出了拓扑结构和 MTU。 在这里运行tcpdump命令

将DF比特置1的650字节UDP数据报

ICMP不能分片差错

图11-13 使用UDP进行路径MTU发现的系统

可以用下面的命令行来产生 650字节UDP数据报,每两个 UDP数据报之间的间隔是 5秒: s o l a r i s %s o c k - u - i - n 1 0 - w 6 5 0 - p 5 s l i p d i s c a r d

图11-14是tcpdump的输出结果。在运行这个例子时,将 bsdi设置成在ICMP“不能分片” 差错中,不返回下一跳 MTU信息。 在发送的第一个数据报中将 D F比特置 1(第1行),其结果是从 b s d i路由器发回我们可以 猜测的结果(第 2行)。令人不解的是,发送一个 D F比特置 1的数据报(第 3行),其结果是同 样的ICMP差错(第4行)。我们预计这个数据报在发送时应该将 DF比特置0。 第5行结果显示, I P已经知道了发往该目的地址的数据报不能将 D F比特置 1,因此, I P进 而将数据报在源站主机上进行分片。这与前面的例子中, I P发送经过 U D P的数据报,允许具 有较小 M T U的路由器(在本例中是 b s d i)对它进行分片的情况不一样。由于 I C M P“不能分 片”报文并没有指出下一跳的 M T U,因此,看来 I P猜测M T U为5 7 6就行了。第一次分片(第 5 行)包含 5 4 4字节的 U D P数据、 8字节U D P首部以及 2 0字节I P首部,因此,总 I P数据报长度是 572字节。第2次分片(第 6行)包含剩余的 106字节UDP数据和20字节IP首部。 不幸的是,第 7行的下一个数据报将其 D F比特置 1,因此 b s d i将它丢弃并返回 I C M P差错。 这时发生了 I P定时器超时,通知 I P 查看是不是因为路径 M T U增大了而将 D F比特再一次置 1。 我们可以从第 1 9行和2 0行看出这个结果。将第 7行与1 9行进行比较,可以看出 I P每过3 0秒就将 DF比特置1,以查看路径MTU是否增大了。 这个 3 0秒的定时器值看来太短。 R F C 11 9 1建议其值取 1 0分钟。可以通过修改 ip_ire_pathmtu_interval(E.4节)参数来改变该值。同时,Solaris 2.2无法对单个

下载

117

第11章 UDP:用户数据报协议使用

UDP应用或所有UDP应用关闭该路径MTU发现。只能通过修改ip_path_mtu_discovery 参数,在系统一级开放或关闭它。正如在这个例子里所能看到的那样,如果允许路径 MTU 发现,那么当UDP应用程序写入可能被分片数据报时,该数据报将被丢弃。

图11-14 使用UDP路径MTU发现

s o l a r i s的I P层所假设的最大数据报长度( 5 7 6字节)是不正确的。在图 11 - 1 3中,我们 看到,实际的 M T U值是2 9 6字节。这意味着经 s o l a r i s 分片的数据报还将被 b s d i分片。图 11 - 1 5给出了在目的主机( s l i p)上所收集到的 t c p d u m p对于第一个到达数据报的输出结果 (图11-14的第5行和第6行)。

图11-15 从solaris到达slip的第一个数据报

在本例中, s o l a r i s不应该对外出数据报分片,它应该将 D F比特置0,让具有最小 M T U 的路由器来完成分片工作。 现在我们运行同一个例子,只是对路由器 b s d i进行修改使其在 I C M P“不能分片”差错 中返回下一跳MTU。图11-16给出了tcpdump输出结果的前6行。 与图11 - 1 4一样,前两个数据报同样是将 D F比特置 1后发送出去的。但是在知道了下一跳 MTU后,只产生了3个数据报片,而图 11-15中的bsdi路由器则产生了 4个数据报片。

118

使用TCP/IP详解,卷1:协议

下载

图11-16 使用UDP的路径MTU发现

11.9

UDP和ARP之间的交互作用

使用UDP,可以看到 UDP与ARP典型实现之间的有趣的(而常常未被人提及)交互作用。 我们用s o c k程序来产生一个包含 8 1 9 2字节数据的 U D P数据报。预测这将会在以太网上产 生6个数据报片(见习题 11 . 3)。同时也确保在运行该程序前, A R P缓存是清空的,这样,在 发送第一个数据报片前必须交换 ARP请求和应答。 bsdi % a r p - a 验证A R P高速缓存是空的 bsdi % s o c k - u - i - n l - w 8 1 9 2 s v r 4 d i s c a r d

预计在发送第一个数据报片前会先发送一个 A R P请求。I P还会产生 5个数据报片,这样就 提出了我们必须用 t c p d u m p来回答的两个问题:在接收到 A R P回答前,其余数据报片是否已 经做好了发送准备?如果是这样,那么在 A R P等待应答时,它会如何处理发往给定目的的多 个报文?图 11-17给出了tcpdump的输出结果。

图11-17 在以太网上发送8192字节UDP数据报时的报文交换

在这个输出结果中有一些令人吃惊的结果。首先,在第一个 A R P应答返回以前,总共产 生了6个A R P请求。我们认为其原因是 I P很快地产生了 6个数据报片,而每个数据报片都引发 了一个ARP请求。 第二,在接收到第一个 A R P应答时(第7行),只发送最后一个数据报片(第 9行)!看来 似乎将前 5个数据报片全都丢弃了。实际上,这是 A R P的正常操作。在大多数的实现中,在等 待一个ARP应答时,只将最后一个报文发送给特定目的主机。 Host Requirements RFC要求实现中必须防止这种类型的ARP洪泛(ARP flooding,

119

第11章 UDP:用户数据报协议使用

下载

即以高速率重复发送到同一个IP地址的ARP请求)。建议最高速率是每秒一次。而这里 却在4.3 ms内发出了6个ARP请求。 Host Requirements RFC规定,ARP应该保留至少一个报文,而这个报文必须是最后 一个报文。这正是我们在这里所看到的结果。 另一个无法解释的不正常的现象是, svr4发回7个,而不是 6个ARP应答。 最后要指出的是,在最后一个 A R P应答返回后,继续运行 t c p d u m p程序5分钟,以看看 s v r 4是否会返回 I C M P“组装超时”差错。并没有发送 I C M P差错(我们在图 8 - 2中给出了该 消息的格式。code字段为1表示在重新组装数据报时发生了超时)。 在第一个数据报片出现时, I P层必须启动一个定时器。这里“第一个”表示给定数据报 的第一个到达数据报片,而不是第一个数据报片(数据报片偏移为 0)。正常的定时器值为 3 0 或6 0秒。如果定时器超时而该数据报的所有数据报片未能全部到达,那么将这些数据报片丢 弃。如果不这么做,那些永远不会到达的数据报片(正如我们在本例中所看到的那样)迟早 会引起接收端缓存满。 这里我们没看到 I C M P消息的原因有两个。首先,大多数从 B e r k e l e y派生的实现从不产生 该差错!这些实现会设置定时器,也会在定时器溢出时将数据报片丢弃,但是不生成 I C M P差 错。第二,并未接收到包含 U D P首部的偏移量为 0的第一个数据报片(这是被 A R P所丢弃的 5 个报文的第1个)。除非接收到第一个数据报片,否则并不要求任何实现产生 I C M P差错。其原 因是因为没有运输层首部,ICMP差错的接收者无法区分出是哪个进程所发送的数据报被丢弃。 这里假设上层( TCP或使用UDP的应用程序)最终会超时并重传。 在本节中,我们使用 I P数据报片来查看 U D P与A R P之间的交互作用。如果发送端迅速发 送多个 U D P数据报,也可以看到这个交互过程。我们选择采用分片的方法,是因为 I P可以生 成报文的速度,比一个用户进程生成多个数据报的速度更快。 尽管本例看来不太可能,但它确实经常发生。 N F S发送的 U D P数据报长度超过 8 1 9 2字节。 在以太网上,这些数据报以我们所指出的方式进行分片,如果适当的 A R P缓存入口发生超时, 那么就可以看到这里所显示的现象。 NFS将超时并重传,但是由于 ARP的有限队列,第一个 IP 数据报仍可能被丢弃。

11.10 最大UDP数据报长度 理论上,IP数据报的最大长度是 65535字节,这是由IP首部(图3-1)16比特总长度字段所 限制的。去除 2 0字节的 I P首部和 8个字节的 U D P首部, U D P数据报中用户数据的最长长度为 65507字节。但是,大多数实现所提供的长度比这个最大值小。 我们将遇到两个限制因素。第一,应用程序可能会受到其程序接口的限制。 socket API提 供了一个可供应用程序调用的函数,以设置接收和发送缓存的长度。对于 UDP socket,这个 长度与应用程序可以读写的最大 U D P数据报的长度直接相关。现在的大部分系统都默认提供 了可读写大于 8 1 9 2字节的 U D P数据报(使用这个默认值是因为 8 1 9 2是N F S读写用户数据数的 默认值)。 第二个限制来自于 T C P / I P的内核实现。可能存在一些实现特性(或差错),使I P数据报长 度小于65535字节。 作者使用sock程序对不同UDP数据报长度进行了试验。在SunOS 4.1.3下使用环回

120

使用TCP/IP详解,卷1:协议

下载

接口的最大IP数据报长度是32767字节。比它大的值都会发生差错。但是从 BSD/386到 SunOS 4.1.3的情况下,Sun所能接收到最大IP数据报长度为32786字节(即32758字节用 户数据)。在Solaris 2.2下使用环回接口,最大可收发 I P数据报长度为 6 5 5 3 5字节。从 Solaris 2.2到AIX 3.2.2,发送的最大IP数据报长度可以是65535字节。很显然,这个限制 与源端和目的端的实现有关。 我们在3.2节中提过,要求主机必须能够接收最短为 576字节的IP数据报。在许多 UDP应用 程序的设计中,其应用程序数据被限制成 5 1 2字节或更小,因此比这个限制值小。例如,我们 在1 0 . 4节中看到,路径信息协议总是发送每份数据报小于 5 1 2字节的数据。我们还会在其他 U D P应用程序如 D N S(第1 4章)、T F T P(第1 5章)、B O O T P(第1 6章)以及 S N M P(第2 5章) 中遇到这个限制。 数据报截断 由于I P能够发送或接收特定长度的数据报并不意味着接收应用程序可以读取该长度的数 据。因此, U D P编程接口允许应用程序指定每次返回的最大字节数。如果接收到的数据报长 度大于应用程序所能处理的长度,那么会发生什么情况呢? 不幸的是,该问题的答案取决于编程接口和实现。 典型的Berkeley版socket API对数据报进行截断,并丢弃任何多余的数据。应用程 序何时能够知道,则与版本有关(4.3BSD Reno及其后的版本可以通知应用程序数据报 被截断)。 SVR4下的socket API(包括Solaris 2.x) 并不截断数据报。超出部分数据在后面的读 取中返回。它也不通知应用程序从单个UDP数据报中多次进行读取操作。 TLI API不丢弃数据。相反,它返回一个标志表明可以获得更多的数据,而应用程 序后面的读操作将返回数据报的其余部分。 在讨论TCP时,我们发现它为应用程序提供连续的字节流,而没有任何信息边界。 TCP以 应用程序读操作时所要求的长度来传送数据,因此,在这个接口下,不会发生数据丢失。

11.11 ICMP源站抑制差错 我们同样也可以使用 U D P产生I C M P“源站抑制 (source quench)”差错。当一个系统(路 由器或主机)接收数据报的速度比其处理速度快时,可能产生这个差错。注意限定词“可能”。 即使一个系统已经没有缓存并丢弃数据报,也不要求它一定要发送源站抑制报文。 图11 - 1 8给出了 I C M P源站抑制差错报文的格式。有一个很好的方案可以在我们的测试网 络里产生该差错报文。可以从 b s d i通过必须经过拨号 S L I P链路的以太网,将数据报发送给路 由器s u n。由于S L I P链路的速度大约只有以太网的千分之一,因此,我们很容易就可以使其缓 存用完。下面的命令行从主机 b s d i通过路由器 s u n发送1 0 0个1 0 2 4字节长数据报给 s o l a r i s。 我们将数据报发送给标准的丢弃服务,这样,这些数据报将被忽略: bsdi % s o c k - u - i - w 1 0 2 4 - n 1 0 0 s o l a r i s d i s c a r d

图11-19给出了与此命令行相对应的 tcpdump输出结果 在这个输出结果中,删除了很多行,这只是一个模型。接收前

2 6个数据报时未发生差

121

第11章 UDP:用户数据报协议使用

下载

错;我们只给出了第一个数据报的结果。然而,从第 2 7个数据报开始,每发送一份数据报, 就会接收到一份源站抑制差错报文。总共有 26 +(74×2)=174行输出结果。 类型(4)

代码(0)

检验和 8字节

未用(必须为0)

IP首部(包括选项)+原始IP数据报中数据的前8 字节

图11-18

ICMP源站抑制差错报文格式

图11-19 来自路由器sun 的ICMP源站抑制

从2 . 1 0节的并行线吞吐率计算结果可以知道,以 9600 b/s速率传送 1 0 2 4字节数据报只需要 1秒时间(由于从 s u n到n e t b的SLIP链路的MTU为552字节,因此在我们的例子中, 20 + 8 + 1 0 2 4字节数据报将进行分片,因此,其时间会稍长一些)。但是我们可以从图 11 - 1 9的时间中 看出, s u n路由器在不到 1秒时间内就处理完所有的 1 0 0个数据报,而这时,第一份数据报还 未通过SLIP链路。因此我们用完其缓存就不足不奇了。 尽管RFC 1009 [Braden and Postel 1987] 要求路由器在没有缓存时产生源站抑制差 错报文,但是新的Router Requirements RFC [Almquist 1993] 对此作了修改,提出路由 器不应该产生源站抑制差错报文。由于源站抑制要消耗网络带宽,且对于拥塞来说是 一种无效而不公平的调整,因此现在人们对于源站抑制差错的态度是不支持的。 在本例中,还需要指出的是, s o c k程序要么没有接收到源站抑制差错报文,要么接收到 却将它们忽略了。结果是如果采用 U D P协议,那么 B S D实现通常忽略其接收到的源站抑制报 文(正如我们在 2 1 . 1 0节所讨论的那样, T C P接受源站抑制差错报文,并将放慢在该连接上的 数据传输速度)。其部分原因在于,在接收到源站抑制差错报文时,导致源站抑制的进程可能 已经中止了。实际上,如果使用 Unix 的time程序来测定 s o c k程序所运行的时间,其结果是它 只运行了大约 0 . 5秒时间。但是从图 11 - 1 9中可以看到,在发送第一份数据报过后 0 . 7 1秒才接收 到一些源站抑制,而此时该进程已经中止。其原因是我们的程序写入了 1 0 0个数据报然后中止 了。但是所有的 100个数据报都已发送出去 — 有一些数据报在输出队列中。 这个例子重申了 U D P是一个非可靠的协议,它说明了端到端的流量控制。尽管 s o c k程序 成功地将 100个数据报写入其网络,但只有 26个数据报真正发送到了目的端。其他 74个数据报

122

使用TCP/IP详解,卷1:协议

下载

可能被中间路由器丢弃。除非在应用程序中建立一些应答机制,否则发送端并不知道接收端 是否收到了这些数据。

11.12 UDP服务器的设计 使用U D P的一些蕴含对于设计和实现服务器会产生影响。通常,客户端的设计和实现比 服务器端的要容易一些,这就是我们为什么要讨论服务器的设计,而不是讨论客户端的设计 的原因。典型的服务器与操作系统进行交互作用,而且大多数需要同时处理多个客户。 通常一个客户启动后直接与单个服务器通信,然后就结束了。而对于服务器来说,它启 动后处于休眠状态,等待客户请求的到来。对于 U D P来说,当客户数据报到达时,服务器苏 醒过来,数据报中可能包含来自客户的某种形式的请求消息。 在这里我们所感兴趣的并不是客户和服务器的编程方面( [Stevens 1990]对这些方面的细 节进行了讨论),而是U D P那些影响使用该协议的服务器的设计和实现方面的协议特性(我们 在 1 8 . 11节中对 T C P服务器的设计进行了描述)。尽管我们所描述的一些特性取决于所使用 UDP的实现,但对于大多数实现来说,这些特性是公共的。 11.12.1 客户IP地址及端口号 来自客户的是 U D P数据报。 I P首部包含源端和目的端 I P地址, U D P首部包含了源端和目 的端的 U D P端口号。当一个应用程序接收到 U D P数据报时,操作系统必须告诉它是谁发送了 这份消息,即源 IP地址和端口号。 这个特性允许一个交互 U D P服务器对多个客户进行处理。给每个发送请求的客户发回应 答。 11.12.2 目的IP地址 一些应用程序需要知道数据报是发送给谁的,即目的 I P地址。例如, Host Requirements R F C规定,T F T P服务器必须忽略接收到的发往广播地址的数据报(我们分别在第 1 2章和第1 5 章对广播和 TFTP进行描述)。 这要求操作系统从接收到的 U D P数据报中将目的 I P地址交给应用程序。不幸的是,并非 所有的实现都提供这个功能。 socket API以IP_RECVDSTADDR socket选项提供了这个功能。对于本文中使用的 系统,只有BSD/386、4.4BSD和AIX 3.2.2支持该选项。SVR4、SunOS 4.x和Solaris 2.x 都不支持该选项。 11.12.3 UDP输入队列 我们在1 . 8节中说过,大多数 U D P服务器是交互服务器。这意味着,单个服务器进程对单 个UDP端口上(服务器上的名知端口)的所有客户请求进行处理。 通常程序所使用的每个 U D P端口都与一个有限大小的输入队列相联系。这意味着,来自 不同客户的差不多同时到达的请求将由 U D P自动排队。接收到的 U D P数据报以其接收顺序交 给应用程序(在应用程序要求交送下一个数据报时)。

下载

123

第11章 UDP:用户数据报协议使用

然而,排队溢出造成内核中的 U D P模块丢弃数据报的可能性是存在的。可以进行以下试 验。我们在作为 UDP服务器的bsdi主机上运行 sock程序: bsdi % s o c k - s - u - v - E - R 2 5 6 - P 3 0 6 6 6 6 f r o m 1 4 0 . 2 5 2 . 1 3 . 3 3 , t o 1 4 0 . 2 5 2 . 1 3 . 6 3 : 1 1 1 1 1 1 1 1 1 1从s u n发送到广播地址 f r o m 1 4 0 . 2 5 2 . 1 3 . 3 4 , t o 1 4 0 . 2 5 2 . 1 3 . 3 5 : 4 4 4 4 4 4 4 4 4 4从s 4 4v4r 4发送到单播地址

我们指明以下标志: - s 表示作为服务器运行, - u 表示 U D P ,- v 表示打印客户的 I P 地 址,- E表示打印目的IP地址(该系统支持这个功能)。另外,我们将这个端口的 UDP接收缓存 设置为 2 5 6字节( - R),其每次应用程序读取的大小也是这个数( - r)。标志 - P 3 0表示创建 U D P端口后,先暂停 3 0秒后再读取第一个数据报。这样,我们就有时间在另两台主机上启动 客户程序,发送一些数据报,以查看接收队列是如何工作的。 服务器一开始工作,处于其 3 0秒的暂停时间内,我们就在 s u n主机上启动一个客户,并 发送三个数据报: sun % s o c k - u - v 1 4 0 . 2 5 2 . 1 3 . 6 3 6 6 6 6 到以太网广播地址 connected on 140.252.13.33.1252 to 140.252.13.63.6666 1111111111 1 1字节的数据(新行) 222222222 1 0字节的数据(新行) 33333333333 1 2字节的数据(新行)

目的地址是广播地址( 1 4 0 . 2 5 2 . 1 3 . 6 3)。我们同时也在主机 s v r 4上启动第 2个客户,并发 送另外三个数据报: svr4 % s o c k - u - v b s d i 6 6 6 6 connected on 0.0.0.0.1042 to 140.252.13.35.6666 4444444444444 555555555555555 66666666

1 4字节的数据(新行) 1 6字节的数据(新行) 9字节的数据(新行)

首先,我们早些时候在 b s d i上所看到的结果表明,应用程序只接收到 2个数据报:来自 sun的第一个全 1报文,和来自svr4的第一个全 4报文。其他 4个数据报看来全被丢弃。 图11 - 2 0给出的 t c p d u m p输出结果表明,所有 6个数据报都发送给了目的主机。两个客户 的数据报以交替顺序键入:第一个来自 s u n,然后是来自 s v r 4的,以此类推。同时也可以看 出,全部6个数据报大约在 12秒内发送完毕,也就是在服务器休眠的 30秒内完成的。

图11-20 两个客户发送UDP数据报的tcpdump 输出结果

我们还可以看到,服务器的- E选项使其可以知道每个数据报的目的 I P地址。如果需要, 它可以选择如何处理其接收到的第一个数据报,这个数据报的地址是广播地址。 我们可以从本例中看到以下几个要点。首先,应用程序并不知道其输入队列何时溢出。 只是由U D P对超出数据报进行丢弃处理。同时,从 t c p d u m p输出结果,我们看到,没有发回 任何信息告诉客户其数据报被丢弃。这里不存在像 I C M P源站抑制这样发回发送端的消息。最 后,看来 U D P输出队列是 F I F O(先进先出)的,而我们在 11 . 9节中所看到的 A R P输入却是

124

使用TCP/IP详解,卷1:协议

下载

LIFO(后进先出)的。 11.12.4 限制本地IP地址 大多数U D P服务器在创建 U D P端点时都使其本地 I P地址具有通配符 ( w i l d c a r d )的特点。这 就表明进入的 U D P数据报如果其目的地为服务器端口,那么在任何本地接口均可接收到它。 例如,我们以端口号 777启动一个UDP服务器: sun % sock -u -s 7777

然后,用netstat命令观察端点的状态: sun % netstat -a -n -f inet Active Internet connections (including servers) Proto Recv-Q Send-Q Local Address Foreign Address udp 0 0 *.7777 *.*

(state)

这里,我们删除了许多行,只保留了其中感兴趣的东西。 - a选项表示报告所有网络端点 的状态。 - n选项表示以点数格式打印 I P地址而不用 D N S把地址转换成名字,打印数字端口号 而不是服务名称。 - f i n e t选项表示只报告 TCP和UDP端点。 本地地址以 *.7777格式打印,星号表示任何本地 IP地址。 当服务器创建端点时,它可以把其中一个主机本地 I P地址包括广播地址指定为端点的本 地I P地址。只有当目的 I P地址与指定的地址相匹配时,进入的 U D P数据报才能被送到这个端 点。用我们的 s o c k程序,如果在端口号之前指定一个 I P地址,那么该 I P地址就成为该端点的 本地IP地址。例如: sun % sock -u -s 140.252.1.29 7777

就限制服务器在 SLIP接口(140.252.1.29)处接收数据报。 netstat输出结果显示如下: Proto Recv-Q Send-Q udp 0 0

Local Address 140.252.1.29.7777

Foreign Address *.*

(state)

如果我们试图在以太网上的主机 b s d i以地址 1 4 0 . 2 5 2 . 1 3 . 3 5向该服务器发送一份数据报, 那么将返回一个 I C M P端口不可达差错。服务器永远看不到这份数据报。这种情形如图 11 - 2 1 所示。

图11-21 服务器本地地址绑定导致拒绝接收UDP数据报

有可能在相同的端口上启动不同的服务器,每个服务器具有不同的本地

I P地址。但是,

一般必须告诉系统应用程序重用相同的端口号没有问题。 使用sockets API时,必须指定SO_REUSEADDR socket选项。在sock程序中是通过-A 选项来完成的。 在主机sun上,可以在同一个端口号( 8888)上启动5个不同的服务器: 对于SLIP链路 对于以太网 对于环回接口 对于以太网广播 其他(IP地址通配)

125

第11章 UDP:用户数据报协议使用

下载

除了第一个以外,其他的服务器都必须以- A选项启动,告诉系统可以重用同一个端口号。 5个服务器的 netstat输出结果如下所示:

在这种情况下,到达服务器的数据报中,只有带星号的本地

I P 地址,其目的地址为

140.252.1.255,因为其他 4个服务器占用了其他所有可能的 IP地址。 如果存在一个含星号的 I P地址,那么就隐含了一种优先级关系。如果为端点指定了特定 I P地址,那么在匹配目的地址时始终优先匹配该 I P地址。只有在匹配不成功时才使用含星号 的端点。 11.12.5 限制远端IP地址 在前面所有的 n e t s t a t输出结果中,远端 I P地址和远端端口号都显示为 * . *,其意思是该端 点将接受来自任何 I P地址和任何端口号的 U D P数据报。大多数系统允许 U D P端点对远端地址 进行限制。 这说明端点将只能接收特定 I P地址和端口号的 U D P数据报。 s o c k程序用 - f选项来指定远 端IP地址和端口号: sun % s o c k - u - s - f 1 4 0 . 2 5 2 . 1 3 . 3 5 . 4 4 4 4 5 5 5 5

这样就设置了远端IP地址140.252.13.35(即主机bsdi)和远端端口号4444 。服务器的有名端 口号为5555。如果运行netstat命令,我们发现本地IP地址也被设置了,尽管我们没有指定。 Proto Recv-Q Send-Q Local Address udp 0 0 140.252.13.33.5555

Foreign Address 140.252.13.35.4444

(state)

这是在伯克利派生系统中指定远端 I P地址和端口号带来的副作用:如果在指定远端地址 时没有选择本地地址,那么将自动选择本地地址。它的值就成为选择到达远端 I P地址路由时 将选择的接口 I P 地址。事实上,在这个例子中,

s u n 在以太网上的 I P 地址与远端地址

140.252.13.33相连。 图11-22总结了UDP服务器本身可以创建的三类地址绑定。 本地地址 localIP.lport localIP.lport *.lport

远端地址 foreignIP.fport *.* *.*

描 述 只限于一个客户 限于到达一个本地接口的数据报: localIP 接收发送到 lport的所有数据报

图11-22 为UDP服务器指定本地和远端IP地址及端口号

在所有情况下, lport指的是服务器有名端口号, localIP必须是本地接口的 IP地址。表中这 三行的排序是 U D P模块在判断用哪个端点接收数据报时所采用的顺序。最为确定的地址(第 一行)首先被匹配,最不确定的地址(最后一行 IP地址带有两个星号)最后进行匹配。 11.12.6 每个端口有多个接收者 尽管在 R F C中没有指明,但大多数的系统在某一时刻只允许一个程序端点与某个本地 I P

126

使用TCP/IP详解,卷1:协议

下载

地址及 U D P端口号相关联。当目的地为该 I P地址及端口号的 U D P数据报到达主机时,就复制 一份传给该端点。端点的 IP地址可以含星号,正如我们前面讨论的那样。 例如,在SunOS 4.1.3中,我们启动一个端口号为 9999的服务器,本地 IP地址含有星号: sun % sock -u -s 9999

接着,如果启动另一个具有相同本地地址和端口号的服务器,那么它将不运行,尽管我 们指定了 -A选项: sun % can't sun % can't

sock -u -s 9999 bind local address: sock -u -s -A 9999 bind local address:

我们预计它会失败 Address already in use 因此,这次尝试 - A参数 Address already in use

在一个支持多播的系统上(第 1 2章),这种情况将发生变化。多个端点可以使用同一个 I P 地址和 U D P 端口号,尽管应用程序通常必须告诉

A P I 是可行的(如,用 - A 标志来指明

SO_REUSEADDR socket选项)。 4.4BSD支持多播传送,需要应用程序设置一个不同的socket选项(SO_REUSEPORT) 以允许多个端点共享同一个端口。另外,每个端点必须指定这个选项,包括使用该端口 的第一个端点。 当U D P数据报到达的目的 I P地址为广播地址或多播地址,而且在目的 I P地址和端口号处 有多个端点时,就向每个端点传送一份数据报的复制(端点的本地 I P地址可以含有星号,它 可匹配任何目的 I P地址)。但是,如果 U D P数据报到达的是一个单播地址,那么只向其中一个 端点传送一份数据报的复制。选择哪个端点传送数据取决于各个不同的系统实现。

11.13 小结 UDP是一个简单协议。它的正式规范是 RFC 768 [Postel 1980],只包含三页内容。它向用 户进程提供的服务位于 I P层之上,包括端口号和可选的检验和。我们用 U D P来检查检验和, 并观察分片是如何进行的。 接着,我们讨论了 I C M P不可达差错,它是新的路径 M T U发现功能中的一部分( 2 . 9节)。 用Tr a c e r o u t e和U D P来观察路径 M T U发现过程。还查看了 U D P和A R P之间的接口,大多数的 ARP实现在等待 ARP应答时只保留最近传送给目的端的数据报。 当系统接收 I P数据报的速率超过这些数据报被处理的速率时,系统可能发送 I C M P源站抑 制差错报文。使用 UDP时很容易产生这样的 ICMP差错。

习题 11.1 在11 . 5节中,向 U D P数据报中写入 1 4 7 3字节用户数据时导致以太网数据报片的发生。在 采用以太网 IEEE 802封装格式时,导致分片的最小用户数据长度为多少? 11.2 阅读RFC 791[Postel 1981a],理解为什么除最后一片外,其他片中的数据长度均要求为 8字节的整数倍? 11.3 假定有一个以太网和一份 8192字节的UDP数据报,那么需要分成多少个数据报片,每个 数据报片的偏移和长度为多少? 11.4 继续前一习题,假定这些数据报片要经过一条 M T U为5 5 2的S L I P链路。必须记住每一个

下载

127

第11章 UDP:用户数据报协议使用

数据报片中的数据(除 I P首部外)为 8字节的整数倍。那么又将分成多少个数据报片? 每个数据报片的偏移和长度为多少? 11.5 一个用 U D P发送数据报的应用程序,它把数据报分成 4个数据报片。假定第 1片和第 2片 到达目的端,而第 3片和第4片丢失了。应用程序在 10秒钟后超时重发该 UDP数据报,并 且被分成相同的 4片(相同的偏移和长度)。假定这一次接收主机重新组装的时间为 60秒, 那么当重发的第 3片和第4片到达目的端时,原先收到的第 1片和第2片还没有被丢弃。接 收端能否把这4片数据重新组装成一份 IP数据报? 11.6 你是如何知道图 11-15中的片实际上与图 11-14中第5行和第6行相对应? 11.7 主机g e m i n i开机3 3天后,n e t s t a t程序显示 48 000 000 份I P数据报中由于首部检验和 差错被丢弃 1 2 9份,在 30 000 000 个T C P段中由于 T C P检验和差错而被丢弃 2 0个。但是, 在大约18 000 000份UDP数据报中,因为UDP检验和差错而被丢弃的数据报一份也没有。 请说明两个方面的原因(提示:参见图 11-4)。 11.8 在讨论分片时没有提及任何关于 I P首部中的选项——它们是否也要被复制到每个数据报 片中,或者只留在第一个数据报片中?我们已经讨论过下面这些

I P选项:记录路由

(7 . 3节)、时间戳(7 . 4节)、严格和宽松的源站选路( 8 . 5节)。你希望分片如何处理这些 选项?对照 RFC 791检查你的答案。 11.9 在图1-8中,我们说 UDP数据报是根据目的 UDP端口号进行分配的。这正确吗?

下载

第12章 广播和多播 12.1 引言 在第1章中我们提到有三种 I P地址:单播地址、广播地址和多播地址。本章将更详细地介 绍广播和多播。 广播和多播仅应用于 U D P,它们对需将报文同时传往多个接收者的应用来说十分重要。 T C P是一个面向连接的协议,它意味着分别运行于两主机(由 I P地址确定)内的两进程(由 端口号确定)间存在一条连接。 考虑包含多个主机的共享信道网络如以太网。每个以太网帧包含源主机和目的主机的以 太网地址(48bit)。通常每个以太网帧仅发往单个目的主机,目的地址指明单个接收接口,因 而称为单播(unicast)。在这种方式下,任意两个主机的通信不会干扰网内其他主机(可能引起 争夺共享信道的情况除外)。 然而,有时一个主机要向网上的所有其他主机发送帧,

交付 丢弃

这就是广播。通过 A R P和R A R P可以看到这一过程。多播 (multicast) 处于单播和广播之间:帧仅传送给属于多播组的 多个主机。

交付 丢弃

为了弄清广播和多播,需要了解主机对由信道传送过 来帧的过滤过程。图 12-1说明了这一过程。 首先,网卡查看由信道传送过来的帧,确定是否接收 该帧,若接收后就将它传往设备驱动程序。通常网卡仅接 收那些目的地址为网卡物理地址或广播地址的帧。另外, 多数接口均被设置为混合模式,这种模式能接收每个帧的

交付 设备驱 动程序

丢弃

交付 接口卡

丢弃

一个复制。作为一个例子, tcpdump使用这种模式。 目前,大多数的网卡经过配置都能接收目的地址为多 播地址或某些子网多播地址的帧。对于以太网,当地址中 最高字节的最低位设置为 1时表示该地址是一个多播地址, 用十六进制可表示为 0 1 : 0 0 : 0 0 : 0 0 : 0 0 : 0 0(以太网广播地址

图12-1 协议栈各层对收到帧 的过滤过程

ff:ff:ff:ff:ff:ff可看作是以太网多播地址的特例)。 如果网卡收到一个帧,这个帧将被传送给设备驱动程序(如果帧检验和错,网卡将丢弃 该帧)。设备驱动程序将进行另外的帧过滤。首先,帧类型中必须指定要使用的协议(

I P、

ARP等等)。其次,进行多播过滤来检测该主机是否属于多播地址说明的多播组。 设备驱动程序随后将数据帧传送给下一层,比如,当帧类型指定为 I P数据报时,就传往 IP层。IP根据IP地址中的源地址和目的地址进行更多的过滤检测。如果正常,就将数据报传送 给下一层(如TCP或UDP)。 每次U D P收到由 I P传送来的数据报,就根据目的端口号,有时还有源端口号进行数据报

129

第12章 广播和多播使用

下载

过滤。如果当前没有进程使用该目的端口号,就丢弃该数据报并产生一个 I C M P不可达报文 (TCP根据它的端口号作相似的过滤)。如果UDP数据报存在检验和错,将被丢弃。 使用广播的问题在于它增加了对广播数据不感兴趣主机的处理负荷。拿一个使用 U D P广 播应用作为例子。如果网内有 5 0个主机,但仅有 2 0个参与该应用,每次这 2 0个主机中的一个 发送 U D P广播数据时,其余 3 0个主机不得不处理这些广播数据报。一直到 U D P层,收到的 U D P广播数据报才会被丢弃。这 3 0个主机丢弃 U D P广播数据报是因为这些主机没有使用这个 目的端口。 多播的出现减少了对应用不感兴趣主机的处理负荷。使用多播,主机可加入一个或多个 多播组。这样,网卡将获悉该主机属于哪个多播组,然后仅接收主机所在多播组的那些多播 帧。

12.2 广播 在图3-9中,我们知道了四种 IP广播地址,下面对它们进行更详细的介绍。 12.2.1 受限的广播 受限的广播地址是 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5。该地址用于主机配置过程中 I P数据报的目的地址, 此时,主机可能还不知道它所在网络的网络掩码,甚至连它的 IP地址也不知道。 在任何情况下,路由器都不转发目的地址为受限的广播地址的数据报,这样的数据报仅 出现在本地网络中。 一个未解的问题是:如果一个主机是多接口的,当一个进程向本网广播地址发送数据报时, 为实现广播,是否应该将数据报发送到每个相连的接口上?如果不是这样,想对主机所有接口 广播的应用必须确定主机中支持广播的所有接口,然后向每个接口发送一个数据报复制。 大多数 B S D系统将 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5看作是配置后第一个接口的广播地址,并且不提供向 所属具备广播能力的接口传送数据报的功能。不过, r o u t e d(见1 0 . 3节)和 r w h o d(B S D r w h o客户的服务器)是向每个接口发送 U D P数据报的两个应用程序。这两个应用程序均用相 似的启动过程来确定主机中的所有接口,并了解哪些接口具备广播能力。同时,将对应于那 种接口的指向网络的广播地址作为发往该接口的数据报的目的地址。 Host Requirements RFC没有进一步涉及多接口主机是否应当向其所有的接口发送 受限的广播。 12.2.2 指向网络的广播 指向网络的广播地址是主机号为全 1的地址。A类网络广播地址为 netid.255.255.255,其中 netid为A类网络的网络号。 一个路由器必须转发指向网络的广播,但它也必须有一个不进行转发的选择。 12.2.3 指向子网的广播 指向子网的广播地址为主机号为全 1且有特定子网号的地址。作为子网直接广播地址的 I P 地址需要了解子网的掩码。例如,如果路由器收到发往 1 2 8 . 1 . 2 . 2 5 5的数据报,当 B类网络

130

使用TCP/IP详解,卷1:协议

下载

1 2 8 . 1的子网掩码为 2 5 5 . 2 5 5 . 2 5 5 . 0时,该地址就是指向子网的广播地址;但如果该子网的掩码 为255.255.254.0,该地址就不是指向子网的广播地址。 12.2.4 指向所有子网的广播 指向所有子网的广播也需要了解目的网络的子网掩码,以便与指向网络的广播地址区分 开。指向所有子网的广播地址的子网号及主机号为全

1 。例如,如果目的子网掩码为

2 5 5 . 2 5 5 . 2 5 5 . 0,那么 I P地址1 2 8 . 1 . 2 5 5 . 2 5 5是一个指向所有子网的广播地址。然而,如果网络 没有划分子网,这就是一个指向网络的广播。 当前的看法 [Almquist 1993]是这种广播是陈旧过时的,更好的方式是使用多播而不是对 所有子网的广播。 [Almquist 1993] 指出RFC 922要求将一个指向所有子网的广播传送给所有子网,但 当前的路由器没有这么做。这很幸运,因为一个因错误配置而没有子网掩码的主机会 把它的本地广播传送到所有子网。例如,如果IP地址为128.1.2.3的主机没有设置子网掩 码,它的广播地址在正常情况下的默认值是 128.1.255.255。但如果子网掩码被设置为 255.255.255.0,那么由错误配置的主机发出的广播将指向所有的子网。 1983年问世的4.2BSD是第一个影响广泛的TCP/IP的实现,它使用主机号全0作为广 播地址。一个最早提到广播IP地址的是IEN 212 [Gurwitz and Hinden 1982],它提出用主 机号中的1比特来表示 IP广播地址( IENs 是互联网试验注释,基本上是 RFC的前身)。 RFC 894 [Hornig 1984]认为4.2BSD使用不标准的广播地址,但RFC 906 [Finlayson 1984] 注意到对广播地址还没有Internet标准。RFC编辑在RFC 906中加了一个脚注承认缺少标 准的广播地址,并强烈推荐将主机号全1作为广播地址。尽管1986年的4.3BSD采用主机 号全1表示广播地址,但直到 90年代早期,操作系统(著名的是 SunOS 4.x)还继续使 用非标准的广播地址。

12.3 广播的例子 广播是怎样传送的?路由器及主机又如何处理广播?很遗憾,这是难以回答的问题,因 为它依赖于广播的类型、应用的类型、 TCP/IP实现方法以及有关路由器的配置。 首先,应用程序必须支持广播。如果执行 sun % ping 255.255.255.255 /usr/etc/ping: unknown host 255.255.255.255

打算在本地电缆上进行广播。但它无法进行,原因在于该应用程序( p i n g)中存在一个程序 设计上的问题。大多数应用程序收到点分十进制的

I P地址或主机名后,会调用函数

i n e t _ a d d r( 3 )来把它们转化为 32 bit的二进制 I P地址。假定要转化的是一个主机名,如果转 化失败,该库函数将返回- 1来表明存在某种差错(例如是字符而不是数字或串中有小数点)。 但本网广播地址( 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5)也被当作存在差错而返回- 1。大多数程序均假定接收到 的字符串是主机名,然后查找 DNS(第14章),失败后输出差错信息如“未知主机”。 如果我们修复 p i n g程序中这个欠缺,结果也并不总是令人满意的。在 6个不同系统的测 试中,仅有一个像预期的那样产生了一个本网广播数据报。大多数则在路由表中查找 I P地址 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5,而该地址被用作默认路由器地址,因此向默认路由器单播一个数据报。最

131

第12章 广播和多播使用

下载 终该数据报被丢弃。

指向子网的广播是我们应该使用的。在 6.3节中,我们向测试网络(见扉页前图)中 IP地址 为140.252.13.63 的以太网发送数据报,并接收以太网中所有主机的应答。与子网广播地址关联 的每个接口是用于命令ifconfig(见3.8节)的值。如果我们ping那个地址,预期的结果是: ARP高速缓存空

键入中断以停止显示

再检验ARP缓存

I P通过目的地址( 1 4 0 . 2 5 2 . 1 3 . 6 3)来确定,这是指向子网的广播地址,然后向链路层的 广播地址发送该数据报。 在6 . 3节提到的这种广播类型的接收对象为局域网中包括发送主机在内的所有主机,因此 可以看到除了收到网内其他主机的答复外,还收到来自发送主机( sun)的答复。 在这个例子中,我们也显示了执行 p i n g广播地址前后 A R P缓存的内容。这可以显示广播 与A R P之间的相互作用。执行 p i n g命令前 A R P缓存是空的,而执行后是满的(也就是说,对 网内其他每个响应回显请求的主机在 A R P缓存中均有一个条目)。我们提到的该以太网数据帧 被传送到链路层的广播地址( 0 x ffffffff)是如何发生的呢?由 s u n主机发送的数据帧不需要 ARP。 如果使用 t c p d u m p来观察p i n g的执行过程,可以看到广播数据帧的接收者在发送它的响 应之前,首先产生一个对 s u n主机的 A R P请求,因为它的应答是单播的。在 4 . 5节我们介绍了 一个ARP请求的接收者(该例中是 s u n)通常在发送ARP应答外,还将请求主机的 IP地址和物 理地址加入到 A R P缓存中去。这基于这样一个假定:如果请求者向我们发送一个数据报,我 们也很可能想向它发回什么。 我们使用的p i n g程序有些特殊,原因在于它使用的编程接口(在大多数 U n i x实现中是低 级插口 (raw socket) )通常允许向一个广播地址发送数据报。如果使用不支持广播的应用如 TFTP,情况又如何呢?( TFTP将在第15章详细介绍。) b s d i % tftp tftp> c o n n e c t 1 4 0 . 2 5 2 . 1 3 . 6 3 tftp> get temp.foo tftp: sendto: Permission denied tftp> quit

启动客户程序 说明服务器的 I P地址 试图从服务器或获取一个文件 终止客户程序

在这个例子中,程序立即产生了一个差错,但不向网络发送任何信息。产生这一切的原因在 于,插口提供的应用程序接口API只有在进程明确打算进行广播时才允许它向广播地址发送UDP

132

使用TCP/IP详解,卷1:协议

下载

数据报。这主要是为了防止用户错误地采用了广播地址(正如此例)而应用程序却不打算广播。 在广播UDP数据报之前,使用插口中 API的应用程序必须设置 SO_BROADCAST插 口选项。 并非所有系统均强制使用这个限制。某些系统中无需进程进行这个说明就能广播 UDP数据报。而某些系统则有更多的限制,需要有超级用户权限的进程才能广播。 下一个问题是是否转发广播数据。有些系统内核和路由器有一选项来控制允许或禁止这 一特性(见附录 E)。 如果让路由器 b s d i能够转发广播数据,然后在主机 s l i p上运行p i n g程序,就能够观察 到由路由器 b s d i转发的子网广播数据报。转发广播数据报意味着路由器接收广播数据,确定 该目的地址是对哪个接口的广播,然后用链路层广播向对应的网络转发数据报。

键入中断以停止显示

我们观察到它的确正常工作了,同时也看到 B S D系统中的 p i n g程序检查重复的数据报序 列号。如果出现重复序列号的数据报就显示 D U P !,这意味着一个数据报已经在某处重复了, 然而它正是我们所期望看到的,因为我们正向一个广播地址发送数据。 我们还可以从远离广播所指向的网络上的主机上来进行这个试验。在主机 angogh.cx.berkeley.edu(和我们的网络距离14跳)上运行ping程序,如果路由器sun被设置 为能够转发所指向的广播,它还能正常工作。在这种情况下,这个IP数据报(传送ICMP回显请求) 被路径上的每个路由器像正常的数据报一样转发,它们均不知道传送的实际上是广播数据。接着最 后一个路由器netb看到主机号为63,就将其转发给路由器sun。路由器sun觉察到该目的IP地址事 实上是一个相连子网接口上的广播地址,就将该数据报以链路层广播传往相应网络。 广播是一种应该谨慎使用的功能。在许多情况下, IP多播被证明是一个更好的解决办法。

12.4 多播 IP多播提供两类服务: 1) 向多个目的地址传送数据。有许多向多个接收者传送信息的应用:例如交互式会议系 统和向多个接收者分发邮件或新闻。如果不采用多播,目前这些应用大多采用

T C P来完成

(向每个目的地址传送一个单独的数据复制)。然而,即使使用多播,某些应用可能继续采用 TCP来保证它的可靠性。 2) 客户对服务器的请求。例如,无盘工作站需要确定启动引导服务器。目前,这项服务 是通过广播来提供的(正如第 16章的BOOTP),但是使用多播可降低不提供这项服务主机的负 担。

133

第12章 广播和多播使用

下载 12.4.1 多播组地址 图12-2显示了D类IP地址的格式。 28位 D 类

多播组ID

图12-2 D类IP地址格式

不像图1 - 5所示的其他三类 I P地址( A、B和C),分配的 28 bit均用作多播组号而不再表示 其他。 多播组地址包括为 111 0的最高 4 bit 和多播组号。它们通常可表示为点分十进制数,范围 从224.0.0.0到239.255.255.255。 能够接收发往一个特定多播组地址数据的主机集合称为主机组 (host group)。一个主机组 可跨越多个网络。主机组中成员可随时加入或离开主机组。主机组中对主机的数量没有限制, 同时不属于某一主机组的主机可以向该组发送信息。 一些多播组地址被 I A N A确定为知名地址。它们也被当作永久主机组,这和 T C P及U D P中 的熟知端口相似。同样,这些知名多播地址在 R F C最新分配数字中列出。注意这些多播地址 所代表的组是永久组,而它们的组成员却不是永久的。 例如,2 2 4 . 0 . 0 . 1代表“该子网内的所有系统组”,2 2 4 . 0 . 0 . 2代表“该子网内的所有路由器 组”。多播地址 2 2 4 . 0 . 1 . 1用作网络时间协议 N T P,2 2 4 . 0 . 0 . 9用作R I P - 2 (见1 0 . 5节),2 2 4 . 0 . 1 . 2用 作SGI公司的dogfight应用。 12.4.2 多播组地址到以太网地址的转换 I A N A拥有一个以太网地址块,即高位 24 bit为0 0 : 0 0 : 5 e(十六进制表示),这意味着该地 址块所拥有的地址范围从 00:00:5e:00:00:00到00:00:5e:ff:ff:ff。IANA将其中的一半分配为多播 地址。为了指明一个多播地址,任何一个以太网地址的首字节必须是 0 1,这意味着与 I P多播 相对应的以太网地址范围从 01:00:5e:00:00:00到01:00:5e:7f:ff:ff。 这里对CSMA/CD或令牌网使用的是Internet标准比特顺序,和在内存中出现的比特 顺序一样。这也是大多数程序设计员和系统管理员采用的顺序。 IEEE文档采用了这种 比特传输顺序。Assigned Numbers RFC给出了这些表示的差别。 这种地址分配将使以太网多播地址中的 2 3 b i t与I P多播组号对应起来,通过将多播组号中 的低位23bit映射到以太网地址中的低位 23bit实现,这个过程如图 12-3所示。 由于多播组号中的最高 5 bit 在映射过程中被忽略,因此每个以太网多播地址对应的多播 组是不唯一的。 3 2 个不同的多播组号被映射为一个以太网地址。例如,多播地址 224.128.64.32(十六进制e0.80.40.20)和224.0.64.32(十六进制e0.00.40.20)都映射为同一以 太网地址01:00:5e:00:40:20。 既然地址映射是不唯一的,那么设备驱动程序或 IP层(见图12-1)就必须对数据报进行过 滤。因为网卡可能接收到主机不想接收的多播数据帧。另外,如果网卡不提供足够的多播数 据帧过滤功能,设备驱动程序就必须接收所有多播数据帧,然后对它们进行过滤。

134

使用TCP/IP详解,卷1:协议

下载 多播组地址中的这 5 bit 未用 作形成以太网地址

D类IP地址 多播组地址中的低位23位复制到 对应的以太网地址中

48位以太网地址

图12-3 D类IP地址到以太网多播地址的映射

局域网网卡趋向两种处理类型:一种是网卡根据对多播地址的散列值实行多播过 滤,这意味仍会接收到不想接收的多播数据;另一种是网卡只接收一些固定数目的多 播地址,这意味着当主机想接收超过网卡预先支持多播地址以外的多播地址时,必须 将网卡设置为“多播混杂(multicast promiscuous)”模式。因此,这两种类型的网卡仍需 要设备驱动程序检查收到的帧是否真是主机所需要的。 即使网卡实现了完美的多播过滤(基于48 bit的硬件地址) ,由于从D类IP地址到48 bit 的硬件地址的映射不是一对一的,过滤过程仍是必要的。 尽管存在地址映射不完美和需要硬件过滤的不足,多播仍然比广播好。 单个物理网络的多播是简单的。多播进程将目的 IP地址指明为多播地址,设备驱动程序将 它转换为相应的以太网地址,然后把数据发送出去。这些接收进程必须通知它们的 IP层,它们 想接收的发往给定多播地址的数据报,并且设备驱动程序必须能够接收这些多播帧。这个过程 就是“加入一个多播组”(使用“接收进程”复数形式的原因在于对一确定的多播信息,在同一 主机或多个主机上存在多个接收者,这也是为什么要首先使用多播的原因)。当一个主机收到多 播数据报时,它必须向属于那个多播组的每个进程均传送一个复制。这和单个进程收到单播 UDP数据报的UDP不同。使用多播,一个主机上可能存在多个属于同一多播组的进程。 当把多播扩展到单个物理网络以外需要通过路由器转发多播数据时,复杂性就增加了。 需要有一个协议让多播路由器了解确定网络中属于确定多播组的任何一个主机。这个协议就 是Internet组管理协议(IGMP),也是下一章介绍的内容。 12.4.3 FDDI和令牌环网络中的多播 F D D I网络使用相同的 D类I P地址到48 bit FDDI 地址的映射过程 [Katz 1990]。令牌环网络 通常使用不同的地址映射方法,这是因为大多数令牌控制中的限制。

12.5 小结 广播是将数据报发送到网络中的所有主机(通常是本地相连的网络),而多播是将数据报 发送到网络的一个主机组。这两个概念的基本点在于当收到送往上一个协议栈的数据帧时采 用不同类型的过滤。每个协议层均可以因为不同的理由丢弃数据报。 目前有四种类型的广播地址:受限的广播、指向网络的广播、指向子网的广播和指向所 有子网的广播。最常用的是指向子网的广播。受限的广播通常只在系统初始启动时才会用到。

下载

135

第12章 广播和多播使用

试图通过路由器进行广播而发生的问题,常常是因为路由器不了解目的网络的子网掩码。 结果与多种因素有关:广播地址类型、配置参数等等。 D类I P地址被称为多播组地址。通过将其低位 23 bit映射到相应以太网地址中便可实现多 播组地址到以太网地址的转换。由于地址映射是不唯一的,因此需要其他的协议实现额外的 数据报过滤。

习题 12.1 广播是否增加了网络通信量? 12.2 考虑一个拥有50台主机的以太网: 20台运行TCP/IP,其他30台运行其他的协议族。主机 如何处理来自运行另一个协议族主机的广播? 12.3 登录到一个过去从来没有用过的 Unix系统,并且打算找出所有支持广播的接口的指向子 网的广播地址。如何做到这点? 12.4 如果我们用 ping程序向一个广播地址发送一个长的分组,如

它正常工作,但将分组的长度再增加一个字节后出现如下差错: sun % ping 140.252.13.63 1473 PING 140.252.13.63: 1473 data bytes sendto: Message too long

究竟出了什么问题 ? 12.5 重做习题 1 0 . 6,假定 8个R I P报文是通过多播而不是广播(使用 RIP 版本2)。有什么变 化?

下载

第13章 IGMP:Internet组管理协议 13.1 引言 1 2 . 4节概述了 I P多播给出,并介绍了 D类I P地址到以太网地址的映射方式。也简要说明了 在单个物理网络中的多播过程,但当涉及多个网络并且多播数据必须通过路由器转发时,情 况会复杂得多。 IP数据报

本章将介绍用于支持主机和路由器进行多播的 I n t e r n e t组管理协议( I G M P)。它让一个物理网络上的所 有系统知道主机当前所在的多播组。多播路由器需要这 些信息以便知道多播数据报应该向哪些接口转发。 I G M P 在RFC 1112中定义 [Deering 1989]。

IP首部

IGMP报文

20字节

8字节

图13-1 IGMP报文封装在IP数据报中

正如 I C M P 一样, I G M P 也被当作 I P 层的一部分。 I G M P报文通过 I P数据报进行传输。不像我们已经见到的其他协议, I G M P有固定的报文长度, 没有可选数据。图 13-1显示了IGMP报文如何封装在 IP数据报中。 IGMP报文通过IP首部中协议字段值为 2来指明。

13.2 IGMP报文 图13-2显示了长度为8字节的IGMP报文格式。 4位 IGMP 版本(1)

4位 IGMP 类型(1-2)

未用

检验和 8字节 32位组地址(D类IP地址)

图13-2 IGMP报文的字段格式

这是版本为1的I G M P。I G M P类型为1说明是由多播路由器发出的查询报文,为 2说明是主 机发出的报告报文。检验和的计算和 ICMP协议相同。 组地址为 D类I P地址。在查询报文中组地址设置为 0,在报告报文中组地址为要参加的组 地址。在下一节中,当介绍 IGMP如何操作时,我们将会更详细地了解它们。

13.3 IGMP 协议 13.3.1 加入一个多播组 多播的基础就是一个进程的概念(使用的术语进程是指操作系统执行的一个程序),该进 程在一个主机的给定接口上加入了一个多播组。在一个给定接口上的多播组中的成员是动态

137

第13章 IGMP:Internet组管理协议使用

下载

的—它随时因进程加入和离开多播组而变化。 这里所指的进程必须以某种方式在给定的接口上加入某个多播组。进程也能离开先前加 入的多播组。这些是一个支持多播主机中任何 API所必需的部分。使用限定词“接口”是因为 多播组中的成员是与接口相关联的。一个进程可以在多个接口上加入同一多播组。 Stanford大学伯克利版Unix中的IP 多播详细说明了有关socket API的变化,这些变 化在Solaris 2.x和ip(7)的文档中也提供了。 这里暗示一个主机通过组地址和接口来识别一个多播组。主机必须保留一个表,此表中 包含所有至少含有一个进程的多播组以及多播组中的进程数量。 13.3.2 IGMP 报告和查询 多播路由器使用 I G M P报文来记录与该路由器相连网络中组成员的变化情况。使用规则如 下: 1) 当第一个进程加入一个组时,主机就发送一个 IGMP报告。如果一个主机的多个进程加 入同一组,只发送一个 IGMP报告。这个报告被发送到进程加入组所在的同一接口上。 2) 进程离开一个组时,主机不发送 IGMP报告,即便是组中的最后一个进程离开。主机知 道在确定的组中已不再有组成员后,在随后收到的 IGMP查询中就不再发送报告报文。 3) 多播路由器定时发送 IGMP查询来了解是否还有任何主机包含有属于多播组的进程。多 播路由器必须向每个接口发送一个 I G M P查询。因为路由器希望主机对它加入的每个多播组均 发回一个报告,因此 IGMP查询报文中的组地址被设置为 0。 4) 主机通过发送 I G M P报告来响应一个 I G M P查询,对每个至少还包含一个进程的组均要 发回IGMP报告。 使用这些查询和报告报文,多播路由器对每个接口保持一个表,表中记录接口上至少还 包含一个主机的多播组。当路由器收到要转发的多播数据报时,它只将该数据报转发到(使 用相应的多播链路层地址)还拥有属于那个组主机的接口上。 图1 3 - 3显示了两个 I G M P报文,一个是主机发送的报告,另一个是路由器发送的查询。该 路由器正在要求那个接口上的每个主机说明它加入的每个多播组。 IGMP报告,TTL=1, IGMP组地址=组地址 目的IP地址=组地址 源IP地址=主机的IP地址

IGMP查询,TTL=1, IGMP组地址=0 目的IP地址=224.0.0.1 源IP地址=路由器IP地址

多播路 由器

主机

图13-3 IGMP的报告和查询

对TTL字段我们将在本节的后面介绍。 13.3.3 实现细节 为改善该协议的效率,有许多实现的细节要考虑。首先,当一个主机首次发送 I G M P报告

138

使用TCP/IP详解,卷1:协议

下载

(当第一个进程加入一个多播组)时,并不保证该报告被可靠接收(因为使用的是 IP交付服务)。 下一个报告将在间隔一段时间后发送。这个时间间隔由主机在 0~10秒的范围内随机选择。 其次,当一个主机收到一个从路由器发出的查询后,并不立即响应,而是经过一定的时 间间隔后才发出一些响应(采用“响应”的复数形式是因为该主机必须对它参加的每个组均 发送一个响应)。既然参加同一多播组的多个主机均能发送一个报告,可将它们的发送间隔设 置为随机时延。在一个物理网络中的所有主机将收到同组其他主机发送的所有报告,因为如 图1 3 - 3所示的报告中的目的地址是那个组地址。这意味着如果一个主机在等待发送报告的过 程中,却收到了发自其他主机的相同报告,则该主机的响应就可以不必发送了。因为多播路 由器并不关心有多少主机属于该组,而只关心该组是否还至少拥有一个主机。的确,一个多 播路由器甚至不关心哪个主机属于一个多播组。它仅仅想知道在给定的接口上的多播组中是 否还至少有一个主机。 在没有任何多播路由器的单个物理网络中,仅有的 I G M P通信量就是在主机加入一个新的 多播组时,支持 IP多播的主机所发出的报告。 13.3.4 生存时间字段 在图1 3 - 3中,我们注意到 I G M P报告和查询的生存时间 ( T T L )均设置为 1,这涉及到 I P首部 中的T T L字段。一个初始 T T L为0的多播数据报将被限制在同一主机。在默认情况下,待传多 播数据报的 T T L被设置为 1,这将使多播数据报仅局限在同一子网内传送。更大的 T T L值能被 多播路由器转发。 回顾6 . 2节,对发往一个多播地址的数据报从不会产生 I C M P差错。当 T T L值为0时,多播 路由器也不产生 ICMP“超时”差错。 在正常情况下,用户进程不关心传出数据报的 TTL。然而,一个例外是Traceroute 程序(第8章),它主要依据设置 TTL值来完成。既然多播应用必须能够设置要传送数 据报的TTL值,这意味着程序设计接口必须为用户进程提供这种能力。 通过增加 T T L值的方法,一个应用程序可实现对一个特定服务器的扩展环搜索 ( e x p a n d i n g ring search)。第一个多播数据报以 TTL等于1发送。如果没有响应,就尝试将 TTL设置为2,然 后3,等等。在这种方式下,该应用能找到以跳数来度量的最近的服务器。 从2 2 4 . 0 . 0 . 0到2 2 4 . 0 . 0 . 2 5 5的特殊地址空间是打算用于多播范围不超过 1跳的应用。不管 TTL值是多少,多播路由器均不转发目的地址为这些地址中的任何一个地址的数据报。 13.3.5 所有主机组 在图1 3 - 3中,我们看到了路由器的 I G M P查询被送到目的 I P地址2 2 4 . 0 . 0 . 1。该地址被称为 所有主机组地址。它涉及在一个物理网络中的所有具备多播能力的主机和路由器。当接口初 始化后,所有具备多播能力接口上的主机均自动加入这个多播组。这个组的成员无需发送 IGMP报告。

13.4 一个例子 现在我们已经了解了一些 I P多播的细节,再来看看所包含的信息。我们使 s u n主机能够支

下载

139

第13章 IGMP:Internet组管理协议使用

持多播,并将采用一些多播软件所提供的测试程序来观察具体的过程。 首先,采用一个经过修改的 n e t s t a t命令来报告每个接口上的多播组成员情况(在 3 . 9节 显示了netstat-ni命令的输出结果)。在下面的输出中,用黑体表示有关的多播组。

其中, - n参数将以数字形式显示 I P地址(而不是按名字来显示它们),- i参数将显示接 口的统计结果, -a参数将显示所有配置的接口。 输出结果中的第 2行l e 0(以太网)显示了这个接口属于主机组 2 2 4 . 0 . 0 . 1(“所有主机”), 和两行地址,后一行显示相应的以太网地址为: 01:00:5e:00:00:01。这正是我们期望看到的以 太网地址,和 1 2 . 4节介绍的地址映射一致。我们还看到其他两个支持多播的接口: S L I P接口 sl0和回送接口 lo0,它们也属于所有主机组。 我们也必须显示 I P路由表,用于多播的路由表同正常的路由表一样。黑体表项显示了所 有传往224.0.0.0的数据报均被送往以太网:

如果将这个路由表与 9 . 2节中 s u n 路由器的路由表作比较,会发现只是多了有关多播的条 目。 现在使用一个测试程序来让我们能在一个接口上加入一个多播组(不再显示使用这个测 试程序的过程)。在以太网接口( 140.252.13.33)上加入多播组 224.1.2.3。执行n e t s t a t程序 看到内核已加入这个组,并得到期望的以太网地址。用黑体字来突出显示和前面 n e t s t a t输 出的不同。

我们在输出中再次显示了其他两个接口: s l 0和l o 0,目的是为了重申加入多播组只发生 在一个接口上。

140

使用TCP/IP详解,卷1:协议

下载

图13-4显示了tcpdump对进程加入这个多播组的跟踪过程。

图13-4 当一个主机加入1个多播组时tcpdump 的输出结果

当主机加入多播组时产生第 1行的输出显示。第 2行是经过时延后的 I G M P报告,我们介绍 过报告重发的时延是 10秒内的随机时延。 在两行中显示硬件地址证实了以太网目的地址就是正确的多播地址。我们也看到了源 I P 地址为相应的 s u n主机地址,而目的 I P地址是多播组地址。同时,报告的地址和期望的多播 组地址是一致的。 最后,我们注意到,正像指明的那样, T T L是1。当T T L的值为0或1时,t c p d u m p在打印 时用方括号将它们括起来,这是因为 T T L在正常情况下均高于这些值。然而,使用多播我们 期望看到许多TTL为1的IP数据报。 在这个输出中暗示了一个多播路由器必须接收在它所有接口上的所有多播数据报。路由 器无法确定主机可能加入哪个多播组。 多播路由器的例子 继续前面的例子,但我们将在 s u n主机中启动一个多播选路的守护程序。这里我们感兴 趣的并不是多播选路协议,而是要研究所交换的 I G M P查询和报告。即使多播选路守护程序只 运行在支持多播的主机( s u n)上,所有的查询和报告都将在那个以太网上进行多播,所以 我们在该以太网中的其他系统中也能观察到它们。 在启动选路守护程序之前,加入另外一个多播组 224.9.9.9,图13-5显示了输出的结果。

图13-5 当多播选路守护程序运行时tcpdump 的输出结果

在这个输出中没有包括以太网地址,因为已经证实了它们是正确的。也删去了 T T L等于1 的说明,同样因为它们也是我们期望的那样。 当选路守护程序启动时,输出第 1行。它发出一个已经加入了组 2 2 4 . 0 . 0 . 4的报告。多播地 址2 2 4 . 0 . 0 . 4是一个知名的地址,它被当前用于多播选路的距离向量多播选路协议

DVMRP

下载

141

第13章 IGMP:Internet组管理协议使用

(Distance Vector Multicast Routing Protocol)所使用( D V M R P在RFC 1075中定义 [Waitzman, Partridge, and Deering])。 在该守护程序启动时,它也发送一个

I G M P 查询(第 2 行)。该查询的目的 I P 地址为

224.0.0.1(所有主机组),如图13-3所示。 第一个报告(第 3行)大约在 5秒后收到,报告给组 2 2 4 . 9 . 9 . 9。这是在下一个查询发出之 前(第4行)收到的唯一报告。当守护程序启动后,两次查询(第 2行和第4行)发出的间隔很 短,这是因为守护程序要将其多播路由表尽快建立起来。 第5、6和7行正是我们期望看到的: s u n主机针对它所属的每个组发出一个报告。注意组 2 2 4 . 0 . 0 . 4是被报告的,而其他两个组则是明确加入的,因为只要选路守护程序还在运行,它 始终要属于组224.0.0.4。 下一个查询位于第 8行,大约在前一个查询的 2分钟后发出。它再次引发三个我们所期望 的报告(第 9、1 0和11行)。这些报告的时间顺序与前面不同,因为接收查询和发送报告的时 间是随机的。 最后的查询在前一个查询的大约 2分钟后发出,我们再次得到了期望的响应。

13.5 小结 多播是一种将报文发往多个接收者的通信方式。在许多应用中,它比广播更好,因为多 播降低了不参与通信的主机的负担。简单的主机成员报告协议 (IGMP)是多播的基本模块。 在一个局域网中或跨越邻近局域网的多播需要使用本章介绍的技术。广播通常局限在单 个局域网中,对目前许多使用广播的应用来说,可采用多播来替代广播。 然而,多播还未解决的一个问题是在广域网内的多播。 [Deering and Cheriton 1990] 提出 扩展目前的路由协议来支持多播。 9.13节中的[Perlman 1992]讨论了广域网多播的一些问题。 [Casner and Deering 1992] 介绍了使用多播和一个称为 MBONE(多播主干)的虚拟网络 在整个Internet上传送IETF会议的情况。

习题 13.1 我们知道主机通过设置随机时延来调度 I G M P的发送。一个局域网中的主机采取什么措 施才能避免两台主机产生相同的随机时延? 13.2 在[Casner and Deering 1992]中,他们提到UDP缺少两个通过MBONE传送音频采样数据 的条件:分组失序检测和分组重复检测。你怎样在 UDP上增加这些功能?

下载

第14章 DNS:域名系统 14.1 引言 域名系统( D N S)是一种用于 T C P / I P应用程序的分布式数据库,它提供主机名字和 I P地 址之间的转换及有关电子邮件的选路信息。这里提到的分布式是指在 I n t e r n e t上的单个站点不 能拥有所有的信息。每个站点(如大学中的系、校园、公司或公司中的部门)保留它自己的 信息数据库,并运行一个服务器程序供 I n t e r n e t上的其他系统(客户程序)查询。 D N S提供了 允许服务器和客户程序相互通信的协议。 从应用的角度上看,对 D N S的访问是通过一个地址解析器( r e s o l v e r)来完成的。在 U n i x 主机中,该解析器主要是通过两个库函数 gethostbyname(3) 和gethostbyaddr(3)来访问 的,它们在编译应用程序时与应用程序连接在一起。前者接收主机名字返回 I P地址,而后者 接收IP地址来寻找主机名字。解析器通过一个或多个名字服务器来完成这种相互转换。 图4 - 2中指出了解析器通常是应用程序的一部分。解析器并不像 T C P / I P协议那样是操作系 统的内核。该图指出的另一个基本概念就是:在一个应用程序请求 T C P打开一个连接或使用 U D P发送一个数据报之前。心须将一个主机名转换为一个 I P地址。操作系统内核中的 T C P / I P 协议族对于 DNS一点都不知道。 本章我们将了解地址解析器如何使用 T C P / I P协议(主要是 U D P)与名字服务器通信。我 们不介绍运行名字服务器或有关可选参数的细节,这些技术细节的内容可以覆盖整整一本书。 (见[Albitz and Liu 1992]标准Unix解析器和名字服务器介绍)。 RFC 1034 [Mockapetris 1987a] 说明了DNS的概念和功能, RFC 1035 [Mockapetris 1987b] 详细说明了DNS 的规范和实现。 DNS最常用的版本(包括解析器和名字服务器)是 BIND— 伯克利 I n t e r n e t域名服务器。该服务器称作 n a m e d。[ D a n z i g、O b r a c z k a和Kumar 1992]分析了 DNS 在广域网中产生的通信量。

14.2 DNS 基础 D N S的名字空间和 U n i x的文件系统相似,也具有层次结构。图 14-1 显示了这种层次的组 织形式。 每个结点(图 1 4 - 1中的圆圈)有一个至多 6 3个字符长的标识。这颗树的树根是没有任何 标识的特殊结点。命名标识中一律不区分大写和小写。命名树上任何一个结点的域名就是将 从该结点到最高层的域名串连起来,中间使用一个点“.”分隔这些域名(注意这和 U n i x文件 系统路径的形成不同,文件路径是由树根依次向下的形成的)。域名树中的每个结点必须有一 个唯一的域名,但域名树中的不同结点可使用相同的标识。 以点“ .”结尾的域名称为绝对域名或完全合格的域名 F Q D N(Full Qualified Domain Name),例如sun.tuc.noao.edu.。如果一个域名不以点结尾,则认为该域名是不完全的。 如何使域名完整依赖于使用的 D N S软件。如果不完整的域名由两个或两个以上的标号组成,

143

第14章 DNS:域名系统使用

下载

则认为它是完整的;或者在该域名的右边加入一个局部后缀。例如域名 s u n通过加上局部后 缀.tuc.noao.edu.成为完整的。 未命名树根

顶级域 阿拉伯联合 酋长国

第二级域

普通域

津巴布韦

国家域

图14-1 DNS的层次组织

顶级域名被分为三个部分: 1) arpa是一个用作地址到名字转换的特殊域(我们将在 14.5节介绍)。 2) 7个3字符长的普通域。有些书也将这些域称为组织域。 3) 所有2字符长的域均是基于 I S O 3 1 6 6中定义的国家代码,这些域被称为国家域,或地理 域。



图14-2列出了7个普通域的正式划分。 在D N S中,通常认为 3字符长的普通域仅 用于美国的组织机构, 2字符长的国家域则用 于每个国家,但情况并不总是这样。许多非美 国的组织机构仍然使用普通域,而一些美国的





商业组织 教育机构 其他美国政府部门 国际组织 美国军事网点 网络 其他组织

组织机构也使用 . u s的国家域( R F C 1 4 8 0 [Cooper and Postel 1993] 详细描述了 . u s域)。

图14-2 3字符长的普通域

普通域中只有.gov和.mil域局限于美国。 许多国家将它们的二级域组织成类似于普通域的结构:例如, . a c . u k是英国研究机构的 二级域名, .co.uk则是英国商业机构的二级域名。 D N S的一个没在如图 1 4 - 1中表示出来的重要特征是 D N S中域名的授权。没有哪个机构来 管理域名树中的每个标识,相反,只有一个机构,即网络信息中心 NIC负责分配顶级域和委派 其他指定地区域的授权机构。

144

使用TCP/IP详解,卷1:协议

下载

一个独立管理的 D N S 子树称为一个区域 ( z o n e )。一个常见的区域是一个二级域,如 n o a o . e d u。许多二级域将它们的区域划分成更小的区域。例如,大学可能根据不同的系来 划分区域,公司可能根据不同的部门来划分区域。 如果你熟悉Unix的文件系统,会注意到 DNS树中区域的划分同一个逻辑 Unix文件 系统到物理磁盘分区的划分很相似。正如无法确定图 14-1中区域的具体位置,我们也 不知道一个Unix文件系统中的目录位于哪个磁盘分区。 一旦一个区域的授权机构被委派后,由它负责向该区域提供多个名字服务器。当一个新 系统加入到一个区域中时,该区域的 D N S管理者为该新系统申请一个域名和一个 I P地址,并 将它们加到名字服务器的数据库中。这就是授权机构存在的必要性。例如,在一个小规模的 大学,一个人就能完成每次新系统的加入。但对一个规模较大的大学来说,这一工作必须被 专门委派的机构(可能是系)来完成,因为一个人已无法维持这一工作。 一个名字服务器负责一个或多个区域。一个区域的管理者必须为该区域提供一个主名字 服务器和至少一个辅助名字服务器。主、辅名字服务器必须是独立和冗余的,以便当某个名 字服务器发生故障时不会影响该区域的名字服务。 主、辅名字服务器的主要区别在于主名字服务器从磁盘文件中调入该区域的所有信息, 而辅名字服务器则从主服务器调入所有信息。我们将辅名字服务器从主服务器调入信息称为 区域传送。 当一个新主机加入一个区域时,区域管理者将适当的信息(最少包括名字和 I P地址)加 入到运行在主名字服务器上的一个磁盘文件中,然后通知主名字服务器重新调入它的配置文 件。辅名字服务器定时(通常是每隔 3小时)向主名字服务器询问是否有新数据。如果有新数 据,则通过区域传送方式获得新数据。 当一个名字服务器没有请求的信息时,它将如何处理?它必须与其他的名字服务器联系。 (这正是 DNS的分布特性)。然而,并不是每个名字服务器都知道如何同其他名字服务器联系。 相反,每个名字服务器必须知道如何同根的名字服务器联系。 1 9 9 3年4月时有 8个根名字服务 器,所有的主名字服务器都必须知道根服务器的 I P地址(这些 I P地址在主名字服务器的配置 文件中,主服务器必须知道根服务器的 I P地址,而不是它们的域名)。根服务器则知道所有二 级域中的每个授权名字服务器的名字和位置(即 I P地址)。这意味着这样一个反复的过程:正 在处理请求的名字服务器与根服务器联系,根服务器告诉它与另一个名字服务器联系。在本 章的后面我们将通过一些例子来详细了解这一过程。 你可以通过匿名的FTP获取当前的根服务器清单。具体是从ftp.rs.internic.net 或nic.ddn.mil

获取文件netinfo/root-servers.txt。

D N S的一个基本特性是使用超高速缓存。即当一个名字服务器收到有关映射的信息(主 机名字到 I P地址)时,它会将该信息存放在高速缓存中。这样若以后遇到相同的映射请求, 就能直接使用缓存中的结果而无需通过其他服务器查询。 1 4 . 7节显示了一个使用高速缓存的 例子。

14.3 DNS的报文格式 DNS定义了一个用于查询和响应的报文格式。图 14-3显示这个报文的总体格式。

145

第14章 DNS:域名系统使用

下载 标识

标志

问题数

资源记录数

授权资源记录数

额外资源记录数

12字节

查询问题 回答 (资源记录数可变) 授权 (资源记录数可变) 额外信息 (资源记录数可变)

图14-3 DNS查询和响应的一般格式

这个报文由 12字节长的首部和 4个长度可变的字段组成。 标识字段由客户程序设置并由服务器返回结果。客户程序通过它来确定响应与查询是否 匹配。 16 bit的标志字段被划分为若干子字段,如图 14-4所示。

图14-4 DNS报文首部中的标志字段

我们从最左位开始依次介绍各子字段: • QR 是1 bit 字段:0表示查询报文, 1表示响应报文。 • opcode是一个4 bit字段:通常值为 0(标准查询),其他值为 1(反向查询)和 2(服务器 状态请求)。 • A A是1 bit 标志,表示“授权回答 (authoritative answer)”。该名字服务器是授权于该域 的。 • T C是1 bit字段,表示“可截断的 ( t r u n c a t e d )”。使用U D P时,它表示当应答的总长度超 过512字节时,只返回前 512个字节。 • R D是1 bit字段表示“期望递归( recursion desired)”。该比特能在一个查询中设置,并 在响应中返回。这个标志告诉名字服务器必须处理这个查询,也称为一个递归查询。如 果该位为 0,且被请求的名字服务器没有一个授权回答,它就返回一个能解答该查询的 其他名字服务器列表,这称为迭代查询。在后面的例子中,我们将看到这两种类型查询 的例子。 • R A是1 bit字段,表示“可用递归”。如果名字服务器支持递归查询,则在响应中将该比 特设置为 1。在后面的例子中可看到大多数名字服务器都提供递归查询,除了某些根服 务器。

146

使用TCP/IP详解,卷1:协议

下载

• 随后的3 bit字段必须为 0。 • r c o d e是一个4 bit的返回码字段。通常的值为 0(没有差错)和 3(名字差错)。名字差错 只有从一个授权名字服务器上返回,它表示在查询中制定的域名不存在。 随后的 4个16 bit 字段说明最后 4个变长字段中包含的条目数。对于查询报文,问题 ( q u e s t i o n )数通常是 1,而其他 3项则均为 0。类似地,对于应答报文,回答数至少是 1,剩下的 两项可以是 0或非0。 14.3.1 DNS查询报文中的问题部分 问题部分中每个问题的格式如图 14-5所示,通常只有一个问题。 31

15 16

0

查询名

查询类

查询类型

图14-5 DNS查询报文中问题部分的格式

查询名是要查找的名字,它是一个或多个标识符的序列。每个标识符以首字节的计数值 来说明随后标识符的字节长度,每个名字以最后字节为 0结束,长度为 0的标识符是根标识符。 计数字节的值必须是 0 ~ 6 3的数,因为标识符的最大长度仅为 6 3(在本节的后面我们将看到计 数字节的最高两比特为 1,即值192~255,将用于压缩格式)。不像我们已经看到的许多其他报 文格式,该字段无需以整 32 bit边界结束,即无需填充字节。 图14-6显示了如何存储域名 gemini.tuc.noao.edu。

计数

计数

计数

图14-6 域名gemini.tuc.noao.edu

计数

计数

的表示

每个问题有一个查询类型,而每个响应(也称一个资源记录,我们下面将谈到)也有一 个类型。大约有 2 0个不同的类型值,其中的一些目前已经过时。图 1 4 - 7显示了其中的一些值。 查询类型是类型的一个超集 (superset):图中显示的类型值中只有两个能用于查询类型。 名











类型?

查询 类型

IP地址 名字服务器 规范名称 指针记录 主机信息 邮件交换记录 或

对区域转换的请求 对所有记录的请求

图14-7 DNS问题和响应的类型值和查询类型值

147

第14章 DNS:域名系统使用

下载

最常用的查询类型是 A类型,表示期望获得查询名的 I P地址。一个 P T R查询则请求获得一 个I P地址对应的域名。这是一个指针查询,我们将在 1 4 . 5节介绍。其他的查询类型将在 1 4 . 6节 介绍。 查询类通常是1,指互联网地址(某些站点也支持其他非 IP地址)。 14.3.2 DNS响应报文中的资源记录部分 D N S报文中最后的三个字段,回答字段、授权字段和附加信息字段,均采用一种称为资 源记录RR(Resource Record)的相同格式。图 14-8显示了资源记录的格式。

域名 类

类型 生存时间 资源数据长度 资源数据

图14-8 DNS资源记录格式

域名是记录中资源数据对应的名字。它的格式和前面介绍的查询名字段格式(图

1 4 - 6)

相同。 类型说明 R R的类型码。它的值和前面介绍的查询类型值是一样的。类通常为

1,指

Internet数据。 生存时间字段是客户程序保留该资源记录的秒数。资源记录通常的生存时间值为 2天。 资源数据长度说明资源数据的数量。该数据的格式依赖于类型字段的值。对于类型 1(A 记录)资源数据是 4字节的IP地址。 现在已经介绍了 D N S查询和响应的基本格式,我们将使用 t c p d u m p程序来观察具体的交 换过程。

14.4 一个简单的例子 让我们从一个简单的例子来了解一个名字解析器与一个名字服务器之间的通信过程。在 sun主机上运行 Telnet客户程序远程登录到 gemini主机上,并连接 daytime服务器: 前3行的输出是从Telnet客户

这是从daytime服务器的输出 这是从Telnet客户的输出

在这个例子中,我们引导 s u n主机(运行 Te l n e t客户程序)上的名字解析器来使用位于 noao.edu(140.252.1.54)的名字服务器。图 14-9显示了这三个系统的排列情况。

148

使用TCP/IP详解,卷1:协议

下载

和以前提到的一样,名字解析器是客户程序

daytime 服务器

名字服 务器

的一部分,并且在 Te l n e t客户程序与 d a y t i m e服务 器建立 T C P连接之前,名字解析器就能通过名字 服务器获取 IP地址。 在这个图中,省略了 s u n主机与 1 4 0 . 2 5 2 . 1以 太网的连接实际上是一个 S L I P连接的细节(参见

Telnet 客户

封2的插图),因为它不影响我们的讨论。通过在 S L I P链路上运行 t c p d u m p程序来了解名字解析器 与名字服务器之间的分组交换。

图14-9 用于简单DNS例子的系统

sun主机上的文件/etc/resolv.conf将告诉名字解析器作什么: sun % cat /etc/resolv.conf nameserver 140.252.1.54 domain tuc.noao.edu

第1行给出名字服务器—主机noao.edu的IP地址。最多可说明 3个名字服务器行来提供 足够的后备以防名字服务器故障或不可达。域名行说明默认域名。如果要查找的域名不是一个 完全合格的域名(没有以句点结束),那末默认的域名.tuc.noao.edu将加到待查名后。 图14-10显示了名字解析器与名字服务器之间的分组交换。

图14-10 向名字服务器查询主机名gemini.tuc.noao.edu

的输出

让 t c p d u m p 程序不再显示每个 I P 数 据 报 的 源 地 址 和 目 的 地 址 。 相 反 , 它 显 示 客 户 (r e s o l v e r)的I P地址1 4 0 . 2 5 2 . 1 . 2 9和名字服务器的 I P地址1 4 0 . 2 5 2 . 1 . 5 4。客户的临时端口号为 1 4 4 7,而名字服务器则使用熟知端口 5 3。如果让 t c p d u m p程序显示名字而不是 I P地址,它可 能会和同一个名字服务器联系(作指示查询),以致产生混乱的输出结果。 第1行中冒号后的字段( 1+)表示标识字段为1,加号“+”表示RD标志(期望递归)为 1。 默认情况下,名字解析器要求递归查询方式。 下一个字段为 A ?,表示查询类型为 A(我们需要一个 I P地址),该问号指明它是一个查询 (不是一个响应)。待查名字显示在后面: g e m i n i . t u c . n o a o . e d u .。名字解析器在待查名 字后加上句点号指明它是一个绝对字段名。 在U D P数据报中的用户数据长度显示为 3 7字节: 1 2字节为固定长度的报文首部(图 1 4 3);2 1字节为查询名字(图 1 4 - 6),以及用于查询类型和查询类的 4个字节。在D N S报文中无 需填充数据。 tcpdump程序的第2行显示的是从名字服务器发回的响应。 1*是标识字段,星号表示设置 AA标志(授权回答) (该服务器是noao.edu域的主域名服务器,其回答在该域内是可相信的。 ) 输出结果 2 / 0 / 0表示在响应报文中最后 3个变长字段的资源记录数:回答 R R数为 2,授权 RR 和附加信息 R R数均为 0。t c p d u m p仅显示第一个回答,回答类型为 A(I P地址),值为 140.252.1.11。

149

第14章 DNS:域名系统使用

下载

为什么我们的查询会得到两个回答?这是因为 g e m i n i是多接口主机,因此得到两个 I P地 址。事实上,另一个有用的 D N S工具是一个称为 h o s t的公开程序,它能将查询传递给名字服 务器,并显示返回的结果。如果使用这个程序,就能看到这个多地址主机的两个 IP地址: sun % host gemini gemini.tuc.noao.edu gemini.tuc.noao.edu

A A

140.252.1.11 140.252.3.54

图1 4 - 1 0中的第一个回答与 h o s t命令的第一行输出均是在同一子网( 1 4 0 . 2 5 2 . 1)的I P地 址。这不是偶然的。如果名字服务器和发出请求的主机位于相同的网络(或子网),那么 BIND会排列显示的结果以便在相同网络的地址优先显示。 我们还可以使用其他的地址来访问gemini主机,但它可能不太有效。在这个例子 中,使用traceroute显示出从子网140.252.1到140.252.3的正常路由不经过gemini主机, 而是经过连接这两个网络的另一个路由器。因此在这种情况下,如果通过其他的IP地址 (140.252.3.54)来访问gemini主机,所有分组均需经过额外的一跳。我们将在25.9节重 新回到这个例子来探讨替换路由,那时可使用SNMP来查看一个路由器的路由表。 还有其他一些程序能很容易地对DNS进行交互访问。nslookup是大多数DNS实现 中包含的程序。[Albitz and Liu 1992]的第10章详细介绍了该程序的使用方法。dig (“域 名Internet搜索(Domain Internet Groper)”)程序是另一个查询 DNS服务器的公开工具。 doc (“域名模糊控制(Domain Obscenity Control)”)是一个使用dig的外壳脚本程序,它 能向合适的名字服务器发送查询来诊断含义不清的域名,并对返回的查询结果进行简 单的分析。附录F有如何获得这些程序的详细介绍。 在这个例子中要说明的最后一个问题是在查询结果中的 U D P数据长度: 6 9字节。为说明 这些字节需要知道以下两点: 1) 在返回的结果中包含查询问题。 2) 在返回的结果中会有许多重复的域名,因此使用压缩方式。在这个例子中,域名 gemini.tuc.noao.edu出现了三次。 压缩方法很简单,当一个域名中的标识符是压缩的,它的单计数字节(范围由 0~6 3)中 的最高两位将被设置为 11。这表示它是一个 16 bit指针而不再是 8 bit的计数字节。指针中的剩 下14 bit说明在该 D N S报文中标识符所在的位置(起始位置由标识字段的第一字节起算)。我 们明确说明只要一个标识符是压缩的,就可以使用这种指针,而不一定非要一个完整的域名 压缩时才能使用。因为一个指针可能指向一个完整的域名,也可能只指向域名的结尾部分 (这是因为给定域名的结尾标识符是相同的)。 图1 4 - 11显示了对应于图 1 4 - 1 0的第2行的D N S应答的格式。我们也显示了 I P首部和 U D P首 部来重申 D N S报文被封装在 U D P数据报中。还明确显示了在问题部分的域名中各标识符的计 数字节。返回的两个回答除了返回的 I P地址不同外,其余都是一样的。在这个例子中,每个 回答中的指针值为 12,表示从DNS首部开始的偏移量。 在这个例子中最后要注意的是使用 telnet命令后输出的第 2行,这里重复一下: sun % telnet gemini daytime 我们只键入 g e m i n i Trying 140.252.1.11 ... C o n n e c t e d t o g e m i n i . t u c . n o a o . e d u . 但T e l n e t客户输出F Q D N

150

使用TCP/IP详解,卷1:协议

下载 IP数据报 UDP数据报 DNS报文

IP首部 20字节

UDP 首部

DNS 首部

8字节 12字节

域名

问题 (图14-5)

回答#1(RR) (图14-8) 字节

25字节

类型 类

指针 类型 类

回答#2(RR) (图14-8) 字节

长度

地址

21字节

图14-11 对应于图14-10中第2行DNS应答的格式

我们仅仅输入了主机名 (g e m i n i)而不是F Q D N,但Te l n e t客户程序部输出了 F Q D N。这是 由于Telnet程序通过调用名字解析器( g e t h o s t b y n a m e)对输入的名字进行查询,返回的结 果包括IP地址和FQDN。Telnet程序就输出它试图与之建立 TCP连接的IP地址,当连接建立后, 它就输出FQDN。 如果在输入Te l n e t命令后间隔很长时间才显示 I P地址,这个时延是由名字解析器和名字服 务器在由域名到 IP地址的解析所引起的。而显示 Trying 到显示C o n n e c t e d t的时延则是 o 由客户与服务器建立 TCP连接所引起的,与 DNS无关。

14.5 指针查询 D N S中一直难于理解的部分就是指针查询方式,即给定一个 I P地址,返回与该地址对应 的域名。 首先回到图14-1,查看一下顶级域arpa,及它下面的in-addr域。当一个组织加入Internet, 并获得DNS域名空间的授权,如noao.edu,则它们也获得了对应IP地址的in-addr.arpa域名 空间的授权。在noao.edu这个例子中,它是网络号为140.252的B类网络。在DNS树中结点inaddr.arpa的下一级必须是该IP地址的第一字节(例中为 140),再下一级为该IP地址的下一个 字节(252),依此类推。但应牢记的是 DNS名字是由DNS树的底部逐步向上书写的。这意味着 对于IP地址为140.252.13.33的sun主机,它的DNS名字为33.13.252.140.in-addr.arpa。 必须写出4字节的IP地址,因为授权的代表是基于网络号: A类地址是第一字节, B类地址 是第一、二字节,C类地址则是第一、二、三字节。 IP地址的第一字节一定位于 in-addr的下 一级,但FQDN却是自树底往上书写的。如果FQDN由顶往下书写,则这个IP地址的DNS名字将 是arpa.in-addr.140.252.13.33,而它所对应的域名将是edu.noao.tuc.sun。 如果D N S树中没有独立的分支来处理这种地址—名字的转换,将无法进行这种反向转换, 除非从树根开始依次尝试每个顶级域。毫不夸张地说,这将需要数天或数周的时间。虽然反 写IP地址和特殊的域名会造成某些混乱,但 in-addr解决方案仍是一种最有效的方式。 只有在使用host程序或tcpdump程序直接同DNS打交道时,才会担心in-addr域和反写IP 地址影响我们。从应用的角度上看,正常的名字解析器函数(gethostbyaddr)将接收一个IP 地址并返回对应主机的有关信息。反转这些字节和添加in-addr.arpa域均由该函数自动完成。

151

第14章 DNS:域名系统使用

下载 14.5.1 举例

使用h o s t程序完成一个指针查询,并使用 t c p d u m p程序来观察这些分组。例子中的设置 和图 1 4 - 9相同,在 s u n主机上运行 h o s t程序,名字服务器在主机 n o a o . e d u上。我们指明 svr4主机的IP地址: sun % host 140.252.13.34 Name: svr4.tuc.noao.edu Address: 140.252.13.34

既然I P地址是仅有的命令行参数, h o s t程序将自动产生指针查询。图 1 4 - 1 2显示了 t c p d u m p 的输出。

图14-12 一个指针查询的tcpdump 输出

第1行显示标识符为 1,期望递归标志设置为 1(加号“ +”),查询类型为 P T R(应注意:问号 “?”表示它是一个查询而不是响应)。4 4字节的数据包括 1 2字节的 D N S报文首部、 2 8字节的 域名标识符和4字节的查询类型和查询类。 查询结果包含一个回答 R R,且为授权回答比特置 1(带星号)。R R的类型是 P T R,资源数 据中包含该域名。 从名字解析器传递给名字服务器的指针查询不再是

32 bit 的 I P 地址,而是域名

34.13.252.140.in-addr.arpa。 14.5.2 主机名检查 当一个IP数据报到达一个作为服务器的主机时,无论是 UDP数据报还是 TCP连接请求,服 务器进程所能获得的是客户的 I P地址和端口号( U D P或T C P)。某些服务器需要客户的 I P地址 来获得在 D N S中的指针记录。在 2 7 . 3节会看到这样的例子,从未知的 I P地址使用匿名 F T P访问 服务器。 其他的一些服务器如 Rlogin服务器(第 26章)不但需要客户的 IP地址来获得指针记录,还 要向D N S询问该 I P地址所对应的域名,并检查返回的地址中是否有地址与收到的数据报中的 源I P地址匹配。该检查是因为 . r h o s t s文件(见 2 6 . 2节)中的条目仅包含主机名,而没有 I P 地址,因此主机需要证实该主机名是否对应源 IP地址。 某些厂商将该项检查自动并入其名字解析器的例程中,特别是函数 g e t h o s t b y a d d r。 这使得任何使用名字解析器的程序均可获得这种检查,而无需在应用中人为地进行这项检查。 来看一个使用SunOS 4.13名字解析器库的例子。我们编制了一个简单的程序通过调用函数 gethostbyaddr来完成一个指针查询。我们已在文件/etc/resolv.conf中将名字服务器设 置为noao.edu,sun主机通过SLIP链路与它相连。图14-13显示了当调用函数gethostbyaddr 获取与IP地址140.252.1.29(sun主机)对应的名字时,tcpdump在SLIP链路上收到的内容。 第1行是预期的指针查询,第 2行是预期的响应。但第 3行显示了该名字解析器函数自动对 第2行返回的名字发出一个 IP地址查询。既然 s u n主机有两个 IP地址,第4行的响应就包括两个

152

使用TCP/IP详解,卷1:协议

下载

回答记录。如果这两个地址中没有与 g e t h o s t b y a d d r输入参数匹配的地址,函数会向系统 的日志发送一条报文,并向应用程序返回差错。

图14-13 调用名字解析器函数执行指针查询

14.6 资源记录 至今我们已经见到了一些不同类型的资源记录(RR):IP地址查询为A类型,指针查询为类型 PTR。也已看到了由名字服务器返回的资源记录:回答RR、授权RR和附加信息RR。现有大约20种 不同类型的资源记录,下面将介绍其中的一些。另外,随着时间的推移,会加入更多类型的 RR。 A

一个A记录定义了一个 IP地址,它存储32 bit的二进制数。

PTR

指针记录用于指针查询。 I P地址被看作是 i n - a d d r . a r p a 域下的一个域名 (标识符串)。

CNAME

这表示“规范名字 (canonical name)”。它用来表示一个域名(标识符串),而 有规范名字的域名通常被称为别名 ( a l i a s )。某些 F T P服务器使用它向其他的系 统提供一个易于记忆的别名。 例如,gated服务器(10.3节提到)可通过匿名FTP从gated.cornell.edu 获得,但这里并没有叫做 gated的系统,这仅是为其他系统提供的别名。其他 系统的规范名为gated.cornell.edu。 sun % h o s t - t c n a m e g a t e d . c o r n e l l . e d u gated.cornell.edu CNAM COMET.CIT.CORNELL.EDU

这里使用的-t选项来指明它是特定的查询类型。 HINFO

表示主机信息:包括说明主机 C P U和操作系统的两个字符串。并非所有的站 点均提供它们系统的 HINFO记录,并且提供的信息也可能不是最新的。 sun % host -t hinfo sun sun.tuc.noao.edu HINFO Sun-4/25

MX

Sun4.1.3

邮件交换记录,用于以下一些场合:(1)一个没有连到 Internet的站点能将一个 连到Internet的站点作为它的邮件交换器。这两个站点能够用一种交替的方式交 换到达的邮件,而通常使用的协议是 UUCP协议。(2)MX记录提供了一种将无 法到达其目的主机的邮件传送到一个替代主机的方式。(3)M X记录允许机构 提供供他人发送邮件的虚拟主机,如cs.university.edu,即使这样的主机 名根本不存在。(4)防火墙网关能使用MX记录来限制外界与内部系统的连接。 许多不能与 I n t e r n e t连接的站点通过 U U C P链路与一个连接在 I n t e r n e t上的站点 如U U N E T相连接。通过 M X记录能使用 u s e r @ h o s t这种邮件地址向那个站点 发送电子邮件。例如,一个假想的域 foo.com可能有下面的MX记录:

153

第14章 DNS:域名系统使用

下载 sun % host -t mx foo.com foo.com MX foo.com MX

relay1.UU.NET relay2.UU.NET

M X记录能被连接在互联网主机中的邮件处理器使用。在这个例子中,其他的 邮件处理器则被告知“如果有邮件要发往 u s e r @ f o o . c o m,就将邮件送到 relay1.uu.net或relay2.uu.net。” 每个M X记录被赋于一个 16 bit 的整数值,该值称为优先值。如果一个目的主 机有多个MX记录,它们按优先值由小到大的顺序使用。 另一个M X记录的例子是处理主机脱机工作或不可达的情况。邮件处理器仅在 无法使用 T C P与目的主机连接时才使用 M X记录。作者的主系统通过 S L I P链路 与互联网相连,它在大多数时间内是脱机工作的,我们有

为了显示优先值,我们使用了- v选项(该选项也会导致其他字段的输出)。第 二个字段, 8 6 4 0 0,是寿命值,单位为秒。因此该 T T L值为2 4小时(2 4×6 0× 6 0)。第3列,I N,是(I n t e r n e t)类。我们看到直接传送给主机自身(第一个 MX记录)有最低的优先值 0。如果没有工作(即 SLIP链路断开),会使用下一 个更高优先值( 1 0)的邮件记录,并试图向主机 n o a o . e d u传送。如果它仍 没有成功,发送将超时并在以后重新发送。 NS

名字服务器记录。它说明一个域的授权名字服务器。它由域名表示(符号串)。 在下节将看到这些类型的例子。

这些是RR的常用类型。将在后面的例子中遇到它们。

14.7 高速缓存 为了减少 I n t e r n e t上D N S的通信量,所有的名字服务器均使用高速缓存。在标准的 U n i x实 现中,高速缓存是由名字服务器而不是由名字解析器维护的。既然名字解析器作为每个应用 的一部分,而应用又不可能总处于工作状态,因此将高速缓存放在只要系统(名字服务器) 处于工作状态就能起作用的程序中显得很重要。这样任何一个使用名字服务器的应用均可获 得高速缓存。在该站点使用这个名字服务器的任何其他主机也能共享服务器的高速缓存。 在迄今为止(图 1 4 - 9)所举例子的网络环境中,在 s u n主机上运行客户程序,通过主机 n o a o . e d u的S L I P链路访问名字服务器。现在将改变这种设置,在 s u n主机上运行名字服务 器。在这种情况下,如果使用 t c p d u m p监视在S L I P链路上的 D N S通信量,将只能看到服务器 因超出其高速缓存而不能处理的查询。 在默认情况下,名字解析器将在本地主机上( U D P端口号为 5 3或T C P端口号为 5 3)寻找 名字服务器。从名字解析器文件中删除 nameserver行,而留下 domain行: sun % c a t /etc/resolv.conf domain tuc.noao.edu

在这个文件中缺少 namerserver指示将导致名字解析器使用本地主机上的名字服务器。

154

使用TCP/IP详解,卷1:协议

下载

使用host命令执行下列查询: sun % host ftp.uu.net ftp.uu.net A

192.48.96.9

图14-14显示了这个查询的输出结果。

图14-14 执行host ftp.uu.net后的tcpdump 输出

这次在tcpdump中使用了新的选项。使用-w选项来收集进出UDP或TCP 53号端口 的所有数据。将这些原始数据记录在一个文件中供以后处理,同时防止 tcpdump试图 调用名字解析器来显示与那个 IP地址相对应的域名。执行查询后,终止 tcpdump并使 用-r选项再次运行它。它会读取含有原始数据的文件并产生正式的输出显示(如图 1414)。这个过程要花费几秒钟,因为tcpdump调用了它自己的名字解析器。 在t c p d u m p输出中要注意的第一点是标识符 ( i d e n t i f i e r )是小整数( 2和3)。这是因为我们 关闭这个名字服务器,后又重新启动它来强制清空它的高速缓存。当名字服务器启动时,它 将标识符初始化为 1。 当键入查询,查找主机 f t p . u u . n e t的I P地址,该名字服务器就同 8个根名字服务器中的 一个 n s . n i c . d d n . m i l(第1行)取得联系。这是以前见到的正常的 A类型查询,但要注意 的是它的期望递归表示没有说明(如果该标志被设置,在标识符 2的后边会跟着一个加号)。 在以前的例子中,经常看到名字解析器设置期望递归标志,但这里的名字服务器在与某个根 服务器联系时没有设置这个标志。这是因为不应该向根名字服务器发出期望递归的查询,它 们仅用来寻找其他授权名字服务器的地址。 第2行显示返回的响应中没有回答资源记录,而包含 5个授权资源记录和 5个附加信息资源 记录。标识符 2后的减号表示期望递归标志( R A)没有被设置。即使我们要求进行递归查询, 这个根名字服务器也不会回答期望递归查询。 尽管tcpdump没有显示返回的10个资源记录,我们也能执行host命令来查看高速缓存的内容:

155

第14章 DNS:域名系统使用

下载

这次采用 - v选项查看的不仅仅只是 A记录。它显示出对于域 u u . n e t有5个授权名字服务 器,而由根名字服务器返回的 5个附加信息资源记录中含有这 5个名字服务器的 I P地址。这避 免了在查找其中的某个名字服务器的地址时,无需再次与根名字服务器联系。这是 D N S中的 另一个实现优化。 h o s t命令指出这个回答不是授权的,这是因为这个回答来自名字服务器的高速缓存,而 不是来自授权名字服务器。 回到图1 4 - 1 4中的第3行,我们的名字服务器与第一个授权名字服务器( n s . u u . n e t)询 问同一个问题: f t p . u u . n e t的I P地址?这次我们的服务器设置了期望递归标志。返回的应 答(第4行)包含一个回答资源记录。 而后我们再次执行 host命令,询问相同的名字: sun % h o s t f t p . u u . n e t ftp.uu.net

A

192.48.96.9

这时t c p d u m p没有输出,这正是我们所期望的,因为由 h o s t命令返回的回答来自于名字 服务器的高速缓存。 再次执行 host命令,查找 ftp.ee.lbl.gov的地址: sun % h o s t f t p . e e . l b l . g o v ftp.ee.lbl.gov CNAME ee.lbl.gov A

ee.lbl.gov 128.3.112.20

图14-15显示了这时的tcpdump输出。

图14-15 对ftp.ee.lbl.gov

主机的tcpdump 输出

这时第1行显示我们的服务器与另一个根名字服务器(c.nyser.net)联系。一个名 字服务器通常轮询不同的根名字服务器来获得往返时间估计,然后选择往返时间最小 的服务器。 既然我们的服务器向一个根服务器发出查询,那么期望递归标志不应被设置。正如我们 在图1 4 - 1 4中所看到的该名字服务器并不清除期望递归标志(即便这样,一个名字服务器还是 不应该向一个根名字服务器发出期望递归的查询)。 在第2行返回的响应中不包含回答资源记录,但含有4个授权记录和4个附加信息资源记录。 正如我们所猜测的那样, 4个授权资源记录是供主机 f t p . e e . l b l . g o v进行域名服务的名字 服务器名,其他 4个记录则是这4个服务器的 IP地址。 第3行是向名字服务器 n s l . l b l . g o v(第2行中返回的 4个名字服务器中的第一个)发出 的查询请求。它的期望递归标志是被设置的。 第4行返回的响应和以往的响应不同。返回了两个回答资源记录, t c p d u m p指出其中的第 一个是CNAME资源记录。 ftp.ee.lbl.gov的规范名称是ee.lbl.gov。

156

使用TCP/IP详解,卷1:协议

下载

这是CNAME记录常见的用法。LBL的FTP站点的名字通常是以ftp开始的,但它 可能不时地从一个主机移到另一个主机。用户只需要知道 ftp.ee.lbl.gov,必要时 DNS会用它的规范名进行替换。 记得我们在运行 h o s t程序时,它显示了规范域名的 C N A M E和I P地址。这是因为响应 (图1 4 - 1 5中的第 4行)中含有两个回答资源记录,第一个是 C N A M E,而第二个是 A记录。如 果A记录没有随C N A M E记录返回,我们的服务器将发出另一个查询请求,询问 e e . l b l . g o v 的IP地址。这是另一个 DNS的实现优化—在一个响应中同时返回一个规范域名的 CNAME记 录和A记录。

14.8 用UDP还是用TCP 注意到 D N S名字服务器使用的熟知端口号无论对 U D P还是T C P都是5 3。这意味着 D N S均 支持U D P和T C P访问,但我们使用 t c p d u m p观察的所有例子都是采用 U D P。那么这两种协议 都在什么情况下采用以及采用的理由都是什么呢? 当名字解析器发出一个查询请求,并且返回响应中的 T C(删减标志)比特被设置为 1时, 它就意味着响应的长度超过了 5 1 2个字节,而仅返回前 5 1 2个字节。在遇到这种情况时,名字 解析器通常使用 TCP重发原来的查询请求,它将允许返回的响应超过 512个字节(回想在 11.10 节讨论的 U D P数据报的最大长度)。既然T C P能将用户的数据流分为一些报文段,它就能用多 个报文段来传送任意长度的用户数据。 此外,当一个域的辅助名字服务器在启动时,将从该域的主名字服务器执行区域传送。 我们也说过辅助服务器将定时(通常是 3小时)向主服务器进行查询以便了解主服务器数据是 否发生变动。如果有变动,将执行一次区域传送。区域传送将使用 T C P,因为这里传送的数 据远比一个查询或响应多得多。 既然D N S主要使用 U D P,无论是名字解析器还是名字服务器都必须自己处理超时和重传。 此外,不像其他的使用 U D P的I n t e r n e t应用(T F T P、B O O T P和S N M P),大部分操作集中在局 域网上, D N S查询和响应通常经过广域网。分组丢失率和往返时间的不确定性在广域网上比 局域网上更大。这样对于 DNS客户程序,一个好的重传和超时程序就显得更重要了。

14.9 另一个例子 让我们通过另一个例子将已经介绍的许多 D N S特性作一个综合性回顾。先启动 Rlogin 客 户程序,然后连接到一个位于其他域的 R l o g i n服务器。图 1 4 - 1 6显示了发生的分组交换过程。 下面发生的 11个步骤都假定客户和服务器的高速缓存中没有任何信息。 1) 客户程序启动后,调用它的名字解析器函数将我们键入的主机名转换为一个 I P地址。 一个A类型的查询请求被送往一个根服务器。 2) 由根服务器返回的响应中包含为该服务器所在域服务的名字服务器名。 3) 客户端的名字解析器将向该服务器的名字服务器重发上述 A类型查询,这个查询通常 是将期望递归标志设置为 1。 4) 返回的应答中包含 Rlogin服务器的IP地址。 5) Rlogin客户和R l o g i n服务器建立一个 T C P连接(第 1 8章将提供该步骤的细节)。客户和 服务器的TCP模块间将交换3个分组。

157

第14章 DNS:域名系统使用

下载 Rlogin 服务器

服务器的名 字服务器

Rlogin 客户

客户的名 字服务器

根名字 服务器

根名字 服务器

图14-16 启动Rlogin客户和服务器的分组交换过程

6) Rlogin服务器收到来自客户的连接请求后,调用它的名字解析器通过 TCP连接请求中的 I P地址获得客户主机名。这是一个 P T R查询请求,由一个根名字服务器处理。这个根名字服 务器可以不同于步骤 1中客户使用的根名字服务器。 7) 这个根名字服务器的响应中含有为客户的 in-addr.arpa域的名字服务器。 8) 服务器上的名字解析器将向客户的名字服务器重传上述 PTR查询。 9) 返回的PTR应答中含有客户主机的 FQDN。 10) 服务器的名字解析器向客户的名字服务器发送一个 A类型查询请求,查找前一步返回 的名字对应的 IP地址。这可能由服务器中的 g e t h o s t b y a d d r函数自动完成,正如我们在 14.5 节中介绍的那样,否则 R l o g i n服务器将完成这一步。此外,客户的名字服务器常常就是客户 的in-addr.arpa名字服务器,但这不是必需的。 11) 从客户的名字服务器返回的响应含有客户主机的 A记录。 R l o g i n服务器将客户的 T C P 连接请求中的IP地址与A记录作比较。 高速缓存将减少这个图中交换的分组数目。

14.10 小结 D N S是任何与 I n t e r n e t相连主机必不可少的一部分,同时它也广泛用于专用的互联网。层 次树是组成 DNS域名空间的基本组织形式。 应用程序通过名字解析器将一个主机名转换为一个 I P地址,也可将一个 I P地址转换为与 之对应的主机名。名字解析器将向一个本地名字服务器发出查询请求,这个名字服务器可能 通过某个根名字服务器或其他名字服务器来完成这个查询。 所有的DNS查询和响应都有相同的报文格式。这个报文格式中包含查询请求和可能的回答资 源记录、授权资源记录和附加资源记录。通过许多例子了解了名字解析器的配置文件以及DNS的 优化措施:指向域名的指针(减少报文的长度)、查询结果的高速缓存、in-addr.arpa域(查 找IP地址对应的域名)以及返回的附加资源记录(避免主机重发同一查询请求) 。

习题 14.1 讨论一个 DNS 名字解析器和一个 D N S名字服务器作为客户程序、服务器或同时作为客

158

使用TCP/IP详解,卷1:协议

下载

户和服务器的情况。 14.2 说明图14-12中构成响应的75个字节的含义。 14.3 在1 2 . 3节我们指出,一个既可接受点分十进制形式的 I P地址、也可接收主机名的应用程 序,应先假定输入的是 I P地址,如果失败,再假定是主机名。如果改变这个测试顺序会 出现什么情况? 14.4 每个UDP数据报有一个相应的长度。一个接收 UDP数据报的进程将被告知这个长度。当 名字解析器使用 T C P而不是 U D P来处理查询请求时,由于 T C P是没有任何记录标记的字 节流,那么应用程序是如何知道有多少数据返回?注意在 D N S的报文首部(图 1 4 - 3)中 没有任何长度字段(提示:查阅 RFC 1035) 14.5 我们说一个名字服务器必须知道根名字服务器的 IP地址,这一信息可通过匿名 FTP获得。 不幸的是当根名字服务器表发生变化时,并不是所有的系统管理员都会更新他们的 DNS 配置文件(根名字服务表的确会发生变化,尽管不是经常的)你认为 DNS如何处理这个 问题? 14.6 利用习题 1 . 8指明的文件来确定谁应负责维护根名字服务器。名字服务器更新的频度是 怎样的? 14.7 维护一个名字服务器和一个无状态的名字解析器高速缓存的问题分别是什么? 14.8 在图14-10的讨论中,我们指出名字服务器将对 A类型记录进行排序以便在公共网中的地 址先出现。谁对 A类型记录进行这种排序,是名字服务器还是名字解析器 ?

下载

第15章 TFTP:简单文件传送协议 15.1 引言 T F T P ( Trivial File Transfer Protocol)即简单文件传送协议,最初打算用于引导无盘系统 (通常是工作站或 X终端)。和将在第 2 7章介绍的使用 T C P的文件传送协议( F T P)不同,为了 保持简单和短小, T F T P将使用 U D P。T F T P的代码(和它所需要的 U D P、I P和设备驱动程序) 都能适合只读存储器。 本章对T F T P只作一般介绍,因为在下一章引导程序协议( Bootstrap Protocol)中还会遇 到T F T P。在图5 - 1中,当从网络上引导 s u n主机时,也曾遇到过 T F T P,s u n主机通过 R A R P获 得它的IP地址后,将发出一个 TFTP请求。 RFC 1350 [Sollins 1992] 是第2版T F T P的正式规范。第 1 2章 [Stevens 1990] 提供了实现 TFTP客户和服务器的全部源代码,并介绍了一些使用 TFTP的编程技术。

15.2 协议 在开始工作时,TFTP的客户与服务器交换信息,客户发送一个读请求或写请求给服务器。 在一个无盘系统进行系统引导的正常情况下,第一个请求是读请求( R R Q)。图1 5 - 1显示了 5 种TFTP报文格式(操作码为 1和2的报文使用相同的格式)。 T F T P报文的头两个字节表示操作码。对于读请求和写请求( W R Q),文件名字段说明客 户要读或写的位于服务器上的文件。这个文件字段以 0字节作为结束(见图 1 5 - 1)。模式字段 是一个ASCII码串n e t a s c i i或o c t e t(可大小写任意组合),同样以0字节结束。n e t a s c i i 表示数据是以成行的 ASCII码字符组成,以两个字节 —回车字符后跟换行字符(称为 CR/LF) 作为行结束符。这两个行结束字符在这种格式和本地主机使用的行定界符之间进行转化。 octet则将数据看作8 bit 一组的字节流而不作任何解释。 每个数据分组包含一个块编号字段,它以后要在确认分组中使用。以读一个文件作为例 子,T F T P客户需要发送一个读请求说明要读的文件名和文件模式 ( m o d e )。如果这个文件能被 这个客户读取, T F T P服务器就返回一个块编号为 1的数据分组。 T F T P客户又发送一个块编号 为1的ACK。TFTP服务器随后发送块编号为 2的数据。 TFTP客户发回块编号为 2的ACK。重复 这个过程直到这个文件传送完。除了最后一个数据分组可含有不足 5 1 2字节的数据,其他每个 数据分组均含有 5 1 2字节的数据。当 T F T P客户收到一个不足 5 1 2字节的数据分组,就知道它收 到最后一个数据分组。 在写请求的情况下, TFTP 客户发送WRQ指明文件名和模式。如果该文件能被 该客户写, TFTP 服务器就返回块编号为 0的A C K包。该客户就将文件的头 5 1 2字节以块编号为 1发出。服 务器则返回块编号为 1的ACK。 这种类型的数据传输称为停止等待协议。它只用在一些简单的协议如 T F T P中。在 2 0 . 3节 中将看到 T C P提供了不同形式的确认,能提供更高的系统吞吐量。 T F T P的优点在于实现的简

160

使用TCP/IP详解,卷1:协议

下载

单而不是高的系统吞吐量。 IP数据报 UDP报文 TFTP数据报 IP首部 20字节

UDP首部

操作码 (1=RRQ) (2=WRQ)

文件名

模式

8字节

2字节

N字节

N字节

操作码 块编号 (3=data)



2字节 2字节



0-512字节

操作码 块编号 (4=ACK) 2字节 2字节 操作码 差错码 (5=error) 2字节

2字节

差错信息 N字节

图15-1 5种TFTP报文格式

最后一种 T F T P报文类型是差错报文,它的操作码为 5。它用于服务器不能处理读请求或 写请求的情况。在文件传输过程中的读和写差错也会导致传送这种报文,接着停止传输。差 错编号字段给出一个数字的差错码,跟着是一个 ASCII表示的差错报文字段,可能包含额外的 操作系统说明的信息。 既然TFTP使用不可靠的 UDP,TFTP就必须处理分组丢失和分组重复。分组丢失可通过发 送方的超时与重传机制解决(注意存在一种称为“魔术新手综合症

( s o r c e r e r’s apprentice

s y n d r o m e )”的潜在问题,如果双方都超时与重传,就可能出现这个问题。 12.2 节 [ S t e v e n s 1990] 介绍了这个问题是如何发生的 )。和许多 U D P应用程序一样, T F T P报文中没有检验和, 它假定任何数据差错都将被 UDP的检验和检测到(参见 11.3节)。

15.3 一个例子 让我们通过观察协议的工作情况来了解 T F T P。在b s d i主机上运行TFTP 客户程序,并从 主机svr4读取一个文本文件: 启动TFTP客户进程 从服务器读取文件 结束 查看我们读取的文件大小 文件行数?

最先引起我们注意的是在 U n i x系统下接收的文件长度是 9 1 4字节,而 T F T P则传送了 9 6 2个 字节。使用 w c程序我们看到文件共有 4 8行,因此 4 8个U n i x的换行符被转化成 4 8个C R / C F对,

161

第15章 TFTP:简单文件传送协议使用

下载

因为默认情况下 TFTP使用netascii模式传送。 图15-2显示了发生的分组交换过程。

图15-2 使用TFTP传输一个文件的分组交换过程

第1行显示了客户向服务器发送的读请求。由于目的 U D P端口是 T F T P熟知端口( 6 9), t c p d u m p将解释 T F T P分组,并显示 R R Q和文件名。 1 9字节的 U D P数据包括 2字节的操作码, 7字节的的文件名, 1字节的0,8字节的netascii模式以及另 1字节的0结束。 下一个分组由服务器发回(第 2行),共包含 5 1 6字节:2字节的操作码, 2字节的数据块号 和512字节的数据。第 3行是这个数据块的确认,它包括 2字节的操作码和 2字节的数据块号。 最后的数据分组(第 4行)包含 450字节的数据。这 450字节的数据加上第 2行的512字节的 数据就是向该客户传送的 962字节的数据。注意 t c p d u m p仅在第1行解释TFTP报文,而在 2~5 行都不显示任何 T F T P协议信息。这是因为服务器进程的端口在第 1行和第 2行发生了变化。 TFTP协议需要客户进程向服务器进程的 UDP熟知端口(69)发送第一个分组(RRQ或WRQ)。 之后服务器进程便向服务器主机申请一个尚未使用的端口( 1 0 7 7,见图1 5 - 2),服务器进程使 用这个端口来进行请求客户进程与服务器进程间的其他数据交换。客户进程的端口号(在这 个例子中为1106)没有变化。 t c p d u m p无法知道主机 srv4上的1077端口是一个 TFTP服务器进 程。 服务器进程端口变化的原因是服务器进程不能占用这个熟知端口来完成需一些时间的文 件传输(可能是几十秒甚至数分钟)。相反,在传输当前文件的过程中,这个熟知端口要留出 来供其他的 TFTP客户进程发送它们的请求。 回顾图 1 0 - 6,当R I P服务器向客户发送的数据超过 5 1 2字节,两个 U D P数据报都使用服务 器的熟知端口。在那个例子中,即使服务器进程必须写多个数据报以便将所有数据发回,服 务器进程也是先写一个,再写一个,它们都使用它的熟知端口。然而, T F T P协议与它不同, 因为客户与服务器间的连接需要持续一个较长的时间(可能是数秒或数分钟)。如果一个服务 器进程使用熟知端口来进行文件传输,那么在文件传输期间,它要么拒绝任何来自其他客户 的请求,要么一个服务器进程在同一端口( 6 9)同时对多个客户进程进行多个文件传输。最 简单的办法是让服务器进程在收到 R R Q或W R Q后,改用新的端口。当然,客户进程在收到第 一个数据分组(图 1 5 - 2的第2行)后必须探测到这个新的端口,并将之后的所有确认(第 3行 和第5行)发送到那个新的端口。 在16.3节我们将看到当 X终端在进行系统引导时将使用 TFTP。

15.4 安全性 注意在TFTP分组(图 15-1)中并不提供用户名和口令。这是 TFTP的一个特征(即“安全 漏洞”)。由于TFTP是设计用于系统引导进程,它不可能提供用户名和口令。 T F T P的这一特性被许多解密高手用于获取 U n i x口令文件的复制,然后来猜测用户口令。

162

使用TCP/IP详解,卷1:协议

下载

为防止这种类型的访问,目前大多数 T F T P服务器提供了一个选项来限制只能访问特定目录下 的文件( U n i x系统中通常是 / t f t p b o o t)。这个目录中只包含无盘系统进行系统引导时所需 的文件。 对其他的安全性, U n i x系统下的T F T P服务器通常将它的用户 I D和组I D设置为不会赋给任 何真正用户的值。这只允许访问具有读或写属性的文件。

15.5 小结 T F T P是一个简单的协议,适合于只读存储器,仅用于无盘系统进行系统引导。它只使用 几种报文格式,是一种停止等待协议。 为了允许多个客户端同时进行系统引导, T F T P服务器必须提供一定形式的并发。因为 U D P在一个客户与一个服务器之间并不提供唯一连接( T C P也一样),T F T P服务器通过为每 个客户提供一个新的 U D P端口来提供并发。这允许不同的客户输入数据报,然后由服务器中 的UDP模块根据目的端口号进行区分,而不是由服务器本身来进行区分。 TFTP协议没有提供安全特性。大多数执行指望 TFTP服务器的系统管理员来限制客户的访 问,只允许它们访问引导所必须的文件。 第27章介绍的文件传输协议( FTP)是设计用于一般目的的、高吞吐量的文件传输。

习题 15.1 阅读Host Requirements RFC,了解如果一个 TFTP 服务器收到的请求的目的 IP地址是一 个广播地址,它将做什么。 15.2 当T F T P块号由6 5 5 3 5跳回到0时,你认为会发生什么? RFC 1350提到了如何处理这一问 题吗? 15.3 T F T P发送方采用超时重发来处理分组丢失。当 T F T P作为引导进程的一部分时,这种方 法对TFTP的使用有何影响? 15.4 使用TFTP时,影响传输文件所需时间的限制性因素是什么?

下载

第16章 BOOTP:引导程序协议 16.1 引言 在第5章我们介绍了一个无盘系统,它在不知道自身 I P地址的情况下,在进行系统引导时 能够通过 R A R P来获取它的 I P地址。然而使用 R A R P有两个问题:(1)I P地址是返回的唯一结 果;( 2)既然 R A R P使用链路层广播, R A R P请求就不会被路由器转发(迫使每个实际网络 设置一个 RARP 服务器)。本章将介绍一种用于无盘系统进行系统引导的替代方法,又称为引 导程序协议,或 BOOTP。 B O O T P使用 U D P,且通常需与 T F T P(参见第 1 5章)协同工作。 RFC 951 [Croft and Gilmore 1985]是BOOTP的正式规范,RFC 1542 [Wimer 1993]则对它作了说明。

16.2 BOOTP 的分组格式 BOOTP 请求和应答均被封装在 UDP数据报中,如图 16-1所示。 IP数据报 UDP数据报 IP首部

UDP首部

20字节

8字节

BOOTP请求/应答 300字节

图16-1 BOOTP 请求和应答封装在一个UDP数据报内

图16-2显示了长度为300字节的BOOTP请求和应答的格式。 “操作码”字段为 1表示请求,为 2表示应答。硬件类型字段为 1表示10 Mb/s的以太网,这 和A R P请求或应答(图 4 - 3)中同名字段表示的含义相同。类似地,对于以太网,硬件地址长 度字段为6字节。 “跳数”字段由客户设置为 0,但也能被一个代理服务器设置(参见 16.5节)。 “事务标识”字段是一个由客户设置并由服务器返回的 32 bit整数。客户用它对请求和应 答进行匹配。对每个请求,客户应该将该字段设置为一个随机数。 客户开始进行引导时,将“秒数”字段设置为一个时间值。服务器能够看到这个时间值, 备用服务器在等待时间超过这个时间值后才会响应客户的请求,这意味着主服务器没有启动。 如果该客户已经知道自身的 I P地址,它将写入“客户 I P地址”字段。否则,它将该字段 设置为 0。对于后面这种情况,服务器用该客户的 I P地址写入“你的 I P地址”字段。“服务器 I P地址”字段则由服务器填写。如果使用了某个代理服务器(见 1 6 . 5节),则该代理服务器就 填写“网关 IP地址”字段。 客户必须设置它的“客户硬件地址”字段。尽管这个值与以太网数据帧头中的值相同, U D P数据报中也设置这个字段,但任何接收这个数据报的用户进程能很容易地获得它(例如

164

使用TCP/IP详解,卷1:协议

下载

一个BOOTP 服务器)。一个进程通过查看 U D P数据报来确定以太网帧首部中的该字段通常是 很困难的(或者说是不可能的)。 操作码 (1=请求,2=应答)

硬件类型 (1=以太网)

硬件地址长度 (以太网为6)

跳数

事务标识 秒数

未使用 客户IP地址 你的IP地址 服务器IP地址 网关IP地址 300字节 客户主机硬件地址(16字节)

服务器主机名(64字节)

引导文件名(128字节)

特定厂商信息(64字节)

图16-2 BOOTP请求和应答的格式

“服务器主机名”字段是一个空值终止串,由服务器填写。服务器还将在“引导文件名字 段”填入包括用于系统引导的文件名及其所在位置的路径全名。 “特定厂商区域”字段用于对 BOOTP进行不同的扩展。 16.6节将介绍这些扩展中的一些。 当一个客户使用 B O O T P(操作码为 1)进行系统引导时,引导请求通常是采用链路层广 播, I P首部中的目的 I P地址为 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5(受限的广播, 1 2 . 2 节)。源 I P地址通常是 0 . 0 . 0 . 0,因为此时客户还不知道它本身的 I P地址。回顾图 3 - 9,在系统进行自引导时, 0 . 0 . 0 . 0 是一个有效的IP地址。 端口号 B O O T P有两个熟知端口: BOOTP 服务器为 6 7,BOOTP 客户为6 8。这意味着 BOOTP 客 户不会选择未用的临时端口,而只用端口 6 8。选择两个端口而不是仅选择一个端口为 B O O T P 服务器用的原因是:服务器的应答可以进行广播(但通常是不用广播的)。 如果服务器的应答是通过广播传送的,同时客户又选择未用的临时端口,那么这些广播

165

第16章 BOOTP:引导程序协议使用

下载

也能被其他的主机中碰巧使用相同临时端口的应用进程接收到。因此,采用随机端口(即临 时端口)对广播来说是一个不好的选择。 如果客户也使用服务器的知名端口( 6 7)作为它的端口,那么网络内的所有服务器会被 唤醒来查看每个广播应答(如果所有的服务器都被唤醒,它们将检查操作码,如果是一个应 答而不是请求,就不作处理)。因此可以让所有的客户使用与服务器知名端口不同的同一知名 端口。 如果多个客户同时进行系统引导,并且服务器广播所有应答,这样每个客户都会收到其 他客户的应答。客户可以通过 B O O T P首部中的事务标识字段来确认应答是否与请求匹配,或 者可以通过检查返回的客户硬件地址加以区分。

16.3 一个例子 让我们看一个用 B O O T P引导一个 X终端的例子。图 1 6 - 3显示了 t c p d u m p的输出结果(例 中客户名为 p r o t e u s,服务器名为 m e r c u r y。这个 t c p d u m p的输出是在不同的网络上获得 的,这个应用程序是其他例子中一直使用的)。

这里删除了许多行

图16-3 用BOOTP引导一个X终端的例子

在第1行中,我们看到客户请求来自 0 . 0 . 0 . 0 . 6 8,发送目的站是 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5 . 6 7。该客 户已经填写的字段是秒数和自身的以太网地址。我们看到客户通常将秒数设置为

100。

t c p d u m p没有显示跳数和事务标识,因为它们均为 0 (事务标识为 0表示该客户忽略这个字段,

166

使用TCP/IP详解,卷1:协议

下载

因为如果打算对返回响应进行验证,它将把这个字段设置为一个随机数值 )。 第2行是服务器返回的应答。由服务器填写的字段是该客户的 IP地址(t c p d u m p显示为名 字 p r o t e u s)、服务器的 I P 地址(显示为名字 m e r c u r y )、网关的 I P 地址(显示为名字 mercury)和引导文件名。 在收到BOOTP应答后,该客户立即发送一个ARP请求来了解网络中其他主机是否有IP地址。 跟在w h o - h a s后的名字 p r o t e u s对应目的 IP地址(图4-3),发送者的 IP地址被设置为0.0.0.0。 它在0.5秒后再发一个相同的ARP请求,之后再过0.5秒又发一个。在第3个ARP请求(第5行)中, 它将发送者的IP地址改变为它自己的IP地址。这是一个没有意义的ARP请求(见4.7节)。 第6行显示该客户在等待另一个 0.5秒后,广播另一个 BOOTP请求。这个请求与第 1行的唯 一不同是此时客户将它的 IP地址写入IP首部中。它收到来自同一个服务器的相同应答(第7行)。 该客户在等待 2秒后,又广播一个 B O O T P请求(第 8行),同样收到来自同一服务器的相同应 答。 该客户等待 2秒后,向它的服务器 m e r c u r y发送一个 A R P请求(第 1 0行)。收到这个 A R P 应答后,它立即发送一个 T F T P读请求,请求读取它的引导文件(第 1 2行)。文件传送过程包 括2464个TFTP数据分组和确认,传送的数据量为 512×2463 + 224 = 1 261 280字节。这将操 作系统调入 X终端。我们已在图 16-3中删除了大多数 TFTP行。 当和图1 5 - 2比较T F T P的数据交换过程时,要注意的是这儿的客户在整个传输过程中使用 T F T P的知名端口( 6 9)。既然通信双方中的一方使用了端口 6 9,t c p d u m p就知道这些分组是 TFTP报文,因此它能用 TFTP协议来解释每个分组。这就是为什么图 16-3能指明哪些包含有数 据,哪些包含有确认,以及每个分组的块编号。在图 1 5 - 2中我们并不能获得这些额外的信息, 因为通信双方均没有使用 T F T P的知名端口进行数据传送。由于 TFTP 服务器作为一个多用户 系统,且使用TFTP的知名端口,因此通常 TFTP客户不能使用那个端口。但这里的系统处于正 被引导的过程中,无法提供一个 TFTP服务器,因此允许该客户在传输期间使用 TFTP的知名端 口。这也暗示在 m e r c u r y上的TFTP服务器并不关心客户的端口号是什么—它只将数据传送 到客户的端口上,而不管发生了什么。 从图16-3可以看出在 9秒内共传送了1 261 280字节。数据速率大约为 140 000 bps。这比大 多数以 F T P文件传送形式访问一个以太网要慢,但对于一个简单的停止等待协议如 T F T P来说 已经很好了。 X终端系统引导后,还需使用 T F T P传送终端的字体文件、某些 D N S名字服务器查询,然 后进行 X协议的初始化。图 1 6 - 3中的所有步骤大概需要 1 5秒钟,其余的步骤需要 6秒钟,这样 无盘X终端系统引导的总时间是 21秒。

16.4 BOOTP服务器的设计 B O O T P客户通常固化在无盘系统只读存储器中,因此了解 BOOTP 服务器的实现将更有 意义。 首先,BOOTP 服务器将从它的熟知端口( 67)读取UDP数据报。这没有特别的地方。它 不同于 RARP 服务器 (5 . 4节),它必须读取类型字段为“ R A R P请求”的以太网帧。 B O O T P 协议通过将客户的硬件地址放入 B O O T P分组中,使得服务器很容易获取客户的硬件地址 (图 16-2)。

下载

167

第16章 BOOTP:引导程序协议使用

这里出现了一个有趣的问题: TFTP 服务器如何能将一个响应直接送回 BOOTP 客户?这 个响应是一个 U D P数据报,而服务器知道该客户的 I P地址(可能通过读取服务器上的配置文 件)。但如果这个客户向那个 I P地址发送一个 U D P数据报(正常情况下会处理 U D P的输出), BOOTP 服务器的主机就可能向那个 IP地址发送一个 ARP请求。但这个客户不能响应这个 ARP 请求,因为它还不知道它自己的 IP地址!(这就是在 RFC951中被称作“鸡和蛋”的问题。) 有两种解决办法:第一种,通常被 Unix 服务器采用,是服务器发一个 ioctl(2)请求给内核, 为该客户在 A R P高速缓存中设置一个条目(这就是命令 a r p - s所做的工作,见 4 . 8节)。服务 器能一直这么做直到它知道客户的硬件地址和 I P地址。这意味着当服务器发送 U D P数据报 (即BOOTP应答)时,服务器的 ARP将在ARP 高速缓存中找到该客户的 IP地址。 另一种可选的解决办法是服务器广播这个 B O O T P应答而不直接将应答发回该客户。既然 通常期望网络广播越少越好,因此这种解决方案应该只在服务器无法在它的 ARP 高速缓存设 置一个条目的情况下使用。通常只有拥有超级用户权限才能在 A R P高速缓存设置一个条目, 如果没有这种权限就只能广播 BOOTP应答。

16.5 BOOTP穿越路由器 我们在 5 . 4节中提到 R A R P的一个缺点就是它使用链路层广播,这种广播通常不会由路由 器转发。这就需要在每个物理网络内设置一个 RARP 服务器。如果路由器支持 B O O T P协议, 那么BOOTP能够由路由器转发(绝大多数路由器厂商的产品都支持这个功能)。 这个功能主要用于无盘路由器,因为如果在磁盘的多用户系统被用作路由器,它就能够 自己运行 BOOTP 服务器。此外,常用的 Unix BOOTP服务器(附录 F)支持这种中继模式 (relay mode)。但如果在这个物理网络内运行一个 BOOTP 服务器,通常没有必要将 BOOTP请 求转发到在另外网络中的另一个服务器。 研究一下当路由器(也称作“ BOOTP 中继代理”)在服务器的熟知端口( 6 7)接收到 B O O T P请求时将会发生什么。当收到一个 B O O T P请求时,中继代理将它的 I P地址填入收到 B O O T P请求中的“网关 I P地址字段”,然后将该请求发送到真正的 B O O T P服务器(由中继代 理填入网关字段的地址是收到的 B O O T P请求接口的 I P地址)。该代理中继还将跳数字段值加 1 (这是为防止请求被无限地在网络内转发。 RFC 951认为如果跳数值到达 3就可以丢弃该请求)。 既然发出的请求是一个单播的数据报(与发起的客户的请求是广播的相反),它能按照一定的 路由通过其他的路由器到达真正的 BOOTP服务器。真正的 BOOTP服务器收到这个请求后,产 生B O O T P应答,并将它发回中继代理,而不是请求的客户。既然请求网关字段不为零,真正 的BOOTP服务器知道这个请求是经过转发的。中继代理收到应答后将它发给请求的客户。

16.6 特定厂商信息 在图 1 6 - 2中我们看到了 6 4字节的“特定厂商区域”。RFC 1533 [Alexander and Droms 1993] 定义了这个区域的格式。这个区域含有服务器返回客户的可选信息。 如果有信息要提供,这个区域的前 4个字节被设置为 I P地址9 9 . 1 3 0 . 8 3 . 9 9。这可称作魔术 甜饼(magic cookie),表示该区域内包含信息。 这个区域的其余部分是一个条目表。每个条目的开始是 1字节标志字段。其中的两个条目 仅有标志字段:标志为 0的条目作为填充字节(为使后面的条目有更好的字节边界),标志为

168

使用TCP/IP详解,卷1:协议

下载

255的条目表示结尾条目。第一个结尾条目后剩余的字节都应设置为这个数值( 255)。 除了这两个 1字节的条目,其他的条目还包含一个单字节的长度字段,后面是相应的信息。 图16-4显示了厂商说明区域中一些条目的格式。 填充: 1字节 子网掩码:

时间偏移:

网关:

tag=1

len=4

子网掩码

1字节

1字节

4字节

tag=2

len=4

时间

1字节

1字节

4字节

tag=3

len=N

首选网关的IP地址

1字节

1字节

4字节

网关IP地址 4字节 N字节

14个其他条目:tag=4~17

结尾: 1字节

图16-4 厂商说明区域中一些条目的格式

子网掩码条目和时间值条目都是定长条目,因为它们的值总是占 4个字节。时间偏移值是 从1900年1月1日0时以来的秒数( UTC)。 网关条目是变长条目。长度通常是 4 的倍数,这个值是一个或多个供客户使用的网关(路 由器)的IP地址。返回的第一个必须是首选的网关。 RFC 1533 还定义了其他 1 4个条目。其中最重要的可能是 D N S名字服务器的 I P地址条目, 条目的志为 6。其他的条目包括打印服务器、时间服务器等的 I P地址。详细情况可参考 R F C文 档。 回到在图16-3中的例子,我们从未看到客户广播一个 ICMP地址掩码请求( 6.3节)来获取 它的子网掩码。尽管 tcpdump不能显示出来,但我们可认为客户所在网络的子网掩码在返回的 BOOTP应答的厂商说明区域内。 Host Requirements RFC文档推荐一个系统使用 BOOTP来获悉它的子网掩码,而不是采用 ICMP。 厂商说明区域的大小被限制为 6 4字节。这对某些应用是个约束。一个新的称为动态主机 配置协议 DHCP(Dynamic Host Configuration Protocol)已经出现,但它不是替代 BOOTP的。 DHCP将这个区域的长度扩展到 312字节,它在 RFC 1541 [Droms 1993] 中定义。

16.7 小结 B O O T P使用U D P,它为引导无盘系统获得它的 I P地址提供了除 R A R P外的另外一种选择。

下载

169

第16章 BOOTP:引导程序协议使用

BOOTP还能返回其他的信息,如路由器的 IP地址、客户的子网掩码和名字服务器的 IP地址。 既然B O O T P用于系统引导过程,一个无盘系统需要下列协议才能在只读存储器中完成: BOOTP、TFTP、UDP、IP和一个局域网的驱动程序。 B O O T P服务器比 R A R P服务器更易于实现,因为 B O O T P请求和应答是在 U D P数据报中, 而不是特殊的数据链路层帧。一个路由器还能作为真正 B O O T P服务器的代理,向位于不同网 络的真正BOOTP服务器转发客户的 BOOTP请求。

习题 16.1 我们说B O O T P优于R A R P的一个方面是 B O O T P能穿越路由器,而 R A R P由于使用链路层 广播则不能。在 16.5节为使BOOTP穿越路由器,我们必须定义特殊的方式。如果在路由 器中增加允许转发 RARP请求的功能会发生什么? 16.2 我们说过,当有多个客户程序同时向一个服务器发出引导请求时,因为服务器要广播多 个B O O T P应答,BOOTP 客户就必须使用事务标识来使响应与请求相匹配。但在图 1 6 - 3 中,事务标识为 0,表示这个客户不考虑事务标识。你认为这个客户将如何将这些响应 与其请求匹配。

下载

第17章 TCP:传输控制协议 17.1 引言 本章将介绍 TCP为应用层提供的服务,以及 TCP首部中的各个字段。随后的几章我们在了 解TCP的工作过程中将对这些字段作详细介绍。 对T C P的介绍将由本章开始,并一直包括随后的 7章。第 1 8章描述如何建立和终止一个 T C P连接,第 1 9和第2 0章将了解正常的数据传输过程,包括交互使用(远程登录)和批量数 据传送(文件传输)。第2 1章提供T C P超时及重传的技术细节,第 2 2和第2 3章将介绍两种其他 的定时器。最后,第 24章概述TCP新的特性以及TCP的性能。

17.2 TCP的服务 尽管TCP和UDP都使用相同的网络层( IP),TCP却向应用层提供与 UDP完全不同的服务。 TCP提供一种面向连接的、可靠的字节流服务。 面向连接意味着两个使用 T C P的应用(通常是一个客户和一个服务器)在彼此交换数据 之前必须先建立一个 T C P连接。这一过程与打电话很相似,先拨号振铃,等待对方摘机说 “喂”,然后才说明是谁。在第 1 8章我们将看到一个 T C P连接是如何建立的,以及当一方通信 结束后如何断开连接。 在一个TCP连接中,仅有两方进行彼此通信。在第 12章介绍的广播和多播不能用于 TCP。 TCP通过下列方式来提供可靠性: • 应用数据被分割成 T C P认为最适合发送的数据块。这和 U D P完全不同,应用程序产生的 数据报长度将保持不变。由 T C P传递给 I P的信息单位称为报文段或段( s e g m e n t)(参见 图1-7)。在18.4节我们将看到TCP如何确定报文段的长度。 • 当T C P发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能 及时收到一个确认,将重发这个报文段。在第 2 1章我们将了解 T C P协议中自适应的超时 及重传策略。 • 当T C P收到发自 T C P连接另一端的数据,它将发送一个确认。这个确认不是立即发送, 通常将推迟几分之一秒,这将在 19.3节讨论。 • T C P将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输 过程中的任何变化。如果收到段的检验和有差错, T C P将丢弃这个报文段和不确认收到 此报文段(希望发端超时并重发)。 • 既然T C P报文段作为 I P数据报来传输,而 I P数据报的到达可能会失序,因此 T C P报文段 的到达也可能会失序。如果必要, T C P将对收到的数据进行重新排序,将收到的数据以 正确的顺序交给应用层。 • 既然IP数据报会发生重复, TCP的接收端必须丢弃重复的数据。 • T C P还能提供流量控制。 T C P连接的每一方都有固定大小的缓冲空间。 T C P的接收端只

171

第17章 TCP:传输控制协议使用

下载

允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲 区溢出。 两个应用程序通过 TCP连接交换8 bit字节构成的字节流。 TCP不在字节流中插入记录标识 符。我们将这称为字节流服务( byte stream service)。如果一方的应用程序先传 10字节,又传 20字节,再传 50字节,连接的另一方将无法了解发方每次发送了多少字节。收方可以分 4次接 收这 8 0个字节,每次接收 2 0字节。一端将字节流放到 T C P连接上,同样的字节流将出现在 TCP连接的另一端。 另外,T C P对字节流的内容不作任何解释。 T C P不知道传输的数据字节流是二进制数据, 还是A S C I I字符、E B C D I C字符或者其他类型数据。对字节流的解释由 T C P连接双方的应用层 解释。 这种对字节流的处理方式与 Unix操作系统对文件的处理方式很相似。 Unix的内核 对一个应用读或写的内容不作任何解释,而是交给应用程序处理。对 Unix的内核来说, 它无法区分一个二进制文件与一个文本文件。

17.3 TCP的首部 TCP数据被封装在一个 IP数据报中,如图 17-1所示。 IP数据报 TCP报文段

IP首部

TCP首部

20字节

20字节

TCP数据

图17-1 TCP数据在IP数据报中的封装

图17-2显示TCP首部的数据格式。如果不计任选字段,它通常是 20个字节。 16位目的端口号

16位源端口号 32位序号

32位确认序号 4位首部 长度

保留(6位)

16位窗口大小 16位紧急指针

16位检验和 选项

数据

图17-2 TCP包首部

172

使用TCP/IP详解,卷1:协议

下载

每个T C P段都包含源端和目的端的端口号,用于寻找发端和收端应用进程。这两个值加 上IP首部中的源端IP地址和目的端IP地址唯一确定一个 TCP连接。 有时,一个 I P地址和一个端口号也称为一个插口( s o c k e t)。这个术语出现在最早的 T C P 规范(R F C 7 9 3)中,后来它也作为表示伯克利版的编程接口(参见 1 . 1 5节)。插口对( s o c k e t p a i r)(包含客户 I P地址、客户端口号、服务器 I P地址和服务器端口号的四元组 )可唯一确定互 联网络中每个TCP连接的双方。 序号用来标识从 TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一 个数据字节。如果将字节流看作在两个应用程序间的单向流动,则 T C P用序号对每个字节进 行计数。序号是 32 bit的无符号数,序号到达 232-1后又从0开始。 当建立一个新的连接时, S Y N标志变1。序号字段包含由这个主机选择的该连接的初始序 号ISN(Initial Sequence Number)。该主机要发送数据的第一个字节序号为这个 ISN加1,因为 SYN标志消耗了一个序号(将在下章详细介绍如何建立和终止连接,届时我们将看到 FIN标志 也要占用一个序号)。 既然每个传输的字节都被计数,确认序号包含发送确认的一端所期望收到的下一个序号。 因此,确认序号应当是上次已成功收到数据字节序号加 1。只有 A C K标志(下面介绍)为 1时 确认序号字段才有效。 发送ACK无需任何代价,因为 32 bit的确认序号字段和 ACK标志一样,总是 TCP首部的一 部分。因此,我们看到一旦一个连接建立起来,这个字段总是被设置, A C K标志也总是被设 置为1。 T C P为应用层提供全双工服务。这意味数据能在两个方向上独立地进行传输。因此,连 接的每一端必须保持每个方向上的传输数据序号。 T C P可以表述为一个没有选择确认或否认的滑动窗口协议(滑动窗口协议用于数据传输 将在2 0 . 3节介绍)。我们说 T C P缺少选择确认是因为 T C P首部中的确认序号表示发方已成功收 到字节,但还不包含确认序号所指的字节。当前还无法对数据流中选定的部分进行确认。例 如,如果 1~1024字节已经成功收到,下一报文段中包含序号从 2049~3072的字节,收端并不 能确认这个新的报文段。它所能做的就是发回一个确认序号为 1 0 2 5的A C K。它也无法对一个 报文段进行否认。例如,如果收到包含 1 0 2 5~2 0 4 8字节的报文段,但它的检验和错, T C P接 收端所能做的就是发回一个确认序号为 1 0 2 5的A C K。在2 1 . 7节我们将看到重复的确认如何帮 助确定分组已经丢失。 首部长度给出首部中 32 bit字的数目。需要这个值是因为任选字段的长度是可变的。这个 字段占4 bit ,因此TCP最多有60字节的首部。然而,没有任选字段,正常的长度是 20字节。 在T C P首部中有 6个标志比特。它们中的多个可同时被设置为 1。我们在这儿简单介绍它 们的用法,在随后的章节中有更详细的介绍。 URG

紧急指针( urgent pointer)有效(见 20.8节)。

ACK

确认序号有效。

PSH

接收方应该尽快将这个报文段交给应用层。

RST

重建连接。

SYN

同步序号用来发起一个连接。这个标志和下一个标志将在第 18章介绍。

FIN

发端完成发送任务。

下载

173

第17章 TCP:传输控制协议使用

T C P的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始 于确认序号字段指明的值,这个值是接收端正期望接收的字节。窗口大小是一个 16 bit 字段, 因而窗口大小最大为 6 5 5 3 5字节。在 2 4 . 4节我们将看到新的窗口刻度选项,它允许这个值按比 例变化以提供更大的窗口。 检验和覆盖了整个的 T C P报文段: T C P首部和T C P数据。这是一个强制性的字段,一定是 由发端计算和存储,并由收端进行验证。 T C P检验和的计算和 U D P检验和的计算相似,使用 如11.3节所述的一个伪首部。 只有当URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值 相加表示紧急数据最后一个字节的序号。 T C P的紧急方式是发送端向另一端发送紧急数据的 一种方式。我们将在 20.8节介绍它。 最常见的可选字段是最长报文大小,又称为 MSS (Maximum Segment Size)。每个连接方 通常都在通信的第一个报文段(为建立连接而设置 S Y N标志的那个段)中指明这个选项。它 指明本端所能接收的最大长度的报文段。我们将在 18.4节更详细地介绍 MSS选项,TCP的其他 选项中的一些将在第 24章中介绍。 从图17-2中我们注意到 TCP报文段中的数据部分是可选的。我们将在 18章中看到在一个连 接建立和一个连接终止时,双方交换的报文段仅有 T C P首部。如果一方没有数据要发送,也 使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何 数据的报文段。

17.4 小结 TCP提供了一种可靠的面向连接的字节流运输层服务。我们简单地介绍了 TCP首部中的各 个字段,并在随后的几章里详细讨论它们。 T C P将用户数据打包构成报文段;它发送数据后启动一个定时器;另一端对收到的数据 进行确认,对失序的数据重新排序,丢弃重复数据; T C P提供端到端的流量控制,并计算和 验证一个强制性的端到端检验和。 许多流行的应用程序如 Telnet、Rlogin、FTP和SMTP都使用TCP。

习题 17.1 我们已经介绍了以下几种分组格式: I P、I C M P、I G M P、U D P和T C P。每一种格式的首 部中均包含一个检验和。对每种分组,说明检验和包括 I P数据报中的哪些部分,以及该 检验和是强制的还是可选的。 17.2 为什么我们已经讨论的所有 Internet协议(IP, ICMP, IGMP, UDP, TCP)收到有检验和错 的分组都仅作丢弃处理? 17.3 T C P提供了一种字节流服务,而收发双方都不保持记录的边界。应用程序如何提供它们 自己的记录标识? 17.4 为什么在TCP首部的开始便是源和目的的端口号? 17.5 为什么TCP首部有一个首部长度字段而 UDP首部(图11-2)中却没有?

下载

第18章 TCP连接的建立与终止 18.1 引言 T C P是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间 建立一条连接。本章将详细讨论一个 TCP连接是如何建立的以及通信结束后是如何终止的。 这种两端间连接的建立与无连接协议如 U D P不同。我们在第 11章看到一端使用 U D P向另 一端发送数据报时,无需任何预先的握手。

18.2 连接的建立与终止 为了了解一个TCP连接在建立及终止时发生了什么,我们在系统 svr4上键入下列命令:

键入Ctrl和右括号,使Telnet客户进程终止连接

t e l n e t命令在与丢弃 ( d i s c a r d )服务(参见 1 . 1 2节)对应的端口上与主机 b s d i建立一条 T C P连接。这服务类型正是我们需要观察的一条连接建立与终止的服务类型,而不需要服务 器发起任何数据交换。 18.2.1 tcpdump的输出 图18-1显示了这条命令产生 TCP报文段的tcpdump输出。

图18-1 TCP连接建立与终止的tcpdump 输出显示

这7个TCP报文段仅包含TCP首部。没有任何数据。 对于TCP段,每个输出行开始按如下格式显示:

175

第18章 TCP连接的建立与终止使用

下载 源 > 目的: 标志

这里的标志代表 T C P首部(图 1 7 - 2)中6个标志比特中的 4个。图 1 8 - 2显示了表示标志的 5 个字符的含义。 标志

描 述

3字符缩写

同步序号 发送方完成数据发送 复位连接 尽可能快地将数据送往接收进程 以上四个标志比特均置0

图18-2 tcpdump 对TCP首部中部分标志比特的字符表示

在这个例子中,我们看到了 S、F和句点“ .”标志符。我们将在以后看到其他的两个标志( R 和P)。TCP首部中的其他两个标志比特—ACK 和 URG—tcpdump将作特殊显示。 图18-2所示的4个标志比特中的多个可能同时出现在一个报文段中,但通常一次只见到一个。 RFC 1025 [Postel 1987], “TCP and IP Bake Off” ,将一种报文段称为Kamikaze分组 , 在这样的报文段中有最大数量的标志比特同时被置为1(SYN, URG, PSH, FIN和1字节的 数据) 。这样的报文段也叫作nastygram, 圣诞树分组,灯测试报文段(lamp test segment)。 在第1行中,字段1 4 1 5 5 3 1 5 2 1 : 1 4 1 5 5 3 1 5 2 1 ( 0 )表示分组的序号是 1 4 1 5 5 3 1 5 2 1,而报文段中 数据字节数为 0。t c p d u m p显示这个字段的格式是开始的序号、一个冒号、隐含的结尾序号 及圆括号内的数据字节数。显示序号和隐含结尾序号的优点是便于了解数据字节数大于 0时的 隐含结尾序号。这个字段只有在满足条件( 1)报文段中至少包含一个数据字节;或者( 2) S Y N、F I N或R S T被设置为1时才显示。图 1 8 - 1中的第1、2、4和6行是因为标志比特被置为 1而 显示这个字段的,在这个例子中通信双方没有交换任何数据。 在第2行中,字段 ack

1 4 1 5 5 3 1 5 2表示确认序号。它只有在首部中的 2 A C K标志比特被

设置1时才显示。 每行显示的字段 w i n 4 0 9 6表示发端通告的窗口大小。在这些例子中,我们没有交换任 何数据,窗口大小就维持默认情况下的 4096(我们将在 20.4节中讨论TCP窗口大小)。 图1 8 - 1中的最后一个字段 表示由发端指明的最大报文段长度选项。发端将 不接收超过这个长度的 T C P报文段。这通常是为了避免分段(见 11 . 5节)。我们将在 1 8 . 4节讨 论最大报文段长度,而在 18.10节介绍不同 TCP 选项的格式。 18.2.2 时间系列 图18-3显示了这些分组序列的时间系列(在图 6-11中已经首次介绍了这些时间系列的一些 基本特性)。这个图显示出哪一端正在发送分组。我们也将对 tcpdump输出作一些扩展(例如, 印出SYN而不是S)。在这个时间系列中也省略窗口大小的值,因为它和我们的讨论无关。 18.2.3 建立连接协议 现在让我们回到图 18-3所示的TCP协议中来。为了建立一条 TCP连接: K a m i k a z e是神风队队员或神风队所使用的飞机。在第二次世界大战末期,日本空军的神风队队员驾驶满载 炸弹的飞机去撞击轰炸目标,企图与之同归于尽。

176

使用TCP/IP详解,卷1:协议

下载

1) 请求端(通常称为客户)发送一个 S Y N段指明客户打算连接的服务器的端口,以及初 始序号(ISN,在这个例子中为 1415531521)。这个SYN段为报文段 1。 2) 服务器发回包含服务器的初始序号的 SYN报文段(报文段 2)作为应答。同时,将确认 序号设置为客户的 ISN加1以对客户的 SYN报文段进行确认。一个 SYN将占用一个序号。 3) 客户必须将确认序号设置为服务器的 I S N加1以对服务器的 S Y N报文段进行确认(报文 段3)。 这三个报文段完成连接的建立。这个过程也称为三次握手( three-way handshake)。

报文段1

报文段2 报文段3

报文段4

报文段5 报文段6 报文段7

图18-3 连接建立与终止的时间系列

发送第一个S Y N的一端将执行主动打开( active open)。接收这个 S Y N并发回下一个 S Y N 的另一端执行被动打开( passive open)(在18.8节我们将介绍双方如何都执行主动打开)。 当一端为建立连接而发送它的 S Y N时,它为连接选择一个初始序号。 I S N随时间而变化, 因此每个连接都将具有不同的 ISN。RFC 793 [Postel 1981c]指出ISN可看作是一个32比特的计 数器,每 4 m s加1。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而 导致某个连接的一方对它作错误的解释。 如何进行序号选择 ? 在4.4BSD(和多数的伯克利的实现版)中,系统初始化时初 始的发送序号被初始化为1。这种方法违背了Host Requirements RFC(在这个代码中的 一个注释确认这是一个错误)。这个变量每 0 . 5秒增加 6 4 0 0 0,并每隔 9 . 5小时又回到 0 (对应这个计数器每8 ms加1,而不是每4 ms加1)。另外,每次建立一个连接后,这个 变量将增加64000。 报文段 3与报文段 4之间4 . 1秒的时间间隔是建立 T C P连接到向 t e l n e t键入q u i t命令来中止该 连接的时间。

下载

177

第18章 TCP连接的建立与终止使用

18.2.4 连接终止协议 建立一个连接需要三次握手,而终止一个连接要经过 4次握手。这由 T C P的半关闭( h a l f c l o s e)造成的。既然一个 T C P连接是全双工(即数据在两个方向上能同时传递),因此每个方 向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个 FIN来终止 这个方向连接。当一端收到一个 FIN,它必须通知应用层另一端几经终止了那个方向的数据传 送。发送FIN通常是应用层进行关闭的结果。 收到一个 F I N只意味着在这一方向上没有数据流动。一个 T C P连接在收到一个 F I N后仍能 发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的 T C P应用 程序这样做。正常关闭过程如图 18-3所示。我们将在 18.5节中详细介绍半关闭。 首先进行关闭的一方(即发送第一个 F I N)将执行主动关闭,而另一方(收到这个 F I N) 执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭,但我们将在 1 8 . 9节看到双方 如何都执行主动关闭。 图1 8 - 3中的报文段4发起终止连接,它由 Te l n e t客户端关闭连接时发出。这在我们键入 q u i t 命令后发生。它将导致 TCP客户端发送一个 FIN,用来关闭从客户到服务器的数据传送。 当服务器收到这个 F I N ,它发回一个 A C K,确认序号为收到的序号加 1(报文段 5)。和SYN一样,一个FIN将占用一个序号。

客户 应用程 序关闭

同时 T C P服务器还向应用程序(即丢弃服

服务器

向应用程 序交付EOF

务器)传送一个文件结束符。接着这个服 应用程 序关闭

务器程序就关闭它的连接,导致它的 T C P 端发送一个 F I N(报文段 6),客户必须发回 一个确认,并将确认序号设置为收到序号 加1(报文段7)。 图18-4显示了终止一个连接的典型握手 顺序。我们省略了序号。在这个图中,发

图18-4 连接终止期间报文段的正常交换

送F I N将导致应用程序关闭它们的连接,这 些FIN的ACK是由TCP软件自动产生的。 连接通常是由客户端发起的,这样第一个 S Y N从客户传到服务器。每一端都能主动关闭 这个连接(即首先发送 F I N)。然而,一般由客户端决定何时终止连接,因为客户进程通常由 用户交互控制,用户会键入诸如“ q u i t”一样的命令来终止进程。在图 1 8 - 4中,我们能改变上 边的标识,将左方定为服务器,右方定为客户,一切仍将像显示的一样工作(例如在 1 4 . 4节 中的第一个例子中就是由 daytime服务器关闭连接的)。 18.2.5 正常的tcpdump输出 对所有的数值很大的序号进行排序是很麻烦的,因此默认情况下 t c p d u m p只在显示 S Y N 报文段时显示完整的序号,而对其后的序号则显示它们与初始序号的相对偏移值(为了得到 图18-1的输出显示必须加上 -S选项)。对应于图 18-1的正常tcpdump显示如图18-5所示: 除非我们需要显示完整的序号,否则将在以下的例子中使用这种形式的输出显示。

178

使用TCP/IP详解,卷1:协议

下载

图18-5 连接建立与终止的正常tcpdump 输出

18.3 连接建立的超时 有很多情况导致无法建立连接。一种情况是服务器主机没有处于正常状态。为了模拟这种情 况,我们断开服务器主机的电缆线,然后向它发出telnet命令。图18-6显示了tcpdump的输出。

图18-6 建立连接超时的tcpdump 输出

在这个输出中有趣的一点是客户间隔多长时间发送一个 S Y N,试图建立连接。第 2个S Y N 与第1个的间隔是 5.8秒,而第3个与第2个的间隔是 24秒。 作为一个附注,这个例子运行38分钟后客户重新启动。这对应初始序号为291 008 001 (约为38×60×64000×2) 。我们曾经介绍过使用典型的伯克利实现版的系统将初始序号初 始化为1,然后每隔0.5秒就增加64 000。 另外,因为这是系统启动后的第一个TCP连接,因此客户的端口号是1024。 图1 8 - 6中没有显示客户端在放弃建立连接尝试前进行 S Y N重传的时间。为了了解它我们 必须对telnet命令进行计时:

时间差值是 7 6秒。大多数伯克利系统将建立一个新连接的最长时间限制为 7 5秒。我们将在 2 1 . 4节看到由客户发出的第 3个分组大约在 1 6 : 2 5 : 2 9超时, 客户在它第 3个分组发出后 4 8秒而 不是75秒后放弃连接。 18.3.1 第一次超时时间 在图18-6中一个令人困惑的问题是第一次超时时间为 5.8秒,接近6秒,但不准确,相比之

179

第18章 TCP连接的建立与终止使用

下载

下第二个超时时间几乎准确地为 2 4秒。运行十多次测试,发现第一次超时时间在 5 . 5 9秒~ 5 . 9 3 秒之间变化。然而,第二次超时时间则总是 24.00秒(精确到小数点后面两位)。 这是因为BSD版的TCP软件采用一种500 ms的定时器。这种 500 ms 的定时器用于确定本章 中所有的各种各样的 TCP超时。当我们键入 telnet命令,将建立一个 6秒的定时器(12个时钟滴 答( t i c k)),但它可能在之后的 5 . 5秒~ 6秒内的任意时刻超时。图 1 8 - 7显示了这一发生过程。 秒 尽管定时器初始化为 1 2个时钟滴答,但定时计数器会在设置后的第一个 0~500 ms中的任意时

刻减1。从那以后,定时计数器大约每隔 500 ms减1,但在第1个500 ms内是可变的(我们使用 限定词“大约”是因为在 T C P每隔500 ms获得系统控制的瞬间,系统内核可能会优先处理其 他中断)。 11时钟滴答× 500ms/滴答=5.5

应用程序在此刻使 TCP设置一个6秒(12 滴答)的定时器

每滴答500毫秒

TCP重新设置一个 24秒的定时器

图18-7 TCP的500 ms定时器

当滴答计数器为 0时,6秒的定时器便会超时(见图 1 8 - 7),这个定时器会在以后的 2 4秒 (4 8个滴答)重新复位。之后的下一个定时器将更接近 2 4秒,因为当T C P的500 ms定时器被内 核调用时,它就会被修改一次。 18.3.2 服务类型字段 在图18-6中,出现了符号 [ tos 0x10 ]。这是IP数据报内的服务类型( TOS)字段(参见图 3-2)。BSD/386中的Telnet客户进程将这个字段设置为最小时延。

18.4 最大报文段长度 最大报文段长度( M S S)表示 T C P传往另一端的最大块数据的长度。当一个连接建立时, 连接的双方都要通告各自的 M S S。我们已经见过 M S S都是1 0 2 4。这导致 I P数据报通常是 4 0字 节长:20字节的TCP首部和20字节的IP首部。 在有些书中,将它看作可“协商”选项。它并不是任何条件下都可协商。当建立一个连 接时,每一方都有用于通告它期望接收的 MSS选项(MSS选项只能出现在 SYN报文段中)。如 果一方不接收来自另一方的 MSS值,则MSS就定为默认值 536字节(这个默认值允许 20字节的 IP首部和20字节的TCP首部以适合 576字节IP数据报)。 一般说来,如果没有分段发生, M S S还是越大越好(这也并不总是正确,参见图 2 4 - 3和 图2 4 - 4中的例子)。报文段越大允许每个报文段传送的数据就越多,相对 I P和T C P首部有更高 的网络利用率。当 T C P发送一个 S Y N时,或者是因为一个本地应用进程想发起一个连接,或 者是因为另一端的主机收到了一个连接请求,它能将 M S S值设置为外出接口上的 M T U长度减 去固定的IP首部和TCP首部长度。对于一个以太网, MSS值可达1460字节。使用 IEEE 802.3的 封装(参见 2.2节),它的MSS可达1452字节。 在本章见到的涉及 B S D / 3 8 6和S V R 4的M S S为1 0 2 4,这是因为许多 B S D的实现版本需要

180

使用TCP/IP详解,卷1:协议

下载

MSS为512的倍数。其他的系统,如 SunOS 4.1.3、Solaris 2.2 和AIX 3.2.2,当双方都在一个本 地以太网上时都规定 M S S为1 4 6 0。[Mogul 1993] 的比较显示了在以太网上 1 4 6 0的M S S在性能 上比1024的MSS更好。 如果目的 I P地址为“非本地的 ( n o n l o c a l )”,M S S通常的默认值为 5 3 6。而区分地址是本地 还是非本地是简单的,如果目的 I P地址的网络号与子网号都和我们的相同,则是本地的;如 果目的 I P地址的网络号与我们的完全不同,则是非本地的;如果目的 I P地址的网络号与我们 的相同而子网号与我们的不同,则可能是本地的,也可能是非本地的。大多数 T C P实现版都 提供了一个配置选项(附录 E和图E-1),让系统管理员说明不同的子网是属于本地还是非本地。 这个选项的设置将确定 M S S可以选择尽可能的大(达到外出接口的 M T U长度)或是默认值 536。 M S S让主机限制另一端发送数据报的长度。加上主机也能控制它发送数据报的长度,这 将使以较小 MTU连接到一个网络上的主机避免分段。 考虑我们的主机 s l i p,通过MTU为296的SLIP链路连接到路由器 b s d i上。图18-8显示这 些系统和主机sun。

图18-8 显示sun 与slip 间TCP连接的MSS值

从s u n向s l i p发起一个 T C P连接,并使用 t c p d u m p来观察报文段。图 1 8 - 9显示这个连接 的建立(省略了通告窗口大小)。

图18-9 tcpdump 显示了从sun 向slip 建立连接的过程

在这个例子中, s u n发送的报文段不能超过 2 5 6字节的数据,因为它收到的 M S S选项值为 2 5 6(第2行)。此外,由于 s l i p知道它外出接口的 M T U长度为 2 9 6,即使 s u n已经通告它的 MSS为1460,但为避免将数据分段,它不会发送超过 256字节数据的报文段。系统允许发送的 数据长度小于另一端的 MSS值。 只有当一端的主机以小于 576字节的MTU直接连接到一个网络中,避免这种分段才会有效。 如果两端的主机都连接到以太网上,都采用 5 3 6的M S S,但中间网络采用 2 9 6的M T U,也将会 出现分段。使用路径上的 MTU发现机制(参见 24.2节)是关于这个问题的唯一方法。

18.5 TCP的半关闭 T C P提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓

181

第18章 TCP连接的建立与终止使用

下载

的半关闭。正如我们早些时候提到的只有很少的应用程序使用它。 为了使用这个特性,编程接口必须为应用程序提供一种方式来说明“我已经完成了数据 传送,因此发送一个文件结束( FIN)给另一端,但我还想接收另一端发来的数据,直到它给 我发来文件结束( FIN)”。 如果应用程序不调用close而调用shutdown,且第2个参数值为1,则插口的API支持 半关闭。然而,大多数的应用程序通过调用close终止两个方向的连接。 图1 8 - 1 0显示了一个半关闭的典型例

服务器

客户

子。让左方的客户端开始半关闭,当然也 可以由另一端开始。开始的两个报文段和 图1 8 - 4是相同的:初始端发出的 F I N,接

应用进程 shutdown

向应用进程 交付EOF

着是另一端对这个 F I N的A C K报文段。但 后面就和图 1 8 - 4不同,因为接收半关闭的 一方仍能发送数据。我们只显示一个数据 报文段和一个 ACK报文段,但可能发送了

应用进程write 应用进程 read

许多数据报文段(将在第 1 9章讨论数据报 文段和确认报文段的交换)。当收到半关 闭的一端在完成它的数据传送后,将发送

应用进程close 向应用进程 交付EOF

一个 F I N关闭这个方向的连接,这将传送 一个文件结束符给发起这个半关闭的应用 进程。当对第二个 F I N进行确认后,这个 图18-10 TCP半关闭的例子

连接便彻底关闭了。

为什么要有半关闭?一个例子是 U n i x中的r s h( 1 )命令,它将完成在另一个系统上执行一 个命令。命令 sun % rsh bsdi sort < datafile

将在主机 b s d i上执行s o r t排序命令,r s h命令的标准输入来自文件 d a t a f i l e。r s h将在它 与在另一主机上执行的程序间建立一个

T C P 连接。 r s h 的 操 作 很 简 单 : 它 将 标 准 输 入

(d a t a f i l e)复制给 T C P连接,并将结果从 T C P连接中复制给标准输出(我们的终端)。图 18-11显示了这个建立过程(牢记 TCP连接是全双工的)。 标准输入

主机bsdi

主机sun TCP连接

终端

标准输出

图18-11 命令:rsh bsdi sort < datafile

在远端主机 b s d i上,r s h d服务器将执行 s o r t程序,它的标准输入和标准输出都是 T C P 连接。第14章的 [Stevens 1990] 详细介绍了有关 Unix进程的结构,但这儿涉及的是使用 TCP连 接以及需要使用 TCP的半关闭。 s o r t程序只有读取到所有输入数据后才能产生输出。所有的原始数据通过

T C P连接从

r s h客户端传送到 s o r t服务器进行排序。当输入( d a t a f i l e)到达文件尾时, r s h客户端

182

使用TCP/IP详解,卷1:协议

下载

执行这个 T C P连接的半关闭。接着 s o r t服务器在它的标准输入(这个 T C P连接)上收到一个 文件结束符,对数据进行排序,并将结果写在它的标准输出上( T C P连接)。r s h客户端继续 接收来自TCP连接另一端的数据,并将排序的文件复制到它的标准输出上。 没有半关闭,需要其他的一些技术让客户通知服务器, 客户端已经完成了它的数据传送,但 仍要接收来自服务器的数据。使用两个TCP连接也可作为一个选择,但使用半关闭的单连接更好。

18.6 TCP的状态变迁图 我们已经介绍了许多有关发起和终止 T C P连接的规则。这些规则都能从图 1 8 - 1 2所示的状 态变迁图中得出。 开始 应用进程:被动打开 发送:无

被动打开

收:SYN SYN收到

应用进程: 关闭

发:SYN,ACF

主动打开

同时打开 应用进程: 关闭 发:

或超时

收: FIN

FIN 数据传送状态

发: ACK 应用进程: 关闭 发:

收: FIN

FIN

同时关闭

收: ACK 发送:无

发: ACK 收: ACK

收: ACK

被动关闭

发送 无

发送: 无 收: FIN 发: ACK

2MSL超时 主动关闭 说明客户的正常状态变迁 说明服务器的正常状态变迁 应用进程: 说明当应用执行某种操作时发生的状态变迁 收: 说明当收到TCP报文段时状态的变迁 发: 说明为了进行某个状态变迁要发送的TCP报文段

图18-12 TCP的状态变迁图

在这个图中要注意的第一点是一个状态变迁的子集是“典型的”。我们用粗的实线箭头表 示正常的客户端状态变迁,用粗的虚线箭头表示正常的服务器状态变迁。

183

第18章 TCP连接的建立与终止使用

下载 第二点是两个导致进入ESTABLISH-

客户

服务器

ED状态的变迁对应打开一个连接,而两 个导致从ESTABLISHED状态离开的变迁

(被动打开) 主动打开

对应关闭一个连接。ESTABLISHED状态 是连接双方能够进行双向数据传递的状 态。以后的章节将介绍这个状态。 将图中左下角 4个状态放在一个虚 线框内,并标为“主动关闭”。其他两 个状态(CLOSE_WAIT和LAST_ACK) 也用虚线框住,并标为“被动关闭”。

主动关闭

在这个图中11个状态的名称

(被动 关闭)

(CLOSED, LISTEN, SYN_SENT 等) 是有意与 n e t s t a t 命令显示的状态名 称一致。 n e t s t a t对状态的命名几乎 与 在 RFC 793 中 的 最 初 描述 一 致 。 C L O S E D 状态不是一个真正的状态, 而是这个状态图的假想起点和终点。 从L I S T E N到S Y N _ S E N T的变迁是

图18-13 TCP正常连接建立和终止所对应的状态

正确的,但伯克利版的 TCP软件并不支持它。 只有当 S Y N _ R C V D状态是从 L I S T E N状态(正常情况)进入,而不是从 S Y N _ S E N T状态 (同时打开)进入时,从 S Y N _ R C V D回到L I S T E N的状态变迁才是有效的。这意味着如果我们 执行被动关闭(进入 LISTEN),收到一个 SYN,发送一个带 ACK的SYN(进入SYN_RCVD), 然后收到一个RST,而不是一个ACK,便又回到 LISTEN状态并等待另一个连接请求的到来。 图18-13显示了在正常的 TCP连接的建立与终止过程中,客户与服务器所经历的不同状态。 它是图18-3的再现,不同的是仅显示了一些状态。 假定在图 1 8 - 1 3中左边的客户执行主动打开,而右边的服务器执行被动打开。尽管图中显 示出由客户端执行主动关闭,但和早前我们提到的一样,另一端也能执行主动关闭。 可以使用图 18-12的状态图来跟踪图 18-13的状态变化过程,以便明白每个状态的变化。 18.6.1 2MSL等待状态 T I M E _ WA I T状态也称为 2 M S L等待状态。每个具体 T C P实现必须选择一个报文段最大生 存时间M S L(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。 我们知道这个时间是有限的,因为 TCP报文段以IP数据报在网络内传输,而 IP数据报则有限制 其生存时间的TTL字段。 RFC 793 [Postel 1981c] 指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟, 或2分钟。 从第8章我们知道在实际应用中,对 IP数据报TTL的限制是基于跳数,而不是定时器。 对一个具体实现所给定的 M S L值,处理的原则是:当 T C P执行一个主动关闭,并发回最

184

使用TCP/IP详解,卷1:协议

下载

后一个 A C K,该连接必须在 TIME_WAIT状态停留的时间为 2倍的M S L。这样可让 T C P再次发 送最后的ACK以防这个ACK丢失(另一端超时并重发最后的 FIN)。 这种 2 M S L 等待的另一个结果是这个 T C P连接在 2 M S L等待期间,定义这个连接的插口 (客户的 I P地址和端口号,服务器的 I P地址和端口号)不能再被使用。这个连接只能在 2 M S L 结束后才能再被使用。 遗憾的是,大多数 T C P实现(如伯克利版)强加了更为严格的限制。在 2 M S L等待期间, 插口中使用的本地端口在默认情况下不能再被使用。我们将在下面看到这个限制的例子。 某些实现和API提供了一种避开这个限制的方法。使用插口 API时,可说明其中的 SO_REUSEADDR选项。它将让调用者对处于2MSL等待的本地端口进行赋值,但我们 将看到TCP原则上仍将避免使用仍处于2MSL连接中的端口。 在连接处于 2 M S L等待时,任何迟到的报文段将被丢弃。因为处于 2 M S L等待的、由该插 口对(socket pair)定义的连接在这段时间内不能被再用,因此当要建立一个有效的连接时,来 自该连接的一个较早替身( i n c a r n a t i o n)的迟到报文段作为新连接的一部分不可能不被曲解 (一个连接由一个插口对来定义。一个连接的新的实例( instance)称为该连接的替身)。 我们说图 1 8 - 1 3中客户执行主动关闭并进入 T I M E _ WA I T是正常的。服务器通常执行被动 关闭,不会进入 TIME_WAIT状态。这暗示如果我们终止一个客户程序,并立即重新启动这个 客户程序,则这个新客户程序将不能重用相同的本地端口。这不会带来什么问题,因为客户 使用本地端口,而并不关心这个端口号是什么。 然而,对于服务器,情况就有所不同,因为服务器使用熟知端口。如果我们终止一个已 经建立连接的服务器程序,并试图立即重新启动这个服务器程序,服务器程序将不能把它的 这个熟知端口赋值给它的端点,因为那个端口是处于 2MSL连接的一部分。在重新启动服务器 程序前,它需要在 1~4分钟。 可以通过 s o c k程序看到这一切。我们启动服务器程序,从一个客户程序进行连接,然后 停止这个服务器程序。 启动服务器进程,在端口6666监听(在bsdi上执行客 户进程与该端口进行连接) 键入中断键停止服务器进程 并立即在同一端口重启服务器进程 检测连接状态

删除了许多其他行

当重新启动服务器程序时,程序报告一个差错信息说明不能绑定它的熟知端口,因为该端口 已被使用(即它处于 2MSL等待)。 运行netstat程序来查看连接的状态,以证实它的确处于 2MSL等待状态。 如果我们一直试图重新启动服务器程序,并测量它直到成功所需的时间,我们就 能确定出2MSL值。对于SunOS 4.1.3、SVR4、BSD/386和AIX 3.2.2,它需要1分钟才能 重新启动服务器程序,这意味着它们的 MSL值为30秒。而对于Solaris 2.2 ,它需要4分

185

第18章 TCP连接的建立与终止使用

下载

钟才能重新启动服务器程序,这表示它的MSL值为2分钟。 如果一个客户程序试图申请一个处于 2 M S L等待的端口(客户程序通常不会这么做),就 会出现同样的差错。 启动客户进程,与回显服务器进程连接 键入这一行 这一行应被服务器进程回显 键入文件结束符终止客户进程

我们在第 1次执行客户程序时采用 - v选项来查看它使用的本地端口为( 11 6 2)。第2次执行客 户程序时则采用- b选项来选择端口 11 6 2为它的本地端口。正如我们所预料的那样,客户程序 无法那么做,因为那个端口是一个还处于 2MSL等待连接的一部分。 需要再次强调2MSL等待的一个效果,因为我们将在第 27章的文件传输协议 FTP中遇到它。 和以前介绍的一样,一个插口对(即包含本地 I P地址、本地端口、远端 I P地址和远端端口的 4 元组)在它处于 2MSL等待时,将不能再被使用。尽管许多具体的实现中允许一个进程重新使 用仍处于 2 M S L等待的端口(通常是设置选项 S O _ R E U S E A D D R),但T C P不能允许一个新的连 接建立在相同的插口对上。可通过下面的试验来看到这一点: 启动服务器进程,在端口6666监听(在bsdi上执行 客户进程与该端口进行连接) 键入中断键停止服务器进程 尝试在本地端口6666启动客户进程 再次尝试,加上-A选项

在第1次运行 s o c k程序中,我们将它作为服务器程序,端口号为 6 6 6 6,并从主机b s d i上的一 个客户程序与它连接,这个客户程序使用的端口为 1 0 9 8。我们终止服务器程序,因此它将执 行主动关闭。这将导致 4 元组 1 4 0 . 2 5 2 . 1 3 . 3 3 (本地 I P地址)、 6 6 6 6 ( 本 地 端 口 号 )、 140.252.13.35(另一端IP地址)和1098(另一端的端口号)在服务器主机进入 2MSL等待。 在第 2次运行 s o c k 程序时,我们将它作为客户程序,并试图将它的本地端口号指明为 6666,同时与主机 b s d i在端口1098上进行连接。但这个程序在试图将它的本地端口号赋值为 6666时产生了一个差错,因为这个端口是处于 2MSL等待4元组的一部分。 为了避免这个差错,我们再次运行这个程序,并使用选项-

A 来设置前面提到的

S O _ R E U S E A D D R。这将让 s o c k程序能将它的本地端口号设置为 6 6 6 6,但当我们试图进行主 动打开时,又出现了一个差错。即使它能将它的本地端口设置为

6 6 6 6,但它仍不能和主机

bsdi在端口1098上进行连接,因为定义这个连接的插口对仍处于 2MSL等待状态。 如果我们试图从其他主机来建立这个连接会如何?首先我们必须在 sun上以-A标记来重新 启动服务器程序,因为它需要的端口( 6666)是还处于 2MSL等待连接的一部分。 sun % s o c k - A - s 6 6 6 6

启动服务器程序,在端口 6 6 6 6监听

接着,在2MSL等待结束前,我们在 bsdi上启动客户程序: bsdi % sock -b1098 sun 6666

186

使用TCP/IP详解,卷1:协议

下载

connected on 140.252.13.35.1098 to 140.252.13.33.6666

不幸的是它成功了!这违反了 T C P规范,但被大多数的伯克利版实现所支持。这些实现允许 一个新的连接请求到达仍处于 TIME_WAIT状态的连接,只要新的序号大于该连接前一个替身 的最后序号。在这个例子中,新替身的 I S N被设置为前一个替身最后序号与 128 000的和。附 录的RFC 1185 [Jacobsan、Braden和Zhang 1990] 指出了这项技术仍可能存在缺陷。 对于同一连接的前一个替身,这个具体实现中的特性让客户程序和服务器程序能连续地 重用每一端的相同端口号,但这只有在服务器执行主动关闭才有效。我们将在图 2 7 - 8中使用 FTP时看到这个 2MSL等待条件的另一个例子。也见习题 18.5。 18.6.2 平静时间的概念 对于来自某个连接的较早替身的迟到报文段, 2 M S L等待可防止将它解释成使用相同插口 对的新连接的一部分。但这只有在处于 2MSL等待连接中的主机处于正常工作状态时才有效。 如果使用处于 2MSL等待端口的主机出现故障,它会在 MSL秒内重新启动,并立即使用故 障前仍处于2MSL的插口对来建立一个新的连接吗?如果是这样,在故障前从这个连接发出而 迟到的报文段会被错误地当作属于重启后新连接的报文段。无论如何选择重启后新连接的初 始序号,都会发生这种情况。 为了防止这种情况, RFC 793指出TCP在重启动后的 MSL秒内不能建立任何连接。这就称 为平静时间 (quiet time)。 只有极少的实现版遵守这一原则,因为大多数主机重启动的时间都比MSL秒要长。 18.6.3 FIN_WAIT_2 状态 在FIN_WAIT_2状态我们已经发出了 F I N,并且另一端也已对它进行确认。除非我们在实 行半关闭,否则将等待另一端的应用层意识到它已收到一个文件结束符说明,并向我们发一 个 F I N 来关闭另一方向的连接。只有当另一端的进程完成这个关闭,我们这端才会从 FIN_WAIT_2状态进入 TIME_WAIT状态。 这意味着我们这端可能永远保持这个状态。另一端也将处于 CLOSE_WAIT状态,并一直 保持这个状态直到应用层决定进行关闭。 许多伯克利实现采用如下方式来防止这种在FIN_WAIT_2状态的无限等待。如果执 行主动关闭的应用层将进行全关闭,而不是半关闭来说明它还想接收数据,就设置一 个定时器。如果这个连接空闲10分钟75秒,TCP将进入CLOSED状态。在实现代码的注 释中确认这个实现代码违背协议的规范。

18.7 复位报文段 我们已经介绍了 TCP首部中的 RST比特是用于“复位”的。一般说来,无论何时一个报文 段发往基准的连接( referenced connection)出现错误, T C P都会发出一个复位报文段(这里 提到的“基准的连接”是指由目的 I P地址和目的端口号以及源 I P地址和源端口号指明的连接。 这就是为什么RFC 793称之为插口)。

187

第18章 TCP连接的建立与终止使用

下载 18.7.1 到不存在的端口的连接请求

产生复位的一种常见情况是当连接请求到达时,目的端口没有进程正在听。对于

U D P,

我们在 6 . 5节看到这种情况,当一个数据报到达目的端口时,该端口没在使用,它将产生一个 ICMP端口不可达的信息。而 TCP则使用复位。 产生这个例子也很容易,我们可使用 Te l n e t客户程序来指明一个目的端口没在使用的情 况: bsdi % telnet svr4 20000 端口2 0 0 0 0未使用 Trying 140.252.13.34... telnet: Unable to connect to remote host: Connection refused

Telnet客户程序会立即显示这个差错信息。图 18-14显示了对应这个命令的分组交换过程。 1

2

0.0

bsdi.1087 > svr4.20000: S 297416193:297416193(0) win 4096 [tos 0x10] 0.003771 (0.0038) svr4.20000 > bsdi.1087: R 0:0(0) ack 297416194 win 0

图18-14 试图在不存在的端口上打开连接而产生的复位

在这个图中需要注意的值是复位报文段中的序号字段和确认序号字段。因为 A C K比特在 到达的报文段中没有被设置为 1,复位报文段中的序号被置为 0,确认序号被置为进入的 ISN加 上数据字节数。尽管在到达的报文段中没有真正的数据,但 S Y N比特从逻辑上占用了 1字节的 序号空间;因此,在这个例子中复位报文段中确认序号被置为 ISN与数据长度(0)、SYN比特 所占的1的总和。 18.7.2 异常终止一个连接 我们在 1 8 . 2节中看到终止一个连接的正常方式是一方发送 F I N。有时这也称为有序释放 (orderly release),因为在所有排队数据都已发送之后才发送 F I N,正常情况下没有任何数据 丢失。但也有可能发送一个复位报文段而不是 FIN来中途释放一个连接。有时称这为异常释放 (abortive release)。 异常终止一个连接对应用程序来说有两个优点:(1)丢弃任何待发数据并立即发送复位 报文段;( 2)R S T的接收方会区分另一端执行的是异常关闭还是正常关闭。应用程序使用的 API必须提供产生异常关闭而不是正常关闭的手段。 使用 s o c k程序能够观察这种异常关闭的过程。 Socket API 通过“ linger on close”选项 (S O _ L I N G E R)提供了这种异常关闭的能力。我们加上 - L选项并将停留时间设为 0。这将导 致连接关闭时进行复位而不是正常的 F I N。我们连接到处于服务器上的 s o c k程序,并键入一 输入行: bsdi % sock -L0 svr4 8888 hello, world ^D

这是客户程序,服务器程序显示后面 键入一行输入,它被发往到另一端 键入文件结束符,终止客户程序

图18-15是这个例子的t c p d u m p输出显示(在这个图中我们已经删除了所有窗口大小的说 明,因为它们与讨论无关)。 第1 ~ 3行显示出建立连接的正常过程。第 4行发送我们键入的数据行( 1 2个字符和 U n i x换

188

使用TCP/IP详解,卷1:协议

下载

行符),第5行是对收到数据的确认。

图18-15 使用复位(RST)而不是FIN来异常终止一个连接

第6行对应为终止客户程序而键入的文件结束符( Control_D)。由于我们指明使用异常关闭 而不是正常关闭(命令行中的-L0选项),因此主机bsdi端的TCP发送一个RST而不是通常的FIN。 RST报文段中包含一个序号和确认序号。需要注意的是 RST报文段不会导致另一端产生任何响 应,另一端根本不进行确认。收到RST的一方将终止该连接,并通知应用层连接复位。 我们在服务器上得到下面的差错信息: svr4 % sock -s 8888 作为服务器进程运行,在端口 8 8 8 8监听 hello, world 这行是客户端发送的 read error: Connection reset by peer

这个服务器程序从网络中接收数据并将它接收的数据显示到其标准输出上。通常,从它 的TCP上收到文件结束符后便将结束,但这里我们看到当收到 RST时,它产生了一个差错。这 个差错正是我们所期待的:连接被对方复位了。 18.7.3 检测半打开连接 如果一方已经关闭或异常终止连接而另一方却还不知道,我们将这样的 T C P连接称为半 打开(H a l f - O p e n)的。任何一端的主机异常都可能导致发生这种情况。只要不打算在半打开 连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。 半打开连接的另一个常见原因是当客户主机突然掉电而不是正常的结束客户应用程序后 再关机。这可能发生在使用 P C机作为 Te l n e t的客户主机上,例如,用户在一天工作结束时关 闭P C机的电源。当关闭 P C机电源时,如果已不再有要向服务器发送的数据,服务器将永远不 知道客户程序已经消失了。当用户在第二天到来时,打开 P C机,并启动新的 Te l n e t客户程序, 在服务器主机上会启动一个新的服务器程序。这样会导致服务器主机中产生许多半打开的 TCP连接(在第23章中我们将看到使用 TCP的keepalive选项能使 TCP的一端发现另一端已经消 失)。 能很容易地建立半打开连接。在 b s d i上运行Telnet客户程序,通过它和 svr4上的丢弃服务 器建立连接。我们键入一行字符,然后通过 t c p d u m p进行观察,接着断开服务器主机与以太 网的电缆,并重启服务器主机。这可以模拟服务器主机出现异常(在重启服务器之前断开以 太网电缆是为了防止它向打开的连接发送 F I N,某些 T C P在关机时会这么做)。服务器主机重 启后,我们重新接上电缆,并从客户向服务器发送另一行字符。由于服务器的 T C P已经重新 启动,它将丢失复位前连接的所有信息,因此它不知道数据报文段中提到的连接。 T C P的处 理原则是接收方以复位作为应答。

189

第18章 TCP连接的建立与终止使用

下载

启动客户进程

运行已正确发送 重新启动服务器主机 导致连接复位

图18-16是这个例子的t c p d u m p输出显示(已从这个输出中删除了窗口大小的说明、服务 类型信息和 MSS声明,因为它们与讨论无关)。

图18-16 复位作为半打开连接上数据段的应答

第1 ~ 3行是正常的连接建立过程。第 4行向丢弃服务器发送字符行“ h i t h e r e”,第5行是确 认。 然后是断开 svr4的以太网电缆,重新启动 s v r 4,并重新接上电缆。这个过程几乎需要 190 秒。接着从客户端输入下一行(即“ another line”),当我们键入回车键后,这一行被发往服 务器(图 1 8 - 1 6的第6行)。这导致服务器产生一个响应,但要注意的是由于服务器主机经过重 新启动,它的 A R P高速缓存为空,因此需要一个 A R P请求和应答(第 7、8行)。第 9行表示 R S T被发送出去。客户收到复位报文段后显示连接已被另一端的主机终止( Te l n e t客户程序发 出的最后信息不再有什么价值)。

18.8 同时打开 两个应用程序同时彼此执行主动打开的情况是可能的,尽管发生的可能性极小。每一方 必须发送一个 S Y N,且这些 S Y N必须传递给对方。这需要每一方使用一个对方熟知的端口作 为本地端口。这又称为同时打开( simultaneous open)。 例如,主机A中的一个应用程序使用本地端口 7777,并与主机B的端口8888执行主动打开。 主机B中的应用程序则使用本地端口 8888,并与主机 A的端口7777执行主动打开。 这与下面的情况不同:主机A中的Telnet客户程序和主机 B中Telnet的服务器程序建立连接, 与此同时,主机 B中的Te l n e t客户程序与主机 A的Te l n e t服务器程序也建立连接。在这个 Te l n e t 例子中,两个 Te l n e t服务器都执行被动打开,而不是主动打开,并且 Te l n e t客户选择的本地端 口不是另一端Telnet服务器进程所熟悉的端口。 T C P是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连 接(其他的协议族,最突出的是 OSI运输层,在这种情况下将建立两条连接而不是一条连接)。 当出现同时打开的情况时,状态变迁与图 1 8 - 1 3所示的不同。两端几乎在同时发送 S Y N, 并进入S Y N _ S E N T状态。当每一端收到 S Y N时,状态变为 S Y N _ R C V D(如图1 8 - 1 2),同时它

190

使用TCP/IP详解,卷1:协议

下载

们都再发 S Y N并对收到的 S Y N进行确认。当双方都收到 S Y N及相应的 A C K时,状态都变迁为 ESTABLISHED。图18-17显示了这些状态变迁过程。 (主动打开)

(主动打开)

图18-17 同时打开期间报文段的交换

一个同时打开的连接需要交换 4个报文段,比正常的三次握手多一个。此外,要注意的是 我们没有将任何一端称为客户或服务器,因为每一端既是客户又是服务器。 一个例子 尽管很难,但仍有可能产生一个同时打开的连接。两端必须几乎在同时启动,以便收到 彼此的S Y N。只要两端有较长的往返时间就能保证这一点。这样我们将一端设置在主机 b s d i 上,另一端则设置在主机 v a n g o g h . c s . b e r k e l e y . e d u上。由于两端之间有一条拨号链路 SLIP,它的往返时间对保证双方同步收到 SYN是足够长的(几百毫秒)。 一端(b s d i)将本地端口设置为 8 8 8 8(使用命令行选项 - b),并对另一端主机端口 7 7 7 7 执行主动打开。

键入该行 在另一端键入这一行 当收到FIN时的输出显示

另一端也几乎在同一时间将本地端口设置为 7777,并对端口 8888执行主动打开。

这是另一端键入的行 键入这行 键入文件结束符EOF

我们指明带- v标志的s o c k程序来验证连接两端的 I P地址和端口号。这个选项也显示每一 端的M S S值。为证实两端确实在相互交谈,我们在每一端还输入一行字符,看它们是否会被 送到另一端并显示出来。 图18-18显示了这个连接的段交换过程(我们删除了出现在来自 v a n g o g h第一个SYN中的 一些新的 T C P选项,因为 v a n g o g h使用4 . 4 B S D系统。将在 1 8 . 1 0节介绍这些较新的选项)。注 意两个SYN(第1~2行)后跟着两个带 ACK的SYN(第3~4行)。它们将执行同时打开。 第5行显示了由 b s d i发送给v a n g o g h的输入行“ hello, world”,第6行对此进行确认。第 7~8行对应另一方向的输入行“ and hi there”和确认。第9~12行显示正常的连接关闭。 许多伯克利版的 TCP实现都不能正确地支持同时打开。在这些系统中,如果能够

191

第18章 TCP连接的建立与终止使用

下载

进行SYN的同步接收,你将经历极多的报文段交换过程才能关闭它们。每个报文段交 换过程包括每个方向上的一个 S Y N 和一个 A C K。图 1 8 - 1 2中从 S Y N _ S E N T 到状态 SYN_RCVD的变迁在许多TCP实现中很少测试过。

图18-18 同时打开期间的报文段交换过程

18.9 同时关闭 我们在以前讨论过一方(通常但不总是客户方)发送第一个 FIN执行主动关闭。双方都执 行主动关闭也是可能的, TCP协议也允许这样的同时关闭( simultaneous close)。 在图1 8 - 1 2中,当应用层发出关闭命令时,两端均从 E S TA B L I S H E D变为 F I N _ WA I T _ 1。 这将导致双方各发送一个 F I N,两个 F I N经过网络传送后分别到达另一端。收到 F I N后,状态 由F I N _ WA I T _ 1变迁到 C L O S I N G,并发送最后的 A C K。当收到最后的 A C K时,状态变化为 TIME_WAIT。图18-19总结了这些状态的变化。 (主动关闭)

(主动关闭)

图18-19 同时关闭期间的报文段交换

同时关闭与正常关闭使用的段交换数目相同。

18.10 TCP 选项 TCP首部可以包含选项部分(图 17-2)。仅在最初的 TCP规范中定义的选项是选项表结束、 无操作和最大报文段长度。在我们的例子中,几乎每个 SYN报文段中我们都遇到过 MSS选项。 新的R F C,主要是 RFC 1323 [Jacobson, Braden和Borman 1992],定义了新的 T C P选项,

192

使用TCP/IP详解,卷1:协议

下载

这些选项的大多数只在最新的 T C P实现中才能见到(我们将在第 2 4章介绍这些新选项)。图 18-20显示了当前 TCP选项的格式,这些选项的定义出自于 RFC 793和RFC 1323。 选项表结束: 1字节 无操作: 1字节 最大报文段 长度

最大报文段长度: 1字节

1字节

2字节 移位 数

窗口扩大因子: 1字节

1字节

1字节 时间戳值

时间戳: 1字节

1字节

时间戳回显应答

4字节

4字节

图18-20 TCP选项

每个选项的开始是 1字节kind字段,说明选项的类型。kind字段为0和1的选项仅占 1个字节。 其他的选项在kind字节后还有 len字节。它说明的长度是指总长度,包括 kind字节和len字节。 设置无操作选项的原因在于允许发方填充字段为 4字节的倍数。如果我们使用 4.4BSD系统 进行初始化 TCP连接,tcpdump将在初始的 SYN上显示下面 TCP选项:

MSS选项设置为 512,后面是NOP,接着是窗口扩大选项。第一个 NOP用来将窗口扩大选项填 充为4字节的边界。同样, 1 0字节的时间戳选项放在两个 N O P后,占 1 2字节,同时使两个 4字 节的时间戳满足 4字节边界。 其他kind值为4、5、6和7的四个选项称为选择 ACK及回显选项。由于回显选项已 被时间戳选项取代,而目前定义的选择 ACK选项仍未定论,并未包括在 RFC 1323中, 因此图18-20没有将它们列出。另外,作为 TCP事务(第 24.7节)的T/TCP建议也指明 kind为11, 12和 13的三个选项。

18.11 TCP 服务器的设计 我们在1.8节说过大多数的 TCP服务器进程是并发的。当一个新的连接请求到达服务器时, 服务器接受这个请求,并调用一个新进程来处理这个新的客户请求。不同的操作系统使用不 同的技术来调用新的服务器进程。在 U n i x系统下,常用的技术是使用 f o r k函数来创建新的进 程。如果系统支持,也可使用轻型进程,即线程( thread)。 我们感兴趣的是 T C P与若干并发服务器的交互作用。需要回答下面的问题:当一个服务 器进程接受一来自客户进程的服务请求时是如何处理端口的?如果多个连接请求几乎同时到

193

第18章 TCP连接的建立与终止使用

下载 达会发生什么情况? 18.11.1 TCP服务器端口号

通过观察任何一个 T C P服务器,我们能了解 T C P如何处理端口号。我们使用 n e t s t a t 命令来观察 Te l n e t服务器。下面是在没有 Te l n e t连接时的显示(只留下显示 Te l n e t服务器的 行)。 sun % n e t s t a t - a - n - f i n e t Active Internet connections (including servers) Proto Recv-Q Send-Q Local Address Foreign Address tcp 0 0 *.23 *.*

(state) LISTEN

- a标志将显示网络中的所有主机端,而不仅仅是处于 E S TA B L I S H E D的主机端。 - n标志 将以点分十进制的形式显示 I P地址,而不是通过 D N S将地址转化为主机名,同时还要求显示 端口号(例如为 2 3)而不是服务名称(如 Te l n e t)。- f i n e t选项则仅要求显示使用 T C P或 U D P的主机。 显示的本地地址为 * . 2 3,星号通常又称为通配符。这表示传入的连接请求(即 S Y N)将 被任何一个本地接口所接收。如果该主机是多接口主机,我们将制定其中的一个 I P地址为本 地I P地址,并且只接收来自这个接口的连接(在本节后面我们将看到这样的例子)。本地端口 为23,这是Telnet的熟知端口号。 远端地址显示为 * . *,表示还不知道远端 I P地址和端口号,因为该端还处于 L I S T E N状态, 正等待连接请求的到达。 现在我们在主机 s l i p(1 4 0 . 2 5 2 . 1 3 . 6 5)启动一个 Te l n e t客户程序来连接这个 Te l n e t服务器。 以下是netstat程序的输出行: Proto Recv-Q Send-Q tcp 0 0 tcp 0 0

Local Address 140.252.13.33.23 *.23

Foreign Address 140.252.13.65.1029 *.*

(state) ESTABLISHED LISTEN

端口为2 3的第1行表示处于E S TABLISHED 状态的连接。另外还显示了这个连接的本地 I P 地址、本地端口号、远端 I P地址和远端端口号。 本地I P地址为该连接请求到达的接口(以太 网接口,140.252.13.33)。 处于L I S T E N状态的服务器进程仍然存在。这个服务器进程是当前 Te l n e t服务器用于接收 其他的连接请求。当传入的连接请求到达并被接收时,系统内核中的 T C P模块就创建一个处 于E S TA B L I S H E D状态的进程。另外,注意处于 E S TA B L I S H E D状态的连接的端口不会变化: 也是23,与处于LISTEN状态的进程相同。 现在我们在主机 slip上启动另一个 Telnet客户进程,并仍与这个 Telnet服务器进行连接。以 下是netstat程序的输出行: Proto Recv-Q Send-Q Local Address tcp 0 0 140.252.13.33.23 tcp 0 0 140.252.13.33.23 tcp 0 0 *.23

Foreign Address 140.252.13.65.1030 140.252.13.65.1029 *.*

(state) ESTABLISHED ESTABLISHED LISTEN

现在我们有两条从相同主机到相同服务器的处于 ESTABLISHED的连接。它们的本地端口号均 为23。由于它们的远端端口号不同,这不会造成冲突。因为每个 Telnet客户进程要使用一个外

194

使用TCP/IP详解,卷1:协议

下载

设端口,并且这个外设端口会选择为主机( s l i p)当前未曾使用的端口,因此它们的端口号 肯定不同。 这个例子再次重申 T C P使用由本地地址和远端地址组成的 4元组:目的 I P地址、目的端口 号、源 I P地址和源端口号来处理传入的多个连接请求。 T C P仅通过目的端口号无法确定那个 进程接收了一个连接请求。另外,在三个使用端口 2 3的进程中,只有处于 L I S T E N的进程能够 接收新的连接请求。处于 E S TA B L I S H E D的进程将不能接收 S Y N报文段,而处于 L I S T E N的进 程将不能接收数据报文段。 下面我们从主机 s o l a r i s上启动第 3个Te l n e t客户进程,这个主机通过 S L I P链路与主机 s u n相 连,而不是以太网接口。 Proto Recv-Q Send-Q tcp 0 0 tcp 0 0 tcp 0 0 tcp 0 0

Local Address 140.252.1.29.23 140.252.13.33.23 140.252.13.33.23 *.23

Foreign Address 140.252.1.32.34603 140.252.13.65.1030 140.252.13.65.1029 *.*

(state) ESTABLISHED ESTABLISHED ESTABLISHED LISTEN

现在第一个 E S TA B L I S H E D连接的本地 I P地址对应多地址主机 s u n中的 S L I P链路接口地址 (140.252.1.29)。 18.11.2 限定的本地IP地址 我们来看看当服务器不能任选其本地 I P地址而必须使用特定的 I P地址时的情况。如果我 们为s o c k程序指明一个 I P地址(或主机名),并将它作为服务器,那么该 I P地址就成为处于 LISTEN服务器的本地IP地址。例如 sun % sock -s 140.252.1.29 8888

使这个服务器程序的连接仅局限于来自 S L I P接口(1 4 0 . 2 5 2 . 1 . 2 9)。n e t s t a t的显示说明了这 一点: Proto Recv-Q Send-Q tcp 0 0

Local Address 140.252.1.29.8888

Foreign Address *.*

(state) LISTEN

如果我们从主机 solaris通过SLIP链路与这个服务器相连接,它将正常工作。 Proto Recv-Q Send-Q Local Address tcp 0 0 140.252.1.29.8888 tcp 0 0 140.252.1.29.8888

Foreign Address 140.252.1.32.34614 *.*

(state) ESTABLISHED LISTEN

但如果我们试图从以太网( 140.252.13)中的主机与这个服务器进行连接,连接请求将被 TCP模块拒绝。如果使用 t c p d u m p来观察这一切,对连接请求 SYN的响应是一个如图 18-21所 示的RST。

图18-21 具有限定本地IP地址服务器对连接请求的拒绝

这个连接请求将不会到达服务器的应用程序,因为它根据应用程序中指定的本地 I P地址 被内核中的 TCP模块拒绝。

195

第18章 TCP连接的建立与终止使用

下载 18.11.3 限定的远端IP地址

在11 . 1 2节,我们知道 U D P服务器通常在指定 I P本地地址和本地端口外,还能指定远端 I P 地址和远端端口。 RFC 793中显示的接口函数允许一个服务器在执行被动打开时,可指明远端 插口(等待一个特定的客户执行主动打开),也可不指明远端插口(等待任何客户)。 遗憾的是,大多数 API都不支持这么做。服务器必须不指明远端插口,而等待连接请求的 到来,然后检查客户端的 IP地址和端口号。 图1 8 - 2 2总结了 T C P服务器进行连接时三种类型的地址绑定。在三种情况中, l p o r t是服务 器的熟知端口,而 localIP必须是一个本地接口的 IP地址。表中行的顺序正是 TCP模块在收到一 个连接请求时确定本地地址的顺序。最常使用的绑定(第 1行,如果支持的话)将最先尝试, 最不常用的(最后一行两端的 IP地址都没有制定)将最后尝试。 描

远端地址

本地地址



限制到一个客户进程(通常不支持) 限制为到达一个本地接口:Local IP的连接 接收发往Lport的所有连接

图18-22 TCP服务器本地和远端IP地址及端口号的规范

18.11.4 呼入连接请求队列 一个并发服务器调用一个新的进程来处理每个客户请求,因此处于被动连接请求的服务 器应该始终准备处理下一个呼入的连接请求。那正是使用并发服务器的根本原因。但仍有可 能出现当服务器在创建一个新的进程时,或操作系统正忙于处理优先级更高的进程时,到达 多个连接请求。当服务器正处于忙时, TCP是如何处理这些呼入的连接请求? 在伯克利的 TCP实现中采用以下规则: 1) 正等待连接请求的一端有一个固定长度的连接队列,该队列中的连接已被

T C P接受

(即三次握手已经完成),但还没有被应用层所接受。 注意区分 T C P接受一个连接是将其放入这个队列,而应用层接受连接是将其从该队列 中移出。 2) 应用层将指明该队列的最大长度,这个值通常称为积压值 ( b a c k l o g )。它的取值范围是 0~5之间的整数,包括 0和5(大多数的应用程序都将这个值说明为 5)。 3) 当一个连接请求(即SYN)到达时,TCP使 用一个算法,根据当前连接队列中的连接数

积压值

最大排队的连接数 传统的BSD

来确定是否接收这个连接。我们期望应用层 说明的积压值为这一端点所能允许接受连接 的最大数目,但情况不是那么简单。图18-23 显示了积压值与传统的伯克利系统和 Solaris 2.2所能允许的最大接受连接数之间的关系。

图18-23 对正在听的端点所允许接受的

注意,积压值说明的是 T C P监听的端点已

最大连接数

被T C P接受而等待应用层接受的最大连接数。这个积压值对系统所允许的最大连接数, 或者并发服务器所能并发处理的客户数,并无影响。 在这个图中,Solaris系统规定的值正如我们所期望的。而传统的BSD系统,将这个

196

使用TCP/IP详解,卷1:协议

下载

值(由于某些原因)设置为积压值乘3除以2,再加1。 4) 如果对于新的连接请求,该 T C P监听的端点的连接队列中还有空间(基于图 1 8 - 2 3), T C P模块将对 S Y N进行确认并完成连接的建立。但应用层只有在三次握手中的第三个 报文段收到后才会知道这个新连接时。另外,当客户进程的主动打开成功但服务器的 应用层还不知道这个新的连接时,它可能会认为服务器进程已经准备好接收数据了 (如果发生这种情况,服务器的 TCP仅将接收的数据放入缓冲队列 )。 5) 如果对于新的连接请求,连接队列中已没有空间, T C P将不理会收到的 S Y N。也不发 回任何报文段(即不发回 R S T)。如果应用层不能及时接受已被 T C P接受的连接,这些 连接可能占满整个连接队列,客户的主动打开最终将超时。 通过s o c k程序能了解这种情况。我们调用它,并使用新的选项( - O)。让它在创建一个 新的服务器进程后而没有接受任何连接请求之前暂停下来。如果在它暂停期间又调用了多个 客户进程,它将导致接受连接队列被填满,通过 tcpdump能够看到这一切。 bsdi % sock -s -v -q1 -O30 5555

- q 1选项将服务器端的积压值置 1。在这种情况下,传统的 B S D系统中的队列允许接受两 个连接请求(图 1 8 - 2 3)。- O 3 0选项使程序在接受任何客户连接之前暂停 3 0秒。在这 3 0秒内, 我们可启动其他客户进程来填充这个队列。在主机 sun上启动4个客户进程。 图1 8 - 2 4显示了 t c p d u m p的输出,首先是第 1个客户进程的第 1个S Y N(省略窗口大小和 MSS声明。当TCP连接建立时,将客户进程的端口号用粗体标出)。 端口为1 0 9 0的第一个客户连接请求被 T C P接受(报文段 1 ~ 3)。端口为 1 0 9 1的第2个客户连 接请求也被 T C P接受(报文段 4 ~ 6)。而服务器的应用仍处于休眠状态,还未接受任何连接。 目前的一切工作都由内核中的 T C P模块完成。另外,两个客户进程已经成功地完成了它们的 主动打开,因为它们建立连接的三次握手已经完成。

图18-24 积压值例子的tcpdump 输出

197

第18章 TCP连接的建立与终止使用

下载

我们接着在报文段 7(端口 1 0 9 2)和报文段 8(端口 1 0 9 3)启动第 3和第4个客户进程。由 于服务器的连接队列已满, TCP将不理会两个SYN。这两个客户进程在报文段 9, 10, 11, 12, 15 重发它们的 S Y N。第4个客户进程的第 3个S Y N重传被接受了,因为服务器程序的 3 0秒休眠结 束后,它将已接受的两个连接从队列中移出,使连接队列变空(服务器程序接收连接的时间 是2 8 . 1 9,小于 3 0的原因在于启动服务器程序后它需要几秒的时间来启动第 1个客户进程 (报文 段1,显示的就是启动时间))。第3个客户进程的第 4个SYN重传这时将被接受(报文段 15~17)。 服务器程序先接受第 4个客户连接(端口 1093)的原因是服务器程序 30秒休眠与客户程序重传 之间的定时交互作用。 我们期望接收连接队列按先进先出顺序传递给应用层。如 TCP接受了端口为1090 和1091的连接,我们希望应用层先接受端口为 1090的连接,然后再接受端口为 1091的 连接。但许多伯克利的 TCP实现都出现按后进先出的传递顺序,这个错误已存在了多 年。产商最近已开始改正这个错误,但在如SunOS 4.13等系统中仍存在这个问题。 当队列已满时,TCP将不理会传入的 SYN,也不发回RST作为应答,因为这是一个软错误, 而不是一个硬错误。通常队列已满是由于应用程序或操作系统忙造成的,这样可防止应用程 序对传入的连接进行服务。这个条件在一个很短的时间内可以改变。但如果服务器的 T C P以 系统复位作为响应,客户进程的主动打开将被废弃(如果服务器程序没有启动我们就会遇到)。 由于不应答 S Y N,服务器程序迫使客户 T C P随后重传 S Y N,以等待连接队列有空间接受新的 连接。 这个例子中有一个巧妙之处,这在大多 T C P / I P的具体实现中都能见到,就是如果服务器 的连接队列未满时, T C P将接受传入的连接请求(即 S Y N),但并不让应用层了解该连接源于 何处(即不告知源 I P地址和源端口)。这不是 T C P所要求的,而只是共同的实现技术(如伯克 利源代码通常都这么做)。如果一个API如TLI(见1.15节)向应用程序提供了解连接请求的到 来的方法,并允许应用程序选择是否接受连接。当应用程序假定被告知连接请求已经到来时, T C P的三次握手已经结束!其他运输层的实现可能将连接请求的到达与接受分开(如 O S I的运 输层),但TCP不是这样。 Solaris 2.2 提供了一个选项使 T C P只有在应用程序说可以接受( t c p _ e a g e r _ listeners见E.4) ,才允许接受传入的连接请求。 这种行为也意味着 T C P服务器无法使客户进程的主动打开失效。当一个新的客户连接传 递给服务器的应用程序时, T C P的三次握手就结束了,客户的主动打开已经完全成功。如果 服务器的应用程序此时看到客户的 I P地址和端口号,并决定是否为该客户进行服务,服务器 所能做的就是关闭连接(发送 F I N),或者复位连接(发送 R S T)。无论哪种情况,客户进程都 认为一切正常,因为它的主动打开已经完成,并且已经向服务器程序发送过请求。

18.12 小结 两个进程在使用 T C P交换数据之前,它们之间必须建立一条连接。完成后,要关闭这 个连接。本章已经详细介绍了如何使用三次握手来建立连接以及使用

4个报文段来关闭连

接。 我们用 t c p d u m p程序显示了 T C P首部中的各个字段。也了解了连接建立是如何超时,连

198

使用TCP/IP详解,卷1:协议

下载

接复位是如何发送,使用半打开连接发生的情况以及 T C P是如何提供半关闭、同时打开和同 时关闭。 弄清TCP操作的关键在于它的状态变迁图。我们跟踪了连接建立与关闭的步骤以及它们的 状态变迁过程。还讨论了在设计TCP并发服务器时TCP连接建立的具体实现方法。 一个T C P连接由一个 4元组唯一确定:本地 I P地址、本地端口号、远端 I P地址和远端端口 号。无论何时关闭一个连接,一端必须保持这个连接,我们看到 TIME_WAIT状态将处理这个 问题。处理的原则是执行主动打开的一端在进入这个状态时要保持的时间为 T C P实现中规定 的MSL值的两倍。

习题 18.1 在1 8 . 2节我们说初始序号( I S N)正常情况下由 1开始,并且每 0 . 5秒增加6 4 0 0 0,每次执 行一个主动打开。这意味着 I S N的最低三位通常总是 0 0 1。但在图 1 8 - 3中,两个方向上 ISN中的最低三位都是 521。究竟是怎么回事 ? 18.2 在图1 8 - 1 5中,我们键入 1 2个字符,看到 T C P发送了 1 3个字节。在图 1 8 - 1 6中我们键入 8 个字符,但 T C P发送了 1 0个字符。为什么在第 1种情况下增加 1个字节,而在第 2种情况 下增加2个字节? 18.3 半打开连接和半关闭连接的区别是什么? 18.4 如果启动 s o c k程序作为一个服务器程序,然后终止它(还没有客户进程与它相连接), 我们能立即重新启动这个服务器程序。这意味着它没有经历 2 M S L等待状态。用状态变 迁来解释这一切。 18.5 在1 8 . 6节我们知道一个客户进程不能重新使用同一个本地端口,如果该端口是仍处于 2 M S L等待连接的一部分。但如果 s o c k程序作为客户程序连续运行两次,并且连接到 d a y t i m e服务器上,我们就能重新使用同一本地端口。另外,对一个仍处于 2 M S L等待的 连接,也能为它创建一个替身。这将如何做?

重用相同的本地端口号

18.6 在1 8 . 6节的最后,我们介绍了 F I N _ WA I T _ 2状态,提到如果应用程序仅过 11分钟后实行 完全关闭(不是半关闭),许多具体的实现都将一个连接由这个状态转移到 C L O S E D状 态。如果另一端(处于 CLOSE_WAIT状态)在宣布关闭(即发送 F I N)之前等待了 1 2分 钟,这一端的TCP将如何响应这个 FIN? 18.7 对于一个电话交谈,哪一方是主动打开,哪一方是被动打开?是否允许同时打开?是否 允许同时关闭? 18.8 在图18-6中,我们没有见到一个 ARP请求或一个 ARP应答。显然主机 s v r 4的硬件地址一 定在bsdi的ARP高速缓存中。如果这个ARP高速缓存不存在,这个图会有什么变化? 18.9 解释如下的 tcpdump输出,并和图18-13进行比较。

下载

199

第18章 TCP连接的建立与终止使用

18.10 为什么图 1 8 - 4中的服务器不将对客户 F I N的A C K与自己的 F I N合并,从而将报文段数减 少为3个? 18.11 在图18-16中,RST的序号为什么是 26368002? 18.12 TCP向链路层查询MTU是否违反分层的规则? 18.13 假定在图 1 4 . 1 6中,每个 D N S使用T C P而不是U D P进行查询,试问需要交换多少个报文 段? 18.14 假定MSL为120秒,试问系统能够初始化一个新连接然后进行主动关闭的最大速率是多 少? 18.15 阅读RFC 793,分析处于 TIME_WAIT状态的主机收到使其进入此状态的重复的 F I N时 所发生的情况。 18.16 阅读RFC 793,分析处于 TIME_WAIT状态的主机收到一个 RST时所发生的情况。 18.17 阅读Host Requirements RFC并找出半双工TCP关闭的定义。 18.18 在图1 - 8中,我们曾提到到来的 T C P报文段可根据其目的端口号进行分用,请问这种说 法是否正确?

下载

第19章 TCP的交互数据流 19.1 引言 前一章我们介绍了 T C P连接的建立与释放,现在来介绍使用 T C P进行数据传输的有关问 题。 一些有关TCP通信量的研究如 [Caceres et al. 1991]发现,如果按照分组数量计算,约有一 半的 T C P报文段包含成块数据(如 F T P、电子邮件和 U s e n e t新闻),另一半则包含交互数据 (如Te l n e t和R l o g i n)。如果按字节计算,则成块数据与交互数据的比例约为 9 0 %和1 0 %。这是 因为成块数据的报文段基本上都是满长度( f u l l - s i z e d)的(通常为 5 1 2字节的用户数据),而 交互数据则小得多(上述研究表明 Te l n e t和R l o g i n分组中通常约 9 0 %左右的用户数据小于 1 0个 字节)。 很明显,TCP需要同时处理这两类数据,但使用的处理算法则有所不同。本章将以 Rlogin 应用为例来观察交互数据的传输过程。将揭示经受时延的确认是如何工作的以及 N a g l e算法怎 样减少了通过广域网络传输的小分组的数目,这些算法也同样适用于 Telnet应用。下一章我们 将介绍成块数据的传输问题。

19.2 交互式输入 首先来观察在一个 R l o g i n连接上键入一个交互命令时所产生的数据流。许多 T C P / I P的初 学者很吃惊地发现通常每一个交互按键都会产生一个数据分组,也就是说,每次从客户传到 服务器的是一个字节的按键(而不是每次一行)。而且, R l o g i n需要远程系统(服务器)回显 我们(客户)键入的字符。这样就会产

客户

服务器

生4个报文段:(1)来自客户的交互按 键;(2)来自服务器的按键确认;(3)

按键

来自服务器的按键回显;( 4)来自客

服务器

户的按键回显确认。图 1 9 - 1表示了这个 数据流。 然而,我们一般可以将报文段 2和

回显 显示

3进行合并 — 按键确认与按键回显一 起发送。下一节将描述这种合并的技术 (称为经受时延的确认)。 本章我们特意使用 R l o g i n 作为例

图19-1 一种可能的处理远程交互按键回显的方法

子,因为它每次总是从客户发送一个字节到服务器。在第 26章讲到Telnet的时候,将会发现它 有一个选项允许客户发送一行到服务器,通过使用这个选项可以减少网络的负载。 图1 9 - 2显示的是当我们键入 5个字符 d a t e \ n时的数据流(我们没有显示连接建立的过程, 并且去掉了所有的服务类型输出。 BSD/386通过设置一个 Rlogin连接的TOS来获得最小时延)。

201

第19章 TCP的交互数据流使用

下载

第1行客户发送字符 d到服务器。第 2行是该字符的确认及回显(也就是图 1 9 - 1的中间两部分数 据的合并)。第3行是回显字符的确认。与字符 a有关的是第 4~6行,与字符t有关的是第 7~9行, 第1 0 ~ 1 2行与字符 e有关。第 3 ~ 4、6 ~ 7、9 ~ 1 0和1 2 ~ 1 3行之间半秒左右的时间差是键入两个字 符之间的时延。 注意到13~15行稍有不同。从客户发送到服务器的是一个字符(按下 RETURN键后产生的 U N I X 系 统 中 的 换 行 符 ), 而 回 显 的 则 是 两 个 字 符 。 这 两 个 字 符 分 别 是 回 车 和 换 行 字 符 (CR/LF),它们的作用是将光标回移到左边并移动到下一行。 第16行是来自服务器的 date命令的输出。这 30个字节由28个字符与最后的 CR/LF组成。紧 接着从服务器发往客户的 7个字符(第 18行)是在服务器主机上的客户提示符: svr4 % 。第19 行确认了这 7个字符。

图19-2 当在Rlogin连接上键入date时的数据流

注意T C P是怎样进行确认的。第 1行以序号 0发送数据字节,第 2行通过将确认序号设为 1, 也就是最后成功收到的字节的序号加 1,来对其进行确认(也就是所谓的下一个期望数据的序 号)。在第 2行中服务器还向客户发送了一序号为 1的数据,客户在第 3行中通过设置确认序号 为2来对该数据进行确认。

19.3 经受时延的确认 在图19-2中有一些与本节将要论及的时间有关的细微之处。图 19-3表示了图 19-2中数据交 换的时间系列(在该时间系列中,去掉了所有的窗口通告,并增加了一个记号来表明正在传 输何种数据)。 把从b s d i发送到s r v 4的7个A C K标记为经受时延的 A C K。通常T C P在接收到数据时并不 立即发送 A C K;相反,它推迟发送,以便将 A C K与需要沿该方向发送的数据一起发送(有时 称这种现象为数据捎带 ACK)。绝大多数实现采用的时延为 200 ms,也就是说, TCP将以最大 200 ms 的时延等待是否有数据一起发送。 如果观察bsdi接收到数据和发送 ACK之间的时间差,就会发现它们似乎是随机的: 123.5、

202

使用TCP/IP详解,卷1:协议

下载

65.6、109.0、132.2、42.0、140.3和195.8 ms。相反,观察到发送 ACK的实际时间(从 0开始) 为:1 3 9 . 9、5 3 9 . 3、9 4 0 . 1、1 3 3 9 . 9、1 7 3 9 . 9、1 9 4 0 . 1和2140.1 ms (在图 1 9 - 3中用星号标出)。 这些时间之间的差则是 200 ms的整数倍,这里所发生的情况是因为 T C P使用了一个 200 ms的 定时器,该定时器以相对于内核引导的 200 ms 固定时间溢出。由于将要确认的数据是随机到 达的(在时刻 16.4, 474.3, 831.1 等),T C P在内核的 200 ms定时器的下一次溢出时得到通知。 这有可能是将来 1~200 ms中的任何一刻。

时延的确认

时延的确认

时延的确认

时延的确认 (新行)

时延的确认

时延的确认

时延的确认

图19-3

在rlogin连接上键入date命令时的数据流时间系列

如果观察 s v r 4为产生所收到的每个字符的回显所使用的时间,则这些时间分别为 1 6 . 5、 16.3、16.5、16.4和17.3 ms。由于这个时间小于 200 ms,因此我们在另一端从来没有观察到一 个经受时延的 ACK。在经受时延的定时器溢出前总是有数据需要发送(如果有一个约为 16 ms 等待时间越过了内核的 200 ms 时钟滴答的边界,则仍可以看到一个经受时延的 A C K。在本例 中我们一个也没有看到)。 在图18-7中,当为检测超时而使用 500 ms的TCP定时器时,我们会看到同样的情况。这两

203

第19章 TCP的交互数据流使用

下载

个200 ms和500 ms 的定时器都在相对于内核引导的时间处溢出。不论 T C P何时设置一个定时 器,该定时器都可能在将来 1~200 ms和1~500 ms的任一处溢出。 Host Requirements RFC声明TCP需要实现一个经受时延的 ACK,但时延必须小于 500 ms。

19.4 Nagle算法 在前一节我们看到 , 在一个R l o g i n连接上客户一般每次发送一个字节到服务器,这就产生 了一些41字节长的分组:20字节的IP首部、20字节的TCP首部和1个字节的数据。在局域网上, 这些小分组(被称为微小分组( t i n y g r a m))通常不会引起麻烦,因为局域网一般不会出现拥 塞。但在广域网上,这些小分组则会增加拥塞出现的可能。一种简单和好的方法就是采用 RFC 896 [Nagle 1984]中所建议的 Nagle算法。 该算法要求一个 T C P连接上最多只能有一个未被确认的未完成的小分组,在该分组的确 认到达之前不能发送其他的小分组。相反, T C P收集这些少量的分组,并在确认到来时以一 个分组的方式发出去。该算法的优越之处在于它是自适应的:确认到达得越快,数据也就发 送得越快。而在希望减少微小分组数目的低速广域网上,则会发送更少的分组(我们将在 22.3节看到“小”的含义是小于报文段的大小)。 在图1 9 - 3中可以看到,在以太网上一个字节被发送、确认和回显的平均往返时间约为 1 6 m s。为了产生比这个速度更快的数据,我们每秒键入的字符必须多于 6 0个。这表明在局域网 环境下两个主机之间发送数据时很少使用这个算法。 但是,当往返时间( RT T)增加时,如通过一个广域网,情况就会发生变化。看一下在 主机s l i p和主机v a n g o g h . c s . b e r k e l e y . e d u之间的R l o g i n连接工作的情况。为了从我们 的网络中出去(参看原书封面内侧),需要使用两个 S L I P链路和 I n t e r n e t。我们希望获得更长 的往返时间。图 1 9 - 4显示了当在客户端快速键入字符(像一个快速打字员一样)时一些数据 流的时间系列(去掉了服务类型信息,但保留了窗口通告)。 比较图19-4与图19-3,我们首先注意到从 s l i p到v a n g o g h不存在经受时延的 ACK。这是 因为在时延定时器溢出之前总是有数据等待发送。 其次,注意到从左到右待发数据的长度是不同的,分别为: 1、1、2、1、2、2、3、1和3 个字节。这是因为客户只有收到前一个数据的确认后才发送已经收集的数据。通过使用 N a g l e 算法,为发送16个字节的数据客户只需要使用 9个报文段,而不再是 16个。 报文段1 4和1 5看起来似乎是与 N a g l e算法相违背的,但我们需要通过检查序号来观察其中 的真相。因为确认序号是 5 4,因此报文段 1 4是报文段 1 2中确认的应答。但客户在发送该报文 段之前,接收到了来自服务器的报文段 13,报文段15中包含了对序号为 56的报文段 13的确认。 因此即使我们看到从客户到服务器有两个连续返回的报文段,客户也是遵守了 Nagle算法的。 在图1 9 - 4中可以看到存在一个经受时延的 A C K,但该 A C K是从服务器到客户的(报文段 12),因为它不包含任何数据,因此我们可以假定这是经受时延的 ACK。服务器当时一定非常 忙,因此无法在服务器的定时器溢出前及时处理所收到的字符。 最后看一下最后两个报文段中数据的数量以及相应的序号。客户发送 3个字节的数据( 18, 1 9和2 0),然后服务器确认这 3个字节(最后的报文段中的 ACK 21),但是只返回了一个字节 (标号为59)。这是因为当服务器的TCP一旦正确收到这3个字节的数据,就会返回对该数据的确

204

使用TCP/IP详解,卷1:协议

下载

认,但只有当 Rlogin服务器发送回显数据时,它才能够发送这些数据的回显。这表明 TCP可以 在应用读取并处理数据前发送所接收数据的确认。 TCP确认仅仅表明TCP已经正确接收了数据。 最后一个报文段的窗口大小为8189而非8192,表明服务器进程尚未读取这三个收到的数据。

图19-4 在slip 和vangogh.cs.berkeley.edu

之间使用rlogin 时的数据流

19.4.1 关闭Nagle算法 有时我们也需要关闭 N a g l e算法。一个典型的例子是 X窗口系统服务器(见 3 0 . 5节):小消 息(鼠标移动)必须无时延地发送,以便为进行某种操作的交互用户提供实时的反馈。 这里将举另外一个更容易说明的例子—在一个交互注册过程中键入终端的一个特殊功 能键。这个功能键通常可以产生多个字符序列,经常从 A S C I I码的转义 ( e s c a p e )字符开始。如 果T C P每次得到一个字符,它很可能会发送序列中的第一个字符( A S C I I码的E S C),然后缓 存其他字符并等待对该字符的确认。但当服务器接收到该字符后,它并不发送确认,而是继 续等待接收序列中的其他字符。这就会经常触发服务器的经受时延的确认算法,表示剩下的 字符没有在 200 ms内发送。对交互用户而言,这将产生明显的时延。 插口API用户可以使用TCP_NODELAY选项来关闭Nagle算法。 Host Requirements RFC声明TCP必须实现Nagle算法,但必须为应用提供一种方法 来关闭该算法在某个连接上执行。

205

第19章 TCP的交互数据流使用

下载 19.4.2 一个例子

可以在 N a g l e算法和产生多个字符的按键之间看到这种交互的情况。在主机 s l i p和主机 v a n g o g h . c s . b e r k e l e y . e d u之间建立一个 Rlogin连接,然后按下 F1功能键,这将产生 3个 字节:一个 e s c a p e、一个左括号和一个 M。然后再按下 F 2功能键,这将产生另外 3个字节。图 19-5表示的是 tcpdump的输出结果(我们去掉了其中的服务类型和窗口通告)。 按F1键

按F2键

图19-5

当键入能够产生多个字节数据的字符时Nagle算法的观察情况

图1 9 - 6表示了这个交互过程的时间系列。在该图的下面部分我们给出了从客户发送到服 务器的6个字节和它们的序号以及将要返回的 8个字节的回显。

按F1

按F2

F2键

F1键 F1的回显

图19-6 图19-5的时间系列(Nagle算法的观察结果)

F2的回显

206

使用TCP/IP详解,卷1:协议

下载

当r l o g i n客户读取到输入的第 1个字节并向T C P写入时,该字节作为报文段 1被发送。这 是F 1键所产生的 3个字节中的第 1个。它的回显在报文段 2中被返回,此时剩余的 2个字节才被 发送(报文段3)。这两个字节的回显在报文段 4被接收,而报文段 5则是对它们的确认。 第1个字节的回显为 2个字节(报文段 2)的原因是因为在 ASCII码中转义符的回显是 2个字 节:插入记号和一个左括号。剩下的两个输入字节:一个左括号和一个 M,分别以自身作为 回显内容。 当按下下一个特殊功能键(报文段 6 ~ 1 0)时,也会发生同样的过程。正如我们希望的那 样,在报文段 5和1 0(s l i p发送回显的确认)之间的时间差是 200 ms 的整数倍,因为这两个 ACK被进行时延。 现在我们使用一个修改后关闭了 N a g l e算法的 r l o g i n版本重复同样的实验。图 1 9 - 7显示 了tcpdump的输出结果(同样去掉了其中的服务类型和窗口通告)。 按F1键

按F2键

图19-7 在一个Rlogin会话中关闭Nagle算法

在已知某些报文段在网络上形成交叉的情况下,以该结果构造时间系列则更具有启发性 和指导意义。这个例子同样也需要随着数据流对序号进行仔细的检查。在图 1 9 - 8中显示这个 结果。用图 19-7中tcpdump输出的号码对报文段进行了相应的编号。 我们注意到的第 1个变化是当 3个字节准备好时它们全部被发送(报文段 1、2和3)。没有 时延发生—Nagle算法被禁止。 在t c p d u m p输出中的下一个分组(报文段 4)中带有来自服务器的第 5个字节及一个确认 序号为 4的A C K。这是不正确的,因为客户并不希望接收到第 5个字节,因此它立即发送一个 确认序号为 2而不是6的响应(没有被延迟)。看起来一个报文段丢失了,在图 1 9 - 8中我们用虚 线表示。 如何知道这个丢失的报文段中包含第 2、3和4个字节,且其确认序号为 3呢?这是因为正 如在报文段 5中声明的那样,我们希望的下一个字节是第 2个字节(每当 T C P接收到一个超出 期望序号的失序数据时,它总是发送一个确认序号为其期望序号的确认)。也正是因为丢失的 分组中包含第 2、3和4个字节,表明服务器必定已经接收到报文段 2,因此丢失的报文段中的 确认序号一定为 3(服务器期望接收的下一个字节号)。最后,注意到重传的报文段 6中包含有 丢失的报文段中的数据和报文段 4,这被称为重新分组化。我们将在 2 2 . 11节对其进行更多的 介绍。

207

第19章 TCP的交互数据流使用

下载

现在回到禁止 N a g l e算法的讨论中来。可以观察到键入的下一个特殊功能键所产生的 3个 字节分别作为单独的报文段(报文段 8、9和10)被发送。这一次服务器首先回显了报文段 8中 的字节(报文段 11),然后回显了报文段 9和10中的字节(报文段 12)。 在这个例子中,我们能够观察到的是在跨广域网运行一个交互应用的环境下,当进行多 字节的按键输入时,默认使用 Nagle算法会引起额外的时延。 在第21章我们将进行有关时延和重传方面的讨论。

按F1

超时并重传

按F2

F2键

F1键 F1的回显

F2的回显

图19-8 图19-7的时间系列(关闭Nagle算法)

19.5 窗口大小通告 在图19-4中,我们可以观察到 s l i p通告窗口大小为 4096字节,而 v a n g o g h通告其窗口大 小为8192个字节。该图中的大多数报文段都包含这两个值中的一个。

208

使用TCP/IP详解,卷1:协议

下载

然而,报文段 5通告的窗口大小为 4 0 9 5个字节,这意味着在 T C P的缓冲区中仍然有一个字 节等待应用程序( R l o g i n客户)读取。同样,来自客户的下一个报文段声明其窗口大小为 4094个字节,这说明仍有两个字节等待读取。 服务器通常通告窗口大小为 8 1 9 2个字节,这是因为服务器在读取并回显接收到的数据之 前,其 T C P没有数据发送。当服务器已经读取了来自客户的输入后,来自服务器的数据将被 发送。 然而,在 A C K到来时,客户的 T C P总是有数据需要发送。这是因为它在等待 A C K的过程 中缓存接收到的字符。当客户 TCP发送缓存的数据时, Rlogin客户没有机会读取来自服务器的 数据,因此,客户通告的窗口大小总是小于 4096。

19.6 小结 交互数据总是以小于最大报文段长度的分组发送。在 Rlogin中通常只有一个字节从客户发 送到服务器。Telnet允许一次发送一行输入数据,但是目前大多数实现仍然发送一个字节。 对于这些小的报文段,接收方使用经受时延的确认方法来判断确认是否可被推迟发送, 以便与回送数据一起发送。这样通常会减少报文段的数目,尤其是对于需要回显用户输入字 符的Rlogin会话。 在较慢的广域网环境中,通常使用 N a g l e算法来减少这些小报文段的数目。这个算法限制 发送者任何时候只能有一个发送的小报文段未被确认。但我们给出的一个例子也表明有时需 要禁止Nagle算法的功能。

习题 19.1 考虑一个 T C P客户应用程序,它发送一个小应用程序首部( 8个字节)和一个小请求 (12个字节),然后等待来自服务器的一个应答。比较以下两种方式发送请求时的处理情 况:先发送 8个字节再发送12个字节和一次发送 20个字节。 19.2 图19-4中我们在路由器 s u n上运行t c p d u m p。这意味着从右至左的箭头中的数据也需要 经过b s d i,同时从左至右的箭头中的数据已经流经 b s d i。当观察一个送往 s l i p的报文 段及下一个来自 s l i p 的报文段时,我们发现它们之间的时间差分别为: 3 4 . 8、2 6 . 7、 30.1、28.1、29.9和35.3 ms。现给定在 s u n和s l i p之间存在两条链路(一个以太链路和 一个9600 b/s的CSLIP链路),试问这些时间差的含义(提示:重新阅读 2.10节)。 19.3 比较在使用 N a g l e算法(图 1 9 - 6)和禁止 N a g l e算法(图 1 9 - 8)的情况下发送一个特殊功 能键并等待其应答所需要的时间。

下载

第20章 TCP的成块数据流 20.1 引言 在第1 5章我们看到 T F T P使用了停止等待协议。数据发送方在发送下一个数据块之前需要 等待接收对已发送数据的确认。本章我们将介绍 T C P所使用的被称为滑动窗口协议的另一种 形式的流量控制方法。该协议允许发送方在停止并等待确认前可以连续发送多个分组。由于 发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输。 我们还将介绍 T C P的P U S H标志,该标志在前面的许多例子中都出现过。此外,我们还要 介绍慢启动,TCP使用该技术在一个连接上建立数据流,最后介绍成块数据流的吞吐量。

20.2 正常数据流 我们以从主机 s v r 4单向传输 8 1 9 2个字节到主机 b s d i开始。在 b s d i上运行 s o c k程序作 为服务器: bsdi % sock -i -s 7777

其中,标志 - i和- s指示程序作为一个“吸收( sink)”服务器运行(从网络上读取并丢弃 数据),服务器端口指明为 7777。相应的客户程序运行为: svr4 % s o c k - i - n 8 b s d i 7 7 7 7

该命令指示客户向网络发送 8个1 0 2 4字节的数据。图 2 0 - 1显示了这个过程的时间系列。我 们在输出的前3个报文段中显示了每一端 MSS的值。 发送方首先传送 3个数据报文段( 4~6)。下一个报文段( 7)仅确认了前两个数据报文段, 这可以从其确认序号为 2048而不是3073看出来。 报文段7的ACK的序号之所以是 2048而不是3073是由以下原因造成的:当一个分组到达时, 它首先被设备中断例程进行处理,然后放置到 I P的输入队列中。三个报文段 4、5和6依次到达 并按接收顺序放到 I P的输入队列。 I P将按同样顺序将它们交给 T C P。当T C P处理报文段 4时, 该连接被标记为产生一个经受时延的确认。 TCP处理下一报文段( 5),由于TCP现在有两个未 完成的报文段需要确认,因此产生一个序号为 2 0 4 8的A C K(报文段 7),并清除该连接产生经 受时延的确认标志。 T C P处理下一个报文段( 6),而连接又被标志为产生一个经受时延的确 认。在报文段9到来之前,由于时延定时器溢出,因此产生一个序号为 3073的ACK(报文段8)。 报文段8中的窗口大小为 3 0 7 2,表明在 T C P的接收缓存中还有 1 0 2 4个字节的数据等待被应用程 序读取。 报文段11 ~ 1 6说明了通常使用的“隔一个报文段确认”的策略。报文段 11、1 2和1 3到达并 被放入 I P的接收队列。当报文段 11被处理时,连接被标记为产生一个经受时延的确认。当报 文段1 2被处理时,它们的 A C K(报文段 1 4)被产生且连接的经受时延的确认标志被清除。报 文段13使得连接再次被标记为产生经受时延。但在时延定时器溢出之前,报文段 15处理完毕, 因此该确认立刻被发送。

210

使用TCP/IP详解,卷1:协议

下载

图20-1 从svr4 传输8192个字节到bsdi

注意到报文段 7、1 4和1 6中的A C K确认了两个收到的报文段是很重要的。使用 T C P的滑动 窗口协议时,接收方不必确认每一个收到的分组。在 T C P中,A C K是累积的—它们表示接 收方已经正确收到了一直到确认序号减 1的所有字节。在本例中,三个确认的数据为 2048字节 而两个确认的数据为 1024字节(忽略了连接建立和终止中的确认)。 用t c p d u m p看到的是 T C P的动态活动情况。我们在线路上看到的分组顺序依赖于许多无 法控制的因素:发送方 TCP的实现、接收方 TCP的实现、接收进程读取数据(依赖于操作系统 的调度)和网络的动态性(如以太网的冲突和退避等)。对这两个TCP而言,没有一种单一的、 正确的方法来交换给定数量的数据。 为显示情况可能怎样变化,图 2 0 - 2显示了在同样两个主机之间交换同样数据时的另一个 时间系列,它们是在图 20-1所示的几分钟之后截获的。 一些情况发生了变化。这一次接收方没有发送一个序号为 3 0 7 3的A C K,而是等待并发送 序号为 4 0 9 7的A C K。接收方仅发送了 4个A C K(报文段 7、1 0、1 2和1 5):三个确认了 2 0 4 8字

211

第20章 TCP的成块数据流使用

下载

节,另一个确认了 1 0 2 4字节。最后 1 0 2 4字节数据的 A C K出现在报文段 1 7中,它与 F I N的A C K 一道发送(比较该图中的报文段 17与图20-1中的报文段 16和18)。

图20-2 从svr4 到bsdi 的另外8192字节数据的传输过程

快的发送方和慢的接收方 图2 0 - 3显示了另外一个时间系列。这次是从一个快的发送方(一个 S p a r c工作站)到一个 慢的接收方(配有慢速以太网卡的 80386机器)。它的动态活动情况又有所不同。 发送方发送 4个背靠背( b a c k - t o - b a c k)的数据报文段去填充接收方的窗口,然后停下来 等待一个 A C K。接收方发送 A C K(报文段 8),但通告其窗口大小为 0,这说明接收方已收到 所有数据,但这些数据都在接收方的 T C P缓冲区,因为应用程序还没有机会读取这些数据。 另一个ACK(称为窗口更新)在 17.4 ms后发送,表明接收方现在可以接收另外的 4096个字节 的数据。虽然这看起来像一个 A C K,但由于它并不确认任何新数据,只是用来增加窗口的右 边沿,因此被称为窗口更新。 发送方发送最后 4个报文段(10~13),再次填充了接收方的窗口。注意到报文段 13中包括 两个比特标志: P U S H和F I N。随后从接收方传来另外两个 A C K,它们确认了最后的 4 0 9 6字节 的数据(从 4097到8192字节)和FIN(标号为8192)。

212

使用TCP/IP详解,卷1:协议

下载

图20-3 从一个快发送方发送8192字节的数据到一个慢接收方

20.3 滑动窗口 图20-4用可视化的方法显示了我们在前一节观察到的滑动窗口协议。 提供的窗口 由接收方通告 可用的窗口

不能够发送,

发送并被确认 发送,但未被确认

直至窗口移动 能够发送ASAP

图20-4 TCP滑动窗口的可视化表示

在这个图中,我们将字节从 1至11进行标号。接收方通告的窗口称为提出的窗口( o ff e r e d w i n d o w),它覆盖了从第 4字节到第 9字节的区域,表明接收方已经确认了包括第 3字节在内的 数据,且通告窗口大小为 6。回顾第 1 7章,我们知道窗口大小是与确认序号相对应的。发送方 计算它的可用窗口,该窗口表明多少数据可以立即被发送。 当接收方确认数据后,这个滑动窗口不时地向右移动。窗口两个边沿的相对运动增加或 减少了窗口的大小。我们使用三个术语来描述窗口左右边沿的运动:

213

第20章 TCP的成块数据流使用

下载

1) 称窗口左边沿向右边沿靠近为窗口合拢。这种现象发生在数据被发送和确认时。 2) 当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开。这种现象发 生在另一端的接收进程读取已经确认的数据并释放了 TCP的接收缓存时。 3) 当右边沿向左移动时,我们称之为窗口收缩。 Host Requirements RFC强烈建议不要使 用这种方式。但 TCP必须能够在某一端产生这种情况时进行处理。第 22.3节给出了这样的一个 例子,一端希望向左移动右边沿来收缩窗口,但没能够这样做。 图2 0 - 5表示了这三种情况。因为窗口的左边

合拢

收缩

沿受另一端发送的确认序号的控制,因此不可能



向左边移动。如果接收到一个指示窗口左边沿向 左移动的 A C K ,则它被认为是一个重复 A C K ,

张开



图20-5 窗口边沿的移动

并被丢弃。 如果左边沿到达右边沿,则称其为一个零窗口,此时发送方不能够发送任何数据。 一个例子 图20-6显示了在图 20-1所示的数据传输过程中滑动窗口协议的动态性。

由第2个报文段通告的窗口 在第4,5,6报文段中发送的数据 被第7报文段确认 由第7个报文段通告的窗口 被第8报 文段确认

由第8个报文段通告的窗口 在第9报文段 中发送的数据 被第10报 文段确认

由第10个报文段通告的窗口 在第11,12,13报文段 中发送的数据 被第14报 由第14个报文段 通告的窗口 文段确认 在第15报文段 中发送的数据 被第16报 文段确认

图20-6 图20-1的滑动窗口协议

以该图为例可以总结如下几点: 1) 发送方不必发送一个全窗口大小的数据。 2) 来自接收方的一个报文段确认数据并把窗口向右边滑动。这是因为窗口的大小是相对 于确认序号的。

214

使用TCP/IP详解,卷1:协议

下载

3) 正如从报文段 7到报文段 8中变化的那样,窗口的大小可以减小,但是窗口的右边沿却 不能够向左移动。 4) 接收方在发送一个 A C K前不必等待窗口被填满。在前面我们看到许多实现每收到两个 报文段就会发送一个 ACK。 下面我们可以看到更多的滑动窗口协议动态变化的例子。

20.4 窗口大小 由接收方提供的窗口的大小通常可以由接收进程控制,这将影响 TCP的性能。 4.2BSD默认设置发送和接受缓冲区的大小为 2048个字节。在4.3BSD中双方被增加 为4 0 9 6个字节。正如我们在本书中迄今为止所看到的例子一样,

SunOS 4.1.3 、

BSD/386和SVR4仍然使用4096字节的默认大小。其他的系统,如Solaris 2.2、4.4BSD和 AIX3.2则使用更大的默认缓存大小,如8192或16384等。 插口A P I允许进程设置发送和接收缓存的大小。接收缓存的大小是该连接上所能 够通告的最大窗口大小。有一些应用程序通过修改插口缓存大小来增加性能。 [Mogul 1993]显示了在改变发送和接收缓存大小(在单向数据流的应用中,如文件传输, 只需改变发送方的发送缓存和接收方的接收缓存大小)的情况下,位于以太网上的两个工作 站之间进行文件传输时的一些结果。它表明对以太网而言,默认的 4 0 9 6字节并不是最理想的 大小,将两个缓存增加到 1 6 3 8 4 个字节可以增加约 4 0 %左右的吞吐量。在 [ P a p a d o p o u l o s和 Parulkar 1993]中也有相似的结果。 在20.7节中,我们将看到在给定通信媒体带宽和两端往返时间的情况下,如何计算最小的 缓存大小。 一个例子 可以使用 sock程序来控制这些缓存的大小。我们以如下方式调用服务器程序: bsdi % s o c k - i - s - R 6 1 4 4 5 5 5 5

该命令设置接收缓存为 6144个字节( - R选项)。接着我们在主机 s u n上启动客户程序并使 之发送8192个字节的数据: sun % sock -i -n1 -w8192 bsdi 5555

图20-7显示了结果。 首先注意到的是在报文段 2中提供的窗口大小为 6 1 4 4字节。由于这是一个较大的窗口,因 此客户立即连续发送了 6个报文段( 4 ~ 9),然后停止。报文段 1 0确认了所有的数据(从第 1到 6144字节),但提供的窗口大小却为 2048,这很可能是接收程序没有机会读取多于 2048字节的 数据。报文段11和12完成了客户的数据传输,且最后一个报文段带有 FIN标志。 报文段 1 3包含与报文段 1 0相同的确认序号,但通告了一个更大的窗口大小。报文段 1 4确 认了最后的 2 0 4 8字节的数据和 F I N,报文段 1 5和1 6仅用于通告一个更大的窗口大小。报文段 17和18完成通常的关闭过程。

215

第20章 TCP的成块数据流使用

下载

图20-7 接收方提供一个6144字节的接收窗口的情况下的数据传输

20.5 PUSH标志 在每一个 T C P例子中,我们都看到了 P U S H标志,但一直没有介绍它的用途。发送方使用 该标志通知接收方将所收到的数据全部提交给接收进程。这里的数据包括与 PUSH一起传送的 数据以及接收方 TCP已经为接收进程收到的其他数据。 在最初的TCP规范中,一般假定编程接口允许发送进程告诉它的 TCP何时设置PUSH标志。 例如,在一个交互程序中,当客户发送一个命令给服务器时,它设置 PUSH标志并停下来等待 服务器的响应(在习题 1 9 . 1中我们假定当发送 1 2字节的请求时客户设置 P U S H标志)。通过允 许客户应用程序通知其 T C P设置P U S H标志,客户进程通知 T C P在向服务器发送一个报文段时 不要因等待额外数据而使已提交数据在缓存中滞留。类似地,当服务器的 T C P接收到一个设 置了PUSH标志的报文段时,它需要立即将这些数据递交给服务器进程而不能等待判断是否还 会有额外的数据到达。 然而,目前大多数的 A P I没有向应用程序提供通知其 T C P设置 P U S H标志的方法。的确, 许多实现程序认为 PUSH标志已经过时,一个好的 TCP实现能够自行决定何时设置这个标志。 如果待发送数据将清空发送缓存,则大多数的源于伯克利的实现能够自动设置 PUSH标志。 这意味着我们能够观察到每个应用程序写的数据均被设置了 PUSH标志,因为数据在写的时候

216

使用TCP/IP详解,卷1:协议

下载

就立即被发送。 代码中的注释表明该算法对那些只有在缓存被填满或收到一个PUSH标志时才向应 用程序提交数据的TCP实现有效。 使用插口 A P I通知T C P设置正在接收数据的 P U S H标志或得到该数据是否被设置 PUSH标志的信息是不可能的。 由于源于伯克利的实现一般从不将接收到的数据推迟交付给应用程序,因此它们忽略所 接收的PUSH标志。 举例 在图20-1中我们观察到所有 8个数据报文段( 4~6、9、11~13和15)的PUSH标志均被置 1, 这是因为客户进行了 8次1024字节数据的写操作,并且每次写操作均清空了发送缓存。 再次观察图 2 0 - 7,我们预计报文段 1 2中的 P U S H标志被置 1,因为它是最后一个报文段。 为什么发送方知道有更多的数据需要发送还设置报文段 7中的P U S H标志呢?这是因为虽然我 们指定写的是8192个字节的数据,但发送方的发送缓存却是 4096个字节。 值得注意的另外一点是在图 2 0 - 7中的第 1 4、1 5和1 6这三个连续的确认报文段。在图 2 0 - 3 中我们也观察到了两个连续的 ACK,但那是因为接收方已经通告其窗口为 0(使发送方停止)。 当窗口张开时,需要发送另一个窗口非 0的A C K来使发送方重新启动。可是,在图 2 0 - 7中,窗 口的大小从来没有达到过 0。然而,当窗口大小增加了 2 0 4 8个字节的时候,另一个 A C K (报文 段1 5和1 6 )被发送以通知对方窗口被更新(在报文段 1 5和1 6中,这两个窗口更新是不需要的, 因为已经收到了对方的 F I N,表明它不会再发送任何数据)。许多 T C P实现在窗口大小增加了 两个最大报文段长度(本例中为 2 0 4 8字节,因为 M S S为1 0 2 4字节)或者最大可能窗口的 5 0 % (本例中为 2 0 4 8字节,因为最大窗口大小为 4 0 9 6字节)时发送这个窗口更新。在第 2 2 . 3节详细 考察糊涂窗口综合症的时候,我们还会看到这种现象。 作为P U S H标志的另一个例子,再次回到图 2 0 - 3。我们之所以看到前 4个报文段(4 ~ 7)的 标志被设置,是因为它们每一个均使 TCP产生了一个报文段并提交给 IP层。但是随后, TCP停 下来等待一个确认来移动 4 0 9 6字节的窗口。在此期间, T C P又得到了应用程序的最后 4 0 9 6个 字节的数据。当窗口张开时(报文段 9),发送方 T C P知道它有 4个可立即发送的报文段,因此 它只设置了最后一个报文段( 13)的PUSH标志。

20.6 慢启动 迄今为止,在本章所有的例子中,发送方一开始便向网络发送多个报文段,直至达到接 收方通告的窗口大小为止。当发送方和接收方处于同一个局域网时,这种方式是可以的。但 是如果在发送方和接收方之间存在多个路由器和速率较慢的链路时,就有可能出现一些问题。 一些中间路由器必须缓存分组,并有可能耗尽存储器的空间。 [Jacobson 1988]证明了这种连 接方式是如何严重降低了 TCP连接的吞吐量的。 现在,TCP需要支持一种被称为“慢启动 (slow start)”的算法。该算法通过观察到新分组 进入网络的速率应该与另一端返回确认的速率相同而进行工作。 慢启动为发送方的 TCP增加了另一个窗口:拥塞窗口 (congestion window),记为cwnd。当

217

第20章 TCP的成块数据流使用

下载

与另一个网络的主机建立 T C P连接时,拥塞窗口被初始化为 1个报文段(即另一端通告的报文 段大小)。每收到一个 A C K,拥塞窗口就增加一个报文段( c w n d以字节为单位,但是慢启动 以报文段大小为单位进行增加)。发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥 塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。 发送方开始时发送一个报文段,然后等待 ACK。当收到该 ACK时,拥塞窗口从 1增加为2, 即可以发送两个报文段。当收到这两个报文段的 ACK时,拥塞窗口就增加为 4。这是一种指数 增加的关系。 在某些点上可能达到了互联网的容量,于是中间路由器开始丢弃分组。这就通知发送方 它的拥塞窗口开得过大。当我们在下一章讨论 T C P的超时和重传机制时,将会看到它们是怎 样对拥塞窗口起作用的。现在,我们来观察一个实际中的慢启动。 一个例子 图20-8表示的是将从主机sun发送到主机vangogh.cs.berkeley.edu的数据。这些数据 将通过一个慢的SLIP链路,该链路是TCP连接上的瓶颈(我们已经在时间系列上去掉了连接建立 的过程) 。

图20-8 慢启动的例子

我们观察到发送方发送一个长度为 512字节的报文段,然后等待 ACK。该ACK在716 ms后 收到。这个时间是一个往返时间的指示。于是拥塞窗口增加了 2个报文段,且又发送了两个报 文段。当收到报文段 5的ACK后,拥塞窗口增加为 3。此时尽管可发送多达 3个报文段,可是在 下一个ACK收到之前,只发送了 2个报文段。 在21.6节中我们将再次讨论慢启动,并介绍怎样采用另一种被称为“拥塞避免”的技术来

218

使用TCP/IP详解,卷1:协议

下载

作为通常的实现。

20.7 成块数据的吞吐量 让我们看一看窗口大小、窗口流量控制以及慢启动对传输成块数据的 T C P连接的吞吐量 的相互作用。 图20-9显示了左边的发送方和右边的接收方之间的一个 TCP连接上的时间系列,共显示了 1 6个时间单元。为简单起见,本图只显示离散的时间单元。每个粗箭头线的上半部分显示的 是从左到右的携带数据的报文段,标记为 1, 2, 3, 等等。在粗线箭头下面表示的是反向传输的 ACK。我们把ACK用细箭头线表示,并标注了被确认的报文段号。 发送方

发送方

发送方

接收方

接收方

接收方 接收方

接收方

接收方

发送方

发送方

图20-9 时间0~15的成块数据吞吐量举例

在时间 0,发送方发送了一个报文段。由于发送方处于慢启动中(其拥塞窗口为 1个报文 段),因此在继续发送以前它必须等待该数据段的确认。 在时间1, 2和3,报文段从左向右移动一个时间单元。在时间 4接收方读取这个报文段并产 生确认。经过时间 5、6和7,A C K移动到左边的发送方。我们有了一个 8个时间单元的往返时 间RTT(Round-Trip Time)。 我们有意把 A C K报文段画得比数据报文段小,这是因为它通常只有一个 I P 首部和一个 T C P首部。这里显示仅仅是一个单向的数据流动,并且假定 A C K的移动速率与数据报文段的 移动速率相等。实际上并不总是这样。

219

第20章 TCP的成块数据流使用

下载

通常发送一个分组的时间取决于两个因素:传播时延(由光的有限速率、传输设 备的等待时间等引起)和一个取决于媒体速率(即媒体每秒可传输的比特数)的发送 时延。对于一个给定的两个接点之间的通路,传播时延一般是固定的,而发送时延则 取决于分组的大小。在速率较慢的情况下发送时延起主要作用(例如,在习题 7.2中我 们甚至没有考虑传播时延),而在千兆比特速率下传播时延则占主要地位(见图24-6)。 当发送方收到ACK后,在时间8和9发送两个报文段(我们标记为2和3)。此时它的拥塞窗口 为2个报文段。这两个报文段向右传送到接收方,在时间12和13接收方产生两个ACK。这两个返 回到发送方的 ACK之间的间隔与报文段之间的间隔一致,被称为 TCP的自计时(self-clocking)行 为。由于接收方只有在数据到达时才产生 ACK,因此发送方接收到的ACK之间的间隔与数据到 达接收方的间隔是一致的(然而在实际中,返回路径上的排队会改变ACK的到达率)。 图2 0 - 1 0表示的是后面 1 6个时间单位。 2个A C K的到达使得拥塞窗口从 2个报文段增加为 4 个,而这 4个报文段在时间 1 6 ~ 1 9时被发送。第 1个A C K在时间2 3到达。4个A C K的到达使得拥 塞窗口从4个报文段增加为 8个,并在时间24~31发送8个报文段。 发送方

发送方

发送方

发送方

发送方

发送方 发送方

发送方 发送方

发送方

发送方

接收方

发送方

接收方

接收方

发送方

接收方

接收方

接收方

接收方

发送方

接收方

接收方

接收方

发送方

接收方

发送方

接收方

接收方

接收方 接收方

发送方

接收方

发送方

接收方

图20-10 时间16~31的成块数据吞吐量举例

220

使用TCP/IP详解,卷1:协议

下载

在时间3 1及其后续时间,发送方和接收方之间的管道 ( p i p e )被填满。此时不论拥塞窗口和 通告窗口是多少,它都不能再容纳更多的数据。每当接收方在某一个时间单位从网络上移去 一个报文段,发送方就再发送一个报文段到网络上。但是不管有多少报文段填充了这个管道, 返回路径上总是具有相同数目的 ACK。这就是连接的理想稳定状态。 20.7.1 带宽时延乘积 现在来回答窗口应该设置为多大的问题。在我们的例子中,作为最大的吞吐量,发送方 在任何时候有 8个已发送的报文段未被确认。接收方的通告窗口必须不小于这个数目,因为通 告窗口限制了发送方能够发送的段的数目。 可以计算通道的容量为: capacity (bit) = bandwidth (b/s) × round-trip time (s) 一般称之为带宽时延乘积。这个值依赖于网络速度和两端的 RT T,可以有很大的变动。例如, 一条穿越美国(RTT约为60 ms)的T1的电话线路(1 544 000 b/s)的带宽时延乘积为11 580字 节。对于20.4节中讨论的缓存大小而言,这个结果是合理的。但是一条穿越美国的 T3电话线路 (45 000 000 b/s)的带宽时延乘积则为337 500字节,这个数值超过了最大所允许的 TCP通告窗 口的大小(65535字节)。在24.4节我们将讨论能够避免当前 TCP限制的新的TCP窗口大小选项。 T1电话线的1 544 000 b/s是原始比特率。由于每193个bit使用1个作为帧同步,因此 实际数据率为1 536 000 b/s。一个T3电话线的原始比特率实际上是44 736 000 b/s,其数 据率可达到44 210 000 b/s。在讨论中我们使用1.544 Mb/s和45 Mb/s。 不论是带宽还是时延均会影响发送方和接收方之间通路的容量。在图 2 0 - 11中我们显示了 一个增加了一倍的 RTT会使通路容量也增加一倍。 在图20-11底下的说明部分,通过使用一个较长的 RTT,这个管道能够容纳 8个报文段而不 是4个。 类似地,图 20-12表示了增加一倍的带宽也可使该管道的容量增加一倍。

倍增的RTT

图20-11 RTT加倍可使管道容量增加一倍

图20-12 带宽加倍可使管道容量增加一倍

在图2 0 - 1 2的下部,假定网络速率已经加倍,使得我们能够只使用上面一半的时间来发送 4个报文段。这样,该管道的容量再次加倍(假定该图的上半部分与下半部分中的报文段具有 同样大小,即具有相同的比特数)。 20.7.2 拥塞 当数据到达一个大的管道(如一个快速局域网)并向一个较小的管道(如一个较慢的广

221

第20章 TCP的成块数据流使用

下载

域网)发送时便会发生拥塞。当多个输入流到达一个路由器,而路由器的输出流小于这些输 入流的总和时也会发生拥塞。 图2 0 - 1 3显示了一个典型的大管道向小管道发送报文的情况。之所以说它典型,是因为大 多数的主机都连接在局域网上,并通过一个路由器与速率相对较低的广域网相连(我们再次 假定图中上半部分的报文段( 9~20)都是相同的,而图中下半部分的 ACK也都是相同的)。 瓶颈 发送方

路由器 R1

路由 器R2

接收方

发送方

路由器 R3

路由器 R4

接收方

图20-13 从较大管道向较小管道发送分组引起的拥塞

在该图中,我们已经标记路由器 R 1为“瓶颈”,因为它是拥塞发生的地方。它从左侧速 率较高的局域网接收数据并向右侧速率较低的广域网发送(通常

R 1与R 3是同样的路由器,

如同R 2与R 4一样。但这并不是必需的,有时也会使用不对称的路径)。当路由器 R 2将所接收 到的分组发送到右侧的局域网时,这些分组之间维持与其左侧广域网上同样的间隔,尽管局 域网具有更高的带宽。类似地,返回的确认之间的间隔也与其在路径中最慢的链路上的间隔 一致。 在图 2 0 - 1 3中已经假定发送方不使用慢启动,它按照局域网的带宽尽可能快地发送编号 为1 ~ 2 0的报文段(假定接收方的通告窗口至少为 2 0个报文段)。正如我们看到的那样, A C K 之间的间隔与在最慢链路上的一致。假定瓶颈路由器具有足够的容纳这

2 0个分组的缓存。

如果这个不能保证,就会引起路由器丢弃分组。在 2 1 . 6节讨论避免拥塞时会看到怎样避免这 种情况。

20.8 紧急方式 T C P提供了“紧急方式 ( u rgent mode) ”,它使一端可以告诉另一端有些具有某种方式的 “紧急数据”已经放置在普通的数据流中。另一端被通知这个紧急数据已被放置在普通数据流 中,由接收方决定如何处理。 可以通过设置 T C P首部(图 1 7 - 2)中的两个字段来发出这种从一端到另一端的紧急数据 已经被放置在数据流中的通知。 U R G比特被置 1,并且一个 1 6 b i t的紧急指针被置为一个正的 偏移量,该偏移量必须与 T C P首部中的序号字段相加,以便得出紧急数据的最后一个字节的 序号。 仍有许多关于紧急指针是指向紧急数据的最后一个字节还是指向紧急数据最后一 个字节的下一个字节的争论。最初的 TCP规范给出了两种解释,但 Host Requirements RFC确定指向最后一个字节是正确的。 然而,问题在于大多数的实现(包括源自伯克利的实现)继续使用错误的解释。

222

使用TCP/IP详解,卷1:协议

下载

所有符合Host Requirements RFC的实现都是可兼容的,但很有可能无法与其他大多数 主机正确通信。 TCP必须通知接收进程,何时已接收到一个紧急数据指针以及何时某个紧急数据指针还不 在此连接上,或者紧急指针是否在数据流中向前移动。接着接收进程可以读取数据流,并必 须能够被告知何时碰到了紧急数据指针。只要从接收方当前读取位置到紧急数据指针之间有 数据存在,就认为应用程序处于“紧急方式”。在紧急指针通过之后,应用程序便转回到正常 方式。 TCP本身对紧急数据知之甚少。没有办法指明紧急数据从数据流的何处开始。 TCP通过连 接传送的唯一信息就是紧急方式已经开始( T C P首部中的 U R G比特)和指向紧急数据最后一 个字节的指针。其他的事情留给应用程序去处理。 不幸的是,许多实现不正确地称 T C P的紧急方式为带外数据 (out-of-band data)。如果一个 应用程序确实需要一个独立的带外信道,第二个 T C P连接是达到这个目的的最简单的方法 (许多运输层确实提供许多人认为的那种真正的带外数据:使用同一个连接的独立的逻辑数据 通道作为正常的数据通道。这是 TCP所没有提供的)。 TCP的紧急方式与带外数据之间的混淆,也是因为主要的编程接口(插口 API)将 TCP的紧急方式映射为称为带外数据的插口。 紧急方式有什么作用呢?两个最常见的例子是 Telnet和Rlogin。当交互用户键入中断键时, 我们在第 2 6章将看到使用紧急方式来完成这个功能的例子。另一个例子是 F T P,当交互用户 放弃一个文件的传输时,我们将在第 27章看到这样的一个例子。 Telnet和Rlogin从服务器到客户使用紧急方式是因为在这个方向上的数据流很可能要被客户 的TCP停止(也即,它通告了一个大小为 0的窗口)。但是如果服务器进程进入了紧急方式,尽 管它不能够发送任何数据,服务器 TCP也会立即发送紧急指针和 URG标志。当客户TCP接收到 这个通知时就会通知客户进程,于是客户可以从服务器读取其输入、打开窗口并使数据流动。 如果在接收方处理第一个紧急指针之前,发送方多次进入紧急方式会发生什么情况呢? 在数据流中的紧急指针会向前移动,而其在接收方的前一个位置将丢失。接收方只有一个紧 急指针,每当对方有新的值到达时它将被覆盖。这意味着如果发送方进入紧急方式时所写的 内容对接收方非常重要,那么这些字节数据必须被发送方用某种方式特别标记。我们将看到 Telnet通过在数据流中加入一个值为 255的字节作为前缀来标记它所有的命令。 一个例子 让我们观察一下即使是在接收方窗口关闭的情况下, T C P是如何发送紧急数据的。在主 机b s d i上启动 s o c k程序,并使之在连接建立后和从网络读取前暂停 1 0秒种(通过使用 - P选 项),这将使另一端填满发送窗口: bsdi % s o c k - i - s - P 1 0 5 5 5 5

接着我们在主机 s u n 上启动客户,使之使用一个 8 1 9 2 字节的发送缓存(使用 - S 选项) 并进行 6个向网络写 1 0 2 4字节数据的操作(使用 - n选项)。还指明 - U 5选项,告知它向网络 写第 5个缓存之前要写 1个字节的数据,并进入紧急数据方式。我们指明详细标志来观察写 的顺序:

223

第20章 TCP的成块数据流使用

下载

我们设置发送缓存为 8 1 9 2个字节,以便让发送应用程序能够立即写所有的数据。图 2 0 - 1 4 显示了t c p d u m p输出的这个交换过程的结果(删去了连接建立的过程)。第1 ~ 5行表示发送方 用4个1 0 2 4字节的报文段去填充接收方的窗口。然后由于接收方的窗口被填满(第 4行的A C K 确认了数据,但并没有移动窗口的右边沿),所以发送方停止发送。 在写了第4个正常数据之后,应用进程写了1个字节并进入紧急方式。第6行是该应用进程写的结 果,紧急指针被设置为4098。尽管发送方不能发送任何数据,但紧急指针和 URG标志一起被发送。 5个这样的 A C K在13 ms 内被发送(第 6 ~ 1 0行)。第1个A C K在应用进程写 1个字节并进入 紧急方式时被发送,后面两个在应用进程写最后两个 1 0 2 4字节的数据时被发送(尽管 T C P不 能发送这 2 0 4 8个字节的数据,可每次当应用程序执行写操作的时候, T C P的输出功能被调用。 当T C P看到正处于紧急方式时,它会发送其他的紧急通知)。第 4个A C K在应用进程关闭其 T C P连接时被发送( T C P的输出功能再次被调用)。发送应用程序在启动几毫秒后终止—在 接收方应用进程已经发出其第一个写操作之前。 T C P将所有的数据进行排队,并在可能时发 送出去(这就是为何指明发送缓存为 8 1 9 2字节的原因,因此只有这样才能够把所有的数据都 放置在缓存中)。第5个A C K很可能是在接收第 4行的A C K时产生的。发送 T C P很可能在这个 A C K到达前便已将其第 4个报文段放入队列以便输出(第 5行)。另一端接收到这个 A C K也会 引起TCP输出例程被调用。

图20-14 tcpdump 对TCP紧急方式的输出结果

224

使用TCP/IP详解,卷1:协议

下载

接着,接收方确认最后的 1 0 2 4字节的数据(第 11行),但同时通告窗口为 0。发送方用一 个包含紧急通知的报文段进行了响应。 在第1 3行,当应用进程被唤醒、并从接收缓存读取一些数据时,接收方通告窗口为 2 0 4 8 字节。于是后面又发送了两个 1 0 2 4字节的报文段(第 1 4和1 5行)。其中,由于紧急指针在第 1 个报文段的范围内,因此这个报文段被设置了紧急通知标志,而第 2个报文段则关闭了该标 志。 当接收方再次打开窗口(第 1 6行)时,发送方传输最后的数据(序号为 6 1 4 5)并发起正 常的连接关闭。 图2 0 - 1 5显示了发送的 6 1 4 5个字节数据的序号。可以看到当进入紧急方式时所发送的字节 的序号是4097,但在图20-14中紧急指针指向 4098,这证明了该实现( SunOS 4.1.3)将紧急指 针设置为紧急数据最后字节的下一个字节。 写

报文段





报文段

报文段



报文段

紧急



报文段



报文段

图20-15 紧急方式例子中,应用进程的写操作和TCP的一些报文段

该图还可以让我们观察 T C P是如何对应用进程写的数据进行重新分组化的。当进入紧急 方式时待输出的 1个字节是与在缓存中的后面 1 0 2 3个字节一同发送的。下一个报文段也包含 1024字节的数据,而最后一个报文段则只包含一个字节。

20.9 小结 正如我们在本章一开始时讲的那样,没有一种单一的方法可以使用 T C P进行成块数据的 交换。这是一个依赖于许多因素的动态处理过程,有些因素我们可以控制(如发送和接收缓 存的大小),而另一些我们则没有办法控制(如网络拥塞、与实现有关的特性等)。在本章, 我们已经考察了许多 TCP的传输过程,介绍了所有我们能够看到的特点和算法。 进行成块数据有效传输的最重要的方法是 TCP的滑动窗口协议。我们考察了 TCP为使发送 方和接收方之间的管道充满来获得最可能快的传输速度而采用的方法。我们用带宽时延乘积 衡量管道的容量,并分析了该乘积与窗口大小之间的关系。在 24.8节介绍TCP性能的时候将再 次涉及这个概念。 我们还介绍了 T C P的P U S H标志,因为在跟踪结果中总是观察到它,但我们无法对它的设 置与否进行控制。本章最后一个主题是 TCP的紧急数据,人们常常错误地称其为“带外数据”。 T C P的紧急方式只是一个从发送方到接收方的通知,该通知告诉接收方紧急数据已被发送, 并提供该数据最后一个字节的序号。应用程序使用的有关紧急数据部分的编程接口常常都不 是最佳的,从而导致更多的混乱。

习题 20.1 在图2 0 - 6中,我们可以看到一个序号为 0的字节和一个序号为 8 1 9 3的字节,试问这两个

下载

225

第20章 TCP的成块数据流使用

字节的含义是什么? 20.2 提前观察图 22-1,并解释主机bsdi设置PUSH标志的含义。 20.3 在一个Usenet记录中,有人抱怨说美国和日本之间的一个128 ms时延、速率为256 000 b/s的链 路吞吐量为120 000 b/s(利用率为47%) ,而当链路通过卫星时其吞吐量则为33 000 b/s(利用 率为13%) 。试问在这两种情况下窗口大小各为多少(假定卫星链路的时延为500 ms)?卫星 链路的窗口大小应该如何调整? 20.4 如果API提供一种方法,使得发送方可以告诉其 TCP打开PUSH标志,而接收方可以查询 一个接收的报文段是否被设置了 PUSH标志,试问该标志能否被用作一个记录标记? 20.5 在图20-3中为什么没有合并报文段 15和16? 20.6 在图2 0 - 1 3中,我们假定对应数据报文段之间的间隔,返回的 A C K之间的间隔被分隔得 很好。如果在链路某处进行缓存并使许多 ACK同时到达发送方,试问会发生什么情况?

下载

第21章 TCP的超时与重传 21.1 引言 T C P提供可靠的运输层。它使用的方法之一就是确认从另一端收到的数据。但数据和确 认都有可能会丢失。 T C P通过在发送时设置一个定时器来解决这种问题。如果当定时器溢出 时还没有收到确认,它就重传该数据。对任何实现而言,关键之处就在于超时和重传的策略, 即怎样决定超时间隔和如何确定重传的频率。 我们已经看到过两个超时和重传的例子:(1)在6.5节的ICMP端口不能到达的例子中,看 到T F T P客户使用 U D P实现了一个简单的超时和重传机制:假定 5秒是一个适当的时间间隔, 并每隔 5秒进行重传;( 2)在向一个不存在的主机发送 A R P的例子中(第 4 . 5节),我们看到 当TCP试图建立连接的时候,在每个重传之间使用一个较长的时延来重传 SYN。 对每个连接,TCP管理4个不同的定时器。 1) 重传定时器使用于当希望收到另一端的确认。在本章我们将详细讨论这个定时器以及 一些相关的问题,如拥塞避免。 2) 坚持(persist)定时器使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口。第 22章将讨论这个问题。 3) 保活(keepalive)定时器可检测到一个空闲连接的另一端何时崩溃或重启。第 23章将描述 这个定时器。 4) 2MSL定时器测量一个连接处于 T I M E _ WA I T状态的时间。我们在 1 8 . 6节对该状态进行 了介绍。 本章以一个简单的 T C P超时和重传的例子开始,然后转向一个更复杂的例子。该例子可 以使我们观察到 TCP时钟管理的所有细节。可以看到 TCP的典型实现是怎样测量 TCP报文段的 往返时间以及 T C P如何使用这些测量结果来为下一个将要传输的报文段建立重传超时时间。 接着我们将研究 TCP的拥塞避免 —当分组丢失时 TCP所采取的动作—并提供一个分组丢失 的实际例子,我们还将介绍较新的快速重传和快速恢复算法,并介绍该算法如何使 T C P检测 分组丢失比等待时钟超时更快。

21.2 超时与重传的简单例子 首先观察 T C P所使用的重传机制,我们将建立一个连接,发送一些分组来证明一切正常, 然后拔掉电缆,发送更多的数据,再观察 TCP的行为。

正常发送本行 在发送本行前断连 9分钟后TCP放弃时输出

227

第21章 TCP的超时与重传使用

下载

图21-1表示的是 tcpdump的输出结果(已经去掉了 bsdi设置的服务类型信息)。

图21-1 TCP超时和重传的简单例子

第1、2和3行表示正常的 T C P连接建立的过程,第 4行是“hello, world”(1 2个字符加上回 车和换行)的传输过程,第 5行是其确认。接着我们从 s v r 4拔掉了以太网电缆,第 6行表示 “and hi”将被发送。第 7 ~ 1 8行是这个报文段的 1 2次重传过程,而第 1 9行则是发送方的 T C P最 终放弃并发送一个复位信号的过程。 现在检查连续重传之间不同的时间差,它们取整后分别为 1、3、6、12、24、48和多个64 秒。在本章的后面,我们将看到当第一次发送后所设置的超时时间实际上为 1 . 5秒(它在首次 发送后的 1 . 0 1 3 6秒而不是精确的 1 . 5秒后,发生的原因我们已在图 1 8 - 7中进行了解释),此后该 时间在每次重传时增加 1倍并直至64秒。 这个倍乘关系被称为“指数退避 (exponential backoff )”。可以将该例子与 6 . 5节中的 T F T P 例子比较,在那里每次重传总是在前一次的 5秒后发生。 首次分组传输(第 6行,24.480秒)与复位信号传输(第 19行,566.488秒)之间的时间差 约为9分钟,该时间在目前的 TCP实现中是不可变的。 对于大多数实现而言,这个总时间是不可调整的。Solaris 2.2允许管理者改变这个时 间(E.4节中的tcp_ip_abort_interval变量) ,且其默认值为2分钟,而不是最常用的 9分钟。

21.3 往返时间测量 TCP超时与重传中最重要的部分就是对一个给定连接的往返时间( RTT)的测量。由于路 由器和网络流量均会变化,因此我们认为这个时间可能经常会发生变化, T C P应该跟踪这些 变化并相应地改变其超时时间。 首先 T C P必须测量在发送一个带有特别序号的字节和接收到包含该字节的确认之间的 RT T。在上一章中,我们曾提到在数据报文段和 A C K之间通常并没有一一对应的关系。在图

228

使用TCP/IP详解,卷1:协议

下载

2 0 . 1中,这意味着发送方可以测量到的一个 RT T,是在发送报文段 4(第1 ~ 1 0 2 4字节)和接收 报文段7(对1~1024字节的ACK)之间的时间,用 M表示所测量到的 RTT。 最初的TCP规范使TCP使用低通过滤器来更新一个被平滑的 RTT估计器(记为O)。 R← R+(1- )M 这里的 是一个推荐值为 0 . 9的平滑因子。每次进行新测量的时候,这个被平滑的 RT T将得到 更新。每个新估计的 90%来自前一个估计,而 10%则取自新的测量。 该算法在给定这个随 RTT的变化而变化的平滑因子的条件下, RFC 793推荐的重传超时时 间RTO(Retransmission TimeOut)的值应该设置为 RTO = R 这里的 是一个推荐值为 2的时延离散因子。 [Jacobson 1988] 详细分析了在 RT T变化范围很大时,使用这个方法无法跟上这种变化, 从而引起不必要的重传。正如 J a c o b s o n记述的那样,当网络已经处于饱和状态时,不必要的 重传会增加网络的负载,对网络而言这就像在火上浇油一样。 除了被平滑的RTT估计器,所需要做的还有跟踪 RTT的方差。在往返时间变化起伏很大时, 基于均值和方差来计算 RTO ,将比作为均值的常数倍数来计算 RTO 能提供更好的响应。在 [Jacobson 1988]中的图5和图6中显示了根据RFC 793计算的某些实际往返时间的 RTO和下面考 虑了往返时间的方差所计算的 RTO的比较结果。 正如J a c o b s o n所描述的,均值偏差是对标准偏差的一种好的逼近,但却更容易进行计算 (计算标准偏差需要一个平方根)。这就引出了下面用于每个 RTT测量M的公式。 Err = M-A A←A + gErr D←D + h( | Err |-D) RTO = A + 4D 这里的A是被平滑的RTT(均值的估计器)而 D则是被平滑的均值偏差。 Err是刚得到的测量结 果与当前的RTT估计器之差。 A和D均被用于计算下一个重传时间( RTO)。增量g起平均作用, 取为1 / 8(0 . 1 2 5)。偏差的增益是 h,取值为 0 . 2 5。当RT T变化时,较大的偏差增益将使 RTO 快 速上升。 [Jacobson 1988] 指明在计算 RTO 时使用 2 D ,但经过后来更深入的研究, [Jacobson1990c]将该值改为4D,也就是在BSD Net/1的实现中使用的那样。 Jacobson指明了一种使用整数运算来计算这些公式的方法,并被许多实现所采用(这也就 是g, h和倍数4均是2的乘方的一个原因,这样一来计算均可只通过移位操作而不需要乘、除运 算来完成)。 将J a c o b s o n与最初的方法比较,我们发现被平滑的均值计算公式是类似的(

是1减去增

益g),而增益可使用不同的值。而且 Jacobson计算RTO的公式依赖于被平滑的 RTT和被平滑的 均值偏差,而最初的方法则使用了被平滑的 RTT的一个倍数。 在看完下一节中的例子时,我们将看到这些估计器是如何被初始化的。 Karn算法 在一个分组重传时会产生这样一个问题:假定一个分组被发送。当超时发生时, RTO 正

下载

229

第21章 TCP的超时与重传使用

如21.2节中显示的那样进行退避,分组以更长的 RTO进行重传,然后收到一个确认。那么这个 ACK是针对第一个分组的还是针对第二个分组呢?这就是所谓的重传多义性问题。 [Karn and Partridge 1987]规定,当一个超时和重传发生时,在重传数据的确认最后到达 之前,不能更新 RT T估计器,因为我们并不知道 A C K对应哪次传输(也许第一次传输被延迟 而并没有被丢弃,也有可能第一次传输的 ACK被延迟)。 并且,由于数据被重传, RTO 已经得到了一个指数退避,我们在下一次传输时使用这 个退避后的 RTO 。对一个没有被重传的报文段而言,除非收到了一个确认,否则不要计算 新的RTO 。

21.4 往返时间RTT的例子 在本章中,我们将使用以下这些例子来检查 T C P的超时和重传、慢启动以及拥塞避免等 方方面面的实现细节。 使用s o c k程序和如下的命令来将 3 2 7 6 8字节的数据从主机 s l i p发送到主机 v a n g o g h . c s . berkeley.edu上的丢弃服务。 slip % s o c k - D - i - n 3 2 v a n g o g h . c s . b e r k e l e y . e d u d i s c a r d

在扉页前图中,可以看到 s l i p 通过两个 S L I P链路与 1 4 0 . 2 5 2 . 1以太网相连,并从这里通过 I n t e r n e t到达目的地。通过使用两个 9600 b/s 的S L I P链路,我们期望能够得到一些可测量的 时延。 该命令执行 32个写1024字节的操作。由于 s l i p和b s d i之间的MTU为296字节,因此这些 操作会产生 1 2 8个报文段,每个报文段包含 2 5 6字节的用户数据。整个传输过程的时间约为 4 5 秒,我们观察到了一个超时和三次重传。 当该传输过程进行时,我们在 s l i p上使用t c p d u m p来截获所有的发送和接收的报文段, 并通过使用 - D 选项来打开插口排错功能(见 A . 6节),这样便可以通过运行一个修改后的 trpt(8)程序来打印出连接控制块中与 RTT、慢启动及拥塞避免等有关的多个变量。 对于给出的跟踪结果,我们不能够完全进行显示,相反,我们将在介绍本章时看到它的 各个部分。图2 1 - 2显示的是前 5秒中的数据和确认的传输过程。与前面 t c p d u m p的输出相比, 我们已对其显示稍微进行了修改。虽然我们仅能够在运行 t c p d u m p的主机上测量分组发送 和接收的时间,但在本图中我们希望显示出分组正在网络中传输(它们确实存在,因为这 个局域网连接与共享式的以太网并不一样)以及接收主机何时可能产生

A C K (在本图中去

掉了所有的窗口大小通告。主机 s l i p总是通告窗口大小为 4 0 9 6,而v a n g o g h则总是通告窗 口大小为 8 1 9 2)。 还需要注意的是在本图中我们已经将报文段按照在主机 s l i p上发送和接收的序号记为 1~13和15。这与在这个主机上所收集的 tcpdump的输出结果有关。 21.4.1 往返时间RTT的测量 在图21-2左边的时间轴上有三个括号,它们表明为进行 RTT计算对哪些报文段进行了计时, 并不是所有的报文段都被计时。 大多数源于伯克利的 TCP实现在任何时候对每个连接仅测量一次 RTT值。在发送一个报文 段时,如果给定连接的定时器已经被使用,则该报文段不被计时。

230

使用TCP/IP详解,卷1:协议

下载

图21-2 分组交换和RTT测量

在每次调用 500 ms 的T C P的定时器例程时,就增加一个计数器来完成计时。这意味着, 如果一个报文段的确认在它发送 550 ms后到达,则该报文段的往返时间 RT T将是1个滴答(即 500 ms)或是2个滴答(即1000 ms)。 对每个连接而言,除了这个滴答计数器,报文段中数据的起始序号也被记录下来。当收 到一个包含这个序号的确认后,该定时器就被关闭。如果 A C K到达时数据没有被重传,则被 平滑的RTT和被平滑的均值偏差将基于这个新测量进行更新。 图2 1 - 2中连接上的定时器在发送报文段 1时启动,并在确认(报文段 2)到达时终止。尽 管它的RTT是1.061秒(t c p d u m p的输出),但插口排错的信息显示该过程经历了 3个TCP时钟 滴答,即RTT为1500 ms。 下一个被计时的是报文段 3。当2.4 ms后传输报文段 4时,由于连接的定时器已经被启动, 因此该报文段不能被计时。当报文段

5 到达时,确认了正在被计时的数据。虽然我们从

t c p d u m p的输出结果可以看到其 RTT是0.808秒,但它的 RTT被计算为1个滴答(500 ms)。 定时器在发送报文段 6时再次被启动,并在 1 . 0 1 5秒后接收到它的确认(报文段 1 0)时终 止。测量到的 RT T是2个滴答。报文段 7和9不能被计时,因为定时器已经被使用。而且,当收

231

第21章 TCP的超时与重传使用

下载

到报文段 8(第7 6 9字节的确认)时,由于该报文段不是正在计时的数据的确认,因此什么也 没有进行更新。 图21-3显示了本例中通过 tcpdump的输出所得到的实际 RTT与时钟滴答计数之间的关系。

1.061秒,3滴答

0.808秒,1滴答

1.015秒,2滴答

图21-3 RTT测量和时钟滴答

在图的上端表示间隔为 500 ms的时钟滴答,图的下端表示 t c p d u m p 的输出时间及定时器 何时被启动和关闭。在发送报文段 1和接收到报文段 2之间经历了 3个滴答,时间为 1 . 0 6 1秒, 因此假定第 1个滴答发生在 0 . 0 3秒处(第 1个滴答一定在 0 ~ 0 . 0 6 1秒之间)。接着该图表示了第 2 个被测量的 RTT为什么被记为1个滴答,而第3个被记为2个滴答。 在这个完整的例子中, 1 2 8个报文段被传送,并收集了 1 8个RT T采样。图 2 1 - 4表示了测量 的RT T(取自 t c p d u m p的输出)和 T C P为超时所使用的 RTO (取自插口排错的输出)。在图 21-2中,x轴从时间0开始,表示的是传输报文段 1的时刻,而不是传输第 1个SYN的时刻。

TCP计算出的RTO

(秒) 测量出的RTT

时间(秒)

图21-4 测量出的RTT和TCP计算的RTO的例子

测量出RT T的前3个数据点对应图 2 1 - 2所示的 3个RT T。在时间 10, 14和2 1处的间隔是由在 这些时刻附近发生的重传(将在本章后面给出)引起的。 K a r n算法在另一个报文段被发送和 确认之前阻止我们更新估计器。同样注意到在这个实现中, T C P计算的 RTO 总是500 ms 的倍 数。 21.4.2 RTT估计器的计算 让我们来看一下 RTT估计器(平滑的 RTT和平滑的均值偏差)是如何被初始化和更新,以 及每个重传超时是怎样计算的。 变量A和D分别被初始化为 0和3秒。初始的重传超时使用下面的公式进行计算

232

使用TCP/IP详解,卷1:协议

下载

RTO = A + 2D = 0 + 2×3 = 6 s (因子2D只在这个初始化计算中使用。正如前面提到的,以后使用4D和A相加来计算 RTO)。 这就是传输初始 SYN所使用的RTO。 结果是这个初始 S Y N丢失了,然后超时并引起了重传。图 2 1 - 5给出了t c p d u m p输出文件中 的前4行。

图21-5 初始SYN的超时和重传

当超时在5.802秒后发生时,计算当前的 RTO值为 RTO = A + 4D = 0 + 4×3 = 12 s 因此,应用于 RTO 的指数退避取为 1 2。由于这是第 1次超时,我们使用倍数 2,因此下一个超 时时间取值为 2 4秒。再下一个超时时间的倍数为 4,得出值为4 8秒(这些初始 RTO,对于一个 连接上的最初的 SYN,取值为6秒,接下来为24秒,正是我们在图 4-5中看到的)。 A C K在重传后 4 6 7 m s到达。 A和D的值没有被更新,这是因为 K a r n算法对重传的处理比较 模糊。下一个发送的报文段是第 4行的A C K,但它只是一个 A C K,所以没有被计时(只有数 据报文段才会被计时)。 当发送第 1个数据报文段时(图 2 1 - 2中的报文段 1),RTO没有改变,这同样是由于 K a r n算 法。当前的 2 4秒一直被使用,直到进行一个 RT T测量。这意味着图 2 1 - 4中时间 0的RTO并不真 的是24,但我们没有画出那个点。 当第1个数据报文段的 A C K(图2 1 - 2中的报文段 2)到达时,经历了 3个时钟滴答,估计器 被初始化为 A = M + 0.5 = 1.5 + 0.5 = 2 D = A/2 = 1 (因为经历 3个时钟滴答,因此, M取值为1 . 5)。在前面,A和D初始化为 0,RTO的初始计算值 为3。这是使用第1个RTT的测量结果 M对估计器进行首次计算的初始值。计算的 RTO值为 RTO = A + 4D = 2 + 4×1 = 6 s 当第2个数据报文段的 ACK(图21-2中的报文段 5)到达时,经历了 1个时钟滴答( 0.5秒), 估计器按如下更新: Err = M -A = 0.5 - 2 =-1.5 A = A + gErr = 2-0.125×1.5 = 1.8125 D = D + h(| Err | - D) = 1 + 0.25×(1.5-1) = 1.125 RTO = A + 4D = 1.8125 + 4×1.125 = 6.3125 E rr、A和D的定点表示与实际使用的定点计算(在简化浮点计算中表示过)有一些微小的差 别。这些不同使 RTO 取值为 6秒(而非 6 . 3 1 2 5秒),正如我们在图 2 1 - 4中的时间 1 . 8 7 1处所画的 那样。

233

第21章 TCP的超时与重传使用

下载 21.4.3 慢启动

我们在第20.6节介绍了慢启动算法,在图 21-2中可再次看到它的工作过程。 连接上最初只允许传输一个报文段,然后在发送下一个报文段之前必须等待接收它的确 认。当报文段2被接收后,就可以再发送两个报文段。

21.5 拥塞举例 现在观察一下数据报文段的传输过程。图 2 1 - 6显示了报文段中数据的起始序号与该报文 段发送时间的对比图。它提供了一种较好的数据传输的可视化方法。通常代表数据的点将向 上和向右移动,这些点的斜率就表示传输速率。当这些点向下和向右移动则表示发生了重传。 在2 1 . 4节开始时,我们曾提到整个传输的时间约为 4 5秒,但在本图中只显示了 3 5秒钟。 这3 5秒只是数据报文段发送的时间。因为第 1个S Y N看来是丢失了并被重传(见图 2 1 - 5),因 此第1个数据报文段是在第 1个S Y N发送6 . 3秒后才发送的。而且,在发送最后一个数据报文段 和F I N(图2 1 - 6中的3 4 . 1秒)之后,在接收方的 F I N到达之前,又花费了另外的 4 . 0秒接收来自 接收方的最后14个ACK。

序号 (千字节)

发送时间(秒)

图21-6 从slip 发送32768个字节的数据到vangogh

可以立即看到图 2 1 - 6中发生在时刻 1 0,1 4和2 1附近的 3个重传。我们还可以看到在这 3个 点中只进行了一次报文段的重传,因为只有一个点下垂低于向上的斜率。 仔细检查一下这几个下垂点中的第 1个点(在 1 0秒标记处的附近)。整理 t c p d u m p的输出 结果可以得到图 21-7。 在这个图中,除了下面将要讨论的报文段 7 2,已经去掉了其他所有的窗口通告。主机 s l i p总是通告窗口大小为 4 0 9 6,而主机 v a n g o g h则通告窗口为 8 1 9 2。该图中报文段的编号 可以看作是图 2 1 - 2的延续,在那里报文段的编号从 1开始。与图 2 1 - 2一样,报文段根据在 s l i p 上发送和接收的顺序进行编号, t c p d u m p在主机 s l i p上运行。我们还去掉了一些与讨论无

234

使用TCP/IP详解,卷1:协议

下载

关的段(第 44, 47和49以及所有来自vangogh的ACK)。

256字节 到应用程序 (HOLE) (保存256字节) (保存256字节) (保存256字节) (保存256字节) (保存256字节) (保存256字节) (保存256字节) (保存256字节)

2340字节 到应用程序

图21-7 10秒标记处附近重传的分组交换

看来报文段4 5丢失或损坏了,这一点无法从该输出上进行辨认。能够在主机 s l i p上看到 的是对第 6657字节(报文段 58)以前数据的确认(不包括字节 6657在内)。紧接着的是带有相 同序号的 8个A C K。正是接收到报文段 6 2,也就是第 3个重复A C K,才引起自序号 6 6 5 7开始的 数据报文段(报文段 6 3)进行重传。的确,源于伯克利的 T C P实现对收到的重复 A C K进行计 数,当收到第 3个时,就假定一个报文段已经丢失并重传自那个序号起的一个报文段。这就是 Jacobson的快速重传算法,该算法通常与他的快速恢复算法一起配合使用。我们在第 21.7节中 介绍这两个算法。 注意到在重传后(报文段 6 3),发送方继续正常的数据传输(报文段 6 7、6 9和7 1)。T C P 不需要等待对方确认重传。 现在检查一下在接收端发生了什么。当按序收到正常数据(报文段 4 3)后,接收 T C P将 255个字节的数据交给用户进程。但下一个收到的报文段(报文段 46)是失序的:数据的开始

下载

235

第21章 TCP的超时与重传使用

序号( 6 9 1 3)并不是下一个期望的序号( 6 6 5 7)。T C P保存2 5 6字节的数据,并返回一个已成 功接收数据的最大序号加 1(6 6 5 7)的A C K。被v a n g o g h接收到的后面 7个报文段( 48, 50, 52, 54, 55, 57和59)也是失序的,接收方 TCP保存这些数据并产生重复 ACK。 目前T C P尚无办法告诉对方缺少一个报文段,也无法确认失序数据。此时主机 v a n g o g h 所能够做的就是继续发送确认序号为 6657的ACK。 当缺少的报文段(报文段 6 3)到达时,接收方 T C P在其缓存中保存第 6 6 5 7 ~ 8 9 6 0字节的数 据,并将这 2 3 0 4字节的数据交给用户进程。所有这些数据在报文段 7 2中进行确认。请注意此 时该A C K通告窗口大小为 5 8 8 8(8 1 9 2-2 3 0 4),这是因为用户进程没有机会读取这些已准备好 的2304字节的数据。 如果仔细检查图 21-6 中t c p d u m p的输出中第14和21秒附近的下垂点,我们会看到它们也 是由于收到了 3个重复ACK引起的,这表明一个分组已经丢失。在这些例子中只有一个分组被 重传。 在介绍完拥塞避免算法后,将在第 21.8节中继续讨论这个例子。

21.6 拥塞避免算法 在第20.6节介绍的慢启动算法是在一个连接上发起数据流的方法,但有时我们会达到中间 路由器的极限,此时分组将被丢弃。拥塞避免算法是一种处理丢失分组的方法。该方法的具 体描述见 [Jacobson 1988]。 该算法假定由于分组受到损坏引起的丢失是非常少的(远小于 1 %),因此分组丢失就意 味着在源主机和目的主机之间的某处网络上发生了拥塞。有两种分组丢失的指示:发生超时 和接收到重复的确认(我们在 2 1 . 5节看到这种现象。如果使用超时作为拥塞指示,则需要使 用一个好的 RTT算法,正如在21.3节中描述的那样)。 拥塞避免算法和慢启动算法是两个目的不同、独立的算法。但是当拥塞发生时,我们希 望降低分组进入网络的传输速率,于是可以调用慢启动来作到这一点。在实际中这两个算法 通常在一起实现。 拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口 c w n d和一个慢 启动门限ssthresh。这样得到的算法的工作过程如下: 1) 对一个给定的连接,初始化 cwnd为1个报文段, ssthresh为65535个字节。 2) TCP输出例程的输出不能超过 c w n d和接收方通告窗口的大小。拥塞避免是发送方使用 的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估 计,而后者则与接收方在该连接上的可用缓存大小有关。 3) 当拥塞发生时(超时或收到重复确认),s s t h re s h被设置为当前窗口大小的一半( c w n d 和接收方通告窗口大小的最小值,但最少为 2个报文段)。此外,如果是超时引起了拥塞,则 cwnd被设置为1个报文段(这就是慢启动)。 4) 当新的数据被对方确认时,就增加 c w n d,但增加的方法依赖于我们是否正在进行慢启 动或拥塞避免。如果 c w n d小于或等于 s s t h re s h,则正在进行慢启动,否则正在进行拥塞避免。 慢启动一直持续到我们回到当拥塞发生时所处位置的半时候才停止(因为我们记录了在步骤 2 中给我们制造麻烦的窗口大小的一半),然后转为执行拥塞避免。 慢启动算法初始设置 c w n d为1个报文段,此后每收到一个确认就加 1。正如 2 0 . 6节描述的

236

使用TCP/IP详解,卷1:协议

下载

那样,这会使窗口按指数方式增长:发送 1个报文段,然后是 2个,接着是 4个……。 拥塞避免算法要求每次收到一个确认时将 cwnd增加1/cwnd。与慢启动的指数增加比起来, 这是一种加性增长 (additive increase)。我们希望在一个往返时间内最多为 c w n d增加1个报文段 (不管在这个 RT T中收到了多少个 A C K),然而慢启动将根据这个往返时间中所收到的确认的 个数增加cwnd。 所有的4.3BSD版本和4.4BSD都在拥塞避免中将增加值不正确地设置为 1个报文段 的一小部分(即一个报文段的大小除以 8),这是错误的,并在以后的版本中不再使用 [Floyd 1994]。但是,为了和(不正确的)实现的结果对应,我们在将来的计算中给出 了这个细节。 在[Leffler et al. 1989]中介绍的4.3BSD Tahoe版本仅在对方处于一个不同的网络上时 才进行慢启动。而4.3BSD Reno版本改变了这种做法,因此,慢启动总是被执行。 图2 1 - 8是慢启动和拥塞避免的一个可视化描述。我们以段为单位来显示 c w n d和s s t h re s h, 但它们实际上都是以字节为单位进行维护的。

ssthresh cwud (报文段)

往返时间

图21-8 慢启动和拥塞避免的可视化描述

在该图中,假定当 c w n d为3 2个报文段时就会发生拥塞。于是设置 s s t h re s h为1 6个报文段, 而c w n d为1个报文段。在时刻 0发送了一个报文段,并假定在时刻 1接收到它的 A C K,此时 c w n d增加为2。接着发送了 2个报文段,并假定在时刻 2接收到它们的 A C K,于是c w n d增加为4 (对每个 A C K增加1次)。这种指数增加算法一直进行到在时刻 3和4之间收到 8个A C K后c w n d等 于s s t h re s h时才停止,从该时刻起, c w n d以线性方式增加,在每个往返时间内最多增加 1个报 文段。 正如我们在这个图中看到的那样,术语“慢启动”并不完全正确。它只是采用了比引起 拥塞更慢些的分组传输速率,但在慢启动期间进入网络的分组数增加的速率仍然是在增加的。 只有在达到 ssthresh拥塞避免算法起作用时,这种增加的速率才会慢下来。

21.7 快速重传与快速恢复算法 拥塞避免算法的修改建议 1990年提出 [Jacobson 1990b]。在我们的例子(见 21.5节)中已

237

第21章 TCP的超时与重传使用

下载 经可以看到这些实施中的修改。

在介绍修改之前,我们认识到在收到一个失序的报文段时, T C P立即需要产生一个 A C K (一个重复的 A C K)。这个重复的 A C K不应该被迟延。该重复的 A C K的目的在于让对方知道收 到一个失序的报文段,并告诉对方自己希望收到的序号。 由于我们不知道一个重复的 A C K是由一个丢失的报文段引起的,还是由于仅仅出现了几 个报文段的重新排序,因此我们等待少量重复的 A C K到来。假如这只是一些报文段的重新排 序,则在重新排序的报文段被处理并产生一个新的 A C K之前,只可能产生 1 ~ 2个重复的 A C K。 如果一连串收到 3个或3个以上的重复 A C K,就非常可能是一个报文段丢失了(我们在 2 1 . 5节 中见到过这种现象)。于是我们就重传丢失的数据报文段,而无需等待超时定时器溢出。这就 是快速重传算法。接下来执行的不是慢启动算法而是拥塞避免算法。这就是快速恢复算法。 在图21-7中可以看到在收到 3个重复的ACK之后没有执行慢启动。相反,发送方进行重传, 接着在收到重传的 ACK以前,发送了3个新的数据的报文段(报文段 67, 69和71)。 在这种情况下没有执行慢启动的原因是由于收到重复的 A C K不仅仅告诉我们一个分组丢 失了。由于接收方只有在收到另一个报文段时才会产生重复的 A C K,而该报文段已经离开了 网络并进入了接收方的缓存。也就是说,在收发两端之间仍然有流动的数据,而我们不想执 行慢启动来突然减少数据流。 这个算法通常按如下过程进行实现: 1) 当收到第 3个重复的 A C K时,将s s t h re s h设置为当前拥塞窗口 c w n d的一半。重传丢失的 报文段。设置cwnd为ssthresh加上3倍的报文段大小。 2) 每次收到另一个重复的 A C K时, c w n d增加 1个报文段大小并发送 1个分组(如果新的 cwnd允许发送)。 3) 当下一个确认新数据的 ACK到达时,设置cwnd为ssthresh(在第1步中设置的值)。这个 A C K应该是在进行重传后的一个往返时间内对步骤 1中重传的确认。另外,这个 A C K也应该 是对丢失的分组和收到的第 1个重复的ACK之间的所有中间报文段的确认。这一步采用的是拥 塞避免,因为当分组丢失时我们将当前的速率减半。 在下一节中我们将看到变量 cwnd和ssthresh的计算过程。 快速重传算法最早出现在 4.3BSD Ta h o e版本中,但它随后错误地使用了慢启动。 快速恢复算法出现在4.3BSD Reno版本中。

21.8 拥塞举例(续) 通过使用 t c m d u m p和插口排错选项(在第 2 1 . 4节进行了介绍)来观察一个连接,就会在 发送每一个报文段时看到 cwnd和ssthresh的值。如果MSS为256字节,则cwnd和ssthresh的初始 值分别为 2 5 6和6 5 5 3 5字节。每当收到一个 A C K时,我们可以看到 c w n d增加了一个 M S S,取值 分别为 512, 768, 1024, 1280 等。假定不会发生拥塞,则最终拥塞窗口将超过接收方的通告窗 口,意味着通告窗口将对数据流进行限制。 一个更有趣的例子是观察在拥塞发生时的情况。使用与 21.4节同样的例子。当这个例子运 行时发生了 4次拥塞。为建立连接而发送的初始 S Y N有一个因超时而引起的重传(见图 2 1 - 5), 接着在数据传输过程中有 3个分组丢失(见图 21-6)。

238

使用TCP/IP详解,卷1:协议

下载

图2 1 - 9显示了当初始 S Y N重传并接着发送了前 7个数据报文段时变量 c w n d和s s t h re s h的值 (在图2 1 - 2中显示了最初的数据报文段及其 A C K之间的交换过程)。使用t c p d u m p的记号来表 示数据字节:1:257(256)表示第1~256字节。 当S Y N的超时发生时, s s t h re s h被置为其最小取值( 5 1 2字节,在本例中表示 2个报文段)。 为进入慢启动阶段, cwnd被置为1个报文段( 256字节,与当前值一致)。 当收到SYN和ACK时,没有对这两个变量做任何修改,因为新的数据还没有被确认。 当ACK 257到达时,因为cwnd小于等于ssthresh,因此仍然处于慢启动阶段,于是将 cwnd 增加256字节。当收到ACK 513时,进行同样的处理。 当ACK 769到达时,我们不再处于慢启动状态,而是进入了拥塞避免状态。新的 c w n d值 按以下方法计算:

考虑到cwnd实际上以字节而非以报文段来维护,因此这就是我们前面提到的增加 1/cwnd。 在这个例子中我们计算

为885字节(使用整数算法)。当下一个 ACK 1025到达时,我们计算

为9 9 1字节(在这些表达式中包括了不正确的 2 5 6 / 8项来匹配实现计算的数值,正如我们在前 面标注的那样)。 报文段号 (图21-2)

发送

行 为 接收

变 量 注释 初始化 超时重传

慢启动 慢启动

图21-9 拥塞避免的例子

这个c w n d持续增加一直到在图 2 1 - 6所示的发生在 1 0秒左右的第 1次重传。图 2 1 - 1 0是使用 与图21-6相同数据得到的图表,并给出了 cwnd增加的数值。 本图中c w n d的前6个值就是我们为图 2 1 - 9所计算的数值。在这个图中,要想直观分辨出在 慢启动过程中的指数增加和在拥塞避免过程中的线性增加之间的区别是不可能的,因为慢启 动的过程太快。

239

第21章 TCP的超时与重传使用

下载

我们需要解释在重传的 3个点上所发生的情况。回想起每个重传都是因为收到 3个重复的 A C K,表明 1个分组丢失了。这就是 2 1 . 7节的快速重传算法。 s s t h re s h立即设置为当重传发生 时正在起作用的窗口大小的一半,但是在接收到重复 A C K的过程中 c w n d允许保持增加,这是 因为每个重复的 A C K表示1个报文段已离开了网络(接收 T C P已缓存了这个报文段,等待所缺 数据的到达)。这就是快速恢复算法。 与图20-9类似,图21-10表示了cwnd和ssthresh的数值。第一列上的报文段编号与图 21-7对 应。

序号(千字节)和 cwnd(100字节)

序号

发送时间(秒)

图21-10 当数据被发送时的发送序号和cwnd的取值 报文段号 (图21-7)

发送

行 为 接收

变 量 注释 新数据的确认 重复 重复 重复 重复 重复 重复

重传

重复 重复 新数据的确认

图21-11 拥塞避免的例子

c w n d的值一直持续增加,从图 2 1 - 9中对应于报文段 1 2的最终取值( 1 0 8 9)到图2 1 - 11中对 应于报文段 5 8的第一个取值( 2 4 2 6),而s s t h re s h的值则保持不变( 5 1 2),这是因为在此过程 中没有出现过重传。 当最初的 2个重复的 A C K(报文段 6 0和6 1)到达时它们被计数,而 c w n d保持不变(也就 是图2 1 - 1 0中处理重传之前的平坦的一段)。然而,当第 3个重复的 A C K到达时, s s t h re s h被置

240

使用TCP/IP详解,卷1:协议

下载

为c w n d的一半(四舍五入到报文段大小的下一个倍数),而c w n d被置为s s t h re s h加上所收到的 重复的ACK数乘以报文段大小(也即 1024加上3倍的256),然后发送重传数据。 又有5个重复的 A C K到达(报文段 64~66, 68和7 0),每次 c w n d增加1个报文段长度。最后 一个新的 ACK(报文段72段)到达时,cwnd被置为ssthresh(1024)并进入正常的拥塞避免过 程。由于 c w n d小于等于 s s t h re s h(现在相等),因此报文段的大小增加到 c w n d,取值为 1 2 8 0。 当下一个新的ACK到达(没有在图 21-11中表示出来)时, cwnd大于ssthresh,取值为1363。 在快速重传和快速恢复阶段,我们收到报文段 6 6、6 8和7 0中的重复的 A C K后才发送新的 数据,而不是在接收到报文段 6 4和6 5中重复的 A C K之后就发送。这是 c w n d的取值与未被确认 的数据大小比较的结果。当报文段 6 5到达时, c w n d为2 0 4 8,但未被确认的数据有 2 3 0 4字节 (9个报文段:46, 48, 50, 52, 54, 55, 57, 59和63),因此不能发送任何数据。当报文段 65到达后, c w n d被置为2 3 0 4,此时我们仍不能进行发送。但是当报文段 6 6到达时, c w n d为2 5 6 0,所以我 们可以发送 1个新的数据报文段。类似地,当报文段 68到达时,cwnd等于2816,该数值大于未 被确认的 2560字节的数据大小,因此我们可以发送另 1个新的数据报文段。报文段 70到达时也 进行了类似的处理。 在图2 1 - 1 0中的时刻 1 4 . 3发生下一个重传,也是因为收到了 3个重复的 A C K。因此当另一 个ACK到达时,可以看到 cwnd以同样的方式增长,之后降低到 1024。 图2 1 - 1 0中的时刻 2 1 . 1也是因为收到了重复的 A C K而引起了重传。在重传后收到了 3个重 复的A C K,因此观察到 c w n d增加3个,之后降低到 1 2 8 0。在传输的后面部分, c w n d以线性方 式增加到最终值 3615。

21.9 按每条路由进行度量 较新的TCP实现在路由表项中维持许多我们在本章已经介绍过的指标。当一个 TCP连接关 闭时,如果已经发送了足够多的数据来获得有意义统计资料,且目的结点的路由表项不是一 个默认的表项,那么下列信息就保存在路由表项中以备下次使用:被平滑的 RT T、被平滑的 均值偏差以及慢启动门限。所谓“足够多的数据”是指 1 6个窗口的数据,这样就可得到 1 6个 RTT采样,从而使被平滑的 RTT过滤器能够集中在正确结果的 5%以内。 而且,管理员可以使用 r o u t e ( 8 )命令来设置给定路由的度量:前一段中给出的三个指标以 及MT、输出的带宽时延乘积(见第 20.7节)和输入的带宽时延乘积。 当建立一个新的连接时,不论是主动还是被动,如果该连接将要使用的路由表项已经有 这些度量的值,则用这些度量来对相应的变量进行初始化。

21.10 ICMP的差错 让我们来看一下 T C P是怎样处理一个给定的连接返回的 I C M P的差错。 T C P能够遇到的最 常见的ICMP差错就是源站抑制、主机不可达和网络不可达。 当前基于伯克利的实现对这些错误的处理是: • 一个接收到的源站抑制引起拥塞窗口 c w n d被置为1个报文段大小来发起慢启动,但是慢 启动门限 s s t h re s h没有变化,所以窗口将打开直至它或者开放了所有的通路(受窗口大 小和往返时间的限制)或者发生了拥塞。 • 一个接收到的主机不可达或网络不可达实际上都被忽略,因为这两个差错都被认为是

241

第21章 TCP的超时与重传使用

下载

短暂现象。这有可能是由于中间路由器被关闭而导致选路协议要花费数分钟才能稳定 到另一个替换路由。在这个过程中就可能发生这两个 I C M P差错中的一个,但是连接 并不必被关闭。相反, T C P试图发送引起该差错的数据,尽管最终有可能会超时(回 想图 2 1 - 1中 T C P 在 9分钟内没有放弃的情况)。当前基于伯克利的实现记录发生的 I C M P差错,如果连接超时, I C M P差错被转换为一个更合适的的差错码而不是“连接 超时”。 早期的BSD实现在任何时候收到一个主机不可达或网络不可达的 ICMP差错时会不 正确的放弃连接。 一个例子 可以通过在连接中拨号 S L I P链路的断开来观察一个 I C M P主机不可达的差错是如何被处理 的。建立一个从主机 s l i p到主机a i x的连接(从扉页前的图中可以看到这个连接经过了我们 的拨号 S L I P链路)。在建立连接并发送一些数据之后,在路由器 s u n和n e t b之间的 S L I P链路 被断开,这引起 s u n上的默认路由表项(见 9 . 2节)被移去。我们希望 s u n对目的为 1 4 0 . 2 5 2 . 1 以太网的IP数据报响应 ICMP主机不可达。希望观察 TCP如何处理这些ICMP差错。 下面是主机 slip的交互会话: 运行sock程序 键入本行 和它的回显 此时挂断SLIP链路 然后键入本行并观察其行为 SLIP链路此时重新建立, 该行及其回显被交换

此时挂断SLIP链路,且没有重新建立 TCP最终放弃

图2 1 - 1 2显示了在路由器 b s d i上截获的 t c p d u m p的相应输出(去掉了连接建立和所有的 窗口通告)。我们连接到在主机 a i x上的回显服务器并键入“ test line ”(第1行),它被回显 (第2行)且回显被确认(第 3行),接着我们断开了 SLIP链路。 我们键入“ another line”(第3行之后)并希望看到 TCP超时和重传报文。的确,这一行在 收到应答前被发送了 6次。第4~13行显示了第1次传输和接着的 4次重传,每个都产生了一个来 自路由器 s u n的I C M P主机不可达。这正是我们所希望的:从 s l i p来的I P数据报发往路由器 bsdi(这是一个指向 sun的默认路由器),并到达检测到链路中断的 sun。 在发生这些重传时, S L I P链路又被连通,在第 1 4行的重传被交付。第 1 5行是来自 a i x的 回显,而第 16行是对这个回显的确认。 这表明T C P忽略I C M P主机不可达的差错并坚持重传。我们也可以观察到所预期的在每一 次重传超时中的指数退避:第 1次约为 2 . 5秒,接着乘 2(约5秒),乘4(约1 0秒),乘8(约2 0 秒),乘14(约40秒)。 接着我们键入输入的第 3行(“line number 3”)并看到它在第 17行被发送,在第18行回显, 并在第19行对回显进行确认。

242

使用TCP/IP详解,卷1:协议

下载

SLIP链路此时被挂断

SLIP链路此时被建立

SLIP链路此时被挂断

在这里14行输出结果被删除

图21-12 TCP对接收到的ICMP主机不可达差错的处理

现在我们希望观察在接收到 I C M P主机不可达后, T C P重传并放弃的情况。于是再次断开 SLIP链路,之后键入“ the last line”,并观察到在 TCP放弃之前该行被发送了 13次(我们已经 从结果中删除了第 30~43行,它们是额外的重传)。 然而,我们所观察到的现象是 s o c k程序在最终放弃时打印出来的差错信息:“没有到达 主机的路由”。这与U n i x的I C M P主机不可达的差错类似(图 6 - 1 2)。这表明 T C P保存了它在连 接上收到的 ICMP差错,并在最终放弃时打印出该差错,而不是“连接超时”。 最后,注意到第 22~46行与第6~14行不同的重传间隔。看起来我们键入的第 3行在第17~19 行被发送和确认时(无任何重传),T C P更新了它的估计器。最初的重传超时时间现在是 3秒, 后续取值为 6, 12, 24, 48,直至上限 64。

243

第21章 TCP的超时与重传使用

下载 21.11 重新分组

当TCP超时并重传时,它不一定要重传同样的报文段。相反, TCP允许进行重新分组而发 送一个较大的报文段,这将有助于提高性能(当然,这个较大的报文段不能够超过接收方声 明的M S S)。在协议中这是允许的,因为 T C P是使用字节序号而不是报文段序号来进行识别它 所要发送的数据和进行确认。 在实际中,可以很容易地看到这一点。我们使用 s o c k程序连接到丢弃服务器并键入一行。 接着拔掉以太网电缆并再键入一行。当这一行被重传时,键入第 3行。我们预期下一个重传包 含第2次和第3次键入的数据。 bsdi % s o c k s v r 4 d i s c a r d hello there line number 2 and 3

第一行发送成功 接着我们断开以太网电缆 本行被重传 在第2行发送成功之前键入本行 接着重新连接以太网电缆

图21-13显示了tcpdump的输出(去掉了连接建立、连接终止以及所有的窗口通告)。

此时断开以太网电缆

此时键入第3行

此时重新连接以太网电缆

图21-13 TCP对数据的重新分组

第1行和第2行显示了头一行(“hello there”)被发送及其 ACK。接着我们拔掉以太网电缆 并键入“ line number 2”(14字节,包括换行)。这些数据在第 3行被发送,并在第 4和第5行被 重传。 在第6行重传前,我们键入“ and 3”(6个字节,包括换行),并观察到这个重传包括 2 0个 字节:键入的两行。当 ACK在第9行到达时,它确认了这 20字节的数据。

21.12 小结 本章提供了对 T C P超时和重传机制的详细研究。使用的第 1个例子是一个丢失的建立连接 的SYN,并观察了在随后的重传和超时中怎样使用指数退避方式。 TCP计算往返时间并使用这些测量结果来维护一个被平滑的 RTT估计器和被平滑的均值偏 差估计器。这两个估计器用来计算下一个重传时间。许多实现对每个窗口仅测量一次 Karn算法在分组丢失时可以不测量 RTT就能解决重传的二义性问题。

RT T。

244

使用TCP/IP详解,卷1:协议

下载

详细例子包括 3个丢失的分组,使我们看到 T C P的许多实际算法:慢启动、拥塞避免、快 速重传和快速恢复。我们也能够使用拥塞窗口和慢启动门限来手工计算 TCP RT T估计器,并 将这些值与跟踪输出的实际数据进行比较。 以多种 I C M P差错对 T C P连接的影响以及 T C P怎样允许对数据进行重新分组来结束本章。 我们观察到“软”的 I C M P差错没有引起 T C P连接终止,但这些差错被保存以便在连接非正常 中止时能够报告这些软差错。

习题 21.1 在图2 1 - 5中第1个超时时间计算为 6秒而第 2个为1 2秒。如果初始 S Y N的确认在 1 2秒超时 溢出时还没有到达,则下一次超时在什么时候发生? 21.2 在图2 1 - 5后面的讨论中,我们提到计算的超时间隔分别为图 4 - 5中表示的 6、2 4和4 8秒。 但是如果观察一个从 SVR4系统到一个不存在的主机的连接,则超时间隔分别为 6, 12, 24 和48秒。请问发生了什么情况? 21.3 按下面的描述比较 T C P滑动窗口协议与 T F T P的停止等待协议的性能。在本章中,我们 在35秒(图21-6)内传输 32768字节的数据,其中链路的平均 RTT是1.5秒(图21-4)。计 算在同样条件下 TFTP需要多长时间? 21.4 在第2 1 . 7节,我们提到过收到一个重复的 A C K是因为一个报文段丢失或重新进行排序。 在2 1 . 5节我们看到 1个丢失的报文段产生一些重复的 A C K。请画图表示重新排序也会产 生一些重复的ACK。 21.5 在图21-6中的时刻28.8和29.8之间有一个显而易见的点,请问这是不是一个重传? 21.6 在21.6节我们提到过,如果目的地址位于一个不同的网络上, 4.3BSD Tahoe版本只执行 慢启动。你认为在这里“不同的网络”是由什么决定的?(提示:参看附录 E)。 21.7 在20.2节我们提到过,在正常情况下, TCP每隔一个报文段进行一次确认,但是在图 212中,我们看到接收方对每个报文段都进行了确认,请解释其中的原因? 21.8 如果默认路由占优势,那么每路由( per-route)的度量是否真的有用?

下载

第22章 TCP的坚持定时器 22.1 引言 我们已经看到 T C P通过让接收方指明希望从发送方接收的数据字节数(即窗口大小)来 进行流量控制。如果窗口大小为 0会发生什么情况呢?这将有效地阻止发送方传送数据,直到 窗口变为非 0为止。 可以在图 2 0 - 3中看到这种情况。当发送方接收到报文段 9时,它打开被报文段 8关闭的窗 口并立即开始发送数据。 TCP必须能够处理打开此窗口的 ACK(报文段 9)丢失的情况。 ACK 的传输并不可靠,也就是说, T C P不对A C K报文段进行确认, T C P只确认那些包含有数据的 ACK报文段。 如果一个确认丢失了,则双方就有可能因为等待对方而使连接终止:接收方等待接收数 据(因为它已经向发送方通告了一个非 0的窗口),而发送方在等待允许它继续发送数据的窗 口更新。为防止这种死锁情况的发生,发送方使用一个坚持定时器 (persist timer)来周期性地 向接收方查询,以便发现窗口是否已增大。这些从发送方发出的报文段称为窗口探查 (window probe)。在本章中,我们将讨论窗口探查和坚持定时器,还将讨论与坚持定时器有关的糊涂窗 口综合症。

22.2 一个例子 为了观察到实际中的坚持定时器,我们启动一个接收进程。它监听来自客户的连接请求, 接受该连接请求,然后在从网上读取数据前休眠很长一段时间。 s o c k程序可以通过指定一个暂停选项 - P使服务器在接受连接和进行第一次读动作之间进 入休眠。我们以这种方式调用服务器: svr4 % s o c k - i - s - P 1 0 0 0 0 0 5 5 5 5

该命令在从网络上读数据之前休眠 100 000秒(2 7 . 8小时)。客户运行在主机 b s d i上,并 向服务器的 5 5 5 5端口执行 1 0 2 4字节的写操作。图 2 2 - 1给出了t c p d u m p的输出结果(我们已经 在结果中去掉了连接的建立过程)。 报文段 1 ~ 1 3显示的是从客户到服务器的正常的数据传输过程,有 9 2 1 6个字节的数据填充 了窗口。服务器通告窗口大小为 4 0 9 6字节,且默认的插口缓存大小为 4 0 9 6字节。但实际上它 一共接收了9216字节的数据,这是在 SVR4中TCP代码和流子系统 (stream subsystem)之间某种 形式交互的结果。 在报文段 1 3中,服务器确认了前面 4个数据报文段,然后通告窗口为 0,从而使客户停止 发送任何其他的数据。这就引起客户设置其坚持定时器。如果在该定时器时间到时客户还没 有接收到一个窗口更新,它就探查这个空的窗口以决定窗口更新是否丢失。由于服务器进程 处于休眠状态,所以 TCP缓存9216字节的数据并等待应用进程读取。 请注意客户发出的窗口探查之间的时间间隔。在收到一个大小为 0的窗口通告后的第 1个

246

使用TCP/IP详解,卷1:协议

下载

(报文段 14)间隔为4.949秒,下一个(报文段 16)间隔是4.996秒,随后的间隔分别约为 6, 12, 24, 48和60秒。

图22-1 坚持定时器探查一个0大小窗口的例子

为什么这些间隔总是比5、6、12、24、48和60小一个零点几秒呢?因为这些探查被TCP的500 ms 定时器超时例程所触发。当定时器时间到时,就发送窗口探查,并大约在4 ms之后收到一个应答。 接收到应答使得定时器被重新启动,但到下一个时钟滴答之间的时间则约为 500减4 ms。 计算坚持定时器时使用了普通的 T C P指数退避。对一个典型的局域网连接,首次超时时 间算出来是 1 . 5秒,第 2次的超时值增加一倍,为 3秒,再下次乘以 4为6秒,之后再乘以 8为1 2 秒等。但是坚持定时器总是在 5~60秒之间,这与我们在图 22-1中观察到的现象一致。 窗口探查包含一个字节的数据(序号为 9 2 1 7)。T C P总是允许在关闭连接前发送一个字节 的数据。请注意,尽管如此,所返回的窗口为 0的A C K并不是确认该字节(它们确认了包括 9216在内的所有数据),因此这个字节被持续重传。 坚持状态与第 2 1章中介绍的重传超时之间一个不同的特点就是 T C P从不放弃发送窗口探 查。这些探查每隔 6 0秒发送一次,这个过程将持续到或者窗口被打开,或者应用进程使用的 连接被终止。

22.3 糊涂窗口综合症 基于窗口的流量控制方案,如 T C P所使用的,会导致一种被称为“糊涂窗口综合症 S W S

下载

247

第22章 TCP的坚持定时器使用

(Silly Window Syndrome)”的状况。如果发生这种情况,则少量的数据将通过连接进行交换, 而不是满长度的报文段 [Clark 1982]。 该现象可发生在两端中的任何一端:接收方可以通告一个小的窗口(而不是一直等到有 大的窗口时才通告),而发送方也可以发送少量的数据(而不是等待其他的数据以便发送一个 大的报文段)。可以在任何一端采取措施避免出现糊涂窗口综合症的现象。 1) 接收方不通告小窗口。通常的算法是接收方不通告一个比当前窗口大的窗口(可以为0), 除非窗口可以增加一个报文段大小(也就是将要接收的 M S S)或者可以增加接收方缓存空间 的一半,不论实际有多少。 2) 发送方避免出现糊涂窗口综合症的措施是只有以下条件之一满足时才发送数据: ( a )可 以发送一个满长度的报文段; ( b )可以发送至少是接收方通告窗口大小一半的报文段; ( c )可以 发送任何数据并且不希望接收 A C K(也就是说,我们没有还未被确认的数据)或者该连接上 不能使用Nagle算法(见第 19.4节)。 条件( b )主要对付那些总是通告小窗口(也许比 1个报文段还小)的主机,条件 ( c )使我们 在有尚未被确认的数据(正在等待被确认)以及在不能使用 N a g l e算法的情况下,避免发送小 的报文段。如果应用进程在进行小数据的写操作(例如比该报文段还小),条件(c)可以避免出 现糊涂窗口综合症。 这三个条件也可以让我们回答这样一个问题:在有尚未被确认数据的情况下,如果 N a g l e 算法阻止我们发送小的报文段,那么多小才算是小呢?从条件 ( a )中可以看出所谓“小”就是 指字节数小于报文段的大小。条件 (b)仅用来对付较老的、原始的主机。 步骤2中的条件(b)要求发送方始终监视另一方通告的最大窗口大小,这是一种发送方猜测 对方接收缓存大小的企图。虽然在连接建立时接收缓存的大小可能会减小,但在实际中这种 情况很少见。 一个例子 现在我们通过仔细查看一个详细的例子来观察实际避免出现糊涂窗口综合症的情况,该 例子也包括了坚持定时器。我们将在发送主机 s u n上运行 s o c k程序,并向网络写 6个1 0 2 4字 节的数据。 sun % s o c k - i - n 6 b s d i 7 7 7 7

但是在主机 bsdi的接收过程中我们加入一些暂停。在第 1次读数据前暂停 4秒,之后每次读 之前暂停2秒。而且,接收方进行的是 256字节的读操作: bsdi % s o c k - i - s - P 4 - p 2 - r 2 5 6 7 7 7 7

最初的暂停是为了让接收缓存被填满,迫使发送方停止发送。随后由于接收方从网络上 进行了一些小数目的读取,我们预期能看到接收方采取的避免糊涂窗口综合症的措施。 图22-2是传输6144字节数据的时间系列(我们去掉了连接建立过程)。 我们还需要跟踪在每个时间点上读取数据时应用程序的运行情况、当前正在接收缓存中 的数据的序号以及接收缓存中可用空间的大小。图 22-3显示了所发生的每件事情。 图22-3中的第1列是每个行为的相对时间点。那些带有 3位小数点的时间是从tcpdump的输 出结果(图 2 2 - 2)中得到的,而小数点部分为 9 9的则是在接收服务器上产生行为的估计时间 (使这些在接收方的估计时间包含一秒的 99%仅与图22-2中的报文段20和22有关,它们是我们能

248

使用TCP/IP详解,卷1:协议

下载

够从tcpdump的输出结果中看到的由接收主机超时引起的仅有的两个事件。而在主机 bsdi上 观察到的其他分组,则是由接收到来自发送方的一个报文段所引起的。这同样是有意义的,因 为这就使我们可以将最初的 4秒暂停刚好放置在发送方发送第 1个数据报文段的时间 0前面。这 是接收方在连接建立过程中收到它的 SYN的ACK之后将要获得控制权的大致时间)。

图22-2 显示接收方避免出现糊涂窗口综合症的时间系列

当接收到来自发送方的数据时,接收方缓存中的数据增加,而当应用进程从缓存中读取 数据时,数据就减少。接下来我们关注的是接收方发给发送方的窗口通告以及这些窗口通告 是什么。这样就可以使我们看到接收方是如何避免糊涂窗口综合症的。 前4个数据报文段及其 ACK(报文段1~5)表示发送方正在填充接收方的缓存。在那个时刻 发送方停止了发送,但仍然有更多的数据需要发送。它将自己的坚持定时器置为最小值 5分钟。 当坚持定时器时间到时,就发送出 1个字节的数据(报文段 6)。接收的应用进程已经从接 收缓存中读取了 2 5 6字节的数据(在时刻 3 . 9 9),因此这个字节被接受并被确认(报文段 7段)。 但是通告窗口仍为 0,由于接收方仍然没有足够的空间来接收一个满长度的报文,或者不能腾

249

第22章 TCP的坚持定时器使用

下载

出缓存空间的一半。这就是接收方的糊涂窗口避免措施。 时间

报文段号 (图22-2)

发送TCP

行 为 接收TCP

应用

接收缓冲区 可用的 数据

读取256 读取256 读取256 读取256 读取256 读取256

读取256 读取256 读取256 读取256 读取256

读取256 读取256 读取256 读取256 读取256 读取256 读取256 读取256 读取256 读取256 读取256 读取256 读取256 读取256(EOF)

图22-3 接收方避免出现糊涂窗口综合症的事件序列

发送方的坚持定时器被复位,并在 5秒后再次到时(在时刻 10.151)。然后又发送一个字节 并被确认(报文段 8和9),而接收方的缓存空间还不够用( 1022字节),使得通告窗口为0。 发送方的坚持定时器在时刻 1 5 . 1 5 1再次时间到,又发送了另一个字节并被确认(报文段 1 0和11)。这一次由于接收方有 1 5 3 3字节的有效缓存空间,因此通告了一个非 0窗口。发送方 立即利用这个窗口发送了 1024字节的数据(报文段12)。对这1024字节数据的确认(报文段 13) 通告其窗口为509字节。这看起来与我们在前面看到的小窗口通告相抵触。 在这里之所以发生这种情况,是因为报文段 11段通告了一个大小为 1 5 3 3字节的窗口,而 发送方只使用了其中的 1024字节。如果在报文段 13中的ACK通告其窗口为0,就会违反窗口的 右边沿不能向左边沿移动而导致窗口收缩的 T C P原则(见第 2 0 . 3节)。这就是为什么必须通告

250

使用TCP/IP详解,卷1:协议

下载

一个509字节的窗口的原因。 接下来我们看到发送方没有立即向这个小窗口发送数据。这就是发送方采取的糊涂窗口 避免策略。相反,它等待另一个坚持定时器在时刻 2 0 . 1 5 1到时间,并在该时刻发送 5 0 9字节的 数据。尽管它最终还是发送了一个长度为 5 0 9字节的小数据段,但在发送前它等待了 5秒钟, 看是否会有一个 ACK到达,以便可以将窗口开得更大。这 509字节的数据使得接收缓存仅剩下 768字节的有效空间,因此接收方通告窗口为 0(报文段15)。 坚持定时器在时刻 2 5 . 1 5 1再次到时间,发送方发送 1个字节,于是接收缓存中有 1 2 7 9字节 的可用空间,这就是在报文段 17所通告的窗口大小。 发送方只有另外的 5 11个字节的数据需要发送,因此在收到 1 2 7 9的窗口通告后立刻发送了 这些数据(报文段 1 8)。这个报文段也带有 F I N标志。接收方确认数据和 F I N,并通告窗口大 小为767(见习题22.2)。 由于发送应用进程在执行完 6个1 0 2 4字节的写操作后发出关闭命令,发送方的连接从 E S TA B L I S H E D状态转变到 FIN_WAIT_1状态,再到 FIN_WAIT_2状态(见图 1 8 - 1 2)。它一直 处于这个状态,直到收到对方的 F I N。在这个状态上没有设置定时器(回忆我们在 1 8 . 6节结束 时的讨论),因为它在报文段 1 8中发送的 F I N被报文段 1 9确认。这就是为什么我们看到发送方 直到接收到 FIN(报文段21)为止没有发送其他任何数据的原因。 接收应用进程继续每隔 2秒从接收缓存区中读取 2 5 6个字节的数据。为什么在时刻 3 9 . 9 9发 送A C K(报文段 2 0)呢?这是因为应用进程在时刻 3 9 . 9 9读取数据时,接收缓存中的可用空间 已经从原来通告的 767(报文段 19)变为2816,这相当于接收缓存中增加了额外的 2049字节的 空间。回忆本节开始讲的第 1个规则,因为现在接收缓存已经增加了其空间的一半,因此接收 方现在发送窗口更新。这意味着每次当应用进程从 TCP的接收缓存中读取数据时,接收的 TCP 将检查是否需要更新发送窗口。 应用进程在时间 5 1 . 9 9发出最后一个读操作,然后收到一个文件结束标志,因为缓存已经 变空。这就导致了最后两个完成连接终止的报文段(报文段 21和22)的发送。

22.4 小结 在连接的一方需要发送数据但对方已通告窗口大小为0时,就需要设置TCP的坚持定时器。发送 方使用与第21章类似的重传间隔时间,不断地探查已关闭的窗口。这个探查过程将一直持续下去。 当运行一个例子来观察坚持定时器时,我们还观察到了 T C P的避免出现糊涂窗口综合症 的现象。这就是使 T C P避免通告小的窗口大小或发送小的报文段。在我们的例子中,可以观 察到发送方和接收方为避免糊涂窗口综合症所使用的策略。

习题 22.1 在图22-3中注意到所有确认(报文段5、7、9、11、13、15和17)的发送时刻为:0.170, 5.170、 10.170、15.170、20.170和25.170,还注意到在接收数据和发送 ACK之间的时间差分别为: 164.5、18.5、18.7、18.8、198.3、18.5和19.1 ms。试解释可能会发生的情况。 22.2 在图2 2 - 3中的时刻 2 5 . 1 7 4,发送出一个 7 6 7字节的通告窗口,而在接收缓存中有 7 6 8字节 的可用空间。为什么相差 1个字节?

下载

第23章 TCP的保活定时器 23.1 引言 许多T C P / I P的初学者会很惊奇地发现可以没有任何数据流通过一个空闲的 T C P连接。也 就是说,如果TCP连接的双方都没有向对方发送数据,则在两个TCP模块之间不交换任何信息。 例如,没有可以在其他网络协议中发现的轮询。这意味着我们可以启动一个客户与服务器建 立一个连接,然后离去数小时、数天、数个星期或者数月,而连接依然保持。中间路由器可 以崩溃和重启,电话线可以被挂断再连通,但是只要两端的主机没有被重启,则连接依然保 持建立。 这意味着两个应用进程—客户进程或服务器进程—都没有使用应用级的定时器来检 测非活动状态,而这种非活动状态可以导致应用进程中的任何一个终止其活动。回想在第 10.7节末尾曾提到过的 BGP每隔30秒就向对端发送一个应用的探查,就是独立于 TCP的保活定 时器之外的应用定时器。 然而,许多时候一个服务器希望知道客户主机是否崩溃并关机或者崩溃又重新启动。许 多实现提供的保活定时器可以提供这种能力。 保活并不是TCP规范中的一部分。Host Requirements RFC提供了3个不使用保活定 时器的理由: (1) 在出现短暂差错的情况下,这可能会使一个非常好的连接释放掉; (2)它们耗费不必要的带宽;(3)在按分组计费的情况下会在互联网上花掉更多的钱。 然而,许多实现提供了保活定时器。 保活定时器是一个有争论的功能。许多人认为如果需要,这个功能不应该在 T C P中提供, 而应该由应用程序来完成。这是应当认真对待的一些问题之一,因为在这个论题上有些人表 达出了很大的热情。 在连接两个端系统的网络出现临时故障的时候,保活选项会引起一个实际上很好的连接 终止。例如,如果在一个中间路由器崩溃并重新启动时发送保活探查,那么 T C P会认为客户 的主机已经崩溃,而实际上所发生的并非如此。 保活功能主要是为服务器应用程序提供的。服务器应用程序希望知道客户主机是否崩溃, 从而可以代表客户使用资源。许多版本的 Rlogin和Telnet服务器默认使用这个选项。 一个说明现在需要使用保活功能的常见例子是当个人计算机用户使用 T C P / I P向一个使用 Telnet的主机注册时。如果在一天结束时,他们仅仅关闭了电源而没有注销,那么便会留下一 个半开放的连接。在图 18-16中,我们看到通过一个半开放连接发送数据会导致返回一个复位, 但那是在来自正在发送数据的客户端。如果客户已经消失了,使得在服务器上留下一个半开 放连接,而服务器又在等待来自客户的数据,则服务器将永远等待下去。保活功能就是试图 在服务器端检测到这种半开放的连接。

252

使用TCP/IP详解,卷1:协议

下载

23.2 描述 在这个描述中,我们称使用保活选项的一端为服务器,而另一端则为客户。并没有什么 使客户不能使用这个选项,但通常都是服务器设置这个功能。如果双方都特别需要了解对方 是否已经消失,则双方都可以使用这个选项(在 29章我们将看到NFS使用TCP时,客户和服务 器都设置了这个选项。但在第 2 6章讲到Te l n e t和R l o g i n时,只有服务器设置了这个选项,而客 户则没有)。 如果一个给定的连接在两个小时之内没有任何动作,则服务器就向客户发送一个探查报 文段(我们将在随后的例子中看到这个探查报文段看起来像什么)。客户主机必须处于以下 4 个状态之一。 1) 客户主机依然正常运行,并从服务器可达。客户的 T C P响应正常,而服务器也知道对 方是正常工作的。服务器在两小时以后将保活定时器复位。如果在两个小时定时器到时间之 前有应用程序的通信量通过此连接,则定时器在交换数据后的未来 2小时再复位。 2) 客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的 T C P都 没有响应。服务器将不能够收到对探查的响应,并在 7 5秒后超时。服务器总共发送 1 0个这样 的探查,每个间隔 7 5秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止 连接。 3) 客户主机崩溃并已经重新启动。这时服务器将收到一个对其保活探查的响应,但是这 个响应是一个复位,使得服务器终止这个连接。 4) 客户主机正常运行,但是从服务器不可达。这与状态 2相同,因为T C P不能够区分状态 4与状态2之间的区别,它所能发现的就是没有收到探查的响应。 服务器不用关注客户主机被关闭和重新启动的情况(这指的是一个操作员的关闭,而不 是主机崩溃)。当系统被操作员关闭时,所有的应用进程也被终止(也就是客户进程),这会 使客户的 T C P在连接上发出一个 F I N。接收到 F I N将使服务器的 T C P向服务器进程报告文件结 束,使服务器可以检测到这个情况。 在第1种情况下,服务器的应用程序没有感觉到保活探查的发生。 T C P层负责一切。这个 过程对应用程序都是透明的,直至第 2、3或4种情况发生。在这三种情况下,服务器应用程序 将收到来自它的 T C P的差错报告(通常服务器已经向网络发出了读操作请求,然后等待来自 客户的数据。如果保活功能返回一个差错,则该差错将作为读操作的返回值返回给服务器)。 在第2种情况下,差错是诸如“连接超时”之类的信息,而在第 3种情况则为“连接被对方复 位”。第4种情况看起来像是连接超时,也可根据是否收到与连接有关的 I C M P差错来返回其他 的差错。在下一节中我们将观察这 4种情况。 一个被人们不断讨论的关于保活选项的问题就是两个小时的空闲时间是否可以改 变。通常他们希望该数值可以小得多,处在分钟的数量级。正如我们在附录 E看到的, 这个值通常可以改变,但是在该附录所描述的所有系统中,保活间隔时间是系统级的 变量,因此改变它会影响到所有使用该功能的用户。 Host Requirements RFC提到一个实现可提供保活的功能,但是除非应用程序指明 要这样,否则就不能使用该功能。而且,保活间隔必须是可配置的,但是其默认值必 须不小于两个小时。

253

第23章 TCP的保活定时器使用

下载 23.3 保活举例

现在详细讨论前一节提到的第 2、3和4种情况。我们将在使用这个选项的情况下检查所交 换的分组。 23.3.1 另一端崩溃 首先观察另一端崩溃且没有重新启动的情况下所发生的现象。为模拟这种情况,我们采 用如下步骤: • 在客户(主机 b s d i上运行的 s o c k程序)和主机 s v r 4上的标准回显服务器之间建立一 个连接。客户使用 -K选项使能保活功能。 • 验证数据可以通过该连接。 • 观察客户TCP每隔2小时发送保活分组,并观察被服务器的 TCP确认。 • 将以太网电缆从服务器上拔掉直到这个例子完成,这会使客户认为服务器主机已经崩 溃。 • 我们预期服务器在断定连接已中断前发送 10个间隔为75秒的保活探查。 这里是客户端的交互输出结果: bsdi % s o c k - K s v r 4 e c h o hello, world hello, world

-K是保活选项 开始时键入本行以验证连接有效 和看到回显 4小时后断开以太网电缆 r e a d e r r o r : C o n n e c t i o n t i m e d o u t这发生在启动后约 6小时1 0分钟

图23-1显示的是 tcpdump的输出结果(已经去掉了连接建立和窗口通告)。

图23-1 决定一个主机已经崩溃的保活分组

客户在第1、2和3行向服务器发送“ Hello, world”并得到回显。第 4行是第一个保活探查, 发生在两个小时以后( 7 2 0 0秒)。在第 6行的T C P报文段能够发送之前,首先观察到的是一个 ARP请求和一个 ARP应答。第6行的保活探查引出来自另一端的响应(第 7行)。两个小时以后, 在第7和8行发生了同样的分组交换过程。

254

使用TCP/IP详解,卷1:协议

下载

如果能够观察到第 6和第1 0行的保活探查中的所有字段,我们就会发现序号字段比下一个 将要发送的序号字段小 1(在本例中,当下一个为 1 4时,它就是 1 3)。但是因为报文段中没有 数据, t c p d u m p不能打印出序号字段(它仅能够打印出设置了 S Y N、F I N或R S T标志的空数 据的序号)。正是接收到这个不正确的序号,才导致服务器的 T C P对保活探查进行响应。这个 响应告诉客户,服务器下一个期望的序号是 14。 一些基于4.2BSD的旧的实现不能够对这些保活探查进行响应,除非报文段中包含 数据。某些系统可以配置成发送一个字节的无用数据来引出响应。这个无用数据是无 害的,因为它不是所期望的数据(这是接收方前一次接收并确认的数据),因此它会被 接收方丢弃。其他一些系统在探查的前半部分发送4.3BSD格式的报文段(不包含数据) , 如果没有收到响应,在后半部分则切换为4.2BSD格式的报文段。 接着我们拔掉电缆,并期望两个小时的再一次探查失败。当这下一个探查发生时,注意 到从来没有看到电缆上出现 T C P报文段,这是因为主机没有响应 A R P请求。在放弃之前,我 们仍可以观察到客户每隔 7 5秒发送一个探查,一共发送了 1 0次。从交互式脚本可以看到返回 给客户进程的差错码被 TCP转换为“连接超时”,这正是实际所发生的。 23.3.2 另一端崩溃并重新启动 在这个例子中,我们可以观察到当客户崩溃并重新启动时发生的情况。最初的环境与前 一个例子相似,但是在我们验证连接有效之后,我们将服务器从以太网上断开,重新启动, 然后再连接到网络上。我们希望看到下一个保活探查产生一个来自服务器的复位,因为现在 服务器不知道关于这个连接的任何信息。这是交互会话的过程: bsdi % s o c k - K s v r 4 e c h o hi, there hi, there

-K使保活选项有效 键入这行以验证连接有效 这是来自另一端的回显 从以太网断连后,服务器这时重新启动 read error: Connection reset by peer

图23-2显示的是tcpdump的输出结果(已经去掉了连接建立和窗口通告)。

图23-2 另一端崩溃并重启时保活的例子

我们建立了连接,并从客户发送 9个字节的数据到服务器(第 1 ~ 3行)。两个小时之后,客 户发送第 1个保活探查,其响应是一个来自服务器的复位。客户应用进程打印出“连接被对端 复位”的差错,这是有意义的。 23.3.3 另一端不可达 在这个例子中,客户没有崩溃,但是在保活探查发送后的 1 0分钟内无法到达,可能是一 个中间路由器已经崩溃,或一条电话线临时出现故障,或发生了其他一些类似的情况。

255

第23章 TCP的保活定时器使用

下载

为了仿真这个例子,我们从主机 s l i p 经过一个拨号 S L I P链路与主机 v a n g o g h . c s . berkeley.edu建立一个连接,然后断掉链路。这里是交互输出的结果: bsdi % s o c k - K v a n g o g h . c s . b e r k e l e y . e d u e c h o testing 我们键入这行 testing 看到这行的回显 在某个时刻这条 S L I P链路被断开 read error: No route to host

图23-3显示了在路由器bsdi上收集到的tcpdump输出结果(已经去掉了连接建立和窗口通告) 。

删除14行

图23-3 当另一端不可达时的保活例子

我们与以前一样开始讨论这个例子:第 1 ~ 3行证实连接是有效的。两个小时之后的第 1个 保活探查是正常的(第 4、5行),但是在两个小时后发生下一个探查之前,我们断开在路由器 sun和netb之间的SLIP连接(拓扑结构参见封)。 第6行的保活探查引发一个来自路由器 s u n的I C M P网络不可达的差错。正如我们在第 21.10节描述的那样,对于主机 s l i p上接收的 TCP而言,这只是一个软差错。它报告收到了一 个I C M P差错,但是差错的接收者并没有终止这个连接。在发送主机最终放弃之前,一共发送 了9个保活探查,间隔为 7 5秒。这时返回给应用进程的差错产生了一个不同的报文:“没有到 达主机的路由”。我们在图 6-12看到这对应于ICMP网络不可达的差错。

23.4 小结 正如我们在前面提到的,对保活功能是有争议的。协议专家继续在争论该功能是否应该 归入运输层,或者应当完全由应用层来处理。 在连接空闲两个小时后,在一个连接上发送一个探查分组来完成保活功能。可能会发生 4 种不同的情况:对端仍然运行正常、对端已经崩溃、对端已经崩溃并重新启动以及对端当前无 法到达。我们使用一个例子来观察每一种情况,并观察到在最后三个条件下返回的不同差错。 在前两个例子中,如果没有提供这种功能,并且也没有应用层的定时器,则客户将永远 无法知道对端已经崩溃或崩溃并重新启动。可是在最后一个例子中,两端都没有发生差错, 只是它们之间的连接临时中断。我们在使用保活时必须关注这个限制。

习题 23.1 列出保活功能的一些优点。 23.2 列出保活功能的一些缺点。

下载

第24章 TCP的未来和性能 24.1 引言 TCP已经在从1200 b/s的拨号SLIP链路到以太数据链路上运行了许多年。在 80年代和90年 代初期,以太网是运行 T C P / I P最主要的数据链路方式。虽然 T C P在比以太网速率高的环境 (如T 2电话线、 F D D I及千兆比网络)中也能够正确运行,但在这些高速率环境下, T C P的某 些限制就会暴露出来。 本章讨论 T C P的一些修改建议,这些建议可以使 T C P在高速率环境中获得最大的吞吐量。 首先要讨论前面已经碰到过的路径 M T U发现机制,本章主要关注它如何与 T C P协同工作。这 个机制通常可以使 TCP为非本地的连接使用大于 536字节的MTU,从而增加吞吐量。 接着介绍长肥管道 (long fat pipe),也就是那些具有很大的带宽时延乘积的网络,以及 TCP 在这些网络上所具有的局限性。为处理长肥管道,我们描述两个新的 T C P选项:窗口扩大选 项(用来增加 T C P的最大窗口,使之超过 6 5 5 3 5字节)和时间戳选项。后面这个选项可以使 T C P对报文段进行更加精确的 RT T测量,还可以在高速率下对可能发生的序号回绕提供保护。 这两个选项在RFC 1323 [Jacobson, Braden, and Borman 1992]中进行定义。 我们还将介绍建议的 T/TCP,这是为增加事务功能而对 TCP进行的修改。通信的事务模式 以客户的请求将被服务器应答的响应为主要特征。这是客户服务器计算的常见模型。 T / T C P 的目的就是减少两端交换的报文段数量,避免三次握手和使用 4个报文段进行连接的关闭,从 而使客户可以在一个 RTT和处理请求所必需的时间内收到服务器的应答。 这些新选项(路径 M T U发现、窗口扩大选项、时间戳选项和 T / T C P)中令人印象最深刻 的就是它们与现有的 T C P实现能够向后兼容,即包括这些新选项的系统仍然可以与原有的旧 系统进行交互。除了在一个 ICMP报文中为路径MTU发现增加了一个额外字段之外,这些新的 选项只需要在那些需要使用它们的端系统中进行实现。 我们以介绍近来发表的有关 TCP性能的图例作为本章的结束。

24.2 路径MTU发现 在2 . 9节我们描述了路径 M T U的概念。这是当前在两个主机之间的路径上任何网络上的最 小M T U。路径 M T U发现在 I P首部中继承并设置“不要分片( D F)”比特,来发现当前路径上 的路由器是否需要对正在发送的 IP数据报进行分片。在 11.6节我们观察到如果一个待转发的 IP 数据报被设置 D F比特,而其长度又超过了 M T U,那么路由器将返回 I C M P不可达的差错。在 11 . 7节我们显示了某版本的 t r a c e r o u t e程序使用该机制来决定目的地的路径 M T U。在11 . 8 节我们看到 U D P是怎样处理路径 M T U发现的。在本节我们将讨论这个机制是如何按照 R F C 1191 [Mogul and Deering 1990]中规定的那样在 TCP中进行使用的。 在本书的多种系统(参看序言)中只有Solaris 2.x支持路径MTU发现。

257

第24章 TCP的未来和性能使用

下载

T C P的路径 M T U发现按如下方式进行:在连接建立时, T C P使用输出接口或对端声明的 M S S中的最小 M T U作为起始的报文段大小。路径 M T U发现不允许 T C P超过对端声明的 M S S。 如果对端没有指定一个 M S S,则默认为 5 3 6。一个实现也可以按 2 1 . 9节中讲的那样为每个路由 单独保存路径MTU信息。 一旦选定了起始的报文段大小,在该连接上的所有被 T C P发送的 I P数据报都将被设置 D F 比特。如果某个中间路由器需要对一个设置了 D F标志的数据报进行分片,它就丢弃这个数据 报,并产生一个我们在 11.6节介绍的ICMP的“不能分片”差错。 如果收到这个 I C M P差错, T C P就减少段大小并进行重传。如果路由器产生的是一个较新 的该类I C M P差错,则报文段大小被设置为下一跳的 M T U减去I P和T C P的首部长度。如果是一 个较旧的该类 I C M P差错,则必须尝试下一个可能的最小 M T U(见图2 - 5)。当由这个 I C M P差 错引起的重传发生时,拥塞窗口不需要变化,但要启动慢启动。 由于路由可以动态变化,因此在最后一次减少路径 M T U的一段时间以后,可以尝试使用 一个较大的值(直到等于对端声明的 M S S或输出接口 M T U的最小值)。RFC 11 9 1推荐这个时 间间隔为10分钟(我们在11.8节看到Solaris 2.2使用一个30分钟的时间间隔)。 在对非本地目的地,默认的 M S S通常为 5 3 6字节,路径 M T U发现可以避免在通过 M T U小 于5 7 6(这非常罕见)的中间链路时进行分片。对于本地目的主机,也可以避免在中间链路 (如以太网)的 M T U小于端点网络(如令牌环网)的情况下进行分片。但为了能使路径 M T U 更加有用和充分利用 M T U大于5 7 6的广域网,一个实现必须停止使用为非本地目的制定的 5 3 6 的MTU默认值。MSS的一个较好的选择是输出接口的 MTU(当然要减去IP和TCP的首部大小) (在附录E中,我们将看到大多数的实现都允许系统管理员改变这个默认的 MSS值)。 24.2.1 一个例子 在某个中间路由器的 MTU比任一个端点接口 MTU小的情况下,我们能够观察路径 MTU发 现是如何工作的。图 24-1显示了这个例子的拓扑结构。 512字节的报文 段在此处分片

此处运行 tcpdump

TCP连接

图24-1 路径MTU例子的拓扑结构

我们从主机 s o l a r i s(支持路径 M T U发现机制)到主机 s l i p建立一个连接。这个建立 过程与 U D P的路径 M T U发现(图 11 - 1 3)中的一个例子相同,但在这里我们已经把 s l i p接口 的M T U设置为 5 5 2,而不是通常的 2 9 6。这使得 s l i p通告一个 5 1 2的M S S。但是在 b s d i上的 S L I P 链路上的 M T U 为2 9 6 ,这就引起超过 2 5 6 的 T C P 报文段被分片。于是就可以观察在 solaris上的路径MTU发现是如何进行处理的。 我们在solaris上运行sock程序并向slip上的丢弃服务器进行一个 512字节的写操作:

258

使用TCP/IP详解,卷1:协议

下载

s o l a r i s %s o c k - i - n 1 - w 5 1 2 s l i p d i s c a r d

图24-2是在主机sun的SLIP接口上收集的tcpdump的输出结果。

图24-2 路径MTU发现的tcpdump 输出结果

在第1和第2行的M S S值是我们所期望的。接着我们观察到 s o l a r i s发送一个包含 5 1 2字节的 数据和对 S Y N的确认报文段(第 3行)(在习题 1 8 . 9中可以看到这种把 S Y N的确认与第一个包 含数据的报文段合并的情况)。这就在第 4行产生了一个 I C M P差错,我们看到路由器 b s d i产 生较新的、包含输出接口 MTU的ICMP差错。 看来在这个差错回到 s o l a r i s之前,就发送了 F I N(第5行)。由于s l i p从没有收到被路由 器b s d i丢弃的 5 1 2字节的数据,因此并不期望接收这个序号( 5 1 3),所以在第 6行用它期望的 序号(1)进行了响应。 在这个时候, I C M P差错返回到了 s o l a r i s,s o l a r i s用两个 2 5 6字节的报文段(第 7和 第9行)重传了 512字节的数据。因为在 b s d i后面可能还有具有更小的 MTU的路由器,因此这 两个报文段都设置了 DF比特。 接着是一个较长的传输过程(持续了大约 1 5分钟),在最初的 5 1 2字节变为 2 5 6字节以后, solaris没有再尝试使用更大的报文段。 24.2.2 大分组还是小分组 常规知识告诉我们较大的分组比较好 [Mogul 1993, 15.2.8节],因为发送较少的大分组比 发送较多的小分组“花费”要少(假定分组的大小不足以引起分片,否则会引起其他方面的 问题)。这些减少的花费与网络(分组首部负荷)、路由器(选路的决定)和主机(协议处理 和设备中断)等有关。但并非所有的人都同意这种观点 [Bellovin 1993]。 考虑下面的例子。我们通过 4个路由器发送 8 1 9 2个字节,每个路由器与一个 T 1电话线( 1 544 000b/s)相连。首先我们使用两个 4096字节的分组,如图 24-3所示。 基本问题在于路由器是存储转发设备。它们通常接收整个输入分组,检验包含 I P检验和 的I P首部,进行选路判决,然后开始发送输出分组。在这个图中,我们可以假定在理想情况 下这些在路由器内部进行的操作不花费时间(水平点状线)。然而,从R 1到R 4它需要花费 4个

259

第24章 TCP的未来和性能使用

下载 单位时间来发送所有的 8192字节。每一跳的时间为

图24-3 通过4个路由器发送两个4096字节的分组

(4096字节+40字节)×8 b/字节 =21.4 ms/跳 1 544 000 b/s (将T C P和I P的首部算为 4 0字节)。发送数据的整个时间为分组个数加上跳数减 1,从图中 可以看到是 4个单位时间,或 85.6秒。每个链路空闲 2个单位时间,或 42.8秒。 图24-4显示了当我们发送 16个512字节的分组时所发生的情况。

图24-4 通过4个路由器发送16个512字节的分组

这将花费更多的单位时间,但是由于发送的分组较短,因此每个单位时间较小。 (512字节+40字节)×8 b/字节 =2.9 ms/跳 1 544 000 b/s 现在总时间为( 18×2.9)=52.2 ms。每个链路也空闲 2个单位的时间,即 5.8 ms。 在这个例子中,我们忽略了确认返回所需要的时间、连接建立和终止以及链路可能被其 他流量共享等的影响。然而,在 [Bellovin 1993]中的测量表明,分组并不一定是越大越好。我 们需要在更多的网络上对该领域进行更多的研究。

24.3 长肥管道 在20.7节,我们把一个连接的容量表示为 capacity (b) = bandwidth (b/s) × round-trip time (s) 并称之为带宽时延乘积。也可称它为两端的管道大小。 当这个乘积变得越来越大时, TCP的某些局限性就会暴露出来。图 24-5显示了多种类型的 网络的某些数值。

260

使用TCP/IP详解,卷1:协议

网 络

下载 带宽(b/s)

RTT(ms)

带宽时延乘积 (字节)

以太局域网 横跨大陆的T1电话线 卫星T1电话线 横跨大陆的T3电话线 横跨大陆的gigabit线路

图24-5 多种网络的带宽时延乘积

可以看到带宽时延乘积的单位是字节,这是因为我们用这个单位来测量每一端的缓存大小和 窗口大小。 具有大的带宽时延乘积的网络被称为长肥网络( Long Fat Network ,即 L F N,发音为 “e l e f a n ( t ) s”),而一个运行在 L F N上的T C P连接被称为长肥管道。回顾图 2 0 - 11和图2 0 - 1 2,管 道可以被水平拉长(一个长的 RT T),或被垂直拉高(较高的带宽),或向两个方向拉伸。使 用长肥管道会遇到多种问题。 1) T C P首部中窗口大小为 16 bit,从而将窗口限制在 6 5 5 3 5个字节内。但是从图 2 4 - 5的最 后一列可以看到,现有的网络需要一个更大的窗口来提供最大的吞吐量。在 2 4 . 4节介 绍的窗口扩大选项可以解决这个问题。 2) 在一个长肥网络 L F N内的分组丢失会使吞吐量急剧减少。如果只有一个报文段丢失, 我们需要利用 2 1 . 7节介绍的快速重传和快速恢复算法来使管道避免耗尽。但是即使使 用这些算法,在一个窗口内发生的多个分组丢失也会典型地使管道耗尽(如果管道耗 尽了,慢启动会使它渐渐填满,但这个过程将需要经过多个 RTT)。 在RFC 1072 [Jacobson and Braden 1988]中建议使用有选择的确认( SACK)来处理在 一个窗口发生的多个分组丢失。但是这个功能在 RFC 1323中被忽略了,因为作者觉得 在把它们纳入TCP之前需要先解决一些技术上的问题。 3) 我们在第 21.4节看到许多TCP实现对每个窗口的 RTT仅进行一次测量。它们并不对每个 报文段进行 RT T测量。在一个长肥网络 L F N上需要更好的 RT T测量机制。我们将在 2 4 . 5 节介绍时间戳选项,它允许更多的报文段被计时,包括重传。 4) TCP对每个字节数据使用一个32 bit无符号的序号来进行标识。如果在网络中有一个被延迟 一段时间的报文段,它所在的连接已被释放,而一个新的连接在这两个主机之间又建立了, 怎样才能防止这样的报文段再次出现呢?首先回想起IP首部中的TTL为每个IP段规定了一 个生存时间的上限—255跳或255秒,看哪一个上限先达到。在18.6节我们定义了最大的 报文段生存时间(MSL)作为一个实现的参数来阻止这种情况的发生。推荐的MSL的值为 2分钟(给出一个240秒的2MSL) ,但是我们在18.6节看到许多实现使用的MSL为30秒。 在长肥网络 L F N上,T C P的序号会碰到一个不同的问题。由于序号空间是有限的,在 已经传输了 4 294 967 296个字节以后序号会被重用。如果一个包含序号 N字节数据的报 文段在网络上被迟延并在连接仍然有效时又出现,会发生什么情况呢?这仅仅是一个 相同序号 N在M S L期间是否被重用的问题,也就是说,网络是否足够快以至于在不到 一个M S L的时候序号就发生了回绕。在一个以太网上要发送如此多的数据通常需要 6 0 分钟左右,因此不会发生这种情况。但是在带宽增加时,这个时间将会减少:一个 T 3 的电话线(45 Mb/s)在1 2分钟内会发生回绕, F D D I(100 Mb/s)为5分钟,而一个千 兆比网络( 1000 Mb/s)则为34秒。这时问题不再是带宽时延乘积,而在于带宽本身。

261

第24章 TCP的未来和性能使用

下载

在2 4 . 6节,我们将介绍一种对付这种情况的办法:使用

T C P 的时间戳选项的 PAW S

(Protection Against Wrapped Sequence numbers)算法(保护回绕的序号)。 4.4BSD包含了我们将要在下面介绍的所有选项和算法:窗口扩大选项、时间戳选 项和保护回绕的序号。许多供应商也正在开始支持这些选项。 千兆比网络 当网络的速率达到千兆比的时候,情况就会发生变化。 [Partridge 1994]详细介绍了千兆比 网络。在这里我们看一下在时延和带宽之间的差别 [Kleinrock 1992]。 考虑通过美国发送一个 1 0 0万字节的文件的情况,假定时延为 30 ms。图2 4 - 6显示了两种 情况:上图显示了使用一个 T1电话线(1 544 000 b/s)的情况,而下图则是使用一个 1 Gb/s网 络的情况。 x轴显示的是时间,发送方在图的左侧,而接收方则在图的右侧, y轴为网络容量。 两幅图中的阴影区域表示发送的 100万字节。 仍然等待发送的994 210字节

文件传输的方向 线路中的5 790字节 1 544 000 b/s 30 ms时延 8 ms

1Gb/s

1 000 000字节

图24-6 以30 ms的延时通过网络发送100万字节的文件

图24-6显示了30 ms后这两个网络的状态。经过 30 ms(延时)以后数据的第 1个比特都已到达 对端。但对 T 1网络而言,由于管道容量仅为 5 790 字节,因此发送方仍然有 994 210个字节等 待发送。而千兆比网络的容量则为 3 750 000字节,因此,整个文件仅使用了 25%左右的带宽, 此时文件的最后一个比特已经到达第 1个字节后8 ms处。 经过T1网络传输文件的总时间为5.211秒。如果增加更多的带宽,使用一个T3网络(45 000 000 b/s) , 则总时间减少到0.208秒。增加约29倍的带宽可以将总时间减小到约25分之一。 使用千兆比网络传输文件的总时间为 0.038秒:30 ms的时延加上 8 ms的真正传输文件的时 间。假定能够将带宽增加为 2000 Mb/s,我们只能够将总时间减小为 0.304 ms:同样30 ms 的时 延和4ms的真正传输时间。现在使带宽加倍仅能够将时间减少约 10%。在千兆比速率下,时延 限制占据了主要地位,而带宽不再成为限制。 时延主要是由光速引起的,而且不能够被减小(除非爱因斯坦是错误的)。当我们考虑到 分组需要建立和终止一个连接时,这个固定时延起的作用就更糟糕了。千兆比网络会引起一 些需要不同看待的连网观点。

262

使用TCP/IP详解,卷1:协议

下载

24.4 窗口扩大选项 窗口扩大选项使 TCP的窗口定义从16 bit增加为32 bit。这并不是通过修改 TCP首部来实现 的, T C P首部仍然使用 16 bit ,而是通过定义一个选项实现对 16 bit 的扩大操作 ( s c a l i n g operation)来完成的。于是 TCP在内部将实际的窗口大小维持为 32 bit的值。 在图1 8 - 2 0可以看到关于这个选项的例子。一个字节的移位记数器取值为 0(没有扩大窗 14

口的操作)和14。这个最大值14表示窗口大小为 1 073 725 440字节(65535×2 )。 这个选项只能够出现在一个 S Y N报文段中,因此当连接建立起来后,在每个方向的扩大 因子是固定的。为了使用窗口扩大,两端必须在它们的 S Y N报文段中发送这个选项。主动建 立连接的一方在其 S Y N中发送这个选项,但是被动建立连接的一方只能够在收到带有这个选 项的SYN之后才可以发送这个选项。每个方向上的扩大因子可以不同。 如果主动连接的一方发送一个非零的扩大因子,但是没有从另一端收到一个窗口扩大选 项,它就将发送和接收的移位记数器置为 0。这就允许较新的系统能够与较旧的、不理解新选 项的系统进行互操作。 Host Requirements RFC要求TCP接受在任何报文段中的一个选项(只有前面定义的一 个选项,即最大报文段大小,仅在SYN报文段中出现) 。它还进一步要求TCP忽略任何它 不理解的选项。这就使事情变得容易,因为所有新的选项都有一个长度字段(图18-20) 。 假定我们正在使用窗口扩大选项,发送移位记数为 S,而接收移位记数则为 R。于是我们 从另一端收到的每一个 16 bit的通告窗口将被左移 R位以获得实际的通告窗口大小。每次当我 们向对方发送一个窗口通告的时候,我们将实际的 32 bit 窗口大小右移 S比特,然后用它来替 换TCP首部中的16 bit的值。 T C P根据接收缓存的大小自动选择移位计数。这个大小是由系统设置的,但是通常向应 用进程提供了修改途径(我们在 20.4节中讨论了这个缓存)。 一个例子 如果在 4 . 4 B S D的主机 v a n g o g h . c s . b e r k e l e y . e d u上使用 s o c k程序来初始化一个连 接,我们可以观察到它的 T C P计算窗口扩大因子的情况。下面的交互输出显示的是两个连续 运行的程序,第 1个指定接收缓存为 128 000字节,而第 2个的缓存则为220 000字节。

我们键入这一行 此处是它的回显 键入文件结束字符以终止

我们键入这一行 此处是它的回显 键入文件结束字符以终止

图2 4 - 7显示了这两个连接的 t c p d u m p输出结果(去掉了第 2个连接的最后 8行,因为没有

263

第24章 TCP的未来和性能使用

下载 什么新内容)。

该连接的其余部分被删除

图24-7 窗口扩大选项的例子

在第 1行,v a n g o g h通告一个 6 5 5 3 5的窗口,并通过设置移位计数为 1来指明窗口扩大选项。 这个通告的窗口是比接收窗口( 128 000)还小的一个最大可能取值,因为在一个 S Y N报文段 中的窗口字段从不进行扩大运算。 1

扩大因子为1表示vangogh发送窗口通告一直到 131 070(65535×2 )。这将调节我们的接 收缓存的大小(12 8000)。因为bsdi在它的SYN(第2行)中没有发送窗口扩大选项,因此这 个选项没有被使用。注意到vangogh在随后的连接阶段继续使用最大可能的窗口( 65535)。 对于第 2个连接 v a n g o g h请求的移位计数为 2,表明它希望发送窗口通告一直为 262 140 2

(65535×2 ),这比我们的接收缓存( 220 000)大。

24.5 时间戳选项 时间戳选项使发送方在每个报文段中放置一个时间戳值。接收方在确认中返回这个数值, 从而允许发送方为每一个收到的 A C K计算 RT T(我们必须说“每一个收到的 A C K”而不是 “每一个报文段”,是因为 T C P通常用一个A C K来确认多个报文段)。我们提到过目前许多实现 为每一个窗口只计算一个 RT T,对于包含 8个报文段的窗口而言这是正确的。然而,较大的窗 口大小则需要进行更好的 RTT计算。 RFC 1323的3.1节给出了需要为较大窗口进行更好的 RTT计算的信号处理的理由。 通常RT T通过对一个数据信号(包含数据的报文段)以较低的频率(每个窗口一次) 进行采样来进行计算,这就将别名引入了被估计的 RTT中。当每个窗口中有8个报文段 时,采样速率为数据率的1/8,这还是可以忍受的。但是如果每个窗口中有100个报文段 时,采样速率则为数据速率的 1/100,这将导致被估计的RTT不精确,从而引起不必要 的重传。如果一个报文段被丢失,则会使情况变得更糟。 图1 8 - 2 0显示了时间戳选项的格式。发送方在第 1个字段中放置一个 32 bit的值,接收方在 应答字段中回显这个数值。包含这个选项的 TCP首部长度将从正常的 20字节增加为 32字节。

264

使用TCP/IP详解,卷1:协议

下载

时间戳是一个单调递增的值。由于接收方只需要回显收到的内容,因此不需要关注时间 戳单元是什么。这个选项不需要在两个主机之间进行任何形式的时钟同步。 RFC 1323推荐在1 毫秒和1秒之间将时间戳的值加 1。 4.4BSD在启动时将时间戳始终设置为0,然后每隔500 ms将时间戳时钟加1。 在图24-7中,如果观察在报文段1和报文段11的时间戳,它们之间的差(89个单元) 对应于每个单元500 ms的规定,因为实际时间差为44.4秒。 在连接建立阶段,对这个选项的规定与前一节讲的窗口扩大选项类似。主动发起连接的 一方在它的 S Y N中指定选项。只有在它从另一方的 S Y N中收到了这个选项之后,该选项才会 在以后的报文段中进行设置。 我们已经看到接收方 T C P不需要对每个包含数据的报文段进行确认,许多实现每两个报 文段发送一个 A C K。如果接收方发送一个确认了两个报文段的 A C K,那么哪一个收到的时间 戳应当放入回显应答字段中来发回去呢? 为了减少任一端所维持的状态数量,对于每个连接只保持一个时间戳的数值。选择何时 更新这个数值的算法非常简单: 1) TCP跟踪下一个A C K中将要发送的时间戳的值(一个名为 t s re c e n t的变量)以及最后发 送的ACK中的确认序号(一个名为 lastack的变量)。这个序号就是接收方期望的序号。 2) 当一个包含有字节号 l a s t a c k的报文段到达时,则该报文段中的时间戳被保存在 t s re c e n t 中。 3) 无论何时发送一个时间戳选项, t s re c e n t就作为时间戳回显应答字段被发送,而序号字 段被保存在 lastack中。 这个算法能够处理下面两种情况: 1) 如果A C K被接收方迟延,则作为回显值的时间戳值应该对应于最早被确认的报文段。 例如,如果两个包含 1 ~ 1 0 2 4和1 0 2 5 ~ 2 0 4 8字节的报文段到达,每一个都带有一个时间戳选项, 接收方产生一个 ACK 2049来对它们进行确认。此时, ACK中的时间戳应该是包含字节 1~1024 的第1个报文段中的时间戳。这种处理是正确的,因为发送方在进行重传超时时间的计算时, 必须将迟延的ACK也考虑在内。 2) 如果一个收到的报文段虽然在窗口范围内但同时又是失序,这就表明前面的报文段已 经丢失。当那个丢失的报文段到达时,它的时间戳(而不是失序的报文段的时间戳)将被回 显。例如,假定有 3个各包含1024字节数据的报文段,按如下顺序接收:包含字节 1~1024的报 文段1,包含字节 2 0 4 9 ~ 4 0 7 2的报文段 3和包含字节 1 0 2 5 ~ 2 0 4 8的报文段 2。返回的 A C K应该是 带有报文段 1的时间戳的 ACK 1025(一个正常的所期望的对数据的 A C K)、带有报文段 1的时 间戳的 ACK 1025(一个重复的、响应位于窗口内但却是失序的报文段的 A C K),然后是带有 报文段 2的时间戳的 ACK 3073(不是报文段 3中的较后的时间戳)。这与当报文段丢失时的对 RT T估计过高具有同样的效果,但这比估计过低要好些。而且,如果最后的 A C K含有来自报 文段3的时间戳,它可以包括重复的 A C K返回和报文段 2被重传所需要的时间,或者可以包括 发送方的报文段 2的重传超时定时器到期的时间。无论在哪一种情况下,回显报文段 3的时间 戳将引起发送方的 RTT计算出现偏差。 尽管时间戳选项能够更好地计算 RT T,它还为发送方提供了一种方法,以避免接收到旧 的报文段,并认为它们是现在的数据的一部分。下一节将对此进行描述。

265

第24章 TCP的未来和性能使用

下载 24.6 PAWS:防止回绕的序号

30

考虑一个使用窗口扩大选项的 T C P连接,其最大可能的窗口大小为 1千兆字节( 2 )(最 14

16

14

大的窗口是 6 5 5 3 5×2 ,而不是 2 ×2 ,但只比这个数值小一点点,并不影响这里的讨论)。 还假定使用了时间戳选项,并且由发送方指定的时间戳对每个将要发送的窗口加 1(这是保守 的方法。通常时间戳比这种方式增加得快)。图2 4 - 8显示了在传输 6 千兆字节的数据时,在两 个主机之间可能的数据流。为了避免使用许多 10位的数字,我们使用 G来表示1 073 741 824的 倍数。我们还使用了 tcpdump的记号,即用J:K来表示通过了J字节的数据,且包括字节 K-1。 时间

发送字节

发送序号 发送时间戳





正确 正确,但有一个段丢失并重发 正确 正确 正确 正确,但重发的段又出现了

图24-8 在6个1千兆字节的窗口中传输6千兆字节的数据

32 bit的序号在时间 D和时间E之间发生了回绕。假定一个报文段在时间 B丢失并被重传。还假 定这个丢失的报文段在时间 E重新出现。 这假定了在报文段丢失和重新出现之间的时间差小于 M S L,否则这个报文段在它的 T T L 到期时会被某个路由器丢弃。正如我们前面提到的,这种情况只有在高速连接上才会发生, 此时旧的报文段重新出现,并带有当前要传输的序号。 我们还可以从图 2 4 - 8中观察到使用时间戳可以避免这种情况。接收方将时间戳视为序列 号的一个 32 bit的扩展。由于在时间 E重新出现的报文段的时间戳为 2,这比最近有效的时间戳 小(5或6),因此PAWS算法将其丢弃。 PAWS算法不需要在发送方和接收方之间进行任何形式的时间同步。接收方所需要的就是 时间戳的值应该单调递增,并且每个窗口至少增加 1。

24.7 T/TCP:为事务用的TCP扩展 T C P提供的是一种虚电路方式的运输服务。一个连接的生存时间包括三个不同的阶段: 建立、数据传输和终止。这种虚电路服务非常适合诸如远程注册和文件传输之类的应用。 但是,还有出现其他的应用进程被设计成使用事务服务。一个事务 ( t r a n s a c t i o n )就是符合 下面这些特征的一个客户请求及其随后的服务器响应。 1) 应该避免连接建立和连接终止的开销,在可能的时候,发送一个请求分组并接收一个 应答分组。 2) 等待时间应当减少到等于 RTT与SPT之和。其中 RTT (Round-Trip Time)为往返时间,而 SPT (Server Processing Time)则是服务器处理请求的时间。 3) 服务器应当能够检测出重复的请求,并且当收到一个重复的请求时不重新处理事务 (避免重新处理意味着服务器不必再次处理请求,而是返回保存的、与该请求对应的应答)。 我们已经看到的一个使用这种类型服务的应用就是域名服务(第 1 4章),尽管D N S与服务 器重新处理重复的请求无关。

266

使用TCP/IP详解,卷1:协议

下载

如今一个应用程序设计人员面对的一种选择是使用 TCP还是UDP。TCP提供了过多的事务 特征,而UDP提供的则不够。通常应用程序使用UDP来构造(避免TCP连接的开销),而许多需 要的特征(如动态超时和重传、拥塞避免等)被放置在应用层,一遍又一遍的重新设计和实现。 一个较好的解决方法是提供一个能够提供足够多的事务处理功能的运输层。我们在本节 所介绍的事务协议被称为 T / T C P。我们从它的定义,即 RFC 1379 [Braden 1992b] 和[ B r a d e n 1992c],开始介绍。 大多数的 T C P需要使用 7个报文段来打开和关闭一个连接(见图 1 8 - 1 3)。现在增加三个报 文段:一个对应于请求,一个对应于应答和对请求的确认,第三个对应于对应答的确认。如 果额外的控制比特被追加到报文段上 — 也就是,第 1个报文段带有 S Y N、客户请求和一个 F I N—客户仍然能够看到一个 2倍的RT T与S P T之和的最小开销(与数据一起发送一个 S Y N 和FIN是合法的;当前的 TCP是否能够正确处理它们是另外一个问题)。 另一个与 T C P有关的问题是 T I M E _ WA I T状态和它需要的 2 M S L的等待时间。正如在习题 18.14中看到的,这使两个主机之间的事务率降低到每秒 268个。 T C P为处理事务而需要进行的两个改动是避免三次握手和缩短 WA I T _ T I M E状态。 T / T C P 通过使用加速打开来避免三次握手: 1) 它为打开的连接指定一个 32 bit的连接计数 CC (Connection Count),无论主动打开还是 被动打开。一个主机的 CC值从一个全局计数器中获得,该计数器每次被使用时加 1。 2) 在两个使用 T / T C P的主机之间的每一个报文段都包括一个新的 T C P选项C C。这个选项 的长度为6个字节,包含发送方在该连接上的 32 bit的CC值。 3) 一个主机维持一个缓存,该缓存保留每个主机上一次的 C C值,这些值从来自这个主机 的一个可接受的 SYN报文段中获得。 4) 当在一个开始的 S Y N中收到一个 C C选项的时候,接收方比较收到的值与为该发送方缓 存的 C C值。如果接收到的 C C比缓存的大,则该 S Y N是新的,报文段中的任何数据被 传递给接收应用进程(服务器)。这个连接被称为半同步。 如果接收的 CC比缓存的小,或者接收主机上没有对应这个客户的缓存 CC,则执行正常 的TCP三次握手过程。 5) 为响应一个开始的 S Y N,带有 S Y N和A C K的报文段在另一个被称为 C C E C H O的选项中 回显所接收到的 CC值。 6) 在一个非 S Y N报文段中的 C C值检测和拒绝来自同一个连接的前一个替身的任何重复的 报文段。 这种“加速打开”避免了使用三次握手的要求,除非客户或者服务器已经崩溃并重新启 动。这样做的代价是服务器必须记住从每个客户接收的最近的 CC值。 基于在两个主机之间测量 RT T来动态计算 T I M E _ WA I T的延时,可以缩短 T I M E _ WA I T状 态。TIME_WAIT时延被设置为8倍的重传超时值 RTO(见21.3节)。 通过使用这些特征,最小的事务序列是交换三个报文段: 1) 由一个主动打开引起的客户到服务器:客户的 S Y N、客户的数据(请求)、客户的 F I N 以及客户的CC。当被动的服务器 TCP接收到这个报文段的时候,如果客户的 CC比为这 个客户缓存的CC要大,则客户的数据被传送给服务器应用程序进行处理。 2) 服务器到客户:服务器的 S Y N、服务器的数据(应答)、服务器的 F I N、对客户的 F I N

下载

267

第24章 TCP的未来和性能使用

的A C K、服务器的 C C以及客户的 C C的C C E C H O。由于 T C P的确认是累积的,这个对 客户的FIN的ACK也对客户的 SYN、数据及FIN进行了确认。 当客户TCP接收到这个报文段,就将其传送给客户应用进程。 3) 客户到服务器:对服务器的 FIN的ACK,它也确认了服务器的 SYN、数据和FIN。 客户对它的请求的响应时间为 RTT与SPT的和。 在参考资料中有许多关于实现这个 TCP选项的很好的地方。我们在这里将它们归纳如下: • 服务器的 S Y N和A C K(第 2个报文段)必须被迟延,从而允许应答与它一起捎带发送 (通常对 S Y N的A C K是不迟延的)。但它也不能迟延得太多,否则客户将超时并引起重 传。 • 请求可以需要多个报文段,但是服务器必须对它们可能失序达到的情况进行处理(通常 当数据在 S Y N之前到达时,该数据被丢弃并产生一个复位。通过使用 T / T C P,这些失序 的数据将放入队列中处理)。 • A P I必须使服务器进程用一个单一的操作来发送数据和关闭连接,从而允许第二个报文 段中的F I N与应答一起捎带发送(通常应用进程先写应答,从而引起发送一个数据报文 段,然后关闭连接,引起发送 FIN)。 • 在收到来自服务器的 M S S通告之前,客户在第 1个报文段中正在发送数据。为避免限制 客户的MSS为536,一个给定主机的 MSS应该与它的 CC值一起缓存。 • 客户在没有接收到来自服务器的窗口通告之前也可以向服务器发送数据。 T/TCP建议默 认的窗口为 4096,并且也为服务器缓存拥塞门限。 • 使用最小 3个报文段交换,在每个方向上只能计算一个 RT T。加上包括了服务器处理时 间的客户测量 RT T。这意味着被平滑的 RT T及其方差的值也必须为服务器缓存起来,这 与我们在21.9节描述的类似。 T/TCP的特征中吸引人的地方在于它对现有协议进行了最小的修改,同时又兼容了现有的 实现。它还利用了 T C P中现有的工程特征(动态超时和重传、拥塞避免等),而不是迫使应用 进程来处理这些问题。 一个可作为替换的事务协议是通用报文事务协议 V M T P(Versatile Message Tr a n s a c t i o n P r o t o c o l),该协议在 RFC 1045 [Cheriton 1988]中进行了描述。与 T / T C P是现有协议的一个小 的扩充不同, V M T P是使用 I P的一个完整的运输层。 V M T P处理差错检测、重传和重复压缩。 它还支持多播通信。

24.8 TCP的性能 在8 0年代中期出版的数值显示出 T C P在一个以 太网上的吞吐量在每秒 100 000~200 000 字节之间 ([Stevens 1990]的1 7 . 5节给出了参考文献)。从那时 起事情已经发生了许多改变。现在通常使用的硬件 (工作站和更快的个人电脑)每秒可以传输 800 000 字节或者更快。 在10 Mb/s的以太网上计算我们能够观察到的理 论上的TCP最大吞吐量是一件值得做的练习 [Warnock

字 段

数据 ACK (字节) (字节)

以太网前导 以太网目的地址 以太网源地址 以太类型字段 IP首部 TCP首部 用户数据 填充字符 以太网CRC检验 分组间隙(9.6ms) 总计

图24-9 计算以太网理论上最大 吞吐量的字段大小

268

使用TCP/IP详解,卷1:协议

下载

1 9 9 1 ]。我们可以在图 2 4 - 9中看到这个计算的基础。这个图显示了满长度的数据报文段和一个 ACK交换的全部的字节。 我们必须计及所有的开销:前同步码、加到确认上的填充字节、循环冗余检验 C R C以及 分组之间的最小间隔( 9.6ms,相当在10 Mb/s速率下的12个字节)。 首先假定发送方传输两个背对背、满长度的数据报文段,然后接收方为这两个报文段发 送一个ACK。于是最大的吞吐量(用户数据)为: 10 000 000 b/s 2×1460 B × throughput= 8 b/B 22×1538 B+84 B

=1 555 063 B/S

如果T C P窗口开到它的最大值( 6 5 5 3 5,不使用窗口扩大选项),这就允许一个窗口容纳 4 4个 1460字节的报文段。如果接收方每个报文段发送一个 ACK,则计算变为: 10 000 000 b/s 22×1460 B =1 183 667 B/S throughput= × 8 b/B 22×1538 B+84 B 这就是理论上的限制,并做出某些假定:接收方发送的一个 A C K没有和发送方的报文段之一 在以太网上发生冲突;发送方可按以太网的最小间隔时间来发送两个报文段;接收方可以在 最小的以太网间隔时间内产生一个 A C K。不论在这些数字上多么乐观, [ Warnock 1991]在一 个以太网上使用标准的多用户工作站(即使是快的工作站)测量到了一个连续的 1 075 000字 节/秒的速率,这个值在理论值的 90%之内。 当移到更快的网络上时,如 FDDI(100 Mb/s),[Schryver 1993]指出三个商业厂家已经演 示了在 F D D I上的T C P在80 Mb/s~90 Mb/s之间。即使在有更多带宽的环境下, [Borman 1992] 报告说两个Gray Y- M P计算机在一个 800 Mb/s的H I P P I通道上最大值为 781 Mb/s,而运行在一 个Gray Y-MP上的使用环回接口的两个进程间的速率为 907 Mb/s。 下面这些实际限制适用于任何的实际情况 [Borman 1991]。 1) 不能比最慢的链路运行得更快。 2) 不能比最慢的机器的内存运行得更快。这假定实现是只使用一遍数据。如果不是这样 (也就是说,实现使用一遍数据是将它从用户空间复制到内核中,而使用另一遍数据是计算 TCP的检验和),那么将运行得更慢。 [Dalton et al. 1993]描述了将数据复制数目减少从而使一 个标准伯克利源程序的性能得到改进。 [Partridge and Pink 1993]将类似的“复制与检验和”的 改变与其他性能改进措施一道应用于 UDP,从而将UDP的性能提高了约 30%。 3) 不能够比由接收方提供的窗口大小除以往返时间所得结果运行得更快(这就是带宽时 延乘积公式,使用窗口大小作为带宽时延乘积,并解出带宽)。如果使用 2 4 . 4节的最大窗口扩 大因子14,则窗口大小为 1.073 千兆字节,所以这除以 RTT的结果就是带宽的极限。 所有这些数字的重要意义就是 TCP的最高运行速率的真正上限是由 TCP的窗口大小和光速 决定的。正如[Partridge and Pink 1993]中计算的那样,许多协议性能问题在于实现中的缺陷而 不是协议所固有的一些限制。

24.9 小结 本章已经讨论了五个新的 T C P特征:路径 M T U发现、窗口扩大选项、时间戳选项、序号 回绕保护以及使用改进的 T C P事务处理。我们观察到中间的三个特征是为在长肥管道——具 有大的带宽时延乘积的网络—上优化性能所需要的。

下载

269

第24章 TCP的未来和性能使用

路径M T U 发现在 M T U较大时,对于非本地连接,允许 T C P使用比默认的 5 3 6大的窗口。 这样可以提高性能。 窗口扩大选项使最大的 T C P窗口从 6 5 5 3 5增加到 1千兆字节以上。时间戳选项允许多个报 文段被精确计时,并允许接收方提供序号回绕保护( PAW S)。这对于高速连接是必须的。这 些新的 T C P选项在连接时进行协商,并被不理解它们的旧系统忽略,从而允许较新的系统与 旧的系统进行交互。 为事务用的T C P扩展,即 T / T C P,允许一个客户 /服务器的请求-应答序列在通常的情况下 只使用三个报文段来完成。它避免使用三次握手,并缩短了 TIME_WAIT状态,其方法是为每 个主机高速缓存少量的信息,这些信息曾用来建立过一个连接。它还在包含数据报文段中使 用SYN和FIN标志。 由于还有许多关于 TCP能够运行多快的不精确的传闻,因此我们以对 TCP性能的分析来结 束本章。对于一个使用本章介绍的较新特征、协调得非常好的实现而言, T C P的性能仅受最 大的1千兆字节窗口和光速(也就是往返时间)的限制。

习题 24.1 当一个系统发送一个开始的 SYN报文段,其窗口扩大因子为 0,这是什么含义? 24.2 如果在图 2 4 - 7中的主机 b s d i支持窗口扩大选项,则来自 v a n g o g h的报文段 3的16 bit窗 口大小字段中的期望值是多少?类似地,如果在该图的第 2个连接中也使用这个选项, 那么报文段 13中的窗口通告应该是多少? 24.3 与在建立连接时的固定窗口扩大因子不同,已经定义过的窗口扩大因子能否在扩大因子 变化时也出现呢? 24.4 假定MSL为2分钟,那么在什么速率下序号回绕会成为一个问题呢 ? 24.5 PAW S被定义为只在一个单独的连接中进行。为了使 T C P将PAW S来替换 2 M S L等待(即 TIME_WAIT状态),需要进行什么改动? 24.6 在2 4 . 4节最后的例子中,为什么 s o c k程序在紧接着(具有 I P地址和端口)后面的一行 之前,将接收缓存的大小来输出呢? 24.7 假定MSS为1024,重新计算 24.8节中的吞吐量。 24.8 时间戳选项是如何影响 Karn算法(见21.3节)的? 24.9 如果主动建立连接的 T C P发送带有 S Y N标志的报文段(没有使用我们在 2 4 . 7节介绍的扩 展),那么接收 TCP应该怎样处理这些数据呢 ? 24.10 在2 4 . 7节我们提到如果没有使用 T / T C P扩展,即使主动开启方发送带有 F I N的数据,客 户在接收服务器的响应的时延仍然是两倍的 RTT再加上SPT。给出符合这种情况的报文 段。 24.11 假定支持T/TCP,且源自伯克利系统的最小 RTO为0.5秒,重做习题18.14。 24.12 如果我们实现了 T / T C P,并测量两个主机之间的事务时间,那么可以通过比较什么指 标来确定它的有效性?

下载

第25章 SNMP: 简单网络管理协议 25.1 引言 随着网络技术的飞速发展,网络的数量也越来越多。而网络中的设备来自各个不同的厂 家,如何管理这些设备就变得十分重要。本章的内容就是介绍管理这些设备的标准。 基于T C P / I P的网络管理包含两个部分:网络管理站(也叫管理进程, m a n a g e r)和被管的 网络单元(也叫被管设备)。被管设备种类繁多,例如:路由器、 X 终端、终端服务器和打印 机等。这些被管设备的共同点就是都运行 T C P / I P协议。被管设备端和管理相关的软件叫做代 理程序 ( a g e n t )或代理进程。管理站一般都是带有彩色监视器的工作站,可以显示所有被管设 备的状态(例如连接是否掉线、各种连接上的流量状况等 )。 管理进程和代理进程之间的通信可以有两种方式。一种是管理进程向代理进程发出请求, 询问一个具体的参数值(例如:你产生了多少个不可达的 I C M P端口?)。另外一种方式是代 理进程主动向管理进程报告有某些重要的事件发生(例如:一个连接口掉线了)。当然,管理 进程除了可以向代理进程询问某些参数值以外,它还可以按要求改变代理进程的参数值(例 如:把默认的IP TTL值改为64)。 基于TCP/IP的网络管理包含 3个组成部分: 1) 一个管理信息库 MIB(Management Information Base)。管理信息库包含所有代理进程 的所有可被查询和修改的参数。 RFC 1213 [McCloghrie and Rose 1991]定义了第二版的 MIB, 叫做MIB-II。 2) 关于 M I B 的一套公用的结构和表示符号。叫做管理信息结构

S M I ( Structure of

Management Information)。这个在RFC 1155 [Rose and McCloghrie 1990] 中定义。例如: SMI 定义计数器是一个非负整数,它的计数范围是 0~4 294 967 295,当达到最大值时,又从 0开始 计数。 3) 管理进程和代理进程之间的通信协议,叫做简单网络管理协议 SNMP(Simple Network Management Protocol)。在RFC 1157 [Case et al. 1990]中定义。SNMP包括数据报交换的格式 等。尽管可以在运输层采用各种各样的协议,但是在 SNMP中,用得最多的协议还是 UDP。 上面提到的R F C所定义的 S N M P叫做SNMP v1,或者就叫做 S N M P,这也是本章的主要内 容。到1993年为止,又有一些新的关于 SNMP的 RFC发表。在这些RFC中定义的SNMP叫做第 二版SNMP(SNMP v2),这将在25.12章节中讨论。 本章首先介绍管理进程和代理进程之间的协议,然后讨论参数的数据类型。在本章中将 用到前面已经出现过的名词,如: I P、U D P和T C P等。我们在叙述中将举一些例子来帮助读 者理解,这些例子和前面的某些章节相关。

25.2 协议 关于管理进程和代理进程之间的交互信息, SNMP定义了5种报文:

271

第25章 SNMP:简单网络管理协议使用

下载

1) get-request操作:从代理进程处提取一个或多个参数值。 2) g e t - n e x t - r e q u e s t操作:从代理进程处提取一个或多个参数的下一个参数值(关 于“下一个(next)”的含义将在后面的章节中介绍)。 3) set-request操作:设置代理进程的一个或多个参数值。 4) g e t - r e s p o n s e操作:返回的一个或多个参数值。这个操作是由代理进程发出的。它 是前面3中操作的响应操作。 5) trap 操作:代理进程主动发出的报文,通知管理进程有某些事情发生。 前面的 3个操作是由管理进程向代理进程发出的。后面两个是代理进程发给管理进程的 (为简化起见,前面3个操作今后叫做get、get-next和set操作)。图25-1描述了这5种操作。 SNMP管理进程

SNMP代理进程 UDP端口161

UDP端口161

UDP端口161

UDP端口162

图25-1 SNMP的5种操作

既然这些操作中的前 4种操作是简单的请求 -应答方式(也就是管理进程发出请求,代理 进程应答响应),而且在 S N M P中往往使用 U D P协议,所以可能发生管理进程和代理进程之间 数据报丢失的情况。因此一定要有超时和重传机制。 管理进程发出的前面 3种操作采用 U D P的1 6 1端口。代理进程发出的 Tr a p操作采用 U D P的 1 6 2端口。由于收发采用了不同的端口号,所以一个系统可以同时为管理进程和代理进程(参 见习题25.1)。 图25-2是封装成UDP数据报的5种操作的SNMP报文格式。 在图中,我们仅仅对 IP和UDP的首部长度进行了标注。这是由于: SNMP报文的编码采用 了ASN.1和BER,这就使得报文的长度取决于变量的类型和值。关于 ASN.1和BER的内容将在 后面介绍。在这里介绍各个字段的内容和作用。 版本字段是 0。该字段的值是通过 SNMP版本号减去 1得到的。显然0代表SNMP v1。 图25-3显示各种PDU对应的值( PDU即协议数据单元,也就是分组)。 共同体字段是一个字符串。这是管理进程和代理进程之间的口令,是明文格式。默认的 值是public。 对于g e t、g e t - n e x t和s e t操作,请求标识由管理进程设置,然后由代理进程在 g e t response中返回。这种类型的字段我们在其他UDP应用中曾经见过(回忆一下在图14-3中DNS 的标识字段,或者是图16-2中的事务标识字段) 。这个字段的作用是使客户进程(在目前情况下是 管理进程)能够将服务器进程(即代理进程)发出的响应和客户进程发出的查询进行匹配。这个

272

使用TCP/IP详解,卷1:协议

下载

字段允许管理进程对一个或多个代理进程发出多个请求,并且从返回的众多 应答中进行分类。 IP数据报 UDP数据报 SNMP报文 公共SNMP首部

IP首部

UDP 首部

20字节

8字节

版本 (0)

共同体

get/set变量部分

get/set首部 PDU 类型 (0-3)

PDU类 型(4)

请求 差错状 差错 标识 态(0-5) 索引

企业

名称



名称



代理 trap 特定 时间戳 名称 值 类型 地址 (0-6) 代码 有意义的变量 trap首部

图25-2 SNMP报文的格式

差错状态字段是一个整数,它是由代理进程标注的,

PDU类型





指明有差错发生。图 25-4是参数值、名称和描述之间的对应 关系。 差错索引字段是一个整数偏移量,指明当有差错发生 时,差错发生在哪个参数。它是由代理进程标注的,并且 图25-3 SNMP报文中的PDU类型

只有在发生 n o S u c h N a m e、r e a d O n l y和b a d V a l u e差错 时才进行标注。 差错状态

名 称





没有错误 代理进程无法把响应放在一个SNMP消息中发送 操作一个不存在的变量 set操作的值或语义有错误 管理进程试图修改一个只读变量 其他错误

图25-4 SNMP差错状态的值

在g e t、g e t - n e x t和s e t的请求数据报中,包含变量名称和变量值的一张表。对于 g e t 和get-next操作,变量值部分被忽略,也就是不需要填写。 对于t r a p操作符( P D U类型是4),S N M P报文格式有所变化。我们将在 2 5 . 1 0节中当讨论 到trap时再详细讨论。

25.3 管理信息结构 SNMP中,数据类型并不多。在本节,我们就讨论这些数据类型,而不关心这些数据类型 在实际中是如何编码的。 • INTEGER。一个变量虽然定义为整型,但也有多种形式。有些整型变量没有范围限制,有些 整型变量定义为特定的数值(例如,IP的转发标志就只有允许转发时的1或者不允许转发时的 2这两种) ,有些整型变量定义为一个特定的范围(例如,UDP和TCP的端口号就从0到65535) 。 • O C T E R S T R I N G。0或多个8 bit字节,每个字节值在 0~255之间。对于这种数据类型和

273

第25章 SNMP:简单网络管理协议使用

下载

下一种数据类型的 B E R编码,字符串的字节个数要超过字符串本身的长度。这些字符 串不是以NULL结尾的字符串。 • D i s p l a y S t r i n g。0或多个8 bit字节,但是每个字节必须是 A S C I I码(2 6 . 4中有A S C I I 字符集)。在MIB-II中,所有该类型的变量不能超过 255个字符(0个字符是可以的)。 • OBJECT IDENTIFIE 。将在下一节中介绍。 R • N U L L。代表相关的变量没有值。例如,在 g e t或g e t - n e x t操作中,变量的值就是 NULL,因为这些值还有待到代理进程处去取。 • I p A d d r e s s。4字节长度的 OCTER STRING,以网络序表示的 I P地址。每个字节代表 IP地址的一个字段。 • P h y s A d d r e s s。OCTER STRING类型,代表物理地址(例如以太网物理地址为 6个字 节长度)。 32

• Counter。非负的整数,可从 0递增到 2 -1(4 294 976 295)。达到最大值后归 0。 • G a u g e。非负的整数,取值范围为从 0到4 294 976 295( 或增或减)。达到最大值后锁定 , 直到复位。例如, M I B中的t c p C u r r E s t a b就是这种类型的变量的一个例子,它代表目前 在ESTABLISHED或CLOSE_WAIT状态的TCP连接数。 • TimeTicks。时间计数器, 以0.01秒为单位递增,但是不同的变量可以有不同的递增幅 度。所以在定义这种类型的变量的时候,必须指定递增幅度。例如,

M I B 中的

s y s U p T i m e变量就是这种类型的变量,代表代理进程从启动开始的时间长度,以多少 个百分之一秒的数目来表示。 • S E Q U E N C E。这一数据类型与 C程序设计语言中的“ s t r u c t u r e”类似。一个 S E Q U E N C E 包括 0 个或多个元素,每一个元素又是另一个

A S N . 1 数据类型。例如, M I B 中的

UdpEntry就是这种类型的变量。它代表在代理进程侧目前“激活”的 UDP数量(“激活” 表示目前被应用程序所用)。在这个变量中包含两个元素: 1) IpAddress类型中的udpLocalAddress,表示IP地址。 2) INTEGER类型中的udpLocalPort,从0到65535,表示端口号。 • S E Q U E N D E OF。这是一个向量的定义,其所有元素具有相同的类型。如果每一个元素 都具有简单的数据类型,例如是整数类型,那么我们就得到一个简单的向量(一个一 维向量)。但是我们将看到, S N M P在使用这个数据类型时,其向量中的每一个元素是 一个SEQUENCE(结构)。因而可以将它看成为一个二维数组或表。 例如,名为 u d p T a b l e的U D P监听表 ( l i s t e n e r )就是这种类型的变量。它是一个二元的 SEQUENCE变量。每个二元组就是一个 UdpEntry。如图25-5所示。 udpLocalAddress 一个IpAddress类型的变量

范围在0~65535的整型变量

图25-5 表格形式的udpTable 变量

274

使用TCP/IP详解,卷1:协议

下载

在S N M P中,对于这种类型的表格并没有标注它的列数。但在 2 5 . 7节中,我们将看到 g e t - n e x t 操作是如何判断已经操作到最后一列的情况。同时,在 2 5 . 6节中,我们还将介绍管理进程如 何表示它对某一行数据进行 get或set操作。

25.4 对象标识符 对象标识是一种数据类型,它指明一种“授权”命名的对象。“授权”的意思就是这些标 识不是随便分配的,它是由一些权威机构进行管理和分配的。 对象标识是一个整数序列,以点(“.”)分隔。这些整数构成一个树型结构,类似于 D N S (图1 4 - 1)或U n i x的文件系统。对象标识从树的顶部开始,顶部没有标识,以 r o o t表示(这和 Unix中文件系统的树遍历方向非常类似)。 图2 5 - 6显示了在 S N M P中用到的这种树型结构。所有的 M I B变量都从 1 . 3 . 6 . 1 . 2 . 1这个标识 开始。 树上的每个结点同时还有一个文字名。例如标识

1 . 3 . 6 . 1 . 2 . 1 就和 i s o . o r g . d o d .

internet.memt.mib对应。这主要是为了人们阅读方便。在实际应用中,也就是说在管理进程 和代理进程进行数据报交互时,MIB变量名是以对象标识来标识的,当然都是以1.3.6.1.2.1开头的。

图25-6 管理信息库中的对象标识

在图2 5 - 6中,我们除了给出了 m i b对象标识外,还给出了 i s o . o r g . d o d . i n t e r n e t . private.enterprises(1.3.6.1.4.1)这个标识。这是给厂家自定义而预留的。在 Assigned Number RFC中列出了在该结点下大约400个标识。

25.5 管理信息库介绍 所谓管理信息库,或者MIB,就是所有代理进程包含的、并且能够被管理进程进行查询和设

275

第25章 SNMP:简单网络管理协议使用

下载

置的信息的集合。我们在前面已经提到了在RFC 1213 [McColghrie 和Rose 1991]中定义的MIB-II。 如图25-6所示,MIB被划分为若干个组,如 system、interfaces、at(地址转换)和 ip组等。 在本节,我们仅仅讨论 U D P组中的变量。这个组比较简单,它包含几个变量和一个表格。 在下一节,我们将以 UDP组为例,详细讲解什么是实例标识( instance identification),什么是 字典式排序 (lexicographic ordering)以及和这些概念有关的一些简单例子。在这些例子之后, 在25.8节我们继续回到 MIB,描述MIB中的其他一些组。 在图25-6中我们画出了udp组在mib的下面。图 25-7就显示了UDP组的结构。

图25-7 UDP组的结构

在该组中,包含 4个简单变量和 1个由两个简单变量组成的表格。图 2 5 - 8描述了这 4个简单 变量。 名



数据类型

描 述 UDP数据报输入数 没有发送到有效端口的UDP数据报个数 接收到的有错误的UDP数据报个数(例如检验错误) UDP数据报输出数

图25-8 UDP组下的简单变量

在本章中,我们就以图 2 5 - 8的格式来描述所有的 M I B变量。“R / W”列如果为空,则代表 该变量是只读的;如果变量是可读可写的,则以“・”符号来表示。哪怕整个组中的变量都 是只读的,我们也将列出“ R / W”列,以提示读者管理进程只能对这些变量进行查询操作 (上图U D P组我们就是这样做的)。同样,如果变量类型是 I N T E G E T类型并且有范围约束,我 们也将标明它的下限和上限,就如我们在下图中描述 UDP端口号所做的一样。 图25-9描述了在udpTable中的两个简单变量。 名



UDP监听表,索引=. 数据类型 描 述 监听进程的本地IP地址。0.0.0.0代表接收任何接口的数据报 监听进程的本地端口号

图25-9 udpTable 中的变量

每次当我们以 S N M P表格形式来描述 M I B变量时,表格的第 1行都表示索引的值,它是表

276

使用TCP/IP详解,卷1:协议

下载

格中的每一列的参考。在下一节中读者将看到的一些例子也是这样做的。 Case图 在图2 5 - 8中,前3个计数器是有相互关系的。 C a s e图真实地描述了一个给出的 M I B组中变 量之间的相互关系。图 25-10就是UDP组的Case图。 应用层

IP层

图25-10 UDP组的Case图

这张图表明,发送到应用层的 U D P数据报的数量( u d p I n D a t a g r a m s)就是从 I P层送到 U D P层的U D P数据报的数量,当然 u d p I n E r r o r和u d p N o P o r t s也类似。同样,发送到 I P层 的U D P数据报的数量( u d p O u t D a t a g r a m s)就是从应用层发出的 U D P数据报的数量。这表 明udpInDatagram不包括udpInError和udpNoPorts。 在深入讲解 M I B的时候,这些 C a s e图被用来验证:分组的所有数据路径都是被计数的。 [Rose 1994] 中显示了所有MIB组的Case图。

25.6 实例标识 当对MIB变量进行操作,如查询和设置变量的值时,必须对MIB的每个变量进行标识。首先, 只有叶子结点是可操作的。SNMP没法处理表格的一整行或一整列。回到图25-7,在图25-8和图 25-9中描述过的变量就是叶子结点,而mib、udp、udpTable和udpEntry就不是叶子结点。 25.6.1 简单变量 对于简单变量的处理方法是通过在其对象标识后面添加“ .0”来处理的。例如图 25-8中的 计数器udpInDatagrams,它的对象标识是1.3.6.1.2.1.7.1,它的实例标识是 1.3.6.1.2.1.7.1.0, 相对应的文字名称是 iso.org.dod.internet.mgmt.mib.udp.udpInDatagrams.0。 虽然这个变量处理后通常可以缩写为 u d p I n D a t a g r a m s . 0,但我们还是要提醒读者在 SNMP报文中(图 25-2)该变量的名称是其对象的标识 1.3.6.1.2.1.7.1.0。 25.6.2 表格 表格的实例标识就要复杂得多。回顾一下图 25-8中的UDP 监听表。 每个M I B中的表格都指明一个以上的索引。对于 U D P监听表来说, M I B定义了包含两个 变量的联合索引,这两个变量是:udpLocalAddress,它是一个IP地址;udpLocalPort, 它是一个整数(在图 25-9中的第1行就显示了这个索引)。 假设在U D P监听表中有 3行具体成员:第 1行的I P地址是0 . 0 . 0 . 0,端口号是 6 7;第2行的I P

277

第25章 SNMP:简单网络管理协议使用

下载

地址是 0 . 0 . 0 . 0,端口号是 1 6 1;第3行的I P地址是 0 . 0 . 0 . 0, 端口号是520。如图25-11所示。 这意味着系统将从端口 6 7(B O O T P服务器)、端口 1 6 1(S N M P)和端口 5 2 0(R I P)接受来自任何接口的 U D P数据报。表格中的这 3行经过处理后的结果在图 2 5 -

图25-11 UDP监听表

12中显示。 25.6.3 字典式排序

M I B中按照对象标识进行排序时有一个隐含的排序规则。 M I B表格是根据其对象标识按 照字典的顺序进行排序的。这就意味着图 2 5 - 1 2中的6个变量排序后的情况如图 2 5 - 1 3所示。从 这种字典式排序中可以得出两个重要的结论。 行

对象标识







图25-12 UDP监听表中行的实例标识 列

对象标识(字典序)







图25-13 UDP监听表的字典式排序

1) 在表格中,一个给定变量(在这里指 u d p L o c a l A d d r e s s)的所有实例都在下个变量 (这里指 u d p L o c a l P o r t)的所有实例之前显示。这暗 示表格的操作顺序是“先列后行”的次序。这是由于对 对象标识进行字典式排序所得到的,而不是按照人们的 阅读习惯而排列的。 2) 表格中对行的排序和表格中索引的值有关。在图2513中,67的字典序小于161,同样161的字典序小于520。 图25-14描述了例子中UDP监听表的这种“先列后行”

图25-14 按“先列后行”次序显示 的UDP监听表

的次序。 在下节中,讲述到 get-next操作时,同样还会遇到这种“先列后行”的次序。

25.7 一些简单的例子 在本节中,我们将介绍如何从 SNMP代理进程处获取变量的值。对代理进程进行查询的软 件属于ISODE系统,叫做 snmpi。两者在[Rose 1994]中有详细的介绍。

278

使用TCP/IP详解,卷1:协议

下载

25.7.1 简单变量 对一个路由器取两个 UDP组的简单变量值:

其中, - a选项代表要和之通信的代理进程名称, - c选项表示 S N M P的共同体名。所谓共 同体名,就是客户进程(在这里指 s n m p i)提供、同时能被服务器进程(这里指代理进程 g a t e w a y)所识别的一个口令,共同体名称是管理进程请求的权限标志。代理进程允许客户 进程用只读共同体名对变量进行读操作,用读写共同体名对变量进行读和写操作。 S n m p i程序的输出提示符是 s n m p i >,在后面可以键入如 g e t这样的命令,该软件将把它 转化为 S N M P中的g e t - r e q u e s t报文。当结束时,键入 q u i t就退出(在后面的例子中,我们 将省略掉quit的操作)。 图25-15显示的是对于这个例子 tcpdump的两行输出结果。

图25-15 简单SNMP查询操作tcpdump 的输出结果

对这两个变量的查询请求是封装在一个 UDP数据报中的,而响应也在一个 UDP数据报中。 显示的变量是以其对象标识的形式显示的,这是在 SNMP报文中实际传输的内容。我们必 须指定这两个变量的实例是 0。注意,变量的名称(它的对象标识)同样也在响应中返回。在 下面我们将看到对于 get-next操作这是必需的。 25.7.2 get-next操作 g e t - n e x t操作是基于 M I B的字典式排序的。在下面的例子中,首先向代理进程询问 U D P后的下一个对象标识(由于不是一个叶子对象,没有指定任何实例)。代理进程将返回 U D P组中的第 1个对象,然后我们继续向代理进程取该对象的下一个对象标识,这时候第 2个 对象将被返回。重复上面的步骤直到取出所有的对象为止。

这个例子解释了为什么 g e t - n e x t操作总是返回变量的名称,这是因为我们向代理进程 询问下一个变量,代理进程就把变量值和名称一起返回了。 采用这种方式进行 g e t - n e x t操作,我们可以想象管理进程只要做一个简单的循环程序,

下载

279

第25章 SNMP:简单网络管理协议使用

就可以从 M I B树的顶点开始,对代理进程一步步地进行查询,就可以得出代理进程处所有的 变量值和标识。该方式的另外一个用处就是可以对表格进行遍历。 25.7.3 表格的访问 对于“先列后行”次序的UDP监听表,只要采用前面的简单查询程序一步一步地进行操作, 就可以遍历整个表格。只要从询问代理进程 u d p T a b l e 的下一个变量开始就可以了。由于 udpTable不是叶子对象,我们不能指定一个实例,但是get-next操作依然能够返回表格中的 下一个对象。然后就可以以返回的结果为基础进行下一步的操作,代理进程也会以“先列后行” 的次序返回下一个变量,这样就可以遍历整个表格。我们可以看到返回的次序和图25-14相同。

我们已完成了对UDP监听表的操作

但是管理进程如何知道已经到达表格的最后一行呢?既然get-next操作返回结果中包含表 格中的下一个变量的值和名称,当返回的结果是超出表格之外的下一个变量时,管理进程就可以 发现变量的名称发生了较大的变化。这样就可以判断出已经到达表格的最后一行。例如在我们的 例子中,当返回的是snmpInPkts变量的时候就代表已经到了UDP监听表的最后一个变量了。

25.8 管理信息库(续) 现在继续讨论 M I B。我们仅仅介绍下列 M I B组:s y s t e m(系统标识)、i f(接口)、a t (地址转换)、ip、icmp和tcp。 25.8.1 system组 s y s t e m组非常简单,它包含 7个简单变量(例如,没有表格)。图25-16列出了s y s t e m组 的名称、数据类型和描述。 可以对netb路由器查询一些简单变量:

280

使用TCP/IP详解,卷1:协议

名 称 sysDescr sysObjectID sysUpTime

数据类型 DisplayString ObjectID TimeTicks

R/W

sysContact sysName sysLocation sysServices

DisplayString DisplayString DisplayString [0…127]

• • • •

下载 描 述 系统的文字描述 在子树1.3.6.1.4.1中的厂商标识 从系统的网管部分启动以来运行的时间(以百分之一 秒为计算单位) 联系人的名字及联系方式 结点的完全合格的域名 (FQDN) 结点的物理位置 指示结点提供的服务的值。该值是此结点所支持的 O S I模型中层次的和。根据所提供的服务,将下面的一 些值相加: 0 x 0 1 (物理层 )、0 x 0 2(数据链路层)、0 x 0 4 (互联网层)、0 x 0 8(端到端的运输层)和 0 x 4 0(应用 层)

图25-16 system组中的简单变量

回到图25-6中,system的对象标识符在internet.private.enterprises组(1.3.6.1.4.1)中, 在Assigned Numbers RFC文档中可以确定下一个对象标识符(12)肯定是指派给了厂家(Epilogue) 。 同时还可以看出, s y s S e r v i c e s变量的值是 4与8的和,它支持网络层(例如选路)和运输 层的应用(例如端到端)。 25.8.2 interface组 在本组中只定义了一个简单变量,那就 是系统的接口数量,如图 25-17所示。

名 称 ifNumber

数据类型 INTEGER

R/W

描述 系统上的网络接 口数

图25-17 if组中的简单变量

在该组中,还有一个表格变量,有 2 2列。表格中的每行定义了接口的一些特征参数。如 图25-18所示。 可以向主机 s u n查询所有这些接口的变量。如 3.8节中所示,我们还是希望访问三个接口, 如果SLIP接口已经启动: 首先看第一个接口的接口索引

281

第25章 SNMP:简单网络管理协议使用

下载

接口表,索引= 名称

数据类型

ifIndex ifDescr ifType

INTEGER DisplayString INTEGER

ifMtu ifSpeed ifPhysAddress

INTEGER Gauge PhysAddress

ifAdminStatus

[1…3]



ifOperStatus

[1…3]



ifLastChange ifInOctets ifInUcastPkts ifInNUcastPkts

TimeTicks Counter Counter Counter

ifInDiscards

Counter

ifInErrors ifInUnknownProtos ifOutOctets ifOutUcastPkts ifOutNUcastPkts

Counter Counter Counter Counter Counter

ifOutDiscards

Counter

ifOutErrors ifOutQLen ifSpecific

Counter Gauge ObjectID



R/W



接口索引,介于 1和i f N u m b e r之间 接口的文字描述 类型,例如: 6 = 以太网,7 = 802.3以太网,9 = 802.5令牌环,23 = PPP,28 = SLIP,还有其他一 些值 接口的MTU 以b/s为单位的速率 物理地址,对无物理地址的接口,以一串 0表示 (例如,串行链路) 所希望的接口状态: 1= 工作,2 = 不工作,3 = 测试 当前接口的状态: 1= 工作,2 = 不工作,3 = 测 试 当接口进入目前运行状态时 s y s U p T i m e的值 收到的字节总数,包括组帧字符 交付给高层的单播分组数 交付给高层的非单播(例如,广播或多播)分 组数 收到的被丢弃的分组数,即使在分组中无差错 (例如,无缓存空间) 收到的由于差错被丢弃的分组数 收到的由于未知的协议被丢弃的分组数 发送的字节总数,包括组帧字符 从高层接收到的单播分组数 从高层接收到的非单播(如广播或多播)分组 数 发出的被丢弃的分组数,即使在分组中无差错 (如无缓存空间) 发出的由于差错被丢弃的分组数 在输出队列中的分组数 对这种特定媒体类型的 MIB定义的引用

图25-18 在接口表中的变量:ifTable

对于第1个接口,采用 get操作提取5个变量值,然后用 g e t - n e x t操作提取第2个接口的相 同的5个参数。对于第 3个接口,同样采用 get-next操作。 对于S L I P链路的接口类型,所报告的是一个点到点的专用串行链路,而不是 S L I P链路。 此外,SLIP链路的速率没有报告。 这个例子对我们理解 g e t - n e x t操作和“先列后行”次序之间的关系十分重要。如果我 们键入命令“ n e x t i f D e s c r .” 1,则系统返回的是表格中的下一行所对应的变量,而不是 同一行中的下个变量。如果表格是按照“先行后列”次序存放,我们就不能通过一个给定变 量来读取下一个变量。 25.8.3 at组 地址转换组对于所有的系统都是必需的,但是在 M I B - I I中已经没有这个组。从 M I B - I I开

282

使用TCP/IP详解,卷1:协议

下载

始,每个网络协议组(如 I P组)都包含它们各自的网络地址转换表。例如对于 I P组,网络地 址转换表就是ipNetToMediaTable。 在该组中,仅有一个由 3列组成的表格变量。如图 25-19所示。 我们将用 s n m p i程序中的一个新命令来转储 ( d u m p )整个表格。向一个叫做 k i n e t i c s的 路由器(该路由器连接了一个 T C P / I P网络和一个 A p p l e Ta l k网络)查询其整个 A R P高速缓存。 命令的输出是字典式排序的整个表格内容。 地址转换表,索引= .1. 名



数据类型



R/W

atIfIndex atPhysAddress

INTEGER PhysAddress

• •

atNetAddress

NetworkAddress





接口数:i f I n d e x 物理地址。若设置为长度为 0的字符 串,则表示无效表项 IP地址

图25-19 网络地址转换表:atTable

让我们来分析一下用tcpdump命令时的分组交互情况。当snmpi要转储整个表格时,首先 发出一条get-next命令以取得表格的名称(在 本例中是at),该名称就是要获取的第一个表项。 然后在屏幕上显示的同时生成另一条get-next 命令。直到遍历完整个表格的内容后才终止。 图25-20显示了在路由器中实际表格的内容。 注意图中,接口2的AppleTalk协议的物理地

图25-20 at表举例(ARP高速缓存)

址是32 bit的数值,而不是我们所熟悉的以太网的48 bit物理地址。同时请注意,正如我们所希望 的那样,在图中有一条记录和netb路由器(其IP地址是140.252.1.183)有关。这是因为netb路 由器和kinetics路由器在同一个以太网中(140.252.1),而且kinetics路由器必需采用ARP 来回送SNMP响应。 25.8.4 ip组 ip组定义了很多简单变量和 3个表格变量。图 25-21显示了所有的简单变量。

283

第25章 SNMP:简单网络管理协议使用

下载 数据类型

R/W

ipForwarding

名称

[1…2]



ipDefaultTTL ipInReceives ipInHdrErrors

INTEGER Counter Counter



ipInAddrErrors ipForwDatagrams ipInUnknownProtos ipInDiscards ipInDelivers ipOutRequests

Counter Counter Counter Counter Counter Counter

ipOutDiscards ipOutNoRoutes ipReasmTimeout

Counter Counter INTEGER

ipReasmReqds ipReasmOKs ipReasmFails ipFragOKs ipFragFails

Counter Counter Counter Counter Counter

ipFragCreates ipRoutingDiscards

Counter Counter





1代表系统正在转发 IP数据报,2则代表不在转 发 当运输层不提供 TTL值时的默认 TTL值 从所有接口收到的 IP数据报的总数 由于首部差错被丢弃的数据报数(例如,检验 和差错,版本不匹配, TTL超过等) 由于不正确的目的地址被丢弃的 IP数据报数 曾进行过一次转发尝试的 IP数据报数 具有无效协议字段的发往本地的 IP数据报数 由于缓存空间不足被丢弃的收到的数据报数 交付到适当的协议模块的 IP数据报数 传递给IP层来传输的 IP数据报总数。不包括已 经在i p F o r w D a t a g r a m s中计入的那些 由于缓存空间不足被丢弃的输出数据报数 由于找不到路由被丢弃的数据报数 在等待重装时已收到的数据报片被保留的最大 秒数 收到的需要进行重装的 IP数据报片的数目 已成功重装的 IP数据报数 IP重装算法失败次数 被成功分片的 IP数据报数 需要进行分片但由于设置了“不分片”标志而 不能分片的 IP数据报数 由分片而产生的 IP数据报片的数目 所选择的选路表项即使是有效的但也要丢弃的 数目

图25-21 ip组中的简单变量

i p组中的第一个表格变量是 I P地址表。系统的每个 I P地址都对应该表格中的一行。每行 中包含了5个变量,如图25-22所示。 IP地址表,索引= 名



数据类型

ipAdEntAddr ipAdEntIfIndex ipAdEntNetMask ipAdEntBcastAddr ipAdEntReasmMaxSize

IpAddress INTEGER IpAddress [0…1] [0…65535]



R/W

这一行的IP地址 对应的接口数: ifIndex 对这个IP地址的子网掩码 IP广播地址中的最低位的值。通常为 1 在这个接口上收到的、能够进行重装的、 最长的IP数据报

图25-22 IP地址表:ipAddrTable

同样可以向主机 sun查询整个IP地址表:



284

使用TCP/IP详解,卷1:协议

下载 环回接口 SLIP接口 以太网接口

所有这三个都使用一个比特进行广播

输出的接口号码可以和图 2 5 - 1 8中的输出进行比较,同样 I P地址和子网掩码可以和 3 . 8节中 采用ifconfig命令时的输出进行比较。 i p组中的第二个表是 I P路由表(请回忆一下我们在 9 . 2节中讲到的路由表),如图2 5 - 2 3所 示。访问该表中每行记录的索引是目的 IP地址。 名



数据类型



R/W

ipRouteDest ipRouteIfIndex ipRouteMetric1

IpAddress INTEGER INTEGER

• • •

ipRouteMetric2 ipRouteMetric3 ipRouteMetric4 ipRouteNextHop ipRouteType

INTEGER INTEGER INTEGER IpAddress INTEGER

• • • • •

ipRouteProto

INTEGER

ipRouteAge

INTEGER



ipRouteMask

IpAddress



ipRouteMetric5 ipRouteInfo

INTEGER ObjectID

• •



目的IP地址。值 0.0.0.0表示一个默认的表项 接口数:i f I n d e x 主要的选路度量。这个度量的意义取决于选路协 议(ipRouteProto)。-1表示未使用 可选的选路度量 可选的选路度量 可选的选路度量 下一跳路由器的IP地址 路由类型:1 = 其他,2 = 无效路由,3 = 直接,4= 间接 选路协议: 1 = 其他,4 = ICMP重定向,8 = RIP, 13 = OSPF, 14 = BGP, 以及其他 自从路由上次被更新或确定是正确的以后所经历 的秒数 在和ipRouteDest相比较之前,掩码要与目的 IP地 址进行逻辑“与” 其他的选路度量 对这种特定选路协议的 MIB定义的引用

图25-23 IP路由表:ipRouteTable

图2 5 - 2 4显示的是用 s n m p i程序采用 d u m p i p R o u t e T a b l 命令从主机 e s u n得到的路由 表。在这张表中,已经删除了所有 5个路由度量,那是由于这 5条记录的度量都是- 1。在列的 标题中,对每个变量名称已经删除了 ipRoute这样的前缀。 间接(4) 直接(3) 直接(3) 直接(3) 间接(4)

其他(1) 其他(1) 其他(1) 其他(1) 其他(1)

图25-24 路由器sun 上的IP路由表

285

第25章 SNMP:简单网络管理协议使用

下载

为比较起见,下面的内容是我们用 n e t s t a t命令(在 9 . 2节中曾经讨论过)格式显示的路 由表信息。图25-24是按字典序显示的,这和 netstat命令显示格式不同:

i p组的最后一个表是地址转换表,如图 2 5 - 2 5所示。正如我们前面所说的, a t组已经被删 除了,在这里已经用 IP表来代替了。 IP地址转换表,索引= . 名



ipNetToMediaIfIndex ipNetToMediaPhysAddress ipNetToMediaNetAddress ipNetToMediaType

数 据 类 型

R/W

INTEGER PhysAddress IpAddress [1…4]

• • • •





对应的接口: ifIndex 物理地址 IP地址 映射的类型: 1 = 其他,2 = 无效的, 3 = 动态的, 4 = 静态的。

图25-25 IP地址转换表:ipNetToMediaTable

这里显示的是系统 sun上的ARP高速缓存信息: sun % arp -a svr4 (140.252.13.34) at 0:0:c0:c2:9b:26 bsdi (140.252.13.35) at 0:0:c0:6f:2d:40

相应的SNMP输出:

25.8.5 icmp组 i c m p组包含 4个普通计数器变量( I C M P报文的输出和输入数量以及 I C M P差错报文的输 入和输出数量)和 2 2个其他I C M P报文数量的计数器: 11个是输出计数器,另外 11个是输入计 数器。如图 25-26所示。 对于有附加代码的 I C M P 报文(请回忆一下图 6 - 3 中,有 1 5种报文代表目的不可达), SNMP没有为它们定义专门的计数器。 25.8.6 tcp组 图25-27显示的是 tcp组中的简单变量。其中的很多变量和图 18-12描述的TCP状态有关。

286

使用TCP/IP详解,卷1:协议





数据类型

icmpInMsgs icmpInErrors

Counter Counter

icmpInDestUnreachs icmpInTimeExcds icmpInParmProbs icmpInSrcQuenchs icmpInRedirects icmpInEchos icmpInEchosReps icmpInTimeStamps icmpInTimeStampReps icmpInAddrMasks icmpInAddrMaskReps icmpOutMsgs icmpOutErrors

Counter Counter Counter Counter Counter Counter Counter Counter Counter Counter Counter Counter Counter

icmpOutDestUnreachs icmpOutTimeExcds icmpOutParmProbs icmpOutSrcQuenchs icmpOutRedirects icmpOutEchos icmpOutEchosReps icmpOutTimeStamps icmpOutTimeStampReps icmpOutAddrMasks icmpOutAddrMaskReps

Counter Counter Counter Counter Counter Counter Counter Counter Counter Counter Counter

下载 描

R/W



收到的ICMP报文总数 收到的有差错的 ICMP报文数(例如,无效 的ICMP检验和) 收到的ICMP目的站不可达报文数 收到的ICMP超时报文数 收到的ICMP参数问题报文数 收到的ICMP源站抑制报文数 收到的ICMP重定向报文数 收到的ICMP回显请求报文数 收到的ICMP回显应答报文数 收到的ICMP时间戳请求报文数 收到的ICMP时间戳应答报文数 收到的ICMP地址掩码请求报文数 收到的ICMP地址掩码应答报文数 输出的ICMP报文总数 由于在ICMP报文中有一个问题(例如,缓 存空间不足)而未发送的 ICMP报文数 发送的ICMP目的站不可达报文数 发送的ICMP超时报文数 发送的ICMP参数问题报文数 发送的ICMP源站抑制报文数 发送的ICMP重定向报文数 发送的ICMP回显请求报文数 发送的ICMP回显应答报文数 发送的ICMP时间戳请求报文数 发送的ICMP时间戳应答报文数 发送的ICMP地址掩码请求报文数 发送的ICMP地址掩码应答报文数

图25-26 icmp 组中的简单变量 名



数据类型

tcpRtoAlgorithm

INTEGER

tcpRtoMin tcpRtoMax tcpMaxConn tcpActiveOpens tcpPassiveOpens tcpAttempFails

INTEGER INTEGER INTEGER Counter Counter Counter

tcpEstabResets

Counter

tcpCurrEstab tcpInSegs tcpOutSegs tcpRetransSegs tcpInErrs tcpOutRsts

Gauge Counter Counter Counter Counter



R/W



用来计算重传超时值的算法:1 =除下列值以外,2 = 固 定的RTO, 3 = MIL-STD-1778附件B, 4 = Van Jacobson算法 以毫秒计的最小重传超时值 以毫秒计的最大重传超时值 最大的TCP连接数。若为动态的,则值为- 1 从CLOSED到SYN_SENT的状态变迁数 从LISTEN到SYN_RCVD的状态变迁数 从SYN_SENT或SYN_RCVD到CLOSED的状态变迁 数,加上从SYN_RCVD到LISTEN的状态变迁数 从ESTABLISHED或CLOSE_WAIT状态到CLOSED的 状态变迁数 当前在ESTABLISHED或CLOSE_WAIT状态的连接数 收到的报文段的总数 发送的报文段的总数,但将仅包含重传字节的除外 重传的报文段的总数 收到的具有一个差错(如无效的检验和)的报文段总数 具有RST标志置位的报文段的总数

图25-27 tcp 组中的简单变量

287

第25章 SNMP:简单网络管理协议使用

下载 现在向系统 sun查询一些tcp组变量:

本系统(指 S u n O S 4 . 1 . 3)使用的是 Van Jacobson 超时重传算法,超时定时器的范围在 200 ms~12.8 s之间,并且对 T C P连接数量没有特定的限制(这里的超时上限 12.8 s恐怕有错, 因为我们在 2 1章中曾经介绍大多数应用的超时上限是 64 s )。 t c p组还包括一个表格变量,即 T C P连接表,如图 2 5 - 2 8所示。对于每个 T C P连接,都对应 表格中的一条记录。每条记录包含 5个变量:连接状态、本地 I P地址、本地端口号、远端 I P地 址以及远端端口号。 索引=... 数据类型

R/W

tcpConnState





[1…12]



tcpConnLocalAddress

IpAddress

tcpConnLocalPort tcpConnRemAddress tcpConnRemPort

[1…65535] IpAddress [1…65535]





连接状态: 1 = CLOSED, 2 = LISTEN, 3 = SYN_SENT, 4 = SYN_RCVD, 5 = ESTABLISHED, 6 = FIN_WAIT, 7 = FIN_WAIT, 8 = CLOSE_WAIT, 9 = LAST_ACK, 10 = CLOSING, 11 = TIME_WAIT, 12 = 删除TCB。管理进程对此变量 可以设置的唯一值就是 1 2(例如,立即终止此连 接) 本地IP地址。0.0.0.0代表监听进程愿意在任何 接口接受连接 本地端口号 远程IP地址 远程端口号

图25-28 TCP连接表:tcpConnTable

让我们看一看在系统 s u n上的这个表。由于有许多服务器进程在监听这些连接,所以我 们只显示该表的一部分内容。在转储全部表格的变量之前,我们必需先建立两条 TCP连接: sun % rlogin gemini

g e m i n i的I P地址是1 4 0 . 2 5 2 . 1 . 1 1

sun % rlogin localhost

I P地址应该是 1 2 7 . 0 . 0 . 1

和 在所有的监听服务器进程中,我们仅仅列出了 FTP服务器进程的情况,它使用 21号端口。

288

使用TCP/IP详解,卷1:协议

下载

对于rlogin到gemini,只显示一条记录,这是因为 gemini是另外一个主机。而且我们 仅仅能够看到连接的客户端信息(端口号是 1023)。但是Telnet连接,客户端和服务器端都显示 (客户端口号是 1415,服务器端口号是23),这是因为这种连接通过环回接口。同时我们还可以 看到,FTP监听服务器程序的本地IP地址是0.0.0.0。这表明它可以接受通过任何接口的连接。

25.9 其他一些例子 现在开始回答前面一些没有回答的问题,我们将用 SNMP的知识进行解释。 25.9.1 接口MTU 回忆一下在 11 . 6节的实验中,我们试图得出一条从 n e t b到s u n的S L I P连接的M T U。现在可 以采用 S N M P 得到这个 M T U。首先从 I P路由表中取到 S L I P连接( 1 4 0 . 2 5 2 . 1 . 2 9)的接口号 (i p R o u t e I f I n d e x),然后就可以用这个数值进入接口表并且取得想要的 S L I P连接的 M T U (通过SLIP的描述和数据类型)。

可以看到,即使连接的类型是 S L I P连接,但是 M T U仍设置为以太网,其值为 1 5 0 0,目的 可能是为了避免分片。 25.9.2 路由表 回忆一下在 1 4 . 4节中,我们讨论了 D N S如何进行地址排序的问题。当时我们介绍了从域 名服务器返回的第 1个I P地址是和客户有相同子网掩码的情况。还介绍了用其他的 I P地址也会 正常工作,但是效率比较低。现在我们从 SNMP的角度来查阅路由表的入口,在这里将用到前 面章节中和 IP路由有关的很多相关知识。 路由器 g e m i n i是一个多接口主机,有两个以太网接口。首先确认一下两个接口都可以 Telnet登录:

下载

289

第25章 SNMP:简单网络管理协议使用

可以看出这两个地址的连接没有什么区别。现在我们采用 t r a c e r o u t e命令来看一下对于每 个地址,是否有选路方面的不同:

可以看到:如果采用属于 1 4 0 . 2 5 2 . 3子网的地址,就多了额外的一跳。下面解释造成这个额外 一跳的原因。 图2 5 - 2 9是系统的连接关系图。从 t r a c e r o u t e命令的输出结果可以看出主机 g e m i n i和 路由器swnrt都连接了两个网段: 140.252.3子网和140.252.1子网。 回忆一下在图 4 - 6中,我们解释了路由器 n e t b采用A R P代理进程,使得 s u n工作站好象是 直接连接到140.252.1子网上的情况。我们忽略了 s u n和n e t b之间SLIP连接的调制解调器,因 为这和我们这里的讨论不相关。 在图2 5 - 2 9中,我们用虚线箭头画出了当 Te l n e t到1 4 0 . 2 5 2 . 3 . 5 4时的路径。返回的数据报怎 么知道直接从 g e m i n i到n e t b,而不是从原路返回呢?我们采用在 8 . 5节中介绍过的,带有宽 松选路特性的traceroute版本来解释:

当在命令中指明是宽松源站选路时, swnrt路由器就不再有 响应。看一下前面没有指明源站选路的 traceroute命令输 出,可以看出 s w n r t路由器是事实上的第 2跳。超时数据必 须这样设置的原因是:当数据报指定了宽松源站选路选项时, 该路由器没有发生ICMP超时差错。所以在traceroute命令 的输出中可以得出,返回路径是从gemini(TTL 3, 4和5)路由 器直接到达netb路由器,而不通过swnrt路由器。 还剩下一个需要用SNMP来解释的问题就是:在 netb路 由器的路由表中,哪条信息代表寻径到140.252.3?该信息表示 netb路由器把分组发送给swnrt而不是直接发送给gemini? 用get命令来取下一跳路由器的值。

图25-29 例子中的网络拓扑结构

290

使用TCP/IP详解,卷1:协议

下载

sun % snmpi -a netb -c secret get ipRouteNextHop.140.252.3.0 ipRouteNextHop.140.252.3.0=140.252.1.6

正如我们所看到发生的那样,路由表设置使得 netb路由器把分组发送到 swnrt路由器。 为什么gemini路由器直接把分组回送给netb路由器?那是因为在gemini路由器端,它要回 送的分组目的地址是140.252.1.29,而子网140.252.1是直接连接到gemini路由器上的。 从上面这个例子可以看出选路的策略。由于gemini是打算作一个多接口主机而不是路由器, 所以默认的到140.253.3子网的路由器是swnrt。这是多接口主机和路由器之间差异的一个典型例子。

25.10 Trap 本章我们看到的例子都是从管理进程到代理进程的。当然代理进程也可以主动发送 t r a p到 管理进程,以告诉管理进程在代理进程侧有某些管理进程所关心的事件发生,如图 2 5 - 1所示。 trap发送到管理进程的 162号端口。 在图2 5 - 2中,我们已经描述了 trap PDU的格式。在下面关于 t c p d u m p输出内容中我们将 再一次用到这些字段。 现在已经定义了 6种特定的 t r a p类型,第 7种t r a p类型是由供应商自己定义的特定类型。图 25-30给出了trap报文中trap类型字段的内容。 trap类型





0 1 2

coldStart warmStart linkDown

3

linkUp

4 5

authenticationFailure egpNeighborLoss

6

enterpriseSpecific





代理进程对自己初始化 代理进程对自己重新初始化 一个接口已经从工作状态改变为故障状态(图 25-18)。 报文中的第一个变量标识此接口 一个接口已经从故障状态改变为工作状态(图 25-18)。 报文中的第一个变量标识此接口 从SNMP管理进程收到无效共同体的报文 一个EGP邻站已变为故障状态。报文中的第一个变量包含 此邻站的IP地址 在这个特定的代码字段中查找 trap信息

图25-30 trap的类型

用tcpdump命令来看看trap的情况。我们在系统sun上启动SNMP代理进程,然后让它产生 coldStart类型的trap(我们告诉代理进程把trap信息发送到bsdi主机。虽然在该主机上并没有运行 处理trap的管理进程,但是可以用tcpdump来查看产生了什么样的分组。回忆一下在图 25-1中, trap是从代理进程发送到管理进程的,而管理进程不需要给代理进程发送确认。所以我们不需 要trap的处理程序)。然后我们用s n m p i程序发送一个请求,但该请求的共同体名称是无效的。 这将产生一个authenticationFailure类型的trap。图25-31显示了命令的输出结果。

图25-31 tcpdump 输出的由SNMP代理进程产生的trap

首先注意一下两个 U D P数据报都是从 S N M P代理进程(端口是 1 6 1,图中显示的名称是 snmp)发送到目的端口号是 162的服务器进程上的(图中显示的名称是 snmp-trap)。 再注意一下 C=traps是trap报文的共同体名称。这是 ISODE SNMP代理进程的配置选项。

291

第25章 SNMP:简单网络管理协议使用

下载

下一个要注意的是:第 1行中的Trap(28)和第2行中的Trap(29)是PDU类型和长度。 两个输出行的第一项内容都是相同的 E : u n i x . 1 . 2 . 5。这代表企业名字段和代理进程的 s y s O b j i e c t I D。它是图 2 5 - 6中1 . 3 . 6 . 1 . 4 . 1(i s o . o r g . d o d . i n t e r n e t . p r i v a t e . enterparses)结点下面的某个结点,所以代理进程的对象标识是1.3.6.1.4.1.4.1.2.5。它的简称 是:unix.agents.fourBSD-isode.5。最后一个数字“5”代表ISODE代理进程软件的版本号。 这些企业名的值代表了产生trap的代理进程软件信息。 输出的下一项是代理进程的 IP地址(140.252.13.33)。 在第1行中,trap的类型显示的是coldStart,第2行中,显示的是authenticationFailure。 与之相对应的trap类型值是0和4(如图25-30所示) 。由于这些都不是厂家自定义的trap,所以特定代 码必须是0,在图中没有显示。 输出的最后分别是 20和1907,这是时间戳字段。它是 T i m e T i c k s类型的值,表示从代理 进程初始化开始到 t r a p发生所经历了多少个百分之一秒。在冷启动 t r a p的情况下,在代理进程 初始化后到 t r a p的产生共经历了 200 ms。同样, t c p d u m p的输出表明第 2个t r a p在第1个t r a p产 生的18.86s后出现,这对应于打印出的 1907个百分之一秒减去 200 ms所得到的值。 图2 5 - 2表明t r a p报文还包含很多代理进程发送给管理进程的变量,但在这些在例子中没有 再讨论。

25.11 ASN.1和BER 在正式的SNMP规范中都是采用 ASN.1(Abstract Syntax Notation 1)语法,并且在 SNMP 报文中比特的编码采用 B E R(Basic Encoding Rule)。和其他介绍 S N M P的书不同,我们有目 的地把 A S N . 1和B E R的讨论放到最后。因为如果放在前面讨论,有可能使读者产生混淆而忽 略了 S N M P的真正目的是进行网络管理。在这里我们也只是对这两个概念简单地进行解释, [Rose 1990]的第8章详细讨论了ASN.1和BER。 A S N . 1是一种描述数据和数据特征的正式语言。它和数据的存储及编码无关。 M I B和 S N M P报文中的所有的字段都是用 A S N . 1描述的。例如:对于 S M I中的 i p A d d r e s s数据类型, ASN.1是这样描述的: IpAddress ::= [APPLICATION 0] -- in network-byte order IMPLICIT OCTET STRING (SIZE (4))

同样,在MIB中,简单变量的定义是这样描述的:

用SEQUENCE和SEQUENCE OF来定义表格的描述更加复杂。 当有了这样的 A S N . 1定义,可以有多种编码方法把数据编码为传输的比特流。 S N M P使用 的编码方法是 B E R。例如,对于一个简单的整数如 6 4,在B E R中需要用 3个字节来表示。第一 个字节说明类型是一个整数,下个字节说明用了多少个字节来存储该整数(在这里是 1),最 后一个字节才是该整数的值。 幸运的是,A S N . 1和B E R这两个繁琐的概念仅仅在实现 S N M P的时候才重要,对我们理解

292

使用TCP/IP详解,卷1:协议

下载

网络管理的概念和流程并没有太大的关系。

25.12 SNMPv2 在1993年,发表了定义新版本 SNMP的11个RFC。RFC 1441 [Case et al. 1993]是其中的第 一个,它系统地介绍了 SNMPv2。同样,有两本书 [Stallings 1993; Rose 1994]也对SNMPv2进 行了介绍。现在已经有两个 S N M P v 2的基本模型(参见附录 B . 3中的[Rose 1994]),但是厂家 的实现到1994年才能广泛使用。 在本节中,我们主要介绍 SNMPv1和SNMPv2之间的重要区别。 1) 在SNMPv2中定义了一个新的分组类型 get-bulk-request,它高效率地从代理进程 读取大块数据。 2) 另的一个新的分组类型是 inform-request,它使一个管理进程可以向另一个管理进 程发送信息。 3) 定义了两个新的 MIB,它们是: SNMPv2 MIB和SNMPv2-M2M MIB(管理进程到管理 进程的MIB)。 4) SNMPv2的安全性比S N M P v 1大有提高。在 S N M P v 1中,从管理进程到代理进程的共同 体名称是以明文方式传送的。而 SNMP v2可以提供鉴别和加密。 厂家提供的设备支持 S N M P v 2的会越来越多,管理站将对两个版本的 S N M P代理进程进行 管理。[Routhier 1993]中描述了如何将 SNMPv1的实现扩展到支持 SNMPv2。

25.13 小结 S N M P是一种简单的、 S N M P管理进程和S N M P代理进程之间的请求 -应答协议。 M I B定义 了所有代理进程所包含的、能够被管理进程查询和设置的变量,这些变量的数据类型并不多。 所有这些变量都以对象标识符进行标识,这些对象标识符构成了一个层次命名结构,由 一长串的数字组成,但通常缩写成人们阅读方便的简单名字。一个变量的特定实例可以用附 加在这个对象标识符后面的一个实例来标识。 很多SNMP变量是以表格形式体现的。它们有固定的栏目,但有多少条记录并不固定。对 于SNMP来讲,重要的是对表格中的每一行如何进行标识(尤其当我们不知道表格中有多少条 记 录 时 ) 以 及 如 何 按 字 典 方 式 进 行 排 序 (“先列后行”的次序)。最后要说明的一点是: SNMP的get-next操作符对任何SNMP管理进程来讲都是最基本的操作。 然后我们介绍了下列的SNMP变量组:system、interface、address translation、IP、ICMP、TCP 和UDP。接着是两个例子,一个介绍如何确定一个接口的MTU,另外一个介绍如何获取路由器的 路由信息。 在本章的后面介绍了 S N M P的t r a p操作,它是当代理进程发生了某些重大事件后主动向管 理进程报告的。最后我们简单介绍了 A S N . 1和B E R,这两个概念比较繁琐,但所幸的是,它 对我们了解 SNMP并不十分重要,仅仅在实现 SNMP的时候才要用到。

习题 25.1 我们说过采用两个不同的UDP端口(161和162)可以使得一个系统既可以是管理进程,也可以 是代理进程。如果对管理进程和代理进程采用一个同样的端口,会出现什么情况? 25.2 用get-next操作,如何列出一张路由表的完整信息?

下载

第26章 Telnet和Rlogin:远程登录 26.1 引言 远程登录( Remote Login )是I n t e r n e t上最广泛的应用之一。我们可以先登录(即注册) 到一台主机然后再通过网络远程登录到任何其他一台网络主机上去,而不需要为每一台主机 连接一个硬件终端(当然必须有登录帐号)。 在TCP/IP网络上,有两种应用提供远程登录功能。 1) Telnet是标准的提供远程登录功能的应用,几乎每个 TCP/IP的实现都提供这个功能。它 能够运行在不同操作系统的主机之间。Telnet通过客户进程和服务器进程之间的选项协商机制, 从而确定通信双方可以提供的功能特性。 2) Rlogin起源于伯克利 U n i x,开始它只能工作在 U n i x系统之间,现在已经可以在其他操 作系统上运行。 在本章中,我们将介绍 Telnet和Rlogin。首先介绍 Rlogin,因为Rlogin比较简单。 Telnet是一种最老的Internet应用,起源于1969年的ARPANET。它的名字是“电信 网络协议(telecommunication network protocol)”的缩写词。 远程登录采用客户-服务器模式。图 2 6 - 1显示的是一个 Te l n e t客户和服务器的典型连接图 (对于Rlogin的客户和服务器连接图,我们可以画得更加简单)。 Telnet服务 器进程

Telnet客户 进程

登录 shell

伪终端 驱动

终端驱动 内核

内核 TCP连接

终端用户

图26-1 客户-服务器模式的Telnet简图

在这张图中,有以下要点需要注意: 1) Telnet客户进程同时和终端用户和 TCP/IP协议模块进行交互。通常我们所键入的任何信 息的传输是通过 TCP连接,连接的任何返回信息都输出到终端上。 2) Telnet服务器进程经常要和一种叫做“伪终端设备”(pseudo-terminal device)打交道, 至少在U n i x系统下是这样的。这就使得对于登录外壳 ( s h e l l )进程来讲,它是被 Te l n e t服务器进 程直接调用的,而且任何运行在登录外壳进程处的程序都感觉是直接和一个终端进行交互。 对于像满屏编辑器这样的应用来讲,就像直接在和终端打交道一样。实际上,如何对服务器 进程的登录外壳进程进行处理,使得它好像在直接和终端交互,往往是编写远程登录服务器

294

使用TCP/IP详解,卷1:协议

下载

进程程序中最困难的方面之一。 3) 仅仅使用了一条 TCP连接。由于客户进程必须多次和服务器进程进行通信(反之亦然), 这就必然需要某些方法,来描绘在连接上传输的命令和用户数据。我们在后面的内容中会介 绍Telnet和Rlogin是如何处理这个问题的。 4) 注意在图 2 6 - 1 中,我们用虚线框把终端驱动进程和伪终端驱动进程框了起来。在 TCP/IP实现中,虚线框的内容一般是操作系统内核的一部分。 Telnet客户进程和服务器进程一 般只是属于用户应用程序。 5) 把服务器进程的登录外壳进程画出来的目的是为了说明:当我们想登录到系统的时候, 必须要有一个帐号, Telnet和Rlogin都是如此。 对于Te l n e t和R l o g i n,如果比较一下它们客户进程和服务器进程源代码的数量,就可以知 道这两者的复杂程度。图 2 6 - 2显示了伯克利不同版本的 Te l n e t和R l o g i n客户进程和服务器进程 源代码的数量。 Telnet客户进程 Telnet服务器进程 源程序代 码行数

Rlogin客户进程 Rlogin服务器进程

图26-2 Telnet/ Rlogin/客户进程/服务器进程的源代码数量比较

现在,不断有新的 Telnet选项被添加到 Telnet中去,这就使得 Telnet实现的源代码数量大大 增加,而Rlogin依然变化不大,还是比较简单。 远程登录不是那种有大量数据报传输的应用。正如我们前面讲到的一样,客户进程和服 务器进程交互的分组大多比较小。 [Paxson 1993]发现客户进程发出的字节数(用户在终端上 键入的信息)和服务器进程端发出的字节数的数量之比是 1 : 2 0。这是因为我们在终端上键入 的一条短命令往往令服务器进程端产生很多输出。

26.2 Rlogin协议 R l o g i n的第一次发布是在 4 . 2 B S D中,当时它仅能实现 U n i x主机之间的远程登录。这就使 得R l o g i n比Te l n e t简单。由于客户进程和服务器进程的操作系统预先都知道对方的操作系统类 型,所以就不需要选项协商机制。在过去的几年中, R l o g i n协议也派生出几种非 U n i x环境的 版本。 RFC 1282 [Kantor 1991]详细说明了 Rlogin协议。类似于选路信息协议( RIP)的RFC,它 是R l o g i n用了许多年后才发布的。 [Stevens 1990]的第1 5章介绍了远程登录的客户进程及服务 器进程端的编程,并且给出了 R l o g i n 的客户进程及服务器进程的完整源代码。 [ C o m e r和 Stevens 1993]的第25章和第26章给出了Telnet的客户进程的实现细节和源代码。

下载

295

第26章 Telnet和Rlogin:远程登录使用

26.2.1 应用进程的启动 R l o g i n的客户进程和服务器进程使用一个 T C P连接。当普通的 T C P连接建立完毕之后,客 户进程和服务器进程之间将发生下面所述的动作。 1) 客户进程给服务器进程发送4个字符串:(a)一个字节的0;(b)用户登录进客户进程主机 的登录名,以一个字节的0结束;(c)登录服务器进程端主机的登录名,以一个字节的0 结束;(d)用户终端类型名,紧跟一个正斜杠“/”,然后是终端速率,以一个字节的0结 束。在这里需要两个登录名字,这是因为用户登录客户和服务器的名称有可能不一样。 由于大多满屏应用程序需要知道终端类型,所以终端类型也必须发送到服务器进程。发 送终端速率的原因是因为有些应用随着速率的改变,它的操作也有所变化。例如 vi编辑 器,当速率比较小的时候,它的工作窗口也变小。所以它不能永远保持同样大小的窗口。 2) 服务器进程返回一个字节的 0。 3) 服务器进程可以选择是否要求用户输入口令。这个步骤的数据交互没有什么特别的协 议,而被当作是普通的数据进行传输。服务器进程给客户进程发送一个字符串(显示 在客户进程的屏幕上),通常是 password: 。如果在一定的限定时间内(通常是 6 0秒) 客户进程没有输入口令,服务器进程将关闭该连接。 通常可以在服务器进程的主目录 (home directory)下生成一个文件(通常叫 .rhosts),该 文件的某些行记录了一个主机名和用户名。如果从该文件中已经记录的主机上用已经 记录的用户名进行登录,服务器进程将不提示我们输入口令。但是很多关于安全性的 文献,如[Curry 1992],强烈建议不要采用这种方法,因为这存在安全漏洞。 如果提示输入口令,那么我们输入的口令将以明文的形式发送到服务器进程。我们所 键入的每个字符都是以明文的格式传输的。所以某人只要能够截取网络上的原始传输 的分组,他就可以截获用户口令。针对这个问题,新版本的

R l o g i n客户程序,例如

4 . 4 B S D版本的客户程序,第一次采用了 K e r b e r o s安全模型。 K e r b e r o s安全模型可以避 免用户口令以明文的形式在网络上传输。当然,这要求服务器进程也支持

Kerberos

([Curry 1992]详细描述了 Kerberos安全模型)。 4) 服务器进程通常要给客户进程发送请求,询问终端的窗口大小(将在后面解释)。 客户进程每次给服务器进程发送一个字节的内容,并且接收服务器进程的所有返回信息。 这在19.2节中已经介绍过了。同样我们也采用了 Nagle算法(在 19.4节中曾经介绍),该算法可 以保证在速率较低的网络上,若干输入字节以单个 T C P报文段传输。操作其实很简单:用户 键入的所有东西被发送到服务器,服务器发送给客户的任何信息返回到用户的屏幕上。 另外,服务器和客户之间还可以互相发送命令。在介绍这些命令之前,先介绍需要用到 这些命令的场合。 26.2.2 流量控制 默认情况下,流量控制是由 R l o g i n的客户进程完成的。客户进程能够识别用户键入的 STOP和START的ASCII字符(Control_S和Control_Q),并且终止或启动终端的输出。 如果不是这样,每次我们为终止终端输出而键入的 C o n t r o l _ S字符将沿网络传输到服务器 进程,这时服务器进程将停止往网络上写数据。但是在写操作终止之前,服务器进程可能已 经往网络上写了一窗口的输出数据。也就是说,在输出停止之前,成千上万的数据字节还将

296

使用TCP/IP详解,卷1:协议

下载

在屏幕上显示。图 26-3显示了这个情况。 要在终端显示的全屏数据 终端用户

Rlogin 客户进程

Control_S→

Rlogin服 务器进程

图26-3 服务器进程执行STOP/START的情况

对于一个交互式用户来讲, Control_S字符的响应延时是较大的。 有时候,服务器的应用程序需要解释输入的每个字节,但又不想让客户对它的输入内容 进行处理,例如对控制字符如 C o n t r o l _ S和C o n t r o l _ Q进行特殊处理( e m a c s编辑器就是这样的 一个例子,它把 C o n t r o l _ S和C o n t r o l _ C作为自己的命令)。解决这个问题的办法就是由服务器 告诉客户是否要进行流量控制。 26.2.3 客户的中断键 当我们为中断服务器正在运行的进程而键入一个中断字符时(通常是

DELETE或

Control_C),会发生和流量控制相同的问题。这个情况和图 26-3所示的类似,在一条 TCP连接 的管道上,从服务器进程向客户进程正在发送大量的数据,而客户进程同时在向服务器进程传 输中断字符。而我们的本意是要中断字符尽快终止某个进程,使屏幕上不再有任何响应输出。 在流量控制和中断键这两种情况中,流量控制机制很少终止客户进程到服务器进程的数 据流。这个方向仅仅包含我们键入的字符。所以对于从客户输出到服务器的特殊输入字符 (Control_S和中断字符)不需要采用 TCP的紧急方式(urgent mode)。 26.2.4 窗口大小的改变 如果是窗口风格的显示方式,当应用程序在运行的时候,我们还可以动态地改变窗口的 大小。一些应用程序(典型的如那些操作整个窗口的应用程序,如全屏编辑器)需要知道窗 口大小的变化。目前大多数 Unix系统提供这种功能,可以告诉应用程序关于窗口大小的变化。 对于远程登录这种情况,窗口大小的变化发生在客户端,而运行在服务器端的应用程序 需要知道窗口大小变化。所以 R l o g i n的客户需要采用某些方法来通知服务器窗口大小变化的 情况以及新窗口的大小。 26.2.5 服务器到客户的命令 现在我们介绍通过 T C P连接, R l o g i n服务器进程可以发送给客户进程的 4条命令。问题是 只有一条 T C P连接可供使用,所以服务器进程必须给这些命令字节做标记,使得客户进程可 以从数据流中识别出这些是命令,而不是显示在终端上。所以我们将使用

T C P 的紧急方式

(在20.8节中曾经介绍)。 当服务器要给客户发送命令时,服务器就进入紧急方式,并且把命令放在紧急数据的最 后一个字节中。当客户进程收到这个紧急方式通知时,它从连接上读取数据并且保存起来, 直到读到命令字节(即紧急数据的最后一个字节)。这时候客户进程根据读到的命令,再决定 对于所读到并保存起来的数据是显示在终端上还是丢弃它。图 26-4介绍了这4个命令。 采用TCP紧急方式发送这些命令的一个原因是第一个命令(“清仓输出(flush output)”)需 要立即发送给客户,即使服务器到客户的数据流被窗口流量控制所终止。这种情况下,即服

297

第26章 Telnet和Rlogin:远程登录使用

下载

务器到客户的输出被流量控制所终止的情况是经常发生的,这是因为运行在服务器的进程的 输出速率通常大于客户终端的显示速率。另一方面,客户到服务器的数据流很少被流量控制 所终止,因为这个方向的数据流仅仅包含用户所键入的字符。 字节 0x02

0x10 0x20 0x80





清仓输出。客户丢弃所有从服务器收到的数据,直到命令字节(紧急数 据的最后一个字节)。客户还丢弃任何有可能被缓存的挂起输出 ( p e n d i n g output)。当服务器收到客户发出的中断命令时,就发送此命令 客户停止执行流量控制 客户继续进行流量控制处理 客户立即响应,将当前窗口大小发送给服务器,并在今后当窗口大小变 化时通知服务器。通常,当连接建立后,服务器就立即发送这个命令

图26-4 服务器到客户的Rlogin命令

回忆一下图 2 0 - 1 4中的例子,在那里我们介绍了即使窗口大小是 0时,紧急通知通过网络 进行传输的情况(在下节中,我们还将介绍一个类似的例子)。其他的 3个命令实时性并不特 别强,但为了简单起见,也采用了和第一个命令相同的技术。 26.2.6 客户到服务器的命令 对于客户到服务器的命令,只定义了一条命令,那就是:将当前窗口大小发送给服务器。 当客户的窗口大小发生变化时,客户并不立即向服务器报告,除非收到了服务器发来的 0 x 8 0 命令(图26-4中有介绍)。 同样,由于只存在一条 T C P连接,客户必须对在连接上传输的该命令字节进行标注,使 得服务器可以从数据流中识别出命令,而不是把它发送到上层的应用程序中去。处理的方法 就是在两个字节的 0xff后面紧跟着发送两个特殊的标志字节。 对于窗口大小命令,两个标志字节是 ASCII码的字符‘s’。之后是 4个16 bit长的数据(按 网络字节顺序),分别是:行数(例如, 25),每列的字符数(例如, 80),X方向的像素数量, Y方向的像素数量。通常情况下,后两个 1 6 b i t是0,因为在 R l o g i n服务器进程调用的应用程序 中,通常是以字符为单位来度量屏幕的,而不是像素点。 上面我们介绍的从客户进程到服务器进程的命令采用带内信令( in-band signaling),这是 因为命令字节和其他的普通数据一起传输。选择 0xff字节来表示这个带内信令的原因是:一般 用户的操作不会产生 0 x ff这个字节。所以说 R l o g i n是不完备的,如果我们采用某种方法,使得 通过键盘就可以产生两个连续的 0 x ff字节,而且正好在这之前是两个 A S C I I的‘s’字符,那 么下面的8个字节就会被误认为是窗口大小了。 图2 6 - 4中介绍的是从服务器到客户的 R l o g i n命令,由于大多数的 A P I采用的技术叫做“带 外数据(out-of-band data)”,所以我们就称它为带外信令 (out-of-band signaling)。但是回忆一 下在20.8节中对TCP紧急方式的讨论,在那里我们说紧急方式数据不是带外数据,命令字节是 按照普通数据流进行传输的,特殊之处是采用了紧急指针。 既然带内信令被用来传输从客户到服务器的命令,那么服务器进程必须检查从客户进程 收到的每个字节,看看是否有两个连续的 0 x ff字节。但是对于采用带外信令的、从服务器传 输到到客户的命令,客户进程不需要检查收到的每个字节,除非服务器进程进入了紧急方

298

使用TCP/IP详解,卷1:协议

下载

式。即使在紧急方式下,客户进程也仅仅需要留意紧急指针所指向的字节。而且由于从客 户进程到服务器的数据流量和相反方向的数据流量之比是 1 : 2 0,这就暗示带内信令适合于数 据量比较小的情况(从客户到服务器),而带外信令适合于数据量比较大的情况(从服务器 到客户)。 26.2.7 客户的转义符 通常情况下,我们向 Rlogin客户进程键入的信息将传输到服务器进程。但是有些时候,我 们并不需要把键入的信息传输到服务器,而是要和 R l o g i n客户进程直接通信。方法是在一行 的开头键入代字符 (tilde)“~”,紧跟着是下列 4个字符之一: 1) 以一个句号结束客户进程。 2) 以文件结束符(通常是 Control_D)结束客户进程。 3) 以任务控制挂起符(通常是 Control_Z)挂起客户进程。 4) 以任务控制延迟挂起符(通常是 Control_Y)来挂起仅仅是客户进程的输入。这时,不 管客户运行什么程序,键入的任何信息将由该程序进行解释,但是从服务器发送到客户的信 息还是输出到终端上。这非常适合当我们需要在服务器上运行一个长时间程序的场合,我们 既想知道该程序的输出结果,同时还想在客户上运行其他程序。 只有当客户进程的 Unix系统支持任务控制时,后两个命令才有效。

26.3 Rlogin的例子 在这里举两个例子:第一个是当 Rlogin会话建立的时候,客户和服务器的协议交互;从第 二个例子可以看到,当用户键入中断键以取消正在服务器运行的程序时,服务器将产生很多 输出。在图 19-2中,我们给出了通常情况下, Rlogin会话上的数据流交互情况。 26.3.1 初始的客户-服务器协议 图2 6 - 5显示的是从主机 b s d i到服务器 s v r 4的R l o g i n建立一个连接时的时间系列 (在图中,去 掉了通常的 TCP连接的建立过程,窗口通告以及服务类型信息)。 上节介绍的协议对应图中的报文段 1 ~ 9。客户发送一个字节的 0(报文段 1)之后发送 3个 字符串(报文段 3)。在本例中,这 3个字符串分别是: r s t e v e n s(客户的登录名)、r s t e v e n s (服务器的登录名)和 i b m p c 3 / 9 6 0 0(终端类型和速率)。当服务器确认了这些信息后回送一个 字节的0(报文段5)。 然后服务器发送窗口请求命令(报文段 7)。这是采用TCP紧急方式发送的,我们又一次看 到一个实现 ( S V R 4 )采用较老的但更普通的解释,即紧急指针指明的序号是紧急数据的最后一 个字节加1。客户回送12字节的数据:2字节的0xff,2字节的‘s’,4个16 bit长度的窗口数据。 下面的 4个报文段( 10, 12, 14 和1 6)是由服务器发送的,是从服务器操作系统的问候 (greeting)。之后报文段18是一个7字节长度的外壳进程提示符“ svr4%”。 客户输入的信息如图 1 9 - 2所示,每次发送一个字节。客户和服务器都可以主动中断该连 接。如果我们输入一个命令,让服务器的外壳程序终止运行,那么服务器将中断该连接。如 果我们给 R l o g i n客户键入一个转移符(通常是一个“ ~”),紧跟着一个句点或者是一个文件结 束符号,那么客户将主动关闭该连接。

299

第26章 Telnet和Rlogin:远程登录使用

下载

图2 6 - 5中,客户进程的端口号是 1 0 2 3,这是由 I A N A分配的(在 1 . 9节中介绍)。 Rlogin协议要求客户进程用小于1024的端口号,术语叫做保留端口。在Unix系统中,客 户进程一般不能使用保留端口号,除非客户进程具有超级用户权限。这是客户进程和服 务器进程相互鉴别的一部分,这种鉴别可以使得用户不需要口令而可以登录。 [Stevens 1990]详细讨论了客户进程和服务器进程相互鉴别的过程和有关保留端口号的问题。

1字节的0

1字节的0

命令0x80: 窗口大小请求 窗口大小信息

操作系统的问候

操作系统的问候

操作系统的问候

操作系统的问候

shell提示符

图26-5 Rlogin连接时间次序

26.3.2 客户中断键 让我们看一下另外一个例子,这个例子涉及到 T C P的紧急方式。当数据流已经终止时, 我们键入中断键。这个例子要用到前面讲到的很多 T C P算法如:紧急方式、糊涂窗口避免技 术、窗口流量控制和坚持计时器。在主机 s u n上运行客户进程。我们登录到主机 b s d i,向终 端输出一个大文本文件,然后键入 C o n t r o l _ S中断输出。当输出停止时,我们键入中断键 (DELETE)以异常方式中止该进程。

300

使用TCP/IP详解,卷1:协议

下载

sun % rlogin bsdi 所有操作系统的问候 bsdi % c a t / u s r / s h a r e / m i s c / t e r m c a p向终端输出大文件 大量的终端输出 键入C o n t r o l _ S以中断输出, 然后等待直到输出停止 ^? 键入中断键,而这被回显了, bsdi % 然后输出了提示符

下面这些要点是关于客户、服务器和连接的状态的概述: 1) 键入Control_S以停止终端的输出。 2) 用户终端的输出缓存很快被填满,所以 Rlogin的客户向终端的写操作被阻塞。 3) 此时客户也不能从网络连接上读取数据,所以客户的 TCP接收缓存也将被填满。 4) 当接收缓存已满时,客户进程的 TCP会向服务器进程的 TCP通告现在的接收窗口是 0。 5) 当服务器收到客户的窗口为 0时,将停止向客户发送数据,这样,服务器的发送缓存也 将被填满。 6) 由于发送缓存已满,所以 Rlogin服务器进程将停止。这样, Rlogin服务器将不能从服务 器运行的应用程序( cat)处读取数据。 7) 当cat程序的输出缓存也被填满时, cat也将停止。 8) 然后我们用中断键来终止服务器上的 c a t程序。这个命令从客户的 T C P传输到服务器 的TCP,这是因为该方向的数据传输没有被流量控制所终止。 9) cat应用程序收到中断命令并且终止。这使得它的输出缓存(也就是 Rlogin服务器进程 读取数据的地方)被清空,这将唤醒 Rlogin服务器进程。然后Rlogin服务器进程进入紧急方式, 向客户进程发送“清仓输出”命令( 0x02)。 图26-6概括了从服务器到客户的数据流(图中的序号就是下面将介绍的图中的时间系列)。 Rlogin服 务器进程

Rlogin 客户进程 4096字节的接收缓存

数据已收到,并也得到了 客户的确认信息,正等待 Rlogin客户进程来读

4096字节的发送缓存

紧急指针 Rlogin客户进程写的数 据,这也是服务器等 待发途的数据

图26-6 Rlogin例子中,服务器进程到客户进程的数据流概述

发送缓存的阴影部分是4096字节的缓存中没有被使用的部分。图26-7是该例子的时间系列。 在报文段 1 ~ 3,服务器进程向客户进程发送满长度(即 1 0 2 4字节)的 T C P报文段。由于此 时客户进程不能向终端写信息,客户进程也不能从网络上读数据,所以在报文段 4中,客户进 程向服务器进程发送 ACK确认时,告诉服务器进程此时接收窗口是 1024个字节。在报文段5中, 服务器进程发送的数据长度就不再是满长度的了。同样,报文段 6中客户进程的确认信号所带 的接收窗口大小是此时接收缓存的空余字节长度。那么在报文段 5中,客户进程ACK信号中为 什么接收窗口大小是 3 4 9而不是 0呢?这是因为如果发送的是 0(糊涂窗口避免技术),那么窗 口指针将右边界移动到了左边界,而这是绝对不能发生的(见 2 0 . 3节)。当服务器进程收到报

301

第26章 Telnet和Rlogin:远程登录使用

下载

文段6的ACK信号后,它就不能再发送全长的数据报了,这时候它就采用糊涂窗口症避免技术, 不发送任何东西,同时置一个 5秒的坚持计时器。当计时器超时,服务器进程就发送一个 3 4 9 字节大小的数据(如报文段 7)。由于此时客户进程依然不能输出接收缓存的信息,所以接收 缓存将被填满,客户进程将发送 ACK信号,此时接收窗口大小为 0(如报文段 8)。 这时候我们键入中断键并且以报文段 9显示的那样传输。此时的接收窗口大小依然为 0。 当服务器进程接收到该中断键后,服务器进程把它发送给应用程序( c a t),应用程序就终止。 由于应用程序被终端中断键所终止,应用程序就清空它的输出缓存。服务器进程发现该变化 后就通过 T C P紧急方式向客户进程发送“清仓输出”命令,这如报文段 1 0所示。注意命令字 节0x02放在第30146字节中(紧急指针减 1)。报文段10告诉客户进程在命令字节前还有 3419个 字节(从26727到30145)在服务器进程的发送缓存中等待发送。 报文段 1 0采用紧急通知方式发送,包含了服务器进程向客户进程发送的下一个字节(序 号是2 6 7 2 7)。它不包含“清仓输出”命令字节。记得在 2 2 . 2节中曾经介绍过,发送进程可以 发送一个字节的数据来试探对方的接收窗口是否关闭。报文段 1 0就是采用了这个原理。然后 客户进程 T C P就立即发送如报文段 11所示的数据。虽然此时接收窗口还是 0,但是在客户进程 内部,由于客户进程的 T C P收到了对方的紧急通知,它把该通知告诉客户进程,客户进程就 知道服务器进程已经进入了紧急方式了。

图26-7 Rlogin举例:当客户程停止输出然后终止服务器的应用程序的情况

302

使用TCP/IP详解,卷1:协议

下载

当Rlogin客户进程从它的 TCP收到了紧急通知,并且客户进程开始读取已经在输入缓存中 等待被读取的数据时,接收窗口就会重新打开(报文段 1 3)。然后服务器进程就开始正常发送 数据(报文段14, 15, 17和18)。注意报文段18的数据报中包含紧急数据的最后一个字节的数据 (序号30146),该字节包含服务器进程发送给客户进程的命令字节。当客户进程收到该命令后, 它就丢弃报文段 1 4、1 5、1 7和1 8所收到的数据,并且清空终端的输出缓存。在报文段 1 9中的 下两个字节是中断键的回显“ ^?”。最后一个报文段( 21)包含了客户进程的外壳提示符。 这个例子描述了当用户键入中断键后,连接的双方数据如何被存储的情况。如果这些动 作仅仅丢弃在服务器的 3 4 1 9个字节数据,而不丢弃已经在客户的 4 0 9 6个字节的数据,那么这 些已经在客户的终端输出缓存中的 4096字节数据将输出到终端上。

26.4 Telnet协议 Te l n e t协议可以工作在任何主机(例如,任何操作系统)或任何终端之间。

RFC 854

[Postel 和Reynolds 1983a]定义了该协议的规范,其中还定义了一种通用字符终端叫做网络虚 拟终端NVT(Network Virtual Terminal)。NVT是虚拟设备,连接的双方,即客户机和服务器, 都必须把它们的物理终端和 N V T进行相互转换。也就是说,不管客户进程终端是什么类型, 操作系统必须把它转换为 N V T格式。同时,不管服务器进程的终端是什么类型,操作系统必 须能够把NVT格式转换为终端所能够支持的格式。 N V T是带有键盘和打印机的字符设备。用户击键产生的数据被发送到服务器进程,服务 器进程回送的响应则输出到打印机上。默认情况下,用户击键产生的数据是发送到打印机上 的,但是我们可以看到这个选项是可以改变的。 26.4.1 NVT ASCII 术语NVT ASCII代表7比特的ASCII字符集,网间网协议族都使用 NVT ASCII。每个7比特 的字符都以 8比特格式发送,最高位比特为 0。 行结束符以两个字符 C R(回车)和紧接着的 L F(换行)这样的序列表示。以 \ r \ n来表 示。单独的一个 C R也是以两个字符序列来表示,它们是 C R和紧接着的 N U L(字节 0),以 \ r \ 0表示。 在下面的章节中可以看到, FTP, SMTP, Finger和Whois协议都以NVT ASCII来描述客户命 令和服务器的响应。 26.4.2 Telnet命令 Te l n e t通信的两个方向都采用带内信令方式。字节

0 x f f (十进制的 2 5 5 )叫做 I A C

(interpret as command,意思是“作为命令来解释”)。该字节后面的一个字节才是命令字节。 如果要发送数据 2 5 5,就必须发送两个连续的字节 2 5 5(在前面一节中我们讲到数据流是 N V T A S C I I,它们都是 7 b i t的格式,这就暗示着 2 5 5这个数据字节不能在 Te l n e t上传输。其实在 Te l n e t中有一个二进制选项,在 R F C 8 5 6 [ P o s t e l和Reynolds 1983b]中有定义,关于这点我们没 有讨论,该选项允许数据以 8bit进行传输)。图26-8列出了所有的Telnet命令。 由于这些命令中很多命令很少用到,所以对于一些重要的命令,如果在下面章节的例子 或叙述中遇到,我们再做解释。

303

第26章 Telnet和Rlogin:远程登录使用

下载 名称 EOF SUSP ABORT EOR SE NOP DM BRK IP AO AYT EC EL GA SB WILL WONT DO DONT IAC

代码(十进制)





文件结束符 挂起当前进程(作业控制) 异常中止进程 记录结束符 子选项结束 无操作 数据标记 中断 中断进程 异常中止输出 对方是否还在运行? 转义字符 删除行 继续进行 子选项开始 选项协商(图 26-9) 选项协商 选项协商 选项协商 数据字节255

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255

图26-8 当前一个字节是IAC(255)时的Telnet命令集

26.4.3 选项协商 虽然我们可以认为 Te l n e t连接的双方都是 N V T,但是实际上 Te l n e t连接双方首先进行交互 的信息是选项协商数据。选项协商是对称的,也就是说任何一方都可以主动发送选项协商请 求给对方。 对于任何给定的选项,连接的任何 一方都可以发送下面 4种请求的任意一 个请求。

发送方 1.

WILL

2.

WILL

3.

DO

4. 5.

DO WONT WONT

6.

DONT

1) WILL : 发 送 方 本 身 将 激 活 (enable)选项。 2) DO :发送方想叫接收端激活选 项。 3) WONT :发送方本身想禁止选 项。 4) DON’T:发送方想让接收端去禁

接收方 → ← → ← → ← → ← → ← → ←

DO DONT WILL

DONT WONT





发送方想激活选项 接收方说同意 发送方想激活选项 接收方说不同意 发送方想让接收方激活选项 接收方说同意 发送方想让接收方激活选项 接收方说不同意 发送方想禁止选项 接收方必须说同意 发送方想让接收方禁止选项 接收方必须说同意

止选项。 由于 Te l n e t规则规定,对于激活选

图26-9 Telnet选项协商的6种情况

项请求(如 1和2),有权同意或者不同意。而对于使选项失效请求(如 3和4),必须同意。这 样,4种请求就会组合出 6种情况,如图26-9所示。 选项协商需要 3个字节:一个 I A C字节,接着一个字节是 WILL, DO, WONT和D O N T这四

304

使用TCP/IP详解,卷1:协议

下载

者之一,最后一个ID字节指明激活或禁止选项。 现在,有 4 0多个选项是可以协商的。 A s s i g n e d Number RFC 文档中指明选项字节的值,并且 一些相关的 R F C文档描述了这些选项。图 2 6 10显示了在本章中会出现的选项代码。 Telnet的选项协商机制和 Telnet协议的大部 分内容一样,是对称的。连接的双方都可以发 起选项协商请求。但我们知道,远程登录不是 对称的应用。客户进程完成某些任务,而服务 器进程则完成其他一些任务。下面我们将看到, 某些 Te l n e t选项仅仅适合于客户进程(例如要 求激活行模式方式),某些选项则仅仅适合于

选项标识 (十进制) 1 3 5 6 24 31 32 33 34 36

名 称

RFC

回显 抑制继续进行 状态 定时标记 终端类型 窗口大小 终端速率 远程流量控制 行方式 环境变量

857 858 859 860 1091 1073 1079 1372 1184 1408

图26-10 本章中将讨论的Telnet选项代码

服务器进程。 26.4.4 子选项协商 有些选项不是仅仅用“激活”或“禁止”就能够表达的。指定终端类型就是一个例子, 客户进程必须发送用一个 ASCII字符串来表示终端类型。为了处理这种选项,我们必须定义子 选项协商机制。 在RFC1091[VanBokkelen 1989]中定义了如何表示终端类型这样的子选项协商机制。首先 连接的某一方(通常是客户进程)发送 3个字节的字符序列来请求激活该选项。

这里的 2 4(十进制)是终端类型选项的 I D号。如果收端(通常是服务器进程)同意,那 么响应数据是:

然后服务器进程再发送如下的字符串:

该字符串询问客户进程的终端类型。其中 S B是子选项协商的起始命令标志。下一个字节 的“24”代表这是终端类型选项的子选项(通常SB后面的选项值就是子选项所要提交的内容)。 下一个字节的“ 1”表示“发送你的终端类型”。子选项协商的结束命令标志也是 I A C,就像 SB是起始命令标志一样。如果终端类型是 ibmpc,客户进程的响应命令将是: <IAC, SB, 24, 0‘I’, ‘B’, ‘M’, ‘P’, ‘C’, IAC, SE> 第4个字节“0”代表“我的终端类型是”(在Assigned Numbers RFC文档中有正式的关于 终端类型的数值定义,但是最起码在 U n i x系统之间,终端类型可以用任何对方可理解的数据 进行表示。只要这些数据在 t e r m c a p或t e r m i n f o数据库中有定义)。在Te l n e t子选项协商过程中, 终端类型用大写表示,当服务器收到该字符串后会自动转换为小写字符。 26.4.5 半双工、一次一字符、一次一行或行方式 对于大多数 Telnet的服务器进程和客户进程,共有 4种操作方式。 1. 半双工

305

第26章 Telnet和Rlogin:远程登录使用

下载

这是Telnet的默认方式,但现在却很少使用。 NVT默认是一个半双工设备,在接收用户输 入之前,它必须从服务器进程获得 GO AHEAD(GA)命令。用户的输入在本地回显,方向是 从NVT键盘到NVT打印机,所以客户进程到服务器进程只能发送整行的数据。 虽然该方式适用于所有类型的终端设备,但是它不能充分发挥目前大量使用的支持全双 工通信的终端功能。 RFC 857 [Postel 和Reynolds 1983c]定义了ECHO选项,RFC 858 [Postel 和Reynolds 1983d]定义了SUPPRESS GO AHEAD(抑制继续进行)选项。如果联合使用这两 个选项,就可以支持下面将讨论的方式:带远程回显的一次一个字符的方式。 2. 一次一个字符方式 这和前面的 Rlogin工作方式类似。我们所键入的每个字符都单独发送到服务器进程。服务 器进程回显大多数的字符,除非服务器进程端的应用程序去掉了回显功能。 该方式的缺点也是显而易见的。当网络速度很慢,而且网络流量比较大的时候,那么回 显的速度也会很慢。虽然如此,但目前大多数 Telnet实现都把这种方式作为默认方式。 我们将看到,如果要进入这种方式,只要激活服务器进程的 SUPPRESS GO AHEAD选项 即可。这可以通过由客户进程发送 DO SUPPRESS GO AHEAD(请求激活服务器进程的选项) 请求完成,也可以通过服务器进程给客户进程发送 WILL SUPPRESS GO AHEAD(服务器进 程激活选项)请求来完成。服务器进程通常还会跟着发送 WILL ECHO,以使回显功能有效。 3. 一次一行方式 该方式通常叫做准行方式( kludge line mode),该方式的实现是遵照 RFC 858的。该RFC 规定:如果要实现带远程回显的一次一个字符方式, E C H O选项和SUPPRESS GO AHEAD选 项必须同时有效。准行方式采用这种方式来表示当两个选项的其中之一无效时, Telnet就是工 作在一次一行方式。在下节中我们将介绍一个例子,可以看到如何协商进入该方式,并且当 程序需要接收每个击键时如何使该方式失效。 4. 行方式 我们用这个术语代表实行方式选项,这是在 RFC 1184[Borman 1990]中定义的。这个选项 也是通过客户进程和服务器进程进行协商而确定的,它纠正了准行方式的所有缺陷。目前比 较新的Telnet实现支持这种方式。 图2 6 - 11是不同的 Te l n e t客户进程和服务器进程之间默认的操作方式。“c h a r”表示一次一 个字符方式,“kludge”表示准行方式,“linemode”表示如RFC 1184定义的实行方式。 客户端

服 务 器 端

图26-11 不同的Telnet客户进程和服务器进程之间默认的操作方式

从图中可以看出,只有当客户进程和服务器进程都是 B S D / 3 8 6或4 . 4 B S D的时候才支持实 行方式。当服务器进程的操作系统是这两者之一时,如果客户进程不支持实行方式,才会协 商进入准行方式。从图中还可以看出,其实任何类型的客户进程和服务器进程都支持准行方 式,但是一般都不把它作为默认方式,除非服务器进程指定。

306

使用TCP/IP详解,卷1:协议

下载

26.4.6 同步信号 Te l n e t以Data Mark命令(即图2 6 - 8中的D M)作为同步信号,该同步信号是以 T C P紧急数 据形式发送的。 D M命令是随数据流传输的同步标志,它告诉收端回到正常的处理过程上来。 Telnet的双方都可以发送该命令。 当一端收到对方已经进入了紧急方式的通知后,它将开始读数据流,一边读一边丢弃所读 的数据,直到读到Telnet命令为止。紧急数据的最后一个字节就是 DM字节。采用TCP紧急方式 的原因就是:即使TCP数据流已经被TCP流量控制所终止, Telnet命令也可以在连接上传输。 在下节中我们将看到 Telnet同步信号的使用情况。 26.4.7 客户的转义符 和R l o g i n的客户进程一样, Te l n e t客户进程也可以使用户直接和客户进程进行交互,而不 是被发送到服务器进程。通常客户的转义字符是 C o n t r o l _ ](c o n t r o l键和右中括号键,通常以 “^]”表示)。这使得客户进程显示它的提示符,通常是“ t e l n e t >”。这时候有很多命令可供 用户选择,以改变连接的特性或打印某些信息。大多数的 U n i x客户进程提供“ h e l p”命令, 该命令将显示所有可用的命令。 在下节中我们将看到客户进程转义的例子,以及此时可以输入的命令。

26.5 Telnet举例 在这里我们将介绍在三种不同的操作方式下 Te l n e t选项协商的情况。这些方式包括:单字 符方式、实行方式和准行方式。同样我们还将讨论当用户在服务器端按了中断键退出了一个 正在运行的进程后,系统的运行情况。 26.5.1 单字符方式 首先介绍基本的单字符方式,该方式类似于 Rlogin。用户在终端输入的每个字符都将由终 端发送到服务器进程,服务器进程的响应也将以字符方式回显到终端上。在这里运行的是一 个新的客户进程 B S D / 3 8 6,它试图激活很多新的选项,服务器进程还是运行老的 S V R 4,我们 将看到很多选项被服务器拒绝。 为了看到服务器和客户机之间选项协商的内容,我们将激活客户进程的一个选项来显示 所有的选项协商。同样我们运行 t c p d u m p来获得数据报交换的时间次序。图 26-12显示了这个 交互会话。 在图中,我们已经对由 S E N T或R C V D开头的选项协商的每一步都进行了标注。关于每一 步的解释如下: 1) 客户发起SUPPRESS GO AHEAD选项协商。由于 GO AHEAD命令通常是由服务器发送 给客户的,而且客户希望服务器激活该选项,因此该选项的请求方式是 D O(由于激活这一选 项将会禁止 G A命令的发送,上述过程很容易让人混淆)。在第 1 0行可以看到服务器进程同意 该选项。 2) 客户进程要按照在RFC 1091[VanBokkelen 1989]中的定义发送终端类型。这对Unix类型的 客户进程来讲是很普通的。因为客户进程要激活本地的选项,所以该选项的请求方式是WILL。

307

第26章 Telnet和Rlogin:远程登录使用

下载

调用客户进程,不带任何命令行选项 告诉客户进程显示所有的选项协商过程 现在和服务器建立连接

(后面将讨论的行序号)

我们键入用户和口令,服务进程不回显这些数据, 然后操作系统问候输出,然后是外壳提示符

图26-12 Telnet双方选项协商的初始化过程

3) NAW S的意思是“协商窗口大小”,它在RFC 1073 [Wa i t z m a n ]中有定义。如果服务器 进程同意该选项(实际上不同意,见 11行),客户进程就要发送终端窗口的行、列大小的子选 项。而且只要窗口大小发生变化,客户进程随时都将向服务器进程发送这一子选项(这和图 26-4中Rlogin的0x80命令类似)。 4) TSPEED 选项允许发送方(通常是客户进程)发送它的终端速率,这在

RFC 1079

[Hedrick 1988b]中有定义。如果服务器进程同意(实际上不同意,见 1 2行),客户进程将发送 其发送速率和接收速率的子选项。 5) LFLOW代表“本地流量控制”,这在RFC1371 [Hedrick 和Borman 1992]中定义。客户 进程给服务器进程发送该选项,表示客户进程希望用命令方式激活或禁止流量控制。如果服 务器进程同意(实际上不同意,见 1 3行),只要C o n t r o l _ S和C o n t r o l _ Q进程需要在客户进程和 服务器进程进行切换,客户进程都要向服务器进程发送子选项(这类似于图 2 6 - 4中R l o g i n的 0 x 1 0和0 x 2 0命令)。正如在关于 R l o g i n的讨论中我们所提到的那样,由客户进程进行流量控制 的效果比由服务器进程来完成要好。 6) LINEMODE代表在26.4中所说的实行方式。所有终端字符的处理由 Telnet客户进程完成 (例如回格,删除行等),然后整行发送给服务器进程。在本节后面,我们将介绍一个例子。 该选项同样被服务器进程拒绝,如 14行所示。 7) ENVIRON选项允许客户进程把环境变量发送给服务器进程,这在 RFC 1408 [Borman

308

使用TCP/IP详解,卷1:协议

1 9 9 3 a ]中有定义。这样就可以把客户进程的用户环境变量自动传播到服务器进程。在

下载 1 5行,

服务器进程拒绝该选项( U n i x中的环境变量通常是大写字母,紧跟一个等号,然后是一个字 符串值,当然这只是一个惯例而已)。默认情况下, BSD/386 Te l n e t客户进程发送两个环境变 量:D I S P L AY和P R I N T E R,前提是这两个变量已经定义并且有效。 Te l n e t用户可以定义其他 一些要发送的环境变量。 8) STAT U S选项(RFC 859 [Postel 和Reynolds 1983e]中定义)允许连接的一方询问对方 对Te l n e t选项目前状态的理解。在这个例子中,客户进程要求对方激活选项( D O)。如果服务 器进程同意(实际上不同意,见 1 6行),客户进程就可以要求服务器进程以子选项的形式发送 它的状态值。 9) 这是服务器进程的第一个响应。服务器进程同意激活终端类型选项(几乎所有的 U n i x 类型的服务器进程都支持该选项)。但现在客户进程还不能立即发送它的终端类型。它必须要 等到服务器进程用子选项的形式询问终端类型的时候才能够发送( 17行)。 10) 服务器进程同意抑制发送 GO AHEAD命令。 11) 服务器进程不同意客户进程发送它的窗口大小。 12) 服务器进程不同意客户进程发送它的终端速率。 13) 服务器进程不同意客户进程实施流量控制。 14) 服务器进程不同意客户进程激活行方式选项。 15) 服务器进程不同意客户进程发送环境变量。 16) 服务器进程不发送状态信息。 17) 这是服务器进程要求客户进程发送终端类型的子选项。 18) 客户进程把终端类型“ IBMPC3”以6字节的字符串形式发送给服务器进程。 19) 服务器进程要求客户进程发起请求,要求服务器进程激活回显选项。这是本例中服务 器进程第一次主动发起选项协商。 20) 客户进程同意由服务器进程实现回显功能。 21) 服务器进程要求客户进程实现回显功能。这个命令是多余的,它只是将前两行进行了 交换。这是目前大多数 U n i x的Te l n e t服务器进程判断客户进程是否运行 4 . 2 B S D或更新的 B S D 版本时的一个方法。如果客户进程回送 WILL ECHO ,就表明客户进程运行的是老版本的 4.2BSD,不支持TCP的紧急方式(在这种情况下就不能采用 TCP紧急方式)。 22) 客户进程回送WONT ECHO,表示它不是一台 4.2BSD主机。 23) 对于客户进程回送的 WONT ECHO,服务器进程以 DONT ECHO作为响应。 图26-13显示的是本例中服务器进程和客户进程交互的时间系列(去掉了连接建立部分)。 报文段1包含了图 2 6 - 1 2中的1 ~ 8行。该报文段中包含 2 4个字节数据,每个选项占 3个字节。 这是客户进程发起的选项协商。该报文段显示多个 Telnet选项可以打在一个 TCP段中发送。 报文段3是图2 6 - 1 2中的第9行,即DO TERMINAL TYPE命令。报文段 5包含下面的 8个选 项协商中服务器进程的响应,即图 2 6 - 1 2中的 1 0 ~ 1 7行。该报文段的长度是 2 7个字节,因为 1 0 ~ 1 6行是常规选项,每个占 3个字节,而 1 7行的子选项部分占 6个字节。报文段 6包含1 2个字 节,和18行对应,这是客户发送它的终端类型的子选项。 报文段 8(5 3个字节)包含两个 Te l n e t命令和 4 7字节的输出数据。前面的两个服务器进程 发送Telnet命令占6字节,:WILL ECHO和DO ECHO(19和21行)。后47个字节的数据是:

309

第26章 Telnet和Rlogin:远程登录使用

下载

\r\n\r\nUNIX(r) System V Release 4.0 (svr4) \r\n\r\0\r\n\r\0 前面4个字节数据在字符串输出之前产生两个空行。两字节的字符序列“ \ r \ n”在Te l n e t中 被认为是换行命令,而两字节的字符序列“ \ r \ 0”则被认为是回车命令。这表明数据和命令可 以在一个数据段中传输。 Te l n e t服务器进程和客户进程必须扫描接收到的每个字符,寻找 I A C 字节并执行它后续的命令。

图26-13 Telnet服务器进程和客户进程选项协商初始化过程

报文段9包含客户进程发送的最后两个选项: 2 0和2 2行。2 3行是报文段1 0的响应,也是服 务器进程发送的最后一个选项数据。 从现在开始,双方就可以交互数据了。当然在交互数据的过程中还可以进行选项协商, 我们在该例子中就不多介绍了。报文段 1 2是服务器进程发送的提示符“ l o g i n :”。报文段 1 4是 用户输入的登录用户名的第一个字符,它的回显在报文段 1 5中。这和我们在 1 9 . 2节中介绍的 Rlogin交互类似:客户进程每次发送一个字符,服务器进程完成回显工作。 图2 6 - 1 2中的选项协商由客户进程初始化的,但是在本书中我们已经介绍了用 Telnet客户进程连接某些标准服务器进程如:日间( daytime)服务器、回显(echo)服务 器等情况。当然我们介绍这些的目的是为了描述 TCP的各种特性。但考察这些例子中

310

使用TCP/IP详解,卷1:协议

下载

的分组交换,如图 18-1,我们并没有看到客户进程发起的选项协商。为什么?这是因 为在Unix系统中,除非使用标准的Telnet端口号23,否则客户进程不进行选项协商。这 个特性使得Telnet客户进程可以使用标准的NVT同其他一些非Telnet服务器进程交换数 据。我们已经在日间服务器、回显服务器和丢弃(discard)服务器中使用了这个特性,在 后面章节介绍FTP和SMTP服务器的时候我们还将使用该特性。 26.5.2 行方式 为了描述 Te l n e t的行方式选项协商过程,我们在主机 b s d i运行客户进程,服务器是位于 v a n g o g h . v s . b e r k e l e y . e d u节点运行 4.4BSD操作系统的一台主机。 BSD/386和4.4BSD都 支持这个选项。 我们不详细讨论所有的报文、选项和子选项协商过程,因为这个过程和前面的例子类似, 而且对于行方式选项我们已经论述得比较清楚。下面我们仅仅讨论在选项协商中的一些区别。 1) 对于B S D / 3 8 6希望协商的选项例如:窗口大小、本地流量控制、状态、环境变量和终 端速率等, 4.4BSD服务器进程都支持。 2) 4 . 4 B S D服务器进程将协商一个 B S D / 3 8 6客户进程不支持的新选项:鉴别(为避免以明 文形式在网络上传输用户口令)。 3) 和上个例子一样,客户进程发送 WILL LINEMODE选项,由于服务器进程支持该选项, 所以服务器进程发送DO LINEMODE。此时客户进程以子选项形式给服务器进程发送16个 特定字符。这些字符是能影响客户进程的特定终端字符值:如中断字符,文件结束符等。 服务器进程给客户进程发送一个子选项,让客户进程处理所有的输入,执行所有的编 辑功能(删除字符,删除行等)。客户进程把除控制字符以外的字符以行的形式发送给 服务器进程。服务器进程还要求客户进程把所有中断键和信号键转换为相应的 Telnet字 符。例如中断键是 Control_C,我们可以按 Control_C来中断服务器端的某个进程。客户 进程必须把 Control_C转换为Telnet的IP命令()传输给服务器进程。 4) 当用户输入口令时情况也有所不同。在 R l o g i n和一次一字符方式的 Te l n e t中,都是由服 务器进程负责回显,所以当服务器进程读到口令时,它并不回显这些字符。但在行方 式中由客户进程负责回显。下面这些交互过程将处理这种情况: (a) 服务器进程发送 WILL ECHO,以告诉客户进程:服务器进程将处理回显。 (b) 客户进程回送DO ECHO。 (c) 服务器进程向客户进程发送字符串 Password:,客户进程把它发送到终端上。 (d) 然后用户输入口令,当用户按下 R E T U R N键的时候,客户进程把口令发送给服务器 进程。此时口令不回显,因为客户进程认为服务器进程将回显它。 (e) 由于口令的结束符 R E T U R N没有回显,服务器进程发送两字节字符序列 C R和L F以 移动光标。 (f) 服务器进程发送 WONT ECHO。 (g) 客户进程回送DONT ECHO。然后继续由客户进程负责回显。 一旦登录完成,客户进程将把数据以整行的方式发送给服务器进程。这就是行方式选项 的目的。行方式大大地减少了客户进程和服务器进程之间的数据交互数量,而且对于用户的

311

第26章 Telnet和Rlogin:远程登录使用

下载

击键(也就是回显和编辑)提供更快的响应。图 2 6 - 1 4显示的是当我们输入命令时,在行方式 连接下分组交换的情况。 Vangogh % date

(去掉了业务种类信息和窗口通告信息)。

图26-14 Telnet行方式下客户进程向服务器进程发送命令的情况

把它和在 R l o g i n中输入同样命令(图 1 9 - 2)时的情况进行一下比较。我们看到在 Te l n e t行 方式下只需要 2个报文段(一个包含数据,另一个用于 A C K,连同 I P和T C P首部共 8 6字节), 而在R l o g i n中要发送 1 5个报文段( 5个有键入的数据, 5个有回显的数据, 5个是A C K,共6 11 字节)。可见节省的数据量是非常可观的。 如果在服务器端运行一个需要进入单个字符方式的应用程序(例如 v i编辑器)会怎么样 呢?实际上将发生如下的一些交互: 1) 当服务器的应用程序启动了,并改变其伪终端方式时, Telnet服务器进程被通告需要进 入单个字符方式。然后服务器发送 WILL ECHO命令和行方式子选项,以告知客户不要再以行 方式工作,转而进入单个字符方式。 2) 客户响应以 DO ECHO,并确认行方式子选项。 3) 应用程序在服务器上运行。我们键入的每个字符将发送到服务器(当然要强制使用 Nagle算法),此时服务器将处理必要的回显工作。 4) 当应用程序终止时,就恢复其伪终端方式,并通告 Telnet服务器。服务器将向客户发送 WONT ECHO命令,同时发送行方式子选项,告诉客户恢复进入行方式。 5) 客户响应DONT ECHO,确认进入行方式。 上述情况同我们键入口令之间的区别表明:回显功能和单个字符方式与一次一行方式没 有依赖关系。当我们键入口令时,回显功能必须失效,但一次一行方式有效。对于一个全屏 应用来讲,例如编辑器,回显必须失效而单个字符方式必须有效。 图26-15概括了Rlogin和Telnet不同方式之间的差异。

312

使用TCP/IP详解,卷1:协议

应用程序

下载

客户进程发送 一次一字符 • •

Rlogin Telnet Telnet,行方式 Telnet,行方式 Telnet,行方式

客户进程回显?

例 子

一次一行 否 否 是 否 否

• • •

正常命令 键入我们的口令 vi编辑器

图26-15 Rlogin和不同方式的Telnet之间的比较

26.5.3 一次一行方式(准行方式) 从图2 6 - 11可以看出,如果客户不支持行方式,那么较新的服务器支持行方式选项,它也 将转入准行方式 (Kludge line mode)。我们同时指出所有的客户进程和服务器进程都支持准行 方式,但它不是默认方式,必须由客户进程或用户特地激活它。让我们来看看如何用 Telnet选 项激活准行方式。 首先介绍当客户进程不支持行方式时, BSD/386服务器进程如何协商进入该方式。 1) 当客户进程不同意服务器进程激活行方式的请求时,服务器进程发送

DO TIMING

MARK选项。RFC 860 [Postel 和Reynolds 1983f]定义了这个 Telnet选项。它的作用是让收发双 方同步,关于这个问题将在本节的后面讲到用户键入中断键时讨论。该选项只是用来判断客 户进程是否支持准行方式。 2) 客户响应WILL TIMING MARK,表明支持准行方式。 3) 服务器发送WONT SUPPRESS GO AHEAD和WONT ECHO选项,告诉客户它希望禁 止这两个选项。我们在前面已经强调:单个字符方式下是假定

SUPPRESS GO AHEAD 和

ECHO选项同时有效的,所以禁止两个选项就进入了准行方式。 4) 客户响应DONT SUPPRESS GO AHEAD和DONT ECHO命令。 5) 服务器发送 login:提示符,然后用户键入用户名。用户名是以整行的方式发送给服务 器,回显由客户进程在本地处理。 6) 服务器发送 P a s s w o r d :提示符和 WILL ECHO命令。这将使客户进程的回显失效,因 为此时客户进程认为服务器进程将处理回显工作,所以用户键入的口令就不回显到屏幕上。 客户响应DO ECHO命令。 7) 我们键入口令。客户以整行方式发送到服务器。 8) 服务器发送 WONT ECHO命令,使得客户重新激活回显功能,客户响应 DONT ECHO。 从此以后的普通命令处理过程就和行方式相似了。客户进程负责所有的编辑和回显,并 以整行的方式发送给服务器进程。 在图2 6 - 11中,我们已经强调:所有标注为“ c h a r”的记录都支持准行方式,只不过默认 是单个字符方式罢了。如果要客户进入行方式,我们就能很容易看到选项协商的过程: 客户是sun,服务器是svr 4 键入Control]以和Telnet客户进程通信(无回显) 检验现在是否在一次一字符方式下

313

第26章 Telnet和Rlogin:远程登录使用

下载

注意选项协商过程 切换到准行方式 客户发送这两个选项 服务器把WONT做为上述两个选项协商的响应

这将使Telnet会话进入准行方式,此时 SUPPRESS GO AHEAD和ECHO选项都是失效的。 如果在服务器端运行如 vi编辑器这样的应用程序,同样会有行方式下遇到的问题。当要运 行这样的应用程序时,服务器进程必须告诉客户进程从准行方式切换到单字符方式。当应用 程序结束时,必须告诉客户进程返回到准行方式。下面是这个过程需要用到的技术要点。 1) 当应用程序改变其伪终端方式并通知服务器进程时,服务器进程将进入单字符方式。 服务器进程向客户进程发送 WILL SUPPRESS GO AHEAD和WILL ECHO,这将使客户进程进 入单字符方式。 2) 客户进程回送DO SUPPRESS GO AHEAD和WILL ECHO。 3) 应用程序开始在服务器端运行。 4) 当应用程序结束并改变其伪终端方式时,服务器进程发送

WONT SUPPRESS GO

AHEAD和WONT ECHO命令,使得客户进程返回准行方式。 5) 客户进程回送DONT SUPPRESS GO AHEAD和DONT ECHO命令,告诉服务器进程它 已经回到了准行方式。 图26-16概括了单个字符方式及准行方式中不同的SUPPRESS GO AHEAD和ECHO选项设置。 方







SUPPRESS GO AHEAD

ECHO

一次一字符





准行方式下的 vi编辑器

准行方式

×

×

正常命令

准行方式

×



键入我们的口令

图26-16 准行方式下Telnet选项的设置

26.5.4 行方式:客户中断键 看一下当用户键入中断键时 Te l n e t将发生什么情况。假定在客户主机 b s d i 和服务器 c a n g o g h . c s . b e r k e l e y . e d u之间建立了一个 Te l n e t会话。图 2 6 - 1 7显示了当用户键入中断 键后的时间系列(去掉了窗口通告和服务类型)。 报文段1中显示的是中断键(通常是Control_C或DELETE)已经转换为Telnet的IP(中断进程) 命令:。下面的3个字节:,组成了Telnet的DO TIMING MARK选项。这 个标志由客户进程发送,必须使用WILL或WONT响应。所有在响应前收到的数据都要丢弃(除 非是Telnet命令) 。这是服务器进程和客户机端的同步过程。报文段1没有采用TCP紧急方式。 Host Requirements RFC叙述了IP命令不能使用Telnet的同步信号来发送。如果可以的 话,那么的后面将跟随,同时紧急指针指向DM字节。大多数的 Unix Telnet 客户有一个选项来使用同步信号发送 IP命令,但是这个选项默认是不用的 (正如我们这里看到的) 。

314

使用TCP/IP详解,卷1:协议

下载

键入中断键

图26-17 行方式下键入中断键后的情况

报文段 2是服务器进程对 DO TIMING MARK 选项的响应。紧随其后的是报文段 3和4中 Telnet的同步信号:。报文段3中的紧急指针指向将在报文段 4中发送的DM字节。 如果服务器进程到客户进程的窗口已满,那么客户进程发送了如报文段 1中的I P命令后就 丢弃收到的所有数据。即使服务器进程被 T C P流量控制所终止而不能发送如报文段 2、3和4中 的数据,紧急指针仍然可以发送。这和图 26-7中的Rlogin类似。 为什么同步信号要分为两个数据段发送( 3和4)?原因就是我们在 20.8节中详细讨论 TCP 紧急指针时提到的情况。有关主机需求的 R F C中提到紧急指针应指向紧急数据的最后一个字 节,而很多衍生于伯克利的系统中,紧急指针指向紧急数据的倒数第 2个字节(回忆一下在图 2 6 - 6中,紧急指针指向命令字节的前一个字节)。Te l n e t服务器进程故意把同步信号的第 1个字 节作为紧急数据,它知道紧急指针将指向下 1个字节(即 D M字节),而I A C字节和紧急指针必 须立即发送,在下一步才发送 DM字节。 最后一个报文段 6发送的是数据,它是服务器进程发生的提示符。

26.6 小结 本章我们介绍了 R l o g i n和Te l n e t操作。两者都提供了从客户进程远程登录到服务器进程, 是我们能够在服务器端运行程序的方法。 这两个应用是不同的。 R l o g i n假定连接的双方都是 U n i x系统,所以只提供一个选项,它 是1个简单的协议。 Telnet则不同,它用于在不同类型的主机之间建立连接。 为了支持这种多机环境, Te l n e t提供客户进程和服务器进程的选项协商机制。如果连接的 双方都支持这些选项,则可以增强一些功能。对于比较简单的客户进程和服务器进程,它可 以提供Telnet的基本功能,而当双方都支持某些选项时,它又可以充分利用双方的新特性。 我们介绍了 Te l n e t的选项协商机制,也介绍了 3种数据传输的方式:单字符方式、准行方 式和实行方式。现在的趋势是只要有可能,就尽量工作在准行方式下。这样可以减少网络上 的数据量,同时为交互用户提供更好的行编辑和回显的响应。 图26-18概括并比较了Rlogin和Telnet的不同特性。

315

第26章 Telnet和Rlogin:远程登录使用

下载 特



Rlogin

Telnet

运输协议 分组方式

一个TCP连接。使用紧急方式 总是一次一字符,远程回显

流量控制

通常由客户完成,可以被服务器禁止

终端类型 终端速率 窗口大小 环境变量 自动登录

总是提供 总是提供 大多数服务器支持此选项 不支持 默认。提示用户键入口令,口令以明 文发送。较新的版本支持 Kerberos方式 的登录

一个TCP连接。使用紧急方式。 通常的默认是一次一字符,远程 回显。带客户回显的准行方式也支 持带回显的实行模式。当服务器上 的应用进程请求时,总是一次一字 符的方式 通常由服务器完成,选项允许客 户来完成 选项,通常被支持 选项 选项 选项 默认是键入登录名和口令。口令 以明文发送。较新的版本支持鉴别选 项

图26-18 Rlogin和Telnet的不同特性

R l o g i n服务器和 Te l n e t服务器通常都将设置 T C P的保活选项以检测客户主机是否崩溃(如 果服务器的 T C P实现支持,见第 2 3章)。这两种应用都采用了 T C P紧急方式,以便即使从服务 器到客户的数据传输被流量控制所终止,服务器仍然可以向客户发送命令。

习题 26.1 在图26-5中,标出所有延迟的 ACK。 26.2 在图26-7中,为什么要发送报文段 12? 26.3 我们说过 R l o g i n 客户进程必须使用保留端口号(见 1 . 9 节)(通常 R l o g i n客户使用 512~1023之间的保留端口)。这会给主机带来什么限制?有没有解决的办法? 26.4 阅读RFC 1097,它描述了 Telnet的阈下报文 (subliminal-message)选项。

下载

第27章 FTP:文件传送协议 27.1 引言 F T P是另一个常见的应用程序。它是用于文件传输的 I n t e r n e t标准。我们必须分清文件传 送( file transfer )和文件存取 (file access) 之间的区别,前者是 F T P提供的,后者是如 N F S (S u n的网络文件系统,第 2 9章)等应用系统提供的。由 F T P提供的文件传送是将一个完整的 文件从一个系统复制到另一个系统中。要使用 FTP,就需要有登录服务器的注册帐号,或者通 过允许匿名 FTP的服务器来使用(本章我们将给出这样的一个例子)。 与Telnet类似,FTP最早的设计是用于两台不同的主机,这两个主机可能运行在不同的操作系 统下、使用不同的文件结构、并可能使用不同字符集。但不同的是,Telnet获得异构性是强制两端 都采用同一个标准:使用7比特ASCII码的NVT。而FTP是采用另一种方法来处理不同系统间的差 异。FTP支持有限数量的文件类型(ASCII,二进制,等等)和文件结构(面向字节流或记录) 。 参考文献959 [Postel 和 Reynolds 1985] 是FTP的正式规范。该文献叙述了近年来文件传输 的历史演变。

27.2 FTP协议 FTP与我们已描述的另一种应用不同,它采用两个 TCP连接来传输一个文件。 1) 控制连接以通常的客户服务器方式建立。服务器以被动方式打开众所周知的用于 F T P的端口( 2 1),等待客户的连接。客户则以主动方式打开 T C P端口 2 1,来建立连 接。控制连接始终等待客户与服务器之间的通信。该连接将命令从客户传给服务器, 并传回服务器的应答。 由于命令通常是由用户键入的,所以IP对控制连接的服务类型就是“最大限度地减小迟延” 。 2) 每当一个文件在客户与服务器之间传输时,就创建一个数据连接。(其他时间也可以创 建,后面我们将说到)。 由于该连接用于传输目的,所以IP对数据连接的服务特点就是“最大限度提高吞吐量”。 图27-1描述了客户与服务器以及它们之间的连接情况 从图中可以看出,交互式用户通常不处理在控制连接中转换的命令和应答。这些细节均 由两个协议解释器来完成。标有“用户接口”的方框功能是按用户所需提供各种交互界面 (全屏幕菜单选择,逐行输入命令,等等),并把它们转换成在控制连接上发送的 F T P命令。 类似地,从控制连接上传回的服务器应答也被转换成用户所需的交互格式。 从图中还可以看出,正是这两个协议解释器根据需要激活文件传送功能。 27.2.1 数据表示 FTP协议规范提供了控制文件传送与存储的多种选择。在以下四个方面中每一个方面都必 须作出一个选择。

317

第27章 FTP:文件传送协议使用

下载 客户

在终端上 的用户

用户接口 服务器

用户协议 解释器

用户数据传 输功能

文件系统

控制连接 (FTP命令) (FTP应答)

服务器协 议接口

服务器数据传 输功能

数据连接

文件系统

图27-1 文件传输中的处理过程

1. 文件类型 (a) ASCII码文件类型 (默认选择)文本文件以NVT ASCII码形式在数据连接中传输。这要求 发方将本地文本文件转换成NVT ASCII码形式,而收方则将NVT ASCII码再还原成本地文本文件。 其中,用NVT ASCII码传输的每行都带有一个回车,而后是一个换行。这意味着收方必须扫描每 个字节,查找CR、LF对(我们在第15.2节见过的关于TFIP的ASCII码文件传输情况与此相同) 。 (b) EBCDIC文件类型 该文本文件传输方式要求两端都是 EBCDIC系统。 (c) 图像文件类型(也称为二进制文件类型)

数据发送呈现为一个连续的比特流。通常

用于传输二进制文件。 (d) 本地文件类型

该方式在具有不同字节大小的主机间传输二进制文件。每一字节的比特

数由发方规定。对使用8 bit字节的系统来说,本地文件以8 bit字节传输就等同于图像文件传输。 2. 格式控制 该选项只对 ASCII和EBCDIC文件类型有效。 (a) 非打印 (默认选择)文件中不含有垂直格式信息。 (b) 远程登录格式控制 文件含有向打印机解释的远程登录垂直格式控制。 (c) Fortran 回车控制 每行首字符是Fortran格式控制符。 3. 结构 (a) 文件结构 (默认选择)文件被认为是一个连续的字节流。不存在内部的文件结构。 (b) 记录结构 该结构只用于文本文件( ASCII或EBCDIC)。 (c) 页结构

每页都带有页号发送,以便收方能随机地存储各页。该结构由 TO P S - 2 0操

作系统提供(主机需求 RFC不提倡采用该结构)。 4. 传输方式 它规定文件在数据连接中如何传输。 (a) 流方式

(默认选择)文件以字节流的形式传输。对于文件结构,发方在文件尾提

示关闭数据连接。对于记录结构,有专用的两字节序列码标志记录结束和文件结束。 (b) 块方式 文件以一系列块来传输,每块前面都带有一个或多个首部字节。 (c) 压缩方式

一个简单的全长编码压缩方法,压缩连续出现的相同字节。在文本文件

318

使用TCP/IP详解,卷1:协议

下载

中常用来压缩空白串,在二进制文件中常用来压缩 0字节(这种方式很少使用,也不受支持。 现在有一些更好的文件压缩方法来支持 FTP)。 如果算一下所有这些选择的排列组合数,那么对传输和存储一个文件来说就有 7 2种不同 的方式。幸运的是,其中很多选择不是废弃了,就是不为多数实现环境所支持,所以我们可 以忽略掉它们。 通常由Unix实现的FTP 客户和服务器把我们的选择限制如下: • 类型:ASCII或图像。 • 格式控制:只允许非打印。 • 结构:只允许文件结构。 • 传输方式:只允许流方式。 这就限制我们只能取一、两种方式: ASCII或图像(二进制)。 该实现满足主机需求 R F C的最小需求(该 R F C也要求能支持记录结构,但只有操作系统 支持它才行,而 Unix不行)。 很多非U n i x的实现提供了处理它们自己文件格式的 F T P功能。主机需求 R F C指出“F T P协 议有很多特征,虽然其中一些通常不实现,但对 FTP中的每一个特征来说,都存在着至少一种 实现”。 27.2.2 FTP命令 命令和应答在客户和服务器的控制连接上以 NVT ASCII 码形式传送。这就要求在每行结 尾都要返回 CR、LF对(也就是每个命令或每个应答)。 从客户发向服务器的 Te l n e t命令(以 I A C打头)只有中断进程( < I A C , I P >)和Te l n e t的同 步信号(紧急方式下 < I A C , D M >)。我们将看到这两条 Te l n e t命令被用来中止正在进行的文件 传输,或在传输过程中查询服务器。另外,如果服务器接受了客户端的一个带选项的 Telnet命 令(WILL,WONT,DO或DONT),它将以DONT 或WONT响应。 这些命令都是 3或4个字节的大写 ASCII字符,其中一些带选项参数。从客户向服务器发送 的FTP命令超过30种。图27-2给出了一些常用命令,其中大部分将在本章再次遇到。 命



ABOR LIST filelist PASS password PORT n1,n2,n3,n4,n5,n6 QUIT RETR filename STOR filename SYST TYPE type USER username





放弃先前的 FTP命令和数据传输 列表显示文件或目录 服务器上的口令 客户端IP地址(n1.n2.n3.n4)和端口( n5×256+n6) 从服务器注销 检索(取)一个文件 存储(放)一个文件 服务器返回系统类型 说明文件类型: A表示ASCII码,I表示图像 服务器上用户名

图27-2

常用的FTP命令

下节我们将通过一些例子看到,在用户交互类型和控制连接上传送的 FTP命令之间有时是 一对一的。但也有些操作下,一个用户命令产生控制连接上多个 FTP命令。

319

第27章 FTP:文件传送协议使用

下载 27.2.3 FTP应答

应答都是ASCII码形式的3位数字,并跟有报文选项。其原因是软件系统需要根据数字代码来 决定如何应答,而选项串是面向人工处理的。由于客户通常都要输出数字应答和报文串,一个可 交互的用户可以通过阅读报文串(而不必记忆所有数字回答代码的含义)来确定应答的含义。 应答 3位码中每一位数字都有不同的含义(我们将在第 2 8章看到简单邮件传送输协议, SMTP,使用相同的命令和应答约定)。 图27-3给出了应答代码第 1位和第2位的含义。 应答 1yz 2yz 3yz 4yz 5yz x0z x1z x2z x3z x4z x5z





肯定预备应答。它仅仅是在发送另一个命令前期待另一个应答时启动 肯定完成应答。一个新命令可以发送 肯定中介应答。该命令已被接受,但另一个命令必须被发送 暂态否定完成应答。请求的动作没有发生,但差错状态是暂时的,所以 命令可以过后再发 永久性否定完成应答。命令不被接受,并且不再重试 语法错误 信息 连接。应答指控制或数据连接 鉴别和记帐。应答用于注册或记帐命令 未指明 文件系统状态

图27-3 应答代码3位数字中第1位和第2位的含义

第3位数字给出差错报文的附加含义。例如,这里是一些典型的应答,都带有一个可能的 报文串。 • 125 数据连接已经打开;传输开始。 • 200 就绪命令。 • 214 帮助报文(面向用户)。 • 331 用户名就绪,要求输入口令。 • 425 不能打开数据连接。 • 452 错写文件。 • 500 语法错误(未认可的命令)。 • 501 语法错误(无效参数)。 • 502 未实现的MODE(方式命令)类型。 通常每个FTP命令都产生一行回答。例如, QUIT命令可以产生如下应答: 221 Goodbye.

如果需要产生一条多行应答,第1行在3位数字应答代码之后包含一个连字号,而不是空格, 最后一行包含相同的3位数字应答代码,后跟一个空格符。例如,HELP命令可以产生如下应答:

320

使用TCP/IP详解,卷1:协议

下载

27.2.4 连接管理 数据连接有以下三大用途: 1) 从客户向服务器发送一个文件。 2) 从服务器向客户发送一个文件。 3) 从服务器向客户发送文件或目录列表。 FTP服务器把文件列表从数据连接上发回,而不是控制连接上的多行应答。这就避免了行 的有限性对目录大小的限制,而且更易于客户将目录列表以文件形式保存,而不是把列表显 示在终端上。 我们已说过,控制连接一直保持到客户-服务器连接的全过程,但数据连接可以根据需要 随时来,随时走。那么需要怎样为数据连接选端口号,以及谁来负责主动打开和被动打开? 首先,我们前面说过通用传输方式( U n i x环境下唯一的传输方式)是流方式,并且文件 结尾是以关闭数据连接为标志。这意味着对每一个文件传输或目录列表来说都要建立一个全 新的数据连接。其一般过程如下: 1) 正由于是客户发出命令要求建立数据连接,所以数据连接是在客户的控制下建立的。 2) 客户通常在客户端主机上为所在数据连接端选择一个临时端口号。客户从该端口发布 一个被动的打开。 3) 客户使用PORT命令从控制连接上把端口号发向服务器。 4) 服务器在控制连接上接收端口号,并向客户端主机上的端口发布一个主动的打开。服 务器的数据连接端一直使用端口 20。 图2 7 - 4给出了第 3步执行时的连接状态。假设客户用于控制连接的临时端口是 11 7 3,客户 用于数据连接的临时端口是 1174。客户发出的命令是 PORT命令,其参数是 6个ASCII中的十进 制数字,它们之间由逗点隔开。前面 4个数字指明客户上的 I P地址,服务器将向它发出主动打 开(本例中是 1 4 0 . 2 5 2 . 1 3 . 3 4),而后两位指明 16 bit端口地址。由于 16 bit端口地址是从这两个 数字中得来,所以其值在本例中就是 4×256+150 = 1174。 图2 7 - 5给出了服务器向客户所在数据连接端发布主动打开时的连接状态。服务器的端点 是端口20。 FTP客户

FTP服务器 控制连接

端口1173

端口21

端口1174 (被动打开) IP地址

图27-4 在FTP控制连接上通过的PORT命令 FTP服务器

FTP客户 端口1173 端口1174 (被动打开)

控制连接 SYN到140.252.13.34, 端口1174

端口21 端口20 (主动打开)

IP地址140.252.13.34

图27-5 主动打开数据连接的FTP服务器

下载

321

第27章 FTP:文件传送协议使用

服务器总是执行数据连接的主动打开。通常服务器也执行数据连接的主动关闭,除非当 客户向服务器发送流形式的文件时,需要客户来关闭连接(它给服务器一个文件结束的通 知)。 客户也有可能不发出 P O RT命令,而由服务器向正被客户使用的同一个端口号发出主动打 开,来结束控制连接。这是可行的,因为服务器面向这两个连接的端口号是不同的:一个是 20,另一个是 21。不过,下节我们将看到为什么现有实现通常不这样做。

27.3 FTP的例子 现在看一些使用 FTP的例子:它对数据连接的管理,采用 NVT ASCII码的文本文件如何发 送,FTP使用Telnet同步信号来中止进行中的文件传输,最后是常用的“匿名 FTP”。 27.3.1 连接管理:临时数据端口 先看一下FTP的连接管理,它只在服务器上用简单 FTP会话显示一个文件。我们用- d标志 (d e b u g)来运行 s v r 4主机上的客户。这告诉它要打印控制连接上变换的命令和应答。所有前 面冠以 - - - >的行是从客户上发向服务器的,所有以 3位数字开头的行都是服务器的应答。客户 的交互提示是ftp>。 svr4 % ftp -d bsdi -d 选项用作排错输出 Connected to bsdi. 客户执行控制连接的主动打开 2 2 0 b s d i F T P s e r v e r ( V e r s i o n 5 . 6 0 ) r e a d y 服务器响应就绪 Name (bsdi:rstevens): 客户提示我们输入 ---> USER rstevens 键入R E T U R N,客户发送默认信息 331 Password required for rstevens. Password: 键入口令;它不需要回显 ---> PASS XXXXXXXX 客户以明文发送它 230 User rstevens logged in. ftp> d i r h e l l o . c 要求列出一个文件的目录 ---> PORT 140,252,13,34,4,150 见图2 7 - 4 200 PORT Command successful. ---> LIST hello.c 150 Opening ASCII mode data connection for /bin/ls. -rw-r--r-1 rstevens staff 38 Jul 17 12:47 hello.c 226 Transfer complete. remote: hello.c 客户输出 56 bytes received in 0.03 seconds (1.8 Kbytes/s) ftp> quit 我们已完成 ---> QUIT 221 Goodbye

当F T P客户提示我们注册姓名时,它打印了默认值(我们在客户上的注册名)。当我们敲 RETURN键时,默认值被发送出去。 对一个文件列出目录的要求引发一个数据连接的建立和使用。本例体现了我们在图 2 7 - 4 和图2 7 - 5中给出的程序。客户要求 T C P为其数据连接的终端提供一个临时端口号,并用 P O RT 命令发送这个端口号( 1174)给服务器。我们也看到一个交互用户命令( d i r)成为两个 FTP 命令(PORT和LIST)。

322

使用TCP/IP详解,卷1:协议

下载

图2 7 - 6是控制连接上分组交换的时间系列(已除去了控制连接的建立和结束,以及所有 窗口大小的通知)。我们关注该图中数据连接在哪儿被打开、使用和过后的关闭。 图27-7是数据连接的时间系列。图中的起始时间与图 27-6中的相同。已除去了所有窗口大 小通知,但留下服务类型字段,以说明数据连接使用另一个服务类型(最大吞吐量),而不同 于控制连接(最小时延)(服务类型 ( TO S )值在图3-2中)。 在时间系列上, FTP服务器执行数据连接的主动打开,从端口 20(称为 f t p - d a t a)到来 自P O RT命令的端口号( 11 7 4)。本例中还可以看到服务器在哪儿向数据连接上执行写操作, 服务器对数据连接执行主动的关闭,这就告诉客户列表已完成。

键入

键入口令

键入

打开数据连接

使用数据连接,然后关闭

键入

图27-6 FTP控制连接示例

323

第27章 FTP:文件传送协议使用

下载

主动打开

主动关闭

图27-7 FTP数据连接示例

27.3.2 连接管理:默认数据端口 如果客户没有向服务器发出 P O RT命令,来指明客户数据连接端的端口号,服务器就用与 控制连接正在用的相同的端口号给数据连接。这会给使用流方式( Unix FTP 客户和服务器一 直使用)的客户带来一些问题。正如下面所示: Host Requirements RFC建议使用流方式的 FTP客户在每次使用数据连接前发一个 PORT命令来启用一个非默认的端口号。 回到先前的例子(图 2 7 - 6),如果我们要求在列出第 1个目录后几秒钟再列出另一个目录, 那该怎么办?客户将要求其内核选择另一个临时端口号(可能是 11 7 5),下一个数据连接将建 立在s v r 4端口11 7 5和b s d i端口2 0之间。但在图 2 7 - 7中服务器执行数据连接的主动打开,我们在 1 8 . 6节说明了服务器将不把端口 2 0分配给新的数据连接,这是因为本地端口号已被更早的连 接使用,而且还处于 2MSL等待状态。 服务器通过指明我们在 18.6节中提到的SO_REUSEADDR选项,来解决这个问题。这让它 把端口20分配给新连接,而新连接将从处于 2MSL等待状态的端口( 1174)处得到一个不一样 的外部端口号( 1175),这样一切都解决了。 如果客户不发送 P O RT命令,而在客户上指明一个临时端口号,那么情况将改变。我们可 以通过执行用户命令 s e n d p o r t给F T P来使之发生。 Unix FTP客户用这个命令在每个数据连接使 用之前关闭向服务器发送 PORT命令。 图27-8给出了用于两个连续 LIST命令的数据连接时间系列。控制连接起自主机 svr4上的端 口11 7 6,所以在没有 P O RT命令的情况下,客户和服务器给数据连接使用相同的端口号(除去 了窗口通知和服务类型值)。 事件序列如下: 1) 控制连接是建立在客户端口 1176到服务器端口21上的(这里我们不展示)。

324

使用TCP/IP详解,卷1:协议

下载

主动打开

(第1个LIST命令的输出) 主动关闭

(所有要打开从bsdi.ftp-data到svr4.1176的TCP 连接的尝试在这里都失败了) 主动打开

(第2个LIST命令的输出) 主动关闭

图27-8 两个连续LIST命令的数据连接

2) 当客户为端口 11 7 6上的数据连接做被动打开时,由于该端口已被客户上的控制连接使 用,所以必须确定 SO_REUSEADDR选项。 3) 服务器给端口 20到端口1176的数据连接(报文段 1)做主动打开。即便端口 1176已在客 户上被使用,客户仍会接受它(报文段 2),这是因为下面这一对插口是不同的:

(在b s d i上的端口号是不同的)。T C P通过查看源 I P地址、源端口号、目的 I P地址、目 的端口号分用各呼入报文段,只要这 4个元素中的一个不同,就行。 4) 服务器对数据连接(报文段 5)做主动的关闭,即把这对插口置入服务器上的一个 2MSL等待。

5) 客户在控制连接上发送另一个 L I S T命令(这里我们不展示)。在此之前,客户在端口 11 7 6上为其数据连接端做一个被动打开。客户必须再一次指明 S O _ R E U S E A D D R,这是 因为端口号 1176已在使用。

下载

325

第27章 FTP:文件传送协议使用

6) 服务器给从端口 20到端口1176的数据连接发出一个主动打开。在此之前,服务器必须指 明SO_REUSEADDR,这是因为本地端口(20)与处于2MSL等待状态的连接是相关联的, 但从18.6节所示可知,该连接将不成功。其原由是这个连接用插口对 (socket pair)与步骤 4中的仍处于2MSL等待状态的插口对相同。 TCP规定禁止服务器发送同步信息( SYN)。 这样就没办法让服务器跨过插口对的2MSL等待状态来重用相同的插口对。 在这一步伯克利软件分发(BSD)服务器每隔5秒就重试一次连接请求,直到满18次,总共 90秒。我们看到报文段9将在大约1分钟后成功(我们在第18章提到过,SVR4使用一个30秒 的MSL,以两个MSL来达到持续1分钟的等待) 。我们没看到在这个时间系列上的这些失败 有任何同步(SYN)信息,这是因为主动打开失败,服务器的TCP不再发送一个SYN。 Host Requirements RFC建议使用PORT命令的原因是在两个相继使用数据连接之间避免出 现这个2MSL。通过不停地改变某一端的端口号,我们所说的这个问题就不会出现。 27.3.3 文本文件传输:NVT ASCII表示还是图像表示 让我们查证一下默认的文本文件传输使用 NVT ASCII码。这次不指定 - d标志,所以不看 客户命令,但注意到客户还将打印服务器的响应: sun % ftp bsdi Connected to bsdi. 220 bsdi FTP server (Version 5.60) ready. Name (bsdi:retevens); 键入R E T U R N 331 Password required for rstevens. Passord : 键入口令 230 User rstevens logged in. ftp> g e t h e l l o . c 取一个文件 200 PORT command successful. 150 Opening ASCII mode data connection for hello.c (38 bytes). 226 Transfer complete. 服务器说明文件含有 3 8字节 local: hello.c remote: hello.c 由客户输出 42 bytes received in 0.0037 seconds (11 Kbytes/s) 字节传过数据连接 42 ftp> quit 221 Goodbye. Sun % l s - l h e l l o . c -rw-rw-r-1 rstevens 38 Jul 18 0 8 : 4 8 h e l l o . 但文件还含有 c 3 8字节 sun % w c - l h e l l o . c 在文件中记行数 4 hello.c

因为文件有 4行,所以从数据连接上传输 42个字节。Unix下的每一新行符( \n)被服务器 转换成 NVT ASCII码的2字节行结尾序列( \ r \ n)来传输,然后再由客户转换成原先形式来 存储。 新客户试图确定服务器是否是相同类型的系统,一旦相同,就可以用二进制码(图像文 件类型)来传输文件,而不是 ASCII码。这可以获得两个方面的好处: 1) 发方和收方不必查看每一字节(很大的节约)。 2) 如果主机操作系统使用比 2字节的NVT ASCII码序列更少的字节来作行尾,就会传输更 少的字节数(很小的节约)。 我们可以看到使用一个 B S D / 3 8 6客户和服务器的最优效果。启动排错( d e b u g)方式来看

326

使用TCP/IP详解,卷1:协议

下载

客户FTP命令: bsdi & f t p - d s l i p 指明 - d来看客户命令 Connected to slip. 220 slip FTP server (Version 5.60) ready. Name (slip:rstevens): 我们键入 R E T U R N ---> USER rstevns 331 Password required for rstevens. Password : 我们键入自己的口令 ---> PASS XXXX 230 User rstevns logged in . ---> SYST 这由客户服务器的应答自动发送 215 UNIX Type: L8 Version : BSD-199103 Remote system type is UNIX. 由客户发出的信息 Using binary mode to transfer files. 由客户发出的信息 ftp> g e t h e l l o . c 取一个文件 ---> TYPE I 由客户自动发送 200 Type set to I. ---> PORT 140,252,13,66,4,84 端口号= 4×2 5 6 + 8 4 = 1 1 0 8 200 PORT command successful. ---> RETR hello.c 150 Opening BINARY mode data connection for hello.c (38 bytes) . 226 Transefer complete . 3 8 b y t e s r e c e i v e d i n 0 . 0 3 5 s e c o n d s ( 1 . 1 K b y t e s /这时只有 s) 3 8个字节 ftp> quit ---> QUIT 221 Goodbye.

注册到服务器后,客户 F T P自动发出 S Y S T命令,服务器将用自己的系统类型来响应。如 果应答起自字符串“ 215 UNIX Type : L8”,并且如果客户在每字节为 8 bit的Unix系统上运行, 那么二进制方式(图像)将被所有文件传输所使用,除非被用户改变。 当我们取文件 h e l l o . c时,客户自动发出命令 TYPE I 把文件类型定成图像。这样在数据连 接上只有38字节被传输。 Host Requirements RFC指出一个FTP服务器必须支持SYST命令(这曾是RFC 959中 的一个选项)。但支持它的使用文本的系统(见封 2)仅仅是 B S D / 3 8 6和AIX 3.2.2 。 SunOS 4.1.3和Solaris 2.x 用500(不能理解的命令)来应答。SVR4采用极不大众化的应 答行为500,并关闭控制连接! 27.3.4 异常中止一个文件的传输:Telnet 同步信号 现在看一下 FTP客户是怎样异常中止一个来自服务器的文件传输。异常中止从客户传向服 务器的文件很容易—只要客户停止在数据连接上发送数据,并发出 A B O R命令到控制连接 上的服务器即可。而异常中止接收就复杂多了,这是因为客户要告知服务器立即停止发送数 据。我们前面提到要使用 Telnet同步信号,下面的例子就是这样。 我们先发起一个接收,并在它开始后键入中断键。这里是交互会话,其中初始注册被略 去:

327

第27章 FTP:文件传送协议使用

下载

ftp> g e t a . o u t 取一个大文件 ---> TYPE I 客户和服务器都是 8 bit字节的Unix系统 200 Type set to I. ---> PORT 140,252,13,66,4,99 200 PORT command successful. ---> RETR a.out 150 Opening BINARY mode data connection for a.out (28672 bytes). ^? 键入的中断键 receive aborted 由客户输出 waiting for remote to finish abort 由客户输出 426 Transfer aborted. Data connection closed. 226 Abort successful 1536 bytes received in 1.7 seconds (0.89 Kbytes/s)

在我们键入中断键之后,客户立即告知我们它将发起异常中止,并正在等待服务器完成。 服务器发出两个应答: 4 2 6和2 2 6。这两个应答都是由 Unix 服务器在收到来自客户的紧急数据 和ABOR命令时发出的。 图2 7 - 9和图2 7 - 1 0展示了会话时间系列。我们已把控制连接(实线)和数据连接(虚线) 合在一起来说明它们之间的关系。

打开数 据连接

开始传 送数据

第1个数据段

图27-9 异常中止一个文件的传输(前半部)

图2 7 - 9的前面1 2个报文段是我们所期望的。通过控制连接的命令和应答建立起文件传输, 数据连接被打开,第 1个报文段的数据从服务器发往客户。

328

使用TCP/IP详解,卷1:协议

下载

键入中断

数据传送 继续

关闭数据 连接

图27-10 异常中止一个文件的传输(后半部)

在图2 7 - 1 0中,报文段 1 3是数据连接上来自服务器的第 6个数据报文段,后跟由我们键入 的中断键产生的报文段 14。客户发出 10个字节来异常中止传输:

由于20.8节中详细讨论过这个问题,我们看到有两个报文段( 14和15)涉及到 TCP的紧急 指针(我们在图 26-17中看过对Telnet问题也做相同的处理)。Host Requirements RFC指出紧急 指针应指向紧急数据的最后一个字节,而多数伯克利的派生实现使之指向紧急数据最后一个 字节后面的第一个字节。了解到紧急指针将(错误地)指向下一个要写的字节(数据标志, D M。在序号为 5 4处),F T P客户进程特意写前 3个字节作为紧急数据。首先写下的 3字节紧急 数据与紧急指针一起被立即发送,紧接着是后面 7个字节( BSD FTP 服务器不会出现由客户 使用的紧急指针的解释问题。当服务器收到控制连接上的紧急数据时,它读下一个 F T P命令, 寻找ABOR或STAT,忽略嵌入的Telnet命令)。 注意到尽管服务器指出传输被异常中止(报文段 1 8,在控制连接上),客户进程还要在数 据连接上再接收 1 4个报文段的数据(序列号是 1 5 3 7 ~ 5 1 2 0)。这些报文段可能在收到异常中止

329

第27章 FTP:文件传送协议使用

下载

时,还在服务器上的网络设备驱动器中排队,但客户打印“收到 1536字节”,意思是在发出异 常中止后(报文段 14和15),略去收到的所有数据报文段。 一旦Te l n e t用户键入中断键,我们在图 2 6 - 1 7中看到 U n i x客户在默认情况下不发出中断进 程命令作为紧急数据。因为几乎没有机会用流控制来中止从客户进程到服务器进程的数据流, 所以我们说这样就行了。 FTP的客户进程也通过控制连接发送一个中断进程命令,因为两个连 接正在被使用,因此没有机会用流控制来中止控制连接。为什么 FTP发送中断进程命令作为紧 急数据而 Te l n e t不呢?答案在于 F T P使用两个连接,而 Te l n e t只使用一个,在某些操作系统上 要求一个进程同时监控两个连接的输入是困难的。 FTP假设这些临界的操作系统至少提供紧急 数据在控制连接上已到达的通知,而后让服务器从处理数据连接切换到控制连接上来。 27.3.5 匿名FTP FTP的一种形式很常用,我们下面给出它的例子。它被称为匿名 FTP,当有服务器支持时, 允许任何人注册并使用 FTP来传输文件。使用这个技术可以提供大量的自由信息。 怎样找出你正在搜寻的站点是一个完全不同的问题。我们将在 30.4节简要介绍。 我们将把匿名 FTP用在站点 f t p . u u . n e t上(一个常用的匿名 FTP站点)来取本书的勘误 表文件。要使用匿名 F T P,须使用“a n o n y m o u s”(复习数遍就能正确地拼写)用户名来注册。 当提示输入口令时,我们键入自己的电子邮箱地址。 sun % f t p f t p . u u . n e t Connected to ftp.uu.net 220 ftp.UU.NET FTP server (Version 2.0WU(13) Fri Apr 9 20:44:32 EDT 1993) ready N a m e ( f t p . u u . n e t : r s t e v e n sa)n:o n y m o u s 331 Guest login ok, send your complete e-mail addraess as password. 键入r s t e v e n s @ n o a o . e d u;它没有回显

Password : 230230230-

Welcome to the UUNET archive. A service of UUNET Technologies Inc, Falls Church, Virginia

230-

For information about UUNET, call +1 703 204 8000, or see the files

230-

in /uunet-info 还有一些问候行

230 Guest login ok, access restrictions apply. ftp> c d p u b l i s h e d / b o o k s

换成需要的目录

250 CWD command successful. ftp> binary

我们将传送一个二进制文件

200 Type set to I. ftp> g e t s t e v e n s . t c p i p i v l . e r r a t a . Z

取文件

200 PORT command successful. 150 Opening BINARY mode data connection for stevens.tcpipivl.errata.Z (150 bytes). 226 Transfer complete.

(你可能得到一个不同的文件大小 )

local: stevens.tcpipivl.errata.Z remote: stevens.tcpipivl.errata.Z 105 bytes received in 4.1 seconds (0.83 Kbytes/s) ftp> quit 221 Goodbye. sun % u n c o m p r e s s s t e v e n s . t c p i p i v l . e r r a t a . Z sun % m o r e s t e v e n s . t c p i p i v l . e r r a t a

330

使用TCP/IP详解,卷1:协议

下载

不压缩是因为很多现行匿名 F T P文件是用 U n i x c o m p r e s (s1 )程序压缩的,这样导致文 件带有.Z的扩展名。这些文件必须使用二进制文件类型来传输,而不是 ASCII码文件类型。 27.3.6 来自一个未知IP地址的匿名FTP 可以把一些使用匿名 F T P的域名系统( D N S)特征和选路特征结合在一起。在 1 4 . 5节中我 们谈到 D N S中指针查询现象—取一个 I P地址并返回其主机名。不幸的是并非所有系统管理 员都能正确地创立涉及指针查询的名服务器。他们经常记得把新主机加入名字到地址匹配的 文件中,却忘了把他们加入到地址到名字匹配的文件中。对此,可用 t r a c e r o u t e经常看到 这种现象,即它打印一个 IP地址,而不是主机名。 有些匿名FTP服务器要求客户有一个有效域名。这就允许服务器来记录正在执行传输的主 机域名。由于服务器在来自客户 I P数据报中收到的关于客户的唯一标识是客户的 I P地址,所 以服务器能叫 D N S来做指针查询,并获得客户的域名。如果负责客户主机的名服务器没有正 确地创立,指针查询将失败。 要看清这个错误,我们来做以下诸步骤: 1) 把主机 s l i p(见封 2的图)的 I P地址换成 1 4 0 . 2 5 2 . 1 3 . 6 7。这是给作者子网的一个有效 IP地址,但没有涉及到 noao.edu域的域名服务器。 2) 把在bsdi上SLIP连接的目的 IP地址换成140.252.13.67。 3) 把将数据报引向 1 4 0 . 2 5 2 . 1 3 . 6 7的s u n上的路由表入口加入路由器 b s d i(回忆一下我们 在9.2节中关于这个选路表的讨论)。 从I n t e r n e t上仍然可以访问我们的主机 s l i p,这是因为在 1 0 . 4节中路由器 g a t e w a y和 n e t b正好把所有目的是子网 1 4 0 . 2 5 2 . 1 3的所有数据报都发送给路由器 s u n。路由器 s u n知道 利用我们在上述第 3步建立的路由表入口来如何处理这些数据报。我们所创建的是拥有完整 Internet连接性的主机,但没有有效的域名。结果,指针查询 IP地址140.252.13.67将失败。 现在给一个我们所知的服务器使用匿名 FTP,需要一个有效的域名:

来自服务器的出错应答是无需加以说明的。

下载

331

第27章 FTP:文件传送协议使用

27.4 小结 F T P是文件传输的 I n t e r n e t标准。与多数其他 T C P应用不同,它在客户进程和服务器进程 之间使用两个 T C P连接—一个控制连接,它一直持续到客户进程与服务器进程之间的会话 完成为止;另一个按需可以随时创建和撤消的数据连接。 F T P使用的关于数据连接的连接管理让我们更详细地了解 T C P连接管理需求。我们看到 TCP在不发出PORT命令的客户进程上对 2MSL等待状态的作用。 FTP使用NVT ASCII码做跨越控制连接的所有远程登录命令和应答。数据传输的默认方式 通常也是NVT ASCII码。我们看到较新的 Unix客户进程会自动发送命令来查看服务器是否是 8 b i t字节的 U n i x主机,并且如果是,那么就使用二进制方式来传输所有文件,那将带来更高的 效率。 我们也展示了匿名 FTP的一个例子,它是在 Internet上分发软件的常用形式。

习题 27.1 图2 7 - 8中,如果客户对第 2个数据连接做一次主动打开,而不是由服务器来做,那将发 生什么变化? 27.2 在本章F T P客户例子中,我们加入诸如由客户输出行的行注释。如果不看源代码,我们 如何确定这些不是来自服务器? local: hello.c remote: hello.c 42 bytes received in 0.0037 seconds (11 Kbytes/s)

下载

第28章 SMTP: 简单邮件传送协议 28.1 引言 电子邮件( e-mail)无疑是最流行的应用程序。 [Caceres et al.1991]说明,所有 TCP连接中 大约一半是用于简单邮件传送协议 SMTP (Simple Mail Transfer Protocol)的(以比特计算为基 础,FTP连接传送更多的数据)。[Paxson 1993] 发现,平均每个邮件中包含大约 1500字节的数 据,但有的邮件中包含兆比特的数据,因为有时电子邮件也用于发送文件。 图28-1显示了一个用TCP/IP交换电子邮件的示意图。 在终端上 的用户

用户代理

要发送邮件 的队列

报文传送 代理

客户

发送方

TCP连接 TCP端口25 在终端上 的用户 接收方

用户代理

用户邮箱

报文传送 代理

服务器

图28-1 Internet电子邮件示意图

用户与用户代理( user agent)打交道,可能会有多个用户代理可供选择。常用的 U n i x上 的用户代理包括 MH,Berkeley Mail, Elm和Mush。 用T C P进行的邮件交换是由报文传送代理 M TA(Message Transfer Agent)完成的。最普 通的U n i x系统中的 M TA是S e n d m a i l。用户通常不和 M TA打交道,由系统管理员负责设置本地 的MTA。通常,用户可以选择它们自己的用户代理。 本章研究在两个 MTA之间如何用 TCP交换邮件。我们不考虑用户代理的运行或实现。 RFC 821 [Postel 1982] 规范了SMTP协议,指定了在一个简单TCP连接上,两个MTA如何进行 通信。RFC 822 [Crocker 1982] 指定了在两个MTA之间用RFC 821 发送的电子邮件报文的格式。

28.2 SMTP协议 两个M TA之间用 NVT ASCII进行通信。客户向服务器发出命令,服务器用数字应答码和 可选的人可读字符串进行响应。这与上一章的 FTP类似。 客户只能向服务器发送很少的命令:不到 1 2个(相比较而言, F T P超过4 0个)。我们用简 单的例子说明发送邮件的工作过程,并不仔细描述每个命令。 28.2.1 简单例子 我们将发送一个只有一行的简单邮件,并观察 SMTP连接。我们用 -v 标志调用用户代理,

下载

333

第28章 SMTP:简单邮件传送协议使用

它被传送给邮件传送代理(本例中是 S e n d m a i l)。当设置该标志时,该 M TA显示在S M T P连接 上发送和接收的内容。以 > > >开始的行是 S M T P客户发出的命令,以 3位数字的应答码开始的 行是从SMTP服务器来的。以下就是交互会话: 调用我们的代理 这是用户代理的输出 然后指示我们键入主题 用户代理在首部和正文之间加上一行空行 这是我们键入的正文 我们在一行上输入一个句点,说明完成了 用户代理上详细的输出 以下是MTA(Sendmail)的输出

这是用户代理的输出

只有5个SMTP命令用于发送邮件: HELO,MAIL,RCTP,DATA和QUIT。 我们键入 m a i l启动用户代理,然后键入主题( s u b j e c t)的提示;键入后,再键入报文的 正文。在一行上键入一个句点结束报文,用户代理把邮件传给 MTA,由MTA进行交付。 客户主动打开 T C P端口2 5。返回时,客户等待从服务器来的问候报文(应答代码为 2 2 0)。 该服务器的应答必须以服务器的完全合格的域名开始:本例中为 n o a o . e d u(通常,跟在数 字应答后面的文字是可选的。这里需要域名。以 Sendmail打头的文字是可选的)。 下一步客户用 H E L O命令标识自己。参数必须是完全合格的的客户主机名: s u n . t u c . noao.edu。 MAIL命令标识出报文的发起人。下一个命令, RCPT,标识接收方。如果有多个接收方, 可以发多个 RCPT命令。 邮件报文的内容由客户通过 D ATA命令发送。报文的末尾由客户指定,是只有一个句点的 一行。最后的命令 QUIT,结束邮件的交换。 图28-2是在发送方 SMTP(客户端)与接收方 SMTP(服务器)之间的一个 SMTP连接。 我们键入到用户代理的数据是一行报文(“1,2,3”),但在报文段 12中共发送了 393字节 的数据。下面的 12行组成了客户发送的 393字节数据:

334

使用TCP/IP详解,卷1:协议

下载

前三行,R e c e i v e d :和Message-Id: 由MTA加上;下一行由用户代理生成。

图28-2 基本SMTP邮件交付

28.2.2 SMTP命令 最小 S M T P实现支持 8种命令。我们在前面的例子中遇到 5个: H E L O,M A I L,R C P T, DATA和QUIT。 R S E T命令异常中止当前的邮件事务并使两端复位。丢掉所有有关发送方、接收方或邮件 的存储信息。

335

第28章 SMTP:简单邮件传送协议使用

下载

VRFY命令使客户能够询问发送方以验证接收方地址,而无需向接收方发送邮件。通常是 系统管理员在查找邮件交付差错时手工使用的。我们将在下一节中给出这方面的例子。 NOOP命令除了强迫服务器响应一个 OK应答码(200)外,不做任何事情。 还有附加和可选命令。 E X P N扩充邮件表,与 V R F Y类似,通常是由系统管理员使用的。 事实上,许多Sendmail的版本都把这两者等价地处理。 4.4BSD 中的 S e n d m a i l版本 8不再将两者等同处理。 V R F Y不扩充别名也不接 受.forward文件。 T U R N命令使客户和服务器交换角色,无需拆除 T C P连接并建立新的连接就能以相反方向 发送邮件( S e n d m a i l不支持这个命令)。其他还有三个很少被实现的命令( S E N D、S O M L和 S A M L)取代M A I L命令。这三个命令允许邮件直接发送到客户终端(如果已注册)或发送到 接收方的邮箱。 28.2.3 信封、首部和正文 电子邮件由三部分组成: 1) 信封(envelope)是MTA用来交付的。在我们的例子中信封由两个 SMTP命令指明: MAIL From: RCPT To:

RFC 821指明了信封的内容及其解释,以及在一个 TCP连接上用于交换邮件的协议。 2) 首部由用户代理使用。在我们的例子中可以看到

9个首部字段: R e c e i v e d 、

M essage-Id、From、D a t a、R e p l y - T o、X - P h o n e、X - M a i l e r、T o和S u b j e c t。每个 首部字段都包含一个名,紧跟一个冒号,接着是字段值。 RFC 822指明了首部字段的格式的解 释(以X-开始的首部字段是用户定义的字段,其他是由 RFC 822定义的)。长首部字段,如例 子中的Received,被折在几行中,多余行以空格开头。 3) 正文(body)是发送用户发给接收用户报文的内容。 RFC 822 指定正文为NVT ASCII 文字行。当用 D ATA命令发送时,先发送首部,紧跟一个空行,然后是正文。用 D ATA命令发 送的各行都必须小于 1000字节。 用户接收我们指定为正文的部分,加上一些首部字段,并把结果传到 M TA。M TA加上一 些首部字段,加上信封,并把结果发送到另一个 MTA。 内容(content)通常用于描述首部和正文的结合。内容是客户用 DATA命令发送的。 28.2.4 中继代理 在我们的例子中本地 MTA的信息输出的第 1行是:“Connecting to mailhost via ether”(即 “通过以太网连接到邮件主机”)。这是因为作者的系统已被配置成把所有非本地的向外的邮件 发送到一台中继机上进行转发。 这样做的原因有两个。首先,简化了除中继系统 M TA外的其他所有 M TA的配置(所有曾 使用过Sendmail的人都能证明,配置一个 MTA并不简单)。第二,它允许某个机构中的一个系 统作为邮件集线器,从而可能把其他所有系统隐藏起来。 在这个例子中,中继系统在本地域( . t u c . n o a o . e d u)中有一个 m a i l h o s t的主机名,而其他 所有系统都被配置成把它们的邮件发往该主机。我们可以执行 host命令来看看在DNS中这个名

336

使用TCP/IP详解,卷1:协议

下载

是如何定义的: sun % host mailhost mailhost.tuc.noao.edu noao.edu

CNAME A

规范名 它的真实 I P地址

noao.edu 140.252.1.54

如果将来用于中继的主机改变了,只需改变它的 D N S名—其他所有单个系统的邮箱配 置都无需改变。 目前许多机构都采用中继系统。图 2 8 - 3是修改后的 I n t e r n e t邮件图(图2 8 - 2),考虑发送主 机和最后的接收主机都可能使用中继主机。 发送主机 在终端上 的用户 要发送邮 件的队列

用户代理

一个机构 本地MTA

邮件队列

本地MTA

本地MTA

中继MTA 在Internet上

邮件队列

本地MTA

中继MTA

本地MTA

本地MTA 一个机构

用户代理

用户邮箱

在终端上 的用户

接收主机

图28-3 在两端都有一个中继系统的Internet电子邮件

在这种情况下,在发送方和接收方之间有 4个M TA。发送方主机上的本地 M TA只把邮件 交给它自己的中继 M TA(该中继 M TA可能在该机构的域中有一个 m a i l h o s t的主机名)。这个通 信就在该机构的本地互联网上用 S M T P。然后,发送方机构的中继 M TA就在I n t e r n e t上把邮件 发送到接收方机构的中继 M TA上,而这个中继 M TA就通过与接收方主机上的本地 M TA通信, 把邮件交给接收方主机。尽管可能存在其他协议,但这个例子中所有 MTA均使用SMTP协议。

下载

337

第28章 SMTP:简单邮件传送协议使用

28.2.5 NVT ASCII S M T P的一个特色是它用 NVT ASCII表示一切:信封、首部和正文。正如我们在 2 6 . 4节中 谈到的,这是一个 7 bit 的字符码,以8 bit字节发送,高位比特被置为 0。 在2 8 . 4节中,我们讨论了 I n t e r n e t邮件的一些新特性、允许发送和接收诸如音频和视频数 据的扩充SMTP和多媒体邮件( MIME)。我们将看到, MIME和NVT ASCII一起表示信封、首 部和正文,只需对用户代理作一些改变。 28.2.6 重试间隔 当用户把一个新的邮件报文传给它的 M TA时,通常立即试图交付。如果交付失败, M TA 必须把该报文放入队列中以后再重试。 Host Requirements RFC推荐初始时间间隔至少为 3 0分钟。发送方至少 4 ~ 5天内不能放弃。 而且,因为交付失败通常是透明的(接收方崩溃或临时网络连接中断),所以当报文在队列中 等待的第1个小时内,尝试两次连接是有意义的。

28.3 SMTP的例子 上面我们说明了普通邮件发送,在这里我们将说明

M X记录如何用于邮件发送,以及

VRFY和EXPN命令的用法。 28.3.1 MX记录:主机非直接连到Internet 在1 4 . 6节中我们提到 D N S中的一种资源记录类型是邮件交换记录,称为 M X记录。在下面 的例子中我们将说明如何用 M X 记录向不直接连到 I n t e r n e t的主机发送邮件。 RFC 974 [Partridge 1986 ] 描述了MTA对MX记录的处理。 主机m l f a r m . c o m不是直接连到 I n t e r n e t的,但是有一个 M X记录指向 I n t e r n e t上的一个邮 件转发器。

有两个MX记录,各有不同的优先级。我们希望 MTA从优先级数值低的开始。 -v标志看MTA在做什么

在这里键入报文的正文(没显示出来) 一行中的一个句号,结束报文

找到MX记录 先试优先级低的那一个 下面是正常的SMTP邮件传送

338

使用TCP/IP详解,卷1:协议

下载

从输出中我们看到, M TA发现目的主机有一个 M X记录,并使用具有低优先级数值的 M X 记录。 在主机 s u n运行这个例子之前,它被配置成不使用本地中继主机,所以我们会看到与目 的主机的邮件交换。主机 s u n还被配置成可使用主机 n o a o . e d u(通过拨号 S L I P链路)上的 域名服务器,所以我们能用 t c p d u m p捕获在 S L I P链路上进行的邮件发送和 D N S通信。图 2 8 - 4 显示了tcpdump输出的开始部分。

图28-4 向一个使用MX记录的主机发送邮件

在第1行,M TA向它的域名服务器查询 m l f a r m . c o m的M X记录。跟在 2后面的加号“ +” 意思是设置要求递归的标志位。第 2行的响应置位授权比特(跟在 2后面的星号“ *”),并包含 两个回答RR(两个MX主机名),0个授权RR,以及两个附加的 RR(两个主机的IP地址)。 第3 ~ 5行与主机 m e r c u r y . h s i . c o m上的S M T P建立了一个 T C P连接。服务器的初始响应 220显示在第6行。 由于某种原因,主机mercury.hsi.com必须把这个邮件报文交付给目的地,mlfarm.com。 对于没有连接到Internet上与它的MX站点交换邮件的系统,UUCP协议是一种常用的办法。 在这个例子中, MTA要求一个MX记录,得到一个肯定的结果,然后发送邮件。但不幸的 是,M TA与D N S之间的交互随不同的实现而不同。 RFC 974指定M TA必须首先要求 M X记录, 如果没有,就尝试提交给目的主机(也就是说,向 DNS要主机的记录和 IP地址)。MTA也必须 处理DNS中的CNAM记录(规范的名)。 作为一个例子,如果我们从一个

B S D / 3 8 6 主机上向 r s t e v e n s @ m a i l h o s t .

tuc.noao.edu发送邮件,则MTA(Sendmail)执行以下步骤: 1) Sendmail 向D N S询问主机 m a i l h o s t . t u c . n o a o . e d u的C N A M E记录。我们看到存 在一个CNAME记录: sun % host -t cname mailhost.tuc.noao.edu mailhost.tuc.noao.edu CNAME noao.edu

2) 发布一个要求noao.edu的CNAME记录的DNS查询,回答是不存在。 3) Sendmail向DNS寻求noao.edu的MX记录并得到一个记录: sun % host -t mx noao.edu noao.edu MX

noao.edu

4) Sendmail 向DNS查询noao.edu的A记录(IP地址),并得到返回值140.252.1.54(这个 A记录大概是由域名服务器为 noao.edu返回的,作为第 3步中MX应答的一个附加的 RR)。 5) 启动一个到 140.252.1.54的SMTP连接并发送邮件。 CNAME查询不是为 MX记录(n o a o . e d u)中返回的数据做的。 MX记录中的数据不能是

339

第28章 SMTP:简单邮件传送协议使用

下载 别名—必须是具有一个 A记录的主机名。

与只用DNS 的SunOS 4.1.3一起发布的Sendmail版本查询MX记录,并且如果没有找 到MX记录就放弃。 28.3.2 MX记录:主机出故障 M X记录的另一个用途是在目的主机出故障时可提供另一个邮件接收器。如果看一下主机 sun的DNS入口,我们就会看到它有两个 MX记录:

最低优先级的 M X记录表明应该首先尝试直接发送到主机本身,下一个优先级是把邮件发 送到主机noao.edu。 在下面的描述中,在关掉目的SMTP服务器后,我们从主机vangogh.cs.berkeley.edu 向位于主机sun.tuc.noao.edu的我们自己发送邮件。当端口25上的连接请求到达时,TCP应 该响应一个RST,因为没有被动打开的进程为等待该端口而挂起。

下面是正常的SMTP邮件传送

我们看到MTA尝试联系 sun.tuc.noao.edu,然后放弃,并转而联系 noao.edu。 图28-5显示了TCP用一个RST向到来的SYN响应的tcpdump输出。

图28-5 尝试连接一个不在运行的SMTP服务器

第1行vangogh向sun的第1个IP地址140.252.1.29 的端口25 发送一个SYN。在第2行它被 拒绝。然后, v a n g o g h上的S M T P客户尝试 s u n的第2个I P地址1 4 0 . 2 5 2 . 1 3 . 3 3(第3行),也产 生一个RST的返回(第 4行)。 SMTP客户不区分第 1行它主动打开时所返回的不同差错,而这是导致它在第 2行尝试其他 I P地址的原因。如果第 1次的差错是类似“ host unreachable(主机不可达 )”,那么第 2次尝试或 许可行。 如果SMTP客户的主动打开失败的原因是因为服务器主机出故障了,我们将看到客户会向 IP地址140.252.1.29重传SYN总共75秒(类似于图 18-6)。然后客户向IP地址140.252.13.33发送 另一个75秒的其他3个SYN。150秒后客户会移到下一个具有更高优先级的 MX记录。

340

使用TCP/IP详解,卷1:协议

下载

28.3.3 VRFY和EXPN命令 VRFY命令无需发送邮件而验证某个接收方地址是否 OK。EXPN的目的是无需向邮件表发 送邮件就可以扩充该表。许多 S M T P实现(如 S e n d m a i l)把两者看成一个,但我们提到新的 Sendmail区分这两者。 作为一个简单测试,我们可以连到一个新的 S e n d m a i l版本,并看到不同之处(已经删除 了无关的Telnet客户输出)。

首先注意到我们故意在 HELO命令中键入错误的主机名: b s d i,而不是 s u n。许多SMTP 服务器得到客户的 IP地址,完成一个 DNS指针查询( 14.5节)并比较主机名。这样允许服务器 基于I P地址注册到客户的连接,而不是基于用户可能错误键入的名。某些服务器会用幽默的 报文回答,如“你是一个骗子”,或“为什么叫你自己……”。在这个例子中我们看到,这个 服务器通过指针查询只打印出我们的真实域名以及我们的 IP地址。 然后我们用一个无效的名字键入 V R F Y命令,服务器就响应 5 5 0差错。下一步我们键入一 个有效的名字,服务器用本地主机上的用户名回答。然后我们试试 EXPN命令,并得到一个不 同的回答。 EXPN命令决定到该用户的邮件是否被转发,并打印出转发的地址。 许多站点禁止VRFY和EXPN命令,有时是因为隐私,有时因为相信这是安全漏洞。例如, 我们可以向白宫的 SMTP服务器试试下面的命令:

28.4 SMTP的未来 Internet邮件发生了很多改变。应当记得 Internet邮件的三个组成部分:信封、首部和正文。 新加入的 S M T P命令影响了信封,首部中可以使用非 A S C I I字母,正文( M I M E)中也加入了 结构。本节中我们依次对这三部分的扩充进行讨论。

下载

341

第28章 SMTP:简单邮件传送协议使用

28.4.1 信封的变化:扩充的SMTP RFC 1425 [Klensin等,1993a] 定义了扩充的 S M T P的框架,其结果被称为扩充的 S M T P (E S M T P)。与其他我们已经讨论过的新特性一样,这些变化以向后兼容的方式被加入,所以 不影响已有的实现。 如果客户想使用新的特性,首先通过发布一个 E H L O而不是H E L O命令启动一个与服务器 的会话。相兼容的服务器用 2 5 0应答码响应。这个应答通常有好几行,每行都包含一个关键字 和一个可选的参数。这些关键字指定了该服务器支持的 SMTP扩充。新的扩充将在一个 RFC中 描述并以 I A N A注册(在一个多行应答中,各行数字应答码的后面都要有一个连字符。最后一 行的数字应答码后面跟一个空行)。 我们将给出到 4个S M T P服务器的初始连接,其中 3个支持扩充的 S M T P。我们用 Te l n e t和 它们连接,但删掉了不必要的 Telnet客户输出。

这个服务器用一个多行 220应答作为它的欢迎报文。对 EHLO命令的250应答中列出的扩充 命令是 E X P N、S I Z E和H E L P。第一个和最后一个来自原来的 RFC 821 规范,但它们是可选命 令。ESMTP服务器说明除了新命令外,它们还支持哪些可选的 RFC 821命令。 这个服务器支持的 SIZE关键字是在 RFC 1427 [Klensin, Freed和Moore 1993] 中定义的。它 让客户在MAIL FROM命令行中以字节的多少指定报文的大小,这样服务器就可以在客户开始 发送该报文之前,验证它是否接收该长度的报文。增加这个命令的原因在于,随着对非 ASCII 码(如图像、音频等)内容的支持, Internet邮件报文的长度在不断增大。 下一个主机也支持 E S M T P,注意 2 5 0应答指明支持包含一个可选参数的 S I Z E关键字。这 表明该服务器将接受长度不超过 461兆字节的报文。

关键字8BITMIME来自于RFC 1426 [Klensin等,1993a]。它允许客户把关键字 BODY加到 MAIL FROM命令中,指定正文中是否包含 NVT ASCII字符(默认的)或 8 bit数据。除非客户 收到服务器应答 EHLO命令发来的 8BITMIME关键字,否则禁止客户发送任何非 NVT ASCII字 符(当我们在本节中谈到 MIME时,我们将看到 MIME不要求8 bit 传送)。 该服务器也通告了 XADR关键字。任何以 X开头的关键字都指的是本地 SMTP扩充。

342

使用TCP/IP详解,卷1:协议

下载

另一个服务器也支持 E S M T P,通知了我们已经看到的 H E L P和S I Z E关键字。它也支持三 个以X开头的本地扩充。

最后,我们将看到当客户试图通过向一个不支持 E H L O的服务器发布 E H L O命令来使用 ESMTP时将发生什么。

对E H L O命令,客户收到一个 5 0 0应答而不是 2 5 0应答。客户应发布 R S E T命令,并跟着一 个HELO命令。 28.4.2 首部变化:非ASCII字符 RFC 1522 [Moore 1993] 指明了一个在RFC 822报文首部中如何发送非 ASCII字符的方法。 这样做的主要用途是为了允许在发送方名、接收方名以及主题中使用其他的字符。 首部字段中可以包含编码字 (coded word)。它们具有以下格式: =?charset?encoding?encoded-text?= charset是字符集规范。有效值是两个字符串 us-ascii和iso-8859-x,其中x 是一个单个数字, 例如在iso-8859-1中的数字“ 1”。 encoding是一个单个字符用来指定编码方法,支持两个值。 1) Q编码意思是引号中可打印的( q u o t e d - p r i n t a b l e),目的是用于拉丁字符集。大多数字 符是作为 NVT ASCII(当然最高位比特置 0)发送的。任何要发送的字符若其第 8比特置1则被 作为3个字符发送:第 1个是字符是“ =”,跟着两个十六进制数。例如,字符é(它的二进制 8 b i t值为0 x e 9)作为三个字符发送: = E 9。空格通常作为下划线或三个字符 = 2 0发送。这种编码 的目的在于,某些文本中除了大多数 ASCII字符外,还有几个特殊字符。 2) B 意思是以 6 4为基数的编码。文本中的 3个连续字节( 2 4 b i t)被编码成 4个6 bit值。用 于表示所有可能的 6 b i t值的6 4个NVT ASCII字符如图 2 8 - 6所示。当要编码的个数不是 3的倍数 时,等号符“=”被用作填充符。 下面两种编码方式的例子取自 RFC 1522:

343

第28章 SMTP:简单邮件传送协议使用

下载 ASCII 字符

6 bit 值

6 bit 值

ASCII 字符

6 bit 值

ASCII 字符

6 bit 值

ASCII 字符

图28-6 6 bit值的编码(以64为基数编码)

能处理这些首部的用户代理将输出:

为说明以 6 4为基数的编码方法是如何工作的,我们看一下主题行中前面 4个编码的字符: S W Y g。按照图 2 8 - 6写出这4个字符的 6 bit值(S = 0 x 1 2 , W = 0 x 1 6 , Y = 0 x 1 8以及g = 0 x 2 0)的二进 制码: 010010

010110

011000

100000

然后把这24 bit重新分组成 3个8 bit 字节: 01001001 =0x49

01100110 =0x66

00100000 =0x20

它们是I、f和空格的ASCII表示。 28.4.3 正文变化:通用Internet邮件扩充 我们已经提到RFC 822指定正文是NVT ASCII文本行,没有结构。RFC 1521 [Borenstein和 Freed 1993] 把扩充定义为允许把结构置入正文。这被称为 MIME,即通用Internet邮件扩充。 M I M E不要求任何扩充,我们在本节前面已作了说明(扩充的 S M T P或非 A S C I I标题)。 M I M E正好加入了一些告知收件者正文结构的新标题(与 RFC 822 相一致)。正文仍可以用 NVT ASCII 码来发送,而不考虑邮件内容。虽然我们前面所述的一些扩充可能会和 M I M E合 在一起产生好的效果—扩充的SMTP SIZE命令,因为MIME报文能变得很长,以及非 ASCII 标题—这些扩充并不是 M I M E所要求的。与另一方交换 M I M E报文所需的一切,就是双方都 要有一个能够理解 MIME的用户代理。在任何一个 MTA中不需要做任何改变。 MIME定义这5个新标题字段如下: Mime-Version: Content-Type: Content-Transfer-Encoding:

344

使用TCP/IP详解,卷1:协议

下载

Content-ID: Content-Description:

作为例子,下面两个标题行可以出现在一个 Internet邮件报文中: Mime-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII

当前 M I M E版本是 1 . 0,内容类型是无格式 A S C I I 码文本,即 I n t e r n e t邮件的默认选择。 P L A I N这个字被认为是内容类型( T E X T)的一个子类型,字符串 c h a r s e t = U S - A S C I I是一个 参数。 Te x t是M I M E的7个被定义的内容类型之一。图 2 8 - 7总结了RFC 1521中定义的 1 6个不同的 内容类型和子类型。对具体的内容类型和子类型来说都有指定的很多参数。 内容类型

子类型

text

plain richtext enriched mixed parallel digest altenative rfc822 partial external-body octet-stream postscript jpeg gif basic mpeg

multipart

message

application image audio video





无格式文本 简单格式文本,如粗体、斜体或下划线等 richtext的简化和改进 多个正文部分,串行处理 多个正文部分,可并行处理 一个电子邮件的摘要 多个正文部分,具有相同的语义内容 内容是另一个 RFC 822邮件报文 内容是一个邮件报文的片断 内容是指向实际报文的指针 任意二进制数据 一个PostScript程序 ISO 10918格式 CompuServe的图形交换格式 用8 bit ISDN 律格式编码 ISO 11172格式

图28-7 MIME 内容类型和子类型

内容类型和用于内容的传送编码是相互独立的。前者由首部字段 Content-Type指明,后者 由首部字段 Content-Transfer-Encoding指明。在RFC 1521中定义了5种不同的编码格式。 1) 7bit,是默认的 NVT ASCII; 2) quoted-printable,我们在前面的一个例子中看到有非 ASCII首部。当字符中只有很少一 部分的第8 bit 置1时非常有用; 3) base64,如图28-6所示; 4) 8bit,包含字符行,其中某些为非 ASCII字符且第8bit置1; 5) binary编码,无需包含多行的 8 bit 数据。 对RFC 821 MTA ,以上 5种编码格式中只有前 3种是有效的。因为这 3种产生只包含 N V T ASCII字符的正文。使用有 8BITMIME支持的扩充 SMTP允许使用8bit编码。 尽管内容类型和编码是独立的, R F C 1 5 2 1推荐有非 A S C I I数据的t e x t使用q u o t e d - p r i n t a b l e, 而i m a g e、a u d i o、v i d e o和octet-stream application 使用b a s e 6 4。这样允许与符合 RFC 821 的 MTA保持最大的互操作性。而且, multipart和message内容类型必须以 7bit编码。

下载

345

第28章 SMTP:简单邮件传送协议使用

作为一个multipart内容类型的例子,图 2 8 - 8显示了一个来自 R F C发布清单的邮件报文。子 类型是 m i x e d,意思是各部分是顺序处理的,各部分的边界是字符串 N e x t P a r t,其前面是行首 的两个连字符。 每个边界上可跟一行用于指明下一部分首部字段。忽略报文中第 1个边界之前和最后一个 边界之后的所有内容。 因为在第一个边界后面跟着一个空行,而不是首部,所以在第 1个和第 2个边界之间的数 据的内容类型被假定为具有 us-ascii字符集的text/plain。这是新RFC的文字描述。 但是第2个边界后面跟着首部字段。它指定了另一个 multipart报文,具有边界OtherAccess。 子类型为 alternative,有两种不同的选择。第 1种OtherAccess选项是用电子邮件获取 RFC,第2 种选项是用匿名 F T P获取。M I M E用户代理将列出这两种选项,允许我们选择一个,然后自动 地用电子邮件或匿名 FTP获取一份复制的 RFC。

第1个边界

这里的细节在新的RFC中

第2个边界 一个具有新边界的嵌套的多部分报文

最后的边界

图28-8 MIME multipart报文的例子

346

使用TCP/IP详解,卷1:协议

下载

这一部分是 M I M E 的一个简要概述。 M I M E 的详细细节和例子,见 RFC 1521 和[ R o s e 1993]。

28.5 小结 电子邮件包括在两端(发送方和接收方)都有的一个用户代理以及两个或多个报文传送 代理。可以把一个邮件报文分成三个部分:信封、首部和正文。我们已经看到这三个部分用 SMTP和Internet标准是如何进行交换的。所有都作为 NVT ASCII字符进行交换。 我们也看到了一些新的扩充:用于信封和非 A S C I I首部的扩充 S M T P,以及使用 M I M E的 正文增加了结构。 MIME的结构和编码允许使用已有的 7bit SMTP MTA交换任意二进制数据。

习题 28.1 读RFC 822,找到域文字( domain literal)的意思。试试用其中一个给自己发送邮件。 28.2 除了连接建立和终止外,要发送一个小的邮件报文的最小网络往返次数是多少? 28.3 T C P是一个全双工协议,但是 S M T P用半双工的形式使用 T C P。客户发送一个命令后停 止等待应答。为什么客户不一次发送多个命令,如一行中包括 H E L O、M A I L、R C P T、 DATA和QUIT命令(假定正文不是太大)? 28.4 当网络在接近其容量运行时, SMTP的这种半双工操作如何欺骗缓慢的启动机制? 28.5 当存在多个具有相同优先值的 MX记录时,名服务器是否总能以相同的顺序返回它们?

下载

第29章 网络文件系统 29.1 引言 本章中我们要讨论另一个常用的应用程序: N F S(网络文件系统),它为客户程序提供透 明的文件访问。 NFS的基础是Sun RPC:远程过程调用。我们首先必须描述一下 RPC。 客户程序使用 N F S不需要做什么特别的工作,当 N F S内核检测到被访问的文件位于一个 NFS服务器时,就会自动产生一个访问该文件的 RPC调用。 我们对 N F S如何访问文件的细节并不感兴趣,只对它如何使用 I n t e r n e t的协议,尤其是 UDP协议,感兴趣。

29.2 Sun远程过程调用 大多数的网络程序设计都是编写一些调用系统提供的函数来完成特定的网络操作的应用 程序。例如,一个函数完成 T C P的主动打开,另一个完成 T C P的被动打开,一个函数在一个 T C P连接上发送数据,另一个设置特定的协议选项(如激活 T C P的k e e p a l i v e定时器)。在1 . 1 5 节我们提到过两个常用的用于网络编程的函数集( A P I):插口 ( s o c k e t )和T L I。正像客户端和 服务器端运行的操作系统可能会不相同一样,双方使用的 API也可能会不相同。由通信协议和 应用协议决定一对客户和服务器是否可以彼此通信。如果两台主机连接在一个网络上,并且 都有一个 TCP/IP的实现,那么一台主机上的一个使用 C语言编写的、使用插口和 TCP的Unix客 户程序可以和另一台主机上的一个使用 C O B O L语言编写的、使用其他 A P I和T C P的大型机服 务器进行通信。 一般来说,客户发送命令给服务器,服务器向客户发送应答。目前为止,我们讨论过的 所有应用程序—P i n g,Tr a c e r o u t e,选路守护程序、以及 D N S、T F T P、B O O T P、S N M P、 Telnet、FTP和SMTP的客户和服务器—都是采用这种方式实现的。 远程过程调用RPC (Remote Procedure Call)是一种不同的网络程序设计方法。客户程序编 写时只是调用了服务器程序提供的函数。这只是程序员所感觉到的,实际上发生了下面一些 动作。 1) 当客户程序调用远程的过程时,它实际上只是调用了一个位于本机上的、由 R P C程序 包生成的函数。这个函数被称为客户残桩( s t u b)。客户残桩将过程的参数封装成一个网络报 文,并且将这个报文发送给服务器程序。 2) 服务器主机上的一个服务器残桩负责接收这个网络报文。它从网络报文中提取参数, 然后调用应用程序员编写的服务器过程。 3) 当服务器函数返回时,它返回到服务器残桩。服务器残桩提取返回值,把返回值封装 成一个网络报文,然后将报文发送给客户残桩。 4) 客户残桩从接收到的网络报文中取出返回值,将其返回给客户程序。 网络程序设计是通过残桩和使用诸如插口或 T L I的某个 A P I的R P C库例程来实现的,但是

348

使用TCP/IP详解,卷1:协议

下载

用户程序 —客户程序和被客户程序调用的服务器过程—不会和这个API打交道。客户应用 程序只是调用服务器的过程,所有网络程序设计的细节都被 R P C程序包、客户残桩和服务器 残桩所隐藏。 一个RPC程序包提供了很多好处。 1) 程序设计更加容易,因为很少或几乎没有涉及网络编程。应用程序设计员只需要编写 一个客户程序和客户程序调用的服务器过程。 2) 如果使用了一个不可靠的协议,如 UDP,像超时和重传等细节就由 RPC程序包来处理。 这就简化了用户应用程序。 3) RPC库为参数和返回值的传输提供任何需要的数据转换。例如,如果参数是由整数和 浮点数组成的, R P C程序包处理整数和浮点数在客户机和服务器主机上存储的不同形式。这 个功能简化了在异构环境中的客户和服务器的编码问题。 R P C程序设计的细节可以参看参考文献 [Stevens 1990]的第1 8章。两个常用的 R P C程序包 是Sun RPC和开放软件基金( OSF)分布式计算环境( DCE)的RPC程序包。我们对于 RPC的 兴趣在于想了解 Sun RPC 中过程调用和过程返回报文的形式,因为本章中讨论的网络文件系 统使用了它们。 Sun RPC的第2版定义在RFC 1057 [Sun Microsystems 1988a]中。 Sun RPC IP首部

Sun RPC 有两个版本。一个版本建立

20字节

在插口 A P I基础上,和 T C P和U D P打交道。 另一个称为 T I - R P C的(独立于运输层),

UDP首部

建立在TLI API基础上,可以和内核提供的

事务标识符(XID)

任何运输层协议打交道。尽管本章中我们

调用(0)

只讨论 T C P和U D P,从讨论的观点来看,

RPC版本(2) 程序号

两者是一样的。 图 2 9 - 1 显示的是使用 U D P 时,一个 R P C过程调用报文的格式。 I P首部和 U D P

Sun RPC过程调 用的公共部分

首部是标准的首部,我们已经在图 3 - 1和图

版本号 过程号 证书

最多400字节

验证

最多400字节

11 - 2中显示过。 U D P首部以下是 R P C程序 包定义的部分。 事务标识符( X I D)由客户程序设置, 由服务器程序返回。当客户收到一个应答, 它将服务器返回的 X I D与它发送的请求的

依赖于所调用的 具体过程

过程参数

X I D相比较。如果不匹配,客户就放弃这 个报文,等待从服务器返回的下一个报文。 每次客户发出一个新的 R P C,它就会改变

图29-1 RPC过程调用报文作为一个UDP数据报的格式

报文的XID。但是如果客户重传一个以前发送过的 RPC(因为它没有收到服务器的一个应答), 重传报文的 XID不会修改。 调用( c a l l )变量在过程调用报文中设置为 0,在应答报文中设置为 1。当前的 R P C版本是 2。 接下来三个变量:程序号、版本号和过程号,标识了服务器上被调用的特定过程。

下载

349

第29章 网络文件系统使用

证书 ( c r e d e n t i a l )字段标识了客户。有些情 况下,证书字段设置为空值;另外一些情况下,

IP首部

20字节

证书字段设置为数字形式的客户的用户号和组 号。服务器可以查看证书字段以决定是否执行

UDP首部

请求的过程。验证 ( v e r i f i e r )字段用于使用了

事务标识符(XID) Sun RPC过

应答(1) 状态(0=接受)

程应答的 公共部分

验证

D E S加密的安全 R P C。尽管证书字段和验证字 段是可变长度的字段,它们的长度也作为字段 的一部分被编码。 接下来是过程参数 (procedure parameter)字

最多400字节

接受状态(0=成功)

段。参数的格式依赖于远程过程的定义。接收者 (服务器残桩)如何知道参数字段的大小呢?既 然使用的是UDP协议,UDP数据报的大小减去验

依赖于具 体过程

过程结果 …

证字段以上所有字段的长度就是参数的大小。如 果使用的不是 UDP而是TCP,因为TCP是一个字 图29-2 RPC应答报文作为一个UDP数据报的格式 节流协议,没有记录边界,所以没有固定的长度。为了解决这个问题,在 TCP首部和XID之间增 加了一个4字节的长度字段,告诉接收者这个RPC调用由多少字节组成。这也使得一个RPC调用 报文在必要时可以用多个TCP段来传输(DNS使用了类似的技术,参见习题14-4)。 图29-2显示了一个 RPC应答报文的格式。当远程过程返回时,服务器残桩将这个报文发送 给客户残桩。 应答报文中的 X I D字段是从调用报文的 X I D字段复制而来。应答字段设置为 1,以区别于 调用报文。如果调用报文被接受,状态字段设置为 0(如果 R P C的版本号不为 2,或者服务器 不能鉴别客户的身份,调用报文可能被拒绝)。安全的RPC使用验证字段来标识服务器。 如果远程过程调用成功,接受状态字段置为 0。一个非零的值可能表示一个不合法的版本 号或者一个不合法的过程号。如果使用的不是 UDP而是TCP,如同RPC调用报文一样,在 TCP 首部和XID字段之间插入一个 4字节的长度字段。

29.3 XDR: 外部数据表示 外部数据表示 XDR (eXternal Data Representation)是一个标准,用来对 RPC调用报文和应 答报文中的值进行编码。这些值包括 R P C首部字段( X I D、程序号、接受状态等)、过程参数 和过程结果。采用标准化的方法对这些值进行编码使得一个系统中的客户可以调用另一个不 同架构的系统中的一个过程。 XDR在RFC 1014中定义[Sun Microsystems 1987]。 X D R定义了很多数据类型以及它们如何在一个 R P C报文中传输的具体形式(如比特顺序, 字节顺序等)。发送者必须采用 XDR格式构造一个RPC报文,然后接收者将 XDR格式的报文转 换为本机的表示形式。例如,在图 29-1和图29-2中,我们显示的所有整数值( XID、调用字段、 程序号等)都是 4字节的整数。在 X D R中,所有的整数的确占据 4个字节。 X D R支持的其他数 据类型包括无符号整数、布尔类型、浮点数、定长数组、可变长数组和结构。

29.4 端口映射器 包含远程过程的 R P C服务器程序使用的是临时端口,而不是知名端口。这就需要某种形

350

使用TCP/IP详解,卷1:协议

下载

式的“注册”程序来跟踪哪一个 RPC程序使用了哪一个临时端口。在 Sun RPC中,这个注册程 序被称为端口映射器 (port mapper)。 “端口”这个词作为Internet协议族的一个特征,来自于TCP和UDP端口号。既然TIRPC可以工作在任何运输层协议之上,而不仅仅是 TCP和UDP,所以使用 TI-RPC的系 统中(如SVR4和Solaris 2.2),端口映射器的名字变成了rpcbind。下面我们继续使用更 为常见的端口映射器的名字。 很自然地,端口映射器本身必须有一个知名端口: UDP端口111和TCP端口111。端口映射 器也就是一个 R P C服务器程序。它有一个程序号( 1 0 0 0 0 0)、一个版本号( 2)、一个T C P端口 111 和一个 U D P 端口 111 。服务器程序使用 R P C调用向端口映射器注册自身,客户程序使用 RPC调用向端口映射器查询。端口映射器提供四个服务过程: 1) PMAPPROC_SET。一个RPC服务器启动时调用这个过程,注册一个程序号、版本号和 带有一个端口号的协议。 2) PMAPPROC_UNSET。RPC服务器调用此过程来删除一个已经注册的映射。 3) PMAPPROC_GETPORT。一个 R P C客户启动时调用此过程。根据一个给定的程序号、 版本号和协议来获得注册的端口号。 4) PMAPPROC_DUMP 。返回端口映射器数据库中所有的记录(每个记录包括程序号、 版本号、协议和端口号): 在一个 R P C服务器程序启动,接着被一个 R P C客户程序调用的过程中,进行了以下一些 步骤: 1) 一般情况下,当系统引导时,端口映射器必须首先启动。它创建一个 TCP端点,并且被 动打开TCP端口111。它也创建一个 UDP端点,并且在UDP端口111等待着UDP数据报的到来。 2) 当R P C服务器程序启动时,它为它所支持的程序的每一个版本创建一个 T C P端点和一 个U D P端点(一个给定的 R P C程序可以支持多个版本。客户调用一个服务器过程时,说明它 想要哪一个版本)。两个端点各自绑定一个临时端口( T C P端口号和 U D P端口号是否一致无关 紧要)。服务器通过 R P C调用端口映射器的 P M A P P R O C _ S E T过程,注册每一个程序、版本、 协议和端口号。 3) 当R P C客户程序启动时,它调用端口映射器的 P M A P P R O C _ G E T P O RT过程来获得一个 指定程序、版本和协议的临时端口号。 4) 客户发送一个 RPC调用报文给第 3步返回的端口号。如果使用的是 UDP,客户只是发送 一个包含 R P C调用报文(见图 2 9 - 1)的U D P数据报到服务器相应的 U D P端口。服务器发送一 个包含RPC应答报文(见图 29-2)的UDP数据报到客户作为响应。 如果使用的是 T C P,客户对服务器的 T C P端口号做一个主动打开,然后在建立的 T C P连接 上发送一个 RPC调用报文。服务器作为响应,在连接上发送一个 RPC应答报文。 程序 r p c i n f o ( 8 ) 打印了端口映射器中当前的映射记录(它调用了端口映射器的 PMAPPROC_DUMP过程)。这里给出的是典型的输出: NFS的安装守护程序

下载

351

第29章 网络文件系统使用 NFS本身 NFS的加锁管理程序

可以看出一些程序确实支持多个版本。在端口映射器中,每一个程序号、版本号和协议 的组合都有自己的端口号映射。 安装守护程序( mount daemon)的两个版本可以通过同样的 TCP端口号(702)和同样的 U D P端口号( 6 9 9)来访问,而加锁管理程序( lock manager)的每个版本都有各自不同的端 口号。

29.5 NFS协议 使用N F S,客户可以透明地访问服务器上的文件和文件系统。这不同于提供文件传输的 F T P(第2 7章)。F T P会产生文件一个完整的副本。 N F S只访问一个进程引用文件的那一部分, 并且N F S的一个目的就是使得这种访问透明。这就意味着任何能够访问一个本地文件的客户 程序不需要做任何修改,就应该能够访问一个 NFS文件。 N F S是一个使用 Sun RPC构造的客户服务器应用程序。 N F S客户通过向一个 N F S服务器发 送R P C请求来访问其上的文件。尽管这一工作可以使用一般的用户进程来实现—即N F S客 户可以是一个用户进程,对服务器进行显式调用。而服务器也可以是一个用户进程—因为 两个理由,NFS一般不这样实现。首先,访问一个 NFS文件必须对客户透明。因此, NFS的客 户调用是由客户操作系统代表用户进程来完成的。第二,出于效率的考虑, N F S服务器在服 务器操作系统中实现。如果 N F S服务器是一个用户进程,每个客户请求和服务器应答(包括 读和写的数据)将不得不在内核和用户进程之间进行切换,这个代价太大。 本节中,我们考察在 RFC1094中说明的第 2版的NFS [Sun Microsystems 1988b]。[X/Open 1991] 中给出了 Sun RPC 、X D R和N F S的一个更好的描述。 [Stern 1991] 给出了使用和管理 NFS的细节。第 3版的NFS协议在1993年发布,我们在 29.7节中对它做一个简单的描述。 图29-3显示了一个 NFS客户和一个 NFS服务器的典型配置,图中有很多地方需要注意。 1) 访问的是一个本地文件还是一个 N F S文件对于客户来说是透明的。当文件被打开时, 由内核决定这一点。文件被打开之后,内核将本地文件的所有引用传递给名为“本地文件访 问”的框中,而将一个 NFS文件的所有引用传递给名为“ NFS客户”的框中。 2) NFS客户通过它的TCP/IP模块向NFS服务器发送 RPC请求。NFS主要使用UDP,最新的 实现也可以使用 TCP。 3) NFS服务器在端口 2 0 4 9接收作为 U D P数据报的客户请求。尽管 N F S可以被实现成使用 端口映射器,允许服务器使用一个临时端口,但是大多数的实现都是直接指定 UDP端口2049。 4) 当N F S服务器收到一个客户请求时,它将这个请求传递给本地文件访问例程,后者访 问服务器主机上的一个本地的磁盘文件。 5) NFS 服务器需要花一定的时间来处理一个客户的请求。访问本地文件系统一般也需要 一部分时间。在这段时间间隔内,服务器不应该阻止其他的客户请求得到服务。为了实现这 一功能,大多数的 NFS服务器都是多线程的—即服务器的内核中实际上有多个 NFS服务器在

352

使用TCP/IP详解,卷1:协议

下载

运行。具体怎么实现依赖于不同的操作系统。既然大多数的 U n i x内核不是多线程的,一个共 同的技术就是启动一个用户进程(常被称为 n f s d)的多个实例。这个实例执行一个系统调用, 使自己作为一个内核进程保留在操作系统的内核中。 用户进程

本地文件 访问

本地文件 访问

NFS 服务器

NFS客户

UDP端口 2049 TCP/UDP IP

TCP/UDP IP 客户内核

服务器内核

本地磁盘

本地磁盘

图29-3 NFS客户和NFS服务器的典型配置

6) 同样,在客户主机上, N F S客户需要花一定的时间来处理一个用户进程的请求。 N F S 客户向服务器主机发出一个 R P C调用,然后等待服务器的应答。为了给使用 N F S的客户主机 上的用户进程提供更多的并发性,在客户内核中一般运行着多个 N F S客户。同样,具体实现 也依赖于操作系统。 Unix系统经常使用类似于 NFS服务器的技术:一个叫作 biod的用户进程执 行一个系统调用,作为一个内核进程保留在操作系统的内核中。 大多数的Unix主机可以作为一个 NFS客户,一个NFS服务器,或者两者都是。大多数 PC机 的实现(MS-DOS)只提供了NFS客户实现。大多数的IBM大型机只提供了NFS服务器功能。 NFS实际上不仅仅由 NFS协议组成。图29-4显示了NFS使用的不同 RPC程序。 应用程序

程序号

端口映射器 NFS 安装程序 加锁管理程序 状态监视器

100000 100003 100005 100021 100024

版本号 2 2 1 1, 2, 3 1

过程数 4 15 5 19 6

图29-4 NFS使用的不同RPC程序

在这个图中,程序的版本是在 SunOS 4.1.3中使用的。更新的实现提供了其中一些 程序更新的版本。例如,Solaris 2.2还支持端口映射器的第3版和第4版,以及安装守护 程序的第2版。SVR4支持第3版的端口映射器。 在客户能够访问服务器上的文件系统之前, N F S客户主机必须调用安装守护程序。我们 在下面讨论安装守护程序。 加锁管理程序和状态监视器允许客户锁定一个 N F S服务器上文件的部分区域。这两个程

353

第29章 网络文件系统使用

下载

序独立于NFS协议,因为加锁需要知道客户和服务器的状态,而 NFS本身在服务器上是无状态 的(下面我们对 N F S的无状态会介绍得更多)。[X/Open 1991] 的第9, 10和11章说明了使用加 锁管理程序和状态监视器进行 NFS文件锁定的过程。 29.5.1 文件句柄 NFS中一个基本概念是文件句柄 (file handle)。它是一个不透明 (opaque)的对象,用来引用 服务器上的一个文件或目录。不透明指的是服务器创建文件句柄,把它传递给客户,然后客 户访问文件时,使用对应的文件句柄。客户不会查看文件句柄的内容—它的内容只对服务 器有意义。 每次一个客户进程打开一个实际上位于一个 N F S服务器上的文件时, N F S客户就会从 N F S 服务器那里获得该文件的一个文件句柄。每次 N F S客户为用户进程读或写文件时,文件句柄 就会传给服务器以指定被访问的文件。 一般情况下,用户进程不会和文件句柄打交道—只有NFS客户和NFS服务器将文件句柄 传来传去。在第 2版的NFS中,一个文件句柄占据 32个字节,第 3版中增加为 64个字节。 Unix服务器一般在文件句柄中存储下面的信息:文件系统标识符(文件系统最大 和最小的设备号),i-node号(在一个文件系统中唯一的数值)和一个 i-node的生成码 (每当一个i-node被一个不同的文件重用时就改变的数值)。 29.5.2 安装协议 客户必须在访问服务器上一个文件系统中的文件之前,使用安装协议安装那个文件系统。 一般情况下,这是在客户主机引导时完成的。最后的结果就是客户获得服务器文件系统的一 个文件句柄。 图29-5显示了一个Unix客户发出mount(8)命令所发生的情况,它说明一个NFS的安装过程。 用户进程

用户进程 端口 映射器

mount命令

用户进程 (1) 在启 动时注册

mountd 守护程序

(6) mount 系统调用 (2) 获取端口号的RPC请求 (3) RPC应答以端口号 (4) 安装文件系统的RPC请求 (5) RPC应答以文件句柄

客户内核

服务器内核

图29-5 使用Unix mount命令的安装协议

依次发生了下面的动作。 1) 服务器上的端口映射器一般在服务器主机引导时被启动。 2) 安装守护程序( m o u n t d)在端口映射器之后被启动。它创建了一个 T C P端点和一个

354

使用TCP/IP详解,卷1:协议

下载

UDP端点,并分别赋予一个临时的端口号。然后它在端口映射器中注册这些端口号。 3) 在客户机上执行 m o u n t命令,它向服务器上的端口映射器发出一个 R P C调用来获得服 务器上安装守护程序的端口号。客户和端口映射器交互既可以使用 T C P也可以使用 U D P,但 一般使用UDP。 4) 端口映射器应答以安装守护程序的端口

sun客户

号。 5) m o u n t 命令向安装守护程序发出一个 RPC调用来安装服务器上的一个文件系统。同样, 既可以使用 T C P也可以使用 U D P,但一般使用

bsdi服务器

UDP。服务器现在可以验证客户,使用客户的 IP 地址和端口号来判别是否允许客户安装指定的文 件系统。

NFS安装程序

6) 安装守护程序应答以指定文件系统的文 件句柄。 7) 客户机上的 m o u n t命令发出 m o u n t系统调 用将第 5步返回的文件句柄与客户机上的一个本 地安装点联系起来。文件句柄被存储在 N F S客户 代码中,从现在开始,用户进程对于那个服务器 文件系统的任何引用都将从使用这个文件句柄

图29-6 将bsdi:/usr 目录安装成主机 sun 上的/nfs/bsdi/usr

开始。

目录

上述实现技术将所有的安装处理,除了客户机上的 m o u n t系统调用,都放在用户进程中, 而不是放在内核中。我们显示的三个程序—m o u n t命令、端口映射器和安装守护程序— 都是用户进程。 作为一个例子,在我们的主机 sun(一个NFS客户机)上执行: sun

# mount -t nfs bsdi:/usr /nfs/bsdi/usr

这个命令将主机 b s d i(一个 N F S服务器)上的 / u s r目录安装成为本地文件系统 / n f s / b s d i / usr。图29-6显示了结果。 当我们引用客户机 s u n上的/ n f s / b s d i / u s r / r s t e v e n s / h e l l文件时,实际上 o.c 引用的是服务器 bsdi上的文件/usr/rstevens/hello.c。 29.5.3 NFS过程 现在我们描述NFS服务器提供的15个过程(使用的个数与 NFS过程的实际个数不一样,因 为我们把它们按照功能分了组)。尽管N F S被设计成可以在不同的操作系统上工作,而不仅仅 是U n i x系统,但是一些提供 U n i x功能的过程可能不被其他操作系统支持(例如硬链接、符号 链接、组的属主和执行权等)。[Stevens 1992]的第4章包含了 U n i x文件系统其他的一些信息, 其中有些被 NFS采用。 1) GETAT T R。返回一个文件的属性:文件类型(一般文件,目录等)、访问权限、文件 大小、文件的属主者及上次访问时间等信息。 2) SETAT T R。设置一个文件的属性。只允许设置文件属性的一个子集:访问权限、文件

下载

355

第29章 网络文件系统使用

的属主、组的属主、文件大小、上次访问时间和上次修改时间。 3) STATFS。返回一个文件系统的状态:可用空间的大小、最佳传送大小等。例如 Unix的 df命令使用此过程。 4) LOOKUP 。查找一个文件。每当一个用户进程打开一个 N F S服务器上的一个文件时, NFS客户调用此过程。 5) READ 。从一个文件中读数据。客户说明文件的句柄、读操作的开始位置和读数据的 最大字节数(最多 8192个字节)。 6) WRITE。对一个文件进行写操作。客户说明文件的句柄、开始位置、写数据的字节数 和要写的数据。 7) CREATE。创建一个文件。 8) REMOVE。删除一个文件。 9) RENAME。重命名一个文件。 10) LINK。为一个文件构造一个硬链接。硬链接是一个 U n i x的概念,指的是磁盘中的一 个文件可以有任意多个目录项(即名字,也叫作硬链接)指向它。 11) SYMLINK。为一个文件创建一个符号链接。符号链接是一个包含另一个文件名字的 文件。大多数引用符号链接的操作(例如,打开)实际上引用的是符号链接所指的文件。 12) READLINK。读一个符号链接。即返回符号链接所指的文件的名字。 13) MKDIR。创建一个目录。 14) RMDIR。删除一个目录。 15) READDIR。读一个目录。例如, Unix的ls命令使用此过程。 这些过程实际上有一个前缀 NFSPROC_,我们把它省略了。 29.5.4 UDP还是TCP N F S最初是用 U D P写的,所有的厂商都提供了这种实现。最新的一些实现也支持 T C P。 TCP支持主要用于广域网,它可以使文件操作更快。 NFS已经不再局限于局域网的使用。 当从LAN转换到WAN时,网络的动态特征变化得非常大。往返时间( round-trip time)变 动范围大,拥塞经常发生。 WA N的这些特征使得我们考虑使用具有 T C P属性的算法—慢启 动,但是可以避免拥塞。既然 U D P没有提供任何类似的东西,那么在 N F S客户和服务器上加 进同样的算法或者使用 TCP。 29.5.5 TCP上的NFS 伯克利实现的 Net/2 NFS支持U D P或者T C P。[Macklem 1991]描述了这个实现。让我们看 一下使用TCP有什么不同。 1) 当服务器主机进行引导时,它启动一个 N F S服务器,后者被动打开 T C P端口2 0 4 9,等 待着客户的连接请求。这通常是另一个 N F S服务器,正常的 NFS UDP服务器在 U D P端口2 0 4 9 等待着进入的UDP数据报。 2) 当客户使用 T C P安装服务器上的文件系统时,它对服务器上的 T C P端口2 0 4 9做一个主 动打开。这样就为这个文件系统在客户和服务器之间形成了一个 T C P连接。如果同样的客户 安装同样服务器上的另一个文件系统,就会创建另一个 TCP连接。

356

使用TCP/IP详解,卷1:协议

下载

3) 客户和服务器在它们连接的两端都要设置 T C P的k e e p a l i v e选项,这样双方都能检测到 对方主机崩溃,或者崩溃然后重启动。 4) 客户方所有使用这个服务器文件系统的应用程序共享这个 T C P连接。例如,在图 2 9 - 6 中,如果在 b s d i的/ u s r目录下还有另一个目录 s m i t h,那么对两个目录 / n f s / b s d i / u s r / r s t e v e n s和 /nfs/bsdi/usr/smith下所有文件的引用将共享同样的 TCP连接。 5) 如果客户检测到服务器已经崩溃,或者崩溃然后重启动(通过收到一个 T C P差错“连 接超时”或者“对方复位连接”),它尝试与服务器重新建立连接。客户做另一个主动打开, 为同一个文件系统请求重新建立 T C P连接。在以前连接上超时的所有客户请求在新的连接上 都会重新发出。 6) 如果客户机崩溃,那么当它崩溃时正在运行的应用程序也要崩溃。当客户机重新启动 时,它很可能使用 T C P重新安装服务器的文件系统,这将导致和服务器的另一个连接。客户 和服务器之间针对同一个文件系统的前一个连接现在打开了一半(服务器方认为它还开着), 但是既然服务器设置了 k e e p a l i v e选项,当服务器发出下一个 k e e p a l i v e探查报文时,这个半开 着的TCP连接就会被中止。 随着时间的流逝,另外一些厂商也计划支持 TCP上的NFS。

29.6 NFS实例 我们使用 t c p d u m p来看一下在典型的文件操作中,客户调用了哪些 N F S过程。当 t c p d u m p 检测到一个包含 R P C调用(在图 2 9 - 1中调用字段等于 0)、目的端口是 2 0 4 9的U D P数据报时, 它把数据报按照一个 N F S请求进行解码。类似地,如果一个 U D P数据报是一个 R P C应答(在 图29-2中应答字段为1),源端口是 2049,tcpdump就把此数据报作为一个 NFS应答来解码。 29.6.1 简单的例子:读一个文件 第一个例子是使用 cat(1)命令将位于一个 NFS服务器上的一个文件复制到终端上: sun % cat /nfs/bsdi/usr/rstevens/hello.c main() { printf("h e l l o , w o r l d \")n; }

把文件复制到终端

如同图 2 9 - 6所示,主机 s u n(N F S客户机)上的文件系统 /nfs/bsdi/usr 实际上是主机 b s d i (N F S服务器)上的 /usr 文件系统。当 c a t打开这个文件时, s u n上的内核检测到这一点,然后 使用NFS去访问文件。图 29-7显示了tcpdump的输出。 当tcpdump解析一个NFS请求或应答报文时,它打印客户的 XID字段,而不是端口号。第 1 行和第2行中的XID字段值是0x7aa6。 客户内核中的打开函数一次处理文件名 /nfs/bsdi/usr/rstevens/hello.c 中的一个成员。当处 理到/nfs/bsdi/usr时,它发现这是指向一个已安装的 NFS文件系统的一个安装点。 在第1行中,客户调用 G E TAT T R过程取得客户已经安装的服务器目录的属性( / u s r)。这 个R P C请求,除 I P首部和 U D P首部之外,包含 1 0 4个字节的数据。第 2行中的应答返回了一个 O K值,除了 I P首部和U D P首部之外,包含了 9 6个字节的数据。在这个图中,我们可以看出最 小的NFS报文包含大约100个字节的数据。

357

第29章 网络文件系统使用

下载

图29-7 读一个文件的NFS操作

在第3行中,客户调用 L O O K U P过程来查看 r s t e v e n s文件。在第 4行中收到一个 O K应答。 L O O K U P过程说明了文件名 r s t e v e n s和远程文件系统被安装时由内核保存的文件句柄。应答中 包含了下一步要使用的一个新的文件句柄。 在第5行中,客户使用第4行中返回的文件句柄对 hello.c调用LOOKUP过程。在第6行返回了 另一个文件句柄。新的文件句柄就是客户在第7行和第9行中引用文件/nfs/bsdi/usr/rstevens/hello.c 所使用的文件句柄。我们看到客户对于正在打开的路径名的每个成员都调用了一次 LOOKUP过 程。 在第7行中,客户又调用了一次 G E TAT T R过程,接着在第 9行中调用了 R E A D过程。客户 请求从偏移 0开始的 1 0 2 4个字节,但是接收到的没有这么多(减去 R P C字段和其他由 R E A D过 程返回的值的大小,在第 10行中返回了 38个字节的数据。这是文件 hello.c的实际大小)。 在这个例子中,应用进程对于内核所做的这些 R P C请求和应答一点儿也不知道。应用进 程只是调用了内核的 o p e n函数,后者引起了 3个R P C请求和 3个应答( 1 ~ 6行),然后应用进程 又调用了内核的 r e a d函数,它引起了两个请求和两个应答( 7 ~ 1 0行)。该文件位于一个 N F S文 件服务器,这一点对客户应用进程来说是透明的。 29.6.2 简单的例子:创建一个目录 作为另一个简单的例子,我们将当前工作目录改变为一个 N F S服务器上的一个目录,然 后创建一个新的目录: sun sun

% cd /nfs/bsdi/usr/rstevens % mkdir Mail

改变当前工作目录 并且创建一个目录

图29-8显示了tcpdump的输出。

图29-8 NFS的操作:cd到NFS目录,然后mkdir

358

使用TCP/IP详解,卷1:协议

下载

改变目录引起客户调用了两次 G E TAT T R过程(1 ~ 4行)。当我们创建新的目录时,客户调 用了G E TAT T R过程( 5 ~ 6行),接着调用 L O O K U P过程(7 ~ 8行,用来验证将创建的目录不存 在),跟着调用了MKDIR过程来创建目录( 9-10行)。在第8行中,应答 OK并不表示目录存在。 它只是表示过程返回了。 t c p d u m p并不理解 N F S过程的返回值。它一般打印 O K和应答报文中 数据的字节数。 29.6.3 无状态 N F S的一个特征( N F S的批评者称之为 N F S的一个瑕疵,而不是一个特征)是 N F S服务器 是无状态的 ( s t a t e l e s s )。服务器并不记录哪个客户正在访问哪个文件。请注意一下在前面给出 的N F S过程中,没有一个 o p e n操作和一个 c l o s e操作。 L O O K U P过程的功能与 o p e n操作有些类 似,但是服务器永远也不会知道客户对一个文件调用了 LOOKUP过程之后是否会引用该文件。 无状态设计的理由是为了在服务器崩溃并且重启动时,简化服务器的崩溃恢复操作。 29.6.4 例子:服务器崩溃 在下面的例子中我们从一个崩溃然后重启动的 N F S服务器上读一个文件。这个例子演示 了无状态的服务器是如何使得客户不知道服务器的崩溃。除了在服务器崩溃然后重启动时一 个时间上的暂停外,客户并不知道发生的问题,客户应用进程没有受到影响。 在客户机sun上,我们对一个长文件( NFS服务器主机 svr4上的文件 /usr/share/lib/termcap) 执行c a t命令。在传送过程中把以太网的网线拔掉,关闭然后重启动服务器主机,再重新将网 线连上。客户被配置成每个 NFS read过程读1024个字节。图 29-9显示了tcpdump的输出。 1 ~ 1 0行对应于客户打开文件,操作类似于图 2 9 - 7所示。在第 11行我们看到对文件的第一 个R E A D操作,在 1 2行返回了 1 0 2 4个字节的数据。这个操作一直继续到 1 2 9行(读1 0 2 4个字节 的数据,跟着一个 OK应答)。 在第1 3 0行和第 1 3 1行我们看到两个请求超时,并且分别在 1 3 2行和1 3 3行重传。第一个问 题是这里为什么会有两个读请求,一个从偏移 6 5 5 3 6开始读,另一个从偏移 7 3 7 2 8开始读?答 案是客户内核检测到客户应用进程正在进行顺序地读操作,所以试图预先取得数据块(大多 数的U n i x内核都采用了这种预读技术)。客户内核也正在运行多个 N F S块I / O守护程序,后者 试图代表客户产生多个 RPC请求。一个守护程序正在从偏移 65536处读8192个字节(以1024字 节为一组数据块),而另一个正在从 73728处预读8192个字节。 客户重传发生在 130~168行。在第169行我们看到服务器已经重启动,在它对第 168行的客 户N F S请求做出应答之前,它发送了一个 A R P请求。对 1 6 8行的响应被发送在 1 7 1行。客户的 READ操作继续进行下去。 除了从 1 2 9行到1 7 1行5分钟的暂停,客户应用进程并不知道服务器崩溃然后又重启动了。 这个服务器的崩溃对于客户是透明的。 为了研究这个例子中的超时和重传时间间隔,首先要意识到这儿有两个客户守护程序, 分别有它们各自的超时。第 1个守护程序(在偏移 6 5 5 3 6处开始读)的间隔,四舍五入到两个 十进制小数点,为 0.68, 0.87, 1.74, 3.48, 6.96, 13.92, 20.0, 20.0, 20.0等等。第2个守护程序(在 偏移7 3 7 2 8处开始读)的间隔也是一样的(精确到两个小数点)。可以看出这些 N F S客户使用 了一个这样的超时定时器:间隔为 0.875秒的倍数,上限为 20秒。每次超时后,重传间隔翻倍:

359

第29章 网络文件系统使用

下载 0.875, 1.75, 3.5, 7.0和14.0。

连续地读

连续重传

服务器重启动

连续读

图29-9 当一个NFS服务器崩溃然后重启动时,客户正在读一个文件的过程

客户要重传多久呢?客户有两个与此有关的选项。首先,如果服务器文件系统是“硬” 安装的,客户就会永远重传下去。但是如果服务器文件系统是“软”安装的,客户重传了固 定数目的次数之后就会放弃。在“硬”安装的情况下,客户还有一个选项决定是否允许用户 中断无限制的重传。如果客户主机安装服务器文件系统时说明了中断能力,并且如果我们不 想在服务器崩溃之后等 5分钟,等着服务器重启动,就可以键入一个中断键以终止客户应用

360

使用TCP/IP详解,卷1:协议

下载

程序。 29.6.5

等幂过程

如果一个 R P C过程被服务器执行多次仍然返回同样的结果,那么就把它叫作等幂过程 (Idempotent Procedure)。例如, N F S的读过程是等幂的。正像我们在图 2 9 - 9中看到的,客户 只是重发一个特定的 READ调用直到它得到一个响应。在我们的例子中,重传的原因是服务器 崩溃了。如果服务器没有崩溃,而是 R P C应答报文丢失了(既然 U D P是不可靠的),客户只是 重传请求,服务器再一次执行同样的 READ过程。同一个文件的同一部分被重读一次,发送给 客户。 这种方法行得通的原因在于每个 R E A D请求指出了读操作开始的偏移位置。如果有一个 N F S过程要求服务器读一个文件的下 N个字节,这种方法就不行了。除非服务器被做成是有状 态的(与无状态相反),如果一个应答丢失了,客户重发读下 N个字节的 R E A D请求,结果将 是不一样的。这就是为什么 N F S的R E A D和W R I T E过程要求客户说明开始的偏移位置的原因。 客户维护着状态(每个文件当前的偏移位置),而不是服务器。 不幸的是并不是所有的文件系统操作都是等幂的。例如,考虑下面的动作:客户 N F S发 出R E M O V E请求来删除一个文件;服务器 N F S删除了文件,并回答 O K;服务器的回答丢失 了;客户 NFS超时,然后重传请求;服务器 NFS找不到指定的文件,回答指出一个错误;客户 应用程序接收到一个错误表示文件不存在。这个返回给客户应用程序的错误是不对的—该 文件的确存在并且被删除了。 等幂的 N F S过程是: G E TAT T R、S TAT E S、L O O K U P、R E A D、W R I T E、R E A D L I N K和 R E A D D I R。不是等幂的过程是: C R E AT E、R E M O V E 、R E N A M E、L I N K、S Y M L I N K、 MKDIR和RMDIR。SETATTR过程如果不用来截断文件,一般是等幂的。 既然使用 U D P总会发生响应报文丢失的现象, N F S服务器需要一种方法来处理非等幂的 操作。大多数的服务器实现了一个最近应答的高速缓存,用于存放非等幂操作最近的应答。 每当服务器收到一个请求,它首先检查这个高速缓存,如果找到了一个匹配,就返回以前的 应答而不再调用相应的 NFS过程。[Juszczak 1989]提供了这种高速缓存的实现细节。 等幂服务器过程的概念可以应用于任何基于 U D P的应用程序,而不仅仅是 N F S。例如, D N S也提供了一个等幂服务。一个 D N S的服务器可以任意多次地执行一个解析者的请求而没 有任何不良的后果(如果不考虑网络资源浪费的话)。

29.7 第3版的NFS 1993年发布了第 3版的NFS协议规范[Sun Microsystem 1994]。其实现有望在 1994年成为可 能。 我们总结一下第 2版和第3版的主要区别。下面把两者分别称为 V2和V3。 1) V2中的文件句柄是 32字节的固定大小的数组。在 V3中,它变成了一个最多为 64个字节 的可变长度的数组。在 XDR中,一个可变长度的数组被编码为一个 4字节的数组成员个数跟着 实际的数组成员字节。这样在实现时减少了文件句柄的长度,例如 U n i x只需要 1 2个字节,但 又允许非Unix实现维护另外的信息。 2) V2 将每个READ和WRITE RPC过程可以读写的数据限制为 8192个字节。这个限制在 V3

下载

361

第29章 网络文件系统使用

中取消了,这就意味着一个 U D P上的实现只受到 I P数据报大小的限制( 6 5 5 3 5字节)。这样允 许在更快的网络上读写更大的分组。 3) 文件大小以及 R E A D和W R I T E过程开始偏移的字节从 3 2字节扩充到 6 4字节,允许读写 更大的文件。 4) 每个影响文件属性值的调用都返回文件的属性。这样减少了客户调用 GETATTR过程的 次数。 5) WRITE过程可以是异步的,而在 V 2中要求同步的 W R I T E过程。这样可以提高 W R I T E 过程的性能。 6) V3 中删去了一个过程( S TAT F S),增加了七个过程: A C C E S S(检查文件访问权限)、 M K N O D(创建一个 U n i x特殊文件)、R E A D D I R P L U S(返回一个目录中的文件名字和它们的 属性)、FSINFO(返回一个文件系统的静态信息)、FSSTAT(返回一个文件系统的动态信息)、 PAT H C O N F(返回一个文件的 P O S I X . 1信息)和 C O M M I T(将以前的异步写操作提交到外存 中)。

29.8 小结 RPC是构造客户-服务器应用程序的一种方式,使得看起来客户只是调用了服务器的过程。 所有的网络操作细节都被隐藏在 R P C程序包为一个应用程序生成的客户和服务器残桩以及 R P C库的例程中。我们显示了 R P C调用和应答报文的格式,并且提到了使用 X D R对传输的值 进行编码,使得 RPC客户和服务器可以运行在不同架构的机器上。 最广泛使用的 R P C应用之一就是 S u n的N F S,一个在各种大小的主机上广泛实现的异构的 文件访问协议。我们浏览了 N F S和它使用 U D P和T C P的方式。第 2版的N F S协议定义了 1 5个过 程。 一个客户对一个 N F S服务器的访问开始于安装协议,返回给客户一个文件句柄。客户接 着可以使用那个文件句柄来访问服务器文件系统中的文件。在服务器上,一次检查文件名的 一个成员,返回每个成员的一个新的文件句柄。最后的结果就是要引用的文件的一个文件句 柄,它可以在随后的读写操作中被使用。 N F S试图把它的所用过程都做成等幂的,使得如果响应报文丢失了,客户只需要重发一 个请求。我们看到了服务器崩溃然后又重启动时,一个客户读服务器上的一个文件的例子。

习题 29.1 在图2 9 - 7中,我们看到 t c p d u m p将分组理解为 N F S的请求和应答,打印了 X I D。t c p d u m p 可以为任何的RPC请求或者应答这样做吗? 29.2 在一个 U n i x系统中,你认为为什么 R P C服务器程序使用的是临时端口,而不是知名端 口? 29.3 一个R P C客户调用了两个服务器过程。第 1个服务器过程执行花了 5秒钟的时间,第二个 过程花了1秒钟。客户有一个 4秒钟的超时。画出客户与服务器之间在时间轴上交互的信 息(假定信息从客户传到服务器或者相反都不花时间)。 29.4 在图2 9 - 9的例子中,如果 N F S服务器关机时,把它的以太网卡给换掉了,将会发生什么 事情?

362

使用TCP/IP详解,卷1:协议

下载

29.5 在图2 9 - 9中,当服务器重启动后,它处理了从偏移 6 5 5 3 6开始的请求( 1 6 8行和1 7 1行), 然后处理了从偏移 6 6 5 6 0开始的下一个请求( 1 7 2行和1 7 3行)。对于从偏移 7 3 7 2 8开始的 请求怎么处理的呢?( 167行) 29.6 当描述等幂 NFS过程时,我们给出了一个 REMOVE应答在网络中丢失的例子。在这种情 况下,如果使用的是 TCP而不是UDP会怎么样呢? 29.7 如果NFS服务器使用的是一个临时端口而不是 2049,那么当服务器崩溃然后又重启动时, 一个NFS客户会发生什么情况呢? 29.8 每个主机最多只有 1 0 2 3个保留端口,所以保留端口是很缺乏的( 1 . 9节)。如果一个 N F S 服务器要求它的客户拥有保留端口(公共的端口),一个N F S客户使用 T C P安装了N个不 同的服务器上的 N个文件系统,那么客户对每个连接都需要一个不同的保留端口号吗?

下载

第30章 其他的TCP/IP应用程序 30.1 引言 本章中我们描述了另外一些很多实现都支持的 T C P / I P应用程序。有些很简单,易于全面 了解( F i n g e r和W h o i s),而另一个则相当复杂( X窗口系统)。我们只提供了这个复杂应用程 序的一个简短的概述,集中介绍其对 TCP/IP协议的使用。 另外,我们提供一些 I n t e r n e t上资源发现工具的概述。包括一组在 I n t e r n e t上导航的工具, 可以帮助寻找一些我们不知道确切位置和名字的信息。

30.2 Finger协议 Finger协议返回一个指定主机上一个或多个用户的信息。它常被用来检查某个人是否登录 了,或者搞清一个人的登录名以便给他发送邮件。 RFC1288 [Zimmerman 1991] 指明了这个协 议。 由于两个原因,很多站点不支持一个 F i n g e r服务器。第一, F i n g e r服务器的一个早期版本 中的一个编程错误被 1 9 8 8年声名狼藉的 I n t e r n e t蠕虫病毒利用,作为进入点之一( R F C 11 3 5 [Reynolds 1989] 和 [Curry 1992] 更详细地描述了蠕虫)。第二,Finger协议有可能会泄露一些 很多管理员认为是有关用户的私有信息(登录名、电话号码,他们上次的登录时间,等等)。 RFC1288的第3节给出了这个有关服务安全方面的细节。 从一个协议的角度来看, Finger服务器有一个知名的端口 79。客户对这个端口做一个主动 打开,然后发送一个在线的请求。服务器处理这个请求,把输出发送回去,然后关闭连接。 查询和响应都是采用 NVT ASCII,类似于我们在 FTP和SMTP协议中所看到的。 尽管大多数的 U n i x用户都是使用 finger ( 1 )客户来访问F i n g e r服务器,我们将从使用 Te l n e t 客户与Finger服务器直接相连开始,看看客户发出的每一条在线命令。如果客户的查询是一个 空行(在 NVT ASCII中,空行以一个回车符 C R跟着一个换行符 L F来传输),它就是一个请求 查询所有在线用户信息的命令。 Telnet客户输出前三行

这儿我们键入回车作为Finger客户的命令

Telnet客户的输出

o ff i c e和o ffice phone的空白输出字段是从用户的口令 ( p a s s w o r d )文件记录的选项字段中取 出的(在这个例子中,这两个字段的值没有提供)。 服务器必须在最后做一个主动的关闭操作,因为服务器返回的是一个可变长度的信息。

364

使用TCP/IP详解,卷1:协议

下载

当客户收到文件结束字符时,就知道服务器的输出结束了。 当客户的请求由一个用户名组成时,服务器只以该用户的信息作为响应。下面是另一个 例子,这个例子中删去了 Telnet客户的输出: 这是我们键入的客户请求

当一个系统完全禁止了 Finger服务时,因为没有进程被动打开端口 79,所以客户的主动打 开将从服务器接收到一个 RST。

一些站点在端口 7 9提供了一个服务器,但服务器只是向客户输出信息,而不理睬客户的 任何请求: 这一行是Finger客户输出的;其余行是服务器输出的

对一个组织来说,另一种可能就是实现一个防火墙网关:在组织内部和 I n t e r n e t之间的一 个路由器,负责过滤(也就是扔掉)特定的 I P数据报( [Cheswick and Bellovin 1994] 详细讨 论了防火墙网关)。防火墙网关可以被配置成扔掉从 Internet进来的这样一些数据报,这些数据 报是目的端口为 79的TCP报文段。 对于F i n g e r的服务器和 U n i x的F i n g e r客户还有其他的实现。欲知详情,请参考 R F C 1 2 8 8和 有关finger(1)的手册。 RFC1288指出提供了Finger服务器的、具有TCP/IP连接的自动售货机应该对客户的 空行请求响应以现有产品的列表。对于由一个名字组成的客户请求,它们应该响应以 一个数目或者与这个产品有关的可用项的列表。

30.3 Whois协议 W h o i s协议是另一种信息服务。尽管任何站点都可以提供一个 W h o i s服务器,在 I n t e r N I C 站点(rs.internic.net)的服务器是最常使用的。这个服务器维护着所有的 DNS域和很多连接在 I n t e r n e t上的系统的系统管理员的信息(另一个可用的服务器在 n i c . d d n . m i l,不过只包含了有 关M I L N E T的信息)。不幸的是信息有可能是过期的或不完整的。 RFC954 [Harrenstein, Stahl,

365

第30章 其他的TCP/IP应用程序使用

下载 and Feinler 1985] 说明了Whois服务。

从协议的角度来看, W h o i s服务器有一个知名的 T C P端口4 3。它接受客户的连接请求,客 户向服务器发送一个在线的查询。服务器响应以任何可用的信息,然后关闭连接。请求和应 答都以NVT ASCII来传输。除了请求和应答所包含的信息不一样, W h o i s服务器和F i n g e r服务 器几乎是一样的。 最常用的Unix客户程序是 whois(1) 程序,尽管我们可以使用 Telent自己手工键入命令。开 始的命令是只包含一个问号的请求,服务器会返回所支持的客户请求的具体信息。 当N I C在1 9 9 3年改变为 I n t e r N I C时,W h o i s服务器的站点也从 n i c . d d n . m i l移到了 rs.internic.net。很多厂商仍然装载了采用nic.ddn.mil版本的whois客户程序。为了和正确 的服务器联系上,你可能需要指明命令行参数 -h rs.internic.net。 另外,我们可以使用Telnet登录rs.internic.net站点,登录名采用whois。 我们将使用 Whois服务器来查询一下本书的作者(已经删去了无关的 Telnet客户输出)。第 一个请求是查询所有匹配“ stevens”的名字。 这是我们键入的客户命令 我们省略了其他25个“stevens”的信息

名字后面的括号中的三个大写字母跟着一个数字,(W R S 2 8),是个人的 N I句柄。下一个 查询包含一个感叹号和一个 NIC句柄,用于获得有关这个人的进一步信息。 我们键入的客户请求

很多有关Internet变量的其他信息也可以查找。例如,请求 net 140.252将返回有关 B类地址 140.252的信息。 白页 使用S M T P的V R F Y命令、 F i n g e r协议以及 W h o i s协议在 I n t e r n e t上查找用户类似于使用电 话号码簿的白页查找一个人的电话号码。在目前阶段,诸如上述的工具已经广泛可用了,为 了提高这种服务的研究正在进行当中。 [Schwartz and Tsirigotis 1991] 包含了正在 Internet上试验的不同白页服务的其他信息。一 个叫作 N e t f i n d的特别工具可以通过使用 Te l e n t,以 n e t f i n d登录到 b r u n o . c s . c o l o r a d o或者 ds.internic.net站点来访问。 RFC1309 [Weider, Reynolds, and Heker 1992]提供了对OSI目录服务X.500的概述,并且比 较了它与当前的 Internet技术(Finger和Whois)的相同点和不同点。

366

使用TCP/IP详解,卷1:协议

下载

30.4 Archie、WAIS、Gopher、Veronica和WWW 前两节我们讨论的工具—F i n g e r、W h o i s和一个白页服务—是用来查找人的信息的。 还有一些工具是用来定位文件和文档的,本节中对这些工具给出了一个概述。我们只提供了 一个概述,因为对每一个工具的细节的研究超出了本书的范围。我们给出了在 I n t e r n e t上找到 这些工具的方法,鼓励你去试一试,找找看哪些工具可以帮助你。还有一些其他的工具正在 被开发。[Obraczka, Danzig, and Li1993] 概述了在Internet上的资源发现服务。 30.4.1 Archie 本书中使用的很多资源都是使用匿名 F T P得到的。问题是如何找到有我们想要的程序的 F T P站点。有时候我们甚至不知道精确的文件名,但知道几个很可能在文件名中出现的关键 字。 Archie提供了Internet上几千个FTP服务器的目录。我们可以通过登录进一个 Archie服务器, 搜索那些名字中包含了一个指定的常规表达式的文件。输出是一个与文件名匹配的 FTP服务器 的列表。然后我们可以使用匿名 FTP去那个站点取得想要的文件。 全世界有很多 A r c h i e服务器。一个比较好的开始点是使用 Te l n e t 以a r c h i e名字登录进 ds.internic.net,然后执行命令 servers。这个命令的输出提供了所有 Archie服务器以及它们的地 址的一个列表。 30.4.2 WAIS Archie帮助我们查找名字中包含关键字的文件,但有时候我们需要查找包含一个关键字的 文件或数据库。即,想查找一个内容中包含一个关键字的文件,而不是文件名字中包含关键 字。 WAIS (Wide Area Information Servers广域信息服务系统 )知道几百个包含了有关计算机主 题的和其他一般性主题信息的数据库。为了使用 WA I S,我们要选择需要查找的数据库,指明 关键字。尝试WAIS服务请使用 Telnet,以wais名字登录quake.think.com站点。 30.4.3 Gopher G o p h e r是其他 I n t e r n e t资源服务如 A r c h i e、WA I S和匿名 F T P的一个菜单驱动的前端程序。 G o p h e r是最容易使用的工具之一,因为不管它使用了哪个资源服务,它的用户界面都是一样 的。 为了尝试Gopher,请使用Telnet,以gopher名字登录is.internic.net站点。 30.4.4 Veronica 就像 A r c h i e是一个匿名 F T P服务器的索引一样, Ve r o n i c a ( Very Easy Rodent-Oriented Netwide Index to Computerized Archives)是一个Gopher标题的索引。一次 Veronica搜索一般要 查找几百个 Gopher服务器。 我们必须通过一个 G o p h e r客户来访问 Ve r o n i c a服务。选择 G o p h e r的菜单项 “B e y o n d InterNIC: Virtual Treasures of the Internet”,然后在下一个菜单中选择 Veronica。

下载

367

第30章 其他的TCP/IP应用程序使用

30.4.5 万维网WWW 万维网使用一个称为超文本的工具,使得我们可以浏览一个大的 /全球范围的服务和文档。 信息和关键字一起显示,不过关键字被突出显示 。我们可以通过选择关键字得到更多的信 息。 为了访问WWW,请使用Telnet登录info.cern.ch站点。

30.5 X窗口系统 X窗口系统(X Window System),或简称为X,是一种客户-服务器应用程序。它可以使得 多个客户(应用)使用由一个服务器管理的位映射显示器。服务器是一个软件,用来管理显 示器、键盘和鼠标。客户是一个应用程序,它与服务器在同一台主机上或者在不同的主机上。 在后一种情况下,客户与服务器之间通信的通用形式是 T C P,尽管也可以使用诸如 D E C N E T 的其他协议。在有些场合,服务器是与其他主机上客户通信的一个专门的硬件(一个 X终端)。 在另一种场合,一个独立的工作站,客户与服务器位于同一台主机,使用那台主机上的进程 间通信机制进行通信,而根本不涉及任何网络操作。在这两种极端情况之间,是一台既支持 同一台主机上的客户又支持不同主机上的客户的工作站。 X需要一个诸如 T C P的、可靠的、双向的流协议( X不是为不可靠协议,如 U D P,而设计 的)。客户与服务器的通信是由在连接上交换的 8 bit字节组成的。[Nye 1992] 给出了客户与服 务器在它们的TCP连接上交换的150多个报文的格式。 在一个U n i x系统中,当 X客户和X服务器在同一台主机上时,一般使用 U n i x系统的本地协 议,而不使用 T C P协议,因为这样比使用 T C P的情况减少了协议处理时间。 U n i x系统的本地 协议是同一台主机上的客户和服务器之间可以使用的一种进程间通信的形式。回忆一下在图 2 - 4中,当使用 T C P作为同一台主机上进程间的通信方式时,在 I P层以下发生了这个数据的环 回(loopback),隐含着所有的 TCP和IP处理都发生了。 图3 0 - 1显示了三个客户使用一个显示器的可能的脚本。一个客户与服务器在同一台主机 上,使用 U n i x系统的本地协议。另外两个位于不同的主机上,使用 T C P。一般来说,其中一 个客户是一个窗口管理程序 (window manager) ,它有权限管理显示器上窗口的布局。例如, 窗口管理程序允许我们在屏幕上移动窗口,或者改变窗口的大小。 在这里客户和服务器这两个词猛一看含义相反了。对于 Te l n e t和F T P的应用,我们把客户 看作是在键盘和显示器上的交互式用户。但是对于 X,键盘和显示器是属于服务器的。服务器 被认为是提供服务的一方。 X提供的服务是对窗口、键盘和鼠标的访问。对于 Te l n e t,服务是 登录远程的主机。对于 FTP,服务是服务器上的文件系统。 当X终端或工作站引导时,一般启动 X服务器。服务器创建一个 T C P端点,在端口 6000 + n 上做一个被动打开,其中 n 是显示器号(一般是 0 )。大多数的 U n i x 服务器也使用名字 /tmp/.X11-unix/Xn创建一个Unix系统的插口,其中 n还是显示器的号。 当一个客户在另一台主机上启动时,它创建一个 T C P端点,对服务器上的端口 6 0 0 0 +n做 一个主动打开。每个客户都得到了一个自己与服务器的连接。服务器负责对所有的客户请求 进行复用。从这点开始,客户通过 T C P连接向服务器发送请求(例如,创建一个窗口),服务 例如通过使用不同的颜色—译者注。

368

使用TCP/IP详解,卷1:协议

下载

器返回应答,服务器也发送事件给客户(鼠标按钮按下,键盘键按下,窗口暴露,窗口大小 改变,等等)。 显示器

客户 进程

窗口 窗口

客户 进程

主机A 窗口 客户 进程

TCP连接

主机B

主机C

图30-1 使用一个显示器的三个X客户

图3 0 - 2将图3 0 - 1重新画,但强调了客户与 X服务器进程间的通信, X服务器进程轮流管理 着每个窗口。图中没有显示的是 X服务器管理键盘和鼠标。 显示器 客户 进程

主机A

窗口 窗口

窗口

客户 进程 主机B

客户 进程

管理显示 X服务器进程

主机C

图30-2 使用一个显示器的三个客户

单个服务器处理多个客户请求的这种设计与我们在 1 8 . 11节描述的正常的 T C P并发服务器 设计不同。例如,每次一个新的 T C P连接请求到达, F T P和Te l n e t服务器都会产生一个新的进 程,因此,每个客户都和一个不同的服务器进程通信。然而,对于 X,运行在同一台主机或者 在不同主机上的所有客户都和同一个服务器通信。 通过X客户和它的服务器之间的TCP连接可以交换很多数据。传输数据的数目依赖于特定的 应用程序设计。例如,如果我们运行Xclock客户,Xclock在服务器的一个窗口中显示客户机当前 的时间和日期。如果我们指定每隔1秒修改一次时间,那么每隔1秒,就会有一个X报文通过TCP 连接从客户传输到服务器。如果我们运行X终端模拟程序,Xterm,我们敲的每一个键都会变成一 个32字节的X报文(加上标准的IP和TCP首部就是72字节) ,在相反方向上的回送字符将是一个更 大的X报文。[Droms and Dyksen 1990]检查了不同的X客户与一个特定的服务器之间的TCP流量。 30.5.1 Xscope程序 Xs c o p e是检查X客户与它的服务器之间交换的信息的一个方便的程序。大多数的 X窗口实

369

第30章 其他的TCP/IP应用程序使用

下载

现都提供这个程序。它处在客户与服务器之间,双向传输所有的数据,同时解析所有的客户 请求和服务器应答。图 30-3显示了这种设置。 显示器 窗口

客户 进程

窗口

窗口

主机A

客户 进程

管理显示

主机B

TCP连接

X服务器过程 主机C

对请求和应答的描述

图30-3 使用xscope 监视一个X连接

首先,我们在服务器所在的主机上启动 x s c o p e进程,但是 x s c o p e不是在端口 6 0 0 0而是 在端口 6 0 0 1上监听 T C P的连接请求。然后我们在另一台主机上启动一个客户,指明显示器号 为1,而不是0,使得客户与xscope相连,而不直接与服务器相连。当客户的连接请求到达时, x s c o p e创建与端口 6 0 0 0上的真正的服务器的一个 T C P连接,在客户与服务器之间复制所有的 数据,同时生成请求与应答的一个可读的描述。 我们将在sun主机上启动 xscope,然后在主机svr4上运行xclock客户。 svr4

%

DISPLAY=sun:1 xclock

-digital

-update 5

这条命令在主机 s u n的一个窗口中以数字形式显示时间和日期。我们指明了一个每 5秒的更新 时间。 Thu

Sep

9

10:32:55

1993

我们对 x s c o p e指明一个 - q选项以产生最小的输出。为了看到每个报文的所有字段,可 以使用不同的冗长级别。下面的输出显示了前三个请求和应答。

客户的第 1个在时刻 0 . 0 0的报文和服务器在时刻 0 . 0 2的响应是客户与服务器之间标准的连 接建立过程。客户标识它的字节顺序以及它希望的服务器版本。服务器响应以有关自己的不

370

使用TCP/IP详解,卷1:协议

下载

同的信息。 下一个在时刻 0 . 0 3的报文包含了两个客户请求。第 1个请求在服务器上创建一个客户可以 在其中画的图形上下文。第2个请求从服务器上得到一个属性(RESOURCE_MANAGER属性)。 属性可以用于客户之间的通信,经常是在一个应用程序和窗口管理程序之间。服务器在时刻 0.20的应答包含了这个属性。 下面两个在时刻 0 . 3 0和0 . 3 8的客户报文形成了返回一个原子的单个请求(每个属性具有一 个唯一的整型标识符称为原子)。服务器在时刻 0.43的应答包含了这个原子。 如果不提供有关 X窗口系统更多的细节是不可能进一步理解这个例子的,但这又不是本节 的目的。在这个例子中,在窗口被显示之前,客户总共发送了 1668个字节组成的 12个报文段, 服务器总共发送了 11 2 0个字节组成的 1 0个报文段。耗费的时间为 3 . 1 7秒。从这以后客户每 5秒 发送一个平均44个字节的小请求,请求更新窗口。这样一直持续到客户被终止。 30.5.2 LBX:低带宽X 为了将X用于局域网,对 X协议使用的编码进行了优化,因为在局域网中花在对数据进行 编码和解码的时间比最小化传输的数据更重要。尽管这种推断对以太网是适用的,但对于低 速的串行线,如 SLIP和PPP链路,就存在问题了( 2.4节和2.6节)。 定义一个称为低带宽 X(LBX)的标准的工作正在进行当中,它使用了下面的技术来减少 网络流量的数目:快速缓存、只发送与前面分组的不同部分以及压缩技术。标准的规范和在 第6版的X窗口系统中的一个样本实现应该会在 1994年的早些时候完成。

30.6 小结 我们介绍的前两个应用, F i n g e r和W h o i s,是用来获得用户信息的。 F i n g e r客户查询一个 服务器,经常是为了找到某个人的登录名(以便给他们发电子邮件),或者去看一下某个人是 否登录了。 W h o i s客户一般与 I n t e r N I C运行的服务器联系,查找关于一个人、机构、域或网络 号的信息。 我们简单描述了其他一些 I n t e r n e t资源发现服务: A r c h i e、WA I S、G o p h e r、Ve r o n i c a和 WWW, 帮助我们在 Internet上定位文件和文档。还有一些资源发现工具正在被开发。 本章的最后简单浏览了另一个 T C P / I P的重要客户程序, X窗口系统。我们看到 X服务器管 理一个显示器上的多个窗口,处理客户与其窗口的通信。每个客户都有它自己的与服务器的 T C P连接,一个单个的服务器为一个给定的显示器管理着所有的客户。通过 X s c o p e程序,我 们看到怎样把一个程序放在一个客户与服务器之间,输出有关两者之间交换的报文的信息。

习题 30.1 试用Whois找到网络号为88的A类网络的拥有者。 30.2 试用W h o i s找到管理 w h i t e h o u s e . g o v域的D N S服务器。这个应答与 D N S给出的答案 相匹配吗? 30.3 在图30-3中,你认为 xscope进程必须和 X服务器运行在同一台主机 上吗?

下载

附录A tcpdump程序 tcpdump程序是由Van Jacobson、Craig Leres和Steven McCanne编写的,他们都来自加利 福尼亚大学伯克利分校的劳伦斯伯克利实验室。本书中使用的是 2.2.1版(1992年6月)。 t c p d u m p通过将网络接口卡设置为混杂模式( promiscuous mode)来截获经过网络接口 的每一个分组。正常情况下,用于诸如以太网媒体的接口卡只截获送往特定接口地址或广播 地址的链路层的帧( 2.2节)。 底层的操作系统必须允许将一个接口设置成混杂模式,并且允许一个用户进程截获帧。 下列的操作系统可以支持 t c p d u m p,或者可以加入对 t c p d u m p的支持: 4 . 4 B S D、B S D / 3 8 6、 S u n O S、U l t r i x和H P - U X。参考一下随着 t c p d u m p发布的R E A D M E文件,了解它支持哪些操 作系统以及哪些版本。 除了tcpdump还有其他一些选择。在图 10-8中,我们使用了 Solaris 2.2的程序snoop来查 看一些分组。AIX 3.2.2提供了iptrace程序,该程序也提供了类似的功能。

A.1 BSD 分组过滤器 当前由 B S D 演变而来的 U n i x内核提供了 BSD 分组过滤器 BPF (BSD Packet Filter) , t c p d u m p用它来截获和过滤来自一个被置为混杂模式的网络接口卡的分组。 B P F也可以工作 在点对点的链路上,如 S L I P(2 . 4节),不需要什么特别的处理就可以截获所有通过接口的分 组。BPF还可以工作在环回接口上( 2.7节)。 BPF有一个很长的历史。1980年卡耐基梅隆大学的Mike Accetta和Rick Rashid创造 了Enet分组过滤程序。斯坦福的Jeffrey Mogul将代码移植到BSD,从1983年开始继续开 发。从那以后,它演变为DEC的Ultrix分组过滤器、SunOS 4.1下的一个STREAMS NIT 模块和BPF。劳伦斯伯克利实验室的Steven McCanne在1990年的夏天实现了BPF。其中 很多设计来自于Van Jacobson。[McCanne and Jacobson 1993] 给出了最新版本的细节以 及与Sun的NIT的一个比较。 图A-1显示了用于以太网的 BPF的特征。 BPF将以太网设备驱动程序设置为混杂模式,然后从驱动程序那里接收每一个收到的分组 和传输的分组。这些分组要通过一个用户指明的过滤器,使得只有那些用户进程感兴趣的分 组才会传递给用户进程。 多个进程可以同时监视一个接口,每个进程指明了一个自己的过滤器。图

A - 1显示了

t c p d u m p的两个实例进程和一个 RARP守护进程(5.4节)监视同样的以太网接口。 t c p d u m p 的每个实例指明了一个自己的过滤器。 t c p d u m p的过滤器可以由用户在命令行指明,而 rarpd总是使用只截获 RARP请求的过滤器。 除了指明一个过滤器, BPF的每个用户还指明了一个超时定时器的值。因为网络的数据传 输率可以很容易地超过 C P U的处理能力,而且一个用户进程从内核中只读小块数据的代价昂

372

使用TCP/IP详解,卷1:协议

下载

贵,因此, B P F试图将多个帧装载进一个读缓存,只有缓存满了或者用户指明的超时到期才 将读缓存保存的帧返回。 t c p d u m p将超时定时器置为 1秒,因为它一般从 B P F收到很多数据。 而RARP守护进程收到的帧很少,所以 rarpd将超时置为 0(收到一个帧就返回)。 用户进程

用户进程

用户进程

内核

接收分组的一个复制 过滤器过滤器 过滤器

以太网设备 驱动程序

BPF驱动程序 发送分组的一个复制

图A-1 BSD分组过滤器

用户指明的过滤器告诉 BPF用户进程对什么帧感兴趣,过滤器是对一个假想机器的一组指 令。这些指令被内核中的 B P F过滤器解释。在内核中过滤,而不在用户进程中,减少了必须 从内核传递到用户进程的数据量。RARP守护进程总是使用绑定在程序里的、同样的过滤程序。 另一方面, t c p d u m p在每次运行时,让用户在命令行指明一个过滤表达式。 t c p d u m p将用户 指明的表达式转换为相应的 BPF的指令序列。tcpdump表达式的例子如下: % tcpdump tcp port 25 % tcpdump icmp [0] != 8 and icmp[0] != 0

第一个只打印源端口和目的端口为 2 5的T C P报文段。第二个只打印不是回送请求和回送 应答的 I C M P报文(也就是非 p i n g的分组)。这个表达式指明了 I C M P报文的第一个字节,图 6-2中的type字段,不等于 8或0,即图6-3中的回送请求和回送应答。正像你所看到的,设计过 滤器需要有底层分组结构的知识。第二个例子中的表达式被放在一对单引号中,防止 U n i x外 壳程序解释特殊字符。 参考t c p d u m p (1) 的手册,了解用户可以指明的表达式的全部细节。 bpf(4) 的手册详细 描述了B P F使用的假想机器指令。 [McCanne and Jacobson 1993] 比较了这个假想机器方法与 其他方法的设计与性能。

A.2 SunOS的网络接口分接头 SunOS 4.1.x提供了一个S T R E A M S伪设备驱动程序 (pseudo-device driver),称为网络接口 分接头(Network Interface Tap)或者NIT([Rago 1993] 包含了流设备驱动程序的其他细节。我

373

附录A tcpdump程序使用

下载

们把这种特征叫作“流”)。N I T类似于 B S D分组过滤器,但不如后者功能强大和效率高。图 A-2显示了使用 NIT所用到的流模块。这个图与图 A-1之间的一个不同点在于 BPF可以截获网络 接口收到的和传送的分组,而 NIT只能截获接口收到的分组。将 t c p d u m p与NIT结合起来意味 着我们只能看见由网络中其他主机发送来的分组—即根本不可能看见我们自己主机发送的 分组(尽管 B P F可以工作在 SunOS 4.1.x上,但它需要对以太网设备驱动程序的源代码进行改 变,大多数的用户没有权限访问源代码,因而这是不可能的)。 当设备/dev/nit被打开时,流驱动程序nit_if就会被打开。既然NIT是使用流来构造的,处 理模块可以放在nit_if驱动程序之上。tcpdump将模块nit_buf放在STREAM之上。这个模块将 多个网络帧聚集在一个读缓存中,允许用户进程指明一个超时的值。这种情况类似于我们在BPF中 所描述的。RARP守护进程没有把这个模块放在它的流之上,因为它只处理了一小部分分组。 用户进程 用户进程 过滤器

内核 流模块

流模块

流驱动程序

接收分组的复制

以太网设备 驱动程序

图A-2 SunOS的网络接口分接头

用户指明的过滤由流模块 n i t _ p f处理。在图 A - 2中,我们注意到这个模块被 R A R P守护 进程所用,但没有被 t c p d u m p使用。在 S u n O S操作系统中, t c p d u m p代之以在用户进程中完 成自己的过滤操作。这么做的理由是 n i t _ p f使用的假想机器的指令与 B P F所支持的指令不同 (不如 B P F所支持的功能强大)。这就意味着当用户对 t c p d u m p指明了一个过滤表达式时,与 BPF相比较,使用NIT就会有更多的数据在内核与用户进程之间交换。

A.3 SVR4数据链路提供者接口 SVR4支持数据链路提供者接口 DLPI (Data Link Provider Interface),它是OSI数据链路服 务定义的一个流实现。SVR4的大多数版本支持第1版的 DLPI,SVR4.2同时支持第 1版和第2版, Sun的Solaris 2.x支持第2版,但是增强了一些功能。 像t c p d u m p的网络监视程序必须使用 D L P I来直接访问数据链路设备驱动程序。在 S o l a r i s 2.x中,分组过滤的流模块被改名为 pfmod,缓存模块被改名为 bufmod。

374

使用TCP/IP详解,卷1:协议

下载

尽管Solaris 2.x还很新, t c p d u m p在其上的一个实现有一天也会出现。 S u n还提供了一个 叫作 s n o o p 的程序,完成的功能类似于

t c p d u m p ( s n o o p 代替了 SunOS 4.x 的程序

etherfind)。作者还不知道 tcpdump到vanilla SVR4上的任何端口实现。

A.4 tcpdump的输出 tcpdump的输出是“原始的”。在本书中包含它的输出时,我们对它进行了修改以便阅读。 首先,它总是输出它正在监听的网络接口的名字。我们把这一行给删去了。 其次,t c p d u m p输出的时间戳在一个微秒精度的系统中采用如同 09:11:22.642008的格式, 在一个 1 0 m s时钟精度的系统中则如同 0 9 : 11 : 2 2 . 6 4一样(在附录 B中,我们更多地讨论了计算 机时钟的精度)。在任何一种情况下, H H : M M : S S的格式都不是我们想要的。我们感兴趣的是 每个分组与开始监听的相对时间以及与下面分组的时间差。我们修改了输出以显示这两个时 间差。第1个差值在微秒精度的系统中打印到十进制小数点后面 6位(对于只有 10 ms精度的系 统打印到小数点后面 2位),第2个差值打印到十进制小数点后面 4位或2位(依赖于时钟精度)。 本书中大多数tcpdump的输出都是在sun主机上收集的,它提供了微秒精度。一些 输出来自于运行0.9.4版BSD/386操作系统的主机bsdi,它只提供了10 ms的精度(如图51所示) 。一些输出收集于当bsdi主机运行1.0版BSD/386时,后者提供了微秒级的精度。 tcpdump总是打印发送主机的名字,接着一个大于号,然后是目的主机的名字。这样显示 很难追踪两个主机之间的分组流。尽管 tcpdump输出仍然显示了数据流的方向,但我们经常把 这条输出删掉,代替以产生一条时间线(在本书中的第一次出现是在图 6-11)。在我们的时间线 上,一个主机在左边,另一个在右边。这样很容易看出哪一边发送分组,哪一边接收分组。 我们给t c p d u m p的每条输出增加了行号,使得我们可以在书中引用特定的行。还在某些 行之间增加了额外的空白,以区别一些不同的分组交换。 最后,tcpdump的输出可能会超出一页的宽度。我们在太长行的适当地方进行了换行。 作为一个例子,相应于图 4 - 4的t c p d u m p的原始输出显示在图 A - 3中。这里假设了一个 8 0 列的终端窗口。 没有显示我们键入的中断键(用于中止 t c p d u m p),也没有显示接收到的和漏掉的分组的 个数(漏掉的分组是那些到达得太快, t c p d u m p来不及处理的分组。因为本文中的例子经常 运行在另外一个空闲网络上,所以漏掉的分组个数总是 0)。

我们没有显示其他4个分组 键入中断字符来中断显示

图A-3 图4-4的tcpdump 的输出

375

附录A tcpdump程序使用

下载 A.5 安全性考虑

很明显,截获网络中传输的数据流使我们可以看到很多不应该看到的东西。例如, T e l n e t和F T P用户输入的口令在网络中传输的内容和用户输入的一样(与口令的加密表示相 比,这称为口令的明文表示。在 U n i x口令文件中,一般是 / e t c / p a s s w d或/ e t c / s h a d o w, 存储的是加密的表示)。然而,很多时候一个网络管理员需要使用一个类似于 t c p d u m p的工 具来分析网络中出现的问题。 我们是把t c p d u m p作为一个学习的工具,用来查看网络中实际传输的东西。对 t c p d u m p 以及其他厂商提供的类似工具的访问权限依赖于具体系统。例如,在 S u n O S,对N I T设备的 访问只限于超级用户。 B S D的分组过滤器使用了一种不同的技术:通过对 / d e v / b p f X X设备 的授权来控制访问。一般来说,只有属主才能读写这些设备(属主应该是超级用户),对于同 组用户是可读的(经常是系统管理组)。这就是说如果系统管理员不对程序设置用户的 I D,一 般的用户是不能运行类似于 tcpdump的程序的。

A.6 插口排错选项 查看一个 T C P连接上发生的事情的另一种方法是使能插口排错选项,当然是在支持这一 特征的系统中。这个特征只能工作在 T C P上(其他协议都不行),并且需要应用程序支持(当 应用程序启动时,使能一个插口排错选项)。 大多数伯克利演变的实现都支持这个特征,包括SunOS、4.4BSD和SVR4。 程序使能了一个插口选项,内核就会保留在那个连接上发生的事情的一个痕迹记录。在这之 后,所有记录的信息都可以使用 t r p t ( 8 )程序打印出来。使能一个插口排错选项不需要特别 的许可,但是因为 trpt程序访问了内核的内存,所以运行 trpt需要特别的权限。 s o c k程序(附录C)的- D选项支持这个特征,但是输出的信息比相应的 t c p d u m p的输出 更难解析和理解。然而,我们在 2 1 . 4节确实使用它查看了 T C P连接上t c p d u m p不能访问的内 核变量。

下载

附录B 计算机时钟 既然本书中的大多数的例子都需要测量一个时间间隔,我们需要更仔细地介绍一下当前 U n i x系统所采用的记录时间的方法。下面的描述适用于本书中例子所使用的系统,也适用于 大多数的Unix系统。[Leffler et al. 1989]的3.4节和3.5节给出了另外的细节。 硬件按照一定的频率产生一个时钟中断。对于 Sun SPARC和Intel 80386,时钟中断每10 ms 产生一次。 应该注意到大多数的计算机使用一种无补偿的晶体振荡器来生成这些时钟中断。正如 RFC1305 [Mills 1992] 的表7指出的,你不要想知道这种振荡器一天的偏差有多少。这就意味着 几乎没有计算机能维持精确的时间(即,中断并不是精确地每 10 ms发生一次)。一个0.01%的 误差就会产生一个每天 8 . 6 4秒的差错。为了得到更好的时间测量需要:(1)一个更好的振荡 器;(2)一个外部的更精确的时间资源(如,全球定位卫星提供的时间资源);或者( 3)通 过因特网访问一个具有更精确时钟的系统。后者通过 RFC1305定义的网络时间协议实现,对该 协议的描述超出了本书的范围。 U n i x系统中引起时间差错的另一个公共的原因是 10 ms 的中断只是引起内核给一个记 录时间的变量增 1。如果内核丢失了一个中断(也就是说两个连续中断之间间隔

10 ms 对

于内核来说太快了),时钟将失去 10 ms 。丢失这种类型的中断经常引起 U n i x系统丢失时 间。 尽管时间中断近似于每 10 ms到达一次,更新的系统,如 SPARC,提供了一个更高精度的 定时器来测量时间差异。通过 NIT驱动程序,t c p d u m p(在附录A中描述)已经访问了这个更 高精度的定时器。在 S PA R C上,这个定时器提供





了微秒级的精度。用户进程也可以通过

次数 4 914 4 831

g e t t i m e o f d a y( 2 )函数来访问这个更高精度的定 时器。 作者做了下面的试验。我们运行了一个程序,这

其他

个程序在一个循环里调用了10 000次gettimeofday 函数,并将每次的返回值保存在一个数组中。在循环 结束后,打印了9999个时间差。对于一个SPARC ELC,

图B-1 在SPARC ELC上调用gettimeofday 函数10 000次所需要的时间分布

时间差的分布如图B-1所示。 在一个空闲的系统中,运行这个程序所花的时钟时间为 0 . 3 8秒。根据这一点,我们可以 说进程调用 g e t t i m e o f d a y所花的时间大约 3 7微秒。既然 E L C的速度是 2 1 M I P S(M I P S表 示每秒1 0 0万指令),3 7微秒相应于大约 8 0 0个指令。这些指令对于内核处理一个用户进程的 调用、执行系统调用、复制 8个字节的结果及返回给用户进程看起来是合理的( M I P S速度是 不可靠的,很难测量当前系统的指令时间。我们试图做的只是得到一个粗略的估计来评价一 下上面的值是否有意义)。 从这个简单的试验,我们可以说 gettimeofday返回的值确实包含了微秒级的精度。

377

附录B 计算机时钟使用

下载 如果我们在 S V R 4 / 3 8 6上进行类似的测试,结 果是不同的。这是因为很多 386 Unix 系统,如 S V R 4,只计数 10 ms 的时钟中断,而没有提供更 高的精度。图 B - 2是运行在 25 MHz 80386 上的 SVR4中9999个时间差的分布。 这些值是无意义的,因为时间差一般小于 1 0





次 数

0 10 000

9 871 128

图B-2 在SVR4/386上调用gettimeofday函数 10000次所需要的时间

m s,都被认为是 0了。在这些系统中,我们所能做的就是在一个空闲的系统上测量时钟时间, 除以循环的次数。这个结果提供了一个上界,因为它包含了调用 p r i n t f函数9999次的时间和 将结果写入一个文件的时间(在 SPARC的情况,图 B - 1,时间差没有包括 p r i n t f的时间,因 为所有10 000个值都是首先获得的,然后才打印结果)。在SVR4的时钟时间为3.15秒,每个系 统调用消耗了315微秒。这个大约比 SPARC慢8.5倍的系统调用时间看来是正确的。 BSD/386 1.0版提供了类似于 SPARC的微秒级的精度。它读 8253时钟寄存器,计算从上次 时钟中断以来的微秒次数。调用 g e t t i m e o f d a y的进程和内核模块,如 BSD 分组过滤器,可 以使用这个精度。 和t c p d u m p联系起来,这些数字意味着我们可以相信在 SPARC和B S D / 3 8 6系统上打印的 毫秒和亚毫秒 (submillisecond)的值。而在SVR4/386上,t c p d u m p打印的值总是10 ms的倍数。 对于其他打印往返时间的程序,如 p i n g(第 7章)和 t r a c e r o u t e(第8章),在S PA R C和 B S D / 3 8 6系统上,我们可以相信它们输出的毫秒值,但在 S V R 4 / 3 8 6上,打印的值总是 1 0的倍 数。为了在 L A N上测量某个 p i n g的时间,在第 7章中我们显示的时间是 3 ms ,所以需要在 SPARC和BSD/386系统上运行 ping程序。 本书中的一些例子是运行在 BSD/386 0.9.4版上,它类似于 SVR4,只提供了10 ms 的时钟精度。在我们显示这个系统的 tcpdump输出时,只显示到小数点后面两位,因 为这就是所提供的精度。

下载

附录C sock程序 在本书中一直使用一个称为 s o c k的小测试程序,用来生成 T C P和U D P数据。它既可以用 作一个客户进程,也可以用作一个服务器进程。有这样一个可以从外壳程序执行的测试程序, 使我们避免了为每一个我们想要研究的特征编写新的客户和服务器 C程序。因为本书的目的 是了解网络互联协议,而不是网络编程,所以在这个附录中我们只描述这个程序和它不同的 选项。 有很多与sock功能类似的程序。Juergen Nickelsen写了一个称为socket的程序, Dave Yost写了一个称为sockio的程序。两者都包含了很多类似的特征。 sock程序的 某些部分也受到了Mike Muuss和Terry Slattery所写的公开域ttcp程序的启发。 sock程序运行在以下四种模式之一: 1) 交互式客户:默认模式。程序和一

TCP连接

标准输入

服务器

标准输出

个服务器相连,然后将标准输入的数据传 图C-1 sock 程序作为交互式客户的默认操作

给服务器,再将从服务器那里接收到的数 据复制到标准输出。如图 C-1所示。

我们必须指明服务器主机的名字和想要连接的服务的名字。主机可指明为点分十进制数, 服务可指明为一个整数的端口号。从 s u n到b s d i与标准的 e c h o服务器( 1.12节)相连,回显 我们键入的每一个字符: sun

% sock bsdi a test line a test line ^D

echo 我们键入这一行 e c h o服务器返回一个复制行 键入文件结束符来中止

2) 交互式服务器:指明- s选项。需要指明服务名字(或端口号): sun

% sock

-s

作为一个在端口 5 5 5 5监听的服务器

5555

程序等待一个客户的连接请求,然后将标准输入复制给客户,将从客户接收到的东西复 制到标准输出。在命令行中,端口号之前可以有一个因特网地址,用来指明接收哪一个本地 接口上的连接: sun

% sock

-s

140.252.13.33

只接受来自以太网的连接

5555

默认的模式是接受任何一个本地接口上的连接请求。 3) 源客户:指明- i选项。在默认情况下,将一个 1 0 2 4字节的缓存写到网络中,写 1 0 2 4 次。- n选项和- w选项可以改变默认值。例如, sun

% sock

-i

-n12

-w4096

bsdi

discard

把12个缓存,每个包含 4096字节的数据,送给主机 bsdi上的discard服务器。 4) 接收器服务器:指明- i选项和- s选项。从网络中读数据然后扔掉。 这些例子都使用了 TCP(默认情况),-u选项指明使用UDP。 s o c k程序有许多选项,用于对程序的运行提供更好的控制。我们需要使用这些选项来产

379

附录C sock程序使用

下载 生本书中用到的所有测试条件。 -b

n

将n绑定为客户的本地端口号(在默认情况下,系统给客户分配一个临 时的端口号)。

-c

将从标准输入读入的新行字符转换为一个回车符和一个换行符。类似 地,当从网络中读数据时,将〈回车,换行〉序列转换为新行字符。 很多因特网应用需要 NVT ASCII(2 6 . 4节),它使用回车和换行来终止 每一行。

-f

a.b.c.d.p 为一个UDP端点指明远端的 IP地址(a.b.c.d)和远端的端口号( p)。

-h

实现T C P的半关闭机制( 1 8 . 5节)。即,当在标准输入中读到一个文件 结束符时并不终止。而是在 T C P连接上发送一个半关闭报文,继续从 网络中读报文直到对方关闭连接。

-i

源客户或接收器服务器。向网络写数据(默认),或者如果和 -s 选项一 起用,从网络读数据。 - n选项可以指明写(或读)的缓存的数目, - w 选项可以指明每次写的大小, -r 选项可以指明每次读的大小。

-n

n

当和-i 选项一起使用时, n指明了读或写的缓存的数目。 n的默认值是 1024。

-p

n

指明每个读或写之间暂停的秒数。这个选项可以和源客户(- i)或接 收器服务器( - i s)一起使用作为每次对网络读写时的延迟。参考- P选 项,实现在第1次读或写之前暂停。

-q

n

为TCP服务器指明挂起的连接队列的大小: TCP将为之进行排队的、已 经接受的连接的数目(图 18-23)。默认值是 5。

-r

n

和- i s选项一起使用, n指明每次从网络中读数据的大小。默认是每次读 1024字节。

-s

作为一个服务器,而不是一个客户。

-u

使用UDP,而不是TCP。

-v

详细模式。在标准差错上打印附加的细节信息(如客户和服务器的临 时端口号)。

-w

n

和- i选项一起使用, n指明每次从网络中写数据的大小。默认值是每次 写1024字节。

-A

使能 S O _ R E U S E A D D R插口选项。对于 T C P,这个选项允许进程给自 己分配一个处于 2 M S L等待的连接的端口号。对于 U D P,这个选项支持 多播,它允许多个进程使用同一个本地端口来接收广播或多播的数据 报。

-B

使能S O _ B R O A D C A S T插口选项,允许向一个广播 I P地址发送 U D P数 据报。

-D

使能S O _ D E B U G插口选项。这个选项使得内核为这个 T C P连接维护另 外的调试信息( A.6节)。以后可以运行 trpt(8)程序输出这个信息。

-E

如果实现支持,使能 I P _ R E C V D S TA D D R插口选项。这个选项用于

380

使用TCP/IP详解 卷1:协议

下载

UDP服务器,用来打印接收到的 UDP数据报的目的IP地址。 -F

指明一个并发的 T C P服务器。即,服务器使用 f o r k函数为每一个客户连 接创建一个新的进程。

-K -L

使能TCP的SO_KEEPALIVE插口选项(第23章)。 n

把一个 T C P端点的拖延时间 (linger time)(S O _ L I N G E R)设置为 n。一 个为0的拖延时间意味着当网络连接关闭时,正在排队等着发送的任何 数据都被丢弃,向对方发送一个重置报文( 1 8 . 7节)。一个正的拖延时 间(百分之一秒)是关闭网络连接必须等待的将所有正在排队等着发 送的数据发送完并收到确认的时间。关闭网络连接时,如果这个拖延 定时器超时,挂起的数据没有全部发送完并收到确认,关闭操作将返 回一个差错信息。

-N

设置TCP_NODELAY插口选项来禁止Nagle算法(19.4节)。

-O

n

指明一个TCP服务器在接受第一个客户连接之前暂停的秒数。

-P

n

指明在第一次对网络进行读或写之前暂停的秒数。这个选项可以和接 收器服务器(- i s)一起使用,完成在接受了客户的连接请求之后但在 执行从网络中第一次读之前的延迟。和接收源(- i)一起使用时,完 成连接建立之后但第一次向网络写之前的延迟。参看- p选项,实现在 接下来的每一次读或写之间进行暂停。

-Q

n

指明当一个 T C P客户或服务器收到了另一端发来的一个文件结束符, 在它关闭自己这一端的连接之前需要暂停的秒数。

-R

n

把插口的接收缓存( S O _ R C V B U F插口选项)设置为 n。这可以直接影 响T C P通告的接收窗口的大小。对于 U D P,这个选项指明了可以接收 的最大的UDP数据报。

-S

n

把插口的发送缓存( S O _ S N D B U F插口选项)设置为 n。对于U D P,这 个选项指明了可以发送的最大的 UDP数据报。

-U

n

在向网络写了数字 n后进入T C P的紧急模式。写一个字节的数据以启动 紧急模式( 20.8节)。

下载

附录D 部分习题的解答 第1章 1.1 答案是: 27-2(1 2 6)+214-2(16 382)+221-2(2 097 150)= 2 113 658。每一部分都 减去2是因为全0或全1网络ID是非法的。 1.2 图D-1显示了直到 1993年8月的有关数据。

网络总数

图D-1 宣布加入NSFNET的网络数

如果网络数继续呈指数增长的话,虚线估计了 2000年可能达到的最大的网络数。 1.3 “自由地接收,保守地发送。”

第3章 3.1 不,任何网络ID为127的A类地址都是可行的,尽管大多数系统使用了 127.0.0.1。 3.2 k p n o有5个接口: 3个点对点链路和 2 个以太网接口。 R 1 0有4个以太网接口。 g a t e w a y 有3个接口: 2个点对点链路和 1 个以太网接口。最后, n e t b有1个以太网接口和 2个点对 点链路。 3.3 没有区别:作为一个没有再区分子网的 C类地址,它们都有一个 2 5 5 . 2 5 5 . 2 5 5 . 0的子网掩 码。 3.5 它是合法的,被称为非连续的子网掩码,因为其用于子网掩码的 1 6位是不连续的。但是 RFC建议反对使用非连续的子网掩码。 3.6 这是一个历史遗留问题。值是 1 0 2 4+5 1 2,但是打印的 M T U值包含了所有需要的首部字 节数。Solaris 2.2将回环接口的 MTU设置为8232(8192+40),其中包含了 8192字节的用 户数据加上 20字节的IP首部和20字节的TCP首部。 3.7 第一,数据报降低了路由器中对于连接状态的需求。第二,数据报提供了基本的构件, 在它的上面可以构造不可靠的( U D P)和可靠的( T C P)的运输层。第三,数据报代表

382

使用TCP/IP详解,卷1:协议

下载

了最小的网络层假定,使得可以使用很大范围的数据链路层服务。

第4章 4.1 发出一条r s h命令与另一台主机建立一个 TCP连接。这样做引起在两个主机之间交换 IP数 据报。为此,在那台主机的 A R P缓存中必须有我们这台主机的登记项。因此,即使在执 行r s h命令之前,ARP缓存是空的,当 r s h服务器执行 a r p命令时,必须保证 ARP缓存中 登记有我们这台主机。 4.2 保证你的主机上的 ARP缓存中没有登记以太网上的某个叫作 f o o的主机。保证f o o引导时 发送一个免费 A R P请求,也许是在 f o o引导时,在那台主机上运行 t c p d u m p。然后关闭 主机foo,使用说明了 t e m p选项的a r p命令,在你的系统的 ARP缓存中为 f o o输入一个不 正确的登记项。引导 f o o并在它启动好之后,察看主机的 A R P缓存,看看不正确的登记 项是不是已经被更正了。 4.3 阅读主机需求( Host Requirement)RFC的2.3.2.2节和本书中的11.9节。 4.4 假设当服务器关闭时,客户机保存了关于服务器的一个完整的 ARP登记项。如果我们继续 试图与(已关闭的)服务器联系,过了 20分钟以后,ARP将超时。最后,当服务器以另一 个新的硬件地址重启动。如果它没有发出一个免费 ARP,旧的、不再正确的 ARP登记项仍 然存在于客户机上。我们将无法和在新硬件地址上的服务器联系直到我们手工删除这个 ARP登记项,或者在20分钟内停止与服务器联系的尝试。

第5章 5.1 一个单独的帧类型并不是必需的,因为图 4-3中的op字段对于所有的四个操作(ARP请求、 A R P应答、 R A R P请求和 R A R P应答)都有一个不同的值。但是实现一个 R A R P服务器, 独立于内核中的 ARP服务器,更容易处理不同的帧类型字段。 5.2 每个RARP服务器在发送一个响应之前可以延迟一个小的随机时间。 作为一个优化,可以指定一个 RARP服务器为主服务器,其他的为次服务器。主服务器发 出响应不需要延迟,而次服务器发出响应则需要一个随机的延迟。 作为另一个优化,也是指定一个主 R A R P服务器,其他为次服务器。次服务器只对在一 个短时间段内发生的重复请求进行响应。这里假设出现重复请求的原因是由于主服务 器停机了。

第6章 6.1 如果在局域网线上有一百个主机,每个都可能在同一时刻发送一个 I C M P端口不可达的报 文。很多报文的传输都可能发生冲突(如果使用的是以太网),这将导致 1秒或2秒的时间 里网络不可用。 6.2 它是一个“ should”。 6.3 如我们在图 3 - 2所指出的,发送一个 I C M P差错总是将 TO S置为0。发送一个 I C M P查询请 求可以将TOS置为任何值,但是发送相应的应答必须将 TOS置为相同的值。 6.4 netstat-s是查看每个协议统计数据的常用方法。在一台收到了4800万个IP数据报的SunOS 4.1.1主机(gemini)上,ICMP的统计为:

下载

383

附录D 部分习题的解答使用

21个类型为10的报文是SunOS 4.1.1不支持的路由器的请求。 也可以使用 S N M P (图 2 5 - 2 6),有些系统,如 Solaris 2.2 ,可以生成使用 S N M P变量名的 netstat-s的输出。

第7章 7.2 8 6字节除以 9 6 0字节/秒,乘以 2得到179.2 ms 。当以这个速率运行 p i n g时,打印的值为 180 ms。 7.3 (8 6+4 8)除以9 6 0字节/秒,乘以 2得到279.2 ms。另外的 4 8字节是因为5 6字节的数据部 分的最后48字节必须忽略: 0xc0是SLIP END字符。 7.4 CSLIP只压缩了TCP报文段的TCP首部和IP首部。它对 ping使用的ICMP报文没有作用。 7.5 在一个SPARC工作站 ELC上,对回环地址的 p i n g操作产生一个1.310 ms的RTT,而对一 个主机的以太网地址的 p i n g操作产生一个 1.460 ms的RT T。这个差值是由于以太网驱动 程序需要时间来判定这个数据报的目的地址是一个本地的主机。需要一个产生微秒级输 出的ping来验证这一点。

第8章 8.1 如果一个输入数据报的 T T L为0,做减一操作然后测试会将把 T T L设置为 2 5 5,并且让数 据报继续传输。尽管一个路由器永远不会收到一个 T T L为0的数据报,但这种情况确实会 发生。 8.2 我们注意到 t r a c e r o u t e在U D P数据报的数据部分存储了 1 2个字节,其中包含了数据报 发送的时间。然而,从图 6-9可以看出 ICMP只返回了出错的 IP数据报的头8个字节,实际 上这是8个字节的 U D P首部。因此, I C M P的差错报文没有返回 t r a c e r o u t e存储的时间 值。traceroute保存了它发送分组的时间,当收到一个 ICMP应答时,取出当时的时间, 把两个值相减就可以得出 RTT。 回忆一下第 7章中, p i n g在输出的 I C M P回显请求中存储了时间,这个值被服务器回显了 回来。这样即使分组返回时失序, ping也能打印出正确的 RTT。 8.3 第1行输出是正确的,并且标识了 R 1。下一个探测分组启动时将 T T L置为2,并且这个值 被R 1减1。当R 2收到这个分组时,把 T T L从1减为0,但是错误地将它传递给了 R 3。R 3看 见进入的TTL是0就将超时的分组发送回来。这就意味着第2行输出( TTL为2)标识了R3, 而不是 R 2。第3行输出正确地标识了 R 3。这个错误所表现出来的线索就是两个连续的输

384

使用TCP/IP详解,卷1:协议

下载

出行标识了同一个路由器。 8.4 在这种情况下, TTL为1标识了R1,TTL为2标识了R2,TTL为3标识了R3,但是当 TTL为 4时,U D P数据报到达了目的地,其输入的 T T L为1。I C M P端口不可达报文生成了,但它 的T T L是1(错误地从进入的 T T L复制而来)。这个I C M P报文到了 R 3,在那儿 T T L被减1, 报文被丢弃。没有生成一个 I C M P超时报文,因为被丢弃的数据报是一个 I C M P的差错报 文(端口不可达)。类似的现象也出现在 T T L为5的探测分组,但这次输出的端口不可达 报文以T T L为2开始(进入的 T T L),这个报文被传给 R 2,在那儿被丢弃。对应于 T T L为6 探测分组的端口不可达报文被传递给 R1,在那儿被丢弃。最后,对应于 TTL为7探测分组 的端口不可达报文被送回了原地,到达时它的 T T L为1(t r a c e r o u t e认为一个 T T L为0 或1的到达I C M P报文是有问题的,因此它在 RT T后面打印了一个惊叹号)。总之, T T L为 1、2和3的行正确地标识了 R 1、R 2和R 3,接下来的三行每个都包含三个超时,再接下来 的TTL为7的行标识了目的地。 8.5 它表明这些路由器都将一个ICMP报文的输出TTL设置为255,这是共同的。从netb输入的 255的TTL值是我们想要的,而从butch输入的253的TTL值表明在butch和netb之间可能 有一个未觉察的路由器。否则,在这个点上我们应该看到一个 T T L值为2 5 4的输入报文。 类似地,我们希望看到一个值为 252而不是249的、来自 e n s s 1 4 2 . U T . w e s t n e t . n e t的 报文。这表明这些未觉察的路由器没有正确地处理向外输出的 UDP数据报,但它们都对返 回的ICMP报文正确地进行了TTL减1操作。 我们必须在查看输入的 TTL时非常小心,因为有时候一个和我们想要的不同的值可能是由 于返回的 I C M P报文采用了一条与输出 U D P数据报不同的路径。但是,在这个例子中证实 了我们所怀疑的—当使用松源路由选项时,确实存在 traceroute没有发现的未察觉的 路由器。 8.7 p i n g的客户把 I C M P回显请求报文(图 7 - 1)的标识符字段设置为它的进程 I D。I C M P回 显应答报文包含同样值的标识符字段。每个客户都要查看这个返回的标识符字段,并且 只处理那些它发送过的报文。 traceroute客户将它的 UDP源端口号设置为它的进程 ID和32768的逻辑或。因为返回的 I C M P报文总是包含产生错误的 I P数据报的前 8个字节(图 6 - 9),这8个字节包括了完整的 UDP首部,所以这个源端口号在 ICMP差错报文中被返回。 8.8 p i n g客户将 I C M P回显请求报文的可选数据部分设置为分组发送的时间。这个可选的数 据必须在 ICMP回显应答中返回。这样使得即使分组返回时失序,客户也能计算出精确的 回环时间。 traceroute客户不能这样操作,因为在 ICMP差错报文中返回的只是 UDP首部(图6-9), 没有U D P数据。因此, t r a c e r o u t e必须记住它发送一个请求的时间,等待应答,然后 计算两者的时间差。 这里显示了 p i n g和t r a c e r o u t e的另一个不同点: p i n g每秒发送一个分组,而不管是 否收到任何应答; t r a c e r o u t e发送一个请求,然后在发送下一个请求前等待一个应答 或者一个超时。 8.9 因为默认情况下 Solaris 2.2从3 2 7 6 8开始使用临时的 U D P端口,所以目的主机上的目的端 口已经被使用的机会更大。

下载

385

附录D 部分习题的解答使用

第9章 9.1 当I C M P标准第1次发布时, RFC 792 [Postel 1981b]所述的划分子网技术还没有使用。另 外,使用一个网络重定向而不是 N个主机重定向(对于目的网络中的所有 N个主机)也节 省了路由表的空间。 9.2 这一项并不需要,但是如果把它删除了,所有到 s l i p的I P数据报将被发送到默认的路由 器(sun),后者又将把它们送到路由器 b s d i。既然s u n将数据报从与接收数据报相同的 接口转发出去,它把一个 I C M P重定向到 s v r 4。这样在 s v r 4中又创建了我们删除过的同 样的路由表项,尽管这一次是因为重定向而创建的,而不是在引导时增加的。 9.3 当那个 4 . 2 B S D主机收到目的地址是 1 4 0 . 1 . 2 5 5 . 2 5 5的数据报,发现它有一个通往该网络 (1 4 0 . 1)的路由,因此就试图转发数据报。它发送一个 A R P广播来寻找 1 4 0 . 1 . 2 5 5 . 2 5 5。 这个A R P请求没有收到任何应答,所以这个数据报最终被丢弃。如果在网线上有很多这 样的 4 . 2 B S D主机,每一个都在差不多同一时刻发送 A R P这个广播,将会暂时地阻塞网 络。 9.4 这次,每一个 A R P请求都收到一个应答,告诉每个 4 . 2 B S D主机向一个指定的硬件地址 (以太网广播)发送数据报。如果网线上有 k个这样的 4 . 2 B S D主机,全部收到了它们自己 的A R P 应答,使得每一个生成了另一个广播。每个主机都收到了每一个目的地址为 1 4 0 . 1 . 2 5 5 . 2 5 5的广播 I P数据报,既然现在每个主机都有一个 A R P缓存项,这个数据报又 被转发给了广播地址。这个过程继续下去,就会产生一次以太网的熔毁

(Ethernet

meltdown)。[Manber 1990] 描述了网络中另一种形式的链式反应。

第10章 10.1 路由表中有 1 3条来自于 k p n o:除了 1 4 0 . 2 5 2 . 1 0 1 . 0和1 4 0 . 2 5 2 . 1 0 4 . 0之外的所有g a t e w a y 直接相连的其他网络。 10.2 丢失的数据报中通告的 25条路由需要60秒才能得到更新。这不成问题,因为一般来说一 条路由如果连续 3分钟没有得到更新, RIP才会声明它失效。 10.3 RIP运行在UDP上,而UDP提供了UDP数据报中数据部分的一个可选的检验和( 11.3节)。 然而,O S P F运行在 I P上,I P的检验和只覆盖了 I P首部,所以 O S P F必须增加它自己的检 验和字段。 10.4 负载平衡增加了分组被失序交付的机会,并且很可能使得运输层计算的环回时间出错。 10.5 这叫作简单的分裂范围( split horizon)。 10.6 在图1 2 - 1中,我们显示了 1 0 0个主机的每一个都通过设备驱动程序、 I P层和U D P层来处 理这个广播的 U D P数据报。当它们发现 U D P端口5 2 0没有被使用时,这个广播数据报才 最终被丢弃。

第11章 11.1 因为使用 IEEE 802封装时,存在 8个额外的首部字节,所以 1 4 6 5个字节的用户数据是引 起分片的最小长度。 11.3 对于I P来说有 8 2 0 0字节的数据需要发送, 8 1 9 2字节的用户数据和 8个字节的 U D P首部。

386

使用TCP/IP详解,卷1:协议

下载

采用t c p d u m p记号,第 1个分片是 1 4 8 0 @ 0 +(1 4 8 0字节的数据,偏移为 0,将“更多片” 比特置1)。第2个是1 4 8 0 @ 1 4 8 0 +,第3个是1 4 8 0 @ 2 9 6 0 +,第4个是1 4 8 0 @ 4 4 4 0 +,第5个 是1480@5920+,第6个是800@7400。1480×5 + 800 = 8200,正好是要发送的字节。 11.4 每个1 4 8 0字节的数据报片被分成三小片:两个 5 2 8字节和一个4 2 4字节。小于 5 3 2(5 5 22 0)的8的最大倍数是 5 2 8。8 0 0字节的数据报片被分成两小片:一个 5 2 8字节和一个 2 7 2 字节。这样,原来 8192字节的数据报变成了 SLIP链路上的17个帧。 11.5 不。问题是当应用程序超时重传时,重传产生的 I P数据报有一个新的标识字段。而重新 装配只针对那些具有相同标识字段的分段。 11.6 IP首部中的标识字段( 47942)是一样的。 11.7 第一,从图 11 - 4我们看到 g e m i n i没有使能输出 U D P的检验和。如果输出 U D P的检验和 没有被使能,这个主机上的操作系统( SunOS 4.1.1 )就不会验证一个进入 U D P的检验 和。第二,大多数的 U D P通信量都是本地的,而不是 WA N的,因此没有服从所有的 WAN特征。 11.8 不严格的和严格的源站选路选项被复制到每一个数据报片中。时间戳选项和记录路由选 项没有被复制到每一个数据报片中—它们只出现在第1个数据报片中。 11.9 不。在11.12节中,我们看到很多实现可以根据目的 IP地址、源IP地址和源端口号来过滤 送往一个给定UDP端口号的输入数据报。

第12章 12.1 广播本身不会增加网络通信量,但它增加了额外的主机处理时间。如果接收主机不正确 地响应了诸如 I C M P端口不可达之类的差错,那么广播也可能导致额外的网络通信量。 路由器一般不转发广播分组,而网桥一般转发,所以在一个桥接网络上的广播分组可能 比在一个路由网络上走得更远。 12.2 每个主机都收到了所有广播分组的一个副本。接口层收到了帧,把它传递给设备驱动程 序。如果类型字段指的是其他协议,设备驱动程序就会丢弃该帧。 12.3 首先执行 n e t s t a t - r来看一下路由表,结果显示了所有接口的名字。然后对每个接口 执行i f c o n f i g(3 . 8节):标志指出了一个接口是否支持广播,如果支持,相应的广播 地址也会被输出。 12.4 伯克利演变的实现不允许对一个广播数据报进行分片。当我们说明了1472字节的长度,产 生的IP数据报将是1500字节,正好是以太网的 MTU。不允许分片一个广播数据报是一个 策略上的决定—没有技术上的原因(并不是想要减少广播分组的数目)。 12.5 依赖于 1 0 0个主机上不同的以太网接口卡的多播支持,多播数据报可能被接口卡忽略, 或者被设备驱动程序丢弃。

第13章 13.1 生成随机数时要使用对于主机唯一的值。 I P地址和链路层地址是每个主机都应该不一样 的两个值。日期时间是一个不好的选择,尤其是在所有的主机都运行了一个类似于 N T P 的协议来同步它们的时钟的情况下。 13.2 他们增加了一个包括一个序号和一个时间戳的应用协议首部。

下载

387

附录D 部分习题的解答使用

第14章 14.1 一个解析器总是一个客户,但一个名字服务器既是一个客户又是一个服务器。 14.2 问题被返回,它占用了前 4 4个字节。一个回答占用了剩下来的 3 1个字节: 2个字节指向 域名的指针(即,指向问题中域名的一个指针),10字节固定长度的字段(类型、种类、 T T L 和 资 源 长 度 ), 1 9 字 节 的 资 源 数 据 ( 一 个 域 名 )。 注 意 到 资 源 数 据 中 的 域 名 (s v r 4 . t u c . n o a o . e d u .)没有共享问题( 3 4 . 1 3 . 2 5 2 . 1 4 0 . i n - a d d r . a r p a .) 中域名的后缀,所以不能使用一个指针。 14.3 将顺序颠倒意味着首先使用 DNS,如果使用 DNS失败,然后才将参数翻转过来作为一个 点分十进制数。这就是说每次说明一个点分十进制数,都要使用 DNS,涉及一个名字服 务器。这是对资源的一种浪费。 14.4 RFC 1035的4.2.2节说明了在实际的 DNS报文之前的两个字节长度的字段。 14.5 当一个名字服务器启动时,它一般从一个磁盘文件中读出一个根服务器列表(可能已经 过时了)。然后尝试和这些根服务器中的一个联系,请求根域的名字服务器记录(一个 N S的查询类型)。这个请求返回了当前最新的根服务器列表。启动磁盘文件中根服务器 项中至少需要一个是有效的。 14.6 InterNIC的注册服务每一周更新三次根服务器。 14.7 就像应用是不定的一样,解析器也是不定的。如果系统配置成使用多个名字服务器,而 且解析器是无状态的,那么解析器就不能记住不同的名字服务器的往返时间。这样定时 太短的解析器将会超时,引起不必要的重传。 14.8 对A记录的排序应该由解析器来执行,而不是名字服务器,因为解析器一般比服务器了解 更多的客户的网络拓扑(更新版本的BIND提供了解析器对A记录排序的功能)。

第15章 15.1 送往广播地址的 T F T P请求应该被忽略。正像 Host Requirements RFC所描述的,对一个 广播请求的响应可能产生一个非常严重的安全漏洞。但是,问题是并不是所有的实现和 A P I都对接收一个 U D P数据报的进程提供了该数据报的目的地址( 11 . 1 2节)。因为这个 原因,很多 TFTP服务器没有严格遵守这个限制。 15.2 不幸的是,RFC没有提到这个块数目环绕问题。具体实现时应该能够传输最大为33 553 920 (65535×512)字节的文件。但是当文件的长度超过16 776 704(32767×512)时,很多实 现都会失败,因为它们将块数目错误地表示为一个有符号的 16位整数,而不是一个无符号 的整数。 15.3 这样简化了编写一个适合于只读内存的 T F T P客户的工作,因为服务器是引导文件的发 送者,所以服务器必须实现超时和重传机制。 15.4 利用它的停止等待协议,TFTP可以在每一次客户与服务器的往返过程中最多传输 512字节 的数据。TFTP的最大吞吐量就是512字节除以客户与服务器之间的往返时间。在以太网上, 假设一个往返时间为3 ms,那么最大的吞吐量就是大约170 000字节/秒。

第16章 16.1 一个路由器可以转发一个 R A R P请求到路由器连接的其他网络上的任何一台主机上。但

388

使用TCP/IP详解,卷1:协议

下载

是发送应答就成问题了,路由器还必须转发 RARP应答。 B O O T P没有这个应答问题,因为应答的地址是路由器知道如何转发的一般 I P地址。问题 是RARP只使用了链路层地址,路由器一般不知道在其他的、没有连接在路由器的网络上 主机的链路层地址。 16.2 它可能使用了自己的硬件地址。该地址应该是唯一的,在请求报文中设置,在应答中返 回。

第17章 17.1 除了U D P的检验和,其他都是必需的。 I P检验和只覆盖了 I P首部,而其他字段都紧接着 IP首部开始。 17.2 源IP地址、源端口号或者协议字段可能被破坏了。 17.3 很多I n t e r n e t应用使用一个回车和换行来标记每个应用记录的结束。这是 NVT ASCII 采 用的编码( 2 6 . 4节)。另外一种技术是在每个记录之前加上一个记录的字节计数, D N S (习题14.4)和Sun RPC(29.2节)采用了这种技术。 17.4 就像我们在6.5节所看到的,一个ICMP差错报文必须至少返回引起差错的 IP数据报中除了 IP首部的前8 个字节。当TCP收到一个ICMP差错报文时,它需要检查两个端口号以决定差 错对应于哪个连接。因此,端口号必须包含在TCP首部的前8个字节里。 17.5 TCP首部的最后有一些选项,但 UDP首部中没有选项。

第18章 18.1 I S N是一个 32 bit的计数器,它在系统引导大约 9 . 5小时后从 4 294 912 000环绕到 8 7 0 4。 再过大约 9 . 5小时它将环绕到 17 408,然后再过 9 . 5小时是 26 11 2,如此继续下去。因为 系统引导时 I S N从1开始,并且因为最低次序的数字在 4、8、2、6和0之间循环,所以 ISN应该总是一个奇数。 18.2 在第1种情况下,我们使用了 s o c k程序。默认情况下它把 Unix的新行字符不作改变地进 行传输 —单个A S C I I字符0 1 2(八进制)。在第2种情况下,我们使用了 Te l n e t客户,它 把U n i x的新行字符转变为两个 A S C I I字符 —一个回车符(八进制 0 1 5)跟着一个换行 符(八进制 012)。 18.3 在一个半关闭的连接上,一个端点已经发送了一个 F I N,正等待另一端的数据或者一个 FIN。一个半打开的连接是当一个端点崩溃了,而另一端还不知道的情况。 18.4 一个连接只有经过了已建立状态才能进入 2MSL等待状态。 18.5 首先,日期服务器在将时间和日期写给客户之后对 T C P连接做一个主动关闭。这可以通 过s o c k程序打印的消息:“connection closed by peer.”表现出来。连接的客户端经历了 被动关闭的状态。这样就把一对插口置于服务器端的 T I M E _ WA I T状态,而不是在客户 端。 其次,正如在18.6节所示,大多数伯克利演变的实现都允许一个新的连接请求到达一个正 处于TIME_WAIT状态的一对插口,这也就是这里所发生的情况。 18.6 因为在一个已经关闭的连接上到达了一个 FIN,所以相应于这个 FIN发送了一个复位。 18.7 拨号的一方做主动打开,电话振铃的一方做被动打开。不允许同时打开,但允许同时关

下载

389

附录D 部分习题的解答使用

闭。 18.8 我们将只看到 A R P请求,而不是 TCP SYN 报文段,但 A R P请求将和图中具有相同的计 时。 18.9 客户在主机 s o l a r i s上,服务器在主机 b s d i上。客户对服务器 S Y N的确认( A C K)和客 户的第一个数据段结合在一起(第 3行)。这种处理非常符合 T C P的规则,尽管大多数的 实现都没有这么做。接着,客户在等待它的数据的确认之前发送了它的 F I N(第4行)。 这样使得服务器可以在第 5行同时确认客户数据和它的 FIN。 这次交互(将一个报文段从客户发送到服务器)需要 7个报文段,而正常的连接建立和 终止(图18-13),以及一个数据段和它的确认,需要 9个报文段。 18.10 首先,服务器对客户的 F I N的确认一般不会被延迟(我们在 1 9 . 3节讨论延迟的确认), 而是在F I N到达后立即发送。应用进程需要一些时间来接收 E O F,告诉它的T C P关闭它 这一端的连接。第二,服务器收到客户的 FIN后,并不一定要关闭它这一端的连接。就 像我们在18.5节中看到的,仍然可以发送数据。 18.11 如果一个产生 R S T的到达报文段有一个 A C K字段,那么 R S T的序号就是到达的 A C K字 段。第6行中值为1的ACK是相对于第 2行中的26368001的ISN。 18.12 参见 [Crowcroft et al. 1992]中对分层的评论。 18.13 发出了5个查询。假设有 3个分组用于建立连接, 1个用于查询,1个用于确认查询, 1个 用于响应, 1个用于确认响应, 4个用于终止连接。这就是说每次查询需要 11个分组, 总共需要55个分组。使用UDP则可以减少到10个分组。 如果对查询的确认和响应结合在一起,每个查询需要的分组可以减少到 10个(19.3节)。 18.14 限制是每秒 268个连接:最大数目的TCP端口号(65536-1024 = 64512,忽略知名端口) 除以TIME_WAIT状态的2MSL。 18.15 重复的FIN会得到确认,2MSL定时器重新开始。 18.16 在T I M E _ WA I T状态中收到一个 R S T引起状态过早地终止。这就叫作 T I M E _ WA I T断开 (assassination)。RFC1337 [Braden 1992a]仔细讨论了这个现象,并显示了潜在的问题。 这个RFC提出的简单的修改就是在 TIME_WAIT状态时忽略 RST段。 18.17 它是在具体实现不支持半关闭连接的时候。一旦应用进程引起发送一个 FIN,应用进程 就不能再从这个连接读数据了。 18.18 不。输入的数据段通过源 I P地址、源端口号、目的 I P地址和目的端口号进行区分。在 1 8 . 11节中我们看到对于输入的连接请求,一个 T C P服务器一般可以通过目的 I P地址来 拒绝接收。

第19章 19.1 应用程序的两个写操作,跟着一个读操作,引起了迟延,因为 N a g l e算法很可能被激活。 第一个报文段(包含 8个字节的数据)被发送后,在发送后面 1 2个字节的数据之前必须 等待第一个报文段的确认。如果服务器实现了延迟的确认,在收到这个确认之前,可能 会有一个达到200 ms的时延(加上RTT)。 19.2 假设5个字节的 CSLIP首部(IP和TCP)和两个字节的数据,这些段通过 SLIP链路的RTT 大约是14.5 ms。我们要加上通过以太网的 RT T(一般是 5~10 ms),加上在 s u n和b s d i

390

使用TCP/IP详解,卷1:协议

下载

上的选路时间。因此,观察到的时间看起来是正确的。 19.3 在图19-6中,第6和第9报文段之间的时间是 533ms。在图19-8中,第8和第12报文段之间 的时间是 272ms(我们测量了 F2键的时间,而不是 F1键,因为F1键的第1个回显在第2个 图中丢掉了)。

第20章 20.1 字节号0是SYN,字节号8193是FIN。SYN和FIN在序号空间里各占用了一个字节。 20.2 应用程序的第1个写操作引起置位 P U S H标志发送第1个报文段。因为 B S D / 3 8 6总是使用 慢启动,它在发送更多数据之前等待第1个确认。在这段时间里,下面三个写操作发生 了,发送 T C P把要发送的数据缓存了起来。因为在缓存中有更多的数据要发送,下面的 三个报文段没有包含 P U S H标志。最后,慢启动跟上了应用程序的写操作,每个应用程 序的写操作引起了发送一个报文段,并且因为那个报文段是缓存中最后的一个,所以设 置PUSH标志。 20.3 为容量求解带宽迟延方程式,第一种情况是 1920字节,卫星的情况是 2062字节。看起来 TCP只声明了一个2048字节的窗口。 一个大于16000字节的窗口应该能够使卫星链路饱和。 20.4 不,因为TCP超时之后可能重新对数据进行分组,就像我们将在 21.11节中看到的。 20.5 作为应用进程读数据的结果,第 1 5报文段是 T C P模块自动发送的窗口更新,它引起窗口 的打开。这类似于图中的第 9报文段。但是,第 1 6报文段是应用进程关闭它这一端连接 的结果。 20.6 这可能引起发送者以比网络实际能够处理的更快的速率向网络发送分组。这叫作确认压 缩(ACK compression)或确认粉碎 (ACK smashing) [Mogul 1993, 15.8.13节]。这个引用显 示了在Internet上发生的ACK压缩,尽管它很少会导致拥塞。

第21章 21.1 下一个超时设定是 48秒:0+4×12。因子4是指数退避的下一个乘数。 21.2 看起来SVR4在计算RTO时仍然使用了因子 2D,而不是4D。 21.3 T F T P使用的停止等待协议被限制为每个往返过程传送 5 1 2字节的数据。 3 2 7 6 8 / 5 1 2×1 . 5 是96秒。 21.4 显示了 4个报文段,编号为 1、2、3和4。假设接收的顺序是 1、3、2和4,接收者产生的 确认将是ACK 1(一个正常的确认),ACK 1(当收到了报文段 3,失序后一个重复的确 认),当收到了报文段 2后的ACK 3(对报文段 2和报文段 3的确认),然后是 ACK 4 。这 儿产生了一个重复的确认。如果接收的顺序是 1、3、4和2,将会产生两个重复的确认。 21.5 不,因为斜率仍然是向上和向右,不是向下。 21.6 参见图E-1。 21.7 在图2 1 - 2中,报文段包含 2 5 6个字节的数据,在 s l i p和b s d i之间的9600 b/s的C S L I P链 路传输大约需要 250 ms。假定数据段没有在 b s d i和v a n g o g h之间的某个地方排队,它 们分别需要大约 250 ms到达v a n g o g h。因为这个时间超过了延迟确认定时器的 200 ms 时间,当下一个延迟确认定时器超时时,每个报文段得到确认。

下载

391

附录D 部分习题的解答使用

第22章 22.1 主机b s d i上的确认很可能都要被延迟,因为没有理由立即发送它们。这就是为什么相 对次数有一个0.170和0.370的小数部分。看起来 b s d i上200 ms的定时器在 s u n上同样的 定时器之后 18 ms 才开始计时。 22.2 F I N标志,和 S Y N标志一样,在序号空间占据了 1个字节。通知的窗口看起来小了一个 字节,因为 TCP允许FIN标志在序号空间占用 1个字节的空间。

第23章 23.1 通常激活 k e e p a l i v e选项比显式地编写应用程序探测报文更容易; k e e p a l i v e探测报文比应 用程序探测报文占用更少的网络带宽(因为 k e e p a l i v e探测报文和应答不包含任何数 据);如果连接不是空闲的,就不会发送探测报文。 23.2 keepalive选项可能会由于一个临时性的网络中断而引起一个非常好的连接断开;发送探 测报文的间隔( 2小时)一般不可以根据应用程序进行配置。

第24章 24.1 它意味着发送 T C P支持窗口扩缩选项,但这个连接并不需要扩缩它的窗口。另一端(接 收这个SYN的)可以说明一个窗口扩缩因子(可以是 0或非0)。 24.2 64 000:接收缓存大小(128 000)向右移1位。55 000:接收缓存(220 000)向右移2位。 24.3 不。问题是确认没有可靠地交付(除非它们被数据捎带在一起发送),因此,一个确认 上的扩缩改变可能会丢失。 24.4 2 32×8/120等于286 Mb/s,2.86倍于FDDI数据率。 24.5 每个 T C P将不得不记住从每个主机的任何一个连接上收到的上一个时间戳。阅读

RFC

1323的附录B.2以了解进一步的细节。 24.6 应用程序必须在和另一端建立连接之前设置接收缓存的大小,因为窗口扩缩选项在初始 的SYN段中发送。 24.7 如果接收者每次确认第2个数据报文段,吞吐量是 1 118 881字节/秒。若使用一个 62个报 文段的窗口,每 31个报文段确认一次,则数值是 1 158 675。 24.8 使用这个选项,确认中回显的时间戳总是来自于引起确认的报文段。对哪个重传的报文段 进行确认没有疑问,但仍然需要Karn算法的另一部分,即处理重传的指数退避。 24.9 接收T C P对数据进行排队,但只有完成了三次握手后,数据才能传递给应用程序:当接 收TCP进入了ESTABLISH状态。 24.10 交换了5个报文段: 1) 客户到服务器: SYN,数据(请求)和 FIN。服务器必须像上个习题所述的一样对数 据进行排队。 2) 服务器到客户: SYN和对客户SYN的确认。 3) 客户到服务器:对服务器的 S Y N的确认和客户的 F I N(再次)。这样使得服务器进入 已建立状态,并且来自报文段 1的排队数据被传递给服务器应用程序。 4) 服务器到客户:客户 F I N的确认(它也确认了客户的数据)、数据(服务器的应答)

392

使用TCP/IP详解,卷1:协议

下载

和服务器的 F I N。这里假定了 S P T足够短以允许这个延迟的确认。当客户 T C P收到这个 报文段,就将应答传递给客户端的应用程序,但是整个时间是 RTT加上SPT的两倍。 5) 客户到服务器:对服务器 FIN的确认。 24.11 每秒16 128次交互(64 512除以4)。 24.12 使用T / T C P的交互时间不可能比两个主机之间交换一个 U D P数据报所需的时间短。因 为T/TCP涉及了UDP没有做的状态处理,所以 T/TCP总是要花更多的时间。

第25章 25.1 如果一个系统运行了一个管理进程和一个代理进程,它们很可能是不同的进程。管理进 程在 U D P端口 1 6 2监听 t r a p告警,代理进程在 U D P端口 1 6 1等待请求。如果 t r a p告警和 SNMP请求使用了同样的端口,将很难区分管理进程和代理进程。 25.2 参考25.7节中的“表访问( Table Access)”部分。

第26章 26.1 我们预期从服务器来的第 2、4和9报文段将被延迟。第 2和第 4报文段之间的时间差是 190.7 ms,第2和第9报文段之间的时间差是 400.7 ms。 从客户到服务器的所有确认看起来都被延迟:第 6、11、1 3、1 5、1 7和1 9报文段。从第 6报文段开始的最后 5个时间差是 400.0、600.0、800.0、1000.0和2.600 ms。 26.2 如果连接的一个端点处于 TCP的紧急模式,每次收到一个报文段,就会发送一个报文段。 这个报文段没有告诉接收者任何新的东西(例如,它不是对新数据的确认),它也不包 含数据,它只是重申进入了紧急模式。 26.3 只有 5 1 2个这样的保留端口( 5 1 2 ~ 1 0 2 3),限制了一个主机只能有 5 1 2 个远程登录的 (R l o g i n)客户。在实际生活中,这个限制一般小于 5 1 2个,因为在这个范围中的一些端 口号被不同的服务器,如远程登录服务器,用作了知名端口。 T C P的限制是一对插口定义的一个连接( 4元组)必须是唯一的。因为 R l o g i n服务器总 是使用了同样的知名端口( 513),一台主机上多个 Rlogin客户只有在它们和不同的服务 器主机相连接时才可以使用相同的保留端口。然而, R l o g i n客户没有采用重用保留端口 的技术。如果使用了这种技术,理论限制就是在同一时刻最多有 5 1 2个R l o g i n客户和同 一个的服务器主机相连。

第27章 27.1 当一对插口处于 2 M S L等待另一端状态时,理论上不能建立连接。然而,实际上,在 1 8 . 6节中我们看到大多数伯克利演变的实现确实为一个处于 T I M E _ WA I T状态的连接接 受了一个新的SYN。 27.2 这些行不是以 3个数字作为应答代码开始的服务器应答的一部分,因此它们不可能来自 于服务器。

第28章 28.1 一个域文字 (domain literal) 是在一对方括号里的点分十进制 I P地址。例如: m a i l

下载

393

附录D 部分习题的解答使用

rstevens@[140.252.1.54]。 28.2 6个来回:HELO命令、MAIL、RCPT、DATA报文主体和 QUIT。 28.3 这是合法的,称为流水线技术 (pipelining) [Rose 1993,4 . 4 . 4节]。不幸的是,有一些脑 子坏了( b r a i n - d a m a g e d)的S M T P接收者实现,每处理完一条命令就要清除它们的输入 缓存,使得这种技术不可用。如果使用了这种技术,客户自然不能丢弃报文直到所有的 应答都已检查过,确信报文已被服务器接受了。 28.4 考虑习题28.2的前5个网络上的往返。每个都是一个小命令(很可能是只有一个报文段), 对网络几乎没有负载。如果所有 5个都没有重传地送到了服务器,当报文主体发送时, 拥塞窗口可能是 6个报文段。如果报文主体很大,客户可能一次发送前 6个报文段,造成 网络可能来不及处理。 28.5 更新版本的 BIND使用同样的值来正移 MX记录,作为平衡负载的一种方式。

第29章 29.1 不,因为 t c p d u m p不能从其他 U D P数据报中区别 R P C请求或应答。它只有在源端口号 或目的端口号为 2 0 4 9时,才将 U D P数据报的内容理解为 N F S分组。随机的 R P C请求和应 答可以使用两个端点上的一个临时的端口号。 29.2 回忆一下 1 . 9节中,一个进程必须有超级用户权限才能给自己分配一个小于 1 0 2 4的端口 号(一个知名端口)。尽管这对于系统提供的服务器没问题,如 Te l n e t服务器、 F T P服务 器、和端口映射器,但我们不想给所有的 RPC服务器提供这个权限。 29.3 这个例子中的两个概念是客户忽略那些服务器应答,如果这些应答不具有客户正在等待 的X I D;U D P对收到的数据报进行排队(队列长度有一个上限),直到应用进程读取了 数据报。另外, XID不会在超时和重传时改变,它只在调用另一个服务器过程时改变。 客户 s t u b执行的事件为:时刻 0:发送请求 1;时刻 4:超时并重传请求 1;时刻 5:接 收服务器应答 1,将应答返回给应用程序;时刻 5:发送请求 2;时刻 9:超时并重传请 求2;时刻 1 0:收到服务器的应答 1,但因为我们在等待应答 2,所以忽略它;时刻 11: 收到服务器的应答 2,将应答返回给应用程序。 服务器的事件如下:时刻 0:收到请求 1,启动操作;时刻 5:发送应答 1;时刻 5:收到 请求1(来自于客户在时刻 4的重传),启动操作;时刻 1 0:发送应答 1;时刻 1 0:收到 请求2(来自于客户在时刻 5的重传),启动操作;时刻 11:发送应答 2;时刻 11:收到 请求2(来自于客户在时刻 9的重传),启动操作;时刻 1 2:发送应答 2。这个最后的服 务器应答仅仅被客户的 U D P排队,用于客户的下一次接收操作。当客户读了这个应答 时,XID将是错误的,客户将忽略它。 29.4 改变服务器的以太网卡就改变了它的物理地址。即使我们注意到在 4 . 7节中S V R 4没有在 引导时发送一个免费 A R P,它仍然必须在能够应答 s u n的N F S请求之前,向 s u n发送一 个请求s u n的物理地址的ARP请求。因为 s u n已经有了 s v r 4的一个ARP登记项,它从这 个ARP请求中根据发送者的(新)硬件地址更新这个登记项。 29.5 客户块I / O守护进程的第2个(在偏移 7 3 7 2 8处读的)与第1个失去同步大约 0 . 7 4秒。即在 行131~145,第2个守护进程在第 1个之后超时 0.74秒。看来服务器没有看到在 167行的请 求,但它看到了 1 6 8行的请求。第2个块 I / O守护进程只会在 1 6 8行之后 0 . 7 4秒才会重传。

394

使用TCP/IP详解,卷1:协议

下载

同时,第1个块 I/O守护进程继续发送请求。 29.6 如果使用的是 T C P,包含着服务器应答的 T C P报文段在网络中丢失了,当服务器的 T C P 模块没有从客户的 T C P模块收到一个确认时,它将超时,并重传应答。最终,这个报文 段将到达客户的 T C P。这里不同的是两个 T C P模块完成超时和重传,而不是 N F S客户和 服务器(当使用 U D P时,N F S客户代码完成超时和重传)。因此, N F S客户并不知道应 答丢失了,需要被重传。 29.7 N F S服务器在重启动之后获得一个不同的端口号是可能的。这将使客户变得很复杂,因 为它需要知道服务器崩溃了,并且在服务器重启动之后与服务器主机的端口映射器联系 以找到NFS服务器的新的端口号。 这种情况,即服务器主机崩溃然后重启动时,一个服务器的 RPC应用程序获得一个新的 临时性端口,可能发生在任何一个没有使用知名端口的 RPC应用程序上。 29.8 不。N F S客户可以为不同的服务器主机使用相同的本地的保留端口号。 T C P要求由本地 I P地址、本地端口、远端 I P地址和远端端口组成的 4元组是唯一的,对于每个服务器主 机来说,远端的 IP地址是不同的。

第30章 30.1 键入whois“net 88”。A类网络号64~95是保留的。 30.2 键入w h o i s w h i t e h o u s e - d。可以使用 om host命令或者nslookup查询DNS。 30.3 不,x s c o p e可以与服务器运行在不同的主机上。如果主机不同, x s c o p e也可以使用 TCP端口6000作为它的呼入连接。

下载

附录E 配 置 选 项 我们已经看到了许多冠以“依赖于具体配置”的 T C P / I P特征。典型的例子包括是否使能 U D P的检验和( 11 . 3节),具有同样的网络号但不同的子网号的目的 I P地址是本地的还是非本 地的( 1 8 . 4节)以及是否转发直接的广播( 1 2 . 3节)。实际上,一个特定的 T C P / I P实现的许多 操作特征都可以被系统管理员修改。 这个附录列举了本书中用到的一些不同的 T C P / I P实现可以配置的选项。就像你可能想到 的,每个厂商都提供了与其他实现不同的方案。不过,这个附录给出的是不同的实现可以修 改的参数类型。一些与实现联系紧密的选项,如内存缓存池的低水平线,没有描述。 这些描述的变量只用于报告的目的。在不同的实现版本中,它们的名字、默认值、 或含义都可以改变。所以你必须检查你的厂商的文档(或向他们要更充分的文档)来 了解这些变量实际使用的单词。 这个附录没有覆盖每次系统引导时发生的初始化工作:对每个网络接口使用 i f c o n f i g 进行初始化(设置 I P地址、子网掩码等等)、往路由表中输入静态路由等等。这个附录集中描 述了影响TCP/IP操作的那些配置选项。

E.1 BSD/386 版本1.0 这个系统是自从 4 . 2 B S D以来使用的“经典” B S D配置的一个例子。因为源代码是和系统 一起发布的,所以管理员可以指明配置选项,内核也可重编译。存在两种类型的选项:在内 核配置文件中定义的常量(参见 c o n f i g(8)手册)和在不同的 C源文件中的变量初始化。大胆 而又经验丰富的管理员也可以使用排错工具修改正在运行的内核或者内核的磁盘映像中这些 变量的值,以避免重新构造内核。 下面列出的是在内核配置文件中可以修改的常量。 IPFORWARDING 这个常量的值初始化内核变量 ipforwarding。如果值为 0(默认),就不转发 IP数据报。 如果是1,就总是使能转发功能。 GATEWAY 如果定义了这个常量,就使得 I P F O RWA R D I N G的值被置为 1。另外,定义这个常量还使 得特定的系统表格( ARP快速缓存表和路由表)更大。 SUBNETSARELOCAL 这个常量的值初始化内核变量 s u b n e t s a r e l o c a l。如果值为 1(默认),一个和发送主 机具有同样网络号、但不同子网号的目的 I P地址被认为是本地的。如果是 0,只有在同一个子

396

使用TCP/IP详解,卷1:协议

下载

网的目的IP地址才认为是本地的。图 E-1总结了上述规律。 网络标识符 子网标识符 相同 相同 不同

相同 不同

subnetsarelocal 本地 本地 非本地

本地 非本地 非本地

注释 总是本地的 依赖于配置 总是非本地的

图E-1 对subnetarelocal内核变量的理解

这个变量的值影响了 TCP选择的MSS。当给一个本地的目的地址发送报文时, TCP选择的 是基于输出接口的 MTU的MSS。而发送给一个非本地的地址时, TCP使用变量 tcp_mssdflt 作为MSS。 IPSENDREDIRECTS 这个常量的值初始化内核变量 i p s e n d r e d i r e c t s。如果值为 1(默认),主机在转发 I P 数据报时,将发送 ICMP重定向。如果是 0,不发送ICMP重定向。 DIRECTED_BROADCAST 如果值为 1(默认),如果收到的数据报的目的地址是主机的一个接口的直接广播地址, 就将它作为一个链路层的广播来转发。如果是 0,这些数据报就会被丢弃。 下面的变量也可以改变,它们在目录 /usr/src/sys/netinet中的不同文件中定义。 tcprexmtthresh 引起快速重传和快速恢复算法的连续 ACK的数目。默认值是 3。 tcp_ttl TCP段的TTL字段的默认值。默认值是 60。 tcp_mssdflt 用于非本地目的地址的默认的 TCP MSS。默认值是 512。 tcp_keepidle 在发送一个 k e e p a l i v e探测报文之前必须等待的 500 ms 时钟间隔的次数。默认值是 1 4 400(2个小时)。 tcp_keepintvl 如果没有收到响应,在两个连续的 k e e p a l i v e探测报文之间等待的 500 ms时钟间隔的次 数。默认值是150(75秒)。 tcp_sendspace TCP发送缓存的默认大小。默认值是 4096。 tcp_recvspace TCP接收缓存的默认大小。这个值影响了提供的窗口大小。默认值是 4096。 udpcksum 如果非 0,对输出的 U D P数据报计算 U D P检验和,并且对于包含了非 0检验和的输入 U D P 数据报要验证它们的检验和。如果值为 0,不计算输出的 U D P数据报的检验和,也不验证输入 UDP数据报的检验和,即使发送者计算了一个检验和。默认值是 1。

下载

397

附录E 配 置 选 项使用

udp_ttl UDP数据报TTL字段的默认值。默认值是 30。 udp_sendspace UDP发送缓存的默认大小。定义了可以发送最大的 UDP数据报。默认值是 9126。 udp_recvspace UDP接收缓存的默认大小。默认值是 41 600,允许40个1024字节的数据报。

E.2 SunOS 4.1.3 SunOS 4.1.3使用的方法类似于我们在 B S D / 3 8 6中看到的。因为大部分的内核源代码都没 有发布,所以所有的 C变量初始化都包含在一个提供的 C源文件中。 管理员的内核配置文件(参见 c o n f i g( 8 )手册)可以定义下面的变量。修改了配置文件 之后,需要构造一个新的内核,然后重启动。 IPFORWARDING 这个常量的值初始化内核变量 i p _ f o r w a r d i n g。如果值为- 1,就不转发 I P数据报,而 且变量的值不能再改变。如果是 0(默认),不转发 I P数据报,但是如果多个接口都工作,变 量的值可以修改为 1。如果是1,就总是能转发 IP数据报。 SUBNETSARELOCAL 这个常量的值初始化内核变量 i p _ s u b n e t s a r e l o c a l。如果值为 1(默认),一个和发 送主机具有同样网络号,但不同子网号的目的 I P地址被认为是本地的。如果是 0,只有在同一 个子网的目的 I P地址才认为是本地的。图 E - 1总结了上述规律。当给一个本地的目的地址发送 报文时, T C P选择的是基于输出接口的 M T U的M S S,而发送给一个非本地的地址时, T C P使 用变量tcp_default_mss作为MSS。 IPSENDREDIRECTS 这个常量的值初始化内核变量 i p _ s e n d r e d i r e c t s。如果值为 1(默认),主机在转发IP 数据报时,将发送 ICMP重定向。如果是 0,不发送ICMP重定向。 DIRECTED_BROADCAST 这个常量的值初始化内核变量 i p _ d i r b r o a d c a s t。如果值为 1(默认),如果收到的数 据报的目的地址是主机的一个接口的直接广播地址,就将它作为一个链路层的广播来转发。 如果是0,这些数据报就会被丢弃。 文件/ u s r / k v m / s y s / n e t i n e t / i n _ p r o t o . c定义了下面一些可以修改的变量。一旦 修改了这些变量,必须构造一个新的内核,然后重启动。 tcp_default_mss 用于非本地地址的默认 TCP MSS。默认值是 512。 tcp_sendspace TCP发送缓存的默认大小。默认值是 4096。

398

使用TCP/IP详解,卷1:协议

下载

tcp_recvspace TCP接收缓存的默认大小。这个值影响了提供的窗口大小。默认值是 4096。 tcp_keeplen 一个发往 4 . 2 B S D主机的 k e e p a l i v e探测报文必须包含一个字节的数据来得到一个响应。把 这个变量的值设置为 1是为了兼容于以前的实现。默认值是 1。 tcp_ttl TCP段的TTL字段的默认值。默认值是 60。 tcp_nodelack 如果非0,对ACK不做延迟。默认值是 0。 tcp_keepidle 在发送一个 k e e p a l i v e探测报文之前必须等待的 500 ms 时钟间隔的次数。默认值是 14 400 (2个小时)。 tcp_keepintvl 如果没有收到响应,在两个连续的 k e e p a l i v e探测报文之间等待的 500 ms时钟间隔的次数。 默认值是150(75秒)。 udp_cksum 如果非 0,对输出的 U D P数据报计算 U D P检验和,并且对于包含了非 0检验和的输入 U D P 数据报要验证它们的检验和。如果值为 0,不计算输出 U D P数据报的检验和,也不验证输入 UDP数据报的检验和,即使发送者计算了一个检验和。默认值是 0。 udp_ttl UDP数据报TTL字段的默认值。默认值是 60。 udp_sendspace UDP发送缓存的默认大小。定义了可以发送最大的 UDP数据报。默认值是 9000。 udp_recvspace UDP接收缓存的默认大小。默认值是 18 000,允许两个 9000字节的数据报。

E.3 SRV4 SVR4的TCP/IP配置类似于前两个系统,但可用的选项更少。在文件/etc/conf/pack.d/ip/ space.c5可以定义两个常量,然后必须重新构造内核并且重启动。 IPFORWARDING 这个常量的值初始化内核变量 i p f o r w a r d i n g。如果是 0(默认),不转发 I P数据报。如 果是1,就总是能转发 IP数据报。 IPSENDREDIRECTS 这个常量的值初始化内核变量 i p s e n d r e d i r e c t s。如果值为 1(默认),主机在转发 I P 数据报时,将发送 ICMP重定向。如果是 0,不发送ICMP重定向。 前两节中,我们描述的许多变量在内核中都有定义,但必须修补内核来改变它们。例如, 存在一个名为tcp_keepidle的变量,它的值是 14 400。

399

附录E 配 置 选 项使用

下载 E.4 Solaris 2.2

Solaris 2.2是较新的Unix系统的典型代表,它为管理员提供了一个可以改变 TCP/IP系统配 置选项的程序。这样可以不必通过修改源文件和重新构造内核来进行配置。 配置程序是 ndd(1)。我们可以运行程序,看看在 UDP模块中可以检验和修改的参数: 读、写 读、写 读、写 读、写 读、写 只读

我们可以指明 5 个模块: / d e v / i p 、/ d e v / i c m p 、 / d e v / a r p 、 / d e v / u d p 和 / d e v / t c p。问号参数(为了防止外壳程序解释问号,我们在它前面加了一个反斜线)告诉 ndd程序列出那个模块的所有参数。查询一个变量的值的例子是: s o l a r i s %n d d / d e v / t c p t c p _ m s s _ d e f 536

为了修改一个变量的值,我们需要有超级用户的权限,输入: s o l a r i s #n d d - s e t / d e v / i p i p _ f o r w a r d i n g 0

这些变量可以划分为三种类型: 1) 系统管理员可以修改的配置变量(如, ip_forwarding)。 2) 只能显示的状态变量(如, A R P快速缓存)。这个信息一般通过命令 i f c o n f i g , netstat和arp以一种更好理解的格式提供。 3) 用于内核源代码的排错变量。使能一些这种变量可以在运行时产生内核的排错输出, 当然这会降低系统的性能。 现在我们可以描述每个模块的参数了。所有的参数如果没有注明“(只读)”,就是可读写 的。只读的参数是上面第 2种情况的状态变量。我们对于第 3种情况的变量注明了“(排错)”。 如果不另外说明,所有的计时变量都以毫秒指明,这和其他系统不同,其他系统一般以 500 ms 时钟间隔的次数来指明时间。

/dev/ip ip_cksum_choice (排错)在 IP检验和算法的两个独立实现之中选择一个。 ip_debug (排错)如果大于 0,使能内核打印排错信息功能。值越大输出的信息越多。默认为 0。 ip_def_ttl 如果运输层没有指明,指定输出 IP数据报默认的TTL。默认值是 255。 ip_forward_directed_broadcasts 如果值为 1(默认),如果收到的数据报的目的地址是主机的一个接口的直接广播地址, 就将它作为一个链路层的广播来转发。如果是 0,这些数据报就会被丢弃。 ip_forward_src_routed 如果为1(默认),就转发包含一个源路由选项的接收数据报。如果为 0,这些数据报将被

400

使用TCP/IP详解,卷1:协议

下载

丢弃。 ip_forwarding 指明系统是否转发进入的 I P数据报: 0表示不转发, 1表示总是转发, 2(默认)表示只有 当两个或两个以上接口都工作时才转发。 ip_icmp_return_data_bytes 一个ICMP差错返回的除了 IP首部以外的数据字节的数目,默认是 64。 ip_ignore_delete_time (排错)一个IP路由表项(IRE)最小的生命期。默认是30秒(这个参数以秒记,不是毫秒) 。 ip_ill_status (只读)显示每个 IP下层数据结构的状态。每个接口存在一个下层数据结构。 ip_ipif_status (只读)显示每个 I P接口数据结构的状态( I P地址、子网掩码等等)。每个接口存在一个 这种结构。 ip_ire_cleanup_interval (排错)扫描IP路由表,删除过时表项的时间间隔。默认是 30 000 ms(30秒)。 ip_ire_flush_interval 从IP路由表中无条件地刷新 ARP信息的间隔。默认是 1200 000 ms(20分钟)。 ip_ire_pathmtu_interval 路径MTU发现算法尝试增加 MTU的间隔。默认是 30 000 ms(30秒)。 ip_ire_redirect_interval 来自ICMP重定向的IP路由表项被删除的间隔。默认是 60 000 ms(60秒)。 ip_ire_status (只读)显示所有的 IP路由表项。 ip_local_cksum 如果为 0(默认),I P不为通过环回接口发送和接收的数据报计算 I P检验和或者更高层的 检验和(即 TCP、UDP、ICMP或IGMP)。如果为1,就要计算这些检验和。 ip_mrtdebug (排错)如果为 1,使能内核打印多播路由的排错输出。默认是 0。 ip_path_mtu_discovery 如果为1(默认),I P执行路径M T U发现。如果是 0,I P不会在输出的数据报中设置“不分 片”比特。 ip_respond_to_address_mask 如果为0(默认),主机不响应ICMP的地址掩码请求。如果为 1,主机则响应。 ip_respond_to_echo_broadcast 如果为1(默认),主机响应发往一个广播地址的 ICMP回显请求。如果为 0,则不响应。 ip_respond_to_timestamp 如果为0(默认),主机不响应ICMP的时间戳请求。如果为 1,则响应。 ip_respond_to_timestamp_broadcast 如果为0(默认),主机不响应发往一个广播地址的 ICMP时间戳请求。如果为 1,则响应。

下载

401

附录E 配 置 选 项使用

ip_rput_pullups (排错)来自于网络接口驱动程序的缓存数目的计数,它需要增长以访问整个 I P首部。引 导时它被初始化为 0,并且可以被复位为 0。 ip_send_redirects 如果为1(默认),当主机作为一个路由器时,它发送 ICMP重定向。如果为 0,则不发送。 ip_send_source_quench 如果为 1(默认),当输入的数据报被丢弃时,主机生成 I C M P源抑制差错。如果为 0,则 不生成这种差错。 ip_wroff_extra (排错)在缓存中为 IP首部分配的额外空间的字节数。默认是 32。 /dev/icmp icmp_bsd_compat (排错)如果为 1(默认),收到的数据报的 I P首部的长度字段的值被调整为不包括 I P首部 的长度。这和伯克利演变的实现是一致的,用于读原始的 I P或原始的 I C M P分组的应用程序。 如果为0,则不改变长度字段的值。 icmp_def_ttl 输出ICMP报文的默认的TTL。默认值为 255。 icmp_wroff_extra (排错)在缓存中为 IP选项和数据链路首部所分配的额外空间的字节数。默认是 32。 /dev/arp arp_cache_report (只读)ARP的快速缓存。 arp_cleanup_interval ARP登记项从ARP快速缓存中被删除的时间间隔。默认是 300 000 ms(5分钟)(IP为完成 的ARP传输维护着它自己的快速缓存;参见 ip_ire_flush_interval)。 arp_debug (排错)如果为 1,使能打印 ARP驱动程序的排错输出。默认是 0。 /dev/udp udp_def_ttl 输出UDP数据报的默认的 TTL。默认值是 255。 udp_do_checksum 如果为1(默认),为输出的U D P数据报计算U D P检验和。如果为 0,输出的 U D P数据报不 包含一个检验和(和其他大多数的实现不一样,这个 U D P检验和标志并不影响进入的数据报。 如果一个接收到的数据报有一个非 0的检验和,它总是要被验证)。 udp_largest_anon_port 可以为UDP临时端口分配的最大端口号。默认是 65535。

402

使用TCP/IP详解,卷1:协议

下载

udp_smallest_anon_port 可以为UDP临时端口分配的最小端口号。默认是 32768。 udp_smallest_nonpriv_port 一个进程需要超级用户的权限才能给自己分配一个小于这个值的端口号。默认是 1024。 udp_status (只读)所有本地的 UDP端点的状态:本地 IP地址和端口,远端 IP地址和端口。 udp_trust_optlen (排错)不再使用。 udp_wroff_extra (排错)在缓存中为 IP选项和数据链路首部所分配的额外空间的字节数。默认是 32。 /dev/tcp tcp_close_wait_interval 2MSL的值:在TIME_WAIT状态花费的时间。默认是 240 000 ms(4分钟)。 tcp_conn_grace_period (排错)当发送一个 SYN时,在定时器间隔上附加的时间。默认是 500 ms。 tcp_conn_req_max 在一个监听的端口上挂起的连接请求的最大数目。默认是 5。 tcp_cwnd_max 拥塞窗口的最大值。默认是 32768。 tcp_debug (排错)如果为 1,使能打印 TCP的排错输出。默认是 0。 tcp_deferred_ack_interval 在发送一个延迟的 ACK之前等待的时间。默认是 50 ms 。 tcp_dupack_fast_retransmit 引起快速重传、快速恢复算法的连续的重复 ACK的数目。默认是 3。 tcp_eager_listeners (排错)如果为 1(默认),T C P在将一个新的连接返回给一个挂起的被动打开的应用程序 之前需要进行三次握手。这是大多数的 T C P实现采用的方式。如果为 0,T C P将呼入连接请求 (收到的 S Y N)传递给应用程序,并不完成三次握手直到该应用程序接受了这个连接(把这个 值置为0可能引起很多已经存在的应用程序不能用)。 tcp_ignore_path_mtu (排错)如果为 1,路径 M T U发现算法忽略接收到的需要 I C M P分段的报文。如果为 0(默 认),使能TCP的路径MTU发现。 tcp_ip_abort_cinterval 当TCP进行一个主动打开时,整个重传超时的值。默认是 240 000 ms(4分钟)。 tcp_ip_abort_interval 一个TCP连接建立以后,整个重传超时的值。默认是 120 000 ms(2分钟)。 tcp_ip_notify_cinterval

下载

403

附录E 配 置 选 项使用

当TCP正在进行一个主动打开时,TCP通知IP去寻找一条新路由超时的值。默认是10 000 ms (10秒)。 tcp_ip_notify_interval TCP为一个已经建立的连接通知IP去寻找一条新路由超时的值。默认是 10 000 ms(10秒)。 tcp_ip_ttl 用于输出TCP段的TTL。默认为255。 tcp_keepalive_interval 在发出一个 k e e p a l i v e探测报文之前,一个连接保持空闲状态的时间。默认为 7200000 ms (2小时)。 tcp_largest_anon_port 为TCP临时端口分配的最大端口号。默认为 65535。 tcp_maxpsz_multiplier (排错)指明了报文流首部将应用程序写的数据分装成几个 MSS。默认是1。 tcp_mss_def 非本地的目的地址的默认的 MSS。默认是536。 tcp_mss_max 最大的MSS。默认为65495。 tcp_mss_min 最小的MSS。默认为1。 tcp_naglim_def (排错)每个连接的 N a g l e算法阈值的最大值。默认是 6 5 5 3 5。每个连接的值以 M S S的最小 值或这个值开始。 TCP_NODELAY插口选项将每个连接的值设置为 1,以禁止Nagle算法。 tcp_old_urp_interpretation (排错)如果为 1(默认),采用紧急指针的一个以前的(但更常见的) B S D的理解:它指 向紧急数据最后一个字节后的一个字节。如果为 0,采用主机需求 R F C理解:它指向紧急数据 的最后一个字节。 tcp_rcv_push_wait (排错)在把接收数据传递给应用程序之前,可以缓存的没有设置 P U S H标志的数据的最 大字节数。默认是 16384。 tcp_rexmit_interval_initial (排错)初始的重传超时间隔。默认是 500 ms。 tcp_rexmit_interval_max (排错)最大的重传超时间隔。默认是 60 000 ms(60秒)。 tcp_rexmit_interval_min (排错)最小的重传超时间隔。默认是 200 ms。 tcp_rwin_credit_pct (排错)在对每个接收的段进行流量控制检查之前,必须达到的接收缓存窗口的百分比。 默认是50%。 tcp_smallest_anon_port

404

使用TCP/IP详解,卷1:协议

下载

分配给TCP临时端口的开始端口号。默认是 32768。 tcp_smallest_nonpriv_port 一个进程需要有超级用户的权限才能给自己分配一个小于这个值的端口号。默认是 1024。 tcp_snd_lowat_fraction (排错)如果非 0,发送缓存的低水平线是发送缓存的大小除以这个值。默认是 0(禁止)。 tcp_status (只读)所有TCP连接的信息。 tcp_sth_rcv_hiwat (排错)如果非 0,把报文流首部的高水平线设置为这个值。默认为 0。 tcp_sth_rcv_lowat (排错)如果非 0,把报文流首部的低水平线设置为这个值。默认为 0。 tcp_wroff_xtra (排错)在缓存中为 IP选项和数据链路首部所分配的额外空间的字节数。默认是 32。

E.5 AIX 3.2.2 A I X 3 . 2 . 2允许在运行时使用 n o命令设置网络选项。它可以显示一个选项的值,设置一个 选项的值,或者将一个选项的值设置为默认。例如,显示一个选项,我们键入: aix % no -o udp_ttl udp_ttl = 30

下面的选项可以被修改。 arpt_killc 在删除一个不活动的、完成的 ARP项之前等待的时间(以分钟计)。默认是20。 ipforwarding 如果为1(默认),总是转发 IP数据报。如果为 0,则禁止转发。 ipfragttl 等待重新装配的 IP数据报片的生存时间(单位为秒)。默认是60。 ipsendredirects 如果为 1(默认),当转发 I P数据报时,主机将发送 I C M P重定向。如果为 0,则不发送 ICMP重定向。 loop_check_sum 如果为1(默认),对通过环回接口发送的数据报计算 I P检验和。如果为 0,则不计算这个 检验和。 nonlocsrcroute 如果为1(默认) ,就转发包含一个源路由选项的接收数据报。如果为0,就丢弃这些数据报。 subnetsarelocal 如果值为 1(默认),一个和发送主机具有同样网络号,但不同子网号的目的 I P地址被认 为是本地的。如果是 0,只有在同一个子网的目的 I P地址才认为是本地的。图 E - 1总结了上述 规律。当给一个本地的目的地址发送报文时, T C P选择的是基于输出接口的 M T U的M S S。当 发送给一个非本地的地址时, TCP使用默认( 536)作为MSS。 tcp_keepidle

下载

405

附录E 配 置 选 项使用

在发送一个keepalive探测报文之前等待的500 ms时间段的倍数。默认值是 14 400(2小时)。 tcp_keepintvl 如果没有收到响应,在发送下一个 k e e p a l i v e探测报文之前等待的 500 ms时间段的倍数。 默认值是150(75秒)。 tcp_recvspace TCP接收缓存的默认大小。它影响了提供的窗口大小。默认值是 16 384。 tcp_sendspace TCP发送缓存的默认大小。默认是 16 384。 tcp_ttl TCP报文段TTL字段的默认值。默认值是 60。 udp_recvspace UDP接收缓存的默认大小。默认值是 41 600,允许40个1024字节数据报。 udp_sendspace UDP发送缓存的默认大小。定义了可以发送的最大的 UDP数据报。默认是 9216。 udp_ttl UDP数据报TTL字段的默认值。默认值是 30。

E.6 4.4BSD 4 . 4 B S D是第一个为多个内核参数提供动态配置的伯克利版本。使用 s y s c t l(8)命令。参 数的名字看起来就像 SNMP的MIB变量的名字。查看一个参数,我们键入: v a n g o g h %s y s c t l n e t . i n e t . i p . f o r w a r d i n g net.inet.ip.forwarding = 1

要修改一个参数的值需要有超级用户的权限,键入: v a n g o g h #s y s c t l - w n e t . i n e t . i p . t t l = 1 2 8

可以修改下面的参数。 net.inet.ip.forwarding 如果为0(默认),就不转发 IP数据报。如果为 1,则使能转发功能。 net.inet.ip.redirect 如果为 1(默认),当转发 I P数据报时,主机将发送 I C M P重定向。如果为 0,则不发送 ICMP重定向。 net.inet.ip.ttl TCP和UDP默认的TTL。默认值是 64。 net.inet.icmp.maskrepl 如果为0(默认),主机不响应ICMP地址掩码请求。如果为 1,则响应。 net.inet.udp.checksum 如果为1(默认),对输出的 U D P数据报计算 U D P检验和,并且对于包含了非 0检验和的呼 入UDP数据报要校验它们的检验和。如果值为 0,不计算输出 UDP数据报的检验和,也不验证 输入UDP数据报的检验和,即使发送者计算了一个检验和。 另外,在本附录前面部分描述的许多变量分散在几个不同的源文件中( tcp_keepidle、 subnetarelocal等等),它们也可以被修改。

下载

附录F 可以免费获得的源代码 本书中使用了很多共享软件包。本附录提供了一些如何获得这些软件的细节。 用来获得共享软件的技术称为匿名 F T P,F T P是标准的 I n t e r n e t文件传输协议(第 2 7章)。 27.3节显示了一个匿名 FTP的例子。如果想了解 Internet资源的一般背景和匿名 FTP的知识,请 参考任何一本关于 Internet的书,如[LaQuey 1993] 或 [Krol 1992]。 这里列出的主机被认为是提供共享软件的主要站点。其他许多站点也提供这些软件。可以 通过Internet的Archie服务查找其他的软件版本。下面列出的软件版本是本书中使用到的版本。 当你读到这本书时,可能已经发布了更新版本的软件。 你应该使用 FTP的dir命令来看一下在指定的主机上是否存在更新的版本。 本附录按照资源在本书中出现的章节号进行排序。 RFC(1.11节) 1.11节提供了请求得到 RFC站点的电子邮件地址。应答中包含了许多可以使用电子邮件或 匿名FTP获得RFC的站点。 记住你要先得到一个当前的索引,在索引中查找想要的 R F C。这个 R F C项中还告诉你这 个RFC是否废弃了或是被一个更新的 RFC替代了。 BSD Net/2 源代码(1.14节) BSD Net/2 源代码,其中包括了 TCP/IP协议的内核实现和标准的应用程序( Telnet客户和 服务器、 F T P客户和服务器等),从主机 f t p . u u . n e t的目录树中以 s y s t e m / u n i x / b s d source开始的位置可以得到。 SLIP(2.4节) 本书中使用的SLIP的版本来自于 f t p . e e . l b l . g o v。文件名以 c s l i p开始,因为它提供 了压缩的SLIP(2.5节)。 icmpaddrmask程序(6.3节) 参见本附录的最后一项。 icmptime程序(6.4节) 参见本附录的最后一项。 ping程序(第7章) BSD版本的ping程序一般比其他厂商提供的版本具有更多的选项和特征。主机ftp.uu.net

下载

407

附录F 可以免费获得的源代码使用

的文件system/unix/bsd-sources/sbin/ping中包含了最新版本的ping程序。 traceroute程序(第8章) t r a c e r o u t e程序来自于主机 f t p . e e . l b l . g o v。本附录的最后一项提供了 8 . 5节使用 的允许不严格的和严格的源站选路的版本。 路由器发现守护程序(9.6节) 可用的一个程序为路由器发现报文提供了主机支持和路由器支持。主机是 g r e g o r i o . s t a n f o r d . e d u,文件是 g w - d i s c o v e r y / n o r d m a r k - r d i s c . t a r。这个程 序是Sun微系统公司开发的一个共享软件。 gated守护程序(10.3节) 在10.3节提到的 gated路由选择守护程序在主机 gated.cornell.edu上可以得到。 traceroute.pmtu程序(11.7节) 参见本附录的最后一项。 IP多播软件(第13章) 为SunOS 4.x和U l t r i x提供I P多播的修改程序在主机 g r e g o r i o . s t a n f o r d . e d u的目录 vmtp-ip中可以得到。这个目录中还包含了为伯克利Unix系统提供IP多播的修改源程序。 BIND名字服务器(第14章) B I N D名字服务器,即 n a m e d守护程序,在主机 f t p . u u . n e t的文件n e t w o r k i n g / i p / dns/bind/bind.4.8.3.tar.z中。 一个更新的版本,即4.9版,从主机gatekeeper.dec.com的目录pub/BSD/bind/4.9中 可以得到。 host程序(第14章) host程序在主机 nikhefh.nikhef.nl的文件host.tar.z中提供。 dig和doc程序(第14章) 第1 4章中提到的 d i g 程序和 d o c 程序在主机 i s i . e d u 的文件 d i g . 2 . 0 . t a r . Z 和 doc.2.0.tar.Z中提供。 BOOTP服务器(第26章) 常用的Unix BOOTP服务器的不同版本在主机 lancaster.andrew.cmu.edu的pub目录中提供。 TCP快速扩充(第24章) T C P窗口扩缩选项、时间戳选项和 PAW S算法的一个共享源代码作为 BSD Net/2版的一组

408

使用TCP/IP详解,卷1:协议

下载

修改程序在主机 uxc.cso.uiuc.edu的文件pub/tcplw.shar.z中提供。 ISODE SNMP管理进程和代理进程(第25章) 2 5 . 7节中描述的 S N M P管理者进程和代理进程是 ISODE 8.0版本的一部分。很多站点都可 以得到它,如ftp.uu.net上的目录networking/osi/isode中。 MIME软件和实例(28.4节) 一个名为MetaMail的程序为很多不同的用户代理提供了 MIME的能力。这个程序可以从主 机thumper.bellcore.com的pub/nsb目录中得到。在这个目录中还有 MIME的其他信息。 Sun RPC(29.2节) RPC 4.0版本的源代码(使用插口API)在主机ftp.uu.net的目录systems/sun/sextape/ rpc4.0中提供。TI-RPC版本的源代码(使用了TLI API)在主机ftp.uu.net的目录networking/rpc 中提供。 Sun NFS(第29章) 一个N F S客户和服务器的共享软件的实现作为 BSD Net/2源代码的一部分提供,在本附录 的前面部分介绍了。 tcpdump程序(附录A) 本书中使用的 t c p d u m p 的版本来自于主机 f t p . e e . l b l . g o v 上的文件 t c p d u m p 2.2.1.tar.z。 BSD分组过滤器(A.1节) BSD分组过滤器是tcpdump发布的一部分。 sock程序(附录C) 参见本附录的最后一项。 ttcp程序 (这个程序本书中没有用到,但它是一个读者应该注意的常用的工具)。t t c p是测量两个 系统之间 T C P和U D P性能的基准工具。它是美国军事弹道研究实验室开发的,是一个共享软 件。它的副本可以从很多匿名 F T P站点获得,但一个增强的版本可以从主机 f t p . s g i . c o m的 目录sgi/src/ttcp中获得。 作者编写的软件 本书中用到的作者编写的软件在主机 f t p . u u . n e t 的文件 p u b l i s h e d / b o o k s / stevens.tcpipiv1.tar.z中提供。

下载

参考文献 所有的RFC都可以如同在1.11节描述的那样在 Internet上通过电子邮件或使用匿名 FTP免费 获得。 Albitz, P., and Liu, C. 1992. DNS and BIND. O’Reilly & Associates, Sebastopol, Calif. 配置和运行一个名字服务器所需要的管理任务的许多细节。 Alexa n d e r, S., and Droms, R. 1993. “DHCP Operations and BOOTP Vendor Extensions, ” RFC1533, 30 pages(Oct.). Almquist, P. 1992. “Type of Service in the Internet Protocol Suite,” RFC1349, 28 pages (July). 怎样使用IP首部的服务类型字段。 Almquist, P., ed. 1993. “Requirements for IP Routers,” Internet Draft (Mar.). 这是代替RFC 1009[Braden and Postel 1987]的一个RFC的草稿。新的RFC将很可能以四卷 出现。卷 1:I n t e r n e t的结构、术语和一般性内容。卷 2:链路层、互联网层、运输层和应 用层。卷3:转发和选路协议。卷 4:操作、维护和网络管理。 这个草稿可以通过匿名 F T P从主机 j e s s i c a . s t a n f o r d . e d u的目录 r r e q中获得。一旦最终的 RFC发布就要忽略这个草稿。 Bellovin, S. M. 1993. Private Communication. Bhide, A., Elnozahy, E. N., and Morgan, S. P. 1991. “A Highly Available Network File Server,” Proceeding of the 1991 Winter USENIX Conference, pp. 199-205, Dallas, Tex. 描述了一个对免费 ARP的使用(4.7节)。 Borenstein, N., and Freed, N. 1993. “MIME (Multipurpose Internet Mail Extensions) Part One: Mechanisms for Specifying and Describing the Format of Internet Message Bodies,” RFC 1521, 81 pages (Sept.). 这个RFC废止了早期的RFC 1341。这个RFC的附录H列出了与RFC 1341的不同点。 Borman, D. A., ed. 1990. “Telnet Linemode Option,” RFC 1184, 23 pages (Oct.). Borman, D. A. 1991. “IP Bandwidth Limits,”Message-ID [email protected]>, Usenet, comp.protocols.tcp-ip Newsgroup (Jan.). 说明了我们在24.8节的最后部分列出的有关 TCP性能的三个限制。 Borman, D. A. 1992. “TCP/IP Performance at Cray Research,” Proceeding of the Twenty-third Internet Engineering Task Force, pp. 492-493 (Mar.), San Diego Supercomputer Center, San Diego, Calif. Borman, D. A., ed. 1993a. “Telnet Environment Option,” RFC 1408, 7 pages (Jan.). 从客户向服务器传递环境变量的 Telnet选项。 Borman, D. A. 1993b. “ A Practical Perspective on Host Networking, ” i n Internet System Handbook, eds. D. C. Lynch and M. T. Rose, pp. 309-367. Addison-Wesley, Reading, Mass. 对于Host Requirements(主机需求)RFC(1122和1123)的一个实际的审视。

410

使用TCP/IP详解,卷1:协议

下载

Braden, R. T., ed. 1989a. “Requirements for Internet Hosts — Communication Layers, ” RFC1122, 116 pages (Oct.). 主机需求RFC的前半部分。这部分覆盖了链路层, IP,TCP和UDP。 Braden, R. T., ed. 1989b. “Requirements for Internet Hosts—Application and Support,” RFC1123, 98 pages (Oct.). 主机需求RFC的后半部分。这部分覆盖了 Telnet,FTP,TFTP,SMTP和DNS。 Braden, R. T. 1989c. “Perspective on the Host Requirements RFCs,” RFC 1127, 20 pages (Oct.). 对于开发主机需求 RFC的IETF工作组的讨论和结果的一个非正式的总结。 Braden, R. T. 1992a. “TIME-WAIT Assassination Hazards in TCP,” RFC1337, 11 pages (May). 显示处于TIME_WAIT状态时,接收一个 RST如何导致问题。 Braden, R. T. 1993b. “Extending TCP for Tr a n s a c t i o n s—C o n c e p t s ,” RFC 1379, 38 pages (Nov.). 开发T/TCP背后的概念和历史。 Braden, R. T. 1992c. “Extending TCP for Transactions—Functional Specification,” Internet Draft, 32 pages (Dec.). T/TCP的功能说明和有关实现的讨论。 Braden, R. T., Borman, D. A., and Partridge, C. 1988. “Computing the Internet Checksum,” RFC 1071, 24 pages (Sept.). 提供计算IP、ICMP、IGMP、UDP和TCP的检验和的技术和算法。 Braden, R. T., and Postel, J. B. 1987. “Requirements for Internet Gateways,” RFC1009, 55 pages (June). 等效于路由器的主机需求 RFC。这个RFC已经被代替了;见 [Almquist 1993]。 Caceres, R., Danzig, P. B., Jamin, S., and Mitzel, D. J. 1991. “Characteristics of Wi d e - A r e a TCP/IP Conversations,” Computer Communication Review , vol. 21, no. 4, pp. 101-11 2 (Sept.). Callon, R. 1992. “TCP and UDP with Bigger Addresses (TUBA), A Simple Proposal for Internet Addressing and Routing,” RFC 1347, 9 pages (June). Case, J. D., Fedor, M. S., Schoffstall, M. L., and Davin, C. 1990. “Simple Network Management (SNMP),” RFC 1157, 36 pages (May). SNMP的协议规范。 Case, J. D., McCloghrie, K., Rose, M. T., and Waldbusser, S. 1993. “An Introduction to Version 2 of the Internet-Standard Network Management Framework,” RFC 1441, 13 pages (Apr.). 一个SNMPv2的介绍和其他11个定义SNMPv2的RFC的引用。 Case, J. D., and Partridge, C. 1989. “Case Diagrams: A First Step to Diagrammed Management Information Bases,” Computer Communication Review, vol. 19, no. 1, pp. 13-16 (Jan.). 定义了用来可视化一个给定模块中 SNMP变量之间关系的图形表示。 Casne r, S., and Deering, S. E. 1992. “ F i r s t I E T F I n t e r n e t A u d i o c a s t , ” C o m p u t e r Communication Review, vol. 22, no. 3, pp. 92-97 (July).

下载

411

参 考 文 献使用

描述了怎样使用 I n t e r n e t的多播技术实时传递一个 I E T F会议的声音信息。这篇文章的一个 P o s t S c r i p t的 副本可以通过匿名 F T P从主机 v e n e r a . i s i . e d u上的文件 p u b / i e t f - a u t o c a s t a r t i c l e . p s中获得。另外,那台主机上的文件 m b o n e / f a q . t x t包含了有关 I n t e r n e t多播骨干 (MBONE)常问的问题。 Cheriton, D. P. 1988. “VMTP: Versatile Message Transaction Protocol,” RFC 1045, 123 pages (Feb.). Cheswick, W. R., and Bellovin, S. M. 1994. Firewalls and Internet Security: Repelling the Wily Hacker. Addison-Wesley, Reading, Mass. 描述了怎样建立和管理一个防火墙网关以及涉及的安全问题。 C l a rk , D. D. 1982. “Window and Acknowledgement Strategy in TCP,” RFC 813, 22 pages (July). 标识了糊涂窗口综合症以及如何避免它的原始 RFC。 C l a rk, D. D. 1988. “ The Design Philosophy of the DARPA Internet Protocols,” C o m p u t e r Communication Review, vol. 18, no. 4, pp. 106-114 (Aug.). 描述了影响 Internet协议的早期的推理技术。 Come r, D. E., and Stevens, D. L. 1993. I n t e r n e t w o r k i n g w i t h TCP/IP: Vol. III: C l i e n t - S e rv e r Programming and Applications, BSD Socket Version. Prentice-Hall, Englewood Cliffs, N.J. Cooper, A. W., and Postel, J. B. 1993. “ The US Doman,” RFC 1480, 47 pages (June). 描述了DNS的.us域。 Crocker, D. H. 1982. “Standard for the Format of ARPA Internet Text Messages,” RFC 822, 47 pages (Aug.). 定义了使用 SMTP传输的电子邮件的格式。 Crocker, D. H. 1993. “Evolving the System,” in Internet System Handbook, eds. D. C. Lynch and M. T. Rose, pp. 41-76. Addison-Wesley, Reading, Mass. 讲述了开发 ARPANET标准的一些历史和 Internet技术共同体当前结构的细节,还定义了当 前Internet标准形成的过程。 Croft, W., and Gilmore, J. 1985. “Bootstrap Protocol (BOOTP),” RFC 951, 12 pages (Sept.). Crowcroft, J., Wakeman, I., Wang, Z., and Sirovica, D. 1992. “Is Layering Harmful?,” IEEE Network, vol. 6, no. 1, pp. 20-24 (Jan.). 这篇文章中漏掉的 7张图刊登在下一期,卷 6,第2期(三月)。 Curry, D. A. 1992. U N LX System Security : A Guide for Users and System Administrators . Addison-Wesley, Reading, Mass. 一本关于Unix安全性的书。第 4章和第5章讲述网络安全。 D a l ton, C., Watson, G., Banks, D., Calamvokis, C., Edwards, A., and Lumley, J. 1993. “A f t e r- b u r n e r,” IEEE Network, vol. 7, no. 4, pp. 36-43 (July). 描述了怎样通过减少数据复制的次数来加快 TCP,以及一个支持这种设计的专用接口卡。 Danzig, P. B., Obraczka, K., and Kumar, A. 1992. “An Analysis of Wide-Area Name Server Tra ffic,” Computer Communication Review, vol. 22, no. 4, pp. 281-292 (Oct.). 对根名字服务器之一进行的两个 2 4小时流量监视的分析。显示来自于一个故障实现的

412

使用TCP/IP详解,卷1:协议

下载

D N S流量如何消耗了 2 0倍于正常的 WA N带宽。这篇文章的一个 P o s t S c r i p t副本通过匿名 FTP在主机caldera.usc.edu的文件pub/danzig/dns.ps.z中提供。 Deering, S. E. 1989. “Host Extensions for IP Multicasting,” RFC 1112, 17 pages (Aug.). IP多播和IGMP的规范。 Deering, S. E., ed. 1991. “ICMP Router Discovery Messages,” RFC 1256, 19 pages (Sept.). Deering, S. E., and Cheriton, D. P. 1990. “Multicast Routing in Datagram Internetworks and Extended LANs,” ACM Transactions on Computer Systems, vol. 8, no. 2, pp. 85-110 (May). 对于常用路由技术的支持多播传输的扩充。 Dixon, T. 1993. “Comparison of Proposals for Next Version of IP,” RFC 1454, 15 pages (May). SIP, PIP 和TUBA的比较和总结。 Droms, R. 1993. “Dynamic Host Configuration Protocol,” RFC 1541, 39 pages (Oct.). D r oms, R., and Dyksen, W. R. 1990. “Performance Measurements of the X Window System C o m m u n i c a t ion Protocol,” Software Practice & Experience, vol. 20, pp. 119-136 (Oct.). 使用不同的 X客户时涉及到的 TCP通信的测量。 Fedor, M. S. 1988. “GATED: A Multi-routing Protocol Daemon for Unix,” Proceeding of the 1988 Summer USENIX Conference, pp. 365-376, San Francisco, Calif. Finlayson, R. 1984. “Bootstrap Loading using TFTP,” RFC 906, 4 pages (June). Finlayson, R., Mann, T., Mogul, J. C., and Theimer, M. 1984. “ A Reverse Address Resolution Protocol,” RFC 903, 4 pages (June). Floyd, S. 1994. Private Communication. Ford, P. S., Rekhter, Y., and Braun, H-W. 1993. “Improving the Routing and Addressing of IP,” IEEE Network, vol. 7, no. 3, pp. 10-15 (May). 对于CIDR(无类型域间路由)的描述。 Fuller, V., Li, T., Yu, J.Y., and Varadhan, K. 1993. “Classless Inter-Domain Routing (CIDR): An Address Assignment and Aggregation Strategy,” RFC 1519, 24 pages (Sept.). CIDR(无类型域间选路)的规范。 Gerich, E. 1993. “Guidelines for Management of IP Address Space,” RFC 1466, 10 pages (May). 将来怎样分配 I P地址的说明(即 B类地址将很难获得,作为替代手段,一般将分配一组 C 类地址)。 Gurwitz, R., and Hinden, R. 1982. “IP—Local Area Network Addressing Issues,” IEN 212, 11 pages (Sept.)。 对IP广播地址最早的引用之一。 Harrenstein, K., Stahl, M. K., and Feinler, E. J. 1985. “NICNAME/WHOIS,” RFC 954, 4 pages (OCT.). Hedrick, C. L. 1988a. “Routing Information Protocol,” RFC 1058, 33 pages (June). Hedrick, C. L. 1988b. “Telnet Terminal Speed Option,” RFC 1079, 3 pages (Dec.). Hedrick, C. L., and Borman, D. A. 1992. “Telnet Remote Flow Control Option,” RFC 1372, 6 pages (Oct.).

下载

413

参 考 文 献使用

Hornig, C. 1984. “Standard for the Transmission of IP Datagrams over Ethernet Networks,” RFC 894, 3 pages (Apr.). Huitema, C. 1993. “IAB Recommendation for an Intermediate Strategy to Address the Issue of Scaling,” RFC 1481, 2 pages (July). 实现CIDR的IAB建议。 Jacobson, V. 1988. “Congrestion Avoidance and Control,” Computer Communication Review, vol. 18, no. 4, pp. 314-329 (Aug.). 描述T C P的慢启动和拥塞避免算法的一篇经典文章。这篇文章的一个 P o s t S c r i p t副本通过 匿名FTP在主机ftp.ee.lbl.gov的文件congavoid.ps.z中提供。 Jacobson, V. 1990a. “Compressing TCP/IP Headers for Low-Speed Serial Links,” RFC 1144, 43 pages (Feb.). 描述CSLIP, 一个压缩TCP和IP首部的SLIP版本。 Jacobson, V. 199b. “Modified TCP Congestion Avoidance Algorithm, ” April 30, 1990, end2end-interest mailing list (Apr.). 描述了快速重传和快速恢复算法。 Jacobson, V. 1990c. “Berkeley TCP Evolution from 4.3-Tahoe to 4.3-Reno,” Proceeding of the Eighteenth Internet Engineering Task Force, p. 365 (Sept.), University of British Columbia, Vancouver, B.C. Jacobson, V., and Braden, R. T. 1988. “TCP Extensions for Long-Delay Paths,” RFC 1072, 16 pages (Oct.). 描述了TCP的选择确认选项,在后来的 RFC 1323中这个选项已经被删去了。 Jacobson, V. Braden, R. T., and Borman, D. A. 1992. “TCP Extensions for High Performance,” RFC 1323, 37 pages (May). 描述了窗口缩放选项、时间戳选项和 PAWS算法,以及需要这些改变的理由。 Jacobson, V., Braden, R. T., and Zhang, L. 1990. “TCP Extensions for High-Speed Paths,” RFC 1185, 21 pages (Oct.). 尽管这个 R F C已经被 RFC 1323 废止了,但有关防止 T C P旧的重复的报文段的附录仍然值 得一读。 Juszczak, C. 1989. “Improving the Performance and Correctness of an NFS Server, ” Proceedings of the 1989 Winter USENIX Conference, pp. 53-63, San Diego, Calif. 提供了NFS服务器快速缓存的实现细节。 Kantor, B. 1991. “BSD Rlogin,” RFC 1282, 5 pages (Dec.). Rlogin协议的规范。 Karn, P., and Partridge, C. 1987. “Improving Round-Trip Time Estimates in Reliable Transport Protocols,” Computer Communication Review, vol. 17, no. 5, pp. 2-7 (Aug.). 处理已经重传报文段的重传超时的 K a r n算法的细节。这篇文章的一个 P o s t S c r i p t副本通过 匿名FTP在主机sics.se的文件pub/craig/karn-partridge.ps中提供。 Katz, D. 1990. “Proposed Standard for the Transmission of IP Datagrams Over FDDI Networks,” RFC 1188, 11 pages (Oct.).

414

使用TCP/IP详解,卷1:协议

下载

说明了在FDDI网络上如何封装 IP数据报和ARP请求和应答,包括多播技术。 Kent, C. A., and Mogul, J. C. 1987. “Fragmentation Considered Harmful, ” C o m p u t e r Communication Review, vol. 17, no. 5, pp. 390-401 (Aug.). Kent, S. T. 1991. “ U.S. Department of Defense Security Options for the Internet Protocol,” RFC 1108, 17 pages (Nov.). Kleinrock, L. 1992. “The Latency / Bandwidth T r a d e o ff in Gigabit Networks, ” I E E E Communications Magazine, vol. 30, no. 4, pp. 36-40 (Apr.). Klensin, J., Freed, N., and Moore, K. 1993. “SMTP Service Extension for Message Size Declaration,” RFC 1427, 8 pages (Feb.). Klensin, J., Freed, N., Rose, M. T., Stefferud, E. A., and Crocker, D. 1993a. “SMTP Service Extensions,” RFC 1425, 10 pages (Feb.). Klensin, J., Freed, N., Rose, M. T., Stefferud, E. A., and Crocker, D. 1993b. “SMTP Service Extension for 8bit-MIME Transport,” RFC 1426, 6 pages (Feb.). Krol, E. 1992. The Whole Internet. O’Reilly & Associates, Sebastopol, Calif. 关于Internet介绍的入门书。 LaQuey, T. 1993. The Internet Companion: A Beginner’s Guide to Global Networking. AddisonWesley, Reading, Mass. 关于Internet介绍的入门书。 L e ff le r, S. J., and Karels, M. J. 1984. “Trailer Encapsulations,” RFC 893, 3 pages (Apr.). Leffle r, S. J., McKusick, M. K., Karels, M. J., and Quarterman, J. S. 1989. The Design and Implementation of the 4.3BSD UNIX Operating System. Addison-Wesley, Mass. 一本完整介绍4.3BSD Unix系统的书。这本书描述了 Tahoe版的4.3BSD。 Lougheed, K., and Rekhter, Y. 1991. “A Border Gateway Protocol 3 (BGP-3),” RFC 1267, 35 pages (Oct.). Lynch, D. C. 1993. “Historical Perspective,” in Internet System Handbook, eds. D. C. Lynch and M. T. Rose, pp. 3-14. Addison-Wesley, Reading, Mass. 描述了Internet的前身:ARPANET。 Macklem, R. 1991. “Lessons Learned Tuning the 4.3BSD Reno Implementation of the NFS Protocol,” Proceedings of the 1991 Winter USENIX Conference, pp. 53-64, Dallas, Tex. 描述了一个同时使用 TCP和UDP的NFS实现。 Malkin, G. S. 1993a. “RIP Version 2: Carrying Additional Information.” RFC 1388, 7 pages (Jan.). Malkin, G. S. 1993b. “Traceroute Using an IP Option,” RFC 1393, 7 pages (Jan.). 建议用于traceroute新版本的ICMP修改。 Mallory, T., and Kullberg, A. 1990. “Incremental Updating of the Internet Checksum,” RFC 1141, 2 pages (Jan.). 描述了递增改变 Internet检验和的实现技术。 Manber, U. 1990. “Chain Reactions in Networks,” IEEE Computer, vol. 23, no. 10, pp. 57-63 (Oct.).

下载

415

参 考 文 献使用

描述了广播风暴和网络崩溃的类型,类似于习题 9.3和9.4所显示的。 McCanne, S., and Jacobson, V. 1993. “The BSD Packet Filter: A New Architecture for UserLevel Packet Capture,” Proceeding of the 1993 Winter USENIX Conference. Pp. 259-269, San Diego, Calif. B S D分组过滤器( B P F)的具体描述以及与 S u n的网络接口栓( N I T)的比较。这篇文章 的一个PostScript副本通过匿名 FTP在主机f t p . e e . l b l . g o v的文件p a p e r s / b p f - u s e n i x 9 3 . p s . Z 中提供。 McCloghrie, K., and Rose, M. T. 1991. “Management Information Base for Network Management of TCP/IP-based Internets: MIB-II,” RFC 1213 (Mar.). McGr e g o r, G. 1992. “PPP Internet Protocol Control Protocol (IPCP),” RFC 1332, 12 pages (May). 用于TCP/IP的PPP的NCP的描述。 Mills, D. L. 1992. “Network Time Protocol (Version 3): Specification, Implementation, and Analysis,” RFC 1305, 113 pages (Mar.). Mockapetris, P. V. 1987a. “Domain Names: Concepts and Facilities,” RFC 1034, 55 pages (Nov.). 一个DNS的介绍。 Mockapetris, P. V. 1987b. “Domain Names: Implementation and Specification,” RFC 1035, 55 pages (Nov.). DNS规范。 Mogul, J. C. 1990. “E fficient Use of Workstations for Passive Monitoring of Local Area Networks,” Computer Communication Review, vol. 20, no. 4, pp. 253-263 (Sept.). 描述了如何使用工作站监视局域网,而不购买专门的网络分析硬件产品。 Mogul, J. C. 1992. “Holy Turbocharger Batman, (evil cheating), NFS async writes,” MessageID [email protected], Usenet, comp.protocols.nfs Newsgroup (Mar.). 在一个繁忙的NFS服务器上收集的, 40天内Internet检验和错误的一些有趣的统计数据。 Mogul, J. C. 1993. “IP Network Performance,” in Internet System Handbook, eds. D. C. Lynch and M. T. Rose, pp. 575-675. Addison-Wesley, Reading, Mass. 包含了许多 TCP/IP协议栈的主题,它们可以用来获得最优的性能。 Mogul, J. C., and Deering, S. E. 1990. “Path MTU Discovery,” RFC 1191, 19 pages (Apr.). Mogul, J. C., and Postel, J. B. 1985. “Internet Standard Subnetting Procedure,” RFC 950, 18 pages (Aug.). Moore, K. 1993. “MIME (Multipurpose Internet Mail Extensions) Part Two: Message Header Extensions for Non-ASCII Text,” RFC 1522, 10 pages (Sept.). 描述了使用 7 bit ASCII在RFC 822邮件首部发送非 ASCII字符的一种方法。 Moy, J. 1991. “OSPF Version 2,” RFC 1247, 189 pages (July). Nagle, J. 1984. “Congestion Control in IP/TCP Internetworks,” RFC 896, 9 pages(Jan.). Nagle算法的描述。 N y e , A., ed. 1992. The X Window System, Volume 0: X Protocol Reference Manual, Third

416

使用TCP/IP详解,卷1:协议

下载

E d i t i o n. O’Reilly & Associates, Sebastopol, Calif. Obracezka, K., Danzig, P. B., and Li, S. 1993. “Internet Resource Discovery Services,” IEEE Computer, vol. 26, no. 9, pp. 8-22 (Sept.). 对当前Internet上资源发现工具的一个综述:Alex、Archie、Gopher、Indie、Knowbot信息服 务、Netfind、Prospero、WAIS、WWW和X.500。这篇文章的一个 PostScript副本通过匿名 FTP在主机caldera.usc.edu的文件/pub/kobraczk/ieeecomputer.ps.z中提供。 Papadopoulos, C., and Parulkar, G. M. 1993. “Experimental Evaluation of SunOS IPC and TCP/IP Protocol Implementation,” IEEE/ACM Transactions on Networking, vol. 1, no. 2, pp. 199-216 (Apr.). 测量当发送数据和接收数据时在协议族的不同层中引入的开销。 Partridge, C. 1986. “Mail Routing and the Domain System,” RFC 974, 7 pages (Jan.). 怎样使用DNS的MX记录进行邮件的选路。 Partridge, C. 1994. Gigabit Networking. Addison-Wesley, Reading, Mass. 描述当网络速率超过 1 Gb/s时产生的问题。 Partridge, C., and Pink, S. 1993. “A Faster UDP.” IEEE/ACM Transactions on Networking, Vol. 1, no. 4, pp. 429-440 (Aug.). 描述了对伯克利实现的改进,可以将 UDP的性能提高 30%。 Paxson, V. 1993. “Empirically-Derived Analytic Models of Wide-Area TCP Connections: Extended Report, ” LBL-34086, Lawrence Berkeley Laboratory and EECS Division, University of California, Berkeley (June). 包含了在 14个广域流量跟踪中 250万个TCP连接的分析。这个报告的一个 PostScript副本通 过匿名 F T P在主机 f t p . e e . l b l . g o v的文件W A N - T C P - m o d e l s . 1 . p s . Z和W A N - T C P models.2.ps.Z中提供。 Perlman, R. 1992. Interconnections: Bridges and Routers. Addison-Wesley, Reading, Mass. 一本包含了许多详细的网络互连方法(桥和路由器)和不同的路由算法的书。 Plummer, D. C. 1982. “An Ethernet Address Resolution Protocol,” RFC 826, 10 pages (Nov.). Postel, J. B. 1980. “User Datagram Protocol,” RFC 768, 3 pages (Aug.). Postel, J. B., ed. 1981a. “Internet Protocol,” RFC 791, 45 pages (Sept.). Postel, J. B. 1981b. “Internet Control Message Protocol,” RFC 792, 21 pages (Sept.). Postel, J. B., ed. 1981c. “Transmission Control Protocol,” RFC 793, 85 pages (Sept.). Postel, J. B. 1982. “Simple Mail Transfer Protocol,” RFC 821, 68 pages (Aug.). Postel, J. B. 1987. “TCP and IP Bake Off,” RFC 1025, 6 pages (Sept.). 描述了在早期开发过程中,为了测试互连性在 T C P / I P的不同实现之间进行的一些测试过 程和记录。 Postel, J. B., ed. 1994. “Internet Official Protocol Standards,” RFC 1600, 36 pages (Mar.). 所有的 I n t e r n e t协议的状态。这个 R F C定期改变—查看最近的 R F C索引以了解当前的版 本。 Postel, J. B., and Reynolds, J. K. 1983a. “Telnet Protocol Specification,” RFC 854, 15 pages (May).

下载

417

参 考 文 献使用

基本的Telnet协议规范。很多以后的 RFC描述了特定的Telnet选项。 Postel, J. B., and Reynolds, J. K. 1983b. “Telnet Binary Tr a n s m i s s i o n ,” RFC 856, 4 pages (May). Postel, J. B., and Reynolds, J. K. 1983c. “Telnet Echo Option,” RFC 857, 5 pages (May). Postel, J. B., and Reynolds, J. K. 1983d. “Telnet Suppress Go Ahead Option,” RFC 858, 3 pages (May). Postel, J. B., and Reynolds, J. K. 1983e. “Telnet Status Option,” RFC 859, 3 pages (May). Postel, J. B., and Reynolds, J. K. 1983f. “Telnet Timing Mark Option,” RFC 860, 4 pages (May). Postel, J. B., and Reynolds, J. K. 1985. “File Transfer Protocol (FTP),” RFC 959, 69 pages (Oct.). Postel, J. B., and Reynolds, J. K. 1988. “Standard for the Transmission of IP Datagrams over IEEE 802 Networks,” RFC 1042, 15 pages (Apr.). 在IEEE 802网络上封装 IP数据报和ARP请求和应答的规范。 Pusateri, T. 1993. “IP Multicast Over Token-Ring Local Area Networks,” RFC 1469, 4 pages (June). Rago, S. A. 1993. UNIX System V Network Programming. Addison-Wesley, Reading, Mass. 一本关于TLI和流子系统的书。 Rekhter, Y. and Gross, P. 1991. “Application of the Border Gateway Protocol in the Internet,” RFC 1268, 13 pages (Oct.). Rekhter, Y., and Li, T. 1993. “An Architecture for IP Address Allocation with CIDR,” RFC 1518, 27 pages (Sept.). Reynolds, J. K. 1989. “The Helminthiasis of the Internet,” RFC 1135, 33 pages (Dec.). 包含了1988年Internet蠕虫的详细讨论。 Reynolds, J. K., and Postel, J. B. 1992. “Assigned Numbers,” RFC 1340, 138 pages (July). I n t e r n e t协议族所有的编码。这个 R F C定期改变—查看最近的 R F C索引以了解当前的版 本。 Romk e y, J. L. 1988. “A Nonstandard for Transmission of IP Datagrams Over Serial Lines: SLIP,” RFC 1055, 6 pages (June). Rose, M. T. 1990. The Open Book: A Practical Perspective on OSI. Prentice-Hall, Englewood Cliffs, N.J. 一本关于OSI协议的书。第8章提供了ASN.1和BER的细节。 Rose, M. T. 1993. The Internet Message: Closing the Book with Electronic Mail. Prentice-Hall, Englewood Cliffs, N.J. 一本关于Internet邮件的书,包含 MIME的细节。 Rose, M. T. 1994. The Simple Book: An Introduction to Internet Management, Second Edition. Prentice-Hall, Englewood Cliffs, N.J. 一本关于SNMPv2的书。这本书的第 1版介绍了SNMPv1。 Rose, M. T., and McCloghrie, K. 1990. “Structure and Identification of Management

418

使用TCP/IP详解,卷1:协议

下载

Information for TCP/IP-based Internets,” RFC 1155, 22 pages (May). 定义了SNMPv1的SMI。 Rosenberg, W., Kenney, D., and Fisher, G. 1992. Understanding DCE. O’Reilly & Associates, Sebastopol, Calif. 提供了OSF的分布式计算环境的概述。 R o u th i e r, S. A. 1993. “Implementation Experience for SNMPv2,” The Simple Times, vol. 2, no. 4, pp. 1-4 (July-Aug.). 描述了对SNMPv1的修改以支持SNMPv2。 这个期刊是免费电子发行的。以主题“ help”向s t - s u b s c r i p t i o n s @ s i m p l e - t i m e s . o r g发送 一个电子邮件以获得订阅信息。 Schry v e r, V. J. 1993. “Info on High Speed Transport Protocols Requested, ” M e s s a g e - I D [email protected], Usenet, comp.protocols.tcp-ip Newsgroup (May). 提供了一些 FDDI实现上的TCP性能数字。 Schwartz, M. F., and Tsirigotis, P. G. 1991. “Experience with a Semantically Cognizant Internet White Pages Directory Tool,” Journal of Internetworking Research and Experience, vol. 2, no. 1, pp. 23-50 (Mar.). 也可以通过匿名 F T P 在主机 f t p . c s . c o l o r a d o . e d u的文件 p u b / c s / t e c h r e p o r t s / s c h w a r t z / PostScript/ White.Pages.ps.Z中得到。 Simpson, W. A. 1993. “The Point-to-Point Protocol (PPP),” RFC 1548, 53 pages (Dec.). 定义了PPP和它的链路控制协议。 Sollins, K. R. 1992. “The TFTP Protocol (Revision 2),” RFC 1350, 11 pages (July). Stallings, W. 1987. Handbook of Computer-Communications Standards, Volume 2: Local Network Standards. Macmillan, New York. 包含了IEEE 802 局域网标准的细节。 Stallings, W. 1993. SNMP, SNMPv2, and CMIP: The Practical Guide to Network-Management Standards. Addison-Wesley, Reading, Mass. 描述了SNMPv1和SNMPv2的差别。 Stern, H. 1991. Managing NFS and NIS. O’Reilly & Associates, Sebastopol, Calif. 包含了许多安装、使用和管理 NFS的细节。 Stevens, W. R. 1990. UNIX Network Programming. Prentice-Hall, Englewood Cliffs, N.J. 一本详细介绍在 Unix上使用插口和TLI进行网络程序设计的书。 Stevens, W. R. 1992. Advanced Programming in the UNIX Environment. Addison-Wesley, Reading, Mass. 一本详细介绍Unix程序设计的书。 Sun Microsystems. 1987. “XDR: External Data Representation Standard,” RFC 1014, 20 pages (June). Sun Microsystems. 1988a. “RFC: Remote Procedure Call, Protocol Specification, Version 2,” RFC 1057, 25 pages (June). Sun Microsystems. 1988b. “NFS: Network File System Protocol Specification,” RFC 1094, 27 pages

下载

419

参 考 文 献使用

(Mar.). 第2版Sun NFS的规范。 Sun Microsystems. 1994. N F S: Network File System Version 3 Protocol Specification . Sun Microsystems, Mountain View, Calif. 这个文档的一个 P o s t S c r i p t副本通过匿名 F T P 在主机 f t p . u u . n e t的文件 n e t w o r k i n g / i p / nfs/NFS3.spec.ps.Z中提供。 Tanenbaum, A. S. 1989. Computer Networks, Second Edition. Prentice-Hall, Englewood Cliffs, N.J. 一本关于计算机网络的通用书。 Topolcic, C. 1993. “Status of CIDR Deployment in the Internet,” RFC 1467, 9 pages (Aug.). Tsuchiya, P. F. 1991. “On the Assignment of Subnet Numbers,” RFC 1219, 13 pages (Apr.). 建议从最高位往下分配子网 I D,从最低位往上分配主机 I D。这样使得以后在某个点上改 变子网掩码时不需要对所有的系统重新编号。 Ullmann, R. 1993. “TP/IX: The Next Internet,” RFC 1475, 35 pages (June). 下一代Internet协议的另一个建议。 VanBokkelen, J. 1989. “Telnet Terminal-Type Option,” RFC 1091, 7 pages (Feb.). Waitzman, D. 1988. “Telnet Window Size Option,” RFC 1073, 4 pages (Oct.). Waitzman, D., Partridge, C., and Deering, S. E. 1988. “Distance Vector Multicast Routing Protocol,” RFC 1075, 24 pages (Nov.). Warnock, R. P. 1991. “Need Help Selecting Ethernet Cards for Very High Performance Throughput Rates, ”Message-ID< [email protected]> Usenet, comp.protocols.tcp-ip Newsgroup (Sept.). 提供了我们从图 24-9计算的TCP的性能数据。 Weide r, C., Reynolds, J. K., and Heker, S. 1992. “Technical Overview of Directory Services Using the X.500 Protocol,” RFC 1309, 16 pages (Mar.). Wime r, W. 1993. “Clarifications and Extensions for the Bootstrap Protocol,” RFC 1542, 23 pages (Oct.). X/Open. 1991. Protocols for X/Open Internetworking: XNFS. X/Open, Reading, Berkshire, U.K. 对Sun RPC、XDR和NFS的一个更好的描述。还包含了 NFS锁定管理程序和状态监视协议 的描述。附录中详细讨论了使用 N F S和使用本地文件访问之间的语义差别。 X/ O p e n文档 编号为XO/CAE/91/030。 Zimmerman, D. P. 1991. “Finger User Information Protocol,” RFC 1288, 12 pages (Dec.)

下载

缩 略 语 ACK (ACKnowledgment) TCP首部中的确认标志 API (Application Programming Interface) 应用编程接口 ARP (Address Resolution Protocol) 地址解析协议 ARPANET (Defense Advanced Research Project Agency NETwork) (美国)国防部远景研究规划局 AS (Autonomous System) 自治系统 ASCII (American Standard Code for Information Interchange) 美国信息交换标准码 ASN.1 (Abstract Syntax Notation One) 抽象语法记法1 BER (Basic Encoding Rule) 基本编码规则 BGP (Border Gateway Protocol) 边界网关协议 BIND (Berkeley Internet Name Domain) 伯克利Internet域名 BOOTP (BOOTstrap Protocol) 引导程序协议 BPF (BSD Packet Filter) BSD 分组过滤器 CIDR (Classless InterDomain Routing) 无类型域间选路 CIX (Commercial Internet Exchange) 商业互联网交换 CLNP (ConnectionLess Network Protocol) 无连接网络协议 CRC (Cyclic Redundancy Check) 循环冗余检验 CSLIP (Compressed SLIP) 压缩的SLIP CSMA (Carrier Sense Multiple Access) 载波侦听多路存取 DCE (Data Circuit-terminating Equipment) 数据电路端接设备 DDN (Defense Data Network) 国防数据网 DF (Don’t Fragment) IP首部中的不分片标志 DHCP (Dynamic Host Configuration Protocol) 动态主机配置协议 DLPI (Data Link Provider Interface) 数据链路提供者接口 DNS (Domain Name System) 域名系统 DSAP (Destination Service Access Point) 目的服务访问点 DSLAM (DSL Access Multiplexer) 数字用户线接入复用器 DSSS (Direct Sequence Spread Spectrum) 直接序列扩频 DTS (Distributed Time Service) 分布式时间服务 DVMRP (Distance Vector Multicast Routing Protocol) 距离向量多播选路协议 EBONE (European IP BackbONE) 欧洲IP主干网 EOL (End of Option List) 选项清单结束 EGP (External Gateway Protocol) 外部网关协议 EIA (Electronic Industries Association) 美国电子工业协会

下载

421

缩 略 语使用

FCS (Frame Check Sequence) 帧检验序列 FDDI (Fiber Distributed Data Interface) 光纤分布式数据接口 FIFO (First In, First Out) 先进先出 FIN (FINish) TCP首部中的结束标志 FQDN (Full Qualified Domain Name) 完全合格的域名 FTP (File Transfer Protocol) 文件传送协议 HDLC (High-level Data Link Control) 高级数据链路控制 HELLO 选路协议 IAB (Internet Architecture Board) Internet体系结构委员会 IANA (Internet Assigned Numbers Authority) Internet号分配机构 ICMP (Internet Control Message Protocol) Internet控制报文协议 IDRP (InterDomain Routing Protocol) 域间选路协议 IEEE (Institute of Electrical and Electronics Engineering) (美国)电气与电子工程师协会 IEN (Internet Experiment Notes) 互联网试验注释 IESG (Internet Engineering Steering Group) Internet工程指导小组 IETF (Internet Engineering Task Force) Internet工程专门小组 IGMP (Internet Group Management Protocol) Internet组管理协议 IGP (Interior Gateway Protocol) 内部网关协议 IMAP (Internet Message Access Protocol) Internet报文存取协议 IP (Internet Protocol) 网际协议 IRTF (Internet Research Task Force) Internet研究专门小组 IS-IS (Intermediate System to Intermediate System Protocol) 中间系统到中间系统协议 ISN (Initial Sequence Number) 初始序号 ISO (International Organization for Standardization) 国际标准化组织 ISOC (Internet SOCiety) Internet协会 LAN (Local Area Network) 局域网 LBX (Low Bandwidth X) 低带宽X LCP (Link Control Protocol) 链路控制协议 LFN (Long Fat Net) 长肥网络 LIFO (Last In, First Out) 后进先出 LLC (Logical Link Control) 逻辑链路控制 LSRR (Loose Source and Record Route) 宽松的源站及记录路由 MBONE (Multicast Backbone On the InterNEt) Internet上的多播主干网 MIB (Management Information Base) 管理信息库 MILNET (MILitary NETwork) 军用网 MIME (Multipurpose Internet Mail Extensions) 通用Internet邮件扩充 MSL (Maximum Segment Lifetime) 报文段最大生存时间 MSS (Maximum Segment Size) 最大报文段长度 MTA (Message Transfer Agent) 报文传送代理

422

使用TCP/IP详解,卷1:协议

MTU (Maximum Transmission Unit) 最大传输单元 NCP (Network Control Protocol) 网络控制协议 NFS (Network File System) 网络文件系统 NIC (Network Information Center) 网络信息中心 NIT (Network Interface Tap) 网络接口栓(Sun公司的一个程序) NNTP (Network News Transfer Protocol) 网络新闻传送协议 NOAO (National Optical Astronomy Observatories) 国家光学天文台 NOP (No Operation) 无操作 NSFNET (National Science Foundation NETwork) 国家科学基金网络 NSI (NASA Science Internet) (美国)国家宇航局 Internet NTP (Network Time Protocol) 网络时间协议 NVT (Network Virtual Terminal) 网络虚拟终端 OSF (Open Software Foudation) 开放软件基金 OSI (Open Systems Interconnection) 开放系统互连 OSPF (Open Shortest Path First) 开放最短通路优先 PAWS (Protection Against Wrapped Sequence number) 防止回绕的序号 PDU (Protocol Data Unit) 协议数据单元 POSIX (Portable Operating System Interface) 可移植操作系统接口 PPP (Point-to-Point Protocol) 点对点协议 PSH (PuSH) TCP首部中的急迫标志 RARP (Reverse Address Resolution Protocol) 逆地址解析协议 RFC (Request For Comments) Internet的文档,其中的少部分成为标准文档 RIP (Routing Information Protocol) 路由信息协议 RPC (Remote Procedure Call) 远程过程调用 RR (Resource Record) 资源记录 RST (ReSeT) TCP首部中的复位标志 RTO (Retransmission Time Out) 重传超时 RTT (Round-Trip Time) 往返时间 SACK (Selective ACKnowledgment) 有选择的确认 SLIP (Serial Line Internet Protocol) 串行线路Internet协议 SMI (Structure of Management Information) 管理信息结构 SMTP (Simple Mail Transfer Protocol) 简单邮件传送协议 SNMP (Simple Network Management Protocol) 简单网络管理协议 SSAP (Source Service Access Point) 源服务访问点 SSRR (Strict Source and Record Route) 严格的源站及记录路由 SWS (Silly Window Syndrome) 糊涂窗口综合症 SYN (SYNchronous) TCP首部中的同步序号标志 TCP (Transmission Control Protocol) 传输控制协议 TFTP (Trivial File Transfer Protocol) 简单文件传送协议

下载

下载 TLI (Transport Layer Interface) 运输层接口 TTL (Time-To-Live) 生存时间或寿命

TUBA (TCP and UDP with Bigger Addresses) 具有更长地址的 TCP和UDP Telnet 远程终端协议 UA (User Agent) 用户代理 UDP (User Datagram Protocol) 用户数据报协议 URG (URGent) TCP首部中的紧急指针标志 UTC (Coordinated Universal Time) 协调的统一时间 UUCP (Unix-to-Unix CoPy) Unix到Unix的复制 WAN (Wide Area Network) 广域网 WWW (World Wide Web) 万维网 XDR (eXternal Data Representation) 外部数据表示 XID (transaction ID) 事务标识符 XTI (X/Open Transport Layer Interface) X/Open运输层接口

423

缩 略 语使用

第1章 概



1.1 引言 本章介绍伯克利 ( B e r k e l e y )联网程序代码。开始我们先看一段源代码并介绍一些通篇要用 的印刷约定。对各种不同代码版本的简单历史回顾让我们可以看到本书中的源代码处于什么 位置。接下来介绍了两种主要的编程接口,它们在 Unix与非Unix系统中用于编写 TCP/IP协议。 然后我们介绍一个简单的用户程序,它发送一个 U D P数据报给一个位于另一主机上的日 期/时间服务器,服务器返回一个 U D P数据报,其中包含服务器上日期和时间的 A S C I I码字符 串。这个进程发送的数据报经过所有的协议栈到达设备驱动器,来自服务器的应答从下向上 经过所有协议栈到达这个进程。通过这个例子的这些细节介绍了很多核心数据结构和概念, 这些数据结构和概念在后面的章节中还要详细说明。 本章的最后介绍了在本书中各源代码的组织,并显示了联网代码在整个组织中的位置。

1.2 源代码表示 不考虑主题,列举 15 000 行源代码本身就是一件难事。下面是所有源代码都使用的文本 格式:

1.2.1 将拥塞窗口设置为 1 387-388 这是文件tcp_subr.c中的函数tcp_quench。这些源文件名引用 4.4BSD-Lite发 布的文件。 4 . 4 B S D在1 . 1 3节中讨论。每个非空白行都有编号。正文所描述的代码的起始和结 束位置的行号记于行开始处,如本段所示。有时在段前有一个简短的描述性题头,对所描述 的代码提供一个概述。 这些源代码同 4 . 4 B S D - L i t e发行版一样,偶尔也包含一些错误,在遇到时我们会提出来并 加以讨论,偶尔还包括一些原作者的编者评论。这些代码已通过了 G N U缩进程序的运行,使 它们从版面上看起来具有一致性。制表符的位置被设置成 4个栏的界线使得这些行在一个页面 中显示得很合适。在定义常量时,有些 # i f d e f语句和它们的对应语句 # e n d i f被删去 (如: G A T E W A Y和M R O U T I N G,因为我们假设系统被作为一个路由器或多播路由器 )。所有register说

2

计计TCP/IP详解 卷 2:实现

明符被删去。有些地方加了一些注释,并且一些注释中的印刷错误被修改了,但代码的其他 部分被保留下来。 这些函数大小不一,从几行 (如前面的 t c p _ q u e n c h)到最大11 0 0行(t c p _ i n p u t)。超过 大约4 0行的函数一般被分成段,一段一段地显示。虽然尽量使代码和相应的描述文字放在同 一页或对开的两页上,但为了节约版面,不可能完全做到。 本书中有很多对其他函数的交叉引用。为了避免给每个引用都添加一个图号和页码,书 封底内页中有一个本书中描述的所有函数和宏的字母交叉引用表和描述的起始页码。因为本 书的源代码来自公开的 4 . 4 B S D _ L i t e版,因此很容易获得它的一个拷贝:附录 B详细说明了各 种方法。当你阅读文章时,有时它会帮助你搜索一个在线拷贝 [例如Unix程序grep (1)]。 描述一个源代码模块的各章通常以所讨论的源文件的列表开始,接着是全局变量、代码 维护的相关统计以及一个实际系统的一些例子统计,最后是与所描述协议相关的 S N M P变量。 全局变量的定义通常跨越各种源文件和头文件,因此我们将它们集中到的一个表中以便于参 考。这样显示所有的统计,简化了后面当统计更新时对代码的讨论。卷

1的第 2 5章提供了

S N M P的所有细节。我们在本文中关心的是由内核中的 T C P / I P例程维护的、支持在系统上运 行的SNMP代理的信息。 1.2.2 印刷约定 通篇的图中,我们使用一个等宽字体表示变量名和结构成员名 (m _ n e x t),用斜体等宽字 体表示定义常量 ( N U L L)或常量的值 (5 1 2)的名称,用带花括号的粗体等宽字体表示结构名称 (mbuf{})。这里有一个例子:

在表中,我们使用等宽字体表示变量名称和结构成员名称,用斜体等宽字体表示定义的 常量。这里有一个例子: m_flags M_BCAST





以链路层广播发送 /接收

通常用这种方式显示所有的 # d e f i n e符号。如果必要,我们显示符号的值 (M _ B C A S T的 值无关紧要 )并且所列符号按字母排序,除非对顺序有特殊要求。 通篇我们会使用像这样的缩进的附加说明来描述历史的观点或实现的细节。 我们用有一个数字在圆括号里的命令名称来表示 Unix命令,如 g r e p(1)。圆括号中的数字 是4.4BSD手册“manual page”中此命令的节号,在那里可以找到其他的信息。

1.3 历史 本书讨论在伯克利的加利福尼亚大学计算机系统研究组的 T C P / I P实现的常用引用。历史 上,它曾以 4.x BSD系统(伯克利软件发行 )和“B S D联网版本”发行。这个源代码是很多其他 实现的起点,不论是 Unix或非Unix操作系统。 图1 - 1显示了各种 B S D版本的年表,包括重要的 T C P / I P特征。显示在左边的版本是公开可

第1章 概

3

述计计

用源代码版,它包括所有联网代码:协议本身、联网接口的内核例程及很多应用和实用程序 (如Telnet和FTP)。 4.2BSD (1983) 第一次被广泛应用的 TCP/IP版本

4.3BSD (1996) 改进了TCP性能

4.3BSD Tahoe (1988) 慢启动,防拥塞,快速 重传 BSD联网软件 版本1.0(1989):Net/1

BSD联网软件 版本2.0(1991):Net/2

4.3BSD Reno (1990) 快速恢复, T C P首部预 测,SLIP首部压缩,路 由表改变

4.4BSD(1993) 多播,长肥管道的修改

4.4BSD-Lite(1994) 在正文中用Net/3表示

图1-1 带有重要 TCP/IP特征的各种 BSD版本

虽然本文描述的软件的官方名称为 4.4BSD-Lite发行软件,但我们简单地称它为 Net/3。 虽然源代码由 U. C. Berkeley发行并被称为伯克利软件发行,但 T C P / I P代码确实是各种研 究者的工作的融合,包括伯克利和其他地区的研究人员。 通篇我们会使用术语源于伯克利的实现来谈及各厂商的实现,如 SunOS 4.x、系统V版本 4 ( S V R 4 )和AIX 3.2,它们的 T C P / I P代码最初都是从伯克利源代码发展而来的。这些实现有很 多共同之处,通常包括同样的错误! 在图1 - 1中没有显示的伯克利联网代码的第 1版实际上是1 9 8 2年的4 . 1 c B S D,但是 广泛发布的是1983年的版本4.2BSD。 在4 . 1 c B S D 之前的 B S D版本使用的一个 T C P / I P实现,是由 Bolt Beranek and Newman(BBN)的Rob Gurwitz和Jack Haverty开发的。[Salus 1994]的第18章提供了另 外一些合并到 4 . 2 B S D中的B B N代码细节。其他对伯克利 T C P / I P代码有影响的实现是 由Ballistics研究室的Mike Muuss为PDP-11开发的TCP/IP实现。 描述联网代码从一个版本到下一个版本的变化的文档有限。

[Karels and

McKusick 1986]描述了从 4 . 2 B S D到4 . 3 B S D的变化,并且 [Jacobson 1990d]描述了从 4.3BSD Tahoe到4.3BSD Reno的变化。

1.4 应用编程接口 在互联网协议中两种常用的应用编程接口 ( A P I )是插口 ( s o c k e t )和T L I (运输层接口 )。前者

4

计计TCP/IP详解 卷 2:实现

有时称为伯克利插口 (Berkeley socket),因为它被广泛地发布于 4.2BSD系统中(见图1-1)。但它 已被移植到很多非 BSD Unix系统和很多非 U n i x系统中。后者最初是由 AT & T开发的,由于被 X / O p e n承认,有时叫作 X T I ( X / O p e n传输接口 )。X / O p e n是一个计算机厂商的国际组织,它制 定自己的标准。 XTI是TLI的一个有效超集。 虽然本文不是一本程序设计书,但既然在 N e t / 3 (和所有 B S D版本)中应用编程用插口来访 问TCP/IP,我们还是说明一下插口。在各种非 Unix系统中也实现了插口。插口和 TLI的编程细 节在[Stevens 1990]中可以找到。 系统Ⅴ版本 4(SVR4)也为应用编程提供了一组插口 API,在实现上与本文中列举的有所不 同。在SVR4中的插口基于“流”子系统,在 [Rago 1993]中有所说明。

1.5 程序示例 在本章我们用一个简单的 C程序(图1-2)来介绍一些 BSD网络实现的很多特点。

图1-2 程序示例:发送一个数据报给 UDP日期/时间服务器并读取一个应答

第1章 概

5

述计计

1. 创建一个数据报插口 socket函数创建了一个 UDP 插口,并且给进程返回一个保存在变量 sockfd中的描

19-20

述符。差错处理函数 e r r _ s y s在[Stevens 1992]的附录 B . 2中给出。它接收任意数量的参数, 并用v s p r i n t f对它们格式化,将系统调用产生的 e r r n o值对应的 U n i x错误信息打印出来, 并中断进程。 我们在不同的地方使用术语插口: ( 1 )为4 . 2 B S D开发的程序用来访问网络协议的 A P I通常叫插口 A P I或者就叫插口接口; (2) socket是插口 A P I中的一个函数的名字; (3)我们把调用 socket创建的端点叫做一个插口,如评注“创建一个数据报插口”。 但是这里还有一些地方也使用术语插口: (4) socket函数的返回值叫一个插口描 述符或者就叫一个插口; ( 5 )在内核中的伯克利联网协议实现叫插口实现,相比较其 他系统如:系统 V的流实现。(6)一个IP地址和一个端口号的组合叫一个插口, IP地址 和端口号对叫一个插口对。所幸的是引用哪一种术语是很明显的。 2. 将服务器地址放到结构 sockaddr_in中 21-24

在一个互联网插口地址结构中存放日期 /时间服务器的 IP地址(140.252.1.32)和端口号

( 1 3 )。大多数 T C P / I P实现都提供标准的日期 /时间服务器,它的端口号为 13 [Stevens 1994,图 1-9]。我们对服务器主机的选择是随意的—直接选择了提供此服务的本地主机 (图1-17)。 函数i n e t _ a d d r将一个点分十进制表示的 I P地址的 A S C I I字符串转换成网络字节序的 3 2 b i t二进制整数。 ( I n t e r n e t协议族的网络字节序是高字节在后 )。函数 h t o n s把一个主机字节序 的短整数(可能是低字节在后 )转换成网络字节序 (高字节在后 )。在Sparc这种系统中,整数是高 字节在后的格式, h t o n s 典型地是一个什么也不做的宏。但是在低字节在后的 8 0 3 8 6上的 B S D / 3 8 6系统中, h t o n s可能是一个宏或者是一个函数,来完成一个 16 bit整数中的两个字节 的交换。 3. 发送数据报给服务器 25-27

程序调用 s e n d t o发送一个 1 5 0字节的数据报给服务器。因为是运行时栈中分配的未初

始化数组,1 5 0字节的缓存内容是不确定的。但没有关系,因为服务器根本就不看它收到的报 文的内容。当服务器收到一个报文时,就发送一个应答给客户端。应答中包含服务器以可读 格式表示的当前时间和日期。 我们选择的 1 5 0字节的客户数据报是随意的。我们有意选择一个报文长度在 1 0 0 ~ 2 0 8之间 的值,来说明在本章的后面要提到的 m b u f链表的使用。为了避免拥塞,在以太网中,我们希 望长度要小于1472。 4. 读取从服务器返回的数据报 28-32

程序通过调用 r e c v f r o m来读取从服务器发回的数据报。 U n i x服务器典型地发回一

个如下格式的26字节字符串 Sat Dec 11 11:28:05 1993\r\n

\ r是一个 A S C I I回车符, \ n是A S C I I换行符。我们的程序将回车符替换成一个空字节,然后 调用printf输出结果。 在本章和下一章我们在分析函数 s o c k e t、s e n d t o和r e c v f r o m的实现时,要进入这个 例子的一些细节部分。

6

计计TCP/IP详解 卷 2:实现

1.6 系统调用和库函数 所有的操作系统都提供服务访问点,程序可以通过它们请求内核中的服务。各种 U n i x都 提供精心定义的有限个内核入口点,即系统调用。我们不能改变系统调用,除非我们有内核 的源代码。 U n i x第7版提供大约 5 0个系统调用, 4 . 4 B S D提供大约 1 3 5个,而 S V R 4大约有 1 2 0 个。 在《U n i x程序员手册》第 2节中有系统调用接口的文档。它是以 C语言定义的,在任何给 定的系统中无需考虑系统调用是如何被调用的。 在各种Unix系统中,每个系统调用在标准 C函数库中都有一个相同名字的函数。一个应用 程序用标准C的调用序列来调用此函数。这个函数再调用相应的内核服务,所使用的技术依赖 于所在系统。例如,函数可能把一个或多个 C参数放到通用寄存器中,并执行几条机器指令产 生一个软件中断进入内核。对于我们来说,可以把系统调用看成 C函数。 在《U n i x程序员手册》的第 3节中为程序员定义了一般用途的函数。虽然这些函数可能调 用一个或多个内核系统调用但没有进入内核的入口点。如函数 p r i n t f可能调用了系统调用 w r i t e 去执行输出,而函数 strcpy(复制一个串 )和atoi(将ASCII码转换成整数)完全不涉及操作系统。 从实现者的角度来看,一个系统调用和库函数有着根本的区别。但在用户看来区别并不 严重。例如,在 4 . 4 B S D 中我们运行图1 - 2 中的程序。程序调用了三个函数: s o c k e t 、 s e n d t o和r e c v f r o m,每个函数最终调用了一个内核中同样名称的函数。在本书的后面我们 可以看到这三个系统调用的 BSD内核实现。 如果我们在 S V R 4中运行这个程序,在那里,用户库中的插口函数调用“流”子系统,那 么三个函数同内核的相互作用是完全不同的。在 SVR4中对s o c k e t的调用最终调用内核 o p e n 系统调用,操作文件 / d e v / u d p并将流模块 s o c k m o d放置到结果流。调用 s e n d t o导致一个 p u t m s g系统调用,而调用 r e c v f r o m导致一个 g e t m s g系统调用。这些 SVR 4的细节在本书 中并不重要,我们仅仅想指出的是:实现可能不同但都提供相同的 API给应用程序。 最后,从一个版本到下一个版本的实现技术可能会改变。例如,在 Net/1中,send和sendto 是分别用内核系统调用实现的。但在 Net/3中,send是一个调用系统调用sendto的库函数: send(int s, char *msg, int len, int flags) { return(sendto(s, msg, len, flags, (struct sockaddr *) NULL, 0)); }

用库函数实现 s e n d的好处是仅调用 s e n d t o,减少了系统调用的个数和内核代码的长度。缺 点是由于多调用了一个函数,增加了进程调用 send的开销。 因为本书是说明 T C P / I P的伯克利实现的,大多数进程调用的函数 (s o c k e t、b i n d、 connect等)是直接由内核系统调用来实现。

1.7 网络实现概述 N e t / 3通过同时对多种通信协议的支持来提供通用的底层基础服务。的确, 4 . 4 B S D支持四 种不同的通信协议族: 1) TCP/IP(互联网协议族),本书的主题。 2) XNS(Xerox网络系统 ),一个与 TCP/IP相似的协议族;在 80年代中期它被广泛应用于连

第1章 概

7

述计计

接X e r o x设备(如打印机和文件服务器 ),通常使用的是以太网。虽然 N e t / 3仍然发布它的代码, 但今天已很少使用这个协议了,并且很多使用伯克利 T C P / I P代码的厂商把 X N S代码删去了 (这 样他们就不需要支持它了 )。 3) OSI协议[Rose 1990;Piscitello and Chapin 1993]。这些协议是在 80年代作为开放系统 技术的最终目标而设计的,来代替所有其他通信协议。在 9 0年代初它没有什么吸引力,以致 于在真正的网络中很少被使用。它的历史地位有待进一步确定。 4) Unix域协议。从通信协议是用来在不同的系统之间交换信息的意义上来说,它还不算 是一套真正的协议,但它提供了一种进程间通信 (IPC)的形式。 相对于其他 I P C,例如系统 V消息队列,在同一主机上两个进程间的 I P C使用U n i x域协议 的好处是 U n i x域协议用与其他三种协议同样的 A P I (插口)访问。另一方面,消息队列和大多数 其他形式 I P C的A P I与插口和 T L I完全不同。在同一主机上的两进程间的 I P C使用网络 A P I,更 容易将一个客户 /服务器应用程序从一台主机移植到多台主机上。在 U n i x域中提供两个不同的 协议 —一个是可靠的,面向连接的,与 T C P相似的字节流协议;一个是不可靠的,无连接 的,与UDP相似的数据报协议。 虽然U n i x域协议可以作为一种同一主机上两进程间的 I P C,但也可以用 T C P / I P来 完成它们之间的通信。进程间通信并不要求使用在不同的主机上的互联网协议。 内核中的联网代码组织成三层,如图

7. 应用

1- 3所示。在图的右侧我们注明了 O S I参考

进 程

6. 表示 5. 会话

模型[ P i s c i t e l l o和Chapin 1994] 的七层分别 系统调用

对应到BSD组织的哪里。

(socket, bind, connect, etc.)

1) 插口层是一个到下面协议相关层的 插口层

协议无关接口。所有系统调用从协议无关 的插口层开始。例如:在插口层中的 b i n d

协议层 (TCP/IP, XNS, OSI, Unix)

系统调用的协议无关代码包含几十行代码, 它们验证的第一个参数是一个有效的插口 描述符,并且第二个参数是一个进程中的

接口层 (以太网、SLIP、环回等等)

4. 运输 3. 网络 2. 数据链路

有效指针。然后调用下层的协议相关代码, 协议相关代码可能包含几百行代码。 2) 协议层包括我们前面提到的四种协 议族(TCP/IP,XNS,OSI和Unix域)的实现。

媒体

1. 物理

图1-3 Net/3联网代码的大概组织

每个协议族可能包含自己的内部结构,在图 1-3中我们没有显示出来。例如,在 Internet协议族 中,IP(网络层)是最低层, TCP和UDP两运输层在 IP的上面。 3) 接口层包括同网络设备通信的设备驱动程序。

1.8 描述符 图1 - 2中,一开始调用 s o c k e t,这要求定义插口类型。 I n t e r n e t协议族(P F _ I N E T)和数据 报插口(SOCK_DGRAM)组合成一个 UDP协议插口。 s o c k e t的返回值是一个描述符,它具有其他 U n i x描述符的所有特性:可以用这个描述 符调用 r e a d和w r i t e;可以用 d u p 复制它,在调用了 f o r k后,父进程和子进程可以共享

8

计计TCP/IP详解 卷 2:实现

它;可以调用 f c n t l来改变它的属性,可以调用 c o l s e来关闭它,等等。在我们的例子中可 以看到插口描述符是函数 s e n d t o 和r e c v f r o m的第一个参数。当程序终止时 (通过调用 exit),所有打开的描述符,包括插口描述符都会被内核关闭。 我们现在介绍在进程调用 s o c k e t时被内核创建的数据结构。在后面的几章中会更详细地 描述这些数据结构。 首先从进程的进程表表项开始。在每个进程的生存期内都会有一个对应的进程表表项存 在。 一个描述符是进程的进程表表项中的一个数组的下标。这个数组项指向一个打开文件表 的结构,这个结构又指向一个描述此文件的 i-node或v-node结构。图1-4说明了这种关系。

图1-4 从一个描述符开始的内核数据结构的基本关系

在这个图中,我们还显示了一个涉及插口的描述符,它是本书的焦点。由于进程表表项 是由以下 C语言定义的,我们把记号 p r o c { }放在进程表项的上面。并且在本书所有的图中都 用它来标注这个结构。 struct proc { ... }

[Stevens 1992,3 . 1 0节]显示了当进程调用 d u p和f o r k时,描述符、文件表结构和 i - n o d e 或v - n o d e之间的关系是如何改变的。这三种数据结构的关系存在于所有版本的 U n i x中,但不 同的实现细节有所变化。在本书中我们感兴趣的是 s o c k e t结构和它所指向的 I n t e r n e t专用数 据结构。但是既然插口系统调用以一个描述符开始,我们就需要理解如何从一个描述符导出 一个socket结构。 如果程序如此执行 a.out

不重定向标准输入 (描述符 0 )、标准输出 (描述符 1 )和标准错误处理 (描述符 2 ),图1 - 5显示 了程序示例中的 N e t / 3数据结构的更多细节。在这个例子中,描述符 0、1和2连接到我们的终 端,并且当 socket被调用时未用描述符的最小编号是 3。 当进程执行了一个系统调用,如 s o c k e t,内核就访问进程表结构。在这个结构中的项 p _ f d 指向进程的 f i l e d e s c 结构。在这个结构中有两个我们现在关心的成员:一个是

第1章 概

9

述计计

f d _ o f i l e f l a g s ,它是一个字符数组指针 ( 每个描述符有一个描述符标志 ) ;一个是 f d _ o f i l e s,它是一个指向文件表结构的指针数组的指针。描述符标志有 8 bit,只有两位可 为任何描述符设置: c l o s e - o n - e x e c标志和m a p p e d - f r o m - d e v i c e标志。在这里我们显示的所有标 志都是0。 由于U n i x描述符与很多东西有关,除了文件外,还有:插口、管道、目录、设

所有UDP Internet协议控制 块的双向循环链表

图1-5 在程序示例中调用 socket后的内核数据结构

10

计计TCP/IP详解 卷 2:实现

备等等,因此,我们有意把本节叫做“描述符”而不是“文件描述符”。但是很多 U n i x文献在谈到描述符时总是加上“文件”这个修饰词,其实没有必要。虽然我们 要说明的是插口描述符,但这个内核数据结构叫 f i l e d e s c { }。我们尽可能地使用 描述符这个未加修饰的术语。 项f d _ o f i l e s指向的数据结构用 * f i l e { } [ ]来表示。它是一个指向 f i l e结构的指针数 组。这个数组及描述符标志数组的下标就是描述符本身: 0、1、2等等,是非负整数。在图 1 5中我们可以看到描述符 0、1、2对应的项指向图底部的同一个 f i l e结构(由于这三个描述符都 对应终端设备)。描述符3对应的项指向另外一个 file结构。 结构 f i l e的成员 f _ t y p e指示描述符的类型是 D T Y P E _ S O C K E T和D T Y P E _ V N O D E。v n o d e是一个通用机制,允许内核支持不同类型的文件系统—磁盘文件系统、网络文件系统 (如N F S )、C D - R O M文件系统、基于存储器的文件系统等等。在本书中关心的不是 v - n o d e,因 为TCP/IP插口的类型总是 DTYPE_SOCKET。 结构f i l e的成员 f _ d a t a指向一个 s o c k e t结构或者一个 v n o d e结构,根据描述符类型 而定。成员 f _ o p s 指向一个有 5个函数指针的向量。这些函数指针用在 r e a d 、r e a d v、 w r i t e、w r i t e v、i o c t l、s e l e c t和c l o s e系统调用中,这些系统调用需要一个插口描述 符或非插口描述符。这些系统调用每次被调用时都要查看 f_type的值,然后做出相应的跳转, 实现者选择了直接通过 fileops结构的相应项来跳转的方式。 我们用一个等宽字体 ( f o _ r e a d )来醒目地表示一个结构成员的名称,用斜体等宽字体 (s o o _ r e a d)来表示一个结构成员的内容。注意,有时我们用一个箭头指向一个结构的左上角 (如结构 f i l e d e s c),有时用一个箭头指向右上角 (如结构 f i l e和f i l e o p s)。我们用这些方 法来简化图例。 下面我们来查看结构 s o c k e t,当描述符的类型是 D T Y P E _ S O C K E T时,结构 f i l e指向结 构socket。在我们的例子中, socket的类型(数据报插口的类型是 SOCK_DGRAM)保存在成 员s o _ t y p e中。还分配了一个 I n t e r n e t协议控制块 ( P C B ):一个 i n p c b结构。结构 s o c k e t的 成员s o _ p c b指向i n p c b,并且结构 i n p c b的成员i n p _ s o c k e t指向结构 s o c k e t。对于一个 给定插口的操作可能来自两个方向:“上”或“下”,因此需要有指针来互相指向。 1) 当进程执行一个系统调用时,如 s e n d t o,内核从描述符值开始,使用 f d _ o f i l e s索 引到 f i l e结构指针向量,直到描述符所对应的 f i l e结构。结构 f i l e指向s o c k e t结构,结 构socket带有指向结构inpcb的指针。 2) 当一个 U D P数据报到达一个网络接口时,内核搜索所有 U D P协议控制块,寻找一个合 适的,至少要根据目标 U D P端口号,可能还要根据目标 I P地址、源 I P地址和源端口号。一旦 定位所找的 inpcb,内核就能通过 inp_socket指针来找到相应的 socket结构。 成员 i n p _ f a d d r 和 i n p _ l a d d r 包含远地和本地 I P 地址,而成员 i n p _ f p o r t 和 inp_lport包含远地和本地端口号。 IP地址和端口号的组合经常叫做一个插口。 在图1 - 5的左边,我们用名称 u d b来标注另一个 i n p c b结构。这是一个全局结构,它是所 有UDP PCB组成的链表表头。我们可以看到两个成员 i n p _ n e x t和i n p _ p r e v把所有的 U D P P C B组成了一个双向环型链表。为了简化此图,我们用两条平行的水平箭头来表示两条链, 而不是用箭头指向 PCB的顶角。右边的 inpcb结构的成员 inp_prev指向结构udb,而不是它 的成员 i n p _ p r e v。来自 u d b . i n p _ p r e v和另一个 P C B成员in p _ n e x t的虚线箭头表示这里

第1章 概

11

述计计

还有其他PCB在这个双链表上,但我们没有画出。 在本章,我们已看了不少内核数据结构,大多数还要在后续章节中说明。现在要理解的 关键是: 1) 我们的进程调用 s o c k e t,最后分配了最小未用的描述符 (在我们的例子中是 3 )。在后 面,所有针对此 socket的系统调用都要用这个描述符。 2) 以下内核数据结构是一起被分配和链接起来的:一个 D T Y P E _ S O C K E T类型f i l e结构、 一个s o c k e t结构和一个 i n p c b结构。这些结构的很多初始化过程我们并没有说明: f i l e结 构的读写标志 (因为调用 s o c k e t总是返回一个可读或可写的描述符 );默认的输入和输出缓 存大小被设置在 socket结构中,等等。 3) 我们显示了标准输入、输出和标准错误处理的非 socket描述符的目的是为了说明所有 描述符最后都对应一个 file结构,虽然 socket描述符和其他描述符之间有所不同。

1.9 mbuf与输出处理 在伯克利联网代码设计中的一个基本概念就是存储器缓存,称作一个 m b u f,在整个联网 代码中用于存储各种信息。通过我们的简单例子 (图1-2)分析一些mbuf的典型用法。在第 2章中 我们会更详细地说明 mbuf。 1.9.1 包含插口地址结构的 mbuf 在s e n d t o调用中,第 5个参数指向一个 I n t e r n e t插口地址结构 (叫s e r v ),第6个参数指示它 的长度(后面我们将要看到是 16个字节)。插口层为这个系统调用做的第一件事就是验证这些参 数是有效的 (即这个指针指向进程地址空间的一段存储器 ),并且将插口地址结构复制到一个 mbuf中。图1-6所示的是这个所得到的 mbuf。

20字节

带有目标IP地址和端口号的 16字节的sockaddr_in{}

128字节

图1-6 mbuf中针对sendto的目的地址

m b u f的前2 0个字节是首部,它包含关于这个 m b u f的一些信息。这 2 0个字节的首部包括四 个4字节字段和两个 2字节字段。 mbuf的总长为128个字节。 稍后我们会看到, m b u f可以用成员 m _ n e x t和m _ n e x t p k t链接起来。在这个例子中都是

12

计计TCP/IP详解 卷 2:实现

空指针,它是一个独立的 mbuf。 成员m _ d a t a指向m b u f中的数据,成员 m _ l e n指示它的长度。对于这个例子, m _ d a t a 指向mbuf中数据的第一个字节 (紧接着mbuf首部)。mbuf后面的92个字节(108-16)没有用(图1-6 的阴影部分 )。 成员m _ t y p e指示包含在 m b u f中数据的类型,在本例中是 M T _ S O N A M E(插口名称 )。首部 的最后一个成员 m_flags,在本例中是零。 1.9.2 包含数据的 mbuf 下面继续讨论我们的例子,插口层将 sendto调用中指定的数据缓存中的数据复制到一个或 多个m b u f中。s e n d t o的第二个参数指示了数据缓存 ( b u ff )的开始位置,第三个参数是它的大 小(150字节)。图1-7显示了150字节的数据是如何存储在两个 mbuf 中的。 指向链中下一个mbuf的指针

mbuf分组首部 50字节的数据

100字节的数据

图1-7 用两个mbuf来存储150字节的数据

这种安排叫做 m b u f链表。在每个 m b u f中的成员 m _ n e x t把链表中所有的 m b u f都链接在一 起。 我们看到的另一个变化是链表中第一个 mbuf的mbuf首部的另外两个成员:m_pkthdr.len 和m _ p k t h d r . r c v i f。这两个成员组成了分组首部并且只用在链表的第一个 m b u f中。成员 m _ f l a g s的值是M _ P K T H D R,指示这个mbuf包含一个分组首部。分组首部结构的成员 len包含 了整个m b u f链表的总长度 (在本例中是1 5 0 ),下一个成员 r c v i f在后面我们会看到,它包含了 一个指向接收分组的接收接口结构的指针。 因为m b u f总是1 2 8个字节,在链表的第一个 m b u f中提供了 1 0 0字节的数据存储能力,而后 面所有的 m b u f有1 0 8字节的存储空间。在本例中的两个 m b u f需要存储 1 5 0字节的数据。我们稍 后会看到当数据超过 208字节时,就需要 3个或更多的 mbuf。有一种不同的技术叫“簇”,一种 大缓存,典型的有 1024或2048字节。 在链表的第一个 m b u f中维护一个带有总长度的分组首部的原因是,当需要总长度时可以 避免查看所有mbuf中的m_len来求和。

第1章 概

13

述计计

1.9.3 添加IP和UDP首部 在插口层将目标插口地址结构复制到一个 m b u f中,并把数据复制到 m b u f链中后,与此插 口描述符 (一个 U D P描述符 )对应的协议层被调用。明确地说, U D P输出例程被调用,指向 mbuf的指针被作为一个参数传递。这个例程要在这 150字节数据的前面添加一个 IP首部和一个 UDP首部,然后将这些 mbuf传递给IP输出例程。 在图1-7中的mbuf链表中添加这些数据的方法是分配另外一个 mbuf,把它放在链首,并将 分组首部从带有 100字节数据的 mbuf复制到这个 mbuf。在图1-8中显示了这三个 mbuf。 链中的 下一个mbuf

链中的下一个mbuf

mbuf 分组首部

50字节的数据

100字节的数据 28字节用于IP首 部和UDP首部

图1-8 在图1-7中的mbuf链表中添加另一个带有 IP和UDP首部的mbuf

I P首部和 U D P首部被放置在新 m b u f的最后,这个新 m b u f就成了整个链表的首部。如果 需要,它允许任何其他低层协议 (例如接口层 )在I P首部前添加自己的首部,而不需要再复制 I P和U D P首部。在第一个 m b u f中的m _ d a t a指针指向这两个首部的起始位置, m _ l e n的值是 2 8 。在分组首部和 I P 首部之间有 7 2 字节的未用空间留给以后的首部,通过适当地修改 m _ d a t a指针和 m _ l e n添加在 I P首部的前面。稍后我们会看见以太网首部就是用这种方法建 立的。 注意,分组首部已从带有 1 0 0字节数据的 m b u f中移到新 m b u f中去了。分组首部必须放在 m b u f链表的第一个 m b u f中。在移动分组首部的同时,在第一个 m b u f设置M _ P K T H D R标志并且 在第二个 m b u f中清除此标志。在第二个 m b u f中分组首部占用的空间现在未用。最后,在此分 组首部中的长度成员由于增加了 28 字节而变成了178。 然后U D P输出例程填写 U D P首部和 I P首部中它们所能填写的部分。例如, I P首部中的目 标地址可以被设置,但 IP检验和要留给IP输出例程来计算和存放。 UDP检验和计算后存储在 UDP首部中。注意,这要求遍历存储在 mbuf链表中的所有150字 节的数据。这样,内核要对这 1 5 0字节的用户数据做两次遍历:一次是把用户缓存中的数据复 制到内核中的 mbuf中,而现在是计算 UDP检验和。对整个数据的额外遍历会降低协议的性能, 在后续章节中我们会介绍另一种可选的实现技术,它可以避免不必要的遍历。 接着,UDP输出例程调用IP输出例程,并把此 mbuf链表的指针传递给 IP输出例程。

14

计计TCP/IP详解 卷 2:实现

1.9.4 IP输出 IP输出例程要填写 IP首部中剩余的字段,包括 IP检验和;确定数据报应发到哪个输出接口 (这是IP路由功能 );必要时,对IP报文分片;以及调用接口输出函数。 假设输出接口是一个以太网接口,再次把此 m b u f链表的指针作为一个参数,调用一个通 用的以太网输出函数。 1.9.5 以太网输出 以太网输出函数的第一个功能就是把 3 2位I P地址转换成相应的 4 8位以太网地址。在使用 ARP(地址解析协议 )时会使用这个功能,并且会在以太网上发送一个 ARP请求并等待一个 ARP 应答。此时,要输出的 mbuf链表已得到,并等待应答。 然后以太网输出例程把一个 1 4字节的以太网首部添加到链表的第一个 m b u f中,紧接在 I P 首部的前面(图1-8)。以太网首部包括 6字节以太网目标地址、 6字节以太网源地址和 2字节以太 网帧类型。 之后此 m b u f链表被加到此接口的输出队列队尾。如果接口不忙,接口的“开始输出”例 程立即被调用。若接口忙,在它处理完输出队列中的其他缓存后,它的输出例程会处理队列 中的这个新 mbuf。 当接口处理它输出队列中的一个 m b u f时,它把数据复制到它的传输缓存中,并且开始输 出。在我们的例子中, 1 9 2字节被复制到传输缓存中: 1 4字节以太网首部、 2 0字节I P首部、 8 字节UDP首部及150字节用户数据。这是内核第三次遍历这些数据。一旦数据从 mbuf链表被复 制到设备传输缓存, m b u f链表就被以太网设备驱动程序释放。这三个 m b u f被放回到内核的自 由缓存池中。 1.9.6 UDP输出小结 我们在图 1 - 9中给出了一个进程调用 s e n d t o传输一个 U D P数据报时的大致处理过程。在图 中我们说明的处理过程与三层内核代码 (图1-3)的关系也显示出来了。 进程 系统调用

插口层

把目标插口地址结构和用户数 据(150字节)复制到mbuf链中

协议层

UDP输出 IP输出

接口层

以太网输出(ARP) 设备驱动器输出

以太网帧

图1-9 三层处理一个简单 UDP输出的执行过程

第1章 概

15

述计计

函数调用控制从插口层到 U D P输出例程,到 I P输出例程,然后到以太网输出例程。每个 函数调用传递一个指向要输出的 m b u f的指针。在最低层,设备驱动程序层, m b u f链表被放置 到设备输出队列并启动设备。函数调用按调用的相反顺序返回,最后系统调用返回给进程。 注意,直到 U D P数据报到达设备驱动程序前, U D P数据没有排队。高层仅仅添加它们的协议 首部并把mbuf传递给下一层。 这时,在我们的程序示例中调用 recvfrom去读取服务器的应答。因为该插口的输入队列是 空的(假设应答还没有到达 ),进程就进入睡眠状态。

1.10 输入处理 输入处理与刚讲过的输出处理不同,因为输入是异步的。就是说,它是通过一个接收完 成中断驱动以太网设备驱动程序来接收一个输入分组,而不是通过进程的系统调用。内核处 理这个设备中断,并调度设备驱动程序进入运行状态。 1.10.1 以太网输入 以太网设备驱动程序处理这个中断,假定它 表示一个正常的接收已完成,数据从设备读到 一个 m b u f链表中。在我们的例子中,接收了 5 4 字节的数据并复制到一个 m b u f中: 2 0字节 I P首 部、8字节UDP首部及26字节数据 (服务器的时间 与日期)。图1-10所示的是这个mbuf的格式。 指向接口结构的指针

这个m b u f是一个分组首部 (m _ f l a g s被设置 成M_PKTHDR),它是一个数据记录的第一个mbuf。

分配了16字节(但未用)

分组首部的成员 l e n包含数据的总长度,成员

20字节IP首部 8字节UDP首部 26字节的数据

rcvif包含一个指针,它指向接收数据的接口的 接口结构 (第3章)。我们可以看到成员 r c v i f用 于接收分组而不是输出分组(图1-7和图1-8)。 m b u f的前 1 6字节数据空间被分配给一个接 口层首部,但没有使用。数据就存储在这个

图1-10 用一个mbuf存储输入的以太网数据

mbuf中,54字节的数据存储在剩余的 84字节的空间中。 设备驱动程序把 m b u f传给一个通用以太网输入例程,它通过以太网帧中的类型字段来确 定哪个协议层来接收此分组。在这个例子中,类型字段标识一个 I P数据报,从而 m b u f被加入 到I P输入队列中。另外,会产生一个软中断来执行 I P输入例程。这样,这个设备中断处理就 完成了。 1.10.2 IP输入 I P输入是异步的,并且通过一个软中断来执行。当接口层在系统的一个接口上收到一个 I P数据报时,它就设置这个软中断。当 I P输入例程执行它时,循环处理在它的输入队列中的 每一个IP数据报,并在整个队列被处理完后返回。 IP输入例程处理每个接收到的 IP数据报。它验证 IP首部检验和,处理 IP选项,验证数据报

16

计计TCP/IP详解 卷 2:实现

被传递到正确的主机 (通过比较数据报的目标 I P地址与主机I P地址),并当系统被配置为一个路 由器,且数据报被表注为其他的 I P地址时,转发此数据报。如果 I P数据报到达它的最终目标, 调用I P首部中标识的协议的输入例程: I C M P,I G M P,T C P或U D P。在我们的例子中,调用 UDP输入例程去处理 UDP数据报。 1.10.3 UDP输入 UDP输入例程验证UDP首部中的各字段 (长度与可选的检验和 ),然后确定是否一个进程应 该接收此数据报。在第 2 3章我们要详细讨论这个检查是如何进行的。一个进程可以接收到一 指定U D P端口的所有数据报,或让内核根据源与目标 I P地址及源与目标端口号来限制数据报 的接收。 在我们的例子中, U D P输入例程从一个全局变量 u d b(图1 - 5 )开始,查看所有 U D P协议控 制块链表,寻找一个本地端口号 ( i n p _ l p o r t)与接收的 U D P数据报的目标端口号匹配的协议 控制块。这个 P C B是由我们调用 s o c k e t创建的,它的成员 i n p _ s o c k e t指向相应插口结构, 并允许接收的数据在此插口排队。 在程序示例中,我们从未为应用程序指定本地端口号。在习题 23.3中,我们会看 到在写第一个 U D P程序时创建一个插口而不绑定一个本地端口号会导致内核自动地 给此插口分配一个本地端口号 ( 称为短期端口 ) 。这就是为什么插口的 P C B 成员 inp_lport不是一个空值的原因。 因为这个 U D P数据报要传递给我们的进程,发送方的 I P地址和 U D P端口号被放置到一个 m b u f中,这个 m b u f和数据 (在我们的例子中是 2 6字节)被追加到此插口的接收队列中。图 1 - 11 所示的是被追加到这个插口的接收队列中的这两个 mbuf。 插口的接 收队列

指向链中下一个mbuf的指针

16字节sockaddr_in{} 带有发送方IP地址和端口号

指向接口结构的指针 44字节(未用)

26字节(未用)

图1-11 发送方地址和数据

比较这个链表中的第二个 m b u f (M T _ D A T A 类型 ) 与图 1 - 1 0中的 m b u f,成员 m _ l e n 和 m _ p k t h d r . l e n都减小了 2 8字节( 2 0字节的I P首部和8字节U D P首部),并且指针 m _ d a t a也减 小了28字节。这有效地将 IP和UDP首部删去,只保留了 26字节数据追加到插口接收队列。

第1章 概

17

述计计

在链表的第一个 m b u f中包括一个 1 6字节I n t e r n e t插口地址结构,它带有发送方 I P 地址和 U D P端口号。它的类型是 M T _ S O N A M E,与图 1 - 6中的m b u f类似。这个 m b u f是插口层创建的, 将这些信息返回给通过调用系统调用 r e c v f o r m或r e c v m s g的调用进程。即使在这个链表的 第二个 m b u f中有空间 ( 1 6字节)存储这个插口地址结构,它也必须存放到它自己的 m b u f中,因 为它们的类型不同 (一个是MT_SONAME,一个是MT_DATA)。 然后接收进程被唤醒。如果进程处于睡眠状态等待数据的到达 (我们例子中的情况 ),进程 被标志为可运行状态等待内核的调度。也可以通过 s e l e c t系统调用或 S I G I O信号来通知进 程数据的到达。 1.10.4 进程输入 我们的进程调用 r e c v f r o m时被阻塞,在内核中处于睡眠状态,现在进程被唤醒。 U D P 层追加到插口接收队列中的 2 6字节的数据 (接收的数据报 )被内核从 m b u f复制到我们程序的缓 存中。 注意,我们的程序把 r e c v f o r m的第5,第6个参数设置为空指针,告诉系统在接收过程 中不关心发送方的 I P地址和 U D P端口号。这使得系统调用 r e c v f r o m时,略过链表中的第一 个mbuf(图1-11),仅返回第二个 mbuf中的26字节的数据。然后内核的 r e c v f r o m代码释放图 111中的两个mbuf,并把它们放回到自由 mbuf池中。

1.11 网络实现概述 (续) 图1 - 1 2总结了在各层间为网络输入输出而进行的通信。图 1 - 1 2是对图 1 - 3进行了重画,它 只考虑Internet协议,并且强调层间的通信。符号 splnet与splimp在下一节讨论。 我们使用复数术语插口队列 (socket queues)和接口队列 (interface queues),因为每个插口 进程

系统调用

插口层

函数调用

插口队列

协议层 (TCP, UDP, IP, ICMP, IGMP) 函数调用 来启动输出

软件中断@splnet (由接口层产生) 协议队列 (IP输入队列)

接口队列

接口层

图1-12 网络输入输出的层间通信

硬件中断@splimp (由网络设备产生)

18

计计TCP/IP详解 卷 2:实现

和每个接口 (以太网、环回、 S L I P、P P P等)都有一个队列,但我们使用单数术语协议队列 (protocol queue),因为只有一个 I P输入队列。如果考虑其他协议层,我们就会有一个队列用 于XNS协议,一个队列用于 OSI协议。

1.12 中断级别与并发 我们在1.10节看到网络代码处理输入分组用的是异步和中断驱动的方式。首先,一个设备 中断引发接口层代码执行,然后它产生一个软中断引发协议层代码执行。当内核完成这些级 别的中断后,执行插口代码。 在这里给每个硬件和软件中断分配一个优先级。图 1 - 1 3所示的是 8个优先级别的顺序,从 最低级别(不阻塞中断 )到最高级别 (阻塞所有中断)。 函





spl0 Splsoftclock splnet spltty splbio splimp splclock splhigh

正常操作方式,不阻塞中断 低优先级时钟处理 网络协议处理 终端输入输出 磁盘与磁带输入输出 网络设备输入输出 高优先级时钟处理 阻塞所有中断

splx(s)

(见正文)

明 (最低优先级 )

(最高优先级 )

图1-13 阻塞所选中断的内核函数

[Leffler et al. 1989]的表4-5显示了用于 VAX实现的优先级别。 386的Net/3的实现 使用图1 - 1 3所示的8个函数,但 s p l s o f t c l o c k与s p l n e t在同一级别, s p l c l o c k 与splhigh也在同一级别。 用于网络接口级的名称 i m p来自于缩写I M P (接口报文处理器 ),它是在 ARPANET 中使用的路由器的最初类型。 不同优先级的顺序意味着高优先级中断可以抢占一个低优先级中断。看图 1 - 1 4所示的事 被抢占 被抢占 插口

插口 协议(IP输入)

协议(IP输入) SLIP设备驱动程序

以太网设备 驱动程序

step 1

2

3

4

5

图1-14 优先级示例与内核处理

6

7

第1章 概

19

述计计

件顺序。 1) 当插口层以级别 s p l 0执行时,一个以太网设备驱动程序中断发生,使接口层以级别 splimp执行。这个中断抢占了插口层代码的执行。这就是异步执行接口输入例程。 2) 当以太网设备驱动程序在运行时,它把一个接收的分组放置到 I P输出队列中并调度一 个s p l n e t级别的软中断。软中断不会立即有效,因为内核正在一个更高的优先级 (s p l i m p) 上运行。 3) 当以太网设备驱动程序完成后,协议层以级别splnet执行。这就是异步执行IP输入例程。 4) 一个终端设备中断发生 (完成一个 S L I P分组),它立即被处理,抢占协议层,因为终端 输入/输出(spltty)优先级比图 1-13中的协议层 (splnet)更高。 5) SLIP驱动程序把接收的分组放到 IP输入队列中并为协议层调度另一个软中断。 6) 当SLIP驱动程序结束时,被抢占的协议层继续以级别 splnet执行,处理完从以太网设备 驱动程序收到的分组后,处理从 SLIP驱动程序接收的分组。仅当没有其他输入分组要处理时, 它会把控制权交还给被它抢占的进程 (在本例中是插口层 )。 7) 插口层从它被中断的地方继续执行。 对于这些不同优先级,一个要关心的问题就是如何处理那些在不同级别的进程间共享的 数据结构。在图 1 - 2中显示了三种在不同优先级进程间共享的数据结构—插口队列、接口队 列和协议队列。例如,当 I P输入例程正在从它的输入队列中取出一个接收的分组时,一个设 备中断发生,抢占了协议层,并且那个设备驱动程序可能添加另一个分组到 I P输入队列。这 些共享的数据结构 (本例中的IP输入队列,它共享于协议层和接口层 ),如果不协调对它们的访 问,可能会破坏数据的完整性。 Net/3的代码经常调用函数 s p l i m p和s p l n e t。这两个调用总是与 s p l x成对出现,s p l x 使处理器返回到原来的优先级。例如下面这段代码,被协议层 I P输入函数执行,去检查是否 有其他分组在它的输入队列中等待处理: struct mbuf *m; int s; s = splimp (); IF_DEQUEUE (&ipintrq, m); splx(s); if (m == 0) return;

调用s p l i m p把CPU的优先级升高到网络设备驱动程序级,防止任何网络设备驱动程序中断发 生。原来的优先级作为函数的返回值存储到变量 s中。然后执行宏 I F _ D E Q U E U E把I P输入队列 (i p i n t r q)头部的第二个分组删去,并把指向此 m b u f链表的指针放到变量 m中。最后,通过 调用带有参数 s (其保存着前面调用 s p l i m p 的返回值 )的s p l x ,C P U的优先级恢复到调用 splimp前的级别。 由于在调用 s p l i m p和s p l x之间所有的网络设备驱动程序的中断被禁止,在这两个调用 间的代码应尽可能的少。如果中断被禁止过长的时间,其他设备会被忽略,数据会被丢失。 因此,对变量m的测试(看是否有其他分组要处理 )被放在调用 splx之后而不是之前。 当以太网输出例程把一个要输出的分组放到一个接口队列,并测试接口当前是否忙时, 若接口不忙则启动接口,这时例程需要调用这些 spl调用。

20

计计TCP/IP详解 卷 2:实现

在这个例子中,设备中断被禁止的原因是防止在协议层正在往队列添加分组时,设备驱 动程序从它的发送队列中取走下一个分组。设备发送队列是一个在协议层和接口层共享的数 据结构。 在整个源代码中到处都会看到 spl函数。

1.13 源代码组织 图1-15所示的是Net/3网络源代码的组织,假设它位于目录 /usr/src/sys。 Intel 80x86专用 通用内核 通用联网 HDLC, X.25 TCP/IP协议 OSI协议 XNS协议 NFS 内核头文件 文件系统 虚拟存储器

图1-15 Net/3源代码组织

本书的重点在目录 n e t i n e t,它包含所有 TCP/IP源代码。在目录 k e r n和n e t中我们也可 找到一些文件。前者是协议无关的插口代码,而后者是一些通用联网函数,用于 TCP/IP例程, 如路由代码。

第1章 概

21

述计计

包含在每个目录中的文件简要地列于下面: • i 3 8 6:因特8 0 x 8 6专用目录。例如,目录 i 3 8 6 / i s a包含专用于 I S A总线的设备驱动程 序。目录 i386/stand包含单机引导程序代码。 • k e r n:通用的内核文件,不属于其他目录。例如,处理系统调用 f o r k和e x e c的内核 文件在这个目录。在这个目录中,我们只考察少数几个文件—用于插口系统调用的文 件(插口层在图 1-3)。 • n e t:通用联网文件,例如,通用联网接口函数, B P F ( B S D分组过滤器 )代码、 S L I P驱 动程序和路由代码。在这个目录中我们考察一些文件。 • netccitt:OSI协议接口代码,包括 HDLC(高级数据链路控制 )和X.25驱动程序。 • n e t i n e t:I n t e r n e t协议代码: I P,I C M P,I G M P,T C P和U D P。本书的重点集中在这 个目录中的文件。 • netiso:OSI协议。 • netns:施乐(Xerox)XNS协议。 • nfs:SUN公司的网络文件系统代码。 • s y s:系统头文件。在这个目录中我们考察几个头文件。这个目录中的文件还出现在目 录/usr/include/sys中。 • ufs:Unix文件系统的代码,有时叫伯克利快速文件系统。它是标准磁盘文件系统。 • vm:虚拟存储器系统代码。 图1 - 1 6所示的是源代码组织的另一种表现形式,它映射到我们的三个内核层。忽略 netimp和nfs这样的目录,在本书中我们不关心它们。 插口层

4000行C代码

net/ 选路 2 100

netinet/ (TCP/IP) 13 000

net/ (以太网,ARP)

net/if_sl* (SLIP)

500

1 750

netns/ (XNS)

netiso/ (OSI)

kern/ (Unix域)

6 000

26 000

750

net/if_loop* (boopback)

net/bpf* (BPF)

以太网设备 驱动程序

2 000

1 000每驱动程序

250

协议层

接口层

图1-16 映射到三个内核层的 Net/3源代码组织

在每个表格框底下的数字是对应功能的 C代码的近似行数,包括源文件中的所有注释。 我们不考察图中所有的源代码。显示目录 n e t n s与n e t i s o是为了与 I n t e r n e t协议比较。 我们仅考虑有阴影的表格框。

1.14 测试网络 图1 - 1 7所示的测试网络用于本书中所有的例子。除了在图顶部的主机 v a n g o g h,所有的

22

计计TCP/IP详解 卷 2:实现

I P地址属于 B类网络地址 1 4 0 . 2 5 2,并且所有主机名属于域 . t u c . n o a o . e d u (n o a o代表“国 家光学天文台”,t u c代表Tu c s o n )。例如,在右下角的系统的主机全名是 s v r 4 . t u c . n o a o . edu,IP地址是140.252.13.34。在每个框图顶上的记号是运行在此系统上操作系统的名称。 在图顶的主机的全名是 v a n g o g h . c s . b e r k e l e y . e d u,其他主机通过 I n t e r n e t可以连接 到它。 这个图与卷 1中的测试网络几乎一样,有一些操作系统升级了,在 s u n与n e t b之间的拨号 链路现在用 P P P取代了 S L I P。另外,我们用 N e t / 3网络代码代替了 BSD/386 V1.1提供的 N e t / 2 网络代码。

路由器

以太网 二进制遥测系统

拨号

以太网

图1-17 用于本书中所有例子的测试网络

1.15 小结 本章是对 N e t / 3网络代码的概述。通过一个简单的程序示例 (图1 - 2 )—发送一个 U D P数据 报给一个日期时间服务器并接收应答,我们分析了通过内核进行输入输出的过程。 m b u f中保 存要输出的信息和接收的 IP数据报。下一章我们要查看 mbuf的更多细节。 当进程执行 s e n d t o系统调用时,产生 UDP输出,而 IP输入是异步的。当一个设备驱动程

第1章 概

23

述计计

序接收了一个 I P数据报,数据报被放到 I P输入队列中并且产生一个软中断使 I P输入函数执行。 我们考察了在内核中用于联网代码的不同中断级别。由于很多联网数据结构被不同的层所共 享,而这些层在不同的中断级别上执行,因此当访问或修改这些共享结构时要特别小心。几 乎所有我们要查看的函数中都会遇到 spl函数。 本章结束时我们查看了 Net/3源代码的整个组织结构,及本书关注的代码。

习题 1.1 输入程序示例 (图1 - 2 )并在你的系统上运行。如果你的系统有系统调用跟踪能力,如 trace (SunOS 4.x)、truss (SVR4)或ktrace (4.4BSD),用它检测本例中调用的 系统调用。 1.2 在1.12节调用I F _ D E Q U E U E的例子中,我们注意到调用 s p l i m p来防止网络设备驱动 程序的中断。当以太网驱动程序以这个级别执行时, SLIP驱动程序会发生什么?

下载

第2章 mbuf:存储器缓存 2.1 引言 网络协议对内核的存储器管理能力提出了很多要求。这些要求包括能方便地操作可变长 缓存,能在缓存头部和尾部添加数据 (如低层封装来自高层的数据 ),能从缓存中移去数据 (如, 当数据分组向上经过协议栈时要去掉首部 ),并能尽量减少为这些操作所做的数据复制。内核 中的存储器管理调度直接关系到联网协议的性能。 在第1章我们介绍了普遍应用于 Net/3内核中的存储器缓存: mbuf,它是“memory buffer” 的缩写。在本章,我们要查看 m b u f和内核中用于操作它们的函数的更多的细节,在本书中几 乎每一页我们都会遇到 mbuf。要理解本书的其他部分必须先要理解 mbuf。 m b u f的主要用途是保存在进程和网络接口间互相传递的用户数据。但 m b u f也用于保存其 他各种数据:源与目标地址、插口选项等等。 图2 - 1显示了我们要遇到的四种不同类型的 m b u f,它们依据在成员 m _ f l a g s中填写的不 同标志M_PKTHDR和M_EXT而不同。图 2-1中四个mbuf的区别从左到右罗列如下: 1) 如果m_flags等于0,mbuf只包含数据。在mbuf中有108字节的数据空间 (m_dat数组)。 指针m _ d a t a指向这1 0 8字节缓存中的某个位置。我们所示的 m _ d a t a指向缓存的起始, 但它能指向缓存中的任意位置。成员 m _ l e n指示了从 m _ d a t a开始的数据的字节数。 图1-6是这类mbuf的一个例子。 在图2 - 1中,结构 m _ h d r中有六个成员,它的总长是 2 0字节。当我们查看此结构的 C语 言定义时,会看见前四个成员每个占用 4字节而后两个成员每个占用 2字节。在图 2 - 1中 我们没有区分4字节成员和 2字节成员。 2) 第二类m b u f的m _ f l a g s值是M _ P K T H D R,它指示这是一个分组首部,描述一个分组数 据的第一个 m b u f。数据仍然保存在这个 m b u f中,但是由于分组首部占用了 8字节,只 有1 0 0字节的数据可存储在这个 m b u f中(在m _ p k t d a t数组中)。图1 - 1 0是这种m b u f的一 个例子。 成员m _ p k t h d r . l e n的值是这个分组的 m b u f链表中所有数据的总长度:即所有通过 m _ n e x t 指针链接的 m b u f的m _ l e n 值的和,如图 1 - 8所示。输出分组没有使用成员 m _ p k t h d r . r c v i f,但对于接收的分组,它包含一个指向接收接口 i f n e t结构(图3 6)的指针。 3) 下一种m b u f不包含分组首部 (没有设置 K _ P K T H D R),但包含超过 2 0 8字节的数据,这时 用到一个叫“簇”的外部缓存 (设置 M _ E X T)。在此 m b u f中仍然为分组首部结构分配了 空间,但没有用—在图2 - 1中,我们用阴影显示出来。 N e t / 3分配一个大小为 1 0 2 4或 2 0 4 8字节的簇,而不是使用多个 m b u f来保存数据 (第一个带有 1 0 0字节数据,其余的每 个带有108字节数据)。在这个mbuf中,指针m_data指向这个簇中的某个位置。 N e t / 3版本支持七种不同的结构。定义了四种 1 0 2 4字节的簇 (惯例值),三种 2 0 4 8字节的

25

第 2章 mbuf:存储器缓存计计

簇。传统上用 1 0 2 4字节的原因是为了节约存储器:如果簇的大小是 2 0 4 8,对于以太网 分组 (最大 1 5 0 0字节 ),每个簇大约有四分之一没有用。在 2 7 . 5节中我们会看到 N e t / 3 T C P发送的每个 T C P报文段从来不超过一簇大小,因为当簇的大小为 1 0 2 4时,每个 1 5 0 0字节的以太网帧几乎三分之一未用。但是 [Mogul 1993,图1 5 - 1 5 ]显示了当在以太 网中发送最大帧而不是 1 0 2 4字节的帧时能明显提高以太网的性能。这就是一种性能 /存 储器互换。老的系统使用 1 0 2 4字节簇来节约存储器,而拥有廉价存储器的新系统用 2048字节的簇来提高性能。在本书中我们假定一簇的大小是 2048字节。

m_hdr{} (20字节)

pkthdr{} (8字节)

m_ext{} (12字节)

108字节的 缓存: m_dat

100字节的 缓存: m.pktdat

2048字节的簇 (外部缓存)

2048字节的簇 (外部缓存)

图2-1 根据不同 m_flags 值的四种不同类型的 mbuf

不幸的是,我们所说的“簇 ( c l u s t e r )”用过不同的名字。常量 M C L B Y T E S是这些 缓存 ( 1 0 2 4或2 0 4 8 )的大小,操作这些缓存的宏的名字是 M C L G E T、M C L A L L O C 和 M C L F R E E。这就是为什么称它们为“簇”的原因。但我们还看到

m b u f的标志是

M_EXT,它代表“外部的”缓存。最后, [Leffler et al. 1989]称它们为映射页 (mapped p a g e )。这后一种称法来源于它们的实现,在 2 . 9节我们会看到当要求一个副本时,这 些簇是可以共享的。 我们可能会希望这种类型的 mbuf的m _ l e n的最小值是209而不是我们在图中所示

26

计计TCP/IP详解 卷 2:实现

的2 0 8。这是指, 2 0 8字节数据的记录是可以存放在两个 m b u f中的,第一个 m b u f存放 100字节,第二个 mbuf存放108字节。但在源代码中有一个差错:若超过或等于 208就 分配一个簇。 4) 最后一类 m b u f包含一个分组首部,并包含超过 2 0 8 字节的数据。同时设置了标志 M_PKTHDR和M_EXT。 对于图2-1,我们还有另外几点需要说明: • m b u f结构的大小总是 1 2 8字节。这意味着图 2 - 1右边两个 m b u f在结构 m _ e x t后面的未用 空间为88字节(128-20-8-12)。 • 既然有些协议(例如UDP)允许零长记录,当然就可以有 m_len为0的数据缓存。 • 在每个 m b u f中的成员 m _ d a t a指向相应缓存的开始 ( m b u f缓存本身或一个簇 )。这个指针 能指向相应缓存的任意位置,不一定是起始。 队头 队尾 链中的下一个 mbuf

链中的下一个mbuf

mbuf 分组首部 50字节的数据

100字节的数据 以太网首部,IP 首部,TCP首部

链中的下一个mbuf 2048字节的簇

1460字节的数据 mbuf 分组首部

以太网首部,IP 首部,TCP首部

图2-2 在一个队列中的两个分组:第一个带有 192字节数据,第二个带有 1514字节数据

27

第 2章 mbuf:存储器缓存计计

• 带有簇的mbuf总是包含缓存的起始地址(m_ext.ext_buf)和它的大小(m_ext.ext_size)。 我们在本书采用的大小为 2048。成员 m _ d a t a和m _ e x t . e x t _ b u f值是不同的(如我们所 示),除非 m _ d a t a 也指向缓存的第一个字节。结构 m _ e x t的第三个成员 e x t _ f r e e, Net/3当前未用。 • 指针m_next把mbuf链接在一起,把一个分组 (记录)形成一条 mbuf链表,如图 1-8所示。 • 指针 m _ n e x t p k t把多个分组 (记录)链接成一个 m b u f链表队列。在队列中的每个分组可 以是一个单独的 m b u f,也可以是一个 m b u f链表。每个分组的第一个 m b u f包含一个分组 首部。如果多个 m b u f定义一个分组,只有第一个 m b u f的成员 m _ n e x t p k t被使用—链 表中其他mbuf的成员m_nextpkt全是空指针。 图2 - 2所示的是在一个队列中的两个分组的例子。它是图 1 - 8的一个修改版。我们已经把 UDP数据报放到接口输出队列中 (显示出14字节的以太网首部已经添加到链表中第一个 mbuf的 I P首部前面 ),并且第二个分组已经被添加到队列中: T C P段包含 1 4 6 0字节的用户数据。 T C P 数据包含在一个簇中,并且有一个 m b u f包含了它的以太网、 I P与T C P首部。通过这个簇,我 们可以看到指向簇的数据指针 (m _ d a t a)不需要指向簇的起始位置。我们所示的队列有一个头 指针和一个尾指针。这就是 N e t / 3处理接口输出队列的方法。我们给有 M _ E X T标志的m b u f还添 加了一个 m_ext结构,并且用阴影表示这个 mbuf中未用的pkthdr结构。 带有U D P数据报分组首部的第一个 m b u f的类型是 M T _ D A T A,但带有 T C P报文段 分组首部的第一个 m b u f的类型是 M T _ H E A D E R。这是由于U D P和T C P采用了不同的方 式往数据中添加首部造成的,但没有什么不同。这两种类型的 m b u f本质上一样。链 表中第一个 mbuf的m_flags的值M_PKTHDR指示了它是一个分组首部。 仔细的读者可能会注意到我们显示一个 mbuf的图(Net/3 mbuf,图2-1)与显示一个 Net/1 mbuf的图[Leffler et al. 1989,p.290]的区别。这个变化是在 Net/2中造成的:添 加了成员 m _ f l a g s,把指针 m _ a c t改名为 m _ n e x t p k t,并把这个指针移到这个 mbuf的前面。 在第一个 m b u f中,U D P与T C P协议首部位置的不同是由于 U D P调用M _ P R E P E N D (图23-15和习题23.1)而TCP调用MGETHDR(图26-25)造成的。

2.2 代码介绍 mbuf函数在一个单独的 C文件中,并且 mbuf宏与各种mbuf定义都在一个单独的头文件中, 如图2-3所示。 文



sys/mbuf.h kern/uipc_mbuf.c





mbuf结构、mbuf宏与定义 mbuf函数

图2-3 本章讨论的文件

2.2.1 全局变量 在本章中有一个全局变量要介绍,如图 2-4所示。

28

计计TCP/IP详解 卷 2:实现





mbstat

数 据 类 型 struct mbstat





mbuf的统计信息 (图2-5)

图2-4 本章介绍的全局变量

2.2.2 统计 在全局结构 mbstat中维护的各种统计,如图 2-5所示。 说

m b s t a t成员 m_clfree m_clusters m_drain m_drops m_mbufs m_mtypes[256] m_spare m_wait



自由簇 从页池中获得的簇 调用协议的 drain函数来回收空间的次数 寻找空间(未用)失败的次数 从页池(未用)中获得的mbuf数 当前mbuf的分配数: M T _xxx索引 剩余空间(未用) 等待空间(未用)的次数

图2-5 在结构mbstat 中维护的 mbuf统计

这个结构能被命令netstat -m检测;图2-6所示的是一些输出示例。关于所用映射页的数 量的两个值是:m_clusters(34)减m_clfree (32)—当前使用的簇数(2)和m_clusters(34)。 分配给网络的存储器的千字节数是 m b u f存储器 ( 9 9×1 2 8字节)加上簇存储器 ( 3 4×2 0 4 8字 节)再除以1 0 2 4。使用百分比是 m b u f存储器 ( 9 9×1 2 8字节)加上所用簇的存储器 ( 2×2 0 4 8字节) 除以网络存储器总数 (80千字节),再乘100。

图2-6 mbuf统计例子

2.2.3 内核统计 mbuf统计显示了在Net/3源代码中的一种通用技术。内核在一个全局变量 (在本例中是结构 m b s t a t)中保持对某些统计信息的跟踪。当内核在运行时,一个进程 (在本例中是 n e t s t a t程 序)可以检查这些统计。 不是提供系统调用来获取由内核维护的统计,而是进程通过读取链接编辑器在内核建立 时保存的信息来获得所关心的数据结构在内核中的地址。然后进程调用函数 k v m ( 3 ),通过使 用特殊文件 / d e v / m e m读取在内核存储器中的相应位置。如果内核数据结构从一个版本改变

29

第 2章 mbuf:存储器缓存计计

为下一版本,任何读取这个结构的程序也必须改变。

2.3 mbuf的定义 处理m b u f时,我们会反复遇到几个常量。它们的值显示在图 2 - 7中。除了 M C L B Y T E S定义 在文件/usr/include/machine/param.h中外,其他所有常量都定义在文件 mbuf.h中。 常



MCLBYTES MHLEN MINCLSIZE MLEN MSIZE

值(字节数) 2048 100 208 108 128





一个mbuf簇(外部缓存)的大小 带分组首部的 mbuf的最大数据量 存储到簇中的最小数据量 在正常mbuf中的最大数据量 每个mbuf的大小

图2-7 mbuf.h 中的mbuf常量

2.4 mbuf结构 图2-8所示的是mbuf结构的定义。

图2-8 mbuf 结构

30

计计TCP/IP详解 卷 2:实现

图2-8 (续)

结构m b u f是用一个 m _ h d r结构跟着一个联合来定义的。如注释所示,联合的内容依赖于 标志M_PKTHDR和M_EXT。 93-103

这11个#define语句简化了对mbuf结构中的结构与联合的成员的访问。我们会看

到这种技术普遍应用于 Net/3源代码中,只要是一个结构包含其他结构或联合这种情况。 我们在前面说明了在结构 m b u f中前两个成员的目的:指针 m _ n e x t把m b u f链接成一个 mbuf链表,而指针m_nextpkt把mbuf链表链接成一个 mbuf队列。 图1 - 8显示了每个 m b u f的成员 m _ l e n与分组首部中的成员 m _ p k t h d r . l e n的区别。后者 是链表中所有mbuf的成员m_len的和。 图2-9所示的是成员m_flags的五个独立的值。 说

m_flags M_BCAST M_EOR M_EXT M_MCAST M_PKTHDR M_COPYFLAGS



作为链路层广播发送 /接收 记录结束 此mubf带有簇(外部缓存 ) 作为链路层多播发送 /接收 形成一个分组 (记录)的第一个mbuf M_PKTHDR/M_EOR/M_BCAST/M_MCAST

图2-9 m_flags 值

我们已经说明了标志 M _ E X T和M _ P K T H D R。M _ E O R 在一个包含记录尾的 m b u f中设置。 Internet协议(例如TCP)从来不设置这个标志,因为 TCP提供一个无记录边界的字节流服务。但 是OSI与XNS运输层要用这个标志。在插口层我们会遇到这个标志,因为这一层是协议无关的, 并且它要处理来自或发往所有运输层的数据。 当要往一个链路层广播地址或多播地址发送分组,或者要从一个链路层广播地址或多播 地址接收一个分组时,在这个 m u b f中要设置接下来的两个标志 M _ B C A S T和M _ M C A S T。这两 个常量是协议层与接口层之间的标志 (图1-3)。 对于最后一个标志值 M _ C O P Y F L A G S,当一个 m b u f包含一个分组首部的副本时,这个标 志表明这些标志是复制的。 图2 - 1 0所示的常量 M T _x x x用于成员 m _ t y p e,指示存储在 m b u f中的数据的类型。虽然我 们总认为一个 m b u f是用来存放要发送或接收的用户数据,但 m b u f可以存储各种不同的数据结 构。回忆图 1 - 6中的一个 m b u f被用来存放一个插口地址结构,其中的目标地址用于系统调用 sendto。它的m_type成员被设置为MT_SONAME。

31

第 2章 mbuf:存储器缓存计计

不是图2 - 1 0中所有的 m b u f类型值都用于 N e t / 3。有些已不再使用 (M T _ H T A B L E),还有一些 不用于 T C P / I P代码中,但用于内核的其他地方。例如, M T _ O O B D A T A用于O S I和X N S协议, 但是T C P用不同方法来处理带外 ( o u t - o f - b a n d )数据(我们在 2 9 . 7节说明 )。当我们在本书的后面 遇到其他mbuf类型时会说明它们的用法。 Mbuf m_t y p e

用于Net/3 TCP/IP代码

MT_CONTROL MT_DATA MT_FREE MT_FTABLE MT_HEADER MT_HTABLE MT_IFADDR MT_OOBDATA MT_PCB MT_RIGHTS MT_RTABLE MT_SONAME MT_SOOPTS MT_SOCKET

• • • •

• •





外部数据协议报文 动态数据分配 应在自由列表中 分片重组首部 分组首部 IMP主机表 接口地址 加速(带外)数据 协议控制块 访问权限 路由表 插口名称 插口选项 插口结构

存储类型 M_MBUF M_MBUF M_FREE M_FTABLE M_MBUF M_HTABLE M_IFADDR M_MBUF M_PCB M_MBUF M_RTABLE M_MBUF M_SOOPTS M_SOCKET

图2-10 成员m_type 的值

本图的最后一列所示的 M_xxx值与内核为不同类型 mbuf分配的存储器片有关。这里有大约 6 0个可能的 M_x x x值指派给由内核函数 m a l l o c和宏M A L L O C分配的不同类型的存储器空间。 图2 - 6所示的是来源于命令 n e t s t a t -m的m b u f分配统计信息,它包括每种 M T _x x x类型的统 计。命令 v m s t a t - 显 m 示了内核的存储分配统计,包括每个 M_xxx类型的统计。 由于m b u f有一个固定长度 ( 1 2 8字节),因此对于 m b u f的使用有一个限制—包含 的数据不能超过 1 0 8字节。 N e t / 3用一个 m b u f来存储一个 T C P协议控制块 (在第2 4章我 们会涉及到 ),这个 m b u f的类型为 M T _ P C B。但是 4 . 4 B S D把这个结构的大小从 1 0 8字 节增加到140字节,并为这个结构使用一种不同的内核存储器分配类型。 仔细的读者会注意到图 2 - 1 0中我们表明未使用 M T _ P C B类型的 m b u f,而图 2 - 6显 示这个类型的计数不为零。 U n i x域协议使用这种类型的 m b u f,并且m b u f的统计功能 用于所有协议,而不只是 Internet协议,记住这一点很重要。

2.5 简单的mbuf宏和函数 有超过两打的宏和函数来处理 m b u f (分配一个 m b u f,释放一个 m b u f,等等)。让我们来查 看几个宏与函数的源代码,看看它们是如何实现的。 有些操作既提供了宏也提供了函数。宏版本的名称是以 M开头的大写字母名称,而函数是 以m _开始的小写字母名称。两者的区别是一种典型的时间 -空间互换。宏版本在每个被用到的 地方都被 C预处理器展开 (要求更多的代码空间 ),但是它在执行时更快,因为它不需要执行函 数调用(对于有些体系结构,这是费时的 )。而对于函数版本,它在每个被调用的地方变成了一 些指令(参数压栈,调用函数等 ),要求较少的代码空间,但会花费更多的执行时间。

32

计计TCP/IP详解 卷 2:实现

2.5.1 m_get函数 让我们先看一下图 2-11中分配mbuf的函数:m_get。这个函数仅仅就是宏 MGET的展开。

图2-11 m_get 函数:分配一个 mbuf

注意,Net/3代码不使用ANSI C 参数声明。但是,如果使用一个ANSI C 编译器,所 有Net/3系统头文件为所有的内核函数都提供了ANSI C函数原型。例如, 头文件中包含这样的行: struct mbuf *m_get(int, int);

这些函数原型为所有内核函数的调用提供编译期间的参数与返回值的检查。 这个调用表明参数 n o w a i t的值为M _ W A I T或M _ D O N T W A I T,它取决于在存储器不可用时 是否要求等待。例如,当插口层请求分配一个 m b u f来存储 s e n d t o系统调用 (图1 - 6 )的目标地 址时,它指定 M _ W A I T,因为在此阻塞是没有问题的。但是当以太网设备驱动程序请求分配一 个m b u f来存储一个接收的帧时 (图1 - 1 0 ),它指定 M _ D O N T W A I T,因为它是作为一个设备中断 处理来执行的,不能进入睡眠状态来等待一个 m b u f。在这种情况下,若存储器不可用,设备 驱动程序丢弃这个帧比较好。 2.5.2 MGET宏 图2 - 1 2所示的是 M G E T宏。调用 M G E T来分配存储 s e n d t o系统调用 (图1 - 6 )的目标地址的 mbuf如下所示: MGET(m, M_WAIT, MT_SONAME); if (m == NULL) return(ENOBUFS);

图2-12 MGET 宏

33

第 2章 mbuf:存储器缓存计计

虽然调用指定了 M _ W A I T,但返回值仍然要检查,因为,如图 2-13所示,等待一个 mbuf并 不保证它是可用的。 154-157

M G E T一开始调用内核宏 M A L L O C,它是通用内核存储器分配器进行的。数组

m b t y p e s把mbuf的M T _xxx值转换成相应的 M _xxx值(图2-10)。若存储器被分配,成员 m _ t y p e 被设置为参数中的值。 158 用于跟踪统计每种mbuf类型的内核结构加1(mbstat)。当执行这句时,宏MBUFLOCK把它作 为参数来改变处理器优先级(图1-13),然后把优先级恢复为原值。这防止在执行语句mbstat.m_ mtypes[type]++;时被网络设备中断,因为mbuf可能在内核中的各层中被分配。考虑这样一 个系统,它用三步来实现一个C中的++运算:(1)把当前值装入一个寄存器;(2)寄存器加1;(3)把 寄存器值存入存储器。假设计数器值为77并且MGET在插口层执行。假设执行了步骤1和2(寄存器 值为78),并且一个设备中断发生。若设备驱动也执行MGET来获得同种类型的mbuf,在存储器中 取值(77),加1(78),并存回在存储器。当被中断执行的MGET的步骤3继续执行时,它将寄存器的 值(78)存入存储器。但是计数器应为79,而不是78,这样计数器就被破坏了。 159-160

两个mbuf指针,m_next和m_nextpkt,被设置为空指针。若必要,由调用者把

这个mbuf加入到一个链或队列。 161-162 最后,数据指针被设置为指向 108字节的mbuf缓存的起始,而标志被设置为 0。 163-164 若内核的存储器分配调用失败,调用 m_retry(图2-13)。第一个参数是 M_WAIT或 M_DONTWAIT。 2.5.3 m_retry函数 图2-13所示的是m_retry函数。

图2-13 m_retry 函数

92-97

被m _ r e t r y调用的第一个函数是 m _ r e c l a i m。在7 . 4节我们会看到每个协议都能定

义一个“ d r a i n”函数,在系统缺乏可用存储器时能被 m _ r e c l a i m调用。在图 1 0 - 3 2中我们还 会发现当 I P的d r a i n函数被调用时,所有等待重新组成 I P数据报的 I P分片被丢弃。 T C P的d r a i n 函数什么都不做,而 UDP甚至就没有定义一个 drain函数。 98-102

因为在调用了 m _ r e c l a i m后有可能有机会得到更多的存储器,因此再次调用宏

M G E T,试图获得 m b u f。在展开宏 M G E T(图2 - 1 2 )之前, m _ r e t r y被定义为一个空指针。这可 以防止当存储器仍然不可用时的无休止的循环:这个 M G E T展开会把 m设置为空指针而不是调 用m _ r e t r y函数。在 M G E T展开以后,这个 m _ r e t r y的临时定义就被取消了,以防在此之后

34

计计TCP/IP详解 卷 2:实现

有对MGET的其他引用。 2.5.4 mbuf锁 在本节中我们所讨论的函数和宏并不调用 s p l函数,而是调用图 2-12中的M B U F L O C K来保 护这些函数和宏不被中断。但在宏 M A L L O C的开始包含一个 s p l i m p,在结束时有一个 s p l x。 宏M F R E E中包含同样的保护机制。由于 m b u f在内核的所有层中被分配和释放,因此内核必须 保护那些用于存储器分配的数据结构。 另外,用于分配和释放 m b u f簇的宏M C L A L L O C与M C L F R E E要用一个s p l i m p和一个s p l x 包括起来,因为它们修改的是一个可用簇链。 因为存储器分配与释放及簇分配与释放的宏被保护起来防止被中断,我们通常在 M G E T和 m_get这样的函数和宏的前后不再调用 spl函数。

2.6 m_devget和m_pullup函数 我们在讨论 IP、ICMP、IGMP、UDP和TCP的代码时会遇到函数 m _ p u l l u p。它用来保证 指定数目的字节 (相应协议首部的大小 )在链表的第一个 m b u f中紧挨着存放;即这些指定数目 的字节被复制到一个新的 m b u f并紧接着存放。为了理解 m _ p u l l u p的用法,必须查看它的实 现及相关的函数 m _ d e v g e t和宏m t o d与d t o m。在分析这些问题的同时我们还可以再次领会 Net/3中mbuf的用法。 2.6.1 m_devget函数 当接收到一个以太网帧时,设备驱动程序调用函数 m _ d e v g e t来创建一个 m b u f链表,并 把设备中的帧复制到这个链表中。根据所接收的帧的长度 (不包括以太网首部 ),可能导致 4种 不同的mbuf链表。图2-14所示的是前两种。

16字节 (未用)

52字节的数据 (以IP首部开始)

85字节的数据 (以IP首部开始)

32字节 (未用) 0≤数据长度≤84

15字节 (未用) 85≤数据长度≤100

图2-14 m_devget 创建的前两种类型的 mbuf

35

第 2章 mbuf:存储器缓存计计

链中的下一个mbuf

4字节的数据

100字节的数据 (以IP首部开始)

104字节 (未用)

101≤数据长度≤207

图2-15 m_devget 创建的第 3种mbuf

1) 图2 - 1 4左边的 m b u f用于数据的长度在 0 ~ 8 4字 节之间的情况。在这个图中,我们假定有 5 2 字节的数据:一个20字节的IP首部和一个32字 节的 T C P首部(标准的 2 0字节的 T C P首部加上 1 2字节的 T C P选项),但不包括 T C P数据。既 然m _ d e v g e t返回的m b u f数据从I P首部开始, m _ l e n的实际最小值是 2 8:2 0字节的I P首部, 8字节的UDP首部和一个0长度的UDP数据报。 m _ d e v g e t在这个 m b u f的开始保留了 1 6字节 未用。虽然 1 4字节的以太网首部不存放在这 里,还是分配了一个 1 4字节的用于输出的以 太网首部,这是同一个 m b u f,用于输出。我

88字节 (未用)

们会遇到两个函数: i c m p _ r e f l e c t 和 t c p _ r e s p o n d,它们通过把接收到的 m b u f 作为输出 m b u f来产生一个应答。在这两种情 况中,接收的数据报应该少于 84 字节,因此 很容易在前面保留 16 字节的空间,这样在建 立输出数据报时可以节省时间。分配 16 字节 而不是 1 4字节的原因是为了在 m b u f中用长字

208≤数据长度≤2048 2048字节簇 1500字节数据 (以IP首部开始)

对准方式存储IP首部。 2) 如果数据在 8 5 ~ 1 0 0字节之间,就仍然存放在 一个分组首部 m b u f中,但在开始没有 1 6字节

548字节(未用)

图2-16 m_devget 创建的第4种mbuf

36

计计TCP/IP详解 卷 2:实现

的空间。数据存储在数组 m _ p k t d a t的开始,并且任何未用的空间放在这个数组的后 面。例如在图2-14的右边的mbuf显示的就是这个例子,假设有 85字节数据。 3) 图2 - 1 5所示的是 m _ d e v g e t创建的第 3种m b u f。当数据在 1 0 1 ~ 2 0 7字节之间时,要求有 两个mbuf。前100字节存放在第一个 mbuf中(有分组首部的mbuf),而剩下的存放在第二 个m b u f中。在此例中,我们显示的是一个 1 0 4字节的数据报。在第一个 m b u f的开始没 有保留16字节的空间。 4) 图 2 - 1 6所示的是 m _ d e v g e t 创建的第四种 m b u f。如果数据超过或等于 208 字节 (M I N C L B Y T E S),要用一个或多个簇。图中的例子假设了一个 1 5 0 0字节的以太网帧。 如果使用 1 0 2 4字节的簇,本例子需要两个 m b u f,每个m b u f都有标志 M _ E X T,和指向一 个簇的指针。 2.6.2 mtod和dtom宏 宏mtod和dtom也定义在文件mbuf.h中。它们简化了复杂的 mbuf结构表达式。

m t o d(“m b u f到数据” )返回一个指向 m b u f数据的指针,并把指针声名为指定类型。例如 代码

存储在mbuf的数据(m _ d a t a)指针i p中。C编译器要求进行类型转换,然后代码用指针 i p引用 IP首部。我们可以看到当一个C结构(通常是一个协议首部 )存储在一个 mbuf中时会用到这个宏。 当数据存放在mbuf本身(图2-14和图2-15)或存放在一个簇中 (图2-16)时,可以用这个宏。 宏dtom (“数据到mbuf”)取得一个存放在一个 mbuf中任意位置的数据的指针,并返回这 个mbuf结构本身的一个指针。例如,若我们知道 ip指向一个mbuf的数据区,下面的语句序列

把指向这个 m b u f开始的指针存放到 m中。我们知道 M S I Z E(1 2 8)是2的幂,并且内核存储 器分配器总是为 mbuf分配连续的 M S I Z E字节的存储块, d t o m仅仅是清除参数中指针的低位来 发现这个mbuf的起始位置。 宏d t o m有一个问题:当它的参数指向一个簇,或在一个簇内,如图 2 - 1 6时,它不能正确 执行。因为那里没有指针从簇内指回 m b u f结构, d t o m不能被使用。这导致了下一个函数: m_pullup。 2.6.3 m_pullup函数和连续的协议首部 函数m _ p u l l u p有两个目的。第一个是当一个协议 ( I P、I C M P、I G M P、U D P或T C P )发现 在第一个 m b u f的数据量 (m _ l e n)小于协议首部的最小长度 (例如: I P是2 0,U D P是8,T C P是 2 0 ) 时。调用 m _ p u l l u p 是基于假定协议首部的剩余部分存放在链表中的下一个

m b u f。

37

第 2章 mbuf:存储器缓存计计

m _ p u l l u p重新安排 m b u f链表,使得前 N字节的数据被连续地存放在链表的第一个 m b u f中。N 是这个函数的一个参数,它必须小于或等于 1 0 0( M H L E N )。如果前 N字节连续存放在第一个 mbuf中,则可以使用宏 mtod和dtom。 例如,我们在IP输入例程中会遇到下面这样的代码:

如果第一个mbuf中的数据少于20(标准IP首部的大小 ),m _ p u l l u p被调用。函数 m _ p u l l u p有 两个原因会失败: ( 1 )如果它需要其他 m b u f并且调用 M G E T失败;或者 ( 2 )如果整个 m b u f链表中 的数据总数少于要求的连续字节数 (即我们所说的 N,在本例中是 2 0 )。通常,失败是因为第二 个原因。在此例中,如果 m_pullup失败,一个 IP计数器加 1,并且此IP数据报被丢弃。注意, 这段代码假设失败的原因是 mbuf 链表中数据少于 20字节。 实际上,在这种情况下, m _ p u l l u p很少能被调用 (注意,C语言的 & &操作符仅当 m b u f长 度小于期待值时才调用它 ),并且当它被调用时,它通常会失败。通过查看图 2-14~图2-16,我 们可以找到它的原因:在第一个 mbuf中,或在簇中,从IP首部开始有至少 100字节的连续字节。 这允许 6 0字节的最大 I P首部,并且后面跟着 4 0字节的 T C P首部(其他协议—— I C M P,I G M P和 U D P——它们的协议首部不到 4 0字节)。如果 m b u f链表中的数据可用 (分组不小于协议要求的 最小值 ),则所要求的字节数总能连续地存放在第一个 m b u f中。但是,如果接收的分组太小 (m _ l e n小于期待的最小值 ),则m _ p u l l u p被调用,并且它返回一个差错,因为在 m b u f链表 中没有所要求数目的可用数据。 源于伯克利的内核维护一个叫 M P F a i l的变量,每次 m _ p u l l u p失败时,它都加 1。在一个 N e t / 3系统中曾经接收了超过 2 7 0 0万的I P数据报,而 M P F a i l只有9。计数 器i p s t a t . i p s _ t o o s m a l l也是9,并且所有其他协议计数器 ( I C M P、I G M P、U D P 和T C P等)所计的m _ p u l l u p失败次数为 0。这证实了我们的断言:大多数 m _ p u l l u p 的失败是因为接收的 IP数据报太小。 2.6.4 m_pullup和IP的分片与重组 使用m _ p u l l u p的第二个用途涉及到 I P和T C P的重组。假定 I P接收到一个长度为 2 9 6的分 组,这个分组是一个大的 I P数据报的一个分片。这个从设备驱动程序传到 I P输入的 m b u f看起 来像我们在图 2 - 1 6中所示的一个 m b u f:2 9 6字节的数据存放在一个簇中。我们将这显示在图 2 17中。 问题在于, IP的分片算法将各分片都存放在一个双向链表中,使用 IP首部中的源与目标 IP 地址来存放向前与向后链表指针 (当然,这两个 I P地址要保存在这个链表的表头中,因为它们 还要放回到重组的数据报中。我们在第 10章讨论这个问题 )。但是如果这个IP首部在一个簇中, 如图2 - 1 7所示,这些链表指针会存放在这个簇中,并且当以后遍历链表时,指向 I P首部的指 针(即指向这个簇的起始的指针 )不能被转换成指向 m b u f的指针。这是我们在本节前面提到的 问题:如果 m _ d a t a指向一个簇时不能使用宏 d t o m,因为没有从簇指回 m b u f的指针。 I P分片

38

计计TCP/IP详解 卷 2:实现

不能如图2-17所示的把链指针存储在簇中。

IP分片的

IP分片的

双向链表

双向链表

276字节的数据

2048字节簇

图2-17 一个长度为 296的IP分片

为解决这个问题,当接收到一个分片时,若分片存放在一个簇中, I P分片例程总是调用 m_pullup。它强行将 20字节的IP首部放到它自己的 mbuf中。代码如下:

图2 - 1 8所示的是在调用了 m _ p u l l u p后得到的 m b u f链表。 m _ p u l l u p分配了一个新的 m u b f,挂在链表的前面,并从簇中取走 4 0字节放入到这个新的 m b u f中。之所以取 4 0字节而不 是仅要求的 2 0字节,是为了保证以后在 I P把数据报传给一个高层协议 (例如: I C M P,I G M P, U D P或T C P )时,高层协议能正确处理。采用不可思议的 4 0 (图7 - 1 7中的m a x _ p r o t o h d r)是因 为最大协议首部通常是一个 20 字节的 I P首部和 2 0字节的 T C P首部的组合 (这假设其他协议族, 例如OSI协议,并不编译到内核中 )。 在图 2 - 1 8中, I P分片算法在左边的 m b u f中保存了一个指向 I P首部的指针,并且可以用 dtom将这个指针转换成一个指向 mbuf本身的指针。

39

第 2章 mbuf:存储器缓存计计

IP分片的 双向 链表

IP首部

IP分片的 双向 链表

数据报的下20字节

数据报的下256字节

2048字节的簇

图2-18 调用m_pullup 后的长度为 296的IP分片

2.6.5 TCP重组避免调用m_pullup 重组TCP报文段使用一个不同的技术而不是调用 m _ p u l l u p。这是因为 m _ p u l l u p开销较 大:分配存储器并且数据从一个簇复制到一个 mbuf中。TCP试图尽可能地避免数据的复制。 卷1的第1 9章提到大约有一半的 T C P数据是批量数据 (通常每个报文段有 5 1 2或更多字节的 数据),并且另一半是交互式数据 (这里面有大约 9 0%的报文段包含不到 1 0字节的数据 )。因此, 当T C P从I P接收报文段时,它们通常是如图 2 - 1 4左边所示的格式 (一个小量的交互数据,存储 在m b u f本身 )或图 2 - 1 6所示的格式 (批量数据,存储在一个簇中 )。当T C P报文段失序到达时, 它们被TCP存储到一个双向链表中。如 IP分片一样,在 IP首部的字段用于存放链表的指针,既 然这些字段在 TCP接收了IP数据报后不再需要,这完全可行。但当 IP首部存放在一个簇中,要 将一个链表指针转换成一个相应的 mbuf指针时,会引起同样的问题 (图2-17)。 为解决这个问题,在 27.9节中我们会看到 TCP把mbuf指针存放在 TCP首部中的一些未用的 字段中,提供一个从簇指回 m b u f的指针,来避免对每个失序的报文段调用 m _ p u l l u p。如果 I P首部包含在 m b u f的数据区 (图2 - 1 8 ),则这个回指指针是无用的,因为宏 d t o m对这个链表指 针会正常工作。但如果 IP首部包含在一个簇中,这个回指指针将被使用。当我们在讨论 27.9节 的tcp_reass时,会研究实现这种技术的源代码。

40

计计TCP/IP详解 卷 2:实现

2.6.6 m_pullup使用总结 我们已经讨论了关于使用 m_pullup的三种情况: • 大多数设备驱动程序不把一个 IP数据报的第一部分分割到几个 mbuf中。假设协议首部都 紧挨着存放,则在每个协议 ( I P、I C M P、I G M P、U D P和T C P )中调用 m _ p u l l u p的可能 性很小。如果调用 m _ p u l l u p,通常是因为 I P数据报太小,并且 m _ p u l l u p返回一个差 错,这时数据报被丢弃,并且差错计数器加 1。 • 对于每个接收到的IP分片,当IP数据报被存放在一个簇中时, m_pullup被调用。这意味 着,几乎对于每个接收的分片都要调用m_pullup,因为大多数分片的长度大于208 字节。 • 只要 T C P 报文段不被 I P 分片,接收一个 T C P 报文段,不论是否失序,都不需调用 m_pullup。这是避免 IP对TCP分片的一个原因。

2.7 mbuf宏和函数的小结 在操作mbuf的代码中,我们会遇到图 2-19中所列的宏和图 2-20中所列的函数。图 2-19中的 宏以函数原型的形式显示,而不是以 # d e f i n e形式来显示参数的类型。由于这些宏和函数主 要用于处理 m b u f数据结构并且不涉及联网问题,因此我们不查看实现它们的源代码。还有另 外一些m b u f宏和函数用于 N e t / 3源代码的其他地方,但由于我们在本书中不会遇到它们,因此 没有把它们列于图中。 宏 MCLGET





获得一个簇 (一个外部缓存 )并将m指向的m b u f中的数据指针 ( m _ d a t a)设置为指向这个 簇。如果存储器不可用,返回时不设置 mbuf中的M _ E X T标志 void MCLGET(struct mbuf * m, int nowait);

MFREE

释放一个m指向的mbuf。若m指向一个簇 (设置了M _ E X T),这个簇的引用计数器减 1,但 这个簇并不被释放,直到它的引用计数器降为 0 (如 2 . 9 节所述 ) 。返回 m 的后继 ( 由 m ->m_n e x t指向,可以为空 )存放在n中 void MFREE(struct mbuf * m, struct mbuf *n);

MGETHDR

分配一个mbuf,并把它初始化为一个分组首部。这个宏与 M G E T(图2-12)相似,但设置 了标志M _ P K T H D R,并且数据指针 (m _ d a t a)指向紧接分组首部后的 100字节的缓存 void MGETHDR(struct mbuf * m, int nowait, int type);

MH_ALIGN

设置包含一个分组首部的 mbuf的m _ d a t a,在这个mbuf数据区的尾部为一个长度为 l e n 字节的对象提供空间。这个数据指针也是长字对准方式的 void MH_ALIGN(struct mbuf * m, int len);

M_PREPEND

在m指向的mbuf中的数据的前面添加len字节的数据。如果mbuf有空间,则仅把指针(m_data) 减len字节,并将长度 (m_len)增加len字节。如果没有足够的空间,就分配一个新的 mbuf, 它的m_next指针被设置为m。一个新mbuf的指针存放在m中。并且新mbuf的数据指针被设置, 这样len字节的数据放置到这个mbuf的尾部(例如,调用MH_ALIGN)。如果一个新mbuf被分配, 并且原来的mbuf的分组首部标志被设置,则分组首部从老mbuf中移到新mbuf中 void M_PREPEND(struct mbuf * m, int len, int nowait);

dtom

将指向一个 mbuf数据区中某个位置的指针 x转换成一个指向这个 mbuf的起始的指针。 struct mbuf *dtom(void *x);

mtod

将m指向的mbuf的数据区指针的类型转换成 type类型 t y p e m t o d( s t r u c t m b u f m, * type) ;

图2-19 我们在本书中会遇到的 mbuf宏

41

第 2章 mbuf:存储器缓存计计





m_adj





从m指向的m b u f中移走len字节的数据。如果 len是正数,则所操作的是紧排在这个 mbuf的开始的len字节数据;否则是紧排在这个 mbuf的尾部的 len绝对值字节数据 void m_adj(struct mbuf *m, int len);

m_cat

把由n指向的mbuf链表链接到由 m指向的m b u f链表的尾部。当我们讨论 IP重组时(第10 章)会遇到这个函数 void m_cat(struct mbuf *m, struct mbuf *n);

m_copy

这是m _ c o p y m的三参数版本,它隐含的第 4个参数的值为 M _ D O N T W A I T struct mbuf * m_copy(struct mbuf *m, int offset, int len);

m_copydata

从m指向的mbuf链表中复制 len字节数据到由 cp指向的缓存。从 mbuf链表数据区起始的 offset字节开始复制 void m_copydata(struct mbuf *m, int offset, int len, caddr_t cp);

m_copyback

从cp指向的缓存复制len字节的数据到由 m指向的mbuf,数据存储在 mbuf链表起始 offset字节后。必要时, mbuf链表可以用其他 mbuf来扩充 void m_copyback(struct mbuf *m, int offset, int len, caddr_t cp);

m_copym

创建一个新的 m b u f链表,并从 m指向的 m b u f链表的开始 o f f s e t处复制 l e n字节的数据。 一个新 m b u f链表的指针作为此函数的返回值。如果 l e n等于常量 M _ C O P Y A L L ,则从这 个m b u f链表的 o f f s e t开始的所有数据都将被复制。在 2 . 9节中,我们会更详细地介绍这个 函数 struct mbuf *m_copym(struct mbuf *m, int offset, int len, int nowait);

m_devget

创建一个带分组首部的 mbuf链表,并返回指向这个链表的指针。这个分组首部的 len和 rcvif字段被设置为 len和ifp。调用函数copy从设备接口 (由buf指向)将数据复制到 mbuf中。 如果c o p y是一个空指针,调用函数 b c o p y。由于尾部协议不再被支持, o f f为0。我们在 2.6节讨论了这个函数 struct mbuf * m_devget(char *buf, int len, int off, struct ifnet * ifp, void (*copy)(const void *, void *, u_int));

m_free

宏M F R E E的函数版本 struct mbuf *m_free(struct mbuf * m);

m_freem

释放m指向的链表中的所有 mbuf void m_freem(struct mbuf * m);

m_get

宏M G E T的函数版本。我们在图 2-12中显示过此函数 struct mbuf *m_get(int nowait, int type);

m_getclr

此函数调用宏 M G E T来得到一个 mbuf,并把108字节的缓存清零 struct mbuf *m_getclr(int nowait, int type);

m _ g ethdr

宏M G E T H D R的函数版本 struct mbuf *m_gethdr(int nowait, int type);

m_pullup

重新排列由 m指向的mbuf中的数据,使得前 len字节的数据连续地存储在链表中的第一 个m b u f中。如果这个函数成功,则宏 m t o d能返回一个正好指向这个大小为 l e n的结构。 我们在2 . 6节讨论了这个函数 s t r u c t m b u f m*_ p u l l u p( s t r u c t m b u f m, * int len) ;

图2-20 在本书中我们要遇到的 mbuf函数

所有原型的参数 n o w a i t是M _ W A I T或M _ D O N T W A I T,参数t y p e是图2 - 1 0中所示的 M T _x x x中 的一个。

42

计计TCP/IP详解 卷 2:实现

M _ P R E P E N D的一个例子是,从图 1 - 7转换到图 1 - 8的过程中,当 I P和U D P首部被添加到数 据的前面时要调用这个宏,因为另一个 m b u f要被分配。但当这个宏再次被调用 (从图1 - 8转换 成图2-2)来添加以太网首部时,在那个 mbuf中已有存放这个首部的空间。 M _ c o p y d a t a的最后一个参数的类型是 c a d d r _ t,它代表“内核地址”。这个 数据类型通常定义在 中,为char *。它最初在内核中使用,但被 某些系统调用使用时被外露出来。例如, m m a p系统调用,不论是 4 . 4 B S D或S V R 4都 把caddr_t作为第一个参数的类型并作为返回值类型。

2.8 Net/3联网数据结构小结 本节总结我们在 N e t / 3联网代码中要遇到的数据结构类型。在 N e t / 3内核中用到其他数据结 构(感兴趣的读者可以查看头文件 ),但下面这些是我们在本书中要遇到的。 1) 一个mbuf链:一个通过m_next指针链接的 mbuf链表。我们已经看过几个这样的例子。 2) 只有一个头指针的 m b u f链的链表。 m b u f链通过每个链的第一个 m b u f中的m _ n e x t p k t 指针链接起来。 图2-21所示的就是这种链表。这种数据结构的例子是一个插口发送缓存和接收缓存。

图2-21 只有头指针的 mbuf链的链表

顶部的两个 m b u f形成这个队列中的第一个记录,底下三个 m b u f形成这个队列的第二个 记录。对于一个基于记录的协议,如 U D P,我们在每个队列中能遇到多个记录。但对 于像T C P这样的协议,它没有记录的边界,每个队列我们只能发现一个记录 (一个m b u f 链可能包含多个 mbuf)。 把一个 m b u f追加到队列的第一个记录中要遍历所有第一个记录的

m b u f,直到遇到

m _ n e x t为空的 m b u f。而追加一个包含新记录的 m b u f链到这个队列中,要查找所有记 录直到遇到 m_nextpkt为空的记录。 3) 一个有头指针和尾指针的 mbuf链的链表。 图2 - 2 2显示的是这种类型的链表。我们在接口队列中会遇到它 (图3 - 1 3 ),并且在图 2 - 2 中已显示过它的一个例子。 在图2-21中仅有一点改变:增加了一个尾指针,来简化增加一个新记录的操作。 4) 双向循环链表。

43

第 2章 mbuf:存储器缓存计计

图2-22 有头指针和尾指针的链表

图2 - 2 3所示的是这种类型的链表,我们在 I P分片与重装 (第1 0章)、协议控制块 (第2 2章) 及TCP失序报文段队列 (第27.9节)中会遇到这种数据结构。 列表表头

图2-23 双向循环链表

在这个链表中的元素不是 m b u f— 它们是一些定义了两个相邻的指针的结构:一个 n e x t指针跟着一个 p r e v i o u s指针。两个指针必须在结构的起始处。如果链表为空,表头 的next和previous指针都指向这个表头本身。 在图中我们简单地把向后指针指向另一个向后指针。显然所有的指针应包含它所指向 的结构的地址,即向前指针的地址 (因为向前和向后指针总是放在结构的起始处 )。 这种类型的数据结构能方便地向前向后遍历,并允许方便地在链表中任何位置进行插 入与删除。 函数insque和remque(图10-20)被调用来对这个链表进行插入和删除。

2.9 m_copy和簇引用计数 使用簇的一个明显的好处就是在要求包含大量数据时能减少 m b u f的数目。例如,如果不 使用簇,要有 1 0个m b u f才能包含 1 0 2 4字节的数据:第一个 m b u f带有1 0 0字节的数据,后面 8个 每个存放 1 0 8字节数据,最后一个存放 6 0字节数据。分配并链接 1 0个m b u f比分配一个包含 1 0 2 4字节簇的 m b u f开销要大。簇的一个潜在缺点是浪费空间。在我们的例子中使用一个簇 (2048 + 128)要2176字节,而1280字节不到1簇(10×128)。 簇的另外一个好处是在多个 mbuf间可以共享一个簇。在 TCP输出和m _ c o p y函数中我们遇 到过这种情况,但现在我们要更详细地说明这个问题。

44

计计TCP/IP详解 卷 2:实现

例如,假设应用程序执行一个 w r i t e,把4 0 9 6字节写到 T C P插口中。假设插口发送缓存 原来是空的,接收窗口至少有 4 0 9 6,则会发生以下操作。插口层把前 2 0 4 8字节的数据放在一 个簇中,并且调用协议的发送例程。 T C P发送例程把这个 m b u f追加到它的发送缓存后,如图 2 - 2 4所示,并调用 t c p _ o u t p u t。结构 s o c k e t中包含 s o c k b u f结构,这个结构中存储着发送 缓存mbuf链的链表的表头: so_snd.sb_mb。

2048字节簇

2048字节的数据

图2-24 包含2048字节数据的 TCP插口发送缓存

假设这个连接 (典型的是以太网 )的一个T C P最大报文段大小 ( M S S )为1 4 6 0,t c p _ o u t p u t 建立一个报文段来发送包含前 1 4 6 0字节的数据。它还建立一个包含 I P和T C P首部的 m b u f,为 链路层首部 ( 1 6字节)预留了空间,并将这个 m b u f链传给 I P输出。在接口输出队列尾部的 m b u f 链显示在图 2-25中。 在1.9节的UDP例子中, UDP用mbuf链来存放数据报,在前面添加一个 mbuf来存放协议首 部,并把此链传给 I P输出。U D P并不把这个m b u f保存在它的发送缓存中。而 T C P不能这样做, 因为TCP是一个可靠协议,并且它必须维护一个发送数据的副本,直到数据被对方确认。 在这个例子中, t c p _ o u t p u t调用函数 m _ c o p y,请求复制 1 4 6 0字节的数据,从发送缓 存起始位置开始。但由于数据被存放在一个簇中, m _ c o p y创建一个 m b u f (图2 - 2 5的右下侧 )并 且对它初始化,将它指向那个已存在的簇的正确位置 (此例中是簇的起始处 )。这个 m b u f的长 度是1 4 6 0,虽然有另外 5 8 8字节的数据在簇中。我们所示的这个 m b u f链的长度是 1 5 1 4,包括 以太网首部、IP首部和TCP首部。 在图2 - 2 5的右下侧我们还显示了这个 m b u f包含一个分组首部,但它不是链中的 第一个 m b u f。当m _ c o p y复制一个包含一个分组首部的 m b u f并且从原来 m b u f的起始 地址开始复制时,分组首部也被复制下来。因为这个 m b u f不是链中的第一个 m b u f,

45

第 2章 mbuf:存储器缓存计计

这个额外的分组首部被忽略。而在这个额外的分组首部中的 m _ p k t h d r . l e n的值 2048也被忽略。

TCP发送缓存

2048字节簇

2048字节的数据

有要发送的 TCP报文段 的接口输出 队列

以太网首部, IP首部, TCP首部

图2-25 TCP插口发送缓存和接口输出队列中的报文段

这个共享的簇避免了内核将数据从一个 m b u f复制到另一个 m b u f中— 这节约了很多开 销。它是通过为每个簇提供一个引用计数来实现的,每次另一个 m b u f指向这个簇时计数加 1, 当一个簇释放时计数减 1。仅当引用计数到达 0时,被这个簇占用的存储器才能被其他程序使 用(见习题2.4)。 例如,当图 2 - 2 5底部的 m b u f链到达以太网设备驱动程序并且它的内容已被复制给这个设 备时,驱动程序调用 m _ f r e e m。这个函数释放带有协议首部的第一个 m b u f,并注意到链中第

46

计计TCP/IP详解 卷 2:实现

二个m b u f指向一个簇。簇引用计数减 1,但由于它的值变成了 1,它仍然保存在存储器中。它 不能被释放,因为它仍在 TCP发送缓存中。

2048字节簇

2048字节簇

1460字节的数据 2048字节的数据 588字节的数据

以太网首部, IP首部, TCP首部

图2-26 用于发送 1460字节TCP报文段的mbuf链

继续我们的例子,由于在发送缓存中剩余的 588字节不能组成一个报文段, t c p _ o u t p u t 在把1 4 6 0字节的报文段传给 I P后返回(在第2 6章我们要详细说明在这种条件下 t c p _ o u t p u t发 送数据的细节 )。插口层继续处理来自应用程序的数据:剩下的 2 0 4 8字节被存放到一个带有一 个簇的 m b u f中,T C P发送例程再次被调用,并且新的 m b u f被追加到插口发送缓存中。因为能 发送一个完整的报文段,tcp_output建立另一个带有协议首部和 1460字节数据的 mbuf链表。

47

第 2章 mbuf:存储器缓存计计

m _ c o p y的参数指定了 1 4 6 0字节的数据在发送缓存中的起始位移和长度 ( 1 4 6 0字节)。这显示在 图2-26中,并假设这个 mbuf链在接口输出队列中 (这个链中的第一个 mbuf的长度反映了以太网 首部、IP首部及TCP首部)。 这次1 4 6 0字节的数据来自两个簇:前 5 8 8字节来自发送缓存的第一个簇而后面的 8 7 2字节 来自发送缓存的第二个簇。它用两个 m b u f来存放 1 4 6 0字节,但 m _ c o p y还是不复制这 1 4 6 0字 节的数据 —它引用已存在的簇。 这次我们没有在图 2 - 2 6右下侧的任何 m b u f中显示一个分组首部。原因是调用 m _ c o p y的起始位移为零。但在插口发送缓存中的第二个 mbuf包含一个分组首部,而 不是链中的第一个 mbuf。这是函数 s o s e n d的特点,这个额外的分组首部被简单地忽 略了。 我们在通篇中会多次遇到函数 m _ c o p y。虽然这个名字隐含着对数据进行物理复制,但如 果数据被包含在一个簇中,却是仅引用这个簇而不是复制。

2.10 其他选择 m b u f远非完美,并且时常遭到批评。但不管怎样,它们形成了所有今天正使用着的伯克 利联网代码的基础。 一种由Van Jacobson [Partridge 1993]完成的I n t e r n e t协议的研究实现,它废除了支持大量 连续缓存的复杂的 m b u f数据结构。 [Jacobson 1993]提出了一种速度能提高一到两个数量级的 改进方案,还包括其他改进,及废除 mbuf。 这个m b u f的复杂性是一种权衡,以避免分配大的固定长度的缓存,这样的大缓存很少能 被装满。而在这种情况下, m b u f要进行设计,一个 VA X - 11 / 7 8 0有4兆存储器,是一个大系统, 并且存储器是昂贵的资源,需要仔细分配。今天存储器已不昂贵了,而焦点已经转向更高的 性能和代码的简单性。 mbuf的性能基于存放在 mbuf中数据量。 [Hutchinson and Peterson 1991]显示了处理 mbuf的 时间与数据量不是线性关系。

2.11 小结 在本书几乎所有的函数中我们都会遇到 m b u f。它们的主要用途是在进程和网络接口之间 传递用户数据时用来存放用户数据,但 m b u f还用于保存其他各种数据:源地址和目标地址、 插口选项等等。 根据M_PKTHDR和M_EXT标志是否被设置,这里有 4种类型的mbuf: • 无分组首部,mbuf本身带有0~108字节数据; • 有分组首部,mbuf本身带有0~100字节数据; • 无分组首部,数据在簇 (外部缓存)中; • 有分组首部,数据在簇 (外部缓存)中。 我们查看了几个 mbuf宏和函数的源代码,但不是所有的 mbuf例程源代码。图2-19和图2-20 提供了所有我们在本书中遇到的 mbuf例程的函数原型和说明。 查看了我们要遇到的两个函数的操作: m _ d e v g e t,很多网络设备驱动程序调用它来存

48

计计TCP/IP详解 卷 2:实现

储一个收到的帧; m_pullup,所有输入例程调用它把协议首部连续放置在一个 mbuf中。 由一个m b u f指向的簇 (外部缓存 )能通过m _ c o p y被共享。例如,用于 T C P输出,因为一个 被传输的数据的副本要被发送端保存,直到数据被对方确认。比起进行物理复制来说,通过 引用计数,共享簇提高了性能。

习题 2.1 在图2-9中定义了M_COPYFLAGS。为什么不复制标志 M_EXT? 2.2 在2 . 6节中,我们列出了两个 m _ p u l l u p失败的原因。实际上有三个原因。查看这个 函数的源代码(附录B),并发现另外一个原因。 2.3 为避免宏 d t o m遇到在 2 . 6节中我们所讨论的问题—当数据在簇中时,为什么不仅 仅给每个簇加一个指向 mbuf的回指指针? 2.4 既然一个 m b u f簇的大小是2的幂(典型的是 1 0 2 4或2 0 4 8 ),簇内的空间不能用于引用计 数。查看Net/3的源代码(附录B),并确定这些引用计数存储在什么地方。 2.5 在图2-5中,我们注意到两个计数器 m _ d r o p s和m _ w a i t现在没有实现。修改 mbuf例 程增加这些计数器。

第3章 接 口 层 3.1 引言 本章开始讨论 N e t / 3在协议栈底部的接口层,它包括在本地网上发送和接收分组的硬件与 软件。 我们使用术语设备驱动程序来表示与硬件及网络接口 (或仅仅是接口 )通信的软件,网络接 口是指在一个特定网络上硬件与设备驱动器之间的接口。 N e t / 3接口层试图在网络协议和连接到一个系统的网络设备的驱动器间提供一个与硬件无 关的编程接口。这个接口层为所有的设备提供以下支持: • 一套精心定义的接口函数; • 一套标准的统计与控制标志; • 一个与设备无关的存储协议地址的方法; • 一个标准的输出分组的排队方法。 这里不要求接口层提供可靠的分组传输,仅要求提供最大努力 ( b e s t - e ff o r t )的服务。更高 协议层必须弥补这种可靠性缺陷。本章说明为所有网络接口维护的通用数据结构。为了说明 相关数据结构和算法,我们参考 Net/3中三种特定的网络接口: 1) 一个AMD 7990 LANCE以太网接口:一个能广播局域网的例子。 2) 一个串行线 IP(SLIP)接口:一个在异步串行线上的点对点网络的例子。 3) 一个环回接口:一个逻辑网络把所有输出分组作为输入返回。

3.2 代码介绍 通用接口结构和初始化代码可在三个头文件和两个 C文件中找到。在本章说明的设备专用 初始化代码可在另外三个 C文件中找到。所有的 8个文件都列于图 3-1中。 文





sys/socket.h net/if.h net/if_dl.h kern/init_main.c net/if.c net/if_loop.c net/if_sl.c hp300/dev/if_le.c

地址结构定义 接口结构定义 链路层结构定义 系统和接口初始化 通用接口代码 环回设备驱动程序 SLIP设备驱动程序 LANCE以太网设备驱动程序

图3-1 本章讨论的文件

3.2.1 全局变量 在本章中介绍的全局变量列于图 3-2中。



50

计计TCP/IP详解 卷 2:实现





数据类型

pdevinit ifnet ifnet_addrs if_indexlim if_index ifqmaxlen hz



struct pdevinit[] struct ifnet * struct ifaddr ** int int int int



伪设备如SLIP和环回接口的初始化参数数组 i f n e t结构的列表的表头 指向链路层接口地址的指针数组 数组i f n e t _ a d d r s的大小 上一个配置接口的索引 接口输出队列的最大值 这个系统的时钟频率 (次/秒)

图3-2 本章中介绍的全局变量

3.2.2 SNMP变量 N e t / 3内核收集了大量的各种联网统计。在大多数章节中,我们都要总结这些统计并说明 它们与定义在简单网络管理协议信息库 (SNMP MIB-II) 中的标准 T C P / I P信息和统计之间的关 系。RFC 1213 [McCloghrie and Rose 1991]说明了SNMP MIB-II,它组织成如图 3-3所示的10 个不同的信息组。 说

SNMP组



系统通用信息 网络接口信息 网络地址到硬件地址的映射表 (不推荐使用 ) IP协议信息 ICMP协议信息 TCP协议信息 UDP协议信息 EGP协议信息 媒体专用信息 SNMP协议信息

System Interfaces Address Translation IP ICMP TCP UDP EGP Transmission SNMP

图3-3 MIB-II中的SNMP组

N e t / 3并不包括一个 S N M P代理。一个针对 N e t / 3的S N M P代理是作为一个进程来实现的, 它根据SNMP的要求通过 2.2节描述的机制来访问这些内核统计。 N e t / 3收集大多数 M I B - I I变量并且能被 S N M P代理直接访问,而其他的变量则要通过间接 的方式来获得。 M I B - I I变量分为三类: ( 1 )简单变量,例如一个整数值、一个时间戳或一个字 节串;( 2 )简单变量的列表,例如一个单独的路由项或一个接口描述项; ( 3 )表的列表,例如整 个路由表和所有接口实体的列表。 ISODE包含有一个 Net/3 SNMP代理例子。 ISODE的信息见附录B。 图3 - 4所示的是一个为 S N M P接口组维护的简单变量。我们在后面的图 4 - 7中描述S N M P接 口表。 SNMP变量

Net/3变量

ifNumber

if_index + 1





i f _ i n d e x是系统中最后一个接口的索引值,并且起始 为0;加1来获得系统中接口个数 i f N u m b e r

图3-4 在接口组中的一个简单的 SNMP变量

51

第 3章 接 口 层计计

3.3 ifnet结构 结构i f n e t中包含所有接口的通用信息。在系统初始化期间,分别为每个网络设备分配 一个独立的 i f n e t结构。每个 i f n e t结构有一个列表,它包含这 个设备的一个或多个协议地址。图 3-5说明了一个接口和它的地址 之间的关系。 在图3 - 5中的接口显示了 3个存放在 i f a d d r结构中的协议地 址。虽然一些网络接口,例如 S L I P,仅支持一个协议;而其他接 口,如以太网,支持多个协议并需要多个地址。例如,一个系统 可能使用一个以太网接口同时用于 I n t e r n e t和O S I两个协议。一个 类型字段标识每个以太网帧的内容,并且因为 I n t e r n e t和O S I协议 使用不同的编址方式,以太网接口必须有一个 I n t e r n e t地址和一个 O S I地址。所有地址用一个链表链接起来 (图3 - 5右侧的箭头 ),并 且每个结构包含一个回指指针指向相关的 i f n e t结构(图3 - 5左侧 图3-5 每个ifnet 结构有一个 ifaddr 结构的列表

的箭头)。

可能一个网络接口支持同一协议的多个地址。例如,在 N e t / 3中可能为一个以太网接口分 配两个Internet地址。 这个特点第一次是出现在 N e t / 2中。当为一个网络重编地址时,一个接口有两个 IP地址是有用的。在过渡期间,接口可以接收老地址和新地址的分组。 结构ifnet比较大,我们分五个部分来说明: • 实现信息 • 硬件信息 • 接口统计 • 函数指针 • 输出队列 图3-6所示的是包含在结构 ifnet中的实现信息。

图3-6 ifnet 结构:实现信息

80-82

i f _ n e x t把所有接口的 i f n e t结构链接成一个链表。函数 i f _ a t t a c h在系统初始

化期间构造这个链表。 i f _ a d d r l i s t 指向这个接口的 i f a d d r 结构列表 (图3 - 1 6 )。每个 ifaddr结构存储一个要用这个接口通信的协议的地址信息。 1. 通用接口信息

52

计计TCP/IP详解 卷 2:实现

83-86

if_name是一个短字符串,用于标识接口的类型,而 if_unit标识多个相同类型的

实例。例如,一个系统有两个 S L I P接口,每个都有一个 i f _ n a m e,包含两字节的“ s 1”和 一个i f _ u n i t。对第一个接口, i f _ u n i t为0;对第二个接口为 1。i f _ i n d e x在内核中唯一 地标识这个接口,这在 sysctl系统调用(见19.14节)以及路由域中要用到。 有时一个接口并不被一个协议地址唯一地标识。例如,几个 S L I P连接可以有同 样的本地IP地址。在这种情况下, if_index明确地指明这个接口。 i f _ f l a g s表明接口的操作状态和属性。一个进程能检查所有的标志,但不能改变在图 37中“内核专用”列中作了记号的标志。这些标志用 4 . 4节讨论的命令 S I O C G I F F L A G S和 SIOCSIFFLAGS来访问。 if_flags

内核专用





IFF_BROADCAST IFF_MULTICAST IFF_POINTOPOINT IFF_LOOPBACK IFF_OACTIVE IFF_RUNNING IFF_SIMPLEX

• • • • • •

接口用于广播网 接口支持多播 接口用于点对点网络 接口用于环回网络 正在传输数据 资源已分配给这个接口 接口不能接收它自己发送的数据

IFF_LINK0 IFF_LINK1 IFF_LINK2

见正文 见正文 见正文

由设备驱动程序定义 由设备驱动程序定义 由设备驱动程序定义 接口正接收所有多播分组 这个接口允许调试 在这个接口上不使用 ARP协议 避免使用尾部封装 接口接收所有网络分组 接口正在工作

IFF_ALLMULTI IFF_DEBUG IFF_NOARP IFF_NOTRAILERS IFF_PROMISC IFF_UP

图3-7 if_flags 值

IFF_BROADCAST和IFF_POINTOPOINT标志是互斥的。 宏I F F _ C A N T C H A N G E是对所有在“内核专用”列中作了记号的标志进行按位 “或”操作。 设备专用标志 (I F F _ L I N Kx)对于一个依赖这个设备的进程可能是可修改的,也 可能是不可修改的。例如,图 3-29显示了这些标志是如何被 SLIP驱动程序定义的。 2. 接口时钟 87 if_timer以秒为单位记录时间,直到内核为此接口调用函数 if_watchdog为止。这个 函数用于设备驱动程序定时收集接口统计,或用于复位运行不正确的硬件。 3. BSD分组过滤器 88-89

下面两个成员, if_pcount和if_bpf,支持BSD分组过滤器 (BPF)。通过BPF,一

个进程能接收由此接口传输或接收的分组的备份。当我们讨论设备驱动程序时,还要讨论分 组是如何通过BPF的。BPF在第31章讨论。 ifnet结构的下一个部分显示在图 3-8中,它用来描述接口的硬件特性。

53

第 3章 接 口 层计计

图3-8 ifnet 结构:接口特性

N e t / 3和本书使用第 1 3 8行~ 1 4 3行的# d e f i n e语句定义的短语来表示 i f n e t的成 员。 4. 接口特性 i f _ t y p e指明接口支持的硬件地址类型。图 3-9列出了n e t / i f _ t y p e s . h中几个公

90-92

共的if_type值。 if_type IFT_OTHER IFT_ETHER IFT_ISO88023 IFT_ISO88025 IFT_FDDI IFT_LOOP IFT_SLIP





未指明 以太网 IEEE 802.3以太网(CSMA/CD) IEEE 802.5令牌环 光纤分布式数据接口 环回接口 串行线IP

图3-9 if_type :数据链路类型

93-94

if_addrlen是数据链路地址的长度,而 if_hdrlen是由硬件附加给任何分组的首

部的长度。例如,以太网有一个长度为 6字节的地址和一个长度为 14字节的首部 (图4-8)。 95 if_mtu是接口传输单元的最大值:接口在一次输出操作中能传输的最大数据单元的字节 数。这是控制网络和传输协议创建分组大小的重要参数。对于以太网来说,这个值是 1500。 96-97 if_metric通常是0;其他更大的值不利于路由通过此接口。 if_baudrate指定接 口的传输速率,只有 SLIP接口才设置它。 接口统计由图3-10中显示的下一组 ifnet接口成员来收集。 5. 接口统计 98-111

这些统计大多数是不言自明的。当分组传输被共享媒体上其他传输中断时,

i f _ c o l l i s i o n s加1。i f _ n o p r o t o统计由于协议不被系统或接口支持而不能处理的分组数

54

计计TCP/IP详解 卷 2:实现

(例如:仅支持 I P的系统接收到一个 O S I分组)。如果一个非 I P分组到达一个 S L I P接口的输出队 列时,if_noproto加1。

图3-10 结构ifnet :接口统计

这些统计在 N e t / 1中不是 i f n e t 结构的一部分。它们被加入来支持接口的标准 SNMP MIB-II变量。 i f _ i q d r o p s仅被S L I P设备驱动程序访问。当 I F _ D R O P被调用时, S L I P和其 他网络驱动程序把 i f _ s n d . i f q _ d r o p s (图3 - 1 3 )加1。在 S N M P 统计加入前, i f q _ d r o p s就已经存在于 BSD软件中了。 ISODE SNMP代理忽略i f _ i q d r o p s而使 用if_snd.ifq_drops。 6. 改变时间戳 112-113 if_lastchange记录任何统计改变的最近时间。 Net/3和本书又一次用从 144行到155行的# d e f i n e语句定义的短名来指明 i f n e t 的成员。 结构i f n e t的下一个部分,显示在图 3 - 11中,它包含指向标准接口层函数的指针,它们 把设备专用的细节从网络层分离出来。每个网络接口实现这些适用于特定设备的函数。

55

第 3章 接 口 层计计

图3-11 结构ifnet :接口过程

7. 接口函数 114-129

在系统初始化时,每个设备驱动程序初始化它自己的 i f n e t结构,包括 7个函数

指针。图3-12说明了这些通用函数。 我们在Net/3中常会看到注释 / * X X X *。它提醒读者这段代码是易混淆的,包 / 括不明确的副作用,或是一个更难问题的快速解决方案。在这里,它指示 i f _ d o n e 不在Net/3中使用。 函



if_init if_output if_start if_done if_ioctl if_reset if_watchdog





初始化接口 对要传输的输出分组进行排队 启动分组的传输 传输完成后的清除 (未用) 处理I/O控制命令 复位接口设备 周期性接口例程

图3-12 结构ifnet :函数指针

在第4章我们要查看以太网、 SLIP和环回接口的设备专用函数,内核通过 i f n e t结构中的 这些指针直接调用它们。例如,如果 ifp指向一个ifnet结构, (*ifp->if_start)(ifp)

调用这个接口的设备驱动程序的 if_start函数。 结构ifnet中剩下的最后一个成员是接口的输出队列,如图 3-13所示。 130-137

i f _ s n d是接口输出分组队列,每个接口有它自己的 i f n e t结构,即它自己的输

出队列。 i f q _ h e a d指向队列的第一个分组 (下一个要输出的分组 ),i f q _ t a i l指向队列最后 一个分组, i f _ l e n是当前队列中分组的数目,而 i f q _ m a x l e n是队列中允许的缓存最大个 数。除非驱动程序修改它,这个最大值被设置为 5 0 (来源于全局整数 i f q m a x l e n,它在编译 期间根据 I F Q _ M A X L E N初始化而来 )。队列作为一个 m b u f链的链表来实现。 i f q _ d r o p s统计

56

计计TCP/IP详解 卷 2:实现

因为队列满而丢弃的分组数。图 3-14列出了那些访问队列的宏和函数。

图3-13 结构ifnet :输出队列 函



IF_QFULL





ifq是否满 int IF_QFULL(struct ifqueue *ifq);

IF_DROP

IF_DROP仅将与ifq关联的ifq_drops加1。这个名字会引起误导:调用者丢弃这个分组 void IF_DROP(struct ifqueue *ifq);

IF_ENQUEUE

把分组m追加到ifq队列的后面。分组通过 mbuf首部中的m _ n e x t p k t链接在一起 void IF_ENQUEUE(struct ifqueue *ifq, struct mbuf *m);

IF_PREPEND

把分组m插入到ifq队列的前面 void IF_PREPEND(struct ifqueue *ifq, struct mbuf *m);

IF_DEQUEUE

从ifq队列中取走第一个分组。 m指向取走的分组,若队列为空,则 m为空值 void IF_DEQUEUE(struct ifqueue *ifq, struct mbuf *m);

if_qflush

丢弃队列ifq中的所有分组,例如,当一个接口被关闭了 v o i d i f _ q f l u s h( s t r u c t i f q u e u e ifq) * ;

图3-14 fiqueue 例程

前5个例程是定义在 n e t / i f . h中的宏,最后一个例程 i f _ q f l u s h是定义在 n e t / i f . c 中的一个函数。这些宏经常出现在下面这样的程序语句中:

这段代码试图把一个分组加到队列中。如果队列满, I F _ D R O P把i f q _ d r o p s加1,并且分组 被丢弃。可靠协议如 TCP会重传丢弃的分组。使用不可靠协议 (如UDP)的应用程序必须自己检 测和处理重传。 访问队列的语句被 s p l i m p和s p l x括起来,阻止网络中断,并且防止在不确定状态时网 络中断服务例程访问此队列。 在s p l x之前调用 m _ f r e e m,是因为这段 m b u f代码有一个临界区运行在 s p l i m p 级别上。若在 m _ f r e e m前调用 s p l x,在m _ f r e e m中进入另一个临界区 ( 2 . 5节)是浪 费效率的。

57

第 3章 接 口 层计计

3.4 ifaddr结构 我们要看的下一个结构是接口地址结构, i f a d d r,它显示在图 3 - 1 5中。每个接口维护一 个i f a d d r结构的链表,因为一些数据链路,如以太网,支持多于一个的协议。一个单独的 i f a d d r结构描述每个分配给接口的地址,通常每个协议一个地址。支持多地址的另一个原因 是很多协议,包括 T C P / I P,支持为单个物理接口指派多个地址。虽然 N e t / 3支持这个特性,但 很多TCP/IP实现并不支持。

图3-15 结构ifaddr

217-219

结构i f a d d r通过i f a _ n e x t把分配给

一个接口的所有地址链接起来,它还包括一个指回

更多其他接口

接口的i f n e t结构的指针i f a _ i f p。图3 - 1 6显示了 结构ifnet与ifaddr之间的关系。 220

i f a _ a d d r 指向接口的一个协议地址,而

i f a _ n e t m a s k 指向一个位掩码,它用于选择 i f a _ a d d r中的网络部分。地址中表示网络部分的 比特在掩码中被设置为 1,地址中表示主机的部分 被设置为 0。两个地址都存放在 s o c k a d d r结构中 ( 3 . 5节)。图3 - 3 8显示了一个地址及其掩码结构。对 于IP地址,掩码选择 IP地址中的网络和子网部分。 221-223

ifa_dstaddr(或 它 的 别 名

i f a _ b r o a d a d d r)指向一个点对点链路上的另一端 的接口协议地址或指向一个广播网中分配给接口的 广播地址 (如以太网 )。接口的 i f n e t结构中互斥的 两个标志 I F F _ B R O A D C A S T和I F F _ P O I N T O P O I N T

图3-16 结构ifnet 和ifaddr

(图3-7)指示接口的类型。 224-228

ifa_rtrequest、ifa_flags和ifa_metric支持接口的路由查找。

i f a _ r e f c n t统计对结构i f a d d r的引用。宏I F A F R E E仅在引用计数降到 0时才释放这个 结构,例如,当地址被命令 S I O C D I F A D D Ri o c t l删除时。结构 i f a d d r使用引用计数是因 为接口和路由数据结构共享这些结构。

58

计计TCP/IP详解 卷 2:实现

如果有其他对 i f a d d r的引用, I F A F R E E将计数器加 1并返回。这是一个通用的 方法,除了最后一个引用外,它避免了每次都调用一个函数的开销。如果是最后一 个引用, IFAFREE调用函数ifafree,来释放这个结构。

3.5 sockaddr结构 一个接口的编址信息不仅仅只包括一个主机地址。 N e t / 3在通用的 s o c k a d d r结构中维护 主机地址、广播地址和网络掩码。通过使用一个通用的结构,将硬件与协议专用的地址细节 相对于接口层隐藏起来。 图3 - 1 7显示的是这个结构的当前定义及早期 B S D版的定义—结构o s o c k a d d r。图3 - 1 8 说明了这些结构的组织。

图3-17 结构sockaddr 和osockaddr

14字节

14字节

图3-18 结构sockaddr 和osockaddr( 省略了前缀 sa_)

在很多图中,我们省略了成员名中的公共前缀。在这里,我们省略了 sa_前缀。 1. Sockaddr结构 120-124

每个协议有它自己的地址格式。 N e t / 3在一个 s o c k a d d r结构中处理通用的地址。

sa_len指示地址的长度 (OSI和Unix域协议有不同长度的地址 ),sa_family指示地址的类型。 图3-19列出了地址族(address family)常量,其中包括我们遇到的。 当指明为 A F _ U N S P E C时,一个 s o c k a d d r的内容要根据情况而定。大多数情况 下,它包含一个以太网硬件地址。 成员s a _ l e n和s a _ f a m i l y允许协议无关代码操作来自多个协议的变长的 s o c k a d d r结 构。剩下的成员 s a _ d a t a,包含一个协议相关格式的地址。 s a _ d a t a定义为一个14字节的数 组,但当 s o c k a d d r结构覆盖更大的内存空间时, s a _ d a t a可能会扩展到 2 5 3字节。s a _ l e n

59

第 3章 接 口 层计计

仅有一个字节,因此整个地址,包括

sa_len和

sa_family

sa_family必须不超过 256字节。 这是C语言的一种通用技术,它允许程序员把 一个结构中的最后一个成员看成是可变长的。 每个协议定义一个专用的 s o c k a d d r结构,该结构 复制成员 s a _ l e n和s a _ f a m i l y,但按那个协议的要 求来定义成员 s a _ d a t a。存储在 s a _ d a t a中的地址是

AF_INET AF_ISO,AF_OSI AF_UNIX AF_ROUTE AF_LINK AF_UNSPEC

图3-19





Internet OSI Unix 路由表 数据链路 (见正文)

sa_family 常量

一个传输地址;它包含足够的信息来标识同一主机上的多个通信端点。在第

6章我们要查看

Internet地址结构 sockaddr_in,它包含了一个 IP地址和一个端口号。 2. Osockaddr结构 271-274

结构osoc k a d d r是4.3BSD Reno版本以前的 s o c k a d d r定义。因为在这个定义中

一个地址的长度不是显式地可用,所以它不能用来写处理可变长地址的协议无关代码。 OSI协 议使用可变长地址,为了包括 O S I协议,使得在 N e t / 3的s o c k a d d r定义中有了我们所见的改 变。结构osockaddr是为了支持对以前编译的程序的二进制兼容。 在本书中我们省略了二进制兼容代码。

3.6 ifnet与ifaddr的专用化 结构i f n e t和i f a d d r包含适用于所有网络接口和协议地址的通用信息。为了容纳其他设 备和协议专用信息,每个设备定义了并且每个协议分配了一个专用化版本的 ifnet和ifaddr 结构。这些专用化的结构总是包含一个 i f n e t或i f a d d r结构作为它们的第一个成员,这样 无须考虑其他专用信息就能访问这些公共信息。 多数设备驱动程序通过分配一个专用化的 i f n e t结构的数组来处理同一类型的多个接口, 但其他设备(例如环回设备 )仅处理一个接口。图 3-20所示的是我们的例子接口的专用化 i f n e t 结构的组织。

以太网

环回

图3-20 设备相关的结构中的 ifnet 结构的组织

60

计计TCP/IP详解 卷 2:实现

注意,每个设备的结构以一个 i f n e t开始,接 下来全是设备相关的数据。环回接口只声名了一个 i f n e t 结构,因为它不要求任何设备相关的数据。 在图3 - 2 0中,我们显示的以太网和 S L I P驱动程序的 结构 s o f t c带有数组下标 0,因为两个设备都支持 多个接口。任何给定类型的接口的最大个数由内核

链路层地址

建立时的配置参数来限制。 结构a r p c o m(图3-26)对于所有以太网设备是通 用的,并且包含地址解析协议 ( A R P )和以太网多播 信息。结构 l e _ s o f t c(图3 - 2 5 )包含专用于 L A N C E 以太网设备驱动器的其他信息。

Internet地址

每个协议把每个接口的地址信息存储在一个专 用化的 i f a d d r结构的列表中。以太网协议使用一 个i n _ i f a d d r结构 ( 6 . 5节),而 O S I协议使用一个 i s o _ i f a d d r结构。另外,当接口被初始化时,内

OSI地址

核为每个接口分配了一个链路层地址,它在内核中 标识这个接口。 内 核通 过 分 配 一 个 i f a d d r 结 构 和 两 个 图3-21 一个包含链路层地址、 Internet地址 s o c k a d d r _ d l结构(一个是链路层地址本身,一个

和OSI地址的接口地址列表

是地址掩码 )来构造一个链路层地址。结构 s o c k a d d r _ d l可被OSI、ARP和路由算法访问。图 3-21显示的是一个带有一个链路层地址、一个 Internet地址和一个OSI地址的以太网接口。 3.11 节说明了链路层地址 (ifaddr和两个sockaddr_ld结构)的构造和初始化。

3.7 网络初始化概述 所有我们说明的结构是在内核初始化时分配和互相链接起来的。在本节我们大致概述一 下初始化的步骤。在后面的章节,我们说明特定设备的初始化步骤和特定协议的初始化步骤。 有些设备,例如 S L I P 和环回接口,完全用软件来实现。这些伪设备用存储在全局 p d e v i n i t数组中的一个 p d e v i n i t结构来表示 (图3 - 2 2 )。在内核配置期间构造这个数组。例 如:

图3-22 结构pdevinit

120-123

对于 S L I P和环回接口,在结构 p d e v i n i t中, p d e v _ a t t a c h分别被设置为

61

第 3章 接 口 层计计

s l a t t a c h和l o o p a t t a c h。当调用这个 a t t a c h函数时, p d e v _ c o u n t作为传递的唯一参数, 它指定创建的设备个数。只有一个环回设备被创建,但如果管理员适当配置 S L I P项可能有多 个SLIP设备被创建。 网络初始化函数从 main开始显示在图3-23中。

图3-23 main 函数:网络初始化

70-96 cpu_startup查找并初始化所有连接到系统的硬件设备,包括任何网络接口。 97-174

在内核初始化硬件设备后,它调用包含在 p d e v i n i t数组中的每个 p d e v _ a t t a c h

函数。 175-234

ifinit和domaininit完成网络接口和协议的初始化,并且 scheduler开始内

核进程调度。ifinit和domaininit在第7章讨论。 在下面几节中,我们说明以太网、 SLIP和环回接口的初始化。

3.8 以太网初始化 作为c p u _ s t a r t u p的一部分,内核查找任何连接的网络设备。这个进程的细节超出了本 书的范围。一旦一个设备被识别,一个设备专用的初始化函数就被调用。图 3 - 2 4显示的是我 们的3个例子接口的初始化函数。

62

计计TCP/IP详解 卷 2:实现

每个设备驱动程序为一个网络接口初始化一个专用 化的i f n e t结构,并调用 i f _ a t t a c h把这个结构插入 到接口链表中。显示在图 3 - 2 5中的结构 le _ s o f t c是我 们的例子以太网驱动程序的专用化 ifnet结构(图3-20)。 1. le_softc结构 69-95





LANCE以太网 SLIP 环回

图3-24

初始化函数 leattach slattach loopattach

网络接口初始化函数

在i f _ l e . c中声明了一个 l e _ s o f t c结构(有

N L E成员)的数组。每个结构的第一个成员是 s c _ a c,一个 a r p c o m结构,它对于所有以太网 接口都是通用的,接下来是设备专用成员。宏 s c _ i f和s c _ a d d r简化了对结构 i f n e t及存储 在结构arpcom(sc_ac)中的以太网地址的访问,如图 3-26所示。

图3-25 结构le_softc

图3-26 结构arpcom

2. arpcom结构 95-101 结构arpcom的第一个成员ac_if是一个ifnet结构,如图 3-20所示。ac_enaddr 是以太网硬件地址,它是在 c p u _ s t a r t u p期间内核检测设备时由 L A N C E设备驱动程序从硬 件上复制的。对于我们的例子驱动程序,这发生在函数 l e a t t a c h中(图3 - 2 7 )。a c _ i p a d d r 是上一个分配给此设备的 I P地址。我们在 6 . 6节讨论地址的分配,可以看到一个接口可以有多 个I P地址。见习题 6 . 3。a c _ m u l t i a d d r s是一个用结构 e t h e r _ m u l t i表示的以太网多播地 址的列表。 ac_multicnt统计这个列表的项数。多播列表在第 12章讨论。 图3-27所示的是LANCE以太网驱动程序的初始化代码。 106-115 内核在系统中每发现一个 LANCE卡都调用一次leattach。 只有一个指向一个 h p _ d e v i c e结构的参数,它包含了 HP专用信息,因为它是专 为HP工作站编写的驱动程序。 l e指向此卡的专用化 i f n e t结构(图3-20),i f p指向这个结构的第一个成员 s c _ i f,一个 通用的ifnet结构。图3-27并不包括设备专用初始化代码,它在本书中不予讨论。

63

第 3章 接 口 层计计

图3-27 函数leattach

3. 从设备复制硬件地址 126-137

对于LANCE设备,由厂商指派的以太网地址在这个循环中以每次半个字节 (4 bit)

从设备复制到sc_addr(即sc_ac.ac_enaddr—见图3-26)。 l e s t d是一个设备专用的位移表,用于定位 h p _ a d d r的相关信息, h p _ a d d r指 向LANCE专用信息。 通过printf语句将完整的地址输出到控制台,来指示此设备存在并且可操作。 4. 初始化ifnet结构 150-157

l e a t t a c h从h p _ d e v i c e结构把设备单元号复制到 i f _ u n i t来标识同类型的多

64

计计TCP/IP详解 卷 2:实现

个接口。这个设备的 i f _ n a m e是“l e”;i f _ m t u为1 5 0 0字节(E T H E R M T U),以太网的最大 传输单元; i f _ i n i t、i f _ r e s e t、i f _ i o c t l、i f _ o u t p u t和i t _ s t a r t都指向控制网络 接口的通用函数的设备专用实现。 4.1节说明这些函数。 158

所有的以太网设备都支持 I F F _ B R O A D C A S T。L A N C E设备不接收它自己发送的数据,

因此被设置为IFF_SIMPLEX。支持多播的设备和硬件还要设置 IFF_MULTICAST。 159-162

b p f a t t a c h登记有 B P F的接口,在图 3 1 - 8中说明。函数 i f _ a t t a c h把初始化了

的ifnet结构插入到接口的链表中 (3.11节)。

3.9 SLIP初始化 依赖标准异步串行设备的 S L I P接口在调用 c p u _ s t a r t u p时初始化。当 m a i n直接通过 SLIP的pdevinit结构中的指针pdev_attach调用slattach时,SLIP伪设备被初始化。 每个SLIP接口由图3-28中的一个sl_softc结构来描述。

图3-28 结构sl_softc

43-54 与所有接口结构一样, sl_softc有一个ifnet结构并且后面跟着设备专用信息。 除了在 i f n e t结构中的输出队列外,一个 S L I P设备还维护另一个队列 s c _ f a s t q,它用 于要求低时延服务的分组—典型地由交互应用产生。 s c _ t t y p指向关联的终端设备。指针 s c _ b u f和s c _ e p分别指向一个接收 S L I P分组的缓 存的第一个字节和最后一个字节。 s c _ m p指向下一个接收字节的地址,并在另一个字节到达 时向前移动。 SLIP定义的4个标志显示在图 3-29中。 常



SC_COMPRESS SC_NOICMP SC_AUTOCOMP SC_ERROR

s c _ s o f t c成员 sc_if.if_flags sc_if.if_flags sc_if.if_flags sc_flags





I F F _ L I N K 0;压缩TCP通信 I F F _ L I N K 1;禁止ICMP通信 I F F _ L I N K 2;允许TCP自动压缩 检测到错误;丢弃接收帧

图3-29 SLIP的if_flags 和sc_flags 值

S L I P在i f n e t结构中定义了 3个接口标志预留给设备驱动程序,另一个标志定义在结构 sl_softc中。 s c _ e s c a p e 用于串行线的 I P封装机制 ( 5 . 3节),而 T C P首部压缩信息 ( 2 9 . 1 3节)保留在

65

第 3章 接 口 层计计

sc_comp中。 指针sc_bpf指向SLIP设备的BPF信息。 结构sl_softc由slattach初始化,如图3-30所示。 1 3 5 - 1 5 2 不像 l e a t t a c h 一次仅初始化一个接口,内核只调用一次 s l a t t a c h,并且 s l a t t a c h初始化所有的 S L I P接口。硬件设备在内核执行 c p u _ s t a r t u p被发现时初始化, 而伪设备都是在 m a i n为这个设备调用 p d e v _ a t t a c h函数时被初始化的。一个 S L I P设备的 i f _ m t u为2 9 6字节(S L M T U)。这包括标准的 2 0字节I P首部、标准的 2 0字节T C P首部和2 5 6字节 的用户数据 (5.3节)。

图3-30 函数slattach

一个S L I P网络由位于一个串行通信线两端的两个接口组成。 s l a t t a c h在i f _ f l a g s中 设置IFF_POINTOPOINT、SC_AUTOCOMP和IFF_MULTICAST。 SLIP接口限制它的输出分组队列 i f _ s n d的长度为50,并且它自己的接口队列 s c _ f a s t q 的长度为 3 2。图3 - 4 2显示i f _ s n d队列的长度默认为 5 0 (i f q m a x l e n),因此,如果设备驱动 程序选择一个长度,这里的初始化是多余的。 以太网设备驱动程序不显式地设置它的输出队列的长度,它依赖于 i f i n i t (图 3-42)把它设置为系统的默认值。 i f _ a t t a c h需要一个指向一个 i f n e t结构的指针,因此 s l a t t a c h将s c _ i f的地址传递 给if_attach,sc_if是一个第一个成员为结构 sl_softc的ifnet结构。 专用程序s l a t t a c h在内核初始化后运行 (从初始化文件 / e t c / n e t s t a r t),并通过打开 串行设备和执行 ioctl命令(5.3节)添加SLIP接口和一个异步串行设备。 153-155 对于每个SLIP设备,slattach调用bpfattach来登记有BPF的接口。

3.10 环回初始化 最后显示环回接口的初始化。环回接口把输出分组放回到相应的输入队列中。接口没有

66

计计TCP/IP详解 卷 2:实现

相关联的硬件设备。环回伪设备在 m a i n通过环回接口的 p d e v i n i t结构中的 p d e v _ a t t a c h 指针直接调用loopattach时初始化。图3-31所示的是函数loopattach。

图3-31 环回接口初始化

41-56

环回i f _ m t u被设置为 1 5 3 6字节(L O M T U)。在i f _ f l a g s中设置I F F _ L O O P B A C K和

IFF_MULTICAST。一个环回接口没有链路首部和硬件地址,因此 if_hdrlen和if_addrlen 被设置为0。if_attach完成ifnet结构的初始化并且 bpfattach登记有BPF的环回接口。 环回MTU至少有1576(40 + 3×512)留给一个标准的 TCP/IP首部。例如 Solaris 2.3 环回M T U设置为 8232(40 + 8×1 0 2 4 )。这些计算基于 I n t e r n e t协议;而其他协议可能 有大于40字节的默认首部。

3.11 if_attach函数 前面显示的三个接口初始化函数都调用 i f _ a t t a c h来完成接口的 i f n e t结构的初始化, 并把这个结构插入到先前配置的接口的列表中。在 i f _ a t t a c h中,内核也为每个接口初始化 并分配一个链路层地址。图 3-32说明了由if_attach构造的数据结构。

图3-32 ifnet 列表

67

第 3章 接 口 层计计

在图3 - 3 2中,i f _ a t t a c h被调用了三次:以一个 l e _ s o f t c结构为参数从 l e a t t a c h调 用,以一个 s l _ s o f t c 结构为参数从 s l a t t a c h调用,以一个通用 i f n e t结构为参数从 l o o p a t t a c h调用。每次调用时,它向 i f n e t列表中添加一个新的 i f n e t结构,为这个接口 创建一个链路层 i f a d d r 结构 ( 包含两个 s o c k a d d r _ d l 结构,图 3 - 3 3 ) ,并且初始化 ifnet_addrs数组中的一项。

图3-33 结构sockaddr_dl

图3-20显示了包含在le_softc[0]和sl_softc[0]中嵌套的结构。 初始化以后,接口仅配置链路层地址。例如, I P地址直到后面讨论的 i f c o n f i g程序才 配置(6.6节)。 链路层地址包含接口的一个逻辑地址和一个硬件地址 (如果网络支持,例如 l e 0的一个 4 8 b i t以太网地址 )。在A R P和O S I协议中要用到这个硬件地址,而一个 s o c k a d d r _ d l中的逻辑 地址包含一个名称和这个接口在内核中的索引数值,它支持用于在接口索引和关联 i f a d d r结 构(ifa_ifwithnet,图6-32)间相互转换的表查找。 结构sockaddr_dl显示在图3-33中。 55-57

回忆图 3 - 1 8,s d l _ l e n指明了整个地址的长度,而 s d l _ f a m i l y指明了地址族类,

在此例中为 AF_LINK。 58

sdl_index在内核中标识接口。图 3-32中的以太网接口会有一个为 1的索引, SLIP接口

的索引为 2,而环回接口的索引为 3。全局整数变量 i f _ i n d e x包含的是内核最近分配的一个 索引值。 60 sdl_type根据这个数据链路地址的 ifnet结构的成员 if_type进行初始化。 61-68

除了一个数字索引,每个接口有一个由结构 ifnet的成员if_name和if_unit组成

的文本名称。例如,第一个 S L I P接口叫“ s l 0”,而第二个叫“ s l 1”。文本名称存储在数组 sdl_data的前面,并且sdl_nlen为这个名称的字节长度 (在我们的SLIP例子中为 3)。 数据链路地址也存储在这个结构中。宏 L L A D D R将一个指向s o c k a d d r _ d l结构的指针转 换成一个指向这个文本名称的第一个字节的指针。 s d l _ a l e n是硬件地址的长度。对于一个 以太网设备, 48 bit硬件地址位于结构 s o c k a d d r _ d l的这个文本名称的前面。图 3 - 3 8所示的 是一个初始化了的 sockaddr_dl结构。

68

计计TCP/IP详解 卷 2:实现

Net/3不使用sdl_slen。 i f _ a t t a c h更新两个全局变量。第一个是 i f _ i n d e x,它存放系统中的最后一个接口的 索引;第二个是 i f n e t _ a d d r s,它指向一个 i f a d d r指针的数组。这个数组的每项都指向一 个接口的链路层地址。这个数组提供对系统中每个接口的链路层地址的快速访问。 函数i f _ a t t a c h较长,并且有几个奇怪的赋值语句。从图 3 - 3 4开始,我们分 5个部分讨 论这个函数。 59-74

i f _ a t t a c h有一个参数:i f p,这是一个指向 i f n e t结构的指针,由网络设备驱动

程序初始化。 N e t / 3在一个链表中维护所有这些 i f n e t结构,全局指针 i f n e t指向这个链表的 首部。 w h i l e循环查找链表的尾部,并将链表尾部的空指针的地址存储到 P中。在循环后,新 i f n e t 结构被接到这个 i f n e t链表的尾部, i f _ i n d e x 加1 ,并且将新索引值赋给 i f p ->if_index。

图3-34 函数if_attach :分配接口索引

1. 必要时调整 ifnet_addrs数组的大小 75-85 第一次调用 if_attach时,数组ifnet_addrs不存在,因此要分配 16(16=8sc_txcnt保持跟踪有多少个缓存被使用。 5. 将设备标记为忙 360-362 最后,lestart在ifnet结构中设置 IFF_OACTIVE来标识这个设备忙于传输帧。 在设备中将多个要传输的帧进行排队有一个负面影响。根据 [Jacobson 1998a], L A N C E芯片能够在两个帧间以很小的时延传输排队的帧。不幸的是,有些 (差的)以 太网设备会丢失帧,因为它们不能足够快地处理输入的数据。 在一个应用如 N F S中,这会很糟糕地互相影响。 N F S发送大的 U D P数据报 (经常 是超过8192字节),数据报被I P分片,并在L A N C E设备中作为多个以太网帧排队。分 片在接收方丢失,当 NFS重传整个UDP数据报时,会导致很多未完成的数据报极大的 时延。 Jacobson提出Sun的LANCE驱动器一次只排队一个帧就可能避免这一问题。

4.4 ioctl系统调用 i o c t l系统调用提供一个通用命令接口,一个进程用它来访问一个设备的标准系统调用 所不支持的特性。 ioctl的原型为: int ioctl(int fd, unsigned long com,…);

f d是一个描述符,通常是一个设备或网络连接。每种类型的描述符都支持它自己的一套 i o c t l命令,这套命令由第二个参数 c o m来指定。第三个参数在原型中显示为“ . . .”,因为 它是依赖于被调用的 i o c t l命令的类型的指针。如果命令要取回信息,第三个参数必须是指 向一个足够保存数据的缓存的指针。在本书中,我们仅讨论用于插口描述符的 ioctl命令。 我们显示的系统调用的原型是一个进程进行系统调用的原型。在第 1 5章中我们 会看见在内核中的这个函数还有一个不同的原型。 我们在第 1 7章讨论系统调用 i o c t l的实现,但在本书的各个部分讨论 i o c t l单个命令的 实现。 我们讨论的第一个 i o c t l命令提供对讨论过的网络接口结构的访问。我们总结的本书中 所有的ioctl命令如图4-20所示。 命



SIOCGIFCONF SIOCGIFFLAGS SIOCGIFMETRIC SIOCSIFFLAGS SIOCSIFMETRIC

第三个参数 struct struct struct struct struct

ifconf * ifreq * ifreq * ifreq * ifreq *





ifconf ifioctl ifioctl ifioctl ifioctl





获取接口配置清单 获得接口标志 获得接口度量 设置接口标志 设置接口度量

图4-20 接口ioctl 的命令

第一列显示的符号常量标识 i o c t l命令(第二个参数, c o m)。第二列显示传递给第一列 所显示的命令的系统调用的第三个参数的类型。第三列是实现这个命令的函数的名称。 图4 - 2 1显示处理 i o c t l命令的各种函数的组织。带阴影的函数我们在本章中说明。其余

90

计计TCP/IP详解 卷 2:实现

的函数在其他章说明。 系统调用

默认

获取配置

图4-21 在本章说明的 ioctl 函数

4.4.1 ifioctl函数 系统调用ioctl将图4-20所列的5种命令传递给图 4-22所示的ifioctl函数。 394-405

对于命令 S I O C G I F C O N F,i f i o c t l调用i f c o n f来构造一个可变长 i f r e q结构

的表。 406-410

对于其他 i o c t l命令,数据参数是指向一个 i f r e q 结构的指针。 i f u n i t在

i f n e t列表中查找名称为进程在 i f r - > i f r _ n a m e中提供的文本名称 (例如:“s l 0”,“l e 1” 或“ l o 0”)的接口。如果没有匹配的接口, i f i o c t l返回E N X I O。剩下的代码依赖于 c m d, 它们在图4-29中说明。 447-454

如果接口ioctl命令不能被识别, ifioctl把命令发送给与所请求插口关联的协

议的用户要求函数。对于 I P,这些命令以一个 U D P插口发送并调用 u d p _ u s r r e q。这一类命

91

第 4章 接口:以太网计计

令在图6-10中描述。23.10节将详细讨论函数 udp_usrreq。 如果控制到达switch语句外,返回0。

图4-22 函数ifioctl :综述与 SIOCGIFCONF

4.4.2 ifconf函数 i f c o n f为进程提供一个标准的方法来发现一个系统中的接口和配置的地址。由结构 ifreq和ifconf表示的接口信息如图 4-23和图4-24所示。 262-279

一个i f r e q结构包含在i f r _ n a m e中一个接口的名称。在联合中的其他成员被各

种ioctl命令访问。通常,用宏来简化对联合的成员的访问语法。 292-300

在结构ifconf中,ifc_len是ifc_buf指向的缓存的字节数。这个缓存由一个

进程分配,但由 i f c o n f用一个具有可变长 i f r e q结构的数组来填充。对于函数 i f c o n f, i f r _ a d d r 是结构 i f r e q 中联合的相关成员。每个 i f r e q 结构有一个可变长度,因为 i f r _ a d d r (一个s o c k a d d r结构)的长度根据地址的类型而变。必须用结构 s o c k a d d r的成员 sa_len来定位每项的结束。图 4-25说明了ifconf所维护的数据结构。 在图4 - 2 5中,左边的数据在内核中,而右边的数据在一个进程中。我们用这个图来讨论 图4-26中所示的 ifconf函数。 462-474

i f c o n f的两个参数是: c m d,它被忽略; d a t a,它指向此进程指定的 i f c o n f

结构的一个副本。

92

计计TCP/IP详解 卷 2:实现

图4-23 结构ifreq

图4-24 结构ifconf

内核

进程

图4-25 ifconf 数据结构

93

第 4章 接口:以太网计计

图4-26 函数ifconf

i f c是强制为一个 i f c o n f结构指针的 d a t a。i f p从i f n e t (列表头)开始遍历接口列表,

94

计计TCP/IP详解 卷 2:实现

而i f a遍历每个接口的地址列表。 c p和e p控制构造在 i f r 中的接口文本名称, i f r是一个 i f r e q结构,它在接口名称和地址复制到进程的缓存前保存接口名称和地址。 i f r q指向这个 缓存,并且在每个地址被复制后指向下一个。 s p a c e是进程缓存中剩余字节的个数, c p用来 搜寻名称的结尾,而 ep标志接口名称数字部分最后的可能位置。 475-488

f o r 循环遍历接口列表。对于每个接口,文本名称被复制到

i f r _ n a m e ,在

i f r _ n a m e的后面跟着 i f _ u n i t数的文本表示。如果没有给接口分配地址,一个全 0的地址被 构造,所得的ifreq结构被复制到进程中,并减小 space,增加ifrp。 489-515

如果接口有一个或多个地址,用 f o r循环来处理每个地址。地址加到 i f r中的接

口名称中,然后 i f r被复制到进程中。长度超过标准 s o c k a d d r结构的地址不放到 i f r中,并 且直接复制到进程。在复制完每个地址后,调整 s p a c e和i f r p的值。所有接口处理完后,更 新缓存长度 ( i f c - > i f c _ l e n),并且 i f c o n f返回。系统调用 i o c t l负责将结构 i f c o n f中 新的内容复制回进程中的结构 ifconf。 4.4.3 举例 图4-27显示了以太网、 SLIP和环回接口被初始化后的接口结构的配置。

图4-27 接口和地址数据结构

图4-28显示了以下代码执行后的 ifc和buffer的内容。

这里对命令 S I O C G I F C O N F操作的插口的类型没有限制,如我们所看到的,这个命令返回 所有协议族类的地址。 在图4 - 2 8中,因为在缓存中返回的三个地址仅占用 108 (3×3 6 )字节,i o c t l将i f c _ l e n

95

第 4章 接口:以太网计计

由1 4 4改为1 0 8。返回三个 s o c k a d d r _ d l地址,并且这个缓存后面的 3 6字节未用。每项的前 16个字节包含接口的文本名称。在这里,这 16字节中只有 3个字节被使用。

(16 字节)

(20字节) 以太网地址

36 字节 36 字节 36 字节 36 字节

图4-28 SIOCGIFCONF 命令返回的数据

i f r _ a d d r为一个 s o c k a d d r结构的形式,因此第一个值为长度 ( 2 0字节),且第二个值 为地址的类型 (1 8,A F _ L I N K)。接下来的一个值为 s d l _ i n d e x,与s d l _ t y p e一样,对于每 个接口,它是不同的 (与IFT_ETHER、IFT_SLIP和IFT_LOOP相对应的值为6、28和24)。 下面三个值为 s a _ n l e n(文本名称的长度 )、s a _ a l e n(硬件地址的长度 )及s a _ s l e n(未 用)。对于所有三项, s a _ n l e n都为 3。以太网地址的 s a _ a l e n为6,而S L I P和环回接口的 sa_alen为0。sa_slen总是为0。 最后,是接口的文本名称,其后面是硬件地址

( 仅对于以太网 )。S L I P 和环回接口在

sockaddr_dl结构中不存放一个硬件级地址。 在此例中,仅返回 s o c k a d d r _ d l地址(因为在图 4 - 2 7中没有配置其他地址类型 ),因此缓 存中的每项大小一样。如果为每个接口配置其他地址

( 例如: I P 或 O S I 地址 ) ,它们会同

sockaddr_dl地址一起返回,并且每项的大小根据返回的地址类型的不同而不同。 4.4.4 通用接口ioctl命令 图4 - 2 0中剩下的四个接口命令 (S I O C G I F F L A G S、S I O C G I F M E T R I C、S I O C S I F F L A G S 和SIOCSIFMETRIC)由函数ifioctl处理。图4-29所示的是处理这些命令的 case语句。 1. SIOCGIFLAGS和SIOCGIFMETRIC 410-416

对于两个S I O C Gxxx命令,i f i o c t l将每个接口的 i f _ f l a g s或i f _ m e t r i c值复

制到 i f r e q 结构中。对于标志,使用联合的成员 i f r _ f l a g s ;而对于度量,使用成员 i f r _ m e t r i c(图4-23)。 2. SIOCSIFFLAGS 417-429

为改变接口的标志,调用进程必须有超级用户权限。如果进程正在关闭一个运行

的接口或启动一个未运行的接口,分别调用 if_down和if_up。 3. 忽略标志IFF_CANTCHAGE 430-434

回忆图 3 - 7,有些接口标志不能被进程改变。表达式 (i f p - > i f _ f l a& g s

96

计计TCP/IP详解 卷 2:实现

I F F _ C A N T C H A N G E)清除能被进程改变的接口标志,而表达式 (i f r - > i f r _ f l a & g s~ I F F _ C A N T C H A N G E)清除在请求中不被进程改变的标志。这两个表达式进行或运算并作为新 值保存在 i f p - > i f _ f l a g s中。在返回前,请求被传递给与设备相关联的 i f _ i o c t l函数(例 如:LANCE驱动器的 leioctl—图4-31)。

图4-29 函数ifioctl :标志和度量

4. SIOCSIFMETRIC 435-439

改变接口的度量要容易些;进程同样要有超级用户权限, ifioctl将接口新的度

量复制到if_metric中。 4.4.5 if_down和if_up函数 利用程序 i f c o n f i g ,一个管理员可以通过命令 S I O C S I F F L A G S 设置或清除标志 IFF_UP来启用或禁用一个接口。图 4-30显示了函数 if_down和if_up的代码。 292-302

当一个接口被关闭时, I F F _ U P 标志被清除并且对与接口关联的每个地址用

p f c t l i n p u t(7 . 7节)发送命令 P R C _ I F D O W N。这给每个协议一个机会来响应被关闭的接口。 有些协议,如 OSI,要使用接口来终止连接。对于 IP,如果可能,要通过其他接口为连接进行 重新路由。 TCP和UDP忽略失效的接口,并依赖路由协议去发现分组的可选路径。

97

第 4章 接口:以太网计计

i f _ q f l u s h忽略接口的任何排队分组。 r t _ i f m s g通知路由系统发生的变化。 T C P自动 重传丢失的分组; UDP应用必须自己显式地检测这种情况,并对此作出响应。 308-315 当一个接口被启用时, IFF_UP标志被设置,并且 rt_ifmsg通知路由系统接口状 态发生变化。

图4-30 函数if_down 和if_up

4.4.6 以太网、SLIP和环回 我们看图4-29中处理SIOCSIFFLAGS命令的代码,ifioctl调用接口的if_ioctl函数。 在我们的三个例子接口中,函数 s l i o c t l 和l o i o c t l为这个被 i f i o c t l忽略的命令返回 E I N V A L。图4-31显示了函数 l e i o c t l及LANCE以太网驱动程序的 S I O C S I F F L A G S命令的处 理。

图4-31 函数leioctl :SIOCSIFFLAGS

98

计计TCP/IP详解 卷 2:实现

图4-31 (续)

614-623

l e i o c t l把第三个参数 d a t a转换为一个 i f a d d r结构的指针,并保存在 i f a中。

l e指针引用下标为 i f p - > i f _ u n i t的l e _ s o f t c结构。基于 c m d的s w i t c h语句构成了这个 函数的主体。 638-656

在图4 - 3 1中仅显示了 case S I O C S I F F L A G S。这次 i f i o c t l调用l e i o c t l,接

口标志被改变。显示的代码强制物理接口进入标志所配置的状态。如果要关闭接口

(没有设

置I F F _ U P),但接口正在工作,则关闭接口。若要启动未操作的接口,接口被初始化并重 启。 如果混淆比特被改变,那么就关闭接口,复位,并重启来实现这种变化。 仅当要求改变 I F F _ P R O M I S C比特时包含异或和 I F F _ P R O M I S C的表达式才为 真。 672-677 处理未识别命令的 default情况分支发送EINVAL,并在函数的结尾将它返回。

4.5 小结 在本章中,我们说明了 L A N C E以太网设备驱动程序的实现,这个驱动程序在全书中多处 引用。我们还看到了以太网驱动程序如何检测输入中的广播地址和多播地址,如何检测以太 网和8 0 2 . 3封装,以及如何将输入的帧分用到相应的协议队列中。在第 2 1章中我们会看到 I P地 址(单播、广播和多播 )是如何在输出转换成正确的以太网地址。

99

第 4章 接口:以太网计计

最后,我们讨论了协议专用的 ioctl命令,它用来访问接口层数据结构。

习题 4.1 在l e r e a d中,当接收到一个广播分组时,总是设置标志 M _ M C A S T(除了M _ B C A S T 外)。与e t h e r _ i n p u t的代码比较,为什么在 l e r e a d和e t h e r _ i n p u t中设置此标 志?它至关重要吗?哪个正确? 4.2 在e t h e r _ i n p u t(图4 - 1 3 )中,如果交换广播地址和多播地址检测次序会发生什么情 况?如果在检测多播地址的 if语句前加上一个 else会发生什么情况?

第5章 接口:SLIP和环回 5.1 引言 在第 4章中,我们查看了以太网接口。在本章中,我们讨论

S L I P和环回接口,同样用

i o c t l命令来配置所有网络接口。 S L I P驱动程序使用的 T C P压缩算法在 2 9 . 1 3节讨论。环回驱 动程序比较简单,在这里我们要对它进行完整地讨论。 像图4-2一样,图5-1列出了针对我们三个示例驱动程序的入口点。 ifnet if_init if_output if_start if_done if_ioctl if_reset if_watchdog

以 太 网

SLIP

leinit ether_output lestart

sloutput

looutput

slioctl

loioctl

leioctl lereset









初始化硬件 接收并将要传输的分组进行排队 开始传输帧 输出完成(未用) 从一个进程处理i o c t l命令 将设备重新设置为一已知状态 监视设备的故障或采集统计信息

图5-1 例子驱动程序的接口函数

5.2 代码介绍 SLIP和环回驱动程序的代码文件列于图 5-2中。 文





net/if_slvar.h net/if_sl.c net/if_loop.c



SLIP定义 SLIP驱动程序函数 环回驱动程序

图5-2 本章讨论的文件

5.2.1 全局变量 在本章讨论 SLIP和环回接口结构。全局变量见图 5-3。 变



sl_softc loif

数据类型 struct sl_softc [] struct ifnet





SLIP接口 环回接口

图5-3 本章中介绍的全局变量

s l _ s o f t c是一个数组,因为可能有很多 S L I P接口。l o i f不是一个数组,因为只可能有 一个环回接口。

101

第 5章 接口:SLIP和环回计计

5.2.2 统计量 在第4章讨论的 i f n e t结构的统计也会被 SLIP和环回驱动程序更新。采集的另一个统计量 (它不在ifnet结构中)显示在图5-4中。 变



tk_nin





被SNMP使用

被任何串行接口 (被SLIP驱动程序更新 )接收的字节数

图5-4 变量tk_nin

5.3 SLIP接口 一个S L I P接口通过一个标准的异步串行线与一个远程系统通信。像以太网一样, S L I P定 义了一个标准的方法对传输在串行线上的 I P分组进行组帧。图 5 - 5显示了将一个包含 S L I P保留 字符的IP分组封装到一个 SLIP帧中。 分组用 SLIP END 字符0 x c 0来分割开。如果 E N D字符出现在 I P分组中,则在它前面填充 SLIP ESC字符0 x d b,并且在传输时将它替换为 0 x d c。当E S C字符出现在 I P分组中时,就在 它前面填充 ESC字符0xdb,并在传输时将它替换为 0xdd。 因为在SLIP帧(与以太网比较)中没有类型字段, SLIP仅适用于传输IP分组。 IP分组 在分组中的END

转义的END

在分组中的ESC

转义的ESC

SLIP帧

图5-5 将一个IP分组进行 SLIP封装

在RFC 1055 [Romkey 1988]中讨论了SLIP,陈述了它的很多弱点和非标准情况。 卷1中包含了SLIP封装的详细讨论。 点对点协议 ( P P P )被设计用来解决 S L I P的问题,并提供一个标准方法来通过一个 串行链路传输帧。 PPP在RFC 1332 [McGregor 1992]和RFC 1548 [Simpson 1993]中定 义。Net/3不包含一个PPP的实现,因此我们不在本书中讨论它。关于 PPP的更多信息 见卷1的2.6节。附录B讨论在哪里获得一个 PPP实现的参考。 5.3.1 SLIP线路规程: SLIPDISC 在N e t / 3中,S L I P接口依靠一个异步串行设备驱动器来发送和接收数据。传统上,这些设 备驱动器称为 T T Y (电传机)。Net/3 TTY子系统包括一个线路规程 (Line discipline)的概念,这 个线路规程作为一个在物理设备和 I/O系统调用 (如r e a d和w r i t e)之间的过滤器。一个线路规

102

计计TCP/IP详解 卷 2:实现

程实现以下特性:如行编辑、换行和回车处理、制表符扩展等等。 S L I P接口作为 T T Y子系统 的一个线路规程,但它不把输入数据传给从设备读数据的进程,也不接受来自向设备写数据 的进程的输出数据。 S L I P接口将输入分组传给 I P输入队列,并通过 S L I P的i f n e t结构中的函 数i f _ o u t p u t来获得要输出的分组。内核通过一个整数常量来标识线路规程,对于 SLIP,该 常量是SLIPDISC。 图5 - 6左边显示的是传统的线路规程,右边是 S L I P规程。我们在右边用 s l a t t a c h显示进 程,因为它是初始化 S L I P接口的程序。 T T Y子系统和线路规程的细节超出了本书的范围。我 们仅介绍理解 S L I P代码工作的相关信息。对于更多关于 T T Y子系统的信息见 [ L e ffler et al. 1 9 8 9 ]。图5 - 7列出了实现 S L I P驱动程序的函数。中间的列指示函数是否实现线路规程特性和 (或)网络接口特性。 进程 进程 内核

线路规程

线路规程

设备驱动程序

设备驱动程序

串行线

串行线

图5-6 SLIP接口作为一个线路规程 函



网络接口

slattach slinit sloutput slioctl sl_btom

• •





初始化s l _ s o f t c结构,并将它连接到 i f n e t列表 初始化SLIP数据结构 对相关TTY设备上要传输的输出分组进行排队 处理插口i o c t l请求 将一个设备缓存转换成一个 m b u f链表

• • • • •

slopen slclose sltioctl slstart slinput

线路规程

• • •

将s l _ s o f t c结构连接到 TTY设备,并初始化驱动程序 取消TTY设备与s l _ s o f t c结构的连接,标记接口为关闭,并释放存储器 处理TTY i o c t l命令

• •

从队列中取分组,并开始在 TTY设备上传输数据 处理从TTY设备输入的字节,如果整个帧被接收,就排列输入的分组

图5-7 SLIP设备驱动程序的函数

在N e t / 3中的S L I P驱动程序通过支持 T C P分组首部压缩来得到更好的吞吐量。我们在 2 9 . 1 3 节讨论分组首部压缩,因此,图 5-7跳过实现这些特性的函数。 Net/3 SLIP接口还支持一种转义序列。当接收方检测到这个序列时,就终止 SLIP

103

第 5章 接口:SLIP和环回计计

的处理,并将对设备的控制返回给标准线路规程。我们这里的讨论忽略这个处理。 图5-8显示了作为一个线路规程的 SLIP和作为一个网络接口的 SLIP间的复杂关系。



输入字符

输出字符

图5-8 SLIP设备驱动程序

在N e t / 3中,s c _ t t y p和t _ s c指向t t y结构和s l _ s o f t c [ 0 ]结构。由于使用两 个箭头会使图显得较乱,我们用一对相反的箭头表示两个指针来说明结构间的双链。 在图5-8中包含很多信息: • 结构sl_softc表示的网络接口和结构 tty表示的TTY设备。 • 输入字节存放在簇中 (显示在结构tyy后面)。当一个完整的 SLIP帧被接收时,封装的 IP分 组被slinput放到ipintrq中。 • 输出分组从 i f _ s n d或s c _ f a s t q退队,转换成 S L I P帧,并被 s l s t a r t传给T T Y设备。 T T Y缓存将字节输出到结构 c l i s t。函数 t _ o p r o c取完,并传输在 c l i s t结构中的字 节。 5.3.2 SLIP初始化: slopen和slinit 我们在3 . 7节讨论了 s l a t t a c h是如何初始化 s l _ s o f t c结构的。接口虽然被初始化,但 还不能操作,直到一个程序 (通常是s l a t t a c h)打开一个 T T Y设备(例如:/ d e v / t t y 0 1),并 发送一个 i o c t l命令用 S L I P规程代替标准的线路规程才能操作。这时, T T Y子系统调用线路 规程的打开函数 (在此是s l o p e n),此函数在一个特定 T T Y设备和一个特定 S L I P接口间建立关 联。slopen显示在图5-9中。

104

计计TCP/IP详解 卷 2:实现

图5-9 函数slopen

181-193

传递给s l o p e n的两个参数为: d e v,一个内核设备标识, s l o p e n未用此参数;

t p,一个指向此 T T Y设备相关 t t y结构的指针。最开始是一些预防处理:若进程没有超级用 户权限,或 TTY的线路规程已经被设置为 SLIPDISC,则slopen立即返回。 194-205

f o r循环在s l _ s o f t c结构数组中查找第一个未用的项,调用 s l i n i t ( 5 . 1 0节),

通过 t _ s c和s c _ t t y p加进结构 t t y和s l _ s o f t c,并将 T T Y输出速率 (t _ o s p e e d)复制到 S L I P接口。 t t y f l u s h丢弃任何在 T T Y队列中追加的输入输出数据。如果一个 S L I P接口结构 不可用, slopen返回ENXIO。若成功,返回 0。 注意,第一个变量 s l _ s o f t c结构与T T Y设备相关。如果系统有多个 S L I P线路, 在T T Y设备和 S L I P接口间不需要固定的映射。实际上,这个映射依赖于 s l a t t a c h 打开和关闭 TTY设备的次序。 显示在图5-10中的函数 slinit初始化结构 sl_softc。 156-175

函数slinit分配一个mbuf簇,并将它用三个指针连接到结构 sl_softc。当一

个完整的 S L I P帧被接收后,输入字节存储在这个簇中。 s c _ b u f总是指向簇中的这个分组的 起始位置, s c _ m p 指向要接收的下一个字节的位置,并且

s c _ e p 指向这个簇的结束。

sl_compress_init为此链路初始化 TCP首部的压缩状态 (29.13节)。 在图 5 - 8 中,我们看到 s c _ b u f 不指向簇的第一个字节。 s l i n i t 保留了 1 4 8 字节 (BUFOFFSET)的空间,因为输入分组可能含有一个压缩了的首部,它会扩展来填充这个空间。 在簇中已接收的字节用阴影表示。我们看到 s c _ m p指向接收的最后一个字节的下一个字节, 并且sc_ep指向这个簇的结尾。图 5-11显示了在几个SLIP常量间的关系。

105

第 5章 接口:SLIP和环回计计

使这个接口能运行,剩下的要做的工作就是给它分配一个 I P地址。同以太网驱动程序一 样,我们将地址分配的讨论推迟到 6.6节。

图5-10 函数slinit 常



MCLBYTES SLBUFSIZE SLIP_HDRLEN BUFOFFSET SLMAX SLMTU SLIP_HIWAT

值 2048 2048 16 148 1900 296 100





一个mbuf簇的大小 一个未压缩的 SLIP分组的最大长度—包括一个BPF首部 SLIP BPF首部的大小 一个扩展的 TCP/IP首部的最大长度加上一个 BPF首部的大小 一个存储在簇中的压缩 SLIP分组的最大长度 SLIP分组的最佳长度;导致最小的时延,同时还有较高的批量吞吐量 在TTY输出队列中排队的最大字节数 BUFOFFSET+SLMAX=SLBUFSIZE=MCLBYTES

图5-11 SLIP常量

5.3.3 SLIP输入处理: slinput T T Y设备驱动程序每次调用 s l i n p u t,都将输入字符传给 S L I P线路规程。图 5 - 1 2显示了 函数slinput,但跳过了帧结束的处理,对于它我们分开讨论。 527-545

传递给slinput的参数为:c,下一个输入字符; tp,一个指向设备tty结构的指

针。全局整数tk_nin计算所有TTY设备的输入字符数。 slinput将tp->t_sc转换成sc,sc 是指向一个sl_softc结构的指针。如果这个TTY设备没有相关联的接口,slinput立即返回。 s l i n p u t的第一个参数是一个整数。除了接收的字符, c还包含从 T T Y设备驱动程序以 高位在前的比特序发送的控制字符。如果在 c中指示了一个差错,或调制解调器控制线禁用并 且不应该被忽略,则 S C _ E R R O R被置位,并且 s l i n p u t返回。之后,当 s l i n p u t处理END字 符时,此帧被丢弃。标志 C L O C A L指示系统应该把这个线路视为一个本地线路 (即不是一个拨 号线路),并且不应该看到调制解调器的控制信号。

106

计计TCP/IP详解 卷 2:实现

图5-12 函数slinput

107

第 5章 接口:SLIP和环回计计

546-636

slinput丢弃c中的控制比特,并用 TTY_CHARMASK来屏蔽掉,更新接口上接收

字节数的计数,同时跳过接收到的字符: • 如果c是一个转义的ESC字符,并且前一字符为ESC,则slinput用一个ESC字符替代c。 • 如果c是一个转义的END字符,并且前一字符为ESC,则slinput用一个END字符代替c。 • 如果c是SLIP ESC字符,则将s c _ e s c a p e置位,并且s l i n p u t立即返回(即,ESC字符 被丢弃)。 • 如果c是SLIP END字符,则将分组放到 IP输入队列。处理 SLIP帧结束字符的代码显示在 图5-13中。

图5-13 函数slinput :帧结束处理

108

计计TCP/IP详解 卷 2:实现

图5-13 (续)

通过这个switch语句的普通控制流会落到switch外(这里没有default情况)。大多数字 节是数据,并且不与这4种情况中的任何一种匹配。前两个case的控制也会落到这个switch外。 637-649 如果控制落到switch外,接收的字符为 IP分组中的一部分。这个字符被存储到簇 中(如果还有空间),指针增加,sc_escape被清除,并且slinput返回。 如果簇满,字符被丢弃,并且 s l i n p u t设置S C _ E R R O R。如果簇满或在处理帧结束时检 测到一个差错,则控制跳到

e r r o r 。程序在 n e w p a c k 为一个新的分组重设簇指针,

sc_escape被清除,并且slinput返回。 图5-13显示了图5-12中跳过的FRAME_END代码。 560-579

如果S C _ E R R O R被设置,同时正在接收分组或如果分组长度小于 3字节(记住,分

组可能被压缩),则slinput立即丢弃此输入 SLIP分组。 如果 S L I P接口带有 B P F,s l i n p u t在c h d r数组中保存这个首部的一个备份 (可能被压 缩)。 580-606

通过检查分组的第一个字节, slinput判断它是一个未压缩的 IP分组,还是一个

压缩的 T C P分段,或者一个未压缩的 T C P分段。类型存放在 c中,并且类型信息从数据的第一 个字节中移去 ( 2 9 . 1 3节)。如果分组以压缩形式出现,并且允许压缩, s l _ u n c o m p r e s s _ t c p 对分组进行解压缩。如果禁止压缩,自动允许压缩被设置,并且如果分组足够大,则仍然调

109

第 5章 接口:SLIP和环回计计

用sl_uncompress_tcp。如果是一个压缩的 TCP分组,则设置压缩标志。 若分组不被识别,slinput跳到error,丢弃此分组。29.13节详细讨论了首部压缩技术。 现在簇中包含一个完整的未压缩分组。 607-618 SLIP解压缩分组后,首部和数据传给 BPF。图5-14显示了slinput构造的缓存格式。

未用缓存空间

压缩的首部

未压缩的首部

数据 原分组 (len字节)

图5-14 BPF格式的SLIP分组

BPF首部的第一个字节是分组方向的编码,在此例中是输入 (S L I P D I R _ I N)。接下来的 15 字节包含压缩的首部。整个分组被传给 bpf_tap。 619-635 sl_btom将簇转换为一个 mbuf链表。如果分组足够小,能放到一个单独的 mbuf中, s l _ b t o m就将分组从簇复制到一个新分配的 m b u f的分组首部;否则 s l _ b t o m将这个簇连接 到一个 m b u f,并为这个接口分配一个新簇。这样比从一个簇复制到另一个簇要快。我们在本 书中不显示 sl_btom的代码。 因为在 S L I P接口上只能传输 I P分组, s l i n p u t不必选择协议队列 (如以太网驱动程序所 做)。分组在 i p i n t r q中排队,一个 I P软件中断被调度,并且 s l i n p u t跳到n e w p a c k,更新 簇的分组指针,并清除 sc_escape。 如果分组不能在 i p i n t r q上排队, S L I P驱动程序增加 i f _ i e r r o r s,而在这种 情况下,以太网或环回驱动程序都不增加这个统计量。 即使在s p l t t y调用s l i n p u t,访问I P输入队列必须用 s p l i m p保护。回忆图 1 - 1 4,一个 splimp中断能抢占 spltty进程。 5.3.4 SLIP输出处理: sloutput 如所有的网络接口,当一个网络层协议调用接口的if_output函数时,开始处理输出。对 于以太网驱动程序,此函数是ether_output。而对于SLIP,此函数是sloutput(图5-15)。 259-289

s l o u t p u t 的4个参数为: i f p,指向 SLIP i f n e t 结构 (在此例中是一个

s l _ s o f t c结构)的指针; m,指向排队等待输出的分组的指针; d s t,分组下一跳的目标地 址; r t p ,指向一个路由表项的指针。 s l o u t p u t未用第 4个参数,但却是要求的,因为 sloutput必须匹配在 ifnet结构中的if_output函数原型。 s l o u t p u t确认d s t是一个IP地址,接口被连接到一个 TTY设备,并且这个 TTY设备是正 在运行的(即有载波信号,或应忽略它 )。如果任何检测失败,则返回差错。 290-291 SLIP为输出分组维护两个队列。默认选择标准队列 if_snd。 292-295 如果输出分组包含一个ICMP报文,并且接口的SC_NOICMP被置位,则丢弃此分组。 这防止一个SLIP链路被一个恶意用户发送的无关 ICMP分组(例如ECHO分组)所淹没(第11章)。

110

计计TCP/IP详解 卷 2:实现

图5-15 函数sloutput

差错码E N E T R E S E T指示分组因决策而被丢弃 (相对于网络故障 )。我们在第 11章会看到除

111

第 5章 接口:SLIP和环回计计

了在本地产生一个 I C M P报文外,此差错简单地被忽略,在这种情况下,一个差错返回给发送 此报文的进程。 N e t / 2在这种情况返回一个 0。对于一个诊断工具,如 p i n g或t r a c e r o u t e,会 出现这种情况:好像这个分组消失了,因为输出操作会报告成功完成。 通常, I C M P报文可以被丢弃。对于正确的操作,它们并不必要,但丢弃它们会 造成更多的麻烦,可能导致不佳的路由决定和较差的性能,并且会浪费网络资源。 296-297

如果在输出分组的 TOS字段指明低时延服务 (I P T O S _ L O W D E L A Y),则输出队列改

为sc_fastq。 RFC 1700 和RFC 1349 [Almquist 1992] 规定了标准协议的 TO S设置。为 Te l n e t、 R l o g i n、F T P (控制)、T F T P、S M T P (命令阶段 )和D N S ( U D P查询)指明了低时延服务。 更多细节见卷1的3.2节。 在以前的 B S D版本中, i p _ t o s不由应用程序设置。 S L I P驱动程序通过检查在 I P分 组中的传输首部来实现 TO S排队。如果发现 F T P (命令)、Te l n e t或R l o g i n端口的T C P分 组,分组就如指明了 I P T O S _ L O W D E L A Y一样被排队。很多路由器仍然这样,因为很 多这些交互服务的实现仍然不设置 ip_tos。 298-312

现在分组被放到所选择的队列中,接口统计被更新,并且 (如果T T Y输出队列为

空) sloutput调用slstart来发起对此分组的传输。 如果接口队列满,则 S L I P增加i f _ o e r r o r s;而对于 e t h e r _ o u t p u t,则不是 这样做的。 不像以太网输出函数 (e t h e r _ o u t p u t),s l o u t p u t不为输出分组构造一个数据链路首 部。因为在 S L I P网络上的另一系统在串行链路的另一端,所以不需要硬件地址或一个协议 (如 A R P )在I P地址和硬件地址间进行转换。协议标识符 (如以太网类型字段 )也是多余的,因为一 个SLIP链路仅承载 IP分组。 5.3.5 slstart函数 除了被 s l o u t p u t调用外,当 T T Y取完它的输出队列并要传输更多的字节时, T T Y设备 调用 s l s t a r t 。T T Y子系统通过一个 c l i s t 结构管理它的队列。在图 5 - 8 中,输出 c l i s t t_outq显示在slstart下面和设备的t_oproc函数的上面。slstart把字节添加到队列中, 而t_oproc将队列取完并传输这些字节。 函数slstart显示在图5-16中。 318-358 当slstart函数被调用时, tp指向设备的 tty结构。slstart的主体由一个for 循环构成。如果输出队列 t _ o u t q不空,s l s t a r t调用设备的输出函数 t _ o p r o c,此函数传 输设备所能接收的字节数。如果 T T Y输出队列中剩余的字节超过 1 0 0字节( S L I P _ H I W A T ), 则s l s t a r t返回而不是将另一分组的字节添加到队列中。当传输完所有字节,输出设备产生 一个中断,并且当输出列表为空时, TTY子系统调用 slstart。 如果TTY输出队列为空,则一个分组从 s c _ f a s t q中退队,或者,若 s c _ f a s t q为空,则 从if_snd队列中退队,这样在其他分组前传输所有交互的分组。

112

计计TCP/IP详解 卷 2:实现

没有标准的 S N M P变量来统计根据 TO S字段排队的分组。在 3 5 3行的X X X注释表 示SLIP驱动程序在 if_omcasts中统计低时延分组数,而不是多播分组数。 359-383

如果S L I P接口带有 B P F,s l s t a r t在任何首部压缩前为输出分组产生一个备份。

这个备份存储在 bpfbuf数组的栈中。 384-388

如果允许压缩,并且分组包含一个

T C P报文段,则 s l o u t p u t 调用 s l _

c o m p r e s s _ t c p来压缩这个分组。得到的分组类型被返回,并与 I P首部的第一个字节 ( 2 9 . 1 3 节)进行逻辑或运算。 389-398

压缩的首部现在复制到 BPF首部,并且方向标记为 SLIPDIR_OUT。完整的BPF分

组传给bpf_tap。 483-484 如果for循环终止,则slstart返回。

图5-16 函数slstart :分组退队

113

第 5章 接口:SLIP和环回计计

图5-16 (续)

s l s t a r t的下一部分 (图5 - 1 7 )在系统存储器容量不足时丢弃分组,并且采用一种简单的 技术来丢弃由于串行线上的噪声产生的数据。这些代码在图 5-16中忽略了。 399-409

如果系统缺少 c l i s t结构,则分组被丢弃,并且作为一个冲突被统计。通过不断地

循环而不是返回, s l s t a r t快速地丢弃所有剩余的排队输出的分组。由于设备仍然有太多字 节为输出排队,每次迭代都要丢弃一个分组。高层协议必须检测丢失的分组并重传它们。 410-418

如果T T Y输出队列为空,则通信线路可能有一段时间空闲,并且接收方在另一端

114

计计TCP/IP详解 卷 2:实现

可能接收了线路噪声产生的无关数据。 s l s t a r t在输出队列中放置一个额外的 SLIP END字 符。一个长度为 0的帧或一个由线路噪声产生的帧应该被接收方 SLIP接口或IP协议丢弃。

图5-17 函数slstart :资源缺乏和线路噪声

图5 - 1 8说明了这个丢弃线路噪声的技术,它来源于由 Phil Karn撰写的 RFC 1055。在图 5 1 8中,传输第二个帧结束符 ( E N D ),因为线路空闲了一段时间。由噪声产生的无效帧和这个 END字节被接收系统丢弃。

分组

额外的

空闲

空闲

分组

噪声

帧1 (OK)

帧2 (丢弃的)

帧3 (OK)

图5-18 Karn的丢弃SLIP线路噪声的方法

在图5-19中,线路上没有噪声并且 0长度帧被接收系统丢弃。 空闲

额外的

分组

分组

帧1 (OK)

帧3 (OK) 帧2 (丢弃的)

图5-19 无噪声的Karn方法

slstart的下一部分 (图5-20)将数据从一个mbuf传给TTY设备的输出队列。 419-467

在这部分的外部 w h i l e循环对链表中的每个 mbuf执行一次。中间的 w h i le循环将

数据从每个 m b u f传给输出设备。内部的 w h i l e循环不断递增 c p,直到它找到一个 E N D或E S C 字符。 b _ t o _ q传输b p到c p之间的数据。 END和ESC字符被转义,并且两次通过调用 p u t c放

115

第 5章 接口:SLIP和环回计计

入队列。中间的循环直到 m b u f的所有字节都传给 T T Y设备输出队列才停止。图 5 - 2 1说明了对 包含了一个 SLIP END字符和一个 SLIP ESC字符的mbuf的处理。 b p标记用b _ t o _ q传输的m b u f的第一部分的开始, c p标记这个部分的结束。 e p标记这个 mbuf中数据的结束位置。

图5-20

函数slstart :传输分组

116

计计TCP/IP详解 卷 2:实现

mbuf 首部

数据

数据

第一次调用b_to_q

第二次调用b_to_q

数据

第三次调用b_to_q

图5-21 单个mbuf的SLIP传输

如果b _ t o _ q或p u t c失败(即,数据不能在 T T Y设备排队 ),则b r e a k导致s l s t a r t退出 内部 w h i l e循环。这种失败表示内核 c l i s t资源用完。在每个 m b u f被复制到 T T Y设备后,或者 当一个差错发生时, m b u f被释放, m增加到链表的下一个 m b u f,并且外部 w h i l e循环继续执 行直到链表中所有 mbuf被处理。 图5-22显示了slstart完成输出帧的处理。

图5-22 函数slstart :帧结束处理

468-482

当外部w h i l e循环处理完对输出队列中的字节排队时,控制到达这段代码。驱动

程序发送一个SLIP END字符,来终止这个帧。 如果这些字节在排队时发生差错,则输出帧无效,并会因为“无效的检验和”或“无效 的长度”被接收系统检测出来。 无论这个帧是不是因为一个差错而终止,如果 E N D字符没有填充到输出队列中,队列的 最后一个字符就要被丢弃,并且 s l s t a r t将使这个帧结束。这保证传输了一个 E N D字符。这 个无效帧在目标站被丢弃。 5.3.6 SLIP分组丢失 S L I P接口提供了一个尽最大努力服务的好例子。如果 T T Y超载,则 S L I P丢弃分组;在分 组开始传输后,如果资源不可用,则它截断分组,并且为了检测和丢弃线路噪声插入无关的 空分组。对以上的每一种情况都不产生差错报文。 S L I P依靠 I P层和运输层来检测损坏的和丢 失的分组。

117

第 5章 接口:SLIP和环回计计

在一个路由器上从一个高速接口例如以太网,发送帧到一个低速的 S L I P线路上。如果发 送方不能意识到瓶颈并相应调节数据速率,则会有大比例的分组被丢弃。在 2 5 . 11节我们会看 到T C P是如何检测并对此响应的。应用程序使用一个无流量控制的协议,如 U D P,必须自己 识别和响应这种情况 (习题5.8)。 5.3.7 SLIP性能考虑 一个S L I P帧的M T U (S L M T U)、c l i s t高水位标记 (high-water mark)(S L I P _ H I W A T)和S L I P的 TOS排队策略都是用来设计交互通信的低速串行链,使得固有的时延最小。 1) 一个小的 M T U能够改进交互数据的时延 (如敲键和回显 ),但有损批量数据传输的吞吐 量。一个大的 M T U能改进批量数据的吞吐量,但增加了交互时延。 S L I P链路的另一个 问题是键入一个字符就要有 4 0字节的开销来写入 T C P首部和 I P首部的信息,这就增加 了通信的时延。 解决办法是挑选一个足够大的 M T U来提供好的交互响应时间和适当的批量数据吞吐 量,并压缩 T C P / I P首部来减小每个分组的负荷。 RFC 1144 [Jacobson 1990a]描述了一 个压缩方案和时间计算,它为一个典型的 9600 b/s异步S L I P链路选择了一个数值为 2 9 6 的M T U。我们在 2 9 . 1 3节讨论压缩的 S L I P ( C S L I P )。卷1的2 . 1 0节和7 . 2节总结了这种定 时考虑,并说明了在 SLIP链路上的时延。 2) 如果有太多的字节缓存在 clist中(因为SLIP_HIWAT设置得太高 ),TOS排队会受到阻碍, 因为新的交互式通信等在大量缓存数据的后面。如果 S L I P一次传给 T T Y驱动程序一个 字节(因为S L I P _ H I W A T设置得太低 ),设备为每个字节调用 s l s t a r t,并在每个字节 传输后线路空闲一段时间。把SLIP_HIWAT设置为100可使在设备排队的数据量最小化, 并且减小了 TTY子系统调用 slstart的频率,大约每 100字符必须调用slstart一次。 3) 如前所述, S L I P驱动程序提供了 TO S排队,其策略是先从 s c _ f a s t q队列中发送交互 式通信数据,然后在标准接口队列 if_snd中发送其他的通信数据。 5.3.8 slclose函数 为了完整性,我们显示函数 s l c l o s e。当s l a t t a c h程序关闭 S L I P的T T Y设备,并且中 断对远程系统的连接时,调用它。

图5-23 函数slclose

118

计计TCP/IP详解 卷 2:实现

图5-23 (续)

210-230

t p指向要关闭的 T T Y设备。 s l c l o s e清除任何残留在串行设备中的数据,中断

T T Y和网络处理,并且将 T T Y复位到默认的线路规程。如果 T T Y设备被连接到一个 S L I P接口, 则关闭这个接口,在这两个结构间的链接被切断,与此接口关联的 m b u f簇被释放,并且指向 现在被丢弃的簇的指针被复位。最后, splx重新允许TTY中断和网络中断。 5.3.9 sltioctl函数 回忆一下, SLIP在内核中有两种作用: • 作为一个网络接口; • 作为一个TTY线路规程。 图5 - 7显示了 s l i o c t l处理通过一个插口描述符发送给一个 S L I P接口的 i o c t l命令。在 4 . 4节中,我们显示了 i f i o c t l是如何调用 s l i o c t l的。我们会看到一个处理 i o c t l命令的 相似模型,并且在后面的章节中会讨论到。 图5 - 7还表示了 s l t i o c t l处理发送给与一个 S L I P网络接口关联的 T T Y设备的 i o c t l命 令。这个被 sltioctl识别的命令显示在图 5-24中。 命



SLIOCGUNIT





int *





sltioctl





返回与TTY设备关联的接口联合

图5-24 sltioctl 命令

函数sltioctl显示在图5-25中。

图5-25 函数sltioctl

119

第 5章 接口:SLIP和环回计计

236-252

t t y结构的 t _ s c指针指向关联的 s l _ s o f t c结构。这个 S L I P接口的设备号从

if_unit被复制到*data,它最后返回给进程 (17.5节)。 当系统被初始化时, s l a t t a c h初始化 i f _ u n i t,并且当 s l a t t a c h程序为此 T T Y设备 选择SLIP线路规程时, s l o p e n初始化t _ s c。因为一个TTY设备和一个 SLIP s l _ s o f t c结构 间的关系是在运行时建立的,一个进程能通过 SLIOCGUNIT命令发现所选择的接口结构。

5.4 环回接口 任何发送给环回接口 (图5-26)的分组立即排入输入队列。接口完全用软件实现。

OSI协议

图5-26 环回设备驱动程序

环回接口的 i f _ o u t p u t指向的函数l o o u t p u t,将输出分组放置到分组的目的地址指明 的协议的输入队列中。 我们已经看到当设备被设置为 I F F _ S I M P L E X时,e t h e r _ o u t p u t会调用l o o u t p u t来 排队一个输出广播分组。在第

1 2 章中,我们会看到多播分组也可能以这种方式环回。

looutput显示在图5-27中。

图5-27 函数looutput

120

计计TCP/IP详解 卷 2:实现

图5-27 (续)

57-66

l o o u t p u t的参数同 e t h e r _ o u t p u t一样,因为都是通过它们的 i f n e t结构中的

i f _ o u t p u t指针直接调用的。 i f p,指向输出接口的 i f n e t结构的指针; m,要发送的分 组; d s t,分组的目的地址; r t,路由信息。如果链表中的第一个 m b u f不包含一个分组, looutput调用panic。 图5-28所示的是一个BPF环回分组的逻辑格式。 69-83

驱动程序在堆栈上的 m 0中构造BPF环回分组,并且把 m 0连接到包含原始分组的 mbuf

121

第 5章 接口:SLIP和环回计计

链表中。注意 m 0的声明不同往常。它是一个 m b u f,而不是一个 m b u f指针。 m 0的m _ d a t a指向 af,它也分配在这个堆栈中。图 5-29显示了这种安排。 BPF 首部 地址簇

原分组

4字节

图5-28 BPF环回分组:逻辑格式

由looutput 在栈上分配

在内核mbuf 池中分配

图5-29 BPF环回分组: mbuf格式

l o o u t p u t将目的地址族复制到 af,并且将新 mbuf链表传递给 b p f _ m t a p,去处理这个分 组。与 b p f _ t a p相比,它在一个单独的连续缓存中接收这个分组而不是在一个 m b u f链表中。 如图中注释所示, BPF从来不释放一个链表中的 mbuf,因此将m 0 (它指向栈中的一个 m b u f)传 给bpf_mtap是安全的。 84-89

l o o u t p u t剩下的代码包含 i n p u t对此分组的处理。虽然这是一个输出函数,但分组

被环回到输入。首先, m - > m _ p k t h d r . r c v i f设置为指向接收接口。如果调用方提供一个路 由项, l o o u t p u t 检查是否它指示此分组应该被拒绝

( R T F _ R E J E C T )或直接被丢弃

(R T F _ B L A C K H O L E)。通过丢弃 m b u f并返回0来实现一个黑洞。从调用者看来就好像分组已经 被传输了。要拒绝一个分组,如果路由是一个主机,则 l o o u t p u t返回E H O S T U N R E A C H;如 果路由是一个网络则返回 ENETUNREACH。 各种RTF_xxx标志在图18-25中描述。 90-120

然后l o o u t p u t通过检查分组目的地址中的 s a _ f a m i l y来选择合适的协议输入队

列和软件中断。接着把识别的分组进行排队,并用 schednetisr来调度一个软件中断。

5.5 小结 我们讨论了两个剩下的接口,它们在书中多次引用: s l 0,一个SLIP接口; l o 0,标准的 环回接口。 我们显示了在 S L I P接口和S L I P线路规程之间的关系,讨论了 S L I P封装方法,并且讨论了 TOS处理交互式通信和 SLIP驱动程序的其他性能考虑。 我们显示了环回接口是如何按目的地址分用输出分组及将分组放到相应的输入队列中 去。

122

计计TCP/IP详解 卷 2:实现

习题 5.1 为什么环回接口没有输入函数? 5.2 你认为为什么图 5-27中的m0要分配在堆栈中? 5.3 分析一个19 200 b/s的串行线的 SLIP特性。对于这个线路, SLIP MTU应该改变吗? 5.4 导出一个根据串行线速率选择 SLIP MTU的公式。 5.5 如果一个分组对于 SLIP输入缓存太大,会发生什么情况? 5.6 一个 s l i n p u t的早期版本,当一个分组在输入缓存溢出时,不将 S C _ E R R O R置位。 在这种情况下如何检测这种差错? 5.7 在图4 - 3 1中l e被下标为 i f p - > i f _ u n i t的l e _ s o f t c数组项初始化。你能想出另一 种初始化 le的方法吗? 5.8 当分组因为网络瓶颈被丢弃时,一个 UDP应用程序如何知道?

第6章 IP 编 址 6.1 引言 本章讨论 N e t / 3如何管理 I P地址信息。我们从 i n _ i f a d d r和s o c k a d d r _ i n结构开始,它 们基于通用的ifaddr和sockaddr结构。 本章其余部分讨论 IP地址的指派和几个查询接口数据结构与维护 IP地址的实用函数。 6.1.1 IP地址 虽然我们假设读者熟悉基本的 Internet编址系统,仍然有几点值得指出。 在I P模型中,地址是指派给一个系统 (一个主机或路由器 )中的网络接口而不是系统本身。 在系统有多个接口的情况下,系统有多重初始地址,并有多个 I P地址。一个路由器被定义为 有多重初始地址。如我们所看到的,这个体系特点有几个小分支。 I P地址定义了 5类。A、B和C类地址支持单播通信。 D类地址支持 I P多播。在一个多播通 信中,一个单独的源方发送一个数据报给多个目标方。 D类地址和多播协议在第 1 2章说明。 E 类地址是试验用的。接收的 E类地址分组被不参与试验的主机丢弃。 我们强调IP多播和硬件多播间的区别是重要的。硬件多播的特点是数据链路硬件用来将帧 传输给多个硬件接口。有些网络硬件,如以太网,支持数据链路多播。其他硬件可能不支持。 I P多播是一个在 I P系统内实现的软件特性,将分组传输给多个可能在 I n t e r n e t中任何位置 的IP地址。 我们假设读者熟悉 I P网络的子网划分 (RFC 950 [Mogul and Postel 1985] 和卷1的第3章)。 我们会看到每个网络接口有一个相关的子网掩码,它是判断一个分组是否到达它最后的目的 地或还需要被转发的关键。通常,当提及一个 I P地址的网络部分时,我们包括任何可能定义 的子网。当需要区分网络和子网时,我们就要明确地指出来。 环回网络, 1 2 7 . 0 . 0 . 0,是一个特殊的 A类网络。这种格式的地址是不会出现在一个主机的 外部的。发送到这个网络的分组被环回并被这个主机接收。 RFC 11 2 2要求所有在环回网络中的地址被正确地处理。因为环回接口必须指派 一个地址,很多系统选择 1 2 7 . 0 . 0 . 1 作为环回地址。如果系统不能正确识别,像 127.0.0.2这样的地址可能不能被路由到环回接口而被传输到一个连接的网络,这是不 允许的。有些系统可能正确地路由这个到环回接口的分组,但由于目标地址与地址 127.0.0.1不匹配,分组被丢弃。 图18-2显示了一个 Net/3系统配置为拒绝接收发送到一个不是 127.0.0.1的环回地址的分组。 6.1.2 IP地址的印刷规定 我们通常以点分十进制数表示法来显示一个 IP地址。图 6-1列出了每类 IP地址的范围。

124

计计TCP/IP详解 卷 2:实现

地址类 A B C D E





类 型

0.0.0.0到127.255.255.255 128.0.0.0到191.255.255.255 192.0.0.0到223.255.255.255 224.0.0.0到239.255.255.255 240.0.0.0到247.255.255.255

单播 多播 试验性

图6-1 不同IP地址类的范围

对于我们的有些例子,子网字段不按一个字节对齐 (即,一个网络 /子网/主机在一个B类网 络中分为 1 6 / 11 / 5 )。从点分十进制数表示法很难表示这样的地址,因此我们还是用方块图来说 明I P地址的内容。我们用三个部分显示每个地址:网络、子网和主机。每个部分的阴影指示 它的内容。图 6-2用我们网络示例 (1.14节)中的主机 s u n的以太网接口来同时说明块表示法和点 分十进制数表示法。

B类网络140.252

子网105

主机1

接口地址 全0 有1有0 网络

子网

主机

有1有0

网络掩码 全1

网络140.252

子网105

所有主机 31

定向的广播

图6-2 可选的IP地址表示法

当地址的一个部分不是全为 0或1时,我们使用两个中等程度的阴影。有两种中等 程度的阴影,这样我们就能区分网络和子网部分或用来显示如图 6 - 3 1所示的地址组 合。 6.1.3 主机和路由器 在一个I n t e r n e t上的系统通常能划分为两类:主机和路由器。一个主机通常有一个网络接 口,并且是一个 I P分组的源或目标方。一个路由器有多个网络接口,当分组向它的目标方移 动时将分组从一个网络转发到下一个网络。为执行这个功能,路由器用各种专用路由协议来 交换关于网络拓扑的信息。 IP路由问题比较复杂,在第 18章开始讨论它们。 如果一个有多个网络接口的系统不在网络接口间路由分组,仍然叫一个主机。一个系统 可能既是一个主机又是一个路由器。这种情况经常发生在当一个路由器提供运输层服务如用

125

第 6章 IP 编 址计计

于配置的 Te l n e t访问,或用于网络管理的 S N M P时。当区分一个主机和路由器间的意义并不重 要时,我们使用术语系统。 不谨慎地配置一个路由器会干扰一个网络的正常运转,因此 RFC 11 2 2规定一个系统必须 默认为一个主机来操作,并且必须显式地由一个管理员来配置作为一个路由器操作。这样做 是不鼓励管理员将通用主机作为路由器来操作而没有仔细地配置。在 N e t / 3中,如果全局整数 i p f o r w a r d i n g不为0,则一个系统作为一个路由器;如果 i p f o r w a r d i n g为0 (默认),则系 统作为一个主机。 在N e t / 3中,一个路由器通常称为网关,虽然术语网关现在更多的是与一个提供应用层路 由的系统相关,如一个电子邮件网关,而不是转发 I P分组的系统。我们在本书中使用术语路 由器,并假设 i p f o r w a r d i n g非0。在编译 N e t / 3内核期间,当 GATEWAY被定义时,我们还 有条件地包括所有代码,它们将 ipforwarding定义为1。

6.2 代码介绍 图6-3所列的两个头文件和两个 C文件包含本章中讨论的结构定义和实用函数。 文





netinet/in.h netinet/in_var.h netinet/in.c netinet/if.c



Internet地址定义 Internet接口定义 Internet初始化和实用函数 Internet接口实用函数

图6-3 本章讨论的文件

全局变量 图6-4所列的是本章中介绍的两个全局变量。 变



in_ifaddr in_interfaces

数据类型





s t r u c t i n _ i f a d d r * i n _ i f a d d r结构列表的首部 int 有IP能力的接口个数

图6-4 在本章中介绍的全局变量

6.3 接口和地址小结 在本章讨论的所有接口和地址结构的一个例子配置如图 6-5所示。 图6 - 5显示了我们的三个接口例子:以太网接口、 S L I P接口和环回接口。它们都有一个链 路层地址作为地址列表中的第一个结点。显示的以太网接口有两个 I P地址, S L I P接口有一个 IP地址,并且环回接口有一个 IP地址和一个 OSI地址。 注意所有的IP地址被链接到in_ifaddr列表中,并且所有链路层地址能从 ifnet_addrs 数组访问。 为了清楚起见,图 6 - 5没有画出每个 i f a d d r结构中的指针 i f a _ i f p。这些指针回指包含 此ifaddr结构的列表的首部 ifnet结构。 接下来的部分讨论图 6-5中的数据结构及用来查看和修改这些结构的 IP专用ioctl命令。

126

计计TCP/IP详解 卷 2:实现

每个in_ifaddr{}以 一个ifaddr{}开始

图6-5 接口和地址数据结构

6.4 sockaddr_in结构 我们在第 3章讨论了通用的 s o c k a d d r和i f a d d r结构。现在我们显示 I P专用的结构: sockaddr_in和in_ifaddr。在Internet域中的地址存放在一个 sockaddr_in结构: 68-70

由于历史原因, Net/3以网络字节序将 Internet地址存储在一个 in_addr结构中。这个结

构只有一个成员 s _ a d d r,它包含这个地址。虽然这是多余和混乱的,但在 N e t / 3中一直保持 这种组织方式。 106-112

s i n _ l e n总是1 6(结构s o c k a d d r _ i n的大小),并且s i n _ f a m i l y为A F _ I N E T。

sin_port是一个网络字节序(不是主机字节序 )的16 bit值,用来分用运输层报文。 sin_addr 标识一个32 bit Internet地址。 图6-6显示了sock a d d r _ i n的成员s i n _ p o r t、s i n _ a d d r和s i n _ z e r o覆盖s o c k a d d r

127

第 6章 IP 编 址计计

的成员sa_data。在I n t e r n e t域中,s i n _ z e r o未用,但必须由全 0字节组成(2.7节)。将它 追加到sockaddr_in结构后面,以得到与一个 sockaddr结构一样的长度。

图6-6 结构sockaddr_in

通常,当一个 I n t e r n e t地址存储在一个 u _ l o n g中时,它以主机字节序存储,以便于地址的 压缩和位操作。在 in_addr结构(图6-7)中的s_addr是一个值得注意的例外。

图6-7 一个sockaddr_in 结构(省略sin_ )的组织

6.5 in_ifaddr结构 图6 - 8显示了为 I n t e r n e t协议定义的接口地址结构。对于每个指派给一个接口的 I P地址,分 配了一个in_ifaddr结构,并且添加到接口地址列表中和 IP地址全局列表中 (图6-5)。 41-45 in_ifaddr开始是一个通用接口地址结构 ia_ifa,跟着是IP专用成员。 ifaddr结 构显示在图 3 - 1 5中。两个宏 i a _ i f p和i a _ f l a g s简化了对存储在通用 i f a d d r结构中的接口 指针和接口地址标志的访问。 i a _ n e x t维护指派给任意接口的所有 I n t e r n e t地址的链接列表。 这个列表独立于每个接口关联的链路层 i f a d d r结构列表,并且通过全局列表 i n _ i f a d d r来 访问。 46-54

其余的成员 (除了ia_multiaddrs)显示在图6-9中,它显示了在我们的 B类网络例子

中s u n的三个接口的相应值。地址按主机字节序以 u _ l o n g 变量存储;变量 i n _ a d d r 和 s o c k a d d r _ i n按照网络字节序存储。 s u n有一个P P P接口,但显示在本表中的信息对于一个 PPP或SLIP接口是一样的。 55-56

结构i n _ i f a d d r的最后一个成员指向一个 i n _ m u l t i结构的列表 ( 1 2 . 6节),其中每

项包含与此接口有关的一个 IP多播地址。

128

计计TCP/IP详解 卷 2:实现

图6-8 结构in_ifaddr 变







以 太 网

PPP

环回

说明 网络,子网和主机号 网络号 网络号掩码 网络和子网号 网络和子网掩码

网络广播地址 定向广播地址 目的地址 像ia_subnetmask 但是用网络字节序

图6-9 sun 上的以太网、 PPP和环回in_ifaddr 结构

6.6 地址指派 在第4章中,我们显示了当接口结构在系统初始化期间被识别时的初始化。在 Internet协议 能通过这个接口进行通信前,必须指派一个 I P地址。一旦 N e t / 3内核运行,程序 i f c o n f i g就 配置这些接口, i f c o n f i g通过在某个插口上的 i o c t l系统调用来发送配置命令。这通常通 过/ e t c / n e t s t a r t s h e脚本来实现,这个脚本在系统引导时执行。 ll 图6 - 1 0显示了本章中讨论的 i o c t l命令。命令相关的地址必须是此命令指定插口所支持 的地址族类 (即,你不能通过一个 U D P插口配置一个 O S I地址)。对于I P地址,i o c t l命令在一

129

第 6章 IP 编 址计计

个UDP插口上发送。 命





SIOCGIFADDR SIOCGIFNETMASK SIOCGIFDSTADDR SIOCGIFBRDADDR SIOCSIFADDR SIOCSIFNETMASK SIOCSIFDSTADDR SIOCSIFBRDADDR SIOCDIFADDR SIOCAIFADDR

struct struct struct struct struct struct struct struct struct struct







ifreq * in_control ifreq * in_control ifreq * in_control ifreq * in_control ifreq * in_control ifreq * in_control ifreq * in_control ifreq * in_control ifreq * in_control i n _ a l i a s r e q * in_control

图6-10 接口ioctl 命令

系统调用

默认

图6-11 本章中说明的ioctl 函数





获得接口地址 获得接口网络掩码 获得接口目标地址 获得接口广播地址 设置接口地址 设置接口网络掩码 设置接口目标地址 设置接口广播地址 删除接口地址 添加接口地址

130

计计TCP/IP详解 卷 2:实现

获得地址信息的命令从 S I O C G开始,设置地址信息的命令从 S I O C S 开始。 S I O C代表 socket ioctl,G代表get,而S代表set。 在第4章中,我们看到了 5个与协议无关的 i o c t l命令。图 6 - 1 0中的命令修改一个接口的 相关地址信息。由于地址是特定协议使用的,因此,命令处理是与协议相关的。图 6 - 11强调 了与这些命令关联的 ioctl相关函数。 6.6.1 ifioctl函数 如图6-11所示,i f i o c t l将协议无关的i o c t l命令传递给此插口关联协议的 p r _ u s r r e q 函数。将控制交给 u d p _ u s r r e q,并且又立即传给 i n _ c o n t r o l,在i n _ c o n t r o l中进行大 部分的处理。如果在一个 T C P插口上发送同样的命令,控制最后也会到达 i n _ c o n t r o l。图 6-12再次显示了 ifioctl中的default代码,第一次显示在图 4-22中。

图6-12 函数ifioctl :特定协议的命令

447-454

函数将图 6 - 1 0中所列 i o c t l命令的所有相关数据传给与请求指定的插口相关联的

协议的用户请求函数。对于一个 UDP插口,调用 u d p _ u s r r e q。23.10节讨论u d p _ u s r r e q函 数的细节。现在,我们仅需要查看 udp_usrreq中的PRU_CONTROL代码: if (req == PRU_CONTROL) return (in_control(so, (int)m, (caddr_t)addr, (struct ifnet *)control));

6.6.2 in_control函数 图6 - 11显示了通过 s o o _ i o c t l中的d e f a u l t或i f i o c t l中的与协议相关的情况,控制 能到达 i n _ c o n t r o l 。在这两种情况中, u d p _ u s r r e q 调用 i n _ c o n t r o l ,并返回 in_control的返回值。图6-13显示了in_control。 132-145

so指向这个ioctl命令(由第二个参数cmd标识)指定的插口。第三个参数 data指

向命令所用或返回的数据 (图6 - 1 0的第二列 )。最后一个参数 i f p为空(来自s o o _ i o c t l的无接 口i o c t l)或指向结构 i f r e q或i n _ a l i a s r e q中命名的接口 (来自i f i o c t l的接口 i o c t l)。 in_control初始化ifr和ifra来访问作为一个 ifreq或in_aliasreq结构的data。 146-152

如果i f p指向一个i f n e t结构,这个f o r循环找到与此接口关联的 I n t e r n e t地址列

表中的第一个地址。如果发现一个地址, ia指向它的in_ifaddr结构;否则 ia为空。 若i f p为空, c m d就不会匹配第一个 s w i t c h中的任何情况;或第二个 s w i t c h中任何非 默认情况。在第二个 switch中的default情况中,当 ifp为空时,返回EOPNOTSUPP。 153-330

in_control中的第一个 switch确保在第二个switch处理命令之前每个命令的

前提条件都满足。在后面的章节会单独说明各个情况。

131

第 6章 IP 编 址计计

图6-13 函数in_control

如果在第二个 s w i t c h中的d e f a u l t情况被执行, i f p指向一个接口结构;并且如果接 口有一个if_ioctl函数,则 in_control将ioctl命令传给这个接口进行设备的特定处理。 N e t / 3不定义任何会被 d e f a u l t情况处理的接口命令。但是,一个特定设备的驱 动程序可能会定义它自己的接口 ioctl命令,并通过这个 case来处理它们。 331-332

我们会看到这个 s w i t c h语句中的很多情况都直接返回了。如果控制落到两个

switch语句外,则 in_control返回0。第二个switch中有几个case执行了跳出语句。 我们按照下面的顺序查看这个接口 ioctl命令: • 指派一个地址、网络掩码或目标地址; • 指派一个广播地址;

132

计计TCP/IP详解 卷 2:实现

• 取回一个地址、网络掩码、目标地址或广播地址; • 给一个接口指派多播地址; • 删除一个地址。 对于每组命令,在第一个 s w i t c h语句中进行前提条件处理,然后在第二个 s w i t c h语句 中处理命令。 6.6.3 前提条件: SIOCSIFADDR、SIOCSIFNETMASK和SIOCSIFDSTADDR 图6 - 1 4显示了对 S I O C S I F A D D R、S I O C S I F N E T M A S K和S I O C S I F D S T A D D R的前提条件 检验。

图6-14 函数in_control :地址指派

133

第 6章 IP 编 址计计

1. 仅用于超级用户 166-172

如果这个插口不是由一个超级用户进程创建的,这些命令被禁止,并且

in_

control返回EPERM。如果此请求没有关联的接口,内核调用 panic。由于如果 ifioctl不 能找到一个接口,它就返回 (图4-22),因此,panic从来不会被调用。 当一个超级用户进程创建一个插口时, s o c r e a t e(图15-16)设置标志 S S _ P R I V。 因为这里的检验是针对标志而不是有效的进程用户 I D的,所以一个设置用户 I D的根 进程能创建一个插口,并且放弃它的超级用户权限,但仍然能发送有特权的 i o c t l 命令。 2. 分配结构 173-191

如果ia为空,命令请求一个新的地址。 in_control分配一个in_ifaddr结构,

用b z e r o清除它,并且将它链接到系统 的 i n _ i f a d d r 列表中和此接口的 if_addrlist列表中。 3. 初始化结构 192-206

代码的下一部分初始化

i n _ i f a d d r结构。首先,在此结构的 i f a d d r部分的通用指针被初始化为指 向结构in_ifaddr中的结构 s o c k a d d r _ i n。必要时,此函数还初 始化结构i a _ s o c k m a s k 和

图6-15 被in_control 初始化后的一个 in_ifaddr 结构

i a _ b r o a d a d d r。图6 - 1 5说明了初始化 后的结构 in_ifaddr。 202-206 最后,in_control建立从in_ifaddr到此接口的 ifnet结构的回指指针。 Net/3在in_interfaces中只统计非环回接口。 6.6.4 地址指派: SIOCSIFADDR 前提条件处理代码保证 i a指向一个要被 S I O C S I F A D D R命令修改的i n _ i f a d d r结构。图 6-16显示了in_control第二个switch中处理这个命令的执行代码。

图6-16 函数in_control :地址指派

159-261

i n _ i f i n i t完成所有的工作。 I P地址包含在 i f r e q结构(i f r _ a d d r)里传递给

in_ifinit。 6.6.5 in_ifinit函数 in_ifinit的主要步骤是: • 将地址复制到此结构并将此变化通知硬件;

134

计计TCP/IP详解 卷 2:实现

• 忽略原地址配置的任何路由; • 为这个地址建立一个子网掩码; • 建立一个默认路由到连接的网络 (或主机); • 将此接口加入到所有主机组。 从图6-17开始分三个部分讨论这段代码。 353-357

i n _ i f i n i t的四个参数为: i f p,指向接口结构的指针; i a,指向要改变的

i n _ i f a d d r结构的指针; s i n,指向请求的 I P地址的指针; s c r u b,指示这个接口如果存在 路由应该被忽略。 i保存主机字节序的 IP地址。

图6-17 函数in_ifinit :地址指派和路由初始化

1. 指派地址并通知硬件 358-374

i n _ c o n t r o l将原来的地址保存在 o l d a d d r中,万一发生差错时,必须恢复它。

如果接口定义了一个 i f _ i o c t l 函数,则 i n _ c o n t r o l 调用它。相同接口的三个函数 l e i o c t l、s l i o c t l和l o i o c t l在下一节讨论。如果发生差错,恢复原来的地址,并且 in_control返回。 2. 以太网配置 375-378

对于以太网设备, a r p _ r t r e q u e s t作为链路层路由函数被选择,并且设置

RT F _ C L O N I N G标志。 a r p _ r t r e q u e s t在2 1 . 1 3节讨论,而 RT F _ C L O N I N G在1 9 . 4节的最后

135

第 6章 IP 编 址计计

讨论。如XXX注释所建议,在此加入代码以避免改变所有以太网驱动程序。 3. 忽略原来的路由 379-384

如果调用者要求已存在的路由被清除,原地址被重新连接到 i f a _ a d d r,同时

in_ifscrub找到并废除任何基于老地址的路由。 in_ifscrub返回后,新地址被恢复。 in_ifinit显示在图6-18中的部分构造网络和子网掩码。

图6-18 函数in_ifinit :网络和子网掩码

4. 构造网络掩码和默认子网掩码 385-400

根据地址是一个 A类、B类或C类地址,在 i a _ n e t m a s k中构造了一个尝试性网络

掩码。如果这个地址没有子网掩码, i a _ s u b n e t m a s k 和i a _ s o c k m a s k 被初始化为 ia_netmask中的尝试性掩码。 如果指定了一个子网, i n _ i f i n i t将这个尝试性网络掩码和这个已存在的子网掩码进行 逻辑与运算来获得一个新的网络掩码。这个操作可能会清除该尝试性网络掩码的一些 1 bit(它 从来不设置 0 bit,因为0逻辑与任何值都得到 0 )。在这种情况下,网络掩码比所考虑的地址类 型所期望的要少一些 1 bit 。 这叫作超级联网,它在 RFC 1519 [Fuller et al. 1993]中作了描述。一个超级网络 是几个A类、B类或C类网络的一个群组。卷 1的10.8节也讨论了超级联网。 一个接口默认配置为不划分子网

( 即,网络和子网的掩码相同 ) 。一个显式请求 ( 用

SIOCSIFNETMASK或SIOCAIFADDR)用来允许子网划分 (或超级联网 )。 5. 构造网络和子网数量 401-403

网络和子网数量通过网络和子网掩码从新地址中获得。函数 in_socktrim通过查

找掩码中包含1 bit的最后一个字节来设置in_sockmask(是一个sockaddr_in结构)的长度。 图6 - 1 9显示了 i n _ i f i n i t的最后一部分,它为接口添加了一个路由,并加入所有主机多 播组。 6. 为主机或网络建立路由 404-422

下一步是为新地址所指定的网络创建一个路由。 i n _ c o n t r o l从接口将路由度量

136

计计TCP/IP详解 卷 2:实现

复制到结构i n _ i f a d d r中。如果接口支持广播,则构造广播地址,并且把目的地址强制为分 配给环回接口的地址。如果一个点对点接口没有一个指派给链路另一端的

I P 地址,则

in_control在试图为这个无效地址建立路由前返回。 in_ifinit将flags初始化为RTF_UP,并与环回和点对点接口的 RTF_HOST进行逻辑或。 r t i n i t为此接口给这个网络 (不设置 R T F _ H O S T)或主机 (设置R T F _ H O S T)安装一个路由。若 rtinit安装成功,则设置ia_flags中的标志IFA_ROUTE,指示已给此地址安装了一个路由。

图6-19 函数in_ifinit :路由和多播组

7. 加入所有主机组 423-433

最后,一个有多播能力的接口当它被初始化时必须加入所有主机多播组。

in_addmulti完成此工作,并在 12.11节讨论。 6.6.6 网络掩码指派: SIOCSIFNETMASK 图6-20显示了网络掩码命令的处理。

图6-20 函数in_control :网络掩码指派

137

第 6章 IP 编 址计计

262-265

i n _ c o n t r o l 从i f r e q 结构中获取网络掩码,并将它以网络字节序保存在

ia_sockmask中,以主机字节序保存在 ia_subnetmask中。 6.6.7 目的地址指派: SIOCSIFDSTADDR 对于点对点接口,在链路另一端的系统的地址用 S I O C S I F D S T A D D R命令指定。图 6 - 1 4显 示了图6-21中的代码的前提条件处理。

图6-21 函数in_control :目的地址指派

236-245

只有点对点网络才有目的地址,因此对于其他网络, in_control返回EINVAL。

将当前目的地址保存在 o l d a d d r后,代码设置新地址,并通过函数 i f _ i o c t l通知硬件。如 果发生差错,则恢复原地址。 246-253

如果地址原来有一个关联的路由,首先调用 r t i n i t删除这个路由,并再次调用

rtinit为新地址安装一个新路由。 6.6.8 获取接口信息 图6 - 2 2显示了命令 S I O C S I F B R D A D D R的前提条件处理,它同将接口信息返回给调用进程 的ioctl命令一样。

图6-22 函数in_control :前提条件处理

138

计计TCP/IP详解 卷 2:实现

207-217 广播地址只能通过一个超级用户进程创建的插口来设置。命令 SIOCSIFBRDADDR 和4个S I O C Gx x x命令仅当已经为此接口定义了一个地址时才起作用,在这种情况下, i a不会 为空(ia被in_control设置,图6-13)。如果ia为空,返回 EADDRNOTAVAIL。 这5个命令(4个get命令和一个 set命令)的处理显示在图 6-23中。

图6-23 函数in_control :处理

220-235

将单播地址、广播地址、目的地址或者网络掩码复制到 i f r e q结构。只有网络接

口支持广播,广播地址才有效;并且只有点对点接口,目的地址才有效。 254-258 仅当接口支持广播,才从结构 ifreq中复制广播地址。 6.6.9 每个接口多个IP地址 S I O C Gx x x和S I O C Sx x x命令只操作与一个接口关联的第一个 I P地址—在i n _ c o n t r o l 开头的循环找到的第一个地址

( 图 6 - 2 5 ) 。为支持每个接口的多个

I P 地址,必须用

S I O C A I F A D D R命令指派和配置其他的地址。实际上, S I O C A I F A D D R能完成所有 S I O C Gx x x 和S I O C Sx x x命令能完成的操作。程序 i f c o n f i g使用S I O C A I F A D D R来配置一个接口的所有 地址信息。 如前所述,每个接口有多个地址便于在主机或网络改号时过渡。一个容错软件系统可能 使用这个特性来准许一个备份系统充当一个故障系统的 IP地址。 Net/3的i f c o n f i g程序的- a l i a s选项将存放在一个 i n _ a l i a s r e q中的其他地址的相关 信息传递给内核,如图 6-24所示。

139

第 6章 IP 编 址计计

图6-24 结构in_aliasreq

59-65

注意,不像结构 i f r e q ,在结构 i n _ a l i a s r e q中没有定义联合。在一个单独的

ioctl调用中可以为SIOCAIFADDR指定地址、广播地址和掩码。 S I O C A I F A D D R增加一个新地址或修改一个已存在地址的相关信息。 S I O C D I F A D D R删除 匹配的 I P地址的i n _ i f a d d r结构。图 6 - 2 5显示命令 S I O C A I F A D D R和S I O C D I F A D D R的前提 条件处理,它假设在

i n _ c o n t r o l ( 图 6 - 1 3 ) 开头的循环已经将 i a设置为指向与

ifra_name(如果存在)指定的接口关联的第一个 IP地址。

图6-25 函数in_control :添加和删除地址

154-165

因为S I O C D I F A D D R代码只查看 * i f r a的前两个成员,图 6 - 2 5所示的代码用于处

理S I O C A I F A D D R(当i f r a指向一个 i n _ a l i a s r e q结构时)和S I O C D I F A D D R(当i f r a指向一 个ifreq结构时)。结构in_aliasreq和ifreq的前两个成员是一样的。 对于这两个命令, i n _ c o n t r o l 开头的循环启动 f o r 循环不断地查找与 i f r a > i f r a _ a d d r指定的IP地址相同的i n _ i f a d d r结构。对于删除命令,如果地址没有找到,则 返回EADDRNOTAVAIL。 在这个处理删除命令的循环和检验后,控制落到我们在图 6 - 1 4中讨论的代码之外。对于 添加命令,图 6-14的代码若找不到一个与 i n _ a l i a s r e q结构中地址匹配的地址,就分配一个 新in_ifaddr结构。 6.6.10 附加IP地址:SIOCAIFADDR 这时 i a 指向一个新的 i n _ i f a d d r 结构或一个包含与请求地址匹配的

I P 地址的旧

in_ifaddr结构。SIOCAIFADDR的处理显示在图 6-26中。 266-277

因为S I O C A I F A D D R能创建一个新地址或修改一个已存在地址的相关信息,标志

m a s k I s N e w和h o s t I s N e w跟踪变化的情况。这样,在这个函数结束时,如果必要,能更新 路由。

140

计计TCP/IP详解 卷 2:实现

图6-26 函数in_control :SIOCAIFADDR 处理

代码在默认方式下取一个新的 IP地址指派给接口 (h o s t I s N e w以1开始)。如果新地址的长 度为0,i n _ c o n t r o l将当前地址复制到请求中,并将 h o s t I s N e w修改为0。如果长度不是 0, 并且新地址与老地址匹配,则这个请求不包含一个新地址,并且 hostIsNew被设置为0。 278-284

如果在这个请求中指定一个网络掩码,则任何使用此当前地址的路由被忽略,并

且in_control安装此新掩码。 285-290

如果接口是一个点对点接口,并且此请求包括一个新目的地址,则 i n _ s c r u b忽

略任何使用此地址的路由,新目的地址被安装,并且 m a s k I s N e w被设置为 1,以强制调用 in_ifinit来重配置接口。 291-297

如果配置了一个新地址或指派了一个新掩码,则 in_ifinit作适当的修改来支持

新的配置 (图6-17)。注意,i n _ i f i n i t的最后一个参数为 0。这表示已注意到这一点,不必刷 新所有路由。最后,如果接口支持广播,则从 in_aliasreq结构复制广播地址。 6.6.11 删除IP地址:SIOCDIFADDR 命令 S I O C D I F A D D R从一个接口删除 I P地址,如图 6 - 2 7所示。记住, i a指向要被删除的 in_ifaddr结构(即,与请求匹配的 )。

141

第 6章 IP 编 址计计

图6-27 in_control 函数:删除地址

298-323

前提条件处理代码将 ia指向要删除的地址。 in_ifscrub删除任何与此地址关联

的路由。第一个 i f 删除接口地址列表的结构。第二个

i f 删除来自 I n t e r n e t 地址列表

(in_ifaddr)的结构。 324-325 IFAFREE只在引用计数降到 0时才释放此结构。 其他引用可能来自路由表中的各项。

6.7 接口ioctl处理 我们现在查看当一个地址被分配给接口时的专用 i o c t l处理,对于我们的每个例子接口, 这个处理分别在函数 leioctl、slioctl和loioctl中。 i n _ i f i n i t通过图6 - 1 6中的S I O C S I F A D D R代码和图 6 - 2 6中的S I O C A I F A D D R代码调用。 in_ifinit总是通过接口的 if_ioctl函数发送SIOCSIFADDR命令(图6-17)。 6.7.1 leioctl函数 图 4 - 3 1 显示了 L A N C E 驱动程序的 S I O C S I F F L A G S 命令的处理。图 6 - 2 8 显示了 SIOCSIFADDR命令的处理。 614-637 在处理命令前, data转换成一个 ifaddr结构指针,并且 ifp->if_unit为此请 求选择相应的le_softc结构。 l e i n i t将接口标志为启动并初始化硬件。对于 I n t e r n e t地址,I P地址保存在 a r p c o m结构

142

计计TCP/IP详解 卷 2:实现

中,并且为此地址发送一个免费 ARP。免费ARP在21.5节和卷1的4.7节中讨论。 未识别命令 627-677 对于未识别命令,返回 EINVAL。

图6-28 函数leioctl

6.7.2 slioctl函数 函数slioctl(图6-29)为SLIP设备驱动器处理命令 SIOCSIFADDR和SIOCSIFDSTADDR。

图6-29 函数slioctld :命令SIOCSIFADDR 和SIOCSIFDSTADDR

143

第 6章 IP 编 址计计

图6-29 (续)

663-672

对于这两个命令,如果地址不是一个

I P 地址,则返回 E A F N O S U P P O R T。

SIOCSIFADDR命令设置IFF_UP。 未识别命令 688-69 3 对于未识别命令,返回 EINVAL。 6.7.3 loioctl函数 函数loioctl和它的SIOCSIFADDR命令的实现显示在图 6-30中。

图6-30 函数loioctl :命令SIOCSIFADDR

144

计计TCP/IP详解 卷 2:实现

图6-30 (续)

135-151 对于Internet地址,loioctl设置IFF_UP,并立即返回。 未识别命令 167-171 对于未识别命令,返回 EINVAL。 注意,对于所有这三个例子驱动程序,指派一个地址会导致接口被标记为启用 (IFF_UP)。

6.8 Internet实用函数 图6 - 3 1列出了几个操作 I n t e r n e t地址或依赖于图 6 - 5中i f n e t结构的函数,它们通常用于发 现不能单从 32 bit IP地址中获得的子网信息。这些函数的实现主要包括数据结构的转换和操作 位掩码。读者在 netinet/in.c中可以找到这些函数。 函



in_netof





返回in中的网络和子网部分。主机比特被设置为 0。对于D类地址,返回 D类首标比 特和用于多播组的 0比特 u_long in_netof(struct in_addr in);

in_canforward

如果地址为 in的IP分组有资格转发,则返回真。 D类和E类地址、环回网络地址和有 一个为0网络号的地址不能转发 int in_canforward(struct in_addr in);

in_localaddr

如果主机in被定位在一个直接连接的网络,则返回真。如果全局变量subnetsarelocal 非0,则所有直接连接的网络的子网也被认为是本地的 int in_localaddr(struct in_addr in);

in_broadcast

如果in是一个由 ifp指向的接口所关联的广播地址,则返回真 int i n _ b r o a d c a s t( s t r u c t i n _ a d d rin, s t r u c t i f n e t if* p) ;

图6-31 Internet地址函数

N e t / 2在i n _ c a n f o r w a r d中有一个错误:它允许转发环回地址。因为大多数 N e t / 2系统被配置为只承认一个环回地址,如 1 2 7 . 0 . 0 . 1,N e t / 2系统常沿着默认路由在 环回网络中转发其他地址 (例如127.0.0.2)。 一个到127.0.0.2的telnet可能不是你所希望的! (习题6.6)

6.9 ifnet实用函数 几个查找数据结构的函数显示在图 6 - 5中。列于图 6 - 3 2的函数接受任何协议族类的地址, 因为它们的参数是指向一个 s o c k a d d r结构的指针,这个结构中包含有地址族类。与图 6 - 3 1 中的函数比较,在那里的每个函数将 32 bit 的I P 地址作为一个参数。这些函数定义在文件 net/if.c中。

145

第 6章 IP 编 址计计





ifa_ifwithaddr





在i f n e t列表中查找有一个单播或广播地址 addr的接口。返回一个指向这个 匹配的i f a d d r结构的指针;或者若没有找到,则返回一个空指针 s t r u c t i f a d d r *i f a _ i f w i t h a d d r( s t r u c t s o c k a d d r ad*dr) ;

ifa_ifwithdstaddr

在i f n e t列表中查找目的地址为 addr的接口。返回一个指向这个匹配的 i f a d d r结构的指针;或者若没有找到,则返回一个空指针 struct ifaddr *ifa_ifwithdstaddr(struct sockaddr a*ddr);

ifa_ifwithnet

在i f n e t列表中查找与 addr同一网络的地址。返回匹配的 i f a d d r结构的指 针;或者若没有找到,则返回一个空指针 s t r u c t i f a d d r *i f a _ i f w i t h n e t( s t r u c t s o c k a d d r ad*dr) ;

ifa_ifwithaf

在i f n e t列表中查找 与a d d具有相同地址族类的第一个地址。返回匹配的 i f a d d r结构的指针;或者若没有找到,则返回一个空指针 s t r u c t i f a d d r *i f a _ i f w i t h a f( s t r u c t s o c k a d d rad*dr) ;

ifaof_ifpforaddr

在ifp列表中查找与 addr匹配的地址。用于精确匹配的引用次序为:一个点对 点链路上的目的地址、一个同一网络上的地址和一个在同一地址族类的地址, 则返回匹配的 i f a d d r结构的指针;或者若没有找到,则返回一个空指针 struct ifaddr * i f a o f _ i f p f o r a d d(rs t r u c t s o c k a d d r *addr, s t r u c t i f n e t if*p);

ifa_ifwithroute

返回目的地址 (dst)和网关地址 (gateway)指定的相应的本地接口的 i f a d d r结 构的指针 s t r u c ti f a d d r* i f a _ i f w i t h r o u t e ( i n t f l a g s , s o c k a d d r d*st, s t r u c t s o c k a d d rga*teway);

ifunit

struct

返回与name关联的i f n e t结构的指针 s t r u c t i f n e t *i f u n i t( c h a r *name) ;

图6-32 ifnet 实用函数

6.10 小结 在本章中,我们概述了 I P编址机制,并且说明了 I P专用的接口地址结构和协议地址结构: 结构in_ifaddr和sockaddr_in。 我们讨论了如何通过程序 ifconfig和ioctl接口命令来配置接口的 IP专用信息。 最后,我们总结了几个操作 IP地址和查找接口数据结构的实用函数。

习题 6.1 你认为为什么在结构 sockaddr_in中的sin_addr最初定义为一个结构? 6.2 ifunit("sl0")返回的指针指向图 6-5中的哪个结构? 6.3 当 I P 地址已经包含在接口的地址列表中的一个 i f a d d r 结构中时,为什么还要在 ac_ipaddr中备份? 6.4 你认为为什么IP接口地址要通过一个 UDP插口而不是一个原始的 IP插口来访问? 6.5 为什么 i n _ s o c k t r i m 要修改 s i n _ l e n 来匹配掩码的长度,而不使用一个 sockaddr_in结构的标准长度? 6.6 当一个 t e l n e t 1 2 7 . 0 . 0 .命令中的连接请求部分被一个 2 N e t / 2系统错误地转发, 并且最后被认可,同时还被默认路由上的一个系统所接收时,会发生什么情况?

第7章 域 和 协 议 7.1 引言 在本章中,我们讨论支持同时操作多个网络协议的 N e t / 3数据结构。用 I n t e r n e t协议来说明 在系统初始化时这些数据结构的构造和初始化。本章为我们讨论 I P协议处理层提供必要的背 景资料,IP协议处理层在第 8章讨论。 N e t / 3组把协议关联到一个域中,并且用一个协议族常量来标识每个域。 N e t / 3还通过所使 用的编址方法将协议分组。回忆图 3 - 1 9,地址族也有标识常量。当前,在一个域中的每个协 议使用同类地址,并且每种地址只被一个域使用。作为结果,一个域能通过它的协议族或地 址族常量唯一标识。图 7-1列出了我们讨论的协议和常量。 协 议 族

地 址 族

PF_INET PF_OSI, PF_ISO PF_LOCAL, PF_UNIX PF_ROUTE n/a





AF_INET Internet AF_OSI, AF_ISO OSI A F _ L O C A L , A F _ U N I X 本地IPC(Unix) AF_ROUTE 路由表 AF_LINK 链路层(例如以太网 )

图7-1 公共的协议和地址族常量

P F _ L O C A L和A F _ L O C A L是支持同一主机上进程间通信的协议的主要标识,它们 是P O S I X . 1 2标准的一部分。在 N e t / 3以前,用 P F _ U N I X和A F _ U N I X标识这些协议。 在Net/3中保留UNIX常量,用于向后兼容,并且要在本书中讨论。 P F _ U N I X域支持在一个单独的 U n i x主机上的进程间通信。细节见 [Stevens 1990] 。 P F _ R O U T E域支持在一个进程和内核中路由软件间的通信 (第1 8章)。我们偶尔引用 P F _ O S I协 议,它作为 N e t / 3特性仅支持 O S I 协议,但我们不讨论它们的细节。大多数讨论是关于 PF_INET协议的。

7.2 代码介绍 本章涉及两个头文件和两个 C文件。图7-2描述了这4个文件。 文



netinet/domain.h netinet/protosw.h netinet/in_proto.c kern/uipc_domain.c





d o m a i n结构定义 p r o t o s w结构定义 IP d o m a i n和p r o t o s w结构 初始化和查找函数

图7-2 在本章中讨论的文件

147

第 7章 域 和 协 议计计

7.2.1 全局变量 图7-3描述了几个重要的全局数据结构和系统参数,它们在本章中讨论,并经常在 Net/3中 引用。 变



domain inetdomain inetsw max_linkhdr max_protohdr max_hdr max_datalen

数据类型 struct domain * struct domain struct protosw[] int int int int





链接的域列表 Internet协议的d o m a i n结构 Internet协议的p r o t o s w结构数组 见图7-17 见图7-17 见图7-17 见图7-17

图7-3 在本章中介绍的全局变量

7.2.2 统计量 除了图7-4显示的由函数 i p _ i n i t分配和初始化的统计量表,本章讨论的代码没有收集其 他统计量。通过一个内核调试工具是查看这个表的唯一方法。 变



ip_ifmatrix

数据类型 i n t[][]





二维数组,用来统计在任意两个接口间传送的分组数

图7-4 在本章中收集的统计量

7.3 domain结构 一个协议域由一个图 7-5所示的domain结构来表示。

图7-5 结构domain 的定义

42-57

d o m _ f a m i l y是一个地址族常量 (例如A F _ I N E T),它指示在此域中协议使用的编址

方式。dom_name是此域的一个文本名称 (例如“internet”)。

148

计计TCP/IP详解 卷 2:实现

除了程序 f s t a t ( 1 )在它格式化插口信息时使用 d o m _ n a m e外,成员 d o m _ n a m e 不被Net/3内核的任何部分访问。 d o m _ i n i t指向初始化此域的函数。 d o m _ e x t e r n a l i z e和d o m _ d i s p o s e指向那些管 理通过此域内通信路径发送的访问权限的函数。 U n i x域实现这个特性在进程间传递文件描述 符。Internet域不实现访问权限。 d o m _ p r o t o s w和d o m _ p r o t o s w N P R O T O S W指向一个 p r o t o s w结构的数组的起始和结 束。 d o m _ n e x t指向在一个内核支持的域链表中的下一个域。包含所有域的链表通过全局指 针domains来访问。 接下来的三个成员, d o m _ r t a t t a c h、d o m _ r t o f f s e t和d o m _ m a x r t k e y保存此域的 路由信息。它们在第 18章讨论。 图7-6显示了一个 domains列表的例子。

图7-6 domains 列表

7.4 protosw结构 在编译期间,Net/3为内核中的每个协议分配一个protosw结构并初始化,同时将在一个域 中的所有协议的这个结构组织到一个数组中。每个 domain结构引用相应的protosw结构数组。 一个内核可以通过提供多个protosw项为同一协议提供多个接口。Protosw结构的定义见图7-7。

图7-7 protosw 结构的定义

57-61

此结构中的前 4个成员用来标识和表征协议。 p r _ t y p e指示协议的通信语义。图 7 - 8

149

第 7章 域 和 协 议计计

列出了pr_type可能的值和对应的 Internet协议。 pr_type

协 议 语 义

Internet协议

SOCK_STREAM SOCK_DGRAM SOCK_RAW SOCK_RDM SOCK_SEQPACKET

可靠的双向字节流服务 最好的运输层数据报服务 最好的网络层数据报服务 可靠的数据报服务 (未实现) 可靠的双向记录流服务

TCP UDP ICMP,IGMP,原始IP n/a n/a

图7-8 pr_type 指明协议的语义

p r _ d o m a i n指向相关的 d o m a i n结构, p r _ p r o t o c o l为域中协议的编号, p r _ f l a g s 标识协议的附加特征。图 7-9列出了pr_flags的可能值。 说

pr_flags



每个进程请求映射为一个单个的协议请求 协议在每个数据报中都传递地址 协议是面向连接的 当一个进程接收到数据时通知协议 协议支持访问权限

PR_ATOMIC PR_ADDR PR_CONNREQUIRED PR_WANTRCVD PR_RIGHTS

图7-9 pr_flags 的值

如果一个协议支持 P R _ A D D R ,必须也支持 P R _ A T O M I C 。 P R _ A D D R 和 PR_CONNREQUIRED是互斥的。 当设置了 P R _ W A N T R C V D,并当插口层将插口接收缓存中的数据传递给一个进程 时(即当在接收缓存中有更多空间可用时 ),它通知协议层。 P R _ R I G H T S指示访问权限控制报文能通过连接来传递。访问权限要求内核中的 其他支持来确保在接收进程没有销毁报文时能完成正确的清除工作。仅 U n i x域支持 访问权限,在那里它们用来在进程间传递描述符。 图7-10所示的是协议类型、协议标志和协议语义间的关系。 PR_

pr_type ADDR

ATOMIC

SOCK_STREAM

是否有记 录边界

可靠否







显式 隐式 隐式

• •

CONNREQUIRED

SOCK_SEQPACKET



• •

SOCK_RDM





SOCK_DGRAM SOCK_RAW

• •

• •



其他

TCP

SPP TP4 SPP

见正文

隐式 隐式



Internet

RDP UDP ICMP

图7-10 协议特征和举例

图7 - 1 0不包括标志 P R _ W A N T R C V D和P R _ R I G H T S。对于可靠的面向连接的协议, PR_WANTRCVD总是被设置。 为了理解 N e t / 3 中一个 p r o t o s w 项的通信语义,我们必须要一起考虑

P R x x x标志和

p r _ t y p e。在图7 - 1 0中,我们用两列 (“是否有记录边界”和“可靠否” )来描述由 p r _ t y p e

150

计计TCP/IP详解 卷 2:实现

隐式指示的语义。图 7-10显示了可靠协议的三种类型: • 面向连接的字节流协议,如TCP和SPP(源于XNS协议族)。这些协议用SOCK_STREAM标识。 • 有记录边界的面向连接的流协议用 S O C K _ S E Q P A C K E T标识。在这种协议类型中, P R _ A T O M I C指示记录是否由每个输出请求隐式地指定,或者显式地通过在输出中设置 标志MSG_EOR来指定。 SPP同时支持语义SOCK_STREAM和SOCK_SEQPACKET。 • 第三种可靠协议提供一个有隐式记录边界的面向连接服务,它由 S O C K _ R D M标识。R D P 不保证按照记录发送的顺序接收记录。 R D P在[Partridge 1987] 中讨论并在 RFC 1 1 5 [Partridge and Hinden 1990]中被描述。 两种不可靠协议显示在图 7-10中: • 一个运输层数据报协议,如 UDP,它包括复用和检验和,由 SOCK_DGRAM指定。 • 一个网络层数据报协议,如 I C M P,它由 S O C K _ R A W指定。在 N e t / 3中,只有超级用户进 程才能创建一个 SOCK_RAW插口(图15-8)。 62-68

接着的5个成员是函数指针,用来提供从其他协议对此协议的访问。 p r _ i n p u t处理

从一个低层协议输入的数据, p r _ o u t p u t处理从一个高层协议输出的数据, p r _ c t l i n p u t 处理来自下层的控制信息,而 p r _ c t l o u t p u t处理来自上层的控制信息。 p r _ u s r r e q处理 来自进程的所有通信请求。如图 7-11所示。 进程 (例如:read, write,等等)

(例如:路由域输出)

(例如:插口选项) 协 议

(例如:到达TCP分组)

(例如:ICMP错误)

图7-11 一个协议的 5个主要入口点

69-75

剩下的 5个成员是协议的实用函数。 p r _ i n i t 处理初始化。 p r _ f a s t t i m o和

p r _ s l o w t i m o分别每200 ms和500 ms被调用来执行周期性的协议函数,如更新重传定时器。 p r _ d r a i n在内存缺乏时被 m _ r e c l a i m调用 (图2 - 1 3 )。它请求协议释放尽可能多的内存。 p r _ s y s c t l为s y s c t l( 8 )命令提供一个接口,一种修改系统范围的参数的方式,如允许转发 分组或UDP检验和计算。

7.5 IP的domain和protosw结构 声明所有协议的结构 d o m a i n 和p r o t o s w ,并进行静态初始化。对于 I n t e r n e t协议, i n e t s w数组包含 p r o t o s w结构。图7 - 1 2总结了在数组 i n e t s w中的协议信息。图 7 - 1 3显示了

151

第 7章 域 和 协 议计计

Internet协议的数组定义和 domain结构的定义。 inetsw[] 0 1 2 3 4 5 6

pr_protocol

pr_type

0 IPPROTO_UDP IPPROTO_TCP IPPROTO_RAW IPPROTO_ICMP IPPROTO_IGMP 0

0 SOCK_DGRAM SOCK_STREAM SOCK_RAW SOCK_RAW SOCK_RAW SOCK_RAW





Internet协议 用户数据报协议 传输控制协议 Internet协议(原始) Internet控制报文协议 Internet组管理协议 Internet协议(原始、默认 )

图7-12 Internet域协议

图7-13 Internet 的domain 和protosw 结构





IP UDP TCP IP(原始) ICMP IGMP IP(原始)

152

计计TCP/IP详解 卷 2:实现

39-77

在i n e t s w数组中的 3个p r o t o s w结构提供对 I P的访问。第一个: i n e t s w [ 0 ],标

识 I P 的管理函数并且只能由内核访问。其他两项:

i n e t s w [ 3 ] 和i n e t s w [ 6 ] ,除了

p r _ p r o t o c o l值以外它们是一样的,都提供到 I P的一个原始接口。 i n e t s w [ 3 ]处理接收到 的任何未识别协议的分组。 i n e t s w [ 6 ]是默认的原始协议,当没有找到其他可匹配的项时, 这个结构由函数 pffindproto返回(7.6节)。 在N e t / 3以前的版本中,通过 i n e t s w [ 3 ]传输不带 I P首部的分组,由进程负责构 造正确的首部。由内核通过 i n e t s w [ 6 ]传输带 I P首部的分组。 4.3BSD Reno引入了 I P _ H D R I N C L插口选项 (32.8节),这样在 i n e t s w [ 3 ]和i n e t s w [ 6 ]之间的区别就不 再重要了。 原始接口允许一个进程发送和接收不涉及运输层的 I P分组。原始接口的一个用途是实现 内核外的传输协议。一旦这个协议稳定下来,就能移植到内核中改进它的性能和对其他进程 的可用性。另一个用途就是作为诊断工具,如 t r a c e r o u t e,它使用原始 I P接口来直接访问 IP。第32章讨论原始 IP接口。图7-14总结了IP protosw结构。 protosw

inetsw[0]



i n e t s w [ 3和6 ]

pr_type pr_domain pr_protocol

0 &inetdomain 0

SOCK_RAW &inetdomain I P P R O T O _ R A W或0

pr_flags pr_input

0 null

PR_ATOMIC/PR_ADDR rip_input

pr_output

ip_output

rip_output

pr_ctlinput pr_ctloutput pr_usrreq pr_init pr_fasttimo pr_slowtimo pr_drain pr_sysctl

null null null ip_init null ip_slowtimo ip_drain ip_sysctl

null rip_ctloutput rip_usrreq n u l l或r i p _ i n i t null null null null



IP提供原始分组服务 两协议都是Internet域的一部分 IPPROTO_RAW(255)和0都是 预留的(RFC 1700),并且不应在 一个IP数据报中出现 插口层标志, IP不使用 从IP、ICMP或IGMP接收未识 别的数据报 分别准备并发送数据报到 IP和 硬件层 IP不使用 响应来自进程的配置请求 响应来自进程的协议请求 i p _ i n i t完成所有初始化 IP不使用 用于IP重装算法的慢超时 如果可能,释放内存 修改系统范围参数

图7-14 IP inetsw 的条目

78-81 Internet协议的domain结构显示在图7-13的下部。Internet域使用AF_INET风格编址, 文本名称为“ i n t e r n e t”,没有初始化和控制报文函数,它的 p r o t o s w结构在i n e t s w数组 中。 I n t e r n e t协议的路由初始化函数是 r n _ i n i t h e a d。一个 I P地址的最大有效位数为 3 2,并 且一个Internet选路键的大小为一个 sockaddr_in结构的大小 (16字节)。 i n e t s w [ 3 ]和i n e t s w [ 6 ]的唯一区别是它们的 p r _ p r o t o c o l号和初始化函数 rip_init,它仅在inetsw[6]中定义,因此只在初始化期间被调用一次。

153

第 7章 域 和 协 议计计

domaininit函数 在系统初始化期间 (图3 - 2 3 ),内核调用 d o m a i n i n i t来链接结构 d o m a i n和p r o t o s w。 domaininit显示在图7-15中。

图7-15 函数domaininit

37-42 ADDDOMAIN宏声明并链接一个 domain结构。例如,ADDDOMAIN(unix)展开为: extern struct domain unixdomain; unixdomain.dom_next = domains; domains = &unixdomain;

宏_C O N C A T定义在 s y s / d e f s . h 中,并且连接两个符号名。例如 _ C O N C A T (u n i x , d o m a i)产生u n nixdomain。 43-54 domaininit通过调用ADDDOMAIN为每个支持的域构造域列表。 因为符号 u n i x常常被C预处理器预定义,因此, N e t / 3在这里显式地取消它的定 义,使ADDDOMAIN能正确工作。

154

计计TCP/IP详解 卷 2:实现

图7 - 1 6显示了链接的结构 d o m a i n和p r o t o s w,它们配置在内核中来支持 I n t e r n e t、U n i x 和OSI协议族。

流 数据报 原始(默认) (原始)

(原始)

图7-16 初始化后的 domain 链表和protosw 数组

55-61

两个嵌套的 f o r 循环查找内核中的每个域和协议,并且若定义了初始化函数

d o m _ i n i t和p r _ i n i t,则调用它们。对于 I n t e r n e t协议,调用下面的函数 (图7 - 1 3 ): ip_init、udp_init、tcp_init、igmp_init和rip_init。 62-65

在d o m a i n i n i t中计算这些参数,用来控制 mbuf中分组的格式,以避免对数据的额

外复制。在协议初始化期间设置了 m a x _ l i n k h d r和m a x _ p r o t o h d r。这里, d o m a i n i n i t 将m a x _ l i n k h d r强制设置为一个下限 1 6。1 6字节用于给带有 4字节边界的 1 4字节以太网首部 留出空间。图7-17和图7-18列出了这些参数和典型的取值。 变



max_linkhdr max_protohdr max_hdr max_datalen







由链路层添加的最大字节数 由网络和运输层添加的最大字节数 max_linkhdr + max_protohdr 在计算了链路和协议首部后的分组首部 m u b f中的可用数据字节数

16 40 56 44

图7-17 用来减少协议数据复制的参数 max_hdr 字节 m_har和 m_pkthdr

链路首部

28字节 MHLEN

16字节 max_linkhdr

网络和运 输首部 40字节 max_protohdr 128字节

数据 44字节 max_datalen

图7-18 mbuf和相关的最大首部长度

m a x _ p r o t o h d r是一个软限制,估算预期的协议首部大小。在 I n t e r n e t域中,I P 和T C P首部长度通常为 2 0字节,但都可达到 6 0字节。长度超过 m a x _ p r o t o h d r的代 价是花时间将数据向后移动,以留出比预期的协议首部更大的空间。

155

第 7章 域 和 协 议计计

66-68

domaininit通过调用timeout启动pfslowtimo和pffasttimo。第3个参数指

明何时内核应该调用这个函数,在这里是在 1个时钟滴答内。两个函数都显示在图 7-19中。

图7-19 函数pfslowtimo 和pffasttimo

153-176

这两个相近的函数用两个 f o r循环分别为每个协议调用函数 p r _ s l o w t i m o和

p r _ f a s t t i m o,前提是如果定义了这两个函数。这两个函数每 500 ms 和200 ms 通过调用 timeout调度自己一次, timeout在图3-43中讨论过。

7.6 pffindproto和pffindtype函数 如图7 - 2 0所示,函数 p f f i n d p r o t o和p f f i n d t y p e通过编号 (例如I P P R O T O _ T C P)或类 型(例如SOCK_STREAM)来查找一个协议。如我们在第 15章要看到的,当进程创建一个插口时, 这两个函数被调用来查找相应的 protosw项。 69-84

p f f i n d t y p e线性搜索d o m a i n s,查找指定的族,然后在域内搜索第一个为此指定

类型的协议。 85-107 pffindproto像pffindtype一样搜索domains,查找由调用者指定的族、类型 和协议。如果 p f f i n d p r o t o在指定的协议族中没有发现一个 (p r o t o c o l,t y p e)匹配项, 并且 t y p e 为 S O C K _ R A W ,而此域有一个默认的原始协议 ( p r _ p r o t o c o l 等于 0 ) ,则 pffindproto选择默认的原始协议而不是完全失败。例如,一个调用如下: pffindproto(PF_INET, 27, SOCK_RAW);

它返回一个指向 i n e t s w [ 6 ]的指针,默认的原始 I P协议,因为 N e t / 3不包括对协议 2 7的支持。 通过访问原始 I P,一个进程可以使用内核来管理 I P分组的发送和接收,从而自己实现协议 2 7

156

计计TCP/IP详解 卷 2:实现

服务。 协议27预留给可靠的数据报协议 (RFC 1151)。 两个函数都返回一个所选协议的 p r o t o s w结构的指针;或者,如果没有找到匹配项,则 返回一个空指针。

图7-20 函数pffindproto 和pffindtype

举例 我们在15.6节中会看到,当一个应用程序进行下面的调用时: socket(PF_INET, SOCK_STREAM, 0);

pffindtype被调用如下:

/* TCP 插口 */

157

第 7章 域 和 协 议计计

pffindtype(PF_INET, SOCK_STREAM);

图7 - 1 2显示p f f i n d t y p e会返回一个指向 i n e t s w [ 2 ]的指针,因为 T C P是此数组中第一 个SOCK_STREAM协议。同样, socket(PF_INET, SOCK_DGRAM, 0);

/* UCP 插口 */

会导致 pffindtype(PF_INET, SOCK_DGRAM);

它返回一个指向 inetsw[1]中UDP的指针。

7.7 pfctlinput函数 函数p f c t l i n p u t给每个域中的每个协议发送一个控制请求 (图7-21)。当可能影响每个协 议的事件发生时,使用这个函数,例如一个接口被关闭,或路由表发生改变。当一个 I C M P重 定向报文到达时, ICMP调用p f c t l i n p u t (图11-14),因为重定向会影响所有 Internet协议(例 如UDP和TCP)。

图7-21 函数pfctlinput

142-152

两个嵌套的 for循环查找每个域中的每个协议。 pfctlinput通过调用每个协议

的p r _ c t l i n p u t 函 数来发送由c m d 指定的协议控 制命令。对于 U D P,调用 udp_ctlinput;而对于TCP,调用tcp_ctlinput。

7.8 IP初始化 如图7 - 1 3所示, I n t e r n e t域没有一个初始化函数,但单个 I n t e r n e t协议有。现在,我们仅查 看I P初始化函数 i p _ i n i t。在第2 3章和第 2 4章中,我们讨论 U D P和T C P初始化函数。在讨论 这些代码前,需要说明一下数组 ip_protox。 7.8.1 Internet传输分用 一个网络层协议像 I P必须分用输入数据报,并将它们传递到相应的运输层协议。为了完 成这些,相应的 p r o t o s w结构必须通过一个在数据报中出现的协议编号得到。对于 Internet协 议,这由数组ip_protox来完成,如图7-22所示。 数组i p _ p r o t o x的下标是来自 IP首部的协议值(i p _ p,图8-8)。被选项是i n e t s w数组中 处理此数据报的协议的下标。例如,一个协议编号为 6的数据报由 i n e t s w [ 2 ]处理,协议为 TCP。内核在协议初始化时构造 ip_protox,如图7-23所示。

158

计计TCP/IP详解 卷 2:实现

(原始)

(原始)

图7-22 数组ip_protox 将协议编号映射到数组 inetsw 中的一项

图7-23 函数ip_init

7.8.2 ip_init函数 domaininit (图7-15)在系统初始化期间调用函数 ip_init。 71-78 pffindproto返回一个指向原始协议 (inetsw[3],图7-14)的指针。如果找不到原 始协议, N e t / 3就调用 p a n i c,因为这是内核要求的部分。如果找不到原始协议,内核一定被 错误配置了。 I P将传输到一个未知传输协议的到达分组传递给此协议,在那里它们由内核外 部的一个进程来处理。 79-85 接着的两个循环初始化数组 ip_protox。第一个循环将数组中的每项设置为 pr,即 默认协议的下标 (图7-22中为3)。第二个循环检查 i n e t s w中的每个协议(而不是协议编号为 0或 I P P R O T O _ R A W的项),并且将 i p _ p r o t o x中的匹配项设置为引用相应的 i n e t s w项。为此, 每个protosw结构中的pr_protocol必须是期望出现在输入数据报中的协议编号。

159

第 7章 域 和 协 议计计

86-92

ip_init初始化IP重装队列ipq(10.6节),用系统时钟植入 ip_id,并将IP输入队列

(i p i n t r q)的最大长度设置为 5 0 (i p q m a x l e n)。i p _ i d用系统时钟设置,为数据报标识符提 供一个随机起点 (10.6节)。最后, i p _ i n i t分配一个两维数组 i p _ i f m a t r i x,统计在系统接 口之间路由的分组数。 在N e t / 3中,有很多变量可以被一个系统管理员修改。为了允许在运行时改变这 些变量而不需重新编译内核,一个常量 (在此例中是 I F Q _ M A X L E N)表示的默认值在编 译时指派给一个变量 (i p q m a x l e n)。一个系统管理员能使用一个内核调试器如 a d b, 来修改i p q m a x l e n,并用新值重启内核。如果图 7 - 2 3直接使用 I F Q _ M A X L E N,它会 要求内核重新编译来改变这个限制。

7.9 sysctl系统调用 系统调用sysctl访问并修改 Net/3系统范围参数。系统管理员通过程序 sysctl(8)修改这 些参数。每个参数由一个分层的整数列表来标识,并有一个相应的类型。此系统调用的原型为: int sysctl(int * name, u_int namelen, void *old, size_t * oldlenp, void *new, size_t newlen);

*n a m e指向一个包含 n a m e l e n个整数的数组。 *o l d指向在此范围内返回的旧值, *n e w指向 在此范围内传递的新值。 图7-24总结了关于联网名称的组织。

图7-24 sysctl 的名称组织

在图7-24中,IP转发标志的全名为 CTL_NET、PF_INET、0、IPCTL_FORWARDING

160

计计TCP/IP详解 卷 2:实现

用4个整数存储在一个数组中。 net_sysctl函数 每层的sysctl命名方案通过不同函数处理。图 7-25显示了处理这些 Internet参数的函数。

图7-25 处理Internet 参数的sysctl 函数

顶层名称由 s y s c t l处理。网络层名称由 n e t _ s y s c t l处理,它根据族和协议将控制转 给此协议的 protosw项指定的pr_sysctl函数。 s y s c t l在内核中通过 _ s y s c t l函数实现,函数 _ s y s c t l不在本书中讨论。它 包含将s y s c t l参数传给内核和从内核取出 s y s c t l参数的代码及一个 s w i t c h语句, 这个switch语句选择相应的函数来处理这些参数,在这里是 net_sysctl。 图7-26所示的是函数net_sysctl。

图7-26 函数net_sysctl

161

第 7章 域 和 协 议计计

图7-26 (续)

108-119

n e t _ s y s c t l的参数除了增加了 p外,同系统调用 s y s c t l一样,p指向当前进程

结构。 120-134

在名称中接下来的两个整数被认为是在结构 d o m a i n和p r o t o s w中指定的协议族

和协议编号成员的值。如果没有指定族,则返回 0。如果指定了族, f o r循环在域列表中查找 一个匹配的族。如果没有找到,则返回 ENOPROTOOPT。 135-141 如果找到匹配域,第二个for循环查找第一个定义了函数 pr_sysctl的匹配协议。 当找到匹配项,将请求传递给此协议的 p r _ s y s c t l函数。注意,把 (n a m e+ 2 )指向的整数传 递给下一级。如果没有找到匹配的协议,返回 ENOPROTOOPT。 图7-27所示的是为 Internet协议定义的 pr_sysctl函数。 pr_protocol

inetsw[]

pr_sysctl

0 IPPROTO_UDP IPPROTO_ICMP

0 1 4

ip_sysctl udp_sysctl icmp_sysctl





IP UDP ICMP





8.9节 23.11节 11.14节

图7-27 Internet协议族的 pr_sysctl 函数

在路由选择域中, pr_sysctl指向函数sysctl_rtable,它在第19章中讨论。

7.10 小结 本章从说明结构 d o m a i n和p r o t o s w开始,这两个结构在 N e t / 3内核中描述及组织协议。 我们看到一个域的所有 p r o t o s w结构在编译时分配在一个数组中, i n e t d o m a i n和数组 i n e t s w描述Internet协议。我们仔细查看了三个描述 IP协议的i n e t s w项:一个用于内核访问 IP,其他两个用于进程访问 IP。 在系统初始化时, d o m a i n i n t将域链接到 d o m a i n s列表中,调用域和协议初始化函数, 并调用快速和慢速超时函数。 两个函数 p f f i n d p r o t o 和 p f f i n d t y p e 通过协议号或类型搜索域和协议列表。 pfctlinput发送一个控制命令给所有协议。 最后,我们说明了 IP初始化程序,它通过数组 ip_protox完成传输分用。

习题 7.1 由谁调用pfsfindproto会返回一个指向 inetsw[6]指针?

第8章 IP:网际协议 8.1 引言 本章我们介绍 I P分组的结构和基本的 I P处理过程,包括输入、转发和输出。假定读者熟 悉IP协议的基本操作,其他 IP的背景知识见卷 1的第3、9和12章。RFC 791 [Postel 1981a] 是IP 的官方规范,RFC 1122 [Braden 1989a] 中有RFC 791的说明。 第9章将讨论选项的处理,第 10章讨论分片和重装。图 8-1显示了IP层常见的组织形式。 运输协议

网络接口

网络

图8-1 IP层的处理

在第4章中,我们看到网络接口如何把到达的 I P分组放到 I P输入队列 i p i n t r q中去,并如 何调用一个软件中断。因为硬件中断的优先级比软件中断的要高,所以在发生一次软件中断 之前,有的分组可能会被放到队列中。在软件中断处理中, i p i n t r函数不断从 i p i n t r q中 移走和处理分组,直到队列为空。在最终的目的地, I P把分组重装为数据报,并通过函数调用 把该数据报直接传给适当的运输层协议。如果分组没有到达最后的目的地,并且如果主机被 配置成一个路由器,则 I P把分组传给 i p _ f o r w a r d。传输协议和 i p _ f o r w a r d把要输出的分 组传给 i p _ o u t p u t,由i p _ o u t p u t完成I P首部、选择输出接口以及在必要时对分组分片。 最终的分组被传给合适的网络接口输出函数。 当产生差错时, I P丢弃该分组,并在某些条件下向分组的源站发出一个差错报文。这些 报文是 I C M P (第11章)的一部分。 N e t / 3通过调用 i c m p _ e r r o r发出I C M P差错报文, i c m p _ e r r o r接收一个 m b u f,其中包含差错分组、发现的差错类型以及一个选项码,提供依赖于差 错类型的附加信息。

163

第 8章 IP:网际协议 计计

本章我们讨论 I P为什么以及何时发送 I C M P报文,至于有关 I C M P本身的详细讨论将在第 11章进行。

8.2 代码介绍 本章讨论两个头文件和三个 C文件。如图 8-2所示。 文







net/route.h 路由入口 netinet/ip.h IP首部结构 n e t i n e t / i p _ i n p u t . c IP输入处理 n e t i n e t / i p _ o u t p u t . c IP输出处理 n e t i n e t / i p _ c k s u m . c Internet检验和算法

图8-2 本章描述的文件

8.2.1 全局变量 在IP处理代码中出现了几个全局变量,见图 8-3。 变



in_ifaddr ip_defttl ip_id ip_protox ipforwarding ipforward_rt ipintrq ipqmaxlen ipsendredirects ipstat

数据类型 struct int int int[] int struct struct int int struct





i n _ i f a d d r * IP地址清单 IP分组的默认 TTL 赋给输出的 IP分组的上一个 ID IP分组的分路矩阵 系统是否转发 IP分组? route 大多数最近转发的路由的缓存 ifqueue IP输入队列 IP输入队列的最大长度 系统是否发送 ICMP重定向? ipstat IP统计

图8-3 本章中引入的全局变量

8.2.2 统计量 I P收集的所有统计量都放在图 8 - 4描述的 i p s t a t结构中。图 8 - 5显示了由 n e t s t a t - s命 令得到的一些统计输出样本。统计是在主机启动 30天后收集的。 ipsta t成员 ips_badhlen ips_badlen ips_badoptions ips_badsum ips_badvers ips_cantforward ips_delivered





IP首部长度无效的分组数 IP首部和IP数据长度不一致的分组数 在选项处理中发现差错的分组数 检验和差错的分组数 IP版本不是4的分组数 目的站不可到达的分组数 向高层交付的数据报数

图8-4 本章收集的统计

SNMP使用的 • • • • • • •

164

计计TCP/IP详解 卷 2:实现



ipsta t成员



SNMP使用的

ips_forward ips_fragdropped ips_fragments ips_fragtimeout ips_noproto ips_reassembled ips_tooshort ips_toosmall ips_total

转发的分组数 分片丢失数 (副本或空间不足 ) 收到分片数 超时的分片数 具有未知或不支持的协议的分组数 重装的数据报数 具有无效数据长度的分组数 无法包含IP分组的太小的分组数 全部接收到的分组数

• • • • • • • • •

ips_cantfrag ips_fragmented ips_localout ips_noroute ips_odropped ips_ofragments ips_rawout ips_redirectsent

由于不分片比特而丢弃的分组数 成功分片的数据报数 系统生成的数据报数 (即没有转发的 ) 丢弃的分组数 —到目的地没有路由 由于资源不足丢掉的分组数 为输出产生的分片数 全部生成的原始 ip分组数 已发送的重定向报文数

• • • • • •

图8-4 (续) 输出

成员

图8-5 IP统计样本

i p s _ n o p r o t o的值很高,因为当没有进程准备接收报文时,它能对 I C M P主机 不可达报文进行计数。见第 32.5节的详细讨论。 8.2.3 SNMP变量 图8-6显示了IP组和Net/3收集的统计中的 SNMP变量之间的关系。

165

第 8章 IP:网际协议 计计

SNMP变量



I p s t a t成员



ipDefaultTTL ipForwarding ipReasmTimeout

ip_defttl ipforwarding IPFRAGTTL

数据报的默认 TTL(64跳) 系统是路由器吗? 分片的重装超时 (30秒)

ipInReceives

ips_total

收到的全部 IP分组数

ipInHdrErrors

ips_badsum+ ips_tooshort+ ips_toosmall+ ips_badhlen+ ips_badlen+ ips_badoptions+ ips_badvers

IP首部出错的分组数

ipInAddrErrors ipForwDatagrams ipReasmReqds ipReasmFails

由于错误交付而丢弃的 IP分组数(i p _ o u t p u t也失败) 转发的IP分组数 收到的分片数 丢失的分片数

ipReasmOKs ipInDiscards ipInUnknownProtos ipInDelivers

ips_cantforward ips_forward ips_fragments ips_fragdropped+ ips_fragtimeout ips_reassembled (未实现) ips_noproto ips_delivered

成功地重装的数据报数 由于资源限制而丢弃的数据报数 具有未知或不支持的协议的数据报数 交付到运输层的数据报数

ipOutRequests ipFragOKs iPFragFails ipFragCreates ipOutDiscards ipOutRoutes

ips_localout ips_fragmented ips_cantfrag ips_ofragments ips_odropped ips_noroute

由运输层产生的数据报数 分片成功的数据报数 由于不分片比特丢弃的 IP分组数 为输出产生的分片数 由于资源短缺丢失的 IP分组数 由于没有路由丢弃的 IP分组数

图8-6 IP组中SNMP变量的例子

8.3 IP分组 为了更准确地讨论 I n t e r n e t协议处理,我们必须定义一些名词。图 8 - 7显示了在不同的 Internet层之间传递数据时用来描述数据的名词。 我们把传输协议交给 I P的数据称为报文。典型的报文包含一个运输层首部和应用程序数 据。图 8 - 7所示的传输协议是 U D P。I P在报文的首部前加上它自己的首部形成一个数据报。如 果在选定的网络中,数据报的长度太大, I P就把数据报分裂成几个分片,每个分片中含有它 自己的IP首部和一段原来数据报的数据。图 8-7显示了一个数据报被分成三个分片。 当提交给数据链路层进行传送时,一个 I P分片或一个很小的无需分片的 I P数据报称为分 组。数据链路层在分组前面加上它自己的首部,并发送得到的帧。 I P只考虑它自己加上的 I P首部,对报文本身既不检查也不修改 (除非进行分片 )。图8 - 8显 示了IP首部的结构。 图8-8包括ip结构(如图8-9)中各成员的名字, Net/3通过该结构访问 IP首部。 47-67

因为在存储器中,比特字段的物理顺序依机器和编译器的不同而不同,所以由 # i f s

保证编译器按照 I P标准排列结构成员。从而,当 N e t / 3把一个i p结构覆盖到存储器中的一个 I P

166

计计TCP/IP详解 卷 2:实现

分组上时,结构成员能够访问到分组中正确的比特。 报文 IP

UDP

应用程序数据 数据报

分片1 IP

UDP

应用程序 数据

IP

分片2

分组 链路层 IP

UDP 应用程序 数据

链路层 IP

应用程序 数据

IP

应用程序数据

分片3

应用程序数据

应用程序 数据

链路层 IP



图8-7 帧、分组、分片、数据报和报文 0

15 16

版本 ip_v

首部长度 ip_hl

服务类型 ip_tos

全长 ip_len

标识符生存时间 ip_id 生存时间 ip_ttl

31

标志和分片偏移 ip_off 协议 ip_p

首部检验和 ip_cksum 32位源IP地址 ip_src 32位目的IP地址 ip_dst 选项(如果有)

数据

图8-8 IP数据报,包括 ip 结构名

图8-9 ip 结构

20字节

167

第 8章 IP:网际协议 计计

图8-9 (续)

IP首部中包含 IP分组格式、内容、寻址、路由选择以及分片的信息。 I P分组的格式由版本 i p _ v指定,通常为 4;首部长度 i p _ h l,通常以 4字节单元度量;分 组长度 i p _ l e n以字节为单位度量;传输协议 i p _ p生成分组内数据; i p _ s u m是检验和,检 测在发送中首部的变化。 标准的 I P首部长度是 2 0个字节,所以 i p _ h l必须大于或等于 5。大于 5表示I P选项紧跟在 标准首部后。如 i p _ h l的最大值为 15 (2 4-1 ),允许最多 4 0个字节的选项 ( 2 0 + 4 0 = 6 0 )。I P数据 报的最大长度为 65535 (2 16-1)字节,因为 ip_len是一个16 bit 的字段。图8-10是整个构成。 (ip_hl×4)字节 标准首部 (20字节)

选项 (最大40字节)

数据 ip_len字节 (最大65535字节)

图8-10 有选项的 IP分组构成

因为ip_hl是以4字节为单元计算的,所以 IP选项必须常常被填充成 4字节的倍数。

8.4 输入处理:ipintr函数 在第3、第4和第5章中,我们描述了示例的网络接口如何对到达的数据报排队以等待协议 处理: 1) 以太网接口用以太网首部中的类型字段分路到达帧 (见4.3节); 2) SLIP接口只处理 IP分组,所以无需分用 (见5.3节); 3) 环回接口在l o o u t p u t函数中结合输入和输出处理,用目的地址中的 s a _ f a m i l y成员 对数据报分用(见5.4节)。 在以上情况中,当接口把分组放到 i p i n t r q上排队后,通过 s c h e d n e t i s r调用一个软 中断。当该软中断发生时,如果 IP处理过程已经由 schednetisr调度,则内核调用ipintr。 在调用ipintr之前,CPU的优先级被改变成 splnet。 8.4.1 ipintr概观 i p i n t r是一个大函数,我们将在 4个部分中讨论: (1)对到达分组验证; (2)选项处理及转 发;( 3 )分组重装; ( 4 )分用。在 i p i n t r中发生分组的重装,但比较复杂,我们将单独放在第

168

计计TCP/IP详解 卷 2:实现

10章中讨论。图8-11显示了ipintr的整体结构。

图8-11 ipintr 函数

100-117

标号next标识主要的分组处理循环的开始。 ipintr从ipintrq中移走分组,并

对之加以处理直到整个队列空为止。如果到函数最后控制失败, g o t o把控制权传回给 n e x t 中最上面的函数。 i p i n t r把分组阻塞在 s p l i m p内,避免当它访问队列时,运行网络的中断 程序(例如slinput和e t h e r _ i n p u)。 t 332-336

标号b a d标识由于释放相关 m b u f并返回到 n e x t中处理循环的开始而自动丢弃的

分组。在整个ipintr中,都是跳到bad来处理差错。 8.4.2 验证 我们从图 8 - 1 2开始:把分组从 i p i n t r q中取出,验证它们的内容。损坏和有差错的分组 被自动丢弃。

图8-12 ipintr 函数

169

第 8章 IP:网际协议 计计

图8-12 (续)

1. IP 版本 118-134

如果i n _ i f a d d r表(见第6.5节)为空,则该网络接口没有指派 IP地址,i p i n t r必

须丢掉所有的 IP分组;没有地址, i p i n t r就无法决定该分组是否要到该系统。通常这是一种

170

计计TCP/IP详解 卷 2:实现

暂时情况,是当系统启动时,接口正在运行但还没有配置好时发生的。我们在 6 . 3节中介绍了 地址是如何分配的问题。 在i p i n t r访问任何 IP首部字段之前,它必须证实ip_v是4(I P V E R S I O N)。RFC 1122要求 某种实现丢弃那些具有无法识别版本号的分组而不回显信息。 Net/2不检查 i p _ v。目前大多数正在使用的 IP实现,包括 Net/2,都是在IP的版本 4之后产生的,因此无需区分不同 I P版本的分组。因为目前正在对 I P进行修正,所以 不久将来的实现都将检查 ip_v。 IEN 119 [Forgie 1979] 和RFC 1190 [Topolcic 1990] 描述了使用 IP版本5和6的实 验协议。版本 6还被选为下一个正式的 I P标准( I P v 6 )。保留版本0和1 5,其他的没有赋 值。 在C中,处理位于一个无类型存储区域中数据的最简单的方法是:在该存储区域上覆盖一 个结构,转而处理该结构中的各个成员,而不再对原始的字节进行操作。如第 2章所言, mbuf 链把一个字节的逻辑序列,例如一个 I P分组,储存在多个物理 m b u f中,各 m b u f相互连接在一 个链表上。因为覆盖技术也可用于 I P分组的首部,所以首部必须驻留在一段连续的存储区内 (也就是说,不能把首部分开放在不同的存储器缓存区 )。 135-146

下面的步骤保证 IP首部(包括选项)位于一段连续的存储器缓存区上:

• 如果在第一个 m b u f中的数据小于一个标准的 I P首部( 2 0字节),m _ p u l l u p会重新把该标 准首部放到一个连续的存储器缓存区上去。 链路层不太可能会把最大的 ( 6 0字节 ) IP 首部分在两个 m b u f中从而使用上面的 m_pullup。 • ip_hl通过乘以4得到首部字节长度,并将其保存在 hlen中。 • 如果IP分组首部的字节数长度 hlen小于标准首部(20字节),将是无效的并被丢弃。 • 如果整个首部仍然不在第一个 m b u f中(也就是说,分组包含了 I P选项),则由m _ p u l l u p 完成其任务。 同样,这不一定是必需的。 检验和计算是所有 I n t e r n e t协议的重要组成。所有的协议均使用相同的算法

( 由函数

i n _ c k s u m完成),但应用于分组的不同部分。对 I P来说,检验和只保证 I P的首部 (以及选项, 如果有的话 )。对传输协议,如 UDP或TCP,检验和覆盖了分组的数据部分和运输层首部。 2. IP 检验和 147-150

ipintr把由in_cksum计算出来的检验和保存首部的 ip_sum字段中。一个未被

破坏的首部应该具有 0检验和。 正如我们将在 8 . 7节中看到的,在计算到达分组的检验和之前,必须对 i p _ s u m 清零。通过把 i n _ c k s u m中的结果储存在 i p _ s u m中,就为分组转发作好了准备 (尽 管还没有减小 TTL)。i p _ o u t p u t函数不依赖这项操作;它为转发的分组重新计算检 验和。 如果结果非零,则该分组被自动丢弃。我们将在 8.7节中详细讨论in_cksum。 3. 字节顺序

171

第 8章 IP:网际协议 计计

151-160

Internet标准在指定协议首部中多字节整数值的字节顺序时非常小心。 N T O H S把

IP首部中所有 16 bit的值从网络字节序转换成主机字节序:分组长度 (ip_len)、数据报标识符 (i p _ i d)和分片偏移 (i p _ o f f)。如果两种格式相同,则 N T O H S是一个空的宏。在这里就转换 成主机字节序,以避免 Net/3每次检查该字段时必须进行一次转换。 4. 分组长度 161-177

如果分组的逻辑长度 (i p _ l e n)比储存在 m b u f中的数据量 (m _ p k t h d r . l e n)大,

并且有些字节被丢失了,就必须丢弃该分组。如果 mbuf比分组大,则去掉多余的字节。 丢失字节的一个常见原因是因为数据到达某个没有或只有很少缓存的串口设备, 例如许多个人计算机。设备丢弃到达的字节,而 IP丢弃最后的分组。 多余的字节可能产生,如在某个以太网设备上,当一个 IP分组的大小比以太网要 求的最小长度还小时。发送该帧时加上的多余字节就在这里被丢掉。这就是为什么 IP分组的长度被保存在首部的原因之一; IP允许链路层填充分组。 现在,有了完整的 I P首部,分组的逻辑长度和物理长度相同,检验和表明分组的首部无 损地到达。 8.4.3 转发或不转发 图8 - 1 3显示了 i p i n t r的下一部分,调用 i p _ d o o p t i o n s(见第9章)来处理I P选项,然后 决定分组是否到达它最后的目的地。如果分组没有到达最后目的地, N e t / 3会尝试转发该分组 (如果系统被配置成路由器 )。如果分组到达最后目的地,就被交付给合适的运输层协议。

图8-13 续ipintr

172

计计TCP/IP详解 卷 2:实现

图8-13 (续)

1. 选项处理 178-186

通过对i p _ n h o p s(见9 . 6节)清零,丢掉前一个分组的原路由。如果分组首部大于

默认首部,它必然包含由 i p _ d o o p t i o n s 处理的选项。如果 i p _ d o o p t i o n s 返回 0, i p i n t r将继续处理该分组;否则, i p _ d o o p t i o n s通过转发或丢弃分组完成对该分组的处 理,ipintr可以处理输入队列中的下一个分组。我们把对选项的进一步讨论放到第 9章进行。 处理完选项后, i p i n t r通过把IP首部内的 i p _ d s t与配置的所有本地接口的 IP地址比较, 以决定分组是否已到达最终目的地。 i p i n t r必须考虑与接口相关的几个广播地址、一个或多 个单播地址以及任意个多播地址。 2. 最终目的地 187-261

ipintr通过遍历 in_ifaddr(图6-5),配置好的 Internet地址表,来决定是否有与

分组的目的地址的匹配。对清单中的每个 i n _ i f a d d r结构进行一系列的比较。要考虑 4种常 见的情况: • 与某个接口地址的完全匹配 (图8-14中的第一行 ), • 与某个与接收接口相关的广播地址的匹配 (图8-14的中间4行), • 与某个与接收接口相关的多播组之一的匹配 (图12-39),或 • 与两个受限的广播地址之一的匹配 (图8-14的最后一行 )。

173

第 8章 IP:网际协议 计计

图8 - 1 4显示的是当分组到达我们的示例网络里的主机 s u n上的以太网接口时要测试的地 址,将在第 12章中讨论的多播地址除外。 变量

以太网

SLIP

环回

线路 (图8.13)

图8-14 为判定分组是否到达最终目的地进行的比较

对i a _ s u b n e t、i a _ n e t和I N A D D R _ A N Y的测试不是必需的,因为它们表示的 是4 . 2 B S D使用的已经过时的广播地址。但不幸的是,许多 T C P / I P实现是从 4 . 2 B S D派 生而来的,所以,在某些网络中能够识别出这些旧广播地址可能十分重要。 3. 转发 262-271

如果ip_dst与所有地址都不匹配,分组还没有到达最终目的地。如果还没有设置

ipforwarding,就丢弃分组。否则,ip_forward尝试把分组路由到它的最终目的地。 当分组到达的某个地址不是目的地址指定的接口时,主机会丢掉该分组。在这 种情况下, N e t / 3将搜索整个 i n _ i f a d d r表;只考虑那些分配给接收接口的地址。 RFC 1122 称此为强端系统 (strong end system)模型。 对多主主机而言,很少出现分组到达接口地址与其目的地址不对应的情况,除 非配置了特定的主机路由。主机路由强迫相邻的路由器把多主主机作为分组的下一 跳路由器。弱端系统 (weak end system)模型要求该主机接收这些分组。实现人员可以 随意选择两种模型。 Net/3实现弱端系统模型。 8.4.4 重装和分用 最后,我们来看 i p i n t r的最后一部分 (图8 - 1 5 ),在这里进行重装和分用。我们略去了重 装的代码,推迟到第 10章讨论。当无法重装完全的数据报时,略去的代码将把指针 ip设成空。 否则,ip指向一个已经到达目的地的完整数据报。 运输分用 325-332

数据报中指定的协议被 i p _ p用i p _ p r o t o x数组(图7-22)映射到i n e t s w数组的下

174

计计TCP/IP详解 卷 2:实现

标。 i p i n t r调用选定的 p r o t o s w结构中的 p r _ i n p u t函数来处理数据报包含的运输报文。 当pr_input返回时,ipintr继续处理ipintrq中的下一个分组。 注意,运输层对分组的处理发生在 i p i n t r处理循环的内部。在 I P和传输协议之间没有到 达分组的排队,这与 TCP/IP中SVR4流实现的排队不同。

图8-15 续ipintr

8.5 转发:ip_forward函数 到达非最终目的地系统的分组需要被转发。只有当 i p f o r w a r d i n g非零( 6 . 1节)或当分组 中包含源路由 ( 9 . 6节)时,i p i n t r才调用实现转发算法的 i p _ f o r w a r d函数。当分组中包含 源路由时, ip_dooptions调用ip_forward,并且第2个参数srcrt设为1。 ip_forward通过图8-16中显示的 route结构与路由表接口。

图8-16 route 结构

46-49

r o u t e 结构有两个成员: r o _ r t ,指向 r t e n t r y 结构的指针; r o _ d s t ,一个

s o c k a d d r结构,指定与 r o _ r t所指的路由项相关的目的地。目的地是在内核的路由表中用 来查找路由信息的关键字。第 18章对rtentry结构和路由表有详细的描述。 我们分两部分讨论ip_forward。第一部分确定允许系统转发分组,修改 IP首部,并为分 组选择路由。第二部分处理ICMP重定向报文,并把分组交给ip_output进行发送。见图8-17。 1. 分组适合转发吗 867-871

i p _ f r o w a r d的第1个参数是指向一个 m b u f链的指针,该 m b u f中包含了要被转发

的分组。如果第 2个参数srcrt为非零,则分组由于源路由选项 (见9.6节)正在被转发。 879-884 if语句识别并丢弃以下分组。 • 链路层广播 任何支持广播的网络接口驱动器必须为收到的广播分组把 M _ B C A S T标志置位。如果分组 寻址是到以太网广播地址,则 e t h e r _ i n p u t(图4-13)就把M _ B C A S T置位。不转发链路层的广 播分组。

175

第 8章 IP:网际协议 计计

RFC 11 2 2不允许以链路层广播的方式发送一个寻址到单播 I P地址的分组,并在 这里将该分组丢掉。 • 环回分组 对寻址到环回网络的分组, i n _ c a n f o r w a r d返回0。这些分组将被 i p i n t r提交给 i p _ forward,因为没有正确配置反馈接口。 • 网络0和E类地址 对这些分组,in_canforward返回0。 这些目的地址是无效的,而且因为没有主机接收 这些分组,所以它们不应该继续在网络中流动。 • D类地址 寻址到 D 类地址的分组应该由多播函数 i p _ m f o r w a r d而不是由 i p _ f o r w a r d处理。 in_canforward拒绝D类(多播)地址。 RFC 791 规定处理分组的所有系统都必须把生存时间 ( T T L )字段至少减去 1,即使 T T L是 以秒计算的。由于这个要求, T T L通常被认为是对 I P分组在被丢掉之前能经过的跳的个数的 界限。从技术角度说,如果路由器持有分组超过 1秒,就必须把ip_ttl减去多于1。

图8-17 ip_forward 函数:路由选择

176

计计TCP/IP详解 卷 2:实现

图8-17 (续)

这就产生了一个问题:在 I n t e r n e t上,最长的路径有多长?这个度量称为网络的 直径( d i a m e t e r )。除了通过实验外无法知道直径的大小。 [Olivier 1994]中有3 7跳的路 径。 2. 减小TTL 885-890

由于转发时不再需要分组的标识符,所以标识符又被转换回网络字节序。但是当

i p _ f o r w a r d 发送包含无效 I P首部的 I C M P差错报文时,分组的标识符又应该是正确的顺 序。 N e t / 3漏做了对已被 i p i n t r转换成主机字节序的 i p _ l e n的转换。作者注意到在 大头机器上,这不会产生问题,因为从未对字节进行过转换。但在小头机器如 386上, 这个小的漏洞允许交换了字节的值在 I C M P差错中的 I P首部中。返回从运行在 3 8 6上 的S V R 4 (可能是N e t / 1码)和AIX3.2(4.3BSD Reno码)返回的I C M P分组中可以观察到这 个小的漏洞。 如果i p _ t t l达到1 (I P T T L D E C),则向发送方返回一个 I C M P超时报文,并丢掉该分组。 否则,ip_forward把ip_ttl减去IPTTLDEC。 系统不接受 T T L为0的I P数据报,但 N e t / 3在即使出现这种情况时也能生成正确的 I C M P差 错,因为p_ttl是在分组被认为是在本地交付之后和被转发之前检测的。 3. 定位下一跳 891-907

IP转发算法把最近的路由缓存在全局 route结构的ipforward_rt中,在可能时

应用于当前分组。研究表明连续分组趋向于同一目的地址 ( [ J a i n和Routhier 1986] 和[ M o g u l 1 9 9 1 ] ) ,所以这种向后一个 ( o n e - b e h i n d )的缓存使路由查询的次数最少。如果缓存为空 (i p f o r w a r d _ r t)或当前分组的目的地不是 i p f o r w a r d _ r t中的路由,就取消前面的路由, r o _ d s t被初始化成新的目的地, r t a l l o c为当前分组的目的地找一个新路由。如果找不到 路由,则返回一个 ICMP主机不可达差错,并丢掉该分组。 908-914

由于在产生差错时, i p _ o u t p u t要丢掉分组,所以 m _ c o p y复制分组的前64个字

节,以便ip_forward发送ICMP差错报文。如果调用m_copy失败,ip_forward并不终止。 在这种情况下,不发送差错报文。 i p _ i f m a t r i x记录在接口之间进行路由选择的分组的个 数。具有接收和发送接口索引的计数器是递增的。

177

第 8章 IP:网际协议 计计

重定向报文 当主机错误地选择某个路由器作为分组的第一跳路由器时,该路由器向源主机返回一个 I C M P重定向报文。 I P网络互连模型假定主机相对地并不知道整个互联网的拓扑结构,把维护 正确路由选择的责任交给路由器。路由器发出重定向报文是向主机表明它为分组选择了一个 不正确的路由。我们用图 8-18说明重定向报文。

重新选路

默认网络

目的网络

图8-18 路由器R1重定向主机 HS使用路由器 R2到达HD

通常,管理员对主机的配置是:把到远程网络的分组发送到某个默认路由器上。在图 8-18 中,主机 H S上R 1被配置成它的默认路由器。当 H S首次向H D发送分组时,它不知道 R 2是合适 的选择,而把分组发给 R 1。R 1识别出差错,就把分组转发给 R 2,并向 H S发回一个重定向报 文。接收到重定向报文后, HS更新它的路由表,下一次发往 HD的分组就直接发给 R2。 RFC 1122推荐只有路由器才发重定向报文,而主机在接收到 ICMP重定向报文后必须更新 它们的路由表 ( 11 . 8节)。因为 N e t / 3只在系统被配置成路由器时才调用 i p _ f o r w a r d,所以 Net/3采用RFC 1122的推荐。 在图8-19中,ip_forward决定是否发重定向报文。 1. 在接收接口上离开吗 915-929

路由器识别重定向情况的规则很复杂。首先,只有在同一接口

( r t _ i f p和

r c v i f)上接收或重发分组时,才能应用重定向。其次,被选择的路由本身必须没有被 I C M P 重定向报文创建或修改过 (R T F _ D Y N A M I C | R T F _ M O D I F I E D),而且该路由也不能是到默认目 的地的(0.0.0.0)。这就保证系统在末授权时不会生成路由选择信息,并且不与其他系统共享自 己的默认路由。 通常,路由选择协议使用特殊目的地址 0.0.0.0定位默认路由。当到某目的地的某 个路由不能使用时,与目的地 0.0.0.0相关的路由就把分组定向到一个默认路由器上。

178

计计TCP/IP详解 卷 2:实现

第18章对默认路由有详细的讨论。 全局整数 i p s e n d r e d i r e c t s 指定系统是否被授权发送重定向

(第8.9节),

i p s e n d r e d i r e c t s的默认值为 1。当传给 i p _ f o r w a r d的参数s r c r t指明系统是对分组路 由选择的源时,禁止系统重定向,因为假定源主机要覆盖中间路由器的选择。

图8-19 ip_forward (续)

2. 发送重定向吗 930-931

这个测试决定分组是否产生于本地子网。如果源地址的子网掩码位和输出接口的

地址相同,则两个地址位于同一 I P网络中。如果源接口和输出的接口位于同一网络中,则该 系统就不应该接收这个分组,因为源站可能已经把分组发给正确的第一跳路由器了。 I C M P重 定向报文告诉主机正确的第一跳目的地。如果分组产生于其他子网,则前一系统是个路由器, 这个系统就不应该发重定向报文;差错由路由选择协议纠正。 在任何情况下,都要求路由器忽略重定向报文。尽管如此,当 i p f o r w a r d i n g 被置位时(也就是说,当它被配置成路由器时 ),Net/3并不丢掉重定向报文。 3. 选择合适的路由器 932-940

ICMP重定向报文中包含正确的下一个系统的地址,如果目的主机不在直接相连的

网络上时,该地址是一个路由器的地址;当目的主机在直接相连的网络中时,该地址是主机 地址。 RFC 792 描述了重定向报文的 4种类型: ( 1 )网络; ( 2 )主机; ( 3 ) TO S和网络; ( 4 ) TO S和主 机。RFC 1009推荐在任何时候都不发送网络重定向报文,因为无法保证接收到重定向报文的 主机能为目的网络找到合适的子网掩码。 RFC 1122推荐主机把网络重定向看作是主机重定向,

179

第 8章 IP:网际协议 计计

以避免二义性。 N e t / 3只发送主机重定向报文,并省略所有对 TO S 的考虑。在图 8 - 2 0 中, ipintr把分组和所有的 ICMP报文提交给链路层。

图8-20 ip_forward(续)

重定向报文的标准化是在子网化之前,在一个非子网化的互联网中,网络重定 向很有用,但在一个子网化的互联网中,由于重定向报文中没有有关子网掩码的信 息,所以容易产生二义性。

180

计计TCP/IP详解 卷 2:实现

4. 转发分组 现在, i p _ f o r w a r d有一个路由,并决定是否需要 I C M P重定向报文。 I p _

941-954

o u t p u t把分组发送到路由 i p f o r w a r d _ r t所指定的下一跳。 I P _ A L L O W B R O A D C A S T标志位 允许被转发分组是个到某局域网的广播。如果 i p _ o u t p u t成功,并且不需要发送任何重定向 报文,则丢掉分组的前 64字节,ip_forward返回。 5. 发送ICMP差错报文? 955-983

i p _ f o r w a r d可能会由于 i p _ o u t p u t失败或重定向而发送 ICMP报文。如果没有

原始分组的复制 (可能当要复制时,曾经缓存不足 ),则无法发送重定向报文, i p _ f o r w a r d 返回。如果有重定向, t y p e和c o d e以前又被置位,但如果 i p _ o u t p u t失败,s w i t c h语句 基于从 i p _ o u t p u t返回的值重新设置新的 I C M P类型和码值。 i c m p _ e r r o r发送该报文。来 自失败的i p _ o u t p u tICMP报文将覆盖任何重定向报文。 处理来自i p _ o u t p u t的差错的s w i t c h语句非常重要。它把本地差错翻译成适当的 ICMP 差错报文,并返回给分组的源站。图 8 - 2 1对差错作了总结。第 11章更详细地描述了 I C M P报 文。 当i p _ o u t p u t返回 E N O B U F S时,N e t / 3通常生成 I C M P源站抑制报文。 R o u t e r R e q u i r e m e n t s (路由器需求 )RFC [Almquist和Kastenholz 1994]不赞成源站抑制并要求 路由器不产生这种报文。 来自ip_output的差错码

生成的I C M P报文

EMSGSIZE

ICMP_UNREACH_NEEDFRAG

ENOBUFS

ICMP_SOURCEQUENCH

EHOSTUNREACH ENETDOWN EHOSTDOWN default

ICMP_UNREACH_HOST





对所选的接口来说,发出的分组太大, 并且禁止分片 (第1 0章) 接口队列满或内核运行内存不足。本报 文向源主机指示降低数据率 找不到到主机的路由 路由指明的输出接口没在运行 接口无法把分组发给选定的主机 所有不识别的差错均作为 I C M P _ U N R E A C H _ H O S T差错报告

图8-21 来自ip_output 的差错

8.6 输出处理:iP_output函数 I P 输出代码从两处接收分组: i p _ f o r w a r d 和运输协议 ( 图8 - 1 ) 。让 i n e t s w [ 0 ] . p r _ o u t p u t能访问到 I P输出操作似乎很有道理,但事实并非如此。标准的 I n t e r n e t传输协议 ( I C M P、I G M P、U D P和T C P )直接调用 i p _ o u t p u t,而不查询 i n e t s w表。对标准 I n t e r n e t传 输协议而言, p r o t o s w结构不必具有一般性,因为调用函数并不是在与协议无关的情况下接 入IP的。在第20章中,我们将看到与协议无关的路由选择插口调用 pr_output接入IP。 我们分三个部分描述 i p _ o u t p u t : • 首部初始化; • 路由选择;和 • 源地址选择和分片。

181

第 8章 IP:网际协议 计计

8.6.1 首部初始化 图8-22显示了i p _ o u t p u t的第一部分,把选项与外出的分组合并,完成传输协议提交 (不 是ip_forward提交的)的分组首部。 44-59

传给ip_output的参数包括:m0,要发送的分组; opt,包含的IP选项;ro,缓存

的到目的地的路由; flags,见图8-23;imo,指向多播选项的指针,见第 12章。 I P _ F O R W A R D I N G被i p _ f o r w a r d和i p _ m f o r w a r d ( 多播分组转发 )设置,并禁止 ip_output重新设置任何IP首部字段。

图8-22 函数ip_output 标





IP_FORWARDING IP_ROUTETOIF IP_ALLOWBROADCAST IP_RAWOUTPUT

图8-23



这是一个转发过的分组 忽略路由表,直接路由到接口 允许发送广播分组 包含一个预构 I P首部的分组

ip_output :flag 值

182

计计TCP/IP详解 卷 2:实现

send、sendto和sendmsg的MSG_DONTROUTE标志使IP_ROUTETOIF有效,并进行一 次写操作 (见1 6 . 4 ),而S O _ D O N T R O U T E插口选项使 I P _ R O U T E T O I F有效,并在某个特定插口 上进行任意的写操作 (见8.8节)。该标志被传输协议传给 ip_ output。 I P _ A L L O W B R O A D C A S T标志可以被 S O _ B R O A D C A S T插口选项 (见8.8节)设置,但只被 U D P 提交。原来的 I P 默认地设置 I P _ A L L O W B R O A D C A S T 。 T C P 不支持广播,所以 I P _ ALLOWBROADCAST不能被TCP提交给ip_output。不存在广播的预请求标志。 1. 构造IP首部 60-73 如果调用程序提供任何 IP选项,它们将被ip_insertoptions(见9.8节)与分组合并, 并返回新的首部长度。 我们将在 8 . 8节中看到,进程可以设置 I P _ O P T I O N S插口选项来为一个插口指定 I P选项。 插口的运输层(TCP或UDP)总是把这些选项提交给 ip_output。 被转发分组(I P _ F O R W A R D I N G)或有预构首部 (I P _ R A W O U T P U T)分组的I P首部不能被 i p _ o u t p u t修改。任何其他分组 (例如,产生于这个主机的 U D P或T C P分组)需要有几个 I P首部字 段被初始化。 i p _ o u t p u t把i p _ v设置成4(I P V E R S I O N),把DF位需要的 i p _ o f f清零,并设 置成调用程序提供的值 (见第1 0章),给来自全局整数的 i p - > i p _ i d赋一个唯一的标识符,把 i p _ i d加1。i p _ i d是在协议初始化时由系统时钟设置的 (见7 . 8节)。i p _ h l被设置成用 32 bit 字度量的首部长度。 IP首部的其他字段—长度、偏移、 TTL、协议、TOS和目的地址—已经被传输协议初 始化了。源地址可能没被设置,因为是在确定了到目的地的路由后选择的 (图8-25)。 2. 分组已经包括首部 74-76

对一个已转发的分组 (或一个有首部的原始 IP分组),首部长度(以字节数度量 )被保存

在hlen中,留给将来分片算法使用。 8.6.2 路由选择 在完成I P首部后, i p _ o u t p u t的下一个任务就是确定一条到目的地的路由。见图 8 - 2 4所 示。

图8-24 ip_output

(续)

183

第 8章 IP:网际协议 计计

图8-24 (续)

1. 验证高速缓存中的路由 77-99

i p _ o u t p u t可能把一条在高速缓存中的路由作为 ro参数来提供。在第 24章中,我们

将看到UDP和TCP维护一个与各插口相关的路由缓存。如果没有路由,则 i p _ o u t p u t把r o设 置成指向临时route结构iproute。 如果高速缓存中的目的地不是去当前分组的目的地,就把该路由丢掉,新的目的地址放 在dst中。 2. 旁路路由选择 100-114

调用方可通过设置 I P _ R O U T E T O I标志(见8 F . 8节)禁止对分组进行路由选择。

i p _ o u t p u t 必须找到一个与分组中指定目的地网络直接相连的接口。 i f a _ i f w i t h d s t a d d r搜索点到点接口,而 i n _ i f w i t h n e t搜索其他接口。如果任一函数 找到与目的网络相连的接口,就返回 ENETUNREACH;否则,ifp指向选定的接口。 这个选项允许路由选择协议绕过本地路由表,并使分组通过某特定接口退出系

184

计计TCP/IP详解 卷 2:实现

统。通过这个方法,即使本地路由表不正确,也可以与其他路由器交换路由选择信 息。 3. 本地路由 115-122

如果分组正被路由选择 (I P _ R O U T E T O I F为关状态 ),并且没有其他缓存的路由,

则rtalloc找到一条到 dst指定地址的路由。如果 rtalloc没找到路由,则 i p _ o u t p u返 t 回E H O S T U N R E A C H。如果 i p _ f o r w a r d调用 i p _ o u t p u t,就把 E H O S T U N R E A C H转换成 ICMP差错。如果某个传输协议调用 ip_output,就把差错传回给进程 (图8-21)。 123-128 ia被设成指向选定接口的地址 (ifaddr结构),而ifp指向接口的 ifnet结构。如 果下一跳不是分组的最终目的地,则把 d s t改成下一跳路由器地址,而不再是分组最终目的 地址。IP首部内的目的地址不变,但接口层必须把分组提交给 dst,即下一跳路由器。 8.6.3 源地址选择和分片 i p _ o u t p u t的最后一部分如图 8 - 2 5所示,保证 I P首部有一个有效源地址,然后把分组提 交给与路由相关的接口。如果分组比接口的 M T U大,就必须对分组分片,然后一片一片地发 送。像前面的重装代码一样,我们省略了分片代码,并推迟到第 10章再讨论。

图8-25 ip_output(续)

185

第 8章 IP:网际协议 计计

图8-25 (续)

1. 选择源地址 212-239

如果没有指定ip_src,则ip_output选择输出接口的 IP地址ia作为源地址。这

不能在早期填充其他 I P首部字段时做,因为那时还没有选定路由。转发的分组通常都有一个 源地址,但是,如果发送进程没有明确指定源地址,产生于本地主机的分组可能没有源地址。 如果目的 I P地址是一个广播地址,则接口必须支持广播 (I F F _ B R O A D C A S T,图3 - 7 ),调 用方必须明确使能广播 (IP_ALLOWBROADCAST,图8-23),而分组必须足够小,无需分片。 最后的测试是一个策略决定。 IP协议规范中没有明确禁止对广播分组的分片。但 是,要求分组适合接口的 M T U,就增加了广播分组被每个接口接收的机会,因为接 收一个未损坏的分组的机会要远大于接收两个或多个未损坏分组的机会。 如果这些条件都不满足,就扔掉该分组,把 E A D D R N O T A V A I L、E A C C E S和E M S G S I Z E返 回给调用方。否则,设置输出分组的 M _ B C A S T,告诉接口输出函数把该分组作为链路级广播 发送。21.20节中,我们将看到 arpresolve把IP广播地址翻译成以太网广播地址。 如果目的地址不是广播地址,则 ip_output把M_BCAST清零。 如果M _ B C A S T没有清零,则对一个作为广播到达的请求分组的应答将可能作为 一个广播被返回。我们将在第 11章中看到, I C M P应答将以这种方式作为 TCP RST 分组(见26.9节)在请求分组内构造。 2. 发送分组 240-252

如果分组对所选择的接口足够小, ip_len和ip_off被转换成网络字节序, IP检

验和与 in_cksum(见8.7节)一起计算,把分组提交给所选接口的 if_output函数。 3. 分片分组 253-338

大分组在被发送之前必须分片。这里我们省略这段代码,推迟到第 10章讨论。

4. 清零 339-346

对每一路由入口都有一个引用计数。我们提到过,如果参数

r o 为空, i p _

186

计计TCP/IP详解 卷 2:实现

output可能会使用一个临时的 route结构(iproute)。如果需要,RTFREE发布iproute内 的路由入口,并把引用计数减 1。Bad处的代码在返回前扔掉当前分组。 引用计数是一个存储器管理技术。程序员必须对一个数据结构的外部引用计 数;当计数返回为 0时,就可以安全地把存储器返回给空存储器池。引用计数要求程 序员遵守一些规定,在恰当的时机增加或减小引用计数。

8.7 Internet检验和: in_cksum函数 有两个操作占据了处理分组的主要时间:复制数据和计算检验和 ([Kay和Pasquale 1993])。 m b u f数据结构的灵活性是 N e t / 3中减少复制操作的主要方法。由于对硬件的依赖,所以检验和 的有效计算相对较难。 Net/3中有几种 in_cksum的实现(图8-26)。 版



源 文 件

图8-26 在Net/3中的几个 in_cksum 版本

即使是可移植 C实现也已经被相当好地优化了。 RFC 1071 [Braden 、B o r m a n和P a r t r i d g e 1988] 和RFC 1141 [Mallory和K u l l b e rg 1990]讨论了 I n t e r n e t检验和函数的设计和实现。 R F C 1141被RFC 1624 [Rijsinghani 1994] 修正。从RFC 1071: 1) 把被检验的相邻字节成对配成 16 bit整数,就形成了这些整数的二进制反码的和。 2) 为生成检验和,把检验和字段本身清零,把 16 bit的二进制反码的和以及这个和的二进 制反码放到检验和字段。 3) 为检验检验和,对同一组字节计算它们的二进制反码的和。如果结果为全 1 (在二进制 反码运算中- 0,见下面的解释 ),则检验成功。 简而言之,当对用二进制反码表示的整数进行加法运算时,把两个整数相加后再加上进 位就得到加法的结果。在二进制反码运算中,只要把每一位求补就得到一个数的反。所以在 二进制反码运算中, 0有两种表示方法:全 0,和全1。有关二进制反码的运算和表示的详细讨 论见 [Mano 1982]。 检验和算法在发送分组之前计算出要放在 I P首部检验和字段的值。为了计算这个值,先 把首部的检验和字段设为 0,然后计算整个首部 (包括选项 )的二进制反码的和。把首部作为一 个16 bit整数数组来处理。让我们把这个计算结果称为 a。因为检验和字段被明确设为 0,所以 a是除了检验和字段外所有 I P首部字段的和。 a的二进制反码,用- a表示,被放在检验和字段 中,发送该分组。 如果在传输过程中没有比特位被改变,则在目的地计算的检验和应该等于 (a+-a)的二进 制反码。在二进制反码运算中 (a+-a)的和是- 0 (全1 ),而它的二进制反码应该等于 0 (全0 )。所 以在目的地,一个没有损坏分组计算出来的检验和应该总是为 0。这就是我们在图 8 - 1 2中看到

187

第 8章 IP:网际协议 计计

的。下面的 C代码(不是Net/3的内容)是这个算法的一种原始的实现:

图8-27 IP检验和计算的一种原始的实现

1-16

这里唯一提高性能之处在于累计 s u m高16 bit的进位。当循环结束时,累计的进位被

加在低 16 bit上,直到没有其他进位发生。 RFC 1071称此为延迟进位 (deferred carries)。在没 有有进位加法指令或检测进位代价很大的机器上,这个技术非常有效。 现在我们显示 N e t / 3的可移植 C版本。它使用了延迟进位技术,作用于存储在一个 m b u f链 中的分组。 42-140 我们的新检验和实现假定所有被检验字节存储在一个连续缓存而不是 mbuf中。这个 版本的检验和计算采用相同的底层算法来正确地处理 mbuf:用32bit整数的延迟进位对 16 bit字 作加法。对奇数个字节的 m b u f,多出来的一个字节被保存起来,并与下一个 m b u f的第一个字 节配对。因为在大多数体系结构中,对 16 bit字的不对齐访问是无效的,甚至会产生严重差错, 所以不对齐字节将被保存, i n _ c k s u m继续加上下一个对齐的字。当这种情况发生时, i n _ c k s u m总是很小心地交换字节,保证位于奇数和偶数位置的字节被放在单独的和字节中,以 满足检验和算法的要求。 循环展开 93-115 函数中的三个while循环在每次迭代中分别在和中加上 16个字、4个字和1个字。展 开的循环减小了循环的耗费,在某些体系结构中可能比一个直接循环要快得多。但代价是代 码长度和复杂性增大。

图8-28 IP检验和计算的一个优化的可移植 C程序

188

计计TCP/IP详解 卷 2:实现

图8-28 (续)

189

第 8章 IP:网际协议 计计

图8-28 (续)

其他优化 RFC 1071提到两个在Net/3中没有出现的优化:联合的有检验和的复制操作和递增的检验 和更新。对 I P首部检验和来说,把复制和检验和操作结合起来并不像对 T C P和U D P那么重要, 因为后者覆盖了更多的字节。在 2 3 . 1 2节中对这个合并的操作进行了讨论。 [ P a r t r i d g e和P i n k 1 9 9 3 ]报告了 I P首部检验和的一个内联版本比调用更一般的 i n _ c k s u m函数要快得多,只需 6~8个汇编指令就可以完成 (标准的20字节IP首部)。 检验和算法设计允许改变分组,并在不重新检查所有字节的情况下更新检验和。

RFC

1 0 7 1对该问题进行简明的讨论。 RFC 11 4 1和1 6 2 4中有更详细的讨论。该技术的一个典型应用 是在分组转发的过程中。通常情况下,当分组没有选项时,转发过程中只有 T T L字段发生变 化。在这种情况下,可以只用一次循环进位,重新计算检验和。 为了进一步提高效率,递增的检验和也有助于检测到被有差错的软件破坏的首部。如果 递增地计算检验和,则下一个系统可以检测到被破坏的首部。但是如果不是递增计算检验和, 那么检验和中就包含了差错的字节,检测不到有问题的首部。 U D P和T C P使用的检验和算法 在最终目的主机检测到该差错。我们将在第 2 3和2 5章看到 U D P和T C P检验和包含了 I P首部的 几个部分。

190

计计TCP/IP详解 卷 2:实现

使用硬件有进位加法指令一次性计算 32 bit检验和的检验和函数,可参见 ./ s y s / v a x / vax/in_cksum.c文件中VAX实现的in_cksum。

8.8 setsockopt和getsockopt系统调用 N e t / 3提供s e t s o c k o p t和g e t s o c k o p t两个系统调用来访问一些网络互连的性质。这两个系统 调用支持一个动态接口,进程可用该动态接口来访问某种网络互连协议的一些性质,而标准 系统调用通常不支持该协议。这两个调用的原型是: int setsockopt(int s, int level, int optname, void *optval, int optlen); int getsockopt(int s, int level, int optname, const void *optval, int optlen);

大多数插口选项只影响它们在其上发布的插口。与 sysctl参数相比,后者影响整个系统。 与多播相关的插口选项是一个明显的例外,将在第 12章中讨论。 s e t s o c k o p t和g e t s o c k o p t设置和获取通信栈所有层上的选项。 N e t / 3按照与 s相关的 协议和由level指定的标识符处理选项。图 8-29列出了在我们讨论的协议中 level可能取得的值。 在第1 7章中,我们描述了 s e t s o c k o p t和g e t s o c k o p t的实现,但在其他适当章节中讨 论有关选项的实现。本章讨论访问 IP性质的选项。 字段

协议

level

任意

任意

SOL_SOCKET

sosetopt和sogetopt

图17-5和图17-11

IP

UDP

IPPROTO_IP

ip_ctloutput

图8-31

TCP

IPPROTO_TCP IPPROTO_IP

tcp_ctloutput ip_ctloutput

30.6节 图8-31

IPPROTO_IP

r i p _ c t l o u t p u t和 ip_ctloutput

32.8节

原始IP ICMP IGMP









图8-29 sosetopt 和sogetopt 参数

我们把本书中出现的所有插口选项总结在图 8 - 3 0中。该图显示了 I P P R O T O _ I P级的选项。 选项出现在第 1列, o p t v a l指向变量的数据类型出现在第 2列,第 3列显示的是处理该选项的 函数。 选 项 名

O p t v a l类型









IP_OPTIONS IP_TOS IP_TTL TP_RECVDSTADDR

void* int int int

in_pcbopts ip_ctloutput ip_ctloutput ip_ctloutput

设置或获取发出的数据报中的 IP选项 设置或获取发出的数据报中的 IP TOS 设置或获取发出的数据报中的 IP TTL 使能或禁止 IP目的地址(只有UDP)的排队

IP_RECVOPTS

int

ip_ctloutput

IP_RECVRETOPTS

int

ip_ctloutput

使能或禁止对到达 IP选项作为控制信息的 排队(只对U D P;还没有实现 ) 使能或禁止与到达数据报相关的逆源路由 (只对UDP;还没有实现 )

图8-30 插口选项: SOCK_RAW 、SOCK_DGRAM 和SOCK_STREAMR

插口的IPPROTO_IP 级

图8 - 3 1显示了用于处理大部分 I P P R O T O _ I P选项的i p _ c t l o u t p u t函数的整个结构。在 32.8节中我们给出与 SOCK_RAW插口一起使用的 IPPROTO_IP选项。

191

第 8章 IP:网际协议 计计

图8-31 ip_ctloutput

431-447

函数:概貌

ip_ctloutput的第一个参数op,可以是PRCO_SETOPT或者PRCO_GETOPT。第二

个参数so,指向向其发布请求的插口。level必须是IPPROTO_IP。O p t n a m e是要改变或要检 索的选项, m p间接地指向一个含有与该选项相关数据的 m b u f,m被初始化为指向由 * m p引用 的mbuf。 448-500

如果在调用s e t s o c k o p t时指定了一个无法识别的选项 (因此,在s w i t c h中调用

PRCO_SETOPT语句),ip_ctloutput释放掉所有调用方传来的缓存,并返回 EINVAL。

192

计计TCP/IP详解 卷 2:实现

501-553

getsockopt传来的无法识别的选项导致ip_ctloutput返回ENOPROTOOPT。在

这种情况下,调用方释放 mbuf。 8.8.1 PRCO_SETOPT的处理 对PRCO_SETOPT的处理如图 8-32所示。

图8-32 ip_ctloutput

函数:处理 PRCO_SETOPT

450-451 IP_OPTIONS是由ip_pcbopts处理的(图9-32)。 452-484

IP_TOS、 IP_TTL、 IP_RECVOPTS、 IP_RECVERTOPTS以 及 IP_

R E C V D S T A D D R选项都需要在由 m指向的m b u f中有一个整数。该整数储存在 o p t v a l中,用来改 变 与 插口 有 关的 i p _ t o s 和 i p _ t t l 的 值, 或 者用 来 设置 或 复位 与 插口 相 关的 I N P _ R E C V O P T S、I N P _ R E C V E R T O P T S和I N P _ R E C V D S T A D D R标志位。如果 o p t v a l是非零 (或0),则宏OPTSET设置(或复位)指定的比特。 图8 - 3 0中显示没有实现 I P _ R E C V O P T S和I P _ R E C V E R T O P T S。在第2 3章中,我

193

第 8章 IP:网际协议 计计

们将看到UDP忽略了这些选项的设置。 8.8.2 PRCO_GETOPT的处理 图8-33显示的一段代码完成了当指定 PRCO_GETOPT时对IP选项的检索。

图8-33 ip_ctloutput

函数:PRCO_GETOPT 的处理

503-538 对IP_OPTIONS,ip_ctloutput返回一个缓存,该缓存中包含了与该插口相关 的选项的备份。对其他选项, ip_ctloutput返回ip_tos和ip_ttl的值,或与该选项相关 的标志的状态。返回的值放在由 m指向的 m b u f中。如果在 i n p _ f l a g s中的b i t是打开 (或关 闭)的,则宏 OPTBIT将返回1(或0)。

8.9 ip_sysctl函数 图7-27显示,在调用 s y s c t l中,当协议和协议族的标识符是 0时,就调用i p _ s y s c t l函

194

计计TCP/IP详解 卷 2:实现

数。图8-34显示了ip_sysctl支持的三个函数。 sy s c t l常量

Net/3变量

IPCTL_FORWARDING IPCTL_SENDREDIRECTS IPCTL_DEFTTL

ipforwarding ipsendredirects ip_defttl





系统是否转发 IP分组? 系统是否发 ICMP重定向? IP分组的默认 TTL

图8-34 sysctl 参数

图8-35显示了ip_sysctl函数。

图8-35 ip_sysctl 函数

因为i p _ s y s c t l并不把 s y s c t l请求转发给其他函数,所以在 n a m e中只能有一个成员。 否则返回 ENOTDIR。 S w i t c h 语句选择恰当的调用 s y s t l _ i n t ,它访问或修改 i p f o r w a r d i n g 、 ipsendredirects或ip_defttl。对无法识别的选项返回 EOPNOTSUPP。

8.10 小结 I P是一个最佳的数据报服务,它为所有其他 I n t e r n e t协议提供交付机制。标准 I P首部长度 为2 0字节,但可跟最多 4 0字节的选项。 I P可以把大的数据报分片发送,并在目的地重装分片。 对选项处理的讨论放在第 9章和第10章讨论分片和重装。 i p i n t r保证I P首部到达时未经破坏,通过把目的地址与系统接口地址及其他几个广播地 址比较来确定它们是否到达最终目的地。 i p i n t r把到达最终目的地的数据报传给分组内指定 的运输层协议。如果系统被配置成路由器,就把还没有到达最终目的地的分组发给

195

第 8章 IP:网际协议 计计

i p _ f o r w a r d 转发到最终目的地。分组有一个受限的生命期。如果

T T L字段变成 0,则

ip_forward就丢掉该分组。 许多Internet协议都使用Internet检验和函数, Net/3用i n _ c k s u m实现。IP检验和只覆盖首 部(和选项),不覆盖数据,数据必须由传输协议级的检验和保护。作为 IP中最耗时的操作,检 验和函数通常要对不同的平台进行优化。

习题 8.1 当没有为任何接口分配 IP地址时, IP是否该接收广播分组? 8.2 修改i p _ f o r w a r d和i p _ o u t p u t,当转发一个没有选项的分组时,对 IP检验和进行 递增的更新。 8.3 当拒绝转发分组时,为什么需要检测链路级广播 (某缓存中的 M _ B C A S T标志)和I P级 广播(i n _ c a n f o r w a r d)?在何种情况下,把一个具有 IP单播目的地的分组作为一个 链路层广播接收? 8.4 当一个IP分组到达时有检验和差错,为什么不向发送方返回一个差错信息? 8.5 假定一个多接口主机上的某个进程为它发出的分组选择了一个明确的源地址。而且, 假定是通过一个接口而不是作为分组源地址所选择的地址到达的。当第一跳路由器 发现分组应该到另一个路由器时,会发生什么情况?会向主机发送重定向报文吗? 8.6 一个新的主机被连到一个已划分子网的网络中,并被配置成完成路由选择的功能 (i p f o r w a r d i n g等于1),但它的网络接口没有分配子网掩码。当该主机接收一个子 网广播分组时会出现什么情况? 8.7 图8-17中,在检测 ip_ttl后(与之前相比 ),为什么需要把它减 1? 8.8 如果两个路由器都认为对方是分组的最佳下一跳目的地,将发生什么情况? 8.9 图8 - 1 4中,对一个到达 S L I P接口的分组,不检测哪些地址?有没有其他在图 8 - 1 4中 没有列出的地址被检测? 8.10 i p _ f o r w a r d在调用i c m p _ e r r o r之前,把分片的 i d从主机字节序转换成网络字 节序。为什么它不对分片的偏移进行转换?

第9章 IP选项处理 9.1 引言 第8章中提到, I P输入函数 (i p i n t r)将在验证分组格式 (检验和,长度等 )之后,确定分组 是否到达目的地之前,对选项进行处理。这表明,分组所遇到的每个路由器以及最终的目的 主机都要对分组的选项进行处理。 RFC 791和1122指定了IP选项和处理规则。本章将讨论大多数 IP选项的格式和处理。我们 也将显示运输协议如何指定 IP数据报内的 IP选项。 I P分组内可以包含某些在分组被转发或被接收之前处理的可选字段。 I P实现可以用任意 顺序处理选项; Net/3按照选项在分组中出现的顺序处理选项。图 9-1显示,标准IP首部之后最 多可跟40字节的选项。 ip_hl×4字节 标准首部 (20字节)

选 项 (0~40字节) 最大60字节

图9-1 一个IP首部可以有 0~40字节的IP选项

9.2 代码介绍 两个首部描述了 I P选项的数据结构。选项处理的代码出现在两个 C文件中。图 9 - 2列出了 相关的文件。 文





netinet/ip.h netinet/ip_var.h netinet/ip_input.c netinet/ip_output.c



i p _ t i m e s t a m p结构 i p o p t i o n结构 选项处理 i p _ i n s e r t o p t i o n s函数

图9-2 本章讨论的文件

9.2.1 全局变量 图9-3描述了两个全局变量支持源路由的逆 (reversal)。 变



ip_nhops ip_srcrt

数据类型 int struct ip_srcrt





以前的源路由跳计数 以前的源路由

图9-3 本章引入的全局变量

197

第 9章 IP选项处理计计

9.2.2 统计量 选项处理代码更新的唯一的统计量是 ipstat结构中的ips_badoptions,如图8-4所示。

9.3 选项格式 IP选项字段可能包含 0个或多个单独选项。选项有两种类型,单字节和多字节,如图 9-4中 所示。 len字节 单字节

多字节

位移字段没有出现在每个多 字节选项中。

1

2 bit

5 bit

图9-4 单字节和多字节IP选项的结构

所有选项都以 1字节类型 (t y p e)字段开始。在多字节选项中,类型字段后面紧接着一个长 度(l e n)字段,其他的字节是数据 (d a t a )。许多选项数据字段的第一个字节是 1字节的位移 (o f f s e t)字段,指向数据字段内的某个字节。长度字节的计算覆盖了类型、长度和数据字段。 类型被继续分成三个子字段: 1 b i t 备份 ( c o p i e d) 标志、 2 b i t 类 ( c l a s s ) 字段和 5 b i t 数字 (n u m b e r)字段。图 9 - 5列出了目前定义的 I P选项。前两个选项是单字节选项;其他的是多字 节选项。 常



类 十进制

型 二进制

长度 (字节)

IPOPT_EOL IPOPT_NOP IPOPT_RR IPOPT_TS IPOPT_SECURITY IPOPT_LSRR

0-0-0 0-0-1 0-0-7 0-2-4 1-0-2 1-0-3

0 1 7 68 130 131

0-00-00000 0-00-00001

1 1

0-00-00111 0-10-00100 1-00-00010 1-00-00011

可变 可变 11 可变

1-00-00101 1-00-01000 1-00-01001

可变

IPOPT_SATID IPOPT_SSRR

1-0-5 133 1-0-8 136 1-0-9 137

• • • •



选项表的结尾 (EOL) 无操作(NOP)



记录路由 时戳 基本的安全 宽松源路由和记录路由 (LSRR)



扩展的安全 流标识符 严格源路由和记录路由 (SSRR)

4 可变



Net/3

图9-5 RFC 791定义的IP选项

第1列显示了 N e t / 3的选项常量,第 2列和第 3列是该类型的十进 制和二进制值,第 4列是选项的长度。 Net/3列显示的是在 Net/3中由 i p _ d o o p t i o n s实现的选项。 I P必须自动忽略所有它不识别的选 项。我们不描述 N e t / 3没有实现的选项:安全和流 I D。流I D选项是 过时的,安全选项主要只由美国军方使用。 RFC 791中有更多的讨 论。 当N e t / 3对一个有选项的分组进行分片时 ( 1 0 . 4节),它将检查

class 0 1 2 3





控制 保留 查错和措施 保留

图9-6 IP选项内的 class字段

copied标志位。该标志位指出是否把所有选项都备份到每个分片的 IP首部。class字段把相关的

198

计计TCP/IP详解 卷 2:实现

选项按如图 9-6所示进行分组。图 9-5中,除时戳选项具有 class为2外,所有选项都是 class为0。

9.4 ip_dooptions函数 在图 8 - 1 3中,我们看到 i p i n t r 在检测分组的目的地址之前调用 i p _ d o o p t i o n s 。 i p _ d o o p t i o n s被传给一个指针 m,该指针指向某个分组, i p _ d o o p t i o n s处理分组中它所 知道的选项。如果 i p _ d o o p t i o n s转发该分组,如在处理 L S R R和S S R R选项时,或由于某个 差错而丢掉该分组时,它返回 1。如果它不转发分组, i p _ d o o p t i o n s返回0,由i p i n t r继 续处理该分组。 i p _ d o o p t i o n s是一个长函数,所以我们分步地显示。第一部分初始化一个 f o r循环, 处理首部中的各选项。 当处理每个选项时, c p指向选项的第一个字节。图 9 - 7显示,当可用时,如何从 c p的常 量位移访问 type、length和offset字段。

len 字节

图9-7 用常量位移访问 IP选项字段

RFC把位移(offset)字段描述作指针 (pointer),指针比位移的描述性略强一些。 offset的值是 某个字节在该选项内的序号 (从t y p e开始,序号为 1 ),不是从 t y p e开始的、且以零开始的计数。 位移的最小值是 4(IPOPT_MINOFF),它指向的是多字节选项中数据字段的第一个字节。 图9-8显示了ip_dooptions函数的整体结构。 555-566

i p _ d o o p t i o n s把I C M P差错类型 t y p e初始化为 I C M P _ P A R A M P R O B,对任何没

有特定差错类型的差错,这是一个一般值。对于 I C M P _ P A R A M P R O B,c o d e指的是出错字节 在分组内的位移。这是默认的 ICMP差错报文。某些选项将改变这些值。 i p指向一个20字节大小的i p结构,所以i p + 1指向的是跟在 IP首部后面的下一个 i p结构。因为 i p _ d o o p t i o n s需要I P首部后面字节的地址,所以就把结果指针转换 成为指向一个无符号字节 (u _ c h a r)的指针。因此, c p指向标准 I P首部以外的第一个 字节,就是 IP选项的第一个字节。 1. EOL和NOP过程 567-582

f o r循环按照每个选项在分组中出现的顺序分别对它们进行处理。 E O L选项以及

一个无效的选项长度 (也即选项长度表明选项数据超过了 IP首部)都将终止该循环。当出现 NOP 选项时,忽略它。 switch语句的default情况隐含要求系统忽略未知的选项。 下面的内容描述了 s w i t c h语句处理的每个选项。如果 i p _ d o o p t i o n s在处理分组选项 时没有出错,就把控制交给 switch下面的代码。 2. 源路由转发

199

第 9章 IP选项处理计计

719-724

如果分组需要被转发, SSRR或LSRR选项处理代码就把 forward置位。分组被传

给ip_forward,并且第2个参数为1,表明分组是按源路由选择的。

图9-8 ip_dooptions

函数

我们在8.5节中讲到,并不为源路由选择分组生成 ICMP重定向—这就是为什么 在传给ip_forward时设置第2个参数的原因。

200

计计TCP/IP详解 卷 2:实现

如果转发了分组,则 i p _ d o o p t i o n s 返回 1 。如果分组中没有源路由,则返回 0给 i p i n t r ,表明需要对该数据报进一步处理。注意,只有当系统被配置成路由器时 (ipforwarding等于1),才发生源路由转发。 从某种程度上说,这是一个有些矛盾的策略,但却是

RFC 1 1 2 2 的书面要求。

RFC 1127 [Braden 1989c]把它作为一个公开问题加以阐述。 3. 差错处理 725-730

如果在s w i t c h语句里出现了错误, i p _ d o o p t i o n s就跳到b a d。从分组长度中

把I P首部长度减去,因为 i c m p _ e r r o r假设首部长度不包含在分组长度里。 i c m p _ e r r o r发 出适当的差错报文, ip_dooptions返回1,避免ipintr处理被丢弃的分组。 下一节描述 Net/3处理的所有选项。

9.5 记录路由选项 记录路由选项使得分组在穿过互联网时所经过的路由被记录在分组内部。项的大小是源 主机在构造时确定的,必须足够保存所有预期的地址。我们记得在 I P分组的首部,选项最多 只能有 4 0字节。记录路由选项可以有 3个字节的开销,后面紧跟地址的列表 (每个地址 4字节)。 如果该选项是唯一的选项,则最多可以有 9个( 3+4×9=3 9 )地址出现。一旦分配给该选项的 空间被填满,就按通常的情况对分组进行转发,中间的系统就不再记录地址。 图9-9说明了一个记录路由选项的格式,图 9-10是其源程序。

4 字节

4 字节

4 字节

图9-9 记录路由选项,其中 n必须≤9

图9-10 函数ip_dooptions

:记录路由选项的处理

201

第 9章 IP选项处理计计

图9-10 (续)

647-657

如果位移选项太小,则 i p _ d o o p t i o n s就发送一个 I C M P参数问题差错。如果变

量c o d e被设置成分组内无效选项的字节位移量,并且 b a d标号(图9 - 8 )语句的执行产生错误, 则发出的 I C M P参数问题差错报文中就具有该 c o d e值。如果选项中没有附加地址的空间,则 忽略该选项,并继续处理下一个选项。 记录地址 658-673 如果ip_dst是某个系统地址(分组已到达目的地),则把接收接口的地址记录在选项 中;否则把i p _ r t a d d r提供的外出接口的地址记录下来。把位移更新为选项中下一个可用地 址位置。如果ip_rtaddr无法找到到目的地的路由,就发送一个 ICMP主机不可达差错报文。 卷1的7.3节举了一些记录路由选项的例子。 ip_rtaddr函数 函数ip_rtaddr查询路由缓存,必要时查询完整的路由表,来找到到给定 IP地址的路由。 它返回一个指向 i n _ i f a d d r结构的指针,该指针与该路由的外出接口有关。图 9 - 11显示了该 函数。

图9-11 函数ip_rtaddr :寻找外出的接口

1. 检查IP转发缓存 735-741

如果路由缓存为空,或者如果 i p _ r t a d d r的唯一参数d e s t与路由缓存中的目的

202

计计TCP/IP详解 卷 2:实现

地不匹配,则必须查询路由表选择一个外出的接口。 2. 确定路由 旧的路由 (如果有的话 )被丢弃,并把新的路由储存在 * s i n (这是转发缓存的

742-750

ro_dst成员)。rtalloc搜索路由表,寻找到目的地的路由。 3. 返回路由信息 如果没有路由可用,就返回一个空指针;否则,就返回一个指针,指向与所选路

751-754

由相关联的接口地址结构。

9.6 源站和记录路由选项 通常是在中间路由器所选择的路径上转发分组。源站和记录路由选项允许源站明确指定 一条到目的地的路由,覆盖掉中间路由器的路由选择决定。而且,在分组到达目的地的过程 中,把该路由记录下来。 严格路由包含了源站和目的站之间的每个中间路由器的地址;宽松路由只指定某些中间 路由器的地址。在宽松路由中,路由器可以自由选择两个系统之间的任何路径;而在严格路 由中,则不允许路由器这样做。我们用图 9-12说明源路由处理 . A、B和C是路由器,而 H S和H D是源和目的主机。因为每个接口都有自己的 I P地址,所以 我们看到路由器 A有三个地址: A 1,A 2和A3。同样,路由器 B和C也有多个地址。图 9 - 1 3显示 了源站和记录路由选项的格式。

图9-12 源路由举例

1

1

1

4 字节

4 字节

4 字节

图9-13 严格和宽松源路由选项

I P首部的源和目的地址以及在选项中列出的位移和地址表,指定了路由以及分组目前在 该路由中所处的位置。图 9-14显示,当分组按照这个宽松源路由从 HS经A、B、C到HD时,信 息是如何改变的。每行代表当分组被第 1列显示的系统发送时的状态。最后一行显示分组被 HD接收。图9-15显示了相关的代码。 符号“•”表示位移与路由中地址的相对位置。注意,每个系统都把出接口的地址放到选 项去。特别地,原来的路由指定 A 3为第一跳目的地,但是外出接口 A 2被记录在路由中。通过 这种方法,分组所采用的路由被记录在选项中。被记录的路由将被目的地系统倒转过来放到 所有应答分组上,让它们沿着原始的路由的逆方向发送。 除了UDP,Net/3在应答时总是把收到的源路由逆转过来。

203

第 9章 IP选项处理计计

系统

IP首部 位移

源路由选项 地址

图9-14 当分组通过该路由时,源路由选项被修改。

图9-15 函数ip_dooptions

:LSRR和SSRR选项处理

204

计计TCP/IP详解 卷 2:实现

图9-15 (续)

583-612 如果选项位移小于 4 (IPOPT_MINOFF),则Net/3发送一个ICMP参数问题差错,并 带上相应的 c o d e值。如果分组的目的地址与本地地址没有一个匹配,且选项是严格源路由 (I P O P T _ S S R R),则发送一个源路由失败差错。如果本地地址不在路由中,则上一个系统把 分组发送到错误的主机上了。对宽松路由 (I P O P T _ L S R R)来说,这不是错误;仅意味着 I P必 须把分组转发到目的地。 1. 源路由的结束 613-620 减小off,把它转换成从选项开始的字节位移。如果 IP首部的ip_dst是某个本地 地址,并且 off所指向的超过了源路由的末尾,源路由中没有地址了,则分组已经到达了目的 地。s a v e _ r t e复制在静态结构 i p _ s r c r t中的路由,并保存在全局 i p _ n h o p s(图9 - 1 8 )里路 由中的地址个数。 i p _ s r c r t被定义成为一个外部静态结构,因为它只能被在 i p _ i n p u t . c中定 义的函数访问。 2. 为下一跳更新分组 621-637 如果ip_dst是一个本地地址,并且 offset指向选项内的一个地址,则该系统是 源路由中指定的一个中间系统,分组也没有到达目的地。在严格路由中,下一个系统必须位 于某个直接相连的网络上。 i f a _ i f w i t h d s t和i f a _ i f w i t h n e t通过在配置的接口中搜索 匹配的目的地址 (一个点到点的接口 )或匹配的网络地址 (广播接口 )来寻找一条到下一个系统的 路由。而在宽松路由中, i p _ r t a d d r(图9 - 11 )通过查询路由表来寻找到下一个系统的路由。 如果没有找到到下一系统的接口或路由,就发送一个 ICMP源路由失败差错报文。 638-644 如果找到一个接口或一条路由,则 ip_dooptions把ip_dst设置成off指向的 I P地址。在源路由选项内,用外出接口的地址代替中间系统的地址,把位移增加,指向路由 中的下一个地址。 3. 多播目的地 6 4 5 - 6 4 6 如果新的目的地址不是多播地址,就将 f o r w a r d设置成1,表明在处理完所有选 项后,应该把分组转发而不是返回给 ipintr。 源路由中的多播地址允许两个多播路由器通过不支持多播的中间路由器进行通信。第 1 4 章详细描述了这一技术。

205

第 9章 IP选项处理计计

卷1的8.5节有更多的源路由选项的例子。 9.6.1 save_rte函数 RFC 11 2 2要求,在最终目的地,运输协议必须能够使用分组中被记录下来的路由。运输 协议必须把该路由倒过来并附在所有应答的分组上。图 9 - 1 8中显示的 s a v e _ r t e函数,把源 路由保存在如图 9-16所示的ip_srcrt结构中。

图9-16 结构ip_srcrt

Route的声明是不正确的,尽管这不是个恶性错误。应该是 Struct in_addr route[(MAX_IPOPTLEN - 3)/sizeof(struct in_addr)];

对图9-26和图9-27的讨论详细地说明了这个问题。 57-63

该代码定义了 i p _ s r c r t结构,并声明了静态变量 i p _ s r c r t。只有两个函数访问

i p _ s r c r t:s a v e _ r t e,把到达分组中的源路由复制到 i p _ s r c r t中;i p _ s r c r o u t e,创 建一个与源路由方向相逆的路由。图 9-17说明了源路由处理过程。 传输协议

IP分组

图9-17 对求逆后的源路由的处理

图9-18 函数save_rte

206

计计TCP/IP详解 卷 2:实现

759-771

当一个源路由选择的分组到达目的地时, i p _ d o o p t i o n s调用 s a v e _ r t e。

option是一个指向分组的源路由选项的指针, dst是从分组首部来的 ip_src (也就是,返回路 由的目的地,图9-12中的HS)。如果选项的长度超过 ip_srcrt结构,save_rte立即返回。 永远也不可能发生这种情况,因为 ip_srcrt结构比最大选项长度(40字节)要大。 s a v e _ r t e把该选项复制到 i p _ s r c r t,计算并保存 i p _ n h o p s中源路由的跳数,把返回 路由的目的地保存在 dst中。 9.6.2 ip_srcroute函数 当响应某个分组时, I C M P和标准的运输层协议必须把分组带的任意源路由逆转。逆转源 路由是通过 ip_srcroute保存的路由构造的,如图 9-19所示。

图9-19 ip_srcroute 函数

207

第 9章 IP选项处理计计

图9-19 (续)

777-783

i p _ s r c r o u t e把保存在i p _ s r c r t结构中的源路由逆转后,返回与 i p o p t i o n

结构 ( 图 9 - 2 6 ) 格式类似的结果。如果

i p _ n h o p s 是 0 ,则没有保存的路由,所以

ip_srcroute返回一个指针。 记得在图 8 - 1 3中,当一个无效分组到达时, i p i n t r把i p _ n h o p s清零。运输层 协议必须调用i p _ s r c r o u t e,并在下一个分组到达之前自己保存逆转后的路由。正 如以前我们注意到的,这样做是正确的,因为 i p i n t r在处理分组时,在 I P输入队列的 下一个分组被处理之前都会调用运输层 (TCP或UDP)的。 为源路由分配存储器缓存 784-786 如果ip_nhops非0,ip_srcroute就分配一个 mbuf,并把m_len设置成足够大, 以便包含第一跳目的地、选项首部信息 (O P T S I Z)以及逆转后的路由。如果分配失败,则返回 一个空指针,跟没有源路由一样。 p被初始化为指向到达路由的末尾,ip_srcroute把最后记录的地址复制到 mbuf的前面, 在这里它为外出的第一跳目的地开始逆转后的路由。然后该函数把一份 N O P选项(习题9 . 4 )和 源路由信息复制到 mbuf中。 805-818

W h i l e循环把其余的 IP 地址从源路由中以相反的顺序复制到 m b u f中。路由的最

后一个地址被设置成到达分组中被 s a v e _ r t e放在i p _ s r c r t . d s t中的源站地址。返回一个 指向mbuf的指针。图 9-20说明了对图 9-12的路由如何构造逆转的路由。

4 字节

没有使用

没有使用 源路由选项

图9-20 ip_srcroute 逆转ip_srcrt 中的路由

9.7 时间戳选项 当分组穿过一个互联网时,时间戳选项使各个系统把它当前的时间表示记录在分组的选 项内。时间是以从 UTC的午夜开始计算的毫秒计,被记录在一个 32 bit 的字段里。 如果系统没有准确的 UTC(几分钟以内 )或没有每秒更新至少 15次,就不把它作为标准时间。 非标准时间必须把时间戳字段的高比特位置位。

208

计计TCP/IP详解 卷 2:实现

有三种时间戳选项类型, Net/3通过如图9-22所示的ip_timestamp结构访问。 114-133

如同ip结构(图8-10)一样,#ifs保证比特字段访问选项中正确的比特位。图 9-21

中列出了由 ipt_flg指定的三种时戳选项类型。 值

ipt_flg IPOOPT_TS_TSONLY IPOPT_TS_TSANDADDR IPOPT_TS_PRESPEC

0 1 2 3 4-15





记录时间戳 记录地址和时间戳 保留 只在预先指定的系统记录时间戳 保留

图9-21 ipt_flg 可能的值

图9-22 ip_timestamp

结构和常量

初始主机必须构造一个具有足够大的数据区存放可能的时间戳和地址的时间戳选项。对 于i p t _ f l g为3的时间戳选项,初始主机在构造该选项时,填写要记录时间戳的系统的地址。 图9-23显示了三种时间戳选项的结构。

4 字节

4 字节

4 字节

4 字节

图9-23 三种时间戳选项 (省略ipt_ )

因为I P选项只能有4 0个字节,所以时戳选项限制只能有 9个时戳(i p t _ f l g等于0 )或4个地

209

第 9章 IP选项处理计计

址和时间戳对(ipt_flg等于1或3)。图9-24显示了对三种不同的时戳选项类型的处理。 674-684

如果选项长度小于 5个字节 (时戳选项的最小长度 ),则i p _ d o o p t i o n s发出一个

I C M P参数问题差错报文。 o f l w字段统计由于选项数据区满而无法登记时戳的系统个数。如 果数据区满,则 o f l w加1;当它本身超过 1 6 (它是一个 4 bit的字段 )而溢出时,发出一个 I C M P 参数问题差错报文。

图9-24 函数ip_dooptions

:时间戳选项处理

210

计计TCP/IP详解 卷 2:实现

1. 只有时间戳 685-687

对于ipt_flg为0的时间戳选项(IPOPT_TS_TSONLY),所有的工作都在 switch

语句之后再做。 2. 时间戳和地址 688-700 对于ipt_flg为1的时间戳选项(IPOPT_TS_TSANDADDR),接收接口的地址被记 录下来 (如果数据区还有空间 ),选项的指针前进一步。因为 N e t / 3支持一个接收接口上的多 I P 地址,所以i p _ d o o p t i o n s调用i f a o f _ i f p f o r a d d r选择与分组的初始目的地址 (也就是在 任何源路由选择发生之前的目的地 )最匹配的地址。如果没有匹配,则跳过时间戳选项 (I N A和 SA定义如图9-15所示)。 3. 预定地址上的时间戳 701-710

如果ipt_flg为3 (IPOPT_TS_PRESPEC),ifa_ifwithaddr确定选项中指定

的下一个地址是否与系统的某个地址匹配。如果不匹配,该选项要求在这个系统上不处理; continue使ip_dooptions继续处理下一个选项。如果下一个地址与系统的某个地址匹配, 则选项的指针前进到下一个位置,控制交给 switch的后面。 4. 插入时间戳 711-713 default截获无效的 ipt_flg值,并把控制传递到 bad。 714-719

时间戳用switch语句后面的代码写入到选项中。 iptime返回自从UTC午夜起到

现在的毫秒数, ip_dooptions记录此时间戳,并增加此选项相对于下一个位置的偏移。 iptime函数 图9-25显示了iptime的实现。

图9-25 函数iptime

458-466

m i c r o t i m e返回从U T C 1 9 7 0年1月1日午夜以来的时间,放在 t i m e v a l结构中。

从午夜以来的毫秒数用 atv计算,并以网络字节序返回。 卷1的7.4节有几个时间戳选项的例子。

9.8 ip_insertoptions函数 我们在 8 . 6节看到, i p _ o u t p u t函数接收一个分组和选项。当 i p _ f o r w a r d调用该函数 时,选项已经是分组的一部分,所以 i p _ f o r w a r d总是把一个空选项指针传给 i p _ o u t p u t。 但是,运输层协议可能会把由 i p _ i n s e r t o p t i o n s(由图8 - 2 2中的i p _ o u t p u t调用)合并到 分组中的选项传递给 ip_forward。

211

第 9章 IP选项处理计计

ip_insertoptions希望选项在 ipoption结构中被格式化,如图 9-26所示。

图9-26 结构ipoption

92-95

该结构只有两个成员: ipopt_dst,如果选项表中有源路由,则其中有第一跳目的

地,i p o p t _ l i s t,是一个最多 4 0 (M A X _ I P O P T L E N)字节的选项矩阵,其格式我们在本章中 已做了描述。如果选项表中没有源路由,则 ipopt_dst全为0。 注意, i p _ s r c r t 结构 (图9 - 1 6 )和由 i p _ s r c r o u t e (图9 - 1 9 )返回的 m b u f都符合由 ipoption结构所指定的格式。图 9-27把结构ip_srcrt和ipoption作了比较。

图9-27 结构ip_srcrt 和ipoption

结构ip_srcrt比ipoption多4个字节。路由矩阵的最后一个入口 (route[9]) 永远都不会填上,因为这样的话,源路由选项将会有 4 4字节长,比 I P首部所能容纳 的要大(图9-16)。 函数ip_insertoptions如图9-28所示。

图9-28 函数ip_insertoptions

212

计计TCP/IP详解 卷 2:实现

图9-28 (续)

352-364

ip_insertoptions有三个参数:m,外出的分组; opt,在结构中格式化的选

项; p h l e n,一个指向整数的指针,在这里返回新首部的长度 (在插入选项之后 )。如果插入 选项分组长度超过最大分组长度

65 535( I P _ M A X P A C K E T ) 字节,则自动将选项丢弃。

i p _ d o o p t i o n s认为i p _ i n s e r t o p t i o n s永远都不会失败,所以无法报告差错。幸好,很 少有应用程序会试图发送最大长度的数据报,更别说选项了。

(20字节)

(8字节) max_linkhdr (16字节)

为以太网首部 分配的

IP首部 (20字节) TCP首部 100字节

(20字节)

44字节

图9-29 函数ip_insertoptions

:TCP报文段

213

第 9章 IP选项处理计计

365-366

如果i p o p t _ d s t . s _ a d d r指定了一个非零地址,则选项中包括了源路由,并且

分组首部的 ip_dst被源路由中的第一跳目的地代替。 在26.2节中,我们将看到 TCP调用M G E T H D R为IP和TCP首部分配一个单独的 mbuf。图9-29 显示了在第 367~378行代码执行之前,一个 TCP报文段的mbuf结构。 mbuf{} m

其他的存储器缓存 20字节

20字节 8字节 max_linkndr (16字节)

ip

m_data bcopy

IP首部 (20字节)

m_data IP选项 (optlen字节)

图9-30 函数ip_insertoptions

TCP首部 (20字节)

:在选项被复制后的 TCP报文段

mbuf{}

20字节 8字节 m_pktdat

16字节的以太网首部和 56字节的IP选项的空间

72字节

m_data IP首部 (20字节) UDP首部(8字节)

图9-31 函数ip_insertoptions

:UDP报文段

214

计计TCP/IP详解 卷 2:实现

如果被插入的选项占据了多于 1 6的字节数,则第 3 6 7行的测试为真,并调用 M G E T H D R分 配另一个mbuf。图9-30显示了选项被复制到新的 mbuf后,该缓存的结构。 367-378

如果分组首部被存放在一簇,或者第一个缓存中没有多余选项的空间,则

i p _ i n s e r t o p t i o n s分配一个新的分组首部 m b u f,初始化它的长度,从旧的缓存中把该 I P 首部截取下来,并把该首部从旧缓存中移动到新缓存中。 如23.6节中所述, UDP使用M _ P R E P E N D把UDP和IP首部放置到缓存的最后,与数据分离。 如图9 - 3 1所示。因为首部是放在缓存的最后,所以在缓存中总有空间存放选项,对 U D P来说, 第367行的条件总为假。 379-384

如果分组在缓存数据区的开始部分有存放选项的空间,则修改 m _ d a t a和m _ l e n,

以包含 o p t l e n更多的字节。并且当前的 I P首部被o v b c o p y(能够处理源站和目的站的重叠问 题)移走,为选项腾出位置。 385-390

i p _ i n s e r t o p t i o n s现在可以把i p o p t i o n结构的成员 i p o p t _ l i s t直接复制

到紧接在 I P 首部后面的缓存中。把新的首部长度存放在

* p h l e n 中,修改数据报长度

(ip_len),并返回一个指向分组首部缓存的指针。

9.9 ip_pcbopts函数 函数i p _ p c b o p t s把I P选项表及 I P _ O P T I O N S插口选项转换成 i p _ o u t p u t希望的格式: ipoption结构。如图 9-32所示。

图9-32 函数ip_pcbopts

215

第 9章 IP选项处理计计

图9-32 (续)

216

计计TCP/IP详解 卷 2:实现

559-562

第一个参数, p c b o p t引用指向当前选项表的指针。然后该函数用一个指向新的

选项表的指针来代替该指针,这个新选项表是由第二个参数 m指向的缓存链所指定的选项构造 而来。该过程所准备的选项表,将被包含在 I P _ O P T I O N S插口选项中,除了 L S R R和S S R R选 项的格式外,看起来象一个标准的 I P选项表。对这些选项,第一跳目的地址是作为路由的第 一个地址出现的。图 9 - 1 4显示,在外出的分组中,第一跳目的地址是作为目的地址出现的, 而不是路由的第一个地址。 1. 扔掉以前的选项 563-580

所有被m _ f r e e和* p c b o p t扔掉的选项都被清除。如果该过程传来一个空缓存或

者根本不传递缓存,则该函数不安装任何新的选项,并立即返回。 如果新选项表没有填充到 4 bit 的边界,则 i p _ p c b o p t s跳到 b a d,扔掉该表,并返回 EINVAL。 该函数的其余部分重新安排该表,使其看起来象一个 i p o p t i o n结构。图9-33显示了这个 过程。 松源路由

第1个

第2个

第3个

最后一个

第1个

第2个

第3个

第2个

第3个

其他选项

最后一个

最后一个

其他选项

其他选项

图9-33 ip_pcbopts 选项表处理

2. 为第一跳目的地腾出位置 581-592

如果缓存中没有位置,则把所有的数据都向缓存的末尾移动

4个字节 (是一个

in_addr结构的大小 )。ovbcopy完成复制。 bzero清除缓存开始的 4个字节。 3. 扫描选项表 59 3 - 6 0 6

f o r循环扫描选项表,寻找 L S R R和S S R R选项。对多字节选项,该循环也验证选

项的长度是否合理。 4. 重新安排LSRR和SSRR选项 607-638

当该循环找到一个 L S R R或S S R R选项时,它把缓存的大小、循环变量和选项长度

减去4,因为选项的每一个地址将被移走,并被移到缓存的前面。 b c o p y把第一个地址移走, o v b c o p y把选项的其他部分移动 4个字节,来填补第一个地

217

第 9章 IP选项处理计计

址留下的空隙。 5. 清除 639-646

循环结束后,选项表的大小(包括第一跳地址)必须不超过44 (MAX_IPOPTLEN

+4)字

节。更长的选项表无法放入 IP分组的首部。该表被保存在 *pcbopt中,函数返回。

9.10 一些限制 除了管理和诊断工具生成的 I P数据报外,很少出现选项。卷 1讨论了两个最常用的工具, p i n g和t r a c e r o u t e。使用I P选项的应用程序很难写。编程接口的文档很少,也没有很好地 标准化。许多厂商提供的应用程序,比如 Te l n e t和F T P,并没有为用户提供方法,来指定如源 路由等的选项。 在大的互联网上,记录路由、时间戳和源路由选项的用途被 I P首部的最大长度所限制。 许多路由含有的跳数远多于 4 0选项字节所能表示的。当多选项在同一分组中出现时,所能得 到的空间是几乎没有用的。 IPv6用一个更为灵活的选项首部设计强调了这个问题。 在分片过程中, I P只把某些选项复制到非初始片上,因为重组时会扔掉非初始片上的选 项。在目的主机上,运输层协议只能用到初始片上的选项 ( 1 0 . 6节)。但有些选项,如源路由, 即使在目的主机上,被非初始片丢弃,依然必须被复制到每个分片。

9.11 小结 本章中我们显示了 I P选项的格式和处理过程。我们没有讨论安全和流 I D选项,因为 N e t / 3 没有实现它们。 我们看到,多字节选项的大小是由源主机在构造它们时确定的。最大选项首部长度只有 40字节,这严格限制了 IP选项的使用。 源路由选项要求最多的支持。到达的源路由被 s a v e _ r t e保存,并保留在 i p _ s r c r o u t e 中。通常不转发分组的主机可能转发源路由选择的分组,但是 RFC 11 2 2默认要求不允许这种 功能。Net/3没有对这种特性的判断,总是转发源路由选择的分组。 最后,我们看到 ip_insertoptions是如何把选项插入到一个外出的分组中去的。

习题 9.1 如果一个分组中有两个不同的源路由选项会发生什么情况? 9.2 一些商用路由器可以被配置成按照分组的 I P目的地址扔掉它们。通过种方式,可以 把一台或一组主机通过路由器隔离在更大的互联网之外。请描述源路由选择的分组 如何绕过这个机制。假定网络中至少有一个主机,路由器没有阻塞,并转发源路由 选择的数据报。 9.3 某些主机可能没有被配置成默认路由。这样主机就无法路由选择到其他与它直接相 连的网络。请描述源路由如何与这种类型的主机通信。 9.4 为什么NOP采用如图9-16所示的ip_srcrt结构? 9.5 时间戳选项中非标准时间值会和标准时间值混淆吗? 9.6 i p _ d o o p t i o n s在处理其他选项之前要把分组的目的地址保存在 d e s t中(图9 - 8 )。 为什么?

第10章 IP的分片与重装 10.1 引言 我们将第8章的IP的分片与重装处理问题推迟到本章来讨论。 I P具有一种重要功能,就是当分组过大而不适合在所选硬件接口上发送时,能够对分组 进行分片。过大的分组被分成两个或多个大小适合在所选定网络上发送的 I P分片。而在去目 的主机的路途中,分片还可能被中间的路由器继续分片。因此,在目的主机上,一个 I P数据 报可能放在一个 I P分组内,或者,如果在发送时被分片,就放在多个 I P分组内。因为各个分 片可能以不同的路径到达目的主机,所以只有目的主机才有机会看到所有分片。因此,也只 有目的主机才能把所有分片重装成一个完整的数据报,提交给合适的运输层协议。 图8 - 5显示在被接收的分组中, 0.3%(72 786/27 881 978) 是分片, 0.12% (264 484/ (29 447 726-796 084)) 的数据报是被分片后发送的。在 w o r l d . s t d . c o m上,被接收分组的 9.5%是被分片的。world有更多的NFS活动,这是 IP分片的主要来源。 I P首部内有三个字段实现分片和重装:标识字段 (i p _ i d)、标志字段 (i p _ o f f的3个高位 比特)和偏移字段 (ip_off的13个低位比特 )。标志字段由三个 1 bit标志组成。比特 0是保留的, 必须为 0;比特 1是“不分片” ( D F )标志;比特 2是“更多分片” ( M F )标志。 N e t / 3中,标志和 偏移字段结合起来,由 ip_off访问,如图 10-1所示。 分片偏移 13比特

图10-1 ip_off 控制IP分组的分片

Net/3通过用IP_DF和 IP_MF掩去i p _ o f f来访问DF和MF。IP实现必须允许应用程序请求 在输出的数据报中设置 DF比特。 当使用UDP或TCP时,Net/3并不提供对 DF比特的应用程序级的控制。 进程可以用原始 I P接口(第3 2章)构造和发送它自己的 I P首部。运输层必须直接设 置DF比特。例如,当 TCP运行“路径 MTU发现(path MTU discovery)”时。 i p _ o f f的其他13 bit指出在原始数据报内分片的位置,以 8字节为单元计算。因而,除最 后一个分片外,其他每个分片都希望是一个 8字节倍数的数据,从而使后面的分片从 8字节边 界开始。图 1 0 - 2显示了在原始数据报内的字节偏移关系,以及在分片的 I P首部内分片的偏移 (ip_off的低位13 bit)。 图1 0 - 2显示了把一个最大的 I P数据报分成 8 1 9 0个分片,除最后一个分片包含 3个字节外, 其他每个分片都包含 8个字节。图中还显示,除最后一个分片外,设置了其余分片的 M F比特。 这是一个不太理想的例子,但它说明了一些实现中存在的问题。

219

第 10章 IP的分片与重装计计

最大数据报

IP首部 20字节

8字节

8字节

8字节

8字节

IP首部 20字节

3 字节

8字节 IP首部 8字节

20字节 IP首部 20字节

8字节

IP首部 20字节

8字节 IP首部 20字节

3字节

图10-2 65535字节的数据报的分片

原始数据报上面的数字是该数据部分在数据报内的字节偏移。分片偏移 (i p _ o f f)是从数 据报的数据部分开始计算的。分片不可能含有偏移超过 65514的字节,因为如果这样的话,重 装的数据报会大于 65535字节—这是ip_len字段的最大值。这就限制了 ip_off的最大值为 8189(8189×8=65512),只为最后一个分片留下 3字节空间。如果有 IP选项,则偏移还要小些。 因为I P互联网是无连接的,所以,在目的主机上,来自一个数据报的分片必然会与来自 其他数据报的分片交错。 i p _ i d唯一地标识某个特定数据报的分片。源系统用相同的源地址 (i p _ s r c)、目的地址 (i p _ d s t)和协议 (i p _ p)值,作为数据报在互联网上生命期的值,把每 个数据报的 ip_id设置成一个唯一的值。 总而言之,ip_id标识了特定数据报的分片,ip_off确定了分片在原始数据报内的位置, 除最后一个分片外, MF标识每个分片。

10.2 代码介绍 重装数据结构出现在一个头文件里。两个 C文件中有重装和分片处理的代码。这三个文件 列在图10-3中。 文



netinet/ip_var.h netinet/ip_output.c netinet/ip_input.c





重装数据结构 分片代码 重装代码

图10-3 本章讨论的文件

220

计计TCP/IP详解 卷 2:实现

10.2.1 全局变量 本章中只有一个全局变量, ipq。如图10-4所示。 变



ipq







struct ipq *



重装表

图10-4 本章介绍的全局变量

10.2.2 统计量 分片和重装代码修改的统计量如图 1 0 - 5所示。它们是图 8 - 4的i p s t a t结构中所包含统计 量的子集。 描

ip s t a t成员 ips_cantfrag ips_odropped ips_ofragments ips_fragmented



要求分片但被 DF比特禁止而没有发送的数据报数 因为内存不够而被丢弃的分组数 被发送的分片数 为输出分片的分组数

图10-5 本章收集的统计量

10.3 分片 我们现在返回到 i p _ o u t p u t,分析分片代码。记得在图 8 - 2 5中,如果分组正好适合选定 出接口的 M T U,就在一个链路级帧中发送它。否则,必须对分组分片,并在多个帧中将其发 送。分组可以是一个完整的数据报或者它自己也是前边系统创建的分片。我们分三个部分讨 论分片代码: • 确定分片大小(图10-6); • 构造分片表 (图10-7);以及 • 构造第一个分片并发送分片 (图10-8)。

图10-6 函数ip_output :确定分片大小

253-261

分片算法很简单,但由于对 mbuf结构和链的操作使实现非常复杂。如果 DF比特禁

221

第 10章 IP的分片与重装计计

止分片,则 i p _ o u t p u t丢弃该分组,并返回 E M S G S I Z E。如果该数据报是在本地生成的,则 运输层协议把该错误传回该进程;但如果分组是被转发的,则 i p _ f o r w a r d生成一个ICMP目 的地不可达差错报文,并指出不分片就无法转发该分组 (图8-21)。 N e t / 3没有实现“路径 M T U发现”算法,该算法用来搜索到目的主机的路径,并发现所有 中间网络支持的最大传送单元。卷 1的11.8节和24.2节讨论了UDP和TCP的路径MTU发现。 每个分片中的数据字节数, l e n的计算是用接口的 M T U减去分组首部的长度后,舍 去低位的 3个比特 ( & ~7 )。后成为 8字节倍数。如果 M T U太小,使每个分片中无法含有 8字节的

262-266

数据,则ip_output返回EMSGSIZE。 每个新的分片中都包含:一个 IP首部、某些原始分组中的选项以及最多 len长度的数据。 图1 0 - 7中的代码,以一个 C的复合语句开始,构造了从第 2个分片开始的分片表。在表生 成后(图10-8),原来的分组被转换成第一个分片。

图10-7 函数ip_output :构造分片表

222

计计TCP/IP详解 卷 2:实现

图10-7 (续)

图10-8 函数ip_output :发送分片

267-269 外部块允许在函数中离使用点更近一点的地方定义 mhlen、firstlen和mnext。 这些变量的范围一直到块的末尾,它们隐藏其他在块外定义的有相同名字的变量。 270-276

因为原来的缓存链现在成了第一个分片,所以 f o r循环从第 2个分片的偏移开始:

hlen+len。对每个分片, ip_output采取以下动作: • 277-284

分配一个新的分组缓存,调整 m _ d a t a指针,为一个 1 6字节链路层首部

(m a x _ l i n k h d r)腾出空间。如果 i p _ o u t p u t不这么做,则网络接口驱动器就必须再分 配一个mbuf来存放链路层首部或移动数据。两种工作都很耗时,在这里就很容易避免。 • 285-290

从原来的分组中把 IP首部和IP选项复制到新的分组中。前者复制在一个结构

中。ip_optcopy只复制那些将被复制到每个分片中的选项 (10.4节)。 • 291-297

设置分片包括 M F比特的偏移字段 (i p _ o f f)。如果原来分组中已设置了 M F

比特,则在所有分片中都把 M F置位。如果原来分组中没有设置 M F比特,则除了最后一 个分片外,其他所有分片中的 MF都置位。 • 298

为分片设置长度,解决首部小一些 (i p _ o p t c o p y可能没有复制所有选项 ),以及

最后一个分片的数据区小一些的问题。以网络字节序存储长度。

223

第 10章 IP的分片与重装计计

• 2 9 9 - 3 0 5 从原始分组中把数据复制到该分片中。如果必要, m _ c o p y会再分配一个 mbuf。如果m_copy失败,则发出ENOBUFS。sendorfree丢弃所有已被分配的缓存。 • 3 0 6 - 3 1 4 调整新创建的分片的 m b u f分组首部,使其具有正确的全长。把新分片的接 口指针清零,把 i p _ o f f转换成网络字节序,计算新分片的检验和。通过 m _ n e x t p k t 把该分片与前面的分片链接起来。 在图10-8中,ip_output构造了第一个分片,并把每个分片传递到接口层。 315-325 把末尾多余的数据截断后,原来的分组就被转换成第一个分片,同时设置 MF比特, 把i p _ l e n和i p _ o f f转换成网络字节序,计算新的检验和。在这个分片中,保留所有的 I P选 项。在目的主机重装时,只保留数据报的第一个分片的 I P选项(图1 0 - 2 8 )。某些选项,如源路 由选项,必须被复制到每个分片中,即使在重装时都被丢弃了。 326-338 此时,ip_output可能有一个完整的分片表,或者已经产生了错误,都必须把生 成的那部分分片表丢弃。 f o r循环遍历该表,发送分片或者由于 e r r o r而丢弃分片。在发送 期间遇到的所有错误都会使后面的分片被丢弃。

10.4 ip_optcopy函数 在分片过程中, i p _ o p t c o p y(图1 0 - 9 )复制到达分组 (如果分组是被转发的 )或者原始数据 报中(如果该数据报是在本地生成的 )中的选项到外出的分片中。

图10-9 函数:确定分片大小

224

计计TCP/IP详解 卷 2:实现

395-422

ip_optcopy的参数是: ip,一个指向外出分组的 IP首部的指针;jp,一个指向

新生成的分片的 IP首部的指针; i p _ o p t c o p y初始化c p和d p指向每个分组的第一个选项,并 在处理每个选项时把 c p和d p向前移动。第一个 f o r循环在每次重复时复制一个选项,当它遇 到EOL选项或已经检查完所有选项时。 NOP选项被复制,用来维持后继选项的对齐限制。 Net/2版本废除了 NOP选项。 如果I P O P T _ C O P I E D指示c o p i e d比特被置位,则 i p _ o p t c o p y把选项复制到新片中。图 9 - 5 显示了哪些选项的 c o p i e d比特是被置位的。如果某个选项的长度太大,就被截断; ip_dooptions应该已经发现这种错误了。 423-426

第2个for循环把选项表填充到 4字节的边界。由于分组首部长度 (ip_hlen)是以4

字节为单位计算的,所以需要这个操作。这也保证了后面跟着的运输层首部与 4字节边界对齐。 这样会提高性能,因为在许多运输层协议的设计中,如果运输层首部从一个 32 bit 边界开始, 那么32 bit首部字段将按照 32 bit边界对齐。在某些机器上, C P U访问32 bit对齐的字有困难, 这时,这种字节安排就提高了 CPU的性能。 图10-10显示了ip_optcopy的运行。 时间戳选项

IP首部 20字节

12字节

IP首部

LSRR选项

20字节

11字节

LSRR选项 11字节

表尾选项

图10-10 在分片中并不复制所有选项

在图1 0 - 1 0中,我们看到 i p _ o p t c o p y不复制时间戳选项 (它的c o p i e d比特为0 ),但却复制 L S R R选项(它的c o p i e d比特为1 )。为了把新选项与 4字节边界对齐, i p _ o p t c o p y也增加了一 个EOL选项。

10.5 重装 到目前为止,我们已经讨论了数据报 (或片)的分片,现在再回到 i p i n t r讨论重装过程。 在图8 - 1 5中,我们把 i p i n t r中的重装代码省略了,并推迟对它的讨论。 i p i n t r可以把数据 报整个地交给运输层处理。 i p i n t r接收的分片被传给 i p _ r e a s s,由它尝试把分片重装成一 个完整的数据报。图 10-11显示了ipintr的代码。 271-279

我们知道 i p _ o f f包含D F比特、M F比特以及分片偏移。如果 M F比特或分片偏移

非零,则 D F就被掩盖掉了,分组就是一个必须被重装的分片。如果两者都为零,则分组就是 一个完整的数据报。跳过重装代码,执行图 1 0 - 11中最后的 e l s e语句,它从全部数据报长度 中排除了首部长度。 280-286

m _ p u l l u p把位于外部簇上的数据移动到 mbuf的数据区。我们记得,如果一个缓

存区无法容纳外部簇上的一个 I P 分组,则 S L I P 接口 ( 5 . 3 节 ) 可能会把该分组整个返回。 m _ d e v g e t 也会全部返回外部簇上的某个 I P分组 ( 2 . 6节)。在 m t o d宏( 2 . 6节)开始工作之前,

225

第 10章 IP的分片与重装计计

m_pullup必须把IP首部从外部簇上移到 mbuf的数据区中去。

图10-11 函数ipintr :分片处理

226

计计TCP/IP详解 卷 2:实现

287-297

Net/3在一个全局双向链表 i p q上记录不完整的数据报。这个名字可能容易产生误

解,因为这个数据结构并不是一个队列。也就是说,可以在表的任何地方插入和删除,并不 限制一定要在末尾。我们将用名词“表 (list)”来强调这个事实。 i p i n t r对表进行线性搜索,为当前分片找到合适的数据报。记住分片是由 4元组{ i p _ i d、 i p _ s r c、i p _ d s t和i p _ p }唯一标识的。 i p q的每个入口是一个分片表,如果 i p i n t r找到一个匹 配,则fp指向匹配的表。 N e t / 3采用线性搜索来访问它的许多数据结构。尽管简单,但是当主机支持大量 网络连接时,这种方法就成为瓶颈。 298-303 在found语句,ipintr为方便重装,修改了分组: • 304

i p i n t r修改i p _ l e n,从中减去标准 I P首部和任何选项。我们必须牢记这一点,

以免混淆对标准 i p _ l e n解释的理解。标准 i p _ l e n中包含了标准首部、选项和数据。 如果跳过重装代码, ip_len也会被改变,因为这个分组不是一个分片。 • 305-307

i p i n t r把MF标志复制到 i p f _ m f f的低位,把 i p _ t o s覆盖掉(&=~1只清除

低位)。注意,在 i p f _ m f f成为一个有效成员之前,必须把 i p指一个 i p a s f r a g结构。 10.6节和图10-14描述了ipasfrag结构。 尽管 RFC 1 1 2 2要求 I P层提供某种机制,允许运输层为每个外出的数据报设置 i p _ t o s比特。但它只推荐,在目的主机, I P层把i p _ t o s值传给运输层。因为 TO S 字段的低位字节必须总是 0,所以当重装算法使用 i p _ o f f(通常在这里找到 M F比特) 时,可以得到MF比特。 现在,可以把 i p _ o f f作为一个 16 bit偏移,而不是 3个标志比特和一个 13 bit偏移来访问 了。 • 308 用8乘ip_off,把它从以 8字节为单元转换成以 1字节为单元。 i p f _ m f f和i p _ o f f决定i p i n t r是否应该重装。图 1 0 - 1 2描述了不同的情况及相应的动 作,其中fp指向的是系统以前为该数据报接收的分片表。许多工作是由 ip_reass做的。 309-322

如果i p _ r e a s s通过把当前分片与以前收到的分片组合在一起,能重装成一个完

整的数据报,它就返回指向该重装好的数据报的指针。如果没有重装好,则 i p _ r e a s s保存 该分片, ipintr跳到next去处理下一个分片 (图8-12)。 ip_off 0 0 任意 任意 非零 非零

ipf_mff 假 假 真 真 假 假

fp 空 非空 空 非空 空 非空





完整数据报 完整数据报 新数据报的分片 不完整新数据报的分片 新数据报的末尾分片 不完整新数据报的末尾分片





没有要求重装 丢弃前面的分片 用这个分片初始化新的分片表 插入到已有的分片表中,尝试重装 初始化新的分片表 插入到已有的分片表中,尝试重装

图10-12 ipintr 和ip_reass 中的IP分片处理

323-324

当到达一个完整的数据报时,就选择这个 e l s e分支,并按照前面的叙述修改

ip_hlen。这是一个普通的流,因为收到的大多数数据报都不是分片。 如果重装处理产生一个完整的数据报, i p i n t r就把这个完整的数据报上传给合适的运输

227

第 10章 IP的分片与重装计计

层协议(图8-15): (*inetsw[ip_protox[ip->ip_p]].pr_input)(m,hlen);

10.6 ip_reass函数 i p i n t r把一个要处理的分片和一个指针传给 i p _ r e a s s,其中指针指向的是 i p q中匹 配的重装首部。 i p _ r e a s s可能重装成功并返回一个完整的数据报,可能把该分片链接到数 据报的重装链表上,等待其他分片到达后重装。每个重装链表的表头是一个 i p q结构,如图 10-13所示。

图10-13 ipq 结构

52-60

用来标识一个数据报分片的四个字段, i p _ i d、i p _ s r c、i p _ d s t和i p _ p,被保

存在每个重装链表表头的 i p q结构中。 N e t / 3用n e x t和p r e v构造数据报链表,用 i p q _ n e x t 和ipq_prev构造分片的链表。 到达分组的 IP首部在被放在重装链表之前,首先被转换成一个 ipasfrag结构(图10-14)。

图10-14 ipasfrag 结构

66-86

ip_reass在一个由 ipf_next和ipf_prev链接起来的双向循环链表上,收集某个

数据报的分片。这些指针覆盖了 I P首部的源地址和目的地址。 i p f _ m f f成员覆盖 i p结构中的

228

计计TCP/IP详解 卷 2:实现

ip_tos。其他成员是相同的。 图10-15显示了分片首部链表 (ipq)和分片(ipasfrag)之间的关系。

重装表表头; 没有分片链接 到这个结构上

分片表,按分片偏移的顺序

收到的某个数 据报的所有分 片

收到的某个数 据报的所有分 片

图10-15 分片首部链表 ipq 和分片

图1 0 - 1 5的左下部是重装首部的链表。表中第一个节点是全局 i p q结构, i p q。它永远不 会有自己的相关分片表。 i p q表是一个双向链表,用于支持快速插入和删除。 n e x t和p r e v 指针指向前一个和后一个 ipq结构,用终止结构的角上的箭头表示。 图1 0 - 1 5仍然没有显示重装结构的所有复杂性。重装代码很难跟踪,因为它完全依靠把指 针指向底层 m b u f上的三个不同的结构。我们已经接触过这个技术了,例如,当一个 i p结构覆 盖某个缓存的数据部分时。 图10-16显示了mbuf、ipq结构、ipasfrag结构和ip结构之间的关系。 图10-16中含有大量信息: • 所有结构都放在一个 mbuf的数据区内。 • i p q链表由 n e x t和p r e v链接起来的 i p q结构组成。每个 i p q结构保存了唯一标识一个 IP数据报的四个字段 (图10-16中的阴影部分)。 • 当作为分片链表的头访问时,每个 i p q结构被看成是一个 i p a s f r a g结构。这些分片由 ipf_next和ipf_prev链接起来,分别覆盖了ipq 结构的ipq_next和ipq_prev成员。 • 每个i p a s f r a g结构都覆盖了到达分片的 i p结构,与分片一起到达的数据在缓存中跟在 该结构之后。ipasfrag结构的阴影部分的成员的含义与其在 ip结构中不太相同。

229

第 10章 IP的分片与重装计计

图10-16 可通过多种结构访问的一段内存区

图10-15显示了这些重装结构之间的物理连接,图10-16显示了ip_reass使用的覆盖技术。 图 1 0 - 1 7 从逻辑的观点说明重装结构:该图显示了三个数据报的重装,以及

i p q 链表和

ipasfrag结构之间的关系。

图10-17 三个IP数据报的重装

每个重装链表的表头包含原始数据报的标识符、协议、源和目的地址。图中只显示了 i p _ i d字段。分片表通过偏移字段排序,如果 M F比特被置位,则用 M F标志分片,缺少的分 片出现在阴影里。每个分片内的数字显示了该分片的开始和结束字节相对于原始数据报数据 区的相对偏移,而不是相对于原始数据报的 IP首部。 这个例子是用来说明三个没有 IP选项的UDP数据报,其中每个数据报都有 1024字节的数据。 每个数据报的全长是 1052(20+8+1024)字节,正好适合 1500字节以太网 MTU。在到目的主机的 途中,这些数据报会遇到一个 S L I P链路,该链路上的路由器对数据报分片,使其大小适于放 在典型的 2 9 6字节的 SLIP MTU 中。每个数据报分 4个分片到达。第 1个分片中包含一个标准的 20字节IP首部,8字节UDP首部和264字节数据。第 2个和第3个分片中包含一个20字节IP首部和 272字节数据。最后一个分片中有一个 20字节首部和216字节数据(1032=272×3+216)。 在图 1 0 - 1 7中,数据报 5缺少一个包含 2 7 2 ~ 5 4 3字节的分片。数据报 6缺少第一个分片,

230

计计TCP/IP详解 卷 2:实现

0~271字节,以及最后一个从偏移 816开始的分片。数据报 7缺少前三个分片, 0~815。 图1 0 - 1 8列出了 i p _ r e a s s。前面讲到,当目的地是本主机的某个 I P分片到达时,在处理 完所有选项后, ipintr会调用ip_reass。

图10-18 函数ip_reass :数据报重装

343-358 当调用ip_reass时,ip指向分片fp或者指向匹配的 ipq结构或者为空。 因为重装只涉及每个分片的数据部分,所以 i p _ r e a s s调整含有该分片的 m b u f的m _ d a t a和 m_len,减去每个分片的 IP首部。 465-469

在重装过程中,如果产生错误,该函数就跳到 d r o p f r a g。d r o p f r a g 增加

ips_fragdropped,丢弃该分片,并返回一个空指针。 在运输层丢弃分片通常会严重降低性能,因为必须重传整个数据报。 T C P谨慎地避免分 片,但是 U D P应用程序必须采取步骤以避免对自己分片。 [ K e n t和Mogul 1987] 解释了为什么 应该避免分片。 所有I P实现必须能够重装最多 5 7 6字节的数据报。没有通用的方法来确定远程主机能重装 的最大数据报的大小。我们将在 27.5节中看到TCP提供了一个机制,可以确定远程主机所能处 理的最大数据报的大小。 UDP没有这样的机制,所以许多基于 UDP的协议(例如,RIP、TFTP、 BOOTP、SNMP和DNS),都限制在 576字节左右。 我们将分7个部分显示重装代码,从图 10-19开始。

231

第 10章 IP的分片与重装计计

图10-19 函数ip_reass :创建重装表

1. 创建重装表 当f p为空时, i p _ r e a s s用新的数据报的第一个分片创建一个重装表。它分配一

359-366

个mbuf来存放新表的头 (一个ipq结构),并调用insque,把该结构插入到重装表的链表中。 图10-20列出了操作数据报和分片链表的函数。 N e t / 3的3 8 6版本是在 m a c h d e p . c文件中定义 i n s q u e和r e m q u e函数的。每台机器都 有自己的文件,在其中定义核心函数,通常是为提高性能。该文件也包括与机器体 系结构有关的函数,包括中断处理支持、 CPU和设备配置以及内存管理函数。 i n s q u e和r e m q u e的存在主要是为了维护内核执行队列。 N e t / 3可把它们用于数 据报重装链表,因为链表具有下一个和前一个两个指针,分别作为各自节点结构的 前两个成员。对任何类型结构的链表,这些函数同样可以用,尽管编译器可能会发 出一些警告。这也是另一个通过两个不同结构访问内存的例子。 在所有内核结构里,下一个指针通常位于前一个指针的前面 (例如,图 1 0 - 1 4 )。 这是因为 i n s q u e和r e m q u e最早是在 VA X上用i n s q u e和r e m q u e硬件指令实现的, 这些指令要求前向和后向指针具有这种顺序。 分片表不是用 i p a s f r a g结构的前两个成员链接起来的 (图1 0 - 1 4 ),所以 N e t / 3调 用ip_deq和ip_enq而不是insque和remque。 函



insque Remque ip_enq ip_deq





紧接在prev后面插入node v o i d i n s q u e(void *node, void* prev) ; 把node从表中移走 v o i d r e m q u e(void *node) ; 紧接在分片 prev后面插入分片 p v o i d i p _ e n q( s t r u c t i p a s f r a gp,* S t r u c t i p a s f r a g p*rev) ; 移走分片p void i p _ d e q( s t r u c t i p a s f r a g p)*;

图10-20 ip_reass 采用的队列函数

232

计计TCP/IP详解 卷 2:实现

2. 重装超时 367

RFC 1122要求有生命期字段 (i p q _ t t l),并限制 Net/3等待分片以完成一个数据报的时

间。这与 IP首部的TTL字段是不同的, IP首部的TTL字段是为了限制分组在互联网中循环的时 间。重用 I P首部的 T T L字段作为重装超时的原因在于,一旦分片到达它的最终目的地,就不 再需要首部 TTL。 在Net/3中,重装超时的初始值设为 60 (I P F R A G T T L)。因为每次内核调用 i p _ s l o w t i m o 时,i p q _ t t l就减去1,而内核每 500 ms调用i p _ s l o w t i m o一次。如果系统在接收到数据报 的任一分片 3 0秒后,还没有组装好一个完整的 I P数据报,那么系统就丢弃该 I P重装链表。重 装定时器在链表被创建后的第一次调用 ip_slowtimo时开始计时。 RFC 1 122推荐重装时间在 60~120秒内,并且当收到数据报的第一个分片且定时器超时时, 向源主机发出一个 I C M P超时差错报文。重装后,总是丢弃其他分片的首部和选项,并且在 ICMP差错报文中必须包含出错数据报的前 64 bit(或者,如果该数据报短于 8字节,就可以少一 些)。所以,如果内核还没有接收到分片 0,它就不能发ICMP报文。 N e t / 3的定时器要短一些,并且当丢弃分片时, N e t / 3不发送 I C M P报文。要求返 回数据报的前 64 bit保证含有运输层首部的前端,这样就可以把差错报文返回给发生 错误的应用程序。注意,因为这个原因, TCP和UDP有意把它们的端口号放在首部的 前8个字节。 3. 数据报标识符 368-375

i p _ r e a s s在分配给该数据报的 i p q 结构中保存 i p _ p、i p _ i d、i p _ s r c和

i p _ d s t,让i p q _ n e x t和i p q _ p r e v指针指向该 i p q结构(也就是说,它用一个节点构造一个 循环链表 ),让q指向这个结构,并跳到 i n s e r t(图1 0 - 2 5 ),把第一个分片 i p插入到新的重装 表中去。 i p _ r e a s s的下一个部分 (图1 0 - 2 1 )是当f p不空,并已当前表中为新的分片找到正确位置 后执行的。

图10-21 函数ip_reass :在重装链表中找位置

376-381

因为f p不空,所以 f o r循环搜索数据报的分片链表,找到一个偏移大于 i p _ o f f

的分片。 在目的主机上,分片包含的字节范围可能会相互覆盖。发生这种情况的原因是,当一个 运输层协议重传某个数据报时,采用与原来数据报不同的路由;而且,分片的模式也可能不 同,这就导致在目的主机上的相互覆盖。传输协议必须能强制 I P使用原来的 I D字段,确保目 的主机识别出该数据报是重传的。 N e t / 3并不为运输层协议提供机制保证在重传的数据报中重用 IP ID字段。在准备

233

第 10章 IP的分片与重装计计

新数据报时, i p _ o u t p u t通过增加全局整数 i p _ i d来赋一个新值 (图8 - 2 2 )。尽管如 此,Net/3系统也能从让运输层用相同 ID字段重传IP数据报的系统上接收重叠的分片。 图1 0 - 2 2说明分片可能会以不同的方式与已经到达的分片重叠。分片是按照它们到达目的 主机的顺序编号的。重装的分片在图 10-22底部显示,分片的阴影部分是被丢弃的多余字节。 在下面的讨论中,早到 (earlier)分片是指先前到达主机的分片。 分片1

分片2

分片5

分片3

分片7

分片4

分片6

重组装的数据报

图10-22 可能会在目的主机重叠的分片的字节范围

图10-23中代码截断或丢弃到达的分片。 382-396 ip_reass把新片中与早到分片末尾重叠的字节丢弃,截断重复的部分(图10-22中分 片5的前部 ),或者,如果新分片的所有字节已经在早先的分片中 (分片4 )出现,就丢弃整个新 分片(分片6)。

图10-23 函数ip_reass :截断到达分组

图10-24中的代码截断或丢弃已有的分片。 397-412

如果当前分片部分地与早到分片的前端部分重叠,就把早到分片中重复的数据截

掉(图10-22中分片2的前部)。丢弃所有与当前分片完全重叠的早到分片 (分片3)。 图10-25中,到达分片被插入到重装链表。 413-426 在截断后, ip_enq把该分片插入链表,并扫描整个链表,确定是否所有分片全 部到达。如果还缺少分片,或链表最后一个分片的 i p f _ m f f被置位,i p _ r e a s s就返回0,并 等待更多的分片。 当目前的分片完成一个数据报后,整个链表被图 10-26所示的代码转换成一个 mbuf链。 427-440

如果某个数据报的所有分片都被接收下来, w h i l e循环用m _ c a t把分片重新构造

234

计计TCP/IP详解 卷 2:实现

成数据报。

图10-24 函数ip_reass :截断已有分组

图10-25 函数ip_reass :插入分组

图10-26 函数ip_reass :重装数据报

图10-27显示了一个有三个分片的数据报的 mbuf和ipq结构之间的关系。

235

第 10章 IP的分片与重装计计

簇 分片1

簇 分片2

簇 分片3

图10-27 m_cat 重装缓存内的分片

图中最暗的区域是分组的数据部分,稍淡的阴影部分是 mbuf中未用的部分。有三个分片, 每个分片都被存放在一个有两个 m b u f的链上:一个分组首部和一个簇。每个分片的第一个缓 存上的 m _ d a t a指针指向分组数据,而不是分组的首部。因此,由 m _ c a t构造的缓存链只包 含分片的数据部分。 当一个分片含有多于208字节的数据时,情况通常是这样的 (2.6节)。缓存的“frag”部分是 分片的IP首部。由于图10-18中的代码,各缓存链第一个缓存的 m_data指针指向“opts”之后。 图1 0 - 2 8显示了用所有分片的缓存重装的数据报。注意,分片 2和3的I P部分和选项不在重 装的数据报里。 第一个分片的首部仍然被用作 i p a s f r a g结构。它被图 1 0 - 2 9中的代码恢复成一个有效的 IP数据报首部。 4. 重建数据报首部 441-456

ip_reass把ip指向链表的第一个分片,将 ipasfrag结构恢复成 ip结构:把数

据报长度恢复成 i p _ l e n ,源站地址恢复成 i p _ s r c ,目的地址恢复成 i p _ d s t ;并把 i p f _ m f f 的低位清零 (从图 1 0 - 1 4可以知道, i p a s f r a g 结构的 i p f _ m f f覆盖了 i p结构的 ipf_tos)。 i p _ r e a s s用r e m q u e把整个分组从重装链表中移走,丢弃链表头 i p q结构,调整第一个 缓存中的 m_len和m_data,将前面被隐藏起来的第一个分片的首部和选项包含进来。 5. 计算分组长度

236

计计TCP/IP详解 卷 2:实现







分片1的IP首部和选项

图10-28 重装的数据报

图10-29 函数ip_reass :数据报重装

457-464

此处的代码总被执行,因为数据报的第一个缓存总是一个分组首部。 f o r循环计

算缓存链中数据的字节数,并把值保存在 m_pkthdr.len中。 在选项类型字段中, copied比特的意义现在应该很明白了。因为目的主机只保留那些出现 在第一个分片中的选项,而且只有那些在分组去往目的主机的途中控制分组处理的选项才被 复制下来。不复制那些在传送过程中收集信息的选项,因为当分组在目的主机上被重装时, 所有收集的信息都被丢弃了。

237

第 10章 IP的分片与重装计计

10.7 ip_slowtimo函数 如7.4节所述, Net/3的各项协议可能指定每 500 ms调用一个函数。对 IP而言,这个函数是 ip_slowtimo,如图10-30所示,为重装链表上的分片计时。

图10-30 ip_slowtimo 函数

515-534

i p _ s l o w t i m o遍历部分数据报的链表,减少重装 T T L字段。当该字段减为 0时,

就调用i p _ f r e e f,把与该数据报相关的分片都丢弃。在 s p l n e t处运行i p _ s l o w t i m o,避 免到达分组修改链表。 ip_freef显示如图10-31。 470-486 ip_freef移走并释放链表上 fp指向的各分片,然后释放链表本身。

图10-31 ip_freef 函数

ip_drain函数 在图7 - 1 4中,我们讲到 I P把i p _ d r a i n定义成一个当内核需要更多内存时才调用的函数。

238

计计TCP/IP详解 卷 2:实现

这种情况通常发生在我们讨论过的 (图2-13)分配缓存时。ip_drain显示如图10-32。

图10-32 ip_drain 函数

538-545

I P释放内存的最简单办法就是丢弃重装链表上的所有 I P分片。对属于某个 T C P报

文段的分片, T C P最终会重传该数据。属于 U D P数据报的 I P分片就丢失了,基于 U D P的协议 必须在应用程序层处理这种情况。

10.8 小结 本章展示了当一个外出的数据报过大而不适于在选定网络上传送时, i p _ o u t p u t如何对 数据报分片。由于分片在向目的地传送的途中可能会被继续分片,也有可能走不同的路径, 所以只有目的主机才能组装原来的数据报。 ip_reass接收到达分片,并试图重装数据报。如果重装成功,就把数据报传回 ipintr, 然后提交给恰当的运输层协议。所有 IP实现必须能够重装最多 576字节的数据报。 Net/3的唯一 限制就是可以得到的 mbuf的个数。如果在一段合理的时间内,没有接收完数据报的所有分片, ip_slowtimo就丢弃不完整的数据报。

习题 10.1 修改i p _ s l o w t i m o,当它丢弃一个不完整数据报时 (图11 - 1 ),发出一个 I C M P超时 差错报文。 10.2 在分片的数据报中,各分片记录的路由可能互不相同。在目的主机上重装某个数据 报时,返回给运输层的哪一个路由? 10.3 画一个图说明图 10-17中ID为7的分片的ipq结构所涉及的mbuf和相关的分片链表。 10.4 [Auerbach 1994] 建议在对数据报分片后,应该首先传送最后的分片。如果接收系 统先收到最后的分片,它就可以利用偏移值为数据报分配一个大小合适的缓冲区。 请修改ip_output,首先发送最后的分片。 [Auerbach 1994] 注意到某些商用 T C P / I P实现如果先收到最后的分片,就会 出现崩溃。 10.5 用图 8 - 5中的统计回答下面的问题。什么是每个重装的数据报的平均分片数?当一 个外出的数据报被分片时,创建的平均分片数是多少? 10.6 如果ip_off的保留比特被置位,分组会发生什么情况?

第11章 ICMP:Internet控制报文协议 11.1 引言 ICMP在IP系统间传递差错和管理报文,是任何 IP实现必需和要求的组成部分。 ICMP的规 范见RFC 792 [Postel 1981b]。RFC 950 [Mogul和Postel 1985]和RFC 1256 [Deering 1991a]定义 了更多的ICMP报文类型。 RFC 792 [Braden 1989a]提供了重要的ICMP细节。 I C M P有自己的传输协议号( 1),允许 I C M P报文在 I P数据报内携带。应用程序可以直接从 第32章讨论的原始IP接口发送或接收 ICMP报文。 我们可把 I C M P 报文分成两类:差错和查询。查询报文是用一对请求和回答定义的。 I C M P差错报文通常包含了引起错误的 I P数据报的第一个分片的 I P首部(和选项),加上该分片 数据部分的前 8个字节。标准假定这 8个字节包含了该分组运输层首部的所有分用信息,这样 运输层协议可以向正确的进程提交 ICMP差错报文。 TCP和UDP端口号在它们首部的前 8个字节内出现。 图11-1显示了所有目前定义的 ICMP报文。双线上面的是 ICMP请求和回答报文;双线下面 的是ICMP差错报文。 描

type和co d e



ICMP_ECHO ICMP_ECHOREPLY

回显请求 回显回答

ICMP_TSAMP ICMP_TSTAMPREPLY

时间戳请求 时间戳回答

ICMP_MASKREQ ICMP_MASKREPLY

地址掩码请求 地址掩码回答

ICMP_IREQ ICMP_IREQREPLY

信息请求(过时的) 信息回答(过时的)

ICMP_ROUTERADVERT ICMP_ROUTESOLICIT

路由器通告 路由器请求

ICMP_REDIRECT ICMP_REDIRECT_NET ICMP_REDIRECT_HOST ICMP_REDIRECT_TOSNET ICMP_REDIRECT_TOSHOST 其他

有更好的路由 网络有更好的路由 主机有更好的路由 TOS和网络有更好的路由 TOS和主机有更好的路由 不识别码

ICMP_UNREACH ICMP_UNREACH_NET ICMP_UNREACH_HOST

目的主机不可达 网络不可达 主机不可达

图11-1 ICMP报文类型和代码

PRC_

PRC_REDIRECT_HOST PRC_REDIRECT_HOST PRC_REDIRECT_HOST PRC_REDIRECT_HOST

PRC_UNREACH_NET PRC_UNREACH_HOST

240

计计TCP/IP详解 卷 2:实现



type和c o d e



PRC_

ICMP_UNREACH_PROTOCOL

目的主机上协议不能用

ICMP_UNREACH_PORT

目的主机上端口没有被激活

PRC_UNREACH_PORT

ICMP_UNREACH_SRCFAIL

源路由失败

PRC_UNREACH_SRCFAIL

ICMP_UNREACH_NEEDFRAG

需要分片并设置 DF比特

PRC_MSGSIZE

ICMP_UNREACH_NET_UNKNOWN

目的网络未知

PRC_UNREACH_NET

ICMP_UNREACH_HOST_UNKNOWN

目的主机未知

PRC_UNREACH_HOST

ICMP_UNREACH_ISOLATED

源主机被隔离

PRC_UNREACH_HOST

ICMP_UNREACH_NET_PROHIB

从管理上禁止与目的网络通信 P R C _ U N R E A C H _ N E T

PRC_UNREACH_PROTOCOL

ICMP_UNREACH_HOST_PROHIB

从管理上禁止与目的主机通信 P R C _ U N R E A C H _ H O S T

ICMP_UNREACH_TOSNET

对服务类型,网络不可达

PRC_UNREACH_NET

ICMP_UNREACH_TOSHOST

对服务类型,主机不可达

PRC_UNREACH_HOST

13

用过滤从管理上禁止通信

14

主机优先违规

15

事实上优先切断

其他

不识别码

ICMP_TIMXCEED

超时

ICMP_TIMXCEED_INTRANS

传送过程中 IP生存期到期

PRC_TIMXCEED_INTRANS

ICMP_TIMXCEED_REASS

重装生存期到期

PRC_TIMXCEED_REASS

其他

不识别码

ICMP_PRRAMPROB

IP首部的问题

0

未指明首部差错

PRC_PARAMPROB

ICMP_PRRAMPROB_OPTABSENT

丢失需要的选项

PRC_PARAMPROB

其他

无效字节的字节偏移

ICMP_SOURCEQUENCH

要求放慢发送

其他

不识别类型

PRC_QUENCH

图11-1 (续)

图11-1和图11-2中含有大量信息: • P R C _栏显示了 N e t / 3处理的与协议无关的差错码( 11 . 6节)和 I C M P报文之间的映射。对请 求和回答,这一列是空的。因为在这种情况下不会产生差错。如果对一个 I C M P差错, 这一行为空,说明 Net/3不识别该码,并自动丢弃该差错报文。 • 图11-3显示了我们讨论图 11-2所列函数的位置。 • icmp_input栏是icmp_input为每个ICMP报文调用的函数。 • UDP 栏是为UDP插口处理ICMP报文的函数。 • T C P栏是为T C P插口处理 I C M P报文的函数。注意,是 t c p _ q u e n c h处理I C M P源站抑制 差错,而不是tcp_notify。 • 如果errno栏为空,内核不向进程报告 ICMP报文。 • 表的最后一行显示,在用于接收 I C M P报文的进程的接收点上,不识别的 I C M P报文被提 交给原来的 IP协议。 在Net/3中,ICMP是作为IP之上的一个运输层协议实现的,它不产生差错或请求;它代表

241

第11章 ICMP:Internet控制报文协议计计

其他协议格式化并发送报文。 I C M P传递到达的差错,并向适当的传输协议或等待 I C M P报文 的进程发出回答。另一方面, I C M P用一个合适的 I C M P回答响应大多数 I C M P请求。图 11 - 4对 此作了总结。 和

其他

其他

其他

其他 其他

图11-2 ICMP报文类型和代码 (续)

242

计计TCP/IP详解 卷 2:实现











为ICMP生成回答 更新IP路由表 向所有协议报告差错 向与插口有关的协议报告差错 进程不识别的 ICMP报文 向进程报告差错或忽略 放慢输出 向进程报告差错

icmp_reflect in_rtchange pfctlinput pr_ctlinput rip_input tcp_notify tcp_quench udp_notify



11.12节 图22-34 7.7节 7.4节 32.5节 图27-12 图27-13 图23-31

图11-3 ICMP输入处理时调用的函数 到

ICMP报文类型 请求 回答 差错 未知





向ICMP请求生成回答 传给原始 IP 传给传输协议和原始 IP 传给原始 IP



由某个进程生成 由内核生成 由IP或传输协议生成 由某个进程生成

图11-4 ICMP报文处理

11.2 代码介绍 图11-5的两个文件中有本章讨论的 ICMP数据结构、统计量和处理的程序。 文





netinet/ip_icmp.h netinet/ip_icmp.c



ICMP结构定义 ICMP处理

图11-5 本章定义的文件

11.2.1 全局变量 本章介绍的全局变量如图 11-6所示。 变



icmpmaskrepl icmpstat









int 使ICMP地址掩码回答的返回有效 s t r u c t i c m p s t a t ICMP统计量(图 11-7)

图11-6 本章介绍的全局变量

11.2.2 统计量 统计量是由图11-7所示的icmpstat结构的成员收集的。 icmpsta t成员 icps_oldicmp icps_oldshort





因为数据报是一个 ICMP报文而丢弃的差错数 因为IP数据报太短而丢弃的差错数

图11-7 本章收集的统计信息

SNMP使用的

243

第11章 ICMP:Internet控制报文协议计计



icmpsta t成员 icps_badcode icps_badlen icps_checksum icps_tooshort icps_outhist[] icps_inhist[] icps_error icps_reflect



SNMP使用的

由于无效码而丢弃的 ICMP报文数 由于无效的 ICMP体而丢弃的 ICMP报文数 由于坏的ICMP检验和而丢弃的 ICMP报文数 由于ICMP首部太短而丢弃的报文数 输出计数器数组 ;每种ICMP类型对应一个 输入计数器数组 ;每种ICMP类型对应一个 i c m p _ e r r o r的调用(重定向除外 )数 内核反映的 ICMP报文数

图11-7 (续)

在分析程序时,我们会看到计数器是递增的。 图11-8显示的是执行n e t s t a t -命 s 令输出的统计信息的例子。 输出

成员

图11-8 ICMP统计信息示例

11.2.3 SNMP变量 图11-9显示了SNMP ICMP组的变量与 Net/3收集的统计量之间的关系。 成员

SNMP变量





见正文 收到的ICMP报文数 由于错误丢弃的ICMP报文数

计数器

每一类收到的ICMP报文数

图11-9 ICMP组内的简单 SNMP变量

244

计计TCP/IP详解 卷 2:实现

见正文

发送的ICMP报文数 由于一个错误而没有发送的ICMP错误数

计数器

每一类发送的ICMP报文数

图11-9 (续)

i c m p I n M s g s是i c p s _ i n h i s t数组和 i c m p I n E r r o r s中的计数之和, i c m p O u t M s g s 是icps_outist数组和icmpOutErrors中的计数之和。

11.3 icmp结构 Net/3通过图11-10中的icmp结构访问某个ICMP报文。 42-45

i c m p _ t y p e标识特定报文, i c m p _ c o d e进一步指定该报文(图 11 - 1的第1栏)。计算

icmp_cksum的算法与IP首部检验和相同,保护整个 ICMP 报文(像IP一样,不仅仅保护首部)。 46-79

联合i c m p _ h u n(首部联合)和 i c m p _ d u n(数据联合)按照 i c m p _ t y p e和i c m p _ c o d e

访问多种 I C M P报文。每种 I C M P报文都使用 i c m p _ h u n;只有一部分报文用 i c m p _ d u n。没 有使用的字段必须设置为 0。 80-86

我们已经看到,利用其他嵌套的结构(例如 m b u f、l e _ s o f t c 和e t h e r _ a r p),

#define宏可以简化对结构成员的访问。 图11 - 11显示了I C M P报文的整体结构,并再次强调 I C M P报文是封装在 I P数据报里的。我 们将在分析程序时,分析所遇报文的特定结构。

图11-10 icmp 结构

245

第11章 ICMP:Internet控制报文协议计计

图11-10 (续) ICMP报文 依赖于type和 code的内容 1

1

2字节

IP首部 IP数据报

图11-11 一个ICMP报文(省略icmp_ )

11.4 ICMP 的protosw结构 i n e t s w [ 4 ](图11 - 1 3)的 p r o t o s w结构描述了 I C M P,并支持内核和进程对协议的访问。 图11 - 1 2显示了该结构。在内核里, i c m p _ i n p u t处理到达的 I C M P报文,进程产生的外出 ICMP报文由rip_output处理。以rip_开头的三个函数将在第 32章中讨论。 成



pr_type pr_domain



inetsw[4] SOCK_RAW &inetdomain



ICMP提供原始分组服务 ICMP是Internet域的一部分

图11-12 ICMP 的inetsw 项

246

计计TCP/IP详解 卷 2:实现







inetsw[4]

pr_protocol pr_flags pr_input pr_output pr_ctlinput pr_ctloutput pr_usrreq pr_init pr_fasttimo pr_slowtimo pr_drain pr_sysctl

IPPROTO_ICMP (1) PR_ATOMIC|PR_ADDR icmp_input rip_output 0 rip_ctloutput rip_usrreq 0 0 0 0 0



出现在IP首部的ip_p字段中 插口层标志, ICMP不使用 从IP层接收ICMP报文 将ICMP报文发送到 IP层 ICMP不使用 响应来自一个进程的管理请求 响应来自一个进程的通信请求 ICMP不使用 ICMP不使用 ICMP不使用 ICMP不使用 ICMP不使用

图11-12 (续)

图11-13 数值为1的ip_p 选择了inetsw[4]

11.5 输入处理: icmp_input函数 回想起ipintr对数据报进行分用是根据 IP首部中的传输协议编号 ip_p。对于ICMP报文, ip_p是1,并通过ip_protox选择inetsw[4]。 当一个ICMP报文到达时,IP 应用程序

层通过inetsw[4]的pr_input 函数,间接调用 i c m p _ i n p u t (图10-11)。

传输协议

我们将看到,在 i c m p _ i n p u t中,每一个I C M P报文要

ICMP差错

ICMP差错应答和未知报文

被处理 3次:被 i c m p _ i n p u t 处理一次;被与 I C M P差错报文

ICMP 应答

中的 I P分组相关联的传输协议 处理一次;被记录收到 I C M P报

ICMP 报文

文的进程处理一次。 I C M P输入 处理过程的总的构成情况如图 11-14所示。

图11-14 ICMP的输入处理过程

ICMP 输出处理 (图11-29)

247

第11章 ICMP:Internet控制报文协议计计

我们将在以下 5节( 11 . 6~11 . 1 0)讨论 i c m p _ i n p u t:(1) 验证收到的报文; (2) ICMP差错 报文;(3) ICMP请求报文; (4) ICMP重定向报文; (5) ICMP回答报文。 i c m p _ i n p u t函数的 第一部分如图11-15所示。

图11-15 icmp_input 函数

248

计计TCP/IP详解 卷 2:实现

图11-15 (续)

1. 静态结构 131-134

因为i c m p _ i n p u t是在中断时调用的,此时堆栈的大小是有限的。所以,为了在

每次调用 i c m p _ i n p u t时,避免动态分配造成的延迟,以及使堆栈最小,这 4个结构是动态分 配的。icmp_input把这4个结构用作临时变量。 i c m p s r c 的 命 名 容 易 引 起 误 解 ,因为 i c m p _ i n p u t 把 它用作临时 s o c k a d d r _ i n变量,而它也从未包含过源站地址。在 Net/2版本的i c m p _ i n p u t中, 在报文被 r a w _ i n p u t函数提交给原始 I P之前,报文的源站地址在函数的最后被复制 到i c m p s r c中。而 N e t / 3调用只需要一个指向该分组的指针的 r i p _ i n p u t,而不是 raw_input。虽然有这个改变,但是 icmpsrc仍然保留了在Net/2中的名字。 2. 确认报文 135-139

i c m p _ i n p u t希望收到的 I C M P报文( m)中含有一个指向该数据报的指针,以及该

数据报 I P首部的字节长度( h l e n)。图 11 - 1 6列出了几个在 i c m p _ i n p u t里用于简化检测无效 ICMP报文的常量。 常量/宏



ICMP_MINLEN ICMP_TSLEN ICMP_MASKLEN ICMP_ADVLENMIN

8 20 12 36

I CMP_ADVLEN(p)

36 + optsize





ICMP报文大小的最小值 ICMP时间戳报文大小 ICMP地址掩码报文大小 ICMP差错(建议)报文大小的最小值 (IP + ICMP + BADIP = 20 + 8 + 8 = 36) ICMP差错报文的大小,包含无效分组 p的IP选项的optsize字节

图11-16 ICMP引用的用来验证报文的常量

140-160

icmp_input从ip_len取出ICMP 报文的大小,并把它存放在 icmplen中。第8

章讲过, i p i n t r从i p _ l e n中排除了 I P首部的长度。如果报文长度太短,不是有效报文,就 生成 i c p s _ t o o s h o r t,并丢弃该报文。如果在第一个 m b u f中,ICMP 首部和I P首部不是连 续的,则 由m_pullup保证ICMP 首部以及封闭的 IP分组的 IP首部在同一个mbuf中。 3. 验证检验和 161-170

icmp_input隐藏mbuf中的IP首部,并用 in_cksum验证ICMP 的检验和。如果

报文被破坏,就增加 icps_checksum,并丢弃该报文。 4. 验证类型 171-175 如果报文类型(icmp_type)不在识别范围内, icmp_input就跳过switch执行

249

第11章 ICMP:Internet控制报文协议计计

r a w语句(图 11 - 9)。如果在识别范围内, i c m p _ i n p u t复制 i c m p _ c o d e,s w i t c h 按照 icmp_type处理该报文。 在ICMP s w i t c h语句处理完后, i c m p _ i n p u t向r i p _ i n p u t发送ICMP 报文,后者把 ICMP 报文发布给准备接收的进程。只有那些被破坏的报文(长度或检验和出错)以及只由内核 处理的 I C M P请求报文才不传给 r i p _ i n p u t。在这两种情况下, i c m p _ i n p u t都立即返回, 并跳过raw处的源程序。 5. 原始ICMP输入 317-325

i c m p _ i n p u t把到达的报文传给 r i p _ i n p u t,r i p _ i n p u t依据报文里含有的协

议及源站和目的站地址信息( 32章),把报文发布给正在监听的进程。 原始IP机制允许进程直接发送和接收 ICMP报文,这样做有几个原因: • 新ICMP 报文可由进程处理而无需修改内核(例如,路由器通告,图 11-28)。 • 可以用进程而无需用内核模块来实现发送

ICMP 请求和处理回答的机制( p i n g 和

traceroute)。 • 进程可以增加对报文的内核处理。与此类似,内核在更新完它的路由表后,会把 I C M P 重定向报文传给一个路由守护程序。

11.6 差错处理 我们首先考虑 I C M P差错报文。当主机发出的数据报无法成功地提交给目的主机时,它就 接收这些报文。目的主机或中间的路由器生成这些报文,并将它们返回到原来的系统。图 11-17 显示了多种 ICMP差错报文的格式。 不可达 超时 源抑制

(必须是0)

被破坏分组的IP首部

4字节 需要分片

被破坏分组的IP首部

(必须是0) 2字节

参数问题 1

1

2字节

2字节 (必须是0)

被破坏分组的IP首部

3字节

8字节

图11-17 ICMP差错报文 (省略icmp_ )

图11-18中的源程序来自图 11-15中的switch语句。

图11-18 icmp_input 函数:差错报文

250

计计TCP/IP详解 卷 2:实现

图11-18 (续)

176-216 对ICMP差错的处理是最少的,因为这主要是运输层协议的责任。 imcp_input把 i c m p _ t y p e和i c m p _ c o d e映射到一个与协议无关的差错码集上,该差错码是由 P R C _常量

251

第11章 ICMP:Internet控制报文协议计计

(图11 - 1 9 )表示的。 P R C _常量有一个隐含的顺序,正好与 I C M P的c o d e相对应。这就解释了为 什么code是按一个PRC_常量递增的。 217-225

如果识别出类型和码, i c m p _ i n p u t就跳到 d e l i v e r。如果没有识别出来,

icmp_input就跳到badcode。 如果对所报告的差错而言,报文长度不正确, icps_badlen的值就加1,并丢弃该报文。 N e t / 3总是丢弃无效的 I C M P报文,也不生成有关该无效报文的 I C M P差错。这样,就避免在两 个有缺陷的实现之间形成无限的差错报文序列。 226-231

i c m p _ i n p u t调用运输层协议的 p r _ c t l i n p u t函数,该函数根据原始数据报的

i p _ p,把到达分组分用到正确的协议,从而构造出原始的 I P数据报。差错码( c o d e)、原始 I P 数据报的目的地址( i c m p s r c )以及一个指向无效数据报的指针(

i c m p _ i p )被传给

pr_ctlinput(如果是为该协议定义的)。图 23-31和图27-12讨论这些差错。 232-234 最后,icps_badcode的值增加1,并终止switch语句的执行。 常







PRC_HOSTDEAD 主机似乎已关闭 PRC_IFDOWN 网络接口关闭 PRC_MSGSIZE 无效报文大小 PRC_PRRAMPROB 首部不正确 PRC_QUENCH 某人说要放慢 PRC_QUENCH2 阻塞比特要求放慢 PRC_REDIRECT_HOST 主机路由选择重定向 PRC_REDIRECT_NET 网络路由选择重定向 PRC_REDIRECT_TOSHOST TOS和主机的重定向 P R C _ R E D I R E C T _ T O S N E T TOS和网络的重定向 PRC_ROUTEDEAD 如果可能,选择新的路由 P R C _ T I M X C E E D _ I N T R A N S 传送过程中分组生命期到期 PRC_TIMXCEED_REASS 分片在重装过程中生命期到期 PRC_UNREACH_HOST 没有到主机的路由 PRC_UNREACH_NET 没有到网络的路由 PRC_UNREACH_PORT 目的主机称端口未激活 PRC_UNREACH_PROTOCOL 目的主机称协议不可用 PRC_UNREACH_SRCFAIL 源路由失败

图11-19 与协议无关的差错码

尽管P R C _常量表面上与协议无关,但它们主要还是基于 I n t e r n e t协议族。其结果 是,当某个 Internet协议族以外的协议把自己的差错映射到 P R C _常量时,会失去可指 定性。

11.7 请求处理 Net/3响应具有正确格式的 ICMP请求报文,但把无效 ICMP请求报文传给r i p _ i n p u t。第 32章讨论了应用程序如何生成 ICMP请求报文。 除路由器通告报文外,大多数 Net/3所接收的 ICMP请求报文都生成回答报文。为避免为回 答报文分配新的 m b u f,i c m p _ i n p u t把请求的缓存转换成回答的缓存,并返回给发送方。我

252

计计TCP/IP详解 卷 2:实现

们将分别讨论各个请求。 11.7.1 回显询问: ICMP_ECHO和ICMP_ECHOREPLY 尽管I C M P非常简单,但是 I C M P回显请求和回答却是网络管理员最有力的诊断工具。发 出I C M P回显请求称为“ p i n g”一个主机,也就是调用 p i n g程序一次。许多系统提供该程序 来手工发送 ICMP回显请求。卷1的第7章详细讨论了ping。 p i n g程序的名字依照了声纳脉冲 (soar ping),用其他物体对声纳脉冲的反射所产生的回 声确定它们的位置。卷 1把这个名字解释成 Packet InterNet Groper,是不正确的。 图11-20是ICMP回显请求和回答报文的结构。

检验和 8字节 顺序号

标识符

可选数据

图11-20 ICMP回显请求和回答

i c m p _ c o d e总是0。 i c m p _ i d和i c m p _ s e q设置成请求的发送方,回答中也不做修改。 源系统可以用这些字段匹配请求和回答。 i c m p _ d a t a中到达的所有数据也被反射。图 11 - 2 1 是ICMP回显处理和 icmp_input实现反射ICMP请求的源程序。

图11-21 icmp_input 函数:回显请求和回答

235-237

通过把 i c m p _ t y p e 变成 I C M P _ E C H O R E P L Y,并跳转到 r e f l e c t发送回答,

icmp_input把回显请求转换成了回显回答。 277-282 在为每个ICMP 请求构造完回答之后, icmp_input执行reflect处的程序。在 这里,存储数据报正确的长度被恢复,在 i c p s _ r e f l e c t和i c p s _ o u t h i s t [ ]中分别计算 请求的数量和ICMP报文的类型。icps_reflect(11.12节)把回答发回给请求方。

253

第11章 ICMP:Internet控制报文协议计计

11.7.2 时间戳询问:ICMP_TSTAMP和ICMP_TSTAMPREPLY ICMP时间戳报文如图 11-22所示。

检验和

标识符

序号

32位原始时间戳

20字节

32位接收时间戳

32位传送时间戳

图11-22 ICMP时间戳请求和回答

i c m p _ c o d e总是0。i c m p _ i d和i c m p _ s e q的作用与它们在 ICMP回显报文中的一样。请 求的发送方设置 i c m p _ o t i m e ( 发出请求的时间 ) ;i c m p _ r t i m e ( 收到请求的时间 )和 i c m p _ t t i m e(发出回答的时间 )由回答的发送方设置。所有时间都是从 U T C午夜开始的毫 秒数。如果时间值没有以标准单位记录,就把高位置位,与 IP时间戳选项一样。 图11-23是实现时间戳报文的程序。

图11-23 icmp_input 函数:时间戳请求和回答

238-246

icmp_input对ICMP的响应,包括:把icmp_type改成ICMP_TSTAMPREPLY,

记录当前icmp_rtime和icmp_ttime,并跳转到reflect发送回答。 很难精确地设置icmp_rtime和icmp_ttime。当系统执行这段程序时,报文可能已经在IP 输入队列中等待处理,这时设置icmp_rtime已经太晚了。类似地,数据报也可能在要求处理时 在网络接口的传输队列中被延迟,这时设置icmp_ttime又太早了。为了把时间戳设置得更接近 真实的接收和发送时间,必须修改每个网络的接口驱动程序,使其能理解ICMP 报文(习题11.8)。 11.7.3 地址掩码询问: ICMP_MASKREQ和ICMP_MASKREPLY ICMP 地址掩码请求和回答如图 11-24所示。 RFC 950 [Mogul和Postel 1985]在原来的ICMP规范说明中增加了地址掩码报文,使系统能 发现某个网络上使用的子网掩码。

254

计计TCP/IP详解 卷 2:实现

除非系统被明确地配置成地址掩码的授权代理,否则, RFC 1122禁止向其发送掩码回答。 这样,就避免系统与所有向它发出请求的系统共享不正确的地址掩码。如果没有管理员授权 回答,系统也要忽略地址掩码请求。

检验和

顺序号

标识符

12字节

32位子网掩码

图11-24 ICMP地址掩码请求和回答

如果全局整数 i c m p m a s k r e p l非零, N e t / 3会响应地址掩码请求。 i c m p m a s k r e p l的默 认值是0,icmp_sysctl可以通过systctl(8)程序( 11.14节)修改它。 N e t / 2系统中没有控制回答地址掩码请求的机制。其结果是,必须非常正确地配 置Net/2接口的地址掩码;该信息是与网络上所有发出地址掩码请求的系统共享的。 地址掩码报文的处理如图 11-25所示。

图11-25 icmp_input 函数:地址掩码请求和回答

255

第11章 ICMP:Internet控制报文协议计计

247-256

如果没有配置响应掩码请求,或者该请求太短,这段程序就中止 s w i t c h的执行,

并把报文传给rip_input(图11-15)。 在这里 N e t / 3无法增加 i c p s _ b a d l e n 。对其他 ICMP 长度差错,它却增加 icps_badlen。 1. 选择子网掩码 257-267 如果地址掩码请求被发到 0.0.0.0或255.255.255.255,源地址被保存在 icmpdst中。 在这里, i f a o f _ o f f o r a d d r把i c m p d s t作为源站地址,在同一网络上查找 i n _ o f a d d r结 构。如果源站地址是 0 . 0 . 0 . 0或2 5 5 . 2 5 5 . 2 5 5 . 2 5 5,i f a o f _ o f f o r a d d r返回一个指针,该指针 指向与接收接口相关的第一个 IP地址。 default情况(针对单播或有向广播 )为ifaof_ifpforaddr保存目的地址。 2. 转换成回答 269-270

通过改变 i c m p _ t y p e,并把所选子网掩码 i a _ s o c k m a s k复制到i c m p _ m a s k,

就完成了把请求转换成回答的工作。 3. 选择目的地址 271-276

如果请求的源站地址全 0(“该网络上的这台主机”,只在引导时用作源站地址,

RFC 11 2 2),并且源站不知道自己的地址, N e t / 3必须广播这个回答,使源站系统接收到这个 报文。在这种情况下,如果接收接口位于某个广播或点到点网络上,该回答的目的地址将分 别是i a _ b r o a d a d d r和i a _ d s t a d d r。i c m p _ i n p u t把回答的目的地址放在 i p _ s r c里,因 为reflect处的程序(图 11-21)会把源站和目的站地址倒过来。不改变单播请求的地址。 11.7.4 信息询问: ICMP_IREQ和ICMP_IREQREPLY I C M P信息报文已经过时了。它们企图广播一个源和目的站地址字段的网络部分为全 0的 请求,使系统发现连接的 I P 网络的数量。响应该请求的主机将返回一个填好网络号的报文。 主机还需要其他办法找到地址的主机部分。 RFC 11 2 2推荐主机不要实现 I C M P信息报文,因为 R A R P(RFC 903 [Finlayson et al. , 1984])和BOOTP(RFC 951 [Croft和Gilmore 1985])更适于发现地址。 RFC 1541 [Droms 1993]描 述的一个新协议,动态主机配置协议( Dynamic Host Configuration Protocol,DHCP),可能会 取代或增强 BOOTP的功能。它现在是一个建议的标准。 Net/2响应ICMP信息请求报文。但是, Net/3把它们传给 rip_input。 11.7.5 路由器发现:ICMP_ROUTERADVERT和ICMP_ROUTERSOLICIT RFC 1256 定义了ICMP路由器发现报文。Net/3内核不直接处理这些报文,而由rip_input 把它们传给一个用户级守护程序,由它发送和响应这种报文。 卷1的9.6节讨论了这种报文的设计和运行。

11.8 重定向处理 图11-26显示了ICMP重定向报文的格式。 i c m p _ i n p u t中要讨论的最后一个 c a s e是I C M P _ R E D I R E C T。如8 . 5节的讨论,当分组

256

计计TCP/IP详解 卷 2:实现

被发给错误的路由器时,产生重定向报文。该路由器把分组转发给正确的路由器,并发回一 个ICMP重定向报文,系统把信息记入它自己的路由表。

检验和 8字节 优选路由器的IP地址

IP首部(包括选项)和原始IP数据报中开始的至少8字节

图11-26 ICMP重定向报文

图11-27显示了icmp_input用来处理重定向报文的程序。

图11-27 icmp_input 函数:重定向报文

1. 验证 283-290

如果重定向报文中含有未识别的 ICMP 码,i c m p _ i n p u t就跳到b a d c o d e(图11 -

1 8的2 3 2行);如果报文具有无效长度或封闭的 I P分组具有无效首部长度,则中止 s w i t c h。图 11-16显示了ICMP差错报文的最小长度是 36(I C M P _ A D V L E N M I N)。I C M P _ A D V L E N(i c p)是当 icp所指向的分组有 IP选项时,ICMP差错报文的最小长度。 291-300

i c m p _ i n p u t分别把重定向报文的源站地址(发送该报文的网关)、为原始分组推

荐的路由器(第一跳目的地)和原始分组的最终目的地址分配给 icmpsrc。

icmpgw、icmpdst和

257

第11章 ICMP:Internet控制报文协议计计

这里, i c m p s r c并不包含源站地址—这是方便存放目的地址的位置,无需再 定义一个sockaddr结构。 2. 更新路由 301-306 Net/3按照RFC 1122的推荐,等价地对待网络重定向和主机重定向。重定向信息被 传给 r t r e d i r e c t,由这个函数更新路由表。重定向的目的地址(保存在 i c m p s r c)被传给 p f c t l i n p u t,由它通告重定向的所有协议域( 7 . 3节),使协议有机会把缓存的到目的站的路 由作废。 按照RFC 11 2 2,应该把网络重定向作为主机重定向对待,因为当目的网络划分 了子网时,它们会提供不正确的路由信息。事实上, RFC 1009要求,在网络划分子 网的情况下,不发送网络重定向。不幸的是,许多路由器违背了这个要求。 N e t / 3从 不发重定向报文。 I C M P重定向报文是 I P路由选择体系结构的基本组成部分。尽管被划分到差错报文类,但 它却是在任何有多个路由器的网络正常运行时出现的。第 18章更详细讨论了 IP路由选择问题。

11.9 回答处理 内核不处理任何 I C M P回答报文。 I C M P请求由进程产生,内核从不产生请求。所以,内 核把它接收的所有回答传给等待 I C M P 报文的进程。另外, I C M P 路由器发现报文被传给 rip_input。

图11-28 icmp_input 函数:回答报文

307-322

内核无需对ICMP回答报文做出任何反应,所以在raw处的switch语句后继续执行(图

11-15)。注意,switch语句的default情况(未识别的ICMP报文)也把控制传给在raw处的代码。

11.10 输出处理 有几种方法产生外出的 ICMP报文。第 8章讲到IP调用icmp_error来产生和发送ICMP 差 错报文。 i c m p _ r e f l e c t发送ICMP 回答报文,同时,进程也可能通过原始 ICMP 协议生成 ICMP 报文。图11-29显示了这些函数与 ICMP外出处理之间的关系。

258

计计TCP/IP详解 卷 2:实现

应用程序

ICMP差错 IP和传输层协议

ICMP输入处理

ICMP

ICMP应答

(图11-15)

图11-29 ICMP外出处理

11.11 icmp_error函数 i c m p _ e r r o r在I P或运输层协议的请求下,构造一个 I C M P差错请求报文,并把它传给 icmp_reflect,在那里该报文被返回无效数据报的源站。我们分三部分分析这个函数:

图11-30 icmp_error 函数:验证

259

第11章 ICMP:Internet控制报文协议计计

• 确认该报文(图11-30); • 构造首部(图11-32);并 • 把原来的数据报包含进来(图 11-33)。 46-57

参数是: n,指向包含无效数据报缓存链的指针; t y p e和c o d e,I C M P差错类型和

代码; d e s t,I C M P重定向报文中的下一跳路由器地址;以及 d e s t i f p,指向原始 I P分组外 出接口的指针。 m t o d把缓存链指针 n转换成 o i p,o i p是指向缓存中 i p结构的指针。原始 I P 分组的字节长度保存在 ioplen中。 58-75

i c p s _ e r r o r统计除重定向报文外的所有 I C M P差错。 N e t / 3不把重定向报文看作错

误,而且icps_error也不是一个 SNMP变量。 icmp_error丢弃无效数据报 oip,并且在以下情况下,不发送差错报文: • 除I P _ M F和I P _ D F外,i p _ o f f的某些位非零(习题 11 . 1 0)。这表明 o i p不是数据报的第 一个分片,而且 ICMP决不能为跟踪数据报的分片而生成差错报文。 • 无效数据报本身是一个 I C M P差错报文。如果 i c m p _ t y p e是I C M P请求或响应类型,则 ICMP_INFOTYPE返回真;如果icmp_type是一个差错类型,则ICMP_INFOTYPE返回假。 Net/3不考虑ICMP重定向报文差错,尽管 RFC 1 122要求考虑。 • 数据报作为链路层广播或多播到达(由 M_BCAST和M_MCAST标志表明)。 在以下两种其他情况下,不能发送 ICMP差错报文: • 该数据报是发给 IP广播和IP多播地址的。 • 数据报的源站地址不是单播 I P地址(也即,这个源站地址是一个全零地址、环回地址、 广播地址、多播地址或 E类地址)。 Net/3无法检查第一种情况。 icmp_reflect函数强调了第二种情况( 11.12节)。 有趣的是, Net/2的Deering多播扩展并不丢弃第一种类型的数据报。因为 Net/3的 多播程序来自Deering多播扩展,所以,检测似乎被删去了。 这些限制的目的是为了避免有错的广播数据报触发网络上所有主机都发出

I C M P差错报

文。当网络上所有主机同时要发送差错报文时,产生的广播风暴会使整个网络的通信崩溃。 这些规则适用于 ICMP差错报文,但不适用于 ICMP回答。如RFC 1122和RFC 1127的讨论, 允许响应广播请求,但既不推荐也不鼓励。 N e t / 3只响应具有单播源地址的广播请求,因为 ip_output会把返回到广播地址的 ICMP 报文丢弃(图 11-39)。 图11-31是ICMP差错报文的构造。 图11-32的程序构造差错报文。 76-106 icmp_error以下面的方式构造 ICMP差错报文的首部: • m _ g e t h d r分配一个新的分组首部缓存。 M H _ A L I G N定位缓存的数据指针,使无效数据 报的ICMP首部、IP首部(和选项)和最多 8字节的数据被放在缓存的最后。 • i c m p _ t y p e、i c m p _ c o d e、i c m p _ g w a d d r(用于重定向)、 i c m p _ p p t r(用于参数问 题)和i c m p _ n e x t m t u(用于要求分片报文)被初始化。 i c m p _ n e x t m t u字段实现了 R F C 11 9 1中描述的要求分片报文的扩展。卷 1的2 4 . 2节描述的“路径 M T U发现算法”依赖于 这个报文。 一旦构造好 ICMP首部,就必须把原始数据报的一部分附到首部上,如图 11-33所示。

260

计计TCP/IP详解 卷 2:实现

有差错的数据报

ICMP差错报文

IP首部

IP选项

IP首部

ICMP 首部

IP首部

前8个 字节

数据

IP选项

前8个 字节

无效数据报

图11-31 ICMP差错报文的构造

图11-32 icmp_error 函数:报文首部构造

107-125

无效数据报的IP首部、选项和数据(一共是 icmplen个字节)被复制到 ICMP差错报

文中。同时,首部的长度被加回无效数据报的 ip_len中。 在u d p _ u s r r e q中,UDP也把首部长度加回到无效数据报的 i p _ l e n。其结果是 一个I C M P报文,该报文具有无效分组 I P首部内的不正确的数据报长度。作者发现, 许多基于Net/2程序的系统都有这个错误, Net/1系统没有这个问题。

261

第11章 ICMP:Internet控制报文协议计计

图11-33 icmp_error 函数:包含原始数据报

因为M H _ A L I G N把I C M P报文分配在缓存的最后,所以缓存的前面应该有足够的空间存放 IP首部。无效数据报的 IP首部(除选项外)被复制到 ICMP报文的前面。 N e t / 2版本的这部分有一个错误:函数的最后一个 b c o p y移动 o i p l e n个字节, 其中包括无效数据报的选项。应该只复制没有选项的标准首部。 在恢复正确的数据报长度( i p _ l e n)、首部长度( i p _ h l)和协议( i p _ p)后, I P首部就完整 了。TOS字段(ip_tos)被清除。 RFC 792和RFC 1122推荐在ICMP报文中,把 TOS字段设为0。 126-129

完整的报文被传给 i c m p _ r e f l e c t,由i c m p _ r e f l e c t把它发回源主机。丢掉

无效数据报。

11.12 icmp_reflect函数 i c m p _ r e f l e c t 把I C M P 回答或差错发回给请求或无效数据报的源站。必须牢记, i c m p _ r e f l e c t在发送数据报之前,把它的源站地址和目的地址倒过来。与 ICMP 报文的源 站和目的站地址有关的规则非常复杂,图 11-34对其中几个函数的作用作了小结。 我们分三部分讨论 i c m p _ r e f l e c t函数:源站和目的站地址选择、选项构造及组装和发 送。图11-35显示了该函数的第一部分。 1. 设置目的地址 329-345

i c m p _ r e f l e c t一开始,就复制 i p _ d s t,并把请求或差错报文的源站地址

i p _ s r c移到i p _ d s t。i c m p _ e r r o r和i c m p _ r e f l e c t保证:i p _ s r c对差错报文而言是有 效的目的地址。 ip_output丢掉所有发往广播地址的分组。

262

计计TCP/IP详解 卷 2:实现





icmp_input icmp_error icmp_reflect

ip_output





在地址掩码请求中,用接收接口的广播或目的地址代替全 0地址 把作为链路级广播或多播发送的数据报引起的差错报文丢弃。应该丢弃 (但没有)发往 IP广播或多播地址的数据报引起的报文 丢弃报文,而不是把它返回给多播或实验地址 把非单播目的地址转换成接收接口的地址,对返回的报文来说,目的地址就是一个有 效的源地址 交换源站和目的站的地址 按照ICMP的请求丢弃输出的广播 (也就是说,丢弃由发往广播地址的分组产生的差错 报文)

图11-34 ICMP丢弃和地址小结

2. 选择源站地址 346-371

icmp_reflect在in_ifaddr中找到具有单播或广播地址的接口,该接口地址与

原始数据报的目的地址匹配,这样,icmp_reflect就为报文选好了源地址。在多接口主机上, 匹配的接口可能不是接收该数据报的接口。如果没有匹配,就选择正在接收的接口的 i n _ i f a d d r结构,或者 i n _ i f a d d r中的第一个地址(如果该接口没有被配置成 IP可用的)。该 函数把ip_src设成所选的地址,并把ip_ttl改为255(MAXTTL),因为这是一个新的数据报。 RFC 1700推荐把所有I P分组的T T L字段设成 6 4。但是现在,许多系统把 I C M P报 文的TTL设成255。 T T L的取值有一个拆衷。小的 T T L避免分组在路由回路里面循环,但也有可能使 分组无法到达远一点的节点 (有很多跳 )。大的T T L允许分组到达远距离的主机,但却 让分组在路由回路里循环较长时间。

图11-35 icmp_reflect

函数:地址选择

263

第11章 ICMP:Internet控制报文协议计计

图11-35 (续)

RFC 11 2 2提出,对到达的回显请求或时间戳请求,要求把其中的源路由选项及记录路由 和时间戳选项的建议,附到回答报文中。在这个过程期间,源路由必须被逆转过来。

RFC

11 2 2没有涉及在其他 I C M P回答报文中如何处理这些选项。 N e t / 3把这些规则应用于地址掩码 请求,因为它在构造地址掩码回答后调用了 icmp_reflect(图11-21)。 程序的下一部分(图 11-36)为ICMP报文构造选项。

图11-36 icmp_reflect 函数:选项构造

264

计计TCP/IP详解 卷 2:实现

图11-36 (续)

3. 取得逆转后的源路由 372-385

如果到达的数据报没有选项,控制被传给 4 3 0行(图 11 - 3 7)。 i c m p _ e r r o r传给

i c m p _ r e f l e c t的差错报文从来没有 I P选项,所以后面的程序只用于那些被转换成回答并直 接传给icmp_reflect的ICMP请求。 c p指向回答的选项的开始。 i p _ s r c r o u t e逆转并返回所有在 i p i n t r处理数据报时保存 下来的源路由选项。如果 i p _ s r c o u t e返回0,即请求中没有源路由选项, i c m p _ r e f l e c t 分配并初始化一个 mbuf,作为空的 ipoption结构。 4. 加上记录路由和时间戳选项 386-416

如果opts指向某个缓存,for循环搜索原始IP首部的选项,在ip_srcroute返回

的源路由后面加上记录路由和时间戳选项。 在ICMP报文发送之前必须移走原始首部里的选项。这由图 11-37中的程序完成。

图11-37 icmp_reflect

函数:最后的组装

265

第11章 ICMP:Internet控制报文协议计计

5. 移走原始选项 417-429

i c m p _ r e f l e c t把I C M P报文移到 I P首部的后面,这样就从原始请求中移走了选

项。如图11-38所示。新选项在 opts所指向的mbuf里,被ip_output再次插入。 6. 发送报文和清除 430-435 在报文和选项被传给 icmp_send之前,要明确地清除广播和多播标志位。此后释 放掉存放选项的缓存。 丢弃

bcopy之前

mbuf 首部

IP首部

bcopy之后

mbuf 首部

IP首部

图11-38 icmp_reflect

IP选项 (optlen字节)

数据

数据

:移走选项

11.13 icmp_send函数 i c m p _ s e n d(图11 - 3 9)处理所有输出的 I C M P报文,并在把它们传给 I P层之前计算I C M P检 验和。

图11-39 icmp_send 函数

440-457 与icmp_input检测ICMP检验和一样, Net/3调整缓存的数据指针和长度,隐藏 IP 首部,让 i n _ c k s u m只看到ICMP报文。计算好的检验和放在首部的 i c m p _ c k s u m,然后把数 据报和所有选项传给 i p _ o u t p u t。ICMP层并不维护路由高速缓存,所以 i c m p _ s e n d只传给 i p _ o u t p u t一个空指针(第 4个参数),而不是控制标志。特别是不传 I P _ A L L O W B R O A D C A S T, 所以i p _ o u t p u t丢弃所有具有广播目的地址的 I C M P报文(也就是说,到达原始数据报的具有 无效的源地址)。

266

计计TCP/IP详解 卷 2:实现

11.14 icmp_sysctl函数 IP的i c m p _ s y s c t l函数只支持图11-40列出的选项。系统管理员可以用 s y s c t l程序修改 该选项。 Sysc t l常量

Net/3变量

ICMPCTL_MASKREPL

icmpmaskrepl





系统是否响应 ICMP地址掩码请求

图11-40 icmp_sysctl 参数

图11-41显示了icmp_sysctl函数。

图11-41 icmp_sysctl 函数

467-478 如果缺少所要求的 ICMP sysctl名,就返回 ENOTDIR。 479-486 ICMP级以下没有选项,所以,如果不识别选项,该函数就调用 sysctl_int修改 icmpmaskrepl或返回ENOPROTOOPT。

11.15 小结 I C M P协议是作为 I P上面的运输层实现的,但它与 I P层紧密结合一起。我们看到,内核直 接响应 I C M P请求报文,但把差错与回答传给合适的运输层协议或应用程序处理。当一个 I C M P重定向报文到达时,内核立刻重定向表,并且也把重定向传给所有等待的进程,比如典 型地传给一个路由守护程序。 我们将在 2 3 . 9和2 7 . 6节看到 U D P和T C P协议如何响应 I C M P差错报文,在第 3 2章看到进程 如何产生ICMP请求。

习题 11.1 一个目的地址是 0.0.0.0的请求所产生的 ICMP地址掩码回答报文的源地址是什么?

267

第11章 ICMP:Internet控制报文协议计计

11.2 试描述一个具有假的单播源地址的分组在链路级的广播会如何影响网络上另一个主 机的运行。 11.3 RFC 1122建议,如果新的第一跳路由器与旧的第一跳路由器位于不同的子网,或者 如果发送报文的路由器不是报文最终目的地的当前第一跳路由器,那么主机应该丢 弃ICMP重定向报文。为什么要采纳这个建议? 11.4 如果ICMP信息请求是过时的,为什么 i c m p _ i n o u t要把它传给 r i p _ i n p u t而不是 丢弃它呢? 11.5 我们指出, N e t / 3在把I P分组放入一个 I C M P差错报文之前,并不把它的偏移和长度 字段转换成网络字节序。为什么这对 IP位移字段来说是无关紧要的? 11.6 描述某种情况,使图 11-25的ifaof_ifpforaddr返回一个空指针。 11.7 在一次时间戳询问中,时间戳后面的数据会怎么样? 11.8 实现以下改变,改进 ICMP时间戳程序: 在缓存分组首部加上一个时间戳字段,让设备驱动程序把接收分组的确切时间记录 在这个字段内,并用 ICMP 时间戳程序把该值复制到 icmp_rtime字段。 在输出端,让 I C M P时间戳程序保存分组中的某个字节偏移,该位置用于保存时间 戳里的当前时间。修改设备驱动程序,在发送分组之前插入时间戳。 11.9 修改i c m p _ e r r o r,使I C M P差错报文中返回最多 6 4字节(像 Solaris 2.x一样)的原始 数据。 11.10 图11-30中,ip_off的高位被置位的分组会发生什么情况? 11.11 为什么图11-39中丢弃了ip_output返回的值?

第12章 IP 多 播 12.1 引言 第8章讲到,D类IP地址(224.0.0.0到239.255.255.255)不识别互联网内的单个接口,但识别 接口组。因为这个原因, D类地址被称为多播组 (multicast group)。具有D类目的地址的数据报 被提交给互联网内所有加入相应多播组的各个接口。 I n t e r n e t上利用多播的实验性应用程序包括:音频和视频会议应用程序、资源发现工具和 共享白板等。 多播组的成员由于接口加入或离开组而动态地变化,这是根据各系统上运行的进程的请 求决定的。因为多播组成员与接口有关,所以多接口主机可能针对每个接口,都有不同的多 播组成员关系表。我们称一个特定接口上的组成员关系为一对 {接口,多播组}。 单个网络上的组成员利用 I G M P协议(第1 3章)在系统之间通信。多播路由器用多播选路协 议(第14章),如DVMRP(Distance Vector Multicast Routing Protocol,距离向量多播路由选择协 议)传播成员信息。标准 IP路由器可能支持多播选路,或者用一专用路由器处理多播选路。 如以太网、令牌环和 FDDI一类的网络直接支持硬件多播。在 Net/3中,如果某个接口支持 多播,那么在接口的 i f n e t结构(图3 - 7 )中的i f _ f l a g s标志的 I F F _ M U L T I C A S T比特就被打 开。因为以太网被广泛使用,并且 N e t / 3有以太网驱动器程序,所以我们将以以太网为例说明 硬件支持的 IP多播。多播业务通常在如 SLIP和环回接口等的点到点网络上实现。 如果本地网络不支持硬件级多播,那么在某个特定接口上就得不到

I P多播业务。 R F C

1122并不反对接口层提供软件级的多播业务,只要它对 IP是透明的。 RFC 1112 [Deering 1989] 描述了多播对主机的要求。分三个级别: 0级:主机不能发送或接收 IP多播。 这种主机应该自动丢弃它收到的具有 D类目的地址的分组。 1级:主机能发送但不能接收 IP多播。 在向某个 I P多播组发送数据报之前,并不要求主机加入该组。多播数据报的发送方 式与单播一样,除了多播数据报的目的地址是 I P多播组之外。网络驱动器必须能够 识别出这个地址,把在本地网络上多播数据报。 2级:主机能发送和接收 IP多播。 为了接收IP多播,主机必须能够加入或离开多播组,而且必须支持IGMP,能够在至少 一个接口上交换组成员信息。多接口主机必须支持在它的接口的一个子网上的多播。 N e t / 3符合2级主机要求,可以完成多播路由器的工作。与单播 I P选路一样,我们假 定所描述的系统是一个多播路由器,并加上了 Net/3多播选路的程序。 知名的IP多播组 和U D P 、T C P的端口号一样,互联网号授权机构 IANA(Internet Assigned Numbers

269

第 12章 IP 多 播计计

A u t h o r i t y )维护着一个注册的 I P多播组表。当前的表可以在 RFC 1700中查到。有关 I A N A的其 他信息可以在RFC 1700中找到。图 12-1只给出了一些知名的多播组。 组





224.0.0.0 224.0.0.1 224.0.0.2 224.0.0.3 224.0.0.4 224.0.0.255

预留 这个子网上的所有系统 这个子网上的所有路由器 没有分配 DVMRP路由器 没有分配

224.0.1.1 224.0.1.2

NTP网络时间协议 SGI-Dogfight

Net/3常量 INADDR_UNSPEC_GROUP INADDR_ALLHOSTS_GROUP INADDR_MAX_LOCAL_GROUP

图12-1 一些注册的 IP多播组

前2 5 6个组( 2 2 4 . 0 . 0 . 0到2 2 4 . 0 . 0 . 2 5 5 )是为实现 I P单播和多播选路机制的协议预留的。不管 发给其中任意一个组的数据报内 I P首部的 T T L值如何变化,多播路由器都不会把它转发出本 地网络。 RFC 1075 只对2 2 4 . 0 . 0 . 0组和2 2 4 . 0 . 0 . 1组有这个要求,但最常见的多播选路实现 mrouted限制这里讨论的其他组。组 224.0.0.0(I N A D D R _ U N S P E C _ G R O U P)被预留,组 224.0.0.255(INADDR_MAX_LOCAL_GROUP)标志着本地最后一个多播组。 对于符合 2 级的系统,要求其在系统初始化时

(图 6 - 1 7 ) ,在所有的多播接口上加入

224.0.0.1组(I N A D D R _ A L L H O S T S _ G R O U P),并且保持为该组成员,直到系统关闭。在一个互 联网上,没有多播组与每个接口都对应。 想像一下,如果你的语音邮件系统有一个选项,可以向公司里的所有语音邮箱 发一个消息。可能你就有这个选项。你发现它有用吗?对更大的公司适用吗?是否 有人能向“所有邮箱”组发邮件,或者是否限制这么做? 单播和多播路由可能会加入 2 2 4 . 0 . 0 . 2组进行互相通信。 I C M P路由器请求报文和路由器通 告报文可能被分别发往 2 2 4 . 0 . 0 . 2 (“所有路由器”组 )和2 2 4 . 0 . 0 . 1 (“所有主机”组 ),而不是受 限的广播地址(255.255.255.255)。 2 2 4 . 0 . 0 . 4组支持在实现 D V M R P的多播路由器之间的通信。本地多播组范围内的其他组被 类似地指派给其他路由选择协议。 除了前256个组外,其他组 (224.0.1.0~239.255.255.255)或者被分配给多个多播应用程序协 议,或者仍然没有被分配。图

1 2 - 1 中有两个例子,网络时间协议

(224.0.1.1)和SGI-

Dogfight(224.0.1.2)。 在本章中,我们注意到,是主机上的运输层发送和接收多播分组。尽管多播程序并不知 道具体是哪个传输协议发送和接收多播数据报,但唯一支持多播的 Internet传输协议是 UDP。

12.2 代码介绍 本章中讨论的基本多播程序与标准 I P程序在相同的文件里。图 1 2 - 2列出了我们研究的文 件。

270

计计TCP/IP详解 卷 2:实现







net/if_either.h netinet/in.h netinet/in_var.h netinet/ip_var.h net/if_ethersubr.c n e t i n e t / i n .c netinet/ip_input.c netinet/ip_output.c



以太网多播数据结构和宏定义 其他Internet多播数据结构 Internet多播数据结构和宏定义 IP多播数据结构 以太网多播函数 组成员函数 输入多播处理 输出多播处理

图12-2 本章讨论的文件

12.2.1 全局变量 本章介绍了三个新的全局变量 (图12-3)。 变



ether_ipmulticast_min ether_ipmulticast_max ip_mrouter

数据类型





u_char [] 为IP预留的最小以太网多播地址 u_char [] 为IP预留的最大以太网多播地址 s t r u c t s o c k e t * 多播选路守护程序创建的指向插口的指针

图12-3 本章引入的全局变量

12.2.2 统计量 本章讨论的程序更新全局 ipstat结构中的几个计数器。 ips t a t成员 ips_forward ips_cantforward ips_noroute





被这个系统转发的分组数 不能被系统转发的分组数—系统不是一个路由器 由于无法访问到路由器而无法转发的分组数

图12-4 多播处理统计量

链路级多播统计放在 ifnet结构中(图4-5),还可能统计除 IP以外的其他协议的多播。

12.3 以太网多播地址 I P多播的高效实现要求 I P充分利用硬件级多播,因为如果没有硬件级多播,就不得不在 网络上广播每个多播 I P数据报,而每台主机也不得不检查每个数据报,把那些不是给它的丢 掉。硬件在数据报到达 IP层之前,就把没有用的过滤掉了。 为了保证硬件过滤器能正常工作,网络接口必须把 I P多播组目的地址转换成网络硬件识 别的链路级多播地址。在点到点网络上,如 S L I P和环回接口,必须明确给出地址映射,因为 只能有一个目的地址。在其他网络上,如以太网,也需要有一个明确地完成映射地址的函数。 以太网的标准映射适用于任何使用 802.3寻址方式的网络。 图4 - 1 2显示了以太网单播和多播地址的区别:如果以太网地址的高位字节的最低位是 1, 则它是一个多播地址;否则,它是一个单播地址。单播以太网地址由接口制造商分配,多播

271

第 12章 IP 多 播计计

地址由网络协议动态分配。 IP到以太网地址映射 因为以太网支持多种协议,所以要采取措施分配多播地址,避免冲突。IEEE管理以太网多播 地址分配。IEEE把一块以太网多播地址分给IANA以支持IP多播。块的地址都以01:00:5e开头。 以00:00:5e开头的以太网单播也被分配给 IANA,但为将来使用预留。 图12-5显示了从一个D类IP地址构造出一个以太网多播地址。 标识以太网 多播地址

必须是0;IANA 预留了1 48位以太 网地址 专用

IANA预留的 以太网前缀

32位D类IP地址

图12-5 IP和以太网地址之间的映射

图1 2 - 5显示的映射是一个多到一的映射。在构造以太网地址时,没有使用 D类I P地址的高 位9比特。32个 IP多播组映射到一个以太网多播地址 (习题12.3)。我们将在 12.14节看到这将如 何影响输入的处理。图 12-6显示了Net/3中实现这个映射的宏。

图12-6 ETHER_MAP_IP_MULTICAST



IP到以太网多播映射 61-71

E T H E R _ M A P _ I P _ M U L T I C A S T实现图12-5所示的映射。 i p a d d r指向D类多播地址,

e n a d d r构造匹配的以太网地址,用 6字节的数组表示。该以太网多播地址的前 3个字节是 0x01,0x00和0x5e,后面跟着 0比特,然后是D类IP地址的低23位。

12.4 ether_multi结构 N e t / 3为每个以太网接口维护一个该硬件接收的以太网多播地址范围表。这个表定义了该 设备要实现的多播过滤。因为大多数以太网设备能选择地接收的地址是有限的,所以 IP层必须 要准备丢弃那些通过了硬件过滤的数据报。地址范围被保存在 ether_multi结构中(图12-7):

272

计计TCP/IP详解 卷 2:实现

图12-7 ether_multi 结构

1. 以太网多播地址 1 4 7 - 1 5 3 e n m _ a d d r l o和e n m _ a d d r h i指定需要被接收的以太网多播地址的范围。当 e n m _ a d d r l o和e n m _ a d d r h i相同时,就指定一个以太网地址。 e t h e r _ m u l t i的完整列表 附在每个以太网接口的 a r p c o m结构中 (图3 - 2 6 )。以太网多播独立于 A R P—使用a r p c o m结 构只是为了方便,因为该结构已经存在于所有以太网接口结构中。 我们将看到,这个范围的开头和结尾总是相同的,因为在 N e t / 3中,进程无法指 定地址范围。 e n m _ a c指回相关接口的 a r p c o m结构,e n m _ r e f c o u n t跟踪对e t h e r _ m u l t i结构的使 用。当引用计数变成 0时,就释放 a r p c o m结构。 e n m _ n e x t把单个接口的 e t h e r _ m u l t i结 构做成链表。图 1 2 - 8显示出,有三个 e t h e r _ m u l t i结构的链表附在 l e _ s o f t c [ 0 ]上,这是 我们以太网接口示例的 ifnet结构。

图12-8 有三个ether_multi 结构的LANCE接口

在图12-8中,我们看到: • 接口已经加入了三个组。很有可能是 2 2 4 . 0 . 0 . 1 (所有主机 )、2 2 4 . 0 . 0 . 2 (所有路由器 )和 224.0.1.2(SGI-dogfight)。因为以太网到 IP地址的映射是一到多的,所以只看到以太网多 播地址的结果,无法确定确切的 I P多播地址。比如,接口可能已经加入了 2 2 5 . 0 . 0 . 1、 225.0.0.2和226.0.1.2组。 • 有了e n m _ a c后向指针,就很容易找到链表的开始,释放某个 e h t e r _ m u l t i结构,无 需再实现双向链表。 • ether_multi只适用于以太网设备。其他多播设备可能有其他实现。 图12-9中的ETHER_LOOKUP_MULTI宏,搜索某个ether_multi结构,找到地址范围。 2. 以太网多播查找 166-177

a d d r l o和a d d r h i指定搜索的范围, a c指向包含了要搜索链表的 a r p c o m结构。

273

第 12章 IP 多 播计计

f o r循环完成线性搜索,在表的最后结束,或者当 e n m _ a d d r l o和e n m _ a d d r h i都分别与和 所提供的 a d d r l o 和a d d r h i 匹配时结束。当循环终止时, e n m 为空或者指向某个匹配的 ether_multi结构。

图12-9 ETHER_LOOKUP_MULTI



12.5 以太网多播接收 从本节以后,本章只讨论 I P多播。但是,在 N e t / 3中,也有可能把系统配置成接收所有以 太网多播分组。虽然对 IP 协议族没有用,但内核的其他协议族可能准备接收这些多播分组。 发出图12-10中的ioctl命令,就可以明确地进行多播配置。 命



SIOCADDMULTI SIOCDELMULTI









s t r u c t i f r e q * ifioctl s t r u c t i f r e q * ifioctl





在接收表里加上多播地址 从接收表里删去多播地址

图12-10 多播ioctl 命令

这两个命令被 i f i o c t l(图1 2 - 11 )直接传给 i f r e q结构(图6 - 1 2 )中所指定的接口的设备驱 动程序。

图12-11 ifioctl 函数:多播命令

440-446

如果该进程没有超级用户权限,或者如果接口没有 i f _ i o c t l结构,则i f i o c t l

返回一个错误;否则,把请求直接传给该设备驱动程序。

12.6 in_multi结构 12.4节描述的以太网多播数据结构并不专用于 IP;它们必须支持所有内核支持的任意协议 族的多播活动。在网络级, IP维护着一个与接口相关的 IP多播组表。 为了实现方便,把这个 IP多播表附在与该接口有关的 i n _ i f a d d r结构中。6.5节讲到,这

274

计计TCP/IP详解 卷 2:实现

个结构中包含了该接口的单播地址。除了它们都与同一个接口相关以外,这个单播地址与所 附的多播组表之间没有任何关系。 这是Net/3实现的产品。也可以在一个不接收IP单播分组的接口上,支持IP多播组。 图12-12中的in_multi结构描述了每个 IP多播{接口,组}对。

图12-12 in_multi 结构

1. IP 多播地址 111-118

inm_addr是一个D类多播地址 (如224.0.0.1,所有主机组)。inm_ifp指回相关接

口的ifnet结构,而inm_ia指回接口的 in_ifaddr结构。 只有当系统中的某个进程通知内核,它要在某个特定的 {接口,组 }对上接收多播数据报 时,才存在一个 i n _ m u l t i结构。由于可能会有多个进程要求接收发往同一个对上的数据报, 所以 i n m _ r e f c o u n t 跟踪对该对的引用次数。当没有进程对某个特定的对感兴趣时, i n m _ r e f c o u n t 就变成 0,i n _ m u l t i 结构就被释放掉。这个动作可能会引起相关的 ether_multi结构也被释放,如果此时它的引用计数也变成了 0。 i n m _ t i m e r是第1 3章描述的 I G M P协议实现的一部分,最后, i n m _ n e x t指向表中的下 一个in_multi结构。 图1 2 - 1 3用接口示例 l e _ s o f t c [ 0 ]显示了接口,即它的单播地址和它的 I P多播组表之间 的关系。

图12-13 le 接口的一个 IP 多播组表

275

第 12章 IP 多 播计计

为了清楚起见,我们已经省略了对应的 e t h e r _ m u l t i结构(图1 2 - 3 4 )。如果系统有两个 以太网网卡,第二个可能由 l e _ s o f t c[ 1 ]管理,还可能有它自己的附在 a r p c o m结构的多播 组表。IN_LOOKUP_MULTI宏(图12-14)搜索IP多播表寻找某个特定多播组。 2. IP 多播查找 131-146

I N _ L O O K U P _ M U L T I 在与接口 i f p相关的多播组表中查找多播组 a d d r 。

I F P _ T O _ I A搜索Internet地址表i n _ i f a d d r,寻找与接口 i f p相关的i n _ i f a d d r结构。如果 I F P _ T O _ I A找到一个接口,则 f o r循环搜索它的 I P多播表。循环结束后, i n m为空或指向匹 配的in_multi结构。

图12-14 IN_LOOKUP_MULTI



12.7 ip_moptions结构 运输层通过 i p _ m o p t i o n s结构包含的多播选项控制多播输出处理。例如, U D P调用 ip_output是: error = ip_output(m, inp->inp_options, &inp->inp_route, inp->inp_socket->so_options & (SO_DONTROUTE|SO_BROADCAST), inp->inp_moptions);

在第22章中我们将看到, i n p指向某个 Internet协议控制块 (PCB),并且UDP为每个由进程 创建的s o c k e t关联一个PCB。在PCB内,i n p _ m o p t i o n s是指向某个 i p _ m o p t i o n s结构的 指针。这里我们看到,对每个输出的数据报,都可以给 ip_moptions结构。图12-15是ip_moptions结构的定义。

图12-15 ip_moptions 结构

i p _ o u t p u t 传一个不同的

276

计计TCP/IP详解 卷 2:实现

多播选项 100-106

i p _ o u t p u t通过i m o _ m u l t i c a s t _ i f p指向的接口对输出的多播数据报进行选

路。如果imo_multicast_ifp为空,就通过目的站多播组的默认接口 (第14章)。 i m o _ m u l t i c a s t _ t t l为外出的多播数据报指定初始的 IP TTL。默认值是1, 把多播数 据报保留在本地网络内。 如果i m o _ m u l t i c a s t _ l o o p是0,就不回送数据报,也不把数据报提交给正在发送的接 口,即使该接口是多播组的成员。如果 i m o _ m u l t i c a s t _ l o o p是1,并且如果正在发送的接 口是多播组的成员,就把多播数据报回送给该接口。 最后,整数 i m o _ n u m _ m e m b e r s h i p s和数组i m o _ m e m b e r s h i p维护与该结构相关的 {接 口,组 }对。所有对该表的改变都转告给 I P,由 I P 在所连到的本地网络上宣布成员的变化。 i m o _ m e m b e r s h i p数组的每个入口都是指向一个 i n _ m u l t i结构的指针,该 i n _ m u l t i结构 附在适当接口的 in_ifaddr结构上。

12.8 多播的插口选项 图12-16显示了几个 IP 级的插口选项,提供对 ip_moptions结构的进程级访问。 命



IP_MULTICAST_IF IP_MULTICAST_TTL IP_MULTICAST_LOOP IP_ADD_MEMBERSHIP IP_DROP_MEMBERSHIP





struct in_addr u_char u_char struct ip_mreq struct ip_mreq





ip_ctloutput ip_ctloutput ip_ctloutput ip_ctloutput ip_ctloutput





为外出的多播选择默认接口 为外出的多播选择默认的 TTL 允许或使能回送外出的多播 加入一个多播组 离开一个多播组

图12-16 多播插口选项

我们在图8-31中看到 i p _ c t l o u t p u t函数的整体结构。图 12-17显示了与改变和检索多播 选项有关的情况语句。

图12-17 ip_ctloutput

函数:多播选项

277

第 12章 IP 多 播计计

图12-17 (续)

486-491

所有多播选项都由 i p _ s e t m o p t i o n s 和 i p _ g e t m o p t i o n s 函数处理。

ip_moptions结构由引用传给 539-549

ip_getmoptions和ip_setmoptions,该结构与发布 ioctl命令的那个插口

关联。 对于P R C O _ S E T O P T和P R C O _ G E T O P T两种情况,选项不识别时返回的差错码是 不一样的。 ENOPROTOOPT是更合理的选择。

12.9 多播的TTL值 多播的TTL值难以理解,因为它们有两个作用。 TTL值的基本作用,如 IP分组一样,是限 制分组在互联网内的生存期,避免它在网络内部无限地循环。第二个作用是,把分组限制在 管理边界所指定的互联网的某个区域内。管理区域是由一些主观的词语指定的,如“这个结 点”,“这个公司”,“这个州”等,并与分组开始的地方有关。与多播分组有关的区域叫做它 的辖域(scope)。 RFC 1 1 2 2的标准实现把生存期和辖域这两个概念合并在 I P 首部的一个 T T L值里。当 I P T T L变成0时,除了丢弃该分组外,多播路由器还给每个接口关联了一个 T T L阈值,限制在该 接口上的多播传输。一个要在该接口上传输的分组必须具有大于或等于该接口阈值的

T T L。

由于这个原因,多播分组可能会在它的 TTL到0之前就被丢弃了。 阈值是管理员在配置多播路由器时分配的,这些值确定了多播分组的辖域。管理员使用 的阈值策略以及数据报的源站与多播接口之间的距离定义多播数据报的初始 TTL值的意义。 图12-18显示了多种应用程序的推荐 TTL值和推荐的阈值。 第一栏是 I P首部中的 i p _ t t l初始值。第二栏是应用程序专用阈值 ([Casner 1993])。第三 栏是与该TTL值相关的推荐的辖域。 例如,一个要与本地结点外的网络通信的接口,多播阈值要被配置成

3 2。所有开始时

278

计计TCP/IP详解 卷 2:实现

T T L为3 2 (或小于 3 2 )的数据报到达该接口时, T T L都小于 3 2 (假定源站和路由器之间至少有一 跳),所以它们在被转发到外部网络之前,都被丢弃了—即使TTL远大于0。 T T L初始值是 1 2 8的多播数据报可以通过阈值为 3 2的结点接口(只要它以少于 1 2 8-3 2 = 9 6跳 到达接口),但将被阈值为 128的洲际接口丢弃。 应用程序

ip_ttl 0 1 31 32 63 64 95 127 128 159 191 223 255





同一接口 同一子网 本地事件视频 同一地点 本地事件音频 同一区域 IETF频道2视频 IETF频道1视频 同一州 IETF频道2音频 IETF频道1音频 IETF频道2低速率音频 IETF频道1低速率音频,辖域不受限

图12-18 IP多播数据报的 TTL值

12.9.1 MBONE I n t e r n e t上有一个路由器子网支持 I P 多播选路。这个多播骨干网称为 M B O N E , [ C a s n e r 1993] 对其作了描述。它是为了支持用 I P多播的实验——尤其是用音频和视频数据流的实验。 在M B O N E里,阈值限制了多种数据流传播的距离。在图 1 2 - 1 8中,我们看到本地事件视频分 组总是以 TTL 31开始。阈值为 3 2的接口总是阻止本地事件视频。另外, I E T F频道1低速率音 频,只受到 IP TTL固有的最大2 5 5跳的限制。它能传播通过整个 M B O N E。MBONE 内的路由 器的管理员可以选择阈值,有选择地接受或丢弃 MBONE数据流。 12.9.2 扩展环搜索 多播TTL的另一种用处是,只要改变探测数据报的初始 TTL值,就能在互联网上探测资源。 这个技术叫做扩展环搜索 (expanding-ring search,[Boggs 1982])。初始TTL 为0的数据报只能 到达与外出接口相关的本地网络上的一个资源; T T L为1,则到达本地子网 (如果存在 )上的资 源;TTL为2,则到达相距2跳的资源。应用程序指数地增加 TTL的值,迅速地在大的互联网上 探测资源。 RFC 1546 [Partridge 、M e n d e z和Milliken 1993] 描述了一种相关业务的任播 (a n y c a s t i n g)。任播依赖一组显著的 I P地址来表示更像多播的多个主机的组。与多播 地址不同,网络必须传播所有任播的分组,直到它被至少一个主机接收。这样简化 了应用程序的实现,不再进行扩展环搜索。

12.10 ip_setmoptions函数 i p _ s e t m o p t i o n s 函数块包括一个用来处理各选项的 s w i t c h 语句。图 1 2 - 1 9是

279

第 12章 IP 多 播计计

ip_setmoptions的开始和结束。下面几节讨论 switch的语句体。

图12-19 ip_setmoptions

650-664

函数

第一个参数, o p t n a m e,指明正在改变哪个多播参数。第二个参数, i m o p,是

指向某个 i p _ m o t i o n s结构的指针。如果 * i m o p不空, i p _ s e t m o p t i o n s修改它所指向的

280

计计TCP/IP详解 卷 2:实现

结构。否则, i p _ s e t m o p t i o n s分配一个新的 i p _ m o p t i o n s结构,并把它的地址保存在 * i m o p里。如果没有内存了, i p _ s e t m o p t i o n s立即返回 E N O B U F S。后面的所有错误都通 告e r r o r,e r r o r在函数的最后被返回给调用方。第三个参数, m,指向存放要改变选项数 据的mbuf(图12-16的第二栏)。 1. 构造默认值 665-679

当分配一个新的 ip_moptions结构时,ip_setmoptions把默认的多播接口指

针初始化为空,把默认 T T L初始化为 1 (I P _ D E F A U L T _ M U L T I C A S T _ T T L),使能多播数据报 的回送,并清除组成员表。有了这些默认值后,ip_output查询路由表选择一个输出的接口, 多播被限制在本地网络中,并且,如果输出的接口是目的多播组的成员,则系统将接收它自 己的多播发送。 2. 进程选项 680-860

i p _ s e t m o p t i o n s体由一个 s w i t c h语句组成,其中对每种选项都有一个 c a s e

语句。default情况(对未知选项 )把error设成EOPNOTSUPP。 3. 如果默认值是OK,丢弃结构 861-872

s w i t c h语句之后,i p _ s e t m o p t i o n s检查i p _ m o p t i o n s结构。如果所有多播

选项与它们对应的默认值匹配,就不再需要该结构,将其释放。 i p _ s e t m o p t i o n s返回0或 公布的差错码。 12.10.1 选择一个明确的多播接口: IP_MULTICAST_IF 当o p t n a m e是I P _ M U L T I C A S T _ I F时,传给i p _ s e t m o p t i o n s的m b u f中就包含了多播 接口的单播地址,该地址指定了在这个插口上发送的多播所使用的特定接口。图 1 2 - 2 0是这个 选项的程序。

图12-20 ip_setmoptions

函数:选择多播输出接口

281

第 12章 IP 多 播计计

图12-20 (续)

1. 验证 如果没有提供 m b u f,或者 m b u f 中的数不是一个 i n _ a d d r 结构的大小,则

681-698

i p _ s e t m o p t i o n s 通告一个 E I N V A L差错;否则把数据复制到 a d d r。如果接口地址是 I N A D D R _ A N Y,则丢弃所有前面选定的接口。对后面用这个 i p _ m o p t i o n s结构的多播,将 根据它们的目的多播组进行选路,而不再通过一个明确命名的接口 (图12-40)。 2. 选择默认接口 699-710

如果addr中有地址,就由 INADDR_TO_IFP找到匹配接口的位置。如果找不到匹

配或接口不支持多播,就发布

E A D D R N O T A V A I L 。否则,匹配接口 i f p 成为与这个

ip_moptions结构相关的输出请求的多播接口。 12.10.2 选择明确的多播 TTL:IP_MULTICAST_TTL 当o p t n a m e是I P _ M U L T I C A S T _ T T L时,缓存中有一个字节指定输出多播的 IP TTL。这 个TTL是ip_output在每个发往相关插口的多播数据报中插入的。图 12-21是该选项的程序。

图12-21 ip_setmoptions

函数:选择明确的多播 TTL

验证和选项默认的 TTL 711-720

如果缓存中有一个字节的数据,就把它复制到 imo_multicast_ttl。否则,发

布EINVAL。 12.10.3 选择多播环回: IP_MULTICAST_LOOP 通常,多播应用程序有两种形式: • 一个系统内一个发送方和多个远程接收方的应用程序。这种配置中,只有一个本地进程 向多播组发送数据报,所以无需回送输出的多播。这样的例子有多播选路守护进程和会 议系统。 • 一个系统内的多个发送方和接收方。必须回送数据报,确保每个进程接收到系统其他发

282

计计TCP/IP详解 卷 2:实现

送方的传送。 IP_MULTICAST_LOOP选项(图12-22)为i p _ m o p t i o n s结构选择回送策略。

图12-22 ip_setmoptions

函数:选择多播环回

验证和选择环回策略 721-732

如果m为空,或者没有 1字节数据,或者该字节不是 0或1,就发布EINVAL。否则,

把该字节复制到 imo_multicast_loop。0指明不要把数据报回送, 1允许环回机制。 图1 2 - 2 3 显示了多播数据报的最大辖域值之间的关系:

imo_multicast_ttl和

imo_multicast_loop。

图12-23 环回和TTL对多播辖域的影响

图1 2 - 2 3显示了根据发送的环回策略,指定的 T T L值接收多播分组的接口的设置。如果硬 件接收自己的发送,则不管采用什么环回策略,都接收分组。数据报可能通过选路穿过该网 络,并到达与系统相连的其他接口 (习题1 2 . 6 )。如果发送系统本身是一个多播路由器,输出的 分组可能被转发到其他接口,但是,只有一个接口接受它们进行输入处理 (第14章)。

12.11 加入一个IP多播组 除了内核自动加入 (图6-17)的IP所有主机组外,其他组成员是由进程明确发出请求产生的。 加入(或离开)多播组选项比其他选项更多使用。必须修改接口的 i n _ m u l t i表以及其他链路层 多播结构,如我们在以太网中讨论的 ether_multi。 当o p t n a m e 是I P _ A D D M E M B E R S H I P 时, m b u f 中的数据是一个如图 1 2 - 2 4所示的 ip_mreq结构。

图12-24 ip_mreq 结构

283

第 12章 IP 多 播计计

148-151

imr_multiaddr指定多播组,imr_interface用相关的单播IP地址指定接口。

ip_mreq结构指定{接口,组}对表示成员的变化。 图12-25显示了加入和离开与我们的以太网接口例子相关的多播组时,所调用的函数。

图12-25 加入和离开一个多播组

我们从 i p _ s e t m o p t i o n s(图1 2 - 2 6 )的I P _ A D D _ M E M B E R S H I P情况开始,在这里修改 i p _ m o p t i o n s结构。然后我们跟踪请求通过 IP层、以太网驱动程序,一直到物理设备—在 这里,是LANCE以太网网卡。

图12-26 ip_setmoptions

函数:加入一个多播组

284

计计TCP/IP详解 卷 2:实现

图12-26 (续)

1. 验证 733-746

ip_setm o p t i o n s从验证该请求开始。如果没有传给 mbuf,或缓存的大小不对,

或结构的地址 (i m r _ m u l t i a d d r)不是一个多播组地址,则 i p _ s e t m o p t i o n s发布E N I V A L。 Mreq指向有效ip_mreq地址。 2. 找到接口 747-774

如果接口的单播地址 (imr_interface)是INADDR_ANY,则ip_setmoptions

285

第 12章 IP 多 播计计

必须找到指定组的默认接口。该多播组构造一个

r o u t e 结构,作为目的地址,并传给

r t a l l o c,由r t a l l o c为多播组找到一个路由器。如果没有路由器可用,则请求失败,产生 错误E A D D R N O T A V A I L。如果找到路由器,则在 i f p中保存指向路由器外出接口的指针,而 不再需要路由器入口,将其释放。 如果i m r _ i n t e r f a c e不是I N A D D R _ A N Y,则请求一个明确的接口。 I N A D D R _ T O _ I F P 宏用请求的单播地址搜索接口。如果没有找到接口或者它支持多播,则请求失败,产生错误 EADDRNOTAVAIL。 8 . 5节描述了 r o u t e结构, 1 9 . 2节描述了 r t a l l o c函数,第 1 4章描述了用路由选 择表选择多播接口。 3. 已经是成员了? 775-792 对请求做的最后检查是检查 imo_membership数组,看看所选接口是否已经是请 求组的成员。如果 f o r循环找到一个匹配,或者成员数组为空,则发布

E A D D R I N U S E或

ETOOMANYREFS,并终止对这个选项的处理。 4. 加入多播组 793-803

此时,请求似乎是合理的了。 i n _ a d d m u l t i安排IP开始接收该组的多播数据报。

i n _ a d d m u l t i返回的指针指向一个新的或已存在的 i n _ m u l t i结构(图1 2 - 1 2 ),该结构位于 接口的多播组表中。这个结构被保存在成员数组中,并且把数组的大小加 1。 12.11.1 in_addmulti函数 i n _ a d d m u l t i和相应的 i n _ d e l m u l t i(图1 2 - 2 7和图1 2 - 3 6 )维护接口已加入多播组的表。 加入请求或者在接口表中增加一个新的 i n _ m u l t i结构,或者增加对某个已有结构的引用次 数。

图12-27 in_addmulti 函数:前半部分

1. 已经是一个成员了 469-487

ip_setmoptions已经证实ap指向一个D类多播地址,ifp指向一个能够多播的

286

计计TCP/IP详解 卷 2:实现

接口。 I N _ L O O K U P _ M U L T I(图1 2 - 1 4 )确定接口是否已经是该组的一个成员。如果是,则 in_addmulti更新引用计数后返回。 如果接口还不是该组的成员,则执行图 12-28中的程序。

图12-28 in_addmulti 函数:后半部分

2. 更新in_multi表 487-509

如果接口还不是成员,则 i n _ a d d m u l t i分配并初始化一个新的 i n _ m u l t i结构,

把该结构插到接口的 in_ifaddr(图12-13)结构中ia_multiaddrs表的前端。 3. 更新接口,通告变化 510-530

如果接口驱动程序已经定义了一个 if_ioctl函数,则i n _ a d d m u l t i构造一个

287

第 12章 IP 多 播计计

包含了该组地址的 i f r e q结构(图4 - 2 3 ),并把 S I O C A D D M U L T I请求传给接口。如果接口拒绝 该请求,则把 i n _ m u l t i 结构从链表中断开,释放掉。最后,

i n _ a d d m u l t i 调用

igmp_joingroup,把成员变化信息传播给其他主机和路由器。 in_addmulti返回一个指针,该指针指向 in_multi结构,或者如果出错,则为空。 12.11.2 slioctl和loioctl函数:SIOCADDMULTI和SIOCDELMULTI S L I P和环回接口的多播组处理很简单:除了检查差错外,不做其他事情。图 1 2 - 2 9显示了 SLIP处理。

图12-29 slioctl 函数:多播处理

673-687 不管请求为空还是不适用于 AF_INET协议族,都返回 EAFNOSUPPORT。 图12-30显示了环回处理。

图12-30 lioctl 函数:多播处理

152-166

环回接口的处理等价于图 1 2 - 2 9中S L I P 的程序。不管请求为空还是不适用于

AF_INET协议族,都返回 EAFNOSUPPORT。

288

计计TCP/IP详解 卷 2:实现

12.11.3 leioctl函数:SIOCADDMULTI和SIOCDELMULTI 在图4 - 2中,我们讲到 L A N C E以太网驱动程序的 l e i o c t l和i f _ i o c t l函数。图 1 2 - 3 1是 处理SIOCADDMULTI和SIOCDELMULTI的程序。

图12-31 leioctl 函数:多播处理

657-671

leioctl把增加和删除请求直接传给 ether_addmulti或ether_delmulti函

数。如果请求改变了该物理硬件必须接收的 I P多播地址集,则两个函数都返回 E N E T R E S E T。 如果发生了这种情况,则 leioctl调用lereset,用新的多播接收表重新初始化该硬件。 我们没有显示 l e r e s e t ,因为它是 L A N C E以太网硬件专用的。对多播来说, l e r e s e t安排硬件接收所有寻址到 e t h e r _ m u l t i中与该接口相关的多播地址的帧。 如果多播表中的每个入口是一个地址,则 L A N C E驱动程序采用散列机制。散列程序 使硬件可以有选择地接收分组。如果驱动程序发现某个入口是一个地址范围,它废 除散列策略,配置硬件接收所有多播分组。如果驱动程序必须回到接收所有以太网 多播地址的状态, lereset就在返回时把IFP_ALLMULTI标志位置位。 12.11.4 ether_addmulti函数 所有以太网驱动程序都调用 e t h e r _ a d d m u l t i函数处理 S I O C A D D M U L T I请求。这个函 数把IP D类地址映射到合适的以太网多播地址 (图12-5)上,并更新 ether_multi表。图12-32 是ether_multi函数的前半部。 1. 初始化地址范围 366-399

首先,e t h e r _ a d d m u l t i初始化a d d r l o和a d d r h i (两者都是六个无符号字符 )

中的多播地址范围。如果所请求的地址来自 A F _ U N S P E C族,e t h e r _ a d d m u l t i假定该地址 是一个明确的以太网多播地址,并把它复制到 a d d r l o和a d d r h i中。如果地址属于 A F _ I N E T 族 , 并 且 是 INADDR_ANY (0.0.0.0), ether_addmulti把 addrlo初 始 化 成 e t h e r _ i p m u l t i c a s t _ m i n,把a d d r h i初始化成 e t h e r _ i p m u l t i c a s t _ m a x。这两个以 太网地址常量定义为: u_char ether_ipmulticast_min[6] = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 }; u_char ether_ipmulticast_max[6] = { 0x01, 0x00, 0x5e, 0x7f, 0xff, 0xff };

289

第 12章 IP 多 播计计

图12-32 ether_addmulti

函数:前一半

与e t h e r b r o a d c a s t a d d r( 4 . 3节)一样,这是一个很方便地定义一个 48 bit常量 的方法。 I P多播路由器必须监听所有 I P多播。把组指定为 I N A D D R _ A N Y,被认为是请求加入所有 I P多播组。在这种情况下,所选择的以太网地址范围跨越了分配给 I A N A的整个 I P多播地址 块。 当 m r o u t e d ( 8 )守 护 程 序 开 始 对 到 多 播 接 口 的 分组 进 行 路 选 时 , 它用 INADDR_ANY发布一个 SIOCADDMULTI请求。 E T H E R _ M A P _ I P _ M U L T I C A S T把其他特定的 I P多播组映射到合适的以太网多播地址。当 发生EAFNOSUPPORT错误时,将拒绝对其他地址族的请求。 尽管以太网多播表支持地址范围,但是除了列举出所有地址外,进程或内核无法对某个 特定范围提出请求,因为总是把 addrlo和addrhi设成同一值。 e t h e r _ a d d m u l t i的第二部分,显示如图 1 2 - 3 3,证实地址范围,并且,如果该地址是

290

计计TCP/IP详解 卷 2:实现

新的,就把它加入表中。

图12-33 ether_addmulti

函数:后一半

2. 已经在接收 400-418

e t h e r _ a d d m u l t i检查高地址和低地址的多播比特位 (图4-12),保证它们是真正

的以太网多播地址。 E T H E R _ L O O K U P _ M U L T I(图12-9)确定硬件是否已经对指定的地址开始监 听。如果是,则增加匹配的 e t h e r _ m u l t i 结构中的引用计数 (e n m _ r e f c o u n t ),并且 ether_addmulti返回0。 3. 更新ether_multi表 419-441

如果这是一个新的地址范围,则分配并初始化一个新的 e t h e r _ m u l t i结构,把

它链到接口 a r p c o m结构(图1 2 - 8 )中的a c _ m u l t i a d d r s表上。如果 e t h e r _ a d d m u l t i返回

291

第 12章 IP 多 播计计

ENETRESET,则调用它的设备驱动程序就知道多播表被改变了,必须更新硬件接收过滤器。 图1 2 - 3 4显示在 L A N C E以太网接口加入所有主机组后, i p _ m o p t i o n s、i n _ m u l t i和 ether_multi结构之间的关系。

图12-34 多播数据结构的整体图

12.12 离开一个 IP多播组 通常 情 况 下 ,离 开一 个多 播组 的步 骤 是 加 入一 个多 播组 的步 骤的 反 序 。 更新 i p _ m o p t i o n s结构中的成员表、 I P接口的 i n _ m u l t i表和设备的 e t h e r _ m u l t i表。首先, 我们回到 ip_setmoptions中的IP_DROP_MEMBERSHIP情况语句,如图 12-35所示。

图12-35 ip_setmoptions

函数:离开一个多播组

292

计计TCP/IP详解 卷 2:实现

图12-35 (续)

1. 验证 804-830

存储器缓存中必然包含一个 ip_mreq结构,其中的imr_multiaddr必须是一个

多播组,而且必须有一个接口与单播地址 i m r _ i n t e r f a c e相关。如果这些条件不满足,则 发布EINVAL和EADDRNOTAVAIL错误信息,继续到该 switch语句的最后进行处理。 2. 删除成员引用 831-856

f o r循环用请求的 {接口,组 }对在组成员表中寻找一个 i n _ m u l t i结构。如果没

有找到,则发布 E A D D R N O T A V A I L错误信息。如果找到了,则 i n _ d e l m u l t i更新i n _ m u l t i 表,并且第二个 f o r循环把成员数组中不用的入口删去,把后面的入口向前移动。数组的大 小也被相应更新。 12.12.1 in_delmulti函数 因为可能会有多个进程接收多播数据报,所以调用 i n _ d e l m u l t i(图1 2 - 3 6 )的结果是, 当对in_multi结构没有引用时,只离开指定的多播组。 更新in_multi结构 534-567

i n _ d e l m u l t i一开始就减少 i n _ m u l t i结构的引用计数,如果该计数非零,则

返回。如果该计数减为 0,则表明在指定的 {接口,组 }对上,没有其他进程等待多播数据报。 调用igmp_leavegroup,但该函数不做任何事情,我们将在 13.8节中看到。 for循环遍历in_multi结构的链表,找到匹配的结构。

293

第 12章 IP 多 播计计

图12-36 in_delmulti 函数

for循环体只包含一个 continue语句。但所有工作都由循环上面的表达式做了, 并不需要continue语句,只是因为它比只有一个分号更清楚一些。 图12-9中的宏E T H E R _ L O O K U P _ M U L T I不用c o n t i n u e语句,仅有一个分号几乎 是不可检测的。 循环结束后,把匹配的 i n _ m u l t i 结构从链表上断开, i n _ d e l m u l t i向接口发布 S I O C D E L M U L T I请求,以便更新所有设备专用的数据结构。对以太网接口来说,这意味着更 新e t h e r _ m u l t i表。最后释放in_multi结构。 L A N C E驱动程序的 S I O C D E L M U L T I情况语句包括在图 1 2 - 3 1中,这里我们也讨 论了SIOCADDRMULTI情况。 12.12.2 ether_delmulti函数 当 I P 释放与某个以太网设备相关的 i n _ m u l t i 结构时,该设备也可能释放匹配的 e t h e r _ m u l t i 结构。我们说“可能”是因为 I P忽略其他监听 I P多播的软件。当 e t h e r _

294

计计TCP/IP详解 卷 2:实现

multi结构的引用计数变成 0时,就释放该结构。图 12-37是 ether_delmulti函数。

图12-37 ether_delmulti

函数

295

第 12章 IP 多 播计计

图12-37 (续)

445-479

e t h e r _ d e l m u l t i函数用 e t h e r _ a d d r m u l t i 函数采用的同一方法初始化

addrlo和addrhi数组。 1. 寻找ether_multi结构 480-494

E T H E R _ L O O K U P _ M U L T I寻找匹配的 e t h e r _ m u l t i结构。如果没有找到,则返

回 E N X I O 。如果找到匹配的结构,则把引用计数减去

1 。如果此时引用计数非零,

e t h e r _ d e l m u l t i立即返回。在这种情况下,可能会由于其他协议也要接收相同的多播分组 而释放该结构。 2. 删除ether_multi结构 495-511 for循环搜索ether_multi表,寻找匹配的地址范围,并从链表中断开匹配的结 构,将它释放掉。最后,更新链表的长度,返回 E N E T R E S E T,使设备驱动程序可以更新它的 硬件接收过滤器。

12.13 ip_getmoptions函数 取得当前的选项设置比设置它们要容易。 ip_getmoptions完成所有的工作,如图 12-38 所示。 复制选项数据和返回 876-914

i p _ g e t m o p t i o n s 的三个参数是: o p t n a m e ,要取得的选项; i m o,

i p _ m o p t i o n s结构;m p,一个指向 m b u f的指针。 m _ g e t分配一个 m b u f存放该选项数据。这 三个选项的指针 (分别是 a d d r、t t l和l o o p)被初始化为指向 m b u f的数据域,而 m b u f的长度 被设成选项数据的长度。 对I P _ M U L T I C A S T _ I F,返回I F P _ T O _ I A发现的单播地址,或者如果没有选择明确的多 播接口,则返回 INADDR_ANY。 对I P _ M U L T I C A S T _ T T L,返回i m o _ m u l t i c a s t _ t t l,或者如果没有选择明确的 TTL, 则返回1(IP_DEFAULT_MULTICAST_TTL)。 对I P _ M U L T I C A S T _ L O O P,返回i m o _ m u l t i c a s t _ l o o p,或者如果没有选择明确的多 播环回策略,则返回 1(IP_DEFAULT_MULTICAST_LOOP)。 最后,如果不识别该选项,则返回 EOPNOTSUPP。

296

计计TCP/IP详解 卷 2:实现

图12-38 ip_getmoptions

函数

12.14 多播输入处理: ipintr函数 到目前为止,我们已经讨论了多播选路,组成员关系,以及多种与 I P和以太网多播有关 的数据结构,现在转入讨论对多播数据报的处理。 在图4 - 1 3中,我们看到 e t h e r _ i n p u t检测到达的以太网多播分组,在把一个 I P分组放到 I P输入队列之前 (i p i n t r q),把m b u f首部的 M _ M C A S T标志位置位。 i p i n t r函数按顺序处理 每个分组。我们在 ipintr中省略的多播处理程序如图 12-39所示。 该段代码来自于 i p i n t r程序,用来确定分组是寻址到本地网络还是应该被转发。此时, 已经检测到分组中的错误,并且已经处理完分组的所有选项。 ip指向分组内的IP首部。

297

第 12章 IP 多 播计计

如果被配置成多播路由器,就转发分组 214-245

如果目的地址不是一个 I P多播组,则跳过整个这部分代码。如果地址是一个多播

组,并且系统被配置成 I P 多播路由器 (i p _ m r o u t e r ) ,就把 i p _ i d 转换成网络字节序 (i p _ m f o r w a r d希望的格式 ),并把分组传给 i p _ m f o r w a r d。如果出现错误或者分组是通过 一个多播隧道 (multicast tunnel)到达的,则 i p _ m f o r w a r d返回一个非零值。分组被丢弃,且 ips_cantforward的值加1。 我们在第14章中描述了多播隧道。它们在两个被标准 IP路由器隔开的多播路由器 之间传递分组。通过隧道到达的分组必须由 i p _ m f o r w a r d处理,而不是由 i p i n t r 处理。 如果ip_mforward返回0,则把ip_id转换回主机字节序,由 ipintr继续处理分组。 如果i p指向一个 I G M P分组,则接受该分组,并在 o u r s处(图1 0 - 11的i p i n t r)继续执行。 不管到达接口的每个目的组或组成员是什么,多播路由器必须接受所有 I G M P分组。 I G M P分 组中有组成员变化的信息。 246-257

根据系统是否被配置成多播路由器来确定是否执行图

1 2 - 3 9中的其余程序。

I N _ L O O K U P _ M U L T I搜索接口加入的多播组表。如果没有找到匹配,则丢弃该分组。当硬件 过滤器接受不需要的分组时,或者当与接口相关的多播组与分组中的目的多播地址映射到同 一个以太网地址时,才出现这种情况。 如果接受了该分组,就继续执行 ipintr(图10-11)的ours标号处的语句。

图12-39 ipintr 函数:多播输入处理

298

计计TCP/IP详解 卷 2:实现

图12-39 (续)

12.15 多播输出处理: ip_output函数 当我们在第 8章讨论i p _ o u t p u t时,推迟了对i p _ o u t p u t的m p参数和多播处理程序的讨 论。在 i p _ o u t p u t中,如果m p指向一个 i p _ m o p t i o n s结构,它就覆盖多播输出处理的默认 值。i p _ o u t p u t中省略的程序在图 1 2 - 4 0和图1 2 - 4 1中显示。 i p指向输出的分组, m指向包含 该分组的mbuf,ifp指向路由表为目的多播组选择的接口。

图12-40 ip_output 函数:默认和源地址

299

第 12章 IP 多 播计计

图12-40 (续)

图12-41 ip_output 函数:环回、转发和发送

300

计计TCP/IP详解 卷 2:实现

1. 建立默认值 129-155

只有分组是到一个多播组时,才执行图 1 2 - 4 0中的程序。此时, i p _ o u t p u t把

m b u f中的M _ M C A S T置位,并把 d s t重设成最终目的地址,因为 i p _ o u t p u t可能曾把它设成 下一跳路由器(图8-24)。 如果传递了一个 i p _ m o p t i o n s结构,则相应地改变 i p _ t t l和i f p。否则,把 i p _ t t l 设成1 (I P _ D E F A U L T _ M U L T I C A S T _ T T L),避免多播分组到达某个远程网络。查询路由表或 i p _ m o p t i o n s结构所得到的接口必须支持多播。如果不支持,则 i p _ o u t p u t丢弃该分组, 并返回ENETUNREACH。 2. 选择源地址 156-167

如果没有指定源地址,则由 f o r循环找到与输出接口相关的单播地址,并填入 I P

首部的ip_src。 与单播分组不同,如果系统被配置成一个多播路由器,则必须在一个以上的接口上发送 输出的多播分组。即使系统不是一个多播路由器,输出的接口也可能是目的多播组的一个成 员,也会需要接收该分组。最后,我们需要考虑一下多播环回策略和环回接口本身。把所有 这些都考虑进去,共有三个问题: • 是否要在输出的接口上接收该分组? • 是否向其他接口转发该分组? • 是否在出去的接口发送该分组? 图12-41显示了ip_output中解决这三个问题的程序。 3. 是否环回? 168-176

如果 I N _ L O O K U P _ M U L T I 确定输出的接口是目的多播组的成员,而且

i m o _ m u l t i c a s t _ l o o p非零,则分组被 i p _ m l o o p b a c k放到输出接口上排队,等待输入。 在这种情况下,不考虑转发原始分组,因为在输入过程中如果需要,分组的复制会被转发 的。 4. 是否转发? 178-197

如果分组不是环回的,但系统被配置成一个多播路由器,并且分组符合转发的条

件,则 i p _ m f o r w a r d向其他多播接口分发该分组的备份。如果 i p _ m f o r w a r d没有返回 0, 则ip_output丢弃该分组,不发送它。这表明分组中有错误。 为了避免 i p _ m f o r w a r d 和i p _ o u t p u t 之间的无限循环, i p _ m f o r w a r d 在调用 i p _ o u t p u t之前,总是把 I P _ F O R W A R D I N G打开。在本系统上产生的数据报是符合转发条件 的,因为运输层不打开 IF_FORWARDING。 5. 是否发送? 198-209

T T L是0的分组可能被环回,但从不转发它们 (i p _ m f o r w a r d丢弃它们 ),也从不

被发送。如果 T T L是0或者如果输出接口是环回接口,则 i p _ o u t p u t丢弃该分组,因为 T T L 超时,或者分组已经被 ip_mloopback环回了。 6. 发送分组 210-211

到这个时候,分组应该已经从物理上在输出接口上被发送了。

s e n d i t(i p _ o u t p u t,图8 - 2 5 )处的程序在把分组传给接口的 i f _ o u t p u t函数之前可能已经 把它分片了。我们将在 2 1 . 1 0节中看到,以太网输出函数 e t h e r _ o u t p u t调用a r p r e s o l v e,

301

第 12章 IP 多 播计计

a r p r e s o l v e又调用E T H E R _ M A P _ M U L T I C A S T,由E T H E R _ M A P _ M U L T I C A S T根据I P多播目 的地址构造一个以太网多播目的地址。 ip_mloopback函数 i p _ m l o o p b a c k 依靠 l o o u t p u t(图5 - 2 7 )完成它的工作。 i p _ m l o o p b a c k 传递的 l o o u t p u t 不是指向环回接口的指针,而是指向输出多播接口的指针。图

1 2 - 4 2 显示了

ip_mloopback函数。

图12-42 ip_mloopback

函数

复制并把分组放到队列中 929-956

仅仅复制分组是不够的;必须看起来分组已经被输出接口接收了,所以

i p _ m l o o p b a c k把i p _ l e n和i p _ o f f转换成网络字节序,并计算分组的检验和。 l o o u t p u t 把分组放到 IP输入队列。

12.16 性能的考虑 N e t / 3的多播实现有几个潜在的性能瓶颈。因为许多以太网网卡并不能完美地实现对多播 地址的过滤,所以操作系统必须能够丢弃那些通过硬件过滤器的分组。在最坏的情况下,以 太网网卡可能会接收所有分组,而其中大部分可能会被 i p i n t r发现不具有合法的 IP多播组地 址。 I P用简单的线性表和线性搜索过滤到达的 I P数据报。如果表增长到一定长度后,某些高 速缓存技术,如移动最近接收地址到表的最前面,将有助于提高性能。

12.17 小结 本章我们讨论了一个主机如何处理 I P多播数据报。我们看到,在 I P的D类地址和以太网多

302

计计TCP/IP详解 卷 2:实现

播地址的格式及它们之间的映射关系。 我们讨论了i n - m u l t i和e t h e r _ m u l t i结构,每个IP多播接口都维护一个它自己的组成 员表,而每个以太网接口都维护一个以太网多播地址。 在输入处理中,只有到达接口是目的多播组的成员时,该 I P多播才被接受下来。尽管如 果系统被配置成多播路由器,它们也可能被继续转发到其他接口。 被配置成多播路由器的系统必须接受所有接口上的所有多播分组。只要为 I N A D D R _ A N Y 地址发布SIOCADDMULTI命令,就可以迅速做到这一点。 i p _ m o p t i o n s结构是多播输出处理的基础。它控制对输出接口的选择、多播数据报 TTL 辖域值的设置以及环回策略。它也控制对 i n _ m u l t i结构的引用计数,从而决定接口加入或 离开某个IP多播组的时机。 我们也讨论了多播 TTL值实现的两个概念:分组生存期和分组辖域。

习题 12.1 发送I P广播分组到 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5和发送I P多播给所有主机组 2 2 4 . 0 . 0 . 1的区别是什 么? 12.2 为什么用多播代码中的 I P单播地址标识接口?如果接口能发送和接收多播地址,但 没有一个单播IP地址,必须做什么改动? 12.3 在12.3节中,我们讲到 32个IP组地址被映射到同一个以太网地址上。因为 32 bit地址 中的9 bit 不在映射中。为什么我们不说 512(29)个IP 组被映射到一个以太网地址上? 12.4 你认为为什么把 I P _ M A X _ M E M B E R S H I P S设成20?能被设得更大一些吗?提示:考 虑ip_moptions结构(图12-15)的大小。 12.5 当一个多播数据报被 I P环回并且被发送它的硬件接口接收 (即一个非单工接口 )时, 会发生什么情况? 12.6 画一个有一个多接口主机的网络图,即使该主机没有被配置成多播路由器,其他接 口也能接收到在某个接口上发送的多播分组。 12.7 通过SLIP和环回接口而不是以太网接口跟踪成员增加请求。 12.8 进程如何请求内核加入多于 IP_MAX_MEMBERSHIPS个组? 12.9 计算环回分组的检验和是多余的。设计一个方法,避免计算环回分组的检验和。 12.10 接口在不重用以太网地址的情况下,最多可加入多少个 IP多播组中? 12.11 细心的读者可能已经注意到 i n _ d e l m u l t i在发布 S I O C D E L M U L T I请求时,假定 接口已经定义了 ioctl函数。为什么这样不会出错? 12.12 如果请求一个未识别的选项,则 i p _ g e t m o p t i o n s中分配的 m b u f将会发生什么 情况? 12.13 为什么把组成员机制与用于接收单播和广播数据报的绑定机制分离开来?

第13章 IGMP:Internet组管理协议 13.1 引言 I G M P在本地网络上的主机和路由器之间传达组成员信息。路由器定时向“所有主机组” 多播I G M P查询。主机多播 I G M P报告报文以响应查询。 I G M P规范在RFC 111 2中。卷1的第1 3 章讨论了IGMP的规范,并给出了一些例子。 从体系结构的观点来看, IGMP是位于IP上面的运输层协议。它有一个协议号 (2),它的报 文是由 I P数据报运载的 (与I C M P一样)。与I C M P一样,进程通常不直接访问 I G M P,但进程可 以通过 I G M P插口发送或接收 I G M P报文。这个特性使得能够把多播选路守护程序作为用户级 进程实现。 图13-1显示了Net/3中IGMP协议的整体结构。

图13-1 IGMP处理概要

I G M P处理的关键是一组在图 1 3 - 1中心显示的 i n _ m u l t i 结构。到达的 I G M P 查询使 i g m p _ i n p u t为每个 i n _ m u l t i结构初始化一个递减定时器。该定时器由 i g m p _ f a s t t i m o 更新,当每个定时器超时时, igmp_fasttimo调用igmp_sendreport。 我们在第 1 2章中看到,当创建一个新的 i n _ m u l t i结构时, i p _ s e t m o p t i o n s调用 i g m p _ j o i n g r o u p。i g m p _ j o i n g r o u p调用i g m p _ s e n d r e p o r t来发布新的组成员信息, 使组的定时器能够在短时间内安排第二次通告。 igmp_sendreport完成对IGMP报文的格式

304

计计TCP/IP详解 卷 2:实现

化,并把它传给 ip_output。 在图13-1的左边和右边,我们看到一个原始插口可以直接发送和接收 IGMP报文。

13.2 代码介绍 图13-2中列出了实现IGMP协议的4个文件。 文







netinet/igmp.h netinet/igmp_var.h netinet/in_var.h

IGMP协议定义 IGMP实现定义 IP多播数据结构

netinet/igmp.c

IGMP协议实现

图13-2 本章讨论的文件

13.2.1 全局变量 本章中介绍的新的全局变量显示在图 13-3中。 变



数据类型

igmp_all_hosts_group igmp_timer_are_running igmp_stat





u_long 网络字节序的“所有主机组”地址 int 如果所有 IGMP定时器都有效,则为真;否则为假 s t r u c t i g m p s t a t IGMP统计(图13-4)

图13-3 本章介绍的全局变量

13.2.2 统计量 IGMP统计信息是在图 13-4的igmpstat变量中维护的。 描

I g m p s t a t成员 igps_rcv_badqueries igps_rcv_badreports igps_rcv_badsum igps_rcv_ourreports igps_rcv_queries igps_rcv_reports igps_rcv_tooshort igps_rcv_total igps snd reports



作为无效查询接收的报文数 作为无效报告接收的报文数 接收的报文检验和错误数 作为逻辑组的报告接收的报文数 作为成员关系查询接收的报文数 作为成员关系报告接收的报文数 字节数太少的报文数 接收的全部报文数 作为成员关系报告发送的报文数

图13-4 IGMP统计

图13-5是在vangogh.cs.berkeley.edu上执行netstat -p

igmp命令后,输出的

统计信息。 在图1 3 - 5中,我们看到 v a n g o g h是连到一个使用 I G M P的网络上的,但是 v a n g o g h没有 加入任何多播组,因为 igps_snd_reports是0。

305

第 13章 IGMP:Internet组管理协议计计

成员

输出

图13-5 IGMP统计示例

13.2.3 SNMP变量 I G M P没有标准的 SNMP MIB,但 [McCloghrie Farinacci 1994a]描述了一个 I G M P的实验 MIB。

13.3 igmp结构 IGMP报文只有8字节长。图 13-6显示了Net/3使用的igmp结构。

图13-6 igmp结构

igmp_type包括一个4 bit 的版本码和一个 4 bit 的类型码。图13-7显示了标准值。 版本 1 1 1

类型 1 2 3



igmp_type



0x11(IGMP_HOST_MEMBERSHIP_QUERY) 成员关系查询 0 x 1 1 ( I G M P _ H O S T _ M E M B E R S H I P _ R E P O R 成员关系报告 T) 0x13 DVMRP报文(第14章)

图13-7 IGMP报文类型 IGMP报文

1

1

2 字节

IP首部 IP数据报

图13-8 IGMP 报文(省略igmp_ )

4 字节

306

计计TCP/IP详解 卷 2:实现

43-44

N e t / 3只使用版本 1的报文。多播路由器发送 1类报文 (I G M P _ H O S T _ M E M B E R S H I P _

Q U E R Y)向本地网络上所有主机请求成员关系报告。对 1类I G M P报文的响应是主机的一个 2类 报文(I G M P _ H O S T _ M E M B E R S H I P _ R E P O R T),报告它们的多播成员信息。 3类报文在路由器 之间传输多播选路信息 (第14章)。主机不处理3类报文。本章后面部分只讨论 1类和2类报文。 45-46

在I G M P版本1中没有使用i g m p _ c o d e。i g m p _ c k s u m与I P类似,计算I G M P报文的

所有8个字节。 47-48 对查询,igmp_group是0。对回答,它包括报告的多播组。 图13-8是相对于IP数据报的IGMP报文结构。

13.4 IGMP的protosw的结构 图13-9是IGMP的protosw结构。 成



pr_type pr_domain pr_protocol pr_flags pr_input pr_output pr_ctlinput pr_ctloutput pr_usrreq pr_init pr_fasttimo pr_slowtimo pr_drain pr_sysctl



I n e t s w[5] SOCK_RAW &inetdomain IPROTO_IGMP (2) PR_ATOMIC|PR_ADDR igmp_input rip_output 0 rip_ctloutput rip_usrreq igmp_init igmp_fasttino 0 0 0



IGMP提供原始分组服务 IGMP是Internet域的一部分 显示在IP首部的i p _ p字段 插口层标志,协议处理不使用 从IP层接收报文 向IP层发送IGMP报文 IGMP没有使用 响应来自进程的管理请求 响应来自进程的通信请求 为IGMP初始化 进程挂起成员关系报告 IGMP没有使用 IGMP没有使用 IGMP没有使用

图13-9 IGMP protosw 的结构

尽管进程有可能通过 IGMP p r o t o s w入口发送原始 I P分组,但在本章,我们只考虑内核 如何处理IGMP报文。第32章讨论进程如何用原始插口访问 IGMP。 三种事件触发IGMP处理: • 一个本地接口加入一个新的多播组 (13.5节); • 某个IGMP定时器超时 (13.6节);和 • 收到一个IGMP查询(13.7节)。 还有两种事件也触发本地 IGMP处理,但结果不发送任何报文: • 收到一个IGMP报告(13.7节);和 • 某个本地接口离开一个多播组 (13.8节)。 下一节将讨论这五种事件。

13.5 加入一个组:igmp_joingroup函数 在第 1 2 章中我们看到,当一个新的 i n _ m u l t i 结构被创建时, i n _ a d d m u l t i 调用 i g m p _ j o i n g r o u p。后面加入同一多播组的请求只增加 i n _ m u l t i结构里的引用计数;不调

307

第 13章 IGMP:Internet组管理协议计计

用igmp_joingroup。igmp_joingroup如图13-10所示。

图13-10 igmp_joingroup

164-178

函数

inm指向组的新 i n _ m u l t i结构。如果新的组是“所有主机组”,或成员关系请求

是环回接口的,则 i n m _ t i m e r被禁止, i g m p _ j o i n g r o u p返回。不报告“所有主机组”的 成员关系,因为假定每个多播主机都是该组的成员。没必要向环回接口发送组成员报告,因 为本地主机是在回路网络上的唯一系统,它已经知道它的成员状态了。 在其他情况下,新组的报告被立即发送,并根据组的情况为组定时器选择一个随机的值。 全局标志位 i g m p _ t i m e r s _ a r e _ r u n n i n g 被设置,表明至少使能一个定时器。 igmp_fasttimo (13.6节)检查这个变量,避免不必要的处理。 59-73

当新组的定时器超时,就发布第 2次成员关系报告。复制报告是无害的,当第一次报

告丢失或被破坏时,有了它就保险了。 IGMP_RANDOM_DELAY (13-11图)计算报告时延。

图13-11 IGMP_RANDOM_DELAY

函数

根据 R F C 1 1 2 2,报告定时器必须设成 0到 1 0之间的随机秒数 (I G M P _ M A X _ H O S T _ R E P O R T _ D E L A Y)。因为 IGMP 定时器每秒被减去 5次(P R _ F A S T H Z),所以 I G M P _ R A N D O M _ D E L A Y必须选择一个在 1 ~ 5 0之间的随机数。如果 r是把接到的所有 I P分组数、主机的原始地址 和多播组相加后得到的随机数,则

308

计计TCP/IP详解 卷 2:实现

0≤(rmod50)≤49 且 1≤(rmod50)+1≤50 要避免为0,因为这会禁止定时器,并且不发送任何报告。

13.6 igmp_fasttimo函数 在讨论igmp_fasttino之前,我们需要描述一下遍历 in_multi结构的机制。 为找到各个 i n _ m u l t i结构, N e t / 3必须遍历每个接口的 i n _ m u l t i表。在遍历过程中, in_multistep结构(如图13-12所示)记录位置。

图13-12 in_multistep

函数

123-126 i_ia指向下一个 in_ifaddr接口结构, i_inm指向当前接口的 in_multi结构。 IN_FIRST_MULTI和IN_NEXT_MULTI宏(显示如图13-13)遍历该表。

图13-13 IN_FIRST_MULTI

和IN_NEXT_MULTI

结构

309

第 13章 IGMP:Internet组管理协议计计

如果 i n _ m u l t i表有多个入口, i _ i n m就前进到下一个入口。当 I N _ N E X T _

154-169

M U L T I到达多播表的最后时, i _ i a就指向下一个接口, i _ i n m指向与该接口相关的第一个 i n _ m u l t i结构。如果该接口没有多播结构, w h i l e循环继续遍历整个接口表,直到搜索完 所有接口。 170-177

i n _ m u l t i s t e p数组初始化时,指向 i n _ i f a d d r表的第一个 i n _ i f a d d r结构,

i_inm设成空。 IN_NEXT_MULTI找到第一个 in_multi结构。 从图 1 3 - 9我们知道, i g m p _ f a s t t i m o 是I G M P 的快速超时函数,每秒被调用 5次。 igmp_fasttimo(如图13-14)递减多播报告定时器,并在定时器超时时发送一个报告。

图13-14 igmp_fasttino

187-198

结构

如果i g m p _ t i m e r s _ a r e _ r u n n i n g为假,i g m p _ f a s t t i m o立即返回,不再浪

费时间检查各个定时器。 199-213

i g m p _ f a s t t i m o重新设置运行标志位,用 I N _ F I R S T _ M U L T I初始化 s t e p和

i n m。i g m p _ f a s t t i m o函数用w h i l e循环找到各个 i n _ m u l t i结构和I N _ N E X T _ M U L T I宏。 对每个结构: • 如果定时器是0,什么都不做。 • 如果定时器不是 0,则将其递减。如果到达 0,则发送一个IGMP组成员关系报告。 • 如果定时器还不是 0,则至少还有一个定时器在运行,所以把 i g m p _ t i m e r s _ a r e _ running设成1。

310

计计TCP/IP详解 卷 2:实现

igmp_sendreport函数 igmp_sendreport函数(图13-15)为一个多播组构造和发送 IGMP报告报文。

图13-15 igmp_sentreport

214-232

函数

唯一的参数 i n m指向被报告组的 i n _ m u l t i结构。i g m p _ s e n d r e p o r t分配一个

新的m b u f,准备存放一个 I G M P报文。i g m p _ s e n d r e p o r t为链路层首部留下空间,把 m b u f

311

第 13章 IGMP:Internet组管理协议计计

的长度和分组的长度设成 IGMP报文的长度。 233-245 每次构造IP首部和IGMP报文的一个字段。数据报的源地址设成 INADDR_ANY,目 的地址是被报告的多播组。 i p _ o u t p u t用输出接口的单播地址替换 I N A D D R _ A N Y。每个组成 员和所有多播路由器都接收报告 (因为路由器接收所有 IP多播)。 2 4 6 - 2 6 0 最后,i g m p _ s e n t r e p o r t构造一个 i p _ m o p t i o n s结构,并把它与报文一起传 给i p _ o u t p u t。与i n _ m u l t i结构相关的接口被选做输出的接口; T T L被设成 1,使报告只 在本地网络上;如果本地系统被配置成路由器,则允许这个请求的多播环回。 进程级的多播路由器必须监听成员关系报告。在 1 2 . 1 4节中我们看到,当系统被 配置成多播路由器时,总是接收 I G M P数据报。通过普通的运输层分用程序把报文传 给IGMP的igmp_input和pr_input函数(图13-9)。

13.7 输入处理:igmp_input函数 在1 2 . 1 4节中,我们描述了 i p i n t r的多播处理部分。我们看到,多播路由器接受所有 I G M P报文,但多播主机只接受那些到达接口是目的多播组成员的 I G M P报文(也即,那些接收 它们的接口是组成员的查询和成员关系报告 )。 标准协议分用机制把接受的报文传给igmp_input。igmp_input的开始和结束如图13-16 所示。下面几节描述每种 IGMP报文类型码。

图13-16 igmp_input 函数

312

计计TCP/IP详解 卷 2:实现

图13-16 (续)

1. 验证IGMP报文 52-96

函数i p i n t r传递一个指向接受分组 (存放在一个m b u f中)的指针m,和数据报 I P首部

的大小iphlen。 数据报的长度必须足够容纳一个 I G M P报文(I G M P _ M I N _ L E N),并能被放在一个标准的 m b u f首部中(m _ p u l l u p),而且还必须有正确的 I G M P检验和。如果发现有任何错误,统计错 误的个数,并自动丢弃该数据报, igmp_input返回。 i g m p _ i n p u t 进程体根据 i g m p _ t y p e 内的代码处理无效报文。记得在图 1 3 - 6 中, i g m p _ t y p e包含一个版本码和一个类型码。 s w i t c h语句基于 i g m p _ t y p e(图1 3 - 7 )中两个值 的结合。下面儿节分别讨论几种情况。 2. 把IGMP报文传给原始IP 1 5 7 - 1 6 3 这个s w i t c h语句没有 d e f a u l t情况。所有有效报文 (也就是,格式正确的报文 ) 被传给 r i p _ i n p u t,在r i p _ i n p u t里被提交给所有监听 IGMP报文的进程。监听进程可以自 由处理或丢弃那些具有内核不识别的版本或类型的 IGMP报文。 mrouted依靠对rip_input的调用接收成员关系查询和报告。 13.7.1 成员关系查询: IGMP_HOST_MEMBERSHIP_QUERY RFC 1075 推荐多播路由器每 1 2 0秒至少发布一次 I G M P 成员关系查询。把查询发到 224.0.0.1组(“所有主机组” )。图13-17显示了主机如何处理报文。 97-122

到达环回接口上的查询报文被自动丢弃 (习题1 3 . 1 )。查询报文被定义成发给“所有

313

第 13章 IGMP:Internet组管理协议计计

主机组”,到达其他地址的查询报文由 igps_rcv_badqueries统计数量,并被丢弃。

图13-17 IGMP查询报文的输入处理

接受查询报文并不会立即引起IGMP成员报告。相反,igmp_input为与接收查询的接口相 关的各个组定时器设置一个随机的值 I G M P _ R A N D O M _ D E L A Y。当某组的定时器超时,则 igmp_fasttimo发送一个成员关系报告,与此同时,其他所有收到查询的主机也进行同一动作。 一旦某个主机上的某个特定组的随机定时器超时,就向该组多播一个报告。这个报告将取消其 他主机上的定时器,保证只有一个报告在网络上多播。路由器与其他组成员一样,接收该报告。 这个情况的一个例外就是“所有主机组”。这个组不设定时器,也不发送报告。 13.7.2 成员关系报告: IGMP_HOST_MEMBERSHIP_REPORT 接收一个 I G M P成员关系报告是我们在 1 3 . 1节中提到的不会产生 I G M P报文的两种事件之 一。该报文的效果限于接收它的接口本地。图 13-18显示了报文处理。 123-146

和发送到不正确多播组的成员关系报告一样,发到环回接口上的报告被丢弃。也

就是说,报文必须寻址到报文内标识的组。 不完整地初始化的主机的源地址中可能没有网络号或主机号

( 或两者都没有 ) 。

i g m p _ r e p o r t查看地址的 A类网络部分,如果地址的网络或子网部分是 0,这部分一定为 0。 如果是这种情况,则把源地址设成子网地址,其中包含正在接收接口的网络标识符和子网标 识符。这样做的唯一原因是为了通知子网号所标识的正在接收接口上的某个进程级守护程序。 如果接收接口属于被报告的组,就把相关的报告定时器重新设成 0。从而使发给该组的第 一个报告能够制止其他主机发布报告。路由器只需知道网络上至少有一个接口是组的成员,

314

计计TCP/IP详解 卷 2:实现

就无需维护一个明确的组成员表或计数器。

图13-18 IGMP报告报文的输入处理

13.8 离开一个组:igmp_leavegroup函数 我们在 1 2章中看到,当 i n _ m u l t i结构中的引用计数器跳到 0时, i n _ d e l m u l t i调用 igmp_leavegroup。如图13-19所示。

图13-19 igmp_leavegroup

函数

315

第 13章 IGMP:Internet组管理协议计计

179-186 当一个接口离开一个组时, IGMP没有采取任何动作。不发明确的通知—下一次 多播路由器发布 I G M P查询时,接口不为该组生成 I G M P报告。如果没有为某个组生成报告, 则多播路由器就假定所有接口已经离开该组,并停止把到该组的分组在网络上多播。 如果当一个报告被挂起时,接口离开了该组 (就是说,此时组的报告定时器正在计时 ),就 不再发送该报告,因为当 i c m p _ l e a v e g r o u p返回时, i n _ d e l m u l t i(图1 2 - 3 6 )已经把组的 定时器及其相关的 in_multi结构丢掉了。

13.9 小结 本章我们讲述了 I G M P,I G M P在一个网络上的主机和路由器之间传递 I P多播成员信息。 当一个接口加入一个组时,或按照多播路由器发布的 I G M P报告查询报文的要求,生成 I G M P 成员关系报告。 设计IGMP使交换成员信息所需要的报文数最少: • 当主机加入一个组时,宣布它们的成员关系; • 对成员关系查询的响应被推迟一个随机的时间,而且第一个响应抑制了其他的响应; • 当主机离开一个组时,不发通知报文; • 每分钟发的成员查询不超过一次。 多播路由器与其他路由器共享自己收集的 I G M P信息(第1 4章),以便于把多播数据报传给 多播目的组的远程成员。

习题 13.1 为什么不需要响应在环回接口上到达的 IGMP查询? 13.2 验证图13-15中226到229行的假设。 13.3 对在点到点网络接口上到达的成员关系查询,是否有必要设置随机的延迟时间?

第14章 IP多播选路 14.1 引言 前面两章讨论了在一个网络上的多播。本章我们讨论在整个互联网上的多播。我们将 讨论 m r o u t e d程序的执行,该程序计算多播路由表,以及在网络之间转发多播数据报的内 核函数。 从技术上说,多播分组 ( p a c k e t )被转发。本章我们假定每个多播分组中都包含一 个完整数据报 (也就是说,没有分片 ),所以我们只用名词数据报 ( d a t a g r a m )。N e t / 3转 发IP分片,也转发IP数据报。 描

m r o u t e d版本

图1 4 - 1是m r o u t e d的几个版本及它们和 B S D

1.2 2.0 3.3

版本的对应关系。 m r o u t e d版本包括用户级守护 程序和内核级多播程序。



修改4.3 BSD Tahoe版本 包括在4.4 BSD和Net/3中 修改SunOS 4.1.3

I P多播技术是一个活跃的研究和开发领域。 图14-1 mrouted 和IP多播版本

本章讨论包括在 N e t / 3中的多播软件的 2 . 0版,但

被认为已经过时了。 3 . 3版的发行还有一段时间,因此无法在本书中完整地讨论,但我们在整 个过程中将指出 3.3版本的一些特点。 因为还没有广泛安装商用多播路由器,所以常用多播隧道连接标准 I P单播互联网上的两 个多播路由器,构造多播网络。 Net/3支持多播隧道,并采用宽松源站记录路由 (LSRR,Loose Source Record Route)选项( 9 . 6节)构造多播隧道。一种更好的隧道技术把 I P多播数据报封装在 一个单播数据报里, 3.3版的多播程序支持这一技术,但 Net/3不支持。 与第1 2章一样,我们用通常名称运输层协议代指发送和接收多播数据报的协议,但 U D P 是唯一支持多播的 Internet协议。

14.2 代码介绍 本章讨论的三个文件显示在图 14-2中。 文



netinet/ip_mroute.h





多播结构定义

n e t i n e t / i p _ m r o u t e . c多播选路函数 netinet/raw_ip.c 多播选路选项

图14-2 本章讨论的文件

14.2.1 全局变量 多播选路程序所使用的全局变量显示在图 14-3中。

317

第14章 IP多播选路计计





cached_mrt cached_origin cached_originmask mrtstat mrttable numvifs viftable

数据类型 struct u_long u_long struct struct vifi_t struct





多播选路的“后面一个”高速缓存 “后面一个” 高速缓存的多播组 “后面一个” 高速缓存的多播组的掩码 m r t s t a t 多播选路统计 m r t * [ ] 指向多播路由器的指针的散列表 允许的多播接口数 vif[] 虚拟多播接口的数组

mrt

图14-3 本章介绍的全局变量

14.2.2 统计量 多播选路程序收集的所有统计信息都放在图 1 4 - 4的m r t s t a t结构中。图 1 4 - 5是在执行 n e t s t a t - g命令后,输出的统计信息。 s 描

mrts t a t成员 mrts_mrt_lookups mrts_mrt_misses mrts_grp_lookups mrts_grp_misses mrts_no_route mrts_bad_tunnel mrts_cant_tunnel



S N M P使用的

查找的多播路由数 高速缓存丢失的多播路由数 查找的组地址数 高速缓存丢失的组地址数 查找失败的多播路由数 有错误的隧道选项的分组数 没有空间存放隧道选项的分组数

图14-4 本章收集的统计量 输出

成员

图14-5 IP多播路由选择统计的例子

这些统计信息来自一个有两个物理接口和一个隧道接口的系统。它们说明, 9 8 %的时间, 在高速缓存中发现多播路由。组地址高速缓存的效率稍低一些,最高只有 3 4 %。图1 4 - 3 4描述 了路由缓存,图 14-21描述了组地址高速缓存。 14.2.3 SNMP变量 多播选路没有标准的 SNMP MIB,但 [ M c C l o g h r i e和Farinacci 1994a] 和 [ M c C l o g h r i e和 Farinacci 1994b] 描述一些多播路由器的实验 MIB。

14.3 多播输出处理(续) 1 2 . 1 5节讲到如何为输出的多播数据报选择接口。我们看到在

i p _ m o p t i o n s 结构中

318

计计TCP/IP详解 卷 2:实现

i p _ o u t p u t被传给一个明确的接口,或者 i p _ o u t p u t在路由表中查找目的组,并使用在路 由入口中返回的接口。 如果在选择了输出的接口后, i p _ o u t p u t回送该数据报,就把它放在所选输出接口等待 输入处理,当ipintr处理它时,把它当作是要转发的数据报。图 14-6显示了这个过程。 传输协议

传输协议

隧道

以太网

图14-6 有环回的多播输出处理

在图 1 4 - 6 中,虚线箭头代表原始输出的数据报,本例是本地以太网上的多播。 i p _ m l o o p b a c k 创建的备份由带箭头的细线表示;并作为输入被传给运输层协议。当 i p _ m f o r w a r d决定通过系统上的另一个接口转发该数据报时,就产生第三个备份。图 14-6中 最粗的箭头代表第三个备份,在多播隧道上发送。 如果数据报不是回送的,则 ip_output把 传输协议

它直接传给 ip_mforward,ip_mforward复 制并处理该数据报,就像它是从 i p _ o u t p u t 选定的接口上收到的一样。图 1 4 - 7显示了这个 过程。 一旦i p _ m f o r w a r d调用i p _ o u t p u t发送 多播数据报,它就把 I P _ F O R W A R D I N G置位, 这样, i p _ o u t p u t 就不再把数据报传回给 ip_mforward,以免导致无限循环。 图1 2 - 4 2显示了 i p _ m l o o p b a c k。1 4 . 8节

隧道

以太网

图14-7 没有环回的多播输出处理

描述了ip_mforward。

14.4 mrouted守护程序 用户级进程 m r o u t e d守护程序允许和管理多播路由选择。 m r o u t e d实现I G M P协议的路 由部分,并与其他多播路由器通信,实现网络间的多播路由选择。路由算法在 m r o u t e d上实 现,但内核维护多播路由选择表,并转发数据报。 本书中我们只讨论支持 m r o u t e d的内核数据结构和函数—不讨论m r o u t e d本身。我们 讨论用于为数据报选择路由的截断逆向路径广播 TRPB(Truncated Reverse Path Broadcast)算法 [ D e e r i n g和Cheriton 1990] ,以及用于在多播路由器之间传递信息的距离向量多播选路协议 DVMRP。我们力求使读者了解内核多播程序的工作原理。

319

第14章 IP多播选路计计

RFC 1075 [Waitzman、Partidge 和Deering1988] 是DVMRP的一个老版本。 mrouted实现 了一个新的 D V M R P ,还没有用 R F C 文档写出来。目前,该算法和协议的最好的文档是 mrouted发布的源代码。附录 B指出在哪里能找到到源代码。 m r o u t e d守护程序通过在一个 I G M P插口上设置选项与内核通信 (第3 2章)。这些选项总结 在图14-8中。 optname DVMRP_INIT DVMRP_DONE DVMRP_ADD_VIF DVMRP_DEL_VIF DVMRP_ADD_LGRP DVMRP_DEL_LGRP DVMRP_ADD_MRT DVMRP_DEL_MRT

o p t v a l类型





ip_mrouter_init ip_mrouter_done struct vifctl add_vif vifi_t del_vif s t r u c t l g r p l c t l add_lgrp s t r u c t l g r p l c t l del_lgrp struct mrtctl add_mrt s t r u c t i n a d d r del_mrt





m r o u t e d开始 m r o u t e d被关闭 增加虚拟接口 删除虚拟接口 为某个接口增加多播组入口 为某个接口删除多播组入口 增加多播路由 删除多播路由

图14-8 多播路由插口选项

图1 4 - 8显示的插口选项被 s e t s o c k o p t系统调用传给 r i p _ c t l o u t p u t( 3 2 . 8节)。图1 4 - 9 显示了处理 DVMRP_xxx 选项的rip_ctloutput部分。

图14-9 rip_ctloutput

173-187

函数:DVMRP_ xxx 插口选项

当调用 s e t s o c k o p t 时, o p等于 P R C O _ S E T O P T ,而且所有选项都被传给

ip_mrouter_cmd函数。对于 g e t s o c k o p t系统调用,o p等于P R C O _ G E T O P T;对所有选项 都返回EINVAL。 图14-10显示了ip_mrouter_cmd函数。

图14-10 ip_mrouter_cmd

函数

320

计计TCP/IP详解 卷 2:实现

图14-10 (续)

321

第14章 IP多播选路计计

这些“选项”更像命令,因为它们引起内核更新多个数据结构。本章后面我们 将使用命令 (command)一词强调这个事实。 84-92

m r o u t e d 发布的第一个命令必须是 D V M R P _ I N I T 。后续命令必须来自发布

DVMRP_INIT的同一插口。当在其他插口上发布其他命令时,返回 EACCES。 94-142 switch语句的每个 case语句检查每条命令中的数据量是否正确,然后调用匹配函 数。如果不能识别该命令,则返回 E O P N O T S U P P。任何从匹配函数返回的错误都在 e r r o r中 发布,并在函数的最后返回。 初始化时, mrouted发布DVMRP_INIT命令,调用图14-11显示的ip_mrouter_init。

图14-11 ip_mrouter_init

146-157

函数:DVMRP_INIT 命令

如果不是在某个原始 IGMP插口上发布命令,或者如果 D V M R P _ I N I T已经被置位,

则分别返回 EOPNOTSUPP和EADDRINUSE。全局变量ip_mrouter保存指向某个插口的指针, 初始化命令就是在该插口上发布的。必须在该插口上发布后续命令。以避免多个 m r o u t e d进 程的并行操作。 下面几节讨论其他 DVMRP_xxx命令。

14.5 虚拟接口 当作为多播路由器运行时, N e t / 3接收到达的多播数据报,复制它们,并在一个或多个接 口上转发备份。通过这种方式,数据报被转发给互联网上的其他多播路由器。 Ts HS

IP数据报

隧道

A

Te

B

HG

网络A

任意实现LSRR的单播IP路由器集

网络B

src=HS dst=G 没有LSRR的 硬件多播

src=HS dst=Te IP单播 LSRR={TS,G}

src=HS dst=G 没有LSRR的 硬件多播

图14-12 多播隧道

322

计计TCP/IP详解 卷 2:实现

输出的接口可以是一个物理接口,也可以是一个多播隧道。多播隧道的两端都与一个多 播路由器上的某个物理接口相关。多播隧道使两个多播路由器,即使被不能转发多播数据报 的路由器分隔,也能够交换多播数据报。图 14-12是一个多播隧道连接的两个多播路由器。 图1 4 - 1 2中,网络 A上的源主机H S正在向组 G多播数据报。组 G的唯一成员在网络 B上,并 通过一个多播隧道连接到网络 A。路由器 A接收多播 (因为多播路由器接收所有多播 ),查询它 的多播路由选择表,并通过多播隧道转发该数据报。 隧道的开始是路由器 A上的一个物理接口,以 I P单播地址 T s标识。隧道的结束是网络 B上 的一个物理接口,以 I P单播地址 Te 标识。隧道本身是一个任意复杂的网络,由实现 L S R R选项 的IP单播路由器连接起来。图 14-13显示IP LSRR选项如何实现多播隧道。 系统

HS Ts Te Te

源路由选项

IP首部 ip_src

ip_dst

偏移

HS HS HS HS

G Te G G

8 12









Ts • G T s 见正文

在网络A上 在隧道上 在路由器 B上i p _ d o o p t i o n s之后 在路由器 B上i p _ m f o r w a r d之后

图14-13 LSRR多播隧道选项

图1 4 - 1 3的第一行是 H S在网络 A上发送的多播数据报。路由器 A全部接收,因为多播路由 器接收本地连接的网络上的所有数据报。 为通过隧道发送数据报,路由器 A在I P首部插入一个 L S R R选项。第二行是在隧道上离开 A时的数据报。 L S R R选项的第一个地址是隧道的源地址,第二个地址是目的多播组地址。数 据报的目的地址是 T e—隧道的另一端。 LSRR偏移指向目的组。 经过隧道的数据报被转发,通过互联网,直到它到达路由器 B上的隧道的另一端。 该图中的第三行是被路由器 B上的ip_dooptions处理之后的数据报。记得第9章中讲到, i p _ d o o p t i o n s在i p i n t r检查数据报的目的地址之前处理 LSRR选项。因为数据报的目的地 址(T e)和路由器 B上的一个接口匹配,所以 i p _ d o o p t i o n s把由选项偏移 (本例中是 G )标识的 地址复制到 I P 首部的目的地址字段。在选项内,

G 被 i p _ r t a d d r 返回的地址取代,

i p _ r t a d d r通常根据 I P目的地址 (本例中是 G )为数据报选择输出的接口。这个地址是不相关 的,因为 ip_mforward将丢弃整个选项。最后, ip_dooptions把选项偏移向前移动。 图1 4 - 1 3的第四行是 i p i n t r调用i p _ m f o r w a r d之后的数据报。在那里, L S R R选项被识 别,并从数据报首部中移走。得到的数据报看起来就象原始多播数据报,由 i p _ m f o r w a r d 处理它,把它作为多播数据报在网络 B上转发,并被HG收到。 用L S R R构造的多播隧道已经过时了。因为 1 9 9 3年3月发布了 m r o u t e d程序,该程序通过 在I P多播数据报的首部前面加上另一个 I P首部来构造隧道。新 I P首部的协议设置为 4,表明分 组的内容是另一个 I P分组。有关这个值的文档在 RFC 1700—“I P中的I P”协议中。新版本 的mrouted程序为了向后兼容,也支持 LSRR隧道。 14.5.1 虚拟接口表 无论物理接口还是隧道接口,内核都为其在虚拟接口 (virtual interface)表中维护一个入口, 其中包含了只有多播使用的信息。每个虚拟接口都用一个 v i f结构表示 (图1 4 - 1 4 )。全局变量

323

第14章 IP多播选路计计

viftable是一个这种结构的数组。数组的下标保存在无符号短整数 vifi_t变量中。

图14-14 vif 结构

105-110

为v _ f l a g s定义的唯一的标志位是 V I F F _ T U N N E L。被置位时,该接口是一个到

远程多播路由器的隧道。没有置位时,接口是在本地系统上的一个物理接口。 v_threshold 是我们在 1 2 . 9节描述的多播阈值。 v _ l c l _ a d d r是与这个虚拟接口相关的本地接口的 I P地址。 v_rmt_addr是一个IP多播隧道远端的单播 IP地址。v_lcl_addr或者v_rmt_addr为非零, 但不会两者都为非零。对物理接口, v _ i f p非空,并指向本地接口的 i f n e t结构。对隧道, v_ifp是空的。



图14-15 viftable 数组

324

计计TCP/IP详解 卷 2:实现

111-116

v_lcl_grps指向一个IP 多播组地址数组,这个数组记录了在连到的接口上的成

员组列表。对隧道来说, v _ l c l _ g r p s总是空的。数组的大小保存在 v _ l c l _ g r p s _ m a x中, 被使用的入口数保存在 v _ l c l _ g r p s _ n 中。数组随着组成员关系表的增大而增长。 v _ c a c h e d _ g r o u p和v _ c a c h e d _ r e s u l t实现“一个入口”高速缓存,其中记录的是最近 一次查找得到的组。 图1 4 - 1 5说明了v i f t a b l e,它最多有 3 2个(M A X V I F S)入口。v i f t a b le [ 2 ]是正在使用的 最后一个入口,所以 n u m v i f s是3。编译内核时固定了表的大小。图中还显示了表的第一个 入口的vif结构的几个成员。 v_ifp指向一个ifnet结构,v_lcl_grps指向in_addr结构中 的一个数组。数组有 32(v_lcl_grps_max)个入口,其中只用了 4个(v_lcl_grps_n)。 m r o u t e d通过 D V M R P _ A D D _ V I F和D V M R P _ D E L _ V I F命令维护 v i f t a b l e。通常,当 m r o u t e d开始运行时,会把本地系统上有多播能力的接口加入表中。当 m r o u t e d阅读自己的 配置文件,通常是 / e t c / m r o u t e d . c o n f时,会把多播隧道加入表中。这个文件中的命令也 可能从虚拟接口表中删除物理接口,或者改变与接口有关的多播信息。 m r o u t e d用D V M R P _ A D D _ V I F命令把c t l结构(图1 4 - 1 6 )传给内核。它指示内核在虚拟接 口表中加入一个接口项。

图14-16 vifctl 结构

78-82

v i f c _ v i f i识别v i f t a b l e 中虚拟接口的下标。其他 4个成员, v i f c _ f l a g s、

vifc_threshold、vifc_lcl_addr和vifc_rmt_addr,被add_vif函数复制到vif函数中。 14.5.2 add_vif函数 图14-17是add_vif函数。

图14-17 add_vif 函数:DVMRP_ADD_VIF

命令

325

第14章 IP多播选路计计

图14-17 (续)

1. 验证下标 202-216

如果m r o u t e d指定的v i f c _ v i f i中的下标太大,或者该表入口已经被使用,则

分别返回 EINVAL或EADDRINUSE。 2. 本地物理接口 217-221 ifa_ifwithaddr取得vifc_lcl_addr中的单播IP地址,并返回一个指向相关 i f n e t结构的指针。这就标识出这个虚拟接口要用的物理接口。如果没有匹配的接口,返回 EADDRNOTAVAIL。 3. 配置隧道接口 222-224 对于隧道,它的远端地址被从 vifctl结构中复制到接口表的 vif结构中。 4. 配置物理接口 225-243

对于物理接口,链路级驱动程序必须支持多播。

S I O C A D D M U L T I 命令与

326

计计TCP/IP详解 卷 2:实现

I N A D D R _ A N Y一起配置接口,开始接收所有 I P多播数据报 (图1 2 - 3 2 ),因为它是一个多播路由 器。当ipintr把到达数据报传给 ip_mforward时,被ip_mforward转发。 5. 保存多播信息 244-253

其他接口信息被从 v i f c t l结构复制到 v i f结构。如果需要,更新 n u m v i f s,记

录正在使用的虚拟接口数。 14.5.3 del_vif函数 图1 4 - 1 8显示的 d e l _ v i f 函数从虚拟接口表中删除表项。当 m r o u t e d设置 D V M R P _ DEL_VIF命令时,调用该函数。 1. 验证下标 257-268

如果传给del_vif的下标大于正在使用的最大下标,或者指向一个没有使用的入

口,则分别返回 EINVAL和EADDRNOTAVAIL。

图14-18 del_vif 函数:DVMRP_DEL_VIF

命令

2. 删除接口 269-278

对于物理接口,释放本地多播组表, S I O C A D D M U L T I禁止接收所有多播数据报,

bzero对viftable的入口清零。

327

第14章 IP多播选路计计

3. 调整接口计数 279-286

f o r循环从以前活动的最大入口开始向后直到第一个入口为止,搜索出第一个活

动的入口。对没有使用的入口, v _ l c l _ a d d r(一个i n _ a d d r结构)的成员 s _ a d d r是0。相应 地更新numvifs,函数返回。

14.6 IGMP(续) 第13章侧重于IGMP协议的主机部分,mrouted实现了这个协议的路由器部分。 mrouted 必须为每个物理接口记录哪个多播组有成员在连到的网络上。

m r o u t e d每1 2 0秒多播一个

IGMP_HOST_MEMBERSHIP_QUERY数据报,并把IGMP_HOST_MEMBERSHIP_REPORT的结 果汇编到与每个网络相关的成员关系数组中。这个数组不是我们在第 13章讲的成员关系表。 m r outed根据收集到的信息构造多播路由选择表。多播组表也提供信息,用来抑制向没有 目的组成员的多播互联网区进行多播。 只为物理接口维护这样的成员关系数组。对其他多播路由器来说,隧道是点到点接口, 所以无需组成员关系信息。 我们在图 1 4 - 1 4中看到, v _ l c l _ g r p s指向一个 I P多播组数组。 m r o u t e d 用D V M R P _ A D D _ L G R P和D V M R P _ D E L _ L G R P命令维护这个表。两个命令都带了一个 l g r p c t l 结构 (图14-19)。

图14-19 lgrpctl 结构

进程 内核 选项

数据报

图14-20 IGMP报告处理

328

计计TCP/IP详解 卷 2:实现

87-90

l g c _ v i f i和l g c _ g a d d r标识{接口,组 }对。接口下标 (无符号短整数 l g c _ v i f i)

标识一个虚拟接口,而不是物理接口。 当收到一个 IGMP_HOST_MEMBERSHIP_REPORT时,调用图 14-20所示的函数。 14.6.1 add_lgrp函数 m r o u t e d检查到达 I G M P报告的源地址,确定是哪个子网,从而确定报告是哪个接口接

图14-21 add_lgrp 函数:DVMRP_ADD_GLRP

命令

329

第14章 IP多播选路计计

收的。根据这个信息, m r o u t e d为该接口设置 D V M R P _ A D D _ L G R P命令,更新内核中的成员 关系表。这个信息也被送到多播路由选择算法,更新路由选择表。图 1 4 - 2 1显示了 a d d _ l g r p 函数。 1. 验证增加请求 291-301

如果该请求标识了一个无效接口,就返回 E I N V A L。如果没有使用该接口或它是

一个隧道,则返回 EADDRNOTAVAIL。 2. 如果需要,扩展组数组 302-326

如果新组无法放在当前的组数组中,就分配一个新的数组。第一次为接口调用

add_lgrp函数时,分配一个能装 32个组的数组。 每次数组被填满后, a d d _ l g r p就分配一个两倍于前面数组大小的新数组。 M a l l o c负责 分配, b z e r o 负责清零, b c o p y 把旧数组中的内容复制到新数组中。更新最大入口数 v_lcl_grps_max,释放旧数组(如果有的话 ),把新数组和v_lcl_grps连接到vif入口。 “偏执狂(paranoid)”评论指出,无法保证 malloc分配的内存全部是 0。 3. 增加新的组 327-332

新组被复制到下一个可用的入口,如果高速缓存中已经存放了新组,就把高速缓

存标记为有效。 查找高速缓存中包含一个地址 v _ c a c h e d _ g r o u p ,以及一个高速缓存的查找结果 v _ c a c h e d _ r e s u l t。g r p l s t _ m e m b e r函数在搜索成员关系数组之前,总是先查一下这个 高速缓存。如果给定的组与 v _ c a c h e d _ g r o u p匹配,就返回高速缓存的查找结果;否则,搜 索成员关系数组。 14.6.2 del_lgrp函数 如果在 2 7 0秒内,没有收到该组任何成员关系的报告,则每个接口的组信息超时。 m r o u t e d维护适当的定时器,并当信息超时后,发布 D V M R P _ D E L _ L G R P命令。图 1 4 - 2 2显示 了del_lgrp。 1. 验证接口下标 337-347

如果请求标识无效接口,叫返回 E I N V A L。如果该接口没有使用或是一个隧道,

则返回EADDRNOTAVAIL。 2. 更新查找高速缓存 348-350 如果要删除的组在高速缓存里,就把查找结果设成 0(假)。 3. 删除组 351-364

如果在成员关系表中没有找到该组,则在 e r r o r中发布E A D D R N O T A V A I L。f o r

循环搜索与该接口相关的成员关系数组。如果 s a m e(是一个宏,用 b c m p比较两个地址 )为真, 则清除 e r r o r,把组计数器加 1。b c o p y移动后续的数组入口,删除该组, d e l _ l g r p跳出该 循环。 如果循环结束,没有找到匹配,则返回 EADDRNOTAVAIL;否则返回 0。

330

计计TCP/IP详解 卷 2:实现

图14-22 del_lgrp 函数:DVMRP_ DEL_LGRP命令

14.6.3 grplst_member函数 在转发多播时,查询成员关系数组,以免把数据报发到没有目的组成员的网络上。图 1 4 23显示的grplst_member函数,搜索整个表,寻找给定组地址。

图14-23 grplst_member

函数

331

第14章 IP多播选路计计

图14-23 (续)

1. 检查高速缓存 368-379 如果请求的组在高速缓存中,则返回高速缓存的结果,不搜索成员关系数组。 2. 搜索成员关系数组 380-390

对数组进行线性搜索,确定组是否在其中。如果找到,就更新高速缓存以记录匹

配的值,并返回 1;如果没有找到,就更新高速缓存记录丢失的,并返回 0。

14.7 多播选路 正如在本章开始提到的,我们不给出 m r o u t e d实现的 T R P B算法,但给出一个有关该机 制的综述,描述内核的多播路由选择表和多播路由选择函数。图 1 4 - 2 4显示了一个我们用于解 释该算法的示例多播网络。

网络 A

隧道 C

E

网络E

网络 C 网络 B

D 网络 D

图14-24 多播网络示例

图1 4 - 2 4中,方框代表路由器,椭圆代表连接到路由器的多播网络。例如,路由器 D可以 在网络 D和网络 C上多播。路由器 C可以向网络 C多播,通过点到点接口向路由器 A和B多播, 并可以通过一个多播隧道向路由器 E多播。 最简单的路由选择办法是,从互联网拓扑中选出一个子网,形成一个生成树。如果每个 路由器都沿着生成树转发多播,则各路由器最终会收到数据报。图 1 4 - 2 5显示了示例网络的一 个生成树。其中,网络 A上的主机S是多播数据报的源。 有关生成树的讨论,参见 [Tanenbaum 1989] 或 [Perlman 1992]。

332

计计TCP/IP详解 卷 2:实现

隧道

图14-25 网络A的生成树

这个生成树是根据从各网络回到网络 A上的源站的最短逆向路径 (reverse path)构造的。图 1 4 - 2 5的生成树中,省略了路由器 B和C之间的线路。源站和路由器 A之间的箭头,以及路由器 B和C之间的箭头,强调了多播网络是生成树的一部分。 如果用同一生成树转发来自网络 C的数据报,为了在网络 B上收到,数据报经过的转发路 径将大于需要的长度。 RFC 1075提出的算法为每个潜在的源站计算了一个单独的生成树,以 避免这种情况。路由选择表为每条路由记录了一个网络号和子网掩码,所以一条路由可以应 用到源子网内的任意主机。 因为构造生成树是为了给源站的数据报提供最短逆向路径,而每个网络都接收所有多播 数据报,所以这个过程称为逆向路径广播 (reverse path broadcast)即RPB。 R P B没有任何多播组成员信息,使许多数据报被不必要地转发到没有目的组成员的网络 上。如果,除了计算生成树外,该路由选择算法还能记录哪些网络是叶子,注意到每个网络 上的组成员关系,那么,连到叶子网络的路由器就可以避免把数据报转发到没有目的组成员 的网络上去。这称为截断逆向路径广播 ( T R P B ),2 . 0版的m r o u t e d在I G M P帮助下记录叶子网 络上的成员关系,从而实现这一算法。 图1 4 - 2 6显示了 T R P B算法的应用。多播来自网络 C上的源站,并在网络 B上有一个目的组 成员。 我们用图14-26说明Net/3多播路由选择表中使用的名词。在这个例子中,有阴影的网络和

隧道

图14-26 网络C的TRPB路由选择

333

第14章 IP多播选路计计

路由器收到来自网络 C上源站的数据报。A和B之间的线路不属于生成树,C与D之间没有连接, 因为C和D直接收到源站发送的多播。 在这个图中,网络 A、B、D和E是叶子网络。路由器 C接收多播,并通过连到路由器 A、 B和E的接口将其转发—尽管把它发给A和E都是浪费。这是 TRPB算法的缺点。 路由器 C上与网络 C相关的接口叫做父亲,因为路由器 C期望用它接收来自网络 C的多播。 从路由器 C到路由器 A、B和E的接口叫做儿子接口。对路由器 A来说,点到点接口是来自 C的 源分组的父亲,到网络 A的接口是儿子。接口相对于数据报的源站,被标识为父亲和儿子。只 在相关的儿子接口上转发多播数据报,不在父亲接口上转发多播。 继续我们的例子,因为网络 A、D和E是叶子网络,并且没有目的组成员,所以它们没有 阴影。在路由器处截断生成树,也不把数据报转发到这些网络上去。路由器 B把数据报转发到 网络B上,因为 B上有一个目的组成员。为实现截断算法,接收数据报的所有路由器都在自己 的viftable中查询与每个虚拟接口相关的组表。 对该多播路由选择算法的最后一个改进叫做逆向路径多播 (reverse path multicasting , R P M )。R P M的目的是修剪 (p r u n e)各生成树,避免在没有目的组成员的分支上发送数据报。 在图14-26中,RPM可以避免路由器 C向A和E发送数据报,因为在这两个分支上没有目的多播 组的成员。 3.3版的mrouted实现了RPM。 图1 4 - 2 7是我们的示例网络,但这一次,只有那些 R P M算法选路数据报能到达的路由器和 网络才有阴影。

图14-27 网络C的RMP路由选择

为了计算生成树对应的路由表,多播路由器和邻近的多播路由器通信,发现多播互联网 拓扑和多播组成员的位置。在 N e t / 3中,用 D V M R P进行这种通信。 D V M R P作为I G M P数据报 传送,发给 224.0.0.4组,该组是给DVMRP通信保留的 (图12-1)。 在图 1 2 - 3 9 中,我们看到,多播路由器总是接受到达的

I G M P 分组,把它们传给

i g m p _ i n p u t和r i p _ i n p u t,然后 m r o u t e d在一个原始 I G M P插口上读它们。 m r o u t e d把 DVMRP报文发送到同一原始 IGMP插口上的其他多播路由器。 实现这些算法需要的有关 R P B、T R P B、R P M以及D V M R P报文的其他细节参见 [ D e e r i n g 和Cheriton 1990] 和mrouted的源代码。 I n t e r n e t上还使用了其他多播路由选择协议。 P r o t e o n路由器实现了 RFC 1584 [Moy 1994] 提出的 M O S P F 协议。 C i s c o从操作软件的 1 0 . 2版开始实现了 PIM(Protocol Independent

334

计计TCP/IP详解 卷 2:实现

Multicasting)。[Deering et al1994]描述了PIM。 14.7.1 多播选路表 现在我们描述 N e t / 3中实现的多播路由选择。内核的多播路由选择表是作为一个有 6 4个入 口的散列表实现的 (M R T H A S H I Z)。该表保存在全局数组 m r t t a b l e中,每个入口指向一个 mrt结构的链表,如图 14-28所示。

图14-28 mrt 结构

120-127

m r t c _ o r i g i n和m r t c _ o r i g i n m a s k标识表中的一个入口。 m r t c _ p a r e n t是

虚拟接口的下标,该虚拟接口上预期有来自起点的所有多播数据报。 m r t c _ c h i l d r e n是一 个位图,标识外出的接口。 m r t c _ l e a v e s也是一个位图,里面标识多播路由选择树中也是 叶子的外出接口。当多条路由散列到同一个数组入口时,最后一个成员 m r t _ n e x t实现该入 口的一个链表。 图 1 4 - 2 9 是多播选路表的整体结构。各 m r t 结构都放在一个散列链上,该散列链与 nethash(图14-31)函数返回的值对应。

mrttable[5] 到 mrttable[62]

图14-29 多播选路表

内核维护的多播选路表是 m r o u t e d维护的多播选路表的一个子集,其中的信息足够内核 支持多播转发。发送内核表更新和 D V M R P _ A D D _ M R T命令,其中包含图 1 4 - 3 0显示的m r t c t l 结构。 95-101 mrtctl结构的5个成员携带了我们谈到的mrouted和内核之间的信息(图14-28)。 多播选路表的键值是多播数据报的源 I P地址。 n e t h a s h(图1 4 - 3 1 )实现该用于该表的散列

335

第14章 IP多播选路计计

算法。它接受源 IP地址,并返回0~63之间的一个值(M R T H A S H S I Z-1)。

图14-30 mrtctl 结构

图14-31 nethash 结构

398-407

in_netof返回in,主机部分设置为全 0,在n中仅留下发送主机的 A、B和C类网

络。右移结果,直到低 8位非零为止。MRTHASHMOD是 #define MRTHASHMOD(h) ((h) & (MRTHASHSIZ - 1))

把低8位与63进行逻辑与运算,留下低 6位,这是0~63之间的一个整数。 用两个函数调用 (n e t h a s h和i n _ n e t o f)计算散列值,作为散列 32 bit地址值太 过昂贵了。 14.7.2 del_mrt函数 m r o u t e d守护程序通过 D V M R P _ A D D _ M R T和D V M R P _ D E L _ M R T命令在内核的多播选路表 中增加或删除表项。图 14-32显示了del_mrt函数。

图14-32 del_mrt 函数:DVMRP_DEL_MRT

命令

336

计计TCP/IP详解 卷 2:实现

图14-32 (续)

1. 找到路由入口 451-462

for循环从hash标识的入口开始 (在nethash中定义时初始化 )。如果没有找到入

口,则返回 ESRCH。 2. 删除路由入口 463-473

如果该入口在高速缓存中,则高速缓存也无效了。从散列链上把该入口断开,并

且释放。当匹配入口在表的最前面时,需要 if语句处理这一特殊情况。 14.7.3

add_mrt函数

add_mrt函数如图14-33所示。

图14-33 add_mrt 函数:处理 DVMRP_ADD_MRT

命令

337

第14章 IP多播选路计计

图14-33 (续)

1. 更新存在的路由 411-427 如果请求的路由已经在路由表中,则把新的信息复制到该路由中, add_mrt返回。 2. 分配新路由 4 2 8 - 4 4 7 在新分配的m b u f中,根据增加请求传递的 m r t c t l结构,构造一个 m r t结构。从 mrtc_origin计算出散列下标,并把新路由插入散列链的第一个入口。 14.7.4 mrtfind函数 mrtfind函数负责搜索多播选路表。如图14-34所示。把数据报的源站地址传给mrtfind, mrtfind返回一个指向匹配mrt结构的指针;如果没有匹配,则返回一个空指针。 1. 检查路由查询高速缓存 4 7 7 - 4 8 8 把给定的源 I P地址(o r g i n)与高速缓存中的原始掩码做逻辑与运算。如果结果与 cached_origin匹配,则返回高速缓存的入口。

图14-34 mrtfind 函数

338

计计TCP/IP详解 卷 2:实现

2. 检查散列表 489-501

n e t h a s h返回该路由入口的散列下标。 f o r循环搜索散列链找到匹配的路由。当

找到一个匹配时,更新高速缓存,返回一个指向该路由的指针。如果没有找到匹配,则返回 一个空指针。

14.8 多播转发:ip_mforward函数 内核实现了整个多播转发。我们在图 1 2 - 3 9中看到,当 i p _ m r o u t e r非空时,也就是 mrouted在运行时, ipintr把到达数据报传给 ip_mforward。 我们在图 1 2 - 4 0 中看到, i p _ o u t p u t 可以把本地主机产生的多播数据报传给

ip_

m f o r w a r d,由i p _ m f o r w a r d为这些数据报选路到除 i p _ o u t p u t选定的接口以外的其他接 口上去。 与单播转发不同,每当多播数据报被转发到某个接口上时,就为该数据报产生一个备份。 例如,如果本地主机是一个多播路由器,并且连接到三个不同的网络,则系统产生的多播数 据报被分别复制三份,在三个接口上等待输出。另外,如果应用程序设置了多播环回标志位, 或者任何输出的接口也接收它自己的传送,则数据报也将被复制,等待输入。 图14-35显示了一个到达某个物理接口的多播数据报。 运输层协议 接收的分组

丢弃数据报 (图14-39)

到达多播

隧道

以太网

图14-35 到达某个物理接口的多播数据报

在图1 4 - 3 5中,数据报到达的接口是目的多播组的一个成员,所以数据报被传给运输层协 议等待输入处理。该数据报也被传给 i p _ m f o r w a r d,在这里它被复制和转发到一个物理接 口和一个隧道上 (带粗线的箭头),这两个必须都不和接收接口相同。 图14-36显示了一个到达某隧道的多播数据报。 在图1 4 - 3 6中,用带虚线的箭头表示与该隧道的本地端有关的物理接口,数据报就在这一 接口上到达。数据报被传给 i p _ m f o r w a r d,我们将在图 14-37看到,因为分组到达一个隧道, 所以ip_mforward返回一个非零值。这导致 ipintr不再把该分组传给运输层协议。 i p _ m f o r w a r d从分组中取出隧道选项,查询多播选路表,并且,在本例中,还把分组转 发到另一个隧道以及到达的物理接口上去,用带细线的箭头表示。这是可行的,因为多播选

339

第14章 IP多播选路计计

路表是根据虚拟接口,而不是物理接口。 在图1 4 - 3 6中,我们假定物理接口是目的多播组的成员,所以 i p _ o u t p u t把该数据报传 给i p _ m l o o p b a c k,i p _ m l o o p b a c k把它送到队列中等待 i p i n t r的处理 (带粗线的箭头 )。 然后,分组又被传给

i p _ m f o r w a r d ,并被这个函数丢弃

( 练习 1 4 . 4 ) 。这一次,

i p _ m f o r w a r d返回0(因为分组是在物理接口上到达的 ),所以i p i n t r接受该数据报,并进行 输入处理。 运输层协议 只有当它在物理接口上 到达时,才接收的分组

丢弃的数据报 (图14-39)

在隧道上到达的分组现在在物 理接口上排队等待输入 到达多播

隧道

图14-36 到达某个多播隧道的多播数据报

我们分三部分说明多播转发程序: • 隧道输入处理(图14-37); • 转发条件合格(图14-39);和 • 转发到出去的接口上 (图14-40)。

图14-37 ip_mforward 函数:到达隧道

以太网

340

计计TCP/IP详解 卷 2:实现

图14-37 (续)

516-526

i p _ m f o r w a r d的两个参数是:一个指向包含该数据报的 m b u f链的指针;另一个

是指向接收接口 ifnet结构的指针。 1. 到达物理接口 527-530

为了区分在同一物理接口上到达的多播数据报是否经过隧道,要检查 I P首部的特

征L S R R选项。如果首部太小,无法包含该选项;或者该选项不是以一个后面跟着一个 L S R R 选项的NOP开始,就假定该数据报是在一个物理接口上到达的,并把 tunnel_src设为0。 2. 到达隧道 531-558 如果数据报看起来像是从隧道上到达的,就检查选项,验证格式是否正确。如果选 项的格式不符合多播隧道,则 i p _ m f o r w a r d返回1,指示应该把该数据报丢弃。图 14-38是隧 道选项的结构。 在图1 4 - 3 8中,我们假定数据报里没有其他选项,但不是必须这样的。任何其他 I P选项都可能出现在 L S R R选项的后面,因为隧道开始端的多播路由器总是把 L S R R 选项插在所有其他选项之前。 3. 删除隧道选项 559-565

如果选项正确,就把后面的选项和数据向前移动,调整 mbuf首部的m_len和IP 首

部的ip_len和ip_hl的值,然后删除隧道选项 (图14-38)。

341

第14章 IP多播选路计计

NOP LSRR 11(length) 12(offset) IP首部 20字节

1 1 1 1

隧道源

目的组

4字节

4字节

数据

隧道选项

IP首部

数据

20字节

10字节

图14-38 多播隧道选项

i p _ m f o r w a r d经常把t u n n e l _ s o u r c e作为返回值。当数据报从隧道上到达时,这个值 只能是非零的。当 i p _ m f o r w a r d返回非零值时,它的调用方就丢弃该数据报。对 i p i n t r来 说,这意味着在隧道上到达的一个数据报被传给 i p _ m f o r w a r d,并且被 i p i n t r丢弃。转发 程序取出隧道信息,复制数据报,用 i p _ o u t p u t将其发送出去;如果接口是目的多播组的成 员,则ip_output调用ip_mloopback。 i p _ m f o r w a r d的下一部分显示在图 1 4 - 3 9中,在这部分程序中,如果数据报不符合转发 的条件,就丢弃它。

图14-39 ip_mforward 函数:转发可行性检查

342

计计TCP/IP详解 卷 2:实现

4. 超时的TTL或本地多播 566-572

如果i p _ t t l是0或1,那么数据报已经到了生存期的最后,不再转发它。如果目

的组小于或等于 I N A D D R _ M A X _ L O C A L _ G R O U P(几个2 2 4 . 0 . 0 . x组,图 1 2 - 1 ),则不允许数据报 离开本地网络,也不转发它。在两种情况下,都把 tunnel_src返回给调用方。 3 . 3版的m r o u t e d支持对某些目的多播组的管理辖域。可把接口配置成丢弃所有寻 址到这些组的数据报,与 224.0.0.x组的自动辖域类似。 5. 没有路由可用 573-579 如果mrtfind无法根据数据报中的源地址找到一条路由,则函数返回。没有路由, 多播路由器无法确定把数据报转发到哪个接口上去。这种情况可能发生在,比如,多播数据 报在mrouted更新多播选路表之前到达。 6. 在没有想到的接口上到达 580-592

如果数据报到达某个物理接口,但系统本来预想它应该到达某个隧道或其他物理

接口,则 i p _ m f o r w a r d返回;如果数据报到达某个隧道,但系统本来预想它应该在某个物 理接口或其他隧道上到达,则 i p _ m f o r w a r d也返回。产生这些情况的原因是,当组成员关 系或网络的物理拓扑发生变化后,正在更新选路表时,数据报到达。 i p _ m f o r w a r d的最后一部分 (图1 4 - 4 0 )把该数据报在多播路由入口所指定的每个输出接 口上发送。

图14-40 ip_mforward 函数:转发

593-615

对viftable中的每个接口,如果以下条件满足,则在该接口上发送数据报:

• 数据报的TTL大于接口的多播阈值; • 接口是该路由的子接口;以及 • 接口没有和某个叶子网络相连。

343

第14章 IP多播选路计计

如 果 该 接口 是 一 个 叶子 ,那 么 只 有 当网 络 上 有 目的 多 播 组 成员 时 ( 也就 是说, grplst_member返回一个非零值 ),才输出该数据报。 tunnel_send在隧道接口上转发该数据报;用 phyint_send在物理接口上转发。 14.8.1 phyint_send函数 为在物理接口上发送多播数据报, p h y i n t _ s e n d(图1 4 - 4 1 )在它传给 i p _ o u t p u t的 ip_moptions结构中,明确指定了输出接口。

图14-41 phyint_send 函数

616-634

m_copy复制输出的数据报。 ip_moptions结构设置为强制在选定的接口上传送

该数据报。递减 TTL,允许多播环回。 数据报被传给 i p _ o u t p u t 。 I P _ F O R W A R D I N G 标志位避免产生无限回路,使 ip_output再次调用ip_mforward。 mbuf 分组首部

IP首部

通道 选项

28字节

20字节

12字节

IP首部

数据

IP选项

20字节

mbuf首部

选项和数据

20字节

图14-42 插入隧道选项

344

计计TCP/IP详解 卷 2:实现

14.8.2 tunnel_send函数 为了在隧道上发送数据报, t u n n e l _ s e n d(图1 4 - 4 3 )必须构造合适的隧道选项 ,并将其 插到输出数据报的首部。图 14-42显示了tunnel_send如何为隧道准备分组。

图14-43 tunnel_send 函数:验证和分配新首部

1. 隧道选项合适吗 635-652

如果I P首部内没有隧道选项的空间, t u n n e l _ s e n d立即返回,不再在隧道上转

发该数据报。可能在其他接口上转发。 2. 复制数据报,为新首部和隧道选项分配 mbuf 653-672

在调用 m _ c o p y时,复制的开始偏移是 2 0 (I P _ H D R _ L E N)。产生的 m b u f链中包含

了数据报的选项和数据报,但没有 I P首部。m b _ o p t s指向M G E T H D R分配的一个新的数据报首 部,这个新的数据报首部被放在 m b _ c o p y的前面。然后调整 m _ l e n和m _ d a t a的值,以容纳 IP首部和隧道选项。

345

第14章 IP多播选路计计

tunnel_send的第二部分,如图 14-44所示,修改输出分组的首部,并发送该分组。

图14-44 tunnel_send 函数:构造首部和发送

3. 修改IP首部 673-679

从原始mbuf链中把原始 IP 首部复制到新分配的 mbuf首部中。减少该首部的 TTL,

把目的地址改成隧道另一端的接口地址。 4. 构造隧道选项 680-664

调整i p _ h l和i p _ l e n的值以容纳隧道选项。隧道选项紧跟在 IP 首部的后面:一

个NOP,后面是LSRR码,LSRR选项的长度 (11字节),以及一个指向选项第二个地址的指针 (8 字节)。源路由包括了本地隧道端点和后面的目的多播组地址 (图14-13)。 5. 发送经过隧道处理的数据报 665-697 现在,这个数据报看起来像一个有 LSRR选项的单播数据报,因为它的目的地址是 隧道另一端的单播地址。 i p _ o u t p u t发送该数据报。当数据报到达隧道的另一端时,隧道选 项被剥离,另一端可能会通过其他隧道将数据报继续转发。

14.9 清理:ip_mrouter_done函数 当m r o u t e d结束时,它发布 D V M R P _ D O N E命令,i p _ m r o u t e r _ d o n e函数(图14-45)处理 这个命令。 161-186

这个函数在 s p l n e t上运行,避免与多播转发代码的任何交互。对每个物理多播

接口,释放本地组表,并发布 S I O C D E L M U L T I 命令,阻止接收多播数据报 (练习 14.3) 。 bzero清零整个 viftable数组,并把 numvifs设置成0。

346

计计TCP/IP详解 卷 2:实现

187-198

释放多播选路表中的所有活动入口, b z e r o 清零整个表,清零缓存,置位

ip_mrouter。 多播选路表中的每个入口都可能是入口链表的第一个。这段代码只释放表的第 一个入口,引起内存泄露。

图14-45 ip_mrouter_done

函数:DVMRP_DONE 命令

14.10 小结 本章我们描述了网际多播的一般概念和支持它的 N e t / 3内核中心专用函数。我们没有讨论 mrouted的实现,有兴趣的读者可以得到源代码。 我们描述了虚拟接口表,讨论了物理接口和隧道之间的区别,以及 N e t / 3中用于实现隧道 的LSRR选项。 我们说明了 R P B、T R P B和R P M算法,描述了根据 T R P B转发多播数据报的内核表,还讨 论了父网络和叶子网络。

347

第14章 IP多播选路计计

习题 14.1 在图14-25中,需要多少多播路由? 14.2 为什么splnet和splx保护对图14-23中组成员关系高速缓存的更新? 14.3 当某个接口用 I P _ A D D _ M E M B E R S H I P选项明确加入一个多播组后,如果向它发布 SIOCDELMULTI,会发生什么? 14.4 当某个上隧道上到达一个数据报,并被 i p _ m f o r w a r d接收后,可能会在转发到某 个物理接口时,被 i p _ o u t p u t 环回。为什么当环回分组到达该物理接口时, ip_mforward会丢弃它呢? 14.5 重新设计组地址高速缓存,提高它的效率。

第15章 插 口 层 15.1 引言 本书共有三章介绍 N e t / 3的插口层代码,本章是第一章。插口概念最早出现于 1 9 8 3年的 4 . 2 B S D版本中,它的主要目的是提供一个统一的访问网络和进程间通信协议的接口。这里讨 论的N e t / 3版基于4.3BSD Reno版,该版本与大多数 U n i x供应商使用的早期的 4 . 2版有些细小的 差别。 如第1 . 7节所介绍的,插口层的主要功能是将进程发送的与协议有关的请求映射到产生插 口时指定的与协议有关的实现。 为了允许标准的 Unix I/O系统调用,如 r e a d和w r i t e,也能读写网络连接,在 B S D版本 中将文件系统和网络功能集成在系统调用级。与通过一个描述符访问一个打开的文件一样, 进程也是通过一个描述符 (一个小整数)来访问插口上的网络连接。这个特点使得标准的文件系 统调用,如 r e a d和w r i t e,以及与网络有关的系统调用,如 s e n d m s g和r e c v m s g,都能通 过描述符来处理插口。 我们的重点是插口及相关的系统调用的实现而不是讨论如何使用插口层来实现网络应用。 关于进程级的插口接口和如何编写网络应用的详细讨论,请参考 [Stevens 1990]和[Rago 1990]。 图15-1说明了进程中的插口接口与内核中的协议实现之间的层次关系。 应用程序 函数调用 插口系统调用 系统调用

进程 内核

插口系统 调用实现 函数调用 插口层函数

TP 4

TCP 通过pr_usrreq或pr_ctloutput调用 UDP

SPP

图15-1 插口层将一般的请求转换为指定的协议操作

349

第 15章 插 口 层计计

splnet处理 插口包含很多对 s p l n e t和s p l x的成对调用。正如第 1 . 1 2节中介绍的,这些调用保护访 问在插口层和协议处理层间共享的数据结构的代码。如果不使用 s p l n e t,初始化协议处理和 改变共亨的数据结构的软件中断将使得插口层代码恢复执行时出现混乱。 我们假定读者理解了这些调用,因而在以后讨论中一般不再特别说明它们。

15.2 代码介绍 本章讨论涉及的三个文件在图 15-2中列出。 文







sys/socketvar.h

s o c k et结构定义

kern/uipc_syscalls.c kern/uipc_socket.c

系统调用实现 插口层函数

图15-2 本章讨论涉及的源文件

全局变量 本章讨论涉及到的两个全局变量如图 15-3所示。 变



socketps sysent

数据类型





s t r u c t f i l e o p s I/O系统调用的 s o c k e t实现 s t r u c t s y s e n[]t 系统调用入口数组

图15-3 本章介绍的全局变量

15.3 socket结构 插口代表一条通信链路的一端,存储或指向与链路有关的所有信息。这些信息包括:使 用的协议、协议的状态信息 (包括源和目的地址 )、到达的连接队列、数据缓存和可选标志。图 15-5中给出了插口和与插口相关的缓存的定义。 41-42

s o _ t y p e由产生插口的进程来指定,它指明插口和相关协议支持的通信语义。

s o _ t y p e的值等于图7 - 8所示的p r _ t y p e。对于U D P,s o _ t y p e等于S O C K _ D G R A M,而对于 TCP,so_type则等于SOCK_STREAM。 43 so_options是一组改变插口行为的标志。图 15-4列出了这些标志。 通过g e t s o c k o p t和s e t s o c k o p t系统调用进程能修改除 S O _ A C C E P T C O N N外 所有的插口选项。当在插口上发送 l i s t e n系统调用时, S O _ A C C E P T C O N N被内核设 置。 44

s o _ l i n g e r等于当关闭一条连接时插口继续发送数据的时间间隔 (单位为一个时钟滴

答)(第15.15节)。 45 值。

s o _ s t a t e表示插口的内部状态和一些其他的特点。图 1 5 - 6列出了s o _ s t a t e可能的取

350

计计TCP/IP详解 卷 2:实现

so_options SO_ACCEPTCONN SO_BROADCAST SO_DEBUG SO_DONTROUTE SO_KEEPALIVE SO_OOBINLINE SO_REUSEADDR SO_REUSEPORT SO_USELOOPBACK

仅用于内核 •





插口接受进入的连接 插口能够发送广播报文 插口记录排错信息 输出操作旁路选路表 插口查询空闲的连接 插口将带外数据同正常数据存放在一起 插口能重新使用一个本地地址 插口能重新使用一个本地地址和端口 仅针对选路域插口;发送进程收到它自己的选路请求

图15-4 so_options 的值

图15-5 struct socket 定义

351

第 15章 插 口 层计计

图15-5 (续) so_state

仅用于内核



插口应该I/O事件的异步通知 插口操作不能阻塞进程

SS_ASYNC SS_NBIO SS_CANTRCVMORE SS_CANTSENDMORE SS_ISCONFIRMING SS_ISCONNECTED SS_ISCONNECTING SS_ISDISCONNECTING SS_NOFDREF SS_PRIV SS_RCVATMARK



• • • • • • • • •

插口不能再从对方接收数据 插口不能再发送数据给对方 插口正在协商一个连接请求 插口被连接到外部插口 插口正在连接一个外部插口 插口正在同对方断连 插口没有同任何描述符相连 插口由拥有超级用户权限的进程所产生 在最近的带外数据到达之前,插口已处理完所有收到的数据

图15-6 so_state 的值

从图 1 5 - 6 的第二列中可以看出,进程可以通过

f c n t l 和i o c t l 系统调用直接修改

S S _ A S Y N C和S S _ N B I O。对于其他的标志,进程只能在系统调用的执行过程中间接修改。例 如,如果进程调用 connect,当连接被建立时, SS_ISCONNECTED标志就会被内核设置。 SS_NBIO和SS_ASYNC标志 在默认情况下,进程在发出 I / O请求后会等待资源。例如,对一个插口发 r e a d系统调用, 如果当前没有网络上来的数据,则 r e a d系统调用就会被阻塞。同样,当一个进程调用 w r i t e 系统调用时,如果内核中没有缓存来存储发送的数据,则内核将阻塞进程。如果设置了 S S _ N B I O,在对插口执行 I / O操作且请求的资源不能得到时,内核并不阻塞进程,而是返回 EWOULDBLOCK。 如果设置了 S S _ A S Y N C,当因为下列情况之一而使插口状态发生变化时,内核发送 SIGIO信号给so_pgid标识的进程或进程组: • 连接请求已完成; • 断连请求已被启动; • 断连请求已完成; • 连接的一个通道已被关闭; • 插口上有数据到达; • 数据已被发送(即,输出缓存中有闲置空间 );或 • UDP或TCP插口上出现了一个异步差错。 46 so_pcb指向协议控制块,协议控制块包含与协议有关的状态信息和插口参数。每一种协 议都定义了自己的控制块结构,所以 s o _ p c b被定义成一个通用的指针。图 15-7列出了我们讨 论的控制块结构。 so_pcb从来不直接指向 tcpcb结构;参考图22-1。

352

计计TCP/IP详解 卷 2:实现





控 制 块

参考章节 第22.3节

UDP

struct inpcb

TCP ICMP、IGMP和原始IP

s t r u c t i n p c b 第22.3节 s t r u c t t c p c b 第24.5节 s t r u c t i n p c b 第22.3节

路由

struct rawcb

第20.3节

图15-7 协议控制块

47 so_proto指向进程在 socket系统调用(第7.4节)中选择的协议的 protosw结构。 48-64

设置了S O _ A C C E P T C O N N标志的插口维护两个连接队列。还没有完全建立的连接 (如

T C P的三次握手还没完成 )被放在队列 s o _ q 0中。已经建立的连接或将被接受的连接 (例如, T C P的三次握手已完成 )被放入队列 s o _ q中。队列的长度分别为 s o _ q 0 l e n和s o _ q l e n。每 一个被排队的连接由它自己的插口来表示。在每一个被排队的插口中, s o _ h e a d指向设置了 SO_ACCEPTCONN的源插口。 插口上可排队的连接数通过 s o _ q l i m i t来控制,进程可以通过 l i s t e n系统调用来设置 s o _ q l i m i t。内核隐含设置的上限为 5 ( S O M A X C O N N,图1 5 - 2 4 )和下限为 0。图1 5 - 2 9中显示 的有点晦涩的公式使用 so_qlimit来控制排队的连接数。 图15-8说明了有三个连接将被接受、一个连接已被建立的情况下的队列内容。

当TCP SYN 到达时,插口 进入此队列

当TCP SYN被应答时将插口 移入此队列, a c c e p t将插口 从此队列中删除

图15-8 插口连接队列

so_timeo用作accept、connet和close处理期间的等待通道 (wait channel,第15.10

65 节)。 66

s o _ e r r o r保存差错代码,直到在引用该插口的下一个系统调用期间差错代码能送给进

程。 67

如果插口的 SS_ASYNC被设置,则 SIGIO信号被发送给进程 (如果so_pgid大于0)或进程

组(如果 s o _ p g i d小于0 )。可以通过 i o c t l的S I O C S P G R P和S I O C G P G R P命令来修改或检查 so_pgid的值。关于进程组的更详细信息请参考 [Stevens 1992]。 68

so_oobmark标识在输入数据流中最近收到的带外数据的开始点。第 16.11节将讨论插口

对带外数据的支持,第 29.7节将讨论TCP中的带外数据的语义。

353

第 15章 插 口 层计计

69-82

每一个插口包括两个数据缓存, s o _ r c v和s o _ s n d,分别用来缓存接收或发送的数

据。s o _ r c v和s o _ s n d是包含在插口结构中的结构而不是指向结构的指针。我们将在第 1 6章 中描述插口缓存的结构和使用。 83-86

在Net/3中不使用so_tpcb。so_upcall和so_upcallarg也仅用于Net/3中的NFS

软件。 NFS与通常的软件不太一样。在很大程度上它是一个进程级的应用但却在内核中 运行。当数据到达接收缓存时,通过 s o _ u p c a l l来触发 N F S的输入处理。在这种情 况下,t s l e e p和w a k e u p机制是不合适的,因为 N F S协议是在内核中运行而不是作 为一个普通进程。 文件socketvar.h和uipc_socket2.c定义了几个简化插口层代码的宏和函数。图 15-9 对它们进行了描述。 名



sosendallatonce





s o中指定的协议要求每一个发送系统调用产生一个协议请求吗? int s o s e n d a l l a t o n c e( s t r u c t s o c k e t so) *;

soisconnecting

将插口状态设置为 S O _ I S C O N N E C T I N G int s o i s c o n n e c t i n g( s t r u c t s o c k e t so) *;

soisconnected soreadable

参考图1 5 - 3 0 插口so上的读调用不阻塞就返回信息吗? int s o r e a d a b l e( s t r u c t s o c k e t so) * ;

sowriteable

插口so上的写调用不阻塞就返回吗? int s o w r i t e a b l e( s t r u c t s o c k e t so) *;

socantsendmore

设置插口标志 S O _ C A N T S E N D M O R E。唤醒所有等待在发送缓存上的进程 int s o c a n t s e n d m o r e( s t r u c t s o c k e t so) *;

socantrcvmore

设置插口标志 S O _ C A N T R C V M O R E。唤醒所有等待在接收缓存上的进程 int s o c a n t r c v m o r e( s t r u c t s o c k e t so) *;

soisdisconnecting

清除SS_ISCONNECTING标志。设置SS_ISDISCONG、SS_CANTRCVMORE 和S S _ C A N T S E N D M O R E标志。唤醒所有等待在插口上的进程 int s o i s d i s c o n n e c t i n g( s t r u c t s o c k e t so) *;

soisdisconnected

清除S S _ I S C O N N E C T I N G、S S _ I S C O N N E C T E D和S S _ I S D I S C O N N E C T I N G 标志。设置 S S _ C A N T R C V M O R E和S S _ C A N T S E N D M O R E标志。唤醒所有等待在 插口上的进程或等待 c l o s e完成的进程 i n t s o i s d i s c o n n e c t e d( s t r u c t s o c k e t so) * ;

soqinsque

将s o插入head指向的队列中。如果 q等于0,插口被插到存放未完成的连接的 s o _ q 0队列的后面。否则,插口被插到存放准备接受的连接的队列 s o _ q的后面。 N e t / 1错误地将插口插到队列的前面 int soqinsque(struct socket * head, struct socket *

soqremque

从队列q中删除so。通过so- >so_head来定位插口队列 int s o q r e m q u e( s t r u c t s o c k e t so, * i n t q) ;

图15-9 插口的宏和函数

so, int q);

354

计计TCP/IP详解 卷 2:实现

15.4 系统调用 进程同内核交互是通过一组定义好的函数来进行的,这些函数称为系统调用。在讨论支 持网络的系统调用之前,我们先来看看系统调用机制的本身。 从进程到内核中的受保护的环境的转换是与机器和实现相关的。在下面的讨论中,我们 使用Net/3在386上的实现来说明如何实现有关的操作。 在B S D内核中,每一个系统调用均被编号,当进程执行一个系统调用时,硬件被配置成 仅传送控制给一个内核函数。将标识系统调用的整数作为参数传给该内核函数。在 386实现中, 这个内核函数为 s y s c a l l。利用系统调用的编号, s y s c a l l在表中找到请求的系统调用的 sysent结构。表中的每一个单元均为一个 sysent结构。 struct sysent { int sy_narg; int (*sy_call) (); };

/* number of arguments */ /* implementing function */ /* system call table entry */

表中有几个项是从 sysent数组中来的,该数组是在 kern/init_sysent.c中定义的。 struct sysent sysent[] = { /* . . . */ { 3, recvmsg }, { 3, sendmsg }, { 6, recvfrom }, {3, accept }, {3, getpeername }, {3, getsockname }, /* . . . */ }

/* /* /* /* /* /*

27 = recvmsg */ 28 = sendmsg */ 29 = recvfrom */ 30 = accept */ 31=getpeername */ 32 = getsockname */

例如, r e c v m s g系统调用在系统调用表中的第 2 7个项,它有两个参数,利用内核中的 recvmsg函数实现。 s y s c a l l将参数从调用进程复制到内核中,并且分配一个数组来保存系统调用的结果。 然后,当系统调用执行完成后, s y s c a l l将结果返回给进程。 s y s c a l l将控制交给与系统调 用相对应的内核函数。在 386实现中,调用有点像: struct sysent *callp; error = (*callp->sy_call) (p, args, rval);

这里指针 c a l l p指向相关的 s y s e n t结构;指针 p则指向调用系统调用的进程的进程表项; a rg s作为参数传给系统调用,它是一个 32 bit长的字数组;而 r v a l则是一个用来保存系统调用 的返回结果的数组,数组有两个元素,每个元素是一个 32 bit长的字。当我们用 “系统调用” 这个词时,我们指的是被 syscall调用的内核中的函数,而不是应用调用的进程中的函数。 s y s c a l l期望系统调用函数 (即s y _ c a l l指向的函数 )在没有差错时返回 0,否则返回非 0 的差错代码。如果没有差错出现,内核将 r v a l中的值作为系统调用 (应用调用的 )的返回值传 送给进程。如果有差错, s y s c a l l忽略r v a l中的值,并以与机器相关的方式返回差错代码 给进程,使得进程能从外部变量 e r r n o中得到差错代码。应用调用的函数则返回 - 1或一个空 指针表示应用应该查看 errno获得差错信息。 在3 8 6上的实现,设置进位比特 (carry bit)来表示 s y s c a l l的返回值是一个差错代码。进 程中的系统调用残桩将差错代码赋给 e r r n o,并返回- 1或空指针给应用。如果没有设置进位

355

第 15章 插 口 层计计

比特,则将 syscall返回的值返回给进程中的系统调用的残桩。 总之,实现系统调用的函数“返回”两个值:一个给 s y s c a l l函数;在没有差错的情况 下,syscall将另一个(在rval中)返回给调用进程。 15.4.1 举例 socket系统调用的原型是: int socket(int domain, int type, int protocol);

实现socket系统调用的内核函数的原型是: struct socket_args { int domain; int type; int protocol; }; socket(struct proc *p, struct socket_args *uap, int *retval);

当一个应用调用socket时,进程用系统调用机制将三个独立的整数传给内核。 syscall 将参数复制到 3 2 b i t值的数组中,并将数组指针作为第二个参数传给 s o c k e t的内核版。内核 版的socket将第二个参数作为指向 socket_args结构的指针。图 15-10显示了上述过程。 进程 从用户空间复制到 内核空间的参数

内核

图15-10 socket 参数处理

同s o c k e t类似,每一个实现系统调用的内核函数将 a r g s说明成一个与系统调用有关的 结构指针,而不是一个指向 32 bit的字的数组的指针。 当原型无效时,隐式的类型转换仅在传统的 K&R C中或ANSI C中是合法的。如 果原型是有效的,则编译器将产生一个警告。 syscall在执行内核系统调用函数之前将返回值置为 0。如果没有差错出现,系统调用函 数直接返回而不需清除 *retval,syscall返回0给进程。 15.4.2 系统调用小结 图15-11对与网络有关的系统调用进行了小结。 我们将在本章中讨论建立、服务器、客户和终止类系统调用。输入、输出类系统调用将 在第16章中介绍,管理类系统调用将在第 17章中介绍。

356

计计TCP/IP详解 卷 2:实现











socket bind

在指明的通信域内产生一个未命名的插口 分配一个本地地址给插口

服务器

listen accept

使插口准备接收连接请求 等待并接受连接





connect

同外部插口建立连接



read readv recv recvfrom recvmsg

接收数据到一个缓存中 接收数据到多个缓存中 指明选项接收数据 接收数据和发送者的地址 接收数据到多个缓存中,接收控制信息和发送者地址;指明接收选项



write writev send sendto sendmsg

发送一个缓存中的数据 发送多个缓存中的数据 指明选项发送数据 发送数据到指明的地址 从多个缓存发送数据和控制信息到指明的地址;指明发送选项

select

等待I/O事件



shutdown close

终止一个或两个方向上的连接 终止连接并释放插口



fcntl ioctl setsockopt getsockopt getsockname getpeername

修改I/O语义 各类插口操作 设置插口或协议选项 得到插口或协议选项 得到分配给插口的本地地址 得到分配给插口的远端地址





I/O 终









图15-11 Net/3中的网络系统调用

图15-12 网络系统调用流程图

357

第 15章 插 口 层计计

图15-12画出了应用使用这些系统调用的顺序。大方块中的 I/O系统调用可以在任何时候调 用。该图不是一个完整的状态流程图,因为一些正确的转换在本图中没有画出;仅显示了一 些常见的转换。

15.5 进程、描述符和插口 在描述插口系统调用之前,我们需要介绍将进程、描述符和插口联系在一起的数据结构。 图1 5 - 1 3给出了这些结构以及与我们的讨论有关的结构成员。关于文件结构的更复杂的解释请 参考[Leffer ea al. 1989]。

图15-13 进程、文件和插口结构

实现系统调用的函数的第一个参数总为 p,即指向调用进程的 proc结构的指针。内核利用 p r o c结构记录进程的有关信息。在 p r o c结构中, p _ f d指向f i l e d e s c结构,该结构的主要 功能是管理 fd_ofiles指向的描述符表。描述符表的大小是动态变化的,由一个指向 file结 构的指针数组组成。每一个 file结构描述一个打开的文件,该结构可被多个进程共亨。 图1 5 - 1 3仅显示了一个 f i l e结构。通过 p - > p _ f d - > f d _ o f i l e s [ f d ]访问到该结构。在 f i l e结构中,有两个结构成员是我们感兴趣的: f _ o p s和f _ d a t a。I / O系统调用 (如r e a d和 w r i t e)的实现因描述符中的 I / O对象类型的不同而不同。 f _ o p s指向f i l e o p s结构,该结构 包含一张实现 r e a d、w r i t e、i o c t l、s e l e c t和c l o s e系统调用的函数指针表。图 1 5 - 1 3 显示f _ o p s指向一个全局的 f i l e o p s结构,即 s o c k e t o p s,该结构包含指向插口用的函数 的指针。 f _ d a t a指向相关 I / O对象的专用数据。对于插口而言, f _ d a t a指向与描述符相关的 s o c k e t结构。最后, s o c k e t结构中的 s o _ p r o t o指向产生插口时选中的协议的 p r o t o s w结 构。回想一下,每一个 protosw结构是由与该协议关联的所有插口共亨的。 下面我们开始讨论系统调用。

358

计计TCP/IP详解 卷 2:实现

15.6 socket系统调用 s o c k e t 系统调用产生一个新的插口,并将插口同进程在参数

domain 、type和

p r o t o c o l中指定的协议联系起来。该函数 (如图1 5 - 1 4所示)分配一个新的描述符,用来在后 续的系统调用中标识插口,并将描述符返回给进程。

图15-14 socket 系统调用

42-55

在每一个系统调用的前面,都定义了一个描述进程传递给内核的参数的结构。在这

种情况下,参数是通过 s o c k e t _ a r g s传入的。所有插口层系统调用都有三个参数: p,指向 调用进程的 p r o c结构; u a p,指向包含进程传送给系统调用的参数的结构; r e t v a l,用来 接收系统调用的返回值。在通常情况下,忽略参数 p和r e t v a l,引用 u a p所指的结构中的内 容。 56-60

f a l l o c分配一个新的 f i l e结构和f d _ o f i l e s数组(图1 5 - 1 3 )中的一个元素。 f p指

向新分配的结构, f d则为结构在数组 f d _ o f i l e s中的索引。







s o c k e t将f i l e结构设置成可读、可写,并且作为一个插口。 将所有插口共亨的全局 f i l e o p s 结构 s o c k e t o p s 连接到 f _ o p s指向的f i l e结构中。 s o c k e t o p s变量在编译时被初始 化,如图15-15所示。 60-69

s o c r e a t e分配并初始化一个 s o c k e t结构。如果

s o c r e a t e执行失败,将差错代码赋给 e r r o r,释放 f i l e结

图15-15 socketops :插口 用全局fileops 结构

359

第 15章 插 口 层计计

构,清除存放描述符的数组元素。如果 s o c r e a t e执行成功,将 f _ d a t a指向s o c k e t结构, 建立插口和描述符之间的联系。通过 * r e t v a l将f d返回给进程。 s o c k e t返回 0或返回由 socreate返回的差错代码。 15.6.1 socreate函数 大多数插口系统调用至少被分成两个函数,与 s o c k e t和s o c r e a t e类似。第一个函数从 进程那里获取需要的数据,调用第二个函数 s oxxx来完成功能处理,然后返回结果给进程。这 种分成多个函数的做法是为了第二个函数能直接被基于内核的网络协议调用,如

NFS。

socreate的代码如图 15-16所示。

图15-16 socreate 函数

43-52

socreate共有四个参数: dom,请求的协议域 (如,PF_INET);aso,保存指向一

个新的s o c k e t结构的指针;t y p e,请求的插口类型 (如,S O C K _ S T R E A M);p r o t o,请求的 协议。 1. 发现协议交换表

360

计计TCP/IP详解 卷 2:实现

53-60

如果p r o t o等于非 0值, p f f i n d p r o t o查找进程请求的协议。如果 p r o t o等于0,

p f f i n d t y p e用由t y p e指定的语义在指定域中查找一种协议。这两个函数均返回一个指向匹 配协议的 protosw结构的指针或空指针 (参考第7.6节)。 2. 分配并初始化socket结构 61-66

socreate分配一个新的socket结构,并将结构内容全清成 0,记录下type。如果

调用进程有超级用户权限,则设置插口结构中的 SS_PRIV标志。 3. PRU_ATTACH请求 67-69

在与协议无关的插口层中发送与协议有关的请求的第一个例子出现在 s o c r e a t e中。

回想在第 7 . 4节和图1 5 - 1 3中,s o - > s o _ p r o t o - > p r _ u s r r e q是一个指向与插口 s o相关联的 协议的用户请求函数指针。每一个协议均提供了一个这样的函数来处理从插口层来的通信请 求。函数原型是: int pr_usrreq(struct socket *so, int req, struct mbuf *mo, *m1, *m2);

第一个参数是一个指向相关插口的指针, re q是一个标识请求的常数。后三个参数 (m0, m1,m2)因请求不同而异。它们总是被作为一个 mbuf结构指针传递,即使它们是其他的类型。 在必要的时候,进行类似转换以避免编译器的警告。 图1 5 - 1 7列出了 p r _ u s r r e q函数提供的通信请求。每一个请求的语义起决于服务请求的 协议。



参 求

描 m0

PRU_ABORT PRU_ACCEPT PRU_ATTACH PRU_BIND PRU_CONNECT PRU_CONNECT2 PRU_DETACH PRU_DISCONNECT PRU_LISTEN PRU_PEERADDR PRU_RCVD PRU_RCVOOB PRU_SEND PRU_SENDOOB PRU_SHUTDOWN PRU_SOCKADDR

数 m1

address protocol address address socket2

buffer data data

buffer flags flags address address



m2

control control

buffer

异常终止每一个存在的连接 等待并接受连接 产生了一个新的插口 绑定地址到插口 同地址建立关联或连接 将两个插口连在一起 插口被关闭 切断插口和另一地址间的关联 开始监听连接请求 返回与插口关联的对方地址 进程已收到一些数据 接收OOB数据 发送正常数据 发送OOB数据 结束同另一地址的通信 返回与插口相关联的本地地址

图15-17 pr_usrreq 函数

P R U _ C O N N E C T 2请求只用于 U n i x域,它的功能是将两个本地插口连接起来。 Unix的管道(pipe)就是通过这种方式来实现的。 4. 退出处理 70-77

回到s o c r e a t e,函数将协议交换表连接到插口,发送 P R U _ A T T A C H请求通知协议

361

第 15章 插 口 层计计

已建立一个新的连接端点。该请求引起大多数协议,如 T C P和U D P,分配并初始化所有支持 新的连接端点的数据结构。 15.6.2 超级用户特权 图15-18列出了要求超级用户权限的网络操作。 函



超级用户 进程

in_control in_control in_pcbbind ifioctl ifioctl rip_usrreq slopen





参考图

插口 • •

• • • • •

分配接口地址、网络掩码、目的地址 分配广播地址 绑定到一个小于 1024的Internet端口 改变接口配置 配置多播地址 (见下面的说明 ) 产生一个ICMP、IGMP或原始 IP插口 将一个SLIP设备与一个 tty设备联系起来

图6-14 图6-22 图22-22 图4-29 图12-11 图32-10 图5-9

图15-18 Net/3中的超级用户特权

当多播 i o c t l 命令 ( S I O C A D D M U L T I 和S I O C D E L M U L T I ) 是被 I P _ A D D _ M E M B E R S H I P和I P _ D R O P _ M E M B E R S H I P插口选项间接激活时,它可以被非超级用 户访问。 在图1 5 - 1 8中,“进程”栏表示请求必须由超级用户进程来发起,“插口”栏表示请求必须 是针对由超级用户产生的插口 (也就是说,进程不需要超级用户权限,而只需有访问插口的权 限,习题 1 5 . 1 )。在 N e t / 3中, s u s e r函数用来判断调用进程是否有超级用户权限,通过 SS_PRIV标志来判断一个插口是否由超级用户进程产生。 因为r i p _ u s r r e q在用s o c r e a t e产生插口后立即检查 S S _ P R I V标志,所以我们认为只 有超级用户进程才能访问这个函数。

15.7 getsock和sockargs函数 这两个函数重复出现在插口系统调用中。 g e t s o c k的功能是将描述符映射到一个文件表 项中, s o c k a r g s将进程传入的参数复制到内核中的一个新分配的 m b u f中。这两个函数都要 检查参数的正确性,如果参数不合法,则返回相应的非 0差错代码。 图15-19列出了getsock函数的代码。 754-767

getsock函数利用fdp查找描述符 fdes指定的文件表项, fdp是指向filedesc

结构的指针。 g e t s o c k将打开的文件结构指针赋给 f p p,并返回,或者当出现下列情况时返 回差错代码:描述符的值超过了范围而不是指向一个打开的文件;描述符没有同插口建立联 系。 图15-20列出了sockargs函数的代码。 768-783

如图1 5 - 4中所描述的, s o c k a r g s将进程传给系统调用的参数的指针从进程复制

到内核而不是复制指针指向的数据,这样做是因为每一个参数的语义只有相对应的系统调用 才知道,而不是针对所有的系统调用。多个系统调用在调用 s o c k a r g s复制参数指针后,将 指针指向的数据从进程复制到内核中新分配的 mbuf中。例如,s o c k a r g s将b i n d的第二个参

362

计计TCP/IP详解 卷 2:实现

数指向的本地插口地址从进程复制到一个 mbuf中。

图15-19 getsock 函数

图15-20 sockargs 函数

如果数据不能存入一个 mbuf中或无法分配 mbuf,则s o c k a r g s返回E I N V A L或E N O B U F S。 注意,这里使用的是标准的 m b u f而不是分组首部的 m b u f。c o p y i n的功能是将数据从进程复 制到mbuf中。copyin返回的最常见的差错是 EACCES,它表示进程提供的地址不正确。 784-785

当出现差错时,丢弃 m b u f,并返回差错代码。如果没有差错,通过 m p返回指向

mbuf的指针,sockargs返回0。 786-794

如果t y p e等于M T _ S O N A M E,则进程传入的是一个 s o c k a d d r结构。s o c k a r g s

363

第 15章 插 口 层计计

将刚复制的参数的长度赋给内部长度变量 s a _ l e n。这一点确保即使进程没有正确地初始化结 构,结构内的大小也是正确的。 Net/3确实包含了一段代码来支持在 pre-4.3BSD Reno系统上编译的应用,这些应 用的sockaddr结构中并没有sa_len字段,但是图15-20中没有显示这段代码。

15.8 bind系统调用 b i n d系统调用将一个本地的网络运输层地址和插口联系起来。一般来说,作为客户的进 程并不关心它的本地地址是什么。在这种情况下,进程在进行通信之前没有必要调用 b i n d; 内核会自动为其选择一个本地地址。 服务器进程则总是需要绑定到一个已知的地址上。所以,进程在接受连接 ( T C P )或接收数 据报( U D P )之前必须调用 b i n d,因为客户进程需要同已知的地址建立连接或发送数据报到已 知的地址。 插口的外部地址由 c o n n e c t指定或由允许指定外部地址的写调用 (s e n d t o或s e n d m s g) 指定。 图15-21列出了bind调用的代码。

图15-21 bind 函数

70-82 bind调用的参数有(在bind_args结构中):s,插口描述符; name,包含传输地址 (如,sockaddr_in结构)的缓存指针;和 namelen,缓存大小。 83-90

getsock返回描述符的 file结构,sockargs将本地地址复制到 mbuf中,sobind

将进程指定的地址同插口联系起来。在 bind返回sobind的结果之前,释放保存地址的 mbuf。 从技术上讲,一个描述符,如 s,标识一个同 s o c k e t结构相关联的 f i l e结构,而它 本身并不是一个 socket结构。将这种描述符看作插口是为了简化我们的讨论。 我们将在下面的讨论中经常看到这种模式:进程指定的参数被复制到 m b u f,必要时还要

364

计计TCP/IP详解 卷 2:实现

进行处理,然后在系统调用返回之前释放 m b u f。虽然m b u f是为方便处理网络数据分组而设计 的,但是将它们用作一般的动态内存分配机制也是有效的。 b i n d说明的另一种模式是:许多系统调用不使用 r e t v a l。在第1 5 . 4节中我们已提到过, 在s y s c a l l将控制交给相应的系统调用之前总是将 r e t v a l清0。如果 0不是合适的返回值, 系统调用并不需要修改 retval。 sobind函数 如图15-22所示,sobind是一个封装器,它给与插口相关联的协议发送 PRU_BIND请求。

图15-22 sobind 函数

78-89

s o b i n d发送P R U _ B I N D请求。如果请求成功,将本地地址 n a m同插口联系起来;否

则,返回差错代码。

15.9 listen系统调用 l i s t e n系统调用的功能是通知协议进程准备接收插口上的连接请求,如图 1 5 - 2 3所示。 它同时也指定插口上可以排队等待的连接数的门限值。超过门限值时,插口层将拒绝进入的 连接请求排队等待。当这种情况出现时, T C P 将忽略进入的连接请求。进程可以通过调用 accept (第15.11节)来得到队列中的连接。

图15-23 listen 系统调用

365

第 15章 插 口 层计计

91-98 listen系统调用有两个参数:一个指定插口描述符;另一个指定连接队列门限值。 99-105 getsock返回描述符 s的file结构,solisten将请求传递给协议层。 solisten函数 solisten函数发送PRU_LISTEN请求,并使插口准备接收连接,如图15-24所示。 90-109 在solisten发送PRU_LISTEN请求且pr_usrreq返回后,标识插口处于准备接收连 接状态。如果当pr_usrreq返回时有连接正在连接队列中,则不设置SS_ACCEPTCONN标志。 计算存放进入连接的队列的最大值,并赋给 s o _ q l i m i t。N e t / 3默认设置下限为 0,上限 为5(SOMAXCONN)条连接。

图15-24 solisten 函数

15.10 tsleep和wakeup函数 当一个在内核中执行的进程因为得不到内核资源而不能继续执行时,它就调用 t s l e e p等 待。tsleep的原型是: int tsleep(caddr_t chan, int pri, char *mesg, int timeo);

t s l e e p的第一个参数 chan,被称之为等待通道。它标志进程等待的特定资源或事件。许 多进程能同时在同一个等待通道上睡眠。当资源可用或事件出现时,内核调用 w a k e u p,并将 等待通道作为唯一的参数传入。 wakeup的原型是: void wakeup(caddr_t chan);

所有等待在该通道上的的进程均被唤醒,并被设置成运行状态。当每一个进程均恢复执 行时,内核安排 tsleep返回。 当进程被唤醒时, t s l e e p的第二个参数 p r i指定被唤醒进程的优先级。 p r i中还包括几个 用于 t s l e e p 的可选的控制标志。通过设置 p r i 中的 P C A T C H 标志,当一个信号出现时, tsleep也返回。mesg是一个说明调用 tsleep的字符串,它将被放在调用报文或ps的输出中。

366

计计TCP/IP详解 卷 2:实现

timeo设置睡眠间隔的上限值,其单位是时钟滴答。 图15-25列出了tsleep的返回值。 因为所有等待在同一等待通道上的进程均被wakeup唤醒,所以我们总是看到在一个循环中 调用tsleep。每一个被唤醒的进程在继续执行之前必须检查等待的资源是否可得到,因为另一 个被唤醒的进程可能已经先一步得到了资源。如果仍然得不到资源,进程再调用tsleep等待。 描

tsleep() 0 EWOULDBLOCK ERESTART EINTR



进程被一个匹配的 wakeup唤醒 进程在睡眠 timeo个时钟滴答后,在匹配的 w a k e u p调用之前被唤醒 在睡眠期间信号被进程处理,应重新启动挂起的系统调用 在睡眠期间信号被进程处理,挂起的系统调用失败

图15-25 tsleep 的返回值

多个进程在一个插口上睡眠等待的情况是不多见的,所以,通常情况下,一次调用 wakeup只有一个进程被内核唤醒。 关于睡眠和唤醒机制的详细讨论请参考 [Leffler et al. 1989]。 举例 多个进程在同一个等待通道上睡眠的一个例子是:让多个服务器进程读同一个 U D P插口。 每一个服务器都调用recvfrom,并且只要没有数据可读就在 tsleep中等待。当一个数据报到 达插口时,插口层调用wakeup,所有等待进程均被放入运行队列。第一个运行的服务器读取了 数据报而其他的服务器则再次调用tsleep。在这种情况下,不需要每一个数据报启动一个新的 进程,就可将进入的数据报分发到多个服务器。这种技术同样可以用来处理 T C P的连接请求, 只需让多个进程在同一个插口上调用accept。这种技术在[Comer and Stevens 1993]中描述。

15.11 accept系统调用 调用l i s t e n后,进程调用 a c c e p t等待连接请求。 a c c e p t返回一个新的描述符,指向 一个连接到客户的新的插口。原来的插口 s仍然是未连接的,并准备接收下一个连接。如果 name指向一个正确的缓存, accept就会返回对方的地址。 处理连接的细节由与插口相关联的协议来完成。对于TCP而言,当一条连接已经被建立(即, 三次握手已经完成 )时,就通知插口层。对于其他的协议,如 OSI的TP4,只要一个连接请求到 达,tsleep就返回。当进程通过在插口上发送或接收数据来显式证实连接后,连接则算完成。 图15-26说明accept的实现。

图15-26 accept 系统调用

367

第 15章 插 口 层计计

图15-26 (续)

368

计计TCP/IP详解 卷 2:实现

图15-26 (续)

106-114 accept有三个参数:s为插口描述符; name为缓存指针,accept将把外部主机 的运输地址填入该缓存; anamelen是一个保存缓存大小的指针。 1. 验证参数 1 1 6 - 1 34 a c c e p t将缓存大小(* a n a m e l e n)赋给n a m e l e n,g e t s o c k返回插口的f i l e结 构。如果插口还没有准备好接收连接 (即,还没有调用 l i s t e n),或已经请求了非阻塞的 I / O, 且没有连接被送入队列,则分别返回 EINVAL或EWOULDBLOCK。 2. 等待连接 1 3 5 - 1 4 5 当出现下列情况时, w h i l e循环退出:有一条连接到达;出现差错;或插口不能 再接收数据。当信号被捕获之后 (t s l e e p返回E I N T R),a c c e p t并不自动重新启动。当协议 层通过sonewconn将一条连接插入队列后,唤醒进程。 在循环内,进程在 t s l e e p中等待,当有连接到达时, t s l e e p返回0。如果t s l e e p被信 号中断或插口被设置成非阻塞,则 accept返回EINTR或EWOULDBLOCK(图15-25)。 3. 异步差错 1 4 6 - 1 51 如果进程在睡眠期间出现差错,则将插口中的差错代码赋给 a c c e p t中的返回码, 清除插口中的差错码后, accept返回。 异步事件改变插口状态是比较常见的。协议处理层通过设置 s o _ e r r o r或唤醒在插口上 等待的所有进程来通知插口层插口状态的改变。因为这一点,插口层必须在每次被唤醒后检 查so_error,查看是否在进程睡眠期间有差错出现。 4. 将插口同描述符相关联 1 5 2 - 1 6 4 f a l l o c为新的连接分配一个描述符;调用 s o q r e m q u e将插口从接收队列中删 除,放到描述符的 file结构中。习题15.4讨论调用panic。 5. 协议处理 167-179 accept分配一个新的mbuf来保存外部地址,并调用 s o a c c e p t来完成协议处理。 在连接处理期间产生的新的插口的分配和排队在第 1 5 . 1 2中描述。如果进程提供了一个缓存来 接收外部地址, c o p y o u t将地址和地址长度分别从 n a m和n a m e l e n中复制给进程。如果有必 要,c o p y o u t还可能将地址截掉,如果进程提供的缓存不够大。最后,释放 m b u f,使能协议 处理,accept返回。 因为仅仅分配了一个 m b u f来存放外部地址,运输地址必须能放入一个 m b u f中。因为 U n i x 域地址是文件系统中的路径名 (最长可达 1 0 2 3个字节 ),所以要受到这个限制,但这对 I n t e r n e t 域中的 1 6字节长的 s o c k a d d r _ i n地址没有影响。第 1 7 0行的注释说明可以通过分配和复制一 个mbuf链的方式来去掉这个限制。 soaccept函数 soaccept函数通过协议层获得新的连接的客户地址,如图 15-27所示。

369

第 15章 插 口 层计计

图15-27 soaccept 函数

184-197

s o a c c e p t 确保插口与一个描述符相连,并发送 P R U _ A C C E P T请求给协议。

pr_usrreq返回后,nam中包含外部插口的名字。

15.12 sonewconn和soisconnected函数 从图1 5 - 2 6中可以看出, a c c e p t等待协议层处理进入的连接请求,并且将它们放入 s o _ q 中。图15-28利用TCP来说明这个过程。 等待进入的连接请求

已完成的连接

未完成的连接

发送SYN和ACK 等待ACK 进入的TCPSYN

TCP握手协议的最后一个ACK

图15-28 处理进入的 TCP连接

370

计计TCP/IP详解 卷 2:实现

在图1 5 - 2 8的左上角, a c c e p t调用t s l e e p等待进入的连接。在左下角, t c p _ i n p u t调 用s o n e w c o n n为新的连接产生一个插口来处理进入的 TCP SYN(图2 8 - 7 )。s o n e w c o n n将产 生的插口放入so_q0排队,因为三次握手还没有完成。 当TCP握手协议的最后一个ACK到达时, tcp_input调用soisconnected(图29-2)来更 新产生的插口,并将它从so_q0中移到so_q中,唤醒所有调用accept等待进入的连接的进程。 图的右上角说明我们在图 1 5 - 2 6中描述的函数。当 t s l e e p返回时, a c c e p t从s o _ q中得 到连接,发送 P R U _ A T T A C H请求。插口同一个新的文件描述符建立了联系, a c c e p t也返回 到调用进程。 图15-29显示了sonewconn函数。

图15-29 sonewconn 函数

123-129

协议层将 h e a d(指向正在接收连接的插口的指针 )和c o n n s t a t u s(指示新连接的

状态的标志 )传给sonewconn。对于TCP而言,connstatus总是等于0。 对于T P 4,c o n n s t a t u s总是等于 S S _ I S C O N F I R M I N G。当一个进程开始从插 口上接收或发送数据时隐式证实连接。

371

第 15章 插 口 层计计

1. 限制进入的连接 130-131 当下面的不等式成立时, sonewconn不再接收任何连接: so_qlen + so_q0len >

3× so_qlimit 2

这个不等式为一直没有完成的连接提供了一个令人费解的因子,且该不等式确保 listen(fd, 0)允许一条连接。有关这个不等式的详细情况请参考卷 1的图18-23。 2. 分配一个新的插口 132-143

一个新的 s o c k e t结构被分配和初始化。如果进程对处理接收连接状态的插口调

用了 s e t s o c k o p t ,则新产生的 s o c k e t 继承好几个插口选项,因为 s o _ o p t i o n s 、 so_linger、so_pgid和sb_hiwat的值被复制到新的 socket结构中。 3. 排队连接 144

在第1 2 9行的代码中,根据 c o n n s t a t u s的值设置 s o q u e u e。如果s o q u e u e为0 (如,

T C P连接),则将新的插口插入到 s o _ q 0中;若c o n n s t a t u s等于非0值,则将其插入到 s o _ q 中(如,TP4连接)。 4. 协议处理 145-150

发送P R U _ A T T A C H请求,启动协议层对新的连接的处理。如果处理失败,则将插

口从队列中删除并丢弃,然后 sonewconn返回一个空指针。 5. 唤醒进程 151-157

如果connstatus等于非0值,所有在 accept中睡眠或查询插口的可读性的进程

均被唤醒。将 c o n n s t a t u s对s o _ s t a t e执行或操作。 T C P协议从来不会执行这段代码,因 为对TCP而言,connstatus总是等于0。 某些将进入的连接首先插入

s o _ q 0 队列中的协议在连接建立阶段完成时调用

soisconnected,如TCP。对于TCP,当第二个 SYN被应答时,就出现这种情况。 图15-30显示了soisconnected的代码。

图15-30 soisconnected

函数

6. 排队未完成的连接 78-87

通过修改插口的状态来表明连接已经完成。当对进入的连接调用 s o i s c o n n e c t e d

372

计计TCP/IP详解 卷 2:实现

(即,本地进程正在调用 accept)时,head为非空。 如果s o q r e m q u e返回1,就将插口放入 s o _ q排队,s o r w a k e u p唤醒通过调用 s e l e c t测 试插口的可读性来监控插口上连接到达的进程。如果进程在 a c c e p t中因等待连接而阻塞,则 wakeup使得相应的 tsleep返回。 7. 唤醒等待新连接的进程 8 8 - 9 3 如果h e a d为空,就不需要调用 s o q r e m q u e,因为进程用 c o n n e c t系统调用初始化 连接,且插口不在队列中。如果 h e a d非空,且 s o q r e m q u e返回0,则插口已经在 s o _ q队列 中。在某些协议中,如 T P 4,就出现这种情况,因为在 T P 4中,连接完成之前就已插入到 s o _ q队列中。 w a k e u p唤醒所有阻塞在 c o n n e c t中的进程,s o r w a k e u p和s o w w a k e u p负责 唤醒那些调用select等待连接完成的进程。

15.13 connect系统调用 服务器进程调用 l i s t e n和a c c e p t系统调用等待远程进程初始化连接。如果进程想自己 初始化一条连接 (即客户端),则调用connect。 对于面向连接的协议如 T C P,c o n n e c t建立一条与指定的外部地址的连接。如果进程没 有调用bind来绑定地址,则内核选择并且隐式地绑定一个地址到插口。 对于无连接协议如 UDP或ICMP,c o n n e c t记录外部地址,以便发送数据报时使用。任何 以前的外部地址均被新的地址所代替。 图15-31显示了UDP或TCP调用connect时涉及到的函数。

请求

请求

TCP开始 三次握手

TCP三次 握手完成

TCP连接建立

图15-31 connect 处理过程

373

第 15章 插 口 层计计

图的左边说明 c o n n e c t 如何处理无连接协议,如 U D P。在这种情况下,协议层调用 soisconnected后connect系统调用立即返回。 图的右边说明 c o n n e c t如何处理面向连接的协议,如 T C P。在这种情况下,协议层开始 建立连接,调用 s o i s c o n n e c t i n g指示连接将在某个时候完成。如果插口是非阻塞的, s o c o n n e c t 调用 t s l e e p 等待连接完成。对于 T C P ,当三次握手完成时,协议层调用 s o i s c o n n e c t e d 将插口标识为已连接,然后调用 w a k e u p唤醒等待的进程,从而完成 connect系统调用。 图15-32列出了connect系统调用的代码。

图15-32 connect 系统调用

374

计计TCP/IP详解 卷 2:实现

180-188

connect的三个参数 (在connect_args结构中)是:s为插口描述符; name是一

个指针,指向存放外部地址的缓存; namelen为缓存的长度。 189-200

g e t s o c k获取插口描述符对应的 f i l e结构。可能已有连接请求在非阻塞的插口

上,若出现这种情况,则返回 EALREADY。函数sockargs将外部地址从进程复制到内核。 1. 开始连接处理 201-208

连接是从调用soconnect开始的。如果soconnect报告差错出现, connect跳

转到 b a d。如果 s o c o n n e c t 返回时连接还没有完成且使能了非阻塞的 I / O ,则立即返回 E I N P R O G R E S S以免等待连接完成。因为通常情况下,建立连接要涉及同远程系统交换几个 分组,因而这个过程可能需要一些时间才能完成。如果连接没完成,则下次对 c o n n e c t调用 就返回EALREADY。当连接完成时, soconnect返回EISCONN。 2. 等待连接建立 208-217

w h i l e循环直到连接已建立或出现差错时才退出。 s p l n e t防止c o n n e c t在测试

插口状态和调用 t s l e e p之间错过w a k e u p。循环完成后, e r r o r包含0、t s l e e p中的差错代 码或插口中的差错代码。 218-224

清除S S _ I S C O N N E C T I N G标志,因为连接已完成或连接请求已失败。释放存储外

部地址的mbuf,返回差错代码。 15.13.1 soconnect函数 s o c o n n e c t函数确保插口处于正确的连接状态。如果插口没有连接或连接没有被挂起, 则连接请求总是正确的。如果插口已经连接或连接正等待处理,则新的连接请求将被面向连 接的协议 (如TCP)拒绝。对于无连接协议,如 UDP,多个连接是允许的,但是每一个新的请求

图15-33 soconnect 函数

375

第 15章 插 口 层计计

中的外部地址会取代原来的外部地址。 图15-33列出了soconnect函数的代码。 198-222

如果插口被标识准备接收连接,则 s o c o n n e c t返回E O P N O T S U P P,因为如果已

经对插口调用了 l i s t e n,则进程不能再初始化连接。如果协议是面向连接的,且一条连接已 经被初始化,则返回 E I S C O N N 。对于无连接协议,任何已有的同外部地址的联系都被 sodisconnect切断。 PRU_CONNECT请求启动相应的协议处理来建立连接或关联。 15.13.2 切断无连接插口和外部地址的关联 对于无连接协议,可以通过调用 c o n n e c t,并传入一个不正确的 n a m e参数,如指向内容 为全0的结构指针或大小不对的结构,来丢弃同插口相关联的外部地址。 s o d i s c o n n e c t删 除同插口相关联的外部地址, P R U _ C O N N E C T 返回差错代码,如 E A F N O S U P P O R T 或 E A D D R N O T A V A I L,留下没有外部地址的插口。这种方式虽然有点晦涩,但却是一种比较有 用的断连方式,在无连接插口和外部地址之间断连,而不是替换。

15.14 shutdown系统调用 s h u t d o w n系统调用关闭连接的读通道、写通道或读写通道,如图 1 5 - 3 4所示。对于读通 道,s h u t d o w n丢弃所有进程还没有读走的数据以及调用 s h u t d o w n之后到达的数据。对于 写通道, s h u t d o w n使协议作相应的处理。对于 T C P,所有剩余的数据将被发送,发送完成 后发送FIN。这就是TCP的半关闭特点(参考卷1的第18.5节)。 为了删除插口和释放描述符,必须调用 c l o s e。可以在没有调用 s h u t d o w n 的情况下, 直接调用 c l o s e。同所有描述符一样,当进程结束时,内核将调用 c l o s e,关闭所有还没有 被关闭的插口。

图15-34 shutdown 系统调用

550-557

在s h u t d o w n _ a r g s结构中, s为插口描述符, h o w指明关闭连接的方式。图 1 5 -

35列出了how和how++(在图15-36中用到的)的期望值。

376

计计TCP/IP详解 卷 2:实现

how

how++

0 1 2

FREAD FWRITE FREAD|FWRITE





关闭连接的读通道 关闭连接的写通道 关闭连接的读写通道

图15-35 shutdown 系统调用选项

注意,在how和常数FREAD和FWRITE之间有一种隐含的数值关系。 558-564

shutdow n是函数s o s h u t d o w n的包装函数 (wrapper function)。由g e t s o c k返回

与描述符相关联的插口,调用 soshutdown,并返回其值。 soshutdown和sorflush函数 关闭连接的读通道是由插口层调用 s o r f l u s h 处理的,写通道的关闭是由协议层的 PRU_SHUTDOWN请求处理的。soshutdown函数如图15-36所示。

图15-36 soshutdown 函数

720-732

如果是关闭插口的读通道,则 s o r f l u s h丢弃插口接收缓存中的数据,禁止读连

接(如图15-37所示)。如果是关闭插口的写通道,则给协议发送 PRU_SHUTDOWN请求。 733-747

进程等待给接收缓存加锁。因为 S B _ N O I N T R 被设置,所以当中断出现时,

s b l o c k并不返回。在修改插口状态时, s p l i m p阻塞网络中断和协议处理,因为协议层在接 收到进入的分组时可能要访问接收缓存。 s o c a n t r c v m o r e标识插口拒绝接收进入的分组。将 s o c k b u f结构保存在 a s b中,当 splx恢复中断后,要使用 asb。调用bzero清除原始的 sockbuf结构,使得接收队列为空。 释放控制mbuf 748-751

当shutdown被调用时,存储在接收队列中的控制信息可能引用了一些内核资源。

通过sockbuf结构的副本中的 sb_mb仍然可以访问mbuf链。 如果协议支持访问权限,且注册了一个dom_dispose函数,则调用该函数来释放这些资源。 在U n i x域中,用控制报文在进程间传递描述符是可能的。这些报文包含一些引 用计数的数据结构的指针。 d o m _ d i s p o s e函数负责去掉这些引用,如果必要,还释 放相关的数据缓存以避免产生一些未引用的结构和导致内存漏洞。有关在 U n i x域内 传递文件描述符的细节请参考 [Stevens 1990]和[Leffler et al. 1989]。

377

第 15章 插 口 层计计

图15-37 sorflush 函数

当s b r e l e a s e释放接收队列中的所有 mbuf时,丢弃所有调用 s h u t d o w n时还没有被处理 的数据。 注意,连接的读通道的关闭完全由插口层来处理 (习题1 5 . 6 ),连接的写通道的关闭通过发 送P R U _ S H U T D O W N请求交由协议处理。 TCP协议收到P R U _ S H U T D O W N请求后,发送所有排队 的数据,然后发送一个 FIN来关闭TCP连接的写通道。

15.15 close系统调用 c l o s e系统调用能用来关闭各类描述符。当 f d是引用对象的最后的描述符时,与对象有 关的close函数被调用: error = (*fp->f_ops->fo_close)(fp,p);

如图15-13所示,插口的fp->f_ops->fo_close是soo_close函数。 15.15.1 soo_close函数 soo_close函数是soclose函数的封装器,如图 15-38所示。

图15-38 soo_close 函数

378

计计TCP/IP详解 卷 2:实现

152-161

如果s o c k e t结构与f i l e相关联,则调用 s o c l o s e,清除f _ d a t a,返回已出现

的差错。 15.15.2 soclose函数 s o c l o s e函数取消插口上所有未完成的连接 (即,还没有完全被进程接受的连接 ),等待 数据被传输到外部系统,释放不需要的数据结构。 soclose函数的代码如图 15-39所示。

图15-39 soclose 函数

379

第 15章 插 口 层计计

1. 丢弃未完成的连接 129-141 如果插口正在接收连接, soclose遍历两个连接队列,并且调用 soabort取消每 一个挂起的连接。如果协议控制块为空,则协议已同插口分离, s o c l o s e跳转到d i s c a r d进 行退出处理。 s o a b o r t 发送 P R U _ A B O R T 请求给协议,并返回结果。本书中没有介绍 soabort的代码。图 23-38和图30-7讨论了UDP和TCP如何处理PRU_ABORT请求。 2. 断开已建立的连接或关联 142-157

如果插口没有同任何外部地址相连接,则跳转到 drop处继续执行。否则,必须断

开插口与对等地址之间的连接。如果断连没有开始,则 s o d i s c o n n e c t启动断连进程。如果 设置了SO_LINGER插口选项, soclose可能要等到断连完成后才返回。对于一个非阻塞的插 口,从来不需要等待断连完成,所以在这种情况下, soclose立即跳转到 drop。否则,连接 终止正在进行且 SO_LINGER选项指示soclose必须等待一段时间才能完成操作。直到出现下 列情况时while才退出:断连完成;拖延时间 (so_linger)到;或进程收到了一个信号。 如果滞留时间被设为 0,t s l e e p仅当断连完成 (也许因为一个差错 )或收到一个信号 时才返回。 3. 释放数据结构 158-173

如果插口仍然同协议相连,则发送 P R U _ D E T A C H请求断开插口与协议的联系。最

后,插口被标记为同任何描述符没有关联,这意味着可以调用 sofree释放插口。 sofree函数代码如图15-40所示。

图15-40 sofree 函数

4. 如果插口仍在用则返回 110-114

如果仍然有协议同插口相连,或如果插口仍然同描述符相连,则 s o f r e e立即返

回。 5. 从连接队列中删除插口 115-119

如果插口仍在连接队列上 (s o _ h e a d非空),则插口的队列应该为空。如果不空,

则插口代码和内核 panic中有差错。如果队列为空,清除 so_head。 6. 释放发送和接收队列中的缓存

380

计计TCP/IP详解 卷 2:实现

120-123 sorelease释放发送队列中的所有缓存,sorflush释放接收队列中的所有缓存。 最后,释放插口本身。

15.16 小结 本章中我们讨论了所有与网络操作有关的系统调用。描述了系统调用机制,并且跟踪系 统调用直到它们通过 pr_usrreq函数进入协议处理层。 在讨论插口层时,我们避免涉及地址格式、协议语义或协议实现等问题。在接下来的章 节中,我们将通过协议处理层中的 Internet协议的实现将链路层处理和插口层处理联系在一起。

习题 15.1 一个没有超级用户权限的进程怎样才能获取对超级用户进程产生的插口的访问权 ? 15.2 一个进程怎样才能判断它提供给 a c c e p t的s o c k a d d r缓存是不是太小以至不能存 放调用返回的外部地址 ? 15.3 I P v 6的插口有一个特点:使 a c c e p t和r e c v f r o m返回一个 128 bit的I P v 6地址的数 组作为源路由,而不是仅返回一个对等地址。因为数组不能存放在一个 mbuf中,所 以修改 a c c e p t和r e c v f r o m,使得它们能够处理协议层来的 m b u f链而不是仅仅一 个m b u f。如果协议在 m b u f簇中返回一个数组而不是一个 m b u f链,已有的代码仍然 能正常工作吗? 15.4 为什么在图 15-26中当soqremque返回一个空指针时要调用 panic? 15.5 为什么sorflush要复制接收缓存 ? 15.6 在s o r f l u s h将插口的接收缓存清 0后,如果还有数据到达会出现什么现象 ?在做这 个习题之前请阅读第 16章的内容。

第16章 插 口 I/O 16.1 引言 本章讨论有关从网络连接上读写数据的系统调用,分三部分介绍。 第一部分介绍四个用来发送数据的系统调用:write、writev、sendto和sendmsg。第 二部分介绍四个用来接收数据的系统调用:read、readv、recvfrom和recvmsg。第三部分 介绍select系统调用,select调用的作用是监控通用描述符和特殊描述符(插口)的状态。 插口层的核心是两个函数: s o s e n d和s o r e c e i v e。这两个函数负责处理所有插口层和 协议层之间的 I / O操作。在后续的章节中我们将看到,因为这两个函数要处理插口层和各种类 型的协议之间的 I/O操作,使得这两个函数特别长和复杂。

16.2 代码介绍 图16-1中列出了本章后续章节要用到的三个头文件和四个 C源文件。 文 件 名



sys/socket.h sys/socketvar.h sys/uio.h kern/uipc_syscalls.c kern/uipc_socket.c kern/sys_generic.c kern/sys_socket.c



插口API中的结构和宏定义 s o c k e t结构和宏定义 u i o结构定义 s o c k e t系统调用 插口层处理 s e l e c t系统调用 s e l e c t对插口的处理

图16-1 本章涉及的头文件和 C源文件

全局变量 图1 6 - 2列出了三个全局变量。前两个变量由 s e l e c t系统调用使用,第三个变量控制分配 给插口的存储器大小。 变



selwait nselcoll sb_max

数据类型 int int u_long





s e l e c t调用的等待通道 避免s e l e c t调用中出现竞争的标志 插口发送或接收缓存的最大字节数

图16-2 本章涉及的全局变量

16.3 插口缓存 从第15.3节我们已经知道,每一个插口都有一个发送缓存和一个接收缓存。缓存的类型为

382

计计TCP/IP详解 卷 2:实现

sockbuf。图16-3中列出了sockbuf结构的定义 (重复图15-5)。

图16-3 sockbuf 结构

72-78

每一个缓存均包含控制信息和指向存储数据的 mbuf链的指针。 sb_mb指向mbuf链的

第一个 m b u f,s b _ c c的值等于存储在 m b u f链中的数据字节数。 s b _ h i w a t和s b _ l o w a t用来 调整插口的流控算法。 sb_mbcnt等于分配给缓存中的所有 mbuf的存储器数量。 在前面的章节中提到过每一个 m b u f可存储 0 ~ 2 0 4 8个字节的数据 (如果使用了外部簇 )。 s b _ m b m a x是分配给插口 mbuf缓存的存储器数量的上限。默认的上限在 socket系统调用中发送 PRU_ATTACH请求时由协议设置。只要内核要求的每个插口缓存的大小不超过 262,144个字节 的限制(s b _ m a x),进程就可以修改缓存的上限和下限。流控算法将在 1 6 . 4节和1 6 . 8节中讨论。 图16-4显示了Internet协议的默认设置。 协

议 (忽略)

原始 (忽略)

图16-4 Internet协议的默认的插口缓存限制

因为每一个进入的 U D P报文的源地址同数据一起排队,所以 U D P协议的 s b _ h i w a t的默 认值设置为能容纳 40个1K字节长的数据报和相应的 sockaddr_in结构(每个16字节)。 79

sb_sel是一个用来实现 select系统调用的 selinfo结构(16.13节)。

80

图16-5列出了sb_flags的所有可能的值。 sb-flags SB_LOCK SB_WANT SB_WAIT SB_SEL SB_ASYNC SB_NOINTR SB_NOTIFY





一个进程已经锁定了插口缓存 一个进程正在等待给插口缓存加锁 一个进程正在等待接收数据或发送数据所需的缓存 一个或多个进程正在选择这个缓存 为这个缓存产生异步 I/O信号 信号不取消加锁请求 (SB_WAIT|SB_AEL|SB_ASYNC) 一个进程正在等待缓存的变化,如果缓存发生任何改变,用 w a k e u p通知该进程

图16-5 sb_flags 的值

383

第 16章 插 口 I/O计计

81-82

s b _ t i m e o用来限制一个进程在读写调用中被阻塞的时间,单位为时钟滴答 ( t i c k )。

默认值为 0,表示进程无限期的等待。 S O _ S N D T I M E O和S O _ R C V T I M E O插口选项可以改变或 读取sb_timeo的值。 插口宏和函数 有许多宏和函数用来管理插口的发送和接收缓存。图 1 6 - 6中列出了与缓存加锁和同步有 关的宏和函数。 名







申请给sb加锁,如果 wf等于M _ W A I T O K,则进程睡眠等待加锁;否则,如果不能立即给 缓存加锁,就返回 E W O U L D B L O C K 。如果进程睡眠被一个信号中断,则返回 E I N T R或 EREST A R T;否则返回 0

sblock

int s b l o c k( s t r u c t s o c k b u f * s b , i n wt f) ; 释放加在sb上的锁。所有等待给 sb加锁的进程被唤醒

sbunlock

v o i d s b u n l o c k( s t r u c t s o c k b u f sb); * 调用t s l e e p等待sb上的协议动作。返回 t s l e e p返回的结果

sbwait

int s b w a i t( s t r u c t s o c k b u f sb); * 通知插口有协议动作出现。唤醒所有匹配的调用 s b w a i t的进程或在 sb上调用t s l e e p的 进程

sowakeup

v o i d s o w a k e u p( s t r u c t s o c k e t sb, * s t r u c t s o c k b u f sb); * sorwakeup

唤醒等待sb上的读事件的进程,如果进程请求了 I/O事件的异步通知,则还应给该进程发 送SIGI O信号 void s o r w a k e u p( s t r u c t s o c k e t sb); *

sowwakeup

唤醒等待sb上的写事件的进程,如果进程请求了 I/O事件的异步通知,则还应给该进程发 送SIGI O信号 v o i d s o w w a k e u p( s t r u c t s o c k e t sb); *

图16-6 与缓存加锁和同步有关的宏和函数

图1 6 - 7显示了设置插口资源限制、往缓存中写数据和从缓存中删除数据的宏和函数。在 该表中,m、m0、n和control都是指向mbuf链的指针。 sb指向插口的发送或接收缓存。 名



sbspace

sballoc sbfree sbappend sbappendrecord





sb中可用的空间 (字节数) : min(sb_hiwat - sb_cc), (sb_mbmax - sb_mbcont) long s b s p a c e( s t r u c t s o c k b u fsb); * 将m加到sb中,同时修改 sb中的s b _ c c和s b _ m b c n t v o i d s b a l l o c( s t r u c t s o c k b u f sb, * s t r u c t m b u f m); * 从sb中删除m,同时修改 sb中的s b _ c c和s b _ m b c n t int s b f r e e( s t r u c t s o c k b u f sb, * s t r u c t m b u f m); * 将m中的mbuf加到sb的最后面 int s b a p p e n d( s t r u c t s o c k b u f sb, * s t r u c t m b u f m*); 将m0中的记录加到 sb的最后面。调用 s b c o m p r e s s int s b a p p e n d r e c o r d( s t r u c t s o c k b u f sb, * s t r u c t m b u f m*0);

图16-7 与插口缓存分配与操作有关的宏和函数

384

计计TCP/IP详解 卷 2:实现





sbappendaddr

sbappendcontrol

sbinsertoob sbcompress

sbdrop sbdroprecord sbrelease sbflush soreserve

sbreserve





将asa的地址放入一个 mbuf。将地址、 control和m0连接成一个 mbuf链,并将该 链放在sb的最后面 int a b a p p e n d a d d r(struct *sb, s t r u c t s o c k a d d ras* a, s t r u c t m b u f m*0, s t r u c t m b u f co*ntrol); 将control和m0连接成一个 mbuf链,并将该链放在 sb的最后面 int a b a p p e n d c o n t r o l( s t r u c t s*b, s t r u c t m b u f m*0, s t r u c t m b u f c* ontrol); 将m0插在没有带外数据的 sb的第一个记录的前面 int a b i n s e r t o o b( s t r u c t s o c k b u f sb, * s t r u c t m b u f m*0); 将m合并到n中并压缩没用的空间 v o i d a b c o m p r e s s( s t r u c t s o c k b u f sb, * s t r u c t m b u f m, * s t r u c t m b u f n); * 删除sb的前len个字节 v o i d s b d r o p( s t r u c t s o c k b u f sb, * i n t len); 删除sb的第一个记录,将下一个记录移作第一个记录 v o i d s b d r o p r e c o r d( s t r u c t s o c k b u f sb); * 调用s b f l u s h释放sb中所有的mbuf。并将s b _ h i w a t和s b _ m b m a x清0 v o i d s b r e l e a s e( s t r u c t s o c k b u f sb); * 释放sb中的所有mbuf v o i d s b f l u s h( s t r u c t s o c k b u f sb); * 设置插口缓存高、低水位标记 ( h i g h - w a t e r a n d l o w - w a t e r m a。对 rk) 于发送缓存,调用 s b r e s e r v e 并传入参数 s n d c c 。对于接收缓存,调用 s b r e s e r v e并传入参数 r c v c c。将发送缓存和接收缓存的 s b _ l o w a t初始化成默 认值(图16-4)。如果超过系统限制,则返回 E N O B U F S int s o r e s e r v e( s t r u c t s o c k e t so, * i n t sndcc, i n t rcvcc); 将sb的高水位标记设置成cc。同时将低水位标记降到cc。本函数不分配存储器 int s b r e s e r v e( s t r u c t s o c k b u f sb, * i n t cc);

图16-7 (续)

16.4 write、writev、sendto和send m s g系统调用 我们将w r i t e、w r i t e v、s e n d t o和s e n d m s g四个系统调用统称为写系统调用,它们的 作用是往网络连接上发送数据。相对于最一般的调用 s e n d m s g而言,前三个系统调用是比较 简单的接口。 所有的写系统调用都要直接或间接地调用 s o s e n d。s o s e n d的功能是将进程来的数据复 制到内核,并将数据传递给与插口相关的协议。图 16-8给出了sosend的工作流程。 在下面的章节中,我们将讨论图

1 6 - 8 中带阴影的函数。其余的四个系统调用和

soo_write 留给读者自己去了解。 图16-9说明了这四个系统调用和一个相关的库函数 (send)的特点。 在N e t / 3中,s e n d被实现成一个调用 s e n d t o的库函数。为了与以前编译的程序 二进制兼容,内核将旧的 send调用映射成函数 osend,该函数不在本书中讨论。 从图16-9的第二栏中可以看出, w r i t e和w r i t e v系统调用适用于任何描述符,而其他的 系统调用只适用于插口描述符。

385

第 16章 插 口 I/O计计

库函数 进程 内核

库函数

TCP

TP 4

通过pr_usrreq发送的 PRU_SEND或PRU_SENDOOB ICMP

UDP

图16-8 所有的插口输出均由 sosend 处理 函



write writev send sendto sendmsg

描述符类型

任何类型 任何类型 插口 插口 插口

缓存数量

是否指明 目的地址

1 [1..UIO_MAXIOV] 1 1 [1..UIO-MAXIOV]

• •



志?

控制信息?

• • •



图16-9 写系统调用

从图1 6 - 9的第三栏中可以看出, w r i t e v和s e n d m s g系统调用可以接收从多个缓存中来 的数据。从多个缓存中写数据称为收集

( g a t h e r i n g ),同它相对应的读操作称为分散

(s c a t t e r i n g)。执行收集操作时,内核按序接收类型为 i o v e c的数组中指定的缓存中的数 据。数组最多有 UIO_MAXIOV个单元。图 16-10显示了类型 iovec的结构。

图16-10 iovec 结构

41-44 在图16-10中,iov_base指向长度为 iov_len个字节的缓存的开始。 如果没有这种接口,一个进程将不得不将多个缓存复制到一个大的缓存中,或调用多个

386

计计TCP/IP详解 卷 2:实现

写系统调用来发送从多个缓存来的数据。相对于用一个系统调用传送类型为 i o v e c的数组, 这两种方法的效率更低。对于数据报协议而言,调用一次 w r i t e v就是发送一个数据报,数据 报的发送不能用多个写动作来实现。 图1 6 - 11说明了i o v e c结构在w r i t e v系统调用中的应用,图中 i o v p指向数组的第一个元 素,iovcnt等于数组的大小。

图16-11 writev 系统调用中的 iovec 参数

数据报协议要求每一个写调用必须指定一个目的地址。因为 w r i t e、w r i t e v和s e n d调 用接口不支持对目的地址的指定,因此这些调用只能在调用 c o n n e c t将目的地址同一个无连 接的插口联系起来后才能被调用。调用 s e n d t o或s e n d m s g时必须提供目的地址,或在调用 它们之前调用connect来指定目的地址。 图1 6 - 9的第五栏显示 s e n d x x x系统调用接收一个可选的控制标志,这些标志在图 1 6 - 1 2中 定义。 flags MSG_DONTROUTE MSG_DONTWAIT MSG_EOR MSG_OOB





发送本报文时,不查路由表 发送本报文时,不等待资源 标志一个逻辑记录的结束 发送带外数据





图16-23 图16-22 图16-25 图16-26

图16-12 send xxx系统调用: flags 值

如图1 6 - 9的最后一栏所示,只有 s e n d m s g系统调用支持控制信息。控制信息和另外几个 参数是通过结构 msghdr(图16-13)一次传递给 sendmsg,而不是分别传递。

图16-13 msghdr 结构

msg_name应该被说明成一个指向sockaddr结构的指针,因为它包含网络地址。 228-236

msghdr结构包含一个目的地址 (msg_name和msg_namelen)、一个分散 /收集数

组(m s g _ i o v和m s g _ i o v l e n)、控制信息(m s g _ c o n t r o l和m s g _ c o n t r o l l e n)和接收标志

387

第 16章 插 口 I/O计计

(msg_flags)。控制信息的类型为 cmsghdr结构,如图 16-14所示。

图16-14 cmsghdr 结构

251-256

插口层并不解释控制信息,但是报文的类型被置为 c m s g _ t y p e,且报文长度为

cmsg_len。多个控制报文可能出现在控制信息缓存中。 举例 图16-15说明了在调用sendmsg时msghdr的结构。

图16-15 sendmsg 系统调用的 msghdr 结构

16.5 sendmsg系统调用 只有通过 s e n d m s g系统调用才能访问到与插口 A P I的输出有关的所有功能。 s e n d m s g和 s e n d i t函数准备 s o s e n d系统调用所需的数据结构,然后由 s o s e n d调用将报文发送给相应 的协议。对 S O C K _ D G R A M协议而言,报文就是数据报。对 S O C K _ S T R E A M协议而言,报文是 一串字节流。对于 S O C K _ S E Q P A C K E T协议而言,报文可能是一个完整的记录 (隐含的记录边 界)或一个大的记录的一部分 (显式的记录边界 )。对于 S O C K _ P D M协议而言,报文总是一个完 整的记录(隐含的记录边界 )。 即使一般的 s o s e n d代码处理 S O C K _ S E Q P A C K E T和S O C K _ P D K协议,但是在 Internet域中没有这样的协议。 图16-16显示了sendmsg系统调用的源代码。 307-319

s e n d m s g有三个参数:插口描述符;指向 m s g h d r结构的指针;几个控制标志。

函数copyin将msghdr结构从用户空间复制到内核。

388

计计TCP/IP详解 卷 2:实现

图16-16 sendmsg 系统调用

1. 复制iov数组 320-334

一个有8个元素(U I O _ S M A L L I O V)的i o v e c数组从栈中自动分配。如果分配的数

组不够大, s e n d m s g 将调用 M A L L O C 分配更大的数组。如果进程指定的数组单元大于 1024(U I O _ M A X I O V),则返回 E M S G S I Z E。c o p y i n将i o v e c数组从用户空间复制到栈中的数 组或一个更大的动态分配的数组中。 这种技术避免了调用 m a l l o c带来的高代价,因为大多数情况下,数组的单元数 小于等于8。 2. sendit和cleanup 335-340

如果s e n d i t返回,则表明数据已经发送给相应的协议或出现差错。 s e n d m s g释

放iovec数组(如果它是动态分配的 ),并且返回sendit调用返回的结果。

16.6 sendit函数 s e n d i t函数是被 s e n d t o和s e n d m s g调用的公共函数。 s e n d i t初始化一个 u i o结构,

389

第 16章 插 口 I/O计计

将控制和地址信息从进程空间复制到内核。在讨论 s o s e n d之前,我们必须先解释 u i o m o v e 函数和uio结构。 16.6.1 uiomove函数 uiomove函数的原型为: int uiomove(caddr_t cp, int n, struct uio *uio);

u i o mo v e函数的功能是在由 c p指向的缓存与 u i o指向的类型为 i o v e c的数组中的多个缓存之 间传送n个字节。图 16-7说明了uio结构的定义,该结构控制和记录 uiomove的行为。

图16-17 uio 结构

45-61

在u i o 结构中, u i o _ i o v 指向类型为 i o v e c 结构的数组, u i o _ o f f s e t记录

u i o m o v e 传送的字节数, u i o _ r e s i d 记录剩余的字节数。每次调用

uiomove,

uio_offset增加n, u i o _ r e s i减去n。同时,u d iomove根据传送的字节数调整 uio_iov 数组中的基指针和缓存长度,从而从缓存中删除每次调用时传送的字节。最后,每当从 uio_iov中传送一块缓存, u i o _ i o v数组的每个单元就向前进一个数组单元。 u i o _ s e g f l g 指向u i o _ i o v数组的基指针指向的缓存的位置。 u i o _ r w指定数据传送的方向。缓存可能在 用户数据空间,用户指令空间或内核数据空间。图 1 6 - 1 8对u i o m o v e函数的操作进行了小结。 图中对操作的描述用到了 uiomove函数原型中的参数名。 uio_segflg UIO_USERSPACE UIO_USERISPACE UIO_USERSPACE UIO_USERISPACE UIO_SYSSPACE



uio_rw



UIO-READ

从内核缓存 cp中分散n个字节到进程缓存

UIO-WRITE

从进程缓存中收集 n个字节到内核缓存 c p

UIO-READ UIO-WRITE

从内核缓存 cp中分散n个字节到多个内核缓存 从多个内核缓存中收集 n个字节到内核缓存 cp中

图16-18 uiomove 操作

390

计计TCP/IP详解 卷 2:实现

16.6.2 举例 图16-19显示了一个调用 uiomove之前的uio结构。

进程 内核

进程

图16-19 调用uiomove 前的uio 结构

u i o _ i o v指向i o v e c数组的第一个单元。 i o v _ b a s e指针数组的每一个单元分别指向它 们在进程地址空间中的缓存的起始地址。 u i o _ o f f s e t等于0,u i o _ r e s i d等于三块缓存的 总的大小。 c p指向内核中的一块缓存,一般来说,这块缓存是一个 m b u f的数据区。图 1 6 - 2 0 显示了调用 uiomove之后同一个 uio结构的内容。 uiomove(cp, n, uio);

进程 内核

进程

图16-20 调用uiomove 后的uio 结构

391

第 16章 插 口 I/O计计

在上述调用中, n 包括第一块缓存中的所有字节和第二块缓存中的部分字节

( 即,

n0 0的测试好像无关紧要。当 a t o m i c没有被设置时, s p a c e也是无关 紧要的,因为一次只传送一个 mbuf给协议。如果设置了 a t o m i c,只有当有足够的缓 存空间来存放整个报文时才进入这个循环。参考习题 16-2。 s o s e n d的最后一段代码的功能是传送数据和控制 m b u f给插口指定的协议,如图 1 6 - 2 6所 示。

图16-26 sosend 函数:协议分散

397-405

在传送数据到协议层的前后,可能通过 SO_DONTROUTE选项选择是否利用路由表

为这个报文选择路由。这是唯一的一个针对单个报文的选项,如图 1 6 - 2 3所示,在写期间通过 MSG_DONTROUTE标志来控制路由选择。 为了防止协议在处理报文期间 p r _ u s r r e q阻塞中断, p r _ u s r r e q被放在s p l n e t函数和 s p l x函数之间执行。一些协议 (如U D P )可能在进行输出处理期间并不阻塞中断,但插口层得 不到这些信息。 如果进程传送的是带外数据,则 s o s e n d 发送 P R U _ S E N D O O B 请求;否则,它发送 PRU_SEND请求。同时将地址和控制 mbuf传送给协议。 406-413

因为控制信息只需传送给协议一次,所以将 clen、control、top和mp初始化,

然后为传送报文的下一部分构造新的 m b u f链。只有 a t o m i c没有被设置时 (如T C P ),r e s i d才

401

第 16章 插 口 I/O计计

可能等于非 0。在这种情况下,如果缓存中仍然有空间,则 s o s e n d回到循环开始,继续写另 一个mbuf。如果没有可用空间,则 sosend回到循环开始,等待可用空间 (图16-24)。 在第2 3章我们将了解到不可靠的协议,如 U D P,立即将数据排队等待发送。第 2 6章描述 可靠的协议,如 TCP,将数据放到插口发送缓存直到数据被发送和确认。 16.7.3 sosend函数小结 s o s e n d是一个比较复杂的函数。它共有 1 4 2行,包含 3个嵌套的循环,一个利用 g o t o实 现的循环,两个基于是否设置 P R _ A T O M I C的代码分支,两个并行锁。像许多其他软件一样, 复杂性是多年积累的结果。 N F S加入M S G _ D O N T W A I T功能以及从m b u f链接收数据而不是从进 程那里接收数据。 S S _ I S C O N F I R M I N G状态和M S G _ E O R标志是为处理 O S I协议连接和记录功 能而加入的。 比较好的做法是为每一种协议实现一个独立的 s o s e n d函数,通过分散指针 p r _ s e n d给 protosw入口来实现。[Partridge and Pink 1993]中提出并实现了这种方法。 16.7.4 性能问题 如图1 6 - 2 5所描述的, s o s e n d尽可能地以 m b u f为单位将报文传送到协议层。与将一个报文 用一个 m b u f链的形式一次建立并传送给协议层的方法相比,这种做法导致了更多的调用,但 是[Jacobson 1998a]说明了这种做法增加了并行性,因而获得了较好的性能。 一次传送一个 m b u f ( 2 0 4 8个字节 )允许C P U在网络硬件传输数据的同时准备一个分组。同 发送一个大的 m b u f链相比:构造一个大的 m b u f链的同时,网络和接收系统是空闲的。在 [Jacobson 1998a]描述的系统中,这种改变导致了网络吞吐量增加 20%。 有一点非常重要,即确保发送缓存的大小总是大于连接的带宽和时延的乘积 (卷1的第20.7 节)。例如,如果 T C P认为一条连接在收到确认之前能保留 2 0个报文段,那么发送缓存必须大 到足够存储 2 0个未被确认的报文段。如果发送缓存太小, T C P在收到第一个确认之前将用完 数据,连接将在一段时间内是空闲的。

16.8

read、readv、recvfrom和recv m s g系统调用

我们将r e a d、r e a d v、r e c v f r o m和r e c v m s g系统调用统称为读系统调用,从网络连接 上接收数据。同 r e c v m s g相比,前三个系统调用比较简单。 r e c v m s g因为比较通用而复杂得 多。图16-27给出了这四个系统调用和一个库函数 (recv)的特点。 函



read readv recv recvfrom recvmsg

描述符类型

缓存数量

任何类型 任何类型 插口 插口 插口

1 [1..UIO_MAXIOV] 1 1 [1..UIO-MAXIOV]

返回发送者的地址吗 ?

• •



志?

• • •

返回控制信息 ?



图16-27 读系统调用

在N e t / 3中,r e c v是一个库函数,通过调用 r e c v f r o m来实现的。为了同以前编

402

计计TCP/IP详解 卷 2:实现

译的程序二进制兼容,内核将旧的 r e c v系统调用映射到函数 o r e c v。我们仅仅讨论 recvfrom的内核实现。 只有read和readv系统调用适用于各类描述符,其他的调用只适用于插口描述符。 同写调用一样,通过 i o v e c结构数组来指定多个缓存。对数据报协议, r e c v f r o m和 r e c v m s g返回每一个收到的数据报的源地址。对于面向连接的协议, g e t p e e r n a m e返回连 接对方的地址。与接收调用相关的标志参考第 16.11节。 同写调用一样,读调用利用一个公共函数 s o r e c e i v e来做所有工作。图 1 6 - 2 8说明读系 统调用的流程。 库函数 进程 内核

通过pr_usrreg发送的PRU_RCVD 或PRU_RCVOOB

图16-28 所有插口输入都由 soreceive 处理

我们仅仅讨论图 16-28中的带阴影的函数。其余的函数读者可以自己查阅有关资料。

16.9 recvmsg系统调用 r e c v m s g函数是最通用的读系统调用。如果一个进程使用任何一个其他的读系统调用, 且地址、控制信息和接收标志的值还未定,则系统可能在没有任何通知的情况下丢弃它们。 图16-29显示了recvmsg函数。 433-445

r e c v m s g的三个参数是:插口描述符;类型为 m s g h d r的结构指针,几个控制标

志。 1. 复制iov数组 446-461

同s e n d m s g一样, r e c v m s g将m s g h d r结构复制到内核,如果自动分配的数组

403

第 16章 插 口 I/O计计

a i o v太小,则分配一个更大的 i o v e c数组,并且将数组单元从进程复制到由 i o v指向的内核 数组(第16.4节)。将第三个参数复制到 msghdr结构中。

图16-29 recvmsg 系统调用

2. recvit和释放缓存 462-470

r e c v i t收完数据后,将更新过的缓存长度和标志的 m s g h d r结构再复制到进程。

如果分配了一个更大的 iovec结构,则返回之前释放它。

16.10 recvit函数 r e c v i t函数被 r e c v、r e c v f r o m和r e c v m s g调用,如图 1 6 - 3 0所示。基于 r e c v x x x调 用提供的msghdr结构,recvit函数为soreceive的处理准备了一个 uio结构。 471-500

g e t s o c k为描述符 s返回一个 f i l e结构,然后r e c v i t初始化u i o结构,该结构

描述从内核到进程之间的一次数据传送。通过对 i o v e c数组中的 m s g _ i o v l e n字段求和得到

404

计计TCP/IP详解 卷 2:实现

传送的字节数。结果保留在 uio_resid中的len中。

图16-30 recvit 函数:初始化 uio 结构

recvit的第二部分调用 soreceive,并且将结果复制到进程,如图 16-31所示。

图16-31 recvit 函数:返回结果

405

第 16章 插 口 I/O计计

图16-31 (续)

1. 调用soreceive 501-510

s o r e c e i v e实现从插口缓存中接收数据的最复杂的功能。传送的字节数保存在

* r e t s i z e 中,并且返回给进程。如果有些数据已经被复制到进程后信号出现或阻塞出现 (len不等于uio_resid),则忽略差错,并返回已经传送的字节。 2. 将地址和控制信息复制到进程 511-542

如果进程传入了一个存放地址或控制信息或两者都有的缓存,则 r e c v i t将结果

写入该缓存,并且根据 s o r e c e i v e返回的结果调整它们的长度。如果缓存太小,则地址信息 可能被截掉。如果进程在发送读调用之前保留缓存的长度,将该长度同内核返回的 n a m e l e n p 变量 ( 或 s o c k a d d r 结构的长度域 ) 相比较就可以发现这个差错。通过设置 msg_flags中的MSG_CTRUNC标志来报告这种差错,参考习题 16-7。 3. 释放缓存 543-549 从out开始,释放存储源地址和控制信息的 mbuf缓存。

16.11 soreceive函数 s o r e c e i v e函数将数据从插口的接收缓存传送到进程指定的缓存。某些协议还提供发送 者的地址,地址可以同可能的附加控制信息一起返回。在讨论它的代码之前,先来讨论接收 操作,带外数据和插口接收缓存的组织的含义。

406

计计TCP/IP详解 卷 2:实现

图16-32 列出了在执行soreceive期间内核知道的一些标志。 描

flags MSG_DONTWAIT MSG_OOB MSG_PEEK MSG_WAITALL



在调用期间不等待资源 接收带外数据而不是正常的数据 接收数据的副本而不取走数据 在返回之前等待数据写缓存





图16-38 图16-39 图16-43 图16-50

图16-32 recv xxx系统调用:传递给内核的标志值

r e c v m s g是唯一返回标志字段给进程的读系统调用。在其他的系统调用中,控制返回给 进程之前,这些信息被内核丢弃。图 16-33列出了在msghdr中recvmsg能设置的标志。 ms g_f l a g s MSG_CTRUNC MSG_EOR MSG_OOB MSG_TRUNC





控制信息的长度大于提供的缓存长度 收到的数据标志一个逻辑记录的结束 缓存中包含带外数据 收到的报文的长度大于提供的缓存长度





图16-31 图16-48 图16-45 图16-51

图16-33 recvmsg 系统调用:内核返回的 msg_flag 值

16.11.1 带外数据 带外数据 ( O O B )在不同的协议中有不同的含义。一般来说,协议利用已建立的通信连接 来发送 O O B数据。O O B数据可能与已发送的正常数据同序。插口层支持两种与协议无关的机 制来实现对 O O B数据的处理:标记和同步。本章讨论插口层实现的抽象的 O O B机制。U D P不 支持OOB数据。TCP的紧急数据机制与插口层的 OOB数据之间的关系在 TCP一章中描述。 发送进程通过在 s e n dx x x调用中设置 M S G _ O O B标志将数据标记为 O O B数据。 s o s e n d将 这个信息传递给插口协议,插口层收到这个信息后,对数据进行特殊处理,如加快发送数据 或使用另一种排队策略。 当一个协议收到OOB数据后,并不将它放进插口的接收缓存而是放在其他地方。进程通过 设置 r e c vx x x调用中的 M S G _ O O B标志来接收到达的 O O B数据。另一种方法是,通过设置 SO_OOBINLINE插口选项(见第17.3节),接收进程可以要求协议将 OOB数据放在正常的数据之 内。当S O _ O O B I N L I N E被设置时,协议将收到的 O O B数据放进正常数据的接收缓存。在这种 情况下, M S G _ O O B不用来接收 O O B数据。读调用要么返回所有的正常数据,要么返回所有的 O O B数据。两种类型的数据从来不会在一个输入调用的输入缓存中混淆。进程使用 r e c v m s g 来接收数据时,可以通过检查 MSG_OOB标志来决定返回的数据是正常数据还是 OOB数据。 插口层支持 O O B数据和正常数据的同步接收,采用的方法是允许协议在正常数据流中标 记O O B数据起始点。接收者可以在每一个读系统调用的后面,通过 S I O C A T M A R Ki o c t l命 令来检查是否已经达到 O O B数据的起始点。当接收正常的数据时,插口层确保在一个报文中 只有在标记前的正常数据才会收到,使得接收者接收的数据不会超过标记。如果在接收者到 达标记之前收到一些附加的 OOB数据,标记就自动向前移。 16.11.2 举例 图1 6 - 3 4说明两种接收带外数据的方法。在两个例了中,字节 A ~ I作为正常数据接收,字

407

第 16章 插 口 I/O计计

节J作为带外数据接收,字节 K ~ L作为正常数据接收。接收进程已经接收了 A之前(不包括A )的 所有数据。 处理

接收缓存

带外数据

已处理

标记

接收缓存

标记和带外数据

图16-34 接收带外数据

在第一个例子中,进程能够正确读出字节 A~I,或者如果设置 MSG_OOB,也能读出字节 J。 即使读请求的长度大于 9个字节( A ~ I ),插口层也只返回 9个字节,以免超过带外数据的同步标 记。当读出字节 I后,SIOCATMARK为真;对于到达带外数据标记的进程,不必读出字节 J。 在第二个例了中,在 SIOCATMARK为真时只能读字节 A~I。第二次调用读字节 J~L。 在图16-34中,字节 J不是TCP的紧急数据指针指示的字节。在本例中,紧急指针指向的是 字节K。有关细节请参考第 29.7节。 16.11.3 其他的接收操作选项 进程能够通过设置标志 M S G _ P E E K来查看是否有数据到达。而数据仍然留在接收队列中, 被下一个不设置 MSG_PEEK的读调用读出。 标志M S G _ W A I T A L L指示读调用只有在读到指定数量的数据后才返回。即使 s o r e c e i v e 中有一些数据可以返回给进程,但它仍然要等到收到剩余的数据后才返回。 当标志M S G _ W A I T A L L被设置后, s o r e c e i v e只有在下列情况下可以在没有读完指定长 度的数据时返回: • 连接的读通道被关闭; • 插口的接收缓存小于所读数据的大小; • 在进程等待剩余的数据时差错出现; • 带外数据到达;或 • 在读缓存被写满之前,一个逻辑记录的结尾出现。 N F S是N e t / 3中唯一使用 M S G _ W A I T A L L和M S G _ D O N T W A I T标志的软件。进程可 以不通过 i o c t l或f c n t l来选择非阻塞的 I / O操作而是设置 M S G _ D O N T W A I T标志来 实现非阻塞的读系统调用。 16.11.4 接收缓存的组织:报文边界 对于支持报文边界的协议,每一个报文存放在一个 m b u f链中。接收缓存中的多个报文通

408

计计TCP/IP详解 卷 2:实现

过m _ n e x t p k t指针链接成一个 m b u f队列(图2 - 2 1 )。协议处理层加数据到接收队列,插口层从 接收队列中移走数据。接收缓存的高水位标记限制了存储在缓存中的数据量。 如果P R _ A T O M I C没有被置位,协议层尽可能多地在缓存中存放数据,丢弃输入数据中的 不合要求的部分。对于 T C P,这就意味着到达的任何数据如果在接收窗口之外都将被丢弃。 如果 P R _ A T O M I C被置位,缓存必须能够容纳整个报文,否则协议层将丢弃整个报文。对于 U D P而言,如果接收缓存已满,则进入的数据报都将被丢弃,缓存满的原因可能是进程读数 据报的速度不够快。 P R _ A D D R被置位的协议使用 s b a p p e n d a d d r构造一个 m b u f链,并将其加入到接收队列。 缓存链包含一个存放报文源地址的 m b u f,0个或更多的控制 m b u f,后面跟着 0个或更多的包含 数据的mbuf。 对于S O C K _ S E Q P A C K E T和S O C K _ R D M协议,它们为每一个记录建立一个 m b u f链。如果 P R _ A T O M I C 被置位,则调用 s b a p p e n d r e c o r d ,将记录加到接收缓存的尾部。如果 P R _ A T O M I C没有被置位 ( O S I的T P 4 ),则用 s b a p p e n d r e c o r d产生一个新的记录,其余的数 据用sbappend加到这个记录中。 假定 P R _ A T O M I C就是表示缓存的组织结构是不正确的。例如, T P 4中并没有 PR_ATOMIC,而是用M_EOR标志来支持记录边界。 图1 6 - 3 5说明了由三个 m b u f链(即三个数据报 )组成的U D P接收缓存的结构。每一个 m b u f中 都标有m_type的值。 在图1 6 - 3 5中,第三个数据报中有一些控制信息。三个 U D P插口选项能够导致控制信息被 存入接收缓存。详细情况参考图 22-5和图23-7。

数据报 1

数据报 2

数据报 3

图16-35 包含三个数据报的 UDP接收缓存

对于P R _ A T O M I C协议,当收到数据时, s b _ l o w a t被忽略。当没有设置 P R _ A T O M I C时, sb_lowat的值等于读系统调用返回的最小的字节数。但也有一些例外,如图 16-41所示。 16.11.5 接收缓存的组织:没有报文边界 当协议不需维护报文边界 (即S O C K _ S T R E A M协议,如 T C P )时,通过 s b a p p e n d将进入的

409

第 16章 插 口 I/O计计

数据加到缓存中的最后一个 m b u f链的尾部。如果进入的数据长度大于缓存的长度,则数据将 被截掉, sb_lowat为一个读系统调用返回的字节数设置了一个下限。 图16-36说明了仅仅包含正常数据的 TCP接收缓存的结构。

图16-36 TCP的so_rcv 缓存

16.11.6 控制信息和带外数据 不像T C P,一些流协议支持控制信息,并且调用 s b a p p e n d c o n t r o l将控制信息和相关 数 据 作 为 一 个 新 的 m b u f 链加 入接 收 缓 存 。 如 果 协议 支 持 内 含 O O B 数 据 , 则调用 s b i n s e r t o o b插入一个新的 m b u f链到任何包含 O O B数据的m b u f链之后,但在任何包含正常 数据的mbuf链之前。这一点确保进入的 OOB数据总是排在正常数据之前。 图16-37说明包含控制信息和 OOB数据的接收缓存的结构。

图16-37 带有控制信息和 OOB数据的so_rcv 缓存

U n i x域流协议支持控制信息, OSI TP4协议支持 M T _ O O B D A T A m b u f。T C P既不支持控制 信息,也不支持 M T _ O O B D A T A形式的带外数据。如果 T C P的紧急指针指向的字节存储在数据 内(S O _ O O B I N L I N E被设置 ),那么该字节是正常数据而不是 O O B数据。 T C P对紧急指针和相

410

计计TCP/IP详解 卷 2:实现

关数据的处理在第 29.7节中讨论。

16.12 soreceive代码 我们现在有足够的背景信息来详细讨论 s o r e c e i v e函数。在接收数据时, s o r e c e i v e 必须检查报文边界,处理地址和控制信息以及读标志所指定的任何特殊操作 (图1 6 - 3 2 )。一般 来说, s o r e c e i v e的一次调用只处理一个记录,并且尽可能返回要求读的字节数。图 1 6 - 3 8 显示了soreceive函数的大概情况。

图16-38 soreceive 函数:概述

411

第 16章 插 口 I/O计计

图16-38 (续)

439-446

s o r e c e i v e有六个参数。指向插口的 s o指针。指向存放接收地址信息的 m b u f缓

存的指针 * p a d d r。如果 m p 0指向一个 m b u f链,则 s o r e c e i v e将接收缓存中的数据传送到 * m p 0指向的 m b u f缓存链。在这种情况下, u i o结构中只有用来记数的 u i o _ r e s i d字段是有 意义的。如果mp0为空,则 soreceive将数据传送到uio结构中指定的缓存。 *controlp指 向包含控制信息的 mbuf缓存。soreceive将图16-33中描述的标志存放在 *flagsp。 447-453

soreceiv e一开始将pr指向插口协议的交换结构,并将 u i o _ r e s i d(接收请求的

大小)保存在o r i g _ r e s i d。如果将控制或地址信息从内核复制到进程,则将 o r i g _ r e s i d清 0。如果复制的是数据,则更新 u i o _ r e s i d。不管哪一种情况, o r i g _ r e s i d都不可能等于 uio_resid。soreceive函数的最后处理要利用这一事实 (图16-51)。 454-461 在这一段代码中,首先将 *paddr和*controlp置空。在将 MSG_EOR标志清0后, 将传给 s o r e c e i v e的* f l a g s p的值保存在 f l a g s中(习题1 6 . 8 )。f l a g s p是一个用来返回结 果的参数,但是只有 recvmsg系统调用才能收到结果。如果 flagsp为空,则将 flags清0。 483-487

在访问接收缓存之前,调用 s b l o c k 给缓存加锁。如果 f l a g s 中没有设置

MSG_DONTWAIT标志,则 soreceive必须等待加锁成功。 支持在内核中从 NFS发调用到插口层带来了另一个负作用。 挂起协议处理,使得在检查缓存过程中 s o r e c e i v e不被中断。 m是接收缓存中的第一个 mbuf链上的第一个mbuf。 1. 如果需要,等待数据 488-541 soreceive要检查几种情况,并且如果需要,它可能要等待接收更多的数据才继 续往下执行。如果 s o r e c e i v e在这里进入睡眠状态,则在它醒来后跳转到 r e s t a r t查看是 否有足够的数据到达。这个过程一直继续,直到收到足够的数据为止。 542-545

当s o r e c e i v e已收到足够的数据来满足读请求所要求的数据量时,就跳转到

dontblock。并将指向接收缓存中的第二个 mbuf链的指针保存在 nextrecord中。

412

计计TCP/IP详解 卷 2:实现

2. 处理地址和控制信息 542-545 在传送数据之前,首先处理地址信息和控制信息。 3. 建立数据传送 591-597

因为只有 O O B数据或正常数据是在一次 s o r e c e i v e调用中传送,这段代码的功

能就是记住队列前端的数据的类型,这样在类型改变时, soreceive能够停止传送。 4. 传送数据循环 598-692

只要缓存中还有 mbuf (m不空),请求的数据还没有传送完毕 (uio_resid>0),且

没有差错出现,本循环就不会退出。 退出处理 693-719

剩余的代码主要是更新指针、标志和偏移;释放插口缓存锁;使能协议处理并返

回。 图16-39说明soreceive对OOB数据的处理。

图16-39 soreceive 函数:带外数据

5. 接收OOB数据 462-477

因为OOB数据不存放在接收缓存中,所以 soreceive为其分配一块标准的 mbuf,

并给协议发送 P R U _ R C V O O B请求。w h i l e循环将协议返回的数据复制到 uio指定的缓存中。复 制完成后, soreceive返回0或差错代码。 对于P R U _ R C V O O B请求, U D P协议总是返回 E O P N O T S U P P。关于 T C P的紧急数据的处理 的详细情况参考第 30.2节。图16-40说明soreceive对连接信息的处理。

图16-40 soreceive 函数:连接信息

6. 连接证实 478-482

如果返回的数据存放在 m b u f链中,则将 * m p初始化成空。如果插口处于

413

第 16章 插 口 I/O计计

SO_ISCONFIRMING状态,PRU_RCVD请求告知协议进程想要接收数据。 S O _ I S C O N F I R M I N G状态仅用于 O S I的流协议, T P 4。在T P 4中,直到一个用户 级进程通过发送或接收数据的方式来证实连接,该连接才被认为已完全建立。在通 过调用g e t p e e r n a m e来获取对方的身份后,进程可能调用 s h u t d o w n或c l o s e来拒 绝连接。 图1 6 - 3 8显示了图 1 6 - 4 1中的代码在检查接收缓存时,接收缓存被加锁。 s o r e c e i v e的这 部分代码的功能是查看接收缓存中的数据是否能满足读系统调用的要求。

图16-41 soreceive 函数:数据够吗 ?

7. 读调用的请求能满足吗 ? 488-504 一般情况下,soreceive要等待直到接收缓存中有足够的数据来满足整个读请求。 但是,有几种情况可能导致差错或返回比读请求要求少的数据。 • 接收缓存没有数据 (m等于0)。 • 缓存中的数据不能满足读请求要求的数量

(s b _ c c < u i o _ r e s i d ) 并且没有设置

M S G _ D O N T W A I T标志,最少的数据也得不到 (s b _ c c < s b _ l o w a t),且当该链到达时更 多的数据能够加到链的后面 (m_nextpkt等于0,且没有设置PR_ATOMIC)。 • 缓存中的数据不能满足读请求要求的数量,能得到最少的数据量,数据能够加到链中来, 但是MSG_WAITALL指示soreceive必须等待直到缓存中的数据能满足读请求。 如果最后一种情况的条件能够满足,但是因为读请求的数据太大以至如果不阻塞等待就 不能满足(uio_resid≤sb_hiwat),soreceive就不等待而是继续往下执行。 如果接收缓存有数据,并且设置了 MSG_DONTWAIT,则sorecevie不等待更多的数据。 有几种原因使得等待更多的数据是不合适的。在图 1 6 - 4 2中,s o r e c e i v e要么检查三种 情况,然后返回;要么等待更多的数据到达。 8. 等待更多的数据吗 ? 505-534

在此处,soreceive已经决定等待更多的数据来满足读请求。在等待之前,它需

要检查以下几种情况: 505-512 • 如果插口处于差错状态,且缓存为空 (m为空),则soreceive返回差错代码。如

414

计计TCP/IP详解 卷 2:实现

果有差错,但是接收缓存中有数据 (m非空),则返回缓存的数据;当下一个读调用来时,如果 没有数据,就返回差错。如果设置了 M S G _ P E E K,就不清除差错,因为设置了 M S G _ P E E K的 读调用不能改变插口的状态。 513-518 • 如果连接的读通道已经被关闭并且数据仍在接收缓存中,则 sosend不等待而是 将数据返回给进程 (在d o n t b l o c k 的情况下 )。如果接收缓存为空,则 s o r e c e i v e跳转到 release,读系统调用返回 0,表示连接的读通道已经被关闭。 519-523

• 如果接收缓存中包含带外数据或出现一个逻辑记录的结尾,则 soreceive不等

待,而是跳转到 dontblock。 524-528

• 如果协议请求中的连接不存在,则设置差错代码为 E N O T C O N N,函数跳转到

release。 529-534

• 如果读请求读 0字节或插口是非阻塞的,则函数跳转到 r e l e a s e,并返回 0或

EWOULDBLOCK(后一种情况 )。

图16-42 soreceive 函数:等待更多的数据吗 ?

415

第 16章 插 口 I/O计计

9. 是,等待更多的数据 535-541 此处soreceive已决定等待更多的数据,并且有理由这么做 (即,将有数据到达)。 在进程调用 sbwait进入睡眠期间,缓存被解锁。如果因为差错或信号出现使得 sbwait返回, 则s o r e c e i v e返回相应的差错;否则 s o r e c e i v e跳转到r e s t a r t,查看接收缓存中的数据 是否能够满足读请求。 同s o s e n d中一样,进程能够利用 S O _ R C V T I M E O插口选项为 s b w a i t设置一个接收定时 器。如果在数据到达之前定时器超时,则 sbwait返回EWOULDBLOCK。 定时器并不能总令人满意。因为当插口上有活动时,定时器每次都被重置。如 果在一个超时间隔内至少有一个字节到达,则定时器从来不会超时,一直到设置了 更长的超时值的读系统调用返回。 s b _ t i m e o是一个不活动定时器,并不要求超时 值上限,但为了满足读系统调用,超时值的上限可能是必要的。 在此处,soreceive准备从接收缓存中传送数据。图 16-43说明了地址信息的传送。

图16-43 soreceive 函数:返回地址信息

10. dontblock 542-545

n e x t r e c o r d指向接收缓存中的下一条记录。在 s o r e c e i v e的后面,当第一个

链被丢弃后,该指针被用来将剩余的 mbuf放入插口缓存。 11. 返回地址信息 546-564

如果协议提供地址信息,如 UDP,则将从mbuf链中删除包含地址的 mbuf,并通过

*paddr返回。如果 paddr为空,则地址被丢弃。 在soreceive中,如果设置了 MSG_PEEK,则数据仍留在缓存中。 图16-44中的代码处理缓存中的控制 mbuf。 12. 返回控制信息

416

计计TCP/IP详解 卷 2:实现

565-590

每一个包含控制信息的 mbuf都将从缓存中删除 (如果设置了 MSG_PEEK,则不删除

而是复制),并连到 *controlp。如果controlp为空,则丢弃控制信息。

图16-44 soreceive 函数:处理控制信息

如果进程准备接收控制信息,则协议定义了一个 d o m _ e x t e r n a l i z e函数,一旦控制信 息m b u f中包含 S C M _ R I G H T S(访问权限 ),就调用 d o m _ e x t e r n a l i z e函数。该函数执行内核 中所有接收访问权限的操作。只有 U n i x域协议支持访问权限,有关细节在第 7 . 3节已讨论过。 如果进程不准备接收控制信息 (controlp为空),则丢弃控制mbuf。 直到处理完所有包含控制信息的 mbuf或出现差错时,循环才退出。 对于U n i x协议域, d o m _ e x t e r n a l i z e函数通过修改接收进程的文件描述符表 来实现文件描述符的传送。 处理完所有的控制 m b u f后, m指向链中的下一个 m b u f。如果在地址或控制信息的后面, 链中没有其他的 m b u f,则m为空。例如,当一个长度为 0的数据报进入接收缓存时就会出现这 种情况。图 16-45说明了soreceive准备从mbuf链中传送数据。

图16-45 soreceive 函数:准备传送 mbuf

417

第 16章 插 口 I/O计计

13. 准备传送数据 591-597

处理完控制m b u f后,链中应该只剩下正常数据、带外数据 m b u f或没有任何m b u f。

如果 m为空,则 s o r e c e i v e完成处理,控制跳到 w h i l e循环的底部。如果 m不空,所有剩余 的m b u f链(n e x t r e c o r d)都将重新连接到 m,并将下一个 m b u f的类型赋给 t y p e。如果下一个 mbuf包含OOB数据,则设置 f l a g s中的M S G _ O O B标志,并在最后返回给进程。因为 TCP不支 持MT_OOBDATA形式的带外数据,所以 MSG_OOB不会返回给 TCP插口上的读调用。 图16-47显示了传送 mbuf循环的第一部分。图 16-46列出了循环中更新的变量。 变



moff offset uio_resid len





当M S G _ P E E K被置位时,将被传送的下一个字节的偏移位置 当M S G _ P E E K被置位时, OOB标记的偏移位置 还未传送的字节数 从本mbuf中将要传送的字节数;如果 u i o _ r e s i d比较小或靠 OOB标记比较近,则 l e n 可能小于m _ l e n。

图16-46 soreceive 函数:循环内的变量

图16-47 soreceive 函数:uiomove

598-600

w h i l e循环的每一次循环中,一个 m b u f中的数据被传送到输出链或 u i o缓存中。

一旦链中没有mbuf或进程的缓存已满或出现差错,就退出循环。 14. 检查OOB和正常数据之前的变换 600-605

如果在处理 mbuf链的过程中,mbuf的类型发生变化,则立即停止传送,以确保正

常数据和带外数据不会混合在一个返回的报文中。但是,这种检查不适用于 TCP。

418

计计TCP/IP详解 卷 2:实现

15. 更新OOB标记 606-611 计算当前字节到 oobmark之间的长度来限制传送的大小,所以 oobmark的前一个 字节为传送的最后一个字节。传送的大小同时还要受 m b u f大小的限制。这段代码同样适用于 TCP。 612-625

如果将数据传送到 uio缓存,则调用uiomove。如果数据是作为一个 mbuf链返回

的,则更新 uio_resid的值,使其等于传送的字节数。 为了避免在传送数据过程中协议处理挂起的时间太长,在调用 u i o m o v e过程中使能协议 处理。所以,在 uiomove运行的过程中,接收缓存中可能会出现新的数据。 图16-48中描述的代码说明调整指针和偏移准备传送下一个 mbuf。

图16-48 soreceive 函数:更新缓存

16. mbuf处理完毕了吗 626-646

如果m b u f中的所有字节都已传送完毕,则必须丢弃 m b u f或将指针向前移。如果

m b u f 中包含了一个逻辑记录的结尾,还应设置 M S G _ E O R。如果将 M S G _ P E E K 置位,则 s o _ r e c e i v e 跳到下一个缓存。在没有将 M S G _ P E E K 置位的情况下,如果数据已通过 u i o m o v e复制完成,则丢弃这块缓存;或者如果数据是作为一个 m b u f链返回,则将缓存添加 到mp中。 图16-49包含处理OOB偏移和MSG_EOR的代码段。

419

第 16章 插 口 I/O计计

图16-49 soreceive 函数:带外数据标记

17. 更新OOB标记 658-670 如果带外数据标志等于非 0,则将其减去已传送的字节数。如果已到达标记处,则 将S S _ R C V A T M A R K置位, s o r e c e i v e跳出w h i l e循环。如果没有将 M S G _ P E E K置位,则更 新offset,而不是so_oobmark。 18. 逻辑记录结束 671-672

如果已到达一个逻辑记录的结尾,则 s o r e c e i v e跳出m b u f处理循环,因而不会

将下一个逻辑记录也作为这个报文的一部分返回。 在图1 6 - 5 0中,当设置了 M S G _ W A I T A L L标志 ,并且读请求还没有完成,则循环将等待更 多的数据到达。

图16-50 soreceive 函数:MSG_WAITALL 处理

19. MSG_WAITALL

420

计计TCP/IP详解 卷 2:实现

673-681 如果将MSG_WAITALL置位,而缓存中没有数据(m等于0),调用者需要更多的数据, s o s e n d a l l a t o n c e为假,并且这是接收缓存中的最后一个记录 (n e x t r e c o r d为空 ),则 soreceive必须等待新的数据。 20. 差错或没有数据到达 682-683 如果差错出现或连接被关闭,则退出循环。 21. 等待数据到达 684-689

当接收缓存被协议层改变时 s b w a i t返回。如果 s b w a i t是被信号中断 (e r r o r非

0),则soreceive立即返回。 22. 用接收缓存同步 m和nextrecord 690-692 更新m和nextrecord,因为接收缓存被协议层修改了。如果数据到达 mbuf,则m 等于非0,while循环结束。 23. 处理下一个 mbuf 693

本行是mbuf处理循环的结尾。控制返回到循环开始的第 600行(图16-47)。一旦接收缓存

中有数据,有新的缓存空间,没有差错出现,则循环继续。 如果soreceive停止复制数据,则执行图 16-51所示的代码段。

图16-51 soreceive 函数:退出处理

24. 被截断的报文 694-698

如果因为进程的接收缓存太小而收到一个被截断的报文 (数据报或记录 ),则插口

层将这种情况通过设置 M S G _ T R U N C来通知进程,报文的被截断部分被丢弃。同其他接收标志 一样,进程只有通过 r e c v m s g系统调用才能获得 M S G _ T R U N C,即使s o r e c e i v e总是设置这 个标志。 25. 记录结尾的处理 699-706

如果没有将M S G _ P E E K置位,则下一个 mbuf链将被连接到接收缓存,并且如果发

送了 P R U _ R C V D协议请求,则通知协议接收操作已经完成。 T C P通过这种机制来完成对连接

421

第 16章 插 口 I/O计计

接收窗口的更新。 26. 没有传送数据 707-712

如果soreceive运行完成,没有传送任何数据,没有到达记录的结尾,且连接的

读通道是活动的,则将接收缓存解锁, soreceive跳回到restart继续等待数据。 713-714 soreceive中设置的任何标志都在*flagsp中返回,缓存被解锁,soreceive返回。 讨论 s o r e c e i v e是一个复杂的函数。导致其复杂性的主要原因是繁锁的指针操作及对多种类 型的数据(带外数据、地址、控制信息和正常数据 )和多目标(进程缓存, mbuf链)的处理。 同s o s e n d类似, s o r e c e i v e的复杂性是多年积累的结果。为每一种协议编写一个特殊 的接收函数将会模糊插口层和协议层之间的边界,但是可以大大简化代码。 [Partridge and Pink 1993]描述了一个专门为 UDP编写的s o r e c e i v e函数,其功能是将数 据报从接收缓存复制到进程缓存中时给数据报求检验和。他们给出的结论是:修改通用的 soreceive函数来支持这一功能将“使本来已经很复杂的插口子程序变得更加复杂。”

16.13 select系统调用 在下面的讨论中,我们假定读者熟悉 select调用的基本操作和含义。关于 select的应用接口 的详细描述参考 [Stevens 1992]。 图16-52列出了select能够监控的插口状态。 select监控的操作 描



有数据可读 连接的读通道被关闭 listen插口已经将连接排队 插口差错未处理





例外

• • • •

缓存可供写操作用,且一个连接存在或还没有连接请求 连接的写通道被关闭 插口差错未处理 OOB同步标记未处理

• • • •

图16-52 select 系统调用:插口事件

我们从select系统调用的第一部分开始讨论,如图 16-53所示。 1. 验证和初始化 390-410

在堆栈中分配两个数组:ibits和obits,每个数组有三个单元,每个单元为一个

描述符集合。用 b z e r o将它们清 0。第一个参数, n d,必须不大于进程的描述符的最大数量。 如果nd大于当前分配给进程的描述符个数,将其减少到当前分配给进程的描述符的个数。 ni等 于用来存放nd个比特(1个描述符占1个比特)的比特掩码所需的字节数。例如,假设最多有256个 描述符(FD_SETSIZE),falset表示一个32 bit的整型(NFDBITS)数组,且nd等于65,那么: ni=howmany(65,32)×4=3×4=12 在上面的公式中, howmany (x,y)返回存储x比特所需要的长度为 y比特的对象的数量。

422

计计TCP/IP详解 卷 2:实现

图16-53 Select 函数:初始化

2. 从进程复制文件描述符集 411-418

g e t b i t s宏用c o p y i n从进程那里将文件描述符集合传送到 i b i t s中的三个描述

符集合。如果描述符集合指针为空,则不需复制。 3. 设置超时值

423

第 16章 插 口 I/O计计

419-438

如果tv为空,则将 timeo置成0,select将无限期等待。如果 tv非空,则将超时

值复制到内核,并调用 i t i m e r f i x将超时值按硬件时钟的分辨率取整。调用 t i m e v a l a d d将 当前时间加到超时值中。调用 h z t o计算从启动到超时之间的时钟滴答数,并保存在 t i m o中。 如果计算出来的结果为 0,将t i m e o置1,从而防止 s e l e c t阻塞,实现利用全 0的t i m e v a l结 构来实现非阻塞操作。 s e l e c t的第二部分代码,如图 1 6 - 5 4所示。其作用是扫描进程指示的文件描述符,当一 个或多个描述符处于就绪状态或定时器超时或信号出现时返回。

图16-54 select 函数:第二部分

4. 扫描文件描述符 439-442

从r e t r y开始的循环直到 s e l e c t能够返回时退出。在调用进程的控制块中保存

424

计计TCP/IP详解 卷 2:实现

全局整数 n s e l c o l l的当前值和 P _ S E L E C T标志。如果在 s e l s c a n(图1 6 - 5 5 )扫描文件描述符 期间出现任何一种变化,则这种变化表明描述符的状态因为中断处理而发生改变, s e l e c t必 须重新扫描文件描述符。 s e l s c a n查看三个输入的描述符集合中的每一个描述符集合,如果 描述符处于就绪状态,则在输出的描述符集合中设置匹配的描述符。

图16-55 selscan 函数

5. 差错或一些描述符准备就绪 443-444 如果差错出现或描述符处于就绪状态,就立即返回。 6. 超时了吗 445-451 如果进程提供的时间限制和当前时间已经超过了超时值,则立即返回。 7. 在执行selscan期间状态发生变化 452-455

s e l s c a n可以被协议处理中断。如果在中断期间插口状态改变,则将 P _ S E L E C T

和nselcoll置位,且selscan必须重新扫描所有描述符。 8. 等待缓存发生变化 456-460

所有调用 s e l e c t的进程均在调用 t s l e e p时用s e l w a i t作为等待通道。如图 16-

60所示,这种做法在多个进程等待同一个插口缓存的情况下将导致效率降低。如果 t s l e e p正 确返回,则 select跳转到retry,重新扫描所有描述符。 9. 准备返回

425

第 16章 插 口 I/O计计

461-480

在done处清除P_SELECT,如果差错代码为 ERESTART,则修改为 EINTR;如果

差错代码为 E W O U L D B L O C K,则将差错代码置成 0。这些改变确保在 s e l e c t调用期间若信号 出现时能返回EINTR;若超时,则返回 0。 16.13.1 selscan函数 s e l e c t函数的核心是图 1 6 - 5 5所示的s e l s c a n函数。对于任意一个描述符集合中设置的 每一个比特, s e l s c a n 找出同它相关联的描述符,并且将控制分散给与描述符相关联的 so_select函数。对于插口而言,就是 soo_select函数。 1. 定位被监视的描述符 481-496

第一个 f o r循环依次查看三个描述符集合:读,写和例外。第二个 f o r循环在每

个描述符集合内部循环,这个循环在集合中每隔 32 bit(NFDBITS)循环一次。 最里面的 while循环检查所有被 32 bit 的掩码标记的描述符,该掩码从当前描述符集合中 获取并保存在 b i t s中。函数 f f s返回b i t s中的第一个被设置的比特的位置,从最低位开始。 例如,如果 bits等于1000 (省略了前面的28个0),则ffs (bits)等于4。 2. 轮询描述符 497-500

从i到f f s函数的返回值,计算与比特相关的描述符,并保存在 f d中。在b i t s中

( 而不是在输入描述符集合中

) 清除比特,找到与描述符相对应的

f i l e 结构,调用

fo_select。 fo_select的第二个参数是 flag数组中的一个元素。msk是外层的for循环的循环变量。 所以,第一次循环时,第二个参数等于 F R E A D,第二次循环时等于 F W R I T E,第三次循环时 等于0。如果描述符不正确,则返回 EBADF。 3. 描述符准备就绪 501-504 当发现某个描述符的状态为准备就绪时,设置输出描述符集合中相对应的比特位。 并将n (状态就绪的描述符的个数 )加1。 505-510 循环继续直到轮询完所有描述符。状态就绪的描述符的个数通过 *retval返回。 16.13.2 soo_select函数 对于s e l s c a n在输入描述符集合中发现的每一个状态就绪的描述符, s e l s c a n调用与描 述符相关的 f i l e o p s结构(参考第1 5 . 5节)中的f o _ s e l e c t指针引用的函数。在本书中,我们 只对插口描述符和图 16-56所示的soo_select函数感兴趣。

图16-56 soo_select 函数

426

计计TCP/IP详解 卷 2:实现

图16-56 (续)

105-112

soo_select每次被调用时,它只检查一个描述符的状态。如果相对于which中指定

的条件,描述符处于就绪状态,则立即返回1。如果描述符没有处于就绪状态,就用selrecord 标记插口的接收缓存或发送缓存,指示进程正在选择该缓存,然后soo_select返回0。 图16-52显示了插口的读、写和例外情况。我们将看到 s o o _ s e l e c t使用了s o r e a d a b l e 和sowriteable宏,这些宏在sys/socketvar.h中定义。 1. 插口可读吗 113-120 soreadable宏的定义如下: #define soreadable(so) \ ((so)->so_rcv.sb_cc >= (so)->so_rcv.sb_lowat || \ ((so)->so_state & SS_CANTRCVMORE) || \ (so)->so_qlen || (so)->so_error)

因为U D P和T C P的接收下限默认值为 1 (图1 6 - 4 ),下列情况表示插口是可读的:接收缓存 中有数据,连接的读通道被关闭,可以接受任何连接或有挂起的差错。 2. 插口可写吗 121-128 sowriteable宏的定义如下: #define sowriteable(so) \ (sbspace(&(so)->so_snd) >= (so)->so_snd.sb_lowat && \ (((so)->so_state&SS_ISCONNECTED) || \ ((so)->so_proto->pr_flags&PR_CONNREQUIRED)==0) || \ ((so)->so_state & SS_CANTSENDMORE) || \ (so)->so_error)

TCP和UDP默认的发送低水位标记是 2048。对于UDP而言,s o w r i t e a b l e总是为真,因

427

第 16章 插 口 I/O计计

为sbspace总是等于sb_hiwat,当然也总是大于或等于 so_lowat,且不要求连接。 对于T C P而言,当发送缓存中的可用空间小于 2 0 4 8个字节时,插口不可写。其他的情况 在图16-52中讨论。 3. 还有挂起的例外情况吗 129-140

对于例外情况,需检查标志 s o _ o o b m a r k和S S _ R E C V A T M A R K。直到进程读完数

据流中的同步标记后,例外情况才可能存在。 16.13.3 selrecord函数 图16-57显示同发送和接收缓存存储在一起的 s e l i n f o结构的定义(图16-3中的s b _ s e l成 员)。

图16-57 selinfo 结构

41-44

当只有一个进程对某一给定的插口缓存调用 s e l e c t时,s l _ p i d等于等待进程的进

程标志符。当其他的进程对同一缓存调用 s e l e c t时,设置 s l _ f l a g s中的S I _ C O L L标志。 将这种情况称为冲突。这个标志是目前 sl_flags中唯一已定义的标志。 当s o o _ s e l e c t发现描述符不在就绪状态时就调用 s e l r e c o r d函数,如图 1 6 - 5 8所示。 该函数记录了足够的信息,使得缓存内容发生变化时协议处理层能够唤醒进程。

图16-58 selrecord 函数

1. 重复选择描述符 522-531

s e l r e c o r d的第一个参数指向调用 s e l e c t进程的p r o c结构。第二个参数指向

s e l i n f o 记录,该记录的 s o _ s n d . s b _ s e l 和s o _ r c v . s b _ s e l 可能会被修改。如果 s e l i n f o中已记录了该进程的信息,则立即返回。例如,进程对同一个描述符调用 s e l e c t 查询读和例外情况时,函数就立即返回。

428

计计TCP/IP详解 卷 2:实现

2. 同另一个进程的操作冲突 ? 532-534 如果另一个进程已经对同一插口缓存执行 select操作,则设置SI_COLL。 3. 没有冲突 535-537

如果调用没有发生冲突,则 s i _ p i d 等于 0,将当前进程的进程标志符赋给

si_pid。 16.13.4 selwakeup函数 当协议处理改变插口缓存的状态 ,并且只有一个进程选择了该缓存时, N e t / 3就能根据 selinfo结构中记录的信息立即将该进程放入运行队列。 当插口缓存发生变化但是有多个进程选择同一插口缓存时 (设置了S I _ C O L L),N e t / 3就无 法确定哪些进程对这种缓存变化感兴趣。我们在讨论图 1 6 - 5 4中的代码段时就已经指出,每一 个调用 s e l e c t 的进程在调用 t s l e e p 时使用 s e l w a i t作为等待通道。这意味着对应的 wakeup将唤醒所有阻塞在 select上的进程—甚至是对缓存的变化不关心的进程。 图16-59说明如何调用selwakeup。

OOB数据已到达

接收缓存已改变

发送缓存已改变

图16-59 selwakeup 处理

当改变插口状态的事件出现时,协议处理层负责调用图 1 6 - 5 9的底部列出的一个函数来通 知插口层。图 1 6 - 5 9底部显示的三个函数都将导致 s e l w a k e u p被调用,在插口上选择的任何 进程将被调度运行。 selwakeup函数如图16-60所示。 541-548 如果si_pid等于0,表明没有进程对该缓存执行 select操作,函数立即返回。 在冲突中唤醒所有进程 549-553

如果多个进程对同一插口执行 s e l e c t操作,将 n s e l c o l l加1,清除冲突标志,

唤醒所有阻塞在 s e l e c t上的进程。正如图 1 6 - 5 4中讨论的,进程在 t s l e e p中阻塞之前若缓 存发生改变,nselcoll能使select重新扫描描述符 (习题16.9)。 554-567

如果si_pid标识的进程正在selwait中等待,则调度该进程运行。如果进程是在

其他等待通道中,则清除P_SELECT标志。如果对一个正确的描述符执行selrecord,则调用 进程可能正在其他的等待通道中等待,然后, selscan在描述符集合中发现一个差错的文件描 述符,并返回EBADF,不清除以前修改的 selinfo记录。到selwakeup运行时,selwakup

429

第 16章 插 口 I/O计计

可能会发现sel_pid标识的进程不再在插口缓存等待,从而忽略selinfo中的信息。 如果没有出现多个进程共享同一个描述符的情况 (也就是同一块插口缓存 ),当然这种情况 很少,则只有一个进程被 sel w a k e u p唤醒。在作者使用的机器上, n s e l c o l l总是等于0,这 说明select冲突是很少发生的。

图16-60 selwakeup 函数

16.14 小结 本章介绍了插口的读、写和选择系统调用。 我们了解到 s o s e n d处理插口层与协议处理层之间的所有输出,而 s o r e c e i v e处理所有 输入。 本章还介绍了发送缓存和接收缓存的组织结构,以及缓存的高、低水位标记的默认值和 含义。 本章的最后一部分介绍了 s e l e c t系统调用。从这部分内容中我们了解到,当只有一个进 程对描述符执行 s e l e c t调用时,协议处理层仅仅唤醒 s e l i n f o结构中标识的那个进程。当 有多个进程对同一个描述符执行 s e l e c t操作而发生冲突时,协议层只能唤醒所有等待在该描 述符上的进程。

习题 16.1 当将一个大于最大的正的有符号整数的无符号整数传给

w r i t e 系统调用时,

430

计计TCP/IP详解 卷 2:实现

sosend中的resid如何变化? 16.2 当s o s e n d将小于M C L B Y T E S个字节的数据放入簇中时, s p a c e被减去M C L B Y T E S, 可能会成为一个负数,这会导致为 a t o m i c协议填写m b u f的循环结束。这种结果是 正常的吗? 16.3 数据报和流协议有着不同的语义。将 s o s e n d和s o r e c e i v e函数分别分成两个函 数,一个用来处理报文,另一个用来处理流。除了使得代码清晰外,这样做还有什 么好处? 16.4 对于 P R _ A T O M I C协议,每一个写调用都指定了一个隐含的报文边界。插口层将这 个报文作为一个整体交给协议。 M S G _ E O R标志允许进程显式指定报文边界。为什 么仅有隐含的报文边界是不够的 ? 16.5 如果插口描述符没有标记为非阻塞,且进程也没有指定

M S G _ D O N T W A I T ,当

sosend不能立即获取发送缓存上的锁时,结果如何 ? 16.6 在什么情况下,虽然 s b _ c c < s b _ h i w a t,但s b s p a c e仍然报告没有闲置空间 ?为 什么在这种情况下进程应该被阻塞 ? 16.7 为什么recvit不将控制报文的长度而是将名字长度返回给进程 ? 16.8 为什么soreceive要清除MSG_EOR? 16.9 如果将nselcoll代码从select和selwakeup中删除,会有什么问题 ? 16.10 修改select系统调用,使得 select返回时返回定时器的剩余时间。

第17章 插 口 选 项 17.1 引言 本章讨论修改插口行为的几个系统调用,以此来结束插口层的介绍。 s e t s o c k o p t和g e t s o c k o p t系统调用已在第 8 . 8节中介绍过,主要描述访问 I P特点的选 项。在本章中,我们将介绍这两个系统调用的实现以及通过它们来控制的插口级选项。 i o c t l函数在第4.4节中已介绍过,在第 4.4节中,我们描述了用于网络接口配置的与协议 无关的i o c t l命令。在第6 . 7节中,我们描述了用来分配网络掩码以及单播、多播和目的地址 的IP专用的ioctl命令。本章我们将介绍 ioctl的实现和fcntl函数的相关特点。 最后,我们介绍 g e t s o c k n a m e和g e t p e e r n a m e系统调用,它们用来返回插口和连接的 地址信息。 图17-1列出了实现插口选项系统调用的函数。本章描述带阴影的函数。

图17-1 setsockopt 和getsockopt 系统调用

17.2 代码介绍 本章中涉及的源代码来自于图 17-2中列出的四个文件。 文 件 名 kern/kern_descrip.c kern/uipc_syscalls.c kern/uipc_socket.c kern/sys_socket.c





f c n t l系统调用 setsockopt、getsockopt、getsockname和getpeername系统调用 插口层对s e t s o c k o p t和g e t s o c k o p t的处理 i o c t l系统调用对插口的处理

图17-2 本章讨论的源文件

432

计计TCP/IP详解 卷 2:实现

全局变量和统计量 本章中描述的系统调用没有定义新的全局变量,也没有收集任何统计量。

17.3 setsockopt系统调用 图8 - 2 9列出了函数 s e t s o c k o p t (和g e t s o c k o p t)能够访问的各种不同的协议层。本章 主要集中在 SOL_SOCKET级的选项,这些选项在图 17-3中列出。 optname

optval 类型





SO_SNDBUF SO_RCVBUF SO_SNDLOWAT SO_RCVLOWAT SO_SNDTIMEO SO_RCVTIMEO SO_DEBUG SO_REUSEADDR SO_REUSEPORT SO_KEEPALIVE SO_DONTROUTE SO_BROADCAST SO_USELOOPBACK

int so_snd.sb_hiwat int so_rcv.sb_hiwat int so_snd.sb_lowat int so_rcv.sb_lowat s t r u c t t i m e v a lso_snd.sb_timeo s t r u c t t i m e v a lso_snd.sb_timeo int so_options int so_options int so_options int so_options int so_options int so_options int so_options

SO_OOBINLINE SO_LINGER SO_ERROR SO_TYPE 其他

int struct linger int int

so_options so_linger so_error so_type





发送缓存高水位标记 接收缓存高水位标记 发送缓存低水位标记 接收缓存低水位标记 发送超时值 接收超时值 记录插口调试信息 插口能重新使用一个本地地址 插口能重新使用一个本地端口 协议查询空闲的连接 旁路路由表 插口支持广播报文 仅用于选路域插口;发送进程接收自己的选 路报文 协议排队内联的带外数据 插口关闭但仍发送剩余数据 获取差错状态并清除;只用于 getsockopt 获取插口类型;只用于 g e t s o c k o p t 返回E N O P R O T O O P T

图17-3 setsockopt 和getsockopt 选项

setsockopt函数原型为: int setsockopt(int s, int level, int optname, void *optval, int optlen);

图17-4显示了setsockopt调用的源代码。 565-597

g e t s o c k返回插口描述符的 f i l e结构。如果 v a l非空,则将 v a l s i z e个字节的

数据从进程复制到用 m _ g e t分配的mbuf中。与选项对应的数据长度不能超过 M L E N个字节,所 以,如果valsize大于MLEN,则返回EINVAL。调用sosetopt,并返回其值。

图17-4 setsockopt 系统调用

433

第 17 章 插 口 选 项计计

图17-4

(续)

sosetopt函数 s o s e t o p t 函数处理所有插口级的选项,并将其他的选项传给与插口关联的协议的 pr_ctloutput函数。图17-5列出了sosetopt函数的部分代码。

图17-5 sosetopt 函数

434

计计TCP/IP详解 卷 2:实现

图17-5 (续)

752-764 如果选项不是插口级的 (SOL_SOCKET)选项,则给底层协议发送 PRCO_SETOPT请 求。注意:调用的是协议的 p r _ c t l o u t p u t函数,而不是它的 p r _ u s r r e q函数。图 1 7 - 6说 明了Internet协议调用的 pr_ctloutput函数。 协



UDP TCP ICMP IGMP 原始IP



p r _ c t l o u t p u t函数



ip_ctloutput tcp_ctloutput

第8.8节 第30.6节

r i p _ c t l o u t p u t和i p _ c t l o u t p u t

第8.8节和第32.8节

图17-6 pr_ctloutput

函数

765 switch语句处理插口级的选项。 841-844 对于不认识的选项,在保存它的 mbuf被释放后返回ENOPROTOOPT。 845-855

如果没有出现差错,则控制总是会执行到 switch。在switch语句中,如果协议

层需要响应请求或插口层,则将选项传送给相应的协议。 I n t e r n e t协议中没有一个预期处理插 口级的选项。 注意,如果协议收到不预期的选项,则直接将其 p r _ c t l o u t p u t函数的返回值丢弃。并 将m置空,以免调用 m_free,因为协议负责释放缓存。 图17-7说明了linger选项和在插口结构中设置单一标志的选项。 766-772 linger选项要求进程传入 linger结构: struct linger { int l_onoff; int l_linger; };

/* option on/off */ /* linger time in seconds */

确保进程已传入长度为 l i n g e r 结构大小的数据后,将结构成员 l _ l i n g e r 复制到 s o _ l i n g e r中。在下一组 c a s e语句后决定是使能还是关闭该选项。 s o _ l i n g e r和c l o s e系 统调用在第 15.15节中已介绍过。 773-789

当进程传入一个非 0值时,设置选项对应的布尔标志;当进程传入的是 0时,将对

应标志清除。第一次检查确保一个整数大小 (或更大 )的对象在 m b u f中,然后设置或清除对应 的选项。 图17-8显示了插口缓存选项的处理。 790-815

这组选项改变插口的发送和接收缓存的大小。第一个 i f语句确保提供给四个选项

的变量是整型。对于 S O _ S N D B U F和S O _ R C V B U F,s b r e s e r v e只调整缓存的高水位标记而不

435

第 17 章 插 口 选 项计计

分配缓存。对于 SO_SNDLOWAT和SO_RCVLOWAT,调整缓存的低水位标记。

图17-7 sosetopt 函数:linger 和标志选项

图17-8 sosetopt 函数:插口缓存选项

436

计计TCP/IP详解 卷 2:实现

图17-9说明超时选项。

图17-9 sosetopt 函数:超时选项

816-824

进程在t i m e v a l结构中设置S O _ S N D T I M E O和S O _ R C V T I M E O选项的超时值。如

果传入的数值不正确,则返回 EINVAL。 825-830

存储在 t i m e v a l结构中的时间间隔值不能太大,因为 s b _ t i m e o是一个短整数,

当时间间隔值的单位为一个时钟滴答时,时间间隔值的大小就不能超过一个短整数的最大值。 第826行代码是不正确的。在下列条件下,时间间隔不能表示为一个短整数: tv_sec × hz +

tv_usec tick

> SHRT_MAX

其中,t i c c k = 1 0 0 0 0 0 0 /和S h z HRT_MAX=32767 所以,如果下列不等式成立,则返回。 tv_sec >

SHRT_MAX tv_usec SHRT_MAX tv_usec − = − hz tick × hz hz 1000000

等式的最后一项不是代码指明的 hz。正确的测试代码应该是: if (tv->tv_sec * hz+tv->tv_usec/tick>SHRT_MAX) error=EDOM;

习题17.3中有更详细的讨论。 831-840

将转换后的时间, v a l,保存在请求的发送或接收缓存中。 s b _ t i m e o限制了进

程等待接收缓存中的数据或发送缓存中的闲置空间的时间。详细讨论参考第 16.7和16.11节。 超时值是传给tsleep的最后一个参数,因为tsleep要求超时值为一个整数,所以 进程最多只能等待65535个时钟滴答。假设时钟频率为100 Hz,则等待时间小于11分钟。

437

第 17 章 插 口 选 项计计

17.4 getsockopt系统调用 getsockopt返回进程请求的插口和协议选项。函数原型是: int getsockopt(int s, int level, int name, caddr_t val, int *valsize);

该调用的源代码如图 17-10所示。

图17-10 getsockopt 系统调用

598-633

这段代码现在看上去应该很熟悉了。 g e t s o c k获取插口的 f i l e结构,将选项缓

存的大小复制到内核,调用 s o g e t o p t来获取选项的值。将 s o g e t o p t返回的数据复制到进 程提供的缓存,可能还需修改缓存长度。如果进程提供的缓存不够大,则返回的数据可能会 被截掉。通常情况下,存储选项数据的 mbuf在函数返回后被释放。 sogetopt函数 同s o s e t o p t一样, s o g e t o p t函数处理所有插口级的选项,并将其他的选项传给与插 口关联的协议。图 17-11列出了sogetopt函数的开始和结束部分的代码。

438

计计TCP/IP详解 卷 2:实现

图17-11 sogetopt 函数:概述

856-871

同 sosetopt一 样 , 函 数 将 那 些 与 插 口 级 选 项 无 关 的 选 项 立 即 通 过

PRCO_GETOPT协议请求传递给相应的协议级。协议将被请求的选项保存在 mp指向的mbuf中。 对于插口级的选项,分配一块标准的 m b u f缓存来保存选项值,选项值通常是一个整数, 所以将m_len设成整数大小。相应的选项值通过 switch语句复制到 mbuf中。 918-925

如果执行的是 s w i t c h中的 d e f a u l t 情况下的语句,则释放 m b u f,并返回

E N O P R O T O O P T。否则, s w i t c h语句执行完成后,将指向 m b u f的指针赋给* m p。当函数返回 后,getsockopt从该mbuf中将数据复制到进程提供的缓存,并释放 mbuf。 图17-12说明对SO_LINGER选项和作为布尔型标志实现的选项的处理。 872-877

S O _ L I N G E R选项请求返回两个值:一个是标志值,赋给 l _ o n o f f;另一个是拖

延时间,赋给l_linger。 878-887

其余的选项作为布尔标志实现。将 so_options和optname执行逻辑与操作,如

果选项被打开,则与操作的结果为非 0值;反之则结果为 0。注意:标志被打开并不表示返回 值等于1。 sogetopt的下一部分代码 (图17-13)将整型值选项的值复制到 mbuf中。 888-906

将每一个选项作为一个整数复制到 mbuf中。注意:有些选项在内核中是作为一个

短整数存储的 (如缓存高低水位标记 ),但是作为整数返回。一旦将 s o _ e r r o r复制到 m b u f中 后,即清除 so_error,这是唯一的一次 getsockopt调用修改插口状态。

439

第 17 章 插 口 选 项计计

图17-12 sogetopt 选项:SO_LINGER 选项和布尔选项

图17-13 sogetopt 函数:整型值选项

图17-14 sogetopt 函数:超时选项

440

计计TCP/IP详解 卷 2:实现

图17-14列出了sogetopt的第三和第四部分代码,它们的作用分别是处理 SO_SNDTIMEO 和SO_RCVTIMEO选项。 907-917

将发送或接收缓存中的 s b _ t i m e o值赋给 v a r。基于 v a l中的时钟滴答数,在

mbuf中构造一个 timeval结构。 计算tv_usec的代码有一个差错。表达式应该为: "(v a l % h z ) * tick"。

17.5 fcntl和ioctl系统调用 因为历史的原因而非有意这么做,插口 API的几个特点既能通过 i o c t l也能通过f c n t l来 访问。关于 ioctl命令,我们已经讨论了很多。我们也几次提到 fcntl。 图17-15显示了本章描述的函数。 系统调用

默认

得到配置

图17-15 fcntl 和ioctl 函数

ioctl和fcntl的原型分别为: int ioctl(int fd, unsigned long result, char *argp); int fcntl(int fd, int cmd,... /* int arg */);

441

第 17 章 插 口 选 项计计

图17-16总结了这两个系统调用与插口有关的特点。我们在图 17-16中还列出了一些传统的 常数,因为它们出现在代码中。考虑与

P o s i x 的兼容性,可以用 O _ N O N B L O C K 来代替

FNONBLOCK,用O_ASYNC来代替FASYNC。 描



fcntl

ioctl

通过打开或关闭 so_st a t e中的S S _ N B I O 来使能或禁止非阻塞功能

F N O N B L O C K文件状态标志

F I O N B I O命令

通过打开或关闭 sb_fl a g s中的 SB_ASYNC 来使能或禁止异步通知功能

F A S Y N C文件状态标志

F I O A S Y N C命令

设置或得到 so_pgid,它是S I G I O G和 S I G U R G信号的目标进程或进程组

F _ S E T O W N或F _ G E T O W N

S I O C S P G R P或S I O C G P G R P命令

得到接收缓存中的字节数;返回 so_rcv.sb_cc

FIONREAD

返回OOB同步标记;即 s o _ s t a t e中的 S S _ R C VATMARK标志

SIOCATMARK

图17-16 fcntl 和ioctl 命令

17.5.1 fcntl代码 图17-17列出了fcntl函数的部分代码。

图17-17 fcntl 系统调用:概况

442

计计TCP/IP详解 卷 2:实现

133-153 验证完指向打开文件的描述符的正确性后, switch语句处理请求的命令。 253-257 对于不认识的命令, fcntl返回EINVAL。 图17-18仅显示fcntl中与插口有关的代码。

图17-18 fcntl 系统调用:插口处理

168-185 F_GETFL返回与描述符相关的当前文件状态标志, F_SETFL设置状态标志。通过 调用f o _ i o c t l将F N O N B L O C K和F A S Y N C的新设置传递给对应的插口,而插口的新设置是通 过图1 7 - 2 0中描述的 s o o _ i o c t l函数来传递的。只有在第二个 f o _ i o c t l调用失败后,才第 三次调用 f o _ i o c t l。该调用的功能是清除 F N O N B L O C K标志,但是应该改为将这个标志恢复

443

第 17 章 插 口 选 项计计

到原来的值。 186-194

F _ G E T O W N返回与插口相关联的进程或进程组的标识符, s o _ p g i d。对于非插口

描述符,将 T I O C G P G R Pi o c t l 命令传给对应的 f o _ i o c t l函数。 F _ S E T O W N的功能是给 so_pgid赋一个新值。 17.5.2 ioctl代码 我们跳过 i o c t l 系统调用本身而先从 s o o _ i o c t l 开始讨论,如图 1 7 - 2 0所示,因为 i o c t l的代码中的大部分是从图 1 7 - 1 7所示的代码中复制的。我们已经说过, s o o _ i o c t l函 数将选路命令发送给 r t i o c t l,接口命令发送给 i f i o c t l,任何其他的命令发送给底层协议 的pr_usrreq函数。 55-68

有几个命令是由 s o o _ i o c t l直接处理的。如果 * d a t a非空,则 F I O N B I O打开非阻

塞方式,否则关闭非阻塞方式。正于我们已经了解的,这个标志将影响到

accept、

connect和close系统调用,也包括其他的读和写系统调用。 69-79

F I O A S Y N C使能或禁止异步 I / O通知功能。如果设置了 S S _ A S Y N C,则无论什么时候

插口上有活动,就调用 sowakeup,将信号SIGIO发送给相应的进程或进程组。 80-88

F I O N R E A D返回接收缓存中的可读字节数。 S I O C S P G R P设置与插口相关的进程组,

S I O C G P G R P则是得到它。 s o _ p g i d作为我们刚讨论过的 S I G I O信号的目标进程或进程组, 当有带外数据到达插口时,则作为 SIGURG信号的目标进程或进程组。 89-92 如果插口正处于带外数据的同步标记,则 SIOCATMARK返回真;否则返回假。 ioctl命令,FIOxxx和SIOxxx常量,有一个内部结构,如图 17-19所示。 输入 输出 空

图17-19 ioctl 命令的内部结构

图17-20 soo_ioctl 函数

444

计计TCP/IP详解 卷 2:实现

图17-20 (续)

如果将 i o c t l的第三个参数作为输入,则 设置i n p u t。如果该参数作为输出,则 o u t p u t被 置位。如果不用该参数,则

v o i d 被置位。

length是参数的大小 (字节)。相关的命令在同一 个 g ro u p 中但每一个命令在组中都有各自的 n u m b e r。图1 7 - 2 1中的宏用来解析 i o c t l命令

宏 IOCPARM_LEN(cmd) IOCBASECMD(cmd) IOCGROUP(cmd)





返回cmd中的length length设为0的命令 返回cmd中的group

图17-21 ioctl 命令宏

中的元素。 93-104

宏I O C G R O U P从命令中得到 8 bit的group。接口命令由 i f i o c t l处理。选路命令由

rtioctl处理。通过 PRU_CONTROL请求将所有其他的命令传递给插口协议。 正如我们在第19章中描述的,Net/2定义了一个新的访问路由选择表接口,在该接 口中,报文是通过一个在 PF_ROUTE域中产生的插口传递给路由选择子系统。用这种 方法来代替这里讨论的ioctl。在不兼容的内核中,rtioctl总是返回ENOTSUPP。

17.6 getsockname系统调用 getsockname系统调用的原型是:

445

第 17 章 插 口 选 项计计

int getsockname(int fd, caddr_t asa, int * alen);

g e t s o c k n a m e得到绑定在插口 f d上的本地地址,并将它存入 a s a指向的缓存中。当在一 个隐式的绑定中内核选择了一个地址,或在一个显式的 b i n d调用中进程指定了一个通配符地 址(2.2.5节)时,该函数就很有用。 getsockname系统调用如图17-22所示。

图17-22 getsockname 系统调用

682-715 getsock返回描述符的file结构。将进程指定的缓存的长度赋给 len。这是我们第 一次看到对 m _ g e t c l r的调用:该函数分配一个标准的 m b u f,并调用 b z e r o清零。当协议收 到PRU_SOCKADDR请求时,协议处理层负责将本地地址存入 m。 如果地址长度大于进程提供的缓存的长度,则返回的地址将被截掉。 * a l e n等于实际返 回的字节数。最后,释放 mbuf,并返回。

17.7 getpeername系统调用 getpeername系统调用的原型是: int getpeername(int fd, caddr_t asa, int * alen);

446

计计TCP/IP详解 卷 2:实现

g e t p e e r n a m e系统调用返回指定插口上连接的远端地址。当一个调用 a c c e p t的进程通 过f o r k和e x e c启动一个服务器时 (即,任何被 i n e t d启动的服务器 ),经常要调用这个函数。 服务器不能得到 a c c e p t返回的远端地址,而只能调用 g e t p e e r n a m e。通常,要在应用的访 问地址表查找返回地址,如果返回地址不在访问表中,则连接将被关闭。 某些协议,如 T P 4,利用这个函数来确定是否拒绝或证实一个进入的连接。在

T P 4中,

a c c e p t 返回的插口上的连接是不完整的,必须经证实之后才算连接成功。基于 g e t p e e r n a m e返回的地址,服务器能够关闭连接或通过发送或接收数据来间接证实连接。 这一特点与 TCP 无关,因为 T C P必须在三次握手完成之后, a c c e p t才能建立连接。图 1 7 - 2 3 列出了getpeername函数的代码。

图17-23 getpeername 系统调用

719-753

图中列出的代码与 getsockname的代码是一样的。 getsock获取插口对应的 file

结构,如果插口还没有同对方建立连接或连接还没有证实 (如,T P 4 ),则返回 E N O T C O N N。如 果已建立连接,则从进程那里得到缓存的大小,并分配一块

m b u f 来存储地址。发送

P R U _ P E E R A D D R请求给协议层来获取远端地址。将地址和地址的长度从内核的 mbuf中复制到

447

第 17 章 插 口 选 项计计

进程提供的缓存中。释放 mbuf,并返回。

17.8 小结 本章中,我们讨论了六个修改插口功能的函数。插口选项由 setsockopt和getsockopt 函数处理。其他的选项,其中有些不仅仅用于插口,由 f c n t l和i o c t l处理。最后,通过 getsockname和getpeername来获取连接信息。

习题 17.1 为什么选项受标准 mbuf大小(MHLEN, 128 个字节)的限制? 17.2 为什么图17-7中的最后一段代码能处理 SO_LINGER选项? 17.3 图17-9中用来测试 timeval结构的代码有些问题,因为 tv-> t v _ s e c * hz可能会 溢出。请对这段代码作些修改来解决这个问题。

第18章 Radix树路由表 18.1 引言 由I P完成的路由选择是一种选路机制,它通过搜索路由表来确定从哪个接口把分组发送 出去。它与选路策略 (routing policy)不一样,选路策略是一组规则的集合,这些规则用来确定 哪些路由可以编入到路由表中。 N e t / 3 内核实现选路机制,而选路守护进程,典型地如 r o u t e d或g a t e d,实现选路策略。由于分组转发是频繁发生的 (一个繁忙的系统每秒要转发 成百上千个分组 ),相对而言,选路策略的变化要少些,因此路由表的结构必须能够适应这种 情况。 关于路由选择的详细情况,我们分三章进行讨论: • 本章将讨论 Net/3分组转发代码所使用的 Radix树路由表的结构。每次发送或转发分组时, IP都将查看该表 (发送时分组需要查看该表,是因为 IP必须决定哪个本地接口将接收该分 组)。 • 第1 9章着重讨论内核与 R a d i x树之间的接口函数以及内核与选路进程 (通常指实现选路策 略的选路守护进程 )之间交换的选路消息。进程可以通过这些消息来修改内核的路由表 (添加路由、删除路由等 ),并且当发生了一个异步事件,可能影响到路由策略 (如收到重 定向,接口断开等 )时,内核也通过这些消息来通知守护进程。 • 第20章给出了内核与进程之间交换选路消息时使用的选路插口。

18.2 路由表结构 在讨论Net/3路由表的内部结构之前,我们需要了解一下路由表中包含的信息类型。图 18-1 是图1-17(作者以太网中的四个系统 )的下半部分。

以太网,140.252.13.0

图18-1 路由表例子中使用子网

图18-2给出了图18-1中bsdi上的路由表。 为了能够更容易地看出每个表项中所设置的标志,我们已经对 n e t s t a t输出的“ F l a g s” 列进行了修改。 该表中的路由是按照下列过程添加的。其中,第 1、3、5、8和第9步是在系统的初始化阶 段执行/ e t c / n e t s t a r t s h e脚本时完成的。 ll

449

第 18章 Radix树路由表计计

图18-2 主机bsdi 上的路由表

1) 默认路由是由route命令添加的。该路由通往主机 sun(140 252 13 33),主机sun拥有 一条到Internet的PPP链路。 2) 到网络 1 2 7的路由表项通常是由选路守护进程

( 如 g a t e d )创建的,也可以通过

/ e t c / n e t s t a r t文件中的 r o u t e命令将其添加到路由表中。该表项使得所有发往该网络的 分组都将被环回驱动器 (图5 - 2 7 )拒绝,但发往主机 1 2 7 . 0 . 0 . 1的分组除外,因为对于该类分组, 在下一步中添加的一条更特殊的路由将屏蔽本路由表项的作用。 3) 到环回接口 (127.0.0.1)的表项是由 ifconfig命令配置的。 4) 到v a n g o g h . c s . b e r k e l e y . e d u( 1 2 8 . 3 2 . 3 3 . 5 )的表项是用 r o u t e命令手工创建的。 该路由指定的路由器与默认路由所指定的相同 (都是140.252.13.33)。但是在拥有一条替代默认 路由的通往特定主机的路由之后,我们就能把路由度量存储在该路由表项中。这些度量能够 可以由管理者选择设置。每次 T C P建立一条到达目的主机的连接时都使用该度量,并且在连 接关闭时,由TCP对其进行更新。我们将在图 27-3中详细描述这些度量。 5) 接口 l e 0的初始化是由 i f c o n f i g命令完成的。该命令会在路由表中增加一条到 140.252.13.32网络的表项。 6) 到以太网上另两台主机 s u n(140.252.13.33)和s v r 4(140.252.13.34)的路由表项是由 ARP 创建的,见第 2 1章。它们都是临时路由,经过一段时间后,如果还未被使用,它们就会被自 动删除。 7) 到本机(140.252.13.35)的表项是在第一次引用本机 IP地址时创建的。该接口是一个环回, 也就是说,任何发往本机 I P地址的数据报将从内部反送回来。 4 . 4 B S D中包含了自动创建该路 由的新功能,见第 21.13节。 8) 到主机140.252.13.65的表项是在 ifconfig配置SLIP接口时创建的。 9) 通过以太网接口到达网络 244的路由是由 route命令添加的。 10) 到多播组224.0.0.1(所有主机的组, all-host group)的表项是Ping程序在连接 224.0.0.1即 “Ping 224.0.0.1”时创建的。它也是一条临时路由,如果在一段时间内未被使用,就会被自动 删除。 图18-2中的“Flags”列需要简单地说明一下。图 18-25列出了所有可能的标志。 U 该路由存在。

450

计计TCP/IP详解 卷 2:实现

G 该路由通向一个网关 (路由器 )。这种路由被称为间接路由。如果没有设置本标志,则 表明路由的目的地与本机直接相连,称为直接路由。 H 该路由通往一台主机,也就是说,目的地址是一个完整的主机地址。如果没有设置本 标志,则路由通往一个网络,目的地址是一个网络地址:一个网络号,或一个网络号 与子网号的组合。 n e t s t a t命令并不区分这一点,但每一条网络路由中都包含一个网 络掩码,而主机路由中则隐含了一个全 1的掩码。 S 该路由是静态的。图 18-2中route命令创建的三个路由表项是静态的。 C 该路由可被克隆 ( c l o n e )以产生新的路由。在本路由表中有两条路由设置了这个标志: 一条是到本地以太网 ( 1 4 0 . 2 5 2 . 1 3 . 3 2 )的路由, A R P通过克隆该路由创建到以太网中其 他特定主机的路由;另一条是到多播组 2 2 4的路由,克隆该路由可以创建到特定多播 组(如224.0.0.1)的路由。 L 该路由含有链路层地址。本标志应用于单播地址和多播地址。由 A R P从以太网路由克 隆而得到的所有主机路由都设置了本标志。 R 环回驱动器 (为设有本标志的路由而设计的普通接口 )将拒绝所有使用该路由的数据报。 添加带有拒绝标志的路由的功能由 N E T / 2提供。它提供了一种简单的方法,来防 止主机向外发送以网络 127为目的地的数据报。参见习题 6.6。 在4.3BSD Reno之前,内核将为 IP地址维护两个不同的路由表:一个针对主机路由,另一 个针对网络路由。对于给定的路由,将根据它的类型添加到相应的路由表中。默认路由被存 储在网络路由表中,其目的地址是 0 . 0 . 0 . 0。查找过程隐含了这样一种层次关系:首先查看主 机路由表;如果找不到,则查找网络路由表;如果仍找不到,则查找默认路由。仅当三次查 找都失败时,才认为目的地不可达。 [ L e ffler et al. 1998] 的第11 . 5节描述了一种带链表结构的 hash表,该hash表同时用于 Net/1中的主机路由表和网络路由表。 4.3BSD Reno [Sklower 1991]的变化主要与路由表的内部表示有关。这些变化允许相同的 路由表函数访问不同协议栈的路由表,如 OSI协议,它的地址是变长的,这一点与长度固定为 32位的IP地址不同。为了提高查询速度,路由表的内部结构也做了变动。 Net/3路由表采用 Patricia树结构[Sedgewick 1990]来表示主机地址和网络地址 (Patricia 支持 “从文字数字的编码中提取信息的 P a t r i c i a算法” )。待查找的地址和树中的地址都被看成比特 序列。这样就可以用相同的函数来查找和维护不同类型的树,如:含有 32 bit定长IP地址的树、 含有48 bit定长XNS地址的树以及一棵含有变长 OSI地址的树。 使用Patricia树构造路由表的思想应归功于 Van Jacobson 的[Sklower 1991]。 举个例子就可以很容易地描述出这个算法。查找路由表的目标就是为了找到一个最能匹 配给定目标的特定地址。我们称这个给定的目标为查找键 (search key)。所谓最能匹配的地址, 也就是说,一个能够匹配的主机地址要优于一个能够匹配的网络地址;而一个能够匹配的网 络地址要优于默认地址。 每条路由表项都有一个对应的网络掩码,尽管在主机路由中没有存储掩码,但它隐含了 一个全1比特的掩码。我们对查找键和路由表项的掩码进行逻辑与运算,如果得到的值与该路 由表项的目的地址相同,则称该路由表项是匹配的。对于某个给定的查找键,可能会从路由 表中找到多条这样的匹配路由,所以在单个表同时包含网络路由和主机路由的情况下,我们

451

第 18章 Radix树路由表计计

必须有效地组织该表,使得总能先找到那个更能匹配的路由。 让我们来讨论图 1 8 - 3给出的例子。图中给出了两个查找键,分别是 1 2 7 . 0 . 0 . 1和1 2 7 . 0 . 0 . 2。 为了更容易地说明逻辑与运算,图中同时给出了它们的十六进制值。图中给出的两个路由表 项分别是主机路由 1 2 7 . 0 . 0 . 1 (它隐含了一个全 1的掩码0 x f f f f f f f f)和网络路由1 2 7 . 0 . 0 . 0 (它的 掩码是0xff000000)。 查找键=127.0.0.1

1 2 3 4

查找键=127.0.0.2

主机路由

网络路由

主机路由

网络路由

查找键 路由表键 路由表掩码 1和3的逻辑与

7f000001 7f000001 ffffffff 7f000001

7f000001 7f000001 ff000000 7f000000

7f000002 7f000001 ffffffff 7f000002

7f000002 7f000000 ff000000 7f000000

2和4相等?

相等

相等

不等

相等

图18-3 分别以127.0.0.1和127.0.0.2为查找键的路由表查找示例

其中两个路由表项都能够匹配查找键 1 2 7 . 0 . 0 . 1,这时路由表的结构必须确保能够先找到 更能匹配该查找键的表项 (127.0.0.1)。 图18-4给出了对应于图 18-2的Net/3路由表的内部表示。执行带- A标志的n e t s t a t命令可 以导出路由表的树型结构,图 18-4就是根据导出的结果而建立的。

图18-4 对应于图 18-2的Net/3路由表

标有“ e n d”的两个阴影框是该树结构中带有特殊标志的叶结点,该标志代表树的端点。 左边的那个拥有一个全 0键,而右边的拥有一个全 1键。左边的两个标有“ e n d”和“ d e f a u l t” 的框垒在一起,这两个框有特殊的意义,它们与重复键有关,具体内容可参考 18.9节。 方角框被称为内部结点 (internal node)或简称为结点 ( n o d e ),圆角框被称为叶子。每一个

452

计计TCP/IP详解 卷 2:实现

内部结点对应于测试查找键的一个比特位,其左右各有一个分枝。每一个叶子对应于一个主 机地址或者对应于一个网络地址。如果在叶子下面有一个十六进制数,那么这个叶子就对应 于一个网络地址,该十六进制数就是叶子的网络掩码。如果在叶子下面没有十六进制的掩码, 那么这个叶子就是一个主机地址,其隐含的掩码是 0xffffffff。 有一些内部结点也含有网络掩码,在后面的学习中,我们将会了解这些掩码在回溯过程 中是如何使用的。图中的每一个结点还包含了一个指向其父结点的指针 (没有在图中表示出来 ), 它能使树结构的回溯、删除及非递归操作更加方便。 比特比较是运用在插口地址结构上的,因此,在图 1 8 - 4中给出的比特位置是从插口地址 结构中的起始位置开始算的。图 18-5给出了sockaddr_in结构中的比特位置。 比特:0

32

63

长度 (16)

族 (2)

端口号

IP地址

(全0)

1字节

1

2

4

8

图18-5 Internet插口地址结构的比特位置

I P地址的最高位比特是比特 3 2,最低位是比特 6 3。此外还列出了长度是 1 6,地址族为 2(AF_INET),这两个数值在我们所列举的例子中将会遇到。 为了解释这些例子,还需要给出树中不同 IP地址的比特表示形式。它们都被列在图 18-6中, 该图还给出了下面例子中要用到的一些其他 I P地址的比特表示形式。该图采用了加粗的字体 来表示图18-4中分支点所对应的比特位置。 现在我们举一些特定的例子来说明路由表的查找过程是如何完成的。 1. 与主机地址匹配的例子 假定主机地址 1 2 7 . 0 . 0 . 1是查找键—待查找的目的地址。比特 3 2为0,因此,沿树顶点向 左分支继续查找,到下一个结点。比特 3 3为1,因此,从该结点右分支继续查找,到下一个结 点。比特 6 3为1,因此,从右分支继续查找,到下一个结点。而下一个结点是个叶子,此时查 找键( 1 2 7 . 0 . 0 . 1 )与叶子中的地址 ( 1 2 7 . 0 . 0 . 1 )相比较。它们完全匹配,这样查找函数就可以返回 该路由表项。 32 bit IP地址 比特 位置

图18-6 图18-2和图18-4中IP地址的比特表示形式

点分割表示

453

第 18章 Radix树路由表计计

2. 与主机地址匹配的例子 再假定查找键是地址 1 4 0 . 2 5 2 . 1 3 . 3 5。比特3 2为1,因此,沿树顶点向右分支继续查找。比 特 3 3 为 0 ,比特 3 6 为 1 ,比特 5 7为 0 ,比特 6 2 为1 ,比特 6 3 为 1 ,因此,查找在底部标有 140.252.13.35的叶子处终止。查找键与路由表键完全匹配。 3. 与网络地址匹配的例子 假定查找键是 1 2 7 . 0 . 0 . 2。比特 3 2为0,比特 3 3为1,比特 6 3为0 ,因此,查找在标有 1 2 7 . 0 . 0 . 0的叶子处终止。查找键和路由表键并没有完全匹配,因此,需要进一步看它是不是 一个能够匹配的网络地址。对查找键和网络掩码 (0 x f f 0 0 0 0 0 0)进行逻辑与运算,得到的结 果与该路由表键相同,即认为该路由表项能够匹配。 4. 与默认地址匹配的例子 假定查找键是 1 0 . 1 . 2 . 3。比特 3 2为0,比特3 3为0,因此,查找在标有“ e n d”和“d e f a u l t” 并带有重复键的叶子处终止。在这两个叶子中重复的路由表键是 0 . 0 . 0 . 0。查找键与路由表键 值没有完全匹配,因此,需要进一步看它是不是一个能够匹配的网络地址。这种匹配运算要 对每个含网络掩码的重复键都试一遍。第一个键 (标有e n d )没有网络掩码,可以跳过不查。第 二个键 (默认表项 )有一个 0 x 0 0 0 0 0 0 0 0的掩码。查找键和这个掩码进行逻辑与运算,所得结 果和路由表键(0)相等,即认为该路由表项能够匹配。这样默认路由就被用做匹配路由。 5. 带回溯过程的与网络地址匹配的例子 假定查找键是 1 2 7 . 0 . 0 . 3。比特 3 2为0,比特 3 3 为1,比特 6 3为1 ,因此,查找在标有 1 2 7 . 0 . 0 . 1的叶子处终止。查找键和路由表键没有完全匹配。由于这个叶子没有网络掩码,无 法进行网络掩码匹配的尝试。此时就要进行回溯。 回溯算法在树中向上移动,每次移动一层。如果遇到的内部结点含有掩码,则对查找关 键字和该掩码进行逻辑与运算,得到一个键值,然后以这个键值作为新的查找键,在含该掩 码的内部结点为开始的子树中进行另一次查找,看是否能找到匹配的结点。如果找不到,则 回溯过程继续沿树上移,直到到达树的顶点。 在这个例子中,查找上移一层到达比特 6 3对应的结点,该结点含有一个掩码。于是对查 找键和掩码 (0 x f f 0 0 0 0 0 0)进行逻辑与运算,得到一个新的查找键,其值为 127.0.0.0。然后从 该结点开始查找 1 2 7 . 0 . 0 . 0。比特6 3为0,因此,沿左分支到达标有 1 2 7 . 0 . 0 . 0的叶子上。用新的 查找键与路由表键相比较,它们是相等的,因此认为这个叶子是匹配的。 6. 多层回溯的例子 假定查找键是 11 2 . 0 . 0 . 1 。比特 3 2 为0,比特 3 3为 1,比特 6 3为1,因此,查找在标有 1 2 7 . 0 . 0 . 1的叶子处终止。该键与查找键不相等,并且路由表项中没有网络掩码,因此需要进 行回溯。 查找过程向上移动一层,到达比特 6 3对应的结点,该结点含有一个掩码。对查找关键字 和该掩码 (0 x f f 0 0 0 0 0 0)进行逻辑与运算,然后再从这个结点开始进一步查找。在新的查找 键中比特 6 3为0,因此,沿左分支到达标有 1 2 7 . 0 . 0 . 0的叶子。比较之后发现逻辑与运算得到的 查找键(112.0.0.0)和路由表键并不相等。 因此继续向上回溯一层,从比特 63对应的结点上移到比特 33对应的结点。但这个结点没有 掩码,再继续向上回溯。到达的下一层是树的顶点

( 比特 3 2 ),它有一个掩码。对查找键

(112.0.0.1)和该掩码(0x00000000)进行逻辑与运算后,从该点开始一个新的查找。在新的查找

454

计计TCP/IP详解 卷 2:实现

键中,比特32为0,比特33也为0,因此,查找在标有“end”和“default”的叶子处结束。通过 与重复键列表中的每一项进行比较,发现默认键与新的查找键相匹配,因此采用默认路由。 从这个例子中可以知道,如果在路由表中存在默认路由,那么当回溯最终到达树的顶点 时,它的掩码为全 0比特,这使得查找向树中最左边叶子的方向进行搜索,最终与默认路由相 匹配。 7. 带回溯和克隆过程、并与主机地址相匹配的例子 假定查找键是 2 2 4 . 0 . 0 . 5。比特 3 2为1,比特 3 3 1,比特 3 5为0,比特 6 3为1,因此,查找在 标有2 2 4 . 0 . 0 . 1的叶子处结束。路由表的键值和查找关键字并不相等,并且该路由表项不包含 网络掩码,因此要进行回溯。 回溯向上移动一层,到达比特 6 3对应的结点。这个结点含有掩码 0 x f f 0 0 0 0 0 0,因此, 对查找键和该掩码进行逻辑与运算,产生一个新的查找键,即 2 2 4 . 0 . 0 . 0。再从这个结点开始 一次新的查找。在新的查找键中比特 6 3为0,于是沿左分支到达标有 2 2 4 . 0 . 0 . 0的叶子。这个路 由表键和逻辑与运算得到的查找键相匹配,因此这个路由表项是匹配的。 该路由上设置了“克隆”标志 (见图1 8 - 2 ),因此,以 2 2 4 . 0 . 0 . 5为地址创建一个新的叶子。 新的路由表项是: Destination 224.0.0.5

Gateway link#1

Flags UHL

Refs 0

Use 0

Interface le0

图1 8 - 7从比特 3 5对应的结点开始,给出了图 1 8 - 4中 路由表树右边部分的新的排列。注意,无论何时向树中 添加新的叶子,都需要两个结点:一个作为叶子,另一 个作为测试某一位比特的内部结点。 新创建的表项就被返回给查找 224.0.0.5的调用者。 8. 大图 图1 8 - 8是一张比较大的图,它描述了所有涉及到的 数据结构。该图的底部来自于图 3-32。 现在我们将解释图中的几个要点,在后面,本章还 将给出详细的阐述。 • r t _ t a b l e s是指向 r a d i x _ n o d e _ h e a d结构的 指针数组。每一个地址族都有一个数组单元与之 对应。r t _ t a b l e s [ A F _ I N E T ]指向I n t e r n e t路由

图18-7 插入224.0.0.5路由表项后 图18-6的改动

表树的顶点。 • r a d i x _ n o d e _ h e a d结构包含三个 r a d i x _ n o d e结构。这三个结构是在初始化路由树 时创建的,中间的是树的顶点。它对应于图 1 8 - 4中最上端标有“ bit 32 ”的结点框。三 个r a d i x _ n o d e结构中的第一个是图 1 8 - 4中最左边的叶子 (与默认路由共享的重复 ),第 三个结构是最右边的叶子。在一个空的路由表中,就只包含这三个 r a d i x _ n o d e结构, 我们将会看到rn_inithead函数是如何构建它们的。 • 全局变量 m a s k _ r n h e a d也指向一个 r a d i x _ n o d e _ h e a d结构。它是包含了所有掩码的 一棵独立树的首部结构。观察图 1 8 - 4中给出的八个掩码可知,有一个掩码重复了四次, 有两个掩码重复了一次。通过把掩码放在一棵单独的树中,可以做到对每一个掩码只需 要维护它的一个备份即可。

455

第 18章 Radix树路由表计计

• 路由表树是用 r t e n t r y 结构创建的,在图 1 8 - 8中,有两个 r t e n t r y 结构。每一个 r t e n t r y结构包含两个 r a d i x _ n o d e结构,因为每次向树中插入一个新的路由时,都 需要两个结点:一个是内部结点,对应于某一位测试比特;另一个是叶子,对应于一个 主机路由或一个网络路由。在每一个 r t e n t r y结构中,给出了内部结点对应的要测试 的那位比特以及叶子中所包含的地址。

左 右

图18-8 路由表中涉及的数据结构

r t e n t r y结构中的其余部分是该路由的一些其他重要信息。虽然我们只给出了该结构中 的一个指向 ifnet结构的指针,但在这个结构中还包含了指向 ifaddr结构的指针、该路由的

456

计计TCP/IP详解 卷 2:实现

标志、指向另一个 rtentry结构的指针(如果该路由是一个非直接路由 )和该路由的度量等等。 • 存在于每一个 UDP和TCP插口(图22-1)中的协议控制块 PCB(见第22章)中包含了一个指向 r t e n t r y结构的r o u t e结构。每次发送一个 IP数据报时, UDP和TCP输出函数都传递一 个指向 P C B中r o u t e结构的指针,作为调用 i p _ o u t p u t的第三个参数。使用相同路由 的PCB都指向相同的路由表项。

18.3 选路插口 在4.3BSD Reno的路由表做了变动后,路由子系统和进程间的交互过程也要做出变动,这 就引出了选路插口 (routing socket)的概念。在 4.3BSD Reno之前,是由进程 (如r o u t e命令)通 过发出定长的ioctl来修改路由表的。 4.3BSD Reno采用新的PF_ROUTE域把它改变成一种更 为通用的消息传递模式。进程在 P F _ R O U T E域中创建一个原始插口 (raw socket),就能够向内 核发送选路消息,以及从内核接收选路消息 (如重定向或来自于内核的其他的异步通知 )。 图1 8 - 9给出了 1 2种不同类型的选路消息。消息类型位于 r t _ m s g h d r结构(图1 9 - 1 6 )中的 rtm_type字段。进程只能发送其中的5种消息(写入到选路插口中),但可以接收全部的12种消息。 我们将在第 19章给出这些选路消息的详细讨论。 rtm_type

发往内核?

RTM_ADD RTM_CHANGE RTM_DELADDR RTM_DELETE RTM_GET RTM_IFINFO RTM_LOCK RTM_LOSING RTM_MISS RTM_NEWADDR RTM_REDIRECT RTM_RESOLVE

• • • • •

从内核发出? • • • • • • • • • • • •





添加路由 改变网关、度量或标志 从接口中删除地址 删除路由 报告度量及其他路由信息 接口打开或关闭等 锁定指明的度量 内核怀疑某路由无效 查找这个地址失败 接口中添加了地址 内核得知要使用不同的路由 请求将目的地址解析成链路层地址

结构类型 rt-msghdr rt-msghdr ifa-msghdr rt-msghdr rt-msghdr rt-msghdr rt-msghdr rt-msghdr rt-msghdr ifa-msghdr rt-msghdr rt-msghdr

图18-9 通过选路插口交换的消息类型

18.4 代码介绍 路由选择中使用的各种结构和函数是通过五个 C文件和三个头文件来定义的。图 1 8 - 1 0列 出了这些文件。 通常,前缀 r n _表示r a d i x结点函数,这些函数可以对 P a t r i c i a树进行查找和操作,前 缀raw_表示路由控制块函数, rout_、rt_和rt这三个前缀表示常用的选路函数。 尽管有的文件和函数以 r a w为前缀,但在所有的选路章节中我们仍使用术语选路 控制块(routing control block)而不是原始控制块。这是为了防止与第 32章中讨论的原 始I P控制块及其函数相混淆。虽然原始控制块及相关函数不仅仅用于 N e t / 3中的选路 插口(使用这些结构和函数的原始 O S I协议之一 ),但是本书中我们只用做 P F _ R O U T E 域中的选路插口。

457

第 18章 Radix树路由表计计









net/radix.h net/raw_cb.h net/route.h

radix结点定义 选路控制块定义 选路结构

net/radix.c net/raw_cb.c net/raw_usrreq.c net/route.c net/rtsock.c

radix结点(Patricia树)函数 选路控制块函数 选路控制块函数 选路函数 选路插口函数

图18-10 本章中讨论的文件 和

系统调用

接口状态改变

系统初始化

接口启动和断 开时由各 i o c t l 调用,以添加 或删除路由

程序

插口接收缓存

给定 T C P连接 上相继重传中 的第四个

各种系统调用

ICMP改变路由 由T C P / I P协议 调用以查找到 目的地的路由

图18-11 各选路函数之间的关系

458

计计TCP/IP详解 卷 2:实现

图1 8 - 11给出了一些基本的选路函数,并表示了它们之间的相互关系。其中带阴影的椭圆 是在本章和下面两章中要涉及的内容。在图中,我们还给出了每种类型的选路消息 (共12种)的 产生之处。 r t a l l o c 函数是由 I n t e r n e t 协议调用的,用于查找到达指定目的地的路由。在 i p _ r t a d d r 、i p _ f o r w a r d 、i p _ o u t p u t 和i p _ s e t m o p t i o n s 函数中都已出现过 rtalloc,在后面介绍的 in_pcbconnect和tcp_mss函数中也将会遇到它。 图18-11还给出了在选路域中创建插口的五个典型程序: • arp处理ARP高速缓存,该ARP高速缓存被存储在 Net/3的IP路由表中 (见第21章); • g a t e d和r o u t e d是选路守护进程,它们与其他路由器进行通信。当选路环境发生变化 时(指路由器及链路断开或连通 ),对内核的路由表进行操作; • route通常是由启动脚本或系统管理员执行的一个程序,用于添加或删除路由; • rwhod在启动时会调用一个选路 sysctl来测定连接的接口。 当然,任何进程 (具有超级用户的权限 )都能打开一个选路插口向选路子系统发送或从中接 收消息;在图18-11中,我们只给出了一些常用的系统程序。 18.4.1 全局变量 图18-12列出了在三个有关路由选择的章节中介绍的全局变量。 变



数据类型





rt_tables mask_rnhead rn_mkfreelist

struct radix_node_head *[] struct radix_node_head * struct radix_mask *

路由表表头指针的数组 指向掩码表表头的指针 可用r a d i x _ m a s k结构的链表表头

max_keylen rn_zeros rn_ones maskedKey

int char * char * char *

以字节为单位的路由表键值的最大长度 长为m a x _ k e y l e n、值为全零比特的数组 长为m a x _ k e y l e n、值为全1比特的数组 长为m a x _ k e y l e n、掩码过的查找键数组

rtstat rttrash

struct rtstat int

路由选择统计 (图18-13) 未释放的非表中路由的数目

rawcb raw_recvspace raw_sendspace

struct rawcb u_long u_long

选路控制块双向链表表头 选路插口接收缓冲区的默认大小, 8192字节 选路插口发送缓冲区的默认大小, 8192字节

route_cb route_dst route_src route_proto

struct struct struct struct

选路插口监听器的数目,每个协议的数目及总的数目 保存选路消息中目的地址的临时变量 保存选路消息中源地址的临时变量 保存选路消息中协议的临时变量

route_cb sockaddr sockaddr sockproto

图18-12 在三个有关选路的章节中介绍的全局变量

18.4.2 统计量 图18-13列举了一些路由选择统计量,它们是在全局结构 rtstat中维护的。 在代码的处理中,我们可以发现计数器是怎样增加的。这些计数器在 SNMP中并未使用。 图1 8 - 1 4 给出了 n e t s t a t r s命令输出的一些统计数据的样例,该命令用于显示 rtstat结构。

459

第 18章 Radix树路由表计计



rtstat成员



在SNMP中的使用

无效重定向调用的数目 由重定向创建的路由数目 由重定向修改的路由数目 查找失败的次数 由通配符匹配的查找次数 (从未使用)

rts_badredirect rts_dynamic rts_newgateway rts_unreach rts_wildcard

图18-13 在rtstat 结构中维护的路由选择统计数据 n e t s t a t - r s的输出

r t s t a t 成员

1029 bad routing redirects rts_badredirect 0 dynamically created routes rts_dynamic 0 n e w g a t e w a y s d u e t o r e d i r e c t sr t s _ n e w g a t e w a y 0 destinations found unreachablr ets_unreach 0 uses of a wildcard route rts-wildcard

图18-14 路由选择统计数据样例

18.4.3 SNMP变量 图18-15给出了名为 ipRouteTable的IP路由表以及相应的内核变量。 IP路由表,index = 变

SNMP变量



ipRouteDest ipRouteIfIndex ipRouteMetric1

rt_key r t _ i f p,i f _ i n d e x -1

ipRouteMetric2 ipRouteMetric3 ipRouteMetric4 ipRouteNextHop ipRouteType

-1 -1 -1 rt_gateway (见正文)

ipRouteProto

(见正文)

ipRouteAge ipRouteMask

(未实现) rt_mask

ipRouteMetric5 ipRouteInfo

-1 NULL





IP目的地址。值为 0 . 0 . 0 . 0时,代表默认路由 接口号: ifIndex 基本的路由度量。其含义取决于选路协议的值 (ipRouteProto)。值为-1,表示没有使用 可选的路由度量 可选的路由度量 可选的路由度量 下一跳路由器的 IP地址 路由类型: 1=其他,2=无效路由, 3=直接的,4=间接 的 路由协议:1=其他,4=ICMP重定向,8=RIP, 13=OSPF, 14= BGP 等 从路由最后一次被修改或被确定为正确时起的秒数 在和i p R o u t e D e s t比较前,与目的主机 IP地址进行逻 辑与运算的掩码 可选的路由度量 本选路协议特定的 MIB定义的引用

图18-15 IP路由表: ipRouteTable

如果在r t _ f l a g s中将标志 R T F _ G A T E W A Y置位,则该路由就是远程的, i p R o u t e T y p e 等于4;否则该路由就是直达的, i p R o u t e T y p e值为3。对于i p R o u t e P r o t o,如果将标志 RTF_DYNAMIC或RTF_MODIFIED置位,则该路由就是由ICMP来创建或修改的,值为 4,否则 为其他情况,其值为1。最后,如果rt_mask指针为空,则返回的掩码就是全 1(即主机路由)。

460

计计TCP/IP详解 卷 2:实现

18.5 Radix结点数据结构 在图18-8中可以发现每一个路由表的表头都是一个radix_node_head结构,而选路树中所 有的结点(包括内部结点和叶子)都是radix_node结构。radix_node_head结构如图18-16所示。

图18-16 radix_node_head

92

结构:每棵选路树的顶点

rnh_treetop指向路由树顶端的radix_node结构。可以看到 r a d i x _ n o d e _ h e 结 ad

构的最后一项分配了三个 radix_node结构,其中中间的那个被初始化成树的顶点 (图18-8)。 93-94 rnh_addrsize和rnh_pktsize目前未被使用。 r n h _ a d d r s i z e是为了能够方便地将路由表代码导入到系统中去,因为系统的 插口地址结构中没有标识其长度的字节。 r n h _ p k t s i z e是为了能够利用 r a d i x结点 机制直接检查分组头结构中的地址,而无需把该地址拷贝到某个插口地址结构中去。 从rnh_addaddr到rnh_walktree是七个函数指针,它们所指向的函数将被调用

95-110

以完成对树的操作。如图 1 8 - 1 7所示,r n _ i n i t h e a d仅初始化了其中的四个指针,剩下的三 个指针在Net/3中未被使用。 111-112

图1 8 - 1 8给出了组成树中结点的

radix_node结构。在图18-8中,我们可以发 现,在radix_node_head中分配了三个这样 的r a d i x _ n o d e结构,而在每一个 r t e n t r y 结构中分配了两个radix_node结构。 41-45

前五个成员是内部结点和叶子都有

的成员。后面是一个 u n i o n:如果结点是叶 子,那么它定义了三个成员;如果是内部结





rnh_addaddr rnh_addpkt rnh_deladdr rnh_delpkt rnh_matchaddr rnh_matchpkt rnh_walktree

被r n _ i n i t h e a d初始化为 rn_addroute NULL rn_delete NULL rn_match NULL rn_walktree

图18-17 在radix_node_head 七个函数指针

结构中的

461

第 18章 Radix树路由表计计

点,那么它定义了另外不同的三个成员。由于在 N e t / 3代码中经常使用 u n i o n中的这些成员, 因此,用一组#define语句定义它们的简写形式。 41-42 rn_mklist是该结点掩码链表的表头。我们将在 18.9节中描述该字段。 rn_p指向该 结点的父结点。 43

如果rn_b值大于或者等于零,那么该结点为内部结点;否则就是叶子。对于内部结点来

说, r n _ b就是要测试的比特位置:例如,在图 1 8 - 4中,树的顶结点的 r n _ b值为3 2。对于叶 子来说, rn_b是负的,它的值等于- 1减去网络掩码索引 (index of the network mask)。该索引 是指掩码中出现的第一个零的比特位置。图 18-19给出了图18-4中掩码的索引。

图18-18 radix_node 结构:路由树的结点 32 bit IP掩码(比特32-36)

索引

图18-19 掩码索引的例子

我们可以发现,其中的全 0掩码的索引是特殊处理的:它的索引是 0,而不是32。 44

内部结点的 r n _ b m a s k是个单字节的掩码,用于检测相应的比特位是 0还是1。在叶子中

它的值为0。很快我们将会看到如何运用成员 rn_bmask和成员rn_off。 45 图18-20给出了成员 rn_flags的三个值。

462

计计TCP/IP详解 卷 2:实现







RNF_ACTIVE RNF_NORMAL RNF_ROOT



该结点是活的 (alive)(针对r t f r e e) 该叶子含有正常路由 (目前未被使用 ) 该叶子是树的根叶子

图18-20 rn_flags 的值

R N F _ R O O T标志只有在 r a d i x _ n o d e _ h e a d结构中的三个 radix结点(树的顶结点、左端结点和 右端结点)中才能设置。这三个结点不能从路由树中删除。 48-49

对于叶子而言, r n _ k e y指向插口地址结构, r n _ m a s k指向保存掩码的插口地址结

构。如果 r n _ m a s k为空,则其掩码为隐含的全 1值(即,该路由指向某个主机而不是某个网 络)。 图18-21例举了一个与图 18-4中的叶子140.252.13.32相对应的radix_node结构的例子。 指向比特63对应的radix_node{}

图18-21 与图18-4中的叶子 140.252.13.32相对应的radix_node 结构

该例子中还给出了图 18-22中描述的 radix_mask结构。我们把它的宽度略微缩小了一些, 以区分于 r a d i x _ n o d e结构;这两种结构在后面的很多图例中都会遇到。有关 r a d i x _ m a s k 结构的作用将在 18.9节中阐述。 值为-60的r n _ b相对应的索引为 59。r n _ k e y指向一个 s o c k a d d r _ i n结构,它的长度值 为1 6,地址族值为 2 (A F _ I N E T)。由r n _ m a s k和r m _ m a s k指向的掩码结构所含的长度值为 8, 地址族值为0(该族为AF_UNSPEC,但我们从未使用它 )。 50-51 当有多个叶子的键值相同时,使用 rn_dupedkey指针。具体内容将在18.9节中阐述。 52-58 有关rn_off的内容将在 18.8节中阐述。 rn_l和rn_r是该内部结点的左、右指针。 图18-22给出了radix_mask结构的定义。 76-83

该结构中包含了一个指向其掩码的指针: r m _ m a s k,实际上是一个保存掩码的插口

地址结构的指针。每一个 r a d i x _ n o d e结构对应一个 r a d i x _ m a s k结构的链表,这就允许每

463

第 18章 Radix树路由表计计

个结点包含多个掩码:成员 r n _ m k l i s t 指向链表的第一个结点,然后每个结构的成员 r m _ m k l i s t指向链表的下一个结点。该结构的定义同时声名了全局变量 r n _ m k f r e e l i s t, 它是可用的 radix_mask结构链表的表头。

图18-22 radix_mask 结构

18.6 选路结构 访问内核路由信息的关键之处是: 1) rtalloc函数,用于查找通往目的地的路由; 2) route结构,它的值由 rtalloc函数填写; 3) route结构所指向的rtentry结构。 图1 8 - 8给出了U D P和T C P (参见第 2 2章)中使用的协议控制块 ( P C B ),其中包含一个 r o u t e 结构,见图 18-23。

图18-23 route 结构

r o _ d s t 被定义成一个一般的插口地址结构,但对于 I n t e r n e t协议而言,它就是一个 s o c k a d d r _ i n结构。注意,对这种结构类型的绝大多数引用都是一个指针,而 r o _ d s t是该 结构的一个实例而非指针。 这里,我们有必要回顾一下图 8-24。从该图可以得知,每次发送 IP数据报时,这些路由是 如何使用的。 • 如果调用者传递了一个 r o u t e结构的指针,那么就使用该结构。否则,就要用一个局部 r o u t e结构,其值设置为 0 (设置 r o _ r t为空指针 )。U D P 和T C P把指向它们的 P C B中 route结构的指针传递给 ip_output。 • 如果r o u t e结构指向一个r t e n t r y结构(r o _ r t指针为非空),同时所引用的接口仍然有 效;而且如果 r o u t e结构中的目的地址与 I P数据报中的目的地址相等,那么该路由就被 使用。否则,目的主机的 I P 地址将会设置在插口地址结构 s o _ d s t 中,并且调用 r t a l l o c来查找一条通向该目的主机的路由。在 TCP链接中,数据报的目的地址始终是 路由的目的地址,不会发生变化,但是 U D P应用可以通过 s e n d t o每次都把数据报发送 到不同的目的地。 • 如果r t a l l o c返回的r o _ r t是个空指针,则表明找不到路由,并且 i p _ o u t p u t返回一

464

计计TCP/IP详解 卷 2:实现

个差错。 • 如果在 r t e n t r y结构中设有R T F _ G A T E W A Y标志,那么该路由为非直接路由 (参见图1 8 2中的G标志)。接口输出函数的目的地址 (d s t)就变成网关的IP地址,即 r t _ g a t e w a y成 员,而不是 IP数据报的目的地地址。 图18-24给出了rtentry结构的定义。

图18-24 rtentry 结构

83-84

在该结构中包含有两个 r a d i x _ n o d e结构。正如我们在图 1 8 - 7的例子中所提到的,

每次向路由树添加一个新叶子的同时也要添加一个新的内部结点。

r t _ n o d e s[ 0 ]为叶子,

r t _ n o d e s[ 1 ]为内部结点。在图 1 8 - 2 4最后的两个 # d e f i n e语句提供了访问该叶结点的键和 掩码的简写形式。 86

图1 8 - 2 5 给出了储存在 r t _ f l a g s 中的各种常量以及在图 1 8 - 2 的“ F l a g s ”列中由

netstat输出的相应字符。 常





n e t s t a t标志



无差错的丢弃分组 (环回驱动器 :图5-27)

RTF_BLACKHOLE RTF_CLONING

C

使用中产生新的路由 (由ARP使用)

RTF_DONE

d

内核的证实,表示消息处理完毕

RTF_DYNAMIC

D

(由重定向)动态创建

RTF_GATEWAY

G

目的主机是一个网关 (非直接路由 )

RTF_HOST

H

主机路由 (否则,为网络路由 )

RTF_LLINFO

L

当r t _ l l i n f o指针无效时,由 ARP设置

RTF_MASK

m

子网掩码存在 (未使用)

RTF_MODIFIED

M

(由重定向)动态修改

RTF_PROTO1

1

协议专用的路由标志

RTF_PROTO2

2

协议专用的路由标志 (ARP使用)

RTF_REJECT

R

有差错的丢弃分组 (环回驱动器 :图5-27)

RTF_STATIC

S

人工添加的路由 (r o u t e程序)

RTF_UP

U

可用路由

RTF_XRESOLVE

X

由外部守护进程解析名字 (用于X.25)

图18-25 rt_flags 的值

465

第 18章 Radix树路由表计计

n e t s t a t不输出 R T F _ B L A C K H O L E标志。两个标志为小写字符的常量, R T F _ D O N E和 RTF_MASK,在路由消息中使用,但通常并不储存在路由表项中。 85

如果设置了R T F _ G A T E W A Y标志,那么 r t _ g a t e w a y所含的插口地址结构的指针就指向

网关的地址 (即网关的 I P地址)。同样, r t _ g w r o u t e就指向该网关的 r t e n t r y。后一个指针 在ether_output(图4-15)中用到。 87

rt_refcnt是一个计数器,保存正在使用该结构的引用数目。在 19.3节的最后部分将具

体描述该计数器。在图 18-2中,该计数器在“ Refs”列输出。 88

当分配该结构存储空间时, r t _ u s e被初始化为0。在图8 - 2 4中,可发现每次利用该路由

输出一份IP数据报时,其值会随之递增。该计数器的值在图 18-2的“Use”栏中输出。 89-90

rt_ifp和rt_ifa分别指接口结构和接口地址结构。在图 6-5中曾指出一个给定的接

口可以有多个地址,因此, rt_ifa是必需的。 92

rt_llinfo指针允许链路层协议在路由表项中储存该协议专用的结构指针。该指针通常

与RTF_LLINFO标志一起使用。图 21-1描述了ARP如何使用该指针。

图18-26 rt_metrics 结构

93 图18-26给出了rt_metrics结构,rtentry结构含有该结构。图 27-3显示了TCP使用了 该结构的六个成员。 54-65

rmx_locks是一个比特掩码,由它告诉内核后面的八个度量中的哪些禁止修改。该

比特掩码的值在图 20-13中给出。 A R P (参见第 2 1章)把r m x _ e x p i r e用作每一个 A R P路由项的定时器。与 r m x _ e x p i r e的 注释不同的是, rm_expire不是用作重定向的。 图1 8 - 2 8概括了我们上面所阐述的各种结构和这些结构之间的关系,以及所引用的各种插 口地址结构。图中给出的 r t e n t r y是图1 8 - 2中到1 2 8 . 3 2 . 3 3 . 5的路由。包含在 r t e n t r y中的另 一个 r a d i x _ n o d e 对应于图 1 8 - 4中位于该结点正上方的测试比特 3 6 的内部结点。第一个 i f a d d r所指的两个 s o c k a d d r _ d l结构如图 3 - 3 8所示。另外,从图 6 - 5中也可注意到 i f n e t 结构包含在 le_softc结构中,第二个 ifaddr结构包含在 in_ifaddr结构中。

18.7 初始化: route_init和rtable_i n i t函数 路由表的初始化过程并非是一目了然的,我们需要回顾一下第 7章中的d o m a i n结构。在描 述这些函数调用之前,图 18-27给出了各协议族中与 domain结构相关的字段。

466

计计TCP/IP详解 卷 2:实现





OSI值

Internet值

选路值

Unix值

XNS值





比特 字节

图18-27 domain 结构中与路由选择有关的成员

P F _ r o u t e 域是唯一具有初始化函数的域。同样,只有那些需要路由表的域才有 dom_rtattach函数,并且该函数总是rn_inithead。选路域和Unix域并不需要路由表。

都是

以太网地址

都是

三个都是

图18-28 选路结构小结

467

第 18章 Radix树路由表计计

d o m _ r t o f f s e t成员是以比特为单位的选路过程中被检测的第一个比特的偏移量 (从域的 插口地址结构的起始处开始计算 )。d o m _ m a x r t k e y给出了该结构的字节长度。在本章的前一 部分的内容中,我们已经知道,

s o c k a d d r _ i n 结构中的 I P 地址是从比特 3 2 开始的。

dom_maxrtkey成员是协议的插口地址结构的字节长度: sockaddr_in的字节长度为16。 图18-29列出了路由表初始化过程所包含的步骤。

初始化选路协议控制块的首部

分配并初始化

分配并初始化一个

结构

图18-29 初始化路由表时包含的步骤

在系统初始化时,内核的 m a i n函数将调用一次 d o m a i n i n i t函数。A D D D O M A I N宏用于

468

计计TCP/IP详解 卷 2:实现

创建一个 d o m a i n结构的链表,并调用每个域的 d o m _ i n i t函数(如果定义了该函数 )。正如图 18-27所示,route_init是唯一的一个dom_init函数,其代码如图 18-30所示。

图18-30 rout_init 函数

在图18-32中的函数rn_init只被调用一次。 在图 1 8 - 3 1 中的函数 r t a b l e _ i n i t 也只被调用一次。它接着调用所有域的

dom_

rtattach函数,这些函数为各自所属的域初始化一张路由表。

图18-31 rtable_init 函数:调用每一个域的 dom_rtattach

函数

从图 1 8 - 2 7中可知, r n _ i n i t h e a d 是唯一的一个 d o m _ r t a t t a c h 函数,关于 r n _ inithead函数将在下一节中介绍。

18.8 初始化: rn_init和rn_inithead函数 图1 8 - 3 2中的函数 r n _ i n i t只被r o u t e _ i n i t调用一次,用于初始化 r a d i x函数使用的一 些全局变量。 1. 确定max_keylen 750-761

检 查 所 有 domain结 构 , 并 将 全 局 变 量 max_keylen设 置 为 最 大 的

d o m _ m a x r t k e y值。在图 1 8 - 2 7中最大值是 3 2 (对应于 A F _ I S O),但是,在一个常用的不含 OSI和XNS协议的系统中, max_key为16,即sockaddr_in结构的大小。 2. 分配并初始化rn_zeros、rn_ones和maskedKey 762-769

先分配了一个大小为 m a x _ k e y l e n的三倍的缓存,并在全局变量 r n _ z e r o s中储

存该缓存的指针。 R _ M a l l o c是一个调用内核的 m a l l o c函数的宏,它指定了 M _ R T A B L E和 M _ D O N T W A I T的类型。我们还会遇到 B c m p、B c o p y、B z e r o和F r e e这些宏,它们对参数进 行适当分类,并调用名称相似的内核函数。 该缓存被分解成三个部分,每一部分被初始化成如图 18-33所示。 r n _ z e r o s是一个全0比特的数组,r n _ o n e s是一个全1比特的数组,m a s k e d K e y数组用 于存放被掩码过的查找键的临时副本。

469

第 18章 Radix树路由表计计

图18-32 rn_init 函数 个字节

个字节

个字节

图18-33 rn_zeros 、rn_ones 和maskedKey 数组

3. 初始化掩码树 770-772

调用 r n _ i n i t h e a d,初始化地址掩码路由树的首部;并使图 1 8 - 8中全局变量

mask_rnhead指向该radix_node_head结构。 从图1 8 - 2 7可知,对于所有需要路由表的协议, rn _ i n i t h e a d也是它们的 d o m _ a t t a c h 函 数。图 1 8 - 3 4 给 出 的 不 是 该 函 数 的 源 代 码 , 而 是 该 函 数为 I n t e r n e t 协议 创建的 radix_node_head结构。 这三个r a d i x _ n o d e结构组成了一棵树:中间的那个结构是树的顶点 (由r n h _ t r e e t o p 指向它 ),第一个结构是树的最左边的叶子,最后一个结构是树的最右边的叶子。这三个结点 的父指针(rn_p)都指向中间的那个结点。 r n h _ n o d e s[ 1 ] . r n _ b的值3 2是待测试的比特位置。它来自于 I n t e r n e t的d o m a i n结构中的 d o m _ r t o f f s e t成员(图1 8 - 2 7 )。它的字节偏移量及字节掩码被预先计算出来,这样就不需要 在处理过程中完成移位和掩码。其中,字节偏移量从插口地址结构起始处开始计算,它被存 放在 r a d i x _ n o d e结构的 r n _ o f f 成员中 ( 在这个例子中它的值为 4 ) ;字节掩码存放在 r n _ b m a s k成员中 (在这个例子中为 0 x 8 0 )。无论何时往树中添加 r a d i x _ n o d e结构,都要计

470

计计TCP/IP详解 卷 2:实现

算这些值,以便于在转发过程中加快比较的速度。其他的例子有:在图 1 8 - 4中检测比特 3 3的 两个结点的偏移量和字节掩码分别为 4和0x40;检测比特63的两个结点的偏移量和字节掩码分 别为7和0x01。 两个叶子中的rn_b成员的值- 33是由-1减去该叶子的索引而得到的。

最左边的叶子

内部结点 也是树的顶点

最右边的叶子

图18-34 rn_inithead 为Internet协议创建的 radix_node_head

结构

最左边结点的键是全 0(rn_zeros),最右边结点的键是全 1(rn_ones)。 这三个结点都设置了 R N F _ R O O T标志(我们省略了前缀 R N F _)。这说明它们都是构成树的 原始结点之一。它们也是唯一具有该标志的结点。 有一个细节我们没有提到,就是网络文件系统 ( N F S )也使用路由表函数。对于本 地主机的每一个装配点 (mount point)都分配一个 r a d i x _ n o d e _ h e a d结构,并且具 有一个指向这些结构的指针数组 (利用协议族检索 ),与r t _ t a b l e s数组相似。每次 输出装配点时,针对该装配点,把能装配该文件系统的主机的协议地址添加到适当 的树中去。

471

第 18章 Radix树路由表计计

18.9 重复键和掩码列表 在介绍查找路由表项的源代码之前,必须先理解 r a d i x _ n o d e结构中的两个字段:一个 是 r n _ d u p e d k e y ,它构成了附加的含重复键的 r a d i x _ n o d e 结构链表;另一个是 rn_mklist,它是含网络掩码的 radix_mask结构链表的开始。 先看一下图18-4中树的最左边标有“end”和“default”的两个框。这些就是重复键。最左 边设有RNF_ROOT标志的结点(在图18-34中的rnh_nodes[0])有一个为全0比特的键,但是它和 默认路由的键相同。如果创建一个 255.255.255.255的路由表项(但该地址是受限的广播地址,不 会在路由树中出现 ),则我们会在树的最右端结点 (该结点有一个值为全 1比特的键 )遇到同样的 问题。总的来说,如果每次都有不同的掩码,那么 Net/3中的radix结点函数就允许重复任何键。 图1 8 - 3 5给出了两个具有全 0比特重复键的结点。在这个图中,我们去掉了 r n _ f l a g s中 的RNF_前缀,并且省略了非空父指针、左指针和右指针,因为它们与要讨论的内容无关。

路由树的首部:图 18-4顶部比特32对应 的结点 比特33结点 的左指针

图18-35 值为全0的键的重复结点

472

计计TCP/IP详解 卷 2:实现

图中最上面的结点即为路由树的顶点—图1 8 - 4中顶部比特 3 2对应的结点。接下来的两 个结点是叶子 (它们的r n _ b为负值),其中第一个叶子的 r n _ d u p e d k e y成员指向第二个结点。 第一个叶子是图 18-34中的r n h _ n o d e s[0]结构,该结构是树的左边标有“ end”的结点—它 设有RNF_ROOT标志。它的键被 rn_inithead设为rn_zeros。 第二个叶子是默认路由的表项。它的 r n _ k e y指向值为 0 . 0 . 0 . 0的s o c k a d d r _ i n结构,并 具有一个全 0的掩码。由于掩码表中相同的掩码是共享的,因此,该叶子的 r n _ m a s k也指向 rn_zeros。 通常,键是不共享的,更不会与掩码共享。由于两个标有 “e n d”的结点的 r n _ k e y指针(具有R N F _ R O O T标志)是由r n _ i n i t h e a d(图1 8 - 3 4 )创建的,因此这两 个指针例外。左边标有 end的结点的键指向 rn_zeros,右边标有 “end”的结点的键 指向rn_ones。 最后一个是 r a d i x _ m a s k结构,树的顶结点和默认路由对应的叶子都指向这个结构。这 个列表是树的顶结点的掩码列表,在查找网络掩码时,回溯算法将使用它。 r a d i x _ m a s k结 构列表和内部结点一起确定了运用于从该结点开始的子树的掩码。在重复键的例子中,掩码 列表和叶子出现在一起,跟着的这个例子也是这样的。 现在我们给出一个特意添加到选路树中的重复键和所得到的掩码列表。在图 1 8 - 4中有一 个主机路由 1 2 7 . 0 . 0 . 1和一个网络路由 1 2 7 . 0 . 0 . 0。图中采用了 A类网络路由的默认掩码,即 0xff000000。如果我们把跟在 A类网络号之后的 24 bit分解成一个 16 bit子网号和一个8 bit主 机号,就可以为子网 127.0.0添加一个掩码为 0xffffff00的路由: bsdi $ route add 127.0.0.0 -netmask 0xffffff00 140 252 13 33

虽然在这种情况下使用网络 1 2 7没什么实际意义,但是我们感兴趣的是所得到的路由表结 构。虽然重复键在 I n t e r n e t协议中并不常见 (除了前面例子中的默认路由之外 ),但是仍需要利 用重复键来为所有网络的 0号子网提供路由。 在网络号 1 2 7的这三个路由表项中存在一个隐含 的优先规则。如果查找键是 1 2 7 . 0 . 0 . 1,则它和这三个 路由表项都匹配,但是只选择主机路由,因为它是最 匹配的:其掩码 (0 x f f f f f f f f)含有最多的 1。如果 查找键是 1 2 7 . 0 . 0 . 2,它与两个网络路由匹配,但是掩 码 为 0 x f f f f f f 0 0 的子 网 0 的路 由比 掩码 为 0 x f f 0 0 0 0的路由更匹配。如果查找键为 1 2 7 . 0 . 2 . 3, 那么只与掩码为 0xff000000的路由表项匹配。 图1 8 - 3 6给出了添加路由之后得到的树结构,从 图18-4中对应比特33的内部结点处开始。由于这个重

图18-36 反映重复键 127.0.0.0的路由树

复键有两个叶子,我们用两个框来表示键值为 127.0.0.0的路由表项。 图18-37给出了所得到的 radix_nod和radix_mask结构。 首先看一下每一个 r a d i x _ n o d e的r a d i x _ m a s k结构的链表。最上端结点 (比特6 3 )的掩 码列表由 0 x f f f f f f 0 0及其后的 0 x f f 0 0 0 0 0 0组成。在列表中首先遇到的是更匹配的掩码, 这样它就能够更早地被测试到。第二个 r a d i x _ n o d e(r n _ b值为- 5 7的那个 )的掩码列表与第 一个相同。但是第三个 radix_node的掩码列表仅由值为 0xff000000的掩码构成。

473

第 18章 Radix树路由表计计

比特63对应的结点

图18-37 网络127.0.0.0的重复键的路由表结构举例

应注意的是,具有相同值的掩码之间可以共享,但是具有相同值的键之间不能共享。这 是因为掩码被保存在它们自己的路由树中,可以显式地被共享,而且值相同的掩码经常出现 (例如,每个 C类网络路由都有相同的掩码 0xffffff00),但是值相同的键却不常见。

18.10 rn_match函数 现在我们介绍 r n _ m a t c h函数,在 I n t e r n e t协议中,它被称为 r n h _ m a t c h a d d r函数。在

474

计计TCP/IP详解 卷 2:实现

后面学习中,我们可以看到它将被 r t a l l o c l函数调用 (而r t a l l o c 1函数将被 r t a l l o c函数 调用)。具体算法如下: 1) 从树的顶端开始搜索,直到到达与查找键的比特相应的叶子。检测该叶子,看能否得 到一个精确的匹配 (图18-38)。 2) 检测该叶结点,看是否能得到匹配的网络地址。 3) 回溯(图18-43)。 图18-38给出了rn_match的第一部分。

图18-38 rn_match 函数:沿着树向下搜索,查找严格匹配的主机地址

135-145 第一个参数 v_arg是一个插口地址结构的指针,第二个参数 head是该协议的指向 r a d i x _ n o d e _ h e a d结构的指针。所有协议都可调用这个函数 (图1 8 - 1 7 ),但调用时使用不同 的head参数。 在变量声明中,off是树的顶结点的 rn_off成员(对Internet地址,其值为4,见图18-34),

475

第 18章 Radix树路由表计计

vlen是查找键插口地址结构中的长度字段 (对Internet地址,其值为16)。 1. 沿着树向下搜索到相应的叶子 146-155

这个循环从树的顶结点开始,然后沿树的左右分支搜索,直到遇到一个叶子为止

(r n _ b小于0)。每次测试相应比特时,都利用了事先计算好的 r n _ b m a s k中的字节掩码和事先 计算好的 rn_off中的偏移量。对于 Internet地址而言, rn_off为4、5、6或7。 2. 检测是否精确匹配 156-164 当遇到叶子时,首先检测能否精确匹配。比较插口地址结构中从协议族的 rn_off值 开始的所有字节。图 18-39给出了Internet插口地址结构的比较情况。



端口

IP地址

(全0)

这12个字节要进行比较

图18-39 比较sockaddr_in 结构时的各种变量

如果发现匹配不成功,就立刻跳到 on1。 通常, s o c k a d d r _ i n 的最后 8 个字节为 0 ,但是地址解析协议代理

(proxy

A R P ) ( 2 1 . 1 2节)会设置其中的一个为非零。这就允许一个给定的 I P地址有两个路由表 项:一个对应于正常 I P地址(最后8个字节为 0 ),另一个对应于相同 I P地址的地址解析 协议代理(最后8个字节中有一个为非零 )。 图18-39中的长度字节在函数的一开始时就赋值给了 v l e n,并且我们还会看到 r t a l l o c l 将利用family成员来选择路由表进行搜索。选路函数未使用 port成员。 3. 显示地检测默认地址 165-172

图1 8 - 3 5给出了存储在键为 0的重复叶子中的默认路由。第一个重复的叶子设有

R N F _ R O O T标志。因此,如果在匹配的结点中设有 R N F _ R O O T标志,并且该叶子含有重复键, 那么就返回指针 r n _ d u p e d k e y的值(即图1 8 - 3 5中含默认路由的结点的指针 )。如果路由树中 没有默认路由,则查找过程匹配左边标有“ end”的叶子 (键为全0比特);或者如果查找时遇到 右边标有“ e n d”的叶子 (键值为全 1比特),那么返回指针 t,它指向一个设有 R N F _ R O O T标志 的结点。我们将看到 r t a l l o c l会显式地检查匹配结点是否设有这个标志,并判断匹配是否 失败。 程序执行到此时, r n _ m a t c h函数已经到达了某个叶子上,但是它并不是查找键的精确 匹配。函数的下一部分将检测该叶子是否为匹配的网络地址,如图 18-40所示。 173-174

c p指向该查找键中那个不相等的字节。 m a t c h e d _ o f f被赋值为该字节在插口地

址结构中的位置偏移量。 175-183

do

w h i l e循环反复与所有重复叶子中的每一个具有网络掩码的叶子进行比较。

下面我们通过一个例子来看这段代码。假定我们要在图

1 8 - 4 所示的路由表中查找 I P地址

1 4 0 . 2 5 2 . 1 3 . 6 0。查找会在标有 1 4 0 . 2 5 2 . 1 3 . 3 2 (比特6 2和6 3都为0 )的结点处终止,该结点包含一 个网络掩码。图 18-41给出了图 18-40中的for循环开始执行时的结构。

476

计计TCP/IP详解 卷 2:实现

图18-40 rn_match 函数:检测是否为匹配的网络地址

图18-41 比较网络掩码的例子

虽然查找键和路由表键都是 s o c k a d d r _ i n结构,但是掩码的长度并不相同。该掩码长度 是非零字节的最小数目。从该点之后直到 max_keylen之间的所有字节都为 0。 184-190

逐个字节地对查找关键字和路由表键进行异或运算,并将结果同网络掩码进行逻

辑与运算。如果所得到的字节出现非零值,就会由于不匹配而终止循环 (习题1 8 . 1 )。如果循环 正常终止,那么与网络掩码进行逻辑与运算后的查找键就和路由表项相匹配。程序将返回指 向该路由表项的指针。 查看I P地址的第四个字节,我们可以从图 1 8 - 4 2中看出本例子是如何匹配成功的,以及 I P 地址1 4 0 . 2 5 2 . 1 3 . 1 8 8是如何匹配失败的。采用这两个地址,是因为它们中的比特 5 7、6 2和6 3都 为0,查找都在图18-41给出的结点上终止。 第一个例子( 1 4 0 . 2 5 2 . 1 3 . 6 0 )匹配成功是因为逻辑与运算的结果为 0 (并且地址、键和掩码中 所有剩余的字节全都为 0)。另一个例子匹配不成功是因为逻辑与运算的结果为非零。

477

第 18章 Radix树路由表计计

查找键 = 140.252.13.60

查找键 = 140.252.13.188

查找键字节 ( * c p ) : 路由表键字节 ( * c p 2 ) :

0011 1100 = 3c 0010 0000 = 20

1011 1100 = bc 0010 0000 = 20

异或: 网络掩码字节 ( * c p 3 ) :

0001 1100 1110 0000 = e0

1001 1100 1110 0000 = e0

逻辑与:

0000 0000

1000 0000

图18-42 用网络掩码进行关键字匹配的例子

191 如果路由表项含有重复键,那么对每一个键都要执行一次该循环体。 r n _ m a t c h的最后一部分,如图 1 8 - 4 3所示,沿路由树向上回溯,以查找匹配的网络地址 或默认地址。

图18-43 rn_match 函数:沿树向上回溯

193-195 do while 循环沿着路由树一直向上,检测每一层的结点,直至检测到树的顶端为止。 196

指向父结点的指针的值被赋给了指针 t,即向上移动了一层。可见,在每一个结点中包

含一个父指针能够简化回溯操作。 197-210

对于回溯到的每一层,只要内部结点的掩码列表非空,就对该层进行检测。

r n _ m k l i s t是指向 r a d i x _ n o d e结构的链表的指针,链表中的每一个 r a d i x _ n o d e结构都 包含一个掩码,这些掩码将应用于从该结点开始的子树。程序中的内部 do 历每一个 radix_mask结构。

w h i l e循环将遍

478

计计TCP/IP详解 卷 2:实现

利用前面的例子, 1 4 0 . 2 5 2 . 1 3 . 1 8 8,图1 8 - 4 4给出了在最内层的 f o r循环开始时的各种数据 结构。这个循环对每个掩码中的字节和对应的查找键的字节进行逻辑与操作,并将结果保存 在全局变量 m a s k e d K e y 中。该掩码值为 0 x f f f f f f e 0 ,搜索会从图 1 8 - 4 中的叶结点 140.252.13.32处回溯两层,到达测试比特 62的结点。 查找键

图18-44 利用掩码过的查找键进行再次搜索的准备

f o r循环完成后,掩码过程也就完成了,再调用 r n _ s e a r c h(如图1 8 - 4 8所示),其调用参 数以 m a s k e d K e y为查找键,以指针 t为查找子树的顶点。图 1 8 - 4 5给出了我们所举例子中的 maskedKey的值。

图18-45 调用rn_search 时的maskedKey

字节0xa0是0xbc(188,查找键)和0xe0(掩码)逻辑与运算的结果。 211

r n _ s e a r c h从起点开始沿着树往下搜索,根据查找键来确定沿向左或向右的分支进行

搜索,直到到达某个叶子。在这个例子中,查找键有 9个字节,如图 18-45所示,所到达的是图 1 8 - 4中标有1 4 0 . 2 5 2 . 1 3 . 3 2的那个叶子,这是因为在字节 0 x a 0中比特6 2和6 3都为0。图1 8 - 4 6给 出了调用 Bcmp检验是否匹配时的数据结构。 由于这两个 9字节的字符串不相同,所以这次比较失败。

479

第 18章 Radix树路由表计计

图18-46 maskedKey 和新叶结点之间的比较 查找键:

图18-47 回溯到路由树的顶端和查找默认叶子的 rn_search

480

计计TCP/IP详解 卷 2:实现

212-221

该w h i l e循环处理各重复键,且处理每个重复键时的掩码不同。唯一被比较的重

复键是那个 rn_mask指针与m->rm_mask相等的键。下面以图 18-36和图18-37为例进行说明。 如果查找从比特 6 3的结点处开始,第一次内部 do

w h i l e循环中, m指向r a d i x _ m a s k结构

0 x f f f f f f 0 0 。当 r n _ s e a r c h 返回指向第一个重复叶子 1 2 7 . 0 . 0 . 0的指针时,该叶子的 r m _ m a s k等于m - > r m _ m a s k,因此,就调用 B c m p。如果比较失败, m的值就被设置成指向列 表中的下一个 r a d i x _ m a s k结构(具有掩码 0 x f f 0 0 0 0 0 0)的指针,并且对新掩码再次执行 d o w h i l e循环体。 r n _ s e a r c h再一次返回指向第一个重复叶子 1 2 7 . 0 . 0 . 0的指针,但是它的 r n _ m a s k并不等于 m - > r m _ m a s k。W h i l e继续进行到下一个重复叶子,它的 r n _ m a s k与m >rm_mask恰好相等。 现在回到查找键为 140.252.13.188的例子中,由于从检测比特 62的结点处开始的搜索失败, 因此,沿着树向上继续回溯,直到到达树的顶点,该顶点就是沿树向上的下一个 r n _ m k l i s t 为非空的结点。 图1 8 - 4 7给出了到达树的顶结点时的数据结构。此时,计算 m a s k e d K e y(为全 0 ),并且 r n _ s e a r c h从这个结点 (树的顶结点 )处开始,继续沿着树的左分支向下两层到达图 1 8 - 4中标 有“default”的叶子。 当r n _ s e a r c h返回时, x指向r n _ b值为-3 3的r a d i x _ n o d e,这是从树的顶端开始沿两 个左分支向下之后遇到的第一个叶子。但是 x - > r n _ m a s k(为空)与m - > r m _ m a s k不等,因此, 将x - > r n _ d u p e d k e y赋给x。用于测试的 w h i l e循环再次执行,但是,此时 x - r n _ m a s k等 于m - > r m _ m a s k,因此该 w h i l e循环终止。 B c m p对从m s t a r t开始的 1 2个值为 0的字节和从 x - > r n _ k e y加4开始的1 2个值为0的字节进行比较,结果相等,函数返回指针 x,该指针指向 默认路由的路由项。

18.11 rn_search函数 在前面一节中,我们已经知道 r n _ m a t c h调用了 r n _ s e a r c h来搜索路由表的子树。如图 18-48所示。

图18-48 rn_search 函数

这个循环和图 1 8 - 3 8中的相似。它在每一个结点上比较查找键中的一位比特,如果该比特

481

第 18章 Radix树路由表计计

为0,就通向左边的分支,如果该比特为 1,就通向右边的分支。在遇到一个叶子时终止搜索, 并返回指向该叶子的指针。

18.12 小结 每一个路由表项都由一个键来标识:在 IP协议中就是目的 IP地址,该IP地址可以是一个主 机地址或者是一个具有相应网络掩码的网络地址。一旦键的搜索确定了路由表项,在该表项 中的其他信息就会指定一个路由器的 I P地址,到目的地址的数据报就会发往该指定地址,还 会指明要用到的接口的指针、度量等等。 由I n t e r n e t协议维护的信息是 r o u t e结构,该 r o u t e结构只有两个成员构成:指向路由表 项的指针和目的地址。在 U D P、T C P和原始 I P使用的每个 I n t e r n e t协议控制块中,我们都会遇 到由Internet协议维护的 route结构。 P a t r i c i a树数据结构非常适合于路由表。由于路由表的查找要比添加或者删除路由频繁得 多,因此从性能的角度来看,在路由表中使用 P a t r i c i a树就更加有意义。 P a t r i c i a树虽然不利于 添加和删除这些附加工作,但是加快了查找的速度。 [Sklower 1991] 给出的 r a d i x树方法和 Net/1散列表的比较结果表明,用 radix树方法构造测试树用比 Net/1散列表法快一倍,搜索速度 快三倍。

习题 18.1 我们说过,在图 1 8 - 3中,查找键与路由表项匹配的一般条件是,它和路由表掩码的 逻辑与运算的结果等于路由表键。但是在图 1 8 - 4 0中采用了不同的测试方法。请建 立一个逻辑真值表以证明这两种方法等价。 18.2 假设某个 Net/3系统中的路由表需要 20 000个表项(IP地址)。在不考虑掩码的情况下, 请估算大约需要多大的存储器? 18.3 radix_node结构对路由表键的长度限制是多少?

第19章 选路请求和选路消息 19.1 引言 内核的各种协议并不直接使用前一章提供的函数来访问选路树,而是调用本章提供的几 个函数:rtalloc和rtalloc1是完成路由表查询的两个函数; rtrequest函数用于添加和 删除路由表项;另外大多数接口在接口连接或断开时都会调用函数 rtinit。 选路消息在两个方向上传递信息。进程(如route命令)或守护进程(routed或gated)把选路 消息写入选路插口,以使内核添加路由、删除路由或修改现有的路由。当有事件发生时,如接 口断开、收到重定向等,内核也会发送选路消息。进程通过选路插口来读取它们感兴趣的内容。 在本章中,我们将讨论这些选路消息的格式及其含义,关于选路插口的讨论将在下一章进行。 内核还提供了另一种访问路由表的接口,即系统的 s y s c t l调用,我们将在本章的结尾部 分阐述。该系统调用允许进程读取整个路由表或所有已配置的接口及接口地址。

19.2 rtalloc和rtalloc1函数 通常,路由表的查找是通过调用 r t a l l o c和r t a l l o c 1函数来实现的。图 1 9 - 1给出了 rtalloc。

图19-1 rtalloc 函数

58-65

参数r o是一个指针,它指向 TCP或UDP所使用的 Internet PCB(第22章)中的r o u t e结

构。如果 r o已经指向了某个 r t e n t r y结构(即r o _ r t非空),而该结构指向一个接口结构且路 由有效,则函数立即返回。否则, r t a l l o c1将被调用,调用的第二个参数为 1。我们很快会 看到该参数的用途。 如图1 9 - 2所示,r t a l l o c 1调用了r n h _ m a t c h a d d r函数,对于 I n t e r n e t地址来说,该函 数就是rn_match数(图18-17)。 66-76

第一个参数是一个指针,它指向一个含有待查找地址的插口地址结构。 sa_family

成员用于选择所查找的路由表。 1. 调用rn_match 77-78 如果符合下列三个条件,则查找成功。 1) 存在该协议族的路由表; 2) rn_match返回一个非空指针;并且

483

第 19 章 选路请求和选路消息计计

3) 匹配的radix_node结构没有设置RNF_ROOT标志。 注意,树中标有 end的两个叶子都设有 RNF_ROOT标志。

图19-2 rtalloc1 函数

2. 查找失败 94-101

在这三个条件中只要有一个条件没有得到满足,查找就会失败,并且统计值

r t s _ u n r e a c h也要递增。这时,如果调用 r t a l l c o1的第二个参数 (r e p o r t)为1,就会产生 一个选路消息。任何感兴趣的进程都可以通过选路插口读取该消息。选路消息的类型为 RTM_MISS,并且函数返回一个空指针。 79

如果三个条件都满足,则查找成功。指向匹配的 r a d i x _ n o d e结构的指针保存在 r t和

n e w r t中。注意,在r t e n t r y结构的定义中(图18-24),两个r a d i x _ n o d e结构在开头的位置 处,如图 1 8 - 8所示,其中第一个代表一个叶结点。因此, r n _ m a t c h返回的 r a d i x _ n o d e结 构的指针事实上是一个指向 rtentry结构的指针,该 rtentry结构是一个匹配的叶结点。

484

计计TCP/IP详解 卷 2:实现

3. 创建克隆表项 80-82 如果调用的第二个参数非零,而且匹配的路由表项设有 RTF_CLONING标志,则调用 r t r e q u e s t函数发送 R T M _ R E S O L V E命令来创建一个新的 r t e n t r y结构,该结构是查询结果 的克隆。 ARP将针对多播地址利用这一机制。 4. 克隆失败 如果r t r e q u e s t返回一个差错, n e w r t就被重新设置成 r n _ m a t c h所返回的表项,

83-87

并增加它的引用计数。然后程序跳转到 miss处,产生一条RTM_MISS消息。 5. 检查是否需要外部转换 如果r t r e q u e s t成功,并且新克隆的表项设有 R T F _ X R E S O L V E标志,则程序跳至

88-91

m i s s处,但这次产生的是 R T M _ R E S O L V E消息。该消息的目的是为了把路由创建的时间通知 给用户进程,在 IP地址到X.121地址的转换过程中会用到它。 6. 为正常的成功查找递增引用计数 当查找成功但没有设置 R T F _ C L O N I N G标志时,该语句将递增路由表项的引用计数。

92-93

这是本函数正常情况下的处理流程,之后程序返回一个非空的指针。 虽然是这样小的一段程序,但是在 r t a l l o c 1的处理过程中有很多选择。该函数有 7个不 同的流程,如图 19-3所示。

参数

标志

返回

标志

产生的 路由消息

返回值 空

查找失败

空 ptr 查找成功

OK OK 差错

ptr ptr ptr ptr

图19-3 rtalloc1 处理过程小结

需要解释的是,如果存在默认路由,前两行 (找不到路由表项的流程 )是不可能出现的。还 有,在第 5、第6两行中的 r t _ r e f c n t也做了递增,因为这两行在调用 r t r e q u e s t时使用了 RTM_RESOLVE参数,递增在rtrequest中完成。

19.3 宏RTFREE和rtfree函数 宏R T F R E E,如图19-4所示,仅在引用计数小于等于 1时才调用 r t f r e e函数;否则,它仅 完成引用计数的递减。 209-213

r t f r e e函数如图 1 9 - 5所示。当不存在对 r t e n t r y结构的引用时,函数就释放该

结构。例如,在图 2 2 - 7中,当释放一个协议控制块时,如果它指向一个路由表项,则需要调 用rtfree。 105-115

首先递减路由表项的引用计数,如果它小于等于 0并且该路由不可用,则该表项可

以被释放。如果该表项设有 R N F _ A C T I V E或R N F _ R O O T标志,那么这是一个内部差错。因为, 如果设有 R N F _ A C T I V E,那么该结构仍是路由表的一部分;如果设有 R N F _ R O O T,那么它是 一个由rn_inithead创建的标有 end的结构。

485

第 19 章 选路请求和选路消息计计

图19-4 宏RTFREE

图19-5 rtfree 函数:释放一个 rtentry 结构

116

r t t r a s h是一个用于调试的计数器,记录那些不在选路树中但仍未释放的路由表项的

数目。当 r t r e q u e s t开始删除路由时,它被递增,然后在这儿递减。正常情况下,它的值应 该是0。 1. 释放接口引用 117-122

先查看引用计数。确认引用计数非负后, I F A F R E E将递减i f a d d r结构的引用计

数。当计数值递减为零时,调用 ifafree释放它。 2. 释放选路存储器 123-124

释放由路由表项关键字及其网关所占的存储器。我们会看到 r t _ s e t g a t e把它们

分配在存储器的同一个连着的块中。因此,只调用一个 F r e e就可以同时把它们释放。最后还 要释放rtentry结构。 路由表引用计数 路由表引用计数 (r t _ r e f c n t)的处理与其他许多引用计数的处理不同。我们看到,在图 1 8 - 2中,大多数路由的引用计数为 0,而这些没有引用的路由表项并没有被删除。原因就在 r t f r e e中:只有当 R T F _ U P标志被删除时,引用计数为 0的表项才会被删除。而仅当从选路 树中删除路由时,该标志才会被 rtrequest删除。

486

计计TCP/IP详解 卷 2:实现

大多数路由是按如下方式使用的。 •如果到某接口的路由是在配置该接口时自动创建的 (典型的,例如以太网接口的配置 ), 则r t i n i t用命令参数R T M _ A D D来调用r t q u e s t,以创建新的路由表项,并设置它的引 用计数为1。然后, rtinit在退出前把该引用计数递减成 0。 对于点到点接口的处理过程也是类似的,所以路由的引用计数也是从 0开始。 如果路由是由 r o u t e命令手工创建的,或者是由选路守护进程创建的,处理过程同样 是类似的。 r o u t e _ o u t p u t用命令参数 R T M _ A D D来调用 r t r e q u e s t,并设置新路由 的引用计数为1。在退出前,route_output把该引用计数递减到 0。 因此,所有新创建的路由都是从引用计数 0开始的。 • 当TCP或UDP在插口上发送 I P数据包时,i p _ o u t p u t调用r t a l l o c,r t a l l o c再调用 rtalloc1。如图19-3所示,如果找到了路由, rtalloc1就会递增其引用计数。 所查找到的路由称为被持路由 (held route),因为协议持有指向路由表项的指针,该指针 通常被包含在协议控制块中的 r o u t e结构里。一个被其他协议持有的 r t e n t r y结构是 不能被删除的。所以,在 rtfree中,当引用计数为 0时,rtentry结构才能被删除。 • 协议通过调用 R T F R E E或r t f r e e来释放被持路由。在图 8 - 2 4中,当i p _ o u t p u t检测到 目的地址改变时,我们已经使用了这种处理。在第 2 2章中,释放持有路由的协议控制块 时,我们还会遇到这种处理。 在后面的代码中可能会引起混淆的是, r t a l l o c1经常被调用以判断路由是否存在,而调 用者并非试图持有该路由。因为 r t a l l o c1递增了该路由的引用计数器,所以调用者就立即 递减该计数器。 考虑一个被 r t r e q u e s t删除的路由。它的 R T F _ U P标志被清除,并且,如果没有被持有 ( 它的引用计数为 0 ) ,就要调用 r t f r e e 。但 r t f r e e 认为引用计数小于 0是错的,所以 r t r e q u e s t查看它的引用计数,如果小于等于 0,就递增该计数值并调用 r t f r e e。通常,这 将使引用计数变成 1,之后,rtfree把引用计数递减到 0,并删除该路由。

19.4 rtrequest函数 r t r e q u e s t函数是添加和删除路由表项的关键点。图 1 9 - 6给出了调用它的一些其他函 数。

图19-6 调用rtrequest 的函数

r t r e q u e s t是一个s w i t c h语句,每个 c a s e对应一个命令: R T M _ A D D、R T M _ D E L E T E 和RTM_RESOLVE。图19-7给出了该函数的开头一段以及 RTM_DELETE命令的处理。 290-307

第二个参数,dst,是一个插口地址结构,它指定在路由表中添加或删除的表项。

487

第 19 章 选路请求和选路消息计计

表项中的 s a _ f a m i l y用于选择路由表。如果 f l a g s参数指出该路由是主机路由 (而不是到某 个网络的路由),则设置netmask指针为空,忽略调用者设置的任何值。

图19-7 rtrequest 函数:RTM_DELETE 命令

1. 从选路树中删除路由 309-315

r n h _ d e l a d d r函数(图1 8 - 1 7中的r n _ d e l e t e)从选路树中删除表项,返回相应

rtentry结构的指针,并清除 RTF_UP标志。 2. 删除对网关路由表项的引用 316-320

如果该表项是一个经过某网关的非直接路由,则 R T F R E E递减该网关路由表项的

引用计数。如它的引用计数被减为 0,则删除它。设置 r t _ g w r o u t e指针为空,并将 r t设置 成原来要删除的表项。

488

计计TCP/IP详解 卷 2:实现

3. 调用接口请求函数 321-322

如果该表项定义了 i f a _ r t r e q u e s t函数,就调用该函数。 A R P会使用该函数,

例如,在第 21章中用它来删除对应的 ARP表项。 4. 返回指针或删除引用 323-330

因为该表项在接着的代码里不一定被删除,所以递增全局变量 rttrash。如果调

用者需要选路树中被删除的 r t e n t r y结构的指针 (即如果r e t _ n r t非空),则返回该指针,但 此时不能释放该表项:调用者必须在使用完该表项后调用 r t f r e e来删除它。如果 r e t _ n r t 为空,则该表项被释放:如果它的引用计数小于等于 0,则递增该计数值,并调用 r t f r e e。 break语句将使函数退出。 图1 9 - 8给出了函数的下一部分,用于处理 R T M _ R E S O L V E命令。只有r t a l l o c1能够携带 此命令参数调用本函数。也只有在从一个设有 R T F _ C L O N I N G标志的表项中克隆一个新的表 项时,rtalloc1才这样用。

图19-8 rtrequest 函数:RTM_RESOLVE 命令

331-339

最后一个参数, r e t _ n r t ,在这个命令里的用途不同:它是一个设有

R T F _ C L O N I N G标志的路由表项的指针 (图1 9 - 2 )。新的表项具有相同的 r t _ i f a指针、相同的 rt_gateway和相同的标志(RTF_CLONING标志被清除 )。如果被克隆表项的 rt_genmask指 针为空,则新表项是一个主机路由,因此要设置它的 R T F _ H O S T标志;否则新表项为网络路 由,其网络掩码通过复制 r t _ g e n m a s k得到。在本节的结尾部分,我们给出了克隆带网络掩 码的路由的一个例子。这个 case将跳转至下个图中的 makeroute标记处继续进行。 图19-9给出了RTM_ADD命令的代码。 5. 定位相应的接口 340-342

函数i f a _ i f w i t h r o u t e为目的(d s t)查找适当的本地接口,并返回指向该接口

的ifaddr结构的指针。 6. 为路由表项分配存储器 343-348

分配了一个 r t e n t r y结构。在前一章中我们知道,该结构包含了两个选路树的

r a d i x _ n o d e结构及其他路由信息。该结构被清零,之后,其标志 r t _ f l a g s被设置成调用 本函数的 flags参数,同时再设置 RTF_UP标志。 7. 分配并复制网关地址 349-352

r t _ g a t e w a y函数(图19-11)为路由表(d s t)及其g a t e w a y分配了存储器,然后将

gateway复制到新分配的存储器中,并设置指针 rt_key、rt_gateway和rt_gwroute。 8. 复制目的地址

489

第 19 章 选路请求和选路消息计计

图19-9 rtrequest 函数:RTM_ADD 命令

353-357

把目的地址 (路由表表项 d s t)复制到r n _ k e y所指向的存储器中。如果提供了网络

掩码,则 r t _ m a s k e d c o p y对d s t和n e t m a s k进行逻辑与运算,得到新的表项。否则, d s t 就会被复制成新的表项。对 d s t和n e t m a s k进行逻辑与运算是为了确保表中的表项已经和它 的掩码进行了与运算。这样,查找表项与表中的表项进行比较时,只需要另外对查找表项和 掩码进行逻辑与运算就可以了。例如,下面的这个命令向以太网接口 l e0添加了另一个 I P地 址(一个别名),其子网为 12而不是13。 bsdi $ ifconfig le0 inet 140.252.12.63 netmask 0xffffffe0 alias

490

计计TCP/IP详解 卷 2:实现

该例子中存在的一个问题是,我们所指定的全 1的主机号是错误的。不过,该表项存入路 由表后,我们用 netstat验证可知该地址已经和掩码进行过逻辑与运算了。 Destination 140.252.12.32

Gateway link#1

Flags U C

Refs 0

Use 0

Interface le0

9. 往选路树中添加表项 358-366

r n h _ a d d a d d r函数(图1 8 - 1 7中的r n _ a d d r o u t e)向选路树中添加这个 r t e n t r y

结构,其中附带了它的目的地址和掩码。如果有差错产生,则释放该结构,并返回 EEXIST(即,该表项已经存在于路由表中了 )。 10. 保存接口指针 367-369 递增ifaddr结构的引用计数,并保存 ifaddr和ifnet结构的指针。 11. 为新克隆的路由复制度量 370-371

如果命令是 R T M _ R E S O L V E(不是R T M _ A D D),则把被克隆的表项中的整个度量结

构复制到新的表项里。如果命令是 RTM_ADD,则调用者可在函数返回后设置该度量值。 12. 调用接口请求函数 372-373

如果为该表项定义了 i f a _ r t r e q u e s t函数,则调用该函数。对于 R T M _ A D D和

RTM_RESOLVE命令,ARP都要用该函数来完成一些额外的处理。 13. 返回指针并递增引用计数 374-378

如果调用者需要该新结构的指针,则通过 ret_nrt返回该指针,并将引用计数值

从0递增到1。 例:克隆的带网络掩码的路由 仅当r t r e q u e s t的R T M _ R E S O L V E命令创建克隆路由时,才使用 r t _ g e n m a s k的值。如 果r t _ g e n m a s k指针非空,则它指向的插口地址结构就成了新创建路由的网络掩码。在我们 的路由表中,即图 1 8 - 2,克隆的路由是针对本地以太网和多播地址的。下面的例子引自 [Sklower 1991],它提供了克隆路由的不同用法。另外一个例子见习题 19.2。 考虑一个B类网络,如 128.1,它在点到点链路之外。子网掩码是 0x f f f f f f00,其中含8 比特的子网号和 8比特的主机号。我们要为所有可能的 2 5 4个子网提供路由表项,这些表项的 网关是与本机直接相连的路由器,该路由器知道如何到达与 128.1网络相连的链路。 假设该网关不是我们的默认路由器,则最简单的方法就是创建单个表项,该表项以 1 2 8.1.0.0为目的、以 0 x f f f f 0 0 0 0为掩码。可是,假设 1 2 8 . 1网络的拓扑使所有可能的 2 5 4 个子网中的每一个都有不同的运营特性: RT Ts、M T U s和时延等。那么如果每个子网都有单 独的路由表项,我们就能够看到,无论何时连接断开后, T C P都会刷新该路由表项的统计值, 如路由的 RT T、RT T变量等 (图2 7 - 3 )。尽管我们可以用 r o u t e命令手工地为 2 5 4个子网中的每 一个子网都添加路由表项,但更好的方法是采用克隆机制。 由系统管理员先创建一个以 1 2 8 . 1 . 0 . 0为目的地,以 0 x f f f f 0 0 0 0为网络掩码的表项。再 设置其 R T F _ C L O N I N G标志,并设置 g e n m a s k为0 x f f f f f f 0 0(与网络掩码不同 )。这时,如 果在路由表中查找 1 2 8 . 1 . 2 . 3 ,而路由表中没有子网 1 2 8 . 1 . 2 的表项,那么具有掩码 0 x f f f f 0 0 0 0的网络1 2 8 . 1的表项为最佳匹配。因为该表项设有 R T F _ C L O N I N G标志,所以要 创建一个新的表项,新表项以 128.1.2为目的地,以0xffffff00(genmask的值)为网络掩码。

491

第 19 章 选路请求和选路消息计计

这样,下一次引用该子网内的主机时,如 128.1.2.88,最佳匹配就是新创建的表项。

19.5 rt_setgate函数 选路树中的每个叶子都有一个表项

( r t _ k e y ,也就是在 r t e n t r y 结构开头的

r a d i x _ n o d e结构的r n _ k e y成员)和一个相关联的网关 (r t _ g a t e w a y)。在创建路由表项时, 它们都被指定为插口地址结构。 rt_setgate为这两个结构分配存储器,如图 19-10所示。

(叶子)

(结点)

(叶子)

(结点)

以太网地址

图19-10 路由表表项和相关网关示例

这个例子给出了图 18-2中的两个表项,它们的表项分别是 127.0.0.1和140.252.13.33。前一 个的网关成员指向一个 I n t e r n e t插口地址结构,后一个的网关成员指向一个含以太网地址的数 据链路插口地址。前一个是在系统初始化时,由 r o u t e系统将其添加到路由表中的;后一个 是由ARP创建的。 在图1 9 - 11中,我们有意识地把两个由 r t _ k e y指向的结构紧挨着画在一起,因为它们是 由rt_setgate一起分配的。

492

计计TCP/IP详解 卷 2:实现

图19-11 rt_setgate 函数

1. 依据插口地址结构设置长度 384-391

d l e n是目的插口地址结构的长度, g l e n是网关插口地址结构的长度。 R O U N D U P

宏把数值上舍入成 4的倍数个字节,但大多数插口地址结构的长度本身就是 4的倍数。 2. 分配存储器 392-401 如果还没有给该路由表表项和网关分配存储器,或 glen大于当前rt_gateway所 指向的结构的长度,则分配一片新的存储器,并使 rn_key指向新分配的存储器。 3. 使用分配给表项和网关的存储器 398-401

由于已经给表项和网关分配了一片足够大小的存储器,因此,直接将 n e w指向这

个已经存在的存储器。 4. 复制新网关 402 复制新的网关结构,并且设置 rt_gateway,使其指向插口地址结构。 5. 从原有的存储器中将表项复制到新存储器中 403-406

如果分配了新的存储器,则在网关字段被复制前,先复制路由表表项 d s t,并释

放原有的存储器片。 6. 释放网关路由指针

493

第 19 章 选路请求和选路消息计计

407-412

如果该路由表项含有非空的 r t _ g w r o u t e指针,则用 R T F R E E释放该指针所指向

的结构,并设置 rt_gwroute为空。 7. 查找并保存新的网关路由指针 413-415

如果路由表项是一个非直接路由,则 r t a l l o c1查找新网关的路由表项,并将它

保存在 r t _ g w r o u t e中。如果非直接路由指定的网关无效,则 r t _ s e t g a t e并不返回任何差 错,但rt_gwroute会是一个空指针。

19.6 rtinit函数 Internet协议添加或删除相关接口的路由时,对 rtinit的调用有四个。 • 在设置点到点接口的目的地址时, i n _ c o n t r o l调用r t i n i t两次。第一次调用指定 R T M _ D E L E T E命令,以删除所有现存的到该目的地址的路由 (图6 - 2 1 );第二次调用指定 RTM_ADD命令,以添加新路由。 • in_ifinit调用rtinit为广播网络添加一条网络路由或为点到点链路(图6-19)添加一条主 机路由。如果是给以太网接口添加的路由,则in_ifinit自动设置其RTF_CLONING标志。 • in_ifscrub调用rtinit,以删除一个接口现存的路由。 图19-12给出了rtinit函数的第一部分。 cmd参数只能是 RTM_ADD或RTM_DELETE。

图19-12 rt_init 函数:调用 rtrequest 处理命令

494

计计TCP/IP详解 卷 2:实现

1. 为路由获取目的地址 452 如果是一个到达某主机的路由,则目的地址是点到点链路的另一端。否则,我们处理的 就是一个网络路由,其目的地址是接口的单播地址 (经ifa_netmask掩码过的)。 2. 用网络掩码给网络地址掩码 453-459

如果要删除路由,则必须在路由表中查找该目的地址,并得到它的路由表项。如

果要删除的是一个网络路由且接口拥有相关联的网络掩码,则分配一个

m b u f ,用

r t _ m a s k e d c o p y对目的地址和调用参数中的掩码地址进行逻辑与运算,并将结果复制到 mbuf中。令dst指向mbuf中掩码过的复制值,它就是下一步要查找的目的地址。 3. 查找路由表项 460-469

r t a l l o c1在路由表中查找目的地址,如果能找到,则先递减该表项的引用计数

(因为r t a l l o c1递增了该引用计数 )。如果路由表中该接口的 i f a d d r指针不等于调用者的参 数,则返回一个差错。 4. 处理请求 470-473

r t _ r e q u e s t执行R T M _ A D D或R T M _ D E L E T E命令。当 r t _ r e q u e s t返回时,如

果之前分配了mbuf,则释放它。 图19-13给出了rtinit的后半部分。

图19-13 rtint 函数:后半部分

5. 删除成功时产生一个选路消息 474-480

如果删除了一个路由,并且 r t r e q u e s t返回0和被删除的 r t e n t r y结构的指针

(n r t中),就用 r t _ n e w a d d r m s g产生一个选路插口消息。如果引用计数小于等于 0,则递增 该引用计数,并用 rtfree释放该路由。

495

第 19 章 选路请求和选路消息计计

6. 成功添加 481-482

如果添加了一个路由,并且 rtrequest返回了0和被添加的 rtentry结构的指针

(nrt),就递减其引用计数 (因为rtrequest递增了该引用计数 )。 7. 不正确的接口 483-494

如果新路由表项中接口的 i f a d d r指针不等于调用参数,则表明有差错产生。利

用r t r e q u e s t,通过调用 i f a _ i f w i t h r o u t e来测定新路由中的 i f a指针(r t r e q u e s t函数 如图 1 9 - 9所示 )。产生这个差错后,做如下步骤:向控制台输出一条出错消息;如果定义了 i f a _ r t r e q u e s t函数,就以 R T M _ D E L E T E为参数调用它;释放 i f a d d r结构;设置r t _ i f a 指针为调用者指定的值;递增接口的引用计数;如果定义了 i f a _ r t r e q u e s t函数,就以 RTM_ADD为参数调用它。 8. 产生选路消息 495 用rt_newaddrmsg为RTM_ADD命令产生一个选路插口消息。

19.7 rtredirect函数 当收到一个 ICMP重定向后, i c m p _ i n p u t调用r t r e d i r e c t及p f c t l i n p u t(图11-27)。 后一个函数又调用 u d p _ c t l i n p u t和t c p _ c t l i n p u t,这两个函数遍历所有的 UDP和TCP协 议控制块 ( P C B )。如果P C B连接到一个外部地址,而到该外部地址的方向已经被改变,并且该 PCB持有到那个外部地址的路由,则调用 r t f r e e释放该路由。下一次使用这些控制块发送到 该外部地址的 I P数据报时,就会调用 r t a l l o c,并在路由表中查找该目的地址,很可能会找 到一条新(改变过方向的)路由。 r t r e d i r e c t函数的作用是验证重定向中的信息,并立即更新路由表,产生选路插口消 息。图19-14给出了rtredirect函数的前半部分。 147-157

函数的参数包括: d s t,导致重定向的数据报的目的 I P地址(图8-1 8中的H D );

g a t e w a y,路由器的 I P地址,用作该目的的新网关字段 (图8-1 8中的R 2 );n e t m a s k,空指 针;f l a g s,设置了R T F _ G A T E W A Y标志和R T F _ H O S T标志;s r c,发送重定向的路由器的 IP 地址 (图8-1 8中的 R 1 );r t p,空指针。需要指出的是, i c m p _ i n p u t调用本函数时,参数 netmask和rtp是空指针,但是其他协议调用本函数时,这两个参数未必为空指针。 1. 新路由必须直接相连 158-162 新的网关必须是直接相连的,否则该重定向无效。 2. 查找目的地址的路由表项并验证重定向 163-177

调用r t a l l o c 1在路由表中查找到目的地址的路由。验证重定向时,下列条件必

须为真,否则该重定向无效,并且函数返回一个差错。要注意的一点是, i c m p _ i n p u t会忽 略从r t r e d i r e c t返回的任何差错。 I C M P也会忽略它,即不会为一个无效的重定向而产生一 个差错信息。 • 必须未设置 RTF_DONE标志; • rtalloc必须已找到一个到 dst的路由表项; • 发送重定向的路由器的地址 (src)必须等于当前为目的地址设置的 rt_gateway; • 新网关的接口 (由i f a _ i f w i t h n e t返回的 i f a结构)必须等于当前为目的地址设置的接 口(rt_ifa),也就是说,新网关必须和当前网关在同一个网络上;并且

496

计计TCP/IP详解 卷 2:实现

• 新网关不能把到这个主机的路由改变为到它自己,也就是说,不能存在与 g a t e w a y相 等的带有单播地址或广播地址的连接着的接口。

图19-14 rtredirect 函数:验证收到的重定向

3. 必须创建一个新路由 178-185

如果到达目的地址的路由没有找到,或找到的路由表项是默认路由,则为该目的

地址创建一个新的路由。如程序注释所述,对于可访问多个路由器的主机来说,当默认路由 器出错时,它可以利用这种机制来获悉正确的路由器。判断是否为默认路由的测试方法是查 看该路由表项是否具有相关的掩码以及掩码的长度字段是否小于 2,因为默认路由的掩码是 rn_zeros(图18-35)。 图19-15给出了rtredirect函数的后半部分。 4. 创建新的主机路由 186-195

如果到达目的地址的当前路由是一个网络路由,并且重定向是主机重定向而不是

497

第 19 章 选路请求和选路消息计计

网络重定向,那么就为该目的地址建立一个主机路由,而不必去管现存的网络路由。我们要 提示的是, f l a g s参数总是指明 R T F _ H O S T标志,因为 Net/3 ICMP把所有收到的重定向都看 成主机重定向。

图19-15 rtrequest 函数:后半部分

5. 创建路由 196-201

r t r e q u e s t创建一个新路由,并将标志 R T F _ G A T E W A Y和R T F _ D Y N A M I C置位。

参数n e t m a s k是一个空指针,这是因为新路由是一个主机路由,它的掩码是隐含的全 1比特。

498

计计TCP/IP详解 卷 2:实现

stat指向一个计数器,它在后面的程序里递增。 6. 改变现存的主机路由 202-211

当到达目的地址的当前路由已经是一个主机路由时,才执行这段代码。此时,不需

要创建新的表项,但需要修改现存的表项:设置 RTF_MODIFIED标志,并调用rt_setgate来 修改路由表项的 rt_gateway字段,使其指向新的网关地址。 7. 如果目的地址直接相连,则忽略 212-213

如果到达目的地址的当前路由是一个直接路由 (没有设置RTF_GATEWAY标志),那

么该重定向针对的是一个已直接连接的目的地址。此时,函数返回 EHOSTUNREACH。 8. 返回指针并递增统计值 214-225

如果找到了路由表项,那么该表项被返回 (如果 r t p 非空且没有出错 )或者用

rtfree释放它。相关的统计值被递增。 9. 产生选路消息 226-232

r t _ a d d r i n f o 结构清零,并由 r t _ m i s s m s g 产生一个选路插口消息。

raw_input把该消息发送到所有对重定向感兴趣的进程。

19.8 选路消息的结构 选路消息由一个定长的首部和至多 8个插口地址结构组成。该定长首部是下列三种结构中 的一个: • rt_msghdr • if_msghdr • ifa_msghdr 图18-11给出了产生不同消息的函数的概观图,图 18-9给出了每种消息类型所使用的结构。 选路消息三种首部结构的前三个成员的数据类型及其含义是相同的,分别为:消息的长度、 版本和类型。这样,消息接受者就可以对消息进行解码了。而且,每种结构都各有一个成员 来编码首部之后 8个可能的插口地址结构: r t m _ a d d r s、i f m _ a d d r s和i f a m _ a d d r s成员, 它们都是一个比特掩码。 图1 9 - 1 6给出了最常用的结构,即 r t _ m s g h d r。R T M _ I F I N F O消息使用了图 1 9 - 1 7中的 if_msghdr结构。RTM_NEWADDR和RTM_DELADDR消息使用图19-18中的ifa_msghdr结构。

图19-16 rt_msghdr 结构

499

第 19 章 选路请求和选路消息计计

图19-17 if_msghdr 结构

图19-18 ifa_msghdr 结构

注意,这三种不同结构的前三个成员具有相同的数据结构和含义。 三个变量 r t m _ a d d r s、i f m _ a d d r s和i f a m _ a d d r s都是比特掩码,它们定义了首部之 后的插口地址结构。图 19-19给出了比特掩码用到的一些常量。 比特掩码 常



RTA_DST RTA_GATEWAY RTA_NETMASK RTA_GENMASK RTA_IFP RTA_IFA RTA_AUTHOR RTA_BRD

数组索引 值 Ox01 Ox02 Ox04 Ox08 Ox10 Ox20 Ox40 Ox80





RTAX_DST RTAX_GATEWAY RTAX_NETMASK RTAX_GENMASK RTAX_IFP RTAX_IFA RTAX_AUTHOR RTAX_BRD RTAX_MAX

rtsock.c 描 值

中的名字

0 1 2 3 4 5 6 7 8

dst gate netmask genmask ifpaddr ifaaddr brdaddr



目的插口地址结构 网关插口地址结构 网络掩码插口地址结构 克隆掩码插口地址结构 接口名称插口地址结构 接口地址插口地址结构 重定向产生者的插口地址结构 广播或点到点的目的地址 r t i - i n t o [ ]数组的元素个数

图19-19 用来引用 rti_info 数组成员的常量

比特掩码的值可以用常数 1左移数组下标指定的位数而得到。例如, 0 x 2 0(R T A _ I F A)是1 左移五位(RTAX_IFA)。我们会在代码中看到这个过程。 插口地址结构总是按照数组下标递增的次序,一个接一个地出现的。例如,如果掩码是 0x8 7,则第一个插口地址结构的内容为目的地址,接着是网关地址,网络掩码,最后是广播 地址。 内核用图 1 9 - 1 9中的数组下标来引用 r t _ a d d r i n f o结构,如图1 9 - 2 0所示。该结构具有与 我们所述相同的比特掩码,以表示哪些地址存在。它的另一个成员指向那些插口地址结构。

500

计计TCP/IP详解 卷 2:实现

图19-20 rt_addrinfo 结构:表示哪些地址存在的掩码和指向这些地址的指针

例如,如果 r t i _ a d d r s 成员中设置了 R T A _ G A T E W A Y 比特,则 r t i _ i n f o [RTA_GATEWAY] 成员就是含网关地址的插口地址结构的指针。对于 I n t e r n e t协议,该插口 地址结构就是含网关的 IP地址的sockaddr_in结构。 图1 9 - 1 9中的第五栏给出了文件 r t s o c k . c中r t i _ i n f o数组成员相应的名称。它们的定 义形式如下: #define dst info.rti_info[RTAX_DST]

在本章的很多源文件中我们都将遇到这些名称。元素 R T A X _ A U T H O R没有命名,因为进程 不会向内核传递该元素。 我们已经有两次遇到过 r t _ a d d r i n f o结构:在函数 r t a l l o c1 (图1 9 - 2 )和r t r e d i r e c t 中(图1 9 - 1 4 )。图1 9 - 2 1给出了 r t a l l o c1在路由表查找失败后调用 r t _ m i s s m s g时创建的该结 构的格式。

没有找到IP地址

图19-21 rtalloc1 传递给rt_missmsg 的rt_addrinfo 结构

所有未使用的指针都是空指针,因为该结构在使用前被设置成

0 。还要指出的是,

r t i _ a d d r s成员没有被初始化成相应的比特掩码,因为在内核使用该结构时, r t i _ i n f o数 组中的指针为空就代表不存在该插口地址结构。仅在进程和内核之间传递的消息里该掩码才 是必不可少的。 图19-22给出了rtredirect调用rt_missmsg时创建的该结构的格式。

导致路由改变的目标IP地址

待使用的新网关的IP地址

发出路由改变的路由器的IP地址

图19-22 rtredirect 传递给rt_missmsg 的rt_addrinfo 结构

501

第 19 章 选路请求和选路消息计计

下一节中将介绍该结构是如何被放置在发送到其他进程的消息里的。 图1 9 - 2 3给出了 r o u t e _ c b结构,我们会在下一节中遇到。该结构由四个计数器组成,前 三个分别针对 IP、XNS和OSI协议,最后一个为“任意”计数器。每个计数器分别记录相应的 域中当前存在的选路插口的数目。 203-208

内核跟踪选路插口监听器的数目。这样,当不存在等待消息的进程时,内核就可

以避免创建选路消息以及调用 raw_input发送该消息。

图19-23 route_cb 结构:选路插口监听器的计数器

19.9 rt_missmsg函数 如图1 9 - 2 4所示,r t _ m i s s m s g函数使用了图 1 9 - 2 1和图1 9 - 2 2中的结构,并调用 r t _ m s g1 在m b u f链中为进程创建了相应的变长消息,之后调用 r a w _ i n p u t将该m b u f链传递给所有相 关的选路插口。

图19-24 rt_missmsg 函数

516-525 如果没有任何选路插口监听器,则函数立即退出。 1. 在mbuf链中创建消息 526-528

r t _ m s g1(19.12节)在mbuf链中创建相应的消息,并返回该链的指针。利用图 19-

2 2中的r t _ a d d r i n f o结构,图 1 9 - 2 5给出了所得到的 m b u f链的一个例子。这些信息之所以要 放在一个 m b u f链中,是因为 r a w _ i n p u t要调用 s b a p p e n d a d d r把该m b u f链添加到插口接收

502

计计TCP/IP详解 卷 2:实现

缓存的尾部。 链中的下一个mbuf

(后一半,8字节)

(16字节)

(76字节)

(未使用) (84字节)

(16字节)

(前一半,8字节)

图19-25 由rt_msg1 创建的对应于图 19-22的mbuf链

2. 完成消息的创建 529-532 成员rtm_flags和rtm_errno被设置成调用者传递的值。成员 rtm_addrs的值 是由r t i _ a d d r s复制而得到的。在图 1 9 - 2 1和图1 9 - 2 2中,我们给出的 r t i _ a d d r s值为0,因 此,rt_msg1依据rti_info数组中的指针是否为空,来计算并保存相应的比特掩码。 3. 设置消息的协议,调用 raw_input 533-534 raw_input的后三个参数指定了选路消息的协议、源和目的。这三个结构被初始 化成: struct sockaddr struct sockaddr struct sockproto

route_dst = { 2, PF_ROUTE, }; route_src = { 2, PF_ROUTE, }; route_proto = { PF_ROUTE };

内核不会修改前两个结构。第三个结构 s o c k p r o t o,我们第一次遇到。图 1 9 - 2 6给出了 它的定义。

图19-26 sockproto 结构

该结构的协议族成员一直保持了它的初始值 P F _ R O U T E,但其协议成员的值在每一次调

503

第 19 章 选路请求和选路消息计计

用r a w _ i n p u t时都要进行设置。当进程调用 s o c k e t创建选路插口时,第三个参数定义了该 进程所感兴趣的协议。 r a w _ i n p u t的调用者再把 r o u t e _ p r o t o结构的 s p _ p r o t o c o l成员 设置成选路消息的协议。在 r t _ m i s s m s g这种情况下,该成员被设置成目的插口地址结构的 sa_family(如果调用者指定了该值 ),在图19-21和图19-22中,其值为 AF_INET。

19.10 rt_ifmsg函数 在图4-30中我们可以看出, i f _ u p和i f _ d o w m都调用了图19-27中的r t _ i f m s g。在接口 连接或断开时,该函数被用来产生一个选路插口消息。

图19-27 rt_ifmsg 函数

547-548 如果没有选路插口监听器,函数立即退出。 1. 在mbuf链中创建消息 549-552 rt_addrinfo结构被设置成0。rt_msg1在一个mbuf链中创建相应的消息。需要 注意的是, r t _ a d d r i n f o结构中的所有指针都为空,选路消息仅由定长的 i f _ m s g h d r结构 组成,而不含任何地址。 2. 完成消息的创建 553-557

把接口的索引、标志和 i f _ d a t a结构复制到m b u f中的报文里。把 i f m _ a d d r s比

特掩码设置成0。 3. 设置消息的协议,调用 raw_input 558-559 因为该消息可应用于所有的协议,所以其协议被设置成 0。并且该消息是关于某接 口的,而不是针对特定的目的地。 raw_input把该消息传递给相应的监听器。

504

计计TCP/IP详解 卷 2:实现

19.11 rt_newaddrmsg函数 从图1 9 - 1 3中可以看到,在接口上添加或从中删除一个地址时, r t i n i t要以R T M _ A D D或 R T M _ D E L E T E为参数调用r t _ n e w a d d r m s g。图19-28给出了r t _ n e w a d d r m s g函数的前半部 分。

图19-28 rt_newaddrmsg

函数的前半部分:创建 ifa_msghdr

580-581 如果没有选路插口监听器,函数立即退出。 1. 产生两个选路消息 582 本函数要产生两个选路报文,一个用于提供有关接口的信息,另一个提供有关地址的信 息。因此, f o r循环执行两次,每次产生一个消息。如果命令是 R T M _ A D D,则第一个消息的 类型是 R T M _ N E W A D D R,第二个消息的类型是 R T M _ A D D;如果命令是 R T M _ D E L E T E,则第一 个消息的类型是 R T M _ D E L E T E,第二个消息的类型是 R T M _ D E L A D D R 。R T M _ N E W A D D R和 R T M _ D E L A D D R消息的首部为 i f a _ m s g h d r结构,而 R T M _ A D D和R T M _ D E L E T E消息的首部为 rt_msghdr结构。 583 rt_addrinfo结构被设置为0。 2. 产生至多含四个地址的消息

505

第 19 章 选路请求和选路消息计计

588-591

这四个插口地址结构包含的是有关被添加或删除的接口地址的信息,它们的指针

存储于 r t i _ i n f o数组中。其中 i f a a d d r、i f p a d d r、n e t m a s k和b r d a d d r引用的是名为 i n f o的r t i _ i n f o数组中的成员,如图 19-19所示。r t _ m s g1在mbuf链中创建了相应的消息。 注意, s a也设置成指向 i f a _ a d d r结构的指针,我们将在函数的尾部看到选路消息的协议被 设置成该插口地址结构的族。 把 i f a _ m s g h d r 结构的其他成员设置成接口的索引、度量和标志。其比特掩码由 rt_msg1设置。 图1 9 - 2 9给出了 r t _ n e w a d d r m s g的后半部分。该部分用于创建 r t _ m s g h d r消息,该消 息中包含了有关被添加或删除的路由表项的信息。 3. 创建消息 600-609

r t _ m a s k、r t _ k e y和r t _ g a t e w a y这三个地址结构的指针存放在 r t i _ i n f o数

组中。 s a被设置成目的地址的指针,它的族将成为选路消息的协议。 r t _ m s g1在mbuf链中创 建相应的消息。 设置其余的 rt_msghdr结构成员。其中,比特掩码由 rt_msg1设置。 4. 设置消息的协议,调用 raw_input 616-619 设置消息的协议,并由 raw_input把消息发送给相应的监听器。函数在完成了两 次循环后返回。

图19-29 rt_newaddrmsg

函数的后半部分:创建 rt_msghdr 消息

19.12 rt_msg1函数 前三节的函数都调用 r t _ m s g 1函数来创建一个相应的选路消息。图 1 9 - 2 5还给出了一个 m b u f链,该链是由 r t _ m s g1按照图1 9 - 2 2中的r t _ m s g h d r和r t _ a d d r i n f o结构创建的。图 19-30给出了本函数的代码。

506

计计TCP/IP详解 卷 2:实现

图19-30 rt_msg1 函数:获取并初始化 mbuf

1. 得到mbuf并确定消息首部的长度 399-422

获得一个含分组首部的 mbuf,并将分组消息定长部分的长度存入 l e n中。图18-9

中各种类型的消息里,有两个使用 i f a _ m s g h d r结构,有一个使用 i f _ m s g h d r结构,其余的 九个使用rt_msghdr结构。

507

第 19 章 选路请求和选路消息计计

2. 验证结构是否适合 mbuf 423-424

定长结构的大小必须完全适合分组首部 m b u f的数据部分,因为该 m b u f指针将被

m t o d转换成一个结构指针,之后通过指针来引用该结构。三个结构中最大的为 i f _ m s g h d r, 其长度为84,小于MHLEN(100)。 3. 初始化mbuf分组首部并使结构清零 425-428 初始化分组首部的两个字段,并将 mbuf中的结构设置成 0。 4. 将插口地址结构复制到 mbuf链中 429-436

调用者传递了一个 r t _ a d d r i n f o结构的指针。与 r t i _ i n f o中所有非空指针相

对应的插口地址结构都被 m _ c o p y b a c k复制到 m b u f里。将数值 1左移下标 R T X _x x x对应的位 数就可以得到相应的 R T A _ x x x 比特掩码 ( 图 1 9 - 1 9 ) 。将每个比特掩码用逻辑或添加到 r t i _ a d d r s成员中去,调用者在函数返回时可将它保存为相应的报文结构成员。 R O U N D U P 宏将每个插口地址结构的大小上舍入成下一个 4的倍数个字节。 437-440

在循环结束时,如果 mbuf分组首部的长度不等于 len,则表明函数m_copyback

没能获得所需的 mbuf。 5. 保存长度、版本和类型 441-445

把长度、版本和报文类型保存到报文结构的前三个成员中。再次说明一下,因为

所有的三种 x x x_ m s g h d r 结构都以相同的成员开始,所以尽管代码中的指针

r t m 是一个

rt_msghdr结构的指针,但它可以处理所有这三种情况。

19.13 rt_msg2函数 r t _ m s g 1在m b u f链中创建一个选路消息,调用它的三个函数接着又调用 r a w _ i n p u t, 从而把 m b u f结构附加到一个或多个插口的接收缓存中去。与 r t _ m s g 1不同, r t _ m s g 2在存 储器缓存中创建选路消息,而不是在 m b u f链中创建。并且 r t _ m s g 2有一个 w a l k a r g结构的 参数,在选路域中处理 s y s c t l系统调用时,有两个函数使用该参数调用了 r t _ m s g 2。以下 是两种调用 rt_msg2的情况: 1) route_output调用它处理 RTM_GET命令 2) sysctl_dumpentry和sysctl_iflist调用它处理 sysctl系统调用。 在给出rt_msg2的代码之前,图19-31给出了在第 2种情况下使用的 walkarg结构的定义。 我们在遇到它的各成员时再一一介绍。

图19-31 walkarg 结构:选路域内 sysctl 系统调用中使用

图19-32给出了rt_msg2函数的前半部分,它与 rt_msg1的前半部分类似。

508

计计TCP/IP详解 卷 2:实现

图19-32 rt_msg2 函数:复制插口地址结构

446-455

本函数将选路消息保存在一个存储器缓存中,调用者用 c p参数指定该缓存的起始

位置。调用者必须保证缓存足够长,以容纳所产生的选路消息。当 c p参数为空时, r t _ m s g2 不保存任何结果而是处理输入参数,并返回保存结果所需要的字节总数。这样可以帮助调用 者确定缓存的大小。我们可以看到 r o u t e _ o u t p u t就利用了这一机制,它调用本函数两次: 第一次确定缓存的大小;在获得了大小无误的缓存后,再次调用本函数以保存结果。 r o u t e _ o u t p u t调用本函数时,最后一个参数为空,但如果是作为 s y s c t l系统调用处理的 一部分被调用时,该参数就不是空指针了。 1. 确定结构的大小 458-470

定长消息结构的大小是根据消息类型来确定的。如果 c p指针非空,则把大小等于

定长消息结构长度的偏移量添加到 cp指针上去。 2. 复制插口地址结构

509

第 19 章 选路请求和选路消息计计

471-482

for循环查看rti_info数组的每个元素。遇到非空指针时,设置 rti_addrs比

特掩码中的相应比特,并将该插口地址结构复制到 cp中(如果cp指针非空),并修改长度变量。 图19-33给出了rt_msg2函数的后半部分。其代码用于处理可选参数 walkarg结构。

图19-33 rt_msg2 函数:处理可选参数 walkarg

483-484 当调用者传递了一个非空的 walkarg结构指针且函数第一次执行到这里时,该 if 语句的判断条件才为真。变量 s e c o n d _ t i m e被初始化成 0,它将在本 i f语句中被设置成 1, 然后程序往回跳转到图 1 9 - 3 2中的标号 a g a i n处。c p为空指针的测试是不必要的,因为当 w指 针非空时, cp指针一定是空,反之亦然。 3. 检查是否要保存数据 485-486

w _ n e e d e d将增大,其增量为报文长度的值。该变量的初始值为 0减去s y s c t l函

数中用户缓存的长度。例如,如果该缓存为 500比特,则w _ n e e d e d的初始值为 -5 0 0。只要该 变量为负值,则表明缓存内还有剩余空间。在调用进程中, w _ w h e r e是指向该缓存的指针。 w _ w h e r e为空表明调用进程不想要函数的处理结果,而仅想获得 s y s t c l处理结果的大小。 因此,当 w _ w h e r e为空时, r t _ m s g 2没有必要将数据复制给进程,也就是返回给调用者。同 样,r t _ m s g 2也没有必要为保存结构而申请缓存,也不需要返回到标号 a g a i n处再次执行, 因为再次执行是为了把结果填入到缓存里。本函数的处理实际上只有五种情况,如图 1 9 - 3 4所 示。 4. 第一次调用时或消息长度增加时分配缓存 487-493 w_tmemsize是w_tmem所指向的缓存的长度。它被sysctl_rtable初始化成0。

510

计计TCP/IP详解 卷 2:实现

因此,对于给定的 s y s c t l请求,在第一次调用 r t _ m s g 2时,必须为它分配一个缓存。同样, 当产生的结果的长度增加时,必须释放原有的缓存,并重新分配一个更大的缓存。 调 用 者

cp

route_output

空 非空

空 空

空 空 非空

非空 非空 非空

sysctl_rtable

w

w.w_where

second_time





希望返回长度 希望返回结果 空 非空 非空

0 0 1

进程希望返回长度 第一次执行,计算长度 第二次执行,保存结果

图19-34 rt_msg2 函数的五种执行情况

5. 返回再执行一次并保存结果 494-499 如果w_tmemsize非空,则表明该缓存已经存在或刚被分配。设置 cp指向该缓存, 把s e c o n d _ t i m e设置成1,跳转至标号 a g a i n处。因为s e c o n d _ t i m e的值为1,所以第二次 执行到本图的第一个语句时, i f 语句的判断不再为真。如果 w _ t m e m 为空,则表明调用 malloc失败,因此,把进程中的缓存指针设置成空指针以阻止返回任何结果。 6. 保存长度、版本和类型 502-509 如果cp非空,则保存消息首部的前三个成员。函数返回报文的长度。

19.14 sysctl_rtable函数 本函数处理选路插口的 s y s c t l系统调用。如图 1 8 - 11所示, n e t _ s y s c t l函数调用了该 函数。 在解释其源代码之前,图 1 9 - 3 5给出了该系统调用关于路由表的一种典型的用法。这个例 子来自于 arp程序。

图 19-35

511

第 19 章 选路请求和选路消息计计

mib数组的前三个元素引导内核调用 sysctl_rtable,以处理其余的元素。 mib[4]用于指定操作的类型,共支持 3种操作类型。 1) N E T _ R T _ D U M P:返回 m i b [3]指定的地址族所对应的路由表。如果地址族为 0,则返 回所有的路由表。 针对每一个路由表项,程序都将返回一个 R T M _ G E T选路消息。每个消息可能包含两个、 三个或四个插口地址结构。这些地址结构由指针 r t _ k e y、r t _ g a t e w a y、r t _ n e t m a s k和 rt_genmask所指向。其中最后两个指针可能为空。 2) N E T _ R T _ F L A G S:与前一个命令相同,但 m i b [5]指定了一个 R T F _xxx标志(图18-25), 程序仅返回那些设置了该标志的表项。 3) NET_RT_IFLIST:返回所有已配置接口的信息。如果 mib[5]的值不是零,则程序仅 返回if_index为相应值的接口。否则,返回所有 ifnet链表上的接口。 针对每个接口,将返回一个 R T M _ I F I N F O消息,该消息传递了有关接口本身的一些信息。 之后用一个 R T M _ N E W A D D R消息传递接口的 i f _ a d d r l i s t上的每个 i f a d d r 结构。如果 mib[3]的值为非0,则RTM_NEWADDR消息仅返回那些地址族与 mib[3]相匹配的地址。否则, mib[3]为0,将返回所有地址的信息。 这个操作是为了替代 S I O C G I F C O N F i o c t(图4 l -26) 与该系统调用有关的一个问题是,该系统返回信息的数量是可变的,这种变化取决于路 由表表项的数目和接口的数目。因此,第一次调用 s y s c t l所指定的第三个参数通常是个空指 针,也就是说,不需要返回任何选路信息,只要把该信息所占的比特数目返回即可。从图 1 9 3 5中我们可以看出,进程第一次调用 s y s c t l之后调用了 m a l l o c,接着再调用 s y s c t l来获 取信息。第二次调用时,通过第四个参数, s y s c t l又返回了该比特数目 (该数目与上次相比 可能会有变化 )。通过该数目我们可以得到指针 l i m,它指向的位置位于返回的最后一个字节 之后。进程接着就遍历缓存中的每个消息,利用 rtm_msglen可找到下一个消息。 图19-36给出了不同的Net/3程序访问路由表和接口列表时指定的这六个 mib变量的值。

图19-36 调用sysctl 获取路由表和接口列表的程序举例

前三个程序从路由表中提取路由表项,后三个从接口列表中提取数据。 r o u t e程序仅支 持I n t e r n e t选路协议,所以它指定 m i b [3]的值为 A F _ I N E T,而g a t e d还支持其他协议, 所以它对应的mib[3]的值为0。 图19-37画出了三个 sysctl_xxx函数的结构,在后面的几节中会逐个予以阐述。 图19-38给出了sysctl_rtable函数。 1. 验证参数 705-719 当进程调用 sysctl来设置一个路由表中不支持的变量时,使用 new参数。因此该 参数必须是一个空指针。

512

计计TCP/IP详解 卷 2:实现

720-721

n a m e l e n必须是 3,因为系统调用处理到这儿时, n a m e 数组中有三个元素:

n a m e [0],地址族(进程中它被指定为 m i b [3]);n a m e [1],操作(m i b [4]);以及n a m e [2], 标志(mib[5])。 系统调用

针对每一个被 选中的叶子

在缓存中创建路由报 文并将之复制给进程

图19-37 支持针对选路插口的 sysctl 系统调用的函数

图19-38 sysctl_rtable

函数:处理 sysctl 系统调用请求

513

第 19 章 选路请求和选路消息计计

图19-38 (续)

2. 初始化walkarg结构 723-728

把w a l k a r g结构(图19-31)设置成0,并初始化下列成员:把 w _ w h e r e设置成调用

进程中为结果准备的缓存地址; w _ g i v e n是该缓存的比特数目 (当w _ w h e r e为空指针时,作 为输入参数,该成员没有实际含义,但在输出时它必须被设置成将要返回的结果的长度

);

w _ n e e d e d被设置成缓存的大小的负数; w _ o p指明操作类型 (值为N E T _ R T _x x x);w _ a r g被 设置成标志值。 3. 导出路由表 731-738

N E T _ R T _ D U M P和N E T _ R T _ F L A G S操作的处理是相同的:利用一个循环语句遍历

所有的路由表 (r t _ t a b l e数组),如果系统使用了该路由表,并且地址族调用参数为 0或地址 族调用参数与该路由表的族相匹配,则调用 r n h _ w a l k t r e e函数来处理整个路由表。图 1 8 1 7所给出的 r n h _ w a l k t r e e函数是通常使用的 r n _ w a l k t r e e函数。该函数的第二个参数的 值是另一个函数的地址,这个函数 (s y s c t l _ d u m p e n t r y)将被调用以处理选路树的每一个叶 子。r n _ w a l k t r e e 的第三个参数是个任意类型的指针,该指针将传递给 s y s c t l _ d u m p e n t r y 函数。在这里,它指向一个 w a l k a r g 结构,该结构包含了有关 sysctl调用的所有信息。

514

计计TCP/IP详解 卷 2:实现

4. 返回接口列表 739-740 NET_RT_IFLIST操作调用sysctl_iflist函数来处理所有的 ifnet结构。 5. 释放缓存 743-744 如果由rt_msg2分配的缓存里含有选路消息,则释放该缓存。 6. 更新w_needed 745

r t _ m s g 2函数把每个消息的长度都加入到 w _ n e e d e d中。而该变量被我们初始化成

w_given的负数,所以它的值可以表示成: w_needed = 0 - w_given + totalbytes

式中, t o t a l b y t e s表示由r t _ m s g 2函数添加的所有消息的长度总和。通过往 w _ n e e d e d中 加入w_given的值,我们就能得到所有消息的字节总数: w_needed = 0 - w_given + totalbytes + w_given = totalbytes

因为等式中两个 w _ g i v e n的值最终相互抵消,所以当进程所指定的 w _ w h e r e是个空指针 时,就没有必要初始化 w_given的值。事实上,图 19-35中的变量needed就没有被初始化。 7. 返回报文的实际长度 746-749

如果w h e r e指针非空,则通过 g i v e n指针返回保存在缓存中的字节数。如果返回

的数值小于进程指定的缓存的大小,则返回一个差错,因为返回信息被截短了。 8. 返回报文长度的估算值 当w h e r e 指针为空时,进程只想获得要返回的字节总数。为了防止在两次

750-752

s y s c t l调用之间相应的表被增大,我们将该字节总数扩大了 1 0 %。1 0 %这个增量的确定没有 特定的理由。

19.15 sysctl_dumpentry函数 在前一节中我们阐述了被 s y s c t l _ r t a b l e调用的r n _ w a l k t r e e是如何调用本函数的。 图19-39给出了本函数的代码。 623-630

每次调用本函数时,第一个参数指向一个 r a d i x _ n o d e结构,同时它也是一个

rtentry结构的指针,第二个参数指向一个由 sysctl_rtable初始化了的 walkarg结构。 1. 检测路由表项的标志 631-632 如果进程指定了标志值(mib[5]),则忽略那些rt_flags成员中没有设置该标志的 表项。在图19-36中,我们可以看到arp程序使用这种方法来选择那些设有 RTF_LLINFO标志的 表项,因为 ARP仅对这些表项感兴趣。 2. 构造选路消息 633-638

r t i _ i n f o数组中的下列四个指针是从路由表项中复制而得的: d s t、g a t e、

n e t m a s k和g e n m a s k。前两个总是非空的,但另外两个可以是空指针。调用 r t _ m s g 2是为 了构造一个 RTM_GET消息。 3. 复制消息回传给进程 639-651

如果进程需要返回一个报文,并且 rt_msg2分配了一个缓存,则将选路报文中的

其余部分填写到 w _ t m e m所指向的缓存中去,并调用 c o p y o u t复制消息回传给进程。如果复 制成功,就增大 w_where,增加的数目等于所复制的字节的数目。

515

第 19 章 选路请求和选路消息计计

图19-39 sysctl_dumpentry

函数:处理一个路由表项

19.16 sysctl_iflist函数 图1 9 - 4 0给出了本函数的代码。本函数由 s y s c t l _ r t a b l e直接调用,用来把接口列表返 回给进程。 本函数由一个 f o r循环组成,该循环从指针 i f n e t开始,针对每个接口重复执行。接着 用 w h i l e 循环处理每个接口的 i f a d d r 结构链表。函数将针对每个接口产生一个 RTM_IFINFO选路消息,并且针对每个地址产生一个 RTM_NEWADDR消息。 1. 检测接口索引 654-666

进程可以指定一个非零的标志参数 (图1 9 - 3 6 中的 m i b [ 5] )。函数用接口的

if_index值与之比较,只有匹配时,才进行处理。 2. 创建选路消息 667-670

在R T M _ I F I N F O消息中只返回了唯一的一个插口地址结构,即 i f p a d d r。该

R T M _ I F I N F O消息是由 r t _ m s g 2创建的。 i n f o结构中的 i f p a d d r指针被设置成 0,因为该 info结构还要用来产生后面的 RTM_NEWADDR消息。 3. 复制消息回传给进程 671-681

如果进程需要返回消息,则填入 i f _ m s g h d r结构的其余部分,用 c o p y o u t给进

516

计计TCP/IP详解 卷 2:实现

程复制该缓存,并增大 w_where。

图19-40 sysctl_iflist

函数:返回接口列表及其地址

4. 循环处理每一个地址结构,检测其地址族 682-684

处理接口的每一个 i f a d d r结构。进程也可以指定一个非零地址族 (图1 9 - 3 6中的

517

第 19 章 选路请求和选路消息计计

mib[3])来选择仅处理那些指定族的接口地址。 5. 创建选路消息 685-688

r t _ m s g 2创建的每个 R T M _ N E W A D D R消息中最多可以返回三个插口地址结构:

ifaddr、netmask和brdaddr。 6. 复制消息回传给进程 689-699

如果进程需要返回消息,则填入 ifa_msghdr结构的其余部分,用 copyout给进

程复制缓存,并增大 w_where。 701 因为info数组还要在下一个接口消息中使用,所以程序将其中的这三个指针设置成 0。

19.17 小结 所有选路消息的格式都是相同的 — 一个定长的结构,后面跟着若干个插口地址结构。 共有三种不同类型的消息,各自具有相应的定长结构,每种定长结构的前三个元素都分别标 识消息的长度、版本和类型。每种结构中的比特掩码指定哪些插口地址结构跟在定长结构之 后。 这些消息以两种方式在内核与进程之间传递。消息可以在任意一个方向上传递,并且都 是通过选路插口每次读或写一个消息。这就使得一个超级用户进程对内核路由表的访问具有 完全的读写能力。选路守护进程 (如routed和gated)就是这样实现其期望的选路策略的。 另外,任何一个进程都可以用 s y s c t l系统调用来读取内核路由表的内容。这种方法不需 要涉及选路插口,也不需要特别的权限。最终的结果通常包含许多选路消息,该结果作为系 统调用的一部分被返回。由于进程不知道结果的大小,因此,为系统调用提供了一种方法来 返回结果的大小而不返回结果本身。

习题 19.1 R T F _ D Y N A M I C和R T F _ M O D I F I E D标志之间有什么区别?对于一个给定的路由表 项,它们可以同时设置吗? 19.2 当用下列命令添加默认路由时会有什么情况发生? bsdi $ route add default -cloning -genmask 255.255.255.255 sun

19.3 某路由表包含了 1 5个A R P表项和2 0个路由,试估算用 s y s c t l导出该路由表时需要 多少空间。

第20章 选 路 插 口 20.1 引言 一个进程使用选路域 (routing domain)中的一个插口来发送和接收前一章所描述的选路报 文。socket系统调用需要指定一个 PF_ROUTE的族类型和一个 SOCK_RAW的插口类型。 接着,该进程可以向内核发送以下五种选路报文: 1) RTM_ADD:增加一条新路由。 2) RTM_DELETE:删除一条已经存在的路由。 3) RTM_GET:取得有关一条路由的所有信息。 4) RTM_CHANGE:改变一条已经存在路由的网关、接口或者度量。 5) RTM_LOCK:说明内核不应该修改哪个度量。 除此之外,该进程可以接收其他七个选路报文,这些报文是在发生某些事件时,如接口 下线和收到重定向报文等等,由内核生成的。 本章简介选路域、为每个选路插口创建的选路控制块、处理进程产生的报文的函数 (r o u t e _ o u t p u t)、发送选路报文给一个或多个进程的函数 (r a w _ i n p u t)、以及不同的支持 一个选路插口上所有插口操作的函数。

20.2 routedomain和protosw结构 在描述选路插口函数之前,我们需要讨论有关选路域的其他一些细节;在选路域中支持 的SOCK_RAW协议;以及每个选路插口所附带的选路控制块。 图20-1列出了称为 routedomain的PF_ROUTE域的domain结构。 成



dom_family dom_name dom_init dom_externalize dom_dispose dom_protosw dom_protoswNPROTOSW dom_next dom_rtattch dom_rtoffset dom_maxrtkey

值 PF_ROUTE route route_init 0 0 routesw

0 0 0





域的协议族 名字 域的初始化,图18-30 在选路域中不使用 在选路域中不使用 协议交换结构,图 20-2 指向协议交换结构之后的指针 由d o m a i n i n i t填入,图7-15 在选路域中不使用 在选路域中不使用 在选路域中不使用

图20-1 routedomain 结构

与支持多个协议 ( T C P 、 U D P 和 I C M P 等 )的 I n t e r n e t 域不一样,在选路域中只支持 SOCK_RAW类型的一种协议。图 20-2列出了PF_ROUTE域的协议转换项。

519

第20章 选 路 插 口计计





pr_type pr_domain pr_protocol pr_flags pr_input pr_output pr_ctlinput pr_ctloutput pr_usrreq pr_init pr_fasttimo pr_slowtimo pr_drain pr_sysctl



routesw[0] SOCK_RAW &routedomain 0 PR_ATOMI|PR_ADDR raw_input route_output raw_ctlinput 0 route_usrreq raw_init 0 0 0 sysctl_rtable



原始插口 选路域部分 插口层标志,协议处理时不使用 不使用这项, r a w _ i n p u t直接调用 P R U _ S E N D请求所调用 控制输入函数 不使用 对一个进程通信请求的响应 初始化 不使用 不使用 不使用 用于s y s c t l(8)系统调用

图20-2 选路协议 protosw 的结构

20.3 选路控制块 每当采用如下形式的调用创建一个选路插口时, socket(PF_ROUTE, SOCK_RAW, protocol);

对协议的用户请求函数 (r o u t e _ u s r r e q)的一个对应的 P R U _ A T T A C H请求分配一个选路 控制块,并且将它链接到插口结构上。 p ro t o c o l参数可以将发送给这个插口上的进程的报文类 型限制为一个特定族。例如,如果将 protocol参数说明为 A F _ I N E T,只有包含了I n t e r n e t地 址的选路报文将被发送给这个进程。 p ro t o c o l参数为 0将使得来自内核的所有选路报文都发送 给这个插口。 记住我们把这些结构称为选路控制块,而不是原始控制块 (raw control block) , 是为了避免与第 32章中的原始 IP控制块相混淆。 图20-3显示了rawcb结构的定义。

图20-3 rawcb 结构

另外,分配了一个相同名字的全局结构, r a w c b,作为这个双向链表的头。图 2 0 - 4显示 了这种情况。 39-47

我们在图 1 9 - 2 6中显示了 s o c k p r o t o的结构。它的 s p _ f a m i l y成员变量被设置为

P F _ R O U T E , s p _ p r o t o c o l 成员变量被设置为 s o c k e t 系 统 调 用 的 第 三 个 参 数 。

520

计计TCP/IP详解 卷 2:实现

r c b _ f a d d r成员变量被永久性地设置为指向 r o u t e _ s r c的指针,我们在图 1 9 - 2 6中描述了 route_src。rcb_laddr总是一个空指针。 描述符

描述符

插口层 协议层

所有选路控制块的 双向链接循环列表

图20-4 原始协议控制块与其他数据结构的关系

20.4 raw_init函数 在图2 0 - 5中显示的 r a w _ i n i t函数是图 2 0 - 2中的p r o t o s w结构的协议初始化函数。我们 在图18-29中描述了选路域的完整初始化过程。 38-42

这个函数将头结构的下一个和前一个指针设置为指向自身来对这个双向链表进行初

始化。

图20-5 raw_init 函数:初始化选路控制块的双向链表

20.5 route_output函数 如同我们在图 1 8 - 11所显示的,当给协议的用户请求函数发送 P R U _ S E N D请求时,就会调

521

第20章 选 路 插 口计计

用r o u t e _ o u t p u t,这是一个进程向一个选路插口进行写操作所引起的。在图 1 8 - 9中,我们 给出了内核接受的、由进程发出的五种不同类型的选路报文。 因为这个函数是由一个进程的写操作激活的,来自于该进程的数据 (发送给进程的选路报 文)被放在一个由 s o s e n d开始的m b u f链中。图 2 0 - 6显示了大概的处理步骤,假定进程发送了 一个R T M _ A D D命令,说明三个地址:目的地址、它的网关和一个网络掩码 (因此,这是一个网 络路由,而不是一个主机路由 )。 进程

进程

进程 raw_input 选择的进程

mbuf链

mbuf链

进程写 的数据 目的IP地址

网关IP地址

网络掩码

从rtm_addrs位掩码构 造的rt_addrs指针数组

图20-6 一个进程发出的RTM_ADD 命令的处理过程示例

在这个图中有几点需要引起注意,我们在介绍 r o u t e _ o u t p u t的源代码时讨论了这里需 要注意的大多数情况。另外,为了节省空间,我们省略了 r t _ a d d r i n f o结构中每个数组下 标的RTAX_前缀。 • 进程通过设置比特掩码 r t m _ a d d r s来说明在定长的 r t _ m s g h d r结构之后有哪些插口地

522

计计TCP/IP详解 卷 2:实现

址结构。我们显示了一个值为 0 x 0 7的比特掩码,表示有一个目的地址、一个网关地址 和一个网络掩码 (图1 9 - 1 9 )。R T M _ A D D命令需要前两个地址;第三个地址是可选的。另 一个可选的地址, genmask说明了用来产生克隆路由的掩码。 • write系统调用(sosend函数)将来自进程的一个缓存数据复制到内核的一个 mbuf链中。 • m _ c o p y d a t a将m b u f链中数据复制到 r o u t e _ o u t p u t使用m a l l o c获得的一个缓存中。 访问存储在单个连续缓存中的结构以及结构后面的若干插口地址结构,比访问一个 mbuf 链更容易。 • r o u t e _ o u t p u t 调用 r t _ x a d d r s函数取得比特掩码,并且构造一个指向缓存的 r t _ a d d r i n f o结构。 r o u t e _ o u t p u t中的代码使用图 1 9 - 1 9中第五栏显示的名字来引 用这些结构。比特掩码也要复制到 rti_addrs成员中。 • r o u t e _ o u t p u t一般要修改r t _ m s g h d r结构。如果发生了一个错误,相应的 e r r n o值 被返回到 r t m _ e r r n o 中 ( 例如,如果路由已经存在,则返回

E E X I S T ) ;否则,

RTF_DONE标志与进程提供的 rtm_flags执行逻辑或操作。 • r t _ m s g h d r结构以及接着的地址成为 0个或多个正在读选路插口的进程的输入。首先使 用m _ c o p y b a c k将缓存转换为一个 m b u f链。r a w _ i n p u t经过所有的选路 P C B,并且传 递一个复制给对应的进程。我们还显示了一个带有选路插口的进程,如果该进程没有禁 止SO_USELOOPBACK插口选项,就会收到它写给那个插口的每个报文的一个复制。 为了避免收到它们自己的选路报文的一个复制,有些程序,如 r o u t e,将第二 个参数置为 0来调用shutdown,以防止从选路插口上收到任何数据。 我们分成七个部分分析 route_output的源代码。图20-7显示了这个函数的大概流程。

图20-7 route_output

处理步骤小结

523

第20章 选 路 插 口计计

图20-7 (续)

route_output的第一部分显示在图 20-8中。

图20-8 route_output

函数:初始化处理,从 mbuf链中复制报文

524

计计TCP/IP详解 卷 2:实现

图20-8 (续)

1. 检查mbuf的合法性 113-136

检查mbuf的合法性:它的长度必须至少是一个 rt_msghdr结构的大小。从 mbuf

的数据部分取出第一个长字,里面包含了 rtm_msglen的值。 2. 分配缓存 137-142 分配一个缓存来存放整个报文, m_copydata将报文从mbuf链复制到缓存。 3. 检查版本号 143-146

检查报文的版本号。如果将来引入了新版本的选路报文,这个成员变量可以用来

对早期版本提供支持。 147-149 进程的ID被复制到rtm_pid,进程提供的比特掩码被复制到该函数的一个内部结 构i n f o.r t i _ a d d r s。函数r t _ x a d d r s(在下一节显示 )填入i n f o结构的第 8个插口地址指针 来指示当前包含报文的缓存。 4. 需要的目的地址 150-151

所有的命令都需要一个目的地址。如果 i n f o.r t i _ i n f o [ R T A X _ D S T ]项是一个

空指针,就需要一个 EINVAL。记住dst引用了这个数组成员 (图19-19)。 5. 处理可选的 genmask 152-159 genmask是可选的,它是在设置了 RTF_CLONING标志后(图19-8),用作所创建路 由的网络掩码。 r n _ a d d m a s k将这个掩码加入到掩码树中,并首先在掩码树中查找是否存在 与这个掩码相同的条目,如果找到,就引用那个条目。如果在掩码树中找到或者将这个掩码 加入到掩码树中,还要再检查一下掩码树中的那个条目是否真等于 genmask的值,如果等于, 则genmask指针就被替代为掩码树中那个掩码的指针。 图20-9显示了route_output函数处理RTM_ADD和RTM_DELETE的下一部分。 162-163 RTM_ADD命令要求进程说明一个网关。 164-165

r t r e q u e s t处理该请求。如果输入的路由是一个主机路由,则 n e t m a s k指针可

以为空。如果一切 OK,则saved_nrt返回新的路由表项的指针。 166-172

将r t _ m e t r i c s结构从调用者缓存中复制到路由表项中。引用计数减 1,并且保

存genmask指针(可能是一个空指针 )。 173-176

处理R T M _ D E L E T E命令非常简单,因为所有的工作都由 r t r e q u e s t来完成。既

然最后一个参数是一个空指针,如果引用计数为 0,r t r e q u e s t就调用r t f r e e从路由表中删 除指定的项 (图19-7)。 下一步的处理过程显示在图 20-10中,它显示了R T M _ G E T、R T M _ C H A N G E和R T M _ L O C K命

525

第20章 选 路 插 口计计

令的公共代码。

图20-9 route_output

图20-10 route_output

函数:进程 RTM_ADD 和RTM_DELETE 命令

函数:RTM_GET 、RTM_CHANGE 和RTM_LOCK 的公共处理部分

6. 查找已经存在的项 177-182

因为三个命令都引用了一个已经存在的项,所以用 rtalloc1函数来查找这个项。

如果没有找到,则返回一个 ESRCH。 7. 不允许网络匹配 183-187

对于R T M _ C H A N G E和R T M _ L O C K命令,一个网络匹配是不合适的:需要一个路由

表关键字的精确匹配。因此,如果 d s t参数不等于路由表关键字,这个匹配就是一个网络匹 配,返回一个ESRCH。

526

计计TCP/IP详解 卷 2:实现

8. 使用网络掩码来查找正确的项 188-193

即使是一个精确的匹配,如果存在网络掩码不同的重复表项,仍然必须查找正确

的项。如果提供了一个 n e t m a s k参数,就要在掩码表中查找它 (m a s k _ r n h e a d)。如果找到 了, n e t m a s k指针就被替代为掩码树中相应掩码的指针。检查重复表项列表的每个叶结点, 查找一个 r n _ m a s k指针等于 n e t m a s k的项。这个测试只是比较指针,而不是指针所指向的结 构。这是因为所有的掩码都出现在掩码树中,并且每个不同的掩码只有一个副本出现在这个 掩码树中。大多数情况下,表项不会重复,因此 f o r循环只执行一次。如果一个主机路由项 被修改了,不应该提供一个网络掩码,因此, n e t m a s k和r n _ m a s k都是空指针 (两者是相等 的)。但是,如果有一个附带掩码的项被修改了,那个掩码必须作为 netmask参数提供。 194-195 如果for循环终止时没有找到一个匹配的网络掩码,就返回 ETOOMANYREFS。 注释XXX表示这个函数必须做所有的工作来找到需要的项。所有这些细节在其他一些 类似rtalloc1的函数中都应该被隐藏,rtalloc1检测网络匹配,并且处理掩码参数。 这个函数的下一部分继续处理

R T M _ G E T 命令,显示在图 2 0 - 1 1 中。这个命令与

图20-11 route_output

函数:RTM_GET 处理过程

527

第20章 选 路 插 口计计

r o u t e _ o u t p u t支持的其他命令的区别在于它能够返回比传递给它的更多的数据。例如,只 需要输入一个插口地址结构,即目的地址,但至少返回两个插口地址结构,即目的地址和它 的网关。参看图 20-6,这就意味着为 m_copydata复制数据所分配的缓存可能需要扩充大小。 9. 返回目的地址、网关和掩码 198-203

r t i _ i n f o数组中存储了四个指针: d s t、g a t e、n e t m a s k和g e n m a s k。后两

个可能是空指针。 info结构中的这些指针指向进程将要返回的各个插口地址结构。 10. 返回接口信息 204-213

进程可以在 r t m _ f l a g s比特掩码中设置 R T A _ I F P和R T A _ I F A掩码。如果设置了

一项或两项,就表示进程想要接收这个路由表项所指示的 i f a d d r结构:接口的链路层地址 (由r t _ i f p-> a d d r l i s t指向)以及这个路由项的协议地址 (由r t _ i f a-> i f a _ a d d r指向)的内 容。接口索引也会被返回。 11. 构造应答报文 214-224

将第三个指针置为空,调用 r t _ m s g 2来计算相应于 R T M _ G E T的选路报文和 i n f o

结构所指向的地址的长度。如果结果报文的长度超过了输入报文的长度,就会分配一个新的 缓存,输入报文被复制到新的缓存中,老的缓存被释放, rtm被重新设置为指向新缓存。 225-230

再次调用rt_msg2,这次调用时第三个指针非空,因为在缓存中已经构造了一个

结果报文。这次调用填入 rt_msghdr结构的最后三个成员项。 图20-12显示了RTM_CHANGE和RTM_LOCK命令的处理过程。 12. 改变网关 231-233 如果进程传递了一个gate地址,rt_setgate就被调用来改变这个路由表项的网关。 13. 查找新的接口 234-244

新的网关 (如果被改变)可能也需要 r t _ i f p和r t _ i f a指针。进程可以通过传递一

个i f p a d d r插口地址结构或者一个 i f a a d d r插口地址结构来说明这些新的值。先看第一个, 然后再看第二个。如果进程两个结构都没传递, rt_ifp和rt_ifa指针就被忽略。 14. 检验接口是否改变 245-256

如果找到了一个接口 (i f a非空),则该路由的现有 r t _ i f a指针要和新的值进行比

较。如果数值已经改变了,则两个针对 r t _ i f p和r t _ i f a的新值需要存储到路由表的表项中 去。在这样做之前,先要用 R T M _ D E L E T E命令调用该接口的请求函数 (如果定义了该函数的 话)。这个删除动作是必须的,因为从一种类型的网络到另一种类型的网络,它们的链路层信 息可能会有很大的差别 (比如说从一个X.25网络改变成以太网的路由 ),同时我们还必须通知输 出例程。 15. 更新度量 257-258 rt_setmetrics修改路由表项的度量。 16. 调用接口请求函数 259-260 如果定义了一个接口请求函数,它就会和 RTM_ADD命令一起被调用。 17. 保存克隆生成的掩码 261-262

如果进程指定了 g e n m a s k 参数,就将在图 2 0 - 8 中获得的掩码的指针保存在

rt_genmask中。 18. 修改加锁度量的比特掩码

528

计计TCP/IP详解 卷 2:实现

266-270

R T M _ L O C K命令修改保存在 r t _ r m x.r m x _ l o c k s中的比特掩码。图 2 0 - 1 3显示了

这个比特掩码中不同比特的值,每个度量一个值。

图20-12 route_output

函数:RTM_CHANGE 和RTM_LOCK 处理过程

路由表项中 r t _ m e t r i c s结构的 r m x _ l o c k s成员是告诉内核哪些度量不要管的比特掩 码。即, r m x _ l o c k s指定的那些度量内核不能修改。内核惟一能使用这些度量的地方是和 T C P一起,如图 2 7 - 3所示。 r m x _ p k s e n t度量不能被初始化或加锁,但是内核也从来没有引 用或修改过这个成员。 进程发出的报文中的

r t m _ i n i t s 值是一个比特掩码,指出哪些度量刚刚被

529

第20章 选 路 插 口计计





RTV_MTU RTV_HOPCOUNT RTV_EXPIRE RTV_RPIPE RTV_SPIPE RTV_SSTHRESH RTV_RTT RTV_RTTVAR

值 0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80





初始化或者锁住 rmx_mtu 初始化或者锁住 rmx_hopcount 初始化或者锁住 rmx_expire 初始化或者锁住 rmx_recvpipe 初始化或者锁住 rmx_sendpipe 初始化或者锁住 rmx_ssthresh 初始化或者锁住 rmx_rtt 初始化或者锁住 rmx_rttvar

图20-13 对度量初始化或加锁的常量

r t _ s e t m e t r i c s初始化过。报文中的 r t m _ r m x.r m x _ l o c k s值是一个指出哪些度量现在应 该加锁的比特掩码。 r t _ r m x.r m x _ l o c k s的值是一个指出路由表中哪些度量当前被加锁的比 特掩码。首先,任何将要初始化的比特

( r t m _ i n i t s ) 都要解锁。任何既被初始化

(rtm_inits)又被加锁(rtm_rmx.rmx_locks)的比特都必须加锁。 273-275

这个d e f a u l t是用于图 2 0 - 9开始的s w i t c h语句,用来处理进程发出的报文中除

了所支持的五个命令以外的其他选路命令。 route_output的最后一部分显示在图 20-14中,用来发送应答给 raw_input。

图20-14 route_output

函数:将结果传递给 raw_input

530

计计TCP/IP详解 卷 2:实现

图20-14 (续)

19. 返回错误或 OK 276-282

flush是该函数开头定义的 senderr宏所跳转的标号。如果发生了一个错误,错

误就在rtm_errno成员中返回;否则,就设置 RTF_DONE标志。 20. 释放拥有的路由 283-284

如果拥有一条路由,就要被释放。如果找到,在图 20-10的开始位置对rtalloc1

的调用拥有这条路由。 21. 没有进程接收报文 285-296 SO_USELOOPBACK插口选项的默认值为真,表示发送进程将会收到它发送给选路 插口的每个选路报文的一个复制 (如果发送者不接收一个复制的报文,它就不能收到 R T M _ G E T 返回的任何信息 )。如果没有设置这个选项,并且选路插口的总数小于或等于 1,就没有其他 进程接收报文,并且发送者不想要一个复制报文。缓存和 mbuf链都会被释放,该函数返回。 22. 没有环回复制报文的其他监听者 297-299

至少有一个其他的监听者而不是发送进程不想要一个复制报文。指针 r p,默认是

空,被设置成指向发送者的选路控制块,它也用来作为发送者不想要复制报文的一个标志。 23. 将缓存转换成mbuf链 300-303 缓存被转换成一个 mbuf链(图20-6),然后释放缓存。 24. 避免环回复制 304-305

如果设置了rp,则某个其他的进程可能想要报文,但是发送者不想要一个复制。发

送者的选路控制块的sp_family成员被临时设置为0,但是报文的sp_family(route_proto 结构,显示在图 19-26中)有一个PF_ROUTE的族。这个技巧防止raw_input将结果的一个复制 传递给发送进程,因为raw_input不会将一个复制传递给sp_family为0的任何插口。 25. 设置选路报文的地址族 306-308

如果dst是一个非空的指针,则那个插口地址结构的地址族成为选路报文的协议。

对于Internet协议,这个值将是PF_INET。通过raw_input,一个复制被传递给合适的监听者。 309-313 如果调用进程的sp_family成员被临时设置为0,它就被复位成正常值,PF_ROUTE。

20.6 rt_xaddrs函数 在将来自进程的选路报文从

m b u f 链复制到一个缓存以及将来自进程的比特掩码

(r t m _ a d d r s)复制到r t _ a d d r i n f o结构的r t i _ i n f o成员之后,只从 r o u t e _ o u t p u t中调 用一次 r t _ x a d d r s 函数 (图2 0 - 8 ) 。r t _ x a d d r s的目的是获取这个比特掩码,并且设置 rti_info数组的指针,使之指向缓存中相应的地址。图 20-15显示了这个函数。 330-340 指针数组被设置成0,因此,所有在比特掩码中不出现的地址结构的指针都将是空。 341-347

测试比特掩码中 8个(R T M _ M A X)可能比特的每一个 (如果设置 ),将相应于插口地址

结构的一个指针存到 r t i _ i n f o数组中。 A D V A N C E宏以插口地址结构的 s a _ l e n字段为参数,

531

第20章 选 路 插 口计计

上舍入为4个字节的倍数,相应地增加指针 cp。

图20-15 rt_xaddrs 函数:将指针填入 rti_info 数组

20.7 rt_setmetrics函数 这个函数在 r o u t e _ o u t p u t中调用了两次:增加一条新路由时和改变一条已经存在的路 由时。来自进程的选路报文的 r t m _ i n i t s成员说明了进程想要初始化 r t m _ r m x数组中的哪 些度量。比特掩码中的比特的值显示在图 20-13中。 请注意, r t m _ a d d r s和r t m _ i n i t s都是来自进程的报文中的比特掩码,前者说明了接 下来的插口地址结构,而后者说明哪些度量将被初始化。为了节省空间,在 r t m _ a d d r s中没 有设置比特的插口地址结构也不会出现在选路报文中。但是整个 r t _ m e t r i c s总是以定长的 rt_msghdr结构的形式出现—在rtm_inits中没有设置比特的数组成员将被忽略。 图20-16显示了rt_setmetrics函数。

图20-16 rt_setmetrics

函数:设置 rt_metrics 结构中的成员

532

计计TCP/IP详解 卷 2:实现

314-318

w h i c h参数总是进程的选路报文的 r t m _ i n i t s 成员。 i n 指向进程的 r t _

metrics结构,而out指向将要创建或修改的路由表项的 rt_metrics结构。 319-329 测试比特掩码中8比特的每一比特,如果该比特被设置,就复制相应的度量。请注意 当使用RTM_ADD创建一个新的路由表项时, route_output调用了rtrequest,后者将整个 路由表项设置为0(图19-9)。因此,在选路报文中,进程没有说明的任何度量,其默认值都是0。

20.8 raw_input函数 向一个进程发送的所有选路报文—包括由内核产生的和由进程产生的—都被传递给 raw_input,后者选择接收这个报文的进程。图 18-11总结了调用 raw_input的四个函数。 当创建一个选路插口时,族总是 P F _ R O U T E;而协议,s o c k e t的第三个参数,可能为 0, 表示进程想要接收所有的选路报文;或者是一个如同 A F _ I N E T的值,限制插口只接收包含指 定协议族地址的报文。为每个选路插口创建一个选路控制块 ( 2 0 . 3节),这两个值分别存储在 rcb_proto结构的sp_family和sp_protocol成员中。 图20-17显示了raw_input函数。

图20-17 raw_input 函数:将选路报文传递给 0个或多个进程

533

第20章 选 路 插 口计计

图20-17 (续)

51-61

在我们所看到的四个对 r a w _ i n p u t的调用中, p r o t o、s r c和d s t参数指向三个全

局变量 r o u t e _ p r o t o、r o u t e _ s r c和r o u t e _ d s t,这些变量都如同图 1 9 - 2 6所示的那样被 声明和初始化。 1. 比较地址族和协议 62-67

f o r循环遍历每个选路控制块来查找一个匹配。控制块里的族 (一般是P F _ R O U T E)必

须与s o c k p r o t o结构的族相匹配,否则这个控制块就被略过。接下来,如果控制块里的协议 (s o c k e t的第三个参数 )非空,它必须匹配 s o c k p r o t o结构的族;否则,这个报文被略去。 因此,以0协议创建了一个选路插口的进程将收到所有的选路报文。 2. 比较本地的和外部的地址 68-81

如果指定了的话,这两个测试比较了控制块里的本地地址和外部地址。目前,进程

不能设置控制块的 r c b _ l a d d r或者r c b _ f a d d r成员。一般来说,进程使用 b i n d设置前者, 用c o n n e c t设置后者,但对于 N e t / 3中的选路插口这是不可能的。作为替代,我们将看到 r o u t e _ u s r r e q将插口固定地连接到 r o u t e _ s r c插口地址结构,这是可行的,因为它总是 这个函数的 src参数。 3. 将报文添加到插口的接收缓存中 82-107 如果last非空,它指向最近看到的应该接收这个报文的 socket结构。如果这个变 量非空,就使用 m _ c o p y和s b a p p e n d a d d r将这个报文的一个复制添加到那个插口的接收缓 存中,并且在这个接收缓存等待的任何进程都会被唤醒。然后, l a s t被设置成指向在以前的 测试中刚刚匹配的插口。使用 l a s t 是为了在只有一个进程接收报文的情况下避免调用 m_copy(一个代价昂贵的操作 )。 如果有N个进程接收报文,那么前 N-1个接收一个复制报文,最后一个进程收到的是这个 报文本身。 在这个函数里递增的 s o c k e t变量并没有被用到。因为只有当报文被传递给一个进程后它 才会被递增,所以,如果在函数的结尾这个变量的值是 0,就表示没有进程接收该报文 (但是

534

计计TCP/IP详解 卷 2:实现

变量值没有在任何地方保存 )。

20.9 route_usrreq函数 r o u t e _ u s r r e q是选路协议的用户请求函数。它被不同的操作调用。图 20-18显示了这个 函数。

图20-18 route_usrreq

函数:处理 PRU_ xxx请求

535

第20章 选 路 插 口计计

1. PRU_ATTACH:分配控制块 64-77

当进程调用s o c k e t时,就会发出 P R U _ A T T A C H请求。为一个选路控制块分配内存。

M A L L O C返回的指针保存在 s o c k e t结构的s o _ p c b成员中。如果分配了内存, r a w c b结构被 设置成0。 2. PRU_DETACH:计数器递减 78-87

c l o s e系统调用发出 P R U _ D E T A C H请求。如果 s o c k e t结构指向一个协议控制块,

r o u t e _ c b结构的计数器中有两个被减 1:一个是 a n y _ c o u n t;另一个是基于该协议的计数 器。 3. 处理请求 88-90 函数raw_usrreq被调用来进一步处理 PRU_xxx请求。 4. 计数器递增 91-104

如果 请求是 P R U _ A T T A C H , 并 且 插 口 指 向 一 个 选 路 控 制 块 , 就要检查

r a w _ u s r r e q是否返回一个错误。然后, r o u t e _ c b结构的计数器中的两个被递增:一个是 any_count,另一个是基于该协议的计数器。 5. 连接插口 105-106 选路控制块里的外部地址被设置成 route_src。这将永久地连接到新的插口来接 收PF_ROUTE族的选路报文。 6. 默认情况下使能 SO_USELOOPBACK 107-111 使能SO_USELOOPBACK插口选项。这是一个默认使能的插口选项—其他所有的 选项默认都被禁止。

20.10 raw_usrreq函数 r a w _ u s r r e q 完成在选路域中用户请求处理的大部分工作。在上一节中它被 r o u t e _ u s r r e q函数所调用。用户请求的处理被划分成这两个函数,是因为其他的一些协议 (例如OSI CLNP)调用r a w _ u s r r e q而不是 r o u t e _ u s r r e q。r a w _ u s r r e q并不是想要成为 一个协议的 p r _ u s r r e q函数,相反,它是一个被不同的 p r _ u s r r e q函数调用的公共的子例 程。 图2 0 - 1 9显示了r a w _ u s r r e q函数的开始和结尾。其中的 s w i t c h语句体在该图后面的图 中单独讨论。 1. PRU_CONTROL请求是不合法的 119-129 PRU_CONTROL请求来自于 ioctl系统调用,在路由选择域中不被支持。 2. 控制信息不合法 130-133

如果进程传递控制信息 (使用s e n d m s g系统调用 ),就会返回一个错误,因为路由

选择域中不使用这个可选的信息。 3. 插口必须有一个控制块 134-137

如果s o c k e t结构没有指向一个选路控制块,就返回一个错误。如果创建了一个

新的插口,调用者 (即r o u t e _ u s r r e q)有责任在调用这个函数之前分配这个控制块,并且将 指针保存在 so_pcb成员中。 262-269 这个switch语句的default子句处理case子句没有处理的两个请求:PRU_BIND

536

计计TCP/IP详解 卷 2:实现

和P R U _ C O N N E C T。这两个请求的代码是提供的,但在 N e t /3中被注释掉了。因此,如果在一 个选路插口上发出 b i n d或c o n n e c t系统调用,就会引起一个内核的告警 (panic)。这是一个程 序错误(bug)。幸运的是创建这种类型的插口需要有超级用户的权限。

图20-19 raw_usrreg 函数体

我们现在讨论单个的 c a s e语句。图 2 0 - 2 0显示了对 P R U _ A T T A C H和P R U _ D E T A C H请求的 处理。 139-148

P R U _ A T T A C H请求是s o c k e t系统调用的一个结果。一个选路插口只能由一个超

级用户的进程创建。 149-150

函数 r a w _ a t t a c h(图2 0 - 2 4 )将控制块链接到双向链接列表中。 n a m 参数是

socket的第三个参数,被存储在控制块中。 151-159

PRU_DETACH是由close系统调用发出的请求。对一个空的 rp指针的测试是多余

的,因为在 switch语句之前已经进行过这个测试了。 160-161 raw_detach(图20-25)从双向链接表中删除这个控制块。 图20-21显示了PRU_CONNECT2、PRU_DISCONNECT和PRU_SHUTDOWN请求的处理。 186-188

PRU_CONNECT2请求来自于 socketpair系统调用,在路由选择域中不被支持。

189-196 因为一个选路插口总是连接的(图20-18),所以PRU_DISCONNECT请求在PRU_DETACH请

537

第20章 选 路 插 口计计

图20-20 raw_usrreq 函数:PRU_ATTACH 和PRU_DETACH 请求

求之前由 c l o s e发出。插口必须已经和一个外部地址相连接,这对于一个选路插口来说总是 成立的。raw_disconnect和soisdisconnected完成这个处理。 197-202

当参数指定在这个插口上没有更多的写操作时,

s h u t d o w n 系统调用发出

PRU_SHUTDOWN请求。socantsendmore禁止以后的写操作。

图20-21 raw_usrreq 函数:PRU_CONNECT2

、PRU_DISCONNECT

和PRU_SHUTDOWN

请求

对一个选路插口最常见的请求:PRU_SEND、PRU_ABORT和PRU_SENSE显示在图20-22中。 203-217 当进程向插口写时, sosend发出了PRU_SEND请求。如果指定了一个 nam参数,

538

计计TCP/IP详解 卷 2:实现

图20-22 raw_usrreq 函数:PRU_SEND 、PRU_ABORT 和PRU_SENSE 请求

即进程使用 s e n d t o 或者 s e n d m s g 指定了一个目的地址,就会返回一个错误,因为 route_usrreq总是为一个选路插口设置 rcb_faddr。 218-222

m 指 向 的 m b u f链 中 的 信 息 被 传 递 给 协 议 的 p r _ o u t p u t 函 数 , 也 就 是

route_output。 223-227 如果发出了一个 PRU_ABORT请求,则该控制块被断开连接,插口被释放,然后被 断开连接。 228-232 fstat系统调用发出PRU_SENSE请求。函数返回 OK。 图20-23显示了剩下的PRU_xxx请求。

图20-23 raw_usrreq 函数:最后部分

539

第20章 选 路 插 口计计

图20-23 (续)

233-243 这五个请求不被支持。 244-261 PRU_SOCKADDR和PRU_PEERADDR请求分别来自于getsockname和getpeername系统调 用。前者总是返回一个错误,因为设置本地地址的bind系统调用在路由选择域中不被支持。后者 总是返回插口地址结构route_src的内容,这个内容是由route_usrreq作为外部地址设置的。

20.11 raw_attach、raw_detach和ra w _ d i s c o n n e c t函数 r a w _ a t t a c h函数,显示在图 2 0 - 2 4中,被 r a w _ i n p u t调用来完成 P R U _ A T T A C H请求的 处理。

图20-24 raw_attach 函数

540

计计TCP/IP详解 卷 2:实现

49-64

调用者必须已经分配了原始的协议控制块。 soreserve将发送和接收缓存的高水位

标记设置为 8192。这对于选路报文应该是绰绰有余了。 65-67

s o c k e t结构的一个指针和 d o m _ f a m i l y(即图20-1中用于选路域的 P F _ R O U T E)以及

proto参数(socket调用的第三个参数 )一起被存储到协议控制块中。 68-70 insque将这个控制块加入到由全局变量 rawcb作为头指针的双向链接表的前面。 r a w _ d e t a c h函数,显示在图 2 0 - 2 5中,被 r a w _ i n p u t调用来完成 P R U _ D E T A C H请求的 处理。

图20-25 raw_detach 函数

75-84

s o c k e t结构中的 s o _ p c b指针被设置成空,然后释放这个插口。使用 r e m q u e从双

向链接表中删除该控制块,使用 free来释放被控制块占用的内存。 r a w _ d i s c o n n e c t 函数,显示在图 2 0 - 2 6 中,被 r a w _ i n p u t 调用来完成 P R U _ DISCONNECT和PRU_ABORT请求的处理。 88-94 如果该插口没有引用一个描述符, raw_detach释放该插口和控制块。

图20-26 raw_disconnect

函数

20.12 小结 一个选路插口是 P F _ R O U T E域中的一个原始插口。选路插口只能被一个超级用户进程创 建。如果一个没有权限的进程想要读内核包含的选路信息,可以使用选路域所支持的 s y s c t l 系统调用(我们在前一章中描述过 )。 在本章中,我们第一次碰到了与插口相联系的协议控制块 ( P C B )。在选路域中,一个专门 的r a w c b包含了有关选路插口的信息:本地和外部的地址、地址族和协议。我们将在第 2 2章 中看到用于 U D P、T C P和原始 I P插口的更大的 I n t e r n e t协议控制块 (i n p c b)。然而概念是相同 的: s o c k e t 结构被插口层使用,而 P C B ,一个 r a w c b 或一个 i n p c b ,被协议层使用。 socket结构指向该 PCB,后者也指向前者。

541

第20章 选 路 插 口计计

route_output函数处理进程可以发出的五个请求。依赖于协议和地址族,raw_input将 一个选路报文发送给一个或多个选路插口。对一个选路插口的不同的

P R U _ x x x请求由

r a w _ u s r r e q和r o u t e _ u s r r e q处理。在后面的章节中,我们将碰到另外的 x x x_ u s r r e q函 数,每个协议 ( U D P、T C P和原始I P )对应一个,每个函数都由一个 s w i t c h语句组成用来处理 每一个请求。

习题 20.1 当进程向一个选路插口写一个报文时,列出两种进程可以从 r o u t e _ o u t p u t收到 返回值的方法。哪种方法更可靠? 20.2 因为 r o u t e s w结构的 p r _ p r o t o c o l成员为 0,所以当进程对 s o c k e t系统调用指 定了一个非 0的protocol参数时,会发生什么情况? 20.3 路由表中的路由 (和ARP项不同)永远不会超时。试在路由上实现一个超时机制。

第21章 ARP:地址解析协议 21.1 介绍 地址解析协议 ( A R P )用于实现 I P地址到网络接口硬件地址的映射。常见的以太网网络接口 硬件地址长度为 48 bit。A R P同时也可以工作在其他类型的数据链路下,但在本章中,我们只 考虑将IP地址映射到 48 bit的以太网地址。 ARP在RFC 826[Plummer 1982]中定义。 当某主机要向以太网中另一台主机发送 IP数据时,它首先根据目的主机的 IP地址在ARP高 速缓存中查询相应的以太网地址, A R P高速缓存是主机维护的一个 I P地址到相应以太网地址 的映射表。如果查到匹配的结点,则相应的以太网地址被写入以太网帧首部,数据报被加入 到输出队列等候发送。如果查询失败, A R P会先保留待发送的 I P数据报,然后广播一个询问 目的主机硬件地址的 ARP报文,等收到回答后再将 IP数据报发送出去。 以上只是简要描述了 A R P协议的基本工作过程,下面我们将结合 N e t / 3中的A R P实现来详 细描述其具体细节。卷 1的第4章包含了ARP的例子。

21.2 ARP和路由表 N e t / 3中A R P的实现是和路由表紧密关联的,这也是为什么我们要在描述路由表结构之后 再来讲解 A R P的原因。图 2 1 - 1显示了本章中我们描述 A R P要用到的一个例子。整个图是与本 书中用到的网络实例相对应的,它显示了 bsdi主机上当前 ARP缓存的相关结构。其中 Ifnet、 i f a d d r和i n _ i f a d d r结构是由图3-32和图6-5简化而来的,所以在这里忽略了在第 3章和第6 章中描述过的这三个结构中的某些细节。例如,图中没有画出在两个

i f a d d r 结构之后的

s o c k a d d r _ d l结构— 而仅仅是概述了这两个结构中的相应信息。同样,我们也仅仅是概 述了三个in_ifaddr结构中的信息。 下面,我们简要概述图中的有关要点。细节部分将随着本章的进行而详细展开。 1) l l i n f o _ a r p结构的双向链表包含了每一个 A R P已知的硬件地址的少量信息。同名全 局变量llinfo_arp是该链表的头结点,图中没有画出第一位的 la_prev指针指向最后一项, 最后一项的 la_next指针指向第一项。该链表由 ARP时钟函数每隔5分钟处理一次。 2) 每一个已知硬件地址的 I P地址都对应一个路由表结点 (r t e n t r y结构)。l l i n f o _ a r p 结构的 l a _ r t 指针成员用来指向相应的 r t e n t r y 结构,同样地, r t e n t r y 结构的 r t _ l l i n f o 指针成员指向 l l i n f o _ a r p 结构。图中对应主机 s u n ( 1 4 0 . 2 5 2 . 1 3 . 3 3 ) 、 s v r 4( 1 4 0 . 2 5 2 . 1 3 . 3 4 )和b s d i( 1 4 0 . 2 5 2 . 1 3 . 3 5 )的三个路由表结点各自具有相应的 l l i n f o _ a r p 结构。如图 18-2所示。 3) 而在图的最左边第四个路由表结点则没有对应的 l l i n f o _ a r p结构,该结点对应于本 地以太网 ( 1 4 0 . 2 5 2 . 1 3 . 3 2 )的路由项。该结点的 r t _ f l a g s中设置了 C比特,表明该结点是被用 来复制形成其他结点的。设置接口 IP 地址功能的 i n _ i f i n i t函数(图6 - 1 9 )通过调用 r t i n i t 函数来创建该结点。其他三个结点是主机路由结点 ( H标志),并由 b s d i向其他机器发送数据

543

第 21章 ARP:地址解析协议计计

时通过ARP间接调用路由相关函数产生的 (L标志)。 4) rtentry结构中的rt_gateway指针成员指向一个sockaddr_dl结构变量。如果保存 物理地址长度的结构sdl_alen成员为6,那么sockaddr_dl结构就包含相应的硬件地址信息。 5) 路由结点变量的 r t _ i f p成员的相应指针成员指向对应网络设备接口的 i f n e t结构。 中间的两个路由结点对应的是以太网上的其他主机,这两个结点都指向 l e _ s o f t c[ 0 ]。而右 边的路由结点对应的是 bsdi,指向环回结构loif。因为rt_ifp.if_output指向输出函数, 所以目的为本机的数据报被路由至环回接口。

图21-1 ARP与路由表和接口结构的关系

544

计计TCP/IP详解 卷 2:实现

6) 每一个路由结点还有指向相应的 i n _ i f a d d r 结构的指针变量 (图 6 - 8 中指出了 i n _ i f a d d r结构内的第一个成员是一个 i f a d d r结构,因此, r t _ i f a同样是指向了 i f a d d r 结构变量 )。在本图中,我们只显示一个路由结点的相应指向,其余的路由结点具有同样的性 质。而一个接口如 l e 0,可以同时设置多个 IP地址,每个 IP地址都有对应的 i n _ i f a d d r结构, 这就是为什么除了 rt_ifp之外还需要 rt_ifa的原因。 7) l a _ h o l d成员是指向mbuf链表的指针。当要向某个 IP传送数据报时,就需要广播一个 A R P请求。当内核等待 A R P回答时,存放该待发数据报的 m b u f链的头结点的地址信息就存放 在la_hold里。当收到 ARP回答后,la_hold指向的mbuf链表中的IP数据被发送出去。 8) 路由表结点中rt_metric结构的变量 rmx_expire存放的是与对应的 ARP结点相关的 定时信息,用来实现删除超时 (通常20分钟)的ARP结点。 在4.3BSD Reno中,路由表结构定义有了很大的变化,但 4.3BSD Reno和N e t / 2 . 4 . 4 B S D中依然定义有 A R P缓存,只是去除了作为单独结构的 A R P 缓存链表,而把 ARP信息放在了路由表结点里。 在N e t / 2中, A R P表是一个结构数组,其中每个元素包含有以下成员: I P地址、 以太网地址、定时器、标志和一个指向 m b u f的指针 (类似于图 2 1 - 1中的l a _ h o l d成 员)。在Net/3中,我们可以看到,这些信息被分散到多个相互链接的结构里。

21.3 代码介绍 如图21-2所示,共有包含 9个ARP函数的一个 C文件和两个头文件。 文







net/if arp.h a r p h d r结构的定义 n e t i n e t / i f e t h e r . h 多个结构和常量的定义 netinet/if_ether.d ARP函数

图21-2 本章中讨论的文件

图2 1 - 3显示了 A R P函数与其他内核函数的关系。该图中还说明了 A R P函数与第 1 9章中某 些子函数的关系,下面将逐步解释这些关系。 21.3.1 全局变量 本章中将介绍10个全局变量,如图 21-4所示。 21.3.2 统计量 保存A R P的统计量有两个全局变量: a r p _ i n u s e和a r p _ a l l o c a t e d,如图 2 1 - 4所示。 前者用来记录当前正在使用的 A R P结点数,后者用来记录在系统初始化时分配的 A R P结点数。 两个统计数都不能由 netstat程序输出,但可以通过调试器来查看。 可以使用命令 arp -a来显示当前ARP缓存的信息,该命令使用 s y s c t l系统调用,参数 如图19-36所示。图21-5显示该命令的一个输出结果。 由于图1 8 - 2中对应多播组 2 2 4 . 0 . 0 . 1的相应路由表项设置了 L标志,而同时由于 a r p程序查 询带有 R T F _ L L I N F O标志位的 A R P结点,所以该程序也输出多播地址。后面我们将解释为什 么该表项标识为“ incomplete”,而在它上面的表项是“ permanent”。

545

第 21章 ARP:地址解析协议计计

arp程序 选路插口

程序 进程 内核 接收到ARP请求/回答 时产生软件中断

针对每个ARP结点

每5分钟

以太网设备驱动器

超时

接口输出函数

赋IP地址时增加路由结点:使用RTM_ADD 命令带RTF_UP和RTF_CLONING标志

针对所有以太网设备的 ifa_rtrequest函数

图21-3 ARP函数和内核中其他函数的关系 变



llinfo_arp arpintrq arpt_prune arpt_keep arpt_down arp_inuse arp_allocated arp_maxtries arpinit_done uselookback

数据类型 struct struct Int Int Int Int Int Int Int int





l l i n f o _ a r p l l i n f o _ a r双 p 向链接表的表头 ifqueue 来自以太网设备驱动程序的 ARP输入队列 检查ARP链表的时间间隔的分钟数 (5) ARP结点的有效时间的分钟数 (20) ARP洪泛算法的时间间隔的秒数 (20) 正在使用的 ARP结点数 已经分配的 ARP结点数 对一个IP地址发送ARP请求的重试次数 (5) 初始化标志 对本机使用环回(默认)

图21-4 本章介绍的全局变量

546

计计TCP/IP详解 卷 2:实现

图21-5 与图18-2相应的arp -a命令的输出

21.3.3 SNMP变量 在卷1的2 5 . 8节中我们讲过,最初的 SNMP MIB定义了一个地址映射组,该组对应的是系 统的当前 ARP缓存信息。在 MIB-II中不再使用该组,而用各个网络协议组 (如IP组)分别包含地 址映射表来替代。注意,从 N e t / 2到N e t / 3,将单独结构的 A R P缓存演化为在路由表中集成的 ARP信息是与SNMP的变化并行的。 IP地址映射表, index = . 名





ipNetToMediaIfIndex ipNetToMediaPhysAddress ipNetToMediaNetAddress ipNetToMediaType







相应接口: i f I n d e x 硬件地址 IP地址 映射类型: 1=其他,2=失效,3=动态,4=静态(见正文)

if_index rt_gateway rt_key rt_flags

图21-6 IP地址映射表: ipNetToMediaTable

图2 1 - 6所示的是 M I B - I I中的一个 I P地址映射表, i p N e t T o M e d i a T a b l e,该表保存的值 来自于路由表结点和相应的 ifnet结构。 如果路由表结点的生存期为 0,则被认为是永久的,也即静态的。否则就是动态的。

21.4 ARP 结构 在以太网中传送的 ARP分组的格式图21-7所示。 硬件类型, 协议类型, 硬件地址长度, 协议地址长度,

以太网目的 以太网源 地址 地址 6 bytes 以太网首部

帧 类型

op

发送者硬件 地址

发送者IP 地址

目标硬件 地址

目标IP 地址

ARP首部 以太网ARP字段

图21-7 在以太网上使用时 ARP请求或回答的格式

结构e t h e r _ h e a d e r定义了以太网帧首部;结构 a r p h d r定义了其后的 5个字段,其信息 用于在任何类型的介质上传送 A R P请求和回答; e t h e r _ a r p结构除了包含 a r p h d r结构外, 还包含源主机和目的主机的地址。 结构arphdr的定义如图 21-8所示。图21-7显示了该结构中的前 4个字段。

547

第 21章 ARP:地址解析协议计计

图21-8 arphdr 结构:通用的 ARP请求/回答数据首部

图2 1 - 9显示了 e t h e r _ a r p结构的定义,其中包含了 a r p h d r结构、源主机和目的主机的 IP地址和硬件地址。注意, ARP用硬件地址来表示 48 bit以太网地址,用协议地址来表示 32 bit IP地址。

图21-9 ether_arp 结构

每个A R P结点中,都有一个 l l i n f o _ a r p结构,如图2 1 - 1 0所示。所有这些结构组成的链 接表的头结点是作为全局变量分配的。我们经常把该链接表称为 A R P高速缓存,因为在图 2 1 1中,只有该数据结构是与 ARP结点一一对应的。

图21-10 llinfo_arp结构

在N e t / 2及以前的系统中,很容易识别作为 A R P高速缓存的数据结构,因为每一 个ARP结点的信息都存放在单一的结构中。而 Net/3则把ARP信息存放在多个结构中, 没有哪个数据结构被称为 A R P高速缓存。但是为了讨论方便,我们依然用 A R P高速 缓存的概念来表示一个 ARP结点的信息。 104-106

该双向链接表的前两项由 insque和remque两个函数更新。 la_rt指向相关的路

548

计计TCP/IP详解 卷 2:实现

由表结点,该路由表结点的 rt_llinfo成员指向la_rt。 107

当ARP接收到一个要发往其他主机的 IP数据报,且不知道相应硬件地址时,必须发送一

个A R P请求,并等待回答。在等待 A R P回答时,指向待发数据报的指针存放在 l a _ h o l d中。 收到回答后,la_hold所指的数据报被发送出去。 108-109

l a _ a s k e d记录了连续为某个 IP地址发送请求而没有收到回答的次数。在图 21-24

中,我们可以看到,当这个数值达到某个限定值时,我们就认为该主机是关闭的,并在其后 一段时间内不再发送该主机的 ARP请求。 110

这个定义使用路由结点中 r t _ m e t r i c s结构的r m x _ e x p i r e成员作为 A R P定时器。当

值为0时,ARP项被认为是永久的;当为非零时,值为当结点到期时算起的秒数。

21.5 arpwhohas函数 a r p w h o h a s函数通常由 a r p r e s o l v e调用,用于广播一个 A R P请求。如图 2 1 - 11所示。 它还可由每个以太网设备驱动程序调用,在将 I P地址赋予该设备接口时主动发送一个地址联 编信息(图6 - 2 8中的S I O C S I F A D D Ri o c t l)。主动发送地址联编信息不但可以检测在以太网 中是否存在 I P地址冲突,并且可以使其他机器更新其相应信息。 a r p w h o h a s只是简单调用下 一部分将要介绍的 arprequest函数。

图21-11 arpwhohas 函数:广播一个 ARP请求

196-202

a r p c o m结构(图3 - 2 6 )对所有以太网设备是通用的,是 l e _ s o f t c结构(图3 - 2 0 )的

一部分。 a c _ i p a d d r成员是接口的 I P地址的复制,当 S I O C S I F A D D Ri o c t l执行时由驱动 程序填写(图6-28)。ac_enaddr是该设备的以太网地址。 该函数的第二个参数 a d d r,是 A R P请求的目的 I P 地址。在主动发送动态联编信息时, a d d r等于a c _ i p a d d r,所以a r p r e q u e s t的第二和第三个参数是一样的,即发送 I P地址和目 的IP地址在主动发送动态联编信息时是一样的。

21.6 arprequest函数 a r p r e q u e s t函数由 a r p w h o h a s函数调用,用于广播一个 A R P请求。该函数建立一个 ARP请求分组,并将它传送到接口的输出函数。 在分析代码之前,我们先来看一下该函数建立的数据结构。传送 A R P请求需要调用以太 网设备的接口输出函数 e t h e r _ o u t p u t。e t h e r _ o u t p u t的一个参数是 m b u f,它包含待发 送数据,即图 2 1 - 7中以太网类型字段后的所有内容。另外一个参数包含目的地址的端口地址 结构。通常情况下,该目的地址是

I P地址 ( 例如,在图 2 1 - 3 中, i p _ o u t p u t 调用

e t h e r _ o u t p u t )。特殊情况下,端口地址的 s a _ f a m i l y被设为 A F _ U N S P E C,即告知 e t h e r _ o u t p u t它所带的是一个已填充的以太网帧首部,包含了目的主机的硬件地址,这就

549

第 21章 ARP:地址解析协议计计

防止了 e t h e r _ o u t p u t去调用a r p r e s l o v e而导致死循环。图 2 1 - 3中没有显示这种循环,在 a r p r e q u e s t 下面的接口输出函数是 e t h e r _ o u t p u t。如果 e t h e r _ o u t p u t再去调用 arpresolve,将导致死循环。 图2 1 - 1 2显示了该函数建立的两个数据结构 m b u f和s o c k a d d r。另外还有两个函数中用到 的指针eh和ea。

以太网首部 1字节

14字节

未使用 (72字节)

(28字节)

图21-12 arprequest 建立的sockaddr 和mbuf

图21-13给出了arprequest函数的源代码。

图21-13 arprequest 函数:创建一个 ARP请求并发送

550

计计TCP/IP详解 卷 2:实现

图21-13 (续)

1. 分配和初始化mbuf 209-223

分配一个分组数据首部的 mbuf,并对两个长度字段赋值。 M H _ A L I G N将28字节的

e t h e r _ a r p结构置于 mbuf的尾部,并相应地设置 m _ d a t a指针的值。将该数据结构置于 mbuf 尾部,是为了允许 ether_output预先考虑将 14字节的以太网帧首部置于同一 mbuf中。 2. 初始化指针 224-226 给ea和eh两个指针赋值,并将 ether_arp结构的值赋为 0。bzero的惟一目的是 将目的硬件地址置 0,该结构中其余 8个字段已被设成相应的值。 3. 填充以太网帧首部 227-229

目的以太网地址设为以太网广播地址,并将以太网帧类型设为 ETHERTYPE_ARP。

注意代码中的注释,接口输出函数将该字段从主机字节序转化为网络字节序,该函数还将填 充本机的以太网地址。图 21-14显示了不同以太网帧类型字段的常量值。 常



ETHERTYPE_IP ETHERTYPE_ARP ETHERTYPE_REVARP ETHERTYPE_IPTRAILERS

值 0x0800 0x0806 0x8035 0x1000





IP帧 ARP帧 逆ARP帧 尾部封装 (已废弃)

图21-14 以太网帧类型字段

RARP 将硬件地址映射成 I P地址,通常在无盘工作站系统引导时使用。一般来说, R A R P 部分不属于内核 TCP/IP实现,所以本书将不作描述,卷 1的第5章讲述了RARP的概念。 4. 填充ARP字段 2 3 0 - 2 3 7 填充了 e t h e r _ a r p的所有字段,除了 A R P请求所要询问的目的硬件地址。常量 A R P H R D _ E T H E R的值为1时,表示硬件地址的格式是 6字节的以太网地址。为了表示协议地址 是4字节的IP地址,a r p _ p r o的值设为图 21-14中所指的IP协议地址类型(0x800)。图21-15显示 了不同的ARP操作码。本章中,我们将看到前两种。后两种在 RARP中使用。 5. 填充sockaddr,并调用接口输出函数 238-241 接口地址结构的 sa_family成员的值设为AP_UNSPEC,sa_member成员的值设 为16。调用接口输出函数 ether_output。

551

第 21章 ARP:地址解析协议计计







ARPOP_REQUEST ARPOP_REPLY ARPOP_REVREQUEST ARPOP_REVREPLY

1 2 3 4





解析协议地址的 ARP请求 回答ARP请求 解析硬件地址的 RARP请求 回答RARP请求

图21-15 ARP 操作码

21.7 arpintr函数 在图4-13中,当ether_input函数接收到帧类型字段为 ETHERTYPE_ARP的以太网帧时, 产生优先级为 N E T I S R _ A R P的软件中断,并将该帧挂在 A R P输入队列 a r p i n t r q的后面。当 内核处理该软件中断时,调用 arpintr函数,如图 21-16所示。

图21-16 arpintr 函数:处理包含 ARP请求/回答的以太网帧

319-343

w h i l e循环一次处理一个以太网帧,直到处理完队列中的所有帧为止。只有当帧

的硬件类型指明为以太网地址,并且帧的长度大于或等于 a r p h d r结构的长度加上两个硬件地 址和两个协议地址的长度时,该帧才能被处理。如果协议地址的类型是 E T H E R T Y P E _ I P或 ETHERTYPE_IPTRAILERS时,调用in_arpinput函数,否则该帧将被丢弃。 注意 i f 语句中对条件的检测顺序。共两次检查帧的长度。首先,当帧长大于或等于 a r p h d r结构的长度时,才去检查帧结构中的其他字段;然后,利用 a r p h d r中的两个长度字 段再次检查帧长。

552

计计TCP/IP详解 卷 2:实现

21.8 in_arpinput函数 该函数由 a r p i n t r调用,用于处理接收到的 A R P请求/回答。 A R P本身的概念比较简单, 但是加上许多规则后,实现就比较复杂,下面先来看一下两种典型情况: 1) 如果收到一个针对本机 I P地址的请求,则发送一个回答。这是一种普通情况。很显然, 我们将继续从那个主机收到数据报,随后也会向它回送报文。所以,如果我们还没有 对应它的 A R P结点,就应该添加一个 A R P结点,因为这时我们已经知道了对方的 I P地 址和硬件地址。这会优化其后与该主机的通信。 2) 如果收到一个 A R P回答,那么此时 A R P结点是完整的,因此就知道了对方的硬件地址。 该地址存放在sockaddr_dl结构中,所有发往该地址的数据报将被发送。 A R P请求是被广播发送的,所以以太网上的所有主机都将看到该请求,当然包括那些 非目的主机。回想一下 a r p r e q u e s t函数,在发送 A R P请求时,帧中包含着请求方的 IP地址和硬件地址,这就产生了下面的情况: 3) 如果其他主机发送了一个 A R P请求或回答,其中发送方的 I P地址与本机相同,那么肯 定有一个主机配置有误。 Net/3将检测到该差错,并向管理员登记一个报文 (这里我们不 分请求或回答,因为 i n _ a r p i n o u t不检查操作类型,但是 A R P回答将被单播,只有 目的主机才能收到信息 )。 4) 如果主机收到来自其他主机的请求或回答,对应的 ARP结点早已存在,但硬件地址发生 了变化,那么 ARP结点将被更新。这种情况是这样发生的:其他主机以不同的硬件地址 重新启动,而本机的对应 ARP结点还未失效。这样,根据机器重启动时主动发送动态联 编信息,可以使主机不至于因其他主机重启动后导致的 ARP结点失效而不能通信。 5).主机可以被配置为代理 A R P服务器。这种情况下,主机可以代其他主机响应 A R P请求, 在回答中提供其他主机的硬件地址。代理 A R P回答中对应目的硬件地址的主机必须能 够把IP数据报转发至ARP请求中指定的目的主机。卷 1的4.6节讨论了代理ARP。 一个N e t / 3系统可以配置成代理 A R P服务器。这些 A R P结点可以通过 a r p命令添加,该命令 中指定 I P地址、硬件地址并使用关键词 p u b。我们将在图 2 1 - 2 0中看到该实现,并在 21-12 节 讨论其实现细节。 将in_arpinput的分析分为四部分,图 21-17显示了第一部分。 358-375

ether_arp结构的长度由调用者 (arp_intr函数)验证,所以 ea指针指向接收到

的分组。 A R P操作码 (请求或回答 )被拷贝至 o p字段,但具体值要到后面来验证。发送方和目 的方的IP地址拷贝到 isaddr和itaddr。 1. 查找匹配的接口和 IP地址 376-382 搜索本机的 Internet地址链表(in_ifaddr结构的链表,图 6-5)。要记住一个接 口可以有多个 I P地址。收到的数据报中有指向接收接口 i f n e t结构的指针(在m b u f数据报的首 部),f o r循环只考虑与接收接口相关的 I P地址。如果查询到有 I P地址等于目的方 I P地址或发 送方IP地址,则退出循环。 383-384

如果循环退出时,变量 maybe_ia的值为0,说明已经搜索了配置的 IP地址整个链

表而没有找到相关项。函数跳至 o u t(图2 1 - 1 9 ),丢弃 m b u f,并返回。这种情况只发生在收到 ARP请求的接口虽然已初始化但还没有分配 IP地址时。

553

第 21章 ARP:地址解析协议计计

385

如果退出循环时, m a y b e _ i a值不为0,即找到了一个接收端接口,但没有一个 I P地址

与目的方 I P地址或发送方 I P地址匹配,则 m y a d d r的值设为该接口的最后一个 I P地址;否则 (正常情况),myaddr包含与目的方或发送方的 IP地址匹配的本地 IP地址。

图21-17 in_arpinput 函数:查找匹配接口

图21-18显示了in_arpinput函数的第二部分,执行分组的验证。

图21-18 in_arpinput 函数:验证接收到的分组

554

计计TCP/IP详解 卷 2:实现

2. 验证发送方的硬件地址 386-388

如果发送方的硬件地址等于本机接口的硬件地址,那是因为收到了本机发出的请

求,忽略该分组。 389-395

如果发送方的硬件地址等于以太网的广播地址,说明出了差错。记录该差错,并

丢弃该分组。 3. 检查发送方 IP地址 396-402

如果发送方的IP地址等于myaddr,说明发送方和本机正在使用同一个 IP地址。这

也是一个差错—要么是发送方,要么是本机系统配置出了差错。记录该差错,在将目的 I P 地址设为 m y a d d r后,程序转至 r e p l y(图2 1 - 1 9 )。注意该 A R P分组本来要送往以太网中其他 主机的 — 该分组本来不是要送给本机的。但是,如果这种形式的 I P地址欺骗被检测到,应 记录差错,并产生回答。 图21-19显示了in_arpinput函数的第三部分。

图21-19 in_arpinput 函数:创建新的 ARP结点或更新已有的 ARP结点

4. 在路由表中搜索与发送方 IP地址匹配的结点 403

arplookup在ARP高速缓存中查找符合发送方的 IP地址(isaddr)。当ARP分组中的目

的I P地址等于本机 I P时,如果要创建新的 A R P结点,那么第二个参数是 1,如果不需要创建新 的A R P结点,那么第二个参数是 0。如果本机就是目的主机,总是要创建 A R P结点的,除非一 个查找其他主机的广播分组,这种情况下只是在已有的 A R P结点中查询。正如前面提到的, 如果主机收到一个对应它自己的 A R P请求,则说明以太网中有其他主机将要与它通信,所以 应该创建一个对应该主机的 ARP结点。 第三个参数是 0,意味着不去查找代理 A R P结点 (后面要证明 )。返回值是指向 l l i n f o _

555

第 21章 ARP:地址解析协议计计

arp结构的指针;如果查不到或没有创建,返回值就是空。 5. 更新已有结点或填充新的结点 404 只有当以下三个条件为真时 if语句才执行: 1) 找到一个已有的 ARP结点或成功创建一个新的 ARP结点(即la非空); 2) ARP结点指向一个路由表结点 (rt); 3) 路由表结点的re_gateway字段指向一个sockaddr_dl结构。 对于每一个目的并非本机的广播 A R P请求,如果发送方的 I P地址不在路由表,则第一个 条件为假。 6. 检查发送方的硬件地址是否已改变 405-408

如果链路层地址长度 (s d l _ a l e n)非0,说明引用的路由表结点是现存的而非新创

建的,则比较链路层地址和发送方的硬件地址。如果不同,则说明发送方的硬件地址已经改 变,这是因为发送方以不同的以太网地址重新启动了系统,而本机的 A R P结点还未超时。这 种情况虽然很少出现,但也必须考虑到。记录差错信息后,程序继续往下执行,更新 A R P结 点的硬件地址。 在这个记录报文中,发送方的 IP地址必须转换为主机字节序,这是一个错误。 7. 记录发送方硬件地址 409-410

将发送方的硬件地址写入路由表结点中 r t _ g a t e w a y成员指向的 s o c k a d d r _ d l

结构。 s o c k a d d r _ d l结构的链路层地址长度 (s d l _ a l e n)也被设为 6。该赋值对于最近创建 的ARP结点是需要的(习题21-3)。 8. 更新最近解析的 ARP结点 411-412

在解析了发送方的硬件地址后,执行以下步骤。如果时限是非零的,则将被复位

成2 0分钟(a r p t _ k e e p)。a r p命令可以创建永久的 A R P结点,即该结点永远不会超时。这些 A R P结点的时限值置为 0。在图 2 1 - 2 4中我们将看到,在发送 A R P请求(非永久性 A R P结点)时, 时限被设为本地时间,它是非 0的。 413-414

清 除 R T F _ R E J E C T 标志 , l a _ a s k e d 计数 器 设为 0。 我们 将看 到 ,在

arpresolve中使用最后两个步骤是为了防止 ARP洪泛。 415-420 如果ARP中保持有正在等待 ARP解析该目的方硬件地址的 mbuf,那么将mbuf 送至 接口输出函数 (如图2 1 - 1所示)。由于该 m b u f是由A R P保持的,即目的地址肯定是在以太网上, 所以接口输出函数应该是 e t h e r _ o u t o u t。该函数也调用 a r p r e s o l v e,但这时硬件地址已 被填充,所以允许 mbuf加入实际的设备输出队列。 9. 如果是ARP回答分组,则返回 421-426 如果该ARP操作不是请求,那么丢弃接收到的分组,并返回。 i n _ a r p i n p u t的剩下部分如图 2 1 - 2 0所示,产生一个对应于 A R P请求的回答。只有当以 下两种情况时才会产生 ARP回答: 1) 本机就是该请求所要查找的目的主机; 2) 本机是该请求所要查找的目的主机的 ARP代理服务器。 函数执行到这个时刻,已经接收了 A R P请求,但 A R P请求是广播发送的,所以目的主机 可能是以太网上的任何主机。 10. 本机就是所要查找的目的主机

556

计计TCP/IP详解 卷 2:实现

427-432 如果目的IP地址等于myaddr,那么本机就是所要查找的目的主机。将发送方硬件 地址拷贝到目的硬件地址字段 (发送方现在变成了目的主机 ),a r p c o m结构中的接口以太网地 址拷贝到源硬件地址字段。 ARP回答中的其余部分在 else语句后处理。

图21-20 in_arpinput函数:形成 ARP回答,并发送出去

11. 检测本机是否目的主机的 ARP代理服务器 433-437

即使本机不是所要查找的目的主机,也可能被配置为目的主机的 ARP代理服务器。

再次调用 a r p l o o k u p函数,将第二个参数设为 0,第三个参数设为 S I N _ P R O X Y,这将在路由 表中查找 S I N _ P R O X Y标志为1的结点。如果查找不到 (这是通常情况,本机收到了以太网上其 他ARP请求的拷贝 ),out处的代码将丢弃 mbuf,并返回。 12. 产生代理回答 437-442

处理代理 A R P请求时,发送方的硬件地址变成目的硬件地址, A R P结点中的以太

网地址拷贝到发送方硬件地址。该 A R P结点中的硬件地址可以是以太网中任一台主机的硬件 地址,只要它可以向目的主机转发 I P数据报。通常,提供代理 A R P服务的主机会填入自己的 硬件地址,当然这不是要求的。代理ARP结点是由系统管理员用 arp命令带关键字pub创建的, 标明目的IP地址(这是路由表项的关键值 )和在ARP回答中返回的以太网地址。 13. 完成构造ARP回答分组 443-444

继续完成 A R P回答分组的构建。发送方和目标的硬件地址已经填充好了,现在交

换发送方和目标的 I P地址。目的I P地址在i t a d d r中,如果发现以太网中有其他主机使用同一

557

第 21章 ARP:地址解析协议计计

IP地址,则该值已经被填充了 (见图21-18)。 445-446

ARP操作码字段设为 A R P O P _ R E P L Y,协议地址类型设为 E T H E R T Y P E _ I P。旁边

加了注释“你需要确定”,是因为当协议地址类型为 E T H E R T Y P E _ I P T R A I L E R S时a r p i n t r 也会调用该函数,但现在跟踪封装 (trailer encapsulation)已不再使用了。 14. 用以太网帧首部填充 sockaddr 447-452

sockaddr结构用14字节的以太网帧首部填充,如图 21-12所示。目的硬件地址变

成了以太网目的地址。 453-455 将ARP回答传送至接口输出函数,并返回。

21.9 ARP定时器函数 A R P结点一般是动态的—需要时创建,超时时自动删除。也允许管理员创建永久性结 点,前面我们讨论的代理结点就是永久性的。回忆一下图 21-1和图21-10中最后的 # d e f i n e语 句,路由度量结构中的 rmx_expire成员就是用作ARP定时器的。 21.9.1 arptimer函数 如图21-21所示,该函数每 5分钟被调用一次。它查看所有 ARP结点是否超时。

图21-21 arptimer 函数:每5分钟查看所有 ARP定时器

1. 设置下一个时限 80

a r p _ r t r e q u e s t函数使 a r p t i m e r函数第一次被调用,随后 a r p t i m e r每隔 5分钟

(arpt_prune)使自己被调用一次。 2. 查看所有ARP结点 81-86

查看A R P结点链表中的每一个结点。如果定时器值是非零的 (不是一个永久结点 ),

而且时间已经超时,那么 a r p t f r e e就删除该结点。如果 r t _ e x p i r e是非零的,它的值是从 结点超时起到现在的秒数。 21.9.2 arptfree函数 如图21-22所示,a r p t f r e e函数由a r p t i m e r函数调用,用于从链接 l l i n f o _ d l表项的

558

计计TCP/IP详解 卷 2:实现

列表中删除一个超时的 ARP结点。 1. 使正在使用的结点无效 (不删除) 467-473

如 果 路 由 表 引 用计 数 器 值 大 于 0, 而 且 r t _ g a t e w a r y 成 员 指 向 一个

sockaddr_dl结构,则arptfree执行以下步骤: 1) 将链路层地址长度设为 0; 2) 将la_asked计数器值设为0; 3) 清除RTF_REJECT标志。 随后函数返回。因为路由表引用计数器值非零,所以该路由结点不能删除。但是将 sdl_alen值设为0,该结点也就无效了。下次要使用该结点时,还将产生一个 ARP请求。

图21-22 arptfree 函数:删除或使一个 ARP结点无效

2. 删除没有被引用的结点 474-475

r t r e q u e s t 删除路由结点,在 2 1 . 1 3节中,我们将看到它调用了

arp_

r t r e q u e s t。a r p _ r t r e q u e s t函数释放所有该 A R P结点保持的 m b u f (由l a _ h o l d指针所指 向),并删除相应的 llinfo_arp结点。

21.10 arpresolve函数 在图4 - 1 6中,e t h e r _ o u t p u t函数调用 a r p r e s o l v e函数以获得对应某个 I P地址的以太 网地址。如果已知该以太网地址,则 a r p r e s l o v e返回值为 1,允许将待发 IP数据报挂在接口 输出队列上。如果不知道该以太网地址,则 a r p r e s o l v e返回值为 0,a r p s o l v e函数利用 llinfo_arp结构的la_hold成员指针“保持(held)”待发IP数据报,并发送一个ARP请求。 收到ARP回答后,再将保持的 IP数据报发送出去。 a r p r e s o l v e应避免 A R P洪泛,也就是说,它不应在尚未收到 A R P回答时高速重复发送 A R P请求。出现这种情况主要有两个原因,第一,有多个 I P数据报要发往同一个尚未解析硬 件地址的主机;第二,一个 I P数据报的每个分片都会作为独立分组调用 e t h e r _ o u t p u t。 11 . 9节讨论了一个由分片引起的 A R P洪泛的例子及相关的问题。图 2 1 - 2 3显示了 a r p r e s o l v e 的前半部分。

559

第 21章 ARP:地址解析协议计计

252-261

d s t是一个指向 s o c k a d d r _ i n的指针,它包含目的 I P地址和对应的以太网地址

(一个6字节的数组 )。

图21-23 arpresolve 函数:查找所需的 ARP结点

1. 处理广播和多播地址 262-270

如果mbuf的M_BCAST标志置位,则用以太网广播地址填充目的硬件地址字段,函

数返回1。如果M _ M C A S T标志置位,则宏 E T H E R _ M A P _ I P _ M U L T I C A S T(图12-6)将D类地址映 射为相应的以太网地址。 2. 得到指向llinfo_arp结构的指针 271-276

目的地址是单播地址。如果调用者传输了一个指向路由表结点的指针,则将 l a设

置为相应的l l i n f o _ a r p结构。否则, a r p l o o k u p根据给定 IP的地址搜索路由表。第二个参 数是1,告诉a r p l o o k u p如果搜索不到相应的 A R P结点就创建一个新的;第三个参数是 0,即 意味着不去查找代理 ARP结点。 277-281

如果rt或la中有一个是空指针,说明刚才请求分配内存时失败,因为即使不存在

已有结点, a r p l o o k u p也已经创建了一个, r t和l a都不应是空值。记录一个差错报文,释 放分组,函数返回 0。 图2 1 - 2 4显示了a r p r e s o l v e的后半部分。它检查 A R P结点是否有效,如无效,则发送一 个ARP请求。

560

计计TCP/IP详解 卷 2:实现

图21-24 arpresolve 函数:检查 ARP结点是否有效,如无效,则发送一个 ARP请求

3. 检查ARP结点的有效性 282-291

即使找到了一个 A R P结点,还需检查其有效性。如以下条件成立,则 A R P结点是

有效的: 1) 结点是永久有效的 (时限值为0),或尚未超时; 2) 由rt_gateway指向的插口地址结构的 sdl_family字段为AF_LINK; 3) 链路层地址长度值 (sdl_alen)不等于0。 a r p t f r e e使一个仍被引用的 A R P结点失效的方法是将 s d l _ a l e n值置0。如果结点是有 效的,则将 sockaddr_dl中的以太网地址拷贝到 desten,函数返回 1。 4. 只保持最近的IP数据报 292-299

此时,已经有了 A R P结点,但它没有一个有效的以太网地址,因此,必须发送一

个ARP请求。将l a _ h o l d指针指向mbuf,同时也就释放了刚才 l a _ h o l d所指的内容。这意味 着,在发送 A R P请求到收到 A R P回答之间,如果有多个发往同一目的地的 I P数据报要发送, 只有最近的一个 I P数据报才被 l a _ h o l d保留,之前的全部丢弃。 N F S就是这样的一个例子, 如果N F S要传送一个 8 5 0 0字节的I P数据报,需要将其分割成 6个分片。如果每个分片都在发送 ARP请求到收到 ARP回答之间由 ip_output送往ether_output,那么前5个分片将被丢弃,

561

第 21章 ARP:地址解析协议计计

当收到ARP回答时,只有最后一个分片被保留了下来。这会使 NFS超时,并重发这 6个分片。 5. 发送ARP请求,但避免ARP洪泛 300-314

RFC 1122要求ARP避免在收到 ARP回答之前以过高的速度对一个以太网地址重发

ARP请求。Net/3采用以下方法来避免 ARP洪泛: • Net/3不在同一秒钟内发送多个对应同一目的地的 ARP请求; • 如果在连续 5个A R P请求(也就是5秒钟)后还没有收到回答,路由结点的 R T F _ R E J E C T标 志置1,时限设为往后的 2 0秒。这会使 e t h e r _ o u t p u t在2 0秒内拒绝发往该目的地址的 IP数据报,并返回 EHOSTDOWN或EHOSTUNREACH(如图4-15所示)。 • 20 秒钟后, arpresolve会继续发送该目的主机的 ARP请求。 如果时限值不等于 0 (非永久性结点 ),则清除 R T F _ R E J E C T标志,该标志是在早些时候为 避免A R P洪泛而设置的。计数器 l a _ a s k e d记录的是连续发往该目的地址的 A R P请求数。如 果计数器值为 0或时限值不等于当前时钟 (只需看一下当前时钟的秒钟部分 ),那么需要再发送 一个A R P请求。这就避免了在同一秒钟内发送多个 A R P请求。然后将时限值设为当前时钟的 秒钟部分(也就是微秒部分, time_tv_usec被忽略)。 将l a _ a s k e d所含计数器值与限定值 5 (a r p _ m a x t r i e s)比较,然后加 1。如果小于5,则 arpwhohas发送ARP请求;如果等于 5,则ARP已经达到了限定值:将RTF_REJECT标志置1, 时限值置为往后的 20秒钟,la_asked计数器值复位为 0。 图2 1 - 2 5显示了一个例子,进一步解释了 a r p r e s o l v e和e t h e r _ o u t p u t为了避免 A R P 洪泛所采用的算法。 数据报数

时间

ARP 请求

ARP 请求

ARP 请求

ARP 请求

ARP 请求

RTF_REJECT 打开

返回 EHOSTDOWN

ARP 请求

图21-25 避免ARP洪泛所采用的算法

图中总共显示了 2 6秒的时间,从 1 0到3 6。我们假定有一个进程每隔 0 . 5秒发送一个 I P数据 报,也就是说,一秒钟内有两个数据报等待发送。数据报依次被标号为 1 ~ 5 2。我们还假定目 的主机已经关闭,所以收不到 ARP回答。ARP将采取以下行动: • 假定当进程写数据报 1时la_asked的值为0。la_hold设为指向数据报 1,rt_expire 值设为当前时钟 (10),la_asked值变为1,发送ARP请求。函数返回 0。 • 进程写数据报 2时,丢弃数据报 1,l a _ h o l d指向数据报 2。由于 r t _ e x p i r e值等于当 前时钟(10),所以不发送ARP请求,函数返回,返回值为 0。 • 进程写数据报 3时,丢弃数据报 2,l a _ h o l d 指向数据报 3。由于当前时钟 ( 11 )不等于 r t _ e x p i r e( 1 0 ),所以将 r t _ e x p i r e设为11。l a _ a s k e d值为1,小于 5,所以发送 ARP请求,并将 la_asked值置为2。 • 进程写数据报 4时,丢弃数据报 3,l a _ h o l d指向数据报 4。由于 r t _ e x p i r e值等于当

562

计计TCP/IP详解 卷 2:实现

前时钟(11),所以无须其他动作,函数返回 0。 • 对于数据报 5 ~ 1 0,情况都是一样的。在数据报 9到达后,发送 A R P请求后, l a _ a s k e d 值被设为5; • 进程写数据报 11时,丢弃数据报 1 0,l a _ h o l d指向数据报 11 。当前时钟 ( 1 5 )不等于 r t _ e x p i r e( 1 4 ),所以将 r t _ e x p i r e的值设为 1 5。此时 l a _ a s k e d的值不再小于 5, A R P避免洪泛的算法开始作用: R T F _ R E J E C T标志位置 1,r t _ e x p i r e的值被设为 35(即往后20秒),la_asked的值设为0,函数返回 0。 • 进程写数据报 12时,e t h e r _ o u t p u t注意到R T F _ R E J E C T标志位为 1,而且当前时钟小 于rt_expire(35),因此,返回EHOSTDOWN给发送者(通常是ip_output)。 • 从数据报13到50,都返回 EHOSTDOWN给发送者。 • 当进程写数据报 5 1时,尽管此时的 R T F _ R E J E C T标志位仍然为 1,但当前时钟的值 ( 3 5 ) 不再小于 r t _ e x p i r e( 3 5 ),因此不会返回出错信息。调用 a r p r e s o l v e,整个过程重 新开始, 5秒钟内发送5个ARP请求,然后是 20秒钟的等待,直到发送者放弃或目的主机 响应ARP请求。

21.11 arplookup函数 a r p l o o k u p函数调用选路函数 r t a l l o c l在Internet路由表中查找ARP结点。我们已经看 到过3次调用arplookup的情况: 1) 在in_arpinput中,在接收到ARP分组后,对应源 IP地址查找或创建一个 ARP结点。 2) 在i n _ a r p i n p u t中,接收到 A R P请求后,查看是否存在目的硬件地址的代理 A R P结 点。 3) 在arpresolve中,查找或创建一个对应待发送数据报 IP地址的ARP结点。 如果a r p l o o k u p执行成功,则返回一个指向相应 l l i n f o _ a r p结构的指针,否则返回一 个空指针。 a r p l o o k u p带有三个参数,第一个参数是目的 I P地址;第二个参数是个标志,为真时表 示若找不到相应结点就创建一个新的结点;第三个参数也是一个标志,为真时表示查找或创 建代理ARP结点。 代理 A R P 结点通过定义一个不同形式的 I n t e r n e t插口地址结构来处理,即 s o c k a d d r _ inarp结构,如图 21-26所示。该结构只在 ARP中使用。

图21-26 sockaddr_inarp

111-119

结构

前面8个字节与s o c k a d d r _ i n结构相同, s i n _ f a m i l y被设为A F _ I N E T。最后8

563

第 21章 ARP:地址解析协议计计

个字节有所不同: s i n _ s r c a d d r、s i n _ t o s和s i n _ o t h e r成员。当结点作为代理结点时, 只用到sin_other成员,并将其设为 SIN_PROXY(1)。 图21-27显示了arplookup函数。

图21-27 arplookup 函数:在路由表中查找 ARP结点

1. 初始化sockaddr_inarp结构,准备查找 480-489

s i n _ a d d r 成员设为将要查找的 I P 地址。如果 p r o x y 参数值不为 0,则

sin_other成员设为SIN_PROXY;否则设为 0。 2. 路由表中查找结点 490-492

r t a l l o c l在I n t e r n e t路由表中查找 I P地址,如果 c r e a t e参数值不为 0,就创

建一个新的结点。如果找不到结点,则函数返回值为 0(空指针)。 3. 减少路由表结点的引用计数值 493

如果找到了结点,则减少路由表结点的引用计数。因为,此时ARP不再被认为像运输层一

样“持有”路由表结点,因此,路由表查找时对rt_refcnt计数的递增,应在这里由ARP取消。 494-499

如果将标志 R T F _ G A T E W A Y置位,或者标志 R T F _ L L I N F O没有置位,或者由

r t _ g a t e w a y指向的插口地址结构的地址族字段值不是 A F _ L I N K,说明出了某些差错,返回 一个空指针。如果结点是这样创建的,应创建一个记录报文。 记录报文中对arptnew的注释是针对老版本 Net/2中创建ARP结点的。 如果r t a l l o c l由于匹配结点的 R T F _ C L O N I N G标志置位而创建一个新的结点,那么函数 arp_rtrequest (21.13节)也要被rtrequest调用。

21.12 代理ARP N e t / 3支持代理 A R P,有两种不同类型的代理 A R P结点,可以通过 a r p命令及 p u b选项将

564

计计TCP/IP详解 卷 2:实现

它们加入到路由表中。添加代理 A R P选项会使 a r p _ r t r e q u e s t主动发送动态联编信息 (如图 21-28所示),因为在创建结点时 RTF_ANNOUNCE标志位被置 1。 代理A R P结点的第一种类型:它允许将网络内的某一主机的 I P地址填入到 A R P高速缓存 内。硬件地址可以设为任意值。这种结点加入到路由表中时使用了直接的掩码 0 x f f f f f f f f。 加掩码的目的是即使插口地址的 S I N _ P R O X Y标志位为 1,在调用图2 1 - 2 7中的r t a l l o c l时能 与该结点匹配。于是在调用图 2 1 - 2 0中的 a r p l o o k u p 时也能与该结点匹配,目的地址的 SIN_PROXY置位。 如果本网中的主机 H 1不能实现 A R P,那么可以使用这种类型的代理 A R P结点。作为代理 的主机代替H 1回答所有的A R P请求,同时提供创建代理 A R P结点时设定的硬件地址 (比如可以 是H1的以太网地址)。这种类型的结点可以通过 arp -a命令查看,它带有“ published”符号。 第二种类型的代理 A R P结点用于已经存有路由表结点的主机。内核为该目的地址创建另 外一个路由表结点,在这个新的结点中含有链路层的信息

( 如以太网地址 )。该新结点中

sockaddr_inarp 结构(图2 1 - 2 6 )的s i n _ o t h e r成员的S I N _ P R O X Y标志置位。回想一下, 搜索路由表时是比较 1 2字节的I n t e r n e t插口地址 (图1 8 - 3 9 )。只有当该结构的最后 8字节非零时, 才会用到 S I X _ P R O X Y标志位。当 a r p l o o k u p指定送往 r t a l l o c l的结构中的s i n _ o t h e r成 员中SIN_PROXY的值时,只有路由表中那些匹配的结点的 SIN_PROXY标志置位。 这种类型的代理 A R P结点通常指明了作为代理 A R P服务器的以太网地址。如果某代理 ARP结点是为主机HD创建的,一般有以下步骤: 1) 代理服务器收到来自主机 HS的查找HD硬件地址的广播 ARP请求,主机 HS认为HD在本 地网上; 2) 代理服务器回答请求,并提供本机的以太网地址; 3) HS 将发往HD的数据报发送给代理服务器; 4) 收到发往HD的数据报后,代理服务器利用路由表中关于 HD的信息将数据报转发给HD。 路由器netb使用这种类型的代理 ARP结点,见卷 1 4.6节中的例子。可以通过命令 a r p a来查看这些带有“ published (proxy only)”的结点。

21.13 arp_rtrequest函数 图2 1 - 3简要显示了 A R P函数和选路函数之间的关系。在 A R P中,我们将调用两个路由表 函数: 1) a r p l o o k u p调用r t a l l o c l查找A R P结点,如果找不到匹配结点,则创建一个新的 ARP结点。 如果在路由表中找到了匹配结点,且该结点的 R T F _ C L O N I N G标志位没有置位 (即该结点就是目的主机的结点 ),则返回该结点。如果 RTF_CLONING标志位被置位, r t a l l o c l 以 R T M _ R E S O L V E 命令为参数调用 r t r e q u e s t 。图 1 8 - 2 中的 140.252.13.33和140.252.13.34结点就是这么创建的,它们是从 140.252.13.32的结点复 制而来的。 2) arptfree以RTM_DELETE命令为参数调用 rtrequest,删除对应 ARP结点的路由表 结点。 此外,a r p命令通过发送和接收路由插口上的路由报文来操纵 ARP高速缓存。a r p以命令

-

565

第 21章 ARP:地址解析协议计计

R T M _ R E S O L V E、R T M _ D E L E T E和R T _ G E T 为参数发布路由信息。前两个参数用于调用 rtrequest,第三个参数用于调用 rtallocl。 最后,当以太网设备驱动程序获得了赋予该接口的 IP地址后,rtinit增加一个网络路由。 于是r t r e q u e s t函数被调用,参数是 R T M _ A D D,标志位是 R T F _ U P和R T F _ C L O N I N G。图182中140 252 13 32结点就是这么创建的。 在第1 9章中我们讲过,每一个 i f a d d r结构都有一个指向函数 (i f a _ r t r e q u e s t成员)的 指针,该函数在创建或删除一个路由表结点时被自动调用。在图 6-17中,对于所有以太网设备, i n _ i f i n i t将该指针指向 a r p _ r t r e q u e s t函数。因此,当调用路由函数为 A R P创建或删除 路由表结点时,总会调用 arp_rtrequest。当任意路由表函数被调用时, a r p _ r t r e q u e s t 函数的作用是做各种初始化或退出处理所需的工作。例如:当创建新的

A R P 结点时,

a r p _ r t r e q u e s t 内要为 l l i n f o _ a r p结构分配内存。同样,当路由函数处理完一个 RTM_DELETE命令后,arp_rtrequest的工作是删除llinfo_arp结构。 图21-28显示了arp_rtrequest函数的第一部分。

图21-28 arp_rtrequest

函数:RTM_ADD 命令

566

计计TCP/IP详解 卷 2:实现

图21-28 (续)

1. 初始化A R P t i m e o u函数 t 92-105 第一次调用 arp_rtrequest函数时(系统初始化阶段,在对第一个以太网接口赋 IP 地址时 ),t i m e o u t函数在一个时钟滴答内调用 a r p t i m e r函数。此后, A R P定时器代码每 5 分钟运行一次,因为 arptimer总是要调用 timeout的。 2. 忽略间接路由 106-107 如果将标志 RTF_GATEWAY置位,则函数返回。 RTF_GATEWAY标志表明该路由表 结点是间接的,而所有 ARP结点都是直接的。 108

一个带有三种可能的 switch语句:RTM_ADD、RTM_RESOLVE和R T M _ D E L E T E (后两

种在后面的图中显示 )。 3. RTM_ADD命令 109

RTM_ADD命令出现在以下两种情况中:执行 arp命令手工创建ARP结点或者 rtinit函

数对以太网接口赋 IP地址(图21-3)。 4. 向后兼容 110-117

若标志RTF _ H O S T没有置位,说明该路由表结点与一个掩码相关 (也就是说是网络

路由,而非主机路由 )。如果掩码不是全 1,那么该结点确实是某一接口的路由,因此,将标 志R T F _ C L O N I N G置位。如注释中所述,这是为了与某些旧版本的路由守护程序兼容。此外, /etc/netstart中的命令: route add -net 224.0.0.0 -interface bsdi

为图18-2所示网络创建带有 RTF_CLONING标志的路由表结点。 5. 初始化到接口的网络路由结点 118-126

若标志R T F _ C L O N I N G(i n _ i f i n i t为所有以太网接口设置该标志 )置位,那么该

路由表结点是由 r t i n i t添加的。 r t _ s e t g a t e为s o c k a d d r _ d l结构分配空间,该结构由 r t _ g a t e w a y指针所指。与图 2 1 - 1中1 4 0 . 2 5 2 . 1 3 . 3 2的路由表结点相关的就是该数据链路插口 地址结构。 s d l _ f a m i l y 和s d l _ l e n成员的值是根据静态定义的 n u l l _ s d 而初始化的, s d l _ t y p e(可能是 I F T _ E T H ER )和s d l _ i n d e x成员的值来自接口的 i f n e t结构。该结构不 包含以太网地址, sdl_alen成员的值为 0。 127-128

最后将时限值设为当前时间,也就是结点的创建时间,执行 b r e a k后返回。对于

在系统初始化时创建的结点,它们的 r m x _ e x p i r e值为系统启动的时间。注意,图 2 1 - 1中该 路由表结点没有相应的 l l i n f o _ a r p结构,所以它不会被 a r p t i m e r处理。但是要用它的 s o c k a d d r _ d l结构,对于以太网中特定主机的路由结点来说,要复制的是 r t _ g a t e w a y结 构,用 R T M _ R E S O L V E命令参数创建路由表结点时, r t r e q u e s t 复制该结构。此外,

567

第 21章 ARP:地址解析协议计计

netstat程序将sdl_index的值输出为 link#n,见图18-2。 6. 发送免费ARP请求 130-135

若将标志 R T F _ A N N O U N C E置位,则该结点是由 a r p命令带p u b选项创建的。该选

项有两个分支: (1) s o c k a d d r _ i n a r p结构中s i n _ o t h e r成员的S I N _ P R O X Y标志被置位; ( 2 )标志R T F _ A N N O U N C E被置位。因为标志 R T F _ A N N O U N C E被置位,所以 a r p r e q u e s t广播 免费A R P请求。注意,第二个和第三个参数是相同的,即该 A R P请求中,发送方 I P地址和目 的方IP地址是一样的。 136 继续执行针对RTM_RESOLVE命令的case语句。 图2 1 - 2 9 显示了 a r p _ r t r e q u e s t函数的第二部分,处理 R T M _ R E S O L V E 命令。当 r t a l l o c l找到一个 R T F _ C L O N I N G标志位置位的路由表结点且 r t a l l o c l的第二个参数值 (a r p l o o k u p的c r e a t e参数)不为0时,调用该命令。需要分配一个新的 l l i n f o _ a r p结构, 并将其初始化。

图21-29 arp_rtrequest

函数:RTM_RESOLVE 命令

568

计计TCP/IP详解 卷 2:实现

图21-29 (续)

7. 验证sockaddr_dl结构 137-144

验证r t _ g a t e w a y指针所指的 s o c k a d d r _ d l结构的s a _ f a m i l y和s a _ l e n成员

的值。接口类型 (可能是IFT_ETHER)和索引值填入新的 sockaddr_dl结构。 8. 处理路由变化 145-146

正常情况下,该路由表结点是新创建的,并没有指向一个 l l i n f o _ a r p结构。如

果l a指针非空,则在路由已发生了变化时调用 a r p _ r t r e q u e s t。此时l l i n f o _ a r p已经分 配,执行break,函数返回。 9. 初始化llinfo_arp结构 147-158

分配一个 l l i n f o _ a r p结构, r t _ l l i n f o中存有指向该结构的指针。统计值变

量a r p _ i n u s e和a r p _ a l l o c a t e d各加1,l l i n f o _ a r p结构置 0。将l a _ h o l d指针置空, la_asked值置0。 159-161

将r t指针存储于 l l i n f o _ a r p结构中,置R T F _ L L I N F O标志位。如图 1 8 - 2所示,

A R P创建的三个结点 1 4 0 . 2 5 2 . 1 3 . 3 3、1 4 0 . 2 5 2 . 1 3 . 3 4和1 4 0 . 2 5 2 . 1 3 . 3 5都有L标志,和 2 4 0 . 0 . 0 . 1一 样。arp程序只检查该标志 (图19-36)。最后insque将llinfo_arp加入到链接表的首部。 就这样创建了一个 A R P结点: r t r e q u e s t创建路由表结点 (经常为以太网克隆一个特定 网络的结点 ),a r p _ r t r e q u e s t分配和初始化 l l i n f o _ a r p结构。剩下只需广播一个 A R P请 求,在收到回答后填充主机的以太网地址。事件发生的一般次序是:

a r p r e s o l v e 调用

a r p l o o k u p,于是 a r p _ r t r e q u e s t被调用(中间可能跟有函数调用,见图 2 1 - 3 )。当控制返 回到arpresolve时,发送ARP广播请求。 10. 处理发给本机的特例情况 162-173

这是4 . 4 B S D新增的测试特例部分 (注释是老版本留下的 )。它创建了图 2 1 - 1中最右

边的路由表结点,该结点包含了本机的 IP地址(140.252.13.35)。i f语句检测它是否等于本机 IP 地址,如等于,那么这个刚创建的结点代表的是本机。 11. 将结点置为永久性,并设置以太网地址 174-176

时限值设为0,意味着该结点是永久有效的—永远不会超时。从接口的 a r p c o m

结构中将硬件地址拷贝至 rt_gateway所指的sockaddr_dl结构中。 12. 将接口指针指向环回接口 177-178

若全局变量 usrloopback值不为0(默认为1),则将路由表结点内的接口指针指向

环回接口。这意味着,如果有数据报发给自己,就送往环回接口。在 4 . 4 B S D以前的版本中, 可以通过/etc/netstart文件中的命令: route add 140.252.13.35 127.0.0.1

来建立从本机 I P地址到环回接口的路由。 4 . 4 B S D仍然支持这种方式,但已不是必需的了。当

569

第 21章 ARP:地址解析协议计计

第一次有数据报发给本机 I P地址时,我们刚才看到的代码会自动创建一个这样的路由。此外, 这些代码对于一个接口只会执行一次。一旦路由表结点和永久性 A R P结点创建好后,它们就 不会超时,所以不会再次出现对本机 IP地址的RTM_RESOLLVE命令。 arp_rtrequest函数的最后部分如图 21-30所示,处理RTM_DELETE请求。从图21-3中, 我们可以看到,该命令是由 a r p命令产生的,用于手工删除一个结点;或者在一个 A R P结点 超时时由arptfree产生。

图21-30 arp_rtrequest函数:RTM_DELETE 命令

13. 验证la指针 182-183

la指针应该是非空的,也就是说路由表结点必须指向一个 llinfo_arp结构;否

则,执行break,函数返回。 14. 删除llinfo_arp结构 184-190

统计值变量 a r p _ i n u s e 减1,r e m q u e 从链表中删除 l l i n f o _ a r p 结构。

r t _ l l i n f o指针置0,清除R T F _ L L I N F O标志。如果该ARP结点保持有mbuf(即该ARP请求未 收到回答),则将mbuf释放。最后释放 llinfo_arp结构。 注意, s w i t c h语句中没有包含 d e f a u l t情况,也没有考虑 R T M _ G E T命令。这是因为 a r p程序产生的 R T M _ G E T命令全部由 r o u t e _ o u t p u t函数处理,并不调用 r t r e q u e s t。此 外,见图 2 1 - 3,在 R T M _ G E T 命令产生的对 r t a l l o c l调用中,指定第二个参数是 0,所以 rtallocl并不调用rtrequest。

21.14 ARP和多播 如果一个 I P数据报要采用多播方式发送, i p _ o u t p u t检测进程是否已将某个特定的接口 赋予插口 (见图12-40)。如果已经赋值,则将数据报发往该接口,否则, i p _ o u t p u t利用路由 表选择输出接口 (见图8 - 2 4 )。因此,对于具有多个多播发送接口的系统来说, I P路由表应指定 每个多播组的默认接口。 在图18-2中我们看到,路由表中有一个结点是为网络 224.0.0.0创建的,该结点具有“ flag” 标志。所有以 2 2 4开头的多播组都以该结点指定的接口 ( l e 0 )为默认接口。对于其他的多播组 (以2 2 5 ~ 2 3 9开头),可以分别创建新的路由表结点,也可以对某个指定多播组创建一个路由表 结点。例如,可以为 2 2 4 . 0 . 11 (网络定时协议 )创建一个与 2 2 4 . 0 . 0 . 0不同的路由表结点。如果路 由表中没有对应某个多播组的结点,同时进程没有用 I P _ M U L T I C A S T _ I F插口选项指明接口, 那么该组的默认接口成为路由表中默认路由的接口。其实图 1 8 - 2中对应 2 2 4 . 0 . 0 . 0的路由表结

570

计计TCP/IP详解 卷 2:实现

点并不是必要的,因为默认接口就是 le0。 如果选定的接口是以太网接口,则调用 a r p r e s o l v e将多播组地址映射为相应的以太网 地址。在图 2 1 - 2 3中,映射通过调用宏 E T H E R _ M A P _ I P _ M U L T I C A S T来完成。该宏所做的就 是将该多播组地址的低 23位与一个常量逻辑或 (图12-6),映射不需要 ARP请求和回答,也不需 要进入ARP高速缓存。每次需要映射时,调用该宏。 如果多播组是从另外一个结点复制得来的,那么多播组地址会出现在 A R P缓存里,如图 2 1 - 5所示。因为这些结点将 R T F _ L L I N F O标志置位。它们不会有 A R P请求和回答,所以说不 是真正的 A R P结点。它们也没有相应的链路层地址,宏 E T H E R _ M A P _ I P _ M U L T I C A S T就可以 完成映射。 这些多播组的 A R P结点的时效与正常的 A R P结点不同。在为某个多播组创建一个路由表 结点时,如图 1 8 - 2中的2 2 4 . 0 . 0 . 1,r t r e q u e s t从被克隆的结点中复制 r t _ m e t r i c s结构(图 1 9 - 9 )。图2 1 - 2 8中,网络路由结点的 r m x _ e x p i r e值被设为 R T M _ A D D命令执行的时间,也即 系统初始化的时间。为 224.0.0.1设置的结点也设置为同样的时间。 这就意味着在下次 arptimer执行时,对应多播组224.0.0.1的ARP结点总是超时的。所以, 当下一次在路由表中查找时就需重新创建该结点。

21.15 小结 ARP提供了IP地址到硬件地址的映射,本章讲述了如何实现这种映射。 Net/3实现与以往的 BSD 版本有很大不同。 ARP信息被存放在多个结构里面:路由表、数 据链路插口地址结构和 llinfo_arp结构。图21-1显示了这些结构之间的关系。 发送一个 A R P请求是很简单的:正确填充相关字段后,将请求广播发送出去就行了。处 理请求就要复杂一些,因为每个主机都收到了广播的

A R P 请求。除了响应请求外,

i n _ a r p i n p u t还要检测是否有其他主机正与它使用同一个 IP地址。因为每一个 ARP请求中包 含发送方的 IP和硬件地址,所以网络上的所有主机都可以通过它来更新自己的 ARP结点。 在局域网中, A R P洪泛将是一个问题, N e t / 3是第一个考虑这种问题的 B S D版本。对于同 一个目的地,一秒钟内只可发送一个 A R P请求,如果连续 5个请求都没有收到回答,必须暂停 20秒钟才可再发送去往该目的地的 ARP请求。

习题 21.1 图21-17中给局部变量ac赋值时,做过什么假设? 21.2 如果我们先 p i n g本地以太网的广播地址,之后执行 a r p -a,就可以发现几乎所有 本地以太网上的其他主机的表项都填入到了 ARP高速缓存中。这是为什么? 21.3 查看代码并解释为什么图 21-19中需要把sdl_alen的值赋为6。 21.4 在N e t / 2中有一个独立于路由表而存在的 A R P表,每次调用 a r p r e s o l v e时,都要 在该ARP表中查找。试与 Net/3的方法比较,哪个更有效? 21.5 N e t / 2中的A R P代码显式地设置 A R P高速缓存中非完整表项的超时为 3分钟,非完整 表项是指正在等待 A R P回答的表项。但我们从没有提过 N e t / 3如何处理该超时,那 么Net/3何时才认为非完整表项超时? 21.6 当N e t / 3系统作为一个路由器并且导致洪泛的分组来自其他主机时,为避免 A R P洪

571

第 21章 ARP:地址解析协议计计

泛要做哪些变动? 21.7 图21-1中给出的四个rmx_expire变量的值是什么?代码在何处设置该值? 21.8 对广播 A R P请求的每个主机,本章中引起要创建一个 A R P结点的代码需要做哪些变 动? 21.9 为了验证图 2 1 - 2 5中的例子,作者运行了卷 1附录C的s o c k程序,每隔 500 ms向本地 以太网上一个不存在的主机发送一个 U D P数据报 (程序的 - p选项改为等待的毫秒 数)。但是在返回第一个 EHOSTDOWN差错之前,仅无差错地发送了 10个UDP数据报, 而不是图21-25所示的11个,这是为什么? 21.10 修改A R P,使得它在等待 A R P回答时持有到目的主机的所有分组,而不是持有最 近的一个。如何实现这种改变?是否像每个接口的输出队列一样,需要一个限 制?是否需要改变数据结构?

第22章 协议控制块 22.1 引言 协议层使用协议控制块 (PCB)存放各UDP和TCP插口所要求的多个信息片。 Internet协议维 护Internet 协议控制块 (Internet protocol control block)和TCP控制块(TCP control block)。因为 UDP是无连接的,所以一个端结点需要的所有信息都可以在 Internet PCB中找到;不存在 UDP 控制块。 Internet PCB含有所有 U D P和T C P端结点共有的信息:外部和本地 I P地址、外部和本地端 号、I P首部原型、该端结点使用的 I P选项以及一个指向该端结点目的地址选路表入口的指针。 TCP控制块包含了TCP为各连接维护的所有结点信息:两个方向的序号、窗口大小、重传次数 等等。 本章我们描述 N e t / 3所用的Internet PCB,在详细讨论 T C P时再探讨 T C P控制块。我们将研 究几个操作 Internet PCB的函数,会在描述 U D P和T C P时遇到它们。大多数的函数以 i n _ p c b 开头。 图2 2 - 1总结了协议控制块以及它们与 f i l e和s o c k e t结构之间的关系。该图中有几点要 考虑: • 当s o c k e t或a c c e p t创建一个插口后,插口层生成一个 f i l e结构和一个s o c k e t结构。 文件类型是 D T Y P E _ S O C K E T,U D P端结点的插口类型是 S O C K _ D G R A M,T C P端结点的 插口类型是 SOCK_STREAM。 • 然后调用协议层。 UDP创建一个 Internet PCB(一个i n p c b结构),并把它链接到 s o c k e t 结构上:so_pcb成员指向inpcb结构,inp_socket成员指向socket结构。 • T C P做同样的工作,也创建它自己的控制块 (一个t c p c b结构),并用指针 i n p _ p p c b和 t_inpcb把它链接到 inpcb上。在两个 UDP inpcb中,inp_ppcb成员是一个空指针, 因为UDP不负责维护它自己的控制块。 • 我们显示的其他四个 i n p c b结构的成员,从 i n p _ f a d d r到i n p _ l p o r t,形成了该端结 点的插口对:外部 IP地址和端口号,以及本地 IP地址和端口号。 • UDP和TCP用指针inp_next和inp_prev维护一个所有Internet PCB的双向链表。它们 在表头分配一个全局 i n p c b结构(命名为 u d b和t c b),在该结构中只使用三个成员:下 一个和前一个指针,以及本地端口号。后一个成员中包含了该协议使用的下一个临时端 口号。 Internet PCB是一个传输层数据结构。 T C P、U D P和原始 I P使用它,但 I P、I C M P或I C M P 不用它。 我们还没有讲过原始 I P,但它也用 Internet PCB。与T C P和U D P不同,原始 I P在P C B中不 用端口号成员,原始

I P 只用本章中提到的两个函数:

in_pcbdetach释放PCB。第32章将讨论原始IP。

i n _ p c b a l l o c 分配 P C B ,

573

第 22 章 协议控制块 计计

描述符

描述符

描述符

描述符

插口层 协议层

所有UDP的双向循环链表 Internet协议控制块

所有TCP的双向循环链表 Internet协议控制块和相关的TCP控制块

图22-1 Internet协议控制块以及与其他结构之间的关系

22.2 代码介绍 所有PCB函数都在一个C文件和一个包含定义的头文件中,如图 22-2所示。 文



netinet/in_pcb.h netinet/in_pcb.c





i n _ p c b结构定义 PCB函数

图22-2 本章中讨论的文件

574

计计TCP/IP详解 卷 2:实现

22.2.1 全局变量 本章只引入一个全局变量,如图 22-3所示。 变



数据类型

zeroin_addr





s t r u c t i n _ a d d r 32 bit 全零IP地址

图22-3 本章中引入的全局变量

22.2.2 统计量 Internet PCB和TCP PCB都是内核的m a l l o c函数分配的 M _ P C B类型。这只是内核分配的 大约 6 0种不同类型内存的一种。例如, m b u f的类型是 M _ B U F,s o c k e t结构分配的类型是 M_SOCKET。 因为内核保持所分配的不同类型内存缓存的计数器,所以维护着几个 P C B数量的统计量。 v m s t a t -m命令显示内核的内存分配统计信息, n e t s t a t -m命令显示的是m b u f分配统计 信息。

22.3 inpcb的结构 图22-4是inpcb结构的定义。这不是一个大结构,只占 84个字节。

图22-4 inpcb 结构

43-45

i n p _ n e x t和i n p _ p r e v为U D P和T C P的所有 P C B形成一个双向链表。另外,每个

P C B都有一个指向协议链表表头的指针 (i n p _ h e a d)。对U D P表上的P C B,i n p _ h e a d总是指 向udb(图22-1);对TCP表上的PCB,这个指针总是指向 tcb。 46-49

下面四个成员: i n p _ f a d d r、i n p _ f p o r t、i n p _ l a d d r和i n p _ l p o r t,包含了

这个IP端结点的插口对:外部 IP地址和端口号,以及本地 IP地址和端口号。 PCB中以网络字节 序而不是以主机字节序维护这四个值。 运输层的 T C P和U D P都使用 Internet PCB。尽管在这个结构里保存本地和外部 I P 地址很有意义,但端口号并不属于这里。端口号及其大小的定义是由各运输层协议

575

第 22 章 协议控制块 计计

指定的,不同的运输层可以指定不同的值。 [Partridge 1987] 提出了这个问题,其中 版本1的R D P采用8 bit 的端口号,需要用 8 bit 的端口号重新实现几个标准内核程序。 版本2的RDP [Partridge和Hinden 1990] 采用16 bit端口号。实际上,端口号属于运输 层专用控制块,例如 T C P的t c p c b。可能会要求采用一种新的 U D P专用的 P C B。尽管 这个方案可行,但却可能使我们马上要讨论的几个程序复杂化。 50-51

inp_socket是一个指向该PCB的socket结构的指针,inp_ppcb是一个指针,它

指向这个 PCB的可选运输层专用控制块。我们在图 22-1中看到, i n p _ p p c b和TCP一起指向对 应的 t c p c b,但U D P不用它。 s o c k e t和i n p c b之间的链接是双向的,因为有时内核从插口 层开始,需要对应的 Internet PCB( 如用户输出 ),而有时内核从 P C B开始,需要找到对应的 socket结构(如处理收到的IP数据报)。 52

如果IP有一个到外部地址的路由,则它被保存在 ipp_route入口处。我们将看到,当收

到一个ICMP重定向报文时,将扫描所有 Internet PCB,找到那些外部 IP地址与重定向IP地址匹 配的P C B,将其i n p _ r o u t e入口标记成无效。当再次将该 P C B用于输出时,迫使 I P重新找一 条到该外部地址的新路由。 53 inp_flags成员中存放了几个标志。图 22-5显示了各标志。 描

inp_flags INP_HDRINCL INP_RECVOPTS INP_RECVRETOPTS INP_RECVDSTADDR INP_CONTROLOPTS



进程提供整个 IP首部(只有原始插口 ) 把到达IP选项作为控制信息接收 (只有UDP,还没有实现 ) 把回答的IP选项作为控制信息接收 (只有UDP,还没有实现 ) 把IP目的地址作为控制信息接收 (只有UDP) INP_RECVOPTS|INP-RECVRETOPTS|INP_RECVDSTADDR

图22-5 inp_flags 值

54

PCB中维护一个 IP首部的备份,但它只使用其中的两个成员, TOS和TTL。TOS被初始化

为0 (普通业务 ),T T L被运输层初始化。我们将看到, T C P和U D P都把T T L的默认值设为 6 4。 进程可以用 IP_TOS或IP_TTL插口选项改变这些默认值,新的值记录在 inpcb-inp_ip结构 中。以后, TCP和UDP在发送IP数据报时,却把该结构用作原型 IP首部。 55-56

进程也可以用 I P _ O P T I O N S插口选项设置外出数据报的 I P选项。函数i p _ p c b o p t s

把调用方选项的备份存放在一个 m b u f中,i n p _ o p t i o n s成员是一个指向该 m b u f的指针。每 次T C P和U D P调用i p _ o u t p u t函数时,就把一个指向这些 I P首部的指针传给 I P,I P将其插到 出去的IP数据报中。类似地, inp_moptions成员是一个指向用户 IP多播选项备份的指针。

22.4 in_pcballoc和in_pcbdetach函数 在创建插口时, T C P、U D P和原始 I P会分配一个 Internet PCB 。系统调用 s o c k e t发布 PRU_ATTACH请求。在UDP情况下,我们将在图 23-33中看到,产生的调用是 struct socket *so; int error; error = in_pcballoc(so, &udb);

图22-6是in_pcballoc函数。 1. 分配PCB,初始化为零

576

计计TCP/IP详解 卷 2:实现

图22-6 in_pcballoc 函数:分配一个 Internet PCB

36-45

i n _ p c b a l l o c使用宏M A L L O C调用内核的内存分配器。因为这些 PCB总是作为系统

调用的结果分配的,所以总能等到一个。 Net/2和早期的伯克利版本把 Internet PCB和TCP PCB都保存在mbuf中。它们的大 小分别是 8 0和1 0 8字节。 N e t / 3版本中的大小变成了 8 4和1 4 0字节,所以 T C P控制块不 再适合存放在mbuf中。Net/3使用内核的内存分配器而不是 mbuf分配两种控制块。 细心的读者会注意到图 2 - 6的例子中,为 P C B分配了 1 7个m b u f,而我们刚刚讲到 Net/3不再用mbuf存放Internet PCB和TCP PCB。但是, Net/3的确用mbuf存放Unix域 的P C B,这就是计数器所指的。 n e t s t a t输出的 m b u f统计信息是针对内核为所有协 议族分配的 mbuf,而不仅仅是Internet协议族。 bzero把PCB设成0。这非常重要,因为 PCB 中的IP地址和端口号必须被初始化成 0。

图22-7 in_pcbdetach

函数:释放一个 Internet PCB

2. 把结构链接起来 46-49

i n _ h e a d成员指向协议的 PCB表头(u d b或t c p),i n p _ s o c k e t成员指向s o c k e t结

577

第 22 章 协议控制块 计计

构,新的 PCB结构被加到协议的双向链表上 (i n s q u e),s o c k e t结构指向该 PCB。i n s q u e函 数把新的PCB放到协议表的表头里。 在发布P R U _ D E T A C H请求后,释放一个 Internet PCB,这是在关闭插口时发生的。图 2 2 - 7 显示了in_pcbdetach函数,最后将调用它。 252-263

s o c k e t结构中的 P C B指针被设成 0,s o f r e e释放该结构。如果给这个 P C B分配

的是一个有 IP选项的mbuf,则由m_free将其释放。如果该 PCB 中有一个路由,则由 rtfree 将其释放。所有多播选项都由 ip_freemoptions释放。 264-265 remque把该PCB从协议的双向链表中移走,该 PCB使用的内存被返回给内核。

22.5 绑定、连接和分用 在研究绑定插口、连接插口和分用进入的数据报的内核函数之前,我们先来看一下内核 对这些动作施加的限制规则。 1. 绑定本地IP地址和端口号 图22-8是进程在调用bind时可以指定的本地 IP地址和本地端口号的六种组合。 前三行通常是服务器的—它们绑定某个特定端口,称为服务器的知名端口 ( w e l l - k n o w n p o r t ),客户都知道这些端口的值。后三行通常是客户的—它们不考虑本地的端口,称为临 时端口(ephemeral port),只要它在客户主机上是唯一的。 大多数服务器和客户在调用 b i n d时,都指定通配 I P地址。如图 2 2 - 8所示,在第 3行和第6 行中,用*表示。 本地IP地址

本地端口

单播或广播 多播 * 单播或广播 多播 *

非零 非零 非零 0 0 0





一个本地接口,特定端口 一个本地多播组,特定端口 任何本地接口或多播组,特定端口 一个本地接口,内核选择端口 一个多播组,内核选择端口 任何本地接口,内核选择端口

图22-8 bind 的本地IP地址和本地端口号的组合

如果服务器把某个特定 I P地址绑定到某个插口上 (也就是说,不是通配地址 ),那么进入的 IP数据报中,只有那些以该特定 IP地址作为目的IP地址的IP数据报—不管是单播、广播或多 播—都被交付给该进程。自然地,当进程把某个特定单播或广播 I P地址绑定到某个插口上 时,内核验证该 IP地址与一个本地接口对应。 尽管可能,但很少出现客户程序绑定某个特定 I P地址的情况 (图2 2 - 8中的第 4行和第 5行)。 通常客户绑定通配 I P地址(图2 2 - 8中的最后一行 ),让内核根据自己选择的到服务器的路由来选 择外出的接口。 图2 2 - 8没有显示如果客户程序试图绑定一个已经被其他插口使用的本地端口时会发生什 么情况。默认情况下,如果一个端口已经被使用,进程是不能绑定它的。如果发生这种情况, 则返回 E A D D R I N U S E差错(地址正在被使用 )。正在被使用 (in use)的定义很简单,就是只要存 在一个 P C B,就把该端口作为它的本地端口。“正在被使用”的概念是相对于绑定协议的: TCP或UDP,因为TCP端口号与UDP端口号无关。

578

计计TCP/IP详解 卷 2:实现

Net/3允许进程指定以下两个插口选项来改变这个默认行为: SO_REUSEADDR 允许进程绑定一个正在被使用的端口号,但被绑定的 I P地址(包括通配 地址)必须没有被绑定到同一个端口。 例如,如果连到的接口的 I P地址是1 4 0 . 2 5 2 . 1 . 2 9,则一个插口可以被绑 定到1 4 0 . 2 5 2 . 1 . 2 9,端口 5 5 5 5;另一个插口可绑定到 1 2 7 . 0 . 0 . 1,端口 5 5 5 5;还有一个插口可以绑定到通配 I P地址,端口 5 5 5 5。在第二种和 第三种情况下调用 b i n d 之前,必须先调用 s e t s o c k o p t ,设置 SO_REUSEADDR选项。 SO_REUSEPORT 允许进程重用 I P地址和端口号,但是包括第一个在内的各个 I P地址和 端口号,必须指定这个插口选项。和 S O _ R E U S E A D D R一样,第一次绑 定端口号时要指定插口选项。 例如,如果连到的接口具有 1 4 0 . 2 5 2 . 1 . 2 9的I P地址,并且某个插口绑定 到140.252.1.29,端口6666,并指定SO_REUSEPORT插口选项,则另一 个插口也可以指定同一个插口选项,并绑定140.252.1.29,端口6666。 本节的后面将讨论在后一个例子中,当到达一个目的地址是 1 4 0 . 2 5 2 . 1 . 2 9,目的端口是 6666的IP数据报时,会发生什么情况。因为这两个插口都被绑定到该端结点上。 S O _ R E U S E P O R T是Net/3新加上的,在4.4BSD中是为支持多播而引入的。在这个 版本之前,两个插口是不可能绑定到同一个 IP地址和同一个端口号的。 不幸的是, S O _ R E U S E P O R T不是原来的标准多播源程序的内容,所以对它的支 持并不广泛。其他支持多播的系统,如 Solaris 2.x,让进程指定SO_REUSEADDR来表 明允许把多个端口绑定到同一 IP地址和相同的端口号。 2. 连接一个UDP插口 我们通常把 c o n n e c t系统调用和 T C P客户联系起来,但是 U D P客户或 U D P服务器也可能 调用c o n n e c t,为插口指定外部 I P地址和外部端口号。这就限制插口必须只与某个特定对方 交换UDP数据报。 当连接U D P插口时,会有一个副作用:本地 I P地址,如果在调用 b i n d时没有指定,会自 动被connect设置。它被设成由 IP选路指定对方所选择的本地接口地址。 图22-9显示了UDP插口的三种不同的状态,以及函数为终止各状态调用的伪代码。 本地插口

外部插口





localIP.lport

foreignIP.fport

限制到一个对方: s o c k e t ( ), b i n d ( * .lport), c o n n e c t (foreignIP, fport) s o c k e t ( ), b i n d (localIP, lport), c o n n e c t (foreignIP, fport)

localIP.lport

*.*

限制在本地接口上到达的数据报: localIP s o c k e t ( ) , b i n(dlocalIP, lport)

*.lport

*.*

接收所有发到 lport的数据报: s o c k e t ( ) , b i n d (*, lport)

图22-9 UDP插口的本地和外部 IP地址和端口号规范

前三个状态叫做已连接的 U D P插口(connected UDP socket),后两个叫做未连接的 U D P插 口(unconnected UDP socket)。两个没有连接上的 U D P插口的区别在于,第一个具有一个完全

579

第 22 章 协议控制块 计计

指定的本地地址,而第二个具有一个通配本地 IP地址。 3. 分用TCP接收的IP数据报 图2 2 - 1 0显示了主机 s u n上的三个 Te l n e t服务器的状态。前两个插口处于 L I S T E N状态,等 待进入的连接请求,加三个连接到 I P地址是140 252 111的主机上的端口 1 5 0 0。第一个监听插 口处理在接口 1 4 0 . 2 5 2 . 1 2 9上到达的连接请求,第二个监听插口将处理所有其他接口 (因为它的 本地IP地址是通配地址 )。 本地地址

本地端口

外部地址

外部端口

TCP状态

140.252.1.29 * 140.252.1.29

23 23 23

* * 140.252.1.11

* * 1500

LISTEN LISTEN ESTABLISHED

图22-10 本地端口是 23的三个TCP插口

两个具有未指定的外部 I P地址和端口号的监听插口都显示了出来,因为插口 A P I不允许 T C P服务器限制任何一个值。 T C P服务器必须 a c c e p t客户的连接,并在连接建立完成之后 (也就是说,当 T C P的三次握手结束之后 )被告知客户的 I P地址和端口号。只有到这个时候,如 果服务器不喜欢客户的 I P地址和端口号,才能关闭连接。这并不是对 T C P要求的特性,这只 是插口API通常的工作方式。 当T C P收到一个目的端口是 2 3的报文段时,它调用 i n _ p c b l o o k u p,搜索它的整个 Internet PCB表,找到一个匹配。马上我们会研究这个函数,将看到它有优先权,因为它的通 配匹配 (wildcard match)数最少。为了确定通配匹配数,我们只考虑本地和外部的 I P地址,不 考虑外部端口号。本地端口号必须匹配,否则我们甚至不考虑

P C B。通配匹配数可以是 0、

1(本地IP地址或外部 IP地址)或2(本地和外部 IP地址)。 例如,假定到达报文段来自 1 4 0 . 2 5 2 . 1 . 11,端口 1 5 0 0,目的地是 1 4 0 . 2 5 2 . 1 . 2 9,端口 2 3。 图22-11是图22-10中三个插口的通配匹配数。 本地地址

本地端口

外部地址

外部端口

TCP状态

通配匹配数

140.252.1.29 * 140.252.1.29

23 23 23

* * 140.252.1.11

* * 1500

LISTEN LISTEN ESTABLISHED

1 2 0

图22-11 从{140.252.1.11, 1500}到{140.252.1.29, 23}的到达报文段

第一个插口匹配这四个值,但有一个通配匹配 (外部I P地址)。第二个插口也和到达报文段 匹配,但有两个通配匹配 (本地和外部 I P地址)。第三个插口是一个没有通配匹配的完全匹配。 Net/3使用第三个插口,它具有最小通配匹配数。 继续这个例子,假定到达报文段来自 1 4 0 . 2 5 2 . 1 . 11,端口 1 5 0 1,目的地是 1 4 0 . 2 5 2 . 1 . 2 9, 端口23。图22-12显示了通配匹配数。 本地地址

本地端口

外部地址

外部端口

TCP状态

通配匹配数

140.252.1.29 * 140.252.1.29

23 23 23

* * 140.252.1.11

* * 1500

LISTEN LISTEN ESTABLISHED

1 2

图22-12 从{140.252.1.11, 1501}到{140.252.1.29, 23}的到达报文段

580

计计TCP/IP详解 卷 2:实现

第一个插口匹配有一个通配匹配;第二个插口匹配有两个通配匹配;第三个插口根本不 匹配,因为外部端口号不相等 (只有当P C B中的外部 I P地址不是通配地址时,才比较外部端口 号)。所以选择第一个插口。 在这两个例子中,我们没有提到到达 T C P报文段的类型:假定图 2 2 - 11中的报文段包含数 据或对一个已经建立的连接的确认,因为它是发送到一个已经建立的插口上的。我们还假定, 图2 2 - 1 2中的报文段是一个到达的连接请求 (一个S Y N ),因为它是发送给一个正在监听的插口 的。但是 i n _ p c b l o o k u p的分用代码并不关心这些。如果 T C P报文段对交付的插口来说是错 误的类型,我们将在后面看到, T C P会处理这种情况。现在,重要的是,分用代码只把 I P数 据报中的源和目的插口对的值与 PCB中的值进行比较。 4. 分用UDP接收的IP数据报 U D P数据报的交付比我们刚才研究的 T C P的例子要复杂得多,因为可以把 U D P数据报发 送到一个广播或多播地址。因为 N e t / 3 (以及大多数支持多播的系统 )允许多个插口有相同的本 地IP地址和端口,所以如何处理多个接收方的情况呢? Net/3的规则是: 1) 把目的地是广播 I P地址或多播 I P地址的到达 U D P数据报交付给所有匹配的插口。这里 没有“最好的”匹配的概念 (也就是具有最小通配匹配数的匹配 )。 2) 把目的地是单播 I P地址的到达 U D P数据报只交付给一个匹配的插口,就是具有最小通 配匹配数的插口。如果有多个插口具有相同的“最小”通配匹配数,那么具体由哪个插口来 接收到达数据报依赖于不同的实现。 图2 2 - 1 3显示了四个我们将在后面例子中使用的 U D P插口。要使四个 U D P插口具有相同的 本地端口号需要使用 S O _R E U S E A D D R或SO_REUSEPORT。前两个插口已经被连接到一个外部 IP地址和端口号,后面两个没有任何连接。 本地地址 140.252.1.29 140.252.13.63 140.252.13.63 *

本地端口 577 577 577 577

外部地址

外部端口

140.252.1.11 140.252.13.35 * *

1500 1500 * *





已连接,本地 IP = 已连接,本地 IP = 未连接,本地 IP = 未连接,本地 IP =

单播 广播 广播 通配地址

图22-13 四个本地端口为 577的UDP插口

考虑目的地是 1 4 0 . 2 5 2 . 1 3 . 6 3 ( 位于子网 1 4 0 . 2 5 2 . 1 3 上的广播地址 ) ,端口 5 7 7 ,来自 140.252.13.34,端口1500。图22-14显示它被交付给第三和第四个插口。 本地地址

本地端口

外部地址

外部端口

140.252.1.29 140.252.13.63 140.252.13.63 *

577 577 577 577

140.252.1.11 140.252.13.35 * *

1500 1500 * *



付?

不,本地和外部 IP不匹配 不,外部 IP不匹配 交付 交付

图22-14 接收从{140.252.13.34, 1500}到{140.252.13.63, 577}的数据报

广播数据报不交付给第一个插口,因为本地 IP地址和目的 IP地址不匹配,外部 IP地址和源 IP地址也不匹配。也不把它交付给第二个插口,因为外部 IP地址和源 IP地址不匹配。 对于下一个例子,考虑目的地是140.252.129(一个单播地址),端口577,来自140 252 1.111,

581

第 22 章 协议控制块 计计

端口1500。图22-15显示了把该数据报交付给哪个端口。 本地地址

本地端口

外部地址

外部端口

140.252.1.29 140.252.13.63 140.252.13.63 *

577 577 577 577

140.252.1.11 140.252.13.35 * *

1500 1500 * *



付?

交付,0个通配匹配 不,本地和外部 IP不匹配 不,本地 IP不匹配 不,2个通配匹配

图22-15 接收从{140.252.1.11, 1500}到{140.252.1.29, 577}的数据报

该数据报和第一个插口匹配,且没有通配匹配;也和第四个插口匹配,但有两个通配匹 配。所以,它被交付给第一个插口,最好的匹配。

22.6 in_pcblookup函数 in_pcblookup函数有几个作用: 1) 当TCP或UDP收到一个IP数据报时, in_pcblookup扫描协议的 Internet PCB表,寻找 一个匹配的 PCB,来接收该数据报。这是运输层对收到数据报的分用。 2) 当进程执行 b i n d系统调用,为某个插口分配一个本地 IP地址和本地端口号时,协议调 用in_pcbbind,验证请求的本地地址对没有被使用。 3) 当进程执行b i n d系统调用,请求给它的插口分配一个临时端口时,内核选了一个临时 端口,并调用 i n _ p c b b i n d检查该端口是否正在被使用。如果正在被使用,就试下一个端口 号,以此类推,直到找到一个没有被使用的端口号。 4) 当进程显式或隐式地执行 c o n n e c t系统调用时,i n _ p c b b i n d验证请求的插口对是唯 一的(当在一个没有连接上的插口上发送一个 UDP数据报时,会隐式地调用 c o n n e c t,我们将 在第23章看到这种情况 )。 在第2种、第 3种和第 4种情况下, i n _ p c b b i n d调用i n _ p c b l o o k u p。两个选项使该函 数的逻辑显得有些混乱。首先,进程可以指定 S O _ R E U S E A D D R或S O _ R E U S E P O R T插口选项, 表明允许复制本地地址。 其次,有时通配匹配也是允许的 (例如,一个到达 U D P数据报可以和一个自己的本地 I P地 址有通配符的 P C B匹配,意味着该插口将接收在任何本地接口上到达的 U D P数据报),而其他 情况下,一个通配匹配是禁止的 (例如,当连接到一个外部 IP地址和端口号时 )。 在原始的标准 I P多播代码中,出现了这样的注释“ i n _ p c b l o o k u p的逻辑比较 模糊,也没有一点说明……”。形容词模糊比较保守。 公开的I P多播码是 B S D/3 8 6的,是由 Craig Leres从4 . 4 B S D派生而来的。他修改 了该函数过载的语义,只对上面的第 1种情况使用 i n _ p c b l o o k u p。第2种和第 4种 情况由一个新函数 i n _ p c b c o n f l i c t处理。情况 3由新函数 i n _ u n i q u e p o r t处理。 把原来的功能分成几个独立的函数就显得更清楚了,但在我们描述的 N e t / 3版本中, 整个逻辑还是结合在一个函数 in_pcblookup中。 图22-16显示了in_pcblookuup函数。 该函数从协议的 P C B表的表头开始,并可能会遍历表中的每个 P C B。变量 m a t c h记录了 到目前为止最佳匹配的指针入口, m a t c h w i l d记录在该匹配中的通配匹配数。后者被初始化

582

计计TCP/IP详解 卷 2:实现

成3,比可能遇到的最大通配匹配数还大 (任何大于2的值都可以)。每次循环时, w i l d c a r d从 0开始,计数每个 PCB的通配匹配数。 1. 比较本地端口号 第一个比较的是本地端口号。如果 PCB 的本地端口和lport参数不匹配,则忽略该 PCB。

图22-16 in_pcblookuup

函数:搜索所有 PCB寻找匹配

583

第 22 章 协议控制块 计计

2. 比较本地地址 419-4 27

in_pcbl o o k u p比较PCB内的本地地址和 l a d d r参数。如果有一个是通配地址,

另一个不是,则 w i l d c a r d计数器加 1。如果都不是通配地址,则它们必须一样;否则忽略这 个PCB 。如果都是通配地址,则什么也不改变:它们不可比,也不增加 w i l d c a r d计数器。 图22-17对四种不同的情况做了小结。 PCB本地IP 不是* 不是* * *



laddr参数 * 不是* * 不是*



wildcard++ 比较IP地址,如果不相等,则略过 PCB 不能比较 wildcard++

图22-17 in_pcblookup

做的四种IP地址比较

3. 比较外部地址和外部端口号 428-437 这几行完成与我们刚才讲的同样的检查,但是用外部地址而不是本地地址。而且, 如果两个外部地址都不是通配地址,则不仅两个 I P地址必须相等,而且两个外部端口也必须 相等。图22-18对外部IP地址的比较作了总结。 PCB外部IP

faddr参数

不是* 不是* * *

* 不是* * 不是*





wildcard++ 比较IP地址和端口,如果不相等,则略过 PCB 不能比较 wildcard++

图22-18 in_pcblookup

做的四种外部 IP地址比较

可以对图 2 2 - 1 8中的第二行进行另外的外部端口号比较,因为一个 P C B不可能具有非通配 外部地址,且外部端口号为 0。这个限制是由 c o n n e c t加上的,我们马上就会看到,该函数 要求一个非通配外部 I P地址和一个非零外部端口。但是,也可能,并且通常都是具有一个通 配本地地址和一个非零本地端口。我们在图 22-10和图22-13看到过这种情况。 4. 检查是否允许通配匹配 4 3 8-4 3 9

参数flags可以被设成INPLOOKUP_WILDCARD,意味着允许匹配中包含通配匹

配。如果在匹配中有通配匹配 (w i l d c a r d非零),并且调用方没有指定这个标志位,则忽略这 个PCB。当TCP和UDP调用这个函数分用一个到达数据报时,总是把 INPLOOKUP_WILDCARD 置位,因为允许通配匹配 (记住我们用图 2 2 - 1 0和图2 2 - 1 3所作的例子)。但是,当这个函数作为 connect系统调用的一部分而调用时,为了验证一个插口对没有被使用,把 flags参数设成0。 5. 记录最佳匹配,如果找到确切匹配,则返回 440-447

这些语句记录到目前为止找到的最佳匹配。重复一下,最佳匹配是具有最小通配

匹配数的匹配。如果一个匹配有一个或两个通配匹配,则记录该匹配,循环继续。但是,如 果找到一个确切的匹配 (wildcard是0),则循环终止,返回一个指向该确切匹配 PCB的指针。 例子—分用收到的 TCP报文段 图2 2 - 1 9取自我们在图 2 2 - 11中的 T C P 的例子。假定 i n _ p c b l o o k u p 正在分用一个从 140.252.1.11即端口1500到140.252.1.29即端口23的数据报。还假定PCB的顺序是图中行的顺序。

584

计计TCP/IP详解 卷 2:实现

laddr是目的IP地址,lport是目的TCP端口,faddr是源IP地址,fport是源TCP端口。 PCB值 wildcard 本地地址

本地端口

外部地址

外部端口

140.252.1.29 * 140.252.1.29

23 23 23

* * 140.252.1.11

* * 1500

1 2 0

图22-19 laddr =140.252.1.29,lport= 23,faddr = 140.252.1.11,fport= 1500

当把第一行和到达报文段比较时,

w i l d c a r d 是 1 ( 外部 I P 地址 ) , f l a g s 被设成

I N P L O O K U P _ W I L D C A R D,所以把 m a t c h设成指向该 P C B,m a t c h w i l d设为1。因为还没有 找到确切的匹配,所以循环继续。下一次循环中, w i l d c a r d是2 (本地和外部 I P地址),因为 比 m a t c h w i l d 大,所以不记录该入口,循环继续。再次循环时,

w i l d c a r d 是 0,比

m a t c h w i l d( 1 )小,所以把这个入口记录在 m a t c h中。因为已经找到了一个确切的地址,所 以终止循环,把指向该 PCB的指针返回给调用方。 如果T C P和U D P只用 i n _ p c b l o o k u p来分用到达数据报,就可以对它进行简化。首先, 没有必要检查 f a d d r或l a d d r是否是通配地址,因为它们是收到数据报的源和目的 I P地址。 参数flags以及与相应的检测也可以不要,因为允许通配匹配。 这一节讨论了 i n _ p c b l o o k u p 函数的机制。我们在讨论

in_pcbbind和

in_pcbconnect如何调用这个函数后,将继续回来讨论它的意义。

22.7 in_pcbbind函数 下一个函数 i n _ p c b b i n d,把一个本地地址和端口号绑定到一个插口上。从五个函数中 调用它: 1) bind为某个TCP插口调用(通常绑定到服务器的一个知名端口上 ); 2) b i n d为某个 U D P插口调用 (绑定到服务器的一个知名端口上,或者绑定到客户插口的 一个临时端口上 ); 3) c o n n e c t为某个TCP插口调用,如果该插口还没有绑定到一个非零端口上 (对TCP客户 来说,这是一种典型情况 ); 4) l i s t e n为某个TCP 插口调用,如果该插口还没有绑定到一个非零端口 (这很少见,因 为是TCP 服务器调用 listen,TCP服务器通常绑定到一个知名端口上,而不是临时端口 ); 5) 从i n _ p c b c o n n e c t( 2 2 . 8节)调用,如果本地 I P地址和本地端口号被置位 (当为一个 UDP插口调用 connect,或为一个未连接 UDP插口调用sendto时,这种情况比较典型 )。 在第3种、第4种和第 5种情形下,把一个临时端口号绑定到该插口上,不改变本地 I P地址 (在它已经被置位的情况下 )。 称情形1和情形2为显式绑定 (explicit bind),情形3、4和5为隐式绑定 (implicit bind)。我们 也注意到,尽管在情形 2时,服务器绑定到一个知名端口是很正常的,但那些用远程过程调用 (RPC)启动的服务器也常常绑定到临时端口上,然后用其他程序注册它们的临时端口,该程序 维护在该服务器的 R P C程序号与其临时端口之间的映射 (例如,卷 1的2 9 . 4节描述的 S u n端口映 射器)。

585

第 22 章 协议控制块 计计

我们分三部分显示 in_pcbbind函数。图22-20是第一部分。

图22-20 in_pcbbind 函数:绑定本地地址和端口号

64-67

前两个测试验证至少有一个接口已经被分配了一个 I P地址,且该插口还没有绑定。

不能两次绑定一个插口。 68-71

这个if语句有点令人疑问。总的结果是如果 SO_REUSEADDR和SO_REUSEPORT都没

有置位,就把wild设置成INPLOOKUP_WILDCARD。 对U D P来说,第二个测试为真,因为 P R _ C O N N R E Q U I R E D对无连接插口为假,对面向连 接的插口为真。 第三个测试就是疑问所在 [ Torek 1992] 。插口标志 S O _ A C C E P T C O N N 只被系统调用 l i s t e n置位( 1 5 . 9节),该值只对面向连接的服务器有效。在正常情况下,一个 T C P服务器调 用s o c k e t、b i n d,然后调用 l i s t e n。因而,当 i n _ p c b b i n d被b i n d调用时,就清除了这 个插口标志位。即使进程调用完 s o c k e t 后就调用 l i s t e n ,而不调用 b i n d ,T C P的 P R U _ L I S T E N请求还是调用 i n _ p c b b i n d,在插口层设置 S O _ A C C E P T C O N N标志位之前,给 插口分配一个临时端口。这意味着 i f语句中的第三个测试,测试 S O _ A C C E P T C O N N是否没有 置位,总是为真。因此 if语句等价于 if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0 && ((so->so_proto->pr_flags & PR_CONNREQUIRED)==0||1) wild = INPLOOKUP_WILDCARD;

因为任何与 1作逻辑或运算的结果都为真,所以这等价于 if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0 ) wild = INPLOOKUP_WILDCARD;

这样简单且容易理解:如果任何一个 R E U S E插口选项被置位, w i l d就是 0。如果没有 R E U S E选项被置位,则把 w i l d设成I N P L O O K U P _ W I L D C A R D。换言之,当函数在后面调用 in_pcblookup时,只有在没有 REUSE选项处于开状态时,才允许通配匹配。

586

计计TCP/IP详解 卷 2:实现

in_pcbbind的下一部分,显示在图 22-22中,函数处理可选 nam参数。 只有当进程显式调用 b i n d时, n a m参数才是一个非零指针。对一个隐式的绑定

72-75

( c o n n e c t、l i s t e n或i n _ p c b c o n n e c t的副作用,本节开始的情形 3、4和5 ),n a m是一个 空指针。当指定了该参数时,它是一个含有 s o c k a d d r _ i n结构的m b u f。图2 2 - 2 1显示了非空 参数nam的四种情形。 PCB成员被设成:

nam参数 localIP

lport

inp_laddr

inp_iport

不是* 不是* * *

0 非零 0 非零

localIP localIP * *

临时端口 lport 临时端口 lport





localIP必须是本地接口 交付给i n _ p c b l o o k u p 交付给 i n _ p c b l o o k u p

图22-21 in_pcbbind 的nam 参数的四种情形

对正确的地址族的测试被注释掉了,但在函数 i n _ p c b c o n n e c t(图2 2 - 2 5 )中执行了

76-83

等价的测试。我们希望两者或者都有或者都没有。 85-94

Net/3测试被绑定的IP地址是否是一个多播组。如果是,则 SO_REUSEADDR选项被认

为与SO_REUSEPORT等价。 95-99

否则,如果调用方绑定的本地地址不是通配地址,则 ifa_ifwithaddr验证该地址

与一个本地接口对应。 注释“ y e c h ”可能是因为插口地址结构中的端口号必须是

0 ,因为 i f a _

ifwithaddr对整个结构作二进制比较,而不仅仅比较 IP地址。 这是进程在调用系统调用之前必须把插口地址结构全部置零的几种情况之一。 如果调用 b i n d ,并且插口地址结构 ( s i n _ z e r o [ 8 ] ) 的最后 8 个字节非零,则 ifa_ifwithaddr将找不到请求的接口, in_pcbbind会返回一个错误。 100-105

当调用方绑定了一个非零端口时,也就是说,进程要绑定一个特殊端口号 (图2 2 -

2 1 中 的 第 2 种 和 第 4 种 情 形 ) , 就 执 行 下 一 个 i f语 句 。 如 果 请 求 的 端 口 小 于 1 0 2 4 (I P P O R T _ R E S E R V E D),则进程必须具有超级用户的优先权限。这不是 I n t e r n e t协议的一 部分,而是伯克利的习惯。使用小于 1 0 2 4的端口号,我们称之为保留端口 (reserved port),例 如,r c m d函数 [Stevens 1990]使用的端口,r l o g i n和r s h客户程序又调用该函数,作为服务 器对它们身份认证的一部分。 106-109

然后调用函数in_pcblookup(图22-16),检测是否已经存在一个具有相同本地 IP

地址和本地端口号的 P C B。第二个参数是通配 I P地址(外部I P地址),第三个参数是一个为 0的 端口号 (外部端口号 )。第二个参数的通配值导致 i n _ p c b l o o k u p忽略该 P C B的外部 I P地址和 外部端口 —只把本地 IP 地址和本地端口号分别和 s i n - > s i n _ a d d r及l p o r t进行比较。我 们前面提到,只有当所有REUSE插口选项都没有被设置时,才把wild设成 INPLOOKUP_WILDCARD。 111

调用方的本地IP地址值存放在PCB中。如果调用方指定,它可以是通配地址。在这种情

况下,由内核选择本地 I P地址,但要等到晚些时候插口连接上时。这就是为什么说本地 I P地 址是根据外部IP地址,由IP路由选择决定。

587

第 22 章 协议控制块 计计

图22-22 in_pcbbind 函数:处理可选的 nam 参数

当调用方显式绑定端口 0,或n a m参数是一个空指针 (隐式绑定 )时,i n _ p c b b i n d的最后 一部分处理分配一个临时端口。 113-122

这个协议(TCP或UDP)使用的下一个临时端口号被维护在该协议的 PCB表的head:

t c b或u d b。除了协议的 h e a d P C B中的i n p _ n e x t和i n p _ b a c k指针外, i n p c b结构另一个 唯一被使用的元素是本地端口号。令人迷惑的是,这个本地端口在 head PCB中是主机字节序, 而在表中其他PCB上,却是网络字节序!使用从1024开始的临时端口号 (I P P O R T _ R E S E R V E D),每次加 1,直到 5 0 0 0 (I P P O R T _ U S E R R E S E R V E D),然后又从 1 0 2 4重 新开始循环。该循环一直执行到 in_pcbbind找不到匹配为止。 1. SO_REUSEADDR举例 让我们通过一些普通的例子,来了解一下 i n _ p c b b i n d与i n _ p c b l o o k u p及两个R E U S E

588

计计TCP/IP详解 卷 2:实现

插口选项之间的交互。

图22-23 in_pcbbind 函数:选择一个临时端口

1) T C P或U D P通常以调用 s o c k e t和b i n d开始。假定一个调用 b i n d的T C P服务器,指定 了通配IP 地址和它的非零知名端口 23(Telnet服务器)。还假定该服务器还没有运行,进 程没有设置 SO_REUSEADDR插口选项。 i n _ p c b b i n d把I N P L O O K U P _ W I L D C A R D作为最后一个参数,调用 i n _ p c b l o o k u p。 i n _ p c b l o o k u p中的循环没有找到匹配的 P C B,就假定没有其他进程使用服务器的知 名TCP端口,返回一个空指针。一切正常, in_pcbbind,返回0。 2) 假定和上面相同的情况,但当再次试图启动服务器时,该服务器已经开始运行。 当调用i n _ p c b l o o k u p时,它发现了本地插口为 {*, 23}的PCB。因为w i l d c a r d计数 器是0,所以 i n _ p c b l o o k u p返回指向这个入口的指针。因为 r e u s e p o r t是0,所以 in_pcbbind返回EADDRINUSE。 3) 假定与上面相同的情况,但当第二次试图启动服务器时,指定了 S O _ R E U S E A D D R插口 选项。 因为指定了这个插口选项,所以 i n _ p c b b i n d在调用i n _ p c b l o o k u p时,最后一个参 数为0。但本地插口为 {*, 23}的P C B仍然匹配,因为 i n _ p c b l o o k u p无法比较两个通 配地址 (图2 2 - 1 7 ),所以 w i l d c a r d为0。i n _ p c b b i n d又返回 E A D D R I N U S E,避免启 动两个具有相同本地插口的服务器例程,不管是否指定了 SO_REUSEADDR。 4) 假定有一个 Te l n e t服务器已经以本地插口 {*, 23} 开始运行,而我们试图以另一个本地 插口{140.252.13.35,23}启动另一个服务器。 假定没有指定 S O _ R E U S E A D D R ,调用 i n _ p c b l o o k u p 时,最后一个参数为 INPLOOKUP_WILDCARD。当它与含有*.23的PCB比较时,wildcard计数器被设为1。 因为允许通配匹配,所以在扫描完所有 TCP PCB 后,就把这个匹配作为最佳匹配。 in_pcbbind返回EADDRINUSE。 5) 这个例子与上一个相同,但为第二个试图绑定本地插口 {140.252.13.35, 23}的服务器指 定了SO_REUSEADDR插口选项。 现在, i n _ p c b l o o k u p的最后一个参数是 0,因为指定了插口选项。当与本地插口为 {*, 23}的P C B比较时, w i l d c a r d计数器为 1,但因为最后的 f l a g s参数是0,所以跳 过这个入口,不把它记作匹配。在比较完所有 TCP PCB 后,函数返回一个空指针, in_pcbbind返回0。

589

第 22 章 协议控制块 计计

6) 假定当我们试图以本地插口{*, 23} 启动第二个服务器时,第一个Telnet服务器以本地插口 {140.252.13.35, 23}启动。与前面的例子一样,但这一次我们以相反的顺序启动服务器。 第一个服务器的启动没有问题,假定没有其他插口绑定到端口 2 3。当我们启动第二个 服务器时,i n _ p c b l o o k u p的最后一个参数是 I N P L O O K U P _ W I L D C A R D,假定没有指 定S O _ R E U S E A D D R插口选项。当和具有本地插口 {140.252.13.35, 23} 的P C B比较时, w i l d c a r d被设成1,记录这个入口。在比较完所有 TCP PCB后,返回指向这个入口的 指针。导致 in_pcbbind返回EADDRINUSE。 7) 如果我们启动同一个服务器的两个例程,并且都是非通配本地 I P地址,会发生什么情 况?假定我们以本地插口 {140.252.13.35, 23}启动第一个 Te l n e t服务器,然后试图用本 地插口{127.0.0.1, 23}启动第二个服务器,且不指定 SO_REUSEADDR。 当第二个服务器调用 i n _ p c b b i n d时,它调用 i n _ p c b l o o k u p ,最后一个参数是 INPLOOKUP_WILDCARD。当比较具有本地插口{140.252.13.35, 23}的PCB时,因为本地 IP地址不相等,所以跳过它。in_pcblookup返回一个空指针,in_pcbbind返回0。 从这个例子中我们看到, SO_REUSEADDR插口选项对非通配 IP地址没有影响。事实上, 只有当wildcard大于0 时,也就是说,当PCB入口具有一个通配IP地址,或者绑定的IP 地址是一个通配地址时,才检查in_pcblookup中的INPLOOKUP_WILDCARD标志位。 8) 作为最后一个例子,假定我们试图启动同一服务器的两个例程,具有相同的非通配本 地IP 地址127.0.0.1。 启动第二个服务器时,in_pcblookup总是返回具有相同本地插口的匹配PCB。不管是否 指定SO_REUSEADDR插口选项,都发生这种情况,因为对这种比较,wildcard计数器总 是0。因为in_pcblookup返回一个非空指针,所以in_pcbbind返回EADDRINUSE。 从这些例子中,我们可以指出本地 I P地址和 S O _ R E U S E A D D R插口选项的绑定规则。这些 规则如图 2 2 - 2 4所示。假定 l o c a l I P1和l o c a l P2是在本地主机上有效的两个不同的单播或广播 I P 地址,l o c a l m c a s t I P是一个多播组。我们还假定进程要绑定到一个已经绑定到某个已存在 P C B 的非零端口号。 我们需要区分单播或多播地址和一个多播地址,因为我们看到, i n _ p c b b i n d认为对多 播地址, SO_REUSEADDR与SO_REUSEPORT是一样。 存在PCB 试图绑定 LocalIP1 localIP1 localIP1 * * localIP1

localIP1 localIP2 * localIP1 * localIP1

SO_REUSEADDR





错误 正确 错误 错误 错误 错误

错误 正确 正确 正确 错误 正确

图22-24 SO_REUSEADDR





每个IP地址和端口一个服务器 每个本地接口一个服务器 一个接口一个服务器,其他接口一个服务器 一个接口一个服务器,其他接口一个服务器 不能复制本地插口 (和第一个例子一样 ) 多个多播接收方

插口选项对绑定本地 IP地址的影响

2. SO_REUSEPORT插口选项 Net/3中对SO_REUSEPORT的处理改变了in_pcbbind的逻辑,只要指定了SO_REUSEPORT, 就允许复制本地插口。换言之,所有服务器都必须同意共享同一本地端口。

22.8 in_pcbconnect函数 函数in_pcbconnect为插口指定 IP地址和外部端口号。有四个函数调用它:

590

计计TCP/IP详解 卷 2:实现

1) connect为某个TCP插口(某个TCP客户的请求 )调用; 2) connect为某个UDP插口(对UDP客户是可选的, UDP服务器很少见)调用; 3) 当在一个没有连接上的 UDP插口(普通)上输出数据报时从 sendto调用; 4) 当一个连接请求 (一个S Y N报文段 )到达一个处于 L I S T E N状态(对T C P服务器是标准的 ) 的TCP插口时, tcp_input调用。 在以上四种情况下,当调用 i n _ p c b c o n n e c t时,通常,但不要求,不指定本地 IP 地址 和本地端口。因此,在没有指定的情形下,由 i n _ p c b c o n n e c t的一个函数给它们赋一个本 地的值。 我们将分四个部分讨论 in_pcbconnect函数。图22-25显示了第一部分。

图22-25 in_pcbconnect

函数:验证参数,检查外部 IP地址

1. 确认参数 130-143

n a m参数指向一个包含 s o c k a d d r _ i n结构以及外部 I P地址和端口号的 m b u f。这

些行确认参数并验证调用方不打算连接到端口号为 0的端口上。 2. 特别处理到 0.0.0.0和255.255.255.255的连接 134-160

对全局变量 i n _ i f a d d r 的检查证实已配置了一个 I P接口。如果外部地址是

0.0.0.0(INADDR_ANY),则用最初的IP接口的IP地址代替0.0.0.0。这就是说,调用进程是连接到 这个主机上的一个对等实体的。如果外部 I P地址是255.255.255.255 (I N A D D R _ B R O A D C A S T),

591

第 22 章 协议控制块 计计

而且原来的接口支持广播,则用原来接口的广播地址代替 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5。这样, U D P应用 程序无需计算它的 IP 地址,就可以在原来的接口上广播 — 它可以简单地把数据报发送给 255.255.255.255,由内核把这个地址转换成该接口合适的 IP地址。 下一部分代码,如图 2 2 - 2 6所示,处理没有指定本地地址的情况。对 T C P和U D P客户程序 来说,本节开始的表中的情形 1、2和3是非常普遍的。

图22-26 in_pcbconnect

3. 如果路由不再有效,则释放该路由

函数:没有指定本地 IP地址

592

计计TCP/IP详解 卷 2:实现

164-175

如果P C B中含有一条路由,但该路由的目的地址和已经连接上的外部地址不同,

或者SO_DONTROUTE插口选项被置位,则放弃该路由。 为了理解为什么一个 P C B会含有一条相关路由,考虑本节开始的表中的情形 3:每次在一 个未连接上的插口上发送 UDP数据报时,就调用in_pcbconnect。每次进程调用sendto时, U D P输出函数调用 i n _ p c b c o n n e c t、i p _ o u t p u t和i n _ p c b d i s c o n n e c t。如果在该插口 上发送的所有数据报都具有相同的目的 I P地址,则第一次通过 i n _ p c b c o n n e c t时,就分配 了一条路由,从此时开始可以使用该路由。但是,因为 UDP应用程序可能在每次调用 s e n d t o 时,都向不同的 I P地址发送数据报,所以必须比较目的地址和保存的路由。当目的地址改变 时,就放弃该路由。 ip_output也作同样的检查,这看起来似乎是多余的。 S O _ D O N T R O U T E插口选项告诉内核旁路掉正常的选路决策,把该 I P数据报发到本地连接 的接口,该接口的 IP网络地址和目的地址的网络部分匹配。 4. 获取路由 176-185

如果没有置位SO_DONTROUTE插口选项,则PCB中没有到目的地的路由,就要调

用rtalloc获取一条路由。 5. 确定外出的接口 186-205

这一节代码的意图是让 i a指向一个接口地址结构 (i n _ i f a d d r,6.5节),该结构

中包含了该接口的 I P地址。如果 P C B中的路由仍然有效,或者如果 r t a l l o c找到一条路由, 并且该路由不是到回环接口的,则使用相应的接口。否则,调用

i f a _ w i t h d s t a d d r和

i f a _ w i t h n e t检查该外部 IP地址是否在一个点到点链路的另一端,或者位于一个连到的网络 上。两个函数都要求插口地址结构中的端口号为 0,以便在调用期间保存在 f p o r t中。如果失 败,就用原来的 IP 地址(in_ifaddr),如果没有配置接口 (in_ifaddr为0),则返回错误。 图22-27显示了in_pcbconnect的下一部分,处理目的地址是多播地址的情况。 206-223

如果目的地址是一个多播地址,且进程指定了多播分组的外出接口

(用

I P _ M U L T I C A S T _ I F插口选项 ),则该接口的 I P地址被用作本地地址。搜索所有 I P接口,找到 与插口选项所指定接口的匹配。如果该接口不存在,则返回错误。 224-225

图 2 2 - 2 6的开头是处理通配本地地址情形的完整代码。指向本地接口

ia的

sockaddr_in结构的指针保存在 ifaddr中。 in_pcblookup的最后一部分显示在图 22-28中。 6. 验证插口对是唯一的 227-233

i n _ p c b l o o k u p验证插口对是唯一的。外部地址和外部端口号是指定给

i n _ p c b c o n n e c t的参数的值。本地地址是已经绑定到该插口的值,或者是 i f a d d r中我们刚 刚介绍的代码计算出来的值。本地端口可以是 0,对T C P客户程序来说这是典型的。我们将在 这部分代码的后面看到,为本地端口选择了一个临时端口。 这个测试避免从相同的本地地址和本地端口上建立两个到同一外部地址和外部端口的 TCP连接。例如,如果我们与主机 s u n上的回显服务器建立了一个 TCP连接,然后试图从同一 本地端口 ( 8 8 8 8,用- b选项指定 )建立另一条到同一服务器的连接,调用 i n _ p c b l o o k u p后返 回一个匹配,导致 connect返回差错EADDRINUSE(我们用卷1附录C的sock程序)。 bsdi S sock -b 8888 sun echo & 启动后台的第一个 bsdi S sock -A -b 8888 sun echo 然后再试一次 connect() error: Address already in use

593

第 22 章 协议控制块 计计

图22-27 in_pcbconnect

图22-28 in_pcbconnect

函数:目的地址是一个多播地址

函数:验证插口对是唯一的

我们指定- A选项,设置SO_REUSEADDR插口选项,使 bind成功,但是connect不成功。 这是一个人为的例子,因为我们显式地把两个插口都绑定到同一本地端口上 (8888)。在正常情 形下,主机 b s d i上的两个不同客户程序连接到 s u n的回显服务器上,当第二个客户程序调用 图22-28中的in_pcblookup函数时,本地端口将是 0。 这个测试也避免了两个 U D P插口从相同的本地端口上连接到同一个外部地址。但这个测 试不能避免两个 U D P插口从同一个本地端口上交替地向同一个外部地址发送数据报,只要它 们都不调用 c o n n e c t。因为 U D P插口在 s e n d t o系统调用的过程中,只是临时连接到一个对 等实体上。 7. 隐式绑定和分配临时端口

594

计计TCP/IP详解 卷 2:实现

234-238

如果插口的本地地址仍然是通配匹配的,则把它设置成 i f a d d r中保存的值。这

是一个隐式绑定: 2 2 . 7节开始时讲的情形 3、4和5。首先,检查本地端口是否已经被绑定,如 果没有, i n _ p c b b i n d 就把该插口绑定到一个临时端口。调用

i n _ p c b b i n d 和给

inp_laddr赋值的顺序很重要,因为如果本地地址不是通配地址,则 in_pcbbind会失败。 8. 把外部地址和外部端口存放在 PCB中 239-240

这个函数的最后一步设置 P C B的外部I P地址和外部端口号成员。如果这个函数成

功返回,我们就能保证 PCB中的插口对 —本地的和外部的—都有了特定的值。 IP源地址与外出接口地址 在IP数据报的源地址和用来发送该数据报接口的 IP地址之间有些微妙的差别。 T C P和U D P把P C B成员 i n p _ l a d d r用作该 I P数据报的源地址。它可由进程设成任何被 bind配置的接口的IP 地址(在in_pcbbind中调用ifa_ifwithaddr验证应用程序想要的本 地地址)。只有当本地地址是一个通配地址时, i n _ p c b c o n n e c t才给它赋值。而当这种情况 发生时,本地地址是根据外出接口分配的 (因为目的地址已知 )。 但是,外出接口也是根据目的 I P地址,由 i p _ o u t p u t确定的。在多接口主机上,当进程 显式绑定一个不同于外出接口的本地地址时,源地址有可能是一个本地接口的 I P地址,且该 接口不是外出的接口。这种情况是允许的,因为 Net/3选择了弱端系统模式 (8.4节)。

22.9 in_pcbdisconnect函数 i p _ p c b d i s c o n n e c t把U D P插口断连。把外部 I P地址设成全 0 (I N A D D R _ A N Y),外部端 口号设成0,就把外部相关内容删除了。 这是在已经在一个未连接上的 U D P插口上发送了一个数据报后,在一个连接上的 U D P插 口上调用 c o n n e c t 时做的。在第一种情况下,调用 i n _ p c b c o n n e c t 把插口临时连接到目的地,

s e n d t o 的次序是: U D P 调用

u d p _ o u t p u t 发送数据报,然后

in_pcbdisconnect删除临时连接。 当关闭插口时,不调用 i n _ p c b d i s c o n n e c t,因为 i n _ p c b d e t a c h处理释放 P C B。只 有当一个不同的地址或端口号要求重用该 PCB时,才断连。 图22-29显示了in_pcbdisconnect函数。

图22-29 in_pcbdisconnect

函数:与外部地址和端口号断连

如果该 P C B不再有文件表引用 (S S _ N O F D R E F置位),则i n _ p c b d e t a c h(图2 2 - 7 )释放该 PCB。

595

第 22 章 协议控制块 计计

22.10 in_setsockaddr和in_setpee r a d d r函数 g e t s o c k n a m e系统调用返回插口的本地协议地址 (例如, I n t e r n e t插口的 IP 地址和端口 号 ), g e t p e e r n a m e 系统调用返回外部协议地址。两个系统调用终止时,都发布一个 P R U _ S O C K A D D R 或 P R U _ P E E R A D D R 请求。然后协议调用 i n _ s e t s o c k a d d r 或 in_setpeeraddr。图22-30显示了以上的第一种情况。

图22-30 in_setsockaddr

函数:返回本地地址和端口号

参数n a m是一个指针,该指针指向一个用来存放结果的 m b u f:一个s o c k a d d r _ i n结构, 系统调用复制给进程的备份。该代码填写插口地址结构的内容,并把

IP 地址和端口号从

Internet PCB拷贝到sin_addr和sin_port成员中。 图2 2 - 3 1显示了 i n _ s e t p e e r a d d r函数。它基本上等同于图 2 2 - 3 0中的代码,但从 P C B中 拷贝了外部 IP地址和端口号。

图22-31 in_setpeeraddr

函数:返回外部地址和端口号

22.11 in_pcbnotify、in_rtchange和i n _ l o s i n g函数 当收到一个 I C M P差错时,调用 i n _ p c b n o t i f y函数,把差错通知给合适的进程。通过 对所有的 P C B搜索一个协议 ( T C P或U D P ),并把本地和外部 I P地址及端口号与 I C M P差错返回 的值进行比较,找到“合适的进程”。例如,当因为一些路由器丢掉了某个 T C P报文段而收到

596

计计TCP/IP详解 卷 2:实现

ICMP源抑制差错时, TCP必须找到产生该差错的连接的 PCB,放慢在该连接上的传输速度。 在显示该函数之前,我们必须回顾一下它是怎样被调用的。图 2 2 - 3 2总结了处理 I C M P差 错时调用的函数。两个有阴影的椭圆是本节描述的函数。

或 协议的控制 输入函数

和 pfctlinput: 所有 协议的控制输入函数

其他ICMP 报文

(见正文)

(软件中断)

图22-32 ICMP差错处理总结

当收到一个 I C M P报文时,调用 i c m p _ i n p u t。I C M P的五种报文按差错来划分 (图11 - 1和 图11-2): • 目的主机不可达; • 参数问题; • 重定向; • 源抑制; • 超时。 重定向的处理不同于其他四个差错。所有其他的 ICMP报文(查询)的处理见第 11章。 每个协议都定义了它的控制输入函数,即 p r o t o s w结构( 7 . 4节)中的p r _ c t l i n p u t入口。 对T C P和U D P,它们分别称为 tc p _ c t l i n p u t和u d p _ c t l i n p u t,我们将在后面几章给出它 们的代码。因为收到的 I C M P差错中包含了引起差错的数据报的 I P首部,所以引起该差错的协 议( T C P或U D P )是已知的。这五个 I C M P差错中的四个将引起对协议的控制输入函数的调用。

597

第 22 章 协议控制块 计计

重定向的处理不同:调用函数 p f c t l i n p u t,它继续调用协议族 ( I n t e r n e t )中所有协议的控制 输入函数。 TCP和UDP是Internet协议族中仅有的两个具有控制输入函数的协议。 重定向的处理是特殊的,因为它们不仅影响产生重定向的数据报,还将影响所有到该目 的地的IP数据报。另一方面,其他四个差错只需由产生差错的协议进行处理。

图22-33 in_spcbnotify

函数:把差错通知传给进程

598

计计TCP/IP详解 卷 2:实现

有关图2 2 - 3 2我们要做的最后一点说明是, T C P在处理源抑制差错时,与其他差错的处理 不同,而重定向由 i n _ p c b n o t i f y 特别处理:不管引起差错的是什么协议,都调用 in_rtchange函数。 图2 2 - 3 3显示了 i n _ p c b n o t i f y函数。当 T C P调用它时,第一个参数是 t c b的地址,最后 一个参数是函数 t c p _ n o t i f y 的地址。对 U D P来说,这两个参数分别是 u d b的地址和函数 udp_notify的地址。 1. 验证参数 306-324 验证cmd参数和目的地址族。检测外部地址,保证它不是 0.0.0.0。 2. 特殊处理重定向 325-338 如果差错是重定向,则对它的处理是特殊的 (差错PRC_HOSTDEAD是一种旧的差错, 由IMP产生。目前的系统再也看不到这种差错了—它是一个历史产物 )。外部端口、本地端口 和本地地址都被设成全 0,这样后面的 f o r循环就不会比较它们了。对于重定向,我们需要该 循环只根据外部 IP 地址选出接收通知的PCB,因为主机是在这个IP地址上接收到重定向的。而 且,为重定向调用的函数是 in_rtchange(图22-34),而不是调用方指定的notify参数。 339

全局数组inetctlerrmap把协议无关差错码 (图11-19中的PRC_xxx值)映射到它对应的

Unix的errno值(图11-1的最后一栏 )。 3. 为所选的PCB调用通知函数 341-353

这个循环选择要通知的 P C B。可以通知多个 P C B—该循环在找到匹配后仍然继

续。第一个 i f语句结合了五个检测,如果这五个中有任一个为真,则跳过该 P C B:( 1 )如果外 部地址不相等; ( 2 )如果该 P C B没有对应的 s o c k e t结构;( 3 )如果本地端口不相等; ( 4 )如果本 地地址不相等;或 (5)如果外部端口不相等。外部地址必须匹配,但只有当对应的参数非零时, 才比较其他三个外部和本地参数。当找到一个匹配时,调用 notify函数。 22.11.1 in_rtchange函数 我们看到,当 ICMP差错是一个重定向时, i n _ p c b n o t i f y调用i n _ r t c h a n g e函数。对 所 有 外 部地址与已重定向的

I P 地址匹配的 P C B, 都调用该函数。图 2 2 - 3 4 显示了

in_rtchange函数。

图22-34 in_rtchange 函数:使路由无效

599

第 22 章 协议控制块 计计

如果该PCB中有路由,则rtfree释放该路由,且该 PCB 成员被标记为空。此时,我们不 用重定向返回的路由来更新路由。当这个 P C B被再次使用时, i p _ o u t p u t会根据内核的选路 表重新分配新的路由,而该选路表是在调用 pfctlinput之前,由重定向报文更新的。 22.11.2 重定向和原始插口 让我们来研究一下重定向、原始插口和缓存在 P C B中的路由之间的交互。如果我们运行 P i n g程序,该程序使用一个原始插口,收到来自被 p i n g的I P地址发来的 I C M P重定向差错。 Ping程序继续使用原来的路由,而不是已重定向的路由。我们可以从以下过程来看。 我们从位于 1 402 521 网络上的 g e m i n i主机 p i n g位于14 025 213 网络上的 s v r 4主机。 gemini的默认路由是gateway,但分组应该被发送到路由器netb。图22-35显示了这个安排。 客户 重新选路到140.252.1.183 ICMP回显请求 以太网140.252.1

目的地

以太网140.252.13

图22-35 ICMP重定向举例

我们希望gateway在收到第一个ICMP回显请求时,发一个重定向。

选项- s使每隔一秒发送一次 I C M P回显请求,选项 - v打印每个收到的 I C M P报文(不仅仅是 ICMP回显回答)。 每个I C M P回显请求引出一个重定向,但 p i n g使用的原始插口从来不通知重定向改变它正 在使用的路由。第一次计算出来并被保存在

P C B 中的路由,使 I P 数据报被发送到路由器

g a t e w a y{ 1 4 0 . 2 5 2 . 1 . 4 },应该更新它,使数据报能被发送到路由器 n e t b{ 1 4 0 . 2 5 2 . 1 . 1 8 3 }上。 我们看到, gemini上的内核接收ICMP重定向,但它们被略过了。 如果我们终止ping程序,并重新运行它,我们就再也看不到重定向了: gemini $ ping -sv svr4

600

计计TCP/IP详解 卷 2:实现

PING 140.252.13.34: 56 data bytes 64 bytes from svr4 (140.252.13.34): icmp_seq=0,time=388. ms 64 bytes from svr4 (140.252.13.34): icmp_seq=1. time=363. ms

这个不正常的原因是原始 I P插口代码 (第3 2章)没有控制输入函数。只有 T C P和U D P有控制 输入函数。当收到重定向时, ICMP更新内核的选路表,调用 p f c t l i n p u t(图22-32)。但是因 为原始IP协议没有控制输入函数,所以不释放与Ping的原始插口相关的 PCB中高速缓存的路由。 但是,当我们第二次运行 p i n g程序时,根据内核更新后的选路表分配路由,所以我们看不到 重定向了。 22.11.3 ICMP差错和UDP插口 插口A P I令人迷惑的一部分是,不把在 U D P插口上收到的 I C M P差错传给应用程序,除非 该应用程序在该插口上发布 c o n n e c t,限制该插口的外部 IP 地址和端口号。现在我们来看一 下 in_pcbnotify是如何实施这一限制的。 考虑某个 I C M P 插口不可达,这大概是

U D P 插口上最普通的一种 I C M P 差错了。

i n _ p c b n o t i f y的d s t参数内的外部 IP 地址和外部端口号是引起 I C M P差错的 I P地址和端口 号。但是,如果该进程已经在该插口上发布

c o n n e c t 命令,则 P C B 的i n p _ f a d d r 和

i n p _ f p o r t成员都是 0,避免 i n _ p c b n o t i f y在该插口上调用 n o t i f y函数。图 2 2 - 3 3中的 for循环将跳过每个 UDP PCB。 产生这个限制的原因有两个。首先,如果正在发送的进程有一个未连接上的

U D P插口,

则该插口对中唯一的非零元素是本地端口 (假定该进程不调用 b i n d)。这是i n _ p c b n o t i f y在 分用进入的 I C M P差错,并把它传给正确进程时,唯一可用的值。尽管很少发生,但也可能有 多个进程都绑定到相同的本地端口上,所以具体由哪个进程接收 I C M P差错就不明确了。还有 一种可能就是,发送引起 I C M P差错数据报的进程已经终止了,而另一个进程又开始运行并使 用同一本地端口。这也不太可能,因为临时端口是从 1 0 2 4到5 0 0 0按顺序分配的,只有循环一 遍以后才可能重用同一端口号 (图22-23)。 这个限制的第二个原因是,内核给进程的差错通知—一个e r r n o值—是不够的。考 虑某个进程连续三次在一个未连接上的 UDP插口上调用 s e n d t o函数,向三个不同的目的地发 送一个U D P数据报,然后用 r e c v f r o m等待回答。如果其中一个数据报生成一个 I C M P端口不 可达差错,且内核将向该进程发布的 r e c v f r o m返回对应的差错 (E C O N N R E F U S E D),那么, errno值并没有告诉进程是哪个数据报产生了该差错。内核具有ICMP差错所要求的所有信息, 但是插口API并不提供手段把这些信息返回给该进程。 因此,如果进程想要得到在某个 U D P插口上的这些 I C M P差错通知,在设计时就必须决定 插口只能连接到一个对等实体上。如果在该连接上的插口返回 E C O N N R E F U S E D差错,毫无疑 问就是该对等实体产生的差错。 还有一种远程可能性,会把 I C M P差错交付给错误的进程。假设某个进程发送了一个 U D P 数据报,引起一个 I C M P差错,但它在收到该差错之前终止了。另一个进程在收到该差错之前 开始运行,并且绑定到同一个本地端口,连接到相同的外部地址和外部端口上,导致这个新 进程接收到前面的 I C M P差错。由于U D P缺少内存,所以无法避免这种情况的发生。我们将看 到TCP用它的TIME_WAIT状态处理这个问题。 在我们前面的例子中,应用程序绕开这个限制的一个办法是使用三个连接上的 U D P插口,

601

第 22 章 协议控制块 计计

而不是一个未连接上的插口,并在其中任意一个有收到的数据报或差错要读写时,调用 select函数来确定。 这里我们有一种情形是内核有足够的信息而 A P I (插口)的信息不足。大多数 U n i x 系统V及其他常见的 A P I ( T L I ),其逆为真: T L I函数t _ r c v u d e r r可以返回对等实体 的I P地址、端口号以及一个差错值。但大多数 T C P / I P的S V R 4流实现都不为 I C M P提 供手段,把差错传递给一个未连接上的 UDP端节点。 在理想情况下, i n _ p c b n o t i f y把ICMP差错交付给所有匹配的 UDP插口,即使 唯一的非通配匹配是本地端口。返回给进程的差错将包括产生差错的目的 I P地址和 目的UDP端口,允许进程确定该差错是否是它发送的数据报产生的。 22.11.4 in_losing函数 处理PCB的最后一个函数图 22-36的i n _ l o s i n g。当TCP的某个连接的重传定时器连续第 三次超时时,调用该函数。

图22-36 in_losing 函数:使高速缓存路由信息无效

1. 产生选路报文 361-374

如果P C B中有一个路由,则丢掉该路由。用要失效的高速缓存路由的有关信息填

充一个 r t _ a d d r i n f o结构。然后调用 r t _ m i s s m s g函数,从 R T M _ L O S I N G类型的选路插口 中生成一个报文,指明有关该路由的问题。 2. 删除或释放路由

602

计计TCP/IP详解 卷 2:实现

375-384

如果高速缓存路由是由一个重定向生成的 ( R T F _ D Y N A M I C 置位 ),则用请求

R T M _ D E L E T E调用r t r e q u e s t,删除该路由。否则释放高速缓存的路由,这样,当该插口上 有下一个输出时,为它重新分配一条到目的地的路由—希望是一条更好的路由。

22.12 实现求精 毫无疑问,这一章我们遇到的最耗时的算法是 i n _ p c b l o o k u p做的对PCB 的线性搜索。 22.6节一开始,我们就注意到有四种情况会调用这个函数。可以忽略对 b i n d和c o n n e c t的调 用,因为 T C P和U D P在分用每个收到的 I P数据报时,调用它们的次数比调用 i n _ p c b l o o k u p 的少得多。 后面几章我们将看到, T C P和U D P试图帮助这个线性搜索,它们都维护一个指向该协议 引用的最后一个 P C B的指针:一个单入口高速缓存。如果高速缓存的 P C B的本地地址、本地 端口、外部地址和外部端口与收到的数据报的值匹配,则协议根本就不调用 in_pcblookup。 如果协议数据适合分组列模型 [ J a i n和Routhier 1986] ,这个简单的高速缓存效果很好。但是, 如果数据不适合这个模型,例如,看起来象联机交易处理系统的数据入口,则单入口高速缓 存的效率很低[McKenney和Dove 1992]。 一个稍好一点的 P C B安排的建议是,当引用某个 P C B时,把它移到该 P C B表的最前面 ( [ M c K e n n e y和Dove 1992] 把这个想法给了 Jon Crowcroft ;[ P a r t r i d g e和Pink 1993]把它给了 Gary Delp)。移动 P C B很容易,因为该表是一个双向链表,而且 i n _ p c b l o o k u p的第一个参 数是一个指向该表表头的指针。 [ M c K e n n e y和Dove 1992]把原始的 N e t / 1实现(没有高速缓存 ),一种提高的单入口发送-接 收高速缓存,“移到最前面”启发算法,以及他们自己的使用散列链的算法做了比较。他们指 出,在散列链上维护一个 P C B的线性表比其他算法的性能提高了一个数量级。散列链的唯一 耗费是需要内存存放散列链的链头,以及计算散列函数。他们也考虑把“移到最前面”启发 算法与他们的散列链算法结合,结论是只增加一些散列链,更为简单。 BSD线性搜索和散列表搜索的另一个比较是在 [Hutchinson和Peterson 1991]中。他们指出, 随着散列表中插口数量的增加,分用一个进入的 U D P数据报所需要的时间是常量,但线性搜 索所需要的时间随插口数量的增加而增加。

22.13 小结 每个Internet插口都有一个相关的 Internet PCB:TCP、UDP和原始IP。它包含了 Internet插 口的一般信息:本地和外部 I P地址,指向一个路由结构的指针等等。给定协议的所有 P C B都 放在该协议维护的一个双向链表上。 本章中,我们研究了多个操作 PCB的函数,对其中的三个作了详细的讨论: 1) TCP 和U D P调用i n _ p c b l o o k u p分用每个进入的数据报。它选择接收数据报的插口, 考虑通配匹配。 i n _ p c b b i n d也调用这个函数来验证本地地址和本地进程是唯一的; i n _ p c b c o n n e c t 调用这个函数验证本地地址、本地进程、外部地址和外部进程的组合是唯一的。 2) i n _ p c b b i n d显式或隐式地把一个本地地址和本地端口号绑定到一个插口。当进程调 用b i n d时,发生显式绑定;当一个 T C P客户程序调用 c o n n e c t而不调用 b i n d时,或当一个

603

第 22 章 协议控制块 计计

UDP进程调用sendto或connect而不调用bind时,发生隐式绑定。 3) i n _ p c b c o n n e c t设置外部地址和外部进程。如果进程还没有设置本地地址,计算一 条到外部地址的路由,结果的本地接口成为本地地址。如果进程还没有设置本地端口, in_pcbbind为插口选择一个临时端口。 图2 2 - 3 7对多种T C P和U D P应用程序以及存放在 P C B中的本地地址,本地端口、外部地址 和外部端口的值做了总结。我们还没有讨论完图 2 2 - 3 7中T C P和U D P进程的所有动作,将在后 面的章节中继续讨论。 应用程序

T C P 客 户 程 序 : c o n n e c t( f o re i g n I P, fport)

本地地址:

本地端口:

外部地址:

外部端口:

inp_laddr

inp_lport

inp_faddr

inp_fport

in_pcbconnect

in_pcbconnect

调用 r t a l l o c 为

调用 i n _ p c b b i n d选

f o re i g n I P分配路由。

择临时端口。

foreignIP

fport

本地地址是本地接口 T C P客户程序: b i n d

localIP

lport

foreignIP

fport

in_pcbconnect

lport

fport

fport

i n _ p c b b i n d选择

foreignIP

fport

(localIP, lport) connect (foreignIP,fport) T C P 客 户 程 序 : bind(*, lport) connect

调用 r t a l l o c 为

(foreignIP, fport)

foreignIP分配路由。 本地地址是本地接口

T C P 客户程序 : b i n d

localIP

临时端口

(l o c a l I P, 0) c o n n e c t (foreignIP, fport) localIP

TCP服务器程序: b i n d( l o c a l I P,

lport

I P首 部 内 的 源 地址

lport )

T C P 首部内 的源端口地址

l i s t e n()accept() TCP服务器程序:bind (* , lport ) l i s t e n ( )

I P首部里的目的地

lport

s e n d t o(foreignIP, fport )

T C P首部内

送 完 数 据 报 后 , 的源端口地址



置位为0.0.0.0

a c c e p t() U D P 客 户 程 序 :

f o re i g n I P。发

foreignIP

f p o r t 。发送

in_pcbconnect

in_pcbconnect

调用 r t a l l o c 为

调用 i n _ p c b b i n d选

完数据报后,

f o reignIP 分配路由。

择临时端口。后面调

置位为0

本地地址是本地接口。

用s e n d t o时不改变

在发送完数据报之后, 置位为0.0.0.0 U D P 客 户 程 序 :

in_pcbconnect

in_pcbconnect

c o n n e c t ( f o re i g n I P,

调用 r t a l l o c 为

调用 i n _ p c b b i n d选

fport) w rite()

f o re i g n I P分配路由。

择临时端口。后面调

本地地址是本地接

用write时不改变

foreignIP

口。后面调用 w r i t e 时不改变

图22-37 in_pcbbind 和in_pcbconnect

的总结

fport

604

计计TCP/IP详解 卷 2:实现

习题 22.1 在图 2 2 - 2 3中,当进程请求一个临时端口,而所有临时端口都被使用时,会发生什 么情况? 22.2 在图2 2 - 1 0中,我们显示了两个有正在监听插口的 Te l n e t服务器:一个具有特定本地 I P地址;另一个的本地 I P地址是通配地址。你的系统的 Te l n e t守护程序允许你指定 本地IP地址吗?如果允许,如何指定? 22.3 假定某个插口被绑定到本地插口 {140.252.1.29, 8888} ,且这是唯一使用本地插口 8 8 8 8的插口。 ( 1 ) 当有另一个插口绑定到

{140.252.1.29, 8888} 时,请执行

i n _ p c b b i n d的所有步骤,假定没有任何插口选项。 ( 2 )当有另一个插口绑定到通 配I P地址,端口 8 8 8 8时,执行 i n _ p c b b i n d的所有步骤,假定没有任何插口选项。 ( 3 ) 当有另一个插口绑定到通配

I P 地址,端口 8 8 8 8 时,且设定了插口选项

SO_REUSEADDR,执行in_pcbbind的所有步骤。 22.4 UDP分配的第一个临时端口号是什么? 22.5 当进程调用 bind时,必须填充sockaddr_in结构中的哪一个元素? 22.6 如果进程要 b i n d一个本地广播地址时,会发生什么情况?如果进程要 b i n d受限广 播地址(255.255.255.255)时,会发生什么情况?

第23章 UDP:用户数据报协议 23.1 引言 用户数据报协议,即 U D P,是一个面向数据报的简单运输层协议:进程的每次输出操作 只产生一个 UDP数据报,从而发送一个 IP数据报。 进程通过创建一个 Internet域内的S O C K_D G R A M类型的插口,来访问 UDP。该类插口默认 地称为无连接的 ( u n c o n n e c t e d )。每次进程发送数据报时,必须指定目的 I P地址和端口号。每 次从插口上接收数据报时,进程可以从数据报中收到源 IP地址和端口号。 我们在22.5节中提到, UDP插口也可以被连接到一个特殊的 IP地址和端口号。这样,所有 写到该插口上的数据报都被发往该目的地,而且只有来自该 I P地址和端口号的数据报才被传 给该进程。 本章讨论UDP的实现。

23.2 代码介绍 9个UDP函数在一个 C文件中, 2个UDP定义的头文件,如图 23-1所示。 图2 3 - 2显示了6个主要的 U D P函数与其他内核函数之间的关系。带阴影的椭圆是本章我们 讨论的6个函数,另外还有其他 3个函数是这 6个函数经常调用的。 文





netinet/udp.h netinet/udp var.h netinet/udp_usrreq.c



u d p h d r结构定义 其他UDP定义 UDP函数

图23-1 本章中讨论的文件 系统调用

系统初始化

插口接收缓冲区

软件中断

图23-2 UDP函数与内核其他函数之间的关系

多个系统调用

606

计计TCP/IP详解 卷 2:实现

23.2.1 全局变量 本章引入的全局变量,如图 22-3所示。 变



udb udp_last_inpcb udpcksum udp_in udpstat udp_recvspace udp_sendspace

数据类型 Struct Struct Int Struct Struct u_long u_long





UDP PCB 表的表头 指向最近收到的数据报的指针:“向后一个”高速缓存 用于计算和验证 UDP检验和的标志位 s o c k a d d r _ i n 在输入时存放发送方的 IP地址 udpstat UDP统计(图23-4) 插口接收缓存的默认大小, 41 600 字节 插口发送缓存的默认大小, 9 216 字节

inpcb inpcb *

图23-3 本章中引入的全局变量

23.2.2 统计量 全局结构 u d p s t a t维护多种 U D P统计量,如图 2 3 - 4所示。讨论代码的过程中,我们会看 到何时增加这些计数器的值。 描

udpstat成员 udps_badlen udps_badsum udps_fullsock udps_hdrops udps_ipackets udps_noport udps_noportbcast udps_opackets udps_pcbcachemiss



SNMP使用的

收到所有数据长度大于分组的数据报个数 收到有检验和错误的数据报个数 收到由于输入插口已满而没有提交的数据报个数 收到分组小于首部的数据报个数 所有收到的数据报个数 收到在目的端口没有进程的数据报个数 收到在目的端口没有进程的广播/多播数据报个数 全部输出数据报的个数 收到的丢失 pcb高速缓存的输入数据报个数

• • • • • • •

图23-4 在udpstat 结构中维持的 UDP统计

图23-5显示了执行 n e t s t a t -后 s 输出的统计信息。 成员

输出

(见正文)

图23-5 UDP统计样本

提交的U D P数据报的个数 (输出的倒数第二行 )是收到的数据报总数 (u d p s _ i p a c k e t s)减 去图23-5中它前面的 6个变量。

607

第 23章 UDP:用户数据报协议计计

23.2.3 SNMP变量 图2 3 - 6显示了 U D P组中的四个简单 S N M P变量,这四个变量在实现该变量的 u d p s t a t结 构中计数。 图23-7显示了UDP监听器表,称为udpTable。SNMP为这个表返回的值是取自 UDP PCB, 而不是udpstat结构。 SNMP变量



u d p s t a t成员



收到的所有提交给进程的数据报个数 收到的由于某些原因不可提交的 U D P数据报个数, 这些原因中不包括在目的端口没有应用程序的原因 (例 如,UDP检验和差错 )

udpInDatagrams udpInErrors

udps_ipackets udps_hdrops + udps_badsum+ udps_badlen

udpNoPorts

udps_noport + udps_noportbcast

收到的所有目的端口没有应用进程的数据报

udpOutDatagrams

udps_opackets

发送的数据报的个数

图23-6 udp 组中的简单 SNMP变量 UDP监听器表,索引 =. SNMP变量

PCB变量

udpLocalAddress udpLocalPort

inp_laddr inp_lport





这个监听器的本地 IP 这个监听器的本地端口号

图23-7 UDP监听器表: udpTable

23.3 UDP的protosw结构 图23-8显示了UDP的协议交换入口 成



pr_type pr_domain pr_protocol pr_flags pr_input pr_output pr_ctlinput pr_ctloutput pr_usrreq pr_init pr_fasttimo pr_slowtimo pr_drain pr_sysctl



i n e t s w[1] SOCK_DGRAM &inetdomain IPPROTO_UDP (17) PR_ATOMIC|PR_ADDR Udp_input 0 udp_ctlinput ip_ctloutput udp_usrreq udp_init 0 0 0 udp_sysctl



UDP提供数据报分组服务 UDP是Internet域的一部分 出现在IP首部的i p _ p字段 插口层标志,协议处理没有使用 从IP层接收报文 UDP没有使用 ICMP差错的控制输入函数 响应来自进程的管理请求 响应来自进程的通信请求 初始化UDP UDP没有使用 UDP没有使用 UDP没有使用 对sysctl (8)系统调用

图23-8 UDP的protosw 结构

本章我们描述五个以 u d p _开头的函数。另外我们还要介绍第 6个函数 u d p _ o u t p u t,它

608

计计TCP/IP详解 卷 2:实现

不在协议交换入口,但当输出一个 UDP数据报时, udp_usrreq会调用它。

23.4 UDP的首部 UDP首部定义成一个 udphdr结构。图23-9是C结构,图23-10是UDP首部的图。

图23-9 udphdr 结构

16位目的端口号

16位源端口号

8字节 16位UDP长度

16位UDP检验和

数据(如果有)

图23-10 UDP首部和可选数据

在源代码中,通常把 U D P 首部作为一个紧跟着 UDP 首部的 I P首部来引用。这就是 u d p _ i n p u t如何处理收到的 IP数据报,以及 u d p _ o u t p u t如何构造外出的 IP数据报。这种联 合的IP/UDP首部是一个 udpiphdr结构,如图 23-11所示。

图23-11 udpiphdr 结构:联合的 IP/UDP首部

20字节的IP首部定义成一个 ipovly结构,如图 23-12所示。 不幸的是,这个结构并不是一个真正的如图 8 - 8所示的 I P首部。大小相同 ( 2 0字节),但字

609

第 23章 UDP:用户数据报协议计计

段不同。我们将在 23.6节讲UDP检验和的计算时回来讨论这个不同之处。

图23-12 ipovly 结构

23.5 udp_init函数 domaininit函数在系统初始化时调用 UDP的初始化函数(udp_init,图23-13)。 这个函数所做的唯一的工作是把头部 P C B (u d b)的向前和向后指针指向它自己。这是一个 双向链表。 u d b P C B 的其他部分都被初始化成 0,尽管在这个头部 P C B 中唯一使用的字段是 i n p _ l p o r t,它是要分配的下一个 U D P临时端口号。在解习题 2 2 . 4时,我们提到,因为这个 本地端口号被初始化成 0,所以第一个临时端口号将是 1024。

图23-13 udp_init 函数

23.6 udp_output函数 当应用程序调用以下五个写函数中的任意一个时,发生 UDP 输出。这五个函数是:send、 s e n d t o、s e n d m s g、w r i t e或w r i t e v。如果插口已连接上的,则可任意调用这五个函数, 尽管用s e n d t o或s e n d m s g不能指定目的地址。如果插口没有连接上,则只能调用 s e n d t o和 s e n d m s g,并且必须指定一个目的地址。图 23-14总结了这五个函数,它们在终止时,都调用 udp_output,该函数再调用 ip_output。 五个函数终止调用 s o s e n d,并把一个指向 m s g h d r结构的指针作为参数传给该函数。要 输出的数据被分装在一个 m b u f链上, s o s e n d把一个可选的目的地址和可选的控制信息放到 mbuf中,并发布 PRU_SEND请求。 UDP调用函数udp_output,该函数的第一部分如图 23-15所示。四个参数分别是: inp, 指向插口Internet PCB的指针; m,指向输出 mbuf链的指针; addr,一个可选指针,指向某个 m b u f,存放分装在一个 s o c k a d d r _ i n结构中的目的地址; c o n t r o l,一个可选指针,指向 一个mbuf,其中存放着sendmsg中的控制信息。 1. 丢掉可选控制信息 333-344 m_freem丢弃可选的控制信息,不产生差错。 UDP输出不使用任何控制信息。

610

计计TCP/IP详解 卷 2:实现

注释x x x是因为忽略控制信息且不产生错误。其他协议如路由选择域和 T C P,当 进程传递控制信息时,都会产生一个错误。 库函数

系统调用 通过

把数据、目的地址和控制 信息放到mbuf中 请求

放到接口的输出队列上

图23-14 五个写函数如何终止调用 udp_output

2. 临时连接一个未连接上的插口 345-359

如 果 调 用 方 为 U D P 数 据 报 指 定 了 目 的 地 址 (a d d r 非 空 ) , 则 插 口 是由

i n _ p c b c o n n e c t临时连接到该目的地址的,并在该函数的最后被断连。在连接之前,要作 一个检测,判断插口是否已经连接上。如果已连接上,则返回错误 E I S C O N N。这就是为什么 sendto在已连接上的插口上指定目的地址时,会返回错误。 在临时连接插口之前, s p l n e t停止I P的输入处理。这样做的原因是因为,临时连接将改 变插口 P C B中的外部地址、外部端口以及本地地址。如果在临时连接该 P C B的过程中处理某 个收到的 U D P数据报,可能把该数据报提交给错误的进程。把处理器设置成比 s p l n e t优先, 只能阻止软件中断引发执行 I P输入程序 (图1 - 1 2 ),它不能阻止接口层接收进入的分组,并把它 们放到IP的输入队列中。 [ P a r t r i d g e和Pink 1993] 注意到临时连接插口的这个操作开销很大,用去每个 UDP传送将近三分之一的时间。 在临时连接之前, P C B的本地地址被保存在 l a d d r中,因为如果它是通配地址,它将被

611

第 23章 UDP:用户数据报协议计计

in_pcbconnect在调用in_pcbbind时改变。 如果进程调用了 c o n n e c t,则应用于目的地址的同一规则也将适用,因为两种情况都将 调用in_pcbconnect。

图23-15 udp_output 函数:临时连接一个未连接上的插口

612

计计TCP/IP详解 卷 2:实现

360-364 如果进程没有指定目的地址,并且插口没有连接上,则返回 ENOTCONN。 3. 在前面加上 IP/UDP首部 366-373

M _ P R E P E N D在数据的前面为 I P和U D P首部分配空间。图 1 - 8是一种情况,假定

m b u f链上的第一个 m b u f已经没有空间存放首部的 2 8个字节。习题 2 3 . 1详细给出了其他情况。 需要指定标志位 M _ D O N T W A I T ,因为如果插口是临时连接的,则 I P 处理被阻塞,所以 M_PREPEND也应被阻塞。 早期的伯克利版本不正确地指定了这里的 M_WAIT。 23.6.1 在前面加上 IP/UDP首部和mbuf簇 在M _ P R E P E N D宏和m b u f簇之间有一个微妙的交互。如果 s o s e n d把用户数据放到一个簇 中,则该簇的最前面的 5 6个字节 (m a x _ h d r,图7 - 1 7 )没有使用,这就为以太网、 I P和U D P首 部提供了空间。避免 M _ P R E P E N D仅仅为存放这些首部而另外再分配一个 m b u f。M _ P R E P E N D 调用M_LEADINGSPACE来计算在mbuf的前面有多大的空间可以使用: #define M_LEADINGSPACE(m) \ ((m)->m_flags & M_EXT ? /* (m)->m_data - (m)->m_ext_buf */ 0 : \ (m)->m_flags & M_PKTHDR ? (m)->m_data - (m)->m_pktdat : \ (m)->m_data - (m)->m_dat)

正确地计算出簇前面可用空间大小的代码被注释掉了,如果数据在簇内,该宏总是返回 0。 这意味着,当用户数据也在某个簇中时, M _ P R E P E N D总是为协议首部分配一个新的 mbuf,而 不再使用 sosend分配的用于存放首部的空间。 M _ L E A D I N G S P A C E中注释掉正确代码的原因是因为该簇可能被共享 ( 2 . 9节),而 且,如果它被共享,使用簇中数据报前面的空间可能会擦掉其他数据。 U D P数据不共享簇,因为 u d p _ o u t p u t不保存数据的备份。但是 T C P在它的发 送缓存内保存数据备份 (等待对该数据的确认 ),而且如果数据不在簇内,则说明它是 共享的。但 t c p _ o u t p u t不调用M _ L E A D I N G S P A C E,因为s o s e n d只为数据报协议 在簇前面留 5 6个字节,所以, t c p _ o u t p u t总是调用 M G E T H D R为协议首部分配一个 mbuf。 23.6.2 UDP检验和计算和伪首部 在讨论u d p _ o u t p u t的后一部分之前,我们描述一下 U D P如何填充 I P / U D P首部的某些字 段,如何计算 U D P检验和,以及如何传递 I P / U D P首部及数据给 I P输出的。这些工作很巧妙地 使用了ipovly结构。 图23-16显示了u d p _ o u t p u t在由m指向的mbuf链的第一个存储器上构造的 28字节IP/UDP 首部。没有阴影的字段是 u d p _ o u t p u t填充的,有阴影的字段是 i p _ o u t p u t填充的。这个图 显示了首部在线路上的格式。 U D P检验和的计算覆盖了三个区域: ( 1 )一个1 2字节的伪首部,其中包含 I P首部的字段; (2)8 字节U D P首部,和 ( 3 ) U D P数据。图 2 3 - 1 7显示了用于检验和计算的 1 2字节伪首部,以及 UDP首部。用于计算检验和的 UDP首部等价于出现在线路上的 UDP首部(图23-16)。

613

第 23章 UDP:用户数据报协议计计

0

15

4位版本号

31

16

8位服务类型 (TOS)

4位首部 长度

16位全长(字节数) 3位 标志

16位标识符 8位生存时间 (TTL)

13位分片偏移

20字节

16位首部检验和

8位协议

32位源IP地址

32位目的IP地址

16位源端口号

16位目的端口号

16位UDP长度

16位UDP检验和

8字节

图23-16 IP/UDP首部:UDP填充没有阴影的字段, IP填充有阴影的字段 0

15

31

16

32位源IP地址 UDP 伪首部

32位目的IP地址 零

8位协议(17)

16位源端口号

16位UDP长度

16位目的端口号 UDP 首部

16位UDP长度

16位UDP检验和

图23-17 检验和计算所使用的伪首部和 UDP首部

在计算UDP检验和时使用以下三个事实: (1)在伪首部(图23-17)中的第三个 32 bit字看起来 与I P首部(图2 3 - 1 6 )中的第三个 32 bit字类似:两个 8 bit值和一个 16 bit值。( 2 )伪首部中三个 3 2 bit值的顺序是无关的。事实上,Internet检验和的计算不依赖于所使用的 16 bit值的顺序(8.7节)。 (3)在检验和计算中另外再加上一个全 0的32 bit字没有任何影响。 u d p _ o u t p u t利用这三个事实,填充 u d p i p h d r结构(图2 3 - 11 )的字段,如图 2 3 - 1 8所示。 该结构包含在由 m指向的mbuf链的第一个 mbuf中。 在2 0字节I P首部( 5个成员: u i _ x 1、u i _ p r、u i _ l e n、u i _ s r c和u i _ d s t)中的最后三 个32 bit字被用作检验和计算的伪首部。 I P首部的前两个 32 bit字(u i _ n e x t和u i _ p r e v)也用 在检验和计算中,但它们被初始化成 0,所以不影响最后的检验和。 图23-19总结了我们描述的操作: 1) 图23-19中最上面的图是伪首部的协议定义,与图 23-17对应。

614

计计TCP/IP详解 卷 2:实现

0

15

8位生存时间

16

31

8位协议

20字节

16位首部检验和

32位源IP地址

32位目的IP地址

16位源端口号

16位目的端口号 8字节

16位UDP长度

16位UDP检验和

图23-18 udp_output 填充的udpiphdr 伪首部

源IP地址

目的IP地址

UDP首部

0 pr

UDP 目的 源端口 长度 端口

UDP UDP 检验和计算的 长度 检验和 协议目的标

多个0不影响

相同的字段

相同的字段

检验和计算

不同的顺序

相同的顺序

0

0

v l T e e O IP 标识 r n S 长度

0 pr UDP 长度

分片 T IP pr 偏移 T 检验和 L

源IP 地址

目的IP 地址

源端口

目的 端口

源IP 地址

目的IP 地址

源端口

目的 UDP UDP 端口 长度 检验和 线路上的首部

IP首部

UDP UDP 用于检验和 长度 检验和 计算的结构

UDP首部

图23-19 填充IP/UDP首部和计算 UDP检验和的操作

2) 中间的图是源代码使用的 udpiphdr结构,与图 23-11对应(为图的可读性,省略了所有 成员的前缀u i _)。这是u d p _ o u t p u t在mbuf链上的第一个 mbuf中构造的结构,然后被用于计 算UDP检验和。 3) 下面的图是出现在线路上的 I P / U D P首部,与图 2 3 - 1 6对应。上面有箭头的 7个字段是 u d p _ o u t p u t在检验和计算之前填充的。上面有星号的 3个字段是 u d p _ o u t p u t在检验和计 算之后填充的。其他 6个有阴影的字段是 ip_output填充的。 图23-20是udp_output函数的后半部分。 1. 为检验和计算准备伪首部 374-387

把u d p i p h d r结构(图2 3 - 1 8 )的所有成员设置成它们相应的值。 P C B中的本地和外

部插口已经是网络字节序,但必须把 U D P的长度转换成网络字节序。 U D P的长度是数据报的

615

第 23章 UDP:用户数据报协议计计

字节数(l e n,可以是 0 )加上UDP 首部的大小 ( 8 )。UDP 长度字段在UDP 检验和计算中出现了 两次:ui_len和ui_ulen。有一个是冗余的。

图23-20 udp_output 函数:填充首部、计算检验和并传给 IP

2. 计算检验和 388-395

计算检验和时,首先把它设成 0,然后调用 in_cksum。如果UDP检验和是禁止的

(一个坏的想法—见卷1的11 . 3节),则检验和的结果是 0。如果计算的检验和是 0,则在首部 中保存 1 6个1,而不是 0 (在求补运算中,全 1和全0都是0 )。这样,接收方就可以区分没有检验 和的U D P分组(检验和字段为 0 )和有检验和的 U D P分组了,后者的检验和值为 0 ( 1 6位的检验和 是16个1)。 变量u d p c k s u m(图2 3 - 3 )通常默认值为 1,使能 U D P检验和。对内核的编译可对 4.2BSD兼容,把 udpcksum初始化为0。 3. 填充UDP长度、TTL和TOS 396-398

指针ui指向一个指向某个标准 IP首部的指针 (ip),UDP设置IP首部内的三个字段。

I P长度字段等于 U D P数据报中数据的个数加上 I P / U D P首部大小 2 8。注意, I P首部的这个字段

616

计计TCP/IP详解 卷 2:实现

以主机字节序保存,不象首部其他多字节字段,是以网络字节序保存的。 i p _ o u t p u t在发送 之前,把它转换成网络字节序。 把IP首部里的 TTL和TOS字段的值设成插口 PCB中的值。在创建插口时, UDP设置这些默 认值,进程可用 s e t s o c k o p t改变它们。因为这三个字段—I P长度、 T T L和TO S—不是 伪首部的内容, U D P 检验和计算时也没有用到它们,所以,在计算检验和之后,调用 ip_output之前,必须设置它们。 4. 发送数据报 400-402

ip_output发送数据报。第二个参数 inp_options,是进程可用setsockopt

设置的 I P选项。这些 I P选项是 i p _ o u t p u t放置到I P首部中的。第三个参数是一个指向高速缓 存在 P C B中的路由的指针,第四个参数是插口选项。传给 i p _ o u t p u t 的唯一插口选项是 S O _ D O N T R O U T E(旁路选路表 )和S O _ B R O A D C A S T(允许广播 )。最后一个参数是一个指向该插 口的多播选项的指针。 5. 断连临时连接的插口 如果插口是临时连接上的,则 i n _ p c b d i s c o n n e c t断连插口,本地 I P地址在

403-407

PCB中恢复,恢复中断级别到保存的值。

23.7 udp_input函数 进程调用五个写函数之一来驱动 UDP 输出。图23-14显示的函数都作为系统调用的组成部 分被直接调用。另一方面,当 I P在它的协议字段指定为 U D P的输入队列上收到一个 I P数据报 时,才发生 U D P 的输入。 I P 通过协议交换表 ( 图 8 - 1 5 ) 中的 p r _ i n p u t 函数调用函数 u d p _ i n p u t 。因为 I P 的输入是在软件中断级,所以 u d p _ i n p u t 也在这一级上执行。 u d p _ i n p u t的目标是把UDP数据报放置到合适的插口的缓存内,唤醒该插口上因输入阻塞的 所有进程。 我们对udp_input函数的讨论分三个部分: 1) UDP对收到的数据报完成一般性的确认; 2) 处理目的地是单播地址的 UDP数据报:找到合适的 PCB,把数据报放到插口的缓存内, 3) 处理目的地是广播或多播地址的 UDP数据报:必须把数据报提交给多个插口。 最后一步是新的,是为了在 Net/3中支持多播,但占用了大约三分之一的代码。 23.7.1 对收到的UDP数据报的一般确认 图23-21是UDP输入的第一部分。 55-65

u d p _ i n p u t的两个参数是: m,一个指向包含了该 I P数据报的 m b u f链的指针;

iphlen,IP首部的长度 (包括可能的 IP选项)。 1. 丢弃IP选项 67-76

如果有IP选项,则ip_stripoptions丢弃它们。正如注释中表明的, UDP 应该保

存IP选项的一个备份,使接收进程可以通过 I P _ R E C V O P T S插口选项访问到它们,但这个还没 有实现。 77-88

如果 m b u f链上的第一个 m b u f小于 2 8字节 ( I P 首部加上 U D P 首部的大小 ),则

m_pullup重新安排mbuf链,使至少有28个字节连续地存放在第一个 mbuf中。

617

第 23章 UDP:用户数据报协议计计

图23-21 udp_input 函数:对收到的 UDP数据报的一般确认

618

计计TCP/IP详解 卷 2:实现

图23-21 (续)

2. 验证UDP长度 333-344 与UDP 数据报相关的两个长度是: IP首部的长度字段 (ip_len)和UDP首部的长度 字段(u h _ u l e n)。前面讲到, i p i n t r在调用 u d p _ i n p u t之前,从 i p _ l e n中抽取出 I P首部 的长度(图10-11)。比较这两个长度,可能有三种可能性: 1) ip_len等于uh_ulen。这是通常情况。 2) i p _ l e n大于u h _ u l e n。I P首部太大,如图 2 3 - 2 2所示。代码相信两个长度中小的那个 ( U D P首部长度 ),m _ a d j从数据报的最后移走多余的数据字节。 m _ a d j的第二个参数是负数, 在图 2 - 2 0中我们说,从 m b u f链的最后截断数据。在这种情况下, U D P的长度字段出现冲突。 如果是这样,假定发送方计算了 U D P的检验和,则不久检验和会检测到这个错误,接收方也 会验证检验和,从而丢弃该数据报。 I P长度字段必须正确,因为 I P根据接口上收到的数据量 验证它,而强制的 IP首部检验和覆盖了 IP首部的长度字段。 IP数据报 IP首部

UDP数据

UDP首部

忽略的 数据

UDP长度uh_ulen IP长度ip_len加上IP首部长度

图23-22 UDP长度太小

3) ip_len小于uh_ulen。当UDP 首部的长度给定时, IP数据报比可能的小。图 23-23显 示了这种情况。这说明数据报有错误,必须丢弃,没有其他的选择:如果 U D P长度字段被破 坏,用UDP检验和是无法检测到的。需要用正确的 UDP长度来计算 UDP检验和。 IP数据报 IP首部

UDP首部

UDP数据

数据不可用

UDP长度uh_ulen IP长度ip_len加上IP首部长度

图23-23 UDP长度太大

正如我们提到的, UDP长度是冗余的。在第 28章中我们将看到, TCP自己的首部 内没有长度字段—它用I P长度字段减去 I P和T C P首部的长度,以此确定数据报内数

619

第 23章 UDP:用户数据报协议计计

据的数量。为什么存在 U D P 长度字段呢?可能是为了加上少量的差错检测,因为 UDP检验和是可选的。 3. 保存IP首部的备份,验证 UDP检验和 102-106 udp_input在验证检验和之前保存 IP首部的备份,因为检验和计算会擦去原始 IP 首部的一些字段。 110 只有当的UDP检验和(udpcksum)是内核允许的,并且发送方也计算了 UDP检验和(收到 的检验和不为0)时,才验证检验和。 这个检测是不正确的。如果发送方计算了一个检验和,就应该验证它,不管外 出的检验和是否被计算。变量 u d p c k s u m应该只指定是否计算外出的检验和。不幸 的是,许多厂商都复制了这个检测,尽管厂商已经改变它们产品的内核,却默认地 允许UDP检验和。 1 1 1 - 1 2 0 在计算检验和之前, I P首部作为 i p o v l y结构(图2 3 - 1 8 )引用,所有字段的初始化 都是udp_output在计算UDP检验和(上一节)时初始化的。 此时,如果数据报的目的地是一个广播或多播 I P地址,将执行特别的代码。我们把这段 代码推迟到本节最后。 23.7.2 分用单播数据报 假定数据报的目的地是一个单播地址,图 23-24显示了执行的代码。

图23-24 udp_input 函数:分用单播数据报

620

计计TCP/IP详解 卷 2:实现

1. 检查“向后一个”高速缓存 206-209

UDP 维护一个指针,该指针指向最后在其上接收数据报的

Internet PCB ,

u d p _ l a s t _ i n p c b。在调用 i n _ p c b l o o k u p之前,可能必须搜索 U D P表上的 P C B,把最近 一次接收 P C B的外部和本地地址以及端口号和收到数据报的进行比较。这称为“向后一个” 高速缓存(one-behind cache)[Partridge和Pink 1993]。它是根据这样一个假设,即收到的数据报 极有可能要发往上一个数据报发往的同一端口 [Mogul 1991]。这个高速缓存技术是在 4 . 3 B S D Tahoe版本中引入的。 210-213

高速缓存的 P C B和收到数据报之间的四个比较的次序是故意安排的。如果 P C B不

匹配,则应尽快结束比较。最大的可能性是目的端口号不相同——这就是为什么第一个检测 它。不匹配的可能性最小的是本地地址,尤其在只有一个接口的主机,所以它是最后一个检 测。 不幸的是,这种“向后一个”高速缓存技术代码,在实际中毫无用处

[ P a r t r i d g e和P i n k

1 9 9 3 ]。最普通的 U D P服务器类型只绑定它的知名端口,它的本地地址、外部地址和外部端口 都是通配地址。最普通的 UDP 客户程序类型并不连接它的 UDP 插口;它用s e n d t o指定每个 数据报的目的地址。因此,大多数时间 PCB内的i n p _ l a d d r、i n p _ f a d d r和i n p _ f p o r t都 是通配的。在高速缓存的比较中,收到数据报的这四个值永远都不是通配的,这意味着只有 当指定P C B的四个本地和外部值是非通配时,高速缓存入口与收到数据报的比较才可能相等。 这种情况只在连接上的 UDP插口上发生。 在系统 b s d i上,u d p p s _ p c b c a c h e m i s s计数器是 41 253,u d p s _ i p a c k e t s 计数器是42 485。小于3% 缓存命中率。 n e t s t a t-s 命令打印出 u d p s t a t结构 (图2 3 - 5 )的大多数字段。不幸的是, N e t / 3版本,以及多数厂家的版本都不打印 u d p p s _ p c b c a c h e m i s s。如果你想看它 们的值,用调试器检查在运行的内核中的变量。 2. 搜索所有UDP的PCB 214-218

假定与高速缓存的比较失败,则

i n _ p c b l o o k u p寻找一个匹配。指定

I N P L O O K U P _ W I L D C A R D标志,允许通配匹配。如果找到一个匹配,则把指向该 P C B的指针 保存在udp_last_inpcb中,我们说它高速缓存了最后收到的 UDP数据报的PCB。 3. 生成ICMP端口不可达差错 220-230

如果没找到匹配的 P C B,UDP 通常产生一个 I C M P端口不可达差错。首先检测收

到的m b u f链的m _ f l a g s,看看该数据报是否是要发送到一个链路级广播或多播地址。有可能 会收到一个发送到链路级广播或多播地址的 I P数据报,具有单播地址,此时不应该产生 I C M P 端口不可达差错。如果成功产生 I C M P差错,则把I P首部恢复成收到它时的值 (s a v e _ i p),也 把IP长度设置成它原来的值。 链路级广播或多播地址的检测是冗余的。 i c m p _ e r r o r也做这个检测。这个冗 余检测的唯一好处是,在

u d p s _ n o p o r t b c a s t 计数器之外,还维护了

udps_noport计数器。 把i p h l e n 改回 i p _ l e n 是一个错误。 i c m p _ e r r o r也会做这项工作,使得 I C M P差错返回的 I P首部的I P长度字段是 2 0字节,这太大了。可以在 T r a c e r o u t e程

621

第 23章 UDP:用户数据报协议计计

序(卷1的第8章)中加上几行新程序,在最终到达目的主机后,打印出 I C M P端口不可 达差错报文中的这个字段,可以测试系统是否有这个错误。 图23-25是处理单播数据报的代码,把数据报提交给与目的 PCB对应的插口。

图23-25 udp_input 函数:把单播数据报提交给插口

4. 返回源站IP地址和源站端口 231-236

收到的 I P数据报的源站 I P地址和源站端口被保存在全局 s o c k a d d r _ i n结构中的

622

计计TCP/IP详解 卷 2:实现

udp_in。在函数的后面,该结构作为参数传给了 sbappendaddr。 采用全局变量保存 I P地址和端口号不出现问题的原因是, u d p _ i n p u t是单线程的。当 i p i n t r调用它时,它在返回之前完整地处理了收到的数据报。而且, s b a p p e n d a d d r还把 该插口结构从全局变量复制一个 mbuf中。 5. IP_RECVDSTADDR插口选项 337-244 常数INP_CONTROLOPTS是三个插口选项的结合,进程可以设置这三个插口选项, 通过系统调用 r e c v m s g返回插口的控制信息 (图2 2 - 5 )。I P _ R E C V D S T A D D R把收到的 UDP 数 据报中的目的 I P地址作为控制信息返回。函数 u d p _ s a v e o p t分配一个 M T _ C O N T R O L类型的 mbuf,并把4字节的目的 IP地址存放在该缓存中。我们在 23.8节中介绍这个函数。 该插口选项与 4.3BSD Reno 一起出现,是为一般文件传输协议 T F T P的应用程序 设计的,它们不响应发给广播地址的客户程序请求。不幸的是,即使接收应用程序 使用这个选项,也很难确定目的 IP地址是否是一个广播地址 (习题23.6)。 当4.4BSD中加上了多播功能后,这个代码只对目的地是单播地址的数据报有效。 我们将在图 2 3 - 2 6看到,对发给多播地址的广播数据报还没有实现这个选项,根本无 法达到该选项的目的。 6. 未实现的插口选项 245-260

这段代码被注释掉了,因为它们不起作用。 I P _ R E C V O P T S插口选项的原意是把

收到数据报的 IP选项作为控制信息返回,而 I P _ R E C V R E T O P T S插口选项返回源路由信息。三 个 I P _ R E C V 插口选项对 m p 变量的操作构造了一个最多有三个

m b u f 的链表,该链表由

s b a p p e n d a d d r放置到插口的缓存。图 2 3 - 2 5显示的代码只把一个选项作为控制信息返回, 所以指向该 mbuf的m_next总是一个空指针。 7. 把数据加到插口的接收队列中 262-272

此时,已经准备好把收到的数据报 (m指向的mbuf链)以及一个表示发送方 IP地址和

端口的插口地址结构 (u d p _ i n)和一些可选的控制信息 (o p t s指向的mbuf,目的IP地址)放到插 口的接收队列中。这个工作由 s b a p p e n d a d d r完成。但在调用这个函数之前,要修正指针和 缓存链上的第一个 mbuf,忽略掉 UDP和IP首部。返回之前,调用 s o r w a k e u p唤醒插口接收队 列中的所有睡眠进程。 8. 返回差错 273-276

如果在 UDP 输入处理的过程中遇到错误, u d p _ i n p u t会跳转到 b a d标号语句,

释放所有包含该数据报以及控制信息 (如果有的话 )的mbuf链。 23.7.3 分用多播和广播数据报 现在返回到 u d p _ i n p u t处理发给广播或多播 I P地址数据报的这部分代码。如图 2 3 - 2 6所 示。 121-138

正如注释所表明的,这些数据报被提交给匹配的所有插口,而不仅仅是一个插口。

我们提到的 U D P接口不够指的是除非连接上插口,否则进程没有能力在 U D P插口上接收异步 差错(特别是ICMP端口不可达差错 )。我们22-11节讨论这个问题。 139-145

源站的 I P地址和端口号被保存在全局 s o c k a d d r _ i n结构的 u d p _ i n中,传给

sbappendaddr。更新mbuf链的长度和数据指针,忽略 UDP和IP首部。

623

第 23章 UDP:用户数据报协议计计

图23-26 udp_input 函数:分用广播或多播数据报

624

计计TCP/IP详解 卷 2:实现

图23-26 (续)

146-164

大的 f o r 循环扫描每个 UDP PCB ,寻找所有匹配 P C B 。这种分用不调用

in_pcblookup,因为它只返回一个 PCB,而广播或多播数据报可能需要提交给多个 PCB。 如果P C B的本地端口和收到数据报的本地端口不匹配,则忽略该入口。如果 P C B的本地 端口不是通配地址,则把它和目的 I P地址比较,如果不相等则跳过该入口。如果 P C B内的外 部地址不是通配地址,就把它和源站 I P地址比较,如果不匹配,则外部端口也必须和源站端 口匹配。最后一个检测假定,如果插口连接到某个外部 I P地址,则它也必须连接到一个外部 端口,反之亦然。这与 in_pcblookup函数的逻辑相同。 165-177 如果这不是第一个匹配 (last非空),则把该数据报放到上一个匹配的接收队列中。 因为当sbappendaddr完成后要释放mbuf链,所以m_copy先要做个备份。 sorwakeup唤醒 所有等待这个数据的进程, last保存指向匹配的 socket结构的指针。 使用变量 l a s t避免调用 m _ c o p y函数(因为要复制整个 m b u f链,所以耗费很大 ),除非有 多个接收方接收该数据报。在通常只有一个接收方的情况下, f o r循环必须把 l a s t设成指向 一个匹配 P C B,当循环终止时, s b a p p e n d a d d r把m b u f链放到插口的接收队列中—不做备 份。 1 7 8 - 1 88

如果匹配的插口没有设置 S O _ R E U S E P O R T或S O _ R E U S E A D D R插口选项,则没必

要再找其他匹配,终止该循环。在循环的外部,调用 s b a p p e n d a d d r把数据报放到这个插口 的接收队列中。 189-197

如果在循环的最后, l a s t为空,没找到匹配,则并不产生 I C M P差错,因为该数

据报是发给广播或多播 IP地址。

625

第 23章 UDP:用户数据报协议计计

198-204

最后的匹配入口 (可能是唯一的匹配入口 )把原来的数据报 (m)放到它的接收队列中。

在调用sorwakeup后,udp_input返回,因为完成了对广播或多播数据报的处理。 函数的其他部分 (图23-24)处理单播数据报。 23.7.4 连接上的UDP插口和多接口主机 在使用连接上的 U D P插口与多接口主机上的一个进程交换数据报时,有一个微妙的问题。 来自对等实体的数据报可能到达时具有不同的源站 IP地址,不能提交给连接上的插口。 考虑图23-27所示的例子。 PPP链路 服务器进程, 未连接上的 UDP

客户进程 连接到 140.252.1.29

以太网140.252.13 UDP请求,目的IP=140.252.1.29 UDP回答源IP=140.252.13.33 ICMP端口不可达

图23-27 连接上的 UDP插口向一个多接口主机发送数据报的例子

有三个步骤: 1) b s d i上的客户程序创建一个 UDP插口,并把它连接到 140.252.1.29,这是s u n上的PPP 接口,而不是以太网接口。客户程序在插口上把数据报发给服务器。 S u n上的服务器接收并收下该数据报,即使到达接口与目的 I P地址不同 (s u n是一个路由 器,所以不管它实现的是弱端系统模型或强端系统模型都没有关系 )。数据报被提交给在未连 接上的UDP插口上等待客户请求的服务器。 2) 服务器发一个回答,因为是在一个未连接上的 U D P插口上发送的,所以由内核根据外 出的接口(140.252.13.33)选择回答的目的 IP地址。请求的目的 IP地址不作为回答的源站地址。 b s d i收到回答时,因为 I P地址不匹配,所以不把它提交给客户程序的连接上的 U D P接 口。 3) 因为无法分用回答,所以 b s d i产生一个 I C M P端口不可达差错 (假定在 b s d i上没有其 他进程符合接收该数据报的条件 )。 这个例子的问题在于,服务器并不把请求中的目的 I P地址作为回答的源站 I P地址。如果 它这样做,就不存在这个问题了,但这个办法并不简单—见习题23.10。我们将在图28-16中 看到,如果一个 T C P服务器没有明确地把一个本地 I P地址绑定它的插口上,它就把来自客户 的目的IP地址用作来自它自己的源 IP地址。

23.8 udp_saveopt函数 如果进程指定了 I P _ R E C V D S T A D D R插口选项,则 u d p _ i n p u t调用u d p _ s a v e o p t,从 收到的数据报中接收目的 IP地址:

626

计计TCP/IP详解 卷 2:实现

*mp = udp_saveopt((caddr_t) & ip_dst, sizeof(struct in_addr), IP_RECVDSTADDR);

图23-28显示了这个函数。

图23-28 udp_saveopt 函数:用控制信息创建 mbuf

20字节

16位控制信息 目的IP地址

图23-29 把收到的数据报的目的地址作为控制信息保存的 mbuf

276-286

参数包括p,一个指向存储在 mbuf中的信息的指针 (收到的数据报的目的 IP地址);

s i z e ,字节数大小 (在这个例子中是 4 ,I P 地址的大小 ) ;以及 t y p e ,控制信息的类型 (IP_RECVDSTADDR)。 290-299

分配一个mbuf,并且因为是在软件中断级执行代码,所以指定 M_DONTWAIT。指

针c p指向m b u f的数据部分,是一个指向 c m s g h d r结构(图1 6 - 1 4 )的指针。 b c o p y把I P首部中

627

第 23章 UDP:用户数据报协议计计

的IP地址复制到 c m s g h d r结构的数据部分。然后设置紧跟在 c m s g h d r结构后面的 mbuf的长度 (在本例中设成16)。图23-29是mbuf的最后一个状态。 c m s g _ l e n字段包含了 c m s g h d r的长度(12)加上c m s g _ d a t a字段的长度(本例中是 4)。如 果应用程序调用 r e c v m s g接收控制信息,则它必须检查 c m s g h d r结构,确定 c m s g _ d a t a字 段的类型和长度。

23.9 udp_ctlinput函数 当i c m p _ i n p u t收到一个 I C M P差错(目的主机不可达、参数问题、重定向、源站抑制和 超时)时,调用相应协议的 pr_ctlinput函数: if (ctlfunc = inetsw[ ip_protox[icp->icmp_ip.ip_p] ].pr_ctlinput) (*ctlfunc)(code, (struct sockaddr *)&icmpsrc, &icp->icmp_ip);

对于UDP,调用图22-32显示的函数 udp_ctlinput。我们将在图23-30中给出这个函数。 314-322

参数包括cmd,图11-19的一个PRC_xxx常数;sa,一个指向sockaddr_in结构的

指针,该结构含有 ICM P报文的源站I P地址; i p,一个指向引起差错的 I P首部的指针。对于目 的站不可达、参数问题、源站抑制和超时差错, ip指向引起差错的IP首部。但当pfctlinput 为重定向(图22-32)调用udp_ctlinput时,sa指向一个 sockaddr_in结构,该结构中包含 要被重定向的目的地址, i p是一个空指针。最后一种情况没有信息丢失,因为我们在 2 2 . 11节 看到,重定向应用于所有连接到目的地址的 T C P和U D P插口。但对其他差错,如端口不可达, 需要非空的第三个参数,因为协议跟在IP首部后面的协议首部包含了不可达端口。 323-325

如果差错不是重定向,并且 P R C _x x x的值太大或全局数组 i n e t c t l e r r m a p中没

有差错码,则忽略该 ICMP差错。为理解这个检测,我们来看一下对收到的 ICMP所做的处理: 1) icmp_input把ICMP类型和码转换成一个 PRC_xxx差错码。 2) 把PRC_xxx差错码传给协议的控制输入函数。 3) Internet PCB协议( T C P和U D P )用i n e t c t l e r r m a p把P R C _x x x差错码映射到一个 U n i x 的errno值,这个值被返回给进程。

图23-30 udp_ctlinput

函数:处理收到的 ICMP差错

628

计计TCP/IP详解 卷 2:实现

图11-1和图11-2总结了ICMP报文的处理。 回到图 2 3 - 3 0 ,我们可以看到如何处理响应

U D P 数据报的 I C M P 源站抑制报文。

i c m p _ i n p u t把I C M P报文转换成差错 P R C _ Q U E N C H,并调用 u d p _ c t l i n p u t。但因为在图 11-2中,这个ICMP差错的errno行是空白,所以忽略该差错。 326-331

in_pcbnotify函数把该ICMP差错通知给恰当的PCB。如果udp_ctlinput的第

三个参数非空,则把引起差错数据报的源和目的UDP端口以及源IP地址,传给in_pcbnotify。 udp_notify函数 i n _ p c b n o t i f y函数的最后一个参数是一个指向函数的指针, i n _ p c b n o t i f y为每个准 备接收差错的PCB调用该函数。对 UDP,该函数是 udp_notify,如图23-31所示。 301-313

该函数的第二个参数 e r r n o保存在插口的 s o _ e r r o r变量中。通过设置这个插口

变量,使插口变成可读,并且如果进程调用 s e l e c t,插口也可写。然后唤醒插口上所有正在 等待接收或发送的进程接收该差错。

图23-31 udp_notify函数:通知进程一个异步差错

23.10 udp_usrreq函数 许多操作都要调用协议的用户请求函数。从图 2 3 - 1 4我们看到,在某个 U D P插口上调用五 个写函数中的任意一个,都以请求 PRU_SEND调用UDP 的用户请求函数结束。 图23-32显示了u d p _ u s r r e q的开始和结束。 s w i t c h单独在后面的图中给出。图 15-17显 示了该函数的参数。

图23-32 udp_usrreq 函数体

629

第 23章 UDP:用户数据报协议计计

图23-32 (续)

417-428 PRU_CONTROL请求来自ioctl系统调用。函数 in_control完整地处理该请求。 429-432

在函数的开头定义 i n p时,把插口指针转换成 P C B指针。唯一允许 P C B指针为空

的时候是创建新插口时 (PRU_ATTACH)。 433-436

注释表明,只要在 UDP PCB表中增加或删除表项,代码必须由 splnet保护起来。

这是因为 u d p _ u s r r e q是作为系统调用的一部分来调用的,在它修改 P C B的双重链表时,不 能被UDP输入中断 (被IP输入作为软件中断调用 )。在修改 PCB的本地或外部地址或端口时,也 必须阻塞UDP输入,避免 in_pcblookup不正确地提交收到的 UDP数据报。 我们现在讨论每个case语句。图23-33语句中的PRU_ATTACH请求,来自socket系统调用。

图23-33 udp_usrreq 函数:PRU_ATTACH 和PRU_DETACH 请求

630

计计TCP/IP详解 卷 2:实现

438-447

如果插口结构已经指向一个 PCB,则返回 EINVAL。in_pcballoc分配一个新的

PCB,把它加到 UDP PCB表的前面,把插口结构和 PCB链接到一起。 448-450

s o r e s e r v e 为插口的发送和接收缓存保留缓存空间。如图

1 6 - 7 所示,

s o r e s e r v e只是实施系统的限制,并没有真正分配缓存空间。发送和接收缓存的默认大小分 别是9 2 1 6字节(u d p _ s e n d s p a c e)和41 600字节(u d p _ r e c v s p a c e)。前者允许最大 9 2 0 0字节 的数据报 (在NFS分组中,有 8 KB的数据),加上16字节目的地址的 s o c k a d d r _ i n结构。后者 允许插口上一次最多有 40个1024字节的数据报排队。进程可调用 setsockopt改变这些值。 451-452

进程通过s e t s o c k o p t函数可以改变 PCB中原型IP首部的两个字段: TTL和TOS。

T T L默认值是 6 4 (i p _ d e f t t l),TO S的默认值是 0 (普通服务 ),因为 i n _ p c b a l l o c把P C B初 始化为0。 453-455

close系统调用发布PRU_DETACH请求,调用图23-34所示的udp_detach函数。

本节后面的 PRU_ABORT请求也调用这个函数。

图23-34 udp_detach 函数:删除一个 UDP PCB

如果最后收到的 P C B指针(“向后一个”缓存 )指向一个已分离的 P C B,则把缓存的指针设 成指向UDP 表的表头(udb)。函数in_pcbdetach从UDP 表中移走PCB,并释放该 PCB。 回到u d p _ u s r r e q,P R U _ B I N D请求是系统调用 b i n d的结果,而P R U _ L I S T E N请求是系 统调用listen的结果。如图23-35所示。 456-460 in_pcbbind完成所有PRU_BIND请求的工作。 461-463 对无连接协议来说,PRU_LISTEN请求是无效的—只有面向连接的协议才使用它。

图23-35 udp_usrreq 函数:PRU_BIND 和PRU_LISTEN 请求

前面提到,一个 U D P应用程序,客户或服务器 (通常是客户 ),可以调用 c o n n e c t。它修 改插口发送或接收的外部 I P地址和端口号。图 2 3 - 6显示了 P R U _ C O N N E C T、P R U _ C O N N E C T 2 和PRU_ACCEPT请求。

631

第 23章 UDP:用户数据报协议计计

464-474

如果插口已经连接上,则返回 EISCONN。在这个时候,不应该连接上插口,因为

在一个已经连接上的 U D P插口上调用 c o n n e c t ,会在生成 P R U _ C O N N E C T请求之前生成 PRU_DISCONNECT请求。否则,由in_pcbconnect完成所有工作。如果没有遇到任何错误, soisconnected就把该插口结构标记成已经连接上的。 475-477 socketpair系统调用发布PRU_CONNECT2请求,只适用于 Unix域的协议。 478-480 PRU_ACCEPT请求来自系统调用 accept,只适用于面向连接的协议。

图23-36 udp_usrreq 函数:PRU _CONNECT 、PRU _CONNECT2 和PRU _ACCEPT 请求

对于UDP插口,有两种情况会产生 PRU_DISCONNECT请求: 1) 当关闭了一个连接上的 UDP插口时,在 PRU_DETACH之前调用PRU_DISCONNECT。 2) 当在一个已经连接上的 U D P插口上发布 c o n n e c t时,s o c o n n e c t在P R U _ C O N N E C T 请求之前发布PRU_DISCONNECT请求。 PRU_DISCONNECT请求如图23-37所示。

图23-37 udp_usrreq 函数:PRU_DISCONNECT

请求

如果插口没有连接上,则返回 E N O T C O N N。否则,i n _ p c b d i s c o n n e c t把外部IP地址设 成0.0.0.0,把外部地址设成 0。本地地址也被设成 0.0.0.0,因为c o n n e c t可能已经设置了这个 PCB变量。 调用s h u t d o w n说明进程数据发送结束,产生 P R U _ S H U T D O W N请求,尽管对 U D P插口来

632

计计TCP/IP详解 卷 2:实现

说,很少有进程发布这个系统调用。图

2 3 - 3 8显示了 P R U _ S H U T D O W N 、P R U _ S E N D 和

PRU_ABORT请求。 492-494 socantsendmore设置插口的标志,阻止其他更多输出。 495-496

图2 3 - 1 4显示了五个写函数如何调用 u d p _ s u r r e q,发布P R U _ S E N D请求。u d p _

output发送该数据报, udp_usrreq返回,避免执行release标号语句(图23-32),因为还不 能释放包含数据的mbuf链(m)。IP输出把这个mbuf链加到合适的接口输出队列中,当发送完数据 后,由设备驱动器释放mbuf链。

图23-38 udp_usrreq 函数体: PRU _SHUTDOWN 、PRU _SEND 和PRU _ABORT 请求

内核中 U D P输出的唯一缓冲是在接口的输出队列中。如果插口的发送缓存内有存放数据 报和目的地址的空间,则 s o s e n d调用u d p _ u s r r e q,该函数调用 u d p _ o u t p u t。图2 3 - 2 0显 示,u d p _ o u t p u t继续调用 i p _ o u t p u t,i p _ o u t p u t 为以太网调用 e t h e r _ o u t p u t,把 数据报放到接口的输出队列中 (如果有空间)。如果进程调用 s e n d t o的动作比接口快,就可以 发送该数据报, ether_output返回ENOBUFS,并被返回给进程。 497-500 在UDP插口上从不发布 PRU_ABORT请求。但如果发布,则断连插口,分离 PCB。 P R U _ S O C K A D D R 和 P R U _ P E E R A D D R 请求分别来自系统调用 g e t s o c k n a m e 和 getpeername。这两个请求和 PRU_SENSE请求一起,如图 23-39所示。

图23-39 udp_usrreq 函数体: PRU _SOCKADDR 、PRU _PEERADDR 和PRU _SENSE 请求

501-506

函数i n _ s e t s o c k a d d r和i n _ s e t p e e r a d d r从PCB中取得信息,并把结果保存

在addr参数中。 507-511

系统调用 f s t a t产生P R U _ S E N S E请求。该函数返回 O K,但并不返回其他信息。

我们将在后面看到, TCP把发送缓存的大小作为 stat结构的st_blksize元素返回。 图23-40显示了其他 7个PRU_xxx请求,UDP插口不支持。

633

第 23章 UDP:用户数据报协议计计

对最后两个请求的处理略微有些不同,因为 PRU_RCVD不把指向mbuf的指针(m是一个非空 指针)作为参数传递,而PRU_RCVOOB则传递指向协议mbuf的指针来填充。两种情况下,立即返 回错误,不终止switch语句的执行,释放mbuf链。调用方用PRU_RCVOOB释放它分配的mbuf。

图23-40 udp_usrreq 函数体:不支持的 7个请求

23.11 udp_sysctl函数 U D P 的s y s c t l 函数只支持一个选项, U D P 检验和标志位。系统管理员可以禁止用 s y s c t l( 8 )程序使能或禁止 U D P检验和。图 2 3 - 4 1 显示了 u d p _ s y s c t l函数。该函数调用 sysctl_int取得或设置整数 udpcksum的值。

图23-41 udp_sysctl 函数

23.12 实现求精 23.12.1 UDP PCB高速缓存 在22.12节中,我们讲到 PCB搜索的一般性质,以及代码是如何线性搜索协议的 PCB表的。 现在我们把它和图 23-24中UDP使用的“向后一个”高速缓存结合起来。 “向后一个”高速缓存的问题发生在当高速缓存的 PCB中有通配值时 (本地地址,外部地址

634

计计TCP/IP详解 卷 2:实现

或外部端口):高速缓存的值永远不和收到的数据报匹配。 [Partridge和Pink 1993] 测试的一个 解决办法是,修改高速缓存,不比较通配值。也就是说,不再把 P C B中的外部地址和数据报 的源地址进行比较,而是只有当 PCB中的外部地址不是通配地址时,才比较这两个值。 这个办法有一个微妙的问题 [ P a r t r i d g e和Pink 1993]。假定有两个插口绑定到本地端口 5 5 5 上。其中一个有三个通配成份,而另一个已经连接到外部地址 1 2 8 . 1 . 2 . 3,外部端口 1 6 0 0。如 果我们高速缓存第一个 P C B,且有一个数据报来自 1 2 8 . 1 . 2 . 3,端口 1 6 0 0,则不能仅仅因为高 速缓存的值具有通配外部地址就不比较外部地址。这叫做高速缓存隐藏 (cache hiding)。在这 个例子中,高速缓存的 PCB隐藏了另一个更好匹配的 PCB。 为解决高速缓存隐藏,当在高速缓存加上或删除一个入口时,要做更多的工作。不能高 速缓存那些可能隐藏其他 P C B的P C B。但这很简单,因为普通情形是每个本地端口都有一个 插口。刚才我们给的例子中,两个插口都绑定到本地端口 5 5 5,尽管可能 (尤其在一个多接口 主机上),但很少见。 [ P a r t r i d g e和Pink 1993] 的另一个提高测试的也是记录最后发送的数据报的 P C B。这是 [Mogul 1991]提出的,指出在所有收到的数据报中,一半都是对最后发送的数据报的回答。在 这里高速缓存隐藏也是个问题,所以不高速缓存那些可能隐藏其他 PCB的PCB。 在通用系统上测试 [Partridge和Pink 1993] 的两种高速缓存结果是, 100 000个左右收到的 UDP数据报显示出 57%命中最近收到 PCB高速缓存, 30%命中最近发送 PCB高速缓存。相比于 没有高速缓存的版本, udp_input使用的CPU时间减少了一半还多。 这两种高速缓存还在某种程度上依赖于位置:刚刚到达的 U D P数据报极大可能来自与最 近收到或发送 U D P数据报相同的对等实体上。后者对发送一个数据报并等待回答的请求—应 答应用程序很典型。 [ M c K e n n e y和Dove 1992] 显示某些应用程序,如联机交易处理 (OLTP)系 统的数据入口,没有产生 [Partridge和Pink 1993] 观察到的很高的命中率。正如我们在 22.12节 中提到的,对于具有上千个 OLTP连接的系统来说,把 P C B放在哈希链上,相对于最近收到和 最近发送高速缓存而言,性能提高了一个数量级。 23.12.2 UDP检验和 提高实现性能的下一个领域是把进程和内核之间的数据复制与检验和计算结合起来。 Net/3中, 在输出操作中,每个数据都被处理两遍:一次是从进程复制到mbuf中(uiomove函数,被sosend 调用);另一次是计算UDP检验和(函数in_cksum被udp_output调用)。输入跟输出一样。 [ P a r t r i d g e和 Pink1993] 修改了图 2 3 - 1 4的 U D P 输出处理,调用一个 U D P 专有函数 u d p _ s o s e n d,而不是s o s e n d。这个新函数计算 UDP 首部和内嵌的伪首部的检验和 (不调用 通用的 i n _ c k s u m函数),然后用特殊函数 i n _ u i o m o v e把数据从进程复制到一个 m b u f链上 (不是通用函数 u i o m o v e),由这个新函数复制数据,更新检验和。采用这个技术,花在复制 数据和计算检验和的时间减少了 40%到45%。 在接收方情况就不同了。 UDP 计算U D P首部和伪首部的检验和,移走 U D P首部,把数据 报在合适的插口上排队。当应用程序读取数据报时,

s o r e c e i v e 的一个特殊版本

(u d p _ s o r e c ei v e )在把数据复制到用户高速缓存的同时,计算检验和。但是,如果检验和不 正确,在整个数据报被复制到用户高速缓存之前,检测不到错误。对于普通的阻塞插口来说, u d p _ s o r e c e i v e仅仅等待下一个数据报的到达。但是若插口是无阻塞的,且下一个数据报 还没有准备好传给进程,就必须返回差错 E W O U L D B L O C K。对于无阻塞读的 U D P插口来说,

635

第 23章 UDP:用户数据报协议计计

这意味着插口接口的两个变化: 1) s e l e c t函数可以指示无阻塞 UDP 插口可读,但如果检验和失败,其中一个读函数依 然要返回错误EWOULDBLOCK。 2) 因为是在数据报被复制到用户高速缓存之后检测到检验和错误,所以即使读没有返回 数据,应用程序的高速缓存也被改变了。 即使是阻塞插口,如果有检验和错误的数据报包含了 1 0 0字节的数据,而下一个没有错误 的数据报包含 4 0字节的数据,则 r e c v f r o m的返回长度是 4 0,但跟在用户高速缓存后面的 6 0 字节没有改变。 [Partridge和Pink1993] 在六台不同计算机上,对单纯复制和有检验和的复制的计时作了比 较。结果显示,在许多体系结构的机器上,在复制操作中计算检验和无需额外时间。这种情 况是在内存访问速度和 CPU处理速度正确匹配的系统上的,目前许多 RISC处理器都符合条件。

23.13 小结 UDP 是一个无连接的简单协议,这是我们为什么在 T C P之前讨论它的原因。 U D P输出很 简单:I P和U D P首部放在用户数据的前面,尽可能填满首部,把结果传递给 i p _ o u t p u t。唯 一复杂的是 U D P检验和计算,包括只为计算 U D P检验和而加上一个伪首部。我们将在第 2 6章 遇到用于计算TCP检验和的伪首部。 当u d p _ i n p u t收到一个数据报时,它首先完成一个常规确认 (长度和检验和 );然后的处 理根据目的 I P地址是单播地址、广播或多播地址而不同。最多把单播数据报提交给一个进程, 但多播或广播数据报可能会被提交给多个进程。“向后一个”高速缓存适用于单播,其中维护 着一个指向在其上接收数据报的最近 Internet PCB的指针。但是,我们也看到,由于 UDP应用 程序普遍使用通配地址,所以这个高速缓存技术实际上毫无用处。 调用u d p _ c t l i n p u t函数处理收到的 I C M P报文, u d p _ u s r r e q函数处理来自插口层的 PRU_xxx请求。

习题 23.1 列出udp_output传给ip_output的mbuf链的五种类型(提示:看看 sosend)。 23.2 当进程为外出的数据报指定了 IP选项时,上一题会是什么答案? 23.3 UDP 客户需要调用bind吗?为什么? 23.4 如果插口没有连接上,并且图 23-15中调用M _ P R E P E N D失败,那么在u d p _ o u t p u t 里,处理器优先级会发生什么变化? 23.5 udp_output不检测目的端口 0。它可能发送一个具有目的端口 0的UDP数据报吗? 23.6 假定当把一个数据报发送到一个广播地址时, I P _ R E C V D S T A D D R插口选项有效, 你如何确定这个地址是否是一个广播地址? 23.7 谁释放udp_saveopt(图23-38)分配的mbuf? 23.8 进程如何断连连接上的 U D P插口?也就是说,进程调用 c o n n e c t并与对等实体交 换数据报,然后进程要断连插口。允许它调用 sendto,并向其他主机发送数据报。 23.9 我们在图22-25的讨论中,注意到一个用外部IP地址255.255.255.255调用connect的UDP 应用程序,在接口上发送时,是把该接口对应的广播地址作为目的IP地址。如果UDP应 用使用未连接的插口,用目的地址255.255.255.255调用sendto,会发生什么情况?

第24章 TCP:传输控制协议 24.1 引言 传输控制协议,即 T C P,是一种面向连接的传输协议,为两端的应用程序提供可靠的端 到端的数据流传输服务。它完全不同于无连接的、提供不可靠的数据报传输服务的 UDP协议。 我们在第 2 3章中详细讨论了 U D P的实现,有 9个函数、约 8 0 0行C代码。我们将要讨论的 TCP实现包括28个函数、约 4500行C代码,因此,我们将 TCP的实现分成 7章来讨论。 这几章中不包括对 T C P概念的介绍,假定读者已阅读过卷 1的第1 7章~第2 4章,熟悉 T C P 的操作。

24.2 代码介绍 T C P实现代码包括 7个头文件,其中定义了大量的 T C P结构和常量和 6个C文件,包含 T C P 函数的具体实现代码。文件如图 24-1所示。 文





netinet/tcp.h

t c p h d r结构定义

netinet/tcp_debug.h

t c p _ d e b u g结构定义

netinet/tcp_fsm.h

TCP有限状态机定义



netinet/tcp_seg.h

实现TCP序号比较的宏定义

netinet/tcp_timer.h

TCP定时器定义

netinet/tcp_var.h

t c p c b (控制块)和t c p s t a t (统计)结构定义

netinet/tcpip.h

T C P + I P首部定义

netinet/tcp_debug.c

支持S O _ D E B U G 协议端口号调试(第27.10节)

netinet/tcp_input.c

t c p _ i n p u t及其辅助函数 (第28和第29章)

netinet/tcp_output.c

t c p _ o u t p u t及其辅助函数 (第26章)

netinet/tcp_subr.c

各种TCP子函数(第27章)

netinet/tcp_timer.c

TCP定时器处理 (第25章)

netinet/tcp_usrreq.c

PRU_xxx请求处理(第30章)

图24-1 TCP各章中将讨论的文件

图24-2描述了各 TCP函数与其他内核函数之间的关系。带阴影的椭圆分别表示我们将要讨 论的9个主要的TCP函数,其中 8个出现在 protosw结构中(图24-8),第9个是tcp_output。 24.2.1 全局变量 图24-3列出了TCP函数中用到的全局变量。

637

第24章 TCP:传输控制协议计计

插口接收缓冲区

系统初始化

多种系统调用

软件中断

每200ms

每500ms

内核mbuf耗尽

图24-2 TCP函数与其他内核函数间的关系 变



tcb tcp_last_inpcb tcpstat tcp_outflags tcp_recvspace tcp_sendspace tcp_iss tcprexmtthresh tcp_mssdflt tcp_rttdflt tcp_do_rfrc1323 tcp_now tcp_keepidle tcp_keepintvl tcp_maxidle

数据类型 struct inpcb struct inpcb * struct tcpstat u_char u_long u_long tcp_seq int int int int u_long int int int





T C P I n t e r n e t P C表表头 B 指向最后收到报文段的 PCB的指针:“后面一个”高速缓存 TCP统计数据(图24-4) 输出标志数组,索引为连接状态 (图24-16) 端口接收缓存大小默认值 (8192字节) 端口发送缓存大小默认值 (8192字节) TCP发送初始序号 (ISS) ACK重复次数的门限值 (3),触发快速重传 默认MSS值(512字节) 没有数据时 RTT的默认值(3秒) 如果为真(默认值),请求窗口大小和时间戳选项 用于RFC 1323时间戳实现的 500 ms计数器 保活:第一次探测前的空闲时间 (2小时) 保活:无响应时两次探测的间隔时间 (75秒) 保活:探测之后、放弃之前的时间 (10分钟)

图24-3 后续章节中将介绍的全局变量

24.2.2 统计量 全局结构变量 t c p s t a t中保存了各种 T C P统计量,图 2 4 - 4描述了各统计量的具体含义。 在接下来的代码分析过程中,读者会了解到这些计数器数值的变化过程。

638

计计TCP/IP详解 卷 2:实现



tcpstat成员 tcps_accepts tcps_closed tcps_connattempt tcps_conndrops tcps_connects tcps_delack tcps_drops tcps_keepdrops tcps_keepprobe tcps_keeptimeo tcps_pawsdrop tcps_pcbcachemiss tcps_persisttimeo tcps_predack tcps_preddat tcps_rcvackbyte tcps_rcvackpack tcps_rcvacktoomuch tcps_rcvafterclose tcps_rcvbadoff tcps_rcvbadsum tcps_rcvbyte tcps_rcvbyteafterwin tcps_rcvdupack tcps_rcvdupbyte tcps_rcvduppack tcps_rcvoobyte tcps_rcvoopack tcps_rcvpack tcps_rcvpackafterwin tcps_rcvpartdupbyte tcps_rcvpartduppack tcps_rcvshort tcps_rcvtotal tcps_rcvwinprobe tcps_rcvwinupd tcps_rexmttimeo tcps_rttupdated tcps_segstimed tcps_sndacks tcps_sndbyte tcps_sndctrl tcps_sndpack tcps_sndprobe tcps_sndrexmitbyte tcps_sndrexmitpack tcps_sndtotal tcps_sndurg tcps_sndwinup tcps_timeoutdrop



被动打开的连接数 关闭的连接数 (包括意外丢失的连接 ) 试图建立连接的次数 (调用c o n n e c t ) 在连接建立阶段失败的连接次数 (SYN收到之前 ) 主动打开的连接次数 (调用c o n n e c t成功) 延迟发送的 A C K数 意外丢失的连接数 (收到SYN之后) 在保活阶段丢失的连接数 (己建立或正等待 SYN) 保活探测指针发送次数 保活定时器或连接建立定时器超时次数 由于PSWS而丢失的报文段数 PCB高速缓存匹配失败次数 持续定时器超时次数 对ACK报文首部预测的正确次数 对数据报文首部预测的正确次数 由收到的 ACK报文确认的发送字节数 收到的ACK报文数 收到的对未发送数据进行确认的 ACK报文数 连接关闭后收到的报文数 收到的首部长度无效的报文数 收到的检验和错误的报文数 连续收到的字节数 在滑动窗口己满时收到的字节数 收到的重复 A C K报文的次数 在完全重复报文中收到的字节数 内容完全一致的报文数 收到失序的字节数 收到失序的报文数 顺序接收的报文数 携带数据超出滑动窗口通告值的报文数 部分内容重复的报文中的重复字节数 部分数据重复的报文数 长度过短的报文数 收到的报文总数 收到的窗口探测报文数 收到的窗口更新报文数 重传超时次数 RTT估算值更新次数 可用于RTT测算的报文数 发送的纯 ACK报文数(数据长度 =0) 发送的字节数 发送的控制 (SYN、FIN、RST)报文数(数据长度=0) 发送的数据报文数 (数据长度>0) 发送的窗口探测次数 (等待定时器强行加入 1字节数据) 重传的数据字节数 重传的报文数 发送的报文总数 只携带URG标志的报文数 (数据长度=0) 只携带窗口更新信息的报文数 (数据长度=0) 由于重传超时而丢失的连接数

图24-4 tcpstat 结构变量中保存的 TCP统计量

SNMP使用 • • •



• •

• •

• • •

639

第24章 TCP:传输控制协议计计

在命令行输入netstat-s,系统将输出当前 TCP的统计值。图24-5的例子显示了主机连续 运行30天后,各统计计数器的值。由于某些统计量互相关联—一个保存数据分组数目,另一 个保存相应的字节数—图中做了一些简化。例如,表中第二行 tcps_snd(pack,byte)实际 表示了两个统计量, tcps_sndpack和tcps_sndbyte。 tcps_sndbyte值应为3 722 884 824字节,而不是-22 194 928字节,平均每个 数据分组有 450字节。类似的, tcps_rcvackbyte值应为3 738 811 552字节,而不 是-21 264 360字节(平均每个数据分组 565字节)。这些数据之所以被错误地显示,是 因为n e t s t a t程序中调用 p r i n t f语句时使用了 % d (符号整型 ),而非 %l u(无符号长 整型)。所有统计量均定义为无符号长整型,上面两个统计量的值己接近无符号 3 2位 长整型的上限(232-1=4 294 967 295)。 输出

图24-5 TCP统计量样本

成员

640

计计TCP/IP详解 卷 2:实现

24.2.3 SNMP变量 图 2 4 - 6列出了 SNMP TCP 组中定义的 1 4个 S N M P 简单变量,以及与它们相对应的 t c p s t a t结构中的统计量。前四项的常量值在 N e t / 3中定义,计数器 t c p C u r r E s t a b用于保 存TCP PCB表中Internet PCB的数目。 图24-7列出了tcpTable,即TCP监听表(listener table )。 SNMP变量 tcpRtoAlgorithm

4

tcpRtoMin tcpRtoMax tcpMaxConn tcpActiveOpens tcpPassiveOpens tcpAttemptFails

1000 64000 -1 tcps_connattempt tcps_accepts tcps_conndrops

tcpEstabResets

tcps_drops

tcpCurrEstab

(见正文)

tcpInSegs tcpOutSegs

tcps_rcvtotal tcps_sndtotal tcps_sndrexmitpack tcps_sndrexmitpack tcps_rcvbadsum + tcps_rcvbadoff + tcps_rcvshort (未实现)

tcpRetransSegs tcpInErrs

tcpOutRsts



t c p s t a t成员或常量



用于计算重传定时时限的算法: 1=其他; 2=RTO为固定值; 3=MIL-STD-1778附录B; 4=Van Jacobson的算法; 最小重传定时时限,以毫秒为单位 最大重传定时时限,以毫秒为单位 可支持的最大 TCP连接数(-1表示动态设置 ) 从CLOSED转换到SYN SENT的次数 从LISTEN转换到SYN RCVD的次数 从SYN_SENT或SYN_RCVD转换到CLOSED的 次数+从SYN_RCVD转换到LISTEN的次数 从ESTABLISHED或CLOSE_WAIT转换到 CLOSED的次数 当前位于 ESTABLISHED或CLOSE_WAIT状态 的连接数 收到的报文总数 发送的报文总数,减去重传报文数 重传的报文总数 收到的出错报文总数

RST标志置位的发送报文数

图24-6 tcp 组中的简单 SNMP变量 index = ... SNMP变量 tcpConnState



PCB变量 t_state



连接状态: 1 = CLOSED, 2=LISTEN, 3 = SYN_SENT, 4 = SYN_RCVD, 5 = ESTABLISHED,6 = FIN_WAIT, 7 = FIN_WAIT_2, 8 = CLOSE_WAIT, 9 = LAST_ACK, 10 = CLOSING, 11 = TIME_WAIT,12 = 删除TCP控制块

tcpConnLocalAddress

inp_laddr

本地IP地址

tcpConnLocalPort

inp_lport

本地端口号

tcpConnRemAddress

inp_faddr

远端IP地址

tcpConnRemPort

inp_fport

远端端口号

图24-7 TCP监听表:tcpTable 中的变量

641

第24章 TCP:传输控制协议计计

第一个 P C B变量(t _ s t a t e)来自T C P控制块 (图2 4 - 1 3 ),其他四个变量来自 Internet PCB (图22-4)。

24.3 TCP 的protosw结构 图2 4 - 8列出了 TCP p r o t o s w结构的成员变量,它定义了 T C P协议与系统内其他协议间的 交互接口。 成员变量 pr_type pr_domain pr_ptotocol pr_flags pr_input pr_output pr_ctlinput pr_ctloutput pr_usrreg pr_init pr_fasttimo pr_slowtimo pr_drain pr_sysctl

inetsw[2] SOCK_STREAM &inetdomain IPPROTO_TCP(6) PR_CONNREQUIRED|PR_WANTRCVD tcp_input 0 tcp_ctlinput tcp_ctloutput tcp_usrreq tcp_init tcp_fasttimo tcp_slowtimo tcp_drain 0





TCP提供字节流传输服务 TCP属于Internet协议族 填充IP首部的i p _ p字段 插口层标志,协议处理中忽略 从IP层接收消息 TCP协议忽略该成员变量 处理ICMP错误的控制输入函数 在进程中响应管理请求 在进程中响应通信请求 TCP初始化 快超时函数,每 200 ms 调用一次 慢超时函数,每 500 ms 调用一次 内核mbuf耗尽时调用 TCP协议忽略该成员变量

图24-8 TCP protosw 结构

24.4 TCP的首部 t c p h d r结构定义了 T C P首部。图 2 4 - 9给出了t c p h d r结构的定义,图 2 4 - 1 0描述了T C P首 部。

图24-9 tcphdr 结构

642

计计TCP/IP详解 卷 2:实现

16位源端口号

16位目的端口号

32位序号 20字节 32位确认序号 4位 首部长度

保留 (6位)

16位窗口大小

16位紧急数据偏移量

16位TCP检验和 选项(如果有)

数据(如果有)

图24-10 TCP首部及可选数据

大多数 R F C文档,相关书籍 (包括卷 1 )和接下来要讨论的 T C P实现代码,都把 t h _ u r p称为“紧急指针 ( u rgent pointer) ”。更准确的名称应该是“紧急数据偏移量 (urgent offset)”,因为这个字段给出的 16 bit无符号整数值,与 t h _ s e g序号字段相加 后,得到发送的紧急数据最后一个八位组的 32 bit序号(关于该序号应该是紧急数据最 后一个字节的序号,或者是紧急数据结束后的第一个字节的序号,一直存在着争议。 但就我们目前的讨论而言,这一点无关紧要 )。图2 4 - 1 3中,T C P代码把保存紧急数据 最后一个八位组的 32 bit 序号的 s n d _ u p正确地称为“紧急数据发送指针”。如果将 TCP首部的16 bit偏移量也称为“指针”,容易引起误解。在练习 26.6中,我们重申了 “紧急指针”和“紧急数据偏移量”间的区别。 T C P首部中4 bit的首部长度、接着的 6 bit的保留字段和 6 bit的码元标志,在 C结构中定义 为两个4 bit的比特字段,和紧跟的一个 8 bit字节。为了处理两个比特字段在 8 bit 字节中的存放 次序,C代码根据不同的主机字节存储顺序使用了 #ifdef语句。 还请注意, T C P中称4 bit 的t h _ o f f为“首部长度”,而C代码中称之为“数据偏移量”。 两种名称都正确,因为它表示 T C P首部的长度,包括可选项,以 32 bit为单位,也就是指向用 户数据第一个字节的偏移量。 th_flags成员变量包括6个码元标志比特,通过图 24-11中定义的名称读写。 N e t / 3中,T C P首部通常意味着“ I P首部 + TCP首部”。t c p _ i n p u t处理收到的 I P数据报 和t c p _ o u t p u t构造待发送的 I P数据报时都采用了这一思想。图 2 4 - 1 2中给出了 t c p i p h d r结 构的定义,形式化地描述了组合的 IP/TCP首部。 38-58

图23-19给出的ipovly结构定义了 20字节长度的 IP首部。通过前面章节的讨论可知,

尽管长度相同(20字节),但这个结构并不是一个真正的 IP首部。

643

第24章 TCP:传输控制协议计计

th_flags TH_ACK TH_FIN TH_PUSH TH_RST TH_SYN TH_URG





确认序号(t h _ a c k)有效 发送方字节流结束 接收方应该立即将数据提交给应用程序 连接复位 序号同步(建立连接) 紧急数据偏移量 (t h _ u r p)有效

图24-11 th_flags 值

图24-12 tcpiphdr 结构定义:组合的 IP/TCP首部

24.5 TCP的控制块 在图2 2 - 1中我们看到,除了标准的 Internet PCB外,T C P还有自己专用的控制块, t c p c b 结构,而UDP则不需要专用控制块,它的全部控制信息都已包含在 Internet PCB中。 TCP控制块较大,需占用 140字节。从图 22-1中可看到,Internet PCB与TCP控制块彼此对 应,都带有指向对方的指针。图 24-13给出了TCP控制块的定义。

图24-13 tcpcb 结构:TCP控制块

644

计计TCP/IP详解 卷 2:实现

图24-13 (续)

645

第24章 TCP:传输控制协议计计

现在暂不讨论上述成员变量的具体含义,在后续代码中遇到时再详细分析。 图24-14列出了t_flags变量的可选值。 t_flags TF_ACKNOW TF_DELACK TF_NODELAY TF_NOOPT TF_SENTFIN TF_RCVD_SCALE TF_RCVD_TSTMP TF_REQ_SCALE TF_REQ_TSTMP





立即发送ACK 延迟发送ACK 立即发送用户数据,不等待形成最大报文段 (禁止Nagle算法) 不使用TCP选项(永不填充TCP选项字段) FIN已发送 对端在SYN报文中发送窗口变化选项时置位 对端在SYN报文中发送时间戳选项时置位 已经/将要在SYN报文中请求窗口变化选项 已以/将要在SYN中请求时间戳选项

图24-14 t_flags 取值

24.6 TCP的状态变迁图 TCP协议根据连接上到达报文的不同类型,采取相应动作,协议规程可抽象为图 24-15所示 的有限状态变迁图。读者在本书的扉页前也可找到这张图,以便在阅读有关TCP的章节时参考。 图中的各种状态变迁组成了 T C P有限状态机。尽管 T C P协议允许从 L I S T E N状态直接变迁 到S Y N _ S E N T状态,但使用 SOCKET API编程时这种变迁不可实现 (调用l i s t e n后不可以调 用connect)。 TCP控制块的成员变量 t_state保存一个连接的当前状态,可选值如图 24-16所示。 图中还定义了 t c p _ o u t f l a g s数组,保存了处于对应连接状态时 t c p _ o u t p u t将使用的 输出标志。 图2 4 - 1 6还列出了与符号常量相对应的数值,因为在代码中将利用它们之间的数值关系。 例如,有下面两个宏定义: #define TCPS_HAVERCVDSYN(s) ((s)>=TCPS_SYN_RECEIVED) #define TCPS_HAVERCVDFIN(s) ((s)>=TCPS_TIME_WAIT)

类似地,连接未建立时,即 t _ s t a t e小于T C P S _ E S T A B L I S H E D时,t c p _ n o t i f y处理 ICMP差错的方式也不同。 T C P S _ H A V E R C V D S Y N的命名是正确的,但 T C P S _ H A V E R C V D F I N则可能引起误 解,因为在 CLOSE_WAIT、CLOSING和LAST_ACK状态也会收到FIN。我们将在第 29章中遇到该宏。 半关闭 当进程调用 s h u t d o w n且第二个参数设为 1时,称为“半关闭”。T C P发送F I N,但允许进 程在同一端口上继续接收数据 (卷1的18.5节中举例介绍了 TCP的半关闭)。 例如,尽管图 2 4 - 1 5中只在 E S TA B L I S H E D状态标注了“数据传输”,但如果进程执行了 “半关闭”,则连接变迁到FIN_WAIT_1状态和其后的FIN_WAIT_2状态,在这两个特定状态中, 进程仍然可以接收数据。

646

计计TCP/IP详解 卷 2:实现

起点

appl被动打开 send:

超时 被动打开

关闭 同时打开

主动打开

或超时

关闭 数据传输状态 关闭

同时关闭

被动关闭



超时 主动关闭 正常情况下,客户端的状态变迁 正常情况下,服务器端的状态变迁 appl: 应用程序执行操作引起的状态变迁 recv: 接收报文引起的状态变迁 send: 状态变迁中发送的报文

图24-15 TCP状态变迁图

24.7 TCP的序号 T C P连接上传输的每个数据字节,以及 S Y N、F I N等控制报文都被赋予一个 32 bit的序号。 T C P首部的序号字段 (图2 4 - 1 0 )填充了报文段第一个数据字节的 32 bit的序号,确认号字段填充

647

第24章 TCP:传输控制协议计计

了发送方希望接收的下一序号,确认已正确接收了所有序号小于等于确认号减 1的数据字节。 换言之,确认号是 A C K发送方等待接收的下一序号。只有当报文首部的 A C K标志置位时,确 认序号才有效。读者将看到,除了在主动打开首次发送 S Y N时( S Y N _ S E N T状态,参见图 2 4 16中的tcp_outflags[2])或在某些RST报文段中, ACK标志总是被置位的。 t_state



TCPS_CLOSED TCPS_LISTEN TCPS_SYN_SENT TCPS_SYN_RECEIVED TCPS_ESTABLISHED TCPS_CLOSE_WAIT TCPS_FIN_WAIT_1 TCPS_CLOSING TCPS_LAST_ACK TCPS_FIN_WAIT_2 TCPS_TIME_WAIT

0 1 2 3 4 5 6 7 8 9 10





关闭 监听连接请求 (被动打开) 已发送SYN(主动打开) 已发送并接收 SYN;等待ACK 连接建立(数据传输) 已收到FIN,等待应用程序关闭 已关闭,发送 FIN;等待ACK和FIN 同时关闭;等待 ACK 收到的FIN已关闭;等待 ACK 已关闭,等待 FIN 主动关闭后 2MSL等待状态

tcp_outflags[] TH_RST | TH_ACK 0 TH_SYN TH_SYN | TH_ACK TH_ACK TH_ACK TH_FIN | TH_ACK TH_FIN | TH_ACK TH_FIN | TH_ACK TH_ACK TH_ACK

图24-16 t_state 取值

由于TCP连接是全双工的,每一端都必须为两个方向上的数据流维护序号。 TCP控制块中 (图24-13)有13个序号:8个用于数据发送 (发送序号空间),5个用于数据接收 (接收序号空间)。 图2 4 - 1 7给出了发送序号空间中 4个变量间的关系: s n d _ w n d、s n d _ u n a、s n d _ n x t和 snd_max。这个例子列出了数据流的第 1~第11字节。 提供的窗口 (由接收方通告) 可用窗口

发送尚未确认

发送并已确认

最早的未确认 过的序号

无法发送直 到窗口移动 能够发送

下一个发送序号

最大发送序号

图24-17 发送序号空间举例

一个有效的 ACK序号必须满足: snd_una < 确认序号 > TCP_RTT_SHIFT) + (tp)->t_ttvar)

其实,这就是我们很熟悉的公式 RTO=s rt t+4×rttvar 用t c p _ x m i t _ t i m e r更新过的缩放后的变量替代上式中的 s rt t和rt t v a r,得到宏的表达 式: RTO =

t_srtt t_rttvar t_srtt +4× = + t_rttvar 8 4 8

此外, RTO 取值应在规定范围之内,最小值为连接上设定的最小

RT O (t _ r t t m i n ,

t_newtcpcb初始化为2个滴答),最大值为 128个滴答(TCPTV_REXMTMAX)。 3. 清除软错误变量 1367-1374 由于只有当收到了已发送的数据报文的确认时,才会调用tcp_xmit_timer,如 果连接上发生了软错误 (t_softerror),该错误将被丢弃。下一节中将详细讨论软错误。

25.11 重传超时:tcp_timers函数 我们现在回到 t c p _ t i m e r s函数,讨论 2 5 . 6节中未涉及的最后一个 c a s e语句:处理重传 定时器。如果在 RTO内没有收到对端对一个已发送数据报的确认,则执行此段代码。 图2 5 - 2 5小结了重传定时器的操作。假定 t c p _ o u t p u t计算的报文首次重传时限为 1 . 5秒, 这是LAN的典型值(参见卷1的图21-1)。 秒



t_rxtshift的新值

图25-25 发送数据时重传定时器小结

x轴为时间轴,以秒为单位,标注依次为: 0、1 . 5、4 . 5等等。这些数字的下方,给出了代 码中用到的t _ r x t s h i f t的值。连续12次重传后,总共为 542.5秒(约9分钟),TCP将放弃并丢 弃连接。 RFC 793建议在建立新连接时,无论主动打开或被动打开,应定义一个参数规定 TCP发送数据的总时限,也就是 TCP在放弃发送并丢弃连接之前试图传输给定数据报 文的总时间。推荐的默认值为 5分钟。 RFC 11 2 2要求应用程序必须为连接指定一个参数,限定 T C P总的重传次数或者 T C P试图发送数据的总时间。这个参数如果设为“无限”,那么 T C P永不会放弃,还 可能不允许终端用户终止连接。 在代码中可看到, N e t / 3不支持应用程序的上述控制权: T C P放弃传输之前的重 传次数是固定的 (12),所用的总时间取决于 RTT。

674

计计TCP/IP详解 卷 2:实现

图25-26给出了重传超时 case语句的前半部分。

图25-26 tcp_timers 函数:重传定时器超时,前半部分

1. 递增移位计数器 146 重传移位计数器 (t_rxtshift)在每次重传时递增,如果大于 12(TCP_MAXRXTSHIFT), 连接将被丢弃。图25-25给出了t_rxtshift每次重传时的取值。请注意两种丢弃连接的区别, 由于收不到对端对已发送数据报文的确认而造成的丢弃连接,和由于保活定时器的作用,在 长时间空闲且收不到对端响应时丢弃连接。两种情况下,

T C P 都会向应用进程报告

ETIMEDOUT差错,除非连接收到了一个软错误。 2. 丢弃连接 147-152

软错误指不会导致 T C P终止已建立的连接或正试图建立的连接的错误,但系统会

记录出现的软错误,以备 TCP将来放弃连接时参考。例如,如果 TCP重传SYN报文段,试图建 立新的连接,但未收到响应, T C P将向应用进程报告 E T I M E D O U T差错。但如果在重传期间, 收到一个 I C M P“主机不可达”差错代码, t c p _ n o t i f y会在t _ s o f t e r r o r中存储这一软错 误。如果 T C P最终决定放弃重传,返回给应用进程的差错代码将为 E H O S T U N R E A C H,而不是

675

第 25 章 TCP的定时器计计

ETIMEDOUT,从而向应用进程提供了更多的信息。如果 TCP发送SYN后,对端的响应为 RST, 这是个硬错误,连接立即被终止,返回差错代码 ECONNRFUSED(图28-18)。 3. 计算新的RTO 153-157

利用TCP_ R E X M T V A L宏实现指数退避,计算新的 RTO值。代码中,给定报文第一

次重传时 t _ r x t s h i f t等于1,因此, RTO 值为T C P _ R E X M T V A L计算值的两倍。新的 RTO 值 存储在 t _ r x t c u r 中,供连接的重传定时器 — t _ t i m e r [ T C P T _ R E X M T ] — 使用, tcp_input在启动重传定时器时会用到它 (图28-12和图29-6)。 4. 向IP询问更换路由 158-167

如果报文段已重传 4次以上, i n _ l o s i n g 将释放缓存中的路由 (如果存在 ),

t c p _ o u t p u t再次重传该报文时 (图25-27中c a s e语句的结尾处),将选择一条新的,也许好一 些的路由。从图 2 5 - 2 5可看到,每次重传定时器超时时,如果重传时限已超过 2 2 . 5秒,将调用 in_losing。 5. 清除RTT估计器 168-170

代码中,已平滑的 RTT估计器(t _ s r t t)被置为0(t _ n e w t c p c b中曾将其初始化为

0 ),强迫t c p _ x m i t _ t i m e r将下一个 RT T测量值做为已平滑的 RT T估计器,这是因为报文段 重传次数已超过 4次,意味着 TCP的已平滑的 RTT估计器可能已失效。若重传定时器再次超时, 进入 c a s e语句后,将利用 T C P _ R E X M T V A L计算新的 RTO 值。由于 t _ s r t t被置为 0,新的计 算值应与本次重传中的计算值相同,再利用指数退避算法加以修正 (图25-28中,在42.464秒处 的重传很好地说明了上面讨论的概念 )。 再次计算RTO时,利用公式 RTO =

t_srtt + t_rttvar 8

由于t _ s r t t等于0,RTO取值不变。如果报文的重传定时器再次超时 (图25-28中从84.064 秒到217.84秒),case语句再次被执行, t_srtt等于0,t_rttvar不变。 6. 强迫重传最早的未确认数据 171

下一个发送序号 (s n d _ n x t)被置为最早的未确认的序号 (s n d _ u n a)。回想图 2 4 - 1 7中,

snd_nxt大于snd_una。把snd_nxt回移,将重传最早的未确认过的报文。 7. Karn算法 172-175

RTT计数器,t_rtt,被置为0。Karn算法认为由于该报文即将重传,对该报文的

计时也就失去了意义。即使收到了 A C K,也无法区分它是对第一次报文,还是对第二次报文 的确认。 [Karn and Partridge 1987]和卷1的2 1 . 3节中都介绍了这一算法。因此, T C P只对未重 传报文计时,利用 t _ r t t计数器得到样本值,并据此修正 RT T估计器。在后面的图 2 9 - 6中将 看到,如何使用 RFC 1323的时间戳功能取代 Karn算法。 25.11.1 慢起动和避免拥塞 图2 5 - 2 7给出了 c a s e语句的后半部分,实现慢起动和避免拥塞,并重传最早的未确认过 的报文。 由于重传定时器超时,网络中很可能发生了拥塞。这种情况下,需要用到 T C P的拥塞避 免算法。如果最终收到了对端发送的确认, T C P采用慢起动算法以较慢的速率继续进行数据

676

计计TCP/IP详解 卷 2:实现

传输。卷1的20.6节和21.6节详细讨论了这两种算法。 176-205

win被置为现有窗口大小 (接收方通告的窗口大小 snd_wnd和发送方拥塞窗口大小

s n d _ c w n d,两者之中的较小值 )的一半,以报文为单位,而非字节 (因此除以 t _ m a x s e g), 最小值为2。它的值等于网络拥塞时现有窗口大小的一半,也就是慢起动门限, t _ s s t h r e s h(以字节为单位,因此乘以 t _ m a x s e q)。拥塞窗口的大小, s n d _ c w n d,被置为 只容纳1个报文,强迫执行慢起动。上述做法假定造成网络拥塞的原因之一是本地数据发送太 快,因此在拥塞发生时,必须降低发送窗口大小。 这段代码放在一对括号中,是因为它是在 4 . 3 B S D和N e t / 1实现之间添加的,并要 求有自己的局部变量 (win)。 206 连续重复ACK计数器, t_dupacks (用于29.4节中将介绍的快速重传算法 )被置为0。我 们将在第29章中介绍它在TCP快速重传和快速恢复算法中的用途。 208

t c p _ o u t p u t重新发送包含最早的未确认序号的报文,即由于重传定时器超时引发了

报文重传。

图25-27 tcp_timer 函数:重传定时器超时,后半部分

677

第 25 章 TCP的定时器计计

25.11.2 精确性 TCP维护的这些估计器的精确性如何呢?首先应指出,因为 RTT以500 ms为测量单位,是 非常不精确的。已平滑的 RTT估计器和平均偏差的精确性要高一些 (缩放因子为8和4),但也不 够,L A N的RT T是毫秒级,横跨大陆的 RT T约为6 0 m s左右。这些估计器仅仅给出了 RT T的上 限,从而在设定重传定时器时,可以不考虑由于重传时限过小而造成不必要的重传。 [Brakmo, O’Malley, and Peterson 1994]描述的TCP实现,能够提供高精度的 RTT样本。他 们的做法是,发送报文段时记录系统时钟读数 (精度比以 500 ms 为测量单位要高得多 ),收到 ACK时再次读取系统时钟,从而得到高精度的 RTT。 N e t / 3支持的时间戳功能 ( 2 6 . 6节)本来可以提供较高精度的 RT T,但N e t / 3将时间戳的精度 也定为500 ms。

25.12 一个RTT的例子 下面讨论一个具体的例子,说明上述计算是如何进行的。我们从主机

bsdi向

v a n g o g h . c s . b e r k e l e y . e d u发送1 2 2 8 8字节的数据。在发送过程中,故意断开工作中的 PPP链路,之后再恢复,看看 TCP如何处理报文的超时与重传。为发送数据,我们运行自己的 sock程序(参见卷1的附录C),加-D选项,置位插口的 SO_DEBUG选项(27.10节)。传输结束后, 运行 t r p t ( 8 )程序检查留在内核的环形缓存中的调试记录,之后打印 T C P控制块中我们感兴 趣的时钟变量。 图2 5 - 2 8列出了各变量在不同时刻的值。我们用 M:N表示序号 M ~ N-1已被发送。本例中 的每个报文段都携带了 5 1 2字节的数据。符号“ ACK M ”表示 A C K报文的确认字段为 M。标 注“实际差值 ( m s )”栏列出了 RT T定时器打开时刻和关闭时刻间的时间差值。标注“ r t t (参 数)”栏列出了调用 t c p _ x m i t _ t i m e r时第二个参数的值: RT T定时器打开时刻和关闭时刻 间的滴答数再加 1。 t c p _ n e w t c p c b函数完成 t _ s r t t、t _ r t t v a r和t _ r x t c u r的初始化,时刻 0 . 0对应的 即为变量初始值。 第一个计时报文是最初的SYN报文,365 ms后收到了对端的ACK,调用tcp_xmit_timer, r t t参数值为2。由于这是第一个 RTT测量值(t _ s r t t = 0),执行图25-23中的e l s e语句,计算 RTT估计器初始值。 携带1 ~ 5 1 2字节的数据报文是第二个计时报文, 1 . 2 5 9秒时收到对应的 A C K,RT T估计器 被更新。 从接下来的三个报文可看出,连续报文是如何被确认的。 1 . 2 6 0秒时发送携带 5 1 3 ~ 1 0 2 4字 节的报文,并启动定时器。之后又发送了携带 1 0 2 5 ~ 1 5 2 6字节的报文,在 2 . 2 0 6秒时收到了对 端的A C K,同时确认了已发送的两个报文。 RT T估计器被更新,因为 A C K确认了正计时报文 的起始序号 (513)。 2 . 2 0 6秒时发送携带 1 5 3 7 ~ 2 0 4 8字节的报文,并启动定时器。 3 . 1 3 2秒时收到对应的 A C K, RTT估计器被更新。 对3 . 1 3 2秒时发送的报文段计时,重传定时器设为 5个滴答 (t _ r x t c u r的当前值 )。这时, 路由器 s u n和n e t b间的P P P链路中断,几分钟后恢复正常。重传定时器在 6 . 0 6 4秒超时,执行 图2 5 - 2 6中的代码更新 RT T变量。 t _ r x t s h i f t从0增至1,t _ r x t c u r置为1 0个滴答 (指数退

678

计计TCP/IP详解 卷 2:实现

避 ) ,重传最早的未确认过的序号

( s n d _ u n a = 3 0 7 3 ) 。 5 秒钟后,定时器再次超时,

t_rxtshift递增为2,重传定时器设为 20个滴答。 发送 时间

发送

接收

RTT 实际时间差 参数 (8个滴答) (4个滴答) 定时器 (ms)

(滴答)

图25-28 实例中的 RTT变量值和估计器

4 2 . 4 6 4秒时,重传定时器再次超时, t _ s r t t清零, t _ r t t v a r置为5。我们在图 2 5 - 2 6的 讨论中提到过,此时 t _ r x t c u r运算得到的结果相同 (因此,下一次运算的结果应为 1 6 0 )。但

679

第 25 章 TCP的定时器计计

由于t _ s r t t重置为 0,下一次更新 RT T估计器时 ( 2 1 8 . 8 3 4秒),与建立一条新的连接相类似, 得到的RTT测量值将成为新的已平滑的 RTT估计器。 之后继续进行数据传输,并且又多次更新了 RTT估计器。

25.13 小结 内核每隔200 ms和500 ms,分别调用 tcp_fasttimo函数和tcp_slowtimo函数。这两 个函数负责维护 TCP为连接建立的各种定时器。 TCP为每条连接维护下列 7个定时器: • 连接建立定时器; • 重传定时器; • 延迟ACK定时器; • 持续定时器; • FIN_WAIT_2定时器; • 2MSL 定时器; 延迟ACK定时器与其他6个定时器不同,设置它时意味着下一次 TCP200 ms定时器超时时, 延迟的 A C K报文必须被发送。其他 6个定时器都是计数器,每次 TCP 500 ms 定时器超时时, 计数器减 1。任何一个计数器减为 0时,触发 T C P完成相应动作:丢弃连接、重传报文、发送 连接探测报文等等,这些内容本章中都有详细讨论。由于某些定时器是彼此互斥的,代码用 4 个计数器实现了这 6个定时器,复杂性有所增加。 本章还介绍了重传定时器取值的标准计算方法。 TCP为每条连接维护两个 RTT估计器:已 平滑的 RT T估计器 (s rt t)和已平滑的 RT T平均偏差估计器 (rt t v a r)。尽管算法简单清楚,但由于 使用了缩放因子 (在不使用内核浮点运算的情况下保证足够的精度 ),使得代码较为复杂。

习题 25.1 T C P快速超时处理函数的效率如何? (提示:参考图 2 4 - 5中列出的延迟 A C K的次数 ) 有没有另外的实现方式? 25.2 为什么在tcp_slowtimo函数,而不是在tcp_init函数中初始化tcp_maxidle? 25.3

t c p _ s l o w t i m o递增t _ i d l e,前面已介绍过 t _ i d l e用于计数从连接上收到最后

一个报文起到当前为止的滴答数。 T C P是否需要计数从连接上发送最后一个报文段起计时的 空闲时间? 25.4 重写图25-10中的代码,分离 TCPT_2MSL计数器两种不同用法的处理逻辑。 25.5 图25-12中,连接进入FIN_WIN_2状态75秒后收到一个重复的 ACK。会发生什么? 25.6 应用程序设置 S O _ K E E P A L I V E选项时连接已空闲了 1小时。第一次连接探测报文在 何时发送, 1小时后还是 2小时后? 25.7 为什么tcp_rttdflt是一个全局变量,而非常量? 25.8 重写与习题 25.6有关的代码,实现另一种结果。

第26章 TCP 输 出 26.1 引言 函数tcp_output负责发送报文段,代码中有很多地方都调用了它。 t c p _ u s r r e q 在多种请求处理中调用了这一函数:处理 P R U _ C O N N E C T,发送初始 S Y N;处理 P R U _ S H U T D O W N, 发送F I N;处理P R U _ R C V D,应用进程从插口接收缓存中读取 若干数据后可能需要发送新的窗口大小通告;处理

P R U _ S E N D ,发送数据;处理

PRU_SENDOOB,发送带外数据。 • tcp_fasttimo调用它发送延迟的 ACK; • tcp_timers在重传定时器超时时,调用它重传报文段; • tcp_timers在持续定时器超时时,调用它发送窗口探测报文段; • tcp_drop调用它发送 RST; • tcp_disconnect调用它发送 FIN; • tcp_input在需要输出或需要立即发送 ACK时调用它; • t c p _ i n p u t在收到一个纯ACK报文段且本地有数据发送时调用它 (纯ACK报文段指不携 带数据,只确认已接收数据的报文段 ); • t c p _ i n p u t 在连续收到 3个重复的 A C K时,调用它发送一个单一报文段 (快速重传算 法); t c p _ i n p u t首先确定是否有报文段等待发送。除了存在需要发往连接对端的数据外, TCP输出还受到其他许多因素的控制。例如,对端可能通告接收窗口为零,阻止 TCP发送任何 数据;Nagle算法阻止 TCP发送大量小报文段;慢启动和避免拥塞算法限制 TCP发送的数据量。 相反,有些函数置位一些特殊标志,强迫 t c p _ o u t p u t发送报文段,如 T F _ A C K N O W标志置位 意味着必须立即发送一个 A C K。如果 t c p _ o u t p u t确定不发送某个报文段,数据 (如果存在 ) 将保留在插口的发送缓存中,等待下一次调用该函数。

26.2 tcp_output概述 tcp_output函数很大,我们将分 14个部分予以讨论。图 26-1给出了函数的框架结构。 1. 是否等待对端的 ACK? 61

如果发送的最大序号 (s n d _ m a x)等于最早的未确认过的序号 (s n d _ u n a),即不等待对端

发送A C K,i d l e为真。图 2 4 - 1 7中,i d l e应为假,因为序号 4 ~ 6己发送但还未被确认, T C P 在等待对端发送对上述序号的确认。 2. 返回慢启动 62-68

如果T C P不等待对端发送 A C K,而且在一个往返时间内也没有收到对端发送的其他

报文段,设置拥塞窗口为仅能容纳一个报文段 (t_maxseg字节),从而在发送下一个报文段时,

681

第 26章 TCP 输 出计计

强迫执行慢启动算法。如果数据传输中出现了显著的停顿 (“显著”指停顿时间超过 RT T ),说 明与先前测量 RT T时相比,网络条件己发生了变化。 N e t / 3假定出现了最坏情况,因而返回慢 起动状态。

图26-1 tcp_output 函数:框架结构

3. 发送多个报文段 69-70

控制跳转至 send后,调用ip_output发送一个报文段。但如果 ip_output确定有

682

计计TCP/IP详解 卷 2:实现

多个报文段需要发送, sendalot置为1,函数将试图发送另一个报文段。因此, ip_output 的一次调用能够发送多个报文段。

26.3 决定是否应发送一个报文段 某些情况下,在报文段准备好之前已调用了 t c p _ o u t p u t。例如,当插口层从插口的接 收缓存中移走数据,传递给用户进程时,会生成 P R U _ R C V D请求。尽管不一定,但完全有可 能因为应用进程取走了大量数据,而使得 T C P有必要发送新的窗口通告。 t c p _ o u t p u t的前 半部分确定是否存在需要发往对端的报文段。如果没有,则函数返回,不执行发送操作。 图26-2给出了判定“是否有报文段发送”测试代码的第一部分。

图26-2 tcp_output 函数:强迫数据发送

71-72 off指从发送缓存起始处算起指向第一个待发送字节的偏移量,以字节为单位。它指 向的第一个字节为 snd_una(已发送但还未被确认的字节 )。 win是对端通告的接收窗口大小 (snd_wnd)与拥塞窗口大小 (snd_cwnd)间的最小值。

683

第 26章 TCP 输 出计计

73

图2 4 - 1 6给出了 t c p _ o u t f l a g s数组,数组值取决于连接的当前状态。 f l a g s包括下列

标志比特的组合: TH_ACK、TH_FIN、TH_RST和TH_SYN,分别表示需向对端发送的报文段 类型。其他两个标志比特, T H _ P U S H和T H _ U R G,如果需要,在报文段发送之前加入,与前 4 个标志比特是逻辑或的关系。 74-105

t _ f o r c e标志非零表示持续定时器超时,或者有带外数据需要发送。这两种条件

下,调用tcp_output的代码均为: tp->t_force = 1; error = tcp_output(tp); tp->t_force = 0;

从而强迫TCP发送数据,尽管在正常情况下不会执行任何发送操作。 如果w i n等于0,连接处于持续状态 (因为t _ f o r c e非零)。如果此时插口的发送缓存中还 存在数据,则FIN标志被清除。win必须置为1,以强迫发送一个字节的数据。 如果 w i n非零,即有带外数据需要发送,则持续定时器复位,指数退避算法的索引, t_rxtshift被置为0。 图26-3给出了tcp_output的下一模块,计算发送的数据量。

图26-3 tcp_output 函数:计算发送的数据量

1. 计算发送的数据量 106

l e n等于发送缓存中比特数和 w i n(对端通告的接收窗口与拥塞窗口间的最小值,强迫

TCP发送数据时也可能等于 1字节 ),两者间的最小值减去 off。减去off是因为发送缓存中的 许多字节已发送过,正等待对端的确认。 2. 窗口缩小检查

684

计计TCP/IP详解 卷 2:实现

107-117

造成l e n小于零的一种可能情况是接收方缩小了窗口,即接收方把窗口的右界移

向左侧,下面的例子说明了这种情况。开始时,接收方通告接收窗口大小为 6字节, T C P发送 报文段,携带字节 4、5和6。紧接着, T C P又发送一个报文段,携带字节 7、8和9。图2 6 - 4显 示了两个报文段发送后本地的状态。 s n d _ w n d=6:接收窗口 (由接收方通告)

发送,尚未确认

下一个发送序号

最早的未确认的 序号

图26-4 发送4~9字节后的本地发送缓存

之后,收到一个 A C K,确认序号字段为 7 (确认所有序号小于 7的数据,包括字节 6 ),但窗 口字段为1。接收方缩小了接收窗口,此时本地的状态如图 26-5所示。

发送,尚未确认

最早的未确认过 的序号

下一个发送序号

图26-5 收到4~7字节的确认后,本地发送缓存

窗口缩小后,执行图 26-2和图26-3中的计算,得到: off = snd_nxt - snd_una = 10 - 7 = 3 win = 1 len = min(so_snd.sb_cc, win) -off = min(3, 1) - 3 = -2

假定发送缓存仅包含字节 7、8和9。 RFC 793和RFC 1122都非常不赞成缩小窗口。尽管如此,具体实现必须考虑这一 问题并加以处理。这种做法遵循了在 RFC 791 中首次提出的稳健性原则:“对接收报 文段的假设尽量少一些,对发送报文段的限制尽量多一些”。 造成 l e n小于 0的另一种可能情况是,已发送过 F I N,但还未收到确认 (见习题 2 6 . 2 )。图 26-6给出了这种情况。

685

第 26章 TCP 输 出计计

已发送和确认

最早的未确 认过的序号

下一个发送序号

图26-6 字节1~9已发送并收到对端确认,之后关闭连接

图26-6是图26-4的继续,假定字节 7~9已被确认, s n d _ u n a的当前值为 10。应用进程随后 关闭连接,向对端发送 F I N。在本章后续部分将看到, T C P发送F I N时,s n d _ n x t将增加1 (因 为FIN也需要序号 ),在本例中,s n d _ n x t将等于11,而FIN的序号为 10。执行图 26-2和图26-3 中的计算,得到: off = snd_nxt - snd_una = 11 - 10 = 1 win = 6 len = min( so_snd.sb_cc, win ) - off = min(0, 6) - 1 = -1

我们假定接收方通告接收窗口大小为 6。这个假定无关紧要,因为发送缓存中待发送的字 节数(0)小于它。 3. 进入持续状态 118-122

len被置为0。如果对端通告的接收窗口大小为 0,则重传定时器将被置为 0,任何

等待的重传将被取消。令 s n d _ n x t等于s n d _ u n a,指针返回发送窗口的最左端,连接将进入 持续状态。如果接收方最终打开了接收窗口,则 TCP将从发送窗口的最左端开始重传。 4. 一次发送一个报文段 124-127

如果需要发送的数据超过了一个报文段的容量,

l e n 置为最大报文段长度,

s e n d a l o t置为1。如图 2 6 - 1所示,这将使 t c p _ o u t p u t在报文段发送完毕后进入另一次循 环。 5. 如果发送缓存不空,关闭 FIN标志 128-129

如果本次输出操作未能清空发送缓存, FIN标志必须被清除 (防止该标志在 f l a g s

中被置位)。图26-7举例说明了这一情况。 (发送缓存)

一个报文

最早的未确认过 的序号

一个报文

下一个发送序号

图26-7 实例:FIN置位时,发送缓存不空

这个例子中,第一个 5 1 2字节的报文段已发送 (还未被确认 ),T C P正准备发送第二个报文 段( 5 1 2 ~ 1 0 2 4字节)。此时,发送缓存中仍有 1字节的数据 ( 1 0 2 5字节),应用进程关闭了连接。

686

计计TCP/IP详解 卷 2:实现

len=512(一个报文段 ),图26-3中的C表达式变为: SEQ_LT (1025, 1026)

如果表达式为真,则 FIN标志被清除;否则, TCP无法向对端发送序号为 1025的字节。 6. 计算接收窗口大小 130

w i n设定为本地接收缓存中可用空间的大小,即 TCP向对端通告的接收窗口的大小。请

注意,这是第二次用到这个变量。在函数前一部分中,它等于允许 T C P发送的最大数据量, 但从现在起,它等于本地向对端通告的接收窗口的大小。 糊涂窗口综合症 (简写为 S W S,详见卷 1第2 2 . 3节)指连接上交换的都是短报文段,而不是 最大长度报文段。这种现象的出现是由于接收方通告的接收窗口过小,或者发送方传输了许 多小报文段,因此,避免糊涂窗口综合症,需要发送方和接收方的共同努力。图 2 6 - 8给出了 发送方避免糊涂窗口综合症的做法。

图26-8 tcp_output 函数:发送方避免糊涂窗口综合症

7. 发送方避免糊涂窗口综合症的方法 142-143 如果待发送报文段是最大长度报文段,则发送它。 144-146 如果无需等待对端的 ACK(idle为真),或者Nagle算法被取消 (TF_NODELAY为真), 并且T C P正在清空发送缓存,则发送数据。 N a g l e算法(详见卷1第1 9 . 4节)的思想是:如果某个 连接需要等待对端的确认,则不允许 T C P发送长度小于最大长度的报文段。通过设定插口选 项T C P _ N O D E L A Y,可以取消这个算法。对于正常的交互式连接 (如Te l n e t或R l o g i n ),即使连 接上存在未确认过的数据,代码中的 if语句也为假,因为默认条件下 TCP会采用Nagle算法。 147-148

如果由于持续定时器超时,或者有带外数据,强迫 T C P执行发送操作,则数据将

被发送。 149-150

如果接收方的接收窗口已至少打开了一半,则发送数据。这个限制条件是为了处

687

第 26章 TCP 输 出计计

理对端一直发送小窗口通告,甚至小于报文段长度的情况。变量 m a x _ s n d w n d由t c p _ i n p u t 维护,等于连接对端发送的所有窗口通告中的最大值。实际上, T C P试图猜测对端接收缓存 的大小,并假定对端永远不会减小其接收缓存。 151-152 如果重传定时器超时,则必须发送一个报文段。 snd_max是已发送过的最高序号, 从图2 5 - 2 6可知,重传定时器超时后, s n d _ n x t将被设为 s n d _ u n a,即s n d _ n x t会指向窗口 的左侧,从而小于 snd_max。 图2 6 - 9给出了 t c p _ o u t p u t的下一部分,确定 T C P是否必须向对端发送新的窗口通告, 称之为“窗口更新”。

图26-9 tcp_output 函数:判定是否需要发送窗口更新报文

154-168 表达式 min (win, (long) TCP_MAXWIN rcv_scale)

等于插口接收缓存可用空间大小 (w i n)和连接上所允许的最大窗口大小之间的最小值,即 T C P 当前能够向对端发送的接收窗口的最大值。表达式 (tp->rcv_adv - tp->rcv_nxt)

等于TCP最后一次通告的接收窗口中剩余空间的大小,以字节为单位。两者相减得到 a d v,窗 口已打开的字节数。 t c p _ i n p u t顺序接收数据时,递增 r c v _ n x t。t c p _ o u t p u t在通告窗 口边界向右移动时,递增 rcv_adv(代码见图26-32)。 回想图 2 4 - 1 8 ,假定收到了字节 4、 5和 6,并提交给应用进程。图 2 6 - 1 0给出了此时 tcp_output中接收缓存的状态。 adv等于3,因为接收空间中还有 3个字节(字节10、11和12)等待对端填充。 169-170

如果剩余的接收空间能够容纳两个或两个以上的报文段,则发送窗口更新报文。

在收到最大长度报文段后, T C P将确认收到的所有其他报文段:“确认所有其他报文段 ( A C K every-other-segment)”的属性(马上就会看到具体的实例 )。

688

计计TCP/IP详解 卷 2:实现

接收缓存大小

下一个接收序号

通告的最高序号加1

图26-10 收到字节 4、5和6后,图24-18中连接的状态变化

171-172 如果可用空间大于插口接收缓存的一半,则发送窗口更新报文。 图2 6 - 11给出了t c p _ o u t p u t下一部分的代码,判定输出标志是否置位,要求 T C P发送相 应报文段。

图26-11 tcp_output 函数:是否需要发送特定报文段

174-178

如果T F _ A C K N O W置位,要求立即发送 A C K,则发送相应报文段。有多种情况可

导致T F _ A C K N O W置位: 200 ms延迟A C K定时器超时,报文段未按顺序到达 (用于快速重传算 法),三次握手时收到了 SYN,收到了窗口探测报文,收到了 FIN。 179-180 如果输出标志flags要求发送SYN或RST,则发送相应报文段。 181-182

如果紧急指针, s n d _ u p,超出了发送缓存的起始边界,则发送相应报文段。紧

急指针由PRU_SENDOOB请求处理代码(图30-9)负责维护。 183-190

如果输出标志 f l a g s要求发送F I N,并且满足下列条件: F I N未发送过或者 F I N等

待重传,则发送相应报文段。 FIN发送后,函数将置位 TF_SENTFIN标志。 到目前为止, t c p _ o u t p u t还没有真正发送报文段,图 2 6 - 1 2给出了函数返回前的最后一 段代码。 191-217

如果发送缓存中存在需要发送的数据 (s o _ s n d . s b _ c c非零),并且重传定时器和

持续定时器都未设定,则启动持续定时器。这是为了处理对端通告的接收窗口过小,无法接 收最大长度报文段,而且也没有特殊原因需要发送立即发送报文段的情况。 218-221 由于不需要发送报文段, tcp_output返回。

689

第 26章 TCP 输 出计计

图26-12 tcp_output 函数:进入持续状态

举例 应用进程向某个空闲的连接写入 1 0 0字节,接着又写入 5 0字节。假定报文段大小为 5 1 2字 节。在第一次写入操作时,由于连接空闲,且

T C P正在清空发送缓存,图 2 6 - 8中的代码

(144~146行)被执行,发送一个报文段,携带 100字节的数据。 在第二次写入 5 0字节时,图 2 6 - 8中的代码被执行,但未发送报文段:待发送数据不能构 成一个最大长度报文段,连接未空闲 (假定T C P正在等待第一个报文段的 A C K ),默认时采用 N a g l e算法, t _ f o r c e未置位,并且假定正常情况下接收窗口大小为 4 0 9 6,5 0不满足大于等 于2 0 4 8的条件。这 5 0字节的数据将暂留在发送缓存中,也许会一直等到第一个报文段的 A C K 到达。由于对端可能延迟发送 ACK,最后50字节数据发送前的延迟有可能会更长。 这个例子说明采用 N a g l e算法时,如果待发送数据无法构成最大长度报文段,如何计算它 的延时。参见习题 26.12。 举例 本例说明 T C P的“确认所有其他报文段”属性。假定连接的报文段大小为 1 0 2 4字节,接 收缓存大小为4096字节。本地不发送数据,只接收数据。

690

计计TCP/IP详解 卷 2:实现

发向对端的对 S Y N的A C K报文中,通告接收窗口大小为 4 0 9 6,图2 6 - 1 3给出了两个变量 rcv_nxt和rcv_adv的初始值。接收缓存为空。 接收缓存大小 =4096

通告的最高序号加1

下一个接收序号

图26-13 接收方通告接收窗口大小为 4096

对端发送 1 ~ 1 0 2 4字节的报文段, t c p _ i n p u t处理报文段后,设置连接的延迟 A C K标志, 把1024字节的数据放入插口的接收缓存中 (图28-13)。更新rcv_nxt,如图26-14所示。 接收缓存大小 =3072

通告的最高序号加1

下一个接收序号

图26-14 图26-13所示的连接在收到 1~1024字节后的状态变迁

应用进程从插口的接收缓存中读取

1 0 2 4 字节的数据。从图 3 0 - 6 中可看到,生成的

P R U _ R C V D请求在处理过程中会调用 t c p _ o u t p u t,因为应用进程从接收缓存读取数据后, 可能需要发送窗口更新报文。当 t c p _ o u t p u t被调用时, r c v _ n x t和r c v _ a d v的值与图 2 6 1 4相同,唯一的区别是接收缓存的可用空间增加至 4 0 9 6,因为应用进程从中读取了第一个 1024字节的数据。把上述具体数值代入图 26-9中的算式,得到: adv = min(4096, 65535) - (4097 - 1025) = 1024

T C P _ M A X W I N等于6 5 5 3 5,我们假定接收窗口大小偏移量为 0。由于窗口的增加值小于两 个最大报文段长度 (2048),无需发送窗口更新报文。但由于延迟 ACK标志置位,如果 200ms定 时器超时,将发送 ACK。 当T C P收到下一个 1 0 2 5 ~ 2 0 4 8字节的报文段时, t c p _ i n p u t处理后,设定连接的延迟 A C K标志(这个标志已置位 ),把1 0 2 4字节的数据放入插口的接收缓存中,更新 r c v _ n x t,如 图26-15所示。 接收缓存大小= 3072

下一个接收序号

通告的最高序号加1

图26-15 图26-14所示的连接收到 1025~2048字节后的状态变迁

691

第 26章 TCP 输 出计计

应用进程读取 1 0 2 4 ~ 2 0 4 8字节的数据,调用 t c p _ o u t p u t。r c v _ n x t和r c v _ a d v的值与 图2 6 - 1 5相同,尽管应用进程读取 1 0 2 4字节的数据后,接收缓存的可用空间增加至 4 0 9 6。把上 述具体数值代入图 26-9的算式中,得到: adv = min(4096, 65535) - (4097 - 2049) = 2048

它等于两个报文段的长度,因此发送窗口更新报文,确认序号字段为 2 0 4 9,通告窗口字 段为4096,表示接收方希望接收序号 2049~6145的数据。我们在后面将看到,函数发送完窗口 更新报文后,将更新 rcv_adv的值为6145。 本例说明了如果数据接收时间少于 200ms延迟定时器时限,在有两个或两个以上报文段到 达,而且应用进程连续读取数据引起了接收窗口的不断变化时,将发送 A C K,确认所有接收 到的报文段。如果有数据到达,但应用进程没有从插口的接收缓存中读取数据,则“确认所 有其他报文段”的属性不会出现。相反,发送方只能看到多个延迟 A C K,每个A C K的窗口字 段均较前一个要小,直到接收缓存被填满,接收窗口缩小为 0。

26.4 TCP选项 T C P首部可以有任选项。由于 t c p _ o u t p u t的下一部分代码将试图确定哪些选项需要发 送,并据此组织将发送的报文段,下面我们将暂时离开函数代码,转而讨论这些选项。 图26-16列出了Net/3支持的选项格式。 选项表结束: 1字节

无操作: 1字节 最大报文段长 度(MSS)

最大报文段长度: 1字节

1字节

2字节

位移值

窗口缩放因子: 1字节

1字节

1字节

时间戳值

时间戳: 1字节

1字节

4字节

时间戳回显应答 4字节

图26-16 Net/3支持的TCP选项

所有选项以 1字节的k i n d字段开头,确定选项类型。头两个选项 (k i n d= 0或k i n d= 1 )只有1个 字节。其余3个选项都是多字节的,带有 len字段,位于kind字段之后,存储选项的长度。长度

692

计计TCP/IP详解 卷 2:实现

中包括kind字段和len字段。 多字节整数 —MSS和两个时间戳值—遵照网络字节序存储。 最后两个选项,窗口大小和时间戳,是新增的,因此许多系统都不支持。为了与以前的 系统兼容,应遵循下列原则: 1) TCP主动打开时 (发送不带 A C K的S Y N ),可以在初始 S Y N中同时发送这两个选项,或 发送其中的任何一个。如果全局变量 t c p _ d o _ r f c 1 3 2 3非零(默认值等于 1 ),则N e t / 3同时支 持这两个选项。此项功能由 tcp_newtcpcb函数实现。 2) 只有对端返回的 S Y N中包含同样的选项时,才可以使用这些选项。图 2 8 - 2 0和图2 9 - 2中 的代码实现此类处理。 3) TCP 被动打开时,如果收到的 S Y N中包含了这两个选项,而且也希望使用这些选项, 则发向对端的响应 (带有ACK的SYN)中必须包含它们,如图 26-23所示。 由于系统必须忽略它不了解的选项,因此新增的选项只有当连接双方都了解这一选项, 且同时希望支持它时才会被使用。 2 7 . 5节将讨论如何处理 M S S选项。下面两节将总结 N e t / 3处理两个新选项的做法:窗口大 小和时间戳。 还有其他可能的选项。 k i n d s等于4、5、6和7,称为选择性 A C K和回显选项,在 RFC 1072[Jacobson and Braden 1998]中定义。图 26-16中并未给出这些选项,因为回 显选项已被时间戳选项所代替,选择性 A C K选项目前还未形成正式标准,未在 R F C 1323中出现。此外,处理 TCP交易的T/TCP建议(RFC 1644[Braden 1994]和卷1的24.7 节)规定了其他 3个选项,kinds分别为11、12和13。

26.5 窗口大小选项 窗口大小选项,在 RFC 1323中定义,避免了TCP首部窗口大小字段只有16 bit的限制(图241 0 )。如果网络带宽较高或延时较长 (如,RT T较长),则需要较大的窗口,称为长肥管道 ( l o n g fat pipe)。卷1的第24.3节举例说明了现代网络需要较大的窗口,以获取最大的 TCP吞吐量。 图2 6 - 1 6中的偏移量最小值为 0 ( 无缩放 ),最大值为 1 4,即窗口最大可设定为 1 073 725 440 (65535×214)字节。Net/3内部实现时,利用 32 bit,而非16 bit整数表示窗口大小。 窗口大小选项只能出现在 S Y N中,因此,连接建立后,每个传输方向上的缩放因子是固 定不变的。 T C P控制块中的两个变量 s n d _ s c a l e和r c v _ s c a l e,分别规定了发送窗口和接收窗口 的偏移量。它们的默认值均为 0,无缩放。每次收到对端发送的窗口通告时, 16 bit的窗口大 小值被左移s n d _ s c a l e比特,得到真正的 32 bit的对端接收窗口大小 (图28-6)。每次准备向对 端发送窗口通告时,内部的 32 bit 窗口大小值被右移 r c v _ s c a l e比特,得到可填入 T C P首部 窗口字段的 16 bit值。 T C P发送 S Y N时,无论是主动打开或被动打开,都是根据本地插口接收缓存大小选取 rcv_scale值,填充窗口大小选项的偏移量字段。

26.6 时间戳选项 RFC 1323中还定义了时间戳选项。发送方在每个报文段中放入时间戳,接收方在 A C K中

693

第 26章 TCP 输 出计计

将时间戳发回。对于每个收到的 ACK,发送方根据返回的时间戳计算相应的 RTT样本值。 图26-17总结了时间戳选项所用到的变量。 12字节时间戳选项 接收报文段

传输报文段

时间戳

时间戳回 显应答

时间戳

时间戳回 显应答

图26-17 时间戳选项中用到的变量小结

全局变量t c p _ n o w是一个时间戳时钟。内核初启时它初始化为 0,之后每500 ms增加1(图 25-8)。为实现时间戳选项, TCP控制块中定义了下面 3个变量: • t s _ r e c e n t等于对端发送的最新的有效时间戳 (后面很快会介绍什么是“有效的”时间 戳)。 • ts_recent_age是最近一次 tcp_recent被更新时的 tcp_now值。 • l a s t _ a c k _ s e n t是最近一次发送报文段时确认字段 (t i _ a c k)的值 (图2 6 - 3 2 )。除非 ACK被延迟,正常情况下,它等于 rcv_nxt,下一个等待接收的序号。 tcp_input函数中的两个局部变量 ts_val和ts_ecr,保存时间戳选项的两个值: • ts_val是对端发送的数据中携带的时间戳。 • ts_ecr是由收到的报文段确认的本地发送报文段中携带的时间戳。 发送报文段中,时间戳选项的前 4个字节为 0 x 0 1 0 1 0 8 0 a,这是RFC 1323附录A中建议的 填充值。第一和第二字节都等于 1,为 N O P;第三字节为 k i n d字段,等于8;第四字节为 l e n字 段,等于 10。在选项之前添加两个 NOP后,紧接着的两个 32 bit时间戳和后续数据都可按照 32 b i t边界对齐。此外,图 2 6 - 1 7中还给出了接收到的时间戳选项,同样采用了推荐的 1 2字节格式 ( N e t / 3通常生成的格式 )。不过,处理接收选项的应用进程代码 (图2 8 - 1 0 ),并不要求必须使用 此格式。图 2 6 - 1 6中定义的 1 0字节格式中,没有两个前导的 N O P,对端接收处理代码一样工作 正常(参见习题28.4)。 从发送报文段至收到其 ACK间的RTT等于t c p _ n o w减t s _ e c r,单位为 500ms滴答,因为 这是Net/3时间戳的单位。 时间戳选项还可以支持 TCP执行PAWS:防止序号回绕(protection against wrapped sequence number)。28.7节将详细讨论这一算法。 PAWS中会用到ts_recent_age变量。 t c p _ o u t p u t向输出报文段中填充时间戳选项时,复制 t c p _ n o w到时间戳字段,复制

694

计计TCP/IP详解 卷 2:实现

t s _ r e c e n t到时间戳回显字段 (图26-24)。如果连接采用了时间戳选项,则必须为所有输出报 文段执行这一操作,除非 RST标志置位。 26.6.1 哪个时间戳需要回显, RFC 1323算法 T C P通过时间戳的有效性测试决定是否更新 t s _ r e c e n t,因为这个变量会被填充到时间 戳回显字段中,也就决定了对端发送的哪个时间戳需要回显。 RFC 1323规定了下面的测试条 件: ti_seq t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) tcp_xmit_timer(tp, tp->t_rtt);

25. 设定重传定时器 430-440

如果重传定时器还未启动,并且报文段中有数据,则重传定时器时限设定为

t_rxtcur。前面已经介绍过,通过测量 RTT样本值,tcp_xmit_timer将更新t_rxtcur。 但如果 s n d _ n x t等于s n d _ u n a(此时s n d _ n x t中已加入了 l e n),则是一个纯 ACK报文段,而 只有在发送数据报文段时才需要启动重传定时器。 441-444

如果持续定时器已启动,则关闭它。对于给定连接,可以在任何时候启动重传定

时器或者持续定时器,但两者不允许同时存在。 26. 持续状态 446-447 由于t_force非零,而且持续定时器已设定,可知连接处于持续状态 (与图26-31起 始处的if语句配对的else语句)。需要时,更新 snd_max。处于持续状态时,len应等于1。 t c p _ o u t p u t 的最后一部分,在图 2 6 - 3 2 中给出,输出报文段准备完毕,调用 ip_output发送数据报。

图26-32 tcp_output 函数:调用 ip_output 发送报文段

706

计计TCP/IP详解 卷 2:实现

图26-32 (续)

27. 为插口调试添加路由记录 448-452

如果选用了 SO_DEBUG选项,tcp_trace会在TCP的循环路由缓存中添加一条记

录,27.10节将详细讨论这个函数。 28. 设置IP长度、TTL和TOS 453-462

IP首部的3个字段必须由传输层设置: IP长度、TTL和TOS,图23-19底部用星号强

调了这3个特殊字段。 注意,注释的内容为“ X X X”,这是因为尽管对于给定连接, T T L和TO S通常是 常量,可以保存在首部模板中,无需每次发送报文段时都明确赋值。只有当 TCP检验 和计算完毕后,这两个字段才能填入 IP首部,因此只能这样实现。 29. 向IP传递数据报 463-464

ip_output发送携带TCP报文段的数据报。TCP的插口选项和 SO_DONTROUTE逻

辑与,从而能向 IP层传送的插口选项只有一个: SO_DONTROUTE。尽管ip_output还测试另 一个选项 SO_BROADCAST,但即使设定了它,与 SO_DONTROUTE的逻辑与也会将其关闭。也 就是说,应用进程不允许向一个广播地址发送 connect,即使它设定了 SO_BROADCAST选项。 467-470

如果接口队列已满,或者 I P请求分配 m b u f失败,则返回差错码 E N O B U F S 。

t c p _ q u e n c h把拥塞窗口设定为只能容纳一个最大报文段长度,强迫连接执行慢起动。注意, 出现上述情况时, T C P 仍旧返回 0 ( O K ) ,而非错误,即使数据报实际已丢弃。这与 u d p _ o u t p u t(图2 3 - 2 0 )不同,后者返回一个错误。 T C P将通过超时重传该数据报 (数据报文 段),希望那时在接口输出队列中会有可用空间或者能申请到更多的 m b u f。如果T C P报文段不 包含数据,对端由于未收到 ACK而引发超时时,将重传由丢失的 ACK所确认的数据。 471-475

如果连接已收到一个 S Y N,但找不到至目的地的路由,则记录连接上出现了一个

软错误。 当t c p _ o u t p u t被t c p _ u s r r e q调用,做为应用进程系统调用的一部分时 (参见第 3 0章, P R U _ C O N N E C T、P R U _ S E N D、P R U _ S E N D O O B和P R U _ S H U T D O W N请求),应用进程将接收 t c p _ o u t p u t的返回值。其他调用 t c p _ o u t p u t的函数,如 t c p _ i n p u t、快超时函数和慢 超时函数,忽略其返回值 (因为这些函数不向应用进程返回差错码 )。

707

第 26章 TCP 输 出计计

30. 更新rcv_adv和last_ack_sent 479-486 如果报文段中通告的最高序号 (rcv_nxt加上win)大于rcv_adv,则保存新的值。 回想图26-9中利用r c v _ a d v确定最后一个报文段发送后新增的可用空间,以及图 26-29中利用 它确定TCP没有缩小窗口。 4 8 7 报文段确认字段的值保存在 l a s t _ a c k _ s e n t中,t c p _ i n p u t利用它处理时间戳选项 (图26-6)。 488 由于所有延迟的 ACK都已被发送,TF_ACKNOW和TF_DELACK标志被清除。 31. 是否还有数据需要发送 489-490 如果sendalot标志置位,控制跳回到 again处(图26-1)。如果发送缓存中的数据 超过一个最大长度报文段的容量 (图2 6 - 3 ),或者由于加入 T C P选项,降低了最大长度报文段的 数据容量,无法在一个报文段中将缓存中的数据发送完毕时,控制将折回。

26.8 tcp_template函数 创建插口时,将调用 t c p _ n e w t c p c b(见前一章 )为T C P控制块分配内存,并完成部分初 始化。当在插口上发送或接收第一个报文段时 (主动打开, P R U _ C O N N E C T请求,或者在监听 的插口上收到了一个 SYN),t c p _ t e m p l a t e为连接的 IP和TCP的首部创建一个模坂,从而减 少了报文段发送时 tcp_output的工作量。 图26-33给出了tcp_template函数。

图26-33 tcp_template

函数:创建 IP和TCP首部的模板

708

计计TCP/IP详解 卷 2:实现

1. 分配mbuf 59-72

I P和T C P的首部模板在一个 m b u f中组建,指向这个 m b u f的指针存储在 T C P控制块的

t _ t e m p l a t e 成员变量中。由于这个函数可在软件中断级被

t c p _ i n p u t 调用,

M_DONTWAIT标志置位。 2. 初始化首部字段 73-88

除下列字段外, IP和TCP首部的其他字段均置为 0:t i _ p r等于TCP的IP协议值(6);

t i _ l e n等于20,TCP首部的默认值; t i _ o f f等于5,TCP首部长度,以32 bit为单位;此外, 还要从Internet PCB中把源IP地址、目的 IP地址和TCP端口号复制到TCP首部模板中。 3. 用于TCP检验和计算的伪首部 73-88

由于预先对 IP和TCP首部中许多字段做了初始化,简化了 TCP检验和的计算,方法与

2 3 . 6节中讨论过的 U D P首部检验和的计算方式相同。参考图 2 3 - 1 9中的u d p i p h d r结构,请读 者自己思考为什么 tcp_template将ti_next和ti_prev等字段初始化为 0。

26.9 tcp_respond函数 函数t c p _ r e s p o n d尽管也调用 i p _ o u t p u t发送I P数据报,但用途不同。主要在下面两 种情况下调用它: 1) tcp_input调用它生成 RST报文段,携带或不携带 ACK; 2) tcp_timers调用它发送保活探测报文。 在这两种特珠情况下, TCP调用t c p _ r e s p o n d,取代t c p _ o u t p u t中复杂的逻辑。但请 注意,下一章中讨论的 t c p _ d r o p函数调用 t c p _ o u t p u t来生成 R S T报文段。并非所有的 RST报文段都由 tcp_respond生成。 图26-34给出了tcp_respond的前半部分。

图26-34 tcp_respond 函数:前半部分

709

第 26章 TCP 输 出计计

图26-34 (续)

104-110 图26-35列出了3种不同情况下调用 tcp_respond时其参数的变化。 参 tp

ti

m

生成不带ACK的RST

tp

ti

m

生成带ACK的RST

tp

ti

m

生成保活探测

tp

t_template

NULL

数 ack 0 ti_seq + ti_len rcv_nxt

seq

flags

ti_ack

TH_RST TH_RST| TH_ACK

0 snd_una

0

图26-35 tcp_respond 的参数

t p是指向 T C P控制块的指针 (可能为空 );t i是指向 I P和T C P首部模板的指针; m是指向 m b u f的指针,其中的报文段引发 R S T。最后3个参数是确认字段、序号字段和待生成报文段的 标志字段。 113-118

如果t c p _ i n p u t收到一个不属于任何连接的报文段,则有可能生成 R S T。例如,

收到的报文段中没有指明任何现存连接 (如,SYN指明的端口上没有正在监听的服务器 )。这种 情况下, t p为空,使用w i n和r o的初始值。如果 t p不空,则通告窗口大小将等于接收缓存中 的可用空间,指向缓存路由的指针保存在 ro中,在后面调用 tcp_input时会用到。 1. 保活定时器超时后发送保活探测 119-127

参数m是指向接收报文段的 m b u f链表的指针。但保活探测报文只有当保活定时器

超时时才会被发送,收到的 T C P报文段不可能引发此项操作,因此 m为空,由 m _ g e t h d r分配 保存I P和T C P首部的 m b u f。T C P数据长度 t l e n,设为 0,因为保活探测报文不包含任何用户 数据。 有些基于 4 . 2 B S D的较老的系统不响应保活探测报文,除非它携带数据。通过配 置,在编译内核时定义 T C P _ C O M P A T _ 4 2,N e t / 3能够在保活探测报文中携带一个字 节的无效数据,以引出这些系统的响应。这种情况下, t l e n设为1,而非0。无效字 节不会造成不良后果,因为它不是对方正等待 (而是一个对方已接收并确认过 )的字节, 对端将丢弃它。 利用赋值语句把 t i指向的T C P首部模板结构复制到 m b u f的数据部分,之后指针 t i将被重

710

计计TCP/IP详解 卷 2:实现

新设定,指向mbuf中的首部模板。 2. 发送RST报文段 128-138

接收到的报文段有可能会引发 t c p _ i n p u t发送RST。发送RST时,保存输入报文

段的mbuf可以重用。因为 t c p _ r e s p o n d生成的报文段中只包含 IP首部和TCP首部,因此,除 第一个 m b u f之外(数据分组首部 ),m _ f r e e将释放链表中其余的所有 m b u f。另外, I P首部和 TCP首部中的源 IP地址和目的 IP地址及端口号应互换。 图26-36给出了tcp_respond的后半部分。

图26-36 tcp_respond 函数:后半部分

139-157

为 计 算 T C P 检 验 和 , I P和 T C P 首 部 字 段 必 须 被 初 始 化 。 这 些 语 句 与

t c p _ t e m p l a t e初始化t _ t e m p l a t e字段的方式类似。序号和确认字段由调用者提供,最后 调用ip_output发送数据分组。

26.10 小结 本章讨论了生成大多数 T C P报文段的通用函数 (t c p _ o u t p u t)及生成R S T报文段和保活探 测的特殊函数(tcp_respond)。 T C P是否发送报文段取决于许多因素:报文段中的标志、对端通告的窗口大小、待发送 的数据量以及连接上是否存在未确认的数据等等。因此, t c p _ o u t p u t中的逻辑决定了是否 发送报文段 (函数的前半部分 ),如果需要发送,如何填充 T C P首部的字段 (函数后半部分 )。报 文段发送之后,还需要更新 TCP控制块中的相应变量。 t c p _ o u t p u t一次只生成一个报文段,但它在结尾处会测试是否还有剩余数据等待发送, 如果有,控制将折回,并试图发送下一个报文段。这样的循环会一直持续到数据全部发送完 毕,或者有其他停止传输的条件出现 (接收方的窗口通告 )。

711

第 26章 TCP 输 出计计

T C P报文段中可以携带选项。 N e t / 3支持的选项规定了最大报文段长度、窗口大小缩放因 子和一对时间戳。头两个选项只能出现在 SYN报文段中,而时间戳选项 (如果连接双方都支持 ) 能够出现在所有报文段中。因为窗口大小和时间戳是新增的选项,如果主动打开的一端希望 使用这些选项,则必须在自己发送的 S Y N中添加它们,并且只有在对端发回的 S Y N也包含了 同样的选项时才能使用。

习题 26.1 图2 6 - 1中,如果发送数据过程中出现停顿, T C P将返回慢启动状态,而空闲时间被 设定为从最后一次收到报文段到现在的时间。为什么 T C P不将空闲时间设定为从最 后一次发送报文到现在的时间? 26.2 图2 6 - 6中,我们说如果 F I N已发送,但还未被确认且没有重传,此时 l e n小于0。如 果FIN已重传,情况会怎样? 26.3 N e t / 3 总在主动打开时发送窗口大小和时间戳选项。为什么需要全局变量 t c p _ d o _ r f c 1 3 2?3 26.4 图2 5 - 2 8中的例子未使用时间戳, RT T估算值被更新了 8次。如果使用了时间戳, RTT估算值会被更新几次? 26.5 图2 6 - 2 3中,调用 b c o p y把收到的 M S S存储在变量 m s s中。为什么不对指向 o p t[ 2 ] 的指针做强制转换,变为不带符号的短整型指针,并利用赋值语句完成这一操作? 26.6 在图 2 6 - 2 9后面,我们讨论了代码的一个错误,可能会导致发送一个错误的紧急数 据偏移量。提出你的解决方案。 (提示:一个TCP报文中能够发送的最大数据量是多 少?) 26.7 图2 6 - 3 2中,我们提到不会向应用进程返回差错代码 E N O B U F S,因为( 1 )如果丢弃的 是数据报文,重传定时器超时后数据将被重传; ( 2 )如果丢弃的是纯 A C K报文,对 端收不到ACK时会重传对应的数据报文。如果丢弃的是 RST报文,情况会怎样? 26.8 解释卷1图20-3中PSH标志的设定。 26.9 为什么图26-36使用ip_defttl作为TTL的值,而图 26-32却使用PCB? 26.10 如果应用进程规定的 I P选项是用于 T C P连接的,图 2 6 - 2 5中分配的 m b u f会出现什么 情况?实现一个更好的方案。 26.11 t c p _ o u t p ut函数很长 (包括注释约 5 0 0行),看上去效率不高,其中许多代码用于 处理特殊情况。假定函数只用于处理准备好的最大长度报文,且没有特殊情况: 无I P选项,无特殊标志如 S Y N、F I N或U R G。实际执行的约有多少行 C代码?报文 递交给ip_output之前会调用多少函数? 26.12 2 6 . 3节结尾的例子中,应用程序向连接写入 1 0 0字节,接着又写入 5 0字节。如果应 用程序为两个缓存各调用一次 w r i t e v,而不是调用 w r i t e两次,有何不同?如 果两个缓存大小分别为 200和300,而不是100和50,调用writev时又有何不同? 26.13 在时间戳选项中发送的时间戳来自于全局变量 t c p _ n o w,它每 5 0 0 m s递增一次。 修改TCP代码,使用更精确的时间戳值。

第27章 TCP的函数 27.1 引言 本章介绍多个TCP函数,它们为下两章进一步讨论 TCP的输入打下了基础: • t c p _ d r a i n是协议的资源耗尽处理函数,当内核的 m b u f用完时被调用。实际上,不做 任何处理。 • tcp_drop发送RST来丢弃连接。 • t c p _ c l o s e执行正常的 T C P连接关闭操作:发送 F I N,并等待协议要求的 4次报文交换 以终止连接。卷 1的18.2节讨论了连接关闭时双方需要交换的 4个报文。 • tcp_mss处理收到的 MSS选项,并在 TCP发送自己的 MSS选项时计算应填入的 MSS值。 • t c p _ c t l i n p u t 在收到对应于某个 T C P报文段的 I C M P 差错时被调用,它接着调用 tcp_notify处理ICMP差错。tcp_quench专门负责处理ICMP的源站抑制差错。 • T C P _ R E A S S宏和t c p _ r e a s s函数管理连接重组队列中的报文段。重组队列处理收到的 乱序报文段,某些报文段还可能互相重复。 • t c p _ t r a c e向内核的 T C P调试循环缓存中添加记录 (插口选项 S O _ D E B U G)。运行 t r p t (8)程序可以打印缓存内容。

27.2 tcp_drain函数 t c p _ d r a i n是所有T C P函数中最简单的。它是协议的 p r _ d r a i n函数,在内核的 m b u f用 完时,由 m _ r e c l a i m调用。图 1 0 - 3 2中,i p _ d r a i n丢弃其重组队列中的所有数据报分片, 而U D P则不定义自己的资源耗尽处理函数。尽管 T C P也占用 m b u f—位于接收窗口内的乱序 报文段 — 但 N e t / 3 实现的 T C P 并不丢弃这些 m b u f ,即使内核的 m b u f 已用完。相反, tcp_drain不做任何处理,假定收到的 (但次序差错 )的TCP报文段比IP分片重要。

27.3 tcp_drop函数 t c p _ d r o p在整个系统中多次被调用,发送 RST报文段以丢弃连接,并向应用进程返回差 错。它与关闭连接 (t c p _ d i s c o n n e c t函数)不同,后者向对端发送 F I N,并遵守 T C P状态变 迁图所规定的连接终止步骤。 图27-1列出了调用 tcp_drop的7种情况和相应的 errno参数。 图27-2给出了tcp_drop函数。 202-213 如果TCP收到了一个SYN,连接被同步,则必须向对端发送RST。tcp_drop把状态 设为CLOSED,并调用tcp_output。从图24-16可知,CLOSED状态的tcp_outflags数组中 包含RST标志。 214-216

如果e r r n o等于E T I M E D O U T,且连接上曾收到过软差错 (如E H O S T U N R E A C H),

713

第 27章 TCP的函数计计

软差错代码将取代内容不确定的 ETIMEDOUT,做为返回的插口差错。 217 tcp_close结束插口关闭操作。 函





errno

tcp_input

ENOBUFS

tcp_input tcp_input tcp_timers tcp_timers

ECONNREFUSED ECONNRESET ETIMEDOUT ETIMEDOUT

tcp_usrreq tcp_usrreq

ECONNABORTED 0



监听服务器收到 SYN,但内核无法为 t _ t e m p l a t e分配所需的 mbuf 收到的RST是对本地发送的SYN的响应 在现存连接上收到了 RST 重传定时器连续超时 13次,仍未收到对端的 ACK(图25-25) 连接建立定时器超时 (图25-16),或者保活定时器超时,且连续 9次发送窗口探测报文段,对方均无响应 P R U _ A B O R T请求 关闭插口,设定 S O _ L I N G E R选项,且拖延时间为 0

图27-1 调用tcp_drop 函数和errno 参数

图27-2 tcp_drop 函数

27.4 tcp_close函数 通常情况下,如果应用进程被动关闭,且在 LAST_ACK状态时收到了ACK,tcp_input 将调用 t c p _ c l o s e关闭连接;或者当 2 M S L定时器超时,插口从 T I M E _ WA I T状态变迁到 CLOSED状态时, t c p _ t i m e r s也会调用 t c p _ c l o s e。它也可以在其他状态被调用,一种可 能是发生了差错,如上一小节讨论过的情况。 t c p _ c l o s e释放连接占用的内存 (IP和TCP首部 模板、TCP控制块、Internet PCB和保存在连接重组队列中的所有乱序报文段 ),并更新路由特 性。 我们分3部分讲解这个函数,前两部分讨论路由特性,最后一部分介绍资源释放。 27.4.1 路由特性 r t _ m e t r i c s结构 (图1 8 - 2 6 )中保存了 9个变量,有 6个用于 T C P。其中 8个变量可通过

714

计计TCP/IP详解 卷 2:实现

r o u t e ( 8 )命令读写 (第9个,r m x _ p k s e n t未使用 ):图 2 7 - 3列出了这些变量。此外,运行 r o u t e命令时,加入 - l o c k选项,可以设置 r m x _ l o c k s成员变量(图20-13)中对应的 R T V _xxx 比特,告诉内核不要更新对应的路由参数。 关闭TCP 插口时,如果下列条件满足:连接上传输的数据量足够生成有效的统计值,并 且变量未被锁定, tcp_close将更新3个路由参数 —已平滑的RTT估计器、已平滑的 RTT平 均偏差估计器和慢起动门限。 rt_metric s成员 rmx_expire rmx_hopcount rmx_mtu rmx_recvpipe rmx_rtt rmx_rttvar rmx_sendpipe rmx_ssthresh

t c p _ c l o s e是否 保存该成员

• • •

t c p _ m s s是否 使用该成员

r o u t e(8)附加参数

• • • • • •

-expire -hopcount -mtu -recvpipe -rtt -rttvar -sendpipe -ssthresh

图27-3 TCP用到的rt_metrics 结构中的变量

图27-4给出了tcp_close的第一部分。 1. 判断是否发送了足够的数据量 234-248

默认的发送缓存大小为 8 1 9 2字节(s b _ h i w a t),因此首先比较初始发送序号和连

接上已发送的最大序号,测试是否已传输了 131 072字节( 1 6个完整的缓存 )的数据。此外,插 口还必须有一条非默认路由的缓存路由 (参见习题19.2)。 请注意,如果传输的数据量在 N×232(N> 1 )和N×2 32+ 1 3 1 0 7 2 (N> 1 )之间,则因为 序号可能回绕,比较时也许会出现问题,尽管可能性不大。但目前很少有连接会传 输4 G的数据。 尽管I n t e r n e t上存在大量的默认路由,缓存路由对于维护有效的路由表还是很有 用的。如果主机长期与另外某个主机 (或网络)交换数据,即使默认路由可用,也应运 行r o u t e命令向路由表中添加源站选路和目的选路的路由,从而在整条连接上维护 有效的路由信息 (参见习题19.2)。这些信息在系统重启时丢失。 250 管理员可以锁定图 27-3中的变量,防止内核修改它们。因此,代码在更新这些变量之前, 必须先检查其锁定状态。 2. 更新RTT 251-264 t_srtt的单位为8个滴答(图25-19),而rmx_rtt的单位为微秒。因此,首先必须 实现单位换算, t _ s r t t乘以1 000 000(R T M _ R T T U N I T),除以2 (滴答/秒)再乘以8,得到RT T 的最新值。如果 rmx_rtt值已存在,它被更新为最新值与原有值和的一半,即两者的平均值。 如果不存在,最新值将直接赋给 rmx_rtt变量。 3. 更新平均偏差 265-273

更新平均偏差的算法与更新 RTT的类似,也需要把单位为 4个滴答的t_rttvar换

算为以微秒为单位。

715

第 27章 TCP的函数计计

图27-4 tcp_close 函数:更新 RTT和平均偏差

图27-5给出了tcp_close的下一部分代码,更新路由的慢起动门限。 274-283

满足下列条件时,慢起动门限被更新: (1)它被更新过 (rmx_ssthresh非零);(2)

管理员规定了 r m x _ s e n d p i p e,而s n d _ s s t h r e s h的最新值小于 r m x _ s e n d p i p e的一半。 如同代码注释中指出的, T C P不会更新 r m x _ s s t h r e s h值,除非因为数据分组丢失而不得不

716

计计TCP/IP详解 卷 2:实现

这样做。从这个角度出发,除非十分必要, TCP不会修改门限值。

图27-5 tcp_close 函数:更新慢起动门限

284-290

变量s n d _ s s t h r e s h以字节为单位,除以 MSS(t _ m a x s e g)得到报文段数,加上

1 / 2t _ m a x s e g是为了保证总报文段容量必定大于 s n d _ s s t h r e s h字节。报文段数的下限为 2 个报文段。 291-297

M S S加上 I P 和T C P首部大小 ( 4 0 ) ,再乘以报文段数,利用得到的结果来更新

rmx_ssthresh,采用的算法与图 27-4中的相同(新值的1/2加上原有值的1/2)。 27.4.2 资源释放 图27-6给出了tcp_close的最后一部分,释放插口占用的内存资源。

图27-6 tcp_close 函数:释放连接资源

717

第 27章 TCP的函数计计

图27-6 (续)

1. 释放重组队列占用的 mbuf 299-306

如果连接重组队列中还有报文段,则丢弃它们。重组队列用于存放收到位于接收

窗口内、但次序差错的报文段。在等待接收的正常序列报文段到达之前,它们会一直保存在 重组队列中;之后,报文段被重组并递交给应用程序。 27.9节会详细讨论这一过程。 2. 释放首部模板和 TCP控制块 307-309

调用 m _ f r e e 释放 I P 和 T C P首部模板,调用 f r e e 释放 T C P控制块,调用

sodisconnected发送PRU_DISCONNECT请求,标记插口已断开连接。 3. 释放PCB 310-318

如果插口的Internet PCB保存在TCP的高速缓存中,则把 TCP的PCB链表表头赋给

tcp_last_inpcb,以清空缓存。接着调用 in_pcbdetach释放PCB占用的内存。

27.5 tcp_mss函数 tcp_mss被两个函数调用: 1) tcp_output,准备发送 SYN时调用,以添加 MSS选项; 2) tcp_input,收到的SYN报文段中包含MSS选项时调用; tcp_mss函数检查到达目的地的缓存路由,计算用于该连接的 MSS。 图2 7 - 7给出了 t c p _ m s s第一部分的代码,如果 P C B中没有到达目的地的路由,则设法得 到所需的路由。

图27-7 tcp_mss 函数:如果 PCB中没有路由,则设法得到所需路由

718

计计TCP/IP详解 卷 2:实现

图27-7 (续)

1. 如果需要,就获取路由 1391-1417 如果插口没有高速缓存路由,则调用 rtalloc得到一条。与外出路由相关的接 口指针存储在 i f p中。外出接口是非常重要的,因为其 M T U会影响 T C P通告的 M S S。如果无 法得到所需路由,函数就立即返回默认值 512 ( tcp_mssdflt)。 图27-8给出了t c p _ m s s的下一部分代码,判断得到的路由是否有相应的参数表。如果有, 则变量t_rttmin、t_srtt和t_rttvar将初始化为参数表中的对应值。

图27-8 tcp_mss 函数:判断路由是否有相应的 RTT参数表

2. 初始化已平滑的 RTT估计器 1420-1432

如果连接上不存在 RT T样本值 (t _ s r t t = 0),并且r m x _ r t t非零,则将后者赋

719

第 27章 TCP的函数计计

给已平滑的 RT T估计器 t _ s r t t。如果路由参数表锁定标志的 R T V _ R T T比特置位,表明连接 的最小 RT T (t _ r t t m i n )也应初始化为 r m x _ r t t 。前面介绍过, t c p _ n e w t c p c b 把 t_rttmin初始化为2个滴答。 rmx_rtt(以微秒为单位)转换为t_srtt(以8个滴答为单位),这是图27-4的反变换。注意, t_rttmin等于t_srtt的1/8,因为前者没有除以缩放因子 TCP_RTT_SCALE。 3. 初始化已平滑的 RTT平均偏差估计器 1433-1439 如果存储的 rmx_rttvar(以微秒为单位)值非零,将其转换为 t_rttvar (以4 个滴答为单位 )。但如果为零,则 t _ r t t v a r等于t _ r t t,即偏差等于均值。已平滑的 RT T平 均偏差估计器默认设置为 ±1 RT T。由于t _ r t t v a r的单位为 4个滴答,而 t _ r t t的单位为 8个 滴答,t_srtt值也必须做相应转换。 4. 计算初始RTO 1440-1442 计算当前的 RTO,并存储在 t_rxtcur中,采用下列算式更新: RTO=s rt t+2×rttvar 计算第一个 RTO 时,乘数取 2,而非 4,上式与图 2 5 - 2 1中用到的算式相同。将缩放关系代 入,得到: t_srtt t_rttvar RTO = +2× = 8 4

t_srtt + t_rttvar 4 2

即为TCPT_RANGESET的第二个参数。 图27-9给出了tcp_mss的下一部分,计算 MSS。

图27-9 tcp_mss 函数:计算 mss

5. 从路由表中的MTU得到MSS 1444-1450

如果路由表中的 MTU有值,则将其赋给 m s s。如果没有,则 m s s初始值等于外

出接口的MTU值减去40(IP和TCP首部默认值 )。对于以太网, MSS初始值应为 1460。 6. 减小MSS,令其等于 MCLBYTES的倍数 1451-1457

如 果 m s s 大 于 M C L B Y T E S , 则 减 小 m s s的 值 , 令 其 等 于 最 接 近 的

720

计计TCP/IP详解 卷 2:实现

M C L B Y T E S( m b u f簇大小)的整数倍。如果 M C L B Y T E S值(通常等于 1 0 2 4或2 0 4 8 )与M C L B Y T E S值 减1逻辑与后等于 0,说明M C L B Y T E S等于2的倍数。例如, 1 0 2 4 (0 x 4 0 0)逻辑与 1 0 2 3 (0 x 3 f f) 等于0。 代码通过清零 m s s的若干低位比特,将 m s s减小到最接近的 M C L B Y T E S的倍数:如果 m b u f簇大小为 1 0 2 4,m s s与1 0 2 3的二进制补码 (0 x f f f f f c 0 0)逻辑与,低位的 10 bit被清零。 对于以太网, m s s 将从 1 4 6 0 减至 1 0 2 4 。如果 m b u f簇大小为 2 0 4 8 ,与 2 0 4 7 的二进制补码 (0 x f f f f 8 0 0 0)逻辑与,低位的 11 bit 被清零。对于令牌环, M T U大小为 4 4 6 4,上述运算将 m s s从4 4 2 4减为4 0 9 6。如果 M C L B Y T E S不是2的倍数,代码用 m s s整数除以 M C L B Y T E S后,再 乘上MCLBYTES,从而将mss减小到最接近的 MCLBYTES的倍数。 7. 判断目的地是本地地址还是远端地址 如 果 目 的 I P不 是 本 地 地 址 (i n _ l o c a l a d d r 返 回 零 ), 且 m s s 大 于

1458-1459

512(tcp_mssdflt),则将mss设为512。 I P地址是否为本地地址取决于全局变量 s u b n e t s a r e l o c a l,内核编译时把符 号变量S U B N E T S A R E L O C A L的值赋给它。默认值为 1,意味着如果给定 I P地址与主机 任一接口的 I P地址具有相同的网络 I D,则被认为是一个本地地址。如果为 0,则给定 IP地址必须与主机任一接口的 IP地址具有相同的网络号和子网号,才会被认为是一个 本地地址。 对于非本地地址,将 M S S最小化是为了避免 I P数据报经广域网时被分片。绝大多 数WA N链路的 M T U只有1 0 0 6,这是从 ARPANET遗留下来的一个问题。在卷 1的11 . 7 节中讨论过,现代的多数 WA N支持1 5 0 0,甚至更大的 M T U。感兴趣的读者还可阅读 卷1的2 4 . 2节中讨论的路由 M T U发现特性 (RFC 11 9 1,[Mogul and Deering 1990]) 。 Net/3不支持路由 MTU发现。 图27-10给出了tcp_mss最后一部分的代码。 8. 对端的MSS用作上限 1461-1472

如果tcp_mss被tcp_input调用,参数 offer非零,等于对端通告的 mss值。

如果m s s大于对端通告的值,则将 o f f e r赋给它。例如,如果函数计算得到的 m s s等于1 0 2 4, 但对端通告的值只有 5 1 2,则 m s s必须被设定为 5 1 2。相反,如果 m s s等于5 3 6 (即输出 M T U等 于5 7 6 ),而对端通告的值为 1 4 6 0,T C P仍旧使用 5 3 6。只要不超过对端通告的值, m s s可以取 小于它的任何一个值。如果 t c p _ m s s被t c p _ o u t p u t调用,o f f e r等于0,用于发送 M S S选 项。注意,尽管 mss的上限可变,其下限固定为 32。 1473-1483

如果mss小于tcp_newtcpcb中设定的默认值t_maxseg(512),或者如果TCP正

在处理收到的MSS选项(offer非零),则需执行下列步骤。首先,如果路由的 rmx_sendpipe 有值,则采用它做为发送缓存的高端 (high-water)标志(图16-4)。如果缓存小于 m s s,则使用较 小的值。除非是应用程序有意把发送缓存定得很小,或者管理员将 rmx_sendpipe定得很小, 这种情况一般不会发生,因为发送缓存的上限默认值为 8192,大于绝大多数的 mss。 9. 增加缓存大小,令其等于最近的 MSS整数倍 1484-1489

增加缓存大小,令其等于最近的 m s s整数倍,上限为 s b _ m a x( N e t / 3中定义为

262 144 ,即2 5 6×1 0 2 4 )。插口发送缓存的上限设定为 s b r e s e r v e。例如,上限默认值等于

721

第 27章 TCP的函数计计

8192,但对于以太网上的本地 TCP传输,其 mbuf簇大小为2048(假定m s s等于1460),代码把上 限值增加到 8760(等于6×1460)。但对于非本地的连接, mss等于512,上限值保持8192不变。

图27-10 tcp_mss 函数:结束处理

1490

由于t_maxseg已小于默认值(512),或者由于收到了对端发送的 MSS选项,所以应更

722

计计TCP/IP详解 卷 2:实现

新它。 1491-1499 对接收缓存的处理与发送缓存相同。 10. 初始化拥塞窗口和慢起动门限 1500-1509

拥塞窗口的值, s n d _ c w n d,等于一个最大报文段长度。如果路由表中的

r m x _ s s t h r e s h非零,慢起动门限 (s n d _ s s t h r e s h)初始化为该值,但应保证其下限为两个 最大报文段长度。 1510

函数最后返回mss。tcp_input忽略这一返回值 (图28-10,因为它已收到对端的 MSS

选项),但图26-23中,tcp_output将它用作MSS通告。 举例 下面通过一个连接建立的实例说明 t c p _ m s s的操作过程。连接建立过程中,它会被调用 两次:发送 SYN时和收到对端带有 MSS选项的SYN时。 1) 创建插口, tcp_newtcpcb初始化t_maxseg为512。 2) 应用进程调用 c o n n e c t。为了在 S Y N报文段中加入 M S S选项, t c p _ o u t p u t调用 t c p _ m s s,参数o f f e r等于零。假定目的 I P为本地以太网地址, m b u f簇大小为 2 0 4 8,执行图 2 7 - 9中的代码后, m s s等于1 4 6 0。由于o f f e r等于零,图 2 7 - 1 0中的代码不修改 m s s值,函数 返回 1 4 6 0 。因为 1 4 6 0 大于默认值 ( 5 1 2 ) 而且未收到对端的 M S S 选项,缓存大小不变。 tcp_output发送MSS选项,通告 MSS大小为1460。 3) 对端发送响应 S Y N,通告 m s s大小为1 0 2 4。t c p _ i n p u t调用t c p _ m s s,参数 o f f e r 等于1 0 2 4。图2 7 - 9的代码逻辑仍旧设定 m s s为1 4 6 0,但在图 2 7 - 1 0起始处的 m i n语句将 m s s减 小为1 0 2 4。因为 o f f e r非零,缓存大小增加至最近的 1 0 2 4的整数倍 (等于8 1 9 2 )。t _ m a x s e g 更新为1024。 初看上去,t c p _ m s s的逻辑存在问题: TCP向对端通告 mss大小为1460,之后从 对端收到的 m s s只有1 0 2 4。尽管 T C P只能发送 1 0 2 4字节的报文段,对端却能够发送 1 4 6 0字节的报文段。读者可能会认为发送缓存应等于 1 0 2 4的倍数,而接收缓存则应 等于1 4 6 0的倍数。但图 2 7 - 1 0中的代码却将两个缓存大小都设为对端通告的 m s s的倍 数。这是因为尽管 T C P通告m s s为1 4 6 0,但对端通告的 m s s仅为1 0 2 4,对端有可能不 会发送1460字节的报文段,而将发送报文段限制为 1024字节。

27.6 tcp_ctlinput函数 回想图22-32中,t c p _ c t l i n p u t处理5种类型的ICMP差错:目的地不可达、数据报参数 错、源站抑制、数据报超时和重定向。所有重定向差错会上交给相应的 TCP或UDP进行处理。 对于其他 4种差错,仅当它们是被 T C P报文段引发的,才会调用 t c p _ c t l i n p u t进行处 理。 图27-11给出了tcp_ctlinput函数,它与图23-30的udp_ctlinput函数类似。 365-366

在逻辑上, t c p _ c t l i n p u t与u d p _ c t l i n p u t的唯一区别是如何处理 ICMP源站

抑制差错。因为 i n e t c t l e r r m a p等于0,U D P忽略源站抑制差错。 T C P检查源站抑制差错, 并把notify函数的默认值tcp_notify改为tcp_quench。

723

第 27章 TCP的函数计计

图27-11 tcp_ctlinput

函数

27.7 tcp_notify函数 t c p _ n o t i f y被t c p _ c t l i n p u t调用,处理目的地不可达、数据报参数错、数据报超时 和重定向差错。与 U D P的差错处理函数相比,它要复杂得多,因为 T C P必须灵活地处理连接 上收到的各种软差错。图 27-12给出了tcp_motify函数。

图27-12 tcp_notify 函数

724

计计TCP/IP详解 卷 2:实现

图27-12 (续)

328-345

如果连接状态为 E S TA B L I S H E D,则忽略 E H O S T U N R E A C H、E N E T U N R E A C H和

EHOSTDOWN差错代码。 处理这 3个差错是 4 . 4 B S D中新增的功能。 N e t / 2及早期版本在连接的软差错变量 (t _ s o f t e r r o r)中记录这些差错,如果连接最终失败,则向应用进程返回相应的差 错码。回想一下, t c p _ x m i t _ t i m e r在收到一个 A C K,确认未发送过的报文段时, 复位t_softerror为零。 346-353

如果连接还未建立,而且 TCP已经至少4次重传了当前报文段, t _ s o f t e r r o r中

已存在差错记录,则最新的差错将被保存在插口的 s o _ e r r o r变量中,从而应用进程可以调 用select对插口进行读写。如果上述条件不满足,当前差错将仍旧保存在 t_softerror中。 我们在 t c p _ d r o p 函数中讨论过,如果连接最终由于超时而被丢弃,

t c p _ d r o p 会把

t _ s o f t e r r o r赋给插口差错变量 e r r n o。任何在插口上等待接收或发送数据的应用进程会 被唤醒,并得到相应的差错代码。

27.8 tcp_quench函数 t c p _ q u e n c h的函数代码在图 2 7 - 1 3中给出。 T C P在两种情况下调用它:当连接上收到源 站抑制差错时,由 t c p _ i n p u t 调用。当 i p _ o u t p u t 返回 E N O B U F S 差错代码时,由 tp_output调用。

图27-13 tcp_quench 函数

拥塞窗口设定为最大报文段长度,强迫

T C P 执行慢起动。慢起动门限不变

(与

t c p _ t i m e r s 处理重传超时的思想相同 ),因此,窗口大小将成指数地增加,直至达到 snd_ssthresh门限或发生拥塞。

27.9 TCP_REASS宏和tcp_reass函数 TCP报文段有可能乱序到达,因此,在数据上交给应用进程之前, TCP必须设法恢复正确

725

第 27章 TCP的函数计计

的报文段次序。例如,如果接收方的接收窗口 大小为 4 0 9 6,等待接收的下一个序号为 0。收 到的第一个报文段携带 0 ~ 1023字节的数据(次 序正确 ),第二个报文段携带了 2048 ~ 3071 字 节的数据,很明显,第二个报文段到达的次序 差错。如果乱序报文段位于接收窗口内, T C P 并不丢弃它,而是将其保存在连接的重组队列 中,继续等待中间缺失的报文段 (携带 1024 ~

16字节(未用)

2 0 4 7字节的报文段 )。这一节我们将讨论处理 T C P重组队列的代码,为后两章讨论 t c p _ input打下基础。 (20字节)

如果假定某个 m b u f中包含 I P首部、 T C P首 部和 4字节的用户数据 ( 回想图 2 - 1 4 的左半部 分),如图 2 7 - 1 4所示。此外还假定数据的序号 依次为7、8、9和10。

(20字节)

图2 4 - 1 2中定义的 t c p i p h d r结构里包含 了i p o v l y和t c p h d r两个结构, t c p h d r结构

4字节数据

在图2 4 - 1 2中给出。图 2 7 - 1 4只列出了与重组有 关的一些变量: t i _ n e x t 、 t i _ p r e v 、 40字节(未用)

t i _ l e n、t i _ d p o r t和t i _ s e q。头两个指 针指向由给定连接所有乱序报文段组成的双向 链表。链表头保存在连接的 T C P控制块中:结 构的头两个成员变量为seg_next和

图27-14 举例:带有 4字节数据的 IP和TCP首部

s e g _ p r e v。t i _ n e x t和t i _ p r e v指针与I P首部的头 8个字节重复,只要数据报到达了 T C P, 就不再需要这些内容。 t i _ l e n等于T C P数据的长度, T C P计算检验和之前首先计算并存储这 个字段。 27.9.1 TCP_REASS宏 t c p _ i n p u t收到数据后,就调用图 2 7 - 1 5中的宏 T C P _ R E A S S,把数据放入连接的重组队 列。TCP_REASS只在一种情况下被调用:参见图 29-22。 54-63

t p是指向连接 T C P控制块的指针, t i是指向接收报文段的 t c p i p h d r结构的指针。

如果下列3个条件均为真: 1) 报文段到达次序正确 (序号t i _ s e q等于连接上等待接收的下一序号, r c v _ n x t);并 且 2) 连接的重组队列为空 (seg_next指向自己,而不是某个 mbuf);并且 3) 连接处于ESTABLISHED状态。 则执行下列步骤:设定延迟 A C K标志;更新 r c v _ n x t,增加报文段携带的数据长度;如果报 文段T C P首部中F I N标志置位,则 f l a g s参数中增加 T H _ F I N标志;更新两个统计值;数据放 入插口的接收缓存;唤醒所有在插口上等待接收的应用进程。

726

计计TCP/IP详解 卷 2:实现

图27-15 TCP_REASS 宏:向连接的重组队列中添加数据

必须满足前述 3个条件的原因是:首先,如果数据次序差错,则必须将其放入重组队列, 直至收到了中间缺失的报文段,才能把数据提交给应用进程。第二,即使当前数据到达次序 正确,但如果重组队列中已存在乱序数据,则新的数据有可能就是所需的缺失数据,从而能 够向应用进程同时提交多个报文段中的数据;第三,尽管允许请求建立连接的 S Y N报文段中 携带数据,但这些数据在连接进入 ESTABLISHED状态之前,必须保存在重组队列中,不允许 直接提交给应用进程。 64-67

如果这3个条件不是同时满足,则 TCP_REASS宏调用TCP_REASS函数,向重组队列

中添加数据。由于收到的报文段如果不是乱序报文段,就有可能是所需的缺失报文段,因此, 置位T F _ A C K N O W,要求立即发送 A C K。T C P的一个重要特性是收到乱序报文段时,必须立即 发送ACK,这有助于快速重传算法 (29.4节)的实现。 在讨论 T C P _ R E A S S 函数代码之前,需要先了解图 2 7 - 1 4中T C P首部的两个端口号, t i_sport和ti_dpor t,所起的作用。其实,只要找到了 TCP控制块并调用了 T C P _ R E A S S, 就不再需要它们了。因此, T C P报文段放入重组队列时,可以把对应 m b u f的地址存储在这两 个端口号变量中。对于图 2 7 - 1 4中的报文段,无需这样做,因为 I P和T C P的首部都存储在 m b u f 的数据部分,可直接使用 d t o m宏。但我们在 2 . 6节讨论m _ p u l l u p时曾指出,如果 I P和T C P的 首部保存在簇中 (如图2 - 1 6所示,对于最大长度报文这是很正常的 ),d t o m宏将无法使用。我 们在该节中曾提到, TCP把从TCP首部指向mbuf的后向指针 (back pointer)存储在TCP的两个端 口号字段中。 图2 7 - 1 6举例说明了这一技术的用法,利用它处理连接上的两个乱序报文段,每个报文段 都存储在一个 m b u f簇中。乱序报文段双向链表的表头是连接的 T C P控制块中的 s e g _ n e x t成 员变量。为简化起见,图中未标出 s e g _ p r e v指针和指向链表最后一个报文段的 t i _ n e x t指 针。 接收窗口等待接收的下一个序号为 1 (r c v _ n x t),但我们假定这个报文段丢失了。接着又 收到了两个报文段,携带 1 4 6 1 ~ 4 3 8 0字节的数据,这是两个乱序报文段。 T C P调用m _ d e v g e t 把它们放入 mbuf簇中,如图 2-16所示。

727

第 27章 TCP的函数计计

(未用)

2048字节簇

(未用)

2048字节簇

(20字节) 后向指针

后向指针

(20字节)

1460字节数据

1460字节数据

548字节(未用)

548字节(未用)

图27-16 两个乱序TCP报文段存储在mbuf簇中

T C P首部的头 32 bit存储指向对应 m b u f的指针,下面介绍的 T C P _ R E A S S函数将用到这个 后向指针。 27.9.2 TCP_REASS函数 图2 7 - 1 7给出了 T C P _ R E A S S函数的第一部分。参数包括: t p,指向 T C P控制块的指针; t i,指向接收报文段 I P和T C P首部的指针; m,指向存储接收报文段的 m b u f链表的指针。前

728

计计TCP/IP详解 卷 2:实现

面曾提到过,ti既可以指向由m所指向的mbuf的数据区,也可以指向一个簇。

图27-17 TCP_REASS 函数:第一部分

6 9 - 8 3 后面将看到, T C P收到一个对 S Y N的确认时, t c p _ i n p u t将调用 T C P _ R E A S S,并 传递一个空的 t i指针(图2 8 - 2 0和图2 9 - 2 )。这意味着连接已建立,可以把 S Y N报文段中携带的 数据(T C P _ R E A S S已将其放入重组队列 )提交给应用程序。连接未建立之前,不允许这样做。 标志“present”位于图27-23中。 8 4 - 9 0 遍历从 s e g _ n e x t 开始的乱序报文段双向链表,寻找序号大于接收报文段序号 (ti_seq)的第一个报文段。注意, for循环体中只包含一个 if语句。 图2 7 - 1 8的例子中,新报文段到达时重组队列中已有两个报文段。图中标出了指针 q,指 向链表的下一个报文段,带有字节 1 0 ~ 1 5 。此外,图中还标出了两个指针 t i _ n e x t 和 t i _ p r e v,起始序号 (t i _ s e q)、长度 (t i _ l e n)和数据字节的序号。由于这些报文段较小, 每个报文段很可能存储在单一的 mbuf中,如图27-14所示。 链表中的前 一报文段

新报文段

图27-18 存储重复报文段的重组队列举例

729

第 27章 TCP的函数计计

图27-19给出了TCP_REASS下一部分的代码

图27-19 TCP_REASS 函数:第二部分

91-107

如果双向链表中 q指向的报文段前还存在报文段,则该报文段有可能与新报文段重

复,因此,挪动指针 q,令其指向 q的前一个报文段 (图2 7 - 1 8中携带字节 4 ~ 8的报文段 ),计算 重复的字节数,并存储在变量 i中: i = q->ti_seq + q->ti_len - ti->ti_seq; = 4 + 5 -7 = 2

如果i大于0,则链表中原有报文段与新报文段携带的数据间存在重复,如例子中给出的 报文段。如果重复的字节数 (i)大于或等于新报文段的大小,即新报文段中所有的数据都已包 含在原有报文段中,新报文段是重复报文段,应予以丢弃。 108-112

如果只有部分数据重复 (如图27-18所示),m_adj丢弃新报文段起始 i字节的数据,

并相应更新新报文段的序号和长度。挪动 q指针,指向链表中的下一个报文段。图 2 7 - 2 0给出 了图27-18中各报文段和变量此时的状态。 116

mbuf的地址m存储在TCP首部的源端口号和目的端口号中,也就是我们在本节前面曾提

到的后向指针,防止 TCP首部被存放在 mbuf簇中,而无法使用 d t o m宏。宏R E A S S _ M B U F定义 为: #define REASS_MBUF(ti) (*(struct mbuf **)&((ti)->ti_t))

t i _ t是一个t c p h d r结构(图2 4 - 1 2 ),最初的两个成员变量是两个 1 6 b i t的端口号。请注意 图2 7 - 1 9中的注释“ X X X”,其中隐含了这样一个假定,指针能够存放在两个端口号占用的 3 2 bit空间中。

730

计计TCP/IP详解 卷 2:实现

链表中的前 一报文段

新报文段

图27-20 删除新报文段中的字节 7和8后,更新图 27-18

图27-21给出了tcp_reass的第三部分,删除重组队列下一报文段中可能的重复字节。 117-135

如果还有后续报文段,则计算新报文段与下一报文段间重复的字节数,并存储在

变量i中。还是以图27-18中的报文段为例,得到: i = 9 + 2 - 10 = 1

因为序号10的字节同时存在于两个报文段中。 根据i值的大小,有可能出现 3种情况: 1) 如果i小于等于0,无重复。 2) 如果i小于下一报文段的字节数 (q - > t i _ l e n),则有部分重复,调用 m _ a d j,从该报 文段中丢弃起始的 i字节。 3) 如果i大于等于下一报文段的字节数,则出现完全重复,从链表中删除该报文段。 136-139

代码最后调用 i n s q u e,把新报文段插入连接的重组双向链表中。图 27-22给出了

图27-18中各报文段和变量此时的状态。

图27-21 TCP_REASS 函数:第三部分

731

第 27章 TCP的函数计计

图27-21 (续)

链表中的前 一报文段

新报文段

图27-22 丢弃所有重复字节后,更新图 27-20

图27-23给出了tcp_reass最后一部分的代码,如果可能,向应用进程递交数据。

图27-23 tcp_reass 函数:第四部分

732

计计TCP/IP详解 卷 2:实现

如果连接还没有收到 SYN(连接处于 LISTEN状态或SYN_SENT状态),不允许向应

145-146

用进程提交数据,函数返回。当函数被宏 T C P _ R E A S S 调用时,返回值 0被赋给宏的参数 f l a g s。这种做法带来的副作用是可能会清除 FIN标志。当宏 T C P _ R E A S S被图29-22的代码调 用时,如果接收报文段包含了 S Y N、F I N和数据(尽管不常见,但却是有效的报文段 ),会出现 这种情况。 147-149

t i设定为链表的第一个报文段。如果链表为空,或者第一个报文段的起始序号

(t i - > t i _ s e q)不等于连接等待接收的下一序号 (r c v _ n x t),则函数返回 0。如果第二个条件 为真,说明在等待接收的下一序号与已收到的数据之间仍然存在缺失报文段。例如,图 2 7 - 2 2 中,如果携带 4 ~ 8 字节的报文段是链表的起始报文段,但 r c v _ n x t等于2,字节 2和3仍旧缺 失,因此,不能把 4 ~ 15字节提交给应用进程。返回值 0将清除FIN标志(如果该标志设定 ),这 是因为还有未收到的数据,所以暂时不能处理 FIN。 150-151

如果连接处于 S Y N _ R C V D状态,且报文段长度非零,则函数返回 0。如果两个条

件均为真,说明插口在监听过程中收到了携带数据的 S Y N报文段。数据将保存在连接队列中, 等待三次握手过程结束。 152-164

循环从链表的第一个报文段开始 (从前面的测试条件可知,它携带数据的次序已经

正确),把数据放入插口的接收缓存,并更新 r c v _ n x t。当链表为空,或者链表下一报文段的 序号又出现差错,即当前处理报文段与下一报文段间存在缺失报文段时,循环结束。此时, f l a g s变量(函数的返回值 )等于0或者为 T H _ F I N,取决于放入插口接收缓存的最后一个报文 段中是否带有FIN标志。 在所有mbuf都放入插口的接收缓存后, s o r w a k e u p唤醒所有在插口上等待接收数据的应 用进程。

27.10 tcp_trace函数 图26-32中,在向IP递交报文段之前, tcp_output调用了tcp_trace函数: if (so->so_options & SO_DEBUG) tcp_trace(TA_OUTPUT, tp->t_state, tp, ti, 0);

在内核的环形缓存中添加一条记录,这些记录可通过 trpt (8)程序读取。此外,如果内核 编译时定义了符号 TCPDEBUG,并且变量tcpconsdebug非零,则信息将输出到系统控制台。 任何进程都可以设定 T C P的插口选项 S O _ D E B U G,要求T C P把信息存储到内核的 环形缓存中。但只有特权进程或系统管理员才能运行 t r p t,因为它必须读取系统内 存才能获取这些信息。 尽管可以为任何类型的插口设定 S O _ D U B U G选项(如U D P或原始I P ),但只有 T C P 才会处理它。 这些信息被保存在 tcp_debug结构中,如图27-24所示。 35-43

t c p _ d e b u g很大 ( 1 9 6字节 ),因为它包含了其他两个结构:保存 I P和T C P首部的

t c p i p h d r和完整的 T C P控制块 t c p c b。由于保存了 T C P控制块,其中的任何变量都可通过 t r p t打印出来。也就是说,如果 t r p t标准输出中没有包含读者感兴趣的信息,可修改源代 码以打印控制块中任何想要的信息 ( N e t / 3版支持这种修改 )。图2 5 - 2 8中的RT T变量就是通过这 种方式得到的。

733

第 27章 TCP的函数计计

图27-24 tcp_debug 结构

53-55

图2 7 - 2 4 还定义了数组 t c p _ d e b u g ,也就是前面提到的环形缓存。数组指针

(tcp_debx)初始化为零,该数组约占 20 000字节。 内核只调用了 t c p _ t r a c e 4次,每次调用都会在结构的 t d _ a c t变量中存入一个不同的 值,如图27-25所示。 描

td_act TA_DROP TA_INPUT TA_OUTPUT TA_USER



当输入报文段被丢弃时,被 t c p _ i n p u t调用 输入处理完毕后,调用 t c p _ o u t p u t之前 调用i p _ o u t p u t发送报文段之前 R P U _xxx请求处理完毕后,被 t c p _ u s r r e q调用





图29-27 图29-26 图26-32 图30-1

图27-25 td_act 值及相应的 tcp_trace 调用

图2 7 - 2 6给出了 t c p _ t r a c e函数的主要部分,我们忽略了直接输出到控制台的那部分代 码。 48-133

在函数被调用时, ostate中保存了连接的前一个状态,与连接的当前状态 (保存在

控制块中 )相比较,可了解连接的状态变迁状况。图 2 7 - 2 5中,T A _ O U T P U T不改变连接状态, 但其他3个调用则会导致状态的转移。

图27-26 tcp_trace 函数:在内核的环形缓存中保存信息

734

计计TCP/IP详解 卷 2:实现

图27-26 (续)

输出举例 图2 7 - 2 7列出了t c p d u m p输出的前 4行,反映 2 5 . 1 2节例子中的三次握手过程和发送的第一 个数据报文段(卷1附录A提供了tcpdump输出格式的细节 )。

图27-27 反映图25-28实例的tcpdump 输出

图27-28列出了与之对应的 trpt的输出。 图2 7 - 2 8的输出与正常的 t r p t输出相比略有一些不同: 32 bit的数字序号显示为 无符号整数(t r p t将其差错地打印为有符号整数 );有些t r p t按1 6进制输出的值被改 为10进制;为了编制图 25-28,作者人为地把从 t _ r t t到t _ r x t c u r的值加入到 t r p t 中。

图27-28 反映图25-28实例的trpt 输出

735

第 27章 TCP的函数计计

图27-28 (续)

在时刻 953 738 ,发送 S Y N。注意,代码中的时间变量有 8位数字,以毫秒为单位,这里 只输出了低6位。输出的结束序号 (20 288 005)是差错的。 SYN中确实携带了 4字节的内容,但 并非数据,而是 M S S选项。重传定时器设定为 6秒(R E X M T),保活定时器为 7 5秒(K E E P),这些 定时器值均以500 ms滴答为单位。t_rtt等于1,意味对该报文段计时,测量 RTT样本值。 发送S Y N是为了响应应用进程的 c o n n e c t调用。一毫秒后,这次系统调用的信息被写入 内核的环形缓存。尽管是因为应用进程调用了 c o n n e c t,才导致发送 S Y N报文段,但 T C P在 处理完PRU_CONNECT请求后,才调用 tcp_trace,环形缓存中实际写入了两条记录。此外, 应用进程调用 c o n n e c t时,连接状态为 C L O S E D,发送完 S Y N后,状态变迁至 S Y N _ S E N T, 这也是两条记录仅有的不同之处。 第三条记录,时刻 954 103,与第一条记录相隔 365 ms (tcpdump显示时间差为362.7ms), 即为图2 5 - 2 8中“实际时间差 ( m s )”一栏的填充值。收到带有 S Y N和A C K的报文段后,连接状 态从SYN_SENT转移到ESTABLISHED。因为计时报文段已得到确认,更新 RTT估计器值。 第四条记录反映了三次握手过程中的第三个报文段:确认对端的 S Y N。因为是纯 A C K报 文段,不用对它计时 (rtt等于0),它在时刻 954 103被发送。connect系统调用返回,应用进 程接着调用 write发送数据,产生 TCP输出。 第五条记录反映了这个数据报文段,在时刻 954 153,三次握手结束后 50 ms ,被发送。它 携带50字节 的数据,起始序号为 20 288 002。重传定时器设为 3秒,需要计时。 应用进程继续调用 w r i t e发送数据。尽管不再显示更多记录,但很明显,接下来的 3条记

736

计计TCP/IP详解 卷 2:实现

录也都是在TCP处理完P R U _ S E N D请求后写入环形缓存的。第一次 P R U _ S E N D请求,生成我们 已看到的第一个 5 1 2字节的输出报文段,其他 3次请求不会引发 T C P输出报文段,此时连接正 处于慢起动状态。只生成 4条记录是因为,图 2 5 - 2 8的例子中的 T C P发送缓存大小只有 4 0 9 6, mbuf簇大小为1024。一旦发送缓存被占满,应用进程就进入休眠状态。

27.11 小结 本章介绍了各种 TCP函数,为后续章节打下基础。 T C P连接正常关闭时,向对端发送 F I N,并等待 4次报文交换过程结束。它被丢弃时,只 需发送RST。 路由表中的每条记录都包含 8个变量,其中有 3个在连接关闭时更新,有 6个用于新连接的 建立,从而内核能够跟踪与同一目标之间建立的正常连接的某些特性,如 RT T估计器值和慢 起动门限。系统管理员可以设置或锁定部分变量,如 M T U、接收管道大小和发送管道大小, 这些特性会影响到达该目标的连接的性能。 T C P对收到的 I C M P差错有一定的容错性—不会导致 T C P终止已建立的连接。 N e t / 3处理 ICMP差错的方式与早期的 Berkeley版本不同。 T C P报文段可能乱序到达,并包含重复数据, T C P必须处理这些异常现象。 T C P为每条连 接维护一个重组队列,保存乱序报文段,处理之后再提交给应用进程。 最后介绍了选定插口选项 S O _ D E B U G时,内核中保存的信息。除某些程序如 t c p d u m p之 外,这些内容也是很有用的调试工具。

习题 27.1 为什么图27-1中最后一行的errno等于0? 27.2 rmx_rtt中存储的最大值是多少? 27.3 为了保存某个给定主机的路由信息 (图2 7 - 3 ),我们用手工在本地的路由表中添加一 条到达该主机的路由。之后,运行 F T P客户程序,向这台主机发送足够多的数据, 如图2 7 - 4所要求的。但终止 F T P客户程序后,检查路由表,到达该主机的所有变量 依旧为0。出了什么问题?

第28章 TCP的输入 28.1 引言 T C P输入处理是系统中最长的一部分代码,函数 t c p _ i n p u t约有11 0 0行代码。输入报文 段的处理并不复杂,但非常繁琐。许多实现,包括 N e t / 3,都完全遵循 RFC 793中定义的输入 事件处理步骤,它详细定义了如何根据连接的当前状态,处理不同的输入报文段。 当收到的数据报的协议字段指明这是一个 T C P报文段时, i p i n t r(通过协议转换表中的 pr_input函数)会调用tcp_input进行处理。 tcp_input在软件中断一级执行。 函数非常长,我们将分两章讨论。图 2 8 - 1列出了 t c p _ i n p u t中的处理框架。本章将结束 对RST报文段处理的讲解,从下一章开始介绍 ACK报文段的处理。 头几个步骤是非常典型的:对输入报文段做有效性验证 (检验和、长度等 ),以及寻找连接 的P C B。尽管后面还有大量代码,但通过“首部预测 (header prediction)”( 2 8 . 4节),算法却有 可能完全跳过后续的逻辑。首部预测算法是基于这样的假定:一般情况下,报文段既不会丢 失,次序也不会错误,因此,对于给定连接, T C P总能猜到下一个接收报文段的内容。如果 算法起作用,函数直接返回,这是 tcp_input中最快的一条执行路径。 如果算法不起作用,函数在“ dodata”处结束,测试几个标志,并且若需要对接收报文段 作出响应,则调用 tcp_output。

图28-1 TCP输入处理步骤小结

738

计计TCP/IP详解 卷 2:实现

图28-1 (续)

函数结尾处有 3个标注,处理出现差错时控制会跳转到这些地方: d r o p a f t e r a c k、 d r o p w i t h r e s e t和d r o p。标注中出现的“ d r o p”指丢弃当前处理的报文段,而非丢弃连 接。不过,当控制跳转到 dropwithreset时,将发送 RST,从而丢弃连接。 函数仅有的另一个分支是首部预测算法后的 s w i t c h语句,如果连接处于 L I S T E N或 S Y N _ S E N T状态时收到了一个有效的 S Y N报文段,它负责分别进行处理。 t r i m t h e n s t e p 6

739

第 28 章 TCP的输入计计

处的代码结束后,跳转到 step 6,继续执行正常的流程。

28.2 预处理 图28-2的代码包含一些声明,并对收到的 TCP报文段进行预处理。 1. 从第一个mbuf中获取IP和TCP首部 170-204

参数i p h l e n等于I P首部长度,包括可能的 I P选项。如果长度大于 2 0字节,可知存

在I P选项,调用 i p _ s t r i p o p t i o n s丢弃这些选项。 T C P忽略除源选路之外的所有 I P选项, 源选路选项由 I P特别保存 ( 9 . 6节),T C P能够读取其内容 (图2 8 - 7 )。如果簇中第一个 m b u f的容 量小于 I P / T C P首部大小 ( 4 0字节 ),则调用 m _ p u l l u p,试着把最初的 4 0字节移入第一个 m b u f中。

图28-2 tcp_input 函数:变量声明及预处理

图28-3给出了函数下一部分的代码,验证 TCP检验和及偏移字段。 2. 验证TCP检验和 205-217

t l e n 指T C P报文段的长度,即 I P 首部后的字节数。前面介绍过, I P 已经从

740

计计TCP/IP详解 卷 2:实现

i p _ l e n中减去了 I P的首部长度,因此,变量 l e n就等于整个 I P数据报的长度,即包括伪首部 在内的需要计算检验和的数据长度。根据 T C P检验和计算的要求,填充伪首部中的各个字段, 如图23-19所示。

图28-3 tcp_input 函数:验证 TCP检验和及偏移字段

3. 验证TCP偏移字段 218-228

T C P的偏移字段, t i _ o f f,是以 32 bit 为单位的 T C P首部长度值,包括所有的

T C P选项。把它乘以 4 (得到T C P报文段中第一个数据字节所在位置的偏移量 ),并验证其有效 性。偏移量必须大于等于标准 TCP首部的大小 (20字节),并且小于等于 TCP报文段的长度。 从T C P长度变量 t l e n中减去首部长度,得到报文段中携带的数据字节数 (可能为 0 ),并把 这个值赋给 tlen,以及TCP首部的变量 ti_len。函数中会多次用到这个值。 图28-4给出了函数下一部分的代码:处理特定的 TCP选项。

图28-4 tcp_input 函数:处理特定的 TCP选项

741

第 28 章 TCP的输入计计

图28-4 (续)

4. 把IP和TCP首部及选项放入第一个 mbuf 230-236

如果首部长度大于 2 0,说明存在 T C P选项。必要时调用 m _ p u l l u p,把标准 I P首

部、标准 T C P首部的所有T C P选项放入簇中的第一个 m b u f中。因为 3部分数据最大只能为 8 0字 节(20+20+40),因此,必定能够放入第一个存储数据分组首部的 mbuf中。 此处能够造成 m _ p u l l u p失败的惟一原因是 I P数据分组的字节数小于 2 0加上T C P 首部长度,而且已通过 TCP检验和的验证,我们认为 m _ p u l l u p不可能失败。但有一 点,图 2 8 - 2中调用的 m _ p u l l u p,将共享计数器 t c p s _ r c v s h o r t,因此,查看 t cps_rcvshort并不能说明哪一个调用失败。不管怎样,从图 24-5可知,即使收到 九百万个TCP报文段之后,这个计数器仍旧为 0。 5. 快速处理时间戳选项 237-255

o p t l e n等于首部中 T C P选项的长度, o p t p是指向第一个选项字节的指针。如果

下列3个条件均为真,说明只存在时间戳选项,而且格式正确: 1) TCP 选项长度等于 1 2 ( T C P O L E N _ T S T A M P _ A P P A );或 T C P选项长度大于 1 2 ,但 optp[12]等于选项结束字节。 2) 选项的头4个字节等于 0x0101080a(TCPOPT_TSTAMP_HDR,在26.6节曾讨论过 )。 3) SYN标志未置位(说明连接已建立,如果报文段中出现时间戳选项,意味着连接双方都 同意使用这一选项 )。 如果上述条件全部满足,则 t s _ p r e s e n t置为1;从接收报文段首部获取两个时间戳值, 分别赋给 t s _ v a l和t s _ e c r;o p t p置为空,因为所有选项已处理完毕。这种辨认时间戳的 方法可以避免调用通用选项处理函数 t c p _ d o o p t i o n s,从而使后者能够专门处理只出现在 SYN报文段中的各种选项 (MSS和窗口大小选项 )。如果连接双方同意使用时间戳,那么在建立 的连接上交换的几乎所有报文段中都可能带有时间戳选项,因此,必须加快其处理速度。 图28-5给出了函数下一部分的代码,寻找报文段的 Internet PCB。 6. 保存输入标志,把字段转换为主机字节序 257-264

接收报文段中的标志 (SYN、FIN等)被保存在本地变量 t i f l a g s中,因为函数在

处理过程中会多次引用这些标志。 TCP首部的两个 16 bit字段和两个 32 bit序号被转换回主机字

742

计计TCP/IP详解 卷 2:实现

节序,而两个 16 bit端口号则不做处理,依旧为网络字节序,因为 Internet PCB中的端口号是 依照网络字节序存储的。

图28-5 tcp_input 函数:寻找报文段的 Internet PCB

7. 寻找Internet PCB 265-279

TCP的缓存(t c p _ l a s t _ i n p c b)中保存了收到的最后一个报文段的 PCB地址,采

用的技术与 U D P相同。T C P使用一对插口来识别连接,寻找 P C B时插口对中 4个元素的比较次 序与 u d p _ i n p u t相同。如果与 T C P缓存中的记录不匹配,则调用 i n _ p c b l o o k u p,把新的 PCB放入缓存。 T C P中不会出现我们在 U D P 中曾遇到过的问题:由于高速缓存中存在通配项 ( w i l d c a r d e n t r y ),导致匹配成功率很低。因为只有处于监听状态的服务器,才可能在其插口中保存通配 项。连接一旦建立,插口对的 4个元素将全部填入确定值。从图 2 4 - 5可知,高速缓存命中率能 够达到80%。 图28-6给出了函数下一部分的代码。

图28-6 tcp_input 函数:判断是否应丢弃报文段

743

第 28 章 TCP的输入计计

图28-6 (续)

8. 丢弃报文段并生成 RST 280-287 如果没有找到PCB,则丢弃输入报文段,并发送 RST作为响应。例如, TCP收到了 一个S Y N,但报文段指定的服务器并不存在,则直接向对端发送 R S T。回想一下,出现这种 情况时UDP的处理方式,它将发送一个 ICMP端口不可达差错。 288-290

如果P C B存在,但对应的 T C P控制块不存在,可能插口已关闭 (t c p _ c l o s e释放

TCP之后,才释放PCB),则丢弃输入报文段,并发送 RST作为响应。 9. 丢弃报文段且不发送响应 291-292

如果T C P控制块存在,但连接状态为 C L O S E D,说明插口已创建,且得到了本地

地址和本地端口号,但还未调用 c o n n e c t或者l i s t e n。报文段被丢弃,且不发送任何响应。 举例来说,如果客户向服务器发送的连接请求报文段到达时,服务器已调用了 b i n d,但还未 调用listen。这种情况下,客户连接请求将超时,导致重传 SYN。 10. 不改变通告窗口大小 293-297

如果需要支持窗口大小选项,连接双方都必须在连接建立时通过窗口大小选项规

定窗口缩放因子。如果报文段中包含 S Y N,说明此时窗口缩放因子还未定义,因此,直接把 T C P首部的窗口字段值复制给 t i w i n;否则,首部中的 16 bit 数值应根据窗口缩放因子左移, 得到32 bit的数值。 图28-7给出了函数的下一部分代码,如果选取了插口的 S O _ D E B U G选项,或者插口正处于 监听状态,则完成一些相应的预处理工作。

图28-7 tcp_input 函数:处理调试选项和监听状态的插口

744

计计TCP/IP详解 卷 2:实现

图28-7 (续)

11. 如果选定了插口调试选项,则保存连接状态及 IP和TCP首部 300-303

如果 S O _ D E B U G 选项置位,则保存当前连接状态 (o s t a t e )及I P和T C P首部

(tcp_saveti)。函数结束时,这些信息将作为参数传给 tcp_trace(图29-26)。 12. 如果监听插口收到了报文段,则创建新的插口 304-319

如果有报文段到达处于监听状态的插口 (listen置位SO_ACCEPTCONN),则调用

s o n e w c o n n创建新的插口。发出 P R U _ A T T A C H协议请求 (图30-2),分配Internet PCB和TCP控 制块。在 T C P最终接受连接请求之前,还需做更多的处理 (如一个最基本的问题,报文段中是 否包含 S Y N ) ,如果发现差错,置位 d r o p s o c k e t 标志,控制跳转至标注“ d r o p ”和 “dropwithreset”,丢弃新的插口。 320-326

i n p和t p将指向新建的插口。本地地址和本地端口号直接从接收报文段 T C P首部

的目的地址和目的端口号字段中复制。如果输入数据报中有源选路的路由,

T C P 调用

i p _ s r c r o u t e,得到指向保存数据报源选路选项的 m b u f的指针,并赋给 i n p _ o p t i o n s。 TCP向连接发送数据时, t c p _ o u t p u t会把源选路选项传给 i p _ o u t p u t,使用与之相同的逆 向路由。 327 新插口的状态设为 LISTEN。如果接收报文段中包含SYN,控制将转到图28-16中的代码, 完成连接建立请求的处理。 13. 计算窗口缩放因子 328-331

窗口缩放因子取决于接收缓存的大小。如果接收缓存大于通告窗口的最大值

( 6 5 5 3 5,T C P _ M A X W I N),则左移 6 5 5 3 5,直到结果大于接收缓存大小,或者窗口缩放因子已 等于最大值(14,T C P _ M A X _ W I N S H I F T)。注意,窗口缩放因子的选取基于监听插口的接收缓 存,也就是说,应用进程调用 l i s t e n进入监听状态之前,应首先设定 S O _ R C V B U F插口选项, 或者继承tcp_recvspace中的默认值。 窗口缩放因子最大值等于 14,而65535×214等于1 073 725 440,已远远大于接收

745

第 28 章 TCP的输入计计

缓存的最大值(Net/3中为2626 144),因此,在窗口缩放因子远小于14时,循环即终止。 参见习题28.1和28.2。 图28-8给出了TCP输入处理下一部分的代码。

图28-8 tcp_inpu t函数:复位空闲时间和保活定时器,处理应用进程选项

14. 复位空闲时间和保活定时器 334-339

由于连接上收到了报文段, t_idle重设为0。保活定时器复位为 2小时。

15. 如果不处于监听状态,处理 TCP选项 340-346

如果TCP首部中有选项,并且连接状态不等于 LISTEN,调用tcp_dooptions进

行处理。前面介绍过,如果连接已建立,接收报文段中只存在时间戳选项,并且时间戳选项 格式符合 RFC 1323附录A的建议,这种情况在图 2 8 - 4中已处理过,而且 o p t p被置为空。如果 插口处于监听状态, T C P把对端地址保存在 P C B中之后,才会调用 t c p _ d o o p t i o n s,这是 因为处理MSS选项时需要了解到达对端的路由,具体代码如图 28-17所示。

28.3 tcp_dooptions函数 函数处理 N e t / 3支持的5个T C P选项(图2 6 - 4 ):E O L、N O P、M S S、窗口大小和时间戳。图 28-9给出了函数的第一部分。

图28-9 tcp_dooptions

函数:处理 EOL和NOP选项

746

计计TCP/IP详解 卷 2:实现

图28-9 (续)

1. 获取选项类型的长度 代码遍历 T C P首部选项,遇到 E O L (选项终止 )时终止循环,函数返回;遇到

1213-1229

N O P 时,将其长度置为 1,因为它后面不带长度字段 (图2 6 - 1 6 ),控制转到 s w i t c h语句的 default子句,对其不做处理。 1230-1234 所有其他选项的长度保存在 optlen中。 所有新增的 Net/3不支持的TCP选项都被忽略。这是因为: 1) 将来定义的所有新选项都将带有长度字段 ( N O P和E O L是仅有的两个不带长度字段的选 项),而for语句的每次循环都跳过 optlen字节。 2) switch语句的default子句忽略所有未知选项。 图2 8 - 1 0给出了 t c p _ d o o p t i o n s最后一部分的代码,处理 M S S、窗口大小和时间戳选 项。 2. MSS选项 1238-1246

如果长度不等于 4(T C P O L E N _ M A X S E G),或者报文段不带 SYN标志,则忽略该

选项。否则,复制两个 M S S字节到本地变量,转换为主机字节序,调用 t c p _ m s s完成处理。 t c p _ m s s负责更新 T C P控制块中的变量 t _ m a x s e g,即发向对端的报文段中允许携带的最大 字节数。 3. 窗口大小选项 1247-1254

如果长度不等于 4(TCPOLEN_WINDOW),或者报文段不带 SYN标志,则忽略该

选项。 N e t / 3 置位 T F _ R C V D _ S C A L E ,说明收到了一个窗口大小选项请求,并在 r e q u e s t e d _ s _ s c a l e中保存缩放因子。由于 c p[ 2 ]只有一个字节,因此,不存在边界问题。 当连接转移到ESTABLISHED状态时,如果连接双方都同意支持窗口大小选项,则使用这一功 能。 4. 时间戳选项 1255-1273

如果长度不等于 1 0 ( T C P O L E N _ T I M E S T A M P ),则忽略该选项。否则,

t s _ p r e s e n t指向的标志被置位 1,两个时间戳值分别保存在 t s _ v a l和t s _ e c r所指向的变 量中。如果收到的报文段带有 S Y N标志,N e t / 3置位T F _ R C V D _ T S T M P,说明收到了一个时间 戳请求。 t s _ r e c e n t等于收到的时间戳值, t s _ r e c e n t _ a g e等于t c p _ n o w,从系统初启 到目前的时间,以 500ms滴答为单位。

748

计计TCP/IP详解 卷 2:实现

[Partridge 1993]介绍了一种基于 Van Jacobson的研究成果,速度更快的 T C P首部 预测算法实现。 图28-11给出了首部预测的第一部分代码。

图28-11 tcp_input 函数:首部预测,第一部分

1. 判定收到的报文段是否是等待接收的报文段 347-366

下列6个条件必须全真,才能说明收到的报文段是连接正等待接收的数据报文段或

ACK报文段: 1) 连接状态等于ESTABLISHED。 2) 下列4个控制标志必须不设定: S Y N、F I N、R S T、或U R G。但A C K标志必须置位。换 言之, T C P的6个控制标志中, A C K标志必须置位,前面列出的 4个标志必须清除, P S H标志 置位与否无关紧要 (连接处于 E S TA B L I S H E D状态时,除非 R S T标志置位,一般情况下, A C K 都会置位)。 3) 如果报文段带有时间戳选项,则最新时间戳值 (t s _ v a l)必须大于或等于连接上以前收 到的时间戳值 (t s _ r e c e n t)。本质上说,这就是 PAW S测试, 2 8 . 7节将详细介绍 PAW S。如果 t s _ v a l小于t s _ r e c e n t,则新报文段是乱序报文段,因为它的发送时间早于连接上收到的 上一个报文段。由于对端通常把时钟值填充到时间戳字段 ( N e t / 3的全局变量 t c p _ n o w),收到 的时间戳正常情况下应该是一个单调递增的序列。 并非每个顺序到达报文段中的时间戳都会增加。事实上, N e t / 3系统每500 ms增加一次时

749

第 28 章 TCP的输入计计

钟值(t c p _ n o w),在这段时间间隔中,完全可能发送多个报文段。假定利用时间戳和序号构 成一个64 bit的数值,序号放在低 32 bit,时间戳放在高 32 bit,对于每个顺序报文段,这个 64 bit的值都至少会增加 1(应考虑取模算法 )。 4) 报文段的起始序号 (t i _ s e q)必须等于连接上等待接收的下一个序号 (r c v _ n x t)。如果 这个条件为假,那么收到的报文段是重传报文段或是乱序报文段。 5) 报文段通告的窗口大小必须非零 (t i w i n),必须等于当前发送窗口 (s n d _ w n d)。也就 是说,无需更新当前发送窗口。 6) 下一个发送序号 (s n d _ n x t)必须等于已发送的最大序号 (s n d _ m a x),也就是说,上一 个发送报文段不是重传报文段。 2. 根据接收的时间戳更新 ts_recent 367-375

如果存在时间戳选项,并且时间戳值满足图 26-18中的测试条件,则把收到的时间

戳值(ts_val)赋给ts_recent,并在ts_recent_age中保存当前时钟 (tcp_now)。 前面讨论过图 26-18中的时间戳有效性测试条件所存在的问题,并在图 26-20中给 出了正确的测试条件。但在首部预测算法的实现中,图 2 6 - 2 0中的T S T M P _ G E Q测试 是多余的,因为图 28-11起始处的 if语句己完成了这一测试 图2 8 - 1 2 给出了首部预测的下一部分代码,用于单向数据的发送方:处理输出数据的 ACK。

图28-12 tcp_input 函数:首部预测,发送方处理

750

计计TCP/IP详解 卷 2:实现

图28-12 (续)

3. 纯ACK测试 376-379 如果下列4个条件全真,则收到的是一个纯 ACK报文段。 1) 报文段不携带数据 (ti_len等于0)。 2) 报文段的确认字段 (t i _ a c k)大于最大的未确认序号 (s n d _ u n a)。由于测试条件是“大 于”,而非“大于等于”,也就是要求收到的 ACK必须确认未曾确认过的数据。 3) 报文段的确认字段 (ti_ack)小于等于已发送的最大序号 (snd_max)。 4) 拥塞窗口大于等于当前发送窗口 (s n d _ w n d),要求窗口完全打开,连接不处于慢起动 或拥塞避免状态。 4. 更新RTT值 384-388

如果报文段携带时间戳选项,或者报文段中的确认序号大于某个计时报文段的起

始序号,则调用 tcp_xmit_timer更新RTT值。 5. 从发送缓存中删除被确认的字节 389-394

a c k e d等于接收报文段确认字段所确认的字节数,调用 s b d r o p从发送缓存中删

除这些字节。更新最大的未确认过的序号 (s n d _ u n a)为报文段的确认字段值,释放保存接收 报文段的mbuf链表(由于数据长度等于 0,实际只有一个保存首部的 mbuf)。 6. 终止重传定时器 395-407

如果接收报文段确认了所有已发送数据 (snd_una等于snd_max),则关闭重传定

时器。若条件不满足,且持续定时器未设定,则重启重传定时器,时限设为 t_rxtcur。 前面介绍过, t c p _ o u t p u t发送报文段时,只有重传定时器未启动,才会设定它。如果 连续发送两个报文段,发送第一个报文段时定时器启动,发送第二个报文段时定时器不变。 但如果只收到第一个报文段的确认,则重传定时器必须重启,防止第二个报文段丢失。 7. 唤醒等待进程 408-409

如果发送缓存修改后,有必要唤醒等待的应用进程,则调用 s o w w a k e u p。从图

16-5可知,如果有应用进程正在等待缓存空间,或者设定了与缓存有关的 s e l e c t选项,或者 正等待插口上的 SIGIO,则SB_NOTIFY为真。 8. 生成更多的输出 410-411

如果发送缓存中有数据,则调用 t c p _ o u t p u t,因为发送窗口已经向右移动。

snd_una已增加,但 snd_wnd未变化,因此,图 24-17中的整个窗口将向右移动。 图2 8 - 1 3给出了首部预测的下一部分代码,接收方收到顺序到达的数据时进行的各种处 理。 9. 测试收到报文段是否是连接等待接收的下一个报文段

751

第 28 章 TCP的输入计计

414-416

如果下列4个条件均为真,则收到的报文段是连接上等待接收的下一报文段,并且

插口缓存中的剩余空间能够容纳到达的数据。 1) 报文段的数据量 (ti_len)大于0,即图28-12起始处if语句的else子句。 2) 确认字段(ti_ack)等于最大的未确认序号,即报文段未确认任何数据。 3) 连接乱序报文段的重组队列为空 (seq_next等于tp)。 4) 接收缓存能够容纳报文段数据。 10. 完成接收数据的处理 423-435

等待接收序号 (r c v _ n x t)递增收到的数据字节数。从 m b u f链中丢弃 I P首部、T C P

首部和所有TCP选项,将剩余的 mbuf链附加到插口的接收缓存,调用 s o r w a k e u p唤醒接收进 程。注意,代码没有调用 T C P _ R E A S S宏,因为宏代码中的条件判定已经包含在首部预测的测 试条件中。设定延迟 ACK标志,输入处理结束。

图28-13 tcp_input 函数:首部预测的接收方处理

统计量 首部预测能在多大程度上改善系统性能?让我们做个简单的实验,跨越

L A N (b d s i和

s v r 4 间的双向通信 ) 的数据传输,和跨越 WA N ( v a n g o g h . c s . b e r k e l e y . e d u 和 f t p . u u . n e t之间的双向通信 )的数据传输。运行 n e t s t a t,得到类似于图 24-5的输出,列出 了两种情况下的首部预测寄存器的值。 跨越L A N传输时,无数据分组丢失,只有一些重复的 A C K。利用首部预测处理的报文段 可占到97%~100%。跨越WAN时,比例有所降低,约为 83%~99%之间。 请注意,首部预测的应用限定于单独的连接,无论主机是否收到了额外的 TCP流量,PCB

752

计计TCP/IP详解 卷 2:实现

缓存必须在主机范围内共享。即使 T C P流量的丢失造成了 P C B缓存缺失,但如果给定连接上 的数据分组未丢失,这条连接上的首部预测仍能工作。

28.5 TCP输入:缓慢的执行路径 下面讨论首部预测失败时的处理代码, t c p _ i n p u t中较慢的一条执行路径。图 2 8 - 1 4给 出了下一部分代码,为输入报文段的处理完成一些准备工作。

图28-14 tcp_input 函数:丢弃 IP和TCP首部

1. 丢弃IP和TCP首部,包括 TCP选项 438-442

更新数据指针和 mbuf链表中的第一个 mbuf的长度,以跳过 IP首部、TCP首部和所

有T C P选项。因为 o f f等于T C P首部长度,包括 T C P选项,因此,表达式中减去了标准 T C P首 部的大小(20字节)。 2. 计算接收窗口 443-455

win等于插口接收缓存中可用的字节数, rcv_adv减去rcv_nxt等于当前通告的

窗口大小,接收窗口等于上述两个值中较大的一个,这是为了保证接收窗口不小于当前通告 窗口的大小。此外,如果最后一次窗口更新后,应用进程从插口接收缓存中取走了数据, w i n可能大于通告窗口,因此, T C P最多能够接收 w i n字节的数据 (即使对端不会发送超过通 告窗口大小的数据 )。 因为函数后面的代码必须确定通告窗口中能放入多少数据 (如果有),所以现在必须计算通 告窗口的大小。落在通告窗口之外的接收数据被丢弃:落在窗口左侧的数据是已接收并确认 过的数据,落在窗口右侧的数据是暂不允许对端发送的数据。

28.6 完成被动打开或主动打开 如果连接状态等于 L I S T E N或者S Y N _ S E N T,则执行本节给出的代码。连接处于这两个状 态时,等待接收的报文段为 SYN,任何其他报文段将被丢弃。

753

第 28 章 TCP的输入计计

28.6.1 完成被动打开 连接状态等于 L I S T E N时,执行图 2 8 - 1 5中的代码,其中变量 t p和i n p指向图 2 8 - 7所创建 的新的插口,而非服务器的监听插口。

图28-15 tcp_input 函数:检测监听插口上是否收到了 SYN

1. 丢弃RST、ACK或非SYN 473-478

如果接收报文段中带有 R S T标志,则丢弃它。如果带有 A C K,则丢弃它并发送

RST作为响应(建立连接的最初的 SYN报文段是少数几个不允许携带 ACK的报文段之一)。如果 未带有 S Y N,则丢弃它。 c a s e子句的后续代码处理连接处于 L I S T E N状态时收到了 S Y N的状 况。新的连接状态等于 SYN_RCVD。 图28-16给出了case语句接下来的代码。

图28-16 tcp_input 函数:处理监听插口上收到的 SYN报文段

754

计计TCP/IP详解 卷 2:实现

图28-16 (续)

2. 如果是广播报文段或多播报文段,则丢弃它 479-486

如果数据报被发送到广播地址或多播地址,则丢弃它, TCP只支持点到点的应用。

前面介绍过,根据数据帧携带的目的硬件地址, e t h e r _ i n p u t置位M _ B C A S T和M _ M C A S T标 志,IN_MULTICAST宏可判定IP地址是否为 D类地址。 注释引用了 i n _ b r o a d c a s t,因为 N e t / 1代码(它不支持多播 )在此处调用了这个 函数,以检测目的 I P地址是否为广播地址。 N e t / 2中改为根据目的硬件地址,通过 ether_input设定M_BCAST和M_MCAST标志。 N e t / 3只测试目的硬件地址是否为广播地址,而且不调用 i n _ b r o a d c a s t测试目 的IP地址是否为广播地址。它假定除非目的硬件地址是广播地址,否则,目的 IP地址 绝不可能是广播地址,从而避免调用 i n _ b r o a d c a s t。另外,如果 N e t / 3真的收到了 一个数据帧,其目的硬件地址为单播地址,而目的 I P地址为广播地址,将执行图 2 8 16中的代码处理此种报文段。 目的地址参数IN_MULTICAST需要被转换为主机字节序。 3. 为客户端的 IP地址和端口号分配 mbuf 487-496

分配一个 m b u f,保存 s o c k a d d r _ i n结构,其中带有客户端的 I P地址和端口号。

IP地址从IP首部的源地址字段中复制,端口号从 TCP首部的源端口号字段中复制。这个结构用 于把服务器的PCB连到客户,之后 mbuf被释放。 注释中的“XXX”,是因为获取mbuf的消耗等同于之后调用 i n _ p c b c o n n e c t的 消耗。不过此处的代码位于 t c p _ i n p u t中较慢的一条执行路径。从图 2 4 - 5可知,不 足%2的接收报文段的处理中会用到这段处理代码。 4. 设定PCB中的本地地址 497-499

laddr是绑定在插口上的本地地址。如果服务器没有为插口绑定一个确定地址 (正

常情况下 ),I P首部的目的地址将成为 P C B中的本地地址。注意,不管数据报是在哪个端口收 到的,都将保存 IP首部中的目的地址。 注意,l a d d r不会是通配地址,因为图 28-7中的代码已将收到报文段中的目的 IP

755

第 28 章 TCP的输入计计

地址赋给了它。 5. 填充PCB中的对端地址 500-505

调用i n _ p c b c o n n e c t,把服务器的 P C B与客户相连,填充 P C B中的对端地址和

对端端口号。之后,释放 mbuf。 图28-17给出了函数下一部分的代码,结束 case语句的处理。

图28-17 tcp_input 函数:完成 LISTEN状态下收到 SYN报文段的处理

6. 分配并初始化IP和TCP首部模板 506-511 调用tcp_template创建IP和TCP首部的模板。图 28-7中调用sonewconn时,为 新连接分配了PCB和TCP控制块,但未分配首部模板。 7. 处理所有的 TCP选项 512-514

如果存在 T C P选项,则调用 t c p _ d o o p t i o n s进行处理。图 2 8 - 8中曾调用过一次

t c p _ d o o p t i o n s,但只处理非 L I S T E N状态时的 T C P选项。现在,插口处于监听状态, P C B 中的对端地址已填入 (t c p _ m s s函数中会用到对端地址 ),调用 t c p _ d o o p t i o n s:获取到达 对端的路由;查看对端主机是本地结点还是远端结点 (选择M S S时,需考虑到对端的网络 I D和 子网ID)。 8. 初始化ISS 515-519

通常情况下,初始发送序号复制自全局变量

t c p _ i s s ,之后增加 64 000

(T C P _ I S S I N C R除以2 )。如果局部变量 i s s非零,则使用 i s s取代t c p _ i s s,初始化连接的 发送序号。 出现以下事件序列时,会用到 iss: • 服务器的IP地址为128.1.2.3,端口号为 27。

756

计计TCP/IP详解 卷 2:实现

• I P地址等于 1 9 2 . 3 . 4 . 5的客户与前述服务器建立了连接,客户端口号等于 3 0 0 0。服务器的 插口对为{128.1.2.3, 27, 192.3.4.5, 3000}。 • 服务器主动关闭了连接,上述插口对的状态转移到 TIME_WAIT。连接处于这种状态时, 最后收到的序号保存在 TCP控制块中。假设序号等于 100 000。 • 连接离开 T I M E _ WA I T状态之前,收到来自于同一客户主机、同一端口号 ( 1 9 2 . 3 . 4 . 5 , 3 0 0 0 )的新的 S Y N,T C P寻找处于 TIME_WAIT状态的连接所对应的 P C B,而不是监听服 务器的PCB。假定新SYN报文段的序号等于 200 000。 • 因为连接状态不等于 L I S T E N,所以将不执行刚讨论过的图 2 8 - 1 7中的代码,而是执行图 2 8 - 2 8中的代码。我们将看到,其中包含了下列处理逻辑:如果新 S Y N报文段的序号 (200 000)大于客户最后发来的序号 (100 000),那么: ( 1 )局部变量 i s s等于100 000加上 128 000;( 2 )处于TIME_WAIT状态的连接被完全关闭 ( P C B和T C P控制块被删除 );( 3 )控 制跳转到 findpcb(图28-5)。 • 寻找服务器监听插口的 P C B (假定监听服务器还在运行 ),执行本节中介绍的代码。图 2 8 17中的代码将使用局部变量 iss(现在等于228 000)初始化新连接的 tcp_iss。 RFC 11 2 2中定义的这种处理逻辑,允许同一个客户和服务器重用同样的插口连接对,只 要服务器主动关闭原有连接。它也解释了为什么只要有进程调用

c o n n e c t ,全局变量

tcp_iss就递增64 000(图30-4):为了确保在某个客户不断地重建与同一个服务器的连接的情 况下,即使前一次连接上没有传输数据,甚至 500 ms定时器都未超时 (定时器超时处理代码会 增加tcp_iss),新建连接仍可以使用较大的 ISS。 9. 初始化控制块中的序号变量 520-522

图2 8 - 1 7中,初始接收序号复制自 S Y N报文段中的序号字段 (i r s)。下面两个宏初

始化了TCP控制块中的相关变量。 #define tcp_rcvseqinit(tp) \ (tp)->rcv_adv = (tp)->rcv_nxt = (tp)->irs + 1 #define tcp_sendseqinit(tp) \ (tp)->snd_una = (tp)->snd_nxt = (tp)->snd_max = (tp)->snd_up = \ (tp)->iss

因为SYN占据一个序号,所以第一个宏表达式需加 1。 10. 确认SYN并更新状态 523-525 因为对于SYN的确认必须立即发送,所以置位 TF_ACKNOW标志。连接状态转移到 S Y N _ R C V D,连接建立定时器设为 7 5秒(T C P T V _ K E E P _ I N I T)。因为 T F _ A C K N O W置位,函 数结束时将调用 t c p _ o u t p u t。从图 2 4 - 1 6可知,此种 t c p _ o u t f l a g s会导致发送携带 S Y N 和ACK的报文段。 526-528

现在,TCP结束了从图 28-7开始的新插口的创建, d r o p插口标志被清除。控制跳

转到t r i m t h e n s t e p 6处,完成 SYN报文段的处理。前面介绍过, SYN报文段能够携带数据, 尽管只有等连接进入 ESTABLISHED状态后,数据才会被提交给应用程序。 28.6.2 完成主动打开 图28-18给出了连接进入 SYN_SENT状态后,处理代码的第一部分。 TCP等待接收SYN。

757

第 28 章 TCP的输入计计

图28-18 tcp_input 函数:判定收到的 SYN是否是所需的响应

1. 验证收到的 ACK 530-546

当应用进程主动打开, T C P发送S Y N时,从

图3 0 - 4可知,连接的 i s s将等于全局变量 t c p _ i s s,宏 t c p _ s e n d s e q i n i t(前一节结尾给出了定义 )被执行。 假设ISS等于365,图28-19给出了t c p _ o u t p u t发送SYN 后的发送序号变量。 t c p _ s e n d s e q i n i t初始化图 2 8 - 1 9中的4个变量为 3 6 5,接着图 2 6 - 3 1中的代码在发送 S Y N之后,把其中两

图28-19 ISS等于365的SYN发送后 的发送序号变量

个增至 3 6 6。因此,如果图 2 8 - 1 8 中的接收报文段包含 A C K,并且确认字段小于等于 i s s( 3 6 5 ),或者大于 s n d _ m a x( 3 6 6 ),A C K无效,丢弃报文段 并发送 R S T作为响应。注意,连接处于 S Y N _ S E N T状态时,收到的报文段中无需携带 A C K。 它可以只包括SYN,这种情况称为同时打开 (simultaneous open)(图24-15)。 2. 处理并丢弃 RST报文段 547-551

如果接收报文段中带有 R S T,则丢弃它。但首先应查看 A C K标志,因为如果报文

段同时携带了有效的 ACK(已验证过)和RST,则说明对端拒绝本次连接请求,通常是因为服务 器进程未运行。这种情况下, t c p _ d r o p设定插口的 s o _ e r r o r变量,并向调用 c o n n e c t的 应用进程返回差错。 3. 判定SYN标志是否置位 552-553 如果收到报文段中的 SYN标志未置位,则丢弃它。 这个c a s e语句的其余代码用于处理本地发送连接请求后,收到对端响应的 S Y N 报文段 (及可选的ACK)的情况。图 28-20给出了tcp_input下一部分的代码,继续处理 SYN。

758

计计TCP/IP详解 卷 2:实现

图28-20 tcp_input 函数:发送连接请求后,收到对端响应的 SYN

4. 处理ACK 554-558

如果报文段中有 A C K,令 s n d _ u n a 等于报文段的确认字段。以图 2 8 - 1 9为例,

s n d _ u n a 应更新为 3 6 6 ,因为确认字段惟一有效的值就是

3 6 6 。如果 s n d _ n x t 小于

snd_una(在图28-19的例子中不可能发生 ),令snd_nxt等于snd_una。 5. 关闭连接建立定时器 559 连接建立定时器被关闭。 此处代码有错误。连接建立定时器只有在ACK标志置位时才能被关闭,因为收到一 个不带ACK的SYN报文段,只是说明连接双方同时打开,而不意味着对端已收到了SYN。 6. 初始化接收序号 560-562

初始接收序号从接收报文段的序号字段中复制。 t c p _ r c v s e q i n i t宏(上一节结

束时给出了定义 )初始化 r c v _ a d v和r c v _ n x t为接收序号加 1。置位 T F _ A C K N O W标志,从而 在函数结尾处调用 t c p _ o u t p u t,发送报文段携带的确认字段应等于 r c v _ n x t(图26-27),确 认刚收到的 SYN。 563-564

如果接收报文段带有 A C K,并且 s n d _ u n a大于连接的 I S S,主动打开处理完毕,

连接进入ESTABLISHED状态。 第二个测试条件其实是多余的。图 28-20起始处,如果 ACK标志置位, s n d _ u n a 将等于接收报文段的确认字段值。另外,图 28-18中紧跟着 case语句的if语句验证了 收到的确认字段大于 ISS。所以,此处只要 ACK置位,就可以确保 snd_una大于ISS。

759

第 28 章 TCP的输入计计

7. 连接建立 565-566

s o i s c o n n e c t e d 设 定 插 口 进 入 连 接 状 态 , T C P连 接 的 状 态 转 移 到

ESTABLISHED。 8. 查看窗口大小选项 567-572

如果TCP在本地SYN中加入窗口大小选项,并且收到的 SYN中也包含了这一选项,

使用窗口缩放功能,设定 s n d _ s c a l e和r c v _ s c a l e。因为t c p _ n e w t c p c b初始化T C P控制 块为0,所以,如果不使用窗口大小选项,这两个变量的默认值为 0。 9. 向应用进程提交队列中的数据 573-574 由于数据可能在连接未建立之前到达,调用 tcp_reass把数据放入接收缓存,第 二个参数为空。 测试条件其实不必要的。因为 T C P 刚收到带有 A C K 的S Y N 报文段,状态从 SYN_SENT转移到ESTABLISHED。即使有数据出现在 SYN中,也会被暂时搁置,直 到函数快结束,控制转到 d o d a t a标注时才会被处理。如果 T C P 收到不带 A C K 的 SYN(同时打开),即使报文段携带数据,也会被暂时搁置,等到收到了 ACK,连接从 SYN_RCVD转移到ESTABLISHED之后,才会被处理。 尽管S Y N中可以携带数据,并且 N e t / 3能够正确处理这样的报文段,但 N e t / 3自己 不会产生这样的报文段。 10. 更新RTT估计器值 575-580

如果确认的 S Y N正被计时, t c p _ x m i t _ t i m e r将根据得到的对 S Y N报文段的测

量值初始化 RTT估计器值。 TCP在此处忽略收到的时间戳选项,只查看 t _ r t t计数器。TCP主动打开时,在 第一个 S Y N中加入时间戳选项 (图2 6 - 2 4 ),如果对端也同意采用时间戳,就会在它响 应的S Y N中回应收到的时间戳 (参见图 2 8 - 1 0,N e t / 3在S Y N中回应收到的时间戳 )。因 此, T C P 在此处可以使用收到的时间戳,而不用 t _ r t t,但因为两者的精度相同 ( 5 0 0 m s ),在这一点上时间戳并无优势可言。使用时间戳,而非 t _ r t t计数器的真正 好处在于高速网络中同时发送大量数据时,能提供更多的 RTT测量值和更好的估计器 值(希望如此)。 11. 同时打开 581-582

如果TCP在SYN_SENT状态收到不带 ACK的SYN,则称为同时打开,连接转移到

SYN_RCVD状态。 图28-21给出了函数的下一部分代码,处理 SYN中可能携带的数据。图 28-17结尾处,代码 跳转至trimthenstep6标注处,这里也有类似的情况。

图28-21 tcp_input 函数:接收 SYN的通用处理

760

计计TCP/IP详解 卷 2:实现

图28-21 (续)

584-589

报文段序号加1,以计入SYN。如果SYN带有数据, t i _ s e q现在应等于数据第一

个字节的序号。 12. 丢弃落在接收窗口外的数据 590-597

t i _ l e n等于报文段中的数据字节数。如果它大于接收窗口,超出部分的数据

(t i _ l e n减去r c v _ w n d)将被 m _ a d j丢弃。函数参数为负值,所以,将从 m b u f链尾部起逆向 删除数据 (图2 - 2 0 )。更新t i _ l e n,等于数据删除后 m b u f中剩余的数据量。清除 F I N标志,这 是因为FIN可能跟在最后一个数据字节之后,落在接收窗口外而被丢弃。 如果 S Y N是对本地连接请求的响应,且携带的数据过多,则说明对端收到的 S Y N报文段中带有窗口通告,但对端忽略了通告的窗口大小,并禁止不规范的行为。 但如果主动打开的 S Y N报文段中带有大量数据,则说明对端还未收到窗口通告,所 以不得不猜测SYN中能够携带多少数据。 13. 强制更新窗口变量 598-599

s n d _ w l 1等于接收序号减 1。从图 2 9 - 1 5中可看到,这将强制更新 3个窗口变量:

s n d _ w n d、s n d _ w l 1和s n d _ w l 2。接收紧急指针 (r c v _ u p)等于接收序号。控制跳转到标注 step6处,与RFC 793定义的步骤相对应,我们将在图 29-15中详细讨论。

28.7 PAWS:防止序号回绕 图2 8 - 2 2给出了 t c p _ i n p u t下一部分的代码,处理可能出现的序号回绕: RFC 1323中定 义的PAWS算法。请回想一下我们在 26.6节关于时间戳的讨论。

图28-22 tcp_input 函数:处理时间戳选项

761

第 28 章 TCP的输入计计

图28-22 (续)

1. 基本PAWS测试 602-613 如果存在时间戳,则调用 tcp_dooptions设定ts_present。如果下列 3个条件 全真,则丢弃报文段: 1) RST 标志未置位 (参见习题28.8)。 2) TCP曾收到过对端发送的有效的时间戳 (ts_recent非零);并且 3) 当前报文段中的时间戳 (ts_val)小于原先收到的时间戳。 PAWS算法基于这样的假定:对于高速连接, 32 bit时间戳值回绕的速度远小于 32 bit序号 回绕的速度。习题 2 8 . 6说明,即使是最高的时钟计数器更新频率 (每毫秒加 1 ),时间戳的符号 位也要 2 4天才会回绕一次。而在千兆级网络中,序号可能 1 7秒就回绕一次 (卷1的2 4 . 3节)。因 此,如果报文段时间戳小于从同一个连接收到的最近一次的时间戳,说明是个重复报文段, 应被丢弃 (还需进行后续的时间戳过期测试 )。尽管因为序号已过时, t c p _ i n p u t也可将其丢 弃,但PAWS算法能够有效地处理序号回绕速率很高的高速网。 注意,PAW S算法是对称的:它不仅丢弃重复的数据报文段,也丢弃重复的 A C K。PAW S 处理所有收到的报文段,前面介绍过,首部预测代码也采用了 PAWS测试(图28-11)。 2. 检查过期时间戳 614-627 尽管可能性不大, PAWS测试还是会失败,因为连接有可能长时间空闲。收到的报 文段并非重复报文段,但连接空闲时间过长,造成时间戳值回绕,从而小于从同一个连接收 到的最近一次的时间戳。 无论何时,t s _ r e c e n t保存接收报文段中的时间戳值, t s _ r e c e n t _ a g e记录当前时间 (t c p _ n o w)。如果t s _ r e c e n t的最后一次更新发生在 24天之前,则将其清零,不是一个有效 的时间戳值。常量 T C P _ P A W S _ I D L E定义为( 2 4×2 4×6 0×6 0×2 ),最后的乘数 2指每秒钟 2个 滴答。这种情况下,不丢弃接收报文段,因为问题是时间戳过期,而非重复报文段。参见习

762

计计TCP/IP详解 卷 2:实现

题28.6和28.7。 图2 8 - 2 3举例说明了时间戳过期问题。连接左侧的系统是一个非 N e t / 3的T C P实现,以 R F C 1323中规定的最高速度,每毫秒更新一次时钟。连接右侧是 Net/3实现。 数据,时间戳=1 时间戳

连接空闲25天= 时间戳 时间戳

时间戳

滴答 时间戳符号位变化

数据、时间戳

图28-23 过期时间戳举例

第一个数据报文段中携带的时间戳值等于 1,所以ts_recent等于1,ts_recent_age等 于当前时间(tcp_now),如图28-11和图28-35所示。连接空闲25天,在此期间 tcp_now增加了 4 320 000 (25×24×60×60×1000),对端的时间戳值增加了 2 160 000 000 (25×24×60×60× 1000),时间戳值的符号位改变,即 2 147 483 649大于1,而2 147 483 650小于1(回想图24-26)。 因此,当收到的数据报文段中的时间戳等于 2 160 000 001 时,调用 T S T M P _ L T宏进行比较, 收到的时间戳小于 t s _ r e c e n t(1),PAWS测试失败。但因为 t c p _ n o w减去t s _ r e c e n t _ a g e 大于24天,说明造成失败的原因是连接空闲时间过长,报文段被接受。 3. 丢弃重复报文段 628-633

如果PAW S算法测试说明收到的是一个重复报文段,确认之后丢弃该报文段 (所有

重复报文段都必须被确认 ),不更新本地时间戳变量。 图2 4 - 5中,t c p _ p a w s d r o p ( 2 2 )远小于t c p s _ r c v d u p p a c k (46 953)。这可能 是因为目前只有很少的系统支持时间戳,导致绝大多数重复报文段直到 TCP输出处理 中才被发现和丢弃,而非 PAWS。

28.8 裁剪报文段使数据在窗口内 本节讨论如何调整收到的报文段,确保它只携带能够放入接收窗口内的数据: • 丢弃接收报文段起始处的重复数据;并且 • 从报文段尾部起,丢弃超出接收窗口的数据。 从而只剩下可放入接收窗口的新数据。图 2 8 - 2 4给出的代码,用于判定报文段起始处是否 存在重复数据。 1. 查看报文段前部是否存在重复数据 635-636

如果接收报文段的起始序号 (t i _ s e q)小于等待接收的下一序号 (r c v _ n x t),则

763

第 28 章 TCP的输入计计

todrop大于0,报文段前部有重复数据。这些数据己被确认并提交给应用进程 (图24-18)。

图28-24 tcp_input 函数:查看报文段起始处的重复数据

2. 丢弃重复SYN 637-645

如果S Y N标志置位,它必然指向报文段的第一个数据序号,现已知是重复数据。

清除SYN,报文段的起始序号加 1,以越过重复的 SYN。此外,如果接收报文段中的紧急指针 大于1 (ti_urp),则必须将其减 1,因为紧急数据偏移量以报文段起始序号为基准。如果紧急 指针等于 0或者1,则不做处理,为防止出现等于 1的情况,清除 U R G标志。最后, t o d r o p减 1(因为SYN占用一个序号)。 图28-25继续处理报文段前部的重复数据。

图28-25 tcp_input 函数:处理完全重复的报文段

764

计计TCP/IP详解 卷 2:实现

图28-25 (续)

3. 判定报文段数据是否完全重复 646-648

如果报文段前部重复的数据字节数大于等于报文段大小,则是一个完全重复的报

文段。 4. 判定重复FIN 649-663 接下来测试 FIN是否重复,图28-26举例说明了这一情况。 TCP已确认并提交给插口层的数据序号

接收报文段

标志置位 下一接收序号

图28-26 举例:带有 FIN标志的重复报文段

图2 8 - 2 6的例子中, t o d r o p等于5,大于等于 t i _ l e n( 4 )。因为 F I N置位,并且 t o d r o p等 于t i _ l e n加1,所以清除FIN标志,t o d r o p重设为4,置位T F _ A C K N O W,函数结束时立即发 送ACK。这个例子也适用于其他报文段,如果 ti_seq加上ti_len等于10。 代码的注释提到了 4 . 2 B S D实现中的保活定时器, N e t / 3省略了相关处理 ( i f语句中 的另一项测试)。 5. 生成重复ACK 664-672

如果todrop非零(报文段携带的全部是重复数据 ),或者ACK标志未置位,则丢弃

报文段,调用 d r o p a f t e r a c k生成ACK。出现这种情况,一般是因为对端未收到 ACK,导致 报文段重发。TCP生成新的ACK。 6. 处理同时打开或半连接 664-672 代码还处理同时打开,以及插口与自己建立连接的情况,将在下一节中详细讨论。 如果 t o d r o p等于 0 (完全重复报文段中不包含数据 ),且 A C K标志置位,则继续下一步的处 理。 i f语句是 4 . 4 B S D版中新加的。早期的基于 B e r k e l e y的系统只是简单地跳转到 dropafterack,即不处理同时打开,也不处理与自己建立连接的情况。

765

第 28 章 TCP的输入计计

即使做了改进,这段代码仍有错误,我们在本节结束时将谈到这一点。 7. 收到部分重复报文段时,更新统计值 673-676

当t o d r o p小于报文段长度时,执行 e l s e语句:报文段携带数据中只有部分重

复。 8. 删除重复数据,更新紧急指针 677-685 调用m_adj,从mbuf链的首部开始删除重复数据,并相应地调整起始序号和长度。 如果紧急指针指向的数据仍在 m b u f中,也需做相应的调整。否则,紧急指针清零,并清除 URG标志。 图28-27给出了函数下一部分的代码,处理应用进程终止后到达的数据。

图28-27 tcp_input 函数:处理应用进程终止后到达的数据

687-696

如果找不到插口的描述符,说明应用进程已关闭了连接 (连接状态等于图 2 4 - 1 6中

大于C L O S E _ WA I T的5个状态中的任何一个 ),若接收报文段中有数据,则连接被关闭。报文 段被丢弃,输出 RST做为响应。 因为T C P支持半关闭功能,如果应用进程意外终止 (也许被某个信号量终止 ),做为进程终 止的一部分,内核将关闭所有打开的描述符, TCP将发送FIN。连接转移到FIN_WAIT_1状态。 因为FIN的接收者无法知道对端执行的是完全关闭,还是半关闭。如果它假定是半关闭,并继 续发送数据,那么将收到图 28-27中发送的FIN。 图28-28给出了函数下一部分的代码,从接收报文段中删除落在通告窗口右侧的数据。

图28-28 tcp_input 函数:删除落在窗口右侧的数据

766

计计TCP/IP详解 卷 2:实现

图28-28 (续)

9. 计算落在通告窗口右侧的字节数 697-703

t o d r o p等于接收报文段中落在通告窗口右侧的字节数。例如,在图 2 8 - 2 9中,

todrop等于(6+5)减去(4+6),即等于1。 接收窗口 向发送方通告

已确认过的序号

接收报文段

图28-29 举例:接收报文段部分数据落在窗口右侧

10. 如果连接处于TIME_WAIT状态,查看有无新的连接请求 704-718 如果todrop大于等于报文段长度,则丢弃整个报文段。如果下列3个条件全真: 1) SYN标志置位;并且 2) 连接处于TIME_WAIT状态;并且 3) 新的起始序号大于连接上最后收到的序号; 说明对端要求在已被关闭且正处于 TIME_WAIT状态的连接上重建连接。 RFC 1122允许这种情 况,但要求新连接的 ISS必须大于最后收到的序号 (r c v _ n x t)。TCP在r c v _ n x t的基础上增加

767

第 28 章 TCP的输入计计

128 000(T C P _ I S S I N C R),得到执行图 2 8 - 1 7中的代码时所使用的 I S S。调用 t c p _ c l o s e释放 处于TIME_WAIT状态的原有连接的 P C B和T C P控制块。控制跳转到 f i n d p c b(图2 8 - 5 ),寻找 监听服务器的PCB(假定服务器仍在运行 )。然后执行图28-7中的代码,为新连接创建新的插口, 最后执行图 28-16和图28-17中的代码,完成新连接请求的处理。 11. 判定是否为窗口探测报文段 719-728 如果接收窗口已关闭 (rcv_wnd等于0),且接收报文段中的数据从窗口最左端开始 (r c v _ n x t),说明是对端发送的窗口探测报文段。 T C P立即发送响应 A C K,其中包含等待接 收的序号。 12. 丢弃完全落在窗口之外的其他报文段 729-730

如果报文段整个落在窗口之外,且并非窗口探测报文段,则丢弃该报文段,并发

送携带等待接收序号的 ACK,作为响应。 13. 处理携带部分有效数据的报文段 731-735

通过m _ a d j,从m b u f链中删除落在窗口右侧的数据,并更新 t i _ l e n。如果接收

报文段是对端发送的窗口探测报文段, m _ a d j将丢弃 m b u f链中的所有数据,并将 t i _ l e n设 为0,最后清除 FIN和PSH标志。 何时丢弃ACK 图2 8 - 2 5 中的代码有错误,在几种情况下,本应继续进行报文段处理,控制却跳转到 d r o p a f t e r a c k[Carlson 1993; Lanciani 1993]。系统实际运行时,如果连接双方重组队列中 都存在缺失报文段,并都进入持续状态,将造成死锁,因为双方都将丢弃正常的 ACK。 纠正的方法是简化图28-25起始处的代码。控制不再跳转到dropafterack,如果收到了完全 重复报文段,则关闭FIN标志,并在函数结束时强迫立即发送ACK。删除图28-25中的646~676行的 代码,而代之以图28-30中的代码。此外,新代码还更正了原代码中的另一个错误(习题28.9)。

图28-30 图28-28中646~676行代码的修正

768

计计TCP/IP详解 卷 2:实现

28.9 自连接和同时打开 读者应首先理解插口与自己建立连接的步骤。接着会看到在 4 . 4 B S D中,如何巧妙地通过 一行代码修正图 28-25中的错误,从而不仅能够处理自连接,还能处理 4.4BSD以前的版本中都 无法正确处理的同时打开。 应用进程创建一个插口,并通过下列系统调用建立自连接: s o c k e t,b i n d绑定到一个 本地端口 (假定为 3 0 0 0 ),之后 c o n n e c t试图与同一个本地地址和同一个端口号建立连接。如 果c o n n e c t成功,则插口已建立了与自己的连接:向这个插口写入的所有数据,都可以在同 一插口上读出。这有点类似于全双工的管道,但只有一个,而非两个标识符。尽管很少有应 用进程会这样做,但实际上它是一种特殊的同时打开,两者的状态变迁图相同。如果系统不 允许插口建立自连接,那么它也很可能无法正确处理同时打开,而后者是 RFC 1122所要求的。 有些人对于自连接能成功感到非常惊诧,因为只用了一个 Internet PCB和一个T C P控制块。不 过,TCP是全双工的、对称的协议,它为每个方向上的数据流保留一份专有数据。 图2 8 - 3 1给出了应用进程调用 c o n n e c t时的发送序号空间, S Y N已发送,连接状态为 SYN_SENT。 插口收到 S Y N后,执行图 2 8 - 1 8和图2 8 - 2 0中的代码,但因为 S Y N中未包含 A C K,连接状 态转移到 S Y N _ R C V D。从状态变迁图 (图2 4 - 1 5 )可知,与同时打开类似。图 2 8 - 3 2给出了接收 序号空间。图 2 8 - 2 0置位T F _ A C K N O W,t c p _ o u t p u t生成的报文段将包含 S Y N和A C K (图2 4 16中的tcp_outflags)。SYN序号等于153,而确认序号等于 154。 发送

状态

图28-31 自连接:SYN发送后的发送序号空间

接收

状态

图28-32 自连接:收到的 SYN处理完毕后的接收序号空间

与图28-20处理的正常情况相比,发送序号空间没有变化,只是连接状态等于 SYN_SENT。 图28-33给出了收到同时带有 SYN和ACK的报文段时,接收序号空间的状态。

接收

图28-33 收到带有 SYN和ACK报文段时,接收序号空间的状态

769

第 28 章 TCP的输入计计

因为连接状态等于 S Y N _ R C V D,将执行图 2 9 - 2中的代码处理收到的报文段,而不用我们 在本章前面讨论过的处理主动打开或被动打开的代码。但在此之前,首先遇到的是图 2 8 - 2 4中 的代码,而且从测试结果看似乎是一个重复 SYN: todrop = rcv_nxt - rcv_seq = 154 - 153 = 1

因为S Y N标志置位,清除该标志, t i _ s e q等于1 5 4,t o d r o p等于0。但因为 t o d r o p等于报 文段长度 (0),图28-25开始处的测试条件为真,从而判定是一个重复报文段,执行注释为“处 理绑定插口自连接的情况”的代码。早期的 T C P实现直接跳到 d r o p a f t e r a c k ,略过了 SYN_RCVD状态的处理逻辑,不可能建立连接。相反,即使 t o d r o p等于0,且ACK标志置位 ( 本例中两个条件都成立 ) , N e t / 3 仍旧继续处理收到的报文段,从而进入函数后面对 SYN_RCVD状态的处理,连接转移到 ESTABLISHED状态。 图28-34给出了自连接处理中函数调用的情况,是非常有意思的。 系统调用

软中断

添加到

动作

发送

软中断

添加到

处理

发送

处理

起始状态 结束状态

图28-34 自连接处理中的函数调用序列

操作顺序从左至右,首先应用进程调用 c o n n e c t,发出 P R U _ C O N N E C T请求,经协议栈 发送S Y N。因为报文段发向主机自己的 I P地址,直接通过环回接口加入到 i p i n t r q,并生成 一个软中断。 系统在软中断处理中调用 i p i n t r,i p i n t r调用 t c p _ i n p u t ,t c p _ i n p u t再调用

770

计计TCP/IP详解 卷 2:实现

tcp_output,经协议栈发送带有ACK的SYN。这个报文段也经由环回接口加入到 ipintrq, 并生成一个软中断。系统调用 i p i n t r处理软中断, i p i n t r调用 t c p _ i n p u t,连接进入 ESTABLISHED状态。

28.10 记录时间戳 图28-35给出了tcp_input下一部分的代码,处理收到的时间戳选项。

图28-35 tcp_input 函数:记录时间戳

737-746

如果收到的报文段中带有时间戳,时间戳值保存在 ts_recent中。我们在 26.6节

曾讨论过Net/3的处理代码有错误。如果 FIN和SYN标志均未置位,表达式 ((tiflags & (TH_SYN|TH_FIN)) != 0)

等于0;如果有一个置位,则等于 1。

28.11 RST处理 图28-36给出了处理 RST标志的switch语句,取决于当前的连接状态。 1. SYN_RCVD状态 759-761

插口差错代码设定为 ECONNREFUSED,控制向前跳转若干行,关闭插口。在两种

状况下,连接进入此状态。一般地讲,连接收到 S Y N后,从L I S T E N转移到S Y N _ R C V D状态。 T C P发送带有 A C K的S Y N做为响应,但接着却收到了对端的 R S T。此时, s o引用的插口是在 图28-7中调用s o n e w c o n n新创建的。因为 d r o p s o c k e t为真,在标注 d r o p处,插口被丢弃, 监听插口不受影响。这也是图 24-15中状态从SYN_RCVD转回LISTEN的原因。 另一种情况是,应用进程调用 c o n n e c t后,出现同时打开,状态也转移到 S Y N _ R C V D。 收到RST后,向应用进程返回插口差错。

图28-36 tcp_input 函数:处理 RST标志

771

第 28 章 TCP的输入计计

图28-36 (续)

2. 其他状态 762-777

如果在E S TA B L I S H E D、F I N _ WA I T _ 1、F I N _ WA I T _ 2或C L O S E _ WA I T状态收到

R S T,则返回差错代码 E C O N N R E S E T。如果状态为 C L O S I N G、L A S T _ A C K或T I M E _ WA I T, 由于应用进程已关闭插口,无需返回差错代码。 如果允许RST终止处于 T I M E _ W A I T状态的连接,那么 T I M E _ W A I T状态也就没有 存在的必要。 RFC 1337 [Braden 1992] 讨论了这一点,及其他取消 TIME_WAIT状态 的可能状况,建议不允许 RST永久终止处于 T I M E _ W A I T状态的连接。参见习题 28.10 中的例子。 图28-37给出了函数下一部分的代码,验证 SYN是否出错, ACK是否存在。

图28-37 tcp_input 函数:处理带有多余 SYN或者缺少 ACK的报文段

778-785

如果 S Y N标志依旧置位,说明出现了差错,连接被丢弃,返回差错代码

ECONNRESET。

772

计计TCP/IP详解 卷 2:实现

786-790

如果ACK标志未置位,则报文段被丢弃。我们将在下一章讨论函数剩余部分的代

码,其中假定ACK标志均置位。

28.12 小结 本章详细介绍了 TCP输入处理的前半部分,下一章将继续讨论函数剩余的部分。 本章介绍了如何验证报文段检验和,处理各种 T C P选项,处理发起和结束连接建立的 SYN报文段,从报文段头尾两个方向删除无效数据,及处理 RST标志。 首部预测算法处理正常情况的数据流是非常有效的,执行速度最快。尽管我们讨论的多 数处理逻辑用于覆盖所有可能发生的情况,但多数报文段都是正常的,只需很少的处理步骤。

习题 28.1 假定 N e t / 3中插口缓存最大等于 262 444,基于图 2 8 - 7的算法,得到的窗口缩放因子 是多少? 28.2 假定N e t / 3中插口缓存最大等于 262 444,如果往返时间等于 6 0 m s,可能的最大吞吐 量是多少? (提示:见卷 1的图24-5及带宽的解 ) 28.3 为什么图28-10中,调用 bcopy获取时间戳值? 28.4 我们在26.6节中提到, TCP要求的时间戳选项格式与 RFC 1323附录A中定义的不同。 尽管TCP能够正确处理时间戳,但由于采用与标准不同的格式,会付出什么代价? 28.5 处理 P R U _ A T T A C H 请求时会分配 P C B 和 T C P 控制块,为什么不接着调用 tcp_template分配首部模板?而是直至收到了SYN,在图28-17中才进行这一操作。 28.6 阅读RFC 1323,理解为什么图 28-22中选取24天做为空闲时间的界限? 28.7 在图 2 8 - 2 2中,如果连接空闲时间超过 2 4天, t c p _ n o w - t s _ r e c e n t _ a g e 与 T C P _ P A W S _ I D L E的比较,会出现符号位回绕的问题。 N e t / 3中采取5 0 0 m s做为时间 戳单位,会在什么时间出现问题? 28.8 阅读RFC 1323,回答为什么图 28-22中PAWS测试不包括 RST报文段? 28.9 客户发送了 SYN,服务器响应SYN/ACK。客户转移到ESTABLISHED状态,并发送 响应 A C K。但这个 A C K丢失,服务器重发 S Y N / A C K。描述一下客户收到重发的 SYN/ACK时的处理步骤。 28.10 客户和服务器已建立了连接,服务器主动关闭。连接正常终止,服务器上的插口 对转移到 TIME_WAIT状态。在服务器的 2 M S L定时器超时前,同一客户 (客户端的 同一个插口对 )向服务器发送 S Y N,但起始序号小于连接上最后收到的序号。会发 生什么?

第29章 TCP的输入(续) 29.1 引言 本章从前一章结束的地方开始,继续介绍 T C P输入处理。回想一下图 2 8 - 3 7中最后的测试 条件,如果 ACK未置位,输入报文段被丢弃。 本章处理 A C K标志,更新窗口信息,处理 U R G标志及报文段中携带的所有数据,最后处 理FIN标志,如果需要,则调用 tcp_output。

29.2 ACK处理概述 在本章中,我们首先讨论 A C K的处理,图 2 9 - 1给出了A C K处理的框架。 S Y N _ R C V D状态 需要特殊处理,紧跟着是其他状态的通用处理代码 (前一章已讨论过在 L I S T E N和S Y N _ S E N T 状态下收到 A C K 时的处理逻辑 ) 。接着是对 T C P S _ F I N _ WA I T _ 1 、 T C P S _ C L O S I N G 和 T C P S _ L A S T _ A C K状态的一些特殊处理,因为在这些状态下收到 A C K会导致状态的转移。此 外,在TIME_WAIT状态下收到 ACK还会导致 2MSL定时器的重启。

图29-1 ACK处理框架

774

计计TCP/IP详解 卷 2:实现

图29-1 (续)

29.3 完成被动打开和同时打开 图2 9 - 2给出了如何处理 S Y N _ R C V D状态下收到的 A C K报文段。如前一章中提到过的,这 也将完成被动打开 (一般情况),或者是同时打开及自连接 (特殊情况)的连接建立过程。 1. 验证收到的 ACK 801-806

如果收到的ACK确认了已发送的SYN,它必须大于snd_una (tcp_sendseqinit将

s n d _ u n a设定为连接的 I S S,S Y N报文段的序号 ),且小于等于 s n d _ m a x。如果条件满足,则 插口进入连接状态 ESTABLISHED。

图29-2 tcp_input 函数:在SYN_RCVD 状态收到 ACK

775

第 29章 TCP的输入(续)计计

在收到三次握手的最后一个报文段后,调用 s o i s c o n n e c t e d唤醒被动打开的应用进程 (一般为服务器 )。如果服务器在调用 a c c e p t上阻塞,则该调用现在返回。如果服务器调用 select等待连接可读,则连接现在已经可读。 2. 查看窗口大小选项 807-812

如果T C P曾发送窗口大小选项,并且收到了对方的窗口大小选项,则在 T C P控制

块中保存发送缩放因子和接收缩放因子。另外, T C P控制块中的 s n d _ s c a l e和r c v _ s c a l e 的默认值为 0 (无缩放)。 3. 向应用进程提交队列中的数据 813 现在可以向应用进程提交连接重组队列中的数据,调用 tcp_reass,第二个参数为空。 重组队列中的数据可能是 SYN报文段中携带的,它同时将连接状态变迁为 SYN_RCVD。 814 snd_wl1等于收到的序号减 1,从图29-15可知,这样将导致更新 3个窗口变量。

29.4 快速重传和快速恢复的算法 图2 9 - 3给出了 A C K处理的下一部分代码,处理重复 A C K,并决定是否起用 T C P的快速重 传和快速恢复算法 [Jacobson 1990c]。两个算法各自独立,但一般都在一起实现 [Floyd 1994]。 • 快速重传算法用于连续出现几次 (一般为 3次)重复A C K时,T C P认为某个报文段已丢失 并且从中推断出丢失报文段的起始序号,丢失报文段被重传。 RFC 11 2 2中的4 . 2 . 2 . 2 1节 提到了这一算法,建议 T C P收到乱序报文段后,立即发送 A C K。我们看到,在图 2 7 - 1 5 中,N e t / 3正是这样做的。这个算法最早出现在 4.3BSD Ta h o e版及后续的 N e t / 1实现中, 丢失报文段被重传之后,连接执行慢起动。 • 快速恢复算法认为采用快速重传算法之后 (即丢失报文段已重传 ),应执行拥塞避免算法, 而非慢起动。这样,如果拥塞不严重,还能保证较大的吞吐量,尤其窗口较大时。这个 算法最早出现在 4.3BSD Reno版和后续的 Net/2实现中。 Net/3同时实现了快速重传和快速恢复算法,下面将做简单介绍。 在图24-17节中,我们提到有效的 ACK必须满足下面的不等式: snd_una < 确认字段 max_rcvd = len;

但即使在Net/1中,变量max_rcvd也未被使用。 1112-1115

如果l e n等于0,且F I N标志未置位,或者连接上已收到了 F I N,则丢弃保存接

收报文段的 mbuf链,并清除 FIN。

29.10 FIN处理 tcp_input的下一步,在图 29-24中给出,处理FIN标志。

图29-24 tcp_input 函数:FIN处理,前半部分

1. 处理收到的第一个 FIN 1116-1125

如果接收报文段 F I N 置位,并且是连接上收到的第一个

F I N,则调用

s o c a n t r c v m o r e,把插口设为只读,置位 T F _ A C K N O W ,从而立即发送 A C K (无延迟 )。

792

计计TCP/IP详解 卷 2:实现

rcv_nxt加1,越过FIN占用的序号。 1126

FIN处理的其余部分是一个大的 switch语句,根据连接的状态进行转换。注意,连接

处于CLOSED、LISTEN和SYN_SENT状态时,不处理 FIN,因为处于这 3个状态时,还未收到 对端发送的 S Y N ,无法同步接收序号,也就无法验证 F I N序号的有效性。此外,连接处于 C L O S I N G、C L O S E _ WA I T和L A S T _ A C K状态时,也不处理 F I N,因为在这 3个状态下收到的 FIN必然是一个重复报文段。 2. SYN_RCVD和ESTABLISHED状态 1127-1134

如果连接处于 S Y N _ R C V D或E S TA B L I S H E D状态,收到 F I N后,新的状态为

CLOSE_WAIT。 尽管在 S Y N _ R C V D状态下收到 F I N是合法的,但却极为罕见。图 2 4 - 1 5的状态图 未列出这一状态变迁。它意味着处于 L I S T E N状态的插口收到一个同时带有 S Y N和 F I N的报文段。或者,正在监听的插口收到了 S Y N,连接转移到 S Y N _ R C V D状态, 但在收到 A C K之前,先收到了 F I N (从分析可知, F I N未携带有效的 A C K,否则,图 29-2中的代码会使连接转移到 ESTABLISHED状态)。 图29-25给出了FIN处理的下一部分。

图29-25 tcp_input 函数:FIN处理,后半部分

3. FIN_WAIT_1状态 1135-1141

因为报文段的ACK处理已结束,如果处理 FIN时,连接处于FIN_WAIT_1状态,

意味着连接两端同时关闭连接—两端发送的两个FIN在网络中交错。连接进入CLOSING状态。

793

第 29章 TCP的输入(续)计计

4. FIN_WAIT_2状态 1142-1148

收到FIN将使连接进入TIME_WAIT状态。当在 FIN_WAIT_1状态收到携带ACK

和F I N的报文段时 (典型情况 ),尽管图 2 4 - 1 5显示连接直接从 FIN_WAIT_1转移到 T I M E _ WA I T 状态,但在图 2 9 - 11中处理 A C K时,连接实际已进入 F I N _ WA I T _ 2状态。此处的 F I N处理再将 连接转到 TIME_WAIT状态。因为ACK在FIN之前处理,所以连接总会经过 FIN_WAIT_2状态, 尽管是暂时性的。 5. 启动TIME_WAIT定时器 1149-1152

关闭所有等待的 TCP定时器,并启动 TIME_WAIT定时器,时限等于 MSL(如果

接收报文段中包含 ACK和FIN,图29-11中的代码会启动 FIN_WAIT_2定时器)。插口断开连接。 6. TIME_WAIT状态 1153-1159

如果在TIME_WAIT状态时收到 F I N,说明这是一个重复报文段。与图 2 9 - 1 4中

的处理类似,启动 TIME_WAIT定时器,时限等于两倍的 MSL。

29.11 最后的处理 图2 9 - 2 6给出了 t c p _ i n p u t函数中首部预测失败时,较慢的执行路径中最后一部分的代 码,以及标注dropafterack。

图29-26 tcp_input 函数:最后的处理

1. SO_DEBUG 插口选项 1161-1162

如果选用了 S O _ D E B U G插口选项,则调用 t c p _ t r a c e向内核的环形缓存中添

加记录。回想一下,图 28-7中的代码同时保存了原有连接状态, IP和TCP的首部,因为函数有 可能改变这些值。 2. 调用tcp_output 1163-1168

如果needoutput标志置位(图29-6和图29-15),或者需要立即发送 ACK,则调

用tcp_output。

794

计计TCP/IP详解 卷 2:实现

3. dropafterack 1169-1179

只有当R S T标志未置位时,才会生成 A C K (带有R S T的报文段不会被确认 ),释

放保存接收报文段的 mbuf链,调用tcp_output立即发送ACK。 图29-27结束tcp_input函数。

图29-27 tcp_input 函数:最后的处理

4. dropwithreset 1180-1188

除了接收报文段也有RST,或者接收报文段是多播和广播报文段的情况之外,应发送

RST。绝不允许因为响应RST而发送新的RST,这将引起RST风暴(两个端点间连续不断地交换RST)。 此处的代码存在与图 2 8 - 1 6同样的错误:它不检查接收报文段的目的地址是否为 广播地址。 类似地,IN_MULTICAST的目的地址参数应转换为主机字节序。 5. RST报文段的序号和确认序号 1189-1196

RST报文段的序号字段值、确认字段值和 ACK标志取决于接收报文段中是否带

有ACK。 图29-28总结了生成 RST报文段中的这些字段。

795

第 29章 TCP的输入(续)计计

生成的RST报文段 接收到的报文段 带有ACK 不带ACK

序 号 值 接收到的确认字段 0

确认序号 0 接收到的序号字段

输出标志 TH_RST TH_RST|TH_ACK

图29-28 生成RST报文段各字段的值

正常情况下,除了起始的 S Y N (图2 4 - 1 6 ),所有报文段都带有 A C K。t c p _ r e s p o n d的第 四个参数是确认序号,第五个参数是序号。 6. 拒绝连接 1192-1193

如果SYN置位,则 ti_len必须加1,从而生成 RST的确认字段比收到的 SYN报

文段的起始序号大 1。如果到达的 S Y N请求与不存在的服务器建立连接,会执行这一段代码。 此时,由于图 2 8 - 6中的代码找不到请求的 Internet PCB,控制跳转到 d r o p w i t h r e s e t。但为 了使发送的 R S T能被对端接受,报文段必须确认 S Y N (图2 8 - 1 8 )。卷1的1 8 . 1 4节举例说明了这 种类型的RST。 最后请注意,t c p _ r e s p o n d利用保存接收报文段的第一个 mbuf构造RST,并且释放链上 的其他mbuf。当第一个 mbuf最终到达设备驱动程序后,它也会被丢弃。 7. 释放临时创建的插口 1197-1199

如果在图28-7中为监听的服务器创建了临时的插口,但图 28-16中的代码发现接

收报文段有错误,它会置位 drop socket。如果出现了这种情况,插口在此处被释放。 8. 丢弃(不带ACK或RST) 1201-1206

如果接收报文段被丢弃,且不生成 A C K或R S T,则调用 t c p _ t r a c e。如果

S O _ D E B U G置位且生成了 A C K,则t c p _ o u t p u t将向内核的环形缓存中添加一条跟踪记录。 如果SO_DEBUG置位且生成了RST,系统不会为RST添加新的跟踪记录。 1207-1211

释放保存接收报文段的 mbuf链。如果dropsocket非零,则释放临时创建的插

口。

29.12 实现求精 为了加速 T C P处理而进行的优化与 U D P类似( 2 3 . 1 2节)。应利用复制数据计算检验和,并 避免在处理中多次遍历数据。 [Dalton et al. 1993]讨论了这些修订。 连接数增加时,对 TCP PCB的线性搜索也是一个处理瓶颈。 [McKenney and Dove 1992]讨 论了这个问题,利用哈希表替代了线性搜索。 [Partridge 1993]介绍了Van Jacobson开发的一个用于研究目的的协议实现,极大地减少了 T C P的输入处理。接收数据分组首先由 I P进行处理 ( R I S C系统中约有 2 5条指令 ),之后由分用 器(demultiplexer)寻找PCB(约10条指令),最后由TCP处理(约30条指令)。这30条指令完成了首 部预测,并计算伪首部检验和。如果数据报文段通过了首部预测,且应用进程正等待接收数 据,则复制数据到应用进程缓存,计算 T C P检验和并完成验证 (一次遍历中完成数据复制和检 验和计算)。如果TCP首部预测失败,则执行 TCP输入处理中较慢的路径。

29.13 首部压缩 下面介绍 T C P首部压缩。尽管首部压缩不是 T C P输入处理的一部分,但需要彻底了解 T C P

796

计计TCP/IP详解 卷 2:实现

的工作机制后,才能很好地理解首部压缩。 RFC 1144[Jacobson 1994a]中详细定义了首部压缩, 因为Van Jacobson首先提出了这一算法,通常也称为 VJ 首部压缩。本节的目的不是详细讨论 首部压缩的源代码 (RFC 1144给出了实现代码,其中有很好的注释,程序量与 tcp_output差 不多),而是概括性地介绍一下算法的思想。请注意区分首部预测 (28.4节)和首部压缩。 29.13.1 引言 多数的S L I P和P P P实现支持首部压缩。尽管首部压缩,在理论上,适用于任何数据链路, 但主要还是面向慢速串行链路。首部压缩只处理 TCP报文段—与其他的IP协议无关 (如ICMP、 I G M P、U D P等等)。它能够把I P / T C P组合首部从正常的 4 0字节压缩到只有 3字节,从而降低了 交互性应用,如远程登录或 Te l n e t中T C P报文段的大小,从典型的 4 1字节减少到只剩 4字节 —大大提高了慢速串行链路的效率。 串行链路的两端,每端都维护着两个连接状态表,一个用于数据报的发送,另一个用于 数据报的接收。每张表最多保存 256条记录,但典型的只有 16条,即同一时间内最多允许 16条 不同的 T C P连接执行首部压缩算法。每条记录中保存一个 8 bit的连接I D (限制记录数最多只能 为256)、某些标志和最近接收 /发送的数据报的未被压缩的首部。 96 bit的插口对可惟一确定一 条连接—源端IP地址和TCP端口、目的IP地址和TCP端口—这些信息都保存在未压缩的首 部中。图29-29举例说明了这些表的结构。 因为T C P连接是全双工的,在两个方向的数据流上都可执行首部压缩算法。连接两端必 须同时实现压缩和解压缩。同一条连接在两端的表中都会出现,如图 2 9 - 2 9所示。在这个例子 上部的两张表中,连接 I D等于1的表项的源端 I P地址都等于 1 2 8 . 1 . 2 . 3,源端 T C P端口号都等于 1 5 0 0,目的 I P地址等于 1 9 2 . 3 . 4 . 5,目的 T C P端口号都等于 2 5。在底部的两张表中,连接 I D等 于2的记录保存了同一条连接反方向数据流的信息。 发送连接状态表 标号 标志

最近的IP/TCP首部

接收连接状态表 标号 标志

接收连接状态表 标号 标志

最近的IP/TCP首部

最近的IP/TCP首部

发送连接状态表 标号 标志

最近的IP/TCP首部

图29-29 链路(如SLIP链路)两端的一组连接状态表

797

第 29章 TCP的输入(续)计计

我们在图 2 9 - 2 9中利用数组表示这些表,但在源代码中,表项定义为一个结构,连接状态 表定义为这些结构组成的环形链表,最近一次用过的结构位于表头。 因为连接两端都保存了最近用过的未压缩的数据报首部,所以只需在链路上传送当前 数据报与前一数据报不同的字段 (及一个特殊的前导字节,指明后续的是哪一个字段 )。因 为某些首部字段在相邻的数据报之间不会变化,而其他的首部字段变化也很小,这种差分 处理是压缩算法的核心。首部压缩只适用于

I P 和T C P首部 — T C P报文段的数据部分不

变。 图29-30给出了发送方利用首部压缩算法,在串行链路上发送 IP数据报时采取的步骤。 待发送的 IP 数据报

检查数据报 其他

非T C P报文或不可

I P型

压缩的 T C P报文

TCP

在连接表中寻找匹 配的96-bit插口对

找到

在连接表相应记 录中保存未压缩 的I P / T C P首部

无法

未找到

使用表中最老的一 条记录

压缩

C O M P R E S S E D _ T C P型

压缩

在连接表记录中 保存未压缩的 I P / T C P首部

U N C O M P R E S S E D _ T C P型

图29-30 发送方采用首部压缩时的步骤

接收方必须能够识别下面 3种类型的数据报: 1) I P型数据报,前导字节的高位 4比特等于 4。这也是 I P首部中正常的 I P版本号 (图8 - 8 ), 说明链路上发送的是正常的、未压缩的数据报。 2) COMPRESSED_TCP型数据报,前导字节的最高位置为 1,类似于IP版本号介于 8和15之 间(剩余的 7 b i t由压缩算法使用 ),说明链路上发送的是压缩过的首部和未压缩的数据,接下来 我们还会谈到这种类型的数据报。 3) U N C O M P R E S S E D _ T C P型数据报,前导字节的高位 4比特等于 7,说明链路上发送的是 正常的、未压缩的数据报,但 I P的协议字段 (等于6,对T C P )被替换为连接 I D,接收方可据此 从连接状态表中找到正确的记录。 接收方查看数据报的第一个字节,即前导字节,确定其类型,实现代码参见图 5-13。图51 6中,发送方调用 s l _ c o m p r e s s _ t c p确认T C P报文段是可压缩的,函数返回值与数据报首 字节逻辑或后,结果依然保存在首字节中。 图2 9 - 3 1列出了链路上传送的前导字节,其中 4位“ -”表示正常的 I P首部长度字段。 7位 “C、I、P、S、A、W和E”指明后续的是哪些可选字段,后面会简单地介绍这些字母的含 义。

798

计计TCP/IP详解 卷 2:实现

4 bit 版本号

4 bit 首部长度

链路上传送 的首字节

图29-31 链路上传送的前导字节

图29-32给出了使用压缩算法之后,不同类型的完整的 IP数据报。 IP数据报的头4bit 协议=TCP

非TCP报文

0-65515字节的IP数据

20-60字节的IP首部

协议=TCP

TCP报文

20-60字节的TCP首部

0-65495字节的TCP数据

20-60字节的TCP首部

0-65495字节的TCP数据

20-60字节的IP首部

协议=连接 ID

0-65495字节的TCP数据 3-16字节 压缩的TCP 首部

图29-32 采用首部压缩后的不同类型的 IP数据报

图中给出了两个 I P型数据报:一个携带了非 T C P报文段(如U D P、I C M P或I G M P协议报文 段),另一个携带了 T C P报文段。这是为了说明做为 I P型数据报发送的 T C P 报文段与做为 U N C O M P R E S S E D _ T C P型数据报发送的 TCP报文段间的差异:前导字节的高位 4比特互不相同, 类似于IP首部的协议字段。 如果IP数据报的协议字段不等于 TCP,或者协议是 TCP,但下列条件之一为真,都不会采 用首部压缩算法。 • 数据报是一个IP分片:分片偏移量非零或者分片标志置位; • SYN 、FIN或RST中的任何一个置位; • ACK 标志未置位。 上述3个条件中只要有一个为真,都将作为 IP型数据报发送。 此外 , 即 使 数据 报 携 带 了可 压缩的 T C P 报 文段 , 压 缩 算法 也 可 能 失败 ,生成 U N C O M P R E S S E D _ T C P型的数据报。可能因为当前数据报与连接上发送的上一个数据报比较 时,有些特殊字段发生了变化,而正常情况下,对于给定的连接,它们应该不变,从而导致 压缩算法无法反映存在的变化。例如, TO S字段,分片标志位。此外,如果某些字段数值的

799

第 29章 TCP的输入(续)计计

差异超过65535,压缩算法也会失败。 29.13.2 首部字段的压缩 下面介绍如何压缩图 2 9 - 3 3中给出的 I P和T C P的首部字段,阴影字段指对于给定连接,正 常情况下不会发生变化的字段。 0

15 4-bit

4-bit

8-bit

版本号

首部长度

服务类型(TOS)

8-bit存活时间( TTL)

16-bit总长(字节) 3-bit

16-bit标识符

31

16

标志 8-bit协议

13-bit分片偏移量

16-bit首部检验和

20字节

32-bit源IP地址

32-bit目的IP地址

16-bit源端口号

16-bit目的端口号

32-bit序号 20字节

32-bit确认序号 4-bit首部

保留(6 bit)

长度

U A P P S F R C S S Y I G K H T N N

16-bit TCP检验和

16-bit窗口大小

16-bit紧急指针

图29-33 组合的IP和TCP首部:阴影字段通常不变化

如果连接上发送的前一个报文段与当前报文段之间,有阴影字段发生变化,则压缩算法 失败,报文段被直接发送。图中未列出 I P和T C P选项,但如果它们存在,且这些选项字段发 生了变化,则报文段也不压缩,而被直接发送 (习题29.7)。 如果阴影字段均未变化,即使算法只传输非阴影字段,也会节省 5 0 %的传输容量。 V J首 部压缩甚至做得更好,图 29-34给出了压缩后的 IP/TCP首部格式。 最小的压缩后的 IP/TCP首部只有 3个字节:第一个字节 (标志比特 ),加上16 bit的TCP检验 和。为了防止可能的链路错误,一般不改动 T C P检验和 ( S L I P不提供链路层的检验和,尽管 PPP提供一个)。

800

计计TCP/IP详解 卷 2:实现

标志位:链路上发送的第一个字节

字节数

如果C=1:连接ID

TCP检验和不变

16-bit TCP检验和,必选

如果U=1:TCP紧急数据偏移量 如果W=1:TCP窗口大小差值 如果A=1:TCP确认序号差值 如果S=1:TCP序号差值 如果I=1:IP标识符差值

数据不变

图29-34 压缩后的 IP/TCP首部格式

其他的6个字段: connid、u rg o f f、 win、 ack、 seq和 ipid,都是可选的。图 29-34的最 左侧列出了各字段压缩后所需的字节数。读者可能认为压缩后的首部最大应占用 1 9字节,但 实际上压缩后的首部中 4 bit的SAWU绝不可能同时置位,因此,压缩首部最大为 16字节,后面 我们还会详细讨论这个问题。 第一个字节的最高位比特必须设为 1,说明这是 C O M P R E S S E D _ T C P型的数据报。其余 7 bit中的6个规定了后续首部中存在哪些可选字段,图 29-35小结了这7 bit的用法。 标志比特 C I P S A W U





结构变量

连接ID IP标识符 TCP推标志 TCP序号 TCP确认序号 TCP窗口 TCP紧急数据偏移量

ip_id th_seq th_ack th_win th_urg

标志等于 0说明

标志等于1说明

连接ID不变 i p _ i d已加1 P S H标志清除 t h _ s e q不变 t h _ a c k不变 t h _ w i n不变 URG标志未置位

connid=连接ID i p i d=IP标识符差值 P S H标志置位 s e q=TCP序号差值 a c k=TCP确认序号差值 w i n=TCP窗口字段差值 u r g o f f=紧急数据偏移量

图29-35 压缩首部中的7个标志比特

C 如果C比特等于 0,则当前报文段与前一报文段 (无论是压缩的或非压缩的 )具有相同的 连接ID。如果等于 1,则connid将等于连接 ID,其值位于 0~255之间。 I 如果 I比特等于 0,当前报文段的 I P标识符较前一报文段加 1 (典型情况 )。如果等于 1, ipid等于ip_id的当前值减去它的前一个值。

801

第 29章 TCP的输入(续)计计

P 这个比特复制自 T C P报文段中的 P S H标志位。因为 P S H标志不同于其他的正常方式, 必须在每个报文段中明确地定义这一标志。 S 如果S比特等于 0,T C P序号不变。如果等于 1, s e q等于t h _ s e q的当前值减去它的前 一 个值。 A 如果A比特等于 0,T C P确认序号不变 (典型情况 )。如果等于 1,∆a c k等于t h _ a c k的当 前值减去它的前一个值。 W 如果W比特等于 0,T C P窗口大小不变。如果等于 1, w i n等于t h _ w i n的当前值减去 它的前一个值。 U 如果U比特等于 0,报文段的U R G标志未置位,紧急数据偏移量不变 (典型情况)。如果 等于1,说明URG标志置位,urgoff等于th_urg的当前值。如果URG标志未置位时, 紧急数据偏移量发生改变,报文段将被直接发送 (这种现象通常发生在紧急数据传送 完毕后的第一个报文段 )。 通过字段的当前值减去它的前一个值,得到需传输的差值。正常情况下,得到的是一个 小正数( win是个例外)。 请注意,图 29-34中有5个字段的长度可变,可占用 0、1或3字节。 0字节:对应标志未置位,此字段不存在; 1字节:发送值在 1~255之间,只需占用 1字节; 3字节:如果发送值等于 0或者在256~65535之间,则需要用 3个字节才能表示:第一个字节 全0,后两个字节保存实际值。这种方法一般用于 3个16 bit的值:u rg o f f、∆w i n和∆i p i d。但如 果两个32 bit字段 ack和∆seq的差值小于 0或者大于65535,报文段将被直接发送。 如果把图29-33中不带阴影的字段与图 29-34中可能的传输字段进行比较,会发现有些字段 永远不会被传输。 • IP总长度字段不会被传输,因为绝大多数链路层向接收方提供接收数据分组的长度。 • 因为I P首部中被传输的惟一字段是 16 bit的I P标识符, I P检验和被忽略。因为它只在一 段链路上保护IP首部,每次转发都会被重新计算。 29.13.3 特殊情况 算法检查输入报文段,如果出现两种特定情况,则用前导字节的低位

4比特 — S AW U

—的两种特殊组合,分别加以表示。因为紧急数据很少出现,如果报文段中 URG标志置位, 并且与前一报文段相比,序号与窗口字段都发生了变化 (意味着低位 4比特应为 1 0 11或1111 ), 此种报文段会跳过压缩算法,被直接发送。因此,如果低位

4比特等于 1 0 11 ( 称为 *S A)或

1111(称为*S),就说明出现了下面两种特定情况: *SA 序号与确认序号都增加,差值等于前一报文段的数据量,窗口大小与紧急数据偏移 量不变,URG标志未置位。采用这种表示法可以避免传送 seq和 ack。 如果对端回送终端数据,那么两个传输方向上的数据报文段中都会经常出现这一现 象。卷1的图19-3和图19-4,举例说明了远程登录应用中出现的这种类型的数据。 *S

序号增加,差值等于前一报文段的数据量,确认序号、窗口大小与紧急数据偏移量 均不变,URG标志未置位。采用这种表示法可以避免传送 ∆seq。 这种类型的数据通常出现在单向数据传输 (如F T P )的发送方。卷 1的图2 0 - 1、图2 0 - 2

802

计计TCP/IP详解 卷 2:实现

和图2 0 - 3举例说明了这种类型的数据传输。此外,如果对端不回送终端数据,那么 在数据发送方的数据报文段中也会出现这种现象。 29.13.4 实例 下面的两个例子,在图 1 - 1 7中的b s d i和s l i p两个系统间,利用 S L I P链路传输数据。这 条SLIP链路在两个传输方向上都采用了首部压缩算法。在主机 b s d i上运行t c p d u m p程序(卷1 的附录 A ),保存所有数据帧的备份。这个程序还支持一个选项,能够输出压缩后的首部,列 出图29-34中的所有字段。 在主机间已建立了两条连接:一条远程登录连接,另一条是从 b s d i到s l i p的文件传输 (FTP)。图29-36列出了两条连接上不同类型数据帧出现的次数。 帧 类 型 IP UNCOMPRESSED_TCP COMPRESSED_TCP 特殊情况*SA 特殊情况*S 一般情况 总数

远程登录

FTP

输入

输出

输入

输出

1 3

1 2

5 2

5 3

75 25 9

75 1 93

0 1 337

0 325 13

113

172

345

346

图29-36 远程登录和 FTP连接上,不同类型数据帧出现的次数

远程登录连接中,在两个传输方向上, *SA都出现了 75次,从而证明了在对端回显终端流 量时,这一特定情况在两个传输方向上都会经常出现。 F T P连接中,在数据的发送方, *S出 现了325次,也证明了对于单向数据传输,这一特定情况会经常出现在数据的发送方。 F T P连接中, I P型的数据帧出现了 1 0次,对应于 4个带有 S Y N的报文段,和 6个带有F I N的 报文段。FTP使用了两条连接:一条用于传输交互式命令,另一条用于文件传输。 U N C O M P R E S S E D _ T C P型数据帧一般对应于连接建立后的第一个报文段,即同步连接 I D 的报文段。这两个例子中还有少量的其他类型的报文段,主要用于服务类型设定 (Net/3中的远 程登录及FTP客户及服务器都是在连接建立后才设定 TOS字段)。 远程登录

FTP

字节数

输入

输出

输入

输出

3 4 5 6 7 8 9

102

44 94 12 6 13

2

4

250 78 2 5 1 1 1

总数

109

338

338

7

169

图29-37 压缩首部大小的分布

5 325 2

803

第 29章 TCP的输入(续)计计

图2 9 - 3 7给出了压缩首部大小的分布情况,后 4栏中压缩首部的平均大小为分别等于 3 . 1、 4 . 1、6 . 0和3 . 3字节,与原来的 4 0字节相比,大大提高了系统的传输效率,尤其对于交互式连 接,效果更加明显。 在FTP输入一栏中,压缩首部大小为 6字节的报文段有 325个,其中绝大多数只携带了值等 于2 5 6的 a c k字段,因为 2 5 6大于2 5 5,所以必须用 3个字节表示。 S L I P M T U等于2 9 6,因此, T C P采用了2 5 6的M S S。在F T P输出一栏中,压缩首部大小为 3字节的报文段有 2 5 0个,其中绝 大多数都代表 *S类的特定情况 (只有序号发生变化 ),差值等于2 5 6。但因为*S的序号差值默认 为前一报文段的数据量,所以只需传输前导字节和 TCP检验和。在FTP输出一栏中, 78个压缩 首部大小为 4字节的报文段也属于同一情况,只不过 IP标识符也发生了变化 (习题29.8)。 29.13.5 配置 对给定的 S L I P或P P P链路,首部压缩必须被选定后才能起作用。配置 S L I P链路接口时, 一般可设定两个标志:首部压缩标志和自动首部压缩标志。配置命令是 i f c o n f i g,分别带 选项l i n k 0和l i n k 2。正常情况下,由客户端 (拨号主机 )决定是否采用首部压缩算法,服务 器(客户通过拨号接入的主机或终端服务器 )只选择是否置位自动首部压缩标志。如果客户选用 了首部压缩算法,它的 T C P首先发送一个 U N C O M P R E S S E D _ T C P型的数据报,规定连接 I D。 如果服务器收到这个数据报,它也开始采用首部压缩算法 (服务器处于自动方式 );如果未收到 这个数据报,服务器绝不会在这条链路上采用首部压缩。 PPP允许在链路建立时,连接双方共同协商传输选项,其中的一个选项即是否支持首部压 缩算法。

29.14 小结 本章结束了我们对 T C P输入处理的详细介绍。首先介绍了如果连接在 S Y N _ R C V D状态时 收到了ACK,该如何处理,即如何完成被动打开、同时打开或自连接。 快速重传算法指 T C P在连续收到的重复 A C K数超过规定的门限值后,能够检测到丢失的 报文段并进行重发,即使重传定时器还未超时。 N e t / 3结合了快速重传算法与快速恢复算法, 执行拥塞避免算法而非慢起动,尽量保证发送方到接收方的数据流不中断。 A C K处理负责从插口的发送缓存中丢弃已确认的数据,并且在收到的 A C K会改变连接当 前状态时,对一些 TCP状态做特殊处理。 处理接收报文段的 U R G标志,如果置位,则通过 T C P紧急方式的处理,提取带外数据。 这一操作是非常复杂的,因为应用进程可以利用正常的数据流缓存,或者特殊的带外数据缓 存接收带外数据,而且 TCP收到URG时,紧急指针所指向的数据可能还未到达。 T C P输入处理结束时,会调用 T C P _ R E A S S,提取报文段中的数据放入插口的接收缓存或 重组队列,处理 F I N标志,并且在接收报文段需要响应时,调用 t c p _ o u t p u t输入响应报文 段。 T C P首部压缩是用于 S L I P和P P P链路的一种技术,能够把 I P和T C P首部长度从 4 0字节减少 到约为 3 ~ 6字节(典型情况 )。这是因为对于给定连接,相邻两个报文段之间,首部的多数字段 不会改变,即使有些字段的值发生了变化,其差值也很小,从而可以通过前导字节中的标志比 特,指明哪些字段发生了变化,在后续部分只传输这些字段的当前值与前一报文段间的差值。

804

计计TCP/IP详解 卷 2:实现

习题 29.1 客户与服务器建立连接,不考虑报文段丢失,哪一个应用进程,客户或服务器,首 先完成连接建立过程? 29.2 N e t / 3系统中,监听服务器收到了一个 S Y N,它同时携带了 5 0字节的数据。会发生 什么? 29.3 继续前一个习题,假定客户没有重传 5 0字节的数据,而是在对服务器 S Y N / A C K报 文段的确认中置位 FIN标志,会发生什么? 29.4 N e t / 3客户向服务器发送 S Y N,服务器响应 S Y N / A C K,其中还携带了 5 0字节的数据 和FIN标志。列出客户端 TCP的处理步骤。 29.5 卷1的图18-19和RFC 793的图14,都给出了出现同时关闭时,连接双方交换的 4个报 文段。但如果连接两端都是 N e t / 3系统,出现同时关闭时,或者一个 N e t / 3系统的自 连接关闭时,彼此将交换 6个报文段,而不是 4个,多余出两个报文段是因为连接两 端各自收到对端的 FIN后,将向对端重发 FIN。问题出在什么地方,如何解决? 29.6 RFC 793第7 2页建议,如果发送缓存中的数据已被对端确认,“应给用户一个确认, 指明缓存中已发送且被确认的数据 (例如,发送缓存返回时应带有‘ O K’响应 )”。 Net/3是否提供了这种机制? 29.7 RFC 1323中定义的选项对 TCP首部压缩有何影响? 29.8 Net/3对IP标识符字段的赋值方式,对 TCP首部压缩有何影响?

第30章 TCP的用户需求 30.1 引言 本章介绍 T C P的用户请求处理函数 t c p _ u s r r e g,它被协议的 p r _ u s r r e q函数调用,处 理各种与 T C P插口有关的系统调用。此外,还将介绍 t c p _ c t l o u t p u t ,应用进程调用 setsockopt设定TCP 插口选项时,会用到它。

30.2 tcp_usrreq函数 T C P的用户请求函数用于处理多种操作。图 3 0 - 1给出了 t c p _ u s r r e q函数的基本框架, 其中s w i t c h的语句体部分将在后续部分逐一展开。图 1 5 - 1 7中列出了函数的参数,其具体含 义取决于所处理的用户请求。

图30-1 tcp_usrreq 函数体

806

计计TCP/IP详解 卷 2:实现

图30-1 (续)

1. in_control处理ioctl请求 45-58

PRU_CONTROL请求来自于 ioctl系统调用,函数 in_control负责处理这一请求。

2. 控制信息无效 59-64

如果试图调用 s e n d m s g,为 TCP 插口配置控制信息,代码将释放 m b u f,并返回

EINVAL差错代码,声明这一操作无效。 65-66

函数接着执行splnet。这种做法极为保守,因为并非在所有情况下都需要锁定,只

是为了防止在 c a s e语句中单个地调用 s p l n e t。我们在图 2 3 - 1 5中曾提到,调用 s p l n e t设定 处理器的优先级,唯一的作用是阻止软中断执行 I P输入处理 (它会接着调用 t c p _ i n p u t),但 却无法阻止接口层接收输入数据分组并放入到 IP的输入队列中。 通过指向插口结构的指针,可得到指向

Internet PCB 的指针。只有在应用进程调用

socket系统调用,发出 PRU_ATTACH请求时,该指针才允许为空。 67-81

如果 i n p非空,当前连接状态将保存在 o s t a t e中,以备函数结束时可能会调用

tcp_trace。 下面我们开始讨论单独的 c a s e语句。应用进程调用 s o c k e t系统调用,或者监听服务器 收到连接请求 (图2 8 - 7 ),调用 s o n e w c o n n函数时,都会发出 P R U _ A T T A C H请求,图 3 0 - 2给出 了这一请求的处理代码。

图30-2 tcp_usrreq 函数:PRU_ATTACH 和PRU_DETACH 请求

807

第 30 章 TCP的用户需求 计计

图30-2 (续)

3. PRU_ATTACH请求 83-94

如果插口结构已经指向某个 PCB,则返回EISCONN差错代码。调用 t c p _ a t t a c h完

成处理:分配并初始化 Internet PCB和TCP控制块。 如 果 选 用 了 S O _ L I N G E R 插 口 选 项 , 且 拖 延 时 间 为 0, 则 将 其 设 为 1 2 0

95-96

(TCP_LINGERTIME)。 为什么在 P R U _ A T T A C H请求发出之前,就可以设定插口选项?尽管不可能在调 用s o c k e t之前就设定插口选项,但 s o n e w c o n n也会发送 P R U _ A T T A C H请求。它在 把监听插口的 s o _ o p t i o n s复制到新建插口之后,才会发送 P R U _ A T T A C H请求。此 处的代码防止新建连接从监听插口中继承拖延时间为 0的SO_LINGER选项。 请注意,此处的代码有错误。常量 T C P _ L I N G E R T I M E在t c p _ t i m e r.h中初始 化为120,该行的注释为“最多等待 2分钟”。但S O _ L I N G E R值也是内核 t s l e e p函数 (由s o c l o s e调用)的最后一个参数,从而成为内核的 t i m e o u t函数的最后一个参数, 单位为滴答,而非秒。如果系统的滴答频率 ( h z )等于1 0 0,则拖延时间将变为 1 . 2秒, 而非2分钟。 97

现在,t p已指向插口的 T C P控制块。这样,如果选定了 S O _ D E B U G插口选项,函数结束

时就可以输出所需信息。 4. PRU_DETACH请求 99-111

c l o s e系统调用在 P R U _ D I S C O N N E C T请求失败后,将发送 P R U _ D E T A C H请求。如

果连接尚未建立 (连接状态小于 ESTABLISHED),则无需向对端发送任何信息。但如果连接已 建立,则调用 t c p _ d i s c o n n e c t初始化 T C P的连接关闭过程 (发送所有缓存中的数据,之后 发送FIN)。 代码if语句的测试条件要求状态大于LISTEN,这是不正确的。因为如果连接状态 等于SYN_SENT或者SYN_RCVD,两者都大于LISTEN,此时tcp_disconnect会直 接调用tcp_close。实际上,这个case语句可以简化为直接调用tcp_disconnect。

808

计计TCP/IP详解 卷 2:实现

图30-3给出了bind和listen系统调用的处理代码。

图30-3 tcp_usrreq 函数:PRU_BIND 和PRU_LISTEN 请求

112-119 PRU_BIND请求的处理只是简单地调用 in_pcbbind。 120-128

对于 P R U _ L I S T E N 请求,如果插口还未绑定在某个本地端口上,则调用

i n _ p c b b i n d自动为其分配一个。这种情况十分少见,因为多数服务器会明确地绑定一个知 名端口,尽管 R P C (远端过程调用 )服务器一般是绑定在一个临时端口上,并通过 Port Mapper 向系统注册该端口 (卷1的2 9 . 4节介绍了 Port Mapper)。连接状态变迁到 L I S T E N,完成了 l i s t e n 调用的主要目的:设定插口的状态,以便接受到达的连接请求 (被动打开)。 图30-4给出了connect系统调用的处理代码:客户发起的主动打开。

图30-4 tcp_usrreq 函数:PRU_CONNECT 请求

809

第 30 章 TCP的用户需求 计计

图30-4 (续)

5. 分配临时端口 129-141

如果插口还未绑定在某个本地端口上,调用 i p _ p c b b i n d自动为其分配一个。对

于客户端,这是很常见的,因为客户一般不关心本地端口值。 6. 连接PCB 142-144

调用i n _ p c b c o n n e c t,获取到达目的地的路由,确定外出接口,验证插口对不

重复。 7. 初始化IP和TCP首部 145-150

调用tcp_template分配mbuf,保存IP和TCP的首部,并初始化两个首部,填入

尽可能多的信息。会造成函数失败的唯一原因是内核耗尽了 mbuf。 8. 计算窗口缩放因子 151-154

计算用于接收缓存的窗口缩放因子:左移 65535(TCP_MAXWIN),直到它大于或等

于接收缓存的大小 (s o _ r c v . s b _ h i w a t)。得到的位移次数 (0~14之间),就是需要在SYN中发 送的缩放因子值 (图2 8 - 7处理被动打开时,有相同的代码 )。应用进程必须在调用 c o n n e c t之 前,设定 S O _ R C V B U F 插口选项, T C P才会在 S Y N中添加窗口大小选项,否则,将使用接收 缓存大小的默认值 (图24-3中的tcp_recvspace)。 9. 设定插口和连接的状态 155-158

调用soisconnecting,置位插口状态变量中恰当的比特,设定 TCP连接状态为

SYN_SENT,从而在后续的tcp_output调用中发送SYN(参见图24-16的tcp_outlags值)。连接 建立定时器启动,时限初始化为75秒。tcp_output还会启动SYN的重传定时器,如图25-16所示。 10. 初始化序号 159-161

令 初 始 序 号 等 于 全 局 变 量 t c p _ i s s , 之 后 令 t c p _ i s s 增 加 64 000

(T C P _ I S S I N C R除以2 )。在监听服务器收到 S Y N并初始化 I S S时(图2 8 - 1 7 ),对t c p _ i s s的相 同的操作。接着调用 tcp_sendseqinit初始化发送序号。 11. 发送初始SYN 162 调用tcp_output发送初始SYN,以建立连接。如果tcp_output返回错误(例如,mbuf 耗尽或没有到达目的地的路由),该差错代码将成为 tcp_usrreq的返回值,报告给应用进程。

810

计计TCP/IP详解 卷 2:实现

图30-5给出了PRU_CONNECT2、PRU_DISCONNECT和PRU_ACCEPT请求的处理代码。 164-169 PRU_CONNECT2请求,来自于socketpair系统调用,对TCP协议无效。 170-183

c l o s e系统调用会发送 P R U _ D I S C O N N E C T请求。如果连接已建立,应调用

tcp_disconnect,发送FIN,执行正常的TCP关闭操作。

图30-5 tcp_usrreq 函数:PRU_CONNECT2

、PRU_DISCONNECT

和PRU_ACCEPT 请求

请注意以“应该实现”起头的注释,这是因为无法接着使用出现错误的插口。 例如,客户调用 c o n n e c t,并得到一个错误,它就无法在同一个插口上再次调用 c o n n e c t,而必须首先关闭插口,调用 s o c k e t创建新的插口,在新的插口上才能 再次调用connect。 184-191

与a c c e p t系统调用有关的工作全部由插口层和协议层完成。 P R U _ A C C E P T请求

只简单地向应用进程返回对端的 IP地址和端口号。 图30-6给出了PRU_SHUTDOWN、PRU_RCVD和PRU_SEND请求的处理代码。 12. PRU_SHUTDOWN请求 192-200

应用进程调用 s h u t d o w n ,禁止更多的输出时,

s o s h u t d o w n 会发送

P R U _ S H U T D O W N请求。调用 s o c a n t s e n d m o r e置位插口的标志,禁止继续发送报文段。接 着调用 t c p _ u s r c l o s e d,根据图 24-15的状态变迁图,设定正确的连接状态。 t c p _ o u t p u t 发送FIN之前,如果发送缓存中仍有数据,会首先发送等待数据。

811

第 30 章 TCP的用户需求 计计

图30-6 tcp_usrreq 函数:PRU_SHUTDOWN

、PRU_RCVD 和PRU_SEND 请求

13. PRU_RCVD请求 201-206

应用进程从插口的接收缓存中读取数据后, soreceive会发送这个请求。此时接

收缓存已扩大,也许会有足够的空间,让 T C P发送更大的窗口通告。 t c p _ o u t p u t会决定是 否需要发送窗口更新报文段。 14. PRU_SEND请求 207-214

图2 3 - 1 4中给出的 5个写函数,都以这一请求结束。调用 s b a p p e n d,向插口的发

送缓存中添加数据 (它将一直保存在缓存中,直到被确认 ),并调用 t c p _ o u t p u t发送新报文 段(如果条件允许)。 图30-7给出了PRU_ABORT和PRU_SENSE请求的处理代码。

图30-7 tcp_usrreq 函数:PRU_ABORT 和PRU_SENSE 请求

15. PRU_ABORT请求 215-220

如果插口是监听插口 (如服务器 ),并且存在等待建立的连接,例如已发送初始

812

计计TCP/IP详解 卷 2:实现

S Y N或已完成三次握手过程,但还未被服务器 a c c e p t的连接,调用 s o c l o s e会导致发送 PRU_ABORT请求。如果连接已同步, tcp_drop将发送RST。 16. PRU_SENSE请求 221-224

f s t a t系统调用会生成 P R U _ S E N S E请求。 T C P返回发送缓存的大小,保存在

stat结构的成员变量 st_blksize中。 图3 0 - 8给出了 P R U _ R C V O O B的处理代码。当应用进程置位 M S G _ O O B标志,试图读取带外 数据时,soreceive会发送这一请求。

图30-8 tcp_usrreq 函数:PRU_RCVOOB 请求

17. 能否读取带外数据 225-232 如果下列3个条件有一个为真,应用进程读取带外数据的努力就会失败。 1) 如果插口的带外数据分界点 (s o _ o o b m a r k)等于0,并且插口的 S S _ R C V A T M A R K标志 未设定;或者 2) 如果SO_OOBINLINE插口选项设定;或者 3) 如果连接的 TCPOOB_HADDATA标志设定(例如,连接的带外数据已被读取 )。 如果上述3个条件中任何一个为真,则返回差错代码 EINVAL。 18. 是否有带外数据到达 233-236 如果上述3个条件全假,但 TCPOOB_HAVEDATA标志置位,说明尽管 TCP已收到了 对端发送的紧急方式通告,但尚未收到序号等于紧急指针减 1的字节(图29-17),此时返回差错 代码E W O U L D B L O C K,有可能因为发送方发送紧急数据通告时,紧急数据偏移量指向了尚未 发送的字节。卷 1的图2 6 - 7举例说明了这种情况,发送方的数据传输被对端的零窗口通告停止 时,常出现这种现象。 19. 返回带外数据字节 237-238 tcp_pulloutofband向应用进程返回存储在 t_iobc中的一个字节的带外数据。 20. 更新标志 239-241 如果应用进程已读取了带外数据(而不是仅大致了解带外数据的情况, MSG_PEEK标 志置位),TCP清除HAVE标志,并置位 HAD标志。case语句执行到此处时,通过前面的代码可

813

第 30 章 TCP的用户需求 计计

以肯定,HAVE标志已置位,而 HAD标志被清除。置位HAD标志的目的是防止应用进程试图再次 读取带外数据。一旦HAD标志置位,在收到新的紧急指针之前,它不会被清除 (图29-17)。 代码使用了让人费解的异或运算,而不是简单的 tp->t_oobflags = TCPOOB_HADDATA;

是为了能够在 t _ o o b f l a g s中定义更多的比特。但 N e t / 3中,实际只用到了上面 提及的两个标志比特。 图30-9中的P R U _ S E N D O O B请求,是在应用进程写入数据并置位 M S G _ O O B时,由s o s e n d 发送的。

图30-9 tcp_usrreq 函数:PRU_SENDOOB 请求

21. 确认发送缓存中有足够空间并添加新数据 242-247

发送带外数据时,允许应用进程写入数据后,待发送数据量超过发送缓存大小,

超出量最多为 5 1 2字节。插口层的限制要宽松一些,写入带外数据后,最多可超出发送缓存 1024字节(图16-24)。调用sbappend向发送缓存末端添加数据。 22. 计算紧急指针 248-257

紧急指针 (s n d _ u p)指向写入的最后一个字节之后的字节。图 2 6 - 3 0举例说明了这

一点,假定发送缓存为空,应用进程写入 3字节的数据,且置位了 M S G _ O O B标志。这是考虑 到若应用进程置位 M S G _ O O B标志,且写入的数据量超过 1字节,如果接收方为伯克利系统, 则只有最后一个字节会被认为是带外数据。 23. 强制TCP输出 258-261

令t _ f o r c e等于1,并调用 t c p _ o u t p u t。即使收到了对端的零窗口通告, T C P

也会发送报文段, U R G标志置位,紧急指针偏移量非零。卷 1的图2 6 - 7说明了如何向一个关闭 的接收窗口发送紧急报文段。 图30-10给出了最后 3个请求的处理。

814

计计TCP/IP详解 卷 2:实现

图30-10 tcp_usrreq 函数:PRU_SOCKADDR

262-267

、PRU_PEERADDR

和PRU_SLOWTIMO

请求

g e t s o c k n a m e 和g e t p e e r n a m e 系统调用分别发送 P R U _ S O C K A D D R 和

P R U _ P E E R A D D R请求。调用i n _ s e t s o c k a d d r和i n _ s e t p e e r a d d r函数,从 PCB中获取需 要信息,存储在 addr参数中。 268-275

执行 t c p _ s l o w t i m o函数会发送 P R U _ S L O W T I M O函数。如同注释所指出的,

t c p _ s l o w t i m o 不直接调用 t c p _ t i m e r s 的唯一原因是为了能够在函数结尾处调用 t c p _ t r a c e,跟踪记录定时器超时事件 (图3 0 - 1 )。为了在记录中指明是 4个T C P定时器中的哪 一个超时,t c p _ s l o w t i m o通过n a m参数传递了 t _ t i m e r数组(图25-1)的指针,并左移 8位后 与请求值(req)逻辑或。trpt程序了解这种做法,并据此完成相应的处理。

30.3 tcp_attach函数 t c p _ a t t a c h函数,在处理P R U _ A T T A C H请求(例如,插口系统调用,或者监听插口上收 到了新的连接请求 )时,由tcp_usrreq调用。图30-11给出了它的代码。 1. 为发送缓存和接收缓存分配资源 361-372 如果还未给插口的发送和接收缓存分配空间, sbreserve将两者都设为8192,即 全局变量 tcp_sendspace和tcp_recvspace的默认值(图24-3)。 这些默认值是否够用,取决于连接两个传输方向上的 MSS,后者又取决于 MTU。 例如,[Comer and lin 1994]论证了,如果发送缓存小于 3倍的M S S,则会出现异常, 严重降低系统性能。某些实现定义的默认值很大,如 61 444字节,已考虑到这些默认 值对性能的影响,尤其对较大的 MTU(如FDDI和ATM)更是如此。 2. 分配Internet PCB和TCP控制块 373-377

in_pcballoc分配Internet PCB,而tcp_newtcpcb分配TCP控制块,并将其与

对应的PCB相连。 3 7 8 - 3 8 4 如果t c p _ n e w t c p c b调用m a l l o c时失败,则执行注释为“ XXX”的代码。前面 已介绍过, P R U _ A T T A C H请求是插口系统调用或监听插口收到新的连接请求 (s o n e w c o n n)的 结果。对于后一种情况,插口标志 S S _ N O F D R E F置位。如果此标志置位, i n _ p c b a l l o c调 用s o f r e e时会释放插口结构。但我们在 t c p _ i n p u t中看到,除非该函数已完成接收报文段

815

第 30 章 TCP的用户需求 计计

的处理 ( 图 2 9 - 2 7 中的 d r o p s o c k e t 标志 ) ,否则,不应释放插口结构。因此,调用 i n _ p c b d e t a c h 时,应将 S S _ N O F D R E F 标志的当前值保存在变量 n o f d 中,并在 tcp_attach返回前重设该标志。 385-386 TCP连接状态初始化为 CLOSED。

图30-11 tcp_attach 函数:创建新的 TCP插口

30.4 tcp_disconnect函数 图30-12给出的tcp_disconnect函数,准备断开 TCP连接。 1. 连接未同步 396-402

如果连接还未进入 ESTABLISHED状态(如L I S T E N、S Y N _ S E N T或S Y N _ R C V D ),

tcp_close只释放PCB和TCP控制块。无需向对端发送任何报文段,因为连接尚未同步。 2. 硬性断开 403-404

如果连接已同步,且 S O _ L I N G E R插口选项置位,拖延时间 (S O _ L I N G E R)设为零,

则调用 t c p _ d r o p丢弃连接。连接不经过 T I M E _ W A I T,直接更新为 C L O S E D,向对端发送 R S T,释放P C B和T C P控制块。调用 c l o s e会发送P R U _ D I S C O N N E C T请求,丢弃仍在发送或 接收缓存中的任何数据。 如果SO_LINGER插口选项置位,且拖延时间非零,则调用 soclose进行处理。 3. 平滑断开 405-406

如果连接已同步,且 S O _ L I N G E R选项未设定,或者选项设定且拖延时间不为零,

816

计计TCP/IP详解 卷 2:实现

则执行TCP正常的连接终止步骤。 soisdisconnecting设定插口状态。

图30-12 tcp_disconnect

函数:准备断开 TCP连接

4. 丢弃滞留的接收数据 407

调用s b f l u s h,丢弃所有滞留在接收缓存中的数据,因为应用进程已关闭了插口。发

送缓存中的数据仍保留, t c p _ o u t p u t将试图发送剩余的数据。我们说“试图”,因为不能保 证数据还能成功地被发送。在收到并确认这些数据之前,对端可能已崩溃,即使对端的 T C P 模块能够接收并确认这些数据,在应用程序读取数据之前,系统也可能崩溃。因为本地进程 已关闭了插口,即使 TCP放弃发送仍滞留在发送缓存中的数据 (因为重传定时器最终超时 ),也 无法向应用进程通告错误。 5. 改变连接状态 408-410

t c p _ u s r c l o s e d基于连接的当前状态,促使其进入下一状态。通常情况下,连

接将转移到 FIN_WAIT_1状态,因为连接关闭时一般都处于 ESTABLIDHED状态。后面会看到, t c p _ u s r c l o s e d通常返回当前控制块的指针 (t p)。因为状态必须先同步才会执行此处的代 码,所以总需要调用 t c p _ o u t p u t 发送报文段。如果连接从 E S TA B L I S H E D 转移到 FIN_WAIT_2,将发送 FIN。

30.5 tcp_usrclosed函数 图30-13给出的这个函数,在 PRU_SHUTDOWN处理中,由 tcp_disconnect调用。 1. 未收到SYN时的简单关闭 429-434

如果连接上还未收到 S Y N ,则无需发送 F I N 。新的状态等于 C L O S E D,

tcp_close将释放Internet PCB和TCP控制块。 2. 转移到FIN_WAIT_1状态 435-438

如果连接当前状态等于 S Y N _ R C V D 和 E S TA B L I S H E D,新的状态将等于

FIN_WAIT_1,再次调用 tcp_output时,将发送 FIN(图24-16中的tcp_outflags值)。 3. 转移到LAST_ACK状态

817

第 30 章 TCP的用户需求 计计

439-441

如果连接当前状态等于 C L O S E _ WA I T,新状态等于 L A S T _ A C K,则再次调用

tcp_output时,将发送 FIN。 443-444

如果连接当前状态等于 FIN_WAIT_2或TIME_WAIT,s o i s d i s c o n n e c t e d将正

确地标注插口的状态。

图30-13 tcp_usrclosed

函数:基于连接关闭的处理进程,将连接转移到下一状态

30.6 tcp_ctloutput函数 t c p _ c t l o u t p u t函数被 g e t s o c k o p t和s e t s o c k o p t函数调用,如果它们的描述符参 数指明了一个 TCP插口,且level不是SOL_SOCKET。图30-14列出了TCP支持的两个插口选项。 选 项 名





TCP_NODELAY TCP_MAXSEG

t_flags t_maxseg





读、写 读、写



Nagel算法(图26-8) TCP将发送的最大报文段长度

图30-14 TCP支持的插口选项

图30-15给出了函数的第一部分。

图30-15 tcp_ctloutput



函数:第一部分

818

计计TCP/IP详解 卷 2:实现

图30-15 (续)

296-303

函数执行时,处理器优先级设为 splnet,inp指向插口的 Internet PCB。如果inp

为空,且操作类型是设定插口选项,则释放 mbuf并返回错误。 304-308

如果 l e v e l(g e t s o c k o p t 和 s e t s o c k o p t 系统调用的第二个参数 )不等于

I P P R O T O _ T C P,说明操作的是其他协议 (如I P )。例如,可以创建一个 T C P插口,并设定其 I P 源选路插口选项。此时应由 IP处理这个插口选项,而不是 TCP。ip_ctloutput处理命令。 309 如果是对TCP选项进行操作, tp将指向TCP控制块。 函数的剩余部分是一个 s w i t c h语句,有两个分支:一个处理 P R C O _ S E T O P T(图3 0 - 1 6中 给出),另一个处理PRCO_GETOPT(图30-17中给出)。

图30-16 tcp_ctloutput

函数:设定插口选项

819

第 30 章 TCP的用户需求 计计

图30-16 (续)

315-316

m是一个 m b u f,保存了 s e t s o c k o p t的第四个参数。对于两个 T C P插口选项,

m b u f中都必须是整数。如果任何一个 m b u f指针为空,或者 m b u f中的数据长度小于整数大小, 则返回错误。 1. TCP_NODELAY选项 317-321

如果整数值非零,则置位 TF_NODELAY标志,从而取消图 26-8中的Negal算法。如

果整数值等于0,则使用Negal算法(默认值),并清除TF_NODELAY标志。 2. TCP_MAXSEG选项 322-327 应用进程只能减少MSS。TCP插口创建时,tcp_newtcpcb初始化t_maxseg为默 认值5 1 2。当收到对端 S Y N中包含的 M S S选项时, t c p _ i n p u t调用t c p _ m s s,t _ m a x s e g最 高可等于外出接口的 M T U (减去 4 0字节, I P和T C P首部的默认值 ),以太网等于 1 4 6 0。因此, 调用插口之后,连接建立之前,应用进程只能以默认值 5 1 2为起点,减少 M S S。连接建立后, 应用进程可以从 tcp_mss选取的任何值起,减少 MSS。 4 . 4 B S D是伯克利版本中第一次支持 M S S做为插口选项,以前的版本只允许利用 getsockopt读取MSS值。 3. 释放mbuf 332-333 释放mbuf链。 图30-17给出了PRCO_GETOPT命令的处理。

图30-17 tcp_ctloutput

函数:读取插口选项

820

计计TCP/IP详解 卷 2:实现

335-337

两个T C P插口选项都向应用进程返回一个整数值,因此,调用 m _ g e t得到一个

mbuf,其长度等于整数长度。 339-341

T C P _ N O D E L A Y返回T F _ N O D E L A Y标志的当前状态:如果标志未置位 (使用N a g e l

算法),则等于0;如果标志置位,则等于 TF_NODELAY。 342-344

T C P _ M A X S E G选项返回 t _ m a x s e g的当前值。前面讨论 P R C O _ S E T O P T命令时曾

提到,返回值取决于插口是否已进入连接状态。

30.7 小结 t c p _ u s r r e q函数处理逻辑很简单,因为绝大多数处理都由其他函数完成。 P R U _x x x请 求是独立于协议的系统调用与 TCP协议处理间的桥梁。 t c p _ c t l s o c k o p t函数也很简单,因为 T C P只支持两个插口选项:使用或取消 N a g e l算 法,设置或读取最大报文段长度。

习题 30.1 现在,我们已经结束了对 T C P 的讨论,如果某个客户执行了正常的 s o c k e t 、 c o n n e c t、 w r i t e (向服务器请求 )和r e a d (读取服务器响应 ),分别列出客户端和 服务器端的处理步骤及 TCP状态变迁。 30.2 如果应用进程设定 S O _ L I N G E R插口选项,且拖延时间等于 0,之后调用c l o s e,我 们给出了如何调用 t c p _ d i s c o n n e c t,从而发送 R S T。如果应用进程设定了这个 插口选项,且拖延时间等于 0,之后进程被某个信号杀死 ( k i l l ),而非调用 c l o s e, 会发生什么?还会发送 RST报文段吗? 30.3 图25-4中描述T C P _ L I N G E R T I M E时,称之为“ S O _ L I N G E R插口选项的最大秒数”。 根据图30-2中的代码,这个说法正确吗? 30.4 某个 N e t / 3客户调用 s o c k e t和c o n n e c t,主动与服务器建立连接,使用了客户的 默认路由。客户主机向服务器发送了 1 129个报文段。假定到达目的地的路由未变, 为了这条连接,客户主机需要搜索多少次路由表?解释你的结论。 30.5 找到卷 1的附录 C中提到的 s o c k程序。把该程序做为服务器运行,读取数据前有停 顿(-p),且有较大的接收缓存。之后在另一个系统中运行同一个程序,但做为客户。 通过t c p d u m p查看数据。确认 TCP“确认所有其他报文段”的属性未出现,服务器 送出的ACK全部是延迟 ACK。 30.6 修改SO_KEEPALIVE插口选项,从而能够配置每个连接的参数。 30.7 阅读RFC 1122,了解为什么它建议 TCP应该允许RST报文段携带数据。修改 Net/3代 码以实现此功能。

第31章 BPF:BSD 分组过滤程序 31.1 引言 B S D分组过滤程序( B P F)是一种软件设备,用于过滤网络接口的数据流,即给网络接口加 上“开关”。应用进程打开 / d e v / b p f 0、/ d e v / b p f 1等等后,可以读取 B P F设备。每个应用 进程一次只能打开一个 BPF设备。 因为每个 B P F设备需要 8 1 9 2字节的缓存,系统管理员一般限制 B P F设备的数目。 如果o p e n返回E B U S Y,说明该设备已被使用,应用进程应该试着打开下一 BPF设备, 直到open成功为止。 通过若干 i o c t l命令,可以配置 B P F设备,把它与某个网络接口相关连,并安装过滤程 序,从而能够选择性地接收输入的分组。 B P F设备打开后,应用进程通过读写设备来接收分 组,或将分组放入网络接口队列中。 我们将一直使用“分组”,尽管“帧”可能更准确一些,因为 B P F工作在数据链 路层,在发送和接收的数据帧中包含了链路层的首部。 B P F设备工作的前提是网络接口必须能够支持 B P F。第3章中提到以太网、 S L I P和环回接 口的驱动程序都调用了 b p f a t t a c h,用于配置读取 B P F设备的接口。本节中,我们将介绍 BPF设备驱动程序是如何组织的,以及数据分组在驱动程序和网络接口之间是如何传递的。 BPF一般情况下用作诊断工具,查看某个本地网络上的流量,卷 1附录A 介绍的t c p d u m p 程序是此类工具中最好的一个。通常情况下,用户感兴趣的是一组指定主机间交互的分组, 或者某个特定协议,甚至某个特定 TCP连接上的数据流。 BPF设备经过适当配置,能够根据过 滤程序的定义丢弃或接受输入的分组。过滤程序的定义类似于伪机器指令, B P F的细节超出 了本书的讨论范围,感兴趣的读者请参阅 bpf(4)和[McCanne and Jacobson 1993]。

31.2 代码介绍 下面将要介绍的有关 B P F设备驱动程序的代码,包括两个头文件和一个 C文件,在图 3 1 - 1 中给出。 文



net/bpf.h net/bpfdesc.h net/bpf.c





BPF常量 BPF结构 BPF设备支持

图31-1 本章讨论的文件

31.2.1 全局变量 本章用到的全局变量在图 31-2中给出。

822

计计TCP/IP详解 卷 2:实现





数 据 类 型

bpf_iflist bpf_dtab bpf_bufsize





支持BPF的接口组成的链表 BPF描述符数组 BPF缓存大小默认值

struct bpf_if * struct bpf_d [] int

图31-2 本章用到的全局变量

31.2.2 统计量 图31-3列出了bpf_d结构中为每个活动的 BPF设备维护的两个统计量。 描

bpf_d成员变量



从网络接口接收的分组的数目 由于缓存空间不足而丢弃的分组的数目

bd_rcount bd_dcount

图31-3 本章讨论的统计值

本章的其余内容分为 4个部分: • BPF 接口结构; • BPF 设备描述符; • BPF 输入处理;和 • BPF 输出处理。

31.3 bpf_if结构 BPF维护一个链表,包括所有支持 BPF的网络接口。每个接口都由一个 bpf_if结构描述, 全局指针bpf_iflist指向表中的第一个结构。图 31-4给出了BPF接口结构。

图31-4 bpf_if 结构

67-79

b i f _ n e x t指向链表中的下一个 B P F接口结构。b i f _ d l i s t指向另一个链表,包括

所有已打开并配置过的 BPF设备。 70 如果某个网络接口已配置了 BPF设备,即被加上了开关,则 bif_driverp将指向ifnet 结构中的 b p f _ i f指针。如果网络接口还未加上开关, * b i f _ d r i v e r p为空。为某个网络接 口配置BPF设备时, * b i f _ d r i v e r p将指向b i f _ i f结构,从而告诉接口可以开始向 BPF传递 分组。 71 接口类型保存在 bif_dlt中。图31-5中列出了前面提到的几个接口所分别对应的常量值。

823

第31章 BPF:BSD分组过滤程序计计

bif_dlt





DLT_EN10MB DLT_SLIP DLT_NULL

10 Mb以太网接口 SLIP接口 环回接口

图31-5 bif_dlt 值

72-74

BPF接受的所有分组都有一个附加的 BPF首部。b i f _ h d r l e n等于首部大小。最后,

bif_ifp指向对应接口的 ifnet结构。 图31-6给出了每个输入分组中附加的 bpf_hdr结构。

图31-6 bpf_hdr 结构

122-128

b h _ t s t a m p记录了分组被捕捉的时间。 b h _ c a p l e n 等于 B P F保存的字节数,

b h _ d a t a l e n等于原始分组中的字节数。 b h _ h e a d l e n等于b p f _ h d r的大小加上所需填充字 节的长度。它用于解释从 BPF设备中读取的分组,应该等同于接收接口的 bif_hdrlen。 图3 1 - 7给出了 b p f _ i f结构是如何与前述 3个接口 (l e _ s o f t c [ 0 ]、s l _ s o f t c [ 0 ]和 loif)的ifnet结构建立连接的。

图31-7 bpf_if 和ifnet 结构

注意,bif_driverp指向网络接口的 if_bpf和sc_bpf指针,而不是接口结构。 S L I P设备使用 s c _ b p f,而不是 if _ b p f。这可能是因为 SLIP BPF 代码完成时,

824

计计TCP/IP详解 卷 2:实现

i f _ b p f成员变量还未加入到 i f n e t结构中。 Net/2中的i f n e t结构不包括 i f _ b p f成 员。 按照各接口驱动程序调用 b p f a t t a c h时给出的信息,对 3个接口初始化链路类型和首部 长度成员变量。 第3章介绍了 b p f a t t a c h被以太网、 S L I P和环回接口的驱动程序调用。每个设备驱动程 序初始化调用bpfattach时,将构建 BPF接口结构链表。图 31-8给出了该函数。

图31-8 bpfattach 函数

1053-1063

每个支持 B P F的设备驱动程序都将调用 b p f a t t a c h。第一个参数是保存在

b i f _ d r i v e r p的指针(图3 1 - 4给出),第二个参数指向接口的 i f n e t结构,第三个参数确认数 据链路层类型,第四个参数传递分组中的数据链路首部大小,为接口分配一个新的 b p f _ i f结 构。 1. 初始化bpf_if结构

825

第31章 BPF:BSD分组过滤程序计计

b p f _ i f 结构根据函数的参数进行初始化,并插入到

1064-1070

B P F 接口链表,

bpf_iflist,的表头。 2. 计算BPF首部大小 1071-1077

设定b i f _ h d r l e n大小,强迫网络层首部 (如I P首部)从一个长字的边界开始。

这样可以提高性能,避免为 B P F加入不必要的对齐限制。图 3 1 - 9列出了在前述 3个接口上,各 自捕捉到的 BPF分组的总体结构。

IP分组 18字节

14字节 填充 IP分组

18字节

2字节

16字节 填充 环回伪链路首部 IP分组

18字节

2字节

4字节

图31-9 BPF分组结构

e t h e r _ h e a d e r结构在图 4 - 1 0中给出, S L I P伪链路首部在图 5 - 1 4中给出,而环回接口伪 链路首部在图5-28中给出。 请注意,SLIP和环回接口分组需要填充 2字节,以强迫IP首部按4字节对齐。 3. 初始化bpf_dtab表 1078-1083

代码初始化图 3 1 - 1 0 中给出的 B P F 描述符表。注意,仅在第一次调用

bpfattach时进行初始化,后续调用将跳过初始化过程。 4. 打印控制台信息 1084-1085 系统向控制台输出一条短信息,宣告接口已配置完毕,可以支持 BPF。

31.4 bpf_d结构 为了能够选择性地接收输入报文,应用进程首先打开一个 B P F设备,调用若干 i c t l命令 规定BPF过滤程序的条件,指明接口、读缓存大小和超时时限。每个 BPF设备都有一个相关的 bpf_d结构,如图 31-10所示。 45-46

如果同一网络接口上配置了多个 BPF设备,与之相应的 b p f _ d结构将组成一个链表。

bd_next指向链表中的下一个结构。 分组缓存 47-52

每个bpf_d结构都有两个分组缓存。输入分组通常保存在 bd_sbuf所对应的缓存(存

储缓存 )中。另一个缓存要么对应于 b d _ f b u f (空闲缓存 ),意味着缓存为空;或者对应于 b d _ h b u f(暂留缓存 ),意味着缓存中有分组等待应用进程读取。 b d _ s l e n和b d _ h l e n分别记

826

计计TCP/IP详解 卷 2:实现

录了保存在存储缓存和暂留缓存中的字节数。

图31-10 bpf_d 结构

如果存储缓存已满,它将被连接到 b d _ h b u f,而空闲缓存将被连接到 b d _ s b u f。当暂留 缓存清空时,它会被连接到 b d _ f b u f 。宏 R O T A T E _ B U F F E R S 负责把存储缓存连接到 b d _ h b u f,空闲缓存连接到 b d _ s b u f,并清空 b d _ f b u f。存储缓存满或者应用进程不想再 等待更多的分组时调用该宏。 b d _ b u f s i z e记录与设备相连的两个缓存的大小,其默认值等于 4096(B P F _ B U F S I Z E)字 节。修改内核代码可以改变默认值大小,或者通过 B I O C S B L E Ni o c t l命令改变某个特定 B P F设备的 b d _ b u f s i z e。B I O C G B L E N命令返回 b d _ b u f s i z e的当前值,其最大值不超过 32768 (BPF_MAXBUFSIZE)字节,最小值为 32 (BPF_MINBUFSIZE)字节。 53-57

b d _ b i f 指向 B P F 设备所对应的 b p f _ i f 结构。 B I O C S E T I F命令可指明设备。

b d _ r t o u t是等待分组时,延迟的滴答数。 b d _ f i l t e r指向B P F设备的过滤程序代码。两个 统计值,应用进程可通过 B I O C G S TAT S命令读取,分别保存在 b d _ r c o u n t和b d _ d c o u n t 中。 58-63

bd_promisc通 过 BIOCPROMISC命 令 置 位 , 从 而 使 接 口 工 作 在 混 杂

(promiscuous)状态。bd_state未使用。bd_immediate通过BIOCIMMEDIATE命令置位, 促使驱动程序收到分组后即返回,不再等待暂留缓存填满。 b d _ p a d填弃b p f _ d结构,从而 与长字边界对齐。 b d _ s e l保存的s e l i n f o结构,可用于s e l e c t系统调用。我们不准备介绍 如何对BPF设备使用select系统调用, 16.13节已介绍了 select的一般用法。 31.4.1 bpfopen函数 应用进程调用open,试图打开一个 BPF设备时,该调用将被转到 bpfopen(图31-11)。 256-263 系统编译时,BPF设备的数目受到 NBPFILTER的限制。如果设备的最小设备号大

827

第31章 BPF:BSD分组过滤程序计计

于 N B P F I L T E R ,则返回 E N X I O ,这是因为系统管理员创建的 / d e v / b p f x 项数大于 NBPFILTER的值。

图31-11 bpfopen 函数

分配bpf_d结构 同一时间内,一个应用进程只能访问一个 B P F设备。如果 b p f _ d结构已被激活,

264-275

则返回 E B U S Y。应用程序,如 t c p d u m p,收到此返回值时,会自动寻找下一个设备。如果该 设备已存在,最小设备号所指定的 bpf_dtab表中的项被清除,分组缓存大小复位为默认值。 31.4.2 bpfioctl函数 设备打开后,可通过 ioctl命令进行配置。图 31-12总结了与BPF设备有关的 ioctl命令。 图3 1 - 1 3给出了 b p f i o c t l函数,只列出 B I O C S E T F和B I O C S E T I F的处理代码,其他未涉及 到的ioctl命令则被忽略。 命



FIONREAD BIOCGBLEN BIOCSBLEN BIOCSETF BIOCFLUSH BIOCPROMISC BIOCGDLT BIOCGETIF BIOCSETIF BIOCSRTIMEOUT BIOCGRTIMEOUT BIOCGSTATS BIOCIMMEDIATE BIOCVERSION

第三个参数





u_int u_int u_int struct bpf_program

u_int struct struct struct struct struct u_int struct

bpfioctl bpfioctl bpfioctl bpf_setf reset_d ifpromisc bpfioctl ifreq bpf_ifname ifreq bpf_setif timeval bpfioctl timeval bpfioctl bpf_stat b p f i o c tl bpfioctl b p f _ v e r s i o n bpfioctl

图31-12 BPF ioctl 命令





返回暂留缓存和存储缓存中的字节数 返回分组缓存大小 设定分组缓存大小 安装BPF程序 丢弃挂起分组 设定混杂方式 返回b i f _ d l t 返回所属接口的名称 为网络接口添加设备 设定“读”操作的超时时限 返回“读”操作的超时时限 返回BPF统计值 设定立即方式 返回BPF版本信息

828

计计TCP/IP详解 卷 2:实现

图31-13 bpfioctl 函数

501-509

与bpfopen类似,通过最小设备号从bpf_dtab表中选取相应的bpf_d结构。整个

命令处理是一个大的switch/case语句。我们给出了两个命令,BIOCSETF和BIOCSETIF,以 及default子句。 510-522

b p f _ s e t f函数安装由a d d r指向的过滤程序, b p f _ s e t i f建立起指定名称接口

与bpf_d结构间的对应关系。本书中没有给出 bpf_setf的实现代码。 668-673 如果命令未知,则返回 EINVAL。 图31-14的例子中, bpf_setif已把bpf_d结构连接到 LANCE接口上。 图中,b i f _ d l i s t指向b p f _ d t a b[0],以太网接口描述符链表中的第一个也是仅有的一 个描述符。在 bpf_dta b[0]中,b d _ s b u f和b d _ h b u f成员分别指向存储缓存和暂留缓存。两 个缓存大小都等于 4096(bd_bufsize)字节。bd_bif回指接口的 bpf_if结构。 i f n e t结构(l e _ s o f t c[ 0 ] )中的i f _ b p f也指回 b p f _ i f结构。如图 4 - 1 9和图4 - 11所示, 如果if_bpf非空,则驱动程序开始调用 bpf_tap,向BPF设备传递分组。 图3 1 - 1 5接着图3 1 - 1 0,给出了打开第二个 B P F设备,并连接到同一个以太网网络接口后的 各结构变量的状态。

829

第31章 BPF:BSD分组过滤程序计计

到其他b p f _ i f结构

存储缓存 空闲缓存

b p f _ d t a b [ 2 ]至 bpf_dtab[NBPFILTER-1]

图31-14 连接到以太网接口的 BPF设备

到其他b p f _ i f结构

存储缓存 空闲缓存

存储缓存 空闲缓存

b p f _ d t a b [ 2 ]至 bpf_dtab[NBPFILTER-1]

图31-15 连接到以太网接口的两个 BPF设备

830

计计TCP/IP详解 卷 2:实现

第二个 B P F 设备打开时,在 b p f _ d t a b 表中分配一个新的 b p f _ d 结构,本例中为 b p f _ d t a b [ 1 ]。因为第二个 B P F 设备也连接到同一个以太网接口, b i f _ d l i s t 指向 b p f _ d t a b[ 1 ],并且 b p f _ d t a b[ 1 ] .b d _ n e x t指向b p f _ d t a b[ 0 ],即以太网上对应的第一个 BPF描述符。系统为新的描述符结构分别分配存储缓存和暂留缓存。 31.4.3 bpf_setif函数 bpf_setif函数,负责建立 BPF描述符与网络接口间的连接,如图 31-16所示。

图31-16 bpf_setif 函数

831

第31章 BPF:BSD分组过滤程序计计

图31-16 (续)

721-746

b p f _ s e t i f的第一部分完成 i f r e q结构(图4-23)中接口名的正文与数字部分的分

离,数字部分保存在 u n i t中。例如,如果 i f r _ n a m e的头4字节为“ s l 1 \ 0”,代码执行完毕 后,将等于“sl\0\0”,且unit等于1。 1. 寻找匹配的 ifnet结构 747-754

for循环用于在支持 BPF的接口(bpf_iflist中)中查找符合 ifreq定义的接口。

755-768

如果未找到匹配的接口,则返回 ENETDOWN。如果接口存在, bpf_allocate为

bpf_d分配空闲缓存和存储缓存,如果它们还未被分配的话。 2. 连接bpf_d结构 769-777

如果B P F设备还未与网络接口建立连接关系,或者连接的网络接口不是 i f r e q中

指定的接口,则调用 b p f _ d e t a c h d丢弃原先的接口 (如果存在 ),并调用 b p f _ a t t a c h d将其 连接到新的接口上。 778-784

r e s e t _ d复位分组缓存,丢弃所有在应用进程中等待的分组。函数返回 0,说明

处理成功;或者 ENXIO,说明未找到指定接口。 31.4.4 bpf_attachd函数 图31-17给出的bpf_attachd函数,建立起BPF描述符与BPF设备和网络接口间的对应关系。

图31-17 bpf_attachd 函数

832

计计TCP/IP详解 卷 2:实现

图31-17 (续)

189-203 首先,令bd_bif指向网络接口的 BPF接口结构。接着, bpf_d结构被插入到与设 备对应的 b p f _ d结构链表的头部。最后,改变网络接口中的 BPF指针,指向当前 BPF结构,从 而促使接口向BPF设备传递分组。

31.5 BPF的输入 一旦B P F设备打开并配置完毕,应用进程就通过 r e a d系统调用从接口中接收分组。 B P F 过滤程序复制输入分组,因此,不会干扰正常的网络处理。输入分组保存在与 B P F设备相连 的存储缓存和暂留缓存中。 31.5.1 bpf_tap函数 下面列出了图 4 - 11中L A N C E设备驱动程序调用 b p f _ t a p的代码,并利用这一调用介绍 bpf_tap函数。图4-11中的调用如下: bpf_tap(le->sc_if.if_bpf, buf, len + sizeof(struct ether_header));

图31-18给出了bpf_tap函数。

图31-18 bpf_tap 函数

833

第31章 BPF:BSD分组过滤程序计计

869-882

第一个参数是指向 b p f _ i f结构的指针,由 b p f a t t a c h设定。第二个参数是指向

进入分组的指针,包括以太网首部。第三个参数等于缓存中包含的字节数,本例中,等于以 太网首部(14字节)大小加上以太网帧的数据部分。 向一个或多个BPF设备传递分组 883-890

f o r循环遍历连接到网络接口的 B P F设备链表。对每个设备,分组被递交给

b p f _ f i l t e r。如果过滤程序接受了分组,它返回捕捉到的字节数,并调用 c a t c h p a c k e t 复制分组。如果过滤程序拒绝了分组, slen等于0,循环继续。循环终止时, bpf_tap返回。 这一机制确保了同一网络接口上对应了多个 B P F设备时,每个设备都能拥有一个独立的过滤 程序。 环回驱动程序调用 b p f _ m t a p,向B P F传递分组。这个函数与 b p f _ t a p类似,然而是在 mbuf链,而不是在一个内存的连续区域中复制分组。本书中不介绍这个函数。 31.5.2 catchpacket函数 图31-18中,过滤程序接受了分组后,将调用 catchpacket,图31-19给出了这个函数。

图31-19 catchpacket 函数

834

计计TCP/IP详解 卷 2:实现

图31-19 (续)

946-955

c a t c h p a c k e t的参数包括: d,指向B P F设备结构的指针; p k t,指向进入分组

的通用指针; p k t l e n,分组被接收时的长度; s n a p l e n ,从分组中保存下来的字节数; c p f n,函数指针,把分组从 p k t中复制到一块连续内存中。如果分组已经保存连续内存中, 则c p t n等于b c o p y。如果分组被保存在 m b u f中(p k t指向m b u f链表中的第一个 m b u f,如环回 驱动程序),则cptn等于bpf_mcopy。 956-964

除了链路层首部和分组, c a t c h p a c k e t为每个分组添加 b p f _ h d r。从分组中保

存的字节数等于 s n a p l e n和p k t l e n中较小的一个。处理过的分组和 b p f _ h d r必须能放入分 组缓存中(bd_bufsize字节)。 1. 分组能否放入缓存 965-985

c u r l e n等于存储缓存中已有的字节数加上所需的填充字节,以保证下一分组能

从长字边界处开始存放。如果进入分组无法放入剩余的缓存空间,说明存储缓存已满。如果 空闲缓存不可用 (如应用进程正从暂留缓存中读取数据 ),则进入分组被丢弃。如是空闲缓存可 用,则调用 ROTATE_BUFFERS宏轮转缓存,并通过 bpf_wakeup唤醒所有等待输入数据的应 用进程。 2. 立即方式处理 986-991

如果设备处于立即方式,则唤醒所有等待进程以处理进入分组—内核中没有分

组的缓存。 3. 添加BPF 首部 992-1004 当前时间(microtime)、分组长度和首部长度均保存在 bpf_hdr中。调用cptf 所指的函数,把分组复制到存储缓存,并更新存储缓存的长度。因为在把分组从设备缓存传 送到某个 m b u f链表之前, b p f _ t a b已由l e r e a d直接调用,接收时间戳近似等于实际的接收

835

第31章 BPF:BSD分组过滤程序计计

时间。 31.5.3 bpfread函数 内核把针对B P F设备的r e a d转交给b p f r e a d处理。通过 B I O C S R T I M E O U T命令,B P F支 持限时读取。这个“特性”也可通过 s e l e c t系统调用来实现,但至少 t c p d u m p还是采用了 B I O C S R T I M E O U T,而非s e l e c t。应用进程提供一个读缓存,能够与设备的暂留缓存大小相 匹配。 B I C O G B L E N命令返回缓存大小。一般情况下,读操作在存储缓存已满时返回。内核轮 转缓存,把存储缓存转给暂留缓存,后者在 read系统调用时被复制到应用进程提供的读缓存, 同时BPF设备继续向存储缓存中存放进入分组。图 31-20给出了bpfread。

图31-20 bpfread 函数

836

计计TCP/IP详解 卷 2:实现

图31-20 (续)

344-357 通过最小设备号在 bpf_dtab中寻找相应的BPF设备。如果读缓存不能匹配 BPF设 备缓存的大小,则返回 EINVAL。 1. 等待数据 358-364

因为多个应用进程能够从同一个 B P F设备中读取数据,如果有某个进程已先读取

了数据, w h i l e循环将强迫读操作继续。如果暂留缓存中存在数据,循环被跳过。这与两个 应用进程通过两个不同的 BPF设备过滤同一个网络接口的情况 (见习题31.2)是不同的。 2. 立即方式 365-373 如果设备处于立即方式,且存储缓存中有数据,则轮回缓存, while循环被终止。 3. 无可用的分组 374-384 如果设备不处于立即方式,或者存储缓存中没有数据,则应用进程进入休眠状态, 直到某个信号到达,读定时器超时,或者有数据到达暂留缓存。如果有信号到达,则返回 EINTR或ERESTART。 记住,应用进程不会见到 E R E S T A R T,因为 s y s c a l l函数将处理这一错误,且 不会向应用进程返回这一错误。 4. 查看暂留缓存

837

第31章 BPF:BSD分组过滤程序计计

385-391 如果定时器超时,且暂留缓存中存在数据,则循环终止。 5. 查看存储缓存 3 9 2 - 3 9 9 如果定时器超时,且存储缓存中没有数据,则 r e a d返回0。应用进程执行限时读 取时,必须考虑到这种情况。如果定时器超时,且存储缓存中存在数据,则把存储缓存转给 暂留缓存,循环终止。 如果tsleep返回正常且存在数据,同时 while循环测试失败,则循环终止。 6. 分组可用 400-416 循环终止时,暂留缓存中已有数据。uiomove从暂留缓存中移出 bd_hlen个字节, 交给应用进程。把暂留缓存转给空闲缓存,清除缓存计数器,函数返回。 u i o m o v e调用前的 注释指出,u i o m o v e通常能向应用进程复制 b d _ h l e n字节的数据,因为前面已检查过读缓存 大小,确保它大于 BPF设备缓存的最大值,即 bd_bufsize。

31.6 BPF的输出 最后,我们讨论如何向带有 BPF设备的网络接口输出队列中添加分组。首先,应用进程必 须构造完整的数据链路帧。对以太网而言,包括源和目的主机的硬件地址和数据帧类型 (图4-8)。 内核在把它放入接口的输出队列前不会修改链路帧。 bpfwrite函数 内核把应用进程的 w r i t e系统调用转给图 3 1 - 2 1给出的 b p f w r i t e处理,数据帧被传给 BPF设备。

图31-21 bpfwrite 函数

838

计计TCP/IP详解 卷 2:实现

图31-21 (续)

1. 检查设备号 4 3 7 - 4 4 9 通过最小的设备号选择 B P F设备,它必须已连接到某个网络接口。如果还没有, 则返回ENXIO。 2. 向mbuf链中复制数据 4 5 0 - 4 5 7 如果w r i t e给出的写入数据长度等于 0,则立即返回0。b p f _ m o v e i n从应用进程 复制数据到一个 m b u f链表,并基于由 b i f _ d l t传递的接口类型计算去除了链路层首部后的分 组长度,并在 d a t l e n中返回该值。它还在 d s t中返回一个已初始化过的 s o c k a d d r结构。对 以太网而言,这个地址结构的类型应该等于 A F _ U N S P E C,说明mbuf链中保存了外出数据帧的 数据链路层首部。如果分组大于接口的 MTU,则返回EMSGSIZE。 3. 分组排队 4 5 8 - 4 6 5 调用i f n e t结构中指定的 i f _ o u t p u t函数,得到的 m b u f链被提交给网络接口。 对于以太网,if_output等于ether_output。

31.7 小结 本章中,我们讨论了如何配置 B P F设备,如何向 B P F设备递交进入数据帧,及如何在一个 BPF设备上传送外出数据帧。 一个网络接口可以有多个 B P F设备,每个B P F设备都有自己的过滤程序。存储缓存和暂留 缓存最大限度地减少了应用进程为了处理进入数据帧而调用 read的次数。 本章中只介绍了 B P F的一些主要特性。有关 B P F设备过滤程序代码的详细情况和其他一些 特性,感兴趣的读者请参阅源代码和 Net/3手册。

习题 31.1 为什么在分组存入 BPF缓存之前,就能在 catchpacket中调用bpf_wakeup? 31.2 图3 1 - 2 0中,我们提到可能会有两个进程在同一 B P F设备上等待数据。图 3 1 - 11中, 我们指出同一时间只能有一个应用进程可以打开一个特定的 B P F设备。为什么这两 种说法都正确呢? 31.3 如果BIOCSETIF命令中指定的设备不支持 BPF,会发生什么现象?

第32章 原 始 IP 32.1 引言 应用进程在 I n t e r n e t域中创建一个 S O C K _ R A W类型的插口,就可以利用原始 I P层。一般有 下列3种用法: 1) 应用进程可利用原始插口发送和接收 ICMP和IGMP报文。 Ping程序利用这种类型的插口,发送 ICMP回显请求和接收 ICMP回显应答。 有些选路守护程序,利用这一特性跟踪通常由内核处理的 I C M P重定向报文段。我们在 1 9 . 7 节 中 提到 , N e t / 3 处 理 重 定向 报 文 段 时, 会 在 需重 定 向 的插 口 上 生成 RTM_REDIRECT消息,从而无需利用原始插口的这一功能。 这个特性还用于实现基于 I C M P的协议,如路由通告和路由请求 (卷1的9 . 6节),它们需 用到ICMP,不过最好由应用进程,而不是内核完成相应处理。 多播路由守护程序利用原始 IGMP插口,发送和接收 IGMP报文。 2) 应用进程可利用原始插口构造自己的 I P首部。路由跟踪程序利用这一特性生成自己的 UDP数据报,包括IP和UDP首部。 3) 应用进程可利用原始插口读写内核不支持的 IP协议的IP数据报。 gated程序利用这一特性支持基于 IP的路由协议:EGP、HELLO和OSPF。 这种类型的原始插口还可用于设计基于 I P的新的运输层协议,而无需增加对内核的支 持。调试应用进程代码比调试内核代码容易得多。 本章介绍原始IP插口的实现。

32.2 代码介绍 图32-1给出的C文件中包含了5个原始IP处理函数。 文



netinet/raw_ip.c





原始IP处理函数

图32-1 本章讨论的文件

图32-2给出了5个原始IP函数与其他内核函数间的关系。 带阴影的椭圆表示我们在本章中将要讨论的 5个函数。请注意,原始 I P函数名中的前缀 “rip”表示“原始IP (Raw IP)”,而不是“选路信息协议 (Routing Information Protocol)”,后者 的缩写也是 RIP。 32.2.1 全局变量 本章中用到 4个全局变量,如图 32-3所示。

840

计计TCP/IP详解 卷 2:实现

插口接收缓存

系统初始化

多种系统调用

软中断

图32-2 原始IP函数与其他内核函数间的关系 变



数据类型





struct inpcb 原始IP的Internet PCB链表表头 s t r u c t s o c k a d d r _ i n在输入中包含发送方的 IP地址 u_long 插口接收缓存大小默认值, 8192字节 u_long 插口发送缓存大小默认值, 8192字节

rawinpcb ripsrc rip_recvspace rip_sendspace

图32-3 本章介绍的全局变量

32.2.2 统计量 原始IP在ipstat结构(图8-4)中维护两个计数器,如图 32-4所示。 描

ipstat成员变量 ips_noproto ips_rawout



协议类型未知或不支持的数据报数目 生成的原始 IP数据报总数

SNMP变量使用 •

图32-4 ipstat 结构中维护的原始 IP统计量

图8 - 6给出了如何在 S N M P中使用i p s _ n o p r o t o计数器。图 8 - 5给出了这两个计数器输出 值的例子。

32.3 原始IP的protosw结构 与所有其他协议不同, i n e t s w数组有多条记录都可以读写原始 IP。i n e t s w结构中有4个 记录的插口类型都等于 SOCK_RAW,但协议类型则各不相同: • IPPROTO_ICMP(协议值1); • IPPROTO_IGMP(协议值2); • IPPROTO_RAW(协议值255);和

841

第 32章 原 始 IP计计

• 原始IP通配记录 (协议值0)。 其中ICMP和IGMP,前面已介绍过 (图11-12和图13-9)。四项记录间的区别总结如下: • 如果应用进程创建了一个原始插口 (S O C K _ R A W),协议值非零 (s o c k e t的第三个参数 ), 并且如果协议值等于 I P P R O T O _ I C M P、I P P R O T O _ I G M P或I P P R O T O _ R A W,则会使用 对应的protosw记录。 • 如果应用进程创建了一个原始插口 (S O C K _ R A W),协议值非零,但内核不支持该协议, pffindproto会返回协议值为 0的通配记录,从而允许应用进程处理内核不支持的 IP协 议,而无需修改内核代码。 我们在7.8节中提到,i p _ p r o t o x数组中的所有未知记录都指向 I P P R O T O _ R A W,它的协 议转换类型如图 32-5所示。 成



pr_type pr_domain pr_protocol pr_flags pr_input pr_output pr_ctlinput pr_ctloutput pr_usrreq pr_init pr_fasttimo pr_slowtimo pr_drain pr_sysctl



i n e t s w[3] SOCK_RAW & inetdomain I P P R O T O _ R A W(255) PR_ATOMIC|PR_ADDR rip_input 0 0 rip_ctlinput rip_usrreq 0 0 0 0 0



原始插口 属于Internet域的原始IP 出现在IP首部的i p _ p字段 插口层标志,不用于协议处理 从IP层接收报文段 原始IP不使用 原始IP不使用 响应应用进程的管理请求 响应应用进程的通信请求 原始IP不使用 原始IP不使用 原始IP不使用 原始IP不使用 原始IP不使用

图32-5 原始IP的protosw 结构

本章中我们将介绍 3个以r i p _开头的函数,此外还大致提一下 r i p _ o u t p u t函数,它没 有出现在协议转换记录中,但输出原始 IP报文段时, rip_usrreq将会调用它。 第五个原始 I P函数, r i p _ i n i t,只出现在通配处理记录中。初始化函数只能调用一次, 所以它既可以出现在 IPPROTP_RAW记录中,也可以放在通配记录中。 不过,图 3 2 - 5中并没有说明其他协议 ( I C M P和I G M P ),在它们自己的 p r o t o s w结构中也用 到了一些原始 IP函数。图 32-6对4个S O C K _ R A W协议各自 p r o t o s w结构的相关成员变量做了一 个比较。为了强调指出彼此间的区别,不同之处都用黑体字标出。 协议类型 记录

通配(0)

图32-6 原始插口的协议散转值的比较

842

计计TCP/IP详解 卷 2:实现

不同 B S D 版本中,原始 I P的实现各有不同。 i p _ p r o t o x 表中,协议号等于 I P P R O T O _ R A W记录通常都用做通配记录以支持未知的 I P协议,而协议号等于 0的记 录通常做为默认记录,从而允许应用进程读写内核不支持的 IP协议数据报。 应用进程使用 I P P R O T O _ R A W记录,最早见于 Van Jacobson开发的 Tr a c e o u t,这 是第一个需要自己写 I P首部(改变T T L字段)的应用进程。为了支持 Tr a c e o u t,修订了 4 . 3 B S D和N e t / 1,包括修改 r i p _ o u t p u t,在收到协议号等于 I P P R O T O _ R A W的数据 报时,假定应用进程提交了一个完整的 I P数据报,包括 I P首部。在 N e t / 2中,引入了 I P _ H D R I N C L插口选项,简化了 I P P R O T O _ R A W的用法,允许应用进程利用通配记 录发送自己的IP首部。

32.4 rip_init函数 系统初始化时, domaininit函数调用原始IP初始化函数 rip_init (图32-7)。

图32-7 rip_init 函数

这个函数执行的唯一操作是令 P C B首部(r a w i n p c b)中的前向和后向指针都指向自己,实 现一个空的双向链表。 只要某个 s o c k e t 系统调用创建了 S O C K _ R A W 类型的插口,下面将介绍的原始

IP

PRU_ATTACH函数就创建一个 Internet PCB,并插入到 rawinpcb链表中。

32.5 rip_input函数 因为i p _ p r o t o x数组中保存的所有关于未知协议记录都指向 I P P R O T O _ R A W(图7 - 8 ),且 后者的p r _ i n p u t函数指向 r i p _ i n p u t (图3 2 - 6 ),所以只要某个接收 I P数据报的协议号内核 无法识别,就会调用此函数。但从图 3 2 - 2可看出, I C M P和I G M P都可能调用 r i p _ i n p u t,只 要满足下列条件: • icmp_input调用rip_input处理所有未知的ICMP报文类型和所有非响应的ICMP报文。 • igmp_input调用rip_input处理所有IGMP分组。 上述两种情况下,调用 r i p _ i n p u t的一个原因是允许创建了原始插口的应用进程处理新 增的ICMP和IGMP报文,内核可能不支持它们。 图32-8给出了rip_input函数。

图32-8 rip_input 函数

843

第 32章 原 始 IP计计

图32-8 (续)

59-66

I P 数据报中的源地址被保存在全局变量 r i p s r c 中,只要找到了匹配的 P C B,

r i p s r c 将做为参数传给 s b a p p e n d a d d r。与U D P不同,原始 I P没有端口号的概念,因此 sockaddr_in结构中的sin_port总等于0。 2. 在所有原始 IP PCB 中寻找一个或多个匹配的记录 67-88 原始IP处理PCB表的方式与 UDP和TCP不同。前面介绍过,这两个协议维护一个指针, 总是指向最近收到的报文段 (单报文段缓存 ),并调用通用函数 i n _ p c b l o o k u p寻找一个最佳 匹配(如果收到的数据报不同于缓存中的记录 )。由于原始 IP数据报可能发送到多个插口上,所 以无法使用 i n _ p c b l o o k u p,因此,必须遍历原始 P C B链表中的所有 P C B。这一点类似于 UDP处理广播报文段和多播报文段的方式 (图23-26)。 3. 协议比较 68-69

如果PCB中的协议字段非零,并且与 IP首部的协议字段不匹配,则 PCB被忽略。也说

明协议值等于0(socket的第三个参数)的原始插口能够匹配所有收到的原始 IP报文段。 4. 比较本地和远端 IP地址

844

计计TCP/IP详解 卷 2:实现

70-75

如果PCB中的本地地址非零,并且与 IP首部的目的 IP地址不匹配,则 PCB被忽略。如

果PCB中的远端地址非零,并且与 IP首部的源IP地址不匹配,PCB被忽略。 上述3种测试说明应用进程能够创建一个协议号等于 0的原始插口,即不绑定到本地地址, 也不与远端地址建立连接,可以接收经 rip_input处理的所有数据报。 代码71行和74行都有同样的错误:相等测试,实际应为不相等测试。 5. 递交接收数据报的复制报文段以备处理 76-94 sbappendaddr向应用进程提交一个接收数据报的复制报文段。变量 last的使用与 图2 3 - 2 6中的用法类似:因为 s b a p p e n d a d d r把报文段放入到适当队列中后将释放所有 m b u f, 如果有多个进程接收数据报的复制报文段, r i p _ i n p u t必须调用 m _ c o p y保存一份复制报文 段。但如果只有一个应用进程接收数据报,则无需复制。 6. 无法上交的数据报 如果无法为数据报找到相匹配的插口,则释放 m b u f,递增 i p s _ n o p r o t o,递减

95-99

i p s _ d e l i v e r e d。I P在调用r i p _ i n p u t之前已经递增过后一个计数器 (图8 - 1 5 )。由于数据 报实际上没有上交给运输层,因此,必须递减 i p s _ d e l i v e r e d,确保两个 S N M P计数器, ipInDiscards和 ipInDelivers (图8-16),的正确性。 本节开始时,我们提到, i c m p _ i n p u t会为未知报文类型或非响应报文调用 r i p _ i n p u t,意味着如果收到 I C M P主机不可达报文,且 r i p _ i n p u t找不到可匹配 的原始插口P C B,i p s _ n o p r o t o会递增。这也说明为什么图 8 - 5中的计数器值较大。 在前面对该计数器的描述中提到“未知或不支持的协议”,这种说法是不正确的。 如果收到的 IP数据报带有的协议字段,既无法为内核辩识,也无法由某个应用进 程通过原始插口处理, N e t / 3不会生成差错代码等于 2 (协议不可达 )的I C M P目的不可 达报文。RFC 1 122建议出现此种情况时应该生成 ICMP差错报文(参见习题32.4)。

32.6 rip_output函数 图3 2 - 6中,I C M P、I G M P和原始I P都调用r i p _ o u t p u t实现原始 I P输出。应用进程调用 5 个写函数之一: s e n d、s e n d t o、s e n d m s g、w r i t e和w r i t e v,系统将输出报文段。如果 插口已建立连接,就可以任意调用上述 5个函数,尽管 s e n d t o和s e n d m s g中不能规定目的地 址。如果插口没有建立连接,则只能调用 sendto和sendmsg,且必须规定目的地址。 图32-9给出了rip_output函数。 1. 内核填充IP首部 119-128

如果IP_HDRINCR插口选项未定义, M_PREPEND为IP首部分配空间,并填充 IP首

部各字段。此处未填充的字段留待 i p _ o u t p u t初始化 (图8 - 2 2 )。协议字段等于 P C B中保存的 值,并且是图32-10中socket系统调用的第三个参数。 TOS等于0,TTL等于255。内核为原始IP插口填充各首部字段时,通常都使用这些固定值。 这与UDP和TCP不同,应用进程能够通过插口选项设定 IP_TTL和IP_TOS值。 129

应用程序通过 I P _ O P T I O N S 插口选项设定的所有 I P 选项,都通过 o p t s 变量传给

ip_output函数。 2. 调用者填充 IP首部:IP_HDRINCR插口选项

845

第 32章 原 始 IP计计

130-133

如果选用了 I P _ H D R I N C R插口选项,调用者在数据报前提供完整的 IP首部。如果

应用进程提供的 ID字段等于 0,对此类IP首部需做的唯一修改是 ID字段。 IP数据服的 ID字段可 以等于 0。此处, r i p _ o u t p u t对I D字段的赋值可以简化应用进程的处理,直接设 I D字段等 于0,rip_output向内核请求内核变量 ip_id的当前值,做为 IP报文段的ID值。 134-136 令opts为空,忽略应用进程通过 IP_OPTIONS可能设定的任何 IP选项。如果调用 者构建了自己的 I P首部,其中肯定已包括了调用者希望加入的 I P选项。 f l a g s变量中必须有 IP_RAWOUTPUT标志,告诉 ip_output不要修改IP首部。

图32-9 rip_output 函数

137 计数器ips_rawout递增。执行 Traceroute时,Traceroute每发送一个变量就会导致此变 量加1。 r i p _ o u t p u t的操作在不同版本中也有所变化。在 N e t / 3中使用I P _ H D R I N C L插 口选项时, r i p _ o u t p u t对I P首部所做的唯一修改就是填充 I D字段,如果应用进程 将其定为0。因为I P _ R A W O U T P U T标志置位, N e t / 3中的i p _ o u t p u t函数不改动I P首

846

计计TCP/IP详解 卷 2:实现

部。但在 N e t / 2中,即使 I P _ H D R I N C L插口选项设定时,它也会修改 I P首部中特定字 段:IP版本号等于 4,分片偏移量等于 0,分片标志被清除。

32.7 rip_usrreq函数 协议的用户请求处理函数能够完成多种操作。与 U D P和T C P的用户请求处理函数类似, rip_usrreq是一个很大的switch语句,每个 PRU_xxx请求,都有一个对应的 case子句。 图32-10给出的PRU_ATTACH请求,来自 socket系统调用。

图32-10 rip_usrreq 函数:PRU_ATTACH 请求

194-206

每次s o c k e t函数被调用时,都会创建新的 s o c k e t结构,此时还没有指向某个

Internet PCB。 1. 确认超级用户 207-210

只有超级用户才能创建原始插口,这是为了防止普通用户向网络发送自己的 I P数

据报。 2. 创建Internet PCB,保留缓存空间 211-215

为输入和输出队列保留所需空间,调用 in_pcballoc分配新的Internet PCB,添

加到原始 IP PCB链表中(r a w i n p c b),并与s o c k e t结构建立对应关系。 r i p _ u s r r e q的n a m 参数就是 s o c k e t系统调用的第三个参数:协议。它被保存在 P C B中,因为 r i p _ i n p u t需用 它上交收到的数据报, r i p _ o u t p u t 也要把它填入到外出数据报的协议字段中

( 如果

IP_HDRINCL未设定)。 原始IP插口与远端 IP地址建立的连接,与 UDP插口和远端 IP地址建立的连接相类似。它固 定了原始插口只能接收来自于特定地址的数据报,如我们在 r i p _ i n p u t中所看到的。原始 I P

847

第 32章 原 始 IP计计

与UDP一样,是一个无连接协议,下面两种情况下会发送 PRU_DISCONNECT请求: 1) 关闭建立连接的原始插口时,在 P R U _ D E T A C H之前会先发送 P R U _ D I S C O N N E C T请 求。 2) 如果对一个已建立连接的原始插口调用 connect,soconnect在发送PRU_CONNECT 请求前会先发送 PRU_DISCONNECT请求。 图32-11给出了PRU_DISCONNECT、PRU_ABORT和PRU_DETACH请求。

图32-11 rip_usrreq 函数:PRU_DISCONNECT

、PRU_ABORT 和PRU_DETACH 请求

217-222 如果处理PRU_DISCONNECT请求的插口没有进入连接状态,则返回错误。 223-225

尽管禁止在一个原始插口上发送 P R U _ A B O R T请求,这个 c a s e 语句实际上是

PRU_DISCONNECT请求处理的延续。插口转入断开状态。 226-230

close系 统 调 用 发 送 PRU_DETACH 请 求 , 这 个 case语 句 还 将 结 束

P R U _ D I S C O N N E C T请求的处理。如果 s o c k e t结构用于多播选路 (i p _ m r o u t e r),则调用 i p _ m r o u t e r _ d o n e 取 消 多 播 选 路 。 一 般 情 况下, m r o u t e d ( 8 )守 护 程 序 会通过 D V M P R _ D O N E插口选项取消多播选路,因此,这个条件用于防止 m r o u t e d (8) 在没有正确设 定插口选项之前就异常终止了。 231 调用in_pcbdetach释放Internet PCB,并从原始 IP PCB表(rawinpcb)中删除。 通过P R U _ B I N D请求,可以把原始 I P插口绑定到某个本地 I P地址上,如图 3 2 - 1 2所示。我 们在rip_input中指出,插口将只能接收发向该地址的数据报。 233-250

应用进程向s o c k a d d r _ i n结构填充本地IP地址。下列 3个条件必须全真,否则将

返回差错代码EADDRNOTAVAIL: 1) 至少配置了一个 IP接口; 2) 地址族应等于AF_INET (或者AF_IMPLINK,历史上人为造成的不一致 );和 3) 如果绑定的IP地址不等于0.0.0.0,它必须对应于某个本地接口。调用者的 sockaddr_in 中的端口号必须等于 0,否则,ifa_ifwithaddr将返回错误。 本地IP地址保存在 PCB中。

848

计计TCP/IP详解 卷 2:实现

图32-12 rip_usrreq 函数:PRU_BIND 请求

应用进程还可以在原始 I P插口与某个特定远端 I P地址间建立连接。我们在 r i p _ i n p u t中 指出,这样可以限制应用进程只能接收源 I P地址等于连接对端 I P地址的数据报。应用进程可 以同时调用 b i n d和c o n n e c t,或者两者都不调用,取决于它希望 r i p _ i n p u t对接收数据报 采用的过滤方式。图 32-13给出了PRU_CONNECT请求的处理逻辑。 251-270

如果调用者的 s o c k a d d r _ i n初始化正确,且至少配置了一个 I P接口,则指定的

远端地址将存储在 P C B中。注意,这一处理和 U D P插口建立与远端 I P地址的连接有所不同。 对于U D P,i n _ p c b c o n n e c t申请到达远端地址的一条路由,并把外出接口视为本地地址 (图 2 2 - 9 ) 。对于原始 I P ,只有远端 I P 地址存储到 P C B 中,除非应用进程还调用了 b i n d , rip_input将只比较远端地址。

图32-13 rip_usrreq 函数:PRU_CONNECT 请求

849

第 32章 原 始 IP计计

应用进程结束发送数据后,调用 s h u t d o w n,生成P R U _ S H U T D O W N请求,尽管应用进程 很少为原始 I P插口调用 s h u t d o w n。图3 2 - 1 4给出了 P R U _ C O N N E C T 2和P R U _ S H U T D O W N请求 的处理逻辑。

图32-14 PRU_CONNECT2

和PRU_SHUTDOWN

请求

271-273 原始IP插口不支持 PRU_CONNECT2请求。 274-279 socantsendmore置位插口标志,禁止所有输出。 图2 3 - 1 4中,我们给出了 5个写函数如何调用协议的 p r _ u s r r e q函数,发送 P R U _ S E N D请 求。图32-15给出了这个请求的处理逻辑。

图32-15 rip_usrreq 函数:PRU_SEND 请求

280-303 如果插口处于连接状态,则调用者不能指定目的地址 (nam参数)。如果插口未建立 连接,则需要指明目的地址。不管哪种情况,只要条件满足,

d s t 将等于目的 I P 地址。

r i p _ o u t p u t发送数据报。令 m b u f指针m为空,防止函数结束时释放 m b u f链。因为接口输出

850

计计TCP/IP详解 卷 2:实现

例程发送数据报之后会释放 m b u f链( 记住, r i p _ o u t p u t 向i p _ o u t p u t 提交 m b u f链, ip_output把它加入到接口的输出队列中 )。 图3 2 - 1 6给出了 r i p _ u s r r e q的最后一部分代码。由 f s t a t系统调用生成的 P R U _ S E N S E 请求,没有返回值。 P R U _ S O C K A D D R 和P R U _ P E E R A D D R 请求分别由 g e t s o c k n a m e 和 getpeername系统调用生成。原始 IP插口不支持其余请求。 319-324

函数i n _ s e t s o c k a d d r和i n _ s e t p e e r a d d r能够从P C B中读取信息,在 n a m参

数中返回结果。

图32-16 rip_usrreq 函数:剩余的请求

32.8 rip_ctloutput函数 s e t s o c k o p t和g e t s o c k o p t函数会调用 r i p _ c t l o u t p u t,它处理一个IP插口选项和 8 个用于多播选路的插口选项。 图32-17给出了rip_ctloutput函数的第一部分。

图32-17 rip_usrreq 函数:处理 IP_HDRINCL 插口选项

851

第 32章 原 始 IP计计

图32-17 (续)

144-172

保存新选项值或者选项当前值的 mbuf至少要能容纳一个整数。对于 setsockopt

系统调用,如果 m b u f中的整数值非零,则设定该标志,否则清除它。对于 g e t s o c k o p t系统 调用,m b u f中的返回值要么等于 0,要么是非零的选项值。函数返回,以避免 s w i t c h语句结 束时处理其他IP选项。 图32-18给出了rip_ctloutput函数的最后一部分,处理 8个多播选路插口选项。

图32-18 rip_usrreq 函数:处理多播选路插口选项

173-188

这 8个插口选项只对 s e t s o c k o p t 系统调用有效,它们由图

1 4 - 9 讨论的

852

计计TCP/IP详解 卷 2:实现

ip_mrouter_cmd函数处理。 189 所有其他IP插口选项,如设定 IP选项的IP_OPTIONS,则由ip_ctloutput处理。

32.9 小结 原始插口为 IP主机提供 3种功能。 1) 用于发送和接收 ICMP和IGMP报文。 2) 支持应用进程构建自己的 IP首部。 3) 允许应用进程支持基于 IP的其他协议。 原始IP较为简单—只填充IP首部的有限几个字段—但它允许应用进程提供自己的 IP首 部。例如,调试程序就能发送任何类型的 IP数据报。 原始I P输入提供了 3种处理方式,能够选择性地接收进入的 I P数据报。应用进程基于下列 因素选择接收数据报: (1) 协议字段; (2) 源I P地址 (由c o n n e c t指明 );(3) 目的I P地址 (由 bind指明)。应用进程可以任意组合上述 3种过滤条件。

习题 32.1 假定 I P _ H D R I N C L 插口选项未设定。如果 s o c k e t 的第三个参数等于 0 , r i p _ o u t p u t填入I P首部协议字段 (i p _ p)的值是多少?如果 s o c k e t的第三个参数 等于IPPROTO_RAW (255),rip_output填入该段(ip_p)的值又是多少? 32.2 应用进程创建了一个原始插口,协议值等于 I P P R O T O _ R A W ( 2 5 5 )。应用进程在这 个插口上将收到什么类型的 IP数据报? 32.3 应用进程创建了一个原始插口,协议值等于 0。应用进程在这个插口上将收到什么 类型的IP数据报? 32.4 修改 r i p _ i n p u t,在适当情况下发送代码等于 2 ( 协议不可达 )的I C M P目的不可达 报文。请注意,不要为 rip_input正处理的ICMP或IGMP数据报生成一个差错。 32.5 如果应用进程希望生成自己的 IP数据报,自己填充IP首部字段,可使用IP_HDRINCL 选项置位的原始 IP插口,或者采用 BPF(第31章),两种方法的区别是什么? 32.6 什么时候应用进程应该读取原始 IP插口?什么时候读取 BPF?

附录A 部分习题的解答 第1章 1.2 S L I P驱动程序执行 s p l t t y(图1 - 1 3 ),其优先级必须低于或等于 s p l i m p,且高于 splnet。因此,SLIP驱动程序由于中断而被阻塞。

第2章 2.1 M_EXT标志是mbuf自身的一个属性,而不是 mbuf中保存的数据报的属性。 2.2 调用者请求大于 100字节(MHLEN)的连续空间。 2.3 不可行,因为多个 m b u f都可指向簇 ( 2 . 9节)。此外,簇中也没有用于后向指针的空间 (习题2.4)。 2.4 在< s y s / m b u f . h >定义的宏M C L A L L O C和M C L F R E E中,我们看到引用计数器是一个 名为mclrefcnt的数组。它在内核初始化时被分配,代码文件为 machdep.c。

第3章 3.3 采用很大的交互式队列,并不符合建立队列的目的,新的交互式流量跟在原有流量 之后,会造成附加时延。 3.4 因为sl_softc结构都是全局变量,内核初始化时都被置为 0。 3.5

9字节 3字节

环回 9字节 3字节

图 A-1

第4章 4.1 l e r e a d必须查看数据报,确认把数据报提交给 B P F之后,是否需要将其丢弃。因为

855

附录 A 部分习题的解答计计

B P F开关会造成接口处于一种混杂模式,数据报的目的地有可能是以太网中的其他 主机,BPF处理完毕后,必须将其丢弃。 如果接口没有加开关,则必须在 ether_input中完成这一测试。 4.2 如果测试反过来,广播标志永远不会置位。 如果第二个 if前没有else,所有广播分组都会带上多播标志。

第5章 5.1 环回接口不需要输入函数,因为它接收的所有分组都直接来自于 l o o u t p u t,后者 实际完成了输入功能。 5.2 堆栈分配快于动态存储器分配。对 B P F处理,性能是首要考虑的因素,因为对每个 进入数据报都会执行该代码。 5.5 缓存溢出的第一个字节被丢弃, S C _ E R R O R置位, s l i n p u t重设簇指针,从缓存起 始处开始收集字符。因为 SC_ERROR置位,slinput收到SLIP END字符后,丢弃当 前接收的数据帧。 5.6 如果检验和无效或者 IP首部长度与实际数据报长度不匹配,数据报被丢弃。 5.7 因为ifp指向le_softc结构的第一个成员, sc = (struct le_softc*) ifp;

sc初始化正确。 5.8 这是非常困难的。某些路由器在开始丢弃数据报时,可能会发送 I C M P源站抑制报文 段,但N e t / 3实现中的 U D P插口丢弃这些报文段 (图2 3 - 3 0 )。应用程序可以使用与 T C P 所采用的相同技术:根据确认的数据报估算往返时间,确认可用的带宽和时延。

第6章 6.1 I P子网出现之前 (RFC 950 [Mogul和Postel 1985]),I P地址的网络和主机部分都以字 节为界。 in_addr结构的定义如下:

图 A-2

I n t e r n e t地址读写单位既可以是 8 bit 字节,也可以是 16 bit 单字,或 32 bit 双字。宏 s_host、s_net、s_imp等等,它们的名字明确地反应出早期 TCP/IP网络的结构。 子网和超网概念的引入,淘汰了这种以字节和单字区分的做法。 6.2 返回指向结构sl_softc[0]的指针。

856

计计TCP/IP详解 卷 2:实现

6.3 接口输出函数,如 e t h e r _ o u t p u t,只有一个指向接口 i f n e t结构的指针,而没有 指向i f a d d r的指针。在 a r p c o m结构(最后一次为接口设定的 I P地址)中使用 I P地址 可以避免从 ifaddr地址链表中寻找所需地址。 6.4 只有超级用户进程才能创建原始 I P插口。通过 U D P插口,任何用户进程能够查看接 口配置,但内核仍拥有超级用户特权,能够修改接口地址。 6.5 有3个函数循环处理网络掩码,一次处理一个字节。它们是 i f a _ i f w i t h n e t 、 ifaof_ifpforaddr和rt_maskedcopy。较短的网络掩码能够提高这些函数的性能。 6.6 与远端系统建立 Te l n e t连接。 N e t / 2系统不应该转交这些数据报,而其他系统不会接 受到达环回接口之外的非环回接口的环回数据报。

第7章 7.1 下列调用返回指向 inetsw[6]的指针: pffindproto(PF_INET, 0, SOCK_RAW);

第8章 8.1 可能不会。系统不可能响应任意的广播报文,因为没有可供响应的源地址。 8.4 因为数据报已经损坏,无法知道首部中的地址是否正确。 8.5 如果应用程序选取的源地址与指定的外出接口的地址不同,则无法发送到下一跳路由 器。如果下一跳路由器发现数据报源地址与其到达的子网地址不符,则不会执行下一 步的转发操作。这是尽量减少终端系统复杂性带来的后果,RFC 1122指出了这一问题。 8.6 新主机认为广播报文来自于某个没有划分子网的网络中的主机,并试图将数据报发 回给源主机。网络接口开始广播 A R P请求,向网络请求该广播地址,当然,这一请 求永远不会收到响应。 8.7 减少T T L的操作出现在小于等于 1的测试之后,是为了避免收到的 T T L等于0,减1后 将等于255,从而引起操作差错。 8.8 如果两个路由器彼此认为对方是某个数据报的下一跳路由器,则形成环路。除非该 环路被打破,原始数据报在两个路由器间来回传递,并且每个路由器都向源主机发 送I C M P重定向报文段,如果该主机与路由器处于同一个网络中。路由更新时,不同 路由器中的路由表暂时存在的不一致现象,会造成这种环路。 原始报文段的TTL最终减为0,数据报被丢弃。这是 TTL存在的一个主要原因。 8.9 不会检查4个以太网广播地址,因为它们不属于接收接口。但应检查有限的广播地址,说 明带有SLIP链路的系统采用有限的广播地址,即使不知道对端地址,也能与对端通信。 8.10 只对数据报的第一个分片 (分片偏移量等于 0 )生成I C M P差错报文。无论是主机字节 序,还是网络字节序, 0的表示都相同,因此无需转换。

第9章 9.1 RFC 11 2 2建议如果数据报中的选项彼此冲突,处理方式由各实现代码自己决定。 N e t / 3能正确处理第一个源路由选项,但因为它会更新数据报首部的 i p _ d s t,第二 条源路由处理将出现差错。 9.2 网络中的主机也可以用做到达网络其他主机的中继。如果目的主机不可直接到达,

857

附录 A 部分习题的解答计计

9.3

9.4

9.5 9.6

源主机可在数据报中加入路由,首先到达中继主机,接着到达最终的目的主机。路 由器不会丢弃数据报,因为目的地址指向中继主机,后者将处理路由并把数据报转 发给最终目的主机。目的主机把路由反转,同样利用中继主机转发响应。 采用与前一个习题同样的原则。我们选取一个能够同时与源主机和目的主机通信的 中继路由器,并构造源路由,穿过中继路由器到达目的地址。中继路由器必须与目 的地址处于同一个网络,通信中无需默认路由。 如果源路由是仅有的 I P选项,N O P选项使得所有 I P地址以4字节边界对齐,从而能够 优化存储器中的地址读取操作。这种对齐技术也适用于多个 I P选项,如果每个 I P选 项都通过NOP填充,保证按4字边界对齐。 不应混淆非标准时间值和标准时间值,最大的标准时间值等于86 399 399(24×60×60× 1000-1),需要28 bit才能表示。由于时间值有32 bit,从而避免了高位比特的混淆问题。 源路由选项代码在处理过程中可能会改变 i p _ d s t。保存目的地址,从保证时间戳处 理使用原始目的地址。

第10章 10.2 重装后,只有第一个分片的选项上交给运输层协议。 10.3 因为数据长度(204 + 20 )大于208(图2-16)。 图10-11中的m_pullup把头40字节复制到一个单独的 mbuf中,如图2-18所示。

id=6的 ipq ipq首部

数据报起始 20字节

接下来的184字节数据报

2048字节簇



A-3

858

计计TCP/IP详解 卷 2:实现

10.5 平均每个数据报收到的分片数等于 7 2 7 8 6− 349 = 4.4 16 557

平均每个输出数据报新建的平均分片数等于 796 084 = 3.1 2 6 0484

10.6 图10-11中,数据报最初被做为分片处理。当 ip_off左移时,保留的比特位被丢弃。 得到的数据报被视为分片或一个完整的数据报,取决于 MF和分片偏移量的值。

第11章 11.1 输出响应使用收到请求的接口的源地址。主机可能无法辩识 0 . 0 . 0 . 0是一个有效的广 播地址,因此,有可能忽略请求。推荐的广播地址等于 255.255.255.255。 11.2 假定主机发送了一个链路层的广播数据报,其源 I P地址是另一台主机的地址,且数 据报有差错,如内容差错的选项。所有主机都能接收并检测出差错,因为这是一个 链路层的广播报文,而且选项的处理先于最终目的地的检测。许多发现差错的主机 会向数据报的源 I P地址发送 I C M P报文,即使原数据报属于链路层广播。另一台主 机将收到大量假的 I C M P差错。这就是为什么不允许为链路层广播而发送 I C M P差错 报文。 11.3 第一个例子中,这种重定向报文不会诱骗主机向另一个子网中的某个主机发送报文 段。这台主机可能被误认为是路由器,但它确实记录收到的流量。 RFC 1009规定路 由器只能向位于同一个子网的其他路由器发送重定向报文。即使主机忽略了这些要 求把数据报转发到另一个子网的报文段,但如果报文段发送者与主机处于同一个子 网中,它们就会被接受。第二个例子,为了防止出现上述现象,要求主机只接受它 (错误地 )选定的原始路由器的重定向报文,即假定这个错误的路由器是管理员指定 的默认路由器。 11.4 通过向 r i p _ i n p u t传递报文段,进程级的守护程序能够正确响应,一些依赖于这 种行为的老系统能够继续得到支持。 11.5 I C M P差错只针对 I P数据报的第一个分片。因为第一个分片的偏移量值必等于 0,字 段的字节表示顺序是无关紧要的。 11.6 如果收到ICMP请求的接口还未配置 IP地址,则ia将为空,且不生成响应。 11.7 Net/3处理与时间戳响应一起到达的数据。 11.10 高位比特被保留,并必须设为 0。如果它必须被发送,则 i c m p _ e r r o r将丢弃数据 报。 11.11 返回值被丢弃,因为 i c m p _ s e n d不返回差错。更重要的是, I C M P报文处理过程 中生成的差错将被丢弃,以避免进入死循环,不断生成差错报文。

第12章 12.1 以太网中, I P广播地址 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5转换为以太网的广播地址 ff : ff : ff : ff : ff : ff,网 络中的所有以太网接口都会接收这样的数据帧。没有运行 I P软件的系统必须主动接

859

附录 A 部分习题的解答计计

收并丢弃这种广播报文。 数据报需发送给多播组 2 2 4 . 0 . 0 . 1 中的所有主机,转换后的以太网多播地址为 0 1 : 0 0 : 5 e : 0 0 : 0 0 : 0 1,只有明确要求其接口接收 I P多播报文的系统才会收到它。 没有运行 I P或者在链路层不兼容的系统不会收到这些报文段,因为以太网接口的硬 件已直接丢弃了这些报文段。 12.2 一种替代方案是通过文本名规定接口,如同 i f r e q结构和i o c t l命令存取接口信息 采取的方式一样。 i p _ s e t m o p t i o n s和i p _ g e t m o p t i o n s可以调用 i f u n i t,取 代INADDR_TO_IFP,寻找指向接口 ifnet结构的指针。 12.3 多播组高位 4 bit通常为1110,因此,只有5个有意义的比特被匹配函数丢弃。 12.4 完整的 i p _ m o p t i o n s结构必须能放入单个 m b u f中,从而限制结构最大只能等于 1 0 8字节(记住2 0字节的 m b u f首部)。I P _ M A X _ M E M B E R S H I P S可以大一些,但必须 小于等于25(4+1+1+2+(4×25)=108)。 12.5 数据报重复,在 I P输入队列中有两份复制的数据报。多播应用程序必须能识别并丢 弃重复的数据报。 12.6

多播路 由器

主机

图 A-4

12.8 应用进程可以创建第二个插口,并通过第二个插口请求 IP_MAX_MEMBERSHIPS。 12.9 为mbuf首部的m_flags成员变量定义一个新的 mbuf标志M_LOCAL。ip_output处 理环回数据报时置位该标志,从而取代检验和。如果该标置位, i p i n t r就跳过检 验和验证。SunOS 5.X提供完成此功能的选项 (ip_local_cksum,卷1的531页)。 12.10 存在223-1(8 388 607)个独立的以太网 IP多播地址。记住保留的 IP组224.0.0.0。 12.11 这个假设正确,因为 i n _ a d d m u l t i拒绝所有新增请求,如果接口没有调用 ioctl函 数,说明如果if_ioctl为空,则永不会调用 in_delmulti。 12.12 m b u f 永远不会被释放,说明 i p _ g e t m o p t i o n s 包含了一个存储器泄露。 ip_getmoptions由ip_ctloutput调用,调用语句如下: ip_getmoptions(IP_ADD_MEMBERSHIP, 0, mp)

会引发ip_getmoptions中的一个差错。

第13章 13.1 要求环回接口响应 I C M P请求是没有必要的,因为本地主机是环回网络中唯一的系 统,它已经知道自己的成员状态。 13.2 m a x _ l i n k h d r + s i z e o (f s t r u c t i)p+IGMP_MINLEN=16+20+8=440。 16.5 应用进程阻塞,直到缓存解锁。本例中,只有在另一个进程检查缓存或向协议层传 送数据时,缓存才会被锁定;而在应用进程等待缓存可用空间时不会加锁,后者有 可能等待无限长的时间。 16.6 如果发送缓存包括许多 m b u f,每个都包括若干字节的数据,那么当 m b u f分配大块 存储器时, s b _ c c很可能大大低于 s b _ h i w a t规定的限制。如果内核不限制每个缓 存可拥有的 mbuf的数量,应用进程就能轻易地造成存储器枯竭。 16.7 recvit分别由r e c v f r o m和r e c v m s g调用。只有 r e c v m s g处理控制信息。它把完 整的 m s g h d r 结构,包括控制信息长度,复制给应用进程。至于地址信息, r e c v m s g把n a m e l e n p参数设为空,因为它可从 m s g _ n a m e l e n中得到所需长度。 当r e c v f r o m调用r e c v i t时,n a m e l e n p非空,因为函数需要从 * n a m e l e n p中得 到所需长度。 16.8 M S G _ E O R由s o r e c e i v e 清除,因此,它不可能在 M _ E O R m b u f被处理前,被 soreceive返回。 16.9 s e l e c t检查描述符时,实际上存在一种竞争。如果某个选定事件发生在 s e l s c a n 查看描述符之后,但在 s e l e c t调用t s l e e p之前,该事件不会被发现,应用进程 将保持睡眠状态,直到下一个选定事件发生。

第17章 17.1 简化在内核和应用进程间复制数据的代码。copyin和copyout可用于单个的mbuf, 但需要uiomove处理多个mbuf。 17.2 代码工作正确,因为 linger结构的第一个成员是所要求的整数标志。

第18章 18.1 做一个 8行的表格,每行对应一种查找键、路由表键和路由表掩码中比特的组合方 式: 行

1 查找键

2 路由表键

3 路由表掩码

1&3

2 = = 4?

1^2

6&3

1 2 3 4 5 6 7 8

0 0 0 0 1 1 1 1

0 0 1 1 0 0 1 1

0 1 0 1 0 1 0 1

0 0 0 0 0 1 0 1

是 是 否 否 是 否 否 是

0 0 1 1 1 1 0 0

0=是 0=是 0=是 1=否 0=是 1=否 0=是 0=是

图 A-5

标注为“2 == 4?”和标注为“ 6 & 3”的两栏,值应相等。第一眼看上去,似乎并 不完全相同,但我们可以略过第 3行和第 7行,因为这两行中路由表比特等于 1,而

862

计计TCP/IP详解 卷 2:实现

在路由表掩码中的对应比特也等于 1。构建路由表时,键值与掩码逻辑与,保证掩 码中的等于 0每一比特位,键值中的对应比特位也等于 0。 可以从另一个角度理解图 1 8 - 4 0中的异或和逻辑与操作,异或结果等于 1的条件是查 找键比特不同于路由表键值中对应的比特位。之后的逻辑与操作忽略所有与掩码中 等于0的比特相对应的比特。如果结果依然非零,则查找键与路由表键值不匹配。 18.2 r t e n t r y结构的大小等于 1 2 0字节,其中包括两个 r a d i x _ n o d e结构。每条记录还 要求两个 sockaddr_in结构(图18-28),有152字节。总数约为 3兆字节。 18.3 因为 r n _ b是一个短整数,假定短整数占 16 bit ,因此,每个键值最多有 3 2 7 6 7 bit(4095字节)。

第19章 19.1 图19-15中,如果重定向报文创建了新的路由,将置位 R T F _ D Y N A M I C标志;如果重 定向报文修改了现有路由的网关字段,则置位 R T F _ M O D I F I E D标志。如果重定向 报文新建了一条路由,之后另一个重定向报文又修改了它,则两个标志都会置位。 19.2 在每个可通过默认路由到达的主机上创建一条主机路由。 T C P能够对每个主机维护 并更新路由矩阵 (图27-3)。 19.3 每个r t _ m s g h d r结构需要 7 6字节。主机路由中包括还两个 s o c k a d d r _ i n结构(目的地 和网关 ),因此,报文段大小为 1 0 8字节。每条 A R P记录的报文段为 11 2字节:一个 sockaddr_in和一个sockaddr_dl。总长度等于(15×112+20×108)即3840字节。 一条网络路由 (非主机路由)还需要另外的 8个字节存放网络掩码 (数据大小等于 116字 节,而非108字节),因此,如果20条路由全部为网络路由,总长度等于 4000字节。

第20章 20.1 返回值放入报文段的 r t m _ e r r n o成员变量中 (图2 0 - 1 4 ),同时也做为 w r i t e的返回 值(图2 0 - 2 2 )。后者更可靠,因为前者可能会因为 m b u f短缺,而丢弃响应报文段 (图 20-17)。 20.2 对S O C K _ R A W型的插口, p f f i n d p r o t o函数(图7 - 2 0 )将返回协议值等于 0 (通配)的 记录,如果没有找到可匹配的记录。

第21章 21.1 它基于假定 ifnet结构位于arpcom的开头,事实也是如此 (图3-20)。 21.2 发送I C M P的回显请求不需要 A R P,因为目的地址是广播地址。但 I C M P的回显响应 一般都是点对点的,因此,发送者必须通过 A R P确定目的以太网地址。本地主机收 到ARP请求时,in_arpinput应答并为另一主机创建一条记录。 21.3 如果创建了一条新的 A R P 记录,图 1 9 - 8 中的 r t r e q u e s t 从源记录中复制 r t _ g a t e w a y值,本例中为 s o c k a d d r _ d l结构。图 2 1 - 1中,我们看到该记录的 sdl_alen值等于0。 21.4 N e t / 3中,如果 a r p r e s o l v e的调用者提供了指向路由表表项的指针,则不会再调 用a r p l o o k u p,通过 r t _ g a t e w a y指针可得到所需的以太网地址 (假定它还未超

863

附录 A 部分习题的解答计计

时)。这样可以避免通常意义上的任何类型的查询。第 2 2章中,我们将看到 T C P和 U D P在自己的协议控制块中保存指向路由表的指针, T C P不再需要搜索路由表 (连 接的目的IP地址不会变化),在目的地址不变时 UDP也不需这样做。 21.5 如果A R P记录不完整,则它在记录创建后 0 ~ 5分钟超时。 a r p r e s o l v e发送A R P请 求时,令 r t _ e x p i r e等于当前时间。下一次执行 a r p r e s o l v e时,如果记录还没 有解析,则删除它。 21.6 e t h e r _ o u t p u t返回E H O S T U N R E A C H,而非 E H O S T D O W N,从而 i p _ f o r w a r d将 发送ICMP主机不可达差错报文。 21.7 图21-28中,为140.252.13.35创建记录时,值等于当前时间。它不会改变。 1 4 0 . 2 5 2 . 1 3 . 3 3和1 4 0 . 2 5 2 . 1 3 . 3 4记录的值复制自 1 4 0 . 2 5 2 . 1 3 . 3 2,因为 r t r e q u e s t根 据140.252.13.32复制前两条记录。之后, a r p r e s o l v e发送ARP请求时,把这两条 记录的值更新为当前时间,最后由 i n _ a r p i n p u t将其更新为收到 A R P响应的时间 加上20分钟。 21.8 修改图21-19开始处的arplookup,第二个参数永远等于 1(创建标志)。 21.9 在下一秒的后半秒发送第一个数据报。因此,第一个和第二个数据报都会导致发送 A R P请求,间隔约为 500 ms,因为内核的 t i m e . t v _ s e c变量在这两个数据报发送 时的值不同。 21.10 每个待发送的数据报都是一个 m b u f链, m _ n e x t p k t指针指向每个链的第一个 mbuf,用于构成等待传输的 mbuf链表。

第22章 22.1 无限循环等待某个端口变为可用,假定允许应用进程打开足够多的描述符,绑定所 有临时端口。 22.2 极少有服务器支持此选项。 [ C h e s w i c k和Bellovin 1994]提到为什么它可用于实现防 火墙系统。 22.4 u d b结构初始化为 0,因此, u d b . i n p _ l p o r t从0开始。第一次调用 i p _ p c b b i n 时,它增加为1,因为小于 1024,所以被设定为 1024。 22.5 一般情况下,调用者把地址族 (s a _ f a m i l y)设为A F _ I N E T,但我们在图 2 2 - 2 0的注 释中看到,最好不进行关于地址族的测试。调用者设定长度变量 (s a _ l e n),但我 们在图 1 5 - 2 0 中看到,函数 s o c k a r g s 将其做为 b i n d 的第 3 个参数,对于 sockaddr_in结构,应等于16,通常都使用C的sizeof操作符。 本地 I P 地址 (s i n _ a d d r )可以指明为通配地址或某个本地 I P 地址。本地端口号 (s i n _ p o r t ),可以等于 0 (告诉内核选择一个临时端口 )或非 0,如果应用进程希 望指明端口号。通常情况下, T C P或U D P服务器指明一个通配 I P地址,端口号等 于0。 22.6 应用进程可以 b i n d一个本地广播地址,因为 i f a _ i f w i t h a d d r(图22-22)的调用成 功。它被用做在该插口上发送的 I P数据报的源地址。 C . 2节中指出, RFC 11 2 2不允 许这种做法。但试图绑定 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5时会失败,因为 i f a _ i f w i t h a d d r不接 受该地址。

864

计计TCP/IP详解 卷 2:实现

第23章 23.1 s o s e n d把用户数据放入单个的 m b u f中,如果其长度小于等于 1 0 0字节;放入两个 mbuf中,如果长度小于等于 207字节;否则,放入多个 mbuf中,每个都带有一个簇。 此外,如果长度小于 1 0 0字节, s o s e n d调用M H _ A L I G N,希望能在 m b u f起始处为 协议首部保留空间。因为udp_output调用M_PREPEND,下述5种情况都是可能的: ( 1 )如果用户数据长度小于等于 7 2字节,一个 m b u f就可以存放 I P首部、 U D P首部和 数据;(2)如果长度位于 73字节和100字节之间, s o s e n d为用户数据分配一个 mbuf, M _ P E R P E N D为I P和T C P首部再分配一个 m b u f;( 3 )如果长度位于 1 0 1字节和2 0 7字节 之间,s o s e n d为用户数据分配两个 mbuf,M _ P R E P E N D为IP和TCP首部再分配一个 m b u f;( 4 )如果长度位于 2 0 8字节和 M C L B Y T E S之间, s o s e n d为用户数据分配一个 带簇的m b u f,M _ P E R P E N D为I P和T C P首部再分配一个 m b u f;( 5 )如果长度超出,则 s o s e n d分配足够多的 m b u f和簇,以存放数据 (最大数据长度 6 5 5 0 7字节,需分配 6 4 个带1024字节簇的mbuf),M_PERPEND为IP和TCP首部再分配一个 mbuf。 23.2 IP选项提交给 i p _ o u t p u t,后者调用 i p _ i n s e r t o p t i o n s在输出IP数据报中插入 IP选项。它接着分配一个新的 mbuf,存放带有IP选项的IP首部,如果第一个 mbuf指 向一个簇 ( U D P输出不可能出现这种情况 ),或者第一个 m b u f中没有足够的剩余空间 存放新增选项。上个习题中给出的第一种情况中,选项大小将决定 ip_insertoptions是否分配另一个mbuf:如果用户数据长度小于 100-28-optlen (IP选项占用的字节数 ),说明mbuf足够存放IP首部、IP选项、UDP首部和数据。 第2、3、4和5种情况中, 第一个mbuf都由M_PREPEND分配,只存放IP和UDP首部。 M _ P R E P E N D调用M _ P R E P E N D,接着调用 M H _ A L I G N,把2 8字节的首部移到 m b u f 尾部,因此,第一个 mbuf中必定有空间存放最大为 40字节的IP选项。 23.3 不。函数 i n _ p c b c o n n e c t只有在应用程序调用 c o n n e c t,或者在一个未连接的 U D P插口上发送第一个数据报时,才会被调用。因为本地地址是通配地址,本地端 口号等于 0,所以 i n _ p c b c o n n e c t 给本地端口号赋一个临时端口 (通过调用 in_pcbbind),并根据到达目的地的路由设定本地地址。 23.4 处理器优选级仍为 splnet不变,没有还原为初始值,这是代码的一个差错。 23.5 不。 i n _ p c b c o n n e c t不允许与等于 0的端口建立连接。即使应用进程没有直接调 用connect,也会间接地执行 connect,因此,in_pcbconnect总会被调用。 23.6 应用程序必须调用 ioctl,命令为SIOCGIFCONF,返回所有已配置的 IP接口信息。 之后,在 i o c t l返回的所有 I P地址和广播地址中寻找接收数据报中的目的地址 (也 可不用 i o c t l,1 9 . 1 4节中介绍的 s y s c t l系统调用也能够返回所有配置接口的信 息)。 23.7 recvit释放带有控制信息的 mbuf。 23.8 为了断开一个已建立连接的 U D P插口,调用 c o n n e c t,传递一个无效的地址参数, 如 0 . 0 . 0 . 0 ,端口号等于 0 。因为插口已经建立了连接,

s o c o n n e c t 调用

s o d i s c o n n e c t,后者调用u d p _ u s r r e q,发送P R U _ D I S C O N N E C T请求,令远端 地址等于 0.0.0.0,远端端口号等于 0。这样,接下来调用 s e n d t o时可以指明目的地

865

附录 A 部分习题的解答计计

址。由于指明地址无效,sodisconnect发送的PRU_CONNECT请求失败。实际上, 我们不希望 c o n n e c t 成功,只是要执行 P R U _ D I S C O N N E C T 请求,而且通过 c o n n e c t 来执行这一请求的做法是唯一可行的方案,因为插口

A P I 没有提供

disconnect函数。 手册中关于c o n n e c t(2)的描述通常包括下述说明:“可通过把数据报插口连接到一 个无效地址,如空地址,来断开其当前连接。”但没有明确指出调用 c o n n e c t时, 如果传送的地址无效,会返回一个差错。“空地址”的含义也易造成混淆,它指 I P 地址0.0.0.0,而非bind的第二个参数的空指针。 23.9 因为i n _ p c b b i n d能够建立 UDP插口与远端IP地址间的临时连接,情况与应用进程 调用connec t类似:如果某接口的目的 IP地址与该接口的广播地址对应,则从该接 口发送数据报。 23.10 服务器必须设定 I P _ R E C V D S T A D D R插口选项,并调用 r e c v m s g从客户请求中获 取目的 I P地址。为了成为响应报文段中的源地址,必须将其绑定在插口上。由于 一个插口只能bind一次,服务器每次响应时都必须创建新的插口。 23.11 注意,i p _ o u t p u t(图8 - 2 2 )中,I P不修改调用者传递的 D F比特。需要定义新的插 口选项,促使udp_output在把数据报传递给 IP之前,设定 DF比特。 23.12 不。它只被 udp_input使用,且应为该函数的局部变量。

第24章 24.1 状态为ESTABLISHED的连接总数为126 820。除以发送和接收的总字节数,得到每 个方向上的平均字节数,约为 30 000字节。 24.2 t c p _ o u t p u t 中,保存 I P 和 T C P 首部的 m b u f 还有空间容纳链路层首部 (m a x _ l i n k h d r)。试图通过b c o p y把I P和T C P首部原型复制到 m b u f中是行不通的, 因为有可能会把 40字节的首部分散在两个 mbuf中。尽管40字节的首部必须放入单个 mbuf中,但链路层首部不存在这样的限制。不过这样做会降低性能,因为后续处理 不得不为链路层首部再次分配 mbuf。 24.3 在作者的 b s d i 系统中,计数器等于 1 6,其中 1 5个是标准系统守护程序 ( Te l n e t、 Rlogin、FTP,等等)。而v a n g o g h . c s . b e r k e l e y . e d u系统,一个约有 20个用户 的中等规模的多用户系统,计数器约为 6 0。对于大型的带有 1 5 0个用户的多用户系 统(world.std.com),则有417个TCP端点和809个UDP端点。

第25章 25.1 图24-5中,2 592 000秒 (30天)中出现了531 285次延迟ACK,平均每5秒钟有一次延 迟A C K,或者说每 2 5次调用 t c p _ f a s t t i m o,才会有一次延迟 A C K。这说明在代 码检查所有 T C P控制块,判定延迟 A C K标志是否置位时, 9 6 % ( 2 5次中有 2 4次)的时 间都未置位。对于习题 2 4 . 3中给出的大型的多用户系统,意味着需查看超过 4 0 0个 的TCP控制块,每秒钟查询 5次。 另一种解决方案是,在需要延迟 A C K时,设定全局标志。只有当全局标志置位时, 才检查控制块列表。或者为需要延迟 ACK的控制块单独建立并维护一个列表。例如,

866

计计TCP/IP详解 卷 2:实现

图13-14中的变量igmp_timers_are_running。 25.2 这 样 使 得 变 量 t c p _ k e e p i n t v l 绑 定 在 运 行 中 的 内 核 上 , 下 次 调 用 tcp_slowtimo时,内核可以改变 tcp_maxidle的值。 25.3 t _ i d l e中保存的实际上是从最后一次接收或发送报文段后算起的时间。因为 T C P 的输出必须被对端确认,与收到数据报文段相同,收到 ACK也将清零t_idle。 25.4 图A-6给出代码的一种可能的重写方式。

图 A-6

25.5 如果收到了重复的 A C K,t _ i d l e等于1 5 0,但被复位为 0。FIN_WAIT_2时钟超时 后,t _ i d l e将等于1048 (11 9 8-1 5 0 ),因此,定时器被设定为 1 5 0个滴答。定时器 再次超时,t _ i d l e应等于11 9 8 + 1 5 0,导致连接被关闭。重复 A C K令时间延长,直 到连接被关闭。 25.6 第一次连接探测报文段将在 1小时后发送。应用进程设定该选项时,实际只置位了 s o c k e t结构中的 S O _ K E E P A L I V E选项。由于设定了该选项,定时器将于 1小时后 超时,图25-15中的代码将发送第一次连接探测报文段。 25.7 t c p _ r t t d f l t用于为每条 T C P连接初始化 RT T估计器的值。如果需要,主机可通 过更改全局变量,改变默认设置。如果通过 #define将其定义为常量,则只有通过重 新编译内核文件才能改变默认值。

第26章 26.1 事实上, T C P并没有刻意计算从连接上最后一次发送报文段算起的时间,因为连接 上的计时器 t_idle一直在起作用。 26.2 图25-26中,snd_nxt被设定为snd_una,len等于0。 26.3 如果运行 Net/3系统,但对端主机却无法处理某个新选项 (例如,对端拒绝建立连接, 即使被要求忽略无法辨识的选项 )。遇到这种情况时,通过在内核中改变这个全局 变量的值,可以禁止某一个或两个选项。 26.4 时间戳选项能够在每次收到对新数据的 A C K时,更新 RT T估计器值。因此,使用时 间戳选项后, RT T估计器值将被更新 1 6次,是不使用该选项时更新次数的两倍。注 意,在时刻 2 1 7 . 9 4 4时,收到了对 6 1 4 5的A C K,RT T估计器值被更新,但这个新的 计算值并不准确—或者是在时刻 3 . 7 4 0发送的携带5 6 3 3 ~ 6 1 4 4字节的数据段,或者 是收到的对 6145的ACK,在网络中延迟了 200秒。 26.5 这种存储器引用时,无法确保 2字节的MSS值能够正确地对齐。

867

附录 A 部分习题的解答计计

26.6 (该解决方案来自于 Dave Borman) 一个数据段能够携带的 T C P数据量的最大值为 6 5 4 9 5字节,即 6 5 5 3 5减去I P和T C P首部的最小值 ( 4 0 )。因此,紧急数据偏移量可取 值范围中有 3 9个值是无意义的: 6 5 4 9 6 ~ 6 5 5 3 5,包括 6 5 5 3 5。无论何时,只要发送 方得到一个超过 6 5 4 9 5的紧急数据偏移量,则将其替换为 6 5 5 3 5,并置位 U R G标志。 从而迫使接收方进入紧急模式,并告知接收方紧急数据偏移量所指向的数据尚未被 发送。发送方将持续发送紧急数据偏移量等于 6 5 5 3 5、且U R G标志置位的数据报文 段,直到紧急数据偏移量小于等于 65495,说明真正的紧急数据偏移量的开始。 26.7 我们提到,数据段的传输是可靠的 (重传机制 ),而A C K则有可能丢失。 R S T报文段 的传输同样也是不可靠的。如果连接上收到了一个假报文段 (例如,不属于本连接 的报文段,或者一个不属于任何连接的报文段 ),则传送RST报文段。如果 RST报文 段被i p _ o u t p u t丢弃,当对端重传导致发送 R S T报文段的数据报文段时,将再次 生成RST报文段。 26.8 应用程序执行了 8次写入1 0 2 4字节的操作。头 4次调用s o s e n d时,t c p _ o u t p u t被 调用,报文段被发送。因为这 4个报文段都包含了发送缓存中最后一个字节的数据, 每个报文段的 P S H标志都置位(图2 6 - 2 5 )。第二个缓存装满后,应用进程进行下一次 写操作,调用 s o s e n d时被挂起。收到对端通告窗口大小等于 0的A C K后,丢弃发 送缓存中已被确认的 4096字节的数据,应用进程被唤醒,又连续执行了 4次写操作, 发送缓存再次被填满。但只有当接收方通告窗口大小不等于 0时,才能继续发送数 据。条件满足时,接下来的 4个报文段被发送,但只有最后一个报文段的 P S H标志 置位,因为前3个报文段并未清空发送缓存。 26.9 如果正在发送的报文段不属于任何连接,传给 t c p _ r e s p o n d的t p参数可以是空指 针。代码只有在指针为空时,才会查看 tp,并代之以默认值。 26.10 t c p _ o u t p u t通常调用 M G E T H D R,分配一个仅能容纳 I P和T C P首部的m b u f,参见 图 2 6 - 2 5 和 图 2 6 - 2 6 。 在 新 的 m b u f 的 前 部 , 代 码 只 预 留 了 链 路 层首 部 (m a x _ l i n k h d r )大小的空间。如果使用了 I P 选项,而且选项的大小超过了 m a x _ l i n k h d r,i p _ i n s e r t o p t i o n s会自动分配另一个 mbuf。但如果 IP选项的 大小小于等于 max_linkhdr,则ip_insertoptions也会占用mbuf首部的空间, 从而导致ether_output仅为链路层首部分配另一个 mbuf(假定以太网输出)。 为了避免多余的 mbuf,图26-25和图26-26中的代码,可以在报文段中携带 IP选项时 调用MH_ALIGN。 26.11 约有80行代码,假定采用了 RFC 1323中的时间戳选项,且报文段被计时。 宏M G E T H D R调用了宏 M A L L O C,后者可能调用函数 m a l l o c。函数m _ c o p y也会 被调用,但一个完整大小的报文段可能需要一个簇,因此,不复制 m b u f,而是保 存一个对簇的引用。 m _ c o p y中调用 M G E T,可能会导致对 m a l l o c的调用。函数 bcopy复制模板,而in_cksum计算TCP的检验和。 26.12 调用w r i t e v没有区别,因为处理逻辑由 s o s e n d实现。因为数据大小等于 1 5 0字 节,小于 M I N C L S I Z E ( 2 0 8 ),所以为头 1 0 0个字节分配了一个 m b u f。并且因为协 议支持数据的分段, P R U _ S E N D请求被发送。接着为剩余的 5 0字节再分配一个 mbuf,并发送相应的 P R U _ S E N D请求(对于P R _ A T O M I C协议,如UDP,w r i t e v只

868

计计TCP/IP详解 卷 2:实现

生成一条“记录”,即只发送一个 PRU_SEND请求。) 如果两个缓存的长度分别等于 200和300,总长度超过了 M I N C L S I Z E,则分配一个 mbuf簇,且只发送一次 PRU_SEND请求。TCP只生成一个 500字节的报文段。

第27章 27.1 表中前 6行记录的差错,都是由于接收报文段或者定时器超时引起的异步差错。通 过在s o _ e r r o r中保存非零的差错代码,应用进程能够在下一次读 /写操作中收到差 错信息。但如果调用来自 t c p _ d i s c o n n e c t,说明应用进程调用了 c l o s e,或者 应用进程终止时系统自动关闭其所拥有的描述符。无论是哪一种情况,描述符被关 闭,应用进程不可能再通过读 /写操作来获取差错代码。此外,因为应用进程必须 明确设定插口选项,强迫 R S T置位,此时返回一个差错代码并不能向应用进程提供 有用的信息。 27.2 假定它是32 bit的u_long,最大值小于4298秒(1.2小时)。 27.3 路由表中的统计数据由 tcp_close更新,但只有当连接进入 CLOSED状态时,它才 会被调用。因为 F T P客户终止向对端发送数据 (执行主动关闭 ),本地连接端点进入 TIME_WAIT状态。必须经过 2MSL后,路由表统计值才会被更新。

第28章 28.1 0、1、2和3。 28.2 34.9Mb/s。对于更高的速率,连接两端需要更大的缓存。 28.3 通常, t c p _ d o o p t i o n不知道两个时间戳值是否按 32 bit边界对齐。图 2 8 - 4中的代 码,在指定情况下,能够确认时间戳值按 32 bit边界对齐,从而避免调用 bcopy。 28.4 图2 8 - 4中实现“选项预测”代码,只能处理系统推荐的格式。如果连接对端未采用 系统推荐的格式,会导致为每个接收到的报文段调用 t c p _ d o o p t i o n s,降低了处 理速度。 28.5 如果在每次创建插口时,而非每次连接建立时,调用 t c p _ t e m p l a t e,则系统中 的每个监听服务器都会拥有一个 tcp_template,而该结构可能永远不会被使用。 28.6 时间戳时钟频率应该在1 b/ms 和1 b/s之间(Net/3采用了2 b/s)。如果采用最高的时钟频 率1 b/ms,32 bit的时间戳将在231 / (24×60×60×1000)天,即24.8天后发生符号位回绕。 28.7 如果频率为每500 ms 1 bit,32 bit的时间戳将在231 / (24×60×60×2)天,即12 427 天,约34年后才会出现符号位回绕。 28.8 对R S T报文段的处理应优先于时间戳,而且, R S T报文段中最好不携带时间戳选项 (图26-24中的tcp_input代码确保了这一点 )。 28.9 因为客户端状态为 E S TA B L I S H E D,处理将在图 2 8 - 2 4的代码处结束。 t o d r o p等于 1,因为r c v _ n x t在收到第一个SYN时已递增过。SYN标志被清除 (因为这是一个重 复报文段),t i _ s e q递增,t o d r o p减为0。因为t o d r o p和t i _ l e n都等于0,执行 图2 8 - 2 5起始处的 i f语句,并跳过下一个 i f语句,直接调用 m _ a d j。但下一章中介绍 tcp_input后续代码时,将谈到在某些情况下不会调用 tcp_output,本题即是一例。 因此,客户端会不响应重复的 S Y N / A C K。服务器端超时后,再次发送 S Y N / A C K

869

附录 A 部分习题的解答计计

(图2 8 - 1 7中介绍了某个被动打开的插口收到 S Y N时,定时器的设置 ),这个重发的 SYN/ACK报文段同样被忽略。我们现在讨论的其实是图 28-25代码中的另一个差错, 图28-30中给出的代码同样也纠正了这一差错。 28.10 客户发出的 S Y N到达服务器,并被交给处于 T I M E _ WA I T状态的插口。图 2 8 - 2 4中 的代码关闭 S Y N标志,图 2 8 - 2 5中的代码跳转至 d r o p a f t e r a c k,丢弃该报文段, 但生成一个 A C K ,确认字段等于 r c v _ n x t ( 图 2 6 - 2 7 ) 。它被称作“再同步 (resynchronization) ACK”报文段,因为其目的是告诉对端本地希望接收的下一序 号。客户端收到此 A C K后(客户处于 S Y N _ S E N T状态),发现它的确认字段所携带 的序号不等于自己期待得到的序号后 (图2 8 - 1 8 ),向服务器发送 R S T报文段。 R S T 报文段的 A C K标志被清除,且序号等于再同步 A C K报文段中确认字段携带的序号 (图2 9 - 2 8 )。服务器收到此 R S T报文段后,其 TIME_WAIT状态提前终止,相应插口 被关闭(图28-36)。客户端6秒钟后超时,重传 SYN报文段。假定监听服务器进程在 服务器主机上运转正常,新的连接将建立。由于 TIME_WAIT状态的这种防护作用, 新连接建立时,下一个 S Y N报文段携带的序号既可以高于前一连接上最后收到的 序号(图28-28中的测试),也可以低于该序号。

第29章 29.1 假定 RT T等于2秒钟。服务器被动打开,客户端在时刻 0主动打开。服务器在时刻 1 收到客户发出的 SYN,并作出响应,发送自己的 SYN和对客户SYN的ACK。客户端 在时刻 2收到报务器的响应报文段,图 2 8 - 2 0中的代码调用 s o i s c o n n e c t e d(唤醒 客户进程 ),完成主动打开过程,并向服务器发送 A C K响应。服务器在时刻 3收到客 户的ACK,图29-2中的代码完成服务器端的被动打开过程,控制返回给服务器进程。 一般情况下,客户进程比服务器进程提早 1/2 RTT时间得到控制。 29.2 假定SYN的序号等于 1000,50字节数据的序号等于 1001~1050。t c p _ i n p u t处理此 SYN报文段时,首先执行图28-15中的起始case语句,令rcv_nxt等于1001,接着跳到 step6。图29-22中的代码调用cp_reass,把数据放入插口的重组队列中。但数据还不 能放入插口的接收缓存 ( 图2 7 - 2 3 ) ,因此, r c v _ n x t 还是等于 1 0 0 1 。在调用 tcp_output生成ACK响应时,rcv_nxt (1001)被放入ACK报文段的确认字段。也 说是说,SYN被确认,但与之同时到达的 50字节的数据没有被确认。因此,客户端不 得不重发50字节的数据,所以,在完成主动打开的SYN报文段中携带数据是没有意义 的。 29.3 客户端的 A C K / F I N报文段到达时,服务器处于 S Y N _ R C V D状态,因此,图 2 9 - 2中 的 t c p _ i n p u t 代码将结束对 A C K 的处理。连接转移到 E S TA B L I S H E D状态, t c p _ r e a s s把已在重组队列中的数据放入接收缓存, r c v _ n x t 递增为 1 0 5 1。 t c p _ i n p u t继续执行,图29-24中的代码负责处理 FIN标志,此时 T F _ A C K N O W标志 置位, r c v _ n x t等于1 0 5 2。s o c a n t r c v m o v e设定插口的状态,使服务器在读取 50字节的数据之后,得到“文件结束”指示。服务器的插口也转移到 C L O S E _ WA I T状态。调用 t c p _ o u t p u t,确认客户端的 F I N (因为 r c v _ n x t等于 1 0 5 2 )。假定服务器进程在收到“文件结束”指示后,关闭其插口,服务器也将向

870

计计TCP/IP详解 卷 2:实现

客户发送FIN,并等待回应。 在这个例子中,这了从客户端向服务器传送50字节的数据,双方需3个来回,共发送6 个报文段。为了减少所需的报文段数,应采用“用于交易的TCP扩展[Braden 1994]”。 29.4 收到服务器响应时,客户插口处于 S Y N _ S E N T状态。图 2 8 - 2 0中的代码处理该报文 段,连接转移到 ESTABLISHED状态,控制跳转到 s t e p 6,由图29-22中的代码继续 处理数据。 T C P _ R E A S S把数据添加到插口的接收缓存,并递增 r c v _ n x t。之后, 图2 9 - 2 4中的代码开始处理 F I N,再次递增 r c v _ n x t,连接转移到 CLOSE_WAIT状 态。在调用 t c p _ o u t p u t时,r c v _ n x t同时确认了SYN、50字节的数据和 FIN。随 后的客户进程首先读取 5 0字节的数据,接着是“文件结束”指示,并可能关闭其插 口。客户端连接进入 L A S T _ A C K状态,向服务器发送 F I N报文段,并等待其响应报 文段。 29.5 问题出在图 24-16中的tcp_outflags[TCPS_CLOSING]。它设定了TH_FIN标志, 而状态变迁图 (图2 4 - 1 5 )并未规定 F I N应被重传。解决问题的方法是,从该状态的 t c p _ o u t f l a g s中除去T H _ F I N标志。这个问题没有什么危害—只不过多交换两 个报文段—而且同时关闭或者在关闭后紧接着自连接的情况是非常罕见的。 29.6 没有。系统调用 w r i t e返回O K,只说明数据已复制到插口的缓存中。在数据得到 对端确认时, N e t / 3不再通知应用进程。如果需要得到此类信息,应设计并实现应 用级的确认机制。 29.7 RFC 1323的时间戳选项,造成“首部压缩”失效。因为只要时间戳变化,即 TCP选 项发生了改变,报文段发送时就不会被压缩。窗口大小选项无效,因为 T C P首部中 值的长度仍为16 bit。 29.8 IP 中I D字段的取值来自一个全局变量,只要发送一个 I P数据报,该变量递增一次。 这种方式导致在同一 T C P连接上两个连续 T C P报文段间的 I D差值大于 1的可能性大 大增加。一旦ID差值大于 1,图29-34中的∆ipid字段将被发送,增大了压缩首部的大 小。一个更好的解决方案是, TCP自己维护一个计数器,用于 ID的赋值。

第30章 30.2 是的,仍会发送 RST报文段。应用进程终止的处理中包括关闭它打开的所有描述符。 同一个函数 (s o c l o s e)最终会被调用,无论是应用进程明确地关闭了插口描述符, 还是隐含地进行了关闭 (首先被终止 )。 30.3 不。这个常量只有在监听插口设定 S O _ L I N G E R选项,且延迟时间等于 0时,才会被 用到。正常情况下,插口选项的这种设定方式会导致在连接关闭时发送 R S T报文段 (图30-12),但图30-2中对于接收连接请求的监听插口,把该值从 0改为120(滴答)。 30.4 如果这是第一次使用默认路由,则为两次;否则为一次。当创建插口时, in_pcballoc将Internet PCB置为0,从而将PCB结构中的route结构设为0。发送 第一个报文段 ( S Y N )时, t c p _ o u t p u t调用i p _ o u t p u t。因为 r o _ r t指针为空, 因此向 r o _ d s t填充I P数据报的目的地址,并调用 r t a l l o c。在该连接的 P C B中, route结构的ro_rt变量中保存默认路由。当ip_output调用ether_output时, 后者检查路由表中的 r t _ g w r o u t e变量是否为空。如果是,则调用 r t a l l o c 1。假

871

附录 A 部分习题的解答计计

定路由没有改变,该连接每次调用 t c p _ o u t p u t时,都会使用保存的 r o _ r t指针, 以避免多余的路由表查询。

第31章 31.1 因为在bpf_wakeup调用唤醒任何沉睡进程之前, catchpacket肯定会结束。 31.2

打开BPF设备的应用进程可能调用fork,导致多个应用进程都有权访问同一个BPF设备。

31.3 只有支持 B P F的设备才会出现在 B P F接口表 (b p f _ i f l i s t)中,因此,如果无法找 到指定接口,bpf_setif将返回ENXIO。

第32章 32.1 在第一个例子中等于 0,第二个例子中等于 255。这些值都是 RFC 1700 [Reynolds和 Postel 1994]中的保留值,不应出现在数据报中。也就是说,如果某个插口创建时的 协议号设定为 I P P R O T O _ R A W,则必须设定其 I P _ H D R I N C L插口选项,且写入到该 插口的数据报必须拥有一个有效的协议值。 32.2 因为 I P协议值 2 5 5是保留值,不会出现在网络中传送的数据报中。但这又是一个非 零的协议值, r i p _ i n p u t的3项测试中的第一项测试将忽略所有协议值不等于 2 5 5 的数据报。因此,应用进程无法在该插口上收到任何数据报。 32.3 即使该协议值是一个保留值,不会出现在网络中传送的数据报中,但 r i p _ i n p u t 的3项测试中的第一项测试保证此类型的插口能够接收任何协议类型的数据报。如 果应用进程调用了 connect或者bind,或者两者都调用,对于此种原始插口而言, 对输入的唯一限制是 IP报的源地址和目的地址。 32.4 因为i p _ p r o t o x数组(图7-22)保存了有关内核所能支持的协议类型的信息,只有在 该协议既没有相关的原始监听插口,而且指针

i n e t s w [ i p _p r o t o x [ i p -

>ip_p]].pr_input等于rip_input时,才会生成ICMP差错报告。 32.5 两种情况下,应用进程都必须自己构造 IP首部,以及其后的内容 (UDP报文段,TCP 报文段或任何其他的报文段 )。对于原始 I P插口,输出时同样调用 s e n d t o,通过 I n t e r n e t插口地址结构指明目的 I P地址。调用 i p _ o u t p u t,并依据给定的目的 I P地 址执行正常的IP选路。 B P F要求应用进程提供完整的数据链路层首部,例如以太网首部。输出时,需调用 w r i t e ,因为无法指明目的地址。数据分组被直接交给接口输出函数,跳过 i p _ o u t p u t函数(图3 1 - 2 0 )。应用进程通过 B I O C S E T I F i o c t l(图3 1 - 1 6 )选择外 出接口。因为未执行 I P选路,数据帧只能发给是直接相连的网络上的另一个主机 (除非应用进程重复 I P选路函数,并将数据帧发给直接相联网络上的某个路由器, 由路由器根据目的 IP地址完成转发)。 32.6 原始I P插口只能接收具有内核不处理的协议类型的数据报,例如,应用进程无法在 原始插口上接收 TCP报文段或UDP报文段。 B P F 能 够接 收到 达 指定 接口 的 所有 数据 帧, 无 论它 们是 否是 I P 数据 报 。 B I O C P R O M I S Ci o c t l使接口处于一种混杂状态,甚至能够接收不是发给本主机 的数据报。

附录B 源代码的获取 URL:统一资源定位符 本附录列出源代码所在的网址和下载方式。例如,常见的“匿名 FTP”地址表示如下: ftp://ftp.cdrom.com/pub/bsd-sources/4.4BSD-Lite.tar.gz

即主机为 f t p . c d r o m . c o m。通过匿名 F T P客户登录后,从目录 p u b / b s d - s o u r c e s下载文 件4 . 4 B S D - L i t e . t a r . g z。后缀. t a r说明文件以标准的 t a r( 1 )格式存储, . g z说明文件由 G N U g z i p(1)程序压缩。

4.4BSD-Lite 有多种方式可得到 4 . 4 B S D - L i t e的正式版代码。完整的 4 . 4 B S D - L i t e正式版代码可通过 Walnut Creek CD-ROM公司得到,网址为 ftp://ftp.cdrom.com/pub/bsd-sources/4.4BSD-Lite.tar.gz

或者直接得到其光碟版。联系电话为 18007869907或+1510 674 0783。 O’Reilly & Associates出版的C D - R O M,包括全套的 4 . 4 B S D手册和 4 . 4 B S D - L i t e正式版代 码。联系电话为 1 800 889 8989或者+1 707 829 0515。

运行4.4BSD-Lite 网络软件的操作系统 4 . 4 B S D - L i t e正式版不是一个完整的操作系统。为了测试本书中介绍的网络软件,需要内 置4.4BSD-Lite正式版的操作系统,或者支持 4.4BSD-Lite的操作系统。 作者使用的操作系统是 Berkeley Software Design Inc.生产的商用系统,联系电话为 1 800 ITS BSD8,+1 719 260 8114,或者[email protected]。 还有些免费的操作系统,已内置了 4 . 4 B S D - L i t e,如N e t B S D、3 8 6 B S D和F r e e BSd。详 情请见Walnut Creek CD-ROM(ftp.cdrom.com)或者comp.os.386bsd Usenet新闻组。

RFC 所有RFC都是免费的,通过电子邮件或匿名 FTP服务器可从英特网上得到所需文档。向下 述地址发送电子邮件: To: [email protected]. Subject: getting rfcs help: ways_to_get_rfcs

回复邮件中会列出通过电子邮件或匿名 FTP服务器获取 RFC不同方法的详细说明。 记住,首先应先下载最新的 RFC 索引,从中查找所需的 R F C,确认所需的 R F C没有被新 的RFC更新或取代。

873

附录B 源代码的获取计计

GNU软件 利用GNU Indent程序对本书出现的所有源代码进行格式调整,并利用 GNU Gzip程序对文 件做了压缩。这些程序可在下列站点找到: ftp://prep.ai.mit.edu/pub/gnu/indent-1.9.1.tar.gz ftp://prep.ai.mit.edu/pub/gnu/gzip-1.2.2.tar

文件名中的数字随版本的不同而不同。此外还有用于其他操作系统的 Gzip程序,如MS-DOS。 英特网上还有许多其他站点也提供 G N U资源,p r e p . a i . m i t . e d u主机的问候词中列出 了这些站点的名称。

PPP软件 有些PPP实现是免费的。 c o m p . p r o t o c o l s . p p p FAQ的第5部分提供了很多有价值的信 息。 http://cs.uni-bonn.de/ppp/part5.html

mrouted软件 mrouted软件的最新版本和其他多播应用程序可从 Xerox Palo Alto研究中心的站点得到: ftp://parcftp.xerox.com/pub/net-research/

ISODE软件 ISODE软件包中的 SNMP代理实现与 Net/3兼容。详细信息参见 ISODE论坛的网站: http://www.isode.com/

下载

第一部分 TCP事务协议 第1章 T/TCP 概 述 1.1 概述 本章首先介绍客户-服务器事务概念。我们从使用 U D P的客户-服务器应用开始,这是最 简单的情形。接着我们编写使用 T C P 的客户和服务器程序,并由此考察两台主机间交互的 T C P / I P分组。然后我们使用 T / T C P,证明利用 T / T C P可以减少分组数,并给出为利用 T / T C P需 要对两端的源代码所做的最少改动。 接下来介绍了运行书中示例程序的测试网络,并对分别使用 U D P、T C P和T / T C P的客户 服务器应用程序进行了简单的时间耗费比较。我们考察了一些使用 T C P的典型 I n t e r n e t应用程 序,看看如果两端都支持 T / T C P,将需要做哪些修改。紧接着,简要介绍了 I n t e r n e t协议族中 事务协议的发展历史,概略叙述了现有的 T/TCP实现。 本书全文以及有关 T / T C P的文献中,事务一词的含义都是指客户向服务器发出一个请求, 然后服务器对该请求作出应答。 I n t e r n e t中最常见的一个例子是,客户向域名服务器 ( D N S )发 出请求,查询域名对应的 I P地址,然后域名服务器给出响应。本书中的事务这个术语并没有 数据库中的事务那样的含义:加锁、两步提交、回退,等等。

1.2 UDP上的客户 -服务器 我们先来看一个简单的 U D P客户-服务器应用程序的例子,其客户程序源代码如图 1 - 1所 示。在这个例子中,客户向服务器发出一个请求,服务器处理该请求,然后发回一个应答。

图1-1 UDP上的简单客户程序

2

计计第一部分 TCP事务协议

下载

图1-1 (续)

本书中所有源代码的格式都是这样。每一非空行前面都标有行号。正文中叙述 某段源代码时,这段源代码的起始和结束行号标记于正文段落的左边,如下面的正 文所示。有时这些段落前面会有一小段说明,对所描述的源代码进行概要说明。源 代码段开头和结尾处的水平线标明源代码段所在的文件名。这些文件名通常都是指 我们在1.9节中将介绍的4.4版BSD-Lite中发布的文件。 我们来讨论这个程序的一些有关特性,但不详细描述插口函数,因为我们假设读者对这 些函数有一些基本的认识。关于插口函数的细节在参考书 [Stevens 1990]的第6章中可以找到。 图1-2给出了头文件cliserv.h。 1. 创建UDP插口 1 0 - 1 1 s o c k e t函数用于创建一个 U D P插口,并将一个非负的插口描述符返回给调用进程。 出错处理函数 e r r _ s y s参见参考书 [Stevens 1992]的附录B . 2。这个函数可以接受任意数目的 参数,但要用 v s p r i n t f 函数对它们格式化,然后这个函数会打印出系统调用所返回的 errno值所对应的 Unix出错信息,然后终止进程。 2. 填写服务器地址 12-15 首先用memset函数将Internet插口地址结构清零,然后填入服务器的 IP地址和端口号。 为简明起见,我们要求用户在程序运行中通过命令行输入一个点分十进制数形式的 I P 地址 (a r g v[ 1 ] )。服务器端口号 (U D P _ S E R V _ P O R T)在头文件 c l i s e r v . h中用# d e f i n e定义,在 本章的所有程序首部中都包含了该头文件。这样做是为了使程序简洁,并避免使调用 gethostbyname和getservbyname函数的源代码复杂化。 3. 构造并向服务器发送请求 1 6 - 1 9 客户程序构造一个请求 (只用一行注释来表示 ),并用s e n d t o函数将其发出,这样就 有一个 U D P 数据报发往服务器。同样是为了简明起见,我们假设请求 (R E Q U E S T )和应答 (R E P L Y)的报文长度为固定值。实用的程序应当按照请求和应答的最大长度来分配缓存空间, 但实际的请求和应答报文长度是变化的,而且一般都比较小。 4. 读取和处理服务器的应答 20-23 调用recvfrom函数将使进程阻塞 (即置为睡眠状态 ),直至收到一个数据报。接着客 户进程处理应答 (用一行注释来表示 ),然后进程终止。 由于r e c v f r o m函数中没有超时机制,请求报文或应答报文中任何一个丢失都将 造成该进程永久挂起。事实上, U D P客户-服务器应用的一个基本问题就是对现实世 界中的此类错误缺少健壮性。在本节的末尾将对这个问题做更详细的讨论。

3

第 1章 T/TCP概述计计

下载

在头文件c l i s e r v . h中,我们将 S A定义为struct s o c k a d d r * ,即指向一般 的插口地址结构的指针。每当有一个插口函数需要一个指向插口地址结构的指针时, 该指针必须被置为指向一个一般性插口地址结构的指针。这是由于插口函数先于 ANSI C 标准出现,在 8 0年代早期开发插口函数的时候, v o i d *(空类型 )指针类型尚 不可用。问题是,“s t r u c t s o c k a d d r”总共有 * 1 7个字符,这经常使这一行源代 码超出屏幕 (或书本页面 )的右边界,因此我们将其缩写成为 S A。这个缩写是从 B S D 内核源代码中借用过来的。 图1-2给出了在本章所有程序中都包含的头文件 cliserv.h。

图1-2 本章各程序中均包含的头文件 cliserv.h

图1-3给出了相应的UDP服务器程序。

图1-3 与图1-1的UDP客户程序对应的 UDP服务器程序

4

计计第一部分 TCP事务协议

下载

图1-3 (续)

5. 创建UDP插口和绑定本机地址 8-15

调用s o c k e t函数创建一个UDP插口,并在其 Internet插口地址结构中填入服务器的本

机地址。这里本机地址设置为通配符 (I N A D D R _ A N Y),这意味着服务器可以从任何一个本机 接口接收数据报 (假设服务器是多宿主的,即可以有多个网络接口 )。端口号设为服务器的知名 端口(U D P _ S E R V _ P O R T),该常量也在前面讲过的头文件 c l i s e r v . h中定义。本机 I P地址和 知名端口用 bind函数绑定到插口上。 6. 处理客户请求 16-25

接下来,服务器程序就进入一个无限循环:等待客户程序的请求到达 (r e c v f r o m),

处理该请求 (我们只用一行注释来表示处理动作 ),然后发出应答 (sendto)。 这只是最简单的 U D P客户-服务器应用。实际中常见的例子是域名服务系统 ( D N S )。D N S 客户(称作解析器)通常是一般客户应用程序 (例如,Telnet客户、FTP客户或WWW浏览器)的一 个部分。解析器向 D N S服务器发出一个 U D P数据报,查询某一域名对应的 I P地址。服务器发 回的应答通常也是一个 UDP数据报。 如果观察客户向服务器发送请求时双方交换的分组,我们就会得到图 1-4这样的时间系列, 页面上时间自上而下递增。服务器程序先启动,其行为过程给在图 1 - 4的右半部,客户程序稍 后启动。 我们分别来看客户和服务器程序中调用的函数及其相应内核执行的动作。在对 s o c k e t函 数的两次调用中,上下紧挨着的两个箭头表示内核执行请求的动作并立即返回。在调用 s e n d t o函数时,尽管内核也立即返回,但实际上已经发出了一个 U D P数据报。为简明起见, 我们假设客户程序的请求和服务器程序的应答所生成的 I P数据报的长度都小于网络的最大传 输单元(MTU),IP数据报不必分段。 在这个图中,有两次调用 r e c v f r o m函数使进程睡眠,直到有数据报到达才被唤醒。我 们把内核中相应的例程记为 sleep和wakeup。 最后,我们还在图中标出了事务所耗费的时间。图 1 - 4的左侧标示的是客户端测得的事务 时间:从客户发出请求到收到服务器的应答所经历的时间。组成这段事务时间的数值标在图 的右侧: RTT + SPT,其中RTT是网络往返时间, SPT是服务器处理客户请求的时间。 UDP客 户-服务器事务的最短时间就是 RTT + SPT。

5

第 1章 T/TCP概述计计

下载 客户端 函数

服务器端 内核

网络

内核

函数

返回 进程请求

返回

图1-4 UDP客户-服务器事务的时序图

尽管没有明确说明,但我们已经假设从客户到服务器的路径需要 1/2 RTT时间,返 回的路径又需 1/2 RTT时间。但实际情况并非总是如此。据对大约 600条Internet路径的 研究[Paxson 1995b]发现:3 0 %的路径呈现明显的不对称性,说明两个方向上的路由 经过了不同的站点。 我们的U D P客户-服务器看起来非常简捷 (每个程序只有大约 3 0行有关网络的源代码 ),但 在实际环境中应用还不够健壮。由于 U D P是不保证可靠的协议,数据报可能会丢失、失序或 重复,因此实用的应用程序必须处理这些问题。这通常是在客户程序调用 r e c v f r o m时设置 一个超时定时器,用以检测数据报的丢失,并重传请求。如果要使用超时定时器,客户程序 就要测量RTT并动态更新,这是因为互连网上的 RTT会在很大范围内变化,并且变化很快。但 如果是服务器的应答丢失,而不是请求,那么服务器就要再次处理同一个请求,这可能会给 某些服务带来问题。解决这个问题的办法之一是让服务器将每个客户最近一次请求的响应暂 存起来,必要时重传这个应答即可,而不需要再次处理这个请求。最后,典型的情况是,客 户向服务器发送的每个请求中都有一个不同的标识,服务器把这个标识在响应中传回来,使 客户能把请求和响应匹配起来。在参考书 [Stevens 1990]的 8.4节中给出了UDP上的客户-服务 器处理这些问题的源代码细节,但这将在程序中增加大约 500行源代码。 一方面,许多 U D P应用程序都通过执行所有这些额外步骤 (超时机制、 RT T值测量、请求 标识,等等 )来增加可靠性;另一方面,随着新的 U D P应用程序不断出现,这些步骤也在不断 地推陈出新。参考书 [Patridge 1990b]中指出,“为了开发‘可靠的 U D P应用程序’,你要有状 态信息(序列号、重传计数器和往返时间估计器 ),原则上你要用到当前 TCP连接块中的全部信

6

计计第一部分 TCP事务协议

下载

息。因此,构筑一个‘可靠的 UDP’,本质上和开发 TCP一样难”。 有些应用程序并不实现上面所述的所有步骤:例如在接收时使用超时机制,但并不测量 RTT值,当然更不会动态地更新RTT值。这样,当应用程序从一个环境(比如局域网)移植到另一 个环境(比如广域网)中应用时,就可能会引发一些问题。比较好的解决办法是用 TCP 而不是用 UDP,这样就可以利用 TCP提供的所有可靠传输特性。但是这种办法会使客户端测得的事务时 间由RTT + SPT增加到2×RTT + SPT(见下一节),而且还会大大增加两个系统之间交换的分组数 目。对付这些新的问题也有一个办法,即用T/TCP取代TCP,我们将在1.4节中对此进行讨论。

1.3 TCP上的客户 -服务器 下一个例子是TCP上的客户-服务器事务应用。图 1-5给出了客户程序。

图1-5 TCP事务的客户

1. 创建TCP插口和连接到服务器 10-17

调用socket函数创建一个TCP插口,然后在Internet插口地址结构中填入服务器的 IP

地址和端口号。对 c o n n e c t函数的调用启动 T C P的三次握手过程,在客户和服务器之间建立 起连接。卷 1的第18章给出了TCP连接建立和释放过程中交换分组的详细情况。 2. 发送请求和半关闭连接 19-22 客户的请求是用 write函数发给服务器的。之后客户调用 shutdown函数(函数的第2

7

第 1章 T/TCP概述计计

下载

个参数为 1 )关闭连接的一半,即数据流从客户向服务器的方向。这就告知服务器客户的数据 已经发完了:从客户端向服务器传递了一个文件结束的通知。这时有一个设置了 F I N标志的 T C P报文段发给服务器。客户此时仍然能够从连接中读取数据—只关闭了一个方向的数据 流。这就叫做TCP的半关闭(half-close)。卷1的第18.5节给出了有关细节。 3. 读取应答 23-24

读取应答是由函数 r e a d _ s t r e a m完成的,如图 1 - 6所示。由于 T C P是一个面向字节

流的协议,没有任何形式的记录定界符,因而从服务器端 T C P传回的应答可能会包含在多个 T C P报文段中。这也就可能会需要多次调用 r e a d函数才能传递给客户进程。而且我们知道, 当服务器发送完应答后就会关闭连接,使得 T C P向客户端发送一个带 F I N的报文段,在 r e a d 函数中返回一个文件结束标志 (返回值为 0 )。为了处理这些细节问题,在 r e a d _ s t r e a m函数 中不断调用 r e a d函数直到接收缓存满或者 r e a d函数返回一个文件结束标志。 r e a d _ s t r e a m 函数的返回值就是读取到的字节数。

图1-6 read_stream 函数

还有一些别的方法可以在类似 T C P 这样的流协议中用来给记录定界。许多 Internet应用程序(FTP、SMTP、HTTP和NNTP)使用回车和换行符来标记记录的结束。 其他一些应用程序 ( D N S,R P C )则在每个记录的前面加上一个定长的记录长度字段。 在我们的例子中,利用了 T C P的文件结束标志 ( F I N ),因为在每次事务中客户只向服 务器发送一个请求,而服务器也只发回一个应答。 FTP也在其数据连接中采用了这项 技术,用以告知对方文件已经结束。 图1-7给出的是TCP的服务器程序。

图1-7 TCP事务的服务器程序

8

计计第一部分 TCP事务协议

下载

图1-7 (续)

4. 创建监听用 TCP插口 8-17 用于创建一个TCP插口,并将服务器的知名端口绑定到该插口上。与 UDP服务器一样, T C P服务器也将通配符作为其 I P地址。调用l i s t e n函数将新创建的插口作为监听插口,用于 等待客户端发起的连接。 l i s t e n函数的第二个参数规定了允许的最大挂起连接数,内核要为 该插口将这些连接进行排队处理。 SOMAXCONN在头文件中定义。其数值过去一直都取5,但现在 有一些比较新的系统将其定为10。对于一些很繁忙的服务器(例如:Web服务器),已经 发现需要取更大的值,比如256或1024。在14.5节中我们还将对此问题进行更多的讨论。 5. 接受连接和处理请求 18-28

服务器进程调用 a c c e p t函数后就进入阻塞状态,直到有客户进程调用 c o n n e c t函

数而建立起一个连接。函数 a c c e p t返回一个新的插口描述符 s o c k f d,代表与客户和服务器 之间所建立的连接。服务器调用函数 r e a d _ s t r e a m读取客户的请求 (图1 - 6 ),再调用 w r i t e 函数向客户发送应答。 这是一个反复循环的服务器:把当前的客户请求处理完毕后才又调用 a c c e p t去 接受另一个客户的连接。并发服务器可以并行地处理多个客户请求 (即:同时处理 )。 在Unix的主机上实现并发服务器的常用技术是:在 a c c e p t函数返回后,调用 Unix的 f o r k 函数创建一个子进程,由子进程处理客户的请求,父进程则紧接着又调用 a c c e p t去接受别的客户连接。实现并发服务器的另一项技术是为每个新建立的连接

9

第 1章 T/TCP概述计计

下载

创建一个线程 (叫做轻量进程 )。为了避免那些与网络无关的进程控制函数把我们的例 子搞复杂,我们只给出了反复循环的服务器。参考书 [Stevens 1992]的第4章讨论比较 了循环服务器和并发服务器。 还有第三个选择是采用预分支服务器。即服务器启动时连续调用 fork函数数次, 并让每个子进程都在同一个监听插口描述符上调用 a c c e p t函数。这种办法节省了为 每个客户的连接请求临时创建子进程的时间开销,这对于繁忙的服务器来说,是很 大的节省。有些 HTTP服务器就采用了这项技术。 图1 - 8给出了 T C P上客户-服务器事务的时间系列。我们首先注意到,与图 1 - 4中U D P上的 服务器端

客户端 函 数

内 核

网 络

内 核

函 数

返回

返回 (数据) 进程请求

返回(数据)

图1-8 TCP上客户-服务器事务的时序

10

计计第一部分 TCP事务协议

下载

事务相比,网络上交换的分组数增加了: T C P上事务的分组数是 9,而U D P上的则是 2。采用 TCP后,客户端测量的事务时间是不少于 2 × RTT + SPT。通常,中间三个从客户到服务器的 报文段(对服务器 S Y N的A C K、请求以及客户的 F I N )是紧密相连的;后面两个从服务器到客户 的报文段 (服务器的应答和 FIN)也是紧密相连的。这使实际事务时间比从图 1-8中看到的更接近 2×RTT + SPT。 本例中多出来的一个 RT T源于T C P连接建立的时间开销:图 1 - 8中前两个报文段所花的时 间。如果 T C P可以把建连和发送客户数据以及客户 F I N (图中客户端发出的前四个报文段 )合起 来,再把服务器的应答和 FIN合起来,事务时间就又可以回到 RTT + SPT了,这与UDP的一样。 事实上,这就是 T/TCP中采用的基本技巧。 6. TCP的TIME_WAIT状态 TCP要求,首先发出 FIN的一端(我们的例子中是客户 ),在通信双方都完全关闭连接之后, 仍然要保持在 TIME_WAIT状态直至两倍的报文段最大生存时间 ( M S L )。M S L的建议值是 1 2 0 秒,也即处于 T I M E _ WAT E状态要达到 4分钟。当连接处于 TIME_WAIT状态时,同一连接 (即 客户IP地址和端口号,以及服务器 IP地址和端口号这 4个值相同 )不能重复打开 (我们在第 4章中 还要更多地讨论 TIME_WAIT状态)。 许多基于伯克利代码的 T C P实现,在 T I M E _ WA I T状态的保持时间仅仅为 6 0秒, 而不是RFC 1122 [Braden 1989]中指定的 2 4 0秒。在本书的所有计算中,我们还是假 定正确的等待周期为 240秒。 在我们的例子中,客户端首先发出 F I N,这称为主动关闭,因而 TIME_WAIT状态出现在 客户端。在这个状态延续期内, T C P要为这个已经关闭的连接保留一定的状态信息,以便能 正确处理那些在网络中延迟一段时间、在连接关闭之后到达的报文段。同样,如果最后一个 ACK丢失了,服务器将重传 FIN,使客户端重传最后的 ACK。 其他一些应用程序,特别是 W W W中的H T T P,要求客户程序发送一个专门的命令来指示 已经将请求发送完毕 (而不是像我们的客户程序那样采用半关闭连接的办法 );接着服务器就发 回应答,紧接着就是服务器的 F I N。然后客户程序再发出 F I N。这样做与前面所述的不同之处 在于,现在的 TIME_WAIT状态出现在服务器端而不是客户端。对许多客户访问的繁忙服务器 来说,需要保留的状态信息会占用服务器的大量内存。因此,当设计一个事务性客户服务器 应用程序时,让连接的哪一端关闭后进入 T I M E _ WA I T状态值得仔细斟酌。我们还将看到, T/TCP可以让TIME_WAIT状态的延续时间从 240秒减少到大约12秒。 7. 减少TCP中的报文段数 像图1-9所示的那样,把数据和控制报文段合并起来可以减少图 1-8中所示的TCP报文段数。 请注意,这里的第一个报文段中包含有 S Y N、数据和 F I N,而不像图 1 - 8中那样仅仅是 S Y N。 类似地,服务器的应答和服务器的 F I N也可以合并。虽然这样的分组序列也符合 T C P的规定, 但是作者无法在应用程序中利用现有的插口 A P I使T C P产生这样的报文段序列 (因此才在图 1 - 9 中客户端产生第一个报文段时和服务器端产生最后一个报文段时标上了问号 );而且据作者所 知,也没有哪一个应用程序确实生成了这样的报文段序列。 值得一提的是,尽管我们把报文段的数目由 9减少到了 5,但客户端观测的事务依然是 2× RTT + SPT。这是因为TCP中规定,服务器端的 TCP在三次握手结束之前不能向服务器进程提 交数据 (卷2的第2 7 . 9节说明了 T C P是如何在连接建立之前将到达的数据进行排队缓存的 )。加

11

第 1章 T/TCP概述计计

下载 客 户 端 函 数

内 核

服务器端 网 络

内 核

函 数

将数据加入队列

返回 (数据)

进程请求

返回(数据)

图1-9 最少TCP事务的时序

上这种限制的原因是服务器必须确信来自客户的 SYN是“新的”,即不是以前某次连接的 SYN 在网络中延迟一段时间后到达服务器端的。确认过程是这样的:服务器对客户发送的 S Y N发 送确认,再发出自己的 S Y N,然后等待客户对该 S Y N的确认。当三次握手完成之后,通信双 方就都知道对方的 S Y N是新的。由于在三次握手结束之前服务器无法开始处理客户的请求, 故分组数的减少并没有缩短客户端测得的事务时间。 下面这段话引自 RFC 1185 [Jacobson, Braden, and Zhang 1990]的附录:“注意: 使连接能够尽快重复利用是早期 TCP开发的重要目标。之所以有这样的要求是因为当

12

计计第一部分 TCP事务协议

下载

时人们希望 TCP既是应用层事务协议的基础,同时也是面向连接协议的基础。当时讨 论中甚至把既包含有 S Y N和F I N比特,同时又包含数据的报文段叫做‘圣诞树’报文 段和‘Kamikaze(敢死队)’报文段。但这种热情很快被泼了冷水,因为人们发现,三 次SYN握手和FIN握手意味着一次数据交换至少需要 5个分组。而且, TIME_WAIT状 态的延续说明同一个连接不可能马上再次打开。于是,再没有人在这个领域做进一 步的研究,尽管现在的某些应用程序 (比如,简单邮件传送协议, S M T P )经常会产生 很短的会话。人们一般都可以采用为每个连接选用不同的端口对的办法来避开重用 问题”。 RFC 1379 [Braden 1992b]中写到:“这些‘K a m i k a z e (敢死队)’报文段不是作为 一种支持的服务来提供,而主要用来搞垮其他实验性的 TCP!” 作为一个实验,作者编写了一个测试程序,这个程序把 SYN与数据和 FIN在一个报文段中 发出去,即图 1-9中的第一个报文段。该报文段发给 8个不同版本 Unix的标准echo服务器(卷1的 第1 . 1 2节),再用Tc p d u m p观察所交换的数据。其中的 7个( 4 . 4 B S D、AIX 3.2.2、BSD/OS 2.0、 HP-UX 9.01、IRIX System V.3、SunOS 4.1.3和System V Release 4.0)都能正确处理该报文段, 另外一个(Solaris 2.4)则把随SYN一起传送的数据扔掉,迫使客户程序重传数据。 那7个系统中的报文段序列与图 1 - 9所描绘的不尽相同。当三次握手结束后,服务器立刻 就对客户的数据和 F I N发出确认。另外,由于 e c h o服务器无法把数据和 F I N捆绑在一起 (图1 - 9 中的第四个报文段 )发送,结果是发了两个报文段而不只是一个:应答和紧接其后的 F I N。因 此,报文段的总数是 7而不是图 1 - 9中所示的 5。我们在 3 . 7节中会进一步讨论与非 T / T C P实现的 兼容性问题,并给出一些 Tcpdump的输出结果。 许多从伯克利演变而来的系统中,服务器无法处理接收到的报文段中只有 S Y N、 F I N,而没有数据、 A C K的情况。这个 b u g使得新创建的插口保持在 CLOSE_WAIT状 态直到主机重新启动。但这却是一个合法的 T / T C P报文段:客户建立起了一个连接, 没有发送任何数据,然后就关闭连接。

1.4 T/TCP上的客户 -服务器 我们的 T / T C P客户-服务器的源代码和上一节的 T C P客户-服务器的源代码略有不同,以 便能够利用 T/TCP的优势。图 1-10给出了T/TCP上的客户程序。

图1-10 T/TCP上的事务客户程序

13

第 1章 T/TCP概述计计

下载

图1-10 (续)

1. 创建TCP插口 10-15 对socket函数的调用与TCP上的客户程序一样,在 Internet插口地址结构中同样也填 入服务器的 IP地址和端口号。 2. 向服务器发送请求 17-19 T/TCP上的客户程序不调用 connect函数。而是直接调用标准的 sendto函数,该函 数向服务器发送请求,同时与服务器建立起连接。此外,我们还用 s e n d t o函数的第 4个参数 指定了一个新的标志 M S G _ E O F,用以告诉系统内核数据已经发送完毕。这样做就相当于图 1-5 中调用s h u t d o w n函数,向服务器发送一个 FIN。M S G _ E O F标志是T/TCP实现中新加入的,不 要把它与 M S G _ E O R标志混淆,后者是基于记录的协议 (比如OSI的运输层协议)中用来标志记录 结束的。我们将在图 1-12中看到,调用 s e n d t o函数的结果是客户端的 SYN、客户的请求以及 F I N都包含在一个报文段中发送出去。换言之,调用一个 s e n d t o函数就实现了 c o n n e c t、 write和shutdown三个函数的功能。 3. 读服务器的应答 20-21

读服务器的应答还是用 r e a d _ s t r e a m函数,与前文讨论过的 T C P上的客户程序一

样。 图1-11所示的是T/TCP上的服务器程序。

图1-11 T/TCP上的事务服务器程序

14

计计第一部分 TCP事务协议

下载

图1-11 ( 续)

这个程序与图 1 - 7 中T C P上的服务器程序几乎完全一样:对 s o c k e t函数、 b i n d函数、 l i s t e n 函数、 a c c e p t 函数和 r e a d _ s t r e a m函数的调用都一模一样。唯一的不同在于 T / T C P上的服务器发送应答时调用的是 s e n d函数,而不是 w r i t e函数。这样就可以设置 MSG_EOF标志,从而可以将服务器的应答和服务器的 FIN合并在一起发送。 图1-12所示的是T/TCP上客户-服务器事务的时序图。 T / T C P上的客户测量到的事务时间和 U D P上的几乎一样 (图1 - 4 ):RTT + SPT。我们估计 T/TCP上的时间会比UDP上的时间稍长一点,这是因为 TCP协议需要处理的事情比 UDP要多一 些,而且通信双方都要执行两次 r e a d操作分别读数据和文件结束标志 (而U D P环境下双方都 只要调用一次 r e c v f r o m函数即可 )。但是双方主机上这一段额外的处理时间比一次网络往返 时间RT T要小得多 (我们在 1 . 6节中给出了一些测试数据,用来比较 U D P、T C P和T / T C P上的客 户-服务器事务的差别 )。由此我们可以得出结论: T / T C P上的事务时间要比 T C P上的事务小大 约一次网络往返时间 RT T。T / T C P中省下来的这个 RT T来自于 TA O,即 T C P加速打开 ( T C P Accelerated Open)。这种方式跳过了三次握手的过程。下面两章中我们将说明其实现方法;在 4.5节中我们还将证明这样做的正确性。 U D P上的事务需要两个分组来传送, T / T C P上的事务需要 3个分组,而 T C P上的事务则需 要9个分组 (这些数字的前提是没有分组丢失 )。因此, T / T C P不仅缩短了客户端的事务处理时 间,而且也减少了网络上传送的分组数。我们希望减少网络上的分组数,因为路由器往往受 限于它们可以转发的分组数,而不是每个分组的长度。 概括地讲, T/TCP以一个额外的分组和可以忽略的延续时间为代价,同时具有了可靠性和 适应性这两个对网络应用至关重要的特性。

15

第 1章 T/TCP概述计计

下载 客 户 端 函 数

服务器端 内 核

网 络

内 核

函 数

返回 (数据) 进程请求

返回(数据)

图1-12 T/TCP上客户-服务器事务的时序

1.5 测试网络 图1-13画出了用于验证本书所有例子的测试网络。 书中大多数的示例程序都运行在 l a p t o p和b s d i这两个系统上,它们都支持 T / T C P协议。 图1 - 1 3中所有的 I P地址都属于 B类子网 1 4 0 . 2 5 2 . 0 . 0。所有主机的名字都属于 t u c . n o a o . e d u 域。n o a o表示“国家光学空间观测站”,t u c表示Tu c s o n。图中每个方框上部的记号表示在 该系统运行的操作系统。

1.6 时间测量程序 我们可以分别测量三种客户-服务器事务的时间,并比较其测量结果。我们要对客户程序 作如下改动: • 在图1-1所示的UDP上客户程序中,我们在即将调用 s e n d t o函数前和 r e c v f r o m函数刚

16

计计第一部分 TCP事务协议

下载

Cisco路由器 以太网

拨号 支持T/TCP的 BSD/OS 2.0

支持T/TCP的 BSD/0S 2.0

以太网140.252.13.0

图1-13用于验证本书所有例子的测试网络,所有 IP地址都以140.252打头

刚返回后分别读取当前系统时间。这两个时间的差值即为客户端测得的事务时间。 • 在图1 - 5所示的 T C P上客户程序中,我们在即将调用 c o n n e c t函数前和 r e a d _ s t r e a m 函数刚刚返回后分别读取当前系统时间。 • 在图1 - 1 0所示的 T / T C P上客户程序中,我们取当前的系统时间为即将调用 s e n d t o函数 前和read_stream函数刚刚返回后。 图1-14给出了以14种不同长度的请求和应答分别测得的结果。客户和服务器分别为图 1-13 中的bsdi和laptop。附录A中给出了这些测量的细节,并分析了影响结果的因素。 T / T C P上的事务时间总是比同样条件下的 U D P上的事务时间要长几个毫秒 (由于这个时间 差是软件造成的,因此这个时间差会随着计算机速度的提高而缩短 )。T / T C P协议栈比 U D P协 议栈所做的操作要多 (图A - 8 ),而且 T / T C P上的客户和服务器要分别调用两次 r e a d函数,而 UDP上的客户和服务器则只需分别调用一次 recvfrom函数。 T C P上的事务时间总是比相同条件下 T / T C P上的事务要长大约 20 ms 。其中部分原因是由 于T C P建立连接时的三次握手。两个 S Y N报文段的长度是 4 4字节( 2 0字节的 I P首部、 2 0字节的 标准T C P首部和 4字节的 TCP MSS 选项)。这相当于用户数据为 1 6字节的 P i n g;从图 A - 3可知, 其网络往返时间 RT T大约为 10 ms 。另外 10 ms的差值可能是因为 T C P协议需要处理额外 6个 TCP报文段造成的。 因此我们可以得出结论: T/TCP上的事务时间接近、但比 UDP上的事务时间略大,比 TCP 上的事务时间短至少相当于一个 44字节报文段的网络往返时间。 就客户段测量的事务时间而言,用 T / T C P取代T C P带来的好处依赖于 RT T和S P T之间的关

17

第 1章 T/TCP概述计计

下载

系。比如,在一个局域网上的 RT T为3 ms(如图A - 2 ),服务器的平均处理时间为 500 ms,那么 T C P上的事务时间大约为 506 ms(2×RT T + S P T ),而T / T C P的事务时间则大约为 503 ms。但如 果是一个网络往返时间 RT T为2 0 0 m s的广域网 (见第1 4 . 4节),服务器处理时间 S P T的平均值为 100 ms,那么TCP上和T/TCP上的事务时间就分别为大约 500 ms和300 ms。我们已经看到,使 用T / T C P所需传送的网络分组数少 (从图1 - 8和图1 - 1 2的比较中看分别是 3个和9个),因此,不 管客户端所测得的事务时间减少了多少,使用 T / T C P总是能减少网络分组数。减少了网络分 组数就可以减少分组丢失的概率,而在 Internet中,分组丢失对整个网络的稳定性有很大影响。

测得的事务 时间(ms)

用户数据:请求和回答的长度(字节)

图1-14 UDP、T/TCP和TCP上客户-服务器事务的时间系列

在A . 3节里,我们介绍了传播时迟和带宽的差异。这两者对 RT T都有影响;但是当网络变 快以后,传播时迟的影响也就变大了。此外,传播时延是我们几乎无法控制的,因为它的大 小取决于客户和服务器之间的信号传播距离和光在介质中的传播速度。于是,在网络速率越 来越快的条件下,省下一个 RT T的时间就显得尤为可贵,使用 T / T C P的相对好处也就越发明 显。 现在可以公开获得并支持 T/TCP的用于测量网络性能的工具: http://www.cup.hp.com/netperf/netperfpage.html

1.7 应用 T / T C P给所有T C P上的应用程序带来的第一个好处就是可以缩短 TIME_WAIT状态的持续 时间。这样,一般情况下协议必须处理的控制块也跟着少了。 4 . 4节详细介绍了 T / T C P协议的 这个特性。现在我们可以这样说:对于连接时间很短 (典型值为小于 2分钟)的所有 T C P应用程

18

计计第一部分 TCP事务协议

下载

序,如果通信双方的主机都支持 T/TCP的话,它们都将因使用该协议而获益。 使用T/TCP的最大好处或许在于避免了三次握手过程,对于那些交换的数据量比较小的应 用程序, T / T C P减少的时延将给它们带来好处。我们将给出几个例子来说明这一点 (附录B谈 到了利用T/TCP来避免三次握手过程要对应用程序做怎样的修改 )。 1. WWW:超文本传输协议 W W W及其所依赖的 H T T P协议(将在第1 3章介绍该协议 )将可能大大地受益于 T / T C P协议。 参考书[Mogul 1995b]中指出:“然而,构成Web应用传输时延的主要因素是网络通信……即便 我们无法提高光的传播速度,但我们至少应该想办法减少一次交互过程中的往返传输次数。 当前Web网中使用的超文本传输协议 (HTTP)实际上造成了大量不必要的往返传输”。 比如,[Mogul 1995b]中对随机抽取的 200 000个H T T P请求的统计发现,应答长度的中值 为1 7 7 0字节(通常使用中值而不使用均值,这是因为很少出现的大文件会使均值变大 )。M o g u l 还引用了另一个例子。该例随机抽样了大约 1 5 0万个请求,其应答的长度中值为 9 5 8字节。客 户的请求一般很短:在 100~300字节之间。 典型的H T T P客户-服务器事务和图 1 - 8所示的很相似。客户端主动打开,向服务器发出很 短的请求,服务器收到请求后发出应答,然后服务器关闭连接。这种情况非常适于使用 T / T C P协议,把客户端的 S Y N和客户的请求合并在一起传送省去三次握手中的往返时间。这 也还减少了网络上的分组数,而这对于已经非常巨大的 Web通信量来说也是很有意义的。 2. FTP 数据连接 F T P数据连接也会从使用 T / T C P协议中获益。从一项对 I n t e r n e t通信量的统计调查中, [Paxson 1994b]发现平均每个FTP数据连接所传输的数据量约为 3 000字节。卷1的第323页给出 了FTP数据连接的一个例子。虽然例子中的数据流是单向的,但其传输过程还是与图 1-12所示 的十分相似。采用 T/TCP后,图中的 8个报文段减少到了 3个。 3. 域名服务系统(DNS) D N S客户的查询请求是用 U D P传送到 D N S服务器的。服务器仍然用 U D P发送给客户的应 答。但如果应答超过 5 1 2字节,那么只有前 5 1 2字节会在应答中返回给客户,同时在应答中有 “t r u n c a t e d (截断)”标志,表示还有信息要传给客户。于是客户用 T C P向服务器重新发送查询 请求,而后服务器用 TCP向客户传送完整的应答。 采用这项技术的原因是不能保证特定的主机能够重组长度超过 5 7 6字节的 I P数据报 (实际 上,许多 U D P应用程序都把用户数据的长度限定在 5 1 2字节以内,以保证不超过 5 7 6字节的限 制)。由于 T C P是一个字节流协议,应答数据量再大也不会有问题。发送方 T C P会根据连接建 立时对等端声明的报文段最大长度 (MSS)限制,把应用程序的应答数据分割成适当长度的报文 段发给对方。接收方 T C P会把这些报文段拼接起来,并以应用程序读取时指定的数据长度交 给接收的应用程序。 D N S的客户和服务器可以利用 T / T C P,既达到 U D P的请求-应答速度,又具有 T C P的所有 好处。 4. 远程过程调用(RPC) 在所有论述将传输协议用于事务的论文中,无不将 R P C作为一个候选的应用协议。 R P C 中客户要向载有待执行程序的服务器发送请求,请求中带有客户给定的参数;服务器的应答 中包括过程执行后所返回的结果。参考书 [Stevens 1994]的第29.2节中讨论了 Sun RPC。

下载

19

第 1章 T/TCP概述计计

R P C的数据包往往会非常大,必须给 R P C协议增加可靠性,使其能在像 U D P这样不保证 可靠性的协议上运行,同时还要避免 TCP的三次握手。使用 T/TCP协议就能实现这一目标,既 有TCP的可靠性,又没有三次握手的开销。 所有建立在 RPC基础上的应用程序,比如网络文件系统 (NFS)等都可以采用T/TCP协议。

1.8 历史 RFC 938 [Miller 1985] 是较早讲述事务的 R F C文档之一。该文档中规定了 I RT P,即: Internet 可靠的事务协议,能保证数据分组的可靠、按顺序提交。该文档中把事务定义为一个 短小的、自包含的报文;而 I RT F定义了任意两台主机 (即I P地址 )之间持续存在的优选连接, 当其中任何一台主机重新启动后,该连接都重新同步。 IRTF协议位于 I P协议之上,并定义了 专门的8字节首部。 RFC 955 [Braden 1985]本质上并未规定任何协议,而只是给出了事务协议的一些设计准 则。它认为 U D P和T C P这两个主流的运输层协议所提供的业务相差太大,而事务协议正好填 补T C P和U D P之间的空档。该 R F C文档把事务定义为一次简单的报文交换:一个请求发给服 务器,然后一个应答发回到客户。它还认为各种事务都有如下特征:不对称的模式 (一端是服 务器,另一端是客户 )、单工数据传递 (任一时刻都只有一个方向有数据传输 )、持续时间短 (可 能延续几十秒,但绝不可能几小时 )、时延小、数据分组少以及面向报文 (不是字节流 )。 该R F C中列举了域名服务系统 D N S的例子。它认为,在考虑是用 U D P还是用 T C P作为域 名服务系统的运输层协议时,设计者往往陷入两难的境地。一个理想的解决方案应该既能提 供可靠的数据传输,又不需要专门地建立和释放连接,不需要报文的分段和重组 (从而应用程 序不再需要知道像 576这类的神秘数字 ),同时还能使两端的空闲状态所处时间最短。 TCP什么 都好,只可惜它需要建立和释放连接。 另一个相关的协议是 RDP,即可靠数据协议。该协议在 RFC 908 [Velten,inden,and Sax 1984]中定义,后来又更新为 RFC 1151 [Patridge and Hinden 1990]。与RDP实现有关的经验在 参考文献 [Patridge 1987] 中可以找到。参考文献 [Patridge 1990a]中对RDP有如下评价:“当人 们寻求一个可靠的数据报协议时,他们通常是想要一个事务协议,一个能够让他们与多个远 端系统可靠地交换数据单元的协议,一个类似于可靠 U D P的协议。 R D P应该看作是一个面向 记录的TCP协议,它利用连接可靠地传输有格式数据块流。 RDP并不是一个事务协议。”(RDP 不是一个事务协议的理由是因为它和 TCP一样采用了三次握手技术 )。 R D P使用通常的插口应用编程接口 ( A P I )。与 T C P类似, R D P提供流插口接口 ( S O C K _ S T R E A M)。另外, R D P还提供 S O C K _ R D M插口类型 (可靠的报文提交 )和S O C K _ S E Q P A C K E T插 口类型(有序的分组 )。 V M T P,即通用报文事务协议,是在 RFC 1045 [Cheriton 1998]中规定的,是一个专门用 于事务的协议,就像远程过程调用一样。像 I RT P和R D P那样, V M T P也是I P之上的运输层协 议,但 V M T P还支持多播通信,这个特性是 T / T C P以及本节提到过的其他协议所不具备的 (参 考文献[Floyd et al. 1995]中有不同意见,他们认为提供可靠的多播通信是应用层的任务,而不 是运输层的任务 )。 V M T P还为应用程序提供不一样的应用编程接口 A P I,其插口类型为 S O C K _ T R A N S A C T。

20

计计第一部分 TCP事务协议

下载

具体定义详见RFC 1045。 虽然T/TCP的许多概念早在 RFC 955中就已经出现,但直到 RFC 1379 [Braden 1992b]发布 才正式有了 T / T C P的第一个规范。该 R F C 文档定义了 T / T C P的概念,接下来的 RFC 1644 [Braden 1994]给出了更多的细节,并讨论了一些实现问题。 图1-15比较了实现各种运输层协议分别都需要多少行 C源代码。 协



UDP(卷2) RDP TCP(卷2) T/TCP模式的TCP VMTP

源代码行数 800 2 700 4 500 5 700 21 000

图1-15 实现各种运输层协议所需要的源代码行数

为支持 T / T C P所需增加的源代码行数 (大约1 2 0 0行)是U D P协议源代码行数的 1 . 5倍。为使 4.4BSD支持多播通信,需要增加大约 2000行源代码 (设备驱动程序的改变和支持多播路由所需 要的代码行数尚未计算在内 )。 V M T P可以从 f t p : / / g r e g o r i o . s t a n f o r d . e d u / v m t p - i p得到。R D P通常 还得不到。

1.9 实现 第一个T/TCP实现是由Bob Braden和Liming Wei在南加州大学的信息科学学院 (USC ISI)完 成的。该项工作得到了国家科学基金 NSF的部分资助,批准号为 NCR-8 922 231。该实现是为 SunOS 4.1.3(从伯克利演变而来的内核 )做的,1 9 9 4年9月就可以用匿名的 F T P得到了。 S u n O S 4.1.3 的源代码补丁可以从 f t p : / / f t p . i s i . e d u / p u b / b r a d e n / T T C P . t a r . Z得到,但你 必须有SunOS内核的源代码才能应用这些补丁。 Tw e n t e大学(荷兰)的Andras Olah修改了 USC ISI 的实现,并于 1 9 9 5年3月将其在 F r e e B S D 2 . 0版中发布。 FreeBSD 2.0中的网络代码是基于 4 . 4 B S D - L i t e版的(卷2中有介绍 )。图1 - 1 6给出 了各种 B S D版本的演变历程。与路由表 (我们将在第 6章中讨论 )有关的所有工作都是由麻省理 工学院(Massachusetts Institute of Technology)的Garrett Wollman完成的。FreeBSD实现的有关 信息可以从 http://www.freebsd.org得到。 本书作者把 FreeBSD实现移植到了BSD/OS 2.0内核(该内核也基于4.4BSD-Lite中的网络代 码)中,也就是运行在主机 b s d i和l a p t o p(图1 - 1 3中)中的代码,本书从头至尾都用它们。为 了支持 T / T C P而对 B S D / O S所做的修改,可以从作者的个人主页里找到:

http://www.

noao.edu/~rstevens。 图1 - 1 6给出了各个 B S D版本的演变历程,其中还标出了重要的 T C P / I P特性。图中左边显 示的是可以公开得到源代码的版本,其中有所有网络代码:协议本身、网络接口的内核例程 以及许多应用程序和实用工具 (比如Telnet和FTP)。 本书中所描述的 T / T C P实现的基础软件的正式名称是 4 . 4 B S D - L i t e,但我们一般简称其为 Net/3。还要说明的是,可以公开得到的 Net/3版本中不包括本书所述为支持 T/TCP而做的修改。

21

第 1章 T/TCP概述计计

下载

第一个广为流行的 TCP/IP的版本

TCP性能改善

慢启动 避免拥塞 快速重传 BSD连网软件 1.0版(1989): Net/1 快速恢复 TCP首部预测 SLIP首部压缩 路由表变更 BSD连网软件 2.0版(1991): Net/2 多播 长肥管道修改

正文中称为Net/3

4.4BSD-Lite2(1995)

图1-16 带有重要 TCP/IP特性的各种 BSD发行饣

当提到Net/3这个术语时,实际所指的就是这个不包含 T/TCP的、可公开得到的版本。 4 . 4 B S D - L i t e 2是1 9 9 5年对4 . 4 B S D - L i t e的升级。从网络部分来看,从 L i t e到L i t e 2仅仅是解 决了一些 bug,以及少量的改进 (比如我们将在 14.9节中介绍的坚持探测的超时 )。我们给出了 3 个基于 L i t e代码的系统: B S D / O S、F r e e B S D和N e t B S D。本书所述全部都是基于 L i t e代码的, 但所有以上的 3个版本都应该在下一个主要版本中升级到 L i t e 2。可以从下面的 Walnut Creek CDROM站点得到含有Lite2版本的光盘:http://www.cdrom.com。 本书全书都将用“从伯克利演变而来的实现”这个术语指称厂商的实现,比如

S u n O S、

SVR4(System V Release 4)和AIX,因为所有这些实现的 TCP/IP代码最初都来自于伯克利源代 码,它们之间有许多共同点,甚至连程序中的差错都相同!

1.10 小结 本章的目的是要让读者相信 T / T C P的确为许多实际中的网络应用问题提供了一个解决方 案。我们从比较一个分别用 U D P、T C P和T / T C P编写的、简单的客户-服务器程序开始。用

22

计计第一部分 TCP事务协议

下载

U D P协议需要交换两个分组,用 T C P需要9个,而用 T / T C P需要3个。我们还发现,用 T / T C P和 用U D P时在客户端测得的事务时间相差无几。图 1 - 1 4所示的时间测量结果证明了我们的结论。 除了达到UDP的性能之外,T/TCP还具有可靠性和适应性,这两点都是对 UDP的重大改进。 T/TCP因为避免了常规 TCP中的三次握手而获得上述各种优点。为了利用这些优点,客户 和服务器程序在应用 T / T C P时必须对源代码做一些简单的改动,主要是在客户端用 s e n d t o函 数代替connect、write和shutdown三个函数。 在后面的3章中,我们将研究协议是如何工作的,同时还会研究更多的 T/TCP应用例子。

下载

第2章 T/TCP协议 2.1 概述 我们分两章 (第2章和第 4章)讨论T / T C P协议。这样,在深入研究 T / T C P协议之前 (第3章), 我们可以先看一些应用 T / T C P的例子。本章主要对协议应用技巧和实现中用到的变量做一个 介绍。下一章我们学习一些 T/TCP应用的示例程序。第 4章结束我们对T/TCP协议的学习。 在第1章中我们已经看到了,当把 TCP协议应用于客户-服务器事务时会存在两个问题: 1) 如图1-8所示,三次握手使客户端测得的事务时间额外多出一个 RTT。 2) 由于客户进程主动关闭连接 (即由客户进程首先发出 F I N ),因而在客户收到服务器的 FIN后还要在TIME_WAIT状态滞留大约240秒。 TIME_WAIT状态和1 6比特T C P端口号这两者结合起来限制了两台主机之间的最大事务 速率。例如,如果同一台客户主机要不断地和同一台服务器主机进行事务通信,那么 它要么每完成一次事务后等待 2 4 0秒才开始下一个事务,要么为紧接着的事务选择另外 一个端口号。但每 2 4 0秒的时间内至多只能有 64 512个端口(65 535减去1 023个知名端 口)可用,从而每秒最多也就只能处理 2 6 8个事务。在 RT T值大约为 1 ~ 3 m s的局域网上, 实际上可能会超过这个速率。 而且,即使应用程序的事务速率低于每秒 2 4 0次,比如每 2 4 0秒只有 50 000次。当客户 端处于 T I M E _ WA I T状态时,协议还是需要控制块来保存连接的状态。卷 2中给出的 BSD实现中,每个连接都需要一个 IP控制块(84字节),一个TCP控制块(140字节)和一个 TCP/IP首部模板(40字节)。这样总共就需要 13 200 000字节的内核存储空间。这个开销 即便在内存不断扩大的今天依然显得大了些。 现在,T/TCP协议解决了这两个问题,采用的方法是绕过三次握手,并把 TIME_WAIT状态 的保持时间由240秒缩短到大约12秒。我们将在第4章中详细研究这两个特点。 T / T C P协议的核心称为 TA O,即T C P加速打开,跳过了 T C P的三次握手。 T / T C P给主机建 立的每个连接分配一个唯一的标识符,称为连接计数 ( C C )。每台 T / T C P主机都要将不同主机 对之间的最新连接计数 C C保持一段时间。当服务器收到来自 T / T C P客户的 S Y N时,如果其中 携带的 C C大于该主机对最新连接的 C C,就保证这是一个新的 S Y N,于是就接受该连接请求, 而不需要三次握手。这个过程称为 TA O测试。如果测试失败, T C P还是用三次握手的老方法 来确认当前这个 SYN是否为新的。

2.2 T/TCP中的新TCP选项 T / T C P协议中有三个新的 T C P选项。图 2 - 1给出了目前 T C P协议使用的所有选项。其中前 3 个出自最初的 T C P协议规范,即 RFC 793 [Postel 1981b] 。而窗口宽度和时间戳则是在 R F C 1323 [Jacobson, Braden, and Borman 1992]中定义的。最后三个选项 (CC、CCnew和CCecho)则 是T/TCP协议新引入的,在RFC 1644 [Braden 1994]中定义。最后这几个选项的使用规则如下:

24

计计第一部分 TCP事务协议

下载

1) C C选项在客户执行主动打开操作时发出的第一个 S Y N报文段中使用。它也可以在其他 一些报文段中使用,但前提是对方发过来的 SYN报文段中带有CC或CCnew选项。 2) C C n e w选项只能在第一个 S Y N报文段中使用。当需要执行正常的三次握手操作时,客 户端的TCP协议就使用 CCnew选项而不用 CC选项。 3) CCecho选项仅在三次握手过程中的第二个报文段中使用:通常由服务器发出该报文段, 并携带有 SYN和ACK。该报文段将CC或CCnew的值返回给客户,告知客户本服务器支 持T/TCP协议。 本章以及下一章的例子中我们还会进一步讨论这些选项。 选项表结束(EOL): 1字节

无操作(NOP): 1字节 报文段最大 长度(MSS)

报文段最大长度: 1字节

1字节

2字节

偏移量

窗口宽度因子: 1字节

1字节

时间戳值

时间戳 1字节

4字节

1字节

时间戳回显应答 4字节

连接计数 1字节

1字节

4字节

新连接计数 1字节

1字节

4字节

连接计数回显 1字节

1字节

4字节

图2-1 TCP选项

不难发现, T/TCP的3个新选项均为6字节长。为了使这些选项继续按 4字节定界 (这在某些 系统体系结构中有助于提高性能),我们通常在这些选项的前面加上两个单字节的无操作(NOP)。

25

第 2章 T/TCP协议计计

下载

如果客户既支持 RFC 1323,也支持T/TCP协议,这时客户发给服务器的第一个 SYN报文段 中的TCP选项,如图2-2所示。我们特意给出了每个选项的类型值和长度值; NOP用阴影表示, 其类型值为 1。第二个选项是窗口宽度,这里用“ W S”标记。方格上方的数字是每个选项相 对于选项字段起始的字节偏移量。 TCP协议选项的最大长度为 40字节,本例中的 TCP选项共需 28字节。从图中可以看出,采用 NOP填充以后,所有 4个4字节的值都符合 4字节定界规则。

1

1 1

时间戳

时间戳回显 1 1

图2-2 同时支持 RFC 1323和T/TCP的客户发给服务器的第一个 SYN报文段的TCP选项

如果服务器既不支持 RFC 1323,也不支持 T / T C P协议,它发给客户带有 S Y N和A C K的应 答中就只有报文段最大长度 ( M S S )选项。但如果服务器既支持 RFC 1323,也支持 T / T C P协议, 那么它给客户的应答中将包含图 2-3所示的TCP选项,总长为36字节。

1

1 1

时间戳

时间戳 回显

1 1

1 1

CC回显

图2-3 服务器对图 2-2所示请求的应答中的 TCP选项

由于C C e c h o选项总是和 C C选项一起发送,因此 T / T C P协议的设计本可以把这两 个选项合二为一,从而为宝贵的 T C P协议选项空间节省 4个字节。或者也可以这样, 这种最坏的选项排列只在服务器给出 S Y N / A C K时出现,而它们的出现无论如何总要 使TCP处理速度变慢的,因此索性连 NOP字节也省去,实际上可以节省 7个字节。 因为报文段的最大长度和窗口宽度选项只在 S Y N报文段中出现,而 C C e c h o选项只在 SYN/ACK报文段中出现,因此,如果连接两端都支持 RFC 1323和T/TCP协议,则自此以后的 报文段中也都只包含时间戳和 CC选项,如图 2-4所示。 1 1

时间戳

时间戳 回显

1 1

图2-4 两端都支持 RFC 1323和T/TCP时非SYN报文段所包含的 TCP选项

可以看出,一旦连接建立,时间戳和连接计数CC选项给所有的TCP报文段都增加了20字节。 当讲到T/TCP协议时,我们常常用一般术语 CC选项作为本节所引入的 3个TCP选项的统称。 时间戳和 C C选项带来了多大的额外开销呢?假设两台主机位于两个不同的网络 上,报文段最大长度 M S S设为典型值 5 1 2字节。要传递一兆字节的文件,如果没有这 些选项,则需要 1 954个报文段;如果使用时间戳和 CC选项,则需要 2 033个报文段, 较前者增加了 4% 。如果报文段最大长度 M S S为1 460 字节,那么报文段数只增加了 1.5%。

2.3 T/TCP 实现所需变量 T/TCP协议要求内核保存一些新增的信息,本节将对这些信息加以描述,后面几节将讨论

26

计计第一部分 TCP事务协议

下载

如何使用这些新信息。 (1) t c p _ c c g e n:这是一个 3 2位的全局整型变量,记录待用的 C C值。每当主机建立了一 个连接,该变量的值就加 1,无论是主动还是被动,也不论是否使用 T/TCP协议。该变 量永不为0。当变量渐渐增长时,如果又回到了 0,那么就将其值置为 1。 (2) 每主机高速缓存 ( p e r-host cache),其中包含了三个新变量: t a o _ c c、t a o _ c c s e n t 和t a o _ m s s o p t。该高速缓存也称为 TA O高速缓存。我们将看到, T / T C P协议为每一 个与之通信的主机创建一个路由表项,并把这些信息存储在路由表项中 (把每主机高速 缓存安排在路由表中是很方便的。当然也可以另开一张完全分离的表作为这个每主机 高速缓存。T / T C P协议不需要对 I P路由功能做任何改动 )。在每主机高速缓存中创建一 个新表项时,tao_cc和tao_ccsent必须初始化为0,表示它们尚未定义。 t a o _ c c记录的是最后一次从对应主机接收到且不含 A C K的合法的 S Y N报文段(即主动 打开连接 )中的C C值。当T / T C P主机收到一个带有 C C选项的S Y N报文段时,如果 C C选 项的值大于 t a o _ c c,那么主机就知道这是一个新的 S Y N报文段,而不是一个重复的 老SYN,这样就可以跳过三次握手 (TAO测试)。 t a o _ c c s e n t记录的是发给相应主机的最后一个不含 A C K的S Y N报文段 (即主动打开 连接)中的C C值。如果该值未定义 (为0 ),那么只有当对方发回一个 C C e c h o选项,表示 其可以使用 T/TCP协议时,才将tao_ccsent设置为非0。 tao_mssopt是最后一次从相应主机接收到的报文段最大长度选项值。 (3) 现有的TCP控制块中需要增加三个新的变量: c c _ s e n d、c c _ r e c v和t _ d u r a t i o n。 第1个变量记录的是该连接上发送的每一个报文段中的 C C值,第 2个变量记录的是希 望对方发来的报文段中所携带的 CC值,最后一个变量则用来记录连接已经建立了多长 时间(以系统的时钟滴答计算 )。当连接主动关闭时,如果该时间计数器显示的连接持 每内核

每主机

每连接

图2-5 T/TCP实现中的变量

下载

27

第 2章 T/TCP协议计计

续时间小于报文段最大生存时间 M S L,则TIME_WAIT状态将被截断。我们在 4 . 4节中 将更详细地讨论这个问题。 我们在图2-5中给出这些新变量。在后续章节讲 T/TCP协议实现时就用这些变量。 在这个图中,我们用 {}表示结构。图中的 TCP控制块是一个t c p c b结构。所有 TCP协议的 实现都必须为其中的连接保存并维护一个控制块,控制块的形式可以这样那样,但必须包含 特定连接的所有变量。

2.4 状态变迁图 T C P协议的工作过程可以用图 2 - 6所示的状态变迁图来描述。大多数状态变迁图都把状态 变迁时发送的报文段标在变迁线的边上。例如,从 C L O S E D状态到 S Y N _ S E N T状态的变迁就 标明发送了一个 S Y N报文段。在图 2 - 6中则没有采用这种标记方法,而是在每个状态框中标出 处于该状态时要发送的报文段类型。例如,当处于 S Y N _ R E C V状态时,要发出一个带有 S Y N 的报文段,其中还包括对所收到 S Y N的确认 ( A C K )。而当处于 C L O S E _ WA I T状态时,要发出 对所收到FIN的确认(ACK)。 我们之所以要这样做是因为,在 T/TCP协议中我们经常需要处理可能造成多次状态变迁的 报文段。于是在处理一个报文段时,重要的是处理完报文段后连接所处的最终状态,因为它 决定了应答的内容。而如果不使用 T / T C P协议,每收到一个报文段通常至多只引起一次状态 变迁,只有在收到 SYN/ACK报文段时才是例外,很快我们就要讨论这个问题。 与RFC 793 [Postel 1981b]中的TCP协议状态变迁图相比,图 2-6还有另外一些不同之处。 • RFC 793的状态变迁图中,当应用程序发送数据时,会有从 L I S T E N状态到 S Y N _ S E N T 状态的变迁。但实际上典型的 API很少提供这种功能。 • RFC 1122 [Braden 1989]中描绘了一个直接从 FIN_WAIT_1状态到TIME_WAIT状态的变 迁,这发生在收到了一个带有 FIN和对所发FIN的确认(ACK)的报文段时。但是当收到这 样一个报文段时,通常都是先处理 A C K使状态变迁到 F I N _ WA I T _ 2,接着再处理 F I N, 并变迁到 TIME_WAIT状态。因此,图 2 - 6也能正确处理这样的报文段。这就是收到一个 报文段导致两次状态变迁的例子。 • 除了SYN_SENT之外的所有状态都发送 ACK(处于LISTEN这个末梢状态时,则什么也不 发送)。这是因为发送 A C K是不受条件限制的:标准 T C P报文段的首部总是留有 A C K的 位置。因此, T C P总是确认已接收到的报文段最高序列号 (加1 ),只有在处理主动打开 (SYN_SENT)的SYN报文段和一些重建 (RST)报文段时才是例外。 TCP输入的处理顺序 T C P协议收到报文段时,对其中所携带的各种控制信息 ( S Y N、F I N、A C K、U R G和R S T 标志,还可能有数据和选项 )的处理顺序不是随意的,也不是各种实现可以自行决定的。 R F C 7 9 3中对处理顺序有明确的规定。图 11 - 1给这些步骤做了个小结,该小结同时也用黑体标明了 T/TCP中所做的改动。 例如,当 T / T C P客户收到一个携带有 S Y N、数据、 F I N和A C K的报文段时,协议首先处理 的是 S Y N (因为此时的插口还处于 S Y N _ S E N T状态 ),接着是处理 A C K标志,再接着是数据, 最后才是FIN。三个标志中的任何一个都有可能引起相应插口的连接状态改变。

28

计计第一部分 TCP事务协议

下载

起始点

被动打开

被动打开

关闭 或超时

同时打开 主动打开

关闭 数据传输状态 关闭 同时关闭

被动关闭

超时 主动关闭

appl: recv:

客户端TCP状态的常规变迁 服务器端TCP状态的常规变迁 应用程序发布操作指令时的TCP状态变迁 收到报文段时的TCP状态变迁

图2-6 TCP的状态变迁图

2.5 T/TCP的扩展状态 T/TCP中定义了7个扩展状态,这些扩展状态都称为加星状态。它们分别是:SYN_SENT*、 S Y N _ R C V D * 、E S TA B L I S H E D * 、C L O S E _ WA I T * 、L A S T _ A C K *、F I N _ WA I T _ 1 * 和

29

第 2章 T/TCP协议计计

下载

C L O S I N G *。例如,在图 1 - 1 2中,客户发出的第一个报文段中包含有 S Y N标志、数据和 F I N。 当该报文段是在主动打开中发送出去时,客户随即进入 SYN_SENT*状态,而不是进入通常的 SYN_SENT状态,这是因为随报文段还必须发出一个 FIN。当收到服务器的应答时,该应答中 包含有服务器的 S Y N、数据和 F I N,以及对客户的 S Y N、数据和 F I N的确认 ( A C K )。这时客户 端插口的连接状态要经历一系列的状态变迁: • 对客户 S Y N的A C K将连接的状态变迁到 F I N _ WA I T _ 1。传统的 E S TA B L I S H E D状态就这 样完全跳过去了,因为这时客户已经发出了 FIN。 • 对客户FIN的ACK将连接状态变迁到了 FIN_WAIT_2。 • 收到服务器的FIN,连接状态变迁到 TIME_WAIT。 RFC 1379详细描述了包括所有这些加星状态后的状态变迁图演变过程。当然,得到的结 果远比图 2 - 6复杂,其中有很多重叠的线。幸运的是,无星状态和对应的加星状态之间只是一 些简单的关系。 • S Y N _ S E N T *状态和 S Y N _ R C V D *状态与对应的无星状态几乎完全相同,唯一的不同之 处是在加星状态下要发出一个 F I N。这就是说,当一端主动打开连接、并且应用程序在 连接建立之前就指定了 M S G _ E O F(发送F I N )时就进入相应的加星状态。在这种情况下, 客户端一般是进入 S Y N _ S E N T *状态, S Y N _ R C V D *状态只有当双方碰巧同时执行打开 连接操作的偶然情况下才会出现,关于这一点我们在卷 1的18.8节中已有详细讨论。 • ESTABLISHED*、CLOSE_WAIT*、L A S T _ A C K *、FIN_WAIT_1*和C L O S I N G *这五个 状态与对应的不加星状态除了要发送 SYN外也完全相同。当连接处于这五个状态之一时, 叫做已经半同步了。当接收端处于被动状态且收到一个带有 TA O测试、可选数据和可选 F I N的S Y N报文段时,连接即进入这些加星状态 ( 4 . 5节详细描述了 TA O测试)。之所以用 半同步这个词是因为,一旦收到 S Y N接收端就认为连接已经建立了 (因为已经通过了 TAO测试),尽管此时刚刚完成了常规三次握手过程的一半。 图2 - 7给出了加星状态和对应的常规状态。对于每个可能的状态,表中还列出了所发出的 报文段类型。 我们将会看到,从实现的角度来看,这些加星的状态是很容易处理的。除了要保持当前 已有的无星状态外,在每个连接的 TCP控制块中还有两个额外的标志: • T F _ S E N D F I N表示需要发送FIN(对应于SYN_SENT*状态和SYN_RCVD*状态); • T F _ S E N D S Y N表示需要发送SYN(对应于图2-7中的5个半同步加星状态 )。 常规状态 CLOSED LISTEN SYN_SENT SYN_RCVD ESTABLISHED CLOSE_WAIT FIN_WAIT_1 CLOSING LAST_ACK FIN_WAIT_2 TIME_WAIT





关闭 监听连接请求 (被动打开) 已发出SYN(主动打开) 已经发出和收到 SYN;等待ACK 连接已经建立 (数据传输) 收到FIN,等待应用程序关闭 已经关闭,发出 FIN;等待ACK和FIN 两端同时关闭;等待 ACK 收到FIN已经关闭;等待 ACK 已经关闭;等待 FIN 主动关闭后长达 2MSL的等待状态





加星状态





RST, ACK SYN SYN, ACK ACK ACK FIN, ACK FIN, ACK FIN, ACK ACK ACK

SYN_SENT* SYN_RCVD* ESTABLISHED* CLOSE_WAIT* FIN_WAIT_1* CLOSING* LAST_ACK*

图2-7 TCP根据不同的当前状态 (常规或加星 )所发送的内容

SYN, FIN SYN, FIN, ACK SYN, ACK SYN, ACK SYN, FIN, ACK SYN, FIN, ACK SYN, FIN, ACK

30

计计第一部分 TCP事务协议

下载

在图2-7中,加星状态下把 SYN和FIN这两个新标志置于开状态时用黑体标出。

2.6 小结 T / T C P的核心是 TA O,即 T C P加速打开。这项技术使得 T / T C P服务器收到 T / T C P客户的 S Y N报文段后能够知道这个 S Y N是新的,从而可以跳过三次握手。确保服务器所收 S Y N是新 S Y N的技术 ( TA O测试)是为主机已经建立的每个连接分配一个唯一的标识符:连接计数 C C。 每个T / T C P主机都要把与每一个对等主机之间最新连接的 C C值保留一段时间。如果所收 S Y N 报文段的CC值大于从对等主机接收的最新 CC值,那么TAO测试成功。 T / T C P定义了 3个新的选项: C C、C C n e w和C C e c h o。所有选项都包含一个长度域 (这和 RFC 1323中规定的其他选项一样 ),使不认识这些选项的 TCP实现能跳过它们。如果某个连接 使用了 T / T C P协议,那么每个报文段都将包含连接计数选项 (不过有时在客户的 S Y N报文段中 用CCnew代替CC)。 T / T C P加入了一个全局内核变量,还在每主机高速缓存中加入了 3个变量,并为正在使用 的每个连接控制块增加了 3个变量。本书中讨论的 T / T C P实现利用业已存在的路由表作为每主 机高速缓存。 T C P的状态变迁图有 1 0个状态, T / T C P协议在此基础上还增加了 7个额外的状态。但实际 上协议实现是简单的:由于新的状态只是已有状态的扩充,因而只需要为每个连接引入两个 新的标志,分别指示是否需要发送一个 SYN报文段以及是否需要发送一个 FIN报文段,即可定 义7种新的状态。

下载

第3章 T/TCP使用举例 3.1 概述 本章中我们将通过几个 T / T C P应用程序例子来学习如何使用这 3个新引入的 T C P选项。这 几个例子说明,针对以下几种情形, T/TCP是如何处理的: • 客户重新启动; • 常规的T/TCP事务; • 服务器收到一个过时的重复 SYN报文段; • 服务器重新启动; • 请求或应答的长度超过报文段最大长度 MSS; • 与不支持T/TCP协议的主机的向下兼容; 下一章我们还将研究另外 2个例子: S Y N报文段到达服务器没有过时也不重复,但其到达 的顺序错乱;客户对重复的服务器 SYN/ACK响应的处理。 这些例子中的 T / T C P客户是 b s d i(图1 - 1 3 ),而服务器则是 l a p t o p。这些主机上运行的 T / T C P客户程序如图 1 - 1 0所示; T / T C P服务器程序如图 1 - 11所示。客户程序发出长度为 3 0 0字 节的请求,服务器则给出长度为 400字节的应答。 在这些例子中,客户程序中支持 RFC 1323的部分已经关闭。这样,在客户发起的 S Y N报 文段中就不会含有窗口宽度和时间戳选项 (由于只要客户不发送这两个选项,服务器的响应中 也不会包含这两个选项,从而服务器是否支持 RFC 1 323就是无关紧要的 )。这样做是为了避 免让那些与我们讨论的主题无关的因素把例子弄得太复杂。但在正常情况下,由于时间戳选 项可以防止把重复的报文段误认为是当前连接的报文段,因而我们可以在 T / T C P应用中支持 RFC 1 323。也就是说,在宽带连接和大数据量传送的情况下,即便是 T / T C P协议也一样需要 防止序号重叠(PAWS,见卷1的24.6节)。

3.2 客户重新启动 客户一旦启动,客户-服务器事务过程也就开始了。客户程序调用 s e n d t o函数,即在路 由表中为对端服务器增加一个表项,其中 t a o _ c c s e n t的值初始化为 0 (表示未定义 )。于是 T C P协议就会发出 C C n e w选项而不是发出 C C选项。服务器上的 T C P协议收到 C C n e w选项后就 执行常规的三次握手操作,其过程可见图 3 - 1所示的Tc p d u m p的输出(不熟悉Tc p d u m p操作及其 输出的读者可参见卷 1的附录 A。在跟踪观察这些分组的时候,不要忘了 S Y N和F I N在序号空 间中各占用一个字节 )。 从第1行的C C n e w选项可以看出,客户端 t c p _ c c g e n的值为 1。在第 2行,服务器对客户 的CCnew给出了回应,服务器的 t c p _ c c g e n值为18。服务器给客户的 SYN发出确认,但不确 认客户的数据。由于收到了客户的 C C n e w选项,即使服务器在其单机高速缓存中有该客户的 表项,它也必须完成正常的三次握手过程。只有当三次握手完成以后,服务器的 T C P协议才

32

计计第一部分 TCP事务协议

下载

图3-1 T/TCP客户重启后向服务器发送一个事务

能把收到的 300字节数据提交给当前的服务进程。 第3行显示的是三次握手过程的最后一个报文段:客户对服务器发出 S Y N的确认。在这个 报文段中客户将 F I N重传,但不包括 3 0 0字节数据。服务器收到该报文段后,立刻确认了收到 的数据和 F I N (第4行)。与一般的报文段不同的是,这个确认是即时发出的,没有被耽搁。这 么做是为了防止客户第 1行发出数据后超时而重传。 第5行显示的是服务器给出的应答以及服务器的 F I N,第6行中客户对服务器的 F I N和应答 都做了确认。注意,第 3、4、5和6行中都有CC选项,而CCnew和CCecho选项则分别只出现在 第1和第2个报文段中。 从现在开始,我们不再专门地在 T/TCP报文段中标示NOP了,因为NOP不是必需 的,而且会把图搞复杂。插入 N O P,使选项长度保持为 4字节整数倍的做法是出于对 提高主机性能的考虑。 机敏的读者可能会注意到,客户端刚刚重新启动时,客户 TCP协议所用的初始序 号( I S N )与卷1中习题1 8 . 1所讨论的一般模式不一样。而且,服务器的初始序号是个偶 数,这在通常从伯克利演变而来的实现中是从来没有的。其原因在于这里的连接所 使用的初始序号是随机选取的;而且每隔 500ms对内核的初始序号所加的增量也是随 机的。这种改动有助于防护序号攻击,具体内容可见参考文献 [Bellovn 1989]。这种 改动是 1 9 9 4年1 2月[Shimomura 1995]很有名的一次因特网侵入事件发生后,首先在 BSD/OS 2.0,然后在4.4BSD-Lite2中加入的。 时间系列图 图3-2给出的是图 3-1所描述的报文段交换过程的时序图。 图中,包含数据的第 1和第5这两个报文段用粗黑线标记。图的两侧还分别标注了客户和 服务器收到报文段后各自发生的状态变迁。开始的时候,客户进程调用 s e n d t o函数,并指定 M S G _ E O F标志,后进入 S Y N _ S E N T *状态。服务器收到并处理了第 3个报文段后发生了两次状 态变迁。先是处理客户对服务器发出

S Y N 的确认后,连接的状态由 S Y N _ R C V D 变迁到

ESTABLISHED状态;紧接着处理客户发来的 FIN又变迁到CLOSE_WAIT状态。当服务器向客 户发出设置了 M S G _ E O F标志的应答后,即进入 L A S T _ A C K状态。注意,客户在第 3个报文段

33

第 3章 T/TCP使用举例计计

下载 中重传了FIN标志(回忆一下图 2-7)。

图3-2 图3-1中报文段交换过程的时间系列图

3.3 常规的T/TCP事务 下面我们还是在上面那对客户和服务器之间发起另一次事务。这一次客户在自己的单机高速 缓存中取到该服务器的tao_ccsent值非0,于是就发出一个CC选项,其中下一个tcp_ccgen的 值为2 (2表示这是客户端重新启动后TCP协议建立的第2个连接)。报文段交换的过程如图3-3所示。

图3-3 常规的T/TCP客户-服务器事务

这是一个常规的、仅包含 3个报文段的最小规模 T/TCP报文交换过程。图 3-4显示了该次报 文交换的时序图以及状态变迁过程。 客户发出包含有 S Y N标志、数据和 F I N标志的报文段后进入 S Y N _ S E N T *状态。服务器收 到该报文段,且 TA O测试成功时,进入半同步的 E S TA B L I S H E D *状态。其中的数据经处理后 交给服务器进程。接着处理完报文段的 F I N标志后服务器进入 CLOSE_WAIT*状态。由于还未 发出 S Y N 报文段,因而服务器一直都处于加星状态。当服务器发出应答并在其中设置 M S G _ E O F标志后,服务器端随即转入 L A S T _ A C K *状态。如图2 - 7所示,这个状态的服务器发 出的报文段中包含了 SYN、FIN和ACK标志。

34

计计第一部分 TCP事务协议

下载

图3-4 图3-3中报文段交换的时间系列图

客户收到第 2个报文段后,其中对 SYN的确认使客户端的连接状态转入 FIN_WAIT_1状态。 接着客户处理报文段中对所发 F I N的确认,并进入 FIN_WAIT_2状态。服务器的应答则送到客 户进程。然后,客户处理该报文段中服务器所发的 F I N后进入 TIME_WAIT状态。在这个最终 的状态,客户发出对服务器所发 FIN的确认。 服务器收到第3个报文段后,其中对服务器所发 SYN的确认使服务器进入 LAST_ACK状态, 对服务器所发FIN的确认则使服务器进入 CLOSED状态。 这个例子清晰地显示了在 T/TCP事务过程中,收到一个报文段是怎样引起多次状态变迁的。 它同时也显示了尚不处于 E S TA B L I S H E D状态的进程是如何接收数据的:客户进程半关闭 (发 出第1个报文段 )了与服务器的连接,处于 FIN_WAIT_1状态下,但仍然能接收数据 (第2个报文 段)。

3.4 服务器收到过时的重复 SYN 如果服务器收到了一个看似过时的 CC值该怎么办呢?我们让客户发出一个连接计数值 CC 为1的S Y N报文段,这个值小于服务器刚刚从该客户收到的 C C值( 2,见图3 - 3 )。事实上,这种 情况也是可能发生的,比如: C C值等于1的这个报文段是客户和服务器之间此前某个连接的, 它在网络上耽搁了一段时间,但还没有超过其报文段最大生存时间 (发出后 M S L秒),最终到 达了服务器。 一个连接是由一对插口定义的,即包含客户端 I P地址和端口号及服务器端 I P地址和端口 号的四元组。连接的新实例称为该连接的替身。 从图3-5我们可以看出,服务器收到一个 CC值为1的SYN报文段后强迫执行三次握手操作, 因为它无法判断该报文段是过时重复的还是新的。 由于激活了三次握手(这一点我们可以从服务器仅仅确认了客户的 SYN而没有确认客户的数 据来判断),服务器的TCP协议在握手过程完全结束以后才会把300字节的数据提交给服务器进程。 本例中,第 1个报文段就是一个过时的重复报文段 (客户的TCP此时并不在等待对这个报文 段中S Y N的响应),于是当第 2个报文段中服务器发出的 S Y N / A C K到达时,客户端 T C P协议的 响应是要求重新建立连接 R S T (第3个报文段 )。这样做也是理所应当的。服务器的 T C P协议收 到这个RST后就扔掉那 300字节的数据,而且 accept函数也不返回到服务器进程。

35

第 3章 T/TCP使用举例计计

下载

图3-5 T/TCP服务器收到过时的重复 SYN报文段

第1个报文段是由一个特殊的测试程序生成的。我们无法让客户的 T / T C P协议自 己生成这样的报文段,而只能让它以过时的重复报文段出现。作者曾试着把内核的 t c p _ c c g e n 变量值改为 1 ,但是,正如我们将在图 1 2 - 3 中看到的,当内核的 t c p _ c c g e n小于它最近一次发给对端的 CC值时,TCP协议自动地发出一个 CCnew选 项而不是发出一个 CC选项。 图3-6所示的就是这对客户-服务器之间的下一次、也是常规的一次 T/TCP事务。正如我们 所预期的,这是一个包含 3个报文段的交换过程。

图3-6 常规的T/TCP客户-服务器事务

服务器希望这个客户发来的 CC值大于2,因此收到 CC值为3的SYN后TAO测试成功。

3.5 服务器重启动 现在我们将服务器重新启动,并让客户在服务器刚启动,即服务器监听进程刚开始运行 的时候就立即发送一个事务请求。图 3-7为报文段交换的情况。

图3-7 服务器刚刚重启动后, T/TCP的交换分组情况

由于客户并不知道服务器已经重新启动了,因而它发出的仍是一个常规的 T/TCP请求,其

36

计计第一部分 TCP事务协议

下载

中C C值为4 (见第1行)。服务器重新启动使其 A R P缓存中的客户硬件地址丢失,于是服务器发 出一个 A R P请求,客户给出应答。服务器强迫执行三次握手操作 (见第4行),因为它不记得上 次从该客户收到的连接计数值 CC。 与我们在图 3 - 1中看到的类似,客户发出一个带有 F I N标志的确认报文段完成三次握手过 程,3 0 0字节的数据则不重传。只有当客户端的重传定时器超时时客户才会重传数据,我们将 在图3 - 11中看到这种情况。收到这第 3个报文段后,服务器立即对数据和 F I N发出确认。服务 器发出应答 (见第7行),第8行则是客户给出的确认。 看过图3 - 7那样的报文交换过程后,我们来看看这对客户和服务器之间接下来继续通信时 的一个最小 T/TCP事务,如图 3-8所示。

图3-8 常规的T/TCP客户-服务器事务

3.6 请求或应答超出报文段最大长度 MSS 到目前为止,在我们所举的所有例子中,无论是客户的请求报文段还是服务器的应答报 文段都没有超过报文段最大长度 (MSS)。如果客户要发送超出报文段最大长度的数据,而且也 确信对等端支持 T / T C P协议,那么它就会发出多个报文段。由于对等端的报文段最大长度存 储在TA O高速缓存中 (图2 - 5的t a o _ m s s o p t),因而客户的 T C P协议能够知道服务器的报文段 最大长度,但无法知道服务器的接收窗口宽度 (卷1的1 8 . 4节和2 0 . 4节分别讨论了报文段最大长 度和窗口宽度 )。对一个特定的主机来说,报文段最大长度一般是个固定值,而此接收窗口的 宽度却会随应用程序改变其插口接收缓存的大小而相应地变化。而且,即使对等端告知了一 个较大的接收窗口 (比如说, 32 768字节),但如果报文段最大长度为 5 1 2字节,那么很可能会 有一些中间的路由器无法处理客户一下子发给服务器的前 6 4个报文段 (也即, T C P协议的慢启 动是不能跳过的 )。T/TCP协议加了两条限制来解决这些问题: 1) T / T C P协议将刚开始时的发送窗口宽度设定为 4 096 字节。在 N e t / 3中,这就是变量 s n d _ w n d的值。该变量控制着 T C P输出流可以发出多少数据。当对等端带有窗口通告 的第1个报文段到达后,窗口宽度的初始值 4 096 将被改变为所需值。 2) 只有当对等端不在本地时, T / T C P协议才使用慢启动方式开始通信。 T C P 协议将 s n d _ c w n d变量设置为 1个报文段时就是慢启动。图 10-14给出了本地 /非本地测试程序, 以内核的 i n _ l o c a l a d d r函数为基础。如果 (a)与本机拥有相同的网络号和子网号,或 者( b )虽然网络号相同子网号不同,但内核的 s u b n e t s a r e l o c a l变量值非 0,这样的 对等主机就是本地主机。 N e t / 3总是用慢启动方式开始每一条连接 (卷2第7 2 1页),但这样就使客户在启动事务时 无法连续发出多个报文段。折衷的结果是,允许向本地的对等主机发送多个报文段, 但最多4 096 字节。

37

第 3章 T/TCP使用举例计计

下载

每次调用 T C P协议的输出模块,它总是选择 s n d _ w n d和s n d _ c w n d中较小的一个作为其 可发送数据量的上限值。前者的初始值为 TCP滑动窗口通告中的最大值,我们假设为 65 535字 节(如果使用窗口大小选项,那么这个最大值可以为 65 535×2 14,大约为 1吉字节)。如果对等 主机在本地,那么 s n d _ w n d和s n d _ c w n d的初始值分别为 4 096和65 535。T C P协议在连接刚 开始时还未收到对方的窗口通告前,可以发出至多 4 096字节的数据。如果对方通告的窗口宽 度为 3 2 7 6 8字节,那么 T C P协议可以持续发送数据直到对等主机的接收窗口满为止

(因为

32 768和65 535的最小值是 32 768)。这样,TCP协议既可以避开慢启动过程,发送数据量又可 以受限于对方通告的窗口宽度。 如果对等主机不在本地,那么 s n d _ w n d的初始值仍为 4 096,但s n d _ c w n d的初始值则为 1 个报文段 (假设保存的对等主机报文段最大长度 M S S为5 1 2 )。T C P协议在连接一开始的时候只 能发出一个报文段,当收到对等主机的窗口通告后,每收到一个确认, s n d _ w n d的值就加 1。 这时慢启动机制在起作用,可以发出的数据量受限于拥塞窗口,直至拥塞窗口宽度超过了对 等主机通告的接收窗口。 作为一个例子,我们对第 1章中的 T / T C P客户和服务器程序加以修改,使请求和应答中的 数据量分别为3 300 和3 400 字节。图3-9给出了分组交换过程。 这个例子要显示 T / T C P交换的多个报文段的序列号,恰好暴露了 Tc p d u m p的一个 打印bug 。第6、第8和第10个报文段的确认号应当打印 3 302 而不是1。

图3-9 3 300字节的客户请求和 3 400 字节的服务器应答

由于客户知道服务器支持 T/TCP协议,客户可以立即发出4 096字节。在前2.6 ms 的时间里, 客户发出了第 1、第2和第3个报文段。第 1个报文段携带了 S Y N标志、1 448字节数据和1 2字节 TCP选项(报文段最大长度 MSS和连接计数 CC)。第2个报文段没有带标志,只有 1 452字节数据

38

计计第一部分 TCP事务协议

下载

和8字节TCP选项。第3个报文段携带FIN和PSH标志、8字节TCP选项以及剩余的 400字节数据。 第2个报文段是唯一一个没有设置任何 T C P标志(共有6个标志),甚至不带 A C K标志的报文段。 通常情况下, A C K标志总是携带的,除非是客户端主动打开,此时的报文段带有 S Y N标志(在 收到服务器的报文段之前,客户是绝不能发出任何确认的 )。 第4个报文段是服务器的 S Y N报文段,它同时也对客户所发来的所有内容做出了确认,包 括SYN标志、数据和FIN标志。在第 5个报文段中,客户立即确认了服务器的 SYN报文段。 第6个报文段晚了 40 ms才到达客户端,它携带了服务器应答的第 1段数据。客户立即对此 给出了确认。第8~11报文段继续同样的过程。服务器的最后一个报文段 (第10行)带有FIN标志, 客户发出的最后一个 ACK报文段对这最后的数据以及 FIN标志做了确认。 一个问题是,为什么客户对 3个服务器应答报文中的前两个立即给出了确认,是因为它们 在很短的时间 ( 4 4 m s )内就到达了吗?答案在 T C P _ R E A S S宏(卷2第7 2 6页)中,客户每收到一个 带有数据的报文段就要调用该宏。由于连接的客户端处理完第

4 个报文段后就进入了

FIN_WAIT_2状态,于是在 T C P _ R E A S S宏中对连接是否处于 ESTABLISHED状态的测试失败, 从而使客户端立即发出 A C K而不是延迟一会儿再发。这一“特性”并非 T / T C P协议所独有, 在Net/3的程序中,如果任何一端半关闭了 TCP连接而进入 FIN_WAIT_1或FIN_WAIT_2状态后, 都会出现这种情形。从此以后,来自对等主机的每一个数据报文段都立即给予确认。 T C P _ R E A S S宏中对是否已进入 ESTABLISHED状态的测试使协议无法在三次握手完成之前把 数据提交给应用程序。实际上,当连接状态大于 ESTABLISHED时,没有必要立刻确认按序收 到的每个报文段 (即:应当修改这种测试 )。 TCP_NOPUSH插口选项 运行该示例程序之前需要对客户程序再做一些修改。下面这段程序打开了 T C P _ N O P U S H 插口选项(T/TCP协议新引入的选项 ):

这段程序在图 1 - 1 0中调用s o c k e t函数之后执行。设置该选项的目的是告诉 T C P协议不要 仅仅为了清空发送缓存而发送报文段。 如果要了解设置该插口选项的原因,我们必须跟踪用户进程调用 s e n d t o 函数发送 3 300 字节数据并设置 MSG_EOF标志的请求后内核所执行的动作。 1) 内核最终要调用 s o s e n d函数(卷2的第1 6 . 7节)来处理输出请求。它把前 2 048字节数据 放入一个mbuf簇中,并向 TCP协议发出一个PRU_SEND请求。 2) 于是内核调用 t c p _ o u t p u t函数(图1 2 - 4 )。由于可以发送一个满长度 ( f u l l - s i z e d )的报文 段,因此发出 m b u f簇中的前 1 448字节数据,并设置 S Y N标志(该报文段中包含 1 2字节 的TCP选项)。 3) 由于m b u f簇中还剩下 6 0 0字节数据,于是再次循环调用 t c p _ o u t p u t函数。我们也许 会认为 N a g l e算法将不会使另一个报文段发出去,但是注意卷 2第6 8 1页可以看到,第 1 次执行 t c p _ o u t p u t函数后, i d l e变量的值为 1。当程序发出长为 1 448 字节的第 1个 报文段后进入 a g a i n分支时, i d l e变量没有重新计算。因此,程序在图 9 - 3所示程序

下载

39

第 3章 T/TCP使用举例计计

段(“发送方的糊涂窗口避免 (sender silly window avoidance)”)中结束。如果idle变量 为真,待发送的数据将把插口发送缓存清空,因此,决定是否发送报文段的是 TF_NOPUSH标志的当前值。 在T / T C P协议引入这个标志以前,如果某个报文段要清空插口的发送缓存,并且 N a g l e 算法允许,这段程序就总是会发送一个不满长的报文段。但是如果应用程序设置了 T F _ N O P U S H标志(利用新的T F _ N O P U S H插口选项),这时TCP协议就不会仅仅为清空发 送缓存而强迫发出数据。 T C P协议将允许现有的数据与后面写操作补充来的数据结合 起来,以期发出较大的报文段。 4) 如果应用程序设置了 T C P _ N O P U S H标志,那就不会发送报文段, t c p _ o u t p u t函数返 回,程序执行的控制权又回到 sosend函数。 如果应用程序没有设置 T C P _ N O P U S H标志,那么协议就发出那个 600字节的报文段,并 在其中设置 PSH标志。 5) s o s e n d函数把剩余的1 252字节数据放入一个 mbuf簇,并发出一个 P R U _ S E N D _ E O F请 求(图5 - 2 ),该请求再次结束 t c p _ o u t p u t函数的调用。然而在这次调用之前,已经调 用过 t c p _ u s r c l o s e d 函数 ( 图 1 2 - 4 ) ,使连接的状态由 S Y N _ S E N T 变迁至 S Y N _ S E N T * (图1 2 - 5 )。设置了 T F _ N O P U S H标志后,当前插口发送缓存中共有 1 852字 节的数据,于是协议又发出一个满长度的报文段,该报文段包含 1 452字节数据和 8字 节T C P选项(如图3 - 9所示)。发出该报文段的原因就是因为它是满长度的 (亦即: N a g l e 算法不起作用 )。尽管SYN_SENT*状态的标志中包含有 FIN标志(图2-7),但由于发送缓 存中还有额外的数据,因此 FIN标志被关掉了(卷2第683页)。 6) 程序又执行了一次循环从而再次调用 t c p _ o u t p u t函数发送缓存中剩余的 4 0 0字节数 据。然而这一回 F I N标志是打开的,因为发送缓存已经空了。尽管图 9 - 3中的N a g l e算法 不允许发出数据,但由于设置了 F I N标志,只有 4 0 0字节的报文段还是发出去了 (卷2第 688页)。 本例中,给插口设置了 T C P _ N O P U S H属性之后,在报文段最大长度 MSS为1 460字节的以 太网上发出一个 3 300 字节的请求就引发出 3个报文段,长度分别为 1 448 、1 452 和4 0 0字节。 如果不设置该选项,那么仍然会有 3个报文段,但其长度分别为 1 448、600和1 252字节。但如 果请求的长度为 3 600字节,则设置了 T C P _ N O P U S H选项时产生 3个报文段 (长度分别为1 448、 1 452和7 0 0字节),而不设置该选项就会产生 4个报文段 (长度分别为 1 448、6 0 0、1 452和1 0 0 字节)。 总之,当客户程序仅调用一次 s e n d t o函数发出请求时,通常应该设置 T C P _ N O P U S H插 口选项。这样,当请求长度超过报文段最大长度 M S S时,协议就会尽可能发出满长度的报文 段。这样可以减少报文段的数量,减少的程度取决于每次发送的数据量。

3.7 向后兼容性 我们还需要研究一下如果客户用 T / T C P协议给一台不支持 T / T C P协议的主机发送数据会发 生什么样的情况。 图3-10显示的就是主机 b s d i上的T/TCP客户程序向主机 s v r 4(一个运行 System V Release 4的主机,不支持 T/TCP)上的TCP服务器发起事务时,它们二者之间分组交换的情况。

40

计计第一部分 TCP事务协议

下载

图3-10 T/TCP客户程序向 TCP服务器发起事务

客户端的 T C P程序发出的第 1个报文段中包含有 S Y N、F I N和P S H标志,还包含了 3 0 0字节 数据。由于客户端的TCP协议在其TAO高速缓存中还没有该服务器主机 svr4的连接计数 CC值, 因而它发出的报文段中带上了 CCnew选项。图中第 2行就是服务器对该报文段的响应,这是标 准三次握手过程中的第 2个报文段;而客户端在第 3行中对该响应做出了确认。注意:第 3行中 没有重传数据。 服务器端收到第 3行的报文段后,立即确认了客户一开始发过来的 300字节数据和 FIN标志 (如卷2第7 9 1页所示,对 F I N的确认从不推迟 )。服务器端 T C P将上述数据保存在队列中,直至 三次握手过程结束才将其交给服务进程。 第5行显示的是服务器给出的响应 (400字节数据),客户端在第6行中立刻对此做出了确认。 第7行显示的是服务器发出的 F I N报文段,客户端同样也迅速地做出了确认。注意,服务器进 程无法把第 5行的数据和第7行的FIN结合在一起发送。 如果我们在同样还是这一对客户和服务器之间再发起一次事务,则报文段交换的顺序与 上一次完全相同。由于在图 3 - 1 0中服务器端并没有发回一个 C C e c h o选项,因此客户端仍然无 法向 s v r 4主机发出带有 C C选项的报文段,从而客户端发出的第 1个报文段 (即初始化报文段 ) 仍然带有 C C n e w选项,其值为 11。支持 T / T C P的客户端总是发出 C C n e w选项的原因是,对不 支持T / T C P的服务器,它从来不会更新在其单机高速缓存中的相关表项,因而 t a o _ c c s e n t 值总是0(未定义)。 在下面的例子(图3-11)中,服务器主机运行 Solaris 2.4,这也是一个基于 SVR4(与图3-10中 的服务器一样)的系统,但二者的 TCP/IP协议栈实现却完全不同。 第1 ~ 3行与图3 - 1 0中的相同:带有 S Y N、F I N、P S H标志和3 0 0字节数据的报文段,接着是 服务器的SYN/ACK报文段,然后是客户的 ACK报文段。这是一次正常的三次握手过程。同样, 由于不知道该服务器的连接计数 C C值,客户端 T C P协议发出的是一个带有 C C n e w选项的报文 段。 Solaris主机发出的每个报文段中携带的“不分段”标志 (DF),用于路径最大传输 单元发现(RFC 1191 [Mogul and Deering 1990])。

41

第 3章 T/TCP使用举例计计

下载

图3-11 T/TCP客户向Solaris 2.4上的TCP服务器发送事务请求

不幸的是,我们遇到了在 S o l a r i s的T C P / I P实现中的一个 b u g。因为这个 b u g,服务器端 T C P把第1行中的数据部分扔掉了 (第2个报文段中没有对该数据做确认 ),造成客户端的 T C P超 时,并在第 4行重传了数据,同时也重传了 F I N。接着,服务器端确认了客户端发来的数据和 F I N (第5行),然后服务器端在第 6行发出应答。客户端在第 7行对应答给出确认,紧接着是服 务器发出FIN报文段(第8行),最后是客户端的确认 (第9行)。 RFC 793 [Postel 1981b]的第30页中指出:“尽管这些例子并不证明采用附带数据 的报文段也能实现连接同步,但这样处理也完全是合法的,接收端的 TCP只有在搞清 楚了数据是正确的以后才能将数据交付给用户 (即,接收端对数据进行缓存,等到连 接状态进入 E S TA B L I S H E D 以后才能交付给用户 ) ”。该 R F C 的第 6 6页还说,在 L I S T E N状态处理接收到的 S Y N时,“任何其他控制信息和正文数据都要先放入队列 待以后处理”。 有一个评论者声称,把上述现象叫做“ b u g”是不对的,因为 R F C中并没有强制 要求服务器在处理 S Y N的同时接受其中附带的数据。声明中还说, S o l a r i s的实现是 正确的,因为还没有向客户端通告接收窗口,这时服务器完全可以丢弃已到达的数 据,因为这些数据都落在窗口之外。不管你如何评价这个特点 (作者仍然称它们为 bug, S U N公司也已经为这个问题分配了一个 Bug ID,即1 222 490,因此也将会在今后的 版本中进行修正 ),处理这样的情况还要符合健壮性原则,该原则在 RFC 791 [Postel 1981a]中首次提出:“你有自由去决定接受什么,但你发送什么却必须遵守规定。”

3.8 小结 我们可以对本章中的例子做下面这样的总结: 1) 如果客户端丢失了服务器的状态信息 (例如,客户端重新启动 ),那么客户端在主动打开

42

计计第一部分 TCP事务协议

下载

时将发出CCnew选项,从而强迫执行三次握手过程。 2) 如果服务器丢失了客户端的状态信息,或者服务器收到的 S Y N报文段中的 C C值小于期 望的值,那么服务器返回给客户的响应将只是一个 S Y N / A C K报文段,从而强迫执行三 次握手过程。在这种情况下,直到三次握手过程完全结束以后,服务器的 T C P才会把 客户在SYN报文段中附带的数据交给上层的服务器进程。 3) 如果服务器想在连接中使用 T / T C P协议,那么它总是用 C C e c h o选项对客户的 C C或 CCnew选项作出应答。 4) 如果客户端和服务器端彼此都掌握有对方的状态信息,那么整个事务过程所收发的报 文段个数将达到最少: 3个(假设请求和响应的长度都小于或等于报文段最大长度 MSS)。 此时收发的分组数最少,时延也最小:为 RTT + SPT。 以上这些例子同时也说明了 T / T C P协议中多个状态的变迁是如何发生的,以及如何使用那些 新扩充的(加星的)状态。 如果客户端向一个不支持 T / T C P协议的主机发送带有 S Y N、数据和 F I N的报文段,那么采 用伯克利网络代码的系统 (包括S V R 4,但不包括 S o l a r i s )能够正确地将数据存储在队列中,直 至三次握手过程完成。然而,其他的一些网络代码也有可能错误地把 S Y N报文段中的数据扔 掉,造成客户端超时,并重传数据。

下载

第4章 T/TCP协议(续) 4.1 概述 本章继续讨论 T / T C P协议。我们首先讨论 T / T C P客户程序如何根据连接持续时间是否会大 于报文段最大生存时间 M S L来分配端口号,以及这个分配结果对 T C P的TIME_WAIT状态有什 么影响。接下来我们研究 T C P协议为什么要定义 TIME_WAIT状态,因为人们对 T C P协议的这 一特点普遍缺乏理解。 T / T C P协议的重要优点之一就是在连接持续时间小于报文段最大生存 时间M S L时,使协议的 T I M E _ WA I T状态由 2 4 0秒缩短至大约 1 2秒。我们将讨论 T / T C P协议是 如何实现这一点的,以及这样做的正确性。 本章最后我们将讨论 T / T C P协议的 TA O,即T C P加速打开。它使 T / T C P的客户-服务器事 务能够跳过三次握手过程,从而节省了一次往返时间,这也正是 T / T C P协议给我们带来的最 大好处。

4.2 客户的端口号和 TIME_WAIT状态 我们编写 T C P客户程序的时候通常不关心如何选择端口号。大部分

T C P客户程序 (如

Te l n e t、F T P以及 W W W等)都是使用临时端口,让主机的 T C P模块选择一个当前未使用的端 口。从伯克利演变来的系统往往选择 1 024~5 000 之间的临时端口 (见图1 4 - 1 4 ),而S o l a r i s则 在32 768~65 535 之间选择。然而, T / T C P协议根据事务速率和持续时间,对端口号的选择 有额外的要求。 常规的TCP主机和常规的TCP客户程序 图4 - 1描述的是一个 T C P客户程序 (例如图 1 - 5所示的程序 )与同一个服务器之间执行的三次 事务,每次事务的持续时间为 1秒,前后事务之间的间隔也为 1秒。三次连接分别开始于第 0秒、 第2秒和第 4秒,而分别终止于第 1秒、第 3秒和第 5秒。x轴表示时间,单位为秒;三次连接分 别用粗线段表示。

图4-1 TCP客户,不同的事务选用不同的本地端口

每次事务各建立一条 T C P连接。我们假定客户程序在创建插口时并不显式地将其绑定到 某个端口,而是让系统的 TCP模块来选择临时端口。我们还假定客户端 TCP模块的报文段最大

44

计计第一部分 TCP事务协议

下载

生存时间 M S L为1 2 0秒。第1条连接要保持在 TIME_WAIT状态,直至第 2 4 1秒;第2条和第3条 连接则分别从第 3秒和第5秒开始保持 TIME_WAIT状态,直至第243秒和第245秒。 在图中, C B表示“控制块”,实际上表示连接使用期间和处于 T I M E _ WA I T状态期间, TCP协议维持的几个控制块的组合,包括: Internet 进程控制块 PCB、TCP控制块和首部模板。 在第2章一开始时我们就说过,在 N e t / 3实现中,这 3个控制块的大小总和为 2 6 4字节。除了内 存要求以外, T C P协议还需要占用 C P U时间来周期性地处理这些控制块 (例如在卷 2的2 5 . 4节和 25.5节中,协议每200ms和500ms就要对所有 TCP控制块处理一遍 )。 N e t / 3中为每个连接保存一份 T C P和I P首部作为“首部模板” (卷2的第 2 6 . 8节)。 该模板中包含了给定连接中用到的所有字段,这些字段在该连接中不会有变化。这 样就节省了每次发送报文段的处理时间,因为程序代码只要把首部模板中的内容复 制到正在构造的输出分组中即可,而不需要分别填写每个字段。 常规的T C P是无法跳过三次握手过程的。客户程序不能在相继的 3条连接中使用同一个本 地端口,即使为插口设置了 SO_REUSEADDR属性也是如此(卷2第592页给出了一个示例程序 )。 T/TCP 主机,每次事务用不同的客户端口 图4 - 2给出的是与图 4 - 1一样的三次事务序列,但这里我们假定两端的主机都支持 T / T C P协 议。我们的客户程序与图 4 - 1中的也是同一个。这有很重要的区别:客户和服务器应用程序不 需要知道是 TCP还是T/TCP,我们只要求两端的主机都支持 T/TCP协议(即支持CC选项)。

图4-2 当客户和服务器端都支持 T/TCP协议时的TCP客户程序

图4 - 2与图4 - 1的不同之处在于,连接处于 TIME_WAIT状态的时间被截断了,因为两端的 主机都支持 CC选项。我们这里假定重传超时是 1.5秒(在局域网上运行的 Net/3中,这是典型值, 见[Brakmo and Peterson 1994]),T/TCP的TIME_WAIT是8倍,这样就将TIME_WAIT的保持时 间从240秒缩短到了 12秒。 当两端的主机都支持 C C选项并且连接持续时间小于报文段最大生存时间 M S L ( 1 2 0秒)时, T/TCP允许TIME_WAIT状态的保持被截断。这是因为 CC选项提供了另外一种保护机制,可以 防止过时的重复报文段被投递给另一个新的连接,这一点将在 4.4节中讨论。 T/TCP主机,各次事务用同一个客户端口 图4-3给出了与图4-2相同的三次事务的序列,但不同的是,我们在这里假定每次事务中客 户端都重复使用同一个端口。为了做到这一点,客户程序必须为插口设置 S O _ R E U S E A D D R选

45

第 4章 T/TCP协议(续)计计

下载

项,并调用 b i n d函数将该插口绑定到某一个特定的本地端口,然后再调用 c o n n e c t函数(对 常规的TCP客户程序)或s e n d t o函数(对T/TCP客户程序)。与图4-2中一样,这里也假定两端的 主机都支持 T/TCP协议。

图4-3 TCP客户程序重用同一个端口;客户和服务器主机同时支持 T/TCP协议

在第 2 秒和第 4 秒创建连接时, T C P 发现了具有相同插口对的控制块,并且正处于 T I M E _ WA I T状态。但是由于前一条连接替身使用了 C C选项,尽管连接的持续时间小于报文 段最大生存时间 MSL,TIME_WAIT状态的持续时间还是被截断了,并且,当前的连接控制块 将被删除,系统将为新的连接分配一个控制块 (新分配的连接控制块可能就是刚刚被删除的旧 连接控制块,但那是实现的细节问题。重要的是当前连接控制块的总数没有增加 )。当第 3条 连接在第5秒被关闭后,TIME_WAIT状态的持续时间也只有 12秒,与图4-2所示的一样。 总之,本节说明了事务过程中的客户程序有两种可能的优化方式: 1) 不需要改动任何程序源代码,只要客户和服务器端都支持

T / T C P 协议,就可将

TIME_WAIT的持续时间缩短到连接中重传超时的 8倍,而不是原来的 240秒。 2) 只修改客户程序,使其重用同一个端口号,这时不但 TIME_WAIT状态的持续时间可以 像前一种情况那样截断到连接中重传超时的 8倍,而且,如果同一连接的另一个替身被 创建,TIME_WAIT状态就会更快地终止。

4.3 设置TIME_WAIT状态的目的 T I M E _ WA I T状态是 T C P协议中最容易被误解的特性之一。这很可能是因为最初的规约 RFC 793中只对该状态做了扼要的解释,尽管后来的 RFC,如RFC 1185,对TIME_WAIT状态 做了详细说明。设置 TIME_WAIT状态的原因主要有两个: 1) 它实现了全双工的连接关闭。 2) 它使过时的重复报文段作废。 下面我们对这两个原因做进一步的讨论。 TCP全双工关闭 图4 - 4给出了一般情况下连接关闭时的报文段交换过程。图中还给出了连接状态的变迁和 在服务器端测得的 RTT值。 图中左侧为客户端,右侧为服务器端。要注意,其中的任何一端都可以主动关闭连接, 但一般都是客户端执行主动关闭。 下面我们来看看最后一个报文段 (最后一个 A C K )丢失时会发生什么现象。这个现象就给 在图4-5中。

46

计计第一部分 TCP事务协议

客户

下载 服务器

主动关闭 被动关闭

图4-4 通常情况下连接关闭时的报文段交换 客户

服务器

主动关闭 被动关闭

重启动

图4-5 最后一个报文段丢失时的 TCP连接关闭

由于没有收到客户的最后一个确认,服务器会超时,并重传最后一个 FIN报文段。我们特 意把服务器的重传超时 (RTO)给得比图4-4中的RTT大,这是因为 RTO的取值是估计的 RTT值加 上若干倍的 RT T方差(卷2的第2 5章详细论述了如何测量 RT T值以及如何计算 RTO )。处理最后 一个FIN报文段丢失的方法也是一样:服务器在超时后继续重传 FIN。 这个例子说明了为什么 TIME_WAIT状态要出现在执行主动关闭的一端:该端发出最后一 个A C K报文段,而如果这个 A C K丢失或是最后一个 F I N丢失了,那么另一端将超时并重传最 后的FIN报文段。因此,在主动关闭的一端保留连接的状态信息,这样它才能在需要的时候重 传最后的确认报文段;否则,它收到最后的 F I N报文段后就无法重传最后一个 A C K,而只能 发出RST报文段,从而造成虚假的错误信息。 图4 - 5还说明了另一个问题,即如果重传的 F I N报文段在客户端主机仍处于 TIME_WAIT状 态的时候到达,那么不仅仅最后一个 A C K会重传,而且 T I M E _ WA I T状态也重新开始。这时, TIME_WAIT状态的持续时间定时器重置为 2倍的报文段最大生存时间,即 2MSL。 问题是,执行了主动关闭的一端,为了处理图 4 - 5所示的情况,需要在 T I M E _ WA I T状态 保持多长的时间?这取决于对端的 RTO值;而 RTO又取决于该连接的 RT T值。RFC 11 8 5中指 出RT T的值超过 1分钟不太可能。但实际上 RTO却很有可能长达 1分钟:在广域网发生拥塞期 间时就会有这种情形。这是因为拥塞会导致多次重传的报文段仍然丢失,从而使 T C P的指数 退避算法生效, RTO的值越来越大。

47

第 4章 T/TCP协议(续)计计

下载 过时的重复报文段失效

设置TIME_WAIT状态的第二个原因是为让过时的重复报文段失效。 T C P协议的运行基于 一个基本的假设,即:互连网上的每一个 I P数据报都有一个有限的生存期限,这个期限值是 由I P首部的 T T L (生存时间 )字段决定的。每一台路由器在转发 I P数据报时都要将其 T T L值减 1;但如果该 I P数据报在路由器中等待的时间超过 1秒,那就要把 T T L的值减去等待的时间。 实际上,很少有 I P数据报在路由器中的等待时间超过 1秒的,因而每个路由器通常都是把 T T L 的值减1(RFC 1812 [Baker 1995])的5 . 3 . 1节)。由于 T T L字段的长度是 8比特,因此每个 I P数据 报所能经历的转发次数至多为 255。 RFC 793把该限制定义为报文段最大生存时间 M S L,并规定其值为 2分钟。该 R F C同时指 出,将报文段最大生存时间 MSL定义为2分钟是一个工程上的选择,其值可以根据经验进行修 改。最后, RFC 793规定TIME_WAIT状态的持续时间为 MSL的2倍。 图4 - 6 给出的是一个连接关闭后在 T I M E _ WA I T 状态保持了 2 倍报文段最大生存时间 (2MSL),然后发起建立新的连接替身。 客户

服务器

过时重复的报 文段过期作废

图4-6 前一个连接替身关闭 2倍报文段最大生存时间后发起该连接的一个新的替身

由于该连接的新的替身必须在前一个连接替身关闭 2 M S L之后才能再次发起,而且由于前 一个连接替身的过时重复报文段在 T I M E _ WA I T状态的第 1个报文段最大生存时间里就已经消 失,因此我们可以保证前一次连接的过时重复报文段不会在新的连接中出现,也就不可能被 误认为是第二次连接的报文段。 TIME_WAIT状态的自结束 RFC 793 中规定,处于 TIME_WAIT状态的连接在收到 R S T后变迁到 C L O S E D状态,这称 为T I M E _ WA I T状态的自结束。 RFC 1337 [Braden 1992a] 中则建议不要用 R S T过早地结束

48

计计第一部分 TCP事务协议

下载

TIME_WAIT状态。

4.4 TIME_WAIT状态的截断 我们从图 4 - 2和图4 - 3中已经看到, T / T C P协议可以截断 T I M E _ WA I T状态的保持时间。采 用T / T C P协议后,保持时间由报文段最大生存时间 M S L的2倍缩短为 RTO (重传超时 )的8倍。在 4 . 3节,我们也看到了设置 T I M E _ WA I T状态的原因有两个。那么,截断了该状态的保持时间 后,对应于每一个原因都分别产生了什么样的后果? TCP全双工关闭 设置TIME_WAIT状态的第一个原因是最后一个 FIN的重传保持所需的信息。如图4-5所示, 花在TIME_WAIT状态的时间实际上应该根据 RTO来定,而不是根据 M S L。T / T C P中选用乘数 8是为了保证对方有足够的时间超时,并重传最后一个报文段。这样就产生了如图 4 - 2所示的 情形:双方都在等待截断的 TIME_WAIT保持期(图中为12秒)过去。 但是让我们来看看图 4 - 3中发生的情况。那里因新的客户程序又使用了同一个插口对,从 而TIME_WAIT状态在8倍RTO的保持时间到期之前就被截断了。图 4-7给出了一个例子。 客户

服务器

给客户的数据和EOF标志 开始TIME_WAIT状态

新的主动打开 截断TIME_WAIT状态

丢弃 隐含着对前一次连接的确认 关闭旧的连接 启动新的连接 将数据交给服务进程

图4-7 最后一个 ACK丢失时TIME_WAIT状态被截断的情形

图中,最后一个 A C K丢失了,但是客户在收到服务器重传的最后一个报文段之前又再次 发起了同一个连接的另一个替身。当服务器收到新的 S Y N报文段时,由于 TA O测试成功 (因为 8大于6),这就隐含着确认了服务器的待确认的报文段 (图中第2个报文段)。老的连接于是关闭, 新的连接开始建立。由于 TAO测试成功,新SYN报文段中的数据被交付给服务器进程。 有意思的是,由于 T / T C P协议把 T I M E _ WA I T状态的保持时间定义为执行主动关 闭一端所测得的 RTO的函数,因而就有了一个隐含的假定:两端测得的 RTO值相近, 并且在一定的范围之内 [Olah 1995]。如果执行主动关闭的一端在另一端重传最后的 FIN之前就结束了 TIME_WAIT状态,那么对重传 FIN报文段的响应将是 RST而不是重

49

第 4章 T/TCP协议(续)计计

下载 传端所等待的ACK。

当RT T的值较小,最小的 3个T / T C P交换报文段中的第 3个报文段丢失,以及客户 端和服务器端具有不同的软件时钟速率和不同的 RTO最小值时,就会发生上述情况 (第1 4 . 7节给出了客户常用的一些 RTO值)。无论如何,当服务器无法测量 RT T的时候 (由于第 3个报文段丢失 ),客户可以测出较小的 RT T值。例如,假设客户测得的 RT T 值为1 0 m s,RTO的最小值为 1 0 0 m s,这时客户在收到服务器响应 8 0 0 m s以后就截断 TIME_WAIT状态。但如果服务器是从伯克利演变而来的版本,那么其缺省的 RTO为 6秒(如图1 4 - 1 3所示)。当服务器在大约 6秒后重传其 S Y N / A C K / d a t a / F I N时,客户端将 发出一个RST作为响应,给服务器应用程序造成一个虚假的错误。 过时的重复报文段失效 TIME_WAIT状态的截断是可行的,因为 CC选项能够防止过时重复报文段错误地传递给后 续连接。但截断的前提是连接的持续时间小于报文段最大生存时间MSL。考虑图4-8所示的情况。 我们让CC生成器(tcp_ccgen)以最大可能速率递增:每两个报文段最大生存时间 (2MSL)就增 长232-1。这使得事务速率达到最大,为4 294 967 295除240,大约为每秒18 000 000次事务。 假设 t c p _ c c g e n的值在时刻 0时为 1,并以上述最大速率递增,那么在 2 M S L、 4MSL等时刻,tcp_ccgen的值又重新回到 1。而且由于tcp_ccgen的值永远不取0, 在2 M S L的时间里只有 2 3 2-1个值而不是 2 3 2个;因此我们在图中画出 M S L时的 2 147 483 648这个值实际上是在 MSL时刻之前很短的时间里出现。 我们假设连接始于时刻 0,C C值为1,连接持续时间为 1 0 0秒。T I M E _ WA I T状态从第 1 0 0 秒开始,一直保持到第 112秒,或者在主机发起下一次连接时提前结束 (这里假定RTO为1.5秒, 因此T I M E _ WA I T状态的保持时间为 1 2秒)。由于连接的持续时间 ( 1 0 0秒)小于报文段最大生存 时间M S L ( 1 2 0秒),因而可以保证该次连接的所有过时重复报文段在第 2 2 0秒以后一定会消失。 我们还假定 t c p _ c c g e n计数器是以最大可能速率递增的。也就是说,主机在第 0 ~ 2 4 0秒的时 间里建立超过40亿条TCP连接。 时间 CC = 1 的连接

MSL: 所有CC=1的过时重复 报文段在这段时间内到期

图4-8 持续时间小于 MSL的连接: TIME_WAIT状态截断是可行的

50

计计第一部分 TCP事务协议

下载

因此,只要连接的持续时间小于报文段最大生存时间 M S L,将TIME_WAIT状态的保持时 间截断就是安全的,因为 CC选项的值直到所有的过时重复报文段都消失以后才会重复。 要明白为什么只有当连接的持续时间小于报文段最大生存时间

M S L 时才能截断

TIME_WAIT状态的保持时间,那得考察图 4-9所示的情形。 我们仍然假设 t c p _ c c g e n计数器以可能的最快速率递增。某个连接开始于时刻 0,C C值 为2,持续时间为140秒。由于持续时间大于报文段最大生存时间 MSL,故TIME_WAIT状态不 能被截断,从而该连接的插口对只有等到第 3 8 0秒以后才能被重用 (从技术上讲,由于我们给 定t c p _ c c g e n在0时刻的值为1,故C C值为2的连接会在0时刻稍后建立,并在第 1 4 0秒稍后终 止。但这不影响我们的讨论 )。 在第2 4 0 ~ 2 6 0秒之间, C C值2可以重用。如果 T I M E _ WA I T状态被截断 (比如发生在第 1 4 0 秒~第1 5 2秒之间的某个时刻 ),并且如果同一条连接的另一次实现在第 2 4 0 ~第2 6 0秒之间重新 建立且C C值为2,那么由于所有过时的重复报文段只有在第 2 6 0秒以后才会全部消失,这样那 些属于前一次连接的过时重复报文段就有可能被误认为是第

2 次连接的新报文段。在第

2 4 0 ~ 2 6 0秒这段时间,其他的连接 (即不同的插口对 )将C C的值取为 2并没有什么问题;但是同 一个插口对就不能重复使用这个 C C值,因为此时网络中可能还有属于该连接的过时重复报文 段。 时间

CC=2的连接

MSL: 所有CC = 2的连接的 报文段在这段时间内到期

图4-9 持续时间大于 MSL的连接:TIME_WAIT状态无法被截断

从应用程序的角度而言,所谓 TIME_WAIT状态截断就意味着客户程序在与同一个服务器 进行一系列事务时,必须选择是让一系列连接使用同一个本地端口,还是让每次事务使用各 自不同的本地端口。当连接的持续时间小于报文段最大生存时间 M S L时(在事务中这是典型的 情况),重用本地端口可以节约 TCP资源(即减少了对控制块内存的需求,见图 4-2和图4-3)。但

51

第 4章 T/TCP协议(续)计计

下载

是如果客户程序在前一次连接的持续时间大于报文段最大生存时间 M S L的情况下试图重用本 地端口,建立连接时将返回 EADDRINUSE错误(图12-2)。 如图4-2所示,无论应用程序采用哪种端口使用策略,如果两端的主机都支持 T/TCP协议, 而且连接的持续时间小于报文段最大生存时间 MSL,那么TIME_WAIT状态的保持时间总是可 以从2倍M S L截断到8倍RTO。这样就节约了资源 (即内存和 C P U时间)。这对支持T / T C P协议的 2台主机之间的任何 T C P连接 (如F T P、S M T P、H T T P以及其他等 ),只要连接持续时间小于 MSL就都适用。

4.5 利用TAO跳过三次握手 T/TCP协议的主要好处就是能够跳过三次握手。为了理解何以能跳过三次握手,我们需要 先了解三次握手的目的。 RFC 793中对此只是做了一个简单的说明:“引入三次握手的主要原 因是为了避免过时的重复连接在再次建连时造成的混乱。为此,专门定义了一个特殊的控制 报文reset来解决这个问题”。 在三次握手中,每一端都是先发出 S Y N报文段,其中含有各自的起始序列号;然后每一 端都要确认对方的 S Y N报文段。这样就可以排除当重复的过时报文段到达某一端时可能带来 的混淆。此外,常规的 TCP不会在进入ESTABLISHED状态之前就把在 SYN报文段中一起传送 过来的数据交付给上层的用户进程。 T/TCP协议必须提供一种方法,使收到 SYN报文段的一方能够不经过三次握手就保证这个 S Y N不是过时的重复报文段,从而使得随该 S Y N报文段一起传送过来的数据能立刻交付给上 层的用户进程。这里的保护手段是客户发出的 S Y N报文段中附带的 C C选项和服务器缓存的最 近一次从该客户收到的合法 CC值。 考虑图4-10时序图所示的情况。与图 4-8中一样,我们假设 t c p _ c c g e n计数器以最大可能 速率递增:即每 2MSL递增2 32-1。 时间

所有CC=1的SYN到期 所有CC=100的SYN到期

连接,CC = 1,服务器将该CC存入缓存 连接,CC = 100,服务器将该CC存入缓存

图4-10 SYN中较高的 CC值保证该SYN不是过时复制品

在0时刻, t c p _ c c g e n的值为 1;很小的一段时间后其值就变为 1 0 0。由于 I P数据报的生 存期有限,我们可以确信在第 1 2 0秒的时候 ( M S L秒以后 ),C C值为1的所有S Y N报文段都已经 过期而在网络中消失;此后再过一小段时间,所有 CC值为100的SYN报文段也都消失了。

52

计计第一部分 TCP事务协议

下载

于是在第 2 4 0秒的时候,又建立了一个 C C值为1的新连接。假设服务器对该报文段的 TA O 测试成功,那么它就把该客户的最新 C C值缓存下来。此后不久,该服务器主机又收到同一客 户的另一个连接请求。由于这时 SYN报文段中的 CC的值(为100)比服务器当前保存的该客户最 近一个合法 C C值( 1 )要大,而且以前 C C值为1 0 0的连接中的所有 S Y N都已经超过了 M S L时间, 因而服务器可以断定这是一个新的 SYN。 事实上,这就是 RFC 1644中所述的TAO测试:“如果某个特定客户主机的第一个 SYN报文 段(即只含 S Y N位而不含 A C K位的报文段 )中所携带的 C C值大于缓存中的该客户 C C值,C C值 的单调递增特性可以确保这是一个新的 S Y N报文段,可以立即接收下来”。正是 C C值的单调 递增特性以及下面的两个假设确保了 SYN报文段是新的,使得 T/TCP协议能够跳过三次握手: 1) 所有的报文段都只有有限的 MSL秒的生存期; 2) tcp_ccgen计数器在2MSL的时间内的递增量不超过 232-1。 失序的SYN报文段 图4-11给出了两台T/TCP主机和一个失序到达的 SYN报文段。这个 SYN并不是一个过时重 复报文段,只不过是没有按照正确的顺序到达服务器。 客户进程

服务器进程

客户端口号1600

客户

客户端口号1601 TAO测试成功,tao_cc [客户]=5000 传送数据给服务器进程

TAO测试失败,数据进队列, 3WHS, tao_cc [客户]=5000

将队列中的数据传给服务器进程 tao_cc [客户]=5000

图4-11 两台T/TCP主机和失序到达的 SYN报文段

服务器缓存的该客户的 CC值为1。报文段1是从客户的端口 1600发出的,携带的CC值为15, 但它在网络上延迟了一段时间。报文段 2是从客户的端口 1 6 0 1发出的,携带的 C C值为 5 0 0 0。 当报文段 2到达服务器时, TA O测试成功 ( 5 0 0 0大于 1 ),于是对该客户缓存的最新 C C值改为 5000,并把数据交付给进程。报文段 3和报文段4完成该次事务。

53

第 4章 T/TCP协议(续)计计

下载

当报文段 1终于到达服务器时, TA O测试失败 ( 1 5小于5 0 0 0 ),于是服务器给出的响应也是 一个S Y N报文段,其中带有对所收到 S Y N的A C K,强迫执行三次握手过程。只有当三次握手 完成后才会把数据交付给服务器进程。报文段 6结束三次握手过程,队列中的数据于是被交付 给服务器进程 (图中没有给出该事务的后续过程 )。但是,尽管三次握手成功结束, C C值为1 5 的报文段也并不是一个过时的重复报文段 (它只是到达的顺序不对 ),服务器所记录的该客户的 C C值并不更新。如果更新 C C会使其值由 5 0 0 0回到1 5,可能会使服务器错误地收下来自该客 户的、CC值介于15~5000之间的过时重复报文段。 翻转了符号位的 CC值 在图4 - 11中我们看到,当 TA O测试失败后,服务器强迫执行三次握手;即使握手过程成 功结束,服务器所记录的该客户 C C值也不更新。从协议的角度出发,这样做是正确的,但却 降低了效率。 服务器端 TA O测试失败是很可能发生的,因为 C C值是客户端生成的。对客户端来说,这 是所有连接的全局变量;对服务器来说,则是“翻转了它们的符号位 (wrap their sign bit) ”。 (类似于 T C P的序列号, C C值也是无符号的 3 2位数。对 C C值进行比较采用模运算,具体算法 可见卷 2第6 4 9 ~ 6 5 0页。当我们说 C C值a的符号位相对于值 b“翻转”了时,其具体含义是 a的 值增加后不再比 b大了,反倒是比 b小了)。看图4-12。 时间

连接,CC = 1,服务器将该CC存入缓存

连接,CC = 2 147 483 648 TAO测试失败,服务器强迫执行三次握手

图4-12 CC的值翻转其符号位造成 TAO测试失败

客户在0时刻与服务器建立连接, CC值为1。服务器TAO测试成功,并把该 CC值记录为该 客户当前的 C C值。接着该客户与其他服务器建立了 2 147 483 646个连接。在第 1 2 0秒时,它 与0时刻建立起连接的服务器又建立一个连接,但此时的 C C值为2 147 483 648 。服务器收到 S Y N后,TA O测试失败 (按模运算,2 147 483 648比1小,如卷 2的图2 4 - 2 6所示),然后三次握 手过程验证了该 SYN报文段,但是服务器当前记录的该客户的 CC值仍然是1。 这就意味着,从此开始到第 2 4 0秒的这一段时间里,该客户向该服务器发送任何一个 S Y N 都要经过三次握手过程。这里假设 t c p _ c c g e n计数器持续以最快的速率递增。但更可能的情 况是该计数器的递增速率会低得多,意味着计数器由 2 147 483 648递增到4 294 967 295就不

54

计计第一部分 TCP事务协议

下载

只需要120秒了,而可能是几个小时甚至几天的时间。可是在该计数器的符号位再次翻转以前, 该客户和该服务器之间的所有 T/TCP连接都要执行三次握手。 这个问题的解决需要连接双方的共同努力。首先,不仅服务器要为每个客户记录其最新 的CC值,而且客户也要记录发给每个服务器的最新 CC值。这两个变量即为图 2-5中的tao_cc 和tao_ccsent。 其次,当客户发现它将要使用的 t c p _ c c g e n值小于它最近发送给服务器的 C C值时,客 户就发出CCnew选项而不是 CC选项。该选项强迫两台主机再次同步它们各自记录的 CC值。 服务器收到一个带有 C C n e w选项的 S Y N报文段后,它把该客户的 t a o _ c c标记为 0 (未定 义)。三次握手成功结束后,如果 TA O缓存中该客户的表项还是未定义,就将其更新为这次收 到的CC值。 由此可见,当客户端没有记录该服务器的 CC值(比如客户端重启,或是缓存中对应于该服 务器的表项被冲掉 )时,或者客户端检测到该服务器的 CC值已翻转时,该客户将在其初始 SYN 报文段中发送CCnew选项而不是 CC选项。 重复的SYN/ACK报文段 到目前为止,我们的注意力一直集中在服务器如何确定所收到的 S Y N报文段是新的还是 过时和重复的。这使得服务器可以绕开三次握手的过程。但是客户端又如何确定所收到的服 务器响应(SYN/ACK报文段)不是过时和重复的呢? 在常规的 T C P协议中,客户端发送的 S Y N报文段中不带数据,因此服务器要确认的内容 只有一个字节: S Y N。此外,从伯克利演变来的实现又在建立新连接时将初始发送序号 ( I S S ) 递增64 000(T C P _ I S S I N C R 除以2) (见卷2第808页),这样客户端发送的后续 SYN中的初始发 送序号就总是比前一次连接的要大。因而过时的重复 S Y N / A C K报文段就不太可能含有客户端 可接受的确认字段。 然而在T/TCP协议中,客户端的 SYN报文段中通常都带有数据,这就扩大了客户端从服务 器收到可接受确认的范围。RFC 1379 [Braden 1992b]的图7给出的一个例子中,客户端就错误地 接受了一个过时的重复 S Y N / A C K报文段。然而,该例子出现这个问题的原因在于前后两个相 继连接的初始发送序号只差 100,小于客户端在 SYN报文段中附带的数据字节数 (300字节)。我 们前面提到过,从伯克利演变来的实现中,每建立一个新的连接时总会把 ISS增加至少64 000。 而64 000已经大于T/TCP协议的默认发送窗口宽度 (通常为4 096字节),这就使客户端不可能接 受一个过时的重复 SYN/ACK报文段。 4 . 4 B S D - L i t e 2系统中采用了我们在 3 . 2节中讨论过的随机产生 I S S值方法。 I S S的 实际增量平均分布于 31 232和96 767之间,均值为 63 999。31 232仍然比默认的发送 窗口大,下面我们将要讨论的 CCecho选项使得这个问题存有争议。 然而,T / T C P协议用C C e c h o选项对过时的重复 S Y N / A C K报文段问题提供完整的保护。客 户端知道自己发出的 SYN报文段中的CC值,而服务器必须在 CCecho选项中把该值原样发回给 客户端。如果服务器的响应中不含所期望的 C C e c h o值,那么客户端就把该响应丢弃 (图11 - 8 )。 C C的值具有单调递增特性,而且在至多 2 MSL秒的时间内就循环一次,这就可以保证客户端 不会接受过时的重复 SYN/ACK报文段。 注意,客户不能对服务器传来的 S Y N / A C K报文段执行 TA O测试:太晚了。服务器已经接

下载

55

第 4章 T/TCP协议(续)计计

受了客户端的 S Y N,数据已经被交付给了服务器进程,而客户收到的 S Y N / A C K报文段中也含 有服务器给出的响应。此时客户端再要强迫执行三次握手就太迟了。 重传的SYN报文段 RFC 1644和我们在本节中的讨论都忽略了客户端的 S Y N或服务器的 S Y N的重传可能性。 例如,在图 4 - 1 0中,我们假设 t c p _ c c g e n在0时刻的值为 1;接着假设所有 C C值为1的S Y N报 文段在第 1 2 0秒时都已经过时消失。而实际上也可能是这样的: C C值为1的S Y N报文段可能在 第0~75秒之间的某个时刻重传了,于是所有CC值为1的SYN报文段在第 195秒时才会过时消失, 而不是在第 1 2 0秒时就过时了 (如卷2第6 6 4页所述,从伯克利演变来的实现中,客户端 S Y N报 文段和服务器SYN报文段的重传超时上限为 75秒)。 这并不影响TA O测试的正确性,但却降低了 t c p _ c c g e n计数器的最大递增速率。前面我 们讲过, t c p _ c c g e n计数器的最大递增速率是在 2 MSL秒的时间里递增 232-1,从而使最大事 务速率达到大约每秒 18 000 000次。当考虑到SYN报文段的重传之后,上述最大速率就变为在 2 MSL+2 MRX秒的时间里递增 232-1,其中MRX是系统规定的SYN报文段重传时限 (在Net/3中 是75秒)。最大事务速率也由此降低到大约每秒 11 000 000次。

4.6 小结 TCP协议的TIME_WAIT状态有两个功能: 1) 实现了连接的全双工关闭。 2) 使得过时的重复报文段超时作废。 如果T/TCP连接的持续时间小于120秒(1倍的报文段最大生存时间 MSL),那么TIME_WAIT 状态的保持时间只要 8倍的重传超时,而不是 240秒。而且,在前一次连接还处于 TIME_WAIT 状态时,客户端就可以建立一个连接的新的替身,从而进一步截短了等待时间。我们说明了为 什么这么做是可行的,仅受限于 T/TCP协议能支持的最大事务速率 (大约每秒18 000 000次)。如 果T / T C P客户知道要与同一台服务器执行大量事务,那么它每次都可以使用同一个本地端口 号,从而可减少 TIME_WAIT状态的控制块的数量。 T C P的TA O ( T C P加速打开 )测试使得 T / T C P的客户-服务器程序可以跳过三次握手过程。 这是在服务器收到客户端的 C C值大于服务器记录的该客户 C C值时实现的。正是 C C值的单调 递增性以及以下两点假定才确保了服务器能够断定客户端的 S Y N是新的,使 T / T C P能够跳过 三次握手过程: 1) 所有报文段都有一个有限的生存期限: MSL秒; 2) tcp_ccgen计数器的最大递增速率为:在 2MSL秒内增加2 32-1。

下载

第5章 T/TCP协议的实现:插口层 5.1 概述 从本章开始我们讨论 Net/3版中T/TCP协议的实现。我们沿用卷 2的编排顺序和表达风格: • 第5章:插口层 • 第6章:路由表 • 第7章:协议控制块 (PCB) • 第8章:TCP概述 • 第9章:TCP输出 • 第10章:TCP函数 • 第11章:TCP输入 • 第12章:TCP用户请求 在这些章节的介绍中,我们都假设用户已经有了本系列书的卷2或者其中源代码的副本。这样 我们就只要介绍实现T/TCP协议所需的1 200行新代码,而不需要重述卷2已介绍过的15 000行代码。 T/TCP协议对插口层所作的改动是很小的: s o s e n d函数需要处理M S G _ E O F标志,允许协 议调用sendto函数和sendmsg函数隐式地打开和关闭连接。

5.2 常量 T/TCP协议中需要使用三个常量: 1) < s y s / s o c k e t . h >中定义的 M S G _ E O F。如果在调用 s e n d、s e n d t o或s e n d m s g函数 时设置了该标志,那么利用该连接发送数据就结束了,实际上就是结合了

w r i t e和

shutdown两个函数的功能。在卷 2的图16-12中应该加上该标志。 2) < s y s / p r o t o s w . h >中定义了一个新的协议请求 P R U _ S E N D _ E O F。在卷2的图15-17中 应该加上该请求。这个请求是由 sosend函数发出的,已在后面的图 5-2中给出。 3) < s y s / p r o t o s w . h >中定义了一个新的协议标志 P R _ I M P L O P C L(意指“隐式打开和关 闭”)。这个标志有两重含义; (a)协议允许在调用 s e n d t o或s e n d m s g函数时给定对等 端的地址,而不必在此之前调用 c o n n c e t函数(隐式打开);(b)协议能够识别 M S G _ E O F 标志(隐式关闭 )。注意,只有在面向连接的协议 (如T C P )中才需要 ( a ),因为在无连接的 协议中总是可以直接调用 s e n d t o和s e n d m s g函数而不需要事先调用 c o n n e c t函数。 应该在卷2的图7-9中加上该标志。 协议代码中 s w i t c h程序块的 T C P入口i n e t s w [ 2 ](卷2中图7 - 1 3的第5 1 ~ 5 5行)应该在 其pr_flags的标志值中加上 PR_IMPLOPCL。

5.3 sosend函数 sosend函数有两处改动。图5-1所示的代码用来替代卷2第397页中第314~321行的程序代码。

57

第 5章 T/TCP协议的实现:插口层计计

下载

图5-1 sosend 函数:差错检查

注意,对代码的替换在这里实际上是从第 3 2 0行开始的,而不是第 3 1 4行。这是 因为在这个文件的前面部分还有一些与 T/TCP无关的修改。由于我们要用这里的 17行 代码替换卷 2中的8行代码以支持 T/TCP协议,因而该文件后面部分代码段中的行号也 与卷2中对应的代码段的行号不一样。当本书提到卷 2中的代码段时,我们所说的行 号通常都是指在卷 2中的行号。由于卷 3中的代码是卷 2中的相应代码经过增删后得到 的,因而相同功能代码段的行号会比较接近,但不一定相同。 320-336

修改后代码段的作用是:当设置了协议的 P R _ I M P L O P C L标志、并且调用进程给

出了目的地址时,允许在面向连接的插口上调用 s e n d t o函数和 s e n d m s g函数。如果调用进 程没有给出目的地址,那么对应于 T C P插口将返回 E N O T C O N N ,对应于 U D P插口则返回 EDESTADDRREQ。 330-331

这个if语句使得当连接处于 SS_ISCONFIRMING状态时,允许只写控制信息而不

写任何协议数据。 OSI TP4协议采用了这种做法, TCP/IP协议则没有采用。 图5-2所示的是对 sendto函数的修改,图中代码用来替代卷 2第400页的第399~403行。

图5-2 sosend 函数:协议发送

58

计计第一部分 TCP事务协议

下载

我们第一次看到内容为 X X X的评注。这是为了提醒读者,所注释的代码作用不明 确,副作用也不明显,抑或是一个难题的快捷解决方法。本例中, s p l n e t函数用于 提高处理优先级,以优先执行这段代码。处理优先级用图 5 - 2底部所示的 s p l x恢复。 卷2的1.12节叙述了Net/3中各种中断的级别。 416-427

如果指定了 M S G _ O O B标志,那就发出 P R U _ S E N D O O B请求。否则,如果指定了

M S G _ E O F标志,协议又支持 P R _ I M P L O P C L标志,而且再没有数据要交给协议了 (r e s i d小于 或等于0),那就发出 PRU_SEND_EOF请求而不是通常的 PRU_SEND请求。 回忆 3 . 6 节中的例子。应用程序调用 s e n d t o 函数发送了 3 3 0 0字节数据,并指定了 MSG_EOF标志。在sosend函数执行的第一次循环中,图 5-2所示的代码发出了一个PRU_SEND 请求,以发送前 2 048字节数据 (一个m b u f簇)。在第二次循环中,发出 P R U _ S E N D _ E O F请求, 以发送剩下的1 252 字节数据(在另一个mbuf簇中)。

5.4 小结 T / T C P给T C P增加了隐式打开和关闭的功能。所谓隐式打开是指应用程序不是通过调用 connect函数建立连接,而是调用 sendto函数或sendmsg函数并指定目的地址来建立连接。 而隐式关闭则是指允许应用程序在调用 s e n d、s e n d t o或s e n d m s g函数时指定 M S G _ E O F标 志,从而把输出和关闭合并起来发布。图 1-10中对s e n d t o函数的调用就把打开、写数据和关 闭合并在一个系统调用中实现。本章所示的程序代码修改给 N e t / 3的插口层加上了隐式打开和 关闭功能。

下载

第6章 T/TCP的实现:路由表 6.1 概述 T/TCP需要在其每主机高速缓存中为每一个与之进行过通信的主机创建一个记录项。每个 记录项包括图 2 - 5所示的t a o _ c c、t a o _ c c s e n t和t a o _ m s s o p t三个变量。已有的 I P路由表 是每主机高速缓存的最合适位置。在 N e t / 3中,利用卷 2第1 9章介绍的“克隆”标志,很容易 为每一个主机创建一个每主机路由表记录项。 在卷2中,我们已经知道网际协议 (没有T/TCP)利用了Net/3提供的一般路由表功能。卷 2的 图18-17说明了调用 r n _ a d d r o u t e函数就可增加路由记录,调用 r n _ d e l e t e可以删除路由记 录,调用 r n _ m a t c h可以查找路由记录,以及调用 r n _ w a l k t r e e可以遍历整棵树 ( N e t / 3中用 二叉树来存储其路由表,叫做基树 (radix tree)。在TCP/IP中,除了这些一般功能外,不再需要 有其他功能支持。然而在 T/TCP中就不一样了。 既然一个主机可以在一个很短的时间内与成百上千的主机通信 (例如几个小时,或者对于 一个非常繁忙的 W W W服务器来说可能不需要一个小时,详见 1 4 . 1 0节的示例 ),因此就需要有 一些方法使每主机路由表中的路由记录超时作废。本章我们主要研究 T/TCP协议在IP路由表中 动态创建和删除每主机路由表记录项的功能。 卷2中的习题 1 9 . 2给出了自动地为每一个与之通信的对等主机创建每主机路由表 记录项的一个琐细方法。我们在本章中所叙述的方法在概念上与其非常相似,但对 大多数 T C P / I P路由都能自动进行。习题中创建的每主机路由是不会超时的;创建以 后它们就一直存在,直到主机再次启动或者管理人员手工删除。这就需要有一个更 好的方法来自动地管理所有的每主机路由。 并非每一个人都认为已有的路由表是开设 T/TCP每主机高速缓存的好地方。另一 个方法是将 T / T C P每主机高速缓存在内核中作为其自身基树来存储。这项技术 (一稞 分立的基树)容易实现,利用了内核中已有的一般基树功能,在 N e t / 3的网络文件系统 NFS中就采用了这个方法。

6.2 代码介绍 C语言文件 n e t i n e t / i n _ r m x . c中定义了 T / T C P为T C P / I P的路由功能所增加的函数。这 个文件中只包含了我们在本章中所介绍的专门用于 I n t e r n e t的函数。我们将不会介绍卷 2第1 8、 19和20章中所叙述的所有路由函数。 图6 - 1中给出了专门用于 I n t e r n e t的新增路由函数 (在本章中介绍的函数用带阴影椭圆表 示,函数名字用 i n _开头 )和一般路由函数 (这些函数的名字通常用 r n _或r t开头 )之间的关 系。

60

计计第一部分 TCP事务协议

下载

每10分钟

图6-1 专用于Internet的路由函数之间的关系

全局变量 图6-2中给出了专用于 Internet的新增全局变量。 FreeBSD版允许系统管理员用 s y s c t l程序修改图6-2中最后三个变量的值,程序 要加前缀 n e t . i n e t . i p。我们没有给出完成这个功能的程序代码,因为它只是对卷 2图8-35中的ip_sysctl函数作了一些小小的补充。 变



rtq_timeout rtq_toomany rtq_reallyold rtq_minreallyold

数据类型 int int int int





i n _ r t q t i m o运行的频率 (默认值= 每一次10分钟) 在动态删除开始前有多少路由 路由已经确实很陈旧时,存在了多长时间 r t q _ r e a l l y o l d最小值

图6-2 专用于Internet的全局路由变量

6.3 radix_node_head结构 在r a d i x _ n o d e _ h e a d结构中新增加了一个指针 (卷2的图1 8 - 1 6 ):r n h _ c l o s e。当它指 向in_clsroute时,除了指向某个 IP路由表外,它的值总是空的,见后面的图 6-7中。 这个函数指针用在 r t f r e e函数中。卷 2的图1 9 - 5中的第 1 0 8行和第 1 0 9行之间要加上下面 这些程序行,以说明并初始化自动变量 rnh: 以下3行程序则加在第 112~113行之间:

61

第 6章 T/TCP的实现:路由表计计

下载

如果这个函数指针非空,并且引用计数达到 0,就要调用关闭函数。

6.4 rtentry结构 T/TCP需要在r t e n t r y结构中增加两个路由标志 (卷2第464页)。但是现有的r t _ f l a g s项 是一个 1 6位短整数,并且 1 5位已经占用了 (卷2第4 6 4页)。为此要在 r t e n t r y结构中新增一个 标志项rt_prflags。 另一个解决的方法是将短整数 r t _ f l a g s改为长整数,这种做法在将来的版本中 可能会有。 T/TCP使用了rt_prflags的两个标志位。 • R T P R F _ W A S C L O N E D是由 r t r e q u e s t设置的 (卷2第4 8 8页第 3 3 5 ~ 3 3 6行),从设置了 RTF_CLONING标志的记录项创建一个新的记录项时就要设置该标志。 • RTPRF_OURS是由in_clsroute设置的(图6-7),当IP路由的最后一个克隆参考项关闭时就 要设置该标志。这时,要设置一个定时器,以便在将来的某个时间将这个路由表项删除。

6.5 rt_metrics结构 T/TCP修改路由表的目的是在每个路由表记录项中存储附加的每主机信息,实际上也就是 三个变量: t a o _ c c 、 t a o _ c c s e n t 和t a o _ m s s o p t 。为了容纳这些附加的信息,在 rt_metrics结构(卷2第464页)中就需要有一个新的字段: u_long rmx_filler[4];

/* protocal family specific metrics */

这就有了一个16字节的协议专用向量, T/TCP可以利用,如图 6-3所示:

图6-3 T/TCP用作TAO高速缓存的 rmxp_tao 结构

153-157

tcp_cc数据类型用于连接计数,是用 typedef定义的一个无符号长整数 (类似于

TCP的序号)。tcp_cc变量的值为 0,表示它还未定义。 158

当给定一个指向 r t e n t r y结构的指针,宏 r m x _ t a o p就返回一个指向相应 r m x p _ t a o

结构的指针值。

6.6 in_inithead函数 卷2第5 0 4页详细介绍了 N e t / 3中路由表初始化工作的所有步骤。 T / T C P所做的第一项修改 是将 i n e t d o m a i n 结构中的 d o m _ r t a t t a c h 字段指向 i n _ i n i t h e a d ,而不是指向 rn_inithead(卷2第151页)。图6-4中给出了in_inithead函数。 1. 执行路由表的初始化 222-225

rn_init h e a d用于分配并初始化一个 r a d i x _ n o d e _ h e a d结构。在Net/3中也就

62

计计第一部分 TCP事务协议

下载

这些功能。该函数的其他功能是 T / T C P新增加的,并且仅仅在“实”路由表初始化时执行。 在NFS装载点初始化另一个路由表时也会调用这个函数。 2. 改变函数指针 226-229

r n _ i n i t h e a d还要将r a d i x _ n o d e _ h e a d结构中取默认值的两个函数指针修改

为r n h _ a d d a d d r和r n h _ m a t c h a d d r。它们也是在卷 2图1 8 - 1 7中给出的四个指针中的两个。 这就使得在调用一般基结点函数前可以执行 I n t e r n e t中专有的一些动作。 r n h _ c l o s e函数指 针是T/TCP中新加的。

图6-4 in_inithead 函数

3. 初始化超时函数 230

i n _ r t q t i m o是超时函数,是第一次调用。这个函数的每一次调用,它总会安排在将

来再次被调用。

6.7 in_addroute函数 用r t r e q u e s t可以创建一个新的路由表记录项,它们或者是 R T M _ A D D命令的结果,也 可能是 R T M _ R E S O L V E命令的结果。这两个命令都会从已经存在并且设置了克隆标志的记录 项中创建一个新的记录项 (卷2第488~489页)。创建以后就要调用 r n h _ a d d a d d r函数,我们在 Internet协议中看到的是 in_addroute函数。图6-5给出了这个新函数。

图6-5 in_addroute 函数

63

第 6章 T/TCP的实现:路由表计计

下载

图6-5 (续)

52-61

如果所增加的路由不是一个主机路由,也没有设置克隆标志,这时就要检查路由表

的主键(IP地址)。如果IP地址不是一个多播地址,该新创建的路由表记录项就要设置克隆标志。 rn_addroute为路由表增加记录项。 这个函数的功能是为所有非多播网络路由设置克隆标志,包括默认的路由。这个克隆标 志的作用是为任何一个在路由表中能够查到一条非多播网络路由或默认路由的目的地址创建 一个新的主机路由。这个新克隆的主机路由是在它第一次查找时创建的。

6.8 in_matroute函数 r t a l l o c l(卷2第4 8 3页)在查找一个路由时调用了 r n h _ m a t c h a d d r指针所指向的函数 (即图6-6中所示的in_matroute函数)。

图6-6 in_matroute 函数

调用rn_match来查找路由 71-78

r n _ m a t c h在路由表中查找路由。如果找到了一个路由并且其参考计数值为 0,这就

是该路由表记录项的第一个参考路由。如果记录项已经超时,也就是说,如果设置了 R T P R F _ O U R S标志,这时就要把这个标志将关闭,并且将 r m x _ e x p i r e定时器设置为 0。当 路由已经关闭,但在删除前又重新使用该路由时,就往往会发生这种情况。

6.9 in_clsroute函数 我们曾经提到过, T / T C P在r a d i x _ n o d e _ h e a d 结构中增加了一个新的函数指针 r n h _ c l o s e 。当参考计数值为零时,这个函数就要在

r t f r e e 中调用,这又将调用

in_clsroute函数,如图 6-7所示。 1. 检查标志 93-99

要做以下的测试:路由必须是正常的, R T F _ H O S T标志必须是打开的 (即这不是一个

64

计计第一部分 TCP事务协议

下载

图6-7 in_clsroute 函数

网络路由 ),R T F _ L L I N F O 标志必须是关闭的 (对A R P 记录项,该标志要打开 ),R T P R F _ W A S C L O N E D必须是打开的 (记录项是克隆的 ),R T P R F _ O U R S必须是关闭的 (该记录项还未超 时)。如果这些测试中有任何一项失败,函数都将结束并返回。 2. 设置路由表记录项的终止时间 100-112

在通常的情况下,如果 r t q _ r e a l l y o l d非零,就要打开 R T P R F _ O U R S标志,并

且要将r m x _ e x p i r e时间值设置为当前时钟的秒值 (t i m e . t v _ s e c)加上r t q _ r e a l l y o l d值 (一般为3 600 秒,即1小时)。如果系统管理员用sysctl程序将rtq_reallyold的值设置为 0, 该路由就会立即被 rtrequest删除。

6.10 in_rtqtimo函数 图6-4中,in_inithead首次调用in_rtqtimo函数。每一次调用执行 in_rtqtimo时, 它都会自动安排在 rtq_timeout(默认值为600秒或者10分钟)后再次得到调用。 i n _ r t q t i m o的目的是 (用一般的 r n _ w a l k t r e e函数)找遍整个 I P路由表,对每一个记 录项调用 i n _ r t q k i l l。i n _ r t q k i l l要决定是否删除相应记录项。需要从 i n _ r t q t i m o 传递有关信息给 i n _ r t q k i l l(回顾图 6 - 1 ),或者反过来。传递是通过给 r n _ w a l k t r e e的 第三个变量实现的。这个变量是 r n _ w a l k t r e e传递给 i n _ r t q k i l l的一个指针。由于该 变量是一个指针,所以信息可以在 i n _ r t q t i m o和i n _ r t q k i l l之间的任何一个方向上传 递。 in_rtqtimo传递给rn_walktree的指针指向 rtqk_arg结构,这个结构如图 6-8所示。

下载

65

第 6章 T/TCP的实现:路由表计计

图6-8 rtqk_arg 结构:in_rtqtimo 与in_rtqkill 之间传递的信息

研究in_rtqtimo函数时,我们可以看到这些字段是怎样用的。如图 6-9所示。

图6-9 in_rtqtimo 函数

66

计计第一部分 TCP事务协议

下载

1. 设置rtqk_arg结构并调用 rn_walktree 167-172

r t q k _ a r g结构的初始化包括:在 I P路由表的首部设置 r n h,计数器 f o u n d和

k i l l e d清零, d r a i n i n g和u p d a t e标志清零,将 n e x t s t o p设置为当前时间 (秒级)加上 r t q _ t i m e o u t(600秒,即10分钟)。r n _ w a l k t r e e要找遍整个 IP路由表,对每一个记录项调 用in_rtqkill(图6-11)。 2. 检查路由表记录项是否过多 173-189 如果以下三个条件满足,就说明路由表中的记录项过多: 1) 已经超时但仍未删除的路由表记录项数(found减去killed)超过了r t q _ t o o m a n y(默认 值为128)。 2) 上一次执行本项操作至今所经过的秒数超过了 rtq_timeout(600秒,即10分钟) 3) rtq_really超过了rtq_minreallyold(默认值为10)。 如果以上条件全部成立,则将 rtq_reallyold设置为其当前值的 2/3(用整数除法 )。由于 该值的初始值为 3 600秒( 6 0分钟),因此它的取值就会分别是 3 6 0 0、2 4 0 0、1 6 0 0、1 0 6 6和7 1 0, 等等。但是该值不允许低于rtq_minreallyold(默认值为10秒)。当前时间值记录在静态变量。 l a s t _ a d j u s t e d _ t i m e o u t中,并且要有一个调试消息发送给 s y s l o g d 守护程序 ([Stevens 1992]的13.4.2节中给出了如何用 log函数发送消息给 syslogd守护程序)。这段代码 以及减少 r t q _ r e a l l y o l d值的目的是缩短路由表的处理周期,在路由表中记录项过多时删 除过时的路由。 190-195

r t q k _ a r g结构中的计数器 f o u n d和k i l l e d又初始化为0,u p d a t i n g标志此时

设置为1,再次调用 rn_walktree。 196-198

i n _ r e q k i l l函数将 r t q k _ a r g 结构中的 n e x t s t o p 字段设置为下次调用

in_rtqtimo的时间。内核的 timeout函数会安排这个事件在需要的时候发生。 每10分钟就游历整个路由表一遍需要多大的开销?很明显这依赖于路由表中记录 项的数目。在14.10节中我们模拟了一个繁忙的 Web服务器中的 T/TCP路由表大小,发 现即使24小时内服务器要与 5 000个不同的客户连接,并且主机路由的超时间隔为 1小 时,路由表中也从不会超过 550个记录项。目前,一些 Internet主干路由器中有成千上 万条的路由表记录项,但是它们不是主机,而是路由器。我们并不会希望主干路由器 支持T/TCP,因此也不必有规律地游历这样一个非常大的路由表以删除过时的路由。

6.11 in_rtqkill函数 rn_walktree要调用in_rtqkill函数,其中 rn_walktree又是被in_rtqtimo调用 的。我们在图6-11中所示的程序in_rtqkill就用于在必要时删除 IP路由表记录项。 1. 只处理已经超时的记录项 134-135

这个函数只对设置了 RTPRF_OURS标志的记录项进行处理,也就是说,只处理已

经被i n _ c l s r o u t e关闭了的记录项 (即它们的参考计数值已经达到零 )和已经过了一个超时间 隔 ( 通常为 1 小时 ) 而过期的记录项。这个函数不影响正在使用的路由

( 因为这些路由的

RTPRF_OURS标志不会打开的 )。 136-146 如果设置了 draining标志(在当前的实现中是永远不会设置的 ),或者超时间隔已 到(r m x _ e x p i r e时间小于当前时间 ),相应的路由就被 r t r e q u e s t删除。r t q k _ a r g结构中

67

第 6章 T/TCP的实现:路由表计计

下载

的f o u n d字段累计已经设置了 R T P R F _ O U R S标志位的路由表记录项数, k i l l e d字段则用于 累计被删除的记录项数。 147-151

e l s e语句在当前记录项还没有超时时执行。如果设置了 u p d a t i n g标志(图6-9中

我们已经看到,当过期的路由太多时就会设置该标志,或者下一次对整个路由表进行处理时 也会设置该标志 ),并且还远未到过期时间 (一定是在将来某一时刻,以便相减时产生一个正 值),这时就将过期时间重新设置为当前时间加上 r t q _ r e a l l y o l d。考虑图 6 - 1 0所示的例子 就容易理解了。 此后过期时间初始设置为3600秒 时间差 = 3100 此后过期时间重置为2400秒

图6-10 in_rtqkill 重新设置过期时间

图6-11 in_rtqkill 函数

图中x轴为时间,单位是秒。一个路由在时刻 1 0 0时被i n _ c l s r o u t e关闭(当它的参考计

68

计计第一部分 TCP事务协议

下载

数值达到零时 ),同时r t q _ r e a l l y o l d有了初始值 3600(1小时)。这样,这个路由的过期时间 就为 3 700 。但在时刻 6 0 0,执行了 i n _ r t q t i m o,并且路由未删除 (因为它的过期时间是在 3 100 秒,还未到 ),但由于路由记录项太多,使得 i n _ r t q t i m o将r t q _ r e a l l y o l d的值重 置为 2 400 、将 u p d a t i n g 设置为 1,并且 r n _ w a l k t r e e 再次处理整个 I P路由表。此时 i n _ r t q k i l l发现u p d a t i n g已经是1并且路由将在3100秒时过期。因为 3100大于2400,过期 时间就要重置为过 2400秒以后,也就是在 3000秒的时刻。路由表变大时,过期时间也就变短。 2. 计算下一个过期时间 152-153

每当发现一个记录项已经过期但其过期时间还未到时,就要执行这段代码。

n e x t s t o p要设置为其当前值与路由表记录项过期时间中的最小值。前面讲过, n e x t s t o p 的初始值是由 i n _ r t q t i m o设置的,设置值为当前时间加上 r t q _ t i m e o u t的值(即1 0分钟以 后)。 想想图 6 - 1 2所示的例子。 x轴代表时间,单位为秒,黑点的时刻为 0、6 0 0、……,等等, 是调用in_rtqtimo函数的时刻。 路由过期

图6-12 根据路由过期时间执行 in_rtqtimo

in_addroute创建一个IP路由,然后在时刻 100时被in_clsroute关闭。它的过期时间 设置为3 7 0 0 ( 1小时以后 )。在时刻 3 0 0创建了第二个路由,然后被关闭,其过期时间设为 3 9 0 0。 i n _ r t q t i m o函数每十分钟就执行一次,分别是在时刻 0、6 0 0、1 2 0 0、1 8 0 0、2 4 0 0、3 0 0 0和 3 6 0 0等。从时刻 0至时刻3 0 0 0,n e x t s t o p的值设置为当前时刻加上 6 0 0,这样在时刻 3 0 0 0为 这两个路由分别调用 in_rtqkill时,nextstop就改为了3600,因为3600小于3700和3900。 但是在时刻 3 6 0 0为这两个路由分别又调用 i n _ r t q k i l l时,n e x t s t o p就设置为 3 7 0 0,因为 3 7 0 0小于3 9 0 0,也小于 4 2 0 0。这就意味着在时刻 3 7 0 0将再次调用 i n _ r t q t i m o,而不是在时 刻4200。此外,当 in_rtqkill在时刻3700被调用时,因为另一个路由需要在时刻 3900过期, 这就要将 n e x t s t o p 设置为 3 9 0 0 。假设没有别的 I P 路由要过期,在时刻 3 9 0 0 执行了 in_rtqtimo以后,它将在4500、5100、……,等等时刻再次执行。 过期时间的交互影响 在路由表记录项的过期时间和 r t _ m e t r i c s结构中的 r m x _ e x p i r e字段之间会有一些微 小的交互影响。首先,地址解析协议 A R P也同样用该字段实现 A R P记录项的超时 (卷2的第2 1 章)。这意味着路由表中有关本地子网中某一主机的路由表记录项 (以及与其相关的 TA O信息) 在该主机的 A R P记录项被删除时也会同时被删除,通常是每 2 0分钟执行一次删除。这个间隔 比i n _ r t q k i l l( 1小时)所用的默认过期时间要短得多。回顾前面应该记得, i n _ c l s r o u t e 明确地忽略已设置了 R T M _ L L I N F O标志的 A R P记录项 (图6 - 7 ),让A R P对它们执行超时处理, 而不用in_rtqkill。 其次,执行 r o u t e程序去读取并打印一个克隆的 T / T C P路由表记录项的度量数据和过期

下载

69

第 6章 T/TCP的实现:路由表计计

时间的副作用是会重置其过期时间。这种情况是这样的。假设有一个使用过的路由关闭了 (其 参考计数值变为零 )。关闭时,其过期时间设置为 1小时以后。 5 9分钟过去了,但就在它即将 过期前的 1分钟,调用了r o u t e程序来打印这个路由的度量数据。以下是执行 r o u t e程序时需 要 调 用 的 内 核 函 数: r o u t e _ o u t p u t 调用 r t a l l o c l , r t a l l o c l 又调用 i n _ m a t r o u t e(Interne t专有的函数 r n h _ m a t c h a d d r),i n _ m a t r o u t e函数加大了参考计数 值,即从 0 到 1 。当这些操作全部完成后,假设参考值又从

1 回到 0 , r t f r e e 就调用

in_clsroute,而in_clsroute将过期时间重置为 1小时以后。

6.12 小结 在T/TCP中,我们为 r t _ m e t r i c s结构增加了 16字节。其中的 10个字节被T/TCP用作TAO 缓存: • tao_cc,从对等端收到的最后一个有效 SYN中的CC值; • tao_ccsent,发给对等端的最后一个 CC值; • tao_mssopt,从对等端收到的最后一个 MSS值。 在r a d i x _ n o d e _ h e a d结构中新增加了一个函数指针: r n h _ c l o s e字段,当路由的参考 计数值达到 0时,就要调用该指针所指的函数 (如果有定义 )。 专门为Internet协议增加了四个新的函数: 1) i n _ i n i t h e a d用于初始化 I n t e r n e t的r a d i x _ n o d e _ h e a d结构,设置我们现在讲述的 这四个函数指针。 2) i n _ a d d r o u t e在IP路由表中增加新路由时调用。它为每一个非主机路由和非多播地址 路由的IP路由打开克隆标志。 3) i n _ m a t r o u t e在每次查找到 I P路由时调用。如果路由被 i n _ c l s r o u t e函数设置为超 时,就要把它的过期时间重置为 0,因为这个路由又有用了。 4) i n _ c l s r o u t e在IP路由的最后一个参考也被关闭时调用。它将路由的过期时间设置为 1小时以后。我们也看到了,如果路由表过大时,过期时间要缩短。

下载

第7章 T/TCP实现:协议控制块 7.1 概述 对于 T / T C P 而言,协议控制块 P C B 函数 ( 卷 2 的第 2 2 章 ) 需要作一些小的修改。函数 i n _ p c b c o n n e c t(卷2第2 2 . 2节)现在要分为两部分:一个名为 i n _ p c b l a d d r的内部例程, 用于分配本地接口地址;另一个为 i n _ p c b c o n n e c t 函数,完成原来的功能 (它要调用 in_pcbladdr)。 我们把这两部分功能分开的原因是因为,当同一连接 (即相同的插口对 )的前一次操作还处 在TIME_WAIT状态时, T/TCP就可以发布下一个 c o n n e c t了。如果先前一次连接的持续时间 少于M S L,并且两端都使用了 C C选项,那么处于 T I M E _ WA I T状态的连接就关闭,允许建立 新的连接。如果我们没有做上述修改,并且 T / T C P使用了未修改的 i n _ p c b c o n n e c t,当遇 到现有PCB处于TIME_WAIT状态时,应用程序就会收到“地址已被使用”这样的出错消息。 不仅在发布 T C P的c o n n e c t时要调用 i n _ p c b c o n n e c t,并且在新的 T C P连接请求到达 时、发布 UDP c o n n e c t以及发布 UDP s e n d t o时都要调用该函数。图 7 - 1总结了N e t / 3中修改 之前的调用关系。 TCP输入

图7-1 Net/3中调用in_pcbconnect

的小结

在T C P输入和 U D P中,对 i n _ p c b c o n n e c t的调用是一样的,但是处理 TCP c o n n e c t (P R U _ C O N N E C T请求)时就要调用一个新的函数 t c p _ c o n n e c t(图12-2和图12-3),该函数又调 用新的函数 i n _ p c b l a d d r。另外,当 T / T C P客户采用 s e n d t o或s e n d m s g隐式打开连接时, 所产生的 P R U _ S E N D或P R U _ S E N D _ E O F请求也将调用 t c p _ c o n n e c t。我们在图7 - 2中给出了 这种新的调用方案。 TCP输入

图7-2 in_pcbconnect

和in_pcbladdr 的新安排

71

第 7章 T/TCP实现:协议控制块计计

下载 7.2 in_pcbladdr函数

i n _ p c b l a d d r函数的第一部分如图 7 - 3所示。这一部分仅仅给出了变量定义和头两行代 码,它与卷 2第590页的第138~139行完全相同。

图7-3 in_pcbladdr 函数:第一部分

136-140

头两个变量与 i n _ p c b c o n n e c t中是一样的,第三个变量是一个指针的指针,用

于返回本地地址。 这个函数的其余部分与卷 2中图22-25和图22-26完全相同,与该卷图 22-27的大部分也相同。卷 2的图22-27中最后两行,即第 593页,则用图 7-4中的代码代替。

图7-4 in_pcbladdr 函数:最后一部分

2 3 2 - 2 3 6 如果调用进程给定了通配符作为本地地址,指向 s o c k a d d r _ i n结构的一个指针 就会通过第三个变量返回。 基本上, i n _ p c b l a d d r 所做的全部操作是进行差错检查,目标地址为 0 . 0 . 0 . 0或 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5这些特殊情况的处理,接着进行本地 I P地址的分配 (如果调用进程还没有分配 IP地址)。connect所需要的其他处理操作都在 in_pcbconnect中实现。

7.3 in_pcbconnect函数 图7-5中给出了i n _ p c b c o n n e c t函数。这个函数调用了上一节所介绍的 i n _ p c b l a d d r, 然后接下来就是卷 2中图22-28中的代码。 1. 分配本地地址 255-259

如果调用进程还未将一个 I P地址绑定到其插口,则调用 i n _ p c b l a d d r函数计算

出本地IP地址,然后通过 ifaddr指针返回。 2. 验证插口对的唯一性 260-266

in_pcblookup验证插口对是唯一的。在 TCP客户端调用 connect(当客户端尚

72

计计第一部分 TCP事务协议

未将一个本地端口或本地地址绑定到一个插口时

下载 ) 的一般情况下,本地端口号为

0,

in_pcblookup就总是返回 0,因为端口 0是不会与任何一个现有的 PCB匹配上的。 3. 如果还没有绑定,则绑定本地地址和本地端口 267-271

如果还没有本地地址和本地端口绑定到插口上, i n _ p c b b i n d要对这两者都进行

分配。如果只是还没有本地地址绑定到插口,本地端口号已经为非零,则 i n _ p c b l a d d r返 回的本地地址记录在 P C B中。在本地端口号还是 0时是不可能将一个本地地址绑定上去的,因 为调用in_pcbbind函数绑定本地地址的同时会给插口分配一个临时使用的端口号。 272-273

外部地址和外部端口 (in_pcbconnect的变量)记录在PCB中。

图7-5 in_pcbconnect

函数

7.4 小结 T/TCP所作的修改是从 i n _ p c b c o n n e c t函数中移去计算本地地址的所有代码,创建一个 名为in_pcbladdr的新函数来完成这项任务。 in_pcbconnect调用该函数,然后完成正常 的连接处理过程。这将使处理 T / T C P 客户连接请求 (或者用 c o n n e c t显式建连,或者用 s e n d t o隐式地建连 )时可以调用 i n _ p c b l a d d r来计算本地地址。 T/TCP客户的端处理则是复 制了图7-5中的处理步骤,但即使前一次连接尚处于 TIME_WAIT状态,T/TCP也还是允许处理 同一连接的后续请求。常规的 T C P是不允许这种情况发生的;这时图 7 - 5的i n _ p c b c o n n e c t 将返回EADDRINUSE。

下载

第8章 T/TCP的实现:TCP概要 8.1 概述 本章内容覆盖了 T / T C P对T C P数据结构和函数所做的全局性修改。增加了两个全局变量: t c p _ c c g e n,即全局 C C计数器,以及 t c p _ d o _ r f c l 6 4 4,这是一个标志变量,说明是否选 用C C选项。 T C P的协议交换记录项也作了修改,以支持隐式的打开和关闭。另外还在 T C P控 制块中增加了4个变量。 对t c p _ s l o w t i m o函数也作了简单修改,以便能够测量每个连接的持续时间。给定一个 连接的持续时间,如果持续时间短于 MSL,则如4.4节所述,T/TCP将截断TIME_WAIT状态的 保持时间。

8.2 代码介绍 T/TCP没有增加新的源文件,但是需要一些新的变量。 全局变量 图8-1中给出了T/TCP新增加的全局变量,在各个 TCP函数中都会用到。 变



tcp_ccgen tcp_do_rfc1644

数据类型 tcp_cc int





要发送的下一个 CC值 如果为真(默认),发送CC或CCnew选项

图8-1 T/TCP新增的全局变量

在第3章我们给出了一些有关 t c p _ c c g e n变量的例子。在 6 . 5节中也提到了 t c p _ c c的数 据类型是用 t y p e d e f定义的,是无符号长整数。 t c p _ c c 变量值为 0,表示它尚未定义。 tcp_ccgen变量总是这样存取的:

其中cc_send是TCP控制块的新字段(见后面的图8-3)。宏CC_INC是在 中定义的:

由于这个值是在使用之前增加的,因此, t c p _ c c g e n要初始化为 0,且它的第一个有用 值为1。 为了按照模运算比较 C C的值,定义了四个宏: C C _ L T、C C _ L E Q、C C _ G T和C C _ G E Q。 这四个宏与卷2第649页定义的四个SEQ_xx宏完全一样。 变量t c p _ d o _ r f c l 6 4 4与卷 2中介绍的变量 t c p _ d o _ r f c l 3 2 3相似。如果 t c p _ d o _ rfcl644为0,TCP不会向对方发送 CC或CCnew选项。

74

计计第一部分 TCP事务协议

下载

统计量 T / T C P新增了 5个计数器,如图 8 - 2所示。它们加在 t c p s t a t结构中,卷 2第6 3 8页对这个 结构有介绍。 说

t c p s t a t字段 tcps_taook tcps_taofail tcps_badccecho tcps_impliedack tcps_ccdrop



TAO正确时接收到 SYN 接收到带有 CC选项的SYN,但TAO测试失败 CCecho选项错误的 SYN/ACK报文段 隐含着对前一次连接的 ACK的新SYN 因为无效的 CC选项而丢弃的报文段

图8-2 在tcpstat 结构中新增的 T/TCP统计量

程序netstat必须经修改才能打印这些新字段的值。

8.3 TCP的protosw结构 我们在第 5章提到过,T C P的p r o t o s w记录项i n e t s w [2 ] (卷2第6 4 1页)的p r _ f l a g s字段 在 T / T C P 中作了修改。新的插口层标志

P R _ I M P L O P C L 必须包括在内,已有的标志

P R _ C O N N R E Q U I R E D和P R _ W A N T R C V D也必须包含在内。在 s o s e n d中,如果调用进程给出了 一个目标地址,这个新的标志允许对一个未建连接的插口调用

s e n d t o,并且如果指定了

MSG_EOF标志,它所起的作用是发出一个 PRU_SEND_EOF请求而不是 PRU_SEND请求。 对p r o t o s w记录项所作的修改中有一个不是 T / T C P所需的,即定义了 t c p _ s y s c t l函数 作为p r _ s y s c t l字段。这就允许系统管理员用前缀为 n e t . i n e t . t c p的s y s c t l程序来修改 能够控制TCP操作的一些变量值 (卷2介绍的Net/3代码仅仅支持s y s c t l程序通过i p _ s y s c t l、 i c m p _ s y s c t l和u d p _ s y s c t l函数对IP、ICMP和UDP的一些变量进行控制 )。在图12-6中给 出了tcp_sysctl函数。

8.4 TCP控制块 T C P控制块中新增了四个变量,卷 2第6 4 3 ~ 6 4 4页说明了 T C P控制块的 t c p c b结构。我们 在图8-3中仅给出了新的字段,并非整个结构。 变



t_duration t_maxopd cc_send cc_recv

数据类型 u_long u_short tcp_cc tcp_cc





以500ms为单位的连接持续时间 MSS加上通常选项的长度 发送给对等端的 CC值 从对等端中接收到的 CC值

图8-3 T/TCP在tcpcb 结构中新增的字段

t_duration用于确定T/TCP是否可以截断TIME_WAIT状态的保持时间,见4.4节的讨论。 当控制块创建时它的值为 0,由tcp_slowtimo(8.6节)每过500 ms加1。 t _ m a x o p d是为了代码的方便而设的。它的取值是已有 t _ m a x s e g字段的值加上 TCP选项 通常所占用的字节数。 t _ m a x s e g是每个报文段中的数据字节数。例如,在 MTU 为1500字节 的一个以太网上,如果时间戳和 T/TCP都用上了,t_maxopd将为1460,t_maxseg则为1440。

75

第 8章 T/TCP的实现:TCP概要计计

下载

它们之间的差值 2 0 字节是由 1 2 字节的时间戮选项加上 8 字节的 C C 选项 ( 图 2 - 4 ) 造成的。 t_maxopd和t_maxseg都是在tcp_mssrcvd函数中计算并记录的。 最后两个变量来自 RFC 1644,在第2章给出了有关这三个变量的例子。如果一个连接的两 端主机都用了CC选项,cc_recv的值将为非 0。 在T C P控制块的 t _ f l a g s字段中新定义了 6个标志,如图 8 - 4所示,是对卷 2的图2 4 - 1 4中 的9个标志的补充。 t_flags TF_SENDSYN TF_SENDFIN TF_SENDCCNEW TF_NOPUSH TF_RCVD_CC TF_REQ_CC





发送SYN(隐藏的半同步连接状态标志 ) 发送FIN(隐藏的状态标志 ) 主动打开时发送 CCnew选项而不是 CC选项 不发送报文段,只清空发送缓存 当对端在SYN中发送了CC选项时设置该标志 已经/将在SYN中申请CC选项

图8-4 T/TCP新增的t_flags 及其取值

不要把T/TCP中的两个标志T F _ S E N D F I N 与TF_SENTFIN混淆,前者表示 TCP需要发送 FIN,而后者表示已经发出 FIN。 T F _ S E N D S Y N和T F _ S E N D F I N这两个名字源于 Bob Braden的“T / T C P的实现”。 F r e e B S D实现中将这两个名字改为 T F _ N E E D S Y N和T F _ N E E D F I N。我们选用了前面 的名字,因为已经用新的标志来表示是否需要发送控制标志,如果选用后面的名字 就会误解为需要接收 S Y N或F I N。然而请注意,因为选用了这样的名字, T / T C P的 T F _ S E N D F I N标志和已有的 T F _ S E N T F I N标志(表明T C P已经发出了 F I N )仅有一个字 符之差。 我们将在下一章的图 9-3和图9-7中分别介绍 TF_NOPUSH和TF_SENDCCNEW标志。

8.5 tcp_init函数 所有的 T / T C P变量都不需要显式的初始化,因此卷 2中介绍的 t c p _ i n i t函数没有变化。 全局变量t c p _ c c g e n是没有初始化的外部变量,按照 C语言的规则,它的默认值为 0。这样做 不会出错,因为在 8 . 2节中定义的宏 C C _ I N C是先对该变量加 1,然后再用,因此在重启动后, tcp_ccgen的第一个有用值是 1。 T/TCP也要求在重启动时将 TAO缓存全部清空,由于在重启动时要初始化 IP路由表,所以 TAO缓存不需要专门处理。在路由表中每增加一个新的 rtentry结构,rtrequest要将该结 构初始化为 0(卷2第489页)。这就意味着rmxp_tao结构中3个TAO变量的默认值都为 0(图6-3)。 为新主机创建新的 TAO记录项时, T/TCP需要将tao_cc的值初始化为0。

8.6 tcp_slowtimo函数 两个TCP定时函数中有一个增加了一行:每次处理 500 ms定时器时,要对每个 TCP控制块 的t_duration字段执行加 1操作,卷2第666页给出了tcp_slowtimo函数。下面这一行

76

计计第一部分 TCP事务协议

下载

加在这个图的第 9 4 ~ 9 5行之间。这个变量的用途是测量每个连接的长度,以 5 0 0 m s为单位。如 果连接持续时间短于 MSL,TIME_WAIT状态的保持时间就可以截断,在 4.4节中已经讨论过。 与这项优化有关的工作是在 头文件中定义了下面这个常量: 我们在图 11-17和图11-19中将可以看到,如果 T/TCP连接是主动关闭,并且 t _ d u r a t i o n的值 小于 T C P T V _ M S L( 6 0个500 ms,即3 0秒),那么 T I M E _ WA I T状态的保持时间就是当前重传超 时( RTO )乘以T C P T V _ T W T R U N C。在局域网环境中, RTO通常为3个5 0 0 m s,即1 . 5秒,这将使 TIME_WAIT状态的保持时间缩短到 12秒。

8.7 小结 T/TCP新增了两个全局变量 (tcp_ccgen和tcp_do_rfcl 644)、4个TCP控制块字段和5 个TCP统计结构计数器。 t c p _ s l o w t i m o函数也作了修改,以 5 0 0 m s为时间单位计量每个 T C P连接的持续时间。 这个持续时间决定了 T/TCP能否在主动关闭时截断 TIME_WAIT状态的保持时间。

下载

第9章 T/TCP的实现:TCP输出 9.1 概述 本章介绍为了支持 T/TCP而对t c p _ o u t p u t函数所做的修改。在 TCP中有许多程序段都要 调用该函数来决定是否应该发出一个报文段,并且如果必要就发出一个。在 T / T C P中作了以 下修改: • 两个隐藏的状态标志可以打开 TH_SYN和TH_FIN标志。 • T/TCP可以在SYN_SENT状态下发出多个报文段,但其前提是确知对等端也支持 T/TCP。 • 发送程序糊涂窗口避免机制必须考虑到新的 T F _ N O P U S H标志,这个标志我们在 3 . 6节中 讨论过。 • 可以发出新的T/TCP选项(CC、CCnew和CCecho)。

9.2 tcp_output函数 9.2.1 新的自动变量 在tcp_output中说明了两个新的自动变量:

其中第一个变量是一个指针,指向相应对等端的 TA O缓存记录项。如果 TA O缓存记录项不存 在(这种情况不应该发生 ),则t a o p指向t a o _ n o n c a c h e d,并且将这个结构初始化为 0 (这样 它的tao_cc值就是未定义的 )。 9.2.2 增加隐藏的状态标志 在tcp_output的开头,要从tcp_outflags向量中读取说明当前连接状态的 TCP标志。 图2 - 7给出了每个状态的相关标志。图 9 - 1中的代码用于在相应的隐藏状态标志处于开状态时, 对TH_FIN标志和TH_SYN标志执行逻辑或。

图9-1 tcp_output :增加隐藏状态标志

78

计计第一部分 TCP事务协议

下载

图9-1 (续)

这些代码位于卷 2第681~682页。 9.2.3 在SYN_SENT状态不要重传SYN 图9 - 2中的程序读取对等端的 TA O缓存内容,并且查看是否已经发出了 S Y N。这段代码位 于卷2中图26-3的开头。 1. 读取TAO缓存记录项 117-119

读取对等端的 TA O缓存内容,如果不存在,则改用自动变量 t a o _ n o n c a c h e d,

其初始值置为0。 如果使用了全 0的记录项,它的值永远不变。这样, t a o _ n o n c a c h e d结构就可 以静态分配并初始化为 0,而不必用 bzero将其设置为 0。 2. 检查客户请求是否超过 MSS 121-133

如果状态标志表明需要发送 S Y N,并且如果已经发出 S Y N,那么T H _ S Y N标志就

要关闭。当一个应用程序用 T / T C P向对等端发送多倍 M S S数量的数据时可能发生这种情况 (见 3 . 6节)。如果对等端支持 T / T C P协议,这时可以分多个报文段发送,但只有第一个报文段应该 设置S Y N标志。如果我们不能确定对等端是否支持 T / T C P (t a o _ c c s e n t值为0 ),这时我们必 须在三次握手过程完成以后才可以发送多个报文段。

图9-2 tcp_output :在SYN_SENT状态不重传 SYN

9.2.4 发送器的糊涂窗口避免机制 发送器的糊涂窗口避免机制有两处作了修改 (卷2第715页),如图9-3所示

79

第 9章 T/TCP的实现:TCP输出计计

下载

图9-3 tcp_output :糊涂窗口避免机制中,确定是否发送报文段

1. 发送最大报文段 169-170 如果允许,就发出最大报文段。 2. 允许应用程序关闭隐式推送 171-174

BSD实现中是这样处理的:如果不是正在等待对等端的 ACK(i d l e值为真),或者

如果N a g l e算法禁用 (T F _ N O D E L A Y值为真),并且 T C P正在清空发送缓存,那么它总是发出一 个报文段。有时称这种方式为隐式推送,因为除非受 N a g l e算法所限,否则应用程序每写一次 都会导致一个报文段发送出去。T/TCP提供了一个新的插口选项,可以使BSD的隐式推送失效, 这个选项就是 T C P _ N O P U S H,最后变成了 T F _ N O P U S H标志。我们在 3 . 6节研究过有关这个标 志的一个例子。在这段代码中,我们看到了只有以下三个条件同时为真,报文段才能发出: 1) 并不在等待 ACK(idle值为真),或者Nagle算法已经禁用(TF_NODELAY值为真); 2) TCP_NOPUSH插口选项没有使用 (默认值); 3) TCP 正在清空发送缓存 (即所有未发的数据可以在一个报文段中发出 )。 3. 检查接收窗口是否打开了至少一半 177-178

在常规的 T C P中,整个这部分代码段不会因为收到第一个 S Y N而执行,因为这时

l e n应该是 0。但是在 T / T C P中,很有可能在接收到另一端发来的 S Y N之前就发送数据。这就 意味着需要根据 m a x _ s n d w n d是否大于0来检测接收窗口是否已经打开了一半。这个变量是对 等端通告的最大窗口,但是在从对等端收到通告前,它一直是 0(即一直到收到对等端的 SYN)。 4. 重传定时器到时发送 179-180 重传定时器到时后, snd_nxt小于snd_max。 9.2.5 有RST或SYN标志时强制发送报文段 卷2第6 8 8页的1 7 9 ~ 1 8 0行代码在 S Y N标志或R S T标志打开时总是要发送一个报文段。这两 行要用图9-4中的代码替代。

图9-4

tcp_output :检查RST和SYN标志,确定是否发送报文段

80

计计第一部分 TCP事务协议

207-209

下载

如果R S T标志打开了,就总要发出一个报文段。如果 S Y N标志打开了,则只有在

相应的隐藏状态标志关闭时才会发出报文段。加上这项限制的理由可以看图 2 - 7。在最后 5个 服务器加星状态 (半同步状态 )下,T F _ S E N D S Y N标志是打开的,这就会使图 9 - 1中的S Y N标志 被打开。在 t c p _ o u t p u t中执行这项测试的目的是只在 S Y N _ S E N T、S Y N _ R C V D、S Y N _ SENT*和SYN_RCVD*状态下才发出报文段。 9.2.6 发送MSS选项 这一小段代码 (卷2第697页)有一个小小的变化。 Net/3中的函数tcp_mss(有两个参量 )改为 t cp_msssend(仅仅以t p为参量)。这是因为我们需要把计算 MSS并发送与处理收到的 MSS选 项区分开来。 N e t / 3中的t c p _ m s s函数同时完成这两项处理;在 T / T C P中,我们则用两个不同 的函数来完成,它们是tcp_msssend和tcp_mssrcvd,我们将在下一章讨论这两个函数。 9.2.7 是否发送时间戳选项 卷2第6 9 8页,如果以下三个条件都成立,就发出时间戳选项: ( 1 ) T C P配置中要求使用时 间戳选项; ( 2 )正在构造的报文段不包括 R S T标志;以及 ( 3 )要么这是一次主动打开或者 T C P已 经从另一端接收到了一个时间戳 (T F _ R C V D _ T S T M P)。对主动打开的测试只要查看 SYN标志是 否打开以及 ACK标志是否关闭即可。完成这三项测试的 T/TCP代码如图9-5所示。

图9-5 tcp_output :是否发送时间戳选项?

283-291

因为我们希望从客户端到服务器方向上发送的所有第一个报文段都携带时间戳选

项(在多报文段请求的情况下,如图 3 - 9所示),而不仅仅只是含有 S Y N的第一个报文段,所以 在T / T C P中第三项测试的前一半有所改变。对所有初始报文段的新测试项都是在没有 A C K标 志的情况下进行的。 9.2.8 发送T/TCP的CC选项 是否发送三个新 C C 选项之一的测试是看 T F _ R E Q _ C C 标志是否打开 (如果全局变量 t c p _ d o _ r f c l 6 4 4非零,该标志由 t c p _ n e w t c p c b激活)、T F _ N O O P T标志是否关闭以及 R S T标志是否关闭。发送哪个 C C选项则取决于输出报文段中 S Y N标志和A C K标志的状态。这 样就有四种可能的组合,前两种如图 9-6所示(这段代码在卷2第698页的第268~269行)。 T F _ N O O P T标志是由新增的 T C P _ N O O P T插口选项控制的。该插口选项出现在 Thomas Skibo写的 RFC 1323 的代码中 (见1 2 . 7节)。在卷2中曾指出,这个标志 (不是 指插口选项 )自从4.2BSD以后就已经在伯克利源代码中存在了,但通常无法将其打开。

81

第 9章 T/TCP的实现:TCP输出计计

下载

如果设置了这个选项, TCP就不用随SYN发送任何选项。新增这个选项用来处理 TCP 实现中的不一致性,因为这些实现不能忽略未知的 TCP选项(自从RFC 1323修改以后, 增加了两个新的 TCP选项)。 T / T C P所作的修改并没有改变确定是否发送 M S S选项的那段代码 (卷2第6 9 7页)。 如果TF_NOOPT标志没有设置,这段代码就不发送 MSS选项。但是 Bob Braden在他的 RFC 1323 代码中指出,没有真正的理由需要阻止发送 M S S选项。 M S S选项是 R F C 793规范中的一部分内容。

图9-6 tcp_output :发送一个 CC选项,第一部分

1. SYN关闭,ACK打开 310-313

如果S Y N标志关闭,但 A C K标志打开,这就是常规的 A C K (即连接已经建立 )。只

有从对等端收到一个 CC选项以后,才会发送 CC选项。 2. SYN关闭,ACK关闭 314-320 只有在SYN_SENT*状态下,即在连接建立以前就发送了一个非 SYN报文段时,这 两个标志才会同时关闭。也就是说,在客户一次发送了多倍 M S S数量的数据时才会这样。图 9 - 2中的代码能够确保只有在对等端也支持 T / T C P时才会进入这种状态。这种情况下就要发送 CC选项。 3. 构造CC选项 321-327

在构造C C选项时,要先加上两个空字符。该连接 c c _ s e n d的值就作为C C选项的

82

计计第一部分 TCP事务协议

下载

内容发送出去。 SYN标志和ACK标志状态组合的剩余两种情况如图 9-7所示。

图9-7 tcp_output :发送CC选项之一,第二部分

4. SYN打开,ACK关闭(客户主动打开) 328-340

当客户执行主动打开时, SYN打开且ACK关闭。如果应该发送 CCnew选项而不是

CC选项,图12-3中的代码完成TF_SENDCCNEW标志的设置,同时也设置 cc_send值。 5. SYN打开,ACK打开(服务器响应客户端的 SYN) 341-360

当SYN标志和ACK标志同时处于打开状态时,这就是服务器对对等端的主动打开

作出了响应。如果对等端发送了 C C或C C n e w选项之一 (设置了T F _ R C V D _ C C),这时我们要向 对等端发送 CC选项(cc_send)和对对等端 CC值(cc_recv)的CCecho。 6. 根据TCP选项调整TCP首部长度 363

所有的TCP选项都加长了TCP首部的长度。

83

第 9章 T/TCP的实现:TCP输出计计

下载 9.2.9 根据TCP选项调整数据长度

t _ m a x o p d是t c p c b结构的新字段且是最大数据长度,并且也是常规 T C P报文段的选项。 因为窗口宽度选项和 CCecho选项只在 SYN报文段中出现,因此在 SYN报文段中的选项 (见图22和图2-3)很有可能会比非 SYN报文段的选项(见图2-4)需要更多的字节空间。图 9-8中的代码根 据T C P选项的大小调整发送报文段中的数据量。这段代码用于替代卷 2第6 9 8页的第 2 7 0 ~ 2 7 7

行。 图9-8 tcp_output :根据TCP选项的大小调整发送数据量

364-377

如果数据长度 (l e n)加上选项长度超过了 t _ m a x o p d,发送的数据量就要缩减,

F I N标志关闭 (如果它原来是开状态 ),且 s e n d a l o t打开(在当前报文段发出后强迫再次执行 tcp_output循环)。 这些代码并不是 T / T C P专有的。它应该对任何一个既携带数据又有 T C P选项 (例 如:RFC 1323时间戳选项 )的报文段执行。

9.3 小结 T / T C P在原本 5 0 0行的t c p _ o u t p u t函数上增加了大约 1 0 0行代码。增加的大部分代码都 与发送新增的T/TCP选项CC、CCnew和CCecho等有关。 另外,如果对等端支持 T/TCP,T/TCP的t c p _ o u t p u t函数可以在 SYN_SENT状态下发送 多个报文段。

下载

第10章 T/TCP实现:TCP函数 10.1 概述 本章包括了 T / T C P 作过修改的各个 T C P 函数。也就是说, t c p _ o u t p u t( 前一章 )、 t c p _ i n p u t ,和 t c p _ u s r r e q (后两章 )以外的所有函数。本章定义了两个新的函数, tcp_rtlookup和tcp_gettaocache,用于在TAO缓存中查找记录项。 t c p _ c l o s e函数修改以后,当使用 T / T C P的连接关闭时,可以在路由表中记录往返时间 估计值(平滑的平均值和平均偏差估计 )。常规协议只在连接上传送了至少 16个满数据报文段后 才记录。然而, T / T C P通常只发送少量数据,但与同一对等端之间的这些不同连接的估计值 应该保留下来。 T / T C P中对 M S S 选项的处理也有所改变。有一部分改变是为了在

N e t / 3中清理过载的

t c p _ m s s函数,这样就把它分成了一个计算 M S S以便发送的函数 (t c p _ m s s e n d)和另一个处 理接收到的 M S S选项的函数 (t c p _ m s s r c v d)。T / T C P同时也将从对等端收到的最新 M S S值保 存到TAO缓存记录项中。在接收到服务器的 SYN和最新的 MSS之前,如果要随 SYN发送数据, T/TCP就用这个记录来初始化发送 MSS。 N e t / 3中的t c p _ d o o p t i o n s函数修改以后能够识别三个新的 T / T C P选项:C C、C C n e w和 CCecho。

10.2 tcp_newtcpcb函数 用P R U _ A T T A C H请求创建新的插口时要调用该函数。图 1 0 - 1中的五行代码用来代替卷 2第 667页的第177~178行。

图10-1 tcp_newtcpcb

180

函数:T/TCP所做的修改

在前面图 8 - 3有关的介绍中提到过, t _ m a x o p d是每个报文段中可以发送的 T C P选项加

上数据的最大字节数。它和 t _ m a x s e g的默认值均为 5 1 2 (t c p _ m s s d f l t)。由于这两个值相 等,表明报文段中不能再有 T C P选项。在后面的图 1 0 - 1 3和图1 0 - 1 4中,如果时间戳选项或者 CC选项(或者两者同时)需要在报文段中发送,就要减小 t_maxseg的值。 183-184

如果全局变量tcp_do_rfcl644非零(它的默认值为1),且设置了 TF_REQ_CC标

志,这将使 tcp_output伴随SYN发出CC或CCnew选项(图9-6)。

85

第10章 T/TCP实现:TCP函数计计

下载 10.3 tcp_rtlookup函数

t c p _ m s s (卷2第7 1 7 ~ 7 1 8页)执行的第一项操作是读取为该连接所缓存的路由 (存储在

图10-2 route 结构

图10-3 tcp_rtlookup

函数

插口对

其中rn_key 为128.32.33.5

图10-4 在Internet PCB中缓存的路由全貌

86

计计第一部分 TCP事务协议

下载

Internet PCB的i n p _ r o u t e字段中),如果该路由还没有缓存过,则调用 r t a l l o c查找路由。 现在这项操作安排在另一个独立的函数 t c p _ r t l o o k u p中实现,我们将在图 1 0 - 3中介绍。这 样做是因为连接的路由表记录项中包括有 TAO信息,T/TCP需要更经常地执行这一项操作。 438-452

如果这个连接的路由还没有在缓存中记录, r t a l l o c就计算出路由。但仅仅当

P C B中的外部地址非 0时才能计算路由。在调用 r t a l l o c之前,要先填写 r o u t e结构中的 sockaddr_in结构。 图10-2给出了route结构,其中的一个结构在每个 Internet PCB中都有。 图10-4给出了这个结构的全貌,图中假定外部地址为 128.32.33.5。

10.4 tcp_gettaocache函数 一个给定主机的 TA O 信息保存在该主机的路由表记录项中,确切地说,是在

rt_

m e t r i c s结构的r m x _ f i l l e r字段中(见6.5节)。图10-5所示的函数t c p _ g e t t a o c a c h e返回 指向该主机 TAO缓存的指针。

图10-5 tcp_gettaocache

460-468

函数

t c p _ r t l o o k u p返回的指针指向外部主机的 r t e n t r y结构。如果查找成功,并

且R T F _ U P和R T F _ H O S T标志均打开了,则宏 r m x _ t a o p(见图6-3)返回的指针指向 r m x p _ t a o 结构。

10.5 重传超时间隔的计算 N e t / 3的T C P要测量数据报文段往返时间、跟踪平滑的 RT T估计器 (s rt t)和平滑的平均偏差 估计器(rttvar),并据此计算重传超时间隔 (RTO)。平均偏差是标准差的良好逼近,比较容易计 算,因为与标准差不一样,平均偏差不需要做平方根运算。文献 [Jacobson 1988]给出了RTT测 量的其他细节,并导出以下的计算公式: delta=data-s rt t s rt t←s rt t + g×delta rttvar←rttvar+h(|delta|-rttvar) RTO=srtt+4×rttvar 其中, d e l t a是刚刚得到的往返时间测量值 (d a t a)与当前的平滑的 RT T估计器(s rt t)之差;g是应 用于RT T估计器的增益,等于 1 / 8;h是应用于平均偏差估计器的增益,等于 1 / 4。在RTO计算

下载

87

第10章 T/TCP实现:TCP函数计计

中的两个增益和乘数 4特意取为 2的乘幂,因此可以通过移位操作来代替乘除运算。卷 2的第25 章给出了如何用定点整数来保存这些值的有关细节。 在常规的 T C P连接中,在计算 s rt t和rt t v a r这两个估计器时,通常要对多个 RT T取样,对于 图1 - 9中的给定最小 T C P连接来说,至少要有两个样本。而且,在一定条件下, N e t / 3将对相同 主机之间的多个连接运用这两个估计器。这是 t c p _ c l o s e函数实现的,在一个连接关闭时, 如果有关对等端的路由表记录项不是默认路由,并且至少得到了 1 6个RT T样值。估计的结果 存储在路由表记录项中 rt_metrics结构的rmx_rtt和rmx_rttvar字段中。新连接建立时, t c p _ m s s r c v d(见1 0 . 8节)从路由表记录项中取出这两个值作为 s rt t和rt t v a r这两个估计器的初 始值。 T/TCP中出现的问题是,一个最小连接只有一个 RTT测量值,而且少于 16个样值是很正常 的,因此在两个对等端之间相继建立拆除的 T / T C P连接对上述测量和估计一点贡献也没有。 这就意味着在 T / T C P中,第一个报文段发出去时根本就不知道 RTO的取值应该是多少。卷 2的 25.8节讨论了tcp_newtcpcb执行初始化时是怎样确定第一个 RTO应该是6秒的。 让t c p _ c l o s e在即使只收集到少于 1 6个样值也存储对 T / T C P连接的平滑估计结果并不难 (在1 0 . 6节中我们会看到为此所做的修改 ),但问题是:如何将新估计值与以前的估计值进行归 并?不幸的是,这仍然还是一个正在研究的问题 [Paxson 1995a]。 为了理解各种不同的可能性,请考虑图 1 0 - 6中的情况。从作者的一台主机上通过 I n t e r n e t 向另一台主机上的回显服务器发送 1 0 0个4 0 0字节长的 U D P数据报(在一个工作日的下午,通常 是Internet上最为拥挤的时候 )。93个数据报有回显返回 (还有7个不知在Internet的哪些地方丢失 了),在图 1 0 - 6中给出了前 9 1个数据报。样值是在 3 0分钟的时间内采集到的,前后数据报之间 的时间间隔是在 0~30秒之间均匀分布的随机数。实际的 RTT是在客户主机上运行 Tcpdump得到 的。黑点就是测量得到的 RT T。另外的三条实线 (从上至下依次是 RTO、s rt t和rt t v a r)是运用本

时间 (ms)

样值

图10-6 RTT测量和对应的RTO、s rt t和rttvar

88

计计第一部分 TCP事务协议

下载

节开头的公式从测得的 RT T计算出来的。计算是用浮点算术完成的,而不是 N e t / 3中实际所用 的定点整数方法。图上所示的 RTO就是从相应的数据点计算出来的值。也就是说,第一个数据 点(大约2200 ms)的RTO是从第一个数据点计算得来的,将用作下一个报文段发送时的 RTO。 尽管所测得的 RT T值平均都在 800 ms 以下(作者的客户系统是通过拨号线上的 P P P连接到 I n t e r n e t上的,穿越整个国家才能到达服务器 ),第2 6个样值的 RT T几乎达到 1400 ms,此后有 少量的一些点在 1000 ms左右。 [Jacobson 1994] 指出,“只要是有竞争的连接共享一条路由, 瞬间RTT波动达到2倍最小值是完全正常的 (它们仅仅表示另外一个连接的开始或丢失后重新开 始),因此,RTO小于2×RTT从来就不会是合理的”。 当估计器有新值存储到路由表记录项中时,必须做出判断,对应于已经过去的历史,有 多少信息是新的。这样,计算公式就为: savesrtt=g×s a v e s rt t + ( 1-g)×srtt s a v e rt t v a r = g×saverttvar+(1-g)×rttvar 这是一个低通滤波器,其中 g是取值在 0~1之间的过滤增益常量, s a v e s rt t和s a v e rt t v a r是存储在 路由表记录项中的数值。当 Net/3用这些公式更新路由表记录时 (当一个连接关闭,并假定得到 了16个样值),它采用的增益是 0.5:存储在路由表中的值有一半是路由表中的旧值,另有一半 是当前估计的值。 Bob Braden的 T/TCP代码中取增益为 0.75。

时间 (ms)

样值

图10-7 TCP平滑与T/TCP平滑的比较

图1 0 - 7给出了从图 1 0 - 6中的数据用常规 T C P计算方法算出的结果与用滤波器增益 0 . 7 5平滑 的计算结果之间的比较。图中的三条虚线就是图 10-6中的三个变量(RTO在最上方,s rt t在中间, rt t v a r在底部 )。三条实线则是假定每一个数据点都是一个独立 T / T C P连接(每一个连接有一个 RT T测量值 )所对应的变量,并且采用滤波增益 0 . 7 5进行了平滑。要知道有这样的差别:虚线 对应的是一个 T C P连接在 3 0分钟内的 9 1个RT T样本;而实线对应的则是在同样的 3 0分钟内 9 1

89

第10章 T/TCP实现:TCP函数计计

下载

个独立的 T/TCP连接,每个连接有一次 RTT测量。实线同时还是 91个连接的所有相继两个估计 值归并后记录到两个路由度量值中的。 代表srtt的实线和虚线差别不大,但是代表rttvar的实线和虚线之间就有明显的差别。rttvar的 实线(T/TCP情况)取值通常大于虚线(单个TCP连接),使T/TCP的重传超时间隔可以取更大的值。 还有其他因素也在影响 T / T C P中的RT T测量。从客户端来看,所测得的 RT T通常包括服务 器的处理时间或者服务器的延迟 A C K定时值,因为服务器的应答通常会延迟到这些事件发生 后才给出。在 N e t / 3中,延迟 A C K的定时器值是每 2 0 0 m s到时一次,而 RT T测量的时间单位为 500 ms,因此应答时延不会是一个大的因素。而且 T/TCP报文段的处理常常会在 TCP输入处理 中遭遇慢通道 (例如,报文段常常不被用于首部预测 ),会加大测得的 RT T值(然而快通道与慢 通道的差别相对于 200 ms的延迟ACK定时器值来说很可能是可以忽略的 )。最后,如果存储在 路由表中的值“过时”了(就是说,其最后一次更新是在一个小时以前 ),在当前事务完成以后, 或许应该用当前的测量值直接替换路由表中的值,而不是用新的测量值与已有的测量值归并。 如RFC 1644 中所指出的,需要对 T C P中的动态特性作更多的研究,特别是 T / T C P,以及 RTT估计。

10.6 tcp_close函数 t c p _ c l o s e的唯一改变是要为 T / T C P事务记录 RT T估计值,即使还没有凑足 1 6个样值。 我们在前一节中已经叙述了这样做的原因。图 10-8给出了代码。

图10-8 tcp_close 函数:为T/TCP事务保存 RTT估计值

90

计计第一部分 TCP事务协议

下载

图10-8 (续)

1. 只对T/TCP事务进行更新 304-311

只有在连接中使用了 T/TCP(cc_recv非0)、有一路由表记录项存在及不是默认路

由时才更新路由表记录项中的度量值。而且,只有当两个 RT T估计值没有加锁 (R T V _ R T T和 RTV_RTTVAR位)时才更新。 2. 更新RTT 312-324

t _ s r t t是以500 ms×8为时间单位保存的, r m x _ r t t则以µs为单位保存。这样,

t _ s r t t就要乘1 000 000( R T M _ R T T U N I T)除2 (时间单位 /秒)再乘8。如果 r m x _ r t t已经有值, 新记录值就是旧值的四分之三加上新值的四分之一。这就是取滤波增益为 0 . 7 5,我们在前一 节已讨论过。否则,直接将新值保存到 rmx_rtt中。 3. 更新平均偏差 325-334

对平均偏差估计值应用同样的算法。它也以 ms为单位保存,需要将 t _ r t t v a r中

的单位时间×4。

10.7 tcp_msssend函数 在N e t / 3中,有一个函数 t c p _ m s s(卷2的2 7 . 5节),在处理 M S S选项时t c p _ i n p u t要调用 它,在需要发送 M S S 选项时 t c p _ o u t p u t 也要调用它。在 T / T C P 中,这个函数改名为 t c p _ m s s r c v d,在执行隐式连接建立时,收到 S Y N后,t c p _ i n p u t要调用它 (在后面的图 1 0 - 1 8中,确定是否需要在 S Y N中包含M S S选项),以及P R U _ S E N D和P R U _ S E N D _ E O F请求要 调用它 (见图1 2 - 4 )。有一个新的函数 t c p _ m s s s e n d,如图1 0 - 9所示,只有当发出了 M S S选项 时,才会被 tcp_output调用。

图10-9 tcp_msssend 函数:返回 MSS值,并在MSS选项中发出

91

第10章 T/TCP实现:TCP函数计计

下载

图10-9 (续)

1. 读取路由表记录项 为每一个对等主机搜索路由表,如果没有找到记录项,则返回默认值

1917-1919

512(tcp_mssdflt)。除非对等主机不可达,否则总是可以查找到一个路由表记录项的。 2. 返回MSS 1920-1926

如果路由表有一个关联的 MTU(r t _ m e t r i c s结构中的r m x _ m t u字段,系统管

理员可以用r o u t e程序设置 ),就返回该值。否则,返回值就取输出接口的 MTU减去40(例如, 以太网上是 1460)。因为路由已经由 tcp_rtlookup确定,输出接口也是已知的。 在路由表中存储 M T U度量的另一个来源是利用路由 M T U发现过程 (卷1的2 4 . 2 节),尽管Net/3中还不支持这种方法。 这个函数不同于通常的BSD做法。如果对等端是非本地主机 (由in_localaddr函数决定), 而且rmx_mtu度量值为0,则Net/3代码(卷2第719页)中总是将MSS取为512(tcp_mssdflt)。 M S S选项的目的是告诉另一端,该选项发送者准备接收多大报文段。 RFC 793 中指出, M S S选项“用于交流发送这个报文段的 T C P的最大可接收报文段”。在一些实现中,这可能受 主机能够重装的最大 I P数据报限制。然而在当前的大多数系统中,合理的限制决定于输出接 口的MTU,因为如果需要分段并且发生报文段丢失,则 TCP的性能会下降。 下面的注释摘抄于 Bob Braden的T / T C P源码修改:“非常不幸,使用 T C P选项要求对 B S D 作可观的修改,因为它对 M S S的处理是错误的。 B S D总是要发出 M S S选项,并且对非本地网 络的主机,这个选项的值是 536。这是对 MSS选项用途的误解,这个选项是要告诉发送者,接 收者准备处理什么。这时发送主机要决定用多大的 M S S,既要考虑它接收的 M S S选项,还要 考虑到路由情况。当我们有了 M T U发现以后,这个路由很可能有一个大于 5 3 6的M T U;这样, B S D就会降低吞吐率。因此,这个程序只确定了应该发送什么样的

M S S选项:本地接口的

MTU减去40。”(这段注释中讲到的值 536应为512)。 我们在下一节 (图1 0 - 1 2 )中会看到,如果对等端是非本地主机, M S S选项的接收者才把 MSS减到512。

10.8 tcp_mssrcvd函数 在执行隐式连接建立时,收到

S Y N 以后的 t c p _ i n p u t 要调用 t c p _ m s s r c v d ,

P R U _ S E N D和P R U _ S E N D _ E O F也都要调用它。该函数与卷 2中的t c p _ m s s函数相似,但是它 们之间还是有足够的差别,能够完成我们所需的全部功能。这个函数的主要目标是设置两个 变量,一个是 t _ m a x s e g(我们在每个报文段中发送的最大数据量 ),另一个是 t _ m a x o p d(在 每个报文段中发送的数据加选项的最大长度 )。图10-10给出了这个函数的第一部分。

92

计计第一部分 TCP事务协议

下载

图10-10 tcp_mssrcvd 函数:第一部分

1. 取对等端的路由及其 TAO缓存 1771-1777 tcp_rtlookup查找到达对等端的路由。如果由于某种原因,查找路由失败了, t_maxseg和t_maxopd就同时设置为512(tcp_mssdflt)。 1778-1799

t a o p指向该对等端的 TA O缓存,位于路由表的记录项中。如果因为用户进程

调用了 s e n d t o(一次隐式连接建立,是 P R U _ S E N D和P R U _ S E N D _ E O F请求的一部分 )而调用 t c p _ m s s r c v d,则o f f e r设置为TA O缓存中保存的值。如果 TA O中的该值为0,o f f e r就设 置为512。TAO缓存中的值被更新。

下载

93

第10章 T/TCP实现:TCP函数计计

图10-11给出了该函数的第二部分,与卷 2第718页完全相同。

图10-11 tcp_mssrcvd 函数:用路由表度量值初始化 RTT变量

1 8 0 0 - 1 8 2 3 如果还没有该连接的 RT T测量值 (t _ s r t t为0 ),并且 r m x _ r t t度量值为非 0, 这时变量t_srtt、t_rttvar和t_rxtcur就用路由表记录项中保存的度量值初始化。 如果路由表度量值加锁标志中的 R T V _ R T T位已经设置,则它表明还要用 r m x _ r t t来初始 化这次连接的最小 RT T (t _ r t t m i n)。默认情况下, t _ r t t m i n初始化为两个时钟步进,这为 系统管理员替换该默认值提供了一个方法。 图10-12给出了tcp_mssrcvd的第三部分,用于设置自动变量 mss的值。

图10-12 tcp_mssrcvd 函数:计算 mss 变量的值

94

计计第一部分 TCP事务协议

1824-1834

下载

如果该路由关联于一个 MTU(rmx_mtu度量值),那就用这个值。否则, mss就

取输出接口的 MTU减去40。另外,如果对等端是在另一个网络,或者也可能在另一个子网 (由 i n _ l o c a l a d d r函数决定)中,这时m s s的最大值取为 512(t c p _ m s s d f l t)。如果路由表记录 项中已经保存有 MTU,那就不再进行本地-非本地测试。 2. 设置t_maxopd 1835-1842

t_maxopd设置为mss,包括了数据和选项的最大报文段长度。

图10-13给出的是第四部分代码,将 mss减去在每一个报文段中都有的选项长度。

图10-13 tcp_mssrcvd 函数:根据选项减小 mss

3. 如果使用时间戳选项就减小 mss 1843-1856

如果下面中的任何一个条件为真,则

m s s就减去时间戳选项的长度

(TCPOLEN_TSTAMP_APPA,即12字节): 1) 本地端将使用时间戳选项 (T F _ R E Q _ T S T A M P),并且还没有收到另一端发来的 m s s选项 (origoffer等于- 1);或 2) 已经收到另一个端发来的时间戳选项。 在代码的注释中指出,由于 t c p _ m s s r c v d是在t c p _ d o o p t i o n s结束时所有的选项处理 完以后调用的(见图10-18),因此第二项测试是成功的。 4. 如果使用CC选项,就减少mss 1857-1860 通过相似的逻辑, mss的值减去8字节(TCPOLEN_CC_APPA)。 这两个长度名称中出现术语 A P P A是因为,RFC 1 323的附录A中建议在时间戳选 项前面置两个空字符 N O P,以便两个 4字节时间戳值的长度都能取 4字节的整数倍。

95

第10章 T/TCP实现:TCP函数计计

下载

同时RFC 1644也有一个附录 A,它对选项排列没有说什么。无论怎样,在三个 C C选 项的任一个前面都置两个 NOP是有一定道理的,如图 9-6所示。 5. 舍入MSS为MCLBYTES的倍数 1861-1867 mss要舍入取整为MCLBYTES的整数倍,即每个 mbuf簇的字节数 (通常为1024或 2 048)。 这段代码有一个糟糕的优化企图,即如果 M C L B Y T E S是2的整数幂,则可以用逻 辑操作来代替乘法或除法运算。自从 N e t / 1开始,它就已经是一条弯路,应该清除 掉。 图10-14给出了tcp_mssrcvd代码的最后一部分,用于设置发送缓存和接收缓存的大小。

图10-14 tcp_mssrcvd 函数:设置发送和接收缓存的大小

96

计计第一部分 TCP事务协议

下载

6. 改变插口发送缓存的大小 1868-1883

系统管理员可以用 r o u t e程序设置 r m x _ s e n d p i p e和r m x _ r e c v p i p e这两个

度量值。 b u f s i z e的值就设置为 r m x _ s e n d p i p e的值(如果已有定义 ),或者当前插口发送缓 存的高位值。如果 b u f s i z e的值小于 m s s,m s s值就减小为取 b u f s i z e的值(这是一种强迫 M S S取比给定目标的默认值还小的取值方法 )。否则, b u f s i z e的值放大,取 m s s的整数倍 (插口缓存的大小总是取报文段长度的整数倍 )。上限为 s b _ m a x,在N e t / 3就是262 144。插口 缓存的高位值由 sbreserve设置。 7. 设置t_maxseg 1884 t_maxseg设置为TCP将发给对等端的最大数据量 (不包括常规选项 )。 8. 改变插口接收缓存的大小 1885-1892 插口接收缓存的高位值可以用类似的逻辑来设置。例如,对于以太网上的本地连 接来说,假定时间戳选项和 C C选项同时都在用,则 t _ m a x o p d将是1 4 6 0,t _ m a x s e g为1 4 40 (见图 2 - 4 )。插口发送缓存和接收缓存都将从它们的默认值 8 1 9 2 ( 卷2的图 1 6 - 4 ) 舍入到 8 6 4 0 ( 1 4 4 0 ×6)。 9. 非本地对等端才有的慢启动 1893-1897

如果对等端不是在本地的网络中 (i n _ l o c a l a d d r为假 ),则把拥塞窗口

(snd_cwnd)设置为1个报文段就开始了慢启动过程。 仅仅当对等端在非本地网中才强迫使用慢启动是 T / T C P修改后的结果。这就使 T/TCP的客户端或服务器端可以向本地对等端发送多个报文段,又不需要慢启动所要 求的额外RTT等待时间(见3.6节)。在Net/3中,总是执行慢启动过程 (卷2第721页)。 10. 设置慢启动门限 1898-1906 如果慢启动门限度量值 (rmx_ssthresh)非0,snd_ssthresh就设置取该值。 我们在图3-1和图3-3中可以看到MSS和TAO缓存与接收缓存大小之间的交叉影响。在图 3-1 中,客户端执行了一次隐式连接建立, PRU_SEND_EOF请求调用tcp_mssrcvd,其中offer 为-1,该函数查找到对应服务器的 t a o _ m s s o p t值为0(因为客户端刚刚重启 )。取MSS为默认 值512,因为只使用了 CC选项(在第2章的例子中,时间戳无效 ),减去8字节后变为 504。注意, 8 1 9 2舍入为 5 0 4 的整数倍后为 8 5 6 8,这是客户端 S Y N所通告的窗口。然而,当服务器调用 t c p _ m s s r c v d时,它已经接收到客户端的 SYN,其中给定MSS为1460。这个值减去8字节(选 项长度)后为1452,8192舍入到1452的整数倍后为 8172。这是服务器的 SYN中通告的窗口。当 客户端处理完服务器的 SYN后(图中第三段),客户端再次调用 t c p _ m s s r c v d,这一次 o f f e r 为1460。这就将客户端的t_maxopd增大至1460,客户端的 t_maxseg则增大至1452,客户端 的接收缓存因舍入而增至 8172。这就是客户端在对服务器的 SYN作出ACK时通告的窗口。 在图3 - 3中,当客户端执行了隐式连接建立时, t a o _ m s s o p t值为1 4 6 0—最近一次从对 等端收到的值。客户端通告的窗口为 8712,1452的整数倍且大于 8192。

10.9 tcp_dooptions函数 在N e t / 1和N e t / 2版中, t c p _ d o o p t i o n s只能识别 N O P、E O L和M S S选项,并且函数有 3 个参数。在Net/3中增加了对窗口宽度和时间戳选项的支持后,参数的数量也增加到 7个(卷2第

97

第10章 T/TCP实现:TCP函数计计

下载

7 4 5 ~ 7 4 6页),其中有 3个就是为了时间戳选项而加的。现在又需要支持 C C、C C n e w和C C e c h o 选项,参数的数量不是增加反而减少到了 5个,因为采用了另一种技术来返回选项是否存在以 及它们各自的取值信息。 图 1 0 - 1 5 给出了 t c p o p t 结构。其中的一个结构是在 t c p _ i n p u t (唯一可以调用 t c p _ d o o p t i o n s的函数)中分配的,并且将指向该结构的指针传给 t c p _ d o o p t i o n s,该函 数填写结构的内容。在处理接收到的报文段时, tcp_input要用到存储在该结构中的值。

图10-15 tcpopt 结构,由 tcp_dooptions

填写数据

图10-16给出了to_flag字段可以组合出的 4个值。 to_flag TOF_CC TOF_CCNEW TOF_CCECHO TOF_TS





CC选项存在 CCnew选项存在 CCecho选项存在 时间戳选项存在

图10-16 to_flag 的取值

图1 0 - 1 7给出了这个函数的参数说明。前 4个参数与 N e t / 3中的相同,但第 5个参数替换了 Net/3版本中的最后3个参数。

图10-17 tcp_dooptions

函数:参数

因为处理 E O L、N O P、M S S、窗口宽度和时间戳选项的代码与卷 2第7 4 5 ~ 7 4 7页的代码几 乎相同,所以这里不再重复介绍 (差别主要在于对新参数的处理,我们刚刚讨论过 )。图1 0 - 1 8 给出了这个函数的最后一部分代码,它们用于 T/TCP处理3个新的选项。 1. 检查长度和是否处理选项 1580-1584

选项长度要验证 (所有3个CC选项的长度必须都是 6)。处理接收到的 CC选项时,

我们也必须发送相应选项 ( 如果内核的 t c p _ d o _ r f c l 6 4 4 标志已经设置,则 t c p _ n e w t c p c b要设置 T F _ R E Q _ C C标志),并且 T F _ N O O P T标志不能设置 (最后这个标志不允许 TCP 在其SYN中发送任何选项 )。

98

计计第一部分 TCP事务协议

下载

2. 设置相应标志并复制 4字节值 1585-1588

设置相应的 to_flag值。四个字节的选项值存储在 tcpopt结构的to_cc字段

中,并且要先转换成主机的字节顺序。 如果这是一个 S Y N报文段,要为该连接设置 T F _ R C V D _ C C标志,因为收到了

1589-1595 CC选项。

3. CCnew和CCecho选项 1596-1623

CCnew和CCecho选项的处理步骤与 CC选项的相似。但因为 CCnew和CCecho选

项仅在SYN报文段中有效,所以要附加一项检测,检查报文段中是否包含 SYN标志。 尽管T O F _ C C N E W标志都有正确设置,但从来不去检查它。这是因为在图 11-6中, 如果C C选项不存在,则缓存的 C C值是无效的 (即需设置为 0 )。如果存在 C C n e w选项, 则c c _ r e c v仍然有正确设置 (注意,在图1 0 - 1 8中,C C和C C n e w选项都在 t o _ c c中存 储其值),并且当三次握手完成时 (图11 - 1 4 ),所缓存的值 t a o _ c c是从c c _ r e c v中复 制过来的。 4. 处理收到的 MSS 1625-1626

局部变量 m s s记录的或者是 M S S选项的值 (如果选项存在 ),或者是表示选项不

存在的 0值。在这两种情况下, t c p _ m s s r c v d都要设置变量 t _ m a x s e g和t _ m a x o p d的值。 在t c p _ d o o p t i o n s快结束时要调用该函数,因为如图 1 0 - 1 3所示, t c p _ m s s r c v d使用了 TF_RCVD_TSTMP和TF_RCVD_CC标志。

10.10 tcp_reass函数 当服务器收到包含数据的 S Y N时,假定 TA O测试失败或报文段中不包含 C C选项,那么 t c p _ i n p u t就将数据存入缓存队列,等待三次握手过程的完成。在图 11 - 6中,协议的状态设 置为S Y N _ R C V D,程序有一个分支 t r i m t h e n s t e p 6,在标号为 d o d a t a的程序行 (卷2第7 9 0 页),宏T C P _ R E A S S发现协议状态不是 E S TA B L I S H E D,因此调用 t c p _ r e a s s将报文段存入 该连接的失序报文队列 (其中的数据并非真的失序;只是因为它是在三次握手过程完成之前到 达的。然而,卷 2的图2 7 - 1 9底部的两个统计计数器 t c p s _ r c v o o p a c k和t c p s _ r c v o o b y t e 的累进是不正确的 )。

图10-18 tcp_dooptions

函数:新T/TCP选项的处理

下载

99

第10章 T/TCP实现:TCP函数计计

图10-18 (续)

当对服务器所发的 S Y N的A C K (通常是三次握手中的第三个报文段 )跚跚来迟时,执行卷 2 第774页的c a s e T C P S _ S Y N _ R E C E I V E D语句,使连接进入到 ESTABLISHED状态,然后调用 r c p _ r e a s s函数将队列中的数据交付给进程,该函数第二个参数为 0。但在图 11 - 1 4中,我们 会看到,如果新的报文段中有数据,或者如果设置了 F I N标志,就跳过对 t c p _ r e a s s函数的 调用,因为这两种情况的任何一种都会引起对标号为 d o d a t a的T C P _ R E A S S函数的调用。问 题是,如果新的报文段完全与以前的报文段重复,则对 T C P _ R E A S S函数的调用不会强行将队 列中的数据交付给进程。 修改tcp_reass函数只需做很小的改变:将卷 2第729页的第106行的r e t u r n改为执行标 号为present的分支。

10.11 小结 给定主机的 TA O信息保存在路由表的记录项中。函数 t c p _ g e t t a o c a c h e读取为某主机 缓存的TA O数据,但如果在 P C B的路由缓存中尚不存在相应的路由,则调用 t c p _ r t l o o k u p 来查找主机。

100

计计第一部分 TCP事务协议

下载

T / T C P修改t c p _ c l o s e函数,在路由表中为 T / T C P连接保存两个估计值 s rt t和rt t v a r,即 使连接中只传送了不到 1 6个满长度的报文段。这样就使与该主机的下一次 T / T C P连接可以在 开始时使用这两个估计值 (假设路由表记录项在下一次连接时还没有超时 )。 N e t / 3的函数 t c p _ m s s在T / T C P中分成了两个函数: t c p _ m s s r c v d和t c p _ m s s s e n d。 前者在收到 M S S选项后调用,后者在发出 M S S选项时调用。后者与通常的 B S D做法的不同之 处在于,它一般声明其 M S S为输出接口的 M T U减去T C P和I P首部的长度。 B S D系统会向非本 地对等主机声明取值为 512的MSS。 N e t / 3中的t c p _ d o o p t i o n s函数在T / T C P中也有改变。函数的若干个参数取消了,用一 个结构来代替。这就使函数可以处理新的选项 (例如T / T C P新增加的 3个选项 ),而不需增加参 数。

下载

第11章 T/TCP实现:TCP输入 11.1 概述 T / T C P对T C P所做的大多数修改是在 t c p _ i n p u t函数中。在整个函数的前前后后都出现 的修改是 t c p _ d o o p t i o n s( 1 0 . 9节)中新增的变量和返回值。我们不打算给出受这一变化而影 响的每一块代码。 图11-1是卷2中图28-1的重写,T/TCP所做的修改用黑体字表示。 我们在说明 t c p _ i n p u t函数所做的修改时,按照各部分在整个函数中出现的顺序来分别 介绍。

图11-1 TCP输入处理步骤小结: T/TCP所做的修改用黑体表示

102

计计第一部分 TCP事务协议

下载

图11-1 (续)

103

第11章 T/TCP实现:TCP输入计计

下载 11.2 预处理

定义了三个新的自动变量,其中之一是 t c p o p t结构,在t c p _ d o o p t i o n s中使用。下面 的几行语句用于替换卷 2第739页的第190行。

将t c p o p t结构初始化为 0是非常重要的:这样就会将 t o _ c c字段(接收到的 C C值)设置为 0,表明它未定义。 在N e t / 3中,唯一回到标号 f i n d p c b的分支是在一个连接处于 TIME_WAIT状态时又收到 一个新的 S Y N报文段 (卷2第7 6 5 ~ 7 6 6页)。因为下面的这两行代码有问题,因而该分支存在一 个缺陷

这两行代码在 f i n d p c b后出现了两次,在 g o t o后又执行了一次 (这两行代码在卷 2第7 5 1页出 现了一次,在第 7 5 2页又出现一次;这两处中只能有一处执行,决定于该报文段是否与首部所 指示的相一致 )。这在T / T C P之前并不会带来问题,因为 S Y N不携带数据,上述这个缺陷只在 当一个连接处于 T I M E _ WA I T状态又收到一个携带数据的新 S Y N 时才会表现出来。然而在 T/TCP中,还会有第2个回到f i n d p c b的分支(在后面的图11-11中会说明,这个分支处理图 4-7 所示的隐式 A C K ),并且要处理的 S Y N很可能携带数据。这样,在 f i n d p c b之前的上述这两 行代码就必须删去,如图 11-2所示。

图11-2 tcp_input : 在findpcb 前修改mbuf指针和长度

这样就从卷 2的第751页和第752页中删除上述的两行。 下一个修改位于卷 2第7 4 4页的第3 2 7行,这段代码在一个新报文段到达监听插口时创建一 个新插口。在 t _ s t a t e设置为T C P S _ L I S T E N以后, T F _ N O P U S H和T F _ N O O P T这两个标志必 须从监听插口复制到新的插口:

其中tp0是指向监听插口 tcpcb的自动变量。 卷2第745页的第344~345行中对tcp_dooptions的调用要改为新的调用序列 (10.9节):

104

计计第一部分 TCP事务协议

下载

11.3 首部预测 是否应用首部预测 (卷2第748页)的第一项测试是检查隐藏状态标志是否关闭。如果这些标 志中有任何一个处于打开状态,则需要用 t c p _ i n p u t中的慢通道处理将其关闭。图 11 - 1 3给 出了新的测试过程。

图11-3 tcp_inpu t:是否可以应用首部预测

1. 验证隐藏状态标志关闭 400 这里的第一项修改就是验证 TF_SENDSYN和TF_SENDFIN标志是否同时处于关闭状态。 2. 检查时间戳选项 (如果存在) 401-402

第2项修改与修改后的 t c p _ d o o p t i o n s函数有关的是:不再测试 t s _ p r e s e n t,

而是测试 t o _ f l a g中的T O F _ T S标志位,并且如果时间戳存在,它的值是在 t o _ t s v a l而不 是ts_val中。 3. 如果使用T/TCP,就验证CC 403-409

最后,如果没有完成 T/TCP协商(我们要求有 CC选项,但另一个端没有发送,或者

我们根本就没有要求 ),则i f测试继续进行。如果使用了 T / T C P,则接收到的报文段必须包含 一个CC选项,且CC值必须等于 cc_recv值,这样才继续进行 if测试。 我们希望在简短的 T / T C P事务中不要频繁使用首部预测。这是因为在一次最小的 T / T C P报 文段交换中,其最初的两个报文段携带有控制标志 ( S Y N和F I N ),这会使图 11 - 3中在第二项测

105

第11章 T/TCP实现:TCP输入计计

下载

试失败。这些 T / T C P报文段用 t c p _ i n p u t的慢通道进行处理。但是,在支持 T / T C P的两个主 机之间的长连接 (例如成批的数据传送 )可以使用CC选项,并从首部预测中获益。 4. 用接收到的时间戳更新 ts_recent 413-423

t s _ r e c e n t是否因该更新的测试与卷 2第748页的第371~372行有所不同。图 11-3

中采用新测试代码的原因在卷 2第694~695页有详细叙述。

11.4 被动打开的启动 我们现在替换掉卷 2第7 5 5页的全部代码:处于 L I S T E N状态的插口处理所收到的 S Y N的 代码的最后一部分。这是当服务器从一个客户端接收到一个 S Y N时被动打开的启动 (我们不 想重复卷 2第7 5 3 ~ 7 5 4页中在该状态下进行初始化的代码 )。图11 - 4给出了这段代码的第一部 分。

图11-4 tcp_input :取TAO记录项,初始化事务的控制块

1. 取客户端的 TAO记录项 551-554

t c p _ g e t t a o c a c h e查找该客户端的 TAO记录项。如果没有找到,在全部设置为

0后使用自动变量。 2. 处理选项和初始化序号 555-564

t c p _ d o o p t i o n s处理所有的选项 (由于连接处于 L I S T E N状态,这个函数在此之

前是不会调用的 )。初始化发送序号 (i s s)和初始接收序号 (i r s)。控制块中的所有序号变量都 由tcp_sendseqinit和tcp_rcvseqinit进行初始化。

106

计计第一部分 TCP事务协议

下载

3. 更新发送窗 565-570

t i w i n是在接收到的 S Y N中由客户端通告的窗口 (卷2第7 4 2 ~ 7 4 3页)。它是新插口

的初始化发送窗口。通常,发送窗口要一直等到收到了一个带有 A C K的报文段才会更新 (卷2 第7 8 5页)。但 T / T C P要利用所收到的 S Y N报文段中的发送窗口值,即使这个报文段不包含 A C K。这个窗口影响到服务器端给出应答时可以立即发送给客户端的数据有多少 ( T / T C P交换 中最小三报文段的第 2个报文段)。 4. 设置cc_send和cc_recv 571-572

c c _ s e n d设置为 t c p _ c c g e n的值,并且如果 C C选项存在,则 c c _ r e c v设置为

C C值。如果 C C选项不存在,因为在函数的一开始已经将 t o初始化为 0,所以 c c _ r e c v也是 0(未定义)。 5. 执行TAO测试 573-587

仅仅在报文段中包含有 CC选项时才进行 TAO测试。如果接收到的 CC值非0且大于

该客户端的缓存值 (tao_cc),则TAO测试成功。 6. TAO测试成功;更新客户端的 TAO缓存 588-594 对这个客户端的缓存值进行更新,并且将连接状态设置为 ESTABLISHED*(隐藏状 态变量在稍后的几行中设置,使之成为半同步加星状态 )。 7. 决定是否延迟发送 ACK 595-606 如果报文段中包含 FIN,或者如果报文段中包含数据,那么客户端应用程序必须按 使用T/TCP来编程(即调用s e n d t o,并指定 M S G _ E O F,在此之前不能调用 c o n n e c t、w r i t e 和s h u t d o w n)。在这种情况下, A C K要延迟发送,以便让服务器的应答来捎带服务器给出的 SYN/ACK。

图11-5 tcp_input :对收到的报文段执行 TAO测试

107

第11章 T/TCP实现:TCP输入计计

下载

图11-5 (续)

如果FIN标志未设置,但是报文段中包含数据,那么由于报文段中同时也包含了 SYN标志, 这很有可能是客户端发来的多报文段数据中的第一段。在这种情况下,如果客户端不在本地 子网中(i n _ l o c a l a d d r函数返回的是 0),这时因为客户端可能处于慢启动状态,确认不再延 迟。 8. 设置rcv_adv 607

r c v _ a d v定义为所接收通告的最高序列号加 1 (卷2的图2 4 - 2 8 ),但是在图 11 - 4中的宏

t c p _ r c v s e q i n i t将其初始化为接收序列号加 1。在这个处理点上, r c v _ w n d将是插口接 收缓存的大小 (卷2第7 5 2页)。这样,将 r c v _ w n d加到 r c v _ a d v以后,后者刚好超出当前的 接收窗口。 r c v _ a d v必须在这里进行初始化,因为它的值要用在 t c p _ o u t p u t的糊涂窗口 避免机制中 (卷2第7 0 0页)。r c v _ a d v在t c p _ o u t p u t快结束时设置,通常是在发送第一个 报文段时 ( 在这里应该是服务器对客户端

S Y N 的响应 S Y N / A C K ) 。但是在 T / T C P 中,

r c v _ a d v需要在 t c p _ o u t p u t 中进行第一次设置,因为我们可能在所发送的第一个报文段 中发送数据。 9. 完成连接 608-609

递增t c p s _ c o n n e c t s和调用s o i s c o n n e c t e d通常是在接收到三次握手中的第

三个报文段时进行的 (卷2第774页)。既然连接已经完成,在 T/TCP中这时就执行这两个步骤。 610-613

连接建立定时器设置为 7 5秒, d r o p s o c k e d 标志设置为 0,增加了标签为

trimthenstep6的分支。 图11-6 给出了在LISTEN状态的插口收到一个 SYN时的其余处理代码。

图11-6 tcp_input :LISTEN处理:没有 CC选项或TAO测试失败

108

计计第一部分 TCP事务协议

下载

图11-6 (续)

10. 无CC选项;缓存设置为 CC未定义 612-622

当C C选项不存在时,执行 e l s e语句(图11 - 5的第一个 i f语句在)。缓存中的 C C值

设置为 0 (即未定义 )。如果该段程序中发现报文段中含有 C C n e w选项,则当三次握手过程完成 以后要更新缓存的 CC值(图11-14)。 11. 要求执行三次握手 623-633

执行到这一点,要么报文段中没有 C C选项,要么有 C C选项但TA O测试失败。在

任何一种情况下,都要求执行三次握手过程。剩余的代码行与卷 2中图2 8 - 1 7的最后部分完全 一样:设置 TF_ACKNOW标志,状态设置为 SYN_RCVD状态,这样就会立即发送 SYN/ACK。

11.5 主动打开的启动 下一个 c a s e是S Y N _ S E N T状态。 T C P先前发送过一个 S Y N (一次主动打开 ),现在是处理 服务器的应答。图 11-7给出了处理程序的第一部分。相应的 Net/3代码从卷2第757页开始。

图11-7 tcp_input :在SYN_SENT状态的初始处理

109

第11章 T/TCP实现:TCP输入计计

下载

图11-7 (续)

1. 取TAO缓存记录项 647-650 取该服务器的TAO缓存记录项。因为我们最近刚刚发送过SYN,应该有一个记录项。 2. 处理不正确的ACK 651-666

如果报文段中包含有 ACK,但是其确认字段不正确 (见卷2中图28-19对进行比较的

几个字段的叙述 ),我们的应答就依赖于是否已经为该主机缓存了

t a o _ c c s e n t 。如果

c c _ c c s e n t非0,则丢弃该报文段,而不发送 R S T。这个处理步骤的代码段在图 4 - 7中的标号 为“discard”处。但如果 t a o _ c c s e n t为0,我们丢弃该报文段,并发送 R S T,这是在该状态 下对不正确 ACK的正常TCP响应。 3. 检查RST 667-671

如果接收到的报文段中设置了 R S T标志,则丢弃报文段。另外,如果设置了 A C K

标志,则说明服务器的 TCP主动拒绝连接,并将 ECONNREFUSED错误返回给调用进程。 4. 必须设置SYN 672-673 如果SYN标志没有设置,则丢弃报文段。 674-677

初始发送窗口设置为报文段中通告的窗口宽度,并将 c c _ r e c v设置为接收到的

C C值(如果C C选项不存在,就为 0 )。i r s是初始接收序号,宏 t c p _ r c v s e q i n i t对控制块中 的接收变量进行初始化。 代码中现在开始出现分支,决定于是否报文段中包含一个对所发 SYN的确认ACK(通常情况 下),或者是否ACK标志没有打开(双方同时进行打开的情况较少发生)。图11-18给出了通常的情 况。

图11-8 tcp_input :在SYN_SENT状态处理 SYN/ACK响应

110

计计第一部分 TCP事务协议

下载

图11-8 (续)

5. ACK标志是打开的 678

如果ACK标志处于开的状态,我们从图 11-7中的ti_ack测试可以知道, ACK确认了我

们的SYN。 6. 检查CCecho值是否存在 679-693 如果报文段中包含了 CCecho选项,但CCecho的值与我们发出的不相等,则丢弃该 报文段(除非另一端发生故障,否则,因为已经收到了对所发 S Y N的A C K,所以这是“决不应 该发生的” )。如果我们并没有发送过 CC选项(tao_ccsent为0),那么就要发送 RST。

111

第11章 T/TCP实现:TCP输入计计

下载 7. 标记插口为已连接和处理窗口宽度选项

694-701 插口标记为已经建立连接,并对窗口宽度选项进行处理 (如果存在)。 Bob Braden的 T/TCP实现有错误,不应在测试 CCecho值之前就执行这两行代码。 8. 如果未定义,则更新 TAO缓存 702-704

报文段可以接受,这样,如果这个服务器的 TAO缓存还没有定义 (例如客户重新启

动了,或发送了一个 C C n e w选项),我们就用接收到的 C C e c h o值(如果C C e c h o选项不存在,它 的值为0)对其进行更新。 9. 设置rcv_adv 705-706

更新r c v _ a d v,如图11 - 4所示。在发出的 S Y N得到确认后, s n d _ u n a(尚未确认

的最小序号数据 )加1。 10. 确定是否延迟发送 ACK 707-714

如果服务器在其 S Y N 中发送数据,那我们就延迟发送 A C K ;否则,立即发出

A C K (因为这很可能是三次握手中的第二个报文段 )。延迟发送 A C K是因为,如果服务器发来 的S Y N中包含了数据,那么服务器很可能正在使用 T / T C P,这样就很可能还会接收到另外的 报文段,其中包含剩余的应答数据,这时就没有必要立即发送 A C K。但如果这个报文段中同 时还包含有服务器的 F I N (最小的三报文段 T / T C P交换中的第二个报文段 ),图11 - 1 8中的代码会 打开TF_ACKNOW标志,以便立即发送 ACK。 715-726

我们知道t_state等于TCPS_SYN_SENT,但如果隐藏状态标志 TF_SENDFIN也

是打开的,我们的状态实际上是

S Y N _ S E N T * 。在这种情况下,我们的状态变迁到

FIN_WAIT_1状态(如果看看 RFC 1644中的状态变迁图就可以看出,这实际上是两个状态变迁 的组合。在 S Y N _ S E N T *状态下接收到 S Y N就变迁到 F I N _ WA I T _ 1 *状态,而对 S Y N的A C K则 变迁到FIN_WAIT_1状态)。 对应于图 11 - 8开头的 i f的e l s e代码如图 11 - 9所示。它对应的是两端同时打开:我们发出 了一个SYN,然后收到了一个没有 ACK的SYN。这个图取代了卷 2第758页的第581~582行。

图11-9 tcp_input :同时打开

112

计计第一部分 TCP事务协议

下载

图11-9 (续)

11. 立即ACK和关闭重传定时器 739-740

立即发出 A C K,并且关闭重传定时器。尽管定时器被关闭,但由于 T F _ A C K N O W

标志是设置了的,在 t c p _ i n p u t快结束时调用 t c p _ o u t p u t。在发送 A C K时,因为至少有 一个数据字节(SYN)已经发出且未得到确认,重新启动重传定时器。 12. 执行TAO测试 741-755 如果报文段中包含有 CC选项,那么就要执行 TAO测试:缓存的值(tao_cc)必须非 0,接收到的 C C值必须大于缓存中的值。如果通过了 TA O测试,缓存的值就要用接收到的 C C 值进行更新,这时要么从 SYN_SENT状态变迁到 ESTABLISHED*状态,要么从 SYN-SNET*状 态变迁到FIN_WAIT_1*状态。 13. TAO测试失败或没有 CC选项 756-765

如果TA O测试失败,新的状态就是 S Y N _ R C V D。如果没有 C C选项,则 TA O缓存

的内容置0(未定义),新的状态也是 SYN_RCVD。 图11 - 1 0给出了标号为 t r i m t h e n s t e p 6的代码段,是在处理 L I S T E N状态结束时的一个 分支(见图11-5)。这个图中的大部分代码是从卷 2第759页复制过来的。

图11-10 tcp_input :处理完主动或被动打开后执行的 trimthenstep6

代码段

113

第11章 T/TCP实现:TCP输入计计

下载

图11-10 (续)

14. 是客户端则不要跳过 ACK处理 784-793

如果ACK标志打开,我们就是事务过程中的客户端。也就是说,我们发送的 SYN

得到了 A C K,我们是从 S Y N _ S E N T状态变迁到当前状态的,而不是从 L I S T E N状态变迁来的。 在这种情况下,我们不能执行 s t e p 6分支,因为那样就会跳过对 A C K的处理过程 (见图11 - 1 ), 而如果我们在 S Y N报文段中发送了数据,就需要对数据的 A C K进行处理 (常规的T C P会在这里 跳过对ACK的处理过程,因为它从来不会随 SYN一起发送数据)。 处理过程中的下一个步骤是 T / T C P中新加的。通常,卷 2第7 5 3页开始的 s w i t c h语句中只 有LISTEN和SYN_SEN T状态这两个c a s e处理代码 (这两种情况我们都刚刚介绍过 )。T / T C P增 加了LAST_ACK、CLOSING和TIME_WAIT状态这三段 case处理代码,如图 11-11所示。

图11-11 tcp_input :LAST_ACK、CLOSING和TIME_WAIT状态的初始处理

114

计计第一部分 TCP事务协议

下载

图11-11 ( 续)

812-813

只有在接收到的报文段中包含了 S Y N和C C选项,并且我们已经有该主机的缓存

CC值(c c _ r e c v非0),才执行接下来的特殊测试。同时知道要进入三种状态之一, TCP已发出 了一个 F I N,并接收到一个 F I N (图2 - 6 )。在L A S T _ A C K和C L O S I N G状态下, T C P等待对其所 发F I N的A C K。所以要执行的测试是在 TIME_WAIT状态下收到新的 S Y N时是否可以安全地截 断TIME_WAIT状态,或者在 L A S T _ A C K或C L O S I N G状态下收到一个新的 S Y N是否隐含着我 们所发送FIN的ACK。 15. 如果持续时间大于 MSL就不允许截断TIME_WAIT状态 814-816

通常,处于 TIME_WAIT状态下的连接是允许接收新 SYN的(卷2第765~766页)。这

是从伯克利演变来的系统所允许的隐式截断 TIME_WAIT状态,至少从 N E T / 1以后就是这样了 (卷1的习题 1 8 . 5的解答就说明了这一特性 )。如果连接处于 T I M E _ WA I T状态的持续时间大于 MSL,上述做法在T/TCP中是不允许的,这时要发送 RST。我们在4.4节中讲到过这个限制。 16. 新SYN是现存连接的隐含 ACK 817-820

如果接收到的CC值大于缓存的CC值,则TAO测试成功(即这是一个新的 SYN)。这

时关闭当前连接,回头执行 f i n d p c b分支,希望找到一个处于 L I S T E N状态的插口来处理新 的SYN。图4-7给出了服务器插口的一个例子,在处理隐含的 ACK时,插口处于 LAST_ACK状 态。

11.6 PAWS:防止序号重复 卷2第7 4 0页的PAW S测试没有变化—就是处理时间戳的代码。图 11 - 1 2所示的测试在这 些时间戳测试之后执行,验证接收到的 CC。

图11-12 tcp_input :验证接收到的 CC

860-871

如果使用T/TCP(TF_REQ_CC和TF_RCVD_CC选项同时打开),这时接收到的报

文段必须包含 CC选项,且CC值必须等于该连接所用的值 (c c _ r e c v);否则,报文段就是过时

115

第11章 T/TCP实现:TCP输入计计

下载

重复的,要丢弃 (但要给出确认,因为所有重复的报文段都需要确认 )。如果报文段中包含了 RST,就不丢弃,允许处理该报文段的函数稍后可以对 RST进行处理。

11.7 ACK处理 在卷2第7 7 1页上, R S T处理后,如果 A C K标志没有打开,报文段就被丢弃。这是常规的 TCP处理过程。 T/TCP改变这一点,如图 11-13所示。

图11-13 tcp_input :处理没有 ACK标志的报文段

1024-1035

如果A C K标志关闭,并且状态是 S Y N _ R C V D,或者 T F _ S E S N D S Y N标志打开

(即半同步 ),则执行 s t e p 6分支,而不是丢弃该报文段。这样做处理的是在连接建立前、但 第一个SYN之后、不带 ACK的数据报文段到达的情况 (例如图3-9的第2报文段和第 3报文段)。

11.8 完成被动打开和同时打开 如卷2的第 2 9章一样,继续对 A C K进行处理。第 7 7 4页的大部分代码还是一样的 (删除第 8 0 6行),但用图 11 - 1 4的代码替代其中的 8 1 3 ~ 8 1 5行。这时我们处于 S Y N _ R C V D状态,处理的 是完成三次握手的最后一个 ACK。这是在服务器上对连接的常规处理过程。 1. 如果缓存的 CC值未定义,就更新 1057-1064

读取这个对等端的 TA O记录项,如果所缓存的 C C值为0 (未定义),则用接收到

的C C值更新。注意,只有在缓存的值未定义时才执行更新操作。回顾前面,图 11 - 6的代码在 C C选项不存在时明确地将 t a o _ c c设置为0 (这样,当三次握手完成时就会进行更新 )。但是如 果TA O测试失败,也就不会修改 t a o _ c c的值。后面这个动作实际上就是收到了一个失序的 SYN,不应引起缓存 tao_cc的改变,如我们在图 4-11中所述。 2. 变迁到新状态 1065-1074

从SYN_RCVD状态变迁到 ESTABLISHED状态,是服务器完成三次握手过程的

常规T C P状态变迁。因为进程已经用 M S G _ E O F标志关闭了用于发送的半个连接,连接状态从 SYN_RCVD*变迁到FIN_WAIT_1状态。

图11-14 tcp_input :被动打开或同时打开的完成

116

计计第一部分 TCP事务协议

下载

图11-14 (续)

3. 检查数据或 FIN 1075-1081 如果报文段中包含有数据或 FIN标志,那么在标号为 dodata的代码行就要调用 宏T C P _ R E A S S(回顾图 11 - 1 )将数据交付给用户进程。卷 2第7 9 0页给出了在标号 d o d a t a处对 这个宏的调用,这段代码在 T / T C P中没有改变。否则就要调用 t c p _ r e a s s,其第二个参数为 0,将队列中的所有数据交付给用户进程。

11.9 ACK处理(续) 快速重传和快速恢复算法 (卷2的2 9 . 4节)保持不变。图 11 - 1 5中的代码插入在卷 2第7 7 9页的 899~900行之间。

图11-15 tcp_input :如果TF_SENDSYN 打开了,就将其关闭

117

第11章 T/TCP实现:TCP输入计计

下载 1. 关闭隐藏状态标志 TF_SENDSYN

如果隐藏状态标志 TF_SENDSYN处于打开状态,则它将被关闭。这是因为接收

1168-1181

到的A C K确认了已经发送出去的一些东西,连接已不再是半同步。因为 S Y N已经得到确认, 并且SYN占用了1字节序号空间, snd_una加1。 图11-16插在卷2第780~781页的926~927行之间。

图11-16 tcp_input :如果没有对数据的 ACK就跳过剩ACK处理过程

2. 如果没有对数据的 ACK,就跳过剩余ACK处理过程 如果没有对数据的确认 (仅对我们的SYN给出了确认 ),ACK处理过程的剩余部

1210-1215

分就跳过去。跳过去的处理代码包括打开拥塞窗口和将已得到确认的数据从发送缓存中移去。 这项测试和程序分支在 T / T C P中不存在。这纠正了在 1 4 . 1 2节的最后讨论的一个 程序缺陷,在那里连接的服务器端通过发送两个背靠背段来执行慢启动,而不是一 个报文段。 第2个变化如图 11 - 1 7所示,用于替代卷 2的图2 9 - 1 2中的代码。这时我们处于 C L O S I N G状 态并对 A C K 进行处理,处理结果是将连接的状态变迁到

T I M E _ WA I T。T / T C P允许截断

TIME_WAIT状态(见4.4节)。

图11-17 tcp_input :在CLOSING状态收到 ACK:设置TIME_WAIT定时器

1276-1280

如果我们从对等端接收到一个 C C值,并且连接的持续时间少于 M S L,这时

TIME_WAIT定时器就设置为当前重传超时的 T C P T V _ T W T R U N C(8)倍。否则,TIME_WAIT定 时器设置为通常的 2倍MSL。

118

计计第一部分 TCP事务协议

下载

11.10 FIN处理 T C P输入处理的接下来的三部分 (更新窗口信息、紧急模式处理和接收数据处理 )在T / T C P 中都没有改变 (回忆图11 - 1 )。再回顾卷2的2 9 . 9节,如果设置了 F I N标志,但因为序号空间的空 洞,它不会得到确认,那一节中的代码就用于清除 F I N标志。因此,在这里我们知道 F I N是要 等待确认的。 FIN处理过程的另一个变化如图 11-18所示。这个修改用于替代卷 2第791页的第1123行。

图11-18 tcp_input :确定是否迟延发送 FIN的ACK

1. 决定是否延迟发送 ACK 如果连接是半同步的 (隐藏状态标志 T F _ S E N D S Y N打开),A C K就要延迟发送,

1414-1424

试图在数据报文段中捎带 A C K。这是一种典型的情况,处于 L I S T E N状态的T / T C P服务器收到 S Y N,这样图 11 - 5中的代码就会设置 T F _ S E N D S Y N标志。注意,图中代码已将延迟发送 A C K 的标志打开,但这里是 TCP要根据已经设置了的 FIN标志确定怎样去做。如果 T F _ S E N D S Y N标 志没有打开,则 ACK不能延迟。 常规的变迁是从 F I N _ WA I T _ 2状态到 T I M E _ WA I T状态,在 T / T C P中这也需要修改,以便 使T I M E _ WA I T状态可能被截断 (见4 . 4节)。图11 - 1 9给出了这些修改,用于取代卷 2第7 9 2页的 1142~1152行。

图11-19 tcp_input :变迁到 TIME_WAIT状态以便可能截断超时间隔

119

第11章 T/TCP实现:TCP输入计计

下载

图11-19 (续)

2. 设置TIME_WAIT超时间隔 1451-1453

如图11 - 1 7所示,只有当我们从对等端收到一个 C C选项,并且连接时间短于

MSL时,TIME_WAIT状态才能截断。 3. 强迫立即发送FIN的ACK 1454-1455

这个变迁通常是在 T / T C P的客户端,当收到服务器的响应以及服务器的 S Y N和

F I N时发生的。服务器的 F I N应该立即给出 A C K,因为两端都已经发送了 F I N,已经没有理由 再延迟发送 ACK了。 在两个地方要重启动 TIME_WAIT定时器:处于 T I M E _ WA I T状态时接收到 A C K 和处于TIME_WAIT状态时接收到 F I N (卷2第7 8 4页和第 7 9 2页)。T / T C P没有修改这些 代码。这表明,即使状态 T I M E _ WA I T被截断,如果在这时收到重复的 A C K或F I N, 定时器就要在 2 M S L时重启动,而不是在截断后的值。重启动定时器所需要的信息在 截断后的值时也能得到 (即控制块 ),但是由于对等端必须重传,更保守的做法是不截 断TIME_WAIT状态。

11.11 小结 T / T C P所做的修改大部分都是在 t c p _ i n p u t中,并且其中的大部分修改都与打开新连接 有关。 在L I S T E N状态收到 S Y N时要执行 TA O测试。如果报文段通过了这个测试,报文段就不是 过时的重复报文段,三次握手也就不需要了。在 SYN_SENT状态收到SYN时,CCecho选项(如 果存在 )就用于验证该 S Y N 不是过时的重复报文段。当处于 L A S T _ A C K 、 C L O S I N G 和 TIME_WAIT状态收到SYN时,很有可能SYN是一个隐含的ACK,可以完成现存连接的关闭。 当主动关闭一个连接时,如果连接的持续时间短于 MSL,则TIME_WAIT状态被截断。

下载

第12章 T/TCP实现:TCP用户请求 12.1 概述 t c p _ u s r e q 函数处理来自插口层的所有 P R U _ x x x 请求。在本章中我们仅仅介绍 P R U _ C O N N E C T、P R U _ S E N D和P R U _ S E N D _ E O F请求,因为 T / T C P中只对这三个请求做了修 改。我们也会介绍 t c p _ u s r c l o s e d函数,当进程发送完数据时要调用这个函数。还有 tcp_sysctl函数也会介绍,它用来处理新的 TCP中的sysctl变量。 我们不打算介绍 t c p _ c t l o u t p u t函数(见卷2的3 0 . 6节)所需的修改,这个函数用于设置 和读取两个新的插口选项: T C P _ N O P U S H和T C P _ N O O P T。所需的修改是非常细微具体的,只 要阅读源代码就很容易理解。

12.2 PRU_CONNECT请求 在 N e t / 3 中,大约需要 2 5 行代码 ( 卷 2 第 8 0 8 ~ 8 0 9 页 ) 来处理 t c p _ u s r r e q 发出的 PRU_CONECT请求。在T/TCP,大部分这些代码都移到了 tcp_connect函数中(下一节介绍 ), 只留下了图 12-1所给出的代码。

图12-1 PRU_CONNECT 请求

137-141

tcp_connect执行连接建立所需的步骤, tcp_output发出SYN报文段(主动打

开)。 当某个进程调用 c o n n e c t时,即使本地主机和待连接的对等端主机都支持 T / T C P,仍然 要经历正常的三次握手过程。这是因为不可能用 c o n n e c t函数传递数据,这样 t c p _ o u t p u t 就仅仅发送 S Y N。为了跳过三次握手过程,应用程序必须避免使用

c o n n e c t ,而是使用

sendto或sendmsg,并给定数据和对等端服务器的地址。

12.3 tcp_connect函数 新的 t c p _ c o n n e c t 函数执行主动打开所需的处理步骤。当进程调用

connect

(PRU_CONNECT请求)或者当进程调用 sendto或sendmsg时,要改为调用该函数,指定待连接 的对等端地址(PRU_SEND和PRU_SEND_EOF请求)。tcp_connect的第一部分在图12-2中给出。 1. 绑定本地端口 308-312

nam指向一个Internet插口地址结构,其中包含待连接的服务器的 IP地址和端口号。

121

第 12章 T/TCP实现:TCP用户请求计计

下载

如果还没有给插口指定一个本地端口 (通常的情况),调用i n _ p c b b i n d就会分配一个端口 (卷2 第558页)。 2. 指定本地地址,检查插口对的唯一性 313-323

如果还没有给插口绑定一个本地 IP地址(通常的情况下),调用in_pcbladdr就可

分配本地 I P地址。 i n _ p c b l o o k u p查找匹配的 P C B,如果找到,就返回一个非空指针。仅仅 在进程绑定了一个专门指定的本地端口时才可能找到一个匹配的

P C B ,因为如果

i n _ p c b b i n d选择本地端口,就会选择一个目前不在使用的本地端口。但是在 T / T C P中,更 有可能的是一个客户端进程为一系列事务绑定同一个本地端口 (见4.2节)。 3. 存在已有连接;检查 TIME_WAIT状态是否可以截断 324-332 如果找到一个匹配的 PCB,进行下面的三项测试: 1) PCB是否处于TIME_WAIT状态; 2) 连接持续时间是否短于 MSL; 3) 连接是否使用T/TCP(也就是说,是否从对等端收到了一个 CC选项或CCnew选项)。 如果上述这三个条件同时为真,则调用 t c p _ c l o s e关闭现有的PCB。这就是我们在 4.4节 中讨论过的,当一个新的连接再次使用同一插口对并执行一次主动打开时, TIME_WAIT状态 的截断。 4. 在互连网PCB中完成插口对 333-336

如果本地地址还是通配符,则 i n _ p c b l a d d r计算出的值存储在 P C B中。外部地

址和外部端口也存储在 PCB中。 图1 2 - 2中的步骤与图 7 - 5中的最后一部分相似。 t c p _ c o n n e c t的最后一部分在图 1 2 - 3中 给出。这段代码与卷 2第808~809页PRU_CONNECT请求的最后一部分相似。

图12-2 tcp_connect 函数:第一部分

122

计计第一部分 TCP事务协议

下载

图12-2 (续)

图12-3 tcp_connect 函数:第二部分

123

第 12章 T/TCP实现:TCP用户请求计计

下载 5. 初始化IP和TCP首部 337-341

tcp_template分配一个mbuf,用于缓存 IP和TCP首部,并用尽可能多的信息来

初始化这两个首部。 6. 计算窗口宽度因子 342-345 计算接收缓存的窗口宽度值。 7. 设置插口和连接的状态 346-349

soisconnecting在插口状态变量中设置特定的一些标志位,并设置 TCP连接的

状态为SYN_SENT(如果进程给出M S G _ E O F标志,并调用s e n d t o或者s e n d m s g,而不是调用 c o n n e c t,我们很快就会看到 t c p _ u s r c l o s e d设置T F _ S E N D S Y N隐藏状态标志,连接状态 变迁到SYN_SENT*)。连接建立定时器初始化为 75秒。 8. 初始化序号 350-352

从全局变量 tcp_iss中复制初始发送序号,然后该全局变量值要增加,即加上除

以4后的TCP_ISSINCR。发送序号由tcp_sendseqinit初始化。 我们在3.2节中讨论过的ISS随机化在宏 TCP_ISSINCR中实现。 9. 生成CC值 353-361

读取对等端的TAO缓存记录项。全局变量 t c p _ c c g e n值加上C C _ I N C(见8.2节)后

存储在T/TCP的变量t c p _ c c g e n中。如同我们以前所述,不论是否使用了 CC选项,主机每建 立一个连接,tcp_ccgen就要加1。 10. 确定是否使用CC或CCnew选项 362-368

如果对应这个主机的 TA O缓存(t a o _ c c s e n t)非0 (说明与该主机之间已经不是第

一次连接 ),并且 c c _ s e n d的值大于或等于 t a o _ c c s e n t( C C值还没有回到 0,继续循环),这 时发出一个 C C 选项并用新的 C C 值更新 TA O 缓存。否则发送一个新的 C C n e w 选项,并将 tao_ccsent设置为0(即未定义)。 回想图 4 - 1 2中,那里的情况可以作为上述 i f条件中的第二部分不成立的一个实例:最后 一次发送这个主机的 C C 值是 1 (t a o _ c c s e n t ),但 t c p _ c c g e n (对这个连接来说,变为 cc_send)的当前值是 2 147 483 648。这样,T/TCP就必须发送 CCnew选项而不是 CC选项,因 为如果我们发出的 CC选项值为2 147 483 648,而对方主机还在其缓存中记着我们上次发送的 C C值(即1 ),那个主机会强制执行三次握手操作,因为 C C值已经回到0并继续循环。对方主机 无法区分 CC值为2 147 483 648的SYN是否是一个过时的重复报文段。而且,如果我们发送了 CC选项,即使三次握手过程顺利完成,对方主机也不会更新对应于本主机的缓存记录项 (请再 看看图 4 - 1 2 )。如果发送的是 C C n e w选项,客户端强制执行三次握手操作,并且会使服务器在 三次握手操作完成后更新对应于本主机的缓存值。 Bob Braden的T / T C P实现是在 t c p _ o u t p u t中测试是发送 C C选项还是 C C n e w选 项,而不是在这个函数中。这就导致了一个微小的缺陷,见下面的解释 [Olah 1995]。 考虑图 4 - 11,但假定报文段 1被中途的某个路由器丢弃。报文段 2 ~ 4如图所示,从客 户端口1 601发起的连接成功地建立。客户端发出的下一个报文段是重传的报文段 1, 但其中包含一个取值为 1 5的C C n e w选项。假设该报文段成功地收到,服务器强制执 行三次握手,完成以后,服务器将对应于该客户端的 C C缓存值更新为 1 5。如果此后

124

计计第一部分 TCP事务协议

下载

网络交付了一个过时的重复报文段 2,其中的 C C值为5 0 0 0,服务器收到后就会收下。 解决的方法是在客户端执行主动打开时判断是发送 C C选项还是 C C n e w选项,而不是 在tcp_output函数中发送报文段时判断。

12.4 PRU_SEND和PRU_SEND_EOF请求 在卷 2第8 11页中,对 P R U _ S E N D 请求的处理仅仅是先调用 s b a p p e n d ,然后再调用 t c p _ o u t p u t 。在 T / T C P 中,对这个请求的处理还是一样,只是代码中加上了对 P R U _ S E N D _ E O F请求的处理,如图 12-4所示。我们可以看到,对 TCP,P R U _ S E N D _ E O F请求 是在指定了 MSG_EOF标志(见图5-2)并且当最后一个 mbuf发送给协议时由 sosend产生的。

图12-4 PRU_SEND 和PRU_SEND_EOF

请求

1. 隐式连接建立 192-202

如果n a m参数非空,进程就调用 s e n d t o或s e n d m s g,并指定一个对等端地址。

如果连接状态是 C L O S E D或L I S T E N,那么 t c p _ c o n n e c t就执行隐式连接建立。初始发送窗 口设置为 4 096(T T C P _ C L I E N T _ S N D _ W N D),因为在T/TCP中,客户端可以在收到服务器的窗 口通告以前就发送数据 (见3.6节)。 2. 为连接设置初始 MSS 203-204

调用t c p _ m s s r c v d函数时第二个参数为- 1,表示我们还没有收到 S Y N,所以用

这个主机的缓存值 (t a o _ m s s o p t)作为初始 MSS。当t c p _ m s s r c v d函数返回时,根据缓存的 t a o _ m s s o p t值或系统管理员在路由表记录项中设置的值 (r t _ m e t r i c s结构中的 r m x _ m t u

125

第 12章 T/TCP实现:TCP用户请求计计

下载

成员)设置变量 t _ m a x s e g和t _ m a x o p d的值。如果并且当收到服务器发出的带有 M S S选项的 S Y N时, t c p _ m s s r c v d将再次被 t c p _ d o o p t i o n s调用。因为在收到对等端的 M S S选项之 前就发出了数据,现在 T/TCP需要在收到 SYN之前就在TCP控制块中设置MSS变量的值。 3. 处理MSG_EOF标志 205-212

如果进程指定了 M S G _ E O F 标志,这时 s o c a n t s e n d m o r e 就要设置插口的

S S _ C A N T S E N D M O R E 标志。然后 t c p _ u s r c l o s e d 就把连接状态从 S Y N _ S E N T ( 由 tcp_connect设置)变迁到SYN_SENT*状态。 4. 发送第一个报文段 213-214

t c p _ o u t p u t检查是否应该发送报文段。在 T/TCP客户端刚刚指定 M S G _ E O F标志

调用了sendto(见图1-10)时,这个调用就发出一个报文段,其中包含 SYN、数据和FIN。

12.5 tcp_usrclosed函数 N e t / 3中,在处理 P R U _ S H U T D O W N请求时,该函数由 t c p _ d i s c o n n e c t调用。我们在图 1 2 - 4中可以看到,在 T / T C P中,这个函数也被 P R U _ S E N D _ E O F请求调用。图 1 2 - 5给出了这个 函数,替代卷2第817页中的代码。

图12-5 tcp_usrclosed

541-546

函数

在T / T C P中,通过设置 T F _ S E N D F I N状态标志,用户在 S Y N _ S E N T或S Y N _ RV D

状态下发起关闭过程,将状态变迁到相应的加星状态。其余的状态变迁在 变。

T / T C P中没有改

126

计计第一部分 TCP事务协议

下载

12.6 tcp_sysctl函数 在为T / T C P而做修改时,用 s y s c t l程序修改 T C P变量的能力也同时加上了。 T / T C P对此 功能并没有严格要求,但这个功能提供了改变特定 T C P变量值的一个简便方法,而不必再使 用调试程序对内核进行修补。 T C P 变量都以前缀 n e t . i n e t . t c p 来标识访问。在 T C P p r o t o s w结构的p r _ s y s c t l字段中(卷2第6 4 1页)记录着指向该函数的一个指针。图 1 2 - 6给出 了这个函数。 570-572 目前只支持三个变量,但是很容易加上更多的变量。

图12-6 tcp_sysctl 函数

12.7 T/TCP的前景 有一件有趣的事,看看在 RFC 1323中定义的 T C P修改方案的普及,实际上是关于窗口宽 度和时间戳选项的变化。这些变化受日益增长的网络速度 ( T 3电话线路和 F D D I )以及潜在的长 时延路由 (卫星线路 )等的驱动。 Thomas Skibo为S G I工作站所完成的修改是最早的实现之一。 然后他又在伯克利 N e t / 2版中做了这些修改,使这些修改在 1 9 9 2年5月可以公开得到 (图1 - 1 6中 详细给出了各个 BSD版本之间的区别及其发行时间 )。大约一年以后 (1993年4月),Bob Braden 和Liming Wei公布了SunOS 4.1.1中类似于RFC 1323的源码修改。1993年8月,伯克利把Skibo 的修改加到了 4 . 4 B S D版中,这使公众在 1 9 9 4年4月可以得到 4 . 4 B S D - L i t e版。到1 9 9 5年,有一 些销售商已经加上了对 RFC 1323的支持,另有一些销售商则宣称准备加上对 RFC 1 323 的支

下载

127

第 12章 T/TCP实现:TCP用户请求计计

持。但 RFC 1 323 并不是很通用的,特别是 P C机上的实现 (事实上,在 1 4 . 6节中我们会看到, 只有不到2%的客户遇到过发送窗口宽度和时间戳选项的特殊 WWW服务器)。 T/TCP很可能会走类似的路。 1994年9月的第一次实现 (见1.9节)只是对SunOS 4.1.3的源码 做了修改,大多数用户对此都不是很感兴趣,除非他们在使用 S u n O S的源码。然而这只不过 是T / T C P设计者的一个参考实现。普遍存在的 8 0×8 6硬件平台上的 F r e e B S D实现 (引入了 S u n O S源码中的修改部分 )在1 9 9 5年的早期就可以公开得到了,它应该会将 T / T C P传播到很多 的用户。 本书这部分章节的目的是用 T / T C P实例来说明为什么 T/TCP 是对T C P的很有价值的改进, 给出文档的细节并解释源码的变化。如同 RFC 1323 中的修改一样, T / T C P实现与非 T / T C P实 现可以互通,仅仅当两端同时都支持 CC选项时才使用它。

12.8 小结 t c p _ c o n n e c t函数是新的,已经有了 T / T C P所需的修改,显式 c o n n e c t要调用它,隐 式连接建立(指定目标地址的 s e n d t o或者s e n d m s g)也调用它。如果连接使用的是 T / T C P,并 且持续时间短于 MSL,则该函数允许还处于 TIME_WAIT状态的连接再次建立新连接。 P R U _ S E N D _ E O F请求是新的,它在最后一次调用协议输出并且应用程序指定了 M S G _ E O F 标志时由插口层产生。该请求允许采用隐式连接建立,并且在指定了 M S G _ E O F标志时还调用 tcp_usrclosed。 对t c p _ u s r c l o s e d函数所做的唯一修改是允许一个进程可以关闭尚处于 S Y N _ S E N T或 SYN_RCVD状态的连接。这时要设置隐藏标志 TF_SENDFIN。

下载

第二部分 TCP的其他应用 第13章 HTTP:超文本传送协议 13.1 概述 超文本传送协议(Hypertext Transfer Protocol,HTTP)是万维网(World Wide Web,WWW,也 简称为Web)的基础。本章我们介绍HTTP协议,在下一章讨论一个实际的Web服务器的运作,它 综合运用了卷1和卷2中的许多有关实际应用的内容。但本章并不介绍Web或如何使用Web浏览器。 NFSnet骨干网提供的统计数据 (见图13-1)表明,自1994年1月以来,使用HTTP协议的增长 速度令人吃惊。 月份

数据

分组数

.01 .04 .07 .10 .01 .04

图13-1 NFSnet骨干网上各种协议的分组数量百分比

以上这些百分数是基于分组数量统计得来的,而不是基于字节数的 (这些统计数据均可从 f t p : / / f t p . m e r i t . e d u / s t a t i s t i c s获得)。随着H T T P协议所占百分比的上升, F T P和 Telnet的比例在下降。同时我们注意到,分组的总数量在整个 1994年都在上升,而 1995年初开 始下降。这是因为 1 9 9 4年1 2月有其他的骨干网开始取代 N F S n e t骨干网。不过,分组数的百分 比仍然有效,它表明使用 HTTP协议的通信量在增加。 Web的简单结构如图 13-2所示。 如上图示, We b客户(通常称为浏览器 )与We b服务器使用一个或多个 T C P连接进行通信。 知名的Web服务器端口是TCP的80号端口。Web浏览时客户端与服务器在 TCP连接上进行通信, 所采用的协议就是本章描述的 H T T P,即超文本传送协议。我们也可看出,一个 We b服务器可 以通过超文本链接“指向”另一 We b服务器。 We b服务器上的这些链接并不是只可以指向 We b 服务器,还可以是其他类型的服务器,例如:一台 FTP或是Telnet服务器。 尽管 H T T P协议从 1 9 9 0就开始使用,但第一个可用的文档出现在 1 9 9 3年( [ B e r n e r s - L e e 1993] 大致描述了 H T T P协议的1 . 0版本),但是该 I n t e r n e t草案早就过期了。虽然有新的可用的 文档([Berners-Lee, Fielding和Nielsen 1995])出现,但是仍旧只是一个 Internet草案。 [Berners-Lee, Connolly 1995] 中描述了一种从 We b服务器返回给客户进程的文档,称为 H T M L (超文本标记语言 )文档。 We b服务器还返回其他类型的文档 (图象, P o s t S c r i p t文件,无 格式文本文件,等等 ),我们将在本章的后面举例说明这些文档。

130

计计第二部分 TCP的其他应用

下载

超文本链接 Web服务器

超文本链接 Web服务器

TCP端口80

TCP端口80

Web服务器

TCP端口80

TCP连接

Web客户 (浏览器)

图13-2 Web客户-服务器结构

下一节我们将简要介绍 H T T P协议和H T M L文档,随后对协议进行详细描述。然后我们讨 论一个流行的浏览器 ( N e t s c a p e )是怎样使用该协议的, H T T P协议使用 T C P的一些统计数据和 HTTP协议的一些性能问题。 [Stein 1995]讨论了运作一个 Web站点的许多有关细节。

13.2 HTTP和HTML概述 H T T P是一个简单的协议。客户进程建立一条同服务器进程的 T C P连接,然后发出请求并 读取服务器进程的响应。服务器进程关闭连接表示本次响应结束。服务器进程返回的文件通 常含有指向其他服务器上文件的指针 (超文本链接 )。用户显然可以很轻松地沿着这些链接从一 个服务器到下一个服务器。 客户进程 (浏览器 )提供简单、漂亮的图形界面。 H T T P服务器进程只是简单返回 客户进程所请求的文档,因此 H T T P服务器软件比 H T T P客户软件要小得多。例如, N C S A版本1 . 3的U n i x服务器由大约 6 500 行C代码写成,而 X Wi n d o w环境下的 U n i x Mosaic 2.5浏览器有约 80 000行C代码。 我们可以用一个简单的方法来了解许多 Internet协议是怎么工作的:那就是运行一个 Telnet 的客户程序与相应的服务器程序通信。这种方法对 H T T P协议也是可行的,这是因为客户进程 发送给服务器进程的语句包含有 A S C I I命令(以回车和紧跟的换行符表示结束,称为 C R / L F ), 服务器进程返回的内容也是以 A C S I I字符行开始。 H T T P协议使用的是 8 bit的ISO Latin 1字符 集,该字符集由 A S C I I字符及一些西欧语言中的字符组成 (以下网站可以找到各种字符集的信 息:http://unicode.drg)。 下面是我们获取 Addison-Wesley主页的例子。 连接到服务器的80号端口 由Telnet客户输出 由Telnet客户输出 由Telnet客户输出 我们只输入了这一行 Web服务器输出的第一行

131

第 13章 HTTP:超文本传送协议计计

下载

这里我们省略了33行输出

这里我们省略了4行输出

由Telnet客户输出

我们只输入了G E T /,服务器却返回了 51行,共3611字节。这样,从该 Web服务器的根目 录下取得了它的主页。 Telnet的客户进程输出的最后一行信息表示服务器进程在输出最后一行 后关闭了TCP连接。 一个完整的 HTML文档以< H T M L >开始,以< / H T M L >结束。大部分的 HTML命令都像这样 成对出现。 H T M L文档含有以 < H E A D >开始、以 < / H E A D >结束的首部和以 < B O D Y >开始、以 < / B O D Y >结束的主体部分。标题通常由客户程序显示在窗口的顶部。

[Raggett, Lam, and

Alexander 1996]中详细讨论了HTML。 下面这一行指定了一张图片 (本例中为公司的标识 )。


< C E N T E R >标志告诉客户程序将该图片放在屏幕中央, 标志含有该图片的相关信 息。客户程序要取得该图片的文件名由 S R C指示,A L T给出当使用纯文本客户程序时要显示的 字符串 (本例中是一个空字符串 )。实现强制换行。 We b服务器程序返回这个主页时并不 返回图片文件本身,它只返回图片文件的文件名,客户程序必须打开另一条 T C P连接来取得 该文件(在本章的后面我们将看到为每一个指定的图像申请不同的连接将增加 Web的负载)。 下面这一行表示开始新的一段 (

)。 这一段位于窗口的中央,它是第一级标题 (< H 1 >)。客户程序可以选择怎样显示第一级标题 (相 对应的有2~7级标题),通常采用比正常更大更粗的字体显示。 从上面可以看出,标记语言

( 如 H T M L )与其他格式化语言 (如 Tr o f f, Te X ,

P o s t S c r i p t )之间的区别。 H T M L 起源于 S M G L ,即标准通用标记语言 ( S t a n d a r d Generalized Markup Language) (h t t p : / / w w w . s g m l o p e n . o r g包含更多的有关 S G M L的信息 )。H T M L指定了文档的数据和结构 (本例中为一个 1级标题 ),但是没有 指定浏览器怎样对文档进行格式化。 接着我们先忽略主页中跟在“ Welcome”后面的很多问候语,看下面几行:

其中 < D D >指明一张定义表的入口。该入口以一张图片 (一个白球 )开始,后面跟着文字 "Information Resource Meta-Index" ,最后一部分指明一个超文本链接 (< A >标志 )和一个以 " h t t p : / / w w w . n c s a . u i u c . e d u "开头的超文本引用 ( H R E F属性)。像这样的超文本链接通

132

计计第二部分 TCP的其他应用

下载

常在客户程序中被加上下划线或以不同的颜色来显示。当遇到上面所示的图象 (公司的标志 )时, 服务器不会返回超链接引用的该图象或 H T M L文档。客户程序通常会立即下载图象并显示在 主页上,但对于超文本链接,除非用户点击 (也就是说,把鼠标移到该链接上并单击 ),否则客 户程序通常不加处理。当用户点击了该链接,客户程序将打开一个到 w w w . n c s a . u i u c . e d u 站点的HTTP连接,并执行GET,得到指明的文档。 类似h t t p : / / w w w . n c s a . u i u c . e d u / S D G / S o f t w a r e / M o s a i c / M e t a I n d e x . h t m l 这样的表示被称为 U R L:统一资源定位符 (Uniform Resource Locator)。U R L的详细说明和意 义在RFC 1738 [Berners-Lee, Masinter and McCahill 1994], 和RFC 1808 [Fielding 1995]中给出。 U R L是另一个重要的机制:统一资源标识符 URI(Uniform Resource Identifier)的一部分, U R I 还包括通用资源名称 URN(Universal Resource Name)。RFC 1630 [Berners-Lee 1994]中描述了 URI。URN试图比URL做得更好,但还没有制定出来。 大多数浏览器都提供查看 We b页面 H T M L源文件的功能,例如, N e t s c a p e和 Mosaic都提供“View Source”的特性。

13.3 HTTP 上节的例子中,客户程序发出的 G E T /命令是HTTP版本0.9的命令,大多数服务器均支持 这个版本 (为了提供向后兼容性 )。但目前 H T T P的版本是 1 . 0。因为1 . 0版本H T T P协议的客户程 序在请求命令行中指出版本号,例如: 因此服务器能得知客户程序所采用的

H T T P 协议的版本。本节我们将更详细地了解

HTTP/1.0。 13.3.1 报文类型:请求与响应 HTTP/1.0报文有两种类型:请求和响应。 HTTP/1.0请求的格式是: request-line headers (0或有多个)

body (只对POST请求有效) request-line的格式是: request request-URI HTTP版本号 支持以下三种请求: 1) GET请求,返回 request-URI所指出的任意信息。 2) H E A D请求,类似于 G E T请求,但服务器程序只返回指定文档的首部信息,而不包含实 际的文档内容。该请求通常被用来测试超文本链接的正确性、可访问性和最近的修改。 3) P O S T请求用来发送电子邮件、新闻或发送能由交互用户填写的表格。这是唯一需要在 请求中发送 b o d y的请求。使用 P O S T请求时需要在报文首部 C o n t e n t - L e n g t h字段中 指出body的长度。 对一个繁忙的 We b服务器进行采样,统计结果表明: 5 00 0 0 0个客户程序的请求中有

133

第 13章 HTTP:超文本传送协议计计

下载

9 9 . 6 8 %是G E T请求,0 . 2 5 %是H E A D请求,0 . 0 7 %是P O S T请求。当然,如果是在一个接受比萨 饼定购的站点上, POST请求的百分比将会更高。 HTTP/1.0响应的格式是: status-line headers (0个或有多个 )

body status-line的格式是: HTTP版本号 response-code response-phrase 下面我们就要讨论这几个字段。 13.3.2 首部字段 H T T P / 1 . 0的请求和响应报文的首部均可包含可变数量的字段。用一个空行将所有首部字 段与报文主体分隔开来。一个首部字段由字段名 (如图13-3所示)和随后的冒号、一个空格和字 段值组成,字段名不区分大小写。 报文头可分为三类:一类应用于请求,一类应用于响应,还有一类描述主体。有一些报 文头(例如:D a t e)既可用于请求又可用于响应。描述主体的报文头可以出现在 POST请求和所 有响应报文中。图 13-3列出了17种不同的报文头,在 [Berners-Lee, Fielding, and Nielsen 1995] 中均有详细的描述。未知的报文头字段将被接收者忽略。我们讨论完响应代码后将回过头来 看几个通用的报文头例子。 首部名称

请求?

响应?

主体?

图13-3 HTTP报文首部的名称

13.3.3 响应代码 服务器程序响应的第一行叫状态行。状态行以 H T T P版本号开始,后面跟着 3位数字表示 响应代码,最后是易读的响应短语。图 1 3 - 4列出了3位数字的响应代码的含义。根据第一位可 以把响应分成5类。

134

计计第二部分 TCP的其他应用

下载

使用这种 3位的响应代码并不是任意的选择。我们将看到 N T T P (见图1 5 - 2 )及其他 的Internet应用如FTP、SMTP也使用这些类型的响应代码。 响

应 1yz 200 201 202 204 301 302 304 400 401 403 404 500 501 502 503





信息型,当前不用 成功 OK,请求成功 OK,新的资源建立 (post命令) 请求被接受,但处理未完成 OK,但没有内容返回 重定向;需要用户代理执行更多的动作 所请求的资源已被指派为新的固定 URL 所请求的资源临时位于另外的 URL 文档没有修改 (条件GET) 客户差错 错误的请求 未被授权;该请求要求用户认证 不明原因的禁止 没有找到 服务器差错 内部服务器差错 没有实现 错误的网关;网关或上游服务器来的无效响应 服务暂时失效

图13-4 HTTP 3位响应码

13.3.4 各种报文头举例 如果我们使用 H T T P / 1 . 0来获取上节列出的主页中所引用的标识图,则需要执行以下一些 操作:

我们输入了这一行 以及这一行 然后输入一个空行表示请求结束 服务器响应的第一行

空行表示服务器响应头部的结束 这里收到了2859字节的二进制GIF图象 由Telnet客户输出

• 在GET请求中指出版本 1.0。 • 发送一个可以被服务器记录的简单的报文头: From。

下载

135

第 13章 HTTP:超文本传送协议计计

• 服务器返回的状态行给出了版本号、响应代码 200和响应短语“OK”。 • D a t e报文头给出服务器上的时间和日期,通常是格林尼治时间。上例中服务器返回一 个老式时间串。推荐的格式应是:缩写的天,日期中不含连字符, 4位数的年,如: • 服务器程序的类型和版本号分别是: NCSA Server版本1.3。 • MIME 版本是1.0。在卷1的28.4节和 [Rose 1993]中有较多关于MIME的内容。 • 报文体的数据类型由 C o n t e n t - T y p e和C o n t e n t - E n c o d i n g字段指出。 C o n t e n t T y p e指出的是类型,类型后跟一‘ /’,然后是子类型。本例中类型是 i m a g e,子类型 是g i f。H T T P使用的I n t e r n e t媒体类型在最近的 Assigned Number RFC文档中定义(本书 写作时最新的文档是: [Reynolds and Postel 1994])。 其他的典型值是:

如果报文主体是经过编码的,则 C o n t e n t - E n c o d i n g报文头也会出现。例如:如果返 回的报文中含有经过 U n i x的c o m p r e s s程序压缩的 P o s tSc r i p t文件(通常带有 . p s . Z后缀),下 面的两种报文头会同时出现:

• Last-Modified指出了最后一次修改资源的时间。 • 图象文件的长度 (2 859字节)在Content-Length报文头中指出。 在最后一个响应报文首部的后面,服务器程序紧跟着图象后发送了一个空行 (一个回车 /换 行对)。因为HTTP协议交换8 bit字节的数据,所以可以通过 TCP连接发送二进制数据。这点不 同于其他的Internet应用,特别是SMTP协议(卷1的第28章),它通过TCP连接传输7 bit的ASCII 字符,显式地将每字节的高位设置为 0,阻止了二进制数据的交换。 U s e r - A g e n t是公用的客户程序报文头,它用来标识客户程序的类型。下面是一些公用 报文头的例子:

13.3.5 例子:客户程序缓存 许多客户程序根据获取文件中的日期和时间在硬盘上缓存 H T T P文档。如果客户程序要获 取的文档已存储在客户程序的缓存中,则客户程序将发送 I f - M o d i f i e d - S i n c e 报文首部。 这样,如果服务器程序发现该文档没有发生任何变化,就无需再发送一次该文档了。这称为 条件GET请求。

用空行结束客户请求

136

计计第二部分 TCP的其他应用

下载

空行表示服务器响应头部的结束

上例中响应报文的响应代码为 3 0 4,它表示文档没有变化。从 T C P协议来看,这样做避免 了将文档的主体 (上例中是一个2 859字节的GIF图象)从服务器程序传送给客户程序。但是余下 的TCP连接的开销 (三次握手、终止连接的四个分组 )还是必须的。 13.3.6 例子:服务器重定向 下面是一个服务器重定向的例子。我们试着去获取作者的主页,但是故意省略最后的“ /” (这是用来指定目录的 URL所必需的一部分 )。

用空行结束客户请求

空行表示服务器响应头部的结束

例子中响应报文的响应代码为 3 0 2,表示所请求的 U R L已经被移动。 L o c a t i o n报文首部 指出了以“ /”结尾的新位置。许多浏览器能自动去连接这个新的 U R L。但如果浏览器不愿意 自动去访问这个新的 URL,服务器程序也将返回一个可供浏览器显示的 HTML文件。

13.4 一个例子 下面有一个使用流行的 Web客户程序 (Netscape 1.1N)的详细例子,通过它我们来逐一查看 H T T P和T C P的使用。我们从 Addison-Wesley的主页(h t t p : / / w w w . a w . c o m)开始,然后到它 指向的三个链接 (都在w w w . a w . c o m上),最后到卷 1中描述过的页面上结束。共使用 1 7条T C P 连接,客户主机发送 3 132字节,服务器主机返回 47 483字节。在17条连接中, 4条是为了传输 HTML文档(共28 159字节),还有13条是传输GIF图象(共19 324字节)。在进行这个会话前,先 清除硬盘上的 N e t s c a p e使用的缓存,迫使客户程序从服务器重新取得所有文件。我们还在客 户主机上运行Tcpdump软件,记录客户程序发送和接收的所有报文段。 如我们所预期的,第一条 T C P连接是访问主页 (G E T /),主页的 H T M L文档共涉及了 7个 G I F图象。客户程序收到这个主页后,马上并行地打开 4条T C P连接去获取前 4个G I F图象。这 是N e t s c a p e程序为了减少打开主页总时间的一种方法 (大多数 We b客户程序并不像这样,而是 只能一次下载一个图象 )。并行连接数量可由用户来配置,默认是 4个。当这些连接中有一条 结束时,客户程序会立即打开一条新的连接来获取下一个图象,直到客户程序取得全部 7个图

137

第 13章 HTTP:超文本传送协议计计

下载

象。图13-5表示这8条TCP连接的时间线, y轴是时间,单位为秒。 这8条TCP连接都由客户程序发起,依次使用 1 114~1 121的8个端口号。而8条连接均由服 务器程序关闭。我们把客户程序发送最初的 S Y N (客户的 c o n n e c t)看作连接的开始,客户程 序收到服务器程序的 F I N后发送F I N (客户的 c l o s e )认为是连接的结束。取得这个主页以及它所 涉及的所有 7个图象共需要约 12秒的时间。 下一章的图 14-22中给出了由客户程序发起的第一条连接 (端口 1 114)的Tcpdump分组跟踪 情况。 端口号:1114

时间(秒)

图13-5 一个主页和 7个GIF图象的8条TCP连接的时间线

注意,端口号为 1 115,1 116,1 117的三条连接是在第一条连接 (端口号为1 114) 结束之前建立的,这是因为 Netscape的客户程序在读到第一条连接上的文件结束标志 以后,并在关闭第一条连接之前发起三条无阻塞的连接。实际上,在图 1 4 - 2 2中我们 可以注意到,客户程序在收到 FIN标志后约半秒钟才发出 FIN分组。 同时使用多条 T C P连接是否真的能减少交互式用户所需的 处理时间呢?为了测试这一点,我们在主机

s u n 上运行

同时存在 的连接数

总时间(秒)

Netscape客户程序(图1-13),还是来获取 Addison-Wesley的主页。 但这台主机是采用如今常用的方式连接 I n t e r n e t,即通过拔号调 制解调器以28.8 Kb/s的速度连接 Internet。我们修改客户程序的 首选文件,对客户程序最大的连接数从 1至7都进行了测试。测 试时关闭了客户程序的硬盘缓存功能。在每一种最大连接数下 客户程序均运行三次,取结果的平均值。图 13-6是测试结果。 上图可以看出,从 1到4,随着连接数增加,总时间在减少。

图13-6 Web客户程序并行 连接数与总时间的比较

138

计计第二部分 TCP的其他应用

下载

但是如果用 Tc p d u m p来跟踪这种交换,我们会发现,虽然用户可能把连接数设成超过 4,但是 程序的极限是 4。不管怎么说,超过 4条连接后增加连接数对总时间即便有影响也是很小,不 如从1~2,2~3,3~4那么明显。 图13-5所示的总时间比图 13-6所示的最短时间 (10.2秒)要多约2秒,这是因为客户 主机的显示硬件速度有差异。图 13-6所示的测试是客户程序运行在一台工作站上,而 图13-5所示的测试客户程序运行在一台显示速度和运行速度均较慢的个人计算机上。 [Padmanabhan 1995]指出了多连接方法的两个问题。首先,这样做对其他协议不公平。例 如,F T P协议获取多个文件时每次只能使用一条连接 (不包括控制连接 )。其次,当在一条连接 上遇到拥塞并执行拥塞避免 (在卷1的第2 1 . 6节中有描述 )时,拥塞避免信息不会传递到其他连 接上去。 对客户程序来说,同时对同一主机使用多条连接实际上使用的可能是同一条路 径。如果处于瓶颈的路由器因为拥塞而丢弃某条连接的分组,那么其他连接的分组 通过该路由器时也同样可能会被丢弃。 客户程序同时使用多个连接带来的另一个问题是容易造成服务器程序未完成的连接队列 溢出,这样会使得客户主机重传它的 S Y N分组而造成较大的时延。下一章我们讨论 We b服务 器时,将在 14.5节中详细讨论服务器程序的未完成连接队列。

13.5 HTTP的统计资料 在下一章中我们将仔细讨论 T C P / I P协议族的一些特性和怎样在一个繁忙的 H T T P服务器上 使用(和误用 )它们。本节我们感兴趣的是一个典型的 H T T P连接到底是怎么回事。我们将使用 下一章一开始要讲到的 24小时的Tcpdump数据集。 图13-7列出了对近 130 000个独立的HTTP连接进行统计的结果。如果客户程序非正常关闭 连接,例如电话掉线等,我们就无法通过 Tcpdump的输出计算图中字节计数的中间值 (Median) 或均值 ( M e a n )或两者的值。存在一些因服务器超时而结束的连接会使图中连接持续时间的均 值比正常值偏高。

客户发送的字节数 /连接 服务器发送的字节 /连接 连接持续时间 (秒)

中 值

均 值

224 3 093 3.4

266 7 900 22.3

图13-7 独立的HTTP连接的统计

大多数关于H T T P连接的统计均采用中间值和均值。中间值能较好地体现“正常”连接的 情况,而均值则会因为少数长文件而较高。 [Mogul 1995b]中统计了 200 000个H T T P连接,发 现服务器返回数据量的中间值和均值分别为 1 7 7 0字节和 12 925 字节。此文还对另一个服务器 上约1 5 0万个检索进行统计,结论是返回数据量的中间值和均值分别为 9 5 8字节和 2 394 字节。 [Braun and Claffy 1994] 列出的对NCSA服务器进行统计的结果是中间值为 3 000字节,均值为 17 000字节。明显可以看出服务器返回数据量的大小取决于该服务器上所提供的文件,不同 的服务器之间有很大的差别。

下载

139

第 13章 HTTP:超文本传送协议计计

在本节中迄今为止我们讨论的都是单个使用 T C P的H T T P连接。大多数运行 We b浏览器的 用户会在一个 H T T P会话期间访问给定服务器的多个文件。因为服务器可利用的信息就是客户 主机的I P地址,所以要测量 H T T P会话的特性比较困难。多个用户能在同一时间利用同一客户 主机访问同一个服务器。此外,还有一些组织把所有的 H T T P客户请求集中起来通过少数几个 服务器(有时结合防火墙网关使用 )去访问外部网的 We b服务器,这样在 We b服务器端看起来很 多用户都在使用少数几个 I P地址(这些少数的服务器通常称为代理服务器,在 [Stein 1995]的第 4章中对它进行了讨论 )。不管怎么说, [Kwan, McGrath, and Reed 1995] 还是试图在N C S A服 务器上对会话的特性进行测定,并定义一个会话最长持续时间为 3 0分钟。在这 3 0分钟的会话 中平均每个客户执行 6个HTTP请求,服务器共返回 95 000字节数据。 本节中提到的统计都是在服务器端进行测量的,因此结论都受服务器所提供的 H T T P文档 类型的影响,例如,一个提供庞大气象图的 We b服务器平均每个 H T T P会话所返回的字节数要 比一个主要提供文本信息的服务器大得多。通常在 We b上跟踪大量客户程序对不同服务器的 HTTP请求能获得更好的统计结果。 [Cunha, Bestavros, and Crovella 1995]中提供了一组测量数 据。他们对 4 7 0 0个H T T P会话进行了测试,其中包括 5 9 1个不同用户对 5 7 5 7 7 2个文件的访问。 测量的结果表明这些文件的平均长度为 11 500 字节,同时他们也提供了不同类型文件 (如HTTP、 图象、声音、视频、文本等 )的平均长度。通过其他测试,他们发现文件长度的分布状态曲线 有一个大尾巴,即有大量的大型文件,这些文件影响了文件的平均字节数。他们发现大量被 访问的是小文件。

13.6 性能问题 随着 H T T P协议使用的增长 (图1 3 - 1 ),它对 I n t e r n e t产生了广泛而重要的影响。 [ K w a n , McGrath, and Reed 1995]中给出了 N C S A服务器上 H T T P协议一般应用的特性。 1 9 9 4年,上文 的作者对服务器的日志文件进行了为期五个月的检查后得出了一些结论。例如,他们注意到 5 8 %的客户请求是由个人计算机发起的,这类请求的每月增长率在 11 % ~ 1 4 %之间。他们在文 中还提供了一周中各天的请求数量、平均连接时间等统计数据。 [Braun and Claffy 1994]中提 供了对 N C S A服务器的其他分析。在这篇论文中,作者还讨论了 H T T P服务器可以通过缓存经 常被访问的文档来提高性能。 影响交互式用户响应时间的最大因素是 H T T P协议中使用的 T C P连接。前面我们看到每个 要传输的文档使用一个 TCP连接。[Spero 1994a]中以“HTTP / 1.0 与TCP交互不协调”为标题 对这个问题进行了描述。客户与服务器之间的 RT T和服务器的负载是影响响应时间的其他因 素。 [Spero 1994a]也提出连接建立较慢 (卷1的2 0 . 6节中有描述 )增加了时延。连接建立时间主 要取决于客户请求报文和服务器的 M S S通告报文 (通过I n t e r n e t的客户连接,典型长度为 5 1 2或 5 3 6字节)的长度。设想如果客户的请求报文小于或等于 5 1 2字节,一个 M S S报文的长度为 5 1 2 字节,那么连接建立时间就不会长了 (但是要注意,很多基于伯克利的实现中的对 m b u f (在 1 4 . 11节中有描述)的访问会引起连接建立慢的问题 )。当客户的请求报文超过服务器的 M S S时, 较慢的建立还要加上额外的 RTT。客户请求报文的长度取决于浏览器软件。 [Spero 1994a]中提 到当X m o a s i c浏览器请求三个 T C P报文段时发起了一个 1 1 3 0字节的请求报文 (这个请求报文共 有4 2行,其中 4 1行是A c c e p t报文首部 )。在1 3 . 4节的例子中, Netscape 1.1N浏览器共发起 1 7

140

计计第二部分 TCP的其他应用

下载

个请求,报文长度的范围是 1 5 0 ~ 1 9 7字节,因此没有发生长时延的情况。图 1 3 - 7列出了客户程 序请求报文长度的中间值和平均值,从中可以看出大多数客户发起向服务器的请求不会引起 长时延,但服务器的应答报文则会引起长时延。 我们刚刚提到, M o s a i c客户程序会发出许多 A c c e p t报文首部,但这些报文首部 并没有在图 13-3中列出来(因为它们没有在 [Berners-Lee, Fielding, and Nielsen 1995]中 出现)。因为少数服务器不对这些报文首部作任何处理,所以在这个 I n t e r n e t草案中没 有提到它们。这些报文首部的作用是告诉服务器,客户程序能接受哪些数据格式, 如G I F图象、P o s t S c r i p t文件等。但也有少数服务器提供一个文档的不同格式的副本, 而且目前还没有提供客户程序与服务器协商文档内容的方法。 另外重要的一点是: H T T P连接通常由服务器关闭,服务器经过 T I M E _ WA I T时延后关闭 连接,导致在繁忙的服务器上许多控制块停留在该状态。 [Padmanabhan 1995]和[Mogul 1995b]中建议客户与服务器保持一个打开的 T C P连接,而 不是服务器在发出响应后关闭连接。当服务器知道生成的响应报文的长度时才可以这样做, 回想前面 1 3 . 3 . 4节中我们提到的例子, C o n t e n t - L e n g t h报文首部中指出 G I F图象的大小。 否则,服务器必须通过关闭连接来为客户程序指出响应的结尾。对协议作这样的修改必须同 时修改客户端和服务器端。客户端规定 P r a g m a : h o l d - c o n n e c t i 报文首部,提供向后 on 兼容的能力。如果服务器不能识别这种 Pragma,就会忽略它,然后在发送完响应后关闭连接。 这种P r a g m a允许新客户程序在尽可能情况下保持连接,同时访问新的服务器,还允许现有所 有客户和服务器交互操作。 H T P P协议的下一版本 (版本1 . 1 )中可能会支持持续的连接,虽然具体怎么做可能 会有变化。 在这里我们实际上提到了当前定义的三种服务器结束响应的方法。最好的办法 是使用Content-Length报文首部,其次是服务器发送一个带有 boundary = 属 性的Content-Type报文首部([Rose 1993]的6.1.1节中给出了怎样使用这种属性的例 子,但是并非所有的客户程序都支持这种特性 )。最差的选择 (但最广泛运用的 )便是 服务器关闭连接。 P a d m a n a b h a n和M o g u l也提出两种新的客户请求报文,用来允许服务器流水线式的响应。 这两种请求是 G E T A L L(服务器将在单个响应内返回一个 H T M L文档和所有内嵌的图象 )和 G E T L I S T(类似客户程序执行一系列的 G E T请求)。当客户程序确认在它的缓存中没有所要请 求的任何文件时,可以使用 G E T A L L报文。当客户程序发起对一个 H T M L文档的 G E T请求后, 用GETLIST命令可以取得该 HTML文档所引用的、不在缓存中的所有文件。 H T T P协议的一个重要问题是:面向字节的 T C P数据流与面向报文的 H T T P服务不匹配。 一种理想的解决方法是:在 T C P协议之上制定一个在 H T T P客户和服务器之间、单个 T C P连接 之上、提供面向报文接口的会话层协议。 [Spero 1994b]中描述了这样一种称为 H T T P - N G的解 决方法。 HTTP-NG在单个TCP连接上提供多个会话。其中一个会话携带控制信息 (客户请求和 服务器响应报文 ),其他的会话从服务器返回所请求的文件。通过 T C P连接交换的数据包括一 个8字节的会话首部 (包含一些标志位、一个会话 I D和所跟数据的长度 ),会话首部后跟着这个 会话的数据。

下载

141

第 13章 HTTP:超文本传送协议计计

13.7 小结 H T T P是一个简单的协议。客户程序与服务器建立一个 T C P连接,发送请求并读回服务器 的响应。服务器通过关闭连接来指示它的响应结束。服务器所返回的文件通常含有指针 (超文 本链接 )指向一些位于其他服务器的文件。用户可以轻松地跟随这些链接从一个服务器到另一 个服务器。 客户请求是简单的 A S C I I文本,服务器的响应也是以 A S C I I文本开始 (首部),后面跟着数 据(可以是 A S C I I或二进制数据 )。客户程序软件 (浏览器 )分析服务器的响应,并把它格式化输 出,同时以高亮显示指向其他文档的链接。 通过H T T P连接传输的数据量较小。客户请求报文长度,为几百字节,服务器响应报文的 典型值也在几百字节至 10 000字节间。因为一些大文档 (如图象或大的 P o s t S c r i p t文件)会将服 务器响应报文长度的平均值拉大,所以 H T T P统计通常报告中间值。许多研究表明,服务器响 应报文长度的中间值小于 3000字节。 H T T P带来的最大的性能问题是每个文件使用一条 T C P连接。我们看一下 1 3 . 4节中提到的 例子,为了打开一个主页,客户程序建立了 8条T C P连接。当客户请求报文的长度超过服务器 通告的 M S S时,缓慢的建立使每一个 T C P连接增加了额外的时延。另一个问题是:服务器进 程正常关闭连接将引起在服务器主机上产生 TIME_WAIT时延,在一个繁忙的服务器上可以看 到很多这种待终止的连接。 我们比较一下几乎与 H T T P协议同时开发的 G o p h e r协议。 G o p h e r协议的文档号是 R F C 1436[Anklesaria et al. 1993]。从网络的观点来看, HTTP与Gopher非常相似。客户程序打开一 条与服务器的连接 ( G o p h e r使用7 0号端口 ),并发起请求。服务器返回带有应答的响应,并关 闭连接。它们的主要区别在于服务器送回给客户的报文的内容。尽管 G o p h e r协议允许服务器 返回非文本信息,如GIF文件,但大多数 Gopher客户程序是为 ASCII终端设计的。因此 Gopher 服务器返回的文档,大多数是 A S C I I文本文件。因为 H T T P协议有明显的优势,所以写作本书 时I n t e r n e t上的许多站点已关闭了它们的 G o p h e r服务程序。当 U R L为g o p h e r : / / h o s t n a m e 时,也有很多Web浏览器能识别Gopher协议,并与这些 Gopher服务器通信。 H T T P协议的下一个版本 (HTTP / 1.1)将在1 9 9 5年1 2月作为一个 I n t e r n e t草案公布。届时, 包括认证(MD5签名)、持续的TCP连接、连接协商等方面均将有所增强。

下载

第14章 在HTTP服务器上找到的分组 14.1 概述 本章我们将通过分析一个繁忙的 H T T P 服务器上所处理的分组,从另外的角度来分析 H T T P协议,同时还将对 I n t e r n e t协议族中的一些特性进行一般性的分析。这样我们就能把卷 1 和卷2中描述的 T C P / I P协议的一些特性与现实世界中的联系起来。从本章也可看到, T C P协议 的行为和实现的变化很多,有时甚至明显不合理。本章有很多主题,我们把它们近似地按照 TCP连接动作的顺序来安排:连接建立、数据传输和连接终止。 我们是从一个商业的 I n t e r n e t服务提供商的系统上收集数据。这个系统为 2 2个组织提 供H T T P服务,同时运行 NCSA h t t p d服务器的 2 2个副本 (我们将在下一节中讨论运行多个服 务器程序)。该系统的 CPU是Intel奔腾处理器,运行的操作系统是 BSD / OS V1.1。 我们收集了三种数据: 1) 在连续的 5天当中每小时运行一次 n e t s t a t程序,运行该程序时带 - s选项,用来收集 I n t e r n e t协议维护的所有计数器。这些计数器在卷 2中我们都有介绍,如第 1 6 4页( I P )、 第639页(TCP)等。 2) 在这5天当中Tc p d u m p程序(见卷1附录A ) 2 4小时运行,记录所有发出的和从 8 0端口来的 带有SYN、FIN或RST标志的TCP分组。这样,我们可以详细考查 TCP连接的统计结果。 在这期间共收集到 686 755 个符合上述条件的分组,它们分属于 147 103 次T C P连接尝 试。 3) 在5天的测量中,做了一次为期 2.5小时的统计,记录所有发出的和从 80端口来的TCP分 组。因为我们可以对除了带有 SYN、FIN或RST标记以外的更多的分组进行检查,所以 我们可以对少数特殊情况进行更详细的分析。在这次统计中共记录了 1 039 235个分组, 平均每秒115个。 收集24小时内的SYS / FIN / RST分组的命令是: -p标志没有把网络接口置于混合模式 (promiscuous),所以只有运行 Tcpdump程序的主机发 出或接收的分组才可能被捕捉,这也正是我们所需要的。这样减少了从本地网络中收集的数 据量,同时也使 Tcpdump程序减少了分组的丢失。 这个标志没有保证非混合模式。也有人可以将网络接口设为混合模式。 在这个主机上多次长时间运行 Tcpdump,报告的分组丢失情况为:每 16 000个丢 失1个至每22 000个丢失1个之间。 - w标志将收集结果以二进制格式存入文件,而不是以文本方式在终端上输出。这个输出 文件的二进制数据随后可以用 -r标志转换成我们所期望的文本文件。 只有发出的或从 80端口来的 TCP分组才被收集。此外还要求:从 TCP分组首部开始算,取 第13字节与数字7进行逻辑与运算,结果必须为 0。这是用来测试 SYN、FIN或RST标志是否被

143

第14章 在HTTP服务器上找到的分组计计

下载

置位(见卷1第1 7 1页)。通过收集满足上述条件的分组,然后分析 S Y N和F I N上的T C P序号,我 们能得到连接的每个方向上,传输数据的字节数。 Vern Paxson的t c p d u m p - r e d u c e软件就 是采用了这种简化方式 (http://town.hall.org/Archive/pub/ITA)。 我们给出的第一张图 (图1 4 - 1 )是5天中尝试连接的总数,包括主动和被动建立的连接。图 中表示的是两个 TCP计数器: t c p s _ c o n n a t t e m p t和t c p s _ a c c e p t s的时间曲线,摘自卷 2 第639页。当为了打开连接而发送一个 SYN分组时,第一个计数器加一;当在侦听端口收到一 个S Y N分组时,第二个计数器加一。这些计数器对主机上的所有 T C P连接进行计数,而不只 是H T T P连接。我们期望系统收到的连接请求比它发出的连接请求要多,因为系统主要提供 Web服务(当然系统也用作其他用途,但主要的 TCP / IP流量是由HTTP分组组成)。 星期二 正午

星期三 正午

星期四 正午

星期五 正午

星期六 正午

星期日 正午

连接尝 试次数

主动打开的连接

系统启动后所经过的时间(分钟)

图14-1 主动与被动连接尝试次数累计

图中星期五正午附近和星期六正午附近的虚线描绘了一个 2 4小时周期,在这 2 4小时中对 S Y N / F I N / R S T分组进行了跟踪、收集。注意被动连接尝试的次数曲线,它的斜率像我们所预 期的那样在正午后一直到午夜前都比较大。我们也可看出,从星期五的午夜开始到周末这段 时间,曲线的斜率一直在减小。我们绘出每小时被动连接尝试次数的曲线,如图 1 4 - 2所示, 从中很容易看出每天的周期性规律。 “繁忙”服务器的定义是什么?我们进行分析的系统每天收到超过150 000个TCP连 接请求,这相当于平均每秒1.74个连接请求。[Braun and Claffy 1994]提供了NCSA服务器 的详细情况:在1994年9月,平均每天有360 000个TCP连接请求(这个数据每6~8个星期 翻一番)。[Mogul 1995b]中描述了两个被作者称为“相对繁忙”的服务器,其中一个是 每天处理一百万个连接请求,而另一个则是在近 3个月时间内平均每天收到 40 000个连 接请求。1995年6月21日的《华尔街》杂志列出了最繁忙的 10个Web服务器,统计了从 1995年5月1日至7日之间对它们的点击次数,最高的达每周430万次 (www.netscape.com),最低的每天也有30万次。说了这么多,我们还是得提醒读者注

144

计计第二部分 TCP的其他应用

下载

意他们声称的Web服务器的性能和统计数据。如本章中我们所看到的,以下这些提法有 很大的区别:每天点击次数、每天连接数、每天客户数和每天会话数。另一个要搞清的 事实是一个组织的Web服务器程序运行在几台主机上,我们将在下一节讨论这种情况。 星期二 正午

星期三 正午

星期四 正午

星期五 正午

星期六 正午

星期日 正午

被动打开的 连接尝试到 达速率(次 数/小时)

系统启动后所经过的时间(分钟)

图14-2 每小时被动连接尝试次数

14.2 多个HTTP服务器 最简单的 H T T P服务器安排是一台主机上运行一个 H T T P服务器程序。有很多 We b站点是 这样做的,但也有两种较为普遍的变形: 1) 一台主机,多个服务器程序。本章中所分析的数据就来源于一台按这种方式运行的主机。 单个主机为多个组织提供HTTP服务。每一个组织的WWW域名(www.organization.com) 映射一个不同的 I P地址(都在同一子网上 ),单个以太网接口分别对每一个不同的 I P地址 赋予别名 (第6.6节中描述了Net / 3怎样允许单个网络接口上的多个 I P地址。在主I P地址 之后指派给网络接口的 I P地址均称为别名 )。这2 2个h t t p d服务器实例中的每一个都只 使用一个IP地址。当服务器程序启动时,它把本地的 IP地址绑定到它的监听 TCP插口上, 因此它只收到那些目的地址是它的 IP地址的连接。 2) 多台主机,每台均提供服务器程序的一个副本。这种技术用于繁忙的组织在多个主机 上分布输入负载 (即负载平衡 )。对应组织的 W W W域名: w w w . o r g a n i z a t i o n . c o m 指派了多个 I P地址,每一个提供 H T T P服务的主机有不同的 I P地址(卷1的第1 4章,D N S 中的多条 A记录)。这种组织的 D N S服务器响应 D N S客户请求时,必须能以不同的顺序 返回多个不同的 I P地址。 D N S中把这个称为循环使用 ( r o u n d - r o b i n ),例如,在通常的 DNS服务器程序当前版本中均支持这种功能。 例如,NCSA提供9个HTTP服务器。我们第一次查询它们的域名服务器时,返回如下:

下载

145

第14章 在HTTP服务器上找到的分组计计

(h o s t程序在卷1第14章有描述并用到了它。 )上例命令中的最后一个参数是我们要查询 的N C S A的D N S服务器的名字,使用该参数的原因是:在缺省情况下, h o s t程序将使 用本地DNS服务器,而本地域名服务器的缓存中可能有这 9个记录,而且可能每次返回 同一个IP地址。 第二次我们再运行上例中的程序时,得到了不同次序。

14.3 客户端SYN的到达间隔时间 下面我们来做一件有趣的事情:通过观察客户端 S Y N的到达,我们来看平均请求速率和 最大请求速率之间的区别。服务器应有能力应付峰值负载,而不是平均负载。 通过对SYN / FIN / RST进行24小时跟踪,我们可以分析客户端 SYN的到达时间间隔。在这 个2 4小时的跟踪期间共有 160 948个S Y N到达(在本章的开头我们曾提到,在这期间有 147 103 次连接尝试。这中间的不同是因为 S Y N的重传。注意到,大约有 1 0 %的S Y N须重传)。最小的 到达间隔时间是 0.1 ms,最大值是 44.5秒,平均值是538 ms,中间值是 222 ms。91%的到达间 隔时间小于 1.5秒。图14-3给出了到达间隔时间的柱状图。 这张图虽然有趣,但它不能提供峰值到达速率。为了测定峰值速率,我们把一天的 2 4小 时划分为1秒的时间间隔,并计算每秒的 SYN到达个数(实际测量了 86 622秒,比24小时长几分 钟)。图1 4 - 4列出了前 2 0个时间间隔内计数器的值。图中第二列给出了所有到达的 S Y N数,第 三列的计数器表示的是忽略重传后的 SYN到达数。在本节的最后,我们将用到第三列的数据。 例如,考虑所有到达的 SYN,一天有27 868秒 (一天中的32%)内没有SYN到达,22 471秒 (一天中的26%)内只有一次SYN到达,等等。一秒中最大的 SYN到达数为73次,一天中共有两 次这种情况。我们观察所有 S Y N到达次数超过 5 0次的“秒”,将发现它们都在一个 3分钟的时 间段内,这就是我们要找的峰值。

146

计计第二部分 TCP的其他应用

下载

计数

中值 均值

间隔时间(ms)

图14-3 客户端SYN的到达间隔时间分布 1秒钟内到达 的SYN个数

所有SYN 的累计数

新SYN的 累计数

图14-4 给定秒数内到达的 SYN数

图1 4 - 6是含有峰值的那个小时的情况。在这个图中,我们把每 3 0秒到达的 S Y N数取平均 值,y轴表示的是每秒到达的 S Y N数,平均到达速率约为每秒 3 . 5个,因此,这个小时处理的 到达的SYN几乎为平均值的两倍。 图14-7给出了包含峰值的那个 3分钟的更详细的情况。

下载

147

第14章 在HTTP服务器上找到的分组计计

在这3分钟中的变化有违人们的直觉,也表明某些客户有反常行为。如果我们检查这 3分 钟Tcpdump程序的输出会发现,问题果然来自一个特别的客户。在包含图 14-7最左边尖峰的30 秒中,那个客户在两个不同的端口发送 1 0 2 4个S Y N,平均每秒 3 0个。有少数几秒还在 6 0 ~ 6 5 次之间,再加上其他客户发送的,在图中的峰值就接近 7 0个。图 1 4 - 7中中间的尖峰也是由这 个客户引起的。 图14-5列出了与这个客户相关的部分 Tcmdump输出。

图14-5 违规的客户以高速率发送无效的 SYN

每秒到达的 SYN个数

时间(分钟)

图14-6 60分钟时间内每秒到达的 SYN数

148

计计第二部分 TCP的其他应用

下载

每秒到达的 SYN个数

时间(秒)

图14-7 3分钟的峰值时间内每秒到达的 SYN数

第一行是表示客户的 S Y N,第二行是服务器的 SYN / ACK 。第三行是从同一个客户的同 一个端口来的另一个 S Y N,但它的起始序列号是 1 3,比第一行的高。第四行是服务器发送一 个RST,第五行发送另一个 RST,第六行是客户发送的 RST。从第7行开始又重复这个情况。 为什么服务器要在一行内给客户发送两个 R S T (第五行和第六行 )?可能是由于设有打印出 来的某些数据段引起,因为遗憾的是 Tc p d u m p跟踪程序仅包含有 S Y N、F I N或R S T标志的报文 段。然而,这个客户显然违规了,在同一个端口如此高速率地发送 S Y N,并且从一个 S Y N到 下一个的序列号增加很小。 忽略重传的 SYN后的计算结果 我们需要忽略重传的 S Y N,重新分析客户 S Y N的到达间隔时间。因为从上面我们可以看 出,一个违反常规的客户就可以将数据拉出显著的峰值来。正如我们在本节的前面所提到的, 忽略重传可以减少约 1 0 %的S Y N。同样,通过考察有效的 S Y N,我们可以来分析连接到达服 务器的速率。所有到达的SYN均影响TCP/IP协议的处理 (因为每一个 SYN要经过设备驱动程序、 I P输入,然后才是 T C P输入),连接的到达速率影响 H T T P服务器 (服务器程序为每一个连接处 理新的客户请求 )。 在忽略重传 S Y N后,图1 4 - 3中的平均值由 538 ms增加至600 ms ,中间值由 222 ms增加至 251 ms 。在图 1 4 - 4中我们已给出每秒到达的 S Y N的分布图。峰值也像图 1 4 - 6中表示的那样, 不过要小得多。一天中到达的 SYN数最大的 3秒内分别为有19、21、33个SYN到达。这就给我 们一个范围,从每秒 4个(由到达时间间隔中值 251 ms得来)到3 3个S Y N,约为 8倍的关系。这 就意味着,当我们设计一个 Web服务器时,应使它能适应的峰值在这种平均值之上。在 14.5节 中我们将看到这种入连接请求队列中的峰值到达速率的作用。

149

第14章 在HTTP服务器上找到的分组计计

下载 14.4 RTT的测量

下一个我们感兴趣的内容是各种客户与服务器之间的往返时间。不幸的是,我们不能通 过在服务器上跟踪 S Y N / F I N / R S T来测量它。图 1 4 - 8描述了 T C P三次握手和用四个报文段来终 止一个连接的情况 (第一个FIN由服务器发出)。加粗的线表示在跟踪 SYN / FIN / RST时可以被 跟踪到。 客户端

服务器端

时延

RTT +时延

图14-8 TCP的三次握手和连接终止

在客户端可以测量 RT T,即发送 S Y N与接收服务器发来的 S Y N之间的时间间隔,但我们 的测量均在服务器端。我们可以通过测量服务器发送 F I N与接收客户发来的 F I N之间的时间间 隔来测量 RT T,但是这种测量包含一个不确定的时延:客户应用程序收到文件结束标志与关 闭连接之间的时间。 我们需要跟踪服务器上的所有分组来测量 RT T,因此我们使用前面提到的 2 . 5小时的跟踪, 并测量服务器发送 SYN / ACK 与收到客户的 A C K之间的时间间隔。客户发送的、用来确认 服务器 S Y N的A C K报文通常不会被延迟 (卷2第7 5 8页),因此这种测量不会包含一个时延的 A C K。这些报文通常都是尽可能的小 (服务器的 S Y N为4 4字节,通常包括一个服务器上使用 的M S S选项,客户的 A C K为4 0字节 ),因此在较慢的 S L I P或P P P链路上也不会产生明显的时 延。 在2 . 5小时内,进行了 19 195 次RT T的测量,涉及 8 1 0个不同的 I P地址。最小的 RT T等于 0(从同一主机的客户程序 ),最大的RTT是12.3秒,平均值是445 ms,中间值是 187 ms。图14-9 给出了 3秒以内的 RT T的分布。 98.5% 的RT T在 3秒以内。这些测量表明,由大西洋岸至太平 洋岸的RTT最好的情况在60 ms左右,典型情况下的 RTT值至少是这个值的三倍。

150

计计第二部分 TCP的其他应用

下载

为什么中间值(178 ms)比由大西洋岸至太平洋岸的 RTT(60 ms)小这么多?一种可 能是目前情况下,大量的用户仍使用拨号线访问 I n t e r n e t,即使是最快的调制解调器 (28 800 bps),也给每个 RT T增加100~200 ms的时延。另外一个原因是,有些客户实 现在处理三次握手的第三个报文段 (客户发送的、用来确认服务器 SYN的ACK报文)时 产生了时延。 中值

计数

均值

RTT(ms)

图14-9 客户往返时间的分布

14.5 用listen设置入连接队列的容量 为了准备一个接收入连接请求的插口,服务器通常执行下面的调用: listen(sockfd, 5);

第二个参数称为 b a c k l o g,指示 l i s t e n调用的入连接队列的容量。 B S D内核因为历史的 原因,通过在 < s y s / s o c k e t . h >头文件中定义 S O M A X C O N N常量,将入连接队列的容量的上 限设为5。如果应用程序指定了一个大于 5的值,内核将不作任何提示地把它置为 SOMAXCONN。 新的内核将 SOMAXCONN的值增加至 10或更高,增加的原因我们马上要介绍。 在插口数据结构中, s o _ q l i m i t值就等于 b a c k l o g参数值(卷2第3 6 5页)。当一个 T C P入连 接请求到达时 (客户端的 S Y N ),T C P程序执行 s o n e w c o n n调用,紧跟着进行如下测试 (卷2第 370页的第130~131行):

正如卷2中所描述的,把应用程序指定的 b a c k l o g乘以一个毫无根据的因子: 3 / 2,

151

第14章 在HTTP服务器上找到的分组计计

下载

确实能在内核指定 b a c k l o g为5时将等待的连接数增加至 8。这个毫无根据的因子只在 基于伯克利的实现中有作用 (卷1第195页)。 这个队列长度的上限限制以下两项的和: 1) 未完成连接队列 (s o _ q 0 l e n,一个SYN已经到达、但三次握手还没有完成的连接 )中的 项数。 2) 已完成连接队列 (s o _ q l e n,三次握手已完成、内核正等待进程执行 a c c e p t调用)中的 项数。 卷2第369页详细描述了当一个 TCP连接请求到达时,服务器端处理的步骤。 当已完成连接队列被填满 ( 例如,服务器进程或服务器主机非常繁忙时,进程执行 a c c e p t调用不够快,不能及时清空队列 ),或未完成连接队列被填满时,将达到 b a c k l o g的上 限。当服务器主机与客户主机的往返时间较长,而相比较而言,新的连接请求到达较快,那 么服务器就要面对上述的后一个问题,因为一个新的 S Y N占用队列中的一个记录项的时间是 一次往返时间。图 14-10描述了未完成连接队列的这部分时间。 客户端

服务器端

新连接停留在未完成连接 队列中的时间 = RTT

图14-10 用分组表示的未完成连接队列中一个记录项的占用时间。

为了检验未完成连接队列是否已满 (不是已完成连接队列 ),我们使用一个被修改过的 n e t s t a t程序,在最繁忙的 H T T P监听服务器上连续打印 s o _ q 0 l e n和s o _ q l e n这两个变量 的值。这个程序共运行了 2个小时,进行了 379 076次采样, 或者说约每 19 ms 进行一次采样。图 14-11给出了结果。 前面曾经提到,将 b a c k l o g设为5时,实际上可以有 8条

队列

未完成连接

长度

队列计数

已完成连接 队列计数

连接在排队。已完成连接队列绝大部分时间是空的,因为 当有连接进入这个队列时,只要服务器程序的 a c c e p t调 用一返回,这条连接便会马上从该队列中被取走。 当队列已满时, TCP丢弃入连接请求 (卷2第743页),并 且假定客户程序会发生超时,重传它的 S Y N,希望在几秒 钟以后在队列中找到空闲位置。但是 N e t / 3的内核并不提供 有关丢失的 S Y N的统计数据,因此系统管理员无法知道这 种情况发生的频度。我们把系统中这一段代码作了如下修 改:

图14-11 繁忙的HTTP服务器的 连接队列长度分布

152

计计第二部分 TCP的其他应用

下载

所作的修改就是增加了一个计数器。 图14-12中列出了为期 5天、一小时采集一次得到的该计数器的值。这个计数器是对主机上 的所有服务器程序进行统计的,但是我们假定所监视的主机主要是作为一台 Web服务器,实际 上绝大多数的溢出也是发生在 httpd的侦听插口上。从平均上来说,这台主机每分钟的呼入连 接请求溢出刚好超过三个(22 918次溢出除以7 139分钟),但是这里也有几个值得注意的连接丢失 数量的跳跃点。大约在第4500分钟 (星期五下午4∶00) 左右,一个小时内丢弃了1964个入连接请 求,约为每分钟32个(每两秒钟一个)。其他两次值得注意的跳跃发生在星期二下午的早些时候。 星期二 正午

星期三 正午

星期四 正午

星期五 正午

星期六 正午

星期日 正午

监听队 列溢出

系统启动后所经过的时间(分钟)

图14-12 服务器监听队列的溢出

必须增加支持繁忙服务器的内核的 b a c k l o g参数的上限,同时必须修改繁忙服务器应用程 序(例如h t t p d),使之设置一个更大的 b a c k l o g。例如, h t t p d的1 . 3版就存在这个问题,因为 它用下面的语句将 backlog强制设置为 5: 1.4版将backlog增加到了35,但这对于某些繁忙的服务器来说还是不够。 不同的厂商采用不同的方法来增加内核的 b a c k l o g的上限。例如, BSD / OS V2.0 内核将 s o m a x c o n n全局变量指定为 1 6,但系统管理员可以将它调整至更大的值。 Solaris 2.4允许系 统管理员使用 n d d程序改变 T C P参数: t c p _ c o n n _ r e q _ m a x,这个参数的默认值为 5,最

153

第14章 在HTTP服务器上找到的分组计计

下载

大可以到 3 2。Solaris 2.5将默认值增加到 3 2,而最大可以到 1 0 2 4。不幸的是,应用程序使用 l i s t e n调用时,没有一个简单的办法来确定当前操作系统内核所允许的队列最大值,所以最 好的办法是应用程序的代码中给这个参数赋一个很大的值 (因为使用 l i s t e n调用时不会因为 这个值太大而返回错误 ),或者让用户可以在命令行中指定这个参数。在 [Mogul 1995c] 提出 一种思想,认为在 l i s t e n调用中应忽略这个参数,而由系统内核直接把它设为最大值。 有些应用程序特意将 backlog参数设为一个较低的值来限制服务器的负载,因此, 在这种情况下我们要避免增加这些应用程序中的这个参数值。 SYN_RCVD错误 当我们检查 n e t s t a t的输出时发现,插口在 SYN_RCVD状态下保持了几分钟。 Net / 3用 它的连接建立定时器限制这个状态保持的时间为 7 5秒(卷2第6 6 4页和7 5 5页),为什么还会出现 这种现象?图14-13列出了Tcpdump的输出。

服务器每64秒重传ACK / SYN

图14-13 服务器插口在 SYN_RCVD状态被阻塞近 11分钟

客户发送的 S Y N在第一个报文段中到达,服务器的 SYN / ACK 在第二个报文段发出。同 时服务器设置连接建立定时器为 75秒,重传定时器为 6秒。上图的第 3行中,重传定时器溢出, 服务器重传 SYN / ACK。这正是我们所期望的。 第4行中可以看到客户端的响应,但这个响应是重传第 1行中的最初的那个 S Y N,而不是 我们所期望的对服务器 SYN的响应ACK。客户端好像是被中断了。服务器给出了正确的响应: 重传 S Y N / A C K 。收到第 4个报文段后,服务器端的 T C P程序将这条连接的保活定时器 (keepalive timer)的超时间隔设为 2小时(卷2第7 4 5页)。但是,保活定时器与连接建立定时器使 用连接控制块中的同一个计数器 (卷2的图2 5 - 2 ),因此,程序清除该计数器的当前值 6 9秒,而

154

计计第二部分 TCP的其他应用

下载

把它设成 2小时。通常客户端用一个响应服务器 S Y N的A C K来完成三次握手,建立 T C P连接。 当这个ACK报文被处理后,保活定时器被设为 2小时,重传定时器则被关闭。 第6、7、8 行的情况类似。服务器的重传定时器在 24秒后超时,重传它的 SYN / ACK,但 是客户端的响应 (它又一次重传了最初的SYN)不正确,因此服务器再次正确地重传 SYN / ACK。 在第9行可以看到,服务器的重传定时器在 48秒后再次超时,同样重传它的 SYN / ACK。这样, 重传定时器到了它的最大值: 6 4秒,在连接被丢弃之前共发生了 1 2次重传( 1 2是卷2第6 7 4页中 的常量TCP_MAXRXTSHIFT的值)。 因为保活定时器、连接建立定时器共用 TCPT_KEEP计数器,所以修补这个故障的方法是: 连接还没有完全建立好时 (卷2第7 4 5页),不将保活定时器的超时间隔设为 2小时。当然,作了 上述修改后,就要求当连接转移到已建立的状态后把保活定时器设置成初始值 2小时。

14.6 客户端的 SYN选项 我们在为期 2 4小时的跟踪中收集了所有的 S Y N报文段,从中我们可以看到伴随 S Y N的一 些不同的参数和选项。 客户端口号 基于伯克利的系统分配的客户临时使用的端口号的范围是 1 0 2 4 ~ 5 0 0 0 (卷2第5 8 8页)。正如 我们所期望的那样,超过 160 000个客户中有 9 3 . 5 %使用的端口在这个范围内。有 1 4个客户连 接请求使用的端口号小于 1024(端口号小于 1024的在Net / 3中通常作为保留端口 ),其余的6.5% 都在5001~65535之间。有些系统,特别是 Solaris 2.x,分配的客户端口号都大于 32768。

计数(对 数座标)

客户端口号

图14-14 客户端口号的范围

155

第14章 在HTTP服务器上找到的分组计计

下载

图1 4 - 1 4是一个客户使用端口号的分布图,每 1 0 0 0个端口(如1 0 0 1 ~ 2 0 0 0、2 0 0 1 ~ 3 0 0 0 )作为 一个统计范围。请注意 y轴是对数座标。同时我们也看到,绝大部分客户使用的端口在 1024~5000之间,而且 2 / 3 的端口在1024~2000之间。 最大报文长度 可以基于选用网络的MTU(见前面我们对图 10-9的讨论)或直接使用固定值(非本地的同层之 间使用5 1 2或5 3 6,较老的 B S D系统使1 0 2 4,等等)来设置M S S。RFC 1191[Mogul and Deering 1 9 9 0 ]列出了典型的 1 6种不同的 M T U。因此,在实验中我们希望能找到 We b客户所发出的不同 的MSS的值有十几种或更多。事实上我们找到了 117种不同的值,范围在128~17 520之间。 图1 4 - 1 5列出了最常见的 1 3种客户通告的 M S S的值。这 5 0 7 1个连到 We b服务器的客户占总 客户数5386的94%。第一栏中标记“ none”的意思是客户的 SYN中没有通告 MSS。 MSS 无 212 216 256 408 472 512 536 966 1024 1396 1440 1460





703 53 47 516 24 21 465 1097 123 31 117 248 1626





RFC 1122指出如不使用选项,则设为536 256-40 MTU为296的PPP或SLIP链路 512-40 非本地主机的常用默认值 非本地主机的常用默认值 ARPANET MTU (1006) - 40 老版本BSD中本地主机的默认值 以太网MTU (1500) - 40

5071

图14-15 客户所通告的 MSS值的分布

初始窗口宽度通告

窗 口

计 数





客户的 S Y N中也包含了客户端的初始窗口宽度 的通告。这里共有 11 7种不同的值,跨越了整个允许 值的范围: 0 ~ 65 5 3 5。图1 4 - 1 6列出了最常见的 1 4种 值的使用统计数。这 4 9 9 0个值占 5 3 8 6个不同客户的 9 3 %。有些值有特殊的意义,但有些值让人感到迷 惑,例如: 22 099。

接收缓存大小的默认值 小于常用的默认值 (以太网上常用)

好像有些 P C平台上的 We b浏览器允许用户 指定 M S S和初始窗口尺寸。这就是我们看到一 些奇怪值的一个原因,用户设置这些值时可能 并没有理解它们的作用。 不管怎么说,我们共找到 11 7种不同的 M S S 值和11 7种不同的初始窗口尺寸,并检查了 2 6 7

图14-16 客户所通告的初始窗口 尺寸的分布

种不同的 M S S和初始窗口尺寸的组合,并没有发现它们之间有明显的相关性。

156

计计第二部分 TCP的其他应用

下载

窗口比例和时戳选项 RFC 1323指定了窗口比例和时戳选项 (图2-1)。在5 386个不同的客户中共有 78个只发送了 窗口比例选项, 2 3个既发送了窗口比例选项,又发送了时戳选项,没有一个只发送时戳选项。 在所有的窗口比例选项中都通告了偏移因子 0 (意味着比例因子是 1,或就是通告的 T C P窗口的 宽度)。 利用SYN发送数据 五个客户在发送的 S Y N中捎带数据,但这些 S Y N并不包含新的 T / TCP的选项。检查这些 分组,发现这些连接都是同一个模式。客户发送一个普通的 S Y N,不含任何数据。三次握手 的第二个报文段是服务器的响应,但是响应好像丢失了,因此客户重传了它的 S Y N。但是每 一个客户重传的 SYN中都包含有数据 (在200~300字节之间,一个常见的 HTTP客户请求)。 路径MTU发现 在RFC 1191[Mogul and Deering 1990]和卷1的第2 4 . 2节均描述了路径 M T U发现。通过检 查客户所发送的 S Y N报文段中的 DF 位(不分段),可以判断客户是否支持这个选项。在我们的 例子中,共有679个客户(占12.6%)支持路径MTU发现。 客户初始序列号 有大量的客户(超过10%)使用0作为初始序列号,用0作为初始序列号明显违反了 TCP规范。 这些客户的 TCP / IP 实现中对所有的主动连接都使用 0作为初始序列号,在跟踪中我们发现, 同一个客户在几秒钟内在不同端口发出的多个连接请求均使用 0作为初始序列号。图 14-19 列 出一个这样的客户。

14.7 客户端的 SYN重传 伯克利派生系统是在初始 S Y N发出6秒后重传 S Y N (如果需要 ),如果在 2 4秒内仍收不到响 应,就再重传 (卷2第6 6 4页)。因为在 2 4小时的跟踪中我们记录下了所有的 S Y N报文(包括那些 没有被网络和 Tc p d u m p丢弃的 ),所以我们能从中看出客户重传 S Y N有多频繁和每一次重传之 间的时间。 在这2 4小时的跟踪中共有 160 948个S Y N到达(见第1 4 . 3节),其中17 680个(占11 % )是重复 的(真正的重传数量要小一些,因为如果指定 I P地址和端口号的连续两个 S Y N报文之间的时间 非常长,那么第二个 S Y N就不是重传,而是后来发起的另一条连接。我们没有试图去减掉这 部分重传,因为它只占 11%中的一小部分)。 S Y N只重传一次 (最通常的情况 ),重传时间典型值是在发出初始 S Y N以后3、4或5秒。如 果要重传多次,许多客户使用 BSD的算法:第一次重传是在 6秒以后,接着的下一次是 24秒后。 我们用{6, 24}来表示这种序列。其他观察到的序列是: • {3, 6, 12, 14}; • {5, 10, 20, 40, 60, 60}; • {4, 4, 4, 4}(违反了RFC 1 122中指数增长的要求 );

下载

157

第14章 在HTTP服务器上找到的分组计计

• {0.7, 1.3}(20跳以外的主机过分频繁的重传;实际上,在这个主机上有 2 0个连接重传 SYN,所有的重传间隔都小于 500 ms !); • {3, 6.5, 13, 26, 3, 6.5, 13, 26, 3, 6.5, 13, 26}(这个主机每 4次重传后重新按指数退避方法 重传); • {2.75, 5.5, 11, 22, 44}; • {21, 17, 106}; • {5, 0.1, 0.2, 0.4, 0.8, 1.4, 3.2, 6.4}(第一次超时后太主动地重传 ); • {0.4, 0.9, 2, 4}(另一个19跳以外的主机过分频繁的重传 ); • {3, 18, 168, 120, 120, 240}。 就像我们所看到的,上面有些奇怪的序列。有些 S Y N被重传很多次,可能是因为发送它 的客户有路由问题:它能发送数据到服务器,但收不到服务器的任何响应。同样,也有可能 是前一个连接请求的新的实例 (卷2第7 6 5 ~ 7 6 6页描述了 B S D服务器是如何处理这种情况的:当 新的SYN的序列号比处在 TIME_WAIT状态的连接的最后一个 SYN的序列号还大时,服务器将 接受这个新的连接请求 ),但是这个时间 (例如,明显的是 3秒或6秒的倍数 )又让人看起来不太 像。

14.8 域名 在2 4小时期间共有 5 3 8 6个不同I P地址的客户连接到 We b服务器。因为 Tc p d u m p (带- w标志) 只记录带IP地址的分组首部,因此我们必须再来找相应的域名。 我们第一轮用 D N S查找名字,试图把这些 I P地址映射到它们的域名,只找到了 4 0 5 2个(占 7 5 % )。然后我们在 D N S上运行了一天,查找剩下的 1 3 3 4个I P地址,又找到了 6 2个域名。这意 味着有23.6%的客户的 IP地址到域名的逆映射不正确 (卷1的第14.5节讨论了这些指针查询 )。虽 然这些客户中的大部分都是通过拨号上网,而且大部分时间是离线的,但他们也应该有他们 的名字服务器来提供名字服务,而且名字服务器应是任何时候都接入 Internet的。 在D N S查找名字失败后,我们马上对剩下的 1 2 7 2个客户运行 P i n g程序,验证这些没有地 址-名字映射的客户是不是会临时不可达。结果是 Ping测试成功了 520台主机(占41%)。 分析这些没有映射到一个域名的 I P地址的顶级域名的分布,发现它们来自 5 7个不同的顶 级域名。其中 5 0个是除美国以外其他国家的两个字母的域名,这也说明用“世界范围内 (world wide)”这个词来形容 Web是恰当的。

14.9 超时的持续探测 Net / 3从没有放弃过发送持续探测 (persist probe)。那就是,当Net / 3收到对等端发送的宽 度为0的窗口通告后,它不管是否曾经收到过对方的任何报文,都不断地发送持续探测。当对 等端完全消失时 (例如,在 S L I P或P P P连接时挂断电话 ),这样做就会产生问题。回忆一下卷 2 第7 2 3页提到的,当客户端消失时,有些中间路由器会发送一个主机不可达错误的 I C M P报文, 一旦连接建立, TCP将忽略这些错误。 如果连接没有被丢弃, T C P会每 6 0秒往已经消失了的主机发送一个持续探测报文 (浪费 Internet资源),同时每一条连接还继续占用主机上的内存和 TCP访问控制块。 图14-17列出的4.4BSD-Lite2中的代码修补了这个问题,用它来替代卷 2第662页的代码。

158

计计第二部分 TCP的其他应用

下载

图14-17 正确处理持续超时的代码

图中的i f语句是新代码。变量 t c p _ m a x p e r s i s t i d l e是一个新定义的变量,它的初值 是 T C P T V _ K E E P _ I D L E ( 14 4 0 0个 500 ms 的时钟嘀哒 (clock tick) ,或 2小时 )。变量 t c p _ t o t b a c k o f f也是一个新变量,它的值是 511,是t c p _ b a c k o f f数组(卷2第669页)中所 有元素之和。最后, t c p s _ p e r s i s t d r o p是t c p s t a t结构(卷2第6 3 8页)中的一个新的计数 器,它统计被丢弃的连接。 T C P _ M A X R X T S H I F T指定了TCP在等待ACK时的最大重传次数,它的值是 12。如果在2小 时或对等端的当前 RTO的5 11倍(取两个中较小的 )内没有收到对方任何报文,在 1 2次重传后将 丢弃连接。例如, RTO是2 . 5秒( 5个时钟嘀哒,一个合理的值 ),在2 2分钟(即2 6 4 0个时钟嘀哒 ) 后,OR测试条件中的后一个将引起丢弃连接,因为 2640大于2555(即5×511)。 代码中的“Hack”注释不是必需的。RFC 1 122中规定:即使提供的窗口宽度为 0, 但只要接收 TCP继续给探测报文发送响应, TCP就必须无限期地保持一个连接在打开 状态。如果长时间内探测没有响应,最好还是丢弃连接。 在系统中加入的代码可以看出这种情况的发生有多频繁。图 1 4 - 1 8给出了为期 5天的这个 新计数器的值。这个系统平均每天丢弃 90个连接,每小时约 4个。 让我们详细看一下其中一条连接。图 14-19给出了Tcpdump分组跟踪的详细情况。 第1~3行中除了初始序列号错误 (0)、MSS值有些奇怪以外,是比较常见的 TCP三次握手过 程。在第 4行,客户发送了一个 182字节的请求报文。第 5行中服务器对请求进行了响应,在响 应报文中包含了应答数据的前 512字节,第6行是包含后 512字节数据的应答。 在第7行客户发送了一个 F I N,第8行中服务器对 F I N进行了响应: A C K,紧接着在第 9行 服务器发送了 1024字节的应答。客户在第 10行确认了服务器的前 512字节的应答,并重传它的 FIN。第11行、第12行是服务器的后 1024字节的应答。第 13~15行中延续了这种情况。 注意,当服务器发送数据时,客户在第 7、1 0、1 3和1 6行通告了窗口的减小,直到第 1 7行

159

第14章 在HTTP服务器上找到的分组计计

下载 星期二 正午

星期三 正午

星期四 正午

星期五 正午

持续发生的 超时次数

系统启动后所经过的时间(分钟)

图14-18 持续探测超时后丢弃连接的数量

图14-19 Tcpdump对持续超时的跟踪

星期六 正午

星期日 正午

160

计计第二部分 TCP的其他应用

下载

持续探测连续进行

图14-19 (续)

窗口变为 0。到17行为止,客户已接收了从服务器发来的 4096字节的数据,4096字节的接收缓 存已满了,所以客户通告窗口为 0。客户端应用程序没有从接收缓存区中读任何数据。 第1 8行中服务器发出了它的第一个持续探测报文,它是收到客户窗口为 0的通告约 5秒钟 后发出的。持续探测报文的间隔时间按照卷 2图2 5 - 1 4的典型情况进行。在第 1 7行和1 8行之间 的时间内,客户离开了 I n t e r n e t。在接下来的 2小时内,服务器共发送了 1 2 4个持续探测报文, 最后服务器丢弃了连接,并在第 1 4 1行发送了一个 R S T报文( R S T是由t c p _ d r o p调用发送的, 见卷2第713页)。 为什么这个例子中服务器发送持续探测报文时间长达 2小时,为什么没有按我们 在本节前面讨论过的 4 . 4 B S D - L i t e 2源代码中的 O R测试的后半个条件来执行?我们所 监视的系统使用的是 BSD / OS V2.0 ,其中持续超时测试代码只测试 t _ i d l e是否大 于或等于 t c p _ m a x p e r s i s t i d l e。O R条件测试的后半部分是在 4 . 4 B S D - L i t e 2中加 入的新代码。在上面的例子中,我们也可以看出加入这段代码的原因:当通信的另 一端显然已离开了 Internet时,就不再需要进行 2小时的持续探测了。 我们在上面提到系统平均每天有 9 0个这种持续超时的连接,这就意味着如果系统内核不 终止这些连接, 4天以后系统中将有 360个这样的“保留”连接,这将引起每秒发送 6个无用的 TCP报文。另外,因为 HTTP服务器还将试图给这些客户发送数据,所以还会产生一些 mbuf在 连接发送等待队列中等待发送。 [Mogul 1995a]中提到:“当客户过早地终止 TCP连接时,会引 发服务器程序中隐藏的故障,从而真正地影响性能”。 图 1 4 - 1 9 的第 7 行中,服务器收到客户发来的一个

F I N 。这使服务器把连接置为

CLOSE_WAIT状态。但在跟踪过程中,有时服务器调用 close调用,而转至LAST_ACK状态, 我们并不能从 Tc p d u m p的输出中区别出来。的确,绝大多数这种连接均在 L A S T _ A C K状态持 续发送探测报文。 在1 9 9 5年早期,最初开始对插口阻塞在 L A S T _ A C K状态的问题进行讨论时,有 人建议设置 S O _ K E E P A L I V E选项来检测客户退出的时间,然后终止连接 (卷1的第2 3 章讨论了这个选项是怎么工作的,卷 2的第2 5 . 6节提供了使用它的细节 )。不幸的是, 这样做还是解决不了问题。注意卷 2第6 6 3页, K E E P A L I V E选项在 F I N _ W A I T _ 1、 F I N _ W A I T _ 2、C L O S I N G和L A S T _ A C K状态并不终止连接。据报导,有些厂商对此 作了改变。

14.10 T/TCP路由表大小的模拟 实现T / T C P的主机为每一个与它通信的主机保留一条路由表的表项 (第6章)。如今的大部 分主机维护的路由表只有一条缺省路由和少数显式指定的路由,所以实现 T / T C P可能要建立 一个比通常使用的路由表大得多的路由表。我们将使用 H T T P服务器发出的数据来模拟 T / T C P 的路由表,看它的空间大小是怎么变化的。

161

第14章 在HTTP服务器上找到的分组计计

下载

我们只进行简单的模拟。我们通过对这个主机进行 2 4小时的分组跟踪来建立一个路由表, 其中包含每一个与 H T T P服务器通信的主机 (共有5 3 8 6个不同的 I P地址)的路由。路由表保留的 每一条路由信息都设有最后一次更新后的失效时间。我们把失效时间分别设为 3 0分钟、 6 0分 钟和2小时来进行仿真。每 1 0分钟扫描一次路由表,把所有超过失效时间的路由信息删除 (模 仿6 . 1 0节中的 i n _ r t q t i m o的动作),用一个计数器来记录表中剩下的条目。这些计数器都列 在图14-20中。 在卷2的习题18.2中我们注意到,每一条 Net / 3的路由表表项要占用 152字节。在T/TCP中, 这个数字变成了 168字节,增加的16字节是r t _ m e t r i c s结构,用作TAO缓存,不过在BSD的 内存分配策略中,实际分配的是 2 5 6字节。如果取最大的失效时间: 2小时,路由表的表项数 将达到1 000 个,即需要 256 000字节。将失效时间减半,可以使内存的占用量减小一半。 当有5 386个不同的 I P地址访问这个服务器时,如果失效时间设为 3 0分钟,路由表最大可 到约300条表项。这样的空间对路由表而言并不是很不切实际的。

2小时失效

路由表中的 记录项数 60分钟失效

30分钟失效

正午

正午 一天中的各个整点

图14-20 T/TCP路由表模拟:每次不同的表项数

路由表的重用 图1 4 - 2 0告诉我们当使用不同的失效时间时路由表会变得多大。但是另一个我们关心的问 题是:路由表中保留的这些路由信息中有多少被重用。没有必要保留那些很少用第二次的路 由信息。 为了考察这一点,我们检查从 2 4小时跟踪中得来的 686 755个分组,并从中找寻在客户发 出最后一个分组至少 1 0分钟以后发出的 S Y N。图1 4 - 2 1给出了主机数与静默时间 (分钟)的相对 关系图。例如,在 5 3 8 6个不同的客户所在的主机中 ,有6 8 3台主机在 1 0分钟或超过 1 0分钟的静

162

计计第二部分 TCP的其他应用

下载

默时间后发送了另一个 S Y N。在11分钟或超过 11分钟的静默时间后发送了另一个 S Y N的主机 减少至669台,静默时间超过 120分钟发送了另一个 SYN的主机为367台。

主机数

不活跃时间(分钟)

图14-21 在一段时间的静默后发送一个 SYN的主机数

如果我们留意一下静默一段时间后又重现的主机,它们的 I P地址所对应的主机名都是一 些w w w p r o x y 1、w e b g a t e 1、p r o x y、g a t e w a y和类似于这样的名字,也就是说,多数是 一些组织的代理服务器。

14.11 mbuf的交互 在用Tc p d u m p对H T T P数据交换进行监视时,我们发现了一个有趣的现象。尽管 M S S的值 大于2 0 8 (通常都是这样 ),可是当应用程序写数据的字节数在 1 0 1 ~ 2 0 8之间时, 4 . 4 B S D系统还 是把它分成了两个 m b u f (一个用来存放前 1 0 0字节,另一个存放剩余的 1 ~ 1 0 8字节),这样成了 两个TCP报文段。这个反常现象的原因是: s o s e n d函数(卷2第399页和第400页)。因为TCP不 是一个原子协议,所以每填充一个 mbuf,协议的输出函数就被调用一次。 使事情变得更糟的是:因为现在客户的请求被分成多个报文,慢启动现象就产生了。客 户只有在收到服务器对第一个报文的确认后才发送第二个报文,这样就增加了一个 RTT时延。 大量的 H T T P请求的长度在 1 0 1 ~ 2 0 8字节间。的确,在 1 3 . 4节中我们讨论的 1 7个请求的长 度均在152~197字节间。这是因为客户的请求基本上都是一个固定的格式,从一个请求转换到 另一个请求只是改变 URL。 要修补这个问题很简单 (如果你有系统内核的源代码 )。常量M I N C L S I Z E的值应从208改为 101。这就使得要写 101~208字节时,不再使用两个 mbuf,而是把超过 100字节的数据放入一个 或多个mbuf串中。作了这个改变后,还可以摆脱在图 A-6和A-7中200字节数据附近的尖峰现象。

163

第14章 在HTTP服务器上找到的分组计计

下载

图1 4 - 2 2 (后面给出 )中Tc p d u m p跟踪的客户就已经作了这个修补。如果没有进行这个修补, 客户的第一个报文将只含有 1 0 0字节,客户将为了等待这个报文的确认而花去一个 RT T (慢起 动),然后客户才发送剩余的 5 2字节。只有在收到剩余的字节后,服务器才会发出第一个应答 报文。 这里有一些其他的修补方法。第一种方法是:一个 m b u f的大小可以由 1 2 8字节增 加至2 5 6字节。有些基于伯克利源码的系统已经作了这种修改 (例如,A I X )。第二种: 对sosend作修改,当使用多个 mbuf时,避免多次调用 TCP输出。

14.12 TCP的PCB高速缓存和首部预测 当N e t / 3收到一个报文时,它把指针保存在相应的

Internet PCB( 指向 i n p c b 结构的

t c p _ l a s t _ i n p c b指针,见卷2图2 8 - 5 )中,并希望下个到达的报文还是属于同一条连接。这 样做避免了查找 T C P的P C B链表,而这样的查找的代价是昂贵的。每一次缓存比较失败,计 数器t c p s _ p c b c a c h e m i s s就增加。在卷2图24-5的抽样统计中缓存的命中率接近 80%,但被 统计的系统不是一个 HTTP服务器而是一个普通的分时系统。 当给定连接所接收的下一个报文不是下一个希望的 A C K (在数据发送方 ),就是下一个希 望的数据报文(在数据接收方)时,TCP的输入也执行一些首部预测 (卷2第28.4节)。 在本章讨论的HTTP服务器上,我们观察到了下面的一些百分数: • 20% 的PCB缓存命中率 (18~20%); • 对下一个ACK报文的15%的首部预测率(14~15%); • 对下一个数据报文的 30%的首部预测率(20~35%)。 所有这些比率都是比较低的。两天中每个小时对这些百分数进行测量,发现它们的变化都很 小:上面括号中列出了高低值的范围。 作为一个在同一时刻有大量不同客户使用 T C P的H T T P服务器, P C B缓存命中率比较低并 不让人感到奇怪。这种低的比率与 HTTP是一个传输协议相适应, [McKenney and Dove 1992] 中表明了Net / 3的PCB缓存机制对事务协议不太有效。 通常一个HTTP服务器发送的数据报文比它接收的要多。图 14-22是图13-5中客户的第一个 H T T P请求的时间线 (客户端口号为 111 4 )。客户的请求是第 4段报文,服务器的应答是第 5、6、 8、9、11、1 3和1 4段报文。这里,服务器只有一个可能的数据报文预测,那就是第 4段报文。 服务器的下一个可能的 ACK报文预测是第 7、10、12、15和16段报文(当第3段报文到达时,连 接还没有完全建立好,第 1 7段报文中 F I N标志使程序不再对首部进行预测 )。这些 A C K报文究 竟会不会限制依赖于窗口通告的首部预测,取决于客户端发送 A C K时它读取了多少服务器返 回的数据。例如在第 7段报文, T C P确认了收到 1 0 2 4字节数据,但是 H T T P客户应用程序只从 插口缓存中读取了 260字节数据(1024-8192+7428)。 当T C P的200 ms 定时器超时时,会发送一个延迟的 A C K,它带有一个可笑的窗 口通告。同样都是延迟的 A C K ,第 7段与第 1 2段报文时间上的差距是 799 ms :4个 TCP 的200 ms时钟中断。这就暗示它们都是延迟的 ACK,发送它们是因为时钟中断, 而不是因为进程执行了新的、从插口缓存读数据的调用。第 1 0段报文看上去好像也 是延迟的ACK,因为它与第7段报文之间的时间为 603 ms。

164

计计第二部分 TCP的其他应用

下载

带有小的窗口通告的 A C K报文的发送也会使首部预测失效,因为只有当窗口通告的值等 于当前发送窗口的值时,才会执行首部预测。 客户端,端口号1114

服务器端,端口号80

图14-22 HTTP客户-服务器事务

总的来说,我们对 H T T P服务器上首部预测成功率低并不感到惊讶。在 T C P连接上交换大 量的数据时首部预测工作得最好。因为系统内核首部预测的统计是计算所有的 TCP连接,我们 只能猜测这台主机上对下一个数据报文的首部预测的高百分比 (与对下一个ACK的预测相比)是 来自非常长时间的 NNTP连接(图15-3),这种NNTP连接平均每条TCP连接接收约1300万字节。 慢启动错误 注意到图 1 4 - 2 2中当服务器发送它的应答时没有像预期的那样发生慢启动。我们预期的是 服务器先发送 5 1 2字节的报文,等待客户的 A C K,然后发送下一个 5 1 2字节的报文。而服务器

下载

165

第14章 在HTTP服务器上找到的分组计计

不是这样做的,它没有等待客户的 A C K而是立即发送了两个 5 1 2字节的报文 (第5段和第 6段报 文)。事实上这种现象在绝大多数伯克利的派生系统是很少见和异常的,因为许多应用程序都 是由客户发送大多数数据给服务器。甚至对于 F T P也是这样,例如,从一个 F T P服务器上获取 一个文件时, F T P服务器打开一个数据传输连接,实际上成为了数据传输的客户端 (卷1图2 7 - 7 给出了一个这样的例子 )。 这个错误出在 t c p _ i n p u t函数上。新的连接启动时拥塞窗口为一个报文。当客户完成连 接建立后 (卷2图2 8 - 2 1 ),代码执行转移到 s t e p 6,跳过了 A C K的处理。当客户发送第一个数 据段时,它的拥塞窗口是一个报文,这是不正确的。但是,当服务器完成连接建立后 (卷2图 2 9 - 2 ),紧接着执行处理 A C K的代码,收到 A C K后拥塞窗口增加 1个报文 (卷2图2 9 - 7 )。这就是 为什么服务器一开始就连续发送两个报文。解决这个问题的办法是把图 11-16中的代码加进去, 不管这样是不是支持 T/TCP。 当服务器在第 7段报文中收到 A C K时,它的拥塞窗口增加至 3个报文段,但接着服务器却 只发送2个报文段(第8和第9段报文)。我们不能从图 14-22中找出原因来,因为我们只在连接的 一端记录报文 (在客户端运行 Tcpdump),第10段和第11段报文可能在网络中客户端与服务器端 中间的什么地方。如果真是这样,那么服务器就的确像我们所预期的那样:拥塞窗口的宽度 为3个报文段。 这些报文交互的线索是从对分组跟踪得来的 RT T值。在客户端测量出来的第 1段与第 2段 报文之间的 RT T是441 ms,第4段与第5段之间是 521 ms,第7段与第8段之间是 432 ms。这些 都是可能的值,在客户端使用 P i n g程序(指定分组长度为 3 0 0字节)也表明到这个服务器之间的 RTT大约是461 ms。但第10段与第11段报文之间的RTT非常小,只有107 ms。

14.13 小结 通过运行一个繁忙的 Web服务器来重点考察 TCP / IP的实现。我们可以看到,服务器会收 到Internet上各种各样的客户发来的一些奇怪的分组。 在本章中,我们对一个繁忙的 We b服务器的分组进行跟踪,并对跟踪结果进行分析,着 眼于各种实现中的特性。我们得到了如下结论: • 客户端SYN的峰值到达速率约为平均到达速率的 8倍(忽略不正常的客户 )。 • 客户到服务器之间的 RTT平均值是445 ms,中间值是 187 ms。 • 采用典型的 b a c k l o g极限值5或1 0时,未完成连接队列很容易溢出。这个问题不是因为服 务器进程太忙,而是因为客户的 S Y N至少要在队列中停留一个 RT T时间。一个繁忙的 We b服务器的这个队列需要比这大得多的容量。同时内核也提供一个计数器对这个队列 的溢出次数进行统计,这样系统管理员就可以知道这种溢出发生的频度。 • 对阻塞在LAST_ACK状态的连接不断进行持续探测,因为这种情况经常出现,所以系统 必须提供一种办法能让这种连接超时。 • 许多伯克利派生系统在客户请求报文的长度为 1 0 1 ~ 2 0 8字节时 (通常许多客户均为这样 ) 使用mubf的效率比较低。 • 许多伯克利派生系统的实现提供 TCP PCB高速缓存,同时绝大多数的系统也提供首部预 测,但是它们对一个繁忙的 Web服务器的帮助却很小。 [Mogul 1995d]中提供了对另一个繁忙的 Web服务器进行了相似的分析。

下载

第15章 NNTP:网络新闻传送协议 15.1 概述 NNTP,即网络新闻传送协议,在协作的主机之间发布新闻文章。 NNTP是一个使用TCP的 应用协议,RFC 977[Kantor and Lapsley 1986]对它进行了详细描述。[Barber 1995]对它的一般实 现进行了扩展。RFC 1 036 [Horton and Adams 1987]对新闻文章中的各种首部字段进行了说明。 网络新闻起源于 ARPANET上的邮件列表,随后发展成为 U s e n e t新闻系统。邮件列表今天 还很流行,但如果纯粹从容量来看,网络新闻在过去的十年有很大的增长。从图 1 3 - 1中可以 看出, N N T P有跟电子邮件一样多的分组数。 [Paxson 1994a]中提到,从 1 9 8 4年以来网络新闻 的流量保持了每年约 75%的增长。 U s e n e t不是一个物理的网络,而是建立在多个不同类型物理网络上的一个逻辑网。多年 以前,在 U s e n e t上流行的交换网络新闻的手段是通过电话线拨号 (为了省钱通常在几个小时以 后),而在今天, I n t e r n e t是绝大多数新闻发布的主要渠道。 [Salus 1995]中的第 1 5章详细讲述 了Usenet的历史。 图1 5 - 1是一个典型的新闻系统的概况。一台作为组织的新闻服务器的主机在磁盘上保留 主机 (新闻服务器)

主机 (新闻服务器)

主机 (新闻服务器)

新闻文稿存 储在磁盘上

新闻客户

主机

新闻客户

主机

新闻客户

新闻客户

新闻机构的网络

图15-1 典型的新闻系统

新闻客户

下载

167

第15章 NNTP:网络新闻传送协议计计

了所有新闻文章。这个新闻服务器通过 I n t e r n e t与其他的新闻服务器通信,互相供给新闻。新 闻服务器之间的通信使用 N N T P协议。新闻服务器有各种不同的实现, I N N ( I n t e r N e t N e w s )正 成为Unix平台上最流行的新闻服务器程序。 组织中的其他主机通过访问新闻服务器来阅读新闻文章和选择新闻组粘贴新闻。我们把 这些客户程序称为“新闻客户”。这些客户程序与新闻服务器之间的通信也采用 N N T P协议。 另外,如果新闻客户与新闻服务器在同一主机上,客户也用 NNTP阅读和粘贴新闻。 不同的客户操作系统平台上有十几种新闻阅读器 (客户程序)。最原始的Unix新闻客户程序 是R e a d n e w s,接着是 R n和它的变种: R r n是一个支持远程操作 ( r e m o t e )的版本,它允许客户和 服务器在不同的主机上; Tr n的意思是“线索 ( t h r e a d e d ) R n”,它可以使用多条线索在一个新闻 组中讨论; X r n是R n的X 11窗口系统的版本。 G N U S是一个内含 E m a c s编辑器的流行的新闻阅 读器。也有一些通用的 We b浏览器,例如 N e t s c a p e,在浏览器中内置访问新闻服务器的接口, 这样就不需要单独的新闻客户程序。就像不同的电子邮件程序提供许多不同的用户接口一样, 每一种不同的新闻客户程序也提供不同的用户接口。 不管使用哪种客户程序,对新闻服务器来说,这些不同的新闻客户程序的相同点是:都 使用NNTP协议,这正是我们在本章要讨论的。

15.2 NNTP协议 N N T P使用 T C P协议,知名的 N N T P服务的端口号是 11 9。N N T P也像其他的 I n t e r n e t应用 (HTTP,FTP,SMTP,等等)一样,客户发送ASCII命令给服务器,服务器返回数值的响应码, 后面跟着可选的 ASCII数据(取决于客户的命令 )。命令和响应都以回车加换行结束。 考察这种协议最简单的办法就是用 Te l n e t程序来连接一台主机上的 N N T P端口,当然,这 台主机运行了 NNTP服务器程序。但是,通常我们必须从一台能被服务器主机识别的主机上运 行客户程序,典型的情况就是从同一组织网络中的一台主机。例如,我们通过 I n t e r n e t从其他 网络的主机上来登录本地的新闻服务器,会收到如下的错误信息: vangogh.cs.berkley.edu % telnet noao.edu nntp Trying 140.252.1.54... Connected to noao.edu. Escape character is '^]'. 502 You have no permission to talk. Goodbye. Connection closed by foreign host.

由Telnet客户程序输出 由Telnet客户程序输出 由Telnet客户程序输出 由Telnet客户程序输出

输出的第 4行是由N N T P服务器输出的,响应码是 5 0 2。当T C P连接被建立后, N N T P服务 器收到客户的IP地址,将它与配置中允许的 IP地址进行比较。 在下面的例子中,我们从一台“本地”主机连接到新闻服务器。

这次从服务器来的响应码为 200(命令OK),响应行中余下的是服务器的有关信息。返回信息的 最后是“ posting ok”或“ no posting”,这取决于是否允许客户粘贴新闻 (这个由系统管理员 根据客户的 IP地址来控制 )。

168

计计第二部分 TCP的其他应用

下载

我们注意到服务器的响应信息中提到,这个服务器是

NNRP(Network News Reading

Protocol)服务器,而不是INND(InterNetNews daemon)服务器。先是 INND服务器接收客户的请 求,查找客户的 IP地址。如果客户的 IP地址是被允许的,并且客户不是一个已知的、提供新闻 的主机,那么 N N R P服务器被激活,替代 I N N D服务器,假定客户是想要读新闻而不是想要给 服务器提供新闻。这就可以将新闻供给服务器 (约10 000行C代码)与新闻阅读服务器 (约5 000 行C代码)分别来实现。 图15-2列出了数字响应码中第1位和第2位的含义。这与FTP中的用法也很相似(卷1的319页)。 应







1yz 2yz 3yz 4yz 5yz

报告情况的消息 命令执行成功 迄今为止命令执行成功;发送余下的命令 命令正确,但因为某些原因不能执行 命令未实现,或命令不正确,或遇到了严重的程序差错

x0z x1z x2z x3z x4z x8z x9z

连接、设置和杂项消息 新闻组选择 文章选择 分发功能 发送 非标准扩展 调试用输出

图15-2 三位响应码中第 1位和第2位的含义

我们发给新闻服务器的第一个命令是 h e l p,h e l p命令会将这个新闻服务器支持的所有 命令列出来。 100是响应吗

.

这一行只有一个句点,表示服务器响应的结束

下载

169

第15章 NNTP:网络新闻传送协议计计

因为客户无法确认服务器返回的信息到底有多少行,所以协议要求服务器以一个只包含 句号的行来结束返回。如果某一行恰好就是要以句号开头,那么服务器会在前面再加上一个 句号再发送,客户收到这行后先去掉这个句号。 下面我们来看一下 l i s t命令。如果不带任何参数执行 l i s t命令,它列出所有本服务器 上每一个新闻组的名字,后面跟着这个组中最后一篇新闻和组中第一篇新闻的编号,最后是 “y”或“m”,表示是否允许这个组粘贴新闻或只是一个普通的组。 215是响应码

还有许多行没有显示出来

还有许多行没有显示出来

这一行只有一个句点,表示服务器响应的结束

当然, 2 1 5是响应码,而不是新闻组的编号。这个例子中的服务器向客户返回了 4 2 3 8个 新闻组的情况,共 175 833字节的TCP数据。返回的新闻组信息没有按字母排序。 在新闻客户上通过较慢的拨号线从一个新闻服务器获取这样一个列表,通常会感到很慢。 例如,如果数据传输速率是 28 800 bit/s,那么这个过程将花费约 1分钟(实际测量时,使用这 样一个调制解调器,并在数据发送时进行压缩,大约需 5 0秒)。在以太网上,这个过程所需时 间不到1秒。 g r o u p 命令用来指定某一新闻组作为客户的“当前”新闻组。下面的命令就是把 comp.protocols.tcp-ip设为当前新闻组。

服务器以响应码 2 11 (命令执行成功 )开头,后面跟着这个组中新闻总数的估计值 ( 1 8 1 ),然 后是本组中第一篇新闻文章的编号 ( 41 2 8 9 )、最后一篇新闻文章的编号 ( 43 8 3 1 )和新闻组的名 字。在新闻文章的起始和结束编号之间的差值 (43831-41289=2542)通常大于新闻文章数 (181)。 一方面是因为有些文章是典型的 FAQ(Frequently Asked Questions),它们的失效时间 (通常为1 个月)比起其他大多数新闻 (很少的几天,取决于服务器硬盘的容量 )要长得多。另一个原因是 这些文章能被显式地删除。 下面我们用 head命令来看一篇特殊文章 (编号为43 814)的首部内容。

170

计计第二部分 TCP的其他应用

下载

应答的第一行带有响应码 2 2 1 (命令执行成功 ),后面是 1 0行的首部,最后是只含有句号的 一行。 大多数首部字段无需解释,但消息的 ID看上去有些让人迷惑。 INN试图按下面的 格式生成唯一的消息 I D格式:当前时间,一个 $符号,进程 I D,一个@符号,本地主 机的完整域名。时间和进程号的数值都以 3 2进制的数字串输出:数字由每 5位二进制 数为一组,每一组用字母: 0...9a...v来表示。 接着我们用 body命令返回同一篇文章的主体。

文章中还有 28行没有列出来

新闻的首部和主体可以用一个命令 (a r t i c l e)获取,但绝大多数新闻客户是先取得文章 的首部,允许客户根据新闻的主题进行选择,然后只取回用户所选取的文章的主体。 我们用quit命令来终止到服务器的连接。

服务器的响应是数字代码: 201。我们的客户程序 Telnet显示服务器关闭了连接。 整个客户与服务器的交互过程只使用单个的、由客户发起的 T C P连接。但是连接上大部 分数据都是由服务器发向客户的。连接的持续时间以及数据的交换量均取决于用户阅读新闻 时间的长短。

15.3 一个简单的新闻客户 下面我们通过使用一个简单的新闻客户程序,进行简要的新闻会话来看一下 NNTP命令与 响应之间的交互。我们使用最老的新闻阅读器中的一种: R n,它简单而且容易使用,同时选 用它还因为它带有调试选项 (- D 1 6命令行选项,假定客户程序编译时打开了调试选项 )。这让 我们可以看到客户发出的 NNTP命令以及相应的服务器的响应。我们用黑体字来表示客户端的 命令。 1) 第一个命令是 l i s t,在上一节中我们看到从服务器返回了约 175 000 字节,每行表示 一个新闻组。同时 R n也把用户想要阅读的新闻组以及在这组中最后读过的新闻的编号 的列表保存在文件 .newsrc(在用户的主目录中 )中。例如,某一行:

通过把文件中保存的、最后读过的新闻的编号与现有最新的新闻的编号进行比较,客 户就知道本组中是否还有没读过的新闻。 2) 然后客户检查是否有新的新闻组建立。 NEWGROUPS 950803 192708 GMT 231 New newsgroups follow. .

231是响应码

171

第15章 NNTP:网络新闻传送协议计计

下载

R n在用户的主目录的文件 . r n l a s t中保存了最近一次通报新的新闻组的时间。这个时 间成为n e w s g r o u p s命令的参数( N N T P命令和命令的参数都与大小写无关 )。在这个例 子中保存的时间是:格林尼治时间 1 9 9 5年8月3日,1 9 : 2 7 : 0 8。服务器返回为空 (在返回 码2 3 1与只包含句点的行中没有其他的内容 ),指示没有新的新闻组建立。如果有新的 新闻组建立,客户程序会询问用户是否要加入这个组。 3) 接着 R n将显示前 5个新闻组中未读新闻的编号,并询问是否要阅读第一个新闻组: comp.protocols.tcp-ip。我们以一个等于号响应,让 Rn返回一个对该组所有文章 的一行摘要,然后我们可以选择想要阅读的文章 (可以用 . r n i n i t文件对R n进行配置, 让Rn按照我们期望的方式给出每一篇新闻文章的摘要。书的作者配置的摘要包括文章编 号、主题、文章的行数和文章的作者 )。group命令是由Rn发出的,设置当前的新闻组。

第一篇未读文章的首部和主体可以用以下命令获得:

.

文章未列出

第一篇未读文章的一行摘要显示在终端上。 4) 对本组中剩下的 17个未读的新闻执行 xhrd命令,然后再用 head命令。如下面的例子:

.

首部的14行未列出

x h d r命令能接受的参数不但可以是单个文章编号,还可以是号码范围,这就是为什么 服务器返回了许多行,然后以只含有句点的行结束的原因。每篇文章的一行摘要显示 在终端上。 5) 我们敲空格键选择第一篇未读的文章,客户程序发出 h e a d命令,接着是 a r t i c l e命 令。文章便显示在终端上。对所有文章相继使用这两个命令。 6) 当我们读完这个组的新闻后,便移到另一个组,这时客户程序又发出另一个 g r o u p命 令。我们向服务器请求每一篇未读新闻的一行摘要,在新的组中再一次执行上面讨论 过的命令。 我们注意到的第一件事情是 R n发出了太多的命令。例如,为了对所有的未读文章取得一 行摘要,先要发出 x h d r取得摘要,然后是用 h e a d命令取得文章的首部。这两个命令中的第 一个是不必要的。增加这些额外命令的原因之一是最开始这些命令是为工作在主机 (也是新闻 服务器 )上的客户程序设计的,因此这些附加命令可能要快一些,因为没有网络的传输时间。 使用NNTP访问远程新闻服务器的功能是后来加上去的。

15.4 一个复杂的新闻客户 下面我们来看一个更复杂的新闻客户程序: Netscape 1.1N版的We b浏览器,它内置了新

172

计计第二部分 TCP的其他应用

下载

闻阅读器。这个客户程序没有调试选项,所以我们只有跟踪它与服务器之间交换的 T C P分组 来看它是怎样工作的。 1) 当我们启动客户程序,并选择新闻阅读特性时,它读 . n e w s r c文件,并且只向服务器 请求在这个文件中我们所预订的新闻组的相关内容。对每一个预订的新闻组都发出 g r o u p命令来确定起始和结束的文章编号,并与 . n e w s r c文件中所存储的最后阅读的 文章编号进行比较。这个例子中,作者在 4 000多个新闻组中只预订了 7 7个,因此共有 7 7个g r o u p命令发向服务器。这在拨号线的 P P P链路上仅需 2 3秒,相比较而言, R n所 使用的list命令要50秒。 如果新闻组的数量由 4 000减少至7 7,客户所花的时间应小于 2 3秒。实际上,用 s o c k (卷1附录C )发送7 7个同样的 g r o u p命令只需约 3秒。看起来浏览器在这 7 7个命 令上叠加了其他的启动处理。 2) 我们选择一个有未读文章的新闻组: c o m p . p r o t o c o l s . t c p - i p,接着执行下面的 命令:

.

指定范围内剩余文章的一行摘要

第一个命令设置当前新闻组,第二个命令向服务器请求指定文章的概况。在这个组中, 43 815是第一篇、43 831是最后一篇未读的文章。每篇文章的一行摘要包括:文章编号、 主题、作者、日期和时间、消息 I D、文章的引用、字节数和行数 (注意每个一行摘要都 很长,所以上面我们把每一行都几次换行。同时我们还把分隔字段的 t a b符换成了 \ t, 这样便于看清楚 )。 N e t s c a p e客户程序按主题组织返回的概况,并显示未读主题的列表以及文章的作者和 行数。将一篇文章及其应答组合在一起,称为编线索,因为一个议题的线索都是组合 在一起的。 3) 对每一篇我们选择阅读的文章,执行一次 article命令,文章便显示出来。 从上面的Netscape新闻客户程序的概况中可以看出,它采用两种优化措施来减少用户的等 待时间。第一个措施是只向服务器请求用户所需要阅读的新闻组,而不是使用 l i s t命令。第 二,它使用 x o v e r命令提供每一篇文章的摘要,而不是对组中的每一篇文章使用一次 h e a d和 xhrd命令。

15.5 NNTP的统计资料 为了理解典型的 N N T P 的用法,我们在第 1 4章曾提到的主机上运行 Tc p d u m p,来收集 N N T P所使用的 S Y N、F I N和R S T报文。这个主机从一台 N N T P新闻供给主机上获得新闻 (可能 有其他备用的新闻供给主机,但是观察到的报文都是来自同一台主机 ),然后分发给 1 0个其他

173

第15章 NNTP:网络新闻传送协议计计

下载

站点。在这 1 0个站点中只有两个使用 N N T P,其他都是使用 U U C P,所以我们的 Tc p d u m p只记 录到两个 N N T P供给主机。两个流出的 N N T P主机收到的新闻只是这台主机收到的新闻的一小 部分。最后,因为这台主机属于一个 I n t e r n e t服务提供商,所以各式各样的客户把主机当成新 闻服务器来阅读新闻。所有的客户阅读新闻均使用 NNTP协议,包括同在主机上的新闻阅读进 程和其他主机上的新闻阅读进程 (典型的是通过 P P P或S L I P连接)。Tc p d u m p连续运行了 11 3小 时(4.7天),共收集了 1 250 个连接上的信息。图 15-3汇总了这些信息。 1个输入 新闻供给 连接数 流入字节总数 流出字节总数 总持续时间 (分钟) 每连接流入字节数 每连接流出字节数 连接平均持续时间 (分钟)

67 875 345 619 4 071 785 6 686 13 064 860 60 773 100

两个输出 新闻供给

新闻阅读 客户

32 4 499 1 194 086 407 141 37 315 13

1 151 593 731 56 488 715 21 758 516 49 078 19





1 250 875 943 849 61 754 586 28 851

图15-3 单个主机上 4.7天的NNTP统计资料

我们首先注意输入新闻供给主机,它每天收到约 1 . 8 6亿字节的新闻,平均每小时约为 8百 万字节。同时我们也可以看出,到主新闻供给主机的 N N T P连接的持续时间很长: 1 0 0分钟, 交换了 1 3 0 0万字节的数据。这台主机与它的输入新闻供给主机之间的 T C P连接经过一段时间 的静默后由新闻服务器关闭。下次需要时再重新建立连接。 典型的新闻阅读程序使用 N N T P连接约 1 9分钟,读取约 50 000 字节的新闻。绝大多数 NNTP流量是单向的:从主新闻供给主机流向新闻服务器,从新闻服务器流向新闻阅读客户。 站点-站点的 N N T P流量之间有巨大的差异。上面的统计数据就是一个例子:这 些统计数据中没有典型值。

15.6 小结 N N T P是又一个使用 T C P协议的简单协议。客户发出 A S C I I命令(服务器支持超过 2 0种不同 的命令 ),服务器的响应先是响应码,然后跟着一行或多行的应答,最后以只包含句号的行结 束(如果响应是可变长度 )。类似其他的 Internet协议,NNTP协议本身已多年没有变化,但是由 客户程序提供给交互式用户的接口却变化很快。 不同新闻阅读程序之间的很多区别都取决于应用程序怎样使用协议。我们看到 R n客户程 序和N e t s c a p e程序之间的不同有:确定哪些文章未读的方法不同,取得未读文章的方法也不 同。 NNTP协议使用单个TCP连接维持整个的客户-服务器数据交换。这一点与HTTP协议不同, H T T P协议从服务器每获取一个文件都要建立一条 T C P连接。这种差异一方面是因为 N N T P客 户只与一个服务器通信,而 H T T P客户能同时与多个不同服务器通信。同时我们也看到绝大多 数TCP连接上的NNTP协议的数据流是单向的。

下载

第三部分 Unix域协议 第16章 Unix域协议:概述 16.1 概述 U n i x域协议是进程间通信 ( I P C )的一种形式,可以通过与网络通信中使用的相同插口 A P I 来访问它们。图 1 6 - 1的左边表示使用插口写成的客户程序和服务器程序,它们在同一台主机 上利用 I n t e r n e t协议进行通信。图 1 6 - 1的右边表示用插口写的利用 U n i x域协议进行通信的客户 程序和服务器程序。

客户进程

服务进程

插口

插口

客户进程

服务进程

插口

插口

Unix域协议

环回 驱动程序 主机

主机

图16-1 使用Internet协议和Unix域协议的客户程序与服务器程序

当客户进程通过 TCP往服务器进程发送数据时,数据首先由 TCP输出处理,然后再经过 IP 输出处理,最后发往环回驱动器 (见卷2的5 . 4节),在环回驱动器中,数据首先被放到 I P输入队 列,然后经过 I P输入和 T C P输入处理,最后传送到服务器。这样工作得很好,并且对于在相 同主机上的对等端 (客户进程和服务器进程 )来说是透明的。然而,在 T C P / I P协议栈里需要大 量的处理过程,当数据没有离开主机时,这些处理过程实际上是不需要的。 U n i x域协议由于知道数据不会离开主机,所以只需要较少的处理过程 (这样数据传送就快 多了)。不需要进行检验和的计算和验证,数据也不会失序,由于内核能控制客户进程和服务 器进程的执行过程,流量控制也被大大简化了,等等。虽然 I P C的其他形式也有这些优点 (消 息队列、共享内存、命名管道,等等 ),但是, U n i x域协议的优点在于它们使用的接口与网络 应用程序使用的插口接口完全一样:客户程序调用 c o n n e c t ,服务器程序调用 l i s t e n和

176

计计第三部分 Unix域协议

下载

a c c e p t,两者都调用 r e a d和w r i t e,等等。而其他形式的 I P C使用完全不同的 A P I,其中有 一些不能与插口以及其形方式的 I / O较好地交互 (例如,在系统 V消息队列中我们不能使用 select函数)。 一些T C P / I P实现努力通过优化去提高性能,例如当目的地址是环回接口时可以 忽略TCP检验和的计算和验证。 U n i x域协议既提供一个流插口 (S O C K _ S T R E A M,与T C P字节流相似 ),又提供一个数据报 插口(S O C K _ D G R A M,与U D P数据报相似 )。U n i x域插口的地址族是 A F _ U N I X。在U n i x域协议 中用于标识插口的名字是文件系统的路径名 ( I n t e r n e t协议使用 I P地址和端口号的组合来标识 TCP和UDP插口)。 网络编程 A P I标准 IEEE POSIX 1003.1g 也支持 U n i x 域协议,它使用的名称是 “local IPC”。其地址族是 A F _ L O C A L,协议族是 P F _ L O C A L。因而使用术语“U n i x” 来描述这些协议也许已成为历史。 U n i x域协议还能提供在不同机器之间进程间通信时所没有的功能。这一功能就是描述符 传递,即通过 U n i x域协议在互不相关的进程间传送描述符的能力,这个例子我们将在第 1 8章 讨论。

16.2 用途 许多应用程序使用 Unix域协议: 1) 管道。在一个源于伯克利的内核里,使用 U n i x域流插口来实现管道。在 1 7 . 1 3节里我们 将讨论pipe系统调用的实现。 2) X Wi n d o w系统。当与 X 11服务器相连时, X 11客户进程通常基于 D I S P L A Y环境变量的 值或基于 - d i s p l a y命令行参数值来决定使用什么协议。这个值的形式是 h o s t n a m e : d i s p l a y. s c re e n,这里 h o s t n a m e是可选的,默认值是当前主机,使用的协议是最有效 的通信方式,其中典型的是 U n i x域流协议。值 u n i x强制使用 U n i x域流协议。服务器绑 定到Unix插口上的名字类似于 /tmp/.X11-unix/X0。 由于X服务器进程通常处理在同一台主机或者网络上的客户进程的请求,这就意味着服 务器进程需要等待一个连接请求到达 TCP插口或者Unix流插口。 3) B S D打印假脱机系统 (l p r客户进程和l p d服务器进程,在 [Stevens 1990]的第1 3章详细 描述)使用一个名为 / d e v / l p的U n i x域流插口在相同的主机上进行通信。像 X服务器一 样,l p d服务器使用 U n i x插口处理在相同主机上客户进程的连接,或者使用 T C P插口 处理网络上客户进程的连接。 4) B S D系统记录器 (s y s l o g库函数,可以被任何应用程序调用 )和s y s l o g d服务器程序 (使用一个名为 /d e v / l o g的U n i x域数据报插口在相同的主机上进行通信 )。客户进程写 一个消息到插口上,服务器进程读出来并进行处理。服务器进程也处理来自其他主机 上使用 U D P插口的客户进程的消息,关于这种机制的详细介绍见

[Stevens 1992] 的

13.4.2节。 5) I n t e r N e t N e w s守护程序 (i n n d)创建一个 Unix 数据报插口来读取控制报文,一个 U n i x流 插口来读取本地新闻阅读器上的文章。这两个插口分别是 c o n t r o l和n n t p i n,通常

177

第 16章 Unix域协议:概述计计

下载 是在/var/news/run目录里。

以上内容并不全面,还有其他的应用程序使用 Unix插口。

16.3 性能 比较 U n i x域插口与 T C P插口的性能是非常有趣的。除了 T C P和U D P插口,修改公共域 t t c p程序的一个版本,使之使用一个 U n i x域流插口。我们在同一台主机上运行的两个程序副 本之间传送 16 777 216个字节的数据,结果如图 16-2所示。 内



最快的TCP (字节/秒)

Unix域 (字节/秒)

增长 百分比

图16-2 Unix域插口与 TCP插口吞吐量的比较

我们感兴趣的是从一个 T C P插口到一个 U n i x域插口速度的增长率,而不是绝对速度 (这些 测试运行在五个不同系统上,覆盖了不同的处理器速度,在不同的行上进行速度比较毫无意 义)。所有的内核都是源于伯克利,而不是 Solaris 2.4。我们可以看到,在源于伯克利内核上 的Unix插口比TCP插口要快两倍多,在 Solaris下增长率要慢得多。 S V R 4以及源于它的 S o l a r i s,采用了完全不同的方法来实现 U n i x域插口。 [ R a g o 1993]的7.5节描述了基于流的 SVR4中实现Unix域插口的方法。 在这些测试里,术语“Fastest TCP(最块的TCP)”意味着这些测试是在下列情况下进行的: 将发送缓存和接收缓存都设置为 32 768(这个值要比一些系统中的默认值大 ),直接指定环回地 址而不是主机自己的 I P地址。在早期的 B S D实现中,如果指定了主机的 I P地址,那么在 A R P 码执行之前分组不会发送到环回接口 (见卷1图2 - 4 ),这稍微降低了性能 (这就是为什么定时测 试运行时要指定环回地址 )。这些主机都有一个本地子网的网络入口,其接口就是网络的设备 驱动程序,卷 1第8 7页中间网络入口 1 4 0 . 2 5 2 . 1 3 . 3 2就是一个例子 (SunOS 4.1.3)。较新的 B S D内 核有一条到主机本身 I P地址的路由,其接口就是环回驱动程序,卷

2 图 1 8 - 2 中入口

140.252.13.35就是一个例子(BSD/OS V2.0)。 我们将在讨论Unix域协议的实现后,在 18.11节再返回到性能问题。

16.4 编码举例 为了说明如何缩小一个 T C P客户-服务器与一个 U n i x域客户-服务器之间的差别,我们修 改了图1 - 5和图1 - 7中的客户-服务器,使它们利用 U n i x域协议通信。图 1 6 - 3表示U n i x域客户程 序,与图1-5的不同之处用黑体字表示。 2-6

我们包含了 头文件,客户和服务器的插口地址结构现在是 sockaddr_un

类型。 11-15

socket调用的协议族是 PF_UNIX,调用strncpy将与服务器相联系的路径名 (从命

令行参数得到)写入插口的地址结构。

178

计计第三部分 Unix域协议

图16-3 Unix域事务客户程序

当我们在下一章讨论具体实现时,就会看到导致这些差别的原因。 图16-4为Unix域服务器程序,与图 1-7的不同之处用黑体字表示。

图16-4 Unix域事务服务器程序

下载

179

第 16章 Unix域协议:概述计计

下载

图16-4 (续)

2-7

我们包含了< s y s / u n . h >头文件,并且定义了与服务器相联系的路径名 (通常路经名应

在客户程序和服务器程序都包含的头文件中定义,为了简单,我们在这里定义 )。现在的插口 地址结构是 sockaddr_un类型。 13-14 调用strncpy将路径名填入到服务器的插口地址结构。

16.5 小结 U n i x域协议提供了进程间通信的一种形式,它使用同网络通信相同的编程接口

(插口 )。

U n i x域协议既提供类似于 T C P的流插口,又提供类似于 U D P的数据报插口。从 U n i x域协议能 获得的优点是速度:在一个源于伯克利的内核上, Unix域协议要比 TCP/IP大约快两倍。 Unix域协议的最大用户是管道和 X Window系统。如果 X客户进程发现X服务器进程与 X客 户进程在同一台主机上,它就会使用 U n i x域流连接来代替 T C P连接,T C P客户-服务器程序和 Unix域客户-服务器程序代码变化是很小的。 下面的两章描述 Net/3内核中Unix域插口的实现。

下载

第17章 Unix域协议:实现 17.1 概述 在u i p c _ u s r r e q . c文件中实现 Unix域协议的源代码包含 16个函数,总共大约有 1000行C 语言源程序,这与在卷 2中实现 U D P的8 0 0行源程序长度差不多,比实现 T C P的4 5 0 0行源程序 要短得多。 我们分两章来描述 U n i x域协议的实现,下一章讨论 I / O和描述符传递,其他的内容都在本 章讨论。

17.2 代码介绍 在一个 C文件中有 1 6个U n i x域函数,在其他 C文件和两个头文件中还有其他有关的定义, 如图17-1所示。 文







sys/un.h sys/unpcb.h

s o c k a d d r _ u n结构的定义 u n p c b结构的定义

kern/uipc_proto.c kern/uipc_usrreq.c kern/uipc_syscalls.c

Unix域p r o t o s w { }和d o m a i n { }的定义 Unix域函数 p i p e和s o c k e t p a i r系统调用

图17-1 在本章中讨论的文件

在本章我们也介绍 pipe和socketpair系统调用,它们都使用本章描述的 Unix域函数。 全局变量 图17-2列出了在本章和下一章中讨论的 11个全局变量。 变



数 据 类 型





unixdomain unixsw sun_noname unp_defer unp_gcing unp_ino unp_rights

struct domain struct protosw struct sockaddr int int ino_t int

域定义(图17-4) 协议定义(图17-5) 包含空路径名的插口地址结构 延迟入口的无用单元收集计数器 如果当前执行无用单元收集函数,就设置 下一个分配的伪i_node号的值 当前传送中的文件描述符数

unpdg_recvspace unpdg_sendspace unpst_recvspace unpst_sendspace

u_long u_long u_long u_long

数据报插口接收缓存的默认范围, 4096字节 数据报插口发送缓存的默认范围, 2048字节 流插口接收缓存的默认范围, 4096字节 流插口接收缓存的默认范围, 4096字节

图17-2 在本章中介绍的全局变量

181

第 17章 Unix域协议:实现计计

下载 17.3 Unix domain和protosw结构

图17-3表示了Net/3系统中常见的三个 domain结构,同时还有相应的 protosw数组。

流 数据报 原始IP

原始IP

图17-3 domain 表和protosw 数组

卷2描述了Internet和路由选择域,图 17-4描述了Unix域协议使用的domain结构(卷2图7-5) 中的字段。 由于历史的原因,两个 raw IP记录项在卷 2图7-12中描述。 单





dom_family dom_name dom_init dom_externalize dom_dispose dom_protosw dom_protoswNPROTOSW dom_next dom_rtattach dom_rtoffset dom_maxrtkey

PF_UNIX unix 0 unp_externalize unp_dispose unixsw

0 0 0





域协议族 名字 在Unix域中没有使用 外部化访问权 (图18-12) 释放内部化权利 (图18-14) 协议转换数组 (图17-5) 协议转换数组的尾部指针 由d o m a i n i n i t填充,卷2 在Unix域中没有使用 在Unix域中没有使用 在Unix域中没有使用

图17-4 unixdomain 结构

仅有Unix domain定义了d o m _ e x t e r n a l i z e和d o m _ d i s p o s e两个函数,我们在第 1 8章 中讨论描述符传递时再描述这两个函数,由于 Unix 域没有路由选择表,所以 d o m a i n结构的最 后三个元素没有定义。 图17-5描述了unixsw结构的初始化(卷2图7-13描述了Internet协议的对应结构 )。 定义三个协议: • 与TCP相似的流协议; • 与UDP相似的数据报协议; • 与原始IP相似的raw协议。

182

计计第三部分 Unix域协议

下载

图17-5 unixsw 数组的初始化

由于Unix 域支持访问权 (就是我们在下一章要讲的描述符传递 ),U n i x域流协议和数据报 协议都设置 P R _ R I G H T S标志。流协议的另外两个标志 P R _ C O N N R E Q U I R E D和P R _ W A N T R C V D 与TCP的标志一样;数据报协议的两个标志 P R _ A T O M I C和P R _ A D D R与UDP的标志一样。需要 注意的是流协议与数据报协议定义的唯一一个函数指针是 u i p c _ u s r r e q,用它处理所有的 用户请求。 在r a w协议的 p r o t o s w结构中的四个函数指针都是以 r a w _开头,与 P R _ R O U T E域中的一 样,这些内容在卷 2的第20章介绍。 作者从来没有听到过一个应用程序使用 Unix域的raw协议。

17.4 Unix域插口地址结构 图17-6描述了一个Unix 域插口地址结构的定义,一个 s o c k a d d r _ u n结构长度为106个字 节。

图17-6 Unix 域插口地址结构

开始的两个域与其他的插口地址结构一样:地址族 (AF_UNIX)后紧跟着一个长度字节。 自从4 . 2 B S D以来,注解“ g a g”就存在了,也许原作者并不喜欢使用路经名来标 识U n i x域插口,或者是因为一个完整的路径名太长以至在 m b u f中写不下 (路经名的长 度能达到1024字节)。

183

第 17章 Unix域协议:实现计计

下载

我们将要看到 Unix 域插口使用文件系统中的路经名来标识插口,并且路径名存储在 s u n _ p a t h中。s u n _ p a t h的大小为 1 0 4字节,一个 m b u f的大小为 1 2 8个字节,刚好存放插口 地址结构和一个表示终止的空字节。如图 17-7所示。

mbuf首部 字节

(128字节)

图17-7 存储在一个 mbuf中的Unix域插口地址结构

我们将m b u f中的m _ t y p e字段设置成M T _ S O N A M E,因为当 m b u f含有一个插口地址结构时 m _ t y p e就是这个普通值。虽然从图上看,最后两个字节没有使用,并且与这些插口相联系的 最长路径名是 1 0 4字节,但是我们将看到 u n p _ b i n d和u n p _ c o n n e c t两个函数允许一个路径 名后面跟一个空字节时可以长达 105字节。 U n i x域插口在一些地方需要一个命名空间,由于文件系统的命名空间已经存在, 所以就选定了路径名。与其他例子一样, I n t e r n e t协议使用 I P地址和端口号作为命名 空间,系统V IPC([Stevens1992]的第1 4章)使用3 2比特密钥。由于 U n i x域客户进程用 路径名来与服务器进程同步,从而通常使用绝对路径名 (以/开头)。如果使用相对路 径名,客户程序和服务器程序必须在相同的目录中,或者服务器程序的绑定路经名 不会被客户程序的 connect和sendto发现。

17.5 Unix域协议控制块 Unix域插口有一个相关联的协议控制块 (PCB),一个 u n p c b结构,我们在图 17-8中描述了 这个36字节的结构。

图17-8 Unix域协议控制块

184

计计第三部分 Unix域协议

下载

不像路由域中使用的 Internet PCB和控制块,这两者都是通过内核 M A L L O C函数来分配的 (分别见卷2图20-18和图22-6),而unpcb结构却存储在mbuf中,这可能是一个历史的人为因素。 另一个不同点是除了 Unix 域协议控制块以外,所有的控制块都保留在一个双向循环链表 上,当数据到达时能通过查找这个链表将数据传递给相应的插口。对于所有的 U n i x域协议控 制块而言,没有必要维护这样的链表,因为同样的操作,也就是当客户进程调用 connect时, 查找服务器的控制块是通过内核中已有的路径名查找函数来实现的。一旦找到服务器的 u n p c b,就可以将它的地址存储在客户进程的 u n p c b中,因为 U n i x域插口的客户进程与服务 器进程在相同的主机上。 图1 7 - 9描述了处理 U n i x域插口的不同数据结构的关系,在这个图中我们描述了两个 U n i x 描述符

描述符

包含绑定到插口 的路径名

图17-9 互相连接的两个 Unix域数据报插口

185

第 17章 Unix域协议:实现计计

下载

域数据报插口,我们假定右边的 (服务器进程)插口已经绑定了一个路径名到它的插口,左边的 (客户进程)插口已经连接到服务器的路径名上。 客户进程PCB的u n p _ c o n n单元指向服务器进程的 PCB,服务器进程的 u n p _ r e f s指向连 接到这个 PCB上的第一个客户进程 (不像流插口,多个数据报客户进程可以连接到同一个服务 器进程上,在17.11节我们要详细讨论 Unix域数据报插口的连接 )。 服务器的u n p _ v n o d e单元指向 v n o d e,v n o d e与绑定到服务器插口的路径名相联系,它 的v _ s o c k e t单元指向服务器的 s o c k e t,这就是定位一个已经绑定了路径名的 u n p c b所需的 链接。例如,当服务器绑定了一个路径名到它的 U n i x域插口时,就会创建一个 v n o d e结构, 并且将 u n p c b的指针存储在 v _ n o d e的v _ s o c k e t中。当客户进程连接到服务器上时,内核中 的路径名查找代码定位 v-node,然后从 v_socket指针获得服务器进程的 unpcb指针。 被绑定到服务器插口的名字包含在 s o c k s d d r _ u n结构中, s o c k a d d r _ u n结构本身包含 在u n p _ s d d r指向的m b u f结构中。 Unix 的v - n o d e从来没有包含指向 v - n o d e的路径名,因为在 一个Unix文件系统中多个名字 (即目录记录项)能同时指向一个给定的文件 (即v-node)。 图1 7 - 9表示两个连接的数据报插口,在图 1 7 - 2 6中我们将看到,处理流插口时与这里有些 不同。

17.6 uipc_usrreq函数 在图 1 7 - 5 中我们看到,对于流和数据报协议,

u n i x s w 结构中引用的唯一函数是

uipc_usrreq,图17-10给出了这个函数的要点。

图17-10 uipc_usrreq 函数体

186

计计第三部分 Unix域协议

下载

图17-10 (续)

1. 无效的PRU_CONTROL请求 57-58 PRU_CONTROL请求来自ioctl系统调用,不被 Unix域支持。 2. 仅为PRU_SEND支持的控制信息 59-62

如果进程传送控制信息 (使用s e n d m s g系统调用 ),请求必须是 P R U _ S E N D;否则,

返回一个错误。描述符在使用该请求的控制信息的进程间传递,这部分我们在第 18章中讨论。 3. 插口必须有一个控制块 63-66

如果s o c k e t结构没有指向一个 U n i x域控制块,请求必须是 P R U _ A T T A C H;否则,

返回一个错误。 67-248 在下面几节中我们讨论这个函数的每一个 case语句,以及调用的不同unp_xxx函数。 249-255 释放任何控制信息和数据 mbuf,然后函数返回。

17.7 PRU_ATTACH请求和unp_attach函数 当一个连接请求到达一个处于监听状态的流插口时, s o c k e t系统调用和 s o n e w c o n n函 数(卷2图15-29)产生PRU_ATTACH请求,如图 17-11所示。

图17-11 PRU_ATTACH 请求

u n p _ a t t a c h函数完成这个请求的所有处理工作,如图 1 7 - 1 2所示。 s o c k e t结构已经被 插口层分配和初始化,现在轮到协议层分配和初始化自身的协议控制块,在本例中这个协议 控制块为unpcb结构。

图17-12 unp_attack 函数

187

第 17章 Unix域协议:实现计计

下载

图17-12 (续)

1. 设置插口高水位标记 277-290

如果插口发送和接收的高水位标记为 0,则s o r e s e r v e将它们设置成图 1 7 - 2所示

的默认值,高水位标记限制了存放在插口发送和接收缓存中的数据量。当通过 s o c k e t系统调 用来调用 unp_attach时,这两个标记都为 0,但是当通过 sonewconn调用unp_attach时, 它们等于监听插口中的值。 2. 分配并初始化PCB 291-296

m_getclr获得一个mbuf用于unpcb结构,将mbuf清零并将类型设置成 MT_PCB。

注意所有的 P C B单元都被初始化为 0。通过 s o _ p c b和u n p _ s o c k e t指针将s o c k e t和u n p c b 结构连接起来。

17.8 PRU_DETACH请求和unp_detach函数 当一个插口关闭时发出 P R U _ D E T A C H 请求 ( 卷 2 图 1 5 - 3 9 ) ,这个请求跟随在 P R U _ DISCONNECT请求(仅针对有连接的插口 )的后面,如图17-13所示。

图17-13 PRU_DETACH 请求

75-77 图17-14中的unp_detach函数完成PRU_DETACH请求的所有处理工作。 1. 释放v-node 303-307

如果插口与一个 v - n o d e相联系,那么将指向 P C B结构的指针置为 0,并且调用

vrele释放v_node。

188

计计第三部分 Unix域协议

下载

图17-14 unp_detach 函数

2. 如果插口连接了其他插口,则断开连接 308-309 如果关闭的插口连接到另一个插口上,那么 unp_disconnect就要断开这两个插 口的连接,这种情况在流和数据报插口中都会发生。 3. 断开连接到关闭插口的插口 310-311

如果其他的数据报插口连接到这个插口,则调用 u n p _ d r o p断开这些连接,那些

插口就会接收到 E C O N N R E S E T错误。w h i l e循环检查连接到这个 u n p c b的所有u n p c b结构链 表。函数 u n p _ d r o p调用u n p _ d i s c o n n e c t,它改变 P C B的u n p _ r e f s单元去指向链表的下 一个单元。当整个链表已经被处理后, PCB的unp_refs指针将为0。 312-313

被关闭的插口由 s o i s d i s c o n n e c t断开连接,指向 PCB的s o c k e t结构中的指针

置为0。 4. 释放地址和 PCB mbuf 314-315

如果插口已经绑定到一个地址, m_freem就释放存储这个地址的 mbuf。注意程序

不检查unp_addr是否为空,因为 m_freem会检查。unpcb由m_free来释放。 这个对 m _ f r e e的调用应当移到函数的末尾,因为指针 u n p可能会在下一段程序 里使用。 5. 检查被传送的描述符 316-326

如果内核里任何进程传来了描述符,则 u n p _ r i g h t s 为非 0,这会导致调用

189

第 17章 Unix域协议:实现计计

下载

sorflush和unp_gc(无用单元收集函数 )。我们将在第18章中讨论描述符的传送。

17.9 PRU_BIND请求和unp_bind函数 可以通过 b i n d将Unix 域中的流和数据报插口绑定到文件系统中的路径名上, b i n d系统 调用产生 PRU_BIND请求,如图 17-15所示。

图17-15 PRU_BIND 请求

78-80

所有的工作都由 unp_bind函数来完成,如图 17-16所示。

1. 初始化nameidata结构 338-339

u n p _ b i n d分配一个 n a m e i d a t a结构,这个结构封装所有传给 n a m e i函数的参

数,并使用 N D I N I T宏来初始化这个结构。 C R E A T E参数指定要创建的路径名, F O L L O W允许 紧跟的符号连接, LOC K P A R E N T指明在返回时必须要锁定父亲的 v-node (防止我们在完成工作 之前其他进程修改 v - n o d e )。U I O _ S Y S S P A C E指明路径名在内核中 (由于b i n d系统调用将路径 名从用户空间复制到一个 m b u f中)。s o u n - > s u n _ p a t h是路径名的起始地址 (它被作为 n a m参 数传送给 u n p _ b i n d)。最后, p是指向发布 b i n d系统调用的进程的 p r o c结构的指针,这个结 构包含所有有关一个进程的信息,内核需要一直将该进程存放在内存中。 N D I N I T宏仅仅初始 化这个结构,对 namei的调用在这个函数后面。

图17-16 unp_bind 函数

190

计计第三部分 Unix域协议

下载

图17-16 (续)

历史上,在文件系统中查询路径名的函数名一直是 n a m e i,它代表“ n a m e - t o inode”。这个函数要搜索整个文件系统去查找指定的名字,如果成功,就初始化内核 中的i n o d e结构,这个结构包含从磁盘上得到的文件的 i _ n o d e信息的副本。尽管 v node已经取代了 i-node,但是术语 namei仍然保留了下来。 这是我们第一次涉及到 BSD内核中文件系统代码。 BSD内核支持许多不同的文件 系统类型:标准的磁盘文件系统 (有时也叫作“快速文件系统” ),网络文件系统 ( N F S ),C D - R O M文件系统, M S - D O S文件系统,基于存储器的文件系统 (对于目录, 例如/ t m p),等等。 [Kleiman 1986]描述了一个早期的 v-node实现。以 V O P _作为名字 开始的函数一般是 v-node操作函数。这样的函数大约有 40个,当被调用时,每个函数 调用一个文件系统定义的函数去执行这个操作。以一个小写字母 v开头的函数是内核 函数,这些函数可能调用一个或更多的 V O P _函数。例如, v p u t调用V O P _ U N L O C K, 然后再调用 v r e l e。v r e l e函数释放一个 v - n o d e : v - n o d e的引用计数器递减,如果达 到0,就调用VOP_INACTIVE。 2. 检查插口是否被绑定 340-341 如果插口PCB的unp_vnode非空,插口就已经被绑定,这是一个错误。 3. 以空字符(null)结束的路径名 342-346

如果包含 s o c k a d d r _ u n结构的mbuf长度是108(M L E N),长度值是从 b i n d系统调

用的第三个参数复制的,则 m b u f的最后一个字节必须是一个空字节。这就保证路径名以空字 符结尾,当在文件系统中查找路径名时这是必需的 (卷2图1 5 - 2 0中的s o c k a r g s函数保证由进 程传送的插口地址结构长度不超过 108字节)。如果mbuf的长度小于 108个字节,则在路径名的 结尾存放一个空字节,以免进程没有以空字符来结束路径名。 4. 在文件系统中查找路径名 347-349

n a m e i在文件系统中查找路径名,并且尽可能在相应的目录中为指定的路径名创

建一个记录项。例如,如果绑定到插口的路径名是 / t m p / . X l l - u n i x / X 0,那么文件名X 0必

191

第 17章 Unix域协议:实现计计

下载

须被加到目录 / t m p / . X l l - u n i x 中,包含 X 0 的记录项的目录叫作父目录。如果目录 / t m p / . X l l - u n i x不存在,或者如果存在,但是已经包含一个 X 0的文件,那么就要返回一 个错误。另一个可能的错误是调用进程没有权限在父目录中创建一个新的文件。从 n a m e i想 得到的结果是从函数返回一个 0值,n d . n i _ v p返回的是一个空指针 (文件不存在 )。如果这两 个条件都正确,那么 nd.ni_dvp就包含要创建新文件名的加锁父目录。 3 4 7行的注释指的是如果路径名已经存在将导致 b i n d返回错误。所以大部分绑 定Unix域插口的应用程序在调用 bind之前先调用 unlink删除已存在的路径名。 5. 路径名已经存在 350-359

如果 n d . n i _ v p非空,那么路径名就已经存在。 v - n o d e引用被释放,并且返回

EADDRINUSE给进程。 6. 创建v-node 360-365

V A T T R _ N U L L宏初始化 v a t t r结构,类型被设置为 V S O C K(一个插口 ),访问模式

设置为八进制 7 7 7 (A C C E S S P E R M S)。这九个权限比特允许文件所有者、组里的成员和其他用 户(也就是每一个用户 )执行读、写和执行操作。在指定的目录中,文件由文件系统的创建函数 间接通过 V O P _ C R E A T E函数创建。传递给创建函数的参数是 n d . n i _ d v p(父目录 v - n o d e的指 针),n d . n i _ c n d(来自n a m e i需要传送给V O P函数的附加信息 ),以及 v a t t r结构。第二个参 数nd.ni_vp接收返回信息, nd.ni_vp指向新创建的v-node(如果创建成功)。 7. 链接结构 365-367 vnode和socket通过v_socket和unp_vnode指针互相指向对方。 8. 保存路径名 368-371

调用m _ c o p y将刚刚绑定到插口的路径名复制到一个 m b u f中,P C B的u n p _ a d d r

指向这个新的mbuf。将v-node解锁。

17.10 PRU_CONNECT请求和unp_conne c t函数 图17-17描述了PRU_LISTEN和PRU_CONNECT请求。

图17-17 PRU_LISTEN 和PRU_CONNECT 请求

1. 验证监听插口是否已经被绑定 81-84

只能在一个已经绑定了一个路径名的插口上执行 listen系统调用。 TCP没有这个需

求,在卷 2图3 0 - 3我们看到对一个没有绑定的 T C P插口调用 l i s t e n时,T C P就会选择一个临 时的端口,并把它分配给插口。 85-87

PRU_CONNECT请求的所有处理工作都由 unp_connect函数来执行,函数的第一部

分如图 1 7 - 1 8所示。对于流插口,该函数被 P R U _ C O N N E C T请求调用;当临时连接一个无连接

192

计计第三部分 Unix域协议

下载

的数据报插口时,该函数被 PRU_SEND请求调用。

图17-18 unp_connect 函数:第一部分

2. 初始化用作路径名查找的 nameidata结构 383-384

nameidata结构由NDINIT宏进行初始化。 LOOKUP参数指明应当查找的路径名,

F O L L O W允许紧跟的符号连接, L O C K L E A F参数指明返回时必须锁定 v - n o d e (防止在执行结束 前其他进程修改这个 v - n o d e ), U I O _ S Y S S P A C E 参数指明路径名在内核中, s o u n > s u n _ p a t h 是路径名的起始地址 (它被作为 n a m参数传递给 u n p _ c o n n e c t )。p指向发布 connect或sendto系统调用的进程的 proc结构。 3. 以空字节结束路径名 385-389

如果插口地址结构的长度是 108字节,最后一个字节必须为空,否则在路径名的结

尾要存储一个空字节。 这段代码与图17-16中的代码相似,但实际上是不同的。不仅第一个 if语句不同, 而且当最后一个字节非空时返回的错误也不同:这里是 E M S G S I Z E,而图 1 7 - 1 6中是

193

第 17章 Unix域协议:实现计计

下载

E I N V A L。另外,这个测试对检查数据是否包含在一个簇中有负面影响,虽然这可能 是偶然的,因为 sockargs函数从来不会把插口地址结构放进一个簇中。 4. 查找路径名并检验其正确性 390-398

namei在文件系统中查找路径名,如果返回值是 OK,那么在 nd.ni_vp中就返回

vnode结构的指针。v-node的类型必须是VSOCK,并且当前进程对插口一定要有写权限。 5. 验证插口是否已绑定到路径名 399-403

一个插口当前必须被绑定到路径名上,这就是说,在 v - n o d e中的v _ s o c k e t指针

必须非空。如果情况不是这样,连接就要被拒绝。如果服务器当前没有运行,但是在上一次 运行时路径名留在文件系统中,这种情况就有可能发生。 6. 验证插口类型 404-407

连接的客户进程插口 (s o)的类型必须与被连接的服务器进程插口 (s o 2)的类型相

同。也就是说,一个流插口不能连接到一个数据报插口或者相反。 图1 7 - 1 9描述了 u n p _ c o n n e c t 函数的剩余部分,它首先处理连接流插口,然后调用 unp_connect2去链接两个 unpcb结构。

图17-19 unp_connect 函数:第二部分

7. 连接流插口 408-415

流插口需要特殊处理,因为必须根据监听插口创建一个新的插口。首先,服务器

插口必须是监听插口: S O _ A C C E P T C O N N标志必须被设置 (由卷2图1 5 - 2 4的s o l i s t e n函数来 完成)。然后调用 s o n e w c o n n创建一个新的插口, s o n e w c o n n还把这个新的插口放到监听插 口的未完成的连接队列中。 8. 复制绑定到监听插口的名字 416-418

如果监听插口包含一个指向 m b u f的指针, m b u f包含一个 s o c k a d d r _ u n,并且

s o c k a d d r _ u n带有绑定到插口的路径名 (这应当总是对的 ),那么调用 m _ c o p y将该m b u f复制 给新创建的插口。 图17-20给出了在so2=so3赋值之前的不同结构的状态,步骤如下:

194

计计第三部分 Unix域协议

下载

• 服务器进程调用 s o c k e t创建最右边的 f i l e、s o c k e t和u n p c b结构,然后调用 b i n d 创建对 v n o d e 和包含路径名的 m b u f的引用。随后调用 l i s t e n,允许客户进程发起连 接。 • 客户进程调用 s o c k e t 创建最左边的 f i l e 、 s o c k e t 和 u n p c b 结构,然后调用 connect,connect调用unp_connect。 • 我们称中间的 s o c k e t 结 构 为 “ 已 连 接 的 服 务 器 插 口 ”,它由 s o n e w c o n n 创建, 客户机描述符

服务器监听描述符

由sonewconn创建

包含绑定到监听 插口的路径名

包含绑定到监听 插口的路径名

图17-20 流插口的connect 调用中的各种结构

195

第 17章 Unix域协议:实现计计

下载

sonewconn创建完该结构后发出 PRU_ATTACH请求,创建相应的 unpcb结构。 • s o n e w c o n n也调用 s o q i n s e q u e将刚产生的 s o c k e t放入监听插口的未完成的连接队 列中 ( 我们假定队列开始是空的 )。我们还看到监听插口的已完成连接队列 (s o _ q 和 so_qlen)为空,新建 socket的so_head指针反过来指向监听插口。 • u n p _ c o n n e c t调用m _ c o p y创建包含绑定到监听插口的路径名的 m b u f的副本,中间的 unpcb指向这个mbuf。我们将看到getpeername系统调用需要这个副本。 • 最后要注意的是,还没有一个 f i l e结构指向新建的 s o c k e t(事实上是通过s o n e w c o n n 设置S S _ N O F D R E F标志来说明这一点的 )。当监听服务器进程调用 a c c e p t时,就会给该 socket分配一个file结构和对应的文件描述符。 v n o d e指针没有从监听插口复制到连接的服务器插口。 v n o d e结构的唯一作用就是允许 客户进程通过v_socket指针调用connect定位相应的服务器的 socket结构。 9. 连接两个流或数据报插口 421

unp_connect中的最后一步是调用 unp_connect2(下一节描述 ),这对于流和数据报

插口是一样的。就图 1 7 - 2 0而言,该函数连接最左边的两个 u n p c b结构的u n p _ c o n n字段,并 且将新创建的插口从监听服务器的 s o c k e t的未完成连接队列移到已完成连接队列中,我们将 在后面的章节中描述最终的数据结构 (图17-26)。

17.11 PRU_CONNECT2请求和unp_conn e c t 2函数 图1 7 - 2 1中的P R U _ C O N N E C T 2请求仅仅作为 s o c k e t p a i r系统调用产生的一个结果,而 且这个请求只在 Unix域中得到支持。

图17-21 PRU_CONNECT2

请求

88-90 这个请求的所有处理工作都由 unp_connect2函数来完成,正如我们在图 17-22中看 到的一样, unp_connect2函数又是从内核中的其他两个地方调用的。 我们将在 1 7 . 1 2节介绍s o c k e t p a i r系统调用和 s o c o n n e c t 2函数,在 1 7 . 1 3节介绍 p i p e 系统调用。图17-23描述了unp_connect2函数。 1. 检验插口类型 426-434

两个参数都是指向 s o c k e t结构的指针: s o连接到 s o 2。首先检查两个插口的类

型是否相同:是流插口或者是数据报插口。 2. 把第一个插口连接到第二个插口 435-436

通过字段 u n p _ c o n n将第一个 u n p c b连接到第二个 u n p c b,然而,下面的步骤在

流和数据报之间是不同的。 3. 连接数据报插口 438-442

P C B的u n p _ n e x t r e f和u n p _ r e f s字段连接数据报插口。例如,考虑一个绑定

了路径名 / t m p / f o o的数据报服务器插口,然后一个数据报客户进程连接到这个路径名。图 1 7 - 2 4给出了在 u n p _ c o n n e c t 2返回后得到的 u n p c b结构(为了简便起见,我们没有描述相应

196

计计第三部分 Unix域协议

下载

系统调用

图17-22 unp_connect2

函数的调用者

图17-23 unp_connect2

函数

197

第 17章 Unix域协议:实现计计

下载

图17-24 连接的数据报插口

的 f i l e 或 s o c k e t 结构,或者与最右边插口相连接的

v n o d e ) 。我们描述了在

unp_connect2中用到的两个指针 unp和unp2。 对于一个已经有连接的数据报插口, u n p _ r e f s指向连接到该插口的所有插口的链表的 第一个PCB。通过unp_nextref指针遍历这个链表。 图1 7 - 2 5表示了第三个数据报插口 (左边的那个 )连接到同一服务器后的三个 P C B的状态, 绑定路径名都是 /tmp/foo。

图17-25 另一个插口 (左边)连接到右边的插口

两个P C B字段u n p _ r e f s和u n p _ n e x t r e f必须分开,因为图 1 7 - 2 5中右边的插口自己能 连接到其他的数据报插口。 4. 连接流插口 443-447

流插口的连接与数据报插口的连接是不同的,这是因为只能有一个客户进程连接

到一个流插口上 (服务器进程),客户进程和服务器进程的 P C B的u n p _ c o n n指针分别指向对方 的PCB,如图17-26所示(这个图是图 17-20的延续)。 这个图中的另一个变化是对于带有so2参数的soisconnected的调用,这个调用将插口从 监听插口的未完成连接队列(图17-20中的so_q0)移到已完成连接队列(so_q)中。accept要从这 个队列中获取新创建的插口 (卷2图15-34)。需要注意的是,soinconnected(卷2图15-30)设置 so_state中的SS_ISCONNECTED标志,仅当插口的so_head指针非空时才将socket从未完 成连接队列移到已完成连接队列(如果插口的so_head指针为空时,插口不在任何一个队列中)。 所以,在图17-23中,对带有so参数的soisconnected的第一次调用仅仅改变so_state。

198

计计第三部分 Unix域协议

下载

客户机描述符

服务器监听描述符

由sonewconn创建

包含绑定到监听 插口的路径名

包含绑定到监听 插口的路径名

图17-26 已建连的流插口

17.12 socketpair系统调用 s o c k e t p a i r系统调用仅在 U n i x域中得到支持。它创建两个插口并连接它们,同时返回 两个描述符,互相连接在一起。例如,一个用户进程发出调用:

下载

199

第 17章 Unix域协议:实现计计

创建一对连接在一起的全双工 U n i x域流插口。在 f d [ 0 ]中返回第一个描述符,在 f d [ 1 ]中返 回第二个描述符。如果第二个参数是 S O C K _ D G R A M,则创建一对互相连接的 U n i x域数据报插 口。如果调用成功, socketpair返回0;否则,返回- 1。 图17-27描述了socketpair系统调用的实现。

图17-27 socketpair 系统调用

200

计计第三部分 Unix域协议

下载

图17-27 (续)

1. 参数 229-239

四个整型参数,从 d o m i a n到r s v,在本节开始部分用户调用 s o c k e t p a i r的例

子中进行了描述。函数 s o c k e t p a i r定义中描述的三个参数 (p、u a p和r e t v a l)是传送到内 核中的系统调用的参数。 2. 创建两个插口和两个描述符 244-261 调用socreate两次,创建两个插口。两个描述符中的第一个由 falloc分配。在 f d中返回描述符的值,而指向相应 f i l e结构的指针在 f p 1中返回。设置 F R E A D和F W R I T E标 描述符

描述符

图17-28 由sockerpair 创建的两个流插口

201

第 17章 Unix域协议:实现计计

下载

志 (由于插口是全双工的 ),文件类型设置为 D T Y P E _ S O C K E T,设置f _ o p s指向五个插口函数 指针的数组(卷2图15-13),设置f _ d a t a指向s o c k e t结构。第二个描述符由 f a l l o c分配,并 且初始化相应的 file结构。 3. 连接两个插口 262-270 soconnect2发出PRU_CONNECT2请求,这个请求仅在 Unix域中得到支持。如果 系统调用正在创建流插口,立即从 soconnect2中返回。此时的结构如图 17-28所示。 如果创建两个数据报插口,就需要调用 s o c o n n e c t 2两次,每一次调用连接一个方向。 两次调用以后,我们就有了图 17-29中的结构。 描述符

描述符

图17-29 由sockerpair 创建的两个数据报插口

4. 将两个描述符复制给进程 271-274 copyout将两个描述符复制给进程。 带有注释 X X X ? ? ?的两个表达式第一次出现在 4.3BSD Reno 版本里。因为 c o p y o u t把两个描述符复制给进程,所以不需要这两个表达式。我们将看到 p i p e系 统调用通过设置 r e t v a l [ 0 ]和r e t v a l [ 1 ]返回两个描述符,其中 r e t v a l是系统调 用的第三个参数。内核中处理系统调用的汇编子程序总是将两个整数 r e t v a l [ 0 ]和 r e t v a l [ 1 ]放在机器的寄存器里作为任何系统调用的返回值。但是在用户进程中,

202

计计第三部分 Unix域协议

下载

激活系统调用的汇编子程序必须查看这些寄存器并且返回进程希望得到的值。 C函数 库中的pipe函数实际上是这样做的,但是 socketpair函数并不这么做。 5. soconnect2函数 图17-30中的函数发出PRU_CONNECT2请求,该函数仅在socketpair系统调用中被调用。

图17-30 soconnect2 函数

17.13 pipe系统调用 图17-31中的pipe系统调用与 socketpair系统调用几乎相同。

图17-31 pipe 系统调用

203

第 17章 Unix域协议:实现计计

下载

图17-31 (续)

654-686 调用socreate创建两个Unix域流插口, pipe系统调用与 socketpair系统调用 的唯一差别就是 p i p e把两个描述符中的第一个设置成只读 ( r e a d - o n l y ),把第二个设置成只写 ( w r i t e _ o n l y );两个描述符由 r e t v a l 参数返回,而不是通过 c o p y o u t;p i p e直接调用 unp_connect2,而不是通过soconnect2函数。 Unix的一些版本,特别是 SVR4,创建的管道两端均可进行读写。

17.14 PRU_ACCEPT请求 对于一个流插口,接受一个新的连接所需的大部分处理工作由其他内核函数完成: s o n e w c o n n创建新的 s o c k e t结构,并发出 P R U _ A T T A C H请求, a c c e p t系统调用将插口从 已完成连接队列中删除并调用 s o a c c e p t。s o a c c e p t(卷2 )仅发出P R U _ A C C E P T请求,用于 Unix域的PRU_ACCEPT请求。如图 17-33所示。 返回客户进程的路径名 94-108

如果客户进程调用 b i n d,并且同客户进程的连接仍然存在,那么这个请求把含有

客户进程路径名的 s o c k a d d r _ u n 复制到由 n a m 参数指向的 m b u f。否则,返回空路径名 (sun_nonname)。

图17-32 PRU_DISCONNECT

请求

图17-33 PRU_ACCEPT 请求

204

计计第三部分 Unix域协议

下载

图17-33 (续)

17.15 PRU_DISCONNECT请求和unp_di s c o n n e c t函数 如果插口已建连, close系统调用就发出 PRU_DISCONNECT请求,如图 17-32所示。 91-93 p_disconnect函数完成所有的断连工作,如图 17-34所示。

图17-34 unp_disconnect

函数

1. 检查插口是否有连接 458-460

如果插口没有连接到其他插口,则函数立即返回;否则就将 unp_conn置为0。表

明这个插口没有连接到其他插口。

205

第 17章 Unix域协议:实现计计

下载 2. 将关闭的数据报 PCB从链表中删除 462-478

这部分代码把关闭插口的 PCB从已连接数据报 PCB的链表中删除。例如,如果我

们从图 1 7 - 2 5 开始,然后关闭最左边的插口,就得到图 1 7 - 3 5 中的数据结构。由于 u n p 2 > u n p _ r e f s等于u n p(被关闭的PCB是链表的头 ),所以被关闭的 PCB的u n p _ n e x t r e f指针成 为新的链表头。 正在关闭的插口

图17-35 最左边插口关闭后图 17-25中的链表所发生的变化

如果我们再从图 17-25开始,关闭中间的插口,就得到图 17-36中的数据结构。这一次被关 闭插口的 P C B就不是链表的头。 u n p 2从链表的头开始查看被关闭的 P C B之前的 P C B。删除关 闭的P C B之后,u n p 2就指向图 1 7 - 3 6中最左边的 P C B。然后将关闭 P C B的u n p _ n e x t r e f指针 赋给链表(unp)上前一个PCB的unp_nextref。 正在关闭的插口

图17-36 中间插口关闭后图 17-25中的链表所发生的变化

3. 完成流插口的断连 479-483

由于一个U n i x域流插口只能同一个流插口建连,因而不涉及到链表,断开连接就

比较简单。将连接对方的 u n p _ c o n n 指针置为 0,并且对客户进程和服务器进程均调用 soisdisconnected。

17.16 PRU_SHUTDOWN请求和unp_shut d o w n函数 当进程调用shutdown禁止任何进一步输出时,发出PRU_SHUTDOWN请求,如图17-37所示。

206

计计第三部分 Unix域协议

图17-37 PRU_SHUTDOWN

109-112

下载

请求

s o c a n t s e n d m o r e设置插口标志禁止任何进一步的输出,然后调用图 1 7 - 3 8中的

unp_shutdown函数。

图17-38 unp_shutdown

函数

如果是流插口通知对等插口 499-502

对于数据报插口不需要再做什么。但是,如果这个插口是流插口,并且还与另一

个插口相连,且对等端插口还有一个 socket结构,则对对等端插口调用 socantrcvmore。

17.17 PRU_ABORT请求和unp_drop函数 如果插口是一个监听插口,并且未完成的连接依然在队列中,那么

s o c l o s e就发出

P R U _ A B O R T请求,如图 1 7 - 3 9所示。 s o c l o s e对在未完成连接队列和已完成连接队列中的每 一个插口都发出这个请求 (卷2图15-39)。

图17-39 PRU_ABORT 请求

209-211

图17-40中的unp_drop函数产生一个ECONNABORTED错误,我们在图 17-14中看

到,unp_detach也调用带有参数 ECONNRESET的unp_drop函数。

图17-40 unp_drop 函数

207

第 17章 Unix域协议:实现计计

下载

图17-40 (续)

1. 保存错误,断开插口连接 509-510

设置插口的 so_error值,并且如果插口上有连接,就调用 unp_disconnect。

2. 如果插口在监听服务器的队列上,就删除数据结构 511-516

如果插口的 so_head指针非空,那么插口当前不是在监听插口的未完成连接队列

上,就是在监听插口的已完成连接队列上。从

s o c k e t 到 u n p c b 的指针都置为 0 ,调用

m _ f r e e m释放包含绑定到监听插口的路径名的 m b u f (回想图1 7 - 2 0 ),下一次调用 m _ f r e e释放 u n p c b结构。s o f r e e释放s o c k e t结构。由于插口在监听服务器的任何一个队列中,所以还 没有与它相对应的 f i l e 结构,因为该结构是在插口从已完成连接队列中被删除时调用 accept分配的。

17.18 其他各种请求 图17-41描述了其余六个尚未讨论的请求。

图17-41 其他的PRU_ xxx请求

208

计计第三部分 Unix域协议

下载

图17-41 (续)

1. PRU_SENSE请求 212-217

这个请求是由 f s t a t系统调用发出的。将插口发送缓存高水位标记的当前值赋给

s t a t结构的s t _ b l k s i z e作为返回值。另外,如果这个插口是一个有连接的流插口,那么将 对等端插口接收缓存中的字节数加到这个值上。当我们讨论 18.2节中的P R U _ S E N D请求时会看 到,这两个值之和就是两个相连的流插口间的实际“管道”容量。 218 将st_dev置为NODEV(所有比特为全1的常数值,代表一个不存在的设备 )。 219-221 I-node号标识文件系统中的文件。该值 (stat结构的st_ino字段)是作为一个 Unix 域插口的 i - n o d e号返回的,它是从全局变量 u n p _ i n o得到的一个唯一值。如果还没有为 u n p c b分配一个这类伪 i-node号,就将 u n p _ i n o的当前值赋给该 u n p c b作为其i-node号,然后 将u n p _ i n o加1。之所以称这些 i - n o d e号为伪 i - n o d e号,是因为它们并不是文件系统中的实际 文件。它们仅在需要时由一个全局计数器产生。如果要求将 U n i x域插口绑定到文件系统中的 一个路径名(不是这种情况 ),P R U _ S E N S E请求就能使用s t _ d e v和s t _ i n o值来代替绑定路径 名。 全局变量 u n p _ i n o的递增应当在赋值之前而不是在赋值之后完成。在内核重启 后,对Unix域插口第一次调用 f s t a t时,存储在插口 u n p c b中的值将为0。但是,如 果对相同的插口再次调用 f s t a t ,由于 u n p c b 中的当前值是 0,所以将全局变量 unp_ino的非0值保存在其 PCB中。 2. PRU_RCVOOB和PRU_SENDOOB请求 223-227 Unix域不支持带外数据。 3. PRU_SOCKADDR请求 228-235

这个请求返回绑定到插口的协议地址 (在U n i x域插口中为路径名 )。如果路径名绑

定到插口, u n p _ a d d r就指向包含存储路径名的 s o c k a d d r _ u n的m b u f。u i p c _ u s r r e q的 n a m参数指向由调用者分配的、用于接收结果的 m b u f。调用 m _ c o p y产生插口地址结构的副 本。如果路径名没有绑定到插口,那么将 mbuf的长度域设置为 0。 4. PRU_PEERADDR请求 236-243

处理这个请求与前一个请求相似,但是期望的路径名是绑定到与发起连接的插口

相连的插口的名字。如果发起连接的插口已连接到一个对等端插口,那么 unp_conn非空。 没有绑定路径名的插口对这两个请求的处理与 P R U _ A C C E P T请求的处理不同 (图 1 7 - 3 3 )。当没有名字存在时, g e t s o c k n a m e和g e t p e e r n a m e系统调用通过第三个

下载

209

第 17章 Unix域协议:实现计计

参数返回 0。而 a c c e p t函数通过第三个参数返回 1 6,通过第二个参数返回包含在 s o c k a d d r _ u n中由空字节组成的路径名 (s u n _ n o n a m e是一个通用的 s o c k a d d r结 构,它的长度是 16个字节)。 5. PRU_SLOWTIMO请求 244-245 由于Unix域协议不使用定时器,所以从来不会发出这个请求。

17.19 小结 我们在本章看到的 U n i x域协议实现简单直观。它提供了流和数据报插口,其中流协议类 似于TCP,数据报协议类似于 UDP。 路径名能绑定到 U n i x域插口。服务器进程绑定其知名的路径名,客户进程连接到这个路 径名。数据报插口也可以建连,与 U D P一样,多个客户进程可以连接到同一个服务器进程上。 S o c k e t p a i r函数也可以创建尚未命名的 U n i x域插口。 Unix p i p e系统调用能创建两个互相 连接的Unix域流插口,源于伯克利系统的管道实际上就是 Unix域流插口。 与U n i x域插口有关的协议控制块是 u n p c b结构。与其他域不同的是这些 P C B并不保存在 一个链表中。然而,当一个 U n i x 域插口需要与另一个 U n i x 域插口同步时 ( c o n n e c t 或 s e n d t o),通过内核中的路径名查找函数 n a m e i 来定位目的 u n p c b,函数 n a m e i得到一个 vnode结构,通过这个结构得到目的 unpcb。

下载

第18章 Unix 域协议:I/O和描述符的传递 18.1 概述 本章继续描述上一章的 U n i x 域协议实现。本章的第一节讲述

I/O、PRU_SEND和

PRU_RCVD请求,其余部分介绍描述符传递。

18.2 PRU_SEND和PRU_RCVD请求 无论什么时候,当一个进程给 U n i x域插口发送数据或者控制信息时都要发出 P R U _ S E N D 请求。请求的第一部分首先处理控制信息,然后处理数据报插口,如图 18-1所示。

图18-1 数据报插口的 PRU_sSEND 请求

211

第18章 Unix域协议:I/O和描述符的传递计计

下载 1. 初始化所有控制信息 141-142

如果进程使用sendmsg发送控制信息,函数 unp_internalize将嵌入的描述符

转换成file指针,我们将在 18.4节中描述这个函数。 2. 暂时连接一个无连接的数据报插口 146-153 如果进程传送一个带有目的地址的插口地址结构 (也就是说, nam参数非空),那么 插口必须是无连接的,否则返回 E I S C O N N错误。通过 u n p _ c o n n e c t连接无连接的插口。暂 时连接一个无连接的数据报插口的代码与卷 2图23-15的UDP代码相似。 154-159

如果进程没有传递一个目的地址,那么对于一个无连接的插口就返回 E N O T C O N N

错误。 3. 传递发送者的地址 160-164

s o 2指向目的插口的 s o c k e t结构。如果发送插口 (u n p)已经绑定了一个路径名,

f r o m 就指向包含路径名的 s o c k a d d r _ u n 结构;否则, f r o m 指向 s u n _ n o n a m e , sun_noname是一个以空字节作为路径名首字符的 sockaddr_un结构。 如果一个 U n i x域数据报的发送者没有绑定一个路径名到它的插口,数据报的接 收者由于没有目的地址 (例如,路径名 )而不能用sendto发送应答。这就与 UDP不同, 当数据报第一次到达一个未绑定的数据报插口时,协议就会自动为其分配一个临时 的端口号。 U D P能为应用程序自动选择端口号的一个原因是这些端口号仅由 U D P使 用。然而,文件系统中的路径名并不是仅为 U n i x域插口保留。因而为一个没有绑定 的Unix域插口自动选择路径名可能会在后面产生冲突。 是否需要一个应答取决于应用程序。例如, s y s l o g函数没有绑定一个路径名到 它的U n i x域数据报插口,它仅发送报文到本地 s y s l o g d守护进程而不想得到一个应 答。 4. 把控制、地址和数据 mbuf添加到插口接收队列 165-170

sbappendaddr将控制信息 (如果需要)、发送者地址和数据添加到接收插口的接

收队列。如果函数调用成功, s o r w a k e u p就要唤醒所有等待这些数据的接收者,为了防止 m b u f指针m和c o n t r o l在函数结束时被释放,将它们全置为 0 (图1 7 - 1 0 )。如果出现错误 (可能 因为在接收队列上没有足够空间来存放数据、地址和控制信息 ),就返回 ENOBUFS。 处理这种错误与 U D P不同。如果在接收队列上没有足够的空间,使用 U n i x域数 据报插口的 s e n d e r就会收到从它的输出操作返回的错误。同 UDP一样,如果在接口 输出队列上有足够的空间,那么发送者的输出操作就会成功。如果接收 U D P发现在 接收插口的接收队列上没有空间,它通常发送一个 ICMP端口不可达的错误给发送者, 但是如果发送者没有连接到接收者,它也就不可能收到这个错误 (如同卷2第6 0 0 ~ 6 0 1 页描述的一样 ) 。为什么当接收者的缓存满时 U n i x 域发送者不阻塞,而是收到 E N O B U F S错误?传统上,数据报不保证可靠的数据传输。 [Rago1993]认为,在 SVR4 下编译内核时,是否给 Unix域数据报插口提供流量控制是由厂家来决定的。 5. 暂时断开与相连插口的连接 171-172 unp_disconnect断开暂时连接的插口。

212

计计第三部分 Unix域协议

下载

图18-2给出了对于流插口的 PRU_SEND请求的处理。

图18-2 流插口的 PRU_SEND 请求

6. 验证插口状态 175-183

如果插口的发送方已经关闭,就返回 E P I P E。因为 s o s e n d验证需要一个连接的

插口是否已建立连接,所以这个插口必须已建连,否则调用 panic(卷2图16-24)。 第一次测试好像是一个早期版本中遗留下来的, s o s e n d已经做了这个测试 (卷2图 16-24)。 7. 把mbuf添加到接收缓存 184-194

so2指向接收插口的socket结构。如果进程使用sendmsg传送了控制信息,那么

控制m b u f和任何数据 m b u f都要通过 s b a p p e n d c o n t r o l添加到接收插口的接收缓存。否则, sbappend将数据mbuf添加到接收缓存。如果sbappendcontrol失败,为了防止在函数结尾 调用m_freem,将control指针设置为0(图17-10),因为sbappendcontrol已经释放了mbuf。 8. 更新发送者和接收者的计数器 (端到端的流量控制 ) 195-199

对于发送者要更新两个变量: s b _ m b m a x(缓存中所有 m b u f允许的最大字节数 )和

213

第18章 Unix域协议:I/O和描述符的传递计计

下载

s b _ h i w a t(缓存中允许存放实际数据的最大字节数 ),在卷2的图16-24中我们注意到,对 mbuf 所做的限制防止了大量小报文消耗太多的 mbuf。 对于U n i x域流插口,这两个限制指的是接收缓存和发送缓存中的两个计数器的和。例如, 一个Unix域流插口的发送缓存和接收缓存的 s b _ h i w a t初始值都是 4096(图17-2)。如果发送者 把1 024字节写到插口上,不仅接收者的 sb_cc(插口缓存中的当前字节数 )从0增长到1024(正如 我们所希望的 ),而且发送者的 s b _ h i w a t从4 0 9 6减到3 0 7 2 (这是我们所不希望的 )。对于其他 协议如TCP,如果没有显式设置插口的选项,缓存的 s b _ h i w a t值决不会变化。 s b _ m b m a x也 是一样:当接收者的 sb_mbcnt值增加时,发送者的 sb_mbmax值下降。 因为发送给 U n i x域流插口的数据从来不会放在发送插口的发送缓存中,所以要改变发送 者的缓存限制和接收者的当前计数。数据被立即加到接收插口的接收缓存中,没有必要浪费 时间把数据放到发送插口的发送队列上,然后立即或晚些时候把它发送到接收队列上。如果 接收缓存中没有空闲空间,发送者就要被阻塞。但是,如果 s o s e n d阻塞发送者,发送缓存中 的空间大小必须反映相应接收缓存中的空间大小。代替修改发送缓存数,当发送缓存中没有 数据时,很容易修改发送缓存限制来反映相应接收缓存中的空间大小。 198-199

如果我们只是检验发送者的 s b _ h i w a t和接收者的 u n p _ c c的操作(s b _ m b m a x和

u n p _ m b c n t 的操作也基本相同 ),在这一点上由于数据刚被添加到接收缓存,所以 r c v >sb_cc就等于接收缓存中的字节数。 u n p - > u n p _ c o n n - > u n p _ c c是r c v - > s b _ c c的前一个 值,所以它们之间的差值就是刚刚添加到接收缓存的字节数 (也就是写的字节数 )。同时,将 s n d - > s b _ h i w a t的值减去相同的字节数 (刚写的字节数 )。接收缓存中的当前字节数保存在 unp->unp_conn->unp_cc中,所以下一次通过这段代码我们能计算出写了多少数据。 例如,当创建插口时,发送者的 s b _ h i w a t是4 0 9 6,接收者的 s b _ c c和u n p _ c c都为0。 如果写了 1 024 字节,那么发送者的 s b _ h i w a t变为3 0 7 2,接收者的 s b _ c c 和u n p _ c c都是 1 024。在图18-3中我们还将看到,当接收进程读这 1 024个字节时,发送者的 s b _ h i w a t增加 到4096,而接收者的sb_cc和unp_cc都降为0。 9. 唤醒等待数据的所有进程 200-201

s o r w a k e u p唤醒等待数据的任何进程,由于 m b u f现在在接收队列上,所以为了

防止在函数结尾调用 m_freem,将m设置为0。 图1 8 - 3中I / O 代码的最后部分是 P R U _ R C V D请求,当从一个插口读数据并且协议设置 PR_WANTRCVD标志时,soreceive发出这个请求(卷2图16-51),图17-5中对Unix域流协议设置 这个标志。这个请求的目的是当插口层把数据从一个插口的接收缓存中移走时让协议层获得控 制。例如,由于插口接收缓存中现在有更多的自由空间, TCP使用这个请求来判断是否应该将 新的窗口宽度发送到对端,Unix域流协议使用这个请求去更新发送者和接收者的缓存计数器。 10. 检查对等实体是否终止 121-122

如果写数据的对等实体已经结束,不需做任何工作。注意,接收者的数据并不丢

弃;然而,由于发送进程关闭了它的插口,所以发送者的缓存计数器就不能更新。由于发送 者不再往插口写任何数据,所以没有必要更新缓存计数器。 11. 更新缓存计数器 123-131

s o 2指向发送者 s o c k e t结构。根据读到的数据来更新发送者的 s b _ m b m a x和

sb_hiwat。例如,unp->unp_cc减去rcv->sb_cc就是所读到的数据字节数。

214

计计第三部分 Unix域协议

下载

图18-3 PRU_RCVD 请求

12. 唤醒任何发送数据进程 当从接收队列读数据时,增加发送者的 sb_hiwat。由于可能有空间,所以任何等待往

132

插口写数据的进程都被唤醒。

18.3 描述符的传递 描述符的传递对于进程间通信来说是一项重大的技术。 [Stevens 1992]的第15章在4.4BSD 和S V R 4下有使用这种技术的例子。虽然在这两种实现中的系统调用不同,但是那些例子提供 了对应用程序屏蔽实现差异的库函数。 历史上描述符传递一直被称为访问权 (access right) 。描述符代表一种对底层对象执行 I / O的权力 (如果我们没有这个权力,内核就不会为我们打开描述符

)。但是这个能力仅在

打开描述符的进程环境中才有意义。例如,将描述符号,假定等于

4,从一个进程传到另

一个进程,但并不传递这些权力,因为在接收进程中描述符

4也许并没有打开,并且即使

已经打开了,它代表的文件也可能与发送进程中所代表的文件不相同。描述符只是一个在 给定进程中才有意义的标识符。一个描述符以及与其相联系的权力从一个进程传送到另一 个进程需要从内核得到额外的支持。唯一能从一个进程传到另一个进程的访问权就是描述 符。 图18-4显示了涉及到将描述符从一个进程传到另一个进程的数据结构。传送过程如下:

215

第18章 Unix域协议:I/O和描述符的传递计计

下载 服务器

Unix 域流插口

任何类型的 描述符

客户 Unix 域流插口

图18-4 在描述符传递中涉及到的数据结构

1) 我们假定最上面进程是一个从 U n i x域流插口上接受连接的服务器进程。客户进程是最 下面的进程,它创建一个 Unix域流插口并与服务器进程的插口建连。客户进程用 fdm引 用它的插口,而服务器进程用 f d i来引用它的插口。在这个例子中我们用的是流插口, 但是我们还将看到描述符传递也能在 U n i x域数据报插口间进行。我们也假定 1 7 . 1 0节中 a c c e p t返回的 f d i作为服务器进程的连接插口,为了简单起见,我们不显示服务器进 程监听插口的结构。 2) 服务器进程还打开另一个文件,并用 f d j来访问它。通过描述符访问的文件可能是任何 类型的文件:文件、设备、插口,等等,我们用 v n o d e来表示这类文件。文件的访问 计数,也就是它的 file结构的f_count字段,在文件第一次打开时等于 1。 3) 服务器在fdi上调用sendmsg发送包含一个类型值 SCM_RIGHTS和 fdj 值的控制信息。从 而将描述符传送给接收者,即客户进程中的 fdm。将与 fdj 相联系的file结构中的引用 数增加到2。 4) 客户进程在 f d m上调用带有控制信息缓存的 r e c v m s g,返回的控制信息有一个类型值 SCM_RIGHTS和 fdn值,在客户进程中 fdn是最低的、尚未使用的描述符。 5) 在服务器进程中,当sendmsg返回后,服务器通常会关闭刚才传送的描述符 (fdj)。这会 导致引用计数减到 1。我们说在sendmsg和recvmsg之间描述符在“传送中”(in flight)。 三个计数器由内核负责维护,描述符传递中要用到它们。

216

计计第三部分 Unix域协议

下载

1) f _ c o u n t是f i l e结构的一个字段,用来记录该结构的引用次数。当多个描述符共享 相同的f i l e结构时,这个字段等于描述符数。例如当一个进程打开一个文件时,该文 件的f _ c o u n t为1。如果进程接着调用 f o r k,由于 f i l e结构在父进程和子进程间共 享,所以 f _ c o u n t的值变为 2,并且父进程和子进程都有一个描述符指向相同的文件 结构。当一个描述符被关闭时, f _ c o u n t值以1递减,如果值减到 0,相应的文件或插 口被关闭,并且 file结构能重新使用。 2) f _ m s g c o u n t也是f i l e结构的一个字段,但是它仅在传送描述符时等于非 0。当描述 符由 s e n d m s g 传送时, f _ m s g c o u n t 以1 递增。当 r e c v m s g 接收到描述符时, f _ m s g c o u n t以1递减。 f _ m s g c o u n t值是这个 f i l e结构的引用数, f i l e结构由插 口接收队列中的描述符保持着 (即目前是在传送中 )。 3) u n p _ r i g h t s是一个全局变量,用来记录当前正被传送的描述符个数,也就是当前插 口接收队列中的描述符总数。 对于一个已打开,但还没有被传送的描述符,f_count的值大于0,f_msgcount的值等于0。 图18-5显示了当一个描述符传送时三个变量的值,我们假定当前内核没有传送其他的描述符。

发送方执行 o p e n后 发送方执行 s e n d m s g后 在接收方的队列上 接收方执行 r e c v m s g后 发送方执行 c l o s e后

f_count

f_msgcount

unp_rights

1 2 2 2 1

0 1 1 0 0

0 1 1 0 0

图18-5 描述符传送过程中内核变量的值

在这个图中我们假定,接收者的 r e c v m s g返回后发送者关闭描述符。但是在接收者调用 r e c v m s g之前,允许发送者在描述符传递过程中关闭它,图 1 8 - 6表示了这种情况发生时三个 变量的值。 f_count 发送方执行 o p e n后 发送方执行 s e n d m s g后 在接收方的队列上 发送方执行 c l o s e后 在接收方的队列上 接收方执行 r e c v m s g后

1 2 2 1 1 1

f_msgcount 0 1 1 1 1 0

unp_rights 0 1 1 1 1 0

图18-6 描述符传送过程中内核变量的值

无论发送者在接收者调用 r e c v m s g之前或之后关闭描述符,最终结果都是一样的。我们 从上面两个图中也能看到, s e n d m s g增加所有的三个计数器,而 r e c v m s g只减少表中的最后 两个计数器。 用来传送描述符的内核代码从概念上看是比较简单的。将传送的描述符转换成相应的 f i l e指针并传送到 U n i x域插口的另一端。在接收进程中,接收者把 f i l e指针转换为最低的、 没有使用的描述符。然而在处理可能的错误时就有问题了,例如,当一个描述符在它的接收

217

第18章 Unix域协议:I/O和描述符的传递计计

下载

队列上时,接收进程就能关闭它的 Unix域插口。 将一个描述符转换成相应的 f i l e指针叫做内部化 ( i n t e r n a l i z i n g ),在接收进程中,随后的 f i l e指针转换成最低的。没有使用的描述符叫做外部化 (externalizing)。如果进程传送控制信 息,图 1 8 - 1 中的 P R U _ S E N D 请求将调用 u n p _ i n t e r n a l i z e 函数。如果进程正在读 MT_CONTROL类型的一个 mbuf,soreceive就调用unp_externalize函数(卷2图16-44)。 图18-7显示了被进程传送到 s e n d m s g的控制信息的定义,这里控制信息用于传送描述符。 当接收到一个描述符时, recvmsg填充相同类型的一个结构。

图18-7 cmsghdr 结构

例如,如果进程发送两个描述符,它们的值分别是 3和7,图1 8 - 8给出了控制信息的格式。 我们还给出了msghdr结构中描述控制信息的两个字段。

图18-8 传送两个描述符的控制信息的例子

通常一个进程使用一个 s e n d m s g能发送任意个描述符,但是传送描述符的应用程序典型 情况下只传送一个描述符。有一个内部约束限制着控制信息总的大小必须适合一个

m b u f (由

s o c k a r g s函数强加的,这个 s o c k a r g s函数又是被s e n d i t函数调用的,分别见卷 2图1 5 - 2 0 和图16-21),这样就限制了任何进程最多只能传送 24个描述符。 在4.3BSD Reno之前,m s g h d r结构的m s g _ c o n t r o l和m s g _ c o n t r o l l e n字段 分别为msg_accrights和msg_accrightslen。 明显冗余的 c m s g _ l e n字段总是等于 m s g _ c o n t r o l l e n,这其中的原因是允许 多条控制信息出现在同一个控制缓存中,但是我们将看到源代码不支持这种情况, 而是要求每个控制缓存仅有一个控制报文。 对于一个 U D P数据报, I n t e r n e t域中支持的唯一控制信息是返回目的 I P地址(卷2 图23-25)。对于各种特定 OSI用途的OSI协议支持四种不同类型的控制信息。 图1 8 - 9总结了在发送和接收描述符过程中调用的函数,带阴影的函数在本卷中讲述, 其余的函数见卷 2。 图1 8 - 1 0总结了 u n p _ i n t e r n a l i z e和u n p _ e x t e r n a l i z e对用户控制缓存和内核 m b u f 中的描述符和file指针的各种操作。

218

计计第三部分 Unix域协议

下载

系统调用

将控制信 息复制到 mbuf中

uipc_usrreq

将数据和控制 mbuf添加到接 收插口的接收 缓存中

unp_internalize 将描述符转换成 file指针 发送进程 接收插口的 接收缓存 接收进程

unp_externalize

将file指针转 换为描述符

从mbuf中复制控 制信息

系统调用

图18-9 在传送描述符过程中涉及到的函数

18.4 unp_internalize函数 图1 8 - 11描述了 u n p _ i n t e r n a l i z e函数。正如我们在图 1 8 - 1中看到的一样,当发出 PRU_SEND请求并且进程正传送描述符时, uipc_usrreq调用这个函数。 1. 验证cmsghdr字段 564-566 用户的cmsghdr结构必须指定类型 SCM_RIGHTS和级别SOL_SOCKET,并且它的 长度字段必须等于 m b u f中的数据量 (这是m s g h d r结构中m s g _ c o n t r o l l e n字段的一个副本, msghdr结构由进程传送到 sendmag)。

219

第18章 Unix域协议:I/O和描述符的传递计计

下载 用户cmsghdr{}

内核的mbuf{}

包含描述符的控 制信息

mbuf首部 (MT_CONTROL)

unp_internalize将 发送进程来的描述符替 换成相应的file指针

发送进程

接收进程

sbappendcontrol将 数据和控制mbuf添加到 接收插口的接收缓存 内核的mbuf{} mbuf首部 (MT_CONTROL)

unp_externalize用 接收进程新分配的描述 符替换file指针

用户 cmsghdr{} 包含描述符的控 制信息

图18-10 由unp_internalize

和unp_externalize

执行的操作

2. 验证传送描述符的有效性 567-574

o l d f d s设置为被传送的描述符数, r p指向第一个描述符。对于每一个被传送的

描述符, f o r循环验证这个描述符不会比当前被进程使用的最大描述符还大,以及指针非空 (即描述符已打开 )。 3. 用file指针替换描述符 575-578

将r p重新设置为指向第一个描述符, f o r循环用引用的 f i l e指针f p替换每一个

描述符。 4. 增加三个计数器 579-581

f i l e结构的f _ c o u n t和f _ m s g c o u n t元素递增,前者在每一次描述符关闭时递

减,而后者由 u n p _ e x t e r n a l i z e递减。另外,对于每一个由 u n p _ i n t e r n a l i z e传送的描 述符来说,全局变量 u n p _ r i g h t s递增。我们将看到,对于每一个由 u n p _ e x t e r n a l i z e接 受的描述符, u n p _ r i g h t s将递减。任何时候它的值都是当前内核中正在传送的描述符数。

220

计计第三部分 Unix域协议

图18-11 unp_internalize

下载

函数

我们在图 1 7 - 1 4中看到,当任何 U n i x域插口关闭,并且计数器非 0时,调用无用单元收集函数 unp_gc,以免关闭的插口在它的接收队列上包含任何正在传送的描述符。

18.5 unp_externalize函数 图1 8 - 1 2表示了 u n p _ e x t e r n a l i z e函数,当一个类型为 M T _ C O N T R O L的m b u f在插口的 接收队列上,并且进程正准备接收控制信息时, soreceive像调用dom_externalize函数 一样调用unp_externalize(卷2图16-44)。 1. 验证接收进程是否有足够的可用描述符 532-541 newfds是外部化的 mbuf中file指针的数目。fdavail是检验进程是否有足够可 用描述符的一个内核函数。如果没有足够的可用描述符,那么对于每一个描述符调用 unp_discard(在下一节描述),并且返回 EMSGSIZE给进程。 2. 把file指针转换成描述符 542-546

对于进程中每一个传送的 f i l e指针,最小的没有使用的描述符由 f d a l l o c来分

配。f d a l l o c的第二个参数 0告诉它不需要分配一个 f i l e结构,因为此时需要的只是一个描 述符。fdalloc通过f返回描述符。进程中的描述符指向 file指针。

221

第18章 Unix域协议:I/O和描述符的传递计计

下载 3. 递减两个计数器

547-548 对于每一个传送的描述符,两个计数器 f_msgcount和unp_rights都要递减。 4. 用描述符替换file指针 549

新分配的描述符替换 mbuf中的file指针,这是作为控制信息返回到进程中的值。 如果由进程传送到 r e c v m s g 的控制缓存不够,接收传送的描述符怎么办? u n p _ e x t e r n a l i z e仍然分配进程中需要的描述符数,描述符全部指向正确的 f i l e结 构,但是 r e c v i t(卷2的图1 6 - 4 4 )仅仅返回与进程分配的缓存相适应的控制信息。如 果导致控制信息的不完整截断,那么就要置上 m s g _ f l a g s字段中的 M S G _ C T R U N C标 志,进程通过测试这个标志来判断 recvmsg返回的控制信息的完整性。

图18-12 unp_externalize

函数

18.6 unp_discard函数 当判断出接收进程没有足够的可用描述符时,在图 1 8 - 1 2中对于每一个传送的描述符调用 图18-13中的unp_discard。 1. 递减两个计数器 730-731 f_msgcount和unp_rights两个计数器全都递减。

222

计计第三部分 Unix域协议

下载

图18-13 unp_discard 函数

2. 调用closef 732 closef关闭file,如果f_count现在是0,closef就减小f_count,并且调用描述 符的fo_close函数(卷2的图15-38)。

18.7 unp_dispose函数 回想图17-14中,如果全局变量 u n p _ r i g h t s非0(即有描述符在传送中 ),那么当关闭一个 U n i x域插口时, u n p _ d e t a c h函数就要调用 s o r f l u s h 。如果有定义,并且协议设置了 P R _ R I G H T S 标志, s o r f l u s h ( 卷 2 图 1 5 - 3 7 ) 执行的最后操作之一就是调用域的 d o m _ d i s p o s e函数。因为将要刷新 (释放)的mbuf也许包含正在传送中的描述符,所以需要执 行这个调用。由于 f i l e 结构中的两个计数器 f _ c o u n t 和f _ m s g c o u n t 以及全局变量 u n p _ r i g h t s都要由u n p _ i n t e r n a l i z e来递增,对于已传送但没有被接收的描述符,这些 计数器全都必须要调整。 Unix域的dom_dispose函数就是unp_dispose(图17-4),如图18-14所示。

图18-14 unp_dispose 函数

调用unp_scan 686-687

u n p _ s c a n完成的所有工作我们在下一节描述。该调用的第二个参数是指向函数

u n p _ d i s c a r d的一个指针,正如我们在上一节看到的一样, u n p _ d i s c a r d删除在插口接收 队列上unp_scan发现的控制缓存中的任何描述符。

18.8 unp_scan函数 从u n p _ d i s p o s e调用u n p _ s c a n函数,其第二个参数为 u n p _ d i s c a r d,并且这个函数 在后面的 u n p _ g c中也会被调用,其第二个参数为 u n p _ m a r k。我们在图 1 8 - 1 5中给出了 unp_scan。

223

第18章 Unix域协议:I/O和描述符的传递计计

下载

图18-15 unp_scan 函数

1. 查找控制mbuf 699-706

这个函数检查插口接收队列上 (m 0参数)所有的分组,并且扫描每一个分组的 mbuf

链去查找一个类型为 M T _ C O N T R O L 的 m b u f 。当发现一个控制报文时,如果层次是 SOL_SOCKET,类型是SCM_RIGHTS,那么mbuf包含没有被接收的传送中的描述符。 2. 释放保持的 file引用 707-716

q f d s 是控制信息中 f i l e 表指针的数量,对每一个 f i l e 指针调用 o p 函数

(u n p _ d i s c a r d或u n p _ m a r k)。o p函数的参数是控制信息中的 f i l e指针。当处理完该控制 mbuf时,执行 break,跳出循环,处理接收缓存中的下一个分组。 7 1 2行的注释 X X X表示:因为 b r e a k假定每个 m b u f链仅有一个控制 m b u f,这实 际上是对的。

18.9 unp_gc函数 我们 已 经 看 到用 来处 理传 送中 的描 述 符 的 无用 单元 收集 函数 的一 种 形 式 :在 u n p _ d e t a c h中,无论什么时候关闭一个 U n i x域插口,并且描述符在传送中, s o r f l u s h就 释放任何传送中的、包含在关闭插口接收队列上的描述符。然而,在 U n i x域插口间传送的描 述符也有可能“丢失”,在三种情况下这种事情可能发生。 1) 当描述符被传送时,一个类型为 M T _ C O N T R O L的m b u f由s b a p p e n d c o n t r o l(图1 8 - 2 ) 放在插口接收队列上。但是,如果接收进程调用 recvmsg却没有说明想接收控制信息,

224

计计第三部分 Unix域协议

下载

或者调用一个不能接收控制信息的其他输入函数, s o r e c e i v e就调用M F R E E,从插口 接收缓存中删除类型为 M T _ C O N T R O L的m b u f,并释放它 (卷2图1 5 - 4 4 )。但是,当由这 个m b u f引用的 f i l e结构被发送者关闭时,它的 f _ c o u n t和f _ m s g c o u n t将全为 1 (回 想图1 8 - 6 ),全局变量 u n p _ r i g h t s仍然表明这个描述符在传送中。这是一个没有被其 他任何描述符引用的 f i l e结构,并且将来也不会被一个描述符引用,但是仍在内核的 活动file结构链表上。 [Leffler et al.1989]的第305页讲到,问题是在报文被传送到插口层等待传送之 后,内核不允许协议再访问该报文;他们还后见之明地讲到,当一个类型为 MT_CONTROL的mbuf被释放时,这个问题应当由触发的每个域的处理函数来处理。 2) 当描述符被传送,但是接收插口没有空间存放这个报文时,不需要任何说明就丢弃这 个传送中的描述符。这种情况在一个 U n i x域流插口中应当不会发生,因为在 1 8 . 2节中 我们看到,发送者的高水位标记反映了接收者缓存中的空间大小,使得在接收缓存有 了空间之前发送者的高水位标记一直阻塞发送者。但是在一个 U n i x域数据报插口中可 能会失败,如果接收缓存没有足够的空间, s b a p p e n d a d d r(在图1 8 - 1中调用 )返回0, e r r o r设置为 E N O B U F S,在标号 r e l e a s e处的代码会删除包含控制报文的 m b u f,这 就如同在前一个例子中一样导致相同的情况:一个没有被任何描述符引用的 file结构, 并且将来也不会被一个描述符引用。 3) 当一个 U n i x域插口 f d i在另一个 U n i x域插口 f d j上传送时, f d j也在f d i上传送。如果两个 U n i x域插口在没有接收到传送的描述符时关闭,这些描述符就有可能丢失。我们将看 到4.4BSD直接处理了这个问题 (图18-18)。 开始两种情况的关键事实是,“丢失的” f i l e结构的f _ c o u n t等于它的 f _ m s g c o u n t(即 对这个描述符的引用是在控制报文中 ),并且 f i l e结构当前没有被内核中所有 U n i x域插口的 接收队列中任何控制报文引用。如果 f i l e结构的f _ c o u n t超过了它的 f _ m s g o u n t,那么差 别就是在引用结构的进程中描述符数,所以结构没有丢失 (一个f i l e的f _ c o u n t值必须不能 小于它的 f _ m s g c o u n t值,否则某些事情就要受到破坏 )。如果f _ c o u n t等于f _ m s g c o u n t, 但是f i l e结构被 U n i x域插口上的控制报文引用,由于一些进程仍然能从该插口接收描述符, 因而不会出现问题。 无用单元收集函数 unp_gc找到这些丢失的 file结构,并回收它们。调用 closef来回收 file结构,如图18-13所示,因为closef返回一个无用的file结构给内核的空闲缓存池。注 意这个函数仅在有传送中描述符时才调用,这就是说,仅当 unp_rights非0(图17-14)和一些 Unix域插口关闭时才调用这个函数。因而由于这个函数似乎涉及过多的开销,它应当很少调用。 unp_gc使用标记-回收(mark-and-sweep)算法去执行无用单元收集,这个函数的前一部分, 即标记阶段,检查内核中的每一个 f i l e结构,并把那些正在使用的置上标志: f i l e结构要 么被进程中的描述符引用,要么被 U n i x 域插口的接收队列上的控制报文引用 (这就是说, f i l e结构对应一个当前在传送中的描述符 )。函数的后一部分,即回收阶段,回收所有尚未 置上标志的 file结构,因为这些 file结构不在使用中。 图18-16给出了unp_gc的前半部分。 1. 防止函数被递归调用 594-596

全局变量u n p _ g c i n g防止函数被递归调用,因为 u n p _ g c能调用s o r f l u s h,而

225

第18章 Unix域协议:I/O和描述符的传递计计

下载

s o r f l u s h能调用 u n p _ d i s p o s e,u n p _ d i s p o s e能调用 u n p _ d i s c a r d,u n p _ d i s c a r d 能调用closef,closef能调用unp_detach,unp_detach又能再次调用unp_gc。 2. 清除FMARK和FDEFER标志 598-599 第一个循环检验内核里面的所有 file结构,并且清除 FMARK和FDEFER标志。 3. 循环到unp_defer等于零 600-622

只要unp_defer标志非0,就执行do while循环。我们将看到,一旦发现一个

以前处理过的 f i l e结构,就置上这个标志,我们认为这个 f i l e结构不在使用中,但是实际 上是在使用的。一旦这种情况发生,我们需要再次回过头来检查所有的 f i l e结构,因为有一 个可能,就是我们刚才标志为忙的结构本身就是一个 U n i x域插口,并且这个 U n i x域插口在它 的接收队列上包括 file引用。 4. 循环检查所有的 file结构 601-603

这个循环检查内核中的所有 f i l e结构,如果一个结构不在使用中 (f _ c o u n t等于

0),我们就跳过去。

图18-16 unp_gc 函数:第一部分,标记阶段

226

计计第三部分 Unix域协议

下载

5. 处理延迟的结构 604-606

如果已经置上 F D E F E R标志,那么就要关闭这个标志,并且 u n p _ d e f e r计数器也

要减小。当 u n p _ m a r k置上F D E F E R标志时,F M A R K标志也被置上,这样我们就知道这个记录 项在使用中,并且我们还将检查在 if语句的末尾是否是一个 Unix域插口。 6. 跳过已经处理过的结构 607-609 如果设置了 FMARK标志,那么记录项正在使用中,并且已经被处理过了。 7. 不标记丢失的结构 610-611

如果f_count等于f_msgcount,则这个记录项会可能丢失。它没有被标记,并

被跳过去了。由于它似乎不在使用中,所以我们不能检查它是否是一个在接收队列上有传送 中描述符的 Unix域插口。 8. 标记使用中的结构 612 在这一点上我们知道这个记录项在使用中,所以要置上 FMARK标志。 9. 检验结构是否与一个 Unix域插口相连 614-619

既然这个记录项在使用中,我们就检验看它是否是一个有 s o c k e t结构的插口。

下一次检验确定这个插口是否是带有 P R _ R I G H T S标志集的 Unix域插口。设置这个标志是为了 Unix域流和数据报协议。如果任何一个测试结果是错的,就要跳过这个记录项。 10. 扫描Unix域插口接收队列上传送中的描述符 620

在这一点上f i l e结构对应一个 U n i x域插口。 u n p _ s c a n遍历插口接收队列,寻找包含

传送中描述符的类型为 MT_CONTROL的mbuf。如果发现,就调用 unp_mark。 在此处,源代码也应当能处理 U n i x域插口的已完成连接队列 (s o _ q) [ M c K u s i c k et a l . 1 9 9 6 ],一个客户进程把描述符传送给一个新创建的还在等着接收的服务器插口 是完全可能的。 图1 8 - 1 7给出了一个标记阶段的例子,并且在标记阶段可能需要多次扫描 f i l e结构链表。 这个图描述了在标记阶段第一次扫描完成时的结构状态,此时 u n p _ d e f e r为1,需要再一次 扫描所有的 file结构。当从左至右处理四个 file结构时,开始下列处理过程。 1) f i l e结构在引用它的进程中有两个描述符 (f _ c o u n t等于2 ),但是没有引用传送中的 描述符 (f _ m s g c o u n t等于0 )。图1 8 - 1 6中的代码设置 f _ f l a g字段中的 F M A R K比特位。 这个结构指向一个 v n o d e(我们忽略了 f _ t y p e值的D T Y P E _前缀,另外我们仅仅给出 了f _ f l a g字段中的 F M A R K和F D E F E R标志,实际上其他标志值也有可能在这个字段上 出现)。 2) 因为f _ c o u n t等于f _ m s g c o u n t,所以这个结构好像没有被引用。当被标记阶段处理 后,f_flag字段不变。 3) 因为这个结构被进程中的一个描述符引用,所以要置上 F M A R K标志。还有,由于这个 结构对应于一个 Unix域插口,unp_scan还要处理插口接收队列上的任何控制报文。 控制报文中的第一个描述符指向第二个 f i l e 结构,并且由于在第二步中没有设置 FMARK标志,un p _ m a r k就要置上F M A R K和F D E F E R这两个标志。因为这个结构已经处 理过,并且发现没有被引用,所以 unp_defer也要增加到 1。 控制报文中的第二个描述符指向第四个 f i l e结构,并且由于没有置上 F M A R K标志(它 甚至还没有被处理过 ),因此就要置上 FMARK和FDEFER标志,unp_defer增加到2。

下载

227

第18章 Unix域协议:I/O和描述符的传递计计

4) 设置这个结构的 FDEFER标志,所以图18-16中的代码关闭这个标志,并将 unp_defer 减小到1。即使这个结构也被进程中的描述符引用,但是因为已经知道这个结构被传送 中的描述符引用,从而也就不需要检查它的 f_count和f_msgcount值。

图18-17 标记阶段第一次扫描后的数据结构

此时,所有四个 f i l e结构都被处理过了,但是 u n p _ d e f e r等于1,所以就需要再一次扫 描所有的结构。因为确信第一次循环没有引用过的第二个结构也许是一个 U n i x域插口,并且

228

计计第三部分 Unix域协议

下载

在这个 U n i x域插口的接收队列上有控制报文,所以要产生再一次循环 (这不在我们的例子中 )。 那个结构需要被再次处理,并且当情况是这样时,这次循环可能会在认为没有被引用的链表 中靠前的一些结构中置上 FMARK和FDEFER标志。 在标记阶段的结尾涉及到多次扫描内核的 f i l e结构链表,其中没有标志的结构不在使用 中。函数的第二段,即回收 (sweep)部分,如图 18-18所示。

图18-18 unp_gc 函数:第二部分,回收阶段

229

第18章 Unix域协议:I/O和描述符的传递计计

下载

图18-18 (续)

11. 更正错误的注释 623-661

注释涉及到 4.3BSD Reno 和N e t / 2版本里的一个错误,这个错误在 4 . 4 B S D里由

Bennet S. Yee更正,注释里提到的旧代码如图 18-19所示。 12. 分配临时区域 662

m a l l o c为指向内核中所有 f i l e结构的指针数组分配空间。 n f i l e s是当前使用中的

f i l e结构数量。 M _ F I L E标识使用内存的目的 (v m s t a t -m命令输出关于内核存储器使用的 信息)。如果当前得不到可用内存,那么 M_WAITOK导致进程转入睡眠状态。 13. 遍历所有的 file结构 663-665

为了发现所有没有引用的 (丢失的)结构,这个循环再次检查内核中的所有 f i l e结

构。 14. 跳过没有使用过的结构 666-667 如果file结构的f_count是0,就跳过这个结构。 15. 检查未引用的结构 668

如果在标记阶段, f _ c o u n t等于f _ m s g c o u n t(唯一的引用来自于传送中的描述符 ),

并且没有设置 F M A R K标志(传送中的描述符没有出现在任何 U n i x域插口接收队列上 ),那么这 个记录项是没有被引用的。 16. 保存指向file结构的指针 669-671

f p的一个副本,即指向 f i l e结构的指针,保存在分配的数组中,递增计数器

nunref,递减file结构的f_count。 17. 对没有引用的插口调用 sorflush 674-676

对每一个没有被引用的插口文件调用 s o r f l u s h函数。函数 s o r f l u s h(卷2的图

1 5 - 3 7 )调用域的 d o m _ d i s p o s e和u n p _ d i s p o s e函数, u n p _ d i s p o s e调用u n p _ s c a n删除 当前插口接收队列上任何传送中的描述符。

u n p _ d i s c a r d 递减 f _ m s g c o u n t 和

u n p _ r i g h t s,并且对在插口接收队列上控制报文中的所有 f i l e结构调用 c l o s e f。由于我 们对这个 f i l e结构(早些时候完成 f _ c o u n t的递增)有一个额外的引用,而且由于那个循环忽 略了 f _ c o u n t 为 0 的结构,从而我们确信 f _ c o u n t 等于 2 或者比 2 还要大。所以作为 s o r f l u s h的结果去调用 c l o s e f将把f i l e结构的 f _ c o u n t减小到一个非 0值,从而避免完 全关闭该结构。这就是为什么对结构的额外引用进行得比较早。 18. 执行最后的关闭 6 7 7 - 6 7 8 对所有没有引用的 f i l e结构调用 c l o s e f。这是最后一次关闭,也就是说,

230

计计第三部分 Unix域协议

下载

f_count应当从1减到0,从而导致插口关闭,并返回 file结构给内核的空闲缓存池。 19. 返回临时数组 679-680 返回早些时候由 malloc分配的数组,并清除 unp_gcing标志。 图18-19表示了u n p _ g c函数的回收阶段,同 Net/2版本一样,这部分代码被图 18-18中的代 码替换。

图18-19 Net/2版本中unp_gc 函数回收阶段的错误代码

这就是在图 18-18开始部分的注释中谈到的代码。 不幸的是,虽然在本节讨论的 N e t / 3代码对图 1 8 - 1 9中的代码进行了改进,并且在 图1 8 - 1 8的开始部分描述了错误的更正,但是代码仍然是不正确的,在本节开始部分 提到的前两种情况下, file结构仍是有可能丢失的。

18.10 unp_mark函数 当u n p _ g c调用u n p _ s c a n时,u n p _ m a r k函数被u n p _ s c a n调用去标记一个 f i l e结构。 当在插口接收队列上发现传送中的描述符时完成标记过程,图 18-20给出了这个函数。

图18-20 unp_mark 函数

717-720

参数f p是指向f i l e结构的指针,这个 f i l e结构是在 Unix域插口接收队列上的控

制报文里发现的。 1. 如果记录项已经被标记,就返回 721-722

如果file结构已经被标记,则不需做任何工作,因为已经知道 file结构在使用

过程中。 2. 设置FMARK和FDEFER标志 723-724

递减unp_defer计数器,并且设置 FMARK和FDEFER标志。如果在内核列表里这

下载

231

第18章 Unix域协议:I/O和描述符的传递计计

个f i l e结构比U n i x域插口 f i l e结构出现得早 (也就是说,这个 f i l e结构已经由u n p _ g c处理 过了,并且似乎不在使用过程中,所以没有被标记

) ,那么在 u n p _ g c 函数的标记阶段

unp_defer的增加会导致另一次对所有 file结构的遍历。

18.11 性能(再讨论) 我们已经讨论了 U n i x域协议的实现,现在返回到它们的性能上来看看,为什么要比 T C P 快两倍(图16-2)。 所有的插口 I/O都调用s o s e n d和s o r e c e i v e,与协议无关。这有利有弊,有利是因为这 两个函数满足许多不同协议的需要,从字节流 ( T C P )到数据报协议 ( U D P ),以及基于记录的协 议(OSI TP4)。不利的原因是其一般性降低了性能,并使代码复杂化。对于不同的协议形式, 这两个函数的优化版本会提高性能。 比较输出性能,对于 T C P,通过 s o s e n d的路径几乎与 U n i x域流协议的路径相同。假定大 的应用程序进行写操作 (图1 6 - 2中用32 768字节写),s o s e n d函数把用户数据打包放到 m b u f簇 中,然后通过 P R U _ S E N D请求将每一个 2 048字节簇传送给协议。所以,无论是 T C P还是U n i x 域都要处理相同数量的 P R U _ S E N D请求。对于速度上的差异, U n i x域P R U _ S E N D(图1 8 - 2 )的输 出应当比TCP输出(它调用IP输出把每一段添加到环回驱动器输出队列 )简单。 由于P R U _ S E N D请求把数据放到接收插口的接收缓存,所以在接收方唯一与 U n i x域插口 有关的函数是 s o r e c e i v e。尽管如此,对于 T C P,环回驱动器把每一段数据放到 I P输入队列 上,后面紧跟着 I P处理,再后面跟着 T C P输入处理把每一段分解到正确的插口,然后将数据 放到插口的接收缓存。

18.12 小结 当把数据写到一个 U n i x域插口时,立即将数据添加到接收插口的接收缓存中,没有必要 将发送插口发送缓存里的数据进行缓存。基于这个原因,为了使流插口能正确地工作, P R U _ S E N D和P R U _ R C V D请求操纵发送缓存的高水位标记,从而使得这个标记总是反映对等 端接收缓存中的空间数量。 U n i x域插口提供了将描述符从一个进程传送到另一个进程的机制。对于进程间通信来说, 这是一项强大的技术。当一个描述符从一个进程传送到另一个进程时,首先这个描述符要内 部化(转换成对应的f i l e指针),再将这个指针传送到接收插口。当接收进程读到控制信息时, f i l e指针要外部化 (转换成接收进程中最小的、没有编号的描述符 )。然后将描述符再返回到 这个进程。 容易处理的一个错误情况是,当 U n i x域插口的接收缓存中有传送中描述符的控制信息时, 插口关闭。不幸的是还有其他两种不容易处理的错误情况:一种是接收进程没有请求接收在 其接收缓存中的控制信息;另一种是接收缓存中没有足够的空间来保存控制信息。在这两种 错误情况下就会丢失 file结构,这就是说,它们既不在内核的空闲缓存池中,也不在使用中。 从而需要无用单元收集函数回收这些丢失的结构。 无用单元收集函数执行一个标记阶段,在这个标记阶段中扫描所有内核的 f i l e结构,同 时把在使用中的描述符置上标志,在后面紧跟着回收阶段,回收所有没有被标记的结构。虽 然需要这个函数,但是很少使用它。

下载

附录A 测量网络时间 本书正文中用到了分组经过网络传输时传输时间的测量。本附录给出其细节和我们能够 测量的各种时间的测量例子。我们要介绍用 Ping程序实现的 RTT测量,向上和向下经过协议栈 的时间测量,以及等待时间与带宽的差异。 网络程序员或系统管理员通常有两种办法可以用来测量应用事务所需的时间: 1) 采用应用程序定时器。例如,在图 1 - 1的U D P客户程序中,我们在调用 s e n d t o之前取 到了系统时钟,在 r e c v f r o m函数返回后又取到了系统时钟,其差额就是应用程序发 送请求至收到应答的时间。 如果内核提供了高精度的时钟 (ms数量级),则我们所测得的值 (几毫秒或以上)就很精确。 卷1的附录A给出了这一类测量方法的更多细节。 2) 采用软件工具来监视指定分组,并计算相应的时间差,如嵌入到数据链路层的 Tcpdump。在卷1的附录A中有这些工具的更多细节。 在这本书中,我们假定数据链路的嵌入点在 Tc p d u m p中用B S D分组过滤器 ( B P F )提供。 卷2的第31章给出了 BPF实现的许多细节。卷 2图4-11和图4-19说明了在典型以太网驱动 程序中哪里要有 BPF调用,图15-27则说明了在环路测试驱动程序中的 BPF调用。 我们注意到本书中的例子用到的系统 (图1 - 1 3 ),包括8 0 3 8 6上的BSD/OS 2.0和S p a r c s t a t i o n ELC上的Solaris 2.4,都给应用程序计时和 Tcpdump时间戳提供高精度的定时器。 最可靠的方法是在网络电缆上连接一个网络分析仪,但往往没有这样的仪器。

A.1 利用Ping的RTT测量 在卷 1的第 7章详细介绍了无所不在的 P i n g程序,利用应用定时器来计算 I C M P分组的 RT T (往返时间 )。程序发送一个 I C M P回显请求分组给服务器,服务器紧接着向客户回复一个 回显应答分组。客户可以在回显请求分组中将发送时的时钟值作为用户可选数据记录在该分 组中,然后服务器会在应答中返回这个时钟值。客户收到回显应答时,它就取当前时钟值计 算出RTT,然后打印出来。图 A-1给出了Ping分组的格式。 首部 20 字节

首部

Ping 用户数据(可选)

8 字节

图A-1 Ping分组:ICMP回显请求或 ICMP回显应答

Ping程序允许我们指定分组中可选用户数据的长度,使我们能够测量分组长度对 RTT的影 响。如果是用Ping来测量RTT,可选数据的长度必须至少8字节(客户发出和服务器应答的时间戳 要占用8个字节)。如果指定的用户数据长度少于8字节,Ping也能工作,但不能计算并打印RTT。 图A - 2画出了在三个不同局域网上的主机间用 P i n g测得的 RT T典型值。图中间的一条线是 图1-13中主机bsdi和sun间的RTT。

233

附录A 测量网络时间计计

下载

Ping: 用户数据(字节)

图A-2 三个以太网上的主机间 Ping RTT值

用1 5个不同的分组长度进行了测量: 8字节用户数据以及从 1 0 0至1 4 0 0字节的用户数据 (以 1 0 0字节递增 )。加上2 0字节的I P首部和8字节的I C M P首部,I P数据报的长度就在 3 6 ~ 1 4 2 8字节 之间。对每一个分组长度都进行了 1 0次测量,图中只画出了 1 0个值中最小的那个。与我们所 期望的一致,分组长度增加后 RT T也增大。三条线之间的差别是因处理器速度、接口卡和操 作系统的不同而造成的。 图A - 3给出了经 I n t e r n e t、WA N互连的各种主机之间典型的 RT T值。注意 y轴的刻度与图 A 2中的有差别。

Ping: 用户数据(字节)

图A-3 经Internet(一个WAN)互连的主机间 Ping RTT值

234

TCP/IP详解 卷3:TCP事务协议、HTTP、NNTP和UNIX域协议

下载

与在LAN上测量那样,对WAN进行了同样的测量:对15个不同长度的分组各进行了10次测 量,每个分组长度只画出了10个值中最小的那个值。图中的括号中是每对主机之间的转发段数。 图中最上边的曲线 ( 最长的 RT T ) 表示 I n t e r n e t 上分别位于 A r i z o n a ( n o a o . e d u ) 和 N e t h e r l a n d s (u t w e n t e . n 1)的一对主机之间需要 2 5段转发。自上而下的第 2条曲线也是跨越大 西洋的,是 C o n n e c t i c u t (c o n n i x . c o m)和L o n d o n (u c l . a c . u k)之间的一对主机。接下来两条 曲线在美国内部,分别是 Connecticut和Arizona之间的一对主机 (c o n n i x . c o m与n o a o . e d u), 以及C a l i f o r n i a和Washington D.C.之间的一对主机 (b e r k e l e y . e d u和u u . n e t)。再接下来的 曲线是地理上很近的一对主机 ( C o n n e c t i c u t的c o n n i x . c o m和B o s t o n的a w . c o m),但从经过 Internet传送的转发段数 (16)来衡量,却是离得很远的。 图中底部的两条线 (RTT值最小的 )是作者所在局域网 (图1 - 1 3 )上的主机之间的。其中最底 下的那条线是从图 A-2复制来的,以便对典型的LAN上的RTT与典型的WAN上的RTT进行比较。 在最底下的第 2条线,即 b s d i和l a p t o p之间的RT T线,后者的以太网卡是插在计算机的并行 口上的。尽管该系统也是接在以太网上的,但由于并行口的传输速率较慢,看上去就像是接 在WAN上一样。

A.2 协议栈测量 我们也可以用 P i n g,并加上 Tc p d u m p来测量在协议栈上花费的时间。例如,图 A - 4中就给 出了在一台主机上运行 Ping和Tcpdump,对环回测试地址 (一般是127.0.0.1),Ping的执行步骤。 应用定时器

Ping进程

Ping进程 用户 内核

ICMP输出

ICMP输入

IP输出

IP输入

ICMP输出

ICMP输入

IP输出

IP输入

环回驱动 程序

环回驱动 程序 Tcpdump定时器

图A-4 在一台主机上运行 Ping和Tcpdump

假设应用程序在就要向操作系统发出回显请求分组时启动定时器,然后在操作系统返回 回显应答时停掉定时器,应用程序测得的时间差和 Tcpdump测得的时间差就分别是 ICMP输出、 IP输出、IP输入和ICMP输入之间所需的时间。 我们也可以测量任何客户—服务器应用程序之间的类似值。图 A - 5给出了 1 . 2节U D P客户 —服务器应用的处理步骤,其中假设客户和服务器在同一台主机上。

235

附录A 测量网络时间计计

下载 应用定时器 客户进程 输出

客户进程 输入

服务器 用户 内核

UDP输出

UDP输入

IP输出

UDP输出

UDP输入

IP输出

IP输入

环回驱动 程序

IP输入

环回驱动 程序 Tcpdump定时器

图A-5 UDP客户-服务器事务的处理步骤

这个U D P的客户-服务器例子与图 A - 4的P i n g例子之间的一个不同之处是,这里的 U D P服 务器是一个用户进程,而 P i n g服务器则是 I C M P内核的一部分 (卷2图11 - 2 1 )。因此, U D P服务 器中在内核和用户进程之间要有两份客户数据:服务器输入和服务器输出。内核与用户进程 之间复制数据通常都是比较费时的操作。 图A - 6给出了在主机 b s d i上进行的各项测试结果,可以比较 P i n g客户-服务器和 U D P客

测得的事 务时间(ms)

用户数据(字节)

图A-6 单个主机上 (环回接口)的Ping和Tcpdump测量结果

236

TCP/IP详解 卷3:TCP事务协议、HTTP、NNTP和UNIX域协议

下载

户-服务器这两种方式。图中 y轴标的是“测得的事务时间”,因为RT T通常都是指网络的往返 时间或 P i n g的时间输出 (在图 A - 8中可以看到,它与网络的 RT T非常接近 )。在这里的 U D P、 T C P和T / T C P客户-服务器方式中,可以测量应用程序的事务时间。在 T C P和T / T C P的例子中, 这可能要包括多个分组和多次网络 RTT。 在这个图的 Ping测量中采用了23种不同的分组长度:用户数据从100字节到2 000 字节变化, 增量为1 0 0字节,再加上 8、1 5 0 8和1 5 0 9字节。其中 8字节是用 P i n g来测量RT T的最短用户数据 长度, 1 5 0 8是不会在 I P层分段的最大数据长度,因为 B S D / O S采用了 1 5 3 6的M T U作为环测接 口(1508+20+8)。1509字节则是会在IP层进行分段的最小数据长度。 在UDP测量中也采用了23种类似长度的分组:用户数据长度从 100字节到2 000字节变化(增 量100),再加上0、1508和1509。0字节的UDP数据报也是允许的。由于 UDP的首部与 ICMP回 显测试分组的首部一样长 (8字节),1508又是避免在环测接口上分段的最大分组, 1 509 是需要 分段的最小分组。 我们首先注意到的是在用户数据为 1 5 0 9字节时的时间跳变,这时需要分段。这也是想像 之中的。当出现分段时,在图 A - 4和图A - 5中左边对“ I P输出”的一次调用会产生两次对“环 测驱动程序”的调用,每段一次。从 1 5 0 8到1 5 0 9,即使用户数据只增加了一个字节,应用程 序就感觉到事务时间增加了近 25%,因为多出一个每分组处理时间。 所有4条线中,在2 0 0字节点的时间增加是由于 B S D的m b u f实现中的非自然处理造成的 (卷 2的第2章)。对于最小分组 ( U D P测量中的 0字节用户数据和 P i n g测量中的 8字节用户数据 ),数 据和分组的首部可以写入一个 m b u f中,在1 0 0字节点需要第二个 m b u f,在2 0 0字节点则需要第 三个m b u f。最后,在 3 0 0字节点,内核开始采用 2 0 4 8字节的m b u f簇来代替较小的 m b u f。看起 来,用一个 m b u f簇比用多个 m b u f会快一些 (例如,在 1 0 0字节点 ),可以减少处理时间。这是 典型的时间—空间折衷的例子。从采用较小的 m b u f到采用较大的 m b u f簇的切换条件是数据量 是否超过208字节,这是在许多年前当内存还很紧张时设计的。 图 1 - 1 4 中的定时测量是用修改后的

B S D / O S 内核实现的,其中的常数

M I N C L S I Z E (卷2图2-7和图16-25)从208改为101。这样就使得一旦用户数据超过 100 字节就分配 mbuf簇。只要注意就可以看到,图 1-14中没有在200字节点出现尖角。 我们在 1 4 - 11节也讨论过这个问题,在那里我们看到,许多 We b客户请求都在 100~200字节之间。 图A - 6中,开始分段之前,两条 U D P曲线之间的相差在 1.5~2 ms之间。因为这个差额已经 把U D P输出、I P输出、I P输入和U D P输入(图A-5 )考虑在内,如果我们假设协议的输出逼近于 协议输入,那么就相当于分组发送时向下穿过协议栈要花不到 1ms的时间,接收时分组向上穿 过协议栈又要花不到 1 m s的时间。这些时间包括了发送时要将多份数据从进程传递给内核, 以及数据返回时从内核传递到进程。 由于图A-5中Tcpdump测量要经历同样的 4个步骤(IP输入、UDP输入、UDP输出和IP输出), 我们可以预计到 UDP Tcpdump的两条曲线相差也在 1.5~2 ms之间(只考虑发生分段前的值 )。与 第一个数据点不同,图 A-6中其余的数据也在 1.5~2 ms之间。 如果我们考虑发生了分段以后的值,图 A - 6中两条U D P曲线之间相差 2.5~3 ms。跟预期的 一样,UDP Tcpdump的值也在2.5~3 ms之间。

237

附录A 测量网络时间计计

下载

最后可以看到,图 A - 6中,P i n g的Tc p d u m p曲线几乎是平坦的,但 P i n g的应用程序测量则 有一个正的斜率。这很可能是因为应用程序测量了两份用户进程和内核之间的数据,但 Tc p d u m p则一份也不需要测量 (因为P i n g服务器是内核的 I C M P实现的一部分 )。另外, P i n g的 Tcpdump曲线非常轻微的正斜率很可能是由于内核 Ping服务器的两次操作造成的,这些操作对 每一个字节都要执行:接收 ICMP的检验和验证和输出 ICMP的检验和计算。 我们也可以修改 1 . 3节和1 . 4节的T C P和T / T C P客户-服务器应用,以测量每一次事务的时 间(见1 . 6节的叙述 ),并对不同分组长度进行测量。测量结果见图 A - 7 (在本附录余下的事务测 量中,我们测到用户数据长度为 1400字节就结束了,因为 TCP不分段)。

测得的事务 时间(ms)

用户数据(字节)

图A-7 单个主机上 (环回接口)的TCP和T/TCP客户-服务器事务时间测量结果

Tc p d u m p曲线测量的是从客户发出第一个报文段 (对T C P客户是一个 S Y N,对T / T C P客户 则是 S Y N、数据和 F I N的组合 )至收到服务器发回的最后一个报文段 (对T C P客户是一个 F I N, 对T/TCP客户则是数据和 FIN的组合)为止的时间间隔。相应地, TCP应用曲线和 TCP Tcpdump 曲线之间的差别也就是协议栈用于处理 c o n n e c t和F I N所需的时间 (图1 - 8给出了分组交换 )。 T / T C P客户-服务器的应用曲线和 Tc p d u m p曲线的差别就是协议栈用于处理客户的 s e n d t o所 需的时间,其中包括客户数据和最后一个 F I N (图1 - 1 2给出了分组交换 )。我们可以看到,两条 T/TCP曲线之间的差距 (大约4 ms) 大于两条TCP曲线之间的差距 (大约2.5 ms),这是合理的,因 为T/TCP的协议栈处理量 (在第一段报文中要发送 SYN、数据和FIN)比TCP的 (第一段报文中只 发送SYN)要大。 4条曲线均在 2 0 0字节处开始上升,再次说明内核应该尽快采用 m b u f簇。注意, T C P和 T / T C P在2 0 0字节处的增加比在图 A - 6中P i n g和U D P的要大得多。对于数据报协议 ( I C M P和 U D P )来说,尽管分配了 3个m b u f来缓存首部和用户数据,但内核中插口层对协议输出例程的

238

TCP/IP详解 卷3:TCP事务协议、HTTP、NNTP和UNIX域协议

下载

调用只有一次 (卷2的1 6 . 7节称,该调用是 s o s e n d函数)。而对流协议 ( T C P )来说,对 T C P输出 例程的调用有两次:一次是前 1 0 0字节用户数据,另一次是第 2个1 0 0字节用户数据。确实, Tc p d u m p证实了要传送两个 1 0 0字节报文段的事实。对协议输出例程多了一次调用就增加了 开销。 T C P和T / T C P应用曲线之间的差别大约是 4 ms ,对所有分组长度几乎都一样,因为 T / T C P 处理的报文段少。图 1 - 8和图1 - 1 2给出了 9个T C P报文段和 3个T C P报文段。报文段数的减少明 显减轻了两端主机的处理开销。 图A - 8总结了图 A - 6和图A - 7中的P i n g、U D P以及T / T C P和T C P客户-服务器的应用时间测 量,没有考虑Tcpdump的时间测量。

测得的事 务时间(ms)

UDP: 应用程序

Ping: 应用程序

用户数据(字节)

图A-8 单个主机上 (环回接口)的Ping、UDP及TCP和T/TCP客户-服务器事务时间测量

结果是预料之中的。 Ping所需的时间最少,没有比它更快的了,因为 Ping服务器是在内核 中的。U D P事务所需时间略大于 P i n g的时间,因为数据要在内核与服务器之间复制两次以上, 但并不大,是 U D P所需的最小处理时间。 T / T C P事务所需的时间大约是 U D P的两倍,因为尽 管分组数量与 U D P相同,但需要更多的协议处理时间 (我们的应用程序定时器并不包括图 1 - 1 2 中最后的 A C K )。T C P的事务时间大约比 T / T C P多5 0 %,因为协议需要处理的分组数较多。图 A-8中UDP、T/TCP和TCP之间的相对时间差与图 1-14中的不一样,因为第 1章中的测量是在实 际网络上进行的,而本附录中的测量是在环测接口上进行的。

A.3 滞后和带宽 在网络通信中,有两个因素在决定交换信息所需的时间:滞后和带宽 [Bellovin 1992]。这 里忽略了服务器处理时间和网络负荷,以及其他明显影响客户事务时间的因素。

239

附录A 测量网络时间计计

下载

滞后(也称为传播时延 )是将一个比特从客户传递到服务器再传回来所需的固定时间,受光 速的限制,从而决定于两个主机之间电或光信号的传播距离。横跨美国东西两岸之间的事务 RT T不会低于 60 ms,除非有人可以提高光速。对滞后可做的唯一控制是将两台主机移近,或 避免使用高滞后的路径 (如卫星链路 )。 理论上,光波穿越美国的时间应该是大约 16 ms,最小的RTT是32 ms 。60 ms 是实 际的RTT。作为试验,作者曾在分别位于美国东西海岸的主机上运行过 Traceroute,只 观察横跨美国的直达链路两端的路由器之间的 RTT。加州与华盛顿之间的RTT是58 ms, 加州与波士顿之间的 RTT是80 ms 。 另一方面,带宽度量每个比特进入网络的速度,发送方以这个速度顺序将数据送入网络。 增加带宽只要购买更快的网络即可。例如,如果 T 1线路还嫌不够快 (大约1 544 000 bit/s) ,你 可以租用T3线路(大约45 000 000 bit/s)。 可以用公园的软管作恰当的比喻 (感谢Ian Lance Taylor):滞后是水从水龙头流到 喷口所需的时间,而带宽就相当于每秒从喷口流出的水量。 一个问题是,网络越来越快 (即,带宽增加 ),但滞后保持不变。例如,要用横跨美国的 T1线路发送 100万字节的数据 (假设单程滞后是 30 ms),需要5.21秒:5.18秒是带宽决定的,另 外0 . 0 3秒是滞后造成的。这时带宽是主要影响。但是,如果采用 T 3线路,则总时间是 2 08 m s: 178 ms 是带宽决定的,另外 30 ms 是滞后造成的。这时滞后是带宽时延的 1 / 6。而以 150 000 000 b/s发送则需要 82 ms:52 ms是带宽决定的, 30 ms是滞后造成的。在最后这个例子中,滞 后越来越接近带宽时延,而在更快的网络中,滞后就成为主要的时延因素,而不再是带宽。 在图 A - 3 中,往返滞后基本上就是每条曲线与 y轴的相交点。最上面两条曲线 (大约在 202ms和155 ms处相交)是美国和欧洲之间的,接下来的两条曲线 (在98 ms 和80 ms处相交)是横 跨整个美国的,再下面这条 (大约在30 ms处相交)是美国东海岸的两台主机之间的。 随着带宽增加,滞后变得越来越重要,这使得 T / T C P更显优越。T / T C P至少使滞后减少了 一个RTT。 顺序发送时延和路由器 如果我们将T1线路租给Internet服务商,用于向另一台以T1线路连接到Internet的主机发送数 据,并且已知所有的中间线路都是T1或更高速率的线路,我们会对这样带来的结果感到惊奇。 例如,在图 A - 3中,如果我们来研究起始点为 80 ms、终止点为 193 ms的曲线,它是位于 Connecticut的connix.com主机和位于Arizona的noao.edu主机之间的,它与y轴相交在80 ms 处,正好反映了东西海岸之间的 RTT(运行Traceroute程序,这在卷1的第8章有详细介绍,其结 果说明分组的确切路由是从 Arizona出发,回到 California,然后到Texas、Washington DC,最 后到Connecticut)。但如果我们计算在 T1线路上发送 1400字节所需的时间,大约只需要 7.5 ms, 因此可以估计1400字节分组的 RTT应该是95 ms左右,远远低于实际测得的值 193 ms。 出现这种情况是因为发送时延与中间路由器的数量成线性关系,因为每个路由器都必须 在转发之前接收到整个数据报。考虑图 A-9中的例子,要从左边的主机通过中间路由器传送一 个1 4 2 8字节长的分组到右边的主机。假设两条链路都是 T 1线路,发出 1 4 2 8字节大约需要 7.5 ms。图中的时钟是从上向下增长的。

240

TCP/IP详解 卷3:TCP事务协议、HTTP、NNTP和UNIX域协议

主机

T1

路由器

5 ms滞后

T1 1 ms 滞后

下载 主机

图A-9 数据的顺序发送

第1个箭头,从时刻 0到1是主机处理输出数据报,根据本附录前面的测量,假设它需要 1 ms 。然后这些数据被发送到网络上,从开始发出第 1个比特到最后一个比特发完,需要 7 . 5 m s。另外在线路两端之间还有 5 ms的滞后,因此第 1个比特到达路由器是时刻 6,最后一个比 特则是在时刻13.5到达。 只有在时刻 1 3 . 5最后一个比特到达以后,路由器才能转发该分组,我们假设转发又需要 1 m s时间。这样,路由器在时刻 1 4 . 5发出第1个比特,并且 1 ms(第2段链路的滞后 )以后到达目 的主机。最后一个比特到达目的主机是在时刻 25。最后我们假设目的主机的处理又需要 1 ms 。 确切的数据速率是在 24 ms 内传送了1428字节,或476 000 b/s,比T1的1/3还小。如果我们 忽略主机和路由器处理分组的时间共 3 ms ,数据速率是544 000 b/s。 如前所述,顺序发送时延与分组所经过的路由器数量成线性关系。这项时延决定于线路速 率(带宽)、分组长度和中间转发次数 (路由器数)。例如,552字节分组(包含512字节数据的典型 T C P报文段)在56 kb/s线路上是 80 ms,在T 1线路上是 2.86 ms,而在T 3线路上则只要 0.10 ms。 这样,1 0段T 1线路就要给总时间加上 28.6 ms(几乎等于东西海岸之间的单程滞后 ),而1 0段T 3 线路只增加 1 ms( 与滞后相比几乎可以忽略 )。 最后,顺序发送时延是一种滞后效应,而不是带宽效应。例如,在图 A - 9中,左边的发送 主机可以在时刻 8 . 5开始发送下一个分组的第 1比特,而不必等到时刻 2 4以后才开始发送下一

下载

241

附录A 测量网络时间计计

个分组。如果左边的主机连续发送 1 0个1 4 2 8字节分组,假设分组之间没有间隙,则最后一个 分组的最后一个比特的到达时刻是 91.5(24+9×7.5)。这样的数据速率是 1 248 525 b/s,非常接 近T1的速率。对 TCP来说,只是需要一个比较大的窗口来抵消顺序发送时延。 回到我们前面的例子,从 c o n n i x . c o m到n o a o . e d u,如果我们用 Tr a c e r o u t e确定了确切 的路径,知道了每条链路的速率,就可以把两台主机之间 1 2个路由器上的顺序发送时延考虑 进去。这样,再假设滞后时间 80 ms,每个中间线路段有 0.5 ms的处理时延,我们估算的总时 延就是187 ms。这已经很接近实测值 193 ms,比前面的估算值 95 ms 要接近得多。

下载

附录B 编写T/TCP应用程序 在第一部分,我们介绍了 T/TCP的两大好处: 1) 避免了TCP的三次握手。 2) 减少了连接持续时间短于 MSL时处于TIME_WAIT状态的时间。 如果一个 T C P连接两端的主机支持 T / T C P,那么第 2条好处是所有的 T C P应用程序都能感 受到的,不需要修改程序。 然而,为了避免三次握手,应用程序中对 c o n n e c t和w r i t e函数的调用要改写为调用 s e n d t o和s e n d m s g。为了把 F I N 标志与数据组合在一起,应用程序必须在最后一次调用 s e n d、s e n d t o或s e n d m s g函数时指定 M S G _ E O F标志,同时不再调用 s h u t d o w n。我们在第 1章介绍TCP和T/TCP的客户和服务器时说明了这些差别。 为了使可移植性最好,我们要求在编写应用程序时充分利用 T/TCP,其条件是: 1) 将要执行编译的主机支持 T/TCP,并且 2) 应用程序要编译成支持 T/TCP。 如果运行程序的主机支持 T / T C P,那么第 2个条件也是在运行时要确定的,因为有时会在 操作系统的某个版本上编译程序,而在另一个版本上运行。 如果在 头文件中定义了 M S G _ E O F标志,那就是说要执行程序编译的 主机支持T/TCP。这会在C预处理器的 #ifdef语句中用到。 #ifdef

MSG_EOF /* 主机支持 T/TCP

*/

/* 主机不支持 T/TCP

*/

#else #endif 第2个条件要求应用程序采用隐式打开 (用s e n d t o或s e n d m s g指定目标地址,不调用 c o n n e c t),但要考虑在主机不支持 T / T C P时处理连接失败。在不支持 T / T C P的主机上,当采 用面向连接的插口但没有连接上时,所有的输出函数都会返回 E N O T C O N N(卷2的图16-34)。这 一点对伯克利版系统和 S V R 4插口库都适用。举个例子,如果应用程序在调用 s e n d t o时接收 到错误指示,那它就改为调用 connect。 TCP或T/TCP的客户和服务器 我们可以在下面的程序中实现这些思想,这些程序只是对第 1章的T / T C P与T C P的客户和 服务器程序作了简单修改。与第 1章中的 C语言程序一样,这里也不对程序作详细介绍,同样 假设读者已经对插口编程有一定的了解。第一个程序如图 B-1所示,是客户的 main函数。 8-13 15-17

用服务器的 IP地址和端口号填入 Internet插口地址结构,这两个参数来自命令行。 用函数s e n d _ r e q u e s t向服务器发送请求。如果一切正常,则这个函数返回插口描

述符;否则返回一个负数,表示错误。第 3个变量(1)告诉函数要在发送完请求以后再发送一个

243

附录B 编写T/TCP应用程序计计

下载 结束标志。

18-19 函数read_stream与图1-6中的同名函数一样。

图B-1 T/TCP或TCP客户的main 函数

函数send_request如图B-2所示。

图B-2 send_request

函数:用T/TCP或TCP发送请求

244

TCP/IP详解 卷3:TCP事务协议、HTTP、NNTP和UNIX域协议

下载

图B-2 (续)

1. 试试T/TCP的sendto 13-29

如果执行编译的主机支持 T / T C P,这段程序就会执行。我们在 3 . 6节讨论过插口选项

T C P _ N O P U S H。如果运行该程序的主机不支持 T / T C P,则对s e t s o c k o p t函数的调用将返回 E N O P R O T O O P T,程序将转移到前面的分支,执行常规的 T C P调用c o n n e c t。如果函数要求 的第3个变量为非 0值,则发出请求后还会发出结束标志。 2. 发出正常的 TCP调用 30-40 这些是常规的TCP程序:connect、write和可选的shutdown。 服务器的main函数如图B-3所示,几乎没有改变。

图B-3 服务器的main 函数

下载

245

附录B 编写T/TCP应用程序计计

图B-3 (续)

27-31 唯一的修改是这里总是调用 send(图1-7中调用了write),但如果主机不支持 T/TCP, 就让第 4个变量的值为 0。即使编译时主机是支持 T / T C P的,到运行时也可能主机并不支持 T / T C P (因此运行时的内核不一定能够理解编译时的 M S G _ E O F值),因此,在伯克利版内核中 的sosend并不会对它所不理解的标志作出反映。

下载

参考文献 所有的 R F C 都可以通过电子邮件、匿名 F T P 或W W W免费得到,从 h t t p : / / w w w . internic.net开始即可。 ftp://ds.internic.net/rfc就是一个RFC目录。 标记有“ Internet Draft ”的项目是 I n t e r n e t工程任务组 ( I E T F )正在进行的工作,通过 Internet也可以免费得到,与 RFC类似。这些草案在发布 6个月以后就算过期,因此部分草案的 版本已经在本书出版以后有了变化,有的草案也已经作为 RFC发布。 在本参考书目中,只要作者指定了参考论文或报告的电子文档位置,一定会包括其 U R L (统一资源定位符 )。也给出了每份 I n t e r n e t草案的 U R L文件名部分,因为文件名中包含了 其版本号。 I n t e r n e t草案的主要存储站点是在目录 f t p : / / d s . i n t e r n i c . n e t / i n t e r n e t drafts。本参考书目中没有给出 RFC的URL。 Anklesaria, F., McCahill, M., Lindner, P., Johnson, D., Torrey, D., and Alberti, B. 1993. "The Internet Gopher Protocol," RFC 1436, 16 pages(Mar.). Baker, F., ed. 1995. "Requirements for IP Version 4 Routers," RFC 1812 175pages(June). 有关路由器的的文档是 RFC 1122 [Braden 1989]。这个RFC文档废弃了 RFC 1009 和RFC 1716。 Barber, S. 1995. "Common NNTP Extensions," Internet Draft(June). 华章draft-barber-nntp-imp-01.txt Bellovin, S.M. 1989 . "Security Problems in the TCP/IP Protocol Suite," C o m p u t e r Communication Review, vol.19 , no.2, pp.32-48(Apr.). 华章ftp://ftp.research.att.com/dist/internet_security/ipext.ps.z Bellovin, S. M. 1992 . A Best-Case Network Performance Model. Private Communication. Berners-Lee, T. 1993. "Hypertext Transfer Protocol," Internet Draft, 31 pages(Nov.). 这是一个 I n t e r n e t草案文档,现在已经过期。不过,它是 H T T P第1版协议的最初 规格说明。 华章draft-ietf-iiir-http-00.txt Berners-Lee, T. 1994. "Universal Resource Identifiers in WWW : A Unifying Syntax for the Expression of Names and Addresses of Objects on the Network as Used in the World-Wide Web," RFC 1630, 28 pages(June). 华章http://www.w3.org/hypertext/www/Addressing/URL/URI_Overview.html Berners-Lee, T., and Connolly, D. 1995. "Hypertext Markup Language —2.0," Internet Draft(Aug.). draft-ietf-html-spec-05.txt Berners-Lee, T., Fielding, R. T., and Nielsen, H. F. 1995. "Hypertext Transfer Protocol—

247

参 考 文 献计计

下载 HTTP/1.0, " Internet Draft, 45 pages(Aug.). draft-ietf-http-v10-spec-02.ps

Berners-Lee, T. , M a s i n t e r,L., and McCahill,M.,eds. 1994. "Uniform Resource Locators(URL)," RFC 1738,25 pages(Dec.). Braden, R.T., 1985. " Towards a Transport Service for Transaction Processing Applications," RFC 955 ,10 pages(Sept.). Braden, R. T., ed. 1989. "Requirements for Internet Hosts-Communication Layers," RFC 1122, 116 pages(Oct.). 这是有关对主机要求的 RFC的前一半,这一半覆盖了链路层、 IP、TCP和UDP。 Braden, R.T. 1992a. "TIME-WALL Assassination Hazards in TCP," RFC 1337,11 pages(May.). Braden, R.T. 1992b. "Extending TCP for Transactions-Concepts," RFC 1379, 38 pages(Nov.). Braden, R.T. 1993. "TCP Extensions for High Performance: An Update," Internet Draft, 10 pages (June). 这是对RFC 1323[Jacobson, Braden, and Borman 1992]的更新。 华章http://

www.noao.edu/~rstevens/tcplw-extensions.txt

Braden, R.T. 1994. "T/TCP-TCP Extensions for Transactions, Functional Specification," RFC 1644, 38 pages(July). Brakmo, L.S., and Peterson, L. L., 1994. Performance Problems in BSD4.4 TCP. 华章ftp://cs.arizona.edu/xkernel/papers/tcp_problems.ps Braun, H-W., and Claffy, K.C. 1994. " Web Traffic Characterization:An Assessment of the Impact of Caching Documents from NCSA's Web Server," Proceedings of the Second World Wide Web Conference' 94 : Mosaic and the Web, pp.1007-1027(Oct.), Chicago, Ill. 华章http://www.ncsa.uiuc. edu/SDG/IT94/Proceedings/DDay/claffy/main.

html

Cheriton, D.P. 1988."VMTP:Versatile Message transaction protocol," RFC 1045, 123 pages(Feb.). Cunha, C.R., Bestavros, A., and Crovella, M.E. 1995. "Characteristics of WWW Client-based Traces, "BU-CS-95-010, Computer Science Department, Boston University(July). 华章ftp://cs-ftp.bu.edu/techreports/95-010-www-client-traces.ps.Z Fielding, R.T. 1995. "Relative Uniform Resource Locators," RFC 1808, 16 pages(June). Floyd, S., Jacobson, V., McCanne, S., Liu, C., -G., and Zhang, L. 1995. "A Reliable Multicast Framework for Lightweight Sessions and Application Level Framing," C o m p u t e r Communication Review, vol. 25, no.4 , pp.342-356(Oct.). 华章ftp://ftp.ee.lbl.gov/papers/srml.tech.ps.Z Horton, M., and Adams, R. 1987. "Standard for Interchange of USENET Messages, " RFC 1036, 19 pages(Dec.).

248

TCP/IP详解 卷3:TCP事务协议、HTTP、NNTP和UNIX域协议

下载

Jacobson, V.1988. "Congestion Avoidance and Control, " Computer Communication Review, vol.18, no.4, pp.314-329(Aug.). 这是一篇介绍TCP中的慢启动和拥塞避免算法的经典论文。 华章ftp://ftp.ee.lbl.gov/papers/congavoid.ps.Z Jacobson, V.1994. "Problems with Arizona's Vegas, " March 14, 1994, end2end-tf mailing list(Mar.). 华章http://www.noao.edu/~rstevens/vanj.94mar14.txt Jacobson, V., Braden, R.T., and Borman, D.A. 1992. " TCP Extensions for High Performance, " RFC 1323, 37 pages(May.). 介绍了窗口宽度选项、时间戳选项和 PAW S算法,并且给出了为什么要做所需修 改的原因, [braden 1993]更新了该RFC文档。 Jacobson, V., Braden, R. T., and Zhang, L. 1990. " TCP Extensions for High-Speed Paths," RFC 1185, 21 pages(Oct.). 尽管这个 R F C文档已经被 RFC 1323 废弃,但其中的附录介绍了在 T C P中防止过 时重复报文段的错误接收问题,值得一读。 K a n t o r, B., and Lapsley, P.1986. "Network News Transfer Protocol," RFC 977, 27 pages(Feb.). Kleiman, S.R. 1986. "Vnodes:An Architecture for Multiple File System Types in Sun UNIX," Proceedings of the 1986 summer USENIX conference, pp.238-247, Atlanta, Ga. Kwan, T.T., McGrath, R.E., and Reed, D.A., 1995. User Access Patterns to NCSA's World Wide Web Server. 华章http://www-pablo.cs.uiuc.edu/papers/WWW.ps.Z L e ff l e r, S.J., McKusick, M.K., Karels, M. J., and Quarterman, J.S. 1989. The Design and Implementation of the 4.3 BSD UNIX Operating System. Addison-Wesley, Reading, Mass. 这本书讲述了4.3BSD Tahoe版。它将被 [McKusick et al. 1996]所取代。 McKenney, P. E., and Dove, K. F. 1992. "Efficient Demultiplexing of Incoming TCP Packets," Computer Communication Review, vol.22, no. 4, pp. 269-279 (Oct.). Mckusick, M.K., Bostic, K., Karels, M. J ., and Quarterman, J. S. 1996. The Design and Implementation of the 4.4BSD Operating Syserm. Addision-Wesley, Reading, Mass. M i l l e r, T. 1985. "Internet Reliable Transaction Protocol Functional and Interface Specification," RFC 938, 16 pages (Feb.). Mogul, J. C. 1995a. "Operating Systems Support for Busy Internet Servers," TN-49, Digital Western Research Laboratory(May.). 华章http://www.research.digital.com/wrl/techreports/abstracts/TN-49.html Mogul, J. C. 1995b. "The Case for Persistent-Connection HTTP," Computer Communication Review, vol. 25, no.4, pp.299-313(Oct.). 华章http://www.research.digital.com/wrl/techreports/abstracts/95.4.html

249

参 考 文 献计计

下载 Mogul, J. C. 1995c. Private Communication.

Mogul, J. C. 1995d. "Network Behavior of a Busy Web Server and its Clients," WRL Research Report 95/5, Digital Western Research Laboratory(Oct.). 华章http://www.research.digital.com/wrl/techreports/abstracts/95.5.html Mogul, J. C., and Deering, S. E. 1990. "Path MTU Discovery," RFC 1191, 19 pages (Apr.). Olah, A. 1995. Private Communication. Padmanabhan, V. N. 1995. "Improving World Wide Web Latency," UCB/CSD-95-875, Computer Science Division, University of California, Berkeley(May.). 华章http://www.cs.berkeley.edu/~padmanab/papers/masters-tr.ps Partridge, C. 1987. "Implementing the Reliable Data Protocol(RDP)," P roceedings of the 1987 Summer USENIX Conference, pp.367-379, Phoenix, Ariz. Partridge, C. 1990a. "Re:Reliable Datagram Protocol," Message-ID , Usenet, comp.protocols.tcp-ip Newsgroup(Oct.). Partridge, C. 1990b. "Re: Reliable Datagram ??? Protocols," Message-ID , Usenet, comp.protocols.tcp-ip Newsgroup(Oct.). Partridge, C., and Hinden, R. 1990. "Version 2 of the Reliable Data Protocol(RDP)," RFC 1151, 4 pages(Apr.). Paxson, V. 1994a. "Growth Trends in Wide-Area TCP Connections," IEEE Network , vol.8,no. 4, pp.8-17(July/Aug.). 华章ftp://ftp.ee.lbl.gov/papers/WAN-TCP-growth-trends.ps.z Paxson, V. 1994b. "Empirically-Derived Analytic Models of Wide-Area TCP Connections," IEEE/ACM Transactions on Networking, vol.2, no.4, pp.316-336(Aug.). 华章ftp://ftp.ee.lbl.gov/papers/WAN-TCP-models.ps.z Paxson, V. 1995a. Private Communication Paxson, V. 1995b. "Re:Traceroute and TTL," Message-ID , Usenet, comp.protocols.tcp-ip Newsgroup(Sept.). 华章http://www.noao.edu/~rstevens/paxson.95sep29.txt Postel, J. B., ed. 1981a. "Internet Protocol, "RFC 791, 45pages(Sept.). Postel, J. B., ed. 1981b. "Transmission Control Protocol,"RFC 793,85 pages(Sept.). Raggett, D., Lam, J., and Alexander, I.1996. The Definitive Guide to HTML 3.0: Electronic Publishing on the World Wide Web. Addison-Wesley, Reading, Mass. Rago, S.A. 1993. UNIX System V Network Programming. Addison-Wesley, Reading, Mass. Reynolds, J. K., and Postel, J. B. 1994. "Assigned Numbers," RFC 1700, 230 pages(Oct.). 这个RFC是定期更新的,请查看最新的 RFC编号。 Rose, M. T. 1993. The Internet Message: Closing the Book with Electronic Mail. PrenticeHall, Upper Saddle River, N. J. Salus, P. H. 1995. Casting the Net: From ARPANET to Internet and Beyond. Addison-Wesley, Reading, Mass.

250

TCP/IP详解 卷3:TCP事务协议、HTTP、NNTP和UNIX域协议

下载

Shimomura, Tsutomu.1995. "Technical details of the attack described by Markoff in NYT, " Message-ID, Usenet, comp.protocols.tcp-ip Newsgroup(Jan.). 对1 9 9 4年1 2月的I n t e r n e t突破给出了详细的技术分析,并给出了相应的 C E RT 咨 询报告。 华章http://www.noao.edu/~rstevens/shimomura.95jan25.txt Spero, S. E., 1994a. Analysis of HTTP Performance Problems. 华章http://sunsite.unc.edu/mdma-release/http-prob.html Spero, S.E., 1994b. Progress on HTTP-NG. 华章http://www.w3.org/hypertext/www/Protocols/HTTP-NG/http-ng-status.html Stein, L. D. 1995. How to Set Up and Maintain a World Wide Web Site: The Guide for Information Providers. Addison-Wesley, Reading, Mass. Stevents, W.R. 1990. UNIX Network Pro g r a m m i n g. Prentice-HALL, Upper Saddle River, N.J. Stevents, W.R. 1992. Advanced Programming in the UNIX Environment. Addision-Wesley, Reading, Mass. Stevents, W.R. 1994. TCP/IP Illustrated, Volume 1: The Pr o t o c o l s. Addison-We s l e y, Reading, Mass. 该系列书的卷1对Internet协议有比较完整的介绍。 Velten, D., Hinden, R., and Sax, J. 1984. "Reliable Data Protocol," RFC 908, 57 pages (July). Wright, G. R., and Stevens, W.R. 1995. TCP/IP Illustrated,Volume 2: The Implementation. Addison-Wesley, Reading, Mass. 该系列书的卷2研究讨论了 4.4BSD-Lite操作系统中的Internet协议实现。

下载

缩 略 语 ACK

TCP首部中的确认(ACKnowledgment)标志

ANSI

American National Standards Institute,美国国家标准协会

API

Application Programming Interface,应用编程接口

ARP

Address Resolution Protocol,地址解析协议

ARPANET

Advanced Research Projects Agency NETwork,远景研究规划局 (美国国防 部)网

ASCII

American Standard Code for Information Interchange,美国信息交换标准代码

BPF

BSD Packet Filter,BSD分组过滤程序

BSD

Berkeley Software Distribution,伯克利软件发布

CC

Connection Count,连接计数

CERT

Computer Emergency Response Team,计算机应急响应工作队

CR

Carriage Return,回车

DF

TCP首部中的不分段 (Don't Fragment)标志

DNS

Domain Name System,域名系统

EOL

End of Option List,选项表结束

FAQ

Frequently Asked Question,经常提出的问题

FIN

TCP首部中的终止(FINish)标志

FTP

File Transfer Protocol,文件传送协议

GIF

Graphics Interchange Format,图形交换格式

HTML

HyperText Markup Language,超文本置标语言

HTTP

HyperText Transfer Protocol,超文本传送协议

ICMP

Internet Control Message Protocol,因特网控制报文协议

IEEE

Institute of Electrical and Electronics Engineers,电气和电子工程师学会(美国)

INN

InterNet News,因特网新闻

INND

InterNet News Daemon,因特网新闻守护程序

IP

Internet Protocol,网际协议

IPC

InterProcess Communication,进程间通信

IRTP

Internet Reliable Transaction Protocol,因特网可靠事务协议

ISN

Initial Sequence Number,初始序号

ISO

International Organization for Standardization,国际标准化组织

ISS

Initial Send Sequence number,初始发送序号

LAN

Local Area Network,局域网

LF

Line Feed,换行

MIME

Multipurpose Internet Mail Extensions,通用因特网邮件扩充

252

TTCP/IP详解 卷3:TCP事务协议、HTTP、NNTP和UNIX域协议

MSL

Maximum Segment Lifetime,报文段最大生存时间

下载

MSS

Maximum Segment Size,报文段最大长度

MTU

Maximum Transmission Unit,最大传输单元

NCSA

National Center for Supercomputing Applications,国家超级计算中心 (美国)

NFS

Network File System,网络文件系统

NNRP

Network News Reading Protocol,网络新闻读取协议

NNTP

Network News Transfer Protocol,网络新闻传送协议

NOAO

National Optical Astronomy Observation,国家光学天文观测 (美国)

NOP

No Operation,无操作

OSF

Open Software Foundation,开放软件基金

OSI

Open Systems Interconnection,开放系统互连

PAWS

Protection Against Wrapped Sequence number,防止序号重叠

PCB

Protocol Control Block,协议控制快

POSIX

Portable Operation System Interface,可移植操作系统接口

PPP

Point-to-Point Protocol,点对点协议

PSH

TCP首部中的急迫(PuSH)标志

RDP

Reliable Datagram Protocol,可靠数据报

RFC

Request For Comment,是Internet的文档,意思是“请提意见”。

RPC

Remote Procedure Call,远程过程调用

RST

TCP首部中的重建(ReSeT)标志

RTO

Retransmission Time Out,重传超时

RTT

Round-Trip Time,往返时间

SLIP

Serial Line Internet Protocol,串行线路因特网协议

SMTP

Simple Mail Transfer Protocol,简单邮件传送协议

SPT

Server Processing Time,服务器处理时间

SVR4

System V Release 4,系统V版本4

SYN

TCP首部中的序号同步 (SYNchronous sequence number )标志

TAO

TCP Accelerated Open,TCP加速打开

TCP

Transmission Control Protocol,传输控制协议

TTL

Time-To-Live,寿命,或生存时间

Telnet

远程登录协议

UDP

User Datagram Protocol,用户数据报协议

URG

TCP首部中的紧急(URGent)指针

URI

Universal Resource Identifier,通用资源标识符

URL

Uniform Resource Locator,统一资源定位符

URN

Uniform Resource Name,统一资源名字

VMTP

Versatile Message Transaction Protocol,通用报文事务协议

WAN

Wide Area Network,广域网

WWW

World Wide Web,万维网