Skip to content

Releases: j0shcap/simple-socks5

v2.2.0

Choose a tag to compare

@j0shcap j0shcap released this 17 Oct 03:49
69ecd27

Upgrading from 2.1 (behaviour changes)

These changes affect you if you override the image's command or healthcheck, or parse its logs:

  1. Healthcheck command. The image healthcheck runs python -m simple_socks5.healthcheck. If
    you copied python -m src.healthcheck into a compose file or Kubernetes probe, update it.
  2. Default command. The image runs python -m simple_socks5 --host 0.0.0.0 --port 1080.
    Overrides that run python app.py ... keep working.
  3. Log file logger names. SOCKS5_LOG_FILE lines name loggers [simple_socks5.…] instead of
    [src.…]. Console lines are unchanged.
  4. Log levels. ERROR now means a fault in the proxy: client mistakes log at DEBUG and
    unreachable destinations at INFO. Authenticated user logs at DEBUG.
  5. Rate-limited warnings. Handshake-timeout and failed-login warnings are logged at most once
    every 10 s. Set LOGGING_LEVEL=debug to see every failure with the client's IP.
  6. CONNECTION lines. A CONNECT logs its CONNECTION line only once the destination has
    connected, so these lines no longer count failed attempts.
  7. Keep 2.1 entirely. Pin jcaponigro20/simple-socks5:2.1.0 (or
    :2.1.0-logging-disabled).

Added

  • pip install . installs a simple-socks5 command, which takes the same flags as
    python3 app.py. python -m simple_socks5 works too.
  • The package ships a py.typed marker, so type checkers use its annotations.
  • SECURITY.md says how to report a vulnerability privately and which versions get fixes;
    CONTRIBUTING.md covers development and releases.
  • python -m benchmarks measures the proxy against direct connections, profiles it and charts the
    results, from a checkout. benchmarks/README.md has the results and the optimizations they point to.

Changed

  • The code is now the simple_socks5 package under src/, and pip install . installs it.
    python3 app.py keeps working from a clone and in the image.
  • Behaviour change: the image healthcheck runs python -m simple_socks5.healthcheck. If
    you copied python -m src.healthcheck into a compose file or Kubernetes probe, update it.
  • Behaviour change: logger names in SOCKS5_LOG_FILE lines change from [src.…] to
    [simple_socks5.…], which matters if you parse that file. Console lines are unchanged.
  • Behaviour change: the image's default command is now
    python -m simple_socks5 --host 0.0.0.0 --port 1080. Commands that override it with
    python app.py ... keep working.
  • The version is read from the installed package, so pyproject.toml is the only place it's
    written. --version from a clone that was never installed prints 0+unknown; run
    pip install -e . to fix it.
  • Contributors lint and format with Ruff instead of flake8: pip install -e '.[dev]', then
    ruff check . and ruff format ..
  • Contributors type-check with mypy in strict mode: pip install -e '.[dev]', then mypy.
  • CI tests on Python 3.10 to 3.14.
  • Behaviour change: ERROR now means a fault in the proxy. Malformed requests, unsupported
    commands and clients hanging up log at DEBUG, and a destination that refuses or doesn't answer
    logs at INFO with the reply code. SOCKS5_LOG_FILE, which keeps ERROR and above, no longer
    fills up with client mistakes.
  • Behaviour change: handshake-timeout and failed-login warnings are rate-limited to one line
    every 10 s, with a count of how many happened since the last one, so idle or brute-force clients
    can't flood the log. If you ban IPs from those lines, set LOGGING_LEVEL=debug (or -L debug):
    every failure is still logged there, with the client's IP.
  • Behaviour change: Authenticated user logs at DEBUG, so at INFO a connection logs only its
    CONNECTION and CLOSED lines.
  • Behaviour change: a CONNECT logs its CONNECTION line once the destination has connected,
    showing the address it connected to. A CONNECT that fails logs only its reply-code line, so
    CONNECTION lines no longer count failed attempts.

Fixed

  • When the server can't start (for example, the port is already in use), it now exits with code 1
    under python -S too. Before, it crashed with NameError: name 'exit' is not defined.
  • IPv6 addresses are shown in brackets in the Server started on line and the UDP RELAY debug
    lines, for example [::]:1080 instead of :::1080.
  • A CONNECT by hostname tries every address the name resolves to, in the order the system returns
    them, until one connects (at most 4, each with SOCKS5_CONNECT_TIMEOUT). Before, only the first
    was tried, so localhost (which usually resolves to ::1 first) failed with "connection refused"
    against a server listening on 127.0.0.1 only, and dual-stack hosts failed with "network
    unreachable" where IPv6 is broken.
  • A hostname that doesn't resolve within the DNS timeout gets "host unreachable" straight away.
    Before, the proxy passed the name to connect(), which looked it up again with no time limit.
    Unfinished lookups are capped, so a hung resolver can't pile up threads.
  • A hostname with an empty or over-long label gets "host unreachable" without logging an ERROR.

v2.1.0

Choose a tag to compare

@j0shcap j0shcap released this 09 Oct 02:40
de04a6b

Upgrading from 2.0 (behaviour changes)

Authentication stays optional and off by default; 2.1 only warns when the proxy is open. These
defaults changed:

  1. Image tags. latest and logging-disabled now move only on releases, and pushes to
    main publish :main. Pin :2, :2.1 or :2.1.0 to choose how far you follow.
  2. Log level. The Docker image logs at info instead of debug, and connection log lines
    have a new format. Set -e LOGGING_LEVEL=debug to restore the old level.
  3. Loopback and link-local destinations. Proxying to loopback, link-local (including the
    cloud metadata service at 169.254.169.254) and unspecified addresses is refused with reply
    0x02. Set -e SOCKS5_ALLOW_LOOPBACK=true to restore it. Containers on Docker's default
    bridge network are unaffected.
  4. Error log file. The image no longer writes /app/errors.log. Set
    -e SOCKS5_LOG_FILE=/app/errors.log to restore it.
  5. Empty credentials. An empty SOCKS5_USERNAME or SOCKS5_PASSWORD can no longer be used
    to log in. There is no opt-out.
  6. Keep 2.0 entirely. Pin jcaponigro20/simple-socks5:2.0.0 (or
    :2.0.0-logging-disabled).

Added

  • Versioned Docker image tags: a vX.Y.Z release publishes X.Y.Z, X.Y and X (and
    X.Y.Z-logging-disabled), so a deployment can pin a version. A pre-release such as
    v2.1.0-rc.1 publishes only its exact version.
  • Startup warning when the proxy runs without authentication on a non-loopback host: anyone
    who can reach the port can use it. Set SOCKS5_AUTH_REQUIRED=false to acknowledge an
    intentional no-auth setup; this logs one INFO line instead. Visible at -L debug, info
    and warning.
  • Startup warning when the built-in default credentials are in use. The password is never
    logged.
  • README Security section: open-proxy risk, requiring authentication, publishing only on
    trusted interfaces, cleartext credentials, and pinning :2.0.0 for the pre-2.1 behaviour.
  • SOCKS5_HANDSHAKE_TIMEOUT and SOCKS5_CONNECT_TIMEOUT (seconds, default 10). An invalid
    value exits at startup with an error.
  • SOCKS5_MAX_CONNECTIONS (default 200): the maximum number of concurrent client
    connections. It must be a positive integer; an invalid value exits at startup with an error.
  • SOCKS5_LOG_FILE: when set, ERROR and CRITICAL lines are also written to this file,
    rotated at 1 MB with 5 backups.
  • SOCKS5_HEALTHCHECK_PORT (default 1080): the port the Docker healthcheck probes. Set it
    when you run the image with a different --port.
  • SOCKS5_ALLOW_LOOPBACK (default false): set to true to allow destinations on loopback,
    link-local and unspecified addresses, which are now refused by default.

Changed

  • latest and logging-disabled now move only on stable releases; pushes to main publish
    :main only. An image is published only after the tests and a container smoke test pass.
  • README Quick Start leads with an authenticated example published on 127.0.0.1; the
    no-auth example is labelled for trusted networks only.
  • Docker image base is now python:3.13-slim, pinned by digest (Python 3.10 reaches end of life
    in October 2026). The image also sets PYTHONUNBUFFERED=1 and PYTHONDONTWRITEBYTECODE=1.
  • Behaviour change: the Docker image now logs at info by default instead of debug, so it
    no longer logs every relayed chunk. Set -e LOGGING_LEVEL=debug to restore the old output.
    This also applies when you override the container command without -L.
  • The Docker image runs python directly as PID 1 (exec-form CMD), so it receives
    docker stop's SIGTERM.
  • app.py reads the LOGGING_LEVEL environment variable when -L/--logging-level isn't
    given. Precedence is -L, then LOGGING_LEVEL, then debug; an invalid value exits with
    an error.
  • TCP relay buffer raised from 4 KiB to 64 KiB, about 7× the throughput. The UDP relay's
    receive buffer, which shares the setting, grows to match.
  • The "connection limit reached" warning is logged at most once every 10 seconds, with the
    number of connections rejected since the previous one, instead of once per rejection.
  • No reverse-DNS (PTR) lookups: CONNECT to an IP address no longer waits up to 2 seconds for
    one, and visited addresses no longer leak to the resolver. Logs show the hostname the client
    sent or the IP address.
  • Connection log lines have a new format, which matters if you parse them. Each connection
    logs CONNECTION | client -> destination when it opens and one summary line when it
    closes, CLOSED | client -> destination | up=N B down=N B | N.NN s, instead of a debug line
    per relayed chunk.
  • A client disconnecting mid-handshake or mid-relay is logged at debug without a traceback.
    So is a client that offers no acceptable authentication method, which was a warning.
  • Log colours are used only when the output is a terminal.
  • Behaviour change: errors are no longer written to errors.log in the working directory
    (/app/errors.log in the Docker image). Set SOCKS5_LOG_FILE to keep a log file.
  • The Docker HEALTHCHECK runs python -m src.healthcheck, which speaks SOCKS5 instead of
    opening and closing a bare TCP connection.

Fixed

  • SIGTERM and SIGINT now shut the server down gracefully: it stops accepting connections, gives
    in-flight connections up to 5 seconds to finish, closes the rest and exits with code 0. This
    includes connections accepted just before the signal. An open UDP association can take the
    full 5 seconds. docker stop previously waited 10 seconds and killed the container (exit
    code 137).
  • README: the default port is 1080, not 9999; removed the claim of configurable
    connection limits; the authentication example now sets SOCKS5_AUTH_REQUIRED=true.
  • The outbound socket leaked when a CONNECT target refused the connection; it is now closed
    before the refusal reply is sent.
  • Downloads were truncated when the client read slower than the origin sent: a full send
    buffer dropped the connection. The relay now waits for the slow side (up to 5 minutes per
    write), so the transfer completes.
  • TCP half-close: a client or server that closes its sending side still receives the reply.
    Before, the first end-of-stream from either side closed the whole connection.
  • Handshake slowloris: a client that connected and then sent nothing (or one byte at a time)
    held a connection slot forever, so 200 idle connections locked out every client. The
    greeting, authentication and request must now arrive within SOCKS5_HANDSHAKE_TIMEOUT
    seconds in total; a client that is still sending is disconnected without a reply. The
    separate 45-second authentication timeout is gone.
  • Connection bursts: the listen backlog was 5, so on Linux clients connecting during a burst
    waited 1 to 3 seconds for TCP retransmits before the proxy accepted them. It is now the
    system maximum (SOMAXCONN).
  • Outbound connect timeout: CONNECT to an unreachable host waited for the OS default (75 s on
    macOS, about 2 minutes on Linux) and replied 0x01. It now gives up after
    SOCKS5_CONNECT_TIMEOUT seconds and replies 0x04 (host unreachable).
  • Accurate SOCKS reply codes (RFC 1928): network unreachable replies 0x03, host unreachable
    and connect timeouts 0x04, an unknown address type 0x08, and a domain name that isn't
    valid UTF-8 0x04. These all replied 0x01 before.
  • A malformed UDP datagram (too short, a non-zero reserved field, an unknown address type or a
    truncated address) ended the UDP association and sent a stray failure reply on its control
    connection; a single spoofed datagram was enough. It is now dropped and the association keeps
    working. A datagram to a destination that can't be sent to (such as port 0) is dropped too.
  • A request now gets exactly one SOCKS reply. An error after the success reply (for example
    during a CONNECT tunnel) used to send a second, failure reply, injecting 10 bytes into the
    stream; it is now only logged.
  • A UDP association now logs a CLOSED line when it ends, like a TCP connection, with the
    datagram payload bytes relayed each way.
  • A UDP association now ends as soon as its TCP control connection closes, as RFC 1928
    requires. Before, its relay port kept relaying datagrams for up to 2 minutes after the client
    disconnected. The 2-minute idle timeout stays, but only datagrams from the client reset it.
  • The Docker healthcheck no longer logs an error with a traceback every 30 seconds (about 2,880
    a day); the proxy now logs nothing above debug for it.
  • Error log rotation: each module had its own handler writing to the same errors.log, so
    rotation renamed the file under the others and could lose or interleave lines.
  • An exception that escaped a connection handler was printed to stderr by socketserver,
    even with -L disabled. It now goes through logging, so -L applies: ERROR with its
    traceback, or debug for a client that disconnected.
  • An empty domain name is rejected with 0x04 (host unreachable). It used to connect to the
    proxy host itself.

Security

  • Usernames and passwords are compared in constant time, and both are always checked, so
    response timing no longer reveals which one was wrong.
  • Stricter RFC 1929 parsing: credentials that aren't valid UTF-8 now get a failure reply
    instead of an uncaught error and a traceback on stderr, and an empty username or password
    is always rejected. Behaviour change: an empty SOCKS5_USERNAME or SOCKS5_PASSWORD
    can no longer be used to log in.
  • A failed login logs the client's IP address instead of the username it submitted.
  • SOCKS5_USERNAME and SOCKS5_PASSWORD are read for each login rather than once at import.
  • Behaviour change: proxying to loopback (127.0.0.0/8, ::1), link-local
    (169.254.0.0/16, including the cloud metadata service at 169.254.169.254, and
    fe80::/10) and unspecified (0.0.0.0/8, ::) addresses is refuse...
Read more

v2.0.0

Choose a tag to compare

@j0shcap j0shcap released this 26 Feb 05:29

What's Changed

Breaking Changes

  • Dropped Python 3.7, 3.8, 3.9 — minimum version is now Python 3.10. Added 3.13 to CI.

Bug Fixes

  • Fixed is_initialized() always returning False due to wrong attribute names
  • Fixed IPv6 replies crashing with struct.error (variable-length packing)
  • Fixed IPv6 getsockname()/getpeername() 4-tuple unpacking crash
  • Fixed UDP relay: added timeouts, error handling, and socket cleanup (previously blocked indefinitely and leaked sockets)
  • Fixed thread-unsafe logger cache with threading.Lock
  • Fixed selector leak when TCPRelay.__init__ fails
  • Fixed socket timeout not restored after authentication
  • Fixed partial TCP reads with recv_exact helper (previously assumed all bytes arrived in one recv)
  • Fixed server shutdown order (shutdown() before server_close())
  • Fixed raise e losing tracebacks in TCP relay (now bare raise)
  • Fixed duplicate log handlers when update_loggers() is called
  • Fixed proxy_address AttributeError (now initialized to None)
  • Fixed DNS thread leaks (replaced ThreadPoolExecutor with daemon threads)
  • Fixed error replies always using IPv4 address type regardless of client
  • Fixed UDP relay crash on domain-name datagrams
  • Fixed double-close of client socket between relay and server
  • Replaced deprecated logger.warn() with logger.warning()
  • Validated RSV field per RFC 1928

Improvements

  • Added connection limit (200 max) to prevent unbounded thread creation
  • Added timeout to both forward and reverse DNS lookups
  • Credentials now configurable via SOCKS5_USERNAME/SOCKS5_PASSWORD environment variables
  • Replaced magic numbers with named constants throughout
  • Unified reply-sending pattern in server command handling
  • Replaced wildcard imports with explicit imports and __all__
  • Consolidated two Dockerfiles into one with LOGGING_LEVEL build arg
  • Added HEALTHCHECK to Dockerfile
  • Added .dockerignore
  • Added pyproject.toml with project metadata
  • Added flake8 linting to CI pipeline
  • Added pytest-cov with 60% coverage threshold to CI

Tests

  • 112 tests, 71% code coverage (up from ~30%)
  • Added TCP relay tests (selector, cleanup, data forwarding)
  • Added UDP relay tests (forwarding, fragmentation, error handling)
  • Added logger thread safety tests
  • Added ProxyConfiguration tests
  • Added DNS timeout tests for both forward and reverse lookups
  • Implemented all TODO test stubs (parse_request, address parsing, RSV validation)
  • Fixed duplicate IPv6 tests, incorrect mock data, and socket leaks in tests

Documentation

  • Rewrote README with CI badges, quick start, Docker usage, auth docs, and testing instructions
  • Updated setup-python action from v3 to v5

Full Changelog: v1.0.0...v2.0.0

v1.0.0

Choose a tag to compare

@j0shcap j0shcap released this 28 Sep 01:17

What's Changed

  • Basic implementation by @JoshCap20 in #1
  • General enhancements by @JoshCap20 in #5
  • Set host, port at runtime by @JoshCap20 in #6
  • Enhance data relay within sockets by @JoshCap20 in #7
  • Improvements to socket data relay logging by @JoshCap20 in #8
  • Implement authentication exchange by @JoshCap20 in #9
  • Code cleanup and refactoring by @JoshCap20 in #11
  • Refactor to data classes, Fix Domain Name Address Handling by @JoshCap20 in #12
  • Documentation update by @JoshCap20 in #13
  • Logging and documentation improvements by @JoshCap20 in #14
  • Organization restructure, add tests by @JoshCap20 in #15
  • Fix authentication failure response, add more tests by @JoshCap20 in #16
  • Documentation by @JoshCap20 in #18
  • Add dockerfile, increase test coverage by @JoshCap20 in #19
  • Refactor for future UDP support by @JoshCap20 in #20
  • Initial UDP implementation by @JoshCap20 in #21
  • Core functionality refactoring by @JoshCap20 in #22
  • Fix client address in logging by @JoshCap20 in #23
  • Better datagram handling for UDP by @JoshCap20 in #24
  • Reorganization utils, Logging enhancements by @JoshCap20 in #25
  • Argument parsing enhancements by @JoshCap20 in #26
  • README update by @JoshCap20 in #29
  • Update Dockerfile to Run as Non-Root User for Enhanced Security by @JoshCap20 in #30
  • Non-blocking I/O for socket relaying by @JoshCap20 in #31
  • Enhances socket cleanup, logger configuration by @JoshCap20 in #32
  • Fix BrokenPipeError in TCP Relay Socket Handling by @JoshCap20 in #33

Full Changelog: https://github.com/JoshCap20/simple-socks5/commits/v1.0.0