fix: add SOCKS5 auth, DNS bridge, and TUN capability support

Three issues prevented transparent proxying from working end-to-end:

1. bwrap dropped CAP_NET_ADMIN before exec, so ip tuntap/link commands
   failed inside the sandbox. Add --cap-add CAP_NET_ADMIN and
   CAP_NET_BIND_SERVICE when transparent proxy is active.

2. tun2socks only offered SOCKS5 no-auth (method 0x00), but many proxies
   (e.g. gost) require username/password auth (method 0x02). Pass through
   credentials from the proxy URL so tun2socks offers both auth methods.

3. DNS resolution failed because UDP DNS needs SOCKS5 UDP ASSOCIATE which
   most proxies don't support. Add --dns flag and DnsBridge that routes
   DNS queries from the sandbox through a Unix socket to a host-side DNS
   server. Falls back to TCP relay through the tunnel when no --dns is set.

Also brings up loopback interface (ip link set lo up) inside the network
namespace so socat can bind to 127.0.0.1.
This commit is contained in:
2026-02-10 14:57:56 -06:00
parent 9cb65151ee
commit 481616455a
5 changed files with 219 additions and 19 deletions

View File

@@ -28,6 +28,7 @@ var (
monitor bool monitor bool
settingsPath string settingsPath string
proxyURL string proxyURL string
dnsAddr string
cmdString string cmdString string
exposePorts []string exposePorts []string
exitCode int exitCode int
@@ -92,6 +93,7 @@ Configuration file format:
rootCmd.Flags().BoolVarP(&monitor, "monitor", "m", false, "Monitor and log sandbox violations") rootCmd.Flags().BoolVarP(&monitor, "monitor", "m", false, "Monitor and log sandbox violations")
rootCmd.Flags().StringVarP(&settingsPath, "settings", "s", "", "Path to settings file (default: OS config directory)") rootCmd.Flags().StringVarP(&settingsPath, "settings", "s", "", "Path to settings file (default: OS config directory)")
rootCmd.Flags().StringVar(&proxyURL, "proxy", "", "External SOCKS5 proxy URL (e.g., socks5://localhost:1080)") rootCmd.Flags().StringVar(&proxyURL, "proxy", "", "External SOCKS5 proxy URL (e.g., socks5://localhost:1080)")
rootCmd.Flags().StringVar(&dnsAddr, "dns", "", "DNS server address on host (e.g., localhost:3153)")
rootCmd.Flags().StringVarP(&cmdString, "c", "c", "", "Run command string directly (like sh -c)") rootCmd.Flags().StringVarP(&cmdString, "c", "c", "", "Run command string directly (like sh -c)")
rootCmd.Flags().StringArrayVarP(&exposePorts, "port", "p", nil, "Expose port for inbound connections (can be used multiple times)") rootCmd.Flags().StringArrayVarP(&exposePorts, "port", "p", nil, "Expose port for inbound connections (can be used multiple times)")
rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "Show version information") rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "Show version information")
@@ -173,10 +175,13 @@ func runCommand(cmd *cobra.Command, args []string) error {
} }
} }
// CLI --proxy flag overrides config // CLI flags override config
if proxyURL != "" { if proxyURL != "" {
cfg.Network.ProxyURL = proxyURL cfg.Network.ProxyURL = proxyURL
} }
if dnsAddr != "" {
cfg.Network.DnsAddr = dnsAddr
}
manager := sandbox.NewManager(cfg, debug, monitor) manager := sandbox.NewManager(cfg, debug, monitor)
manager.SetExposedPorts(ports) manager.SetExposedPorts(ports)

View File

@@ -27,6 +27,7 @@ type Config struct {
// NetworkConfig defines network restrictions. // NetworkConfig defines network restrictions.
type NetworkConfig struct { type NetworkConfig struct {
ProxyURL string `json:"proxyUrl,omitempty"` // External SOCKS5 proxy (e.g. socks5://host:1080) ProxyURL string `json:"proxyUrl,omitempty"` // External SOCKS5 proxy (e.g. socks5://host:1080)
DnsAddr string `json:"dnsAddr,omitempty"` // DNS server address on host (e.g. localhost:3153)
AllowUnixSockets []string `json:"allowUnixSockets,omitempty"` AllowUnixSockets []string `json:"allowUnixSockets,omitempty"`
AllowAllUnixSockets bool `json:"allowAllUnixSockets,omitempty"` AllowAllUnixSockets bool `json:"allowAllUnixSockets,omitempty"`
AllowLocalBinding bool `json:"allowLocalBinding,omitempty"` AllowLocalBinding bool `json:"allowLocalBinding,omitempty"`
@@ -196,6 +197,11 @@ func (c *Config) Validate() error {
return fmt.Errorf("invalid network.proxyUrl %q: %w", c.Network.ProxyURL, err) return fmt.Errorf("invalid network.proxyUrl %q: %w", c.Network.ProxyURL, err)
} }
} }
if c.Network.DnsAddr != "" {
if err := validateHostPort(c.Network.DnsAddr); err != nil {
return fmt.Errorf("invalid network.dnsAddr %q: %w", c.Network.DnsAddr, err)
}
}
if slices.Contains(c.Filesystem.AllowRead, "") { if slices.Contains(c.Filesystem.AllowRead, "") {
return errors.New("filesystem.allowRead contains empty path") return errors.New("filesystem.allowRead contains empty path")
@@ -261,6 +267,16 @@ func validateProxyURL(proxyURL string) error {
return nil return nil
} }
// validateHostPort validates a host:port address.
func validateHostPort(addr string) error {
// Must contain a colon separating host and port
host, port, found := strings.Cut(addr, ":")
if !found || host == "" || port == "" {
return errors.New("must be in host:port format (e.g. localhost:3153)")
}
return nil
}
// validateHostPattern validates an SSH host pattern. // validateHostPattern validates an SSH host pattern.
// Host patterns are more permissive than domain patterns: // Host patterns are more permissive than domain patterns:
// - Can contain wildcards anywhere (e.g., prod-*.example.com, *.example.com) // - Can contain wildcards anywhere (e.g., prod-*.example.com, *.example.com)
@@ -385,8 +401,9 @@ func Merge(base, override *Config) *Config {
AllowPty: base.AllowPty || override.AllowPty, AllowPty: base.AllowPty || override.AllowPty,
Network: NetworkConfig{ Network: NetworkConfig{
// ProxyURL: override wins if non-empty // ProxyURL/DnsAddr: override wins if non-empty
ProxyURL: mergeString(base.Network.ProxyURL, override.Network.ProxyURL), ProxyURL: mergeString(base.Network.ProxyURL, override.Network.ProxyURL),
DnsAddr: mergeString(base.Network.DnsAddr, override.Network.DnsAddr),
// Append slices (base first, then override additions) // Append slices (base first, then override additions)
AllowUnixSockets: mergeStrings(base.Network.AllowUnixSockets, override.Network.AllowUnixSockets), AllowUnixSockets: mergeStrings(base.Network.AllowUnixSockets, override.Network.AllowUnixSockets),

View File

@@ -20,13 +20,88 @@ import (
// ProxyBridge bridges sandbox to an external SOCKS5 proxy via Unix socket. // ProxyBridge bridges sandbox to an external SOCKS5 proxy via Unix socket.
type ProxyBridge struct { type ProxyBridge struct {
SocketPath string // Unix socket path SocketPath string // Unix socket path
ProxyHost string // Parsed from ProxyURL ProxyHost string // Parsed from ProxyURL
ProxyPort string // Parsed from ProxyURL ProxyPort string // Parsed from ProxyURL
ProxyUser string // Username from ProxyURL (if any)
ProxyPass string // Password from ProxyURL (if any)
HasAuth bool // Whether credentials were provided
process *exec.Cmd process *exec.Cmd
debug bool debug bool
} }
// DnsBridge bridges DNS queries from the sandbox to a host-side DNS server via Unix socket.
// Inside the sandbox, a socat relay converts UDP DNS queries (port 53) to the Unix socket.
// On the host, socat forwards from the Unix socket to the actual DNS server (TCP).
type DnsBridge struct {
SocketPath string // Unix socket path
DnsAddr string // Host-side DNS address (host:port)
process *exec.Cmd
debug bool
}
// NewDnsBridge creates a Unix socket bridge to a host-side DNS server.
func NewDnsBridge(dnsAddr string, debug bool) (*DnsBridge, error) {
if _, err := exec.LookPath("socat"); err != nil {
return nil, fmt.Errorf("socat is required for DNS bridge: %w", err)
}
id := make([]byte, 8)
if _, err := rand.Read(id); err != nil {
return nil, fmt.Errorf("failed to generate socket ID: %w", err)
}
socketID := hex.EncodeToString(id)
tmpDir := os.TempDir()
socketPath := filepath.Join(tmpDir, fmt.Sprintf("fence-dns-%s.sock", socketID))
bridge := &DnsBridge{
SocketPath: socketPath,
DnsAddr: dnsAddr,
debug: debug,
}
// Start bridge: Unix socket -> DNS server TCP
socatArgs := []string{
fmt.Sprintf("UNIX-LISTEN:%s,fork,reuseaddr", socketPath),
fmt.Sprintf("TCP:%s", dnsAddr),
}
bridge.process = exec.Command("socat", socatArgs...) //nolint:gosec // args constructed from trusted input
if debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Starting DNS bridge: socat %s\n", strings.Join(socatArgs, " "))
}
if err := bridge.process.Start(); err != nil {
return nil, fmt.Errorf("failed to start DNS bridge: %w", err)
}
// Wait for socket to be created
for range 50 {
if fileExists(socketPath) {
if debug {
fmt.Fprintf(os.Stderr, "[fence:linux] DNS bridge ready (%s -> %s)\n", socketPath, dnsAddr)
}
return bridge, nil
}
time.Sleep(100 * time.Millisecond)
}
bridge.Cleanup()
return nil, fmt.Errorf("timeout waiting for DNS bridge socket to be created")
}
// Cleanup stops the DNS bridge and removes the socket file.
func (b *DnsBridge) Cleanup() {
if b.process != nil && b.process.Process != nil {
_ = b.process.Process.Kill()
_ = b.process.Wait()
}
_ = os.Remove(b.SocketPath)
if b.debug {
fmt.Fprintf(os.Stderr, "[fence:linux] DNS bridge cleaned up\n")
}
}
// ReverseBridge holds the socat bridge processes for inbound connections. // ReverseBridge holds the socat bridge processes for inbound connections.
type ReverseBridge struct { type ReverseBridge struct {
Ports []int Ports []int
@@ -77,6 +152,13 @@ func NewProxyBridge(proxyURL string, debug bool) (*ProxyBridge, error) {
debug: debug, debug: debug,
} }
// Capture credentials from the proxy URL (if any)
if u.User != nil {
bridge.HasAuth = true
bridge.ProxyUser = u.User.Username()
bridge.ProxyPass, _ = u.User.Password()
}
// Start bridge: Unix socket -> external SOCKS5 proxy TCP // Start bridge: Unix socket -> external SOCKS5 proxy TCP
socatArgs := []string{ socatArgs := []string{
fmt.Sprintf("UNIX-LISTEN:%s,fork,reuseaddr", socketPath), fmt.Sprintf("UNIX-LISTEN:%s,fork,reuseaddr", socketPath),
@@ -305,8 +387,8 @@ func getMandatoryDenyPaths(cwd string) []string {
// WrapCommandLinux wraps a command with Linux bubblewrap sandbox. // WrapCommandLinux wraps a command with Linux bubblewrap sandbox.
// It uses available security features (Landlock, seccomp) with graceful fallback. // It uses available security features (Landlock, seccomp) with graceful fallback.
func WrapCommandLinux(cfg *config.Config, command string, proxyBridge *ProxyBridge, reverseBridge *ReverseBridge, tun2socksPath string, debug bool) (string, error) { func WrapCommandLinux(cfg *config.Config, command string, proxyBridge *ProxyBridge, dnsBridge *DnsBridge, reverseBridge *ReverseBridge, tun2socksPath string, debug bool) (string, error) {
return WrapCommandLinuxWithOptions(cfg, command, proxyBridge, reverseBridge, tun2socksPath, LinuxSandboxOptions{ return WrapCommandLinuxWithOptions(cfg, command, proxyBridge, dnsBridge, reverseBridge, tun2socksPath, LinuxSandboxOptions{
UseLandlock: true, // Enabled by default, will fall back if not available UseLandlock: true, // Enabled by default, will fall back if not available
UseSeccomp: true, // Enabled by default UseSeccomp: true, // Enabled by default
UseEBPF: true, // Enabled by default if available UseEBPF: true, // Enabled by default if available
@@ -315,7 +397,7 @@ func WrapCommandLinux(cfg *config.Config, command string, proxyBridge *ProxyBrid
} }
// WrapCommandLinuxWithOptions wraps a command with configurable sandbox options. // WrapCommandLinuxWithOptions wraps a command with configurable sandbox options.
func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge *ProxyBridge, reverseBridge *ReverseBridge, tun2socksPath string, opts LinuxSandboxOptions) (string, error) { func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge *ProxyBridge, dnsBridge *DnsBridge, reverseBridge *ReverseBridge, tun2socksPath string, opts LinuxSandboxOptions) (string, error) {
if _, err := exec.LookPath("bwrap"); err != nil { if _, err := exec.LookPath("bwrap"); err != nil {
return "", fmt.Errorf("bubblewrap (bwrap) is required on Linux but not found: %w", err) return "", fmt.Errorf("bubblewrap (bwrap) is required on Linux but not found: %w", err)
} }
@@ -586,18 +668,50 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
} }
// Bind the proxy bridge Unix socket into the sandbox (needs to be writable) // Bind the proxy bridge Unix socket into the sandbox (needs to be writable)
var dnsRelayResolvConf string // temp file path for custom resolv.conf
if proxyBridge != nil { if proxyBridge != nil {
bwrapArgs = append(bwrapArgs, bwrapArgs = append(bwrapArgs,
"--bind", proxyBridge.SocketPath, proxyBridge.SocketPath, "--bind", proxyBridge.SocketPath, proxyBridge.SocketPath,
) )
// Bind /dev/net/tun for TUN device creation inside the sandbox if tun2socksPath != "" && features.CanUseTransparentProxy() {
if features.HasDevNetTun { // Bind /dev/net/tun for TUN device creation inside the sandbox
bwrapArgs = append(bwrapArgs, "--dev-bind", "/dev/net/tun", "/dev/net/tun") if features.HasDevNetTun {
} bwrapArgs = append(bwrapArgs, "--dev-bind", "/dev/net/tun", "/dev/net/tun")
// Bind the tun2socks binary into the sandbox (read-only) }
if tun2socksPath != "" { // Preserve CAP_NET_ADMIN (TUN device + network config) and
// CAP_NET_BIND_SERVICE (DNS relay on port 53) inside the namespace
bwrapArgs = append(bwrapArgs, "--cap-add", "CAP_NET_ADMIN")
bwrapArgs = append(bwrapArgs, "--cap-add", "CAP_NET_BIND_SERVICE")
// Bind the tun2socks binary into the sandbox (read-only)
bwrapArgs = append(bwrapArgs, "--ro-bind", tun2socksPath, "/tmp/fence-tun2socks") bwrapArgs = append(bwrapArgs, "--ro-bind", tun2socksPath, "/tmp/fence-tun2socks")
} }
// Bind DNS bridge socket if available
if dnsBridge != nil {
bwrapArgs = append(bwrapArgs,
"--bind", dnsBridge.SocketPath, dnsBridge.SocketPath,
)
}
// Override /etc/resolv.conf to point DNS at our local relay (port 53).
// Inside the sandbox, a socat relay on UDP :53 converts queries to the
// DNS bridge (Unix socket -> host DNS server) or to TCP through the tunnel.
if dnsBridge != nil || (tun2socksPath != "" && features.CanUseTransparentProxy()) {
tmpResolv, err := os.CreateTemp("", "fence-resolv-*.conf")
if err == nil {
_, _ = tmpResolv.WriteString("nameserver 127.0.0.1\n")
tmpResolv.Close()
dnsRelayResolvConf = tmpResolv.Name()
bwrapArgs = append(bwrapArgs, "--ro-bind", dnsRelayResolvConf, "/etc/resolv.conf")
if opts.Debug {
if dnsBridge != nil {
fmt.Fprintf(os.Stderr, "[fence:linux] DNS: overriding resolv.conf -> 127.0.0.1 (bridge to %s)\n", dnsBridge.DnsAddr)
} else {
fmt.Fprintf(os.Stderr, "[fence:linux] DNS: overriding resolv.conf -> 127.0.0.1 (TCP relay through tunnel)\n")
}
}
}
}
} }
// Bind reverse socket directory if needed (sockets created inside sandbox) // Bind reverse socket directory if needed (sockets created inside sandbox)
@@ -632,8 +746,21 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
innerScript.WriteString("export FENCE_SANDBOX=1\n") innerScript.WriteString("export FENCE_SANDBOX=1\n")
if proxyBridge != nil && tun2socksPath != "" && features.CanUseTransparentProxy() { if proxyBridge != nil && tun2socksPath != "" && features.CanUseTransparentProxy() {
// Build the tun2socks proxy URL with credentials if available
// Many SOCKS5 proxies require the username/password auth flow even
// without real credentials (e.g., gost always selects method 0x02).
// Including userinfo ensures tun2socks offers both auth methods.
tun2socksProxyURL := "socks5://127.0.0.1:${PROXY_PORT}"
if proxyBridge.HasAuth {
userinfo := url.UserPassword(proxyBridge.ProxyUser, proxyBridge.ProxyPass)
tun2socksProxyURL = fmt.Sprintf("socks5://%s@127.0.0.1:${PROXY_PORT}", userinfo.String())
}
// Set up transparent proxy via TUN device + tun2socks // Set up transparent proxy via TUN device + tun2socks
innerScript.WriteString(fmt.Sprintf(` innerScript.WriteString(fmt.Sprintf(`
# Bring up loopback interface (needed for socat to bind on 127.0.0.1)
ip link set lo up
# Set up TUN device for transparent proxying # Set up TUN device for transparent proxying
ip tuntap add dev tun0 mode tun ip tuntap add dev tun0 mode tun
ip addr add 198.18.0.1/15 dev tun0 ip addr add 198.18.0.1/15 dev tun0
@@ -646,13 +773,33 @@ socat TCP-LISTEN:${PROXY_PORT},fork,reuseaddr,bind=127.0.0.1 UNIX-CONNECT:%s >/d
BRIDGE_PID=$! BRIDGE_PID=$!
# Start tun2socks (transparent proxy via gvisor netstack) # Start tun2socks (transparent proxy via gvisor netstack)
/tmp/fence-tun2socks -device tun0 -proxy socks5://127.0.0.1:${PROXY_PORT} >/dev/null 2>&1 & /tmp/fence-tun2socks -device tun0 -proxy %s >/dev/null 2>&1 &
TUN2SOCKS_PID=$! TUN2SOCKS_PID=$!
`, proxyBridge.SocketPath)) `, proxyBridge.SocketPath, tun2socksProxyURL))
// DNS relay: convert UDP DNS queries on port 53 so apps can resolve names.
if dnsBridge != nil {
// Dedicated DNS bridge: UDP :53 -> Unix socket -> host DNS server
innerScript.WriteString(fmt.Sprintf(`# DNS relay: UDP queries -> Unix socket -> host DNS server (%s)
socat UDP4-RECVFROM:53,fork,reuseaddr UNIX-CONNECT:%s >/dev/null 2>&1 &
DNS_RELAY_PID=$!
`, dnsBridge.DnsAddr, dnsBridge.SocketPath))
} else {
// Fallback: UDP :53 -> TCP to public DNS through the tunnel
innerScript.WriteString(`# DNS relay: UDP queries -> TCP 1.1.1.1:53 (through tun2socks tunnel)
socat UDP4-RECVFROM:53,fork,reuseaddr TCP:1.1.1.1:53 >/dev/null 2>&1 &
DNS_RELAY_PID=$!
`)
}
} else if proxyBridge != nil { } else if proxyBridge != nil {
// Fallback: no TUN support, use env-var-based proxying // Fallback: no TUN support, use env-var-based proxying
innerScript.WriteString(fmt.Sprintf(` innerScript.WriteString(fmt.Sprintf(`
# Bring up loopback interface (needed for socat to bind on 127.0.0.1)
ip link set lo up 2>/dev/null
# Set up SOCKS5 bridge (no TUN available, env-var-based proxying) # Set up SOCKS5 bridge (no TUN available, env-var-based proxying)
PROXY_PORT=18321 PROXY_PORT=18321
socat TCP-LISTEN:${PROXY_PORT},fork,reuseaddr,bind=127.0.0.1 UNIX-CONNECT:%s >/dev/null 2>&1 & socat TCP-LISTEN:${PROXY_PORT},fork,reuseaddr,bind=127.0.0.1 UNIX-CONNECT:%s >/dev/null 2>&1 &

View File

@@ -15,6 +15,12 @@ type ProxyBridge struct {
ProxyPort string ProxyPort string
} }
// DnsBridge is a stub for non-Linux platforms.
type DnsBridge struct {
SocketPath string
DnsAddr string
}
// ReverseBridge is a stub for non-Linux platforms. // ReverseBridge is a stub for non-Linux platforms.
type ReverseBridge struct { type ReverseBridge struct {
Ports []int Ports []int
@@ -38,6 +44,14 @@ func NewProxyBridge(proxyURL string, debug bool) (*ProxyBridge, error) {
// Cleanup is a no-op on non-Linux platforms. // Cleanup is a no-op on non-Linux platforms.
func (b *ProxyBridge) Cleanup() {} func (b *ProxyBridge) Cleanup() {}
// NewDnsBridge returns an error on non-Linux platforms.
func NewDnsBridge(dnsAddr string, debug bool) (*DnsBridge, error) {
return nil, fmt.Errorf("DNS bridge not available on this platform")
}
// Cleanup is a no-op on non-Linux platforms.
func (b *DnsBridge) Cleanup() {}
// NewReverseBridge returns an error on non-Linux platforms. // NewReverseBridge returns an error on non-Linux platforms.
func NewReverseBridge(ports []int, debug bool) (*ReverseBridge, error) { func NewReverseBridge(ports []int, debug bool) (*ReverseBridge, error) {
return nil, fmt.Errorf("reverse bridge not available on this platform") return nil, fmt.Errorf("reverse bridge not available on this platform")
@@ -47,12 +61,12 @@ func NewReverseBridge(ports []int, debug bool) (*ReverseBridge, error) {
func (b *ReverseBridge) Cleanup() {} func (b *ReverseBridge) Cleanup() {}
// WrapCommandLinux returns an error on non-Linux platforms. // WrapCommandLinux returns an error on non-Linux platforms.
func WrapCommandLinux(cfg *config.Config, command string, proxyBridge *ProxyBridge, reverseBridge *ReverseBridge, tun2socksPath string, debug bool) (string, error) { func WrapCommandLinux(cfg *config.Config, command string, proxyBridge *ProxyBridge, dnsBridge *DnsBridge, reverseBridge *ReverseBridge, tun2socksPath string, debug bool) (string, error) {
return "", fmt.Errorf("Linux sandbox not available on this platform") return "", fmt.Errorf("Linux sandbox not available on this platform")
} }
// WrapCommandLinuxWithOptions returns an error on non-Linux platforms. // WrapCommandLinuxWithOptions returns an error on non-Linux platforms.
func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge *ProxyBridge, reverseBridge *ReverseBridge, tun2socksPath string, opts LinuxSandboxOptions) (string, error) { func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge *ProxyBridge, dnsBridge *DnsBridge, reverseBridge *ReverseBridge, tun2socksPath string, opts LinuxSandboxOptions) (string, error) {
return "", fmt.Errorf("Linux sandbox not available on this platform") return "", fmt.Errorf("Linux sandbox not available on this platform")
} }

View File

@@ -12,6 +12,7 @@ import (
type Manager struct { type Manager struct {
config *config.Config config *config.Config
proxyBridge *ProxyBridge proxyBridge *ProxyBridge
dnsBridge *DnsBridge
reverseBridge *ReverseBridge reverseBridge *ReverseBridge
tun2socksPath string // path to extracted tun2socks binary on host tun2socksPath string // path to extracted tun2socks binary on host
exposedPorts []int exposedPorts []int
@@ -64,6 +65,19 @@ func (m *Manager) Initialize() error {
return fmt.Errorf("failed to initialize proxy bridge: %w", err) return fmt.Errorf("failed to initialize proxy bridge: %w", err)
} }
m.proxyBridge = bridge m.proxyBridge = bridge
// Create DNS bridge if a DNS server is configured
if m.config.Network.DnsAddr != "" {
dnsBridge, err := NewDnsBridge(m.config.Network.DnsAddr, m.debug)
if err != nil {
m.proxyBridge.Cleanup()
if m.tun2socksPath != "" {
os.Remove(m.tun2socksPath)
}
return fmt.Errorf("failed to initialize DNS bridge: %w", err)
}
m.dnsBridge = dnsBridge
}
} }
// Set up reverse bridge for exposed ports (inbound connections) // Set up reverse bridge for exposed ports (inbound connections)
@@ -114,7 +128,7 @@ func (m *Manager) WrapCommand(command string) (string, error) {
case platform.MacOS: case platform.MacOS:
return WrapCommandMacOS(m.config, command, m.exposedPorts, m.debug) return WrapCommandMacOS(m.config, command, m.exposedPorts, m.debug)
case platform.Linux: case platform.Linux:
return WrapCommandLinux(m.config, command, m.proxyBridge, m.reverseBridge, m.tun2socksPath, m.debug) return WrapCommandLinux(m.config, command, m.proxyBridge, m.dnsBridge, m.reverseBridge, m.tun2socksPath, m.debug)
default: default:
return "", fmt.Errorf("unsupported platform: %s", plat) return "", fmt.Errorf("unsupported platform: %s", plat)
} }
@@ -125,6 +139,9 @@ func (m *Manager) Cleanup() {
if m.reverseBridge != nil { if m.reverseBridge != nil {
m.reverseBridge.Cleanup() m.reverseBridge.Cleanup()
} }
if m.dnsBridge != nil {
m.dnsBridge.Cleanup()
}
if m.proxyBridge != nil { if m.proxyBridge != nil {
m.proxyBridge.Cleanup() m.proxyBridge.Cleanup()
} }