Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,99 @@ jobs:
test -f stage/lib/libnetcode.so || test -L stage/lib/libnetcode.so
echo "install layout ok"

# the installed library is what a package manager ships, so CI links a program against it
# from a clean prefix: the default vendored build carries its own crypto into libnetcode,
# and the system libsodium build exports the libsodium it found.
consumer:
name: consumer (${{ matrix.name }})
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- name: vendored sodium
options: ""
system_sodium: false
- name: system sodium
options: "-DNETCODE_SYSTEM_SODIUM=ON"
system_sodium: true
steps:
- uses: actions/checkout@v4

- name: Install libsodium
if: matrix.system_sodium
run: sudo apt-get update && sudo apt-get install -y libsodium-dev

- name: Configure
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DNETCODE_BUILD_TESTS=OFF ${{ matrix.options }}

- name: Build
run: cmake --build build --parallel

- name: Install to a clean prefix
run: cmake --install build --prefix "${{ github.workspace }}/prefix"

- name: Build and run the consumer
run: |
cmake -S tools/consumer -B consumer-build -DCMAKE_PREFIX_PATH="${{ github.workspace }}/prefix"
cmake --build consumer-build --parallel
./consumer-build/consumer

# the only big endian coverage in the matrix: the address conversions read and write
# network byte order for real here, where host order is not a synonym for it
big-endian:
name: big endian s390x / ${{ matrix.config }}
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
config: [ Debug, Release ]
steps:
- uses: actions/checkout@v4

- name: Install s390x cross toolchain and qemu
run: |
sudo apt-get update
sudo apt-get install -y gcc-s390x-linux-gnu g++-s390x-linux-gnu qemu-user-static

# static linking so the emulated binaries need no s390x sysroot at runtime
- name: Configure
run: >
cmake -B build
-DCMAKE_BUILD_TYPE=${{ matrix.config }}
-DCMAKE_SYSTEM_NAME=Linux
-DCMAKE_SYSTEM_PROCESSOR=s390x
-DCMAKE_C_COMPILER=s390x-linux-gnu-gcc
-DCMAKE_CXX_COMPILER=s390x-linux-gnu-g++
-DCMAKE_C_FLAGS="-static"
-DCMAKE_CXX_FLAGS="-static"
-DCMAKE_CROSSCOMPILING_EMULATOR=qemu-s390x-static

- name: Build
run: cmake --build build --parallel

- name: Test
run: ctest --test-dir build --output-on-failure --timeout 600

# every packet the suite encrypts is recorded by key and nonce, and a repeat fails the run
nonce-audit:
name: nonce audit
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- name: Configure
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DNETCODE_NONCE_AUDIT=ON

- name: Build
run: cmake --build build --parallel

- name: Test
run: ctest --test-dir build --output-on-failure --timeout 600

sanitizers:
name: sanitizers (asan+ubsan)
runs-on: ubuntu-24.04
Expand Down
13 changes: 11 additions & 2 deletions BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@ By default netcode builds as a static library against the vendored libsodium sub
cmake --install build --prefix /some/prefix

- `NETCODE_SYSTEM_SODIUM=ON` links the system libsodium instead of the vendored copy (they are interchangeable — the vendored subset is a byte-identical slice of upstream).
- `BUILD_SHARED_LIBS=ON` builds `libnetcode` as a shared library.
- `cmake --install` installs `netcode.h` and the library (`NETCODE_INSTALL=OFF` disables the install target, e.g. when embedding netcode as a subproject).
- `BUILD_SHARED_LIBS=ON` builds `libnetcode` as a shared library. Shared builds are not supported on Windows: `netcode.h` declares no export macro, so a DLL built from these sources exports nothing, and CMake refuses the combination. On Windows link the static library or compile `netcode.c` into your application.
- `cmake --install` installs `netcode.h`, the library, and a CMake package. In the default vendored build the sodium objects are compiled into `libnetcode`, so the installed library is self contained; the system libsodium build exports the libsodium it found instead.

A consumer picks the installed library up with:

find_package(netcode CONFIG REQUIRED)
target_link_libraries(your_app PRIVATE netcode::netcode)

pointing CMake at the install prefix with `-DCMAKE_PREFIX_PATH=/some/prefix`. `NETCODE_INSTALL=OFF` disables the install target, e.g. when embedding netcode as a subproject.

## Floating point: netcode builds with -ffp-contract=off

Expand Down Expand Up @@ -65,6 +72,8 @@ To build everything with AddressSanitizer and UndefinedBehaviorSanitizer, config

Fuzz harnesses for the untrusted-input surface live in `fuzz/` and are built with `-DNETCODE_FUZZ=ON`. See [fuzz/README.md](fuzz/README.md) for details.

`-DNETCODE_NONCE_AUDIT=ON` builds the test runner with the key and nonce of every packet it encrypts recorded, and adds a test that fails if any pair repeats. It is a test-only option: nothing it adds is compiled into the library.

## Building on Windows

You need Visual Studio to build the source code. If you don't have Visual Studio you can [download the community edition for free](https://visualstudio.microsoft.com/downloads/).
Expand Down
27 changes: 21 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ DECISIONS THAT READ AS BUGS (they are not — do not "fix" them)
BOTH start and stop. Nonce-space separation: pre-connection and per-client packets share
a key, so their nonces must not collide. Seeding on create only was the AEAD nonce-reuse
bug fixed in 1.4.0. Keep every seeding site.
- **A connect token is single use.** Its history entry is created pending on the first
connection request, admits retransmitted requests from that same address while the
handshake runs, and becomes consumed when the client is installed in a slot. A consumed
entry admits nothing, whatever the address, so the keys inside a connect token encrypt
exactly one session. Entries live until their token expires and a full history refuses new
tokens rather than evicting one. The server also refuses any connect token that could have
been issued before it started, using `max_connect_token_lifetime` in the server config.
STANDARD.md's *Connect Token History* and *Nonce Reuse and Server Restarts* are the contract.
- **Replay protection advances the window only AFTER authentication** (netcode.c:1863,
1907). The cheap pre-decrypt reject is an optimisation; moving the window advance before
auth would let spoofed plaintext sequence numbers poison it.
Expand Down Expand Up @@ -90,16 +98,23 @@ independent implementations (C#, Go, Rust, TypeScript).
- `sodium/` — vendored subset of libsodium, amalgamated into a single `sodium.h` +
`sodium.c` pair (see `sodium/NOTES.md` for how it is generated and validated).
- Build: CMake. `cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel`,
then `ctest --test-dir build --output-on-failure` runs the suite (42 tests). The
then `ctest --test-dir build --output-on-failure` runs the suite (51 tests). The
`netcode_test` target compiles netcode.c into itself with `NETCODE_ENABLE_TESTS`, so it
links only sodium. `-DNETCODE_SANITIZE=ON` adds ASan+UBSan (sodium gets ASan only);
`-DNETCODE_FUZZ=ON` builds the `fuzz/` harnesses (libFuzzer where available, else a
standalone file replayer). For packaging: `-DNETCODE_SYSTEM_SODIUM=ON` links the
system libsodium instead of the vendored copy, `-DBUILD_SHARED_LIBS=ON` builds
libnetcode shared, and `cmake --install` installs netcode.h + the library
(this is the homebrew configuration, covered by a CI leg). CI (`.github/workflows/ci.yml`) builds and tests Debug +
standalone file replayer); `-DNETCODE_NONCE_AUDIT=ON` records the key and nonce of every
packet the tests encrypt and fails the run on a repeat (test-only, nothing enters the
library). For packaging: `-DNETCODE_SYSTEM_SODIUM=ON` links the system libsodium instead
of the vendored copy, `-DBUILD_SHARED_LIBS=ON` builds libnetcode shared everywhere except
Windows, where CMake refuses it because netcode.h declares no export macro, and
`cmake --install` installs netcode.h, the library and a CMake package consumers find with
`find_package(netcode CONFIG)` and link as `netcode::netcode` (the vendored build folds the
sodium objects into libnetcode, so the installed static library is self contained; the
system-sodium build, the homebrew configuration, exports its libsodium instead).
CI (`.github/workflows/ci.yml`) builds and tests Debug +
Release on Linux x64, Linux arm64, macOS Apple Silicon, and Windows x64 (MSVC + a
MinGW leg), plus a Linux ASan+UBSan leg and a bounded smoke-fuzz leg. A separate
MinGW leg), plus a Linux ASan+UBSan leg, a bounded smoke-fuzz leg, a big endian s390x leg
under QEMU, a clean-prefix consumer leg for both install shapes, and the nonce audit leg. A separate
nightly workflow (`.github/workflows/scheduled.yml`) runs deep fuzzing with an
accumulating cached corpus, a 15-minute ASan soak, and a libsodium-upstream-release
check that opens a tracking issue when the vendored version falls behind.
Expand Down
68 changes: 58 additions & 10 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(CMAKE_TARGET_MESSAGES OFF)
endif()

project(netcode VERSION 1.4.4 LANGUAGES C CXX)
project(netcode VERSION 1.4.5 LANGUAGES C CXX)

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
Expand Down Expand Up @@ -69,6 +69,17 @@ option(NETCODE_FUZZ "Build the fuzz targets" OFF)
option(NETCODE_BUILD_TESTS "Build netcode tests and examples" ${NETCODE_TOP_LEVEL})
option(NETCODE_SYSTEM_SODIUM "Link against the system libsodium instead of the vendored copy" OFF)
option(NETCODE_INSTALL "Generate the install target (netcode.h and the netcode library)" ON)
option(NETCODE_NONCE_AUDIT "Record the key and nonce of every packet the tests encrypt and fail on a repeat" OFF)

# netcode.h declares no export macro, so a Windows DLL built from these sources exports
# nothing and every consumer fails to link. Static is the supported shape on Windows: link
# the static library, or compile netcode.c straight into your application.

if(BUILD_SHARED_LIBS AND WIN32)
message(FATAL_ERROR "netcode does not support shared library builds on Windows. Build the static library (the default), or compile netcode.c into your application.")
endif()

include(GNUInstallDirs)

# sanitizers apply to the whole build. the vendored crypto is exempted from UBSan
# below (third-party SIMD code uses intentional type punning / unaligned access that
Expand Down Expand Up @@ -102,7 +113,7 @@ else()
# vendored libsodium subset, amalgamated into a single header + source pair.
# see sodium/NOTES.md for how it is generated and validated.

add_library(sodium STATIC sodium/sodium.c sodium/sodium.h)
add_library(sodium OBJECT sodium/sodium.c sodium/sodium.h)
target_include_directories(sodium PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/sodium)
set_target_properties(sodium PROPERTIES POSITION_INDEPENDENT_CODE ON)
if(NOT MSVC)
Expand All @@ -120,13 +131,28 @@ else()

endif()

# the netcode library. static by default; -DBUILD_SHARED_LIBS=ON builds it shared
# (the vendored sodium objects are position independent, so they fold in either way)
# the netcode library. static by default; -DBUILD_SHARED_LIBS=ON builds it shared on the
# platforms that support it (the vendored sodium objects are position independent, so they
# fold in either way).
#
# in the default vendored build the sodium objects are compiled into libnetcode itself, so
# the installed library is self contained: a consumer links netcode and nothing else. the
# system-sodium build exports the libsodium it found instead, because that libsodium is the
# package manager's to supply and update.

add_library(netcode netcode.c netcode.h)
target_include_directories(netcode PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
if(NETCODE_SYSTEM_SODIUM)
add_library(netcode netcode.c netcode.h)
target_include_directories(netcode PRIVATE ${NETCODE_SODIUM_INCLUDE_DIR})
target_link_libraries(netcode PUBLIC ${NETCODE_SODIUM_LIBRARY})
else()
add_library(netcode netcode.c netcode.h $<TARGET_OBJECTS:sodium>)
target_include_directories(netcode PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/sodium)
endif()

target_include_directories(netcode PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_compile_options(netcode PRIVATE ${NETCODE_WARNINGS})
target_link_libraries(netcode PUBLIC sodium)
set_target_properties(netcode PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR})
Expand All @@ -135,12 +161,31 @@ if(WIN32)
endif()

if(NETCODE_INSTALL)
include(GNUInstallDirs)
install(TARGETS netcode
include(CMakePackageConfigHelpers)

install(TARGETS netcode EXPORT netcodeTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES netcode.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

install(EXPORT netcodeTargets
FILE netcodeTargets.cmake
NAMESPACE netcode::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/netcode)

configure_package_config_file(cmake/netcodeConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/netcodeConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/netcode)

write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/netcodeConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/netcodeConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/netcodeConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/netcode)
endif()

if(NETCODE_BUILD_TESTS)
Expand All @@ -166,6 +211,9 @@ if(NETCODE_BUILD_TESTS)
if(WIN32)
target_link_libraries(netcode_test PRIVATE ws2_32 iphlpapi)
endif()
if(NETCODE_NONCE_AUDIT)
target_compile_definitions(netcode_test PRIVATE NETCODE_ENABLE_NONCE_AUDIT=1)
endif()

add_test(NAME netcode_test COMMAND netcode_test)

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ char * server_address = "127.0.0.1:40000";

struct netcode_server_config_t server_config;
netcode_default_server_config( &server_config );
server_config.max_connect_token_lifetime = 30;
memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES );

struct netcode_server_t * server = netcode_server_create( server_address, &server_config, time );
Expand All @@ -56,6 +57,8 @@ if ( !server )
}
```

`max_connect_token_lifetime` is the longest lifetime in seconds your backend issues connect tokens with. The server refuses any connect token that could have been issued before it started, and this is how it knows which those are, so set it to the lifetime your backend uses.

Then start the server with the number of client slots you want:

```c
Expand Down
1 change: 1 addition & 0 deletions client_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ int main( int argc, char ** argv )
struct netcode_server_config_t server_config;
netcode_default_server_config( &server_config );
server_config.protocol_id = PROTOCOL_ID;
server_config.max_connect_token_lifetime = CONNECT_TOKEN_EXPIRY;
memcpy( &server_config.private_key, private_key, NETCODE_KEY_BYTES );

char * server_address = "[::1]:40000";
Expand Down
5 changes: 5 additions & 0 deletions cmake/netcodeConfig.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/netcodeTargets.cmake")

check_required_components(netcode)
1 change: 1 addition & 0 deletions fuzz/fuzz_read_packet.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ static void * fuzz_call_read_packet( uint8_t * buffer, int buffer_length, uint64
packet_key,
FUZZ_PROTOCOL_ID,
0, // current timestamp: zero so fuzz-chosen expire timestamps pass
0, // minimum expire timestamp: zero so no connect token is refused for predating the server start
private_key,
allowed_packets,
replay_protection,
Expand Down
Loading
Loading