summaryrefslogtreecommitdiffstats
path: root/transport/tor/tor.go
blob: a6a1e351572acf386a7ce8da9be5988d68a35035 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Package tor implements Tor Hidden Service transport
package tor

import (
	"fmt"
	"net"
	"net/url"

	"golang.org/x/net/proxy"

	"veilith/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)
}