New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: tcp relay #219
Fix: tcp relay #219
Conversation
@xjasonlyu PTAL. |
#218 这个问题我没仔细看,理论上不应该会有问题🤔 不过需要注意的是,在tun2socks里relay的时候,有一侧并不是 |
嗯。 考虑了的,加上了这个方法: func (tt *tcpTracker) CloseWrite() error {
return tt.Conn.(*net.TCPConn).CloseWrite()
}
|
当时加超时机制主要是因为,有时候有一端明明已经断开了,但是netstack的连接还是没有正确关闭,即出现了连接永久挂起的问题。不知道这个解决方案会不会有这个问题。 |
从你描述的问题以及代码看,应该是因为一方关闭断开后(其中一个 目前的解决方案,不会有这个问题。 |
不过我参考类似v2ray里的relay代码,他们也是用这种的timeout机制的。大多数客户端一般就直接close,较少才会closeWrite/Read分开。 |
我测试了v2ray也有同样的bug。 绝大部分应用层代码都只需要调用Close即可(因为大多数时候,都是在业务逻辑处理完成后,调用一次Close即可,后续就直接退出了,没有后续逻辑要处理了),只有做一些更底层的应用,一些较特殊的场景,可能会调用CloseWrite。 而Tun2socks是需要处理所有通用场景,因此需要考虑使用。 |
有道理,我再多测试一下! |
看了一下现在的Clash relay代码,也是使用的类似设置超时的方式。我看看是否也存在这个问题。 |
OK,我测试了几次,感觉这么写似乎没有问题! |
然后我觉得,可以在closeWrite的同时也setReadDeadline。 另外,我觉得不需要再在tracker里添加closeWrite方法,直接在relay里通过一个closeWriter接口判断能不能调用,可以的话直接调用就行了。 |
我按自己的理解重新优化了一下代码,你再看看。 |
关于 原理:如果 实现:给 |
我昨天一直在找有没有什么更优雅的solution,然后参考了一些代码,比如 cloudflared: 它这个似乎直接就一侧完成以后就退出close掉了,我有点迷惑。 |
看上去它也没有处理CloseWrite的问题,不知道是不是cloudflared大部分场景都是用于HTTP的请求协议,HTTP请求过来的话,本身也没办法使用CloseWrite(无法支持),然后它把到目标地址的连接直接关闭了,HTTP服务这边是可以自动重用连接的。 |
我按上面说的优化方案,更新了一版代码。 |
我看看。
另外,这里我觉得可以把tcp wait timeout变成一个cli 参数,这块我先去调整一下。 |
我发现目前的实现,似乎还是会出现这个PR #155 (comment) 里提到的问题。 |
怎么能重现能说一下吗,我去试试。 |
似乎是用的ftp命令行操作,不过具体我也不是很清楚。 |
等下,我忽然想到一个问题,不知道你之前测试是基于哪个代理的;我之前做的所有测试用的代理,其实都是 所以在我们本地实现的closeWrite/Read,如果对面代理还是直接关闭的话,似乎意义就不大了? |
是的。如果对面代理不支持closeWrite就没用了。我之前测试是基于direct://,以及我自己写的代理:easyss。 但是首先要tun2socks这边支持,整个链条才可能通,如果对方代理不支持也是没有什么影响的,就回退到了原来的样子。 |
我发现目前的实现有个问题: 假设,服务端是读取来自客户端请求,然后关闭连接, func server() {
listener, err := net.Listen("tcp", ":9878")
if err != nil {
log.Fatal(err)
}
// defer listener.Close()
conn, err := listener.Accept()
if err != nil {
log.Fatal("server", err)
os.Exit(1)
}
fmt.Println("connected from:", conn.RemoteAddr())
data := make([]byte, 1)
for {
if _, err := conn.Read(data); err != nil {
log.Println("server:", err)
break
}
}
conn.(*net.TCPConn).Close()
} 客户端出于某种原因,连接上之后没有任何读写操作就直接关闭连接, func client() {
conn, err := net.Dial("tcp", "localhost:9878")
if err != nil {
log.Fatal("client", err)
}
// time.Sleep(3*time.Second)
conn.Close()
} 正常直连情况下,服务端读到EOF,就会自动断开:
但是经过tun2socks之后,按照目前的PR实现: |
没明白呢,调用 |
Sorry我看走眼了。 不过我参考了一下其他的一些solution,如:https://www.excentis.com/blog/tcp-half-close-a-cool-feature-that-is-now-broken/
我觉得CloseWrite/Read可以增加进去,然后CloseWrite之后也必须要调用一次SetReadDeadline,时长使用Linux默认的60s。你觉得呢? |
我按照上面的思路重新实现了一下,试了一下效果还不错,你可以试试 |
我看了你给的参考链接,确实考虑到可能的拒绝攻击,设置一个相对较小的超时时间是合理的。 最新的代码,整体我也比较赞同,有一个点我觉得需要讨论,就是 |
# Conflicts: # tunnel/tcp.go
这块考虑是测试的原因?不是很清楚。
考虑到对称一下,也没什么影响。 |
嗯,问题不大,主要是个审美偏好,我是如果这个代码不是必须的,就会删除。 看你,我都能接受。 |
嗯,另一方面 func (h *tcpHandler) relay(lhs, rhs net.Conn) {
upCh := make(chan struct{})
cls := func(dir direction, interrupt bool) {
lhsDConn, lhsOk := lhs.(duplexConn)
rhsDConn, rhsOk := rhs.(duplexConn)
if !interrupt && lhsOk && rhsOk {
switch dir {
case dirUplink:
lhsDConn.CloseRead()
rhsDConn.CloseWrite()
case dirDownlink:
lhsDConn.CloseWrite()
rhsDConn.CloseRead()
default:
panic("unexpected direction")
}
} else {
lhs.Close()
rhs.Close()
}
}
... |
感觉没什么问题,直接Merge了 :-P |
而且这里没什么需要担心的,因为 |
Ok,明白。 |
Fixes: #218