You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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通常采用以下两种方式绕过防火墙限制:

  1. 动态添加防火墙规则:启动时通过Windows防火墙API(或netsh命令)为自身添加永久/临时入站规则,允许所有UDP流量指向自身监听端口,不受对方端口限制。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 17:14:54