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 th
(_ context.Context, listenAddr, user, pass string, debugTiming bool, factory SessionFactory)
| 34 | // signaling (the underlying go-socks5 library doesn't take a ctx, so this |
| 35 | // just wires it through for parity with the rest of the codebase). |
| 36 | func Serve(_ context.Context, listenAddr, user, pass string, debugTiming bool, factory SessionFactory) error { |
| 37 | opts := []socks5.Option{ |
| 38 | socks5.WithDial(func(_ context.Context, _, addr string) (net.Conn, error) { |
| 39 | s := factory(addr) |
| 40 | if debugTiming { |
| 41 | log.Printf("[socks] new session %x for %s", s.ID[:4], addr) |
| 42 | } |
| 43 | return NewVirtualConn(s), nil |
| 44 | }), |
| 45 | socks5.WithAssociateHandle(func(_ context.Context, w io.Writer, _ *socks5.Request) error { |
| 46 | _ = socks5.SendReply(w, statute.RepCommandNotSupported, nil) |
| 47 | return fmt.Errorf("UDP associate not supported") |
| 48 | }), |
| 49 | socks5.WithResolver(noopResolver{}), |
| 50 | } |
| 51 | if user != "" { |
| 52 | opts = append(opts, socks5.WithAuthMethods([]socks5.Authenticator{ |
| 53 | socks5.UserPassAuthenticator{ |
| 54 | Credentials: socks5.StaticCredentials{user: pass}, |
| 55 | }, |
| 56 | })) |
| 57 | } |
| 58 | |
| 59 | ln, err := net.Listen(listenNetwork(listenAddr), listenAddr) |
| 60 | if err != nil { |
| 61 | return err |
| 62 | } |
| 63 | server := socks5.NewServer(opts...) |
| 64 | return server.Serve(&noDelayListener{Listener: ln}) |
| 65 | } |
| 66 | |
| 67 | // listenNetwork picks the right network family for net.Listen based on the |
| 68 | // literal address. Defaulting to "tcp" causes Go to bind an AF_INET6 socket |
no test coverage detected