test_event opens 256 UDP receivers plus a sender on top of the loop's own
descriptors. macOS starts processes with a soft RLIMIT_NOFILE of 256, so
socket() eventually returned -1 and the unchecked descriptor tripped the
bind() assertion when running ctest from a stock terminal (#3058).
Wrap every registered unit test in a small sh launcher that raises the soft
limit to 1024 before exec'ing the binary, and have test_event raise its own
limit as well so it also passes when run directly. Assert on socket()
failures so a limit problem is reported as such instead of as a bind error.
Fixes#3058
Claude-Session: https://claude.ai/code/session_01PSG4XV3jiqitDgdgFSGATx
Document the shadowsocks-libev history and shadowsocks-c rename, and recommend Docker installation with mounted configuration and TCP/UDP port mappings.
Add native AMD64/ARM64 builds of the unprivileged static runtime image, real container relay and shutdown tests, and GHCR publication after both architecture checks pass. All 24 build/test checks pass at reviewed head 3f3f6bf8e01e5614821d1e15fda3ad8f07812902; publication is intentionally skipped on pull requests.
Rename the project to shadowsocks-c while retaining ss-* commands, the embedding ABI, and legacy library/package lookup compatibility.
Replace libev with bundled libuv for IOCP/kqueue event backends, expand asynchronous runtime DNS coverage, and add actionlint/Ruff with strict clang-tidy failure handling. Validate native platforms, static builds, packaging, interoperability, sanitizers, and canonical/legacy consumers.
All 22 hosted checks pass at reviewed head a8493250eb.
Bundle pinned offline dependencies, remove libcork/libipset and submodule requirements, and add portable runtime helpers with relocatable static/shared library installations.
Add Clang static build validation for Linux/musl, macOS and Windows, real TCP/UDP and SIP003 interoperability coverage, and recorded performance tradeoffs. All 21 hosted checks pass at the reviewed PR head 8d504e6218.
crypto_init() initializes the nonce bloom filter on every call, so a test
that builds a crypto_t per case has to tear it down per case as well.
Freeing only once at the end leaked a pair of filters for every crypto_t
after the first, which the valgrind memcheck job caught.
test_crypto.c already frees it alongside each crypto_t; do the same here
by folding it into the local free_crypto() helper.
shadowsocks-rust takes a whole server definition as a URL; libev could
only be given the pieces separately, so importing a server meant taking a
URL apart by hand. ss-local now accepts --server-url and reads the
address, port, cipher, password and SIP003 plugin from it.
Both encodings are handled: the SIP002 form with the userinfo in
base64url, and the older form where the whole body is one base64 blob.
The userinfo is also accepted in the clear, because that is what
shadowsocks-rust's `ssurl` emits for the 2022 ciphers, whose base64
pre-shared keys it percent-encodes instead. IPv6 literals, a trailing
slash, the plugin query and the '#' tag are all covered.
Options given later on the command line still win, since they are applied
as getopt reaches them.
ssurl.c sits with the ss-local sources rather than the shared ones: it
depends on base64.c, which ss-manager does not link.
The tests include URLs copied verbatim from `ssurl --encode` output, so
the parser is pinned against what another implementation really produces
rather than only against my reading of the spec. Verified end to end by
pointing ss-local at a rust-generated URL and proxying through their
ssserver.
A cipher can round-trip perfectly against our own implementation and
still be wrong: header layout, padding rules, and nonce ordering are only
pinned down by talking to another implementation. This drives ss-local
against rust's ssserver and rust's sslocal against ss-server, over a
local origin server so it needs no network access.
Covers the three SIP022 methods plus aes-256-gcm, so a regression in the
legacy path shows up here too. Skips itself when shadowsocks-rust or the
built binaries are absent, so it is safe to run anywhere.
The buffers were all large enough for a uint16, so none of these could
actually overflow, but sprintf gives the compiler and the linters nothing
to check. Passing the destination size makes that guarantee explicit and
survives any later change to the buffers.
parse_udprelay_header writes through a caller-supplied pointer, so it
uses the MAX_PORT_STR_LEN contract its callers already allocate to.
crypto_parse_key sized a stack array from strlen() of the key given on
the command line or in the config file, so a long enough key string
overflowed the stack before any validation ran. Only the first key_len
bytes were ever used, and base64_decode already stops at the size it is
given, so a fixed MAX_KEY_LENGTH buffer is enough. crypto_parse_psk
needs one byte of slack to tell an over-long key from an exact-size one.
The two "here is a valid key instead" error paths were identical; they
are now one helper, which also drops the last input-sized VLA.
Switch the standard from gnu99 to C11 via CMAKE_C_STANDARD rather than a
hand-written -std flag, so CMake picks the right spelling per compiler.
shadowsocks-rust has supported the 2022 edition for years while libev was
stuck on the 2017 AEAD construction, the largest functional gap between
the two implementations. This adds the three SIP022 methods:
2022-blake3-aes-128-gcm
2022-blake3-aes-256-gcm
2022-blake3-chacha20-poly1305
TCP uses BLAKE3 derive_key for session subkeys, standalone header chunks
carrying a type byte and timestamp, request padding, and a response header
echoing the request salt. Replay protection uses an exact-match salt pool
with 60s retention rather than the bloom filter, which SIP022 forbids
because false positives are unacceptable there.
UDP is session-based: an AES-ECB separate header for the AES methods and a
merged XChaCha20-Poly1305 construction for ChaCha, with a per-session
sliding window replay filter. Servers must route by client session ID,
which is only known after decryption, so the crypto layer owns the
server-side session table and hands the session back to udprelay for the
reply path.
The 2022 code lives in aead2022.c and aead2022_udp.c so aead.c keeps its
current size, with aead_internal.h carrying what they share. BLAKE3 is
vendored (portable backend only) as its own build target.
Keys are base64 pre-shared keys of exactly the cipher's key size; per
SIP022 a password is never stretched into a key.
Verified interoperable in both directions against shadowsocks-rust 1.24.0
for all three ciphers over TCP and UDP, including an 8MB transfer checked
by digest, plus unit tests for header framing, partial delivery, tamper
detection, and UDP replay rejection.
The decode table only mapped the URL-safe alphabet, so '+' and '/' were
rejected as invalid characters. Keys are commonly generated with
`openssl rand -base64`, which emits standard base64, and shadowsocks-rust
accepts either form; a key containing '+' or '/' was refused outright
while the same key worked elsewhere.
Map both alphabets. The affected table entries previously decoded as
"invalid", so no input that used to decode changes meaning.
find_library's search order puts CMAKE_PREFIX_PATH ahead of HINTS, so on
a Homebrew machine with both mbedtls (4.x) and the keg-only mbedtls@3,
the headers resolved to mbedtls@3 while the libraries resolved to 4.x.
That is not a link error but memory corruption at runtime:
mbedtls_cipher_context_t grew between the two versions, so every context
allocated with ss_malloc(sizeof(cipher_evp_t)) from the 3.x headers was
overrun by 4.x's mbedtls_cipher_init. It corrupted the heap in every
binary that sets up a cipher, and made the unit tests segfault inside an
unrelated malloc.
Pick a prefix that provides both the headers and libmbedcrypto and pin
every component to it, then verify at configure time that the header
version string matches what the linked library reports, so a mismatch
fails the build instead of corrupting memory.
Real bugs fixed:
- manager.c: kill_server/stop_server checked fscanf() != EOF, so malformed
pid file content left pid uninitialized and passed it to kill(). Parse
with fgets + ss_parse_int and require pid > 0.
- utils.c: get_default_conf() crashed on strlen(NULL) when HOME is unset
(e.g. daemons started by init) and left a dangling static pointer after
free(), a latent use-after-free on repeated calls. Use a static buffer
and fall back to the system config when HOME is missing.
- local.c: in UDP_ONLY mode, start_ss_local_server() passed uninitialized
listen_ctx.fd to the library callback. Initialize it to -1.
Improvements:
- Replace rand()/srand(time(NULL)) upstream-server selection in ss-local,
ss-redir and ss-tunnel with libsodium randombytes_uniform(): unbiased,
unpredictable, and no seeding required (cert-msc30/msc32).
- Mark FATAL() noreturn so both the compiler and analyzers understand
control flow (removes a family of analyzer false positives).
- Drop two dead stores (manager.c restore_sigchld, udprelay.c src_addr_len).
All remaining findings were verified as false positives and carry NOLINT
comments with rationale (uthash macro internals, symmetric back-pointer
cleanup, analyzer-invisible postconditions). clang-tidy-18 on Linux now
reports zero warnings, so MAX_WARNINGS drops from 29 to 0.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Migrate from autotools to cmake plugin, upgrade base from core18 to
core22, point source to local git repo instead of stale v3.3.5 tarball,
update PCRE3 to PCRE2, and update runtime library packages for Ubuntu
22.04.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Modernize build instructions to focus on CMake (the sole build system),
remove outdated autotools/backports references, and add documentation
for the ss-setup interactive TUI tool and the ss-nat transparent proxy
helper script with practical usage examples.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WITH_STATIC defaults to ON in CMakeLists.txt but CI was explicitly
disabling it. Remove -DWITH_STATIC=OFF so the static targets are
also built and linked, catching issues like the c-ares static
library rename (#3024).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
c-ares >= 1.27 renamed the static library from libcares.a to
libcares_static.a. Search for both names so the static build works
on older distros (libcares.a) and newer ones (libcares_static.a).
Fixes#3024
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The idx-tracking optimization in aead_decrypt skipped memmove when
partial AEAD chunks spanned multiple calls, but never compacted the
dead space before idx. This caused the chunk buffer to grow
proportionally to total data transferred (~10 MB growth per 10 MB
transferred), tripping the stress test memory leak threshold.
Compact residual data to the front when appending new ciphertext,
keeping the buffer bounded to the size of the residual plus new data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add bswap_data() to swap buffer pointers instead of copying, replace
brealloc+memcpy with pointer swaps in aead and stream codecs, add
in-place fast paths for non-Salsa20 stream ciphers, and use index
tracking instead of memmove in aead_decrypt chunked reassembly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>