-
Notifications
You must be signed in to change notification settings - Fork 185
Expand file tree
/
Copy pathserver.go
More file actions
121 lines (111 loc) · 4.18 KB
/
Copy pathserver.go
File metadata and controls
121 lines (111 loc) · 4.18 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
package socks
import (
"context"
"fmt"
"io"
"log"
"net"
"github.com/kianmhz/GooseRelayVPN/internal/session"
"github.com/things-go/go-socks5"
"github.com/things-go/go-socks5/statute"
)
// SessionFactory creates a new tunneled session for the given "host:port"
// target. The returned session is owned by the carrier (which polls it for
// outgoing frames and routes incoming ones).
type SessionFactory func(target string) *session.Session
// Serve starts a SOCKS5 listener on listenAddr that wraps every connection in
// a VirtualConn over a fresh tunneled session. The DNS resolver is overridden
// with a no-op to prevent local DNS leaks (clients must use socks5h://).
//
// Wraps the listener with a TCP_NODELAY + TCP_QUICKACK applying acceptor so
// the kernel doesn't introduce 40 ms Nagle delays on small SOCKS payloads
// (HTTP request lines, TLS handshake records) and doesn't hold back ACKs for
// up to 40 ms on small request/reply pairs. The exit side already disables
// Nagle for upstream connections; mirroring on the local side closes the loop.
//
// When user and pass are both non-empty, RFC 1929 username/password
// authentication is required; unauthenticated clients are rejected.
//
// Blocks until ListenAndServe returns. Caller passes ctx for shutdown
// signaling (the underlying go-socks5 library doesn't take a ctx, so this
// just wires it through for parity with the rest of the codebase).
func Serve(_ context.Context, listenAddr, user, pass string, debugTiming bool, factory SessionFactory) error {
opts := []socks5.Option{
socks5.WithDial(func(_ context.Context, _, addr string) (net.Conn, error) {
s := factory(addr)
if debugTiming {
log.Printf("[socks] new session %x for %s", s.ID[:4], addr)
}
return NewVirtualConn(s), nil
}),
socks5.WithAssociateHandle(func(_ context.Context, w io.Writer, _ *socks5.Request) error {
_ = socks5.SendReply(w, statute.RepCommandNotSupported, nil)
return fmt.Errorf("UDP associate not supported")
}),
socks5.WithResolver(noopResolver{}),
}
if user != "" {
opts = append(opts, socks5.WithAuthMethods([]socks5.Authenticator{
socks5.UserPassAuthenticator{
Credentials: socks5.StaticCredentials{user: pass},
},
}))
}
ln, err := net.Listen(listenNetwork(listenAddr), listenAddr)
if err != nil {
return err
}
server := socks5.NewServer(opts...)
return server.Serve(&noDelayListener{Listener: ln})
}
// listenNetwork picks the right network family for net.Listen based on the
// literal address. Defaulting to "tcp" causes Go to bind an AF_INET6 socket
// with V4MAPPED even for explicit IPv4 addresses like "0.0.0.0"; on Linux
// hosts where net.ipv6.bindv6only=1, that socket then refuses IPv4
// connections (issues #94 and #111). Forcing "tcp4" / "tcp6" when the host
// is an IP literal sidesteps that, while leaving hostnames on "tcp" so
// resolver-driven setups (e.g. "localhost") still work.
func listenNetwork(addr string) string {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return "tcp"
}
if host == "" {
return "tcp" // bare ":1080" — let Go pick
}
ip := net.ParseIP(host)
if ip == nil {
return "tcp"
}
if ip.To4() != nil {
return "tcp4"
}
return "tcp6"
}
// noDelayListener wraps net.Listener so each accepted *net.TCPConn has both
// SetNoDelay(true) and (on Linux) TCP_QUICKACK applied. This eliminates the
// kernel's 40 ms Nagle delay on small SOCKS write payloads and the 40 ms
// delayed-ACK on small read replies — together they cover both directions
// of every interactive request/reply pair (DNS-over-HTTPS, REST GETs, TLS
// handshake records).
type noDelayListener struct {
net.Listener
}
func (l *noDelayListener) Accept() (net.Conn, error) {
c, err := l.Listener.Accept()
if err != nil {
return nil, err
}
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetNoDelay(true)
}
setQuickAck(c)
return c, nil
}
// noopResolver is a SOCKS5 name resolver that returns the host string verbatim
// (no DNS lookup). Combined with socks5h:// clients, this keeps DNS off the
// local machine entirely — it's resolved on the VPS exit instead.
type noopResolver struct{}
func (noopResolver) Resolve(ctx context.Context, _ string) (context.Context, net.IP, error) {
return ctx, nil, nil
}