pull/2141/merge
jeff (codeaholic) 1 year ago committed by GitHub
commit 775c471efd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -315,6 +315,34 @@ local mac_builder(name,
],
};
// iphone builder
local iphone_builder(name,
build_type='Release',
werror=true,
cmake_extra='',
local_mirror=true,
extra_cmds=[],
jobs=6,
allow_fail=false) = {
kind: 'pipeline',
type: 'exec',
name: name,
platform: { os: 'darwin', arch: 'amd64' },
steps: [
{ name: 'submodules', commands: submodule_commands },
{
name: 'build',
environment: { SSH_KEY: { from_secret: 'SSH_KEY' } },
commands: [
'echo "Building on ${DRONE_STAGE_MACHINE}"',
'export CMAKE_BUILD_PARALLEL_LEVEL=6',
'ulimit -n 1024', // because macos sets ulimit to 256 for some reason yeah idk
'./contrib/ios.sh ' + ci_dep_mirror(local_mirror),
] + extra_cmds,
},
],
};
local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = {
kind: 'pipeline',
type: 'docker',
@ -438,6 +466,11 @@ local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = {
deb_builder(docker_base + 'ubuntu-jammy-builder', 'jammy', 'ubuntu/jammy'),
deb_builder(docker_base + 'debian-sid-builder', 'sid', 'debian/sid', arch='arm64'),
// iphone builds:
iphone_builder('iOS (embedded lokinet)', build_type='Debug', extra_cmds=[
'UPLOAD_OS=iphone ./contrib/ci/drone-static-upload.sh',
]),
// Macos builds:
mac_builder('macOS (Release)', extra_cmds=[
'./contrib/ci/drone-check-static-libs.sh',

1
.gitignore vendored

@ -4,6 +4,7 @@
*.a
*.o
*.so
*.DS_Store
/build*/
**/__pycache__/**

6
.gitmodules vendored

@ -39,3 +39,9 @@
[submodule "external/CLI11"]
path = external/CLI11
url = https://github.com/CLIUtils/CLI11.git
[submodule "external/ios-cmake"]
path = external/ios-cmake
url = https://github.com/leetal/ios-cmake.git
[submodule "external/libuv"]
path = external/libuv
url = https://github.com/libuv/libuv.git

@ -50,6 +50,7 @@ option(USE_AVX2 "enable avx2 code" OFF)
option(USE_NETNS "enable networking namespace support. Linux only" OFF)
option(NATIVE_BUILD "optimise for host system and FPU" ON)
option(WITH_EMBEDDED_LOKINET "build liblokinet.so for embedded lokinet" OFF)
option(LIBLOKINET_TEST_UTILS "build test utils in contrib/liblokinet" OFF)
option(XSAN "use sanitiser, if your system has it (requires -DCMAKE_BUILD_TYPE=Debug)" OFF)
option(USE_JEMALLOC "Link to jemalloc for memory allocations, if found" ON)
option(TESTNET "testnet build" OFF)
@ -75,6 +76,11 @@ option(STATIC_LINK "link statically against dependencies" ${BUILD_STATIC_DEPS})
if(BUILD_STATIC_DEPS AND NOT STATIC_LINK)
message(FATAL_ERROR "Option BUILD_STATIC_DEPS requires STATIC_LINK to be enabled as well")
endif()
if (BUILD_STATIC_DEPS OR WITH_EMBEDDED_LOKINET)
include(cmake/combine_archives.cmake)
endif()
if(BUILD_STATIC_DEPS)
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
include(StaticBuild)
@ -119,7 +125,11 @@ include(cmake/gui-option.cmake)
include(cmake/solaris.cmake)
include(cmake/win32.cmake)
include(cmake/macos.cmake)
option(APPLE_IOS "Set if building for iOS" OFF)
if (APPLE AND (NOT APPLE_IOS))
include(cmake/macos.cmake)
endif()
# No in-source building
include(MacroEnsureOutOfSourceBuild)
@ -213,20 +223,28 @@ if (WOW64_CROSS_COMPILE OR WIN64_CROSS_COMPILE)
include(cmake/cross_compile.cmake)
endif()
if(NOT APPLE)
if(NATIVE_BUILD)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL ppc64le)
add_compile_options(-mcpu=native -mtune=native)
else()
add_compile_options(-march=native -mtune=native)
endif()
elseif(NOT NON_PC_TARGET)
if (USE_AVX2)
add_compile_options(-march=haswell -mtune=haswell -mfpmath=sse)
else()
# Public binary releases
add_compile_options(-march=nocona -mtune=haswell -mfpmath=sse)
endif()
if(APPLE)
if(IOS AND ARCH STREQUAL "arm64")
message(STATUS "IOS: Changing arch from arm64 to armv8")
add_compile_options(-march=armv8)
elseif(IOS AND ARCH STREQUAL "x86_64")
message(STATUS "IOS: Changing arch from x86_64 to x86-64")
add_compile_options(-march=x86-64)
endif()
endif()
if(NATIVE_BUILD)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL ppc64le)
add_compile_options(-mcpu=native -mtune=native)
else()
add_compile_options(-march=native -mtune=native)
endif()
elseif(NOT NON_PC_TARGET)
if (USE_AVX2)
add_compile_options(-march=haswell -mtune=haswell -mfpmath=sse)
else()
# Public binary releases
add_compile_options(-march=nocona -mtune=haswell -mfpmath=sse)
endif()
endif()
@ -313,11 +331,15 @@ if(ANDROID)
add_subdirectory(jni)
endif()
if(WITH_EMBEDDED_LOKINET AND LIBLOKINET_TEST_UTILS)
add_subdirectory(contrib/liblokinet)
endif()
add_subdirectory(docs)
include(cmake/gui.cmake)
if(APPLE)
if(APPLE AND (NOT APPLE_IOS))
macos_target_setup()
endif()

@ -48,13 +48,6 @@ set(ZMQ_SOURCE zeromq-${ZMQ_VERSION}.tar.gz)
set(ZMQ_HASH SHA512=e198ef9f82d392754caadd547537666d4fba0afd7d027749b3adae450516bcf284d241d4616cad3cb4ad9af8c10373d456de92dc6d115b037941659f141e7c0e
CACHE STRING "libzmq source hash")
set(LIBUV_VERSION 1.44.2 CACHE STRING "libuv version")
set(LIBUV_MIRROR ${LOCAL_MIRROR} https://dist.libuv.org/dist/v${LIBUV_VERSION}
CACHE STRING "libuv mirror(s)")
set(LIBUV_SOURCE libuv-v${LIBUV_VERSION}.tar.gz)
set(LIBUV_HASH SHA512=91197ff9303112567bbb915bbb88058050e2ad1c048815a3b57c054635d5dc7df458b956089d785475290132236cb0edcfae830f5d749de29a9a3213eeaf0b20
CACHE STRING "libuv source hash")
set(ZLIB_VERSION 1.2.13 CACHE STRING "zlib version")
set(ZLIB_MIRROR ${LOCAL_MIRROR} https://zlib.net
CACHE STRING "zlib mirror(s)")
@ -109,9 +102,18 @@ endfunction()
set(cross_host "")
set(cross_rc "")
if(CMAKE_CROSSCOMPILING)
set(cross_host "--host=${ARCH_TRIPLET}")
if (ARCH_TRIPLET MATCHES mingw AND CMAKE_RC_COMPILER)
set(cross_rc "WINDRES=${CMAKE_RC_COMPILER}")
if(APPLE_TARGET_TRIPLE)
if(PLATFORM MATCHES "OS64" OR PLATFORM MATCHES "SIMULATORARM64")
set(APPLE_TARGET_TRIPLE aarch64-apple-ios)
elseif(PLATFORM MATCHES "SIMULATOR64")
set(APPLE_TARGET_TRIPLE x86_64-apple-ios)
endif()
set(cross_host "--host=${APPLE_TARGET_TRIPLE}")
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(ANDROID)
@ -162,11 +164,6 @@ if(WITH_LTO)
set(deps_CFLAGS "${deps_CFLAGS} -flto")
endif()
if(APPLE)
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()
if(_winver)
set(deps_CFLAGS "${deps_CFLAGS} -D_WIN32_WINNT=${_winver}")
set(deps_CXXFLAGS "${deps_CXXFLAGS} -D_WIN32_WINNT=${_winver}")
@ -179,6 +176,15 @@ else()
set(_make make)
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}")
set(deps_noarch_${lang}FLAGS "${deps_${lang}FLAGS}")
foreach(arch ${CMAKE_OSX_ARCHITECTURES})
string(APPEND deps_${lang}FLAGS " -arch ${arch}")
endforeach()
endforeach()
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
@ -219,15 +225,6 @@ function(build_external target)
)
endfunction()
build_external(libuv
CONFIGURE_COMMAND ./autogen.sh && ./configure ${cross_host} ${cross_rc} --prefix=${DEPS_DESTDIR} --with-pic --disable-shared --enable-static "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS}"
BUILD_BYPRODUCTS
${DEPS_DESTDIR}/lib/libuv.a
${DEPS_DESTDIR}/include/uv.h
)
add_static_target(libuv libuv_external libuv.a)
target_link_libraries(libuv INTERFACE ${CMAKE_DL_LIBS})
build_external(zlib
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS} -fPIC" ${cross_extra} ./configure --prefix=${DEPS_DESTDIR} --static
@ -242,7 +239,7 @@ set(openssl_system_env "")
set(openssl_arch "")
set(openssl_configure_command ./config)
set(openssl_flags "CFLAGS=${deps_CFLAGS}")
set(unbound_ldflags "")
set(openssl_cc "${deps_cc}")
if(CMAKE_CROSSCOMPILING)
if(ARCH_TRIPLET STREQUAL x86_64-w64-mingw32)
set(openssl_arch mingw64)
@ -255,6 +252,15 @@ if(CMAKE_CROSSCOMPILING)
set(openssl_system_env LD=${deps_ld} RANLIB=${deps_ranlib} AR=${deps_ar} ANDROID_NDK_ROOT=${CMAKE_ANDROID_NDK} "PATH=${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin:$ENV{PATH}")
list(APPEND openssl_flags "CPPFLAGS=-D__ANDROID_API__=${ANDROID_API}")
set(openssl_extra_opts no-asm)
elseif(IOS)
get_filename_component(apple_toolchain "${CMAKE_C_COMPILER}" DIRECTORY)
get_filename_component(apple_sdk "${CMAKE_OSX_SYSROOT}" NAME)
if(NOT ${apple_toolchain} MATCHES Xcode OR NOT ${apple_sdk} MATCHES "iPhone(OS|Simulator)")
message(FATAL_ERROR "didn't find your toolchain and sdk correctly from ${CMAKE_C_COMPILER}/${CMAKE_OSX_SYSROOT}: found toolchain=${apple_toolchain}, sdk=${apple_sdk}")
endif()
set(openssl_system_env CROSS_COMPILE=${apple_toolchain}/ CROSS_TOP=${CMAKE_DEVELOPER_ROOT}/ CROSS_SDK=${apple_sdk}/)
set(openssl_configure_command ./Configure iphoneos-cross)
set(openssl_cc "clang")
elseif(ARCH_TRIPLET STREQUAL mips64-linux-gnuabi64)
set(openssl_arch linux-mips64)
elseif(ARCH_TRIPLET STREQUAL mips-linux-gnu)
@ -281,7 +287,7 @@ endif()
build_external(openssl
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env CC=${deps_cc} ${openssl_system_env} ${openssl_configure_command}
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env CC=${openssl_cc} ${openssl_system_env} ${openssl_configure_command}
--prefix=${DEPS_DESTDIR} --libdir=lib ${openssl_extra_opts}
no-shared no-capieng no-dso no-dtls1 no-ec_nistp_64_gcc_128 no-gost
no-md2 no-rc5 no-rdrand no-rfc3779 no-sctp no-ssl-trace no-ssl3
@ -311,6 +317,12 @@ build_external(expat
add_static_target(expat expat_external libexpat.a)
set(unbound_extra)
if(APPLE AND IOS)
set(unbound_extra CPP=cpp)
endif()
if(WIN32)
set(unbound_patch
PATCH_COMMAND ${PROJECT_SOURCE_DIR}/contrib/apply-patches.sh
@ -320,10 +332,10 @@ build_external(unbound
DEPENDS openssl_external expat_external
${unbound_patch}
CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} --prefix=${DEPS_DESTDIR} --disable-shared
--enable-static --with-libunbound-only --with-pic
--enable-static --with-libunbound-only --with-pic --disable-gost
--$<IF:$<BOOL:${WITH_LTO}>,enable,disable>-flto --with-ssl=${DEPS_DESTDIR}
--with-libexpat=${DEPS_DESTDIR}
"CC=${deps_cc}" "CFLAGS=${deps_CFLAGS}" "LDFLAGS=${unbound_ldflags}"
"CC=${deps_cc}" "CFLAGS=${deps_CFLAGS}" ${unbound_extra}
)
add_static_target(libunbound unbound_external libunbound.a)
if(NOT WIN32)
@ -340,7 +352,9 @@ add_static_target(sodium sodium_external libsodium.a)
if(WITH_PEERSTATS_BACKEND)
build_external(sqlite3)
build_external(sqlite3 CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} --prefix=${DEPS_DESTDIR} --enable-static --disable-shared --disable-readline --with-pic "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS}"
BUILD_COMMAND true
INSTALL_COMMAND make install-includeHEADERS install-libLTLIBRARIES)
add_static_target(sqlite3 sqlite3_external libsqlite3.a)
endif()
@ -359,10 +373,16 @@ if(CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw)
${PROJECT_SOURCE_DIR}/contrib/patches/libzmq-mingw-unistd.patch)
endif()
set(zmq_cross_host "${cross_host}")
if(IOS AND cross_host MATCHES "-ios$")
# zmq doesn't like "-ios" for the host, so replace it with -darwin
string(REGEX REPLACE "-ios$" "-darwin" zmq_cross_host ${cross_host})
endif()
build_external(zmq
DEPENDS sodium_external
${zmq_patch}
CONFIGURE_COMMAND ./configure ${cross_host} --prefix=${DEPS_DESTDIR} --enable-static --disable-shared
CONFIGURE_COMMAND ./configure ${zmq_cross_host} --prefix=${DEPS_DESTDIR} --enable-static --disable-shared
--disable-curve-keygen --enable-curve --disable-drafts --disable-libunwind --with-libsodium
--without-pgm --without-norm --without-vmci --without-docs --with-pic --disable-Werror --disable-libbsd ${zmq_extra}
"CC=${deps_cc}" "CXX=${deps_cxx}" "CFLAGS=${deps_CFLAGS} -fstack-protector" "CXXFLAGS=${deps_CXXFLAGS} -fstack-protector"

@ -0,0 +1,30 @@
function(combine_archives output_archive)
set(FULL_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/lib${output_archive}.a)
set(output_archive_dummy_file ${CMAKE_CURRENT_BINARY_DIR}/${output_archive}.dummy.cpp)
add_custom_command(OUTPUT ${output_archive_dummy_file}
COMMAND touch ${output_archive_dummy_file}
DEPENDS ${ARGN})
add_library(${output_archive} STATIC EXCLUDE_FROM_ALL ${output_archive_dummy_file})
if(NOT APPLE)
set(mri_file ${CMAKE_CURRENT_BINARY_DIR}/${output_archive}.mri)
set(mri_content "create ${FULL_OUTPUT_PATH}\n")
foreach(in_archive ${ARGN})
string(APPEND mri_content "addlib $<TARGET_FILE:${in_archive}>\n")
endforeach()
string(APPEND mri_content "save\nend\n")
file(GENERATE OUTPUT ${mri_file} CONTENT "${mri_content}")
add_custom_command(TARGET ${output_archive}
POST_BUILD
COMMAND ar -M < ${mri_file})
else()
set(merge_libs)
foreach(in_archive ${ARGN})
list(APPEND merge_libs $<TARGET_FILE:${in_archive}>)
endforeach()
add_custom_command(TARGET ${output_archive}
POST_BUILD
COMMAND /usr/bin/libtool -static -o ${FULL_OUTPUT_PATH} ${merge_libs})
endif()
endfunction(combine_archives)

@ -1,8 +1,3 @@
if(NOT APPLE)
return()
endif()
option(MACOS_SYSTEM_EXTENSION
"Build the network extension as a system extension rather than a plugin. This must be ON for non-app store release builds, and must be OFF for dev builds and Mac App Store distribution builds"
OFF)

@ -60,6 +60,9 @@ elif [ -e build-mac ]; then
archive="$base.tar.xz"
mv build-mac/Lokinet*/ "$base"
tar cJvf "$archive" "$base"
elif [ -e build/iphone/ ]; then
archive="$base.tar.xz"
mv build/iphone/*.tar.xz "$archive"
else
cp -av build/daemon/lokinet{,-vpn} "$base"
cp -av contrib/bootstrap/mainnet.signed "$base/bootstrap.signed"

@ -45,7 +45,7 @@ for arch in $archs ; do
-DSTATIC_LINK=ON \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTING=OFF \
-DBUILD_LIBLOKINET=OFF \
-DWITH_EMBEDDED_LOKINET=OFF \
-DWITH_TESTS=OFF \
-DNATIVE_BUILD=OFF \
-DSTATIC_LINK=ON \

@ -1,4 +1,3 @@
CLANG_FORMAT_DESIRED_VERSION=14
CLANG_FORMAT=$(command -v clang-format-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null)

@ -0,0 +1,85 @@
#!/bin/bash
#
# Builds embeded lokinet for iphone
set -e
set -x
if ! [ -f LICENSE ] || ! [ -d llarp ]; then
echo "You need to run this as ./contrib/ios.sh from the top-level lokinet project directory"
fi
root="$(readlink -f $(dirname $0)/../)"
build_dir="$root/build/iphone"
deviceArchs=(arm64)
devicePlat=(OS64)
simArchs=(arm64 x86_64)
simPlat=(SIMULATORARM64 SIMULATOR64)
for i in "${!deviceArchs[@]}"; do
./contrib/ios/ios-configure.sh "$build_dir/device/${deviceArchs[i]}" ${devicePlat[i]} $@
done
for i in "${!simArchs[@]}"; do
./contrib/ios/ios-configure.sh "$build_dir/sim/${simArchs[i]}" ${simPlat[i]} $@
done
for targ in ${deviceArchs[@]} ; do
./contrib/ios/ios-build.sh "$build_dir/device/$targ"
done
for targ in ${simArchs[@]} ; do
./contrib/ios/ios-build.sh "$build_dir/sim/$targ"
done
pkg_name="iphone_lokinet_embedded_$(date +%s)"
pkg_dir="$build_dir/$pkg_name"
# Combine and device/simulator libraries into a single file so XCode doesn't complain
function combineArchsIfNeeded() {
local group=$1
local destination=$2
shift; shift
local archs=("$@")
if [ ${#archs[@]} -gt 1 ]; then
local dirs=("${archs[@]/#/$build_dir/${group}/}")
local libs=("${dirs[@]/%//llarp/liblokinet-embedded.a}")
mkdir -p "$build_dir/$group/$destination/llarp"
lipo -create "${libs[@]}" -output "$build_dir/$group/${destination}/llarp/liblokinet-embedded.a"
fi
}
deviceArchsString="${deviceArchs[*]}"
joinedDeviceArchs="${deviceArchsString// /_}"
simArchsString="${simArchs[*]}"
joinedSimArchs="${simArchsString// /_}"
combineArchsIfNeeded "device" $joinedDeviceArchs ${deviceArchs[*]}
combineArchsIfNeeded "sim" $joinedSimArchs ${simArchs[*]}
# Create a '.xcframework' so XCode can deal with the different architectures
xcodebuild -create-xcframework \
-library "$build_dir/device/$joinedDeviceArchs/llarp/liblokinet-embedded.a" \
-library "$build_dir/sim/$joinedSimArchs/llarp/liblokinet-embedded.a" \
-output "$pkg_dir/libLokinet.xcframework"
# Copy the headers over
mkdir -p "$pkg_dir/libLokinet.xcframework/lokinet"
cp -a "$root"/include/lokinet{,/*}.h "$pkg_dir/libLokinet.xcframework/lokinet"
mv "$pkg_dir/libLokinet.xcframework/lokinet/lokinet.h" "$pkg_dir/libLokinet.xcframework/lokinet.h"
# The 'module.modulemap' is needed for XCode to be able to find the headers
echo -e 'module Lokinet {' > "$pkg_dir/libLokinet.xcframework/module.modulemap"
echo -e ' umbrella header "lokinet.h"' >> "$pkg_dir/libLokinet.xcframework/module.modulemap"
echo -e ' export *' >> "$pkg_dir/libLokinet.xcframework/module.modulemap"
echo -e '}' >> "$pkg_dir/libLokinet.xcframework/module.modulemap"
# Archive the result
cd "$build_dir"
tar cfv "$pkg_name.tar" $pkg_name
cd -
xz -T 0 "$build_dir/$pkg_name.tar"

@ -0,0 +1,7 @@
#!/bin/bash
#
# Build the shit for iphone
test -e $1 || ( echo "run ios-configure.sh first" ; exit 1 )
cmake --build $1 --target lokinet-embedded

@ -0,0 +1,44 @@
#!/bin/bash
#
# configure step for ios
set -x
root=$(readlink -f "$( dirname $0 )/../../")
unset SDKROOT
export SDKROOT="$(xcrun --sdk iphoneos --show-sdk-path)"
targ=$1
plat=$2
shift
shift
mkdir -p $targ
cmake \
-G Ninja \
-DCMAKE_TOOLCHAIN_FILE="$root/external/ios-cmake/ios.toolchain.cmake" -DPLATFORM=$plat -DDEPLOYMENT_TARGET=13 -DENABLE_VISIBILITY=ON -DENABLE_BITCODE=OFF \
-DAPPLE_IOS=ON \
-DWITH_PEERSTATS=OFF \
-DBUILD_STATIC_DEPS=ON \
-DBUILD_PACKAGE=OFF \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTING=OFF \
-DWITH_EMBEDDED_LOKINET=ON \
-DWITH_TESTS=OFF \
-DNATIVE_BUILD=OFF \
-DSTATIC_LINK=ON \
-DWITH_SYSTEMD=OFF \
-DWITH_BOOTSTRAP=ON \
-DBUILD_DAEMON=OFF \
-DFORCE_OXENMQ_SUBMODULE=ON \
-DFORCE_OXENC_SUBMODULE=ON \
-DFORCE_NLOHMANN_SUBMODULE=ON \
-DFORCE_LIBUV_SUBMODULE=ON \
-DSUBMODULE_CHECK=ON \
-DWITH_LTO=OFF \
-DCMAKE_CXX_FLAGS='-Oz' -DCMAKE_C_FLAGS='-Oz' \
-DCMAKE_BUILD_TYPE=Debug \
-S "$root" -B $targ \
$@

@ -0,0 +1,10 @@
our scientists have yet to reverse engineer the correct way to use the apple build process as dictated on the official apple documentation so we have a made a hack to make it work until that occurs.
the build process for embedded lokinet on iphone is as follows:
* obtain holy water, sprinkle onto keyboard and single button trackpad accordingly.
* run ./contrib/ios.sh
* after it runs and the proper number of goats have been offered to the slaughter you will get an xz's tarball in the build/iphone/ directory
additional cmake flags can be passed to ./contrib/ios.sh as command line arguments like so:
$ ./contrib/ios.sh -DYOLO_SWAG=ON

@ -1,10 +1,14 @@
cmake_minimum_required(VERSION 3.10)
#[[
project(udptest LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
add_executable(udptest udptest.cpp)
include_directories(../../include)
target_link_libraries(udptest PUBLIC lokinet)
]]
add_executable(tcp_listen
tcp_listen.cpp)
target_link_libraries(tcp_listen PUBLIC lokinet-embedded-api)
add_executable(tcp_connect
tcp_connect.cpp)
target_link_libraries(tcp_connect PUBLIC lokinet-embedded-api)

@ -0,0 +1,120 @@
// Test utility to bind a local tcp socket listener with liblokinet
#include <lokinet.h>
#include <llarp/util/logging.hpp>
#include <csignal>
#include <memory>
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
#include <chrono>
#include <thread>
bool run{true};
void
signal_handler(int)
{
run = false;
int a;
}
int
main(int argc, char* argv[])
{
if (argc < 3 || argc > 4)
{
std::cerr << "Usage: " << argv[0] << " something.{loki,snode} port [testnet]\n";
return 0;
}
std::string netid = "lokinet";
std::string data_dir = "./tcp_connect_data_dir";
if (argc == 4) // if testnet
{
netid = "gamma"; // testnet netid
data_dir += "/testnet";
}
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
if (auto* loglevel = getenv("LOKINET_LOG"))
lokinet_log_level(loglevel);
else
lokinet_log_level("info");
std::cerr << "starting up\n";
lokinet_set_netid(netid.c_str());
auto shared_ctx = std::shared_ptr<lokinet_context>(lokinet_context_new(), lokinet_context_free);
auto* ctx = shared_ctx.get();
lokinet_set_data_dir(data_dir.c_str(), ctx);
if (lokinet_context_start(ctx))
throw std::runtime_error{"could not start context"};
int status;
for (status = lokinet_status(ctx); run and status == -1; status = lokinet_status(ctx))
{
std::cerr << "waiting for lokinet to be ready..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds{500});
}
if (not run)
{
std::cerr << "exit requested before context was ready.\n";
return 0;
}
if (status != 0)
{
std::cerr << "lokinet_status = " << status << " after waiting for ready.\n";
return 0;
}
if (auto* loglevel = getenv("QUIC_LOG"))
llarp::log::set_level("quic", llarp::log::level_from_string(loglevel));
else
llarp::log::set_level("quic", llarp::log::Level::trace);
std::cerr << "\n\nquic log level: " << llarp::log::to_string(llarp::log::get_level("quic")) << "\n\n";
auto addr_c = lokinet_address(ctx);
std::string addr{addr_c};
free(addr_c);
std::cerr << "lokinet address: " << addr << "\n";
lokinet_tcp_result stream_res;
std::string target{argv[1]};
target = target + ":" + argv[2];
// hard-coded IP:port for liblokinet to bind to
// connect via tcp will stream traffic to whatever
lokinet_outbound_tcp(&stream_res, target.c_str(), "127.0.0.1:54321", ctx, nullptr, nullptr, nullptr);
if (stream_res.error)
{
std::cerr << "failed to prepare outbound tcp: " << strerror(stream_res.error) << "\n";
return 0;
}
do
{
std::this_thread::sleep_for(std::chrono::milliseconds{100});
} while (run);
std::cerr << "tcp_connect shutting down...\n";
lokinet_close_tcp(stream_res.tcp_id, ctx);
std::this_thread::sleep_for(std::chrono::milliseconds{1000});
return 0;
}

@ -0,0 +1,96 @@
// Test utility to bind a local tcp socket listener with liblokinet
#include <lokinet.h>
#include <csignal>
#include <memory>
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
#include <chrono>
#include <thread>
bool run{true};
void
signal_handler(int)
{
run = false;
}
int
main(int argc, char* argv[])
{
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
if (auto* loglevel = getenv("LOKINET_LOG"))
lokinet_log_level(loglevel);
else
lokinet_log_level("info");
std::cout << "starting up\n";
auto shared_ctx = std::shared_ptr<lokinet_context>(lokinet_context_new(), lokinet_context_free);
auto* ctx = shared_ctx.get();
if (lokinet_context_start(ctx))
throw std::runtime_error{"could not start context"};
int status;
for (status = lokinet_status(ctx); run and status == -1; status = lokinet_status(ctx))
{
std::cout << "waiting for lokinet to be ready..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds{500});
}
if (not run)
{
std::cout << "exit requested before context was ready.\n";
return 0;
}
if (status != 0)
{
std::cout << "lokinet_status = " << status << " after waiting for ready.\n";
return 0;
}
// wait a bit just so log output calms down so we can see stuff
// printed from here
std::this_thread::sleep_for(std::chrono::milliseconds{3000});
const auto port = 10000;
auto id = lokinet_inbound_tcp(port, ctx);
if (id < 0)
{
std::cout << "failed to bind tcp socket\n";
return 0;
}
std::cout << "bound tcp on localhost port: " << port << "\n";
auto addr_c = lokinet_address(ctx);
std::string addr{addr_c};
free(addr_c);
std::cout << "lokinet address: " << addr << "\n";
size_t counter = 0;
do
{
std::this_thread::sleep_for(std::chrono::milliseconds{100});
if (++counter % 30 == 0)
std::cout << "lokinet address: " << addr << "\n";
} while (run);
std::cout << "tcp_listen shutting down...\n";
lokinet_close_tcp(id, ctx);
return 0;
}

@ -14,6 +14,7 @@ signit() {
codesign \
--verbose=4 \
--force \
--deep \
-s "@CODESIGN_ID@" \
--entitlements "$entitlements" \
--strict \

@ -47,7 +47,7 @@ set(NTRU_AVX_SRC
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-mavx2 COMPILER_SUPPORTS_AVX2)
check_cxx_compiler_flag(-mfma COMPILER_SUPPORTS_FMA)
if(COMPILER_SUPPORTS_AVX2 AND COMPILER_SUPPORTS_FMA AND (NOT ANDROID))
if(COMPILER_SUPPORTS_AVX2 AND COMPILER_SUPPORTS_FMA AND (NOT (ANDROID OR IOS)))
target_sources(lokinet-cryptography PRIVATE ${NTRU_AVX_SRC})
set_property(SOURCE ${NTRU_AVX_SRC} APPEND PROPERTY COMPILE_FLAGS "-mavx2 -mfma")
message(STATUS "Building libntrup with runtime AVX2/FMA support")

@ -31,6 +31,7 @@ if(SUBMODULE_CHECK)
check_submodule(sqlite_orm)
check_submodule(oxen-mq)
check_submodule(oxen-encoding)
check_submodule(libuv)
check_submodule(uvw)
check_submodule(cpr)
check_submodule(ngtcp2)
@ -67,6 +68,8 @@ set(JSON_BuildTests OFF CACHE INTERNAL "")
set(JSON_Install OFF CACHE INTERNAL "")
system_or_submodule(NLOHMANN nlohmann_json nlohmann_json>=3.7.0 nlohmann)
system_or_submodule(LIBUV uv_a libuv>=1.44.2 libuv)
if (STATIC OR FORCE_SPDLOG_SUBMODULE OR FORCE_FMT_SUBMODULE)
set(OXEN_LOGGING_FORCE_SUBMODULES ON CACHE INTERNAL "")
endif()
@ -92,7 +95,7 @@ endif()
add_library(uvw INTERFACE)
target_include_directories(uvw INTERFACE uvw/src)
target_link_libraries(uvw INTERFACE libuv)
target_link_libraries(uvw INTERFACE uv_a::uv_a)
# ngtcp2 needs some massaging to build nicely:
include(ngtcp2_lib)

@ -0,0 +1 @@
Subproject commit ab8607a7f57970d99d590d420ecd8e2d7f9a9c77

1
external/libuv vendored

@ -0,0 +1 @@
Subproject commit 0c1fa696aa502eb749c2c4735005f41ba00a27b8

2
external/ngtcp2 vendored

@ -1 +1 @@
Subproject commit 026b8434ebcbeec48939d1c7671a0a4d5c75202b
Subproject commit 86206d86f185885da57a1b1fcc6518f267bf0052

@ -4,5 +4,5 @@
#include "lokinet/lokinet_srv.h"
#include "lokinet/lokinet_misc.h"
#include "lokinet/lokinet_addr.h"
#include "lokinet/lokinet_stream.h"
#include "lokinet/lokinet_tcp.h"
#include "lokinet/lokinet_udp.h"

@ -50,6 +50,12 @@ extern "C"
int EXPORT
lokinet_add_bootstrap_rc(const char*, size_t, struct lokinet_context*);
/// set data directory for saving/loading node db
/// does nothing if called after lokinet_context_start
/// TODO: use for saving config changes in-client?
void EXPORT
lokinet_set_data_dir(const char*, struct lokinet_context*);
#ifdef __cplusplus
}
#endif

@ -1,59 +0,0 @@
#pragma once
#include "lokinet_context.h"
#ifdef __cplusplus
extern "C"
{
#endif
/// the result of a lokinet stream mapping attempt
struct lokinet_stream_result
{
/// set to zero on success otherwise the error that happened
/// use strerror(3) to get printable string of this error
int error;
/// the local ip address we mapped the remote endpoint to
/// null terminated
char local_address[256];
/// the local port we mapped the remote endpoint to
int local_port;
/// the id of the stream we created
int stream_id;
};
/// connect out to a remote endpoint
/// remoteAddr is in the form of "name:port"
/// localAddr is either NULL for any or in the form of "ip:port" to bind to an explicit address
void EXPORT
lokinet_outbound_stream(
struct lokinet_stream_result* result,
const char* remoteAddr,
const char* localAddr,
struct lokinet_context* context);
/// stream accept filter determines if we should accept a stream or not
/// return 0 to accept
/// return -1 to explicitly reject
/// return -2 to silently drop
typedef int (*lokinet_stream_filter)(const char* remote, uint16_t port, void* userdata);
/// set stream accepter filter
/// passes user parameter into stream filter as void *
/// returns stream id
int EXPORT
lokinet_inbound_stream_filter(
lokinet_stream_filter acceptFilter, void* user, struct lokinet_context* context);
/// simple stream acceptor
/// simple variant of lokinet_inbound_stream_filter that maps port to localhost:port
int EXPORT
lokinet_inbound_stream(uint16_t port, struct lokinet_context* context);
void EXPORT
lokinet_close_stream(int stream_id, struct lokinet_context* context);
#ifdef __cplusplus
}
#endif

@ -0,0 +1,63 @@
#pragma once
#include "lokinet_context.h"
#ifdef __cplusplus
extern "C"
{
#endif
/// the result of a lokinet tcp mapping attempt
struct lokinet_tcp_result
{
/// set to zero on success otherwise the error that happened
/// use strerror(3) to get printable string of this error
int error;
/// set once the tcp connection is successfully established
bool success;
/// the local ip address we mapped the remote endpoint to
/// null terminated
char local_address[256];
/// the local port we mapped the remote endpoint to
int local_port;
/// the id (aka: 'pseudo-port') of the tcp we created
int tcp_id;
};
/// connect out to a remote endpoint
/// remoteAddr is in the form of "name:port"
/// localAddr is either NULL for any or in the form of "ip:port" to bind to an explicit address
void EXPORT
lokinet_outbound_tcp(
struct lokinet_tcp_result* result,
const char* remoteAddr,
const char* localAddr,
struct lokinet_context* context,
void (*open_cb)(bool success, void* user_data),
void (*close_cb)(int rv, void* user_data),
void* user_data);
/// tcp accept filter determines if we should accept a tcp or not
/// return 0 to accept
/// return -1 to explicitly reject
/// return -2 to silently drop
typedef int (*lokinet_tcp_filter)(const char* remote, uint16_t port, void* userdata);
/// set tcp accepter filter
/// passes user parameter into tcp filter as void *
/// returns tcp id
int EXPORT
lokinet_inbound_tcp_filter(
lokinet_tcp_filter acceptFilter, void* user, struct lokinet_context* context);
/// simple tcp acceptor
/// simple variant of lokinet_inbound_tcp_filter that maps port to localhost:port
int EXPORT
lokinet_inbound_tcp(uint16_t port, struct lokinet_context* context);
void EXPORT
lokinet_close_tcp(int tcp_id, struct lokinet_context* context);
#ifdef __cplusplus
}
#endif

@ -440,7 +440,7 @@ function(link_lokinet_layers)
lokinet_link_lib(${ARGV1} ${lib})
list(REMOVE_AT ARGV 1)
target_link_libraries(${lib} PRIVATE ${ARGV1})
# recursion :D
# recursion :D
link_lokinet_layers(${ARGV})
else()
lokinet_link_lib(${lib})
@ -520,18 +520,35 @@ target_link_libraries(lokinet-plainquic PUBLIC
if(WITH_EMBEDDED_LOKINET)
include(GNUInstallDirs)
add_library(lokinet-shared SHARED lokinet_shared.cpp)
target_link_libraries(lokinet-shared PUBLIC lokinet-amalgum)
if(WIN32)
set(CMAKE_SHARED_LIBRARY_PREFIX_CXX "")
add_library(lokinet-embedded-api lokinet_shared.cpp)
target_link_libraries(lokinet-embedded-api PUBLIC lokinet-amalgum)
if(BUILD_STATIC_DEPS)
get_target_property(embedded-libs lokinet-amalgum INTERFACE_LINK_LIBRARIES)
combine_archives(lokinet-embedded
${embedded-libs}
lokinet-embedded-api
ngtcp2_static
uv_a::uv_a
sodium
zlib
OpenSSL::SSL
OpenSSL::Crypto
expat
libunbound
libzmq
fmt::fmt
spdlog::spdlog
oxen::logging
oxenmq::oxenmq)
endif()
set_target_properties(lokinet-shared PROPERTIES OUTPUT_NAME lokinet)
if(WIN32)
target_link_libraries(lokinet-shared PUBLIC ws2_32 iphlpapi -fstack-protector)
install(TARGETS lokinet-shared DESTINATION bin COMPONENT liblokinet)
elseif(NOT APPLE)
install(TARGETS lokinet-shared LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT liblokinet)
set(CMAKE_SHARED_LIBRARY_PREFIX_CXX "")
target_link_libraries(lokinet-embedded-api PUBLIC ws2_32 iphlpapi -fstack-protector)
endif()
install(TARGETS lokinet-embedded-api DESTINATION bin COMPONENT liblokinet)
endif()
file(GLOB_RECURSE docs_SRC */*.hpp *.hpp)

@ -1654,7 +1654,8 @@ namespace llarp
{
auto config = std::make_shared<Config>();
config->Load();
config->logging.m_logLevel = log::Level::off;
config->logging.m_logLevel = log::Level::debug;
config->logging.m_logFile = "stderr";
config->api.m_enableRPCServer = false;
config->network.m_endpointType = "null";
config->network.m_saveProfiles = false;

@ -122,7 +122,7 @@ namespace llarp
virtual bool
EnsurePathTo(
AddressVariant_t addr,
std::function<void(std::optional<service::ConvoTag>)> hook,
std::function<void(std::optional<std::variant<service::Address, RouterID>>)> hook,
llarp_time_t timeout) = 0;
virtual void
@ -134,7 +134,9 @@ namespace llarp
virtual bool
SendToOrQueue(
service::ConvoTag tag, const llarp_buffer_t& payload, service::ProtocolType t) = 0;
std::variant<service::Address, RouterID> addr,
const llarp_buffer_t& payload,
service::ProtocolType t) = 0;
/// lookup srv records async
virtual void

@ -119,7 +119,15 @@ namespace llarp
if (not quic)
return false;
m_TxRate += buf.size();
quic->receive_packet(tag, std::move(buf));
std::variant<service::Address, RouterID> addr;
if (auto maybe = m_Parent->GetEndpointWithConvoTag(tag))
addr = *maybe;
else
return false;
quic->receive_packet(std::move(addr), std::move(buf));
m_LastActive = m_Parent->Now();
return true;
}

@ -207,7 +207,15 @@ namespace llarp
auto quic = m_Parent->GetQUICTunnel();
if (not quic)
return false;
quic->receive_packet(tag, buf);
std::variant<service::Address, RouterID> addr;
if (auto maybe = m_Parent->GetEndpointWithConvoTag(tag))
addr = *maybe;
else
return false;
quic->receive_packet(std::move(addr), buf);
return true;
}

@ -97,42 +97,40 @@ namespace llarp
bool
ExitEndpoint::SendToOrQueue(
service::ConvoTag tag, const llarp_buffer_t& payload, service::ProtocolType type)
std::variant<service::Address, RouterID> addr,
const llarp_buffer_t& payload,
service::ProtocolType type)
{
if (auto maybeAddr = GetEndpointWithConvoTag(tag))
if (std::holds_alternative<service::Address>(addr))
return false;
if (auto* rid = std::get_if<RouterID>(&addr))
{
if (std::holds_alternative<service::Address>(*maybeAddr))
return false;
if (auto* rid = std::get_if<RouterID>(&*maybeAddr))
for (auto [itr, end] = m_ActiveExits.equal_range(PubKey{*rid}); itr != end; ++itr)
{
for (auto [itr, end] = m_ActiveExits.equal_range(PubKey{*rid}); itr != end; ++itr)
if (not itr->second->LooksDead(Now()))
{
if (not itr->second->LooksDead(Now()))
{
if (itr->second->QueueInboundTraffic(payload.copy(), type))
return true;
}
if (itr->second->QueueInboundTraffic(payload.copy(), type))
return true;
}
}
if (not m_Router->PathToRouterAllowed(*rid))
return false;
if (not m_Router->PathToRouterAllowed(*rid))
return false;
ObtainSNodeSession(*rid, [pkt = payload.copy(), type](auto session) mutable {
if (session and session->IsReady())
{
session->SendPacketToRemote(std::move(pkt), type);
}
});
}
return true;
ObtainSNodeSession(*rid, [pkt = payload.copy(), type](auto session) mutable {
if (session and session->IsReady())
{
session->SendPacketToRemote(std::move(pkt), type);
}
});
}
return false;
return true;
}
bool
ExitEndpoint::EnsurePathTo(
AddressVariant_t addr,
std::function<void(std::optional<service::ConvoTag>)> hook,
std::function<void(std::optional<std::variant<service::Address, RouterID>>)> hook,
llarp_time_t)
{
if (std::holds_alternative<service::Address>(addr))
@ -142,15 +140,11 @@ namespace llarp
if (m_SNodeKeys.count(PubKey{*rid}) or m_Router->PathToRouterAllowed(*rid))
{
ObtainSNodeSession(
*rid, [hook, routerID = *rid](std::shared_ptr<exit::BaseSession> session) {
if (session and session->IsReady())
*rid, [hook, routerID = *rid, this](std::shared_ptr<exit::BaseSession> session) {
if (session and session->IsReady(); auto path = session->GetPathByRouter(routerID))
{
if (auto path = session->GetPathByRouter(routerID))
{
hook(service::ConvoTag{path->RXID().as_array()});
}
else
hook(std::nullopt);
auto tag = service::ConvoTag{path->RXID().as_array()};
hook(GetEndpointWithConvoTag(tag));
}
else
hook(std::nullopt);
@ -159,7 +153,13 @@ namespace llarp
else
{
// probably a client
hook(GetBestConvoTagFor(addr));
if (auto maybe_tag = GetBestConvoTagFor(addr);
auto maybe_addr = GetEndpointWithConvoTag(*maybe_tag))
{
hook(maybe_addr);
}
else
hook(std::nullopt);
}
}
return true;

@ -43,7 +43,7 @@ namespace llarp
bool
EnsurePathTo(
AddressVariant_t addr,
std::function<void(std::optional<service::ConvoTag>)> hook,
std::function<void(std::optional<std::variant<service::Address, RouterID>>)> hook,
llarp_time_t timeout) override;
void
@ -64,7 +64,9 @@ namespace llarp
bool
SendToOrQueue(
service::ConvoTag tag, const llarp_buffer_t& payload, service::ProtocolType t) override;
std::variant<service::Address, RouterID> addr,
const llarp_buffer_t& payload,
service::ProtocolType t) override;
void
Tick(llarp_time_t now);

@ -70,7 +70,14 @@ namespace llarp::handlers
LogWarn("invalid incoming quic packet, dropping");
return false;
}
quic->receive_packet(tag, buf);
std::variant<service::Address, RouterID> addr;
if (auto maybe = GetEndpointWithConvoTag(tag))
addr = *maybe;
else
return false;
quic->receive_packet(std::move(addr), buf);
return true;
}

@ -1255,9 +1255,10 @@ namespace llarp
}
// try sending it on an existing convotag
// this succeds for inbound convos, probably.
if (auto maybe = GetBestConvoTagFor(to))
if (auto maybe_tag = GetBestConvoTagFor(to);
auto maybe_addr = GetEndpointWithConvoTag(*maybe_tag))
{
if (SendToOrQueue(*maybe, pkt.ConstBuffer(), type))
if (SendToOrQueue(*maybe_addr, pkt.ConstBuffer(), type))
{
MarkIPActive(dst);
Router()->TriggerPump();
@ -1314,6 +1315,14 @@ namespace llarp
uint64_t seqno)
{
LogTrace("Inbound ", t, " packet (", buf.sz, "B) on convo ", tag);
std::variant<service::Address, RouterID> addr;
if (auto maybe = GetEndpointWithConvoTag(tag))
{
addr = *maybe;
}
else
return false;
if (t == service::ProtocolType::QUIC)
{
auto* quic = GetQUICTunnel();
@ -1327,21 +1336,15 @@ namespace llarp
LogWarn("invalid incoming quic packet, dropping");
return false;
}
LogInfo("tag active T=", tag);
quic->receive_packet(tag, buf);
log::trace(logcat, "tag active T={}", tag);
quic->receive_packet(std::move(addr), buf);
return true;
}
if (t != service::ProtocolType::TrafficV4 && t != service::ProtocolType::TrafficV6
&& t != service::ProtocolType::Exit)
return false;
std::variant<service::Address, RouterID> addr;
if (auto maybe = GetEndpointWithConvoTag(tag))
{
addr = *maybe;
}
else
return false;
huint128_t src, dst;
net::IPPacket pkt;

@ -6,11 +6,14 @@
#include <llarp/router/abstractrouter.hpp>
#include <queue>
#include "oxen/log.hpp"
namespace llarp
{
namespace iwp
{
static auto logcat = log::Cat("endpoint");
ILinkSession::Packet_t
CreatePacket(Command cmd, size_t plainsize, size_t minpad, size_t variance)
{
@ -141,6 +144,8 @@ namespace llarp
void
Session::EncryptAndSend(ILinkSession::Packet_t data)
{
if (m_State == State::Closed)
return;
m_EncryptNext.emplace_back(std::move(data));
TriggerPump();
if (!IsEstablished())
@ -176,12 +181,9 @@ namespace llarp
return;
auto close_msg = CreatePacket(Command::eCLOS, 0, 16, 16);
m_Parent->UnmapAddr(m_RemoteAddr);
m_State = State::Closed;
if (m_SentClosed.test_and_set())
return;
EncryptAndSend(std::move(close_msg));
LogInfo(m_Parent->PrintableName(), " closing connection to ", m_RemoteAddr);
m_State = State::Closed;
}
bool
@ -352,7 +354,7 @@ namespace llarp
bool
Session::TimedOut(llarp_time_t now) const
{
if (m_State == State::Ready)
if (m_State == State::Ready || m_State == State::LinkIntro)
{
return now > m_LastRX
&& now - m_LastRX
@ -588,7 +590,7 @@ namespace llarp
{
if (pkt.size() <= PacketOverhead)
{
LogError("packet too small from ", m_RemoteAddr);
log::error(logcat, "Packet too small from {}", m_RemoteAddr);
return false;
}
const llarp_buffer_t buf(pkt);
@ -598,30 +600,27 @@ namespace llarp
curbuf.sz -= ShortHash::SIZE;
if (not CryptoManager::instance()->hmac(H.data(), curbuf, m_SessionKey))
{
LogError("failed to caclulate keyed hash for ", m_RemoteAddr);
log::error(logcat, "Failed to calculate keyed hash for {}", m_RemoteAddr);
return false;
}
const ShortHash expected{buf.base};
if (H != expected)
{
LogDebug(
log::debug(
logcat,
"{} keyed hash mismatch {} != {} from {} state={} size={}",
m_Parent->PrintableName(),
" keyed hash mismatch ",
H,
" != ",
expected,
" from ",
m_RemoteAddr,
" state=",
int(m_State),
" size=",
buf.sz);
return false;
}
const TunnelNonce N{curbuf.base};
curbuf.base += 32;
curbuf.sz -= 32;
LogTrace("decrypt: ", curbuf.sz, " bytes from ", m_RemoteAddr);
log::trace(logcat, "Decrypt: {} bytes from {}", curbuf.sz, m_RemoteAddr);
return CryptoManager::instance()->xchacha20(curbuf, m_SessionKey, N);
}

@ -206,7 +206,6 @@ namespace llarp
std::atomic_flag m_PlaintextEmpty;
llarp::thread::Queue<CryptoQueue_t> m_PlaintextRecv;
std::atomic_flag m_SentClosed;
void
EncryptWorker(CryptoQueue_t msgs);

@ -1,4 +1,7 @@
#include <lokinet.h>
#include <algorithm>
#include <exception>
#include <filesystem>
#include <llarp.hpp>
#include <llarp/config/config.hpp>
#include <llarp/crypto/crypto_libsodium.hpp>
@ -18,6 +21,8 @@
#include <memory>
#include <chrono>
#include <stdexcept>
#include <unordered_map>
#include "lokinet/lokinet_tcp.h"
#ifdef _WIN32
#define EHOSTDOWN ENETDOWN
@ -25,6 +30,8 @@
namespace
{
static auto logcat = llarp::log::Cat("liblokinet");
struct Context : public llarp::Context
{
using llarp::Context::Context;
@ -32,7 +39,7 @@ namespace
std::shared_ptr<llarp::NodeDB>
makeNodeDB() override
{
return std::make_shared<llarp::NodeDB>();
return llarp::Context::makeNodeDB();
}
};
@ -308,41 +315,47 @@ struct lokinet_context
return impl->router->hiddenServiceContext().GetEndpointByName(name);
}
std::unordered_map<int, bool> streams;
/// false: outbound connection
/// true: inbound connection
std::unordered_map<int, bool> tcp_conns;
/// maps address to pair of (stream_id, ready)
std::unordered_map<std::string, lokinet_tcp_result> active_conns;
std::unordered_map<int, std::shared_ptr<UDPHandler>> udp_sockets;
void
inbound_stream(int id)
inbound_tcp(int id)
{
streams[id] = true;
tcp_conns[id] = true;
}
void
outbound_stream(int id)
outbound_tcp(std::string remote_addr, lokinet_tcp_result& res)
{
streams[id] = false;
tcp_conns[res.tcp_id] = false;
active_conns[remote_addr] = lokinet_tcp_result{res};
}
};
namespace
{
void
stream_error(lokinet_stream_result* result, int err)
tcp_error(lokinet_tcp_result* result, int err)
{
std::memset(result, 0, sizeof(lokinet_stream_result));
*result = lokinet_tcp_result{};
result->error = err;
}
void
stream_okay(lokinet_stream_result* result, std::string host, int port, int stream_id)
tcp_okay(lokinet_tcp_result* result, std::string host, int port, int tcp_id)
{
stream_error(result, 0);
tcp_error(result, 0);
std::copy_n(
host.c_str(),
std::min(host.size(), sizeof(result->local_address) - 1),
result->local_address);
result->local_port = port;
result->stream_id = stream_id;
result->tcp_id = tcp_id;
}
std::pair<std::string, int>
@ -355,7 +368,7 @@ namespace
portStr = data.substr(pos + 1);
}
else
throw EINVAL;
throw std::invalid_argument("Error: invalid address passed");
if (auto* serv = getservbyname(portStr.c_str(), proto.c_str()))
{
@ -395,7 +408,6 @@ struct lokinet_srv_lookup_private
{
std::promise<int> promise;
{
auto lock = ctx->acquire();
if (ctx->impl and ctx->impl->IsUp())
{
ctx->impl->CallSafe([host, service, &promise, ctx, this]() {
@ -483,6 +495,8 @@ extern "C"
int EXPORT
lokinet_add_bootstrap_rc(const char* data, size_t datalen, struct lokinet_context* ctx)
{
// FIXME: bootstrap loading was rewritten but this code needs updated to do
// it how Router does now.
if (data == nullptr or datalen == 0)
return -3;
llarp_buffer_t buf{data, datalen};
@ -621,138 +635,199 @@ extern "C"
}
void EXPORT
lokinet_outbound_stream(
struct lokinet_stream_result* result,
lokinet_set_data_dir(const char* path, struct lokinet_context* ctx)
{
fs::path dir{path};
dir = fs::canonical(dir);
fs::current_path(dir);
if (not ctx)
return;
auto lock = ctx->acquire();
if (ctx->impl->IsUp() or ctx->impl->IsStopping())
return;
ctx->config->router.m_dataDir = dir;
}
void EXPORT
lokinet_outbound_tcp(
struct lokinet_tcp_result* result,
const char* remote,
const char* local,
struct lokinet_context* ctx)
struct lokinet_context* ctx,
void (*open_cb)(bool success, void* user_data),
void (*close_cb)(int rv, void* user_data),
void* user_data)
{
if (ctx == nullptr)
{
stream_error(result, EHOSTDOWN);
tcp_error(result, EHOSTDOWN);
return;
}
std::promise<void> promise;
{
auto lock = ctx->acquire();
if (not ctx->impl->IsUp())
if (auto itr = ctx->active_conns.find(remote); itr != ctx->active_conns.end())
{
stream_error(result, EHOSTDOWN);
*result = lokinet_tcp_result{itr->second};
llarp::LogError("Active connection to {} already exists", remote);
return;
}
std::string remotehost;
int remoteport;
try
}
if (not ctx->impl->IsUp())
{
tcp_error(result, EHOSTDOWN);
return;
}
std::string remotehost;
int remoteport;
try
{
auto [h, p] = split_host_port(remote);
remotehost = h;
remoteport = p;
}
catch (std::exception& e)
{
llarp::log::error(logcat, "Error: exception caught: {}", e.what());
tcp_error(result, EINVAL);
return;
}
// TODO: make configurable (?)
// FIXME: appears unused?
std::string endpoint{"default"};
llarp::SockAddr localAddr;
try
{
if (local)
localAddr = llarp::SockAddr{std::string{local}};
else
localAddr = llarp::SockAddr{"127.0.0.1:0"};
}
catch (std::exception& ex)
{
tcp_error(result, EINVAL);
return;
}
auto on_open = [ctx, localAddr, remote, open_cb](bool success, void* user_data) {
llarp::log::info(
logcat,
"Quic tunnel {}<->{}.",
localAddr,
remote,
success ? "opened successfully" : "failed");
auto lock = ctx->acquire();
if (auto conn = ctx->active_conns.find(remote); conn != ctx->active_conns.end())
{
auto [h, p] = split_host_port(remote);
remotehost = h;
remoteport = p;
if (success)
conn->second.success = success;
else
ctx->active_conns.erase(remote);
}
catch (int err)
if (open_cb)
open_cb(success, user_data);
};
auto on_close = [ctx, localAddr, remote, close_cb](int rv, void* user_data) {
llarp::log::info(logcat, "Quic tunnel {}<->{} closed.", localAddr, remote);
{
stream_error(result, err);
return;
auto lock = ctx->acquire();
ctx->active_conns.erase(remote);
}
// TODO: make configurable (?)
std::string endpoint{"default"};
llarp::SockAddr localAddr;
if (close_cb)
close_cb(rv, user_data);
};
std::promise<void> promise;
std::future<void> future = promise.get_future();
ctx->impl->CallSafe([&promise,
ctx,
result,
router = ctx->impl->router,
remotehost,
remoteport,
on_open = std::move(on_open),
on_close = std::move(on_close),
localAddr]() mutable {
try
{
if (local)
localAddr = llarp::SockAddr{std::string{local}};
else
localAddr = llarp::SockAddr{"127.0.0.1:0"};
}
catch (std::exception& ex)
{
stream_error(result, EINVAL);
return;
}
auto call = [&promise,
ctx,
result,
router = ctx->impl->router,
remotehost,
remoteport,
endpoint,
localAddr]() {
auto ep = ctx->endpoint();
if (ep == nullptr)
{
stream_error(result, ENOTSUP);
promise.set_value();
return;
}
if (not ep)
throw std::runtime_error{"lokinet_context->endpoint() returned null pointer."};
auto* quic = ep->GetQUICTunnel();
if (quic == nullptr)
{
stream_error(result, ENOTSUP);
promise.set_value();
return;
}
try
{
auto [addr, id] = quic->open(
remotehost, remoteport, [](auto) {}, localAddr);
auto [host, port] = split_host_port(addr.ToString());
ctx->outbound_stream(id);
stream_okay(result, host, port, id);
}
catch (std::exception& ex)
{
std::cout << ex.what() << std::endl;
stream_error(result, ECANCELED);
}
catch (int err)
{
stream_error(result, err);
}
if (not quic)
throw std::runtime_error{"lokinet_context endpoint has no quic tunnel manager."};
auto [addr, id] =
quic->open(remotehost, remoteport, std::move(on_open), std::move(on_close), localAddr);
auto [host, port] = split_host_port(addr.ToString());
result->tcp_id = id;
tcp_okay(result, host, port, id);
promise.set_value();
};
ctx->impl->CallSafe([call]() {
// we dont want the mainloop to die in case setting the value on the promise fails
try
{
call();
}
catch (...)
{}
});
}
auto future = promise.get_future();
try
{
if (auto status = future.wait_for(std::chrono::seconds{10});
status == std::future_status::ready)
{
future.get();
}
else
catch (...)
{
stream_error(result, ETIMEDOUT);
promise.set_exception(std::current_exception());
}
});
try
{
future.get();
}
catch (std::exception& ex)
catch (std::invalid_argument& e)
{
llarp::log::error(logcat, "Error: exception caught: {}", e.what());
tcp_error(result, EINVAL);
return;
}
catch (std::runtime_error& e)
{
stream_error(result, EBADF);
llarp::log::error(logcat, "Error: exception caught: {}", e.what());
tcp_error(result, ENOTSUP);
return;
}
catch (std::exception& e)
{
llarp::log::error(logcat, "Error: exception caught: {}", e.what());
tcp_error(result, EBADF);
return;
}
catch (...)
{
llarp::log::error(logcat, "Unknown exception caught.");
tcp_error(result, EBADF);
return;
}
auto lock = ctx->acquire();
ctx->outbound_tcp(remote, *result);
assert(result->error == 0);
return;
}
int EXPORT
lokinet_inbound_stream(uint16_t port, struct lokinet_context* ctx)
lokinet_inbound_tcp(uint16_t port, struct lokinet_context* ctx)
{
/// FIXME: delete pointer later
return lokinet_inbound_stream_filter(&accept_port, (void*)new std::uintptr_t{port}, ctx);
return lokinet_inbound_tcp_filter(&accept_port, (void*)new std::uintptr_t{port}, ctx);
}
int EXPORT
lokinet_inbound_stream_filter(
lokinet_stream_filter acceptFilter, void* user, struct lokinet_context* ctx)
lokinet_inbound_tcp_filter(
lokinet_tcp_filter acceptFilter, void* user, struct lokinet_context* ctx)
{
if (acceptFilter == nullptr)
{
@ -762,7 +837,6 @@ extern "C"
return -1;
std::promise<int> promise;
{
auto lock = ctx->acquire();
if (not ctx->impl->IsUp())
{
return -1;
@ -792,7 +866,7 @@ extern "C"
auto id = ftr.get();
{
auto lock = ctx->acquire();
ctx->inbound_stream(id);
ctx->inbound_tcp(id);
}
return id;
}
@ -815,32 +889,50 @@ extern "C"
}
void EXPORT
lokinet_close_stream(int stream_id, struct lokinet_context* ctx)
lokinet_close_tcp(int tcp_id, struct lokinet_context* ctx)
{
if (not ctx)
return;
auto lock = ctx->acquire();
if (not ctx->impl->IsUp())
return;
{
auto lock = ctx->acquire();
if (not ctx->impl->IsUp())
return;
}
try
{
std::promise<void> promise;
bool inbound = ctx->streams.at(stream_id);
ctx->impl->CallSafe([stream_id, inbound, ctx, &promise]() {
bool inbound{false};
{
auto lock = ctx->acquire();
inbound = ctx->tcp_conns.at(tcp_id);
}
ctx->impl->CallSafe([tcp_id, inbound, ctx, &promise]() {
auto lock = ctx->acquire();
auto ep = ctx->endpoint();
auto* quic = ep->GetQUICTunnel();
try
{
if (inbound)
quic->forget(stream_id);
quic->forget(tcp_id);
else
quic->close(stream_id);
quic->close(tcp_id);
}
catch (...)
{}
promise.set_value();
});
{
auto lock = ctx->acquire();
for (auto& itr : ctx->active_conns)
{
if (itr.second.tcp_id == tcp_id)
ctx->active_conns.erase(itr.first);
}
}
promise.get_future().get();
}
catch (...)
@ -958,9 +1050,10 @@ extern "C"
return EINVAL;
std::promise<int> ret;
ctx->impl->router->loop()->call([addr = *maybe, pkt = std::move(pkt), ep, &ret]() {
if (auto tag = ep->GetBestConvoTagFor(addr))
if (auto tag = ep->GetBestConvoTagFor(addr); auto addr = ep->GetEndpointWithConvoTag(*tag))
{
if (ep->SendToOrQueue(*tag, pkt.ConstBuffer(), llarp::service::ProtocolType::TrafficV4))
if (ep->SendToOrQueue(
std::move(*addr), pkt.ConstBuffer(), llarp::service::ProtocolType::TrafficV4))
{
ret.set_value(0);
return;

@ -77,6 +77,6 @@ namespace llarp
result = router->SendToOrQueue(session->GetPubKey(), reply);
}
}
return true;
return result;
}
} // namespace llarp

@ -274,7 +274,7 @@ namespace llarp
return llarp::net::ToHost(x);
}
[[deprecated("use llarp::net::ToHost instead")]] inline net::ipv4addr_t
[[deprecated("use llarp::net::ToNet instead")]] inline net::ipv4addr_t
xhtonl(huint32_t x)
{
return ToNet(x);

@ -72,6 +72,7 @@ namespace llarp
init();
fromString(addr);
}
SockAddr::SockAddr(std::string_view addr, huint16_t port)
{
init();

@ -1,12 +1,14 @@
#include "address.hpp"
#include <cstring>
#include <iostream>
#include <optional>
namespace llarp::quic
{
using namespace std::literals;
Address::Address(const SockAddr& addr) : saddr{*addr.operator const sockaddr_in6*()}
Address::Address(const SockAddr& addr, std::optional<std::variant<service::Address, RouterID>> ep)
: saddr{*addr.operator const sockaddr_in6*()}, endpoint{ep}
{}
Address&
@ -14,6 +16,7 @@ namespace llarp::quic
{
std::memmove(&saddr, &other.saddr, sizeof(saddr));
a.addrlen = other.a.addrlen;
endpoint = other.endpoint;
return *this;
}
@ -39,6 +42,7 @@ namespace llarp::quic
return result;
}
// FIXME: remote now has std::variant
std::string
Path::ToString() const
{

@ -6,6 +6,7 @@
#include <cassert>
#include <cstring>
#include <memory>
#include <optional>
#include <string>
#include <iosfwd>
@ -13,6 +14,7 @@
#include <llarp/net/sock_addr.hpp>
#include <llarp/service/convotag.hpp>
#include "llarp/service/address.hpp"
namespace llarp::quic
{
@ -25,13 +27,17 @@ namespace llarp::quic
public:
Address() = default;
Address(const SockAddr& addr);
Address(
const SockAddr& addr,
std::optional<std::variant<service::Address, RouterID>> ep = std::nullopt);
Address(const Address& other)
{
*this = other;
}
std::optional<std::variant<service::Address, RouterID>> endpoint{};
Address&
operator=(const Address&);
@ -60,7 +66,7 @@ namespace llarp::quic
return a;
}
size_t
ngtcp2_socklen
sockaddr_size() const
{
return sizeof(sockaddr_in6);
@ -89,7 +95,7 @@ namespace llarp::quic
// ConvoTag operator
operator SockAddr() const
{
return SockAddr(saddr);
return SockAddr{saddr};
}
std::string

@ -1,4 +1,5 @@
#include "client.hpp"
#include "llarp/net/net_int.hpp"
#include "tunnel.hpp"
#include <llarp/util/logging/buffer.hpp>
#include <llarp/util/logging.hpp>
@ -12,7 +13,14 @@
namespace llarp::quic
{
Client::Client(EndpointBase& ep, const SockAddr& remote, uint16_t pseudo_port) : Endpoint{ep}
static auto logcat = log::Cat("quic");
Client::Client(
EndpointBase& ep,
const uint16_t port,
std::variant<service::Address, RouterID>&& remote,
uint16_t pseudo_port)
: Endpoint{ep}
{
default_stream_buffer_size =
0; // We steal uvw's provided buffers so don't need an outgoing data buffer
@ -21,8 +29,7 @@ namespace llarp::quic
// back to *this* client.
local_addr.port(ToNet(huint16_t{pseudo_port}));
uint16_t tunnel_port = remote.getPort();
if (tunnel_port == 0)
if (port == 0)
throw std::logic_error{"Cannot tunnel to port 0"};
// TODO: need timers for:
@ -34,10 +41,13 @@ namespace llarp::quic
//
// - key_update_timer
Path path{local_addr, remote};
llarp::LogDebug("Connecting to ", remote);
// to try: set ports to 0
Path path{
Address{SockAddr{"::1"sv, huint16_t{0}}, std::nullopt},
Address{SockAddr{"::1"sv, huint16_t{pseudo_port}}, std::move(remote)}};
auto conn = std::make_shared<Connection>(*this, ConnectionID::random(), std::move(path), port);
auto conn = std::make_shared<Connection>(*this, ConnectionID::random(), path, tunnel_port);
conn->io_ready();
conns.emplace(conn->base_cid, std::move(conn));
}

@ -14,7 +14,11 @@ namespace llarp::quic
// `remote.getPort()` on the remote's lokinet address. `pseudo_port` is *our* unique local
// identifier which we include in outgoing packets (so that the remote server knows where to
// send the back to *this* client).
Client(EndpointBase& ep, const SockAddr& remote, uint16_t pseudo_port);
Client(
EndpointBase& ep,
const uint16_t port,
std::variant<service::Address, RouterID>&& remote,
uint16_t pseudo_port);
// Returns a reference to the client's connection to the server. Returns a nullptr if there is
// no connection.

File diff suppressed because it is too large Load Diff

@ -245,6 +245,9 @@ namespace llarp::quic
int
setup_server_crypto_initial();
int
get_handshake_confirmed();
// Flush any streams with pending data. Note that, depending on available ngtcp2 state, we may
// not fully flush all streams -- some streams can individually block while waiting for
// confirmation.
@ -276,9 +279,9 @@ namespace llarp::quic
// called.
std::function<void(Connection&)> on_handshake_complete;
// Returns true iff this connection has completed a handshake with the remote end.
bool
get_handshake_completed();
// Returns non-zero iff this connection has not completed a handshake with the remote end.
int
complete_handshake();
// Callback that is invoked whenever new streams become available: i.e. after handshaking, or
// after existing streams are closed. Note that this callback is invoked whenever the number of
@ -287,6 +290,11 @@ namespace llarp::quic
// least 1 when this callback is invoked).
std::function<void(Connection&)> on_stream_available;
// Callback that is invoked by the Connection's owning Endpoint whenever the Connection
// is put into a "closing" state (draining, closing, immediate dismissal, etc.) After
// calling this once, the Endpoint clears it.
std::function<void(Connection&)> on_closing;
// Returns the number of available streams that can currently be opened on the connection
int
get_streams_available();
@ -322,8 +330,8 @@ namespace llarp::quic
send_magic(ngtcp2_crypto_level level);
int
send_transport_params(ngtcp2_crypto_level level);
void
complete_handshake();
// void
// complete_handshake();
};
} // namespace llarp::quic

@ -1,5 +1,7 @@
#include "endpoint.hpp"
#include "client.hpp"
#include "llarp/net/net_int.hpp"
#include "ngtcp2/ngtcp2.h"
#include "server.hpp"
#include "uvw/async.h"
#include <llarp/crypto/crypto.hpp>
@ -22,6 +24,8 @@ extern "C"
namespace llarp::quic
{
static auto logcat = log::Cat("quic");
Endpoint::Endpoint(EndpointBase& ep) : service_endpoint{ep}
{
randombytes_buf(static_secret.data(), static_secret.size());
@ -31,7 +35,7 @@ namespace llarp::quic
expiry_timer->on<uvw::TimerEvent>([this](const auto&, auto&) { check_timeouts(); });
expiry_timer->start(250ms, 250ms);
LogDebug("Created QUIC endpoint");
log::debug(logcat, "Created QUIC endpoint");
}
Endpoint::~Endpoint()
@ -48,49 +52,60 @@ namespace llarp::quic
return loop;
}
// TODO: does the lookup need to be done every single packet?
// revisit this during libQUICinet
// Endpoint::receive_packet(const SockAddr& src, uint8_t ecn, bstring_view data)
void
Endpoint::receive_packet(const SockAddr& src, uint8_t ecn, bstring_view data)
Endpoint::receive_packet(Address remote, uint8_t ecn, bstring_view data)
{
// ngtcp2 wants a local address but we don't necessarily have something so just set it to
// IPv4 or IPv6 "unspecified" address (0.0.0.0 or ::)
SockAddr local = src.isIPv6() ? SockAddr{in6addr_any} : SockAddr{nuint32_t{INADDR_ANY}};
// SockAddr local = src.isIPv6() ? SockAddr{in6addr_any} : SockAddr{nuint32_t{INADDR_ANY}};
Packet pkt{Path{local, src}, data, ngtcp2_pkt_info{.ecn = ecn}};
Packet pkt{
Path{Address{SockAddr{"::1"sv, huint16_t{0}}, std::nullopt}, remote},
data,
ngtcp2_pkt_info{.ecn = ecn}};
LogTrace("[", pkt.path, ",ecn=", pkt.info.ecn, "]: received ", data.size(), " bytes");
log::trace(logcat, "[{},ecn={}]: received {} bytes", pkt.path, pkt.info.ecn, data.size());
handle_packet(pkt);
LogTrace("Done handling packet");
log::debug(logcat, "Done handling packet");
}
void
Endpoint::handle_packet(const Packet& p)
{
LogTrace("Handling incoming quic packet: ", buffer_printer{p.data});
auto maybe_dcid = handle_packet_init(p);
if (!maybe_dcid)
{
log::debug(logcat, "Handle packet init failed");
return;
}
auto& dcid = *maybe_dcid;
// See if we have an existing connection already established for it
LogTrace("Incoming connection id ", dcid);
log::trace(logcat, "Incoming connection id {}", dcid);
auto [connptr, alias] = get_conn(dcid);
if (!connptr)
{
if (alias)
{
LogDebug("Incoming packet QUIC CID is an expired alias; dropping");
log::debug(logcat, "Incoming packet QUIC CID is an expired alias; dropping");
return;
}
connptr = accept_initial_connection(p);
if (!connptr)
{
log::warning(logcat, "Connection could not be created");
return;
}
}
if (alias)
LogTrace("CID is alias for primary CID ", connptr->base_cid);
log::debug(logcat, "CID is alias for primary CID {}", connptr->base_cid);
else
LogTrace("CID is primary CID");
log::debug(logcat, "CID is primary CID");
handle_conn_packet(*connptr, p);
}
@ -98,47 +113,40 @@ namespace llarp::quic
std::optional<ConnectionID>
Endpoint::handle_packet_init(const Packet& p)
{
version_info vi;
auto rv = ngtcp2_pkt_decode_version_cid(
&vi.version,
&vi.dcid,
&vi.dcid_len,
&vi.scid,
&vi.scid_len,
u8data(p.data),
p.data.size(),
NGTCP2_MAX_CIDLEN);
if (rv == 1)
{ // 1 means Version Negotiation should be sent and otherwise the packet should be ignored
ngtcp2_version_cid vi;
auto rv = ngtcp2_pkt_decode_version_cid(&vi, u8data(p.data), p.data.size(), NGTCP2_MAX_CIDLEN);
if (rv == NGTCP2_ERR_VERSION_NEGOTIATION)
{ // Version Negotiation should be sent and otherwise the packet should be ignored
send_version_negotiation(vi, p.path.remote);
return std::nullopt;
}
if (rv != 0)
{
LogWarn("QUIC packet header decode failed: ", ngtcp2_strerror(rv));
log::warning(logcat, "QUIC packet header decode failed: {}", ngtcp2_strerror(rv));
return std::nullopt;
}
if (vi.dcid_len > ConnectionID::max_size())
if (vi.dcidlen > ConnectionID::max_size())
{
LogWarn("Internal error: destination ID is longer than should be allowed");
log::warning(logcat, "Internal error: destination ID is longer than should be allowed");
return std::nullopt;
}
return std::make_optional<ConnectionID>(vi.dcid, vi.dcid_len);
return std::make_optional<ConnectionID>(vi.dcid, vi.dcidlen);
}
void
Endpoint::handle_conn_packet(Connection& conn, const Packet& p)
{
if (ngtcp2_conn_is_in_closing_period(conn))
{
LogDebug("Connection is in closing period, dropping");
log::debug(logcat, "Connection is in closing period, dropping");
close_connection(conn);
return;
}
if (conn.draining)
{
LogDebug("Connection is draining, dropping");
log::debug(logcat, "Connection is draining, dropping");
// "draining" state means we received a connection close and we're keeping the
// connection alive just to catch (and discard) straggling packets that arrive
// out of order w.r.t to connection close.
@ -147,29 +155,52 @@ namespace llarp::quic
if (auto result = read_packet(p, conn); !result)
{
LogWarn("Read packet failed! ", ngtcp2_strerror(result.error_code));
log::warning(logcat, "Read packet failed! {}", ngtcp2_strerror(result.error_code));
log::debug(logcat, "Packet: {}", buffer_printer{p.data});
}
// FIXME - reset idle timer?
LogTrace("Done with incoming packet");
log::trace(logcat, "Done with incoming packet");
}
io_result
Endpoint::read_packet(const Packet& p, Connection& conn)
{
LogTrace("Reading packet from ", p.path);
auto rv =
ngtcp2_conn_read_pkt(conn, p.path, &p.info, u8data(p.data), p.data.size(), get_timestamp());
if (rv == 0)
conn.io_ready();
else
LogWarn("read pkt error: ", ngtcp2_strerror(rv));
if (rv == NGTCP2_ERR_DRAINING)
else if (rv == NGTCP2_ERR_DRAINING)
{
log::debug(logcat, "Draining connection {}", conn.base_cid);
start_draining(conn);
else if (rv == NGTCP2_ERR_DROP_CONN)
}
else if (rv == NGTCP2_ERR_PROTO)
{
log::warning(
logcat,
"Immediately closing connection {} due to error {}",
conn.base_cid,
ngtcp2_strerror(rv));
close_connection(conn, rv, "ERR_PROTO"sv);
}
else if (rv == NGTCP2_ERR_DROP_CONN) // drop connection w/o calling
// ngtcp2_conn_write_connection_close()
{
log::warning(
logcat, "Deleting connection {} due to error {}", conn.base_cid, ngtcp2_strerror(rv));
delete_conn(conn.base_cid);
}
else
{
log::warning(
logcat,
"Immediately closing connection {} due to error {}",
conn.base_cid,
ngtcp2_strerror(rv));
close_connection(conn, rv, ngtcp2_strerror(rv));
}
return {rv};
}
@ -186,19 +217,24 @@ namespace llarp::quic
bstring_view outgoing{buf_.data(), outgoing_len};
if (service_endpoint.SendToOrQueue(
to, llarp_buffer_t{outgoing.data(), outgoing.size()}, service::ProtocolType::QUIC))
*to.endpoint,
llarp_buffer_t{outgoing.data(), outgoing.size()},
service::ProtocolType::QUIC))
{
LogTrace("[", to, "]: sent ", buffer_printer{outgoing});
log::trace(logcat, "[{}]: sent {}", to, buffer_printer{outgoing});
}
else
{
LogDebug("Failed to send to quic endpoint ", to, "; was sending ", outgoing.size(), "B");
log::warning(
logcat, "Failed to send to quic endpoint {}; was sending {}B", to, outgoing.size());
return io_result{-1};
}
return {};
return io_result{0};
}
void
Endpoint::send_version_negotiation(const version_info& vi, const Address& source)
Endpoint::send_version_negotiation(const ngtcp2_version_cid& vi, const Address& source)
{
std::array<std::byte, Endpoint::max_pkt_size_v4> buf;
std::array<uint32_t, NGTCP2_PROTO_VER_MAX - NGTCP2_PROTO_VER_MIN + 2> versions;
@ -212,65 +248,85 @@ namespace llarp::quic
buf.size(),
std::uniform_int_distribution<uint8_t>{0, 255}(rng),
vi.dcid,
vi.dcid_len,
vi.dcidlen,
vi.scid,
vi.scid_len,
vi.scidlen,
versions.data(),
versions.size());
if (nwrote < 0)
LogWarn("Failed to construct version negotiation packet: ", ngtcp2_strerror(nwrote));
log::warning(
logcat, "Failed to construct version negotiation packet: {}", ngtcp2_strerror(nwrote));
if (nwrote <= 0)
return;
send_packet(source, bstring_view{buf.data(), static_cast<size_t>(nwrote)}, 0);
if (auto rv = send_packet(source, bstring_view{buf.data(), static_cast<size_t>(nwrote)}, 0);
not rv)
log::warning(logcat, "Failed to send version negotiation packet");
}
void
Endpoint::close_connection(
Connection& conn, uint64_t code, bool application, std::string_view close_reason)
Endpoint::close_connection(Connection& conn, int code, std::string_view close_reason)
{
LogDebug("Closing connection ", conn.base_cid);
if (!conn.closing)
log::debug(logcat, "Closing connection {}", conn.base_cid);
if (!conn || conn.closing || conn.draining)
return;
if (code == NGTCP2_ERR_IDLE_CLOSE)
{
conn.conn_buffer.resize(max_pkt_size_v4);
Path path;
ngtcp2_pkt_info pi;
auto write_close_func = application ? ngtcp2_conn_write_application_close_versioned
: ngtcp2_conn_write_connection_close_versioned;
auto written = write_close_func(
conn,
path,
NGTCP2_PKT_INFO_VERSION,
&pi,
u8data(conn.conn_buffer),
conn.conn_buffer.size(),
code,
reinterpret_cast<const uint8_t*>(close_reason.data()),
close_reason.size(),
get_timestamp());
if (written <= 0)
{
LogWarn(
"Failed to write connection close packet: ",
written < 0 ? ngtcp2_strerror(written) : "unknown error: closing is 0 bytes??");
return;
}
assert(written <= (long)conn.conn_buffer.size());
conn.conn_buffer.resize(written);
conn.closing = true;
log::warning(logcat, "Connection {} passed idle expiry timer, closing now", conn.base_cid);
delete_conn(conn.base_cid);
return;
}
// "The error not specifically mentioned, including NGTCP2_ERR_HANDSHAKE_TIMEOUT,
// should be dealt with by calling ngtcp2_conn_write_connection_close."
// https://github.com/ngtcp2/ngtcp2/issues/670#issuecomment-1417300346
if (code == NGTCP2_ERR_HANDSHAKE_TIMEOUT)
{
log::warning(logcat, "Connection {} handshake timed out, closing now", conn.base_cid);
}
conn.path = path;
ngtcp2_connection_close_error err;
ngtcp2_connection_close_error_set_transport_error_liberr(
&err,
code,
reinterpret_cast<uint8_t*>(const_cast<char*>(close_reason.data())),
close_reason.size());
conn.conn_buffer.resize(max_pkt_size_v4);
ngtcp2_pkt_info pi;
auto written = ngtcp2_conn_write_connection_close(
conn,
&conn.path.path,
&pi,
u8data(conn.conn_buffer),
conn.conn_buffer.size(),
&err,
get_timestamp());
if (written <= 0)
{
log::warning(
logcat,
"Failed to write connection close packet: {}",
written < 0 ? ngtcp2_strerror(written) : "unknown error: closing is 0 bytes??");
log::warning(logcat, "Failed to write packet: removing connection {}", conn.base_cid);
delete_conn(conn.base_cid);
return;
}
assert(written <= (long)conn.conn_buffer.size());
conn.conn_buffer.resize(written);
conn.closing = true;
assert(conn.closing && !conn.conn_buffer.empty());
if (auto sent = send_packet(conn.path.remote, conn.conn_buffer, 0); not sent)
{
LogWarn(
"Failed to send packet: ",
log::warning(
logcat,
"Failed to send packet: {}; removing connection {}",
strerror(sent.error_code),
"; removing connection ",
conn.base_cid);
delete_conn(conn.base_cid);
return;
@ -285,7 +341,13 @@ namespace llarp::quic
{
if (conn.draining)
return;
LogDebug("Putting ", conn.base_cid, " into draining mode");
if (conn.on_closing)
{
log::trace(logcat, "Calling Connection.on_closing for connection {}", conn.base_cid);
conn.on_closing(conn); // only call once
conn.on_closing = nullptr;
}
log::debug(logcat, "Putting {} into draining mode", conn.base_cid);
conn.draining = true;
// Recommended draining time is 3*Probe Timeout
draining.emplace(conn.base_cid, get_time() + ngtcp2_conn_get_pto(conn) * 3 * 1ns);
@ -295,7 +357,6 @@ namespace llarp::quic
Endpoint::check_timeouts()
{
auto now = get_time();
uint64_t now_ts = get_timestamp(now);
// Destroy any connections that are finished draining
bool cleanup = false;
@ -305,25 +366,14 @@ namespace llarp::quic
{
if (std::holds_alternative<primary_conn_ptr>(it->second))
cleanup = true;
LogDebug("Deleting connection ", it->first);
log::debug(logcat, "Deleting connection {}", it->first);
conns.erase(it);
}
draining.pop();
}
if (cleanup)
clean_alias_conns();
for (auto it = conns.begin(); it != conns.end(); ++it)
{
if (auto* conn_ptr = std::get_if<primary_conn_ptr>(&it->second))
{
Connection& conn = **conn_ptr;
auto exp = ngtcp2_conn_get_idle_expiry(conn);
if (exp >= now_ts || conn.draining)
continue;
start_draining(conn);
}
}
}
std::pair<std::shared_ptr<Connection>, bool>
@ -344,15 +394,27 @@ namespace llarp::quic
auto it = conns.find(cid);
if (it == conns.end())
{
LogDebug("Cannot delete connection ", cid, ": cid not found");
log::debug(logcat, "Cannot delete connection {}: cid not found", cid);
return false;
}
bool primary = std::holds_alternative<primary_conn_ptr>(it->second);
LogDebug("Deleting ", primary ? "primary" : "alias", " connection ", cid);
if (primary)
{
auto ptr = var::get<primary_conn_ptr>(it->second);
if (ptr->on_closing)
{
log::trace(logcat, "Calling Connection.on_closing for connection {}", cid);
ptr->on_closing(*ptr); // only call once
ptr->on_closing = nullptr;
}
}
log::debug(logcat, "Deleting {} connection {}", primary ? "primary" : "alias", cid);
conns.erase(it);
if (primary)
clean_alias_conns();
return true;
}
@ -378,7 +440,7 @@ namespace llarp::quic
cid = ConnectionID::random(cid_length);
inserted = conns.emplace(cid, conn.weak_from_this()).second;
}
LogDebug("Created cid ", cid, " alias for ", conn.base_cid);
log::debug(logcat, "Created cid {} alias for {}", cid, conn.base_cid);
return cid;
}

@ -43,7 +43,7 @@ namespace llarp::quic
/// address based on the convo tag. The port is not used.
/// \param ecn - the packet ecn parameter
void
receive_packet(const SockAddr& src, uint8_t ecn, bstring_view data);
receive_packet(Address addr, uint8_t ecn, bstring_view data);
/// Returns a shared pointer to the uvw loop.
std::shared_ptr<uvw::Loop>
@ -109,16 +109,6 @@ namespace llarp::quic
virtual ~Endpoint();
// Version & connection id info that we can potentially extract when decoding a packet
struct version_info
{
uint32_t version;
const uint8_t* dcid;
size_t dcid_len;
const uint8_t* scid;
size_t scid_len;
};
// Called to handle an incoming packet
void
handle_packet(const Packet& p);
@ -185,7 +175,7 @@ namespace llarp::quic
}
void
send_version_negotiation(const version_info& vi, const Address& source);
send_version_negotiation(const ngtcp2_version_cid& vi, const Address& source);
// Looks up a connection. Returns a shared_ptr (either copied for a primary connection, or
// locked from an alias's weak pointer) if the connection was found or nullptr if not; and a
@ -206,10 +196,7 @@ namespace llarp::quic
// values.
void
close_connection(
Connection& conn,
uint64_t code = NGTCP2_NO_ERROR,
bool application = false,
std::string_view close_reason = ""sv);
Connection& conn, int code = NGTCP2_NO_ERROR, std::string_view close_reason = ""sv);
/// Puts a connection into draining mode (i.e. after getting a connection close). This will
/// keep the connection registered for the recommended 3*Probe Timeout, during which we drop
@ -220,7 +207,7 @@ namespace llarp::quic
void
check_timeouts();
/// Deletes a connection from `conns`; if the connecion is a primary connection shared pointer
/// Deletes a connection from `conns`; if the connection is a primary connection shared pointer
/// then it is removed and clean_alias_conns() is immediately called to remove any aliases to
/// the connection. If the given connection is an alias connection then it is removed but no
/// cleanup is performed. Returns true if something was removed, false if the connection was

@ -5,7 +5,11 @@
namespace llarp::quic
{
// Cranks a value to "11", i.e. set it to its maximum
static auto logcat = log::Cat("quic");
// "It's one louder, init?"
// "Why not make '10' higher?"
// "...These go to 11"
template <typename T>
void
crank_to_eleven(T& val)
@ -22,10 +26,11 @@ namespace llarp::quic
}
void
NullCrypto::client_initial(Connection& conn)
NullCrypto::client_initial(ngtcp2_conn* conn)
{
log::debug(logcat, "Client initial null crypto setup");
ngtcp2_conn_set_initial_crypto_ctx(conn, &null_ctx);
ngtcp2_conn_install_initial_key(
int rv = ngtcp2_conn_install_initial_key(
conn,
&null_aead_ctx,
null_iv.data(),
@ -34,16 +39,20 @@ namespace llarp::quic
null_iv.data(),
&null_cipher_ctx,
null_iv.size());
if (rv != 0)
log::debug(logcat, "Call to ngtcp2_conn_set_initial_crypto_ctx unsuccessful at {}", __LINE__);
ngtcp2_conn_set_retry_aead(conn, &null_aead, &null_aead_ctx);
ngtcp2_conn_set_crypto_ctx(conn, &null_ctx);
}
void
NullCrypto::server_initial(Connection& conn)
NullCrypto::server_initial(ngtcp2_conn* conn)
{
LogDebug("Server initial null crypto setup");
log::debug(logcat, "Server initial null crypto setup");
ngtcp2_conn_set_initial_crypto_ctx(conn, &null_ctx);
ngtcp2_conn_install_initial_key(
int rv = ngtcp2_conn_install_initial_key(
conn,
&null_aead_ctx,
null_iv.data(),
@ -52,26 +61,33 @@ namespace llarp::quic
null_iv.data(),
&null_cipher_ctx,
null_iv.size());
if (rv != 0)
log::debug(logcat, "Call to ngtcp2_conn_set_initial_crypto_ctx unsuccessful at {}", __LINE__);
ngtcp2_conn_set_crypto_ctx(conn, &null_ctx);
}
bool
NullCrypto::install_tx_handshake_key(Connection& conn)
NullCrypto::install_tx_handshake_key(ngtcp2_conn* conn)
{
log::debug(logcat, "Calling {}", __PRETTY_FUNCTION__);
return ngtcp2_conn_install_tx_handshake_key(
conn, &null_aead_ctx, null_iv.data(), null_iv.size(), &null_cipher_ctx)
== 0;
}
bool
NullCrypto::install_rx_handshake_key(Connection& conn)
NullCrypto::install_rx_handshake_key(ngtcp2_conn* conn)
{
log::debug(logcat, "Calling {}", __PRETTY_FUNCTION__);
return ngtcp2_conn_install_rx_handshake_key(
conn, &null_aead_ctx, null_iv.data(), null_iv.size(), &null_cipher_ctx)
== 0;
}
bool
NullCrypto::install_tx_key(Connection& conn)
NullCrypto::install_tx_key(ngtcp2_conn* conn)
{
log::debug(logcat, "Calling {}", __PRETTY_FUNCTION__);
return ngtcp2_conn_install_tx_key(
conn,
null_iv.data(),
@ -83,8 +99,9 @@ namespace llarp::quic
== 0;
}
bool
NullCrypto::install_rx_key(Connection& conn)
NullCrypto::install_rx_key(ngtcp2_conn* conn)
{
log::debug(logcat, "Calling {}", __PRETTY_FUNCTION__);
return ngtcp2_conn_install_rx_key(
conn, nullptr, 0, &null_aead_ctx, null_iv.data(), null_iv.size(), &null_cipher_ctx)
== 0;

@ -16,20 +16,20 @@ namespace llarp::quic
NullCrypto();
void
client_initial(Connection& conn);
client_initial(ngtcp2_conn* conn);
void
server_initial(Connection& conn);
server_initial(ngtcp2_conn* conn);
bool
install_tx_handshake_key(Connection& conn);
install_tx_handshake_key(ngtcp2_conn* conn);
bool
install_tx_key(Connection& conn);
install_tx_key(ngtcp2_conn* conn);
bool
install_rx_handshake_key(Connection& conn);
install_rx_handshake_key(ngtcp2_conn* conn);
bool
install_rx_key(Connection& conn);
install_rx_key(ngtcp2_conn* conn);
private:
std::array<uint8_t, 8> null_iv{};

@ -11,41 +11,45 @@
namespace llarp::quic
{
static auto logcat = log::Cat("quic");
std::shared_ptr<Connection>
Server::accept_initial_connection(const Packet& p)
{
LogDebug("Accepting new connection");
log::debug(logcat, "Accepting new connection");
// This is a new incoming connection
ngtcp2_pkt_hd hd;
auto rv = ngtcp2_accept(&hd, u8data(p.data), p.data.size());
if (rv == -1)
{ // Invalid packet
LogWarn("Invalid packet received, length=", p.data.size());
LogTrace("packet body: ", buffer_printer{p.data});
return nullptr;
}
if (rv == 1)
if (rv == NGTCP2_ERR_VERSION_NEGOTIATION)
{ // Invalid/unexpected version, send a version negotiation
LogDebug("Invalid/unsupported version; sending version negotiation");
log::debug(logcat, "Invalid/unsupported version; sending version negotiation");
send_version_negotiation(
version_info{hd.version, hd.dcid.data, hd.dcid.datalen, hd.scid.data, hd.scid.datalen},
ngtcp2_version_cid{
hd.version, hd.dcid.data, hd.dcid.datalen, hd.scid.data, hd.scid.datalen},
p.path.remote);
return nullptr;
}
else if (rv < 0)
{ // Invalid packet. rv could be NGTCP2_ERR_RETRY but that will only
// happen if the incoming packet is 0RTT which we don't use.
log::warning(logcat, "Invalid packet received, length={}", p.data.size());
log::trace(logcat, "packet body: {}", buffer_printer{p.data});
return nullptr;
}
if (hd.type == NGTCP2_PKT_0RTT)
{
LogWarn("Received 0-RTT packet, which shouldn't happen in our implementation; dropping");
log::warning(
logcat, "Received 0-RTT packet, which shouldn't happen in our implementation; dropping");
return nullptr;
}
if (hd.type == NGTCP2_PKT_INITIAL && hd.token.len)
{
// This is a normal QUIC thing, but we don't do it:
LogWarn("Unexpected token in initial packet");
log::warning(logcat, "Unexpected token in initial packet");
}
// create and store Connection

@ -1,6 +1,7 @@
#include "stream.hpp"
#include "connection.hpp"
#include "endpoint.hpp"
#include "llarp/util/logging/buffer.hpp"
#include <llarp/util/logging.hpp>
#include <cassert>
@ -43,6 +44,8 @@
namespace llarp::quic
{
static auto logcat = log::Cat("quic");
std::string
StreamID::ToString() const
{
@ -71,7 +74,7 @@ namespace llarp::quic
Stream::~Stream()
{
LogTrace("Destroying stream ", stream_id);
log::trace(logcat, "Destroying stream {}", stream_id);
if (avail_trigger)
{
avail_trigger->close();
@ -124,26 +127,24 @@ namespace llarp::quic
auto data_split = data.begin() + (buffer.size() - wpos);
std::copy(data.begin(), data_split, buffer.begin() + wpos);
std::copy(data_split, data.end(), buffer.begin());
LogTrace(
"Wrote ",
log::trace(
logcat,
"Wrote {} bytes to buffer ranges [{},)+[0,{})",
data.size(),
" bytes to buffer ranges [",
wpos,
",",
buffer.size(),
")+[0,",
data.end() - data_split,
")");
data.end() - data_split);
}
else
{
// No wrap needs, it fits before the end:
std::copy(data.begin(), data.end(), buffer.begin() + wpos);
LogTrace(
"Wrote ", data.size(), " bytes to buffer range [", wpos, ",", wpos + data.size(), ")");
log::trace(
logcat, "Wrote {} bytes to buffer range [{},{})", data.size(), wpos, wpos + data.size());
}
size += data.size();
LogTrace("New stream buffer: ", size, "/", buffer.size(), " bytes beginning at ", start);
log::trace(
logcat, "New stream buffer: {}/{} bytes beginning at {}", size, buffer.size(), start);
conn.io_ready();
return true;
}
@ -161,6 +162,7 @@ namespace llarp::quic
Stream::append_buffer(const std::byte* buffer, size_t length)
{
assert(this->buffer.empty());
log::debug(logcat, "buffer: {}", buffer_printer{buffer, length});
user_buffers.emplace_back(buffer, length);
size += length;
conn.io_ready();
@ -177,7 +179,7 @@ namespace llarp::quic
//
assert(bytes <= unacked_size && unacked_size <= size);
LogTrace("Acked ", bytes, " bytes of ", unacked_size, "/", size, " unacked/total");
log::debug(logcat, "Acked {} bytes of {}/{} unacked/total", bytes, unacked_size, size);
unacked_size -= bytes;
size -= bytes;
@ -308,7 +310,7 @@ namespace llarp::quic
// [ áaarrrrrr ] or [rr áaar]
// to:
// [ áaaaaarrr ] or [aa áaaa]
LogTrace("wrote ", bytes, ", unsent=", unsent());
log::debug(logcat, "wrote {}, unsent={}", bytes, unsent());
assert(bytes <= unsent());
unacked_size += bytes;
}
@ -316,20 +318,21 @@ namespace llarp::quic
void
Stream::close(std::optional<uint64_t> error_code)
{
LogDebug(
"Closing ",
log::debug(
logcat,
"Closing {} {}",
stream_id,
error_code ? " immediately with code " + std::to_string(*error_code) : " gracefully");
if (is_shutdown)
LogDebug("Stream is already shutting down");
else if (error_code)
log::debug(logcat, "Stream is already shutting down");
else if (error_code && *error_code != 0)
{
is_closing = is_shutdown = true;
ngtcp2_conn_shutdown_stream(conn, stream_id.id, *error_code);
}
else if (is_closing)
LogDebug("Stream is already closing");
log::debug(logcat, "Stream is already closing");
else
is_closing = true;

@ -201,6 +201,20 @@ namespace llarp::quic
return is_closing;
}
// Informs the stream that TCP EOF has been received on this end
void
set_eof()
{
tcp_eof = true;
}
// Returns true of the TCP connection on this end gave us EOF
bool
has_eof()
{
return tcp_eof;
}
// Callback invoked when data is received
using data_callback_t = std::function<void(Stream&, bstring_view)>;
@ -357,6 +371,7 @@ namespace llarp::quic
bool is_closing{false};
bool sent_fin{false};
bool is_shutdown{false};
bool tcp_eof{false};
// Async trigger we use to schedule when_available callbacks (so that we can make them happen in
// batches rather than after each and every packet ack).

@ -2,6 +2,7 @@
#include <llarp/service/convotag.hpp>
#include <llarp/service/endpoint.hpp>
#include <llarp/service/name.hpp>
#include "llarp/net/net_int.hpp"
#include "stream.hpp"
#include <limits>
#include <llarp/util/logging.hpp>
@ -16,6 +17,8 @@ namespace llarp::quic
{
namespace
{
static auto logcat = log::Cat("quic");
// Takes data from the tcp connection and pushes it down the quic tunnel
void
on_outgoing_data(uvw::DataEvent& event, uvw::TCPHandle& client)
@ -24,21 +27,24 @@ namespace llarp::quic
assert(stream);
std::string_view data{event.data.get(), event.length};
auto peer = client.peer();
LogTrace(peer.ip, ":", peer.port, " → lokinet ", buffer_printer{data});
// log::trace(logcat, "{}:{} → lokinet {}", peer.ip, peer.port, buffer_printer{data});
log::debug(logcat, "{}:{} → lokinet {}", peer.ip, peer.port, buffer_printer{data});
// Steal the buffer from the DataEvent's unique_ptr<char[]>:
stream->append_buffer(reinterpret_cast<const std::byte*>(event.data.release()), event.length);
if (stream->used() >= tunnel::PAUSE_SIZE)
{
LogDebug(
"quic tunnel is congested (have ",
stream->used(),
" bytes in flight); pausing local tcp connection reads");
log::debug(
logcat,
"quic tunnel is congested (have {} bytes in flight); pausing local tcp connection "
"reads",
stream->used());
client.stop();
stream->when_available([](Stream& s) {
auto client = s.data<uvw::TCPHandle>();
if (s.used() < tunnel::PAUSE_SIZE)
{
LogDebug("quic tunnel is no longer congested; resuming tcp connection reading");
log::debug(
logcat, "quic tunnel is no longer congested; resuming tcp connection reading");
client->read();
return true;
}
@ -47,7 +53,7 @@ namespace llarp::quic
}
else
{
LogDebug("Queued ", event.length, " bytes");
log::debug(logcat, "Queued {} bytes", event.length);
}
}
@ -62,7 +68,7 @@ namespace llarp::quic
std::string_view data{reinterpret_cast<const char*>(bdata.data()), bdata.size()};
auto peer = tcp->peer();
LogTrace(peer.ip, ":", peer.port, " ← lokinet ", buffer_printer{data});
log::trace(logcat, "{}:{} ← lokinet {}", peer.ip, peer.port, buffer_printer{data});
if (data.empty())
return;
@ -85,7 +91,7 @@ namespace llarp::quic
{
if (auto tcp = st.data<uvw::TCPHandle>())
{
LogTrace("Closing TCP connection");
log::trace(logcat, "Closing TCP connection");
tcp->close();
}
};
@ -96,42 +102,58 @@ namespace llarp::quic
{
tcp.data(stream.shared_from_this());
stream.weak_data(tcp.weak_from_this());
auto weak_conn = stream.get_connection().weak_from_this();
tcp.clear(); // Clear any existing initial event handlers
tcp.on<uvw::CloseEvent>([](auto&, uvw::TCPHandle& c) {
tcp.on<uvw::CloseEvent>([weak_conn = std::move(weak_conn)](auto&, uvw::TCPHandle& c) {
// This fires sometime after we call `close()` to signal that the close is done.
if (auto stream = c.data<Stream>())
{
LogInfo("Local TCP connection closed, closing associated quic stream ", stream->id());
stream->close();
log::info(
logcat,
"Local TCP connection closed, closing associated quic stream {}",
stream->id());
// There is an awkwardness with Stream ownership, so make sure the Connection
// which it holds a reference to still exists, as stream->close will segfault
// otherwise
if (auto locked_conn = weak_conn.lock())
{
// If this end of the stream closed due to an abrupt close to the local TCP
// connection rather than an EOF, send an error code along so the other end
// knows this end is dead. Otherwise, the streams on either end should die
// gracefully when both ends of the TCP connection are properly closed.
stream->close(stream->has_eof() ? 0 : tunnel::ERROR_TCP);
}
stream->data(nullptr);
}
c.data(nullptr);
});
tcp.on<uvw::EndEvent>([](auto&, uvw::TCPHandle& c) {
// This fires on eof, most likely because the other side of the TCP connection closed it.
LogInfo("EOF on connection to ", c.peer().ip, ":", c.peer().port);
log::info(logcat, "EOF on connection to {}:{}", c.peer().ip, c.peer().port);
if (auto stream = c.data<Stream>())
{
stream->set_eof(); // CloseEvent will send graceful shutdown to other end
}
c.close();
});
tcp.on<uvw::ErrorEvent>([](const uvw::ErrorEvent& e, uvw::TCPHandle& tcp) {
LogError(
"ErrorEvent[",
log::error(
logcat,
"ErrorEvent[{}:{}] on connection with {}:{}, shutting down quic stream",
e.name(),
": ",
e.what(),
"] on connection with ",
tcp.peer().ip,
":",
tcp.peer().port,
", shutting down quic stream");
tcp.peer().port);
if (auto stream = tcp.data<Stream>())
{
stream->close(tunnel::ERROR_TCP);
stream->data(nullptr);
tcp.data(nullptr);
}
// tcp.closeReset();
tcp.close();
});
tcp.on<uvw::DataEvent>(on_outgoing_data);
stream.data_callback = on_incoming_data;
@ -147,7 +169,7 @@ namespace llarp::quic
void
initial_client_data_handler(uvw::TCPHandle& client, Stream& stream, bstring_view bdata)
{
LogTrace("initial client handler; data: ", buffer_printer{bdata});
log::trace(logcat, "initial client handler; data: {}", buffer_printer{bdata});
if (bdata.empty())
return;
client.clear(); // Clear these initial event handlers: we either set up the proper ones, or
@ -164,14 +186,15 @@ namespace llarp::quic
bdata.remove_prefix(1);
stream.data_callback(stream, std::move(bdata));
}
LogTrace("starting client reading");
log::trace(logcat, "starting client reading");
}
else
{
LogWarn(
"Remote connection returned invalid initial byte (0x",
oxenc::to_hex(bdata.begin(), bdata.begin() + 1),
"); dropping connection");
log::warning(
logcat,
"Remote connection returned invalid initial byte (0x{}); dropping "
"connection",
oxenc::to_hex(bdata.begin(), bdata.begin() + 1));
stream.close(tunnel::ERROR_BAD_INIT);
client.close();
}
@ -187,14 +210,14 @@ namespace llarp::quic
uvw::TCPHandle& client, Stream& /*stream*/, std::optional<uint64_t> error_code)
{
if (error_code && *error_code == tunnel::ERROR_CONNECT)
LogDebug("Remote TCP connection failed, closing local connection");
log::debug(logcat, "Remote TCP connection failed, closing local connection");
else
LogWarn(
"Stream connection closed ",
error_code ? "with error " + std::to_string(*error_code) : "gracefully",
"; closing local TCP connection.");
log::warning(
logcat,
"Stream connection closed {}; closing local TCP connection.",
error_code ? "with error " + std::to_string(*error_code) : "gracefully");
auto peer = client.peer();
LogDebug("Closing connection to ", peer.ip, ":", peer.port);
log::debug(logcat, "Closing connection to {}:{}", peer.ip, peer.port);
client.clear();
if (error_code)
client.close();
@ -208,7 +231,7 @@ namespace llarp::quic
{
// Cleanup callback to clear out closed tunnel connections
service_endpoint_.Loop()->call_every(500ms, timer_keepalive_, [this] {
LogTrace("Checking quic tunnels for finished connections");
log::trace(logcat, "Checking quic tunnels for finished connections");
for (auto ctit = client_tunnels_.begin(); ctit != client_tunnels_.end();)
{
// Clear any accepted connections that have been closed:
@ -220,7 +243,7 @@ namespace llarp::quic
// stop the TCP connection when the quic side gets congested.
if (not *it or not(*it)->data())
{
LogDebug("Cleanup up closed outgoing tunnel on quic:", port);
log::debug(logcat, "Cleanup up closed outgoing tunnel on quic:{}", port);
it = ct.conns.erase(it);
}
else
@ -231,43 +254,40 @@ namespace llarp::quic
// destroy the whole thing.
if (ct.conns.empty() and (not ct.tcp or not ct.tcp->active()))
{
LogDebug("All sockets closed on quic:", port, ", destroying tunnel data");
log::debug(logcat, "All sockets closed on quic:{}, destroying tunnel data", port);
if (ct.close_cb)
ct.close_cb(0, nullptr);
ctit = client_tunnels_.erase(ctit);
}
else
++ctit;
}
LogTrace("Done quic tunnel cleanup check");
log::trace(logcat, "Done quic tunnel cleanup check");
});
}
void
TunnelManager::make_server()
{
// auto loop = get_loop();
server_ = std::make_unique<Server>(service_endpoint_);
server_->stream_open_callback = [this](Stream& stream, uint16_t port) -> bool {
stream.close_callback = close_tcp_pair;
// FIXME
auto& conn = stream.get_connection();
auto remote = service_endpoint_.GetEndpointWithConvoTag(conn.path.remote);
if (!remote)
{
LogWarn("Received new stream open from invalid/unknown convo tag, dropping stream");
return false;
}
auto lokinet_addr = var::visit([](auto&& remote) { return remote.ToString(); }, *remote);
auto lokinet_addr =
var::visit([](auto&& remote) { return remote.ToString(); }, *conn.path.remote.endpoint);
auto tunnel_to = allow_connection(lokinet_addr, port);
if (not tunnel_to)
return false;
LogInfo("quic stream from ", lokinet_addr, " to ", port, " tunnelling to ", *tunnel_to);
log::debug(
logcat, "quic stream from {} to {} tunnelling to {}", lokinet_addr, port, *tunnel_to);
auto tcp = get_loop()->resource<uvw::TCPHandle>();
auto error_handler = tcp->once<uvw::ErrorEvent>(
[&stream, to = *tunnel_to](const uvw::ErrorEvent&, uvw::TCPHandle&) {
LogWarn("Failed to connect to ", to, ", shutting down quic stream");
log::warning(logcat, "Failed to connect to {}, shutting down quic stream", to);
stream.close(tunnel::ERROR_CONNECT);
});
@ -281,16 +301,16 @@ namespace llarp::quic
auto stream = streamw.lock();
if (!stream)
{
LogWarn(
"Connected to TCP ",
log::warning(
logcat,
"Connected to TCP {}:{} but quic stream has gone away; close/resetting local TCP "
"connection",
peer.ip,
":",
peer.port,
" but quic stream has gone away; close/resetting local TCP connection");
peer.port);
tcp.close();
return;
}
LogDebug("Connected to ", peer.ip, ":", peer.port, " for quic ", stream->id());
log::debug(logcat, "Connected to {}:{} for quic {}", peer.ip, peer.port, stream->id());
// Set up the data stream forwarding (which also clears these initial handlers).
install_stream_forwarding(tcp, *stream);
assert(stream->used() == 0);
@ -324,7 +344,7 @@ namespace llarp::quic
TunnelManager::listen(SockAddr addr)
{
return listen([addr](std::string_view, uint16_t p) -> std::optional<SockAddr> {
LogInfo("try accepting ", addr.getPort());
log::info(logcat, "try accepting {}", addr.getPort());
if (p == addr.getPort())
return addr;
return std::nullopt;
@ -349,19 +369,20 @@ namespace llarp::quic
}
catch (const std::exception& e)
{
LogWarn(
"Incoming quic connection from ",
log::warning(
logcat,
"Incoming quic connection from {} to {} denied via exception ({})",
lokinet_addr,
" to ",
port,
" denied via exception (",
e.what(),
")");
e.what());
return std::nullopt;
}
}
LogWarn(
"Incoming quic connection from ", lokinet_addr, " to ", port, " declined by all handlers");
log::warning(
logcat,
"Incoming quic connection from {} to {} declined by all handlers",
lokinet_addr,
port);
return std::nullopt;
}
@ -413,15 +434,15 @@ namespace llarp::quic
auto it = client_tunnels_.find(pseudo_port);
if (it == client_tunnels_.end())
{
LogDebug("QUIC tunnel to ", addr, " closed before ", step_name, " finished");
log::debug(logcat, "QUIC tunnel to {} closed before {} finished", addr, step_name);
return false;
}
if (!step_success)
{
LogWarn("QUIC tunnel to ", addr, " failed during ", step_name, "; aborting tunnel");
log::warning(logcat, "QUIC tunnel to {} failed during {}; aborting tunnel", addr, step_name);
it->second.tcp->close();
if (it->second.open_cb)
it->second.open_cb(false);
it->second.open_cb(false, nullptr);
client_tunnels_.erase(it);
}
return step_success;
@ -429,7 +450,11 @@ namespace llarp::quic
std::pair<SockAddr, uint16_t>
TunnelManager::open(
std::string_view remote_address, uint16_t port, OpenCallback on_open, SockAddr bind_addr)
std::string_view remote_address,
uint16_t port,
OpenCallback on_open,
CloseCallback on_close,
SockAddr bind_addr)
{
std::string remote_addr = lowercase_ascii_string(std::string{remote_address});
@ -494,24 +519,26 @@ namespace llarp::quic
"Unable to open an outgoing quic connection: too many existing connections"};
(next_pseudo_port_ = pport)++;
LogInfo("Bound TCP tunnel ", saddr, " for quic client :", pport);
log::trace(logcat, "Bound TCP tunnel {} for quic client :{}", saddr, pport);
// We are emplacing into client_tunnels_ here: beyond this point we must not throw until we
// return (or if we do, make sure we remove this row from client_tunnels_ first).
assert(client_tunnels_.count(pport) == 0);
auto& ct = client_tunnels_[pport];
ct.open_cb = std::move(on_open);
ct.close_cb = std::move(on_close);
ct.tcp = std::move(tcp_tunnel);
// We use this pport shared_ptr value on the listening tcp socket both to hand to pport into the
// accept handler, and to let the accept handler know that `this` is still safe to use.
ct.tcp->data(std::make_shared<uint16_t>(pport));
auto after_path = [this, port, pport = pport, remote_addr](auto maybe_convo) {
if (not continue_connecting(pport, (bool)maybe_convo, "path build", remote_addr))
auto after_path = [this, port, pport = pport, remote_addr](auto maybe_addr) {
if (maybe_addr)
{
make_client(port, *maybe_addr, *client_tunnels_.find(pport));
return;
SockAddr dest{maybe_convo->ToV6()};
dest.setPort(port);
make_client(dest, *client_tunnels_.find(pport));
}
continue_connecting(pport, false, "path build", remote_addr);
};
if (!maybe_remote)
@ -520,10 +547,8 @@ namespace llarp::quic
// then we have to build a path to that address.
service_endpoint_.LookupNameAsync(
remote_addr,
[this,
after_path = std::move(after_path),
pport = pport,
remote_addr = std::move(remote_addr)](auto maybe_remote) {
[this, after_path = std::move(after_path), pport = pport, remote_addr](
auto maybe_remote) {
if (not continue_connecting(
pport, (bool)maybe_remote, "endpoint ONS lookup", remote_addr))
return;
@ -536,8 +561,9 @@ namespace llarp::quic
auto& remote = *maybe_remote;
// See if we have an existing convo tag we can use to start things immediately
if (auto maybe_convo = service_endpoint_.GetBestConvoTagFor(remote))
after_path(maybe_convo);
if (auto maybe_convo = service_endpoint_.GetBestConvoTagFor(remote);
auto maybe_addr = service_endpoint_.GetEndpointWithConvoTag(*maybe_convo))
after_path(maybe_addr);
else
{
service_endpoint_.MarkAddressOutbound(remote);
@ -582,18 +608,47 @@ namespace llarp::quic
}
void
TunnelManager::make_client(const SockAddr& remote, std::pair<const uint16_t, ClientTunnel>& row)
TunnelManager::make_client(
const uint16_t port,
std::variant<service::Address, RouterID> remote,
std::pair<const uint16_t, ClientTunnel>& row)
{
assert(remote.getPort() > 0);
assert(port > 0);
auto& [pport, tunnel] = row;
assert(not tunnel.client);
tunnel.client = std::make_unique<Client>(service_endpoint_, remote, pport);
tunnel.client = std::make_unique<Client>(service_endpoint_, port, std::move(remote), pport);
auto conn = tunnel.client->get_connection();
conn->on_stream_available = [this, id = row.first](Connection&) {
LogDebug("QUIC connection :", id, " established; streams now available");
log::debug(logcat, "QUIC connection :{} established; streams now available", id);
if (auto it = client_tunnels_.find(id); it != client_tunnels_.end())
{
flush_pending_incoming(it->second);
if (it->second.open_cb)
{
log::trace(logcat, "Calling ClientTunnel.open_cb()");
it->second.open_cb(true, nullptr);
it->second.open_cb = nullptr; // only call once
}
}
else
log::warning(
logcat, "Connection.on_stream_available fired but we have no associated ClientTunnel!");
};
conn->on_closing = [this, id = row.first](Connection&) {
log::debug(logcat, "QUIC connection :{} closing, closing tunnel", id);
if (auto it = client_tunnels_.find(id); it != client_tunnels_.end())
{
if (it->second.close_cb)
{
log::trace(logcat, "Calling ClientTunnel.close_cb()");
it->second.close_cb(0, nullptr);
}
}
else
log::debug(logcat, "Connection.on_closing fired but no associated ClientTunnel found.");
this->close(id);
};
}
@ -626,21 +681,22 @@ namespace llarp::quic
}
catch (const std::exception& e)
{
LogWarn("Opening quic stream failed: ", e.what());
log::warning(logcat, "Opening quic stream failed: {}", e.what());
tcp_client->close();
}
LogTrace("Set up new stream");
log::trace(logcat, "Set up new stream");
conn.io_ready();
}
}
void
TunnelManager::receive_packet(const service::ConvoTag& tag, const llarp_buffer_t& buf)
TunnelManager::receive_packet(
std::variant<service::Address, RouterID> remote, const llarp_buffer_t& buf)
{
if (buf.sz <= 4)
{
LogWarn("invalid quic packet: packet size (", buf.sz, ") too small");
log::warning(logcat, "invalid quic packet: packet size ({}) too small", buf.sz);
return;
}
auto type = static_cast<std::byte>(buf.base[0]);
@ -650,51 +706,62 @@ namespace llarp::quic
auto ecn = static_cast<uint8_t>(buf.base[3]);
bstring_view data{reinterpret_cast<const std::byte*>(&buf.base[4]), buf.sz - 4};
SockAddr remote{tag.ToV6()};
huint16_t remote_port{pseudo_port};
quic::Endpoint* ep = nullptr;
if (type == CLIENT_TO_SERVER)
{
LogTrace("packet is client-to-server from client pport ", pseudo_port);
// Client-to-server: the header port is the return port
remote.setPort(pseudo_port);
log::debug(logcat, "packet is client-to-server from client pport {}", pseudo_port);
if (!server_)
{
LogWarn("Dropping incoming quic packet to server: no listeners");
log::warning(logcat, "Dropping incoming quic packet to server: no listeners");
return;
}
ep = server_.get();
}
else if (type == SERVER_TO_CLIENT)
{
LogTrace("packet is server-to-client to client pport ", pseudo_port);
log::debug(logcat, "packet is server-to-client to client pport {}", pseudo_port);
// Server-to-client: the header port tells us which client tunnel this is going to
if (auto it = client_tunnels_.find(pseudo_port); it != client_tunnels_.end())
ep = it->second.client.get();
if (not ep)
{
LogWarn("Incoming quic packet to invalid/closed client; dropping");
log::warning(logcat, "Incoming quic packet to invalid/closed client; dropping");
return;
}
// The server doesn't send back the port because we already know it 1-to-1 from our outgoing
// connection.
// connection
if (auto conn = static_cast<quic::Client&>(*ep).get_connection())
{
remote.setPort(conn->path.remote.port());
LogTrace("remote port is ", remote.getPort());
remote_port = llarp::net::ToHost(conn->path.remote.port());
log::debug(logcat, "remote port is {}", remote_port);
}
else
{
LogWarn("Incoming quic to a quic::Client without an active quic::Connection; dropping");
log::warning(
logcat, "Incoming quic to a quic::Client without an active quic::Connection; dropping");
return;
}
}
else
{
LogWarn("Invalid incoming quic packet type ", type, "; dropping packet");
log::warning(logcat, "Invalid incoming quic packet type {}; dropping packet", type);
return;
}
ep->receive_packet(remote, ecn, data);
log::debug(
logcat,
"remote_port = {}, pseudo_port = {} at line {}",
remote_port,
pseudo_port,
__LINE__);
auto remote_addr = Address{SockAddr{"::1"sv, remote_port}, std::move(remote)};
ep->receive_packet(std::move(remote_addr), ecn, data);
}
} // namespace llarp::quic

@ -78,7 +78,10 @@ namespace llarp::quic
forget(int id);
/// Called when open succeeds or times out.
using OpenCallback = std::function<void(bool success)>;
using OpenCallback = std::function<void(bool success, void* user_data)>;
/// Called when the tunnel is closed for any reason
using CloseCallback = std::function<void(int rv, void* user_data)>;
/// Opens a quic tunnel to some remote lokinet address. (Should only be called from the event
/// loop thread.)
@ -115,6 +118,7 @@ namespace llarp::quic
std::string_view remote_addr,
uint16_t port,
OpenCallback on_open = {},
CloseCallback on_close = {},
SockAddr bind_addr = {127, 0, 0, 1});
/// Start closing an outgoing tunnel; takes the ID returned by `open()`. Note that an existing
@ -129,7 +133,7 @@ namespace llarp::quic
/// \param buf - the raw arriving packet
///
void
receive_packet(const service::ConvoTag& tag, const llarp_buffer_t& buf);
receive_packet(std::variant<service::Address, RouterID> remote, const llarp_buffer_t& buf);
/// return true if we have any listeners added
inline bool
@ -147,6 +151,8 @@ namespace llarp::quic
std::unique_ptr<Client> client;
// Callback to invoke on quic connection established (true argument) or failed (false arg)
OpenCallback open_cb;
// Callback to invoke when the tunnel is closed, if it was successfully opened
CloseCallback close_cb;
// TCP listening socket
std::shared_ptr<uvw::TCPHandle> tcp;
// Accepted TCP connections
@ -170,7 +176,10 @@ namespace llarp::quic
uint16_t pseudo_port, bool step_success, std::string_view step_name, std::string_view addr);
void
make_client(const SockAddr& remote, std::pair<const uint16_t, ClientTunnel>& row);
make_client(
const uint16_t port,
std::variant<service::Address, RouterID> ep,
std::pair<const uint16_t, ClientTunnel>& row);
void
flush_pending_incoming(ClientTunnel& ct);

@ -18,6 +18,8 @@
namespace llarp
{
static auto logcat = log::Cat("rc-lookup");
void
RCLookupHandler::AddValidRouter(const RouterID& router)
{
@ -91,6 +93,11 @@ namespace llarp
{
itr_pair.first->second.push_back(callback);
}
log::trace(
logcat,
"RC Lookup for {} has {} pending callbacks.",
router,
itr_pair.first->second.size());
shouldDoLookup = itr_pair.second;
}

@ -72,6 +72,7 @@ namespace llarp
_lastTick = llarp::time_now_ms();
m_NextExploreAt = Clock_t::now();
m_Pump = _loop->make_waker([this]() { PumpLL(); });
m_Work = _loop->make_waker([this]() { submit_work(); });
}
Router::~Router()
@ -79,6 +80,15 @@ namespace llarp
llarp_dht_context_free(_dht);
}
void
Router::submit_work()
{
m_lmq->job([work = std::move(m_WorkJobs)]() {
for (const auto& job : work)
job();
});
}
void
Router::PumpLL()
{
@ -411,7 +421,7 @@ namespace llarp
if (log::get_level_default() != log::Level::off)
log::reset_level(conf.logging.m_logLevel);
log::clear_sinks();
// log::clear_sinks();
log::add_sink(log_type, log_type == log::Type::System ? "lokinet" : conf.logging.m_logFile);
// re-add rpc log sink if rpc enabled, else free it
@ -482,8 +492,8 @@ namespace llarp
LogError("RC is invalid, not saving");
return false;
}
if (m_isServiceNode)
_nodedb->Put(_rc);
if (IsServiceNode())
_nodedb->Put(rc());
QueueDiskIO([&]() { HandleSaveRC(); });
return true;
}
@ -1631,7 +1641,10 @@ namespace llarp
void
Router::QueueWork(std::function<void(void)> func)
{
m_lmq->job(std::move(func));
_loop->call([this, func = std::move(func)]() mutable {
m_WorkJobs.push_back(std::move(func));
m_Work->Trigger();
});
}
void

@ -78,6 +78,12 @@ namespace llarp
path::BuildLimiter m_PathBuildLimiter;
std::shared_ptr<EventLoopWakeup> m_Pump;
std::shared_ptr<EventLoopWakeup> m_Work;
std::vector<std::function<void()>> m_WorkJobs;
/// submits cpu heavy work from last event loop tick cycle to worker threads.
void
submit_work();
path::BuildLimiter&
pathBuildLimiter() override
@ -196,9 +202,11 @@ namespace llarp
return _vpnPlatform.get();
}
/// queue functionally pure cpu heavy work to be done in another thread.
void
QueueWork(std::function<void(void)> func) override;
/// queue disk io bound work to be done in the disk io thread.
void
QueueDiskIO(std::function<void(void)> func) override;

@ -366,4 +366,4 @@ namespace llarp::rpc
}
}
}
} // namespace llarp::rpc
} // namespace llarp::rpc

@ -223,8 +223,9 @@ namespace llarp::rpc
try
{
// FIXME: real open/close callbacks
auto [addr, id] = quic->open(
quicconnect.request.remoteHost, quicconnect.request.port, [](auto&&) {}, laddr);
quicconnect.request.remoteHost, quicconnect.request.port, nullptr, nullptr, laddr);
util::StatusObject status;
status["addr"] = addr.ToString();

@ -148,7 +148,10 @@ namespace llarp
if (auto* quic = GetQUICTunnel())
{
if (quic->hasListeners())
{
log::debug(logcat, "IntroSet setting QUIC as available protocol.");
introSet().supportedProtocols.push_back(ProtocolType::QUIC);
}
}
introSet().intros.clear();
@ -1054,26 +1057,29 @@ namespace llarp
{
if (not NameIsValid(name))
{
handler(ParseAddress(name));
log::warning(logcat, "\"{}\" is not a valid ONS name", name);
handler(std::nullopt);
return;
}
auto& cache = m_state->nameCache;
const auto maybe = cache.Get(name);
if (maybe.has_value())
{
log::debug(logcat, "Returning cached result for ONS name \"{}\"", name);
handler(maybe);
return;
}
LogInfo(Name(), " looking up LNS name: ", name);
// log::info(logcat, "{} looking up ONS name \"{}\"", Name(), name);
log::debug(logcat, "{} looking up ONS name \"{}\"", Name(), name);
auto paths = GetUniqueEndpointsForLookup();
// not enough paths
if (not ReadyToDoLookup(paths.size()))
{
LogWarn(
log::warning(
logcat,
"{} not enough paths for ONS lookup, have {} need {}",
Name(),
" not enough paths for lns lookup, have ",
paths.size(),
" need ",
MIN_ENDPOINTS_FOR_LNS_LOOKUP);
handler(std::nullopt);
return;
@ -1111,7 +1117,8 @@ namespace llarp
for (const auto& path : chosenpaths)
{
LogInfo(Name(), " lookup ", name, " from ", path->Endpoint());
// log::info(logcat, "{} lookup \"{}\" via {}", Name(), name, path->Endpoint());
log::debug(logcat, "{} lookup \"{}\" via {}", Name(), name, path->Endpoint());
auto job = new LookupNameJob{this, GenTXID(), name, resultHandler};
job->SendRequestViaPath(path, m_router);
}
@ -1696,37 +1703,6 @@ namespace llarp
return true;
}
bool
Endpoint::SendToOrQueue(ConvoTag tag, const llarp_buffer_t& pkt, ProtocolType t)
{
if (tag.IsZero())
{
LogWarn("SendToOrQueue failed: convo tag is zero");
return false;
}
LogDebug(Name(), " send ", pkt.sz, " bytes on T=", tag);
if (auto maybe = GetEndpointWithConvoTag(tag))
{
if (auto* ptr = std::get_if<Address>(&*maybe))
{
if (*ptr == m_Identity.pub.Addr())
{
ConvoTagTX(tag);
m_state->m_Router->TriggerPump();
if (not HandleInboundPacket(tag, pkt, t, 0))
return false;
ConvoTagRX(tag);
return true;
}
}
if (not SendToOrQueue(*maybe, pkt, t))
return false;
return true;
}
LogDebug("SendToOrQueue failed: no endpoint for convo tag ", tag);
return false;
}
bool
Endpoint::SendToOrQueue(const RouterID& addr, const llarp_buffer_t& buf, ProtocolType t)
{
@ -1879,7 +1855,7 @@ namespace llarp
bool
Endpoint::EnsurePathTo(
std::variant<Address, RouterID> addr,
std::function<void(std::optional<ConvoTag>)> hook,
std::function<void(std::optional<std::variant<Address, RouterID>>)> hook,
llarp_time_t timeout)
{
if (auto ptr = std::get_if<Address>(&addr))
@ -1888,14 +1864,15 @@ namespace llarp
{
ConvoTag tag{};
if (auto maybe = GetBestConvoTagFor(*ptr))
tag = *maybe;
if (auto maybe_tag = GetBestConvoTagFor(*ptr))
tag = *maybe_tag;
else
tag.Randomize();
PutSenderFor(tag, m_Identity.pub, true);
ConvoTagTX(tag);
Sessions()[tag].forever = true;
Loop()->call_soon([tag, hook]() { hook(tag); });
auto maybe_addr = GetEndpointWithConvoTag(tag);
Loop()->call_soon([maybe_addr, hook]() { hook(maybe_addr); });
return true;
}
if (not WantsOutboundSession(*ptr))
@ -1907,10 +1884,11 @@ namespace llarp
return EnsurePathToService(
*ptr,
[hook](auto, auto* ctx) {
[hook, this](auto, auto* ctx) {
if (ctx)
{
hook(ctx->currentConvoTag);
if (auto maybe_addr = GetEndpointWithConvoTag(ctx->currentConvoTag))
hook(maybe_addr);
}
else
{
@ -1921,10 +1899,11 @@ namespace llarp
}
if (auto ptr = std::get_if<RouterID>(&addr))
{
return EnsurePathToSNode(*ptr, [hook](auto, auto session, auto tag) {
return EnsurePathToSNode(*ptr, [hook, this](auto, auto session, auto tag) {
if (session)
{
hook(tag);
if (auto maybe_addr = GetEndpointWithConvoTag(tag))
hook(maybe_addr);
}
else
{
@ -2075,7 +2054,7 @@ namespace llarp
bool
Endpoint::SendToOrQueue(
const std::variant<Address, RouterID>& addr, const llarp_buffer_t& data, ProtocolType t)
std::variant<Address, RouterID> addr, const llarp_buffer_t& data, ProtocolType t)
{
return var::visit([&](auto& addr) { return SendToOrQueue(addr, data, t); }, addr);
}

@ -346,7 +346,7 @@ namespace llarp
bool
EnsurePathTo(
std::variant<Address, RouterID> addr,
std::function<void(std::optional<ConvoTag>)> hook,
std::function<void(std::optional<std::variant<Address, RouterID>>)> hook,
llarp_time_t timeout) override;
// passed a sendto context when we have a path established otherwise
@ -468,19 +468,20 @@ namespace llarp
// Looks up the ConvoTag and, if it exists, calls SendToOrQueue to send it to a remote client
// or a snode (or nothing, if the convo tag is unknown).
bool
SendToOrQueue(ConvoTag tag, const llarp_buffer_t& payload, ProtocolType t) override;
// bool
// SendToOrQueue(std::variant<service::Address, RouterID> addr, const llarp_buffer_t& payload,
// ProtocolType t) override;
// Send a to (or queues for sending) to either an address or router id
bool
SendToOrQueue(
const std::variant<Address, RouterID>& addr,
std::variant<Address, RouterID> addr,
const llarp_buffer_t& payload,
ProtocolType t);
ProtocolType t) override;
// Sends to (or queues for sending) to a remote client
bool
SendToOrQueue(const Address& addr, const llarp_buffer_t& payload, ProtocolType t);
SendToOrQueue(const Address& remote, const llarp_buffer_t& payload, ProtocolType t);
// Sends to (or queues for sending) to a router
bool

@ -0,0 +1,93 @@
#pragma once
#include "platform.hpp"
#include <unistd.h>
namespace llarp::vpn
{
class NullInterface : public NetworkInterface
{
/// we use a pipe because it isnt going to poll itself
int m_pipe[2];
public:
NullInterface(InterfaceInfo info) : NetworkInterface{std::move(info)}
{
::pipe(m_pipe);
}
virtual ~NullInterface()
{
::close(m_pipe[1]);
::close(m_pipe[0]);
}
int
PollFD() const override
{
return m_pipe[0];
}
net::IPPacket
ReadNextPacket() override
{
return net::IPPacket{};
}
/// write a packet to the interface
/// returns false if we dropped it
bool
WritePacket(net::IPPacket) override
{
return true;
}
};
class NopRouteManager : public IRouteManager
{
public:
void AddRoute(net::ipaddr_t, net::ipaddr_t) override{};
void DelRoute(net::ipaddr_t, net::ipaddr_t) override{};
void
AddDefaultRouteViaInterface(NetworkInterface&) override{};
void
DelDefaultRouteViaInterface(NetworkInterface&) override{};
void
AddRouteViaInterface(NetworkInterface&, IPRange) override{};
void
DelRouteViaInterface(NetworkInterface&, IPRange) override{};
std::vector<net::ipaddr_t>
GetGatewaysNotOnInterface(NetworkInterface&) override
{
return std::vector<net::ipaddr_t>{};
};
};
class NullPlatform : public Platform
{
NopRouteManager _routes;
public:
NullPlatform() : Platform{}, _routes{}
{}
virtual ~NullPlatform() = default;
std::shared_ptr<NetworkInterface>
ObtainInterface(InterfaceInfo info, AbstractRouter*) override
{
return std::make_shared<NullInterface>(std::move(info));
}
IRouteManager&
RouteManager() override
{
return _routes;
}
};
} // namespace llarp::vpn

@ -11,6 +11,9 @@
#include "linux.hpp"
#endif
#endif
#ifdef __APPLE__
#include "null.hpp"
#endif
#include <exception>
@ -38,8 +41,11 @@ namespace llarp::vpn
#endif
#endif
#ifdef __APPLE__
throw std::runtime_error{"not supported"};
plat = std::make_shared<NullPlatform>();
#endif
if (not plat)
throw std::runtime_error{"not supported"};
return plat;
}

@ -0,0 +1,16 @@
add_stream_data for stream <> returned:
[-240,0]
increase buflen until == 1200
add_stream_data for stream <> returned:
[1200,-1]
Send packet
[0,-1]
Congestion limited or nothing to do
move on
add_stream_data for non-stream returned:
[1200,-1] OR [{>0},-1]
send data packet
[0,-1]
Congestion limited or nothing to do

@ -0,0 +1,12 @@
#!/bin/bash
rm -rf build && \
mkdir build && \
cd build && \
cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_EMBEDDED_LOKINET=ON -DLIBLOKINET_TEST_UTILS=ON -DOXEN_LOGGING_RELEASE_TRACE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=1 && \
make -j6 && \
mkdir -p logs && \
mkdir -p tcp_connect_data_dir/testnet && \
cp ../contrib/bootstrap/mainnet.signed ./tcp_connect_data_dir/bootstrap.signed && \
cp ../contrib/bootstrap/testnet.signed ./tcp_connect_data_dir/testnet/bootstrap.signed && \
sudo setcap cap_net_admin,cap_net_bind_service=+eip daemon/lokinet
Loading…
Cancel
Save