TFTP客户端开启Windows Defender防火墙后无法接收STID端口数据包的问题
TFTP客户端Windows防火墙拦截问题分析与解决
问题背景
TFTP协议流程如下:
- 客户端从随机CTID端口向服务器69端口发送RRQ/WRQ请求:
Client:CTID ------> Server:69 - 服务器从随机STID端口向客户端CTID端口回复:
Client:CTID <------ Server:STID - 后续所有传输均在
Client:CTID <-----> Server:STID之间交互
实际测试发现:Windows Defender防火墙开启时,客户端无法接收服务器STID端口的UDP回复;关闭防火墙则正常。自行编写的C语言TFTP客户端代码复现了该问题,但tftp64这类工具在防火墙开启时仍能正常工作。
复现代码
#include <stdint.h> #include <stdio.h> #include <winsock2.h> #define DBG(fmt, ...) fprintf(stderr, "% 8d : " fmt "\n", __LINE__ __VA_OPT__(, ) __VA_ARGS__) int main(void) { uint32_t ip = 0xC0A8010A; // 192.168.1.10 uint16_t port = 69; int err; WSADATA WsaDat; if (WSAStartup(MAKEWORD(2, 2), &WsaDat) != 0) { err = WSAGetLastError(); DBG("WSAStartup() failed (Err: %d)\n", err); return -1; } int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); DBG("sock: %d", sock); if (sock < 0) { return -1; } struct sockaddr_in destAddr; memset(&destAddr, 0, sizeof(destAddr)); destAddr.sin_family = AF_INET; destAddr.sin_addr.s_addr = htonl(ip); destAddr.sin_port = htons(port); char buffer[1024] = "\x00\x02\x2f\x62\x61\x63\x6b\x65\x6e\x64\x2e\x68\x00\x6f\x63\x74\x65\x74\x00\x74\x73\x69\x7a\x65\x00\x30\x00"; DBG("Send TO %u.%u.%u.%u:%u", (ip & 0xff000000) >> 24, (ip & 0xff0000) >> 16, (ip & 0xff00) >> 8, ip & 0xff, port); err = sendto(sock, buffer, 27, 0, (struct sockaddr *) &destAddr, sizeof(destAddr)); DBG("sendto: %d", err); if (err != 27) { return -1; } struct sockaddr_in srcAddr; memset(&srcAddr, 0, sizeof(srcAddr)); int32_t srcAddrLen = sizeof(srcAddr); err = recvfrom(sock, buffer, 1024, 0, (struct sockaddr*)&srcAddr, &srcAddrLen); DBG("recvfrom: %d", err); if (err > 0) { ip = ntohl(srcAddr.sin_addr.s_addr); port = ntohs(srcAddr.sin_port); DBG("Recv From %u.%u.%u.%u:%u", (ip & 0xff000000) >> 24, (ip & 0xff0000) >> 16, (ip & 0xff00) >> 8, ip & 0xff, port); } return 0; }
运行结果
FIREWALL ON 20 : sock: 264 33 : Send TO 192.168.1.10:69 35 : sendto: 27 ^C FIREWALL OFF 20 : sock: 244 33 : Send TO 192.168.1.10:69 35 : sendto: 27 46 : recvfrom: 4 50 : Recv From 192.168.1.10:50000
原因分析
Windows Defender防火墙的UDP会话追踪逻辑是核心问题:
- 当应用发送UDP包到
Server:69时,防火墙会创建临时入站规则,仅允许来自该服务器IP的69端口的反向流量。 - TFTP服务器的回复来自随机STID端口,不符合临时规则的端口要求,因此被拦截。
- 绑定CTID端口不代表能接收所有目标为该端口的流量,防火墙入站规则优先级高于端口绑定,默认未允许应用接收任意端口的UDP入站流量。
tftp64的处理方式
tftp64通常采用以下两种方式绕过防火墙限制:
- 动态添加防火墙规则:启动时通过Windows防火墙API(或
netsh命令)为自身添加永久/临时入站规则,允许所有UDP流量指向自身监听端口,不受对方端口限制。 - UDP Socket连接绑定:调用
connect函数将UDP Socket与TFTP服务器IP关联,防火墙会识别该Socket的通信对象为特定IP,允许来自该IP任意端口的回复流量。
解决方案
针对你的代码,推荐两种修改方式:
方式一:关联服务器IP(无需管理员权限)
在sendto之后添加connect调用,将Socket与服务器IP绑定,让防火墙放行该IP的所有端口回复:
// 在sendto之后添加 struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = htonl(ip); err = connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); if (err == SOCKET_ERROR) { DBG("connect failed (Err: %d)", WSAGetLastError()); }
修改后的完整代码:
#include <stdint.h> #include <stdio.h> #include <winsock2.h> #define DBG(fmt, ...) fprintf(stderr, "% 8d : " fmt "\n", __LINE__ __VA_OPT__(, ) __VA_ARGS__) int main(void) { uint32_t ip = 0xC0A8010A; // 192.168.1.10 uint16_t port = 69; int err; WSADATA WsaDat; if (WSAStartup(MAKEWORD(2, 2), &WsaDat) != 0) { err = WSAGetLastError(); DBG("WSAStartup() failed (Err: %d)\n", err); return -1; } int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); DBG("sock: %d", sock); if (sock < 0) { return -1; } struct sockaddr_in destAddr; memset(&destAddr, 0, sizeof(destAddr)); destAddr.sin_family = AF_INET; destAddr.sin_addr.s_addr = htonl(ip); destAddr.sin_port = htons(port); char buffer[1024] = "\x00\x02\x2f\x62\x61\x63\x6b\x65\x6e\x64\x2e\x68\x00\x6f\x63\x74\x65\x74\x00\x74\x73\x69\x7a\x65\x00\x30\x00"; DBG("Send TO %u.%u.%u.%u:%u", (ip & 0xff000000) >> 24, (ip & 0xff0000) >> 16, (ip & 0xff00) >> 8, ip & 0xff, port); err = sendto(sock, buffer, 27, 0, (struct sockaddr *) &destAddr, sizeof(destAddr)); DBG("sendto: %d", err); if (err != 27) { return -1; } // 将socket连接到服务器IP,让防火墙允许来自该IP的任意端口回复 struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = htonl(ip); err = connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); if (err == SOCKET_ERROR) { DBG("connect failed (Err: %d)", WSAGetLastError()); } struct sockaddr_in srcAddr; memset(&srcAddr, 0, sizeof(srcAddr)); int32_t srcAddrLen = sizeof(srcAddr); err = recvfrom(sock, buffer, 1024, 0, (struct sockaddr*)&srcAddr, &srcAddrLen); DBG("recvfrom: %d", err); if (err > 0) { ip = ntohl(srcAddr.sin_addr.s_addr); port = ntohs(srcAddr.sin_port); DBG("Recv From %u.%u.%u.%u:%u", (ip & 0xff000000) >> 24, (ip & 0xff0000) >> 16, (ip & 0xff00) >> 8, ip & 0xff, port); } closesocket(sock); WSACleanup(); return 0; }
方式二:动态添加防火墙规则(需管理员权限)
通过netsh命令为当前应用添加临时入站规则,程序退出时自动删除:
#include <windows.h> // ... 其他头文件 ... // 在main开头添加 char exePath[MAX_PATH]; GetModuleFileNameA(NULL, exePath, MAX_PATH); char addRuleCmd[1024]; snprintf(addRuleCmd, sizeof(addRuleCmd), "netsh advfirewall firewall add rule name=\"TFTP Client Temp\" dir=in action=allow program=\"%s\" protocol=UDP", exePath); system(addRuleCmd); // 注册退出时删除规则 atexit([](){ system("netsh advfirewall firewall delete rule name=\"TFTP Client Temp\""); });
验证结果
修改后的代码在防火墙开启时运行,可正常接收服务器STID端口的回复:
20 : sock: 256 33 : Send TO 192.168.1.10:69 35 : sendto: 27 44 : connect failed (Err: 0) 50 : recvfrom: 4 54 : Recv From 192.168.1.10:50000
内容的提问来源于stack exchange,提问作者Maximilian
相关产品推荐
相关产品推荐

