eycorsican / go-tun2socks

A tun2socks implementation written in Go.
MIT License
1.29k stars 431 forks source link

Go-lwIP may cause memory leak #157

Closed trumpfounder closed 3 years ago

trumpfounder commented 3 years ago

Hello,我基于 go-tun2socks 项目构建了自己的 Trojan for iOS 客户端,平时使用开网页、TG、油管都没有大问题,但连续使用数小时后会容易断开,为此我做了一些测试。

我的测试思路是:先完成一次UDP和TCP连接,观察内存情况记为A,之后无论如何折腾,内存都应该恢复到A的水平。

操作流程如下:

  1. 正常启动,用Safari打开域名下的简单页面(如 https://res.wx.qq.com/a/wx_fed/weixin_portal/res/static/img/2vQ5FGm.png ),此时会触发DNS解析(UDP)和TCP连接。观察内存情况约为9.5M左右(iPhone 6P/iOS 13.5)
  2. 然后打开 google.com 、YouTube或ins,内存会到10M~12M,并且基本不会再下降(即便GC后)。

以上猜测有memory leak。

然后我又进行了以下操作:

  1. 开启pprof server,并设置runtime.MemProfileRate = 1024(再小导出时很容易崩了),会多占2M左右内存
  2. 重复以上步骤操作,操作前后Go内存波动在100K以内,但Xcode显示内存增幅仍然和上面一样有1~3M
  3. 持续打印Goroutine的数量,不管如何操作,之后都会恢复到5~7个,应该没有Goroutine leak
  4. 打开SpeedTest客户端进行测速,选单线程,下载怎么都没有问题,上传必超过15M限制

以上猜测有Go以外的memory leak,有可能是在通过Go调用lwIP时刻有未处理到位的地方(没有释放、或是上传时没有处理到?)。

以上纯属个人猜测,请各位大佬指正。

另外近期我注意到 go-tun2socks 上有说新的项目 https://github.com/eycorsican/leaf ,我按照指示安装了TestFlight,使用SpeedTest上传时遇到了相同的问题。(由于是Rust的,我还没来得及细读源码以及编译使用,也就没能用Xcode进行内存使用跟踪)

trumpfounder commented 3 years ago

另外调试过程中还发现一个现象,不知道是否与此相关。

就是若在tcp_callback_export.go中的tcpRecvFn函数中输出log,会看到core/tcp_conn.go在调用tcpConn.Close()后,会收到大量重复包,持续发送10~20s,内容类似:

[23 3 3 0 34 108 127 214 67 52 169 3 90 67 0 42 50 137 212 2 84 210 168 174 236 94 37 134 175 166 92 75 169 212 118 225 233 94 234]

tcpRecvFn函数中会走入switch的LWIP_ERR_CLSD分支,但即便LWIP_ERR_CLSD分支里调用了C.tcp_shutdown关闭了rx侧还是会持续收到。

为何仅在close之后才会发送?是否有办法通知lwIP侧在入口处彻底不再接收这些包?感觉也是会消耗性能的

eycorsican commented 3 years ago

是否有内存泄漏我觉得你可以在桌面端测试,没必要在 IOS 上做。

leaf 使用的 lwip 跟 go-tun2socks 的在配置上不太一样,前者 lwip 配置了固定大小的 heap,后者没有使用上限,我之前测试 leaf speedtest 上传是很稳定的,go-tun2socks 必崩。

我个人已经基本不用 go-tun2socks,所以不会再去优化它,go-tun2socks 在内存和性能两方面都有缺陷,都是因为用了 Go 的关系没有简单解决办法。根据我自己的经验,我建议你不要浪费时间去把 go-tun2socks 跑在 IOS 上,因为有个 15MB 内存限制,以及 Go 的应用本来就很难控制内存使用。

trumpfounder commented 3 years ago

非常感谢回复。

那我也将方向转向leaf和rust吧(虽然之前也花了不少时间研究有点可惜~_~),如果有问题我再回复。

最后想请教一下,我想研究一下lwIP(在使用层面),但不知道会不会是个坑?有什么建议吗?

eycorsican commented 3 years ago

lwip 最大的注意点是它是单线程的,如果在多线程环境使用,调用大多函数时必须用锁之类的手段做同步。

比如调用了 input 后(从这里开始加锁进入 lwip 的线程),回调函数 tcpRecvFn、tcpAcceptFn 等一般也随之被调用(这里依然处于 input 时的锁的作用范围),这是上行链路的锁。而这个就是下行链路的锁,锁 tcp_write 和 tcp_output,然后 output 回调也随之被调用。

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days