Repository navigation
Releases: j0shcap/simple-socks5
Releases · j0shcap/simple-socks5
Release list
v2.2.0
Upgrading from 2.1 (behaviour changes)
These changes affect you if you override the image's command or healthcheck, or parse its logs:
- Healthcheck command. The image healthcheck runs
python -m simple_socks5.healthcheck. If
you copiedpython -m src.healthcheckinto a compose file or Kubernetes probe, update it. - Default command. The image runs
python -m simple_socks5 --host 0.0.0.0 --port 1080.
Overrides that runpython app.py ...keep working. - Log file logger names.
SOCKS5_LOG_FILElines name loggers[simple_socks5.…]instead of
[src.…]. Console lines are unchanged. - Log levels. ERROR now means a fault in the proxy: client mistakes log at DEBUG and
unreachable destinations at INFO.Authenticated userlogs at DEBUG. - Rate-limited warnings. Handshake-timeout and failed-login warnings are logged at most once
every 10 s. SetLOGGING_LEVEL=debugto see every failure with the client's IP. - CONNECTION lines. A CONNECT logs its
CONNECTIONline only once the destination has
connected, so these lines no longer count failed attempts. - Keep 2.1 entirely. Pin
jcaponigro20/simple-socks5:2.1.0(or
:2.1.0-logging-disabled).
Added
pip install .installs asimple-socks5command, which takes the same flags as
python3 app.py.python -m simple_socks5works too.- The package ships a
py.typedmarker, so type checkers use its annotations. SECURITY.mdsays how to report a vulnerability privately and which versions get fixes;
CONTRIBUTING.mdcovers development and releases.python -m benchmarksmeasures the proxy against direct connections, profiles it and charts the
results, from a checkout.benchmarks/README.mdhas the results and the optimizations they point to.
Changed
- The code is now the
simple_socks5package undersrc/, andpip install .installs it.
python3 app.pykeeps working from a clone and in the image. - Behaviour change: the image healthcheck runs
python -m simple_socks5.healthcheck. If
you copiedpython -m src.healthcheckinto a compose file or Kubernetes probe, update it. - Behaviour change: logger names in
SOCKS5_LOG_FILElines 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.tomlis the only place it's
written.--versionfrom a clone that was never installed prints0+unknown; run
pip install -e .to fix it. - Contributors lint and format with Ruff instead of flake8:
pip install -e '.[dev]', then
ruff check .andruff format .. - Contributors type-check with mypy in strict mode:
pip install -e '.[dev]', thenmypy. - 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, setLOGGING_LEVEL=debug(or-L debug):
every failure is still logged there, with the client's IP. - Behaviour change:
Authenticated userlogs at DEBUG, so at INFO a connection logs only its
CONNECTION and CLOSED lines. - Behaviour change: a CONNECT logs its
CONNECTIONline once the destination has connected,
showing the address it connected to. A CONNECT that fails logs only its reply-code line, so
CONNECTIONlines 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
underpython -Stoo. Before, it crashed withNameError: name 'exit' is not defined. - IPv6 addresses are shown in brackets in the
Server started online and the UDPRELAYdebug
lines, for example[::]:1080instead 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 withSOCKS5_CONNECT_TIMEOUT). Before, only the first
was tried, solocalhost(which usually resolves to::1first) 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 toconnect(), 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
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:
- Image tags.
latestandlogging-disablednow move only on releases, and pushes to
mainpublish:main. Pin:2,:2.1or:2.1.0to choose how far you follow. - Log level. The Docker image logs at
infoinstead ofdebug, and connection log lines
have a new format. Set-e LOGGING_LEVEL=debugto restore the old level. - Loopback and link-local destinations. Proxying to loopback, link-local (including the
cloud metadata service at169.254.169.254) and unspecified addresses is refused with reply
0x02. Set-e SOCKS5_ALLOW_LOOPBACK=trueto restore it. Containers on Docker's default
bridge network are unaffected. - Error log file. The image no longer writes
/app/errors.log. Set
-e SOCKS5_LOG_FILE=/app/errors.logto restore it. - Empty credentials. An empty
SOCKS5_USERNAMEorSOCKS5_PASSWORDcan no longer be used
to log in. There is no opt-out. - 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.Zrelease publishesX.Y.Z,X.YandX(and
X.Y.Z-logging-disabled), so a deployment can pin a version. A pre-release such as
v2.1.0-rc.1publishes 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. SetSOCKS5_AUTH_REQUIRED=falseto acknowledge an
intentional no-auth setup; this logs one INFO line instead. Visible at-L debug,info
andwarning. - Startup warning when the built-in default credentials are in use. The password is never
logged. - README
Securitysection: open-proxy risk, requiring authentication, publishing only on
trusted interfaces, cleartext credentials, and pinning:2.0.0for the pre-2.1 behaviour. SOCKS5_HANDSHAKE_TIMEOUTandSOCKS5_CONNECT_TIMEOUT(seconds, default10). An invalid
value exits at startup with an error.SOCKS5_MAX_CONNECTIONS(default200): 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(default1080): the port the Docker healthcheck probes. Set it
when you run the image with a different--port.SOCKS5_ALLOW_LOOPBACK(defaultfalse): set totrueto allow destinations on loopback,
link-local and unspecified addresses, which are now refused by default.
Changed
latestandlogging-disablednow move only on stable releases; pushes tomainpublish
:mainonly. 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 setsPYTHONUNBUFFERED=1andPYTHONDONTWRITEBYTECODE=1. - Behaviour change: the Docker image now logs at
infoby default instead ofdebug, so it
no longer logs every relayed chunk. Set-e LOGGING_LEVEL=debugto restore the old output.
This also applies when you override the container command without-L. - The Docker image runs
pythondirectly as PID 1 (exec-formCMD), so it receives
docker stop's SIGTERM. app.pyreads theLOGGING_LEVELenvironment variable when-L/--logging-levelisn't
given. Precedence is-L, thenLOGGING_LEVEL, thendebug; 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
logsCONNECTION | client -> destinationwhen 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
debugwithout 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.login the working directory
(/app/errors.login the Docker image). SetSOCKS5_LOG_FILEto keep a log file. - The Docker
HEALTHCHECKrunspython -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 stoppreviously waited 10 seconds and killed the container (exit
code 137). - README: the default port is
1080, not9999; removed the claim of configurable
connection limits; the authentication example now setsSOCKS5_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 withinSOCKS5_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 replied0x01. It now gives up after
SOCKS5_CONNECT_TIMEOUTseconds and replies0x04(host unreachable). - Accurate SOCKS reply codes (RFC 1928): network unreachable replies
0x03, host unreachable
and connect timeouts0x04, an unknown address type0x08, and a domain name that isn't
valid UTF-80x04. These all replied0x01before. - 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
CLOSEDline 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 abovedebugfor 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-Lapplies: ERROR with its
traceback, ordebugfor 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 emptySOCKS5_USERNAMEorSOCKS5_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_USERNAMEandSOCKS5_PASSWORDare 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 at169.254.169.254, and
fe80::/10) and unspecified (0.0.0.0/8,::) addresses is refuse...
v2.0.0
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 returningFalsedue 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_exacthelper (previously assumed all bytes arrived in onerecv) - Fixed server shutdown order (
shutdown()beforeserver_close()) - Fixed
raise elosing tracebacks in TCP relay (now bareraise) - Fixed duplicate log handlers when
update_loggers()is called - Fixed
proxy_addressAttributeError (now initialized toNone) - Fixed DNS thread leaks (replaced
ThreadPoolExecutorwith 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()withlogger.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_PASSWORDenvironment 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_LEVELbuild arg - Added
HEALTHCHECKto Dockerfile - Added
.dockerignore - Added
pyproject.tomlwith project metadata - Added flake8 linting to CI pipeline
- Added
pytest-covwith 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
ProxyConfigurationtests - 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-pythonaction from v3 to v5
Full Changelog: v1.0.0...v2.0.0
v1.0.0
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