// Package tor implements Tor Hidden Service transport package tor import ( "fmt" "net" "net/url" "golang.org/x/net/proxy" "khimera/transport" ) // TorTransport implements Tor-based networking type TorTransport struct { socksProxy string // SOCKS5 proxy address (default: 127.0.0.1:9050) dialer proxy.Dialer listener net.Listener closed bool } // NewTorTransport creates a new Tor transport func NewTorTransport(socksProxy string) (*TorTransport, error) { if socksProxy == "" { socksProxy = "127.0.0.1:9050" // Default Tor SOCKS proxy } // Create SOCKS5 dialer proxyURL, err := url.Parse("socks5://" + socksProxy) if err != nil { return nil, fmt.Errorf("invalid SOCKS proxy address: %w", err) } dialer, err := proxy.FromURL(proxyURL, proxy.Direct) if err != nil { return nil, fmt.Errorf("failed to create SOCKS dialer: %w", err) } return &TorTransport{ socksProxy: socksProxy, dialer: dialer, }, nil } // Connect connects to a Tor Hidden Service (.onion address) func (t *TorTransport) Connect(address string) (net.Conn, error) { if t.closed { return nil, fmt.Errorf("tor transport is closed") } // Validate .onion address if !isOnionAddress(address) { return nil, fmt.Errorf("invalid .onion address: %s", address) } // Connect via SOCKS proxy conn, err := t.dialer.Dial("tcp", address) if err != nil { return nil, fmt.Errorf("failed to connect to %s: %w", address, err) } return conn, nil } // Listen starts a Tor Hidden Service listener // Note: This is a simplified implementation. In production, you'd need to: // 1. Configure torrc to create a hidden service // 2. Get the .onion address from Tor control port // 3. Listen on the local port specified in torrc func (t *TorTransport) Listen(address string) (net.Listener, error) { if t.closed { return nil, fmt.Errorf("tor transport is closed") } if t.listener != nil { return nil, fmt.Errorf("already listening") } // Listen on localhost (Tor will forward to this) // In production, parse the address to get the port listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return nil, fmt.Errorf("failed to create listener: %w", err) } t.listener = listener return listener, nil } // Type returns the transport type func (t *TorTransport) Type() transport.TransportType { return transport.TransportTor } // Close closes the Tor transport func (t *TorTransport) Close() error { if t.closed { return nil } t.closed = true if t.listener != nil { if err := t.listener.Close(); err != nil { return fmt.Errorf("failed to close listener: %w", err) } t.listener = nil } return nil } // GetSOCKSProxy returns the SOCKS proxy address func (t *TorTransport) GetSOCKSProxy() string { return t.socksProxy } // isOnionAddress checks if an address is a valid .onion address func isOnionAddress(address string) bool { // Simple validation: check if it contains ".onion" // In production, use regex to validate v3 onion addresses (56 chars base32) return len(address) > 6 && address[len(address)-6:] == ".onion" || len(address) > 13 && address[len(address)-13:len(address)-7] == ".onion:" } // String returns a string representation func (t *TorTransport) String() string { return fmt.Sprintf("TorTransport(proxy=%s, closed=%v)", t.socksProxy, t.closed) }