Skip to content

Commit 9f000d8

Browse files
authored
fix(mobile): close duplicated TUN fd after engine.Stop to prevent fd leak (#18)
StartTun and StartTunBridge dup the TUN fd so Android and tun2socks each own an independent copy, preventing a SIGSEGV race on disconnect (commit 41c3eef). However, the dup'd fd was never closed by the Go side — engine.Stop() closes it only on the happy path; if it panics (the recover() block swallows it) or engine.Start() never completed, the fd leaks. On Android, repeated connect/disconnect cycles exhaust the fd table (EMFILE). Fix: track the dup'd fd in tunOwnedFd (guarded by engineMu); close it explicitly after engine.Stop() in StopTun and StopTunBridge, and defensively close any pre-existing tracked fd before re-inserting in StartTun and StartTunBridge. On the happy path the explicit close is a harmless double-close (EBADF ignored
1 parent 04128af commit 9f000d8

1 file changed

Lines changed: 25 additions & 1 deletion

File tree

mobile/mobile.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ var (
3131
tunBridgeRunning bool
3232
clientDone chan struct{}
3333
engineMu sync.Mutex // Protects tun2socks engine Start/Stop
34+
35+
// tunOwnedFd is the most recently dup'd TUN fd inserted into the
36+
// tun2socks engine. It is -1 when no fd is owned. Guarded by engineMu
37+
// (so it is always accessed together with engine.Start/Stop).
38+
tunOwnedFd int32 = -1
3439
)
3540

3641
var (
@@ -256,10 +261,17 @@ func StartTun(fd int64, proxyAddr string) {
256261
Device: fmt.Sprintf("fd://%d", safeFd),
257262
MTU: 1500,
258263
}
259-
264+
260265
engineMu.Lock()
266+
// If a previous fd is still tracked, close it now. This guards
267+
// against the (unlikely) case where StartTun is called twice without
268+
// an intervening StopTun.
269+
if tunOwnedFd >= 0 {
270+
_ = syscall.Close(int(tunOwnedFd))
271+
}
261272
engine.Insert(key)
262273
engine.Start()
274+
tunOwnedFd = int32(safeFd)
263275
engineMu.Unlock()
264276

265277
mu.Lock()
@@ -282,6 +294,10 @@ func StopTun() {
282294
defer func() { recover() }()
283295
engine.Stop()
284296
}()
297+
if tunOwnedFd >= 0 {
298+
_ = syscall.Close(int(tunOwnedFd))
299+
tunOwnedFd = -1
300+
}
285301
}
286302

287303
// TUN Bridge wrapper functions (calls mobile/tun subpackage)
@@ -302,8 +318,12 @@ func StartTunBridge(tunFd int64, mtu int64, socksAddr string) error {
302318
}
303319

304320
engineMu.Lock()
321+
if tunOwnedFd >= 0 {
322+
_ = syscall.Close(int(tunOwnedFd))
323+
}
305324
engine.Insert(key)
306325
engine.Start()
326+
tunOwnedFd = int32(safeFd)
307327
engineMu.Unlock()
308328

309329
mu.Lock()
@@ -328,6 +348,10 @@ func StopTunBridge() {
328348
defer func() { recover() }()
329349
engine.Stop()
330350
}()
351+
if tunOwnedFd >= 0 {
352+
_ = syscall.Close(int(tunOwnedFd))
353+
tunOwnedFd = -1
354+
}
331355
engineMu.Unlock()
332356

333357
tun.StopFakeDNSProxy()

0 commit comments

Comments
 (0)