TCP/IP协议 — TFTP 与 BOOTP 协议
[TOC]
TFTP 协议
简单文件传送协议(Trivial File Transfer Protocol,TFTP),最初用于无盘系统(通常是无盘系统或者X终端)。与FTP
协议不同,为了更简单、小巧,TFTP
被设计使用UDP
。TFTP
(及其所需的UDP
、IP
和设备驱动程序)可以装入只读存储器。
协议
客户和服务器之间的每次交换开始时,客户请求服务器读取或写入一个文件。请求报文格式如下:

操作码 字段,读请求RRQ
,操作码为1
;写请求WRQ
,操作码为2
。
文件名 字段,表示客户要读取或写入的位于服务器上的文件。文件字段以0
字节作为结束。
模式 字段,一个ASCII
码串,同样以0
字节结束。netascii
表示数据以成行的ASCII
码字符组成,以两个字节—回车符跟换行符(CR/LF
)作为结束符,默认使用该模式;octet
表示将数据看作8bit
一组的字节流而不作任何解释。
块编号 字段,每个数据分组包含一个块编号字段,它以后要在确认分组中使用。
当
TFTP
客户需要发送一个读请求说明要读的文件名和文件模式 (mode
)。如果这个文件能被这个客户读取,TFTP
服务器就返回一个块编号为1
的数据分组。TFTP
客户又发送一个块编号为1
的ACK
。TFTP
服务器随后发送块编号为2
的数据。TFTP
客户发回块编号为2
的ACK
。重复这个过程直到这个文件传送完。除了最后一个数据分组可含有不足512
字节的数据,其他每个数据分组均含有512
字节的数据。当TFTP
客户收到一个不足512
字节的数据分组,就知道它收到最后一个数据分组。在写请求的情况下,
TFTP
客户发送WRQ
指明文件名和模式。如果该文件能被 该客户写,TFTP
服务器就返回块编号为0
的ACK
包。该客户就将文件的头512
字节以块编号为1
发出。服务器则返回块编号为1
的ACK
。
这种类型的数据传输称为停止等待协议。它只用在一些简单的协议,(如TFTP
,在任一时刻外部网络中只有一个数据包处于传输中,并且任一方每接收到一个包,就要又发送一个新的包来作为回复,一直到文件传输完毕)。TCP
提供了不同形式的确,能提供更高的系统吞吐量。TFTP
的优点在于实现的简单而不是高的系统吞吐量。
最后一种TFTP
报文类型是差错报文,它的操作码为5
。它用于服务器不能处理读请求或写请求的情况。在文件传输过程中的读和写差错也会导致传送这种报文,接着停止传输。差错编号 字段给出一个数字的差错码,跟着是一个ASCII
表示的 差错报文 字段,可能包含额外的操作系统说明的信息。
既然
TFTP
使用不可靠的UDP
,TFTP
就必须处理分组丢失和分组重复。分组丢失可通过发送方的超时与重传机制解决,在原先的TFTP(RFC 783)版本里这会引起一个叫 魔术新手综合症(Sorcerer’s Apprentice Syndrome,SAS) 问题,在第二版的TFTP(RFC 1350)被修正,说明里指出只有第一个ACK会被承认并引发下一个资料区块的传送,同一份资料的ACK将被直接忽略。
安全性
TFTP
分组中并不提供用户名和口令,这是它的一个特征(即,“安全漏洞”)。由于TFTP
是设计用于系统引导过程进程,它不可能提供用户名和口令。
这一特性被许多解密高手用于获取Unix
口令文件的复制,然后来猜测用户口令。为防止这种类型的访问,目前大多数TFTP
服务器提供了一个选项来限制只能访问特定目录下的文件(Unix
系统中通常是 /tftpboo
)。这个目录中只包含无盘系统进行系统引导时所需的文件。
对其他的安全性, Unix
系统下的TFTP
服务器通常将它的用户ID
和组ID
设置为不会赋给任何真正用户的值。这只允许访问具有读或写属性的文件。
BOOTP 协议
引导程序协议(Bootstrap Protocol,BOOTP) ,基于IP/UDP
协议,也称自举协议,是DHCP
协议的前身。用于无盘工作站的局域网中,可以让无盘工作站从一个中心服务器上获得IP
地址。
在进行系统引导时,能够通过RARP
来获取它的IP
地址,但是会有两个问题:
IP
地址是返回的唯一结果。RARP
使用链路层广播,所以它的请求不会被路由器转发,迫使每个网络都要设置一个RARP
服务器。
BOOTP
使用UDP
,且通常需与TFTP
协同工作,用于无盘系统进行系统引导的替代方法。
分组格式
BOOTP
请求和应答均被封装在UDP
数据报中,如下:

下图是一个长度为300
字节的BOOTP
请求和应答格式:

操作码 字段,请求为1
;应答为2
。
硬件类型 字段,1
表示10Mb/s
的以太网,与ARP
请求或应答含义相同。
硬件地址长度 字段,同样地与ARP
一样为6
字节。
跳数 字段,由客户设置为0
,也可以被一个代理服务器设置。
事务标识 字段,由客户设置,并由服务器返回的32bit
整数。客户用对请求和应答进行匹配。对每个请求客户应该将该字段设置为一个随机数。 0
表示该客户忽略这个字段。
秒数 字段,客户开始进行引导时,将该字段设置一个时间值。服务器能够看到这个时间值,备用服务器在等待时间超过这个时间值后才会响应客户请求,这意味着主服务器没有启动。
客户 IP 地址 字段,如果客户已经知道了自身的IP
地址,它将写入该字段。否则,将该字段置为0
。
你的 IP 地址 字段,如果客户不知道自身的IP
地址时,服务器将该客户的IP
地址写入该字段。
服务器 IP 地址 字段,由服务器填写。
网关 IP 地址 字段,如果使用了某个代理服务器,则该代理服务器填写该字段。
客户主机硬件地址 字段,客户必须在该设置它的硬件地址,尽管这个值与以太网数据帧头的值相同。UDP
数据报中也设置了这个字段,使任何接收这个数据报的用户进程(如:BOOTP
服务器)很容易使用它。
服务器主机名 字段,是一个以空值终止的字符串,由服务器选则性地填写。
引导文件名 字段,由服务器填写的,用于系统引导的文件名及其所在位置的路径全名。
特定厂商信息 字段,用于对BOOTP
进行不同的扩展。
RFC 1533 [Alexander and Droms 1993]
定义了这个区域的格式,这个区域含有服务器返回客户的可选信息。如果有信息要提供,这个区域的前4
个字节被设置为IP
地址99.130.83.99
。这可称作 魔术甜饼 (magic cookie
),表示该区域内包含信息。其余部分是一个条目表,格式如下:

每个条目开始是一个1
字节的标志字段,其中的两个条目仅有标志字段:标志为0
的条目作为填充字节(为使后面的条目有更好的字节边界),标志为255
的条目表示结尾条目。第一个结尾条目后剩余的字节都应设置为这个数值(255
)。除了这两个1
字节的条目,其他的条目还包含一个单字节的长度字段,后面是相应的信息。
子网掩码 条目和 时间值 条目都是定长条目,因为它们的值总是占4
个字节。时间偏移值是从1900年1月1日0时
以来的秒数(UTC
)。
网关 条目是变长条目。长度通常是4
的倍数,这个值是一个或多个供客户使用的网关(路由器)的IP
地址。返回的第一个必须是首选的网关。
RFC 1533
还定义了其他14
个条目。其中最重要的可能是DNS
名字服务器的IP
地址条目,条目的值为6
。其他的条目包括打印服务器、时间服务器等的IP
地址。详细情况可参考RFC
文档。厂商说明区域的大小被限制为
64
字节。这对某些应用是个约束。一个新的称为 **动态主机配置协议(Dynamic Host Configuration Protocol,DHCP)**已经出现,但它不是替代BOOTP
的。DHCP
将这个区域的长度扩展到312
字节,它在RFC 1541 [Droms 1993]
中定义。
端口号
BOOTP
有两个熟知的端口,服务器67
,客户68
。这意味着BOOTP
客户不会选择未用的临时端口,而只用端口68
。选择两个端口而不是仅选择一个端口因为BOOTP
务器的应答可以进行广播(但通常是不用广播的)。
如果服务器的应答是通过广播传送的,同时客户又选择未用的临时端口,那么这些广播也能被其他的主机中碰巧使用相同临时端口的应用进程接收到。因此,采用随机端口(即临时端口)对广播来说是一个不好的选择。
如果客户也使用服务器的知名端口(67
)作为它的端口,那么网络内的所有服务器会被唤醒来查看每个广播应答(如果所有的服务器都被唤醒,它们将检查操作码,如果是一个应答而不是请求,就不作处理)。因此可以让所有的客户使用与服务器知名端口不同的同一知名端口。
如果多个客户同时进行系统引导,并且服务器广播所有应答,这样每个客户都会收到其他客户的应答。客户可以通过BOOTP
首部中的事务标识字段来确认应答是否与请求匹配,或者可以通过检查返回的客户硬件地址加以区分。
BOOTP 服务器设计
BOOTP
客户通常固化在无盘系统只读存储器中,因此了解BOOTP
服务器的实现将更有意义。
BOOTP
服务器将从它的熟知端口号67
读取UDP
数据报,没有特别的需要。这与必须要读取以太网帧类型字段RARP
请求的RARP
服务器不同。BOOTP
协议通过将客户的硬件地址放入BOOTP
分组中,使得服务器很容易获取客户硬件地址。
这里出现了一个有趣的问题:
TFTP
服务器如何能将一个响应直接送回BOOTP
客户?这个响应是一个UDP
数据报,而服务器知道该客户的IP
地址(可能通过读取服务器上的配置文件)。由于客户端不知道自己的IP
地址,因此无法应答ARP
请求,因此服务器上的ARP
无法找到客户的硬件地址。(这就是RFC951
中被称作 "chicken and egg
"的问题)
对于上面问题,有两种解决方案:
第一种是服务器发送一个IOCTL(2)
请求给内核,为该客户在ARP
高速缓存中设置一个条目(这就是命令arp -s
的工作),所以当服务器发送UDP
数据报时,服务器ARP
模块将会在ARP
高速缓存中发现客户IP
地址。服务器一直这么做直到它知道客户的硬件地址和IP
地址。
第二种是服务器广播这个BOOTP
应答而不是直接将应答发回该客户,通常期望网络广播越少越好,因此这种解决方案应该只在服务器无法在它的ARP
高速缓存设置一个条目的情况下使用。通常只有拥有超级用户权限才能在ARP
高速缓存设置一个条目,如果没有这种权限就只能广播BOOTP
应答。
当一个客户使用
BOOTP
(操作码为1
)进行系统引导时,引导请求通常是采用链路层广播,IP
首部中的目的IP
地址为255.255.255.255
(受限的广播)。源IP
地址通常是0.0.0.0
,因为此时客户还不知道它本身的IP
地址。
BOOTP 穿越路由
绝大多数路由器厂商的产品都支持BOOTP
协议,所以能够被路由器转发。这个功能主要用于无盘路由器,因为如果一个带磁盘的多用户系统被用作路由器,它就能够自己运行BOOTP
服务器。
常用的
Unix BOOTP
服务器支持这种中继模式(relay mode
)。如果在这个物理网络内运行一个
BOOTP
服务器,通常没有必要将BOOTP
请求转发到在另外网络中的另一个服务器。
当路由器(也称作“BOOTP
中继代理”)在服务器的熟知端口(67
)接收到BOOTP
请求时,中继代理将IP
地址填入收到BOOTP
请求中的“ 网关IP地址 字段”,然后将该请求发送到真正的BOOTP
服务器(由中继代
理填入网关字段的地址是收到的BOOTP
请求接口的IP
地址)。中继代理还将跳数字段值加1
(这是为防止请求被无限地在网络内转发。 RFC951
认为如果跳数值到达3
就可以丢弃该请求)。
发出的请求是一个单播的数据报(与发起的客户的请求是广播的相反),它能按照一定的路由通过其他的路由器到达真正的BOOTP
服务器。真正的BOOTP
服务器收到这个请求后,产生BOOTP
应答,并将它发回中继代理,而不是请求的客户。既然请求网关字段不为零,真正的BOOTP
服务器知道这个请求是经过转发的。中继代理收到应答后将它发给请求的客户。