V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
FaiChou
V2EX  ›  macOS

macOS 中是如何将 utun 网卡接口接管所有网络请求的?

  •  
  •   FaiChou · 2023-09-10 18:48:07 +08:00 · 2541 次点击
    这是一个创建于 444 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比如使用 ClashX Pro 开启了 Enhanced Mode 后,通过 ifconfig 可以看到有一个 utun10:

    utun10: [
        {
          address: '198.18.0.1',
          netmask: '255.255.0.0',
          family: 'IPv4',
          mac: '00:00:00:00:00:00',
          internal: false,
          cidr: '198.18.0.1/16'
        }
    ]
    

    但它是如何接管电脑上所有的网络请求的呢? ClashX Pro 是闭源的,没有办法查看源码。

    通过命令 route -n get default 查看默认 route 是 en0 接口:

       route to: default
    destination: default
           mask: default
        gateway: 192.168.11.1
      interface: en0
          flags: <UP,GATEWAY,DONE,STATIC,PRCLONING,GLOBAL>
     recvpipe  sendpipe  ssthresh  rtt,msec    rttvar  hopcount      mtu     expire
           0         0         0         0         0         0      1500         0
    

    难道使用 Network Extension 中的 Packet Tunnel Provider 不需要手动设置网络的转发吗?

    我所理解的过程是这样的:

    1. 新建一个 TUN 网络接口
    2. 接管系统的请求
    3. 将请求处理
    4. 处理后的接口转发到其他网络接口
    5. 收到网络请求后其他网络接口转发到 TUN 网络接口中
    6. 处理收到的网络请求
    7. 传递给上层应用

    所以总结下我的问题,第二步是如何接管系统所有请求的?第四步是如何转发到其他接口的?在 Linux 中应该是需要手动写吧?在 macOS 中是用 NE 里哪一些 API ?

    另外,在 iOS 端也是和 macOS 一样的吧?开启 QX 应用后,在设置中可以看到有 utun 接口。类似的应用都是用 TUN 模式的( Loon 用 TUN 和 HTTP mode 两种模式)。

    28 条回复    2023-09-11 12:08:20 +08:00
    julyclyde
        1
    julyclyde  
       2023-09-10 19:14:29 +08:00
    什么叫“将……接管”?
    恕我无法对你的汉语进行语法分析
    MrGba2z
        2
    MrGba2z  
       2023-09-10 19:17:59 +08:00
    > 接管系统的请求

    就好比你装了雷电物理网卡,你在系统里选择优先使用这个网卡,那么系统就会默认使用他。大部分情况下软件都会按照系统的优先级使用第一个网络,但是软件也可以不遵守规则强制使用某一个,这时候 tun 代理就失效了。
    FaiChou
        3
    FaiChou  
    OP
       2023-09-10 19:29:41 +08:00
    @julyclyde 哈哈 标题起得草率了,没有回去读一遍。原意是想表达某些应用比如 clashxpro 是如何将它新建的 utun interface 起到接管整个系统网络请求的。
    leonshaw
        4
    leonshaw  
       2023-09-10 19:30:18 +08:00 via Android   ❤️ 1
    2 是把流通过操作系统导入 tun ,在桌面端一般是路由和 nf/pf/wfp 等,移动端就是 VPN 相关 API
    4 是正常的 socket
    lcdtyph
        5
    lcdtyph  
       2023-09-10 20:27:50 +08:00   ❤️ 3
    @FaiChou #3
    就是建立优先级更高的路由表项来把流量路由到 utun 而已
    default 相当于 0.0.0.0/0 是优先级最低的匹配项
    掩码长度越长优先级越高,netstat -nrf inet 可以看到以下几个路由表项
    default via en0
    1.0.0.0/8 via utun
    2.0.0.0/7 via utun
    4.0.0.0/6 via utun
    8.0.0.0/5 via utun
    ...
    128.0.0.0/1 via utun
    这样可以在不修改 default 路由的情况下把系统流量“劫持”到 utun
    fuis
        6
    fuis  
       2023-09-10 20:48:40 +08:00
    可以参考这个项目的 README 就可以了解了

    https://github.com/songgao/water

    赞同 #5
    FaiChou
        7
    FaiChou  
    OP
       2023-09-10 20:58:13 +08:00
    @lcdtyph 谢谢,之前一直搞不懂 netstat -rn 给出的这个结果,你一讲我明白了:

    default 192.168.11.1 UGScg en0
    default link#25 UCSIg utun3
    1 198.18.0.1 UGSc utun10
    2/7 198.18.0.1 UGSc utun10
    4/6 198.18.0.1 UGSc utun10
    8/5 198.18.0.1 UGSc utun10
    16/4 198.18.0.1 UGSc utun10
    32/3 198.18.0.1 UGSc utun10
    64/2 198.18.0.1 UGSc utun10
    100.64/10 link#25 UCS utun3
    duduke
        8
    duduke  
       2023-09-10 20:58:56 +08:00 via iPhone
    路由表指定设备,曾经用这个折腾本地开发环境访问线上,还被警告了😂
    FaiChou
        9
    FaiChou  
    OP
       2023-09-10 21:22:45 +08:00
    @leonshaw 嗯 谢谢,我大概写一下这个 **4 处理后的请求转发到其他网口** 这个逻辑:

    ```c
    // 创建两个套接字
    int utun_sock = socket(AF_INET, SOCK_STREAM, 0);
    int eth0_sock = socket(AF_INET, SOCK_STREAM, 0);

    // 绑定 utun_sock 到 utun 的 IP 地址
    struct sockaddr_in utun_addr;
    // 初始化 utun_addr
    bind(utun_sock, (struct sockaddr *)&utun_addr, sizeof(utun_addr));

    // 绑定 eth0_sock 到 eth0 接口
    setsockopt(eth0_sock, SOL_SOCKET, SO_BINDTODEVICE, "eth0", strlen("eth0"));

    // 读取数据并转发
    char buffer[2048];
    while (1) {
    // 从 utun_sock 读取数据
    int n = read(utun_sock, buffer, sizeof(buffer));
    if (n <= 0) {
    // 错误处理
    break;
    }

    // 将数据写入 eth0_sock
    int m = write(eth0_sock, buffer, n);
    if (m <= 0) {
    // 错误处理
    break;
    }
    }

    ```

    大概是这样吧。
    julyclyde
        10
    julyclyde  
       2023-09-10 21:25:48 +08:00
    @lcdtyph 让我想起以前 OpenVPN 的,做了两条就覆盖了所有地址
    leonshaw
        11
    leonshaw  
       2023-09-10 22:09:14 +08:00 via Android
    @FaiChou tun 不是 socket 。linux 下一般是打开 /dev/tun 创建设备,读写这个 fd ,把读到的 IP 包按 VPN 协议封装后发出去。
    FaiChou
        12
    FaiChou  
    OP
       2023-09-10 22:14:42 +08:00
    @leonshaw #11 我理解的应该没错吧,在 linux/unix 中一切设备和 I/O 操作都是通过文件描述符抽象的。"把读到的 IP 包按 VPN 协议封装后发出去" 发出去也需要经过物理网口吧,那最终也是需要经过绑定这个物理网口的 "eth0" 网络接口。所以应该是 app network->utun->eth0->network
    FaiChou
        13
    FaiChou  
    OP
       2023-09-10 22:26:46 +08:00
    @julyclyde #10
    @lcdtyph

    clashxpro 开增强后,netstat -rn 给出的 `1 2/7 4/6 8/5 16/4 32/3 64/2` 不是标准的 IP 地址或网络地址,那它是怎么展开的,代表哪些地址段?
    adoal
        14
    adoal  
       2023-09-10 22:57:13 +08:00
    并不是 utun“接管”了系统的网络流量再按需转发到物理网卡。
    utun 这种虚拟网卡,在操作系统的看来跟物理网卡一样,都是货真价实的网卡。
    只不过物理网卡插的是物理线,虚拟网卡插的是虚拟线。
    你就理解成在 utun 和远程服务器上的 tun 虚拟网卡之间拉了一根“线”(虽然不存在物理形状),让你的电脑多了一个网卡。只不过这根线的底层是一个通过物理网卡连的 VPN 而已。
    那么电脑发出去的包是要走物理网卡还是虚拟网卡,只是正常的路由选择而已,不存在任何接管的手续。
    只不过根据路由表确定走虚拟网卡的时候,再由虚拟网卡的驱动封包走虚拟网卡。

    重复三遍:
    对操作系统来说,虚拟网卡跟物理网卡是一样性质的网卡。
    对操作系统来说,虚拟网卡跟物理网卡是一样性质的网卡。
    对操作系统来说,虚拟网卡跟物理网卡是一样性质的网卡。
    lcdtyph
        15
    lcdtyph  
       2023-09-11 00:13:34 +08:00 via iPhone
    @FaiChou
    我记得这是 bsd 特有的表达法,就是后缀零可以省略掉
    比如说 16.0.0.0/4 可以直接省略成 16/4
    lcdtyph
        16
    lcdtyph  
       2023-09-11 00:15:24 +08:00 via iPhone
    @julyclyde
    是的,0.0.0.0/1 和 128.0.0.0/1
    zzzkkk
        17
    zzzkkk  
       2023-09-11 00:31:39 +08:00 via Android
    我的理解是楼主还没明白 tproxy 和 vpn 区别 对不对
    就算 tproxy 也不是这种 socket 拷贝
    zzzkkk
        18
    zzzkkk  
       2023-09-11 00:32:35 +08:00 via Android
    tproxy 是指 shadowsocks 的 ss-redir
    FaiChou
        19
    FaiChou  
    OP
       2023-09-11 09:04:38 +08:00 via iPhone
    @lcdtyph 哦原来是这样啊,和 ipv6 一样省略连起来的 0 。
    slowmist
        20
    slowmist  
       2023-09-11 09:04:39 +08:00
    @lcdtyph

    所有路由给 utun33
    小🐱遇到 ip direct 的时候是怎么处理的
    比如让 223.5.5.5 直连 走 default en10 出去
    从系统原理上和代码上怎么理解
    怎么避免产生回环的?

    netstat -rnf inet
    Routing tables

    Internet:
    Destination Gateway Flags Netif Expire
    default 192.168.88.1 UGScg en10
    1 198.18.0.1 UGSc utun33
    2/7 198.18.0.1 UGSc utun33
    4/6 198.18.0.1 UGSc utun33
    8/5 198.18.0.1 UGSc utun33
    16/4 198.18.0.1 UGSc utun33
    32/3 198.18.0.1 UGSc utun33
    64/2 198.18.0.1 UGSc utun33
    127 127.0.0.1 UCS lo0
    127.0.0.1 127.0.0.1 UH lo0
    128.0/1 198.18.0.1 UGSc utun33
    169.254 link#7 UCS en10 !
    192.168.88 link#7 UCS en10 !
    192.168.88.1/32 link#7 UCS en10 !
    192.168.88.1 cd:cd:cd:cd:cd:cd UHLWIir en10 1179
    192.168.88.8/32 link#7 UCS en10 !
    192.168.88.255 ff:ff:ff:ff:ff:ff UHLWbI en10 !
    198.18.0.1 198.18.0.1 UH utun33

    Flags:路由的标志位
    U:Up: 路由处于活动状态。
    H:Host: 路由目标是单个主机。
    G:Gateway: 所有发到目的地的网络传到这一远程系统上, 并由它决定最后发到哪里。
    S:Static: 这个路由是手工配置的,不是由系统自动生成的。
    C:Clone: 生成一个新的路由, 通过这个路由我们可以连接上这些机子。 这种类型的路由通常用于本地网络。
    W:WasCloned: 指明一个路由――它是基于本地区域网络 (克隆) 路由自动配置的。
    L:Link: 路由涉及到了以太网硬件。
    Netif: 网络接口,如 en0 ,是我的机器默认 wifi 接口,而 lo0 表示本机(“回环设备”),也就是这条规则的包不通过 Lan 来发出

    差不多是这样:?
    sudo route add -net 1.0.0.0/8 198.18.0.1
    sudo route add -net 2.0.0.0/7 198.18.0.1
    sudo route add -net 4.0.0.0/6 198.18.0.1
    sudo route add -net 8.0.0.0/5 198.18.0.1
    sudo route add -net 16.0.0.0/4 198.18.0.1
    sudo route add -net 32.0.0.0/3 198.18.0.1
    sudo route add -net 64.0.0.0/2 198.18.0.1
    sudo route add -net 128.0.0.0/1 198.18.0.1
    sudo route add -net 198.18.0.0/15 198.18.0.1
    slowmist
        21
    slowmist  
       2023-09-11 09:22:03 +08:00
    @MrGba2z 但是软件也可以不遵守规则强制使用某一个
    哪个软件这么厉害?
    要怎么应对?
    lcdtyph
        22
    lcdtyph  
       2023-09-11 09:31:33 +08:00 via iPhone
    @slowmist
    各个平台都有类似的 socket option
    macos 上是 IP_BOUND_IF ,或者更简单的,直接 bind 你想使用的那个 interface 的地址再进行 connect 就行了
    linux 是 SO_BINDTODEVICE

    你想让 223.5.5.5 直连,setsockopt(IP_BOUND_IF) 然后 connect(223.5.5.5.5) 就行了

    这样可以让 OS 做路由决策的时候忽略掉其他 interface 的路由,以此避免回环
    lcdtyph
        23
    lcdtyph  
       2023-09-11 09:40:38 +08:00 via iPhone
    @slowmist
    比如一些多网口负载均衡的服务就会自己 bind device ,比如 webrtc 好像会多网口同时尝试,quic 草案里也有类似的机制
    要想完全避免这种情况只能上透明代理,把 clash 挪到网关上去;或者本机上防火墙规则
    slowmist
        24
    slowmist  
       2023-09-11 10:17:44 +08:00
    @lcdtyph
    原来是这样啊 解惑了
    https://www.v2ex.com/t/590555
    不知道这哥们解决了吗

    firewall 不是问题 macos 上有 pf

    之前测试 google one vbn
    它的 utun 会路由所有的 v4 v6
    第一次遇到遇到路由 v6 的 即使本机没有 v6 地址
    而且会修改 dns 到 8.8.8.8 4.4 和对应的 v6 地址
    cat /etc/resolv.conf
    里面是这样显示的

    系统设置里面的自定义 dns 已经失效了 google 的 dns 最优先了
    不知道用什么方法实现的
    julyclyde
        25
    julyclyde  
       2023-09-11 10:32:41 +08:00
    @FaiChou “封装后”再发出去那就是另一次发送了。另一次就独立的进行全套的路由选择
    以及,eth0 并没有对应的/dev/文件
    lcdtyph
        26
    lcdtyph  
       2023-09-11 10:56:31 +08:00 via iPhone
    @slowmist
    我看了一下这哥们问题应该出在路由表

    我没用过 google one
    不过我猜它可能是直接去改的 resolv.conf 或者用 scutil 来设置的 dns
    FaiChou
        27
    FaiChou  
    OP
       2023-09-11 11:54:01 +08:00
    @slowmist #21 开了 clashxpro 增强,但同时 Tailscale 也开着,Tailscale 的请求貌似就不会走 clashxpro 的 utun ,而是通过路由表走自己的 utun 然后请求再发出去,这样就 bypass 了 clashxpro 。不知道我说的对不。
    slowmist
        28
    slowmist  
       2023-09-11 12:08:20 +08:00
    @FaiChou 要看各自的设置和 log 了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2739 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 15:28 · PVG 23:28 · LAX 07:28 · JFK 10:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.