TFTP(简单文件传输协议)是一种基于UDP的简易文件传输协议,主要可用于在局域网内快速传输文件,常用于网络设备引导、系统无盘工作站启动、固件升级等等场景。本实验编写了一个简单的TFTP客户端,可以与Tftpd64服务器进行连接,实现简单的文件上传、下载及日志记录功能,并能丢包重传,具有一定的抗干扰稳定性,附上源代码框架和测试环境

TFTP协议基本特征

  • 协议端口:默认使用UDP 69端口
  • 传输模式: 无连接,基于UDP协议
  • 数据传输单位:每个数据包最大512字节(可扩展,RFC-2348)
  • 可靠性:通过确认(ACK)与重传机制来保证数据可靠性

TFTP协议工作流程

客户端请求

客户端向服务器端69号端口发送读请求(RRQ)或写请求(WRQ),相关请求报文格式如下所示:

1
2
3
2 bytes   string   1 byte   string   1 byte
--------------------------------------------
| Opcode | Filename | 0 | Mode | 0 |
  • Opcode字段:指定操作类型 0x0001 = RRQ,0x0002 = WRQ
  • Filename字段:指定读取或传输的文件名,文件名必须以"\0"(字节0x00)结尾,如”test.txt\0”
  • Mode字段:指定文件传输格式,netascii = 文本模式,octet = 二进制模式,其必须以"\0"结尾

服务器端响应

如果收到客户端RRQ请求,服务器会发送第一个数据包(DATA包),如果是WRQ请求,服务器会发送第一个确认包(ACK包),表示已做好接收数据准备

文件传输

传输的文件被分成多个DATA包,每个DATA包负载均为512字节(最后一块可能小于512字节),接收端需要持续发送ACK包确认报文,且如果出现超时,需要重传,DATA包与ACK包结构如下所示:

1
2
3
4
5
6
7
8
9
-- DATA包
2 bytes 2 bytes n bytes
----------------------------
| Opcode | Block -- | Data |

-- ACK包
2 bytes 2 bytes
-----------------
| Opcode | Block |

结束会话

当接收方收到的数据包小于512字节时,发送最后一个ACK包并关闭会话即可(因此即使文件大小可以被512整除,也需要发送一个负载为0字节的报文

TFTP协议特点

丢包重传

通过丢包重传机制,TFTP协议依托不可靠的UDP传输协议,实现较可靠的文件传输功能
TFTP使用停等协议实现丢包重传(Stop-and-Wait ARQ):

  • 发送方每次仅发送一个DATA包,只有接收到ACK包才发送下一个指定DATA包
  • 接收方每接收到一个DATA包,就发送携带最后一个正确包Block编号的ACK确认报文。若收到的DATA包为重复包或错误Block号包,则丢弃(TFTP协议为严格顺序传输协议
    TFTP超时重传规则:
  • 超时时间:典型实现为5s,可根据实际网络通信状况修改
  • 重传次数:一个报文最多重新传输次数,超过该值则认为文件传输失败,立刻终止会话
  • 发送DATA包后没收到任何ACK确认包
    发送方会再次发送同一个Block号DATA包
  • 发送RRQ\WRQ\ACK后没收到任何回应
    发送方会重复发送同一个请求报文

ERROR报文

一个不常用的TFTP报文,其格式为:

1
2
3
2 bytes   2 bytes      string     1 byte
----------------------------------------
| Opcode | ErrorCode | ErrMsg | 0 |
  • Opcode:固定为5
  • ErrorCode:2字节,错误码
  • ErrMsg:错误信息字符串,自定义,文本格式可读,以”\0”结尾

常见的错误码有

ErrorCode 含义
0 未定义错误(Not defined)
1 文件未找到(File not found)
2 访问违规(Access violation)
3 磁盘满/分配超出(Disk full)
4 非法操作(Illegal TFTP operation)
5 未知传输 ID(Unknown transfer ID)
6 文件已存在(File already exists)
7 无效用户(No such user)

Windows套接字编程

编写简易TFTP程序需要用到Windows套接字编程相关函数,现将函数原型和作用列出:

  • WSAStartup函数
    WSAStartup函数用于初始化 Windows 套接字库(Winsock),在使用任何套接字相关函数前必须调用。成功返回 0,失败返回错误码
1
2
3
4
int WSAStartup(
WORD wVersionRequested, // 请求的Winsock版本号,如MAKEWORD(2, 2)表示请求的Winsock版本号为2.2
LPWSADATA lpWSAData // 指向WSADATA结构体的指针,用于接收初始化信息
);
  • WSACleanup函数
    WSACleanup函数用于释放 Winsock 库资源,通常在网络操作结束后调用。成功返回 0,失败返回错误码
1
int WSACleanup(void);
  • sendto函数
    sendto函数用于通过指定的套接字向目标地址发送数据,适用于 UDP(无连接)通信,返回值为实际发送的字节数,若失败则返回 SOCKET_ERROR
1
2
3
4
5
6
7
8
int sendto(
SOCKET s, // 套接字描述符
const char* buf, // 要发送的数据缓冲区
int len, // 数据长度
int flags, // 标志位,通常为 0
const struct sockaddr* to, // 目标地址结构体指针
int tolen // 目标地址结构体长度
);
  • recvfrom函数
    recvfrom函数是 UDP 通信中常用的函数,用于接收数据报文,同时获取发送方的地址信息。它适用于无连接的套接字,其成功时,返回接收到的字节数;
    失败时,返回 SOCKET_ERROR,可以通过 WSAGetLastError() 获取错误码
1
2
3
4
5
6
7
8
int recvfrom(
SOCKET s, // 套接字描述符
char* buf, // 接收数据的缓冲区
int len, // 缓冲区最大长度
int flags, // 标志位,通常为 0
struct sockaddr* from, // 发送方地址结构体指针,指向一个 sockaddr 结构体,用于存储发送方的地址信息。如果不需要发送方地址,可以传入NULL
int* fromlen // 发送方地址结构体长度指针,指向一个整数变量,表示 from 结构体的大小。函数返回时,该变量会被更新为实际的地址长度
);

轻量级TFTP客户端测试

Upload测试

客户端命令如下:

1
$ .\tftp_client.exe -u 127.0.0.1 test.txt netascii

客户端效果:
image
Tftp64服务器效果:
image

Download测试

客户端命令如下:

1
$ .\tftp_client.exe  -d 10.12.172.220 EUPL-EN.pdf octet

Tftp64服务器效果:
image
查看日志,结果如下所示
image

补:还可以使用测试环境中提供的clumsy-bandwidth工具模拟真实网络通信环境下,进行TFTP丢包率测试。测试结果表明客户端Tftp丢包重传机制可以正常运行