-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathclient.go
More file actions
1427 lines (1362 loc) · 37.3 KB
/
client.go
File metadata and controls
1427 lines (1362 loc) · 37.3 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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package socksgo
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/netip"
"net/url"
"time"
"github.com/asciimoth/bufpool"
"github.com/asciimoth/gonnect"
"github.com/asciimoth/gonnect/helpers"
"github.com/asciimoth/socksgo/internal"
"github.com/asciimoth/socksgo/protocol"
"github.com/xtaci/smux"
)
// Static type assertions
var (
_ gonnect.Network = &Client{}
_ gonnect.Dial = (&Client{}).Dial
_ gonnect.PacketDial = (&Client{}).PacketDial
_ gonnect.Listen = (&Client{}).Listen
_ gonnect.PacketListen = (&Client{}).ListenPacket
_ gonnect.LookupIP = (&Client{}).LookupIP
_ gonnect.LookupIPAddr = (&Client{}).LookupIPAddr
_ gonnect.LookupNetIP = (&Client{}).LookupNetIP
_ gonnect.LookupHost = (&Client{}).LookupHost
_ gonnect.LookupAddr = (&Client{}).LookupAddr
_ gonnect.DialTCP = (&Client{}).DialTCP
_ gonnect.ListenTCP = (&Client{}).ListenTCP
_ gonnect.DialUDP = (&Client{}).DialUDP
_ gonnect.ListenUDP = (&Client{}).ListenUDP
)
// ClientNoProxy returns a Client that bypasses any proxy.
//
// This client passes all connections directly without using a proxy.
// It's equivalent to setting Filter to gonnect.TrueFilter.
//
// # Examples
//
// // Direct connections only
// client := socksgo.ClientNoProxy()
// conn, err := client.Dial(ctx, "tcp", "example.com:80")
// // conn is a direct TCP connection to example.com:80
//
// # See Also
//
// - gonnect.TrueFilter: Filter that always returns true
func ClientNoProxy() *Client {
return &Client{
Filter: gonnect.TrueFilter,
GostMbind: true,
GostUDPTun: true,
TorLookup: true,
}
}
// ClientFromURLObjSafe creates a Client from a URL without insecure options.
//
// This is a safe constructor that parses a URL and creates a Client with
// secure defaults. Insecure options like insecureudp are ignored.
//
// # URL Format
//
// socks[4|4a|5][+tls|+ws|+wss]://[user:pass@]host[:port][?options]
//
// # Supported Options
//
// - pass: Enable PassAllFilter (all connections through proxy, no bypass)
// - gost: Enable Gost extensions (MBIND, UDPTun)
// - tor: Enable Tor lookup extensions
// - secure: Enable TLS certificate verification (default: skip verification)
//
// # Defaults
//
// - TLS: InsecureSkipVerify = true (disable with "secure" option)
// - UDP: Plaintext UDP over TLS disabled (use "insecureudp" in unsafe version)
// - Filter: LoopbackFilter (bypass proxy for localhost)
//
// # The "pass" Option
//
// The "pass" option enables PassAllFilter, which forces ALL connections
// through the proxy with no bypass. Without this option, LoopbackFilter
// is used by default ("localhost" and loopback addresses bypass the proxy).
//
// # Examples
//
// // Basic SOCKS5 proxy
// u, _ := url.Parse("socks5://proxy.example.com:1080")
// client := socksgo.ClientFromURLObjSafe(u)
//
// // With authentication (credentials in URL)
// u, _ := url.Parse("socks5://user:pass@proxy.example.com:1080")
// client := socksgo.ClientFromURLObjSafe(u)
//
// // Force all traffic through proxy (no bypass)
// u, _ := url.Parse("socks5://proxy.example.com:1080?pass")
// client := socksgo.ClientFromURLObjSafe(u)
//
// // SOCKS5 over TLS with certificate verification
// u, _ := url.Parse("socks5+tls://proxy.example.com:1080?secure")
// client := socksgo.ClientFromURLObjSafe(u)
//
// // SOCKS5 over WebSocket
// u, _ := url.Parse("socks5+ws://proxy.example.com:8080/ws")
// client := socksgo.ClientFromURLObjSafe(u)
func ClientFromURLObjSafe(u *url.URL) *Client {
client := &Client{}
if u == nil {
return client
}
version, isTLS, isWS := internal.ParseScheme(u.Scheme)
client.SocksVersion = version
client.TLS = isTLS
client.ProxyAddr = u.Host
wsUrl := ""
if isWS {
wsu := url.URL{
Scheme: "ws",
Host: u.Host,
Path: "/ws", // Default for gost compat
}
if u.Path != "" {
wsu.Path = u.Path
}
if isTLS {
wsu.Scheme = "wss"
}
wsUrl = wsu.String()
}
client.WebSocketURL = wsUrl
q := u.Query()
if f, s := helpers.CheckURLBoolKey(q, "gost"); s {
client.GostMbind = f
client.GostUDPTun = f
}
if f, s := helpers.CheckURLBoolKey(q, "tor"); s {
client.TorLookup = f
}
if u.User != nil {
var password string
if pass, ok := u.User.Password(); ok {
password = pass
}
client.Auth = client.Auth.Add(&protocol.PassAuthMethod{
User: u.User.Username(),
Pass: password,
})
}
if f, s := helpers.CheckURLBoolKey(q, "pass"); s && f {
client.Filter = gonnect.FalseFilter
}
client.TLSConfig = &tls.Config{
InsecureSkipVerify: true, //nolint
}
// In safe constructor we can enable it but not disable
if f, s := helpers.CheckURLBoolKey(q, "secure"); s && f {
client.TLSConfig.InsecureSkipVerify = false
}
return client
}
// ClientFromURLSafe creates a Client from a URL string safely.
//
// Wrapper around ClientFromURLObjSafe that parses the URL string first.
// Returns an error if the URL cannot be parsed.
//
// # Examples
//
// client, err := socksgo.ClientFromURLSafe("socks5://proxy.example.com:1080")
//
// # See Also
//
// - ClientFromURLObjSafe: Parse *url.URL safely
// - ClientFromURL: Unsafe version with all options
func ClientFromURLSafe(urlstr string) (*Client, error) {
u, err := url.Parse(urlstr)
if err != nil {
return nil, err
}
return ClientFromURLObjSafe(u), nil
}
// ClientFromENVSafe creates a Client from environment variables safely.
//
// Reads the proxy URL from standard environment variables:
// - ALL_PROXY, all_proxy: Fallback for any scheme
// - {scheme}_proxy, {scheme}_PROXY: Scheme-specific (e.g., http_proxy)
//
// Returns ClientNoProxy() if no environment variable is set.
//
// # Examples
//
// // Reads HTTP_PROXY or ALL_PROXY
// client, err := socksgo.ClientFromENVSafe("http")
//
// # Environment Variable Priority
//
// 1. {scheme}_proxy (lowercase)
// 2. {scheme}_PROXY (uppercase)
// 3. ALL_PROXY (lowercase)
// 4. all_proxy (uppercase)
//
// # See Also
//
// - ClientFromURLObjSafe: Parse URL safely
// - ClientNoProxy: Default when no env var set
func ClientFromENVSafe(scheme string) (*Client, error) {
urlstring := internal.GetProxyFromEnvVar(scheme)
if urlstring == "" {
return ClientNoProxy(), nil
}
return ClientFromURLSafe(urlstring)
}
// ClientFromURLObj creates a Client from a URL with all options.
//
// This is the unsafe constructor that supports all URL options including
// insecure ones. Use ClientFromURLObjSafe for secure defaults.
//
// # Supported Options
//
// - pass: Enable PassAllFilter (all connections through proxy, no bypass)
// - gost: Enable Gost extensions (MBIND, UDPTun)
// - tor: Enable Tor lookup extensions
// - secure: Disable TLS certificate verification (default: skip)
// - insecureudp: Allow plaintext UDP over TLS (security risk!)
// - assocprob: Enable UDP assoc prober (monitors control connection)
//
// # Security Warning
//
// Using "insecureudp" allows plaintext UDP even when the control connection
// is encrypted. This can lead to security vulnerabilities.
//
// # The "pass" Option
//
// The "pass" option enables PassAllFilter, which forces ALL connections
// through the proxy with no bypass. Without this option, LoopbackFilter
// is used by default (localhost and loopback addresses bypass the proxy).
//
// # Examples
//
// // With insecure UDP over TLS (NOT RECOMMENDED)
// u, _ := url.Parse("socks5+tls://proxy.example.com:1080?insecureudp")
// client := socksgo.ClientFromURLObj(u)
//
// // Force all traffic through proxy (no bypass)
// u, _ := url.Parse("socks5://proxy.example.com:1080?pass")
// client := socksgo.ClientFromURLObj(u)
//
// # See Also
//
// - ClientFromURLObjSafe: Safe version without insecure options
// - ClientFromURL: Parse URL string with all options
func ClientFromURLObj(u *url.URL) *Client {
client := ClientFromURLObjSafe(u)
q := u.Query()
if f, s := helpers.CheckURLBoolKey(q, "insecureudp"); s {
client.InsecureUDP = f
}
if f, s := helpers.CheckURLBoolKey(q, "assocprob"); s {
client.DoNotSpawnUDPAsocProbber = !f
}
if f, s := helpers.CheckURLBoolKey(q, "secure"); s {
client.TLSConfig.InsecureSkipVerify = !f
}
return client
}
// ClientFromURL creates a Client from a URL string with all options.
//
// Wrapper around ClientFromURLObj that parses the URL string first.
// Supports all options including insecure ones.
//
// # Examples
//
// client, err := socksgo.ClientFromURL("socks5://user:pass@proxy.example.com:1080?pass")
//
// # See Also
//
// - ClientFromURLObj: Parse *url.URL with all options
// - ClientFromURLSafe: Safe version without insecure options
func ClientFromURL(urlstr string) (*Client, error) {
u, err := url.Parse(urlstr)
if err != nil {
return nil, err
}
return ClientFromURLObj(u), nil
}
// ClientFromENV creates a Client from environment variables with all options.
//
// Reads the proxy URL from environment variables and creates a Client
// with all options enabled. Uses ClientFromURL which supports insecure options
// if specified in the URL.
//
// # Examples
//
// // Reads SOCKS5_PROXY environment variable
// client, err := socksgo.ClientFromENV("socks5")
//
// # See Also
//
// - ClientFromENVSafe: Safe version without insecure options
// - ClientFromURL: Parse URL string with all options
func ClientFromENV(scheme string) (*Client, error) {
urlstring := internal.GetProxyFromEnvVar(scheme)
if urlstring == "" {
return ClientNoProxy(), nil
}
return ClientFromURL(urlstring)
}
// Client is a SOCKS proxy client configuration.
//
// Client provides a high-level interface for connecting through SOCKS4, SOCKS4a,
// and SOCKS5 proxies. It supports TCP connections, UDP associations, BIND commands,
// and extensions for Gost and Tor compatibility.
//
// # Quick Start
//
// // Basic SOCKS5 client
// client := &socksgo.Client{
// SocksVersion: "5",
// ProxyAddr: "proxy.example.com:1080",
// }
// conn, err := client.Dial(ctx, "tcp", "example.com:80")
//
// // With authentication
// client := &socksgo.Client{
// SocksVersion: "5",
// ProxyAddr: "proxy.example.com:1080",
// Auth: (&protocol.AuthMethods{}).Add(&protocol.PassAuthMethod{
// User: "username",
// Pass: "password",
// }),
// }
//
// // SOCKS over TLS
// client := &socksgo.Client{
// SocksVersion: "5",
// ProxyAddr: "proxy.example.com:1080",
// TLS: true,
// }
//
// // SOCKS over WebSocket
// client := &socksgo.Client{
// SocksVersion: "5",
// WebSocketURL: "wss://proxy.example.com/ws",
// }
//
// // Tor stream isolation
// client := &socksgo.Client{
// SocksVersion: "5",
// ProxyAddr: "127.0.0.1:9050",
// }
// isolatedClient := client.WithTorIsolation(nil) // Random isolation
// sessionID := "my-session"
// isolatedClient = client.WithTorIsolation(&sessionID) // Specific isolation
//
// # Thread Safety
//
// Client is safe for concurrent use after initialization. Do not modify
// fields after calling Dial, Listen, or other methods.
//
// # See Also
//
// - ClientFromURL: Create client from URL string
// - ClientFromENV: Create client from environment variables
// - ClientNoProxy: Create client that bypasses proxy
// - Client.WithTorIsolation: Enable Tor stream isolation
type Client struct {
// SocksVersion specifies the SOCKS protocol version.
//
// Valid values:
// - "4": SOCKS4 (no domain name support)
// - "4a": SOCKS4a (supports domain names)
// - "5": SOCKS5 (full feature support)
// - "": Empty string defaults to "5"
//
// Default: "5"
SocksVersion string
// ProxyNet specifies the network type for connecting to the proxy.
//
// Valid values: "tcp", "tcp4", "tcp6"
//
// Default: "tcp"
ProxyNet string
// ProxyAddr is the proxy server address in host:port format.
//
// If port is omitted (e.g., "proxy.example.com"), port 1080 is used.
// Ignored when WebSocketURL is set.
//
// Examples:
// - "proxy.example.com:1080"
// - "192.168.1.1:9050"
// - "proxy.example.com" (uses default port 1080)
ProxyAddr string
// Auth contains authentication methods for SOCKS5.
//
// Multiple methods can be added; the server selects one during negotiation.
//
// Examples:
//
// client.Auth = (&protocol.AuthMethods{}).
// Add(&protocol.PassAuthMethod{User: "user", Pass: "pass"}).
// Add(&protocol.NoAuthMethod{})
Auth *protocol.AuthMethods
// InsecureUDP allows plaintext UDP over TLS connections.
//
// WARNING: This is a security risk! When true, UDP packets are sent
// unencrypted even when the control TCP connection uses TLS.
//
// Only enable in trusted networks or when you understand the implications.
//
// Default: false
InsecureUDP bool
// DoNotSpawnUDPAsocProbber disables the UDP ASSOC prober goroutine.
//
// When false (default), a goroutine monitors the control TCP connection
// and closes the UDP association when the control connection closes.
//
// Set to true to disable this behavior (manual cleanup required).
DoNotSpawnUDPAsocProbber bool
// GostMbind enables Gost multiplexed BIND extension.
//
// When enabled, the client can use MBIND command for multiplexed
// incoming connections over a single TCP connection using smux.
//
// Default: false
GostMbind bool
// GostUDPTun enables Gost UDP Tunnel extension.
//
// When enabled, UDP traffic is tunneled over TCP instead of using
// standard UDP ASSOCIATE.
//
// Default: false
GostUDPTun bool
// TorLookup enables Tor DNS resolution extensions.
//
// When enabled, LookupIP and LookupAddr methods use Tor's SOCKS
// extensions for DNS resolution through the proxy.
//
// Default: false
TorLookup bool
// Filter determines which connections bypass the proxy.
//
// When Filter returns true, connections use direct dialing instead
// of going through the proxy. Commonly used for NO_PROXY-style rules.
//
// Default: gonnect.LoopbackFilter (bypasses localhost and loopback addresses)
//
// Examples:
//
// client.Filter = gonnect.FilterFromString("localhost,192.168.0.0/16").Filter
Filter gonnect.Filter
// Dialer is used to establish TCP connections to the proxy.
//
// Also used for direct connections when Filter returns true.
//
// Default: net.Dialer.DialContext
Dialer gonnect.Dial
// PacketDialer is used to establish UDP connections for proxy operations.
//
// Also used for direct UDP when Filter returns true.
//
// Default: net.DialUDP
PacketDialer gonnect.PacketDial
// DirectListener is used for direct TCP listening (BIND) when
// Filter returns true.
//
// Default: net.ListenConfig.Listen
DirectListener gonnect.Listen
// DirectPacketListener is used for direct UDP listening when
// Filter returns true.
//
// Default: net.ListenUDP
DirectPacketListener gonnect.PacketListen
// Resolver is used for DNS lookups.
//
// Used for SOCKS4 (non-4a) clients and Tor LookupIP/LookupAddr
// requests when Filter returns true.
//
// Default: net.DefaultResolver
Resolver gonnect.Resolver
// HandshakeTimeout specifies the timeout for SOCKS handshake.
//
// If zero, uses context deadline or no timeout.
//
// Default: 0 (no explicit timeout)
HandshakeTimeout time.Duration
// Smux configures connection multiplexing for Gost MBIND.
//
// Only used when GostMbind is true.
//
// Example:
//
// client.Smux = &smux.Config{
// MaxFrameSize: 65535,
// MaxReceiveBuffer: 4194304,
// }
Smux *smux.Config
// TLS enables TLS for the proxy connection.
//
// When true, the connection to ProxyAddr is wrapped in TLS.
// TLSConfig controls certificate verification and other TLS settings.
//
// Default: false
TLS bool
// TLSConfig configures TLS behavior.
//
// If nil, a default config is used with:
// - InsecureSkipVerify: true (disable with "secure" URL option)
// - ServerName: Auto-set from ProxyAddr or WebSocketURL
//
// Example for secure connections:
//
// client.TLSConfig = &tls.Config{
// InsecureSkipVerify: false,
// ServerName: "proxy.example.com",
// }
TLSConfig *tls.Config
// WebSocketURL enables SOCKS over WebSocket transport.
//
// When non-empty, connects to this WebSocket URL instead of
// ProxyAddr. ProxyNet and ProxyAddr are ignored.
//
// URL scheme determines encryption:
// - "ws://": Plaintext WebSocket
// - "wss://": Encrypted WebSocket (TLS)
//
// Examples:
// - "ws://proxy.example.com/ws"
// - "wss://proxy.example.com/socks"
WebSocketURL string
// WebSocketConfig configures WebSocket-specific options.
//
// If nil, default WebSocket settings are used.
//
// See WebSocketConfig for available options.
WebSocketConfig *WebSocketConfig
// Pool is a buffer pool for memory-efficient operations.
//
// If nil, a no pool is used.
//
// Using a shared pool across multiple clients can reduce
// memory allocations and GC pressure.
Pool bufpool.Pool
}
// IsNoProxy reports whether the client bypasses the proxy.
//
// Returns true if:
// - Client is nil
// - Both ProxyAddr and WebSocketURL are empty
//
// When true, all connections use direct.
func (c *Client) IsNoProxy() bool {
return c == nil || (c.ProxyAddr == "" && c.WebSocketURL == "")
}
func (c *Client) IsNative() bool {
return c.IsNoProxy()
}
// Request sends a low-level SOCKS request to the proxy.
//
// Request establishes a connection to the proxy, performs authentication
// if needed, and sends the specified command for the given address.
//
// # Parameters
//
// - ctx: Context for cancellation and timeouts
// - cmd: SOCKS command (Connect, Bind, UDPAssoc, or extensions)
// - address: Target address for the command
//
// # Returns
//
// - proxy: Established connection to proxy (or target for CONNECT)
// - addr: Bound address from server (relevant for BIND/UDPAssoc)
// - err: Error if connection, auth, or request fails
//
// # Behavior
//
// On success, the connection deadline is cleared for indefinite use.
// On error, the connection is closed.
//
// # Examples
//
// // CONNECT command
// conn, _, err := client.Request(ctx, protocol.CmdConnect,
// protocol.AddrFromHostPort("example.com:80", "tcp"))
//
// // BIND command
// listener, bindAddr, err := client.Request(ctx, protocol.CmdBind,
// protocol.AddrFromHostPort("0.0.0.0:0", "tcp"))
//
// # See Also
//
// - Dial: High-level TCP connection
// - DialPacket: High-level UDP connection
// - Listen: High-level BIND
func (c *Client) Request(
ctx context.Context,
cmd protocol.Cmd,
address protocol.Addr,
) (
proxy net.Conn,
addr protocol.Addr,
err error,
) {
proxy, addr, err = c.request(ctx, cmd, address)
if err == nil {
// Unset timeout after successful socks handshake
err = proxy.SetDeadline(time.Time{})
}
return
}
// Dial establishes a TCP connection through the SOCKS proxy.
//
// Dial is the primary method for creating TCP connections through the proxy.
// It handles network validation, filter checking, and SOCKS protocol handshake.
//
// # Parameters
//
// - ctx: Context for cancellation and timeouts
// - network: Network type ("tcp", "tcp4", "tcp6")
// - address: Target address in host:port format
//
// # Returns
//
// Established net.Conn or error.
//
// # Behavior
//
// 1. Validates network support for the SOCKS version
// 2. Checks Filter - if true, dials directly
// 3. Sends CONNECT command through proxy
// 4. Returns established connection
//
// # UDP Networks
//
// If network is "udp", "udp4", or "udp6", DialPacket is called instead.
//
// # Examples
//
// // Basic connection
// conn, err := client.Dial(ctx, "tcp", "example.com:80")
//
// // With timeout context
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
// defer cancel()
// conn, err := client.Dial(ctx, "tcp", "example.com:443")
//
// // Direct connection via filter
// client.Filter = socksgo.BuildFilter("localhost")
// conn, err := client.Dial(ctx, "tcp", "localhost:8080") // Direct, not via proxy
//
// # Errors
//
// Returns WrongNetworkError for unsupported networks.
// Returns RejectdError if server rejects the connection.
//
// # See Also
//
// - DialPacket: UDP connections
// - Listen: BIND command for incoming connections
// - Request: Low-level SOCKS request
func (c *Client) Dial(
ctx context.Context,
network, address string,
) (net.Conn, error) {
if network == "udp" || network == "udp4" || network == "udp6" {
return c.PacketDial(ctx, network, address)
}
err := c.CheckNetworkSupport(network)
if err != nil {
return nil, err
}
if c.DoFilter(network, address) {
return c.GetDialer()(ctx, network, address)
}
conn, _, err := c.Request(
ctx,
protocol.CmdConnect,
protocol.AddrFromHostPort(address, network),
)
if err != nil {
return nil, err
}
return conn, nil
}
// PacketDial establishes a UDP connection through the SOCKS proxy.
//
// PacketDial creates a PacketConn for UDP communication through the proxy.
// It supports both standard UDP ASSOCIATE and Gost UDP Tunnel extensions.
//
// # Parameters
//
// - ctx: Context for cancellation and timeouts
// - network: Network type ("udp", "udp4", "udp6")
// - address: Target address in host:port format
//
// # Returns
//
// PacketConn for UDP communication or error.
//
// # Behavior
//
// 1. Validates network support
// 2. Checks Filter - if true, dials UDP directly
// 3. If GostUDPTun enabled: Uses UDP tunnel over TCP
// 4. Otherwise: Uses standard UDP ASSOCIATE
//
// # Gost UDP Tunnel
//
// When GostUDPTun is true, UDP is encapsulated in TCP.
//
// # Examples
//
// // DNS query through proxy
// conn, err := client.PacketDial(ctx, "udp", "8.8.8.8:53")
// if err == nil {
// _, err := conn.Write(dnsQuery)
// _, err = conn.Read(response)
// }
//
// // Gost UDP tunnel
// client.GostUDPTun = true
// conn, err := client.PacketDial(ctx, "udp", "target:123")
//
// # See Also
//
// - ListenPacket: Listen for UDP packets
// - Dial: TCP connections
func (c *Client) PacketDial(
ctx context.Context,
network, address string,
) (gonnect.PacketConn, error) {
err := c.CheckNetworkSupport(network)
if err != nil {
return nil, err
}
if c.DoFilter(network, address) {
return c.GetPacketDialer()(ctx, network, address)
}
raddr := protocol.AddrFromHostPort(address, network)
if c.GostUDPTun {
return c.setupUDPTun5(
ctx,
protocol.AddrFromHostPort("", network),
&raddr,
)
}
return c.dialPacket5(ctx, raddr)
}
// ListenPacket listens for UDP packets through the SOCKS proxy.
//
// ListenPacket creates a UDP listener that receives packets through
// the SOCKS proxy. It supports both standard UDP ASSOCIATE and
// Gost UDP Tunnel extensions.
//
// # Parameters
//
// - ctx: Context for cancellation and timeouts
// - network: Network type ("udp", "udp4", "udp6")
// - address: Local address to listen on (use "0.0.0.0:0" for any)
//
// # Returns
//
// PacketConn for receiving UDP packets or error.
//
// # Behavior
//
// 1. Validates network support
// 2. Checks Filter - if true, listens directly
// 3. If GostUDPTun enabled: Uses UDP tunnel over TCP
// 4. Otherwise: Uses standard UDP ASSOCIATE
//
// # Gost UDP Tunnel
//
// When GostUDPTun is true, creates a bound UDP tunnel. The server
// acts like NAT and doesn't return the bound address - use STUN or
// similar to discover the external address.
//
// # Standard UDP ASSOCIATE
//
// For standard UDP ASSOCIATE, the laddr parameter is ignored. The
// server assigns a UDP port and returns it in the BIND address.
//
// # Examples
//
// // Listen on any available port
// conn, err := client.ListenPacket(ctx, "udp", "0.0.0.0:0")
//
// // Gost UDP tunnel (bound)
// client.GostUDPTun = true
// conn, err := client.ListenPacket(ctx, "udp", "0.0.0.0:0")
// // Use STUN to discover external address
//
// # See Also
//
// - DialPacket: Send UDP packets
// - Listen: TCP BIND command
func (c *Client) ListenPacket(
ctx context.Context,
network, address string,
) (gonnect.PacketConn, error) {
err := c.CheckNetworkSupport(network)
if err != nil {
return nil, err
}
if c.DoFilter(network, address) {
return c.GetPacketListener()(ctx, network, address)
}
laddr := protocol.AddrFromHostPort(address, network)
if c.GostUDPTun {
return c.setupUDPTun5(ctx, laddr, nil)
}
// Standard UDP ASSOC doesn't support listen addr specification
return c.dialPacket5(ctx, protocol.AddrFromHostPort("", network))
}
// Listen establishes a TCP listener through the SOCKS proxy (BIND command).
//
// Listen uses the SOCKS BIND command to create a listener on the proxy
// server that forwards incoming connections to the client.
//
// # Parameters
//
// - ctx: Context for cancellation and timeouts
// - network: Network type ("tcp", "tcp4", "tcp6")
// - address: Local address to bind (use "0.0.0.0:0" for any)
//
// # Returns
//
// net.Listener for accepting connections or error.
//
// # Behavior
//
// 1. Validates network support
// 2. Checks Filter - if true, listens directly
// 3. Sends BIND command to proxy
// 4. Returns listener for incoming connections
//
// # Examples
//
// // Listen for incoming connections
// listener, err := client.Listen(ctx, "tcp", "0.0.0.0:0")
// if err == nil {
// for {
// conn, err := listener.Accept()
// if err != nil {
// break
// }
// go handleConnection(conn)
// }
// }
//
// # See Also
//
// - Dial: Outgoing TCP connections
// - DialPacket: UDP connections
func (c *Client) Listen(
ctx context.Context,
network, address string,
) (net.Listener, error) {
err := c.CheckNetworkSupport(network)
if err != nil {
return nil, err
}
if c.DoFilter(network, address) {
return c.GetListener()(ctx, network, address)
}
cmd := protocol.CmdBind
ver := c.Version()
if c.GostMbind && ver == "5" {
cmd = protocol.CmdGostMuxBind
}
conn, addr, err := c.Request(
ctx,
cmd,
protocol.AddrFromHostPort(address, network),
)
if err != nil {
return nil, err
}
addr.NetTyp = network
if ver == "4" || ver == "4a" {
return &clientListener4{
conn: conn,
addr: addr,
}, nil
}
if ver == "5" {
if c.GostMbind {
return c.listenSmuxWithHook(conn, addr)
}
return &clientListener5{
conn: conn,
addr: addr,
}, nil
}
testListenCloseHook(conn)
return nil, UnknownSocksVersionError{ver}
}
// LookupIP performs a forward DNS lookup through the SOCKS proxy.
//
// LookupIP uses Tor's SOCKS extension to resolve hostnames to IP addresses
// through the proxy. This provides DNS privacy by preventing DNS leaks.
//
// # Parameters
//
// - ctx: Context for cancellation and timeouts
// - network: IP version ("ip", "ip4", "ip6")
// - address: Hostname to resolve
//
// # Returns
//
// Slice of resolved IP addresses or error.
//
// # Requirements
//
// TorLookup must be enabled. Returns ErrResolveDisabled if not set.
//
// # IP types Note
//
// Due to Tor SOCKS extension limitations, the returned IP version may
// differ from the requested network
// (e.g., ipv6 when ipv4 was requested or vise versa).
//
// # Examples
//
// // Enable Tor lookup
// client.TorLookup = true
//
// // Resolve hostname
// ips, err := client.LookupIP(ctx, "ip4", "example.com")
// if err == nil && len(ips) > 0 {
// fmt.Printf("Resolved to: %v\n", ips[0])
// }
//
// # Errors
//
// Returns *net.DNSError with ErrResolveDisabled if TorLookup is false.
//
// # See Also
//
// - LookupAddr: Reverse DNS lookup
// - net.Resolver.LookupIP: Standard DNS lookup
func (c *Client) LookupIP(
ctx context.Context,
network, address string,
) ([]net.IP, error) {
if network != "ip" && network != "ip4" && network != "ip6" {
return nil, &net.DNSError{
UnwrapErr: net.UnknownNetworkError(network),
Err: fmt.Sprintf("network type is unsupported: %s", network),
Name: address,
IsNotFound: true,