macOS 中是如何将 utun 网卡接口接管所有网络请求的? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ethusdt
0.01D
V2EX    macOS

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

  •  
  •   ethusdt 2023 年 9 月 10 日 4789 次点击
    这是一个创建于 956 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比如使用 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. 处理收到的网络请求

    所以总结下我的问题,第二步是如何接管系统所有请求的?第四步是如何转发到其他接口的?在 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 年 9 月 10 日
    什么叫“将……接管”?
    恕我无法对你的汉语进行语法分析
    v2gba
        2
    v2gba  
       2023 年 9 月 10 日
    > 接管系统的请求

    就好比你装了雷电物理网卡,你在系统里选择优先使用这个网卡,那么系统就会默认使用他。大部分情况下软件都会按照系统的优先级使用第一个网络,但是软件也可以不遵守规则强制使用某一个,这时候 tun 代理就失效了。
    ethusdt
        3
    ethusdt  
    OP
       2023 年 9 月 10 日
    @julyclyde 哈哈 标题起得草率了,没有回去读一遍。原意是想表达某些应用比如 clashxpro 是如何将它新建的 utun interface 起到接管整个系统网络请求的。
    leonshaw
        4
    leonshaw  
       2023 年 9 月 10 日 via Android   1
    2 是把流通过操作系统导入 tun ,在桌面端一般是路由和 nf/pf/wfp 等,移动端就是 VPN 相关 API
    4 是正常的 socket
    lcdtyph
        5
    lcdtyph  
       2023 年 9 月 10 日   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 年 9 月 10 日
    可以参考这个项目的 README 就可以解了

    https://github.com/songgao/water

    赞同 #5
    ethusdt
        7
    ethusdt  
    OP
       2023 年 9 月 10 日
    @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 年 9 月 10 日 via iPhone
    路由表指定设备,曾经用这个折腾本地开发环境访问线上,还被警告了
    ethusdt
        9
    ethusdt  
    OP
       2023 年 9 月 10 日
    @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 年 9 月 10 日
    @lcdtyph 让我想起以前 OpenVPN 的,做了两条就覆盖了所有地址
    leonshaw
        11
    leonshaw  
       2023 年 9 月 10 日 via Android
    @FaiChou tun 不是 socket 。linux 下一般是打开 /dev/tun 创建设备,读写这个 fd ,把读到的 IP 包按 VPN 协议封装后发出去。
    ethusdt
        12
    ethusdt  
    OP
       2023 年 9 月 10 日
    @leonshaw #11 我理解的应该没错吧,在 linux/unix 中一切备和 I/O 操作都是通过文件描述符抽象的。"把读到的 IP 包按 VPN 协议封装后发出去" 发出去也需要经过物理网口吧,那最终也是需要经过绑定这个物理网口的 "eth0" 网络接口。所以应该是 app network->utun->eth0->network
    ethusdt
        13
    ethusdt  
    OP
       2023 年 9 月 10 日
    @julyclyde #10
    @lcdtyph

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

    重复三遍:
    对操作系统来说,虚拟网卡跟物理网卡是一样性质的网卡。
    对操作系统来说,虚拟网卡跟物理网卡是一样性质的网卡。
    对操作系统来说,虚拟网卡跟物理网卡是一样性质的网卡。
    lcdtyph
        15
    lcdtyph  
       2023 年 9 月 11 日 via iPhone
    @FaiChou
    我记得这是 bsd 特有的表达法,就是后缀零可以省略掉
    比如说 16.0.0.0/4 可以直接省略成 16/4
    lcdtyph
        16
    lcdtyph  
       2023 年 9 月 11 日 via iPhone
    @julyclyde
    是的,0.0.0.0/1 和 128.0.0.0/1
    zzzkkk
        17
    zzzkkk  
       2023 年 9 月 11 日 via Android
    我的理解是楼主还没明白 tproxy 和 vpn 区别 对不对
    就算 tproxy 也不是这种 socket 拷贝
    zzzkkk
        18
    zzzkkk  
       2023 年 9 月 11 日 via Android
    tproxy 是指 shadowsocks 的 ss-redir
    ethusdt
        19
    ethusdt  
    OP
       2023 年 9 月 11 日 via iPhone
    @lcdtyph 哦原来是这样啊,和 ipv6 一样省略连起来的 0 。
    slowmist
        20
    slowmist  
       2023 年 9 月 11 日
    @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 年 9 月 11 日
    @MrGba2z 但是软件也可以不遵守规则强制使用某一个
    哪个软件这么厉害?
    要怎么应对?
    lcdtyph
        22
    lcdtyph  
       2023 年 9 月 11 日 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 年 9 月 11 日 via iPhone
    @slowmist
    比如一些多网口负载均衡的服务就会自己 bind device ,比如 webrtc 好像会多网口同时尝试,quic 草案里也有类似的机制
    要想完全避免这种情况只能上透明代理,把 clash 挪到网关上去;或者本机上防火墙规则
    slowmist
        24
    slowmist  
       2023 年 9 月 11 日
    @lcdtyph
    原来是这样啊 解惑了
    t/590555
    不知道这哥们解决了吗

    firewall 不是问题 macos 上有 pf

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

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

    我没用过 google one
    不过我猜它可能是直接去改的 resolv.conf 或者用 scutil 来设置的 dns
    ethusdt
        27
    ethusdt  
    OP
       2023 年 9 月 11 日
    @slowmist #21 开了 clashxpro 增强,但同时 Tailscale 也开着,Tailscale 的请求貌似就不会走 clashxpro 的 utun ,而是通过路由表走自己的 utun 然后请求再发出去,这样就 bypass 了 clashxpro 。不知道我说的对不。
    slowmist
        28
    slowmist  
       2023 年 9 月 11 日
    @FaiChou 要看各自的设置和 log 了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4765 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 71ms UTC 09:42 PVG 17:42 LAX 02:42 JFK 05:42
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86