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

MacOS下UTUN接口写入的数据包无法送达目标应用问题排查

MacOS UTUN接口VPN客户端数据包送达问题

问题背景

在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序列号/确认号均验证正确。


问题列表

  1. 是否遗漏了某些网络配置?
  2. 为何写入接口的数据包可在Wireshark中捕获,但无法送达目标应用?
  3. 如何配置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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 22:25:54