Closed Mythologyli closed 11 months ago
好功能,我今天之前看看 😸
IPv4 Route Table
===========================================================================
Active Routes:
Network Destination Netmask Gateway Interface Metric
0.0.0.0 0.0.0.0 192.168.8.1 192.168.8.200 25
0.0.0.0 0.0.0.0 On-link 10.190.65.75 10004
10.0.0.0 255.0.0.0 On-link 10.190.65.75 261
10.190.65.75 255.255.255.255 On-link 10.190.65.75 261
10.255.255.255 255.255.255.255 On-link 10.190.65.75 261
目前运行时路由表是这个样子
可以考虑使用ip命令取代ifconfig,所有Linux发行版基本上都提供了ip
命令(吧)
可以考虑使用ip命令取代ifconfig,所有Linux发行版基本上都提供了
ip
命令(吧)
确实,不过 macOS 上貌似还没有,所以 darwin 那个还是用 ifconfig
https://superuser.com/questions/687310/ip-command-in-mac-os-x-terminal/
想到一个可能需要的功能
开了tun mode后,尽管只在10.0.0.0/8网段下提供服务,但是可能还是需要有一个全局生效的dns服务器,开在tun设备的53端口上,来负责把*.zju.edu.cn解析到10.0.0.0/8, windows可以通过设置tun网卡的dns1,并把tun网卡metrics调低,用命令netsh interface ipv4 show interface
就可以看到dns优先级,linux就稍微麻烦点,得把/etc/resolve.conf覆写掉
Windows 下 DNS 设置现在我正在做,目前在写 DNS Server Linux 下也许可以用这种方法:https://github.com/xjasonlyu/tun2socks/wiki/Hijack-DNS
如果在 TUN 模式下,把 zju-connect 内部的 DNS 逻辑暴露成一个 DNS Server,并设置 TUN 接口使用这个 DNS,下面这个本地解析的逻辑会不会导致循环:
func (resolve *DnsResolve) ResolveWithLocal(ctx context.Context, host string) (context.Context, net.IP, error) {
if target, err := net.ResolveIPAddr("ip4", host); err != nil {
log.Printf("Resolve IPv4 addr failed using local DNS: " + host + ". Try IPv6 addr.")
if target, err = net.ResolveIPAddr("ip6", host); err != nil {
log.Printf("Resolve IPv6 addr failed using local DNS: " + host + ". Reject connection.")
return ctx, nil, err
} else {
log.Printf("%s -> %s", host, target.IP.String())
return ctx, target.IP, nil
}
} else {
log.Printf("%s -> %s", host, target.IP.String())
return ctx, target.IP, nil
}
}
感觉如果暴露出一个 DNS Server 的话,干脆取消解析失败后用 local DNS 的逻辑,这样就可以得到一个使用 ZJU DNS + DNS 规则 + 自定义 DNS 规则的 DNS Server。如果以上方式都失败就直接放弃
我也想过这个问题,感觉是会的,但是手头账号用于ical了没法试 😢
要么就取消解析失败后用 local DNS 的逻辑,这样就可以得到一个使用 ZJU DNS + 服务端下发的 DNS 规则 + 自定义 DNS 规则的 DNS Server。如果以上方式都失败就直接放弃
或者更复杂一点,给一个设置项手动指定后备 DNS
如果不设置后备dns的话,tun mode接管所有dns会不会有问题,可以考虑像clash tun mode那样默认几个国内的dns 不过windows下想了一下应该也没啥问题,毕竟可以支持使用多个dns, dns server没响应就用其他的。但是linux使用劫持了之后,还能使用其他网卡下的dns吗
flag.StringVar(&core.ZjuDnsServer, "zju-dns-server", "10.10.0.21", "ZJU DNS server address")
flag.StringVar(&core.SecondaryDnsServer, "secondary-dns-server", "114.114.114.114", "Secondary DNS server address. Leave empty to use system default DNS server")
flag.StringVar(&core.DnsServerBind, "dns-server-bind", "", "The address DNS server listens on (e.g. 127.0.0.1:53)")
flag.StringVar(&core.TunDnsServer, "tun-dns-server", "", "DNS Server address for TUN interface (e.g. 127.0.0.1). You should not specify the port")
打算用 secondary-dns-server 决定 ZJU DNS 解析失败后用什么。给一个默认值,如果留空就直接用默认的解析
Windows 虽然有多个 DNS,但超时时间会导致体验很差
打算用 secondary-dns-server 决定 ZJU DNS 解析失败后用什么。给一个默认值,如果留空就直接用默认的解析
那tun mode下ResolveWithLocal的逻辑,如果使用system default的dns会导致循环吗(
打算用 secondary-dns-server 决定 ZJU DNS 解析失败后用什么。给一个默认值,如果留空就直接用默认的解析
那tun mode下ResolveWithLocal的逻辑,如果使用system default的dns会导致循环吗(
如果设置成空就会,但默认是用 114.114.114.114
func (resolve *DnsResolve) Resolve(ctx context.Context, host string) (context.Context, net.IP, error) {
if config.IsDnsRuleAvailable() {
if ip, hasDnsRule := config.GetSingleDnsRule(host); hasDnsRule {
ctx = context.WithValue(ctx, "USE_PROXY", true)
log.Printf("%s -> %s", host, ip)
return ctx, net.ParseIP(ip), nil
}
}
var useProxy = false
if config.IsZjuForceProxyRuleAvailable() {
if isInZjuForceProxyRule := config.IsInZjuForceProxyRule(host); isInZjuForceProxyRule {
useProxy = true
}
}
if !useProxy && config.IsDomainRuleAvailable() {
if _, found := config.GetSingleDomainRule(host); found {
useProxy = true
}
}
ctx = context.WithValue(ctx, "USE_PROXY", useProxy)
if UseZjuDns {
if cachedIP, found := GetDnsCache(host); found {
log.Printf("%s -> %s", host, cachedIP.String())
return ctx, cachedIP, nil
} else {
这个地方现在的逻辑似乎是服务端下发的规则 > 自定义规则,因为 GetDnsCache
在后面
刚刚看了一下原版 EasyConnect,发现它会根据下发的规则把一些 IP 加入路由表。我打算做一下这个功能
我看到现在linux还没有拦截dns的代码,感觉在linux上可以用类似clash的机制,把所有dst是53的请求全扔到tun上,然后tun再去处理,这样即使用户没显式指定DnsServerBind,也能正常处理tun mode下的dns
clash tun下的ip rule:
9500: not from all dport 53 lookup main suppress_prefixlength 0 (所有非53端口的走main表)
9510: not from all iif lo lookup 1970566510 (所有非lo的走1970566510)
9520: from 0.0.0.0 iif lo uidrange 0-4294967294 lookup 1970566510
9530: from 198.18.0.1 iif lo uidrange 0-4294967294 lookup 1970566510
ip table 1970566510:
default dev utun proto static
对 ip rule 还不太懂()如果你有空的话要不实现一下这部分~我可以帮忙测试
对 ip rule 还不太懂()如果你有空的话要不实现一下这部分~我可以帮忙测试
okok,这两天我试着实现一下linux/drawin拦截dns部分
目前发现一个匪夷所思的问题
在使用 TUN 模式时,无论是直接走 TUN 还是通过 SOCKS5 间接走,都会在访问 speedtest.zju.edu.cn 时出现问题。一般表现为:第一次打开时网页能加载,测速时有延迟和抖动但是无速度,之后刷新会无法加载网站。此时哪怕是重连 zju-connect 也无法加载,等待一段时间后可能会恢复第一次打开时的状态。无法加载页面时的抓包结果:
别的网站暂时没发现此现象
Windows 和 Ubuntu 上使用 TUN 模式都可以复现这个问题。使用 gVisor 时不存在此问题,原版 EasyConnect 也不存在此问题
更新:目前发现这个 TUN 模式问题还不小
使用 TUN 模式 + SOCKS5 + proxy_all 测试,访问百度无法成功:
访问 cnki.net 却可以成功,访问 CC98 也成功:
如果用 gVisor,都可以成功
感觉像是 TUN 和 EasyConnect 服务端“不合”的问题
在 Ubuntu 下测试走 TUN 访问百度这些都没问题,当然 speedtest.zju.edu.cn 不行(还有学在浙大也不行)
在 Windows 下测试走 TUN 访问百度,通过 EasyConnect 服务端访问是不行的,如果先走 TUN 再走别的网卡访问(也就是直连)是可以的
百度的话是在服务端下发的规则里的,学在浙大也是常用的网站。感觉不解决这个问题这个 TUN 模式很难应用
测试结果: Windows: TUN+PROXY: 百度(失败)浙大测速(失败)学在浙大(失败) TUN+DIRECT: 百度(成功)浙大测速(成功)学在浙大(成功)
Ubuntu: TUN+PROXY: 百度(成功)浙大测速(失败)学在浙大(失败) TUN+DIRECT: 百度(成功)浙大测速(成功)学在浙大(成功)
我这边在wsl2 2.0(开启镜像windows网络) 上用wsl的chrome和wireshark也测了一下
TUN+PROXY: 百度(失败)浙大测速(失败)学在浙大(成功)cspo.zju.edu.cn(成功) TUN+DIRECT: 百度(成功)浙大测速(失败)学在浙大(成功)cspo.zju.edu.cn(成功)
BTW,我看到现在baidu.com会走proxy,翻了一下,使用domainutil.Domain拿到top domain,后续根据top domain进行匹配是正常的吗,看到baidu.com 是因为hm.baidu.com的规则被引入,那是否意思是应该hm.baidu.com走proxy,其他*.baidu.com走direct
func AppendSingleDomainRule(host string, ports []int, debug bool) {
if domainRules == nil {
domainRules = hashmap.New[string, []int]()
}
var domain = domainutil.Domain(host)
if domain == "" {
domain = host
}
if strings.Contains(host, "baidu") {
log.Printf("AppendSingleDomainRule: %s %s[%v]", host, domain, ports)
}
if debug {
log.Printf("AppendSingleDomainRule: %s[%v]", domain, ports)
}
domainRules.Set(domain, ports)
}
tun mode导致baidu无法访问的原因主要是因为route的设置,现在默认是10.0.0.0/8 才被路由到 tun 网卡,进而走rvpn逻辑,所以
if useProxy {
if TunMode {
if network == "tcp" {
log.Printf("tcp tun %s -> PROXY", addr)
addrTarget := net.TCPAddr{
IP: target.IP,
Port: port,
}
bind := net.TCPAddr{
IP: net.IP(dialer.client.clientIp),
Port: 0,
}
return net.DialTCP(network, &bind, &addrTarget)
这里只有addrTarget是10.0.0.0/8的包,才能被tun接收并处理,baidu.com解析出来是39.*.*.*,会直接以src ip: 10.190.*.*, dst ip:39.*.*.*的形式从wan口出
感觉问题不在 route 上,我先给 server 设置一条路由,然后给 tun 设置到 0.0.0.0 的路由,并且 metric 比有线网卡低,百度还是无法访问
在 TUN 网卡上抓包也能抓到:
并且一开始百度服务器是有回应的
感觉问题不在 route 上,我先给 server 设置一条路由,然后给 tun 设置到 0.0.0.0 的路由,并且 metric 比有线网卡低,百度还是无法访问
确实,现在 speedtest.zju.edu.cn 解析出来是10.0.0.0/8 但是还是没法访问,还得再想想。
灵光乍现,MTU改成1400即可! sudo ip link set dev tun0 mtu 1400
灵光乍现,MTU改成1400即可! sudo ip link set dev tun0 mtu 1400
Nice!果然是这样,在 Windows 这边也成功修复问题
之所以没想到是因为 tun_stack_windows.go 在 Setup TUN 的时候设置过 1400 的 MTU,结果用命令一查发现没生效(太坑了)
tun mode最终提供给的形式应该是0.0.0.0的路由还是10.0.0.0/8的路由呢,如果是10.0.0.0/8的形式话,tun mode 配合 proxy会有大问题,非10.0.0.0/8的ip没法响应
tun mode最终提供给的形式应该是0.0.0.0的路由还是10.0.0.0/8的路由呢,如果是10.0.0.0/8的形式话,tun mode 配合 proxy会有大问题,非10.0.0.0/8的ip没法响应
Windows 上可以设置两个 0.0.0.0 的路由并设置优先级,在 linux 上这样可行吗
Windows 上可以设置两个 0.0.0.0 的路由并设置优先级,在 linux 上这样可行吗
不可行,linux能干的事情是创两个路由表,每个路由表指定默认路由,然后将不同的流分到使用不同的路由表
感觉一个可行的思路是用10.0.0.0/8, 然后这里只处理addr 是10.0.0.0/8的,其他proxy情况交给gvisor处理,基本上也够tun的场景使用
dns则是全局劫持,windows和linux都能做到
if TunMode && target.IP[0] == 10{
if network == "tcp" {
log.Printf("tcp tun %s -> PROXY", addr)
addrTarget := net.TCPAddr{
IP: target.IP,
Port: port,
}
bind := net.TCPAddr{
IP: net.IP(dialer.client.clientIp),
Port: 0,
}
return net.DialTCP(network, &bind, &addrTarget)
} else if network == "udp" {
log.Printf("%s -> PROXY", addr)
addrTarget := net.UDPAddr{
IP: target.IP,
Port: port,
}
bind := net.UDPAddr{
IP: net.IP(dialer.client.clientIp),
Port: 0,
}
return net.DialUDP(network, &bind, &addrTarget)
意思是同时启用 TUN 和 gVisor 网络栈吗,有没有可能产生冲突,比如发出的包的源端口,还有入站的情况怎么处理
意思是同时启用 TUN 和 gVisor 网络栈吗,有没有可能产生冲突,比如发出的包的源端口,还有入站的情况怎么处理
是的同时启用,确实我没试过启用这俩会不会有问题,不过我猜测大概率可行。源端口的话还按照现在的代码来应该就能用)
入站的话 gVisor应该不用其他处理,其他入站情况
- dns解析劫持,开启tun模式后所有dns走proxy
- 10.* ip 直接进tun设备走代理
- http proxy和socks proxy走代理的话根据是不是10.*区分
- 非http/socks proxy, 非10.* ip 正常走wan口出
好像不行,再想想。。
其实最“漂亮”的方案是像 https://github.com/xjasonlyu/tun2socks 那样,用一个 gVisor 网络栈处理来自 TUN 设备的 TCP/UDP,然后改一改 tun2socks 的 proxy(https://github.com/xjasonlyu/tun2socks/tree/main/proxy) ,走 EasyConnect 的 gVisor 栈出去,相当于起两个 gVisor 栈,不过代价会不会有点大了(
我感觉是不是有可能只创建一个gVisor栈,把tun设备整成一个EasyConnectGvisorEndpoint NIC,添加到gVisor栈中,然后所有路由都走之前的default NIC出,看起来就很合理
意思是同时启用 TUN 和 gVisor 网络栈吗,有没有可能产生冲突,比如发出的包的源端口,还有入站的情况怎么处理
如果严格保证 10.0.0.0/8 走 TUN 其他走 gVisor 也许能保证源端口不冲突?()
EasyConnect 服务端发回的每一个包都要同时往 TUN 和 gVisor 里面写
我感觉是不是有可能只创建一个gVisor栈,把tun设备整成一个EasyConnectGvisorEndpoint NIC,添加到gVisor栈中,然后所有路由都走之前的default NIC出,看起来就很合理
这样话应该没法给 TUN 设备设置正确的 IP,大概率要放弃入站功能(RVPN 的网卡是可以入站的,并且没有防火墙)
如果严格保证 10.0.0.0/8 走 TUN 其他走 gVisor 也许能保证源端口不冲突?()
刚才试了下,起不来,只能和server端建立一个tls 😢
找到一个有意思的实现:
https://github.com/jursonmo/tcpbinddev
使用原生的BindToDevice之后connection就不归go的网络轮询器管理了,之后读会用阻塞的read syscall,这个使用net.FileConn(file)再给connection加回网络轮询器
现在把代码改成这样是可以正常tun mode下用http proxy下访问内网外网的,美中不足就是只有linux实现
if useProxy {
if TunMode {
if network == "tcp" {
log.Printf("tcp tun %s -> PROXY", addr)
addrTarget := net.TCPAddr{
IP: target.IP,
Port: port,
}
bind := net.TCPAddr{
IP: net.IP(dialer.client.clientIp),
Port: 0,
}
return tcpbinddev.TcpBindToDev("tcp4", addrTarget.String(), bind.String(), "tun0", 3)
https://github.com/extremecoders-re/go-dispatch-proxy/blob/master/servers_response_linux.go#L24-L24 这个方案我昨天试是可以的
https://github.com/jursonmo/tcpbinddev 这一个我刚才试一下也是可以的。看他的 README 这个是有性能优势嘛
另外 UDP 的话有类似的实现吗
https://github.com/jursonmo/tcpbinddev 这一个我刚才试一下也是可以的。看他的 README 这个是有性能优势嘛
我仔细分析了一下go的dial过程,发现他claim的点是在使用原生的syscall.Socket下有性能优势,但是如果像dispatch repo里使用Dialer下的Control函数,就还是走go的网络监控器,一切和net.DialTCP类似,所以dispatch的方案更好,而且更加通用
分析到这里发现调用Control是在调用TCP/UDP都通用的路径上,所以这个方法应该TCP/UDP都通用(猜测)
OS X 的情况还不太了解,不知道是像 Windows 那样绑定的 IP 就可以还是要想办法绑定到接口。你那边有条件测试嘛
我晚上可以测一下macos上的效果。btw,windows不需要绑定到接口吗(
不需要,Windows 只需要一条 0.0.0.0/0 的路由(把 metric 设置为 9999 避免成为默认),然后 dial 的时候设置源 IP 就可以
func (s *Stack) DialTCP(addr *net.TCPAddr) (net.Conn, error) {
return net.DialTCP("tcp4", &net.TCPAddr{
IP: s.endpoint.ip,
Port: 0,
}, addr)
}
func (s *Stack) DialUDP(addr *net.UDPAddr) (net.Conn, error) {
return net.DialUDP("udp4", &net.UDPAddr{
IP: s.endpoint.ip,
Port: 0,
}, addr)
}
在macos上找到了这样的解决方法,并测试成功:
if TunMode {
if network == "tcp" {
log.Printf("%s -> PROXY", addr)
goDialer := &net.Dialer{
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
iface, err := net.InterfaceByName("utun10")
if err != nil {
fmt.Println(err)
}
if err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVIF, iface.Index); err != nil {
fmt.Println(err)
}
})
},
}
return goDialer.Dial("tcp", addr)
https://github.com/insomniacslk/dhcp/issues/378 https://stackoverflow.com/questions/20616029/os-x-equivalent-of-so-bindtodevice/57013928#57013928 这两个里面都提到了使用IP_BOUND_IF这个flag,但是我使用这个flag并不行,设置成IP_RECVIF则可以在proxy mode下正确的访问内网外网
测试了一下macos上clash tun mode的情况,由于没有linux那样的网络子系统,clash是修改dns到8.8.8.8,然后路由写死,全部流量进clash,包括8.8.8.8:53,然后再用之前系统的dns进行解析。
感觉最后可以提供一个这样形式的dns server:
还有一个想法:初步实现中,使用配置文件中指定的SecondaryDns感觉没啥问题,但是后续改成可以自动使用用户机器原本的dns
此 PR 希望为 zju-connect 带来 TUN 模式,具体的好处有:
目前的初步想法为:
10.0.0.0/8
。如果路由0.0.0.0
可能分流比较复杂存在的问题:
github.com/songgao/water
对 Android 的支持似乎不好,所以目前暂未实现 Android