{"id":37348923,"url":"https://github.com/aeondave/go-s5","last_synced_at":"2026-01-16T04:00:51.939Z","repository":{"id":316603908,"uuid":"1064065921","full_name":"AeonDave/go-s5","owner":"AeonDave","description":"A minimal, fast, and extensible SOCKS5 lib written in Go.","archived":false,"fork":false,"pushed_at":"2025-12-02T15:29:20.000Z","size":193,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-05T03:24:15.055Z","etag":null,"topics":["golang","socks5","socks5-proxy","socks5-server","tunnel-server"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AeonDave.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-09-25T13:47:39.000Z","updated_at":"2025-12-02T15:26:40.000Z","dependencies_parsed_at":"2025-09-25T16:23:37.779Z","dependency_job_id":"80c5d73b-d8de-4d97-b69c-b5309a496fbd","html_url":"https://github.com/AeonDave/go-s5","commit_stats":null,"previous_names":["aeondave/go-s5"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/AeonDave/go-s5","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AeonDave%2Fgo-s5","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AeonDave%2Fgo-s5/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AeonDave%2Fgo-s5/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AeonDave%2Fgo-s5/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AeonDave","download_url":"https://codeload.github.com/AeonDave/go-s5/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AeonDave%2Fgo-s5/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28477206,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T03:13:13.607Z","status":"ssl_error","status_checked_at":"2026-01-16T03:11:47.863Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","socks5","socks5-proxy","socks5-server","tunnel-server"],"created_at":"2026-01-16T04:00:31.013Z","updated_at":"2026-01-16T04:00:51.865Z","avatar_url":"https://github.com/AeonDave.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-s5\n\nA minimal, fast, and extensible SOCKS5 lib written in Go.\nIt implements the three primary commands from RFC 1928: CONNECT, BIND, and UDP ASSOCIATE. The library exposes clear extension points for authentication, authorization, DNS resolution, address rewriting, and per‑command middleware. It also includes pragmatic I/O optimizations for high throughput.\n\n[![CodeQL Advanced](https://github.com/AeonDave/go-s5/actions/workflows/codeql.yml/badge.svg)](https://github.com/AeonDave/go-s5/actions/workflows/codeql.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/AeonDave/go-s5)](https://goreportcard.com/report/github.com/AeonDave/go-s5)\n![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/AeonDave/go-s5)\n![GitHub last commit](https://img.shields.io/github/last-commit/AeonDave/go-s5)\n![GitHub License](https://img.shields.io/github/license/AeonDave/go-s5)\n\nContents\n- Overview\n- Features\n- Install\n- Quick Start\n- CLI (s5)\n- Authentication (NoAuth, User/Pass, mTLS)\n- Options (With... API)\n- Client API (CONNECT/BIND/UDP, Multi-hop)\n- Client helper packages (TCP/UDP utilities)\n- Link quality monitoring\n- Examples\n  - Basic server\n  - Username/password\n  - TLS and mTLS\n  - Custom rules\n  - Custom resolver\n  - Address rewriter\n  - Middleware\n  - Upstream chaining\n  - Client: multi-hop DialChain\n  - Advanced BIND\n  - Advanced UDP ASSOCIATE\n- Performance Notes\n- Compatibility\n- Testing\n\nOverview\nThis repository provides a reusable library to build SOCKS5 servers. It performs method negotiation, request parsing, and replies (REP with BND.ADDR/BND.PORT), then proxies data between the client and the target.\n\nFeatures\n- Full SOCKS5: CONNECT, BIND, UDP ASSOCIATE\n- Pluggable authentication: NoAuth and Username/Password; transport‑level mTLS supported via TLS listener\n- Rules/ACLs: customizable authorization (default PermitAll)\n- DNS: custom resolver support\n- Address rewriting: transform destination before dialing\n- Per‑command middleware and optional custom handlers\n- Flexible dialing: WithDial, WithDialAndRequest, WithDialer\n- First-class client with multi-hop chaining over a single stream (Handshake+CONNECT per hop)\n- TCP options: handshake timeout, TCP keep‑alive\n- BIND tuning: bind IP, accept timeout, peer validation mode\n- UDP ASSOCIATE: udp4/udp6 selection, FQDN handling, peer limits with idle GC, optional bind IP\n- Server-side upstream chaining via CLI `-upstream` flag\n- I/O performance: buffer pool, fast-paths, half-close, duplex proxy\n- Logging and goroutine pool (GPool) integration\n- Graceful shutdown hooks: ServeContext, per-connection contexts/metadata, and ConnState callbacks\n\nInstall\n- Go 1.24+\n- As a library (server, client, protocol):\n```\ngo get github.com/AeonDave/go-s5/server github.com/AeonDave/go-s5/client github.com/AeonDave/go-s5/protocol\n```\nImport examples:\n```\nimport socks5 \"github.com/AeonDave/go-s5/server\"\nimport client \"github.com/AeonDave/go-s5/client\"\nimport socks5protocol \"github.com/AeonDave/go-s5/protocol\"\n```\n\nCLI (s5)\n- Build the CLI:\n```\ngo build -o s5 ./cmd/s5\n```\n- Start a server on :1080 (NoAuth by default):\n```\n./s5 server -listen :1080\n```\n- With username/password and handshake/keepalive tuning:\n```\n./s5 server -listen :1080 -user alice -pass secret -handshake-timeout 5s -tcp-keepalive 30s\n```\n- Log accepts/closes (optional):\n```\n./s5 server -listen :1080 -log-connections\n```\n- With TLS and optional mTLS:\n```\n./s5 server -listen :1080 -tls-cert cert.pem -tls-key key.pem -mtls-ca ca.pem\n```\n- Chain through an upstream SOCKS5 hop (with optional auth):\n```\n./s5 server -listen :1080 -upstream 1.2.3.4:1080\n./s5 server -listen :1080 -upstream 1.2.3.4:1080 -upstream-user alice -upstream-pass secret\n```\n- Track outbound hop quality (direct or upstream) and print periodic snapshots:\n```\n./s5 server -listen :1080 -linkquality -linkquality-interval 3s\n./s5 server -listen :1080 -upstream 1.2.3.4:1080 -linkquality -linkquality-interval 3s\n```\n- Test a CONNECT via the client helper (prints response to stdout):\n```\n./s5 dial -socks 127.0.0.1:1080 -dest example.com:80 -send $'GET / HTTP/1.0\\r\\n\\r\\n' -io-timeout 5s\n```\n- Open a stdio tunnel to a destination:\n```\n./s5 dial -socks 127.0.0.1:1080 -dest example.com:80 -stdio\n```\n\nQuick Start\nMinimal server on :1080 (no authentication):\n```\npackage main\n\nimport (\n    \"log\"\n    socks5 \"github.com/AeonDave/go-s5/server\"\n)\n\nfunc main() {\n    s := socks5.New()\n    log.Fatal(s.ListenAndServe(\"tcp\", \":1080\"))\n}\n```\n\nNeed graceful shutdown? Use `ServeContext` instead of `ListenAndServe` and cancel the context when it is time to stop; every accepted connection inherits (and can derive from) that context so dialers, middleware, and custom handlers observe cancellation immediately.\n\nClient API (CONNECT/BIND/UDP, Multi-hop)\n- Create a client, perform Handshake, then CONNECT/BIND/UDP as needed.\n- For multi-hop, use DialChain to build N hops over the same stream (Handshake+CONNECT per hop), then CONNECT to the final target.\n\nSingle hop CONNECT example:\n```\nconn, _ := net.Dial(\"tcp\", \"127.0.0.1:1080\")\ncli := client.New(client.WithHandshakeTimeout(5*time.Second), client.WithIOTimeout(5*time.Second))\n_, _ = cli.Handshake(ctx, conn, nil) // NoAuth\ndst, _ := socks5protocol.ParseAddrSpec(\"example.com:80\")\n_, _ = cli.Connect(ctx, conn, dst)\n```\n\nMulti-hop DialChain (client-side chaining):\n```\nchain := []client.Hop{\n  { Address: \"10.0.0.2:1080\", Creds: \u0026client.Credentials{Username:\"alice\", Password:\"secret\"} },\n  { Address: \"hop3.example:1080\", /* TLSConfig: myTLS */ },\n}\ncli := client.New(client.WithHandshakeTimeout(5*time.Second), client.WithIOTimeout(5*time.Second))\nconn, err := cli.DialChain(ctx, chain, \"example.org:443\", 5*time.Second)\nif err != nil { /* handle */ }\ndefer conn.Close()\n// conn now speaks to example.org:443 through 2 SOCKS hops over a single stream\n```\n\nNotes:\n- Per-hop creds/TLS are optional via Hop.{Creds,TLSConfig}.\n- DialChain respects ctx and client timeouts; set WithHandshakeTimeout/WithIOTimeout.\n- You can also call the method form: `cli.DialChain(ctx, chain, final, 5*time.Second)`.\n\nClient helper packages (TCP/UDP utilities)\n- The root `client` package keeps backwards compatibility helpers while `client/tcp`\n  and `client/udp` provide focused APIs for stream and datagram workloads.\n- Both helpers accept standard `context.Context` deadlines and surface\n  convenience wrappers so callers do not need to hand-roll read/write loops.\n\n### TCP stream helper\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"net\"\n    \"time\"\n\n    client \"github.com/AeonDave/go-s5/client\"\n    socks5protocol \"github.com/AeonDave/go-s5/protocol\"\n)\n\nfunc main() {\n    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n    defer cancel()\n\n    conn, _ := net.Dial(\"tcp\", \"127.0.0.1:1080\")\n    defer conn.Close()\n\n    cli := client.New()\n    _, _ = cli.Handshake(ctx, conn, nil)\n\n    dst, _ := socks5protocol.ParseAddrSpec(\"example.org:443\")\n    stream, _, _ := cli.ConnectStream(ctx, conn, dst)\n    defer stream.Close()\n\n    // Set deadlines before exchanging data to avoid hanging sockets.\n    _ = stream.SetDeadline(time.Now().Add(5 * time.Second))\n\n    _, _ = stream.WriteString(\"GET / HTTP/1.1\\r\\nHost: example.org\\r\\n\\r\\n\")\n\n    buf := make([]byte, 1024)\n    n, _ := stream.Read(buf)\n    fmt.Printf(\"response: %s\\n\", buf[:n])\n}\n```\n\n- `client/tcp.Stream.Relay` proxies two `net.Conn` instances using your context to\n  enforce cancellation and deadline propagation.\n- Security tip: when you promote the SOCKS hop to TLS use a hardened\n  `tls.Config` with `MinVersion: tls.VersionTLS12` (or newer) and populate\n  `ServerName` so certificate verification succeeds.\n\n### UDP association helper\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"net\"\n    \"time\"\n\n    client \"github.com/AeonDave/go-s5/client\"\n)\n\nfunc main() {\n    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n    defer cancel()\n\n    conn, _ := net.Dial(\"tcp\", \"127.0.0.1:1080\")\n    defer conn.Close()\n\n    cli := client.New()\n    _, _ = cli.Handshake(ctx, conn, nil)\n\n    assoc, _, _ := cli.UDPAssociate(ctx, conn)\n    defer assoc.Close()\n\n    pc := assoc.PacketConn()\n    target, _ := client.ParseUDPAddr(\"198.51.100.42:12345\")\n    _, _ = pc.WriteTo([]byte(\"payload\"), target)\n\n    buf := make([]byte, 1500)\n    n, addr, _ := pc.ReadFrom(buf)\n    fmt.Printf(\"reply from %s: %x\\n\", addr.String(), buf[:n])\n}\n```\n\nLink quality monitoring\n-----------------------\nThe `linkquality` package offers a lightweight, non-invasive tracker that reuses\nexisting handshakes, keep-alives, and data transfers to estimate link health\nwithout injecting additional traffic or altering socket options. Key entry\npoints:\n\n- `Tracker`: thread-safe accumulator exposed via `Score()` (0–100) and\n  `ConnectionInfo()` (detailed metrics: RTT/jitter, success rate, throughput,\n  uptime, metadata).\n- `RecordProbe`: call with the duration and error from an existing handshake or\n  TCP dial to register latency and success/failure without issuing new packets.\n- `WrapConn`: wraps any `net.Conn` to passively record throughput; it never\n  changes deadlines, keep-alive state, or TLS settings and simply mirrors reads\n  and writes while timing them.\n- `ProbeTCP` / `ProbeSOCKSHandshake`: optional helpers that run bounded health\n  checks when you explicitly need active measurements (e.g., periodic scoring\n  against an idle hop). Use contexts/timeouts to keep probes short-lived.\n\nExample: track a SOCKS handshake and the resulting data stream without\nincreasing traffic volume:\n\n```go\nimport (\n    \"time\"\n\n    \"github.com/AeonDave/go-s5/client\"\n    \"github.com/AeonDave/go-s5/linkquality\"\n)\n\ntracker := linkquality.NewTracker(linkquality.Metadata{\n    Name: \"exit-eu-1\",\n    Kind: linkquality.EndpointSOCKS5,\n    TLS:  true,\n})\n\n// Measure the existing handshake; no extra messages are sent.\nstart := time.Now()\n_, err := client.Handshake(ctx, conn, creds)\ntracker.RecordProbe(time.Since(start), err)\n\n// Wrap the established stream to passively account for throughput.\nstream := linkquality.WrapConn(conn, tracker)\n_ , _ = stream.Write(payload)\n\nscore := tracker.Score()              // 0..100 composite\ninfo := tracker.ConnectionInfo()      // detailed metrics for debugging/selection\n_ = score\n_ = info\n```\n\nThe tracker is designed to stay out of the way: it only observes timings and\nbyte counts already flowing through the connection and holds a minimal mutex to\navoid contention in high-throughput scenarios.\n\nServer side: you can enable linkquality for outbound hops by passing a tracker\ninto the server (`server.WithLinkQuality(tr)` or `srv.LinkQualityTracker()` if\ncreated by the CLI) and reading `tr.Score()` or `tr.ConnectionInfo()` whenever\nyou need to rank/inspect routes. The `s5 server` CLI also supports\n`-linkquality`/`-linkquality-interval`, emitting the same stderr snapshots for\ndirect dials or upstream chains, so you can monitor hop health without touching\nthe traffic. To log connection accepts/closes with peer addresses, pass\n`-log-connections` (CLI) or use `server.WithConnectionLogging(true)` in code.\n\nTo view live quality snapshots when using the bundled CLI, start `s5 dial` with\n`-linkquality` (and optionally `-linkquality-interval 2s`). The tool will print\nthe composite score, success ratio, RTT/jitter and throughput estimates to\nstderr at the requested cadence without affecting traffic flow:\n\n```\ns5 dial -socks host:1080 -dest example.com:443 -linkquality -stdio\n[linkquality] score=94 | success=3/3 | uptime=100.0%\n[linkquality] latency: min/avg/max 18.3ms/21.5ms/24.2ms | jitter: 1.9ms | throughput: 820.0 KB/s (peak 1040.5)\n```\n\n- Use `Association.RelayAddress()` if you need the relay endpoint for firewall\n  rules or observability, without risking in-place mutation.\n- The helper preserves datagram boundaries and accepts both SOCKS-aware\n  addresses (`client.UDPAddr`) and native `*net.UDPAddr` values.\n\n### Production checklist\n\nOperational readiness:\n\n- Run the SOCKS listener behind TLS when crossing untrusted networks. The\n  client helpers accept the same `tls.Config` tuning you would expect from\n  HTTPS clients—set `MinVersion` to at least TLS 1.2 and populate\n  `ServerName` so certificate verification succeeds.\n- Configure client helpers with explicit deadlines (`context.Context` or\n  `WithHandshakeTimeout`/`WithIOTimeout`) and, for long-lived tunnels, enable\n  UDP keep-alives via `client.WithUDPKeepAlive` to keep stateful firewalls from\n  reclaiming the association.\n- Decide on logging verbosity up front. Use `client.NewStdLogger` combined with\n  `client.WithLogger` to surface helper diagnostics, or `client.NewSilentLogger`\n  to suppress them entirely when running inside higher-level frameworks.\n- Monitor relay health using the TCP helper’s `Relay` return values: wrap calls\n  and feed errors into your observability pipeline so asymmetric failures do\n  not go unnoticed.\n\nSecurity hardening:\n\n- Prefer mutually authenticated TLS (mTLS) for administrative or\n  intra-datacenter deployments. The README’s TLS section shows how to inject a\n  CA pool and enable `tls.RequireAndVerifyClientCert`.\n- Rotate credentials regularly and leverage the rules engine to scope\n  high-privilege accounts to the minimum set of destinations.\n- The UDP helper intentionally ignores fragmented datagrams (`FRAG != 0`). This\n  is documented under Compatibility; plan accordingly if your workload requires\n  oversized datagrams.\n\nAuthentication\n- NoAuth (default)\n  - Enabled when no credentials are provided.\n- Username/Password\n  - Provide `WithCredential(auth.StaticCredentials)` or `WithAuthMethods` including `auth.UserPassAuthenticator`.\n- Mutual TLS (mTLS)\n  - Run the server on a TLS listener with `ClientAuth: tls.RequireAndVerifyClientCert`.\n  - Using `ListenAndServeTLS` automatically enriches the `AuthContext.Payload` with TLS peer details you can use in rules or logging:\n    - `tls.subject`, `tls.issuer`, `tls.san.dns`, `tls.san.ip`, `tls.fingerprint.sha256`.\n  - Example below in TLS and mTLS.\n\nOptions (With... API)\n- Authentication\n  - `WithAuthMethods([]auth.Authenticator)`\n  - `WithCredential(auth.CredentialStore)`\n- Rules/ACL\n  - `WithRule(rules.RuleSet)`\n- Resolver\n  - `WithResolver(resolver.NameResolver)`\n- Rewriter\n  - `WithRewriter(handler.AddressRewriter)`\n- Dialing\n  - `WithDial(func(ctx, network, addr) (net.Conn, error))`\n  - `WithDialAndRequest(func(ctx, network, addr, req) (net.Conn, error))`\n  - `WithDialer(net.Dialer)`\n- TCP\n  - `WithHandshakeTimeout(time.Duration)`\n  - `WithTCPKeepAlive(time.Duration)`\n  - `WithBindIP(net.IP)`\n- Connection lifecycle \u0026 metadata\n  - `WithBaseContext(func(net.Listener) context.Context)`\n  - `WithConnContext(func(context.Context, net.Conn) context.Context)`\n  - `WithConnState(func(net.Conn, server.ConnState))`\n  - `WithConnMetadata(func(net.Conn) map[string]string)`\n- BIND\n  - `WithBindAcceptTimeout(time.Duration)`\n  - `WithBindPeerCheckIPOnly(bool)`\n- UDP ASSOCIATE\n  - `WithUseBindIpBaseResolveAsUdpAddr(bool)`\n  - `WithUDPAssociateLimits(maxPeers int, idleTimeout time.Duration)`\n- Infra\n  - `WithGPool(GPool)`, `WithLogger(Logger)`, `WithBufferPool(buffer.BufPool)`\n\n### Connection context \u0026 metadata\n\n`Server.ServeContext(ctx, listener)` binds the provided context to the accept loop and every connection derived from it. Combine it with `WithConnContext` to attach request-scoped values, `WithConnMetadata` to surface immutable attributes on `handler.Request.Metadata`, and `WithConnState` to observe lifecycle transitions.\n\n`handler.Request` now exposes the derived `Context` and the optional `Metadata` map so custom middleware, dialers, and handlers can consume the same data without wrapping `net.Conn`.\n\n```go\nctx, cancel := context.WithCancel(context.Background())\nsrv := socks5.New(\n    server.WithConnContext(func(ctx context.Context, conn net.Conn) context.Context {\n        return context.WithValue(ctx, ctxKey{}, selectNode(conn))\n    }),\n    server.WithConnMetadata(func(conn net.Conn) map[string]string {\n        return map[string]string{\"session_id\": shortID(conn)}\n    }),\n)\ngo srv.ServeContext(ctx, listener)\n// ... later\ncancel() // drains every connection\n```\n\nExamples\nBasic server\n```\ns := socks5.New(\n    socks5.WithHandshakeTimeout(5*time.Second),\n    socks5.WithTCPKeepAlive(30*time.Second),\n)\nlog.Fatal(s.ListenAndServe(\"tcp\", \":1080\"))\n```\n\nTLS and mTLS\n```\ncfg := \u0026tls.Config{\n    Certificates: []tls.Certificate{cert},\n    // For mTLS\n    ClientAuth: tls.RequireAndVerifyClientCert,\n    ClientCAs:  clientCAPool,\n}\n\ns := socks5.New(\n    socks5.WithHandshakeTimeout(5*time.Second),\n)\nlog.Fatal(s.ListenAndServeTLS(\"tcp\", \":1080\", cfg))\n```\nNote: when TLS is enabled, the server completes the handshake early and enriches `AuthContext.Payload` with client certificate identity (subject, issuer, SANs, SHA‑256 fingerprint) for rules/ACLs or logging.\n\nUsername/password authentication\n```\ncreds := auth.StaticCredentials{\"alice\": \"secret\", \"bob\": \"p@ss\"}\ns := socks5.New(\n    socks5.WithCredential(creds), // automatically enables User/Pass\n)\nlog.Fatal(s.ListenAndServe(\"tcp\", \":1080\"))\n```\n\nCustom rules/ACLs\nThe `rules` package provides a default `PermitAll`. You can implement your own `RuleSet`:\n```\ntype onlyLocal struct{}\nfunc (onlyLocal) Allow(ctx context.Context, req *handler.Request) (context.Context, bool) {\n    ip := req.DestAddr.IP\n    if ip.IsLoopback() || ip.IsPrivate() {\n        return ctx, true\n    }\n    return ctx, false\n}\n\ns := socks5.New(\n    socks5.WithRule(onlyLocal{}),\n)\n```\n\nCustom DNS resolver\n```\ntype staticResolver struct{}\nfunc (staticResolver) Resolve(ctx context.Context, host string) (context.Context, net.IP, error) {\n    // example: force 1.2.3.4\n    return ctx, net.ParseIP(\"1.2.3.4\"), nil\n}\n\ns := socks5.New(\n    socks5.WithResolver(staticResolver{}),\n)\n```\n\nAddress rewriter\n```\ntype rewriteToLocal struct{}\nfunc (rewriteToLocal) Rewrite(ctx context.Context, r *handler.Request) (context.Context, *protocol.AddrSpec) {\n    // redirect everything to the same port on 127.0.0.1\n    d := *r.DestAddr\n    d.IP = net.ParseIP(\"127.0.0.1\")\n    d.FQDN = \"\"\n    return ctx, \u0026d\n}\n\ns := socks5.New(socks5.WithRewriter(rewriteToLocal{}))\n```\n\nMiddleware for logging/metrics\n```\nlogMW := handler.MiddlewareFunc(func(next handler.Handler) handler.Handler {\n    return func(ctx context.Context, w io.Writer, r *handler.Request) error {\n        start := time.Now()\n        err := next(ctx, w, r)\n        dur := time.Since(start)\n        log.Printf(\"%s %s -\u003e %s in %v (err=%v)\", r.CommandName(), r.RemoteAddr, r.DestAddr, dur, err)\n        return err\n    }\n})\n\ns := socks5.New(\n    socks5.WithConnectMiddleware(logMW),\n    socks5.WithBindMiddleware(logMW),\n    socks5.WithAssociateMiddleware(logMW),\n)\n```\n\nUpstream chaining (server-side)\nUse `WithDial` or `WithDialAndRequest` to relay TCP traffic through another SOCKS5 proxy.\n```\nimport xproxy \"golang.org/x/net/proxy\"\n\nupstream, _ := xproxy.SOCKS5(\"tcp\", \"hop2.example:1080\", nil, \u0026net.Dialer{})\n\ndial := func(ctx context.Context, network, addr string) (net.Conn, error) {\n    type ctxDialer interface{ DialContext(context.Context, string, string) (net.Conn, error) }\n    if d, ok := upstream.(ctxDialer); ok { return d.DialContext(ctx, network, addr) }\n    return upstream.Dial(network, addr)\n}\n\ns := socks5.New(socks5.WithDial(dial))\n```\n\nClient-side chaining with ProxyChains\nExample of a strict chain with 3 hops:\n```\n# ~/.proxychains/proxychains.conf\nstrict_chain\nquiet_mode\nproxy_dns\n[ProxyList]\nsocks5 127.0.0.1 1080\nsocks5 10.0.0.2 1080 user pass\nsocks5 example.last 1080\n```\nRun: `proxychains4 -q curl https://ifconfig.me`\n\nClient: multi-hop DialChain\n```\nchain := []client.Hop{{Address:\"127.0.0.1:1080\"}, {Address:\"10.0.0.2:1080\"}}\ncli := client.New(client.WithHandshakeTimeout(5*time.Second), client.WithIOTimeout(5*time.Second))\nconn, err := cli.DialChain(ctx, chain, \"ifconfig.me:443\", 5*time.Second)\n```\n\nAdvanced BIND options\n```\ns := socks5.New(\n    socks5.WithBindIP(net.ParseIP(\"0.0.0.0\")),\n    socks5.WithBindAcceptTimeout(30*time.Second),\n    socks5.WithBindPeerCheckIPOnly(true), // validate peer by IP only\n)\n```\n\nAdvanced UDP ASSOCIATE options\n```\ns := socks5.New(\n    socks5.WithUseBindIpBaseResolveAsUdpAddr(true), // bind UDP socket to bindIP\n    socks5.WithUDPAssociateLimits(1024, 2*time.Minute), // peer limit and idle GC\n)\n```\nNotes:\n- For FQDN destinations, the server preserves the hostname and selects `udp4` or `udp6` to match the client’s address family.\n- Datagram packets with `FRAG != 0` are dropped.\n\nPerformance Notes\n- Proxy I/O uses a shared buffer pool and fast paths (`io.WriterTo`/`io.ReaderFrom`) where safe.\n- To avoid platform‑specific hangs with certain reader implementations, the proxy prefers `WriteTo`, and selectively uses `ReadFrom` for well‑behaved readers (e.g., `*bytes.Reader`, `*strings.Reader`).\n- The proxy attempts half‑closes (`CloseWrite`/`CloseRead`) where supported.\n\nHandshake timeout and TCP keep-alive\n```\ns := socks5.New(\n    socks5.WithHandshakeTimeout(5*time.Second),\n    socks5.WithTCPKeepAlive(30*time.Second),\n)\n```\n\nBuffer pool tuning and GPool integration\n```\n// 64 KiB buffer pool\ns := socks5.New(\n    socks5.WithBufferPool(buffer.NewPool(64*1024)),\n)\n\n// Integrate with an external goroutine pool\nvar myPool GPool = newMyPool()\ns = socks5.New(socks5.WithGPool(myPool))\n```\n\nCompatibility\n- Conforms to SOCKS5 (RFC 1928) for CONNECT, BIND, and UDP ASSOCIATE.\n- Accurate REP code mapping for typical dial errors.\n- UDP: fragmented datagrams (FRAG != 0) are not supported.\n- ProxyChains does not implement end‑to‑end UDP ASSOCIATE (only optional DNS‑over‑TCP).\n- BIND: expected peer validation; with `WithBindPeerCheckIPOnly(true)`, matches by IP only.\n\nTesting\nRun the test suite:\n```\ngo test ./...\n```\n\nClient multi-hop DialChain and UDP/BIND examples\n\nMulti-hop DialChain\n```\nchain := []client.Hop{\n  { Address: \"127.0.0.1:1080\" },\n  { Address: \"10.0.0.2:1080\" },\n}\ncli := client.New(client.WithHandshakeTimeout(5*time.Second), client.WithIOTimeout(5*time.Second))\nconn, err := cli.DialChain(ctx, chain, \"ifconfig.me:443\", 5*time.Second)\nif err != nil { /* handle */ }\ndefer conn.Close()\n```\n\nPer-hop TLS and credentials\n```\nchain := []client.Hop{\n  { Address: \"10.0.0.2:1080\", Creds: \u0026client.Credentials{Username: \"alice\", Password: \"secret\"} },\n  { Address: \"hop3.example:1080\", TLSConfig: \u0026tls.Config{ServerName: \"hop3.example\", MinVersion: tls.VersionTLS12} },\n}\ncli := client.New(client.WithHandshakeTimeout(5*time.Second), client.WithIOTimeout(5*time.Second))\nconn, err := cli.DialChain(ctx, chain, \"example.org:443\", 5*time.Second)\n```\n\nNotes:\n- Per-hop creds/TLS are optional via Hop.{Creds,TLSConfig}.\n- DialChain respects ctx and client timeouts; set client.WithHandshakeTimeout/client.WithIOTimeout.\n- Control the first-hop dial with client.WithDialer (custom net.Dialer) or the dialTimeout argument.\n\nUDP and BIND on the last hop\n```\n// Build the TCP chain first\n// Pass empty finalTarget to stop at the last hop and speak to the SOCKS server\ncli := client.New(client.WithHandshakeTimeout(5*time.Second), client.WithIOTimeout(5*time.Second))\nconn, err := cli.DialChain(ctx, chain, \"\", 5*time.Second)\nif err != nil { /* handle */ }\ndefer conn.Close()\n\n// UDP ASSOCIATE\nassoc, rep, err := cli.UDPAssociate(ctx, conn)\nif err != nil { /* handle */ }\ndefer assoc.Close()\ndst := socks5protocol.AddrSpec{IP: net.ParseIP(\"127.0.0.1\"), Port: 9999, AddrType: socks5protocol.ATYPIPv4}\n_, _ = assoc.WriteTo(dst, []byte(\"ping\"))\n\n// CONNECT helper with TCP stream utilities\nstream, _, err := cli.ConnectStream(ctx, conn, socks5protocol.AddrSpec{FQDN: \"example.org\", Port: 443, AddrType: socks5protocol.ATYPDomain})\nif err != nil { /* handle */ }\ndefer stream.Close()\n_, _ = stream.WriteString(\"GET / HTTP/1.1\\r\\nHost: example.org\\r\\n\\r\\n\")\n\n\n// BIND (two-step)\npeer := socks5protocol.AddrSpec{IP: net.ParseIP(\"0.0.0.0\"), Port: 0, AddrType: socks5protocol.ATYPIPv4}\nfirst, second, err := cli.Bind(ctx, conn, peer)\n_ = first; _ = second // see bind.go for details\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faeondave%2Fgo-s5","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faeondave%2Fgo-s5","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faeondave%2Fgo-s5/lists"}