Commit Graph
2470 Commits
Author SHA1 Message Date
Max Lv ed75faf80f Rewrite the cache on a fixed slab without per-entry allocation
cache.cpp keeps its C API but replaces uthash and the two mallocs per
entry with one slab of nodes holding their keys inline, linked in
last-use order, and an open-addressing index of node ids that hashes
through the slab with a per-cache random seed. Both blocks are reserved
by cache_create() and touched only as entries are used, so a large,
mostly empty cache costs no resident memory.

Semantics are unchanged: an insert evicts the least recently used entry
once the cache holds capacity entries (the AEAD-2022 session table's
4095/4096 boundary), lookups refresh recency and age, cache_clear()
drops stale entries, and free_cb may re-enter the cache. Re-inserting a
key now replaces its data instead of shadowing it. cache_create_keyed()
sizes keys per cache; the AEAD-2022 salt pool and session table use it.

test_cache_cpp covers the boundary, ordering, replacement, ageing,
re-entrant callbacks and limits; alloc_steady_cache requires zero
allocations across 100,000 inserts on a full cache. AEAD-2022 ss-server
drops from 84 to 80 allocations per relayed connection.
2026-10-14 22:53:07 +08:00
Max Lv 110459eba8 Disable the zero-allocation checks with a system Mbed TLS
They hold for the bundled Mbed TLS (MBEDTLS_BLOCK_CIPHER_C); a system
one may allocate in GCM key setup, as the Debian package build shows.
2026-10-14 22:53:06 +08:00
Max Lv ab365b8fff Include C++ headers before crypto.h in test_crypto_aead_cpp.cpp
crypto.h defines min/max macros for the C sources; libstdc++ headers
included after it fail to parse.
2026-10-14 22:51:57 +08:00
Max Lv 0ff1fa6d66 Build the C++ AEAD codecs against Mbed TLS 2.x
Mbed TLS 2 returns void from mbedtls_sha1_starts/update/finish (the int
versions are the _ret variants), which the system-library build on
Ubuntu (2.28) rejects. crypto/mbedtls_compat.hpp gives one int-returning
spelling for both.
2026-10-14 22:51:57 +08:00
Max Lv 3e662faae7 Include <algorithm> for std::min in test_crypto_aead_cpp.cpp 2026-10-14 22:51:57 +08:00
Max Lv 556c1f0696 Add allocation-free C++ AEAD codecs
src/crypto/aead.hpp implements the SIP004/SIP007 AEAD ciphers as value
types: AeadEngine holds the libsodium or Mbed TLS GCM state by value,
AeadTcpEncryptor/AeadTcpDecryptor frame the chunk stream with a fixed
reassembly buffer, and aead_udp_seal/open handle packets. HKDF-SHA1 runs
on stack SHA-1 state instead of Mbed TLS's allocating HMAC API.

The bundled Mbed TLS config now enables MBEDTLS_BLOCK_CIPHER_C, which
keeps the AES context inside mbedtls_gcm_context, so GCM key setup no
longer allocates. That alone saves the C programs two allocations per
connection.

test_crypto_aead_cpp checks HKDF against RFC 5869 and crypto_hkdf, runs
TCP streams and UDP packets between the C and C++ implementations in
both directions for all five AEAD methods, and covers tampering,
framing and salt replay. alloc_steady_crypto_aead runs it under the
malloc counter and requires zero allocations across 600 new
connections and 600 packets (tests/alloc_audit/check_steady.py).

ss_strict_warnings() now applies -Wextra per source file so targets
mixing new C++ with converted C modules keep the latter at -Wall, and
Result gains move assignment.
2026-10-14 22:51:56 +08:00
Max Lv df7a6dc13f Add the C++ event layer over libuv
src/event/loop.hpp wraps uv_loop/poll/timer/signal/async handles that
live by value inside their owner and dispatch to an owner member
function chosen at compile time. A Poll owns its socket and carries
read and write interest on one handle, closing the descriptor in
libuv's close callback. CloseGroup collects the asynchronous closes of
an owner's handles and reports completion afterwards, which is how
pooled owners implement AsyncClosable. DeadlineList drives the idle
timeouts of many owners from one timer.

test_event_cpp covers readiness, closing a peer from inside a
callback, recycling a pooled owner only after its handles closed,
repeating timers, deadline ordering and cross-thread wakeups.
2026-10-14 22:51:38 +08:00
Max Lv b040563874 Add the two casts ss-redir needs as C++
ss-redir only builds on Linux, so the macOS conversion checks never
compiled it: getsockname() wants a struct sockaddr *, and memcpy's
void * result needs a cast.
2026-10-14 22:51:17 +08:00
Max Lv 227ad1bb9c Compile the event loop, UDP relay and programs as C++
Rename utils, ss_event, udprelay, winsock, local, server, tunnel, redir
and manager from .c to .cpp. Inside the moved files only the
\snippet{doc} file references and header comments change; snippet
references in doc/*.md, tests/test_cli_docs.py, the build files and
CONTRIBUTION.md follow the new names.

Also cast the Windows-only string literal returned by
get_default_conf(), found by the zig x86_64-windows-gnu build.

Every module of src/ except the unbuilt android.c and the vendored
blake3 is now compiled as C++20. The new .cpp files join the no-heap
ratchet baseline.
2026-10-14 22:51:17 +08:00
Max Lv 1f259d0fa0 Make the event loop, UDP relay and programs compile as C++
Cast allocator results and void* callback data explicitly, cast string
literals stored in char * variables, replace compound literals in
ss_event.c with zeroing plus member assignment, and give ss-server's
listener array its MAX_REMOTE_NUM bound instead of a VLA.

Where a goto CLEAN_UP jumps over an initialised declaration (C++
rejects that), split the declaration from its initialisation in place.
The variables were already indeterminate at the label in C, and the
cleanup code does not read them. Checked for every MODULE_* variant of
udprelay.c, plus LIB_ONLY for local.c. Still valid C.
2026-10-14 22:51:16 +08:00
Max Lv 349d895fd2 Compile the crypto, config, DNS, ACL and plugin modules as C++
Rename netutils, jconf, stream, aead, aead2022, aead2022_udp, crypto,
acl, plugin, plugin_disabled and resolv from .c to .cpp without changing
their contents, and update source lists, snippet references, test
comments and debian/copyright.

The Doxygen source list and scripts/check_cli_docs.py now accept either
extension for each module, so the remaining programs can move one at a
time. The new .cpp files are added to the no-heap ratchet baseline.
2026-10-14 22:51:16 +08:00
Max Lv 3ae698f5be Raise the test origin's listen backlog
socketserver listens with a backlog of 5. With concurrent connections
through the proxy on a loaded machine (parallel ctest), the backlog
overflows, the kernel resets the origin connection, and the proxy
correctly drops the client, which surfaced as intermittent
alloc_audit_report failures ("getpeername: Invalid argument" in
ss-local, "Connection reset by peer" in ss-server). Reproduced
reliably at 64 concurrent connections; gone with a backlog of 256.
2026-10-14 22:51:16 +08:00
Max Lv 68bfae9b57 Make the crypto, config, DNS and plugin modules compile as C++
Cast allocator and memchr results explicitly, pass mbedtls_operation_t
to mbedtls_cipher_setkey, and declare the two locals in
ss2022_encrypt_first() before the gotos that jump past them. Drop the
unused RESOLV_MODE global that resolv.c defined by accident through an
anonymous enum declaration. Still valid C.
2026-10-14 22:51:16 +08:00
Max Lv a739789775 Keep GCC's C++-only warnings off for modules converted from C
GCC's C++ -Wall includes -Wsign-compare and -Wclass-memaccess, which C's
-Wall does not, so the converted modules failed -Werror on GCC (Linux,
UCRT64) although nothing in them changed. They keep the warning set they
were written against; code under ss_strict_warnings() re-enables both.
2026-10-14 22:51:16 +08:00
Max Lv e16c6bcd3e check_cxx_runtime: ignore glibc symbol versions
nm on glibc prints __cxa_atexit@GLIBC_2.2.5, which the allowlist for the
libc-provided __cxa_atexit/__cxa_finalize did not match.
2026-10-14 22:51:15 +08:00
Max Lv 21c13e8ddc Keep clang-tidy clean for the converted modules
Once the C headers are included from C++ translation units,
bugprone-dynamic-static-initializers reports each extern variable
declaration in them; none is defined in a header, so disable the check.
Mark the two bounded memchr searches in ssurl.cpp, which
bugprone-not-null-terminated-result misreads as string copies.
2026-10-14 22:51:15 +08:00
Max Lv 96ce822c6b Fix the Windows branches of process.cpp for C++
Cast allocator results and the thread argument explicitly and
value-initialise JOBOBJECT_EXTENDED_LIMIT_INFORMATION with {}. Found by
the zig x86_64-windows-gnu cross build.
2026-10-14 22:51:15 +08:00
Max Lv 738956b5da Compile the leaf modules as C++
Rename base64, json, ipset, process, ppbloom, rule, ssurl and cache from
.c to .cpp without touching their contents, and update the CMake source
lists, debian/copyright and the docs.

Ported code keeps -Wall; -Wextra is now opt-in through
ss_strict_warnings() for code written as C++ (the core tests).
check_no_heap.py gains a ratchet baseline for the converted modules,
which still allocate the way the C code did. A new check_cxx_runtime
test verifies with nm that the programs and libshadowsocks-c.a still
need no C++ runtime library, so C consumers are unaffected.
2026-10-14 22:51:15 +08:00
Max Lv 537bc77e79 Make headers and leaf modules compile as C++
Wrap the declarations of every project header in extern "C" (after the
system includes, which must keep C++ linkage), so converted modules and
the remaining C files agree on linkage.

In the leaf modules, cast allocator and memchr results explicitly,
rename identifiers called new, replace the getpwnam_r VLA with the
fixed 16 KiB buffer the loop already capped itself at, and make the
rlimit initialiser conversion explicit. All of this is still valid C.
2026-10-14 22:51:15 +08:00
Max Lv e052bf2e84 CI: let clang-tidy ignore GCC-only warning flags
The compile database comes from a GCC build, whose flags (for example
-Wno-class-memaccess) clang-tidy does not know and reports as errors.
2026-10-14 22:51:04 +08:00
Max Lv af339ccd4d CI: allow running the test and build workflows by hand
Stacked PRs target their parent branch, which these workflows do not
trigger on; workflow_dispatch lets each layer run the full matrix.
2026-10-14 20:11:15 +08:00
Max Lv 18550f5868 Fix CI for the C++ core: libstdc++ headers, static unwinder, newer clang
- docker/static: Alpine's libstdc++ headers come with g++, and the C++
  tests' static libstdc++ needs an unwinder that compiler-rt lacks; add
  g++ and llvm-libunwind.
- FixedBuffer: drop the unused kCapacity (clang-tidy 18 reports it as a
  dynamically initialized header static).
- ss-server on Windows: manager_addr was only read in POSIX-only code,
  which newer clang rejects (-Wunused-but-set-global); say that the
  option is ignored there.
2026-10-14 19:20:21 +08:00
Max Lv 2285528788 Document the C++20 port and allocation baseline
docs/cpp-core.md describes the language profile, the core primitives,
the phase plan and the measured C baseline (77-89 allocations per
connection in ss-server, 27-39 in ss-local). The compatibility contract
now keeps the C11 public header instead of a C11 code base. CLAUDE.md
drops stale libev, submodule and gnu99 references.
2026-10-14 12:51:47 +08:00
Max Lv 641643ec48 Add heap allocation audit for relayed connections
alloc_counter is a preloadable malloc counter (glibc, macOS) that writes
a snapshot on SIGUSR2. measure_alloc.py warms ss-local and ss-server up,
snapshots both, relays more connections and reports allocations per
connection. It runs report-only as the alloc_audit_report ctest.
interop.peers() now yields the started processes.
2026-10-14 12:51:47 +08:00
Max Lv f90cfe9034 Add C++20 ownership and allocation core
Header-only primitives for the no-heap-after-startup design:
Result/Status, Region, ObjectPool with PoolPtr (std::unique_ptr with a
pool deleter), generation-checked Handle and asynchronous close support,
intrusive RefPtr, FixedBuffer, MonotonicArena, FixedHashMap and FixedLru,
IntrusiveList, and a bounded FixedHeap for third-party allocator hooks.

Unit tests cover each primitive (test_core_*). scripts/check_no_heap.py
rejects heap allocation in C++ sources under src/ and runs as a ctest.
clang-tidy now also analyses C++ sources and the core headers.
2026-10-14 12:51:47 +08:00
Max Lv 0799d3a9aa Enable C++20 alongside C11 in the build
Add CXX to the project with C++20 (no extensions) and build C++ sources
with -Wextra -fno-exceptions -fno-rtti -fno-threadsafe-statics on top of
the existing warning flags. Give every toolchain a C++ compiler: the zig
Windows toolchain, the static macOS/Windows CI jobs, the static musl
image (clang++ plus libstdc++ headers) and the RPM spec.
2026-10-14 12:51:47 +08:00
Max LvandClaude 553cdc7945 Add opt-in printable TCP salt experiment for ss-local (#3063)
Add `ss-local --printable-salt` / `"printable_salt": true`, disabled by
default. For each outgoing TCP salt it picks a prefix length uniformly
from 6-12 bytes and samples those bytes as printable ASCII (0x20-0x7e)
immediately before key derivation. The salt stays 32 bytes with no
framing or markers, so the wire format is unchanged and any stock
server interoperates. Only chacha20-ietf-poly1305 and aes-256-gcm are
accepted; other ciphers fail at startup. UDP, server responses, and
AEAD-2022 are untouched.

Crypto unit tests cover length distribution, empty first writes, exact
framing overhead, salt-tail preservation, byte-at-a-time decryption,
tampering and replay rejection. CLI tests check local-only exposure and
cipher rejection via flag, config key and bad config type. interop.py
gains --printable-salt and --server-bin for independent stock servers.

docs/printable-salt.md records the design, entropy bound, fingerprint
caveats and route-experiment procedure; docs/encoding-experiments.md
and tests/experiments/ hold the Base85/lowpop85/TLS outer-transport
prototypes. docs/measurements/printable-salt-2026-09-23/ archives the
sanitized results of the aborted field trials.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-13 09:09:51 +08:00
Max Lv d52ddf2bce Merge pull request #3062 from shadowsocks/feature/server-endpoints
Combine server host and port with strict IPv6 endpoint parsing
2026-09-13 07:23:22 +08:00
Max Lv 454bdc0a67 Accept combined server endpoints with strict IPv6 parsing 2026-09-13 07:15:28 +08:00
Max Lv 243f1f92ec Merge pull request #3061 from shadowsocks/feature/redesigned-cli
Redesign CLI flags, help, and argument validation
2026-09-12 21:25:11 +08:00
Max Lv ea951e6bd1 Redesign CLI flags, help, and argument validation 2026-09-12 21:17:38 +08:00
Max Lv 7bcb3d7dee Merge pull request #3060 from shadowsocks/madeye/cmake-build-test-error-on-macos
Raise the file descriptor limit for descriptor-heavy tests
2026-09-12 15:30:51 +08:00
Max Lv 8773939dce Raise the file descriptor limit for descriptor-heavy tests
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
2026-09-12 15:23:43 +08:00
Max Lv 36a5cc6b8d Merge pull request #3059 from shadowsocks/feature/publish-doxygen-pages
Publish native Doxygen documentation with GitHub Pages
2026-09-12 15:01:38 +08:00
Max Lv 6b11c7a632 Include downloadable man pages in the published documentation 2026-09-12 14:55:13 +08:00
Max Lv f5d5bf0be1 Publish native Doxygen documentation with GitHub Pages 2026-09-12 14:46:42 +08:00
Max Lv b281b739c7 Merge pull request #3057 from shadowsocks/feature/generated-cli-docs
Generate CLI manual sections from source option declarations
2026-09-12 10:57:59 +08:00
Max Lv 146818c182 Generate CLI manual sections from source option declarations 2026-09-12 10:51:25 +08:00
Max Lv 463ff72ca0 Merge pull request #3056 from shadowsocks/docs/add-contribution-guide
docs: add contribution guide
2026-09-12 10:31:55 +08:00
Max Lv 00ea8dec77 docs: add contribution guide 2026-09-12 10:31:30 +08:00
Max Lv dce4aaaa7e Merge pull request #3055 from shadowsocks/docs/add-code-of-conduct
docs: add community code of conduct
2026-09-12 10:19:58 +08:00
Max Lv 0d58d15e99 docs: add community code of conduct 2026-09-12 10:14:24 +08:00
Max Lv bff687bd34 Add Docker publishing, recommended installation and project history (#3052)
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.
2026-09-11 16:22:30 +08:00
Max Lv bdc77408cb Rename to shadowsocks-c with libuv, async DNS and CI lint (#3051)
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.
2026-09-11 14:18:37 +08:00
Max Lv 201e70c4bd Modernize C builds with offline dependencies and portable runtime helpers (#3050)
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.
2026-09-11 13:42:11 +08:00
Max Lv 8fe386b5c2 Merge pull request #3049 from shadowsocks/feature/aead-2022
Add Shadowsocks 2022 (SIP022) ciphers and ss:// URL support
2026-08-20 09:18:27 +08:00
Max Lv fdde2c7cee Release the bloom filter in the AEAD-2022 tests
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.
2026-08-20 09:14:04 +08:00
Max Lv 8cc58cea95 Accept an ss:// server URL on the ss-local command line
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.
2026-08-19 20:15:38 +08:00
Max Lv 3728a993a7 Add an interoperability test against shadowsocks-rust
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.
2026-08-19 18:42:39 +08:00
Max Lv 06bbf3d86c Use snprintf for port formatting
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.
2026-08-19 18:27:19 +08:00