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.
This commit is contained in:
Max Lv authored and GitHub committed 2026-09-11 13:42:11 +08:00
1 parent 8fe386b5c2
commit 201e70c4bd
120 files changed
+4631 -1523

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
# clang-tidy configuration for shadowsocks-libev project sources (src/ only).
# Bundled submodules (libcork/, libipset/, libbloom/) are third-party code and
# Bundled sources under third_party/ and src/blake3/ are third-party code and
# are excluded by the file regex used in CI:
# run-clang-tidy -p build '/src/[^/]+\.c$'
#
+2 -6
View File
@@ -15,8 +15,6 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
@@ -31,7 +29,7 @@ jobs:
- name: Build
run: |
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake .. -DCMAKE_BUILD_TYPE=Release -DSS_DEPENDENCY_MODE=system -DWITH_STATIC=OFF
jobs="$(nproc 2>/dev/null || sysctl -n hw.ncpu)"
make -j"$jobs"
@@ -39,15 +37,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpcre2-dev libmbedtls-dev libsodium-dev libev-dev libc-ares-dev
sudo apt-get install -y --no-install-recommends \
build-essential debhelper dpkg-dev fakeroot asciidoc-base xmlto pkg-config
build-essential cmake debhelper dpkg-dev fakeroot asciidoc-base xmlto pkg-config
- name: Debian package build test
run: bash tests/test_deb_build.sh
+199
View File
@@ -0,0 +1,199 @@
name: portability
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
jobs:
bundled:
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-latest]
minimal: ['OFF', 'ON']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Build without installed third-party development packages
run: |
cmake -S . -B build -DSS_MINIMAL=${{ matrix.minimal }}
cmake --build build --parallel 4
ctest --test-dir build -L 'unit|vendor' --output-on-failure --no-tests=error
- name: Real TCP and UDP relay
run: python3 tests/interop.py --self --bin build/bin
- name: SIP003 transport and child cleanup
if: matrix.minimal == 'OFF'
run: python3 tests/interop.py --self --bin build/bin --plugin tests/sip003_fixture.py
- name: Relocated installation and public API consumers
run: |
cmake --install build --prefix "$PWD/install"
mv install relocated
cmake -S tests/consumer -B consumer -DCMAKE_PREFIX_PATH="$PWD/relocated"
cmake --build consumer
./consumer/consumer_static
./consumer/consumer_shared
windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v6
- uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
install: >-
mingw-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-cmake
mingw-w64-ucrt-x86_64-ninja
mingw-w64-ucrt-x86_64-python
- name: Build and run native Windows binaries
run: |
cmake -S . -B build -G Ninja
cmake --build build --parallel 4
ctest --test-dir build -L 'unit|vendor' --output-on-failure --no-tests=error
objdump -p build/bin/ss-server.exe | sed -n '/DLL Name/p'
python tests/interop.py --self --bin build/bin --isolate-windows-runtime
- name: Installed consumers
run: |
cmake --install build --prefix "$PWD/install"
cmake -S tests/consumer -B consumer -G Ninja -DCMAKE_PREFIX_PATH="$PWD/install"
cmake --build consumer
- name: Run installed consumers without toolchain DLLs
shell: pwsh
run: |
$env:PATH = "$env:GITHUB_WORKSPACE\install\bin;$env:SystemRoot\System32;$env:SystemRoot"
& ./consumer/consumer_static.exe
if ($LASTEXITCODE -ne 0) { throw "Static consumer failed: $LASTEXITCODE" }
& ./consumer/consumer_shared.exe
if ($LASTEXITCODE -ne 0) { throw "Shared consumer failed: $LASTEXITCODE" }
static-macos:
runs-on: macos-latest
env:
CC: clang
steps:
- uses: actions/checkout@v6
- name: Build Clang static-only installation
run: |
cmake -S . -B build-static -DCMAKE_C_COMPILER=clang -DSS_DEPENDENCY_MODE=bundled -DWITH_STATIC=ON -DSS_BUILD_STATIC_LIBRARY=ON -DSS_BUILD_SHARED_LIBRARY=OFF
cmake --build build-static --parallel 4
ctest --test-dir build-static -L 'unit|vendor' --output-on-failure --no-tests=error
cmake --install build-static --prefix "$PWD/install-static"
mv install-static relocated-static
cmake -S tests/consumer -B consumer-static -DCMAKE_C_COMPILER=clang -DCMAKE_PREFIX_PATH="$PWD/relocated-static"
cmake --build consumer-static
test ! -e consumer-static/consumer_shared
./consumer-static/consumer_static
- name: Reject third-party dynamic libraries
run: python3 tests/check_static_runtime.py relocated-static/bin/ss-* consumer-static/consumer_static
- name: Exercise installed static programs
run: |
python3 tests/interop.py --self --bin relocated-static/bin
python3 tests/interop.py --self --bin relocated-static/bin --plugin tests/sip003_fixture.py
- uses: actions/upload-artifact@v4
with:
name: static-macos-${{ runner.arch }}
path: relocated-static/
if-no-files-found: error
static-windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v6
- uses: msys2/setup-msys2@v2
with:
msystem: CLANG64
update: true
install: >-
mingw-w64-clang-x86_64-clang
mingw-w64-clang-x86_64-llvm
mingw-w64-clang-x86_64-cmake
mingw-w64-clang-x86_64-ninja
mingw-w64-clang-x86_64-python
- name: Build Clang static-only installation
run: |
cmake -S . -B build-static -G Ninja -DCMAKE_C_COMPILER=clang -DSS_DEPENDENCY_MODE=bundled -DWITH_STATIC=ON -DSS_BUILD_STATIC_LIBRARY=ON -DSS_BUILD_SHARED_LIBRARY=OFF
cmake --build build-static --parallel 4
ctest --test-dir build-static -L 'unit|vendor' --output-on-failure --no-tests=error
cmake --install build-static --prefix "$PWD/install-static"
mv install-static relocated-static
cmake -S tests/consumer -B consumer-static -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_PREFIX_PATH="$PWD/relocated-static"
cmake --build consumer-static
test ! -e consumer-static/consumer_shared.exe
- name: Reject third-party DLL imports, including delay imports
run: python tests/check_static_runtime.py relocated-static/bin/ss-*.exe consumer-static/consumer_static.exe
- name: Exercise installed programs without toolchain DLLs
run: python tests/interop.py --self --bin relocated-static/bin --isolate-windows-runtime
- name: Run static consumer with only Windows system directories on PATH
shell: pwsh
run: |
$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot"
& ./consumer-static/consumer_static.exe
if ($LASTEXITCODE -ne 0) { throw "Static consumer failed: $LASTEXITCODE" }
- uses: actions/upload-artifact@v4
with:
name: static-windows-${{ runner.arch }}
path: relocated-static/
if-no-files-found: error
static-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build and test fully static Clang/musl executables
run: docker build -f docker/static/Dockerfile --target artifacts --output type=local,dest=static-linux .
- uses: actions/upload-artifact@v4
with:
name: static-linux-${{ runner.arch }}
path: static-linux/
if-no-files-found: error
freebsd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: vmactions/freebsd-vm@v1
with:
release: '14.3'
usesh: true
prepare: pkg install -y cmake ninja python3
run: |
cmake -S . -B build -G Ninja
cmake --build build --parallel 4
ctest --test-dir build -L 'unit|vendor' --output-on-failure --no-tests=error
python3 tests/interop.py --self --bin build/bin
cmake --install build --prefix "$PWD/install"
cmake -S tests/consumer -B consumer -G Ninja -DCMAKE_PREFIX_PATH="$PWD/install"
cmake --build consumer
./consumer/consumer_static
./consumer/consumer_shared
offline-musl:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Source archive
run: python3 scripts/source_archive.py release/shadowsocks-libev.tar.gz
- name: Prepare compiler image
run: |
printf 'FROM alpine:3.24\nRUN apk add --no-cache build-base cmake python3 linux-headers\n' > /tmp/ss-toolchain.Dockerfile
docker build -t ss-toolchain -f /tmp/ss-toolchain.Dockerfile /tmp
- name: Build release archive with network disabled
run: |
docker run --rm --network none -v "$PWD/release:/release:ro" ss-toolchain sh -ec '
mkdir /src
tar xzf /release/shadowsocks-libev.tar.gz -C /src --strip-components=1
cmake -S /src -B /build -DCMAKE_DISABLE_FIND_PACKAGE_Python3=TRUE
cmake --build /build --parallel 4
ctest --test-dir /build -L "unit|vendor" --output-on-failure --no-tests=error
python3 /src/tests/interop.py --self --bin /build/bin
'
+21 -13
View File
@@ -15,8 +15,6 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
@@ -47,21 +45,19 @@ jobs:
- name: Stress test
run: |
python3 tests/stress_test.py --bin build/shared/bin/ --size 10
python3 tests/stress_test.py --bin build/bin/ --size 10
- name: ss-redir transparent proxy test (QEMU)
if: runner.os == 'Linux'
run: |
sudo apt-get install -y qemu-system-x86
bash tests/test_redir_qemu.sh build/shared/bin/
bash tests/test_redir_qemu.sh build/bin/
timeout-minutes: 8
asan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install dependencies
run: |
@@ -78,7 +74,7 @@ jobs:
run: ctest --test-dir build -LE memcheck --output-on-failure --no-tests=error
- name: Stress test (ASan + UBSan)
run: python3 tests/stress_test.py --bin build/shared/bin/ --size 10
run: python3 tests/stress_test.py --bin build/bin/ --size 10
clang-tidy:
runs-on: ubuntu-latest
@@ -91,8 +87,6 @@ jobs:
MAX_WARNINGS: 0
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install dependencies
run: |
@@ -104,7 +98,7 @@ jobs:
- name: Run clang-tidy on project sources
run: |
run-clang-tidy-18 -quiet -p build '/src/[^/]+\.c$' 2>/dev/null | tee tidy.log || true
run-clang-tidy-18 -quiet -p build "$PWD/src/[^/]+\.c$" 2>/dev/null | tee tidy.log || true
grep 'warning:' tidy.log | sort -u > warnings.txt || true
count="$(wc -l < warnings.txt)"
echo "clang-tidy warnings: $count (max allowed: $MAX_WARNINGS)"
@@ -128,8 +122,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install dependencies
run: |
@@ -145,7 +137,7 @@ jobs:
- name: Run tests
run: |
ctest --test-dir build -LE memcheck --output-on-failure --no-tests=error
python3 tests/stress_test.py --bin build/shared/bin/ --size 10
python3 tests/stress_test.py --bin build/bin/ --size 10
- name: Generate coverage report
run: |
@@ -162,3 +154,19 @@ jobs:
with:
name: coverage-html
path: build/coverage-html/
interop:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- name: Install dependencies and independent peer
run: brew install mbedtls@3 libsodium libev c-ares pcre2 shadowsocks-rust
- name: Build
run: |
cmake -S . -B build -DSS_DEPENDENCY_MODE=system -DWITH_STATIC=OFF
cmake --build build --parallel
- name: Required interoperability
env:
SS_REQUIRE_INTEROP: '1'
SS_BIN_DIR: build/bin
run: bash tests/test_interop_rust.sh
+12
View File
@@ -102,3 +102,15 @@ doc/*.html
# CMake objection
obj-*
# Pinned source archives are part of offline release builds.
!third_party/dist/*.tar.gz
!third_party/dist/*.tar.bz2
# Former dependency checkouts retained locally during migration.
/libipset/
/libbloom/
/libcork/
# Python integration-test bytecode.
__pycache__/
-12
View File
@@ -1,12 +0,0 @@
[submodule "libcork"]
path = libcork
url = https://github.com/shadowsocks/libcork.git
ignore = dirty
[submodule "libipset"]
path = libipset
url = https://github.com/shadowsocks/ipset.git
ignore = dirty
[submodule "libbloom"]
path = libbloom
url = https://github.com/shadowsocks/libbloom.git
ignore = dirty
+38 -127
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)
set(PROJECT_NAME shadowsocks-libev)
set(RELEASE_DATE 2026-02-09)
@@ -6,9 +6,13 @@ set(PROJECT_VERSION "3.3.6")
set(PROJECT_DESC "a lightweight secured socks5 proxy")
set(PROJECT_URL "https://shadowsocks.org")
set(PROJECT_ISSUES_URL "https://github.com/shadowsocks/shadowsocks-libev")
project(${PROJECT_NAME} VERSION ${PROJECT_VERSION})
project(${PROJECT_NAME} VERSION ${PROJECT_VERSION} LANGUAGES C)
if(MSVC)
message(FATAL_ERROR "MSVC is not supported yet. Use MinGW-w64 (UCRT64) or the Zig Windows toolchain; see docs/modernization.md.")
endif()
include(GNUInstallDirs)
include(CTest)
# Compilation database for clang-tidy and other tooling
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -19,63 +23,48 @@ set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS ON)
# Note: -Werror is applied per-target in src/CMakeLists.txt to avoid
# breaking bundled submodules (libcork, libipset, libbloom)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_GNU_SOURCE")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O2 -Wall -Wno-deprecated-declarations -fno-strict-aliasing")
option(ENABLE_SANITIZERS "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF)
if (ENABLE_SANITIZERS)
set(SANITIZER_FLAGS "-fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${SANITIZER_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${SANITIZER_FLAGS}")
message(STATUS "Sanitizers enabled (ASan + UBSan)")
endif ()
option(ENABLE_COVERAGE "Build with coverage instrumentation (gcov)" OFF)
if (ENABLE_COVERAGE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 --coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage")
message(STATUS "Coverage instrumentation enabled")
endif ()
option(ENABLE_COVERAGE "Build with coverage instrumentation" OFF)
option(SS_WARNINGS_AS_ERRORS "Treat project warnings as errors" ON)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
set(RUNTIME_SHARED_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/shared/bin)
set(CMAKE_MACOSX_RPATH TRUE)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build configuration" FORCE)
endif ()
# Detect linux
if (UNIX AND NOT APPLE)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LINUX TRUE)
endif ()
message(STATUS "Running cmake version ${CMAKE_VERSION}")
option(WITH_STATIC "build with static libraries." ON)
option(WITH_EMBEDDED_SRC "build with embedded libcork, libipset, and libbloom source." ON)
option(WITH_STATIC "Link executable dependencies statically" ON)
option(SS_BUILD_EXECUTABLES "Build command-line programs" ON)
option(SS_BUILD_STATIC_LIBRARY "Build the static embedding library" ON)
option(SS_BUILD_SHARED_LIBRARY "Build the shared embedding library" ON)
option(SS_INSTALL_TOOLS "Install platform shell tools" OFF)
option(ENABLE_CONNMARKTOS "Enable saved connmark to IP TOS QoS feature" OFF)
option(ENABLE_NFTABLES "Report malicious IP to nftables" OFF)
# When choose to not use embedded libcork, libipset and libbloom, use libs shipped by system
if (NOT WITH_EMBEDDED_SRC)
set(USE_SYSTEM_SHARED_LIB TRUE)
endif ()
# Find dependencies via Find modules
find_package(PCRE2 REQUIRED)
find_package(MbedTLS REQUIRED)
find_package(Sodium REQUIRED)
find_package(Cares REQUIRED)
option(SS_MINIMAL "Disable regex, plugins, manager and legacy stream ciphers" OFF)
option(SS_ENABLE_REGEX "Enable PCRE2 ACL patterns" ON)
option(SS_ENABLE_PLUGINS "Enable SIP003 and obfsproxy subprocesses" ON)
option(SS_ENABLE_LEGACY "Enable legacy stream ciphers" ON)
if(SS_MINIMAL)
set(SS_ENABLE_REGEX OFF)
set(SS_ENABLE_PLUGINS OFF)
set(SS_ENABLE_LEGACY OFF)
set(WITH_SS_MANAGER OFF)
endif()
include(cmake/Dependencies.cmake)
# Connmarktos support
if(ENABLE_CONNMARKTOS)
@@ -108,100 +97,20 @@ endif()
# Run platform tests
include(${PROJECT_SOURCE_DIR}/cmake/configure.cmake)
configure_file(${PROJECT_SOURCE_DIR}/cmake/config.h.cmake ${PROJECT_BINARY_DIR}/src/config.h)
add_definitions(-I${PROJECT_BINARY_DIR}/src)
add_definitions(-DHAVE_CONFIG_H)
# pkg-config
configure_file(
${PROJECT_SOURCE_DIR}/cmake/shadowsocks-libev.pc.cmake
${PROJECT_BINARY_DIR}/pkgconfig/shadowsocks-libev.pc
@ONLY
)
install(FILES
${PROJECT_BINARY_DIR}/pkgconfig/shadowsocks-libev.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
if (WITH_EMBEDDED_SRC)
# We need libcork,libipset headers
include_directories(libcork/include)
include_directories(libipset/include)
include_directories(libbloom/murmur2)
include_directories(libbloom)
set(LIBCORK_SOURCE
libcork/src/libcork/cli/commands.c
libcork/src/libcork/core/allocator.c
libcork/src/libcork/core/error.c
libcork/src/libcork/core/gc.c
libcork/src/libcork/core/hash.c
libcork/src/libcork/core/ip-address.c
libcork/src/libcork/core/mempool.c
libcork/src/libcork/core/timestamp.c
libcork/src/libcork/core/u128.c
libcork/src/libcork/core/version.c
libcork/src/libcork/ds/array.c
libcork/src/libcork/ds/bitset.c
libcork/src/libcork/ds/buffer.c
libcork/src/libcork/ds/dllist.c
libcork/src/libcork/ds/file-stream.c
libcork/src/libcork/ds/hash-table.c
libcork/src/libcork/ds/managed-buffer.c
libcork/src/libcork/ds/ring-buffer.c
libcork/src/libcork/ds/slice.c
libcork/src/libcork/posix/directory-walker.c
libcork/src/libcork/posix/env.c
libcork/src/libcork/posix/exec.c
libcork/src/libcork/posix/files.c
libcork/src/libcork/posix/process.c
libcork/src/libcork/pthreads/thread.c
)
if (NOT MINGW)
set(LIBCORK_SOURCE ${LIBCORK_SOURCE} libcork/src/libcork/posix/subprocess.c)
else ()
set(LIBCORK_SOURCE ${LIBCORK_SOURCE} libcork/src/libcork/posix/mingw.c)
endif ()
add_library(cork STATIC ${LIBCORK_SOURCE})
target_compile_definitions(cork PUBLIC -DCORK_API=CORK_LOCAL)
if (MINGW)
target_link_libraries(cork ws2_32)
endif ()
set(LIBIPSET_SOURCE
libipset/src/libipset/general.c
libipset/src/libipset/bdd/assignments.c
libipset/src/libipset/bdd/basics.c
libipset/src/libipset/bdd/bdd-iterator.c
libipset/src/libipset/bdd/expanded.c
libipset/src/libipset/bdd/reachable.c
libipset/src/libipset/bdd/read.c
libipset/src/libipset/bdd/write.c
libipset/src/libipset/map/allocation.c
libipset/src/libipset/map/inspection.c
libipset/src/libipset/map/ipv4_map.c
libipset/src/libipset/map/ipv6_map.c
libipset/src/libipset/map/storage.c
libipset/src/libipset/set/allocation.c
libipset/src/libipset/set/inspection.c
libipset/src/libipset/set/ipv4_set.c
libipset/src/libipset/set/ipv6_set.c
libipset/src/libipset/set/iterator.c
libipset/src/libipset/set/storage.c
)
add_library(ipset STATIC ${LIBIPSET_SOURCE})
include(cmake/ProjectOptions.cmake)
include_directories(${PROJECT_BINARY_DIR}/src)
add_compile_definitions(HAVE_CONFIG_H)
set(LIBBLOOM_SOURCE
libbloom/bloom.c
libbloom/murmur2/MurmurHash2.c
third_party/bloom/bloom.c
third_party/bloom/murmur2/MurmurHash2.c
)
add_library(bloom STATIC ${LIBBLOOM_SOURCE})
target_link_libraries(ipset cork bloom)
endif ()
target_include_directories(bloom PUBLIC third_party/bloom third_party/bloom/murmur2)
add_subdirectory(src)
include(cmake/InstallPackage.cmake)
add_subdirectory(doc)
# Testing
@@ -234,9 +143,11 @@ if (ENABLE_COVERAGE)
endif ()
# Install ss-nat on Linux
if(LINUX)
if(LINUX AND SS_INSTALL_TOOLS)
install(PROGRAMS src/ss-nat DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
# Install ss-setup TUI tool
install(PROGRAMS scripts/ss-setup.sh DESTINATION ${CMAKE_INSTALL_BINDIR} RENAME ss-setup)
if(UNIX AND SS_INSTALL_TOOLS)
install(PROGRAMS scripts/ss-setup.sh DESTINATION ${CMAKE_INSTALL_BINDIR} RENAME ss-setup)
endif()
+143
View File
@@ -0,0 +1,143 @@
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 20,
"patch": 0
},
"configurePresets": [
{
"name": "system",
"displayName": "System dependencies",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-system",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"WITH_STATIC": "OFF",
"WITH_DOC_MAN": "OFF",
"WITH_DOC_HTML": "OFF",
"SS_DEPENDENCY_MODE": "system"
}
},
{
"name": "system-static",
"inherits": "system",
"binaryDir": "${sourceDir}/build-system-static",
"cacheVariables": {
"WITH_STATIC": "ON",
"SS_BUILD_SHARED_LIBRARY": "OFF"
}
},
{
"name": "asan",
"inherits": "system",
"binaryDir": "${sourceDir}/build-asan",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"ENABLE_SANITIZERS": "ON"
}
},
{
"name": "bundled",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-bundled",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"SS_DEPENDENCY_MODE": "bundled",
"WITH_STATIC": "ON"
}
},
{
"name": "minimal",
"inherits": "bundled",
"binaryDir": "${sourceDir}/build-minimal",
"cacheVariables": {
"SS_MINIMAL": "ON"
}
}
],
"buildPresets": [
{
"name": "system",
"configurePreset": "system"
},
{
"name": "system-static",
"configurePreset": "system-static"
},
{
"name": "asan",
"configurePreset": "asan"
},
{
"name": "bundled",
"configurePreset": "bundled"
},
{
"name": "minimal",
"configurePreset": "minimal"
}
],
"testPresets": [
{
"name": "system",
"configurePreset": "system",
"output": {
"outputOnFailure": true
},
"filter": {
"include": {
"label": "unit"
}
}
},
{
"name": "system-static",
"configurePreset": "system-static",
"output": {
"outputOnFailure": true
},
"filter": {
"include": {
"label": "unit"
}
}
},
{
"name": "asan",
"configurePreset": "asan",
"output": {
"outputOnFailure": true
},
"filter": {
"include": {
"label": "unit"
}
}
},
{
"name": "bundled",
"configurePreset": "bundled",
"output": {
"outputOnFailure": true
},
"filter": {
"include": {
"label": "unit|vendor"
}
}
},
{
"name": "minimal",
"configurePreset": "minimal",
"output": {
"outputOnFailure": true
},
"filter": {
"include": {
"label": "unit|vendor"
}
}
}
]
}
+82 -122
View File
@@ -63,7 +63,7 @@ sudo snap install shadowsocks-libev --edge
+ [Run](#run)
+ [Run as client](#run-as-client)
- [OpenWRT](#openwrt)
- [OS X](#os-x)
- [macOS](#macos)
- [Windows (MinGW)](#windows-mingw)
- [Docker](#docker)
@@ -71,55 +71,58 @@ sudo snap install shadowsocks-libev --edge
### Build from source (CMake)
shadowsocks-libev uses CMake as its sole build system. Start by pulling submodules:
The default build uses pinned sources included in this repository. It needs a
C11 compiler, CMake 3.20+, and Make or Ninja. No Git submodules, dependency
package installations, or network access are needed for configuration/build.
Python is used only by integration tests; documentation generation is optional.
```bash
git submodule update --init --recursive
```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build -L 'unit|vendor' --output-on-failure
cmake --install build --prefix /your/install/prefix
```
Then build:
Programs are in `build/bin/`. Bundled binaries link to platform runtime
libraries; they do not require separately installed third-party libraries.
```bash
mkdir -p build && cd build
cmake ..
make
sudo make install
```
For a smaller build, use `-DSS_MINIMAL=ON`. It excludes PCRE2 regex, plugin
subprocesses, the manager, and legacy stream ciphers. Minimal ACLs support
IPv4/IPv6 CIDRs, `full:example.com` for exact domains, and
`suffix:example.com` for the apex and subdomains. Literal matches ignore ASCII
case and respect label boundaries. Unsupported regex rules are rejected.
To run unit tests:
Distribution packages can use `-DSS_DEPENDENCY_MODE=system -DWITH_STATIC=OFF`
with libev, c-ares, libsodium, Mbed TLS 3.x, and PCRE2 development packages.
Use `-DCMAKE_PREFIX_PATH=/opt/homebrew/opt/mbedtls@3` when needed on macOS.
```bash
cd build
ctest --output-on-failure
```
#### CMake options
For a complete list of available options, run `cmake -LH` from a build directory.
Commonly used options:
| Option | Default | Description |
| Option | Default | Purpose |
|---|---|---|
| `-DWITH_EMBEDDED_SRC=OFF` | `ON` | Use system libcork/libipset/libbloom instead of bundled submodules |
| `-DWITH_DOC_MAN=OFF` | `ON` | Skip man page generation (removes asciidoc/xmlto dependency) |
| `-DBUILD_TESTING=OFF` | `ON` | Disable unit tests |
| `-DENABLE_CONNMARKTOS=ON` | `OFF` | Linux netfilter conntrack QoS support |
| `-DENABLE_NFTABLES=ON` | `OFF` | nftables firewall integration |
| `SS_DEPENDENCY_MODE` | `bundled` | `bundled` sources or `system` libraries |
| `WITH_STATIC` | `ON` | Link dependency archives; system mode also supports shared dependencies |
| `SS_BUILD_EXECUTABLES` | `ON` | Command-line tools |
| `SS_BUILD_STATIC_LIBRARY` | `ON` | Static embedding library with installed dependency archives |
| `SS_BUILD_SHARED_LIBRARY` | `ON` | Shared embedding library |
| `SS_MINIMAL` | `OFF` | Disable regex, plugins, manager, and legacy stream ciphers |
| `SS_ENABLE_REGEX` / `SS_ENABLE_PLUGINS` / `SS_ENABLE_LEGACY` | `ON` | Individual compatibility features |
| `WITH_DOC_MAN` / `WITH_DOC_HTML` | `OFF` | Generate documentation (requires asciidoc; man pages also need xmlto) |
| `SS_INSTALL_TOOLS` | `OFF` | Install platform shell helpers |
| `ENABLE_SANITIZERS` | `OFF` | AddressSanitizer and UndefinedBehaviorSanitizer |
| `ENABLE_CONNMARKTOS` / `ENABLE_NFTABLES` | `OFF` | Optional Linux firewall integrations |
On macOS, if libraries are installed via Homebrew, specify paths:
```bash
cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/mbedtls;/usr/local/opt/libsodium"
```
CMake consumers can use `find_package(shadowsocks-libev CONFIG REQUIRED)` and
link `shadowsocks::static`, `shadowsocks::shared`, or `shadowsocks::shadowsocks`
(which prefers the shared library when installed). A pkg-config file is also
installed. Dependency provenance and update instructions are in
[third_party/README.md](third_party/README.md); modernization progress and
validation limits are tracked in [docs/modernization.md](docs/modernization.md).
### Debian & Ubuntu
#### Install from repository (not recommended)
#### Distribution packages
Shadowsocks-libev is available in the official repository for following distributions:
* Debian 8 or higher, including oldoldstable (jessie), old stable (stretch), stable (buster), testing (bullseye) and unstable (sid)
* Ubuntu 16.10 or higher
Package availability and versions depend on the distribution release; packaged
versions can differ from this source branch.
```bash
sudo apt update
@@ -128,17 +131,15 @@ sudo apt install shadowsocks-libev
#### Build deb package from source
You can build shadowsocks-libev and all its dependencies by script:
Install the build dependencies listed in `debian/control`, then build the
packages from this checkout:
```bash
mkdir -p ~/build-area/
cp ./scripts/build_deb.sh ~/build-area/
cd ~/build-area
./build_deb.sh
dpkg-buildpackage -b -us -uc
```
For older systems, building `.deb` packages is not supported.
Please try to build and install directly from source. See the [Linux](#linux) section below.
Debian packaging explicitly uses system libraries. For a bundled build without
library development packages, use the [CMake instructions](#build-from-source-cmake).
#### Configure and start the service
@@ -156,19 +157,10 @@ sudo systemctl start shadowsocks-libev # for systemd
### Fedora & RHEL
Supported distributions:
* Recent Fedora versions (until EOL)
* RHEL 6, 7 and derivatives (including CentOS, Scientific Linux)
#### Build from source with centos
If you are using CentOS 7, you need to install these prerequirements to build from source code:
```bash
yum install epel-release -y
yum install gcc gettext autoconf libtool automake make pcre-devel asciidoc xmlto c-ares-devel libev-devel libsodium-devel mbedtls-devel -y
```
Use the bundled CMake build above with a C11 compiler, CMake 3.20+ and Make
or Ninja. Older distribution toolchains may need upgrading. Autotools, gettext
and separately installed crypto/event/DNS development libraries are not required
for bundled mode.
### Archlinux & Manjaro
@@ -193,47 +185,20 @@ nix-env -iA nixpkgs.shadowsocks-libev
### Linux
In general, you need the following build dependencies:
* cmake (>= 3.2)
* a C compiler (gcc or clang)
* pkg-config
* libmbedtls
* libsodium (>= 1.0.4)
* libpcre2
* libev
* libc-ares
* asciidoc (for documentation only)
* xmlto (for documentation only)
If your system is too old to provide libmbedtls and libsodium (>= 1.0.4), you will need to either install those libraries manually or upgrade your system.
Install build dependencies for your distribution:
The default bundled build needs only a C11 compiler, CMake 3.20+ and Make
or Ninja. For example, on Debian/Ubuntu:
```bash
# Debian / Ubuntu
sudo apt-get install --no-install-recommends build-essential cmake pkg-config \
libpcre2-dev libev-dev libc-ares-dev libmbedtls-dev libsodium-dev \
asciidoc xmlto
# CentOS / Fedora / RHEL
sudo yum install gcc cmake make pkg-config pcre2-devel c-ares-devel \
libev-devel libsodium-devel mbedtls-devel asciidoc xmlto
# Arch
sudo pacman -S gcc cmake make pkg-config pcre2 c-ares libev libsodium mbedtls \
asciidoc xmlto
sudo apt-get install --no-install-recommends build-essential cmake
cmake -S . -B build
cmake --build build --parallel
ctest --test-dir build -L 'unit|vendor' --output-on-failure
sudo cmake --install build
```
Then build and install:
```bash
git submodule update --init --recursive
mkdir -p build && cd build
cmake ..
make
sudo make install
```
Distribution packagers can install `libpcre2-dev libev-dev libc-ares-dev
libmbedtls-dev libsodium-dev` and select `-DSS_DEPENDENCY_MODE=system
-DWITH_STATIC=OFF`. Documentation additionally needs asciidoc and xmlto.
### FreeBSD
#### Install
@@ -284,40 +249,35 @@ Note that is simply a workaround, each time you upgrade the port your changes wi
The OpenWRT project is maintained here:
[openwrt-shadowsocks](https://github.com/shadowsocks/openwrt-shadowsocks).
### OS X
For OS X, use [Homebrew](http://brew.sh) to install or build.
### macOS
Install Homebrew:
```bash
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```
Install shadowsocks-libev:
```bash
brew install shadowsocks-libev
```
Use the bundled CMake instructions above with Xcode Command Line Tools and
CMake. The bundled executables require no Homebrew runtime libraries.
For system mode, use Mbed TLS 3 and point `CMAKE_PREFIX_PATH` at its prefix.
### Windows (MinGW)
To build Windows native binaries, the recommended method is to use Docker:
* On Windows: double-click `make.bat` in `docker\mingw`
* On Unix-like system:
In an MSYS2 UCRT64 shell, install the toolchain and build native Windows programs:
cd shadowsocks-libev/docker/mingw
make
```bash
pacman -S --needed mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja
cmake -S . -B build -G Ninja
cmake --build build --parallel
ctest --test-dir build -L 'unit|vendor' --output-on-failure
```
A tarball with 32-bit and 64-bit binaries will be generated in the same directory.
Unix hosts with Zig installed can cross-compile without a separate MinGW SDK:
You could also manually use MinGW-w64 compilers to build in Unix-like shell (MSYS2/Cygwin), or cross-compile on Unix-like systems (Linux/MacOS). Please refer to build scripts in `docker/mingw`.
```bash
cmake -S . -B build-windows -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/zig-windows.cmake
cmake --build build-windows --parallel
```
Currently you need to use a patched libev library for MinGW:
* https://github.com/shadowsocks/libev/archive/mingw.zip
Notice that TCP Fast Open (TFO) is only available on **Windows 10**, **1607** or later version (precisely, build >= 14393). If you are using **1709** (build 16299) or later version, you also need to run the following command in PowerShell/Command Prompt **as Administrator** and **reboot** to use TFO properly:
netsh int tcp set global fastopenfallback=disabled
Cross-compilation does not run Windows tests. The portability workflow runs
native UCRT64 tests and TCP/UDP relay checks on Windows. Bundled mode is required
because libev must use the project's Winsock handle adapter. MSVC remains a
separate, unsupported milestone; configuration reports this explicitly.
The historical Autotools scripts in `docker/mingw` are superseded by this build.
### Docker
@@ -521,7 +481,7 @@ Select "Install a SIP003 plugin" from the main menu (requires root). Supports au
### ss-nat
`ss-nat` is a helper script that sets up iptables NAT rules for `ss-redir` to provide transparent TCP/UDP redirection. It is installed on Linux systems by `make install`.
`ss-nat` is a helper script that sets up iptables NAT rules for `ss-redir` to provide transparent TCP/UDP redirection. Enable `-DSS_INSTALL_TOOLS=ON` to install it on Linux.
**Prerequisites:** Linux with `iptables`, `ipset`, and optionally TPROXY kernel module for UDP
+69
View File
@@ -0,0 +1,69 @@
set(SS_DEPENDENCY_MODE "bundled" CACHE STRING "Dependency source: bundled or system")
set_property(CACHE SS_DEPENDENCY_MODE PROPERTY STRINGS bundled system)
set(SS_MATH_LIBRARY "")
if(NOT WIN32)
set(SS_MATH_LIBRARY m)
endif()
find_package(Threads REQUIRED)
if(SS_DEPENDENCY_MODE STREQUAL "bundled")
if(NOT WITH_STATIC)
message(FATAL_ERROR "Bundled mode requires WITH_STATIC=ON; use system mode for shared dependencies")
endif()
add_subdirectory(third_party)
return()
elseif(NOT SS_DEPENDENCY_MODE STREQUAL "system")
message(FATAL_ERROR "SS_DEPENDENCY_MODE must be bundled or system")
endif()
if(WIN32)
message(FATAL_ERROR "Windows requires bundled dependencies: libev must use this project's Winsock handle adapter")
endif()
# Select one linkage mode consistently for programs, libraries, and tests.
# Never require shared copies in a static dependency build.
if(WITH_STATIC)
set(_ss_saved_suffixes ${CMAKE_FIND_LIBRARY_SUFFIXES})
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
endif()
if(SS_ENABLE_REGEX)
find_package(PCRE2 REQUIRED)
endif()
find_package(MbedTLS REQUIRED)
find_package(Sodium REQUIRED)
find_package(Cares REQUIRED)
find_path(LIBEV_INCLUDE_DIR ev.h)
find_library(LIBEV_LIBRARY NAMES ev)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libev REQUIRED_VARS LIBEV_LIBRARY LIBEV_INCLUDE_DIR)
find_package(Threads REQUIRED)
if(WITH_STATIC)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${_ss_saved_suffixes})
endif()
function(ss_import_dependency target location includes)
add_library(${target} UNKNOWN IMPORTED GLOBAL)
set_target_properties(${target} PROPERTIES
IMPORTED_LOCATION "${location}"
INTERFACE_INCLUDE_DIRECTORIES "${includes}")
endfunction()
ss_import_dependency(ss_sodium "${SODIUM_LIBRARY}" "${SODIUM_INCLUDE_DIRS}")
ss_import_dependency(ss_mbedcrypto "${MBEDTLS_CRYPTO_LIBRARY}" "${MBEDTLS_INCLUDE_DIRS}")
ss_import_dependency(ss_cares "${CARES_LIBRARY}" "${CARES_INCLUDE_DIRS}")
if(SS_ENABLE_REGEX)
ss_import_dependency(ss_pcre2 "${PCRE2_LIBRARY}" "${PCRE2_INCLUDE_DIRS}")
endif()
ss_import_dependency(ss_ev "${LIBEV_LIBRARY}" "${LIBEV_INCLUDE_DIR}")
if(WIN32)
set_property(TARGET ss_cares APPEND PROPERTY INTERFACE_LINK_LIBRARIES ws2_32 iphlpapi)
set_property(TARGET ss_ev APPEND PROPERTY INTERFACE_LINK_LIBRARIES ws2_32)
if(WITH_STATIC)
set_property(TARGET ss_cares APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS CARES_STATICLIB)
if(SS_ENABLE_REGEX)
set_property(TARGET ss_pcre2 APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS PCRE2_STATIC)
endif()
set_property(TARGET ss_sodium APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS SODIUM_STATIC)
endif()
endif()
set(SS_MATH_LIBRARY "")
if(NOT WIN32)
set(SS_MATH_LIBRARY m)
endif()
+3
View File
@@ -42,3 +42,6 @@ else()
set(CARES_FOUND FALSE)
message(FATAL_ERROR "Could not find c-ares library. Install libc-ares-dev or equivalent.")
endif()
# CMake find_dependency uses the exact package-name spelling.
set(Cares_FOUND ${CARES_FOUND})
+26 -3
View File
@@ -18,6 +18,8 @@ include(CheckCSourceCompiles)
# This codebase targets the mbedTLS 3.x API, so prefer a 3.x installation.
set(_MBEDTLS_PREFIX_HINTS
${MbedTLS_ROOT}
${CMAKE_PREFIX_PATH}
/opt/homebrew/opt/mbedtls@3
/usr/local/opt/mbedtls@3
/opt/homebrew/opt/mbedtls
@@ -29,11 +31,12 @@ set(_MBEDTLS_PREFIX_HINTS
# Locate a prefix that provides both the headers and libmbedcrypto, so the
# two can never be drawn from different installations.
if(NOT CMAKE_CROSSCOMPILING)
foreach(_prefix IN LISTS _MBEDTLS_PREFIX_HINTS)
if(EXISTS "${_prefix}/include/mbedtls/cipher.h")
find_library(_MBEDTLS_CRYPTO_IN_PREFIX
NAMES mbedcrypto
PATHS "${_prefix}/lib"
PATHS "${_prefix}/lib" "${_prefix}/lib/${CMAKE_LIBRARY_ARCHITECTURE}"
NO_DEFAULT_PATH
)
if(_MBEDTLS_CRYPTO_IN_PREFIX)
@@ -43,6 +46,7 @@ foreach(_prefix IN LISTS _MBEDTLS_PREFIX_HINTS)
unset(_MBEDTLS_CRYPTO_IN_PREFIX CACHE)
endif()
endforeach()
endif()
if(_MBEDTLS_ROOT)
# Pin every component to the prefix chosen above.
@@ -53,12 +57,12 @@ if(_MBEDTLS_ROOT)
)
find_library(MBEDTLS_CRYPTO_LIBRARY
NAMES mbedcrypto
PATHS "${_MBEDTLS_ROOT}/lib"
PATHS "${_MBEDTLS_ROOT}/lib" "${_MBEDTLS_ROOT}/lib/${CMAKE_LIBRARY_ARCHITECTURE}"
NO_DEFAULT_PATH
)
find_library(MBEDTLS_TLS_LIBRARY
NAMES mbedtls
PATHS "${_MBEDTLS_ROOT}/lib"
PATHS "${_MBEDTLS_ROOT}/lib" "${_MBEDTLS_ROOT}/lib/${CMAKE_LIBRARY_ARCHITECTURE}"
NO_DEFAULT_PATH
)
else()
@@ -74,6 +78,17 @@ if(MBEDTLS_INCLUDE_DIR AND MBEDTLS_CRYPTO_LIBRARY)
set(MBEDTLS_FOUND TRUE)
set(MBEDTLS_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR})
set(_mbedtls_version_header "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h")
if(EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h")
set(_mbedtls_version_header "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h")
endif()
file(STRINGS "${_mbedtls_version_header}" _mbedtls_version_line
REGEX "^#define MBEDTLS_VERSION_STRING +\"[0-9.]+\"")
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" MBEDTLS_VERSION "${_mbedtls_version_line}")
if(NOT MBEDTLS_VERSION OR MBEDTLS_VERSION VERSION_LESS 2 OR NOT MBEDTLS_VERSION VERSION_LESS 4)
message(FATAL_ERROR "Supported system Mbed TLS versions are 2.x and 3.x; found ${MBEDTLS_VERSION}")
endif()
set(CMAKE_REQUIRED_INCLUDES ${MBEDTLS_INCLUDE_DIR})
set(CMAKE_REQUIRED_LIBRARIES ${MBEDTLS_CRYPTO_LIBRARY})
@@ -136,3 +151,11 @@ else()
set(MBEDTLS_FOUND FALSE)
message(FATAL_ERROR "Could not find mbedTLS library. Install libmbedtls-dev or equivalent.")
endif()
# CMake find_dependency uses the exact package-name spelling.
set(MbedTLS_FOUND ${MBEDTLS_FOUND})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MbedTLS
REQUIRED_VARS MBEDTLS_INCLUDE_DIR MBEDTLS_CRYPTO_LIBRARY
VERSION_VAR MBEDTLS_VERSION)
+6 -63
View File
@@ -1,63 +1,6 @@
# FindPCRE2.cmake - Find PCRE2 library (8-bit)
#
# Sets:
# PCRE2_FOUND
# PCRE2_INCLUDE_DIRS
# PCRE2_LIBRARIES
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(_PCRE2 QUIET libpcre2-8)
endif()
if(_PCRE2_FOUND)
set(PCRE2_INCLUDE_DIRS ${_PCRE2_INCLUDE_DIRS})
set(PCRE2_LIBRARIES ${_PCRE2_LIBRARIES})
set(PCRE2_FOUND TRUE)
else()
# Try pcre2-config
find_program(PCRE2_CONFIG pcre2-config)
if(PCRE2_CONFIG)
execute_process(COMMAND ${PCRE2_CONFIG} --cflags
OUTPUT_VARIABLE PCRE2_CFLAGS
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${PCRE2_CONFIG} --libs8
OUTPUT_VARIABLE PCRE2_LDFLAGS
OUTPUT_STRIP_TRAILING_WHITESPACE)
string(REGEX REPLACE "-I" "" PCRE2_INCLUDE_DIRS "${PCRE2_CFLAGS}")
set(PCRE2_LIBRARIES ${PCRE2_LDFLAGS})
set(PCRE2_FOUND TRUE)
else()
# Manual search
find_path(PCRE2_INCLUDE_DIR
NAMES pcre2.h
HINTS
/opt/homebrew/include
/usr/local/include
/usr/include
)
find_library(PCRE2_LIBRARY
NAMES pcre2-8
HINTS
/opt/homebrew/lib
/usr/local/lib
/usr/lib
)
if(PCRE2_INCLUDE_DIR AND PCRE2_LIBRARY)
set(PCRE2_INCLUDE_DIRS ${PCRE2_INCLUDE_DIR})
set(PCRE2_LIBRARIES ${PCRE2_LIBRARY})
set(PCRE2_FOUND TRUE)
else()
set(PCRE2_FOUND FALSE)
endif()
endif()
endif()
if(PCRE2_FOUND)
message(STATUS "Found PCRE2: ${PCRE2_LIBRARIES}")
else()
message(FATAL_ERROR "Could not find PCRE2 library. Install libpcre2-dev or equivalent.")
endif()
find_path(PCRE2_INCLUDE_DIR NAMES pcre2.h)
find_library(PCRE2_LIBRARY NAMES pcre2-8)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PCRE2 REQUIRED_VARS PCRE2_INCLUDE_DIR PCRE2_LIBRARY)
set(PCRE2_INCLUDE_DIRS ${PCRE2_INCLUDE_DIR})
set(PCRE2_LIBRARIES ${PCRE2_LIBRARY})
+3
View File
@@ -55,3 +55,6 @@ else()
set(SODIUM_FOUND FALSE)
message(FATAL_ERROR "Could not find libsodium. Install libsodium-dev or equivalent.")
endif()
# CMake find_dependency uses the exact package-name spelling.
set(Sodium_FOUND ${SODIUM_FOUND})
+78
View File
@@ -0,0 +1,78 @@
include(CMakePackageConfigHelpers)
if(SS_BUILD_STATIC_LIBRARY OR SS_BUILD_SHARED_LIBRARY)
set(SS_SYSTEM_DEPENDENCIES "")
set(SS_PC_PRIVATE "")
set(private_links)
set(private_content "# Generated private linkage for the installed static library.\n")
if(SS_BUILD_STATIC_LIBRARY)
set(private_targets bloom blake3)
if(SS_DEPENDENCY_MODE STREQUAL "bundled")
list(APPEND private_targets ss_sodium ss_mbedcrypto ss_ev ss_cares)
if(SS_ENABLE_REGEX)
list(APPEND private_targets ss_pcre2)
endif()
else()
set(SS_SYSTEM_DEPENDENCIES
"list(APPEND CMAKE_MODULE_PATH \"\${CMAKE_CURRENT_LIST_DIR}/modules\")\n find_dependency(MbedTLS ${MBEDTLS_VERSION} EXACT)\n find_dependency(Sodium)\n find_dependency(Cares)\n find_library(SS_CONSUMER_EV NAMES ev REQUIRED)")
if(SS_ENABLE_REGEX)
string(APPEND SS_SYSTEM_DEPENDENCIES "\n find_dependency(PCRE2)")
list(APPEND private_links "\${PCRE2_LIBRARY}")
string(APPEND SS_PC_PRIVATE " -lpcre2-8")
endif()
list(APPEND private_links "\${SODIUM_LIBRARY}" "\${MBEDTLS_CRYPTO_LIBRARY}"
"\${CARES_LIBRARY}" "\${SS_CONSUMER_EV}")
string(APPEND SS_PC_PRIVATE " -lsodium -lmbedcrypto -lcares -lev")
install(FILES cmake/FindMbedTLS.cmake cmake/FindSodium.cmake
cmake/FindCares.cmake cmake/FindPCRE2.cmake cmake/mbedtls_version_check.c
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/shadowsocks-libev/modules)
endif()
foreach(target IN LISTS private_targets)
install(FILES $<TARGET_FILE:${target}>
DESTINATION ${CMAKE_INSTALL_LIBDIR}/shadowsocks-libev)
list(APPEND private_links "\${_ss_libdir}/shadowsocks-libev/$<TARGET_FILE_NAME:${target}>")
string(APPEND SS_PC_PRIVATE " \${libdir}/shadowsocks-libev/$<TARGET_FILE_NAME:${target}>")
endforeach()
list(APPEND private_links Threads::Threads ${SS_MATH_LIBRARY} ${CMAKE_DL_LIBS})
if(WIN32)
list(APPEND private_links ws2_32 iphlpapi bcrypt advapi32)
string(APPEND SS_PC_PRIVATE " -lws2_32 -liphlpapi -lbcrypt -ladvapi32")
else()
string(APPEND SS_PC_PRIVATE " -lm -pthread")
if(APPLE)
list(APPEND private_links resolv)
string(APPEND SS_PC_PRIVATE " -lresolv")
endif()
endif()
# Resolve libdir relative to this installed config, supporting --prefix relocation.
file(RELATIVE_PATH libdir_from_config "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/shadowsocks-libev" "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}")
string(APPEND private_content "get_filename_component(_ss_libdir \"\${CMAKE_CURRENT_LIST_DIR}/${libdir_from_config}\" ABSOLUTE)\n")
string(APPEND private_content "set_property(TARGET shadowsocks::static PROPERTY INTERFACE_LINK_LIBRARIES \"${private_links}\")\n")
endif()
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/package/shadowsocks-private.cmake" CONTENT "${private_content}")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/package/shadowsocks-private.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/shadowsocks-libev)
set(SS_STATIC_FILENAME "${CMAKE_STATIC_LIBRARY_PREFIX}shadowsocks-libev${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(SS_SHARED_FILENAME "${CMAKE_SHARED_LIBRARY_PREFIX}shadowsocks-libev${CMAKE_SHARED_LIBRARY_SUFFIX}")
set(SS_SHARED_IMPLIB "${CMAKE_IMPORT_LIBRARY_PREFIX}shadowsocks-libev${CMAKE_IMPORT_LIBRARY_SUFFIX}")
configure_package_config_file(cmake/shadowsocks-libev-config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/package/shadowsocks-libev-config.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/shadowsocks-libev
PATH_VARS CMAKE_INSTALL_LIBDIR CMAKE_INSTALL_BINDIR CMAKE_INSTALL_INCLUDEDIR)
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/package/shadowsocks-libev-config-version.cmake"
VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/package/shadowsocks-libev-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/package/shadowsocks-libev-config-version.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/shadowsocks-libev)
file(RELATIVE_PATH SS_PC_PREFIX_RELATIVE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/pkgconfig" "${CMAKE_CURRENT_BINARY_DIR}")
configure_file(cmake/shadowsocks-libev.pc.cmake "${CMAKE_CURRENT_BINARY_DIR}/package/shadowsocks-libev.pc.in" @ONLY)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/shadowsocks-libev.pc"
INPUT "${CMAKE_CURRENT_BINARY_DIR}/package/shadowsocks-libev.pc.in")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/shadowsocks-libev.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
endif()
install(FILES COPYING LICENSE third_party/README.md third_party/manifest.json
DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/shadowsocks-libev)
install(FILES third_party/bloom/LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/shadowsocks-libev/bloom)
install(DIRECTORY src/blake3/ DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/shadowsocks-libev/blake3
FILES_MATCHING PATTERN "LICENSE*")
+52
View File
@@ -0,0 +1,52 @@
include(CheckCCompilerFlag)
option(DISABLE_SSP "Disable stack protector" OFF)
if(NOT DISABLE_SSP AND NOT MSVC)
check_c_compiler_flag(-fstack-protector-strong HAS_STACK_PROTECTOR)
endif()
function(ss_project_options target)
set_property(TARGET ${target} PROPERTY NO_SYSTEM_FROM_IMPORTED TRUE)
target_include_directories(${target} BEFORE PRIVATE ${MBEDTLS_INCLUDE_DIRS})
if(WIN32)
target_compile_definitions(${target} PRIVATE WIN32_LEAN_AND_MEAN __USE_MINGW_ANSI_STDIO=1)
# MinGW can otherwise pull in libwinpthread/libgcc from the toolchain,
# even when all explicitly selected dependencies are static archives.
get_target_property(ss_target_type ${target} TYPE)
# Zig already supplies its own runtime for DLLs; its -static switch
# changes the output kind instead of just selecting static libraries.
if(NOT ss_target_type STREQUAL "SHARED_LIBRARY" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")
target_link_options(${target} PRIVATE -static)
endif()
endif()
if(MSVC)
target_compile_options(${target} PRIVATE /W4)
if(SS_WARNINGS_AS_ERRORS)
target_compile_options(${target} PRIVATE /WX)
endif()
else()
target_compile_options(${target} PRIVATE -Wall -Wno-deprecated-declarations -fno-strict-aliasing)
if(SS_WARNINGS_AS_ERRORS)
target_compile_options(${target} PRIVATE -Werror)
endif()
if(HAS_STACK_PROTECTOR)
target_compile_options(${target} PRIVATE -fstack-protector-strong)
endif()
if(MINGW OR CYGWIN)
target_compile_options(${target} PRIVATE -mno-ms-bitfields)
endif()
endif()
if(LINUX)
target_compile_definitions(${target} PRIVATE _GNU_SOURCE)
endif()
if(ENABLE_SANITIZERS)
if(MSVC)
message(FATAL_ERROR "ASan + UBSan requires GCC or Clang")
endif()
target_compile_options(${target} PRIVATE -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer)
target_link_options(${target} PRIVATE -fsanitize=address,undefined)
endif()
if(ENABLE_COVERAGE)
target_compile_options(${target} PRIVATE -O0 --coverage)
target_link_options(${target} PRIVATE --coverage)
endif()
endfunction()
+4
View File
@@ -307,4 +307,8 @@
/* Define as `fork' if `vfork' does not work. */
#cmakedefine vfork
#cmakedefine01 SS_ENABLE_REGEX
#cmakedefine01 SS_ENABLE_PLUGINS
#cmakedefine01 SS_ENABLE_LEGACY
#endif
+1 -16
View File
@@ -16,7 +16,7 @@ include(CheckCCompilerFlag)
# Set CONNECT_IN_PROGRESS based on platform
if(MINGW)
set(CONNECT_IN_PROGRESS "WSAEWOULDBLOCK")
set(CONNECT_IN_PROGRESS "EWOULDBLOCK")
else()
set(CONNECT_IN_PROGRESS "EINPROGRESS")
endif()
@@ -180,18 +180,3 @@ endif ()
if (NOT HAVE_WORKING_VFORK)
set(vfork fork)
endif ()
# Stack protector detection
option(DISABLE_SSP "Disable -fstack-protector" OFF)
if(NOT DISABLE_SSP)
check_c_compiler_flag(-fstack-protector HAS_STACK_PROTECTOR)
if(HAS_STACK_PROTECTOR)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector")
message(STATUS "Stack protector enabled")
endif()
endif()
# MinGW/Cygwin compiler flags
if(MINGW OR CYGWIN)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mno-ms-bitfields")
endif()
+34
View File
@@ -0,0 +1,34 @@
@PACKAGE_INIT@
if(@SS_BUILD_SHARED_LIBRARY@ AND NOT TARGET shadowsocks::shared)
add_library(shadowsocks::shared SHARED IMPORTED)
set_target_properties(shadowsocks::shared PROPERTIES
IMPORTED_LOCATION "@PACKAGE_CMAKE_INSTALL_LIBDIR@/@SS_SHARED_FILENAME@"
INTERFACE_INCLUDE_DIRECTORIES "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
if(WIN32)
set_target_properties(shadowsocks::shared PROPERTIES
IMPORTED_LOCATION "@PACKAGE_CMAKE_INSTALL_BINDIR@/@SS_SHARED_FILENAME@"
IMPORTED_IMPLIB "@PACKAGE_CMAKE_INSTALL_LIBDIR@/@SS_SHARED_IMPLIB@")
endif()
endif()
if(@SS_BUILD_STATIC_LIBRARY@ AND NOT TARGET shadowsocks::static)
include(CMakeFindDependencyMacro)
find_dependency(Threads)
@SS_SYSTEM_DEPENDENCIES@
add_library(shadowsocks::static STATIC IMPORTED)
set_target_properties(shadowsocks::static PROPERTIES
IMPORTED_LOCATION "@PACKAGE_CMAKE_INSTALL_LIBDIR@/@SS_STATIC_FILENAME@"
INTERFACE_INCLUDE_DIRECTORIES "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
include("${CMAKE_CURRENT_LIST_DIR}/shadowsocks-private.cmake")
if(WIN32)
set_property(TARGET shadowsocks::static PROPERTY INTERFACE_LINK_OPTIONS -static)
endif()
endif()
if(NOT TARGET shadowsocks::shadowsocks)
if(TARGET shadowsocks::shared)
add_library(shadowsocks::shadowsocks ALIAS shadowsocks::shared)
elseif(TARGET shadowsocks::static)
add_library(shadowsocks::shadowsocks ALIAS shadowsocks::static)
endif()
endif()
check_required_components(shadowsocks-libev)
+2 -1
View File
@@ -1,4 +1,4 @@
prefix=@CMAKE_INSTALL_PREFIX@
prefix=${pcfiledir}/@SS_PC_PREFIX_RELATIVE@
exec_prefix=${prefix}
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
@@ -10,3 +10,4 @@ Version: @PROJECT_VERSION@
Requires:
Cflags: -I${includedir}
Libs: -L${libdir} -lshadowsocks-libev
Libs.private: @SS_PC_PRIVATE@
+20
View File
@@ -0,0 +1,20 @@
# Cross-compile the C project from a Unix host without Docker or a MinGW SDK.
# cmake -S . -B build-windows -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/zig-windows.cmake
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR AMD64)
find_program(SS_ZIG_EXECUTABLE NAMES zig REQUIRED)
set(CMAKE_C_COMPILER "${SS_ZIG_EXECUTABLE}" cc -target x86_64-windows-gnu)
# CMake's archive tools accept an executable path, not an argv list.
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/zig-tools")
foreach(tool ar ranlib)
set(wrapper "${CMAKE_BINARY_DIR}/zig-tools/${tool}")
file(WRITE "${wrapper}" "#!/bin/sh\nexec \"${SS_ZIG_EXECUTABLE}\" ${tool} \"$@\"\n")
file(CHMOD "${wrapper}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
set(CMAKE_${tool} "${wrapper}")
endforeach()
set(CMAKE_AR "${CMAKE_BINARY_DIR}/zig-tools/ar")
set(CMAKE_RANLIB "${CMAKE_BINARY_DIR}/zig-tools/ranlib")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+6
View File
@@ -7,6 +7,7 @@ Uploaders:
Boyuan Yang <byang@debian.org>,
Roger Shimizu <rosh@debian.org>
Build-Depends:
cmake (>= 3.20),
asciidoc-base | asciidoc,
debhelper (>= 10),
libc-ares-dev,
@@ -51,6 +52,11 @@ Section: libdevel
Breaks: shadowsocks-libev (<< 2.4.0)
Depends:
libshadowsocks-libev2 (= ${binary:Version}),
libc-ares-dev,
libev-dev,
libmbedtls-dev,
libpcre2-dev,
libsodium-dev,
${misc:Depends}
Description: lightweight and secure socks5 proxy (development files)
Shadowsocks-libev is a lightweight and secure socks5 proxy for
+3
View File
@@ -1,3 +1,6 @@
usr/include/
usr/lib/*/libshadowsocks-libev.so
usr/lib/*/libshadowsocks-libev.a
usr/lib/*/shadowsocks-libev/
usr/lib/*/cmake/shadowsocks-libev/
usr/lib/*/pkgconfig/
+1 -1
View File
@@ -11,7 +11,7 @@ override_dh_auto_install:
dh_auto_install
override_dh_auto_configure:
dh_auto_configure -- -DWITH_STATIC=OFF
dh_auto_configure -- -DSS_DEPENDENCY_MODE=system -DWITH_STATIC=OFF -DWITH_DOC_MAN=ON -DWITH_DOC_HTML=ON -DSS_INSTALL_TOOLS=ON
override_dh_installchangelogs:
dh_installchangelogs -XChanges
+1
View File
@@ -5,3 +5,4 @@ debian/shadowsocks-libev-*.service lib/systemd/system
usr/bin/
usr/share/man/
usr/share/doc/shadowsocks-libev/
usr/share/licenses/shadowsocks-libev/
+8 -12
View File
@@ -1,18 +1,14 @@
find_program(XMLTO_EXECUTABLE NAMES xmlto)
find_program(ASCIIDOC_EXECUTABLE NAMES asciidoc asciidoc.py)
# Opt-in doc build option
if (NOT XMLTO_EXECUTABLE OR NOT ASCIIDOC_EXECUTABLE)
option(WITH_DOC_MAN "Build manpage doc" OFF)
else ()
option(WITH_DOC_MAN "Build manpage doc" ON)
endif ()
if (NOT ASCIIDOC_EXECUTABLE)
option(WITH_DOC_HTML "Build html doc" OFF)
else ()
option(WITH_DOC_HTML "Build html doc" ON)
endif ()
option(WITH_DOC_MAN "Build manpage documentation" OFF)
option(WITH_DOC_HTML "Build HTML documentation" OFF)
if((WITH_DOC_MAN OR WITH_DOC_HTML) AND NOT ASCIIDOC_EXECUTABLE)
message(FATAL_ERROR "Documentation generation requires asciidoc")
endif()
if(WITH_DOC_MAN AND NOT XMLTO_EXECUTABLE)
message(FATAL_ERROR "Manpage generation requires xmlto")
endif()
# NOTE For brew user, we have to setup this env var. see `brew info asciidoc'
set(XMLTO_ENV)
+1 -6
View File
@@ -16,17 +16,12 @@ RUN set -x \
&& apk add --no-cache --virtual .build-deps \
build-base \
cmake \
c-ares-dev \
libcap \
libev-dev \
libsodium-dev \
linux-headers \
mbedtls-dev \
pcre2-dev \
# Build & install
&& cd /tmp/repo \
&& mkdir -p build && cd build \
&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_TESTING=OFF -DWITH_STATIC=OFF -DCMAKE_BUILD_TYPE=Release \
&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_TESTING=OFF -DSS_BUILD_STATIC_LIBRARY=OFF -DSS_BUILD_SHARED_LIBRARY=OFF -DCMAKE_BUILD_TYPE=Release \
&& make -j$(getconf _NPROCESSORS_ONLN) \
&& make install \
&& cd /usr/local/bin \
+33
View File
@@ -0,0 +1,33 @@
ARG ALPINE_IMAGE=alpine:3.24
FROM ${ALPINE_IMAGE} AS build
ARG ALPINE_MIRROR=https://dl-cdn.alpinelinux.org/alpine
RUN sed -i "s|https://dl-cdn.alpinelinux.org/alpine|${ALPINE_MIRROR}|g" /etc/apk/repositories \
&& apk add --no-cache clang lld make musl-dev compiler-rt cmake linux-headers python3 binutils file
ENV CC=clang
WORKDIR /src
COPY . .
ARG SS_MINIMAL=OFF
RUN --network=none cmake -S . -B /build \
-DCMAKE_BUILD_TYPE=Release \
-DSS_DEPENDENCY_MODE=bundled -DWITH_STATIC=ON \
-DSS_BUILD_STATIC_LIBRARY=OFF -DSS_BUILD_SHARED_LIBRARY=OFF \
-DSS_MINIMAL=${SS_MINIMAL} -DCMAKE_EXE_LINKER_FLAGS="-static -fuse-ld=lld --rtlib=compiler-rt" \
-DCMAKE_INSTALL_PREFIX=/out \
&& cmake --build /build --parallel 4 \
&& ctest --test-dir /build -L 'unit|vendor' --output-on-failure --no-tests=error \
&& python3 tests/interop.py --self --bin /build/bin \
&& cmake --install /build --strip \
&& for binary in /out/bin/ss-*; do \
file "$binary"; \
! readelf -l "$binary" | grep -q INTERP || exit 1; \
! readelf -d "$binary" | grep -q NEEDED || exit 1; \
done
# Export binaries and their license files with --target artifacts --output.
FROM scratch AS artifacts
COPY --from=build /out/ /
# No libc, dynamic loader, package manager or shell in the runtime image.
FROM scratch AS runtime
COPY --from=build /out/ /usr/local/
ENTRYPOINT ["/usr/local/bin/ss-server"]
+8
View File
@@ -0,0 +1,8 @@
.git
build*
release
libcork
libipset
libbloom
**/__pycache__
**/.DS_Store
+41
View File
@@ -0,0 +1,41 @@
# Fully static Linux builds
The Alpine/musl builder uses Clang with LLD by default and links both third-party dependencies and libc statically.
`WITH_STATIC=ON` alone only selects static third-party libraries; this image also
passes `-DCMAKE_EXE_LINKER_FLAGS=-static` and omits shared-library outputs.
The compile, unit/vendor tests and six-method TCP/UDP relay tests run with Docker
network access disabled. The final ELF check rejects interpreters and dynamic
library dependencies. Initial image/toolchain installation requires networking.
From the repository root, export binaries and licenses (ARM64 example):
```sh
docker build --platform linux/arm64 -f docker/static/Dockerfile \
--target artifacts --output type=local,dest=build-artifacts/static-linux-arm64 .
```
Requires Docker Buildx/BuildKit. If Docker Hub is unavailable, add
`--build-arg ALPINE_IMAGE=public.ecr.aws/docker/library/alpine:3.24`.
An alternate package mirror can be selected with `--build-arg ALPINE_MIRROR=https://mirrors.aliyun.com/alpine`; APK signature checks remain enabled.
Docker build proxy settings apply to package installation; a broken configured
proxy can be bypassed for this command with empty `HTTP_PROXY`, `HTTPS_PROXY`,
`ALL_PROXY` build arguments and their lowercase equivalents.
Use `--platform linux/amd64` and a different destination for x86-64. Docker needs
a matching native builder or CPU emulation to compile and run the tests for a
foreign architecture. Add `--build-arg SS_MINIMAL=ON` for the minimal profile.
Build the scratch runtime image using the same cached build stage:
```sh
docker build --platform linux/arm64 -f docker/static/Dockerfile \
--target runtime -t shadowsocks-libev:static-arm64 .
docker run --rm -p 8388:8388/tcp -p 8388:8388/udp \
shadowsocks-libev:static-arm64 \
-s 0.0.0.0 -p 8388 -k example-password -m aes-256-gcm -u
```
The runtime image has no shell or external plugin executables. Supply any SIP003
plugin and its own runtime requirements separately. Configuration files can be
bind-mounted and passed with `-c`. Exported binaries run on Linux with the same
CPU architecture; they do not run directly on macOS or Windows.
@@ -0,0 +1,42 @@
{
"commit": "8fe386b5c270fc34d2bf53ef5ffb93d93242fac2",
"platform": "Darwin arm64",
"binaries": {
"ss-local": {
"bytes": 295912,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)",
"\t/opt/homebrew/opt/libev/lib/libev.4.dylib (compatibility version 5.0.0, current version 5.0.0)",
"\t/opt/homebrew/opt/c-ares/lib/libcares.2.dylib (compatibility version 2.0.0, current version 2.19.5)",
"\t/usr/lib/libpcre2-8.0.dylib (compatibility version 12.0.0, current version 12.2.0)",
"\t/opt/homebrew/opt/libsodium/lib/libsodium.26.dylib (compatibility version 31.0.0, current version 31.0.0)",
"\t/opt/homebrew/opt/mbedtls@3/lib/libmbedtls.21.dylib (compatibility version 21.0.0, current version 3.6.6)",
"\t/opt/homebrew/opt/mbedtls@3/lib/libmbedcrypto.16.dylib (compatibility version 16.0.0, current version 3.6.6)"
]
},
"ss-server": {
"bytes": 314360,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)",
"\t/opt/homebrew/opt/libev/lib/libev.4.dylib (compatibility version 5.0.0, current version 5.0.0)",
"\t/opt/homebrew/opt/c-ares/lib/libcares.2.dylib (compatibility version 2.0.0, current version 2.19.5)",
"\t/usr/lib/libpcre2-8.0.dylib (compatibility version 12.0.0, current version 12.2.0)",
"\t/opt/homebrew/opt/libsodium/lib/libsodium.26.dylib (compatibility version 31.0.0, current version 31.0.0)",
"\t/opt/homebrew/opt/mbedtls@3/lib/libmbedtls.21.dylib (compatibility version 21.0.0, current version 3.6.6)",
"\t/opt/homebrew/opt/mbedtls@3/lib/libmbedcrypto.16.dylib (compatibility version 16.0.0, current version 3.6.6)"
]
},
"ss-tunnel": {
"bytes": 268680,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)",
"\t/opt/homebrew/opt/libev/lib/libev.4.dylib (compatibility version 5.0.0, current version 5.0.0)",
"\t/opt/homebrew/opt/c-ares/lib/libcares.2.dylib (compatibility version 2.0.0, current version 2.19.5)",
"\t/usr/lib/libpcre2-8.0.dylib (compatibility version 12.0.0, current version 12.2.0)",
"\t/opt/homebrew/opt/libsodium/lib/libsodium.26.dylib (compatibility version 31.0.0, current version 31.0.0)",
"\t/opt/homebrew/opt/mbedtls@3/lib/libmbedtls.21.dylib (compatibility version 21.0.0, current version 3.6.6)",
"\t/opt/homebrew/opt/mbedtls@3/lib/libmbedcrypto.16.dylib (compatibility version 16.0.0, current version 3.6.6)"
]
}
}
}
@@ -0,0 +1,24 @@
{
"commit": "8fe386b5c270fc34d2bf53ef5ffb93d93242fac2",
"platform": "Darwin arm64",
"binaries": {
"ss-local": {
"bytes": 1326328,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)"
]
},
"ss-server": {
"bytes": 1500968,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)"
]
},
"ss-tunnel": {
"bytes": 828712,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)"
]
}
}
}
@@ -0,0 +1,101 @@
[
{
"cipher": "aes-128-gcm",
"bandwidth_mbps": 7063.43292556653,
"duration_sec": 0.11876106262207031,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2656,
"server_rss_after_kb": 3472,
"tunnel_rss_before_kb": 2400,
"tunnel_rss_after_kb": 2576,
"error": null
},
{
"cipher": "aes-128-gcm",
"bandwidth_mbps": 7001.809356509003,
"duration_sec": 0.11980628967285156,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2704,
"server_rss_after_kb": 3504,
"tunnel_rss_before_kb": 2400,
"tunnel_rss_after_kb": 2576,
"error": null
},
{
"cipher": "aes-128-gcm",
"bandwidth_mbps": 6698.608105647417,
"duration_sec": 0.1252291202545166,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2688,
"server_rss_after_kb": 3488,
"tunnel_rss_before_kb": 2384,
"tunnel_rss_after_kb": 2560,
"error": null
},
{
"cipher": "aes-256-gcm",
"bandwidth_mbps": 7690.119291065227,
"duration_sec": 0.10908293724060059,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2704,
"server_rss_after_kb": 3472,
"tunnel_rss_before_kb": 2400,
"tunnel_rss_after_kb": 2576,
"error": null
},
{
"cipher": "aes-256-gcm",
"bandwidth_mbps": 7890.252575300613,
"duration_sec": 0.10631608963012695,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2672,
"server_rss_after_kb": 3488,
"tunnel_rss_before_kb": 2400,
"tunnel_rss_after_kb": 2576,
"error": null
},
{
"cipher": "aes-256-gcm",
"bandwidth_mbps": 7775.56902641376,
"duration_sec": 0.10788416862487793,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2704,
"server_rss_after_kb": 3520,
"tunnel_rss_before_kb": 2400,
"tunnel_rss_after_kb": 2576,
"error": null
},
{
"cipher": "chacha20-ietf-poly1305",
"bandwidth_mbps": 3438.7140620682108,
"duration_sec": 0.24394607543945312,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2656,
"server_rss_after_kb": 3552,
"tunnel_rss_before_kb": 2384,
"tunnel_rss_after_kb": 2592,
"error": null
},
{
"cipher": "chacha20-ietf-poly1305",
"bandwidth_mbps": 3471.04357984401,
"duration_sec": 0.24167394638061523,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2656,
"server_rss_after_kb": 3408,
"tunnel_rss_before_kb": 2368,
"tunnel_rss_after_kb": 2576,
"error": null
},
{
"cipher": "chacha20-ietf-poly1305",
"bandwidth_mbps": 3387.1740528144805,
"duration_sec": 0.24765801429748535,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2656,
"server_rss_after_kb": 3472,
"tunnel_rss_before_kb": 2384,
"tunnel_rss_after_kb": 2592,
"error": null
}
]
@@ -0,0 +1,74 @@
[
{
"cipher": "aes-128-gcm",
"trial": 0,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.25436816699948395,
"payload_mbps": 75.4811430474276
},
{
"cipher": "aes-128-gcm",
"trial": 1,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.2318156250003085,
"payload_mbps": 82.8244429165396
},
{
"cipher": "aes-128-gcm",
"trial": 2,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.24796225000136474,
"payload_mbps": 77.43114123175735
},
{
"cipher": "aes-256-gcm",
"trial": 0,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.2386028750006517,
"payload_mbps": 80.46843526067134
},
{
"cipher": "aes-256-gcm",
"trial": 1,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.2360522500002844,
"payload_mbps": 81.33792412475148
},
{
"cipher": "aes-256-gcm",
"trial": 2,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.23466012499920907,
"payload_mbps": 81.82046268007704
},
{
"cipher": "chacha20-ietf-poly1305",
"trial": 0,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.24850704099844734,
"payload_mbps": 77.26139236481416
},
{
"cipher": "chacha20-ietf-poly1305",
"trial": 1,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.24412816600124643,
"payload_mbps": 78.64721352923273
},
{
"cipher": "chacha20-ietf-poly1305",
"trial": 2,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.24165658300080395,
"payload_mbps": 79.45159102053563
}
]
@@ -0,0 +1,27 @@
{
"commit": "46d3acd9d6b72e088b688c2f84aae5c0466885d8",
"platform": "Darwin arm64",
"binaries": {
"ss-local": {
"bytes": 756920,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)",
"\t/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)"
]
},
"ss-server": {
"bytes": 962632,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)",
"\t/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)"
]
},
"ss-tunnel": {
"bytes": 416440,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)",
"\t/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)"
]
}
}
}
@@ -0,0 +1,101 @@
[
{
"cipher": "aes-128-gcm",
"bandwidth_mbps": 7860.532450008042,
"duration_sec": 0.10671806335449219,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2288,
"server_rss_after_kb": 3072,
"tunnel_rss_before_kb": 1872,
"tunnel_rss_after_kb": 2016,
"error": null
},
{
"cipher": "aes-128-gcm",
"bandwidth_mbps": 6582.251473027347,
"duration_sec": 0.1274428367614746,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2256,
"server_rss_after_kb": 3040,
"tunnel_rss_before_kb": 1872,
"tunnel_rss_after_kb": 2032,
"error": null
},
{
"cipher": "aes-128-gcm",
"bandwidth_mbps": 7002.575801490699,
"duration_sec": 0.11979317665100098,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2256,
"server_rss_after_kb": 3008,
"tunnel_rss_before_kb": 1872,
"tunnel_rss_after_kb": 2032,
"error": null
},
{
"cipher": "aes-256-gcm",
"bandwidth_mbps": 8336.501684349796,
"duration_sec": 0.10062503814697266,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2256,
"server_rss_after_kb": 2768,
"tunnel_rss_before_kb": 1872,
"tunnel_rss_after_kb": 2016,
"error": null
},
{
"cipher": "aes-256-gcm",
"bandwidth_mbps": 8556.573148352736,
"duration_sec": 0.0980370044708252,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2256,
"server_rss_after_kb": 2992,
"tunnel_rss_before_kb": 1872,
"tunnel_rss_after_kb": 2032,
"error": null
},
{
"cipher": "aes-256-gcm",
"bandwidth_mbps": 8498.291637500875,
"duration_sec": 0.0987093448638916,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2288,
"server_rss_after_kb": 3072,
"tunnel_rss_before_kb": 1872,
"tunnel_rss_after_kb": 2016,
"error": null
},
{
"cipher": "chacha20-ietf-poly1305",
"bandwidth_mbps": 3276.7750490181143,
"duration_sec": 0.25600194931030273,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2272,
"server_rss_after_kb": 3072,
"tunnel_rss_before_kb": 1856,
"tunnel_rss_after_kb": 2000,
"error": null
},
{
"cipher": "chacha20-ietf-poly1305",
"bandwidth_mbps": 3267.788061223197,
"duration_sec": 0.25670599937438965,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2272,
"server_rss_after_kb": 3072,
"tunnel_rss_before_kb": 1840,
"tunnel_rss_after_kb": 1984,
"error": null
},
{
"cipher": "chacha20-ietf-poly1305",
"bandwidth_mbps": 2988.673848516679,
"duration_sec": 0.28067994117736816,
"bytes_transferred": 104857600,
"server_rss_before_kb": 2272,
"server_rss_after_kb": 3040,
"tunnel_rss_before_kb": 1856,
"tunnel_rss_after_kb": 2032,
"error": null
}
]
@@ -0,0 +1,74 @@
[
{
"cipher": "aes-128-gcm",
"trial": 0,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.25596854199829977,
"payload_mbps": 75.00921734408884
},
{
"cipher": "aes-128-gcm",
"trial": 1,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.25268954199964355,
"payload_mbps": 75.98256678160065
},
{
"cipher": "aes-128-gcm",
"trial": 2,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.2656113329994696,
"payload_mbps": 72.28607222131723
},
{
"cipher": "aes-256-gcm",
"trial": 0,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.31145487499998126,
"payload_mbps": 61.6461694491093
},
{
"cipher": "aes-256-gcm",
"trial": 1,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.30316929199943843,
"payload_mbps": 63.3309523974993
},
{
"cipher": "aes-256-gcm",
"trial": 2,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.281550084000628,
"payload_mbps": 68.1939061327262
},
{
"cipher": "chacha20-ietf-poly1305",
"trial": 0,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.29268420900007186,
"payload_mbps": 65.59971262404282
},
{
"cipher": "chacha20-ietf-poly1305",
"trial": 1,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.282807709001645,
"payload_mbps": 67.89065286720428
},
{
"cipher": "chacha20-ietf-poly1305",
"trial": 2,
"packets": 2000,
"payload_bytes": 1200,
"seconds": 0.25814837500001886,
"payload_mbps": 74.37583134117578
}
]
@@ -0,0 +1,27 @@
{
"commit": "46d3acd9d6b72e088b688c2f84aae5c0466885d8",
"platform": "Darwin arm64",
"binaries": {
"ss-local": {
"bytes": 416424,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)",
"\t/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)"
]
},
"ss-server": {
"bytes": 622024,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)",
"\t/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)"
]
},
"ss-tunnel": {
"bytes": 397704,
"dependencies": [
"\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)",
"\t/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)"
]
}
}
}
+165
View File
@@ -0,0 +1,165 @@
# Self-contained C modernization
Status: complete for the agreed modernization scope. Native MSVC remains an
explicitly deferred milestone. Implementation branch: feature/self-contained-portable-c;
review: https://github.com/shadowsocks/shadowsocks-libev/pull/3050 (draft).
## Compatibility contract
Keep C11 and the existing CLI, JSON configuration, ss:// parsing, SIP003 plugins,
ACL blacklist/whitelist/outbound precedence, public shadowsocks.h API, legacy
stream ciphers, AEAD and SIP022 TCP/UDP behavior in the compatibility profile.
Removing regex or legacy ciphers is explicit opt-in, with configuration errors
for unavailable functionality. Linux redirection/netfilter remains Linux-only.
## Work and evidence
- [x] Baseline: fresh rebuild, unit/integration results, binary/dependency sizes,
TCP and UDP throughput, and memory measurements.
- [x] Required independent interoperability including TCP and UDP; missing peers
fail the required CI job and are reported as skips in optional local runs.
- [x] Target-scoped CMake, separate programs/static/shared library options,
reliable dependency discovery, installation/export metadata and presets.
- [x] Offline bundled dependency mode and source release archive; explicit system
mode for distro packages; checksums, licenses, versions, update procedure.
- [x] Remove libcork: address parsing, lists, hash maps, subprocess lifecycle.
- [x] Remove libipset: bounded IPv4/IPv6 prefix storage with insertion/deletion;
differential tests including holes inside larger networks.
- [x] Vendor the small bloom filter and remove all three submodule requirements.
- [x] Portability layer for sockets/errors, I/O, clocks, randomness and processes.
- [x] Runtime CI: Linux glibc/musl, macOS, FreeBSD, Windows MinGW.
- [x] Document MSVC/event-loop compatibility assessment as a later milestone.
- [x] Minimal profile: no regex/plugins/manager/legacy stream ciphers.
- [x] Crypto-only Mbed TLS configuration and evidence-backed provider assessment.
- [x] Complete sanitizers/static analysis, compatibility and stress validation,
release archive offline build, installed consumer, before/after report.
## Baseline provenance
Base commit: 8fe386b. Its libcork checkout is 074e074b, different from the
superproject gitlink; this was pre-existing local state. That checkout, libipset
and libbloom are preserved and ignored locally. Fresh baseline builds use those
same checkouts, with no modernization changes applied to baseline source.
The fresh baseline passes all 13 original unit tests.
## Build interface
Requires CMake 3.20+, a C11 compiler, and a build executor. Presets use Ninja;
manual configuration may use another generator.
```
cmake --preset system
cmake --build --preset system
ctest --preset system
```
Programs are in build-system/bin for either linkage mode. WITH_STATIC selects
static third-party dependencies; it does not promise a static libc or OS runtime.
SS_BUILD_EXECUTABLES, SS_BUILD_STATIC_LIBRARY and SS_BUILD_SHARED_LIBRARY control
outputs independently. Docs and platform shell tools are explicit opt-ins.
Bundled mode is the default and the `bundled` / `minimal` presets exercise it.
Maintainers stage the intended source files, then run
`scripts/source_archive.py build-artifacts/shadowsocks-libev.tar.gz`.
The archive includes tracked working-tree contents (so uncommitted changes are
included); record the final commit with a published release and use a clean
checkout for publishing. No submodules are accepted in the archive.
## Platform validation
Implementation validation at commit cdb192a passes the full platform matrix:
Linux glibc x86-64/ARM64 and macOS full/minimal builds, real TCP/UDP relay, SIP003
cleanup (full profile), and relocated installed consumers. FreeBSD 14.3 passes
compilation, unit/vendor tests, real TCP/UDP relay and installed consumers.
Native Windows UCRT64 passes all 31 unit/vendor tests, six-method TCP/UDP relay,
and installed static/shared consumers. Relay and consumer execution excludes
MSYS2/toolchain DLL directories from PATH; binaries require only Windows system
DLLs. Zig cross-builds also include both libraries and installed consumers.
Alpine/musl builds the release archive with networking and Python discovery
disabled and passes all unit/vendor and TCP/UDP tests. Linux Valgrind, ASan/UBSan,
coverage, required shadowsocks-rust interoperability, clang-tidy-18, and Debian
package construction pass. Evidence: [platform matrix](https://github.com/shadowsocks/shadowsocks-libev/actions/runs/34505389620),
[tests and analysis](https://github.com/shadowsocks/shadowsocks-libev/actions/runs/34505389548),
[system builds and packaging](https://github.com/shadowsocks/shadowsocks-libev/actions/runs/34505389561).
The same committed source archive also builds locally on Linux arm64 with
networking and Python discovery disabled, passing all 31 unit/vendor tests and
six-method TCP/UDP plus SIP003 tests. The source release itself needs no Git or
Python to compile. Executable-only installation omits library metadata, and a
minimal static-library-only build succeeds with the PCRE2 archive removed.
The final documentation commit is rechecked by the same CI matrix before delivery.
## Compatibility details
Address classification now uses inet_pton consistently with socket conversion;
malformed dotted addresses previously accepted by libcork but rejected by
inet_pton are no longer classified as IP literals. Full and suffix ACL rules
are explicit additions; normal regex remains available by default.
Legacy methods unavailable in Mbed TLS 3 (such as RC4) are not resurrected.
Crypto providers remain libsodium plus the reduced Mbed TLS primitive set:
AES-128/192-GCM, software AES fallback, MD5/SHA1 compatibility derivation, and
legacy AES/Camellia stream modes prevent a transparent libsodium-only switch.
See https://doc.libsodium.org/secret-key_cryptography/aead/aes-256-gcm and
https://shadowsocks.org/doc/sip022.html for the provider and protocol contracts.
## Local validation
- Fresh base-commit build (8fe386b) passes all 13 original unit tests, using the
preserved dependency checkouts described above. Both its system-shared and
static-dependency programs were rebuilt for size comparisons.
- macOS full bundled: 31 tests; minimal: 29 tests. System dependencies: 17 tests.
- ASan + UBSan: all 17 project tests and six-method TCP/UDP SIP003 tests pass.
- Clang-tidy: zero warnings/errors with Homebrew LLVM; project compiler warnings
remain errors. CI additionally uses its existing pinned clang-tidy-18 check.
- IP-set differential test: 1,028,000 comparisons against the original libipset,
including randomized full-width IPv4/IPv6 insertion/deletion and membership.
- Linux x86-64 musl: 31 unit/vendor tests and all six TCP/UDP relay cases pass
in containers with networking disabled. The preinstalled cross compiler
intermittently crashed under CPU emulation; incremental retries completed.
- Installed bundled static/shared consumers run after relocation. Installed
system-mode static/shared consumers also link and run. System static exports
require the exact Mbed TLS version used at build time to avoid an ABI mismatch.
- Windows x86-64: programs, both libraries, all tests and installed consumers
cross-compile with Zig. Native UCRT64 execution and installed consumers pass
with runtime DLL lookup restricted to the installation and Windows system paths.
- SIP003 fixture: real TCP forwarding, UDP bypass, and child cleanup pass through
the actual programs. DNS cancellation covers outstanding A/AAAA requests,
exactly-once callbacks/free callbacks and reinitialization after shutdown.
### MSVC assessment
MSVC is an explicitly deferred milestone, not a supported build today. MinGW-w64
supplies the POSIX compatibility headers/functions (`unistd.h`, `getopt`,
`ssize_t`, string helpers) used throughout the existing CLI. Native MSVC needs
those interfaces isolated, compiler-specific flags in the libsodium adapter,
libev/Win32 build verification and export/import validation. CMake now rejects
MSVC early with the supported UCRT64/Zig alternatives. Removing the event loop
or inventing replacement crypto implementations is outside this modernization.
### Crypto provider decision
Retain libsodium plus Mbed TLS crypto primitives. The bundled Mbed TLS config
omits TLS, X.509, certificate parsing and handshake machinery; it retains only
cipher/hash primitives needed by protocol compatibility. Libsodium alone cannot
replace AES-128-GCM, software AES fallback, MD5/SHA1 compatibility derivation and
legacy stream modes. PCRE2 is absent in the minimal profile. This removes three
submodule dependencies and reduces what is built without replacing audited
cryptographic algorithms with project-specific implementations.
See [measurements and tradeoffs](performance.md) for reproducible local results.
## Static build CI
The `static-macos` and `static-windows` jobs use Clang, bundled dependencies and
`SS_BUILD_SHARED_LIBRARY=OFF`. They run unit/vendor tests, relocate the installed
static library, build its consumer, and exercise the installed TCP/UDP programs.
The macOS job also exercises SIP003. `tests/check_static_runtime.py` rejects
non-system dylibs and DLLs (including Windows delay imports) in every installed
program and the static consumer. Windows runtime tests exclude toolchain DLL
paths. Windows uses the native [MSYS2 CLANG64 environment](https://www.msys2.org/docs/environments/).
macOS and Windows retain their OS libraries; static builds on these platforms
mean no separately distributed project, crypto, event-loop or toolchain shared
libraries. The `static-linux` job builds the Clang/musl Dockerfile, which also
links libc statically and rejects ELF interpreters and `NEEDED` entries. All
three jobs publish the tested installations as workflow artifacts.
+76
View File
@@ -0,0 +1,76 @@
# Modernization measurements
Local macOS arm64 results, 2026-09-10. Base commit: `8fe386b`; comparison:
`feature/self-contained-portable-c`. Raw JSON lives in [measurements](measurements/).
These are short loopback measurements on one machine, not capacity guarantees.
## Size and runtime dependencies
Unstripped Release executable sizes in bytes:
| Program | Old system-shared | Old static dependencies | Bundled | Minimal |
|---|---:|---:|---:|---:|
| ss-local | 295,912 | 1,326,328 | 756,920 | 416,424 |
| ss-server | 314,360 | 1,500,968 | 962,632 | 622,024 |
| ss-tunnel | 268,680 | 828,712 | 416,440 | 397,704 |
Bundled programs link only macOS OS libraries (`libSystem`, and where needed
`libresolv`). They need no separately installed libev, c-ares, PCRE2, libsodium
or Mbed TLS. Compared with the old static-dependency build, ss-server shrinks
about 36%; minimal shrinks about 59%. Compared with a shared-dependency binary
alone, bundling makes the executable larger; that comparison excludes the old
libraries' disk footprint. Bundled source archives add roughly 12 MB to checkout
and release size. There is no configure-time network access or submodule fetch.
## TCP and resident memory
Median of three 100 MiB transfers using `tests/stress_test.py`, old shared
dependencies versus bundled. Both sets pass all nine transfers.
| Cipher | Before Mbps | Bundled Mbps | Before server RSS after, KiB | Bundled server RSS after, KiB |
|---|---:|---:|---:|---:|
| AES-128-GCM | 7,002 | 7,003 | 3,488 | 3,040 |
| AES-256-GCM | 7,776 | 8,498 | 3,488 | 2,992 |
| ChaCha20-IETF-Poly1305 | 3,439 | 3,268 | 3,472 | 3,072 |
Bundled tunnel RSS after transfer was about 2,000–2,032 KiB versus
2,576–2,592 KiB before. RSS includes OS accounting and shared-page effects;
this is not a proof of bounded memory under arbitrary workloads. Transfer times
are short (roughly 0.1–0.3 s), so the apparent AES-256 improvement and ChaCha
regression need longer controlled measurements before drawing performance claims.
## UDP
Median of three runs, 2,000 sequential 1,200-byte echo packets per run through
SOCKS5 UDP ASSOCIATE. Every packet's payload and relay source port are checked.
| Cipher | Before payload Mbps | Bundled payload Mbps |
|---|---:|---:|
| AES-128-GCM | 77.43 | 75.01 |
| AES-256-GCM | 81.34 | 63.33 |
| ChaCha20-IETF-Poly1305 | 78.65 | 67.89 |
This round-trip benchmark includes Python thread creation, scheduling and SOCKS
overhead. It shows lower observed UDP rates in this run, up to 22%; modernization
is not claimed to improve UDP throughput. The portable libsodium backend is a
maintenance/performance tradeoff. Optimized backends require separate compiler
probes and cross-platform known-answer tests before adoption.
Independently of these timings, the new interoperability test exposed the old
MTU-sized receive buffer truncating 4 KiB UDP payloads. Full datagram reception
now passes 1, 128, 1,200 and 4,096-byte interoperability cases against
shadowsocks-rust in both directions.
## Reproduce
```sh
python3 tests/stress_test.py --bin build-bundled/bin --size 100 --repeat 3 --json tcp.json
python3 tests/measure_udp.py --bin build-bundled/bin --output udp.json
python3 tests/interop.py --bin build-bundled/bin
python3 tests/interop.py --self --bin build-bundled/bin --plugin tests/sip003_fixture.py
```
Run comparisons serially. The baseline was rebuilt from its own worktree using
the preserved original dependency checkouts; no modernization fixes were applied
to that source. The historical curl interop harness obeyed `no_proxy` and could
bypass SOCKS, so its old results are excluded from compatibility evidence.
Submodule libbloom deleted from 437e1add5a.
Submodule libcork deleted from 5ed3f83717.
Submodule libipset deleted from 3ea7fe30ad.
+11 -41
View File
@@ -1,44 +1,14 @@
#!/usr/bin/env bash
set -e
archive() {
export TARBALL_NAME=$1
export TARBALL_OUTDIR=$2
# archive this repo
cd "$(git rev-parse --show-toplevel)"
git archive HEAD --format=tar --prefix="${TARBALL_NAME}/" \
-o "${TARBALL_OUTDIR}/${TARBALL_NAME}.tar"
# archive submodules
git submodule update --init
git submodule foreach --quiet 'git archive HEAD --format=tar \
--prefix="${TARBALL_NAME}/${path}/" \
-o "${TARBALL_OUTDIR}/${TARBALL_NAME}-submodule-${path}-${sha1}.tar"
tar -n --concatenate --file="${TARBALL_OUTDIR}/${TARBALL_NAME}.tar" \
"${TARBALL_OUTDIR}/${TARBALL_NAME}-submodule-${path}-${sha1}.tar"'
gzip -c "${TARBALL_OUTDIR}/${TARBALL_NAME}.tar" > "${TARBALL_OUTDIR}/${TARBALL_NAME}.tar.gz"
# clean-up
git submodule foreach --quiet 'rm ${TARBALL_OUTDIR}/${TARBALL_NAME}-submodule-${path}-${sha1}.tar'
rm "${TARBALL_OUTDIR}/${TARBALL_NAME}.tar"
}
TARGET_TARBALL_NAME=shadowsocks-libev
TARGET_TARBALL_DIR=$(git rev-parse --show-toplevel)
while getopts "n:o:" opt
do
case ${opt} in
o)
TARGET_TARBALL_DIR=$(readlink -f -- $OPTARG)
;;
n)
TARGET_TARBALL_NAME=$OPTARG
;;
\?)
exit 1
;;
# Backward-compatible maintainer entrypoint for the offline release archive.
set -euo pipefail
root=$(git rev-parse --show-toplevel)
name=shadowsocks-libev
directory=$root
while getopts "n:o:" opt; do
case "$opt" in
n) name=$OPTARG ;;
o) directory=$OPTARG ;;
*) exit 1 ;;
esac
done
archive "${TARGET_TARBALL_NAME}" "${TARGET_TARBALL_DIR}"
exec python3 "$root/scripts/source_archive.py" "$directory/$name.tar.gz" --prefix "$name"
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Create an offline source archive from Git's tracked working-tree files.
Maintainer-only tool; building the resulting archive needs neither Git nor
Python (unless integration tests or generated documentation are requested).
"""
import argparse
import gzip
import io
import os
from pathlib import Path
import subprocess
import tarfile
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("output", type=Path)
parser.add_argument("--prefix", default="shadowsocks-libev-3.3.6")
args = parser.parse_args()
root = Path(__file__).resolve().parents[1]
if "/" in args.prefix or args.prefix in ("", ".", ".."):
parser.error("prefix must be a single directory name")
epoch = int(os.environ.get("SOURCE_DATE_EPOCH") or subprocess.check_output(
["git", "show", "-s", "--format=%ct", "HEAD"], cwd=root, text=True).strip())
entries = subprocess.check_output(["git", "ls-files", "--stage", "-z"], cwd=root).split(b"\0")
args.output.parent.mkdir(parents=True, exist_ok=True)
with args.output.open("wb") as raw, gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=epoch) as compressed:
with tarfile.open(fileobj=compressed, mode="w|") as archive:
for entry in entries:
if not entry:
continue
metadata, encoded = entry.split(b"\t", 1)
mode, _, stage = metadata.split()
if stage != b"0" or mode == b"160000":
raise RuntimeError("Unmerged entry or submodule in source archive")
relative = os.fsdecode(encoded)
path = root / relative
info = tarfile.TarInfo(args.prefix + "/" + relative)
info.mtime = epoch
info.mode = int(mode, 8) & 0o777
if path.is_symlink():
info.type = tarfile.SYMTYPE
info.linkname = os.readlink(path)
archive.addfile(info)
else:
content = path.read_bytes()
info.size = len(content)
archive.addfile(info, io.BytesIO(content))
print(args.output)
if __name__ == "__main__":
main()
+64 -239
View File
@@ -10,6 +10,7 @@ endif ()
set(SS_ACL_SOURCE
acl.c
ipset.c
rule.c
)
@@ -18,7 +19,6 @@ set(SS_CRYPTO_SOURCE
aead.c
aead2022.c
aead2022_udp.c
stream.c
base64.c
)
@@ -39,9 +39,14 @@ target_include_directories(blake3 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(blake3 PRIVATE -Wno-error -Wno-unused-function)
set_target_properties(blake3 PROPERTIES POSITION_INDEPENDENT_CODE ON)
set(SS_PLUGIN_SOURCE
plugin.c
)
if(SS_ENABLE_LEGACY)
list(APPEND SS_CRYPTO_SOURCE stream.c)
endif()
if(SS_ENABLE_PLUGINS)
set(SS_PLUGIN_SOURCE plugin.c process.c)
else()
set(SS_PLUGIN_SOURCE plugin_disabled.c)
endif()
set(SS_SHARED_SOURCES
ppbloom.c
@@ -104,243 +109,63 @@ set(SS_REDIR_SOURCE
${SS_PLUGIN_SOURCE}
)
# Apply -Werror only to project sources (not bundled submodules)
set(SS_WERROR_FLAGS -Werror)
# Include directories from Find modules
# MbedTLS must come first to avoid conflicts with other mbedtls versions
# that may be in /opt/homebrew/include (e.g. mbedtls 4.x vs 3.x)
include_directories(BEFORE ${MBEDTLS_INCLUDE_DIRS})
include_directories(${SODIUM_INCLUDE_DIRS})
include_directories(${PCRE2_INCLUDE_DIRS})
include_directories(${CARES_INCLUDE_DIRS})
# Extract mbedTLS lib dir to ensure static/shared libs come from same version
get_filename_component(_MBEDTLS_LIB_DIR "${MBEDTLS_CRYPTO_LIBRARY}" DIRECTORY)
if (WITH_STATIC)
find_library(LIBSODIUM libsodium.a)
find_library(LIBMBEDTLS libmbedtls.a HINTS ${_MBEDTLS_LIB_DIR} NO_DEFAULT_PATH)
find_library(LIBMBEDTLS libmbedtls.a)
find_library(LIBMBEDCRYPTO libmbedcrypto.a HINTS ${_MBEDTLS_LIB_DIR} NO_DEFAULT_PATH)
find_library(LIBMBEDCRYPTO libmbedcrypto.a)
find_library(LIBEV libev.a)
find_library(LIBUDNS NAMES libcares.a libcares_static.a)
find_library(LIBPCRE2 libpcre2-8.a)
# Dependencies we need for static and shared
list(APPEND DEPS
m
blake3
bloom
${LIBEV}
${LIBUDNS}
${LIBPCRE2}
${LIBSODIUM}
${LIBMBEDTLS}
${LIBMBEDCRYPTO}
)
if (MINGW)
list(APPEND DEPS ws2_32 iphlpapi)
add_compile_definitions(CARES_STATICLIB PCRE2_STATIC)
endif ()
endif ()
find_library(LIBSODIUM_SHARED sodium)
find_library(LIBMBEDTLS_SHARED mbedtls HINTS ${_MBEDTLS_LIB_DIR} NO_DEFAULT_PATH)
find_library(LIBMBEDTLS_SHARED mbedtls)
find_library(LIBMBEDCRYPTO_SHARED mbedcrypto HINTS ${_MBEDTLS_LIB_DIR} NO_DEFAULT_PATH)
find_library(LIBMBEDCRYPTO_SHARED mbedcrypto)
find_library(LIBEV_SHARED ev)
find_library(LIBUDNS_SHARED cares)
find_library(LIBPCRE2_SHARED pcre2-8)
if (WITH_EMBEDDED_SRC)
list(APPEND DEPS_SHARED
m
blake3
bloom
cork
ipset
${LIBEV_SHARED}
${LIBUDNS_SHARED}
${LIBPCRE2_SHARED}
${LIBSODIUM_SHARED}
${LIBMBEDTLS_SHARED}
${LIBMBEDCRYPTO_SHARED}
)
else ()
find_library(LIBBLOOM_SHARED bloom)
find_library(LIBCORK_SHARED cork)
find_library(LIBCORKIPSET_SHARED corkipset)
list(APPEND DEPS_SHARED
m
blake3
${LIBBLOOM_SHARED}
${LIBCORK_SHARED}
${LIBCORKIPSET_SHARED}
${LIBEV_SHARED}
${LIBUDNS_SHARED}
${LIBPCRE2_SHARED}
${LIBSODIUM_SHARED}
${LIBMBEDTLS_SHARED}
${LIBMBEDCRYPTO_SHARED}
)
endif ()
# Connmarktos/nftables libraries for ss-server
set(SS_CORE_DEPS ss_ev ss_cares ss_sodium ss_mbedcrypto blake3 bloom Threads::Threads ${SS_MATH_LIBRARY})
set(SS_ACL_DEPS)
if(SS_ENABLE_REGEX)
list(APPEND SS_ACL_DEPS ss_pcre2)
endif()
if(ENABLE_CONNMARKTOS)
list(APPEND DEPS_CONNMARKTOS ${NETFILTER_CONNTRACK_LIB})
if(NFNETLINK_LIB)
list(APPEND DEPS_CONNMARKTOS ${NFNETLINK_LIB})
list(APPEND SS_SERVER_DEPS ${NETFILTER_CONNTRACK_LIB} ${NFNETLINK_LIB})
endif()
if(ENABLE_NFTABLES)
list(APPEND SS_SERVER_DEPS ${MNL_LIB} ${NFTNL_LIB})
endif()
function(ss_program name module sources)
add_executable(${name} ${sources})
target_compile_definitions(${name} PRIVATE ${module})
target_link_libraries(${name} PRIVATE ${SS_CORE_DEPS} ${ARGN})
ss_project_options(${name})
install(TARGETS ${name} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endfunction()
if(SS_BUILD_EXECUTABLES)
ss_program(ss-local MODULE_LOCAL "${SS_LOCAL_SOURCE}" ${SS_ACL_DEPS})
ss_program(ss-server MODULE_REMOTE "${SS_SERVER_SOURCE}" ${SS_ACL_DEPS} ${SS_SERVER_DEPS})
ss_program(ss-tunnel MODULE_TUNNEL "${SS_TUNNEL_SOURCE}")
if(WITH_SS_MANAGER)
ss_program(ss-manager MODULE_MANAGER "${SS_MANAGER_SOURCE}")
endif()
if(WITH_SS_REDIR)
if(NOT LINUX)
message(FATAL_ERROR "ss-redir requires Linux")
endif()
ss_program(ss-redir MODULE_REDIR "${SS_REDIR_SOURCE}")
endif()
endif()
if(ENABLE_NFTABLES)
list(APPEND DEPS_NFTABLES ${MNL_LIB} ${NFTNL_LIB})
foreach(kind STATIC SHARED)
if(SS_BUILD_${kind}_LIBRARY)
string(TOLOWER "${kind}" suffix)
set(target shadowsocks-libev-${suffix})
add_library(${target} ${kind} ${LIBSHADOWSOCKS_LIBEV_SOURCE})
target_compile_definitions(${target} PRIVATE MODULE_LOCAL LIB_ONLY)
target_include_directories(${target} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_link_libraries(${target} PRIVATE ${SS_CORE_DEPS} ${SS_ACL_DEPS})
set_target_properties(${target} PROPERTIES OUTPUT_NAME shadowsocks-libev)
if(kind STREQUAL "SHARED")
set_target_properties(${target} PROPERTIES VERSION 2.0.0 SOVERSION 2)
endif()
ss_project_options(${target})
install(TARGETS ${target}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
endforeach()
if(SS_BUILD_STATIC_LIBRARY OR SS_BUILD_SHARED_LIBRARY)
install(FILES shadowsocks.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
endif()
find_package (Threads)
if (WITH_STATIC)
# ------------------------------------------------------------------
# Static
# By default we use normal name for static, all shared targets will add a `-shared' suffix
add_executable(ss-server ${SS_SERVER_SOURCE})
add_executable(ss-tunnel ${SS_TUNNEL_SOURCE})
if (WITH_SS_MANAGER)
add_executable(ss-manager ${SS_MANAGER_SOURCE})
else ()
add_executable(ss-manager EXCLUDE_FROM_ALL ${SS_MANAGER_SOURCE})
endif ()
add_executable(ss-local ${SS_LOCAL_SOURCE})
if (WITH_SS_REDIR)
add_executable(ss-redir ${SS_REDIR_SOURCE})
else ()
add_executable(ss-redir EXCLUDE_FROM_ALL ${SS_REDIR_SOURCE})
endif ()
add_library(shadowsocks-libev STATIC ${LIBSHADOWSOCKS_LIBEV_SOURCE})
target_compile_definitions(ss-server PUBLIC -DMODULE_REMOTE)
target_compile_definitions(ss-tunnel PUBLIC -DMODULE_TUNNEL)
target_compile_definitions(ss-manager PUBLIC -DMODULE_MANAGER)
target_compile_definitions(ss-local PUBLIC -DMODULE_LOCAL)
target_compile_definitions(ss-redir PUBLIC -DMODULE_REDIR)
target_compile_definitions(shadowsocks-libev PUBLIC -DMODULE_LOCAL -DLIB_ONLY)
target_compile_options(ss-server PRIVATE ${SS_WERROR_FLAGS})
target_compile_options(ss-tunnel PRIVATE ${SS_WERROR_FLAGS})
target_compile_options(ss-manager PRIVATE ${SS_WERROR_FLAGS})
target_compile_options(ss-local PRIVATE ${SS_WERROR_FLAGS})
target_compile_options(ss-redir PRIVATE ${SS_WERROR_FLAGS})
target_compile_options(shadowsocks-libev PRIVATE ${SS_WERROR_FLAGS})
# Connmarktos/nftables compile definitions
if(ENABLE_CONNMARKTOS)
target_compile_definitions(ss-server PUBLIC -DUSE_NFCONNTRACK_TOS)
endif()
if(ENABLE_NFTABLES)
target_compile_definitions(ss-server PUBLIC -DUSE_NFTABLES)
endif()
target_include_directories(shadowsocks-libev PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(ss-server cork ipset ${DEPS} ${DEPS_CONNMARKTOS} ${DEPS_NFTABLES})
target_link_libraries(ss-tunnel cork ${DEPS})
target_link_libraries(ss-manager m bloom cork ${LIBEV} ${LIBUDNS})
target_link_libraries(ss-local cork ipset ${DEPS})
target_link_libraries(ss-redir cork ipset ${DEPS})
target_link_libraries(shadowsocks-libev cork ipset ${DEPS})
endif ()
# ------------------------------------------------------------------
# Shared
# Shared target will have a `-shared' suffix
# All shared dependency also have `-shared' suffix
# For shared binary, we still use the same name as static, without `-shared', but will output to shared directory
add_executable(ss-server-shared ${SS_SERVER_SOURCE})
add_executable(ss-tunnel-shared ${SS_TUNNEL_SOURCE})
if (WITH_SS_MANAGER)
add_executable(ss-manager-shared ${SS_MANAGER_SOURCE})
else ()
add_executable(ss-manager-shared EXCLUDE_FROM_ALL ${SS_MANAGER_SOURCE})
endif ()
add_executable(ss-local-shared ${SS_LOCAL_SOURCE})
if (WITH_SS_REDIR)
add_executable(ss-redir-shared ${SS_REDIR_SOURCE})
else ()
add_executable(ss-redir-shared EXCLUDE_FROM_ALL ${SS_REDIR_SOURCE})
endif ()
add_library(shadowsocks-libev-shared SHARED ${LIBSHADOWSOCKS_LIBEV_SOURCE})
target_compile_definitions(ss-server-shared PUBLIC -DMODULE_REMOTE)
target_compile_definitions(ss-tunnel-shared PUBLIC -DMODULE_TUNNEL)
target_compile_definitions(ss-manager-shared PUBLIC -DMODULE_MANAGER)
target_compile_definitions(ss-local-shared PUBLIC -DMODULE_LOCAL)
target_compile_definitions(ss-redir-shared PUBLIC -DMODULE_REDIR)
target_compile_definitions(shadowsocks-libev-shared PUBLIC -DMODULE_LOCAL -DLIB_ONLY)
target_compile_options(ss-server-shared PRIVATE ${SS_WERROR_FLAGS})
target_compile_options(ss-tunnel-shared PRIVATE ${SS_WERROR_FLAGS})
target_compile_options(ss-manager-shared PRIVATE ${SS_WERROR_FLAGS})
target_compile_options(ss-local-shared PRIVATE ${SS_WERROR_FLAGS})
target_compile_options(ss-redir-shared PRIVATE ${SS_WERROR_FLAGS})
target_compile_options(shadowsocks-libev-shared PRIVATE ${SS_WERROR_FLAGS})
# Connmarktos/nftables compile definitions for shared
if(ENABLE_CONNMARKTOS)
target_compile_definitions(ss-server-shared PUBLIC -DUSE_NFCONNTRACK_TOS)
endif()
if(ENABLE_NFTABLES)
target_compile_definitions(ss-server-shared PUBLIC -DUSE_NFTABLES)
endif()
target_include_directories(shadowsocks-libev-shared PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(ss-server-shared ${DEPS_SHARED} ${DEPS_CONNMARKTOS} ${DEPS_NFTABLES})
target_link_libraries(ss-tunnel-shared ${DEPS_SHARED})
target_link_libraries(ss-manager-shared ${CMAKE_THREAD_LIBS_INIT} ${LIBEV_SHARED} ${LIBUDNS_SHARED} ${DEPS_SHARED})
target_link_libraries(ss-local-shared ${DEPS_SHARED})
target_link_libraries(ss-redir-shared ${DEPS_SHARED})
target_link_libraries(shadowsocks-libev-shared ${DEPS_SHARED})
set_target_properties(ss-server-shared PROPERTIES OUTPUT_NAME ss-server)
set_target_properties(ss-tunnel-shared PROPERTIES OUTPUT_NAME ss-tunnel)
set_target_properties(ss-manager-shared PROPERTIES OUTPUT_NAME ss-manager)
set_target_properties(ss-local-shared PROPERTIES OUTPUT_NAME ss-local)
set_target_properties(ss-redir-shared PROPERTIES OUTPUT_NAME ss-redir)
set_target_properties(ss-server-shared ss-tunnel-shared ss-manager-shared ss-local-shared ss-redir-shared
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${RUNTIME_SHARED_OUTPUT_DIRECTORY}
)
set_target_properties(shadowsocks-libev-shared PROPERTIES OUTPUT_NAME shadowsocks-libev)
set_target_properties(shadowsocks-libev-shared PROPERTIES VERSION 2.0.0 SOVERSION 2)
target_compile_definitions(shadowsocks-libev-shared PUBLIC -DMODULE_LOCAL)
target_link_libraries(shadowsocks-libev-shared ${DEPS_SHARED})
# ------------------------------------------------------------------
# Misc
# Recommend to install shared by default
install(DIRECTORY ${RUNTIME_SHARED_OUTPUT_DIRECTORY}/
USE_SOURCE_PERMISSIONS
DESTINATION ${CMAKE_INSTALL_BINDIR})
if (WITH_STATIC)
install(TARGETS shadowsocks-libev
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif ()
install(TARGETS shadowsocks-libev-shared
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES shadowsocks.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
add_custom_target(distclean
COMMAND ${CMAKE_COMMAND} -E echo WARNING: distclean target is not functional
COMMAND ${CMAKE_COMMAND} -E echo Use 'git clean -fdx' instead
VERBATIM
)
+73 -66
View File
@@ -25,33 +25,30 @@
#endif
#include <ctype.h>
#include <string.h>
#ifdef USE_SYSTEM_SHARED_LIB
#include <libcorkipset/ipset.h>
#else
#include <ipset/ipset.h>
#endif
#include "ipset.h"
#include "core.h"
#include "rule.h"
#include "netutils.h"
#include "utils.h"
#include "cache.h"
#include "acl.h"
static struct ip_set white_list_ipv4;
static struct ip_set white_list_ipv6;
static struct ss_ipset white_list_ipv4;
static struct ss_ipset white_list_ipv6;
static struct ip_set black_list_ipv4;
static struct ip_set black_list_ipv6;
static struct ss_ipset black_list_ipv4;
static struct ss_ipset black_list_ipv6;
static struct cork_dllist black_list_rules;
static struct cork_dllist white_list_rules;
static struct ss_list black_list_rules;
static struct ss_list white_list_rules;
static int acl_mode = BLACK_LIST;
static struct ip_set outbound_block_list_ipv4;
static struct ip_set outbound_block_list_ipv6;
static struct cork_dllist outbound_block_list_rules;
static struct ss_ipset outbound_block_list_ipv4;
static struct ss_ipset outbound_block_list_ipv6;
static struct ss_list outbound_block_list_rules;
static int
parse_addr_cidr(const char *str, char *host, size_t host_len, int *cidr)
@@ -120,22 +117,21 @@ init_acl(const char *path)
}
// initialize ipset
ipset_init_library();
ipset_init(&white_list_ipv4);
ipset_init(&white_list_ipv6);
ipset_init(&black_list_ipv4);
ipset_init(&black_list_ipv6);
ipset_init(&outbound_block_list_ipv4);
ipset_init(&outbound_block_list_ipv6);
ss_ipset_clear(&white_list_ipv4);
ss_ipset_clear(&white_list_ipv6);
ss_ipset_clear(&black_list_ipv4);
ss_ipset_clear(&black_list_ipv6);
ss_ipset_clear(&outbound_block_list_ipv4);
ss_ipset_clear(&outbound_block_list_ipv6);
cork_dllist_init(&black_list_rules);
cork_dllist_init(&white_list_rules);
cork_dllist_init(&outbound_block_list_rules);
ss_list_init(&black_list_rules);
ss_list_init(&white_list_rules);
ss_list_init(&outbound_block_list_rules);
struct ip_set *list_ipv4 = &black_list_ipv4;
struct ip_set *list_ipv6 = &black_list_ipv6;
struct cork_dllist *rules = &black_list_rules;
struct ss_ipset *list_ipv4 = &black_list_ipv4;
struct ss_ipset *list_ipv6 = &black_list_ipv6;
struct ss_list *rules = &black_list_rules;
FILE *f = fopen(path, "r");
if (f == NULL) {
@@ -145,8 +141,7 @@ init_acl(const char *path)
char buf[MAX_HOSTNAME_LEN];
while (!feof(f))
if (fgets(buf, 256, f)) {
while (!ferror(f) && !feof(f) && fgets(buf, sizeof(buf), f) != NULL) {
// Discards the whole line if longer than 255 characters
int long_line = 0; // 1: Long 2: Error
while ((strlen(buf) == 255) && (buf[254] != '\n')) {
@@ -214,8 +209,8 @@ init_acl(const char *path)
continue;
}
struct cork_ip addr;
int err = cork_ip_init(&addr, host);
struct ss_ip addr;
int err = ss_ip_init(&addr, host);
if (!err) {
if (addr.version == 4) {
if (cidr > 32 || cidr == -2) {
@@ -223,9 +218,9 @@ init_acl(const char *path)
continue;
}
if (cidr >= 0) {
ipset_ipv4_add_network(list_ipv4, &(addr.ip.v4), cidr);
ss_ipset_assign(list_ipv4, addr.bytes, 32, (unsigned)cidr, true);
} else {
ipset_ipv4_add(list_ipv4, &(addr.ip.v4));
ss_ipset_assign(list_ipv4, addr.bytes, 32, 32, true);
}
} else if (addr.version == 6) {
if (cidr > 128 || cidr == -2) {
@@ -233,9 +228,9 @@ init_acl(const char *path)
continue;
}
if (cidr >= 0) {
ipset_ipv6_add_network(list_ipv6, &(addr.ip.v6), cidr);
ss_ipset_assign(list_ipv6, addr.bytes, 128, (unsigned)cidr, true);
} else {
ipset_ipv6_add(list_ipv6, &(addr.ip.v6));
ss_ipset_assign(list_ipv6, addr.bytes, 128, 128, true);
}
}
} else {
@@ -243,7 +238,15 @@ init_acl(const char *path)
if (rule == NULL) {
continue;
}
if (accept_rule_arg(rule, line) != 1 || init_rule(rule) != 1) {
int status = accept_rule_arg(rule, line);
if (status == 1) status = init_rule(rule);
if (status < 0) {
free_rule(rule);
fclose(f);
free_acl();
return -1;
}
if (status != 1) {
free_rule(rule);
continue;
}
@@ -251,17 +254,21 @@ init_acl(const char *path)
}
}
int failed = ferror(f);
fclose(f);
if (failed) {
free_acl();
return -1;
}
return 0;
}
void
free_rules(struct cork_dllist *rules)
free_rules(struct ss_list *rules)
{
struct cork_dllist_item *iter;
while ((iter = cork_dllist_head(rules)) != NULL) {
rule_t *rule = cork_container_of(iter, rule_t, entries);
struct ss_list_item *iter;
while ((iter = ss_list_head(rules)) != NULL) {
rule_t *rule = ss_container_of(iter, rule_t, entries);
remove_rule(rule);
}
}
@@ -269,12 +276,12 @@ free_rules(struct cork_dllist *rules)
void
free_acl(void)
{
ipset_done(&black_list_ipv4);
ipset_done(&black_list_ipv6);
ipset_done(&white_list_ipv4);
ipset_done(&white_list_ipv6);
ipset_done(&outbound_block_list_ipv4);
ipset_done(&outbound_block_list_ipv6);
ss_ipset_clear(&black_list_ipv4);
ss_ipset_clear(&black_list_ipv6);
ss_ipset_clear(&white_list_ipv4);
ss_ipset_clear(&white_list_ipv6);
ss_ipset_clear(&outbound_block_list_ipv4);
ss_ipset_clear(&outbound_block_list_ipv6);
free_rules(&black_list_rules);
free_rules(&white_list_rules);
@@ -295,9 +302,9 @@ get_acl_mode(void)
int
acl_match_host(const char *host)
{
struct cork_ip addr;
struct ss_ip addr;
int ret = 0;
int err = cork_ip_init(&addr, host);
int err = ss_ip_init(&addr, host);
if (err) {
int host_len = strlen(host);
@@ -309,14 +316,14 @@ acl_match_host(const char *host)
}
if (addr.version == 4) {
if (ipset_contains_ipv4(&black_list_ipv4, &(addr.ip.v4)))
if (ss_ipset_contains(&black_list_ipv4, addr.bytes, 32))
ret = 1;
else if (ipset_contains_ipv4(&white_list_ipv4, &(addr.ip.v4)))
else if (ss_ipset_contains(&white_list_ipv4, addr.bytes, 32))
ret = -1;
} else if (addr.version == 6) {
if (ipset_contains_ipv6(&black_list_ipv6, &(addr.ip.v6)))
if (ss_ipset_contains(&black_list_ipv6, addr.bytes, 128))
ret = 1;
else if (ipset_contains_ipv6(&white_list_ipv6, &(addr.ip.v6)))
else if (ss_ipset_contains(&white_list_ipv6, addr.bytes, 128))
ret = -1;
}
@@ -326,16 +333,16 @@ acl_match_host(const char *host)
int
acl_add_ip(const char *ip)
{
struct cork_ip addr;
int err = cork_ip_init(&addr, ip);
struct ss_ip addr;
int err = ss_ip_init(&addr, ip);
if (err) {
return -1;
}
if (addr.version == 4) {
ipset_ipv4_add(&black_list_ipv4, &(addr.ip.v4));
ss_ipset_assign(&black_list_ipv4, addr.bytes, 32, 32, true);
} else if (addr.version == 6) {
ipset_ipv6_add(&black_list_ipv6, &(addr.ip.v6));
ss_ipset_assign(&black_list_ipv6, addr.bytes, 128, 128, true);
}
return 0;
@@ -344,16 +351,16 @@ acl_add_ip(const char *ip)
int
acl_remove_ip(const char *ip)
{
struct cork_ip addr;
int err = cork_ip_init(&addr, ip);
struct ss_ip addr;
int err = ss_ip_init(&addr, ip);
if (err) {
return -1;
}
if (addr.version == 4) {
ipset_ipv4_remove(&black_list_ipv4, &(addr.ip.v4));
ss_ipset_assign(&black_list_ipv4, addr.bytes, 32, 32, false);
} else if (addr.version == 6) {
ipset_ipv6_remove(&black_list_ipv6, &(addr.ip.v6));
ss_ipset_assign(&black_list_ipv6, addr.bytes, 128, 128, false);
}
return 0;
@@ -366,9 +373,9 @@ acl_remove_ip(const char *ip)
int
outbound_block_match_host(const char *host)
{
struct cork_ip addr;
struct ss_ip addr;
int ret = 0;
int err = cork_ip_init(&addr, host);
int err = ss_ip_init(&addr, host);
if (err) {
int host_len = strlen(host);
@@ -378,10 +385,10 @@ outbound_block_match_host(const char *host)
}
if (addr.version == 4) {
if (ipset_contains_ipv4(&outbound_block_list_ipv4, &(addr.ip.v4)))
if (ss_ipset_contains(&outbound_block_list_ipv4, addr.bytes, 32))
ret = 1;
} else if (addr.version == 6) {
if (ipset_contains_ipv6(&outbound_block_list_ipv6, &(addr.ip.v6)))
if (ss_ipset_contains(&outbound_block_list_ipv6, addr.bytes, 128))
ret = 1;
}
+1 -1
View File
@@ -39,7 +39,7 @@
#include "aead.h"
#include "aead_internal.h"
#include "utils.h"
#include "winsock.h"
#include "ss_windows.h"
#define NONE (-1)
+6 -5
View File
@@ -33,6 +33,7 @@
#include <stdlib.h>
#include "cache.h"
#include "platform.h"
#include "utils.h"
/** Creates a new cache object
@@ -118,7 +119,7 @@ cache_delete(struct cache *cache, int keep_data)
* @return EINVAL if cache is NULL, 0 otherwise
*/
int
cache_clear(struct cache *cache, ev_tstamp age)
cache_clear(struct cache *cache, double age)
{
struct cache_entry *entry, *tmp;
@@ -126,7 +127,7 @@ cache_clear(struct cache *cache, ev_tstamp age)
return EINVAL;
}
ev_tstamp now = ev_time();
double now = ss_monotonic_time();
HASH_ITER(hh, cache->entries, entry, tmp){
if (now - entry->ts > age) {
@@ -220,7 +221,7 @@ cache_lookup(struct cache *cache, char *key, size_t key_len, void *result)
HASH_FIND(hh, cache->entries, key, key_len, tmp);
if (tmp) {
HASH_DELETE(hh, cache->entries, tmp);
tmp->ts = ev_time();
tmp->ts = ss_monotonic_time();
// NOLINTNEXTLINE(clang-analyzer-core.DivideZero): uthash bucket count is never zero
HASH_ADD_KEYPTR(hh, cache->entries, tmp->key, key_len, tmp);
*dirty_hack = tmp->data;
@@ -243,7 +244,7 @@ cache_key_exist(struct cache *cache, char *key, size_t key_len)
HASH_FIND(hh, cache->entries, key, key_len, tmp);
if (tmp) {
HASH_DELETE(hh, cache->entries, tmp);
tmp->ts = ev_time();
tmp->ts = ss_monotonic_time();
// NOLINTNEXTLINE(clang-analyzer-core.DivideZero): uthash bucket count is never zero
HASH_ADD_KEYPTR(hh, cache->entries, tmp->key, key_len, tmp);
return 1;
@@ -287,7 +288,7 @@ cache_insert(struct cache *cache, char *key, size_t key_len, void *data)
entry->key[key_len] = 0;
entry->data = data;
entry->ts = ev_time();
entry->ts = ss_monotonic_time();
// NOLINTNEXTLINE(clang-analyzer-core.DivideZero): uthash bucket count is never zero
HASH_ADD_KEYPTR(hh, cache->entries, entry->key, key_len, entry);
+2 -8
View File
@@ -30,19 +30,13 @@
#include "uthash.h"
#ifdef HAVE_LIBEV_EV_H
#include <libev/ev.h>
#else
#include <ev.h>
#endif
/**
* A cache entry
*/
struct cache_entry {
char *key; /**<The key */
void *data; /**<Payload */
ev_tstamp ts; /**<Timestamp */
double ts; /**<Timestamp */
UT_hash_handle hh; /**<Hash Handle for uthash */
};
@@ -58,7 +52,7 @@ struct cache {
int cache_create(struct cache **dst, const size_t capacity,
void (*free_cb)(void *key, void *element));
int cache_delete(struct cache *cache, int keep_data);
int cache_clear(struct cache *cache, ev_tstamp age);
int cache_clear(struct cache *cache, double age);
int cache_lookup(struct cache *cache, char *key, size_t key_len, void *result);
int cache_insert(struct cache *cache, char *key, size_t key_len, void *data);
int cache_remove(struct cache *cache, char *key, size_t key_len);
+74
View File
@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#ifndef SS_CORE_H
#define SS_CORE_H
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include "platform.h"
#define ss_container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member)))
struct ss_list_item {
struct ss_list_item *next, *prev;
};
struct ss_list {
struct ss_list_item head;
};
static inline void
ss_list_init(struct ss_list *list)
{
list->head.next = list->head.prev = &list->head;
}
static inline void
ss_list_add(struct ss_list *list, struct ss_list_item *item)
{
item->prev = list->head.prev;
item->next = &list->head;
item->prev->next = item;
list->head.prev = item;
}
static inline void
ss_list_remove(struct ss_list_item *item)
{
item->prev->next = item->next;
item->next->prev = item->prev;
}
static inline struct ss_list_item *
ss_list_start(const struct ss_list *list)
{
return list->head.next;
}
static inline bool
ss_list_is_end(const struct ss_list *list, const struct ss_list_item *item)
{
return item == &list->head;
}
static inline struct ss_list_item *
ss_list_head(const struct ss_list *list)
{
return ss_list_is_end(list, list->head.next) ? NULL : list->head.next;
}
/* Cache the next link before the body so the current item may be freed. */
#define ss_list_foreach_void(list, item, after) \
for ((item) = ss_list_start(list), (after) = (item)->next; \
!ss_list_is_end((list), (item)); (item) = (after), (after) = (item)->next)
struct ss_ip {
unsigned version;
uint8_t bytes[16];
};
static inline int
ss_ip_init(struct ss_ip *ip, const char *text)
{
if (inet_pton(AF_INET, text, ip->bytes) == 1) {
ip->version = 4;
return 0;
}
if (inet_pton(AF_INET6, text, ip->bytes) == 1) {
ip->version = 6;
return 0;
}
ip->version = 0;
return -1;
}
#endif
+3 -1
View File
@@ -150,6 +150,7 @@ crypto_init(const char *password, const char *key, const char *method)
#endif
if (method != NULL) {
#if SS_ENABLE_LEGACY
for (i = 0; i < STREAM_CIPHER_NUM; i++)
if (strcmp(method, supported_stream_ciphers[i]) == 0) {
m = i;
@@ -174,6 +175,7 @@ crypto_init(const char *password, const char *key, const char *method)
return crypto;
}
#endif
for (i = 0; i < AEAD_CIPHER_NUM; i++)
if (strcmp(method, supported_aead_ciphers[i]) == 0) {
m = i;
@@ -203,7 +205,7 @@ crypto_init(const char *password, const char *key, const char *method)
}
}
LOGE("invalid cipher name: %s", method);
LOGE("unsupported or disabled cipher: %s", method);
return NULL;
}
+1 -1
View File
@@ -49,7 +49,7 @@ typedef mbedtls_md_info_t digest_type_t;
#define MAX_NONCE_LENGTH 32
#define MAX_MD_SIZE MBEDTLS_MD_MAX_SIZE
/* we must have MBEDTLS_CIPHER_MODE_CFB defined */
#if !defined(MBEDTLS_CIPHER_MODE_CFB)
#if SS_ENABLE_LEGACY && !defined(MBEDTLS_CIPHER_MODE_CFB)
#error Cipher Feedback mode a.k.a CFB not supported by your mbed TLS.
#endif
#ifndef MBEDTLS_GCM_C
+99
View File
@@ -0,0 +1,99 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "ipset.h"
#include <assert.h>
#include <stdlib.h>
#include "utils.h"
/* NULL is an empty subtree. A full node represents every address below it.
* Recursion is bounded by the address width, never by the number of rules. */
struct ss_ipset_node {
struct ss_ipset_node *child[2];
bool full;
};
static struct ss_ipset_node *
node_new(bool full)
{
struct ss_ipset_node *node = ss_malloc(sizeof(*node));
node->child[0] = node->child[1] = NULL;
node->full = full;
return node;
}
static void
node_free(struct ss_ipset_node *node)
{
if (node != NULL) {
node_free(node->child[0]);
node_free(node->child[1]);
free(node);
}
}
void
ss_ipset_clear(struct ss_ipset *set)
{
node_free(set->root);
set->root = NULL;
}
static struct ss_ipset_node *
node_assign(struct ss_ipset_node *node, const uint8_t *address,
unsigned bit, unsigned prefix, bool present)
{
if ((node == NULL && !present) || (node != NULL && node->full && present)) {
return node;
}
if (bit == prefix) {
node_free(node);
return present ? node_new(true) : NULL;
}
if (node == NULL) {
node = node_new(false);
} else if (node->full) {
node->child[0] = node_new(true);
node->child[1] = node_new(true);
node->full = false;
}
unsigned side = (address[bit / 8] >> (7 - bit % 8)) & 1;
node->child[side] = node_assign(node->child[side], address, bit + 1, prefix, present);
if (node->child[0] == NULL && node->child[1] == NULL) {
free(node);
return NULL;
}
if (node->child[0] != NULL && node->child[0]->full &&
node->child[1] != NULL && node->child[1]->full) {
node_free(node->child[0]);
node_free(node->child[1]);
node->child[0] = node->child[1] = NULL;
node->full = true;
}
return node;
}
void
ss_ipset_assign(struct ss_ipset *set, const uint8_t *address,
unsigned width, unsigned prefix, bool present)
{
assert(width == 32 || width == 128);
if (prefix <= width) {
set->root = node_assign(set->root, address, 0, prefix, present);
}
}
bool
ss_ipset_contains(const struct ss_ipset *set, const uint8_t *address, unsigned width)
{
const struct ss_ipset_node *node = set->root;
for (unsigned bit = 0; node != NULL; bit++) {
if (node->full) {
return true;
}
if (bit == width) {
break;
}
unsigned side = (address[bit / 8] >> (7 - bit % 8)) & 1;
node = node->child[side];
}
return false;
}
+18
View File
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#ifndef SS_IPSET_H
#define SS_IPSET_H
#include <stdbool.h>
#include <stdint.h>
struct ss_ipset_node;
struct ss_ipset {
struct ss_ipset_node *root;
};
/* Addresses are network-order bytes; width is 32 or 128. A set stores one
* address family. Removing a host from a network creates a hole in that net. */
void ss_ipset_clear(struct ss_ipset *set);
void ss_ipset_assign(struct ss_ipset *set, const uint8_t *address,
unsigned width, unsigned prefix, bool present);
bool ss_ipset_contains(const struct ss_ipset *set, const uint8_t *address, unsigned width);
#endif
+3 -3
View File
@@ -33,7 +33,7 @@
#include "json.h"
#include "string.h"
#include <libcork/core.h>
#include "core.h"
#define check_json_value_type(value, expected_type, message) \
do { \
@@ -88,8 +88,8 @@ parse_addr(const char *str_in, ss_addr_t *addr)
char *str = strdup(str_in);
len = strlen(str_in);
struct cork_ip ip;
if (cork_ip_init(&ip, str) != -1) {
struct ss_ip ip;
if (ss_ip_init(&ip, str) != -1) {
addr->host = str;
addr->port = NULL;
return;
+5 -4
View File
@@ -778,9 +778,10 @@ json_value * json_parse_ex (json_settings * settings,
}
/* Overflow check for integer */
long prev = top->u.integer;
if ((prev > 0 && (prev > (LONG_MAX / 10) || (prev == (LONG_MAX / 10) && (b - '0') > (LONG_MAX % 10)))) ||
(prev < 0 && (prev < (LONG_MIN / 10) || (prev == (LONG_MIN / 10) && -(b - '0') < (LONG_MIN % 10))))) {
json_int_t prev = top->u.integer;
/* Digits accumulate as a positive int64_t magnitude;
* long is only 32 bits on LLP64 platforms such as Windows. */
if (prev > (INT64_MAX / 10) || (prev == (INT64_MAX / 10) && (b - '0') > (INT64_MAX % 10))) {
sprintf(error, "%d:%d: Integer too large (overflow)", line_and_col);
goto e_failed;
}
@@ -789,7 +790,7 @@ json_value * json_parse_ex (json_settings * settings,
}
/* Overflow check for fraction */
if (num_fraction > (LONG_MAX / 10) || (num_fraction == (LONG_MAX / 10) && (b - '0') > (LONG_MAX % 10))) {
if (num_fraction > (INT64_MAX / 10) || (num_fraction == (INT64_MAX / 10) && (b - '0') > (INT64_MAX % 10))) {
sprintf(error, "%d:%d: Fraction too large (overflow)", line_and_col);
goto e_failed;
}
+55 -55
View File
@@ -49,7 +49,7 @@
#define SET_INTERFACE
#endif
#include <libcork/core.h>
#include "core.h"
#include "netutils.h"
#include "ssurl.h"
@@ -58,7 +58,7 @@
#include "acl.h"
#include "plugin.h"
#include "local.h"
#include "winsock.h"
#include "ss_windows.h"
#ifndef LIB_ONLY
#ifdef __APPLE__
@@ -149,7 +149,7 @@ static void close_and_free_server(EV_P_ server_t *server);
static remote_t *new_remote(int fd, int timeout);
static server_t *new_server(int fd);
static struct cork_dllist connections;
static struct ss_list connections;
#ifndef __MINGW32__
int
@@ -189,15 +189,15 @@ create_and_bind(const char *addr, const char *port)
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
listen_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
listen_sock = ss_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (listen_sock == -1) {
continue;
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
if (reuse_port) {
int err = set_reuseport(listen_sock);
@@ -207,11 +207,11 @@ create_and_bind(const char *addr, const char *port)
}
if (tcp_incoming_sndbuf > 0) {
setsockopt(listen_sock, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof(int));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof(int));
}
if (tcp_incoming_rcvbuf > 0) {
setsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof(int));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof(int));
}
s = bind(listen_sock, rp->ai_addr, rp->ai_addrlen);
@@ -222,7 +222,7 @@ create_and_bind(const char *addr, const char *port)
ERROR("bind");
}
close(listen_sock);
ss_socket_close(listen_sock);
listen_sock = -1;
}
@@ -265,9 +265,9 @@ launch_or_create(const char *addr, const char *port)
static void
free_connections(struct ev_loop *loop)
{
struct cork_dllist_item *curr, *next;
cork_dllist_foreach_void(&connections, curr, next) {
server_t *server = cork_container_of(curr, server_t, entries);
struct ss_list_item *curr, *next;
ss_list_foreach_void(&connections, curr, next) {
server_t *server = ss_container_of(curr, server_t, entries);
remote_t *remote = server->remote;
close_and_free_server(loop, server);
close_and_free_remote(loop, remote);
@@ -277,7 +277,7 @@ free_connections(struct ev_loop *loop)
static void
delayed_connect_cb(EV_P_ ev_timer *watcher, int revents)
{
server_t *server = cork_container_of(watcher, server_t,
server_t *server = ss_container_of(watcher, server_t,
delayed_connect_watcher);
server_recv_cb(EV_A_ & server->recv_ctx->io, revents);
@@ -296,7 +296,7 @@ server_handshake_reply(EV_P_ ev_io *w, int udp_assc, struct socks5_response *res
if (udp_assc) {
socklen_t addr_len = sizeof(sock_addr);
if (getsockname(server->fd, (struct sockaddr *)&sock_addr, &addr_len) < 0) {
LOGE("getsockname: %s", strerror(errno));
LOGE("getsockname: %s", strerror(ss_socket_error()));
response->rep = SOCKS5_REP_CONN_REFUSED;
send(server->fd, (char *)response, sizeof(struct socks5_response), 0);
close_and_free_remote(EV_A_ remote);
@@ -392,9 +392,9 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
if (acl || verbose) {
uint16_t p = load16_be(buf->data + request_len + in_addr_len);
if (!inet_ntop(AF_INET, (const void *)(buf->data + request_len),
if (!ss_inet_ntop(AF_INET, (const void *)(buf->data + request_len),
ip, INET_ADDRSTRLEN)) {
LOGI("inet_ntop(AF_INET): %s", strerror(errno));
LOGI("ss_inet_ntop(AF_INET): %s", strerror(ss_socket_error()));
ip[0] = '\0';
}
snprintf(port, sizeof(port), "%d", p);
@@ -427,9 +427,9 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
if (acl || verbose) {
uint16_t p = load16_be(buf->data + request_len + in6_addr_len);
if (!inet_ntop(AF_INET6, (const void *)(buf->data + request_len),
if (!ss_inet_ntop(AF_INET6, (const void *)(buf->data + request_len),
ip, INET6_ADDRSTRLEN)) {
LOGI("inet_ntop(AF_INET6): %s", strerror(errno));
LOGI("ss_inet_ntop(AF_INET6): %s", strerror(ss_socket_error()));
ip[0] = '\0';
}
snprintf(port, sizeof(port), "%d", p);
@@ -494,14 +494,14 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
case AF_INET:
{
struct sockaddr_in *addr_in = (struct sockaddr_in *)&storage;
if (!inet_ntop(AF_INET, &(addr_in->sin_addr), ip, INET_ADDRSTRLEN))
if (!ss_inet_ntop(AF_INET, &(addr_in->sin_addr), ip, INET_ADDRSTRLEN))
goto not_bypass;
break;
}
case AF_INET6:
{
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&storage;
if (!inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip, INET6_ADDRSTRLEN))
if (!ss_inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip, INET6_ADDRSTRLEN))
goto not_bypass;
break;
}
@@ -650,7 +650,7 @@ server_stream(EV_P_ ev_io *w, buffer_t *buf)
// connecting, wait until connected
int r = connect(remote->fd, (struct sockaddr *)&(remote->addr), remote->addr_len);
if (r == -1 && errno != CONNECT_IN_PROGRESS) {
if (r == -1 && ss_socket_error() != CONNECT_IN_PROGRESS) {
ERROR("connect");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
@@ -672,7 +672,7 @@ server_stream(EV_P_ ev_io *w, buffer_t *buf)
do {
int optval = 1;
// Set fast open option
if (setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN,
if (ss_setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN,
&optval, sizeof(optval)) != 0) {
ERROR("setsockopt");
break;
@@ -724,7 +724,7 @@ server_stream(EV_P_ ev_io *w, buffer_t *buf)
NULL, 0, NULL, NULL);
#elif defined(TCP_FASTOPEN_CONNECT)
int optval = 1;
if (setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
if (ss_setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
(void *)&optval, sizeof(optval)) < 0)
FATAL("failed to set TCP_FASTOPEN_CONNECT");
s = connect(remote->fd, (struct sockaddr *)&(remote->addr), remote->addr_len);
@@ -735,15 +735,15 @@ server_stream(EV_P_ ev_io *w, buffer_t *buf)
s = send(remote->fd, remote->buf->data, remote->buf->len, 0);
#endif
if (s == -1) {
if (errno == CONNECT_IN_PROGRESS) {
if (ss_socket_error() == CONNECT_IN_PROGRESS) {
// in progress, wait until connected
remote->buf->idx = 0;
ev_io_stop(EV_A_ & server_recv_ctx->io);
ev_io_start(EV_A_ & remote->send_ctx->io);
return;
} else {
if (errno == EOPNOTSUPP || errno == EPROTONOSUPPORT ||
errno == ENOPROTOOPT) {
if (ss_socket_error() == EOPNOTSUPP || ss_socket_error() == EPROTONOSUPPORT ||
ss_socket_error() == ENOPROTOOPT) {
LOGE("fast open is not supported on this platform");
// just turn it off
fast_open = 0;
@@ -767,7 +767,7 @@ server_stream(EV_P_ ev_io *w, buffer_t *buf)
} else {
int s = send(remote->fd, remote->buf->data, remote->buf->len, 0);
if (s == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data, wait for send
remote->buf->idx = 0;
ev_io_stop(EV_A_ & server_recv_ctx->io);
@@ -818,7 +818,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
close_and_free_server(EV_A_ server);
return;
} else if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
@@ -915,7 +915,7 @@ server_send_cb(EV_P_ ev_io *w, int revents)
ssize_t s = send(server->fd, server->buf->data + server->buf->idx,
server->buf->len, 0);
if (s == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) {
ERROR("server_send_cb_send");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
@@ -954,7 +954,7 @@ static void
remote_timeout_cb(EV_P_ ev_timer *watcher, int revents)
{
remote_ctx_t *remote_ctx
= cork_container_of(watcher, remote_ctx_t, watcher);
= ss_container_of(watcher, remote_ctx_t, watcher);
remote_t *remote = remote_ctx->remote;
server_t *server = remote->server;
@@ -982,7 +982,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
close_and_free_server(EV_A_ server);
return;
} else if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
@@ -1015,7 +1015,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
int s = send(server->fd, server->buf->data, server->buf->len, 0);
if (s == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data, wait for send
server->buf->idx = 0;
ev_io_stop(EV_A_ & remote_recv_ctx->io);
@@ -1036,8 +1036,8 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
// Disable TCP_NODELAY after the first response are sent
if (!remote->recv_ctx->connected && !no_delay) {
int opt = 0;
setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
}
remote->recv_ctx->connected = 1;
}
@@ -1075,7 +1075,7 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
}
// Make getpeername work
if (setsockopt(remote->fd, SOL_SOCKET,
if (ss_setsockopt(remote->fd, SOL_SOCKET,
SO_UPDATE_CONNECT_CONTEXT, NULL, 0) != 0) {
ERROR("setsockopt");
}
@@ -1114,7 +1114,7 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
ssize_t s = send(remote->fd, remote->buf->data + remote->buf->idx,
remote->buf->len, 0);
if (s == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) {
ERROR("remote_send_cb_send");
// close and free
close_and_free_remote(EV_A_ remote);
@@ -1186,7 +1186,7 @@ close_and_free_remote(EV_P_ remote_t *remote)
ev_timer_stop(EV_A_ & remote->send_ctx->watcher);
ev_io_stop(EV_A_ & remote->send_ctx->io);
ev_io_stop(EV_A_ & remote->recv_ctx->io);
close(remote->fd);
ss_socket_close(remote->fd);
free_remote(remote);
}
}
@@ -1226,7 +1226,7 @@ new_server(int fd)
ev_timer_init(&server->delayed_connect_watcher,
delayed_connect_cb, 0.05, 0);
cork_dllist_add(&connections, &server->entries);
ss_list_add(&connections, &server->entries);
return server;
}
@@ -1234,7 +1234,7 @@ new_server(int fd)
static void
free_server(server_t *server)
{
cork_dllist_remove(&server->entries);
ss_list_remove(&server->entries);
if (server->remote != NULL) {
server->remote->server = NULL;
@@ -1267,7 +1267,7 @@ close_and_free_server(EV_P_ server_t *server)
ev_io_stop(EV_A_ & server->send_ctx->io);
ev_io_stop(EV_A_ & server->recv_ctx->io);
ev_timer_stop(EV_A_ & server->delayed_connect_watcher);
close(server->fd);
ss_socket_close(server->fd);
free_server(server);
}
}
@@ -1290,28 +1290,28 @@ create_remote(listen_ctx_t *listener,
if (listener->mptcp < 0) {
protocol = IPPROTO_MPTCP; // Enable upstream MPTCP
}
int remotefd = socket(remote_addr->sa_family, SOCK_STREAM, protocol);
int remotefd = ss_socket(remote_addr->sa_family, SOCK_STREAM, protocol);
if (remotefd == -1) {
ERROR("socket");
return NULL;
}
int opt = 1;
setsockopt(remotefd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(remotefd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(remotefd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(remotefd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
// Enable out-of-tree MPTCP
if (listener->mptcp > 1) {
int err = setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
int err = ss_setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
if (err == -1) {
ERROR("failed to enable out-of-tree multipath TCP");
}
} else if (listener->mptcp == 1) {
int i = 0;
while ((listener->mptcp = mptcp_enabled_values[i]) > 0) {
int err = setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
int err = ss_setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
if (err != -1) {
break;
}
@@ -1323,11 +1323,11 @@ create_remote(listen_ctx_t *listener,
}
if (tcp_outgoing_sndbuf > 0) {
setsockopt(remotefd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int));
ss_setsockopt(remotefd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int));
}
if (tcp_outgoing_rcvbuf > 0) {
setsockopt(remotefd, SOL_SOCKET, SO_RCVBUF, &tcp_outgoing_rcvbuf, sizeof(int));
ss_setsockopt(remotefd, SOL_SOCKET, SO_RCVBUF, &tcp_outgoing_rcvbuf, sizeof(int));
}
// Setup
@@ -1388,7 +1388,7 @@ static void
plugin_watcher_cb(EV_P_ ev_io *w, int revents)
{
char buf[1];
SOCKET fd = accept(plugin_watcher.fd, NULL, NULL);
SOCKET fd = ss_accept(plugin_watcher.fd, NULL, NULL);
if (fd == INVALID_SOCKET) {
return;
}
@@ -1408,16 +1408,16 @@ void
accept_cb(EV_P_ ev_io *w, int revents)
{
listen_ctx_t *listener = (listen_ctx_t *)w;
int serverfd = accept(listener->fd, NULL, NULL);
int serverfd = ss_accept(listener->fd, NULL, NULL);
if (serverfd == -1) {
ERROR("accept");
return;
}
setnonblocking(serverfd);
int opt = 1;
setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
server_t *server = new_server(serverfd);
@@ -1878,7 +1878,7 @@ main(int argc, char **argv)
// Listen on plugin control port
if (plugin != NULL && plugin_watcher.port != 0) {
SOCKET fd;
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
fd = ss_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd != INVALID_SOCKET) {
plugin_watcher.valid = 0;
do {
@@ -2046,7 +2046,7 @@ main(int argc, char **argv)
#endif
// Init connections
cork_dllist_init(&connections);
ss_list_init(&connections);
// Enter the loop
ev_run(loop, 0);
@@ -2204,7 +2204,7 @@ _start_ss_local_server(profile_t profile, ss_local_callback callback, void *udat
}
// Init connections
cork_dllist_init(&connections);
ss_list_init(&connections);
if (callback) {
callback(listen_ctx.fd, udp_fd, udata);
@@ -2221,7 +2221,7 @@ _start_ss_local_server(profile_t profile, ss_local_callback callback, void *udat
if (mode != UDP_ONLY) {
ev_io_stop(loop, &listen_ctx.io);
free_connections(loop);
close(listen_ctx.fd);
ss_socket_close(listen_ctx.fd);
}
if (mode != TCP_ONLY) {
+3 -3
View File
@@ -23,7 +23,7 @@
#ifndef _LOCAL_H
#define _LOCAL_H
#include <libcork/ds.h>
#include "core.h"
#ifdef HAVE_LIBEV_EV_H
#include <libev/ev.h>
@@ -32,7 +32,7 @@
#endif
#ifdef __MINGW32__
#include "winsock.h"
#include "ss_windows.h"
#endif
#include "crypto.h"
@@ -72,7 +72,7 @@ typedef struct server {
ev_timer delayed_connect_watcher;
struct cork_dllist_item entries;
struct ss_list_item entries;
} server_t;
typedef struct remote_ctx {
+49 -41
View File
@@ -51,7 +51,7 @@
#include <sys/socket.h>
#include <pwd.h>
#include <sys/wait.h>
#include <libcork/core.h>
#include "core.h"
#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_NET_IF_H) && defined(__linux__)
#include <net/if.h>
@@ -73,9 +73,9 @@
int verbose = 0;
char *executable = "ss-server";
char *working_dir = NULL;
int working_dir_size = 0;
size_t working_dir_size = 0;
static struct cork_hash_table *server_table;
static struct server *server_table;
static int
copy_port(char *dst, size_t dst_len, const char *src, size_t src_len)
@@ -760,30 +760,30 @@ create_and_bind(const char *host, const char *port, int protocol)
}
for (/*rp = result*/; rp != NULL; rp = rp->ai_next) {
listen_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
listen_sock = ss_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (listen_sock == -1) {
continue;
}
if (rp->ai_family == AF_INET6) {
int ipv6only = host ? 1 : 0;
setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
ss_setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
s = bind(listen_sock, rp->ai_addr, rp->ai_addrlen);
if (s == 0) {
/* We managed to bind successfully! */
close(listen_sock);
ss_socket_close(listen_sock);
break;
} else {
ERROR("bind");
close(listen_sock);
ss_socket_close(listen_sock);
}
}
@@ -845,8 +845,13 @@ add_server(struct manager_ctx *manager, struct server *server)
return -1;
}
bool new = false;
cork_hash_table_put(server_table, (void *)server->port, (void *)server, &new, NULL, NULL);
struct server *replaced;
// NOLINTNEXTLINE(clang-analyzer-core.DivideZero,clang-analyzer-security.ArrayBound): uthash initializes nonzero buckets and hashes the NUL-terminated port within its key length
HASH_REPLACE_STR(server_table, port, server, replaced);
if (replaced != NULL) {
destroy_server(replaced);
ss_free(replaced);
}
return 0;
}
@@ -854,13 +859,19 @@ add_server(struct manager_ctx *manager, struct server *server)
static void
kill_pid_from_file(FILE *f)
{
char buf[16];
char buf[16] = {0};
int pid;
if (fgets(buf, sizeof(buf), f) == NULL) {
return;
}
buf[strcspn(buf, "\r\n")] = '\0';
for (size_t i = 0; i < sizeof(buf); i++) {
if (buf[i] == '\0') break;
if (buf[i] == '\r' || buf[i] == '\n') {
buf[i] = '\0';
break;
}
}
// Reject malformed pid file content instead of signaling a garbage pid
if (ss_parse_int(buf, 1, INT_MAX, &pid) == 0) {
kill(pid, SIGTERM);
@@ -911,12 +922,13 @@ stop_server(char *prefix, char *port)
static void
remove_server(char *prefix, char *port)
{
char *old_port = NULL;
struct server *old_server = NULL;
cork_hash_table_delete(server_table, (void *)port, (void **)&old_port, (void **)&old_server);
// NOLINTNEXTLINE(clang-analyzer-security.ArrayBound): uthash hashes only strlen(port) bytes; manager ports are validated and NUL-terminated
HASH_FIND_STR(server_table, port, old_server);
if (old_server != NULL) {
HASH_DEL(server_table, old_server);
destroy_server(old_server);
ss_free(old_server);
}
@@ -930,7 +942,9 @@ update_stat(char *port, uint64_t traffic)
if (verbose) {
LOGI("update traffic %" PRIu64 " for port %s", traffic, port);
}
void *ret = cork_hash_table_get(server_table, (void *)port);
struct server *ret;
// NOLINTNEXTLINE(clang-analyzer-security.ArrayBound): uthash hashes only strlen(port) bytes; manager ports are validated and NUL-terminated
HASH_FIND_STR(server_table, port, ret);
if (ret != NULL) {
struct server *server = (struct server *)ret;
server->traffic = traffic;
@@ -1000,15 +1014,12 @@ manager_recv_cb(EV_P_ ev_io *w, int revents)
ERROR("add_sendto");
}
} else if (strcmp(action, "list") == 0) {
struct cork_hash_table_iterator iter;
struct cork_hash_table_entry *entry;
struct server *server, *next;
char buf[BUF_SIZE];
memset(buf, 0, BUF_SIZE);
strcpy(buf, "[");
cork_hash_table_iterator_init(server_table, &iter);
while ((entry = cork_hash_table_iterator_next(&iter)) != NULL) {
struct server *server = (struct server *)entry->value;
HASH_ITER(hh, server_table, server, next) {
char *method = server->method ? server->method : manager->method;
char entry_buf[BUF_SIZE];
size_t entry_pos = 0;
@@ -1068,8 +1079,7 @@ manager_recv_cb(EV_P_ ev_io *w, int revents)
update_stat(port, traffic);
} else if (strcmp(action, "ping") == 0) {
struct cork_hash_table_entry *entry;
struct cork_hash_table_iterator server_iter;
struct server *server, *next;
char buf[BUF_SIZE];
size_t pos = 0;
@@ -1079,10 +1089,7 @@ manager_recv_cb(EV_P_ ev_io *w, int revents)
goto ERROR_MSG;
}
cork_hash_table_iterator_init(server_table, &server_iter);
while ((entry = cork_hash_table_iterator_next(&server_iter)) != NULL) {
struct server *server = (struct server *)entry->value;
HASH_ITER(hh, server_table, server, next) {
char entry_buf[64];
int entry_len = snprintf(entry_buf, sizeof(entry_buf),
"\"%s\":%" PRIu64 ",",
@@ -1191,18 +1198,18 @@ create_server_socket(const char *host, const char *port)
}
for (/*rp = result*/; rp != NULL; rp = rp->ai_next) {
server_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
server_sock = ss_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (server_sock == -1) {
continue;
}
if (rp->ai_family == AF_INET6) {
int ipv6only = host ? 1 : 0;
setsockopt(server_sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
ss_setsockopt(server_sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
}
int opt = 1;
setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ss_setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
s = bind(server_sock, rp->ai_addr, rp->ai_addrlen);
if (s == 0) {
@@ -1212,7 +1219,7 @@ create_server_socket(const char *host, const char *port)
ERROR("bind");
}
close(server_sock);
ss_socket_close(server_sock);
}
if (result != NULL) {
@@ -1505,7 +1512,7 @@ main(int argc, char **argv)
struct passwd *pw = getpwuid(getuid());
if (workdir == NULL || strlen(workdir) == 0) {
workdir = pw->pw_dir;
workdir = pw != NULL ? pw->pw_dir : NULL;
// If home dir is still not defined or set to nologin/nonexistent, fall back to /tmp
if (workdir == NULL || strlen(workdir) == 0 || strstr(workdir, "nologin") ||
strstr(workdir, "nonexistent") || strcmp(workdir, "/") == 0) {
@@ -1520,6 +1527,9 @@ main(int argc, char **argv)
working_dir = ss_malloc(working_dir_size);
snprintf(working_dir, working_dir_size, "%s", workdir);
}
if (working_dir == NULL) {
FATAL("unable to allocate working directory");
}
LOGI("working directory points to %s", working_dir);
int err = mkdir(working_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
@@ -1597,7 +1607,7 @@ main(int argc, char **argv)
FATAL("Couldn't open the directory");
}
server_table = cork_string_hash_table_new(MAX_PORT_NUM, 0);
server_table = NULL;
if (conf != NULL) {
for (i = 0; i < conf->port_password_num; i++) {
@@ -1631,7 +1641,7 @@ main(int argc, char **argv)
FATAL("manager unix socket path is too long");
}
sfd = socket(AF_UNIX, SOCK_DGRAM, 0); /* Create server socket */
sfd = ss_socket(AF_UNIX, SOCK_DGRAM, 0); /* Create server socket */
if (sfd == -1) {
ss_free(working_dir);
FATAL("socket");
@@ -1674,14 +1684,12 @@ main(int argc, char **argv)
}
// Clean up
struct cork_hash_table_entry *entry;
struct cork_hash_table_iterator server_iter;
cork_hash_table_iterator_init(server_table, &server_iter);
while ((entry = cork_hash_table_iterator_next(&server_iter)) != NULL) {
struct server *server = (struct server *)entry->value;
struct server *server, *next;
HASH_ITER(hh, server_table, server, next) {
stop_server(working_dir, server->port);
HASH_DEL(server_table, server);
destroy_server(server);
ss_free(server);
}
ev_signal_stop(EV_DEFAULT, &sigint_watcher);
+3 -1
View File
@@ -24,7 +24,8 @@
#define _MANAGER_H
#include <time.h>
#include <libcork/ds.h>
#include "core.h"
#include "uthash.h"
#ifdef HAVE_LIBEV_EV_H
#include <libev/ev.h>
@@ -66,6 +67,7 @@ struct manager_ctx {
};
struct server {
UT_hash_handle hh;
char port[8];
char password[128];
char fast_open[8];
+11 -10
View File
@@ -25,7 +25,8 @@
#include <math.h>
#include <stdlib.h>
#include <libcork/core.h>
#include "core.h"
#include <string.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -57,7 +58,7 @@ static const char valid_label_bytes[] =
"-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
static int
parse_numeric_port(const char *port, in_port_t *port_out)
parse_numeric_port(const char *port, uint16_t *port_out)
{
char *endptr;
unsigned long value;
@@ -72,7 +73,7 @@ parse_numeric_port(const char *port, in_port_t *port_out)
return -1;
}
*port_out = (in_port_t)value;
*port_out = (uint16_t)value;
return 0;
}
@@ -80,7 +81,7 @@ int
set_reuseport(int socket)
{
int opt = 1;
return setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
return ss_setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
}
size_t
@@ -101,7 +102,7 @@ setinterface(int socket_fd, const char *interface_name)
struct ifreq interface;
memset(&interface, 0, sizeof(struct ifreq));
strncpy(interface.ifr_name, interface_name, IFNAMSIZ - 1);
int res = setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, &interface,
int res = ss_setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, &interface,
sizeof(struct ifreq));
return res;
}
@@ -114,8 +115,8 @@ parse_local_addr(struct sockaddr_storage *storage_v4,
const char *host)
{
if (host != NULL) {
struct cork_ip ip;
if (cork_ip_init(&ip, host) != -1) {
struct ss_ip ip;
if (ss_ip_init(&ip, host) != -1) {
if (ip.version == 4) {
memset(storage_v4, 0, sizeof(struct sockaddr_storage));
struct sockaddr_in *addr = (struct sockaddr_in *)storage_v4;
@@ -153,9 +154,9 @@ get_sockaddr(char *host, char *port,
struct sockaddr_storage *storage, int block,
int ipv6first)
{
struct cork_ip ip;
if (cork_ip_init(&ip, host) != -1) {
in_port_t numeric_port = 0;
struct ss_ip ip;
if (ss_ip_init(&ip, host) != -1) {
uint16_t numeric_port = 0;
if (port != NULL && parse_numeric_port(port, &numeric_port) == -1) {
LOGE("invalid port: %s", port);
return -1;
+3 -1
View File
@@ -22,11 +22,13 @@
#ifndef _NETUTILS_H
#define _NETUTILS_H
#include "platform.h"
#ifdef __MINGW32__
#include "winsock.h"
#include "ss_windows.h"
#else
#include <sys/socket.h>
#include <netinet/in.h>
#endif
#if defined(HAVE_LINUX_TCP_H)
+120
View File
@@ -0,0 +1,120 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#ifndef SS_PLATFORM_H
#define SS_PLATFORM_H
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#ifdef _WIN32
#include "ss_windows.h"
typedef SOCKET ss_socket_t;
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
typedef int ss_socket_t;
#endif
/* Socket errors are separate from CRT errno on Windows. Return normalized
* errno constants for portable branching without redefining libc names. */
static inline int
ss_socket_error(void)
{
#ifdef _WIN32
int error = WSAGetLastError();
switch (error) {
case WSAEWOULDBLOCK: return EWOULDBLOCK;
case WSAEINPROGRESS: return EINPROGRESS;
case WSAEINTR: return EINTR;
case WSAEINVAL: return EINVAL;
case WSAEOPNOTSUPP: return EOPNOTSUPP;
case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT;
case WSAENOPROTOOPT: return ENOPROTOOPT;
case WSAECONNRESET: return ECONNRESET;
case WSAENOTCONN: return ENOTCONN;
case WSAETIMEDOUT: return ETIMEDOUT;
default: return error;
}
#else
return errno;
#endif
}
static inline int
ss_socket_close(ss_socket_t fd)
{
#ifdef _WIN32
return closesocket(fd);
#else
return close(fd);
#endif
}
static inline ss_socket_t
ss_socket_noinherit(ss_socket_t fd)
{
#ifdef _WIN32
if (fd != INVALID_SOCKET && !SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, 0)) {
closesocket(fd);
WSASetLastError(WSAEINVAL);
return INVALID_SOCKET;
}
#else
if (fd >= 0 && fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
int error = errno;
close(fd);
errno = error;
return -1;
}
#endif
return fd;
}
static inline ss_socket_t
ss_socket(int family, int type, int protocol)
{
return ss_socket_noinherit(socket(family, type, protocol));
}
static inline ss_socket_t
ss_accept(ss_socket_t fd, struct sockaddr *address, socklen_t *length)
{
return ss_socket_noinherit(accept(fd, address, length));
}
static inline int
ss_setsockopt(ss_socket_t fd, int level, int option, const void *value, socklen_t length)
{
#ifdef _WIN32
return setsockopt(fd, level, option, (const char *)value, length);
#else
return setsockopt(fd, level, option, value, length);
#endif
}
static inline const char *
ss_inet_ntop(int family, const void *address, char *text, socklen_t length)
{
#ifdef _WIN32
return inet_ntop(family, (void *)address, text, length);
#else
return inet_ntop(family, address, text, length);
#endif
}
static inline double
ss_monotonic_time(void)
{
#ifdef _WIN32
LARGE_INTEGER counter, frequency;
if (!QueryPerformanceCounter(&counter) || !QueryPerformanceFrequency(&frequency)) abort();
return (double)counter.QuadPart / (double)frequency.QuadPart;
#else
struct timespec value;
if (clock_gettime(CLOCK_MONOTONIC, &value) != 0) abort();
return (double)value.tv_sec + (double)value.tv_nsec / 1000000000.0;
#endif
}
#endif
+40 -105
View File
@@ -25,6 +25,7 @@
#endif
#include <string.h>
#include <errno.h>
#ifndef __MINGW32__
#include <unistd.h>
#include <sys/socket.h>
@@ -32,12 +33,12 @@
#include <netinet/in.h>
#endif
#include <libcork/core.h>
#include <libcork/os.h>
#include "ss_process.h"
#include "platform.h"
#include "utils.h"
#include "plugin.h"
#include "winsock.h"
#include "ss_windows.h"
#define CMD_RESRV_LEN 128
@@ -47,49 +48,8 @@
#define TEMPDIR
#endif
static int exit_code;
static struct cork_env *env = NULL;
static struct cork_exec *exec = NULL;
static struct cork_subprocess *sub = NULL;
#ifdef __MINGW32__
static uint16_t sub_control_port = 0;
void cork_subprocess_set_control(struct cork_subprocess *self, uint16_t port);
#endif
static int
plugin_log__data(struct cork_stream_consumer *vself,
const void *buf, size_t size, bool is_first)
{
size_t bytes_written = fwrite(buf, 1, size, stderr);
/* If there was an error writing to the file, then signal this
* to the producer */
if (bytes_written == size) {
return 0;
} else {
cork_system_error_set();
return -1;
}
}
static int
plugin_log__eof(struct cork_stream_consumer *vself)
{
/* We don't close the file, so there's nothing special to do at
* end-of-stream. */
return 0;
}
static void
plugin_log__free(struct cork_stream_consumer *vself)
{
return;
}
struct cork_stream_consumer plugin_log = {
.data = plugin_log__data,
.eof = plugin_log__eof,
.free = plugin_log__free,
};
static struct ss_process *sub;
static uint16_t sub_control_port;
static int
start_ss_plugin(const char *plugin,
@@ -100,32 +60,26 @@ start_ss_plugin(const char *plugin,
const char *local_port,
enum plugin_mode mode)
{
cork_env_add(env, "SS_REMOTE_HOST", remote_host);
cork_env_add(env, "SS_REMOTE_PORT", remote_port);
ss_process_env(sub, "SS_REMOTE_HOST", remote_host);
ss_process_env(sub, "SS_REMOTE_PORT", remote_port);
cork_env_add(env, "SS_LOCAL_HOST", local_host);
cork_env_add(env, "SS_LOCAL_PORT", local_port);
ss_process_env(sub, "SS_LOCAL_HOST", local_host);
ss_process_env(sub, "SS_LOCAL_PORT", local_port);
if (plugin_opts != NULL)
cork_env_add(env, "SS_PLUGIN_OPTIONS", plugin_opts);
ss_process_env(sub, "SS_PLUGIN_OPTIONS", plugin_opts);
exec = cork_exec_new(plugin);
cork_exec_add_param(exec, plugin); // argv[0]
ss_process_arg(sub, plugin); // argv[0]
#ifdef __ANDROID__
extern int vpn;
if (vpn)
cork_exec_add_param(exec, "-V");
ss_process_arg(sub, "-V");
#endif
cork_exec_set_env(exec, env);
sub = cork_subprocess_new_exec(exec, NULL, NULL, &exit_code);
#ifdef __MINGW32__
cork_subprocess_set_control(sub, sub_control_port);
#endif
return cork_subprocess_start(sub);
return ss_process_start(sub, sub_control_port);
}
#define OBFSPROXY_OPTS_MAX 4096
@@ -173,24 +127,20 @@ start_obfsproxy(const char *plugin,
opts_dump = strndup(plugin_opts, OBFSPROXY_OPTS_MAX);
if (!opts_dump) {
ERROR("start_obfsproxy strndup failed");
if (env != NULL) {
cork_env_free(env);
}
return -ENOMEM;
}
}
exec = cork_exec_new(plugin);
/* The first parameter will be skipped, so pass @plugin again */
cork_exec_add_param(exec, plugin);
ss_process_arg(sub, plugin);
cork_exec_add_param(exec, "--data-dir");
ss_process_arg(sub, "--data-dir");
buf_size = 20 + strlen(plugin) + strlen(remote_host)
+ strlen(remote_port) + strlen(local_host) + strlen(local_port);
buf = ss_malloc(buf_size);
snprintf(buf, buf_size, TEMPDIR "%s_%s:%s_%s:%s", plugin,
remote_host, remote_port, local_host, local_port);
cork_exec_add_param(exec, buf);
ss_process_arg(sub, buf);
/*
* Iterate @plugin_opts by space
@@ -198,7 +148,7 @@ start_obfsproxy(const char *plugin,
if (opts_dump != NULL) {
pch = strtok(opts_dump, " ");
while (pch) {
cork_exec_add_param(exec, pch);
ss_process_arg(sub, pch);
pch = strtok(NULL, " ");
}
}
@@ -206,28 +156,23 @@ start_obfsproxy(const char *plugin,
/* The rest options */
if (mode == MODE_CLIENT) {
/* Client mode */
cork_exec_add_param(exec, "--dest");
ss_process_arg(sub, "--dest");
snprintf(buf, buf_size, "%s:%s", remote_host, remote_port);
cork_exec_add_param(exec, buf);
cork_exec_add_param(exec, "client");
ss_process_arg(sub, buf);
ss_process_arg(sub, "client");
snprintf(buf, buf_size, "%s:%s", local_host, local_port);
cork_exec_add_param(exec, buf);
ss_process_arg(sub, buf);
} else {
/* Server mode */
cork_exec_add_param(exec, "--dest");
ss_process_arg(sub, "--dest");
snprintf(buf, buf_size, "%s:%s", local_host, local_port);
cork_exec_add_param(exec, buf);
cork_exec_add_param(exec, "server");
ss_process_arg(sub, buf);
ss_process_arg(sub, "server");
snprintf(buf, buf_size, "%s:%s", remote_host, remote_port);
cork_exec_add_param(exec, buf);
ss_process_arg(sub, buf);
}
cork_exec_set_env(exec, env);
sub = cork_subprocess_new_exec(exec, NULL, NULL, &exit_code);
#ifdef __MINGW32__
cork_subprocess_set_control(sub, sub_control_port);
#endif
ret = cork_subprocess_start(sub);
ret = ss_process_start(sub, sub_control_port);
ss_free(opts_dump);
free(buf);
return ret;
@@ -253,9 +198,9 @@ start_plugin(const char *plugin,
if (strlen(plugin) == 0)
return 0;
#ifndef __MINGW32__
env = cork_env_clone_current();
#else
stop_plugin();
sub = ss_process_new(plugin);
#ifdef __MINGW32__
sub_control_port = control_port;
#endif
@@ -265,14 +210,14 @@ start_plugin(const char *plugin,
else
ret = start_ss_plugin(plugin, plugin_opts, remote_host, remote_port,
local_host, local_port, mode);
env = NULL;
if (ret != 0) stop_plugin();
return ret;
}
uint16_t
get_local_port()
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
int sock = ss_socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
return 0;
}
@@ -283,16 +228,16 @@ get_local_port()
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = 0;
if (bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
close(sock);
ss_socket_close(sock);
return 0;
}
socklen_t len = sizeof(serv_addr);
if (getsockname(sock, (struct sockaddr *)&serv_addr, &len) == -1) {
close(sock);
ss_socket_close(sock);
return 0;
}
if (close(sock) < 0) {
if (ss_socket_close(sock) < 0) {
return 0;
}
@@ -300,24 +245,14 @@ get_local_port()
}
void
stop_plugin()
stop_plugin(void)
{
if (sub != NULL) {
cork_subprocess_abort(sub);
#ifndef __MINGW32__
if (cork_subprocess_wait(sub) == -1) {
LOGI("error on terminating the plugin.");
}
#endif
cork_subprocess_free(sub);
}
ss_process_free(sub);
sub = NULL;
}
int
is_plugin_running()
is_plugin_running(void)
{
if (sub != NULL) {
return cork_subprocess_is_finished(sub);
}
return 0;
return ss_process_running(sub);
}
+23
View File
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include <stdint.h>
#include "plugin.h"
#include "utils.h"
int
start_plugin(const char *plugin, const char *options, const char *remote_host,
const char *remote_port, const char *local_host, const char *local_port,
#ifdef __MINGW32__
uint16_t control_port,
#endif
enum plugin_mode mode)
{
(void)plugin; (void)options; (void)remote_host; (void)remote_port;
(void)local_host; (void)local_port; (void)mode;
#ifdef __MINGW32__
(void)control_port;
#endif
LOGE("Plugin support is disabled in this build");
return -1;
}
uint16_t get_local_port(void) { FATAL("Plugin support is disabled in this build"); return 0; }
void stop_plugin(void) {}
int is_plugin_running(void) { return 0; }
+344
View File
@@ -0,0 +1,344 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "ss_process.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "utils.h"
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <signal.h>
#include <spawn.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
extern char **environ;
#endif
struct ss_process {
char *program;
char **argv, **env;
size_t argc, envc;
#ifdef _WIN32
HANDLE child, job, monitor;
uint16_t control_port;
#else
pid_t pid;
#endif
};
static char *
copy_string(const char *text)
{
size_t size = strlen(text) + 1;
char *copy = ss_malloc(size);
memcpy(copy, text, size);
return copy;
}
struct ss_process *
ss_process_new(const char *program)
{
struct ss_process *process = ss_malloc(sizeof(*process));
memset(process, 0, sizeof(*process));
process->program = copy_string(program);
return process;
}
void
ss_process_arg(struct ss_process *process, const char *argument)
{
process->argv = ss_realloc(process->argv, (process->argc + 2) * sizeof(char *));
process->argv[process->argc++] = copy_string(argument);
process->argv[process->argc] = NULL;
}
void
ss_process_env(struct ss_process *process, const char *name, const char *value)
{
size_t size = strlen(name) + strlen(value) + 2;
char *entry = ss_malloc(size);
snprintf(entry, size, "%s=%s", name, value);
process->env = ss_realloc(process->env, (process->envc + 2) * sizeof(char *));
process->env[process->envc++] = entry;
process->env[process->envc] = NULL;
}
#ifndef _WIN32
static bool
overridden(const struct ss_process *process, const char *entry)
{
const char *equals = strchr(entry, '=');
if (equals == NULL) {
return false;
}
size_t length = (size_t)(equals - entry) + 1;
for (size_t i = 0; i < process->envc; i++) {
if (strncmp(entry, process->env[i], length) == 0) {
return true;
}
}
return false;
}
int
ss_process_start(struct ss_process *process, uint16_t control_port)
{
(void)control_port;
size_t count = 0, used = 0;
while (environ[count] != NULL) {
count++;
}
char **env = ss_malloc((count + process->envc + 1) * sizeof(char *));
for (size_t i = 0; i < count; i++) {
if (!overridden(process, environ[i])) {
env[used++] = environ[i];
}
}
for (size_t i = 0; i < process->envc; i++) {
env[used++] = process->env[i];
}
env[used] = NULL;
posix_spawnattr_t attr;
int error = posix_spawnattr_init(&attr);
if (error == 0) {
sigset_t empty, defaults;
sigemptyset(&empty);
sigemptyset(&defaults);
sigaddset(&defaults, SIGPIPE);
sigaddset(&defaults, SIGTERM);
sigaddset(&defaults, SIGINT);
error = posix_spawnattr_setpgroup(&attr, 0);
if (error == 0) error = posix_spawnattr_setsigmask(&attr, &empty);
if (error == 0) error = posix_spawnattr_setsigdefault(&attr, &defaults);
if (error == 0) error = posix_spawnattr_setflags(&attr,
POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF);
if (error == 0) error = posix_spawnp(&process->pid, process->program, NULL,
&attr, process->argv, env);
posix_spawnattr_destroy(&attr);
}
free(env);
if (error != 0) {
process->pid = 0;
errno = error;
return -1;
}
return 0;
}
bool
ss_process_running(struct ss_process *process)
{
if (process == NULL || process->pid == 0) return false;
siginfo_t info;
memset(&info, 0, sizeof(info));
int result;
do {
result = waitid(P_PID, (id_t)process->pid, &info, WEXITED | WNOHANG | WNOWAIT);
} while (result < 0 && errno == EINTR);
/* Preserve the zombie until cleanup so its process-group ID cannot be
* reused before all descendants have been signaled. */
if (result < 0 && errno == ECHILD) process->pid = 0;
return result == 0 && info.si_pid == 0;
}
static void
stop_process(struct ss_process *process)
{
if (process->pid == 0) return;
kill(-process->pid, SIGTERM);
for (unsigned i = 0; i < 50 && ss_process_running(process); i++) {
struct timespec delay = {0, 10000000};
nanosleep(&delay, NULL);
}
if (process->pid != 0) {
kill(-process->pid, SIGKILL);
while (waitpid(process->pid, NULL, 0) < 0 && errno == EINTR) {}
process->pid = 0;
}
}
#else
static wchar_t *
wide_string(const char *text)
{
int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, -1, NULL, 0);
if (count == 0) return NULL;
wchar_t *wide = ss_malloc((size_t)count * sizeof(wchar_t));
if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, -1, wide, count)) {
free(wide);
return NULL;
}
return wide;
}
static DWORD WINAPI
monitor_process(void *argument)
{
struct ss_process *process = argument;
WaitForSingleObject(process->child, INFINITE);
if (process->control_port != 0) {
SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd != INVALID_SOCKET) {
struct sockaddr_in address = {0};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
address.sin_port = htons(process->control_port);
if (connect(fd, (struct sockaddr *)&address, sizeof(address)) == 0) {
send(fd, "\0", 1, 0);
}
closesocket(fd);
}
}
return 0;
}
static int
compare_environment(const void *left, const void *right)
{
return _wcsicmp(*(const wchar_t *const *)left, *(const wchar_t *const *)right);
}
static wchar_t *
sort_environment(wchar_t *env)
{
size_t count = 0, length = 1;
for (wchar_t *entry = env; *entry; entry += wcslen(entry) + 1) {
count++;
length += wcslen(entry) + 1;
}
wchar_t **entries = ss_malloc((count + 1) * sizeof(*entries));
size_t index = 0;
for (wchar_t *entry = env; *entry; entry += wcslen(entry) + 1) entries[index++] = entry;
qsort(entries, count, sizeof(*entries), compare_environment);
wchar_t *sorted = ss_malloc((length + 1) * sizeof(*sorted)), *out = sorted;
for (size_t i = 0; i < count; i++) {
size_t size = wcslen(entries[i]) + 1;
memcpy(out, entries[i], size * sizeof(*out));
out += size;
}
*out++ = L'\0';
*out = L'\0';
free(entries);
free(env);
return sorted;
}
int
ss_process_start(struct ss_process *process, uint16_t control_port)
{
/* Quote each argv element according to the Windows C runtime rules.
* Double backslashes before a quote or the closing quote. */
size_t capacity = 1;
for (size_t i = 0; i < process->argc; i++) capacity += 2 * strlen(process->argv[i]) + 4;
char *command = ss_malloc(capacity), *out = command;
for (size_t i = 0; i < process->argc; i++) {
if (i != 0) *out++ = ' ';
*out++ = '"';
const char *arg = process->argv[i];
while (*arg != '\0') {
size_t slashes = 0;
while (*arg == '\\') { slashes++; arg++; }
size_t copies = (*arg == '"' || *arg == '\0') ? slashes * 2 : slashes;
while (copies-- != 0) *out++ = '\\';
if (*arg == '\0') break;
if (*arg == '"') *out++ = '\\';
*out++ = *arg++;
}
*out++ = '"';
}
*out = '\0';
wchar_t *wide_command = wide_string(command);
free(command);
if (wide_command == NULL) return -1;
wchar_t *inherited = GetEnvironmentStringsW();
if (inherited == NULL) { free(wide_command); return -1; }
wchar_t **overrides = ss_malloc((process->envc + 1) * sizeof(wchar_t *));
size_t total = 2;
for (const wchar_t *entry = inherited; *entry; entry += wcslen(entry) + 1) total += wcslen(entry) + 1;
for (size_t i = 0; i < process->envc; i++) {
overrides[i] = wide_string(process->env[i]);
if (overrides[i] == NULL) {
for (size_t j = 0; j < i; j++) free(overrides[j]);
free(overrides); FreeEnvironmentStringsW(inherited); free(wide_command);
return -1;
}
total += wcslen(overrides[i]) + 1;
}
wchar_t *env = ss_malloc(total * sizeof(wchar_t)), *dest = env;
for (const wchar_t *entry = inherited; *entry; entry += wcslen(entry) + 1) {
const wchar_t *equals = wcschr(entry, L'=');
bool replace = false;
if (equals != NULL && equals != entry) {
for (size_t i = 0; i < process->envc; i++) {
if (_wcsnicmp(entry, overrides[i], (size_t)(equals - entry) + 1) == 0) replace = true;
}
}
if (!replace) {
size_t length = wcslen(entry) + 1;
memcpy(dest, entry, length * sizeof(wchar_t)); dest += length;
}
}
for (size_t i = 0; i < process->envc; i++) {
size_t length = wcslen(overrides[i]) + 1;
memcpy(dest, overrides[i], length * sizeof(wchar_t)); dest += length;
free(overrides[i]);
}
*dest++ = L'\0'; *dest = L'\0';
free(overrides); FreeEnvironmentStringsW(inherited);
env = sort_environment(env);
STARTUPINFOW startup = {0};
PROCESS_INFORMATION child = {0};
startup.cb = sizeof(startup);
process->job = CreateJobObjectW(NULL, NULL);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = {0};
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
BOOL ok = process->job != NULL && SetInformationJobObject(process->job,
JobObjectExtendedLimitInformation, &limits, sizeof(limits));
if (ok) ok = CreateProcessW(NULL, wide_command, NULL, NULL, FALSE,
CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED, env, NULL, &startup, &child);
free(env); free(wide_command);
if (ok) {
process->child = child.hProcess;
ok = AssignProcessToJobObject(process->job, child.hProcess);
if (ok) ok = ResumeThread(child.hThread) != (DWORD)-1;
CloseHandle(child.hThread);
}
if (!ok) {
if (process->child) TerminateProcess(process->child, 1);
return -1;
}
process->control_port = control_port;
process->monitor = CreateThread(NULL, 0, monitor_process, process, 0, NULL);
return process->monitor == NULL ? -1 : 0;
}
bool
ss_process_running(struct ss_process *process)
{
return process != NULL && process->child != NULL &&
WaitForSingleObject(process->child, 0) == WAIT_TIMEOUT;
}
static void
stop_process(struct ss_process *process)
{
if (process->job) { CloseHandle(process->job); process->job = NULL; }
if (process->monitor) {
WaitForSingleObject(process->monitor, INFINITE);
CloseHandle(process->monitor);
}
if (process->child) { WaitForSingleObject(process->child, INFINITE); CloseHandle(process->child); }
}
#endif
void
ss_process_free(struct ss_process *process)
{
if (process == NULL) return;
stop_process(process);
for (size_t i = 0; i < process->argc; i++) free(process->argv[i]);
for (size_t i = 0; i < process->envc; i++) free(process->env[i]);
free(process->argv); free(process->env); free(process->program); free(process);
}
+48 -48
View File
@@ -41,7 +41,7 @@
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <libcork/core.h>
#include "core.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -167,15 +167,15 @@ create_and_bind(const char *addr, const char *port)
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
listen_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
listen_sock = ss_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (listen_sock == -1) {
continue;
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
if (reuse_port) {
int err = set_reuseport(listen_sock);
@@ -194,7 +194,7 @@ create_and_bind(const char *addr, const char *port)
optname = IPV6_TRANSPARENT;
}
if (setsockopt(listen_sock, level, optname, &opt, sizeof(opt)) != 0) {
if (ss_setsockopt(listen_sock, level, optname, &opt, sizeof(opt)) != 0) {
ERROR("setsockopt IP_TRANSPARENT");
exit(EXIT_FAILURE);
}
@@ -202,11 +202,11 @@ create_and_bind(const char *addr, const char *port)
}
if (tcp_incoming_sndbuf > 0) {
setsockopt(listen_sock, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof(int));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof(int));
}
if (tcp_incoming_rcvbuf > 0) {
setsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof(int));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof(int));
}
s = bind(listen_sock, rp->ai_addr, rp->ai_addrlen);
@@ -217,7 +217,7 @@ create_and_bind(const char *addr, const char *port)
ERROR("bind");
}
close(listen_sock);
ss_socket_close(listen_sock);
listen_sock = -1;
}
@@ -244,7 +244,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
close_and_free_server(EV_A_ server);
return;
} else if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
@@ -265,11 +265,11 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
if (AF_INET == server->destaddr.ss_family) {
struct sockaddr_in *sa = (struct sockaddr_in *)&(server->destaddr);
inet_ntop(AF_INET, &(sa->sin_addr), ipstr, INET_ADDRSTRLEN);
ss_inet_ntop(AF_INET, &(sa->sin_addr), ipstr, INET_ADDRSTRLEN);
port = ntohs(sa->sin_port);
} else {
struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&(server->destaddr);
inet_ntop(AF_INET6, &(sa->sin6_addr), ipstr, INET6_ADDRSTRLEN);
ss_inet_ntop(AF_INET6, &(sa->sin6_addr), ipstr, INET6_ADDRSTRLEN);
port = ntohs(sa->sin6_port);
}
@@ -294,7 +294,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
int s = send(remote->fd, remote->buf->data, remote->buf->len, 0);
if (s == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data, wait for send
remote->buf->idx = 0;
ev_io_stop(EV_A_ & server_recv_ctx->io);
@@ -334,7 +334,7 @@ server_send_cb(EV_P_ ev_io *w, int revents)
ssize_t s = send(server->fd, server->buf->data + server->buf->idx,
server->buf->len, 0);
if (s == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) {
ERROR("send");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
@@ -358,7 +358,7 @@ server_send_cb(EV_P_ ev_io *w, int revents)
static void
delayed_connect_cb(EV_P_ ev_timer *watcher, int revents)
{
server_t *server = cork_container_of(watcher, server_t,
server_t *server = ss_container_of(watcher, server_t,
delayed_connect_watcher);
remote_t *remote = server->remote;
@@ -367,7 +367,7 @@ delayed_connect_cb(EV_P_ ev_timer *watcher, int revents)
remote->addr = NULL;
if (r == -1 && errno != CONNECT_IN_PROGRESS) {
if (r == -1 && ss_socket_error() != CONNECT_IN_PROGRESS) {
ERROR("connect");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
@@ -383,7 +383,7 @@ static void
remote_timeout_cb(EV_P_ ev_timer *watcher, int revents)
{
remote_ctx_t *remote_ctx
= cork_container_of(watcher, remote_ctx_t, watcher);
= ss_container_of(watcher, remote_ctx_t, watcher);
remote_t *remote = remote_ctx->remote;
server_t *server = remote->server;
@@ -409,7 +409,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
close_and_free_server(EV_A_ server);
return;
} else if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
@@ -436,7 +436,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
int s = send(server->fd, server->buf->data, server->buf->len, 0);
if (s == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data, wait for send
server->buf->idx = 0;
ev_io_stop(EV_A_ & remote_recv_ctx->io);
@@ -457,8 +457,8 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
// Disable TCP_NODELAY after the first response are sent
if (!remote->recv_ctx->connected && !no_delay) {
int opt = 0;
setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
}
remote->recv_ctx->connected = 1;
}
@@ -557,7 +557,7 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
if (remote->addr != NULL) {
#if defined(TCP_FASTOPEN_CONNECT)
int optval = 1;
if (setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
if (ss_setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
(void *)&optval, sizeof(optval)) < 0)
FATAL("failed to set TCP_FASTOPEN_CONNECT");
s = connect(remote->fd, remote->addr, get_sockaddr_len(remote->addr));
@@ -574,12 +574,12 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
remote->addr = NULL;
if (s == -1) {
if (errno == CONNECT_IN_PROGRESS) {
if (ss_socket_error() == CONNECT_IN_PROGRESS) {
ev_io_start(EV_A_ & remote_send_ctx->io);
ev_timer_start(EV_A_ & remote_send_ctx->watcher);
} else {
if (errno == EOPNOTSUPP || errno == EPROTONOSUPPORT ||
errno == ENOPROTOOPT) {
if (ss_socket_error() == EOPNOTSUPP || ss_socket_error() == EPROTONOSUPPORT ||
ss_socket_error() == ENOPROTOOPT) {
fast_open = 0;
LOGE("fast open is not supported on this platform");
} else {
@@ -596,7 +596,7 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
}
if (s == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) {
ERROR("send");
// close and free
close_and_free_remote(EV_A_ remote);
@@ -667,7 +667,7 @@ close_and_free_remote(EV_P_ remote_t *remote)
ev_timer_stop(EV_A_ & remote->send_ctx->watcher);
ev_io_stop(EV_A_ & remote->send_ctx->io);
ev_io_stop(EV_A_ & remote->recv_ctx->io);
close(remote->fd);
ss_socket_close(remote->fd);
free_remote(remote);
}
}
@@ -735,7 +735,7 @@ close_and_free_server(EV_P_ server_t *server)
ev_io_stop(EV_A_ & server->send_ctx->io);
ev_io_stop(EV_A_ & server->recv_ctx->io);
ev_timer_stop(EV_A_ & server->delayed_connect_watcher);
close(server->fd);
ss_socket_close(server->fd);
free_server(server);
}
}
@@ -749,7 +749,7 @@ accept_cb(EV_P_ ev_io *w, int revents)
int err;
int serverfd = accept(listener->fd, NULL, NULL);
int serverfd = ss_accept(listener->fd, NULL, NULL);
if (serverfd == -1) {
ERROR("accept");
return;
@@ -763,9 +763,9 @@ accept_cb(EV_P_ ev_io *w, int revents)
setnonblocking(serverfd);
int opt = 1;
setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
int index = (int)randombytes_uniform((uint32_t)listener->remote_num);
@@ -775,16 +775,16 @@ accept_cb(EV_P_ ev_io *w, int revents)
if (listener->mptcp < 0) {
protocol = IPPROTO_MPTCP; // Enable upstream MPTCP
}
int remotefd = socket(remote_addr->sa_family, SOCK_STREAM, protocol);
int remotefd = ss_socket(remote_addr->sa_family, SOCK_STREAM, protocol);
if (remotefd == -1) {
ERROR("socket");
return;
}
// Set flags
setsockopt(remotefd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(remotefd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(remotefd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(remotefd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
// Enable TCP keepalive feature
@@ -792,37 +792,37 @@ accept_cb(EV_P_ ev_io *w, int revents)
int keepIdle = 40;
int keepInterval = 20;
int keepCount = 5;
setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(remotefd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
setsockopt(remotefd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(remotefd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
ss_setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
ss_setsockopt(remotefd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
ss_setsockopt(remotefd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
ss_setsockopt(remotefd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
// Set non blocking
setnonblocking(remotefd);
if (listener->tos >= 0) {
int rc = setsockopt(remotefd, IPPROTO_IP, IP_TOS, &listener->tos, sizeof(listener->tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", errno);
int rc = ss_setsockopt(remotefd, IPPROTO_IP, IP_TOS, &listener->tos, sizeof(listener->tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
}
#ifdef IPV6_TCLASS
rc = setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &listener->tos, sizeof(listener->tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", errno);
rc = ss_setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &listener->tos, sizeof(listener->tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
}
#endif
}
// Enable out-of-tree MPTCP
if (listener->mptcp > 1) {
int err = setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
int err = ss_setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
if (err == -1) {
ERROR("failed to enable out-of-tree multipath TCP");
}
} else if (listener->mptcp == 1) {
int i = 0;
while ((listener->mptcp = mptcp_enabled_values[i]) > 0) {
int err = setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
int err = ss_setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
if (err != -1) {
break;
}
@@ -834,11 +834,11 @@ accept_cb(EV_P_ ev_io *w, int revents)
}
if (tcp_outgoing_sndbuf > 0) {
setsockopt(remotefd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int));
ss_setsockopt(remotefd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int));
}
if (tcp_outgoing_rcvbuf > 0) {
setsockopt(remotefd, SOL_SOCKET, SO_RCVBUF, &tcp_outgoing_rcvbuf, sizeof(int));
ss_setsockopt(remotefd, SOL_SOCKET, SO_RCVBUF, &tcp_outgoing_rcvbuf, sizeof(int));
}
server_t *server = new_server(serverfd);
@@ -854,7 +854,7 @@ accept_cb(EV_P_ ev_io *w, int revents)
} else {
int r = connect(remotefd, remote_addr, get_sockaddr_len(remote_addr));
if (r == -1 && errno != CONNECT_IN_PROGRESS) {
if (r == -1 && ss_socket_error() != CONNECT_IN_PROGRESS) {
ERROR("connect");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
+6 -12
View File
@@ -39,7 +39,7 @@
#include <errno.h>
#include <unistd.h>
#else
#include "winsock.h" // Should be before <ares.h>
#include "ss_windows.h" // Should be before <ares.h>
#endif
#include <ares.h>
@@ -49,18 +49,12 @@
#include <ev.h>
#endif
#include <libcork/core.h>
#include "core.h"
#include "resolv.h"
#include "utils.h"
#include "netutils.h"
#ifdef __MINGW32__
#define CONV_STATE_CB (ares_sock_state_cb)
#else
#define CONV_STATE_CB
#endif
/*
* Implement DNS resolution interface using libc-ares
*/
@@ -107,7 +101,7 @@ static int resolv_mode = MODE_IPV4_FIRST;
static void resolv_sock_cb(struct ev_loop *, struct ev_io *, int);
static void resolv_timer_cb(struct ev_loop *, struct ev_timer *, int);
static void resolv_sock_state_cb(void *, int, int, int);
static void resolv_sock_state_cb(void *, ares_socket_t, int, int);
static void dns_query_v4_cb(void *, int, int, struct hostent *);
static void dns_query_v6_cb(void *, int, int, struct hostent *);
@@ -156,7 +150,7 @@ resolv_init(struct ev_loop *loop, char *nameservers, int ipv6first)
memset(&default_ctx, 0, sizeof(struct resolv_ctx));
default_ctx.options.sock_state_cb_data = &default_ctx;
default_ctx.options.sock_state_cb = CONV_STATE_CB resolv_sock_state_cb;
default_ctx.options.sock_state_cb = resolv_sock_state_cb;
default_ctx.options.timeout = 3000;
default_ctx.options.tries = 2;
@@ -439,7 +433,7 @@ all_requests_are_null(struct resolv_query *query)
static void
resolv_timer_cb(struct ev_loop *loop, struct ev_timer *w, int revents)
{
struct resolv_ctx *ctx = cork_container_of(w, struct resolv_ctx, timer);
struct resolv_ctx *ctx = ss_container_of(w, struct resolv_ctx, timer);
ev_tstamp now = ev_now(default_loop);
ev_tstamp after = ctx->last_tick - now + SS_TIMER_AFTER;
@@ -460,7 +454,7 @@ resolv_timer_cb(struct ev_loop *loop, struct ev_timer *w, int revents)
* Handle c-ares events
*/
static void
resolv_sock_state_cb(void *data, int s, int read, int write)
resolv_sock_state_cb(void *data, ares_socket_t s, int read, int write)
{
struct resolv_ctx *ctx = (struct resolv_ctx *)data;
int events = (read ? EV_READ : 0) | (write ? EV_WRITE : 0);
+53 -14
View File
@@ -67,21 +67,30 @@ accept_rule_arg(rule_t *rule, const char *arg)
}
void
add_rule(struct cork_dllist *rules, rule_t *rule)
add_rule(struct ss_list *rules, rule_t *rule)
{
cork_dllist_add(rules, &rule->entries);
ss_list_add(rules, &rule->entries);
}
int
init_rule(rule_t *rule)
{
if (strncmp(rule->pattern, "full:", 5) == 0) {
rule->kind = 1;
rule->literal = rule->pattern + 5;
} else if (strncmp(rule->pattern, "suffix:", 7) == 0) {
rule->kind = 2;
rule->literal = rule->pattern + 7;
}
if (rule->kind != 0) {
return *rule->literal != '\0' ? 1 : 0;
}
#if SS_ENABLE_REGEX
if (rule->pattern_re == NULL) {
int errcode;
PCRE2_SIZE erroffset;
rule->pattern_re =
pcre2_compile((PCRE2_SPTR)rule->pattern, PCRE2_ZERO_TERMINATED,
0, &errcode, &erroffset, NULL);
rule->pattern_re = pcre2_compile((PCRE2_SPTR)rule->pattern,
PCRE2_ZERO_TERMINATED, 0, &errcode, &erroffset, NULL);
if (rule->pattern_re == NULL) {
PCRE2_UCHAR errbuf[256];
pcre2_get_error_message(errcode, errbuf, sizeof(errbuf));
@@ -89,30 +98,58 @@ init_rule(rule_t *rule)
rule->pattern, errbuf, (int)erroffset);
return 0;
}
rule->match_data = pcre2_match_data_create_from_pattern(
rule->pattern_re, NULL);
rule->match_data = pcre2_match_data_create_from_pattern(rule->pattern_re, NULL);
if (rule->match_data == NULL) {
pcre2_code_free(rule->pattern_re);
rule->pattern_re = NULL;
return 0;
}
}
return 1;
#else
LOGE("Regex ACL support is disabled; use full:domain or suffix:domain: %s", rule->pattern);
return -1;
#endif
}
static bool
literal_equal(const char *a, const char *b, size_t length)
{
for (size_t i = 0; i < length; i++) {
unsigned char x = (unsigned char)a[i], y = (unsigned char)b[i];
if (x >= 'A' && x <= 'Z') x += 'a' - 'A';
if (y >= 'A' && y <= 'Z') y += 'a' - 'A';
if (x != y) return false;
}
return true;
}
rule_t *
lookup_rule(const struct cork_dllist *rules, const char *name, size_t name_len)
lookup_rule(const struct ss_list *rules, const char *name, size_t name_len)
{
struct cork_dllist_item *curr, *next;
struct ss_list_item *curr, *next;
if (name == NULL) {
name = "";
name_len = 0;
}
for (curr = cork_dllist_start(rules); !cork_dllist_is_end(rules, curr);
for (curr = ss_list_start(rules); !ss_list_is_end(rules, curr);
curr = next) {
next = curr->next;
rule_t *rule = cork_container_of(curr, rule_t, entries);
rule_t *rule = ss_container_of(curr, rule_t, entries);
if (rule->kind != 0) {
size_t length = strlen(rule->literal);
if (name_len >= length &&
(name_len == length || (rule->kind == 2 && name[name_len - length - 1] == '.')) &&
literal_equal(name + name_len - length, rule->literal, length)) return rule;
continue;
}
#if SS_ENABLE_REGEX
if (pcre2_match(rule->pattern_re, (PCRE2_SPTR)name,
name_len, 0, 0, rule->match_data, NULL) >= 0)
return rule;
#endif
}
return NULL;
@@ -121,7 +158,7 @@ lookup_rule(const struct cork_dllist *rules, const char *name, size_t name_len)
void
remove_rule(rule_t *rule)
{
cork_dllist_remove(&rule->entries);
ss_list_remove(&rule->entries);
free_rule(rule);
}
@@ -132,9 +169,11 @@ free_rule(rule_t *rule)
return;
ss_free(rule->pattern);
#if SS_ENABLE_REGEX
if (rule->match_data != NULL)
pcre2_match_data_free(rule->match_data);
if (rule->pattern_re != NULL)
pcre2_code_free(rule->pattern_re);
#endif
ss_free(rule);
}
+11 -4
View File
@@ -31,24 +31,31 @@
#include "config.h"
#endif
#include <libcork/ds.h>
#include "core.h"
#if SS_ENABLE_REGEX
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#endif
typedef struct rule {
struct cork_dllist_item entries;
struct ss_list_item entries;
char *pattern;
/* 0: regex, 1: full domain, 2: domain suffix (including the apex). */
unsigned kind;
const char *literal;
#if SS_ENABLE_REGEX
/* Runtime fields */
pcre2_code *pattern_re;
pcre2_match_data *match_data;
#endif
} rule_t;
void add_rule(struct cork_dllist *, rule_t *);
void add_rule(struct ss_list *, rule_t *);
int init_rule(rule_t *);
rule_t *lookup_rule(const struct cork_dllist *, const char *, size_t);
rule_t *lookup_rule(const struct ss_list *, const char *, size_t);
void free_rule(rule_t *);
void remove_rule(rule_t *);
rule_t *new_rule();
+70 -69
View File
@@ -43,7 +43,8 @@
#include <pthread.h>
#include <sys/un.h>
#endif
#include <libcork/core.h>
#include "core.h"
#include <inttypes.h>
#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_NET_IF_H) && defined(__linux__)
#include <net/if.h>
@@ -70,7 +71,7 @@ enum datatypes {
#include "acl.h"
#include "plugin.h"
#include "server.h"
#include "winsock.h"
#include "ss_windows.h"
#include "resolv.h"
#ifndef EAGAIN
@@ -166,7 +167,7 @@ static struct plugin_watcher_t {
} plugin_watcher;
#endif
static struct cork_dllist connections;
static struct ss_list connections;
#ifndef __MINGW32__
static void
@@ -188,7 +189,7 @@ stat_update_cb(EV_P_ ev_timer *watcher, int revents)
parse_addr(manager_addr, &ip_addr);
if (ip_addr.host == NULL || ip_addr.port == NULL) {
sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
sfd = ss_socket(AF_UNIX, SOCK_DGRAM, 0);
if (sfd == -1) {
ERROR("stat_socket");
return;
@@ -202,7 +203,7 @@ stat_update_cb(EV_P_ ev_timer *watcher, int revents)
if (bind(sfd, (struct sockaddr *)&claddr, sizeof(struct sockaddr_un)) == -1) {
ERROR("stat_bind");
close(sfd);
ss_socket_close(sfd);
return;
}
@@ -210,7 +211,7 @@ stat_update_cb(EV_P_ ev_timer *watcher, int revents)
svaddr.sun_family = AF_UNIX;
if (strlen(manager_addr) >= sizeof(svaddr.sun_path)) {
LOGE("manager unix socket path is too long");
close(sfd);
ss_socket_close(sfd);
unlink(claddr.sun_path);
return;
}
@@ -219,7 +220,7 @@ stat_update_cb(EV_P_ ev_timer *watcher, int revents)
if (sendto(sfd, resp, strlen(resp) + 1, 0, (struct sockaddr *)&svaddr,
sizeof(struct sockaddr_un)) != msgLen) {
ERROR("stat_sendto");
close(sfd);
ss_socket_close(sfd);
return;
}
@@ -232,7 +233,7 @@ stat_update_cb(EV_P_ ev_timer *watcher, int revents)
return;
}
sfd = socket(storage.ss_family, SOCK_DGRAM, 0);
sfd = ss_socket(storage.ss_family, SOCK_DGRAM, 0);
if (sfd == -1) {
ERROR("stat_socket");
@@ -243,12 +244,12 @@ stat_update_cb(EV_P_ ev_timer *watcher, int revents)
if (sendto(sfd, resp, strlen(resp) + 1, 0, (struct sockaddr *)&storage,
addr_len) != msgLen) {
ERROR("stat_sendto");
close(sfd);
ss_socket_close(sfd);
return;
}
}
close(sfd);
ss_socket_close(sfd);
}
#endif
@@ -256,9 +257,9 @@ stat_update_cb(EV_P_ ev_timer *watcher, int revents)
static void
free_connections(struct ev_loop *loop)
{
struct cork_dllist_item *curr, *next;
cork_dllist_foreach_void(&connections, curr, next) {
server_t *server = cork_container_of(curr, server_t, entries);
struct ss_list_item *curr, *next;
ss_list_foreach_void(&connections, curr, next) {
server_t *server = ss_container_of(curr, server_t, entries);
remote_t *remote = server->remote;
close_and_free_server(loop, server);
close_and_free_remote(loop, remote);
@@ -277,10 +278,10 @@ get_peer_name(int fd)
if (err == 0) {
if (addr.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
inet_ntop(AF_INET, &s->sin_addr, peer_name, INET_ADDRSTRLEN);
ss_inet_ntop(AF_INET, &s->sin_addr, peer_name, INET_ADDRSTRLEN);
} else if (addr.ss_family == AF_INET6) {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
inet_ntop(AF_INET6, &s->sin6_addr, peer_name, INET6_ADDRSTRLEN);
ss_inet_ntop(AF_INET6, &s->sin6_addr, peer_name, INET6_ADDRSTRLEN);
}
} else {
return NULL;
@@ -523,10 +524,10 @@ setfastopen(int fd)
#else
int opt = 5;
#endif
s = setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &opt, sizeof(opt));
s = ss_setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &opt, sizeof(opt));
if (s == -1) {
if (errno == EPROTONOSUPPORT || errno == ENOPROTOOPT) {
if (ss_socket_error() == EPROTONOSUPPORT || ss_socket_error() == ENOPROTOOPT) {
LOGE("fast open is not supported on this platform");
fast_open = 0;
} else {
@@ -604,20 +605,20 @@ create_and_bind(const char *host, const char *port, int mptcp)
if (mptcp < 0) {
protocol = IPPROTO_MPTCP; // Enable upstream MPTCP
}
listen_sock = socket(rp->ai_family, rp->ai_socktype, protocol);
listen_sock = ss_socket(rp->ai_family, rp->ai_socktype, protocol);
if (listen_sock == -1) {
continue;
}
if (rp->ai_family == AF_INET6) {
int opt = host ? 1 : 0;
setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
ss_setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
if (reuse_port) {
int err = set_reuseport(listen_sock);
@@ -627,18 +628,18 @@ create_and_bind(const char *host, const char *port, int mptcp)
}
if (tcp_incoming_sndbuf > 0) {
setsockopt(listen_sock, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof(int));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof(int));
}
if (tcp_incoming_rcvbuf > 0) {
setsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof(int));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof(int));
}
// Enable out-of-tree mptcp
if (mptcp == 1) {
int i = 0;
while ((mptcp = mptcp_enabled_values[i]) > 0) {
int err = setsockopt(listen_sock, IPPROTO_TCP, mptcp, &opt, sizeof(opt));
int err = ss_setsockopt(listen_sock, IPPROTO_TCP, mptcp, &opt, sizeof(opt));
if (err != -1) {
break;
}
@@ -658,7 +659,7 @@ create_and_bind(const char *host, const char *port, int mptcp)
FATAL("failed to bind address");
}
close(listen_sock);
ss_socket_close(listen_sock);
listen_sock = -1;
}
@@ -684,11 +685,11 @@ connect_to_remote(EV_P_ struct addrinfo *res,
if (res->ai_addr->sa_family == AF_INET) {
struct sockaddr_in s;
memcpy(&s, res->ai_addr, sizeof(struct sockaddr_in));
inet_ntop(AF_INET, &s.sin_addr, ipstr, INET_ADDRSTRLEN);
ss_inet_ntop(AF_INET, &s.sin_addr, ipstr, INET_ADDRSTRLEN);
} else if (res->ai_addr->sa_family == AF_INET6) {
struct sockaddr_in6 s;
memcpy(&s, res->ai_addr, sizeof(struct sockaddr_in6));
inet_ntop(AF_INET6, &s.sin6_addr, ipstr, INET6_ADDRSTRLEN);
ss_inet_ntop(AF_INET6, &s.sin6_addr, ipstr, INET6_ADDRSTRLEN);
}
if (outbound_block_match_host(ipstr) == 1) {
@@ -699,26 +700,26 @@ connect_to_remote(EV_P_ struct addrinfo *res,
}
// initialize remote socks
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
sockfd = ss_socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd == -1) {
ERROR("socket");
close(sockfd);
ss_socket_close(sockfd);
return NULL;
}
int opt = 1;
setsockopt(sockfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(sockfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ss_setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (tcp_outgoing_sndbuf > 0) {
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int));
ss_setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int));
}
if (tcp_outgoing_rcvbuf > 0) {
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &tcp_outgoing_rcvbuf, sizeof(int));
ss_setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &tcp_outgoing_rcvbuf, sizeof(int));
}
// setup remote socks
@@ -742,7 +743,7 @@ connect_to_remote(EV_P_ struct addrinfo *res,
if (iface) {
if (setinterface(sockfd, iface) == -1) {
ERROR("setinterface");
close(sockfd);
ss_socket_close(sockfd);
return NULL;
}
}
@@ -761,7 +762,7 @@ connect_to_remote(EV_P_ struct addrinfo *res,
do {
int optval = 1;
// Set fast open option
if (setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN,
if (ss_setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN,
&optval, sizeof(optval)) != 0) {
ERROR("setsockopt");
break;
@@ -802,7 +803,7 @@ connect_to_remote(EV_P_ struct addrinfo *res,
int s = -1;
#if defined(TCP_FASTOPEN_CONNECT)
int optval = 1;
if (setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
if (ss_setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
(void *)&optval, sizeof(optval)) < 0)
FATAL("failed to set TCP_FASTOPEN_CONNECT");
s = connect(sockfd, res->ai_addr, res->ai_addrlen);
@@ -824,11 +825,11 @@ connect_to_remote(EV_P_ struct addrinfo *res,
s = send(sockfd, server->buf->data + server->buf->idx, server->buf->len, 0);
#endif
if (s == -1) {
if (errno == CONNECT_IN_PROGRESS) {
if (ss_socket_error() == CONNECT_IN_PROGRESS) {
// The remote server doesn't support tfo or it's the first connection to the server.
// It will automatically fall back to conventional TCP.
} else if (errno == EOPNOTSUPP || errno == EPROTONOSUPPORT ||
errno == ENOPROTOOPT) {
} else if (ss_socket_error() == EOPNOTSUPP || ss_socket_error() == EPROTONOSUPPORT ||
ss_socket_error() == ENOPROTOOPT) {
// Disable fast open as it's not supported
fast_open = 0;
LOGE("fast open is not supported on this platform");
@@ -844,7 +845,7 @@ connect_to_remote(EV_P_ struct addrinfo *res,
if (!fast_open) {
int r = connect(sockfd, res->ai_addr, res->ai_addrlen);
if (r == -1 && errno != CONNECT_IN_PROGRESS) {
if (r == -1 && ss_socket_error() != CONNECT_IN_PROGRESS) {
ERROR("connect");
close_and_free_remote(EV_A_ remote);
return NULL;
@@ -866,7 +867,7 @@ setMarkDscpCallback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, vo
// Extract DSCP value from mark value
tracker->dscp = tracker->mark & 0x00ff;
int tos = (tracker->dscp) << 2;
if (setsockopt(server->fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) != 0) {
if (ss_setsockopt(server->fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) != 0) {
ERROR("iptable setsockopt IP_TOS");
}
}
@@ -884,7 +885,7 @@ conntrackQuery(server_t *server)
nfct_callback_register(h, NFCT_T_ALL, setMarkDscpCallback, (void *)server);
int x = nfct_query(h, NFCT_Q_GET, tracker->ct);
if (x == -1) {
LOGE("QOS: Failed to retrieve connection mark %s", strerror(errno));
LOGE("QOS: Failed to retrieve connection mark %s", strerror(ss_socket_error()));
}
nfct_close(h);
} else {
@@ -970,7 +971,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
close_and_free_server(EV_A_ server);
return;
} else if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
@@ -1007,7 +1008,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
if (server->stage == STAGE_STREAM) {
int s = send(remote->fd, remote->buf->data, remote->buf->len, 0);
if (s == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data, wait for send
remote->buf->idx = 0;
ev_io_stop(EV_A_ & server_recv_ctx->io);
@@ -1059,7 +1060,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
addr->sin_family = AF_INET;
if (server->buf->len >= in_addr_len + 3) {
memcpy(&addr->sin_addr, server->buf->data + offset, in_addr_len);
inet_ntop(AF_INET, (const void *)(server->buf->data + offset),
ss_inet_ntop(AF_INET, (const void *)(server->buf->data + offset),
host, INET_ADDRSTRLEN);
offset += in_addr_len;
} else {
@@ -1096,8 +1097,8 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
close_and_free_server(EV_A_ server);
return;
}
struct cork_ip ip;
if (cork_ip_init(&ip, host) != -1) {
struct ss_ip ip;
if (ss_ip_init(&ip, host) != -1) {
info.ai_socktype = SOCK_STREAM;
info.ai_protocol = IPPROTO_TCP;
if (ip.version == 4) {
@@ -1132,7 +1133,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
addr->sin6_family = AF_INET6;
if (server->buf->len >= in6_addr_len + 3) {
memcpy(&addr->sin6_addr, server->buf->data + offset, in6_addr_len);
inet_ntop(AF_INET6, (const void *)(server->buf->data + offset),
ss_inet_ntop(AF_INET6, (const void *)(server->buf->data + offset),
host, INET6_ADDRSTRLEN);
offset += in6_addr_len;
} else {
@@ -1243,7 +1244,7 @@ server_send_cb(EV_P_ ev_io *w, int revents)
ssize_t s = send(server->fd, server->buf->data + server->buf->idx,
server->buf->len, 0);
if (s == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) {
ERROR("server_send_send");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
@@ -1276,7 +1277,7 @@ static void
server_timeout_cb(EV_P_ ev_timer *watcher, int revents)
{
server_ctx_t *server_ctx
= cork_container_of(watcher, server_ctx_t, watcher);
= ss_container_of(watcher, server_ctx_t, watcher);
server_t *server = server_ctx->server;
remote_t *remote = server->remote;
@@ -1381,7 +1382,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
close_and_free_server(EV_A_ server);
return;
} else if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
@@ -1416,7 +1417,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
int s = send(server->fd, server->buf->data, server->buf->len, 0);
if (s == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data, wait for send
server->buf->idx = 0;
ev_io_stop(EV_A_ & remote_recv_ctx->io);
@@ -1437,8 +1438,8 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
// Disable TCP_NODELAY after the first response are sent
if (!remote->recv_ctx->connected && !no_delay) {
int opt = 0;
setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
}
remote->recv_ctx->connected = 1;
}
@@ -1482,7 +1483,7 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
}
// Make getpeername work
if (setsockopt(remote->fd, SOL_SOCKET,
if (ss_setsockopt(remote->fd, SOL_SOCKET,
SO_UPDATE_CONNECT_CONTEXT, NULL, 0) != 0) {
ERROR("setsockopt");
}
@@ -1523,7 +1524,7 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
ssize_t s = send(remote->fd, remote->buf->data + remote->buf->idx,
remote->buf->len, 0);
if (s == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) {
ERROR("remote_send_send");
// close and free
close_and_free_remote(EV_A_ remote);
@@ -1607,7 +1608,7 @@ close_and_free_remote(EV_P_ remote_t *remote)
if (remote != NULL) {
ev_io_stop(EV_A_ & remote->send_ctx->io);
ev_io_stop(EV_A_ & remote->recv_ctx->io);
close(remote->fd);
ss_socket_close(remote->fd);
free_remote(remote);
if (verbose) {
remote_conn--;
@@ -1658,7 +1659,7 @@ new_server(int fd, listen_ctx_t *listener)
ev_timer_init(&server->recv_ctx->watcher, server_timeout_cb,
timeout, timeout);
cork_dllist_add(&connections, &server->entries);
ss_list_add(&connections, &server->entries);
return server;
}
@@ -1677,7 +1678,7 @@ free_server(server_t *server)
free(tracker);
}
#endif
cork_dllist_remove(&server->entries);
ss_list_remove(&server->entries);
if (server->remote != NULL) {
server->remote->server = NULL;
@@ -1711,7 +1712,7 @@ close_and_free_server(EV_P_ server_t *server)
ev_io_stop(EV_A_ & server->send_ctx->io);
ev_io_stop(EV_A_ & server->recv_ctx->io);
ev_timer_stop(EV_A_ & server->recv_ctx->watcher);
close(server->fd);
ss_socket_close(server->fd);
free_server(server);
if (verbose) {
server_conn--;
@@ -1752,7 +1753,7 @@ static void
plugin_watcher_cb(EV_P_ ev_io *w, int revents)
{
char buf[1];
SOCKET fd = accept(plugin_watcher.fd, NULL, NULL);
SOCKET fd = ss_accept(plugin_watcher.fd, NULL, NULL);
if (fd == INVALID_SOCKET) {
return;
}
@@ -1772,7 +1773,7 @@ static void
accept_cb(EV_P_ ev_io *w, int revents)
{
listen_ctx_t *listener = (listen_ctx_t *)w;
int serverfd = accept(listener->fd, NULL, NULL);
int serverfd = ss_accept(listener->fd, NULL, NULL);
if (serverfd == -1) {
ERROR("accept");
return;
@@ -1784,16 +1785,16 @@ accept_cb(EV_P_ ev_io *w, int revents)
if ((get_acl_mode() == BLACK_LIST && acl_match_host(peer_name) == 1)
|| (get_acl_mode() == WHITE_LIST && acl_match_host(peer_name) >= 0)) {
LOGE("Access denied from %s", peer_name);
close(serverfd);
ss_socket_close(serverfd);
return;
}
}
}
int opt = 1;
setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
setnonblocking(serverfd);
@@ -2259,7 +2260,7 @@ main(int argc, char **argv)
// Listen on plugin control port
if (plugin != NULL && plugin_watcher.port != 0) {
SOCKET fd;
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
fd = ss_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd != INVALID_SOCKET) {
plugin_watcher.valid = 0;
do {
@@ -2410,7 +2411,7 @@ main(int argc, char **argv)
#endif
// Init connections
cork_dllist_init(&connections);
ss_list_init(&connections);
// start ev loop
ev_run(loop, 0);
@@ -2437,7 +2438,7 @@ main(int argc, char **argv)
listen_ctx_t *listen_ctx = &listen_ctx_list[i];
if (mode != UDP_ONLY) {
ev_io_stop(loop, &listen_ctx->io);
close(listen_ctx->fd);
ss_socket_close(listen_ctx->fd);
}
if (plugin != NULL)
break;
+3 -3
View File
@@ -24,7 +24,7 @@
#define _SERVER_H
#include <time.h>
#include <libcork/ds.h>
#include "core.h"
#ifdef HAVE_LIBEV_EV_H
#include <libev/ev.h>
@@ -33,7 +33,7 @@
#endif
#ifdef __MINGW32__
#include "winsock.h"
#include "ss_windows.h"
#endif
#include "crypto.h"
@@ -89,7 +89,7 @@ typedef struct server {
struct query *query;
struct cork_dllist_item entries;
struct ss_list_item entries;
#ifdef USE_NFCONNTRACK_TOS
struct dscptracker *tracker;
#endif
+13
View File
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#ifndef SS_PROCESS_H
#define SS_PROCESS_H
#include <stdbool.h>
#include <stdint.h>
struct ss_process;
struct ss_process *ss_process_new(const char *program);
void ss_process_arg(struct ss_process *process, const char *argument);
void ss_process_env(struct ss_process *process, const char *name, const char *value);
int ss_process_start(struct ss_process *process, uint16_t control_port);
bool ss_process_running(struct ss_process *process);
void ss_process_free(struct ss_process *process);
#endif
+4 -42
View File
@@ -20,8 +20,8 @@
* <http://www.gnu.org/licenses/>.
*/
#ifndef _WINSOCK_H
#define _WINSOCK_H
#ifndef SS_WINSOCK_COMPAT_H
#define SS_WINSOCK_COMPAT_H
#ifdef __MINGW32__
@@ -39,42 +39,11 @@
#endif
// Winsock headers
#include <windows.h>
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <mswsock.h>
// Override POSIX error number
#ifdef errno
#undef errno
#endif
#define errno WSAGetLastError()
#ifdef EWOULDBLOCK
#undef EWOULDBLOCK
#endif
#define EWOULDBLOCK WSAEWOULDBLOCK
#ifdef CONNECT_IN_PROGRESS
#undef CONNECT_IN_PROGRESS
#endif
#define CONNECT_IN_PROGRESS WSAEWOULDBLOCK
#ifdef EOPNOTSUPP
#undef EOPNOTSUPP
#endif
#define EOPNOTSUPP WSAEOPNOTSUPP
#ifdef EPROTONOSUPPORT
#undef EPROTONOSUPPORT
#endif
#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
#ifdef ENOPROTOOPT
#undef ENOPROTOOPT
#endif
#define ENOPROTOOPT WSAENOPROTOOPT
// Check if ConnectEx supported in header
#ifdef WSAID_CONNECTEX
// Hardcode TCP fast open option
@@ -85,13 +54,6 @@
#define TCP_FASTOPEN_WINSOCK 1
#endif
// Override close function
#define close(fd) closesocket(fd)
// Override MinGW functions
#define setsockopt(a, b, c, d, e) setsockopt(a, b, c, (const char *)(d), e)
#define inet_ntop(a, b, c, d) inet_ntop(a, (void *)(b), c, d)
// Override Windows built-in functions
#ifdef ERROR
#undef ERROR
@@ -120,4 +82,4 @@ int winsock_dummybind(SOCKET fd, struct sockaddr *sa);
#endif // __MINGW32__
#endif // _WINSOCK_H
#endif // SS_WINSOCK_COMPAT_H
+3 -1
View File
@@ -228,10 +228,12 @@ ss_url_free(ss_url_t *url)
int
ss_url_parse(const char *uri, ss_url_t *url)
{
if (uri == NULL || url == NULL)
if (url == NULL)
return -1;
memset(url, 0, sizeof(*url));
if (uri == NULL)
return -1;
if (strncasecmp(uri, SS_URL_SCHEME, strlen(SS_URL_SCHEME)) != 0)
return -1;
+2 -2
View File
@@ -391,7 +391,7 @@ stream_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx, size_t capacity)
}
crypto_stream_xor_ic((uint8_t *)(ciphertext->data + nonce_len),
(const uint8_t *)plaintext->data,
(uint64_t)(plaintext->len + padding),
(uint64_t)plaintext->len + padding,
(const uint8_t *)cipher_ctx->nonce,
cipher_ctx->counter / SODIUM_BLOCK_SIZE, cipher->key,
cipher->method);
@@ -557,7 +557,7 @@ stream_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
}
crypto_stream_xor_ic((uint8_t *)plaintext->data,
(const uint8_t *)(ciphertext->data),
(uint64_t)(ciphertext->len + padding),
(uint64_t)ciphertext->len + padding,
(const uint8_t *)cipher_ctx->nonce,
cipher_ctx->counter / SODIUM_BLOCK_SIZE, cipher->key,
cipher->method);
+43 -43
View File
@@ -47,13 +47,13 @@
#define SET_INTERFACE
#endif
#include <libcork/core.h>
#include "core.h"
#include "netutils.h"
#include "utils.h"
#include "plugin.h"
#include "tunnel.h"
#include "winsock.h"
#include "ss_windows.h"
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
@@ -80,7 +80,7 @@ static void close_and_free_server(EV_P_ server_t *server);
static int
validate_tunnel_addr(const ss_addr_t *addr)
{
struct cork_ip ip;
struct ss_ip ip;
uint16_t port;
if (addr->host == NULL || addr->port == NULL) {
@@ -89,7 +89,7 @@ validate_tunnel_addr(const ss_addr_t *addr)
if (ss_parse_uint16_port(addr->port, &port) == -1) {
return -1;
}
if (cork_ip_init(&ip, addr->host) == -1) {
if (ss_ip_init(&ip, addr->host) == -1) {
size_t host_len = strlen(addr->host);
if (!validate_hostname(addr->host, host_len)) {
return -1;
@@ -172,15 +172,15 @@ create_and_bind(const char *addr, const char *port)
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
listen_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
listen_sock = ss_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (listen_sock == -1) {
continue;
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
if (reuse_port) {
int err = set_reuseport(listen_sock);
@@ -190,11 +190,11 @@ create_and_bind(const char *addr, const char *port)
}
if (tcp_incoming_sndbuf > 0) {
setsockopt(listen_sock, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof(int));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof(int));
}
if (tcp_incoming_rcvbuf > 0) {
setsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof(int));
ss_setsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof(int));
}
s = bind(listen_sock, rp->ai_addr, rp->ai_addrlen);
@@ -205,7 +205,7 @@ create_and_bind(const char *addr, const char *port)
ERROR("bind");
}
close(listen_sock);
ss_socket_close(listen_sock);
listen_sock = -1;
}
@@ -234,7 +234,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
close_and_free_server(EV_A_ server);
return;
} else if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
@@ -260,7 +260,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
int s = send(remote->fd, remote->buf->data, remote->buf->len, 0);
if (s == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data, wait for send
remote->buf->idx = 0;
ev_io_stop(EV_A_ & server_recv_ctx->io);
@@ -297,7 +297,7 @@ server_send_cb(EV_P_ ev_io *w, int revents)
ssize_t s = send(server->fd, server->buf->data + server->buf->idx,
server->buf->len, 0);
if (s == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) {
ERROR("send");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
@@ -328,7 +328,7 @@ static void
remote_timeout_cb(EV_P_ ev_timer *watcher, int revents)
{
remote_ctx_t *remote_ctx
= cork_container_of(watcher, remote_ctx_t, watcher);
= ss_container_of(watcher, remote_ctx_t, watcher);
remote_t *remote = remote_ctx->remote;
server_t *server = remote->server;
@@ -358,7 +358,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
close_and_free_server(EV_A_ server);
return;
} else if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
@@ -385,7 +385,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
int s = send(server->fd, server->buf->data, server->buf->len, 0);
if (s == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data, wait for send
server->buf->idx = 0;
ev_io_stop(EV_A_ & remote_recv_ctx->io);
@@ -406,8 +406,8 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
// Disable TCP_NODELAY after the first response are sent
if (!remote->recv_ctx->connected && !no_delay) {
int opt = 0;
setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
}
remote->recv_ctx->connected = 1;
}
@@ -442,8 +442,8 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
buffer_t *abuf = remote->buf;
ss_addr_t *sa = &server->destaddr;
struct cork_ip ip;
if (cork_ip_init(&ip, sa->host) != -1) {
struct ss_ip ip;
if (ss_ip_init(&ip, sa->host) != -1) {
if (ip.version == 4) {
// send as IPv4
struct in_addr host;
@@ -532,7 +532,7 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
do {
int optval = 1;
// Set fast open option
if (setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN,
if (ss_setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN,
&optval, sizeof(optval)) != 0) {
ERROR("setsockopt");
break;
@@ -581,7 +581,7 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
NULL, 0, NULL, NULL);
#elif defined(TCP_FASTOPEN_CONNECT)
int optval = 1;
if (setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
if (ss_setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
(void *)&optval, sizeof(optval)) < 0)
FATAL("failed to set TCP_FASTOPEN_CONNECT");
s = connect(remote->fd, remote->addr, get_sockaddr_len(remote->addr));
@@ -598,13 +598,13 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
remote->addr = NULL;
if (s == -1) {
if (errno == CONNECT_IN_PROGRESS) {
if (ss_socket_error() == CONNECT_IN_PROGRESS) {
ev_io_start(EV_A_ & remote_send_ctx->io);
ev_timer_start(EV_A_ & remote_send_ctx->watcher);
} else {
fast_open = 0;
if (errno == EOPNOTSUPP || errno == EPROTONOSUPPORT ||
errno == ENOPROTOOPT) {
if (ss_socket_error() == EOPNOTSUPP || ss_socket_error() == EPROTONOSUPPORT ||
ss_socket_error() == ENOPROTOOPT) {
LOGE("fast open is not supported on this platform");
} else {
ERROR("fast_open_connect");
@@ -620,7 +620,7 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
}
if (s == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) {
ERROR("send");
// close and free
close_and_free_remote(EV_A_ remote);
@@ -690,7 +690,7 @@ close_and_free_remote(EV_P_ remote_t *remote)
ev_timer_stop(EV_A_ & remote->send_ctx->watcher);
ev_io_stop(EV_A_ & remote->send_ctx->io);
ev_io_stop(EV_A_ & remote->recv_ctx->io);
close(remote->fd);
ss_socket_close(remote->fd);
free_remote(remote);
}
}
@@ -755,7 +755,7 @@ close_and_free_server(EV_P_ server_t *server)
if (server != NULL) {
ev_io_stop(EV_A_ & server->send_ctx->io);
ev_io_stop(EV_A_ & server->recv_ctx->io);
close(server->fd);
ss_socket_close(server->fd);
free_server(server);
}
}
@@ -764,16 +764,16 @@ static void
accept_cb(EV_P_ ev_io *w, int revents)
{
struct listen_ctx *listener = (struct listen_ctx *)w;
int serverfd = accept(listener->fd, NULL, NULL);
int serverfd = ss_accept(listener->fd, NULL, NULL);
if (serverfd == -1) {
ERROR("accept");
return;
}
setnonblocking(serverfd);
int opt = 1;
setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
int index = (int)randombytes_uniform((uint32_t)listener->remote_num);
@@ -783,7 +783,7 @@ accept_cb(EV_P_ ev_io *w, int revents)
if (listener->mptcp < 0) {
protocol = IPPROTO_MPTCP; // Enable upstream MPTCP
}
int remotefd = socket(remote_addr->sa_family, SOCK_STREAM, protocol);
int remotefd = ss_socket(remote_addr->sa_family, SOCK_STREAM, protocol);
if (remotefd == -1) {
ERROR("socket");
return;
@@ -800,7 +800,7 @@ accept_cb(EV_P_ ev_io *w, int revents)
if (!not_protect) {
if (protect_socket(remotefd) == -1) {
ERROR("protect_socket");
close(remotefd);
ss_socket_close(remotefd);
return;
}
}
@@ -808,22 +808,22 @@ accept_cb(EV_P_ ev_io *w, int revents)
#endif
int keepAlive = 1;
setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(remotefd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
ss_setsockopt(remotefd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(remotefd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
ss_setsockopt(remotefd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
// Enable out-of-tree MPTCP
if (listener->mptcp > 1) {
int err = setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
int err = ss_setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
if (err == -1) {
ERROR("failed to enable out-of-tree multipath TCP");
}
} else if (listener->mptcp == 1) {
int i = 0;
while ((listener->mptcp = mptcp_enabled_values[i]) > 0) {
int err = setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
int err = ss_setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt));
if (err != -1) {
break;
}
@@ -835,11 +835,11 @@ accept_cb(EV_P_ ev_io *w, int revents)
}
if (tcp_outgoing_sndbuf > 0) {
setsockopt(remotefd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int));
ss_setsockopt(remotefd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int));
}
if (tcp_outgoing_rcvbuf > 0) {
setsockopt(remotefd, SOL_SOCKET, SO_RCVBUF, &tcp_outgoing_rcvbuf, sizeof(int));
ss_setsockopt(remotefd, SOL_SOCKET, SO_RCVBUF, &tcp_outgoing_rcvbuf, sizeof(int));
}
// Setup
@@ -862,7 +862,7 @@ accept_cb(EV_P_ ev_io *w, int revents)
} else {
int r = connect(remotefd, remote_addr, get_sockaddr_len(remote_addr));
if (r == -1 && errno != CONNECT_IN_PROGRESS) {
if (r == -1 && ss_socket_error() != CONNECT_IN_PROGRESS) {
ERROR("connect");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
@@ -907,7 +907,7 @@ static void
plugin_watcher_cb(EV_P_ ev_io *w, int revents)
{
char buf[1];
SOCKET fd = accept(plugin_watcher.fd, NULL, NULL);
SOCKET fd = ss_accept(plugin_watcher.fd, NULL, NULL);
if (fd == INVALID_SOCKET) {
return;
}
@@ -1334,7 +1334,7 @@ main(int argc, char **argv)
// Listen on plugin control port
if (plugin != NULL && plugin_watcher.port != 0) {
SOCKET fd;
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
fd = ss_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd != INVALID_SOCKET) {
plugin_watcher.valid = 0;
do {
+1 -1
View File
@@ -30,7 +30,7 @@
#endif
#ifdef __MINGW32__
#include "winsock.h"
#include "ss_windows.h"
#endif
#include "crypto.h"
+75 -73
View File
@@ -46,13 +46,13 @@
#define SET_INTERFACE
#endif
#include <libcork/core.h>
#include "core.h"
#include "utils.h"
#include "netutils.h"
#include "cache.h"
#include "udprelay.h"
#include "winsock.h"
#include "ss_windows.h"
#ifdef MODULE_REMOTE
#define MAX_UDP_CONN_NUM 512
@@ -105,7 +105,10 @@ extern struct sockaddr_storage local_addr_v6;
#endif
static int packet_size = DEFAULT_PACKET_SIZE;
static int buf_size = DEFAULT_PACKET_SIZE * 2;
/* UDP preserves datagram boundaries. MTU is a transmission hint, not a
* receive limit: a smaller buffer silently truncates valid fragmented packets
* and makes authentication fail. Covers the full IPv4/IPv6 UDP datagram. */
static const int buf_size = 65536;
static int server_num = 0;
static server_ctx_t *server_ctx_list[MAX_REMOTE_NUM] = { NULL };
@@ -129,7 +132,7 @@ static int
set_broadcast(int socket_fd)
{
int opt = 1;
return setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
return ss_setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
}
#endif
@@ -139,7 +142,7 @@ static int
set_nosigpipe(int socket_fd)
{
int opt = 1;
return setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
return ss_setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
}
#endif
@@ -264,7 +267,7 @@ parse_udprelay_header(const char *buf, const size_t buf_len,
memcpy(&addr->sin_port, buf + offset + in_addr_len, sizeof(uint16_t));
}
if (host != NULL) {
inet_ntop(AF_INET, (const void *)(buf + offset),
ss_inet_ntop(AF_INET, (const void *)(buf + offset),
host, INET_ADDRSTRLEN);
}
offset += in_addr_len;
@@ -278,10 +281,10 @@ parse_udprelay_header(const char *buf, const size_t buf_len,
if (name_len + 4 <= buf_len) {
if (storage != NULL) {
char tmp[MAX_HOSTNAME_LEN] = { 0 };
struct cork_ip ip;
struct ss_ip ip;
memcpy(tmp, buf + offset + 1, name_len);
tmp[name_len] = '\0';
if (cork_ip_init(&ip, tmp) != -1) {
if (ss_ip_init(&ip, tmp) != -1) {
if (ip.version == 4) {
struct sockaddr_in *addr = (struct sockaddr_in *)storage;
inet_pton(AF_INET, tmp, &(addr->sin_addr));
@@ -315,7 +318,7 @@ parse_udprelay_header(const char *buf, const size_t buf_len,
memcpy(&addr->sin6_port, buf + offset + in6_addr_len, sizeof(uint16_t));
}
if (host != NULL) {
inet_ntop(AF_INET6, (const void *)(buf + offset),
ss_inet_ntop(AF_INET6, (const void *)(buf + offset),
host, INET6_ADDRSTRLEN);
}
offset += in6_addr_len;
@@ -349,14 +352,14 @@ get_addr_str(const struct sockaddr *sa, bool has_port)
switch (sa->sa_family) {
case AF_INET:
memcpy(&sa_in, sa, sizeof(struct sockaddr_in));
inet_ntop(AF_INET, &sa_in.sin_addr, addr, INET_ADDRSTRLEN);
ss_inet_ntop(AF_INET, &sa_in.sin_addr, addr, INET_ADDRSTRLEN);
p = ntohs(sa_in.sin_port);
snprintf(port, sizeof(port), "%d", p);
break;
case AF_INET6:
memcpy(&sa_in6, sa, sizeof(struct sockaddr_in6));
inet_ntop(AF_INET6, &sa_in6.sin6_addr, addr, INET6_ADDRSTRLEN);
ss_inet_ntop(AF_INET6, &sa_in6.sin6_addr, addr, INET6_ADDRSTRLEN);
p = ntohs(sa_in6.sin6_port);
snprintf(port, sizeof(port), "%d", p);
break;
@@ -390,7 +393,7 @@ create_remote_socket(int ipv6)
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any;
addr.sin6_port = 0;
remote_sock = socket(AF_INET6, SOCK_DGRAM, 0);
remote_sock = ss_socket(AF_INET6, SOCK_DGRAM, 0);
if (remote_sock == -1) {
ERROR("[udp] cannot create socket");
return -1;
@@ -420,7 +423,7 @@ create_remote_socket(int ipv6)
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = 0;
remote_sock = socket(AF_INET, SOCK_DGRAM, 0);
remote_sock = ss_socket(AF_INET, SOCK_DGRAM, 0);
if (remote_sock == -1) {
ERROR("[udp] cannot create socket");
return -1;
@@ -448,7 +451,7 @@ create_remote_socket(int ipv6)
#if defined(__linux__)
// Disable fragmentation
int val = IP_PMTUDISC_DO;
setsockopt(remote_sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));
ss_setsockopt(remote_sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));
#endif
return remote_sock;
@@ -500,18 +503,18 @@ create_server_socket(const char *host, const char *port)
}
for (/*rp = result*/; rp != NULL; rp = rp->ai_next) {
server_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
server_sock = ss_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (server_sock == -1) {
continue;
}
if (rp->ai_family == AF_INET6) {
int ipv6only = host ? 1 : 0;
setsockopt(server_sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
ss_setsockopt(server_sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
}
int opt = 1;
setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ss_setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
set_nosigpipe(server_sock);
#endif
@@ -524,14 +527,14 @@ create_server_socket(const char *host, const char *port)
#ifdef IP_TOS
// Set QoS flag
int tos = 46 << 2;
int rc = setsockopt(server_sock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", errno);
int rc = ss_setsockopt(server_sock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
}
#ifdef IPV6_TCLASS
rc = setsockopt(server_sock, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", errno);
rc = ss_setsockopt(server_sock, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
}
#endif
#endif
@@ -541,12 +544,12 @@ create_server_socket(const char *host, const char *port)
int flag_t = rp->ai_family == AF_INET ? IP_TRANSPARENT : IPV6_TRANSPARENT;
int flag_r = rp->ai_family == AF_INET ? IP_RECVORIGDSTADDR : IPV6_RECVORIGDSTADDR;
if (setsockopt(server_sock, sol, flag_t, &opt, sizeof(opt))) {
if (ss_setsockopt(server_sock, sol, flag_t, &opt, sizeof(opt))) {
ERROR("[udp] setsockopt IP_TRANSPARENT");
exit(EXIT_FAILURE);
}
if (setsockopt(server_sock, sol, flag_r, &opt, sizeof(opt))) {
if (ss_setsockopt(server_sock, sol, flag_r, &opt, sizeof(opt))) {
FATAL("[udp] setsockopt IP_RECVORIGDSTADDR");
}
#endif
@@ -559,7 +562,7 @@ create_server_socket(const char *host, const char *port)
ERROR("[udp] bind");
}
close(server_sock);
ss_socket_close(server_sock);
server_sock = -1;
}
@@ -568,7 +571,7 @@ create_server_socket(const char *host, const char *port)
#if defined(__linux__)
// Disable fragmentation
int val = IP_PMTUDISC_DO;
setsockopt(server_sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));
ss_setsockopt(server_sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));
#endif
return server_sock;
@@ -637,7 +640,7 @@ close_and_free_remote(EV_P_ remote_ctx_t *ctx)
if (ctx != NULL) {
ev_timer_stop(EV_A_ & ctx->watcher);
ev_io_stop(EV_A_ & ctx->io);
close(ctx->fd);
ss_socket_close(ctx->fd);
if (ctx->udp_session != NULL
&& ctx->server_ctx != NULL
&& ctx->server_ctx->crypto->udp_session_release != NULL) {
@@ -651,7 +654,7 @@ static void
remote_timeout_cb(EV_P_ ev_timer *watcher, int revents)
{
remote_ctx_t *remote_ctx
= cork_container_of(watcher, remote_ctx_t, watcher);
= ss_container_of(watcher, remote_ctx_t, watcher);
if (verbose) {
LOGI("[udp] connection timeout");
@@ -704,14 +707,14 @@ resolv_cb(struct sockaddr *addr, void *data)
#ifdef IP_TOS
// Set QoS flag
int tos = 46 << 2;
int rc = setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", errno);
int rc = ss_setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
}
#ifdef IPV6_TCLASS
rc = setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", errno);
rc = ss_setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
}
#endif
#endif
@@ -774,7 +777,7 @@ void convert_ipv4_mapped_ipv6(struct sockaddr_storage* addr) {
uint8_t* ipv6_raw_addr = mapped_ipv6_addr->sin6_addr.s6_addr;
ipv6_raw_addr[10] = 0xff;
ipv6_raw_addr[11] = 0xff;
in_addr_t ipv4_raw_addr = ntohl(ipv4_addr->sin_addr.s_addr);
uint32_t ipv4_raw_addr = ntohl(ipv4_addr->sin_addr.s_addr);
ipv6_raw_addr[12] = (ipv4_raw_addr >> 24) & 0xff;
ipv6_raw_addr[13] = (ipv4_raw_addr >> 16) & 0xff;
ipv6_raw_addr[14] = (ipv4_raw_addr >> 8) & 0xff;
@@ -901,7 +904,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
if (buf->len > packet_size) {
if (verbose) {
LOGI("[udp] remote_recv_sendto fragmentation, MTU at least be: " SSIZE_FMT, buf->len + PACKET_HEADER_SIZE);
LOGI("[udp] remote_recv_sendto fragmentation, MTU at least be: " SIZE_FMT, buf->len + PACKET_HEADER_SIZE);
}
}
@@ -911,20 +914,20 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
size_t remote_dst_addr_len = get_sockaddr_len((struct sockaddr *)&dst_addr);
int src_fd = socket(AF_INET6, SOCK_DGRAM, 0);
int src_fd = ss_socket(AF_INET6, SOCK_DGRAM, 0);
if (src_fd < 0) {
ERROR("[udp] remote_recv_socket");
goto CLEAN_UP;
}
int opt = 1;
if (setsockopt(src_fd, SOL_IPV6, IPV6_TRANSPARENT, &opt, sizeof(opt))) {
if (ss_setsockopt(src_fd, SOL_IPV6, IPV6_TRANSPARENT, &opt, sizeof(opt))) {
ERROR("[udp] remote_recv_setsockopt");
close(src_fd);
ss_socket_close(src_fd);
goto CLEAN_UP;
}
if (setsockopt(src_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
if (ss_setsockopt(src_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
ERROR("[udp] remote_recv_setsockopt");
close(src_fd);
ss_socket_close(src_fd);
goto CLEAN_UP;
}
if (reuse_port) {
@@ -935,20 +938,20 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
#ifdef IP_TOS
// Set QoS flag
int tos = 46 << 2;
int rc = setsockopt(src_fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", errno);
int rc = ss_setsockopt(src_fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
}
#ifdef IPV6_TCLASS
rc = setsockopt(src_fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", errno);
rc = ss_setsockopt(src_fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
}
#endif
#endif
if (bind(src_fd, (struct sockaddr *)&dst_addr, remote_dst_addr_len) != 0) {
ERROR("[udp] remote_recv_bind");
close(src_fd);
ss_socket_close(src_fd);
goto CLEAN_UP;
}
@@ -960,19 +963,19 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
int s = sendto(src_fd, buf->data, buf->len, 0,
(struct sockaddr *)&mapped_src_addr, remote_src_addr_len);
if (s == -1 && !(errno == EAGAIN || errno == EWOULDBLOCK)) {
if (s == -1 && !(ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK)) {
ERROR("[udp] remote_recv_sendto");
close(src_fd);
ss_socket_close(src_fd);
goto CLEAN_UP;
}
close(src_fd);
ss_socket_close(src_fd);
#else
size_t remote_src_addr_len = get_sockaddr_len((struct sockaddr *)&remote_ctx->src_addr);
int s = sendto(server_ctx->fd, buf->data, buf->len, 0,
(struct sockaddr *)&remote_ctx->src_addr, remote_src_addr_len);
if (s == -1 && !(errno == EAGAIN || errno == EWOULDBLOCK)) {
if (s == -1 && !(ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK)) {
ERROR("[udp] remote_recv_sendto");
goto CLEAN_UP;
}
@@ -1026,7 +1029,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
goto CLEAN_UP;
} else if (buf->len > packet_size) {
if (verbose) {
LOGI("[udp] UDP server_recv_recvmsg fragmentation, MTU at least be: " SSIZE_FMT,
LOGI("[udp] UDP server_recv_recvmsg fragmentation, MTU at least be: " SIZE_FMT,
buf->len + PACKET_HEADER_SIZE);
}
}
@@ -1160,8 +1163,8 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
uint16_t port_net_num = htons(port_num);
int addr_header_len = 0;
struct cork_ip ip;
if (cork_ip_init(&ip, host) != -1) {
struct ss_ip ip;
if (ss_ip_init(&ip, host) != -1) {
if (ip.version == 4) {
// send as IPv4
struct in_addr host_addr;
@@ -1305,14 +1308,14 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
#ifdef IP_TOS
// Set QoS flag
int tos = 46 << 2;
int rc = setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", errno);
int rc = ss_setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
}
#ifdef IPV6_TCLASS
rc = setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", errno);
rc = ss_setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
}
#endif
#endif
@@ -1327,7 +1330,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
if (vpn) {
if (protect_socket(remotefd) == -1) {
ERROR("protect_socket");
close(remotefd);
ss_socket_close(remotefd);
goto CLEAN_UP;
}
}
@@ -1363,7 +1366,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
if (buf->len > packet_size) {
if (verbose) {
LOGI("[udp] server_recv_sendto fragmentation, MTU at least be: " SSIZE_FMT, buf->len + PACKET_HEADER_SIZE);
LOGI("[udp] server_recv_sendto fragmentation, MTU at least be: " SIZE_FMT, buf->len + PACKET_HEADER_SIZE);
}
}
@@ -1382,7 +1385,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
if (buf->len - addr_header_len > packet_size) {
if (verbose) {
LOGI("[udp] server_recv_sendto fragmentation, MTU at least be: " SSIZE_FMT,
LOGI("[udp] server_recv_sendto fragmentation, MTU at least be: " SIZE_FMT,
buf->len - addr_header_len + PACKET_HEADER_SIZE);
}
}
@@ -1406,14 +1409,14 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
#ifdef IP_TOS
// Set QoS flag
int tos = 46 << 2;
int rc = setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", errno);
int rc = ss_setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
}
#ifdef IPV6_TCLASS
rc = setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
if (rc < 0 && errno != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", errno);
rc = ss_setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
}
#endif
#endif
@@ -1521,7 +1524,6 @@ init_udprelay(const char *server_host, const char *server_port,
return -1;
}
packet_size = mtu - PACKET_HEADER_SIZE;
buf_size = packet_size * 2;
}
// ////////////////////////////////////////////////
@@ -1568,7 +1570,7 @@ free_udprelay()
while (server_num > 0) {
server_ctx_t *server_ctx = server_ctx_list[--server_num];
ev_io_stop(loop, &server_ctx->io);
close(server_ctx->fd);
ss_socket_close(server_ctx->fd);
cache_delete(server_ctx->conn_cache, 0);
#ifdef MODULE_LOCAL
free((char*) server_ctx->remote_addr);
+1 -6
View File
@@ -192,14 +192,9 @@ extern int use_syslog;
#endif // if __ANDROID__
// Workaround for "%z" in Windows printf
#ifdef __MINGW32__
#define SSIZE_FMT "%Id"
#define SIZE_FMT "%Iu"
#else
/* C99 size formats; MinGW targets explicitly enable its ANSI stdio adapter. */
#define SSIZE_FMT "%zd"
#define SIZE_FMT "%zu"
#endif
#ifdef __MINGW32__
// Override Windows built-in functions
+1 -1
View File
@@ -22,7 +22,7 @@
#ifdef __MINGW32__
#include "winsock.h"
#include "ss_windows.h"
#include "utils.h"
#ifndef ENABLE_QUICK_EDIT
+73 -68
View File
@@ -6,24 +6,28 @@ include_directories(${SODIUM_INCLUDE_DIRS})
include_directories(${PCRE2_INCLUDE_DIRS})
include_directories(${CARES_INCLUDE_DIRS})
if(WITH_EMBEDDED_SRC)
include_directories(${PROJECT_SOURCE_DIR}/libcork/include)
include_directories(${PROJECT_SOURCE_DIR}/libipset/include)
include_directories(${PROJECT_SOURCE_DIR}/libbloom/murmur2)
include_directories(${PROJECT_SOURCE_DIR}/libbloom)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
find_program(VALGRIND_EXECUTABLE NAMES valgrind)
endif()
# Helper function to add a unit test
function(ss_add_test name sources libs)
if(WIN32 AND "${sources}" MATCHES "utils.c")
list(APPEND sources ${PROJECT_SOURCE_DIR}/src/winsock.c)
endif()
add_executable(${name} ${sources})
target_compile_definitions(${name} PRIVATE -DHAVE_CONFIG_H)
target_link_libraries(${name} ${libs})
if(WIN32)
target_link_libraries(${name} ws2_32)
endif()
# Keep assertions enabled for tests even in Release builds.
target_compile_options(${name} PRIVATE -UNDEBUG)
ss_project_options(${name})
if(MSVC)
target_compile_options(${name} PRIVATE /UNDEBUG)
else()
target_compile_options(${name} PRIVATE -UNDEBUG)
endif()
add_test(NAME ${name} COMMAND ${name})
set_tests_properties(${name} PROPERTIES LABELS "unit")
@@ -53,88 +57,67 @@ ss_add_test(test_base64
# test_utils - needs sodium (for ss_malloc -> uses sodium in utils.c)
ss_add_test(test_utils
"test_utils.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"${LIBSODIUM_SHARED}")
"ss_sodium")
# test_json - standalone json parser
ss_add_test(test_json
"test_json.c;${PROJECT_SOURCE_DIR}/src/json.c"
"m")
"${SS_MATH_LIBRARY}")
# test_ssurl - ss:// URI parsing (SIP002 and legacy)
ss_add_test(test_ssurl
"test_ssurl.c;${PROJECT_SOURCE_DIR}/src/ssurl.c;${PROJECT_SOURCE_DIR}/src/base64.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"${LIBSODIUM_SHARED}")
"ss_sodium")
# test_netutils - needs cork for ip address parsing
# test_netutils - address parsing and resolver integration
set(TEST_NETUTILS_LIBS
${LIBSODIUM_SHARED}
${LIBEV_SHARED}
${LIBUDNS_SHARED}
ss_sodium
ss_ev
ss_cares
)
if(WITH_EMBEDDED_SRC)
list(APPEND TEST_NETUTILS_LIBS cork)
else()
list(APPEND TEST_NETUTILS_LIBS ${LIBCORK_SHARED})
endif()
ss_add_test(test_netutils
"test_netutils.c;${PROJECT_SOURCE_DIR}/src/netutils.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"${TEST_NETUTILS_LIBS}")
# test_cache - needs libev for ev_time()
# test_cache - expiration uses a platform monotonic clock
ss_add_test(test_cache
"test_cache.c;${PROJECT_SOURCE_DIR}/src/cache.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"${LIBEV_SHARED};${LIBSODIUM_SHARED}")
"ss_sodium")
# test_ppbloom - needs bloom
set(TEST_PPBLOOM_LIBS "")
if(WITH_EMBEDDED_SRC)
list(APPEND TEST_PPBLOOM_LIBS bloom)
else()
list(APPEND TEST_PPBLOOM_LIBS ${LIBBLOOM_SHARED})
endif()
list(APPEND TEST_PPBLOOM_LIBS bloom)
ss_add_test(test_ppbloom
"test_ppbloom.c;${PROJECT_SOURCE_DIR}/src/ppbloom.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"${TEST_PPBLOOM_LIBS};${LIBSODIUM_SHARED};m")
"${TEST_PPBLOOM_LIBS};ss_sodium;${SS_MATH_LIBRARY}")
# test_rule - needs pcre and cork
if(SS_ENABLE_REGEX)
# test_rule - regex matching
set(TEST_RULE_LIBS
${LIBPCRE2_SHARED}
${LIBSODIUM_SHARED}
ss_pcre2
ss_sodium
)
if(WITH_EMBEDDED_SRC)
list(APPEND TEST_RULE_LIBS cork)
else()
list(APPEND TEST_RULE_LIBS ${LIBCORK_SHARED})
endif()
ss_add_test(test_rule
"test_rule.c;${PROJECT_SOURCE_DIR}/src/rule.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"${TEST_RULE_LIBS}")
# test_acl - needs pcre, cork, and ipset
# test_acl - ACL precedence and prefix matching
set(TEST_ACL_LIBS ${TEST_RULE_LIBS})
if(WITH_EMBEDDED_SRC)
list(APPEND TEST_ACL_LIBS ipset)
else()
list(APPEND TEST_ACL_LIBS ${LIBCORKIPSET_SHARED})
endif()
ss_add_test(test_acl
"test_acl.c;${PROJECT_SOURCE_DIR}/src/acl.c;${PROJECT_SOURCE_DIR}/src/rule.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"test_acl.c;${PROJECT_SOURCE_DIR}/src/acl.c;${PROJECT_SOURCE_DIR}/src/ipset.c;${PROJECT_SOURCE_DIR}/src/rule.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"${TEST_ACL_LIBS}")
# test_jconf - needs cork and json
set(TEST_JCONF_LIBS
${LIBSODIUM_SHARED}
${LIBEV_SHARED}
${LIBUDNS_SHARED}
)
if(WITH_EMBEDDED_SRC)
list(APPEND TEST_JCONF_LIBS cork)
else()
list(APPEND TEST_JCONF_LIBS ${LIBCORK_SHARED})
endif()
# test_jconf - configuration parsing
set(TEST_JCONF_LIBS
ss_sodium
ss_ev
ss_cares
)
ss_add_test(test_jconf
"test_jconf.c;${PROJECT_SOURCE_DIR}/src/jconf.c;${PROJECT_SOURCE_DIR}/src/json.c;${PROJECT_SOURCE_DIR}/src/utils.c;${PROJECT_SOURCE_DIR}/src/netutils.c"
"${TEST_JCONF_LIBS};m")
"${TEST_JCONF_LIBS};${SS_MATH_LIBRARY}")
# Crypto sources shared by the tests that link the crypto layer. The AEAD-2022
# ciphers pull in BLAKE3 (linked as the `blake3` target) and the session-based
@@ -144,40 +127,39 @@ set(SS_CRYPTO_TEST_SOURCES
${PROJECT_SOURCE_DIR}/src/aead.c
${PROJECT_SOURCE_DIR}/src/aead2022.c
${PROJECT_SOURCE_DIR}/src/aead2022_udp.c
${PROJECT_SOURCE_DIR}/src/stream.c
${PROJECT_SOURCE_DIR}/src/ppbloom.c
${PROJECT_SOURCE_DIR}/src/cache.c
${PROJECT_SOURCE_DIR}/src/base64.c
${PROJECT_SOURCE_DIR}/src/utils.c
)
if(SS_ENABLE_LEGACY)
list(APPEND SS_CRYPTO_TEST_SOURCES ${PROJECT_SOURCE_DIR}/src/stream.c)
endif()
# test_buffer - needs sodium and mbedtls for crypto.c buffer functions
ss_add_test(test_buffer
"test_buffer.c;${SS_CRYPTO_TEST_SOURCES}"
"${LIBSODIUM_SHARED};${LIBMBEDTLS_SHARED};${LIBMBEDCRYPTO_SHARED};${LIBEV_SHARED};${TEST_PPBLOOM_LIBS};blake3;m")
"ss_sodium;;ss_mbedcrypto;ss_ev;${TEST_PPBLOOM_LIBS};blake3;${SS_MATH_LIBRARY}")
# test_crypto - full crypto test
set(TEST_CRYPTO_LIBS
${LIBSODIUM_SHARED}
${LIBMBEDTLS_SHARED}
${LIBMBEDCRYPTO_SHARED}
${LIBEV_SHARED}
ss_sodium
ss_mbedcrypto
ss_ev
)
if(WITH_EMBEDDED_SRC)
list(APPEND TEST_CRYPTO_LIBS bloom)
else()
list(APPEND TEST_CRYPTO_LIBS ${LIBBLOOM_SHARED})
endif()
list(APPEND TEST_CRYPTO_LIBS bloom)
# BLAKE3 backs the 2022 ciphers' key derivation
list(APPEND TEST_CRYPTO_LIBS blake3)
ss_add_test(test_crypto
"test_crypto.c;${SS_CRYPTO_TEST_SOURCES}"
"${TEST_CRYPTO_LIBS};m")
"${TEST_CRYPTO_LIBS};${SS_MATH_LIBRARY}")
# test_aead2022 - Shadowsocks 2022 Edition (SIP022) ciphers
ss_add_test(test_aead2022
"test_aead2022.c;${SS_CRYPTO_TEST_SOURCES}"
"${TEST_CRYPTO_LIBS};m")
"${TEST_CRYPTO_LIBS};${SS_MATH_LIBRARY}")
# test_interop_rust - interoperability against shadowsocks-rust.
# Skips itself when shadowsocks-rust or the built binaries are absent.
@@ -187,10 +169,33 @@ add_test(NAME test_interop_rust
set_tests_properties(test_interop_rust PROPERTIES
LABELS "integration"
TIMEOUT 300
ENVIRONMENT "SS_BIN_DIR=${RUNTIME_SHARED_OUTPUT_DIRECTORY}")
SKIP_RETURN_CODE 77
ENVIRONMENT "SS_BIN_DIR=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
# test_ss_setup - bash unit tests for the ss-setup TUI tool
add_test(NAME test_ss_setup
COMMAND bash "${PROJECT_SOURCE_DIR}/tests/test_ss_setup.sh"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}")
set_tests_properties(test_ss_setup PROPERTIES LABELS "integration")
ss_add_test(test_ipset
"test_ipset.c;${PROJECT_SOURCE_DIR}/src/ipset.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"ss_sodium")
ss_add_test(test_process
"test_process.c;${PROJECT_SOURCE_DIR}/src/process.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"ss_sodium")
set_tests_properties(test_process PROPERTIES TIMEOUT 20)
ss_add_test(test_resolv
"test_resolv.c;${PROJECT_SOURCE_DIR}/src/resolv.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"ss_ev;ss_cares;ss_sodium")
set_tests_properties(test_resolv PROPERTIES TIMEOUT 20)
set(TEST_LITERAL_LIBS ss_sodium)
if(SS_ENABLE_REGEX)
list(APPEND TEST_LITERAL_LIBS ss_pcre2)
endif()
ss_add_test(test_acl_literal
"test_acl_literal.c;${PROJECT_SOURCE_DIR}/src/acl.c;${PROJECT_SOURCE_DIR}/src/ipset.c;${PROJECT_SOURCE_DIR}/src/rule.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"${TEST_LITERAL_LIBS}")
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Reject non-system runtime dependencies in static macOS/Windows outputs."""
import argparse
import platform
import re
import subprocess
from pathlib import Path
WINDOWS_SYSTEM_DLLS = {
'advapi32.dll', 'bcrypt.dll', 'crypt32.dll', 'dnsapi.dll', 'iphlpapi.dll',
'kernel32.dll', 'msvcrt.dll', 'ntdll.dll', 'ole32.dll', 'psapi.dll',
'rpcrt4.dll', 'secur32.dll', 'shell32.dll', 'ucrtbase.dll', 'user32.dll',
'userenv.dll', 'winmm.dll', 'ws2_32.dll',
}
def dependencies(binary, system):
if system == 'Darwin':
output = subprocess.check_output(['otool', '-L', str(binary)], text=True)
return [line.strip().split(' (compatibility version', 1)[0]
for line in output.splitlines() if line.startswith('\t')]
output = subprocess.check_output(
['llvm-readobj', '--coff-imports', str(binary)], text=True)
return re.findall(r'^\s+Name: (.+)$', output, re.MULTILINE)
def is_system_dependency(name, system):
if system == 'Darwin':
return name.startswith(('/usr/lib/', '/System/Library/'))
name = name.strip().lower()
return name in WINDOWS_SYSTEM_DLLS or (
name.startswith(('api-ms-win-', 'ext-ms-win-')) and name.endswith('.dll'))
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('binaries', type=Path, nargs='+')
args = parser.parse_args()
system = platform.system()
if system not in ('Darwin', 'Windows'):
parser.error('this check supports macOS and native Windows Python')
for binary in args.binaries:
names = dependencies(binary, system)
if not names:
raise SystemExit(f'{binary}: no OS imports found; check file format/tool output')
unexpected = [name for name in names if not is_system_dependency(name, system)]
if unexpected:
raise SystemExit(f'{binary}: non-system dependencies: {unexpected}')
print(f'PASS {binary}: {", ".join(names)}')
if __name__ == '__main__':
main()
+9
View File
@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.20)
project(shadowsocks_consumer LANGUAGES C)
find_package(shadowsocks-libev CONFIG REQUIRED)
foreach(kind shared static)
if(TARGET shadowsocks::${kind})
add_executable(consumer_${kind} main.c)
target_link_libraries(consumer_${kind} PRIVATE shadowsocks::${kind})
endif()
endforeach()
+7
View File
@@ -0,0 +1,7 @@
#include <stddef.h>
#include <shadowsocks.h>
/* Force the linker to resolve the complete embedding API and its dependencies
* without starting an unbounded event loop in this installation smoke test. */
static int (*volatile start)(profile_t) = start_ss_local_server;
static int (*volatile callback_start)(profile_t, ss_local_callback, void *) = start_ss_local_server_with_callback;
int main(void) { return start == NULL || callback_start == NULL; }
+275
View File
@@ -0,0 +1,275 @@
#!/usr/bin/env python3
"""Real TCP/UDP interoperability, independent of HTTP proxy environment variables."""
import argparse
import base64
import concurrent.futures
import contextlib
import os
from pathlib import Path
import shutil
import socket
import socketserver
import struct
import subprocess
import tempfile
import threading
import time
class TCPHandler(socketserver.BaseRequestHandler):
def handle(self):
self.request.settimeout(15)
try:
while chunk := self.request.recv(16384):
self.request.sendall(chunk)
except (OSError, TimeoutError):
pass
class UDPHandler(socketserver.BaseRequestHandler):
def handle(self):
data, sock = self.request
sock.sendto(data, self.client_address)
class TCPOrigin(socketserver.ThreadingTCPServer):
daemon_threads = True
class UDPOrigin(socketserver.ThreadingUDPServer):
daemon_threads = True
def receive(sock, size):
data = bytearray()
while len(data) < size:
chunk = sock.recv(size - len(data))
if not chunk:
raise AssertionError(f"unexpected EOF after {len(data)}/{size} bytes")
data.extend(chunk)
return bytes(data)
def address(sock, kind):
if kind == 1:
host = socket.inet_ntop(socket.AF_INET, receive(sock, 4))
elif kind == 4:
host = socket.inet_ntop(socket.AF_INET6, receive(sock, 16))
elif kind == 3:
host = receive(sock, receive(sock, 1)[0]).decode("ascii")
else:
raise AssertionError(f"invalid SOCKS address type {kind}")
return host, struct.unpack("!H", receive(sock, 2))[0]
def socks(proxy_port, command, destination):
sock = socket.create_connection(("127.0.0.1", proxy_port), timeout=10)
try:
sock.sendall(b"\x05\x01\x00")
assert receive(sock, 2) == b"\x05\x00", "SOCKS authentication failed"
host, port = destination
encoded = host.encode("ascii")
sock.sendall(bytes([5, command, 0, 3, len(encoded)]) + encoded + struct.pack("!H", port))
header = receive(sock, 4)
assert header[:3] == b"\x05\x00\x00", f"SOCKS request failed: {header.hex()}"
relay = address(sock, header[3])
return sock, relay
except BaseException:
sock.close()
raise
def tcp_case(proxy_port, origin_port, size):
sock, _ = socks(proxy_port, 1, ("localhost", origin_port))
payload = os.urandom(size)
with sock, concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
# Upload and download concurrently to exercise backpressure in both directions.
upload = pool.submit(sock.sendall, payload)
assert receive(sock, len(payload)) == payload, "TCP payload mismatch"
upload.result(timeout=10)
def udp_case(proxy_port, origin_port):
control, relay = socks(proxy_port, 3, ("0.0.0.0", 0))
host, port = relay
if host in ("0.0.0.0", "::"):
host = "127.0.0.1"
with control, socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.settimeout(10)
for size in (1, 128, 1200, 4096):
payload = os.urandom(size)
header = b"\x00\x00\x00\x01" + socket.inet_aton("127.0.0.1") + struct.pack("!H", origin_port)
sock.sendto(header + payload, (host, port))
try:
reply, sender = sock.recvfrom(65535)
except TimeoutError as error:
raise AssertionError(f"UDP timeout for {size}-byte payload") from error
assert sender[1] == port, "UDP reply bypassed relay"
assert reply[:3] == b"\x00\x00\x00", "fragmented or invalid SOCKS UDP reply"
if reply[3] == 1:
offset = 10
elif reply[3] == 4:
offset = 22
elif reply[3] == 3:
offset = 7 + reply[4]
else:
raise AssertionError("invalid UDP address type")
assert reply[offset:] == payload, "UDP payload mismatch"
def free_port():
# TCP and UDP have separate port reservations, especially on Windows.
# Probe both while holding the TCP socket so a TCP-only free port cannot
# select a reserved or occupied UDP endpoint.
for _ in range(100):
with socket.socket() as tcp, socket.socket(type=socket.SOCK_DGRAM) as udp:
tcp.bind(("127.0.0.1", 0))
port = tcp.getsockname()[1]
try:
udp.bind(("127.0.0.1", port))
except OSError:
continue
return port
raise RuntimeError("could not find a free TCP/UDP port")
def wait_ready(process, port):
deadline = time.monotonic() + 15
while time.monotonic() < deadline:
if process.poll() is not None:
raise AssertionError(f"peer exited with {process.returncode}")
try:
with socket.create_connection(("127.0.0.1", port), timeout=.1):
return
except OSError:
time.sleep(.03)
raise AssertionError(f"peer failed to listen on port {port}")
@contextlib.contextmanager
def peers(commands, ports, environment=None):
processes = []
with tempfile.TemporaryDirectory(prefix="ss-interop-") as temp, contextlib.ExitStack() as stack:
logs = []
try:
for index, (command, port) in enumerate(zip(commands, ports)):
path = Path(temp) / f"peer-{index}.log"
log = stack.enter_context(path.open("wb"))
logs.append(path)
process = subprocess.Popen(command, stdout=log, stderr=subprocess.STDOUT, env=environment)
processes.append(process)
wait_ready(process, port)
yield
except BaseException:
for path in logs:
print(path.name + ":\n" + path.read_text(errors="replace")[-6000:], flush=True)
raise
finally:
for process in reversed(processes):
if process.poll() is None:
process.terminate()
for process in reversed(processes):
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.wait(timeout=5)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--self", action="store_true", help="use this build for both peers")
parser.add_argument("--method", help="run one cipher")
parser.add_argument("--bin", default=os.environ.get("SS_BIN_DIR", "build/bin"), help="program directory")
parser.add_argument("--plugin", help="SIP003 fixture executable (requires --self)")
parser.add_argument("--isolate-windows-runtime", action="store_true",
help="remove MSYS2/toolchain DLL directories from child PATH")
args = parser.parse_args()
if args.plugin and not args.self:
parser.error("--plugin requires --self")
environment = None
if args.isolate_windows_runtime:
if os.name != "nt":
parser.error("--isolate-windows-runtime requires Windows")
environment = os.environ.copy()
system_root = os.environ["SYSTEMROOT"]
environment["PATH"] = os.pathsep.join((system_root, os.path.join(system_root, "System32")))
binary_dir = Path(args.bin).resolve()
suffix = ".exe" if os.name == "nt" else ""
local, server = [str(binary_dir / (name + suffix)) for name in ("ss-local", "ss-server")]
rust_local, rust_server = shutil.which("sslocal"), shutil.which("ssserver")
missing = [p for p in (local, server) if not os.path.isfile(p)]
if not args.self:
missing += [name for name, found in (("sslocal", rust_local), ("ssserver", rust_server)) if not found]
if missing:
print("Missing interoperability prerequisites: " + ", ".join(missing))
return 1 if os.environ.get("SS_REQUIRE_INTEROP") == "1" else 77
methods = ["2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305", "aes-128-gcm", "aes-256-gcm",
"chacha20-ietf-poly1305"]
if args.method:
methods = [args.method]
failures = 0
with TCPOrigin(("127.0.0.1", 0), TCPHandler) as tcp, UDPOrigin(("127.0.0.1", 0), UDPHandler) as udp:
for origin in (tcp, udp):
threading.Thread(target=origin.serve_forever, daemon=True).start()
try:
for method in methods:
key_size = 16 if method == "2022-blake3-aes-128-gcm" else 32
psk = base64.b64encode(os.urandom(key_size)).decode("ascii")
directions = ("self",) if args.self else ("C client -> Rust server", "Rust client -> C server")
for direction in directions:
label = f"{method}: {direction}"
print("RUN " + label, flush=True)
server_port, local_port = free_port(), free_port()
c_server = [server, "-s", "127.0.0.1", "-p", str(server_port), "-k", psk, "-m", method, "-u"]
c_local = [local, "-s", "127.0.0.1", "-p", str(server_port), "-l", str(local_port), "-k", psk, "-m", method, "-u"]
commands = [c_server, c_local]
plugin_temp = tempfile.TemporaryDirectory(prefix="ss-plugin-") if args.plugin else None
markers = []
if args.plugin:
for mode, command in zip(("server", "client"), commands):
marker = Path(plugin_temp.name) / mode
markers.append(marker)
command.extend(["--plugin", str(Path(args.plugin).resolve()),
"--plugin-opts", f"{mode};marker={marker}"])
if direction.startswith("C client"):
commands[0] = [rust_server, "-s", f"127.0.0.1:{server_port}", "-k", psk, "-m", method, "-U"]
elif direction.startswith("Rust client"):
commands[1] = [rust_local, "-b", f"127.0.0.1:{local_port}", "-s", f"127.0.0.1:{server_port}", "-k", psk, "-m", method, "-U"]
try:
with peers(commands, [server_port, local_port], environment):
for marker in markers:
deadline = time.monotonic() + 10
while not marker.exists() and time.monotonic() < deadline:
time.sleep(.03)
assert marker.exists(), "plugin did not become ready"
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool:
cases = [pool.submit(tcp_case, local_port, tcp.server_address[1], size)
for size in (1, 65536, 1048576)]
for case in cases:
case.result(timeout=30)
udp_case(local_port, udp.server_address[1])
for marker in markers:
pid = int(marker.read_text())
try:
os.kill(pid, 0)
except ProcessLookupError:
pass
else:
raise AssertionError(f"plugin child {pid} survived parent shutdown")
print("PASS TCP (3 concurrent streams) + UDP: " + label, flush=True)
except (OSError, AssertionError, TimeoutError) as error:
failures += 1
print(f"FAIL {label}: {error}", flush=True)
finally:
if plugin_temp is not None:
plugin_temp.cleanup()
finally:
tcp.shutdown()
udp.shutdown()
return int(failures != 0)
if __name__ == "__main__":
raise SystemExit(main())
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Loopback UDP echo measurement; includes Python/SOCKS overhead, not a capacity test."""
import argparse
import base64
import json
import os
from pathlib import Path
import socket
import struct
import threading
import time
from interop import UDPOrigin, UDPHandler, free_port, peers, socks
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--bin", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path)
parser.add_argument("--count", type=int, default=2000)
parser.add_argument("--repeat", type=int, default=3)
args = parser.parse_args()
results = []
with UDPOrigin(("127.0.0.1", 0), UDPHandler) as origin:
threading.Thread(target=origin.serve_forever, daemon=True).start()
try:
for method in ("aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305"):
server_port, local_port = free_port(), free_port()
key = base64.b64encode(os.urandom(32)).decode("ascii")
common = ["-s", "127.0.0.1", "-p", str(server_port), "-m", method, "-k", key, "-u"]
commands = [[str(args.bin.resolve() / "ss-server"), *common],
[str(args.bin.resolve() / "ss-local"), *common, "-l", str(local_port)]]
with peers(commands, [server_port, local_port]):
control, relay = socks(local_port, 3, ("0.0.0.0", 0))
with control, socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp:
udp.settimeout(5)
relay = ("127.0.0.1", relay[1])
prefix = b"\0\0\0\x01\x7f\0\0\x01" + struct.pack("!H", origin.server_address[1])
for trial in range(args.repeat):
start = time.perf_counter()
for sequence in range(args.count):
payload = struct.pack("!I", sequence) + bytes(1196)
udp.sendto(prefix + payload, relay)
response, sender = udp.recvfrom(65536)
assert sender[1] == relay[1] and response[10:] == payload
elapsed = time.perf_counter() - start
results.append(dict(cipher=method, trial=trial, packets=args.count,
payload_bytes=1200, seconds=elapsed,
payload_mbps=args.count * 1200 * 8 / elapsed / 1e6))
finally:
origin.shutdown()
args.output.write_text(json.dumps(results, indent=2) + "\n")
if __name__ == "__main__":
main()
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Transparent SIP003 test transport; never used as a production plugin."""
import os
from pathlib import Path
import socket
import socketserver
import threading
mode, marker = os.environ["SS_PLUGIN_OPTIONS"].split(";marker=", 1)
local = (os.environ["SS_LOCAL_HOST"], int(os.environ["SS_LOCAL_PORT"]))
remote = (os.environ["SS_REMOTE_HOST"], int(os.environ["SS_REMOTE_PORT"]))
listen, destination = (remote, local) if mode == "server" else (local, remote)
def copy(source, target):
try:
while data := source.recv(16384):
target.sendall(data)
target.shutdown(socket.SHUT_WR)
except OSError:
pass
class Handler(socketserver.BaseRequestHandler):
def handle(self):
try:
with socket.create_connection(destination, timeout=10) as upstream:
self.request.settimeout(10)
thread = threading.Thread(target=copy, args=(self.request, upstream), daemon=True)
thread.start()
copy(upstream, self.request)
thread.join(timeout=10)
except OSError:
pass
class Server(socketserver.ThreadingTCPServer):
daemon_threads = True
with Server(listen, Handler) as server:
Path(marker).write_text(str(os.getpid()), encoding="ascii")
server.serve_forever()
+10 -19
View File
@@ -1,7 +1,7 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "test_helpers.h"
#include "acl.h"
@@ -10,11 +10,8 @@ int verbose = 0;
static void
test_invalid_regex_is_not_installed(void)
{
char path[] = "/tmp/ss_acl_test.XXXXXX";
int fd = mkstemp(path);
assert(fd >= 0);
FILE *f = fdopen(fd, "w");
char path[4096];
FILE *f = test_tempfile(path, sizeof(path));
assert(f != NULL);
fprintf(f, "[black_list]\n");
fprintf(f, "[invalid\n");
@@ -31,17 +28,14 @@ test_invalid_regex_is_not_installed(void)
assert(outbound_block_match_host("outbound.example") == 1);
free_acl();
unlink(path);
remove(path);
}
static void
test_long_acl_line_is_discarded(void)
{
char path[] = "/tmp/ss_acl_test.XXXXXX";
int fd = mkstemp(path);
assert(fd >= 0);
FILE *f = fdopen(fd, "w");
char path[4096];
FILE *f = test_tempfile(path, sizeof(path));
assert(f != NULL);
fprintf(f, "[black_list]\n");
for (int i = 0; i < 400; i++) {
@@ -55,17 +49,14 @@ test_long_acl_line_is_discarded(void)
assert(acl_match_host("kept.example") == 1);
free_acl();
unlink(path);
remove(path);
}
static void
test_invalid_cidr_is_discarded(void)
{
char path[] = "/tmp/ss_acl_test.XXXXXX";
int fd = mkstemp(path);
assert(fd >= 0);
FILE *f = fdopen(fd, "w");
char path[4096];
FILE *f = test_tempfile(path, sizeof(path));
assert(f != NULL);
fprintf(f, "[black_list]\n");
fprintf(f, "1.2.3.0/999\n");
@@ -83,7 +74,7 @@ test_invalid_cidr_is_discarded(void)
assert(acl_match_host("path/with/slash.example") == 1);
free_acl();
unlink(path);
remove(path);
}
int
+50
View File
@@ -0,0 +1,50 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "acl.h"
#include "rule.h"
int verbose = 0;
int
main(int argc, char **argv)
{
(void)argc;
char path[4096];
assert(snprintf(path, sizeof(path), "%s.acl", argv[0]) < (int)sizeof(path));
FILE *file = fopen(path, "w");
assert(file != NULL);
fputs("[black_list]\nfull:Exact.example\nsuffix:blocked.example\n10.0.0.0/8\n"
"2001:db8::/32\n[white_list]\nsuffix:allowed.example\n"
"[outbound_block_list]\nfull:outbound.example\n", file);
fclose(file);
assert(init_acl(path) == 0);
assert(acl_match_host("EXACT.example") == 1);
assert(acl_match_host("sub.exact.example") == 0);
assert(acl_match_host("blocked.example") == 1);
assert(acl_match_host("a.blocked.example") == 1);
assert(acl_match_host("notblocked.example") == 0);
assert(acl_match_host("blocked.example.evil") == 0);
assert(acl_match_host("a.allowed.example") == -1);
assert(outbound_block_match_host("outbound.example") == 1);
assert(outbound_block_match_host("a.outbound.example") == 0);
assert(acl_match_host("10.42.1.2") == 1);
assert(acl_remove_ip("10.42.1.2") == 0);
assert(acl_match_host("10.42.1.2") == 0);
assert(acl_match_host("10.42.1.3") == 1);
assert(acl_add_ip("10.42.1.2") == 0);
assert(acl_match_host("10.42.1.2") == 1);
assert(acl_remove_ip("2001:db8::42") == 0);
assert(acl_match_host("2001:db8::42") == 0);
assert(acl_match_host("2001:db8::43") == 1);
free_acl();
#if !SS_ENABLE_REGEX
file = fopen(path, "w");
assert(file != NULL);
fputs("[black_list]\n10.0.0.0/8\n^old\\.regex$\n", file);
fclose(file);
assert(init_acl(path) == -1);
assert(acl_match_host("10.1.1.1") == 0);
#endif
remove(path);
return 0;
}
+7
View File
@@ -181,6 +181,7 @@ test_aead_repeat_salt_rejection_releases_context(void)
* the first segment carries the nonce, so a codec that mishandles the
* post-nonce steady state still round-trips a single segment correctly.
*/
#if SS_ENABLE_LEGACY
static void
test_stream_multi_segment_roundtrip(const char *method)
{
@@ -222,6 +223,8 @@ test_stream_multi_segment_roundtrip(const char *method)
ss_free(crypto);
}
#endif
int
main(void)
{
@@ -236,6 +239,7 @@ main(void)
test_crypto_parse_key();
test_aead_repeat_salt_rejection_releases_context();
#if SS_ENABLE_LEGACY
/* mbedTLS-backed ciphers and libsodium-backed ciphers use different
* code paths in stream.c, so cover both. */
test_stream_multi_segment_roundtrip("aes-256-cfb");
@@ -243,5 +247,8 @@ main(void)
test_stream_multi_segment_roundtrip("camellia-128-cfb");
test_stream_multi_segment_roundtrip("chacha20-ietf");
test_stream_multi_segment_roundtrip("salsa20");
#else
assert(crypto_init("password", NULL, "aes-256-cfb") == NULL);
#endif
return 0;
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef SS_TEST_HELPERS_H
#define SS_TEST_HELPERS_H
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "platform.h"
static inline void test_network_cleanup(void)
{
#ifdef _WIN32
WSACleanup();
#endif
}
static inline void test_network_init(void)
{
#ifdef _WIN32
WSADATA data;
assert(WSAStartup(MAKEWORD(2, 2), &data) == 0);
assert(atexit(test_network_cleanup) == 0);
#endif
}
static inline FILE *test_tempfile(char *path, size_t size)
{
#ifdef _WIN32
char directory[MAX_PATH];
DWORD length = GetTempPathA(sizeof(directory), directory);
assert(length != 0 && length < sizeof(directory));
assert(size >= MAX_PATH);
assert(GetTempFileNameA(directory, "ssc", 0, path) != 0);
return fopen(path, "w");
#else
assert(snprintf(path, size, "/tmp/ss-test.XXXXXX") < (int)size);
int fd = mkstemp(path);
assert(fd >= 0);
return fdopen(fd, "w");
#endif
}
#endif
+8 -120
View File
@@ -1,121 +1,9 @@
#!/bin/bash
#
# Interoperability tests against shadowsocks-rust.
#
# The 2022 (SIP022) ciphers are only useful if they interoperate with other
# implementations, and a spec detail can be got wrong in a way that still
# round-trips against ourselves. This drives our binaries against
# shadowsocks-rust's in both directions.
#
# Skips (exit 0) when shadowsocks-rust is not installed, so it is safe to
# run everywhere. Install it with `brew install shadowsocks-rust` or
# `cargo install shadowsocks-rust`.
set -u
BIN_DIR="${SS_BIN_DIR:-build/shared/bin}"
ORIGIN_PORT=${ORIGIN_PORT:-18999}
SERVER_PORT=${SERVER_PORT:-18388}
LOCAL_PORT=${LOCAL_PORT:-18081}
result=0
SPID=""
LPID=""
HPID=""
cleanup() {
[ -n "$SPID" ] && kill -9 "$SPID" 2>/dev/null
[ -n "$LPID" ] && kill -9 "$LPID" 2>/dev/null
[ -n "$HPID" ] && kill -9 "$HPID" 2>/dev/null
wait 2>/dev/null
return 0
}
trap cleanup EXIT
for tool in sslocal ssserver; do
if ! command -v "$tool" > /dev/null 2>&1; then
echo "SKIP: shadowsocks-rust ($tool) not installed"
exit 0
fi
done
for tool in curl python3 openssl; do
if ! command -v "$tool" > /dev/null 2>&1; then
echo "SKIP: $tool not available"
exit 0
fi
done
if [ ! -x "$BIN_DIR/ss-local" ] || [ ! -x "$BIN_DIR/ss-server" ]; then
echo "SKIP: shadowsocks-libev binaries not found in $BIN_DIR"
echo " (set SS_BIN_DIR to override)"
exit 0
#!/bin/sh
# Compatibility entry point. Python uses explicit SOCKS sockets, never curl
# or HTTP proxy environment variables. Required CI treats missing peers as errors.
if ! command -v python3 >/dev/null 2>&1; then
echo 'Missing interoperability prerequisite: python3' >&2
if [ "${SS_REQUIRE_INTEROP:-0}" = 1 ]; then exit 1; fi
exit 77
fi
# A local origin server keeps the test off the network.
WWW_DIR="$(mktemp -d)"
echo "interop-ok" > "$WWW_DIR/index.html"
(cd "$WWW_DIR" && python3 -m http.server "$ORIGIN_PORT" --bind 127.0.0.1) \
> /dev/null 2>&1 &
HPID=$!
sleep 1
# Fetch through the SOCKS5 proxy and check we got the origin's content back.
fetch_through_proxy() {
curl -s --max-time 15 --socks5-hostname "127.0.0.1:$LOCAL_PORT" \
"http://127.0.0.1:$ORIGIN_PORT/" 2>/dev/null | grep -q "interop-ok"
}
run_case() {
local desc="$1" server_cmd="$2" client_cmd="$3"
printf '\e[0;36mrunning: %s\e[0m\n' "$desc"
$server_cmd > /dev/null 2>&1 &
SPID=$!
sleep 1
$client_cmd > /dev/null 2>&1 &
LPID=$!
sleep 2
if fetch_through_proxy; then
printf '\e[0;32mOK: %s\e[0m\n' "$desc"
else
printf '\e[0;31mFAILED: %s\e[0m\n' "$desc"
result=1
fi
kill -9 "$SPID" "$LPID" 2>/dev/null
wait "$SPID" "$LPID" 2>/dev/null
SPID=""
LPID=""
# let the listening sockets go away before the next case binds them
sleep 2
}
test_method() {
local method="$1" key_size="$2"
local psk
psk="$(openssl rand -base64 "$key_size")"
run_case "$method: libev client -> rust server" \
"ssserver -s 127.0.0.1:$SERVER_PORT -k $psk -m $method" \
"$BIN_DIR/ss-local -s 127.0.0.1 -p $SERVER_PORT -l $LOCAL_PORT -k $psk -m $method"
run_case "$method: rust client -> libev server" \
"$BIN_DIR/ss-server -s 127.0.0.1 -p $SERVER_PORT -k $psk -m $method" \
"sslocal -b 127.0.0.1:$LOCAL_PORT -s 127.0.0.1:$SERVER_PORT -k $psk -m $method"
}
# SIP022. The key sizes are fixed by the spec.
test_method 2022-blake3-aes-128-gcm 16
test_method 2022-blake3-aes-256-gcm 32
test_method 2022-blake3-chacha20-poly1305 32
# A legacy AEAD cipher, to catch a regression that only affects the old path.
test_method aes-256-gcm 32
rm -rf "$WWW_DIR"
exit $result
exec python3 "$(dirname "$0")/interop.py" "$@"
+59
View File
@@ -0,0 +1,59 @@
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include "ipset.h"
int verbose = 0;
static uint32_t state = 42;
static uint32_t
next_random(void)
{
state = state * 1664525u + 1013904223u;
return state;
}
static void
check_family(unsigned width)
{
struct ss_ipset set = {0};
uint8_t address[16] = {0};
bool oracle[256] = {false};
unsigned last = width / 8 - 1;
/* Independent exhaustive bitmap oracle over a /24 or /120. Prefix writes
* can cover the whole space; host deletion must punch holes in them. */
for (unsigned round = 0; round < 2000; round++) {
unsigned host = next_random() >> 24;
unsigned bits = next_random() % 9;
bool present = (next_random() >> 31) != 0;
unsigned mask = bits == 0 ? 0 : (255u << (8 - bits)) & 255u;
address[last] = (uint8_t)host;
ss_ipset_assign(&set, address, width, width - 8 + bits, present);
for (unsigned i = 0; i < 256; i++) {
if ((i & mask) == (host & mask)) {
oracle[i] = present;
}
address[last] = (uint8_t)i;
assert(ss_ipset_contains(&set, address, width) == oracle[i]);
}
}
ss_ipset_assign(&set, address, width, 0, true);
memset(address, 255, sizeof(address));
assert(ss_ipset_contains(&set, address, width));
ss_ipset_assign(&set, address, width, width, false);
assert(!ss_ipset_contains(&set, address, width));
address[last]--;
assert(ss_ipset_contains(&set, address, width));
ss_ipset_assign(&set, address, width, width + 1, false);
assert(ss_ipset_contains(&set, address, width));
ss_ipset_clear(&set);
ss_ipset_clear(&set);
assert(!ss_ipset_contains(&set, address, width));
}
int
main(void)
{
check_family(32);
check_family(128);
return 0;
}
+59
View File
@@ -0,0 +1,59 @@
/* Optional migration differential test. Link against the pre-migration ipset
* and cork archives; these are intentionally not production dependencies. */
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <ipset/ipset.h>
#include "ipset.h"
int verbose = 0;
static uint32_t state = 0x725cab91;
static uint32_t next_random(void)
{
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
return state;
}
static void check_family(unsigned width)
{
struct ss_ipset current = {0};
struct ip_set reference;
ipset_init(&reference);
for (unsigned round = 0; round < 2000; round++) {
uint8_t bytes[16];
for (unsigned i = 0; i < sizeof(bytes); i++) bytes[i] = (uint8_t)next_random();
unsigned prefix = next_random() % (width + 1);
bool present = (next_random() & 1) != 0;
ss_ipset_assign(&current, bytes, width, prefix, present);
struct cork_ipv4 v4;
struct cork_ipv6 v6;
memcpy(v4._.u8, bytes, sizeof(v4._.u8));
memcpy(v6._.u8, bytes, sizeof(v6._.u8));
if (width == 32) {
if (present) ipset_ipv4_add_network(&reference, &v4, prefix);
else ipset_ipv4_remove_network(&reference, &v4, prefix);
} else {
if (present) ipset_ipv6_add_network(&reference, &v6, prefix);
else ipset_ipv6_remove_network(&reference, &v6, prefix);
}
for (unsigned sample = 0; sample < 257; sample++) {
if (sample != 0) {
for (unsigned i = 0; i < sizeof(bytes); i++) bytes[i] = (uint8_t)next_random();
}
memcpy(v4._.u8, bytes, sizeof(v4._.u8));
memcpy(v6._.u8, bytes, sizeof(v6._.u8));
bool old = width == 32 ? ipset_contains_ipv4(&reference, &v4) : ipset_contains_ipv6(&reference, &v6);
assert(old == ss_ipset_contains(&current, bytes, width));
}
}
ipset_done(&reference);
ss_ipset_clear(&current);
}
int main(void)
{
ipset_init_library();
check_family(32);
check_family(128);
puts("1,028,000 IPv4/IPv6 membership comparisons matched the original libipset");
return 0;
}
+36 -19
View File
@@ -6,8 +6,10 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "test_helpers.h"
#ifndef _WIN32
#include <sys/wait.h>
#endif
int verbose = 0;
@@ -103,11 +105,8 @@ test_parse_addr_invalid_ipv6_like_host_is_not_rewritten(void)
static void
test_read_jconf_preserves_64bit_integer_strings(void)
{
char path[] = "/tmp/ss_jconf_test.XXXXXX";
int fd = mkstemp(path);
assert(fd >= 0);
FILE *f = fdopen(fd, "w");
char path[4096];
FILE *f = test_tempfile(path, sizeof(path));
assert(f != NULL);
fprintf(f, "{\n");
fprintf(f, "\"server\":\"127.0.0.1\",\n");
@@ -122,7 +121,7 @@ test_read_jconf_preserves_64bit_integer_strings(void)
assert(conf->remote_port != NULL);
assert(strcmp(conf->remote_port, "4294967296") == 0);
unlink(path);
remove(path);
}
static void
@@ -139,19 +138,33 @@ write_minimal_config(FILE *f, const char *extra_line)
fprintf(f, "\n}\n");
}
#ifndef __MINGW32__
static const char *test_executable;
static void
test_read_jconf_rejects_out_of_range_int_options(void)
{
char path[] = "/tmp/ss_jconf_test.XXXXXX";
int fd = mkstemp(path);
assert(fd >= 0);
FILE *f = fdopen(fd, "w");
char path[4096];
FILE *f = test_tempfile(path, sizeof(path));
assert(f != NULL);
write_minimal_config(f, "\"tcp_incoming_sndbuf\":2147483648");
fclose(f);
#ifdef _WIN32
char command[3 * 4096];
assert(snprintf(command, sizeof(command), "\"%s\" --read-config \"%s\"",
test_executable, path) < (int)sizeof(command));
STARTUPINFOA startup = {0};
PROCESS_INFORMATION child = {0};
startup.cb = sizeof(startup);
assert(CreateProcessA(test_executable, command, NULL, NULL, FALSE, 0,
NULL, NULL, &startup, &child));
CloseHandle(child.hThread);
assert(WaitForSingleObject(child.hProcess, 10000) == WAIT_OBJECT_0);
DWORD status;
assert(GetExitCodeProcess(child.hProcess, &status));
CloseHandle(child.hProcess);
/* FATAL exits with -1, which is a valid nonzero DWORD exit status. */
assert(status != 0);
#else
pid_t pid = fork();
assert(pid >= 0);
if (pid == 0) {
@@ -163,14 +176,20 @@ test_read_jconf_rejects_out_of_range_int_options(void)
assert(waitpid(pid, &status, 0) == pid);
assert(WIFEXITED(status));
assert(WEXITSTATUS(status) != 0);
unlink(path);
}
#endif
remove(path);
}
int
main(void)
main(int argc, char **argv)
{
test_executable = argv[0];
test_network_init();
if (argc == 3 && strcmp(argv[1], "--read-config") == 0) {
read_jconf(argv[2]);
return 0;
}
test_parse_addr_ipv4_with_port();
test_parse_addr_ipv6_with_port();
test_parse_addr_hostname_with_port();
@@ -179,8 +198,6 @@ main(void)
test_parse_addr_malformed_bracketed_ipv6_is_not_rewritten();
test_parse_addr_invalid_ipv6_like_host_is_not_rewritten();
test_read_jconf_preserves_64bit_integer_strings();
#ifndef __MINGW32__
test_read_jconf_rejects_out_of_range_int_options();
#endif
return 0;
}
+9
View File
@@ -139,6 +139,15 @@ test_parse_empty_array(void)
int
main(void)
{
const char *wide_numbers = "[4294967296,9223372036854775807,-4294967296,0.4294967296]";
json_value *wide = json_parse(wide_numbers, strlen(wide_numbers));
assert(wide != NULL && wide->type == json_array && wide->u.array.length == 4);
assert(wide->u.array.values[0]->u.integer == INT64_C(4294967296));
assert(wide->u.array.values[1]->u.integer == INT64_MAX);
assert(wide->u.array.values[2]->u.integer == -INT64_C(4294967296));
assert(wide->u.array.values[3]->u.dbl > 0.4294967295 && wide->u.array.values[3]->u.dbl < 0.4294967297);
json_value_free(wide);
assert(json_parse("9223372036854775808", 19) == NULL);
test_parse_simple_object();
test_parse_array();
test_parse_nested();
+2 -3
View File
@@ -5,9 +5,7 @@
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "test_helpers.h"
int verbose = 0;
@@ -129,6 +127,7 @@ test_get_sockaddr_rejects_invalid_ip_literal_ports(void)
int
main(void)
{
test_network_init();
test_get_sockaddr_len();
test_sockaddr_cmp();
test_sockaddr_cmp_addr();
+85
View File
@@ -0,0 +1,85 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ss_process.h"
#ifdef _WIN32
#include <windows.h>
static void pause_tick(void) { Sleep(10); }
#else
#include <time.h>
static void pause_tick(void) { struct timespec delay = {0, 10000000}; nanosleep(&delay, NULL); }
#endif
int verbose = 0;
int
main(int argc, char **argv)
{
if (argc > 1 && strcmp(argv[1], "--child") == 0) {
assert(argc == 5);
FILE *file = fopen(argv[2], "w");
assert(file != NULL);
assert(strcmp(argv[3], "spaces and \"quotes\" with \\") == 0);
assert(strcmp(argv[4], "") == 0);
assert(strcmp(getenv("SS_PROCESS_TEST"), "child value") == 0);
fputs("ready\n", file);
fclose(file);
for (unsigned i = 0; i < 1000; i++) pause_tick();
return 0;
}
if (argc > 1 && strcmp(argv[1], "--exit") == 0) return 17;
struct ss_process *process = ss_process_new("ss-no-such-program-ef91ac");
ss_process_arg(process, "ss-no-such-program-ef91ac");
int started = ss_process_start(process, 0);
assert(started == -1 || started == 0);
/* POSIX permits exec failure to be reported by child exit (notably under
* Valgrind's spawn wrapper), rather than synchronously by posix_spawnp. */
if (started == 0) {
for (unsigned i = 0; i < 500 && ss_process_running(process); i++) pause_tick();
assert(!ss_process_running(process));
}
ss_process_free(process);
char path[4096];
assert(snprintf(path, sizeof(path), "%s.child-output", argv[0]) < (int)sizeof(path));
remove(path);
process = ss_process_new(argv[0]);
ss_process_arg(process, argv[0]);
ss_process_arg(process, "--child");
ss_process_arg(process, path);
ss_process_arg(process, "spaces and \"quotes\" with \\");
ss_process_arg(process, "");
ss_process_env(process, "SS_PROCESS_TEST", "child value");
const char *before = getenv("SS_PROCESS_TEST");
char *saved = before == NULL ? NULL : strdup(before);
assert(ss_process_start(process, 0) == 0);
bool ready = false;
for (unsigned i = 0; i < 500; i++) {
FILE *file = fopen(path, "r");
if (file != NULL) {
char line[16];
ready = fgets(line, sizeof(line), file) != NULL && strcmp(line, "ready\n") == 0;
fclose(file);
}
if (ready) break;
pause_tick();
}
assert(ready);
assert(ss_process_running(process));
const char *after = getenv("SS_PROCESS_TEST");
assert(saved == NULL ? after == NULL : after != NULL && strcmp(after, saved) == 0);
free(saved);
ss_process_free(process);
remove(path);
process = ss_process_new(argv[0]);
ss_process_arg(process, argv[0]);
ss_process_arg(process, "--exit");
assert(ss_process_start(process, 0) == 0);
for (unsigned i = 0; i < 500 && ss_process_running(process); i++) pause_tick();
assert(!ss_process_running(process));
ss_process_free(process);
ss_process_free(NULL);
return 0;
}
Loaded 100 of 120 files, more files were not shown because too many files have changed in this diff. Show more