Replace built-in proxies with tun2socks transparent proxying

Remove the built-in HTTP/SOCKS5 proxy servers and domain allowlist/denylist
system. Instead, use tun2socks with a TUN device inside the network namespace
to transparently route all TCP/UDP traffic through an external SOCKS5 proxy.

This enables truly transparent proxying where any binary (Go, static, etc.)
has its traffic routed through the proxy without needing to respect
HTTP_PROXY/ALL_PROXY environment variables. The external proxy handles its
own filtering.

Key changes:
- NetworkConfig: remove AllowedDomains/DeniedDomains/proxy ports, add ProxyURL
- Delete internal/proxy/, internal/templates/, internal/importer/
- Embed tun2socks binary (downloaded at build time via Makefile)
- Replace LinuxBridge with ProxyBridge (single Unix socket to external proxy)
- Inner script sets up TUN device + tun2socks inside network namespace
- Falls back to env-var proxying when TUN is unavailable
- macOS: best-effort env-var proxying to external SOCKS5 proxy
- CLI: remove --template/import, add --proxy flag
- Feature detection: add ip/tun/tun2socks status to --linux-features
This commit is contained in:
2026-02-09 20:41:12 -06:00
parent da5f61e390
commit 9cb65151ee
38 changed files with 588 additions and 4155 deletions

View File

@@ -6,18 +6,14 @@ import (
"github.com/Use-Tusk/fence/internal/config"
"github.com/Use-Tusk/fence/internal/platform"
"github.com/Use-Tusk/fence/internal/proxy"
)
// Manager handles sandbox initialization and command wrapping.
type Manager struct {
config *config.Config
httpProxy *proxy.HTTPProxy
socksProxy *proxy.SOCKSProxy
linuxBridge *LinuxBridge
proxyBridge *ProxyBridge
reverseBridge *ReverseBridge
httpPort int
socksPort int
tun2socksPath string // path to extracted tun2socks binary on host
exposedPorts []int
debug bool
monitor bool
@@ -38,7 +34,7 @@ func (m *Manager) SetExposedPorts(ports []int) {
m.exposedPorts = ports
}
// Initialize sets up the sandbox infrastructure (proxies, etc.).
// Initialize sets up the sandbox infrastructure.
func (m *Manager) Initialize() error {
if m.initialized {
return nil
@@ -48,32 +44,27 @@ func (m *Manager) Initialize() error {
return fmt.Errorf("sandbox is not supported on platform: %s", platform.Detect())
}
filter := proxy.CreateDomainFilter(m.config, m.debug)
m.httpProxy = proxy.NewHTTPProxy(filter, m.debug, m.monitor)
httpPort, err := m.httpProxy.Start()
if err != nil {
return fmt.Errorf("failed to start HTTP proxy: %w", err)
}
m.httpPort = httpPort
m.socksProxy = proxy.NewSOCKSProxy(filter, m.debug, m.monitor)
socksPort, err := m.socksProxy.Start()
if err != nil {
_ = m.httpProxy.Stop()
return fmt.Errorf("failed to start SOCKS proxy: %w", err)
}
m.socksPort = socksPort
// On Linux, set up the socat bridges
// On Linux, set up proxy bridge and tun2socks if proxy is configured
if platform.Detect() == platform.Linux {
bridge, err := NewLinuxBridge(m.httpPort, m.socksPort, m.debug)
if err != nil {
_ = m.httpProxy.Stop()
_ = m.socksProxy.Stop()
return fmt.Errorf("failed to initialize Linux bridge: %w", err)
if m.config.Network.ProxyURL != "" {
// Extract embedded tun2socks binary
tun2socksPath, err := extractTun2Socks()
if err != nil {
m.logDebug("Failed to extract tun2socks: %v (will fall back to env-var proxying)", err)
} else {
m.tun2socksPath = tun2socksPath
}
// Create proxy bridge (socat: Unix socket -> external SOCKS5 proxy)
bridge, err := NewProxyBridge(m.config.Network.ProxyURL, m.debug)
if err != nil {
if m.tun2socksPath != "" {
os.Remove(m.tun2socksPath)
}
return fmt.Errorf("failed to initialize proxy bridge: %w", err)
}
m.proxyBridge = bridge
}
m.linuxBridge = bridge
// Set up reverse bridge for exposed ports (inbound connections)
// Only needed when network namespace is available - otherwise they share the network
@@ -81,9 +72,12 @@ func (m *Manager) Initialize() error {
if len(m.exposedPorts) > 0 && features.CanUnshareNet {
reverseBridge, err := NewReverseBridge(m.exposedPorts, m.debug)
if err != nil {
m.linuxBridge.Cleanup()
_ = m.httpProxy.Stop()
_ = m.socksProxy.Stop()
if m.proxyBridge != nil {
m.proxyBridge.Cleanup()
}
if m.tun2socksPath != "" {
os.Remove(m.tun2socksPath)
}
return fmt.Errorf("failed to initialize reverse bridge: %w", err)
}
m.reverseBridge = reverseBridge
@@ -93,7 +87,11 @@ func (m *Manager) Initialize() error {
}
m.initialized = true
m.logDebug("Sandbox manager initialized (HTTP proxy: %d, SOCKS proxy: %d)", m.httpPort, m.socksPort)
if m.config.Network.ProxyURL != "" {
m.logDebug("Sandbox manager initialized (proxy: %s)", m.config.Network.ProxyURL)
} else {
m.logDebug("Sandbox manager initialized (no proxy, network blocked)")
}
return nil
}
@@ -114,9 +112,9 @@ func (m *Manager) WrapCommand(command string) (string, error) {
plat := platform.Detect()
switch plat {
case platform.MacOS:
return WrapCommandMacOS(m.config, command, m.httpPort, m.socksPort, m.exposedPorts, m.debug)
return WrapCommandMacOS(m.config, command, m.exposedPorts, m.debug)
case platform.Linux:
return WrapCommandLinux(m.config, command, m.linuxBridge, m.reverseBridge, m.debug)
return WrapCommandLinux(m.config, command, m.proxyBridge, m.reverseBridge, m.tun2socksPath, m.debug)
default:
return "", fmt.Errorf("unsupported platform: %s", plat)
}
@@ -127,14 +125,11 @@ func (m *Manager) Cleanup() {
if m.reverseBridge != nil {
m.reverseBridge.Cleanup()
}
if m.linuxBridge != nil {
m.linuxBridge.Cleanup()
if m.proxyBridge != nil {
m.proxyBridge.Cleanup()
}
if m.httpProxy != nil {
_ = m.httpProxy.Stop()
}
if m.socksProxy != nil {
_ = m.socksProxy.Stop()
if m.tun2socksPath != "" {
os.Remove(m.tun2socksPath)
}
m.logDebug("Sandbox manager cleaned up")
}
@@ -144,13 +139,3 @@ func (m *Manager) logDebug(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "[fence] "+format+"\n", args...)
}
}
// HTTPPort returns the HTTP proxy port.
func (m *Manager) HTTPPort() int {
return m.httpPort
}
// SOCKSPort returns the SOCKS proxy port.
func (m *Manager) SOCKSPort() int {
return m.socksPort
}