From dd3b0aeb9a0d698d874f8a591078e010a421f829 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 2 Oct 2026 09:40:41 +1000 Subject: [PATCH 01/12] Switch dependency handling to session-deps Replace the libsodium-internal, zstd and simdutf submodules and cmake/StaticBuild.cmake with cmake/session-deps, and bump session-router to 2b91e9ab4 (libquic c0b9cf6). xed25519::pubkey() used libsodium-internal's fe25519 internals; it now uses a TweetNaCl subset (src/xed25519-tweetnacl.cpp). session-deps builds nettle 4, so digest calls go through nettle-compat.hpp. zstd moves 1.5.2 -> 1.5.7, which changes compressed output; test vectors updated. USE_LTO becomes the single LTO switch for session-deps and session-router. --- .gitmodules | 12 +- CMakeLists.txt | 47 ++++-- cmake/StaticBuild.cmake | 224 --------------------------- cmake/session-deps | 1 + external/CMakeLists.txt | 259 ++++++++------------------------ external/libsodium-internal | 1 - external/session-router | 2 +- external/simdutf | 1 - external/zstd | 1 - src/CMakeLists.txt | 28 ++-- src/nettle-compat.hpp | 21 +++ src/onionreq/hop_encryption.cpp | 5 +- src/xed25519-tweetnacl.cpp | 150 ++++++++++++++++++ src/xed25519.cpp | 22 +-- tests/CMakeLists.txt | 2 +- tests/test_compression.cpp | 10 +- 16 files changed, 293 insertions(+), 493 deletions(-) delete mode 100644 cmake/StaticBuild.cmake create mode 160000 cmake/session-deps delete mode 160000 external/libsodium-internal delete mode 160000 external/simdutf delete mode 160000 external/zstd create mode 100644 src/nettle-compat.hpp create mode 100644 src/xed25519-tweetnacl.cpp diff --git a/.gitmodules b/.gitmodules index a029c2087..8d6f7cfea 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,24 +1,18 @@ -[submodule "external/libsodium-internal"] - path = external/libsodium-internal - url = https://github.com/session-foundation/libsodium-internal.git [submodule "tests/Catch2"] path = tests/Catch2 url = https://github.com/catchorg/Catch2 [submodule "external/ios-cmake"] path = external/ios-cmake url = https://github.com/leetal/ios-cmake -[submodule "external/zstd"] - path = external/zstd - url = https://github.com/facebook/zstd.git [submodule "external/protobuf"] path = external/protobuf url = https://github.com/protocolbuffers/protobuf.git [submodule "external/session-router"] path = external/session-router url = https://github.com/session-foundation/session-router.git -[submodule "external/simdutf"] - path = external/simdutf - url = https://github.com/simdutf/simdutf.git [submodule "external/date"] path = external/date url = https://github.com/HowardHinnant/date.git +[submodule "cmake/session-deps"] + path = cmake/session-deps + url = https://github.com/session-foundation/session-deps.git diff --git a/CMakeLists.txt b/CMakeLists.txt index fee48f551..9ea88526e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,12 @@ else() set(static_default ON) endif() -option(BUILD_STATIC_DEPS "Build all dependencies statically rather than trying to link to them on the system" ${static_default}) +# session-deps declares BUILD_STATIC_DEPS itself, defaulting to OFF; setting the cache entry first +# keeps the historical "static unless building a shared library" default. +set(BUILD_STATIC_DEPS ${static_default} CACHE BOOL "Build all dependencies statically rather than trying to link to them on the system") + +include(cmake/session-deps/Deps.cmake) + option(STATIC_BUNDLE "Build a single static .a containing everything (both code and dependencies)" ${BUILD_STATIC_DEPS}) if(STATIC_BUNDLE AND NOT BUILD_STATIC_DEPS) @@ -107,6 +112,22 @@ if(IPO_ENABLED AND NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) endif() +# USE_LTO is the single LTO knob. Two submodule-provided options default ON independently of it: +# SESSIONDEPS_LTO (session-deps) controls LTO for static dependency builds, and WITH_LTO +# (session-router's enable_lto.cmake) controls LTO for session-router's own targets. Leaving either +# ON in a non-LTO build breaks the link: LTO'd static archives can't be consumed by a non-LTO link +# (clang emits pure bitcode archives that a plain bfd link reports as "file format not recognized", +# or silently drops the members), and session-router's LTO probe fails outright on iOS, where the +# build script configures with the macOS SDK. Seed both before any submodule evaluates its own +# option() so there is exactly one LTO switch. +set(SESSIONDEPS_LTO ${USE_LTO} CACHE BOOL "Use LTO for static dependency builds, where supported" FORCE) +set(WITH_LTO ${USE_LTO} CACHE BOOL "enable lto on compile time" FORCE) + +# The same for plain subdirectories: one whose cmake_minimum_required predates 3.9 (protobuf's is +# 3.5) has CMP0069 OLD, which makes CMake ignore CMAKE_INTERPROCEDURAL_OPTIMIZATION and build it +# without LTO, so a link that pulls such an archive's members in late fails on inline std:: functions. +set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) + if(STATIC_LIBSTD) add_link_options(-static-libstdc++) if(NOT CMAKE_CXX_COMPILER_ID MATCHES Clang) @@ -123,29 +144,23 @@ include(AddStaticBundleLib) set(CMAKE_POSITION_INDEPENDENT_CODE ON) +# For a static-deps build we build everything from source; don't let oxen-logging pick up system +# fmt/spdlog via find_package. Beyond the usual reasons to prefer our own versions, a system +# fmt/spdlog is built against libstdc++ and cannot be linked into a libc++ build (undefined +# std::__1 symbols), so force the bundled submodules which build with our toolchain. +if(BUILD_STATIC_DEPS) + set(OXEN_LOGGING_FORCE_SUBMODULES ON CACHE INTERNAL "") +endif() + add_subdirectory(external) if(ENABLE_NETWORKING) - if(NOT TARGET nettle::nettle) - if(BUILD_STATIC_DEPS) - message(FATAL_ERROR "Internal error: nettle::nettle target (expected via libquic BUILD_STATIC_DEPS) not found") - else() - find_package(PkgConfig REQUIRED) - pkg_check_modules(NETTLE REQUIRED IMPORTED_TARGET nettle) - add_library(nettle INTERFACE) - target_link_libraries(nettle INTERFACE PkgConfig::NETTLE) - add_library(nettle::nettle ALIAS nettle) - endif() - endif() + session_dep(nettle 3) endif() add_subdirectory(src) add_subdirectory(proto) -if (BUILD_STATIC_DEPS) - include(StaticBuild) -endif() - if(STATIC_BUNDLE) include(combine_archives) diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake deleted file mode 100644 index 49863f8c8..000000000 --- a/cmake/StaticBuild.cmake +++ /dev/null @@ -1,224 +0,0 @@ -# cmake bits to do a full static build, downloading and building all dependencies. - -# Most of these are CACHE STRINGs so that you can override them using -DWHATEVER during cmake -# invocation to override. - -set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads") - -include(ExternalProject) - -set(DEPS_DESTDIR ${CMAKE_BINARY_DIR}/static-deps) -set(DEPS_SOURCEDIR ${CMAKE_BINARY_DIR}/static-deps-sources) - -file(MAKE_DIRECTORY ${DEPS_DESTDIR}/include) - -add_library(libsession-external-libs INTERFACE IMPORTED GLOBAL) -target_include_directories(libsession-external-libs SYSTEM BEFORE INTERFACE ${DEPS_DESTDIR}/include) - -set(deps_cc "${CMAKE_C_COMPILER}") -set(deps_cxx "${CMAKE_CXX_COMPILER}") - - -function(expand_urls output source_file) - set(expanded) - foreach(mirror ${ARGN}) - list(APPEND expanded "${mirror}/${source_file}") - endforeach() - set(${output} "${expanded}" PARENT_SCOPE) -endfunction() - -function(add_static_target target ext_target libname) - add_library(${target} STATIC IMPORTED GLOBAL) - add_dependencies(${target} ${ext_target}) - target_link_libraries(${target} INTERFACE libsession-external-libs) - set_target_properties(${target} PROPERTIES - IMPORTED_LOCATION ${DEPS_DESTDIR}/lib/${libname} - ) - if(ARGN) - target_link_libraries(${target} INTERFACE ${ARGN}) - endif() - libsession_static_bundle(${target}) -endfunction() - - - -set(cross_host "") -set(cross_rc "") -if(CMAKE_CROSSCOMPILING) - if(APPLE AND NOT ARCH_TRIPLET AND APPLE_TARGET_TRIPLE) - set(ARCH_TRIPLET "${APPLE_TARGET_TRIPLE}") - endif() - set(cross_host "--host=${ARCH_TRIPLET}") - if (ARCH_TRIPLET MATCHES mingw AND CMAKE_RC_COMPILER) - set(cross_rc "WINDRES=${CMAKE_RC_COMPILER}") - endif() -endif() -if(ANDROID) - set(android_toolchain_suffix linux-android) - set(android_compiler_suffix linux-android${ANDROID_PLATFORM_LEVEL}) - if(CMAKE_ANDROID_ARCH_ABI MATCHES x86_64) - set(cross_host "--host=x86_64-linux-android") - set(android_compiler_prefix x86_64) - set(android_compiler_suffix linux-android${ANDROID_PLATFORM_LEVEL}) - set(android_toolchain_prefix x86_64) - set(android_toolchain_suffix linux-android) - elseif(CMAKE_ANDROID_ARCH_ABI MATCHES x86) - set(cross_host "--host=i686-linux-android") - set(android_compiler_prefix i686) - set(android_compiler_suffix linux-android${ANDROID_PLATFORM_LEVEL}) - set(android_toolchain_prefix i686) - set(android_toolchain_suffix linux-android) - elseif(CMAKE_ANDROID_ARCH_ABI MATCHES armeabi-v7a) - set(cross_host "--host=armv7a-linux-androideabi") - set(android_compiler_prefix armv7a) - set(android_compiler_suffix linux-androideabi${ANDROID_PLATFORM_LEVEL}) - set(android_toolchain_prefix arm) - set(android_toolchain_suffix linux-androideabi) - elseif(CMAKE_ANDROID_ARCH_ABI MATCHES arm64-v8a) - set(cross_host "--host=aarch64-linux-android") - set(android_compiler_prefix aarch64) - set(android_compiler_suffix linux-android${ANDROID_PLATFORM_LEVEL}) - set(android_toolchain_prefix aarch64) - set(android_toolchain_suffix linux-android) - else() - message(FATAL_ERROR "unknown android arch: ${CMAKE_ANDROID_ARCH_ABI}") - endif() - set(deps_cc "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_compiler_prefix}-${android_compiler_suffix}-clang") - set(deps_cxx "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_compiler_prefix}-${android_compiler_suffix}-clang++") - set(deps_ld "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_compiler_prefix}-${android_toolchain_suffix}-ld") - set(deps_ranlib "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_toolchain_prefix}-${android_toolchain_suffix}-ranlib") - set(deps_ar "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_toolchain_prefix}-${android_toolchain_suffix}-ar") -endif() - -set(deps_CFLAGS "-O2") -set(deps_CXXFLAGS "-O2") - -if(CMAKE_C_COMPILER_LAUNCHER) - set(deps_cc "${CMAKE_C_COMPILER_LAUNCHER} ${deps_cc}") -endif() -if(CMAKE_CXX_COMPILER_LAUNCHER) - set(deps_cxx "${CMAKE_CXX_COMPILER_LAUNCHER} ${deps_cxx}") -endif() - -if(WITH_LTO) - set(deps_CFLAGS "${deps_CFLAGS} -flto") -endif() - -if(APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET) - if(SDK_NAME) - set(deps_CFLAGS "${deps_CFLAGS} -m${SDK_NAME}-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") - set(deps_CXXFLAGS "${deps_CXXFLAGS} -m${SDK_NAME}-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") - else() - set(deps_CFLAGS "${deps_CFLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") - set(deps_CXXFLAGS "${deps_CXXFLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") - endif() -endif() - -if(_winver) - set(deps_CFLAGS "${deps_CFLAGS} -D_WIN32_WINNT=${_winver}") - set(deps_CXXFLAGS "${deps_CXXFLAGS} -D_WIN32_WINNT=${_winver}") -endif() - - -if("${CMAKE_GENERATOR}" STREQUAL "Unix Makefiles") - set(_make $(MAKE)) -else() - set(_make make) -endif() - - -# Builds a target; takes the target name (e.g. "readline") and builds it in an external project with -# target name suffixed with `_external`. Its upper-case value is used to get the download details -# (from the variables set above). The following options are supported and passed through to -# ExternalProject_Add if specified. If omitted, these defaults are used: -set(build_def_DEPENDS "") -set(build_def_PATCH_COMMAND "") -set(build_def_CONFIGURE_COMMAND ./configure ${cross_host} --disable-shared --prefix=${DEPS_DESTDIR} --with-pic - "CC=${deps_cc}" "CXX=${deps_cxx}" "CFLAGS=${deps_CFLAGS}" "CXXFLAGS=${deps_CXXFLAGS}" ${cross_rc}) -set(build_def_CONFIGURE_EXTRA "") -set(build_def_BUILD_COMMAND ${_make}) -set(build_def_INSTALL_COMMAND ${_make} install) -set(build_def_BUILD_BYPRODUCTS ${DEPS_DESTDIR}/lib/lib___TARGET___.a ${DEPS_DESTDIR}/include/___TARGET___.h) - -function(build_external target) - set(options DEPENDS PATCH_COMMAND CONFIGURE_COMMAND CONFIGURE_EXTRA BUILD_COMMAND INSTALL_COMMAND BUILD_BYPRODUCTS) - cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "${options}") - foreach(o ${options}) - if(NOT DEFINED arg_${o}) - set(arg_${o} ${build_def_${o}}) - endif() - endforeach() - string(REPLACE ___TARGET___ ${target} arg_BUILD_BYPRODUCTS "${arg_BUILD_BYPRODUCTS}") - - string(TOUPPER "${target}" prefix) - expand_urls(urls ${${prefix}_SOURCE} ${${prefix}_MIRROR}) - set(extract_ts) - if(NOT CMAKE_VERSION VERSION_LESS 3.24) - set(extract_ts DOWNLOAD_EXTRACT_TIMESTAMP ON) - endif() - ExternalProject_Add("${target}_external" - DEPENDS ${arg_DEPENDS} - BUILD_IN_SOURCE ON - PREFIX ${DEPS_SOURCEDIR} - URL ${urls} - URL_HASH ${${prefix}_HASH} - DOWNLOAD_NO_PROGRESS ON - PATCH_COMMAND ${arg_PATCH_COMMAND} - CONFIGURE_COMMAND ${arg_CONFIGURE_COMMAND} ${arg_CONFIGURE_EXTRA} - BUILD_COMMAND ${arg_BUILD_COMMAND} - INSTALL_COMMAND ${arg_INSTALL_COMMAND} - BUILD_BYPRODUCTS ${arg_BUILD_BYPRODUCTS} - EXCLUDE_FROM_ALL ON - ${extract_ts} - ) -endfunction() - - -set(apple_cflags_arch) -set(apple_cxxflags_arch) -set(apple_ldflags_arch) -set(gmp_build_host "${cross_host}") -if(APPLE AND CMAKE_CROSSCOMPILING) - if(gmp_build_host MATCHES "^(.*-.*-)ios([0-9.]+)(-.*)?$") - set(gmp_build_host "${CMAKE_MATCH_1}darwin${CMAKE_MATCH_2}${CMAKE_MATCH_3}") - endif() - if(gmp_build_host MATCHES "^(.*-.*-.*)-simulator$") - set(gmp_build_host "${CMAKE_MATCH_1}") - endif() - - set(apple_arch) - if(ARCH_TRIPLET MATCHES "^(arm|aarch)64.*") - set(apple_arch "arm64") - elseif(ARCH_TRIPLET MATCHES "^x86_64.*") - set(apple_arch "x86_64") - else() - message(FATAL_ERROR "Don't know how to specify -arch for GMP for ${ARCH_TRIPLET} (${APPLE_TARGET_TRIPLE})") - endif() - - set(apple_cflags_arch " -arch ${apple_arch}") - set(apple_cxxflags_arch " -arch ${apple_arch}") - if(CMAKE_OSX_DEPLOYMENT_TARGET) - if (SDK_NAME) - set(apple_ldflags_arch " -m${SDK_NAME}-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") - elseif(CMAKE_OSX_DEPLOYMENT_TARGET) - set(apple_ldflags_arch " -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") - endif() - endif() - set(apple_ldflags_arch "${apple_ldflags_arch} -arch ${apple_arch}") - - if(CMAKE_OSX_SYSROOT) - foreach(f c cxx ld) - set(apple_${f}flags_arch "${apple_${f}flags_arch} -isysroot ${CMAKE_OSX_SYSROOT}") - endforeach() - endif() -elseif(gmp_build_host STREQUAL "") - set(gmp_build_host "--build=${CMAKE_LIBRARY_ARCHITECTURE}") -endif() - -link_libraries(-static-libstdc++) -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - link_libraries(-static-libgcc) -endif() -if(MINGW) - link_libraries(-Wl,-Bstatic -lpthread) -endif() diff --git a/cmake/session-deps b/cmake/session-deps new file mode 160000 index 000000000..dae3b07a9 --- /dev/null +++ b/cmake/session-deps @@ -0,0 +1 @@ +Subproject commit dae3b07a96020efcb3d2844eb40de35a7816f66e diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 305c3749b..9753a5410 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,162 +1,68 @@ -option(SUBMODULE_CHECK "Enables checking that vendored library submodules are up to date" ON) - -if(SUBMODULE_CHECK) - find_package(Git) - if(GIT_FOUND) - function(check_submodule relative_path) - execute_process(COMMAND git rev-parse "HEAD" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path} OUTPUT_VARIABLE localHead) - execute_process(COMMAND git rev-parse "HEAD:external/${relative_path}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE checkedHead) - string(COMPARE EQUAL "${localHead}" "${checkedHead}" upToDate) - if (upToDate) - message(STATUS "Submodule 'external/${relative_path}' is up-to-date") - else() - message(FATAL_ERROR "Submodule 'external/${relative_path}' is not up-to-date. Please update with\ngit submodule update --init --recursive\nor run cmake with -DSUBMODULE_CHECK=OFF") - endif() - - # Extra arguments check nested submodules - foreach(submod ${ARGN}) - execute_process(COMMAND git rev-parse "HEAD" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path}/${submod} OUTPUT_VARIABLE localHead) - execute_process(COMMAND git rev-parse "HEAD:${submod}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path} OUTPUT_VARIABLE checkedHead) - string(COMPARE EQUAL "${localHead}" "${checkedHead}" upToDate) - if (NOT upToDate) - message(FATAL_ERROR "Nested submodule '${relative_path}/${submod}' is not up-to-date. Please update with\ngit submodule update --init --recursive\nor run cmake with -DSUBMODULE_CHECK=OFF") - endif() - endforeach() - endfunction () - - message(STATUS "Checking submodules") - check_submodule(ios-cmake) - check_submodule(libsodium-internal) - check_submodule(zstd) - check_submodule(protobuf) - check_submodule(session-router) - check_submodule(date) - endif() -endif() +include(../cmake/session-deps/Deps.cmake) + +message(STATUS "Checking submodules") +check_submodule(ios-cmake) +check_submodule(session-router external/nlohmann external/oxen-libquic) +# check_submodule's extra arguments are resolved as gitlinks of the first argument's repo, and it +# only recurses one level, so anything deeper needs its own call rooted at the repo that records it: +# oxen-encoding and oxen-logging belong to oxen-libquic, and fmt/spdlog to oxen-logging. +set(libquic_dir "${CMAKE_CURRENT_SOURCE_DIR}/session-router/external/oxen-libquic") +check_submodule(external/oxen-encoding WORKING_DIRECTORY "${libquic_dir}") +check_submodule(external/oxen-logging fmt spdlog WORKING_DIRECTORY "${libquic_dir}") +check_submodule(protobuf) +check_submodule(date) if(NOT BUILD_STATIC_DEPS AND NOT FORCE_ALL_SUBMODULES) find_package(PkgConfig REQUIRED) endif() -macro(libsession_system_or_submodule BIGNAME smallname target pkgconf subdir) - if(NOT TARGET ${target}) - option(FORCE_${BIGNAME}_SUBMODULE "force using ${smallname} submodule" OFF) - if(NOT BUILD_STATIC_DEPS AND NOT FORCE_${BIGNAME}_SUBMODULE AND NOT FORCE_ALL_SUBMODULES) - pkg_check_modules(${BIGNAME} ${pkgconf} IMPORTED_TARGET GLOBAL) - endif() - if(${BIGNAME}_FOUND) - add_library(${smallname} INTERFACE IMPORTED GLOBAL) - if(NOT TARGET PkgConfig::${BIGNAME} AND CMAKE_VERSION VERSION_LESS "3.21") - # Work around cmake bug 22180 (PkgConfig::THING not set if no flags needed) - else() - target_link_libraries(${smallname} INTERFACE PkgConfig::${BIGNAME}) - endif() - message(STATUS "Found system ${smallname} ${${BIGNAME}_VERSION}") - else() - message(STATUS "using ${smallname} submodule ${subdir}") - add_subdirectory(${subdir}) - endif() - if(NOT TARGET ${target}) - add_library(${target} ALIAS ${smallname}) - endif() - if(BUILD_STATIC_DEPS AND STATIC_BUNDLE) - libsession_static_bundle(${smallname}::${smallname}) - endif() - endif() -endmacro() - - -set(deps_cc "${CMAKE_C_COMPILER}") -set(cross_host "") -set(cross_rc "") -if(CMAKE_CROSSCOMPILING) - if(APPLE_TARGET_TRIPLE) - set(cross_host "--host=${APPLE_TARGET_TRIPLE}") - elseif(ANDROID) - if(CMAKE_ANDROID_ARCH_ABI MATCHES x86_64) - set(cross_host "--host=x86_64-linux-android") - set(android_compiler_prefix x86_64) - set(android_compiler_suffix linux-android) - elseif(CMAKE_ANDROID_ARCH_ABI MATCHES x86) - set(cross_host "--host=i686-linux-android") - set(android_compiler_prefix i686) - set(android_compiler_suffix linux-android) - elseif(CMAKE_ANDROID_ARCH_ABI MATCHES armeabi-v7a) - set(cross_host "--host=armv7a-linux-androideabi") - set(android_compiler_prefix armv7a) - set(android_compiler_suffix linux-androideabi) - elseif(CMAKE_ANDROID_ARCH_ABI MATCHES arm64-v8a) - set(cross_host "--host=aarch64-linux-android") - set(android_compiler_prefix aarch64) - set(android_compiler_suffix linux-android) - else() - message(FATAL_ERROR "unknown android arch: ${CMAKE_ANDROID_ARCH_ABI}") - endif() - - string(REPLACE "android-" "" android_platform_num "${ANDROID_PLATFORM}") - set(deps_cc "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_compiler_prefix}-${android_compiler_suffix}${android_platform_num}-clang") - else() - set(cross_host "--host=${ARCH_TRIPLET}") - if (ARCH_TRIPLET MATCHES mingw AND CMAKE_RC_COMPILER) - set(cross_rc "WINDRES=${CMAKE_RC_COMPILER}") - endif() - endif() -endif() - -if(ENABLE_NETWORKING) - set(LIBQUIC_BUILD_TESTS OFF CACHE BOOL "") - libsession_system_or_submodule(OXENQUIC quic oxen::quic liboxenquic>=1.8 session-router/external/oxen-libquic) -endif() - -libsession_system_or_submodule(OXENC oxenc oxenc::oxenc liboxenc>=1.5.0 session-router/external/oxen-libquic/external/oxen-encoding) +function(add_static_subdirectory dir) + set(BUILD_SHARED_LIBS OFF) + add_subdirectory(${dir} ${ARGN}) +endfunction() -if(NOT TARGET oxen::logging) - # Find this one using an intermediate target alias (oxenlogging::oxenlogging) rather than the - # final oxen::logging alias target that we actually use: that way either we go into using it as - # a submodule (in which case the submodule sets up oxen::logging), or else we find via system - # lib, in which case we need an extra intermediate interface library that also brings in fmt and - # spdlog. - libsession_system_or_submodule(OXENLOGGING oxen-logging oxenlogging::oxenlogging liboxen-logging>=1.2.0 session-router/external/oxen-libquic/external/oxen-logging) - if(NOT TARGET oxen::logging) - # If we load oxen-logging via system lib then we won't necessarily have fmt/spdlog targets, - # but this script will give us them: - include(session-router/external/oxen-libquic/external/oxen-logging/cmake/load_fmt_spdlog.cmake) +# Stock libsodium, not the libsodium-internal fork: the one thing the fork exposed that we used was +# the field arithmetic behind xed25519::pubkey(), which src/xed25519-tweetnacl.cpp now supplies. +session_dep(libsodium 1.0.21) +libsession_static_bundle(sessiondep::libsodium) - add_library(oxen-logging-fmt-spdlog INTERFACE) - target_link_libraries(oxen-logging-fmt-spdlog INTERFACE oxenlogging::oxenlogging ${OXEN_LOGGING_FMT_TARGET} ${OXEN_LOGGING_SPDLOG_TARGET}) - add_library(oxen::logging ALIAS oxen-logging-fmt-spdlog) - endif() +# Needed even without networking: oxenc is part of the public interface of `common`. +if(NOT TARGET oxenc::oxenc) + set(OXENC_BUILD_TESTS OFF CACHE BOOL "") + set(OXENC_BUILD_DOCS OFF CACHE BOOL "") + sessiondep_or_submodule(liboxenc 1.5.0 session-router/external/oxen-libquic/external/oxen-encoding oxenc::oxenc) endif() +if(ENABLE_NETWORKING AND ENABLE_NETWORKING_SROUTER) + set(SROUTER_FULL OFF CACHE BOOL "") + set(SROUTER_DAEMON OFF CACHE BOOL "") + set(SROUTER_NATIVE_BUILD OFF CACHE BOOL "") + set(SROUTER_JEMALLOC OFF CACHE BOOL "") - -if(CMAKE_C_COMPILER_LAUNCHER) - set(deps_cc "${CMAKE_C_COMPILER_LAUNCHER} ${deps_cc}") + add_static_subdirectory(session-router EXCLUDE_FROM_ALL) + libsession_static_bundle(session-router::core) endif() -set(deps_CFLAGS "-O2") -if(IPO_ENABLED) - set(deps_CFLAGS "${deps_CFLAGS} -flto") +# session-router provides oxen::quic when it is built (above); otherwise pull in oxen-libquic alone. +if(ENABLE_NETWORKING AND NOT TARGET oxen::quic) + set(LIBQUIC_BUILD_TESTS OFF CACHE BOOL "") + sessiondep_or_submodule(liboxenquic 1.8.0 session-router/external/oxen-libquic oxen::quic) endif() -if(APPLE) - foreach(lang C CXX) - string(APPEND deps_${lang}FLAGS " ${CMAKE_${lang}_SYSROOT_FLAG} ${CMAKE_OSX_SYSROOT} ${CMAKE_${lang}_OSX_DEPLOYMENT_TARGET_FLAG}${CMAKE_OSX_DEPLOYMENT_TARGET}") - foreach(arch ${CMAKE_OSX_ARCHITECTURES}) - string(APPEND deps_${lang}FLAGS " -arch ${arch}") - endforeach() - endforeach() +if(NOT TARGET oxen::logging) + # oxen-logging can't go through sessiondep_or_submodule: its public headers expose fmt types and + # a system liboxen-logging's .pc doesn't pull in fmt/spdlog, so consumers must add them + # explicitly. oxen-logging ships a cmake/load.cmake that resolves oxen::logging for us -- either + # a system liboxen-logging bundled with suitable fmt/spdlog targets, or the submodule (which + # builds its own fmt/spdlog). We just force the submodule path when our other deps would also + # prefer submodules over system libs. + option(DEPS_FORCE_liboxen-logging_SUBMODULE "force using liboxen-logging submodule" OFF) + if(BUILD_STATIC_DEPS OR DEPS_FORCE_SUBMODULE OR DEPS_FORCE_liboxen-logging_SUBMODULE) + set(OXEN_LOGGING_FORCE_SUBMODULES ON) + endif() + include(session-router/external/oxen-libquic/external/oxen-logging/cmake/load.cmake) endif() -function(add_static_subdirectory dir) - set(BUILD_SHARED_LIBS OFF) - add_subdirectory(${dir} ${ARGN}) -endfunction() - -add_static_subdirectory(libsodium-internal) -libsession_static_bundle(libsodium::sodium-internal) - - set(protobuf_VERBOSE ON CACHE BOOL "" FORCE) set(protobuf_INSTALL ON CACHE BOOL "" FORCE) set(protobuf_WITH_ZLIB OFF CACHE BOOL "" FORCE) @@ -167,60 +73,23 @@ set(protobuf_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(protobuf_ABSL_PROVIDER "module" CACHE STRING "" FORCE) set(protobuf_BUILD_PROTOC_BINARIES OFF CACHE BOOL "") set(protobuf_BUILD_PROTOBUF_BINARIES ON CACHE BOOL "" FORCE) -libsession_system_or_submodule(PROTOBUF_LITE protobuf_lite protobuf::libprotobuf-lite protobuf-lite>=3.21 protobuf) -if(TARGET PkgConfig::PROTOBUF_LITE AND NOT TARGET protobuf::libprotobuf-lite) - add_library(protobuf::libprotobuf-lite ALIAS PkgConfig::PROTOBUF_LITE) +sessiondep_or_submodule(protobuf-lite 3.21 protobuf protobuf::libprotobuf-lite) + +# Force a static libzstd: we want semi-stable compressed output, which means we want all session +# clients to use the same version (where that is guaranteed) as much as possible, so that duplicate +# configs get deduplicated at the swarm. +set(BUILD_STATIC_libzstd ON CACHE BOOL "" FORCE) +session_dep(libzstd 1.5) +libsession_static_bundle(sessiondep::libzstd) + +if(NOT TARGET nlohmann_json::nlohmann_json) + set(JSON_BuildTests OFF CACHE INTERNAL "") + set(JSON_Install ON CACHE INTERNAL "") # Required to export targets that we use + sessiondep_or_submodule(nlohmann_json 3.7.0 session-router/external/nlohmann nlohmann_json::nlohmann_json) endif() - -set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "") -set(ZSTD_BUILD_TESTS OFF CACHE BOOL "") -set(ZSTD_BUILD_CONTRIB OFF CACHE BOOL "") -set(ZSTD_BUILD_SHARED OFF CACHE BOOL "") -set(ZSTD_BUILD_STATIC ON CACHE BOOL "") -set(ZSTD_MULTITHREAD_SUPPORT OFF CACHE BOOL "") -add_subdirectory(zstd/build/cmake EXCLUDE_FROM_ALL) -# zstd's cmake doesn't properly set up include paths on its targets, so we have to wrap it in an -# interface target that does: -add_library(libzstd_static_fixed_includes INTERFACE) -target_include_directories(libzstd_static_fixed_includes INTERFACE zstd/lib zstd/lib/common) -target_link_libraries(libzstd_static_fixed_includes INTERFACE libzstd_static) -add_library(libzstd::static ALIAS libzstd_static_fixed_includes) -export( - TARGETS libzstd_static_fixed_includes - NAMESPACE libsession:: - FILE libsessionZstd.cmake -) -libsession_static_bundle(libzstd_static) - - -set(JSON_BuildTests OFF CACHE INTERNAL "") -set(JSON_Install ON CACHE INTERNAL "") # Required to export targets that we use -libsession_system_or_submodule(NLOHMANN nlohmann_json nlohmann_json::nlohmann_json nlohmann_json>=3.7.0 session-router/external/nlohmann) - -if(ENABLE_NETWORKING AND ENABLE_NETWORKING_SROUTER) - set(SROUTER_FULL OFF CACHE BOOL "") - set(SROUTER_DAEMON OFF CACHE BOOL "") - set(SROUTER_NATIVE_BUILD OFF CACHE BOOL "") - set(SROUTER_JEMALLOC OFF CACHE BOOL "") - - add_library(sodium INTERFACE) - target_link_libraries(sodium INTERFACE libsodium::sodium-internal) - add_static_subdirectory(session-router EXCLUDE_FROM_ALL) - libsession_static_bundle(session-router::libsessionrouter) -endif() - -set(JSON_BuildTests OFF CACHE INTERNAL "") -set(JSON_Install ON CACHE INTERNAL "") # Required to export targets that we use - -function(simdutf_subdir) - set(SIMDUTF_TESTS OFF CACHE BOOL "") - set(SIMDUTF_TOOLS OFF CACHE BOOL "") - set(BUILD_SHARED_LIBS OFF) - add_subdirectory(simdutf) -endfunction() -simdutf_subdir() -libsession_static_bundle(simdutf) +session_dep(simdutf 7) +libsession_static_bundle(sessiondep::simdutf) # We need Howard Hinnant's header-only date library for now because the STL implementation of # std::chrono::parse() is spotty or broken on: diff --git a/external/libsodium-internal b/external/libsodium-internal deleted file mode 160000 index e12e612dc..000000000 --- a/external/libsodium-internal +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e12e612dc735909a59be552f9bf03fe98e320703 diff --git a/external/session-router b/external/session-router index d2def4c91..2b91e9ab4 160000 --- a/external/session-router +++ b/external/session-router @@ -1 +1 @@ -Subproject commit d2def4c91d024f9d896b43e817520f27a41a7995 +Subproject commit 2b91e9ab42b36c4f802d3a1548d139712ee9e5df diff --git a/external/simdutf b/external/simdutf deleted file mode 160000 index 7b3f5afca..000000000 --- a/external/simdutf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7b3f5afcae322391a03736809ef6eea0c2934388 diff --git a/external/zstd b/external/zstd deleted file mode 160000 index e47e674cd..000000000 --- a/external/zstd +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e47e674cd09583ff0503f0f6defd6d23d8b718d3 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e6815bc3a..578d25a1b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,6 +60,7 @@ add_libsession_util_library(crypto session_protocol.cpp sodium_array.cpp xed25519.cpp + xed25519-tweetnacl.cpp pro_backend.cpp types.cpp ) @@ -91,15 +92,16 @@ target_link_libraries(util PUBLIC common oxen::logging - libzstd::static - simdutf + PRIVATE + sessiondep::libzstd + sessiondep::simdutf ) target_link_libraries(crypto PUBLIC util PRIVATE - libsodium::sodium-internal + sessiondep::libsodium nlohmann_json::nlohmann_json libsession::protos ) @@ -109,17 +111,11 @@ target_link_libraries(config crypto libsession::protos PRIVATE - libsodium::sodium-internal + sessiondep::libsodium ) if(ENABLE_NETWORKING) - # libevent - if(NOT TARGET libevent::core) - add_library(libevent_core INTERFACE) - pkg_check_modules(LIBEVENT_core libevent_core>=2.1 IMPORTED_TARGET REQUIRED) - target_link_libraries(libevent_core INTERFACE PkgConfig::LIBEVENT_core) - add_library(libevent::core ALIAS libevent_core) - endif() + session_dep(libevent_core 2.1) add_libsession_util_library(network onionreq/builder.cpp @@ -145,20 +141,20 @@ if(ENABLE_NETWORKING) target_link_libraries(network PUBLIC crypto - quic + oxen::quic PRIVATE nlohmann_json::nlohmann_json - libsodium::sodium-internal - nettle::nettle + sessiondep::libsodium + sessiondep::nettle date::date - libevent::core + sessiondep::libevent_core ) if(ENABLE_NETWORKING_SROUTER) target_sources(network PRIVATE network/routing/session_router_router.cpp) - target_link_libraries(network PUBLIC session-router::libsessionrouter) + target_link_libraries(network PUBLIC session-router::core) endif() if (BUILD_STATIC_DEPS) diff --git a/src/nettle-compat.hpp b/src/nettle-compat.hpp new file mode 100644 index 000000000..07111004c --- /dev/null +++ b/src/nettle-compat.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace session { + +// nettle 4.0 dropped the length argument from the *_digest functions, which now always write the +// full digest. We build against both a distro nettle 3 and the vendored nettle 4, so dispatch on +// whichever signature the nettle in use declares rather than on a version macro. Every call site +// asks for the full digest length, which is what nettle 3 needs and what nettle 4 assumes, so the +// two spellings compute the same thing. +template +void nettle_digest(Ctx* ctx, std::size_t length, Out* out) { + if constexpr (std::is_invocable_v) + digest(ctx, length, out); + else + digest(ctx, out); +} + +} // namespace session diff --git a/src/onionreq/hop_encryption.cpp b/src/onionreq/hop_encryption.cpp index 69a491c6c..10fb69d81 100644 --- a/src/onionreq/hop_encryption.cpp +++ b/src/onionreq/hop_encryption.cpp @@ -16,6 +16,7 @@ #include #include +#include "../nettle-compat.hpp" #include "session/export.h" #include "session/network/key_types.hpp" #include "session/onionreq/builder.hpp" @@ -134,7 +135,7 @@ std::vector HopEncryption::encrypt_aesgcm( o += plaintext.size(); // Append digest - gcm_aes256_digest(&ctx, GCM_DIGEST_SIZE, o); + nettle_digest(&ctx, GCM_DIGEST_SIZE, o); o += GCM_DIGEST_SIZE; assert(o == output.data() + output.size()); @@ -169,7 +170,7 @@ std::vector HopEncryption::decrypt_aesgcm( gcm_aes256_decrypt(&ctx, ciphertext.size(), plaintext.data(), ciphertext.data()); std::array digest_out; - gcm_aes256_digest(&ctx, digest_out.size(), digest_out.data()); + nettle_digest(&ctx, digest_out.size(), digest_out.data()); if (sodium_memcmp(digest_out.data(), digest_in.data(), GCM_DIGEST_SIZE) != 0) throw std::runtime_error{"Decryption failed (AES256-GCM)"}; diff --git a/src/xed25519-tweetnacl.cpp b/src/xed25519-tweetnacl.cpp new file mode 100644 index 000000000..b0b572221 --- /dev/null +++ b/src/xed25519-tweetnacl.cpp @@ -0,0 +1,150 @@ +// This file contains a subset of TweetNaCl (https://tweetnacl.cr.yp.to/software.html) public domain +// code to perform the X25519 -> Ed25519 conversion; libsodium doesn't provide enough access to +// internals to compute this without hacking up libsodium's build, which is fragile. Hence we use +// this subset of the portable TweetNaCl for that single function, and libsodium for everything +// else. + +#include +#include +#include + +#include "session/xed25519.hpp" + +namespace session::xed25519 { + +namespace { + + // clang-format off + +#define FOR(i,n) for (i = 0;i < n;++i) + +using gf = int64_t[16]; + +const gf gf1 = {1}; + +void car25519(gf o) +{ + int i; + int64_t c; + FOR(i,16) { + o[i]+=(1LL<<16); + c=o[i]>>16; + o[(i+1)*(i<15)]+=c-1+37*(c-1)*(i==15); + o[i]-=c<<16; + } +} + +void sel25519(gf p,gf q,int b) +{ + int64_t t,i,c=~(b-1); + FOR(i,16) { + t= c&(p[i]^q[i]); + p[i]^=t; + q[i]^=t; + } +} + +void pack25519(uint8_t *o,const gf n) +{ + int i,j,b; + gf m,t; + FOR(i,16) t[i]=n[i]; + car25519(t); + car25519(t); + car25519(t); + FOR(j,2) { + m[0]=t[0]-0xffed; + for(i=1;i<15;i++) { + m[i]=t[i]-0xffff-((m[i-1]>>16)&1); + m[i-1]&=0xffff; + } + m[15]=t[15]-0x7fff-((m[14]>>16)&1); + b=(m[15]>>16)&1; + m[14]&=0xffff; + sel25519(t,m,1-b); + } + FOR(i,16) { + o[2*i]=t[i]&0xff; + o[2*i+1]=t[i]>>8; + } +} + +void unpack25519(gf o, const uint8_t *n) +{ + int i; + FOR(i,16) o[i]=n[2*i]+((int64_t)n[2*i+1]<<8); + o[15]&=0x7fff; +} + +void A(gf o,const gf a,const gf b) +{ + int i; + FOR(i,16) o[i]=a[i]+b[i]; +} + +void Z(gf o,const gf a,const gf b) +{ + int i; + FOR(i,16) o[i]=a[i]-b[i]; +} + +void M(gf o,const gf a,const gf b) +{ + int64_t i,j,t[31]; + FOR(i,31) t[i]=0; + FOR(i,16) FOR(j,16) t[i+j]+=a[i]*b[j]; + FOR(i,15) t[i]+=38*t[i+16]; + FOR(i,16) o[i]=t[i]; + car25519(o); + car25519(o); +} + +void S(gf o,const gf a) +{ + M(o,a,a); +} + +// Y +void inv25519(gf o,const gf i) +{ + gf c; + int a; + FOR(a,16) c[a]=i[a]; + for(a=253;a>=0;a--) { + S(c,c); + if(a!=2&&a!=4) M(c,c,i); + } + FOR(a,16) o[a]=c[a]; +} + + // clang-format on + +} // namespace + +std::array pubkey(std::span x_pk) { + gf u; + unpack25519(u, x_pk.data()); + + // u - 1 + gf u_minus_one; + Z(u_minus_one, u, gf1); + + // Compute: u + 1 + gf u_plus_one; + A(u_plus_one, u, gf1); + + // Compute: (u + 1)^-1 + gf u_plus_one_inv; + inv25519(u_plus_one_inv, u_plus_one); + + // Compute: y = (u - 1) * (u + 1)^-1 + gf y; + M(y, u_minus_one, u_plus_one_inv); + + // Encode to 32 bytes (sign bit is naturally 0) + std::array ed_pk; + pack25519(ed_pk.data(), y); + return ed_pk; +} + +} // namespace session::xed25519 diff --git a/src/xed25519.cpp b/src/xed25519.cpp index ce1e9e5fb..6e5a87177 100644 --- a/src/xed25519.cpp +++ b/src/xed25519.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -21,16 +20,6 @@ using bytes = std::array; namespace { - void fe25519_montx_to_edy(fe25519 y, const fe25519 u) { - fe25519 one; - crypto_internal_fe25519_1(one); - fe25519 um1, up1; - crypto_internal_fe25519_sub(um1, u, one); - crypto_internal_fe25519_add(up1, u, one); - crypto_internal_fe25519_invert(up1, up1); - crypto_internal_fe25519_mul(y, um1, up1); - } - // We construct an Ed25519-like signature with one important difference: where Ed25519 // calculates `r = H(S || M) mod L` (where S is the second half of the SHA-512 hash of the // secret key) we instead calculate `r = H(a || M || Z) mod L`. @@ -144,16 +133,7 @@ bool verify(std::string_view signature, std::string_view curve25519_pubkey, std: return verify(sig.first<64>(), pubkey.first<32>(), to_span(msg)); } -std::array pubkey(std::span curve25519_pubkey) { - fe25519 u, y; - crypto_internal_fe25519_frombytes(u, curve25519_pubkey.data()); - fe25519_montx_to_edy(y, u); - - std::array ed_pubkey; - crypto_internal_fe25519_tobytes(ed_pubkey.data(), y); - - return ed_pubkey; -} +// pubkey(std::span) is in xed25519-tweetnacl.cpp std::string pubkey(std::string_view curve25519_pubkey) { auto x_pk = to_span(curve25519_pubkey); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3f0cd7f9a..4b93aa096 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -52,7 +52,7 @@ add_library(test_libs INTERFACE) target_link_libraries(test_libs INTERFACE libsession::config - libsodium::sodium-internal + sessiondep::libsodium nlohmann_json::nlohmann_json oxen::logging) diff --git a/tests/test_compression.cpp b/tests/test_compression.cpp index 2667d3423..fa361e334 100644 --- a/tests/test_compression.cpp +++ b/tests/test_compression.cpp @@ -123,11 +123,11 @@ TEST_CASE("compression", "[config][compression]") { d = data2; session::config::compress_message(d, 19); CHECK(d[0] == 'z'); - CHECK(d.size() == 157); // Yeah, it actually gets *bigger* with supposedly "higher" compression + CHECK(d.size() == 156); CHECK(d.size() < data2.size()); CHECK(to_hex(d) == - "7a28b52ffd20aa9d0400e40764313a23693165313a2664313a6e31323a4b616c6c6965313a7032393a68" - "7474703a2f2f6b2e6578616d706c652e6f72672f4b626d70313a71323473656372657465313a3c6c6c69" - "306533323aea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c96564653d6431" - "3a6e303a313a7071303a6565070028812c55282f03fceac460149b57cd509a"); + "7a28b52ffd20aa95040022881f1f907d9c93291a7627219a79d06bb82c3c69341b104115dbf3c0860176" + "f63013ff7ba4247de211d1275be493fffff6eb7892db81b9dc9da26f40955e5d868586cd577bb69e00f7" + "caf2110f04219f7cf49bda3f19a5f4091966d5c199a3f14132c4d26f7cc7e14914edbca3903ef91e0862" + "955712d1275be1939f78844fb606008c12e0cb50be3a1c18c5e655339426"); } From 44958ab9fcadf93c17bba0f10cf6560412725b2a Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 2 Oct 2026 10:30:20 +1000 Subject: [PATCH 02/12] Port session_router_router to session-router's tunnel claims; bump session-router to afb98f9 --- external/session-router | 2 +- .../network/routing/session_router_router.hpp | 9 +++++++ src/network/routing/session_router_router.cpp | 27 ++++++++++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/external/session-router b/external/session-router index 2b91e9ab4..afb98f959 160000 --- a/external/session-router +++ b/external/session-router @@ -1 +1 @@ -Subproject commit 2b91e9ab42b36c4f802d3a1548d139712ee9e5df +Subproject commit afb98f959f4f7e0ef996aef02fa21a6caf26b1e9 diff --git a/include/session/network/routing/session_router_router.hpp b/include/session/network/routing/session_router_router.hpp index 64e5c4e23..805b734dc 100644 --- a/include/session/network/routing/session_router_router.hpp +++ b/include/session/network/routing/session_router_router.hpp @@ -42,6 +42,15 @@ class SessionRouter : public IRouter, public std::enable_shared_from_this _transport; std::unordered_map _active_tunnels; + + // The claim on each tunnel we have asked session-router for. session-router closes a tunnel + // when its last claim is destroyed, so these have to outlive the request that created them: + // dropping the return value of establish_udp() tears the tunnel down immediately. Type-erased + // (each is a session::router::udp_tunnel) so that this header need not include + // . Guarded separately from `_active_tunnels` because session-router + // invokes the failure callback, which releases a claim, from its own thread. + std::mutex _tunnel_claims_mutex; + std::unordered_map> _tunnel_claims; std::unordered_map>> _pending_requests; std::unordered_map> _active_uploads; diff --git a/src/network/routing/session_router_router.cpp b/src/network/routing/session_router_router.cpp index 19240cfc1..4645d0f43 100644 --- a/src/network/routing/session_router_router.cpp +++ b/src/network/routing/session_router_router.cpp @@ -302,6 +302,10 @@ void SessionRouter::_close_connections() { // Clear all storage of requests, paths and connections so that we are in a fresh state on // relaunch _active_tunnels.clear(); + { + std::lock_guard lock{_tunnel_claims_mutex}; + _tunnel_claims.clear(); + } _pending_requests.clear(); _update_status(ConnectionStatus::disconnected); log::info(cat, "Closed all connections."); @@ -855,7 +859,7 @@ void SessionRouter::_establish_tunnel( "[Request {}] Establishing new tunnel to {}.", initiating_req_id, address_pubkey_hex); - srouter->establish_udp( + auto claim = srouter->establish_udp( srouter_address, test_port, [weak_self = weak_from_this(), this, address_pubkey_hex, initiating_req_id]( @@ -888,18 +892,26 @@ void SessionRouter::_establish_tunnel( _send_via_tunnel(info, std::move(req), std::move(cb)); } }, - [weak_self = weak_from_this(), this, address_pubkey_hex, initiating_req_id]() mutable { + [weak_self = weak_from_this(), this, address_pubkey_hex, initiating_req_id]( + router::tunnel_failure reason) mutable { auto self = weak_self.lock(); if (!self) return; log::info( cat, - "[Request {}] Unable to establish session router UDP connection to {}.", + "[Request {}] Unable to establish session router UDP connection to {} " + "({}).", initiating_req_id, - address_pubkey_hex); + address_pubkey_hex, + reason == router::tunnel_failure::unreachable ? "no relay contact" + : "timed out"); _active_tunnels.erase(address_pubkey_hex); + { + std::lock_guard lock{_tunnel_claims_mutex}; + _tunnel_claims.erase(address_pubkey_hex); + } // Fail all the pending requests for this connection if (auto it = _pending_requests.find(address_pubkey_hex); @@ -924,6 +936,13 @@ void SessionRouter::_establish_tunnel( if (_active_tunnels.empty()) _update_status(ConnectionStatus::disconnected); }); + + // Keep the tunnel open for as long as we hold the node's entry. If the failure callback has + // already run on session-router's thread this leaves a claim behind for a tunnel that failed, + // which is harmless: a later request to the same node replaces it. + std::lock_guard lock{_tunnel_claims_mutex}; + _tunnel_claims.insert_or_assign( + address_pubkey_hex, std::make_shared(std::move(claim))); } void SessionRouter::_send_via_tunnel( From 203c65b0bf00d78da8544038f00cce413e84ff21 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 2 Oct 2026 13:59:44 +1000 Subject: [PATCH 03/12] quic: use max_udp_payload::minimum() instead of the deprecated disable_mtu_discovery libquic 1.9 deprecates opt::disable_mtu_discovery as exactly this value; behaviour is unchanged. --- src/network/transport/quic_transport.cpp | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/network/transport/quic_transport.cpp b/src/network/transport/quic_transport.cpp index a743944db..8c53bbe76 100644 --- a/src/network/transport/quic_transport.cpp +++ b/src/network/transport/quic_transport.cpp @@ -147,25 +147,17 @@ void QuicTransport::send_request(Request request, network_response_callback_t ca void QuicTransport::_recreate_endpoint() { // The optional must CONTAIN the option to have any effect: libquic's optional-taking // handle_ep_opt overload does nothing when the optional is empty, so a default-constructed - // std::optional{} silently leaves discovery enabled. + // std::optional{} silently leaves discovery enabled. // - // TODO: replace with quic::opt::max_udp_payload::minimum(), which is the same value under the - // name that isn't deprecated. That needs a newer libquic than this pin carries, and moving the - // pin needs the dependency handling in external/CMakeLists.txt moved onto session-deps first - - // the newer session-router defines oxen::quic itself, which collides with our own definition of - // it. Both of those come over with the Session client work, and this goes when they do. - // - // Until then the deprecated name is the only spelling that compiles against both this pin and - // the newer liboxenquic the Linux CI stages take from the system, which deprecates it under - // -Werror. The replacement does not exist in the pinned copy at all. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + // Capping the payload at the QUIC minimum leaves path MTU discovery nothing to probe, which is + // what "disable" has always meant here. This replaces the deprecated opt::disable_mtu_discovery, + // which libquic defines as exactly this value. _endpoint = quic::Endpoint::endpoint( *_loop, quic::Address{}, - (_config.disable_mtu_discovery ? std::make_optional() - : std::nullopt)); -#pragma GCC diagnostic pop + (_config.disable_mtu_discovery + ? std::make_optional(quic::opt::max_udp_payload::minimum()) + : std::nullopt)); } void QuicTransport::_close_connections() { From 798a4cfbb0d007394f0c43ad77ccabd6fb720e35 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Oct 2026 09:48:29 +1100 Subject: [PATCH 04/12] Bump version number --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ea88526e..cda2a7b90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ if(CCACHE_PROGRAM) endif() project(libsession-util - VERSION 1.9.2 + VERSION 1.9.3 DESCRIPTION "Session client utility library" LANGUAGES ${LANGS}) From 1ed9624daa7d17a6ee51fbafa7167858f1e99a7b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Oct 2026 10:05:05 +1100 Subject: [PATCH 05/12] Drop the unused build-tree export() calls; clang-format session-deps' system-library targets (e.g. protobuf-lite) are not in any export set, so export(TARGETS protos) fails to generate when building against system libraries. Nothing consumes libsessionTargets.cmake; client removed these as well. --- proto/CMakeLists.txt | 5 ----- src/CMakeLists.txt | 5 ----- src/network/transport/quic_transport.cpp | 4 ++-- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 324b116b8..a7ab778af 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -23,11 +23,6 @@ endif() libsession_static_bundle(protos) add_library(libsession::protos ALIAS protos) -export( - TARGETS protos - NAMESPACE libsession:: - FILE libsessionTargets.cmake -) list(APPEND libsession_export_targets protos) set(libsession_export_targets "${libsession_export_targets}" PARENT_SCOPE) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 578d25a1b..8f9cff05e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -212,11 +212,6 @@ target_link_libraries(common INTERFACE version) foreach(tgt ${export_targets}) add_library("libsession::${tgt}" ALIAS "${tgt}") endforeach() -export( - TARGETS ${export_targets} common version - NAMESPACE libsession:: - APPEND FILE libsessionTargets.cmake -) list(APPEND libsession_export_targets ${export_targets}) set(libsession_export_targets "${libsession_export_targets}" PARENT_SCOPE) diff --git a/src/network/transport/quic_transport.cpp b/src/network/transport/quic_transport.cpp index 8c53bbe76..f4c97376a 100644 --- a/src/network/transport/quic_transport.cpp +++ b/src/network/transport/quic_transport.cpp @@ -150,8 +150,8 @@ void QuicTransport::_recreate_endpoint() { // std::optional{} silently leaves discovery enabled. // // Capping the payload at the QUIC minimum leaves path MTU discovery nothing to probe, which is - // what "disable" has always meant here. This replaces the deprecated opt::disable_mtu_discovery, - // which libquic defines as exactly this value. + // what "disable" has always meant here. This replaces the deprecated + // opt::disable_mtu_discovery, which libquic defines as exactly this value. _endpoint = quic::Endpoint::endpoint( *_loop, quic::Address{}, From 6f27df0c9e24ed8d37e495c5c581c474fbbdfe10 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Fri, 2 Oct 2026 13:47:53 -0300 Subject: [PATCH 06/12] Bump session-deps to current dev libquic 1.11.0 relies on session-deps' patched ngtcp2 1.25.0 recipe (6b37832, d74f5b1). That change kept session_deps_version at 1.28, the same as 58c5a08, and with equal versions the first copy loaded wins: ours, at the top level. So anything older here would quietly build libquic against the unpatched ngtcp2. Also picked up from dae3b07 (what client pins): 9051cd2 passes the macOS deployment target to the CMake-built deps on native builds (zstd and simdutf were being built for the SDK's own macOS version, 15.0 on a current Xcode, rather than our 10.15), and cc67e32 builds CMake deps as Release by default. --- cmake/session-deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/session-deps b/cmake/session-deps index dae3b07a9..d74f5b137 160000 --- a/cmake/session-deps +++ b/cmake/session-deps @@ -1 +1 @@ -Subproject commit dae3b07a96020efcb3d2844eb40de35a7816f66e +Subproject commit d74f5b1376cd2c106461d9a334a5c635d50bed87 From 8ee3ea55008da340cb9ed91fadf965f265e3ee23 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Fri, 2 Oct 2026 13:48:01 -0300 Subject: [PATCH 07/12] Make ENABLE_NETWORKING_SROUTER=OFF link, and default it off session_network.cpp called SessionRouter::make() unconditionally while session_router_router.cpp is only compiled with ENABLE_NETWORKING_SROUTER, so an OFF build produced a static library with an undefined reference that only showed up when something linked it (which is why the mingw CI build, a static library alone, never noticed). Gate the session-router code on a compile definition, as client does, and throw if the session_router router is requested from a build without it. No client on this branch routes through session-router, so default the option off rather than making every networking build carry it. --- CMakeLists.txt | 4 +++- src/CMakeLists.txt | 1 + src/network/session_network.cpp | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cda2a7b90..a9389a577 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,7 +93,9 @@ option(USE_LTO "Use Link-Time Optimization" ${use_lto_default}) # Provide this as an option for now because GMP and Desktop are sometimes unhappy with each other. option(ENABLE_NETWORKING "Build with networking functionality" ON) -option(ENABLE_NETWORKING_SROUTER "Build with session-router networking support (requires ENABLE_NETWORKING)" ON) +# No client on this branch routes through session-router, so it is off by default rather than costing +# every networking build its dependency tree. +option(ENABLE_NETWORKING_SROUTER "Build with session-router networking support (requires ENABLE_NETWORKING)" OFF) if(USE_LTO) include(CheckIPOSupported) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8f9cff05e..00821b6da 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -155,6 +155,7 @@ if(ENABLE_NETWORKING) PRIVATE network/routing/session_router_router.cpp) target_link_libraries(network PUBLIC session-router::core) + target_compile_definitions(network PRIVATE ENABLE_NETWORKING_SROUTER) endif() if (BUILD_STATIC_DEPS) diff --git a/src/network/session_network.cpp b/src/network/session_network.cpp index 1270771db..f56e690be 100644 --- a/src/network/session_network.cpp +++ b/src/network/session_network.cpp @@ -18,7 +18,9 @@ #include "session/network/request_queue.hpp" #include "session/network/routing/direct_router.hpp" #include "session/network/routing/onion_request_router.hpp" +#ifdef ENABLE_NETWORKING_SROUTER #include "session/network/routing/session_router_router.hpp" +#endif #include "session/network/session_network.h" #include "session/network/session_network_types.hpp" #include "session/network/transport/quic_transport.hpp" @@ -87,6 +89,7 @@ namespace { return {file_server_config}; } +#ifdef ENABLE_NETWORKING_SROUTER config::SessionRouter build_session_router_config( const config::Config& main_config, const config::FileServer& file_server_config) { if (!main_config.cache_directory) @@ -101,6 +104,7 @@ namespace { *main_config.cache_directory, main_config.path_length}; } +#endif config::OnionRequestRouter build_onion_request_router_config( const config::Config& main_config, const config::FileServer& file_server_config) { @@ -203,12 +207,16 @@ Network::Network(config::Config _conf) : break; case opt::router::Type::session_router: +#ifdef ENABLE_NETWORKING_SROUTER _router = SessionRouter::make( std::move(build_session_router_config(config, file_server_config)), _loop, _snode_pool, _transport); break; +#else + throw std::runtime_error{"Session Router support is not enabled in this build!"}; +#endif case opt::router::Type::direct: _router = std::make_unique( From 5893e627490278a48226fe20472fef1150030574 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Fri, 2 Oct 2026 13:59:50 -0300 Subject: [PATCH 08/12] quic_transport: describe the MTU cap, not the change that introduced it --- src/network/transport/quic_transport.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/network/transport/quic_transport.cpp b/src/network/transport/quic_transport.cpp index f4c97376a..2ccc40320 100644 --- a/src/network/transport/quic_transport.cpp +++ b/src/network/transport/quic_transport.cpp @@ -150,8 +150,7 @@ void QuicTransport::_recreate_endpoint() { // std::optional{} silently leaves discovery enabled. // // Capping the payload at the QUIC minimum leaves path MTU discovery nothing to probe, which is - // what "disable" has always meant here. This replaces the deprecated - // opt::disable_mtu_discovery, which libquic defines as exactly this value. + // how "disable" is expressed to libquic. _endpoint = quic::Endpoint::endpoint( *_loop, quic::Address{}, From 025c2288f11c566a5877e0e5f313a50671a782fc Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Mon, 5 Oct 2026 18:49:50 -0300 Subject: [PATCH 09/12] Bump session-router for libquic 1.11.0 libsession takes libquic from session-router's submodule even with ENABLE_NETWORKING_SROUTER off, so this is what moves it from a pre-1.9.0 snapshot (c1835882) to 1.11.0. Since then libquic has fixed a use-after-free in Endpoint::drop_connection (1.9.1), stopped using a connection that ngtcp2 has rejected and stopped requesting chunks from a closing stream (1.10.1), no longer treats writing to a closing or draining connection as an error (1.10.2), and in 1.11.0 does its own PMTU probing, drops packets on EMSGSIZE/ENOBUFS rather than closing the connection, and migrates rather than closes a connection when the host's source address changes. --- external/session-router | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/session-router b/external/session-router index afb98f959..1f5da8692 160000 --- a/external/session-router +++ b/external/session-router @@ -1 +1 @@ -Subproject commit afb98f959f4f7e0ef996aef02fa21a6caf26b1e9 +Subproject commit 1f5da86924d49278ada9e926eff38f78d8c9a25e From 609408cd70964c4044425d1b85bf2afa5096a939 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Mon, 5 Oct 2026 19:54:44 -0300 Subject: [PATCH 10/12] Bump session-deps for gnutls's Apple frameworks session-deps' gnutls recipe never passed on the Security and CoreFoundation frameworks that gnutls's own gnutls.pc lists on darwin, so a static macOS link of anything using libquic failed on gnutls's system-trust symbols unless something else happened to add them. With session-router off by default here, nothing else does. --- cmake/session-deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/session-deps b/cmake/session-deps index d74f5b137..695ac85f0 160000 --- a/cmake/session-deps +++ b/cmake/session-deps @@ -1 +1 @@ -Subproject commit d74f5b1376cd2c106461d9a334a5c635d50bed87 +Subproject commit 695ac85f00cde0117f8017b7b68a7c3db17f2c70 From 080744c138428785558c795c96a49b1307965561 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Mon, 5 Oct 2026 22:34:15 -0300 Subject: [PATCH 11/12] Bump session-deps for macOS cross-arch builds session-deps' meson cross file setup died on utils/macos.sh's x86_64 build (CMAKE_SYSTEM_NAME=Darwin with no SDK_NAME) before any dependency was asked for, breaking the static macOS build. --- cmake/session-deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/session-deps b/cmake/session-deps index 695ac85f0..e809a6419 160000 --- a/cmake/session-deps +++ b/cmake/session-deps @@ -1 +1 @@ -Subproject commit 695ac85f00cde0117f8017b7b68a7c3db17f2c70 +Subproject commit e809a64190494683935b63a8b8481178c1b1a8ab From a9bafa6c42d93018c00d5ca4a6da704b714cafa5 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Oct 2026 12:37:22 +1100 Subject: [PATCH 12/12] Add opt::quic_max_udp_payload (from client) Backports client's QUIC payload cap (b46b88f7), MTU part only: a size cap in the C++ options, Config, the C API (session_network_config.quic_max_udp_payload) and QuicTransport. quic_disable_mtu_discovery stays as a supported alias for a 1200 cap rather than being deprecated. Values under 1200 are rejected when the option is set instead of when libquic builds the endpoint. --- include/session/network/network_config.hpp | 3 +- include/session/network/network_opt.hpp | 11 ++++++- include/session/network/session_network.h | 5 ++- .../network/transport/quic_transport.hpp | 2 +- src/network/network_config.cpp | 13 ++++++-- src/network/session_network.cpp | 9 ++++-- src/network/transport/quic_transport.cpp | 9 ++---- tests/CMakeLists.txt | 1 + tests/test_network_config.cpp | 32 +++++++++++++++++++ tests/test_quic_transport.cpp | 2 +- 10 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 tests/test_network_config.cpp diff --git a/include/session/network/network_config.hpp b/include/session/network/network_config.hpp index 50eded586..c1b0bcf65 100644 --- a/include/session/network/network_config.hpp +++ b/include/session/network/network_config.hpp @@ -64,7 +64,7 @@ struct Config { // Quic Transport Options std::chrono::milliseconds quic_handshake_timeout{3s}; std::chrono::seconds quic_keep_alive{10s}; - bool quic_disable_mtu_discovery = false; + std::optional quic_max_udp_payload; template requires(sizeof...(Opt) > 0 && (opt::is_option> && ...)) @@ -115,6 +115,7 @@ struct Config { // Quic transport options void handle_config_opt(opt::quic_handshake_timeout qht); void handle_config_opt(opt::quic_keep_alive qka); + void handle_config_opt(opt::quic_max_udp_payload qmup); void handle_config_opt(opt::quic_disable_mtu_discovery qdmd); // Onion request router options diff --git a/include/session/network/network_opt.hpp b/include/session/network/network_opt.hpp index 67c52bfc5..10ee3170b 100644 --- a/include/session/network/network_opt.hpp +++ b/include/session/network/network_opt.hpp @@ -376,7 +376,15 @@ namespace opt { quic_keep_alive(std::chrono::seconds duration) : duration{duration} {} }; - /// Can be used to disable Quic MTU discovery. + /// Caps the QUIC UDP payload size. Path MTU discovery still probes upward from 1200 but never + /// past this value. Must be at least 1200, the QUIC minimum. + struct quic_max_udp_payload { + size_t size; + explicit quic_max_udp_payload(size_t s) : size{s} {} + }; + + /// Equivalent to `quic_max_udp_payload{1200}`, which leaves path MTU discovery nothing to + /// probe. struct quic_disable_mtu_discovery {}; // MARK: Onion Request Router Options @@ -467,6 +475,7 @@ namespace opt { // Quic transport options quic_handshake_timeout, quic_keep_alive, + quic_max_udp_payload, quic_disable_mtu_discovery, // Onion request router options diff --git a/include/session/network/session_network.h b/include/session/network/session_network.h index 43451f18d..dff3a5025 100644 --- a/include/session/network/session_network.h +++ b/include/session/network/session_network.h @@ -94,7 +94,10 @@ typedef struct session_network_config { // Quic transport options (for transport == SESSION_NETWORK_TRANSPORT_QUIC) uint32_t quic_handshake_timeout_seconds; uint32_t quic_keep_alive_seconds; - bool quic_disable_mtu_discovery; + bool quic_disable_mtu_discovery; // same as quic_max_udp_payload = 1200 + /// Maximum QUIC UDP payload size; 0 for no cap. Must otherwise be at least 1200. Takes + /// precedence over quic_disable_mtu_discovery when non-zero. + size_t quic_max_udp_payload; } session_network_config; diff --git a/include/session/network/transport/quic_transport.hpp b/include/session/network/transport/quic_transport.hpp index 12f220b71..d0281f5ce 100644 --- a/include/session/network/transport/quic_transport.hpp +++ b/include/session/network/transport/quic_transport.hpp @@ -24,7 +24,7 @@ namespace config { std::chrono::milliseconds handshake_timeout; std::chrono::seconds keep_alive; - bool disable_mtu_discovery; + std::optional max_udp_payload; }; } // namespace config diff --git a/src/network/network_config.cpp b/src/network/network_config.cpp index e97aca9ea..392460c01 100644 --- a/src/network/network_config.cpp +++ b/src/network/network_config.cpp @@ -235,9 +235,18 @@ void Config::handle_config_opt(opt::quic_keep_alive qka) { log::debug(cat, "Network config quic keep alive set to {}s", qka.duration.count()); } +void Config::handle_config_opt(opt::quic_max_udp_payload qmup) { + // libquic rejects anything smaller, but only once the endpoint is built: catch it here, where + // it is still a configuration error rather than a failure deep inside network setup. + if (qmup.size < 1200) + throw std::invalid_argument{ + "quic_max_udp_payload must be at least 1200, got {}"_format(qmup.size)}; + quic_max_udp_payload = qmup.size; + log::debug(cat, "Network config max QUIC UDP payload set to {} bytes", qmup.size); +} + void Config::handle_config_opt(opt::quic_disable_mtu_discovery) { - quic_disable_mtu_discovery = true; - log::debug(cat, "Network config disabled MTU discovery for Quic"); + handle_config_opt(opt::quic_max_udp_payload{1200}); } // MARK: Onion Request Router Options diff --git a/src/network/session_network.cpp b/src/network/session_network.cpp index f56e690be..5d70f1ed3 100644 --- a/src/network/session_network.cpp +++ b/src/network/session_network.cpp @@ -81,7 +81,7 @@ namespace { config::QuicTransport build_quic_transport_config(const config::Config& main_config) { return {main_config.quic_handshake_timeout, main_config.quic_keep_alive, - main_config.quic_disable_mtu_discovery}; + main_config.quic_max_udp_payload}; } config::DirectRouter build_direct_router_config( @@ -1349,7 +1349,8 @@ LIBSESSION_C_API session_network_config session_network_config_default() { .count(); config.quic_keep_alive_seconds = std::chrono::duration_cast(cpp_defaults.quic_keep_alive).count(); - config.quic_disable_mtu_discovery = cpp_defaults.quic_disable_mtu_discovery; + config.quic_disable_mtu_discovery = false; + config.quic_max_udp_payload = cpp_defaults.quic_max_udp_payload.value_or(0); return config; } @@ -1536,7 +1537,9 @@ LIBSESSION_C_API bool session_network_init( cpp_opts.emplace_back(opt::quic_keep_alive{ std::chrono::seconds{config->quic_keep_alive_seconds}}); - if (config->quic_disable_mtu_discovery) + if (config->quic_max_udp_payload > 0) + cpp_opts.emplace_back(opt::quic_max_udp_payload{config->quic_max_udp_payload}); + else if (config->quic_disable_mtu_discovery) cpp_opts.emplace_back(opt::quic_disable_mtu_discovery{}); break; diff --git a/src/network/transport/quic_transport.cpp b/src/network/transport/quic_transport.cpp index 2ccc40320..16c73c813 100644 --- a/src/network/transport/quic_transport.cpp +++ b/src/network/transport/quic_transport.cpp @@ -147,15 +147,12 @@ void QuicTransport::send_request(Request request, network_response_callback_t ca void QuicTransport::_recreate_endpoint() { // The optional must CONTAIN the option to have any effect: libquic's optional-taking // handle_ep_opt overload does nothing when the optional is empty, so a default-constructed - // std::optional{} silently leaves discovery enabled. - // - // Capping the payload at the QUIC minimum leaves path MTU discovery nothing to probe, which is - // how "disable" is expressed to libquic. + // std::optional{} silently leaves discovery uncapped. _endpoint = quic::Endpoint::endpoint( *_loop, quic::Address{}, - (_config.disable_mtu_discovery - ? std::make_optional(quic::opt::max_udp_payload::minimum()) + (_config.max_udp_payload + ? std::make_optional(*_config.max_udp_payload) : std::nullopt)); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4b93aa096..d06b99f31 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,6 +41,7 @@ set(LIB_SESSION_UTESTS_SOURCES ) if(ENABLE_NETWORKING) + list(APPEND LIB_SESSION_UTESTS_SOURCES test_network_config.cpp) list(APPEND LIB_SESSION_UTESTS_SOURCES test_network_swarm.cpp) list(APPEND LIB_SESSION_UTESTS_SOURCES test_onionreq.cpp) list(APPEND LIB_SESSION_UTESTS_SOURCES test_onion_request_router.cpp) diff --git a/tests/test_network_config.cpp b/tests/test_network_config.cpp new file mode 100644 index 000000000..dc60c9347 --- /dev/null +++ b/tests/test_network_config.cpp @@ -0,0 +1,32 @@ +#include + +#include +#include + +using namespace session::network; + +TEST_CASE("Network config: QUIC UDP payload cap", "[network][config][quic]") { + SECTION("unset by default") { + config::Config cfg{opt::quic_keep_alive{std::chrono::seconds{10}}}; + CHECK_FALSE(cfg.quic_max_udp_payload.has_value()); + } + SECTION("explicit cap") { + config::Config cfg{opt::quic_max_udp_payload{1400}}; + REQUIRE(cfg.quic_max_udp_payload.has_value()); + CHECK(*cfg.quic_max_udp_payload == 1400); + } + SECTION("disable_mtu_discovery is a 1200-byte cap") { + config::Config cfg{opt::quic_disable_mtu_discovery{}}; + REQUIRE(cfg.quic_max_udp_payload.has_value()); + CHECK(*cfg.quic_max_udp_payload == 1200); + } + SECTION("below the QUIC minimum is rejected") { + CHECK_THROWS_AS(config::Config{opt::quic_max_udp_payload{1199}}, std::invalid_argument); + CHECK_NOTHROW(config::Config{opt::quic_max_udp_payload{1200}}); + } + SECTION("C defaults leave it uncapped") { + auto c = session_network_config_default(); + CHECK(c.quic_max_udp_payload == 0); + CHECK_FALSE(c.quic_disable_mtu_discovery); + } +} diff --git a/tests/test_quic_transport.cpp b/tests/test_quic_transport.cpp index 52464e236..33fcde945 100644 --- a/tests/test_quic_transport.cpp +++ b/tests/test_quic_transport.cpp @@ -10,7 +10,7 @@ namespace { std::shared_ptr make_transport(std::shared_ptr loop) { return std::make_shared( config::QuicTransport{ - .handshake_timeout = 1s, .keep_alive = 10s, .disable_mtu_discovery = false}, + .handshake_timeout = 1s, .keep_alive = 10s, .max_udp_payload = std::nullopt}, std::move(loop)); }