Repository navigation
UDP 可以按 UID 分流吗? #3391
|
目前在尝试做一个用户可自己配置的 TUN。思路是系统级别起一个 mihomo 做 TUN,然后上游按照 UID,分流到不同用户的 mihomo 中(当然用户的 mihomo 要从物理网卡/回到系统级 mihomo 从 DIRECT 出站)。 现在 TCP 的流量好像已经成功了,但是 UDP 会出现 (稍后会尝试补一个可复现的仓库) |
Replies: 3 comments 2 replies
|
有时候 TCP 好像也会漏? 😿 |
|
TL;DR:能,但别靠 UID 规则逐连接反查,更稳的是在 TUN 上用 看了下源码(Alpha 1. 先排除:代理不支持 UDP 系统级 mihomo 里指向用户 mihomo 的那个 proxy 必须支持 UDP:socks5 要写 2. 双栈 socket(推测) 桌面 Linux 的 你这个架构里最直接的例子就是每个用户自己的 mihomo。Linux 上 DIRECT、socks5、ss 的 UDP 出站都是 验证:临时设 # A:AF_INET
python3 -c 'import socket,time; s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.sendto(b"x",("1.1.1.1",9999)); print(s.getsockname()); time.sleep(5)'
# B:AF_INET6 双栈,发往同一个 IPv4
python3 -c 'import socket,time; s=socket.socket(socket.AF_INET6,socket.SOCK_DGRAM); s.sendto(b"x",("::ffff:1.1.1.1",9999)); print(s.getsockname()); time.sleep(5)'
别用 53 端口,会被 dns-hijack 截走。用 root 跑 另外几点
按用户分流:逐连接反查总有竞态(比如发完就关的 UDP socket)。更稳的是让内核在路由时就按 socket 的 UID 分:TUN 的 listeners:
- name: tun-1000
type: tun
device: tun1000 # 每个用户的 device / 地址 / table-index 都要不同
auto-route: true
inet4-address: [198.19.0.1/30]
iproute2-table-index: 2100
# 每个实例占用 rule index ~ index+10,互相不能重叠(间隔 ≥ 11);保留顶层 tun 时 index+10 要小于它的 9000
iproute2-rule-index: 8000
include-uid: [1000] # 范围写 include-uid-range: ["1000:1999"],必须用冒号
proxy: user-1000 # proxies 里 type: socks5 的条目,指向该用户 mihomo 的 socks/mixed 端口有 IPv6 的话,每个 TUN 也要配不同的 DNS 要注意:listener 型 tun 默认 用户自己的 mihomo 出站必须绕开所有 TUN(用该用户的 UID 跑的话,出站会被 include-uid 拉回自己的 TUN,形成环路)。一种是在用户 mihomo 里设 多 TUN 这套我没实测过。不用 listener 的话,手写 上游修复 patch(能编译,还没在 Linux 上跑过)思路:AF_INET 查不到时,再用 AF_INET6 + v4-mapped 源地址查一次。Android 的 procfs 兜底本来就是 --- a/component/process/process_linux.go
+++ b/component/process/process_linux.go
@@ -5,6 +5,7 @@ import (
"bytes"
"encoding/binary"
"encoding/hex"
+ "errors"
"fmt"
"net/netip"
"os"
@@ -90,7 +91,16 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string
return uid, pp, err
}
-func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uid uint32, inode uint32, err error) {
+func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) {
+ uid, inode, err := resolveSocketByNetlinkFamily(network, ip, srcPort)
+ if errors.Is(err, ErrNotFound) && ip.Is4() {
+ // 双栈 AF_INET6 socket 也会发 IPv4 包,但 INET_DIAG 只 dump 同 family 的 socket
+ return resolveSocketByNetlinkFamily(network, netip.AddrFrom16(ip.As16()), srcPort)
+ }
+ return uid, inode, err
+}
+
+func resolveSocketByNetlinkFamily(network string, ip netip.Addr, srcPort int) (uid uint32, inode uint32, err error) {
request := &inetDiagRequest{
States: 0xffffffff,
Cookie: [2]uint32{0xffffffff, 0xffffffff},如果你跑完 B 确实拿不到 UID,我可以把这个整理成 issue 或 PR。 |
TL;DR:能,但别靠 UID 规则逐连接反查,更稳的是在 TUN 上用
include-uid,让内核按 UID 分流(见下面「按用户分流」)。UDP 报could not get uid,先排除「代理不支持 UDP」,再看「双栈 socket」(每个用户自己的 mihomo 转发 UDP 用的就是这种 socket)。看了下源码(Alpha
69c16319)。我这边没法在你的环境里验证,下面按排查顺序说。1. 先排除:代理不支持 UDP
系统级 mihomo 里指向用户 mihomo 的那个 proxy 必须支持 UDP:socks5 要写
udp: true(默认是 false),http类型本身不支持 UDP。否则 UID 规则就算匹配上了也会被跳过(tunnel.go#L684-L687,只打一条 debug 级别的xxx UDP is not supported),UDP 落到后面的规则;在没打 #3324 的版本里,后面的 UID 规则就会打could not get uid。(用户 mihomo 那边的 socks/mixed 入站默认就支持 UDP,别关掉就行。)2. 双栈 socket(推测)
桌面 Linux 的
resolveSocketByNetlink只发一次INET_DIAGdump,family 跟着包的源地址走,IPv4 包就用AF_INET(L99-L103)。内核 dump 时会跳过sk_family不同的 socket(udp_diag.c#L120-L121,TCP 同理:tcp_diag.c#L509…