diff --git a/.clang-format b/.clang-format index 3f18c42194..3f5427ffb5 100644 --- a/.clang-format +++ b/.clang-format @@ -1,19 +1,19 @@ BasedOnStyle: Google + +# alignment AlignAfterOpenBracket: AlwaysBreak AlignConsecutiveAssignments: 'false' AlignConsecutiveDeclarations: 'false' AlignEscapedNewlinesLeft: 'true' AlignOperands: 'false' AlignTrailingComments: 'true' -AllowShortBlocksOnASingleLine: 'false' -AllowShortCaseLabelsOnASingleLine: 'false' -AllowShortFunctionsOnASingleLine: None -AllowShortIfStatementsOnASingleLine: 'false' -AllowShortLoopsOnASingleLine: 'false' -AlwaysBreakAfterDefinitionReturnType: All -AlwaysBreakAfterReturnType: All -AlwaysBreakTemplateDeclarations: 'true' -BreakBeforeBinaryOperators: NonAssignment +ColumnLimit: 120 +PointerAlignment: Left +QualifierAlignment: Custom +QualifierOrder: ['inline', 'static', 'constexpr', 'const', 'type'] +ReferenceAlignment: Left + +# bracing BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: true @@ -31,11 +31,31 @@ BraceWrapping: SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false + +# breaking +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: 'true' +BreakBeforeBinaryOperators: NonAssignment BreakBeforeTernaryOperators: 'true' -BreakConstructorInitializersBeforeComma: 'true' -Cpp11BracedListStyle: 'true' -KeepEmptyLinesAtTheStartOfBlocks: 'false' +BreakConstructorInitializers: BeforeColon + +# indentation +AccessModifierOffset: -2 +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +IndentWidth: 4 NamespaceIndentation: All + +# shorties +AllowShortBlocksOnASingleLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: 'false' +AllowShortLoopsOnASingleLine: 'false' + +# spacing +KeepEmptyLinesAtTheStartOfBlocks: 'false' PenaltyBreakString: '3' SpaceBeforeParens: ControlStatements SpacesInAngles: 'false' @@ -44,18 +64,30 @@ SpacesInParentheses: 'false' SpacesInSquareBrackets: 'false' Standard: Cpp11 UseTab: Never -SortIncludes: false -ColumnLimit: 100 -# treat pointers and reference declarations as if part of the type -DerivePointerAlignment: false -PointerAlignment: Left - -# when wrapping function calls/declarations, force each parameter to have its own line +# wrapping +PackConstructorInitializers: NextLine BinPackParameters: 'false' BinPackArguments: 'false' -# TODO: uncomment me when we are reading to rearrange the header includes -# IncludeBlocks: Regroup -# IncludeCategories: 'llarp/' - +# Include block sorting in the following order: +# - Main header for source file (clang-format default prioritizes this first) +# - Relative path includes in quotation marks +# - Absolute path includes in angle brackets +# - External dependencies +# - System dependencies +SortIncludes: CaseInsensitive +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '".+\.h' + Priority: 2 + - Regex: '^' + Priority: 4 + - Regex: '' + Priority: 5 + - Regex: '^<.*\.h(pp)?>$' + Priority: 6 + - Regex: '(<)(.)+(>)' + Priority: 7 diff --git a/.clang-tidy b/.clang-tidy index b9b50e74bf..dbcf69f4bc 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,2 +1,8 @@ HeaderFilterRegex: 'llarp/.*' -Checks: 'readability-else-after-return,clang-analyzer-core-*,modernize-*,-modernize-use-trailing-return-type,-modernize-use-nodiscard,bugprone-*,-bugprone-easily-swappable-parameters' +Checks: +'readability-else-after-return, +clang-analyzer-core-*,modernize-*, +-modernize-use-trailing-return-type, +-modernize-use-nodiscard, +bugprone-*, +-bugprone-easily-swappable-parameters' diff --git a/.drone.jsonnet b/.drone.jsonnet index 793ee75855..9a4827a13d 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -9,6 +9,7 @@ local default_deps_base = [ 'libsqlite3-dev', 'libcurl4-openssl-dev', 'libzmq3-dev', + 'libgnutls28-dev', 'make', ]; local default_deps_nocxx = ['libsodium-dev'] + default_deps_base; // libsodium-dev needs to be >= 1.0.18 @@ -30,11 +31,25 @@ local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https:/ local apt_get_quiet = 'apt-get -o=Dpkg::Use-Pty=0 -q'; +local kitware_repo(distro) = [ + 'eatmydata ' + apt_get_quiet + ' install -y curl ca-certificates', + 'curl -sSL https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - >/usr/share/keyrings/kitware-archive-keyring.gpg', + 'echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ ' + distro + ' main" >/etc/apt/sources.list.d/kitware.list', + 'eatmydata ' + apt_get_quiet + ' update', +]; + +local debian_backports(distro, pkgs) = [ + 'echo "deb http://deb.debian.org/debian ' + distro + '-backports main" >/etc/apt/sources.list.d/' + distro + '-backports.list', + 'eatmydata ' + apt_get_quiet + ' update', + 'eatmydata ' + apt_get_quiet + ' install -y ' + std.join(' ', std.map(function(x) x + '/' + distro + '-backports', pkgs)), +]; + // Regular build on a debian-like system: local debian_pipeline(name, image, arch='amd64', deps=default_deps, + extra_setup=[], build_type='Release', lto=false, werror=true, @@ -42,7 +57,7 @@ local debian_pipeline(name, local_mirror=true, extra_cmds=[], jobs=6, - tests=true, + tests=false, // FIXME TODO: temporary until test suite is fixed oxen_repo=false, allow_fail=false) = { kind: 'pipeline', @@ -70,13 +85,14 @@ local debian_pipeline(name, 'echo deb http://deb.oxen.io $$(lsb_release -sc) main >/etc/apt/sources.list.d/oxen.list', 'eatmydata ' + apt_get_quiet + ' update', ] else [] - ) + [ + ) + extra_setup + + [ 'eatmydata ' + apt_get_quiet + ' dist-upgrade -y', 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y gdb cmake git pkg-config ccache ' + std.join(' ', deps), 'mkdir build', 'cd build', 'cmake .. -DWITH_SETCAP=OFF -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always -DCMAKE_BUILD_TYPE=' + build_type + ' ' + - (if build_type == 'Debug' then ' -DWARN_DEPRECATED=OFF ' else '') + + '-DWARN_DEPRECATED=OFF ' + (if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') + '-DWITH_LTO=' + (if lto then 'ON ' else 'OFF ') + '-DWITH_TESTS=' + (if tests then 'ON ' else 'OFF ') + @@ -90,6 +106,23 @@ local debian_pipeline(name, }, ], }; +local local_gnutls(jobs=6, prefix='/usr/local') = [ + apt_get_quiet + ' install -y curl ca-certificates', + 'curl -sSL https://ftp.gnu.org/gnu/nettle/nettle-3.9.1.tar.gz | tar xfz -', + 'curl -sSL https://www.gnupg.org/ftp/gcrypt/gnutls/v3.8/gnutls-3.8.0.tar.xz | tar xfJ -', + 'export PKG_CONFIG_PATH=' + prefix + '/lib/pkgconfig:' + prefix + '/lib64/pkgconfig', + 'export LD_LIBRARY_PATH=' + prefix + '/lib:' + prefix + '/lib64', + 'cd nettle-3.9.1', + './configure --prefix=' + prefix + ' CC="ccache gcc"', + 'make -j' + jobs, + 'make install', + 'cd ..', + 'cd gnutls-3.8.0', + './configure --prefix=' + prefix + ' --with-included-libtasn1 --with-included-unistring --without-p11-kit --disable-libdane --disable-cxx --without-tpm --without-tpm2 CC="ccache gcc"', + 'make -j' + jobs, + 'make install', + 'cd ..', +]; local apk_builder(name, image, extra_cmds=[], allow_fail=false, jobs=6) = { kind: 'pipeline', type: 'docker', @@ -301,7 +334,7 @@ local mac_builder(name, 'ulimit -n 1024', // because macos sets ulimit to 256 for some reason yeah idk './contrib/mac-configure.sh ' + ci_dep_mirror(local_mirror) + - (if build_type == 'Debug' then ' -DWARN_DEPRECATED=OFF ' else '') + + '-DWARN_DEPRECATED=OFF ' + codesign, 'cd build-mac', // We can't use the 'package' target here because making a .dmg requires an active logged in @@ -351,30 +384,32 @@ local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = { 'echo "Building on ${DRONE_STAGE_MACHINE}"', apt_get_quiet + ' update', apt_get_quiet + ' install -y eatmydata', - 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y git clang-format-14 jsonnet', + 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y git clang-format-15 jsonnet', './contrib/ci/drone-format-verify.sh', ], }], }, // documentation builder - docs_pipeline('Documentation', - docker_base + 'docbuilder', - extra_cmds=['UPLOAD_OS=docs ./contrib/ci/drone-static-upload.sh']), + //docs_pipeline('Documentation', + // docker_base + 'docbuilder', + // extra_cmds=['UPLOAD_OS=docs ./contrib/ci/drone-static-upload.sh']), // Various debian builds debian_pipeline('Debian sid (amd64)', docker_base + 'debian-sid'), debian_pipeline('Debian sid/Debug (amd64)', docker_base + 'debian-sid', build_type='Debug'), - clang(13), - full_llvm(13), + clang(16), + full_llvm(16), debian_pipeline('Debian stable (i386)', docker_base + 'debian-stable/i386'), - debian_pipeline('Debian buster (amd64)', docker_base + 'debian-buster', cmake_extra='-DDOWNLOAD_SODIUM=ON'), + debian_pipeline('Debian bullseye (amd64)', + docker_base + 'debian-bullseye', + extra_setup=debian_backports('bullseye', ['cmake']) + local_gnutls()), debian_pipeline('Ubuntu latest (amd64)', docker_base + 'ubuntu-rolling'), debian_pipeline('Ubuntu LTS (amd64)', docker_base + 'ubuntu-lts'), - debian_pipeline('Ubuntu bionic (amd64)', - docker_base + 'ubuntu-bionic', - deps=['g++-8'] + default_deps_nocxx, - cmake_extra='-DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8', - oxen_repo=true), + debian_pipeline('Ubuntu focal (amd64)', + docker_base + 'ubuntu-focal', + deps=['g++-10'] + default_deps_nocxx, + extra_setup=kitware_repo('focal') + local_gnutls(), + cmake_extra='-DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10'), // ARM builds (ARM64 and armhf) debian_pipeline('Debian sid (ARM64)', docker_base + 'debian-sid', arch='arm64', jobs=4), @@ -399,15 +434,16 @@ local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = { './contrib/ci/drone-static-upload.sh', ]), - // Static build (on bionic) which gets uploaded to builds.lokinet.dev: - debian_pipeline('Static (bionic amd64)', - docker_base + 'ubuntu-bionic', - deps=['g++-8', 'python3-dev', 'automake', 'libtool'], + // Static build (on focal) which gets uploaded to builds.lokinet.dev: + debian_pipeline('Static (focal amd64)', + docker_base + 'ubuntu-focal', + deps=['g++-10', 'python3-dev', 'automake', 'libtool'], + extra_setup=kitware_repo('focal'), lto=true, tests=false, oxen_repo=true, cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON ' + - '-DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8 ' + + '-DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 ' + '-DCMAKE_CXX_FLAGS="-march=x86-64 -mtune=haswell" ' + '-DCMAKE_C_FLAGS="-march=x86-64 -mtune=haswell" ' + '-DNATIVE_BUILD=OFF -DWITH_SYSTEMD=OFF -DWITH_BOOTSTRAP=OFF -DBUILD_LIBLOKINET=OFF', @@ -416,10 +452,11 @@ local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = { './contrib/ci/drone-static-upload.sh', ]), // Static armhf build (gets uploaded) - debian_pipeline('Static (buster armhf)', - docker_base + 'debian-buster/arm32v7', + debian_pipeline('Static [FIXME] (bullseye armhf)', + docker_base + 'debian-bullseye/arm32v7', arch='arm64', deps=['g++', 'python3-dev', 'automake', 'libtool'], + extra_setup=debian_backports('bullseye', ['cmake']), cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON ' + '-DCMAKE_CXX_FLAGS="-march=armv7-a+fp -Wno-psabi" -DCMAKE_C_FLAGS="-march=armv7-a+fp" ' + '-DNATIVE_BUILD=OFF -DWITH_SYSTEMD=OFF -DWITH_BOOTSTRAP=OFF', @@ -427,8 +464,10 @@ local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = { './contrib/ci/drone-check-static-libs.sh', 'UPLOAD_OS=linux-armhf ./contrib/ci/drone-static-upload.sh', ], + allow_fail=true, // XXX FIXME: build currently fails! jobs=4), + /* // integration tests debian_pipeline('Router Hive', docker_base + 'ubuntu-lts', @@ -440,6 +479,7 @@ local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = { deb_builder(docker_base + 'debian-bullseye-builder', 'bullseye', 'debian/bullseye'), deb_builder(docker_base + 'ubuntu-jammy-builder', 'jammy', 'ubuntu/jammy'), deb_builder(docker_base + 'debian-sid-builder', 'sid', 'debian/sid', arch='arm64'), + */ // Macos builds: mac_builder('macOS (Release)', extra_cmds=[ diff --git a/.gitignore b/.gitignore index d0dfc98445..0b20f7ba53 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,7 @@ testnet_tmp vsproject/ .vs -daemon.ini +*.ini .gradle/ @@ -67,3 +67,4 @@ regdbhelper.dll # xcode xcuserdata/ +scc.py diff --git a/.gitmodules b/.gitmodules index 837e3a6575..941ef0f2d2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "external/nlohmann"] path = external/nlohmann url = https://github.com/nlohmann/json.git -[submodule "external/ghc-filesystem"] - path = external/ghc-filesystem - url = https://github.com/gulrak/filesystem.git [submodule "test/Catch2"] path = test/Catch2 url = https://github.com/catchorg/Catch2 @@ -17,25 +14,21 @@ [submodule "external/oxen-mq"] path = external/oxen-mq url = https://github.com/oxen-io/oxen-mq -[submodule "external/uvw"] - path = external/uvw - url = https://github.com/jagerman/uvw.git [submodule "external/cpr"] path = external/cpr url = https://github.com/whoshuu/cpr -[submodule "external/ngtcp2"] - path = external/ngtcp2 - url = https://github.com/ngtcp2/ngtcp2.git - branch = v0.1.0 -[submodule "external/oxen-encoding"] - path = external/oxen-encoding - url = https://github.com/oxen-io/oxen-encoding.git -[submodule "external/oxen-logging"] - path = external/oxen-logging - url = https://github.com/oxen-io/oxen-logging.git [submodule "gui"] path = gui url = https://github.com/oxen-io/lokinet-gui.git [submodule "external/CLI11"] path = external/CLI11 url = https://github.com/CLIUtils/CLI11.git +[submodule "external/oxen-libquic"] + path = external/oxen-libquic + url = https://github.com/oxen-io/oxen-libquic.git +[submodule "external/oxen-encoding"] + path = external/oxen-encoding + url = https://github.com/oxen-io/oxen-encoding.git +[submodule "external/libevent"] + path = external/libevent + url = https://github.com/libevent/libevent.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 039262a2bc..5bb6f298f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,18 @@ cmake_minimum_required(VERSION 3.13...3.24) # 3.13 is buster's version +# Cmake 3.24+ breaks extraction timestamps by default, hurray, but the option to not break +# timestamps fails in cmake <3.24, extra hurray! +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + cmake_policy(SET CMP0135 OLD) +endif() + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Has to be set before `project()`, and ignored on non-macos: set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "macOS deployment target (Apple clang only)") option(BUILD_DAEMON "build lokinet daemon and associated utils" ON) - +option(GRAPH_DEPENDENCIES "Produce graphviz representation of cmake dependencies" OFF) set(LANGS C CXX) if(APPLE AND BUILD_DAEMON) @@ -35,8 +41,6 @@ if(APPLE) set(LOKINET_APPLE_BUILD 5) endif() -set(RELEASE_MOTTO "Our Lord And Savior" CACHE STRING "Release motto") - list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(DEFAULT_WITH_BOOTSTRAP ON) @@ -70,14 +74,20 @@ include(cmake/enable_lto.cmake) option(CROSS_PLATFORM "cross compiler platform" "Linux") option(CROSS_PREFIX "toolchain cross compiler prefix" "") +option(BUILD_SHARED_LIBS "Build shared library" OFF) option(BUILD_STATIC_DEPS "Download, build, and statically link against core dependencies" OFF) 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 AND BUILD_SHARED_LIBS) + message(FATAL_ERROR "Incompatible options: BUILD_STATIC_DEPS cannot be used with BUILD_SHARED_LIBS") +endif() + if(BUILD_STATIC_DEPS) set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE) - include(StaticBuild) + include(cmake/StaticBuild.cmake) endif() if(NOT CMAKE_BUILD_TYPE) @@ -90,21 +100,16 @@ if(CMAKE_BUILD_TYPE MATCHES "[Dd][Ee][Bb][Uu][Gg]") add_definitions(-DLOKINET_DEBUG) endif() -option(WARN_DEPRECATED "show deprecation warnings" ${debug}) - -if(BUILD_STATIC_DEPS AND STATIC_LINK) - message(STATUS "we are building static deps so we won't build shared libs") - set(BUILD_SHARED_LIBS OFF CACHE BOOL "") -endif() +option(WARN_DEPRECATED "show deprecation warnings" OFF) include(CheckCXXSourceCompiles) include(CheckLibraryExists) -set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) include(cmake/target_link_libraries_system.cmake) include(cmake/add_import_library.cmake) @@ -129,8 +134,6 @@ macro_ensure_out_of_source_build("${PROJECT_NAME} requires an out-of-source buil set(CMAKE_POSITION_INDEPENDENT_CODE ON) include(cmake/unix.cmake) -include(cmake/check_for_std_optional.cmake) -include(cmake/check_for_std_filesystem.cmake) if(NOT WIN32) if(IOS OR ANDROID) @@ -148,18 +151,6 @@ endif() find_package(PkgConfig REQUIRED) -if(NOT BUILD_STATIC_DEPS) - pkg_check_modules(LIBUV libuv>=1.18.0 IMPORTED_TARGET) -endif() -if(LIBUV_FOUND AND NOT BUILD_STATIC_DEPS) - add_library(libuv INTERFACE) - target_link_libraries(libuv INTERFACE PkgConfig::LIBUV) -else() - if(NOT BUILD_STATIC_DEPS) - message(FATAL_ERROR "Could not find libuv >= 1.28.0; install it on your system or use -DBUILD_STATIC_DEPS=ON") - endif() -endif() - if(NOT TARGET sodium) # Allow -D DOWNLOAD_SODIUM=FORCE to download without even checking for a local libsodium option(DOWNLOAD_SODIUM "Allow libsodium to be downloaded and built locally if not found on the system" OFF) @@ -184,18 +175,22 @@ if(NOT TARGET sodium) endif() set(warning_flags -Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Werror=vla) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") list(APPEND warning_flags -Wno-unknown-warning-option) endif() -if(WARN_DEPRECATED) - list(APPEND warning_flags -Wdeprecated-declarations) -else() - list(APPEND warning_flags -Wno-deprecated-declarations) + +if(WARNINGS_AS_ERRORS) + list(APPEND warning_flags -Werror -Wno-error=array-bounds) endif() +list(APPEND warning_flags -Wno-deprecated-declarations) + +add_library(lokinet-base-internal_warnings INTERFACE) + # If we blindly add these directly as compile_options then they get passed to swiftc on Apple and # break, so we use a generate expression to set them only for C++/C/ObjC -add_compile_options("$<$,$,$>:${warning_flags}>") +target_compile_options(lokinet-base-internal_warnings INTERFACE "$<$,$,$>:${warning_flags}>") if(XSAN) string(APPEND CMAKE_CXX_FLAGS_DEBUG " -fsanitize=${XSAN} -fno-omit-frame-pointer -fno-sanitize-recover") @@ -229,6 +224,7 @@ if(NOT APPLE) endif() endif() +# now have libevent2.21 w/ pthreads set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) @@ -261,23 +257,32 @@ pkg_check_modules(SD libsystemd IMPORTED_TARGET) # Default WITH_SYSTEMD to true if we found it option(WITH_SYSTEMD "enable systemd integration for sd_notify" ${SD_FOUND}) -# Base interface target where we set up global link libraries, definitions, includes, etc. -add_library(base_libs INTERFACE) +add_subdirectory(external) + +# interface library for setting common includes, linkage and flags +add_library(lokinet-base INTERFACE) +target_include_directories(lokinet-base INTERFACE . include) +target_link_libraries(lokinet-base INTERFACE quic nlohmann_json::nlohmann_json) + +target_compile_features(lokinet-base INTERFACE cxx_std_20) + +add_library(lokinet-base-internal INTERFACE) +target_include_directories(lokinet-base-internal INTERFACE include/lokinet) + +target_link_libraries(lokinet-base-internal INTERFACE lokinet-base-internal_warnings) if(WITH_SYSTEMD AND (NOT ANDROID)) if(NOT SD_FOUND) message(FATAL_ERROR "libsystemd not found") endif() - target_link_libraries(base_libs INTERFACE PkgConfig::SD) - target_compile_definitions(base_libs INTERFACE WITH_SYSTEMD) + target_link_libraries(lokinet-base INTERFACE PkgConfig::SD) + target_compile_definitions(lokinet-base INTERFACE WITH_SYSTEMD) endif() -add_subdirectory(external) - if(USE_JEMALLOC AND NOT STATIC_LINK) pkg_check_modules(JEMALLOC jemalloc IMPORTED_TARGET) if(JEMALLOC_FOUND) - target_link_libraries(base_libs INTERFACE PkgConfig::JEMALLOC) + target_link_libraries(lokinet-base INTERFACE PkgConfig::JEMALLOC) else() message(STATUS "jemalloc not found, not linking to jemalloc") endif() @@ -287,8 +292,8 @@ endif() if(ANDROID) - target_link_libraries(base_libs INTERFACE log) - target_compile_definitions(base_libs INTERFACE ANDROID) + target_link_libraries(lokinet-base INTERFACE log) + target_compile_definitions(lokinet-base INTERFACE ANDROID) set(ANDROID_PLATFORM_SRC android/ifaddrs.c) endif() diff --git a/cmake/CMakeGraphVizOptions.cmake b/cmake/CMakeGraphVizOptions.cmake new file mode 100644 index 0000000000..322db57e3f --- /dev/null +++ b/cmake/CMakeGraphVizOptions.cmake @@ -0,0 +1,4 @@ +set(GRAPHVIZ_GRAPH_NAME "graph.dot" CACHE STRING "") +set(GRAPHVIZ_GENERATE_PER_TARGET FALSE CACHE BOOL "") +set(GRAPHVIZ_GENERATE_DEPENDERS FALSE CACHE BOOL "") +set(GRAPHVIZ_OBJECT_LIBS OFF CACHE BOOL "") diff --git a/cmake/DownloadLibCurl.cmake b/cmake/DownloadLibCurl.cmake deleted file mode 100644 index 022a7377c4..0000000000 --- a/cmake/DownloadLibCurl.cmake +++ /dev/null @@ -1,57 +0,0 @@ -set(LIBCURL_PREFIX ${CMAKE_BINARY_DIR}/libcurl) -set(LIBCURL_URL https://github.com/curl/curl/releases/download/curl-7_67_0/curl-7.67.0.tar.xz) -set(LIBCURL_HASH SHA256=f5d2e7320379338c3952dcc7566a140abb49edb575f9f99272455785c40e536c) - -if(CURL_TARBALL_URL) - # make a build time override of the tarball url so we can fetch it if the original link goes away - set(LIBCURL_URL ${CURL_TARBALL_URL}) -endif() - - -file(MAKE_DIRECTORY ${LIBCURL_PREFIX}/include) - -include(ExternalProject) -include(ProcessorCount) -ProcessorCount(PROCESSOR_COUNT) -if(PROCESSOR_COUNT EQUAL 0) - set(PROCESSOR_COUNT 1) -endif() - -set(libcurl_cc ${CMAKE_C_COMPILER}) -if(CCACHE_PROGRAM) - set(libcurl_cc "${CCACHE_PROGRAM} ${libcurl_cc}") -endif() -set(CURL_CONFIGURE ./configure --prefix=${LIBCURL_PREFIX} - --without-ssl --without-nss --without-gnutls --without-mbedtls --without-wolfssl --without-mesalink - --without-bearssl --without-ca-bundle --without-libidn2 --without-zlib --without-nghttp2 --without-nghttp3 - --without-quiche --without-zsh-functions-dir --without-fish-functions-dir - --without-librtmp --without-ca-fallback --without-ca-path --without-brotli --without-libpsl - --disable-manual --disable-dict --disable-file --disable-ftp --disable-gopher --disable-imap --disable-ldap --disable-ldaps - --disable-pop3 --disable-rtsp --disable-smtp --disable-telnet --disable-tftp - --enable-static --disable-shared CC=${libcurl_cc}) - -if (CMAKE_C_COMPILER_ARG1) - set(CURL_CONFIGURE ${CURL_CONFIGURE} CPPFLAGS=${CMAKE_C_COMPILER_ARG1}) -endif() - -if (CROSS_TARGET) - set(CURL_CONFIGURE ${CURL_CONFIGURE} --target=${CROSS_TARGET} --host=${CROSS_TARGET}) -endif() - - -ExternalProject_Add(libcurl_external - BUILD_IN_SOURCE ON - PREFIX ${LIBCURL_PREFIX} - URL ${LIBCURL_URL} - URL_HASH ${LIBCURL_HASH} - CONFIGURE_COMMAND ${CURL_CONFIGURE} - BUILD_COMMAND make -j${PROCESSOR_COUNT} - BUILD_BYPRODUCTS ${LIBCURL_PREFIX}/lib/libcurl.a ${LIBCURL_PREFIX}/include -) - -add_library(curl_vendor STATIC IMPORTED GLOBAL) -add_dependencies(curl_vendor curl_external) -set_target_properties(curl_vendor PROPERTIES - IMPORTED_LOCATION ${LIBCURL_PREFIX}/lib/libcurl.a - INTERFACE_INCLUDE_DIRECTORIES ${LIBCURL_PREFIX}/include -) diff --git a/cmake/FindLibUV.cmake b/cmake/FindLibUV.cmake deleted file mode 100644 index 4187efbe1f..0000000000 --- a/cmake/FindLibUV.cmake +++ /dev/null @@ -1,95 +0,0 @@ -# Taken from https://github.com/neovim/neovim/blob/master/cmake/FindLibUV.cmake -# - Try to find libuv -# Once done, this will define -# -# LIBUV_FOUND - system has libuv -# LIBUV_INCLUDE_DIRS - the libuv include directories -# LIBUV_LIBRARIES - link these to use libuv -# -# Set the LIBUV_USE_STATIC variable to specify if static libraries should -# be preferred to shared ones. - -find_package(PkgConfig) -if (PKG_CONFIG_FOUND) - pkg_check_modules(PC_LIBUV QUIET libuv) -endif() - -find_path(LIBUV_INCLUDE_DIR uv.h - HINTS ${PC_LIBUV_INCLUDEDIR} ${PC_LIBUV_INCLUDE_DIRS}) - -# If we're asked to use static linkage, add libuv.a as a preferred library name. -if(LIBUV_USE_STATIC) - list(APPEND LIBUV_NAMES - "${CMAKE_STATIC_LIBRARY_PREFIX}uv${CMAKE_STATIC_LIBRARY_SUFFIX}") -endif(LIBUV_USE_STATIC) - -list(APPEND LIBUV_NAMES uv) - -find_library(LIBUV_LIBRARY NAMES ${LIBUV_NAMES} - HINTS ${PC_LIBUV_LIBDIR} ${PC_LIBUV_LIBRARY_DIRS}) - -mark_as_advanced(LIBUV_INCLUDE_DIR LIBUV_LIBRARY) - -if(PC_LIBUV_LIBRARIES) - list(REMOVE_ITEM PC_LIBUV_LIBRARIES uv) -endif() - -set(LIBUV_LIBRARIES ${LIBUV_LIBRARY} ${PC_LIBUV_LIBRARIES}) -set(LIBUV_INCLUDE_DIRS ${LIBUV_INCLUDE_DIR}) - -# Deal with the fact that libuv.pc is missing important dependency information. - -include(CheckLibraryExists) - -check_library_exists(dl dlopen "dlfcn.h" HAVE_LIBDL) -if(HAVE_LIBDL) - list(APPEND LIBUV_LIBRARIES dl) -endif() - -check_library_exists(kstat kstat_lookup "kstat.h" HAVE_LIBKSTAT) -if(HAVE_LIBKSTAT) - list(APPEND LIBUV_LIBRARIES kstat) -endif() - -check_library_exists(kvm kvm_open "kvm.h" HAVE_LIBKVM) -if(HAVE_LIBKVM AND NOT CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") - list(APPEND LIBUV_LIBRARIES kvm) -endif() - -check_library_exists(nsl gethostbyname "nsl.h" HAVE_LIBNSL) -if(HAVE_LIBNSL) - list(APPEND LIBUV_LIBRARIES nsl) -endif() - -check_library_exists(perfstat perfstat_cpu "libperfstat.h" HAVE_LIBPERFSTAT) -if(HAVE_LIBPERFSTAT) - list(APPEND LIBUV_LIBRARIES perfstat) -endif() - -check_library_exists(rt clock_gettime "time.h" HAVE_LIBRT) -if(HAVE_LIBRT) - list(APPEND LIBUV_LIBRARIES rt) -endif() - -check_library_exists(sendfile sendfile "" HAVE_LIBSENDFILE) -if(HAVE_LIBSENDFILE) - list(APPEND LIBUV_LIBRARIES sendfile) -endif() - -if(WIN32) - # check_library_exists() does not work for Win32 API calls in X86 due to name - # mangling calling conventions - list(APPEND LIBUV_LIBRARIES iphlpapi) - list(APPEND LIBUV_LIBRARIES psapi) - list(APPEND LIBUV_LIBRARIES userenv) - list(APPEND LIBUV_LIBRARIES ws2_32) -endif() - -include(FindPackageHandleStandardArgs) - -# handle the QUIETLY and REQUIRED arguments and set LIBUV_FOUND to TRUE -# if all listed variables are TRUE -find_package_handle_standard_args(LibUV DEFAULT_MSG - LIBUV_LIBRARY LIBUV_INCLUDE_DIR) - -mark_as_advanced(LIBUV_INCLUDE_DIR LIBUV_LIBRARY) diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index 5663fa66da..0fe4347f2b 100644 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -41,25 +41,18 @@ set(SODIUM_SOURCE libsodium-${SODIUM_VERSION}.tar.gz) set(SODIUM_HASH SHA512=17e8638e46d8f6f7d024fe5559eccf2b8baf23e143fadd472a7d29d228b186d86686a5e6920385fe2020729119a5f12f989c3a782afbd05a8db4819bb18666ef CACHE STRING "libsodium source hash") -set(ZMQ_VERSION 4.3.4 CACHE STRING "libzmq version") +set(ZMQ_VERSION 4.3.5 CACHE STRING "libzmq version") set(ZMQ_MIRROR ${LOCAL_MIRROR} https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION} CACHE STRING "libzmq mirror(s)") set(ZMQ_SOURCE zeromq-${ZMQ_VERSION}.tar.gz) -set(ZMQ_HASH SHA512=e198ef9f82d392754caadd547537666d4fba0afd7d027749b3adae450516bcf284d241d4616cad3cb4ad9af8c10373d456de92dc6d115b037941659f141e7c0e +set(ZMQ_HASH SHA512=a71d48aa977ad8941c1609947d8db2679fc7a951e4cd0c3a1127ae026d883c11bd4203cf315de87f95f5031aec459a731aec34e5ce5b667b8d0559b157952541 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_VERSION 1.3 CACHE STRING "zlib version") set(ZLIB_MIRROR ${LOCAL_MIRROR} https://zlib.net CACHE STRING "zlib mirror(s)") set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.xz) -set(ZLIB_HASH SHA256=d14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98 +set(ZLIB_HASH SHA256=8a9ba2898e1d0d774eca6ba5b4627a11e5588ba85c8851336eb38de4683050a7 CACHE STRING "zlib source hash") set(CURL_VERSION 7.86.0 CACHE STRING "curl version") @@ -219,16 +212,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 BUILD_BYPRODUCTS @@ -237,7 +220,6 @@ build_external(zlib ) add_static_target(zlib zlib_external libz.a) - set(openssl_system_env "") set(openssl_arch "") set(openssl_configure_command ./config) @@ -352,16 +334,9 @@ if(ARCH_TRIPLET MATCHES mingw) endif() endif() -if(CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw) - set(zmq_patch - PATCH_COMMAND ${PROJECT_SOURCE_DIR}/contrib/apply-patches.sh - ${PROJECT_SOURCE_DIR}/contrib/patches/libzmq-mingw-wepoll.patch - ${PROJECT_SOURCE_DIR}/contrib/patches/libzmq-mingw-unistd.patch) -endif() build_external(zmq DEPENDS sodium_external - ${zmq_patch} CONFIGURE_COMMAND ./configure ${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} @@ -370,6 +345,7 @@ build_external(zmq ) add_static_target(libzmq zmq_external libzmq.a) + set(libzmq_link_libs "sodium") if(CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw) list(APPEND libzmq_link_libs iphlpapi) diff --git a/cmake/check_for_std_filesystem.cmake b/cmake/check_for_std_filesystem.cmake deleted file mode 100644 index 432b6b4715..0000000000 --- a/cmake/check_for_std_filesystem.cmake +++ /dev/null @@ -1,51 +0,0 @@ -# Figure out if we need -lstdc++fs or -lc++fs and add it to the `filesystem` interface, if needed -# (otherwise just leave it an empty interface library; linking to it will do nothing). The former -# is needed for gcc before v9, and the latter with libc++ before llvm v9. But this gets more -# complicated than just using the compiler, because clang on linux by default uses libstdc++, so -# we'll just give up and see what works. - -add_library(filesystem INTERFACE) - -set(filesystem_code [[ -#include - -int main() { - auto cwd = std::filesystem::current_path(); - return !cwd.string().empty(); -} -]]) - -if(CMAKE_CXX_COMPILER STREQUAL "AppleClang" AND CMAKE_OSX_DEPLOYMENT_TARGET) - # It seems that check_cxx_source_compiles doesn't respect the CMAKE_OSX_DEPLOYMENT_TARGET, so this - # check would pass on Catalina (10.15) and then later compilation would fail because you aren't - # allowed to use when the deployment target is anything before 10.15. - set(CMAKE_REQUIRED_FLAGS -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}) -endif() - -check_cxx_source_compiles("${filesystem_code}" filesystem_compiled) -if(filesystem_compiled) - message(STATUS "No extra link flag needed for std::filesystem") - set(filesystem_is_good 1) -else() - foreach(fslib stdc++fs c++fs) - set(CMAKE_REQUIRED_LIBRARIES -l${fslib}) - check_cxx_source_compiles("${filesystem_code}" filesystem_compiled_${fslib}) - if (filesystem_compiled_${fslib}) - message(STATUS "Using -l${fslib} for std::filesystem support") - target_link_libraries(filesystem INTERFACE ${fslib}) - set(filesystem_is_good 1) - break() - endif() - endforeach() -endif() -unset(CMAKE_REQUIRED_LIBRARIES) -if(filesystem_is_good EQUAL 1) - message(STATUS "we have std::filesystem") -else() - # Probably broken AF macos - message(STATUS "std::filesystem is not available, apparently this compiler isn't C++17 compliant; falling back to ghc::filesystem") - set(GHC_FILESYSTEM_WITH_INSTALL OFF CACHE INTERNAL "") - add_subdirectory(external/ghc-filesystem) - target_link_libraries(filesystem INTERFACE ghc_filesystem) - target_compile_definitions(filesystem INTERFACE USE_GHC_FILESYSTEM CLI11_HAS_FILESYSTEM=0) -endif() diff --git a/cmake/check_for_std_optional.cmake b/cmake/check_for_std_optional.cmake deleted file mode 100644 index 7d9eda2663..0000000000 --- a/cmake/check_for_std_optional.cmake +++ /dev/null @@ -1,18 +0,0 @@ -# check for std::optional because macos is broke af sometimes - -set(std_optional_code [[ -#include - -int main() { - std::optional maybe; - maybe = 1; - return *maybe == 1; -} -]]) - -check_cxx_source_compiles("${std_optional_code}" was_compiled) -if(was_compiled) - message(STATUS "we have std::optional") -else() - message(FATAL_ERROR "we dont have std::optional your compiler is broke af") -endif() diff --git a/cmake/solaris.cmake b/cmake/solaris.cmake index afc788065d..c8c6bce42a 100644 --- a/cmake/solaris.cmake +++ b/cmake/solaris.cmake @@ -1,5 +1,4 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "SunOS") - # we switched to libuv set(SOLARIS ON) set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lsocket -lnsl") add_definitions(-D_POSIX_PTHREAD_SEMANTICS) diff --git a/cmake/static_link.cmake b/cmake/static_link.cmake deleted file mode 100644 index 6dc602c259..0000000000 --- a/cmake/static_link.cmake +++ /dev/null @@ -1,7 +0,0 @@ -if(STATIC_LINK) - if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - link_libraries( -static-libstdc++ ) - else() - link_libraries( -static-libstdc++ -static-libgcc ) - endif() -endif() diff --git a/cmake/win32.cmake b/cmake/win32.cmake index a8f977c284..fbddaa14f7 100644 --- a/cmake/win32.cmake +++ b/cmake/win32.cmake @@ -14,28 +14,42 @@ option(WITH_WINDOWS_32 "build 32 bit windows" OFF) # to .r[o]data section one after the other! add_compile_options(-fno-ident -Wa,-mbig-obj) +function(expand_urls output source_file) + set(expanded) + foreach(mirror ${ARGN}) + list(APPEND expanded "${mirror}/${source_file}") + endforeach() + set(${output} "${expanded}" PARENT_SCOPE) +endfunction() + +function(add_static_target target ext_target libname) + add_library(${target} STATIC IMPORTED GLOBAL) + add_dependencies(${target} ${ext_target}) + set_target_properties(${target} PROPERTIES + IMPORTED_LOCATION ${DEPS_DESTDIR}/lib/${libname} + ) +endfunction() + if(EMBEDDED_CFG) link_libatomic() endif() set(WINTUN_VERSION 0.14.1 CACHE STRING "wintun version") -set(WINTUN_MIRROR https://www.wintun.net/builds +set(WINTUN_MIRROR ${LOCAL_MIRROR} https://www.wintun.net/builds CACHE STRING "wintun mirror(s)") set(WINTUN_SOURCE wintun-${WINTUN_VERSION}.zip) set(WINTUN_HASH SHA256=07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51 CACHE STRING "wintun source hash") -set(WINDIVERT_VERSION 2.2.0-A CACHE STRING "windivert version") -set(WINDIVERT_MIRROR https://reqrypt.org/download +set(WINDIVERT_VERSION 2.2.2-A CACHE STRING "windivert version") +set(WINDIVERT_MIRROR ${LOCAL_MIRROR} https://reqrypt.org/download CACHE STRING "windivert mirror(s)") set(WINDIVERT_SOURCE WinDivert-${WINDIVERT_VERSION}.zip) -set(WINDIVERT_HASH SHA256=2a7630aac0914746fbc565ac862fa096e3e54233883ac52d17c83107496b7a7f +set(WINDIVERT_HASH SHA512=92eb2ef98ced175d44de1cdb7c52f2ebc534b6a997926baeb83bfe94cba9287b438f796aff11f6163918bcdbc25bcd4e3383715f139f690d207ce219f846a345 CACHE STRING "windivert source hash") -set(WINTUN_URL ${WINTUN_MIRROR}/${WINTUN_SOURCE} - CACHE STRING "wintun download url") -set(WINDIVERT_URL ${WINDIVERT_MIRROR}/${WINDIVERT_SOURCE} - CACHE STRING "windivert download url") +expand_urls(WINTUN_URL ${WINTUN_SOURCE} ${WINTUN_MIRROR}) +expand_urls(WINDIVERT_URL ${WINDIVERT_SOURCE} ${WINDIVERT_MIRROR}) message(STATUS "Downloading wintun from ${WINTUN_URL}") file(DOWNLOAD ${WINTUN_URL} ${CMAKE_BINARY_DIR}/wintun.zip EXPECTED_HASH ${WINTUN_HASH}) diff --git a/contrib/bootstrap/testnet.signed b/contrib/bootstrap/testnet.signed index 366c5c5a00..3046275704 100644 Binary files a/contrib/bootstrap/testnet.signed and b/contrib/bootstrap/testnet.signed differ diff --git a/contrib/format-version.sh b/contrib/format-version.sh index 8b9c8de9b8..e727cf9b6c 100644 --- a/contrib/format-version.sh +++ b/contrib/format-version.sh @@ -1,5 +1,5 @@ -CLANG_FORMAT_DESIRED_VERSION=15 +CLANG_FORMAT_DESIRED_VERSION=16 CLANG_FORMAT=$(command -v clang-format-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null) if [ $? -ne 0 ]; then diff --git a/contrib/format.sh b/contrib/format.sh index d757554e01..6a4ffbff13 100755 --- a/contrib/format.sh +++ b/contrib/format.sh @@ -1,15 +1,34 @@ #!/usr/bin/env bash +# set -x +set -e + . $(dirname $0)/format-version.sh cd "$(dirname $0)/../" +sources=($(find jni daemon llarp include pybind | grep -E '\.([hc](pp)?|m(m)?)$' | grep -v '#')) + +incl_pat='^(#include +)"(llarp|libntrup|oxen|oxenc|oxenmq|quic|CLI|cpr|nlohmann|ghc|fmt|spdlog|uvw?)([/.][^"]*)"' + if [ "$1" = "verify" ] ; then - if [ $($CLANG_FORMAT --output-replacements-xml $(find jni daemon llarp include pybind | grep -E '\.([hc](pp)?|m(m)?)$' | grep -v '#') | grep '' | wc -l) -ne 0 ] ; then + if [ $($CLANG_FORMAT --output-replacements-xml "${sources[@]}" | grep '' | wc -l) -ne 0 ] ; then exit 2 fi + + if grep --color -E "$incl_pat" "${sources[@]}"; then + exit 5 + fi else - $CLANG_FORMAT -i $(find jni daemon llarp include pybind | grep -E '\.([hc](pp)?|m(m)?)$' | grep -v '#') &> /dev/null + $CLANG_FORMAT -i "${sources[@]}" &> /dev/null + + perl -pi -e "s{$incl_pat}"'{$1<$2$3>}' "${sources[@]}" &> /dev/null +fi + +# Some includes just shouldn't exist anywhere, but need to be fixed manually: +if grep --color -E '^#include ([<"]external/|/dev/null) diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index c73a6ed142..b39422f28b 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -1,5 +1,5 @@ -add_library(lokinet-cryptography +add_library(lokinet-libntrup STATIC libntrup/src/ntru.cpp libntrup/src/ref/randomsmall.c @@ -20,7 +20,7 @@ add_library(lokinet-cryptography libntrup/src/ref/rq.c ) -target_include_directories(lokinet-cryptography PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libntrup/include) +target_include_directories(lokinet-libntrup PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libntrup/include) # The avx implementation uses runtime CPU feature detection to enable itself, so we *always* want to # compile it with avx2/fma support when supported by the compiler even if we aren't compiling with @@ -48,19 +48,19 @@ 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)) - target_sources(lokinet-cryptography PRIVATE ${NTRU_AVX_SRC}) + target_sources(lokinet-libntrup 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") else() - target_sources(lokinet-cryptography PRIVATE libntrup/src/noavx-stubs.c) + target_sources(lokinet-libntrup PRIVATE libntrup/src/noavx-stubs.c) message(STATUS "Not building with libntrup runtime AVX2/FMA support (either this architecture doesn't support them, or your compile doesn't support the -mavx2 -mfma flags") endif() -enable_lto(lokinet-cryptography) +enable_lto(lokinet-libntrup) if (WARNINGS_AS_ERRORS) - target_compile_options(lokinet-cryptography PUBLIC -Wall -Wextra -Werror) + target_compile_options(lokinet-libntrup PUBLIC -Wall -Wextra -Werror) endif() -target_link_libraries(lokinet-cryptography PUBLIC sodium) +target_link_libraries(lokinet-libntrup PUBLIC sodium) diff --git a/crypto/libntrup/src/ntru.cpp b/crypto/libntrup/src/ntru.cpp index b21fdc690a..44b95cd4ec 100644 --- a/crypto/libntrup/src/ntru.cpp +++ b/crypto/libntrup/src/ntru.cpp @@ -1,4 +1,5 @@ #include +#include #ifdef __x86_64__ #include diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index c9ff4aec6e..772e665327 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -6,9 +6,9 @@ if(APPLE) else() add_executable(lokinet lokinet.cpp) endif() -add_executable(lokinet-vpn lokinet-vpn.cpp) -enable_lto(lokinet lokinet-vpn) -list(APPEND exetargets lokinet-vpn) +# add_executable(lokinet-vpn lokinet-vpn.cpp) +# enable_lto(lokinet lokinet-vpn) +# list(APPEND exetargets lokinet-vpn) if(WITH_BOOTSTRAP) add_executable(lokinet-bootstrap lokinet-bootstrap.cpp) @@ -59,7 +59,8 @@ foreach(exe ${exetargets}) elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") target_link_directories(${exe} PRIVATE /usr/local/lib) endif() - target_link_libraries(${exe} PUBLIC lokinet-amalgum hax_and_shims_for_cmake) + # target_link_libraries(${exe} PUBLIC lokinet-amalgum hax_and_shims_for_cmake) + target_link_libraries(${exe} PUBLIC lokinet-core hax_and_shims_for_cmake) if(STRIP_SYMBOLS) add_custom_command(TARGET ${exe} POST_BUILD @@ -79,7 +80,10 @@ foreach(exe ${exetargets}) endif() endforeach() -if(SETCAP) +target_link_libraries(lokinet PRIVATE CLI11) +# target_link_libraries(lokinet-vpn PRIVATE CLI11) + +if(WITH_SETCAP) install(CODE "execute_process(COMMAND ${SETCAP} cap_net_admin,cap_net_bind_service=+eip ${CMAKE_INSTALL_PREFIX}/bin/lokinet)") endif() diff --git a/daemon/lokinet-bootstrap.cpp b/daemon/lokinet-bootstrap.cpp index 435ec6767b..535bb940f4 100644 --- a/daemon/lokinet-bootstrap.cpp +++ b/daemon/lokinet-bootstrap.cpp @@ -1,13 +1,9 @@ -#include #include #include -#include -#include -#include -#include -#include +#include +#include #include #include @@ -15,110 +11,96 @@ #include #endif -#include - -#ifndef _WIN32 -#include -#endif - namespace { - int - fail(std::string msg) - { - std::cout << msg << std::endl; - return 1; - } - - int - print_help(std::string exe) - { - std::cout << R"(Lokinet bootstrap.signed fetchy program thing + int fail(std::string msg) + { + std::cout << msg << std::endl; + return 1; + } + + int print_help(std::string exe) + { + std::cout << R"(Lokinet bootstrap.signed fetchy program thing Downloads the initial bootstrap.signed for lokinet into a local file from a default or user defined server via the reachable network. -Usage: )" << exe - << R"( [bootstrap_url [output_file]] +Usage: )" << exe << R"( [bootstrap_url [output_file]] bootstrap_url can be specified as a full URL, or a special named value ("mainnet" or "testnet") to download from the pre-defined mainnet or testnet bootstrap URLs. )"; - return 0; - } + return 0; + } } // namespace -int -main(int argc, char* argv[]) +int main(int argc, char* argv[]) { - const std::unordered_map bootstrap_urls = { - {"mainnet", "https://seed.lokinet.org/lokinet.signed"}, - {"lokinet", "https://seed.lokinet.org/lokinet.signed"}, - {"testnet", "https://seed.lokinet.org/testnet.signed"}, - {"gamma", "https://seed.lokinet.org/testnet.signed"}}; - - std::string bootstrap_url = bootstrap_urls.at("lokinet"); - fs::path outputfile{llarp::GetDefaultBootstrap()}; - - const std::unordered_set help_args = {"-h", "--help"}; - - for (int idx = 1; idx < argc; idx++) - { - const std::string arg{argv[idx]}; - if (help_args.count(arg)) - return print_help(argv[0]); - } - - if (argc > 1) - { - if (auto itr = bootstrap_urls.find(argv[1]); itr != bootstrap_urls.end()) + const std::unordered_map bootstrap_urls = { + {"lokinet", "https://seed.lokinet.org/lokinet.signed"}, {"testnet", "https://seed.lokinet.org/testnet.signed"}}; + + std::string bootstrap_url = bootstrap_urls.at("lokinet"); + fs::path outputfile{llarp::GetDefaultBootstrap()}; + + const std::unordered_set help_args = {"-h", "--help"}; + + for (int idx = 1; idx < argc; idx++) { - bootstrap_url = itr->second; + const std::string arg{argv[idx]}; + if (help_args.count(arg)) + return print_help(argv[0]); } - else + + if (argc > 1) + { + if (auto itr = bootstrap_urls.find(argv[1]); itr != bootstrap_urls.end()) + { + bootstrap_url = itr->second; + } + else + { + bootstrap_url = argv[1]; + } + } + if (argc > 2) { - bootstrap_url = argv[1]; + outputfile = fs::path{argv[2]}; } - } - if (argc > 2) - { - outputfile = fs::path{argv[2]}; - } - std::cout << "fetching " << bootstrap_url << std::endl; - cpr::Response resp = + std::cout << "fetching " << bootstrap_url << std::endl; + cpr::Response resp = #ifdef _WIN32 - cpr::Get( - cpr::Url{bootstrap_url}, cpr::Header{{"User-Agent", std::string{llarp::VERSION_FULL}}}); + cpr::Get(cpr::Url{bootstrap_url}, cpr::Header{{"User-Agent", std::string{llarp::VERSION_FULL}}}); #else - cpr::Get( - cpr::Url{bootstrap_url}, - cpr::Header{{"User-Agent", std::string{llarp::VERSION_FULL}}}, - cpr::Ssl(cpr::ssl::CaPath{X509_get_default_cert_dir()})); + cpr::Get( + cpr::Url{bootstrap_url}, + cpr::Header{{"User-Agent", std::string{llarp::LOKINET_VERSION_FULL}}}, + cpr::Ssl(cpr::ssl::CaPath{X509_get_default_cert_dir()})); #endif - if (resp.status_code != 200) - { - return fail("failed to fetch '" + bootstrap_url + "' HTTP " + std::to_string(resp.status_code)); - } - std::stringstream ss; - ss << resp.text; - std::string data{ss.str()}; - if (data[0] == 'l' or data[0] == 'd') - { - try + if (resp.status_code != 200) { - std::cout << "writing bootstrap file to: " << outputfile << std::endl; - fs::ofstream ofs{outputfile, std::ios::binary}; - ofs.exceptions(fs::ofstream::failbit); - ofs << data; - return 0; + return fail("failed to fetch '" + bootstrap_url + "' HTTP " + std::to_string(resp.status_code)); } - catch (std::exception& ex) + + const auto& data = resp.text; + + if (data[0] == 'l' or data[0] == 'd') { - return fail(std::string{"failed to write bootstrap file: "} + ex.what()); + try + { + std::cout << "writing bootstrap file to: " << outputfile << std::endl; + std::ofstream ofs{outputfile, std::ios::binary}; + ofs.exceptions(std::ofstream::failbit); + ofs << data; + return 0; + } + catch (std::exception& ex) + { + return fail(std::string{"failed to write bootstrap file: "} + ex.what()); + } } - } - return fail("got invalid bootstrap file content"); + return fail("got invalid bootstrap file content"); } diff --git a/daemon/lokinet-vpn.cpp b/daemon/lokinet-vpn.cpp index 64b41dddb9..d14444a9a8 100644 --- a/daemon/lokinet-vpn.cpp +++ b/daemon/lokinet-vpn.cpp @@ -1,299 +1,281 @@ -#include -#include +#include #include +#include +#include + #include -#include -#include -#include #include - -#include -#include -#include -#include "oxenmq/address.h" +#include #ifdef _WIN32 // add the unholy windows headers for iphlpapi #include -#include + #include #include +#include #else -#include #endif /// do a oxenmq request on an omq instance blocking style /// returns a json object parsed from the result -std::optional -OMQ_Request( +std::optional OMQ_Request( oxenmq::OxenMQ& omq, const oxenmq::ConnectionID& id, std::string_view method, std::optional args = std::nullopt) { - std::promise> result_promise; + std::promise> result_promise; - auto handleRequest = [&result_promise](bool success, std::vector result) { - if ((not success) or result.empty()) + auto handleRequest = [&result_promise](bool success, std::vector result) { + if ((not success) or result.empty()) + { + result_promise.set_value(std::nullopt); + return; + } + result_promise.set_value(result[0]); + }; + if (args.has_value()) { - result_promise.set_value(std::nullopt); - return; + omq.request(id, method, handleRequest, args->dump()); } - result_promise.set_value(result[0]); - }; - if (args.has_value()) - { - omq.request(id, method, handleRequest, args->dump()); - } - else - { - omq.request(id, method, handleRequest); - } - auto ftr = result_promise.get_future(); - const auto str = ftr.get(); - if (str.has_value()) - return nlohmann::json::parse(*str); - return std::nullopt; + else + { + omq.request(id, method, handleRequest); + } + auto ftr = result_promise.get_future(); + const auto str = ftr.get(); + if (str.has_value()) + return nlohmann::json::parse(*str); + return std::nullopt; } namespace { - struct command_line_options - { - // bool options - bool verbose = false; - bool help = false; - bool vpnUp = false; - bool vpnDown = false; - bool swap = false; - bool printStatus = false; - bool killDaemon = false; - - // string options - std::string exitAddress; - std::string rpc; - std::string endpoint = "default"; - std::string token; - std::optional range; - std::vector swapExits; - - // oxenmq - oxenmq::address rpcURL{}; - oxenmq::LogLevel logLevel = oxenmq::LogLevel::warn; - }; - - // Takes a code, prints a message, and returns the code. Intended use is: - // return exit_error(1, "blah: {}", 42); - // from within main(). - template - [[nodiscard]] int - exit_error(int code, const std::string& format, T&&... args) - { - fmt::print(format, std::forward(args)...); - fmt::print("\n"); - return code; - } - - // Same as above, but with code omitted (uses exit code 1) - template - [[nodiscard]] int - exit_error(const std::string& format, T&&... args) - { - return exit_error(1, format, std::forward(args)...); - } + struct command_line_options + { + // bool options + bool verbose = false; + bool help = false; + bool vpnUp = false; + bool vpnDown = false; + bool swap = false; + bool printStatus = false; + bool killDaemon = false; + + // string options + std::string exitAddress; + std::string rpc; + std::string endpoint = "default"; + std::string token; + std::optional range; + std::vector swapExits; + + // oxenmq + oxenmq::address rpcURL{}; + oxenmq::LogLevel logLevel = oxenmq::LogLevel::warn; + }; + + // Takes a code, prints a message, and returns the code. Intended use is: + // return exit_error(1, "blah: {}", 42); + // from within main(). + template + [[nodiscard]] int exit_error(int code, const std::string& format, T&&... args) + { + fmt::print(format, std::forward(args)...); + fmt::print("\n"); + return code; + } + + // Same as above, but with code omitted (uses exit code 1) + template + [[nodiscard]] int exit_error(const std::string& format, T&&... args) + { + return exit_error(1, format, std::forward(args)...); + } } // namespace -int -main(int argc, char* argv[]) +int main(int argc, char* argv[]) { - CLI::App cli{"lokiNET vpn control utility", "lokinet-vpn"}; - command_line_options options{}; - - // flags: boolean values in command_line_options struct - cli.add_flag("-v,--verbose", options.verbose, "Verbose"); - cli.add_flag("--add,--up", options.vpnUp, "Map VPN connection to exit node [--up is deprecated]"); - cli.add_flag( - "--remove,--down", - options.vpnDown, - "Unmap VPN connection to exit node [--down is deprecated]"); - cli.add_flag("--status", options.printStatus, "Print VPN status and exit"); - cli.add_flag("-k,--kill", options.killDaemon, "Kill lokinet daemon"); - - // options: string values in command_line_options struct - cli.add_option("--exit", options.exitAddress, "Specify exit node address")->capture_default_str(); - cli.add_option("--endpoint", options.endpoint, "Endpoint to use")->capture_default_str(); - cli.add_option("--token,--auth", options.token, "Exit auth token to use")->capture_default_str(); - cli.add_option("--range", options.range, "IP range to map exit to")->capture_default_str(); - cli.add_option( - "--swap", options.swapExits, "Exit addresses to swap mapped connection to [old] [new]") - ->expected(2) - ->capture_default_str(); - - // options: oxenmq values in command_line_options struct - cli.add_option("--rpc", options.rpc, "Specify RPC URL for lokinet")->capture_default_str(); - cli.add_option( - "--log-level", options.logLevel, "Log verbosity level, see log levels for accepted values") - ->type_name("LEVEL") - ->capture_default_str(); - - try - { - cli.parse(argc, argv); - } - catch (const CLI::ParseError& e) - { - return cli.exit(e); - } - - try - { - if (options.verbose) - options.logLevel = oxenmq::LogLevel::debug; - } - catch (const CLI::OptionNotFound& e) - { - cli.exit(e); - } - catch (const CLI::Error& e) - { - cli.exit(e); - }; - - int numCommands = options.vpnUp + options.vpnDown + options.printStatus + options.killDaemon - + (not options.swapExits.empty()); - - switch (numCommands) - { - case 0: - return exit_error(3, "One of --add/--remove/--swap/--status/--kill must be specified"); - case 1: - break; - default: - return exit_error(3, "Only one of --add/--remove/--swap/--status/--kill may be specified"); - } - - if (options.vpnUp and options.exitAddress.empty()) - return exit_error("No exit address provided, must specify --exit
"); - - oxenmq::OxenMQ omq{ - [](oxenmq::LogLevel lvl, const char* file, int line, std::string msg) { - std::cout << lvl << " [" << file << ":" << line << "] " << msg << std::endl; - }, - options.logLevel}; - - options.rpcURL = oxenmq::address{(options.rpc.empty()) ? "tcp://127.0.0.1:1190" : options.rpc}; - - omq.start(); - - std::promise connectPromise; - - const auto connectionID = omq.connect_remote( - options.rpcURL, - [&connectPromise](auto) { connectPromise.set_value(true); }, - [&connectPromise](auto, std::string_view msg) { - std::cout << "Failed to connect to lokinet RPC: " << msg << std::endl; - connectPromise.set_value(false); - }); - - auto ftr = connectPromise.get_future(); - if (not ftr.get()) - return 1; - - if (options.killDaemon) - { - auto maybe_halt = OMQ_Request(omq, connectionID, "llarp.halt"); - - if (not maybe_halt) - return exit_error("Call to llarp.halt failed"); - - if (auto err_it = maybe_halt->find("error"); - err_it != maybe_halt->end() and not err_it.value().is_null()) + CLI::App cli{"lokiNET vpn control utility", "lokinet-vpn"}; + command_line_options options{}; + + // flags: boolean values in command_line_options struct + cli.add_flag("-v,--verbose", options.verbose, "Verbose"); + cli.add_flag("--add,--up", options.vpnUp, "Map VPN connection to exit node [--up is deprecated]"); + cli.add_flag("--remove,--down", options.vpnDown, "Unmap VPN connection to exit node [--down is deprecated]"); + cli.add_flag("--status", options.printStatus, "Print VPN status and exit"); + cli.add_flag("-k,--kill", options.killDaemon, "Kill lokinet daemon"); + + // options: string values in command_line_options struct + cli.add_option("--exit", options.exitAddress, "Specify exit node address")->capture_default_str(); + cli.add_option("--endpoint", options.endpoint, "Endpoint to use")->capture_default_str(); + cli.add_option("--token,--auth", options.token, "Exit auth token to use")->capture_default_str(); + cli.add_option("--range", options.range, "IP range to map exit to")->capture_default_str(); + cli.add_option("--swap", options.swapExits, "Exit addresses to swap mapped connection to [old] [new]") + ->expected(2) + ->capture_default_str(); + + // options: oxenmq values in command_line_options struct + cli.add_option("--rpc", options.rpc, "Specify RPC URL for lokinet")->capture_default_str(); + cli.add_option("--log-level", options.logLevel, "Log verbosity level, see log levels for accepted values") + ->type_name("LEVEL") + ->capture_default_str(); + + try { - return exit_error("{}", err_it.value().dump()); + cli.parse(argc, argv); + } + catch (const CLI::ParseError& e) + { + return cli.exit(e); } - } - - if (options.printStatus) - { - const auto maybe_status = OMQ_Request(omq, connectionID, "llarp.status"); - - if (not maybe_status) - return exit_error("Call to llarp.status failed"); try { - const auto& ep = maybe_status->at("result").at("services").at(options.endpoint).at("exitMap"); - - if (ep.empty()) - { - std::cout << "No exits found" << std::endl; - } - else - { - for (const auto& [range, exit] : ep.items()) - { - std::cout << range << " via " << exit.get() << std::endl; - } - } + if (options.verbose) + options.logLevel = oxenmq::LogLevel::debug; } - catch (std::exception& ex) + catch (const CLI::OptionNotFound& e) { - return exit_error("Failed to parse result: {}", ex.what()); + cli.exit(e); } - return 0; - } + catch (const CLI::Error& e) + { + cli.exit(e); + }; - if (not options.swapExits.empty()) - { - nlohmann::json opts{{"exit_addresses", std::move(options.swapExits)}}; + int numCommands = + options.vpnUp + options.vpnDown + options.printStatus + options.killDaemon + (not options.swapExits.empty()); - auto maybe_swap = OMQ_Request(omq, connectionID, "llarp.swap_exits", std::move(opts)); + switch (numCommands) + { + case 0: + return exit_error(3, "One of --add/--remove/--swap/--status/--kill must be specified"); + case 1: + break; + default: + return exit_error(3, "Only one of --add/--remove/--swap/--status/--kill may be specified"); + } - if (not maybe_swap) - return exit_error("Failed to swap exit node connections"); + if (options.vpnUp and options.exitAddress.empty()) + return exit_error("No exit address provided, must specify --exit
"); - if (auto err_it = maybe_swap->find("error"); - err_it != maybe_swap->end() and not err_it.value().is_null()) + oxenmq::OxenMQ omq{ + [](oxenmq::LogLevel lvl, const char* file, int line, std::string msg) { + std::cout << lvl << " [" << file << ":" << line << "] " << msg << std::endl; + }, + options.logLevel}; + + options.rpcURL = oxenmq::address{(options.rpc.empty()) ? "tcp://127.0.0.1:1190" : options.rpc}; + + omq.start(); + + std::promise connectPromise; + + const auto connectionID = omq.connect_remote( + options.rpcURL, + [&connectPromise](auto) { connectPromise.set_value(true); }, + [&connectPromise](auto, std::string_view msg) { + std::cout << "Failed to connect to lokinet RPC: " << msg << std::endl; + connectPromise.set_value(false); + }); + + auto ftr = connectPromise.get_future(); + if (not ftr.get()) + return 1; + + if (options.killDaemon) { - return exit_error("{}", err_it.value().dump()); + auto maybe_halt = OMQ_Request(omq, connectionID, "llarp.halt"); + + if (not maybe_halt) + return exit_error("Call to llarp.halt failed"); + + if (auto err_it = maybe_halt->find("error"); err_it != maybe_halt->end() and not err_it.value().is_null()) + { + return exit_error("{}", err_it.value().dump()); + } } - } - if (options.vpnUp) - { - nlohmann::json opts{{"address", options.exitAddress}, {"token", options.token}}; - if (options.range) - opts["ip_range"] = *options.range; + if (options.printStatus) + { + const auto maybe_status = OMQ_Request(omq, connectionID, "llarp.status"); - auto maybe_result = OMQ_Request(omq, connectionID, "llarp.map_exit", std::move(opts)); + if (not maybe_status) + return exit_error("Call to llarp.status failed"); - if (not maybe_result) - return exit_error("Could not add exit"); + try + { + const auto& ep = maybe_status->at("result").at("services").at(options.endpoint).at("exitMap"); + + if (ep.empty()) + { + std::cout << "No exits found" << std::endl; + } + else + { + for (const auto& [range, exit] : ep.items()) + { + std::cout << range << " via " << exit.get() << std::endl; + } + } + } + catch (std::exception& ex) + { + return exit_error("Failed to parse result: {}", ex.what()); + } + return 0; + } - if (auto err_it = maybe_result->find("error"); - err_it != maybe_result->end() and not err_it.value().is_null()) + if (not options.swapExits.empty()) { - return exit_error("{}", err_it.value().dump()); + nlohmann::json opts{{"exit_addresses", std::move(options.swapExits)}}; + + auto maybe_swap = OMQ_Request(omq, connectionID, "llarp.swap_exits", std::move(opts)); + + if (not maybe_swap) + return exit_error("Failed to swap exit node connections"); + + if (auto err_it = maybe_swap->find("error"); err_it != maybe_swap->end() and not err_it.value().is_null()) + { + return exit_error("{}", err_it.value().dump()); + } } - } - if (options.vpnDown) - { - nlohmann::json opts{{"unmap_exit", true}}; - if (options.range) - opts["ip_range"] = *options.range; - auto maybe_down = OMQ_Request(omq, connectionID, "llarp.unmap_exit", std::move(opts)); + if (options.vpnUp) + { + nlohmann::json opts{{"address", options.exitAddress}, {"token", options.token}}; + if (options.range) + opts["ip_range"] = *options.range; + + auto maybe_result = OMQ_Request(omq, connectionID, "llarp.map_exit", std::move(opts)); - if (not maybe_down) - return exit_error("Failed to unmap exit node connection"); + if (not maybe_result) + return exit_error("Could not add exit"); - if (auto err_it = maybe_down->find("error"); - err_it != maybe_down->end() and not err_it.value().is_null()) + if (auto err_it = maybe_result->find("error"); err_it != maybe_result->end() and not err_it.value().is_null()) + { + return exit_error("{}", err_it.value().dump()); + } + } + if (options.vpnDown) { - return exit_error("{}", err_it.value().dump()); + nlohmann::json opts{{"unmap_exit", true}}; + if (options.range) + opts["ip_range"] = *options.range; + + auto maybe_down = OMQ_Request(omq, connectionID, "llarp.unmap_exit", std::move(opts)); + + if (not maybe_down) + return exit_error("Failed to unmap exit node connection"); + + if (auto err_it = maybe_down->find("error"); err_it != maybe_down->end() and not err_it.value().is_null()) + { + return exit_error("{}", err_it.value().dump()); + } } - } - return 0; + return 0; } diff --git a/daemon/lokinet.cpp b/daemon/lokinet.cpp index f4607a9f96..c731abc853 100644 --- a/daemon/lokinet.cpp +++ b/daemon/lokinet.cpp @@ -1,677 +1,637 @@ +#include #include // for ensure_config +// #include +#include #include -#include -#include +#include #include -#include -#include +// #include +#include +#include + +#include +#include +#include + +#include +// #include +// #include +#include +// #include +#include +// #include +#include +// #include #ifdef _WIN32 #include -#include #else #include #endif -#include +namespace +{ + struct command_line_options + { + // bool options + bool help = false; + bool version = false; + bool generate = false; + bool router = false; + bool config = false; + bool configOnly = false; + bool overwrite = false; + + // string options + // TODO: change this to use a std::filesystem::path once we stop using ghc::filesystem on + // some platforms + std::string configPath; + + // windows options + bool win_install = false; + bool win_remove = false; + }; -#include -#include -#include -#include + // windows-specific function declarations + int startWinsock(); + void install_win32_daemon(); + void uninstall_win32_daemon(); -#include -#include -#include + // operational function definitions + int lokinet_main(int, char**); + void handle_signal(int sig); + static void run_main_context(std::optional confFile, const llarp::RuntimeOptions opts); -namespace -{ - struct command_line_options - { - // bool options - bool help = false; - bool version = false; - bool generate = false; - bool router = false; - bool config = false; - bool configOnly = false; - bool overwrite = false; - - // string options - // TODO: change this to use a std::filesystem::path once we stop using ghc::filesystem on some - // platforms - std::string configPath; - - // windows options - bool win_install = false; - bool win_remove = false; - }; - - // windows-specific function declarations - int - startWinsock(); - void - install_win32_daemon(); - void - uninstall_win32_daemon(); - - // operational function definitions - int - lokinet_main(int, char**); - void - handle_signal(int sig); - static void - run_main_context(std::optional confFile, const llarp::RuntimeOptions opts); - - // variable declarations - static auto logcat = llarp::log::Cat("main"); - std::shared_ptr ctx; - std::promise exit_code; - - // operational function definitions - void - handle_signal(int sig) - { - llarp::log::info(logcat, "Handling signal {}", sig); - if (ctx) - ctx->loop->call([sig] { ctx->HandleSignal(sig); }); - else - std::cerr << "Received signal " << sig << ", but have no context yet. Ignoring!" << std::endl; - } + // variable declarations + static auto logcat = llarp::log::Cat("main"); + std::shared_ptr ctx; + std::promise exit_code; - // Windows specific code -#ifdef _WIN32 - extern "C" LONG FAR PASCAL - win32_signal_handler(EXCEPTION_POINTERS*); - extern "C" VOID FAR PASCAL - win32_daemon_entry(DWORD, LPTSTR*); - VOID - insert_description(); - - extern "C" BOOL FAR PASCAL - handle_signal_win32(DWORD fdwCtrlType) - { - UNREFERENCED_PARAMETER(fdwCtrlType); - handle_signal(SIGINT); - return TRUE; // probably unreachable - }; - - int - startWinsock() - { - WSADATA wsockd; - int err; - err = ::WSAStartup(MAKEWORD(2, 2), &wsockd); - if (err) - { - perror("Failed to start Windows Sockets"); - return err; - } - ::CreateMutex(nullptr, FALSE, "lokinet_win32_daemon"); - return 0; - } - - void - install_win32_daemon() - { - SC_HANDLE schSCManager; - SC_HANDLE schService; - std::array szPath{}; - - if (!GetModuleFileName(nullptr, szPath.data(), MAX_PATH)) + // operational function definitions + void handle_signal(int sig) { - llarp::LogError("Cannot install service ", GetLastError()); - return; + llarp::log::info(logcat, "Handling signal {}", sig); + + if (ctx) + ctx->_loop->call([sig]() { ctx->handle_signal(sig); }); + else + std::cerr << "Received signal " << sig << ", but have no context yet. Ignoring!" << std::endl; } - // Get a handle to the SCM database. - schSCManager = OpenSCManager( - nullptr, // local computer - nullptr, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights + // Windows specific code +#ifdef _WIN32 + extern "C" LONG FAR PASCAL win32_signal_handler(EXCEPTION_POINTERS*); + extern "C" VOID FAR PASCAL win32_daemon_entry(DWORD, LPTSTR*); + VOID insert_description(); - if (nullptr == schSCManager) + extern "C" BOOL FAR PASCAL handle_signal_win32(DWORD fdwCtrlType) { - llarp::LogError("OpenSCManager failed ", GetLastError()); - return; - } + UNREFERENCED_PARAMETER(fdwCtrlType); + handle_signal(SIGINT); + return TRUE; // probably unreachable + }; - // Create the service - schService = CreateService( - schSCManager, // SCM database - strdup("lokinet"), // name of service - "Lokinet for Windows", // service name to display - SERVICE_ALL_ACCESS, // desired access - SERVICE_WIN32_OWN_PROCESS, // service type - SERVICE_DEMAND_START, // start type - SERVICE_ERROR_NORMAL, // error control type - szPath.data(), // path to service's binary - nullptr, // no load ordering group - nullptr, // no tag identifier - nullptr, // no dependencies - nullptr, // LocalSystem account - nullptr); // no password - - if (schService == nullptr) + int startWinsock() { - llarp::LogError("CreateService failed ", GetLastError()); - CloseServiceHandle(schSCManager); - return; + WSADATA wsockd; + int err; + err = ::WSAStartup(MAKEWORD(2, 2), &wsockd); + if (err) + { + perror("Failed to start Windows Sockets"); + return err; + } + ::CreateMutex(nullptr, FALSE, "lokinet_win32_daemon"); + return 0; } - else - llarp::LogInfo("Service installed successfully"); - - CloseServiceHandle(schService); - CloseServiceHandle(schSCManager); - insert_description(); - } - - VOID - insert_description() - { - SC_HANDLE schSCManager; - SC_HANDLE schService; - SERVICE_DESCRIPTION sd; - LPTSTR szDesc = strdup( - "LokiNET is a free, open source, private, " - "decentralized, \"market based sybil resistant\" " - "and IP based onion routing network"); - // Get a handle to the SCM database. - schSCManager = OpenSCManager( - NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (nullptr == schSCManager) + + void install_win32_daemon() { - llarp::LogError("OpenSCManager failed ", GetLastError()); - return; - } + SC_HANDLE schSCManager; + SC_HANDLE schService; + std::array szPath{}; - // Get a handle to the service. - schService = OpenService( - schSCManager, // SCM database - "lokinet", // name of service - SERVICE_CHANGE_CONFIG); // need change config access + if (!GetModuleFileName(nullptr, szPath.data(), MAX_PATH)) + { + llarp::log::error(logcat, "Cannot install service {}", GetLastError()); + return; + } - if (schService == nullptr) - { - llarp::LogError("OpenService failed ", GetLastError()); - CloseServiceHandle(schSCManager); - return; - } + // Get a handle to the SCM database. + schSCManager = OpenSCManager( + nullptr, // local computer + nullptr, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights - // Change the service description. - sd.lpDescription = szDesc; + if (nullptr == schSCManager) + { + llarp::log::error(logcat, "OpenSCManager failed {}", GetLastError()); + return; + } - if (!ChangeServiceConfig2( - schService, // handle to service - SERVICE_CONFIG_DESCRIPTION, // change: description - &sd)) // new description - { - llarp::LogError("ChangeServiceConfig2 failed"); + // Create the service + schService = CreateService( + schSCManager, // SCM database + strdup("lokinet"), // name of service + "Lokinet for Windows", // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_DEMAND_START, // start type + SERVICE_ERROR_NORMAL, // error control type + szPath.data(), // path to service's binary + nullptr, // no load ordering group + nullptr, // no tag identifier + nullptr, // no dependencies + nullptr, // LocalSystem account + nullptr); // no password + + if (schService == nullptr) + { + llarp::log::error(logcat, "CreateService failed {}", GetLastError()); + CloseServiceHandle(schSCManager); + return; + } + else + llarp::log::info(logcat, "Service installed successfully"); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + insert_description(); } - else - llarp::LogInfo("Service description updated successfully."); - CloseServiceHandle(schService); - CloseServiceHandle(schSCManager); - } + VOID insert_description() + { + SC_HANDLE schSCManager; + SC_HANDLE schService; + SERVICE_DESCRIPTION sd; + LPTSTR szDesc = strdup( + "LokiNET is a free, open source, private, " + "decentralized, \"market based sybil resistant\" " + "and IP based onion routing network"); + // Get a handle to the SCM database. + schSCManager = OpenSCManager( + NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (nullptr == schSCManager) + { + llarp::log::error(logcat, "OpenSCManager failed {}", GetLastError()); + return; + } - void - uninstall_win32_daemon() - { - SC_HANDLE schSCManager; - SC_HANDLE schService; + // Get a handle to the service. + schService = OpenService( + schSCManager, // SCM database + "lokinet", // name of service + SERVICE_CHANGE_CONFIG); // need change config access - // Get a handle to the SCM database. - schSCManager = OpenSCManager( - nullptr, // local computer - nullptr, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights + if (schService == nullptr) + { + llarp::log::error(logcat, "OpenService failed {}", GetLastError()); + CloseServiceHandle(schSCManager); + return; + } - if (nullptr == schSCManager) - { - llarp::LogError("OpenSCManager failed ", GetLastError()); - return; - } + // Change the service description. + sd.lpDescription = szDesc; - // Get a handle to the service. - schService = OpenService( - schSCManager, // SCM database - "lokinet", // name of service - 0x10000); // need delete access + if (!ChangeServiceConfig2( + schService, // handle to service + SERVICE_CONFIG_DESCRIPTION, // change: description + &sd)) // new description + { + llarp::log::error(logcat, "ChangeServiceConfig2 failed"); + } + else + llarp::log::info(log_cat, "Service description updated successfully."); - if (schService == nullptr) - { - llarp::LogError("OpenService failed ", GetLastError()); - CloseServiceHandle(schSCManager); - return; + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); } - // Delete the service. - if (!DeleteService(schService)) + void uninstall_win32_daemon() { - llarp::LogError("DeleteService failed ", GetLastError()); - } - else - llarp::LogInfo("Service deleted successfully\n"); - - CloseServiceHandle(schService); - CloseServiceHandle(schSCManager); - } - - /// minidump generation for windows jizz - /// will make a coredump when there is an unhandled exception - LONG - GenerateDump(EXCEPTION_POINTERS* pExceptionPointers) - { - const auto flags = - (MINIDUMP_TYPE)(MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData | MiniDumpWithUnloadedModules | MiniDumpWithThreadInfo); - - std::stringstream ss; - ss << "C:\\ProgramData\\lokinet\\crash-" << llarp::time_now_ms().count() << ".dmp"; - const std::string fname = ss.str(); - HANDLE hDumpFile; - SYSTEMTIME stLocalTime; - GetLocalTime(&stLocalTime); - MINIDUMP_EXCEPTION_INFORMATION ExpParam{}; - - hDumpFile = CreateFile( - fname.c_str(), - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_WRITE | FILE_SHARE_READ, - 0, - CREATE_ALWAYS, - 0, - 0); - - ExpParam.ExceptionPointers = pExceptionPointers; - ExpParam.ClientPointers = TRUE; - - MiniDumpWriteDump( - GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, flags, &ExpParam, NULL, NULL); - - return 1; - } - - VOID FAR PASCAL - SvcCtrlHandler(DWORD dwCtrl) - { - // Handle the requested control code. - - switch (dwCtrl) - { - case SERVICE_CONTROL_STOP: - // tell service we are stopping - llarp::log::debug(logcat, "Windows service controller gave SERVICE_CONTROL_STOP"); - llarp::sys::service_manager->system_changed_our_state(llarp::sys::ServiceState::Stopping); - handle_signal(SIGINT); - return; + SC_HANDLE schSCManager; + SC_HANDLE schService; - case SERVICE_CONTROL_INTERROGATE: - // report status - llarp::log::debug(logcat, "Got win32 service interrogate signal"); - llarp::sys::service_manager->report_changed_state(); - return; + // Get a handle to the SCM database. + schSCManager = OpenSCManager( + nullptr, // local computer + nullptr, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights - default: - llarp::log::debug(logcat, "Got win32 unhandled signal {}", dwCtrl); - break; + if (nullptr == schSCManager) + { + llarp::log::error(logcat, "OpenSCManager failed {}", GetLastError()); + return; + } + + // Get a handle to the service. + schService = OpenService( + schSCManager, // SCM database + "lokinet", // name of service + 0x10000); // need delete access + + if (schService == nullptr) + { + llarp::log::error(logcat, "OpenService failed {}", GetLastError()); + CloseServiceHandle(schSCManager); + return; + } + + // Delete the service. + if (!DeleteService(schService)) + { + llarp::log::error(logcat, "DeleteService failed {}", GetLastError()); + } + else + llarp::log::info(logcat, "Service deleted successfully"); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); } - } - - // The win32 daemon entry point is where we go when invoked as a windows service; we do the - // required service dance and then pretend we were invoked via main(). - VOID FAR PASCAL - win32_daemon_entry(DWORD, LPTSTR* argv) - { - // Register the handler function for the service - auto* svc = dynamic_cast(llarp::sys::service_manager); - svc->handle = RegisterServiceCtrlHandler("lokinet", SvcCtrlHandler); - - if (svc->handle == nullptr) + + /// minidump generation for windows jizz + /// will make a coredump when there is an unhandled exception + LONG GenerateDump(EXCEPTION_POINTERS* pExceptionPointers) { - llarp::LogError("failed to register daemon control handler"); - return; - } + const auto flags = (MINIDUMP_TYPE)(MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData + | MiniDumpWithUnloadedModules | MiniDumpWithThreadInfo); - // we hard code the args to lokinet_main. - // we yoink argv[0] (lokinet.exe path) and pass in the new args. - std::array args = { - reinterpret_cast(argv[0]), - reinterpret_cast(strdup("c:\\programdata\\lokinet\\lokinet.ini")), - reinterpret_cast(0)}; - lokinet_main(args.size() - 1, args.data()); - } + const std::string fname = fmt::format("C:\\ProgramData\\lokinet\\crash-{}.dump", llarp::time_now_ms().count()); -#endif + HANDLE hDumpFile; + SYSTEMTIME stLocalTime; + GetLocalTime(&stLocalTime); + MINIDUMP_EXCEPTION_INFORMATION ExpParam{}; - int - lokinet_main(int argc, char** argv) - { - if (auto result = Lokinet_INIT()) - return result; + hDumpFile = CreateFile( + fname.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); - llarp::RuntimeOptions opts; - opts.showBanner = false; + ExpParam.ExceptionPointers = pExceptionPointers; + ExpParam.ClientPointers = TRUE; -#ifdef _WIN32 - if (startWinsock()) - return -1; - SetConsoleCtrlHandler(handle_signal_win32, TRUE); -#endif + MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, flags, &ExpParam, NULL, NULL); - CLI::App cli{ - "LokiNET is a free, open source, private, decentralized, market-based sybil resistant and " - "IP " - "based onion routing network", - "lokinet"}; - command_line_options options{}; - - // flags: boolean values in command_line_options struct - cli.add_flag("--version", options.version, "Lokinet version"); - cli.add_flag("-g,--generate", options.generate, "Generate default configuration and exit"); - cli.add_flag( - "-r,--router", options.router, "Run lokinet in routing mode instead of client-only mode"); - cli.add_flag("-f,--force", options.overwrite, "Force writing config even if file exists"); - - // options: string - cli.add_option("config,--config", options.configPath, "Path to lokinet.ini configuration file") - ->capture_default_str(); - - if constexpr (llarp::platform::is_windows) - { - cli.add_flag("--install", options.win_install, "Install win32 daemon to SCM"); - cli.add_flag("--remove", options.win_remove, "Remove win32 daemon from SCM"); + return 1; } - try + VOID FAR PASCAL SvcCtrlHandler(DWORD dwCtrl) { - cli.parse(argc, argv); + // Handle the requested control code. + + switch (dwCtrl) + { + case SERVICE_CONTROL_STOP: + // tell service we are stopping + llarp::log::debug(logcat, "Windows service controller gave SERVICE_CONTROL_STOP"); + llarp::sys::service_manager->system_changed_our_state(llarp::sys::ServiceState::Stopping); + handle_signal(SIGINT); + return; + + case SERVICE_CONTROL_INTERROGATE: + // report status + llarp::log::debug(logcat, "Got win32 service interrogate signal"); + llarp::sys::service_manager->report_changed_state(); + return; + + default: + llarp::log::debug(logcat, "Got win32 unhandled signal {}", dwCtrl); + break; + } } - catch (const CLI::ParseError& e) + + // The win32 daemon entry point is where we go when invoked as a windows service; we do the + // required service dance and then pretend we were invoked via main(). + VOID FAR PASCAL win32_daemon_entry(DWORD, LPTSTR* argv) { - return cli.exit(e); + // Register the handler function for the service + auto* svc = dynamic_cast(llarp::sys::service_manager); + svc->handle = RegisterServiceCtrlHandler("lokinet", SvcCtrlHandler); + + if (svc->handle == nullptr) + { + llarp::log::error(logcat, "failed to register daemon control handler"); + return; + } + + // we hard code the args to lokinet_main. + // we yoink argv[0] (lokinet.exe path) and pass in the new args. + std::array args = { + reinterpret_cast(argv[0]), + reinterpret_cast(strdup("c:\\programdata\\lokinet\\lokinet.ini")), + reinterpret_cast(0)}; + lokinet_main(args.size() - 1, args.data()); } - std::optional configFile; +#endif - try + int lokinet_main(int argc, char** argv) { - if (options.version) - { - std::cout << llarp::VERSION_FULL << std::endl; - return 0; - } + llarp::RuntimeOptions opts; + opts.showBanner = false; - if constexpr (llarp::platform::is_windows) - { - if (options.win_install) +#ifdef _WIN32 + if (startWinsock()) + return -1; + SetConsoleCtrlHandler(handle_signal_win32, TRUE); +#endif + + CLI::App cli{ + "LokiNET is a free, open source, private, decentralized, market-based sybil resistant " + "and " + "IP " + "based onion routing network", + "lokinet"}; + command_line_options options{}; + + // flags: boolean values in command_line_options struct + cli.add_flag("--version", options.version, "Lokinet version"); + cli.add_flag("-g,--generate", options.generate, "Generate default configuration and exit"); + cli.add_flag("-r,--router", options.router, "Run lokinet in routing mode instead of client-only mode"); + cli.add_flag("-f,--force", options.overwrite, "Force writing config even if file exists"); + + // options: string + cli.add_option("config,--config", options.configPath, "Path to lokinet.ini configuration file") + ->capture_default_str(); + + if constexpr (llarp::platform::is_windows) { - install_win32_daemon(); - return 0; + cli.add_flag("--install", options.win_install, "Install win32 daemon to SCM"); + cli.add_flag("--remove", options.win_remove, "Remove win32 daemon from SCM"); } - if (options.win_remove) + + try { - uninstall_win32_daemon(); - return 0; + cli.parse(argc, argv); + } + catch (const CLI::ParseError& e) + { + return cli.exit(e); } - } - - opts.isSNode = options.router; - - if (options.generate) - { - options.configOnly = true; - } - if (not options.configPath.empty()) - { - configFile = options.configPath; - } - } - catch (const CLI::OptionNotFound& e) - { - cli.exit(e); - } - catch (const CLI::Error& e) - { - cli.exit(e); - }; + std::optional configFile; - if (configFile.has_value()) - { - // when we have an explicit filepath - fs::path basedir = configFile->parent_path(); - if (options.configOnly) - { try { - llarp::ensureConfig(basedir, *configFile, options.overwrite, opts.isSNode); + if (options.version) + { + std::cout << llarp::LOKINET_VERSION_FULL << std::endl; + return 0; + } + + if constexpr (llarp::platform::is_windows) + { + if (options.win_install) + { + install_win32_daemon(); + return 0; + } + if (options.win_remove) + { + uninstall_win32_daemon(); + return 0; + } + } + + opts.isSNode = options.router; + + if (options.generate) + { + options.configOnly = true; + } + + if (not options.configPath.empty()) + { + configFile = options.configPath; + } } - catch (std::exception& ex) + catch (const CLI::OptionNotFound& e) { - llarp::LogError("cannot generate config at ", *configFile, ": ", ex.what()); - return 1; + cli.exit(e); } - } - else - { - try + catch (const CLI::Error& e) { - if (!fs::exists(*configFile)) - { - llarp::LogError("Config file not found ", *configFile); - return 1; - } + cli.exit(e); + }; + + if (configFile.has_value()) + { + // when we have an explicit filepath + fs::path basedir = configFile->parent_path(); + if (options.configOnly) + { + try + { + llarp::ensure_config(basedir, *configFile, options.overwrite, opts.isSNode); + } + catch (std::exception& ex) + { + llarp::log::error(logcat, "cannot generate config at {}: {}", *configFile, ex.what()); + return 1; + } + } + else + { + try + { + if (!fs::exists(*configFile)) + { + llarp::log::error(logcat, "Config file not found {}", *configFile); + return 1; + } + } + catch (std::exception& ex) + { + llarp::log::error(logcat, "cannot check if ", *configFile, " exists: ", ex.what()); + return 1; + } + } } - catch (std::exception& ex) + else { - llarp::LogError("cannot check if ", *configFile, " exists: ", ex.what()); - return 1; + try + { + llarp::ensure_config( + llarp::GetDefaultDataDir(), llarp::GetDefaultConfigPath(), options.overwrite, opts.isSNode); + } + catch (std::exception& ex) + { + llarp::log::error(logcat, "cannot ensure config: {}", ex.what()); + return 1; + } + configFile = llarp::GetDefaultConfigPath(); } - } - } - else - { - try - { - llarp::ensureConfig( - llarp::GetDefaultDataDir(), - llarp::GetDefaultConfigPath(), - options.overwrite, - opts.isSNode); - } - catch (std::exception& ex) - { - llarp::LogError("cannot ensure config: ", ex.what()); - return 1; - } - configFile = llarp::GetDefaultConfigPath(); - } - if (options.configOnly) - return 0; + if (options.configOnly) + return 0; #ifdef _WIN32 - SetUnhandledExceptionFilter(&GenerateDump); + SetUnhandledExceptionFilter(&GenerateDump); #endif - std::thread main_thread{[configFile, opts] { run_main_context(configFile, opts); }}; - auto ftr = exit_code.get_future(); + std::thread main_thread{[configFile, opts] { run_main_context(configFile, opts); }}; + auto ftr = exit_code.get_future(); - do - { - // do periodic non lokinet related tasks here - if (ctx and ctx->IsUp() and not ctx->LooksAlive()) - { - auto deadlock_cat = llarp::log::Cat("deadlock"); - for (const auto& wtf : - {"you have been visited by the mascott of the deadlocked router.", - "⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠄⠄⠄⠄", - "⠄⠄⠄⠄⠄⢀⣀⣀⡀⠄⠄⠄⡠⢲⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠄⠄", - "⠄⠄⠄⠔⣈⣀⠄⢔⡒⠳⡴⠊⠄⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⣿⣿⣧⠄⠄", - "⠄⢜⡴⢑⠖⠊⢐⣤⠞⣩⡇⠄⠄⠄⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠄⠝⠛⠋⠐", - "⢸⠏⣷⠈⠄⣱⠃⠄⢠⠃⠐⡀⠄⠄⠄⠄⠙⠻⢿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠸⠄⠄⠄⠄", - "⠈⣅⠞⢁⣿⢸⠘⡄⡆⠄⠄⠈⠢⡀⠄⠄⠄⠄⠄⠄⠉⠙⠛⠛⠛⠉⠉⡀⠄⠡⢀⠄⣀", - "⠄⠙⡎⣹⢸⠄⠆⢘⠁⠄⠄⠄⢸⠈⠢⢄⡀⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠃⠄⠄⠄⠄⠄", - "⠄⠄⠑⢿⠈⢆⠘⢼⠄⠄⠄⠄⠸⢐⢾⠄⡘⡏⠲⠆⠠⣤⢤⢤⡤⠄⣖⡇⠄⠄⠄⠄⠄", - "⣴⣶⣿⣿⣣⣈⣢⣸⠄⠄⠄⠄⡾⣷⣾⣮⣤⡏⠁⠘⠊⢠⣷⣾⡛⡟⠈⠄⠄⠄⠄⠄⠄", - "⣿⣿⣿⣿⣿⠉⠒⢽⠄⠄⠄⠄⡇⣿⣟⣿⡇⠄⠄⠄⠄⢸⣻⡿⡇⡇⠄⠄⠄⠄⠄⠄⠄", - "⠻⣿⣿⣿⣿⣄⠰⢼⠄⠄⠄⡄⠁⢻⣍⣯⠃⠄⠄⠄⠄⠈⢿⣻⠃⠈⡆⡄⠄⠄⠄⠄⠄", - "⠄⠙⠿⠿⠛⣿⣶⣤⡇⠄⠄⢣⠄⠄⠈⠄⢠⠂⠄⠁⠄⡀⠄⠄⣀⠔⢁⠃⠄⠄⠄⠄⠄", - "⠄⠄⠄⠄⠄⣿⣿⣿⣿⣾⠢⣖⣶⣦⣤⣤⣬⣤⣤⣤⣴⣶⣶⡏⠠⢃⠌⠄⠄⠄⠄⠄⠄", - "⠄⠄⠄⠄⠄⠿⠿⠟⠛⡹⠉⠛⠛⠿⠿⣿⣿⣿⣿⣿⡿⠂⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄", - "⠠⠤⠤⠄⠄⣀⠄⠄⠄⠑⠠⣤⣀⣀⣀⡘⣿⠿⠙⠻⡍⢀⡈⠂⠄⠄⠄⠄⠄⠄⠄⠄⠄", - "⠄⠄⠄⠄⠄⠄⠑⠠⣠⣴⣾⣿⣿⣿⣿⣿⣿⣇⠉⠄⠻⣿⣷⣄⡀⠄⠄⠄⠄⠄⠄⠄⠄", - "file a bug report now or be cursed with this " - "annoying image in your syslog for all time."}) + do { - llarp::log::critical(deadlock_cat, wtf); - llarp::log::flush(); - } - llarp::sys::service_manager->failed(); - std::abort(); - } - } while (ftr.wait_for(std::chrono::seconds(1)) != std::future_status::ready); + // do periodic non lokinet related tasks here + if (ctx and ctx->is_up() and not ctx->looks_alive()) + { + auto deadlock_cat = llarp::log::Cat("deadlock"); + llarp::log::critical(deadlock_cat, "Router is deadlocked!"); + llarp::log::flush(); + llarp::sys::service_manager->failed(); + std::abort(); + } + } while (ftr.wait_for(std::chrono::seconds(1)) != std::future_status::ready); - main_thread.join(); + main_thread.join(); - int code = 0; + int code = 0; - try - { - code = ftr.get(); - } - catch (const std::exception& e) - { - std::cerr << "main thread threw exception: " << e.what() << std::endl; - code = 1; - } - catch (...) - { - std::cerr << "main thread threw non-standard exception" << std::endl; - code = 2; - } + try + { + code = ftr.get(); + } + catch (const std::exception& e) + { + std::cerr << "main thread threw exception: " << e.what() << std::endl; + code = 1; + } + catch (...) + { + std::cerr << "main thread threw non-standard exception" << std::endl; + code = 2; + } - llarp::log::flush(); - llarp::sys::service_manager->stopped(); - if (ctx) - { - ctx.reset(); + llarp::log::flush(); + llarp::sys::service_manager->stopped(); + if (ctx) + { + ctx.reset(); + } + return code; } - return code; - } - - // this sets up, configures and runs the main context - static void - run_main_context(std::optional confFile, const llarp::RuntimeOptions opts) - { - llarp::LogInfo(fmt::format("starting up {} {}", llarp::VERSION_FULL, llarp::RELEASE_MOTTO)); - try + + // this sets up, configures and runs the main context + static void run_main_context(std::optional confFile, const llarp::RuntimeOptions opts) { - std::shared_ptr conf; - if (confFile) - { - llarp::LogInfo("Using config file: ", *confFile); - conf = std::make_shared(confFile->parent_path()); - } - else - { - conf = std::make_shared(llarp::GetDefaultDataDir()); - } - if (not conf->Load(confFile, opts.isSNode)) - { - llarp::LogError("failed to parse configuration"); - exit_code.set_value(1); - return; - } - - // change cwd to dataDir to support relative paths in config - fs::current_path(conf->router.m_dataDir); - - ctx = std::make_shared(); - ctx->Configure(std::move(conf)); - - signal(SIGINT, handle_signal); - signal(SIGTERM, handle_signal); + llarp::log::info(logcat, "starting up {}", llarp::LOKINET_VERSION_FULL); + try + { + std::shared_ptr conf; + if (confFile) + { + llarp::log::info(logcat, "Using config file: {}", *confFile); + conf = std::make_shared(confFile->parent_path()); + } + else + { + conf = std::make_shared(llarp::GetDefaultDataDir()); + } + if (not conf->load(confFile, opts.isSNode)) + { + llarp::log::error(logcat, "failed to parse configuration"); + exit_code.set_value(1); + return; + } + + // change cwd to dataDir to support relative paths in config + fs::current_path(conf->router.data_dir); + + ctx = std::make_shared(); + ctx->configure(std::move(conf)); + + signal(SIGINT, handle_signal); + signal(SIGTERM, handle_signal); #ifndef _WIN32 - signal(SIGHUP, handle_signal); - signal(SIGUSR1, handle_signal); + signal(SIGHUP, handle_signal); + signal(SIGUSR1, handle_signal); #endif - try - { - ctx->Setup(opts); - } - catch (llarp::util::bind_socket_error& ex) - { - llarp::LogError(fmt::format("{}, is lokinet already running? 🤔", ex.what())); - exit_code.set_value(1); - return; - } - catch (std::exception& ex) - { - llarp::LogError(fmt::format("failed to start up lokinet: {}", ex.what())); - exit_code.set_value(1); - return; - } - llarp::util::SetThreadName("llarp-mainloop"); - - auto result = ctx->Run(opts); - exit_code.set_value(result); - } - catch (std::exception& e) - { - llarp::LogError("Fatal: caught exception while running: ", e.what()); - exit_code.set_exception(std::current_exception()); - } - catch (...) - { - llarp::LogError("Fatal: caught non-standard exception while running"); - exit_code.set_exception(std::current_exception()); + try + { + ctx->setup(opts); + } + catch (llarp::util::bind_socket_error& ex) + { + llarp::log::error(logcat, "{}; is lokinet already running?", ex.what()); + exit_code.set_value(1); + return; + } + catch (const std::exception& ex) + { + llarp::log::error(logcat, "failed to start up lokinet: {}", ex.what()); + exit_code.set_value(1); + return; + } + llarp::util::SetThreadName("llarp-mainloop"); + + auto result = ctx->run(opts); + exit_code.set_value(result); + } + catch (const std::exception& e) + { + llarp::log::error(logcat, "Fatal: caught exception while running: {}", e.what()); + exit_code.set_exception(std::current_exception()); + } + catch (...) + { + llarp::log::error(logcat, "Fatal: caught non-standard exception while running"); + exit_code.set_exception(std::current_exception()); + } } - } } // namespace -int -main(int argc, char* argv[]) +int main(int argc, char* argv[]) { - // Set up a default, stderr logging for very early logging; we'll replace this later once we read - // the desired log info from config. - llarp::log::add_sink(llarp::log::Type::Print, "stderr"); - llarp::log::reset_level(llarp::log::Level::info); + // Set up a default, stderr logging for very early logging; we'll replace this later once we + // read the desired log info from config. + oxen::log::add_sink(llarp::log::Type::Print, "stderr"); + oxen::log::reset_level(llarp::log::Level::info); + // oxen::log::set_level("quic", oxen::log::Level::info); - llarp::logRingBuffer = std::make_shared(100); - llarp::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO); + llarp::logRingBuffer = std::make_shared(100); + oxen::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO); #ifndef _WIN32 - return lokinet_main(argc, argv); + return lokinet_main(argc, argv); #else - SERVICE_TABLE_ENTRY DispatchTable[] = { - {strdup("lokinet"), (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry}, {NULL, NULL}}; + if (auto hntdll = GetModuleHandle("ntdll.dll")) + { + if (GetProcAddress(hntdll, "wine_get_version")) + { + static const char* text = "Don't run lokinet in wine, aborting startup"; + static const char* title = "Lokinet Wine Error"; + MessageBoxA(NULL, text, title, MB_ICONHAND); + std::abort(); + } + } - // Try first to run as a service; if this works it fires off to win32_daemon_entry and doesn't - // return until the service enters STOPPED state. - if (StartServiceCtrlDispatcher(DispatchTable)) - return 0; + SERVICE_TABLE_ENTRY DispatchTable[] = { + {strdup("lokinet"), (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry}, {NULL, NULL}}; - auto error = GetLastError(); + // Try first to run as a service; if this works it fires off to win32_daemon_entry and doesn't + // return until the service enters STOPPED state. + if (StartServiceCtrlDispatcher(DispatchTable)) + return 0; - // We'll get this error if not invoked as a service, which is fine: we can just run directly - if (error == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) - { - llarp::sys::service_manager->disable(); - return lokinet_main(argc, argv); - } - else - { - llarp::log::critical( - logcat, "Error launching service: {}", std::system_category().message(error)); - return 1; - } + auto error = GetLastError(); + + // We'll get this error if not invoked as a service, which is fine: we can just run directly + if (error == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) + { + llarp::sys::service_manager->disable(); + return lokinet_main(argc, argv); + } + else + { + llarp::log::critical(logcat, "Error launching service: {}", std::system_category().message(error)); + return 1; + } #endif -} \ No newline at end of file +} diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000000..8fd7d51ece --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,46 @@ +# High-Level Architecture + +## Path Building + +

+ +

+ +Starting from the top, here's a high-level overview of how the lokinet client builds a path to a terminating node + +1. Client semi-randomly selects SN's for hops 2 and 3 using Introset Hash Ring (IHR) + - First hop is sticky: upon initialization of lokinet, 4-5 first hops are selected + +2. Message sent to hop 1 + - Message consists of eight records in a linked list. Four hops are typically used, leaving the last 4 links as dummy records + - Each record contains a TX (upstream) path ID and RX (downstream) path ID + - Each record has a pointer to the next record, except for the final hops' record; the pointer here is recursive, signalling the end of the path-build + +3. Hop 2 pops top record, appends metadata, and pushes record to the back of linked list + - Hop adds metadata to the record, such as optional lifetime, pubkey to derive shared secret, etc + +4. Steps 2-3 are repeated for the remaining hops until destination is reached + - Final hop reads the recursive pointer signalling the end of the path-build process + +5. Upon completion, plain-text reply is propagated backwards, where the client can then decrypt all records + +6. Client measures latency + - A) Routing message is sequentially encrypted using hop 4's key through hop 1's key + - At each iteration, the nonce is permuted by XOR'ing the previous nonce with the hash of the secret key of each hop + - B) Routing message is sent s.t. each hop can decrypt, with final hop receiving plain-text + - Each hop appends latency and expiration time data, with the final hop interpreting the plain-text as a routing message and sending it back to the client + +7. Introset is published to IHR upon successful completion; introset contains: + - Path ID's of routers + - Latency and expiration time for each hop + - DNS SRV records + - etc + +### Failure Cases + +1. Next hop is an invalid SN +2. Cannot connect to SN + +In either case, the path-build status is sent backwards with an error flag. Once received by the client, metadata related to the prospective path is wiped and the path forgotten + + diff --git a/docs/high-level-overview.md b/docs/high-level-overview.md deleted file mode 100644 index 1d2baea84c..0000000000 --- a/docs/high-level-overview.md +++ /dev/null @@ -1,19 +0,0 @@ -## onion routing overview - - - - - - - -## endpoint zmq api - - - -## DNS - - - - - - diff --git a/docs/ideal-ux.md b/docs/ideal-ux.md index 7cda8e29a5..9b78c763a2 100644 --- a/docs/ideal-ux.md +++ b/docs/ideal-ux.md @@ -8,7 +8,7 @@ The `.snode` gtld refers to a router on the network by its public ed25519 key. The `.loki` gtld refers to clients that publish the existence anonymously to the network by their ed25519 public key. (`.loki` also has the ability to use short names resolved via external consensus method, like a blockchain). -# How Do I use Lokinet? +# How do I use Lokinet? set system dns resolver to use the dns resolver provided by lokinet, make sure the upstream dns provider that lokinet uses for non lokinet gtlds is set as desired (see lokinet.ini `[dns]` section) @@ -16,7 +16,7 @@ configure exit traffic provider if you want to tunnel ip traffic via lokinet, by note: per flow (ip+proto/port) isolation is trivial on a technical level but currently not implemented at this time. -# Can I run lokinet on a soho router +# Can I run lokinet on a soho router? Yes and that is the best way to run it in practice. diff --git a/docs/install.md b/docs/install.md index 17cf9da086..21d69dd5b3 100644 --- a/docs/install.md +++ b/docs/install.md @@ -30,7 +30,7 @@ You can get the latest stable release for lokinet on windows or macos from https You do not have to build from source if you do not wish to, we provide [apt](#deb-install) and [rpm](#rpm-install) repos. -#### APT repository +#### APT Repository You can install debian packages from `deb.oxen.io` by adding the apt repo to your system. diff --git a/docs/lokinet_pathbuild_no_steps.png b/docs/lokinet_pathbuild_no_steps.png new file mode 100644 index 0000000000..7967150e34 Binary files /dev/null and b/docs/lokinet_pathbuild_no_steps.png differ diff --git a/docs/net-comparisons.md b/docs/net-comparisons.md index 65264cd376..4dc1ffc0eb 100644 --- a/docs/net-comparisons.md +++ b/docs/net-comparisons.md @@ -1,6 +1,5 @@ # How is lokinet different than ... - ## Tor Browser Tor browser is a hardened Firefox Web Browser meant exclusively to surf http(s) sites via Tor. It is meant to be a complete self contained browser you open and run to surf the Web (not the internet) anonymously. diff --git a/docs/project-structure.md b/docs/project-structure.md index 7c06c70f85..61c63c2914 100644 --- a/docs/project-structure.md +++ b/docs/project-structure.md @@ -1,8 +1,8 @@ -# Lokinet project structure +# Lokinet Project Structure this codebase is a bit large. this is a high level map of the current code structure. -## lokinet executable main functions `(/daemon)` +## Lokinet executable main functions `(/daemon)` * `lokinet.cpp`: lokinet daemon executable * `lokinet.swift`: macos sysex/appex executable @@ -10,14 +10,14 @@ this codebase is a bit large. this is a high level map of the current code struc * `lokinet-bootstrap.cpp`: legacy util for windows, downloads a bootstrap file via https -## lokinet public headers `(/include)` +## Lokinet public headers `(/include)` `lokinet.h and lokinet/*.h`: C headers for embedded lokinet `llarp.hpp`: semi-internal C++ header for lokinet executables -## lokinet core library `(/llarp)` +## Lokinet core library `(/llarp)` * `/llarp`: contains a few straggling compilation units * `/llarp/android`: android platform compat shims @@ -49,7 +49,7 @@ this codebase is a bit large. this is a high level map of the current code struc * `/llarp/win32`: windows specific code -## component relations +## Component relations ### `/llarp/service` / `/llarp/handlers` / `/llarp/exit` @@ -82,7 +82,7 @@ node to node traffic logic and wire protocol dialects * `//TODO: separte implementation details from interfaces` -## platform contrib code `(/contrib)` +## Platform contrib code `(/contrib)` grab bag directory for non core related platform specific non source code diff --git a/docs/readme.md b/docs/readme.md index 534a19497e..25e4b21ade 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -2,33 +2,25 @@ This is where Lokinet documentation lives. -[How Do I install Lokinet?](install.md) +## Contents: -[How Do I use Lokinet?](ideal-ux.md) +### Local Environment Set-Up + - [Installing Lokinet](install.md) + - [Using Lokinet](ideal-ux.md) -## High level -[How is Lokinet different to \[insert network technology name here\] ?](net-comparisons.md) +### High Level Overview + - [Lokinet versus \[insert network technology name here\]](net-comparisons.md) + - [Lokinet architecture](architecture.md) + - [Lokinet and DNS](dns-overview.md) + - [Limitations of Lokinet](we-cannot-make-sandwiches.md) - - -[Lokinet and DNS](dns-overview.md) - -[What Lokinet can't do](we-cannot-make-sandwiches.md) - -## Lokinet Internals - -[High level layout of the git repo](project-structure.md) - - -[Build Doxygen Docs for internals](doxygen.md) - -## Lokinet (SN)Application Developer Portal - - -[What are "SNApps" and how to develop them.](snapps-dev-guide.md) - -[How do I embed lokinet into my application?](liblokinet-dev-guide.md) +### Lokinet Internals + - [Git repo layout and project structure](project-structure.md) + - [Building Doxygen Docs for internals](doxygen.md) +### Lokinet (SN)Application Developer Portal + - [SNapps development overview](snapps-dev-guide.md) + - [Embedded Lokinet](liblokinet-dev-guide.md) diff --git a/external/CLI11 b/external/CLI11 index 4c7c8ddc45..6c7b07a878 160000 --- a/external/CLI11 +++ b/external/CLI11 @@ -1 +1 @@ -Subproject commit 4c7c8ddc45d2ef74584e5cd945f7a4d27c987748 +Subproject commit 6c7b07a878ad834957b98d0f9ce1dbe0cb204fc9 diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index c2226bd773..59d18a9fe0 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -24,17 +24,15 @@ if(SUBMODULE_CHECK) endfunction () message(STATUS "Checking submodules") + check_submodule(CLI11) + check_submodule(cpr) + check_submodule(libevent) check_submodule(nlohmann) - check_submodule(ghc-filesystem) - check_submodule(oxen-logging fmt spdlog) + check_submodule(oxen-encoding) + check_submodule(oxen-libquic) + check_submodule(oxen-mq) check_submodule(pybind11) check_submodule(sqlite_orm) - check_submodule(oxen-mq) - check_submodule(oxen-encoding) - check_submodule(uvw) - check_submodule(cpr) - check_submodule(ngtcp2) - check_submodule(CLI11) endif() endif() @@ -60,25 +58,38 @@ macro(system_or_submodule BIGNAME smallname pkgconf subdir) endif() endmacro() -system_or_submodule(OXENC oxenc liboxenc>=1.0.4 oxen-encoding) +# libevent +set(EVENT__LIBRARY_TYPE "STATIC" CACHE STRING "" FORCE) +set(EVENT__DISABLE_MBEDTLS ON CACHE BOOL "" FORCE) +set(EVENT__DISABLE_MBEDTLS ON CACHE BOOL "" FORCE) +set(EVENT__DISABLE_OPENSSL ON CACHE BOOL "" FORCE) +set(EVENT__DISABLE_BENCHMARK ON CACHE BOOL "" FORCE) +set(EVENT__DISABLE_SAMPLES ON CACHE BOOL "" FORCE) +set(EVENT__DISABLE_TESTS ON CACHE BOOL "" FORCE) +add_subdirectory(libevent EXCLUDE_FROM_ALL) + +add_library(libevent::core ALIAS event_core) +add_library(libevent::threads ALIAS event_pthreads) +add_library(libevent::extra ALIAS event_extra) + + +system_or_submodule(OXENC oxenc liboxenc>=1.0.10 oxen-encoding) system_or_submodule(OXENMQ oxenmq liboxenmq>=1.2.14 oxen-mq) + set(JSON_BuildTests OFF CACHE INTERNAL "") set(JSON_Install OFF CACHE INTERNAL "") system_or_submodule(NLOHMANN nlohmann_json nlohmann_json>=3.7.0 nlohmann) -if (STATIC OR FORCE_SPDLOG_SUBMODULE OR FORCE_FMT_SUBMODULE) - set(OXEN_LOGGING_FORCE_SUBMODULES ON CACHE INTERNAL "") -endif() -set(OXEN_LOGGING_SOURCE_ROOT "${PROJECT_SOURCE_DIR}" CACHE INTERNAL "") -add_subdirectory(oxen-logging) if(WITH_HIVE) add_subdirectory(pybind11 EXCLUDE_FROM_ALL) endif() + system_or_submodule(CLI11 CLI11 CLI11>=2.2.0 CLI11) + if(WITH_PEERSTATS) add_library(sqlite_orm INTERFACE) target_include_directories(sqlite_orm SYSTEM INTERFACE sqlite_orm/include) @@ -90,29 +101,52 @@ if(WITH_PEERSTATS) target_link_libraries(sqlite_orm INTERFACE sqlite3) endif() -add_library(uvw INTERFACE) -target_include_directories(uvw INTERFACE uvw/src) -target_link_libraries(uvw INTERFACE libuv) -# ngtcp2 needs some massaging to build nicely: -include(ngtcp2_lib) -add_ngtcp2_lib() +# we get oxen-logging from libquic; forcing submodule ensures fmt>10.0.0 +#set(OXEN_LOGGING_FMT_HEADER_ONLY ON CACHE INTERNAL "") +#set(OXEN_LOGGING_SPDLOG_HEADER_ONLY ON CACHE INTERNAL "") +set(OXEN_LOGGING_FORCE_SUBMODULES ON CACHE INTERNAL "") +add_subdirectory(oxen-libquic) + # cpr configuration. Ideally we'd just do this via add_subdirectory, but cpr's cmake requires # 3.15+, and we target lower than that (and this is fairly simple to build). if(WITH_BOOTSTRAP) if(NOT BUILD_STATIC_DEPS) - find_package(CURL REQUIRED COMPONENTS HTTP HTTPS SSL) - - # CURL::libcurl wasn't added to FindCURL until cmake 3.12, so add it if necessary - if (CMAKE_VERSION VERSION_LESS 3.12 AND NOT TARGET CURL::libcurl) - add_library(libcurl UNKNOWN IMPORTED GLOBAL) - set_target_properties(libcurl PROPERTIES - IMPORTED_LOCATION ${CURL_LIBRARIES} - INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}") - add_library(CURL_libcurl INTERFACE) - target_link_libraries(CURL_libcurl INTERFACE libcurl) - add_library(CURL::libcurl ALIAS CURL_libcurl) + find_package(CURL "7.71.0" COMPONENTS HTTP HTTPS SSL) + + if(CURL_FOUND) + if(NOT (CURL_VERSION_STRING VERSION_GREATER_EQUAL "7.71.0")) + message(STATUS "System curl version (${CURL_VERSION_STRING}) is too old; using submodule...") + set(USE_CURL_SUBMODULE TRUE) + else() + set(USE_CURL_SUBMODULE FALSE) + endif() + else() + message(STATUS "Could not find system curl; using submodule...") + set(USE_CURL_SUBMODULE TRUE) + endif() + + if(USE_CURL_SUBMODULE) + + SET(CURL_ZLIB OFF CACHE STRING "" FORCE) + set(HTTP_ONLY ON CACHE INTERNAL "" FORCE) + set(BUILD_CURL_EXE OFF CACHE INTERNAL "" FORCE) + set(BUILD_TESTING OFF) + set(CURL_ENABLE_SSL OFF CACHE INTERNAL "" FORCE) + set(CURL_CA_PATH "none" CACHE INTERNAL "" FORCE) + set(CURL_USE_SCHANNEL OFF CACHE INTERNAL "" FORCE) + set(CURL_WINDOWS_SSPI OFF CACHE INTERNAL "" FORCE) + set(CURL_USE_OPENSSL OFF CACHE INTERNAL "" FORCE) + set(CURL_USE_SECTRANSP OFF CACHE INTERNAL "" FORCE) + set(CURL_USE_MBEDTLS OFF CACHE INTERNAL "" FORCE) + + FetchContent_Declare(curl + URL https://github.com/curl/curl/releases/download/curl-8_4_0/curl-8.4.0.tar.xz + URL_HASH SHA256=16c62a9c4af0f703d28bda6d7bbf37ba47055ad3414d70dec63e2e6336f2a82d # the file hash for curl-8.4.0.tar.xz + USES_TERMINAL_DOWNLOAD TRUE) # <---- This is needed only for Ninja to show download progress + FetchContent_MakeAvailable(curl) + endif() endif() @@ -145,7 +179,10 @@ endif() set(default_libcrypt OFF) if(LINUX AND NOT STATIC_LINK) - set(default_libcrypt ON) + pkg_check_modules(LIBCRYPT libcrypt IMPORTED_TARGET) + if(LIBCRYPTO_FOUND) + set(default_libcrypt ON) + endif() endif() if(MACOS) set(default_libcrypt ON) @@ -156,7 +193,7 @@ option(WITH_LIBCRYPT "enable fast password hash with libcrypt" ${default_libcryp add_library(lokinet-libcrypt INTERFACE) if(WITH_LIBCRYPT) pkg_check_modules(LIBCRYPT libcrypt IMPORTED_TARGET REQUIRED) - add_definitions(-DHAVE_CRYPT) + target_compile_definitions(lokinet-libcrypt INTERFACE -DHAVE_CRYPT) target_link_libraries(lokinet-libcrypt INTERFACE PkgConfig::LIBCRYPT) message(STATUS "using libcrypt ${LIBCRYPT_VERSION}") else() diff --git a/external/cpr b/external/cpr index f88fd7737d..23598e8a86 160000 --- a/external/cpr +++ b/external/cpr @@ -1 +1 @@ -Subproject commit f88fd7737de3e640c61703eb57a0fa0ce00c60cd +Subproject commit 23598e8a867934280db1f3bd3fe8cfa268a8d378 diff --git a/external/ghc-filesystem b/external/ghc-filesystem deleted file mode 160000 index cd6805e94d..0000000000 --- a/external/ghc-filesystem +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cd6805e94dd5d6346be1b75a54cdc27787319dd2 diff --git a/external/libevent b/external/libevent new file mode 160000 index 0000000000..fe9dc8f614 --- /dev/null +++ b/external/libevent @@ -0,0 +1 @@ +Subproject commit fe9dc8f614db0520027e8e2adb95769193d4f0a3 diff --git a/external/ngtcp2 b/external/ngtcp2 deleted file mode 160000 index 026b8434eb..0000000000 --- a/external/ngtcp2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 026b8434ebcbeec48939d1c7671a0a4d5c75202b diff --git a/external/nlohmann b/external/nlohmann index bc889afb4c..9cca280a4d 160000 --- a/external/nlohmann +++ b/external/nlohmann @@ -1 +1 @@ -Subproject commit bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d +Subproject commit 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 diff --git a/external/oxen-encoding b/external/oxen-encoding index a869ae2b01..7c8ab72db8 160000 --- a/external/oxen-encoding +++ b/external/oxen-encoding @@ -1 +1 @@ -Subproject commit a869ae2b0152ad70855e3774a425c39a25ae1ca6 +Subproject commit 7c8ab72db826f3d77f7fc2e58b029b1d2e67d52e diff --git a/external/oxen-libquic b/external/oxen-libquic new file mode 160000 index 0000000000..d837250265 --- /dev/null +++ b/external/oxen-libquic @@ -0,0 +1 @@ +Subproject commit d83725026529c259395d2abc2d3e91840d67cdc6 diff --git a/external/oxen-logging b/external/oxen-logging deleted file mode 160000 index b6e70ad3aa..0000000000 --- a/external/oxen-logging +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b6e70ad3aa3b2d718f8607599864cd50a951166a diff --git a/external/oxen-mq b/external/oxen-mq index 4f6dc35ea1..b25830efcb 160000 --- a/external/oxen-mq +++ b/external/oxen-mq @@ -1 +1 @@ -Subproject commit 4f6dc35ea13722a5f9dcd0c3d65b6b7ac3d0f0c5 +Subproject commit b25830efcb75419176672c217af5258cddead903 diff --git a/external/pybind11 b/external/pybind11 index aa304c9c7d..3e9dfa2866 160000 --- a/external/pybind11 +++ b/external/pybind11 @@ -1 +1 @@ -Subproject commit aa304c9c7d725ffb9d10af08a3b34cb372307020 +Subproject commit 3e9dfa2866941655c56877882565e7577de6fc7b diff --git a/external/sqlite_orm b/external/sqlite_orm index fdcc1da46f..ff7c878af8 160000 --- a/external/sqlite_orm +++ b/external/sqlite_orm @@ -1 +1 @@ -Subproject commit fdcc1da46fbd90feb886c0588462a62d29eb5a06 +Subproject commit ff7c878af872f7987b62d825284fb25d6043af10 diff --git a/external/uvw b/external/uvw deleted file mode 160000 index 36fdf810a6..0000000000 --- a/external/uvw +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 36fdf810a6385b9edbf9cbaf14862b8c9a547f15 diff --git a/include/llarp.hpp b/include/llarp.hpp index 2d00bdeba4..8acbca2f48 100644 --- a/include/llarp.hpp +++ b/include/llarp.hpp @@ -10,110 +10,91 @@ namespace llarp { - namespace vpn - { - class Platform; - } - - class EventLoop; - struct Config; - struct RouterContact; - struct Config; - struct Crypto; - struct CryptoManager; - struct AbstractRouter; - class NodeDB; - - namespace thread - { - class ThreadPool; - } - - struct RuntimeOptions - { - bool showBanner = true; - bool debug = false; - bool isSNode = false; - }; - - struct Context - { - std::shared_ptr crypto = nullptr; - std::shared_ptr cryptoManager = nullptr; - std::shared_ptr router = nullptr; - std::shared_ptr loop = nullptr; - std::shared_ptr nodedb = nullptr; - - Context(); - virtual ~Context() = default; - - void - Setup(const RuntimeOptions& opts); - - int - Run(const RuntimeOptions& opts); - - void - HandleSignal(int sig); - - /// Configure given the specified config. - void - Configure(std::shared_ptr conf); - - /// handle SIGHUP - void - Reload(); - - bool - IsUp() const; - - bool - LooksAlive() const; - - bool - IsStopping() const; - - /// close async - void - CloseAsync(); - - /// wait until closed and done - void - Wait(); - - /// call a function in logic thread - /// return true if queued for calling - /// return false if not queued for calling - bool - CallSafe(std::function f); - - /// Creates a router. Can be overridden to allow a different class of router - /// to be created instead. Defaults to llarp::Router. - virtual std::shared_ptr - makeRouter(const std::shared_ptr& loop); - - /// create the nodedb given our current configs - virtual std::shared_ptr - makeNodeDB(); - - /// create the vpn platform for use in creating network interfaces - virtual std::shared_ptr - makeVPNPlatform(); - - int androidFD = -1; - - protected: - std::shared_ptr config = nullptr; - - private: - void - SigINT(); - - void - Close(); - - std::unique_ptr> closeWaiter; - }; + namespace vpn + { + class Platform; + } + + class EventLoop; + struct Config; + struct RouterContact; + struct Config; + struct Router; + class NodeDB; + + namespace thread + { + class ThreadPool; + } + + struct RuntimeOptions + { + bool showBanner = true; + bool debug = false; + bool isSNode = false; + }; + + struct Context + { + std::shared_ptr router = nullptr; + std::shared_ptr _loop = nullptr; + std::shared_ptr nodedb = nullptr; + + Context(); + virtual ~Context() = default; + + void setup(const RuntimeOptions& opts); + + int run(const RuntimeOptions& opts); + + void handle_signal(int sig); + + /// Configure given the specified config. + void configure(std::shared_ptr conf); + + /// handle SIGHUP + void reload(); + + bool is_up() const; + + bool looks_alive() const; + + bool is_stopping() const; + + /// close async + void close_async(); + + /// wait until closed and done + void wait(); + + /// call a function in logic thread + /// return true if queued for calling + /// return false if not queued for calling + bool call_safe(std::function f); + + /// Creates a router + std::shared_ptr make_router(const std::shared_ptr& loop, std::promise p); + + /// create the nodedb given our current configs + // virtual std::shared_ptr make_nodedb(); + + /// create the vpn platform for use in creating network interfaces + virtual std::shared_ptr make_vpn_platform(); + + int androidFD = -1; + + protected: + std::shared_ptr config = nullptr; + + private: + void signal(int s); + + void close(); + + std::unique_ptr> close_waiter; + + std::unique_ptr> loop_waiter; + }; } // namespace llarp #endif diff --git a/include/lokinet.h b/include/lokinet.h index 4cda5b53f9..2da3d251a6 100644 --- a/include/lokinet.h +++ b/include/lokinet.h @@ -1,8 +1,8 @@ #pragma once -#include "lokinet/lokinet_context.h" -#include "lokinet/lokinet_srv.h" -#include "lokinet/lokinet_misc.h" -#include "lokinet/lokinet_addr.h" -#include "lokinet/lokinet_stream.h" -#include "lokinet/lokinet_udp.h" +#include "lokinet/addr.h" +#include "lokinet/context.h" +#include "lokinet/misc.h" +#include "lokinet/srv.h" +#include "lokinet/stream.h" +#include "lokinet/udp.h" diff --git a/include/lokinet/addr.h b/include/lokinet/addr.h new file mode 100644 index 0000000000..d55c772204 --- /dev/null +++ b/include/lokinet/addr.h @@ -0,0 +1,14 @@ +#pragma once +#include "context.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + /// get a free()-able null terminated string that holds our .loki address + /// returns NULL if we dont have one right now + char* EXPORT lokinet_address(struct lokinet_context*); +#ifdef __cplusplus +} +#endif diff --git a/include/lokinet/context.h b/include/lokinet/context.h new file mode 100644 index 0000000000..23e9e35133 --- /dev/null +++ b/include/lokinet/context.h @@ -0,0 +1,48 @@ +#pragma once + +#include "export.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + struct lokinet_context; + + /// allocate a new lokinet context + struct lokinet_context* EXPORT lokinet_context_new(); + + /// free a context allocated by lokinet_context_new + void EXPORT lokinet_context_free(struct lokinet_context*); + + /// spawn all the threads needed for operation and start running + /// return 0 on success + /// return non zero on fail + int EXPORT lokinet_context_start(struct lokinet_context*); + + /// return 0 if we our endpoint has published on the network and is ready to send + /// return -1 if we don't have enough paths ready + /// retrun -2 if we look deadlocked + /// retrun -3 if context was null or not started yet + int EXPORT lokinet_status(struct lokinet_context*); + + /// wait at most N milliseconds for lokinet to build paths and get ready + /// return 0 if we are ready + /// return nonzero if we are not ready + int EXPORT lokinet_wait_for_ready(int N, struct lokinet_context*); + + /// stop all operations on this lokinet context + void EXPORT lokinet_context_stop(struct lokinet_context*); + + /// load a bootstrap RC from memory + /// return 0 on success + /// return non zero on fail + int EXPORT lokinet_add_bootstrap_rc(const char*, size_t, struct lokinet_context*); + +#ifdef __cplusplus +} +#endif diff --git a/include/lokinet/lokinet_export.h b/include/lokinet/export.h similarity index 100% rename from include/lokinet/lokinet_export.h rename to include/lokinet/export.h diff --git a/include/lokinet/lokinet_addr.h b/include/lokinet/lokinet_addr.h deleted file mode 100644 index cd36fc2e67..0000000000 --- a/include/lokinet/lokinet_addr.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include "lokinet_context.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - /// get a free()-able null terminated string that holds our .loki address - /// returns NULL if we dont have one right now - char* EXPORT - lokinet_address(struct lokinet_context*); -#ifdef __cplusplus -} -#endif diff --git a/include/lokinet/lokinet_context.h b/include/lokinet/lokinet_context.h deleted file mode 100644 index f3281e28c4..0000000000 --- a/include/lokinet/lokinet_context.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include "lokinet_export.h" - -#include -#include -#include - -#ifdef __cplusplus -extern "C" -{ -#endif - - struct lokinet_context; - - /// allocate a new lokinet context - struct lokinet_context* EXPORT - lokinet_context_new(); - - /// free a context allocated by lokinet_context_new - void EXPORT - lokinet_context_free(struct lokinet_context*); - - /// spawn all the threads needed for operation and start running - /// return 0 on success - /// return non zero on fail - int EXPORT - lokinet_context_start(struct lokinet_context*); - - /// return 0 if we our endpoint has published on the network and is ready to send - /// return -1 if we don't have enough paths ready - /// retrun -2 if we look deadlocked - /// retrun -3 if context was null or not started yet - int EXPORT - lokinet_status(struct lokinet_context*); - - /// wait at most N milliseconds for lokinet to build paths and get ready - /// return 0 if we are ready - /// return nonzero if we are not ready - int EXPORT - lokinet_wait_for_ready(int N, struct lokinet_context*); - - /// stop all operations on this lokinet context - void EXPORT - lokinet_context_stop(struct lokinet_context*); - - /// load a bootstrap RC from memory - /// return 0 on success - /// return non zero on fail - int EXPORT - lokinet_add_bootstrap_rc(const char*, size_t, struct lokinet_context*); - -#ifdef __cplusplus -} -#endif diff --git a/include/lokinet/lokinet_misc.h b/include/lokinet/lokinet_misc.h deleted file mode 100644 index cc928a0a80..0000000000 --- a/include/lokinet/lokinet_misc.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include "lokinet_export.h" -#ifdef __cplusplus -extern "C" -{ -#endif - - /// change our network id globally across all contexts - void EXPORT - lokinet_set_netid(const char* netid); - - /// get our current netid - /// must be free()'d after use - const char* EXPORT - lokinet_get_netid(); - - /// set log level - /// possible values: trace, debug, info, warn, error, critical, none - /// return 0 on success - /// return non zero on fail - int EXPORT - lokinet_log_level(const char* level); - - /// Function pointer to invoke with lokinet log messages - typedef void (*lokinet_logger_func)(const char* message, void* context); - - /// Optional function to call when flushing lokinet log messages; can be NULL if flushing is not - /// meaningful for the logging system. - typedef void (*lokinet_logger_sync)(void* context); - - /// set a custom logger function; it is safe (and often desirable) to call this before calling - /// initializing lokinet via lokinet_context_new. - void EXPORT - lokinet_set_syncing_logger(lokinet_logger_func func, lokinet_logger_sync sync, void* context); - - /// shortcut for calling `lokinet_set_syncing_logger` with a NULL sync - void EXPORT - lokinet_set_logger(lokinet_logger_func func, void* context); - - /// @brief take in hex and turn it into base32z - /// @return value must be free()'d later - char* EXPORT - lokinet_hex_to_base32z(const char* hex); - -#ifdef __cplusplus -} -#endif diff --git a/include/lokinet/lokinet_socket.h b/include/lokinet/lokinet_socket.h deleted file mode 100644 index f8aff201d4..0000000000 --- a/include/lokinet/lokinet_socket.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "lokinet_context.h" -#include "lokinet_os.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - /// poll many sockets for activity - /// each pollfd.fd should be set to the socket id - /// returns 0 on sucess - int EXPORT - lokinet_poll(struct pollfd* poll, nfds_t numsockets, struct lokinet_context* ctx); - - /// close a udp socket or a stream socket by its id - void EXPORT - lokinet_close_socket(int id, struct lokinet_context* ctx); - -#ifdef __cplusplus -} -#endif diff --git a/include/lokinet/lokinet_srv.h b/include/lokinet/lokinet_srv.h deleted file mode 100644 index 27110ab78b..0000000000 --- a/include/lokinet/lokinet_srv.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include "lokinet_context.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - // a single srv record - struct lokinet_srv_record - { - /// the srv priority of the record - uint16_t priority; - /// the weight of this record - uint16_t weight; - /// null terminated string of the hostname - char target[256]; - /// the port to use - int port; - }; - - /// private members of a srv lookup - struct lokinet_srv_lookup_private; - - /// the result of an srv lookup - struct lokinet_srv_lookup_result - { - /// set to zero on success otherwise is the error code - int error; - /// pointer to internal members - /// dont touch me - struct lokinet_srv_lookup_private* internal; - }; - - /// do a srv lookup on host for service - /// caller MUST call lokinet_srv_lookup_done when they are done handling the result - int EXPORT - lokinet_srv_lookup( - char* host, - char* service, - struct lokinet_srv_lookup_result* result, - struct lokinet_context* ctx); - - /// a hook function to handle each srv record in a srv lookup result - /// passes in NULL when we are at the end of iteration - /// passes in void * user data - /// hook should NOT free the record - typedef void (*lokinet_srv_record_iterator)(struct lokinet_srv_record*, void*); - - /// iterate over each srv record in a lookup result - /// user is passes into hook and called for each result and then with NULL as the result on the - /// end of iteration - void EXPORT - lokinet_for_each_srv_record( - struct lokinet_srv_lookup_result* result, lokinet_srv_record_iterator iter, void* user); - - /// free internal members of a srv lookup result after use of the result - void EXPORT - lokinet_srv_lookup_done(struct lokinet_srv_lookup_result* result); - -#ifdef __cplusplus -} -#endif diff --git a/include/lokinet/lokinet_stream.h b/include/lokinet/lokinet_stream.h deleted file mode 100644 index 385a67fc80..0000000000 --- a/include/lokinet/lokinet_stream.h +++ /dev/null @@ -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 diff --git a/include/lokinet/lokinet_udp.h b/include/lokinet/lokinet_udp.h deleted file mode 100644 index 9e520dcd38..0000000000 --- a/include/lokinet/lokinet_udp.h +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once - -#include "lokinet_context.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - /// information about a udp flow - struct lokinet_udp_flowinfo - { - /// remote endpoint's .loki or .snode address - char remote_host[256]; - /// remote endpont's port - uint16_t remote_port; - /// the socket id for this flow used for i/o purposes and closing this socket - int socket_id; - }; - - /// a result from a lokinet_udp_bind call - struct lokinet_udp_bind_result - { - /// a socket id used to close a lokinet udp socket - int socket_id; - }; - - /// flow acceptor hook, return 0 success, return nonzero with errno on failure - typedef int (*lokinet_udp_flow_filter)( - void* userdata, - const struct lokinet_udp_flowinfo* remote_address, - void** flow_userdata, - int* timeout_seconds); - - /// callback to make a new outbound flow - typedef void(lokinet_udp_create_flow_func)( - void* userdata, void** flow_userdata, int* timeout_seconds); - - /// hook function for handling packets - typedef void (*lokinet_udp_flow_recv_func)( - const struct lokinet_udp_flowinfo* remote_address, - const char* pkt_data, - size_t pkt_length, - void* flow_userdata); - - /// hook function for flow timeout - typedef void (*lokinet_udp_flow_timeout_func)( - const struct lokinet_udp_flowinfo* remote_address, void* flow_userdata); - - /// inbound listen udp socket - /// expose udp port exposePort to the void - //// - /// @param filter MUST be non null, pointing to a flow filter for accepting new udp flows, called - /// with user data - /// - /// @param recv MUST be non null, pointing to a packet handler function for each flow, called - /// with per flow user data provided by filter function if accepted - /// - /// @param timeout MUST be non null, - /// pointing to a cleanup function to clean up a stale flow, staleness determined by the value - /// given by the filter function returns 0 on success - /// - /// @returns nonzero on error in which it is an errno value - int EXPORT - lokinet_udp_bind( - uint16_t exposedPort, - lokinet_udp_flow_filter filter, - lokinet_udp_flow_recv_func recv, - lokinet_udp_flow_timeout_func timeout, - void* user, - struct lokinet_udp_bind_result* result, - struct lokinet_context* ctx); - - /// @brief establish a udp flow to remote endpoint - /// - /// @param create_flow the callback to create the new flow if we establish one - /// - /// @param user passed to new_flow as user data - /// - /// @param remote the remote address to establish to - /// - /// @param ctx the lokinet context to use - /// - /// @return 0 on success, non zero errno on fail - int EXPORT - lokinet_udp_establish( - lokinet_udp_create_flow_func create_flow, - void* user, - const struct lokinet_udp_flowinfo* remote, - struct lokinet_context* ctx); - - /// @brief send on an established flow to remote endpoint - /// blocks until we have sent the packet - /// - /// @param flowinfo remote flow to use for sending - /// - /// @param ptr pointer to data to send - /// - /// @param len the length of the data - /// - /// @param ctx the lokinet context to use - /// - /// @returns 0 on success and non zero errno on fail - int EXPORT - lokinet_udp_flow_send( - const struct lokinet_udp_flowinfo* remote, - const void* ptr, - size_t len, - struct lokinet_context* ctx); - - /// @brief close a bound udp socket - /// closes all flows immediately - /// - /// @param socket_id the bound udp socket's id - /// - /// @param ctx lokinet context - void EXPORT - lokinet_udp_close(int socket_id, struct lokinet_context* ctx); - -#ifdef __cplusplus -} -#endif diff --git a/include/lokinet/misc.h b/include/lokinet/misc.h new file mode 100644 index 0000000000..55cd5c179c --- /dev/null +++ b/include/lokinet/misc.h @@ -0,0 +1,41 @@ +#pragma once +#include "export.h" +#ifdef __cplusplus +extern "C" +{ +#endif + + /// change our network id globally across all contexts + void EXPORT lokinet_set_netid(const char* netid); + + /// get our current netid + /// must be free()'d after use + const char* EXPORT lokinet_get_netid(); + + /// set log level + /// possible values: trace, debug, info, warn, error, critical, none + /// return 0 on success + /// return non zero on fail + int EXPORT lokinet_log_level(const char* level); + + /// Function pointer to invoke with lokinet log messages + typedef void (*lokinet_logger_func)(const char* message, void* context); + + /// Optional function to call when flushing lokinet log messages; can be NULL if flushing is not + /// meaningful for the logging system. + typedef void (*lokinet_logger_sync)(void* context); + + /// set a custom logger function; it is safe (and often desirable) to call this before calling + /// initializing lokinet via lokinet_context_new. + void EXPORT lokinet_set_syncing_logger(lokinet_logger_func func, lokinet_logger_sync sync, void* context); + + /// shortcut for calling `lokinet_set_syncing_logger` with a NULL sync + void EXPORT lokinet_set_logger(lokinet_logger_func func, void* context); + + /// @brief take in hex and turn it into base32z + /// @return value must be free()'d later + char* EXPORT lokinet_hex_to_base32z(const char* hex); + +#ifdef __cplusplus +} +#endif diff --git a/include/lokinet/srv.h b/include/lokinet/srv.h new file mode 100644 index 0000000000..aa581bba37 --- /dev/null +++ b/include/lokinet/srv.h @@ -0,0 +1,58 @@ +#pragma once + +#include "context.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + // a single srv record + struct lokinet_srv_record + { + /// the srv priority of the record + uint16_t priority; + /// the weight of this record + uint16_t weight; + /// null terminated string of the hostname + char target[256]; + /// the port to use + int port; + }; + + /// private members of a srv lookup + struct lokinet_srv_lookup_private; + + /// the result of an srv lookup + struct lokinet_srv_lookup_result + { + /// set to zero on success otherwise is the error code + int error; + /// pointer to internal members + /// dont touch me + struct lokinet_srv_lookup_private* internal; + }; + + /// do a srv lookup on host for service + /// caller MUST call lokinet_srv_lookup_done when they are done handling the result + int EXPORT lokinet_srv_lookup( + char* host, char* service, struct lokinet_srv_lookup_result* result, struct lokinet_context* ctx); + + /// a hook function to handle each srv record in a srv lookup result + /// passes in NULL when we are at the end of iteration + /// passes in void * user data + /// hook should NOT free the record + typedef void (*lokinet_srv_record_iterator)(struct lokinet_srv_record*, void*); + + /// iterate over each srv record in a lookup result + /// user is passes into hook and called for each result and then with NULL as the result on the + /// end of iteration + void EXPORT + lokinet_for_each_srv_record(struct lokinet_srv_lookup_result* result, lokinet_srv_record_iterator iter, void* user); + + /// free internal members of a srv lookup result after use of the result + void EXPORT lokinet_srv_lookup_done(struct lokinet_srv_lookup_result* result); + +#ifdef __cplusplus +} +#endif diff --git a/include/lokinet/stream.h b/include/lokinet/stream.h new file mode 100644 index 0000000000..69a2e7171d --- /dev/null +++ b/include/lokinet/stream.h @@ -0,0 +1,55 @@ +#pragma once + +#include "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 diff --git a/include/lokinet/udp.h b/include/lokinet/udp.h new file mode 100644 index 0000000000..c6c83e1309 --- /dev/null +++ b/include/lokinet/udp.h @@ -0,0 +1,111 @@ +#pragma once + +#include "context.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + /// information about a udp flow + struct lokinet_udp_flowinfo + { + /// remote endpoint's .loki or .snode address + char remote_host[256]; + /// remote endpont's port + uint16_t remote_port; + /// the socket id for this flow used for i/o purposes and closing this socket + int socket_id; + }; + + /// a result from a lokinet_udp_bind call + struct lokinet_udp_bind_result + { + /// a socket id used to close a lokinet udp socket + int socket_id; + }; + + /// flow acceptor hook, return 0 success, return nonzero with errno on failure + typedef int (*lokinet_udp_flow_filter)( + void* userdata, const struct lokinet_udp_flowinfo* remote_address, void** flow_userdata, int* timeout_seconds); + + /// callback to make a new outbound flow + typedef void(lokinet_udp_create_flow_func)(void* userdata, void** flow_userdata, int* timeout_seconds); + + /// hook function for handling packets + typedef void (*lokinet_udp_flow_recv_func)( + const struct lokinet_udp_flowinfo* remote_address, + const char* pkt_data, + size_t pkt_length, + void* flow_userdata); + + /// hook function for flow timeout + typedef void (*lokinet_udp_flow_timeout_func)( + const struct lokinet_udp_flowinfo* remote_address, void* flow_userdata); + + /// inbound listen udp socket + /// expose udp port exposePort to the void + //// + /// @param filter MUST be non null, pointing to a flow filter for accepting new udp flows, + /// called with user data + /// + /// @param recv MUST be non null, pointing to a packet handler function for each flow, called + /// with per flow user data provided by filter function if accepted + /// + /// @param timeout MUST be non null, + /// pointing to a cleanup function to clean up a stale flow, staleness determined by the value + /// given by the filter function returns 0 on success + /// + /// @returns nonzero on error in which it is an errno value + int EXPORT lokinet_udp_bind( + uint16_t exposedPort, + lokinet_udp_flow_filter filter, + lokinet_udp_flow_recv_func recv, + lokinet_udp_flow_timeout_func timeout, + void* user, + struct lokinet_udp_bind_result* result, + struct lokinet_context* ctx); + + /// @brief establish a udp flow to remote endpoint + /// + /// @param create_flow the callback to create the new flow if we establish one + /// + /// @param user passed to new_flow as user data + /// + /// @param remote the remote address to establish to + /// + /// @param ctx the lokinet context to use + /// + /// @return 0 on success, non zero errno on fail + int EXPORT lokinet_udp_establish( + lokinet_udp_create_flow_func create_flow, + void* user, + const struct lokinet_udp_flowinfo* remote, + struct lokinet_context* ctx); + + /// @brief send on an established flow to remote endpoint + /// blocks until we have sent the packet + /// + /// @param flowinfo remote flow to use for sending + /// + /// @param ptr pointer to data to send + /// + /// @param len the length of the data + /// + /// @param ctx the lokinet context to use + /// + /// @returns 0 on success and non zero errno on fail + int EXPORT lokinet_udp_flow_send( + const struct lokinet_udp_flowinfo* remote, const void* ptr, size_t len, struct lokinet_context* ctx); + + /// @brief close a bound udp socket + /// closes all flows immediately + /// + /// @param socket_id the bound udp socket's id + /// + /// @param ctx lokinet context + void EXPORT lokinet_udp_close(int socket_id, struct lokinet_context* ctx); + +#ifdef __cplusplus +} +#endif diff --git a/jni/lokinet_config.cpp b/jni/lokinet_config.cpp index 5313743c3f..359cb23fa2 100644 --- a/jni/lokinet_config.cpp +++ b/jni/lokinet_config.cpp @@ -1,74 +1,68 @@ +#include "lokinet_jni_common.hpp" #include "network_loki_lokinet_LokinetConfig.h" + #include #include -#include "lokinet_jni_common.hpp" extern "C" { - JNIEXPORT jobject JNICALL - Java_network_loki_lokinet_LokinetConfig_Obtain(JNIEnv* env, jclass, jstring dataDir) - { - auto conf = VisitStringAsStringView( - env, dataDir, [](std::string_view val) -> llarp::Config* { - return new llarp::Config{val}; - }); - - if (conf == nullptr) - return nullptr; - return env->NewDirectByteBuffer(conf, sizeof(llarp::Config)); - } + JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetConfig_Obtain(JNIEnv* env, jclass, jstring dataDir) + { + auto conf = VisitStringAsStringView( + env, dataDir, [](std::string_view val) -> llarp::Config* { return new llarp::Config{val}; }); - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetConfig_Free(JNIEnv* env, jclass, jobject buf) - { - auto ptr = FromBuffer(env, buf); - delete ptr; - } + if (conf == nullptr) + return nullptr; + return env->NewDirectByteBuffer(conf, sizeof(llarp::Config)); + } - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetConfig_Load(JNIEnv* env, jobject self) - { - auto conf = GetImpl(env, self); - if (conf == nullptr) - return JNI_FALSE; - if (conf->Load()) + JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetConfig_Free(JNIEnv* env, jclass, jobject buf) { - return JNI_TRUE; + auto ptr = FromBuffer(env, buf); + delete ptr; } - return JNI_FALSE; - } - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetConfig_Save(JNIEnv* env, jobject self) - { - auto conf = GetImpl(env, self); - if (conf == nullptr) - return JNI_FALSE; - try + JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetConfig_Load(JNIEnv* env, jobject self) { - conf->Save(); + auto conf = GetImpl(env, self); + if (conf == nullptr) + return JNI_FALSE; + if (conf->Load()) + { + return JNI_TRUE; + } + return JNI_FALSE; } - catch (...) + + JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetConfig_Save(JNIEnv* env, jobject self) { - return JNI_FALSE; + auto conf = GetImpl(env, self); + if (conf == nullptr) + return JNI_FALSE; + try + { + conf->Save(); + } + catch (...) + { + return JNI_FALSE; + } + return JNI_TRUE; } - return JNI_TRUE; - } - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetConfig_AddDefaultValue( - JNIEnv* env, jobject self, jstring section, jstring key, jstring value) - { - auto convert = [](std::string_view str) -> std::string { return std::string{str}; }; + JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetConfig_AddDefaultValue( + JNIEnv* env, jobject self, jstring section, jstring key, jstring value) + { + auto convert = [](std::string_view str) -> std::string { return std::string{str}; }; - const auto sect = VisitStringAsStringView(env, section, convert); - const auto k = VisitStringAsStringView(env, key, convert); - const auto v = VisitStringAsStringView(env, value, convert); + const auto sect = VisitStringAsStringView(env, section, convert); + const auto k = VisitStringAsStringView(env, key, convert); + const auto v = VisitStringAsStringView(env, value, convert); - auto conf = GetImpl(env, self); - if (conf) - { - conf->AddDefault(sect, k, v); + auto conf = GetImpl(env, self); + if (conf) + { + conf->AddDefault(sect, k, v); + } } - } } diff --git a/jni/lokinet_daemon.cpp b/jni/lokinet_daemon.cpp index 8bd90f5106..a3897cd92d 100644 --- a/jni/lokinet_daemon.cpp +++ b/jni/lokinet_daemon.cpp @@ -1,121 +1,113 @@ -#include "network_loki_lokinet_LokinetDaemon.h" #include "lokinet_jni_common.hpp" +#include "network_loki_lokinet_LokinetDaemon.h" + #include #include -#include +#include extern "C" { - JNIEXPORT jobject JNICALL - Java_network_loki_lokinet_LokinetDaemon_Obtain(JNIEnv* env, jclass) - { - auto* ptr = new llarp::Context(); - if (ptr == nullptr) - return nullptr; - return env->NewDirectByteBuffer(ptr, sizeof(llarp::Context)); - } + JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetDaemon_Obtain(JNIEnv* env, jclass) + { + auto* ptr = new llarp::Context(); + if (ptr == nullptr) + return nullptr; + return env->NewDirectByteBuffer(ptr, sizeof(llarp::Context)); + } - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetDaemon_Free(JNIEnv* env, jclass, jobject buf) - { - auto ptr = FromBuffer(env, buf); - delete ptr; - } + JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_Free(JNIEnv* env, jclass, jobject buf) + { + auto ptr = FromBuffer(env, buf); + delete ptr; + } - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetDaemon_Configure(JNIEnv* env, jobject self, jobject conf) - { - auto ptr = GetImpl(env, self); - auto config = GetImpl(env, conf); - if (ptr == nullptr || config == nullptr) - return JNI_FALSE; - try + JNIEXPORT jboolean JNICALL + Java_network_loki_lokinet_LokinetDaemon_Configure(JNIEnv* env, jobject self, jobject conf) { - llarp::RuntimeOptions opts{}; + auto ptr = GetImpl(env, self); + auto config = GetImpl(env, conf); + if (ptr == nullptr || config == nullptr) + return JNI_FALSE; + try + { + llarp::RuntimeOptions opts{}; - // janky make_shared deep copy because jni + shared pointer = scary - ptr->Configure(std::make_shared(*config)); - ptr->Setup(opts); + // janky make_shared deep copy because jni + shared pointer = scary + ptr->Configure(std::make_shared(*config)); + ptr->Setup(opts); + } + catch (...) + { + return JNI_FALSE; + } + return JNI_TRUE; } - catch (...) + + JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_Mainloop(JNIEnv* env, jobject self) { - return JNI_FALSE; + auto ptr = GetImpl(env, self); + if (ptr == nullptr) + return -1; + llarp::RuntimeOptions opts{}; + return ptr->Run(opts); } - return JNI_TRUE; - } - - JNIEXPORT jint JNICALL - Java_network_loki_lokinet_LokinetDaemon_Mainloop(JNIEnv* env, jobject self) - { - auto ptr = GetImpl(env, self); - if (ptr == nullptr) - return -1; - llarp::RuntimeOptions opts{}; - return ptr->Run(opts); - } - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetDaemon_IsRunning(JNIEnv* env, jobject self) - { - auto ptr = GetImpl(env, self); - return (ptr != nullptr && ptr->IsUp()) ? JNI_TRUE : JNI_FALSE; - } + JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_IsRunning(JNIEnv* env, jobject self) + { + auto ptr = GetImpl(env, self); + return (ptr != nullptr && ptr->IsUp()) ? JNI_TRUE : JNI_FALSE; + } - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetDaemon_Stop(JNIEnv* env, jobject self) - { - auto ptr = GetImpl(env, self); - if (ptr == nullptr) - return JNI_FALSE; - if (not ptr->IsUp()) - return JNI_FALSE; - ptr->CloseAsync(); - ptr->Wait(); - return ptr->IsUp() ? JNI_FALSE : JNI_TRUE; - } + JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_Stop(JNIEnv* env, jobject self) + { + auto ptr = GetImpl(env, self); + if (ptr == nullptr) + return JNI_FALSE; + if (not ptr->IsUp()) + return JNI_FALSE; + ptr->CloseAsync(); + ptr->Wait(); + return ptr->IsUp() ? JNI_FALSE : JNI_TRUE; + } - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv* env, jobject self) - { - if (auto ptr = GetImpl(env, self)) - ptr->androidFD = GetObjectMemberAsInt(env, self, "m_FD"); - } + JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv* env, jobject self) + { + if (auto ptr = GetImpl(env, self)) + ptr->androidFD = GetObjectMemberAsInt(env, self, "m_FD"); + } - JNIEXPORT jint JNICALL - Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv* env, jobject self) - { - if (auto ptr = GetImpl(env, self); ptr and ptr->router) - return ptr->router->OutboundUDPSocket(); - return -1; - } + JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv* env, jobject self) + { + if (auto ptr = GetImpl(env, self); ptr and ptr->router) + return ptr->router->outbound_socket(); + return -1; + } - JNIEXPORT jstring JNICALL - Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv* env, jclass) - { - std::string rangestr{}; - if (auto maybe = llarp::net::Platform::Default_ptr()->FindFreeRange()) + JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv* env, jclass) { - rangestr = maybe->ToString(); + std::string rangestr{}; + if (auto maybe = llarp::net::Platform::Default_ptr()->FindFreeRange()) + { + rangestr = maybe->ToString(); + } + return env->NewStringUTF(rangestr.c_str()); } - return env->NewStringUTF(rangestr.c_str()); - } - JNIEXPORT jstring JNICALL - Java_network_loki_lokinet_LokinetDaemon_DumpStatus(JNIEnv* env, jobject self) - { - std::string status{}; - if (auto ptr = GetImpl(env, self)) + JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DumpStatus(JNIEnv* env, jobject self) { - if (ptr->IsUp()) - { - std::promise result; - ptr->CallSafe([&result, router = ptr->router]() { - const auto status = router->ExtractStatus(); - result.set_value(status.dump()); - }); - status = result.get_future().get(); - } + std::string status{}; + if (auto ptr = GetImpl(env, self)) + { + if (ptr->IsUp()) + { + std::promise result; + ptr->CallSafe([&result, router = ptr->router]() { + const auto status = router->ExtractStatus(); + result.set_value(status.dump()); + }); + status = result.get_future().get(); + } + } + return env->NewStringUTF(status.c_str()); } - return env->NewStringUTF(status.c_str()); - } } diff --git a/jni/lokinet_jni_common.hpp b/jni/lokinet_jni_common.hpp index 8eb77069c3..443983be80 100644 --- a/jni/lokinet_jni_common.hpp +++ b/jni/lokinet_jni_common.hpp @@ -1,79 +1,74 @@ #pragma once #include -#include + #include +#include /// visit string as native bytes /// jvm uses some unholy encoding internally so we convert it to utf-8 template -static T -VisitStringAsStringView(JNIEnv* env, jobject str, V visit) +static T VisitStringAsStringView(JNIEnv* env, jobject str, V visit) { - const jclass stringClass = env->GetObjectClass(str); - const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); + const jclass stringClass = env->GetObjectClass(str); + const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); - const jstring charsetName = env->NewStringUTF("UTF-8"); - const jbyteArray stringJbytes = (jbyteArray)env->CallObjectMethod(str, getBytes, charsetName); - env->DeleteLocalRef(charsetName); + const jstring charsetName = env->NewStringUTF("UTF-8"); + const jbyteArray stringJbytes = (jbyteArray)env->CallObjectMethod(str, getBytes, charsetName); + env->DeleteLocalRef(charsetName); - const size_t length = env->GetArrayLength(stringJbytes); - jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL); + const size_t length = env->GetArrayLength(stringJbytes); + jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL); - T result = visit(std::string_view((const char*)pBytes, length)); + T result = visit(std::string_view((const char*)pBytes, length)); - env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); - env->DeleteLocalRef(stringJbytes); + env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); + env->DeleteLocalRef(stringJbytes); - return result; + return result; } /// cast jni buffer to T * template -static T* -FromBuffer(JNIEnv* env, jobject o) +static T* FromBuffer(JNIEnv* env, jobject o) { - if (o == nullptr) - return nullptr; - return static_cast(env->GetDirectBufferAddress(o)); + if (o == nullptr) + return nullptr; + return static_cast(env->GetDirectBufferAddress(o)); } /// get T * from object member called membername template -static T* -FromObjectMember(JNIEnv* env, jobject self, const char* membername) +static T* FromObjectMember(JNIEnv* env, jobject self, const char* membername) { - jclass cl = env->GetObjectClass(self); - jfieldID name = env->GetFieldID(cl, membername, "Ljava/nio/ByteBuffer;"); - jobject buffer = env->GetObjectField(self, name); - return FromBuffer(env, buffer); + jclass cl = env->GetObjectClass(self); + jfieldID name = env->GetFieldID(cl, membername, "Ljava/nio/ByteBuffer;"); + jobject buffer = env->GetObjectField(self, name); + return FromBuffer(env, buffer); } /// visit object string member called membername as bytes template -static T -VisitObjectMemberStringAsStringView(JNIEnv* env, jobject self, const char* membername, V v) +static T VisitObjectMemberStringAsStringView(JNIEnv* env, jobject self, const char* membername, V v) { - jclass cl = env->GetObjectClass(self); - jfieldID name = env->GetFieldID(cl, membername, "Ljava/lang/String;"); - jobject str = env->GetObjectField(self, name); - return VisitStringAsStringView(env, str, v); + jclass cl = env->GetObjectClass(self); + jfieldID name = env->GetFieldID(cl, membername, "Ljava/lang/String;"); + jobject str = env->GetObjectField(self, name); + return VisitStringAsStringView(env, str, v); } /// get object member int called membername template -Int_t -GetObjectMemberAsInt(JNIEnv* env, jobject self, const char* membername) +Int_t GetObjectMemberAsInt(JNIEnv* env, jobject self, const char* membername) { - jclass cl = env->GetObjectClass(self); - jfieldID name = env->GetFieldID(cl, membername, "I"); - return env->GetIntField(self, name); + jclass cl = env->GetObjectClass(self); + jfieldID name = env->GetFieldID(cl, membername, "I"); + return env->GetIntField(self, name); } /// get implementation on jni type template -T* -GetImpl(JNIEnv* env, jobject self) +T* GetImpl(JNIEnv* env, jobject self) { - return FromObjectMember(env, self, "impl"); + return FromObjectMember(env, self, "impl"); } diff --git a/jni/network_loki_lokinet_LokinetConfig.h b/jni/network_loki_lokinet_LokinetConfig.h index 341a64fdd8..050585a6bc 100644 --- a/jni/network_loki_lokinet_LokinetConfig.h +++ b/jni/network_loki_lokinet_LokinetConfig.h @@ -8,46 +8,41 @@ extern "C" { #endif - /* - * Class: network_loki_lokinet_LokinetConfig - * Method: Obtain - * Signature: (Ljava/lang/String;)Ljava/nio/ByteBuffer; - */ - JNIEXPORT jobject JNICALL - Java_network_loki_lokinet_LokinetConfig_Obtain(JNIEnv*, jclass, jstring); + /* + * Class: network_loki_lokinet_LokinetConfig + * Method: Obtain + * Signature: (Ljava/lang/String;)Ljava/nio/ByteBuffer; + */ + JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetConfig_Obtain(JNIEnv*, jclass, jstring); - /* - * Class: network_loki_lokinet_LokinetConfig - * Method: Free - * Signature: (Ljava/nio/ByteBuffer;)V - */ - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetConfig_Free(JNIEnv*, jclass, jobject); + /* + * Class: network_loki_lokinet_LokinetConfig + * Method: Free + * Signature: (Ljava/nio/ByteBuffer;)V + */ + JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetConfig_Free(JNIEnv*, jclass, jobject); - /* - * Class: network_loki_lokinet_LokinetConfig - * Method: Load - * Signature: ()Z - */ - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetConfig_Load(JNIEnv*, jobject); + /* + * Class: network_loki_lokinet_LokinetConfig + * Method: Load + * Signature: ()Z + */ + JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetConfig_Load(JNIEnv*, jobject); - /* - * Class: network_loki_lokinet_LokinetConfig - * Method: Save - * Signature: ()Z - */ - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetConfig_Save(JNIEnv*, jobject); + /* + * Class: network_loki_lokinet_LokinetConfig + * Method: Save + * Signature: ()Z + */ + JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetConfig_Save(JNIEnv*, jobject); - /* - * Class: network_loki_lokinet_LokinetConfig - * Method: AddDefaultValue - * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - */ - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetConfig_AddDefaultValue( - JNIEnv*, jobject, jstring, jstring, jstring); + /* + * Class: network_loki_lokinet_LokinetConfig + * Method: AddDefaultValue + * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + */ + JNIEXPORT void JNICALL + Java_network_loki_lokinet_LokinetConfig_AddDefaultValue(JNIEnv*, jobject, jstring, jstring, jstring); #ifdef __cplusplus } diff --git a/jni/network_loki_lokinet_LokinetDaemon.h b/jni/network_loki_lokinet_LokinetDaemon.h index cc4d54fed4..c019a22b21 100644 --- a/jni/network_loki_lokinet_LokinetDaemon.h +++ b/jni/network_loki_lokinet_LokinetDaemon.h @@ -8,85 +8,75 @@ extern "C" { #endif - /* - * Class: network_loki_lokinet_LokinetDaemon - * Method: Obtain - * Signature: ()Ljava/nio/ByteBuffer; - */ - JNIEXPORT jobject JNICALL - Java_network_loki_lokinet_LokinetDaemon_Obtain(JNIEnv*, jclass); + /* + * Class: network_loki_lokinet_LokinetDaemon + * Method: Obtain + * Signature: ()Ljava/nio/ByteBuffer; + */ + JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetDaemon_Obtain(JNIEnv*, jclass); - /* - * Class: network_loki_lokinet_LokinetDaemon - * Method: Free - * Signature: (Ljava/nio/ByteBuffer;)V - */ - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetDaemon_Free(JNIEnv*, jclass, jobject); + /* + * Class: network_loki_lokinet_LokinetDaemon + * Method: Free + * Signature: (Ljava/nio/ByteBuffer;)V + */ + JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_Free(JNIEnv*, jclass, jobject); - /* - * Class: network_loki_lokinet_LokinetDaemon - * Method: Configure - * Signature: (Lnetwork/loki/lokinet/LokinetConfig;)Z - */ - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetDaemon_Configure(JNIEnv*, jobject, jobject); + /* + * Class: network_loki_lokinet_LokinetDaemon + * Method: Configure + * Signature: (Lnetwork/loki/lokinet/LokinetConfig;)Z + */ + JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_Configure(JNIEnv*, jobject, jobject); - /* - * Class: network_loki_lokinet_LokinetDaemon - * Method: Mainloop - * Signature: ()I - */ - JNIEXPORT jint JNICALL - Java_network_loki_lokinet_LokinetDaemon_Mainloop(JNIEnv*, jobject); + /* + * Class: network_loki_lokinet_LokinetDaemon + * Method: Mainloop + * Signature: ()I + */ + JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_Mainloop(JNIEnv*, jobject); - /* - * Class: network_loki_lokinet_LokinetDaemon - * Method: IsRunning - * Signature: ()Z - */ - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetDaemon_IsRunning(JNIEnv*, jobject); + /* + * Class: network_loki_lokinet_LokinetDaemon + * Method: IsRunning + * Signature: ()Z + */ + JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_IsRunning(JNIEnv*, jobject); - /* - * Class: network_loki_lokinet_LokinetDaemon - * Method: Stop - * Signature: ()Z - */ - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetDaemon_Stop(JNIEnv*, jobject); + /* + * Class: network_loki_lokinet_LokinetDaemon + * Method: Stop + * Signature: ()Z + */ + JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_Stop(JNIEnv*, jobject); - /* - * Class: network_loki_lokinet_LokinetDaemon - * Method: InjectVPNFD - * Signature: ()V - */ - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv*, jobject); + /* + * Class: network_loki_lokinet_LokinetDaemon + * Method: InjectVPNFD + * Signature: ()V + */ + JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv*, jobject); - /* - * Class: network_loki_lokinet_LokinetDaemon - * Method: GetUDPSocket - * Signature: ()I - */ - JNIEXPORT jint JNICALL - Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv*, jobject); + /* + * Class: network_loki_lokinet_LokinetDaemon + * Method: GetUDPSocket + * Signature: ()I + */ + JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv*, jobject); - /* - * Class: network_loki_lokinet_LokinetDaemon - * Method: DetectFreeRange - * Signature: ()Ljava/lang/String; - */ - JNIEXPORT jstring JNICALL - Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv*, jclass); + /* + * Class: network_loki_lokinet_LokinetDaemon + * Method: DetectFreeRange + * Signature: ()Ljava/lang/String; + */ + JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv*, jclass); - /* - * Class: network_loki_lokinet_LokinetDaemon - * Method: DumpStatus - * Signature: ()Ljava/lang/String; - */ - JNIEXPORT jstring JNICALL - Java_network_loki_lokinet_LokinetDaemon_DumpStatus(JNIEnv*, jobject); + /* + * Class: network_loki_lokinet_LokinetDaemon + * Method: DumpStatus + * Signature: ()Ljava/lang/String; + */ + JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DumpStatus(JNIEnv*, jobject); #ifdef __cplusplus } diff --git a/jni/network_loki_lokinet_LokinetVPN.h b/jni/network_loki_lokinet_LokinetVPN.h index a3a56a8e40..b7fda58fa0 100644 --- a/jni/network_loki_lokinet_LokinetVPN.h +++ b/jni/network_loki_lokinet_LokinetVPN.h @@ -8,61 +8,54 @@ extern "C" { #endif - /* - * Class: network_loki_lokinet_LokinetVPN - * Method: PacketSize - * Signature: ()I - */ - JNIEXPORT jint JNICALL - Java_network_loki_lokinet_LokinetVPN_PacketSize(JNIEnv*, jclass); + /* + * Class: network_loki_lokinet_LokinetVPN + * Method: PacketSize + * Signature: ()I + */ + JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetVPN_PacketSize(JNIEnv*, jclass); - /* - * Class: network_loki_lokinet_LokinetVPN - * Method: Alloc - * Signature: ()Ljava/nio/Buffer; - */ - JNIEXPORT jobject JNICALL - Java_network_loki_lokinet_LokinetVPN_Alloc(JNIEnv*, jclass); + /* + * Class: network_loki_lokinet_LokinetVPN + * Method: Alloc + * Signature: ()Ljava/nio/Buffer; + */ + JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetVPN_Alloc(JNIEnv*, jclass); - /* - * Class: network_loki_lokinet_LokinetVPN - * Method: Free - * Signature: (Ljava/nio/Buffer;)V - */ - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetVPN_Free(JNIEnv*, jclass, jobject); + /* + * Class: network_loki_lokinet_LokinetVPN + * Method: Free + * Signature: (Ljava/nio/Buffer;)V + */ + JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetVPN_Free(JNIEnv*, jclass, jobject); - /* - * Class: network_loki_lokinet_LokinetVPN - * Method: Stop - * Signature: ()V - */ - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetVPN_Stop(JNIEnv*, jobject); + /* + * Class: network_loki_lokinet_LokinetVPN + * Method: Stop + * Signature: ()V + */ + JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetVPN_Stop(JNIEnv*, jobject); - /* - * Class: network_loki_lokinet_LokinetVPN - * Method: ReadPkt - * Signature: (Ljava/nio/ByteBuffer;)I - */ - JNIEXPORT jint JNICALL - Java_network_loki_lokinet_LokinetVPN_ReadPkt(JNIEnv*, jobject, jobject); + /* + * Class: network_loki_lokinet_LokinetVPN + * Method: ReadPkt + * Signature: (Ljava/nio/ByteBuffer;)I + */ + JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetVPN_ReadPkt(JNIEnv*, jobject, jobject); - /* - * Class: network_loki_lokinet_LokinetVPN - * Method: WritePkt - * Signature: (Ljava/nio/ByteBuffer;)Z - */ - JNIEXPORT jboolean JNICALL - Java_network_loki_lokinet_LokinetVPN_WritePkt(JNIEnv*, jobject, jobject); + /* + * Class: network_loki_lokinet_LokinetVPN + * Method: WritePkt + * Signature: (Ljava/nio/ByteBuffer;)Z + */ + JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetVPN_WritePkt(JNIEnv*, jobject, jobject); - /* - * Class: network_loki_lokinet_LokinetVPN - * Method: SetInfo - * Signature: (Lnetwork/loki/lokinet/LokinetVPN/VPNInfo;)V - */ - JNIEXPORT void JNICALL - Java_network_loki_lokinet_LokinetVPN_SetInfo(JNIEnv*, jobject, jobject); + /* + * Class: network_loki_lokinet_LokinetVPN + * Method: SetInfo + * Signature: (Lnetwork/loki/lokinet/LokinetVPN/VPNInfo;)V + */ + JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetVPN_SetInfo(JNIEnv*, jobject, jobject); #ifdef __cplusplus } #endif diff --git a/jni/network_loki_lokinet_Lokinet_JNI.h b/jni/network_loki_lokinet_Lokinet_JNI.h index c94eed4458..296fc3bce3 100644 --- a/jni/network_loki_lokinet_Lokinet_JNI.h +++ b/jni/network_loki_lokinet_Lokinet_JNI.h @@ -8,46 +8,39 @@ extern "C" { #endif - /* - * Class: network_loki_lokinet_Lokinet_JNI - * Method: getABICompiledWith - * Signature: ()Ljava/lang/String; - */ - JNIEXPORT jstring JNICALL - Java_network_loki_lokinet_Lokinet_1JNI_getABICompiledWith(JNIEnv*, jclass); - - /* - * Class: network_loki_lokinet_Lokinet_JNI - * Method: startLokinet - * Signature: (Ljava/lang/String;)Ljava/lang/String; - */ - JNIEXPORT jstring JNICALL - Java_network_loki_lokinet_Lokinet_1JNI_startLokinet(JNIEnv*, jclass, jstring); - - JNIEXPORT jstring JNICALL - Java_network_loki_lokinet_Lokinet_1JNI_getIfAddr(JNIEnv*, jclass); - - JNIEXPORT jint JNICALL - Java_network_loki_lokinet_Lokinet_1JNI_getIfRange(JNIEnv*, jclass); - - /* - * Class: network_loki_lokinet_Lokinet_JNI - * Method: stopLokinet - * Signature: ()V - */ - JNIEXPORT void JNICALL - Java_network_loki_lokinet_Lokinet_1JNI_stopLokinet(JNIEnv*, jclass); - - JNIEXPORT void JNICALL - Java_network_loki_lokinet_Lokinet_1JNI_setVPNFileDescriptor(JNIEnv*, jclass, jint, jint); - - /* - * Class: network_loki_lokinet_Lokinet_JNI - * Method: onNetworkStateChanged - * Signature: (Z)V - */ - JNIEXPORT void JNICALL - Java_network_loki_lokinet_Lokinet_1JNI_onNetworkStateChanged(JNIEnv*, jclass, jboolean); + /* + * Class: network_loki_lokinet_Lokinet_JNI + * Method: getABICompiledWith + * Signature: ()Ljava/lang/String; + */ + JNIEXPORT jstring JNICALL Java_network_loki_lokinet_Lokinet_1JNI_getABICompiledWith(JNIEnv*, jclass); + + /* + * Class: network_loki_lokinet_Lokinet_JNI + * Method: startLokinet + * Signature: (Ljava/lang/String;)Ljava/lang/String; + */ + JNIEXPORT jstring JNICALL Java_network_loki_lokinet_Lokinet_1JNI_startLokinet(JNIEnv*, jclass, jstring); + + JNIEXPORT jstring JNICALL Java_network_loki_lokinet_Lokinet_1JNI_getIfAddr(JNIEnv*, jclass); + + JNIEXPORT jint JNICALL Java_network_loki_lokinet_Lokinet_1JNI_getIfRange(JNIEnv*, jclass); + + /* + * Class: network_loki_lokinet_Lokinet_JNI + * Method: stopLokinet + * Signature: ()V + */ + JNIEXPORT void JNICALL Java_network_loki_lokinet_Lokinet_1JNI_stopLokinet(JNIEnv*, jclass); + + JNIEXPORT void JNICALL Java_network_loki_lokinet_Lokinet_1JNI_setVPNFileDescriptor(JNIEnv*, jclass, jint, jint); + + /* + * Class: network_loki_lokinet_Lokinet_JNI + * Method: onNetworkStateChanged + * Signature: (Z)V + */ + JNIEXPORT void JNICALL Java_network_loki_lokinet_Lokinet_1JNI_onNetworkStateChanged(JNIEnv*, jclass, jboolean); #ifdef __cplusplus } diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 6653a5f08c..877b9215ac 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -1,44 +1,117 @@ include(Version) -target_sources(lokinet-cryptography PRIVATE - crypto/crypto_libsodium.cpp +# Add an internal lokinet static library target, enables LTO (if enabled) on the target, +# and links it to the common lokinet-base interface. +# Invoke with the target/library name (e.g. "lokinet-foo") and list of source files, e.g. +# lokinet_add_library(lokinet-foo foo/source1.cpp foo/source2.cpp) +function(lokinet_add_library libname) + add_library(${libname} STATIC ${ARGN}) + target_link_libraries(${libname} PUBLIC lokinet-base PRIVATE lokinet-base-internal) + enable_lto(${libname}) +endfunction() + +lokinet_add_library(lokinet-cryptography crypto/crypto.cpp - crypto/encrypted_frame.cpp crypto/types.cpp + crypto/key_manager.cpp ) -add_library(lokinet-util - STATIC +# Functional objects use by lokinet-core and other libraries +# needed by vpn/ router/ rpc/ handlers/ net/ link/ +lokinet_add_library(lokinet-core-utils + # endpoint_base.cpp + + auth/file_auth.cpp + auth/rpc_auth.cpp + auth/session_auth.cpp + + handlers/session.cpp + handlers/tun.cpp + + service/identity.cpp + service/info.cpp + service/intro.cpp # path + service/intro_set.cpp + service/name.cpp + + vpn/egres_packet_router.cpp +) + +lokinet_add_library(lokinet-core + context.cpp + + consensus/reachability_testing.cpp + + link/link_manager.cpp + + router/router.cpp + router/route_poker.cpp + + service/types.cpp + + session/session.cpp +) + +lokinet_add_library(lokinet-rpc + rpc/json_binary_proxy.cpp + rpc/json_conversions.cpp + rpc/rpc_client.cpp + rpc/rpc_request_parser.cpp + rpc/rpc_server.cpp +) + +lokinet_add_library(lokinet-wire + link/connection.cpp + link/contacts.cpp + link/link_manager.cpp + link/tunnel.cpp +) + +# config, crypto, timeplace, nodedb, bootstrap.cpp, net, router +lokinet_add_library(lokinet-utils ${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp - util/bencode.cpp util/buffer.cpp util/file.cpp - util/json.cpp - util/logging/buffer.cpp - util/easter_eggs.cpp +# util/logging/buffer.cpp util/mem.cpp util/str.cpp - util/thread/queue_manager.cpp util/thread/threading.cpp - util/time.cpp) + util/thread/queue_manager.cpp + util/time.cpp +) -add_dependencies(lokinet-util genversion) +add_dependencies(lokinet-utils genversion) + +# Addressing and event loop files used by lokinet-core and other libraries +# needed by rpc/ link/ service/ config/ path/ dht/ +lokinet_add_library(lokinet-addressing + address/address.cpp + address/ip_packet.cpp + address/ip_range.cpp + address/keys.cpp + address/utils.cpp + + ev/loop.cpp + ev/tcp.cpp + ev/types.cpp + ev/udp.cpp -# lokinet-platform holds all platform specific code -add_library(lokinet-platform - STATIC - # for networking - ev/ev.cpp - ev/libuv.cpp - net/interface_info.cpp net/ip.cpp - net/ip_address.cpp - net/ip_packet.cpp - net/ip_range.cpp net/net_int.cpp - net/sock_addr.cpp + + router_contact.cpp # TODO: move these + router_id.cpp to lokinet-core-utils..? + router_contact_local.cpp + router_contact_remote.cpp + router_id.cpp + router_version.cpp # to be deleted shortly + + service/tag.cpp +) + +# lokinet-platform holds all platform specific code +lokinet_add_library(lokinet-platform + net/interface_info.cpp vpn/packet_router.cpp - vpn/egres_packet_router.cpp vpn/platform.cpp ) @@ -78,49 +151,40 @@ endif() # lokinet-dns is the dns parsing and hooking library that we use to # parse modify and reconstitute dns wire proto, dns queries and RR -# should have no concept of dns caching, this is left as an implementation -# detail of dns resolvers (LATER: make separate lib for dns resolvers) -add_library(lokinet-dns - STATIC - dns/message.cpp - dns/name.cpp +lokinet_add_library(lokinet-dns + dns/message.cpp # dns/server + dns/name.cpp # srv_data, question, rr dns/platform.cpp - dns/question.cpp + dns/question.cpp # message dns/rr.cpp dns/serialize.cpp dns/server.cpp - dns/srv_data.cpp) + dns/srv_data.cpp +) # platform specific bits and bobs for setting dns add_library(lokinet-dns-platform INTERFACE) if(WITH_SYSTEMD) - add_library(lokinet-dns-systemd STATIC dns/nm_platform.cpp dns/sd_platform.cpp) + lokinet_add_library(lokinet-dns-systemd dns/nm_platform.cpp dns/sd_platform.cpp) target_link_libraries(lokinet-dns-platform INTERFACE lokinet-dns-systemd) endif() # lokinet-nodedb holds all types and logic for storing parsing and constructing # nodedb data published to the network and versions of it stored locally -add_library(lokinet-nodedb - STATIC - bootstrap.cpp - net/address_info.cpp - net/exit_info.cpp - net/traffic_policy.cpp +lokinet_add_library(lokinet-nodedb + bootstrap.cpp # config, router.hpp + net/traffic_policy.cpp # config, intro_set nodedb.cpp - pow.cpp - profiling.cpp - router_contact.cpp - router_id.cpp - router_version.cpp + profiling.cpp # path, router, service::endpoint ) set(BOOTSTRAP_FALLBACKS) foreach(bs IN ITEMS MAINNET TESTNET) if(BOOTSTRAP_FALLBACK_${bs}) - message(STATUS "Building with ${bs} fallback boostrap path \"${BOOTSTRAP_FALLBACK_${bs}}\"") + message(STATUS "Building with ${bs} fallback bootstrap path \"${BOOTSTRAP_FALLBACK_${bs}}\"") file(READ "${BOOTSTRAP_FALLBACK_${bs}}" bs_data HEX) if(bs STREQUAL TESTNET) - set(network "gamma") + set(network "testnet") elseif(bs STREQUAL MAINNET) set(network "lokinet") else() @@ -133,395 +197,112 @@ endforeach() configure_file("bootstrap-fallbacks.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-fallbacks.cpp" @ONLY) target_sources(lokinet-nodedb PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-fallbacks.cpp") - # lokinet-config is for all configuration types and parsers -add_library(lokinet-config - STATIC +lokinet_add_library(lokinet-config config/config.cpp config/definition.cpp config/ini.cpp - config/key_manager.cpp) - -# lokinet-consensus is for deriving and tracking network consensus state for both service nodes and clients -add_library(lokinet-consensus - STATIC - consensus/reachability_testing.cpp -) - -# lokinet-dht holds all logic related to interacting with and participating in the DHT hashring -add_library(lokinet-dht - STATIC - dht/context.cpp - dht/dht.cpp - dht/explorenetworkjob.cpp - dht/localtaglookup.cpp - dht/localrouterlookup.cpp - dht/localserviceaddresslookup.cpp - dht/message.cpp - dht/messages/findintro.cpp - dht/messages/findrouter.cpp - dht/messages/gotintro.cpp - dht/messages/gotrouter.cpp - dht/messages/pubintro.cpp - dht/messages/findname.cpp - dht/messages/gotname.cpp - dht/publishservicejob.cpp - dht/recursiverouterlookup.cpp - dht/serviceaddresslookup.cpp - dht/taglookup.cpp -) - -# lokinet-layer-flow is the flow layer which sits atop the routing layer which manages -# flows between lokinet snapp endpoints be they .loki or .snode -add_library(lokinet-layer-flow - STATIC - layers/flow/stub.cpp # todo: remove me ) - -# lokinet-layer-onion is the "dumb" onion routing layer with builds manages and does i/o -# with onion paths. onion paths anonymize routing layer pdu. -add_library(lokinet-layer-onion - STATIC - path/ihophandler.cpp - path/path_context.cpp +# All path objects; link directly to lokinet-core +lokinet_add_library(lokinet-path path/path.cpp - path/pathbuilder.cpp - path/pathset.cpp + path/path_context.cpp + path/path_handler.cpp path/transit_hop.cpp - messages/relay.cpp - messages/relay_commit.cpp - messages/relay_status.cpp -) - -# lokinet-layer-wire is a layer 1 analog which splits up -# layer 2 frames into layer 1 symbols which in the case of iwp are encrypted udp/ip packets -add_library(lokinet-layer-wire - STATIC - iwp/iwp.cpp - iwp/linklayer.cpp - iwp/message_buffer.cpp - iwp/session.cpp -) - -# lokinet-layer-link is for our layer 2 analog which splits up layer 2 frames into -# a series of layer 1 symbols which are then transmitted between lokinet instances -add_library(lokinet-layer-link - STATIC - link/link_manager.cpp - link/session.cpp - link/server.cpp - messages/dht_immediate.cpp - messages/link_intro.cpp - messages/link_message_parser.cpp -) - -# lokinet-plainquic is for holding the tunneled plainquic code, not quic wire protocol code -add_library(lokinet-plainquic - STATIC - quic/address.cpp - quic/client.cpp - quic/connection.cpp - quic/endpoint.cpp - quic/null_crypto.cpp - quic/server.cpp - quic/stream.cpp - quic/tunnel.cpp -) - -# lokinet-context holds the contextualized god objects for a lokinet instance -# it is what any main function would link to in practice but it is hidden behind an interface library (lokinet-amalgum) -add_library(lokinet-context - STATIC - context.cpp - link/link_manager.cpp - router/outbound_message_handler.cpp - router/outbound_session_maker.cpp - router/rc_lookup_handler.cpp - router/rc_gossiper.cpp - router/router.cpp - router/route_poker.cpp ) -# lokinet-rpc holds all rpc related compilation units -add_library(lokinet-rpc - STATIC - rpc/json_binary_proxy.cpp - rpc/json_conversions.cpp - rpc/lokid_rpc_client.cpp - rpc/rpc_request_parser.cpp - rpc/rpc_server.cpp - rpc/endpoint_rpc.cpp -) +# Link libraries to their internals +target_link_libraries(lokinet-core-utils PUBLIC lokinet-dns) +target_link_libraries(lokinet-core PUBLIC lokinet-core-utils) -# optional peer stats library -add_library(lokinet-peerstats - STATIC - peerstats/peer_db.cpp - peerstats/types.cpp -) +# Link lokinet-dns to alternate libraries +target_link_libraries(lokinet-dns PUBLIC lokinet-dns-platform) -# lokinet-layer-routing holds logic related to the routing layer -# routing layer is anonymized over the onion layer -add_library(lokinet-layer-routing - STATIC - routing/dht_message.cpp - routing/message_parser.cpp - routing/path_confirm_message.cpp - routing/path_latency_message.cpp - routing/path_transfer_message.cpp - routing/transfer_traffic_message.cpp -) +target_link_libraries(lokinet-wire PUBLIC lokinet-addressing) +target_link_libraries(lokinet-dns PUBLIC lokinet-utils lokinet-cryptography lokinet-config) +target_link_libraries(lokinet-nodedb PUBLIC lokinet-addressing lokinet-cryptography) +target_link_libraries(lokinet-platform PUBLIC lokinet-cryptography) +target_link_libraries(lokinet-rpc PUBLIC lokinet-wire) +target_link_libraries(lokinet-addressing PUBLIC lokinet-utils lokinet-cryptography) +target_link_libraries(lokinet-wire PUBLIC lokinet-cryptography) +target_link_libraries(lokinet-config PUBLIC lokinet-cryptography) -# kitchen sink to be removed after refactor -add_library(lokinet-service-deprecated-kitchensink - STATIC - endpoint_base.cpp - exit/context.cpp - exit/endpoint.cpp - exit/exit_messages.cpp - exit/policy.cpp - exit/session.cpp - handlers/exit.cpp - handlers/tun.cpp - service/name.cpp - service/address.cpp - service/async_key_exchange.cpp - service/auth.cpp - service/convotag.cpp - service/context.cpp - service/endpoint_state.cpp - service/endpoint_util.cpp - service/endpoint.cpp - service/hidden_service_address_lookup.cpp - service/identity.cpp - service/info.cpp - service/intro_set.cpp - service/intro.cpp - service/lns_tracker.cpp - service/lookup.cpp - service/name.cpp - service/outbound_context.cpp - service/protocol.cpp - service/router_lookup_job.cpp - service/sendcontext.cpp - service/session.cpp - service/tag.cpp -) - -add_library(lokinet-layer-platform - STATIC - layers/platform/stub.cpp # todo: remove me +target_link_libraries(lokinet-dns + PUBLIC + lokinet-addressing ) - -# interal tooling for pybind -add_library(lokinet-tooling INTERFACE) -if(WITH_HIVE) - add_library(lokinet-hive-tooling - STATIC - tooling/router_hive.cpp - tooling/hive_router.cpp - tooling/hive_context.cpp - ) - target_link_libraries(lokinet-tooling INTERFACE lokinet-hive-tooling) -endif() - - -# interface library for setting commone includes, linkage and flags. -add_library(lokinet-base INTERFACE) -target_include_directories(lokinet-base - INTERFACE ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include -) -target_link_libraries(lokinet-base INTERFACE oxen::logging lokinet-cryptography) - -if(WITH_PEERSTATS) - target_compile_definitions(lokinet-base INTERFACE -DLOKINET_PEERSTATS_BACKEND) - target_link_libraries(lokinet-base INTERFACE sqlite_orm) -endif() - -# interface libraries for internal linkage -add_library(lokinet-layers INTERFACE) -add_library(lokinet-amalgum INTERFACE) - - -# helper function to link a library to lokinet-base, enable lto, add to lokinet-amalgum and then link to other libs -function(lokinet_link_lib libname) - message(DEBUG "created target: ${libname}") - enable_lto(${libname}) - target_link_libraries(${libname} PUBLIC lokinet-base ${ARGN}) - target_link_libraries(lokinet-amalgum INTERFACE ${libname}) -endfunction() - -# internal public linkages of components -lokinet_link_lib(lokinet-util) -lokinet_link_lib(lokinet-cryptography lokinet-libcrypt lokinet-util) -lokinet_link_lib(lokinet-peerstats lokinet-context) -lokinet_link_lib(lokinet-consensus lokinet-context) -lokinet_link_lib(lokinet-layer-link lokinet-peerstats) - -if(TARGET lokinet-hive-tooling) - lokinet_link_lib(lokinet-hive-tooling lokinet-context) -endif() - -if(TARGET lokinet-dns-systemd) - lokinet_link_lib(lokinet-dns-systemd - lokinet-dns - lokinet-platform - ) -endif() - -lokinet_link_lib(lokinet-platform lokinet-util) - -lokinet_link_lib(lokinet-config - lokinet-util - lokinet-nodedb - lokinet-dns - lokinet-platform +target_link_libraries(lokinet-path + PUBLIC + lokinet-addressing ) -lokinet_link_lib(lokinet-context +target_link_libraries(lokinet-core-utils + PUBLIC lokinet-config lokinet-platform - lokinet-peerstats - lokinet-layers - lokinet-consensus - lokinet-rpc + lokinet-rpc + lokinet-wire ) -lokinet_link_lib(lokinet-dht - lokinet-util - lokinet-nodedb +target_link_libraries(lokinet-cryptography + PUBLIC + lokinet-libcrypt + lokinet-libntrup ) -lokinet_link_lib(lokinet-plainquic - lokinet-platform - lokinet-config +target_link_libraries(lokinet-utils + PUBLIC + lokinet-cryptography ) -lokinet_link_lib(lokinet-dns - lokinet-platform - lokinet-dns-platform - lokinet-config -) - -lokinet_link_lib(lokinet-nodedb - lokinet-util - lokinet-platform -) - -lokinet_link_lib(lokinet-util +# cross linkage +target_link_libraries(lokinet-core + PUBLIC + lokinet-cryptography lokinet-nodedb - lokinet-platform + lokinet-path + lokinet-rpc + lokinet-wire ) -lokinet_link_lib(lokinet-rpc - lokinet-context - lokinet-peerstats - lokinet-util -) - -# inter lokinet-layer public/private linkage. -# when linking each layer, we consider the layer directly below private linkage and the layer above public linkage. -# this lets us hide functionality of layers below us when depended on by another component. -# -# from highest to lowest layer, the above layers are stacked as follows: -# -# platform (what lokinet snapps interact with, be it l3 os interaction or embedded lokinet) -# flow (how we want to route and stripe over our onion routing) -# routing (what we are onion routing) -# onion (how the onion routing happens) -# link (what we want to send over the wire and to where) -# wire (what is actually sent over the wire) -# -function(link_lokinet_layers) - set(lib ${ARGV0}) - if(${ARGC} GREATER 1) - lokinet_link_lib(${ARGV1} ${lib}) - list(REMOVE_AT ARGV 1) - target_link_libraries(${lib} PRIVATE ${ARGV1}) - # recursion :D - link_lokinet_layers(${ARGV}) - else() - lokinet_link_lib(${lib}) - endif() -endfunction() - -link_lokinet_layers( - lokinet-layer-platform - lokinet-layer-flow - lokinet-layer-routing - lokinet-layer-onion - lokinet-layer-link - lokinet-layer-wire -) - -# set me to OFF to disable old codepath -set(use_old_impl ON) -if(use_old_impl) - # flow layer deprecated-kitchensink (remove me after refactor) - lokinet_link_lib(lokinet-service-deprecated-kitchensink - lokinet-dns - lokinet-nodedb - lokinet-context - lokinet-plainquic - lokinet-layer-routing - lokinet-layer-onion - lokinet-dht - lokinet-platform - lokinet-rpc - ) - target_link_libraries(lokinet-layers INTERFACE lokinet-service-deprecated-kitchensink) -endif() - -target_link_libraries(lokinet-layers INTERFACE - lokinet-layer-platform - lokinet-layer-flow - lokinet-layer-routing - lokinet-layer-onion - lokinet-layer-link - lokinet-layer-wire -) +target_link_libraries(lokinet-base + INTERFACE + + libevent::core + libevent::threads + libevent::extra -# per component external deps + Threads::Threads -target_link_libraries(lokinet-config PUBLIC oxenmq::oxenmq) -target_link_libraries(lokinet-platform PUBLIC oxenmq::oxenmq) -target_link_libraries(lokinet-dns PUBLIC libunbound) + oxenc::oxenc + oxen::logging + oxenmq::oxenmq + + libunbound -target_link_libraries(lokinet-cryptography PUBLIC - oxenc::oxenc sodium ) -target_link_libraries(lokinet-context PUBLIC - CLI11 - oxenmq::oxenmq - uvw -) - -target_link_libraries(lokinet-platform PUBLIC - Threads::Threads - base_libs - uvw -) - -target_link_libraries(lokinet-util PUBLIC - nlohmann_json::nlohmann_json - filesystem - oxenc::oxenc -) -target_link_libraries(lokinet-plainquic PUBLIC - ngtcp2_static - uvw -) +# target_link_libraries(lokinet-rpc PUBLIC oxenmq::oxenmq) +# target_link_libraries(lokinet-core PUBLIC oxenmq::oxenmq) +# target_link_libraries(lokinet-config PUBLIC oxenmq::oxenmq) +# target_link_libraries(lokinet-nodedb PUBLIC oxenmq::oxenmq) +# target_link_libraries(lokinet-path PUBLIC oxenmq::oxenmq) +# target_link_libraries(lokinet-platform PUBLIC oxenmq::oxenmq Threads::Threads) +# target_link_libraries(lokinet-cryptography PUBLIC sodium) +# target_link_libraries(lokinet-dns PUBLIC libunbound) +# target_link_libraries(lokinet-utils PUBLIC nlohmann_json::nlohmann_json) +# target_link_libraries(lokinet-wire PUBLIC oxenmq::oxenmq quic) if(WITH_EMBEDDED_LOKINET) include(GNUInstallDirs) add_library(lokinet-shared SHARED lokinet_shared.cpp) - target_link_libraries(lokinet-shared PUBLIC lokinet-amalgum) + # target_link_libraries(lokinet-shared PUBLIC lokinet-amalgum) + target_link_libraries(lokinet-shared PUBLIC lokinet-core) if(WIN32) set(CMAKE_SHARED_LIBRARY_PREFIX_CXX "") endif() diff --git a/llarp/address/address.cpp b/llarp/address/address.cpp new file mode 100644 index 0000000000..6a11496c68 --- /dev/null +++ b/llarp/address/address.cpp @@ -0,0 +1,90 @@ +#include "address.hpp" + +#include "utils.hpp" + +namespace llarp +{ + static auto logcat = log::Cat("address"); + + std::optional NetworkAddress::from_network_addr(const std::string& arg) + { + std::optional ret = std::nullopt; + + if (arg.ends_with(TLD::SNODE)) + { + ret = NetworkAddress{arg, TLD::SNODE}; + } + else if (arg.ends_with(TLD::LOKI)) + { + ret = NetworkAddress{arg, TLD::LOKI}; + } + else + log::warning(logcat, "Invalid NetworkAddress constructor input (arg:{})", arg); + + return ret; + } + + NetworkAddress NetworkAddress::from_pubkey(const RouterID& rid, bool is_client) + { + return NetworkAddress{rid, is_client}; + } + + NetworkAddress::NetworkAddress(std::string_view arg, std::string_view tld) : _tld{tld} + { + if (not _pubkey.from_string(arg.substr(0, _tld.size()))) + throw std::invalid_argument{"Invalid pubkey passed to NetworkAddress constructor: {}"_format(arg)}; + + _is_client = tld == TLD::LOKI; + } + + bool NetworkAddress::operator<(const NetworkAddress& other) const + { + return std::tie(_pubkey, _is_client) < std::tie(other._pubkey, other._is_client); + } + + bool NetworkAddress::operator==(const NetworkAddress& other) const + { + return std::tie(_pubkey, _is_client) == std::tie(other._pubkey, other._is_client); + } + + bool NetworkAddress::operator!=(const NetworkAddress& other) const + { + return !(*this == other); + } + + std::optional RelayAddress::from_relay_addr(std::string arg) + { + std::optional ret = std::nullopt; + + if (not arg.ends_with(".snode")) + log::warning(logcat, "Invalid RelayAddress constructor input lacking '.snode' (input:{})", arg); + else + ret = RelayAddress{arg}; + + return ret; + } + + RelayAddress::RelayAddress(std::string_view arg) + { + // This private constructor is only called after checking for a '.snode' suffix; only Santa checks twice + arg.remove_suffix(6); + + if (not _pubkey.from_string(arg)) + throw std::invalid_argument{"Invalid pubkey passed to RelayAddress constructor: {}"_format(arg)}; + } + + bool RelayAddress::operator<(const RelayAddress& other) const + { + return _pubkey < other._pubkey; + } + + bool RelayAddress::operator==(const RelayAddress& other) const + { + return _pubkey == other._pubkey; + } + + bool RelayAddress::operator!=(const RelayAddress& other) const + { + return !(*this == other); + } +} // namespace llarp diff --git a/llarp/address/address.hpp b/llarp/address/address.hpp new file mode 100644 index 0000000000..3b54d178d1 --- /dev/null +++ b/llarp/address/address.hpp @@ -0,0 +1,163 @@ +#pragma once + +#include "keys.hpp" +#include "utils.hpp" + +#include +#include +#include +#include + +#include + +#include + +namespace llarp +{ + /** NOTE: + - These classes are purposely differentiated at the moment. At first pass, they seem like they could be easily + combined into one shared class utilizing some sort of variant or templating + - This may eventually be true, but the currently enforced heterogeneity is intended to leave space for a + near-future replacement of RouterID and PubKey with ClientKey and RelayKey + */ + + /** NetworkAddress: + This address type conceptually encapsulates any addressible hidden service or exit node operating on the + network. This type is to be strictly used in contexts referring to remote exit nodes or hidden services + operated on clients and clients/relays respectively. It can be constructed a few specific ways: + - static ::from_network_addr(...) : this function expects a network address string terminated in '.loki' + or '.snode'. + */ + struct NetworkAddress + { + private: + PubKey _pubkey; + + bool _is_client{false}; + std::string _tld; + + // This private constructor expects a '.snode' or '.loki' suffix + explicit NetworkAddress(std::string_view addr, std::string_view tld); + + // This private constructor expects NO '.snode' or '.loki' suffix + explicit NetworkAddress(RouterID rid, bool is_client) : _pubkey{std::move(rid)}, _is_client{is_client} {} + + public: + NetworkAddress() = default; + ~NetworkAddress() = default; + + NetworkAddress(const NetworkAddress& other) = default; + NetworkAddress(NetworkAddress&& other) = default; + + NetworkAddress& operator=(const NetworkAddress& other) = default; + NetworkAddress& operator=(NetworkAddress&& other) = default; + + bool operator<(const NetworkAddress& other) const; + bool operator==(const NetworkAddress& other) const; + bool operator!=(const NetworkAddress& other) const; + + bool is_empty() const { return _pubkey.is_zero() and _tld.empty(); } + + // Will throw invalid_argument with bad input. Assumes that the network address terminates in either '.loki' + // or '.snode' + static std::optional from_network_addr(const std::string& arg); + + // Assumes that the pubkey passed is NOT terminated in either a '.loki' or '.snode' suffix + static NetworkAddress from_pubkey(const RouterID& rid, bool is_client); + + bool is_client() const { return _is_client; } + + bool is_relay() const { return !is_client(); } + + const PubKey& pubkey() const { return _pubkey; } + + PubKey& pubkey() { return _pubkey; } + + const RouterID& router_id() const { return static_cast(pubkey()); } + + RouterID& router_id() { return static_cast(pubkey()); } + + std::string name() const { return _pubkey.to_string(); } + + std::string to_string() const { return name().append(_tld); } + + static constexpr bool to_string_formattable{true}; + }; + + /** RelayAddress: + This address object encapsulates the concept of an addressible service node operating on the network as a + lokinet relay. This object is NOT meant to be used in any scope referring to a hidden service or exit node + being operated on that remote relay (not that service nodes operate exit nodes anyways) -- for that, use the + above NetworkAddress type. + + This object will become more differentiated from NetworkAddress once {Relay,Client}PubKey is implemented. + That is a whole other can of worms... + */ + struct RelayAddress + { + private: + PubKey _pubkey; + + explicit RelayAddress(std::string_view addr); + + public: + RelayAddress() = default; + ~RelayAddress() = default; + + explicit RelayAddress(PubKey cpk) : _pubkey{std::move(cpk)} {} + + RelayAddress(const RelayAddress& other) = default; + + RelayAddress(RelayAddress&& other) : _pubkey{std::move(other._pubkey)} {} + + RelayAddress& operator=(const RelayAddress& other) = default; + RelayAddress& operator=(RelayAddress&& other) = default; + + bool operator<(const RelayAddress& other) const; + bool operator==(const RelayAddress& other) const; + bool operator!=(const RelayAddress& other) const; + + // Will throw invalid_argument with bad input + static std::optional from_relay_addr(std::string arg); + + const PubKey& pubkey() const { return _pubkey; } + + PubKey& pubkey() { return _pubkey; } + + const RouterID& router_id() const { return static_cast(pubkey()); } + + RouterID& router_id() { return static_cast(pubkey()); } + + std::string to_string() const { return _pubkey.to_string().append(TLD::SNODE); } + + static constexpr bool to_string_formattable = true; + }; + + namespace concepts + { + template + concept NetworkAddrType = std::is_base_of_v; + }; // namespace concepts + +} // namespace llarp + +namespace std +{ + template <> + struct hash + { + virtual size_t operator()(const llarp::NetworkAddress& r) const + { + return std::hash{}(r.to_string()); + } + }; + + template <> + struct hash + { + virtual size_t operator()(const llarp::RelayAddress& r) const + { + return std::hash{}(r.to_string()); + } + }; +} // namespace std diff --git a/llarp/address/ip_packet.cpp b/llarp/address/ip_packet.cpp new file mode 100644 index 0000000000..3d1ebbccb6 --- /dev/null +++ b/llarp/address/ip_packet.cpp @@ -0,0 +1,382 @@ +#include "ip_packet.hpp" + +#include "utils.hpp" + +#include +#include + +#include + +#include +#include + +namespace llarp +{ + static auto logcat = log::Cat("ip_packet"); + + IPPacket::IPPacket(size_t sz) + { + if (sz and sz < MIN_PACKET_SIZE) + throw std::invalid_argument{"Buffer size is too small for an IP packet!"}; + _buf.resize(sz); + std::fill(_buf.begin(), _buf.end(), 0); + } + + IPPacket::IPPacket(bstring_view data) : IPPacket{reinterpret_cast(data.data()), data.size()} + {} + + IPPacket::IPPacket(ustring_view data) : IPPacket{data.data(), data.size()} {} + + IPPacket::IPPacket(std::vector data) : IPPacket{data.data(), data.size()} {} + + IPPacket::IPPacket(const uint8_t* buf, size_t len) + { + if (len < MIN_PACKET_SIZE) + { + _buf.resize(0); + } + else + { + _buf.resize(len); + std::copy_n(buf, len, _buf.data()); + } + + _init_internals(); + } + + IPPacket IPPacket::from_netpkt(NetworkPacket pkt) + { + auto data = pkt.data(); + return IPPacket{reinterpret_cast(data.data()), data.size()}; + } + + std::optional IPPacket::from_buffer(const uint8_t* buf, size_t len) + { + std::optional ret = std::nullopt; + + if (IPPacket b; b.load(buf, len)) + ret.emplace(std::move(b)); + + return ret; + } + + void IPPacket::_init_internals() + { + _header = reinterpret_cast(data()); + _v6_header = reinterpret_cast(data()); + + _is_v4 = _header->protocol == uint8_t{4}; + _is_udp = _header->protocol == uint8_t{17}; + + uint16_t src_port = + (_is_udp) ? *reinterpret_cast(data() + (static_cast(_header->header_len) * 4)) : 0; + uint16_t dest_port = (_is_udp) + ? *reinterpret_cast(data() + (static_cast(_header->header_len) * 4) + 2) + : 0; + + if (_is_v4) + { + auto src = in_addr{_header->src}; + auto dest = in_addr{_header->dest}; + + _src_addr.set_addr(&src); + _dst_addr.set_addr(&dest); + } + else + { + _src_addr.set_addr(&_v6_header->srcaddr); + _dst_addr.set_addr(&_v6_header->dstaddr); + } + + _src_addr.set_port(src_port); + _dst_addr.set_port(dest_port); + } + + std::optional> IPPacket::l4_data() const + { + size_t hdr_sz = 0; + + if (_header->protocol == 0x11) + { + hdr_sz = 8; + } + else + return std::nullopt; + + // check for invalid size + if (size() < (static_cast(_header->header_len) * 4) + hdr_sz) + return std::nullopt; + + const uint8_t* ptr = data() + ((static_cast(_header->header_len) * 4) + hdr_sz); + + return std::make_pair(reinterpret_cast(ptr), std::distance(ptr, data() + size())); + } + + void IPPacket::update_ipv4_address(ipv4 src, ipv4 dst) + { + log::debug(logcat, "Setting new source ({}) and destination ({}) IPs", src, dst); + + std::basic_string_view head_u16s{reinterpret_cast(_header), sizeof(ip_header)}; + // set new IP addresses + _header->src = src.addr; + _header->dest = dst.addr; + + switch (_header->protocol) + { + case 6: // TCP + case 17: // UDP + case 136: // UDP-Lite - same checksum place, same 0->0xFFff condition + case 33: // DCCP + _header->checksum = tcpudp_checksum_ipv4( + _header->src, _header->dest, _header->header_len, _header->protocol, _header->checksum); + break; + default: + // do nothing + break; + } + + // IPv4 checksum + auto v4chk = (uint16_t*)&(_header->checksum); + *v4chk = checksum_ipv4(_header, _header->header_len); + + _init_internals(); + } + + void IPPacket::update_ipv6_address(ipv6 src, ipv6 dst, std::optional flowlabel) + { + const size_t ihs = 4 + 4 + 16 + 16; + const auto sz = size(); + // XXX should've been checked at upper level? + if (sz <= ihs) + return; + + auto hdr = v6_header(); + if (flowlabel.has_value()) + { + // set flow label if desired + hdr->set_flowlabel(*flowlabel); + } + + // IPv6 address + hdr->srcaddr = src.to_in6(); + hdr->dstaddr = dst.to_in6(); + + // TODO IPv6 header options + auto* pld = data() + ihs; + auto psz = sz - ihs; + + size_t fragoff = 0; + auto nextproto = hdr->protocol; + for (;;) + { + switch (nextproto) + { + case 0: // Hop-by-Hop Options + case 43: // Routing Header + case 60: // Destination Options + { + nextproto = pld[0]; + auto addlen = (size_t(pld[1]) + 1) * 8; + if (psz < addlen) + return; + pld += addlen; + psz -= addlen; + break; + } + + case 44: // Fragment Header + /* + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Header | Reserved | Fragment Offset |Res|M| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Identification | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + nextproto = pld[0]; + fragoff = (uint16_t(pld[2]) << 8) | (uint16_t(pld[3]) & 0xFC); + if (psz < 8) + return; + pld += 8; + psz -= 8; + + // jump straight to payload processing + if (fragoff != 0) + goto endprotohdrs; + break; + + default: + goto endprotohdrs; + } + } + endprotohdrs: + + uint16_t chksumoff{0}; + uint16_t chksum{0}; + + switch (nextproto) + { + case 6: // TCP + chksumoff = 16; + [[fallthrough]]; + case 33: // DCCP + chksum = tcp_checksum_ipv6(&hdr->srcaddr, &hdr->dstaddr, hdr->payload_len, 0); + + // ones-complement addition fo 0xFFff is 0; this is verboten + if (chksum == 0xFFff) + chksum = 0x0000; + + chksumoff = chksumoff == 16 ? 16 : 6; + _is_udp = false; + break; + case 17: // UDP + case 136: // UDP-Lite - same checksum place, same 0->0xFFff condition + chksum = udp_checksum_ipv6(&hdr->srcaddr, &hdr->dstaddr, hdr->payload_len, 0); + _is_udp = true; + break; + default: + // do nothing + break; + } + + auto check = _is_udp ? (uint16_t*)(pld + 6) : (uint16_t*)(pld + chksumoff - fragoff); + + *check = chksum; + + _init_internals(); + } + + std::optional IPPacket::make_icmp_unreachable() const + { + if (is_ipv4()) + { + auto ip_hdr_sz = _header->header_len * 4; + auto pkt_size = (ICMP_HEADER_SIZE + ip_hdr_sz) * 2; + + IPPacket pkt{static_cast(pkt_size)}; + + _header->version = 4; + _header->header_len = 0x05; + _header->service_type = 0; + _header->checksum = 0; + _header->total_len = ntohs(pkt_size); + _header->src = _header->dest; + _header->dest = _header->src; + _header->protocol = 1; // ICMP + _header->ttl = _header->ttl; + _header->frag_off = htons(0b0100000000000000); + + uint16_t* checksum; + uint8_t* itr = pkt.data() + ip_hdr_sz; + uint8_t* icmp_begin = itr; // type 'destination unreachable' + *itr++ = 3; + + // code 'Destination host unknown error' + *itr++ = 7; + + // checksum + unused + oxenc::write_host_as_big(0, itr); + checksum = (uint16_t*)itr; + itr += 4; + + // next hop mtu is ignored but let's put something here anyways just in case tm + oxenc::write_host_as_big(1500, itr); + itr += 2; + + // copy ip header and first 8 bytes of datagram for icmp rject + std::copy_n(data(), ip_hdr_sz + ICMP_HEADER_SIZE, itr); + itr += ip_hdr_sz + ICMP_HEADER_SIZE; + + // calculate checksum of ip header + _header->checksum = checksum_ipv4(_header, _header->header_len); + const auto icmp_size = std::distance(icmp_begin, itr); + + // calculate icmp checksum + *checksum = checksum_ipv4(icmp_begin, icmp_size); + return pkt; + } + return std::nullopt; + } + + NetworkPacket IPPacket::make_netpkt() + { + return NetworkPacket{oxen::quic::Path{_src_addr, _dst_addr}, bview()}; + } + + bool IPPacket::load(ustring_view data) + { + return load(data.data(), data.size()); + } + + bool IPPacket::load(std::string_view data) + { + return load(reinterpret_cast(data.data()), data.size()); + } + + bool IPPacket::load(std::vector data) + { + return load(data.data(), data.size()); + } + + bool IPPacket::load(const uint8_t* buf, size_t len) + { + if (len < MIN_PACKET_SIZE) + return false; + + _buf.clear(); + _buf.resize(len); + std::copy_n(buf, len, _buf.data()); + + _init_internals(); + + return true; + } + + bool IPPacket::take(std::vector data) + { + auto len = data.size(); + if (len < MIN_PACKET_SIZE) + return false; + + _buf.clear(); + _buf.resize(len); + std::memmove(_buf.data(), data.data(), len); + + _init_internals(); + + return true; + } + + std::vector IPPacket::steal_buffer() && + { + std::vector b; + b.resize(size()); + _buf.swap(b); + return b; + } + + std::string IPPacket::steal_payload() && + { + auto ret = to_string(); + _buf.clear(); + return ret; + } + + std::vector IPPacket::give_buffer() + { + std::vector b; + b.resize(size()); + std::memcpy(b.data(), data(), size()); + return b; + } + + std::string IPPacket::to_string() + { + return {reinterpret_cast(data()), size()}; + } + + std::string IPPacket::info_line() const + { + return "IPPacket (src:{}, dest:{}, size:{})"_format(_src_addr, _dst_addr, size()); + } + +} // namespace llarp diff --git a/llarp/address/ip_packet.hpp b/llarp/address/ip_packet.hpp new file mode 100644 index 0000000000..3c49ef8819 --- /dev/null +++ b/llarp/address/ip_packet.hpp @@ -0,0 +1,142 @@ +#pragma once + +#include "types.hpp" + +#include +#include +#include + +namespace llarp +{ + inline constexpr size_t MAX_PACKET_SIZE{1500}; + inline constexpr size_t MIN_PACKET_SIZE{20}; + + struct IPPacket; + + // Typedef for packets being transmitted between lokinet instances + using NetworkPacket = oxen::quic::Packet; + + using net_pkt_hook = std::function; + using ip_pkt_hook = std::function; + + /** IPPacket + This class encapsulates the functionalities and attributes required for data transmission between the local + lokinet instance and the surrounding IP landscape. As data enters lokinet from the device/internet/etc, it is + transmitted across the network as a NetworkPacket via QUIC. As it exits lokinet to the device/internet/etc, it + is constructed into an IPPacket. + + This allows for necessary functionalities at the junction that data is entering and exiting the local lokinet + instance. For example + + */ + struct IPPacket + { + private: + std::vector _buf; + + ip_header* _header{}; + ipv6_header* _v6_header{}; + + oxen::quic::Address _src_addr{}; + oxen::quic::Address _dst_addr{}; + + bool _is_v4{false}; + bool _is_udp{false}; + + void _init_internals(); + + public: + IPPacket() : IPPacket{size_t{0}} {} + explicit IPPacket(size_t sz); + explicit IPPacket(bstring_view data); + explicit IPPacket(ustring_view data); + explicit IPPacket(std::vector data); + explicit IPPacket(const uint8_t* buf, size_t len); + + static IPPacket from_netpkt(NetworkPacket pkt); + static std::optional from_buffer(const uint8_t* buf, size_t len); + + NetworkPacket make_netpkt(); + + bool is_ipv4() const { return _is_v4; } + + const oxen::quic::Address& source() const { return _src_addr; } + + uint16_t source_port() { return source().port(); } + + const oxen::quic::Address& destination() const { return _dst_addr; } + + uint16_t dest_port() { return destination().port(); } + + ipv4 source_ipv4() { return _src_addr.to_ipv4(); } + + ipv6 source_ipv6() { return _src_addr.to_ipv6(); } + + ipv4 dest_ipv4() const { return _dst_addr.to_ipv4(); } + + ipv6 dest_ipv6() const { return _dst_addr.to_ipv6(); } + + ip_header* header() { return _header; } + + const ip_header* header() const { return reinterpret_cast(_header); } + + ipv6_header* v6_header() { return _v6_header; } + + const ipv6_header* v6_header() const { return reinterpret_cast(_v6_header); } + + std::optional> l4_data() const; + + void clear_addresses() + { + if (_is_v4) + return update_ipv4_address(ipv4{}, ipv4{}); + return update_ipv6_address(ipv6{}, ipv6{}); + } + + void update_ipv4_address(ipv4 src, ipv4 dst); + + void update_ipv6_address(ipv6 src, ipv6 dst, std::optional flowlabel = std::nullopt); + + std::optional make_icmp_unreachable() const; + + uint8_t* data() { return _buf.data(); } + + const uint8_t* data() const { return _buf.data(); } + + const uint8_t* protocol() const { return _is_v4 ? &header()->protocol : &v6_header()->protocol; } + + size_t size() const { return _buf.size(); } + + bool empty() const { return _buf.empty(); } + + bool load(ustring_view data); + + bool load(std::string_view data); + + bool load(std::vector data); + + bool load(const uint8_t* buf, size_t len); + + // takes posession of the data + bool take(std::vector data); + + // steals posession of the underlying data, and can only be used in an r-value context + std::vector steal_buffer() &&; + + std::string steal_payload() &&; + + // gives a copy of the underlying data + std::vector give_buffer(); + + std::string_view view() const { return {reinterpret_cast(data()), size()}; } + + bstring_view bview() const { return {reinterpret_cast(data()), size()}; } + + ustring_view uview() const { return {data(), size()}; } + + std::string to_string(); + + std::string info_line() const; + }; + +} // namespace llarp diff --git a/llarp/address/ip_range.cpp b/llarp/address/ip_range.cpp new file mode 100644 index 0000000000..c47069af65 --- /dev/null +++ b/llarp/address/ip_range.cpp @@ -0,0 +1,119 @@ +#include "ip_range.hpp" + +namespace llarp +{ + static auto logcat = log::Cat("iprange"); + + void IPRange::_init_ip() + { + if (_is_ipv4) + { + _base_ip = _addr.to_ipv4().to_base(_mask); + _ip_range = ipv4_range{std::get(_base_ip), _mask}; + _max_ip = std::get(_ip_range).max_ip(); + } + else + { + _base_ip = _addr.to_ipv6().to_base(_mask); + _ip_range = ipv6_range{std::get(_base_ip), _mask}; + _max_ip = std::get(_ip_range).max_ip(); + } + } + + std::optional IPRange::from_string(std::string arg) + { + std::optional range = std::nullopt; + oxen::quic::Address _addr; + uint8_t _mask; + + if (auto pos = arg.find_first_of('/'); pos != std::string::npos) + { + try + { + auto [host, p] = detail::parse_addr(arg.substr(0, pos), 0); + assert(p == 0); + _addr = oxen::quic::Address{host, p}; + + if (parse_int(arg.substr(pos + 1), _mask)) + range = IPRange{std::move(_addr), _mask}; + else + log::warning( + logcat, + "Failed to construct IPRange from string input:{} parsed into addr:{}, mask:{}", + arg, + _addr, + arg.substr(pos + 1)); + } + catch (const std::exception& e) + { + log::error(logcat, "Exception caught parsing IPRange:{}", e.what()); + } + } + + return range; + } + + bool IPRange::contains(const IPRange& other) const + { + if (is_ipv4() ^ other.is_ipv4()) + return false; + + if (is_ipv4()) + return _contains(std::get(other.base_ip())); + + return _contains(std::get(other.base_ip())); + } + + bool IPRange::_contains(const ipv4& other) const + { + return _ipv4_range().contains(other); + } + + bool IPRange::_contains(const ipv6& other) const + { + return _ipv6_range().contains(other); + } + + bool IPRange::contains(const ip_v& other) const + { + if (is_ipv4() ^ std::holds_alternative(other)) + return false; + + return is_ipv4() ? _contains(std::get(other)) : _contains(std::get(other)); + } + + std::optional IPRange::find_private_range(const std::list& excluding, bool ipv6_enabled) + { + if (excluding.empty()) + return std::nullopt; + + auto filter = [&excluding](const ip_range_v& range) -> bool { + for (const auto& e : excluding) + if (e == range) + return false; + return true; + }; + + // check ipv4 private addresses + if (excluding.front().is_ipv4()) + { + for (const auto& r : ipv4_private) + { + if (filter(r)) + return r; + } + } + // the invoking context also ensures thet the list `excluding` also does not include ipv6 before passing + // the boolean to this function + else if (ipv6_enabled) + { + for (size_t n = 0; n < num_ipv6_private; ++n) + { + if (auto v6 = ipv6(0xfd2e, 0x6c6f, 0x6b69, n) / 64; filter(v6)) + return v6; + } + } + + return std::nullopt; + } +} // namespace llarp diff --git a/llarp/address/ip_range.hpp b/llarp/address/ip_range.hpp new file mode 100644 index 0000000000..101b01545c --- /dev/null +++ b/llarp/address/ip_range.hpp @@ -0,0 +1,230 @@ +#pragma once + +#include "utils.hpp" + +#include + +#include + +namespace llarp +{ + inline constexpr size_t num_ipv6_private{65536}; + inline constexpr std::array ipv4_private = detail::generate_private_ipv4(); + + struct IPRange + { + private: + oxen::quic::Address _addr; + uint8_t _mask; + bool _is_ipv4; + + ip_v _base_ip; + ip_range_v _ip_range; + ip_v _max_ip; + + void _init_ip(); + + // internal functions that do no type checking for ipv4 vs ipv6 + bool _contains(const ipv4& other) const; + bool _contains(const ipv6& other) const; + + // getters to DRY out variant access + ipv4_range& _ipv4_range() { return std::get(_ip_range); } + const ipv4_range& _ipv4_range() const { return std::get(_ip_range); } + ipv6_range& _ipv6_range() { return std::get(_ip_range); } + const ipv6_range& _ipv6_range() const { return std::get(_ip_range); } + + public: + IPRange() : IPRange{oxen::quic::Address{}, 0} {} + + explicit IPRange(std::string a, uint8_t m = 0) : IPRange{oxen::quic::Address{std::move(a), 0}, m} {} + + explicit IPRange(oxen::quic::Address a, uint8_t m) : _addr{std::move(a)}, _mask{m}, _is_ipv4{_addr.is_ipv4()} + { + _init_ip(); + } + + IPRange(const ipv4_range& ipv4) + : _addr{ipv4.base}, + _mask{ipv4.mask}, + _is_ipv4{true}, + _base_ip{ipv4.base}, + _ip_range{ipv4}, + _max_ip{ipv4.max_ip()} + {} + + IPRange(const ipv6_range& ipv6) + : _addr{ipv6.base}, + _mask{ipv6.mask}, + _is_ipv4{false}, + _base_ip{ipv6.base}, + _ip_range{ipv6}, + _max_ip{ipv6.max_ip()} + {} + + static std::optional find_private_range( + const std::list& excluding, bool ipv6_enabled = false); + + void bt_encode(oxenc::bt_list_producer& btlp) const { btlp.append(to_string()); } + + std::string to_string() const { return is_ipv4() ? _ipv4_range().to_string() : _ipv6_range().to_string(); } + + static std::optional from_string(std::string arg); + + bool contains(const IPRange& other) const; + bool contains(const ip_v& other) const; + + bool is_ipv4() const { return _is_ipv4; } + + ip_range_v get_ip_range() const { return _ip_range; } + + ip_v base_ip() const { return _base_ip; } + + ip_v max_ip() const { return _max_ip; } + + const uint8_t& mask() const { return _mask; } + uint8_t mask() { return _mask; } + + const oxen::quic::Address& address() const { return _addr; } + oxen::quic::Address address() { return _addr; } + + bool operator<(const IPRange& other) const + { + return std::tie(_addr, _mask) < std::tie(other._addr, other._mask); + } + + bool operator==(const IPRange& other) const + { + return std::tie(_addr, _mask) == std::tie(other._addr, other._mask); + } + + bool operator==(const ip_range_v& other) const + { + if (_is_ipv4 and std::holds_alternative(other)) + return _ipv4_range() == std::get(other); + if (not _is_ipv4 and std::holds_alternative(other)) + return _ipv6_range() == std::get(other); + + return false; + } + + static constexpr bool to_string_formattable = true; + }; + + /** IPRangeIterator + - When lokinet is assigning IP's within a range, this object functions as a robust managing context for the + distribution and tracking of IP's within that range + */ + struct IPRangeIterator + { + private: + IPRange _ip_range; + bool _is_ipv4; + + ip_v _current_ip; + ip_v _max_ip; + + ipv4 _current_ipv4() { return std::get(_current_ip); } + const ipv4& _current_ipv4() const { return std::get(_current_ip); } + ipv6 _current_ipv6() { return std::get(_current_ip); } + const ipv6& _current_ipv6() const { return std::get(_current_ip); } + + ipv4 _max_ipv4() { return std::get(_max_ip); } + const ipv4& _max_ipv4() const { return std::get(_max_ip); } + ipv6 _max_ipv6() { return std::get(_max_ip); } + const ipv6& _max_ipv6() const { return std::get(_max_ip); } + + // internal incrementing mutators that will return true on success and false on overflow/reset + bool _increment_ipv4() + { + bool ret = false; + + if (auto next_v4 = _current_ipv4().next_ip(); next_v4) + { + _current_ip = *next_v4; + ret = true; + } + + return ret; + } + + bool _increment_ipv6() + { + bool ret = false; + + if (auto next_v6 = _current_ipv6().next_ip(); next_v6) + { + _current_ip = *next_v6; + ret = true; + } + + return ret; + } + + public: + IPRangeIterator() = default; + + IPRangeIterator(const IPRange& range) + : _ip_range{range}, _is_ipv4{range.is_ipv4()}, _current_ip{range.base_ip()}, _max_ip{range.max_ip()} + {} + + // Returns the next ip address in the iterating range; returns std::nullopt if range is exhausted + std::optional next_ip() + { + std::optional ret = std::nullopt; + + if (range_exhausted()) + return ret; + + if (is_ipv4() ? _increment_ipv4() : _increment_ipv6()) + ret = _current_ip; + + return ret; + } + + ip_v max_ip() { return _max_ip; } + + void reset() + { + _current_ip = _ip_range.base_ip(); + _max_ip = _ip_range.max_ip(); + } + + bool range_exhausted() const + { + return is_ipv4() ? _current_ipv4() == _max_ipv4() : _current_ipv6() == _max_ipv6(); + } + + bool is_ipv4() const { return _is_ipv4; } + }; + + namespace concepts + { + template + concept LocalAddrType = std::is_same_v || std::is_same_v + || std::is_same_v; + } // namespace concepts + +} // namespace llarp + +namespace std +{ + template <> + struct hash + { + size_t operator()(const llarp::IPRange& r) const + { + size_t h; + + if (r.is_ipv4()) + h = hash{}(std::get(r.base_ip())); + else + h = hash{}(std::get(r.base_ip())); + + h ^= hash{}(r.mask()) + oxen::quic::inverse_golden_ratio + (h << 6) + (h >> 2); + + return h; + } + }; + +} // namespace std diff --git a/llarp/address/keys.cpp b/llarp/address/keys.cpp new file mode 100644 index 0000000000..1005244d12 --- /dev/null +++ b/llarp/address/keys.cpp @@ -0,0 +1,46 @@ +#include "keys.hpp" + +#include + +namespace llarp +{ + bool PubKey::from_hex(const std::string& str) + { + if (str.size() != 2 * size()) + return false; + oxenc::from_hex(str.begin(), str.end(), begin()); + return true; + } + + std::string PubKey::to_string() const + { + return oxenc::to_hex(begin(), end()); + } + + PubKey& PubKey::operator=(const uint8_t* ptr) + { + std::copy(ptr, ptr + SIZE, begin()); + return *this; + } + + PubKey& PubKey::operator=(const PubKey& other) + { + std::memcpy(begin(), other.begin(), PUBKEYSIZE); + return *this; + } + + bool PubKey::operator<(const PubKey& other) const + { + return as_array() < other.as_array(); + } + + bool PubKey::operator==(const PubKey& other) const + { + return as_array() == other.as_array(); + } + + bool PubKey::operator!=(const PubKey& other) const + { + return !(*this == other); + } +} // namespace llarp diff --git a/llarp/address/keys.hpp b/llarp/address/keys.hpp new file mode 100644 index 0000000000..36aa127dea --- /dev/null +++ b/llarp/address/keys.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include + +/** TODO: + - re-configure string_view and ustring_view methods after deprecating RouterID + +*/ + +namespace llarp +{ + struct PubKey : public AlignedBuffer + { + PubKey() = default; + + bool from_hex(const std::string& str); + + std::string to_string() const; + + explicit PubKey(const uint8_t* data) : AlignedBuffer{data} {} + explicit PubKey(const std::array& data) : AlignedBuffer{data} {} + explicit PubKey(ustring_view data) : AlignedBuffer{data.data()} {} + explicit PubKey(std::string_view data) : PubKey{to_usv(data)} {} + PubKey(const PubKey& other) : PubKey{other.data()} {} + PubKey(PubKey&& other) : PubKey{other.data()} {} + + PubKey& operator=(const PubKey& other); + + // revisit this + PubKey& operator=(const uint8_t* ptr); + + bool operator<(const PubKey& other) const; + bool operator==(const PubKey& other) const; + bool operator!=(const PubKey& other) const; + }; +} // namespace llarp + +namespace std +{ + template <> + struct hash : public hash> + {}; +} // namespace std diff --git a/llarp/address/map.hpp b/llarp/address/map.hpp new file mode 100644 index 0000000000..72ab53a401 --- /dev/null +++ b/llarp/address/map.hpp @@ -0,0 +1,176 @@ +#pragma once + +#include "address.hpp" +#include "ip_range.hpp" +#include "types.hpp" + +#include +#include + +namespace llarp +{ + /** TODO: + - if libquic Address is never used for this templated class, then either: + - this map can potentially be made (mostly) constexpr + - a new map just for IP addresses can be made fully constexpr + */ + + /** This class will accept any types satisfying the concepts LocalAddrType and RemoteAddrType + LocalAddrType: oxen::quic::Address, IPRange, or ip_v (ipv{4,6} variant) + NetworkAddrType: must be inherited from NetworkAddress + */ + template + struct address_map + { + protected: + std::unordered_map _local_to_remote; + std::unordered_map _remote_to_local; + std::unordered_map _name_to_remote; + + using Lock_t = util::NullLock; + mutable util::NullMutex addr_mutex; + + public: + /** This functions exactly as std::unordered_map's ::insert_or_assign method. If a key equivalent + to `local` or `remote` already exists, then they will be assigned to the corresponding value. + Otherwise, the values will be inserted. + + The returned `bool` is true if the insertion took place and `false` if assignment occurred. + */ + bool insert_or_assign(local_addr_t local, net_addr_t remote) + { + Lock_t l{addr_mutex}; + + auto name = remote.name(); + + auto [_1, b1] = _local_to_remote.insert_or_assign(local, remote); + auto [_2, b2] = _remote_to_local.insert_or_assign(remote, local); + auto [_3, b3] = _name_to_remote.insert_or_assign(name, remote); + + return b1 & b2 & b3; + } + + std::optional get_local_from_remote(const net_addr_t& remote) + { + Lock_t l{addr_mutex}; + + std::optional ret = std::nullopt; + + if (auto itr = _remote_to_local.find(remote); itr != _remote_to_local.end()) + ret = itr->second; + + return ret; + } + + std::optional get_remote_from_local(const local_addr_t& local) + { + Lock_t l{addr_mutex}; + + std::optional ret = std::nullopt; + + if (auto itr = _local_to_remote.find(local); itr != _local_to_remote.end()) + ret = itr->second; + + return ret; + } + + std::optional get_remote_from_name(const std::string& name) + { + Lock_t l{addr_mutex}; + + std::optional ret = std::nullopt; + + if (auto itr = _name_to_remote.find(name); itr != _local_to_remote.end()) + ret = itr->second; + + return ret; + } + + std::optional get_local_from_name(const std::string& name) + { + Lock_t l{addr_mutex}; + + std::optional ret = std::nullopt; + + if (auto itr = _name_to_remote.find(name); itr != _local_to_remote.end()) + ret = get_local_from_remote(itr->second); + + return ret; + } + + bool has_local(const local_addr_t& local) const + { + Lock_t l{addr_mutex}; + + return _local_to_remote.contains(local); + } + + bool has_remote(const net_addr_t& remote) const + { + Lock_t l{addr_mutex}; + + return _remote_to_local.contains(remote); + } + + void unmap(const net_addr_t& remote) + { + Lock_t l{addr_mutex}; + + auto name = remote.name(); + + if (auto it_a = _remote_to_local.find(remote); it_a != _remote_to_local.end()) + { + if (auto it_b = _local_to_remote.find(it_a->second); it_b != _local_to_remote.end()) + { + if (auto it_c = _name_to_remote.find(name); it_c != _name_to_remote.end()) + { + _name_to_remote.erase(it_c); + } + _local_to_remote.erase(it_b); + } + _remote_to_local.erase(it_a); + } + } + + void unmap(const local_addr_t& local) + { + Lock_t l{addr_mutex}; + + if (auto it_a = _local_to_remote.find(local); it_a != _local_to_remote.end()) + { + if (auto it_b = _remote_to_local.find(it_a->second); it_b != _remote_to_local.end()) + { + if (auto it_c = _name_to_remote.find(it_b->first.name()); it_c != _name_to_remote.end()) + { + _name_to_remote.erase(it_c); + } + _remote_to_local.erase(it_b); + } + _local_to_remote.erase(it_a); + } + } + + // All types satisfying the concept RemoteAddrType have a ::name() overload + void unmap(const std::string& name) + { + Lock_t l{addr_mutex}; + + if (auto it_a = _name_to_remote.find(name); it_a != _name_to_remote.end()) + { + if (auto it_b = _remote_to_local.find(it_a->second); it_b != _remote_to_local.end()) + { + if (auto it_c = _local_to_remote.find(it_b->second); it_c != _local_to_remote.end()) + { + _local_to_remote.erase(it_c); + } + _remote_to_local.erase(it_b); + } + _name_to_remote.erase(it_a); + } + } + + std::optional operator[](const net_addr_t& remote) { return get_local_from_remote(remote); } + + std::optional operator[](const local_addr_t& local) { return get_remote_from_local(local); } + }; +} // namespace llarp diff --git a/llarp/address/types.hpp b/llarp/address/types.hpp new file mode 100644 index 0000000000..3120423559 --- /dev/null +++ b/llarp/address/types.hpp @@ -0,0 +1,162 @@ +#pragma once + +#include + +#include + +namespace llarp +{ + using ipv4 = oxen::quic::ipv4; + using ipv6 = oxen::quic::ipv6; + using ip_v = std::variant; + using ipv4_range = oxen::quic::ipv4_net; + using ipv6_range = oxen::quic::ipv6_net; + using ip_range_v = std::variant; + + namespace concepts + { + template + concept IPType = std::is_same_v || std::is_same_v; + + template + concept IPRangeType = std::is_same_v || std::is_same_v; + } // namespace concepts + + using KeyedAddress = oxen::quic::RemoteAddress; + + inline constexpr uint32_t ipv6_flowlabel_mask = 0b0000'0000'0000'1111'1111'1111'1111'1111; + + inline constexpr size_t ICMP_HEADER_SIZE{8}; + + // Compares the given ip variant against a quic address + // Returns: + // - true : ip == address + // - false : ip != address + // Error: + // - throws : ip and address are mismatched ipv4 vs ipv6 + inline bool ip_equals_address(const ip_v& ip, const oxen::quic::Address& addr, bool compare_v4) + { + if (compare_v4 and std::holds_alternative(ip)) + return std::get(ip) == addr.to_ipv4(); + + if (not compare_v4 and std::holds_alternative(ip)) + return std::get(ip) == addr.to_ipv6(); + + throw std::invalid_argument{ + "Failed to compare ip variant in desired {} scheme!"_format(compare_v4 ? "ipv4" : "ipv6")}; + } + + struct ip_header_le + { + uint8_t header_len : 4; + uint8_t version : 4; + uint8_t service_type; + uint16_t total_len; + uint16_t id; + uint16_t frag_off; + uint8_t ttl; + uint8_t protocol; + uint16_t checksum; + uint32_t src; + uint32_t dest; + }; + + struct ip_header_be + { + uint8_t version : 4; + uint8_t header_len : 4; + uint8_t service_type; + uint16_t total_len; + uint16_t id; + uint16_t frag_off; + uint8_t ttl; + uint8_t protocol; + uint16_t checksum; + uint32_t src; + uint32_t dest; + }; + + using ip_header = std::conditional_t; + + static_assert(sizeof(ip_header) == 20); + + struct ipv6_header_preamble_le + { + unsigned char pad_small : 4; + unsigned char version : 4; + uint8_t pad[3]; + }; + + struct ipv6_header_preamble_be + { + unsigned char version : 4; + unsigned char pad_small : 4; + uint8_t pad[3]; + }; + + using ipv6_header_preamble = + std::conditional_t; + + static_assert(sizeof(ipv6_header_preamble) == 4); + + struct ipv6_header + { + union + { + ipv6_header_preamble preamble; + uint32_t flowlabel; + } preamble; + + uint16_t payload_len; + uint8_t protocol; + uint8_t hoplimit; + in6_addr srcaddr; + in6_addr dstaddr; + + /// Returns the flowlabel (stored in network order) in HOST ORDER + uint32_t set_flowlabel() const { return ntohl(preamble.flowlabel & htonl(ipv6_flowlabel_mask)); } + + /// Sets a flowlabel in network order. Takes in a label in HOST ORDER + void set_flowlabel(uint32_t label) + { + // the ipv6 flow label is the last 20 bits in the first 32 bits of the header + preamble.flowlabel = + (htonl(ipv6_flowlabel_mask) & htonl(label)) | (preamble.flowlabel & htonl(~ipv6_flowlabel_mask)); + } + }; + + static_assert(sizeof(ipv6_header) == 40); + +} // namespace llarp + +namespace std +{ + template <> + struct hash + { + size_t operator()(const llarp::ipv4& obj) const { return hash{}(obj.addr); } + }; + + template <> + struct hash + { + size_t operator()(const llarp::ipv6& obj) const + { + auto h = hash{}(obj.hi); + h ^= hash{}(obj.lo) + oxen::quic::inverse_golden_ratio + (h << 6) + (h >> 2); + return h; + } + }; + + template <> + struct hash + { + size_t operator()(const llarp::ip_v& obj) const + { + if (auto maybe_v4 = std::get_if(&obj)) + return hash{}(*maybe_v4); + + return hash{}(std::get(obj)); + } + }; +} // namespace std diff --git a/llarp/address/utils.cpp b/llarp/address/utils.cpp new file mode 100644 index 0000000000..a0aa2152ed --- /dev/null +++ b/llarp/address/utils.cpp @@ -0,0 +1,148 @@ +#include "utils.hpp" + +namespace llarp +{ + static auto logcat = log::Cat("Address-utils"); + + uint16_t from_32_to_16(uint32_t x) + { + /* add up 16-bit and 16-bit for 16+c bit */ + x = (x & 0xffff) + (x >> 16); + /* add up carry.. */ + x = (x & 0xffff) + (x >> 16); + return x; + } + + uint32_t from_64_to_32(uint64_t x) + { + /* add up 32-bit and 32-bit for 32+c bit */ + x = (x & 0xffffffff) + (x >> 32); + /* add up carry.. */ + x = (x & 0xffffffff) + (x >> 32); + return x; + } + uint16_t fold_csum(uint32_t csum) + { + auto sum = csum; + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + return static_cast(~sum); + } + + uint32_t ipv4_checksum_magic(const uint8_t *buf, uint16_t len) + { + uint16_t odd{1}, result{0}; + + odd &= (unsigned long)buf; + + if (odd) + { + result += oxenc::little_endian ? (*buf << 8) : *buf; + --len; + ++buf; + } + + if (len >= 2) + { + if (2 & (unsigned long)buf) + { + result += *(unsigned short *)buf; + len -= 2; + buf += 2; + } + + if (len >= 4) + { + const unsigned char *end = buf + ((unsigned)len & ~3); + + unsigned int carry = 0; + do + { + unsigned int w = *(unsigned int *)buf; + buf += 4; + result += carry; + result += w; + carry = (w > result); + } while (buf < end); + + result += carry; + result = (result & 0xffff) + (result >> 16); + } + + if (len & 2) + { + result += *(unsigned short *)buf; + buf += 2; + } + } + + if (len & 1) + result += oxenc::little_endian ? *buf : (*buf << 8); + + result = from_32_to_16(result); + + if (odd) + result = ((result >> 8) & 0xff) | ((result & 0xff) << 8); + + return result; + } + + uint16_t checksum_ipv4(const void *header, uint8_t header_len) + { + return ~ipv4_checksum_magic(static_cast(header), header_len * 4); + } + + uint32_t tcpudp_checksum_ipv4(uint32_t src, uint32_t dest, uint32_t len, uint8_t proto, uint32_t sum) + { + auto _sum = static_cast(sum); + + _sum += src; + _sum += dest; + + _sum += oxenc::big_endian ? proto + len : (proto + len) << 8; + + return from_64_to_32(_sum); + } + + uint16_t ipv6_checksum_magic( + const struct in6_addr *saddr, const struct in6_addr *daddr, uint32_t len, uint8_t proto, uint32_t csum) + { + uint32_t sum = csum; + + for (size_t i = 0; i < 4; ++i) + { + auto val = static_cast(saddr->s6_addr32[i]); + sum += val; + sum += (sum < val); + } + + for (size_t i = 0; i < 4; ++i) + { + auto val = static_cast(daddr->s6_addr32[i]); + sum += val; + sum += (sum < val); + } + + uint32_t ulen = htonl(len); + uint32_t uproto = htonl(proto); + + sum += ulen; + sum += (sum < ulen); + + sum += uproto; + sum += (sum < uproto); + + return fold_csum(sum); + } + + uint32_t tcp_checksum_ipv6(const struct in6_addr *saddr, const struct in6_addr *daddr, uint32_t len, uint32_t csum) + { + return ~ipv6_checksum_magic(saddr, daddr, len, IPPROTO_TCP, csum); + } + + uint32_t udp_checksum_ipv6(const struct in6_addr *saddr, const struct in6_addr *daddr, uint32_t len, uint32_t csum) + { + return ~ipv6_checksum_magic(saddr, daddr, len, IPPROTO_UDP, csum); + } + +} // namespace llarp diff --git a/llarp/address/utils.hpp b/llarp/address/utils.hpp new file mode 100644 index 0000000000..14c6fde7b4 --- /dev/null +++ b/llarp/address/utils.hpp @@ -0,0 +1,129 @@ +#pragma once + +#include "types.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace llarp +{ + namespace PREFIX + { + inline constexpr auto EXIT = "exit::"sv; + inline constexpr auto LOKI = "loki::"sv; + inline constexpr auto SNODE = "snode::"sv; + } // namespace PREFIX + + namespace TLD + { + inline constexpr auto SNODE = ".snode"sv; + inline constexpr auto LOKI = ".loki"sv; + + static std::set allowed = {SNODE, LOKI}; + } // namespace TLD + + uint16_t checksum_ipv4(const void *header, uint8_t header_len); + + uint32_t tcpudp_checksum_ipv4(uint32_t src, uint32_t dest, uint32_t len, uint8_t proto, uint32_t sum); + + uint32_t tcp_checksum_ipv6(const struct in6_addr *saddr, const struct in6_addr *daddr, uint32_t len, uint32_t csum); + + uint32_t udp_checksum_ipv6(const struct in6_addr *saddr, const struct in6_addr *daddr, uint32_t len, uint32_t csum); + + namespace detail + { + // inline auto utilcat = log::Cat("addrutils"); + inline static std::optional parse_addr_string(std::string_view arg, std::string_view tld) + { + std::optional ret = std::nullopt; + + if (auto pos = arg.find_first_of('.'); pos != std::string_view::npos) + { + auto _prefix = arg.substr(0, pos); + // check the pubkey prefix is the right length + if (_prefix.length() != PUBKEYSIZE) + return ret; + + // verify the tld is allowed + auto _tld = arg.substr(pos); + + if (_tld == tld and TLD::allowed.count(_tld)) + ret = _prefix; + } + + return ret; + }; + + inline static std::pair parse_addr( + std::string_view addr, std::optional default_port) + { + std::pair result; + + if (auto p = addr.find_last_not_of("0123456789"); + p != std::string_view::npos && p + 2 <= addr.size() && addr[p] == ':') + { + if (!parse_int(addr.substr(p + 1), result.second)) + throw std::invalid_argument{"Invalid address: could not parse port"}; + addr.remove_suffix(addr.size() - p); + } + else if (default_port) + { + // log::critical(utilcat, "Setting default port for addr parse!"); + result.second = *default_port; + } + else + { + throw std::invalid_argument{"Invalid address: no port was specified and there is no default"}; + } + + bool had_sq_brackets = false; + + if (!addr.empty() && addr.front() == '[' && addr.back() == ']') + { + addr.remove_prefix(1); + addr.remove_suffix(1); + had_sq_brackets = true; + } + + if (auto p = addr.find_first_not_of("0123456789."); p != std::string_view::npos) + { + if (auto q = addr.find_first_not_of("0123456789abcdef:."); q != std::string_view::npos) + throw std::invalid_argument{"Invalid address: does not look like IPv4 or IPv6!"}; + else if (!had_sq_brackets) + throw std::invalid_argument{"Invalid address: IPv6 addresses require [...] square brackets"}; + } + + // if (addr.empty()) + // { + // log::critical(utilcat, "addr is empty, tough titties buddy"); // TESTNET: remove this log please + // // addr = "::"; + // } + + result.first = addr; + return result; + } + + inline constexpr size_t num_ipv4_private{272}; + + inline constexpr std::array generate_private_ipv4() + { + std::array ret{}; + + for (size_t n = 16; n < 32; ++n) + ret[n - 16] = ipv4(172, n, 0, 0) / 16; + + for (size_t n = 0; n < 256; ++n) + ret[n + 16] = ipv4(10, n, 0, 0) / 16; + + return ret; + } + } // namespace detail + +} // namespace llarp diff --git a/llarp/android/ifaddrs.c b/llarp/android/ifaddrs.c index 8702feca83..31784383d4 100644 --- a/llarp/android/ifaddrs.c +++ b/llarp/android/ifaddrs.c @@ -24,675 +24,645 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ifaddrs.h" -#include -#include -#include #include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include typedef struct NetlinkList { - struct NetlinkList* m_next; - struct nlmsghdr* m_data; - unsigned int m_size; + struct NetlinkList* m_next; + struct nlmsghdr* m_data; + unsigned int m_size; } NetlinkList; -static int -netlink_socket(void) +static int netlink_socket(void) { - int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (l_socket < 0) - { - return -1; - } - - struct sockaddr_nl l_addr; - memset(&l_addr, 0, sizeof(l_addr)); - l_addr.nl_family = AF_NETLINK; - if (bind(l_socket, (struct sockaddr*)&l_addr, sizeof(l_addr)) < 0) - { - close(l_socket); - return -1; - } + int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (l_socket < 0) + { + return -1; + } + + struct sockaddr_nl l_addr; + memset(&l_addr, 0, sizeof(l_addr)); + l_addr.nl_family = AF_NETLINK; + if (bind(l_socket, (struct sockaddr*)&l_addr, sizeof(l_addr)) < 0) + { + close(l_socket); + return -1; + } - return l_socket; + return l_socket; } -static int -netlink_send(int p_socket, int p_request) +static int netlink_send(int p_socket, int p_request) { - struct - { - struct nlmsghdr m_hdr; - struct rtgenmsg m_msg; - } l_data; - - memset(&l_data, 0, sizeof(l_data)); - - l_data.m_hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); - l_data.m_hdr.nlmsg_type = p_request; - l_data.m_hdr.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; - l_data.m_hdr.nlmsg_pid = 0; - l_data.m_hdr.nlmsg_seq = p_socket; - l_data.m_msg.rtgen_family = AF_UNSPEC; - - struct sockaddr_nl l_addr; - memset(&l_addr, 0, sizeof(l_addr)); - l_addr.nl_family = AF_NETLINK; - return (sendto( - p_socket, - &l_data.m_hdr, - l_data.m_hdr.nlmsg_len, - 0, - (struct sockaddr*)&l_addr, - sizeof(l_addr))); + struct + { + struct nlmsghdr m_hdr; + struct rtgenmsg m_msg; + } l_data; + + memset(&l_data, 0, sizeof(l_data)); + + l_data.m_hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); + l_data.m_hdr.nlmsg_type = p_request; + l_data.m_hdr.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; + l_data.m_hdr.nlmsg_pid = 0; + l_data.m_hdr.nlmsg_seq = p_socket; + l_data.m_msg.rtgen_family = AF_UNSPEC; + + struct sockaddr_nl l_addr; + memset(&l_addr, 0, sizeof(l_addr)); + l_addr.nl_family = AF_NETLINK; + return (sendto(p_socket, &l_data.m_hdr, l_data.m_hdr.nlmsg_len, 0, (struct sockaddr*)&l_addr, sizeof(l_addr))); } -static int -netlink_recv(int p_socket, void* p_buffer, size_t p_len) +static int netlink_recv(int p_socket, void* p_buffer, size_t p_len) { - struct msghdr l_msg; - struct iovec l_iov = {p_buffer, p_len}; - struct sockaddr_nl l_addr; - - for (;;) - { - l_msg.msg_name = (void*)&l_addr; - l_msg.msg_namelen = sizeof(l_addr); - l_msg.msg_iov = &l_iov; - l_msg.msg_iovlen = 1; - l_msg.msg_control = NULL; - l_msg.msg_controllen = 0; - l_msg.msg_flags = 0; - int l_result = recvmsg(p_socket, &l_msg, 0); - - if (l_result < 0) + struct msghdr l_msg; + struct iovec l_iov = {p_buffer, p_len}; + struct sockaddr_nl l_addr; + + for (;;) { - if (errno == EINTR) - { - continue; - } - return -2; - } + l_msg.msg_name = (void*)&l_addr; + l_msg.msg_namelen = sizeof(l_addr); + l_msg.msg_iov = &l_iov; + l_msg.msg_iovlen = 1; + l_msg.msg_control = NULL; + l_msg.msg_controllen = 0; + l_msg.msg_flags = 0; + int l_result = recvmsg(p_socket, &l_msg, 0); + + if (l_result < 0) + { + if (errno == EINTR) + { + continue; + } + return -2; + } - if (l_msg.msg_flags & MSG_TRUNC) - { // buffer was too small - return -1; + if (l_msg.msg_flags & MSG_TRUNC) + { // buffer was too small + return -1; + } + return l_result; } - return l_result; - } } -static struct nlmsghdr* -getNetlinkResponse(int p_socket, int* p_size, int* p_done) +static struct nlmsghdr* getNetlinkResponse(int p_socket, int* p_size, int* p_done) { - size_t l_size = 4096; - void* l_buffer = NULL; - - for (;;) - { - free(l_buffer); - l_buffer = malloc(l_size); - if (l_buffer == NULL) - { - return NULL; - } + size_t l_size = 4096; + void* l_buffer = NULL; - int l_read = netlink_recv(p_socket, l_buffer, l_size); - *p_size = l_read; - if (l_read == -2) - { - free(l_buffer); - return NULL; - } - if (l_read >= 0) + for (;;) { - pid_t l_pid = getpid(); - struct nlmsghdr* l_hdr; - for (l_hdr = (struct nlmsghdr*)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); - l_hdr = (struct nlmsghdr*)NLMSG_NEXT(l_hdr, l_read)) - { - if ((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + free(l_buffer); + l_buffer = malloc(l_size); + if (l_buffer == NULL) { - continue; + return NULL; } - if (l_hdr->nlmsg_type == NLMSG_DONE) + int l_read = netlink_recv(p_socket, l_buffer, l_size); + *p_size = l_read; + if (l_read == -2) { - *p_done = 1; - break; + free(l_buffer); + return NULL; } - - if (l_hdr->nlmsg_type == NLMSG_ERROR) + if (l_read >= 0) { - free(l_buffer); - return NULL; + pid_t l_pid = getpid(); + struct nlmsghdr* l_hdr; + for (l_hdr = (struct nlmsghdr*)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); + l_hdr = (struct nlmsghdr*)NLMSG_NEXT(l_hdr, l_read)) + { + if ((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if (l_hdr->nlmsg_type == NLMSG_DONE) + { + *p_done = 1; + break; + } + + if (l_hdr->nlmsg_type == NLMSG_ERROR) + { + free(l_buffer); + return NULL; + } + } + return l_buffer; } - } - return l_buffer; - } - l_size *= 2; - } + l_size *= 2; + } } -static NetlinkList* -newListItem(struct nlmsghdr* p_data, unsigned int p_size) +static NetlinkList* newListItem(struct nlmsghdr* p_data, unsigned int p_size) { - NetlinkList* l_item = malloc(sizeof(NetlinkList)); - if (l_item == NULL) - { - return NULL; - } + NetlinkList* l_item = malloc(sizeof(NetlinkList)); + if (l_item == NULL) + { + return NULL; + } - l_item->m_next = NULL; - l_item->m_data = p_data; - l_item->m_size = p_size; - return l_item; + l_item->m_next = NULL; + l_item->m_data = p_data; + l_item->m_size = p_size; + return l_item; } -static void -freeResultList(NetlinkList* p_list) +static void freeResultList(NetlinkList* p_list) { - NetlinkList* l_cur; - while (p_list) - { - l_cur = p_list; - p_list = p_list->m_next; - free(l_cur->m_data); - free(l_cur); - } + NetlinkList* l_cur; + while (p_list) + { + l_cur = p_list; + p_list = p_list->m_next; + free(l_cur->m_data); + free(l_cur); + } } -static NetlinkList* -getResultList(int p_socket, int p_request) +static NetlinkList* getResultList(int p_socket, int p_request) { - if (netlink_send(p_socket, p_request) < 0) - { - return NULL; - } - - NetlinkList* l_list = NULL; - NetlinkList* l_end = NULL; - int l_size; - int l_done = 0; - while (!l_done) - { - struct nlmsghdr* l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done); - if (!l_hdr) - { // error - freeResultList(l_list); - return NULL; - } - - NetlinkList* l_item = newListItem(l_hdr, l_size); - if (!l_item) - { - freeResultList(l_list); - return NULL; - } - if (!l_list) + if (netlink_send(p_socket, p_request) < 0) { - l_list = l_item; + return NULL; } - else + + NetlinkList* l_list = NULL; + NetlinkList* l_end = NULL; + int l_size; + int l_done = 0; + while (!l_done) { - l_end->m_next = l_item; + struct nlmsghdr* l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done); + if (!l_hdr) + { // error + freeResultList(l_list); + return NULL; + } + + NetlinkList* l_item = newListItem(l_hdr, l_size); + if (!l_item) + { + freeResultList(l_list); + return NULL; + } + if (!l_list) + { + l_list = l_item; + } + else + { + l_end->m_next = l_item; + } + l_end = l_item; } - l_end = l_item; - } - return l_list; + return l_list; } -static size_t -maxSize(size_t a, size_t b) +static size_t maxSize(size_t a, size_t b) { - return (a > b ? a : b); + return (a > b ? a : b); } -static size_t -calcAddrLen(sa_family_t p_family, int p_dataSize) +static size_t calcAddrLen(sa_family_t p_family, int p_dataSize) { - switch (p_family) - { - case AF_INET: - return sizeof(struct sockaddr_in); - case AF_INET6: - return sizeof(struct sockaddr_in6); - case AF_PACKET: - return maxSize( - sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize); - default: - return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize); - } + switch (p_family) + { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + case AF_PACKET: + return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize); + default: + return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize); + } } -static void -makeSockaddr(sa_family_t p_family, struct sockaddr* p_dest, void* p_data, size_t p_size) +static void makeSockaddr(sa_family_t p_family, struct sockaddr* p_dest, void* p_data, size_t p_size) { - switch (p_family) - { - case AF_INET: - memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size); - break; - case AF_INET6: - memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size); - break; - case AF_PACKET: - memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size); - ((struct sockaddr_ll*)p_dest)->sll_halen = p_size; - break; - default: - memcpy(p_dest->sa_data, p_data, p_size); - break; - } - p_dest->sa_family = p_family; + switch (p_family) + { + case AF_INET: + memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size); + break; + case AF_INET6: + memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size); + break; + case AF_PACKET: + memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size); + ((struct sockaddr_ll*)p_dest)->sll_halen = p_size; + break; + default: + memcpy(p_dest->sa_data, p_data, p_size); + break; + } + p_dest->sa_family = p_family; } -static void -addToEnd(struct ifaddrs** p_resultList, struct ifaddrs* p_entry) +static void addToEnd(struct ifaddrs** p_resultList, struct ifaddrs* p_entry) { - if (!*p_resultList) - { - *p_resultList = p_entry; - } - else - { - struct ifaddrs* l_cur = *p_resultList; - while (l_cur->ifa_next) + if (!*p_resultList) + { + *p_resultList = p_entry; + } + else { - l_cur = l_cur->ifa_next; + struct ifaddrs* l_cur = *p_resultList; + while (l_cur->ifa_next) + { + l_cur = l_cur->ifa_next; + } + l_cur->ifa_next = p_entry; } - l_cur->ifa_next = p_entry; - } } -static int -interpretLink(struct nlmsghdr* p_hdr, struct ifaddrs** p_resultList) +static int interpretLink(struct nlmsghdr* p_hdr, struct ifaddrs** p_resultList) { - struct ifinfomsg* l_info = (struct ifinfomsg*)NLMSG_DATA(p_hdr); - - size_t l_nameSize = 0; - size_t l_addrSize = 0; - size_t l_dataSize = 0; - - size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); - struct rtattr* l_rta; - for (l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) - { - void* l_rtaData = RTA_DATA(l_rta); - (void)l_rtaData; - size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); - switch (l_rta->rta_type) - { - case IFLA_ADDRESS: - case IFLA_BROADCAST: - l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize)); - break; - case IFLA_IFNAME: - l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); - break; - case IFLA_STATS: - l_dataSize += NLMSG_ALIGN(l_rtaSize); - break; - default: - break; - } - } - - struct ifaddrs* l_entry = - malloc(sizeof(struct ifaddrs) + sizeof(int) + l_nameSize + l_addrSize + l_dataSize); - if (l_entry == NULL) - { - return -1; - } - memset(l_entry, 0, sizeof(struct ifaddrs)); - l_entry->ifa_name = ""; - - char* l_index = ((char*)l_entry) + sizeof(struct ifaddrs); - char* l_name = l_index + sizeof(int); - char* l_addr = l_name + l_nameSize; - char* l_data = l_addr + l_addrSize; - - // save the interface index so we can look it up when handling the addresses. - memcpy(l_index, &l_info->ifi_index, sizeof(int)); - - l_entry->ifa_flags = l_info->ifi_flags; - - l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); - for (l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) - { - void* l_rtaData = RTA_DATA(l_rta); - size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); - switch (l_rta->rta_type) + struct ifinfomsg* l_info = (struct ifinfomsg*)NLMSG_DATA(p_hdr); + + size_t l_nameSize = 0; + size_t l_addrSize = 0; + size_t l_dataSize = 0; + + size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); + struct rtattr* l_rta; + for (l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) { - case IFLA_ADDRESS: - case IFLA_BROADCAST: - { - size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize); - makeSockaddr(AF_PACKET, (struct sockaddr*)l_addr, l_rtaData, l_rtaDataSize); - ((struct sockaddr_ll*)l_addr)->sll_ifindex = l_info->ifi_index; - ((struct sockaddr_ll*)l_addr)->sll_hatype = l_info->ifi_type; - if (l_rta->rta_type == IFLA_ADDRESS) + void* l_rtaData = RTA_DATA(l_rta); + (void)l_rtaData; + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch (l_rta->rta_type) { - l_entry->ifa_addr = (struct sockaddr*)l_addr; + case IFLA_ADDRESS: + case IFLA_BROADCAST: + l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize)); + break; + case IFLA_IFNAME: + l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); + break; + case IFLA_STATS: + l_dataSize += NLMSG_ALIGN(l_rtaSize); + break; + default: + break; } - else + } + + struct ifaddrs* l_entry = malloc(sizeof(struct ifaddrs) + sizeof(int) + l_nameSize + l_addrSize + l_dataSize); + if (l_entry == NULL) + { + return -1; + } + memset(l_entry, 0, sizeof(struct ifaddrs)); + l_entry->ifa_name = ""; + + char* l_index = ((char*)l_entry) + sizeof(struct ifaddrs); + char* l_name = l_index + sizeof(int); + char* l_addr = l_name + l_nameSize; + char* l_data = l_addr + l_addrSize; + + // save the interface index so we can look it up when handling the addresses. + memcpy(l_index, &l_info->ifi_index, sizeof(int)); + + l_entry->ifa_flags = l_info->ifi_flags; + + l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); + for (l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void* l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch (l_rta->rta_type) { - l_entry->ifa_broadaddr = (struct sockaddr*)l_addr; + case IFLA_ADDRESS: + case IFLA_BROADCAST: + { + size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize); + makeSockaddr(AF_PACKET, (struct sockaddr*)l_addr, l_rtaData, l_rtaDataSize); + ((struct sockaddr_ll*)l_addr)->sll_ifindex = l_info->ifi_index; + ((struct sockaddr_ll*)l_addr)->sll_hatype = l_info->ifi_type; + if (l_rta->rta_type == IFLA_ADDRESS) + { + l_entry->ifa_addr = (struct sockaddr*)l_addr; + } + else + { + l_entry->ifa_broadaddr = (struct sockaddr*)l_addr; + } + l_addr += NLMSG_ALIGN(l_addrLen); + break; + } + case IFLA_IFNAME: + strncpy(l_name, l_rtaData, l_rtaDataSize); + l_name[l_rtaDataSize] = '\0'; + l_entry->ifa_name = l_name; + break; + case IFLA_STATS: + memcpy(l_data, l_rtaData, l_rtaDataSize); + l_entry->ifa_data = l_data; + break; + default: + break; } - l_addr += NLMSG_ALIGN(l_addrLen); - break; - } - case IFLA_IFNAME: - strncpy(l_name, l_rtaData, l_rtaDataSize); - l_name[l_rtaDataSize] = '\0'; - l_entry->ifa_name = l_name; - break; - case IFLA_STATS: - memcpy(l_data, l_rtaData, l_rtaDataSize); - l_entry->ifa_data = l_data; - break; - default: - break; } - } - addToEnd(p_resultList, l_entry); - return 0; + addToEnd(p_resultList, l_entry); + return 0; } -static struct ifaddrs* -findInterface(int p_index, struct ifaddrs** p_links, int p_numLinks) +static struct ifaddrs* findInterface(int p_index, struct ifaddrs** p_links, int p_numLinks) { - int l_num = 0; - struct ifaddrs* l_cur = *p_links; - while (l_cur && l_num < p_numLinks) - { - char* l_indexPtr = ((char*)l_cur) + sizeof(struct ifaddrs); - int l_index; - memcpy(&l_index, l_indexPtr, sizeof(int)); - if (l_index == p_index) + int l_num = 0; + struct ifaddrs* l_cur = *p_links; + while (l_cur && l_num < p_numLinks) { - return l_cur; - } + char* l_indexPtr = ((char*)l_cur) + sizeof(struct ifaddrs); + int l_index; + memcpy(&l_index, l_indexPtr, sizeof(int)); + if (l_index == p_index) + { + return l_cur; + } - l_cur = l_cur->ifa_next; - ++l_num; - } - return NULL; + l_cur = l_cur->ifa_next; + ++l_num; + } + return NULL; } -static int -interpretAddr(struct nlmsghdr* p_hdr, struct ifaddrs** p_resultList, int p_numLinks) +static int interpretAddr(struct nlmsghdr* p_hdr, struct ifaddrs** p_resultList, int p_numLinks) { - struct ifaddrmsg* l_info = (struct ifaddrmsg*)NLMSG_DATA(p_hdr); - struct ifaddrs* l_interface = findInterface(l_info->ifa_index, p_resultList, p_numLinks); - - if (l_info->ifa_family == AF_PACKET) - { - return 0; - } + struct ifaddrmsg* l_info = (struct ifaddrmsg*)NLMSG_DATA(p_hdr); + struct ifaddrs* l_interface = findInterface(l_info->ifa_index, p_resultList, p_numLinks); - size_t l_nameSize = 0; - size_t l_addrSize = 0; + if (l_info->ifa_family == AF_PACKET) + { + return 0; + } - int l_addedNetmask = 0; + size_t l_nameSize = 0; + size_t l_addrSize = 0; - size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); - struct rtattr* l_rta; - for (l_rta = IFA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) - { - void* l_rtaData = RTA_DATA(l_rta); - (void)l_rtaData; - size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + int l_addedNetmask = 0; - switch (l_rta->rta_type) + size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); + struct rtattr* l_rta; + for (l_rta = IFA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) { - case IFA_ADDRESS: - case IFA_LOCAL: - if ((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask) - { // make room for netmask - l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); - l_addedNetmask = 1; + void* l_rtaData = RTA_DATA(l_rta); + (void)l_rtaData; + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + + switch (l_rta->rta_type) + { + case IFA_ADDRESS: + case IFA_LOCAL: + if ((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask) + { // make room for netmask + l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); + l_addedNetmask = 1; + } + case IFA_BROADCAST: + l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); + break; + case IFA_LABEL: + l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); + break; + default: + break; } - case IFA_BROADCAST: - l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); - break; - case IFA_LABEL: - l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); - break; - default: - break; } - } - - struct ifaddrs* l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize); - if (l_entry == NULL) - { - return -1; - } - memset(l_entry, 0, sizeof(struct ifaddrs)); - l_entry->ifa_name = (l_interface ? l_interface->ifa_name : ""); - - char* l_name = ((char*)l_entry) + sizeof(struct ifaddrs); - char* l_addr = l_name + l_nameSize; - - l_entry->ifa_flags = l_info->ifa_flags; - if (l_interface) - { - l_entry->ifa_flags |= l_interface->ifa_flags; - } - - l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); - for (l_rta = IFA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) - { - void* l_rtaData = RTA_DATA(l_rta); - size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); - switch (l_rta->rta_type) + + struct ifaddrs* l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize); + if (l_entry == NULL) + { + return -1; + } + memset(l_entry, 0, sizeof(struct ifaddrs)); + l_entry->ifa_name = (l_interface ? l_interface->ifa_name : ""); + + char* l_name = ((char*)l_entry) + sizeof(struct ifaddrs); + char* l_addr = l_name + l_nameSize; + + l_entry->ifa_flags = l_info->ifa_flags; + if (l_interface) + { + l_entry->ifa_flags |= l_interface->ifa_flags; + } + + l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); + for (l_rta = IFA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) { - case IFA_ADDRESS: - case IFA_BROADCAST: - case IFA_LOCAL: - { - size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize); - makeSockaddr(l_info->ifa_family, (struct sockaddr*)l_addr, l_rtaData, l_rtaDataSize); - if (l_info->ifa_family == AF_INET6) + void* l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch (l_rta->rta_type) { - if (IN6_IS_ADDR_LINKLOCAL((struct in6_addr*)l_rtaData) - || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr*)l_rtaData)) - { - ((struct sockaddr_in6*)l_addr)->sin6_scope_id = l_info->ifa_index; - } + case IFA_ADDRESS: + case IFA_BROADCAST: + case IFA_LOCAL: + { + size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize); + makeSockaddr(l_info->ifa_family, (struct sockaddr*)l_addr, l_rtaData, l_rtaDataSize); + if (l_info->ifa_family == AF_INET6) + { + if (IN6_IS_ADDR_LINKLOCAL((struct in6_addr*)l_rtaData) + || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr*)l_rtaData)) + { + ((struct sockaddr_in6*)l_addr)->sin6_scope_id = l_info->ifa_index; + } + } + + if (l_rta->rta_type == IFA_ADDRESS) + { // apparently in a point-to-point network IFA_ADDRESS contains the + // dest address and IFA_LOCAL contains the local address + if (l_entry->ifa_addr) + { + l_entry->ifa_dstaddr = (struct sockaddr*)l_addr; + } + else + { + l_entry->ifa_addr = (struct sockaddr*)l_addr; + } + } + else if (l_rta->rta_type == IFA_LOCAL) + { + if (l_entry->ifa_addr) + { + l_entry->ifa_dstaddr = l_entry->ifa_addr; + } + l_entry->ifa_addr = (struct sockaddr*)l_addr; + } + else + { + l_entry->ifa_broadaddr = (struct sockaddr*)l_addr; + } + l_addr += NLMSG_ALIGN(l_addrLen); + break; + } + case IFA_LABEL: + strncpy(l_name, l_rtaData, l_rtaDataSize); + l_name[l_rtaDataSize] = '\0'; + l_entry->ifa_name = l_name; + break; + default: + break; } + } - if (l_rta->rta_type == IFA_ADDRESS) - { // apparently in a point-to-point network IFA_ADDRESS contains the - // dest address and IFA_LOCAL contains the local address - if (l_entry->ifa_addr) - { - l_entry->ifa_dstaddr = (struct sockaddr*)l_addr; - } - else - { - l_entry->ifa_addr = (struct sockaddr*)l_addr; - } - } - else if (l_rta->rta_type == IFA_LOCAL) + if (l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6)) + { + unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128); + unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen); + char l_mask[16] = {0}; + unsigned i; + for (i = 0; i < (l_prefix / 8); ++i) { - if (l_entry->ifa_addr) - { - l_entry->ifa_dstaddr = l_entry->ifa_addr; - } - l_entry->ifa_addr = (struct sockaddr*)l_addr; + l_mask[i] = 0xff; } - else + if (l_prefix % 8) { - l_entry->ifa_broadaddr = (struct sockaddr*)l_addr; + l_mask[i] = 0xff << (8 - (l_prefix % 8)); } - l_addr += NLMSG_ALIGN(l_addrLen); - break; - } - case IFA_LABEL: - strncpy(l_name, l_rtaData, l_rtaDataSize); - l_name[l_rtaDataSize] = '\0'; - l_entry->ifa_name = l_name; - break; - default: - break; - } - } - - if (l_entry->ifa_addr - && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6)) - { - unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128); - unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen); - char l_mask[16] = {0}; - unsigned i; - for (i = 0; i < (l_prefix / 8); ++i) - { - l_mask[i] = 0xff; - } - if (l_prefix % 8) - { - l_mask[i] = 0xff << (8 - (l_prefix % 8)); - } - makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr*)l_addr, l_mask, l_maxPrefix / 8); - l_entry->ifa_netmask = (struct sockaddr*)l_addr; - } + makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr*)l_addr, l_mask, l_maxPrefix / 8); + l_entry->ifa_netmask = (struct sockaddr*)l_addr; + } - addToEnd(p_resultList, l_entry); - return 0; + addToEnd(p_resultList, l_entry); + return 0; } -static int -interpretLinks(int p_socket, NetlinkList* p_netlinkList, struct ifaddrs** p_resultList) +static int interpretLinks(int p_socket, NetlinkList* p_netlinkList, struct ifaddrs** p_resultList) { - int l_numLinks = 0; - pid_t l_pid = getpid(); - for (; p_netlinkList; p_netlinkList = p_netlinkList->m_next) - { - unsigned int l_nlsize = p_netlinkList->m_size; - struct nlmsghdr* l_hdr; - for (l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); - l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) + int l_numLinks = 0; + pid_t l_pid = getpid(); + for (; p_netlinkList; p_netlinkList = p_netlinkList->m_next) { - if ((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) - { - continue; - } - - if (l_hdr->nlmsg_type == NLMSG_DONE) - { - break; - } - - if (l_hdr->nlmsg_type == RTM_NEWLINK) - { - if (interpretLink(l_hdr, p_resultList) == -1) + unsigned int l_nlsize = p_netlinkList->m_size; + struct nlmsghdr* l_hdr; + for (l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) { - return -1; + if ((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if (l_hdr->nlmsg_type == NLMSG_DONE) + { + break; + } + + if (l_hdr->nlmsg_type == RTM_NEWLINK) + { + if (interpretLink(l_hdr, p_resultList) == -1) + { + return -1; + } + ++l_numLinks; + } } - ++l_numLinks; - } } - } - return l_numLinks; + return l_numLinks; } -static int -interpretAddrs( - int p_socket, NetlinkList* p_netlinkList, struct ifaddrs** p_resultList, int p_numLinks) +static int interpretAddrs(int p_socket, NetlinkList* p_netlinkList, struct ifaddrs** p_resultList, int p_numLinks) { - pid_t l_pid = getpid(); - for (; p_netlinkList; p_netlinkList = p_netlinkList->m_next) - { - unsigned int l_nlsize = p_netlinkList->m_size; - struct nlmsghdr* l_hdr; - for (l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); - l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) + pid_t l_pid = getpid(); + for (; p_netlinkList; p_netlinkList = p_netlinkList->m_next) { - if ((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) - { - continue; - } - - if (l_hdr->nlmsg_type == NLMSG_DONE) - { - break; - } - - if (l_hdr->nlmsg_type == RTM_NEWADDR) - { - if (interpretAddr(l_hdr, p_resultList, p_numLinks) == -1) + unsigned int l_nlsize = p_netlinkList->m_size; + struct nlmsghdr* l_hdr; + for (l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) { - return -1; + if ((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if (l_hdr->nlmsg_type == NLMSG_DONE) + { + break; + } + + if (l_hdr->nlmsg_type == RTM_NEWADDR) + { + if (interpretAddr(l_hdr, p_resultList, p_numLinks) == -1) + { + return -1; + } + } } - } } - } - return 0; + return 0; } -int -getifaddrs(struct ifaddrs** ifap) +int getifaddrs(struct ifaddrs** ifap) { - if (!ifap) - { - return -1; - } - *ifap = NULL; - - int l_socket = netlink_socket(); - if (l_socket < 0) - { - return -1; - } - - NetlinkList* l_linkResults = getResultList(l_socket, RTM_GETLINK); - if (!l_linkResults) - { - close(l_socket); - return -1; - } + if (!ifap) + { + return -1; + } + *ifap = NULL; + + int l_socket = netlink_socket(); + if (l_socket < 0) + { + return -1; + } + + NetlinkList* l_linkResults = getResultList(l_socket, RTM_GETLINK); + if (!l_linkResults) + { + close(l_socket); + return -1; + } + + NetlinkList* l_addrResults = getResultList(l_socket, RTM_GETADDR); + if (!l_addrResults) + { + close(l_socket); + freeResultList(l_linkResults); + return -1; + } + + int l_result = 0; + int l_numLinks = interpretLinks(l_socket, l_linkResults, ifap); + if (l_numLinks == -1 || interpretAddrs(l_socket, l_addrResults, ifap, l_numLinks) == -1) + { + l_result = -1; + } - NetlinkList* l_addrResults = getResultList(l_socket, RTM_GETADDR); - if (!l_addrResults) - { - close(l_socket); freeResultList(l_linkResults); - return -1; - } - - int l_result = 0; - int l_numLinks = interpretLinks(l_socket, l_linkResults, ifap); - if (l_numLinks == -1 || interpretAddrs(l_socket, l_addrResults, ifap, l_numLinks) == -1) - { - l_result = -1; - } - - freeResultList(l_linkResults); - freeResultList(l_addrResults); - close(l_socket); - return l_result; + freeResultList(l_addrResults); + close(l_socket); + return l_result; } -void -freeifaddrs(struct ifaddrs* ifa) +void freeifaddrs(struct ifaddrs* ifa) { - struct ifaddrs* l_cur; - while (ifa) - { - l_cur = ifa; - ifa = ifa->ifa_next; - free(l_cur); - } + struct ifaddrs* l_cur; + while (ifa) + { + l_cur = ifa; + ifa = ifa->ifa_next; + free(l_cur); + } } diff --git a/llarp/android/ifaddrs.h b/llarp/android/ifaddrs.h index 73d3d2877e..89ed7b425c 100644 --- a/llarp/android/ifaddrs.h +++ b/llarp/android/ifaddrs.h @@ -23,18 +23,17 @@ * BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp */ -#ifndef _IFADDRS_H_ -#define _IFADDRS_H_ +#pragma once struct ifaddrs { - struct ifaddrs* ifa_next; - char* ifa_name; - unsigned int ifa_flags; - struct sockaddr* ifa_addr; - struct sockaddr* ifa_netmask; - struct sockaddr* ifa_dstaddr; - void* ifa_data; + struct ifaddrs* ifa_next; + char* ifa_name; + unsigned int ifa_flags; + struct sockaddr* ifa_addr; + struct sockaddr* ifa_netmask; + struct sockaddr* ifa_dstaddr; + void* ifa_data; }; /* @@ -48,10 +47,6 @@ struct ifaddrs #include __BEGIN_DECLS -extern int -getifaddrs(struct ifaddrs** ifap); -extern void -freeifaddrs(struct ifaddrs* ifa); +extern int getifaddrs(struct ifaddrs** ifap); +extern void freeifaddrs(struct ifaddrs* ifa); __END_DECLS - -#endif diff --git a/llarp/apple/CMakeLists.txt b/llarp/apple/CMakeLists.txt index 8dd561ef75..3a96efefb9 100644 --- a/llarp/apple/CMakeLists.txt +++ b/llarp/apple/CMakeLists.txt @@ -11,7 +11,7 @@ find_library(FOUNDATION Foundation REQUIRED) find_library(NETEXT NetworkExtension REQUIRED) find_library(COREFOUNDATION CoreFoundation REQUIRED) -target_link_libraries(lokinet-util PUBLIC ${FOUNDATION}) +target_link_libraries(lokinet-base INTERFACE ${FOUNDATION}) target_sources(lokinet-platform PRIVATE vpn_platform.cpp vpn_interface.cpp route_manager.cpp context_wrapper.cpp) @@ -27,7 +27,7 @@ enable_lto(lokinet-extension) target_compile_options(lokinet-extension PRIVATE -fobjc-arc) if(MACOS_SYSTEM_EXTENSION) target_compile_definitions(lokinet-extension PRIVATE MACOS_SYSTEM_EXTENSION) - target_compile_definitions(lokinet-util PUBLIC MACOS_SYSTEM_EXTENSION) + target_compile_definitions(lokinet-base INTERFACE MACOS_SYSTEM_EXTENSION) else() target_link_options(lokinet-extension PRIVATE -e _NSExtensionMain) endif() @@ -41,7 +41,7 @@ else() endif() target_link_libraries(lokinet-extension PRIVATE - lokinet-amalgum + lokinet-core ${COREFOUNDATION} ${NETEXT}) diff --git a/llarp/apple/DNSTrampoline.h b/llarp/apple/DNSTrampoline.h index 117d567b21..7464f1aee1 100644 --- a/llarp/apple/DNSTrampoline.h +++ b/llarp/apple/DNSTrampoline.h @@ -1,6 +1,6 @@ #pragma once -#include #include +#include extern NSString* error_domain; @@ -22,23 +22,24 @@ extern NSString* error_domain; */ @interface LLARPDNSTrampoline : NSObject { - // The socket libunbound talks with: - uv_udp_t request_socket; - // The reply address. This is a bit hacky: we configure libunbound to just use single address - // (rather than a range) so that we don't have to worry about tracking different reply addresses. - @public - struct sockaddr reply_addr; - // UDP "session" aimed at the upstream DNS - @public - NWUDPSession* upstream; - // Apple docs say writes could take time *and* the crappy Apple datagram write methods aren't - // callable again until the previous write finishes. Deal with this garbage API by queuing - // everything than using a uv_async to process the queue. - @public - int write_ready; - @public - NSMutableArray* pending_writes; - uv_async_t write_trigger; + // The socket libunbound talks with: + uv_udp_t request_socket; + // The reply address. This is a bit hacky: we configure libunbound to just use single address + // (rather than a range) so that we don't have to worry about tracking different reply + // addresses. + @public + struct sockaddr reply_addr; + // UDP "session" aimed at the upstream DNS + @public + NWUDPSession* upstream; + // Apple docs say writes could take time *and* the crappy Apple datagram write methods aren't + // callable again until the previous write finishes. Deal with this garbage API by queuing + // everything than using a uv_async to process the queue. + @public + int write_ready; + @public + NSMutableArray* pending_writes; + uv_async_t write_trigger; } - (void)startWithUpstreamDns:(NWUDPSession*)dns listenIp:(NSString*)listenIp diff --git a/llarp/apple/DNSTrampoline.m b/llarp/apple/DNSTrampoline.m index 9ef950c4e0..a32912da3d 100644 --- a/llarp/apple/DNSTrampoline.m +++ b/llarp/apple/DNSTrampoline.m @@ -1,84 +1,77 @@ #include "DNSTrampoline.h" + #include NSString* error_domain = @"org.lokinet"; // Receiving an incoming packet, presumably from libunbound. NB: this is called from the libuv // event loop. -static void -on_request( - uv_udp_t* socket, - ssize_t nread, - const uv_buf_t* buf, - const struct sockaddr* addr, - unsigned flags) +static void on_request( + uv_udp_t* socket, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) { - (void)flags; - if (nread < 0) - { - NSLog(@"Read error: %s", uv_strerror(nread)); - free(buf->base); - return; - } - - if (nread == 0 || !addr) - { - if (buf) - free(buf->base); - return; - } - - LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)socket->data; - - // We configure libunbound to use just one single port so we'll just send replies to the last port - // to talk to us. (And we're only listening on localhost in the first place). - t->reply_addr = *addr; - - // NSData takes care of calling free(buf->base) for us with this constructor: - [t->pending_writes addObject:[NSData dataWithBytesNoCopy:buf->base length:nread]]; - - [t flushWrites]; + (void)flags; + if (nread < 0) + { + NSLog(@"Read error: %s", uv_strerror(nread)); + free(buf->base); + return; + } + + if (nread == 0 || !addr) + { + if (buf) + free(buf->base); + return; + } + + LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)socket->data; + + // We configure libunbound to use just one single port so we'll just send replies to the last + // port to talk to us. (And we're only listening on localhost in the first place). + t->reply_addr = *addr; + + // NSData takes care of calling free(buf->base) for us with this constructor: + [t->pending_writes addObject:[NSData dataWithBytesNoCopy:buf->base length:nread]]; + + [t flushWrites]; } -static void -on_sent(uv_udp_send_t* req, int status) +static void on_sent(uv_udp_send_t* req, int status) { - (void)status; - NSArray* datagrams = (__bridge_transfer NSArray*)req->data; - (void)datagrams; - free(req); + (void)status; + NSArray* datagrams = (__bridge_transfer NSArray*)req->data; + (void)datagrams; + free(req); } // NB: called from the libuv event loop (so we don't have to worry about the above and this one // running at once from different threads). -static void -write_flusher(uv_async_t* async) +static void write_flusher(uv_async_t* async) { - LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)async->data; - if (t->pending_writes.count == 0) - return; - - NSArray* data = [NSArray arrayWithArray:t->pending_writes]; - [t->pending_writes removeAllObjects]; - __weak LLARPDNSTrampoline* weakSelf = t; - [t->upstream writeMultipleDatagrams:data - completionHandler:^(NSError* error) { - if (error) - NSLog(@"Failed to send request to upstream DNS: %@", error); - - // Trigger another flush in case anything built up while Apple was doing its - // things. Just call it unconditionally (rather than checking the queue) - // because this handler is probably running in some other thread. - [weakSelf flushWrites]; - }]; + LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)async->data; + if (t->pending_writes.count == 0) + return; + + NSArray* data = [NSArray arrayWithArray:t->pending_writes]; + [t->pending_writes removeAllObjects]; + __weak LLARPDNSTrampoline* weakSelf = t; + [t->upstream writeMultipleDatagrams:data + completionHandler:^(NSError* error) { + if (error) + NSLog(@"Failed to send request to upstream DNS: %@", error); + + // Trigger another flush in case anything built up while Apple was doing its + // things. Just call it unconditionally (rather than checking the queue) + // because this handler is probably running in some other thread. + [weakSelf flushWrites]; + }]; } -static void -alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) +static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { - (void)handle; - buf->base = malloc(suggested_size); - buf->len = suggested_size; + (void)handle; + buf->base = malloc(suggested_size); + buf->len = suggested_size; } @implementation LLARPDNSTrampoline @@ -89,78 +82,72 @@ - (void)startWithUpstreamDns:(NWUDPSession*)dns uvLoop:(uv_loop_t*)loop completionHandler:(void (^)(NSError* error))completionHandler { - NSLog(@"Setting up trampoline"); - pending_writes = [[NSMutableArray alloc] init]; - write_trigger.data = (__bridge void*)self; - uv_async_init(loop, &write_trigger, write_flusher); - - request_socket.data = (__bridge void*)self; - uv_udp_init(loop, &request_socket); - struct sockaddr_in recv_addr; - uv_ip4_addr(listenIp.UTF8String, listenPort, &recv_addr); - int ret = uv_udp_bind(&request_socket, (const struct sockaddr*)&recv_addr, UV_UDP_REUSEADDR); - if (ret < 0) - { - NSString* errstr = - [NSString stringWithFormat:@"Failed to start DNS trampoline: %s", uv_strerror(ret)]; - NSError* err = [NSError errorWithDomain:error_domain code:ret userInfo:@{@"Error": errstr}]; - NSLog(@"%@", err); - return completionHandler(err); - } - uv_udp_recv_start(&request_socket, alloc_buffer, on_request); - - NSLog(@"Starting DNS trampoline"); - - upstream = dns; - __weak LLARPDNSTrampoline* weakSelf = self; - [upstream - setReadHandler:^(NSArray* datagrams, NSError* error) { - // Reading a reply back from the UDP socket used to talk to upstream - if (error) - { - NSLog(@"Reader handler failed: %@", error); - return; - } - LLARPDNSTrampoline* strongSelf = weakSelf; - if (!strongSelf || datagrams.count == 0) - return; - - uv_buf_t* buffers = malloc(datagrams.count * sizeof(uv_buf_t)); - size_t buf_count = 0; - for (NSData* packet in datagrams) - { - buffers[buf_count].base = (void*)packet.bytes; - buffers[buf_count].len = packet.length; - buf_count++; + NSLog(@"Setting up trampoline"); + pending_writes = [[NSMutableArray alloc] init]; + write_trigger.data = (__bridge void*)self; + uv_async_init(loop, &write_trigger, write_flusher); + + request_socket.data = (__bridge void*)self; + uv_udp_init(loop, &request_socket); + struct sockaddr_in recv_addr; + uv_ip4_addr(listenIp.UTF8String, listenPort, &recv_addr); + int ret = uv_udp_bind(&request_socket, (const struct sockaddr*)&recv_addr, UV_UDP_REUSEADDR); + if (ret < 0) + { + NSString* errstr = [NSString stringWithFormat:@"Failed to start DNS trampoline: %s", uv_strerror(ret)]; + NSError* err = [NSError errorWithDomain:error_domain code:ret userInfo:@{@"Error": errstr}]; + NSLog(@"%@", err); + return completionHandler(err); + } + uv_udp_recv_start(&request_socket, alloc_buffer, on_request); + + NSLog(@"Starting DNS trampoline"); + + upstream = dns; + __weak LLARPDNSTrampoline* weakSelf = self; + [upstream + setReadHandler:^(NSArray* datagrams, NSError* error) { + // Reading a reply back from the UDP socket used to talk to upstream + if (error) + { + NSLog(@"Reader handler failed: %@", error); + return; + } + LLARPDNSTrampoline* strongSelf = weakSelf; + if (!strongSelf || datagrams.count == 0) + return; + + uv_buf_t* buffers = malloc(datagrams.count * sizeof(uv_buf_t)); + size_t buf_count = 0; + for (NSData* packet in datagrams) + { + buffers[buf_count].base = (void*)packet.bytes; + buffers[buf_count].len = packet.length; + buf_count++; + } + uv_udp_send_t* uvsend = malloc(sizeof(uv_udp_send_t)); + uvsend->data = (__bridge_retained void*)datagrams; + int ret = + uv_udp_send(uvsend, &strongSelf->request_socket, buffers, buf_count, &strongSelf->reply_addr, on_sent); + free(buffers); + if (ret < 0) + NSLog(@"Error returning DNS responses to unbound: %s", uv_strerror(ret)); } - uv_udp_send_t* uvsend = malloc(sizeof(uv_udp_send_t)); - uvsend->data = (__bridge_retained void*)datagrams; - int ret = uv_udp_send( - uvsend, - &strongSelf->request_socket, - buffers, - buf_count, - &strongSelf->reply_addr, - on_sent); - free(buffers); - if (ret < 0) - NSLog(@"Error returning DNS responses to unbound: %s", uv_strerror(ret)); - } - maxDatagrams:NSUIntegerMax]; - - completionHandler(nil); + maxDatagrams:NSUIntegerMax]; + + completionHandler(nil); } - (void)flushWrites { - uv_async_send(&write_trigger); + uv_async_send(&write_trigger); } - (void)dealloc { - NSLog(@"Stopping DNS trampoline"); - uv_close((uv_handle_t*)&request_socket, NULL); - uv_close((uv_handle_t*)&write_trigger, NULL); + NSLog(@"Stopping DNS trampoline"); + uv_close((uv_handle_t*)&request_socket, NULL); + uv_close((uv_handle_t*)&write_trigger, NULL); } @end diff --git a/llarp/apple/PacketTunnelProvider.m b/llarp/apple/PacketTunnelProvider.m index dfcf203117..629f980143 100644 --- a/llarp/apple/PacketTunnelProvider.m +++ b/llarp/apple/PacketTunnelProvider.m @@ -1,31 +1,30 @@ -#include -#include #include "context_wrapper.h" #include "DNSTrampoline.h" +#include +#include + #define LLARP_APPLE_PACKET_BUF_SIZE 64 @interface LLARPPacketTunnel : NEPacketTunnelProvider { - void* lokinet; - llarp_incoming_packet packet_buf[LLARP_APPLE_PACKET_BUF_SIZE]; - @public - NEPacketTunnelNetworkSettings* settings; - @public - NEIPv4Route* tun_route4; - @public - NEIPv6Route* tun_route6; - LLARPDNSTrampoline* dns_tramp; + void* lokinet; + llarp_incoming_packet packet_buf[LLARP_APPLE_PACKET_BUF_SIZE]; + @public + NEPacketTunnelNetworkSettings* settings; + @public + NEIPv4Route* tun_route4; + @public + NEIPv6Route* tun_route6; + LLARPDNSTrampoline* dns_tramp; } - (void)startTunnelWithOptions:(NSDictionary*)options completionHandler:(void (^)(NSError* error))completionHandler; -- (void)stopTunnelWithReason:(NEProviderStopReason)reason - completionHandler:(void (^)(void))completionHandler; +- (void)stopTunnelWithReason:(NEProviderStopReason)reason completionHandler:(void (^)(void))completionHandler; -- (void)handleAppMessage:(NSData*)messageData - completionHandler:(void (^)(NSData* responseData))completionHandler; +- (void)handleAppMessage:(NSData*)messageData completionHandler:(void (^)(NSData* responseData))completionHandler; - (void)readPackets; @@ -33,368 +32,344 @@ - (void)updateNetworkSettings; @end -static void -nslogger(const char* msg) +static void nslogger(const char* msg) { - NSLog(@"%s", msg); + NSLog(@"%s", msg); } -static void -packet_writer(int af, const void* data, size_t size, void* ctx) +static void packet_writer(int af, const void* data, size_t size, void* ctx) { - if (ctx == nil || data == nil) - return; + if (ctx == nil || data == nil) + return; - NSData* buf = [NSData dataWithBytesNoCopy:(void*)data length:size freeWhenDone:NO]; - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; - [t.packetFlow writePackets:@[buf] withProtocols:@[[NSNumber numberWithInt:af]]]; + NSData* buf = [NSData dataWithBytesNoCopy:(void*)data length:size freeWhenDone:NO]; + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; + [t.packetFlow writePackets:@[buf] withProtocols:@[[NSNumber numberWithInt:af]]]; } -static void -start_packet_reader(void* ctx) +static void start_packet_reader(void* ctx) { - if (ctx == nil) - return; + if (ctx == nil) + return; - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; - [t readPackets]; + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; + [t readPackets]; } -static void -add_ipv4_route(const char* addr, const char* netmask, void* ctx) +static void add_ipv4_route(const char* addr, const char* netmask, void* ctx) { - NSLog(@"Adding IPv4 route %s:%s to packet tunnel", addr, netmask); - NEIPv4Route* route = - [[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] - subnetMask:[NSString stringWithUTF8String:netmask]]; + NSLog(@"Adding IPv4 route %s:%s to packet tunnel", addr, netmask); + NEIPv4Route* route = [[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] + subnetMask:[NSString stringWithUTF8String:netmask]]; - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; - for (NEIPv4Route* r in t->settings.IPv4Settings.includedRoutes) - if ([r.destinationAddress isEqualToString:route.destinationAddress] && - [r.destinationSubnetMask isEqualToString:route.destinationSubnetMask]) - return; // Already in the settings, nothing to add. + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; + for (NEIPv4Route* r in t->settings.IPv4Settings.includedRoutes) + if ([r.destinationAddress isEqualToString:route.destinationAddress] && + [r.destinationSubnetMask isEqualToString:route.destinationSubnetMask]) + return; // Already in the settings, nothing to add. - t->settings.IPv4Settings.includedRoutes = - [t->settings.IPv4Settings.includedRoutes arrayByAddingObject:route]; + t->settings.IPv4Settings.includedRoutes = [t->settings.IPv4Settings.includedRoutes arrayByAddingObject:route]; - [t updateNetworkSettings]; + [t updateNetworkSettings]; } -static void -del_ipv4_route(const char* addr, const char* netmask, void* ctx) +static void del_ipv4_route(const char* addr, const char* netmask, void* ctx) { - NSLog(@"Removing IPv4 route %s:%s to packet tunnel", addr, netmask); - NEIPv4Route* route = - [[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] - subnetMask:[NSString stringWithUTF8String:netmask]]; - - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; - NSMutableArray* routes = - [NSMutableArray arrayWithArray:t->settings.IPv4Settings.includedRoutes]; - for (size_t i = 0; i < routes.count; i++) - { - if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] && - [routes[i].destinationSubnetMask isEqualToString:route.destinationSubnetMask]) + NSLog(@"Removing IPv4 route %s:%s to packet tunnel", addr, netmask); + NEIPv4Route* route = [[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] + subnetMask:[NSString stringWithUTF8String:netmask]]; + + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; + NSMutableArray* routes = [NSMutableArray arrayWithArray:t->settings.IPv4Settings.includedRoutes]; + for (size_t i = 0; i < routes.count; i++) { - [routes removeObjectAtIndex:i]; - i--; + if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] && + [routes[i].destinationSubnetMask isEqualToString:route.destinationSubnetMask]) + { + [routes removeObjectAtIndex:i]; + i--; + } } - } - if (routes.count != t->settings.IPv4Settings.includedRoutes.count) - { - t->settings.IPv4Settings.includedRoutes = routes; - [t updateNetworkSettings]; - } + if (routes.count != t->settings.IPv4Settings.includedRoutes.count) + { + t->settings.IPv4Settings.includedRoutes = routes; + [t updateNetworkSettings]; + } } -static void -add_ipv6_route(const char* addr, int prefix, void* ctx) +static void add_ipv6_route(const char* addr, int prefix, void* ctx) { - NEIPv6Route* route = - [[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] - networkPrefixLength:[NSNumber numberWithInt:prefix]]; + NEIPv6Route* route = [[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] + networkPrefixLength:[NSNumber numberWithInt:prefix]]; - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; - for (NEIPv6Route* r in t->settings.IPv6Settings.includedRoutes) - if ([r.destinationAddress isEqualToString:route.destinationAddress] && - [r.destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength]) - return; // Already in the settings, nothing to add. + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; + for (NEIPv6Route* r in t->settings.IPv6Settings.includedRoutes) + if ([r.destinationAddress isEqualToString:route.destinationAddress] && + [r.destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength]) + return; // Already in the settings, nothing to add. - t->settings.IPv6Settings.includedRoutes = - [t->settings.IPv6Settings.includedRoutes arrayByAddingObject:route]; + t->settings.IPv6Settings.includedRoutes = [t->settings.IPv6Settings.includedRoutes arrayByAddingObject:route]; - [t updateNetworkSettings]; + [t updateNetworkSettings]; } -static void -del_ipv6_route(const char* addr, int prefix, void* ctx) +static void del_ipv6_route(const char* addr, int prefix, void* ctx) { - NEIPv6Route* route = - [[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] - networkPrefixLength:[NSNumber numberWithInt:prefix]]; - - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; - NSMutableArray* routes = - [NSMutableArray arrayWithArray:t->settings.IPv6Settings.includedRoutes]; - for (size_t i = 0; i < routes.count; i++) - { - if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] && - [routes[i].destinationNetworkPrefixLength - isEqualToNumber:route.destinationNetworkPrefixLength]) + NEIPv6Route* route = [[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] + networkPrefixLength:[NSNumber numberWithInt:prefix]]; + + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; + NSMutableArray* routes = [NSMutableArray arrayWithArray:t->settings.IPv6Settings.includedRoutes]; + for (size_t i = 0; i < routes.count; i++) { - [routes removeObjectAtIndex:i]; - i--; + if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] && + [routes[i].destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength]) + { + [routes removeObjectAtIndex:i]; + i--; + } } - } - if (routes.count != t->settings.IPv6Settings.includedRoutes.count) - { - t->settings.IPv6Settings.includedRoutes = routes; - [t updateNetworkSettings]; - } + if (routes.count != t->settings.IPv6Settings.includedRoutes.count) + { + t->settings.IPv6Settings.includedRoutes = routes; + [t updateNetworkSettings]; + } } -static void -add_default_route(void* ctx) +static void add_default_route(void* ctx) { - NSLog(@"Making the tunnel the default route"); - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; + NSLog(@"Making the tunnel the default route"); + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; - t->settings.IPv4Settings.includedRoutes = @[NEIPv4Route.defaultRoute]; - t->settings.IPv6Settings.includedRoutes = @[NEIPv6Route.defaultRoute]; + t->settings.IPv4Settings.includedRoutes = @[NEIPv4Route.defaultRoute]; + t->settings.IPv6Settings.includedRoutes = @[NEIPv6Route.defaultRoute]; - [t updateNetworkSettings]; + [t updateNetworkSettings]; } -static void -del_default_route(void* ctx) +static void del_default_route(void* ctx) { - NSLog(@"Removing default route from tunnel"); - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; + NSLog(@"Removing default route from tunnel"); + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; - t->settings.IPv4Settings.includedRoutes = @[t->tun_route4]; - t->settings.IPv6Settings.includedRoutes = @[t->tun_route6]; + t->settings.IPv4Settings.includedRoutes = @[t->tun_route4]; + t->settings.IPv6Settings.includedRoutes = @[t->tun_route6]; - [t updateNetworkSettings]; + [t updateNetworkSettings]; } @implementation LLARPPacketTunnel - (void)readPackets { - [self.packetFlow readPacketObjectsWithCompletionHandler:^(NSArray* packets) { - if (lokinet == nil) - return; + [self.packetFlow readPacketObjectsWithCompletionHandler:^(NSArray* packets) { + if (lokinet == nil) + return; - size_t size = 0; - for (NEPacket* p in packets) - { - packet_buf[size].bytes = p.data.bytes; - packet_buf[size].size = p.data.length; - size++; - if (size >= LLARP_APPLE_PACKET_BUF_SIZE) + size_t size = 0; + for (NEPacket* p in packets) { - llarp_apple_incoming(lokinet, packet_buf, size); - size = 0; + packet_buf[size].bytes = p.data.bytes; + packet_buf[size].size = p.data.length; + size++; + if (size >= LLARP_APPLE_PACKET_BUF_SIZE) + { + llarp_apple_incoming(lokinet, packet_buf, size); + size = 0; + } } - } - if (size > 0) - llarp_apple_incoming(lokinet, packet_buf, size); + if (size > 0) + llarp_apple_incoming(lokinet, packet_buf, size); - [self readPackets]; - }]; + [self readPackets]; + }]; } - (void)startTunnelWithOptions:(NSDictionary*)options completionHandler:(void (^)(NSError*))completionHandler { - NSString* default_bootstrap = [NSBundle.mainBundle pathForResource:@"bootstrap" ofType:@"signed"]; - NSString* home = NSHomeDirectory(); - - llarp_apple_config conf = { - .config_dir = home.UTF8String, - .default_bootstrap = default_bootstrap.UTF8String, - .ns_logger = nslogger, - .packet_writer = packet_writer, - .start_reading = start_packet_reader, - .route_callbacks = - {.add_ipv4_route = add_ipv4_route, - .del_ipv4_route = del_ipv4_route, - .add_ipv6_route = add_ipv6_route, - .del_ipv6_route = del_ipv6_route, - .add_default_route = add_default_route, - .del_default_route = del_default_route}, - }; - - lokinet = llarp_apple_init(&conf); - if (!lokinet) - { - NSError* init_failure = [NSError errorWithDomain:error_domain - code:500 - userInfo:@{@"Error": @"Failed to initialize lokinet"}]; - NSLog(@"%@", [init_failure localizedDescription]); - return completionHandler(init_failure); - } - - NSString* ip = [NSString stringWithUTF8String:conf.tunnel_ipv4_ip]; - NSString* mask = [NSString stringWithUTF8String:conf.tunnel_ipv4_netmask]; - - // We don't have a fixed address so just stick some bogus value here: - settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.3.2.1"]; + NSString* default_bootstrap = [NSBundle.mainBundle pathForResource:@"bootstrap" ofType:@"signed"]; + NSString* home = NSHomeDirectory(); + + llarp_apple_config conf = { + .config_dir = home.UTF8String, + .default_bootstrap = default_bootstrap.UTF8String, + .ns_logger = nslogger, + .packet_writer = packet_writer, + .start_reading = start_packet_reader, + .route_callbacks = + {.add_ipv4_route = add_ipv4_route, + .del_ipv4_route = del_ipv4_route, + .add_ipv6_route = add_ipv6_route, + .del_ipv6_route = del_ipv6_route, + .add_default_route = add_default_route, + .del_default_route = del_default_route}, + }; + + lokinet = llarp_apple_init(&conf); + if (!lokinet) + { + NSError* init_failure = [NSError errorWithDomain:error_domain + code:500 + userInfo:@{@"Error": @"Failed to initialize lokinet"}]; + NSLog(@"%@", [init_failure localizedDescription]); + return completionHandler(init_failure); + } + + NSString* ip = [NSString stringWithUTF8String:conf.tunnel_ipv4_ip]; + NSString* mask = [NSString stringWithUTF8String:conf.tunnel_ipv4_netmask]; + + // We don't have a fixed address so just stick some bogus value here: + settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.3.2.1"]; #ifdef MACOS_SYSTEM_EXTENSION - NSString* dns_ip = [NSString stringWithUTF8String:conf.dns_bind_ip]; + NSString* dns_ip = [NSString stringWithUTF8String:conf.dns_bind_ip]; #else - // TODO: placeholder - NSString* dns_ip = ip; + // TODO: placeholder + NSString* dns_ip = ip; #endif - NSLog(@"setting dns to %@", dns_ip); - NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[dns_ip]]; - dns.domainName = @"localhost.loki"; - dns.matchDomains = @[@""]; - // In theory, matchDomains is supposed to be set to DNS suffixes that we resolve. This seems - // highly unreliable, though: often it just doesn't work at all (perhaps only if we make ourselves - // the default route?), and even when it does work, it seems there are secret reasons that some - // domains (such as instagram.com) still won't work because there's some magic sauce in the OS - // that Apple engineers don't want to disclose ("This is what I expected, actually. Although I - // will not comment on what I believe is happening here", from - // https://developer.apple.com/forums/thread/685410). - // - // So the documentation sucks and the feature doesn't appear to work, so as much as it would be - // nice to capture only .loki and .snode when not in exit mode, we can't, so capture everything - // and use our default upstream. - dns.matchDomains = @[@""]; - dns.matchDomainsNoSearch = true; - dns.searchDomains = @[]; - settings.DNSSettings = dns; - - NWHostEndpoint* upstreamdns_ep; - if (strlen(conf.upstream_dns)) - upstreamdns_ep = - [NWHostEndpoint endpointWithHostname:[NSString stringWithUTF8String:conf.upstream_dns] - port:@(conf.upstream_dns_port).stringValue]; - - NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[ip] subnetMasks:@[mask]]; - tun_route4 = [[NEIPv4Route alloc] initWithDestinationAddress:ip subnetMask:mask]; - ipv4.includedRoutes = @[tun_route4]; - settings.IPv4Settings = ipv4; - - NSString* ip6 = [NSString stringWithUTF8String:conf.tunnel_ipv6_ip]; - NSNumber* ip6_prefix = [NSNumber numberWithUnsignedInt:conf.tunnel_ipv6_prefix]; - NEIPv6Settings* ipv6 = [[NEIPv6Settings alloc] initWithAddresses:@[ip6] - networkPrefixLengths:@[ip6_prefix]]; - tun_route6 = [[NEIPv6Route alloc] initWithDestinationAddress:ip6 networkPrefixLength:ip6_prefix]; - ipv6.includedRoutes = @[tun_route6]; - settings.IPv6Settings = ipv6; - - __weak LLARPPacketTunnel* weakSelf = self; - [self setTunnelNetworkSettings:settings - completionHandler:^(NSError* err) { - if (err) - { - NSLog(@"Failed to configure lokinet tunnel: %@", err); - return completionHandler(err); - } - LLARPPacketTunnel* strongSelf = weakSelf; - if (!strongSelf) - return completionHandler(nil); - - int start_ret = llarp_apple_start(strongSelf->lokinet, (__bridge void*)strongSelf); - if (start_ret != 0) - { - NSError* start_failure = - [NSError errorWithDomain:error_domain - code:start_ret - userInfo:@{@"Error": @"Failed to start lokinet"}]; - NSLog(@"%@", start_failure); - lokinet = nil; - return completionHandler(start_failure); - } - - NSString* dns_tramp_ip = @"127.0.0.1"; - NSLog( - @"Starting DNS exit mode trampoline to %@ on %@:%d", - upstreamdns_ep, - dns_tramp_ip, - dns_trampoline_port); - NWUDPSession* upstreamdns = - [strongSelf createUDPSessionThroughTunnelToEndpoint:upstreamdns_ep - fromEndpoint:nil]; - strongSelf->dns_tramp = [LLARPDNSTrampoline alloc]; - [strongSelf->dns_tramp - startWithUpstreamDns:upstreamdns - listenIp:dns_tramp_ip - listenPort:dns_trampoline_port - uvLoop:llarp_apple_get_uv_loop(strongSelf->lokinet) - completionHandler:^(NSError* error) { - if (error) - NSLog(@"Error starting dns trampoline: %@", error); - return completionHandler(error); - }]; - }]; + NSLog(@"setting dns to %@", dns_ip); + NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[dns_ip]]; + dns.domainName = @"localhost.loki"; + dns.matchDomains = @[@""]; + // In theory, matchDomains is supposed to be set to DNS suffixes that we resolve. This seems + // highly unreliable, though: often it just doesn't work at all (perhaps only if we make + // ourselves the default route?), and even when it does work, it seems there are secret reasons + // that some domains (such as instagram.com) still won't work because there's some magic sauce + // in the OS that Apple engineers don't want to disclose ("This is what I expected, actually. + // Although I will not comment on what I believe is happening here", from + // https://developer.apple.com/forums/thread/685410). + // + // So the documentation sucks and the feature doesn't appear to work, so as much as it would be + // nice to capture only .loki and .snode when not in exit mode, we can't, so capture everything + // and use our default upstream. + dns.matchDomains = @[@""]; + dns.matchDomainsNoSearch = true; + dns.searchDomains = @[]; + settings.DNSSettings = dns; + + NWHostEndpoint* upstreamdns_ep; + if (strlen(conf.upstream_dns)) + upstreamdns_ep = [NWHostEndpoint endpointWithHostname:[NSString stringWithUTF8String:conf.upstream_dns] + port:@(conf.upstream_dns_port).stringValue]; + + NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[ip] subnetMasks:@[mask]]; + tun_route4 = [[NEIPv4Route alloc] initWithDestinationAddress:ip subnetMask:mask]; + ipv4.includedRoutes = @[tun_route4]; + settings.IPv4Settings = ipv4; + + NSString* ip6 = [NSString stringWithUTF8String:conf.tunnel_ipv6_ip]; + NSNumber* ip6_prefix = [NSNumber numberWithUnsignedInt:conf.tunnel_ipv6_prefix]; + NEIPv6Settings* ipv6 = [[NEIPv6Settings alloc] initWithAddresses:@[ip6] networkPrefixLengths:@[ip6_prefix]]; + tun_route6 = [[NEIPv6Route alloc] initWithDestinationAddress:ip6 networkPrefixLength:ip6_prefix]; + ipv6.includedRoutes = @[tun_route6]; + settings.IPv6Settings = ipv6; + + __weak LLARPPacketTunnel* weakSelf = self; + [self setTunnelNetworkSettings:settings + completionHandler:^(NSError* err) { + if (err) + { + NSLog(@"Failed to configure lokinet tunnel: %@", err); + return completionHandler(err); + } + LLARPPacketTunnel* strongSelf = weakSelf; + if (!strongSelf) + return completionHandler(nil); + + int start_ret = llarp_apple_start(strongSelf->lokinet, (__bridge void*)strongSelf); + if (start_ret != 0) + { + NSError* start_failure = [NSError errorWithDomain:error_domain + code:start_ret + userInfo:@{@"Error": @"Failed to start lokinet"}]; + NSLog(@"%@", start_failure); + lokinet = nil; + return completionHandler(start_failure); + } + + NSString* dns_tramp_ip = @"127.0.0.1"; + NSLog( + @"Starting DNS exit mode trampoline to %@ on %@:%d", + upstreamdns_ep, + dns_tramp_ip, + dns_trampoline_port); + NWUDPSession* upstreamdns = [strongSelf createUDPSessionThroughTunnelToEndpoint:upstreamdns_ep + fromEndpoint:nil]; + strongSelf->dns_tramp = [LLARPDNSTrampoline alloc]; + [strongSelf->dns_tramp startWithUpstreamDns:upstreamdns + listenIp:dns_tramp_ip + listenPort:dns_trampoline_port + uvLoop:llarp_apple_get_uv_loop(strongSelf->lokinet) + completionHandler:^(NSError* error) { + if (error) + NSLog(@"Error starting dns trampoline: %@", error); + return completionHandler(error); + }]; + }]; } -- (void)stopTunnelWithReason:(NEProviderStopReason)reason - completionHandler:(void (^)(void))completionHandler +- (void)stopTunnelWithReason:(NEProviderStopReason)reason completionHandler:(void (^)(void))completionHandler { - if (lokinet) - { - llarp_apple_shutdown(lokinet); - lokinet = nil; - } - completionHandler(); + if (lokinet) + { + llarp_apple_shutdown(lokinet); + lokinet = nil; + } + completionHandler(); } -- (void)handleAppMessage:(NSData*)messageData - completionHandler:(void (^)(NSData* responseData))completionHandler +- (void)handleAppMessage:(NSData*)messageData completionHandler:(void (^)(NSData* responseData))completionHandler { - NSData* response = [NSData dataWithBytesNoCopy:"ok" length:3 freeWhenDone:NO]; - completionHandler(response); + NSData* response = [NSData dataWithBytesNoCopy:"ok" length:3 freeWhenDone:NO]; + completionHandler(response); } - (void)updateNetworkSettings { - self.reasserting = YES; - __weak LLARPPacketTunnel* weakSelf = self; - // Apple documentation says that setting network settings to nil isn't required before setting it - // to a new value. Apple lies: both end up with a routing table that looks exactly the same (from - // both `netstat -rn` and from everything that happens in `route -n monitor`), but if we don't - // call with nil first then everything fails to route to either lokinet *and* clearnet through the - // exit, so there is apparently some special magic internal Apple state that actually *does* - // require the tunnel settings being reset with nil first. - // - // Thanks for the accurate documentation, Apple. - // - [self setTunnelNetworkSettings:nil - completionHandler:^(NSError* err) { - if (err) - NSLog(@"Failed to clear lokinet tunnel settings: %@", err); - LLARPPacketTunnel* strongSelf = weakSelf; - if (strongSelf) - { - [weakSelf - setTunnelNetworkSettings:strongSelf->settings - completionHandler:^(NSError* err) { - LLARPPacketTunnel* strongSelf = weakSelf; - if (strongSelf) - strongSelf.reasserting = NO; - if (err) - NSLog(@"Failed to reconfigure lokinet tunnel settings: %@", err); - }]; - } - }]; + self.reasserting = YES; + __weak LLARPPacketTunnel* weakSelf = self; + // Apple documentation says that setting network settings to nil isn't required before setting + // it to a new value. Apple lies: both end up with a routing table that looks exactly the same + // (from both `netstat -rn` and from everything that happens in `route -n monitor`), but if we + // don't call with nil first then everything fails to route to either lokinet *and* clearnet + // through the exit, so there is apparently some special magic internal Apple state that + // actually *does* require the tunnel settings being reset with nil first. + // + // Thanks for the accurate documentation, Apple. + // + [self setTunnelNetworkSettings:nil + completionHandler:^(NSError* err) { + if (err) + NSLog(@"Failed to clear lokinet tunnel settings: %@", err); + LLARPPacketTunnel* strongSelf = weakSelf; + if (strongSelf) + { + [weakSelf setTunnelNetworkSettings:strongSelf->settings + completionHandler:^(NSError* err) { + LLARPPacketTunnel* strongSelf = weakSelf; + if (strongSelf) + strongSelf.reasserting = NO; + if (err) + NSLog( + @"Failed to reconfigure lokinet tunnel settings: " + @"%@", + err); + }]; + } + }]; } @end #ifdef MACOS_SYSTEM_EXTENSION -int -main() +int main() { - [NEProvider startSystemExtensionMode]; - dispatch_main(); + [NEProvider startSystemExtensionMode]; + dispatch_main(); } #endif diff --git a/llarp/apple/context.hpp b/llarp/apple/context.hpp index 4fc808874b..f44f69da2d 100644 --- a/llarp/apple/context.hpp +++ b/llarp/apple/context.hpp @@ -1,27 +1,27 @@ #pragma once -#include -#include "vpn_platform.hpp" #include "route_manager.hpp" +#include "vpn_platform.hpp" + +#include namespace llarp::apple { - struct Context : public llarp::Context - { - std::shared_ptr - makeVPNPlatform() override + struct Context : public llarp::Context { - return std::make_shared( - *this, m_PacketWriter, m_OnReadable, route_callbacks, callback_context); - } + std::shared_ptr make_vpn_platform() override + { + return std::make_shared( + *this, m_PacketWriter, m_OnReadable, route_callbacks, callback_context); + } - // Callbacks that must be set for packet handling *before* calling Setup/Configure/Run; the main - // point of these is to get passed through to VPNInterface, which will be called during setup, - // after construction. - VPNInterface::packet_write_callback m_PacketWriter; - VPNInterface::on_readable_callback m_OnReadable; - llarp_route_callbacks route_callbacks{}; - void* callback_context = nullptr; - }; + // Callbacks that must be set for packet handling *before* calling Setup/Configure/Run; the + // main point of these is to get passed through to VPNInterface, which will be called during + // setup, after construction. + VPNInterface::packet_write_callback m_PacketWriter; + VPNInterface::on_readable_callback m_OnReadable; + llarp_route_callbacks route_callbacks{}; + void* callback_context = nullptr; + }; } // namespace llarp::apple diff --git a/llarp/apple/context_wrapper.cpp b/llarp/apple/context_wrapper.cpp index e30c6c5d62..5c5f846640 100644 --- a/llarp/apple/context_wrapper.cpp +++ b/llarp/apple/context_wrapper.cpp @@ -1,206 +1,201 @@ -#include -#include -#include -#include +#include "context_wrapper.h" + +#include "context.hpp" +#include "vpn_interface.hpp" + +#include #include #include -#include -#include #include #include #include -#include "vpn_interface.hpp" -#include "context_wrapper.h" -#include "context.hpp" + +// #include + +#include +#include +#include namespace { - struct instance_data - { - llarp::apple::Context context; - std::thread runner; - packet_writer_callback packet_writer; - start_reading_callback start_reading; + static auto logcat = oxen::log::Cat("apple.ctx_wrapper"); + + struct instance_data + { + llarp::apple::Context context; + std::thread runner; + packet_writer_callback packet_writer; + start_reading_callback start_reading; - std::weak_ptr iface; - }; + std::weak_ptr iface; + }; } // namespace // Expose this with C linkage so that objective-c can use it extern "C" const uint16_t dns_trampoline_port = llarp::apple::dns_trampoline_port; -void* -llarp_apple_init(llarp_apple_config* appleconf) +void* llarp_apple_init(llarp_apple_config* appleconf) { - llarp::log::clear_sinks(); - llarp::log::add_sink(std::make_shared( - [](const char* msg, void* nslog) { reinterpret_cast(nslog)(msg); }, - nullptr, - reinterpret_cast(appleconf->ns_logger))); - llarp::logRingBuffer = std::make_shared(100); - llarp::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO); - - try - { - auto config_dir = fs::u8path(appleconf->config_dir); - auto config = std::make_shared(config_dir); - fs::path config_path = config_dir / "lokinet.ini"; - if (!fs::exists(config_path)) - llarp::ensureConfig(config_dir, config_path, /*overwrite=*/false, /*asRouter=*/false); - config->Load(config_path); - - // If no range is specified then go look for a free one, set that in the config, and then return - // it to the caller via the char* parameters. - auto& range = config->network.m_ifaddr; - if (!range.addr.h) - { - if (auto maybe = llarp::net::Platform::Default_ptr()->FindFreeRange()) - range = *maybe; - else - throw std::runtime_error{"Could not find any free IP range"}; - } - auto addr = llarp::net::TruncateV6(range.addr).ToString(); - auto mask = llarp::net::TruncateV6(range.netmask_bits).ToString(); - if (addr.size() > 15 || mask.size() > 15) - throw std::runtime_error{"Unexpected non-IPv4 tunnel range configured"}; - std::strncpy(appleconf->tunnel_ipv4_ip, addr.c_str(), sizeof(appleconf->tunnel_ipv4_ip)); - std::strncpy( - appleconf->tunnel_ipv4_netmask, mask.c_str(), sizeof(appleconf->tunnel_ipv4_netmask)); - - // TODO: in the future we want to do this properly with our pubkey (see issue #1705), but that's - // going to take a bit more work because we currently can't *get* the (usually) ephemeral pubkey - // at this stage of lokinet configuration. So for now we just stick our IPv4 address into it - // until #1705 gets implemented. - llarp::huint128_t ipv6{ - llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(range.addr).h}}; - std::strncpy( - appleconf->tunnel_ipv6_ip, ipv6.ToString().c_str(), sizeof(appleconf->tunnel_ipv6_ip)); - appleconf->tunnel_ipv6_prefix = 48; - - appleconf->upstream_dns[0] = '\0'; - for (auto& upstream : config->dns.m_upstreamDNS) + llarp::log::clear_sinks(); + llarp::log::add_sink(std::make_shared( + [](const char* msg, void* nslog) { reinterpret_cast(nslog)(msg); }, + nullptr, + reinterpret_cast(appleconf->ns_logger))); + llarp::logRingBuffer = std::make_shared(100); + llarp::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO); + + try { - if (upstream.isIPv4()) - { - std::strcpy(appleconf->upstream_dns, upstream.hostString().c_str()); - appleconf->upstream_dns_port = upstream.getPort(); - break; - } - } + auto config_dir = fs::u8path(appleconf->config_dir); + auto config = std::make_shared(config_dir); + fs::path config_path = config_dir / "lokinet.ini"; + if (!fs::exists(config_path)) + llarp::ensure_config(config_dir, config_path, /*overwrite=*/false, /*asRouter=*/false); + config->load(config_path); + + // If no range is specified then go look for a free one, set that in the config, and then + // return it to the caller via the char* parameters. + auto& range = config->network.if_addr; + if (!range.addr.h) + { + if (auto maybe = llarp::net::Platform::Default_ptr()->find_free_range()) + range = *maybe; + else + throw std::runtime_error{"Could not find any free IP range"}; + } + auto addr = llarp::net::TruncateV6(range.addr).to_string(); + auto mask = llarp::net::TruncateV6(range.netmask_bits).to_string(); + if (addr.size() > 15 || mask.size() > 15) + throw std::runtime_error{"Unexpected non-IPv4 tunnel range configured"}; + std::strncpy(appleconf->tunnel_ipv4_ip, addr.c_str(), sizeof(appleconf->tunnel_ipv4_ip)); + std::strncpy(appleconf->tunnel_ipv4_netmask, mask.c_str(), sizeof(appleconf->tunnel_ipv4_netmask)); + + // TODO: in the future we want to do this properly with our pubkey (see issue #1705), but + // that's going to take a bit more work because we currently can't *get* the (usually) + // ephemeral pubkey at this stage of lokinet configuration. So for now we just stick our + // IPv4 address into it until #1705 gets implemented. + llarp::huint128_t ipv6{llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(range.addr).h}}; + std::strncpy(appleconf->tunnel_ipv6_ip, ipv6.to_string().c_str(), sizeof(appleconf->tunnel_ipv6_ip)); + appleconf->tunnel_ipv6_prefix = 48; + + appleconf->upstream_dns[0] = '\0'; + for (auto& upstream : config->dns.upstream_dns) + { + if (upstream.isIPv4()) + { + std::strcpy(appleconf->upstream_dns, upstream.hostString().c_str()); + appleconf->upstream_dns_port = upstream.getPort(); + break; + } + } #ifdef MACOS_SYSTEM_EXTENSION - std::strncpy( - appleconf->dns_bind_ip, - config->dns.m_bind.front().hostString().c_str(), - sizeof(appleconf->dns_bind_ip)); + std::strncpy( + appleconf->dns_bind_ip, config->dns.m_bind.front().hostString().c_str(), sizeof(appleconf->dns_bind_ip)); #endif - // If no explicit bootstrap then set the system default one included with the app bundle - if (config->bootstrap.files.empty()) - config->bootstrap.files.push_back(fs::u8path(appleconf->default_bootstrap)); + // If no explicit bootstrap then set the system default one included with the app bundle + if (config->bootstrap.files.empty()) + config->bootstrap.files.push_back(fs::u8path(appleconf->default_bootstrap)); - auto inst = std::make_unique(); - inst->context.Configure(std::move(config)); - inst->context.route_callbacks = appleconf->route_callbacks; + auto inst = std::make_unique(); + inst->context.Configure(std::move(config)); + inst->context.route_callbacks = appleconf->route_callbacks; - inst->packet_writer = appleconf->packet_writer; - inst->start_reading = appleconf->start_reading; + inst->packet_writer = appleconf->packet_writer; + inst->start_reading = appleconf->start_reading; - return inst.release(); - } - catch (const std::exception& e) - { - llarp::LogError("Failed to initialize lokinet from config: ", e.what()); - } - return nullptr; + return inst.release(); + } + catch (const std::exception& e) + { + oxen::log::error(logcat, "Failed to initialize lokinet from config: {}", e.what()); + } + return nullptr; } -int -llarp_apple_start(void* lokinet, void* callback_context) +int llarp_apple_start(void* lokinet, void* callback_context) { - auto* inst = static_cast(lokinet); - - inst->context.callback_context = callback_context; + auto* inst = static_cast(lokinet); + + inst->context.callback_context = callback_context; + + inst->context.m_PacketWriter = [inst, callback_context](int af_family, void* data, size_t size) { + inst->packet_writer(af_family, data, size, callback_context); + return true; + }; + + inst->context.m_OnReadable = [inst, callback_context](llarp::apple::VPNInterface& iface) { + inst->iface = iface.weak_from_this(); + inst->start_reading(callback_context); + }; + + std::promise result; + inst->runner = std::thread{[inst, &result] { + const llarp::RuntimeOptions opts{}; + try + { + inst->context.Setup(opts); + } + catch (...) + { + result.set_exception(std::current_exception()); + return; + } + result.set_value(); + inst->context.Run(opts); + }}; - inst->context.m_PacketWriter = [inst, callback_context](int af_family, void* data, size_t size) { - inst->packet_writer(af_family, data, size, callback_context); - return true; - }; - - inst->context.m_OnReadable = [inst, callback_context](llarp::apple::VPNInterface& iface) { - inst->iface = iface.weak_from_this(); - inst->start_reading(callback_context); - }; - - std::promise result; - inst->runner = std::thread{[inst, &result] { - const llarp::RuntimeOptions opts{}; try { - inst->context.Setup(opts); + result.get_future().get(); } - catch (...) + catch (const std::exception& e) { - result.set_exception(std::current_exception()); - return; + oxen::log::error(logcat, "Failed to initialize lokinet: {}", e.what()); + return -1; } - result.set_value(); - inst->context.Run(opts); - }}; - - try - { - result.get_future().get(); - } - catch (const std::exception& e) - { - llarp::LogError("Failed to initialize lokinet: ", e.what()); - return -1; - } - - return 0; + + return 0; } -uv_loop_t* -llarp_apple_get_uv_loop(void* lokinet) +uv_loop_t* llarp_apple_get_uv_loop(void* lokinet) { - auto& inst = *static_cast(lokinet); - auto uvw = inst.context.loop->MaybeGetUVWLoop(); - assert(uvw); - return uvw->raw(); + auto& inst = *static_cast(lokinet); + auto uvw = inst.context.loop->MaybeGetUVWLoop(); + assert(uvw); + return uvw->raw(); } -int -llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size) +int llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size) { - auto& inst = *static_cast(lokinet); - - auto iface = inst.iface.lock(); - if (!iface) - return -1; - - int count = 0; - for (size_t i = 0; i < size; i++) - { - llarp_buffer_t buf{static_cast(packets[i].bytes), packets[i].size}; - if (iface->OfferReadPacket(buf)) - count++; - else - llarp::LogError("invalid IP packet: ", llarp::buffer_printer(buf)); - } - - iface->MaybeWakeUpperLayers(); - return count; + auto& inst = *static_cast(lokinet); + + auto iface = inst.iface.lock(); + if (!iface) + return -1; + + int count = 0; + for (size_t i = 0; i < size; i++) + { + llarp_buffer_t buf{static_cast(packets[i].bytes), packets[i].size}; + if (iface->OfferReadPacket(buf)) + count++; + else + oxen::log::error(logcat, "invalid IP packet: {}", llarp::buffer_printer(buf)); + } + + iface->MaybeWakeUpperLayers(); + return count; } -void -llarp_apple_shutdown(void* lokinet) +void llarp_apple_shutdown(void* lokinet) { - auto* inst = static_cast(lokinet); + auto* inst = static_cast(lokinet); - inst->context.CloseAsync(); - inst->context.Wait(); - inst->runner.join(); - delete inst; + inst->context.CloseAsync(); + inst->context.Wait(); + inst->runner.join(); + delete inst; } diff --git a/llarp/apple/context_wrapper.h b/llarp/apple/context_wrapper.h index 1f09a46d35..ebee1f5993 100644 --- a/llarp/apple/context_wrapper.h +++ b/llarp/apple/context_wrapper.h @@ -8,161 +8,157 @@ extern "C" { #endif -#include #include +#include #include - // Port (on localhost) for our DNS trampoline for bouncing DNS requests through the exit route - // when in exit mode. - extern const uint16_t dns_trampoline_port; - - /// C callback function for us to invoke when we need to write a packet - typedef void (*packet_writer_callback)(int af, const void* data, size_t size, void* ctx); - - /// C callback function to invoke once we are ready to start receiving packets - typedef void (*start_reading_callback)(void* ctx); - - /// C callback that bridges things into NSLog - typedef void (*ns_logger_callback)(const char* msg); - - /// C callbacks to add/remove specific and default routes to the tunnel - typedef void (*llarp_route_ipv4_callback)(const char* addr, const char* netmask, void* ctx); - typedef void (*llarp_route_ipv6_callback)(const char* addr, int prefix, void* ctx); - typedef void (*llarp_default_route_callback)(void* ctx); - typedef struct llarp_route_callbacks - { - /// Callback invoked to set up an IPv4 range that should be routed through the tunnel - /// interface. Called with the address and netmask. - llarp_route_ipv4_callback add_ipv4_route; - - /// Callback invoked to set the tunnel as the default IPv4 route. - llarp_default_route_callback add_ipv4_default_route; - - /// Callback invoked to remove a specific range from the tunnel IPv4 routes. Called with the - /// address and netmask. - llarp_route_ipv4_callback del_ipv4_route; - - /// Callback invoked to set up an IPv6 range that should be routed through the tunnel - /// interface. Called with the address and netmask. - llarp_route_ipv6_callback add_ipv6_route; - - /// Callback invoked to remove a specific range from the tunnel IPv6 routes. Called with the - /// address and netmask. - llarp_route_ipv6_callback del_ipv6_route; - - /// Callback invoked to set the tunnel as the default IPv4/IPv6 route. - llarp_default_route_callback add_default_route; - - /// Callback invoked to remove the tunnel as the default IPv4/IPv6 route. - llarp_default_route_callback del_default_route; - } llarp_route_callbacks; - - /// Pack of crap to be passed into llarp_apple_init to initialize - typedef struct llarp_apple_config - { - /// lokinet configuration directory, expected to be the application-specific "home" directory, - /// which is where state files are stored and the lokinet.ini will be loaded (or created if it - /// doesn't exist). - const char* config_dir; - /// path to the default bootstrap.signed file included in installation, which will be used by - /// default when no specific bootstrap is in the config file. - const char* default_bootstrap; - /// llarp_apple_init writes the IP address for the primary tunnel IP address here, - /// null-terminated. - char tunnel_ipv4_ip[INET_ADDRSTRLEN]; - /// llarp_apple_init writes the netmask of the tunnel address here, null-terminated. - char tunnel_ipv4_netmask[INET_ADDRSTRLEN]; - /// Writes the IPv6 address for the tunnel here, null-terminated. - char tunnel_ipv6_ip[INET6_ADDRSTRLEN]; - /// IPv6 address prefix. - uint16_t tunnel_ipv6_prefix; - - /// The first upstream DNS server's IPv4 address the OS should use when in exit mode. - /// (Currently on mac in exit mode we only support querying the first such configured server). - char upstream_dns[INET_ADDRSTRLEN]; - uint16_t upstream_dns_port; + // Port (on localhost) for our DNS trampoline for bouncing DNS requests through the exit route + // when in exit mode. + extern const uint16_t dns_trampoline_port; + + /// C callback function for us to invoke when we need to write a packet + typedef void (*packet_writer_callback)(int af, const void* data, size_t size, void* ctx); + + /// C callback function to invoke once we are ready to start receiving packets + typedef void (*start_reading_callback)(void* ctx); + + /// C callback that bridges things into NSLog + typedef void (*ns_logger_callback)(const char* msg); + + /// C callbacks to add/remove specific and default routes to the tunnel + typedef void (*llarp_route_ipv4_callback)(const char* addr, const char* netmask, void* ctx); + typedef void (*llarp_route_ipv6_callback)(const char* addr, int prefix, void* ctx); + typedef void (*llarp_default_route_callback)(void* ctx); + typedef struct llarp_route_callbacks + { + /// Callback invoked to set up an IPv4 range that should be routed through the tunnel + /// interface. Called with the address and netmask. + llarp_route_ipv4_callback add_ipv4_route; + + /// Callback invoked to set the tunnel as the default IPv4 route. + llarp_default_route_callback add_ipv4_default_route; + + /// Callback invoked to remove a specific range from the tunnel IPv4 routes. Called with + /// the address and netmask. + llarp_route_ipv4_callback del_ipv4_route; + + /// Callback invoked to set up an IPv6 range that should be routed through the tunnel + /// interface. Called with the address and netmask. + llarp_route_ipv6_callback add_ipv6_route; + + /// Callback invoked to remove a specific range from the tunnel IPv6 routes. Called with + /// the address and netmask. + llarp_route_ipv6_callback del_ipv6_route; + + /// Callback invoked to set the tunnel as the default IPv4/IPv6 route. + llarp_default_route_callback add_default_route; + + /// Callback invoked to remove the tunnel as the default IPv4/IPv6 route. + llarp_default_route_callback del_default_route; + } llarp_route_callbacks; + + /// Pack of crap to be passed into llarp_apple_init to initialize + typedef struct llarp_apple_config + { + /// lokinet configuration directory, expected to be the application-specific "home" + /// directory, which is where state files are stored and the lokinet.ini will be loaded (or + /// created if it doesn't exist). + const char* config_dir; + /// path to the default bootstrap.signed file included in installation, which will be used + /// by default when no specific bootstrap is in the config file. + const char* default_bootstrap; + /// llarp_apple_init writes the IP address for the primary tunnel IP address here, + /// null-terminated. + char tunnel_ipv4_ip[INET_ADDRSTRLEN]; + /// llarp_apple_init writes the netmask of the tunnel address here, null-terminated. + char tunnel_ipv4_netmask[INET_ADDRSTRLEN]; + /// Writes the IPv6 address for the tunnel here, null-terminated. + char tunnel_ipv6_ip[INET6_ADDRSTRLEN]; + /// IPv6 address prefix. + uint16_t tunnel_ipv6_prefix; + + /// The first upstream DNS server's IPv4 address the OS should use when in exit mode. + /// (Currently on mac in exit mode we only support querying the first such configured + /// server). + char upstream_dns[INET_ADDRSTRLEN]; + uint16_t upstream_dns_port; #ifdef MACOS_SYSTEM_EXTENSION - /// DNS bind IP; llarp_apple_init writes the lokinet config value here so that we know (in Apple - /// API code) what to set DNS to when lokinet gets turned on. Null terminated. - char dns_bind_ip[INET_ADDRSTRLEN]; + /// DNS bind IP; llarp_apple_init writes the lokinet config value here so that we know (in + /// Apple API code) what to set DNS to when lokinet gets turned on. Null terminated. + char dns_bind_ip[INET_ADDRSTRLEN]; #endif - /// \defgroup callbacks Callbacks - /// Callbacks we invoke for various operations that require glue into the Apple network - /// extension APIs. All of these except for ns_logger are passed the pointer provided to - /// llarp_apple_start when invoked. - /// @{ - - /// simple wrapper around NSLog for lokinet message logging - ns_logger_callback ns_logger; - - /// C function callback that will be called when we need to write a packet to the packet - /// tunnel. Will be passed AF_INET or AF_INET6, a void pointer to the data, and the size of - /// the data in bytes. - packet_writer_callback packet_writer; - - /// C function callback that will be called when lokinet is setup and ready to start receiving - /// packets from the packet tunnel. This should set up the read handler to deliver packets - /// via llarp_apple_incoming. - start_reading_callback start_reading; - - /// Callbacks invoked to add/remove routes to the tunnel. - llarp_route_callbacks route_callbacks; - - /// @} - } llarp_apple_config; - - /// Initializes a lokinet instance by initializing various objects and loading the configuration - /// (if /lokinet.ini exists). Does not actually start lokinet (call llarp_apple_start - /// for that). - /// - /// Returns NULL if there was a problem initializing/loading the configuration, otherwise returns - /// an opaque void pointer that should be passed into the other llarp_apple_* functions. - /// - /// \param config pointer to a llarp_apple_config where we get the various settings needed - /// and return the ip/mask/dns fields needed for the tunnel. - void* - llarp_apple_init(llarp_apple_config* config); - - /// Starts the lokinet instance in a new thread. - /// - /// \param lokinet the void pointer returned by llarp_apple_init - /// - /// \param callback_context Opaque pointer that is passed into the various callbacks provided to - /// llarp_apple_init. This code does nothing with this pointer aside from passing it through to - /// callbacks. - /// - /// \returns 0 on succesful startup, -1 on failure. - int - llarp_apple_start(void* lokinet, void* callback_context); - - /// Returns a pointer to the uv event loop. Must have called llarp_apple_start already. - uv_loop_t* - llarp_apple_get_uv_loop(void* lokinet); - - /// Struct of packet data; a C array of tests gets passed to llarp_apple_incoming - typedef struct llarp_incoming_packet - { - const void* bytes; - size_t size; - } llarp_incoming_packet; - - /// Called to deliver one or more incoming packets from the apple layer into lokinet. Takes a C - /// array of `llarp_incoming_packets` with pointers/sizes set to the individual new packets that - /// have arrived. - /// - /// Returns the number of valid packets on success (which can be less than the number of provided - /// packets, if some failed to parse), or -1 if there is no current active VPNInterface associated - /// with the lokinet instance (which generally means llarp_apple_start wasn't called or failed, or - /// lokinet is in the process of shutting down). - int - llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size); - - /// Stops a lokinet instance created with `llarp_apple_initialize`. This waits for lokinet to - /// shut down and rejoins the thread. After this call the given pointer is no longer valid. - void - llarp_apple_shutdown(void* lokinet); + /// \defgroup callbacks Callbacks + /// Callbacks we invoke for various operations that require glue into the Apple network + /// extension APIs. All of these except for ns_logger are passed the pointer provided to + /// llarp_apple_start when invoked. + /// @{ + + /// simple wrapper around NSLog for lokinet message logging + ns_logger_callback ns_logger; + + /// C function callback that will be called when we need to write a packet to the packet + /// tunnel. Will be passed AF_INET or AF_INET6, a void pointer to the data, and the size of + /// the data in bytes. + packet_writer_callback packet_writer; + + /// C function callback that will be called when lokinet is setup and ready to start + /// receiving packets from the packet tunnel. This should set up the read handler to + /// deliver packets via llarp_apple_incoming. + start_reading_callback start_reading; + + /// Callbacks invoked to add/remove routes to the tunnel. + llarp_route_callbacks route_callbacks; + + /// @} + } llarp_apple_config; + + /// Initializes a lokinet instance by initializing various objects and loading the configuration + /// (if /lokinet.ini exists). Does not actually start lokinet (call + /// llarp_apple_start for that). + /// + /// Returns NULL if there was a problem initializing/loading the configuration, otherwise + /// returns an opaque void pointer that should be passed into the other llarp_apple_* functions. + /// + /// \param config pointer to a llarp_apple_config where we get the various settings needed + /// and return the ip/mask/dns fields needed for the tunnel. + void* llarp_apple_init(llarp_apple_config* config); + + /// Starts the lokinet instance in a new thread. + /// + /// \param lokinet the void pointer returned by llarp_apple_init + /// + /// \param callback_context Opaque pointer that is passed into the various callbacks provided to + /// llarp_apple_init. This code does nothing with this pointer aside from passing it through to + /// callbacks. + /// + /// \returns 0 on succesful startup, -1 on failure. + int llarp_apple_start(void* lokinet, void* callback_context); + + /// Returns a pointer to the uv event loop. Must have called llarp_apple_start already. + uv_loop_t* llarp_apple_get_uv_loop(void* lokinet); + + /// Struct of packet data; a C array of tests gets passed to llarp_apple_incoming + typedef struct llarp_incoming_packet + { + const void* bytes; + size_t size; + } llarp_incoming_packet; + + /// Called to deliver one or more incoming packets from the apple layer into lokinet. Takes a C + /// array of `llarp_incoming_packets` with pointers/sizes set to the individual new packets that + /// have arrived. + /// + /// Returns the number of valid packets on success (which can be less than the number of + /// provided packets, if some failed to parse), or -1 if there is no current active VPNInterface + /// associated with the lokinet instance (which generally means llarp_apple_start wasn't called + /// or failed, or lokinet is in the process of shutting down). + int llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size); + + /// Stops a lokinet instance created with `llarp_apple_initialize`. This waits for lokinet to + /// shut down and rejoins the thread. After this call the given pointer is no longer valid. + void llarp_apple_shutdown(void* lokinet); #ifdef __cplusplus } // extern "C" diff --git a/llarp/apple/route_manager.cpp b/llarp/apple/route_manager.cpp index 021095eaf7..cf0e21fb5d 100644 --- a/llarp/apple/route_manager.cpp +++ b/llarp/apple/route_manager.cpp @@ -1,103 +1,101 @@ #include "route_manager.hpp" -#include -#include + #include +#include + #include namespace llarp::apple { - void - RouteManager::check_trampoline(bool enable) - { - if (trampoline_active == enable) - return; - auto router = context.router; - if (!router) + static auto logcat = log::Cat("apple.route_manager"); + + void RouteManager::check_trampoline(bool enable) { - LogError("Cannot reconfigure to use DNS trampoline: no router"); - return; - } + if (trampoline_active == enable) + return; + auto router = context.router; + if (!router) + { + log::error(logcat, "Cannot reconfigure to use DNS trampoline: no router"); + return; + } - std::shared_ptr tun; - router->hiddenServiceContext().ForEachService([&tun](const auto& /*name*/, const auto ep) { - tun = std::dynamic_pointer_cast(ep); - return !tun; - }); + std::shared_ptr tun; + router->hidden_service_context().ForEachService([&tun](const auto& /*name*/, const auto ep) { + tun = std::dynamic_pointer_cast(ep); + return !tun; + }); - if (!tun) - { - LogError("Cannot reconfigure to use DNS trampoline: no tun endpoint found (!?)"); - return; - } + if (!tun) + { + log::error(logcat, "Cannot reconfigure to use DNS trampoline: no tun endpoint found (!?)"); + return; + } - if (enable) - tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, {dns_trampoline_port}}}); - else - tun->ReconfigureDNS(router->GetConfig()->dns.m_upstreamDNS); + if (enable) + tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, {dns_trampoline_port}}}); + else + tun->ReconfigureDNS(router->config()->dns.upstream_dns); - trampoline_active = enable; - } + trampoline_active = enable; + } - void - RouteManager::AddDefaultRouteViaInterface(vpn::NetworkInterface&) - { - check_trampoline(true); - if (callback_context and route_callbacks.add_default_route) - route_callbacks.add_default_route(callback_context); - } + void RouteManager::add_default_route_via_interface(vpn::NetworkInterface&) + { + check_trampoline(true); + if (callback_context and route_callbacks.add_default_route) + route_callbacks.add_default_route(callback_context); + } - void - RouteManager::DelDefaultRouteViaInterface(vpn::NetworkInterface&) - { - check_trampoline(false); - if (callback_context and route_callbacks.del_default_route) - route_callbacks.del_default_route(callback_context); - } + void RouteManager::delete_default_route_via_interface(vpn::NetworkInterface&) + { + check_trampoline(false); + if (callback_context and route_callbacks.del_default_route) + route_callbacks.del_default_route(callback_context); + } - void - RouteManager::AddRouteViaInterface(vpn::NetworkInterface&, IPRange range) - { - check_trampoline(true); - if (callback_context) + void RouteManager::add_route_via_interface(vpn::NetworkInterface&, IPRange range) { - if (range.IsV4()) - { - if (route_callbacks.add_ipv4_route) - route_callbacks.add_ipv4_route( - range.BaseAddressString().c_str(), - net::TruncateV6(range.netmask_bits).ToString().c_str(), - callback_context); - } - else - { - if (route_callbacks.add_ipv6_route) - route_callbacks.add_ipv6_route( - range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context); - } + check_trampoline(true); + if (callback_context) + { + if (range.IsV4()) + { + if (route_callbacks.add_ipv4_route) + route_callbacks.add_ipv4_route( + range.BaseAddressString().c_str(), + net::TruncateV6(range.netmask_bits).to_string().c_str(), + callback_context); + } + else + { + if (route_callbacks.add_ipv6_route) + route_callbacks.add_ipv6_route( + range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context); + } + } } - } - void - RouteManager::DelRouteViaInterface(vpn::NetworkInterface&, IPRange range) - { - check_trampoline(false); - if (callback_context) + void RouteManager::delete_route_via_interface(vpn::NetworkInterface&, IPRange range) { - if (range.IsV4()) - { - if (route_callbacks.del_ipv4_route) - route_callbacks.del_ipv4_route( - range.BaseAddressString().c_str(), - net::TruncateV6(range.netmask_bits).ToString().c_str(), - callback_context); - } - else - { - if (route_callbacks.del_ipv6_route) - route_callbacks.del_ipv6_route( - range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context); - } + check_trampoline(false); + if (callback_context) + { + if (range.IsV4()) + { + if (route_callbacks.del_ipv4_route) + route_callbacks.del_ipv4_route( + range.BaseAddressString().c_str(), + net::TruncateV6(range.netmask_bits).to_string().c_str(), + callback_context); + } + else + { + if (route_callbacks.del_ipv6_route) + route_callbacks.del_ipv6_route( + range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context); + } + } } - } } // namespace llarp::apple diff --git a/llarp/apple/route_manager.hpp b/llarp/apple/route_manager.hpp index 69403c04d5..aee306de29 100644 --- a/llarp/apple/route_manager.hpp +++ b/llarp/apple/route_manager.hpp @@ -1,58 +1,47 @@ #pragma once -#include -#include #include "context_wrapper.h" +#include +#include + namespace llarp::apple { - class RouteManager final : public llarp::vpn::IRouteManager - { - public: - RouteManager(llarp::Context& ctx, llarp_route_callbacks rcs, void* callback_context) - : context{ctx}, callback_context{callback_context}, route_callbacks{std::move(rcs)} - {} + class RouteManager final : public llarp::vpn::AbstractRouteManager + { + public: + RouteManager(llarp::Context& ctx, llarp_route_callbacks rcs, void* callback_context) + : context{ctx}, callback_context{callback_context}, route_callbacks{std::move(rcs)} + {} - /// These are called for poking route holes, but we don't have to do that at all on macos - /// because the appex isn't subject to its own rules. - void - AddRoute(net::ipaddr_t /*ip*/, net::ipaddr_t /*gateway*/) override - {} + /// These are called for poking route holes, but we don't have to do that at all on macos + /// because the appex isn't subject to its own rules. + void add_route(oxen::quic::Address /*ip*/, oxen::quic::Address /*gateway*/) override {} - void - DelRoute(net::ipaddr_t /*ip*/, net::ipaddr_t /*gateway*/) override - {} + void delete_route(oxen::quic::Address /*ip*/, oxen::quic::Address /*gateway*/) override {} - void - AddDefaultRouteViaInterface(vpn::NetworkInterface& vpn) override; + void add_default_route_via_interface(vpn::NetworkInterface& vpn) override; - void - DelDefaultRouteViaInterface(vpn::NetworkInterface& vpn) override; + void delete_default_route_via_interface(vpn::NetworkInterface& vpn) override; - void - AddRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override; + void add_route_via_interface(vpn::NetworkInterface& vpn, IPRange range) override; - void - DelRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override; + void delete_route_via_interface(vpn::NetworkInterface& vpn, IPRange range) override; - std::vector - GetGatewaysNotOnInterface(vpn::NetworkInterface& /*vpn*/) override - { - // We can't get this on mac from our sandbox, but we don't actually need it because we - // ignore the gateway for AddRoute/DelRoute anyway, so just return a zero IP. - std::vector ret; - ret.emplace_back(net::ipv4addr_t{}); - return ret; - } - - private: - llarp::Context& context; - bool trampoline_active = false; - void - check_trampoline(bool enable); - - void* callback_context = nullptr; - llarp_route_callbacks route_callbacks; - }; + std::vector get_non_interface_gateways(vpn::NetworkInterface& /*vpn*/) override + { + // We can't get this on mac from our sandbox, but we don't actually need it because we + // ignore the gateway for AddRoute/DelRoute anyway, so just return a zero IP. + return std::vector{}; + } + + private: + llarp::Context& context; + bool trampoline_active = false; + void check_trampoline(bool enable); + + void* callback_context = nullptr; + llarp_route_callbacks route_callbacks; + }; } // namespace llarp::apple diff --git a/llarp/apple/vpn_interface.cpp b/llarp/apple/vpn_interface.cpp index f0b3a6c32d..4ffa535650 100644 --- a/llarp/apple/vpn_interface.cpp +++ b/llarp/apple/vpn_interface.cpp @@ -1,59 +1,56 @@ #include "vpn_interface.hpp" + #include "context.hpp" -#include + +#include namespace llarp::apple { - VPNInterface::VPNInterface( - Context& ctx, - packet_write_callback packet_writer, - on_readable_callback on_readable, - AbstractRouter* router) - : vpn::NetworkInterface{{}} - , m_PacketWriter{std::move(packet_writer)} - , m_OnReadable{std::move(on_readable)} - , _router{router} - { - ctx.loop->call_soon([this] { m_OnReadable(*this); }); - } - - bool - VPNInterface::OfferReadPacket(const llarp_buffer_t& buf) - { - llarp::net::IPPacket pkt; - if (!pkt.Load(buf)) - return false; - m_ReadQueue.tryPushBack(std::move(pkt)); - return true; - } - - void - VPNInterface::MaybeWakeUpperLayers() const - { - _router->TriggerPump(); - } - - int - VPNInterface::PollFD() const - { - return -1; - } - - net::IPPacket - VPNInterface::ReadNextPacket() - { - net::IPPacket pkt{}; - if (not m_ReadQueue.empty()) - pkt = m_ReadQueue.popFront(); - return pkt; - } - - bool - VPNInterface::WritePacket(net::IPPacket pkt) - { - int af_family = pkt.IsV6() ? AF_INET6 : AF_INET; - return m_PacketWriter(af_family, pkt.data(), pkt.size()); - } + VPNInterface::VPNInterface( + Context& ctx, packet_write_callback packet_writer, on_readable_callback on_readable, Router* router) + : vpn::NetworkInterface{}, + _pkt_writer{std::move(packet_writer)}, + _on_readable{std::move(on_readable)}, + _router{router} + { + ctx._loop->call_soon([this] { _on_readable(*this); }); + } + + bool VPNInterface::OfferReadPacket(const llarp_buffer_t& buf) + { + IPPacket pkt; + if (!pkt.load(buf.copy())) + return false; + _read_que.tryPushBack(std::move(pkt)); + return true; + } + + void VPNInterface::MaybeWakeUpperLayers() const + { + // + } + + int VPNInterface::PollFD() const + { + return -1; + } + + IPPacket VPNInterface::read_next_packet() + { + IPPacket pkt{}; + if (not _read_que.empty()) + pkt = _read_que.popFront(); + return pkt; + } + + bool VPNInterface::write_packet(IPPacket pkt) + { + // TODO: replace this with IPPacket::to_udp + (void)pkt; + // int af_family = pkt() ? AF_INET6 : AF_INET; + // return _pkt_writer(af_family, pkt.data(), pkt.size()); + return true; + } } // namespace llarp::apple diff --git a/llarp/apple/vpn_interface.hpp b/llarp/apple/vpn_interface.hpp index b030afd9a9..82d682737a 100644 --- a/llarp/apple/vpn_interface.hpp +++ b/llarp/apple/vpn_interface.hpp @@ -1,56 +1,48 @@ #pragma once #include -#include #include +#include + #include namespace llarp::apple { - struct Context; + struct Context; - class VPNInterface final : public vpn::NetworkInterface, - public std::enable_shared_from_this - { - public: - using packet_write_callback = std::function; - using on_readable_callback = std::function; + class VPNInterface final : public vpn::NetworkInterface, public std::enable_shared_from_this + { + public: + using packet_write_callback = std::function; + using on_readable_callback = std::function; - explicit VPNInterface( - Context& ctx, - packet_write_callback packet_writer, - on_readable_callback on_readable, - AbstractRouter* router); + explicit VPNInterface( + Context& ctx, packet_write_callback packet_writer, on_readable_callback on_readable, Router* router); - // Method to call when a packet has arrived to deliver the packet to lokinet - bool - OfferReadPacket(const llarp_buffer_t& buf); + // Method to call when a packet has arrived to deliver the packet to lokinet + bool OfferReadPacket(const llarp_buffer_t& buf); - int - PollFD() const override; + int PollFD() const override; - net::IPPacket - ReadNextPacket() override; + IPPacket read_next_packet() override; - bool - WritePacket(net::IPPacket pkt) override; + bool write_packet(IPPacket pkt) override; - void - MaybeWakeUpperLayers() const override; + void MaybeWakeUpperLayers() const override; - private: - // Function for us to call when we have a packet to emit. Should return true if the packet was - // handed off to the OS successfully. - packet_write_callback m_PacketWriter; + private: + // Function for us to call when we have a packet to emit. Should return true if the packet + // was handed off to the OS successfully. + packet_write_callback _pkt_writer; - // Called when we are ready to start reading packets - on_readable_callback m_OnReadable; + // Called when we are ready to start reading packets + on_readable_callback _on_readable; - static inline constexpr auto PacketQueueSize = 1024; + inline static constexpr auto PacketQueueSize = 1024; - thread::Queue m_ReadQueue{PacketQueueSize}; + thread::Queue _read_que{PacketQueueSize}; - AbstractRouter* const _router; - }; + Router* const _router; + }; } // namespace llarp::apple diff --git a/llarp/apple/vpn_platform.cpp b/llarp/apple/vpn_platform.cpp index 1d1eafb8e4..757c58838e 100644 --- a/llarp/apple/vpn_platform.cpp +++ b/llarp/apple/vpn_platform.cpp @@ -1,23 +1,23 @@ #include "vpn_platform.hpp" + #include "context.hpp" namespace llarp::apple { - VPNPlatform::VPNPlatform( - Context& ctx, - VPNInterface::packet_write_callback packet_writer, - VPNInterface::on_readable_callback on_readable, - llarp_route_callbacks route_callbacks, - void* callback_context) - : m_Context{ctx} - , m_RouteManager{ctx, std::move(route_callbacks), callback_context} - , m_PacketWriter{std::move(packet_writer)} - , m_OnReadable{std::move(on_readable)} - {} + VPNPlatform::VPNPlatform( + Context& ctx, + VPNInterface::packet_write_callback packet_writer, + VPNInterface::on_readable_callback on_readable, + llarp_route_callbacks route_callbacks, + void* callback_context) + : _context{ctx}, + _route_manager{ctx, std::move(route_callbacks), callback_context}, + _packet_writer{std::move(packet_writer)}, + _read_cb{std::move(on_readable)} + {} - std::shared_ptr - VPNPlatform::ObtainInterface(vpn::InterfaceInfo, AbstractRouter* router) - { - return std::make_shared(m_Context, m_PacketWriter, m_OnReadable, router); - } + std::shared_ptr VPNPlatform::obtain_interface(vpn::InterfaceInfo, Router* router) + { + return std::make_shared(_context, _packet_writer, _read_cb, router); + } } // namespace llarp::apple diff --git a/llarp/apple/vpn_platform.hpp b/llarp/apple/vpn_platform.hpp index 3e9023ee67..3e5fbbb7ec 100644 --- a/llarp/apple/vpn_platform.hpp +++ b/llarp/apple/vpn_platform.hpp @@ -1,35 +1,31 @@ #pragma once -#include -#include "vpn_interface.hpp" #include "route_manager.hpp" +#include "vpn_interface.hpp" + +#include namespace llarp::apple { - class VPNPlatform final : public vpn::Platform - { - public: - explicit VPNPlatform( - Context& ctx, - VPNInterface::packet_write_callback packet_writer, - VPNInterface::on_readable_callback on_readable, - llarp_route_callbacks route_callbacks, - void* callback_context); + class VPNPlatform final : public vpn::Platform + { + public: + explicit VPNPlatform( + Context& ctx, + VPNInterface::packet_write_callback packet_writer, + VPNInterface::on_readable_callback on_readable, + llarp_route_callbacks route_callbacks, + void* callback_context); - std::shared_ptr - ObtainInterface(vpn::InterfaceInfo, AbstractRouter*) override; + std::shared_ptr obtain_interface(vpn::InterfaceInfo, Router*) override; - vpn::IRouteManager& - RouteManager() override - { - return m_RouteManager; - } + vpn::AbstractRouteManager& RouteManager() override { return _route_manager; } - private: - Context& m_Context; - apple::RouteManager m_RouteManager; - VPNInterface::packet_write_callback m_PacketWriter; - VPNInterface::on_readable_callback m_OnReadable; - }; + private: + Context& _context; + apple::RouteManager _route_manager; + VPNInterface::packet_write_callback _packet_writer; + VPNInterface::on_readable_callback _read_cb; + }; } // namespace llarp::apple diff --git a/llarp/auth/auth.hpp b/llarp/auth/auth.hpp new file mode 100644 index 0000000000..85b23cf7fe --- /dev/null +++ b/llarp/auth/auth.hpp @@ -0,0 +1,181 @@ +#pragma once + +#include "types.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace llarp +{ + struct Router; +} // namespace llarp + +namespace llarp +{ + namespace auth + { + struct AuthPolicy + { + protected: + Router& _router; + + public: + AuthPolicy(Router& r) : _router{r} {} + + virtual ~AuthPolicy() = default; + + virtual std::weak_ptr get_weak() = 0; + + virtual std::shared_ptr get_self() = 0; + + const Router& router() const { return _router; } + + Router& router() { return _router; } + }; + + struct SessionAuthPolicy final : public AuthPolicy, public std::enable_shared_from_this + { + private: + const bool _is_snode_service{false}; + const bool _is_exit_service{false}; + + Ed25519SecretKey _session_key; + NetworkAddress _remote; + + public: + SessionAuthPolicy(Router& r, RouterID& remote, bool is_snode, bool is_exit = false); + + bool load_identity_from_file(const char* fname); + + std::optional fetch_auth_token(); + + const Ed25519SecretKey& session_key() const { return _session_key; } + + bool is_snode_service() const { return _is_snode_service; } + + bool is_exit_service() const { return _is_exit_service; } + + std::weak_ptr get_weak() override { return weak_from_this(); } + + std::shared_ptr get_self() override { return shared_from_this(); } + }; + + struct FileAuthPolicy final : public AuthPolicy, public std::enable_shared_from_this + { + FileAuthPolicy(Router& r, std::set files, AuthFileType filetype) + : AuthPolicy{r}, _files{std::move(files)}, _type{filetype} + {} + + std::weak_ptr get_weak() override { return weak_from_this(); } + + std::shared_ptr get_self() override { return shared_from_this(); } + + private: + const std::set _files; + const AuthFileType _type; + mutable util::Mutex _m; + std::unordered_set _pending; + /// returns an auth result for a auth info challange, opens every file until it finds a + /// token matching it this is expected to be done in the IO thread + AuthResult check_files(const AuthInfo& info) const; + + bool check_passwd(std::string hash, std::string challenge) const; + }; + + struct RPCAuthPolicy final : public AuthPolicy, public std::enable_shared_from_this + { + explicit RPCAuthPolicy(Router& r, std::string url, std::string method, std::shared_ptr lmq); + + ~RPCAuthPolicy() override = default; + + std::weak_ptr get_weak() override { return weak_from_this(); } + + std::shared_ptr get_self() override { return shared_from_this(); } + + void start(); + + private: + const std::string _endpoint; + const std::string _method; + // const std::unordered_set _whitelist; + // const std::unordered_set _static_tokens; + + std::shared_ptr _omq; + std::optional _omq_conn; + std::unordered_set _pending_sessions; + }; + } // namespace auth + + namespace concepts + { + template + concept AuthPolicyType = std::is_base_of_v; + } // namespace concepts + + template + inline static std::shared_ptr make_auth_policy(Router& r, Opt&&... opts) + { + return std::make_shared(r, std::forward(opts)...); + } + + /// maybe get auth result from string + inline std::optional parse_auth_code(std::string data) + { + std::unordered_map values = { + {"OKAY", auth::AuthCode::ACCEPTED}, + {"REJECT", auth::AuthCode::REJECTED}, + {"PAYME", auth::AuthCode::PAYMENT_REQUIRED}, + {"LIMITED", auth::AuthCode::RATE_LIMIT}}; + auto itr = values.find(data); + if (itr == values.end()) + return std::nullopt; + return itr->second; + } + + /// get an auth type from a string + /// throws std::invalid_argument if arg is invalid + inline auth::AuthType parse_auth_type(std::string data) + { + std::unordered_map values = { + {"file", auth::AuthType::FILE}, + {"lmq", auth::AuthType::OMQ}, + {"whitelist", auth::AuthType::WHITELIST}, + {"none", auth::AuthType::NONE}}; + const auto itr = values.find(data); + if (itr == values.end()) + throw std::invalid_argument("no such auth type: " + data); + return itr->second; + } + + /// get an auth file type from a string + /// throws std::invalid_argument if arg is invalid + inline auth::AuthFileType parse_auth_file_type(std::string data) + { + std::unordered_map values = { + {"plain", auth::AuthFileType::PLAIN}, + {"plaintext", auth::AuthFileType::PLAIN}, + {"hashed", auth::AuthFileType::HASHES}, + {"hashes", auth::AuthFileType::HASHES}, + {"hash", auth::AuthFileType::HASHES}}; + const auto itr = values.find(data); + if (itr == values.end()) + throw std::invalid_argument("no such auth file type: " + data); +#ifndef HAVE_CRYPT + if (itr->second == auth::AuthFileType::HASHES) + throw std::invalid_argument("unsupported auth file type: " + data); +#endif + return itr->second; + } +} // namespace llarp diff --git a/llarp/auth/file_auth.cpp b/llarp/auth/file_auth.cpp new file mode 100644 index 0000000000..f4f2c596f7 --- /dev/null +++ b/llarp/auth/file_auth.cpp @@ -0,0 +1,44 @@ +#include "auth.hpp" + +#include +#include + +namespace llarp::auth +{ + AuthResult FileAuthPolicy::check_files(const AuthInfo& info) const + { + for (const auto& f : _files) + { + std::ifstream i{f}; + std::string line{}; + while (std::getline(i, line)) + { + // split off comments + const auto parts = split_any(line, "#;", true); + if (auto part = parts[0]; not parts.empty() and not parts[0].empty()) + { + // split off whitespaces and check password + if (check_passwd(std::string{TrimWhitespace(part)}, info.token)) + return AuthResult{AuthCode::ACCEPTED, "accepted by whitelist"}; + } + } + } + return AuthResult{AuthCode::REJECTED, "rejected by whitelist"}; + } + + bool FileAuthPolicy::check_passwd(std::string hash, std::string challenge) const + { + switch (_type) + { + case AuthFileType::PLAIN: + return hash == challenge; + case AuthFileType::HASHES: +#ifdef HAVE_CRYPT + return crypto::check_passwd_hash(std::move(hash), std::move(challenge)); +#endif + default: + return false; + } + } + +} // namespace llarp::auth diff --git a/llarp/auth/rpc_auth.cpp b/llarp/auth/rpc_auth.cpp new file mode 100644 index 0000000000..c66a934279 --- /dev/null +++ b/llarp/auth/rpc_auth.cpp @@ -0,0 +1,36 @@ +#include "auth.hpp" + +#include + +namespace llarp::auth +{ + static auto logcat = log::Cat("rpc.auth"); + + RPCAuthPolicy::RPCAuthPolicy(Router& r, std::string url, std::string method, std::shared_ptr lmq) + : AuthPolicy{r}, + _endpoint{std::move(url)}, + _method{std::move(method)}, + // _whitelist{std::move(whitelist_addrs)}, + // _static_tokens{std::move(whitelist_tokens)}, + _omq{std::move(lmq)} + { + if (_endpoint.empty() or _method.empty()) + throw std::invalid_argument{ + "RPC AuthPolicy must be initialized with an endpoint to query and a method to invoke!"}; + } + + void RPCAuthPolicy::start() + { + _omq->connect_remote( + _endpoint, + [self = shared_from_this()](oxenmq::ConnectionID c) { + self->_omq_conn = std::move(c); + log::info(logcat, "OMQ connected to endpoint auth server"); + }, + [self = shared_from_this()](oxenmq::ConnectionID, std::string_view fail) { + log::warning(logcat, "OMQ failed to connect to endpoint auth server: {}", fail); + self->_router.loop()->call_later(1s, [self] { self->start(); }); + }); + } + +} // namespace llarp::auth diff --git a/llarp/auth/session_auth.cpp b/llarp/auth/session_auth.cpp new file mode 100644 index 0000000000..729ade273f --- /dev/null +++ b/llarp/auth/session_auth.cpp @@ -0,0 +1,39 @@ +#include "auth.hpp" + +#include + +namespace llarp::auth +{ + SessionAuthPolicy::SessionAuthPolicy(Router& r, RouterID& remote, bool is_snode, bool is_exit) + : AuthPolicy{r}, + _is_snode_service{is_snode}, + _is_exit_service{is_exit}, + _remote{NetworkAddress::from_pubkey(remote, not _is_snode_service)} + { + // These can both be false but CANNOT both be true + if (_is_exit_service & _is_snode_service) + throw std::runtime_error{"Cannot create SessionAuthPolicy for a remote exit and remote service!"}; + + if (_is_snode_service) + _session_key = _router.identity(); + else + crypto::identity_keygen(_session_key); + } + + std::optional SessionAuthPolicy::fetch_auth_token() + { + std::optional ret = std::nullopt; + auto& exit_auths = _router.config()->network.exit_auths; + + if (auto itr = exit_auths.find(_remote); itr != exit_auths.end()) + ret = itr->second; + + return ret; + } + + bool SessionAuthPolicy::load_identity_from_file(const char* fname) + { + return _session_key.load_from_file(fname); + } + +} // namespace llarp::auth diff --git a/llarp/auth/types.hpp b/llarp/auth/types.hpp new file mode 100644 index 0000000000..e2fe4537d8 --- /dev/null +++ b/llarp/auth/types.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +namespace llarp::auth +{ + /// authentication status code + enum class AuthCode : uint64_t + { + /// explicitly accepted + ACCEPTED = 0, + /// explicitly rejected + REJECTED = 1, + /// attempt failed + FAILED = 2, + /// attempt rate limited + RATE_LIMIT = 3, + /// need mo munny + PAYMENT_REQUIRED = 4 + }; + + /// auth result object with code and reason + struct AuthResult + { + AuthCode code; + std::string reason; + }; + + /// info needed by clients in order to authenticate to a remote endpoint + struct AuthInfo + { + std::string token; + }; + + /// what kind of backend to use for auth + enum class AuthType + { + /// no authentication + NONE, + /// manual whitelist + WHITELIST, + /// LMQ server + OMQ, + /// static file + FILE, + }; + + /// how to interpret an file for auth + enum class AuthFileType + { + PLAIN, + HASHES, + }; + +} // namespace llarp::auth diff --git a/llarp/bootstrap-fallbacks.cpp.in b/llarp/bootstrap-fallbacks.cpp.in index 5c81ea7166..51754db48f 100644 --- a/llarp/bootstrap-fallbacks.cpp.in +++ b/llarp/bootstrap-fallbacks.cpp.in @@ -1,25 +1,24 @@ #include -#include "llarp/bootstrap.hpp" +#include namespace llarp { - using namespace std::literals; - std::unordered_map load_bootstrap_fallbacks() { std::unordered_map fallbacks; - using init_list = std::initializer_list>; - // clang-format off - for (const auto& [network, bootstrap] : init_list{ + + for (const auto& [network, bootstrap] : std::initializer_list>{ @BOOTSTRAP_FALLBACKS@ }) - // clang-format on { - llarp_buffer_t buf{bootstrap.data(), bootstrap.size()}; + if (network != RouterContact::ACTIVE_NETID) + continue; + auto& bsl = fallbacks[network]; - bsl.BDecode(&buf); + bsl.bt_decode(bootstrap); } + return fallbacks; } } // namespace llarp diff --git a/llarp/bootstrap.cpp b/llarp/bootstrap.cpp index 32f830bdce..b6551f2c85 100644 --- a/llarp/bootstrap.cpp +++ b/llarp/bootstrap.cpp @@ -1,71 +1,177 @@ #include "bootstrap.hpp" -#include "util/bencode.hpp" + +#include "util/file.hpp" #include "util/logging.hpp" #include "util/logging/buffer.hpp" -#include "util/fs.hpp" namespace llarp { - void - BootstrapList::Clear() - { - clear(); - } - - bool - BootstrapList::BDecode(llarp_buffer_t* buf) - { - return bencode_read_list( - [&](llarp_buffer_t* b, bool more) -> bool { - if (more) - { - RouterContact rc{}; - if (not rc.BDecode(b)) - { - LogError("invalid rc in bootstrap list: ", llarp::buffer_printer{*b}); - return false; - } - emplace(std::move(rc)); - } - return true; - }, - buf); - } - - bool - BootstrapList::BEncode(llarp_buffer_t* buf) const - { - return BEncodeWriteList(begin(), end(), buf); - } - - void - BootstrapList::AddFromFile(fs::path fpath) - { - bool isListFile = false; + static auto logcat = log::Cat("Bootstrap"); + + bool BootstrapList::bt_decode(std::string_view buf) { - std::ifstream inf(fpath.c_str(), std::ios::binary); - if (inf.is_open()) - { - const char ch = inf.get(); - isListFile = ch == 'l'; - } + const auto& f = buf.front(); + + switch (f) + { + case 'l': + return bt_decode_list(buf); + case 'd': + return bt_decode_dict(buf); + default: + log::critical(logcat, "Unable to parse bootstrap as bt list or dict!"); + return false; + } } - if (isListFile) + + bool BootstrapList::bt_decode_dict(std::string_view buf) { - if (not BDecodeReadFile(fpath, *this)) - { - throw std::runtime_error{fmt::format("failed to read bootstrap list file '{}'", fpath)}; - } + log::trace(logcat, "{} called", __PRETTY_FUNCTION__); + + bool ret = true; + + try + { + ret &= emplace(buf).second; + } + catch (...) + { + log::warning(logcat, "Unable to decode bootstrap RemoteRC"); + return false; + } + + _curr = begin(); + return ret; } - else + + bool BootstrapList::bt_decode_list(std::string_view buf) { - RouterContact rc; - if (not rc.Read(fpath)) - { - throw std::runtime_error{ - fmt::format("failed to decode bootstrap RC, file='{}', rc={}", fpath, rc)}; - } - this->insert(rc); + log::trace(logcat, "{} called", __PRETTY_FUNCTION__); + + bool ret = true; + + try + { + oxenc::bt_list_consumer btlc{buf}; + + while (not btlc.is_finished()) + ret &= emplace(btlc.consume_dict_data()).second; + } + catch (const std::exception& e) + { + log::warning(logcat, "Unable to decode bootstrap RemoteRC: {}", e.what()); + return false; + } + + _curr = begin(); + return ret; + } + + bool BootstrapList::contains(const RouterID& rid) const + { + for (const auto& it : *this) + { + if (it.router_id() == rid) + return true; + } + + return false; + } + + bool BootstrapList::contains(const RemoteRC& rc) const + { + return count(rc); + } + + std::string_view BootstrapList::bt_encode() const + { + oxenc::bt_list_producer btlp{}; + + for (const auto& it : *this) + btlp.append(it.view()); + + return btlp.view(); + } + + void BootstrapList::populate_bootstraps(std::vector paths, const fs::path& def, bool load_fallbacks) + { + for (const auto& f : paths) + { + // TESTNET: TODO: revise fucked config + log::trace(logcat, "Loading BootstrapRC from file at path:{}", f); + if (not read_from_file(f)) + throw std::invalid_argument{"User-provided BootstrapRC is invalid!"}; + } + + if (empty()) + { + log::trace(logcat, "BootstrapRC list empty; looking for default BootstrapRC from file at path:{}", def); + read_from_file(def); + } + + for (auto itr = begin(); itr != end(); ++itr) + { + if (RouterContact::is_obsolete(*itr)) + { + log::debug(logcat, "Deleting obsolete BootstrapRC (rid:{})", itr->router_id()); + itr = erase(itr); + continue; + } + } + + // TESTNET: force load fallbacks + if (/* empty() and */ load_fallbacks) + { + // log::critical(logcat, "BootstrapRC list empty; loading fallbacks..."); + log::critical(logcat, "BootstrapRC list force loading fallbacks..."); + auto fallbacks = llarp::load_bootstrap_fallbacks(); + + if (auto itr = fallbacks.find(RouterContact::ACTIVE_NETID); itr != fallbacks.end()) + { + log::debug(logcat, "Loading {} default fallback bootstrap router(s)!", itr->second.size()); + log::critical(logcat, "Fallback bootstrap loaded: {}", itr->second.current()); + merge(itr->second); + } + + if (empty()) + { + log::error( + logcat, + "No Bootstrap routers were loaded. The default Bootstrap file {} does not " + "exist, and " + "loading fallback Bootstrap RCs failed.", + def); + + throw std::runtime_error("No Bootstrap nodes available."); + } + } + + log::debug(logcat, "We have {} Bootstrap router(s)!", size()); + _curr = begin(); + } + + bool BootstrapList::read_from_file(const fs::path& fpath) + { + bool result = false; + + if (not fs::exists(fpath)) + { + log::critical(logcat, "Bootstrap RC file non-existant at path:{}", fpath); + return result; + } + + auto content = util::file_to_string(fpath); + result = bt_decode(content); + + log::trace( + logcat, + "{}uccessfully loaded BootstrapRC file ({}B) at path:{}, contents: {}", + result ? "S" : "Uns", + content.size(), + fpath, + buffer_printer{content}); + + _curr = begin(); + return result; } - } } // namespace llarp diff --git a/llarp/bootstrap.hpp b/llarp/bootstrap.hpp index c4a4b7d3fb..83f973676d 100644 --- a/llarp/bootstrap.hpp +++ b/llarp/bootstrap.hpp @@ -1,28 +1,63 @@ #pragma once #include "router_contact.hpp" + +#include + #include #include -#include "llarp/util/fs.hpp" namespace llarp { - struct BootstrapList final : public std::set - { - bool - BDecode(llarp_buffer_t* buf); + struct BootstrapList final : public std::set + { + std::set::iterator _curr = begin(); + + const RemoteRC& current() { return *_curr; } + + bool bt_decode(std::string_view buf); + + bool bt_decode_dict(std::string_view buf); + + bool bt_decode_list(std::string_view buf); + + bool bt_decode(oxenc::bt_list_consumer btlc); + + bool bt_decode(oxenc::bt_dict_consumer btdc); + + std::string_view bt_encode() const; + + void populate_bootstraps(std::vector paths, const fs::path& def, bool load_fallbacks); + + bool read_from_file(const fs::path& fpath); + + bool contains(const RouterID& rid) const; + + // returns a reference to the next bootstrap in the list + const RemoteRC& next() + { + if (size() < 2) + return *_curr; + + ++_curr; + + if (_curr == this->end()) + _curr = this->begin(); + + return *_curr; + } - bool - BEncode(llarp_buffer_t* buf) const; + bool contains(const RemoteRC& rc) const; - void - AddFromFile(fs::path fpath); + void randomize() + { + if (size() > 1) + _curr = std::next(begin(), std::uniform_int_distribution{0, size() - 1}(csrng)); + } - void - Clear(); - }; + void clear_list() { clear(); } + }; - std::unordered_map - load_bootstrap_fallbacks(); + std::unordered_map load_bootstrap_fallbacks(); } // namespace llarp diff --git a/llarp/bootstrap_fallbacks.cpp b/llarp/bootstrap_fallbacks.cpp new file mode 100644 index 0000000000..14dcf6d818 --- /dev/null +++ b/llarp/bootstrap_fallbacks.cpp @@ -0,0 +1,24 @@ +#include + +#include + +namespace llarp +{ + std::unordered_map load_bootstrap_fallbacks() + { + std::unordered_map fallbacks; + + for (const auto& [network, bootstrap] : std::initializer_list>{ + // + }) + { + if (network != RouterContact::ACTIVE_NETID) + continue; + + auto& bsl = fallbacks[network]; + bsl.bt_decode(bootstrap); + } + + return fallbacks; + } +} // namespace llarp diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index b00e38e58e..74527912d8 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -1,1665 +1,1717 @@ #include "config.hpp" + #include "definition.hpp" #include "ini.hpp" -#include #include #include -#include #include -#include -#include +#include #include #include -#include -#include -#include - -#include -#include -#include -#include -#include +#include namespace llarp { - // constants for config file default values - constexpr int DefaultMinConnectionsForRouter = 6; - constexpr int DefaultMaxConnectionsForRouter = 60; - - constexpr int DefaultMinConnectionsForClient = 4; - constexpr int DefaultMaxConnectionsForClient = 6; - - constexpr int DefaultPublicPort = 1090; + static auto logcat = log::Cat("config"); - using namespace config; - namespace - { - struct ConfigGenParameters_impl : public ConfigGenParameters + static bool check_path_op(std::optional& path) { - const llarp::net::Platform* - Net_ptr() const - { - return llarp::net::Platform::Default_ptr(); - } - }; - } // namespace - - void - RouterConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) - { - constexpr Default DefaultJobQueueSize{1024 * 8}; - constexpr Default DefaultWorkerThreads{0}; - constexpr Default DefaultBlockBogons{true}; - - conf.defineOption( - "router", "job-queue-size", DefaultJobQueueSize, Hidden, [this](int arg) { - if (arg < 1024) - throw std::invalid_argument("job-queue-size must be 1024 or greater"); - - m_JobQueueSize = arg; - }); - - conf.defineOption( - "router", - "netid", - Default{llarp::DEFAULT_NETID}, - Comment{ - "Network ID; this is '"s + llarp::DEFAULT_NETID + "' for mainnet, 'gamma' for testnet.", - }, - [this](std::string arg) { - if (arg.size() > NetID::size()) - throw std::invalid_argument{ - fmt::format("netid is too long, max length is {}", NetID::size())}; - - m_netId = std::move(arg); - }); + if (not path.has_value()) + { + log::info(logcat, "Path input failed to parse..."); + } + else if (path->empty()) + { + log::warning(logcat, "Path contents ({}) empty...", path->c_str()); + path.reset(); + } + else + { + log::debug(logcat, "Valid path parsed ({})", path->c_str()); + return true; + } - int minConnections = - (params.isRelay ? DefaultMinConnectionsForRouter : DefaultMinConnectionsForClient); - conf.defineOption( - "router", - "min-connections", - Default{minConnections}, - Comment{ - "Minimum number of routers lokinet will attempt to maintain connections to.", - }, - [=](int arg) { - if (arg < minConnections) - throw std::invalid_argument{ - fmt::format("min-connections must be >= {}", minConnections)}; + return false; + } - m_minConnectedRouters = arg; - }); + using namespace config; + namespace + { + struct ConfigGenParameters_impl : public ConfigGenParameters + { + const llarp::net::Platform* net_ptr() const override { return llarp::net::Platform::Default_ptr(); } + }; + } // namespace - int maxConnections = - (params.isRelay ? DefaultMaxConnectionsForRouter : DefaultMaxConnectionsForClient); - conf.defineOption( - "router", - "max-connections", - Default{maxConnections}, - Comment{ - "Maximum number (hard limit) of routers lokinet will be connected to at any time.", - }, - [=](int arg) { - if (arg < maxConnections) - throw std::invalid_argument{ - fmt::format("max-connections must be >= {}", maxConnections)}; + void RouterConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) + { + constexpr Default DefaultJobQueueSize{1024 * 8}; + constexpr Default DefaultWorkerThreads{0}; + constexpr Default DefaultBlockBogons{true}; - m_maxConnectedRouters = arg; - }); + conf.define_option("router", "job-queue-size", DefaultJobQueueSize, Hidden, [this](int arg) { + if (arg < 1024) + throw std::invalid_argument("job-queue-size must be 1024 or greater"); - conf.defineOption("router", "nickname", Hidden, AssignmentAcceptor(m_nickname)); - - conf.defineOption( - "router", - "data-dir", - Default{params.defaultDataDir}, - Comment{ - "Optional directory for containing lokinet runtime data. This includes generated", - "private keys.", - }, - [this](fs::path arg) { - if (arg.empty()) - throw std::invalid_argument("[router]:data-dir is empty"); - if (not fs::exists(arg)) - throw std::runtime_error{ - fmt::format("Specified [router]:data-dir {} does not exist", arg)}; - - m_dataDir = std::move(arg); + job_que_size = arg; }); - conf.defineOption( - "router", - "public-ip", - RelayOnly, - Comment{ - "For complex network configurations where the detected IP is incorrect or non-public", - "this setting specifies the public IP at which this router is reachable. When", - "provided the public-port option must also be specified.", - }, - [this, net = params.Net_ptr()](std::string arg) { - if (arg.empty()) - return; - nuint32_t addr{}; - if (not addr.FromString(arg)) - throw std::invalid_argument{fmt::format("{} is not a valid IPv4 address", arg)}; - - if (net->IsBogonIP(addr)) + conf.define_option( + "router", + "netid", + Default{llarp::LOKINET_DEFAULT_NETID}, + Comment{ + "Network ID; this is '"s + llarp::LOKINET_DEFAULT_NETID + "' for mainnet, '"s + + llarp::LOKINET_TESTNET_NETID + "' for testnet."s, + }, + [this](std::string arg) { + if (arg.size() > NETID_SIZE) + throw std::invalid_argument{"netid is too long, max length is {}"_format(NETID_SIZE)}; + + net_id = std::move(arg); + }); + + conf.define_option( + "router", + "relay-connections", + Default{CLIENT_ROUTER_CONNECTIONS}, + ClientOnly, + Comment{ + "Minimum number of routers lokinet client will attempt to maintain connections to.", + }, + [=, this](int arg) { + if (arg < CLIENT_ROUTER_CONNECTIONS) + throw std::invalid_argument{ + "Client relay connections must be >= {}"_format(CLIENT_ROUTER_CONNECTIONS)}; + + client_router_connections = arg; + }); + + conf.define_option( + "router", + "min-connections", + Deprecated, + Comment{ + "Minimum number of routers lokinet will attempt to maintain connections to.", + }, + [=](int) { + log::warning(logcat, "Router min-connections is deprecated; use relay-connections for clients"); + }); + + conf.define_option( + "router", + "max-connections", + Deprecated, + Comment{ + "Maximum number (hard limit) of routers lokinet will be connected to at any time.", + }, + [=](int) { + log::warning(logcat, "Router max-connections is deprecated; use relay-connections for clients"); + }); + + conf.define_option("router", "nickname", Deprecated); + + conf.define_option( + "router", + "data-dir", + Default{params.default_data_dir}, + Comment{ + "Optional directory for containing lokinet runtime data. This includes generated", + "private keys.", + }, + [this](fs::path arg) { + if (arg.empty()) + throw std::invalid_argument("[router]:data-dir is empty"); + if (not fs::exists(arg)) + throw std::runtime_error{"Specified [router]:data-dir {} does not exist"_format(arg)}; + + data_dir = std::move(arg); + }); + + conf.define_option( + "router", + "public-ip", + RelayOnly, + Comment{ + "For complex network configurations where the detected IP is incorrect or " + "non-public", + "this setting specifies the public IP at which this router is reachable. When", + "provided the public-port option must also be specified.", + }, + [this](std::string arg) { public_ip = std::move(arg); }); + + conf.define_option("router", "public-address", Hidden, [](std::string) { throw std::invalid_argument{ - fmt::format("{} is not a publicly routable ip address", addr)}; - - PublicIP = addr; + "[router]:public-address option no longer supported, use [router]:public-ip and " + "[router]:public-port instead"}; }); - conf.defineOption("router", "public-address", Hidden, [](std::string) { - throw std::invalid_argument{ - "[router]:public-address option no longer supported, use [router]:public-ip and " - "[router]:public-port instead"}; - }); - - conf.defineOption( - "router", - "public-port", - RelayOnly, - Default{DefaultPublicPort}, - Comment{ - "When specifying public-ip=, this specifies the public UDP port at which this lokinet", - "router is reachable. Required when public-ip is used.", - }, - [this](int arg) { - if (arg <= 0 || arg > std::numeric_limits::max()) - throw std::invalid_argument("public-port must be >= 0 and <= 65536"); - PublicPort = ToNet(huint16_t{static_cast(arg)}); - }); - - conf.defineOption( - "router", - "worker-threads", - DefaultWorkerThreads, - Comment{ - "The number of threads available for performing cryptographic functions.", - "The minimum is one thread, but network performance may increase with more.", - "threads. Should not exceed the number of logical CPU cores.", - "0 means use the number of logical CPU cores detected at startup.", - }, - [this](int arg) { - if (arg < 0) - throw std::invalid_argument("worker-threads must be >= 0"); - - m_workerThreads = arg; - }); - - // Hidden option because this isn't something that should ever be turned off occasionally when - // doing dev/testing work. - conf.defineOption( - "router", "block-bogons", DefaultBlockBogons, Hidden, AssignmentAcceptor(m_blockBogons)); - - constexpr auto relative_to_datadir = - "An absolute path is used as-is, otherwise relative to 'data-dir'."; - - conf.defineOption( - "router", - "contact-file", - RelayOnly, - Default{llarp::our_rc_filename}, - AssignmentAcceptor(m_routerContactFile), - Comment{ - "Filename in which to store the router contact file", - relative_to_datadir, - }); - - conf.defineOption( - "router", - "encryption-privkey", - RelayOnly, - Default{llarp::our_enc_key_filename}, - AssignmentAcceptor(m_encryptionKeyFile), - Comment{ - "Filename in which to store the encryption private key", - relative_to_datadir, - }); - - conf.defineOption( - "router", - "ident-privkey", - RelayOnly, - Default{llarp::our_identity_filename}, - AssignmentAcceptor(m_identityKeyFile), - Comment{ - "Filename in which to store the identity private key", - relative_to_datadir, - }); - - conf.defineOption( - "router", - "transport-privkey", - RelayOnly, - Default{llarp::our_transport_key_filename}, - AssignmentAcceptor(m_transportKeyFile), - Comment{ - "Filename in which to store the transport private key.", - relative_to_datadir, - }); - - // Deprecated options: - - // these weren't even ever used! - conf.defineOption("router", "max-routers", Deprecated); - conf.defineOption("router", "min-routers", Deprecated); - - // TODO: this may have been a synonym for [router]worker-threads - conf.defineOption("router", "threads", Deprecated); - conf.defineOption("router", "net-threads", Deprecated); - - m_isRelay = params.isRelay; - } - - void - NetworkConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) - { - (void)params; - - static constexpr Default ProfilingValueDefault{true}; - static constexpr Default SaveProfilesDefault{true}; - static constexpr Default ReachableDefault{true}; - static constexpr Default HopsDefault{4}; - static constexpr Default PathsDefault{6}; - static constexpr Default IP6RangeDefault{"fd00::"}; - - conf.defineOption( - "network", "type", Default{"tun"}, Hidden, AssignmentAcceptor(m_endpointType)); - - conf.defineOption( - "network", - "save-profiles", - SaveProfilesDefault, - Hidden, - AssignmentAcceptor(m_saveProfiles)); - - conf.defineOption( - "network", - "profiling", - ProfilingValueDefault, - Hidden, - AssignmentAcceptor(m_enableProfiling)); - - conf.defineOption("network", "profiles", Deprecated); - - conf.defineOption( - "network", - "strict-connect", - ClientOnly, - MultiValue, - [this](std::string value) { - RouterID router; - if (not router.FromString(value)) - throw std::invalid_argument{"bad snode value: " + value}; - if (not m_strictConnect.insert(router).second) - throw std::invalid_argument{"duplicate strict connect snode: " + value}; - }, - Comment{ - "Public keys of routers which will act as pinned first-hops. This may be used to", - "provide a trusted router (consider that you are not fully anonymous with your", - "first hop). This REQUIRES two or more nodes to be specified.", - }); - - conf.defineOption( - "network", - "keyfile", - ClientOnly, - AssignmentAcceptor(m_keyfile), - Comment{ - "The private key to persist address with. If not specified the address will be", - "ephemeral.", - }); - - conf.defineOption( - "network", - "auth", - ClientOnly, - Comment{ - "Set the endpoint authentication mechanism.", - "none/whitelist/lmq/file", - }, - [this](std::string arg) { - if (arg.empty()) - return; - m_AuthType = service::ParseAuthType(arg); - }); - - conf.defineOption( - "network", - "auth-lmq", - ClientOnly, - AssignmentAcceptor(m_AuthUrl), - Comment{ - "lmq endpoint to talk to for authenticating new sessions", - "ipc:///var/lib/lokinet/auth.socket", - "tcp://127.0.0.1:5555", - }); - - conf.defineOption( - "network", - "auth-lmq-method", - ClientOnly, - Default{"llarp.auth"}, - Comment{ - "lmq function to call for authenticating new sessions", - "llarp.auth", - }, - [this](std::string arg) { - if (arg.empty()) - return; - m_AuthMethod = std::move(arg); - }); - - conf.defineOption( - "network", - "auth-whitelist", - ClientOnly, - MultiValue, - Comment{ - "manually add a remote endpoint by .loki address to the access whitelist", - }, - [this](std::string arg) { - service::Address addr; - if (not addr.FromString(arg)) - throw std::invalid_argument{fmt::format("bad loki address: {}", arg)}; - m_AuthWhitelist.emplace(std::move(addr)); - }); - - conf.defineOption( - "network", - "auth-file", - ClientOnly, - MultiValue, - Comment{ - "Read auth tokens from file to accept endpoint auth", - "Can be provided multiple times", - }, - [this](fs::path arg) { - if (not fs::exists(arg)) - throw std::invalid_argument{ - fmt::format("cannot load auth file {}: file does not exist", arg)}; - m_AuthFiles.emplace(std::move(arg)); - }); - conf.defineOption( - "network", - "auth-file-type", - ClientOnly, - Comment{ - "How to interpret the contents of an auth file.", - "Possible values: hashes, plaintext", - }, - [this](std::string arg) { m_AuthFileType = service::ParseAuthFileType(std::move(arg)); }); - - conf.defineOption( - "network", - "auth-static", - ClientOnly, - MultiValue, - Comment{ - "Manually add a static auth code to accept for endpoint auth", - "Can be provided multiple times", - }, - [this](std::string arg) { m_AuthStaticTokens.emplace(std::move(arg)); }); - - conf.defineOption( - "network", - "reachable", - ClientOnly, - ReachableDefault, - AssignmentAcceptor(m_reachable), - Comment{ - "Determines whether we will pubish our snapp's introset to the DHT.", - }); + conf.define_option( + "router", + "public-port", + RelayOnly, + Comment{ + "When specifying public-ip=, this specifies the public UDP port at which this " + "lokinet", + "router is reachable. Required when public-ip is used.", + }, + [this](uint16_t arg) { + if (arg <= 0 || arg > std::numeric_limits::max()) + throw std::invalid_argument("public-port must be >= 0 and <= 65536"); + public_port = arg; + }); + + conf.define_option( + "router", + "worker-threads", + DefaultWorkerThreads, + Comment{ + "The number of threads available for performing cryptographic functions.", + "The minimum is one thread, but network performance may increase with more.", + "threads. Should not exceed the number of logical CPU cores.", + "0 means use the number of logical CPU cores detected at startup.", + }, + [this](int arg) { + if (arg < 0) + throw std::invalid_argument("worker-threads must be >= 0"); + + worker_threads = arg; + }); + + // Hidden option because this isn't something that should ever be turned off occasionally + // when doing dev/testing work. + conf.define_option( + "router", "block-bogons", DefaultBlockBogons, Hidden, assignment_acceptor(block_bogons)); + + constexpr auto relative_to_datadir = "An absolute path is used as-is, otherwise relative to 'data-dir'."; + + conf.define_option( + "router", + "contact-file", + RelayOnly, + [this](std::string arg) { + if (arg.empty()) + return; + + rc_file = arg; + if (check_path_op(rc_file)) + log::info(logcat, "Relay configured to try RC file path: {}", rc_file->c_str()); + else + log::warning(logcat, "Bad input for relay RC file path ({}), using default...", arg); + }, + Comment{ + "Filename in which to store the router contact file", + relative_to_datadir, + }); + + conf.define_option("router", "encryption-privkey", Deprecated); + + conf.define_option("router", "ident-privkey", Deprecated); + + conf.define_option("router", "transport-privkey", RelayOnly, Deprecated); + + // Deprecated options: + + // these weren't even ever used! + conf.define_option("router", "max-routers", Deprecated); + conf.define_option("router", "min-routers", Deprecated); + + // TODO: this may have been a synonym for [router]worker-threads + conf.define_option("router", "threads", Deprecated); + conf.define_option("router", "net-threads", Deprecated); + + is_relay = params.is_relay; + } - conf.defineOption( - "network", - "hops", - HopsDefault, - Comment{ - "Number of hops in a path. Min 1, max 8.", - }, - [this](int arg) { - if (arg < 1 or arg > 8) - throw std::invalid_argument("[endpoint]:hops must be >= 1 and <= 8"); - m_Hops = arg; - }); + void NetworkConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) + { + (void)params; + + static constexpr Default ProfilingValueDefault{true}; + static constexpr Default SaveProfilesDefault{true}; + static constexpr Default ReachableDefault{true}; + static constexpr Default HopsDefault{4}; + static constexpr Default PathsDefault{4}; + static constexpr Default IP6RangeDefault{"[fd00::]/16"}; + + conf.define_option( + "network", "save-profiles", SaveProfilesDefault, Hidden, assignment_acceptor(save_profiles)); + + conf.define_option( + "network", "profiling", ProfilingValueDefault, Hidden, assignment_acceptor(enable_profiling)); + + conf.define_option("network", "profiles", Deprecated); + + conf.define_option( + "network", + "strict-connect", + ClientOnly, + MultiValue, + [this](std::string value) { + RouterID router; + if (not router.from_relay_address(value)) + throw std::invalid_argument{"bad snode value: " + value}; + if (not strict_connect.insert(router).second) + throw std::invalid_argument{"duplicate strict connect snode: " + value}; + }, + Comment{ + "Public keys of routers which will act as pinned first-hops. This may be used to", + "provide a trusted router (consider that you are not fully anonymous with your", + "first hop). This REQUIRES two or more nodes to be specified.", + }); + + conf.define_option( + "network", + "keyfile", + ClientOnly, + [this](std::string arg) { + if (arg.empty()) + return; + + keyfile = arg; + + if (check_path_op(keyfile)) + log::info(logcat, "Client configured to try private key file at path: {}", keyfile->c_str()); + else + log::warning(logcat, "Bad input for client private key file ({}); using ephemeral...", arg); + }, + Comment{ + "The private key to persist address with. If not specified the address will be", + "ephemerally generated.", + }); + + conf.define_option( + "network", + "auth-type", + ClientOnly, + Comment{ + "Set the endpoint authentication type.", + "none/whitelist/lmq/file", + }, + [this](std::string arg) { + if (arg.empty()) + return; + auth_type = parse_auth_type(arg); + }); + + conf.define_option( + "network", + "omq-auth-endpoint", + ClientOnly, + assignment_acceptor(auth_endpoint), + Comment{ + "OMQ endpoint to talk to for authenticating new sessions", + "ipc:///var/lib/lokinet/auth.socket", + "tcp://127.0.0.1:5555", + }); + + conf.define_option( + "network", + "omq-auth-method", + ClientOnly, + Default{"llarp.auth"}, + Comment{ + "OMQ function to call for authenticating new sessions", + "llarp.auth", + }, + [this](std::string arg) { + if (arg.empty()) + return; + auth_method = std::move(arg); + }); + + conf.define_option( + "network", + "auth-whitelist", + ClientOnly, + MultiValue, + Comment{ + "manually add a remote endpoint by .loki address to the access whitelist", + }, + [this](std::string arg) { + if (auto addr = NetworkAddress::from_network_addr(arg)) + auth_whitelist.emplace(std::move(*addr)); + else + throw std::invalid_argument{"bad loki address: {}"_format(arg)}; + }); + + conf.define_option( + "network", + "auth-file", + ClientOnly, + MultiValue, + Comment{ + "Read auth tokens from file to accept endpoint auth", + "Can be provided multiple times", + }, + [this](fs::path arg) { + if (not fs::exists(arg)) + throw std::invalid_argument{"cannot load auth file {}: file does not exist"_format(arg)}; + auth_files.emplace(std::move(arg)); + }); + + conf.define_option( + "network", + "auth-file-type", + ClientOnly, + Comment{ + "How to interpret the contents of an auth file.", + "Possible values: hashes, plaintext", + }, + [this](std::string arg) { auth_file_type = parse_auth_file_type(std::move(arg)); }); + + conf.define_option( + "network", + "auth-static", + ClientOnly, + MultiValue, + Comment{ + "Manually add a static auth code to accept for endpoint auth", + "Can be provided multiple times", + }, + [this](std::string arg) { auth_static_tokens.emplace(std::move(arg)); }); + + conf.define_option( + "network", + "reachable", + ClientOnly, + ReachableDefault, + assignment_acceptor(is_reachable), + Comment{ + "Determines whether we will pubish our service's introset to the DHT.", + }); + + conf.define_option( + "network", + "hops", + HopsDefault, + Comment{ + "Number of hops in a path. Min 1, max 8.", + }, + [this](int arg) { + if (arg < 1 or arg > 8) + throw std::invalid_argument("[endpoint]:hops must be >= 1 and <= 8"); + hops = arg; + }); + + conf.define_option( + "network", + "paths", + ClientOnly, + PathsDefault, + Comment{ + "Number of paths to maintain at any given time.", + }, + [this](int arg) { + if (arg < 3 or arg > 8) + throw std::invalid_argument("[endpoint]:paths must be >= 3 and <= 8"); + paths = arg; + }); + + conf.define_option( + "network", + "exit", + ClientOnly, + Default{false}, + assignment_acceptor(allow_exit), + Comment{ + "Whether or not we should act as an exit node. Beware that this increases demand", + "on the server and may pose liability concerns. Enable at your own risk.", + }); + + conf.define_option( + "network", + "routed-range", + MultiValue, + Comment{ + "When in exit mode announce one or more IP ranges that this exit node routes", + "traffic for. If omitted, the default is all public ranges. Can be set to", + "public to indicate that this exit routes traffic to the public internet.", + "For example:", + " routed-range=10.0.0.0/16", + " routed-range=public", + "to advertise that this exit routes traffic to both the public internet, and to", + "10.0.x.y addresses.", + "", + "Note that this option does not automatically configure network routing; that", + "must be configured separately on the exit system to handle lokinet traffic.", + }, + [this](std::string arg) { + if (auto range = IPRange::from_string(arg)) + _routed_ranges.insert(std::move(*range)); + else + throw std::invalid_argument{"Bad IP range passed to routed-range:{}"_format(arg)}; + }); + + conf.define_option( + "network", + "traffic-whitelist", + MultiValue, + Comment{ + "Adds an IP traffic type whitelist; can be specified multiple times. If any are", + "specified then only matched traffic will be allowed and all other traffic will be", + "dropped. Examples:", + " traffic-whitelist=tcp", + "would allow all TCP/IP packets (regardless of port);", + " traffic-whitelist=0x69", + "would allow IP traffic with IP protocol 0x69;", + " traffic-whitelist=udp/53", + "would allow UDP port 53; and", + " traffic-whitelist=tcp/smtp", + "would allow TCP traffic on the standard smtp port (21).", + }, + [this](std::string arg) { + if (not traffic_policy) + traffic_policy = net::TrafficPolicy{}; + + // this will throw on error + traffic_policy->protocols.emplace(arg); + }); + + conf.define_option( + "network", + "exit-node", + ClientOnly, + MultiValue, + Comment{ + "Specify a `.loki` address and an ip range to use as an exit broker.", + "Examples:", + " exit-node=whatever.loki", + "would route all exit traffic through whatever.loki; and", + " exit-node=stuff.loki:100.0.0.0/24", + "would route the IP range 100.0.0.0/24 through stuff.loki.", + "This option can be specified multiple times (to map different IP ranges).", + }, + [this](std::string arg) { + if (arg.empty()) + return; + + std::optional range; + + const auto pos = arg.find(":"); + + std::string input = (pos == std::string::npos) ? "0.0.0.0/0"s : arg.substr(pos + 1); + + range = IPRange::from_string(std::move(input)); + + if (not range.has_value()) + throw std::invalid_argument("[network]:exit-node invalid ip range for exit provided"); + + if (pos != std::string::npos) + arg = arg.substr(0, pos); + + if (service::is_valid_ons(arg)) + _ons_ranges.emplace(std::move(arg), std::move(*range)); + else if (auto maybe_raddr = NetworkAddress::from_network_addr(arg); maybe_raddr) + _exit_ranges.emplace(std::move(*maybe_raddr), std::move(*range)); + else + throw std::invalid_argument{"[network]:exit-node bad address: {}"_format(arg)}; + }); + + conf.define_option( + "network", + "exit-auth", + ClientOnly, + MultiValue, + Comment{ + "Specify an optional authentication code required to use a non-public exit node.", + "For example:", + " exit-auth=myfavouriteexit.loki:abc", + "uses the authentication code `abc` whenever myfavouriteexit.loki is accessed.", + "Can be specified multiple times to store codes for different exit nodes.", + }, + [this](std::string arg) { + if (arg.empty()) + throw std::invalid_argument{"Empty argument passed to 'exit-auth'"}; + + const auto pos = arg.find(":"); + + if (pos == std::string::npos) + { + throw std::invalid_argument( + "[network]:exit-auth invalid format, expects exit-address.loki:auth-code-goes-here"); + } + + const auto addr = arg.substr(0, pos); + auto auth = arg.substr(pos + 1); + + if (service::is_valid_ons(addr)) + { + ons_exit_auths.emplace(std::move(addr), std::move(auth)); + } + else if (auto exit = NetworkAddress::from_network_addr(addr); exit->is_client()) + { + exit_auths.emplace(std::move(*exit), std::move(auth)); + } + else + throw std::invalid_argument("[network]:exit-auth invalid exit address"); + }); + + conf.define_option( + "network", + "auto-routing", + ClientOnly, + Default{true}, + Comment{ + "Enable / disable automatic route configuration.", + "When this is enabled and an exit is used Lokinet will automatically configure the", + "operating system routes to route public internet traffic through the exit node.", + "This is enabled by default, but can be disabled if advanced/manual exit routing", + "configuration is desired."}, + assignment_acceptor(enable_route_poker)); + + conf.define_option( + "network", + "blackhole-routes", + ClientOnly, + Default{true}, + Comment{ + "Enable / disable route configuration blackholes.", + "When enabled lokinet will drop IPv4 and IPv6 traffic (when in exit mode) that is " + "not", + "handled in the exit configuration. Enabled by default."}, + assignment_acceptor(blackhole_routes)); + + conf.define_option( + "network", + "ifname", + Comment{ + "Interface name for lokinet traffic. If unset lokinet will look for a free name", + "matching 'lokitunN', starting at N=0 (e.g. lokitun0, lokitun1, ...).", + }, + assignment_acceptor(_if_name)); + + conf.define_option( + "network", + "ifaddr", + Comment{ + "Local IP and range for lokinet traffic. For example, 172.16.0.1/16 to use", + "172.16.0.1 for this machine and 172.16.x.y for remote peers. If omitted then", + "lokinet will attempt to find an unused private range.", + }, + [this](std::string arg) { + if (auto maybe_range = IPRange::from_string(arg); maybe_range) + { + log::critical(logcat, "Parsed local ip range from config: {}", *maybe_range); + _local_ip_range = *maybe_range; + _local_addr = _local_ip_range->address(); + log::critical(logcat, "Parsed local addr from config: {}", *_local_addr); + _local_base_ip = _local_ip_range->base_ip(); + } + else + throw std::invalid_argument{"[network]:ifaddr invalid value: '{}'"_format(arg)}; + }); + + conf.define_option( + "network", + "enable-ipv6-tun", + Default{false}, + assignment_acceptor(enable_ipv6), + Comment{"Enable IPv6 addressing for lokinet virtual TUN device interface (default: off)"}); + + conf.define_option( + "network", + "ip6-range", + ClientOnly, + Comment{ + "For all IPv6 exit traffic you will use this as the base address bitwised or'd " + "with the v4 address in use.To disable ipv6 set this to an empty value.", + "!!! WARNING !!! Disabling ipv6 tunneling when you have ipv6 routes WILL lead to ", + "de-anonymization as lokinet will no longer carry your ipv6 traffic.", + }, + IP6RangeDefault, + [this](std::string arg) { + if (arg.empty()) + { + log::warning( + logcat, + "!!! Disabling ipv6 tunneling when you have ipv6 routes WILL lead to de-anonymization as " + "lokinet will no longer carry your ipv6 traffic !!!"); + return; + } + + if (not _base_ipv6_range->from_string(arg)) + { + throw std::invalid_argument{"[network]:ip6-range invalid value: '{}'"_format(arg)}; + } + }); + + conf.define_option( + "network", + "mapaddr", + ClientOnly, + MultiValue, + Comment{ + "Map a remote `.loki` address to always use a fixed local IP. For example:", + " mapaddr=.loki:172.16.0.10", + "maps `.loki` to `172.16.0.10` instead of using the next available IP.", + "The given IP address must be inside the range configured by ifaddr=, and the", + "remote `.loki` cannot be an ONS address"}, + [this](std::string arg) { + if (arg.empty()) + return; + + const auto pos = arg.find(":"); + + if (pos == std::string::npos) + throw std::invalid_argument{"[endpoint]:mapaddr invalid entry: {}"_format(arg)}; + + auto addr_arg = arg.substr(0, pos); + auto ip_arg = arg.substr(pos + 1); + + if (service::is_valid_ons(addr_arg)) + throw std::invalid_argument{"`mapaddr` cannot take an ONS entry: {}"_format(arg)}; + + if (auto maybe_raddr = NetworkAddress::from_network_addr(std::move(addr_arg)); maybe_raddr) + { + ip_v ipv; + // ipv6 + if (ip_arg.find(':') != std::string_view::npos) + ipv = ipv6{std::move(ip_arg)}; + else + ipv = ipv4{std::move(ip_arg)}; + + _reserved_local_ips.emplace(std::move(*maybe_raddr), std::move(ipv)); + } + else + throw std::invalid_argument{"[endpoint]:mapaddr invalid entry: {}"_format(arg)}; + }); + + conf.define_option( + "network", + "blacklist-snode", + ClientOnly, + MultiValue, + Comment{ + "Adds a lokinet relay `.snode` address to the list of relays to avoid when", + "building paths. Can be specified multiple times.", + }, + [this](std::string arg) { + RouterID id; + if (not id.from_relay_address(arg)) + throw std::invalid_argument{"Invalid RouterID: {}"_format(arg)}; + + auto itr = snode_blacklist.emplace(std::move(id)); + if (not itr.second) + throw std::invalid_argument{"Duplicate blacklist-snode: {}"_format(arg)}; + }); + + // TODO: support SRV records for routers, but for now client only + conf.define_option( + "network", + "srv", + ClientOnly, + MultiValue, + Comment{ + "Specify SRV Records for services hosted on the SNApp for protocols that use SRV", + "records for service discovery. Each line specifies a single SRV record as:", + " srv=_service._protocol priority weight port target.loki", + "and can be specified multiple times as needed.", + "For more info see", + "https://docs.oxen.io/products-built-on-oxen/lokinet/snapps/hosting-snapps", + "and general description of DNS SRV record configuration.", + }, + [this](std::string arg) { + auto maybe_srv = dns::SRVData::from_srv_string(arg); + + if (not maybe_srv) + throw std::invalid_argument{"Invalid SRV Record string: {}"_format(arg)}; + + srv_records.push_back(std::move(*maybe_srv)); + }); + + conf.define_option( + "network", + "path-alignment-timeout", + ClientOnly, + Comment{ + "How long to wait (in seconds) for a path to align to a pivot router when " + "establishing", + "a path through the network to a remote .loki address.", + }, + [this](int val) { + if (val <= 0) + throw std::invalid_argument{"invalid path alignment timeout: " + std::to_string(val) + " <= 0"}; + path_alignment_timeout = std::chrono::seconds{val}; + }); + + constexpr auto addrmap_errorstr = "Invalid entry in persist-addrmap-file:"sv; + + conf.define_option( + "network", + "persist-addrmap-file", + ClientOnly, + Default{fs::path{params.default_data_dir / "addrmap.dat"}}, + Comment{ + "If given this specifies a file in which to record mapped local tunnel addresses so", + "the same local address will be used for the same lokinet address on reboot. If this", + "is not specified then the local IP of remote lokinet targets will not persist across", + "restarts of lokinet.", + }, + [this, &addrmap_errorstr](fs::path file) { + if (file.empty()) + throw std::invalid_argument("persist-addrmap-file cannot be empty"); + + if (not fs::exists(file)) + throw std::invalid_argument("persist-addrmap-file path invalid: {}"_format(file)); + + bool load_file = true; + { + constexpr auto ADDR_PERSIST_MODIFY_WINDOW = 1min; + const auto last_write_time = fs::last_write_time(file); + const auto now = decltype(last_write_time)::clock::now(); + + if (now < last_write_time or now - last_write_time > ADDR_PERSIST_MODIFY_WINDOW) + { + load_file = false; + } + } + + std::vector data; + + if (auto maybe = util::OpenFileStream(file, std::ios_base::binary); maybe and load_file) + { + log::debug(logcat, "Config loading persisting address map file from path:{}", file); + + maybe->seekg(0, std::ios_base::end); + const size_t len = maybe->tellg(); + + maybe->seekg(0, std::ios_base::beg); + data.resize(len); + + log::trace(logcat, "Config reading {}B", len); + + maybe->read(data.data(), data.size()); + } + else + { + auto err = "Config could not load persisting address map file from path:{}"_format(file); + + log::warning(logcat, "{} {}", err, load_file ? "NOT FOUND" : "STALE"); + } + if (not data.empty()) + { + std::string_view bdata{data.data(), data.size()}; + + log::trace(logcat, "Config parsing address map data: {}", bdata); + + const auto parsed = oxenc::bt_deserialize(bdata); + + for (const auto& [key, value] : parsed) + { + try + { + oxen::quic::Address addr{key, 0}; + + ip_v _ip; + + if (addr.is_ipv4()) + _ip = addr.to_ipv4(); + else + _ip = addr.to_ipv6(); + + if (_ip == _local_base_ip) + continue; + + if (not _local_ip_range->contains(_ip)) + { + log::warning( + logcat, + "{}: {}", + addrmap_errorstr, + "out of range IP! (local range:{}, IP:{})"_format(*_local_ip_range, addr.host())); + continue; + } + + const auto* arg = std::get_if(&value); + + if (not arg) + { + log::warning(logcat, "{}: {}", addrmap_errorstr, "not a string!"); + continue; + } + + if (service::is_valid_ons(*arg)) + { + log::warning(logcat, "{}: {}", addrmap_errorstr, "cannot accept ONS names!"); + continue; + } + + if (auto maybe_netaddr = NetworkAddress::from_network_addr(*arg)) + { + _reserved_local_ips.emplace(std::move(*maybe_netaddr), std::move(_ip)); + } + else + log::warning(logcat, "{}: {}", addrmap_errorstr, *arg); + } + catch (const std::exception& e) + { + log::warning( + logcat, + "Exception caught parsing key:value (key:{}) pair in addr persist file:{}", + key, + e.what()); + } + } + } + + addr_map_persist_file = file; + }); + + // Deprecated options: + conf.define_option("network", "enabled", Deprecated); + } - conf.defineOption( - "network", - "paths", - ClientOnly, - PathsDefault, - Comment{ - "Number of paths to maintain at any given time.", - }, - [this](int arg) { - if (arg < 3 or arg > 8) - throw std::invalid_argument("[endpoint]:paths must be >= 3 and <= 8"); - m_Paths = arg; - }); + void DnsConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) + { + (void)params; - conf.defineOption( - "network", - "exit", - ClientOnly, - Default{false}, - AssignmentAcceptor(m_AllowExit), - Comment{ - "Whether or not we should act as an exit node. Beware that this increases demand", - "on the server and may pose liability concerns. Enable at your own risk.", - }); + // Most non-linux platforms have loopback as 127.0.0.1/32, but linux uses 127.0.0.1/8 so + // that we can bind to other 127.* IPs to avoid conflicting with something else that may be + // listening on 127.0.0.1:53. + constexpr std::array DefaultDNSBind{ +#ifdef __linux__ +#ifdef WITH_SYSTEMD + // when we have systemd support add a random high port on loopback as well + // see https://github.com/oxen-io/lokinet/issues/1887#issuecomment-1091897282 + Default{"127.0.0.1:0"}, +#endif + Default{"127.3.2.1:53"}, +#else + Default{"127.0.0.1:53"}, +#endif + }; - conf.defineOption( - "network", - "owned-range", - MultiValue, - Comment{ - "When in exit mode announce we allow a private range in our introset. For example:", - " owned-range=10.0.0.0/24", - }, - [this](std::string arg) { - IPRange range; - if (not range.FromString(arg)) - throw std::invalid_argument{"bad ip range: '" + arg + "'"}; - m_OwnedRanges.insert(range); - }); + auto parse_addr_for_dns = [](const std::string& arg) { + std::optional addr = std::nullopt; + std::string_view arg_v{arg}, port; + std::string host; + uint16_t p{DEFAULT_DNS_PORT}; - conf.defineOption( - "network", - "traffic-whitelist", - MultiValue, - Comment{ - "Adds an IP traffic type whitelist; can be specified multiple times. If any are", - "specified then only matched traffic will be allowed and all other traffic will be", - "dropped. Examples:", - " traffic-whitelist=tcp", - "would allow all TCP/IP packets (regardless of port);", - " traffic-whitelist=0x69", - "would allow IP traffic with IP protocol 0x69;", - " traffic-whitelist=udp/53", - "would allow UDP port 53; and", - " traffic-whitelist=tcp/smtp", - "would allow TCP traffic on the standard smtp port (21).", - }, - [this](std::string arg) { - if (not m_TrafficPolicy) - m_TrafficPolicy = net::TrafficPolicy{}; - - // this will throw on error - m_TrafficPolicy->protocols.emplace(arg); - }); + if (auto pos = arg_v.find(':'); pos != arg_v.npos) + { + host = arg_v.substr(0, pos); + port = arg_v.substr(pos + 1); - conf.defineOption( - "network", - "exit-node", - ClientOnly, - MultiValue, - Comment{ - "Specify a `.loki` address and an optional ip range to use as an exit broker.", - "Examples:", - " exit-node=whatever.loki", - "would map all exit traffic through whatever.loki; and", - " exit-node=stuff.loki:100.0.0.0/24", - "would map the IP range 100.0.0.0/24 through stuff.loki.", - "This option can be specified multiple times (to map different IP ranges).", - }, - [this](std::string arg) { - if (arg.empty()) - return; - service::Address exit; - IPRange range; - const auto pos = arg.find(":"); - if (pos == std::string::npos) - { - range.FromString("::/0"); - } - else if (not range.FromString(arg.substr(pos + 1))) - { - throw std::invalid_argument("[network]:exit-node invalid ip range for exit provided"); - } - if (pos != std::string::npos) - { - arg = arg.substr(0, pos); - } - - if (service::NameIsValid(arg)) - { - m_LNSExitMap.Insert(range, arg); - return; - } + if (not llarp::parse_int(port, p)) + log::info(logcat, "Failed to parse port in arg:{}, defaulting to DNS port 53", port); - if (arg != "null" and not exit.FromString(arg)) - { - throw std::invalid_argument{fmt::format("[network]:exit-node bad address: {}", arg)}; - } - m_ExitMap.Insert(range, exit); - }); + addr = oxen::quic::Address{host, p}; + } - conf.defineOption( - "network", - "exit-auth", - ClientOnly, - MultiValue, - Comment{ - "Specify an optional authentication code required to use a non-public exit node.", - "For example:", - " exit-auth=myfavouriteexit.loki:abc", - "uses the authentication code `abc` whenever myfavouriteexit.loki is accessed.", - "Can be specified multiple times to store codes for different exit nodes.", - }, - [this](std::string arg) { - if (arg.empty()) - return; - service::Address exit; - service::AuthInfo auth; - const auto pos = arg.find(":"); - if (pos == std::string::npos) - { - throw std::invalid_argument( - "[network]:exit-auth invalid format, expects " - "exit-address.loki:auth-code-goes-here"); - } - const auto exit_str = arg.substr(0, pos); - auth.token = arg.substr(pos + 1); - - if (service::NameIsValid(exit_str)) - { - m_LNSExitAuths.emplace(exit_str, auth); - return; - } + return addr; + }; + + conf.define_option( + "dns", + "upstream", + MultiValue, + Comment{ + "Upstream resolver(s) to use as fallback for non-loki addresses.", + "Multiple values accepted.", + }, + [this, parse_addr_for_dns](std::string arg) mutable { + if (not arg.empty()) + { + if (auto maybe_addr = parse_addr_for_dns(arg)) + _upstream_dns.push_back(std::move(*maybe_addr)); + else + log::warning(logcat, "Failed to parse upstream DNS resolver address:{}", arg); + } + }); + + conf.define_option( + "dns", + "l3-intercept", + Default{ + platform::is_windows or platform::is_android or (platform::is_macos and not platform::is_apple_sysex)}, + Comment{"Intercept all dns traffic (udp/53) going into our lokinet network interface " + "instead of binding a local udp socket"}, + assignment_acceptor(l3_intercept)); + + conf.define_option( + "dns", + "query-bind", +#if defined(_WIN32) + Default{"0.0.0.0:0"}, +#else + Hidden, +#endif + Comment{ + "Address to bind to for sending upstream DNS requests.", + }, + [this, parse_addr_for_dns](std::string arg) mutable { + if (not arg.empty()) + { + if (auto maybe_addr = parse_addr_for_dns(arg)) + _query_bind = std::move(*maybe_addr); + else + log::warning(logcat, "Failed to parse bind address for DNS queries:{}", arg); + } + }); + + conf.define_option( + "dns", + "bind", + DefaultDNSBind, + MultiValue, + Comment{ + "Address to bind to for handling DNS requests.", + }, + [this, parse_addr_for_dns](std::string arg) mutable { + if (not arg.empty()) + { + if (auto maybe_addr = parse_addr_for_dns(arg)) + { + _bind_addrs.push_back(std::move(*maybe_addr)); + } + else + log::warning(logcat, "Failed to parse bind address for handling DNS requests:{}", arg); + } + }); + + conf.define_option( + "dns", + "add-hosts", + ClientOnly, + Comment{"Add a hosts file to the dns resolver", "For use with client side dns filtering"}, + [=, this](fs::path path) { + if (path.empty()) + return; + if (not fs::exists(path)) + throw std::invalid_argument{"cannot add hosts file {} as it does not exist"_format(path)}; + hostfiles.emplace_back(std::move(path)); + }); + + // Ignored option (used by the systemd service file to disable resolvconf configuration). + conf.define_option( + "dns", + "no-resolvconf", + ClientOnly, + Comment{ + "Can be uncommented and set to 1 to disable resolvconf configuration of lokinet " + "DNS.", + "(This is not used directly by lokinet itself, but by the lokinet init scripts", + "on systems which use resolveconf)", + }); + + // forward the rest to libunbound + conf.add_undeclared_handler( + "dns", [this](auto, std::string_view key, std::string_view val) { extra_opts.emplace(key, val); }); + } - if (not exit.FromString(exit_str)) - { - throw std::invalid_argument("[network]:exit-auth invalid exit address"); - } - m_ExitAuths.emplace(exit, auth); - }); + void LinksConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) + { + conf.add_section_comments( + "bind", + { + "This section allows specifying the IPs that lokinet uses for incoming and " + "outgoing", + "connections. For simple setups it can usually be left blank, but may be required", + "for routers with multiple IPs, or routers that must listen on a private IP with", + "forwarded public traffic. It can also be useful for clients that want to use a", + "consistent outgoing port for which firewall rules can be configured.", + }); + + const auto* net_ptr = params.net_ptr(); + + conf.define_option( + "bind", + "public-ip", + Hidden, + RelayOnly, + Comment{ + "The IP address to advertise to the network instead of the incoming= or " + "auto-detected", + "IP. This is typically required only when incoming= is used to listen on an " + "internal", + "private range IP address that received traffic forwarded from the public IP.", + }, + [this](std::string arg) { + public_addr = std::move(arg); + log::warning( + logcat, + "Using deprecated option; pass this value to [Router]:public-ip instead " + "PLEASE"); + }); + + conf.define_option( + "bind", + "public-port", + Hidden, + RelayOnly, + Comment{ + "The port to advertise to the network instead of the incoming= (or default) port.", + "This is typically required only when incoming= is used to listen on an internal", + "private range IP address/port that received traffic forwarded from the public IP.", + }, + [this](uint16_t arg) { + if (arg <= 0 || arg > std::numeric_limits::max()) + throw std::invalid_argument("public-port must be >= 0 and <= 65536"); + public_port = arg; + log::warning( + logcat, + "Using deprecated option; pass this value to [Router]:public-port instead " + "PLEASE"); + }); + + auto parse_addr_for_link = [net_ptr](const std::string& arg, bool& given_port_only) { + std::optional maybe = std::nullopt; + std::string_view arg_v{arg}, host; + uint16_t p{}; + + if (auto pos = arg_v.find(':'); pos != arg_v.npos) + { + // host = arg_v.substr(0, pos); + log::critical(logcat, "Parsing input: {}", arg); + std::tie(host, p) = detail::parse_addr(arg_v, DEFAULT_LISTEN_PORT); + log::critical(logcat, "Parsed input = {}:{}", host, p); + } - conf.defineOption( - "network", - "auto-routing", - ClientOnly, - Default{true}, - Comment{ - "Enable / disable automatic route configuration.", - "When this is enabled and an exit is used Lokinet will automatically configure the", - "operating system routes to route public internet traffic through the exit node.", - "This is enabled by default, but can be disabled if advanced/manual exit routing", - "configuration is desired."}, - AssignmentAcceptor(m_EnableRoutePoker)); - - conf.defineOption( - "network", - "blackhole-routes", - ClientOnly, - Default{true}, - Comment{ - "Enable / disable route configuration blackholes.", - "When enabled lokinet will drop IPv4 and IPv6 traffic (when in exit mode) that is not", - "handled in the exit configuration. Enabled by default."}, - AssignmentAcceptor(m_BlackholeRoutes)); - - conf.defineOption( - "network", - "ifname", - Comment{ - "Interface name for lokinet traffic. If unset lokinet will look for a free name", - "matching 'lokitunN', starting at N=0 (e.g. lokitun0, lokitun1, ...).", - }, - AssignmentAcceptor(m_ifname)); - - conf.defineOption( - "network", - "ifaddr", - Comment{ - "Local IP and range for lokinet traffic. For example, 172.16.0.1/16 to use", - "172.16.0.1 for this machine and 172.16.x.y for remote peers. If omitted then", - "lokinet will attempt to find an unused private range.", - }, - [this](std::string arg) { - if (not m_ifaddr.FromString(arg)) - { - throw std::invalid_argument{fmt::format("[network]:ifaddr invalid value: '{}'", arg)}; - } - }); + if (host.empty()) + { + log::critical( + logcat, "Host value empty, port:{}{}", p, p == DEFAULT_LISTEN_PORT ? "(DEFAULT PORT)" : ""); + given_port_only = p != DEFAULT_LISTEN_PORT; + maybe = net_ptr->get_best_public_address(true, p); + } + else + maybe = oxen::quic::Address{std::string{host}, p}; + + if (maybe and maybe->is_loopback()) + throw std::invalid_argument{"{} is a loopback address"_format(arg)}; + + log::critical(logcat, "parsed address: {}", *maybe); + + return maybe; + }; + + conf.define_option( + "bind", + "listen", + Comment{ + "IP and/or port for lokinet to bind to for inbound/outbound connections.", + "", + "If IP is omitted then lokinet will search for a local network interface with a", + "public IP address and use that IP (and will exit with an error if no such IP is " + "found", + "on the system). If port is omitted then lokinet defaults to 1090.", + "", + "Note: only one address will be accepted. If this option is not specified, it " + "will ", + "default", + "to the inbound or outbound value. Conversely, specifying this option will " + "supercede ", + "the", + "deprecated inbound/outbound opts.", + "", + "Examples:", + " listen=15.5.29.5:443", + " listen=10.0.2.2", + " listen=:1234", + "", + "Using a private range IP address (like the second example entry) will require " + "using", + "the public-ip= and public-port= to specify the public IP address at which this", + "router can be reached.", + }, + [this, parse_addr_for_link](const std::string& arg) { + if (auto a = parse_addr_for_link(arg, only_user_port)) + { + if (not a->is_addressable()) + throw std::invalid_argument{"Listen address ({}) is not addressible!"_format(*a)}; + + listen_addr = *a; + using_new_api = true; + } + else + throw std::invalid_argument{"Could not parse listen address!"}; + }); + + conf.define_option( + "bind", "inbound", RelayOnly, MultiValue, Hidden, [this, parse_addr_for_link](const std::string& arg) { + if (using_new_api) + throw std::runtime_error{"USE THE NEW API -- SPECIFY LOCAL ADDRESS UNDER [LISTEN]"}; + + if (auto a = parse_addr_for_link(arg, only_user_port); a) + { + if (a->is_addressable() or (!a->is_any_port() and only_user_port)) + { + log::warning( + logcat, + "Loaded address {} from deprecated [inbound] options; update your config to " + "use " + "[bind]:listen instead PLEASE", + *a); + listen_addr = *a; + } + } + }); + + conf.define_option("bind", "outbound", MultiValue, Deprecated, Hidden); + + conf.add_undeclared_handler("bind", [this](std::string_view, std::string_view key, std::string_view val) { + if (using_new_api) + throw std::runtime_error{"USE THE NEW API -- SPECIFY LOCAL ADDRESS UNDER [LISTEN]"}; + + log::warning(logcat, "Please update your config to use [bind]:listen instead"); + + uint16_t port{0}; + + if (auto rv = llarp::parse_int(val, port); not rv) + throw std::runtime_error{"Could not parse port; stop using this deprecated handler"}; + + port = port == 0 ? DEFAULT_LISTEN_PORT : port; + + // special case: wildcard for outbound + if (key == "*") + { + log::warning( + logcat, + "Wildcat address referencing port {} is referencing deprecated outbound " + "config " + "options; use [bind]:listen instead", + port); + return; + } - conf.defineOption( - "network", - "ip6-range", - ClientOnly, - Comment{ - "For all IPv6 exit traffic you will use this as the base address bitwised or'd with ", - "the v4 address in use.", - "To disable ipv6 set this to an empty value.", - "!!! WARNING !!! Disabling ipv6 tunneling when you have ipv6 routes WILL lead to ", - "de-anonymization as lokinet will no longer carry your ipv6 traffic.", - }, - IP6RangeDefault, - [this](std::string arg) { - if (arg.empty()) - { - LogError( - "!!! Disabling ipv6 tunneling when you have ipv6 routes WILL lead to " - "de-anonymization as lokinet will no longer carry your ipv6 traffic !!!"); - m_baseV6Address = std::nullopt; - return; - } - m_baseV6Address = huint128_t{}; - if (not m_baseV6Address->FromString(arg)) - throw std::invalid_argument{ - fmt::format("[network]:ip6-range invalid value: '{}'", arg)}; - }); + oxen::quic::Address temp; - // TODO: could be useful for snodes in the future, but currently only implemented for clients: - conf.defineOption( - "network", - "mapaddr", - ClientOnly, - MultiValue, - Comment{ - "Map a remote `.loki` address to always use a fixed local IP. For example:", - " mapaddr=whatever.loki:172.16.0.10", - "maps `whatever.loki` to `172.16.0.10` instead of using the next available IP.", - "The given IP address must be inside the range configured by ifaddr=", - }, - [this](std::string arg) { - if (arg.empty()) - return; - huint128_t ip; - service::Address addr; - const auto pos = arg.find(":"); - if (pos == std::string::npos) - { - throw std::invalid_argument{fmt::format("[endpoint]:mapaddr invalid entry: {}", arg)}; - } - std::string addrstr = arg.substr(0, pos); - std::string ipstr = arg.substr(pos + 1); - if (not ip.FromString(ipstr)) - { - huint32_t ipv4; - if (not ipv4.FromString(ipstr)) + try { - throw std::invalid_argument{fmt::format("[endpoint]:mapaddr invalid ip: {}", ipstr)}; + temp = oxen::quic::Address{std::string{key}, port}; + } + catch (const std::exception& e) + { + throw std::runtime_error{ + "Could not parse address {}; please update your config to use " + "[bind]:listen " + "instead: {}"_format(key, e.what())}; } - ip = net::ExpandV4(ipv4); - } - if (not addr.FromString(addrstr)) - { - throw std::invalid_argument{ - fmt::format("[endpoint]:mapaddr invalid addresss: {}", addrstr)}; - } - if (m_mapAddrs.find(ip) != m_mapAddrs.end()) - { - throw std::invalid_argument{ - fmt::format("[endpoint]:mapaddr ip already mapped: {}", ipstr)}; - } - m_mapAddrs[ip] = addr; - }); - conf.defineOption( - "network", - "blacklist-snode", - ClientOnly, - MultiValue, - Comment{ - "Adds a lokinet relay `.snode` address to the list of relays to avoid when", - "building paths. Can be specified multiple times.", - }, - [this](std::string arg) { - RouterID id; - if (not id.FromString(arg)) - throw std::invalid_argument{fmt::format("Invalid RouterID: {}", arg)}; - - auto itr = m_snodeBlacklist.emplace(std::move(id)); - if (not itr.second) - throw std::invalid_argument{fmt::format("Duplicate blacklist-snode: {}", arg)}; - }); + if (not temp.is_addressable()) + { + throw std::runtime_error{ + "Invalid address: {}; stop using this deprecated handler, update your " + "config to " + "use " + "[bind]:listen instead PLEASE"_format(temp)}; + } - // TODO: support SRV records for routers, but for now client only - conf.defineOption( - "network", - "srv", - ClientOnly, - MultiValue, - Comment{ - "Specify SRV Records for services hosted on the SNApp for protocols that use SRV", - "records for service discovery. Each line specifies a single SRV record as:", - " srv=_service._protocol priority weight port target.loki", - "and can be specified multiple times as needed.", - "For more info see", - "https://docs.oxen.io/products-built-on-oxen/lokinet/snapps/hosting-snapps", - "and general description of DNS SRV record configuration.", - }, - [this](std::string arg) { - llarp::dns::SRVData newSRV; - if (not newSRV.fromString(arg)) - throw std::invalid_argument{fmt::format("Invalid SRV Record string: {}", arg)}; - - m_SRVRecords.push_back(std::move(newSRV)); + listen_addr = std::move(temp); + only_user_port = true; }); + } - conf.defineOption( - "network", - "path-alignment-timeout", - ClientOnly, - Comment{ - "How long to wait (in seconds) for a path to align to a pivot router when establishing", - "a path through the network to a remote .loki address.", - }, - [this](int val) { - if (val <= 0) - throw std::invalid_argument{ - "invalid path alignment timeout: " + std::to_string(val) + " <= 0"}; - m_PathAlignmentTimeout = std::chrono::seconds{val}; - }); + void ApiConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) + { + constexpr std::array DefaultRPCBind{ + Default{"tcp://127.0.0.1:1190"}, +#ifndef _WIN32 + Default{"ipc://rpc.sock"}, +#endif + }; + + conf.define_option( + "api", + "enabled", + Default{not params.is_relay}, + assignment_acceptor(enable_rpc_server), + Comment{ + "Determines whether or not the LMQ JSON API is enabled. Defaults ", + }); + + conf.define_option( + "api", + "bind", + DefaultRPCBind, + MultiValue, + [this, first = true](std::string arg) mutable { + if (first) + { + rpc_bind_addrs.clear(); + first = false; + } + if (arg.find("://") == std::string::npos) + { + arg = "tcp://" + arg; + } + rpc_bind_addrs.emplace_back(arg); + }, + Comment{ + "IP addresses and ports to bind to.", + "Recommend localhost-only for security purposes.", + }); + + conf.define_option("api", "authkey", Deprecated); + + // TODO: this was from pre-refactor: + // TODO: add pubkey to whitelist + } - conf.defineOption( - "network", - "persist-addrmap-file", - ClientOnly, - Default{fs::path{params.defaultDataDir / "addrmap.dat"}}, - Comment{ - "If given this specifies a file in which to record mapped local tunnel addresses so", - "the same local address will be used for the same lokinet address on reboot. If this", - "is not specified then the local IP of remote lokinet targets will not persist across", - "restarts of lokinet.", - }, - [this](fs::path arg) { - if (arg.empty()) - throw std::invalid_argument("persist-addrmap-file cannot be empty"); - m_AddrMapPersistFile = arg; + void LokidConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) + { + (void)params; + conf.define_option( + "lokid", + "disable-testing", + Default{false}, + Hidden, + RelayOnly, + Comment{"Development option: set to true to disable reachability testing when using ", "testnet"}, + assignment_acceptor(disable_testing)); + + conf.define_option( + "lokid", + "rpc", + RelayOnly, + Required, + Comment{ + "oxenmq control address for for communicating with oxend. Depends on oxend's", + "lmq-local-control configuration option. By default this value should be", + "ipc://OXEND-DATA-DIRECTORY/oxend.sock, such as:", + " rpc=ipc:///var/lib/oxen/oxend.sock", + " rpc=ipc:///home/USER/.oxen/oxend.sock", + "but can use (non-default) TCP if oxend is configured that way:", + " rpc=tcp://127.0.0.1:5678", + }, + [this](std::string arg) { rpc_addr = oxenmq::address(arg); }); + + // Deprecated options: + conf.define_option("lokid", "jsonrpc", RelayOnly, Hidden, [](std::string arg) { + if (arg.empty()) + return; + throw std::invalid_argument( + "the [lokid]:jsonrpc option is no longer supported; please use the [lokid]:rpc " + "config " + "option instead with oxend's lmq-local-control address -- typically a value such " + "as " + "rpc=ipc:///var/lib/oxen/oxend.sock or rpc=ipc:///home/snode/.oxen/oxend.sock"); }); + conf.define_option("lokid", "enabled", RelayOnly, Deprecated); + conf.define_option("lokid", "username", Deprecated); + conf.define_option("lokid", "password", Deprecated); + conf.define_option("lokid", "service-node-seed", Deprecated); + } - // Deprecated options: - conf.defineOption("network", "enabled", Deprecated); - } + void BootstrapConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) + { + (void)params; + + conf.define_option( + "bootstrap", + "seed-node", + Default{false}, + Comment{"Whether or not to run as a seed node. We will not have any bootstrap routers " + "configured."}, + assignment_acceptor(seednode)); + + conf.define_option( + "bootstrap", + "add-node", + MultiValue, + Comment{ + "Specify a bootstrap file containing a list of signed RouterContacts of service " + "nodes", + "which can act as a bootstrap. Can be specified multiple times.", + }, + [this](std::string arg) { + if (arg.empty()) + throw std::invalid_argument("cannot use empty filename as bootstrap"); + + files.emplace_back(std::move(arg)); + + if (not fs::exists(files.back())) + throw std::invalid_argument("file does not exist: " + arg); + }); + } - void - DnsConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) - { - (void)params; + void LoggingConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) + { + (void)params; + + constexpr Default DefaultLogType{platform::is_android or platform::is_apple ? "system" : "print"}; + constexpr Default DefaultLogFile{""}; + + const Default DefaultLogLevel{params.is_relay ? "warn" : "info"}; + + conf.define_option( + "logging", + "type", + DefaultLogType, + [this](std::string arg) { type = log::type_from_string(arg); }, + Comment{ + "Log type (format). Valid options are:", + " print - print logs to standard output", + " system - logs directed to the system logger (syslog/eventlog/etc.)", + " file - plaintext formatting to a file", + }); + + conf.define_option( + "logging", + "level", + DefaultLogLevel, + [this](std::string arg) { level = log::level_from_string(arg); }, + Comment{ + "Minimum log level to print. Logging below this level will be ignored.", + "Valid log levels, in ascending order, are:", + " trace", + " debug", + " info", + " warn", + " error", + " critical", + " none", + }); + + conf.define_option( + "logging", + "file", + DefaultLogFile, + assignment_acceptor(file), + Comment{ + "When using type=file this is the output filename.", + }); + } - // Most non-linux platforms have loopback as 127.0.0.1/32, but linux uses 127.0.0.1/8 so that we - // can bind to other 127.* IPs to avoid conflicting with something else that may be listening on - // 127.0.0.1:53. - constexpr std::array DefaultDNSBind{ -#ifdef __linux__ -#ifdef WITH_SYSTEMD - // when we have systemd support add a random high port on loopback as well - // see https://github.com/oxen-io/lokinet/issues/1887#issuecomment-1091897282 - Default{"127.0.0.1:0"}, -#endif - Default{"127.3.2.1:53"}, -#else - Default{"127.0.0.1:53"}, -#endif - }; - - // Default, but if we get any upstream (including upstream=, i.e. empty string) we clear it - constexpr Default DefaultUpstreamDNS{"9.9.9.10:53"}; - m_upstreamDNS.emplace_back(DefaultUpstreamDNS.val); - - conf.defineOption( - "dns", - "upstream", - MultiValue, - Comment{ - "Upstream resolver(s) to use as fallback for non-loki addresses.", - "Multiple values accepted.", - }, - [=, first = true](std::string arg) mutable { - if (first) - { - m_upstreamDNS.clear(); - first = false; - } - if (not arg.empty()) - { - auto& entry = m_upstreamDNS.emplace_back(std::move(arg)); - if (not entry.getPort()) - entry.setPort(53); - } - }); + void PeerSelectionConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) + { + (void)params; + + constexpr Default DefaultUniqueCIDR{32}; + conf.define_option( + "paths", + "unique-range-size", + DefaultUniqueCIDR, + ClientOnly, + [=, this](int arg) { + if (arg > 32 or arg < 4) + throw std::invalid_argument{"[paths]:unique-range-size must be between 4 and 32"}; + + unique_hop_netmask = static_cast(arg); + }, + Comment{ + "Netmask for router path selection; each router must be from a distinct IPv4 " + "subnet", + "of the given size.", + "E.g. 16 ensures that all routers are using IPs from distinct /16 IP ranges."}); - conf.defineOption( - "dns", - "l3-intercept", - Default{ - platform::is_windows or platform::is_android - or (platform::is_macos and not platform::is_apple_sysex)}, - Comment{"Intercept all dns traffic (udp/53) going into our lokinet network interface " - "instead of binding a local udp socket"}, - AssignmentAcceptor(m_raw_dns)); - - conf.defineOption( - "dns", - "query-bind", -#if defined(_WIN32) - Default{"0.0.0.0:0"}, -#else - Hidden, +#ifdef WITH_GEOIP + conf.defineOption( + "paths", + "exclude-country", + ClientOnly, + MultiValue, + [=](std::string arg) { m_ExcludeCountries.emplace(lowercase_ascii_string(std::move(arg))); }, + Comment{ + "Exclude a country given its 2 letter country code from being used in path builds.", + "For example:", + " exclude-country=DE", + "would avoid building paths through routers with IPs in Germany.", + "This option can be specified multiple times to exclude multiple countries"}); #endif - Comment{ - "Address to bind to for sending upstream DNS requests.", - }, - [this](std::string arg) { m_QueryBind = SockAddr{arg}; }); - - conf.defineOption( - "dns", - "bind", - DefaultDNSBind, - MultiValue, - Comment{ - "Address to bind to for handling DNS requests.", - }, - [=](std::string arg) { - SockAddr addr{arg}; - // set dns port if no explicit port specified - // explicit :0 allowed - if (not addr.getPort() and not ends_with(arg, ":0")) - addr.setPort(53); - m_bind.emplace_back(addr); - }); - - conf.defineOption( - "dns", - "add-hosts", - ClientOnly, - Comment{"Add a hosts file to the dns resolver", "For use with client side dns filtering"}, - [=](fs::path path) { - if (path.empty()) - return; - if (not fs::exists(path)) - throw std::invalid_argument{ - fmt::format("cannot add hosts file {} as it does not exist", path)}; - m_hostfiles.emplace_back(std::move(path)); - }); + } - // Ignored option (used by the systemd service file to disable resolvconf configuration). - conf.defineOption( - "dns", - "no-resolvconf", - ClientOnly, - Comment{ - "Can be uncommented and set to 1 to disable resolvconf configuration of lokinet DNS.", - "(This is not used directly by lokinet itself, but by the lokinet init scripts", - "on systems which use resolveconf)", - }); + bool PeerSelectionConfig::check_rcs(const std::set& rcs) const + { + if (unique_hop_netmask == 0) + return true; - // forward the rest to libunbound - conf.addUndeclaredHandler("dns", [this](auto, std::string_view key, std::string_view val) { - m_ExtraOpts.emplace(key, val); - }); - } - - void - LinksConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) - { - conf.addSectionComments( - "bind", - { - "This section allows specifying the IPs that lokinet uses for incoming and outgoing", - "connections. For simple setups it can usually be left blank, but may be required", - "for routers with multiple IPs, or routers that must listen on a private IP with", - "forwarded public traffic. It can also be useful for clients that want to use a", - "consistent outgoing port for which firewall rules can be configured.", - }); + std::set seen_ranges; - const auto* net_ptr = params.Net_ptr(); - - static constexpr Default DefaultInboundPort{uint16_t{1090}}; - static constexpr Default DefaultOutboundPort{uint16_t{0}}; - - conf.defineOption( - "bind", - "public-ip", - RelayOnly, - Comment{ - "The IP address to advertise to the network instead of the incoming= or auto-detected", - "IP. This is typically required only when incoming= is used to listen on an internal", - "private range IP address that received traffic forwarded from the public IP.", - }, - [this](std::string_view arg) { - SockAddr pubaddr{arg}; - PublicAddress = pubaddr.getIP(); - }); - conf.defineOption( - "bind", - "public-port", - RelayOnly, - Comment{ - "The port to advertise to the network instead of the incoming= (or default) port.", - "This is typically required only when incoming= is used to listen on an internal", - "private range IP address/port that received traffic forwarded from the public IP.", - }, - [this](uint16_t arg) { PublicPort = net::port_t::from_host(arg); }); - - auto parse_addr_for_link = [net_ptr]( - const std::string& arg, net::port_t default_port, bool inbound) { - std::optional addr = std::nullopt; - // explicitly provided value - if (not arg.empty()) - { - if (arg[0] == ':') - { - // port only case - default_port = net::port_t::from_string(arg.substr(1)); - if (!inbound) - addr = net_ptr->WildcardWithPort(default_port); - } - else + for (const auto& hop : rcs) { - addr = SockAddr{arg}; - if (net_ptr->IsLoopbackAddress(addr->getIP())) - throw std::invalid_argument{fmt::format("{} is a loopback address", arg)}; + if (auto [it, b] = seen_ranges.emplace(hop.addr(), unique_hop_netmask); not b) + return false; } - } - if (not addr) - { - // infer public address - if (auto maybe_ifname = net_ptr->GetBestNetIF()) - addr = net_ptr->GetInterfaceAddr(*maybe_ifname); - } - - if (addr) - { - // set port if not explicitly provided - if (addr->getPort() == 0) - addr->setPort(default_port); - } - return addr; - }; - - conf.defineOption( - "bind", - "inbound", - RelayOnly, - MultiValue, - Comment{ - "IP and/or port to listen on for incoming connections.", - "", - "If IP is omitted then lokinet will search for a local network interface with a", - "public IP address and use that IP (and will exit with an error if no such IP is found", - "on the system). If port is omitted then lokinet defaults to 1090.", - "", - "Examples:", - " inbound=15.5.29.5:443", - " inbound=10.0.2.2", - " inbound=:1234", - "", - "Using a private range IP address (like the second example entry) will require using", - "the public-ip= and public-port= to specify the public IP address at which this", - "router can be reached.", - }, - [this, parse_addr_for_link](const std::string& arg) { - auto default_port = net::port_t::from_host(DefaultInboundPort.val); - if (auto addr = parse_addr_for_link(arg, default_port, /*inbound=*/true)) - InboundListenAddrs.emplace_back(std::move(*addr)); - }); - - conf.defineOption( - "bind", - "outbound", - MultiValue, - params.isRelay ? Comment{ - "IP and/or port to use for outbound socket connections to other lokinet routers.", - "", - "If no outbound bind IP is configured, or the 0.0.0.0 wildcard IP is given, then", - "lokinet will bind to the same IP being used for inbound connections (either an", - "explicit inbound= provided IP, or the default). If no port is given, or port is", - "given as 0, then a random high port will be used.", - "", - "If using multiple inbound= addresses then you *must* provide an explicit oubound= IP.", - "", - "Examples:", - " outbound=1.2.3.4:5678", - " outbound=:9000", - " outbound=8.9.10.11", - "", - "The second example binds on the default incoming IP using port 9000; the third", - "example binds on the given IP address using a random high port.", - } : Comment{ - "IP and/or port to use for outbound socket connections to lokinet routers.", - "", - "If no outbound bind IP is configured then lokinet will use a wildcard IP address", - "(equivalent to specifying 0.0.0.0). If no port is given then a random high port", - "will be used.", - "", - "Examples:", - " outbound=1.2.3.4:5678", - " outbound=:9000", - " outbound=8.9.10.11", - "", - "The second example binds on the wildcard address using port 9000; the third example", - "binds on the given IP address using a random high port.", - }, - [this, net_ptr, parse_addr_for_link](const std::string& arg) { - auto default_port = net::port_t::from_host(DefaultOutboundPort.val); - auto addr = parse_addr_for_link(arg, default_port, /*inbound=*/false); - if (not addr) - addr = net_ptr->WildcardWithPort(default_port); - OutboundLinks.emplace_back(std::move(*addr)); - }); - conf.addUndeclaredHandler( - "bind", [this, net_ptr](std::string_view, std::string_view key, std::string_view val) { - LogWarn( - "using the [bind] section with *=/IP=/INTERFACE= is deprecated; use the inbound= " - "and/or outbound= settings instead"); - std::optional addr; - // special case: wildcard for outbound - if (key == "*") - { - addr = net_ptr->Wildcard(); - // set port, zero is acceptable here. - if (auto port = std::stoi(std::string{val}); - port < std::numeric_limits::max()) - { - addr->setPort(port); - } - else - throw std::invalid_argument{fmt::format("invalid port value: '{}'", val)}; - OutboundLinks.emplace_back(std::move(*addr)); - return; - } - // try as interface name first - addr = net_ptr->GetInterfaceAddr(key, AF_INET); - if (addr and net_ptr->IsLoopbackAddress(addr->getIP())) - throw std::invalid_argument{fmt::format("{} is a loopback interface", key)}; - // try as ip address next, throws if unable to parse - if (not addr) - { - addr = SockAddr{key, huint16_t{0}}; - if (net_ptr->IsLoopbackAddress(addr->getIP())) - throw std::invalid_argument{fmt::format("{} is a loopback address", key)}; - } - // parse port and set if acceptable non zero value - if (auto port = std::stoi(std::string{val}); - port and port < std::numeric_limits::max()) - { - addr->setPort(port); - } - else - throw std::invalid_argument{fmt::format("invalid port value: '{}'", val)}; - - InboundListenAddrs.emplace_back(std::move(*addr)); - }); - } - - void - ConnectConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) - { - (void)params; - - conf.addUndeclaredHandler( - "connect", [this](std::string_view section, std::string_view name, std::string_view value) { - fs::path file{value.begin(), value.end()}; - if (not fs::exists(file)) - throw std::runtime_error{fmt::format( - "Specified bootstrap file {} specified in [{}]:{} does not exist", - value, - section, - name)}; - - routers.emplace_back(std::move(file)); - return true; - }); - } - - void - ApiConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) - { - constexpr std::array DefaultRPCBind{ - Default{"tcp://127.0.0.1:1190"}, -#ifndef _WIN32 - Default{"ipc://rpc.sock"}, -#endif - }; - - conf.defineOption( - "api", - "enabled", - Default{not params.isRelay}, - AssignmentAcceptor(m_enableRPCServer), - Comment{ - "Determines whether or not the LMQ JSON API is enabled. Defaults ", - }); + return true; + } - conf.defineOption( - "api", - "bind", - DefaultRPCBind, - MultiValue, - [this, first = true](std::string arg) mutable { - if (first) - { - m_rpcBindAddresses.clear(); - first = false; - } - if (arg.find("://") == std::string::npos) - { - arg = "tcp://" + arg; - } - m_rpcBindAddresses.emplace_back(arg); - }, - Comment{ - "IP addresses and ports to bind to.", - "Recommend localhost-only for security purposes.", - }); + std::unique_ptr Config::make_gen_params() const + { + return std::make_unique(); + } - conf.defineOption("api", "authkey", Deprecated); - - // TODO: this was from pre-refactor: - // TODO: add pubkey to whitelist - } - - void - LokidConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) - { - (void)params; - - conf.defineOption( - "lokid", - "enabled", - RelayOnly, - Default{true}, - Comment{ - "Whether or not we should talk to oxend. Must be enabled for staked routers.", - }, - AssignmentAcceptor(whitelistRouters)); - - conf.defineOption("lokid", "jsonrpc", RelayOnly, [](std::string arg) { - if (arg.empty()) - return; - throw std::invalid_argument( - "the [lokid]:jsonrpc option is no longer supported; please use the [lokid]:rpc config " - "option instead with oxend's lmq-local-control address -- typically a value such as " - "rpc=ipc:///var/lib/oxen/oxend.sock or rpc=ipc:///home/snode/.oxen/oxend.sock"); - }); - - conf.defineOption( - "lokid", - "rpc", - RelayOnly, - Required, - Comment{ - "oxenmq control address for for communicating with oxend. Depends on oxend's", - "lmq-local-control configuration option. By default this value should be", - "ipc://OXEND-DATA-DIRECTORY/oxend.sock, such as:", - " rpc=ipc:///var/lib/oxen/oxend.sock", - " rpc=ipc:///home/USER/.oxen/oxend.sock", - "but can use (non-default) TCP if oxend is configured that way:", - " rpc=tcp://127.0.0.1:5678", - }, - [this](std::string arg) { lokidRPCAddr = oxenmq::address(arg); }); - - // Deprecated options: - conf.defineOption("lokid", "username", Deprecated); - conf.defineOption("lokid", "password", Deprecated); - conf.defineOption("lokid", "service-node-seed", Deprecated); - } - - void - BootstrapConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) - { - (void)params; - - conf.defineOption( - "bootstrap", - "seed-node", - Default{false}, - Comment{"Whether or not to run as a seed node. We will not have any bootstrap routers " - "configured."}, - AssignmentAcceptor(seednode)); - - conf.defineOption( - "bootstrap", - "add-node", - MultiValue, - Comment{ - "Specify a bootstrap file containing a list of signed RouterContacts of service nodes", - "which can act as a bootstrap. Can be specified multiple times.", - }, - [this](std::string arg) { - if (arg.empty()) - { - throw std::invalid_argument("cannot use empty filename as bootstrap"); - } - files.emplace_back(std::move(arg)); - if (not fs::exists(files.back())) - { - throw std::invalid_argument("file does not exist: " + arg); - } - }); - } - - void - LoggingConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) - { - (void)params; - - constexpr Default DefaultLogType{ - platform::is_android or platform::is_apple ? "system" : "print"}; - constexpr Default DefaultLogFile{""}; - - const Default DefaultLogLevel{params.isRelay ? "warn" : "info"}; - - conf.defineOption( - "logging", - "type", - DefaultLogType, - [this](std::string arg) { m_logType = log::type_from_string(arg); }, - Comment{ - "Log type (format). Valid options are:", - " print - print logs to standard output", - " system - logs directed to the system logger (syslog/eventlog/etc.)", - " file - plaintext formatting to a file", - }); + Config::Config(std::optional datadir) : data_dir{datadir ? std::move(*datadir) : fs::current_path()} {} - conf.defineOption( - "logging", - "level", - DefaultLogLevel, - [this](std::string arg) { m_logLevel = log::level_from_string(arg); }, - Comment{ - "Minimum log level to print. Logging below this level will be ignored.", - "Valid log levels, in ascending order, are:", - " trace", - " debug", - " info", - " warn", - " error", - " critical", - " none", - }); + constexpr auto GetOverridesDir = [](auto datadir) -> fs::path { return datadir / "conf.d"; }; - conf.defineOption( - "logging", - "file", - DefaultLogFile, - AssignmentAcceptor(m_logFile), - Comment{ - "When using type=file this is the output filename.", - }); - } - - void - PeerSelectionConfig::defineConfigOptions( - ConfigDefinition& conf, const ConfigGenParameters& params) - { - (void)params; - - constexpr Default DefaultUniqueCIDR{32}; - conf.defineOption( - "paths", - "unique-range-size", - DefaultUniqueCIDR, - ClientOnly, - [=](int arg) { - if (arg == 0) - { - m_UniqueHopsNetmaskSize = arg; - } - else if (arg > 32 or arg < 4) - { - throw std::invalid_argument{"[paths]:unique-range-size must be between 4 and 32"}; - } - m_UniqueHopsNetmaskSize = arg; - }, - Comment{ - "Netmask for router path selection; each router must be from a distinct IPv4 subnet", - "of the given size.", - "E.g. 16 ensures that all routers are using IPs from distinct /16 IP ranges."}); + void Config::save() + { + const auto overridesDir = GetOverridesDir(data_dir); + if (not fs::exists(overridesDir)) + fs::create_directory(overridesDir); + parser.save(); + } -#ifdef WITH_GEOIP - conf.defineOption( - "paths", - "exclude-country", - ClientOnly, - MultiValue, - [=](std::string arg) { - m_ExcludeCountries.emplace(lowercase_ascii_string(std::move(arg))); - }, - Comment{ - "Exclude a country given its 2 letter country code from being used in path builds.", - "For example:", - " exclude-country=DE", - "would avoid building paths through routers with IPs in Germany.", - "This option can be specified multiple times to exclude multiple countries"}); -#endif - } - - bool - PeerSelectionConfig::Acceptable(const std::set& rcs) const - { - if (m_UniqueHopsNetmaskSize == 0) - return true; - const auto netmask = netmask_ipv6_bits(96 + m_UniqueHopsNetmaskSize); - std::set seenRanges; - for (const auto& hop : rcs) + void Config::override(std::string section, std::string key, std::string value) { - for (const auto& addr : hop.addrs) - { - const auto network_addr = net::In6ToHUInt(addr.ip) & netmask; - if (auto [it, inserted] = seenRanges.emplace(network_addr, netmask); not inserted) - { - return false; - } - } + parser.add_override(GetOverridesDir(data_dir) / "overrides.ini", section, key, value); } - return true; - } - - std::unique_ptr - Config::MakeGenParams() const - { - return std::make_unique(); - } - - Config::Config(std::optional datadir) - : m_DataDir{datadir ? std::move(*datadir) : fs::current_path()} - {} - - constexpr auto GetOverridesDir = [](auto datadir) -> fs::path { return datadir / "conf.d"; }; - - void - Config::Save() - { - const auto overridesDir = GetOverridesDir(m_DataDir); - if (not fs::exists(overridesDir)) - fs::create_directory(overridesDir); - m_Parser.Save(); - } - - void - Config::Override(std::string section, std::string key, std::string value) - { - m_Parser.AddOverride(GetOverridesDir(m_DataDir) / "overrides.ini", section, key, value); - } - - void - Config::LoadOverrides(ConfigDefinition& conf) const - { - ConfigParser parser; - const auto overridesDir = GetOverridesDir(m_DataDir); - if (fs::exists(overridesDir)) + + void Config::load_overrides(ConfigDefinition& conf) const { - util::IterDir(overridesDir, [&](const fs::path& overrideFile) { - if (overrideFile.extension() == ".ini") + ConfigParser parser; + const auto overridesDir = GetOverridesDir(data_dir); + if (fs::exists(overridesDir)) { - ConfigParser parser; - if (not parser.LoadFile(overrideFile)) - throw std::runtime_error{"cannot load '" + overrideFile.u8string() + "'"}; - - parser.IterAll([&](std::string_view section, const SectionValues_t& values) { - for (const auto& pair : values) + for (const auto& f : fs::directory_iterator{overridesDir}) { - conf.addConfigValue(section, pair.first, pair.second); + if (not f.is_regular_file() or f.path().extension() != ".ini") + continue; + ConfigParser parser; + if (not parser.load_file(f.path())) + throw std::runtime_error{"cannot load file at path:{}"_format(f.path().string())}; + + parser.iter_all_sections([&](std::string_view section, const SectionValues& values) { + for (const auto& [k, v] : values) + conf.add_config_value(section, k, v); + }); } - }); } - return true; - }); } - } - - void - Config::AddDefault(std::string section, std::string key, std::string val) - { - m_Additional.emplace_back(std::array{section, key, val}); - } - - bool - Config::LoadConfigData(std::string_view ini, std::optional filename, bool isRelay) - { - auto params = MakeGenParams(); - params->isRelay = isRelay; - params->defaultDataDir = m_DataDir; - ConfigDefinition conf{isRelay}; - addBackwardsCompatibleConfigOptions(conf); - initializeConfig(conf, *params); - - for (const auto& item : m_Additional) + + void Config::add_default(std::string section, std::string key, std::string val) { - conf.addConfigValue(item[0], item[1], item[2]); + additional.emplace_back(std::array{section, key, val}); } - m_Parser.Clear(); + bool Config::load_config_data(std::string_view ini, std::optional filename, bool isRelay) + { + auto params = make_gen_params(); + params->is_relay = isRelay; + params->default_data_dir = data_dir; + ConfigDefinition conf{isRelay}; + add_backcompat_opts(conf); + init_config(conf, *params); + + for (const auto& item : additional) + { + conf.add_config_value(item[0], item[1], item[2]); + } - if (filename) - m_Parser.Filename(*filename); - else - m_Parser.Filename(fs::path{}); + parser.clear(); - if (not m_Parser.LoadFromStr(ini)) - return false; + if (filename) + parser.set_filename(*filename); + else + parser.set_filename(fs::path{}); - m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) { - for (const auto& pair : values) - { - conf.addConfigValue(section, pair.first, pair.second); - } - }); + if (not parser.load_from_str(ini)) + return false; - LoadOverrides(conf); + parser.iter_all_sections([&](std::string_view section, const SectionValues& values) { + for (const auto& pair : values) + { + conf.add_config_value(section, pair.first, pair.second); + } + }); - conf.process(); + load_overrides(conf); - return true; - } + conf.process(); - bool - Config::Load(std::optional fname, bool isRelay) - { - std::string ini; - if (fname) - { - try - { - ini = util::slurp_file(*fname); - } - catch (const std::exception&) - { - return false; - } + return true; } - return LoadConfigData(ini, fname, isRelay); - } - - bool - Config::LoadString(std::string_view ini, bool isRelay) - { - return LoadConfigData(ini, std::nullopt, isRelay); - } - - bool - Config::LoadDefault(bool isRelay) - { - return LoadString("", isRelay); - } - - void - Config::initializeConfig(ConfigDefinition& conf, const ConfigGenParameters& params) - { - router.defineConfigOptions(conf, params); - network.defineConfigOptions(conf, params); - paths.defineConfigOptions(conf, params); - connect.defineConfigOptions(conf, params); - dns.defineConfigOptions(conf, params); - links.defineConfigOptions(conf, params); - api.defineConfigOptions(conf, params); - lokid.defineConfigOptions(conf, params); - bootstrap.defineConfigOptions(conf, params); - logging.defineConfigOptions(conf, params); - } - - void - Config::addBackwardsCompatibleConfigOptions(ConfigDefinition& conf) - { - // These config sections don't exist anymore: - - conf.defineOption("system", "user", Deprecated); - conf.defineOption("system", "group", Deprecated); - conf.defineOption("system", "pidfile", Deprecated); - - conf.defineOption("netdb", "dir", Deprecated); - - conf.defineOption("metrics", "json-metrics-path", Deprecated); - } - - void - ensureConfig(fs::path dataDir, fs::path confFile, bool overwrite, bool asRouter) - { - // fail to overwrite if not instructed to do so - if (fs::exists(confFile) && !overwrite) + + bool Config::load(std::optional fname, bool isRelay) { - LogDebug("Not creating config file; it already exists."); - return; + std::string ini; + if (fname) + { + try + { + ini = util::file_to_string(*fname); + } + catch (const std::exception&) + { + return false; + } + } + return load_config_data(ini, fname, isRelay); } - const auto parent = confFile.parent_path(); - - // create parent dir if it doesn't exist - if ((not parent.empty()) and (not fs::exists(parent))) + bool Config::load_string(std::string_view ini, bool isRelay) { - fs::create_directory(parent); + return load_config_data(ini, std::nullopt, isRelay); } - llarp::LogInfo( - "Attempting to create config file for ", - (asRouter ? "router" : "client"), - " at ", - confFile); - - llarp::Config config{dataDir}; - std::string confStr; - if (asRouter) - confStr = config.generateBaseRouterConfig(); - else - confStr = config.generateBaseClientConfig(); - - // open a filestream - try + bool Config::load_default_config(bool isRelay) { - util::dump_file(confFile, confStr); + return load_string("", isRelay); } - catch (const std::exception& e) + + void Config::init_config(ConfigDefinition& conf, const ConfigGenParameters& params) { - throw std::runtime_error{ - fmt::format("Failed to write config data to {}: {}", confFile, e.what())}; + router.define_config_options(conf, params); + network.define_config_options(conf, params); + paths.define_config_options(conf, params); + dns.define_config_options(conf, params); + links.define_config_options(conf, params); + api.define_config_options(conf, params); + lokid.define_config_options(conf, params); + bootstrap.define_config_options(conf, params); + logging.define_config_options(conf, params); } - llarp::LogInfo("Generated new config ", confFile); - } + void Config::add_backcompat_opts(ConfigDefinition& conf) + { + // These config sections don't exist anymore: - void - generateCommonConfigComments(ConfigDefinition& def) - { - // router - def.addSectionComments( - "router", - { - "Configuration for routing activity.", - }); + conf.define_option("system", "user", Deprecated); + conf.define_option("system", "group", Deprecated); + conf.define_option("system", "pidfile", Deprecated); - // logging - def.addSectionComments( - "logging", - { - "logging settings", - }); + conf.define_option("netdb", "dir", Deprecated); - // api - def.addSectionComments( - "api", - { - "JSON API settings", - }); + conf.define_option("metrics", "json-metrics-path", Deprecated); + } - // dns - def.addSectionComments( - "dns", + void ensure_config(fs::path dataDir, fs::path confFile, bool overwrite, bool asRouter) + { + // fail to overwrite if not instructed to do so + if (fs::exists(confFile) && !overwrite) { - "DNS configuration", - }); + log::debug(logcat, "Config file already exists; NOT creating new config"); + return; + } - // bootstrap - def.addSectionComments( - "bootstrap", + const auto parent = confFile.parent_path(); + + // create parent dir if it doesn't exist + if ((not parent.empty()) and (not fs::exists(parent))) { - "Configure nodes that will bootstrap us onto the network", - }); + fs::create_directory(parent); + } + + log::info( + logcat, + "Attempting to create config file for {} at file path:{}", + asRouter ? "router" : "client", + confFile); + + llarp::Config config{dataDir}; + std::string confStr; + if (asRouter) + confStr = config.generate_router_config_base(); + else + confStr = config.generate_client_config_base(); - // network - def.addSectionComments( - "network", + // open a filestream + try { - "Network settings", - }); - } - - std::string - Config::generateBaseClientConfig() - { - auto params = MakeGenParams(); - params->isRelay = false; - params->defaultDataDir = m_DataDir; - - llarp::ConfigDefinition def{false}; - initializeConfig(def, *params); - generateCommonConfigComments(def); - def.addSectionComments( - "paths", + util::buffer_to_file(confFile, confStr); + } + catch (const std::exception& e) { - "path selection algorithm options", - }); + throw std::runtime_error{"Failed to write config data to {}: {}"_format(confFile, e.what())}; + } - def.addSectionComments( - "network", - { - "Snapp settings", - }); + log::info(logcat, "Generated new config (path: {})", confFile); + } - return def.generateINIConfig(true); - } + void generate_common_config_comments(ConfigDefinition& def) + { + // router + def.add_section_comments( + "router", + { + "Configuration for routing activity.", + }); - std::string - Config::generateBaseRouterConfig() - { - auto params = MakeGenParams(); - params->isRelay = true; - params->defaultDataDir = m_DataDir; + // logging + def.add_section_comments( + "logging", + { + "logging settings", + }); - llarp::ConfigDefinition def{true}; - initializeConfig(def, *params); - generateCommonConfigComments(def); + // api + def.add_section_comments( + "api", + { + "JSON API settings", + }); - // oxend - def.addSectionComments( - "lokid", - { - "Settings for communicating with oxend", - }); + // dns + def.add_section_comments( + "dns", + { + "DNS configuration", + }); + + // bootstrap + def.add_section_comments( + "bootstrap", + { + "Configure nodes that will bootstrap us onto the network", + }); - return def.generateINIConfig(true); - } - - std::shared_ptr - Config::EmbeddedConfig() - { - auto config = std::make_shared(); - config->Load(); - config->logging.m_logLevel = log::Level::off; - config->api.m_enableRPCServer = false; - config->network.m_endpointType = "null"; - config->network.m_saveProfiles = false; - config->bootstrap.files.clear(); - return config; - } + // network + def.add_section_comments( + "network", + { + "Network settings", + }); + } + + std::string Config::generate_client_config_base() + { + auto params = make_gen_params(); + params->is_relay = false; + params->default_data_dir = data_dir; + + llarp::ConfigDefinition def{false}; + init_config(def, *params); + generate_common_config_comments(def); + def.add_section_comments( + "paths", + { + "path selection algorithm options", + }); + + def.add_section_comments( + "network", + { + "Snapp settings", + }); + + return def.generate_ini_config(true); + } + + std::string Config::generate_router_config_base() + { + auto params = make_gen_params(); + params->is_relay = true; + params->default_data_dir = data_dir; + + llarp::ConfigDefinition def{true}; + init_config(def, *params); + generate_common_config_comments(def); + + // oxend + def.add_section_comments( + "lokid", + { + "Settings for communicating with oxend", + }); + + return def.generate_ini_config(true); + } + + std::shared_ptr Config::make_embedded_config() + { + auto config = std::make_shared(); + config->load(); + config->logging.level = log::Level::off; + config->api.enable_rpc_server = false; + config->network.init_tun = false; + config->network.save_profiles = false; + config->bootstrap.files.clear(); + return config; + } } // namespace llarp diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 3165f03543..51e8aae3d1 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -1,316 +1,327 @@ #pragma once -#include "ini.hpp" -#include "definition.hpp" -#include +#include "definition.hpp" +#include "ini.hpp" +#include +#include +#include #include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include +#include #include +#include +#include +#include #include +#include +#include + +#include +#include #include #include #include #include +#include #include #include -#include - -#include namespace llarp { - using SectionValues_t = llarp::ConfigParser::SectionValues_t; - using Config_impl_t = llarp::ConfigParser::Config_impl_t; - - // TODO: don't use these maps. they're sloppy and difficult to follow - /// Small struct to gather all parameters needed for config generation to reduce the number of - /// parameters that need to be passed around. - struct ConfigGenParameters - { - ConfigGenParameters() = default; - virtual ~ConfigGenParameters() = default; - - ConfigGenParameters(const ConfigGenParameters&) = delete; - ConfigGenParameters(ConfigGenParameters&&) = delete; - - bool isRelay = false; - fs::path defaultDataDir; - - /// get network platform (virtual for unit test mocks) - virtual const llarp::net::Platform* - Net_ptr() const = 0; - }; + using SectionValues = llarp::ConfigParser::SectionValues; + using ConfigMap = llarp::ConfigParser::ConfigMap; + + inline constexpr uint16_t DEFAULT_LISTEN_PORT{1090}; + inline constexpr uint16_t DEFAULT_DNS_PORT{53}; + inline constexpr int CLIENT_ROUTER_CONNECTIONS = 4; + + // TODO: don't use these maps. they're sloppy and difficult to follow + /// Small struct to gather all parameters needed for config generation to reduce the number of + /// parameters that need to be passed around. + struct ConfigGenParameters + { + ConfigGenParameters() = default; + virtual ~ConfigGenParameters() = default; + + ConfigGenParameters(const ConfigGenParameters&) = delete; + ConfigGenParameters(ConfigGenParameters&&) = delete; + + bool is_relay = false; + fs::path default_data_dir; + + /// get network platform (virtual for unit test mocks) + virtual const llarp::net::Platform* net_ptr() const = 0; + }; + + struct RouterConfig + { + int client_router_connections{CLIENT_ROUTER_CONNECTIONS}; + + std::string net_id; + + fs::path data_dir; + + bool block_bogons = false; + + int worker_threads = -1; + int net_threads = -1; + + size_t job_que_size = 0; + + std::optional rc_file; + + bool is_relay = false; + + std::optional public_ip; + std::optional public_port; + + void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); + }; + + /// config for path hop selection + struct PeerSelectionConfig + { + /// in our hops what netmask will we use for unique ips for hops + /// i.e. 32 for every hop unique ip, 24 unique /24 per hop, etc + uint8_t unique_hop_netmask; + + /// set of countrys to exclude from path building (2 char country code) + std::unordered_set exclude_countries; + + void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); + + /// return true if this set of router contacts is acceptable against this config + bool check_rcs(const std::set& hops) const; + }; + + struct NetworkConfig + { + bool enable_profiling; + bool save_profiles; + std::set strict_connect; + + std::optional keyfile; + + std::optional hops; + std::optional paths; + + bool enable_ipv6{false}; + bool allow_exit{false}; + bool is_reachable{false}; + bool init_tun{true}; + + std::set snode_blacklist; + + // Used by RemoteHandler to provide auth tokens for remote exits + std::unordered_map exit_auths; + std::unordered_map ons_exit_auths; + + /* Auth specific config */ + auth::AuthType auth_type = auth::AuthType::NONE; + auth::AuthFileType auth_file_type = auth::AuthFileType::HASHES; + + std::optional auth_endpoint; + std::optional auth_method; + + std::unordered_set auth_whitelist; + + std::unordered_set auth_static_tokens; + + std::set auth_files; + + std::vector srv_records; + + std::optional traffic_policy; + + std::optional path_alignment_timeout; + + /* TESTNET: Under modification */ + + // Contents of this file are read directly into ::_reserved_local_addrs + std::optional addr_map_persist_file; + + // the only member that refers to an actual interface + std::optional _if_name; + + // used for in6_ifreq + net::if_info _if_info; + + // If _local_ip_range is set, the following two optionals are also set + + // config mapped as "if-addr" + std::optional _local_ip_range; + std::optional _local_addr; + std::optional _local_base_ip; + + std::optional _base_ipv6_range = std::nullopt; + + // Remote exit or hidden service addresses mapped to fixed local IP addresses + // TODO: + // - load directly into TunEndpoint mapping + // - when a session is created, check mapping when assigning IP's + std::unordered_map _reserved_local_ips; + + // Remote client exit addresses mapped to local IP ranges + std::unordered_map _exit_ranges; + + // Remote client ONS exit addresses mapped to local IP ranges pending ONS address resolution + std::unordered_map _ons_ranges; + + // Used when in exit mode; pass down to LocalEndpoint + std::set _routed_ranges; + + bool enable_route_poker; + bool blackhole_routes; + + void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); + }; + + struct DnsConfig + { + bool l3_intercept; + + std::vector hostfiles; + + /* TESTNET: Under modification */ + std::vector _upstream_dns; + oxen::quic::Address _default_dns{"9.9.9.10", DEFAULT_DNS_PORT}; + std::optional _query_bind; + std::vector _bind_addrs; + + // Deprecated + // std::vector upstream_dns; + // std::optional query_bind; + // std::vector bind_addr; + /*************************************/ + + std::unordered_multimap extra_opts; + + void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); + }; + + struct LinksConfig + { + // DEPRECATED -- use [Router]:public_addr + std::optional public_addr; + // DEPRECATED -- use [Router]:public_port + std::optional public_port; + + std::optional listen_addr; + + bool only_user_port = false; + bool using_new_api = false; + + void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); + }; + + // TODO: remove oxenmq from this header + struct ApiConfig + { + bool enable_rpc_server = false; + std::vector rpc_bind_addrs; + + void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); + }; + + struct LokidConfig + { + fs::path id_keyfile; + oxenmq::address rpc_addr; + bool disable_testing = true; + + void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); + }; + + struct BootstrapConfig + { + std::vector files; + bool seednode; + + void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); + }; + + struct LoggingConfig + { + log::Type type = log::Type::Print; + log::Level level = log::Level::off; + std::string file; + + void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); + }; + + struct Config + { + explicit Config(std::optional datadir = std::nullopt); + + virtual ~Config() = default; + + /// create generation params (virtual for unit test mock) + virtual std::unique_ptr make_gen_params() const; + + RouterConfig router; + NetworkConfig network; + PeerSelectionConfig paths; + DnsConfig dns; + LinksConfig links; + ApiConfig api; + LokidConfig lokid; + BootstrapConfig bootstrap; + LoggingConfig logging; + + // Initialize config definition + void init_config(ConfigDefinition& conf, const ConfigGenParameters& params); + + /// Insert config entries for backwards-compatibility (e.g. so that the config system will + /// tolerate old values that are no longer accepted) + /// + /// @param conf is the config to modify + void add_backcompat_opts(ConfigDefinition& conf); + + // Load a config from the given file if the config file is not provided LoadDefault is + // called + bool load(std::optional fname = std::nullopt, bool isRelay = false); + + // Load a config from a string of ini, same effects as Config::Load + bool load_string(std::string_view ini, bool isRelay = false); + + std::string generate_client_config_base(); + + std::string generate_router_config_base(); + + void save(); + + void override(std::string section, std::string key, std::string value); + + void add_default(std::string section, std::string key, std::string value); + + /// create a config with the default parameters for an embedded lokinet + static std::shared_ptr make_embedded_config(); + + private: + /// Load (initialize) a default config. + /// + /// This delegates to the ConfigDefinition to generate a default config, + /// as though an empty config were specified. + /// + /// If using Config without the intention of loading from file (or string), this is + /// necessary in order to obtain sane defaults. + /// + /// @param isRelay determines whether the config will reflect that of a relay or client + /// @param dataDir is a path representing a directory to be used as the data dir + /// @return true on success, false otherwise + bool load_default_config(bool isRelay); + + bool load_config_data(std::string_view ini, std::optional fname = std::nullopt, bool isRelay = false); + + void load_overrides(ConfigDefinition& conf) const; + + std::vector> additional; + ConfigParser parser; + const fs::path data_dir; + }; - struct RouterConfig - { - size_t m_minConnectedRouters = 0; - size_t m_maxConnectedRouters = 0; - - std::string m_netId; - std::string m_nickname; - - fs::path m_dataDir; - - bool m_blockBogons = false; - - int m_workerThreads = -1; - int m_numNetThreads = -1; - - size_t m_JobQueueSize = 0; - - std::string m_routerContactFile; - std::string m_encryptionKeyFile; - std::string m_identityKeyFile; - std::string m_transportKeyFile; - - bool m_isRelay = false; - /// deprecated - std::optional PublicIP; - /// deprecated - std::optional PublicPort; - - void - defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); - }; - - /// config for path hop selection - struct PeerSelectionConfig - { - /// in our hops what netmask will we use for unique ips for hops - /// i.e. 32 for every hop unique ip, 24 unique /24 per hop, etc - /// - int m_UniqueHopsNetmaskSize; - - /// set of countrys to exclude from path building (2 char country code) - std::unordered_set m_ExcludeCountries; - - void - defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); - - /// return true if this set of router contacts is acceptable against this config - bool - Acceptable(const std::set& hops) const; - }; - - struct NetworkConfig - { - std::optional m_enableProfiling; - bool m_saveProfiles; - std::set m_strictConnect; - std::string m_ifname; - IPRange m_ifaddr; - - std::optional m_keyfile; - std::string m_endpointType; - bool m_reachable = false; - std::optional m_Hops; - std::optional m_Paths; - bool m_AllowExit = false; - std::set m_snodeBlacklist; - net::IPRangeMap m_ExitMap; - net::IPRangeMap m_LNSExitMap; - - std::unordered_map m_ExitAuths; - std::unordered_map m_LNSExitAuths; - - std::unordered_map m_mapAddrs; - - service::AuthType m_AuthType = service::AuthType::eAuthTypeNone; - service::AuthFileType m_AuthFileType = service::AuthFileType::eAuthFileHashes; - std::optional m_AuthUrl; - std::optional m_AuthMethod; - std::unordered_set m_AuthWhitelist; - std::unordered_set m_AuthStaticTokens; - std::set m_AuthFiles; - - std::vector m_SRVRecords; - - std::optional m_baseV6Address; - - std::set m_OwnedRanges; - std::optional m_TrafficPolicy; - - std::optional m_PathAlignmentTimeout; - - std::optional m_AddrMapPersistFile; - - bool m_EnableRoutePoker; - bool m_BlackholeRoutes; - - void - defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); - }; - - struct DnsConfig - { - bool m_raw_dns; - std::vector m_bind; - std::vector m_upstreamDNS; - std::vector m_hostfiles; - std::optional m_QueryBind; - - std::unordered_multimap m_ExtraOpts; - - void - defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); - }; - - struct LinksConfig - { - std::optional PublicAddress; - std::optional PublicPort; - std::vector OutboundLinks; - std::vector InboundListenAddrs; - - void - defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); - }; - - struct ConnectConfig - { - std::vector routers; - - void - defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); - }; - - struct ApiConfig - { - bool m_enableRPCServer = false; - std::vector m_rpcBindAddresses; - - void - defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); - }; - - struct LokidConfig - { - bool whitelistRouters = false; - fs::path ident_keyfile; - oxenmq::address lokidRPCAddr; - - void - defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); - }; - - struct BootstrapConfig - { - std::vector files; - BootstrapList routers; - bool seednode; - void - defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); - }; - - struct LoggingConfig - { - log::Type m_logType = log::Type::Print; - log::Level m_logLevel = log::Level::off; - std::string m_logFile; - - void - defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); - }; - - struct Config - { - explicit Config(std::optional datadir = std::nullopt); - - virtual ~Config() = default; - - /// create generation params (virtual for unit test mock) - virtual std::unique_ptr - MakeGenParams() const; - - RouterConfig router; - NetworkConfig network; - PeerSelectionConfig paths; - ConnectConfig connect; - DnsConfig dns; - LinksConfig links; - ApiConfig api; - LokidConfig lokid; - BootstrapConfig bootstrap; - LoggingConfig logging; - - // Initialize config definition - void - initializeConfig(ConfigDefinition& conf, const ConfigGenParameters& params); - - /// Insert config entries for backwards-compatibility (e.g. so that the config system will - /// tolerate old values that are no longer accepted) - /// - /// @param conf is the config to modify - void - addBackwardsCompatibleConfigOptions(ConfigDefinition& conf); - - // Load a config from the given file if the config file is not provided LoadDefault is called - bool - Load(std::optional fname = std::nullopt, bool isRelay = false); - - // Load a config from a string of ini, same effects as Config::Load - bool - LoadString(std::string_view ini, bool isRelay = false); - - std::string - generateBaseClientConfig(); - - std::string - generateBaseRouterConfig(); - - void - Save(); - - void - Override(std::string section, std::string key, std::string value); - - void - AddDefault(std::string section, std::string key, std::string value); - - /// create a config with the default parameters for an embedded lokinet - static std::shared_ptr - EmbeddedConfig(); - - private: - /// Load (initialize) a default config. - /// - /// This delegates to the ConfigDefinition to generate a default config, - /// as though an empty config were specified. - /// - /// If using Config without the intention of loading from file (or string), this is necessary - /// in order to obtain sane defaults. - /// - /// @param isRelay determines whether the config will reflect that of a relay or client - /// @param dataDir is a path representing a directory to be used as the data dir - /// @return true on success, false otherwise - bool - LoadDefault(bool isRelay); - - bool - LoadConfigData( - std::string_view ini, std::optional fname = std::nullopt, bool isRelay = false); - - void - LoadOverrides(ConfigDefinition& conf) const; - - std::vector> m_Additional; - ConfigParser m_Parser; - const fs::path m_DataDir; - }; - - void - ensureConfig(fs::path dataDir, fs::path confFile, bool overwrite, bool asRouter); + void ensure_config(fs::path dataDir, fs::path confFile, bool overwrite, bool asRouter); } // namespace llarp diff --git a/llarp/config/definition.cpp b/llarp/config/definition.cpp index 76177603ce..1e5ab8570f 100644 --- a/llarp/config/definition.cpp +++ b/llarp/config/definition.cpp @@ -1,286 +1,273 @@ #include "definition.hpp" + +#include #include +#include #include #include -#include namespace llarp { - template <> - bool - OptionDefinition::fromString(const std::string& input) - { - if (input == "false" || input == "off" || input == "0" || input == "no") - return false; - else if (input == "true" || input == "on" || input == "1" || input == "yes") - return true; - else - throw std::invalid_argument{fmt::format("{} is not a valid bool", input)}; - } - - ConfigDefinition& - ConfigDefinition::defineOption(OptionDefinition_ptr def) - { - using namespace config; - // If explicitly deprecated or is a {client,relay} option in a {relay,client} config then add a - // dummy, warning option instead of this one. - if (def->deprecated || (relay ? def->clientOnly : def->relayOnly)) + static auto logcat = log::Cat("config.def"); + + template <> + bool OptionDefinition::from_string(const std::string& input) { - return defineOption( - def->section, - def->name, - MultiValue, - Hidden, - [deprecated = def->deprecated, - relay = relay, - opt = "[" + def->section + "]:" + def->name](std::string_view) { - LogWarn( - "*** WARNING: The config option ", - opt, - (deprecated ? " is deprecated" - : relay ? " is not valid in service node configuration files" - : " is not valid in client configuration files"), - " and has been ignored."); - }); + if (input == "false" || input == "off" || input == "0" || input == "no") + return false; + if (input == "true" || input == "on" || input == "1" || input == "yes") + return true; + throw std::invalid_argument{"{} is not a valid bool"_format(input)}; } - auto [sectionItr, newSect] = m_definitions.try_emplace(def->section); - if (newSect) - m_sectionOrdering.push_back(def->section); - auto& section = sectionItr->first; - - auto [it, added] = m_definitions[section].try_emplace(std::string{def->name}, std::move(def)); - if (!added) - throw std::invalid_argument{ - fmt::format("definition for [{}]:{} already exists", def->section, def->name)}; + ConfigDefinition& ConfigDefinition::define_option(std::unique_ptr def) + { + using namespace config; + // If explicitly deprecated or is a {client,relay} option in a {relay,client} config then + // add a dummy, warning option instead of this one. + if (def->deprecated || (relay ? def->clientOnly : def->relay_only)) + { + return define_option( + def->section, + def->name, + MultiValue, + Hidden, + [deprecated = def->deprecated, relay = relay, opt = "[" + def->section + "]:" + def->name]( + std::string_view) { + log::warning( + logcat, + "*** WARNING: The config option {} {} and has been ignored", + opt, + (deprecated ? "is deprecated" + : relay ? "is not valid in service node configuration files" + : "is not valid in client configuration files")); + }); + } - m_definitionOrdering[section].push_back(it->first); + auto [sectionItr, newSect] = definitions.try_emplace(def->section); + if (newSect) + section_ordering.push_back(def->section); + auto& section = sectionItr->first; - if (!it->second->comments.empty()) - addOptionComments(section, it->first, std::move(it->second->comments)); + auto [it, added] = definitions[section].try_emplace(std::string{def->name}, std::move(def)); + if (!added) + throw std::invalid_argument{"definition for [{}]:{} already exists"_format(def->section, def->name)}; - return *this; - } + definition_ordering[section].push_back(it->first); - ConfigDefinition& - ConfigDefinition::addConfigValue( - std::string_view section, std::string_view name, std::string_view value) - { - // see if we have an undeclared handler to fall back to in case section or section:name is - // absent - auto undItr = m_undeclaredHandlers.find(std::string(section)); - bool haveUndeclaredHandler = (undItr != m_undeclaredHandlers.end()); + if (!it->second->comments.empty()) + add_option_comments(section, it->first, std::move(it->second->comments)); - // get section, falling back to undeclared handler if needed - auto secItr = m_definitions.find(std::string(section)); - if (secItr == m_definitions.end()) - { - // fallback to undeclared handler if available - if (not haveUndeclaredHandler) - throw std::invalid_argument{fmt::format("unrecognized section [{}]", section)}; - auto& handler = undItr->second; - handler(section, name, value); - return *this; + return *this; } - // section was valid, get definition by name - // fall back to undeclared handler if needed - auto& sectionDefinitions = secItr->second; - auto defItr = sectionDefinitions.find(std::string(name)); - if (defItr != sectionDefinitions.end()) + ConfigDefinition& ConfigDefinition::add_config_value( + std::string_view section, std::string_view name, std::string_view value) { - OptionDefinition_ptr& definition = defItr->second; - definition->parseValue(std::string(value)); - return *this; - } - - if (not haveUndeclaredHandler) - throw std::invalid_argument{fmt::format("unrecognized option [{}]: {}", section, name)}; - - auto& handler = undItr->second; - handler(section, name, value); - return *this; - } - - void - ConfigDefinition::addUndeclaredHandler(const std::string& section, UndeclaredValueHandler handler) - { - auto itr = m_undeclaredHandlers.find(section); - if (itr != m_undeclaredHandlers.end()) - throw std::logic_error{fmt::format("section {} already has a handler", section)}; - - m_undeclaredHandlers[section] = std::move(handler); - } - - void - ConfigDefinition::removeUndeclaredHandler(const std::string& section) - { - auto itr = m_undeclaredHandlers.find(section); - if (itr != m_undeclaredHandlers.end()) - m_undeclaredHandlers.erase(itr); - } - - void - ConfigDefinition::validateRequiredFields() - { - visitSections([&](const std::string& section, const DefinitionMap&) { - visitDefinitions(section, [&](const std::string&, const OptionDefinition_ptr& def) { - if (def->required and def->getNumberFound() < 1) + // see if we have an undeclared handler to fall back to in case section or section:name is + // absent + auto undItr = undeclared_handlers.find(std::string(section)); + bool haveUndeclaredHandler = (undItr != undeclared_handlers.end()); + + // get section, falling back to undeclared handler if needed + auto secItr = definitions.find(std::string(section)); + if (secItr == definitions.end()) { - throw std::invalid_argument{ - fmt::format("[{}]:{} is required but missing", section, def->name)}; + // fallback to undeclared handler if available + if (not haveUndeclaredHandler) + throw std::invalid_argument{"unrecognized section [{}]"_format(section)}; + auto& handler = undItr->second; + handler(section, name, value); + return *this; } - // should be handled earlier in OptionDefinition::parseValue() - assert(def->getNumberFound() <= 1 or def->multiValued); - }); - }); - } - - void - ConfigDefinition::acceptAllOptions() - { - visitSections([this](const std::string& section, const DefinitionMap&) { - visitDefinitions( - section, [](const std::string&, const OptionDefinition_ptr& def) { def->tryAccept(); }); - }); - } - - void - ConfigDefinition::addSectionComments( - const std::string& section, std::vector comments) - { - auto& sectionComments = m_sectionComments[section]; - for (auto& c : comments) - sectionComments.emplace_back(std::move(c)); - } - - void - ConfigDefinition::addOptionComments( - const std::string& section, const std::string& name, std::vector comments) - { - auto& defComments = m_definitionComments[section][name]; - if (defComments.empty()) - defComments = std::move(comments); - else - defComments.insert( - defComments.end(), - std::make_move_iterator(comments.begin()), - std::make_move_iterator(comments.end())); - } - - std::string - ConfigDefinition::generateINIConfig(bool useValues) - { - std::string ini; - auto ini_append = std::back_inserter(ini); - - int sectionsVisited = 0; - - visitSections([&](const std::string& section, const DefinitionMap&) { - std::string sect_str; - auto sect_append = std::back_inserter(sect_str); - - visitDefinitions(section, [&](const std::string& name, const OptionDefinition_ptr& def) { - bool has_comment = false; - // TODO: as above, this will create empty objects - // TODO: as above (but more important): this won't handle definitions with no entries - // (i.e. those handled by UndeclaredValueHandler's) - for (const std::string& comment : m_definitionComments[section][name]) + // section was valid, get definition by name + // fall back to undeclared handler if needed + auto& sectionDefinitions = secItr->second; + auto defItr = sectionDefinitions.find(std::string(name)); + if (defItr != sectionDefinitions.end()) { - fmt::format_to(sect_append, "\n# {}", comment); - has_comment = true; + std::unique_ptr& definition = defItr->second; + definition->parse_value(std::string(value)); + return *this; } - if (useValues and def->getNumberFound() > 0) - { - for (const auto& val : def->valuesAsString()) - fmt::format_to(sect_append, "\n{}={}", name, val); - *sect_append = '\n'; - } - else if (not def->hidden) - { - if (auto defaults = def->defaultValuesAsString(); not defaults.empty()) - for (const auto& val : defaults) - fmt::format_to(sect_append, "\n{}{}={}", def->required ? "" : "#", name, val); - else - // We have no defaults so we append it as "#opt-name=" so that we show the option name, - // and make it simple to uncomment and edit to the desired value. - fmt::format_to(sect_append, "\n#{}=", name); - *sect_append = '\n'; - } - else if (has_comment) - *sect_append = '\n'; - }); - - if (sect_str.empty()) - return; // Skip sections with no options - - if (sectionsVisited > 0) - ini += "\n\n"; - - fmt::format_to(ini_append, "[{}]\n", section); - - // TODO: this will create empty objects as a side effect of map's operator[] - // TODO: this also won't handle sections which have no definition - for (const std::string& comment : m_sectionComments[section]) - { - fmt::format_to(ini_append, "# {}\n", comment); - } - ini += "\n"; - ini += sect_str; - - sectionsVisited++; - }); - - return ini; - } - - const OptionDefinition_ptr& - ConfigDefinition::lookupDefinitionOrThrow(std::string_view section, std::string_view name) const - { - const auto sectionItr = m_definitions.find(std::string(section)); - if (sectionItr == m_definitions.end()) - throw std::invalid_argument{fmt::format("No config section [{}]", section)}; - - auto& sectionDefinitions = sectionItr->second; - const auto definitionItr = sectionDefinitions.find(std::string(name)); - if (definitionItr == sectionDefinitions.end()) - throw std::invalid_argument{ - fmt::format("No config item {} within section {}", name, section)}; - - return definitionItr->second; - } - - OptionDefinition_ptr& - ConfigDefinition::lookupDefinitionOrThrow(std::string_view section, std::string_view name) - { - return const_cast( - const_cast(this)->lookupDefinitionOrThrow(section, name)); - } - - void - ConfigDefinition::visitSections(SectionVisitor visitor) const - { - for (const std::string& section : m_sectionOrdering) + if (not haveUndeclaredHandler) + throw std::invalid_argument{"unrecognized option [{}]: {}"_format(section, name)}; + + auto& handler = undItr->second; + handler(section, name, value); + return *this; + } + + void ConfigDefinition::add_undeclared_handler(const std::string& section, UndeclaredValueHandler handler) + { + auto itr = undeclared_handlers.find(section); + if (itr != undeclared_handlers.end()) + throw std::logic_error{"section {} already has a handler"_format(section)}; + + undeclared_handlers[section] = std::move(handler); + } + + void ConfigDefinition::remove_undeclared_handler(const std::string& section) + { + auto itr = undeclared_handlers.find(section); + if (itr != undeclared_handlers.end()) + undeclared_handlers.erase(itr); + } + + void ConfigDefinition::validate_required_fields() { - const auto itr = m_definitions.find(section); - assert(itr != m_definitions.end()); - visitor(section, itr->second); + visit_sections([&](const std::string& section, const DefinitionMap&) { + visit_definitions(section, [&](const std::string&, const std::unique_ptr& def) { + if (def->required and def->get_number_found() < 1) + { + throw std::invalid_argument{"[{}]:{} is required but missing"_format(section, def->name)}; + } + + // should be handled earlier in OptionDefinition::parse_value() + assert(def->get_number_found() <= 1 or def->multi_valued); + }); + }); } - }; - void - ConfigDefinition::visitDefinitions(const std::string& section, DefVisitor visitor) const - { - const auto& defs = m_definitions.at(section); - const auto& defOrdering = m_definitionOrdering.at(section); - for (const std::string& name : defOrdering) + + void ConfigDefinition::accept_all_options() + { + visit_sections([this](const std::string& section, const DefinitionMap&) { + visit_definitions(section, [](const std::string&, const std::unique_ptr& def) { + def->try_accept(); + }); + }); + } + + void ConfigDefinition::add_section_comments(const std::string& section, std::vector comments) + { + auto& sectionComments = section_comments[section]; + for (auto& c : comments) + sectionComments.emplace_back(std::move(c)); + } + + void ConfigDefinition::add_option_comments( + const std::string& section, const std::string& name, std::vector comments) { - const auto itr = defs.find(name); - assert(itr != defs.end()); - visitor(name, itr->second); + auto& defComments = definition_comments[section][name]; + if (defComments.empty()) + defComments = std::move(comments); + else + defComments.insert( + defComments.end(), std::make_move_iterator(comments.begin()), std::make_move_iterator(comments.end())); } - }; + + std::string ConfigDefinition::generate_ini_config(bool useValues) + { + std::string ini; + auto ini_append = std::back_inserter(ini); + + int sectionsVisited = 0; + + visit_sections([&](const std::string& section, const DefinitionMap&) { + std::string sect_str; + auto sect_append = std::back_inserter(sect_str); + + visit_definitions(section, [&](const std::string& name, const std::unique_ptr& def) { + bool has_comment = false; + // TODO: as above, this will create empty objects + // TODO: as above (but more important): this won't handle definitions with no + // entries + // (i.e. those handled by UndeclaredValueHandler's) + for (const std::string& comment : definition_comments[section][name]) + { + fmt::format_to(sect_append, "\n# {}", comment); + has_comment = true; + } + + if (useValues and def->get_number_found() > 0) + { + for (const auto& val : def->values_as_string()) + fmt::format_to(sect_append, "\n{}={}", name, val); + *sect_append = '\n'; + } + else if (not def->hidden) + { + if (auto defaults = def->default_values_as_string(); not defaults.empty()) + for (const auto& val : defaults) + fmt::format_to(sect_append, "\n{}{}={}", def->required ? "" : "#", name, val); + else + // We have no defaults so we append it as "#opt-name=" so that we show + // the option name, and make it simple to uncomment and edit to the + // desired value. + fmt::format_to(sect_append, "\n#{}=", name); + *sect_append = '\n'; + } + else if (has_comment) + *sect_append = '\n'; + }); + + if (sect_str.empty()) + return; // Skip sections with no options + + if (sectionsVisited > 0) + ini += "\n\n"; + + fmt::format_to(ini_append, "[{}]\n", section); + + // TODO: this will create empty objects as a side effect of map's operator[] + // TODO: this also won't handle sections which have no definition + for (const std::string& comment : section_comments[section]) + { + fmt::format_to(ini_append, "# {}\n", comment); + } + ini += "\n"; + ini += sect_str; + + sectionsVisited++; + }); + + return ini; + } + + const std::unique_ptr& ConfigDefinition::lookup_definition_or_throw( + std::string_view section, std::string_view name) const + { + const auto sectionItr = definitions.find(std::string(section)); + if (sectionItr == definitions.end()) + throw std::invalid_argument{"No config section [{}]"_format(section)}; + + auto& sectionDefinitions = sectionItr->second; + const auto definitionItr = sectionDefinitions.find(std::string(name)); + if (definitionItr == sectionDefinitions.end()) + throw std::invalid_argument{"No config item {} within section {}"_format(name, section)}; + + return definitionItr->second; + } + + std::unique_ptr& ConfigDefinition::lookup_definition_or_throw( + std::string_view section, std::string_view name) + { + return const_cast&>( + const_cast(this)->lookup_definition_or_throw(section, name)); + } + + void ConfigDefinition::visit_sections(SectionVisitor visitor) const + { + for (const std::string& section : section_ordering) + { + const auto itr = definitions.find(section); + assert(itr != definitions.end()); + visitor(section, itr->second); + } + }; + void ConfigDefinition::visit_definitions(const std::string& section, DefVisitor visitor) const + { + const auto& defs = definitions.at(section); + const auto& defOrdering = definition_ordering.at(section); + for (const std::string& name : defOrdering) + { + const auto itr = defs.find(name); + assert(itr != defs.end()); + visitor(name, itr->second); + } + }; } // namespace llarp diff --git a/llarp/config/definition.hpp b/llarp/config/definition.hpp index bd8d0fc173..bc5fe6260c 100644 --- a/llarp/config/definition.hpp +++ b/llarp/config/definition.hpp @@ -1,602 +1,565 @@ #pragma once -#include -#include -#include #include -#include +#include + +#include +#include +#include +#include #include #include +#include #include #include #include +#include #include #include -#include -#include -#include + +namespace fs = std::filesystem; namespace llarp { - namespace config - { - // Base class for the following option flag types - struct option_flag - {}; - - struct Required_t : option_flag - {}; - struct Hidden_t : option_flag - {}; - struct MultiValue_t : option_flag - {}; - struct RelayOnly_t : option_flag - {}; - struct ClientOnly_t : option_flag - {}; - struct Deprecated_t : option_flag - {}; - - /// Value to pass for an OptionDefinition to indicate that the option is required - inline constexpr Required_t Required{}; - /// Value to pass for an OptionDefinition to indicate that the option should be hidden from the - /// generate config file if it is unset (and has no comment). Typically for deprecated, renamed - /// options that still do something, and for internal dev options that aren't usefully exposed. - /// (For do-nothing deprecated options use Deprecated instead). - inline constexpr Hidden_t Hidden{}; - /// Value to pass for an OptionDefinition to indicate that the option takes multiple values - inline constexpr MultiValue_t MultiValue{}; - /// Value to pass for an option that should only be set for relay configs. If found in a client - /// config it be ignored (but will produce a warning). - inline constexpr RelayOnly_t RelayOnly{}; - /// Value to pass for an option that should only be set for client configs. If found in a relay - /// config it will be ignored (but will produce a warning). - inline constexpr ClientOnly_t ClientOnly{}; - /// Value to pass for an option that is deprecated and does nothing and should be ignored (with - /// a deprecation warning) if specified. Note that Deprecated implies Hidden, and that - /// {client,relay}-only options in a {relay,client} config are also considered Deprecated. - inline constexpr Deprecated_t Deprecated{}; - - /// Wrapper to specify a default value to an OptionDefinition - template - struct Default + namespace config { - T val; - constexpr explicit Default(T val) : val{std::move(val)} - {} - }; + namespace flag + { + // Base class for the following option flag types + struct opt + {}; + + struct REQUIRED : opt + {}; + struct HIDDEN : opt + {}; + struct MULTIVALUE : opt + {}; + struct RELAYONLY : opt + {}; + struct CLIENTONLY : opt + {}; + struct DEPRECATED : opt + {}; + } // namespace flag + + /// Value to pass for an OptionDefinition to indicate that the option is required + inline constexpr flag::REQUIRED Required{}; + /// Value to pass for an OptionDefinition to indicate that the option should be hidden from + /// the generate config file if it is unset (and has no comment). Typically for deprecated, + /// renamed options that still do something, and for internal dev options that aren't + /// usefully exposed. (For do-nothing deprecated options use Deprecated instead). + inline constexpr flag::HIDDEN Hidden{}; + /// Value to pass for an OptionDefinition to indicate that the option takes multiple values + inline constexpr flag::MULTIVALUE MultiValue{}; + /// Value to pass for an option that should only be set for relay configs. If found in a + /// client config it be ignored (but will produce a warning). + inline constexpr flag::RELAYONLY RelayOnly{}; + /// Value to pass for an option that should only be set for client configs. If found in a + /// relay config it will be ignored (but will produce a warning). + inline constexpr flag::CLIENTONLY ClientOnly{}; + /// Value to pass for an option that is deprecated and does nothing and should be ignored + /// (with a deprecation warning) if specified. Note that Deprecated implies Hidden, and + /// that {client,relay}-only options in a {relay,client} config are also considered + /// Deprecated. + inline constexpr flag::DEPRECATED Deprecated{}; + + /// Wrapper to specify a default value to an OptionDefinition + template + struct Default + { + T val; + constexpr explicit Default(T val) : val{std::move(val)} {} + }; - /// Adds one or more comment lines to the option definition. - struct Comment + /// Adds one or more comment lines to the option definition. + struct Comment + { + std::vector comments; + explicit Comment(std::initializer_list comments) : comments{std::move(comments)} {} + }; + + /// A convenience function that returns an acceptor which assigns to a reference. + /// + /// Note that this holds on to the reference; it must only be used when this is safe to do. + /// In particular, a reference to a local variable may be problematic. + template + auto assignment_acceptor(T& ref) + { + return [&ref](T arg) { ref = std::move(arg); }; + } + + // C++20 backport: + template + using remove_cvref_t = std::remove_cv_t>; + + template + constexpr bool is_default = false; + template + constexpr bool is_default> = true; + template + constexpr bool is_default = is_default>; + + template + constexpr bool is_default_array = false; + template + constexpr bool is_default_array, N>> = true; + template + constexpr bool is_default_array = is_default_array>; + + template + constexpr bool is_option = + std::is_base_of_v> or std::is_same_v + or is_default