diff --git a/internal/net/dialer/dialer.go b/internal/net/dialer/dialer.go index 5128667d..ddc341f8 100644 --- a/internal/net/dialer/dialer.go +++ b/internal/net/dialer/dialer.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net" - "runtime" "strings" "syscall" "time" @@ -12,7 +11,6 @@ import ( "github.com/go-gost/core/logger" ctxvalue "github.com/go-gost/x/ctx" xnet "github.com/go-gost/x/internal/net" - "github.com/vishvananda/netns" ) const ( @@ -54,29 +52,11 @@ func (d *Dialer) Dial(ctx context.Context, network, addr string) (conn net.Conn, }) if d.Netns != "" { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - originNs, err := netns.Get() + restore, err := switchNetns(d.Netns) if err != nil { - return nil, fmt.Errorf("netns.Get(): %v", err) - } - defer netns.Set(originNs) - - var ns netns.NsHandle - if strings.HasPrefix(d.Netns, "/") { - ns, err = netns.GetFromPath(d.Netns) - } else { - ns, err = netns.GetFromName(d.Netns) - } - if err != nil { - return nil, fmt.Errorf("netns.Get(%s): %v", d.Netns, err) - } - defer ns.Close() - - if err := netns.Set(ns); err != nil { - return nil, fmt.Errorf("netns.Set(%s): %v", d.Netns, err) + return nil, err } + defer restore() } if d.DialFunc != nil { diff --git a/internal/net/dialer/dialer_android.go b/internal/net/dialer/dialer_android.go new file mode 100644 index 00000000..91b20892 --- /dev/null +++ b/internal/net/dialer/dialer_android.go @@ -0,0 +1,11 @@ +//go:build android + +package dialer + +import "fmt" + +// switchNetns is a no-op stub — network namespace switching is not supported +// on Android (requires privileged kernel syscalls unavailable to app sandboxes). +func switchNetns(name string) (restore func(), err error) { + return nil, fmt.Errorf("netns not supported on android") +} diff --git a/internal/net/dialer/dialer_netns.go b/internal/net/dialer/dialer_netns.go new file mode 100644 index 00000000..df620445 --- /dev/null +++ b/internal/net/dialer/dialer_netns.go @@ -0,0 +1,49 @@ +//go:build linux && !android + +package dialer + +import ( + "fmt" + "runtime" + "strings" + + "github.com/vishvananda/netns" +) + +// switchNetns enters the named network namespace and returns a cleanup +// function that restores the original namespace. The caller must defer +// the returned function. +func switchNetns(name string) (restore func(), err error) { + runtime.LockOSThread() + + originNs, err := netns.Get() + if err != nil { + runtime.UnlockOSThread() + return nil, fmt.Errorf("netns.Get(): %v", err) + } + + var ns netns.NsHandle + if strings.HasPrefix(name, "/") { + ns, err = netns.GetFromPath(name) + } else { + ns, err = netns.GetFromName(name) + } + if err != nil { + netns.Set(originNs) + runtime.UnlockOSThread() + return nil, fmt.Errorf("netns.Get(%s): %v", name, err) + } + + if err := netns.Set(ns); err != nil { + ns.Close() + netns.Set(originNs) + runtime.UnlockOSThread() + return nil, fmt.Errorf("netns.Set(%s): %v", name, err) + } + + return func() { + netns.Set(originNs) + ns.Close() + runtime.UnlockOSThread() + }, nil +}