mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-28 10:44:43 +08:00
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:
1 parent
8fe386b5c2
commit
201e70c4bd
120 files changed
+4630
-1522
No files matched your search
+1
-1
@@ -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$'
|
||||
#
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
@@ -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
@@ -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})
|
||||
@@ -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})
|
||||
@@ -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*")
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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@
|
||||
@@ -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)
|
||||
Vendored
+6
@@ -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
@@ -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/
|
||||
Vendored
+1
-1
@@ -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
|
||||
|
||||
Vendored
+1
@@ -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
@@ -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)
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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"]
|
||||
@@ -0,0 +1,8 @@
|
||||
.git
|
||||
build*
|
||||
release
|
||||
libcork
|
||||
libipset
|
||||
libbloom
|
||||
**/__pycache__
|
||||
**/.DS_Store
|
||||
@@ -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)"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
-1
Submodule libbloom deleted from 437e1add5a.
-1
Submodule libcork deleted from 5ed3f83717.
-1
Submodule libipset deleted from 3ea7fe30ad.
+11
-41
@@ -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"
|
||||
Executable
+54
@@ -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
@@ -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
|
||||
)
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -30,7 +30,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#include "winsock.h"
|
||||
#include "ss_windows.h"
|
||||
#endif
|
||||
|
||||
#include "crypto.h"
|
||||
|
||||
+75
-73
@@ -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
@@ -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
@@ -22,7 +22,7 @@
|
||||
|
||||
#ifdef __MINGW32__
|
||||
|
||||
#include "winsock.h"
|
||||
#include "ss_windows.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifndef ENABLE_QUICK_EDIT
|
||||
|
||||
+72
-67
@@ -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.
|
||||
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}")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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; }
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
Executable
+43
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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" "$@"
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(¤t, 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(¤t, bytes, width));
|
||||
}
|
||||
}
|
||||
ipset_done(&reference);
|
||||
ss_ipset_clear(¤t);
|
||||
}
|
||||
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
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
Reference in new issue
Block a user