一、TFTP 协议基础
协议概述
- TFTP(Trivial File Transfer Protocol)是一种简单的文件传输协议,基于 UDP 实现,无连接、轻量级,常用于网络设备启动或嵌入式环境。
- 端口号:69(服务器监听)。
- 传输模式:通常使用“octet”(二进制)和“netascii”(文本)两种模式。
报文格式
操作码 | 含义 | 报文格式描述 |
---|---|---|
1 | RRQ | 2 字节 Opcode + 文件名 + 0 + 模式 + 0 |
2 | WRQ | 同上 |
3 | DATA | Opcode(2B) + Block#(2B) + Data(0-512B) |
4 | ACK | Opcode(2B) + Block#(2B) |
5 | ERROR | Opcode(2B) + ErrorCode(2B) + ErrMsg + 0 |
传输流程
读取文件(RRQ)
- 客户端向服务器发送 RRQ 请求,包含文件名和传输模式。
- 服务器回复第一个 DATA 数据包(块号从1开始)。
- 客户端写入数据并发送对应 ACK。
- 服务器发送下一块 DATA。
- 重复以上步骤,直到收到数据长度小于 512 字节,表示文件传输结束。
写入文件(WRQ)
- 客户端发送 WRQ 请求,告知服务器准备上传文件。
- 服务器回应 ACK (块号 0),表示可以开始发送数据。
- 客户端从块号 1 开始发送 DATA。
- 服务器对每个 DATA 发送 ACK。
- 当客户端发送最后一块数据,且数据长度小于 512 字节时,传输完成。
传输可靠性
- TFTP 使用简单的停止等待(Stop-and-Wait)机制。
- 每个数据包发送后等待对应 ACK,超时重传。
- 支持重传次数限制,超过则中断传输。
二、程序结构与设计
主要数据结构
1 | struct tftp_handle { |
核心函数设计
- socket_init()
创建 UDP 套接字。
初始化服务器地址结构。
打开本地文件,RRQ 以写入模式打开文件(接收数据),WRQ 以读取模式打开(发送数据)。
参数校验(文件名长度、IP 地址格式)。 - send_request()
构造 RRQ 或 WRQ 请求报文。
格式为:Opcode(2B) + 文件名 + 0 + 传输模式 + 0。
使用 sendto() 发送给服务器69端口。 - Read_data()
通过 recvfrom() 接收 DATA 报文。
验证操作码是否为 DATA。
检查块号是否符合预期(防止重复或乱序)。
将数据写入本地文件。
发送 ACK 给服务器确认收到。
处理重复包,发送 ACK 并重试,防止丢包。
返回数据长度,数据长度小于512字节时认为传输结束。 - Write_data()
初次调用时等待服务器对 WRQ 的 ACK(块号0),以便初始化 client_addr。
读取本地文件数据,封装成 DATA 报文发送。
等待服务器 ACK,支持重传机制。
检查 ACK 的块号,防止重复 ACK。
发送完最后一块(<512字节)数据后结束。 - send_ack()
构造 ACK 包(Opcode=4,Block#)。
发送给服务器,确认数据包接收。 - resource_free()
关闭文件和套接字,释放资源。
错误处理机制
超时和重传:重试计数 retry,超过最大次数后停止传输。
收到错误报文(Opcode=5)后终止传输。
文件打开失败、网络异常等均有提示并终止。
程序流程简述
初始化套接字和文件。 ->
发送 RRQ/WRQ 请求。 ->
进入数据收发循环:{ RRQ:调用 Read_data() 循环接收数据包并 ACK。WRQ:调用 Write_data() 循环发送数据包并等待 ACK。 } ->
传输完成后释放资源。
抓包分析
调用 Write 模式时 抓包
客户端发 WRQ 请求给服务器 UDP 69 端口。
服务器收到 WRQ 后:会分配一个新的临时端口(非 69)作为后续数据传输的目标端口。这个在抓包中的表现为:服务器通过新的端口(58372)向客户端发送 ACK(block0)。
客户端socket收到这个 ACK 包需要“绑定”服务器的这个新端口(client_addr),确保后续的发送/接收针对正确的端口。
整个写的过程就是 客户端向服务器发送数据和block, 服务器向客户端回复ACK和已收到的block,当客户端发送的数据长度小于512时,就是最后一个包,发完就结束了。
调用 Read 模式时 抓包
第一个包都是请求包,有请求的类型(读/写),请求的文件名,文件的编码方式等,也只有请求包是和服务器的69端口在通讯,后面都是和服务器的一个随机端口通讯。
这个发包的过程和 Write 一样,只不过是服务器发数据包,客户端回复block号以表示确认。