Skip to content

Commit 6b15768

Browse files
mapleafgoclaude
andcommitted
feat: 移动端 TUN fd 复用,ReloadConfig 无需重新 SetTunFd
移动端(Android/iOS)开启 VPN 后更换订阅时,sing-box 销毁旧实例 会 close(fd) 导致 TUN fd 失效,新实例 OpenTun 报错。改为 dup() 复制 fd 给 sing-box 使用,保留原始 fd 不被消费,reload 时可重复 分配新副本。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 55340be commit 6b15768

4 files changed

Lines changed: 42 additions & 9 deletions

File tree

core/dup_other.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build !android && !ios
2+
3+
package core
4+
5+
func dupFd(fd int32) (int32, error) { return fd, nil }
6+
7+
func closeFd(_ int32) error { return nil }

core/dup_unix.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//go:build android || ios
2+
3+
package core
4+
5+
import "syscall"
6+
7+
func dupFd(fd int32) (int32, error) {
8+
n, err := syscall.Dup(int(fd))
9+
if err != nil {
10+
return 0, err
11+
}
12+
return int32(n), nil
13+
}
14+
15+
func closeFd(fd int32) error { return syscall.Close(int(fd)) }

core/platform.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ func NewPlatformIO() *PlatformIO {
6464
func (p *PlatformIO) SetTunFd(fd int32) {
6565
p.mu.Lock()
6666
defer p.mu.Unlock()
67+
if p.tunFd != 0 && p.tunFd != fd {
68+
closeFd(p.tunFd)
69+
}
6770
p.tunFd = fd
6871
if fd != 0 {
6972
p.externalTun = true
@@ -75,6 +78,9 @@ func (p *PlatformIO) SetTunFd(fd int32) {
7578
func (p *PlatformIO) ResetTunFd() {
7679
p.mu.Lock()
7780
defer p.mu.Unlock()
81+
if p.tunFd != 0 {
82+
closeFd(p.tunFd)
83+
}
7884
oldFd := p.tunFd
7985
p.tunFd = 0
8086
p.externalTun = false
@@ -164,15 +170,17 @@ func (p *PlatformIO) AutoDetectInterfaceControl(fd int32) error {
164170
func (p *PlatformIO) OpenTun(options libbox.TunOptions) (int32, error) {
165171
p.mu.Lock()
166172
fd := p.tunFd
167-
if fd != 0 {
168-
p.tunFd = 0
169-
}
170173
p.tunOptions = options
171174
p.mu.Unlock()
172175

173176
if fd != 0 {
174-
slog.Debug("[OpenTun] returning fd", "fd", fd)
175-
return fd, nil
177+
duped, err := dupFd(fd)
178+
if err != nil {
179+
slog.Error("[OpenTun] dup failed", "fd", fd, "error", err)
180+
return 0, fmt.Errorf("dup tun fd: %w", err)
181+
}
182+
slog.Debug("[OpenTun] returning duped fd", "original", fd, "duped", duped)
183+
return duped, nil
176184
}
177185
slog.Warn("[OpenTun] no TUN fd available; mobile platforms must call SetTunFd before starting/reloading")
178186
return 0, os.ErrInvalid

core/platform_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,16 @@ func TestOpenTun_ReturnsExternalFd(t *testing.T) {
5050
}
5151
}
5252

53-
func TestOpenTun_ConsumesFd(t *testing.T) {
53+
func TestOpenTun_ReusesFd(t *testing.T) {
5454
p := newPlatform()
5555
p.SetTunFd(42)
56-
_, _ = p.OpenTun(nil)
5756
_, err := p.OpenTun(nil)
58-
if err == nil {
59-
t.Fatal("second OpenTun should fail after fd was consumed")
57+
if err != nil {
58+
t.Fatalf("first OpenTun failed: %v", err)
59+
}
60+
_, err = p.OpenTun(nil)
61+
if err != nil {
62+
t.Fatalf("second OpenTun should succeed, fd is retained: %v", err)
6063
}
6164
}
6265

0 commit comments

Comments
 (0)