-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
231 lines (204 loc) · 6.4 KB
/
main.go
File metadata and controls
231 lines (204 loc) · 6.4 KB
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"time"
)
var (
exitNode = flag.String("exit-node", "", "Tailscale exit node to use (hostname or IP)")
configFile = flag.String("config", "", "Path to configuration file")
hostname = flag.String("hostname", "tailproxy", "Hostname for this tsnet node")
authKey = flag.String("authkey", "", "Tailscale auth key (optional, for unattended setup)")
proxyPort = flag.Int("port", 1080, "SOCKS5 proxy port")
verbose = flag.Bool("verbose", false, "Verbose logging")
exportListeners = flag.Bool("export-listeners", false, "Export bound ports via tsnet")
exportAllowPorts = flag.String("export-allow-ports", "", "Comma-separated ports or ranges to allow (e.g. '3000,8080,10000-10100')")
exportDenyPorts = flag.String("export-deny-ports", "", "Comma-separated ports or ranges to deny")
exportMax = flag.Int("export-max", 32, "Maximum number of simultaneous exported ports")
)
func init() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options] [command [args...]]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Modes:\n")
fmt.Fprintf(os.Stderr, " Proxy-only: %s [options]\n", os.Args[0])
fmt.Fprintf(os.Stderr, " Run SOCKS5 proxy server only\n\n")
fmt.Fprintf(os.Stderr, " Command mode: %s [options] <command> [args...]\n", os.Args[0])
fmt.Fprintf(os.Stderr, " Execute command with transparent proxying via LD_PRELOAD\n\n")
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " # Run proxy server only\n")
fmt.Fprintf(os.Stderr, " %s -exit-node=exit-node-1\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, " # Execute command with transparent proxying\n")
fmt.Fprintf(os.Stderr, " %s -exit-node=exit-node-1 curl https://ifconfig.me\n", os.Args[0])
}
}
func main() {
flag.Parse()
// Check if running in proxy-only mode (no command provided)
proxyOnly := flag.NArg() == 0
// Load config if provided
var config *Config
if *configFile != "" {
var err error
config, err = LoadConfig(*configFile)
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
} else {
config = &Config{
ExitNode: *exitNode,
Hostname: *hostname,
AuthKey: *authKey,
ProxyPort: *proxyPort,
Verbose: *verbose,
ExportListeners: *exportListeners,
ExportAllowPorts: *exportAllowPorts,
ExportDenyPorts: *exportDenyPorts,
ExportMax: *exportMax,
}
}
// Override config with command-line flags if provided
if *exitNode != "" {
config.ExitNode = *exitNode
}
if *hostname != "tailproxy" {
config.Hostname = *hostname
}
if *authKey != "" {
config.AuthKey = *authKey
}
if flag.Lookup("export-listeners").Value.String() != flag.Lookup("export-listeners").DefValue {
config.ExportListeners = *exportListeners
}
if *exportAllowPorts != "" {
config.ExportAllowPorts = *exportAllowPorts
}
if *exportDenyPorts != "" {
config.ExportDenyPorts = *exportDenyPorts
}
if *exportMax != 32 {
config.ExportMax = *exportMax
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Handle signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
log.Println("Received interrupt signal, shutting down...")
cancel()
}()
// Start the proxy server
proxy, err := NewProxyServer(config)
if err != nil {
log.Fatalf("Failed to create proxy server: %v", err)
}
proxyChan := make(chan error, 1)
readyChan := make(chan struct{})
go func() {
proxyChan <- proxy.StartWithReady(ctx, readyChan)
}()
// Wait for proxy to be ready or error
select {
case err := <-proxyChan:
log.Fatalf("Proxy failed to start: %v", err)
case <-readyChan:
// Proxy is ready
}
if proxyOnly {
// Proxy-only mode: just wait for interrupt
fmt.Fprintf(os.Stderr, "SOCKS5 proxy running on 127.0.0.1:%d\n", config.ProxyPort)
if config.ExitNode != "" {
fmt.Fprintf(os.Stderr, "Using exit node: %s\n", config.ExitNode)
}
if config.ExportListeners {
fmt.Fprintf(os.Stderr, "Export listeners mode: enabled\n")
}
fmt.Fprintf(os.Stderr, "Press Ctrl+C to stop\n")
// Wait for interrupt
<-sigChan
cancel()
// Wait for proxy to finish
select {
case err := <-proxyChan:
if err != nil && err != context.Canceled {
log.Printf("Proxy server error: %v", err)
}
case <-time.After(2 * time.Second):
if config.Verbose {
log.Println("Timeout waiting for proxy to stop")
}
}
return
}
// Command execution mode
// Find the preload library
exePath, err := os.Executable()
if err != nil {
log.Fatalf("Failed to get executable path: %v", err)
}
exeDir := filepath.Dir(exePath)
preloadLib := filepath.Join(exeDir, "libtailproxy.so")
// Check if library exists
if _, err := os.Stat(preloadLib); os.IsNotExist(err) {
log.Fatalf("Preload library not found: %s\nPlease run 'make' to build it", preloadLib)
}
// Execute the command with LD_PRELOAD
cmd := exec.CommandContext(ctx, flag.Arg(0), flag.Args()[1:]...)
// Set up environment with LD_PRELOAD and proxy configuration
env := os.Environ()
env = append(env,
fmt.Sprintf("LD_PRELOAD=%s", preloadLib),
fmt.Sprintf("TAILPROXY_HOST=127.0.0.1"),
fmt.Sprintf("TAILPROXY_PORT=%d", config.ProxyPort),
)
if config.Verbose {
env = append(env, "TAILPROXY_VERBOSE=1")
}
// Add export listener configuration if enabled
if config.ExportListeners {
env = append(env,
"TAILPROXY_EXPORT_LISTENERS=1",
fmt.Sprintf("TAILPROXY_CONTROL_SOCK=%s", proxy.GetControlSocketPath()),
)
}
cmd.Env = env
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if config.Verbose {
log.Printf("Executing command: %v", flag.Args())
log.Printf("LD_PRELOAD: %s", preloadLib)
log.Printf("Proxy configured on 127.0.0.1:%d", config.ProxyPort)
}
cmdErr := cmd.Run()
// Cancel context to stop proxy
cancel()
proxy.Stop()
// Wait for proxy to finish
select {
case err := <-proxyChan:
if err != nil && err != context.Canceled {
log.Printf("Proxy server error: %v", err)
}
case <-time.After(2 * time.Second):
if config.Verbose {
log.Println("Timeout waiting for proxy to stop")
}
}
if cmdErr != nil {
if exitErr, ok := cmdErr.(*exec.ExitError); ok {
os.Exit(exitErr.ExitCode())
}
log.Fatalf("Command failed: %v", cmdErr)
}
}