{"id":50847670,"url":"https://github.com/khodaparastan/s5lb","last_synced_at":"2026-06-14T11:02:49.139Z","repository":{"id":353727136,"uuid":"1216238016","full_name":"Khodaparastan/s5lb","owner":"Khodaparastan","description":"High-performance, extensible SOCKS5 load balancer (TCP \u0026 UDP) for cloud-native deployments. Written in Go with pluggable transports, multiple strategies, backpressure, OpenTelemetry, and an admin API/UI.","archived":false,"fork":false,"pushed_at":"2026-05-24T11:23:45.000Z","size":200,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T13:06:44.769Z","etag":null,"topics":["backpressure","high-availability","kubeexec","load-balancer","proxy","socks5","web-sockets"],"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/Khodaparastan.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":null,"dco":null,"cla":null}},"created_at":"2026-04-20T17:52:55.000Z","updated_at":"2026-05-24T11:39:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Khodaparastan/s5lb","commit_stats":null,"previous_names":["khodaparastan/socks5lb","khodaparastan/s5lb"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Khodaparastan/s5lb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Khodaparastan%2Fs5lb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Khodaparastan%2Fs5lb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Khodaparastan%2Fs5lb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Khodaparastan%2Fs5lb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Khodaparastan","download_url":"https://codeload.github.com/Khodaparastan/s5lb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Khodaparastan%2Fs5lb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34318525,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-14T02:00:07.365Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["backpressure","high-availability","kubeexec","load-balancer","proxy","socks5","web-sockets"],"created_at":"2026-06-14T11:02:47.584Z","updated_at":"2026-06-14T11:02:49.130Z","avatar_url":"https://github.com/Khodaparastan.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# s5lb\n\nA SOCKS5 load balancer written in Go with pluggable balancing strategies, configurable backpressure, UDP ASSOCIATE, OpenTelemetry tracing, Prometheus metrics, two-phase graceful drain, SIGHUP hot reload, multi-group mode, and pluggable upstream transports (direct TCP and Kubernetes pod exec).\n\n## Features\n\n- **9 balancing strategies**: least-active, round-robin, weighted-round-robin, random, weighted-random, p2c, least-latency, consistent-hash (HRW), priority-failover\n- **4 backpressure strategies**: reject, wait, drop-oldest, drop-lowest-priority\n- **SOCKS5 CONNECT + UDP_ASSOCIATE** (RFC 1928/1929); BIND explicitly unsupported\n- **Per-upstream concurrency cap** + FIFO waiter queue + global admission gate\n- **Active health probing** with sliding-window circuit breaker\n- **EWMA latency** per upstream (feeds least-latency selector)\n- **`splice(2)` zero-copy** TCP tunneling on Linux (when idle-timeout is disabled)\n- **Multi-group mode**: run independent SOCKS5 listener pools from a single process, each with its own strategy, upstreams, and transport\n- **Pluggable upstream transports**: direct TCP or Kubernetes pod exec (SPDY, WebSocket, or raw WSS); includes a `kube-socks-relay` sidecar binary\n- **OpenTelemetry** tracing via OTLP/gRPC (opt-in)\n- **Prometheus metrics**, liveness/readiness probes, optional pprof\n- **Admin UI**: single-page dashboard with live SSE state stream, upstream drain control, and session viewer\n- **Admin REST API**: multi-group endpoints, sessions, upstream drain, config snapshot, reload\n- **YAML config** + CLI flag overrides\n- **SIGHUP hot reload** of upstreams, backpressure, timeouts, log level\n- **Two-phase graceful drain**: soft (wait for sessions) + hard (force close)\n\n## Layout\n\n```\ncmd/\n  s5lb/              # main entrypoint\n  kube-socks-relay/  # sidecar: connects stdin/stdout to host:port (used by kubernetes-exec transport)\ninternal/\n  admin/             # HTTP admin server: UI, /metrics, /healthz, /readyz, /version, REST API, pprof\n  admission/         # global admission gate + backpressure strategies + session tracker\n  balancer/          # accept loop, queue, health probing, CONNECT + UDP sessions, reload\n  config/            # YAML loader, defaults, validation, upstream spec parser\n  logging/           # slog setup\n  metrics/           # Prometheus collectors\n  socks5/            # wire protocol (TCP + UDP header codec) + pipe\n  strategy/          # pluggable LB strategies (pure functions of snapshots)\n  telemetry/         # OpenTelemetry setup (opt-in)\n  transport/         # upstream dialers: direct TCP, kubernetes-exec (SPDY/WS/raw-WSS)\n  upstream/          # Upstream model + State (self-locking) + EWMA\n```\n\n## Build\n\n```bash\nmake build          # produces ./bin/s5lb\n./bin/s5lb -version\n```\n\nBuild both binaries:\n```bash\ngo build ./cmd/s5lb\ngo build ./cmd/kube-socks-relay\n```\n\nCross-platform release artifacts:\n```bash\nmake release        # linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64 → ./dist/\n```\n\n## Quick start\n\n```bash\n# Config file\n./bin/s5lb -config config.example.yaml -log-level debug\n\n# Flags only\n./bin/s5lb \\\n  -strategy p2c \\\n  -backpressure drop-oldest \\\n  -upstream '10.0.0.1:1080#w=3,p=0' \\\n  -upstream '10.0.0.2:1080#w=1,p=0'\n```\n\n## CLI flags\n\n| Flag | Default | Description |\n| --- | --- | --- |\n| `-config` | | Path to YAML config file; flags override |\n| `-listen` | `127.0.0.1:1080` | Frontend listen address |\n| `-admin-addr` | `127.0.0.1:9090` | Admin HTTP listen address |\n| `-upstream` | | Upstream spec (repeatable) |\n| `-strategy` | `least-active` | Balancing strategy |\n| `-hash-key` | `client-ip` | `client-ip \\| destination \\| destination-host` |\n| `-backpressure` | `reject` | Backpressure strategy |\n| `-max-per-proxy` | `100` | Max concurrent connections per upstream |\n| `-max-clients` | `4096` | Global max concurrent clients |\n| `-udp` | `true` | Enable UDP_ASSOCIATE |\n| `-otel` | `false` | Enable OpenTelemetry tracing |\n| `-otel-endpoint` | | OTLP gRPC endpoint (e.g. `otel-collector:4317`) |\n| `-otel-insecure` | `true` | Disable TLS to OTLP endpoint |\n| `-log-level` | `info` | `debug \\| info \\| warn \\| error` |\n| `-log-format` | `json` | `json \\| text` |\n| `-version` | | Print version and exit |\n| `-list-strategies` | | List available strategies and exit |\n\n## Strategies\n\n| Name                       | Description                                 |\n| -------------------------- | ------------------------------------------- |\n| `least-active` _(default)_ | Fewest active connections; random tie-break |\n| `round-robin`              | Strict rotation                             |\n| `weighted-round-robin`     | Smooth WRR (nginx algorithm)                |\n| `random`                   | Uniform random                              |\n| `weighted-random`          | Proportional to `weight`                    |\n| `p2c`                      | Power of two choices                        |\n| `least-latency`            | Lowest EWMA dial+handshake latency          |\n| `consistent-hash`          | Rendezvous hashing (HRW), sticky by `hash_key` |\n| `priority-failover`        | Ordered by `priority`; failover on unhealthy |\n\n## Backpressure strategies\n\n| Name                       | Behavior when `max_clients` is saturated                |\n| -------------------------- | ------------------------------------------------------- |\n| `reject` _(default)_       | Fast-fail with `RepGeneralFailure`                      |\n| `wait`                     | Block up to `admission_wait_timeout`                    |\n| `drop-oldest`              | Evict the oldest in-flight session                      |\n| `drop-lowest-priority`     | Evict oldest session on the lowest-priority upstream    |\n\nEviction closes the victim's client socket; the session goroutine unwinds normally.\n\n## Upstream spec forms (flag)\n\n```\nhost:port\nuser:pass@host:port\nsocks5://user:pass@host:port?weight=N\u0026priority=N\u0026id=foo\nhost:port#w=N,p=N,id=foo\n```\n\nOr define them in YAML — see `config.example.yaml`.\n\n## Config reference\n\nAll fields with defaults (single-group mode):\n\n```yaml\nlisten: \"127.0.0.1:1080\"\nadmin:  \"127.0.0.1:9090\"\n\nmax_per_proxy: 100\nmax_clients:   4096\n\nhealth_interval:    20s\nretry_backoff:      30s\nconnect_timeout:    5s\nhandshake_timeout:  10s\nqueue_wait_timeout: 10s\n\nfailure_threshold: 5\nfailure_window:    30s\n\nidle_timeout: 0s   # 0 enables splice(2) fast path on Linux\n\ndrain_soft_timeout: 20s\ndrain_hard_timeout: 10s\n\ntcp_keepalive: true\n\nstrategy: \"least-active\"\nhash_key: \"client-ip\"   # client-ip | destination | destination-host\n\nbackpressure: \"reject\"\nadmission_wait_timeout: 2s\n\nudp_enabled:      true\nudp_bind:         \"\"\nudp_idle_timeout: 60s\n\n# admin_token: \"\"   # required when admin is not on loopback\n# admin_pprof: false\n\ntransport:\n  type: direct   # direct | kubernetes-exec\n\notel:\n  enabled:      false\n  endpoint:     \"\"\n  insecure:     true\n  service_name: \"s5lb\"\n  sample_ratio: 1.0\n  headers: {}\n\nlog_level:  \"info\"\nlog_format: \"json\"\n\nupstreams:\n  - host: \"10.0.0.1\"\n    port: 1080\n    username: \"user\"\n    password: \"pass\"\n    weight:   3\n    priority: 0\n```\n\n### Reloadable fields (SIGHUP / `POST /admin/reload`)\n\nUpstreams, backpressure, timeouts, and log level are reloaded at runtime.  \nNot reloadable (documented as warnings in logs): listen addr, admin addr, strategy, hash_key, max_clients, OTel.\n\n## Multi-group mode\n\nWhen `groups:` is present in config, each entry is an independent SOCKS5 listener pool with its own upstreams, strategy, and transport. Global settings (OTel, admin, log) are shared.\n\n```yaml\ngroups:\n  - name: web\n    listen: \"0.0.0.0:1080\"\n    strategy: least-active\n    upstreams:\n      - { host: proxy-web-1.internal, port: 1080, weight: 3 }\n      - { host: proxy-web-2.internal, port: 1080, weight: 1 }\n\n  - name: db\n    listen: \"0.0.0.0:1081\"\n    strategy: round-robin\n    connect_timeout: 3s\n    upstreams:\n      - { host: proxy-db-1.internal, port: 1080 }\n\n  - name: k8s\n    listen: \"0.0.0.0:1082\"\n    strategy: least-latency\n    transport:\n      type: kubernetes-exec\n      kubernetes:\n        namespace: relay\n        pod: relay-0\n        container: relay\n        command: [\"/usr/local/bin/kube-socks-relay\", \"tcp\", \"{{host}}\", \"{{port}}\"]\n    upstreams:\n      - { host: internal-svc.cluster.local, port: 8080 }\n```\n\n## Upstream transports\n\n### `direct` (default)\n\nPlain TCP dial. Supports keepalive and configurable connect timeout.\n\n### `kubernetes-exec`\n\nOpens upstream byte streams through Kubernetes pod exec. The exec command must bridge its stdin/stdout to the target host:port. The included `kube-socks-relay` binary is designed for this role.\n\n```yaml\ntransport:\n  type: kubernetes-exec\n  kubernetes:\n    kubeconfig: \"\"         # empty = in-cluster or $KUBECONFIG\n    context:    \"\"\n    namespace:  relay\n    pod:        relay-0\n    container:  relay\n    mode:       websocket-preferred  # spdy | websocket | websocket-preferred | raw-wss\n    command:    [\"/usr/local/bin/kube-socks-relay\", \"tcp\", \"{{host}}\", \"{{port}}\"]\n    # wss_url: \"wss://api.example.com/api/v1/namespaces/ns/pods/pod/exec?container=relay\"\n    # bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n    # ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n```\n\nCommand template placeholders: `{{host}}`, `{{port}}`, `{{address}}`.\n\n#### `kube-socks-relay`\n\nA minimal sidecar binary that connects stdin/stdout to a TCP address. Deploy it inside the relay pod and reference it in `transport.kubernetes.command`.\n\n```bash\nkube-socks-relay tcp \u003chost\u003e \u003cport\u003e\n```\n\n## Observability\n\n### Admin endpoints\n\n| Path                                  | Method | Purpose                                       |\n| ------------------------------------- | ------ | --------------------------------------------- |\n| `/metrics`                            | GET    | Prometheus scrape                             |\n| `/healthz`                            | GET    | Liveness (always OK while alive)              |\n| `/readyz`                             | GET    | 200 iff ≥1 upstream healthy                  |\n| `/version`                            | GET    | Build info JSON                               |\n| `/admin/reload`                       | POST   | Reload from config file                       |\n| `/admin/api/groups`                   | GET    | JSON array of all group summaries             |\n| `/admin/api/groups/{name}`            | GET    | JSON detail for one group                     |\n| `/admin/api/groups/{name}/reload`     | POST   | Reload a single group                         |\n| `/admin/api/state`                    | GET    | Flat single-group state snapshot              |\n| `/admin/api/events`                   | GET    | SSE stream of state updates (2 s interval)    |\n| `/admin/api/sessions`                 | GET    | Active session list                           |\n| `/admin/api/config`                   | GET    | Running config snapshot (JSON)                |\n| `/admin/api/reload`                   | POST   | Reload (alias for UI)                         |\n| `/admin/api/upstreams/{id}/drain`     | POST   | Set/clear drain flag (`?enabled=true\\|false`) |\n| `/debug/pprof/*`                      | GET    | Profiling (opt-in via `admin_pprof: true`)    |\n\n**Admin UI**: browsing to `http://\u003cadmin-addr\u003e/` serves a single-page dashboard with live upstream status, session viewer, and reload/drain controls.\n\n**Security**: when `admin` is not a loopback address, `admin_token` is required. Requests must include `Authorization: Bearer \u003ctoken\u003e`.\n\n### OpenTelemetry\n\nSet `otel.enabled: true` and `otel.endpoint: \"otel-collector:4317\"`. Spans emitted per session:\n\n```\nsocks5.session\n├── socks5.greeting\n├── socks5.request\n├── balancer.acquire\n├── upstream.dial\n├── upstream.handshake\n├── upstream.connect        (CONNECT)\n├── upstream.udp_associate  (UDP ASSOCIATE)\n└── tunnel.pipe             (TCP only)\n```\n\n### Useful PromQL\n\n```promql\n# Spread across upstreams\nsum by (upstream) (rate(s5lb_upstream_selected_total[5m]))\n\n# p99 dial latency\nhistogram_quantile(0.99,\n  sum by (le, upstream) (rate(s5lb_upstream_dial_seconds_bucket[5m])))\n\n# Queue pressure\ns5lb_queue_depth\nhistogram_quantile(0.99, sum by (le) (rate(s5lb_queue_wait_seconds_bucket[5m])))\n\n# Backpressure evictions per second\nrate(s5lb_backpressure_evictions_total[1m])\n\n# UDP drop rate by reason\nsum by (reason) (rate(s5lb_udp_dropped_total[5m]))\n```\n\n## Lifecycle\n\n- **`SIGTERM` / `SIGINT`** — two-phase drain:\n    1. Stop accepting, cancel context.\n    2. Wait up to `drain_soft_timeout` for sessions to finish.\n    3. Force-close survivors; wait up to `drain_hard_timeout`.\n- **`SIGHUP`** — reload config file (upstreams, backpressure, timeouts, log level).\n- **`POST /admin/reload`** — same as SIGHUP.\n- **`POST /admin/api/groups/{name}/reload`** — reload a single group.\n- **`POST /admin/api/upstreams/{id}/drain?enabled=true`** — drain a single upstream without restart.\n\n## Kubernetes probes\n\n```yaml\nlivenessProbe:\n  httpGet: { path: /healthz, port: 9090 }\n  periodSeconds: 10\nreadinessProbe:\n  httpGet: { path: /readyz, port: 9090 }\n  periodSeconds: 5\n```\n\n## Testing\n\n```bash\nmake test        # full suite with race detector\nmake check       # fmt + vet + test\n```\n\nThe suite covers: SOCKS5 protocol greeting/request parsing, UDP header codec round-trip, every strategy's determinism and eligibility filtering, admission tracker ordering, queue FIFO behavior, config loading, and multi-group config merging.\n\n## Known limitations\n\n- **No frontend auth / ACLs.** Anyone reachable at `listen` can use it.\n- **No per-IP rate limiting.**\n- **No TLS to upstreams** (direct transport).\n- **No BIND command.** Responds with `RepCommandNotSupported`.\n- **Single-instance.** No `SO_REUSEPORT`, no clustering.\n- **UDP: no fragmentation.** Datagrams with `FRAG != 0` are dropped.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkhodaparastan%2Fs5lb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkhodaparastan%2Fs5lb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkhodaparastan%2Fs5lb/lists"}