W6300使用UPnP协议访问路由器进行端口映射
更新于 2026年10月16日
本篇总结
W6300 实现 UPnP 协议,通过 SSDP(Simple Service Discovery Protocol)搜索 IGD 网关并以 SOAP 消息调用 AddPortMapping / DeletePortMapping / GetGenericPortMappingEntry 接口,实现路由器端口的动态映射、删除与查询。
硬件平台:本例程基于 W6300-EVB-M4 开发板运行。
采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W6300-EVB-M4 开发板
本示例演示如何在 W6300 上实现 UPnP 协议,并通过实战例程,为大家讲解如何使用 UPnP 协议来配置路由器的端口转发功能。该例程用到的其他网络协议,例如 DHCP,请参考相关章节。
前置工具准备
开始本例程前,请确认以下工具已安装:
UPnP 协议简介
UPnP(Universal Plug and Play)协议是一种支持设备在局域网中实现自动发现和通信的网络协议。其端口转发功能由 IGD Profile 提供,允许局域网设备动态请求路由器为其开放指定的端口,以实现外部设备访问内部服务。这种功能消除了手动配置端口转发的复杂性,特别适用于需要穿透 NAT(网络地址转换)环境的应用场景。
IGD(Internet Gateway Device,互联网网关设备)是 UPnP 协议的一部分,主要用于管理网络中的网关设备(如路由器)的服务和资源。IGD 扩展定义了一套标准接口,允许局域网设备与网关设备通信,动态配置网络设置,例如端口转发、带宽管理和连接状态查询等。
应用场景
通过 UPnP 端口转发功能,可以实现远程访问(将外部请求转发到局域网设备如 NAS、监控摄像头)和设备发现与即插即用(通过 UPnP 广播发现新设备,获取设备描述文件来获取服务和接口)。
硬件连接与拓扑
请注意:
W6300 必须接入在支持 UPnP/IGD 端口转发的路由器下,且路由器的 UPnP 功能已开启。PC(运行 UPnP Tool)和 W6300 需处于同一局域网。
- Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
- 网线连接开发板 RJ45 网口 到路由器 LAN 口
│ 路由器(IGD / UPnP 网关) │
│ 192.168.2.1 :5431 │
└────┬───────────────────┬──────────┘
│ │
┌────┴──────┐ ┌────┴──────────────┐
│ W6300 │ │ PC │
│(UPnP CP) │ │ UPnP Tool │
│ SSDP:1900 │ │ SocketTester │
└────┬──────┘ └───────────────────┘
│ QSPI ↑
┌────┴──────┐ DEBUG USB
│ MH2457 │─────────→ WIZ UartTool
│ MCU │
└───────────┘
工程结构
工程核心文件如下:
Project/
├── main.c ← 系统入口:串口、GPIO、QSPI 初始化 + user_run()
└── USER/
├── user_main.c ← 网络配置 + UPnP 流程 + 主菜单
├── UPnP.c/h ← UPnP 协议实现(SSDP/描述/订阅/SOAP)
├── MakeXML.c/h ← SOAP XML 组装
├── hyperterminal.c/h ← 主菜单与串口交互
├── wiz_platform.c/h ← 平台适配:debug_usart_init、wizchip_gpio_init、hspi_init
└── wiz_interface.c/h ← W6300 驱动接口:wizchip_initialize、network_init、PHY 检测、软件定时器
报文讲解
通过 UPnP 给路由器添加端口转发规则的完整工作流程如下:
- 设备发现:W6300 通过 SSDP 向局域网发送组播请求(HTTP M-SEARCH),搜索支持 IGD 的网关设备。
- 获取服务描述:W6300 访问网关设备获取服务描述文件,了解支持的服务和接口。
- 订阅 IGD 事件:通过事件订阅,W6300 可在不主动轮询的情况下接收实时通知。
- 调用服务接口:使用 UPnP 的 SOAP 消息调用 IGD 提供的端口映射接口。
- 数据交互测试:外部通过访问映射的端口及路由器地址和局域网内部设备进行通信。
设备搜索(SSDP)
SSDP 是 UPnP 协议中的关键协议,用于设备发现和服务发布。它通过 HTTP over UDP 的形式在局域网内广播和接收报文,采用多播地址 239.255.255.250 和端口 1900。
M-SEARCH 请求报文实例:
M-SEARCH * HTTP/1.1
Host:239.255.255.250:1900
ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1
Man:"ssdp:discover"
MX:3M-SEARCH * HTTP/1.1:表明是一个搜索请求。
Host:多播地址和端口。
ST:搜索目标类型,这里是 IGD 设备。
MX:最大响应时间,设备需要在 3 秒内返回响应。
Man:搜索请求类型,固定值。
M-SEARCH 响应报文实例:
HTTP/1.1 200 OK
ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1
USN:uuid:34ca819a-9d18-34ca-819a-9d180098c02d::urn:schemas-upnp-org:device:InternetGatewayDevice:1
Location: http://192.168.2.1:5431/dyndev/uuid:34ca819a-9d18-34ca-819a-9d180098c02d
Server: Custom/1.0 UPnP/1.0 Proc/Ver
EXT:
Cache-Control:max-age=30
DATE: Fri, 16 Oct 2026 08:45:56 GMTHTTP/1.1 200 OK:表示响应成功。
CACHE-CONTROL:响应有效时间为 30 秒。
LOCATION:设备描述文件的 URL。
SERVER:设备的操作系统、UPnP 版本和设备名称。
USN:唯一设备标识符。
获取设备描述
这一步会通过 HTTP GET 方式去请求 xml 文件。
GET /dyndev/uuid:34ca819a-9d18-34ca-819a-9d180098c02d HTTP/1.1
Accept: text/xml, application/xml
User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)
Host: 192.168.2.1:5431
Connection: Keep-Alive
Cache-Control: no-cache
Pragma: no-cache响应示例(设备描述 XML,节选):
HTTP/1.0 200 OK
SERVER: LINUX/2.6 UPnP/1.0
DATE: Fri, 16 Oct 2026 08:45:56 GMT
CONTENT-TYPE: application/octet-stream
Connection: Close
<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
...
<device>
<deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
<controlURL>/uuid:34ca819a-9d18-34ca-819a-9d180298c02d/WANIPConnection:1</controlURL>
<eventSubURL>/uuid:34ca819a-9d18-34ca-819a-9d180298c02d/WANIPConnection:1</eventSubURL>
<SCPDURL>/dynsvc/WANIPConnection:1.xml</SCPDURL>
</service>
</serviceList>
</device>
...
</root>
其中 controlURL 用于发送 SOAP 控制请求,eventSubURL 用于订阅事件。若设备描述中不支持 WANIPConnection 服务,则说明不支持端口映射。
订阅 IGD 事件
通过 HTTP SUBSCRIBE 订阅 IGD 事件,示例如下:
SUBSCRIBE /uuid:34ca819a-9d18-34ca-819a-9d180298c02d/WANIPConnection:1 HTTP/1.1
Host: 192.168.2.1:5431
USER-AGENT: Mozilla/4.0 (compatible; UPnP/1.1; Windows NT/5.1)
CALLBACK: <http://192.168.2.26:5002/>
NT: upnp:event
TIMEOUT: Second-1800响应示例:
HTTP/1.1 200 OK
Content-Type: text/xml;charset=UTF-8
Content-Length: 0
Connection: close
Server: Custom/1.0 UPnP/1.0 Proc/Ver
Timeout: Second-1800
SID: uuid:...添加端口映射报文
例如,我们想映射 TCP 协议的内部端口 8000 到外部端口 12345 上,可以按照以下示例进行 HTTP 请求:
POST /uuid:34ca819a-9d18-34ca-819a-9d180298c02d/WANIPConnection:1 HTTP/1.1
Content-Type: text/xml; charset="utf-8"
SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"
Host: 192.168.2.1:5431
Content-Length: 1131
Connection: Keep-Alive
<?xml version="1.0"?>
<SOAP-ENV:Envelope ...>
<SOAP-ENV:Body>
<m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>12345</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
<NewInternalPort>8000</NewInternalPort>
<NewInternalClient>192.168.2.26</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>W6300_uPnPGetway</NewPortMappingDescription>
<NewLeaseDuration>0</NewLeaseDuration>
</m:AddPortMapping>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>m:AddPortMapping:添加端口映射
NewExternalPort:外部端口号
NewProtocol:协议类型(TCP/UDP)
NewInternalPort:内部端口号
NewInternalClient:内部地址
删除端口映射报文
例如,我们想删除上面映射的 12345 端口,可以按照以下示例进行 HTTP 请求:
POST /uuid:34ca819a-9d18-34ca-819a-9d180298c02d/WANIPConnection:1 HTTP/1.1
Content-Type: text/xml; charset="utf-8"
SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping"
Host: 192.168.2.1:5431
Content-Length: 604
Connection: Keep-Alive
<?xml version="1.0"?>
<SOAP-ENV:Envelope ...>
<SOAP-ENV:Body>
<m:DeletePortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>12345</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
</m:DeletePortMapping>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>m:DeletePortMapping:删除端口映射
NewExternalPort:外部端口号
NewProtocol:协议类型
NewRemoteHost:外部访问来源,可以为空
实现过程
在这个例程中,我们实现了通过串口控制 LED 灯开关、获取和设置网络地址信息、TCP 和 UDP 回环数据测试以及 UPnP 添加映射端口、删除映射端口和查询映射表的功能。
步骤1:设置以太网缓存大小
文件:USER/user_main.c
wizchip_initialize();
network_init(ethernet_buf, &default_net_info);
W6300 的 Socket 缓存分配在 network_init() 内部完成。其中 socket0 用于 UPnP 协议处理,socket1 用于 TCP 和 UDP 回环处理,socket2 用于监听 IGD 事件。
步骤2:LED 控制函数注册
UserLED_Control_Init(set_user_led_status);
set_user_led_status() 函数为控制 LED 的函数,用于菜单选项 1(点亮)和 2(熄灭)。
步骤3:搜索 UPnP 设备
do
{
printf("Send SSDP.. \r\n");
} while (SSDPProcess(SOCKET_ID) != 0); /* SSDP Search discovery */
SSDP 头部定义及 SSDPProcess() 函数通过 UDP 发送 M-SEARCH 组播报文并接收响应:
/* SSDP Header */
unsigned char SSDP[] = "\
M-SEARCH * HTTP/1.1\r\n\
Host:239.255.255.250:1900\r\n\
ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n\
Man:\"ssdp:discover\"\r\n\
MX:3\r\n\
\r\n\
";
signed char SSDPProcess(SOCKET sockfd)
{
...
unsigned char mcast_addr[4] = {239, 255, 255, 250};
unsigned char mcast_mac[6] = {0x01, 0x00, 0x5E, 0x7F, 0xFF, 0xFA};
close(sockfd);
setSn_DHAR(sockfd, mcast_mac);
setSn_DIPR(sockfd, mcast_addr);
setSn_DPORT(sockfd, PORT_SSDP);
setSn_MR(sockfd, Sn_MR_UDP);
setSn_PORTR(sockfd, PORT_SSDP);
setSn_CR(sockfd, Sn_CR_OPEN);
while (getSn_CR(sockfd));
if (getSn_SR(sockfd) != SOCK_UDP) return -1;
/* Send SSDP M-SEARCH */
if (sendto(sockfd, SSDP, strlen((char *)SSDP), mcast_addr, PORT_SSDP, 4) <= 0)
printf("SSDP Send error!!!!!!!\r\n");
/* Receive Reply: skip NOTIFY, only accept 200 OK */
endTime = my_time + 3;
while (my_time < endTime)
{
recv_len = recvfrom(sockfd, (unsigned char *)recv_buffer, RECV_BUFFER_SIZE,
recv_addr, &recv_port, &addr_len);
if (recv_len <= 0) continue;
if (strstr(recv_buffer, "HTTP/1.1 200 OK") == NULL) continue;
if ((ret_value = parseSSDP(recv_buffer)) == 0)
{
UPnP_Step = 1;
close(sockfd);
return 0;
}
}
close(sockfd);
return -1;
}W6300 多播发送
SSDP 的 M-SEARCH 必须发到多播地址 239.255.255.250:1900。W6300 的 sendto() 对多播地址需要手动指定目的 MAC,否则芯片会走 ARP 解析导致超时。这里通过 setSn_DHAR() 设置多播 MAC 01:00:5E:7F:FF:FA(239.255.255.250 对应的多播 MAC),让芯片直接按这个 MAC 发帧。
在这个函数中,主要是使用 SSDP 协议搜索 IGD 设备。成功解析后 UPnP_Step 置为 1。
步骤4:获取 IGD 设备描述
if (GetDescriptionProcess(SOCKET_ID) == 0) /* GET IGD description */
{
printf("GetDescription Success!!\r\n");
}
else
{
printf("GetDescription Fail!!\r\n");
}
GetDescriptionProcess() 建立 TCP 连接到 IGD,通过 MakeGETHeader() 组包发送 HTTP GET 请求,然后通过 parseDescription() 解析设备描述 XML,提取 controlURL 和 eventSubURL。
W6300 分块接收
W6300 默认每个 socket 的 RX 缓存为 2KB,单次 recv() 最多只能读 2KB。IGD 设备描述 XML 可能超过 2KB,一次读不完会导致 controlURL / eventSubURL 解析失败。因此源码里新增 upnp_recv_response() 函数,循环分块读取并拼接,直到读到结束标记(如 </root>)或超时。
如果设备描述中不支持 WANIPConnection 服务,则说明不支持端口映射,返回错误。解析成功后 UPnP_Step 置为 2。
步骤5:订阅 IGD 事件
if (SetEventing(SOCKET_ID) == 0) /* Subscribes IGD event messages */
{
printf("SetEventing Success!!\r\n");
}
else
{
printf("SetEventing Fail!!\r\n");
}
请求报文通过 MakeSubscribe() 函数组包,最后通过 parseHTTP() 函数解析 HTTP 响应报文,判断是否订阅成功。订阅成功后,后续 IGD 的状态变化会通过 eventing_listener() 回调处理。
步骤6:执行 UPnP 主程序
Main_Menu(SOCKET_ID, SOCKET_ID + 1, SOCKET_ID + 2,
ethernet_buf, tcps_port, udps_port); /* Main menu */在这里会执行一个用户选项菜单:选项 1 和 2 控制 LED 开关,选项 3 和 4 打印和设置网络地址信息,选项 5 运行 TCP 回环测试程序,选项 6 运行 UDP 回环测试程序,选项 7 添加一个 UPnP 端口映射表,选项 8 删除一个 UPnP 端口映射表,选项 9 查询路由器上所有端口映射表。这里我们主要讲解 UPnP 相关的选项 7、8、9。
步骤7:添加一个 UPnP 端口映射表
if (strcmp(choice, "7") == 0)
{
printf("\r\nType a Protocol(TCP/UDP) : ");
GetInputString(msg);
strncpy(protocol, msg, 3);
protocol[3] = '\0';
printf("\r\nType a External Port Number : ");
GetInputString(msg);
external_port = ATOI(msg, 10);
printf("\r\nType a Internal Port Number : ");
GetInputString(msg);
internal_port = ATOI(msg, 10);
tcps_port = internal_port;
close(sn2);
wizchip_getnetinfo(&net_info);
memcpy(Sip, net_info.ip, sizeof(Sip));
sprintf(ipaddr, "%d.%d.%d.%d", Sip[0], Sip[1], Sip[2], Sip[3]);
ret = AddPortProcess(sn, protocol, external_port, ipaddr, internal_port, "W6300_uPnPGetway");
if (ret == 0)
printf("AddPort Success!!\r\n");
else
printf("AddPort Error Code is %d\r\n", ret);
}
在这里需要外部输入端口映射的协议类型(TCP 或 UDP),以及外部端口号和内部端口号。输入完成后,通过 AddPortProcess() 函数执行添加端口映射处理。程序首先通过 MakeSOAPAddControl() 组装 SOAP XML,再通过 MakePOSTHeader() 拼接 HTTP 头部发送请求,最后通过 parseAddPort() 解析响应判断是否成功。
步骤8:删除一个 UPnP 端口映射表
if (strcmp(choice, "8") == 0)
{
printf("\r\nType a Protocol(TCP/UDP) : ");
GetInputString(msg);
strncpy(protocol, msg, 3);
protocol[3] = '\0';
printf("\r\nType a External Port Number : ");
GetInputString(msg);
external_port = ATOI(msg, 10);
ret = DeletePortProcess(sn, protocol, external_port);
if (ret == 0)
printf("DeletePort Success!!\r\n");
else
printf("DeletePort Error Code is %d\r\n", ret);
}
在这里需要外部输入删除端口映射的协议类型(TCP 或 UDP),以及外部端口号。输入完成后,通过 DeletePortProcess() 函数执行删除端口映射处理,通过 MakeSOAPDeleteControl() 组装 SOAP XML,发送后通过 parseDeletePort() 解析响应判断是否成功。
步骤9:查询 UPnP 端口映射表
if (strcmp(choice, "9") == 0)
{
bTreat = SET;
ret = GetGenericPortMappingEntriesProcess(sn);
if (ret == 0)
printf("\r\nListPortMappings Success!!\r\n");
else
printf("\r\nListPortMappings Error Code is %d\r\n", ret);
}
选项 9 通过 GetGenericPortMappingEntriesProcess() 函数调用 IGD 的 GetGenericPortMappingEntry 接口,从索引 0 开始逐个查询,把路由器上所有端口映射条目打印出来,直到返回错误码 713(数组索引无效,表示已到末尾)或 714 为止。
运行验证
请注意
测试实例需要 W6300 接入在支持 UPnP 端口转发的路由器下。
烧录例程运行后,首先进行了 PHY 链路检测,然后是通过 DHCP 获取网络地址并打印网络地址信息。接下来是搜索 IGD 设备,搜索成功后会进行获取设备描述以及设置订阅 IGD 事件,全部成功后则进入主菜单。
接着,我们输入 7,添加一个 TCP 协议的端口映射,外部端口为 12345,内部端口为 8000。打开 UPnP Tool 软件,点击刷新后可以看到我们添加的端口映射表。
然后我们输入 5,打开 TCP 回环测试程序。随后打开一个网络调试助手(如 SocketTester),选择 TCP Client 模式,服务器地址为路由器外部 IP 地址,端口号为外部端口号 12345,点击"Connect"连接后,可以看到成功连接到内部的 W6300 上了。UDP 也是同样进行操作。
接着我们输入 Q 退出回环测试程序,然后输入 8,将之前添加的 TCP 协议的 12345 外部端口删除。在UPnP Tool 上点击刷新,可以看到已经成功删除,再次执行回环测试程序,已经无法连接上内部的 W6300 了。






常见问题
Q: 为什么搜索不到 IGD 设备
请确认路由器支持 UPnP/IGD 功能且已开启。SSDP 使用 UDP 多播地址 239.255.255.250:1900,确保 W6300 与路由器在同一局域网且 DHCP 已正确获取 IP。另外 W6300 的 sendto() 对多播地址需要手动设置目的 MAC(通过 setSn_DHAR),源码中已设置 01:00:5E:7F:FF:FA。若路由器关闭了 UPnP 功能,M-SEARCH 请求将无法收到响应,函数会在 3 秒后超时返回 -1。
Q: 获取设备描述时提示不支持端口映射
parseDescription() 函数会在设备描述 XML 中查找 WANIPConnection 服务。如果路由器的设备描述中不包含该服务(仅支持 WANPPPConnection 或不提供端口映射功能),则说明该路由器不支持通过此方式进行端口映射,需要更换支持的路由器。
Q: AddPortMapping 返回错误码是什么意思
返回 0 表示成功;-2 表示 UPnP 步骤无效(未完成设备发现或描述获取);-1 表示响应超时;1 表示 XML 解析错误;其他值为 UPnP 错误码,如 718(ConflictInMappingEntry,端口已被映射)、500(ActionFailed)等。可通过 parseError() 函数获取详细错误描述。
Q: 为什么 GetDescriptionProcess 要分块接收
W6300 默认每个 socket 的 RX 缓存为 2KB,单次 recv() 最多只能读 2KB。IGD 设备描述 XML 可能超过 2KB,一次读不完会导致 controlURL / eventSubURL 解析失败。因此源码里新增 upnp_recv_response() 函数,循环分块读取并拼接,直到读到结束标记或超时。
Q: UPnP 端口映射的安全性如何
UPnP 端口映射会自动在路由器上开放端口,外部网络可直接访问内部设备,存在一定安全风险。建议仅在需要时添加映射,使用完毕后及时删除。NewRemoteHost 字段可限制来源 IP,NewLeaseDuration 可设置映射有效期(0 表示永久)。生产环境中应结合防火墙等安全措施使用。
总结
- 协议栈:UPnP 基于 SSDP(UDP 多播)发现设备、HTTP GET 获取描述、SUBSCRIBE 订阅事件、SOAP(HTTP POST)调用控制接口
- 工作流程:设备发现 → 获取描述 → 订阅事件 → 添加/删除/查询端口映射,通过 UPnP_Step 变量控制流程状态
- 核心接口:AddPortMapping 添加映射(需指定协议、内外端口、内部 IP),DeletePortMapping 删除映射(需指定协议和外部端口),GetGenericPortMappingEntry 查询映射表
- Socket 分配:socket0 处理 UPnP 协议,socket1 处理 TCP/UDP 回环,socket2 监听 IGD 事件
- W6300 特点:多播发送需手动设置目的 MAC,TCP 响应需分块接收(每 socket RX 缓存 2KB)
- 应用价值:消除手动配置端口转发的复杂性,实现 NAT 穿透,适用于远程访问 NAS、监控摄像头等场景