MacOS下UTUN接口写入的数据包无法送达目标应用问题排查
问题背景
在MacOS上开发一款VPN客户端,程序通过创建UTUN接口并添加路由规则,读取发往44.10.0.0/16的数据包,处理后伪造回复包发送给源应用。
程序执行步骤
- 创建utun接口,绑定IP地址
44.10.0.1 - 添加路由规则:
44.10.0.0/16-> utun设备 - 关联到utun接口
- 可从utun接口读取数据包
- 可向utun接口写入数据包(Wireshark可捕获且格式正常),但内核似乎会忽略这些包
已尝试操作
为测试程序,每当从utun接口读取到TCP数据包时,程序会伪造TCP RST包发回给源端,但源端始终无法接收:
现象:RST packets are ignored
预期行为
示例场景:在终端执行curl 44.10.0.240:8080,会发送TCP SYN包(抓包可见),程序读取到包后立即回复RST包(抓包也可见),但curl并未停止连接尝试,持续发送SYN包,仿佛未收到RST包。
补充信息
配置信息
接口配置
ifconfig utun3 utun3: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500 inet 44.10.0.1 --> 44.10.0.1 netmask 0xff000000
注:MacOS下配置utun接口必须指定源和目标IP,当前为Point-to-Point模式。
路由规则
Destination Gateway Flags Netif Expire 44 44.10.0.1 UGSc utun3 44.10.0.1 44.10.0.1 UH utun3
代码片段
配置utun接口的C代码:
/* Create interface */ int fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); struct ctl_info ctlInfo = {0}; strlcpy(ctlInfo.ctl_name, "com.apple.net.utun_control", sizeof(ctlInfo.ctl_name)); if (ioctl(fd, CTLIOCGINFO, &ctlInfo) == -1) { perror("ioctl"); return -1; } /* Connect to interface */ struct sockaddr_ctl sc = {0}; sc.sc_id = ctlInfo.ctl_id; sc.sc_len = sizeof(sc); sc.sc_family = AF_SYSTEM; sc.ss_sysaddr = AF_SYS_CONTROL; sc.sc_unit = 4;/* On my machine, this is the available sc unit for the previously created interface */ if (connect(fd, (struct sockaddr *)&sc, sizeof(sc)) < 0) { perror("Connect"); return -1; } /* Configure IP addresses */ char interface_name[] = "utun3"; struct sockaddr_in *sin; struct ifaliasreq ifra; int s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket(AF_INET, SOCK_DGRAM)"); return -1; } memset(&ifra, 0, sizeof(ifra)); strncpy(ifra.ifra_name, interface_name, sizeof(ifra.ifra_name)); // Setup source address sin = (struct sockaddr_in *)&ifra.ifra_addr; sin->sin_family = AF_INET; inet_pton(AF_INET, "44.10.0.1", &sin->sin_addr); sin->sin_len = sizeof(*sin); // Setup destination address sin = (struct sockaddr_in *)&ifra.ifra_broadaddr; sin->sin_family = AF_INET; inet_pton(AF_INET, "44.10.0.1", &sin->sin_addr); sin->sin_len = sizeof(*sin); if (ioctl(s, SIOCAIFADDR, &ifra) == -1) { perror("ioctl (SIOCAIFADDR)"); close(s); return -1; } close(s);
之后通过fd的read()和write()函数读写接口数据包。
其他异常现象
在已配置的utun接口上启动服务器无法接收任何数据包:
例如执行nc -l 44.10.0.1 7777,Netcat正常监听,但执行curl 44.10.0.1:7777时,SYN包可在Wireshark中捕获,但Netcat无法接收,该现象与当前问题同源。
注:MacOS防火墙已关闭,Pf也已禁用;伪造包的TCP序列号/确认号均验证正确。
问题列表
- 是否遗漏了某些网络配置?
- 为何写入接口的数据包可在Wireshark中捕获,但无法送达目标应用?
- 如何配置utun接口/路由表或调整代码,使内核能解析并将数据包送达目标应用?
解答
1. 遗漏的网络配置分析
核心问题是MacOS内核的IP输入路径过滤,主要遗漏以下关键配置:
- UTUN接口的P2P模式配置错误:将源和目标IP都设为
44.10.0.1,导致内核认为该接口的数据包只能本地回环,无法正确路由到主机TCP/IP协议栈。 - 缺少子网归属标识:当前配置未将
44.10.0.0/16标记为本地接口的子网,内核无法识别该段IP的本地可达性。 - 路由规则指向错误:现有路由将
44.0.0.0/8指向网关44.10.0.1,而非直接绑定到utun接口,导致内核无法正确处理子网内的数据包。
2. 数据包可见但无法送达的原因
Wireshark在数据链路层捕获数据包,但内核的IP输入栈会进行严格校验:
- 内核检查数据包目标IP是否属于本地接口子网,当前P2P配置下,
44.10.0.0/16未被标记为本地子网,因此丢弃数据包。 - 从UTUN接口写入的数据包,若接口配置不符合内核预期(如P2P端点重复),会被内核判定为非法来源,直接丢弃不向上传递到TCP/UDP协议栈。
- 路由规则中的
44.10.0.1 UH utun3仅针对单个IP,无法覆盖44.10.0.0/16段的其他IP,内核无法识别这些IP的本地归属。
3. 解决方法
调整UTUN接口配置
修改IP配置,将P2P模式改为子网模式,通过设置子网掩码替代重复的目标IP:
// 修改配置IP的代码部分,替换广播地址为子网掩码 memset(&ifra, 0, sizeof(ifra)); strncpy(ifra.ifra_name, interface_name, sizeof(ifra.ifra_name)); // Setup source address sin = (struct sockaddr_in *)&ifra.ifra_addr; sin->sin_family = AF_INET; inet_pton(AF_INET, "44.10.0.1", &sin->sin_addr); sin->sin_len = sizeof(*sin); // Setup subnet mask sin = (struct sockaddr_in *)&ifra.ifra_mask; sin->sin_family = AF_INET; inet_pton(AF_INET, "255.255.0.0", &sin->sin_addr); // 对应44.10.0.0/16 sin->sin_len = sizeof(*sin); if (ioctl(s, SIOCAIFADDR, &ifra) == -1) { perror("ioctl (SIOCAIFADDR)"); close(s); return -1; }
修改后ifconfig utun3应显示:
utun3: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500 inet 44.10.0.1 netmask 0xffff0000 broadcast 44.10.255.255
调整路由规则
删除现有路由,添加直接绑定到接口的子网路由:
sudo route delete 44.0.0.0/8 sudo route delete 44.10.0.1/32 # 添加子网路由,指定接口而非网关 sudo route add -net 44.10.0.0/16 -interface utun3
新路由表应显示:
Destination Gateway Flags Netif Expire 44.10.0.0/16 utun3 UCS utun3 44.10.0.1 utun3 UH utun3
调整数据包写入逻辑
从UTUN接口写入数据包时需确保:
- 伪造包的源IP属于
44.10.0.0/16段(如回复44.10.0.240的SYN包时,RST包的源IP应为44.10.0.240)。 - 伪造包的目的IP是发起请求的本地IP(如curl的源IP,通常为
192.168.x.x或127.0.0.1)。 - 确保TCP/IP校验和正确:MacOS内核会严格校验校验和,错误的校验和会导致数据包被丢弃。
验证配置有效性
修改后执行ping 44.10.0.1应收到回复,再测试nc -l 44.10.0.1 7777和curl 44.10.0.1:7777,确认数据包能正常送达。
内容的提问来源于stack exchange,提问作者Olshaften

