diff --git a/.gitmodules b/.gitmodules index 2477762b8..7ba32fba6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,16 +10,10 @@ [submodule "test/Catch2"] path = test/Catch2 url = https://github.com/catchorg/Catch2 -[submodule "external/date"] - path = external/date - url = https://github.com/HowardHinnant/date.git [submodule "external/pybind11"] path = external/pybind11 url = https://github.com/pybind/pybind11 branch = stable -[submodule "external/clang-format-hooks"] - path = external/clang-format-hooks - url = https://github.com/barisione/clang-format-hooks/ [submodule "external/sqlite_orm"] path = external/sqlite_orm url = https://github.com/fnc12/sqlite_orm @@ -35,3 +29,13 @@ [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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d0244044..13514b766 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,15 @@ -cmake_minimum_required(VERSION 3.10) # bionic's cmake version +cmake_minimum_required(VERSION 3.13...3.24) # 3.13 is buster's version set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Has to be set before `project()`, and ignored on non-macos: -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "macOS deployment target (Apple clang only)") +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) + set(LANGS C CXX) -if(APPLE) +if(APPLE AND BUILD_DAEMON) set(LANGS ${LANGS} OBJC Swift) endif() @@ -22,26 +25,25 @@ endif() project(lokinet - VERSION 0.9.8 + VERSION 0.9.11 DESCRIPTION "lokinet - IP packet onion router" LANGUAGES ${LANGS}) if(APPLE) # Apple build number: must be incremented to submit a new build for the same lokinet version, # should be reset to 0 when the lokinet version increments. - set(LOKINET_APPLE_BUILD 0) + set(LOKINET_APPLE_BUILD 6) endif() -set(RELEASE_MOTTO "A Series of Tubes" CACHE STRING "Release motto") +set(RELEASE_MOTTO "Our Lord And Savior" CACHE STRING "Release motto") -add_definitions(-DLLARP_VERSION_MAJOR=${lokinet_VERSION_MAJOR}) -add_definitions(-DLLARP_VERSION_MINOR=${lokinet_VERSION_MINOR}) -add_definitions(-DLLARP_VERSION_PATCH=${lokinet_VERSION_PATCH}) -if(RELEASE_MOTTO AND CMAKE_BUILD_TYPE MATCHES "[Rr][Ee][Ll][Ee][Aa][Ss][Ee]") - add_definitions(-DLLARP_RELEASE_MOTTO="${RELEASE_MOTTO}") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +set(DEFAULT_WITH_BOOTSTRAP ON) +if(APPLE) + set(DEFAULT_WITH_BOOTSTRAP OFF) endif() -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") # Core options option(USE_AVX2 "enable avx2 code" OFF) @@ -49,20 +51,26 @@ option(USE_NETNS "enable networking namespace support. Linux only" OFF) option(NATIVE_BUILD "optimise for host system and FPU" ON) option(EMBEDDED_CFG "optimise for older hardware or embedded systems" OFF) option(BUILD_LIBLOKINET "build liblokinet.so" ON) -option(SHADOW "use shadow testing framework. linux only" OFF) option(XSAN "use sanitiser, if your system has it (requires -DCMAKE_BUILD_TYPE=Debug)" OFF) option(USE_JEMALLOC "Link to jemalloc for memory allocations, if found" ON) option(TESTNET "testnet build" OFF) option(WITH_COVERAGE "generate coverage data" OFF) -option(USE_SHELLHOOKS "enable shell hooks on compile time (dangerous)" OFF) option(WARNINGS_AS_ERRORS "treat all warnings as errors. turn off for development, on for release" OFF) -option(TRACY_ROOT "include tracy profiler source" OFF) -option(WITH_TESTS "build unit tests" ON) +option(WITH_TESTS "build unit tests" OFF) option(WITH_HIVE "build simulation stubs" OFF) option(BUILD_PACKAGE "builds extra components for making an installer (with 'make package')" OFF) +option(WITH_BOOTSTRAP "build lokinet-bootstrap tool" ${DEFAULT_WITH_BOOTSTRAP}) +option(WITH_PEERSTATS "build with experimental peerstats db support" OFF) +option(STRIP_SYMBOLS "strip off all debug symbols into an external archive for all executables built" OFF) + +set(BOOTSTRAP_FALLBACK_MAINNET "${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed" CACHE PATH "Fallback bootstrap path (mainnet)") +set(BOOTSTRAP_FALLBACK_TESTNET "${PROJECT_SOURCE_DIR}/contrib/bootstrap/testnet.signed" CACHE PATH "Fallback bootstrap path (testnet)") include(cmake/enable_lto.cmake) +option(CROSS_PLATFORM "cross compiler platform" "Linux") +option(CROSS_PREFIX "toolchain cross compiler prefix" "") + 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) @@ -77,9 +85,17 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() +set(debug OFF) +if(CMAKE_BUILD_TYPE MATCHES "[Dd][Ee][Bb][Uu][Gg]") + set(debug ON) + 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) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "") endif() include(CheckCXXSourceCompiles) @@ -93,19 +109,18 @@ set(CMAKE_C_EXTENSIONS OFF) include(cmake/target_link_libraries_system.cmake) include(cmake/add_import_library.cmake) -include(cmake/add_log_tag.cmake) include(cmake/libatomic.cmake) -include(cmake/link_dep_libs.cmake) if (STATIC_LINK) set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) message(STATUS "setting static library suffix search") endif() -add_definitions(-D${CMAKE_SYSTEM_NAME}) +include(cmake/gui-option.cmake) include(cmake/solaris.cmake) include(cmake/win32.cmake) +include(cmake/macos.cmake) # No in-source building include(MacroEnsureOutOfSourceBuild) @@ -170,32 +185,19 @@ if(NOT TARGET sodium) export(TARGETS sodium NAMESPACE sodium:: FILE sodium-exports.cmake) endif() -option(FORCE_OXENMQ_SUBMODULE "force using oxenmq submodule" OFF) -if(NOT FORCE_OXENMQ_SUBMODULE) - pkg_check_modules(OXENMQ liboxenmq>=1.2.4) +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(OXENMQ_FOUND) - add_library(oxenmq INTERFACE) - link_dep_libs(oxenmq INTERFACE "${OXENMQ_LIBRARY_DIRS}" ${OXENMQ_LIBRARIES}) - target_include_directories(oxenmq INTERFACE ${OXENMQ_INCLUDE_DIRS}) - add_library(oxenmq::oxenmq ALIAS oxenmq) - message(STATUS "Found system liboxenmq ${OXENMQ_VERSION}") +if(WARN_DEPRECATED) + list(APPEND warning_flags -Wdeprecated-declarations) else() - message(STATUS "using oxenmq submodule") - add_subdirectory(${CMAKE_SOURCE_DIR}/external/oxen-mq) -endif() - - -if(NOT APPLE) - add_compile_options(-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations -Werror=vla) - if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wno-unknown-warning-option) - endif() + list(APPEND warning_flags -Wno-deprecated-declarations) endif() -if (NOT CMAKE_SYSTEM_NAME MATCHES "Linux" AND SHADOW) - message( FATAL_ERROR "shadow-framework is Linux only" ) -endif() +# 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}>") if(XSAN) string(APPEND CMAKE_CXX_FLAGS_DEBUG " -fsanitize=${XSAN} -fno-omit-frame-pointer -fno-sanitize-recover") @@ -205,20 +207,6 @@ if(XSAN) message(STATUS "Doing a ${XSAN} sanitizer build") endif() -if(CMAKE_BUILD_TYPE MATCHES "[Dd][Ee][Bb][Uu][Gg]") - add_definitions(-DLOKINET_DEBUG=1) -endif() - -if(WITH_SHELLHOOKS) - add_definitions(-DENABLE_SHELLHOOKS) -endif() - -if(TRACY_ROOT) - include_directories(${TRACY_ROOT}) - add_definitions(-DTRACY_ENABLE) -endif() - - include(cmake/coverage.cmake) # these vars are set by the cmake toolchain spec @@ -247,22 +235,12 @@ set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) -if(USE_NETNS) - add_definitions(-DNETNS=1) -else() - add_definitions(-DNETNS=0) -endif() - if(TESTNET) - add_definitions(-DTESTNET=1) + add_definitions(-DTESTNET) # 5 times slower than realtime # add_definitions(-DTESTNET_SPEED=5) endif() -if(SHADOW) - include(cmake/shadow.cmake) -endif() - unset(GIT_VERSION) unset(GIT_VERSION_REAL) @@ -297,7 +275,6 @@ if(WITH_SYSTEMD AND (NOT ANDROID)) endif() add_subdirectory(external) -include_directories(SYSTEM external/sqlite_orm/include) if(USE_JEMALLOC AND NOT STATIC_LINK) pkg_check_modules(JEMALLOC jemalloc IMPORTED_TARGET) @@ -317,33 +294,34 @@ if(ANDROID) set(ANDROID_PLATFORM_SRC android/ifaddrs.c) endif() -if(TRACY_ROOT) - target_link_libraries(base_libs INTERFACE dl) -endif() - if(WITH_HIVE) - add_definitions(-DLOKINET_HIVE=1) + add_definitions(-DLOKINET_HIVE) endif() add_subdirectory(crypto) add_subdirectory(llarp) -add_subdirectory(daemon) - +if(BUILD_DAEMON) + add_subdirectory(daemon) +endif() if(WITH_HIVE) add_subdirectory(pybind) endif() -if (NOT SHADOW) - if(WITH_TESTS OR WITH_HIVE) - add_subdirectory(test) - endif() - if(ANDROID) - add_subdirectory(jni) - endif() +if(WITH_TESTS OR WITH_HIVE) + add_subdirectory(test) +endif() +if(ANDROID) + add_subdirectory(jni) endif() add_subdirectory(docs) +include(cmake/gui.cmake) + +if(APPLE) + macos_target_setup() +endif() + # uninstall target if(NOT TARGET uninstall) configure_file( @@ -355,7 +333,10 @@ if(NOT TARGET uninstall) COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) endif() - if(BUILD_PACKAGE AND NOT APPLE) - include(cmake/installer.cmake) + include(cmake/installer.cmake) +endif() + +if(TARGET package) + add_dependencies(package assemble_gui) endif() diff --git a/CMakeSettings.json b/CMakeSettings.json deleted file mode 100644 index 20e32e018..000000000 --- a/CMakeSettings.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "configurations": [ - { - "name": "x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "variables": [] - }, - { - "name": "x64-Release", - "generator": "Ninja", - "configurationType": "RelWithDebInfo", - "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "inheritEnvironments": [ "msvc_x64_x64" ], - "variables": [] - } - ] -} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d8e5deaf..9a7a0e67f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ * Act like a responsible adult. -* RUN `make format` BEFORE COMMITING ALWAYS. +* RUN `./contrib/format.sh` BEFORE COMMITING ALWAYS. # Do NOT diff --git a/LICENSE.txt b/LICENSE similarity index 98% rename from LICENSE.txt rename to LICENSE index 49211c38d..e72bfddab 100644 --- a/LICENSE.txt +++ b/LICENSE @@ -1,16 +1,3 @@ -LokiNET is the reference implementation of LLARP (Low Latency Anonymous -Routing Protocol). - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Copyright (c) 2018-2020 The Loki Project -Copyright (c) 2018-2020 Jeff Becker -Windows NT port and portions Copyright (c) 2018-2020 Rick V. - - GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 @@ -684,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. \ No newline at end of file diff --git a/cmake/GenVersion.cmake b/cmake/GenVersion.cmake index 21aeab9df..9cda8b8ae 100644 --- a/cmake/GenVersion.cmake +++ b/cmake/GenVersion.cmake @@ -57,4 +57,4 @@ else() endif() endif() -configure_file("${SRC}" "${DEST}") +configure_file("${SRC}" "${DEST}" @ONLY) diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index 87ba00a81..78e77a370 100644 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -5,32 +5,32 @@ set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads") -set(OPENSSL_VERSION 1.1.1l CACHE STRING "openssl version") +set(OPENSSL_VERSION 3.0.7 CACHE STRING "openssl version") set(OPENSSL_MIRROR ${LOCAL_MIRROR} https://www.openssl.org/source CACHE STRING "openssl download mirror(s)") set(OPENSSL_SOURCE openssl-${OPENSSL_VERSION}.tar.gz) -set(OPENSSL_HASH SHA256=0b7a3e5e59c34827fe0c3a74b7ec8baef302b98fa80088d7f9153aa16fa76bd1 +set(OPENSSL_HASH SHA256=83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e CACHE STRING "openssl source hash") -set(EXPAT_VERSION 2.3.0 CACHE STRING "expat version") +set(EXPAT_VERSION 2.5.0 CACHE STRING "expat version") string(REPLACE "." "_" EXPAT_TAG "R_${EXPAT_VERSION}") set(EXPAT_MIRROR ${LOCAL_MIRROR} https://github.com/libexpat/libexpat/releases/download/${EXPAT_TAG} CACHE STRING "expat download mirror(s)") set(EXPAT_SOURCE expat-${EXPAT_VERSION}.tar.xz) -set(EXPAT_HASH SHA512=dde8a9a094b18d795a0e86ca4aa68488b352dc67019e0d669e8b910ed149628de4c2a49bc3a5b832f624319336a01f9e4debe03433a43e1c420f36356d886820 +set(EXPAT_HASH SHA512=2da73b991b7c0c54440485c787e5edeb3567230204e31b3cac1c3a6713ec6f9f1554d3afffc0f8336168dfd5df02db4a69bcf21b4d959723d14162d13ab87516 CACHE STRING "expat source hash") -set(UNBOUND_VERSION 1.13.2 CACHE STRING "unbound version") +set(UNBOUND_VERSION 1.17.0 CACHE STRING "unbound version") set(UNBOUND_MIRROR ${LOCAL_MIRROR} https://nlnetlabs.nl/downloads/unbound CACHE STRING "unbound download mirror(s)") set(UNBOUND_SOURCE unbound-${UNBOUND_VERSION}.tar.gz) -set(UNBOUND_HASH SHA256=0a13b547f3b92a026b5ebd0423f54c991e5718037fd9f72445817f6a040e1a83 +set(UNBOUND_HASH SHA512=f6b9f279330fb19b5feca09524959940aad8c4e064528aa82b369c726d77e9e8e5ca23f366f6e9edcf2c061b96f482ed7a2c26ac70fc15ae5762b3d7e36a5284 CACHE STRING "unbound source hash") -set(SQLITE3_VERSION 3350500 CACHE STRING "sqlite3 version") -set(SQLITE3_MIRROR ${LOCAL_MIRROR} https://www.sqlite.org/2021 +set(SQLITE3_VERSION 3400000 CACHE STRING "sqlite3 version") +set(SQLITE3_MIRROR ${LOCAL_MIRROR} https://www.sqlite.org/2022 CACHE STRING "sqlite3 download mirror(s)") set(SQLITE3_SOURCE sqlite-autoconf-${SQLITE3_VERSION}.tar.gz) -set(SQLITE3_HASH SHA512=039af796f79fc4517be0bd5ba37886264d49da309e234ae6fccdb488ef0109ed2b917fc3e6c1fc7224dff4f736824c653aaf8f0a37550c5ebc14d035cb8ac737 - CACHE STRING "sqlite3 source hash") +set(SQLITE3_HASH SHA3_256=7ee8f02b21edb4489df5082b5cf5b7ef47bcebcdb0e209bf14240db69633c878 + CACHE STRING "sqlite3 source hash") set(SODIUM_VERSION 1.0.18 CACHE STRING "libsodium version") set(SODIUM_MIRROR ${LOCAL_MIRROR} @@ -48,29 +48,27 @@ set(ZMQ_SOURCE zeromq-${ZMQ_VERSION}.tar.gz) set(ZMQ_HASH SHA512=e198ef9f82d392754caadd547537666d4fba0afd7d027749b3adae450516bcf284d241d4616cad3cb4ad9af8c10373d456de92dc6d115b037941659f141e7c0e CACHE STRING "libzmq source hash") -set(LIBUV_VERSION 1.41.0 CACHE STRING "libuv version") +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=33613fa28e8136507300eba374351774849b6b39aab4e53c997a918d3bc1d1094c6123e0e509535095b14dc5daa885eadb1a67bed46622ad3cc79d62dc817e84 +set(LIBUV_HASH SHA512=91197ff9303112567bbb915bbb88058050e2ad1c048815a3b57c054635d5dc7df458b956089d785475290132236cb0edcfae830f5d749de29a9a3213eeaf0b20 CACHE STRING "libuv source hash") -set(ZLIB_VERSION 1.2.11 CACHE STRING "zlib version") +set(ZLIB_VERSION 1.2.13 CACHE STRING "zlib version") set(ZLIB_MIRROR ${LOCAL_MIRROR} https://zlib.net CACHE STRING "zlib mirror(s)") -set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.gz) -set(ZLIB_HASH SHA512=73fd3fff4adeccd4894084c15ddac89890cd10ef105dd5e1835e1e9bbb6a49ff229713bd197d203edfa17c2727700fce65a2a235f07568212d820dca88b528ae - CACHE STRING "zlib source hash") - -set(CURL_VERSION 7.76.1 CACHE STRING "curl version") +set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.xz) +set(ZLIB_HASH SHA256=d14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98 + CACHE STRING "zlib source hash") + +set(CURL_VERSION 7.86.0 CACHE STRING "curl version") set(CURL_MIRROR ${LOCAL_MIRROR} https://curl.haxx.se/download https://curl.askapache.com CACHE STRING "curl mirror(s)") set(CURL_SOURCE curl-${CURL_VERSION}.tar.xz) -set(CURL_HASH SHA256=64bb5288c39f0840c07d077e30d9052e1cbb9fa6c2dc52523824cc859e679145 +set(CURL_HASH SHA512=18e03a3c00f22125e07bddb18becbf5acdca22baeb7b29f45ef189a5c56f95b2d51247813f7a9a90f04eb051739e9aa7d3a1c5be397bae75d763a2b918d1b656 CACHE STRING "curl source hash") - - include(ExternalProject) set(DEPS_DESTDIR ${CMAKE_BINARY_DIR}/static-deps) @@ -127,21 +125,21 @@ if(ANDROID) set(android_toolchain_prefix x86_64) set(android_toolchain_suffix linux-android) elseif(CMAKE_ANDROID_ARCH_ABI MATCHES x86) - set(android_machine i686) + set(android_machine x86) set(cross_host "--host=i686-linux-android") set(android_compiler_prefix i686) set(android_compiler_suffix linux-android23) set(android_toolchain_prefix i686) set(android_toolchain_suffix linux-android) elseif(CMAKE_ANDROID_ARCH_ABI MATCHES armeabi-v7a) - set(android_machine armv7) + set(android_machine arm) set(cross_host "--host=armv7a-linux-androideabi") set(android_compiler_prefix armv7a) set(android_compiler_suffix linux-androideabi23) set(android_toolchain_prefix arm) set(android_toolchain_suffix linux-androideabi) elseif(CMAKE_ANDROID_ARCH_ABI MATCHES arm64-v8a) - set(android_machine aarch64) + set(android_machine arm64) set(cross_host "--host=aarch64-linux-android") set(android_compiler_prefix aarch64) set(android_compiler_suffix linux-android23) @@ -169,6 +167,11 @@ if(APPLE) set(deps_CXXFLAGS "${deps_CXXFLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") endif() +if(_winver) + set(deps_CFLAGS "${deps_CFLAGS} -D_WIN32_WINNT=${_winver}") + set(deps_CXXFLAGS "${deps_CXXFLAGS} -D_WIN32_WINNT=${_winver}") +endif() + if("${CMAKE_GENERATOR}" STREQUAL "Unix Makefiles") set(_make $(MAKE)) @@ -225,7 +228,7 @@ build_external(libuv 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 @@ -236,24 +239,51 @@ add_static_target(zlib zlib_external libz.a) set(openssl_system_env "") +set(openssl_arch "") +set(openssl_configure_command ./config) +set(openssl_flags "CFLAGS=${deps_CFLAGS}") if(CMAKE_CROSSCOMPILING) if(ARCH_TRIPLET STREQUAL x86_64-w64-mingw32) - set(openssl_system_env SYSTEM=MINGW64 RC=${CMAKE_RC_COMPILER} AR=${ARCH_TRIPLET}-ar RANLIB=${ARCH_TRIPLET}-ranlib) + set(openssl_arch mingw64) + set(openssl_system_env RC=${CMAKE_RC_COMPILER} AR=${ARCH_TRIPLET}-ar RANLIB=${ARCH_TRIPLET}-ranlib) elseif(ARCH_TRIPLET STREQUAL i686-w64-mingw32) - set(openssl_system_env SYSTEM=MINGW32 RC=${CMAKE_RC_COMPILER} AR=${ARCH_TRIPLET}-ar RANLIB=${ARCH_TRIPLET}-ranlib) + set(openssl_arch mingw) + set(openssl_system_env RC=${CMAKE_RC_COMPILER} AR=${ARCH_TRIPLET}-ar RANLIB=${ARCH_TRIPLET}-ranlib) elseif(ANDROID) - set(openssl_system_env SYSTEM=Linux MACHINE=${android_machine} LD=${deps_ld} RANLIB=${deps_ranlib} AR=${deps_ar}) + set(openssl_arch android-${android_machine}) + set(openssl_system_env LD=${deps_ld} RANLIB=${deps_ranlib} AR=${deps_ar} ANDROID_NDK_ROOT=${CMAKE_ANDROID_NDK} "PATH=${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin:$ENV{PATH}") + list(APPEND openssl_flags "CPPFLAGS=-D__ANDROID_API__=${ANDROID_API}") set(openssl_extra_opts no-asm) + elseif(ARCH_TRIPLET STREQUAL mips64-linux-gnuabi64) + set(openssl_arch linux-mips64) + elseif(ARCH_TRIPLET STREQUAL mips-linux-gnu) + set(openssl_arch linux-mips32) + elseif(ARCH_TRIPLET STREQUAL mipsel-linux-gnu) + set(openssl_arch linux-mips) + elseif(ARCH_TRIPLET STREQUAL aarch64-linux-gnu) + # cross compile arm64 + set(openssl_arch linux-aarch64) + elseif(ARCH_TRIPLET MATCHES arm-linux) + # cross compile armhf + set(openssl_arch linux-armv4) + elseif(ARCH_TRIPLET MATCHES powerpc64le) + # cross compile ppc64le + set(openssl_arch linux-ppc64le) endif() elseif(CMAKE_C_FLAGS MATCHES "-march=armv7") # Help openssl figure out that we're building from armv7 even if on armv8 hardware: - set(openssl_system_env SYSTEM=Linux MACHINE=armv7) + set(openssl_arch linux-armv4) endif() + + build_external(openssl - CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env CC=${deps_cc} ${openssl_system_env} ./config - --prefix=${DEPS_DESTDIR} ${openssl_extra_opts} no-shared no-capieng no-dso no-dtls1 no-ec_nistp_64_gcc_128 no-gost - no-heartbeats no-md2 no-rc5 no-rdrand no-rfc3779 no-sctp no-ssl-trace no-ssl2 no-ssl3 - no-static-engine no-tests no-weak-ssl-ciphers no-zlib no-zlib-dynamic "CFLAGS=${deps_CFLAGS}" + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env CC=${deps_cc} ${openssl_system_env} ${openssl_configure_command} + --prefix=${DEPS_DESTDIR} --libdir=lib ${openssl_extra_opts} + no-shared no-capieng no-dso no-dtls1 no-ec_nistp_64_gcc_128 no-gost + no-md2 no-rc5 no-rdrand no-rfc3779 no-sctp no-ssl-trace no-ssl3 + no-static-engine no-tests no-weak-ssl-ciphers no-zlib no-zlib-dynamic ${openssl_flags} + ${openssl_arch} + BUILD_COMMAND ${CMAKE_COMMAND} -E env ${openssl_system_env} ${_make} INSTALL_COMMAND ${_make} install_sw BUILD_BYPRODUCTS ${DEPS_DESTDIR}/lib/libssl.a ${DEPS_DESTDIR}/lib/libcrypto.a @@ -267,7 +297,6 @@ endif() set(OPENSSL_INCLUDE_DIR ${DEPS_DESTDIR}/include) set(OPENSSL_CRYPTO_LIBRARY ${DEPS_DESTDIR}/lib/libcrypto.a ${DEPS_DESTDIR}/lib/libssl.a) -set(OPENSSL_VERSION 1.1.1) set(OPENSSL_ROOT_DIR ${DEPS_DESTDIR}) build_external(expat @@ -278,8 +307,14 @@ build_external(expat add_static_target(expat expat_external libexpat.a) +if(WIN32) + set(unbound_patch + PATCH_COMMAND ${PROJECT_SOURCE_DIR}/contrib/apply-patches.sh + ${PROJECT_SOURCE_DIR}/contrib/patches/unbound-delete-crash-fix.patch) +endif() build_external(unbound DEPENDS openssl_external expat_external + ${unbound_patch} CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} --prefix=${DEPS_DESTDIR} --disable-shared --enable-static --with-libunbound-only --with-pic --$,enable,disable>-flto --with-ssl=${DEPS_DESTDIR} @@ -299,8 +334,11 @@ build_external(sodium CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} -- --enable-static --with-pic "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS}") add_static_target(sodium sodium_external libsodium.a) -build_external(sqlite3) -add_static_target(sqlite3 sqlite3_external libsqlite3.a) + +if(WITH_PEERSTATS_BACKEND) + build_external(sqlite3) + add_static_target(sqlite3 sqlite3_external libsqlite3.a) +endif() if(ARCH_TRIPLET MATCHES mingw) @@ -312,7 +350,9 @@ 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-closesocket.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 @@ -335,6 +375,19 @@ set_target_properties(libzmq PROPERTIES INTERFACE_LINK_LIBRARIES "${libzmq_link_libs}" INTERFACE_COMPILE_DEFINITIONS "ZMQ_STATIC") + +# +# +# +# Everything that follows is *only* for lokinet-bootstrap (i.e. if adding new deps put them *above* +# this). +# +# +# +if(NOT WITH_BOOTSTRAP) + return() +endif() + set(curl_extra) if(WIN32) set(curl_ssl_opts --without-ssl --with-schannel) @@ -386,7 +439,7 @@ foreach(curl_arch ${curl_arches}) --enable-crypto-auth --disable-ntlm-wb --disable-tls-srp --disable-unix-sockets --disable-cookies --enable-http-auth --enable-doh --disable-mime --enable-dateparse --disable-netrc --without-libidn2 --disable-progress-meter --without-brotli --with-zlib=${DEPS_DESTDIR} ${curl_ssl_opts} - --without-libmetalink --without-librtmp --disable-versioned-symbols --enable-hidden-symbols + --without-librtmp --disable-versioned-symbols --enable-hidden-symbols --without-zsh-functions-dir --without-fish-functions-dir --without-nghttp3 --without-zstd "CC=${deps_cc}" "CFLAGS=${deps_noarch_CFLAGS}${cflags_extra}" ${curl_extra} @@ -400,7 +453,7 @@ foreach(curl_arch ${curl_arches}) list(APPEND curl_lib_outputs ${curl_prefix}/lib/libcurl.a) endforeach() -message(STATUS "TARGETS: ${curl_lib_targets}") + if(IOS AND num_arches GREATER 1) # We are building multiple architectures for different iOS devices, so we need to glue the diff --git a/cmake/Version.cmake b/cmake/Version.cmake index 45037a081..9b786ce85 100644 --- a/cmake/Version.cmake +++ b/cmake/Version.cmake @@ -1,22 +1,46 @@ +# We do this via a custom command that re-invokes a cmake script because we need the DEPENDS on .git/index so that we will re-run it (to regenerate the commit tag in the version) whenever the current commit changes. If we used a configure_file directly here, it would only re-run when something else causes cmake to re-run. -find_package(Git QUIET) -set(GIT_INDEX_FILE "${PROJECT_SOURCE_DIR}/.git/index") -if(EXISTS ${GIT_INDEX_FILE} AND ( GIT_FOUND OR Git_FOUND) ) - message(STATUS "Found Git: ${GIT_EXECUTABLE}") - - add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" - COMMAND "${CMAKE_COMMAND}" - "-D" "GIT=${GIT_EXECUTABLE}" - "-D" "SRC=${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" - "-D" "DEST=${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" - "-P" "${CMAKE_CURRENT_LIST_DIR}/GenVersion.cmake" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" - "${GIT_INDEX_FILE}") +if(LOKINET_VERSIONTAG) + set(VERSIONTAG "${LOKINET_VERSIONTAG}") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" @ONLY) else() - message(STATUS "Git was not found! Setting version to to nogit") - set(VERSIONTAG "nogit") - configure_file("${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp") + set(VERSIONTAG "${GIT_VERSION}") + set(GIT_INDEX_FILE "${PROJECT_SOURCE_DIR}/.git/index") + find_package(Git) + if(EXISTS "${GIT_INDEX_FILE}" AND ( GIT_FOUND OR Git_FOUND) ) + message(STATUS "Found Git: ${GIT_EXECUTABLE}") + set(genversion_args "-DGIT=${GIT_EXECUTABLE}") + foreach(v lokinet_VERSION lokinet_VERSION_MAJOR lokinet_VERSION_MINOR lokinet_VERSION_PATCH RELEASE_MOTTO) + list(APPEND genversion_args "-D${v}=${${v}}") + endforeach() + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" + COMMAND "${CMAKE_COMMAND}" + ${genversion_args} + "-D" "SRC=${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" + "-D" "DEST=${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" + "-P" "${CMAKE_CURRENT_LIST_DIR}/GenVersion.cmake" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" + "${GIT_INDEX_FILE}") + else() + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" @ONLY) + endif() endif() -add_custom_target(genversion DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp") + +if(WIN32) + foreach(exe IN ITEMS lokinet lokinet-vpn lokinet-bootstrap) + set(lokinet_EXE_NAME "${exe}.exe") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/win32/version.rc.in" "${CMAKE_BINARY_DIR}/${exe}.rc" @ONLY) + set_property(SOURCE "${CMAKE_BINARY_DIR}/${exe}.rc" PROPERTY GENERATED 1) + endforeach() +endif() + +add_custom_target(genversion_cpp DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp") +if(WIN32) + add_custom_target(genversion_rc DEPENDS "${CMAKE_BINARY_DIR}/lokinet.rc" "${CMAKE_BINARY_DIR}/lokinet-vpn.rc" "${CMAKE_BINARY_DIR}/lokinet-bootstrap.rc") +else() + add_custom_target(genversion_rc) +endif() +add_custom_target(genversion DEPENDS genversion_cpp genversion_rc) diff --git a/cmake/add_log_tag.cmake b/cmake/add_log_tag.cmake deleted file mode 100644 index b86ab5717..000000000 --- a/cmake/add_log_tag.cmake +++ /dev/null @@ -1,7 +0,0 @@ -function(add_log_tag target) - get_target_property(TARGET_SRCS ${target} SOURCES) - foreach(F ${TARGET_SRCS}) - get_filename_component(fpath "${F}" ABSOLUTE) - set_property(SOURCE ${F} APPEND PROPERTY COMPILE_DEFINITIONS SOURCE_ROOT=\"${PROJECT_SOURCE_DIR}\") - endforeach() -endfunction() diff --git a/cmake/check_for_std_filesystem.cmake b/cmake/check_for_std_filesystem.cmake index aef0448ec..84248d07e 100644 --- a/cmake/check_for_std_filesystem.cmake +++ b/cmake/check_for_std_filesystem.cmake @@ -44,6 +44,7 @@ if(filesystem_is_good EQUAL 1) 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) diff --git a/cmake/enable_lto.cmake b/cmake/enable_lto.cmake index c315c6cff..dd81906bb 100644 --- a/cmake/enable_lto.cmake +++ b/cmake/enable_lto.cmake @@ -2,6 +2,9 @@ include(CheckIPOSupported) option(WITH_LTO "enable lto on compile time" ON) if(WITH_LTO) + if(WIN32) + message(FATAL_ERROR "LTO not supported on win32 targets, please set -DWITH_LTO=OFF") + endif() check_ipo_supported(RESULT IPO_ENABLED OUTPUT ipo_error) if(IPO_ENABLED) message(STATUS "LTO enabled") diff --git a/cmake/gui-option.cmake b/cmake/gui-option.cmake new file mode 100644 index 000000000..1ec141ea2 --- /dev/null +++ b/cmake/gui-option.cmake @@ -0,0 +1,17 @@ +set(default_build_gui OFF) +if(APPLE OR WIN32) + set(default_build_gui ON) +endif() + +if(WIN32) + set(GUI_EXE "" CACHE FILEPATH "path to a pre-built Windows GUI .exe to use (implies -DBUILD_GUI=OFF)") + if(GUI_EXE) + set(default_build_gui OFF) + endif() +endif() + +option(BUILD_GUI "build electron gui from 'gui' submodule source" ${default_build_gui}) + +if(BUILD_GUI AND GUI_EXE) + message(FATAL_ERROR "-DGUI_EXE=... and -DBUILD_GUI=ON are mutually exclusive") +endif() diff --git a/cmake/gui.cmake b/cmake/gui.cmake new file mode 100644 index 000000000..6b74ab9ed --- /dev/null +++ b/cmake/gui.cmake @@ -0,0 +1,67 @@ + +if(WIN32 AND GUI_EXE) + message(STATUS "using pre-built lokinet gui executable: ${GUI_EXE}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${GUI_EXE}" "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe") +elseif(BUILD_GUI) + message(STATUS "Building lokinet-gui from source") + + set(default_gui_target pack) + if(APPLE) + set(default_gui_target macos:raw) + elseif(WIN32) + set(default_gui_target win32) + endif() + + set(GUI_YARN_TARGET "${default_gui_target}" CACHE STRING "yarn target for building the GUI") + set(GUI_YARN_EXTRA_OPTS "" CACHE STRING "extra options to pass into the yarn build command") + + # allow manually specifying yarn with -DYARN= + if(NOT YARN) + find_program(YARN NAMES yarnpkg yarn REQUIRED) + endif() + message(STATUS "Building lokinet-gui with yarn ${YARN}, target ${GUI_YARN_TARGET}") + + if(NOT WIN32) + add_custom_target(lokinet-gui + COMMAND ${YARN} install --frozen-lockfile && + ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET} + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui") + endif() + + if(APPLE) + add_custom_target(assemble_gui ALL + DEPENDS assemble lokinet-gui + COMMAND mkdir "${lokinet_app}/Contents/Helpers" + COMMAND cp -a "${PROJECT_SOURCE_DIR}/gui/release/mac/Lokinet-GUI.app" "${lokinet_app}/Contents/Helpers/" + COMMAND mkdir -p "${lokinet_app}/Contents/Resources/en.lproj" + COMMAND cp "${PROJECT_SOURCE_DIR}/contrib/macos/InfoPlist.strings" "${lokinet_app}/Contents/Resources/en.lproj/" + COMMAND cp "${lokinet_app}/Contents/Resources/icon.icns" "${lokinet_app}/Contents/Helpers/Lokinet-GUI.app/Contents/Resources/icon.icns" + COMMAND cp "${PROJECT_SOURCE_DIR}/contrib/macos/InfoPlist.strings" "${lokinet_app}/Contents/Helpers/Lokinet-GUI.app/Contents/Resources/en.lproj/" + COMMAND /usr/libexec/PlistBuddy + -c "Delete :CFBundleDisplayName" + -c "Add :LSHasLocalizedDisplayName bool true" + -c "Add :CFBundleDevelopmentRegion string en" + -c "Set :CFBundleShortVersionString ${lokinet_VERSION}" + -c "Set :CFBundleVersion ${lokinet_VERSION}.${LOKINET_APPLE_BUILD}" + "${lokinet_app}/Contents/Helpers/Lokinet-GUI.app/Contents/Info.plist" + ) + elseif(WIN32) + file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gui") + add_custom_command(OUTPUT "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" + COMMAND ${YARN} install --frozen-lockfile && + USE_SYSTEM_7ZA=true DISPLAY= WINEDEBUG=-all WINEPREFIX="${PROJECT_BINARY_DIR}/wineprefix" ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${PROJECT_SOURCE_DIR}/gui/release/Lokinet-GUI_portable.exe" + "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui") + add_custom_target(assemble_gui ALL COMMAND "true" DEPENDS "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe") + else() + message(FATAL_ERROR "Building/bundling the GUI from this repository is not supported on this platform") + endif() +else() + message(STATUS "not building gui") +endif() + +if(NOT TARGET assemble_gui) + add_custom_target(assemble_gui COMMAND "true") +endif() diff --git a/cmake/installer.cmake b/cmake/installer.cmake index b8c04e563..503fd15dd 100644 --- a/cmake/installer.cmake +++ b/cmake/installer.cmake @@ -1,13 +1,46 @@ set(CPACK_PACKAGE_VENDOR "lokinet.org") set(CPACK_PACKAGE_HOMEPAGE_URL "https://lokinet.org/") set(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/contrib/readme-installer.txt") -set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE.txt") +set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") if(WIN32) include(cmake/win32_installer_deps.cmake) + install(FILES ${CMAKE_SOURCE_DIR}/contrib/configs/00-exit.ini DESTINATION share/conf.d COMPONENT exit_configs) + install(FILES ${CMAKE_SOURCE_DIR}/contrib/configs/00-keyfile.ini DESTINATION share/conf.d COMPONENT keyfile_configs) + install(FILES ${CMAKE_SOURCE_DIR}/contrib/configs/00-debug-log.ini DESTINATION share/conf.d COMPONENT debug_configs) + get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) + list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified" "lokinet" "gui" "exit_configs" "keyfile_configs" "debug_configs") + list(APPEND CPACK_COMPONENTS_ALL "lokinet" "gui" "exit_configs" "keyfile_configs" "debug_configs") +elseif(APPLE) + set(CPACK_GENERATOR DragNDrop;ZIP) + get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) + list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified") endif() - -# This must always be last! include(CPack) +if(WIN32) + cpack_add_component(lokinet + DISPLAY_NAME "lokinet" + DESCRIPTION "core required lokinet files" + REQUIRED) + + cpack_add_component(gui + DISPLAY_NAME "lokinet gui" + DESCRIPTION "electron based control panel for lokinet") + + cpack_add_component(exit_configs + DISPLAY_NAME "auto-enable exit" + DESCRIPTION "automatically enable usage of exit.loki as an exit node\n" + DISABLED) + + cpack_add_component(keyfile_configs + DISPLAY_NAME "persist address" + DESCRIPTION "persist .loki address across restarts of lokinet\nnot recommended when enabling exit nodes" + DISABLED) + + cpack_add_component(debug_configs + DISPLAY_NAME "debug logging" + DESCRIPTION "enable debug spew log level by default" + DISABLED) +endif() diff --git a/cmake/link_dep_libs.cmake b/cmake/link_dep_libs.cmake deleted file mode 100644 index 14e4b4f16..000000000 --- a/cmake/link_dep_libs.cmake +++ /dev/null @@ -1,13 +0,0 @@ -# Properly links a target to a list of library names by finding the given libraries. Takes: -# - a target -# - a linktype (e.g. INTERFACE, PUBLIC, PRIVATE) -# - a library search path (or "" for defaults) -# - any number of library names -function(link_dep_libs target linktype libdirs) - foreach(lib ${ARGN}) - find_library(link_lib-${lib} NAMES ${lib} PATHS ${libdirs}) - if(link_lib-${lib}) - target_link_libraries(${target} ${linktype} ${link_lib-${lib}}) - endif() - endforeach() -endfunction() diff --git a/cmake/macos.cmake b/cmake/macos.cmake new file mode 100644 index 000000000..7510ba1fe --- /dev/null +++ b/cmake/macos.cmake @@ -0,0 +1,214 @@ +if(NOT APPLE) + return() +endif() + + +option(MACOS_SYSTEM_EXTENSION + "Build the network extension as a system extension rather than a plugin. This must be ON for non-app store release builds, and must be OFF for dev builds and Mac App Store distribution builds" + OFF) +option(CODESIGN "codesign the resulting app and extension" ON) +set(CODESIGN_ID "" CACHE STRING "codesign the macos app using this key identity; if empty we'll try to guess") +set(default_profile_type "dev") +if(MACOS_SYSTEM_EXTENSION) + set(default_profile_type "release") +endif() +set(CODESIGN_PROFILE "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.${default_profile_type}.provisionprofile" CACHE FILEPATH + "Path to a .provisionprofile to use for the main app") +set(CODESIGN_EXT_PROFILE "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.${default_profile_type}.provisionprofile" CACHE FILEPATH + "Path to a .provisionprofile to use for the lokinet extension") + +if(CODESIGN AND NOT CODESIGN_ID) + if(MACOS_SYSTEM_EXTENSION) + set(codesign_cert_pattern "Developer ID Application") + else() + set(codesign_cert_pattern "Apple Development") + endif() + execute_process( + COMMAND security find-identity -v -p codesigning + COMMAND sed -n "s/^ *[0-9][0-9]*) *\\([A-F0-9]\\{40\\}\\) *\"\\(${codesign_cert_pattern}.*\\)\"\$/\\1 \\2/p" + RESULT_VARIABLE find_id_exit_code + OUTPUT_VARIABLE find_id_output) + if(NOT find_id_exit_code EQUAL 0) + message(FATAL_ERROR "Finding signing identities with security find-identity failed; try specifying an id using -DCODESIGN_ID=...") + endif() + + string(REGEX MATCHALL "(^|\n)[0-9A-F]+" find_id_sign_id "${find_id_output}") + if(NOT find_id_sign_id) + message(FATAL_ERROR "Did not find any \"${codesign_cert_pattern}\" identity; try specifying an id using -DCODESIGN_ID=...") + endif() + if (find_id_sign_id MATCHES ";") + message(FATAL_ERROR "Found multiple \"${codesign_cert_pattern}\" identities:\n${find_id_output}\nSpecify an identify using -DCODESIGN_ID=...") + endif() + set(CODESIGN_ID "${find_id_sign_id}" CACHE STRING "" FORCE) +endif() + +if(CODESIGN) + message(STATUS "Codesigning using ${CODESIGN_ID}") + + if (NOT MACOS_NOTARIZE_USER AND NOT MACOS_NOTARIZE_PASS AND NOT MACOS_NOTARIZE_ASC AND EXISTS "$ENV{HOME}/.notarization.cmake") + message(STATUS "Loading notarization info from ~/.notarization.cmake") + include("$ENV{HOME}/.notarization.cmake") + endif() + + if (MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC) + message(STATUS "Enabling notarization with account ${MACOS_NOTARIZE_ASC}/${MACOS_NOTARIZE_USER}") + else() + message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization will fail; see contrib/macos/README.txt") + endif() + +else() + message(WARNING "Codesigning disabled; the resulting build will not run on most macOS systems") +endif() + + +foreach(prof IN ITEMS CODESIGN_PROFILE CODESIGN_EXT_PROFILE) + if(NOT ${prof}) + message(WARNING "Missing a ${prof} provisioning profile: Apple will most likely log an uninformative error message to the system log and then kill harmless kittens if you try to run the result") + elseif(NOT EXISTS "${${prof}}") + message(FATAL_ERROR "Provisioning profile ${${prof}} does not exist; fix your -D${prof} path") + endif() +endforeach() +message(STATUS "Using ${CODESIGN_PROFILE} app provisioning profile") +message(STATUS "Using ${CODESIGN_EXT_PROFILE} extension provisioning profile") + + + +set(lokinet_installer "${PROJECT_BINARY_DIR}/Lokinet ${PROJECT_VERSION}") +if(NOT CODESIGN) + set(lokinet_installer "${lokinet_installer}-UNSIGNED") +endif() +set(lokinet_app "${lokinet_installer}/Lokinet.app") + + +if(MACOS_SYSTEM_EXTENSION) + set(lokinet_ext_dir Contents/Library/SystemExtensions) +else() + set(lokinet_ext_dir Contents/PlugIns) +endif() + +if(CODESIGN) + if(MACOS_SYSTEM_EXTENSION) + set(LOKINET_ENTITLEMENTS_TYPE sysext) + set(notarize_py_is_sysext True) + else() + set(LOKINET_ENTITLEMENTS_TYPE plugin) + set(notarize_py_is_sysext False) + endif() + + configure_file( + "${PROJECT_SOURCE_DIR}/contrib/macos/sign.sh.in" + "${PROJECT_BINARY_DIR}/sign.sh" + @ONLY) + + add_custom_target( + sign + DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" + COMMAND "${PROJECT_BINARY_DIR}/sign.sh" + ) + + if(MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC) + configure_file( + "${PROJECT_SOURCE_DIR}/contrib/macos/notarize.py.in" + "${PROJECT_BINARY_DIR}/notarize.py" + @ONLY) + add_custom_target( + notarize + DEPENDS "${PROJECT_BINARY_DIR}/notarize.py" sign + COMMAND "${PROJECT_BINARY_DIR}/notarize.py" + ) + else() + message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization disabled") + endif() +else() + add_custom_target(sign COMMAND "true") + add_custom_target(notarize DEPENDS sign COMMAND "true") +endif() + +set(mac_icon "${PROJECT_BINARY_DIR}/lokinet.icns") +add_custom_command(OUTPUT "${mac_icon}" + COMMAND ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet-mac.svg "${mac_icon}" + DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet-mac.svg ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh) +add_custom_target(icon DEPENDS "${mac_icon}") + +if(BUILD_PACKAGE) + add_executable(seticon "${PROJECT_SOURCE_DIR}/contrib/macos/seticon.swift") + add_custom_command(OUTPUT "${lokinet_installer}.dmg" + DEPENDS notarize seticon + COMMAND create-dmg + --volname "Lokinet ${PROJECT_VERSION}" + --volicon lokinet.icns + --background "${PROJECT_SOURCE_DIR}/contrib/macos/installer.tiff" + --text-size 16 + --icon-size 128 + --window-size 555 440 + --icon Lokinet.app 151 196 + --hide-extension Lokinet.app + --app-drop-link 403 196 + --eula "${PROJECT_SOURCE_DIR}/LICENSE" + --no-internet-enable + "${lokinet_installer}.dmg" + "${lokinet_installer}" + COMMAND ./seticon lokinet.icns "${lokinet_installer}.dmg" + ) + add_custom_target(package DEPENDS "${lokinet_installer}.dmg") +endif() + + +# Called later to set things up, after the main lokinet targets are set up +function(macos_target_setup) + + if(MACOS_SYSTEM_EXTENSION) + target_compile_definitions(lokinet PRIVATE MACOS_SYSTEM_EXTENSION) + endif() + + set_target_properties(lokinet + PROPERTIES + OUTPUT_NAME Lokinet + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_STRING "Lokinet IP Packet Onion Router" + MACOSX_BUNDLE_BUNDLE_NAME "Lokinet" + MACOSX_BUNDLE_BUNDLE_VERSION "${lokinet_VERSION}" + MACOSX_BUNDLE_LONG_VERSION_STRING "${lokinet_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${lokinet_VERSION_MAJOR}.${lokinet_VERSION_MINOR}" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.lokinet" + MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.Info.plist.in" + MACOSX_BUNDLE_COPYRIGHT "© 2022, The Oxen Project" + ) + + add_custom_target(copy_bootstrap + DEPENDS lokinet-extension + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed + $/Contents/Resources/bootstrap.signed + ) + + + add_dependencies(lokinet lokinet-extension icon) + + + if(CODESIGN_PROFILE) + add_custom_target(copy_prov_prof + DEPENDS lokinet + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CODESIGN_PROFILE} + $/Contents/embedded.provisionprofile + ) + else() + add_custom_target(copy_prov_prof COMMAND true) + endif() + + add_custom_target(assemble ALL + DEPENDS lokinet lokinet-extension icon copy_prov_prof copy_bootstrap + COMMAND rm -rf "${lokinet_app}" + COMMAND mkdir -p "${lokinet_installer}" + COMMAND cp -a $ "${lokinet_app}" + COMMAND mkdir -p "${lokinet_app}/${lokinet_ext_dir}" + COMMAND cp -a $ "${lokinet_app}/${lokinet_ext_dir}/" + COMMAND mkdir -p "${lokinet_app}/Contents/Resources" + COMMAND cp -a "${mac_icon}" "${lokinet_app}/Contents/Resources/icon.icns" + ) + + if(BUILD_GUI) + add_dependencies(sign assemble_gui) + else() + add_dependencies(sign assemble) + endif() +endfunction() diff --git a/cmake/ngtcp2_lib.cmake b/cmake/ngtcp2_lib.cmake index c6d06a42a..a14160b15 100644 --- a/cmake/ngtcp2_lib.cmake +++ b/cmake/ngtcp2_lib.cmake @@ -45,8 +45,9 @@ function(add_ngtcp2_lib) configure_file(ngtcp2/cmakeconfig.h.in ngtcp2/config.h) include_directories("${CMAKE_CURRENT_BINARY_DIR}/ngtcp2") # for config.h - + set(ENABLE_STATIC_LIB ON FORCE BOOL) + set(ENABLE_SHARED_LIB OFF FORCE BOOL) add_subdirectory(ngtcp2/lib EXCLUDE_FROM_ALL) - target_compile_definitions(ngtcp2 PRIVATE -DHAVE_CONFIG_H -D_GNU_SOURCE) + target_compile_definitions(ngtcp2_static PRIVATE -DHAVE_CONFIG_H -D_GNU_SOURCE) endfunction() diff --git a/cmake/shadow.cmake b/cmake/shadow.cmake deleted file mode 100644 index deed362b1..000000000 --- a/cmake/shadow.cmake +++ /dev/null @@ -1,18 +0,0 @@ -set(WITH_STATIC OFF) -set(WITH_SHARED ON) -if("${SHADOW_ROOT}" STREQUAL "") - set(SHADOW_ROOT "$ENV{HOME}/.shadow") -endif("${SHADOW_ROOT}" STREQUAL "") -if(EXISTS "${SHADOW_ROOT}") - message(STATUS "SHADOW_ROOT = ${SHADOW_ROOT}") -else() - message(FATAL_ERROR "SHADOW_ROOT path does not exist: '${SHADOW_ROOT}'") -endif(EXISTS "${SHADOW_ROOT}") - -set(CMAKE_MODULE_PATH "${SHADOW_ROOT}/share/cmake/Modules") -include_directories(${CMAKE_MODULE_PATH}) -include(ShadowTools) -add_compile_options(-fno-inline -fno-strict-aliasing ) -add_definitions(-DTESTNET=1) -add_definitions(-DLOKINET_SHADOW) -include_directories(${SHADOW_ROOT}/include) diff --git a/cmake/win32.cmake b/cmake/win32.cmake index 2a4af72a0..a8f977c28 100644 --- a/cmake/win32.cmake +++ b/cmake/win32.cmake @@ -1,31 +1,49 @@ if(NOT WIN32) return() endif() +if (NOT STATIC_LINK) + message(FATAL_ERROR "windows requires static builds (thanks balmer)") +endif() enable_language(RC) -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - -if(NOT MSVC_VERSION) - add_compile_options($<$:-Wno-bad-function-cast>) - add_compile_options($<$:-Wno-cast-function-type>) - add_compile_options($<$:-fpermissive>) - # unlike unix where you get a *single* compiler ID string in .comment - # GNU ld sees fit to merge *all* the .ident sections in object files - # to .r[o]data section one after the other! - add_compile_options(-fno-ident -Wa,-mbig-obj) - link_libraries( -lws2_32 -lshlwapi -ldbghelp -luser32 -liphlpapi -lpsapi -luserenv) - # zmq requires windows xp or higher - add_definitions(-DWINVER=0x0501 -D_WIN32_WINNT=0x0501) -endif() +option(WITH_WINDOWS_32 "build 32 bit windows" OFF) + +# unlike unix where you get a *single* compiler ID string in .comment +# GNU ld sees fit to merge *all* the .ident sections in object files +# to .r[o]data section one after the other! +add_compile_options(-fno-ident -Wa,-mbig-obj) if(EMBEDDED_CFG) link_libatomic() endif() -add_definitions(-DWIN32_LEAN_AND_MEAN -DWIN32) +set(WINTUN_VERSION 0.14.1 CACHE STRING "wintun version") +set(WINTUN_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") -if (NOT STATIC_LINK AND NOT MSVC) - message("must ship compiler runtime libraries with this build: libwinpthread-1.dll, libgcc_s_dw2-1.dll, and libstdc++-6.dll") - message("for release builds, turn on STATIC_LINK in cmake options") -endif() +set(WINDIVERT_VERSION 2.2.0-A CACHE STRING "windivert version") +set(WINDIVERT_MIRROR https://reqrypt.org/download + CACHE STRING "windivert mirror(s)") +set(WINDIVERT_SOURCE WinDivert-${WINDIVERT_VERSION}.zip) +set(WINDIVERT_HASH SHA256=2a7630aac0914746fbc565ac862fa096e3e54233883ac52d17c83107496b7a7f + 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") + +message(STATUS "Downloading wintun from ${WINTUN_URL}") +file(DOWNLOAD ${WINTUN_URL} ${CMAKE_BINARY_DIR}/wintun.zip EXPECTED_HASH ${WINTUN_HASH}) +message(STATUS "Downloading windivert from ${WINDIVERT_URL}") +file(DOWNLOAD ${WINDIVERT_URL} ${CMAKE_BINARY_DIR}/windivert.zip EXPECTED_HASH ${WINDIVERT_HASH}) + +execute_process(COMMAND ${CMAKE_COMMAND} -E tar x ${CMAKE_BINARY_DIR}/wintun.zip + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +execute_process(COMMAND ${CMAKE_COMMAND} -E tar x ${CMAKE_BINARY_DIR}/windivert.zip + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/cmake/win32_installer_deps.cmake b/cmake/win32_installer_deps.cmake index 95ea1aedf..8099e9a00 100644 --- a/cmake/win32_installer_deps.cmake +++ b/cmake/win32_installer_deps.cmake @@ -1,40 +1,46 @@ -if(NOT GUI_ZIP_URL) - set(GUI_ZIP_URL "https://oxen.rocks/oxen-io/loki-network-control-panel/lokinet-gui-windows-32bit-v0.3.8.zip") - set(GUI_ZIP_HASH_OPTS EXPECTED_HASH SHA256=60c2b738cf997e5684f307e5222498fd09143d495a932924105a49bf59ded8bb) +install(DIRECTORY ${CMAKE_BINARY_DIR}/gui DESTINATION share COMPONENT gui) + +if(WITH_WINDOWS_32) + install(FILES ${CMAKE_BINARY_DIR}/wintun/bin/x86/wintun.dll DESTINATION bin COMPONENT lokinet) + install(FILES ${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/x86/WinDivert.sys DESTINATION lib COMPONENT lokinet) + install(FILES ${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/x86/WinDivert.dll DESTINATION bin COMPONENT lokinet) +else() + install(FILES ${CMAKE_BINARY_DIR}/wintun/bin/amd64/wintun.dll DESTINATION bin COMPONENT lokinet) + install(FILES ${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/x64/WinDivert64.sys DESTINATION lib COMPONENT lokinet) + install(FILES ${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/x64/WinDivert.dll DESTINATION bin COMPONENT lokinet) endif() -set(TUNTAP_URL "https://build.openvpn.net/downloads/releases/latest/tap-windows-latest-stable.exe") -set(TUNTAP_EXE "${CMAKE_BINARY_DIR}/tuntap-install.exe") set(BOOTSTRAP_FILE "${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed") - -file(DOWNLOAD - ${TUNTAP_URL} - ${TUNTAP_EXE}) - -file(DOWNLOAD - ${GUI_ZIP_URL} - ${CMAKE_BINARY_DIR}/lokinet-gui.zip - ${GUI_ZIP_HASH_OPTS}) - -execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_BINARY_DIR}/lokinet-gui.zip - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) - -install(DIRECTORY ${CMAKE_BINARY_DIR}/gui DESTINATION share COMPONENT gui) -install(PROGRAMS ${TUNTAP_EXE} DESTINATION bin COMPONENT tuntap) install(FILES ${BOOTSTRAP_FILE} DESTINATION share COMPONENT lokinet RENAME bootstrap.signed) +set(win_ico "${PROJECT_BINARY_DIR}/lokinet.ico") +add_custom_command(OUTPUT "${win_ico}" + COMMAND ${PROJECT_SOURCE_DIR}/contrib/make-ico.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg "${win_ico}" + DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${PROJECT_SOURCE_DIR}/contrib/make-ico.sh) +add_custom_target(icon ALL DEPENDS "${win_ico}") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "Lokinet") -set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/win32-setup/lokinet.ico") +set(CPACK_NSIS_MUI_ICON "${PROJECT_BINARY_DIR}/lokinet.ico") set(CPACK_NSIS_DEFINES "RequestExecutionLevel admin") set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) -set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ifFileExists $INSTDIR\\\\bin\\\\tuntap-install.exe 0 +2\\nExecWait '$INSTDIR\\\\bin\\\\tuntap-install.exe /S'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe --install'\\nExecWait 'sc failure lokinet reset= 60 actions= restart/1000'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe -g C:\\\\ProgramData\\\\lokinet\\\\lokinet.ini'\\nCopyFiles '$INSTDIR\\\\share\\\\bootstrap.signed' C:\\\\ProgramData\\\\lokinet\\\\bootstrap.signed\\n") -set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "ExecWait 'net stop lokinet'\\nExecWait 'taskkill /f /t /im lokinet-gui.exe'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe --remove'\\nRMDir /r /REBOOTOK C:\\\\ProgramData\\\\lokinet") -set(CPACK_NSIS_CREATE_ICONS_EXTRA - "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Lokinet.lnk' '$INSTDIR\\\\share\\\\gui\\\\lokinet-gui.exe'" -) -set(CPACK_NSIS_DELETE_ICONS_EXTRA - "Delete '$SMPROGRAMS\\\\$START_MENU\\\\Lokinet.lnk'" -) - -get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) -list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified") + +function(read_nsis_file filename outvar) + file(STRINGS "${filename}" _outvar) + list(TRANSFORM _outvar REPLACE "\\\\" "\\\\\\\\") + list(JOIN _outvar "\\n" out) + set(${outvar} ${out} PARENT_SCOPE) +endfunction() + +read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_preinstall.nsis" _extra_preinstall) +read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_install.nsis" _extra_install) +read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_uninstall.nsis" _extra_uninstall) +read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_create_icons.nsis" _extra_create_icons) +read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_delete_icons.nsis" _extra_delete_icons) + +set(CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS "${_extra_preinstall}") +set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${_extra_install}") +set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "${_extra_uninstall}") +set(CPACK_NSIS_CREATE_ICONS_EXTRA "${_extra_create_icons}") +set(CPACK_NSIS_DELETE_ICONS_EXTRA "${_extra_delete_icons}") + +set(CPACK_NSIS_COMPRESSOR "/SOLID lzma") diff --git a/contrib/android-configure.sh b/contrib/android-configure.sh new file mode 100755 index 000000000..065e83e18 --- /dev/null +++ b/contrib/android-configure.sh @@ -0,0 +1,68 @@ +#!/bin/bash +set -e + +default_abis="armeabi-v7a arm64-v8a x86_64" +build_abis=${ABIS:-$default_abis} + +test x$NDK = x && test -e /usr/lib/android-ndk && export NDK=/usr/lib/android-ndk +test x$NDK = x && exit 1 + +echo "building abis: $build_abis" + +root=$(readlink -f "$1") +shift +build=$(readlink -f "$1") +shift +mkdir -p $build +cd $build + +for abi in $build_abis; do + mkdir -p build-$abi + cd build-$abi + cmake \ + -S "$root" -B . \ + -G 'Unix Makefiles' \ + -DANDROID=ON \ + -DANDROID_ABI=$abi \ + -DANDROID_ARM_MODE=arm \ + -DANDROID_PLATFORM=android-23 \ + -DANDROID_API=23 \ + -DANDROID_STL=c++_static \ + -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \ + -DBUILD_STATIC_DEPS=ON \ + -DBUILD_PACKAGE=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_TESTING=OFF \ + -DBUILD_LIBLOKINET=OFF \ + -DWITH_TESTS=OFF \ + -DWITH_BOOTSTRAP=OFF \ + -DNATIVE_BUILD=OFF \ + -DSTATIC_LINK=ON \ + -DWITH_SYSTEMD=OFF \ + -DFORCE_OXENMQ_SUBMODULE=ON \ + -DFORCE_OXENC_SUBMODULE=ON \ + -DFORCE_FMT_SUBMODULE=ON \ + -DFORCE_SPDLOG_SUBMODULE=ON \ + -DFORCE_NLOHMANN_SUBMODULE=ON \ + -DSUBMODULE_CHECK=OFF \ + -DWITH_LTO=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + "$@" + cd - +done +rm -f $build/Makefile +echo "# generated makefile" >> $build/Makefile +echo "all: $build_abis" >> $build/Makefile +for abi in $build_abis; do + echo -ne "$abi:\n\t" >> $build/Makefile + echo -ne '$(MAKE) -C ' >> $build/Makefile + echo "build-$abi lokinet-android" >> $build/Makefile + echo -ne "\tmkdir -p out/$abi && cp build-$abi/jni/liblokinet-android.so out/$abi/liblokinet-android.so\n\n" >> $build/Makefile + echo -ne "clean-$abi:\n\t" >> $build/Makefile + echo -ne '$(MAKE) -C ' >> $build/Makefile + echo "build-$abi clean" >> $build/Makefile +done + +echo -ne "clean:" >> $build/Makefile +for targ in $build_abis ; do echo -ne " clean-$targ" >> $build/Makefile ; done +echo "" >> $build/Makefile diff --git a/contrib/android.sh b/contrib/android.sh index 9dfd64df2..bcb55d5a4 100755 --- a/contrib/android.sh +++ b/contrib/android.sh @@ -2,50 +2,7 @@ set -e set +x -default_abis="armeabi-v7a arm64-v8a x86 x86_64" -build_abis=${ABIS:-$default_abis} - -test x$NDK = x && echo "NDK env var not set" -test x$NDK = x && exit 1 - -echo "building abis: $build_abis" - root="$(readlink -f $(dirname $0)/../)" -out=$root/lokinet-jni-$(git describe || echo unknown) -mkdir -p $out -mkdir -p $root/build-android -cd $root/build-android - -for abi in $build_abis; do - mkdir -p build-$abi $out/$abi - cd build-$abi - cmake \ - -G 'Unix Makefiles' \ - -DANDROID=ON \ - -DANDROID_ABI=$abi \ - -DANDROID_ARM_MODE=arm \ - -DANDROID_PLATFORM=android-23 \ - -DANDROID_STL=c++_static \ - -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \ - -DBUILD_STATIC_DEPS=ON \ - -DBUILD_PACKAGE=ON \ - -DBUILD_SHARED_LIBS=OFF \ - -DBUILD_TESTING=OFF \ - -DBUILD_LIBLOKINET=OFF \ - -DWITH_TESTS=OFF \ - -DNATIVE_BUILD=OFF \ - -DSTATIC_LINK=ON \ - -DWITH_SYSTEMD=OFF \ - -DFORCE_OXENMQ_SUBMODULE=ON \ - -DSUBMODULE_CHECK=OFF \ - -DWITH_LTO=OFF \ - -DCMAKE_BUILD_TYPE=Release \ - $@ $root - make lokinet-android -j${JOBS:-$(nproc)} - cp jni/liblokinet-android.so $out/$abi/liblokinet-android.so - cd - -done - - -echo -echo "build artifacts outputted to $out" +cd "$root" +./contrib/android-configure.sh . build-android "$@" +make -C build-android -j ${JOBS:-$(nproc)} diff --git a/contrib/bootserv/.gitignore b/contrib/bootserv/.gitignore deleted file mode 100644 index 0c119ef3e..000000000 --- a/contrib/bootserv/.gitignore +++ /dev/null @@ -1 +0,0 @@ -lokinet-bootserv \ No newline at end of file diff --git a/contrib/bootserv/config/lokinet-bootserv-nginx.conf b/contrib/bootserv/config/lokinet-bootserv-nginx.conf deleted file mode 100644 index 4fe24ee47..000000000 --- a/contrib/bootserv/config/lokinet-bootserv-nginx.conf +++ /dev/null @@ -1,29 +0,0 @@ -# replace your.server.tld with your server's fqdn - -server { - listen 80; - server_name your.server.tld; - location / { - return 302 https://your.server.tld$request_uri; - } - location /.well-known/acme-challenge { - root /var/www/letsencrypt; - } -} - -server { - listen 443 ssl; - server_name your.server.tld; - ssl_certificate /etc/letsencrypt/live/your.server.tld/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/your.server.tld/privkey.pem; - - location / { - root /var/www/lokinet-bootserv; - } - - location /bootstrap.signed { - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME /usr/local/bin/lokinet-bootserv; - fastcgi_pass unix://tmp/cgi.sock; - } -} \ No newline at end of file diff --git a/contrib/bootserv/config/lokinet-bootserv.ini b/contrib/bootserv/config/lokinet-bootserv.ini deleted file mode 100644 index 1c1eda45b..000000000 --- a/contrib/bootserv/config/lokinet-bootserv.ini +++ /dev/null @@ -1,4 +0,0 @@ - -# set me to where the nodedb is for lokinet -#[nodedb] -#dir=/path/to/nodedb/ diff --git a/contrib/bootserv/makefile b/contrib/bootserv/makefile deleted file mode 100644 index 081016c40..000000000 --- a/contrib/bootserv/makefile +++ /dev/null @@ -1,20 +0,0 @@ - -SRC = $(sort $(wildcard src/*.cpp)) -OBJS = $(SRC:.cpp=.cpp.o) - -CGI_EXE = lokinet-bootserv - -CXX = clang++ - -all: build - -build: $(CGI_EXE) - -%.cpp.o: %.cpp - $(CXX) -g -std=c++17 -c -Wall -Werror -Wpedantic $^ -o $^.o - -$(CGI_EXE): $(OBJS) - $(CXX) -o $(CGI_EXE) $^ - -clean: - rm -f $(CGI_EXE) $(OBJS) diff --git a/contrib/bootserv/readme.md b/contrib/bootserv/readme.md deleted file mode 100644 index 572ba50f8..000000000 --- a/contrib/bootserv/readme.md +++ /dev/null @@ -1,35 +0,0 @@ -# lokinet-bootserv - -cgi executable for serving a random RC for bootstrap from a nodedb - -## configuring - -copy the example config (privileged) - - # cp configs/lokinet-bootserv.ini /usr/local/etc/lokinet-bootserv.ini - -edit config to have proper values, -specifically make sure the `[nodedb]` section has a `dir` value that points to a static copy of a healthy nodedb - -## building - -to build: - - $ make - -## installing (priviledged) - -install cgi binary: - - # cp lokinet-bootserv /usr/local/bin/lokinet-bootserv - -set up with nginx cgi: - - # cp configs/lokinet-bootserv-nginx.conf /etc/nginx/sites-available/lokinet-bootserv.conf - # ln -s /etc/nginx/sites-available/lokinet-bootserv.conf /etc/nginx/sites-enabled/ - -## maintainence - -add the following to crontab - - 0 0 * * * /usr/local/bin/lokinet-bootserv --cron diff --git a/contrib/bootserv/src/cgi.cpp b/contrib/bootserv/src/cgi.cpp deleted file mode 100644 index 46482ab7c..000000000 --- a/contrib/bootserv/src/cgi.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include "lokinet-cgi.hpp" -#include -#include -#include -#include - -namespace lokinet -{ - namespace bootserv - { - CGIHandler::CGIHandler(std::ostream& o) : Handler(o) - { - } - - CGIHandler::~CGIHandler() - { - } - - int - CGIHandler::Exec(const Config& conf) - { - const char* e = getenv("REQUEST_METHOD"); - if(e == nullptr) - return ReportError("$REQUEST_METHOD not set"); - std::string_view method(e); - - if(method != "GET") - { - out << "Content-Type: text/plain" << std::endl; - out << "Status: 405 Method Not Allowed" << std::endl << std::endl; - return 0; - } - - std::string fname; - if(!conf.VisitSection( - "nodedb", [&](const Config::Section_t& sect) -> bool { - auto itr = sect.find("dir"); - if(itr == sect.end()) - return false; - fname = PickRandomFileInDir( - std::string(itr->second.data(), itr->second.size())); - return true; - })) - - return ReportError("bad values in nodedb section of config"); - if(fname.empty()) - { - // no files in nodedb - out << "Content-Type: text/plain" << std::endl; - out << "Status: 404 Not Found" << std::endl << std::endl; - return 0; - } - return ServeFile(fname.c_str(), "application/octect-stream"); - } - - std::string - CGIHandler::PickRandomFileInDir(std::string dirname) const - { - // collect files - std::list< std::string > files; - { - DIR* d = opendir(dirname.c_str()); - if(d == nullptr) - { - return ""; - }; - std::list< std::string > subdirs; - dirent* ent = nullptr; - while((ent = readdir(d))) - { - std::string_view f = ent->d_name; - if(f != "." && f != "..") - { - std::stringstream ss; - ss << dirname; - ss << '/'; - ss << f; - subdirs.emplace_back(ss.str()); - } - } - closedir(d); - for(const auto& subdir : subdirs) - { - d = opendir(subdir.c_str()); - if(d) - { - while((ent = readdir(d))) - { - std::string_view f; - f = ent->d_name; - if(f != "." && f != ".." - && f.find_last_of(".signed") != std::string_view::npos) - { - std::stringstream ss; - ss << subdir << "/" << f; - files.emplace_back(ss.str()); - } - } - closedir(d); - } - } - } - uint32_t randint; - { - std::basic_ifstream< uint32_t > randf("/dev/urandom"); - if(!randf.is_open()) - return ""; - randf.read(&randint, 1); - } - auto itr = files.begin(); - if(files.size() > 1) - std::advance(itr, randint % files.size()); - return *itr; - } - - int - CGIHandler::ServeFile(const char* fname, const char* contentType) const - { - std::ifstream f(fname); - if(f.is_open()) - { - f.seekg(0, std::ios::end); - auto sz = f.tellg(); - f.seekg(0, std::ios::beg); - if(sz) - { - out << "Content-Type: " << contentType << std::endl; - out << "Status: 200 OK" << std::endl; - out << "Content-Length: " << std::to_string(sz) << std::endl - << std::endl; - char buf[512] = {0}; - size_t r = 0; - while((r = f.readsome(buf, sizeof(buf))) > 0) - out.write(buf, r); - out << std::flush; - } - else - { - out << "Content-Type: text/plain" << std::endl; - out << "Status: 500 Internal Server Error" << std::endl << std::endl; - out << "could not serve '" << fname << "' as it is an empty file" - << std::endl; - } - } - else - { - out << "Content-Type: text/plain" << std::endl; - out << "Status: 404 Not Found" << std::endl << std::endl; - out << "could not serve '" << fname - << "' as it does not exist on the filesystem" << std::endl; - } - return 0; - } - - int - CGIHandler::ReportError(const char* err) - { - out << "Content-Type: text/plain" << std::endl; - out << "Status: 500 Internal Server Error" << std::endl << std::endl; - out << err << std::endl; - return 0; - } - - Handler_ptr - NewCGIHandler(std::ostream& out) - { - return std::make_unique< CGIHandler >(out); - } - - } // namespace bootserv -} // namespace lokinet diff --git a/contrib/bootserv/src/handler.hpp b/contrib/bootserv/src/handler.hpp deleted file mode 100644 index 7327e900c..000000000 --- a/contrib/bootserv/src/handler.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef LOKINET_BOOTSERV_HANDLER_HPP -#define LOKINET_BOOTSERV_HANDLER_HPP -#include -#include "lokinet-config.hpp" - -namespace lokinet -{ - namespace bootserv - { - struct Handler - { - Handler(std::ostream& o) : out(o){}; - - virtual ~Handler(){}; - - /// handle command - /// return exit code - virtual int - Exec(const Config& conf) = 0; - - /// report an error to system however that is done - /// return exit code - virtual int - ReportError(const char* err) = 0; - - protected: - std::ostream& out; - }; - - using Handler_ptr = std::unique_ptr< Handler >; - - /// create cgi handler - Handler_ptr - NewCGIHandler(std::ostream& out); - - /// create cron handler - Handler_ptr - NewCronHandler(std::ostream& out); - - } // namespace bootserv -} // namespace lokinet - -#endif diff --git a/contrib/bootserv/src/lokinet-cgi.hpp b/contrib/bootserv/src/lokinet-cgi.hpp deleted file mode 100644 index 032bf3d3d..000000000 --- a/contrib/bootserv/src/lokinet-cgi.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef BOOTSERV_LOKINET_CRON_HPP -#define BOOTSERV_LOKINET_CRON_HPP - -#include "handler.hpp" - -namespace lokinet -{ - namespace bootserv - { - struct CGIHandler final : public Handler - { - CGIHandler(std::ostream& o); - ~CGIHandler(); - - int - Exec(const Config& conf) override; - - int - ReportError(const char* err) override; - - int - ServeFile(const char* fname, const char* mime) const; - - std::string - PickRandomFileInDir(std::string dirname) const; - }; - - } // namespace bootserv -} // namespace lokinet - -#endif diff --git a/contrib/bootserv/src/lokinet-config.cpp b/contrib/bootserv/src/lokinet-config.cpp deleted file mode 100644 index 155fb66f7..000000000 --- a/contrib/bootserv/src/lokinet-config.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "lokinet-config.hpp" -#include -#include -#include - -namespace lokinet -{ - namespace bootserv - { - const char* Config::DefaultPath = "/usr/local/etc/lokinet-bootserv.ini"; - - bool - Config::LoadFile(const char* fname) - { - { - std::ifstream f(fname); - if(!f.is_open()) - return false; - f.seekg(0, std::ios::end); - m_Data.resize(f.tellg()); - f.seekg(0, std::ios::beg); - if(m_Data.size() == 0) - return false; - f.read(m_Data.data(), m_Data.size()); - } - return Parse(); - } - - void - Config::Clear() - { - m_Config.clear(); - m_Data.clear(); - } - - bool - Config::Parse() - { - std::list< String_t > lines; - { - auto itr = m_Data.begin(); - // split into lines - while(itr != m_Data.end()) - { - auto beg = itr; - while(itr != m_Data.end() && *itr != '\n' && *itr != '\r') - ++itr; - lines.emplace_back(std::addressof(*beg), (itr - beg)); - if(itr == m_Data.end()) - break; - ++itr; - } - } - - String_t sectName; - - for(const auto& line : lines) - { - String_t realLine; - auto comment = line.find_first_of(';'); - if(comment == String_t::npos) - comment = line.find_first_of('#'); - if(comment == String_t::npos) - realLine = line; - else - realLine = line.substr(0, comment); - // blank or commented line? - if(realLine.size() == 0) - continue; - // find delimiters - auto sectOpenPos = realLine.find_first_of('['); - auto sectClosPos = realLine.find_first_of(']'); - auto kvDelim = realLine.find_first_of('='); - if(sectOpenPos != String_t::npos && sectClosPos != String_t::npos - && kvDelim == String_t::npos) - { - // section header - - // clamp whitespaces - ++sectOpenPos; - while(std::isspace(realLine[sectOpenPos]) - && sectOpenPos != sectClosPos) - ++sectOpenPos; - --sectClosPos; - while(std::isspace(realLine[sectClosPos]) - && sectClosPos != sectOpenPos) - --sectClosPos; - // set section name - sectName = realLine.substr(sectOpenPos, sectClosPos); - } - else if(kvDelim != String_t::npos) - { - // key value pair - String_t::size_type k_start = 0; - String_t::size_type k_end = kvDelim; - String_t::size_type v_start = kvDelim + 1; - String_t::size_type v_end = realLine.size() - 1; - // clamp whitespaces - while(std::isspace(realLine[k_start]) && k_start != kvDelim) - ++k_start; - while(std::isspace(realLine[k_end]) && k_end != k_start) - --k_end; - while(std::isspace(realLine[v_start]) && v_start != v_end) - ++v_start; - while(std::isspace(realLine[v_end])) - --v_end; - - // sect.k = v - String_t k = realLine.substr(k_start, k_end); - String_t v = realLine.substr(v_start, v_end); - Section_t& sect = m_Config[sectName]; - sect[k] = v; - } - else // malformed? - return false; - } - return true; - } - - bool - Config::VisitSection( - const char* name, - std::function< bool(const Section_t& sect) > visit) const - { - auto itr = m_Config.find(name); - if(itr == m_Config.end()) - return false; - return visit(itr->second); - } - - } // namespace bootserv -} // namespace lokinet diff --git a/contrib/bootserv/src/lokinet-config.hpp b/contrib/bootserv/src/lokinet-config.hpp deleted file mode 100644 index caad9a2bc..000000000 --- a/contrib/bootserv/src/lokinet-config.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef LOKINET_BOOTSERV_CONFIG_HPP -#define LOKINET_BOOTSERV_CONFIG_HPP -#include -#include -#include -#include -#include - -namespace lokinet -{ - namespace bootserv - { - struct Config - { - using String_t = std::string_view; - using Section_t = std::unordered_map< String_t, String_t >; - using Config_impl_t = std::unordered_map< String_t, Section_t >; - - static const char* DefaultPath; - - /// clear config - void - Clear(); - - /// load config file for bootserv - /// return true on success - /// return false on error - bool - LoadFile(const char* fname); - - /// visit a section in config read only by name - /// return false if no section or value propagated from visitor - bool - VisitSection(const char* name, - std::function< bool(const Section_t&) > visit) const; - - private: - bool - Parse(); - - std::vector< char > m_Data; - Config_impl_t m_Config; - }; - } // namespace bootserv -} // namespace lokinet - -#endif diff --git a/contrib/bootserv/src/lokinet-cron.cpp b/contrib/bootserv/src/lokinet-cron.cpp deleted file mode 100644 index dfca782a2..000000000 --- a/contrib/bootserv/src/lokinet-cron.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "lokinet-cron.hpp" - -namespace lokinet -{ - namespace bootserv - { - CronHandler::CronHandler(std::ostream& o) : Handler(o) - { - } - - CronHandler::~CronHandler() - { - } - - int - CronHandler::Exec(const Config& conf) - { - // this runs the cron tasks - // TODO: implement me - return 0; - } - - int - CronHandler::ReportError(const char* err) - { - out << "error: " << err << std::endl; - return 1; - } - - Handler_ptr - NewCronHandler(std::ostream& out) - { - return std::make_unique< CronHandler >(out); - } - - } // namespace bootserv -} // namespace lokinet diff --git a/contrib/bootserv/src/lokinet-cron.hpp b/contrib/bootserv/src/lokinet-cron.hpp deleted file mode 100644 index 8bd9ba3c6..000000000 --- a/contrib/bootserv/src/lokinet-cron.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef BOOTSERV_LOKINET_CRON_HPP -#define BOOTSERV_LOKINET_CRON_HPP - -#include "handler.hpp" - -namespace lokinet -{ - namespace bootserv - { - struct CronHandler final : public Handler - { - CronHandler(std::ostream& o); - ~CronHandler(); - - int - Exec(const Config& conf) override; - - int - ReportError(const char* err) override; - }; - - } // namespace bootserv -} // namespace lokinet - -#endif diff --git a/contrib/bootserv/src/main.cpp b/contrib/bootserv/src/main.cpp deleted file mode 100644 index ad51d0f0b..000000000 --- a/contrib/bootserv/src/main.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "handler.hpp" -#include "lokinet-config.hpp" - -#include -#include -#include - -static int -printhelp(const char* exe) -{ - std::cout << "usage: " << exe << " [--cron] [--conf /path/to/alt/config.ini]" - << std::endl; - return 1; -} - -int -main(int argc, char* argv[]) -{ - bool RunCron = false; - - const char* confFile = lokinet::bootserv::Config::DefaultPath; - lokinet::bootserv::Config config; - - lokinet::bootserv::Handler_ptr handler; - - option longopts[] = {{"cron", no_argument, 0, 'C'}, - {"help", no_argument, 0, 'h'}, - {"conf", required_argument, 0, 'c'}, - {0, 0, 0, 0}}; - - int c = 0; - int index = 0; - while((c = getopt_long(argc, argv, "hCc:", longopts, &index)) != -1) - { - switch(c) - { - case 'h': - return printhelp(argv[0]); - case 'C': - RunCron = true; - break; - case 'c': - confFile = optarg; - break; - } - } - if(RunCron) - handler = lokinet::bootserv::NewCronHandler(std::cout); - else - handler = lokinet::bootserv::NewCGIHandler(std::cout); - - if(!config.LoadFile(confFile)) - { - std::stringstream ss; - ss << "failed to load " << confFile; - return handler->ReportError(ss.str().c_str()); - } - else - return handler->Exec(config); -} diff --git a/contrib/bootstrap/testnet.signed b/contrib/bootstrap/testnet.signed index 9ad107115..366c5c5a0 100644 --- a/contrib/bootstrap/testnet.signed +++ b/contrib/bootstrap/testnet.signed @@ -1 +1 @@ -d1:ald1:ci2e1:d3:iwp1:e32:9xsXl%<,s؛_1:i21:::ffff:144.76.164.2021:pi1666e1:vi0eee1:i5:gamma1:k32:m=oZ1mc%SĹ1:p32:!Ez: /0ڄ ݪNB1:rli0ei0ei8ei3ee1:ui1614788310454e1:vi0e1:xle1:z64:uGD=x{51`߀Ew m)q2g )TP1e \ No newline at end of file +ld1:ald1:ci2e1:d3:iwp1:e32:9xsXl%<,s؛_1:i21:::ffff:144.76.164.2021:pi1666e1:vi0eee1:i5:gamma1:k32:m=oZ1mc%SĹ1:p32:!Ez: /0ڄ ݪNB1:rli0ei0ei8ei3ee1:ui1614788310454e1:vi0e1:xle1:z64:uGD=x{51`߀Ew m)q2g )TP1ee diff --git a/contrib/ci/docker/rebuild-docker-images.py b/contrib/ci/docker/rebuild-docker-images.py index 916fa2ee2..11f40eb39 100755 --- a/contrib/ci/docker/rebuild-docker-images.py +++ b/contrib/ci/docker/rebuild-docker-images.py @@ -82,7 +82,7 @@ def run_or_report(*args, myline): log.write(e.output.encode()) global failure failure = True - print_line(myline, "\033[31;1mError! See {} for details", log.name) + print_line(myline, "\033[31;1mError! See {} for details".format(log.name)) raise e diff --git a/contrib/ci/drone-check-static-libs.sh b/contrib/ci/drone-check-static-libs.sh index 98aeef857..b4d8b0b2a 100755 --- a/contrib/ci/drone-check-static-libs.sh +++ b/contrib/ci/drone-check-static-libs.sh @@ -7,7 +7,8 @@ set -o errexit bad= if [ "$DRONE_STAGE_OS" == "darwin" ]; then - if otool -L daemon/lokinet | grep -Ev '^daemon/lokinet:|^\t(/usr/lib/libSystem\.|/usr/lib/libc\+\+\.|/System/Library/Frameworks/CoreFoundation)'; then + if otool -L llarp/apple/org.lokinet.network-extension.systemextension/Contents/MacOS/org.lokinet.network-extension | \ + grep -Ev '^llarp/apple:|^\t(/usr/lib/lib(System\.|c\+\+|objc))|/System/Library/Frameworks/(CoreFoundation|NetworkExtension|Foundation|Network)\.framework'; then bad=1 fi elif [ "$DRONE_STAGE_OS" == "linux" ]; then diff --git a/contrib/ci/drone-format-verify.sh b/contrib/ci/drone-format-verify.sh index 387340018..197b2e2a9 100755 --- a/contrib/ci/drone-format-verify.sh +++ b/contrib/ci/drone-format-verify.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash test "x$IGNORE" != "x" && exit 0 + +. $(dirname $0)/../format-version.sh + repo=$(readlink -e $(dirname $0)/../../) -clang-format-11 -i $(find $repo/jni $repo/daemon $repo/llarp $repo/include $repo/pybind | grep -E '\.[hc](pp)?$') +$CLANG_FORMAT -i $(find $repo/jni $repo/daemon $repo/llarp $repo/include $repo/pybind | grep -E '\.[hc](pp)?$') jsonnetfmt -i $repo/.drone.jsonnet git --no-pager diff --exit-code --color || (echo -ne '\n\n\e[31;1mLint check failed; please run ./contrib/format.sh\e[0m\n\n' ; exit 1) diff --git a/contrib/ci/drone-static-upload.sh b/contrib/ci/drone-static-upload.sh index f2b445ddb..2f2f7f2ef 100755 --- a/contrib/ci/drone-static-upload.sh +++ b/contrib/ci/drone-static-upload.sh @@ -19,9 +19,15 @@ set -o xtrace # Don't start tracing until *after* we write the ssh key chmod 600 ssh_key -os="${UPLOAD_OS:-$DRONE_STAGE_OS-$DRONE_STAGE_ARCH}" -if [ -n "$WINDOWS_BUILD_NAME" ]; then - os="windows-$WINDOWS_BUILD_NAME" +os="$UPLOAD_OS" +if [ -z "$os" ]; then + if [ "$DRONE_STAGE_OS" == "darwin" ]; then + os="macos-$DRONE_STAGE_ARCH" + elif [ -n "$WINDOWS_BUILD_NAME" ]; then + os="windows-$WINDOWS_BUILD_NAME" + else + os="$DRONE_STAGE_OS-$DRONE_STAGE_ARCH" + fi fi if [ -n "$DRONE_TAG" ]; then @@ -34,8 +40,11 @@ else fi mkdir -v "$base" -if [ -e build-windows ]; then - cp -av build-windows/lokinet-*.exe "$base" +if [ -e build/win32 ]; then + # save debug symbols + cp -av build/win32/daemon/debug-symbols.tar.xz "$base-debug-symbols.tar.xz" + # save installer + cp -av build/win32/*.exe "$base" # zipit up yo archive="$base.zip" zip -r "$archive" "$base" @@ -43,9 +52,17 @@ elif [ -e lokinet.apk ] ; then # android af ngl archive="$base.apk" cp -av lokinet.apk "$archive" +elif [ -e build-docs ]; then + archive="$base.tar.xz" + cp -av build-docs/docs/mkdocs.yml build-docs/docs/markdown "$base" + tar cJvf "$archive" "$base" +elif [ -e build-mac ]; then + archive="$base.tar.xz" + mv build-mac/Lokinet*/ "$base" + tar cJvf "$archive" "$base" else - cp -av daemon/lokinet daemon/lokinet-vpn "$base" - cp -av ../contrib/bootstrap/mainnet.signed "$base/bootstrap.signed" + cp -av build/daemon/lokinet{,-vpn} "$base" + cp -av contrib/bootstrap/mainnet.signed "$base/bootstrap.signed" # tar dat shiz up yo archive="$base.tar.xz" tar cJvf "$archive" "$base" @@ -57,6 +74,7 @@ upload_to="oxen.rocks/${DRONE_REPO// /_}/${DRONE_BRANCH// /_}" # -mkdir a/, -mkdir a/b/, -mkdir a/b/c/, ... commands. The leading `-` allows the command to fail # without error. upload_dirs=(${upload_to//\// }) +put_debug= mkdirs= dir_tmp="" for p in "${upload_dirs[@]}"; do @@ -64,10 +82,13 @@ for p in "${upload_dirs[@]}"; do mkdirs="$mkdirs -mkdir $dir_tmp" done - +if [ -e "$base-debug-symbols.tar.xz" ] ; then + put_debug="put $base-debug-symbols.tar.xz $upload_to" +fi sftp -i ssh_key -b - -o StrictHostKeyChecking=off drone@oxen.rocks < build-cross/Makefile +for arch in $archs ; do + mkdir -p $root/build-cross/build-$arch + cd $root/build-cross/build-$arch + cmake \ + -G 'Unix Makefiles' \ + -DCROSS_PLATFORM=$platform \ + -DCROSS_PREFIX=$arch \ + -DCMAKE_EXE_LINKER_FLAGS=-fstack-protector \ + -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always \ + -DCMAKE_TOOLCHAIN_FILE=$root/contrib/cross/cross.toolchain.cmake \ + -DBUILD_STATIC_DEPS=ON \ + -DSTATIC_LINK=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_TESTING=OFF \ + -DBUILD_LIBLOKINET=OFF \ + -DWITH_TESTS=OFF \ + -DNATIVE_BUILD=OFF \ + -DSTATIC_LINK=ON \ + -DWITH_SYSTEMD=OFF \ + -DFORCE_OXENMQ_SUBMODULE=ON \ + -DSUBMODULE_CHECK=OFF \ + -DWITH_LTO=OFF \ + -DWITH_BOOTSTRAP=OFF \ + -DCMAKE_BUILD_TYPE=RelWithDeb \ + "${cmake_extra[@]}" \ + $root + cd $root/build-cross + echo -ne "$arch:\n\t\$(MAKE) -C build-$arch\n" >> $root/build-cross/Makefile + +done +cd $root +make -j${JOBS:-$(nproc)} -C build-cross diff --git a/contrib/cross/aarch64.toolchain.cmake b/contrib/cross/aarch64.toolchain.cmake deleted file mode 100644 index 03357daf1..000000000 --- a/contrib/cross/aarch64.toolchain.cmake +++ /dev/null @@ -1,12 +0,0 @@ -set(CMAKE_SYSTEM_NAME Linux) -set(TOOLCHAIN_PREFIX aarch64-linux-gnu) -#set(TOOLCHAIN_SUFFIX) - -set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) - -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - -set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc${TOOLCHAIN_SUFFIX}) -set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++${TOOLCHAIN_SUFFIX}) diff --git a/contrib/cross/armhf.toolchain.cmake b/contrib/cross/cross.toolchain.cmake similarity index 73% rename from contrib/cross/armhf.toolchain.cmake rename to contrib/cross/cross.toolchain.cmake index a61a270bb..2a7cde131 100644 --- a/contrib/cross/armhf.toolchain.cmake +++ b/contrib/cross/cross.toolchain.cmake @@ -1,7 +1,5 @@ -set(CMAKE_SYSTEM_NAME Linux) -set(TOOLCHAIN_PREFIX arm-linux-gnueabihf) -set(TOOLCHAIN_SUFFIX -8) - +set(CMAKE_SYSTEM_NAME ${CROSS_PLATFORM}) +set(TOOLCHAIN_PREFIX ${CROSS_PREFIX}) set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) @@ -10,3 +8,4 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc${TOOLCHAIN_SUFFIX}) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++${TOOLCHAIN_SUFFIX}) +set(ARCH_TRIPLET ${TOOLCHAIN_PREFIX}) diff --git a/contrib/cross/mingw_core.cmake b/contrib/cross/mingw_core.cmake index a42673eda..8086ab7f3 100644 --- a/contrib/cross/mingw_core.cmake +++ b/contrib/cross/mingw_core.cmake @@ -1,4 +1,8 @@ -set(CMAKE_SYSTEM_VERSION 5.0) +set(CMAKE_SYSTEM_VERSION 6.0) + +# the minimum windows version, set to 6 rn because supporting older windows is hell +set(_winver 0x0600) +add_definitions(-D_WIN32_WINNT=${_winver}) # target environment on the build host system # second one is for non-root installs diff --git a/contrib/cross/ppc64le.toolchain.cmake b/contrib/cross/ppc64le.toolchain.cmake deleted file mode 100644 index c045b4939..000000000 --- a/contrib/cross/ppc64le.toolchain.cmake +++ /dev/null @@ -1,12 +0,0 @@ -set(CMAKE_SYSTEM_NAME Linux) -set(TOOLCHAIN_PREFIX powerpc64le-linux-gnu) -set(TOOLCHAIN_SUFFIX -8) - -set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) - -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - -set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc${TOOLCHAIN_SUFFIX}) -set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++${TOOLCHAIN_SUFFIX}) diff --git a/contrib/dtrace/lokinet.xml b/contrib/dtrace/lokinet.xml deleted file mode 100644 index 82f46a6c9..000000000 --- a/contrib/dtrace/lokinet.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contrib/dtrace/profiler.d b/contrib/dtrace/profiler.d deleted file mode 100644 index ce45a1ea8..000000000 --- a/contrib/dtrace/profiler.d +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/sbin/dtrace -s - -syscall:::entry -/pid == $target/ -{ - @calls[ustack(10), probefunc] = count(); -} - -profile:::tick-1sec -{ - /** print */ - printa(@calls); - /** clear */ - clear(@calls); - trunc(@calls, 15); -} - - diff --git a/contrib/format-version.sh b/contrib/format-version.sh new file mode 100644 index 000000000..d8fe6e855 --- /dev/null +++ b/contrib/format-version.sh @@ -0,0 +1,19 @@ + +CLANG_FORMAT_DESIRED_VERSION=14 + +CLANG_FORMAT=$(command -v clang-format-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null) +if [ $? -ne 0 ]; then + CLANG_FORMAT=$(command -v clang-format-mp-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null) +fi +if [ $? -ne 0 ]; then + CLANG_FORMAT=$(command -v clang-format 2>/dev/null) + if [ $? -ne 0 ]; then + echo "Please install clang-format version $CLANG_FORMAT_DESIRED_VERSION and re-run this script." + exit 1 + fi + version=$(clang-format --version) + if [[ ! $version == *"clang-format version $CLANG_FORMAT_DESIRED_VERSION"* ]]; then + echo "Please install clang-format version $CLANG_FORMAT_DESIRED_VERSION and re-run this script." + exit 1 + fi +fi diff --git a/contrib/format.sh b/contrib/format.sh index 956f5db92..d757554e0 100755 --- a/contrib/format.sh +++ b/contrib/format.sh @@ -1,43 +1,27 @@ #!/usr/bin/env bash -CLANG_FORMAT_DESIRED_VERSION=11 - -binary=$(command -v clang-format-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null) -if [ $? -ne 0 ]; then - binary=$(command -v clang-format-mp-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null) -fi -if [ $? -ne 0 ]; then - binary=$(command -v clang-format 2>/dev/null) - if [ $? -ne 0 ]; then - echo "Please install clang-format version $CLANG_FORMAT_DESIRED_VERSION and re-run this script." - exit 1 - fi - version=$(clang-format --version) - if [[ ! $version == *"clang-format version $CLANG_FORMAT_DESIRED_VERSION"* ]]; then - echo "Please install clang-format version $CLANG_FORMAT_DESIRED_VERSION and re-run this script." - exit 1 - fi -fi +. $(dirname $0)/format-version.sh cd "$(dirname $0)/../" + if [ "$1" = "verify" ] ; then - if [ $($binary --output-replacements-xml $(find jni daemon llarp include pybind | grep -E '\.([hc](pp)?|mm?)$' | grep -v '\#') | grep '' | wc -l) -ne 0 ] ; 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 exit 2 fi else - $binary -i $(find jni daemon llarp include pybind | grep -E '\.([hc](pp)?|mm)$' | grep -v '\#') &> /dev/null + $CLANG_FORMAT -i $(find jni daemon llarp include pybind | grep -E '\.([hc](pp)?|m(m)?)$' | grep -v '#') &> /dev/null fi swift_format=$(command -v swiftformat 2>/dev/null) if [ $? -eq 0 ]; then if [ "$1" = "verify" ] ; then - for f in $(find daemon | grep -E '\.swift$' | grep -v '\#') ; do + for f in $(find daemon | grep -E '\.swift$' | grep -v '#') ; do if [ $($swift_format --quiet --dryrun < "$f" | diff "$f" - | wc -l) -ne 0 ] ; then exit 3 fi done else - $swift_format --quiet $(find daemon | grep -E '\.swift$' | grep -v '\#') + $swift_format --quiet $(find daemon | grep -E '\.swift$' | grep -v '#') fi fi diff --git a/contrib/freebsd/openrc/lokinet.rc b/contrib/freebsd/openrc/lokinet.rc deleted file mode 100644 index 67d8b336d..000000000 --- a/contrib/freebsd/openrc/lokinet.rc +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -. /etc/rc.subr - -name=lokinet -rcvar=lokinet_enable - -command="/usr/local/bin/${name}" -command_args="/usr/local/etc/${name}/daemon.ini > /dev/null 2>&1" - -pidfile="/usr/local/etc/${name}/lokinet.pid" - -required_files="/usr/local/etc/${name}/daemon.ini" - -sig_reload="HUP" - -start_precmd="${command} -g /usr/local/etc/${name}/daemon.ini" - -load_rc_config $name -run_rc_command "$1" \ No newline at end of file diff --git a/contrib/keygen.py b/contrib/keygen.py new file mode 100755 index 000000000..44891d1bf --- /dev/null +++ b/contrib/keygen.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# +# .loki secret key generator script +# makes keyfile contents +# +# usage: python3 keygen.py out.private +# python3 keygen.py > /some/where/over/the/rainbow +# +from nacl.bindings import crypto_sign_keypair +import sys + +out = sys.stdout + +close_out = lambda : None +args = sys.argv[1:] + +if args and args[0] != '-': + out = open(args[0], 'wb') + close_out = out.close + +pk, sk = crypto_sign_keypair() +out.write(b'64:') +out.write(sk) +out.flush() +close_out() + diff --git a/contrib/liblokinet/CMakeLists.txt b/contrib/liblokinet/CMakeLists.txt new file mode 100644 index 000000000..6985f741b --- /dev/null +++ b/contrib/liblokinet/CMakeLists.txt @@ -0,0 +1,10 @@ + +cmake_minimum_required(VERSION 3.10) + +project(udptest LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +add_executable(udptest udptest.cpp) +include_directories(../../include) +target_link_libraries(udptest PUBLIC lokinet) + diff --git a/contrib/liblokinet/readme.md b/contrib/liblokinet/readme.md new file mode 100644 index 000000000..e0f63d135 --- /dev/null +++ b/contrib/liblokinet/readme.md @@ -0,0 +1,13 @@ +# liblokinet examples + +building: + + $ mkdir -p build + $ cd build + $ cp /path/to/liblokinet.so . + $ cmake .. -DCMAKE_EXE_LINKER_FLAGS='-L.' + $ make + +running: + + $ ./udptest /path/to/bootstrap.signed diff --git a/contrib/liblokinet/udptest.cpp b/contrib/liblokinet/udptest.cpp new file mode 100644 index 000000000..9db9c399e --- /dev/null +++ b/contrib/liblokinet/udptest.cpp @@ -0,0 +1,239 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool _run{true}; + +using Lokinet_ptr = std::shared_ptr; + +[[nodiscard]] auto +MakeLokinet(const std::vector& bootstrap) +{ + auto ctx = std::shared_ptr(lokinet_context_new(), lokinet_context_free); + if (auto err = lokinet_add_bootstrap_rc(bootstrap.data(), bootstrap.size(), ctx.get())) + throw std::runtime_error{strerror(err)}; + if (lokinet_context_start(ctx.get())) + throw std::runtime_error{"could not start context"}; + return ctx; +} + +void +WaitForReady(const Lokinet_ptr& ctx) +{ + while (_run and lokinet_wait_for_ready(1000, ctx.get())) + { + std::cout << "waiting for context..." << std::endl; + } +} + +class Flow +{ + lokinet_udp_flowinfo const _info; + lokinet_context* const _ctx; + + public: + explicit Flow(const lokinet_udp_flowinfo* info, lokinet_context* ctx) : _info{*info}, _ctx{ctx} + {} + + lokinet_context* + Context() const + { + return _ctx; + } + + std::string + String() const + { + std::stringstream ss; + ss << std::string{_info.remote_host} << ":" << std::to_string(_info.remote_port) + << " on socket " << _info.socket_id; + return ss.str(); + } +}; + +struct ConnectJob +{ + lokinet_udp_flowinfo remote; + lokinet_context* ctx; +}; + +void +CreateOutboundFlow(void* user, void** flowdata, int* timeout) +{ + auto* job = static_cast(user); + Flow* flow = new Flow{&job->remote, job->ctx}; + *flowdata = flow; + *timeout = 30; + std::cout << "made outbound flow: " << flow->String() << std::endl; + ; +} + +int +ProcessNewInboundFlow(void* user, const lokinet_udp_flowinfo* remote, void** flowdata, int* timeout) +{ + auto* ctx = static_cast(user); + Flow* flow = new Flow{remote, ctx}; + std::cout << "new udp flow: " << flow->String() << std::endl; + *flowdata = flow; + *timeout = 30; + + return 0; +} + +void +DeleteFlow(const lokinet_udp_flowinfo* remote, void* flowdata) +{ + auto* flow = static_cast(flowdata); + std::cout << "udp flow from " << flow->String() << " timed out" << std::endl; + delete flow; +} + +void +HandleUDPPacket(const lokinet_udp_flowinfo* remote, const char* pkt, size_t len, void* flowdata) +{ + auto* flow = static_cast(flowdata); + std::cout << "we got " << len << " bytes of udp from " << flow->String() << std::endl; +} + +void +BounceUDPPacket(const lokinet_udp_flowinfo* remote, const char* pkt, size_t len, void* flowdata) +{ + auto* flow = static_cast(flowdata); + std::cout << "bounce " << len << " bytes of udp from " << flow->String() << std::endl; + if (auto err = lokinet_udp_flow_send(remote, pkt, len, flow->Context())) + { + std::cout << "bounce failed: " << strerror(err) << std::endl; + } +} + +Lokinet_ptr sender, recip; + +void +signal_handler(int) +{ + _run = false; +} + +int +main(int argc, char* argv[]) +{ + if (argc == 1) + { + std::cout << "usage: " << argv[0] << " bootstrap.signed" << std::endl; + return 1; + } + + /* + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + */ + + std::vector bootstrap; + + // load bootstrap.signed + { + std::ifstream inf{argv[1], std::ifstream::ate | std::ifstream::binary}; + size_t len = inf.tellg(); + inf.seekg(0); + bootstrap.resize(len); + inf.read(bootstrap.data(), bootstrap.size()); + } + + if (auto* loglevel = getenv("LOKINET_LOG")) + lokinet_log_level(loglevel); + else + lokinet_log_level("none"); + + std::cout << "starting up" << std::endl; + + recip = MakeLokinet(bootstrap); + WaitForReady(recip); + + lokinet_udp_bind_result recipBindResult{}; + + const auto port = 10000; + + if (auto err = lokinet_udp_bind( + port, + ProcessNewInboundFlow, + BounceUDPPacket, + DeleteFlow, + recip.get(), + &recipBindResult, + recip.get())) + { + std::cout << "failed to bind recip udp socket " << strerror(err) << std::endl; + return 0; + } + + std::cout << "bound recip udp" << std::endl; + + sender = MakeLokinet(bootstrap); + WaitForReady(sender); + + std::string recipaddr{lokinet_address(recip.get())}; + + std::cout << "recip ready at " << recipaddr << std::endl; + + lokinet_udp_bind_result senderBindResult{}; + + if (auto err = lokinet_udp_bind( + port, + ProcessNewInboundFlow, + HandleUDPPacket, + DeleteFlow, + sender.get(), + &senderBindResult, + sender.get())) + { + std::cout << "failed to bind sender udp socket " << strerror(err) << std::endl; + return 0; + } + + ConnectJob connect{}; + connect.remote.socket_id = senderBindResult.socket_id; + connect.remote.remote_port = port; + std::copy_n(recipaddr.c_str(), recipaddr.size(), connect.remote.remote_host); + connect.ctx = sender.get(); + + std::cout << "bound sender udp" << std::endl; + + do + { + std::cout << "try establish to " << connect.remote.remote_host << std::endl; + if (auto err = + lokinet_udp_establish(CreateOutboundFlow, &connect, &connect.remote, sender.get())) + { + std::cout << "failed to establish to recip: " << strerror(err) << std::endl; + usleep(100000); + } + else + break; + } while (true); + std::cout << "sender established" << std::endl; + + const std::string buf{"liblokinet"}; + + const std::string senderAddr{lokinet_address(sender.get())}; + + do + { + std::cout << senderAddr << " send to remote: " << buf << std::endl; + if (auto err = lokinet_udp_flow_send(&connect.remote, buf.data(), buf.size(), sender.get())) + { + std::cout << "send failed: " << strerror(err) << std::endl; + } + usleep(100000); + } while (_run); + return 0; +} diff --git a/contrib/lokinet-mac.svg b/contrib/lokinet-mac.svg new file mode 100644 index 000000000..10ddf0ca2 --- /dev/null +++ b/contrib/lokinet-mac.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/lokinet.svg b/contrib/lokinet.svg index 896e74bbb..dcf5c5d77 100644 --- a/contrib/lokinet.svg +++ b/contrib/lokinet.svg @@ -1,21 +1,34 @@ - + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/contrib/mac-configure.sh b/contrib/mac-configure.sh new file mode 100755 index 000000000..fa45a960b --- /dev/null +++ b/contrib/mac-configure.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +set -e +set -x + +if ! [ -f LICENSE ] || ! [ -d llarp ]; then + echo "You need to run this as ./contrib/mac.sh from the top-level lokinet project directory" >&2 + exit 1 +fi + +mkdir -p build-mac +cd build-mac +cmake \ + -G Ninja \ + -DBUILD_STATIC_DEPS=ON \ + -DBUILD_LIBLOKINET=OFF \ + -DWITH_TESTS=OFF \ + -DWITH_BOOTSTRAP=OFF \ + -DNATIVE_BUILD=OFF \ + -DWITH_LTO=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DMACOS_SYSTEM_EXTENSION=ON \ + -DCODESIGN=ON \ + -DBUILD_PACKAGE=ON \ + "$@" \ + .. + +echo "cmake build configured in build-mac" diff --git a/contrib/mac.sh b/contrib/mac.sh index 4719b40d2..0a51fe672 100755 --- a/contrib/mac.sh +++ b/contrib/mac.sh @@ -8,32 +8,20 @@ # set -e -set +x -if ! [ -f LICENSE.txt ] || ! [ -d llarp ]; then - echo "You need to run this as ./contrib/mac.sh from the top-level lokinet project directory" +set -x + +if ! [ -f LICENSE ] || ! [ -d llarp ]; then + echo "You need to run this as ./contrib/mac.sh from the top-level lokinet project directory" >&2 + exit 1 fi -mkdir -p build-mac +./contrib/mac-configure.sh "$@" + cd build-mac -cmake \ - -G Ninja \ - -DBUILD_STATIC_DEPS=ON \ - -DBUILD_PACKAGE=ON \ - -DBUILD_SHARED_LIBS=OFF \ - -DBUILD_TESTING=OFF \ - -DBUILD_LIBLOKINET=OFF \ - -DWITH_TESTS=OFF \ - -DNATIVE_BUILD=OFF \ - -DSTATIC_LINK=ON \ - -DWITH_SYSTEMD=OFF \ - -DFORCE_OXENMQ_SUBMODULE=ON \ - -DSUBMODULE_CHECK=OFF \ - -DWITH_LTO=ON \ - -DCMAKE_BUILD_TYPE=Release \ - "$@" \ - .. -ninja sign +rm -rf Lokinet\ * +ninja -j${JOBS:-1} package +cd .. echo -e "Build complete, your app is here:\n" -ls -lad $(pwd)/daemon/lokinet.app +ls -lad $(pwd)/build-mac/Lokinet\ * echo "" diff --git a/contrib/macos/Info.plist.in b/contrib/macos/Info.plist.in deleted file mode 100644 index 9311f2404..000000000 --- a/contrib/macos/Info.plist.in +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - Lokinet - CFBundleExecutable - MacOS/lokinet - CFBundleIdentifier - com.loki-project.lokinet - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - lokinet - CFBundlePackageType - XPC! - CFBundleShortVersionString - @lokinet_VERSION@ - CFBundleVersion - @lokinet_VERSION@.@LOKINET_APPLE_BUILD@ - - diff --git a/contrib/macos/InfoPlist.strings b/contrib/macos/InfoPlist.strings new file mode 100644 index 000000000..873c6aed6 Binary files /dev/null and b/contrib/macos/InfoPlist.strings differ diff --git a/contrib/macos/LokinetExtension.Info.plist.in b/contrib/macos/LokinetExtension.Info.plist.in deleted file mode 100644 index 80afb1b94..000000000 --- a/contrib/macos/LokinetExtension.Info.plist.in +++ /dev/null @@ -1,40 +0,0 @@ - - - - - CFBundleDisplayName - Lokinet - - CFBundleExecutable - lokinet-extension - - CFBundleIdentifier - com.loki-project.lokinet.network-extension - - CFBundleInfoDictionaryVersion - 6.0 - - CFBundlePackageType - XPC! - - CFBundleName - lokinet - - CFBundleVersion - @lokinet_VERSION@ - - ITSAppUsesNonExemptEncryption - - - LSMinimumSystemVersion - 11.0 - - NSExtension - - NSExtensionPointIdentifier - com.apple.networkextension.packet-tunnel - NSExtensionPrincipalClass - LLARPPacketTunnel - - - diff --git a/contrib/macos/README.txt b/contrib/macos/README.txt deleted file mode 100644 index 9880ecc3c..000000000 --- a/contrib/macos/README.txt +++ /dev/null @@ -1,38 +0,0 @@ -This directory contains the magical incantations and random voodoo symbols needed to coax an Apple -build. There's no reason builds have to be this stupid, except that Apple wants to funnel everyone -into the no-CI, no-help, undocumented, non-toy-apps-need-not-apply modern Apple culture. - -This is disgusting. - -But it gets worse. - -The following two files, in particular, are the very worst manifestations of this already toxic -Apple cancer: they are required for proper permissions to run on macOS, are undocumented, and can -only be regenerated through the entirely closed source Apple Developer backend, for which you have -to pay money first to get a team account (a personal account will not work), and they lock the -resulting binaries to only run on individually selected Apple computers selected at the time the -profile is provisioned (with no ability to allow it to run anywhere). - - lokinet.provisionprofile - lokinet-extension.provisionprofile - -This is actively hostile to open source development, but that is nothing new for Apple. - -In order to make things work, you'll have to replace these provisioning profiles with your own -(after paying Apple for the privilege of developing on their platform, of course) and change all the -team/application/bundle IDs to reference your own team, matching the provisioning profiles. The -provisioning profiles must be a "macOS Development" provisioning profile, and must include the -signing keys and the authorized devices on which you want to run it. (The profiles bundled in this -repository contains the lokinet team's "Apple Development" keys associated with the Oxen project, -and mac dev boxes. This is *useless* for anyone else). - -Also take note that you *must not* put a development build `lokinet.app` inside /Applications -because if you do, it won't work because *on top* of the ridiculous signing and entitlement bullshit -that Apple makes you jump through, the rules *also* differ for binaries placed in /Applications -versus binaries placed elsewhere, but like everything else here, it is entirely undocumented. - -If you are reading this to try to build Lokinet for yourself for an Apple operating system and -simultaneously care about open source, privacy, or freedom then you, my friend, are a walking -contradiction: you are trying to get Lokinet to work on a platform that actively despises open -source, privacy, and freedom. Even Windows is a better choice in all of these categories than -Apple. diff --git a/contrib/macos/installer.png b/contrib/macos/installer.png new file mode 100644 index 000000000..1be3b2839 Binary files /dev/null and b/contrib/macos/installer.png differ diff --git a/contrib/macos/installer.tiff b/contrib/macos/installer.tiff new file mode 100644 index 000000000..569c5bcc1 Binary files /dev/null and b/contrib/macos/installer.tiff differ diff --git a/contrib/macos/installer@2x.png b/contrib/macos/installer@2x.png new file mode 100644 index 000000000..d8c289635 Binary files /dev/null and b/contrib/macos/installer@2x.png differ diff --git a/contrib/macos/lokinet-extension.Info.plist.in b/contrib/macos/lokinet-extension.Info.plist.in new file mode 100644 index 000000000..7647393ee --- /dev/null +++ b/contrib/macos/lokinet-extension.Info.plist.in @@ -0,0 +1,64 @@ + + + + + CFBundleDevelopmentRegion + en + + CFBundleDisplayName + Lokinet Network Extension + + CFBundleExecutable + org.lokinet.network-extension + + CFBundleIdentifier + org.lokinet.network-extension + + CFBundleInfoDictionaryVersion + 6.0 + + CFBundlePackageType + SYSX + + CFBundleName + org.lokinet.network-extension + + CFBundleVersion + @lokinet_VERSION@.@LOKINET_APPLE_BUILD@ + + CFBundleShortVersionString + @lokinet_VERSION@ + + CFBundleSupportedPlatforms + + MacOSX + + + ITSAppUsesNonExemptEncryption + + + LSMinimumSystemVersion + 10.15 + + NSHumanReadableCopyright + Copyright © 2022 The Oxen Project, licensed under GPLv3-or-later + + NSSystemExtensionUsageDescription + Provides Lokinet Network connectivity. + + NetworkExtension + + NEMachServiceName + SUQ8J2PCT7.org.lokinet.network-extension + + NEProviderClasses + + com.apple.networkextension.packet-tunnel + LLARPPacketTunnel + + com.apple.networkextension.dns-proxy + LLARPDNSProxy + + + + diff --git a/contrib/macos/lokinet-extension.dev.provisionprofile b/contrib/macos/lokinet-extension.dev.provisionprofile new file mode 100644 index 000000000..c7a1b3269 Binary files /dev/null and b/contrib/macos/lokinet-extension.dev.provisionprofile differ diff --git a/contrib/macos/lokinet-extension.entitlements.plist b/contrib/macos/lokinet-extension.plugin.entitlements.plist similarity index 82% rename from contrib/macos/lokinet-extension.entitlements.plist rename to contrib/macos/lokinet-extension.plugin.entitlements.plist index 8233a7926..b8baadbc7 100644 --- a/contrib/macos/lokinet-extension.entitlements.plist +++ b/contrib/macos/lokinet-extension.plugin.entitlements.plist @@ -3,11 +3,12 @@ com.apple.application-identifier - SUQ8J2PCT7.com.loki-project.lokinet.network-extension + SUQ8J2PCT7.org.lokinet.network-extension com.apple.developer.networking.networkextension packet-tunnel-provider + dns-proxy com.apple.developer.team-identifier @@ -16,9 +17,6 @@ com.apple.security.app-sandbox - com.apple.security.get-task-allow - - com.apple.security.network.client diff --git a/contrib/macos/lokinet-extension.provisionprofile b/contrib/macos/lokinet-extension.provisionprofile deleted file mode 100644 index 71f066bda..000000000 Binary files a/contrib/macos/lokinet-extension.provisionprofile and /dev/null differ diff --git a/contrib/macos/lokinet-extension.release.provisionprofile b/contrib/macos/lokinet-extension.release.provisionprofile new file mode 100644 index 000000000..1eaefd12e Binary files /dev/null and b/contrib/macos/lokinet-extension.release.provisionprofile differ diff --git a/contrib/macos/lokinet-extension.sysext.entitlements.plist b/contrib/macos/lokinet-extension.sysext.entitlements.plist new file mode 100644 index 000000000..26086f9fe --- /dev/null +++ b/contrib/macos/lokinet-extension.sysext.entitlements.plist @@ -0,0 +1,32 @@ + + + + + com.apple.application-identifier + SUQ8J2PCT7.org.lokinet.network-extension + + com.apple.developer.networking.networkextension + + packet-tunnel-provider-systemextension + dns-proxy-systemextension + + + com.apple.developer.team-identifier + SUQ8J2PCT7 + + com.apple.security.app-sandbox + + + com.apple.security.application-groups + + SUQ8J2PCT7.org.lokinet + + + com.apple.security.network.client + + + com.apple.security.network.server + + + + diff --git a/contrib/macos/lokinet.Info.plist.in b/contrib/macos/lokinet.Info.plist.in new file mode 100644 index 000000000..1ca51c59e --- /dev/null +++ b/contrib/macos/lokinet.Info.plist.in @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + en + + CFBundleExecutable + Lokinet + + CFBundleIdentifier + org.lokinet + + CFBundleInfoDictionaryVersion + 6.0 + + CFBundleName + Lokinet + + CFBundleIconFile + icon.icns + + CFBundlePackageType + APPL + + CFBundleShortVersionString + @lokinet_VERSION@ + + CFBundleVersion + @lokinet_VERSION@.@LOKINET_APPLE_BUILD@ + + LSMinimumSystemVersion + 10.15 + + NSHumanReadableCopyright + Copyright © 2022 The Oxen Project, licensed under GPLv3-or-later + + LSUIElement + + + LSHasLocalizedDisplayName + + + + diff --git a/contrib/macos/lokinet.dev.provisionprofile b/contrib/macos/lokinet.dev.provisionprofile new file mode 100644 index 000000000..e15cccff4 Binary files /dev/null and b/contrib/macos/lokinet.dev.provisionprofile differ diff --git a/contrib/macos/lokinet.entitlements.plist b/contrib/macos/lokinet.plugin.entitlements.plist similarity index 64% rename from contrib/macos/lokinet.entitlements.plist rename to contrib/macos/lokinet.plugin.entitlements.plist index 3869f5b04..7c172b9e0 100644 --- a/contrib/macos/lokinet.entitlements.plist +++ b/contrib/macos/lokinet.plugin.entitlements.plist @@ -3,13 +3,13 @@ com.apple.application-identifier - SUQ8J2PCT7.com.loki-project.lokinet + SUQ8J2PCT7.org.lokinet com.apple.developer.networking.networkextension packet-tunnel-provider - dns-proxy - dns-settings + dns-proxy + dns-settings com.apple.developer.team-identifier @@ -18,13 +18,10 @@ com.apple.security.app-sandbox - com.apple.security.get-task-allow + com.apple.security.network.client - com.apple.security.network.client - - - com.apple.security.network.server + com.apple.security.network.server diff --git a/contrib/macos/lokinet.provisionprofile b/contrib/macos/lokinet.provisionprofile deleted file mode 100644 index f740cd98a..000000000 Binary files a/contrib/macos/lokinet.provisionprofile and /dev/null differ diff --git a/contrib/macos/lokinet.release.provisionprofile b/contrib/macos/lokinet.release.provisionprofile new file mode 100644 index 000000000..6aaeead39 Binary files /dev/null and b/contrib/macos/lokinet.release.provisionprofile differ diff --git a/contrib/macos/lokinet.sysext.entitlements.plist b/contrib/macos/lokinet.sysext.entitlements.plist new file mode 100644 index 000000000..4c08d92f5 --- /dev/null +++ b/contrib/macos/lokinet.sysext.entitlements.plist @@ -0,0 +1,36 @@ + + + + + com.apple.application-identifier + SUQ8J2PCT7.org.lokinet + + com.apple.developer.networking.networkextension + + packet-tunnel-provider-systemextension + dns-proxy-systemextension + dns-settings + + + com.apple.developer.team-identifier + SUQ8J2PCT7 + + com.apple.developer.system-extension.install + + + com.apple.security.app-sandbox + + + com.apple.security.application-groups + + SUQ8J2PCT7.org.lokinet + + + com.apple.security.network.client + + + com.apple.security.network.server + + + + diff --git a/contrib/macos/lokinet_macos_daemon_script.sh b/contrib/macos/lokinet_macos_daemon_script.sh deleted file mode 100755 index 279f1db66..000000000 --- a/contrib/macos/lokinet_macos_daemon_script.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh - -scutil_query() -{ - key=$1 - - scutil< - - - - Label - network.loki.lokinet.daemon - - ProgramArguments - - /var/lib/lokinet/lokinet_macos_daemon_script.sh - - - - KeepAlive - - PathState - - /var/lib/lokinet/suspend-launchd-service - - - - - StandardOutPath - /var/log/lokinet.log - - diff --git a/contrib/macos/notarize.py.in b/contrib/macos/notarize.py.in old mode 100644 new mode 100755 index e042bface..b35cee2d9 --- a/contrib/macos/notarize.py.in +++ b/contrib/macos/notarize.py.in @@ -4,21 +4,47 @@ import sys import plistlib import subprocess import time +import os +import os.path + +def bold_red(x): + return "\x1b[31;1m" + x + "\x1b[0m" + +if not @notarize_py_is_sysext@: + print(bold_red("\nUnable to notarize: this lokinet is not built as a system extension\n"), file=sys.stderr) + sys.exit(1) + +if not all(("@MACOS_NOTARIZE_USER@", "@MACOS_NOTARIZE_PASS@", "@MACOS_NOTARIZE_ASC@")): + print(bold_red("\nUnable to notarize: one or more required notarization variable not set; see contrib/macos/README.txt\n") + + " Called with -DMACOS_NOTARIZE_USER=@MACOS_NOTARIZE_USER@\n" + " -DMACOS_NOTARIZE_PASS=@MACOS_NOTARIZE_PASS@\n" + " -DMACOS_NOTARIZE_ASC=@MACOS_NOTARIZE_ASC@\n", + file=sys.stderr) + sys.exit(1) + +os.chdir("@PROJECT_BINARY_DIR@") +app = "@lokinet_app@" +zipfile = f"Lokinet.app.notarize.zip" +print(f"Creating {zipfile} from {app}") +if os.path.exists(zipfile): + os.remove(zipfile) +subprocess.run(['ditto', '-v', '-c', '-k', '--sequesterRsrc', '--keepParent', app, zipfile]) -pkg = "lokinet-@PROJECT_VERSION@-Darwin.pkg" userpass = ('--username', "@MACOS_NOTARIZE_USER@", '--password', "@MACOS_NOTARIZE_PASS@") -print("Submitting {} for notarization; this may take a minute...".format(pkg)) +print("Submitting {} for notarization; this may take a minute...".format(zipfile)) started = time.time() -result = subprocess.run([ +command = [ 'xcrun', 'altool', '--notarize-app', - '--primary-bundle-id', 'org.lokinet.lokinet.pkg.@PROJECT_VERSION@', + '--primary-bundle-id', 'org.lokinet.@PROJECT_VERSION@', *userpass, '--asc-provider', "@MACOS_NOTARIZE_ASC@", - '--file', pkg, + '--file', zipfile, '--output-format', 'xml' - ], stdout=subprocess.PIPE) + ] +print(command) +result = subprocess.run(command, stdout=subprocess.PIPE) data = plistlib.loads(result.stdout) if 'success-message' not in data or 'notarization-upload' not in data or 'RequestUUID' not in data['notarization-upload']: @@ -42,24 +68,27 @@ while not done: '--notarization-info', uuid, *userpass, '--output-format', 'xml' - ], stdout=subprocess.PIPE) - result.check_returncode() - data = plistlib.loads(result.stdout) - if 'notarization-info' not in data or 'Status' not in data['notarization-info']: - status = 'Request failed' + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode == 1 and b'Gateway Timeout' in result.stderr: + status = "Apple's servers are trash (aka Gateway Timeout)" else: - status = data['notarization-info']['Status Message'] if 'Status Message' in data['notarization-info'] else '' - st = data['notarization-info']['Status'] - if st == 'success': - success = True - done = True - elif st == 'invalid': - done = True - elif st == 'in progress' and len(status) == 0: - status = 'Notarization in progress' - - if done and 'LogFileURL' in data['notarization-info']: - status += '\n\nlog file: {}'.format(data['notarization-info']['LogFileURL']) + result.check_returncode() + data = plistlib.loads(result.stdout) + if 'notarization-info' not in data or 'Status' not in data['notarization-info']: + status = 'Request failed' + else: + status = data['notarization-info']['Status Message'] if 'Status Message' in data['notarization-info'] else '' + st = data['notarization-info']['Status'] + if st == 'success': + success = True + done = True + elif st == 'invalid': + done = True + elif st == 'in progress' and len(status) == 0: + status = 'Notarization in progress' + + if done and 'LogFileURL' in data['notarization-info']: + status += '\n\nlog file: {}'.format(data['notarization-info']['LogFileURL']) elapsed = time.time() - started_waiting mins, secs = int(elapsed // 60), int(elapsed % 60) @@ -70,7 +99,15 @@ print("\n") if not success: sys.exit(42) -print("Stapling {}".format(pkg)) -result = subprocess.run(['xcrun', 'stapler', 'staple', pkg]) +if os.path.exists(zipfile): + os.remove(zipfile) + +print("Stapling {}...".format(app), end='') +result = subprocess.run(['xcrun', 'stapler', 'staple', app]) result.check_returncode() + +with open("macos-notarized.stamp", 'w'): + pass + +print(" success.\n") diff --git a/contrib/macos/postinstall b/contrib/macos/postinstall deleted file mode 100644 index ac2674f7f..000000000 --- a/contrib/macos/postinstall +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh - -PERMS_OWNER=root -PERMS_GROUP=admin -CHOWN=$PERMS_OWNER:$PERMS_GROUP - -# set up lokinet data dir -[ -e /var/lib/lokinet/ ] || mkdir /var/lib/lokinet -chown $CHOWN /var/lib/lokinet -chmod g+w /var/lib/lokinet - -# mv files copied into $INSTALL_PREFIX/extra/ to their proper locations -mv /opt/lokinet/extra/lokinet_macos_daemon_script.sh /var/lib/lokinet -chown $CHOWN /var/lib/lokinet/lokinet_macos_daemon_script.sh -chmod 770 /var/lib/lokinet/lokinet_macos_daemon_script.sh - -mv /opt/lokinet/extra/network.loki.lokinet.daemon.plist /Library/LaunchDaemons/ -chown $CHOWN /Library/LaunchDaemons/network.loki.lokinet.daemon.plist -chmod 640 /Library/LaunchDaemons/network.loki.lokinet.daemon.plist - -mv /opt/lokinet/extra/lokinet-newsyslog.conf /etc/newsyslog.d/lokinet.conf -chown $CHOWN /etc/newsyslog.d/lokinet.conf -chmod 640 /etc/newsyslog.d/lokinet.conf - -# clean up by removing 'extra/' (so long as it's empty) -rmdir /opt/lokinet/extra/ - -# bootstrap -/opt/lokinet/bin/lokinet-bootstrap mainnet /var/lib/lokinet/bootstrap.signed -chown $CHOWN /var/lib/lokinet/bootstrap.signed - -# generate configs -/opt/lokinet/bin/lokinet -g /var/lib/lokinet/lokinet.ini -chown $CHOWN /var/lib/lokinet/lokinet.ini - -# register with launchd and start -launchctl load /Library/LaunchDaemons/network.loki.lokinet.daemon.plist -launchctl start network.loki.lokinet.daemon diff --git a/contrib/macos/preinstall b/contrib/macos/preinstall deleted file mode 100644 index 27006b982..000000000 --- a/contrib/macos/preinstall +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/sh - - -# this is for dns tomfoolery -scutil_query() -{ - key=$1 - - scutil<&2 + exit 1 +fi + +signit() { + target="$1" + entitlements="$2" + echo -e "\n\e[33;1mSigning ${target/*\/Lokinet.app/Lokinet.app}...\e[0m" >&2 + codesign \ + --verbose=4 \ + --force \ + -s "@CODESIGN_ID@" \ + --entitlements "$entitlements" \ + --strict \ + --timestamp \ + --options=runtime \ + "$target" +} + +gui_entitlements="@PROJECT_SOURCE_DIR@/gui/node_modules/app-builder-lib/templates/entitlements.mac.plist" +ext_entitlements="@PROJECT_SOURCE_DIR@/contrib/macos/lokinet-extension.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist" +app_entitlements="@PROJECT_SOURCE_DIR@/contrib/macos/lokinet.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist" + +SIGN_TARGET="@PROJECT_BINARY_DIR@/Lokinet @PROJECT_VERSION@/Lokinet.app" + +for ext in systemextension appex; do + netext="$SIGN_TARGET/@lokinet_ext_dir@/org.lokinet.network-extension.$ext" + if [ -e "$netext" ]; then + signit "$netext" "$ext_entitlements" + fi done + +if [ "@BUILD_GUI@" == "ON" ]; then + gui_app="$SIGN_TARGET"/Contents/Helpers/Lokinet-GUI.app + gui_sign_targets=() + for bundle in \ + "$gui_app"/Contents/Frameworks/*.framework \ + "$gui_app"/Contents/Frameworks/*.app + do + + if [ -d "$bundle/Libraries" ]; then + gui_sign_targets+=("$bundle"/Libraries/*.dylib) + fi + if [ -d "$bundle/Helpers" ]; then + gui_sign_targets+=("$bundle"/Helpers/*) + fi + if [ -d "$bundle/Resources" ]; then + for f in "$bundle/Resources"/*; do + if [[ -f "$f" && -x "$f" && "$(file -b "$f")" == Mach-O* ]]; then + gui_sign_targets+=("$f") + fi + done + fi + + gui_sign_targets+=("$bundle") + done + + gui_sign_targets+=("$gui_app") + + for target in "${gui_sign_targets[@]}"; do + signit "$target" "$gui_entitlements" + done + + signit "$SIGN_TARGET"/Contents/MacOS/Lokinet "$app_entitlements" +fi + +signit "$SIGN_TARGET" "$app_entitlements" + +touch "@PROJECT_BINARY_DIR@"/macos-signed.stamp diff --git a/contrib/macos/uninstaller/CMakeLists.txt b/contrib/macos/uninstaller/CMakeLists.txt deleted file mode 100644 index eddfdfaa0..000000000 --- a/contrib/macos/uninstaller/CMakeLists.txt +++ /dev/null @@ -1,85 +0,0 @@ -cmake_minimum_required(VERSION 3.10) # bionic's cmake version - -# Has to be set before `project()`, and ignored on non-macos: -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "macOS deployment target (Apple clang only)") - -find_program(CCACHE_PROGRAM ccache) -if(CCACHE_PROGRAM) - foreach(lang C CXX) - if(NOT DEFINED CMAKE_${lang}_COMPILER_LAUNCHER AND NOT CMAKE_${lang}_COMPILER MATCHES ".*/ccache") - message(STATUS "Enabling ccache for ${lang}") - set(CMAKE_${lang}_COMPILER_LAUNCHER ${CCACHE_PROGRAM} CACHE STRING "") - endif() - endforeach() -endif() - -set(PROJECT_NAME lokinet-uninstaller) -project(${PROJECT_NAME} - VERSION 0.0.1 - DESCRIPTION "lokinet uninstaller for macos" - LANGUAGES CXX) - -add_executable(${PROJECT_NAME} - main.cpp) - -find_package(Qt5 COMPONENTS Widgets REQUIRED) - -target_link_libraries(${PROJECT_NAME} PRIVATE - "-framework Security" - Qt5::Core Qt5::Widgets) - -set_target_properties(${PROJECT_NAME} - PROPERTIES - CXX_STANDARD 17 - CXX_EXTENSIONS OFF - CXX_STANDARD_REQUIRED ON - ) - - -set(MACOS_SIGN "" - CACHE STRING "enable codesigning -- use a 'Apple Distribution' key (or key description) from `security find-identity -v`") - -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lokinet-uninstall.icns - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/mk-icns.sh ${CMAKE_CURRENT_SOURCE_DIR}/icon.svg ${CMAKE_CURRENT_BINARY_DIR}/lokinet-uninstall.icns - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/icon.svg ${CMAKE_CURRENT_SOURCE_DIR}/mk-icns.sh) - -target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/lokinet-uninstall.icns) - -set_target_properties(${PROJECT_NAME} - PROPERTIES - MACOSX_BUNDLE TRUE - OUTPUT_NAME UninstallLokinet - RESOURCE "${CMAKE_CURRENT_BINARY_DIR}/lokinet-uninstall.icns") - -set(MACOSX_BUNDLE_BUNDLE_NAME UninstallLokinet) -set(MACOSX_BUNDLE_GUI_IDENTIFIER org.lokinet.lokinet-uninstaller) -set(MACOSX_BUNDLE_INFO_STRING "Lokinet uninstaller") -set(MACOSX_BUNDLE_ICON_FILE lokinet-uninstall.icns) -set(MACOSX_BUNDLE_LONG_VERSION_STRING ${PROJECT_VERSION}) -set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}) -set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}) -set(MACOSX_BUNDLE_COPYRIGHT "© 2020, The Loki Project") - -get_target_property(uic_location Qt5::uic IMPORTED_LOCATION) -get_filename_component(qt_dir ${uic_location} DIRECTORY) - -if(MACOS_SIGN) - add_custom_command(TARGET ${PROJECT_NAME} - POST_BUILD - COMMAND echo "Running qt magic macos deploy script" - COMMAND "${qt_dir}/macdeployqt" UninstallLokinet.app -always-overwrite - COMMAND echo "Signing app bundle and everything inside it" - COMMAND codesign -s "${MACOS_SIGN}" --deep --strict --options runtime --force -vvv UninstallLokinet.app - ) -else() - add_custom_command(TARGET ${PROJECT_NAME} - POST_BUILD - COMMAND echo "Running qt magic macos deploy script" - COMMAND "${qt_dir}/macdeployqt" UninstallLokinet.app -always-overwrite - ) -endif() - -install(TARGETS lokinet-uninstaller - RUNTIME DESTINATION bin - BUNDLE DESTINATION . - RESOURCE DESTINATION .) diff --git a/contrib/macos/uninstaller/icon.svg b/contrib/macos/uninstaller/icon.svg deleted file mode 100644 index 096d925a4..000000000 --- a/contrib/macos/uninstaller/icon.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - image/svg+xml - - lokinet icon - - - - - - - lokinet icon - - - - - - - - - - - diff --git a/contrib/macos/uninstaller/main.cpp b/contrib/macos/uninstaller/main.cpp deleted file mode 100644 index a58b116c8..000000000 --- a/contrib/macos/uninstaller/main.cpp +++ /dev/null @@ -1,45 +0,0 @@ - -#include -#include -#include -#include - -int uninstall(); - -int main(int argc, char * argv[]) -{ - QApplication app{argc, argv}; - if(QMessageBox::question(nullptr, "Lokinet Uninstaller", "Do You want to uninstall Lokinet?", - QMessageBox::Yes|QMessageBox::No) - == QMessageBox::Yes) - { - QMessageBox msgBox; - const auto retcode = uninstall(); - if(retcode == 0) - { - msgBox.setText("Lokinet has been successfully uninstalled, you may now remove the uninstaller if you wish."); - } - else - { - msgBox.setText("Failed to uninstall lokinet"); - } - msgBox.exec(); - } - return 0; -} - -int uninstall() -{ - AuthorizationRef authorizationRef; - OSStatus status; - - status = AuthorizationCreate(nullptr, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef); - if(status != 0) - return status; - char* tool = "/bin/sh"; - char* args[] = {"/opt/lokinet/bin/lokinet_uninstall.sh", nullptr}; - FILE* pipe = stdout; - - return AuthorizationExecuteWithPrivileges(authorizationRef, tool, kAuthorizationFlagDefaults, args, &pipe); -} - diff --git a/contrib/make-ico.sh b/contrib/make-ico.sh new file mode 100755 index 000000000..cdc74183d --- /dev/null +++ b/contrib/make-ico.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Invoked from cmake as make-ico.sh /path/to/icon.svg /path/to/output.ico +svg="$1" +out="$2" +outdir="$out.d" + +set -e + +sizes=(16 24 32 40 48 64 96 192 256) +outs="" + +mkdir -p "${outdir}" +for size in "${sizes[@]}"; do + outf="${outdir}/${size}x${size}.png" + if [ $size -lt 32 ]; then + # For 16x16 and 24x24 we crop the image to 2/3 of its regular size make it all white + # (instead of transparent) to zoom in on it a bit because if we resize the full icon to the + # target size it ends up a fuzzy mess, while the crop and resize lets us retain some detail + # of the logo. + rsvg-convert -b white \ + --page-height $size --page-width $size \ + -w $(($size*3/2)) -h $(($size*3/2)) --left " -$(($size/4))" --top " -$(($size/4))" \ + "$svg" >"$outf" + else + rsvg-convert -b transparent -w $size -h $size "$svg" >"$outf" + fi + outs="-r $outf $outs" +done + +icotool -c -b 32 -o "$out" $outs diff --git a/contrib/munin/lokinet-munin.py b/contrib/munin/lokinet-munin.py deleted file mode 100644 index f9c067319..000000000 --- a/contrib/munin/lokinet-munin.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 -# -# requires python3-requests -# -import requests -import json -import os -import sys - -from collections import defaultdict as Dict - -from requests.exceptions import RequestException - - -def jsonrpc(method, **args): - return requests.post('http://127.0.0.1:1190/', data=json.dumps( - {'method': method, 'params': args, 'id': 'munin'}), headers={'content-type': 'application/json'}).json() - - -def exit_sessions_main(): - if len(sys.argv) == 2 and sys.argv[1] == 'config': - print("graph_title lokinet exit sessions") - print("graph_vlabel sessions") - print("graph_category network") - print("graph_info This graph shows the number of exit sessions on a lokinet exit") - print("_exit_sessions.info Number of exit sessions") - print("_exit_sessions.label sessions") - else: - count = 0 - try: - j = jsonrpc("llarp.admin.exit.list") - count = len(j['result']) - except RequestException: - pass - print("_exit_sessions.value {}".format(count)) - - -def peers_main(): - if len(sys.argv) == 2 and sys.argv[1] == 'config': - print("graph_title lokinet peers") - print("graph_vlabel peers") - print("graph_category network") - print("graph_info This graph shows the number of node to node sessions of this lokinet router") - print("_peers_outbound.info Number of outbound lokinet peers") - print("_peers_inbound.info Number of inbound lokinet peers") - print("_peers_outbound.label outbound peers") - print("_peers_inbound.label inbound peers") - print("_peers_clients.info Number of lokinet client peers") - print("_peers_clients.label lokinet client peers") - else: - inbound = Dict(int) - outbound = Dict(int) - clients = Dict(int) - try: - j = jsonrpc("llarp.admin.link.neighboors") - for peer in j['result']: - if peer["svcnode"]: - if peer["outbound"]: - outbound[peer['ident']] += 1 - else: - inbound[peer['ident']] += 1 - else: - clients[peer['ident']] += 1 - except RequestException: - pass - - print("_peers_outbound.value {}".format(len(outbound))) - print("_peers_inbound.value {}".format(len(inbound))) - print("_peers_clients.value {}".format(len(clients))) - -if __name__ == '__main__': - exe = os.path.basename(sys.argv[0]).lower() - if exe == 'lokinet_peers': - peers_main() - elif exe == 'lokinet_exit': - exit_sessions_main() - else: - print( - 'please symlink this as `lokinet_peers` or `lokinet_exit` in munin plugins dir') diff --git a/contrib/patches/libzmq-mingw-closesocket.patch b/contrib/patches/libzmq-mingw-closesocket.patch deleted file mode 100644 index 1971d6bc9..000000000 --- a/contrib/patches/libzmq-mingw-closesocket.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/tests/testutil.hpp b/tests/testutil.hpp -index c6f5e4de..6a1c8bb8 100644 ---- a/tests/testutil.hpp -+++ b/tests/testutil.hpp -@@ -102,7 +102,6 @@ const uint8_t zmtp_ready_sub[27] = { - #include - #include - #include --#define close closesocket - typedef int socket_size_t; - inline const char *as_setsockopt_opt_t (const void *opt) - { diff --git a/contrib/patches/libzmq-mingw-unistd.patch b/contrib/patches/libzmq-mingw-unistd.patch new file mode 100644 index 000000000..e1528cf96 --- /dev/null +++ b/contrib/patches/libzmq-mingw-unistd.patch @@ -0,0 +1,14 @@ +diff --git a/tests/testutil.hpp b/tests/testutil.hpp +index c6f5e4de78..09b9fa77e5 100644 +--- a/tests/testutil.hpp ++++ b/tests/testutil.hpp +@@ -41,6 +41,9 @@ + // For AF_INET and IPPROTO_TCP + #if defined _WIN32 + #include "../src/windows.hpp" ++#if defined(__MINGW32__) ++#include ++#endif + #else + #include + #include diff --git a/contrib/patches/unbound-delete-crash-fix.patch b/contrib/patches/unbound-delete-crash-fix.patch new file mode 100644 index 000000000..d80799d5f --- /dev/null +++ b/contrib/patches/unbound-delete-crash-fix.patch @@ -0,0 +1,33 @@ +commit 56d816014d5e8a7eb055169c7e13a303dad5e50f +Author: Jason Rhinelander +Date: Mon Oct 31 22:07:03 2022 -0300 + + Set tube->ev_listen to NULL to prevent double unregister + + On windows when using threaded mode (i.e. `ub_ctx_async(ctx, 1)`) + tube_remove_bg_listen gets called twice: once when the thread does its + own cleanup, then again in `tube_delete()`. Because `ev_listen` doesn't + get cleared, however, we end we calling ub_winsock_unregister_wsaevent + with a freed pointer. + + This doesn't always manifest because, apparently, for various compilers + and settings that memory *might* be overwritten in which case the + additional check for ev->magic will prevent anything actually happening, + but in my case under mingw32 that doesn't happen and we end up + eventually crashing. + + This fixes the crash by properly NULLing the pointer so that the second + ub_winsock_unregister_wsaevent(...) becomes a no-op. + +diff --git a/util/tube.c b/util/tube.c +index 43455fee..a92dfa77 100644 +--- a/util/tube.c ++++ b/util/tube.c +@@ -570,6 +570,7 @@ void tube_remove_bg_listen(struct tube* tube) + { + verbose(VERB_ALGO, "tube remove_bg_listen"); + ub_winsock_unregister_wsaevent(tube->ev_listen); ++ tube->ev_listen = NULL; + } + + void tube_remove_bg_write(struct tube* tube) diff --git a/contrib/py/pylokinet/pylokinet/__init__.py b/contrib/py/pylokinet/pylokinet/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/contrib/py/pylokinet/pylokinet/__main__.py b/contrib/py/pylokinet/pylokinet/__main__.py deleted file mode 100644 index 710fd7900..000000000 --- a/contrib/py/pylokinet/pylokinet/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python3 - -from pylokinet.instance import main -main() \ No newline at end of file diff --git a/contrib/py/pylokinet/pylokinet/bencode.py b/contrib/py/pylokinet/pylokinet/bencode.py deleted file mode 100644 index 2f6963209..000000000 --- a/contrib/py/pylokinet/pylokinet/bencode.py +++ /dev/null @@ -1,111 +0,0 @@ -# -# super freaking dead simple wicked awesome bencode library -# -from io import BytesIO - -class BCodec: - encoding = 'utf-8' - def __init__(self, fd): - self._fd = fd - - def _write_bytestring(self, bs): - self._fd.write('{}:'.format(len(bs)).encode('ascii')) - self._fd.write(bs) - - def _write_list(self, l): - self._fd.write(b'l') - for item in l: - self.encode(item) - self._fd.write(b'e') - - def _write_dict(self, d): - self._fd.write(b'd') - keys = list(d.keys()) - keys.sort() - for k in keys: - if isinstance(k, str): - self._write_bytestring(k.encode(self.encoding)) - elif isinstance(k, bytes): - self._write_bytestring(k) - else: - self._write_bytestring('{}'.format(k).encode(self.encoding)) - self.encode(d[k]) - self._fd.write(b'e') - - def _write_int(self, i): - self._fd.write('i{}e'.format(i).encode(self.encoding)) - - def encode(self, obj): - if isinstance(obj, dict): - self._write_dict(obj) - elif isinstance(obj, list): - self._write_list(obj) - elif isinstance(obj, int): - self._write_int(obj) - elif isinstance(obj, str): - self._write_bytestring(obj.encode(self.encoding)) - elif isinstance(obj, bytes): - self._write_bytestring(obj) - elif hasattr(obj, bencode): - obj.bencode(self._fd) - else: - raise ValueError("invalid object type") - - def _readuntil(self, delim): - b = bytes() - while True: - ch = self._fd.read(1) - if ch == delim: - return b - b += ch - - def _decode_list(self): - l = list() - while True: - b = self._fd.read(1) - if b == b'e': - return l - l.append(self._decode(b)) - - def _decode_dict(self): - d = dict() - while True: - ch = self._fd.read(1) - if ch == b'e': - return d - k = self._decode_bytestring(ch) - d[k] = self.decode() - - def _decode_int(self): - return int(self._readuntil(b'e'), 10) - - def _decode_bytestring(self, ch): - ch += self._readuntil(b':') - l = int(ch, base=10) - return self._fd.read(l) - - def _decode(self, ch): - if ch == b'd': - return self._decode_dict() - elif ch == b'l': - return self._decode_list() - elif ch == b'i': - return self._decode_int() - elif ch in [b'0',b'1',b'2',b'3',b'4',b'5',b'6',b'7',b'8',b'9']: - return self._decode_bytestring(ch) - else: - raise ValueError(ch) - - def decode(self): - return self._decode(self._fd.read(1)) - - -def bencode(obj): - buf = BytesIO() - b = BCodec(buf) - b.encode(obj) - return buf.getvalue() - -def bdecode(bytestring): - buf = BytesIO(bytestring) - return BCodec(buf).decode() \ No newline at end of file diff --git a/contrib/py/pylokinet/pylokinet/bootserv.py b/contrib/py/pylokinet/pylokinet/bootserv.py deleted file mode 100644 index a196c507b..000000000 --- a/contrib/py/pylokinet/pylokinet/bootserv.py +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/env python3 -# -# python wsgi application for managing many lokinet instances -# - -__doc__ = """lokinet bootserv wsgi app -also handles webhooks for CI -run me with via gunicorn pylokinet.bootserv:app -""" - -import os - -from pylokinet import rc -import json - -import random -import time -from datetime import datetime -from email.utils import parsedate, format_datetime -from dateutil.parser import parse as date_parse -import requests - - -root = './lokinet' - -def _compare_dates(left, right): - """ - return true if left timestamp is bigger than right - """ - return date_parse(left) > date_parse(right) - -class TokenHolder: - - _dir = root - _token = None - - def __init__(self, f="token"): - if not os.path.exists(self._dir): - os.mkdir(self._dir, 0o700) - f = os.path.join(self._dir, f) - if os.path.exists(f): - with open(f) as fd: - self._token = fd.read().replace("\n", "") - - def verify(self, token): - """ - return true if token matches - """ - if self._token is None: - return False - return self._token == token - -class BinHolder: - """ - serves a binary file in a dir - """ - _dir = os.path.join(root, 'bin') - - def __init__(self, f): - if not os.path.exists(self._dir): - os.mkdir(self._dir, 0o700) - self._fpath = os.path.join(self._dir, f) - - def put(self, r): - """ - put a new file into the place that is held - """ - with open(self._fpath, "wb") as f: - for chunk in r.iter_content(chunk_size=1024): - f.write(chunk) - - - def is_new(self, date): - """ - return true if last modified timestamp is fresher than current - """ - t = date_parse('{}'.format(date)) - if not t: - return False - if os.path.exists(self._fpath): - st = os.stat(self._fpath) - return st.st_mtime < t.timestamp() - return True - - - def serve(self, last_modified, respond): - """ - serve file with caching - """ - t = parsedate(last_modified) - if t: - t = time.mktime(t) - if t is None: - t = 0 - if not os.path.exists(self._fpath): - respond("404 Not Found", []) - return [] - st = os.stat(self._fpath) - if st.st_mtime < t: - respond("304 Not Modified", [("Last-Modified", format_datetime(st.st_mtime)) ]) - return [] - with open(self._fpath, "rb") as f: - data = f.read() - respond("200 OK", [("Content-Type", "application/octect-stream"), - ("Last-Modified", format_datetime(datetime.fromtimestamp(int(st.st_mtime)))),("Content-Length", "{}".format(st.st_size))]) - return [data] - - -class RCHolder: - - _dir = os.path.join(root, 'nodedb') - - _rc_files = list() - - def __init__(self): - if os.path.exists(self._dir): - for root, _, files in os.walk(self._dir): - for f in files: - self._add_rc(os.path.join(root, f)) - else: - os.mkdir(self._dir, 0o700) - - def prune(self): - """ - remove invalid entries - """ - delfiles = [] - for p in self._rc_files: - with open(p, 'rb') as f: - if not rc.validate(f.read()): - delfiles.append(p) - for f in delfiles: - os.remove(f) - - def validate_then_put(self, body): - if not rc.validate(body): - return False - k = rc.get_pubkey(body) - print(k) - if k is None: - return False - with open(os.path.join(self._dir, k), "wb") as f: - f.write(body) - return True - - def _add_rc(self, fpath): - self._rc_files.append(fpath) - - def serve_random(self): - with open(random.choice(self._rc_files), 'rb') as f: - return f.read() - - def empty(self): - return len(self._rc_files) == 0 - - -def handle_rc_upload(body, respond): - holder = RCHolder() - if holder.validate_then_put(body): - respond("200 OK", [("Content-Type", "text/plain")]) - return ["rc accepted".encode('ascii')] - else: - respond("400 Bad Request", [("Content-Type", "text/plain")]) - return ["bad rc".encode('ascii')] - - -def serve_random_rc(): - holder = RCHolder() - if holder.empty(): - return None - else: - return holder.serve_random() - -def response(status, msg, respond): - respond(status, [("Content-Type", "text/plain"), ("Content-Length", "{}".format(len(msg)))]) - return [msg.encode("utf-8")] - -def handle_serve_lokinet(modified_since, respond): - l = BinHolder('lokinet.zip') - return l.serve(modified_since, respond) - - -def fetch_lokinet(j, ref="staging", name="build:linux"): - holder = BinHolder("lokinet.zip") - if 'builds' not in j: - return False - selected = None - attrs = dict() - if 'object_attributes' in j: - attrs = j['object_attributes'] - if 'ref' not in attrs or attrs["ref"] != ref: - return True - - for build in j['builds']: - if 'name' not in build or build['name'] != name: - continue - if 'status' not in build or build['status'] != 'success': - continue - if 'finished_at' not in build or build['finished_at'] is None: - continue - if holder.is_new(build['finished_at']): - if selected is None or _compare_dates(build["finished_at"], selected["finished_at"]): - selected = build - if selected and 'id' in selected: - url = 'https://gitlab.com/lokiproject/loki-network/-/jobs/{}/artifacts/download'.format(selected['id']) - r = requests.get(url) - if r.status_code == 200: - holder.put(r) - return True - - #if 'artifacts_file' not in selected: - # return False - #f = selected["artifacts_file"] - #return True - -def handle_webhook(j, token, event, respond): - """ - handle CI webhook - """ - t = TokenHolder() - if not t.verify(token): - respond("403 Forbidden", []) - return [] - event = event.lower() - if event == 'pipeline hook': - if fetch_lokinet(j): - respond("200 OK", []) - return [] - else: - respond("500 Internal Server Error", []) - return [] - else: - respond("404 Not Found", []) - return [] - - -def app(environ, start_response): - request_body_size = int(environ.get("CONTENT_LENGTH", 0)) - method = environ.get("REQUEST_METHOD") - if method.upper() == "PUT" and request_body_size > 0: - rcbody = environ.get("wsgi.input").read(request_body_size) - return handle_rc_upload(rcbody, start_response) - elif method.upper() == "POST": - if environ.get("PATH_INFO") == "/": - j = json.loads(environ.get("wsgi.input").read(request_body_size)) - token = environ.get("HTTP_X_GITLAB_TOKEN") - return handle_webhook(j, token, environ.get("HTTP_X_GITLAB_EVENT"), start_response) - else: - return response("404 Not Found", 'bad url', start_response) - elif method.upper() == "GET": - if environ.get("PATH_INFO") == "/bootstrap.signed": - resp = serve_random_rc() - if resp is not None: - start_response('200 OK', [("Content-Type", "application/octet-stream")]) - return [resp] - else: - return response('404 Not Found', 'no RCs', start_response) - elif environ.get("PATH_INFO") == "/ping": - return response('200 OK', 'pong', start_response) - elif environ.get("PATH_INFO") == "/lokinet.zip": - return handle_serve_lokinet(environ.get("HTTP_IF_MODIFIED_SINCE"),start_response) - elif environ.get("PATH_INFO") == "/": - return response("200 OK", "lokinet bootserv", start_response) - else: - return response('404 Not Found', 'Not found', start_response) - else: - return response('405 Method Not Allowed', 'method not allowed', start_response) - - -def main(): - """ - run as cron job - """ - h = RCHolder() - h.prune() - -if __name__ == '__main__': - main() diff --git a/contrib/py/pylokinet/pylokinet/instance.py b/contrib/py/pylokinet/pylokinet/instance.py deleted file mode 100644 index 4639ffec8..000000000 --- a/contrib/py/pylokinet/pylokinet/instance.py +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env python3 -# -# lokinet runtime wrapper -# - -from ctypes import * -import configparser -import signal -import time -import threading -import os -import sys -import requests - -from pylokinet import rc - -lib_file = os.path.join(os.path.realpath('.'), 'liblokinet-shared.so') - - -def log(msg): - sys.stderr.write("lokinet: {}\n".format(msg)) - sys.stderr.flush() - - -class LokiNET(threading.Thread): - - lib = None - ctx = 0 - failed = False - up = False - - asRouter = True - - def configure(self, lib, conf, ip=None, port=None, ifname=None, seedfile=None, lokid_host=None, lokid_port=None): - log("configure lib={} conf={}".format(lib, conf)) - if not os.path.exists(os.path.dirname(conf)): - os.mkdir(os.path.dirname(conf)) - try: - self.lib = CDLL(lib) - except OSError as ex: - log("failed to load library: {}".format(ex)) - return False - if self.lib.llarp_ensure_config(conf.encode('utf-8'), os.path.dirname(conf).encode('utf-8'), True, self.asRouter): - config = configparser.ConfigParser() - config.read(conf) - log('overwrite ip="{}" port="{}" ifname="{}" seedfile="{}" lokid=("{}", "{}")'.format( - ip, port, ifname, seedfile, lokid_host, lokid_port)) - if seedfile and lokid_host and lokid_port: - if not os.path.exists(seedfile): - log('cannot access service node seed at "{}"'.format(seedfile)) - return False - config['lokid'] = { - 'service-node-seed': seedfile, - 'enabled': "true", - 'jsonrpc': "{}:{}".format(lokid_host, lokid_port) - } - if ip: - config['router']['public-address'] = '{}'.format(ip) - if port: - config['router']['public-port'] = '{}'.format(port) - if ifname and port: - config['bind'] = { - ifname: '{}'.format(port) - } - with open(conf, "w") as f: - config.write(f) - self.ctx = self.lib.llarp_main_init(conf.encode('utf-8')) - else: - return False - return self.lib.llarp_main_setup(self.ctx, False) == 0 - - def inform_fail(self): - """ - inform lokinet crashed - """ - self.failed = True - self._inform() - - def inform_up(self): - self.up = True - self._inform() - - def _inform(self): - """ - inform waiter - """ - - def wait_for_up(self, timeout): - """ - wait for lokinet to go up for :timeout: seconds - :return True if we are up and running otherwise False: - """ - # return self._up.wait(timeout) - - def signal(self, sig): - if self.ctx and self.lib: - self.lib.llarp_main_signal(self.ctx, int(sig)) - - def run(self): - # self._up.acquire() - self.up = True - code = self.lib.llarp_main_run(self.ctx) - log("llarp_main_run exited with status {}".format(code)) - if code: - self.inform_fail() - self.up = False - # self._up.release() - - def close(self): - if self.lib and self.ctx: - self.lib.llarp_main_free(self.ctx) - - -def getconf(name, fallback=None): - return name in os.environ and os.environ[name] or fallback - - -def run_main(args): - seedfile = getconf("LOKI_SEED_FILE") - if seedfile is None: - print("LOKI_SEED_FILE was not set") - return - - lokid_host = getconf("LOKI_RPC_HOST", "127.0.0.1") - lokid_port = getconf("LOKI_RPC_PORT", "22023") - - root = getconf("LOKINET_ROOT") - if root is None: - print("LOKINET_ROOT was not set") - return - - rc_callback = getconf("LOKINET_SUBMIT_URL") - if rc_callback is None: - print("LOKINET_SUBMIT_URL was not set") - return - - bootstrap = getconf("LOKINET_BOOTSTRAP_URL") - if bootstrap is None: - print("LOKINET_BOOTSTRAP_URL was not set") - - lib = getconf("LOKINET_LIB", lib_file) - if not os.path.exists(lib): - lib = "liblokinet-shared.so" - timeout = int(getconf("LOKINET_TIMEOUT", "5")) - ping_interval = int(getconf("LOKINET_PING_INTERVAL", "60")) - ping_callback = getconf("LOKINET_PING_URL") - ip = getconf("LOKINET_IP") - port = getconf("LOKINET_PORT") - ifname = getconf("LOKINET_IFNAME") - if ping_callback is None: - print("LOKINET_PING_URL was not set") - return - conf = os.path.join(root, "daemon.ini") - log("going up") - loki = LokiNET() - log("bootstrapping...") - try: - r = requests.get(bootstrap) - if r.status_code == 404: - log("bootstrap gave no RCs, we are probably the seed node") - elif r.status_code != 200: - raise Exception("http {}".format(r.status_code)) - else: - data = r.content - if rc.validate(data): - log("valid RC obtained") - with open(os.path.join(root, "bootstrap.signed"), "wb") as f: - f.write(data) - else: - raise Exception("invalid RC") - except Exception as ex: - log("failed to bootstrap: {}".format(ex)) - loki.close() - return - if loki.configure(lib, conf, ip, port, ifname, seedfile, lokid_host, lokid_port): - log("configured") - - loki.start() - try: - log("waiting for spawn") - while timeout > 0: - time.sleep(1) - if loki.failed: - log("failed") - break - log("waiting {}".format(timeout)) - timeout -= 1 - if loki.up: - log("submitting rc") - try: - with open(os.path.join(root, 'self.signed'), 'rb') as f: - r = requests.put(rc_callback, data=f.read(), headers={ - "content-type": "application/octect-stream"}) - log('submit rc reply: HTTP {}'.format(r.status_code)) - except Exception as ex: - log("failed to submit rc: {}".format(ex)) - loki.signal(signal.SIGINT) - time.sleep(2) - else: - while loki.up: - time.sleep(ping_interval) - try: - r = requests.get(ping_callback) - log("ping reply: HTTP {}".format(r.status_code)) - except Exception as ex: - log("failed to submit ping: {}".format(ex)) - else: - log("failed to go up") - loki.signal(signal.SIGINT) - except KeyboardInterrupt: - loki.signal(signal.SIGINT) - time.sleep(2) - finally: - loki.close() - else: - loki.close() - - -def main(): - run_main(sys.argv[1:]) - - -if __name__ == "__main__": - main() diff --git a/contrib/py/pylokinet/pylokinet/rc.py b/contrib/py/pylokinet/pylokinet/rc.py deleted file mode 100644 index 8a8a9347c..000000000 --- a/contrib/py/pylokinet/pylokinet/rc.py +++ /dev/null @@ -1,31 +0,0 @@ -from pylokinet import bencode -import pysodium -import binascii -import time - -def _expired(ts, lifetime=84600000): - """ - return True if a timestamp is considered expired - lifetime is default 23.5 hours - """ - return (int(time.time()) * 1000) - ts >= lifetime - -def validate(data): - rc = bencode.bdecode(data) - if b'z' not in rc or b'k' not in rc: - return False - sig = rc[b'z'] - rc[b'z'] = b'\x00' * 64 - buf = bencode.bencode(rc) - try: - k = rc[b'k'] - pysodium.crypto_sign_verify_detached(sig, buf, k) - except: - return False - else: - return not _expired(rc[b't']) - -def get_pubkey(data): - rc = bencode.bdecode(data) - if b'k' in rc: - return binascii.hexlify(rc[b'k']).decode('ascii') \ No newline at end of file diff --git a/contrib/py/pylokinet/readme.md b/contrib/py/pylokinet/readme.md deleted file mode 100644 index 45b56103e..000000000 --- a/contrib/py/pylokinet/readme.md +++ /dev/null @@ -1,27 +0,0 @@ -# pylokinet - -lokinet with python 3 - - # python3 setup.py install - -## bootserv - -bootserv is a bootstrap server for accepting and serving RCs - - $ gunicorn -b 0.0.0.0:8000 pylokinet.bootserv:app - -## pylokinet instance - -obtain `liblokinet-shared.so` from a lokinet build - -run (root): - - # export LOKINET_ROOT=/tmp/lokinet-instance/ - # export LOKINET_LIB=/path/to/liblokinet-shared.so - # export LOKINET_BOOTSTRAP_URL=http://bootserv.ip.address.here:8000/bootstrap.signed - # export LOKINET_PING_URL=http://bootserv.ip.address.here:8000/ping - # export LOKINET_SUBMIT_URL=http://bootserv.ip.address.here:8000/ - # export LOKINET_IP=public.ip.goes.here - # export LOKINET_PORT=1090 - # export LOKINET_IFNAME=eth0 - # python3 -m pylokinet \ No newline at end of file diff --git a/contrib/py/pylokinet/setup.py b/contrib/py/pylokinet/setup.py deleted file mode 100644 index f20b3a8f4..000000000 --- a/contrib/py/pylokinet/setup.py +++ /dev/null @@ -1,14 +0,0 @@ -from setuptools import setup, find_packages - - - -setup( - name="pylokinet", - version="0.0.1", - license="ZLIB", - author="jeff", - author_email="jeff@i2p.rocks", - description="lokinet python bindings", - url="https://github.com/loki-project/loki-network", - install_requires=["pysodium", "requests", "python-dateutil"], - packages=find_packages()) \ No newline at end of file diff --git a/contrib/py/vanity/.gitignore b/contrib/py/vanity/.gitignore deleted file mode 100644 index 8e5737fb0..000000000 --- a/contrib/py/vanity/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -__pycache__ -*.private \ No newline at end of file diff --git a/contrib/py/vanity/bencode.py b/contrib/py/vanity/bencode.py deleted file mode 100644 index 5c131fc9b..000000000 --- a/contrib/py/vanity/bencode.py +++ /dev/null @@ -1,112 +0,0 @@ -# -# super freaking dead simple wicked awesome bencode library -# -from io import BytesIO - -class BCodec: - encoding = 'utf-8' - def __init__(self, fd): - self._fd = fd - - def _write_bytestring(self, bs): - self._fd.write('{}:'.format(len(bs)).encode('ascii')) - self._fd.write(bs) - - def _write_list(self, l): - self._fd.write(b'l') - for item in l: - self.encode(item) - self._fd.write(b'e') - - def _write_dict(self, d): - self._fd.write(b'd') - keys = list(d.keys()) - keys.sort() - for k in keys: - if isinstance(k, str): - self._write_bytestring(k.encode(self.encoding)) - elif isinstance(k, bytes): - self._write_bytestring(k) - else: - self._write_bytestring('{}'.format(k).encode(self.encoding)) - self.encode(d[k]) - self._fd.write(b'e') - - def _write_int(self, i): - self._fd.write('i{}e'.format(i).encode(self.encoding)) - - def encode(self, obj): - if isinstance(obj, dict): - self._write_dict(obj) - elif isinstance(obj, list): - self._write_list(obj) - elif isinstance(obj, int): - self._write_int(obj) - elif isinstance(obj, str): - self._write_bytestring(obj.encode(self.encoding)) - elif isinstance(obj, bytes): - self._write_bytestring(obj) - elif hasattr(obj, bencode): - obj.bencode(self._fd) - else: - raise ValueError("invalid object type") - - def _readuntil(self, delim): - b = bytes() - while True: - ch = self._fd.read(1) - if ch == delim: - return b - b += ch - - def _decode_list(self): - l = list() - while True: - b = self._fd.read(1) - if b == b'e': - return l - l.append(self._decode(b)) - - def _decode_dict(self): - d = dict() - while True: - ch = self._fd.read(1) - if ch == b'e': - return d - k = self._decode_bytestring(ch) - d[k] = self.decode() - - def _decode_int(self): - return int(self._readuntil(b'e'), 10) - - def _decode_bytestring(self, ch): - ch += self._readuntil(b':') - l = int(ch, base=10) - return self._fd.read(l) - - def _decode(self, ch): - if ch == b'd': - return self._decode_dict() - elif ch == b'l': - return self._decode_list() - elif ch == b'i': - return self._decode_int() - elif ch in [b'0',b'1',b'2',b'3',b'4',b'5',b'6',b'7',b'8',b'9']: - return self._decode_bytestring(ch) - else: - raise ValueError(ch) - - def decode(self): - return self._decode(self._fd.read(1)) - - -def bencode(obj): - buf = BytesIO() - b = BCodec(buf) - b.encode(obj) - return buf.bytes() - -def bdecode(bytestring): - buf = BytesIO() - buf.write(bytestring) - return BCodec(buf).decode() \ No newline at end of file diff --git a/contrib/py/vanity/lokinet-vanity.py b/contrib/py/vanity/lokinet-vanity.py deleted file mode 100644 index 5fc1119dd..000000000 --- a/contrib/py/vanity/lokinet-vanity.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python3 -import bencode -import sys -import libnacl -import struct -from io import BytesIO -import time -from multiprocessing import Process, Array, Value - - -def print_help(): - print('usage: {} keyfile.private prefix numthreads'.format(sys.argv[0])) - return 1 - - -_zalpha = ['y', 'b', 'n', 'd', 'r', 'f', 'g', '8', - 'e', 'j', 'k', 'm', 'c', 'p', 'q', 'x', - 'o', 't', '1', 'u', 'w', 'i', 's', 'z', - 'a', '3', '4', '5', 'h', '7', '6', '9'] - - -def zb32_encode(buf): - s = str() - bits = 0 - l = len(buf) - idx = 0 - tmp = buf[idx] - while bits > 0 or idx < l: - if bits < 5: - if idx < l: - tmp <<= 8 - tmp |= buf[idx] & 0xff - idx += 1 - bits += 8 - else: - tmp <<= 5 - bits - bits = 5 - bits -= 5 - s += _zalpha[(tmp >> bits) & 0x1f] - return s - - -def _gen_si(keys): - e = keys[b'e'][32:] - s = keys[b's'][32:] - v = keys[b'v'] - return {'e': e, 's': s, 'v': v} - - -class AddrGen: - - def __init__(self, threads, keys, prefix): - self._inc = threads - self._keys = keys - self._c = Value('i') - self.sync = Array('i', 3) - self._procs = [] - self.prefix = prefix - - def runit(self): - for ch in self.prefix: - if ch not in _zalpha: - print("invalid prefix, {} not a valid character".format(ch)) - return None, None - print("find ^{}.loki".format(self.prefix)) - i = self._inc - while i > 0: - p = Process(target=self._gen_addr_tick, args=(self.prefix, abs( - libnacl.randombytes_random()), abs(libnacl.randombytes_random()), _gen_si(self._keys))) - p.start() - self._procs.append(p) - i -= 1 - return self._runner() - - def _gen_addr_tick(self, prefix, lo, hi, si): - print(prefix) - fd = BytesIO() - addr = '' - enc = bencode.BCodec(fd) - while self.sync[2] == 0: - si['x'] = struct.pack('>QQ', lo, hi) - fd.seek(0, 0) - enc.encode(si) - pub = bytes(fd.getbuffer()) - addr = zb32_encode(libnacl.crypto_generichash(pub)) - if addr.startswith(prefix): - self.sync[2] = 1 - self.sync[0] = hi - self.sync[1] = lo - return - hi += self._inc - if hi == 0: - lo += 1 - self._c.value += 1 - - def _print_stats(self): - print('{} H/s'.format(self._c.value)) - self._c.value = 0 - - def _joinall(self): - for p in self._procs: - p.join() - - def _runner(self): - while self.sync[2] == 0: - time.sleep(1) - self._print_stats() - self._joinall() - fd = BytesIO() - enc = bencode.BCodec(fd) - hi = self.sync[0] - lo = self.sync[1] - si = _gen_si(self._keys) - si['x'] = struct.pack('>QQ', lo, hi) - enc.encode(si) - pub = bytes(fd.getbuffer()) - addr = zb32_encode(libnacl.crypto_generichash(pub)) - return si['x'], addr - - -def main(args): - if len(args) != 3: - return print_help() - keys = None - with open(args[0], 'rb') as fd: - dec = bencode.BCodec(fd) - keys = dec.decode() - runner = AddrGen(int(args[2]), keys, args[1]) - keys[b'x'], addr = runner.runit() - if addr: - print("found {}.loki".format(addr)) - with open(args[0], 'wb') as fd: - enc = bencode.BCodec(fd) - enc.encode(keys) - - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/contrib/py/vanity/readme.md b/contrib/py/vanity/readme.md deleted file mode 100644 index f8161176d..000000000 --- a/contrib/py/vanity/readme.md +++ /dev/null @@ -1,10 +0,0 @@ -# lokinet vanity address generator - -installing deps: - - sudo apt install libsodium-dev - pip3 install --user -r requirements.txt - -to generate a nonce with a prefix `^7oki` using 8 cpu threads: - - python3 lokinet-vanity.py keyfile.private 7oki 8 diff --git a/contrib/py/vanity/requirements.txt b/contrib/py/vanity/requirements.txt deleted file mode 100644 index 15d991e7b..000000000 --- a/contrib/py/vanity/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -libnacl \ No newline at end of file diff --git a/contrib/shadow/genconf.py b/contrib/shadow/genconf.py deleted file mode 100644 index 0a4c0794a..000000000 --- a/contrib/shadow/genconf.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env python3 - -import configparser -import sys -import os - -from xml.etree import ElementTree as etree - - -def getSetting(s, name, fallback): return name in s and s[name] or fallback - - -shadowRoot = getSetting(os.environ, "SHADOW_ROOT", - os.path.join(os.environ['HOME'], '.shadow')) - -libpath = 'libshadow-plugin-lokinet.so' - - -def nodeconf(conf, baseDir, name, ifname=None, port=None): - conf['netdb'] = {'dir': 'tmp-nodes'} - conf['router'] = {} - conf['router']['contact-file'] = os.path.join( - baseDir, '{}.signed'.format(name)) - conf['router']['ident-privkey'] = os.path.join( - baseDir, '{}-ident.key'.format(name)) - conf['router']['transport-privkey'] = os.path.join( - baseDir, '{}-transport.key'.format(name)) - if ifname and port: - conf['bind'] = {ifname: port} - conf['connect'] = {} - - -def addPeer(conf, baseDir, peer): - conf['connect'][peer] = os.path.join(baseDir, '{}.signed'.format(peer)) - - -def createNode(pluginName, root, peer, life=600): - node = etree.SubElement(root, 'node') - node.attrib['id'] = peer['name'] - node.attrib['interfacebuffer'] = '{}'.format(1024 * 1024 * 100) - app = etree.SubElement(node, 'process') - app.attrib['plugin'] = pluginName - app.attrib['time'] = '{}'.format(life) - app.attrib['arguments'] = peer['configfile'] - - -def makeBase(settings, name, id): - return { - 'id': id, - 'name': name, - 'contact-file': os.path.join(getSetting(settings, 'baseDir', 'tmp'), '{}.signed'.format(name)), - 'configfile': os.path.join(getSetting(settings, 'baseDir', 'tmp'), '{}.ini'.format(name)), - 'config': configparser.ConfigParser() - } - - -def makeClient(settings, name, id): - peer = makeBase(settings, name, id) - basedir = getSetting(settings, 'baseDir', 'tmp') - nodeconf(peer['config'], basedir, name) - peer['config']['network'] = { - 'type': 'null', - 'tag': 'test', - 'prefetch-tag': 'test' - } - return peer - - -def makeSVCNode(settings, name, id, port): - peer = makeBase(settings, name, id) - nodeconf(peer['config'], getSetting( - settings, 'baseDir', 'tmp'), name, 'eth0', port) - peer['config']['network'] = { - 'type': 'null' - } - return peer - - -def genconf(settings, outf): - root = etree.Element('shadow') - root.attrib["environment"] = 'LLARP_SHADOW=1' - topology = etree.SubElement(root, 'topology') - topology.attrib['path'] = getSetting(settings, 'topology', os.path.join( - shadowRoot, 'share', 'topology.graphml.xml')) - - pluginName = getSetting(settings, 'name', 'lokinet-shared') - - kill = etree.SubElement(root, 'kill') - kill.attrib['time'] = getSetting(settings, 'runFor', '600') - - baseDir = getSetting(settings, 'baseDir', - os.path.join('/tmp', 'lokinet-shadow')) - - if not os.path.exists(baseDir): - os.mkdir(baseDir) - - plugin = etree.SubElement(root, "plugin") - plugin.attrib['id'] = pluginName - plugin.attrib['path'] = libpath - basePort = getSetting(settings, 'svc-base-port', 19000) - svcNodeCount = getSetting(settings, 'service-nodes', 80) - peers = list() - for nodeid in range(svcNodeCount): - peers.append(makeSVCNode( - settings, 'svc-node-{}'.format(nodeid), str(nodeid), basePort + 1)) - basePort += 1 - - # make all service nodes know each other - for peer in peers: - for nodeid in range(svcNodeCount): - if str(nodeid) != peer['id']: - addPeer(peer['config'], baseDir, 'svc-node-{}'.format(nodeid)) - - # add client nodes - for nodeid in range(getSetting(settings, 'client-nodes', 200)): - peer = makeClient( - settings, 'client-node-{}'.format(nodeid), str(nodeid)) - peers.append(peer) - for p in range(getSetting(settings, 'client-connect-to', 10)): - addPeer(peer['config'], baseDir, - 'svc-node-{}'.format((p + nodeid) % svcNodeCount)) - - # generate xml and settings files - for peer in peers: - createNode(pluginName, root, peer) - - with open(peer['configfile'], 'w') as f: - peer['config'].write(f) - - # render - outf.write(etree.tostring(root).decode('utf-8')) - - -if __name__ == '__main__': - settings = { - 'baseDir': os.path.join("/tmp", "lokinet-shadow"), - 'topology': os.path.join(shadowRoot, 'share', 'topology.graphml.xml'), - 'runFor': '{}'.format(60 * 10 * 10) - } - with open(sys.argv[1], 'w') as f: - genconf(settings, f) diff --git a/contrib/tarball.sh b/contrib/tarball.sh index 60435b01d..54b62f1bd 100755 --- a/contrib/tarball.sh +++ b/contrib/tarball.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash # # create signed release tarball with submodules bundled +# usage: ./contrib/tarball.sh [keyid] # repo=$(readlink -e $(dirname $0)/..) branch=$(test -e $repo/.git/ && git rev-parse --abbrev-ref HEAD) out="lokinet-$(git describe --exact-match --tags $(git log -n1 --pretty='%h') 2> /dev/null || ( echo -n $branch- && git rev-parse --short HEAD)).tar.xz" -git-archive-all -C $repo --force-submodules $out && rm -f $out.sig && (gpg --sign --detach $out &> /dev/null && gpg --verify $out.sig) +git-archive-all -C $repo --force-submodules $out && rm -f $out.sig && (gpg -u ${1:-jeff@lokinet.io} --sign --detach $out &> /dev/null && gpg --verify $out.sig) diff --git a/contrib/testnet/.gitignore b/contrib/testnet/.gitignore deleted file mode 100644 index 6f53c74ca..000000000 --- a/contrib/testnet/.gitignore +++ /dev/null @@ -1 +0,0 @@ -v/ \ No newline at end of file diff --git a/contrib/testnet/genconf.py b/contrib/testnet/genconf.py deleted file mode 100755 index 49fa5c876..000000000 --- a/contrib/testnet/genconf.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env python -# -# this script generate supervisord configs for running a test network on loopback -# - - -from argparse import ArgumentParser as AP -from configparser import ConfigParser as CP - -import os - - -def svcNodeName(id): return 'svc-node-%03d' % id - - -def clientNodeName(id): return 'client-node-%03d' % id - - -def main(): - ap = AP() - ap.add_argument('--valgrind', type=bool, default=False) - ap.add_argument('--dir', type=str, default='testnet_tmp') - ap.add_argument('--svc', type=int, default=20, - help='number of service nodes') - ap.add_argument('--baseport', type=int, default=19000) - ap.add_argument('--clients', type=int, default=200, - help='number of client nodes') - ap.add_argument('--bin', type=str, required=True) - ap.add_argument('--out', type=str, required=True) - ap.add_argument('--connect', type=int, default=10) - ap.add_argument('--ip', type=str, default=None) - ap.add_argument('--ifname', type=str, default='lo') - ap.add_argument('--netid', type=str, default=None) - ap.add_argument('--loglevel', type=str, default='debug') - args = ap.parse_args() - - if args.valgrind: - exe = 'valgrind {}'.format(args.bin) - else: - exe = '{} -v'.format(args.bin) - basedir = os.path.abspath(args.dir) - - for nodeid in range(args.svc): - config = CP() - config['router'] = { - 'data-dir': '.', - 'net-threads': '1', - 'worker-threads': '4', - 'nickname': svcNodeName(nodeid), - 'min-connections': "{}".format(args.connect) - } - if args.netid: - config['router']['netid'] = args.netid - - if args.ip: - config['router']['public-ip'] = args.ip - config['router']['public-port'] = str(args.baseport + nodeid) - - config['bind'] = { - args.ifname: str(args.baseport + nodeid) - } - config["logging"] = { - "level": args.loglevel - } - config['netdb'] = { - 'dir': 'netdb' - } - config['network'] = { - 'type' : 'null', - 'save-profiles': 'false' - } - config['api'] = { - 'enabled': 'false' - } - config['lokid'] = { - 'enabled': 'false', - } - config["logging"] = { - "level": args.loglevel - } - d = os.path.join(args.dir, svcNodeName(nodeid)) - if not os.path.exists(d): - os.mkdir(d) - fp = os.path.join(d, 'daemon.ini') - with open(fp, 'w') as f: - config.write(f) - for n in [0]: - if nodeid is not 0: - f.write("[bootstrap]\nadd-node={}\n".format(os.path.join(basedir,svcNodeName(n), 'self.signed'))) - else: - f.write("[bootstrap]\nseed-node=true\n") - - for nodeid in range(args.clients): - config = CP() - - config['router'] = { - 'data-dir': '.', - 'net-threads': '1', - 'worker-threads': '2', - 'nickname': clientNodeName(nodeid) - } - if args.netid: - config['router']['netid'] = args.netid - - config["logging"] = { - "level": args.loglevel - } - - config['netdb'] = { - 'dir': 'netdb' - } - config['api'] = { - 'enabled': 'false' - } - config['network'] = { - 'type' : 'null' - } - d = os.path.join(args.dir, clientNodeName(nodeid)) - if not os.path.exists(d): - os.mkdir(d) - fp = os.path.join(d, 'client.ini') - with open(fp, 'w') as f: - config.write(f) - for n in [0]: - otherID = (n + nodeid) % args.svc - f.write("[bootstrap]\nadd-node={}\n".format(os.path.join(basedir,svcNodeName(otherID), 'self.signed'))) - - with open(args.out, 'w') as f: - basedir = os.path.join(args.dir, 'svc-node-%(process_num)03d') - f.write('''[program:svc-node] -directory = {} -command = {} -r {}/daemon.ini -autorestart=true -redirect_stderr=true -#stdout_logfile=/dev/fd/1 -stdout_logfile={}/svc-node-%(process_num)03d-log.txt -stdout_logfile_maxbytes=0 -process_name = svc-node-%(process_num)03d -numprocs = {} -'''.format(basedir, exe, basedir, args.dir, args.svc)) - basedir = os.path.join(args.dir, 'client-node-%(process_num)03d') - f.write('''[program:Client-node] -directory = {} -command = bash -c "sleep 5 && {} {}/client.ini" -autorestart=true -redirect_stderr=true -#stdout_logfile=/dev/fd/1 -stdout_logfile={}/client-node-%(process_num)03d-log.txt -stdout_logfile_maxbytes=0 -process_name = client-node-%(process_num)03d -numprocs = {} -'''.format(basedir, exe, basedir, args.dir, args.clients)) - f.write('[supervisord]\ndirectory=.\n') - - -if __name__ == '__main__': - main() diff --git a/contrib/testnet/readme.md b/contrib/testnet/readme.md deleted file mode 100644 index cb95b3334..000000000 --- a/contrib/testnet/readme.md +++ /dev/null @@ -1,23 +0,0 @@ -loopback testnet scripts - -requirements: - -* bash -* python3 -* supervisord - - -setup: - -make a testnet compatable lokinet build: - - $ cmake -DWITH_TESTNET=ON -B build-testnet -S . - $ make -C build-testnet lokinet - -usage: - -from root of repo run: - - $ ./contrib/testnet/testnet.sh build-testnet/daemon/lokinet 20 200 - -this will spin up 20 service nodes and 200 clients diff --git a/contrib/testnet/requirements.txt b/contrib/testnet/requirements.txt deleted file mode 100644 index 010599389..000000000 --- a/contrib/testnet/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -flask -pynacl diff --git a/contrib/testnet/testnet.sh b/contrib/testnet/testnet.sh deleted file mode 100755 index 86d55e832..000000000 --- a/contrib/testnet/testnet.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -for arg in "$1" "$2" "$3" ; do - test x = "x$arg" && echo "usage: $0 path/to/lokinet num_svc num_clients" && exit 1; -done - -script_root=$(dirname $(readlink -e $0)) -testnet_dir=/tmp/lokinet-testnet - -mkdir -p $testnet_dir - -set -x - -$script_root/genconf.py --bin $1 --netid=testnet --out=$testnet_dir/testnet.ini --svc $2 --dir=$testnet_dir --clients $3 || exit 1 -supervisord -n -c $testnet_dir/testnet.ini diff --git a/contrib/windows-configure.sh b/contrib/windows-configure.sh new file mode 100755 index 000000000..f41ef3af8 --- /dev/null +++ b/contrib/windows-configure.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -e +set -x + +# Usage: windows-configure.sh [rootdir [builddir]] -DWHATEVER=BLAH ... + +if [ $# -ge 1 ] && [[ "$1" != -* ]]; then + root="$1" + shift +else + root="$(dirname $0)"/.. +fi +root="$(readlink -f "$root")" + +if [ $# -ge 1 ] && [[ "$1" != -* ]]; then + build="$(readlink -f "$1")" + shift +else + build="$root/build/win32" + echo "Setting up build in $build" +fi + +mkdir -p "$build" +cmake \ + -S "$root" -B "$build" \ + -G 'Unix Makefiles' \ + -DCMAKE_EXE_LINKER_FLAGS=-fstack-protector \ + -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always \ + -DCMAKE_TOOLCHAIN_FILE="$root/contrib/cross/mingw64.cmake" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_STATIC_DEPS=ON \ + -DBUILD_PACKAGE=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_TESTING=OFF \ + -DBUILD_LIBLOKINET=OFF \ + -DWITH_TESTS=OFF \ + -DWITH_BOOTSTRAP=OFF \ + -DNATIVE_BUILD=OFF \ + -DSTATIC_LINK=ON \ + -DWITH_SYSTEMD=OFF \ + -DFORCE_OXENMQ_SUBMODULE=ON \ + -DFORCE_OXENC_SUBMODULE=ON \ + -DFORCE_FMT_SUBMODULE=ON \ + -DFORCE_SPDLOG_SUBMODULE=ON \ + -DFORCE_NLOHMANN_SUBMODULE=ON \ + -DWITH_LTO=OFF \ + "$@" diff --git a/contrib/windows.sh b/contrib/windows.sh index 5d6bf399c..d06adab84 100755 --- a/contrib/windows.sh +++ b/contrib/windows.sh @@ -6,25 +6,8 @@ set -e set +x -mkdir -p build-windows -cd build-windows -cmake \ - -G 'Unix Makefiles' \ - -DCMAKE_EXE_LINKER_FLAGS=-fstack-protector \ - -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always\ - -DCMAKE_TOOLCHAIN_FILE=../contrib/cross/mingw64.cmake\ - -DBUILD_STATIC_DEPS=ON \ - -DBUILD_PACKAGE=ON \ - -DBUILD_SHARED_LIBS=OFF \ - -DBUILD_TESTING=OFF \ - -DBUILD_LIBLOKINET=ON \ - -DWITH_TESTS=OFF \ - -DNATIVE_BUILD=OFF \ - -DSTATIC_LINK=ON \ - -DWITH_SYSTEMD=OFF \ - -DFORCE_OXENMQ_SUBMODULE=ON \ - -DSUBMODULE_CHECK=OFF \ - -DWITH_LTO=OFF \ - -DCMAKE_BUILD_TYPE=Release \ - $@ .. -make package -j${JOBS:-$(nproc)} +root="$(readlink -f $(dirname $0)/../)" +mkdir -p $root/build/win32 +$root/contrib/windows-configure.sh $root $root/build/win32 "$@" +make package -j${JOBS:-$(nproc)} -C $root/build/win32 + diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 083d6bfb6..e4cedd9b6 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -57,7 +57,6 @@ else() endif() enable_lto(lokinet-cryptography) -add_log_tag(lokinet-cryptography) if (WARNINGS_AS_ERRORS) target_compile_options(lokinet-cryptography PUBLIC -Wall -Wextra -Werror) diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index bc7be3a1f..c9ff4aec6 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -1,21 +1,19 @@ -set(DEFAULT_WITH_BOOTSTRAP ON) -if(APPLE) - set(DEFAULT_WITH_BOOTSTRAP OFF) -endif() -option(WITH_BOOTSTRAP "build lokinet-bootstrap tool" ${DEFAULT_WITH_BOOTSTRAP}) +set(exetargets lokinet) -add_executable(lokinet-vpn lokinet-vpn.cpp) if(APPLE) add_executable(lokinet lokinet.swift) - enable_lto(lokinet) + target_compile_options(lokinet BEFORE PRIVATE -target x86_64-apple-macos${CMAKE_OSX_DEPLOYMENT_TARGET}) else() add_executable(lokinet lokinet.cpp) - enable_lto(lokinet lokinet-vpn) +endif() +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) - enable_lto(lokinet-bootstrap) - endif() +if(WITH_BOOTSTRAP) + add_executable(lokinet-bootstrap lokinet-bootstrap.cpp) + list(APPEND exetargets lokinet-bootstrap) + enable_lto(lokinet-bootstrap) endif() @@ -48,86 +46,45 @@ if(WITH_BOOTSTRAP) endif() endif() -set(exetargets lokinet lokinet-vpn) -if(WITH_BOOTSTRAP) - list(APPEND exetargets lokinet-bootstrap) +# cmake interface library for bunch of cmake hacks to fix final link order +add_library(hax_and_shims_for_cmake INTERFACE) +if(WIN32) + target_link_libraries(hax_and_shims_for_cmake INTERFACE uvw oxenmq::oxenmq -lws2_32 -lshlwapi -ldbghelp -luser32 -liphlpapi -lpsapi -luserenv) endif() foreach(exe ${exetargets}) - if(WIN32 AND NOT MSVC_VERSION) - target_sources(${exe} PRIVATE ../llarp/win32/version.rc) + if(WIN32) + target_sources(${exe} PRIVATE ${CMAKE_BINARY_DIR}/${exe}.rc) target_link_libraries(${exe} PRIVATE -static-libstdc++ -static-libgcc --static -Wl,--pic-executable,-e,mainCRTStartup,--subsystem,console:5.00) - target_link_libraries(${exe} PRIVATE ws2_32 iphlpapi) elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") target_link_directories(${exe} PRIVATE /usr/local/lib) endif() - target_link_libraries(${exe} PUBLIC liblokinet) + target_link_libraries(${exe} PUBLIC lokinet-amalgum hax_and_shims_for_cmake) + if(STRIP_SYMBOLS) + add_custom_command(TARGET ${exe} + POST_BUILD + COMMAND ${CMAKE_OBJCOPY} ARGS --only-keep-debug $ $.debug + COMMAND ${CMAKE_STRIP} ARGS --strip-all $) + endif() target_include_directories(${exe} PUBLIC "${PROJECT_SOURCE_DIR}") - target_compile_definitions(${exe} PRIVATE -DVERSIONTAG=${GIT_VERSION_REAL}) - add_log_tag(${exe}) if(should_install) if(APPLE) - install(TARGETS ${exe} BUNDLE DESTINATION "${PROJECT_BINARY_DIR}" COMPONENT lokinet) + install(TARGETS ${exe} + BUNDLE DESTINATION "${PROJECT_BINARY_DIR}" + RUNTIME DESTINATION "." + COMPONENT lokinet) else() install(TARGETS ${exe} RUNTIME DESTINATION bin COMPONENT lokinet) endif() endif() endforeach() -if(APPLE) - - set(CODESIGN_APP "" CACHE STRING "codesign the macos app using this key identity") - set(CODESIGN_APPEX "${CODESIGN_APP}" CACHE STRING "codesign the internal extension using this key identity; defaults to CODESIGN_APP if empty") - - set(mac_icon ${CMAKE_CURRENT_BINARY_DIR}/lokinet.icns) - add_custom_command(OUTPUT ${mac_icon} - COMMAND ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${mac_icon} - DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh) - add_custom_target(icons DEPENDS ${mac_icon}) - add_dependencies(lokinet icons lokinet-extension) - add_custom_command(TARGET lokinet - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed - $/Contents/Resources/bootstrap.signed - COMMAND mkdir -p $/Contents/PlugIns - COMMAND cp -a $ $/Contents/PlugIns/ - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.provisionprofile - $/Contents/embedded.provisionprofile - ) - - set_target_properties(lokinet - PROPERTIES - MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_INFO_STRING "Lokinet IP Packet Onion Router" - MACOSX_BUNDLE_BUNDLE_NAME "Lokinet" - MACOSX_BUNDLE_BUNDLE_VERSION "${lokinet_VERSION}" - MACOSX_BUNDLE_LONG_VERSION_STRING "${lokinet_VERSION}" - MACOSX_BUNDLE_SHORT_VERSION_STRING "${lokinet_VERSION_MAJOR}.${lokinet_VERSION_MINOR}" - MACOSX_BUNDLE_GUI_IDENTIFIER "com.loki-project.lokinet" - MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/contrib/macos/Info.plist.in" - MACOSX_BUNDLE_ICON_FILE "${mac_icon}" - MACOSX_BUNDLE_COPYRIGHT "© 2021, The Oxen Project") - if (CODESIGN_APP AND CODESIGN_APPEX) - message(STATUS "codesigning with ${CODESIGN_APP} (app) ${CODESIGN_APPEX} (appex)") - set(SIGN_TARGET "${CMAKE_CURRENT_BINARY_DIR}/lokinet.app") - configure_file( - "${PROJECT_SOURCE_DIR}/contrib/macos/sign.sh.in" - "${PROJECT_BINARY_DIR}/sign.sh" - @ONLY) - add_custom_target( - sign - DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" lokinet lokinet-extension - COMMAND "${PROJECT_BINARY_DIR}/sign.sh" - ) - else() - message(WARNING "Not codesigning: CODESIGN_APP (=${CODESIGN_APP}) and/or CODESIGN_APPEX (=${CODESIGN_APPEX}) are not set") - add_custom_target( - sign - DEPENDS lokinet lokinet-extension - COMMAND "true") - endif() -endif() - if(SETCAP) install(CODE "execute_process(COMMAND ${SETCAP} cap_net_admin,cap_net_bind_service=+eip ${CMAKE_INSTALL_PREFIX}/bin/lokinet)") endif() + +if(STRIP_SYMBOLS) + add_custom_target(symbols ALL + COMMAND ${CMAKE_COMMAND} -E tar cJf ${CMAKE_CURRENT_BINARY_DIR}/debug-symbols.tar.xz $.debug + DEPENDS lokinet) +endif() diff --git a/daemon/lokinet-vpn.cpp b/daemon/lokinet-vpn.cpp index dff05d0ee..3cdc7955d 100644 --- a/daemon/lokinet-vpn.cpp +++ b/daemon/lokinet-vpn.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -16,11 +17,11 @@ #include #endif -/// do a oxenmq request on an lmq instance blocking style +/// do a oxenmq request on an omq instance blocking style /// returns a json object parsed from the result std::optional -LMQ_Request( - oxenmq::OxenMQ& lmq, +OMQ_Request( + oxenmq::OxenMQ& omq, const oxenmq::ConnectionID& id, std::string_view method, std::optional args = std::nullopt) @@ -37,11 +38,11 @@ LMQ_Request( }; if (args.has_value()) { - lmq.request(id, method, handleRequest, args->dump()); + omq.request(id, method, handleRequest, args->dump()); } else { - lmq.request(id, method, handleRequest); + omq.request(id, method, handleRequest); } auto ftr = result_promise.get_future(); const auto str = ftr.get(); @@ -50,6 +51,50 @@ LMQ_Request( return std::nullopt; } +namespace +{ + template + constexpr bool is_optional = false; + template + constexpr bool is_optional> = true; + + // Extracts a value from a cxxopts result and assigns it into `value` if present. The value can + // either be a plain value or a std::optional. If not present, `value` is not touched. + template + void + extract_option(const cxxopts::ParseResult& r, const std::string& name, T& value) + { + if (r.count(name)) + { + if constexpr (is_optional) + value = r[name].as(); + else + value = r[name].as(); + } + } + + // 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[]) { @@ -74,8 +119,8 @@ main(int argc, char* argv[]) oxenmq::address rpcURL("tcp://127.0.0.1:1190"); std::string exitAddress; std::string endpoint = "default"; - std::optional token; - std::string range = "::/0"; + std::string token; + std::optional range; oxenmq::LogLevel logLevel = oxenmq::LogLevel::warn; bool goUp = false; bool goDown = false; @@ -95,69 +140,48 @@ main(int argc, char* argv[]) { logLevel = oxenmq::LogLevel::debug; } - if (result.count("rpc") > 0) - { - rpcURL = oxenmq::address(result["rpc"].as()); - } - if (result.count("exit") > 0) - { - exitAddress = result["exit"].as(); - } goUp = result.count("up") > 0; goDown = result.count("down") > 0; printStatus = result.count("status") > 0; killDaemon = result.count("kill") > 0; - if (result.count("endpoint") > 0) - { - endpoint = result["endpoint"].as(); - } - if (result.count("token") > 0) - { - token = result["token"].as(); - } - if (result.count("auth") > 0) - { - token = result["auth"].as(); - } - if (result.count("range") > 0) - { - range = result["range"].as(); - } + extract_option(result, "rpc", rpcURL); + extract_option(result, "exit", exitAddress); + extract_option(result, "endpoint", endpoint); + extract_option(result, "token", token); + extract_option(result, "auth", token); + extract_option(result, "range", range); } catch (const cxxopts::option_not_exists_exception& ex) { - std::cerr << ex.what(); - std::cout << opts.help() << std::endl; - return 1; + return exit_error(2, "{}\n{}", ex.what(), opts.help()); } catch (std::exception& ex) { - std::cout << ex.what() << std::endl; - return 1; - } - if ((not goUp) and (not goDown) and (not printStatus) and (not killDaemon)) - { - std::cout << opts.help() << std::endl; - return 1; + return exit_error(2, "{}", ex.what()); } + + int num_commands = goUp + goDown + printStatus + killDaemon; + + if (num_commands == 0) + return exit_error(3, "One of --up/--down/--status/--kill must be specified"); + if (num_commands != 1) + return exit_error(3, "Only one of --up/--down/--status/--kill may be specified"); + if (goUp and exitAddress.empty()) - { - std::cout << "no exit address provided" << std::endl; - return 1; - } + return exit_error("no exit address provided"); - oxenmq::OxenMQ lmq{ + oxenmq::OxenMQ omq{ [](oxenmq::LogLevel lvl, const char* file, int line, std::string msg) { std::cout << lvl << " [" << file << ":" << line << "] " << msg << std::endl; }, logLevel}; - lmq.start(); + omq.start(); std::promise connectPromise; - const auto connID = lmq.connect_remote( + const auto connID = omq.connect_remote( rpcURL, [&connectPromise](auto) { connectPromise.set_value(true); }, [&connectPromise](auto, std::string_view msg) { @@ -173,23 +197,16 @@ main(int argc, char* argv[]) if (killDaemon) { - const auto maybe = LMQ_Request(lmq, connID, "llarp.halt"); - if (not maybe.has_value()) - { - std::cout << "call to llarp.admin.die failed" << std::endl; - return 1; - } + if (not OMQ_Request(omq, connID, "llarp.halt")) + return exit_error("call to llarp.halt failed"); return 0; } if (printStatus) { - const auto maybe_status = LMQ_Request(lmq, connID, "llarp.status"); - if (not maybe_status.has_value()) - { - std::cout << "call to llarp.status failed" << std::endl; - return 1; - } + const auto maybe_status = OMQ_Request(omq, connID, "llarp.status"); + if (not maybe_status) + return exit_error("call to llarp.status failed"); try { @@ -209,43 +226,34 @@ main(int argc, char* argv[]) } catch (std::exception& ex) { - std::cout << "failed to parse result: " << ex.what() << std::endl; - return 1; + return exit_error("failed to parse result: {}", ex.what()); } return 0; } if (goUp) { - std::optional maybe_result; - if (token.has_value()) - { - maybe_result = LMQ_Request( - lmq, - connID, - "llarp.exit", - nlohmann::json{{"exit", exitAddress}, {"range", range}, {"token", *token}}); - } - else - { - maybe_result = LMQ_Request( - lmq, connID, "llarp.exit", nlohmann::json{{"exit", exitAddress}, {"range", range}}); - } + nlohmann::json opts{{"exit", exitAddress}, {"token", token}}; + if (range) + opts["range"] = *range; - if (not maybe_result.has_value()) - { - std::cout << "could not add exit" << std::endl; - return 1; - } + auto maybe_result = OMQ_Request(omq, connID, "llarp.exit", std::move(opts)); + + if (not maybe_result) + return exit_error("could not add exit"); - if (maybe_result->contains("error") and maybe_result->at("error").is_string()) + if (auto err_it = maybe_result->find("error"); + err_it != maybe_result->end() and not err_it.value().is_null()) { - std::cout << maybe_result->at("error").get() << std::endl; - return 1; + return exit_error("{}", err_it.value()); } } if (goDown) { - LMQ_Request(lmq, connID, "llarp.exit", nlohmann::json{{"range", range}, {"unmap", true}}); + nlohmann::json opts{{"unmap", true}}; + if (range) + opts["range"] = *range; + if (not OMQ_Request(omq, connID, "llarp.exit", std::move(opts))) + return exit_error("failed to unmap exit"); } return 0; diff --git a/daemon/lokinet.cpp b/daemon/lokinet.cpp index 2c1a70794..2683ccab0 100644 --- a/daemon/lokinet.cpp +++ b/daemon/lokinet.cpp @@ -2,13 +2,15 @@ #include #include #include +#include #include -#include -#include #include #ifdef _WIN32 +#include #include +#else +#include #endif #include @@ -22,25 +24,24 @@ int lokinet_main(int, char**); #ifdef _WIN32 -#include extern "C" LONG FAR PASCAL win32_signal_handler(EXCEPTION_POINTERS*); extern "C" VOID FAR PASCAL win32_daemon_entry(DWORD, LPTSTR*); -BOOL ReportSvcStatus(DWORD, DWORD, DWORD); + VOID insert_description(); -SERVICE_STATUS SvcStatus; -SERVICE_STATUS_HANDLE SvcStatusHandle; -bool start_as_daemon = false; + #endif +static auto logcat = llarp::log::Cat("main"); std::shared_ptr ctx; std::promise exit_code; void handle_signal(int sig) { + llarp::log::info(logcat, "Handling signal {}", sig); if (ctx) ctx->loop->call([sig] { ctx->HandleSignal(sig); }); else @@ -83,9 +84,6 @@ install_win32_daemon() llarp::LogError("Cannot install service ", GetLastError()); return; } - // just put the flag here. we eat it later on and specify the - // config path in the daemon entry point - StringCchCat(szPath.data(), 1024, " --win32-daemon"); // Get a handle to the SCM database. schSCManager = OpenSCManager( @@ -102,7 +100,7 @@ install_win32_daemon() // Create the service schService = CreateService( schSCManager, // SCM database - "lokinet", // name of service + strdup("lokinet"), // name of service "Lokinet for Windows", // service name to display SERVICE_ALL_ACCESS, // desired access SERVICE_WIN32_OWN_PROCESS, // service type @@ -135,10 +133,10 @@ insert_description() SC_HANDLE schSCManager; SC_HANDLE schService; SERVICE_DESCRIPTION sd; - LPTSTR szDesc = + LPTSTR szDesc = strdup( "LokiNET is a free, open source, private, " "decentralized, \"market based sybil resistant\" " - "and IP based onion routing network"; + "and IP based onion routing network"); // Get a handle to the SCM database. schSCManager = OpenSCManager( NULL, // local computer @@ -229,7 +227,7 @@ uninstall_win32_daemon() static void run_main_context(std::optional confFile, const llarp::RuntimeOptions opts) { - llarp::LogTrace("start of run_main_context()"); + llarp::LogInfo(fmt::format("starting up {} {}", llarp::VERSION_FULL, llarp::RELEASE_MOTTO)); try { std::shared_ptr conf; @@ -263,14 +261,18 @@ run_main_context(std::optional confFile, const llarp::RuntimeOptions o { 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( - "failed to set up lokinet: ", ex.what(), ", is lokinet already running? 🤔"); + 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); @@ -289,46 +291,14 @@ run_main_context(std::optional confFile, const llarp::RuntimeOptions o } #ifdef _WIN32 -void -TellWindowsServiceStopped() -{ - ::WSACleanup(); - if (not start_as_daemon) - return; - - llarp::LogInfo("Telling Windows the service has stopped."); - if (not ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0)) - { - auto error_code = GetLastError(); - if (error_code == ERROR_INVALID_DATA) - llarp::LogError( - "SetServiceStatus failed: \"The specified service status structure is invalid.\""); - else if (error_code == ERROR_INVALID_HANDLE) - llarp::LogError("SetServiceStatus failed: \"The specified handle is invalid.\""); - else - llarp::LogError("SetServiceStatus failed with an unknown error."); - } - llarp::LogContext::Instance().ImmediateFlush(); -} - -class WindowsServiceStopped -{ - public: - WindowsServiceStopped() = default; - - ~WindowsServiceStopped() - { - TellWindowsServiceStopped(); - } -}; /// minidump generation for windows jizz /// will make a coredump when there is an unhandled exception LONG GenerateDump(EXCEPTION_POINTERS* pExceptionPointers) { - const DWORD flags = MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData - | MiniDumpWithUnloadedModules | MiniDumpWithThreadInfo; + const auto flags = + (MINIDUMP_TYPE)(MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData | MiniDumpWithUnloadedModules | MiniDumpWithThreadInfo); std::stringstream ss; ss << "C:\\ProgramData\\lokinet\\crash-" << llarp::time_now_ms().count() << ".dmp"; @@ -361,39 +331,57 @@ GenerateDump(EXCEPTION_POINTERS* pExceptionPointers) 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); + + llarp::logRingBuffer = std::make_shared(100); + llarp::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO); + #ifndef _WIN32 return lokinet_main(argc, argv); #else SERVICE_TABLE_ENTRY DispatchTable[] = { - {"lokinet", (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry}, {NULL, NULL}}; - if (lstrcmpi(argv[1], "--win32-daemon") == 0) + {strdup("lokinet"), (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry}, {NULL, NULL}}; + + // 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; + + 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) { - start_as_daemon = true; - StartServiceCtrlDispatcher(DispatchTable); + llarp::sys::service_manager->disable(); + return lokinet_main(argc, argv); } else - return lokinet_main(argc, argv); + { + llarp::log::critical( + logcat, "Error launching service: {}", std::system_category().message(error)); + return 1; + } #endif } int -lokinet_main(int argc, char* argv[]) +lokinet_main(int argc, char** argv) { - auto result = Lokinet_INIT(); - if (result) - { + if (auto result = Lokinet_INIT()) return result; - } + llarp::RuntimeOptions opts; + opts.showBanner = false; #ifdef _WIN32 - WindowsServiceStopped stopped_raii; if (startWinsock()) return -1; SetConsoleCtrlHandler(handle_signal_win32, TRUE); - - // SetUnhandledExceptionFilter(win32_signal_handler); #endif + cxxopts::Options options( "lokinet", "LokiNET is a free, open source, private, " @@ -401,19 +389,16 @@ lokinet_main(int argc, char* argv[]) "and IP based onion routing network"); // clang-format off options.add_options() - ("v,verbose", "Verbose", cxxopts::value()) #ifdef _WIN32 ("install", "install win32 daemon to SCM", cxxopts::value()) ("remove", "remove win32 daemon from SCM", cxxopts::value()) #endif - ("h,help", "help", cxxopts::value())("version", "version", cxxopts::value()) - ("g,generate", "generate client config", cxxopts::value()) - ("r,router", "run as router instead of client", cxxopts::value()) - ("f,force", "overwrite", cxxopts::value()) - ("c,colour", "colour output", cxxopts::value()->default_value("true")) - ("b,background", "background mode (start, but do not connect to the network)", - cxxopts::value()) - ("config", "path to configuration file", cxxopts::value()) + ("h,help", "print this help message", cxxopts::value()) + ("version", "print version string", cxxopts::value()) + ("g,generate", "generate default configuration and exit", cxxopts::value()) + ("r,router", "run in routing mode instead of client only mode", cxxopts::value()) + ("f,force", "force writing config even if it already exists", cxxopts::value()) + ("config", "path to lokinet.ini configuration file", cxxopts::value()) ; // clang-format on @@ -426,24 +411,11 @@ lokinet_main(int argc, char* argv[]) { auto result = options.parse(argc, argv); - if (result.count("verbose") > 0) - { - SetLogLevel(llarp::eLogDebug); - llarp::LogDebug("debug logging activated"); - } - - if (!result["colour"].as()) - { - llarp::LogContext::Instance().logStream = - std::make_unique(false, std::cerr); - } - if (result.count("help")) { std::cout << options.help() << std::endl; return 0; } - if (result.count("version")) { std::cout << llarp::VERSION_FULL << std::endl; @@ -467,11 +439,6 @@ lokinet_main(int argc, char* argv[]) genconfigOnly = true; } - if (result.count("background") > 0) - { - opts.background = true; - } - if (result.count("router") > 0) { opts.isSNode = true; @@ -555,18 +522,15 @@ lokinet_main(int argc, char* argv[]) SetUnhandledExceptionFilter(&GenerateDump); #endif - std::thread main_thread{[&] { run_main_context(configFile, opts); }}; + std::thread main_thread{[configFile, opts] { run_main_context(configFile, opts); }}; auto ftr = exit_code.get_future(); -#ifdef _WIN32 - ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); -#endif - 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.", "⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠄⠄⠄⠄", @@ -588,12 +552,10 @@ lokinet_main(int argc, char* argv[]) "file a bug report now or be cursed with this " "annoying image in your syslog for all time."}) { - llarp::LogError{wtf}; - llarp::LogContext::Instance().ImmediateFlush(); + llarp::log::critical(deadlock_cat, wtf); + llarp::log::flush(); } -#ifdef _WIN32 - TellWindowsServiceStopped(); -#endif + llarp::sys::service_manager->failed(); std::abort(); } } while (ftr.wait_for(std::chrono::seconds(1)) != std::future_status::ready); @@ -617,7 +579,8 @@ lokinet_main(int argc, char* argv[]) code = 2; } - llarp::LogContext::Instance().ImmediateFlush(); + llarp::log::flush(); + llarp::sys::service_manager->stopped(); if (ctx) { ctx.reset(); @@ -626,29 +589,6 @@ lokinet_main(int argc, char* argv[]) } #ifdef _WIN32 -BOOL -ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) -{ - static DWORD dwCheckPoint = 1; - - // Fill in the SERVICE_STATUS structure. - SvcStatus.dwCurrentState = dwCurrentState; - SvcStatus.dwWin32ExitCode = dwWin32ExitCode; - SvcStatus.dwWaitHint = dwWaitHint; - - if (dwCurrentState == SERVICE_START_PENDING) - SvcStatus.dwControlsAccepted = 0; - else - SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; - - if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED)) - SvcStatus.dwCheckPoint = 0; - else - SvcStatus.dwCheckPoint = dwCheckPoint++; - - // Report the status of the service to the SCM. - return SetServiceStatus(SvcStatusHandle, &SvcStatus); -} VOID FAR PASCAL SvcCtrlHandler(DWORD dwCtrl) @@ -658,44 +598,45 @@ SvcCtrlHandler(DWORD dwCtrl) switch (dwCtrl) { case SERVICE_CONTROL_STOP: - ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); - // Signal the service to 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: - break; + // 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; } } -// The win32 daemon entry point is just a trampoline that returns control -// to the original lokinet entry -// and only gets called if we get --win32-daemon in the command line +// 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 argc, LPTSTR* argv) +win32_daemon_entry(DWORD, LPTSTR* argv) { // Register the handler function for the service - SvcStatusHandle = RegisterServiceCtrlHandler("lokinet", SvcCtrlHandler); + auto* svc = dynamic_cast(llarp::sys::service_manager); + svc->handle = RegisterServiceCtrlHandler("lokinet", SvcCtrlHandler); - if (!SvcStatusHandle) + if (svc->handle == nullptr) { llarp::LogError("failed to register daemon control handler"); return; } - // These SERVICE_STATUS members remain as set here - SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; - SvcStatus.dwServiceSpecificExitCode = 0; - - // Report initial status to the SCM - ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000); - // SCM clobbers startup args, regenerate them here - argc = 2; - argv[1] = "c:/programdata/lokinet/lokinet.ini"; - argv[2] = nullptr; - lokinet_main(argc, argv); + // 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()); } #endif diff --git a/daemon/lokinet.swift b/daemon/lokinet.swift index 4af96bac9..adf11e9f3 100644 --- a/daemon/lokinet.swift +++ b/daemon/lokinet.swift @@ -1,34 +1,83 @@ import AppKit import Foundation import NetworkExtension +import SystemExtensions let app = NSApplication.shared +let START = "--start" +let STOP = "--stop" + +let HELP_STRING = "usage: lokinet {--start|--stop}" + class LokinetMain: NSObject, NSApplicationDelegate { var vpnManager = NETunnelProviderManager() - let lokinetComponent = "com.loki-project.lokinet.network-extension" + var mode = START + let netextBundleId = "org.lokinet.network-extension" func applicationDidFinishLaunching(_: Notification) { - setupVPNJizz() + if mode == START { + startNetworkExtension() + } else if mode == STOP { + tearDownVPNTunnel() + } else { + result(msg: HELP_STRING) + } } func bail() { app.terminate(self) } - func setupVPNJizz() { - NSLog("Starting up lokinet") + func result(msg: String) { + NSLog(msg) + // TODO: does lokinet continue after this? + bail() + } + + func tearDownVPNTunnel() { + NSLog("Stopping Lokinet") NETunnelProviderManager.loadAllFromPreferences { [self] (savedManagers: [NETunnelProviderManager]?, error: Error?) in if let error = error { - NSLog(error.localizedDescription) - bail() + self.result(msg: error.localizedDescription) return } if let savedManagers = savedManagers { for manager in savedManagers { - if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.lokinetComponent { - NSLog("%@", manager) + if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.netextBundleId { + manager.connection.stopVPNTunnel() + self.result(msg: "Lokinet Down") + } + } + } + self.result(msg: "Lokinet is not up") + } + } + + func startNetworkExtension() { + #if MACOS_SYSTEM_EXTENSION + NSLog("Loading Lokinet network extension") + // Start by activating the system extension + let activationRequest = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: netextBundleId, queue: .main) + activationRequest.delegate = self + OSSystemExtensionManager.shared.submitRequest(activationRequest) + #else + setupVPNTunnel() + #endif + } + + func setupVPNTunnel() { + NSLog("Starting up Lokinet tunnel") + NETunnelProviderManager.loadAllFromPreferences { [self] (savedManagers: [NETunnelProviderManager]?, error: Error?) in + if let error = error { + self.result(msg: error.localizedDescription) + return + } + + if let savedManagers = savedManagers { + for manager in savedManagers { + if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.netextBundleId { NSLog("Found saved VPN Manager") self.vpnManager = manager } @@ -37,8 +86,10 @@ class LokinetMain: NSObject, NSApplicationDelegate { let providerProtocol = NETunnelProviderProtocol() providerProtocol.serverAddress = "loki.loki" // Needs to be set to some non-null dummy value providerProtocol.username = "anonymous" - providerProtocol.providerBundleIdentifier = self.lokinetComponent - providerProtocol.enforceRoutes = true + providerProtocol.providerBundleIdentifier = self.netextBundleId + if #available(macOS 11, *) { + providerProtocol.enforceRoutes = true + } // macos seems to have trouble when this is true, and reports are that this breaks and // doesn't do what it says on the tin in the first place. Needs more testing. providerProtocol.includeAllNetworks = false @@ -46,28 +97,30 @@ class LokinetMain: NSObject, NSApplicationDelegate { self.vpnManager.isEnabled = true // self.vpnManager.isOnDemandEnabled = true self.vpnManager.localizedDescription = "lokinet" - self.vpnManager.saveToPreferences(completionHandler: { error -> Void in + self.vpnManager.saveToPreferences(completionHandler: { [self] error -> Void in if error != nil { NSLog("Error saving to preferences") - NSLog(error!.localizedDescription) - bail() + self.result(msg: error!.localizedDescription) } else { self.vpnManager.loadFromPreferences(completionHandler: { error in if error != nil { NSLog("Error loading from preferences") - NSLog(error!.localizedDescription) - bail() + self.result(msg: error!.localizedDescription) } else { do { NSLog("Trying to start") self.initializeConnectionObserver() try self.vpnManager.connection.startVPNTunnel() } catch let error as NSError { - NSLog(error.localizedDescription) - bail() + self.result(msg: error.localizedDescription) } catch { - NSLog("There was a fatal error") - bail() + self.result(msg: "There was a fatal error") + } + + // Check if we are already connected because, if so, we won't get a + // status change and will just hang waiting for one. + if self.vpnManager.connection.status == .connected { + self.result(msg: "VPN already connected"); } } }) @@ -77,11 +130,11 @@ class LokinetMain: NSObject, NSApplicationDelegate { } func initializeConnectionObserver() { - NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: vpnManager.connection, queue: OperationQueue.main) { _ -> Void in + NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: vpnManager.connection, queue: OperationQueue.main) { [self] _ -> Void in if self.vpnManager.connection.status == .invalid { - NSLog("VPN configuration is invalid") + self.result(msg: "VPN configuration is invalid") } else if self.vpnManager.connection.status == .disconnected { - NSLog("VPN is disconnected.") + self.result(msg: "VPN is disconnected.") } else if self.vpnManager.connection.status == .connecting { NSLog("VPN is connecting...") } else if self.vpnManager.connection.status == .reasserting { @@ -89,12 +142,102 @@ class LokinetMain: NSObject, NSApplicationDelegate { } else if self.vpnManager.connection.status == .disconnecting { NSLog("VPN is disconnecting...") } else if self.vpnManager.connection.status == .connected { - NSLog("VPN Connected") + self.result(msg: "VPN Connected") } } } } -let delegate = LokinetMain() -app.delegate = delegate -app.run() +#if MACOS_SYSTEM_EXTENSION + + extension LokinetMain: OSSystemExtensionRequestDelegate { + func request(_: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) { + guard result == .completed else { + NSLog("Unexpected result %d for system extension request", result.rawValue) + return + } + NSLog("Lokinet system extension loaded") + setupVPNTunnel() + } + + func request(_: OSSystemExtensionRequest, didFailWithError error: Error) { + NSLog("System extension request failed: %@", error.localizedDescription) + self.bail() + } + + func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { + NSLog("Extension %@ requires user approval", request.identifier) + } + + func request(_ request: OSSystemExtensionRequest, + actionForReplacingExtension existing: OSSystemExtensionProperties, + withExtension extension: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction + { + NSLog("Replacing extension %@ version %@ with version %@", request.identifier, existing.bundleShortVersion, `extension`.bundleShortVersion) + return .replace + } + } + +#endif + +let args = CommandLine.arguments + +// If we are invoked with no arguments then exec the gui. This is dumb, but there doesn't seem to +// be a nicer way to do this on Apple's half-baked platform because: +// - we have three "bundles" we need to manage: the GUI app, the system extension, and the Lokinet +// app (this file) which loads the system extension. +// - if we embed the system extension directly inside the GUI then it fails to launch because the +// electron GUI's requirements (needed for JIT) conflict with the ability to load a system +// extensions. +// - if we embed Lokinet.app inside Lokinet-GUI.app and then the system extension inside Lokinet.app +// then it works, but macos loses track of the system extension and doesn't remove it when you +// remove the application. (It breaks your system, leaving an impossible-to-remove system +// extension, in just the same way it breaks if you don't use Finder to remove the Application. +// Apple used to say (around 2 years ago as of writing) that they would fix this situation "soon", +// but hasn't, and has stopped saying anything about it.) +// - if we try to use multiple executables (one to launch the system extension, one simple shell +// script to execs the embedded GUI app) inside the Lokinet.app and make the GUI the default for +// the application then Lokinet gets killed by gatekeeper because code signing only applies the +// (required-for-system-extensions) provisioningprofile to the main binary in the app. +// +// So we are left needing *one* single binary that isn't the GUI but has to do double-duty for both +// exec'ing the binary and loading lokinet, depending on how it is called. +// +// But of course there is no way to specify command-line arguments to the default binary macOS runs, +// so we can't use a `--gui` flag or anything so abhorrent to macos purity, thus this nasty +// solution: +// - no args -- exec the GUI +// - `--start` -- load the system extension and start lokinet +// - `--stop` -- stop lokinet +// +// macOS: land of half-baked implementations and nasty hacks to make anything work. + +if args.count == 1 { + let gui_path = Bundle.main.resourcePath! + "/../Helpers/Lokinet-GUI.app" + if !FileManager.default.fileExists(atPath: gui_path) { + NSLog("Could not find gui app at %@", gui_path) + exit(1) + } + let gui_url = URL(fileURLWithPath: gui_path, isDirectory: false) + let gui_app_conf = NSWorkspace.OpenConfiguration() + let group = DispatchGroup() + group.enter() + NSWorkspace.shared.openApplication(at: gui_url, configuration: gui_app_conf, + completionHandler: { (app, error) in + if error != nil { + NSLog("Error launching gui: %@", error!.localizedDescription) + } else { + NSLog("Lauched GUI"); + } + group.leave() + }) + group.wait() + +} else if args.count == 2 { + let delegate = LokinetMain() + delegate.mode = args[1] + app.delegate = delegate + app.run() +} else { + NSLog(HELP_STRING) +} diff --git a/daemon/lokinetctl.cpp b/daemon/lokinetctl.cpp deleted file mode 100644 index 8eaf099aa..000000000 --- a/daemon/lokinetctl.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -namespace -{ - bool - dumpRc(const std::vector& files) - { - nlohmann::json result; - for (const auto& file : files) - { - llarp::RouterContact rc; - const bool ret = rc.Read(file.c_str()); - - if (ret) - { - result[file] = rc.ToJson(); - } - else - { - std::cerr << "file = " << file << " was not a valid rc file\n"; - } - } - std::cout << result << "\n"; - return true; - } - -} // namespace - -int -main(int argc, char* argv[]) -{ - cxxopts::Options options( - "lokinetctl", - "LokiNET is a free, open source, private, " - "decentralized, \"market based sybil resistant\" " - "and IP based onion routing network"); - - options.add_options()("v,verbose", "Verbose", cxxopts::value())( - "h,help", "help", cxxopts::value())( - "c,config", - "config file", - cxxopts::value()->default_value(llarp::GetDefaultConfigPath().string()))( - "dump", "dump rc file", cxxopts::value>(), "FILE"); - - try - { - const auto result = options.parse(argc, argv); - - if (result.count("verbose") > 0) - { - SetLogLevel(llarp::eLogDebug); - llarp::LogContext::Instance().logStream = - std::make_unique(true, std::cerr); - llarp::LogDebug("debug logging activated"); - } - else - { - SetLogLevel(llarp::eLogError); - llarp::LogContext::Instance().logStream = - std::make_unique(true, std::cerr); - } - - if (result.count("help") > 0) - { - std::cout << options.help() << std::endl; - return 0; - } - - if (result.count("dump") > 0) - { - if (!dumpRc(result["dump"].as>())) - { - return 1; - } - } - } - catch (const cxxopts::OptionParseException& ex) - { - std::cerr << ex.what() << std::endl; - std::cout << options.help() << std::endl; - return 1; - } - - return 0; -} diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index c7b8a4fd5..4daf79b6c 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -3,37 +3,82 @@ if (NOT DOXYGEN) message(STATUS "Documentation generation disabled (doxygen not found)") return() endif() -find_program(SPHINX_BUILD sphinx-build) -if (NOT SPHINX_BUILD) - message(STATUS "Documentation generation disabled (sphinx-build not found)") + +find_program(MKDOCS mkdocs) +if (NOT MKDOCS) + message(STATUS "Documentation generation disabled (mkdocs not found)") return() - endif() - +endif() + set(lokinet_doc_sources "${DOCS_SRC}") string(REPLACE ";" " " lokinet_doc_sources_spaced "${lokinet_doc_sources}") +add_custom_target(clean_xml COMMAND ${CMAKE_COMMAND} -E rm -rf doxyxml) +add_custom_target(clean_markdown COMMAND ${CMAKE_COMMAND} -E rm -rf markdown) + add_custom_command( OUTPUT doxyxml/index.xml COMMAND ${DOXYGEN} Doxyfile DEPENDS + clean_xml ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile ${lokinet_doc_sources} ) +# find doxybook2 +find_program(DOXYBOOK2 doxybook2) +if(NOT DOXYBOOK2) + if(NOT DOXYBOOK2_ZIP_URL) + set(DOXYBOOK2_VERSION v1.4.0 CACHE STRING "doxybook2 version") + set(DOXYBOOK2_ZIP_URL "https://github.com/matusnovak/doxybook2/releases/download/${DOXYBOOK2_VERSION}/doxybook2-linux-amd64-${DOXYBOOK2_VERSION}.zip") + set(DOXYBOOK2_ZIP_HASH_OPTS EXPECTED_HASH SHA256=bab9356f5daa550cbf21d8d9b554ea59c8be039716a2caf6e96dee52713fccb0) + endif() + + file(DOWNLOAD + ${DOXYBOOK2_ZIP_URL} + ${CMAKE_CURRENT_BINARY_DIR}/doxybook2.zip + ${DOXYBOOK2_ZIP_HASH_OPTS}) + + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_CURRENT_BINARY_DIR}/doxybook2.zip + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set(DOXYBOOK2 ${CMAKE_CURRENT_BINARY_DIR}/bin/doxybook2) + set(doxybook_localbin ${DOXYBOOK2}) +endif() + add_custom_command( - OUTPUT html/index.html - COMMAND ${SPHINX_BUILD} -j auto - -Dbreathe_projects.lokinet=${CMAKE_CURRENT_BINARY_DIR}/doxyxml - -Dversion=${lokinet_VERSION} -Drelease=${lokinet_VERSION} - -Aversion=${lokinet_VERSION} -Aversions=${lokinet_VERSION_MAJOR},${lokinet_VERSION_MINOR},${lokinet_VERSION_PATCH} - -b html - ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/html + OUTPUT gen + COMMAND ${DOXYBOOK2} --input ${CMAKE_CURRENT_BINARY_DIR}/doxyxml --output ${CMAKE_CURRENT_BINARY_DIR}/gen --config config.json DEPENDS - ${CMAKE_CURRENT_BINARY_DIR}/index.rst - ${CMAKE_CURRENT_BINARY_DIR}/conf.py - ${CMAKE_CURRENT_BINARY_DIR}/doxyxml/index.xml + ${doxybook_localbin} + ${CMAKE_CURRENT_BINARY_DIR}/gen/index.md + ${CMAKE_CURRENT_BINARY_DIR}/config.json + ${CMAKE_CURRENT_BINARY_DIR}/doxyxml/index.xml) + +add_custom_target(clean_html COMMAND ${CMAKE_COMMAND} -E rm -rf html) + +add_custom_command( + OUTPUT markdown + COMMAND find ${CMAKE_CURRENT_BINARY_DIR}/gen/ -type f -name '*.md' -exec ${CMAKE_CURRENT_SOURCE_DIR}/fix-markdown.sh {} "\;" && ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/gen ${CMAKE_CURRENT_BINARY_DIR}/markdown + DEPENDS gen ) -add_custom_target(doc DEPENDS html/index.html) -configure_file(conf.py.in conf.py @ONLY) + +add_custom_command( + OUTPUT html + COMMAND ${MKDOCS} build + DEPENDS + clean_html + ${CMAKE_CURRENT_BINARY_DIR}/markdown) + +add_custom_target(doc DEPENDS markdown) + configure_file(Doxyfile.in Doxyfile @ONLY) -configure_file(index.rst index.rst COPYONLY) +configure_file(index.md.in index.md @ONLY) + +configure_file(config.json config.json COPYONLY) +configure_file(mkdocs.yml mkdocs.yml COPYONLY) + +# we seperate this step out so we force clean_markdown to run before markdown target +add_custom_command( + OUTPUT gen/index.md + COMMAND ${CMAKE_COMMAND} -E copy index.md gen/index.md + DEPENDS clean_markdown) diff --git a/docs/README b/docs/README deleted file mode 100644 index f01a01d3b..000000000 --- a/docs/README +++ /dev/null @@ -1,5 +0,0 @@ -Protocol Specifications Directory - -All documents in this directory are licened CC0 and placed into the public domain. - -Please note that the reference implementation LokiNET is licensed under ZLIB license diff --git a/docs/api_v0.txt b/docs/api_v0.txt deleted file mode 100644 index 0aaa9fec5..000000000 --- a/docs/api_v0.txt +++ /dev/null @@ -1,263 +0,0 @@ - -LLARP Traffic Routing Protocol (LTRP) - -LRTP is a protocol that instructs how to route hidden service traffic on LLARP -based networks. - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this -document are to be interpreted as described in RFC 2119 [RFC2119]. - -Overview: - -LRTP is a message oriented data delivery and receival protocol for hidden -service traffic. All structures are BitTorrent Encoded dictionaries sent -over TCP. - -all structures are bencoded when sent over the networks. -In this document they are provided in JSON for ease of display. - -message format: - -<2 bytes length (N)> - - - -Nouns (data structures): - -Path: information about a path that we have built - -{ - H: [router_id_32_bytes, router_id_32_bytes, router_id_32_bytes, router_id_32_bytes], - R: "<16 bytes local rxid>", - T: "<16 bytes local txid>" -} - -Introduction: a hidden service introduction - -{ - E: expiration_ms_since_epoch_uint64, - L: advertised_latency_ms_uint64, - P: "<16 bytes pathid>", - R: "<32 bytes RouterID>" -} - -ServiceInfo: public key info for hidden service address - -{ - A: "<32 bytes .loki address>", - E: "<32 bytes public encryption key>", - S: "<32 bytes public signing key>" -} - -IntroSet: information about an introduction set from the network - -{ - E: expires_at_timestamp_ms_since_epoch_uint64, - I: [Intro0, Intro1, ... IntroN], - S: ServiceInfo -} - -Converstation: information about a loki network converstation - -{ - L: "<32 bytes loki address provided if a loki address>", - S: "<32 bytes snode address provided if a snode address>", - T: "<16 bytes convo tag>" -} - -SessionInfo: information about our current session - -{ - I: [inbound,convos,here], - O: [outbound,covos,here], - P: [Path0, Path1, .... PathN], - S: Current IntroSet, -} - -Verbs (methods): - -session requset (C->S) - -the first message sent by the client - -{ - A: "session", - B: "<8 bytes random>", - T: milliseconds_since_epoch_client_now_uint64, - Y: 0, - Z: "<32 bytes keyed hash>" -} - -session accept (S->C) - -sent in reply to a session message to indicate session accept and give -a session cookie to the client. - -{ - A: "session-reply", - B: "<8 bytes random from session request>", - C: "<16 bytes session cookie>", - T: milliseconds_since_epoch_server_now_uint64, - Y: 0, - Z: "<32 bytes keyed hash>" -} - -session reject (S->C) - -sent in reply to a session message to indicate session rejection - -{ - A: "session-reject", - B: "<8 bytes random from session request>", - R: "", - T: milliseconds_since_epoch_server_now_uint64, - Y: 0, - Z: "<32 bytes keyed hash>" -} - -spawn a hidden service (C->S) - -only one hidden service can be made per session - -{ - A: "spawn", - C: "<16 bytes session cookie>", - O: config_options_dict, - Y: 1, - Z: "<32 bytes keyed hash>" -} - -inform that we have spawned a new hidden service endpoint (S->C) - -{ - A: "spawn-reply", - C: "<16 bytes session cookie>", - S: ServiceInfo, - Y: 1, - Z: "<32 bytes keyed hash>" -} - -inform that we have not spaned a new hidden service endpint (S->C) - -after sending this message the server closes the connection - -{ - A: "spawn-reject", - C: "<16 bytes session cookie>", - E: "", - Y: 1, - Z: "<32 bytes keyed hash>" -} - -create a new convseration on a loki/snode address (C->S) - -{ - A: "start-convo", - B: "<8 bytes random>", - C: "<16 bytes session cookie>", - R: "human readable remote address .snode/.loki", - Y: sequence_num_uint64, - Z: "<32 bytes keyed hash>" -} - -sent in reply to a make-convo message to indicate rejection (S->C) - -{ - A: "start-convo-reject", - B: "<8 bytes random from start-convo message>", - C: "<16 bytes session cookie>", - S: status_bitmask_uint, - Y: sequence_num_uint64, - Z: "<32 bytes keyed hash>" -} - -sent in reply to a make-convo message to indicate that we have accepted this -new conversation and gives the convo tag it uses. - -{ - A: "start-convo-accept", - B: "<8 bytes random from start-convo message>", - C: "<16 bytes session cookie>", - Y: sequence_num_uint64, - Z: "<32 bytes keyed hash> -} - -infrom the status of a converstation on a loki address (S->C) - -for an outbund conversation it is sent every time the status bitmask changes. -for inbound convos it is sent immediately when a new inbound conversation is made. - -S bit 0 (LSB): we found the introset/endpoint for (set by outbound) -S bit 1: we found the router to align on (set by outbound) -S bit 2: we have a path right now (set by outbound) -S bit 3: we have made the converstation (set by both) -S bit 4: we are an inbound converstation (set by inbound) - -{ - A: "convo-status", - C: "<16 bytes session cookie>", - R: "human readable address .snode/.loki", - S: bitmask_status_uint64, - T: "<16 bytes convotag>", - Y: sequence_num_uint64, - Z: "<32 bytes keyed hash>" -} - -send or recieve authenticated data to or from the network (bidi) - -protocol numbers are - -1 for ipv4 -2 for ipv6 - -{ - A: "data", - C: "<16 bytes session cookie>", - T: "<16 bytes convotag>", - W: protocol_number_uint, - X: "", - Y: sequence_num_uint64, - Z: "<32 bytes keyed hash>" -} - -get session information (C->S) - -{ - A: "info", - C: "<16 bytes session cookie>", - Y: sequence_num_uint64, - Z: "<32 bytes keyed hash>" -} - - -session information update (S->C) - -sent in reply to a get session information message - -{ - A: "info-reply", - C: "<16 bytes session cookie>", - I: hiddenserviceinfo, - Y: sequence_num_uint64, - Z: "<32 bytes keyed hash>" -} - -Protocol Flow: - -all messages have an A, C, Y and Z value - -A is the function name being called - -C is the session cookie indicating the current session - -Y is the 64 bit message sequence number as an integer - -Z is the keyed hash computed by MDS(BE(msg), K) where K is HS(api_password) -with the msg.Z being set to 32 bytes of \x00 - -both client and server MUST know a variable length string api_password used to -authenticate access to the api subsystem. - -the Y value is incremented by 1 for each direction every time the sender sends -a message in that direction. diff --git a/docs/code_structure.org b/docs/code_structure.org deleted file mode 100644 index 51952c731..000000000 --- a/docs/code_structure.org +++ /dev/null @@ -1,117 +0,0 @@ - -* lokinet components - -** basic data structures -*** AlignedBuffer -*** RouterContact -**** self signed router descriptor -*** Crypto key types - - -** network / threading / sync utilities -*** threadpool -*** logic (single thread threadpool) - -** configuration -*** ini parser -*** llarp::Configuration - -** cryptography -*** llarp::Crypto interface -**** libsodium / sntrup implementation -*** llarp::CryptoManager -**** crypto implementation signleton manager - -** nodedb -*** llarp_nodedb -**** holds many RouterContacts loaded from disk in memory -**** uses a filesystem skiplist for on disk storage -**** stores in a std::unordered_map addressable via public identity key - -** event loop -*** llarp_event_loop -**** udp socket read/write -**** network interface packet read/write -**** stream connection (outbound stream) -**** stream acceptor (inbound stream) - -** link layer message transport: -*** ILinkSession -**** the interface for an entity that is single session with relay -**** responsible for delivery recieval of link layer messages in full -*** ILinkLayer -**** bound to an address / interface -**** has a direction, inbound / outbound -**** distinctly identified by the union of interface and direction -**** Holds many ILinkSessions bound on the distinctly idenitfied direction/interface - - -** link layer messages -*** link layer message parser -**** parses buffers as bencoded dicts -*** link layer message handler -**** handles each type of link layer message - - -** IHopHandler -*** llarp::PathHopConfig -**** txid, rxid, shared secret at hop -*** llarp::path::Path -**** a built path or a path being built -**** owns a std::vector for each hop's info -*** TransitHop -**** a single hop on a built path -**** has txid, rxid, shared secret, hash of shared secret - - -** pathset -*** path::Builder -**** builds and maintains a set of paths for a common use - - -** routing layer message router -*** routing::IMessageHandler -**** interface for routing layer message processing -**** transit hops implement this if they are an endpoint -**** path::Path implement this always - - -** dht "layer" / rc gossiper -*** TODO rewrite/refactor - -** hidden service data structures -*** IntroSet -**** decrypted plaintext hidden service descriptor -*** EncryptedIntroSet -**** public encrpyted / signed version of IntroSet - - -** service endpoint / outbound context connectivitybackend -*** service::Endpoint -**** backend for sending/recieving packets over the hidden service protocol layer -**** kitchen sink -*** service::SendContext -**** interface type for sending to a resource on the network -*** service::OutboundContext -**** implements SendContext extends path::Builder and path::PathSet -**** for maintaining a pathset that aligns on an introset's intros -~ - -** snode / exit connectivity backend -*** exit::BaseSession -**** extends path::Builder -**** obtains an exit/snode session from the router they are aligning to -*** exit::Endpoint -**** snode/exit side of an exit::Session - -** snapp / exit / mobile / null frontend handlers -*** handlers::TunEndpoint -**** extends service::Endpoint -**** provides tun interface frontend for hidden service backend -*** handlers::ExitEndpoint -**** provides tun interface frontend for exit/snode backend - - -** outbound message dispatcher -*** TODO tom please document these - diff --git a/docs/conf.py.in b/docs/conf.py.in deleted file mode 100644 index e99ba91ec..000000000 --- a/docs/conf.py.in +++ /dev/null @@ -1,189 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- Project information ----------------------------------------------------- - -project = 'lokinet' -copyright = '2020, Jeff Becker' -author = 'Jeff Becker' - -# The short X.Y version -version = '' -# The full version, including alpha/beta/rc tags -release = '' - - - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. - -extensions = ['breathe', 'exhale'] -breathe_projects = { "lokinet": "@CMAKE_CURRENT_BINARY_DIR@/doxyxml/" } -breathe_default_project = "lokinet" -breathe_domain_by_extension = {"h" : "cpp", "hpp": "cpp"} - -exhale_args = { - # These arguments are required - "containmentFolder": "./api", - "rootFileName": "lokinet.rst", - "rootFileTitle": "lokinet internals", - "doxygenStripFromPath": "..", - # Suggested optional arguments - "createTreeView": True, - # TIP: if using the sphinx-bootstrap-theme, you need - # "treeViewIsBootstrap": True, -} - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -source_suffix = ['.rst', '.md'] - -# The master toctree document. -master_doc = 'index' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' -highlight_language = 'c++' -primary_domain = 'cpp' -default_role = 'cpp:any' -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'lokinetdoc' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'lokinet.tex', 'lokinet Documentation', - 'Jeff Becker', 'manual'), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'lokinet', 'lokinet Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'lokinet', 'lokinet Documentation', - author, 'lokinet', 'One line description of project.', - 'Miscellaneous'), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] diff --git a/docs/config.json b/docs/config.json new file mode 100644 index 000000000..a160cbfba --- /dev/null +++ b/docs/config.json @@ -0,0 +1,9 @@ +{ + "baseUrl": "", + "indexInFolders": false, + "linkSuffix": ".md", + "mainPageInRoot": false, + "mainPageName": "index", + "linkLowercase": false, + "foldersToGenerate": ["files", "classes", "namespaces"] +} diff --git a/docs/crypto_v0.txt b/docs/crypto_v0.txt deleted file mode 100644 index 84f234d4d..000000000 --- a/docs/crypto_v0.txt +++ /dev/null @@ -1,39 +0,0 @@ - - -cryptography: - -H(x) is 512 bit blake2b digest of x -HS(x) is 256 bit blake2b digest of x -MD(x, k) is 512 bit blake2b hmac of x with secret value k -MDS(x, k) is 256 bit blake2b hmac of x with secret value k -SE(k, n, x) is chacha20 encrypt data x using symettric key k and nounce n -SD(k, n, x) is chacha20 dectypt data x using symettric key k and nounce n -S(k, x) is sign x with ed25519 using secret key k -EDKG() is generate ec keypair (p, s) public key p (32 bytes), secret key s (64 bytes) -V(k, x, sig) is verify x data using signature sig using public key k -EDDH(a, b) is curve25519 scalar multiplication of a and b -HKE(a, b, x) is hashed key exchange between a and b using a secret key x HS(a + b + EDDH(x, b)) -TKE(a, b, x, n) is a transport shared secret kdf using MDS(n, HKE(a, b, x)) - -when A is client and B is server where n is a 32 bytes shared random - -client computes TKE(A.pk, B.pk, A.sk, n) -server computes TKE(A.pk, B.pk, B.sk, n) - -PDH(a, b, x) is path shared secret generation HS(a + b + EDDH(x, b)) - -PKE(a, b, x, n) is a path shared secret kdf using MDS(n, PDH(a, b, x)) - -given A is the path creator and B is a hop in the path and n is 32 bytes shared random - -A computes PKE(A.pk, B.pk, A.sk, n) as S_a -B computes PKE(A.pk, B.pk, B.sk, n) as S_b - -S_a is equal to S_b - -RAND(n) is n random bytes - -PQKG() is generate a sntrup4591761 key pair (sk, pk) -PQKE_A(pk) is alice generating (x, k) where x is sntrup4591761 ciphertext block and k is the session key -PQKE_B(x, sk) is bob calculating k where x is sntrup4591761 ciphertext block, sk is bob's sntrup4591761 secretkey and k is the session key - diff --git a/docs/dht_v0.txt b/docs/dht_v0.txt deleted file mode 100644 index 868ea3d57..000000000 --- a/docs/dht_v0.txt +++ /dev/null @@ -1,153 +0,0 @@ -DHT messages - -DHT messages can be either wrapped in a LIDM message or sent anonymously over a path inside a DHT routing message - -The distance function is A xor B (traditional kademlia) - -The dht implements both iterative and recursive lookups. - -Recursive lookups forward the request to a node closer if not found. -Iterative lookups return the key and of the DHT node who is closer. - -In the case of iterative FRCM the RC of the closer router is provided. -In the case of iterative FIM the pubkey of a dht node who is closer is provided in the GIM. - -find introduction message (FIM) - -variant 1: find an IS by SA - -{ - A: "F", - R: 0 for iterative and 1 for recurisve, - S: "<32 bytes SA>", - T: transaction_id_uint64, - V: 0 -} - -variant 2: recursively find many IS in a tag - -{ - A: "F", - E: [list, of, excluded, SA], - R: 0 for iterative and 1 for recurisve, - N: "<16 bytes topic tag>", - T: transaction_id_uint64, - V: 0 -} - -exclude adding service addresses in E if present - - -got introduction message (GIM) - -sent in reply to FIM containing the result of the search -sent in reply to PIM to acknoledge the publishing of an IS - -{ - A: "G", - I: [IS, IS, IS, ...], - K: "<32 bytes public key of router who is closer, provided ONLY if FIM.R is 0>", - T: transaction_id_uint64, - V: 0, -} - -The I value MUST NOT contain more than 4 IS. -The I value MUST contain either 1 or 0 IS for PIM and FIM variant 1. - - -publish introduction message (PIM) - -publish one IS to the DHT. - -version 0 uses the SA of the IS as the keyspace location. - -in the future the location will be determined by the dht kdf -which uses a shared random source to obfuscate keyspace location. - - -R is currently set to 0 by the sender. - -{ - A: "I", - I: IS, - R: random_walk_counter, - S: optional member 0 for immediate store otherwise non zero, - T: transaction_id_uint64, - V: 0 -} - -if R is greater than 0, decrement R and forward the request to a random DHT -node without storing the IS. -As of protocol version 0, R is always 0. - -If S is provided store the IS for later lookup unconditionally, then -decrement S by 1 and forward to dht peer who is next closest to -the SA of the IS. If S is greater than 3, don't store the IS and -discard this message. - -acknoledge introduction message (AIM) - -acknoledge the publishing of an introduction - -{ - A: "A", - P: published_to_counter, - T: transaction_id_uint64, - V: 0 -} - -increment P by 1 and forward to requester - - -find router contact message (FRCM) - -find a router by long term RC.k public key - -{ - A: "R", - E: 0 or 1 if exploritory lookup, - I: 0 or 1 if iterative lookup, - K: "<32 byte public key of router>", - T: transaction_id_uint64, - V: 0 -} - -if E is provided and non zero then return E dht nodes that are closest to K -if I is provided and non zero then this request is considered an iterative lookup -during an iterative lookup the response's GRCM.K is set to the pubkey of the router closer in key space. -during a recursive lookup the request is forwarded to a router who is closer in -keyspace to K. -If we have no peers that are closer to K and we don't have the RC known locally -we reply with a GRCM whose R value is emtpy. -In any case if we have a locally known RC with pubkey equal to K reply with -a GRCM with 1 RC in the R value corrisponding to that locally known RC. - -got router contact message (GRCM) - -R is a list containing a single RC if found or is an empty list if not found -sent in reply to FRCM only - -{ - A: "S", - K: "<32 bytes public identity key of router closer, provided ONLY if FRCM.I is 1>", - R: [RC], - T: transaction_id_uint64, - V: 0 -} - -in response to an exploritory router lookup, where FRCM.E is provided and non zero. - -{ - A: "S", - N: [list, of, router, publickeys, near, K], - T: transaction_id_uint64, - V: 0 -} - -sent in reply to a dht request to indicate transaction timeout - -{ - A: "T", - T: transaction_id_uint64, - V: 0 -} \ No newline at end of file diff --git a/docs/dht_v1.txt b/docs/dht_v1.txt deleted file mode 100644 index 5832caa7d..000000000 --- a/docs/dht_v1.txt +++ /dev/null @@ -1,94 +0,0 @@ -llarp's dht is a recusrive kademlia dht with optional request proxying via paths for requester anonymization. - -dht is separated into 2 different networks, one for router contacts, one for introsets. - - - - -format for consesus propagation messages: - -keys: A, H, K, N, O, T, U, V - -concensus request messages - -requester requests current table hash, H,N,O is set to zeros if not known - -C -> S - -{ - A: "C", - H: "<32 byte last hash of consensus table>", - N: uint64_number_of_entries_to_request, - O: uint64_offset_in_table, - T: uint64_txid, - V: [] -} - - -when H or N is set to zero from the requester, they are requesting the current consensus table's hash - -consensus response message is as follows for a zero H or N value - -S -> C - -{ - A: "C", - H: "<32 byte hash of current consensus table>", - N: uint64_number_of_entries_in_table, - T: uint64_txid, - U: uint64_ms_next_update_required, - V: [proto, major, minor, patch] -} - -requester requests a part of the current table for hash H - -N must be less than or equal to 512 - -C -> S - -{ - A: "C", - H: "<32 bytes current consensus table hash>", - N: 256, - O: 512, - T: uint64_txid, - V: [] -} - -consensus response message for routers 512 to 512 + 256 - -S -> C - -{ - A: "C", - H: "<32 bytes current concensus table hash>", - K: [list, of, N, pubkeys, from, request, starting, at, position, O], - T: uint64_txid, - V: [proto, major, minor, patch] -} - -consensus table is a concatination of all public keys in lexigraphical order. - -the hash function in use is 256 bit blake2 - - - -gossip RC message - -broadcast style RC publish message. sent to all peers infrequently. - -it is really an unwarrented GRCM, propagate to all peers. - -{ - A: "S", - R: [RC], - T: 0, - V: proto -} - -replays are dropped using a decaying hashset or decaying bloom filter. - - - -the introset dht has 3 message: GetIntroSet Message (GIM), PutIntroSet Message (PIM), FoundIntroSet Message (FIM) - diff --git a/docs/dns-overview.md b/docs/dns-overview.md new file mode 100644 index 000000000..d3fcab22a --- /dev/null +++ b/docs/dns-overview.md @@ -0,0 +1,36 @@ +# DNS in Lokinet + +Lokinet uses dns are its primary interface for resolving, mapping and querying resources inside of lokinet. +This was done not because DNS is *good* protocol, but because there is almost no relevent userland applications that are incapable of interacting with DNS, across every platform. +Using DNS in lokinet allows for the most zero config setup possible with the current set of standard protocols. + +Lokinet provides 2 internal gtld, `.loki` and `.snode` + +## .snode + +The `.snode` gtld is used to address a lokinet router in the form of `.snode`. +Traffic bound to a `.snode` tld will have its source authenticatable only if it originates from another valid lokinet router. +Clients can also send traffic to and from addresses mapped to `.snode` addresses, but the source address on the service node side is ephemeral. +In both cases, ip traffic to addresses mapped to `.snode` addresses will have the destination ip rewritten by the lokinet router to be its local interface ip, this ensures traffic stays on the lokinet router' interface for snode traffic and preventing usage as an exit node. + +## .loki + +The `.loki` gtld is used to address anonymously published routes to lokinet clients on the network. + + + +## What RR are provided? + +All `.loki` domains by default have the following dns rr synthesized by lokinet: + +* `A` record for initiating address mapping +* `MX` record pointing to the synthesizesd `A` record +* free wildcard entries for all of the above. + +Wildard entries are currently only pointing + +All `.snode` domains have by defult just an `A` record for initiating address mapping. + +Additionally both `.loki` and `.snode` can optionally provide multiple `SRV` records to advertise existence of services on or off of the name. + + diff --git a/docs/doxygen.md b/docs/doxygen.md new file mode 100644 index 000000000..f520f02b8 --- /dev/null +++ b/docs/doxygen.md @@ -0,0 +1,27 @@ + +# Doxygen + +building doxygen docs requires the following: + +* cmake +* doxygen +* sphinx-build +* sphinx readthedocs theme +* breathe +* exhale + +install packages: + + $ sudo apt install make cmake doxygen python3-sphinx python3-sphinx-rtd-theme python3-breathe python3-pip + $ pip3 install --user exhale + +build docs: + + $ mkdir -p build-docs + $ cd build-docs + $ cmake .. && make doc + +serve built docs via http, will be served at http://127.0.0.1:8000/ + + $ python3 -m http.server -d docs/html + diff --git a/docs/fix-markdown.sh b/docs/fix-markdown.sh new file mode 100644 index 000000000..23f35a31d --- /dev/null +++ b/docs/fix-markdown.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# apply markdown file content quarks + + +# rewrite br tags +sed -i 's|
|
|g' $@ diff --git a/docs/high-level-overview.md b/docs/high-level-overview.md new file mode 100644 index 000000000..1d2baea84 --- /dev/null +++ b/docs/high-level-overview.md @@ -0,0 +1,19 @@ +## onion routing overview + + + + + + + +## endpoint zmq api + + + +## DNS + + + + + + diff --git a/docs/high-level.txt b/docs/high-level.txt deleted file mode 100644 index a7ae94076..000000000 --- a/docs/high-level.txt +++ /dev/null @@ -1,144 +0,0 @@ -LLARP - Low Latency Anon Routing Protocol - - TL;DR edition: an onion router with a tun interface for transporting ip packets - anonymously between you and the internet and internally inside itself to other users. - - - This document describes the high level outline of LLARP, specific all the - project's goals, non-goals and network architecture from a bird's eye view. - - - Preface: - - - Working on I2P has been a really big learning experience for everyone involved. - After much deliberation I have decided to start making a "next generation" onion - routing protocol. Specifically LLARP is (currently) a research project - to explore the question: - - - "What if I2P was made in the current year (2018)? What would be different?" - - - Project Non Goals: - - - This project does not attempt to solve traffic shape correlation or active nation - state sponsored network attacks. The former is an inherit property of low latency - computer networks that I personally do not think is possible to properly fully - "solve". The latter is a threat that lies outside the scope of what the current - toolset that is available to me at the moment provides. - - - This project does not attempt to be a magical application level cure-all for - application or end user security. At the end of the day that is a problem that - exists between chair and keyboard. - - - The Single Project Goal: - - - LLARP is a protocol suite meant to anonymize IP by providing an anonymous - network level (IPv4/IPv6) tunnel broker for both "hidden services" and - communication back to "the clearnet" (the normal internet). Both hidden service - and clearnet communication MUST permit both outbound and inbound traffic on the - network level without any NAT (except for IPv4 in which NAT is permitted due to - lack of address availability). - - - In short We want to permit both anonymous exit and entry network level traffic - between LLARP enabled networks and the internet. - - - Rationale for starting over: - - - Despite Tor Project's best efforts to popularize Tor use, Tor2Web seems to be - widely popular for people who do not wish to opt into the ecosystem. My proposed - solution would be to permit inbound traffic from "exit nodes" in addition to - allowing outbound exit traffic. I have no ideas on how this could be done with - the existing protocols in Tor or if it is possible or advisable to attempt such - as I am not familiar with their ecosystem. - - - I2P could have been used as a medium for encrypted anonymous IP transit but the - current network has issues with latency and throughput. Rebasing I2P atop more - modern cryptography has been going on internally inside I2P for at least 5 years - with less progress than desired. Like some before me, I have concluded that it - would be faster to redo the whole stack "the right way" than to wait for I2P to - finish rebasing. That being said, nothing is preventing I2P from be used for - encrypted anonymous IP transit traffic in a future where I2P finishes their - protocol migrations, I just don't want to wait. - - - In short, I want to take the "best parts" from Tor and I2P and make a new - protocol suite. - - - For both Tor and I2P I have 2 categories for the attributes they have. - - - the good - the bad and the ugly - - - The good (I2P): - - - I2P aims to provide an anonymous unspoofable load balanced network layer. - - - I want this feature. - - - I2P is trust agile, it does not have any hard coded trusts in its network - architecture. Even network boostrap can be done from a single router if the user - desires to (albeit this is currently ill advised). - - - I want this feature. - - - The good (Tor): - - - Tor embraces the reality of the current internet infrastructure by having a - client/server architecture. This allows very low barriers of entry in using the - Tor network and a higher barrier of entry for contributing routing - infrastructure. This promotes a healthy network shape of high capacity servers - serving low capacity clients that "dangle off of the side" of the network. - - - I want this feature. - - - The bad and the ugly (I2P): - - - Bad: I2P uses old cryptography, specially 2048 bit ElGamal using non standard primes. - The use of ElGamal is so pervasive throughout the I2P protocol stack that it - exists at every level of it. Removing it is a massive task that is taking a long - LONG time. - - - I don't want this feature. - - - Ugly: I2P cannot currently mitigate most sybil attacks with their current network - architecture. Recently I2P has added some blocklist solutions signed by release - signers but this probably won't scale in the event of a "big" attack. In - addition I2P isn't staffed for such attacks either. - - - This is a hard problem to solve that the Loki network may be able to help - with by creating a financial barrier to running multiple a relays. - - - - The bad and the ugly (Tor): - - - Bad: Tor is strictly TCP oriented. - I don't want this feature. - - diff --git a/docs/ideal-ux.md b/docs/ideal-ux.md new file mode 100644 index 000000000..7cda8e29a --- /dev/null +++ b/docs/ideal-ux.md @@ -0,0 +1,42 @@ +# What does Lokinet actually do? + +Lokinet is an onion routed authenticated unicast IP network. It exposes an IP tunnel to the user and provides a dns resolver that maps `.loki` and `.snode` gtld onto a user defined ip range. + +Lokinet allows users to tunnel arbitrary ip ranges to go to a `.loki` address to act as a tunnel broker via another network accessible via another lokinet client. This is commonly known as an "exit node" but the way lokinet does this is much more generic so that term is not very accurate given what it actually does. + +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? + +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) + +configure exit traffic provider if you want to tunnel ip traffic via lokinet, by default this is off as we cannot provide a sane defualt that makes everyone happy. to enable an exit node, see lokinet.ini `[network]` section, add multiple `exit-node=exitaddrgoeshere.loki` lines for each endpoint you want to use for exit traffic. each `exit-node` entry will be used to randomly stripe across per IP you are sending to. + +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 + +Yes and that is the best way to run it in practice. + +## The "easy" way + +We have a community maintained solution for ARM SBCs like rasperry pi: https://github.com/necro-nemesis/LabyrinthAP + +## The "fun" way (DIY) + +It is quite nice to DIY. if you choose to do so there is some assembly required: + +on the lokinet side, make sure that the... + +* ip ranges for `.loki` and `.snode` are statically set (see lokinet.ini `[network]` section `ifaddr=` option) +* network interace used by lokinet is statically set (see lokinet.ini `[network]` section `ifname=` option) +* dns socket is bound to an address the soho router's dns resolver can talk to, see `[dns]` section `bind=` option) + +on the soho router side: + +* route queries for `.loki` and `.snode` gtld to go to lokinet dns on soho router's dns resolver +* use dhcp options to set dns to use the soho router's dns resolver +* make sure that the ip ranges for lokinet are reachable via the LAN interface +* if you are tunneling over an exit ensure that LAN traffic will only forward to go over the lokinet vpn interface diff --git a/docs/index.md.in b/docs/index.md.in new file mode 100644 index 000000000..d7fe69d00 --- /dev/null +++ b/docs/index.md.in @@ -0,0 +1,7 @@ +# Lokinet @lokinet_VERSION@ (git rev: @GIT_VERSION@) + +summary goes here + +## Overview + +[code internals](index_namespaces.md) diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index a58c3ac24..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,8 +0,0 @@ - -Welcome to Lokinet Internals -============================ - -.. toctree:: - :maxdepth: 2 - - api/lokinet diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 000000000..17cf9da08 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,174 @@ +# Installing + +If you are simply looking to install Lokinet and don't want to compile it yourself we provide several options for platforms to run on: + +Tier 1: + +* [Linux](#linux-install) +* [Windows](#windows-install) +* [MacOS](#macos-install) + +Tier 2: + +* [FreeBSD](#freebsd-install) + +Currently Unsupported Platforms: (maintainers welcome) + +* [Android](#apk-install) +* Apple iPhone +* Homebrew +* \[Insert Flavor of the Month windows package manager here\] + + +## Official Builds + +### Windows / MacOS + +You can get the latest stable release for lokinet on windows or macos from https://lokinet.org/ or check the [releases page on github](https://github.com/oxen-io/lokinet/releases). + +### Linux + +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 + +You can install debian packages from `deb.oxen.io` by adding the apt repo to your system. + + $ sudo curl -so /etc/apt/trusted.gpg.d/oxen.gpg https://deb.oxen.io/pub.gpg + $ echo "deb https://deb.oxen.io $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/oxen.list + +This apt repo is also available via lokinet at `http://deb.loki` + +Once added you can install lokinet with: + + $ sudo apt update + $ sudo apt install lokinet + +When running from debian package the following steps are not needed as it is already running and ready to use. You can stop/start/restart it using `systemctl start lokinet`, `systemctl stop lokinet`, etc. + +#### RPM + +We also provide an RPM repo, see `rpm.oxen.io`, also available on lokinet at `rpm.loki` + +## Bleeding Edge dev builds + +automated builds from dev branches for the brave or impatient can be found from our CI pipeline [here](https://oxen.rocks/oxen-io/lokinet/). (warning: these nightly builds may or may not consume your first born child.) + +## Building + +Build requirements: + +* Git +* CMake +* C++ 17 capable C++ compiler +* libuv >= 1.27.0 +* libsodium >= 1.0.18 +* libssl (for lokinet-bootstrap) +* libcurl (for lokinet-bootstrap) +* libunbound +* libzmq +* cppzmq + +### Linux Compile + +If you want to build from source: + + $ sudo apt install build-essential cmake git libcap-dev pkg-config automake libtool libuv1-dev libsodium-dev libzmq3-dev libcurl4-openssl-dev libevent-dev nettle-dev libunbound-dev libssl-dev nlohmann-json3-dev + $ git clone --recursive https://github.com/oxen-io/lokinet + $ cd lokinet + $ mkdir build + $ cd build + $ cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF + $ make -j$(nproc) + $ sudo make install + +set up the initial configs: + + $ lokinet -g + $ lokinet-bootstrap + +after you create default config, run it: + + $ lokinet + +This requires the binary to have the proper capabilities which is usually set by `make install` on the binary. If you have errors regarding permissions to open a new interface this can be resolved using: + + $ sudo setcap cap_net_admin,cap_net_bind_service=+eip /usr/local/bin/lokinet + + +#### Arch Linux + +Due to [circumstances beyond our control](https://github.com/oxen-io/lokinet/discussions/1823) a working `PKGBUILD` can be found [here](https://raw.githubusercontent.com/oxen-io/lokinet/makepkg/contrib/archlinux/PKGBUILD). + +#### Cross Compile For Linux + +current cross targets: + +* aarch64-linux-gnu +* arm-linux-gnueabihf +* mips-linux-gnu +* mips64-linux-gnuabi64 +* mipsel-linux-gnu +* powerpc64le-linux-gnu + +install the toolchain (this one is for `aarch64-linux-gnu`, you can provide your own toolchain if you want) + + $ sudo apt install g{cc,++}-aarch64-linux-gnu + +build 1 or many cross targets: + + $ ./contrib/cross.sh arch_1 arch_2 ... arch_n + +### Building For Windows + +windows builds are cross compiled from debian/ubuntu linux + +additional build requirements: + +* nsis +* cpack +* rsvg-convert (`librsvg2-bin` package on Debian/Ubuntu) + +setup: + + $ sudo apt install build-essential cmake git pkg-config mingw-w64 nsis cpack automake libtool + $ sudo update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix + $ sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix + +building: + + $ git clone --recursive https://github.com/oxen-io/lokinet + $ cd lokinet + $ ./contrib/windows.sh + +### Compiling for MacOS + +Source code compilation of Lokinet by end users is not supported or permitted by apple on their platforms, see [this](../contrib/macos/README.txt) for more information. + +If you find this disagreeable consider using a platform that permits compiling from source. + +### FreeBSD + +Currently has no VPN Platform code, see issue `#1513` + +build: + + $ pkg install cmake git pkgconf + $ git clone --recursive https://github.com/oxen-io/lokinet + $ cd lokinet + $ mkdir build + $ cd build + $ cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON -DBUILD_STATIC_DEPS=ON .. + $ make + +install (root): + + # make install + +### Android + +We have an Android APK for lokinet VPN via android VPN API. + +Coming to F-Droid whenever that happens. [[issue]](https://github.com/oxen-io/lokinet-flutter-app/issues/8) + +* [source code](https://github.com/oxen-io/lokinet-flutter-app) diff --git a/docs/liblokinet-dev-guide.md b/docs/liblokinet-dev-guide.md new file mode 100644 index 000000000..f89f8e9f2 --- /dev/null +++ b/docs/liblokinet-dev-guide.md @@ -0,0 +1,11 @@ +# Embedding Lokinet into an existing application + +When all else fails and you want to deploy lokinet inside your app without the OS caring you can embed an entire lokinet client and a few of the upper network layers into your application manually. + +## Why you should avoid this route + +`// TODO: this` + +## When you cannot avoid this route, how do i use it? + +`// TODO: this` diff --git a/docs/linux-setcap-readme.txt b/docs/linux-setcap-readme.txt deleted file mode 100644 index 01b94b3c8..000000000 --- a/docs/linux-setcap-readme.txt +++ /dev/null @@ -1,16 +0,0 @@ -Lokinet needs certain capabilities to run to set up a virtual network interface and provide a DNS server. The preferred approach to using this is through the linux capabilities mechanism, which allows assigning limited capabilities without needing to run the entire process as root. - -There are two main ways to do this: - -1. If you are running lokinet via an init system such as systemd, you can specify the capabilities in the service file by adding: - -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE - - into the [Service] section of the systemd service file. This will assign the necessary permissions when running the process and allow lokinet to work while running as a non-root user. - -2. You can set the capabilities on the binary by using the setcap program (if not available you may need to install libcap2-bin on Debian/Ubuntu-based systems) and running: - -setcap cap_net_admin,cap_net_bind_service=+eip lokinet - - This grants the permissions whenever the lokinet binary is executed. diff --git a/docs/llarp.png b/docs/llarp.png deleted file mode 100644 index 781db1124..000000000 Binary files a/docs/llarp.png and /dev/null differ diff --git a/docs/llarp_structure.dot b/docs/llarp_structure.dot deleted file mode 100644 index 1052f0998..000000000 --- a/docs/llarp_structure.dot +++ /dev/null @@ -1,102 +0,0 @@ -digraph { - constants -> util; - - crypto -> constants; - crypto -> llarp; - crypto -> util; - - dht -> crypto; - dht -> messages; - dht -> llarp; - dht -> path; - dht -> routing; - dht -> service; - dht -> util; - - dns -> crypto; - dns -> ev; - dns -> handlers; - dns -> llarp; - dns -> net; - dns -> service; - dns -> util; - - ev -> net; - ev -> util; - - exit -> crypto; - exit -> handlers; - exit -> messages; - exit -> net; - exit -> path; - exit -> routing; - exit -> util; - - handlers -> dns; - handlers -> ev; - handlers -> exit; - handlers -> net; - handlers -> service; - handlers -> util; - - link -> constants; - link -> crypto; - link -> ev; - link -> messages; - link -> net; - link -> util; - - messages -> crypto; - messages -> dht; - messages -> exit; - messages -> link; - messages -> llarp; - messages -> path; - messages -> routing; - messages -> service; - messages -> util; - - net -> crypto; - net -> util; - - path -> crypto; - path -> dht; - path -> llarp; - path -> messages; - path -> routing; - path -> service; - path -> util; - - routing -> llarp; - routing -> messages; - routing -> path; - routing -> util; - - service -> crypto; - service -> dht; - service -> ev; - service -> exit; - service -> handlers; - service -> messages; - service -> net; - service -> path; - service -> routing; - service -> util; - - util -> constants; - - llarp -> constants; - llarp -> crypto; - llarp -> dht; - llarp -> dns; - llarp -> ev; - llarp -> exit; - llarp -> handlers; - llarp -> link; - llarp -> messages; - llarp -> net; - llarp -> path; - llarp -> routing; - llarp -> service; - llarp -> util; -} diff --git a/docs/logo.svg b/docs/logo.svg deleted file mode 100644 index 423f32691..000000000 --- a/docs/logo.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/lokinet_admin_v0.txt b/docs/lokinet_admin_v0.txt deleted file mode 100644 index 6ec5f2a5d..000000000 --- a/docs/lokinet_admin_v0.txt +++ /dev/null @@ -1,70 +0,0 @@ -LokiNET admin api - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this -document are to be interpreted as described in RFC 2119 [RFC2119]. - ------- - -the admin api currently uses jsonrpc 2.0 over http - -the methods currently provided are: - -llarp.nodedb.rc.getbykey - - get rc by public identity key - - required parameters: - - key: 32 bytes public identity key - - returns: - - a list of RCs (see protocol v0 spec) that have this public identity key - usually 0 or 1 RCs - - -llarp.nodedb.rc.getbycidr - - get a list of RCs in an address range - - required parameters: - - cidr: ipv6 network cidr string, i.e. "::ffff.21.0.0.0/8" or "fc00::/7" - limit: integer max number of items to fetch, zero or positive integer, - if zero no limit. - - returns: - - a list of 0 to limit RCs that advertise themselves as being reachble via an - address in the given CIDR. - - -llarp.admin.sys.uptime (authentication required) - - required paramters: - - (none) - - returns: - - an integer milliseconds since unix epoch we've been online - -llarp.admin.link.neighboors - - get a list of connected service nodes on all links - - required parameters: - - (none) - - returns: - - list of 0 to N dicts in the following format: - - { - "connected" : uint64_milliseconds_timestamp_connected_at - "ident" : "<64 hex encoded public identity key>", - "laddr" : "local address", - "raddr" : "remote address" - } diff --git a/docs/macos-signing.txt b/docs/macos-signing.txt index 9e6f2ba07..9e323da8e 100644 --- a/docs/macos-signing.txt +++ b/docs/macos-signing.txt @@ -1,73 +1,123 @@ -Codesigning and notarization on macOS +If you are reading this to try to build Lokinet for yourself for an Apple operating system and +simultaneously care about open source, privacy, or freedom then you, my friend, are a walking +contradiction: you are trying to get Lokinet to work on a platform that actively despises open +source, privacy, and freedom. Even Windows is a better choice in all of these categories than +Apple. -This is painful. Thankfully most of the pain is now in CMake and a python script. +This directory contains the magical incantations and random voodoo symbols needed to coax an Apple +build. There's no reason builds have to be this stupid, except that Apple wants to funnel everyone +into the no-CI, no-help, undocumented, non-toy-apps-need-not-apply modern Apple culture. -To build, codesign, and notarized and installer package, CMake needs to be invoked with: +This is disgusting. - cd build - rm -rf * # optional but recommended - cmake .. -DBUILD_PACKAGE=ON -DDOWNLOAD_SODIUM=ON -DMACOS_SIGN_APP=ABC123... -DMACOS_SIGN_PKG=DEF456... +But it gets worse. -where the ABC123... key is a "Developer ID Installer" key and PKG key is a "Developer ID -Application" key. You have to go through a bunch of pain, pay Apple money, and then read a bunch of -poorly written documentation that doesn't help very much to create these and get them working. But once you have them -set up in Keychain, you should be able to list your keys with: +The following two files, in particular, are the very worst manifestations of this already toxic +Apple cancer: they are required for proper permissions to run on macOS, are undocumented, and can +only be regenerated through the entirely closed source Apple Developer backend, for which you have +to pay money first to get a team account (a personal account will not work), and they lock the +resulting binaries to only run on individually selected Apple computers selected at the time the +profile is provisioned (with no ability to allow it to run anywhere). - security find-identity -v + lokinet.dev.provisionprofile + lokinet-extension.dev.provisionprofile -and you should see (at least) one "Developer ID Installer: ..." and one "Developer ID Application: -...". You need both for reasons that only Apple knows. The former is used to sign the installer -.pkg, and the latter is used to sign everything *inside* the .pkg, and you can't use the same key -for both because Apple designed code signing by marketing committee rather than ask any actual -competent software developers how code signing should work. +This is actively hostile to open source development, but that is nothing new for Apple. -Either way, these two values can be specified either by hex value or description string that -`security find-identity -v` spits out. +There are also release provisioning profiles -You also need to set up the notarization parameters; these can either be specified directly on the -cmake command line by adding: + lokinet.release.provisionprofile + lokinet-extension.release.provisionprofile - -DMACOS_NOTARIZE_ASC=XYZ123 -DMACOS_NOTARIZE_USER=me@example.com -DMACOS_NOTARIZE_PASS=@keychain:codesigning-password +These ones allow distribution of the app, but only if notarized, and again require notarization plus +signing by a (paid) Apple developer account. -or, more simply, by putting them inside a `~/.notarization.cmake` file that will be included if it -exists (and the MACOS_SIGN_* variables are set) -- see below. +In order to make things work, you'll have to replace these provisioning profiles with your own +(after paying Apple for the privilege of developing on their platform, of course) and change all the +team/application/bundle IDs to reference your own team, matching the provisioning profiles. The dev +provisioning profiles must be a "macOS Development" provisioning profile, and must include the +signing keys and the authorized devices on which you want to run it. (The profiles bundled in this +repository contains the lokinet team's "Apple Development" keys associated with the Oxen project, +and mac dev boxes. This is *useless* for anyone else). -These three values here are: +For release builds, you still need a provisioning profile, but it must be a "Distribution: Developer +ID" provisioning profile, and are tied to a (paid) Developer ID. The ones in the repository are +attached to the Oxen Project Developer ID and are useless to anyone else. -MACOS_NOTARIZE_ASC: +Once you have that in place, you need to build and sign the package using a certificate matching +your provisioning profile before your Apple system will allow it to run. (That's right, your $2000 +box won't let you run programs you build from source on it unless you also subscribe to a $100/year +Apple developer account). -Organization-specific unique value; this is printed inside (brackets) when you run: `security -find-identity -v`: +Okay, so now that you have paid Apple more money for the privilege of using your own computer, +here's how you make a signed lokinet app: - 1) 1C75DDBF884DEF3D5927C3F29BB7FC5ADAE2E1B3 "Apple Development: me@example.com (ABC123XYZ9)" +1) Decide which type of build you are doing: a lokinet system extension, or an app extension. The + former must be signed and notarized and will only work when placed in the /Applications folder, + but will not work as a dev build and cannot be distributed outside the Mac App Store. The latter + is usable as a dev build, but still requires a signature and Apple-provided provisioningprofile + listing the limited number of devices on which it is allowed to run. -MACOS_NOTARIZE_USER: + For system extension builds you want to add the -DMACOS_SYSTEM_EXTENSION=ON flag to cmake. -Your Apple Developer login. +2) Figure out the certificate to use for signing and make sure you have it installed. For a + distributable system extension build you need a "Developer ID Application" key and certificate, + issued by your paid developer.apple.com account. For dev builds you need a "Apple Development" + certificate. -MACOS_NOTARIZE_PASS: + In most cases you don't need to specify these; the default cmake script will figure them out. + (If it can't, e.g. because you have multiple of the right type installed, it will error with the + keys it found). -This should be an app-specific password created for signing on the Apple Developer website. You -*can* specify it directly, but it is much better to use the magic `@keychain:blah` value, where -'blah' is a password name recorded in Keychain. To get that in place you run: + To be explicit, use `security find-identity -v` to list your keys, then list the key identity + with -DCODESIGN_ID=..... - export HISTFILE='' # for bash: you don't want to store this in your history - xcrun altool --store-password-in-keychain-item "NOTARIZE_PASSWORD" -u "user" -p "password" +3) If you are doing a system extension build you will need to provide notarization login information by adding: -where NOTARIZE_PASSWORD is just some name for the password (I called it 'blah' or -'codesigning-password' above), and the "user" and "password" are replaced with your actual Apple -Developer account device-specific login credentials. + -DMACOS_NOTARIZE_ASC=XYZ123 -DMACOS_NOTARIZE_USER=me@example.com -DMACOS_NOTARIZE_PASS=@keychain:codesigning-password -Optionally, put these last three inside a `~/.notarization.cmake` file: + a) The first value (XYZ123) needs to be the organization-specific unique value, and is printed in + brackets in the certificate description. For example: - set(MACOS_NOTARIZE_USER "jagerman@jagerman.com") - set(MACOS_NOTARIZE_PASS "@keychain:codesigning-password") - set(MACOS_NOTARIZE_ASC "SUQ8J2PCT7") + 15095CD1E6AF441ABC69BDC52EE186A18200A49F "Developer ID Application: Some Developer (ABC123XYZ9)" -Then, finally, you can build the package from the build directory with: + would require ABC123XYZ9 for this field. - make package -j4 # or whatever -j makes you happy - make notarize + b) The USER field is your Apple Developer login e-mail address. -The former builds and signs the package, the latter submits it for notarization. This can take a -few minutes; the script polls Apple's server until it is finished passing or failing notarization. + c) The PASS field is a keychain reference holding your "Application-Specific Password". To set + up such a password for your account, consult Apple documentation. Once you have it, load it + into your keychain via: + + export HISTFILE='' # Don't want to store this in the shell history + xcrun altool --store-password-in-keychain-item "codesigning-password" -u "user" -p "password" + + You can change "codesigning-password" to whatever you want (just make sure it agrees with the + -DMACOS_NOTARIZE_PASS option you build with). "user" and "password" should be your developer + account device-specific login credentials provided by Apple. + + To make your life easier, stash these settings into a `~/.notarization.cmake` file inside your + home directory; if you have not specified them in the build, and this file exists, lokinet's + cmake will load it: + + set(MACOS_NOTARIZE_USER "me@example.com") + set(MACOS_NOTARIZE_PASS "@keychain:codesigning-password") + set(MACOS_NOTARIZE_ASC "ABC123XYZ9") + +4) Build and sign the package; there is a script `contrib/mac.sh` that can help (extra cmake options + you need can be appended to the end), or you can build yourself in a build directory. See the + script for the other cmake options that are typically needed. Note that `-G Ninja` (as well as a + working ninja builder) are required. + + If you get an error `errSecInternalComponent` this is Apple's highly descriptive way of telling + you that you need to unlock your keychain, which you can do by running `security unlock`. + + If doing it yourself, `ninja sign` will build and then sign the app. + + If you need to also notarize (e.g. for a system extension build) run `./notarize.py` from the + build directory (or alternatively `ninja notarize`, but the former gives you status output while + it runs). + +5) Packaging the app: you want to use `-DBUILD_PACKAGE=ON` when configuring with cmake and then, + once all signing and notarization is complete, run `cpack` which will give you a .dmg and a .zip + containing the release. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 000000000..4ba3d76cd --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,5 @@ +site_name: Lokinet +theme: + name: 'readthedocs' +docs_dir: markdown +site_dir: html diff --git a/docs/net-comparisons.md b/docs/net-comparisons.md new file mode 100644 index 000000000..65264cd37 --- /dev/null +++ b/docs/net-comparisons.md @@ -0,0 +1,35 @@ +# 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. +Lokinet does not provide a web browser at this time because that is not a small task to do at all, and an even larger task to do in a way that is secure, robust and private. Community Contribuitions Welcomed. + +## Tor/Onion Services + +While Tor Browser is the main user facing product made by Tor Project, the main powerhouse is Tor itself. Tor provides a way to anonymize tcp connections made by an initiator and optionally additionally the recipient, when using a .onion address. Lokinet provides a similar feature set but can carry anything that can be encapsulated in an IP packet (currently only unicast traffic). + +The Lokinet UX differs greatly from that of Tor. By default we do not provide exit connectivity. Because each user's threat model greatly varies in scope and breadth, there exists no one size fits all way to do exit connectivity. Users obtain their exit node information out-of-band at the moment. In the future we want to add decentralized network wide service discovery not limited to just exit providers, but this is currently unimplemented. We think that by being hands-off with respect to exit node requirements a far more diverse set of exit nodes can exist. In addition to having totally open unrestrcited exits, there is merit to permitting "specialized" exit providers that are allowed to do excessive filtering or geo blocking for security theatre checkbox compliance. + +Lokinet additionally encourages the manual selection and pinning of edge connections to fit each user's threat model. + +## I2P + +Integrating applications to utilize i2p's network layer is painful and greatly stunts mainstream adoption. +Lokinet takes the inverse approach of i2p: make app integration in lokinet should require zero custom shims or modifications to code to make it work. + +## DVPNs / Commercial VPN Proxies + +One Hop VPNs can see your real IP and all of the traffic you tunnel over them. They are able to turn your data over to authorities even if they claim to not log. + +Lokinet can see only 1 of these 2 things, but NEVER both: + +* Encrypted data coming from your real IP going to the first hop Lokinet Router forwarded to another Lokinet Router. +* A lokinet exit sees traffic coming from a `.loki` address but has no idea what the real IP is for it. + +One Hop Commericial VPN Tunnels are no log by **policy**. You just have to trust that they are telling the truth. + +Lokinet is no log by **design** it doesn't have a choice in this matter because the technology greatly hampers efforts to do so. + +Any Lokinet Client can be an exit if they want to and it requires no service node stakes. Exits are able to charge users for exiting to the internet, tooling for orechestrating this is in development. diff --git a/docs/old-wire-protocol.txt b/docs/old-wire-protocol.txt deleted file mode 100644 index 30f07659c..000000000 --- a/docs/old-wire-protocol.txt +++ /dev/null @@ -1,90 +0,0 @@ -Wire Protocol (version ½) - - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this -document are to be interpreted as described in RFC 2119 [RFC2119]. - -LLARP supports by default an authenticated and framed transport over UTP [1] - -Handshake: - -Alice establishes a UTP "connection" with Bob. - -Alice sends a LIM a_L encrpyted with the initial b_K key - -if Bob accepts Alice's router, Bob replies with a LIM b_L encrpyted with the -b_K key. - -next the session keys are generated via: - -a_h = HS(a_K + a_L.n) -b_h = HS(b_K + b_L.n) -a_K = TKE(A.p, B_a.e, sk, a_h) -b_K = TKE(A.p, B_a.e, sk, b_h) - -A.tx_K = b_K -A.rx_K = a_K -B.tx_K = a_K -B.rx_K = B_K - -the initial value of a_K is HS(A.k) and b_K is HS(B.k) - -608 byte fragments are sent over UTP in an ordered fashion. - -The each fragment F has the following structure: - -[ 32 bytes blake2 keyed hash of the following 576 bytes (h)] -[ 32 bytes random nonce (n)] -[ 544 bytes encrypted payload (p)] - -the recipiant verifies F.h == MDS(F.n + F.p, rx_K) and the UTP session -is reset if verification fails. - -the decrypted payload P has the following structure: - -[ 24 bytes random (A) ] -[ big endian unsigned 32 bit message id (I) ] -[ big endian unsigned 16 bit fragment length (N) ] -[ big endian unsigned 16 bit fragment remaining bytes (R) ] -[ N bytes of plaintext payload (X) ] -[ trailing bytes discarded ] - -link layer messages fragmented and delievered in any order the sender chooses. - -recipaint ensures a buffer for message number P.I exists, allocating one if it -does not exist. - -recipiant appends P.X to the end of the buffer for message P.I - -if P.R is zero then message number P.I is completed and processed as a link -layer messages. otherwise the recipiant expects P.R additional bytes. -P.R's value MUST decrease by P.N in the next fragment sent. - -message size MUST NOT exceed 8192 bytes. - -if a message is not received in 2 seconds it is discarded and any further -fragments for the message are also discarded. - -P.I MUST have the initial value 0 -P.I MUST be incremeneted by 1 for each new messsage transmitted -P.I MAY wrap around back to 0 - - -after every fragment F the session key K is mutated via: - -K = HS(K + P.A) - -Periodically the connection initiator MUST renegotiate the session key by -sending a LIM after L.p milliseconds have elapsed. - -If the local RC changes while a connection is established they MUST -renegotioate the session keys by sending a LIM to ensure the new RC is sent. - - -references: - -[1] http://www.bittorrent.org/beps/bep_0029.html - - - diff --git a/docs/project-structure.md b/docs/project-structure.md new file mode 100644 index 000000000..7c06c70f8 --- /dev/null +++ b/docs/project-structure.md @@ -0,0 +1,110 @@ +# 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.cpp`: lokinet daemon executable +* `lokinet.swift`: macos sysex/appex executable +* `lokinet-vpn.cpp`: lokinet rpc tool for controlling exit node usage +* `lokinet-bootstrap.cpp`: legacy util for windows, downloads a bootstrap file via https + + +## 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)` + +* `/llarp`: contains a few straggling compilation units +* `/llarp/android`: android platform compat shims +* `/llarp/apple`: all apple platform specific code +* `/llarp/config`: configuration structs, generation/parsing/validating of config files +* `/llarp/consensus`: network consenus and inter relay testing +* `/llarp/constants`: contains all compile time constants +* `/llarp/crypto`: cryptography interface and implementation, includes various secure helpers +* `/llarp/dht`: dht message structs, parsing, validation and handlers of dht related parts of the protocol +* `/llarp/dns`: dns subsytem, dns udp wire parsers, resolver, server, rewriter/interceptor, the works +* `/llarp/ev`: event loop interfaces and implementations +* `/llarp/exit`: `.snode` endpoint "backend" +* `/llarp/handlers`: packet endpoint "frontends" +* `/llarp/iwp`: "internet wire protocol", hacky homegrown durable udp wire protocol used in lokinet +* `/llarp/link`: linklayer (node to node) communcation subsystem +* `/llarp/messages`: linklayer message parsing and handling +* `/llarp/net`: wrappers and helpers for ip addresses / ip ranges / sockaddrs, hides platform specific implemenation details +* `/llarp/path`: onion routing path logic, both client and relay side, path selection algorithms. +* `/llarp/peerstats`: deprecated +* `/llarp/quic`: plainquic shims for quic protocol inside lokinet +* `/llarp/router`: the relm of the god objects +* `/llarp/routing`: routing messages (onion routed messages sent over paths), parsing, validation and handler interfaces. +* `/llarp/rpc`: lokinet zmq rpc server and zmq client for externalizing logic (like with blockchain state and custom `.loki` endpoint orchestration) +* `/llarp/service`: `.loki` endpoint "backend" +* `/llarp/simulation`: network simulation shims +* `/llarp/tooling`: network simulation tooling +* `/llarp/util`: utility function dumping ground +* `/llarp/vpn`: vpn tunnel implemenation for each supported platform +* `/llarp/win32`: windows specific code + + +## component relations + +### `/llarp/service` / `/llarp/handlers` / `/llarp/exit` + +for all codepaths for traffic over lokinet, there is 2 parts, the "frontend" and the "backend". + +the "backend" is responsible for sending and recieving data inside lokinet using our internal formats via paths, it handles flow management, lookups, timeouts, handover, and all state we have inside lokinet. + +the "fontend", is a translation layer that takes in IP Packets from the OS, and send it to the backend to go where ever it wants to go, and recieves data from the "backend" and sends it to the OS as an IP Packet. + +there are 2 'backends': `.snode` and `.loki` + +there are 2 'frontends': "tun" (generic OS vpn interface) and "null" (does nothing) + +* `//TODO: the backends need to be split up into multiple sub components as they are a kitchen sink.` +* `//TODO: the frontends blend into the backend too much and need to have their boundery clearer.` + + +### `/llarp/ev` / `/llarp/net` / `/llarp/vpn` + +these contain most of the os/platform specific bits + +* `//TODO: untangle these` + + +### `/llarp/link` / `/llarp/iwp` + +node to node traffic logic and wire protocol dialects + +* `//TODO: make better definitions of interfaces` +* `//TODO: separte implementation details from interfaces` + + +## platform contrib code `(/contrib)` + +grab bag directory for non core related platform specific non source code + +* `/contrib/format.sh`: clang-format / jsonnetfmt / swiftformat helper, will check or correct code style. + +system layer and packaging related: + +* `/contrib/NetworkManager` +* `/contrib/apparmor` +* `/contrib/systemd-resolved` +* `/contrib/lokinet-resolvconf` +* `/contrib/bootstrap` + +build shims / ci helpers + +* `/contrib/ci` +* `/contrib/patches` +* `/contrib/cross` +* `/contrib/android.sh` +* `/contrib/android-configure.sh` +* `/contrib/windows.sh` +* `/contrib/windows-configure.sh` +* `/contrib/mac.sh` +* `/contrib/ios.sh` +* `/contrib/cross.sh` diff --git a/docs/proto_v0.txt b/docs/proto_v0.txt deleted file mode 100644 index 6a3bb0ecf..000000000 --- a/docs/proto_v0.txt +++ /dev/null @@ -1,903 +0,0 @@ -LLARP v0 - -LLARP (Low Latency Anon Routing Protocol) is a protocol for anonymizing senders and -recipiants of encrypted messages sent over the internet without a centralised -trusted party. - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this -document are to be interpreted as described in RFC 2119 [RFC2119]. - -basic structures: - -all structures are key, value dictionaries encoded with bittorrent encoding -notation: - -a + b is a concatanated with b - -a ^ b is a bitwise XOR b - -x[a:b] is a memory slice of x from index a to b - -BE(x) is bittorrent encode x - -BD(x) is bittorrent decode x - -{ a: b, y: z } is a dictionary with two keys a and y - whose values are b and z respectively - -[ a, b, c ... ] is a list containing a b c and more items in that order - -"" is a bytestring whose contents and length is described by the - quoted value - -"" * N is a bytestring containing the concatenated N times. - -cryptography: - -see crypto_v0.txt - ---- - -wire protocol - -see wire-protocol.txt - ---- - -datastructures: - -all datastructures are assumed version 0 if they lack a v value -otherwise version is provided by the v value - -all ip addresses can be ipv4 via hybrid dual stack ipv4 mapped ipv6 addresses, -i.e ::ffff.8.8.8.8. The underlying implementation MAY implement ipv4 as native -ipv4 instead of using a hybrid dual stack. - -net address: - -net addresses are a variable length byte string, if between 7 and 15 bytes it's -treated as a dot notation ipv4 address (xxx.xxx.xxx.xxx) -if it's exactly 16 bytes it's treated as a big endian encoding ipv6 address. - -address info (AI) - -An address info (AI) defines a publically reachable endpoint - -{ - c: transport_rank_uint16, - d: "", - e: "<32 bytes public encryption key>", - i: "", - p: port_uint16, - v: 0 -} - -example wank address info: - -{ - c: 1, - d: "wank", - e: "<32 bytes of 0x61>", - i: "123.123.123.123", - p: 1234, - v: 0 -} - -bencoded form: - -d1:ci1e1:d4:wank1:e32:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1:d3:iwp1:i15:123.123.123.1231:pi1234e1:vi0ee - -Traffic Policy (TP) - -Traffic policy (TP) defines port, protocol and QoS/drop policy. - -{ - a: protocol_integer, - b: port_integeger, - d: drop_optional_integer, - v: 0 -} - -drop d is set to 1 to indicate that packets of protocol a with source port b will be dropped. -if d is 0 or not provided this traffic policy does nothing. - -Exit Info (XI) - -An exit info (XI) defines a exit address that can relay exit traffic to the -internet. - -{ - a: "", - b: "", - k: "<32 bytes public encryption/signing key>", - p: [ list, of, traffic, policies], - v: 0 -} - - -Exit Route (XR) - -An exit route (XR) define an allocated exit address and any additional -information required to access the internet via that exit address. - -{ - a: "<16 bytes big endian ipv6 gateway address>", - b: "<16 bytes big endian ipv6 netmask>", - c: "<16 bytes big endian ipv6 source address>", - l: lifetime_in_milliseconds_uint64, - v: 0 -} - -router contact (RC) - -router's full identity - -{ - a: [ one, or, many, AI, here ... ], - e: extensions_supported, - i: "", - k: "<32 bytes public long term identity signing key>", - n: "", - p: "<32 bytes public path encryption key>", - s: [services, supported], - u: time_signed_at_milliseconds_since_epoch_uint64, - v: 0, - x: [ Exit, Infos ], - z: "<64 bytes signature using identity key>" -} - -e is a dict containing key/value of supported protocol extensions on this service node, keys are strings, values MUST be 0 or 1. - -s is a list of services on this service node: - -each service is a 6 item long, list of the following: - -["_service", "_proto", ttl_uint, priority_uint, weight_uint, port_uint] - -with the corrisponding SRV record: - -<_service>.<_proto>.router_pubkey_goes_here.snode. IN SRV router_pubkey_goes_here.snode - - -RC.t is the timestamp of when this RC was signed. -RC is valid for a maximum of 1 hour after which it MUST be resigned with the new -timestamp. - -RC.i is set to the network identifier. - -only routers with the same network identifier may connect to each other. - -"testnet" for testnet. -"lokinet" for the "official" lokinet mainline network. - -other values of RC.i indicate the router belongs to a network fork. - - -service info (SI) - -public information blob for a hidden service - -e is the long term public encryption key -s is the long term public signing key -v is the protocol version -x is a nounce value for generating vanity addresses that can be omitted - -if x is included it MUST be equal to 16 bytes - -{ - e: "<32 bytes public encryption key>", - s: "<32 bytes public signing key>", - v: 0, - x: "" -} - -service address (SA) - -the "network address" of a hidden service, which is computed as the blake2b -256 bit hash of the public infomration blob. - -HS(BE(SI)) - -when in string form it's encoded with z-base32 and uses the .loki tld - -introduction (I) - -a descriptor annoucing a path to a hidden service - -k is the rc.k value of the router to contact -p is the path id on the router that is owned by the service -v is the protocol version -x is the timestamp milliseconds since epoch that this introduction expires at - -{ - k: "<32 bytes public identity key of router>", - l: advertised_path_latency_ms_uint64, (optional) - p: "<16 bytes path id>", - v: 0, - x: time_expires_milliseconds_since_epoch_uint64 -} - -introduction set (IS) - -and introset is a signed set of introductions for a hidden service -a is the service info of the publisher -h is a list of srv records in same format as in RCs -i is the list of introductions that this service is advertising with -k is the public key to use when doing encryption to this hidden service -n is a 16 byte null padded utf-8 encoded string tagging the hidden service in - a topic searchable via a lookup (optional) -v is the protocol version -w is an optinal proof of work for DoS protection (slot for future) -z is the signature of the entire IS where z is set to zero signed by the hidden -service's signing key. - -{ - a: SI, - h: [list, of, advertised, services], - i: [ I, I, I, ... ], - k: "<1218 bytes sntrup4591761 public key block>", - n: "<16 bytes service topic (optional)>", - t: timestamp_uint64_milliseconds_since_epoch_published_at, - v: 0, - w: optional proof of work, - z: "<64 bytes signature using service info signing key>" -} - -h is a list of services on this endpoint - -each service is a 7 item long, list of the following: - -["_service", "_proto", ttl_uint, priority_uint, weight_uint, port_uint, "<32 bytes SA of the service>"] - -with the corrisponding SRV record: - -<_service>.<_proto>..loki. IN SRV .loki. - -recursion on SRV records is NOT permitted. - ---- - -Encrypted frames: - - -Encrypted frames are encrypted containers for link message records like LRCR. - -32 bytes hmac, h -32 bytes nounce, n -32 bytes ephmeral sender's public encryption key, k -remaining bytes ciphertext, x - -decryption: - -0) verify hmac - -S = PKE(n, k, our_RC.K) -verify h == MDS(n + k + x, S) - -If the hmac verification fails the entire parent message is discarded - -1) decrypt and decode - -new_x = SD(S, n[0:24], x) -msg = BD(new_x) - -If the decoding fails the entire parent message is discarded - -encryption: - -to encrypt a frame to a router with public key B.k - -0) prepare nounce n, ephemeral keypair (A.k, s) and derive shared secret S - -A.k, s = ECKG() -n = RAND(32) -S = PKE(p, A.k, B.k, n) - -1) encode and encrypt - -x = BE(msg) -new_x = SE(S, n[0:24], x) - -2) generate message authentication - -h = MDS(n + A.k + new_x, S) - -resulting frame is h + n + A.k + new_x - - ---- - -link layer messages: - -the link layer is responsible for anonymising the source and destination of -routing layer messages. - -any link layer message without a key v is assumed to be version 0 otherwise -indicates the protocol version in use. - - - -link introduce message (LIM) - -This message MUST be the first link message sent before any others. This message -identifies the sender as having the RC contained in r. The recipiant MUST -validate the RC's signature and ensure that the public key in use is listed in -the RC.a matching the ipv6 address it originated from. - -{ - a: "i", - e: "<32 bytes ephemeral public encryption key>", - n: "<32 bytes nonce for key exhcange>", - p: uint64_milliseconds_session_period, - r: RC, - v: 0, - z: "<64 bytes signature of entire message by r.k>" -} - -the link session will be kept open for p milliseconds after which -the session MUST be renegotiated. - -link relay commit message (LRCM) - -request a commit to relay traffic to another node. - -{ - a: "c", - c: [ list, of, encrypted, LRCR frames ], - v: 0 -} - -c MUST contain dummy records if the hop length is less than the maximum -hop length. - -link relay commit record (LRCR) - -record requesting relaying messages for 600 seconds to router -on network whose i is equal to RC.k and decrypt data any messages using -PKE(n, rc.p, c) as symmetric key for encryption and decryption. - -if l is provided and is less than 600 and greater than 10 then that lifespan -is used (in seconds) instead of 600 seconds. - -if w is provided and fits the required proof of work then the lifetime of -the path is extended by w.y seconds - -{ - c: "<32 byte public encryption key used for upstream>", - d: uint_optional_ms_delay, // TODO - i: "<32 byte RC.k of next hop>", - l: uint_optional_lifespan, - n: "<32 bytes nounce for key exchange>", - r: "<16 bytes rx path id>", - t: "<16 bytes tx path id>", - u: "", - v: 0, - w: proof of work -} - -w if provided is a dict with the following struct - -{ - t: time_created_seconds_since_epoch, - v: 0, - y: uint32_seconds_extended_lifetime, - z: "<32 bytes nonce>" -} - -the validity of the proof of work is that given - -h = HS(BE(w)) - -h has log_e(y) prefixed bytes being 0x00 - -this proof of work requirement is subject to change - -if i is equal to RC.k then any LRDM.x values are decrypted and interpreted as -routing layer messages. This indicates that we are the farthest hop in the path. - -link relay status message (LRSM) - -response to path creator about build status - -{ - a: "s", - c: [ list, of, encrypted, LRSR frames], - p: "<16 bytes rx path id>", - s: uint_status_flags, // for now, only set (or don't) success bit - v: 0 -} - -the creator of the LRSM MUST include dummy LRSR records -to pad out to the maximum path length - -link relay status record (LRSR) - -record indicating status of path build - -{ - s: uint_status_flags, - v: 0 -} - -uint_status_flags (bitwise booleans): - [0] = success - [1] = fail, hop timeout - [2] = fail, congestion - [3] = fail, refusal, next hop is not known to be a snode - [4] = fail, used by hop creator when decrypting frames if decryption fails - [5] = fail, used by hop creator when record decode fails - [4-63] reserved - -link relay upstream message (LRUM) - -sent to relay data via upstream direction of a previously created path. - -{ - a: "u", - p: "<16 bytes path id>", - v: 0, - x: "", - y: "<32 bytes nonce>" -} - -x1 = SD(k, y, x) - -if we are the farthest hop, process x1 as a routing message -otherwise transmit a LRUM to the next hop - -{ - a: "u", - p: p, - v: 0, - x: x1, - y: y ^ HS(k) -} - -link relay downstream message (LRDM) - -sent to relay data via downstream direction of a previously created path. - -{ - a: "d", - p: "<16 bytes path id>", - v: 0, - x: "", - y: "<32 bytes nonce>" -} - -if we are the creator of the path decrypt x for each hop key k - -x = SD(k, y, x) - -otherwise transmit LRDM to next hop - -x1 = SE(k, y, x) - -{ - a: "d", - p: p, - v: 0, - x: x1, - y: y ^ HS(k) -} - -link immediate dht message (LIDM): - -transfer one or more dht messages directly without a previously made path. - -{ - a: "m", - m: [many, dht, messages], - v: 0 -} - - -link immediate state message (LISM) - -transfer a state message between nodes - -{ - a: "s", - s: state_message, - v: 0 -} - ---- - -state message: - -NOTE: this message type is currently a documentation stub and remains unimplemented. - -state messages propagate changes to the service nodes concensous to thin clients. - -service node joined network - -{ - A: "J", - R: "<32 bytes public key>", - V: 0 -} - -service node parted network - -{ - A: "P", - R: "<32 bytes public key>", - V: 0 -} - -service node list request - -request the service node list starting at index I containing R entries - -{ - A: "R", - I: starting_offset_int, - R: number_of_entires_to_request_int, - V: 0 -} - -service node list response - -response to service node list request - -{ - A: "L", - S: { - "<32 bytes pubkey>" : 1, - "<32 bytes pubkey>" : 1, - .... - }, - V: 0 -} - ---- - -routing layer: - -the routing layer provides inter network communication between the LLARP link -layer and ip (internet protocol) for exit traffic or ap (anonymous protocol) for -hidden services. replies to messages are sent back via the path they -originated from inside a LRDM. all routing messages have an S value which -provides the sequence number of the message so the messages can be ordered. - -ipv4 addresses are allowed via ipv4 mapped ipv6 addresses, i.e. ::ffff.10.0.0.1 - -path confirm message (PCM) - -sent as the first message down a path after it's built to confirm it was built - -always the first message sent - -{ - A: "P", - L: uint64_milliseconds_path_lifetime, - S: 0, - T: uint64_milliseconds_sent_timestamp, - V: 0 -} - -path latency message (PLM) - -a latency measurement message, reply with a PLM response if we are the far end -of a path. - -variant 1, request, generated by the path creator: - -{ - A: "L", - S: uint64_sequence_number, - V: 0 -} - -variant 2, response, generated by the endpoint that recieved the request. - -{ - A: "L", - S: uint64_sequence_number, - T: uint64_timestamp_recieved, - V: 0 -} - -obtain exit message (OXM) - -sent to an exit router to obtain ip exit traffic context. -replies are sent down the path that messages originate from. - -{ - A: "X", - B: [list, of, permitted, blacklisted, traffic, policies], - E: 0 for snode communication or 1 for internet access, - I: "<32 bytes signing public key for future communication>", - S: uint64_sequence_number, - T: uint64_transaction_id, - V: 0, - W: [list, of, required, whitelisted, traffic, policies], - X: lifetime_of_address_mapping_in_seconds_uint64, - Z: "<64 bytes signature using I>" -} - -grant exit messsage (GXM) - -sent in response to an OXM to grant an ip for exit traffic from an external -ip address used for exit traffic. - -{ - A: "G", - S: uint64_sequence_number, - T: transaction_id_uint64, - Y: "<16 byte nonce>", - V: 0, - Z: "<64 bytes signature>" -} - -any TITM recieved on the same path will be forwarded out to the internet if -OXAM.E is not 0, otherwise it is interpreted as service node traffic. - - -reject exit message (RXM) - -sent in response to an OXAM to indicate that exit traffic is not allowed or -was denied. - -{ - A: "J", - B: backoff_milliseconds_uint64, - R: [list, of, rejected, traffic, policies], - S: uint64_sequence_number, - T: transaction_id_uint64, - V: 0, - Y: "<16 byte nonce>", - Z: "<64 bytes signature>" -} - - -discarded data fragment message (DDFM) - -sent in reply to TDFM when we don't have a path locally or are doing network -congestion control from a TITM. - -{ - A: "D", - P: "<16 bytes path id>", - S: uint64_sequence_number_of_fragment_dropped, - V: 0 -} - -transfer data fragment message (TDFM) - -transfer data between paths. - -{ - A: "T", - P: "<16 bytes path id>", - S: uint64_sequence_number, - T: hidden_service_frame, - V: 0 -} - -transfer data to another path with id P on the local router place a random 32 -byte and T values into y and z values into a LRDM message (respectively) and -send it in the downstream direction. if this path does not exist on the router -it is replied to with a DDFM. - - - -hidden service data (HSD) - -data sent anonymously over the network to a recipiant from a sender. -sent inside a HSFM encrypted with a shared secret. - -{ - a: protocol_number_uint, - d: "", - i: Introduction for reply, - n: uint_message_sequence_number, - o: N seconds until this converstation plans terminate, - s: SI of sender, - t: "<16 bytes converstation_tag>, - v: 0 -} - - -hidden service frame (HSF) - -TODO: document this better - -establish converstation tag message (variant 1) - -generate a new convotag that is contained inside an encrypted HSD - -{ - A: "H", - C: "<1048 bytes ciphertext block>", - D: "", - F: "<16 bytes source path_id>", - N: "<32 bytes nonce for key exchange>", - V: 0, - Z: "<64 bytes signature of entire message using sender's signing key>" -} - -alice (A) wants to talk to bob (B) over the network, both have hidden services -set up and are online on the network. - -A and B are both referring to alice and bob's SI respectively. -A_sk is alice's private signing key. - -for alice (A) to send the string "beep" to bob (B), alice picks an introduction -to use on one of her paths (I_A) such that I_A is aligning with one of bobs's -paths (I_B) - -alice generates: - -T = RAND(16) - -m = { - a: 0, - d: "beep", - i: I_A, - n: 0, - s: A, - t: T, - v: 0 -} - -X = BE(m) - -C, K = PQKE_A(I_B.k) -N = RAND(32) -D = SE(X, K, N) - -path = PickSendPath() - -M = { - A: "T", - P: I_B.P, - S: uint64_sequence_number, - T: { - A: "H", - C: C, - D: D, - F: path.lastHop.txID, - N: N, - V: 0, - Z: "\x00" * 64 - }, - V: 0 -} - -Z = S(A_sk, BE(M)) - -alice transmits a TDFM to router with public key I_B.K via her path that ends -with router with public key I_B.k - -path = PickSendPath() - -{ - A: "T", - P: I_B.P, - S: uint64_sequence_number, - T: { - A: "H", - C: C, - D: D, - F: path.lastHop.txID, - N: N, - V: 0, - Z: Z - }, - V: 0 -} - -the shared secret (S) for further message encryption is: - -S = HS(K + PKE(A, B, sk, N)) - -given sk is the local secret encryption key used by the current hidden service - -please note: -signature verification of the outer message can only be done after decryption -because the signing keys are inside the encrypted HSD. - -data from a previously made session (variant 2) - -transfer data on a converstation previously made - -{ - A: "H", - D: "", - F: "<16 bytes path id of soruce>", - N: "<32 bytes nonce for symettric cipher>", - T: "<16 bytes converstation tag>", - V: 0, - Z: "<64 bytes signature using sender's signing key>" -} - -reject a message sent on a convo tag, when a remote endpoint -sends this message a new converstation SHOULD be established. - -{ - A: "H", - F: "<16 bytes path id of soruce>", - R: 1, - T: "<16 bytes converstation tag>", - V: 0, - Z: "<64 bytes signature using sender's signing key>" -} - - -transfer ip traffic message (TITM) - -transfer ip traffic - -{ - A: "I", - S: uint64_sequence_number, - V: 0, - X: [list, of, ip, packet, buffers], -} - -an ip packet buffer is prefixed with a 64 bit big endian unsigned integer -denoting the sequence number for re-ordering followed by the ip packet itself. - -X is parsed as a list of IP packet buffers. -for each ip packet the source addresss is extracted and sent on the -appropriate network interface. - -When we receive an ip packet from the internet to an exit address, we put it -into a TITM, and send it downstream the corresponding path in an LRDM. - -update exit path message (UXPM) - -sent from a new path by client to indicate that a previously established exit -should use the new path that this message came from. - -{ - A: "U", - P: "<16 bytes previous tx path id>", - S: uint64_sequence_number, - T: uint64_txid, - V: 0, - Y: "<16 bytes nonce>", - Z: "<64 bytes signature using previously provided signing key>" -} - -close exit path message (CXPM) - -client sends a CXPM when the exit is no longer needed or by the exit if the -exit wants to close prematurely. -also sent by exit in reply to a CXPM to confirm close. - -{ - A: "C", - S: uint64_sequence_number, - V: 0, - Y: "<16 bytes nonce>", - Z: "<64 bytes signature>" -} - -update exit verify message (EXVM) - -sent in reply to a UXPM to verify that the path handover was accepted - -{ - A: "V", - S: uint64_sequence_number, - T: uint64_txid, - V: 0, - Y: "<16 bytes nonce>", - Z: "<64 bytes signature>" -} - - -DHT message holder message: - -wrapper message for sending many dht messages down a path. - -{ - A: "M", - M: [many, dht, messages, here], - S: uint64_sequence_number, - V: 0 -} diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 000000000..534a19497 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,34 @@ +# Lokinet Docs + +This is where Lokinet documentation lives. + +[How Do I install Lokinet?](install.md) + +[How Do I use Lokinet?](ideal-ux.md) + +## High level + +[How is Lokinet different to \[insert network technology name here\] ?](net-comparisons.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) + + + diff --git a/docs/snap-config-options.md b/docs/snap-config-options.md deleted file mode 100644 index 0979fe22a..000000000 --- a/docs/snap-config-options.md +++ /dev/null @@ -1,47 +0,0 @@ - -# snapp config options - -## required - -### ifname -network interface name -### ifaddr -ip range of network interface - -## optional - -### keyfile -the private key to persist address with. -if not specified the address will be ephemeral. -### reachable -bool value that sets if we publish our snapp to the dht -`true`: we are reachable via dht -`false`: we are not reachable via dht -### hops -number of hops in a path, min is `1`, max is `8`, defaults to `4` -### paths -number of paths to maintain at any given time, defaults to `6`. -### blacklist-snode -adds a `.snode` to path build blacklist -### exit-node -specify a `.snode` or `.loki` address to use as an exit broker -### local-dns -address to bind local dns resoler to, defaults to `127.3.2.1:53` -if port is omitted it uses port `53` -### upstream-dns -address to forward non lokinet related queries to. if not set lokinet dns will reply with srvfail. -### mapaddr -perma map `.loki` address to an ip owned by the snapp -to map `whatever.loki` to `10.0.10.10` it can be specified via: -``` -mapaddr=whatever.loki:10.0.10.10 -``` - -## compile time optional features - -### on-up -path to shell script to call when our interface is up -### on-down -path to shell script to call when our interface is down -### on-ready -path to shell script to call when snapp is first ready diff --git a/docs/snapps-dev-guide.md b/docs/snapps-dev-guide.md new file mode 100644 index 000000000..24ebb8ff3 --- /dev/null +++ b/docs/snapps-dev-guide.md @@ -0,0 +1,30 @@ +# (SN)Apps Development Guide + + +## Our approach + +`// TODO: this` + +## Differences From Other approaches + +`// TODO: this` + +### SOCKS Proxy/HTTP Tunneling + +`// TODO: this` + +### Embedding a network stack + +`// TODO: this` + +## High Level Code Practices + +`// TODO: this` + +### Goodisms + +`// TODO: this` + +### Badisms + +`// TODO: this` diff --git a/docs/tooling.txt b/docs/tooling.txt deleted file mode 100644 index e41325330..000000000 --- a/docs/tooling.txt +++ /dev/null @@ -1,38 +0,0 @@ -What is a RouterEvent? - - A RouterEvent is a way of representing a conceptual event that took place in a "router" (relay or client). - - RouterEvents are used in order to collect information about a network all in one place and preserve causality. - -How do I make a new RouterEvent? - - Add your event following the structure in llarp/tooling/router_event.{hpp,cpp} - - Add your event to pybind in pybind/llarp/tooling/router_event.cpp - -What if a class my event uses is missing members in pybind? - - Find the relevant file pybind/whatever/class.cpp and remedy that! - -What if I need to refer to classes which aren't available already in pybind? - - Add pybind/namespace/namespace/etc/class.cpp and pybind it! - - You will need to edit the following files accordingly: - pybind/common.hpp - pybind/module.cpp - pybind/CMakeLists.txt - -How do I use a RouterEvent? - - From the cpp side, find the place in the code where it makes the most logical sense - that the conceptual event has taken place (and you have the relevant information to - create the "event" instance) and create it there as follows: - - #include - - where the event takes place, do the following: - auto event = std::make_unique(constructor_args...); - somehow_get_a_router->NotifyRouterEvent(std::move(event)); - - From the Python side...it's a python object! diff --git a/docs/we-cannot-make-sandwiches.md b/docs/we-cannot-make-sandwiches.md new file mode 100644 index 000000000..3798f28b6 --- /dev/null +++ b/docs/we-cannot-make-sandwiches.md @@ -0,0 +1,19 @@ +# What Lokinet can't do + +Lokinet does a few things very well, but obviously can't do everything. + +## Anonymize OS/Application Fingerprints + +Mitigating OS/Application Fingerprinting is the responsibility of the OS and Applications. Lokinet is an Unspoofable IP Packet Onion router, tuning OS fingerprints to be uniform would be a great thing to have in general even outside of the context of Lokinet. The creation of such an OS bundle is a great idea, but outside the scope of Lokinet. We welcome others to develop a solution for this. + +## Malware + +Lokinet cannot prevent running of malicious programs. Computer security unfortunately cannot be solved unilaterally by networking software without simply dropping all incoming and outgoing traffic. + +## Phoning Home + +Lokinet cannot prevent software which sends arbitrary usage data or private information to Microsoft/Apple/Google/Amazon/Facebook/etc. If you are using a service that requires the ability to phone home in order to work, that is a price you pay to use that service. + +## Make Sandwiches + +At its core, Lokinet is technology that anonymizes and authenticates IP traffic. At this current time Lokinet cannot make you a sandwich. No, not even as root. diff --git a/docs/wire-protocol.txt b/docs/wire-protocol.txt deleted file mode 100644 index 0cb51cc0c..000000000 --- a/docs/wire-protocol.txt +++ /dev/null @@ -1,309 +0,0 @@ -Wire Protocol (version 1) - - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this -document are to be interpreted as described in RFC 2119 [RFC2119]. - -LLARP's wire protocol is Internet Wire Protocol (IWP) - -The main goal of iwp is to provide an authenticated encrypted -reliable semi-ordered durable datagram transfer protocol supporting -datagrams of larger size than link mtu. - -in iwp there is an initiator who initiates a session to a recipiant. - -iwp has 3 phases. the first phase is the proof of flow phase. -the second is a session handshake phase, the third is data transmission. - -proof of flow: - -the purpose of the proof of flow phase is to verify the existence -of the initiator's endpoint. - -At any time before the data transfer phase a reject message -is sent the session is reset. - -Alice (A) is the sender and Bob (B) is the recipiant. - -A asks for a flow id from B. - -B MAY send a flow id to A or MAY reject the message from A. - -session handshake: - -an encrypted session is established using establish wire session messages -using a newly created flow id. - -message format: - -there are 2 layers in this protocol, outer messages and inner messages. - -outer messages are sent in plaintext and / or obfsucated with symettric -encryption using a preshared key. - -inner messages are inside an encrypted and authenticated envelope -wrapped by an outer messages, which is always a data tranmssion message. - -outer message format: - -every outer message MAY be obfsucated via symettric encryption for dpi -resistance reasons, this is not authenticated encryption. - -the message is first assumed to be sent in clear first. -if parsing of clear variant fails then the recipiant MUST fall back to assuming -the protocol is in obfuscated mode. - - -<16 bytes nounce, n> - - -obfuscated via: - -K = HS(B_k) -N = HS(n + K) -X = SD(K, m, N[0:24]) - -where -B_k is the long term identity public key of the recipient. -HS is blake2 256 bit non keyed hash -SD is xchacha20 symettric stream cipher (decryption) - -outer-header: - -<1 byte command> -<1 byte reserved set to 0x3d> - -command 'O' - obtain flow id - -obtain a flow id - - -<6 magic bytes "netid?"> -<8 bytes netid, I> -<8 bytes timestamp milliseconds since epoch, T> -<32 bytes public identity key of sender, A_k> -<0-N bytes discarded> - - -the if the network id differs from the current network's id a reject message -MUST be sent - -MUST be replied to with a message rejected or a give handshake cookie - -command 'G' - give flow id - - -<6 magic bytes "netid!"> -<16 bytes new flow id> -<32 bytes public identiy key of sender, A_k> -<0-N bytes ignored but included in signature> - - -after recieving a give flow id message a session negotiation can happen with that flow id. - -command 'R' - flow rejected - -reject new flow - - -<14 ascii bytes reason for rejection null padded> -<8 bytes timestamp> -<32 bytes public identity key of sender, A_k> -<0-N bytes ignored but included in signature> - - -command 'E' - establish wire session - -establish an encrypted session using a flow id - - -<2 bytes 0x0a 0x0d> -<4 bytes flags, F> -<16 bytes flow id, B> -<32 bytes ephemeral public encryption key, E> -<8 bytes packet counter starting at 0> - - - - -F is currently set to all zeros - -every time we try establishing a wire session we increment the counter -by 1 for the next message we send. - -when we get an establish wire session message -we reply with an establish wire session message with counter being counter + 1 - -if A is provided that is interpreted as being generated via: - -h0 = HS('') -h1 = EDDH(us, them) -A = HS(B + h0 + h1) - -each side establishes their own rx key using this message. -when each side has both established thier rx key data can be transmitted. - -command 'D' - encrypted data transmission - -transmit encrypted data on a wire session - - -<16 bytes flow-id, F> -<24 bytes nonce, N> - - - - -B is the flow id from the recipiant (from outer header) -N is a random nounce -X is encrypted data -Z is keyed hash of entire message - -Z is generated via: - -msg.Z = MDS(outer-header + F + N + X, tx_K) - -data tranmission: - -inner message format of X (after decryption): - -inner header: - -<1 byte protocol version> -<1 byte command> - - -command: 'k' (keep alive) - -tell other side to acknoledge they are alive - - -<2 bytes resevered, set to 0> -<2 bytes attempt counter, set to 0 and incremented every retransmit, reset when we get a keepalive ack> -<2 bytes milliseconds ping timeout> -<8 bytes current session TX limit in bytes per second> -<8 bytes current session RX use in bytes per second> -<8 bytes milliseconds since epoch our current time> - - -command: 'l' (keep alive ack) - -acknolege keep alive message - - -<6 bytes reserved, set to 0> -<8 bytes current session RX limit in bytes per second> -<8 bytes current session TX use in bytes per second> -<8 bytes milliseconds since epoch our current time> - - - -command: 'n' (advertise neighboors) - -tell peer about neighboors, only sent by non service nodes to other non service -nodes. - - - -<0 or more intermediate routes> - - -route: - -<1 byte route version (currently 0)> -<1 byte flags, lsb set indicates src is a service node> -<2 bytes latency in ms> -<2 bytes backpressure> -<2 bytes number of connected peers> -<8 bytes publish timestamp ms since epoch> -<32 bytes pubkey neighboor> -<32 bytes pubkey src> -<64 bytes signature of entire route signed by src> - -command: 'c' (congestion) - -tell other side to slow down - - -<2 bytes reduce TX rate by this many 1024 bytes per second> -<4 bytes milliseconds slowdown lifetime> - - -command: 'd' (anti-congestion) - -tell other side to speed up - - -<2 bytes increase TX rate by this many 1024 bytes per second> -<4 bytes milliseconds speedup lifetime> - - - -command: 's' (start transmission) - -initate the transmission of a message to the remote peer - - -<1 byte flags F> -<1 byte reserved R set to zero> -<2 bytes total size of full message> -<4 bytes sequence number S> -<32 bytes blake2 hash of full message> - - -if F lsb is set then there is no further fragments - -command: 't' (continued transmission) - -continue transmission of a bigger message - - -<1 byte flags F> -<1 bytes reserved R set to zero> -<2 bytes 16 byte block offset in message> -<4 bytes sequence number S> - - - -command: 'q' (acknoledge transmission) - -acknoledges a transmitted message - - - -command: 'r' (rotate keys) - -inform remote that their RX key should be rotated - -given alice(A) sends this message to bob(B) the new keys are computed as such: - -n_K = TKE(K, B_e, K_seed, N) - -A.tx_K = n_K -B.rx_K = n_K - - -<2 bytes milliseconds lifetime of old keys, retain them for this long and then discard> -<4 bytes reserved, set to 0> -<32 bytes key exchange nounce, N> -<32 bytes next public encryption key, K> - - -command: 'u' (upgrade) - -request protocol upgrade - - -<1 byte protocol min version to upgrade to> -<1 byte protocol max version to upgrade to> - - -command: 'v' (version upgrade) - -sent in response to upgrade message - - -<1 byte protocol version selected> -<1 byte protocol version highest we support> - diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 2ddd0e96f..d235c1ec2 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -12,39 +12,84 @@ if(SUBMODULE_CHECK) else() message(FATAL_ERROR "Submodule 'external/${relative_path}' is not up-to-date. Please update with\ngit submodule update --init --recursive\nor run cmake with -DSUBMODULE_CHECK=OFF") endif() + + # Extra arguments check nested submodules + foreach(submod ${ARGN}) + execute_process(COMMAND git rev-parse "HEAD" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path}/${submod} OUTPUT_VARIABLE localHead) + execute_process(COMMAND git rev-parse "HEAD:${submod}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path} OUTPUT_VARIABLE checkedHead) + string(COMPARE EQUAL "${localHead}" "${checkedHead}" upToDate) + if (NOT upToDate) + message(FATAL_ERROR "Nested submodule '${relative_path}/${submod}' is not up-to-date. Please update with\ngit submodule update --init --recursive\nor run cmake with -DSUBMODULE_CHECK=OFF") + endif() + endforeach() endfunction () message(STATUS "Checking submodules") check_submodule(nlohmann) check_submodule(cxxopts) check_submodule(ghc-filesystem) - check_submodule(date) + check_submodule(oxen-logging fmt spdlog) check_submodule(pybind11) check_submodule(sqlite_orm) check_submodule(oxen-mq) + check_submodule(oxen-encoding) check_submodule(uvw) check_submodule(cpr) check_submodule(ngtcp2) endif() endif() +macro(system_or_submodule BIGNAME smallname pkgconf subdir) + option(FORCE_${BIGNAME}_SUBMODULE "force using ${smallname} submodule" OFF) + if(NOT BUILD_STATIC_DEPS AND NOT FORCE_${BIGNAME}_SUBMODULE AND NOT FORCE_ALL_SUBMODULES) + pkg_check_modules(${BIGNAME} ${pkgconf} IMPORTED_TARGET) + endif() + if(${BIGNAME}_FOUND) + add_library(${smallname} INTERFACE) + if(NOT TARGET PkgConfig::${BIGNAME} AND CMAKE_VERSION VERSION_LESS "3.21") + # Work around cmake bug 22180 (PkgConfig::THING not set if no flags needed) + else() + target_link_libraries(${smallname} INTERFACE PkgConfig::${BIGNAME}) + endif() + message(STATUS "Found system ${smallname} ${${BIGNAME}_VERSION}") + else() + message(STATUS "using ${smallname} submodule") + add_subdirectory(${subdir}) + endif() + if(NOT TARGET ${smallname}::${smallname}) + add_library(${smallname}::${smallname} ALIAS ${smallname}) + endif() +endmacro() + +system_or_submodule(OXENC oxenc liboxenc>=1.0.4 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() -set(JSON_BuildTests OFF CACHE INTERNAL "") -add_subdirectory(nlohmann EXCLUDE_FROM_ALL) add_subdirectory(cxxopts EXCLUDE_FROM_ALL) -add_subdirectory(date EXCLUDE_FROM_ALL) - -add_library(sqlite_orm INTERFACE) -target_include_directories(sqlite_orm SYSTEM INTERFACE sqlite_orm/include) -if(NOT TARGET sqlite3) - add_library(sqlite3 INTERFACE) - pkg_check_modules(SQLITE3 REQUIRED IMPORTED_TARGET sqlite3) - target_link_libraries(sqlite3 INTERFACE PkgConfig::SQLITE3) + +if(WITH_PEERSTATS) + add_library(sqlite_orm INTERFACE) + target_include_directories(sqlite_orm SYSTEM INTERFACE sqlite_orm/include) + if(NOT TARGET sqlite3) + add_library(sqlite3 INTERFACE) + pkg_check_modules(SQLITE3 REQUIRED IMPORTED_TARGET sqlite3) + target_link_libraries(sqlite3 INTERFACE PkgConfig::SQLITE3) + endif() + target_link_libraries(sqlite_orm INTERFACE sqlite3) endif() -target_link_libraries(sqlite_orm INTERFACE sqlite3) add_library(uvw INTERFACE) target_include_directories(uvw INTERFACE uvw/src) @@ -56,26 +101,42 @@ add_ngtcp2_lib() # 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) -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) + # 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) + endif() endif() -endif() -file(GLOB cpr_sources ${conf_depends} cpr/cpr/*.cpp) + file(GLOB cpr_sources ${conf_depends} cpr/cpr/*.cpp) + + add_library(cpr STATIC EXCLUDE_FROM_ALL ${cpr_sources}) + target_link_libraries(cpr PUBLIC CURL::libcurl) + target_include_directories(cpr PUBLIC cpr/include) + target_compile_definitions(cpr PUBLIC CPR_CURL_NOSIGNAL) + add_library(cpr::cpr ALIAS cpr) -add_library(cpr STATIC EXCLUDE_FROM_ALL ${cpr_sources}) -target_link_libraries(cpr PUBLIC CURL::libcurl) -target_include_directories(cpr PUBLIC cpr/include) -target_compile_definitions(cpr PUBLIC CPR_CURL_NOSIGNAL) -add_library(cpr::cpr ALIAS cpr) + file(READ cpr/CMakeLists.txt cpr_cmake_head LIMIT 1000) + if(cpr_cmake_head MATCHES "project\\(cpr VERSION ([0-9]+)\.([0-9]+)\.([0-9]+) LANGUAGES CXX\\)") + set(cpr_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(cpr_VERSION_MINOR ${CMAKE_MATCH_2}) + set(cpr_VERSION_PATCH ${CMAKE_MATCH_3}) + set(cpr_VERSION "${cpr_VERSION_MAJOR}.${cpr_VERSION_MINOR}.${cpr_VERSION_PATCH}") + set(cpr_VERSION_NUM "(${cpr_VERSION_MAJOR} * 0x10000 + ${cpr_VERSION_MINOR} * 0x100 + ${cpr_VERSION_PATCH})") + + configure_file(cpr/cmake/cprver.h.in "${CMAKE_CURRENT_BINARY_DIR}/cpr_generated_includes/cpr/cprver.h") + target_include_directories(cpr PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/cpr_generated_includes") + else() + message(FATAL_ERROR "Could not identify cpr submodule version!") + endif() + +endif() diff --git a/external/clang-format-hooks b/external/clang-format-hooks deleted file mode 160000 index ac35e705e..000000000 --- a/external/clang-format-hooks +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ac35e705ebf20187b7ff92c29d1411f6a4c8d522 diff --git a/external/cpr b/external/cpr index aac5058a1..f88fd7737 160000 --- a/external/cpr +++ b/external/cpr @@ -1 +1 @@ -Subproject commit aac5058a15e9ad5ad393973dc6fe44d7614a7f55 +Subproject commit f88fd7737de3e640c61703eb57a0fa0ce00c60cd diff --git a/external/cxxopts b/external/cxxopts index 6fa46a748..c74846a89 160000 --- a/external/cxxopts +++ b/external/cxxopts @@ -1 +1 @@ -Subproject commit 6fa46a748838d5544ff8e9ab058906ba2c4bc0f3 +Subproject commit c74846a891b3cc3bfa992d588b1295f528d43039 diff --git a/external/date b/external/date deleted file mode 160000 index cac99da8d..000000000 --- a/external/date +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cac99da8dc88be719a728dc1b597b0ac307c1800 diff --git a/external/ghc-filesystem b/external/ghc-filesystem index 2a8b380f8..cd6805e94 160000 --- a/external/ghc-filesystem +++ b/external/ghc-filesystem @@ -1 +1 @@ -Subproject commit 2a8b380f8d4e77b389c42a194ab9c70d8e3a0f1e +Subproject commit cd6805e94dd5d6346be1b75a54cdc27787319dd2 diff --git a/external/ngtcp2 b/external/ngtcp2 index 15ba6021c..026b8434e 160000 --- a/external/ngtcp2 +++ b/external/ngtcp2 @@ -1 +1 @@ -Subproject commit 15ba6021ca352e2e60f9b43f4b96d2e97a42f60b +Subproject commit 026b8434ebcbeec48939d1c7671a0a4d5c75202b diff --git a/external/nlohmann b/external/nlohmann index db78ac1d7..bc889afb4 160000 --- a/external/nlohmann +++ b/external/nlohmann @@ -1 +1 @@ -Subproject commit db78ac1d7716f56fc9f1b030b715f872f93964e4 +Subproject commit bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d diff --git a/external/oxen-encoding b/external/oxen-encoding new file mode 160000 index 000000000..a869ae2b0 --- /dev/null +++ b/external/oxen-encoding @@ -0,0 +1 @@ +Subproject commit a869ae2b0152ad70855e3774a425c39a25ae1ca6 diff --git a/external/oxen-logging b/external/oxen-logging new file mode 160000 index 000000000..9f2323a2d --- /dev/null +++ b/external/oxen-logging @@ -0,0 +1 @@ +Subproject commit 9f2323a2db5fc54fe8394892769eff859967f735 diff --git a/external/oxen-mq b/external/oxen-mq index 51754037e..ac6ef82ff 160000 --- a/external/oxen-mq +++ b/external/oxen-mq @@ -1 +1 @@ -Subproject commit 51754037ea19204610751c2ea8ae72b7ed6c1818 +Subproject commit ac6ef82ff6fd20437b7d073466dbef82a95a2173 diff --git a/external/pybind11 b/external/pybind11 index 8de7772cc..aa304c9c7 160000 --- a/external/pybind11 +++ b/external/pybind11 @@ -1 +1 @@ -Subproject commit 8de7772cc72daca8e947b79b83fea46214931604 +Subproject commit aa304c9c7d725ffb9d10af08a3b34cb372307020 diff --git a/external/readme.md b/external/readme.md new file mode 100644 index 000000000..681e6e005 --- /dev/null +++ b/external/readme.md @@ -0,0 +1,13 @@ +directory for git submodules + +* cpr: curl for people, used by lokinet-bootstrap toolchain (to be removed) +* cxxopts: cli argument parser (to be removed) +* ghc-filesystem: `std::filesystem` shim lib for older platforms (like macos) +* ngtcp2: quic implementation +* nlohmann: json parser +* oxen-encoding: [bencode](https://www.bittorrent.org/beps/bep_0003.html#bencoding)/endian header-only library +* oxen-logging: spdlog wrapper library +* oxen-mq: zmq wrapper library for threadpool and rpc +* pybind11: for pybind modules +* sqlite_orm: for peer stats db +* uvw: libuv header only library for main event loop diff --git a/external/sqlite_orm b/external/sqlite_orm index 4c6a46bd4..fdcc1da46 160000 --- a/external/sqlite_orm +++ b/external/sqlite_orm @@ -1 +1 @@ -Subproject commit 4c6a46bd4dcfba14a650e0fafb86331526878587 +Subproject commit fdcc1da46fbd90feb886c0588462a62d29eb5a06 diff --git a/gui b/gui new file mode 160000 index 000000000..1545d5479 --- /dev/null +++ b/gui @@ -0,0 +1 @@ +Subproject commit 1545d5479eb0db2df96db08b52bf76503eabf0ee diff --git a/include/llarp.hpp b/include/llarp.hpp index 5838d3569..cb8ca495b 100644 --- a/include/llarp.hpp +++ b/include/llarp.hpp @@ -31,7 +31,7 @@ namespace llarp struct RuntimeOptions { - bool background = false; + bool showBanner = true; bool debug = false; bool isSNode = false; }; @@ -45,6 +45,7 @@ namespace llarp std::shared_ptr nodedb = nullptr; std::string nodedb_dir; + Context(); virtual ~Context() = default; void @@ -100,14 +101,8 @@ namespace llarp virtual std::shared_ptr makeVPNPlatform(); -#ifdef ANDROID - int androidFD = -1; - int - GetUDPSocket(); -#endif - protected: std::shared_ptr config = nullptr; diff --git a/include/lokinet/lokinet_misc.h b/include/lokinet/lokinet_misc.h index dea41f9ba..cc928a0a8 100644 --- a/include/lokinet/lokinet_misc.h +++ b/include/lokinet/lokinet_misc.h @@ -7,7 +7,7 @@ extern "C" /// change our network id globally across all contexts void EXPORT - lokinet_set_netid(const char*); + lokinet_set_netid(const char* netid); /// get our current netid /// must be free()'d after use @@ -15,11 +15,32 @@ extern "C" lokinet_get_netid(); /// set log level - /// possible values: trace, debug, info, warn, error, none + /// 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*); + 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 } diff --git a/include/lokinet/lokinet_stream.h b/include/lokinet/lokinet_stream.h index d5d97754b..385a67fc8 100644 --- a/include/lokinet/lokinet_stream.h +++ b/include/lokinet/lokinet_stream.h @@ -8,7 +8,6 @@ extern "C" #endif /// the result of a lokinet stream mapping attempt -#pragma pack(1) struct lokinet_stream_result { /// set to zero on success otherwise the error that happened @@ -23,7 +22,6 @@ extern "C" /// the id of the stream we created int stream_id; }; -#pragma pack() /// connect out to a remote endpoint /// remoteAddr is in the form of "name:port" @@ -39,7 +37,7 @@ extern "C" /// 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*); + 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 * @@ -53,6 +51,9 @@ extern "C" 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 index 27b00c3e7..9e520dcd3 100644 --- a/include/lokinet/lokinet_udp.h +++ b/include/lokinet/lokinet_udp.h @@ -2,54 +2,22 @@ #include "lokinet_context.h" -#ifdef _WIN32 -extern "C" -{ - struct iovec - { - void* iov_base; - size_t iov_len; - }; -} -#else -#include -#endif - #ifdef __cplusplus extern "C" { #endif /// information about a udp flow - struct lokinet_udp_flow + struct lokinet_udp_flowinfo { - /// the socket id for this flow used for i/o purposes and closing this socket - int socket_id; /// remote endpoint's .loki or .snode address - char remote_addr[256]; - /// local endpoint's ip address - char local_addr[64]; + char remote_host[256]; /// remote endpont's port - int remote_port; - /// local endpoint's port - int local_port; + uint16_t remote_port; + /// the socket id for this flow used for i/o purposes and closing this socket + int socket_id; }; - /// establish an outbound udp flow - /// remoteHost is the remote .loki or .snode address conneting to - /// remotePort is either a string integer or an srv record name to lookup, e.g. thingservice in - /// which we do a srv lookup for _udp.thingservice.remotehost.tld and use the "best" port provided - /// localAddr is the local ip:port to bind our socket to, if localAddr is NULL then - /// lokinet_udp_sendmmsg MUST be used to send packets return 0 on success return nonzero on fail, - /// containing an errno value - int EXPORT - lokinet_udp_establish( - char* remoteHost, - char* remotePort, - char* localAddr, - struct lokinet_udp_flow* flow, - struct lokinet_context* ctx); - /// a result from a lokinet_udp_bind call struct lokinet_udp_bind_result { @@ -57,48 +25,97 @@ extern "C" 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 - /// if srv is not NULL add an srv record for this port, the format being "thingservice" in which - /// will add a srv record "_udp.thingservice.ouraddress.tld" that advertises this port provide - /// localAddr to forward inbound udp packets to "ip:port" if localAddr is NULL then the resulting - /// socket MUST be drained by lokinet_udp_recvmmsg + //// + /// @param filter MUST be non null, pointing to a flow filter for accepting new udp flows, called + /// with user data /// - /// returns 0 on success - /// returns nonzero on error in which it is an errno value + /// @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( - int exposedPort, - char* srv, - char* localAddr, - struct lokinet_udp_listen_result* result, + 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); - /// poll many udp sockets for activity - /// returns 0 on sucess + /// @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 /// - /// returns non zero errno on error + /// @param ctx the lokinet context to use + /// + /// @return 0 on success, non zero errno on fail int EXPORT - lokinet_udp_poll( - const int* socket_ids, - size_t numsockets, - const struct timespec* timeout, + lokinet_udp_establish( + lokinet_udp_create_flow_func create_flow, + void* user, + const struct lokinet_udp_flowinfo* remote, struct lokinet_context* ctx); - struct lokinet_udp_pkt - { - char remote_addr[256]; - int remote_port; - struct iovec pkt; - }; + /// @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); - /// analog to recvmmsg - ssize_t EXPORT - lokinet_udp_recvmmsg( - int socket_id, - struct lokinet_udp_pkt* events, - size_t max_events, - struct lokient_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 } diff --git a/jni/CMakeLists.txt b/jni/CMakeLists.txt index 176d4a45e..ef24506a3 100644 --- a/jni/CMakeLists.txt +++ b/jni/CMakeLists.txt @@ -2,5 +2,4 @@ add_library(lokinet-android SHARED lokinet_config.cpp lokinet_daemon.cpp) -add_log_tag(lokinet-android) -target_link_libraries(lokinet-android liblokinet) +target_link_libraries(lokinet-android lokinet-amalgum) diff --git a/jni/lokinet_daemon.cpp b/jni/lokinet_daemon.cpp index 8de704ae8..8bd90f510 100644 --- a/jni/lokinet_daemon.cpp +++ b/jni/lokinet_daemon.cpp @@ -77,24 +77,23 @@ extern "C" JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv* env, jobject self) { - auto ptr = GetImpl(env, self); - - ptr->androidFD = GetObjectMemberAsInt(env, self, "m_FD"); + 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) { - auto ptr = GetImpl(env, self); - - return ptr->GetUDPSocket(); + if (auto ptr = GetImpl(env, self); ptr and ptr->router) + return ptr->router->OutboundUDPSocket(); + return -1; } JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv* env, jclass) { std::string rangestr{}; - if (auto maybe = llarp::FindFreeRange()) + if (auto maybe = llarp::net::Platform::Default_ptr()->FindFreeRange()) { rangestr = maybe->ToString(); } diff --git a/jni/readme.md b/jni/readme.md new file mode 100644 index 000000000..0ab7564d7 --- /dev/null +++ b/jni/readme.md @@ -0,0 +1 @@ +jni binding for lokinet vpn using android vpn api diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 05a60bbc3..ce7eb7142 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -5,20 +5,11 @@ add_library(lokinet-util ${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp util/bencode.cpp util/buffer.cpp - util/fs.cpp + util/file.cpp util/json.cpp - util/logging/android_logger.cpp util/logging/buffer.cpp - util/logging/file_logger.cpp - util/logging/logger.cpp - util/logging/logger_internal.cpp - util/logging/loglevel.cpp - util/logging/ostream_logger.cpp - util/logging/syslog_logger.cpp - util/logging/win32_logger.cpp - util/lokinet_init.c + util/easter_eggs.cpp util/mem.cpp - util/printer.cpp util/str.cpp util/thread/queue_manager.cpp util/thread/threading.cpp @@ -33,75 +24,107 @@ target_link_libraries(lokinet-util PUBLIC lokinet-cryptography nlohmann_json::nlohmann_json filesystem - date::date - oxenmq::oxenmq + oxenc::oxenc + oxen::logging ) -if(ANDROID) - target_link_libraries(lokinet-util PUBLIC log) -endif() - add_library(lokinet-platform STATIC # for networking ev/ev.cpp - ev/ev_libuv.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.cpp net/net_int.cpp net/sock_addr.cpp vpn/packet_router.cpp + vpn/egres_packet_router.cpp vpn/platform.cpp ) target_link_libraries(lokinet-platform PUBLIC lokinet-cryptography lokinet-util Threads::Threads base_libs uvw) - +target_link_libraries(lokinet-platform PRIVATE oxenmq::oxenmq) if (ANDROID) - target_sources(lokinet-platform PRIVATE android/ifaddrs.c) + target_sources(lokinet-platform PRIVATE android/ifaddrs.c util/nop_service_manager.cpp) endif() if(CMAKE_SYSTEM_NAME MATCHES "Linux") - target_sources(lokinet-platform PRIVATE linux/netns.cpp) - - if(NON_PC_TARGET) - add_import_library(rt) - target_link_libraries(lokinet-platform PUBLIC rt) + target_sources(lokinet-platform PRIVATE linux/dbus.cpp) + if(WITH_SYSTEMD) + target_sources(lokinet-platform PRIVATE linux/sd_service_manager.cpp) + else() + target_sources(lokinet-platform PRIVATE util/nop_service_manager.cpp) endif() endif() if (WIN32) target_sources(lokinet-platform PRIVATE - win32/win32_inet.c - win32/win32_intrnl.c) - - target_link_libraries(lokinet-platform PUBLIC iphlpapi) + net/win32.cpp + vpn/win32.cpp + win32/service_manager.cpp + win32/exec.cpp) + add_library(lokinet-win32 STATIC + win32/dll.cpp + win32/exception.cpp) + add_library(lokinet-wintun STATIC + win32/wintun.cpp) + add_library(lokinet-windivert STATIC + win32/windivert.cpp) + + # wintun and windivert are privated linked by lokinet-platform + # this is so their details do not leak out to deps of lokinet-platform + # wintun and windivert still need things from lokinet-platform + target_compile_options(lokinet-wintun PUBLIC -I${CMAKE_BINARY_DIR}/wintun/include/) + target_compile_options(lokinet-windivert PUBLIC -I${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/include/) + target_include_directories(lokinet-windivert PUBLIC ${PROJECT_SOURCE_DIR}) + target_link_libraries(lokinet-wintun PUBLIC lokinet-platform lokinet-util lokinet-config) + target_link_libraries(lokinet-win32 PUBLIC lokinet-util) + target_link_libraries(lokinet-windivert PUBLIC oxen-logging) + target_link_libraries(lokinet-windivert PRIVATE lokinet-win32) + target_link_libraries(lokinet-platform PRIVATE lokinet-win32 lokinet-wintun lokinet-windivert) +else() + target_sources(lokinet-platform PRIVATE + net/posix.cpp) endif() + if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") target_include_directories(lokinet-platform SYSTEM PUBLIC /usr/local/include) endif() -add_library(liblokinet +add_library(lokinet-dns STATIC - config/config.cpp - config/definition.cpp - config/ini.cpp - config/key_manager.cpp - dns/message.cpp dns/name.cpp + dns/platform.cpp dns/question.cpp dns/rr.cpp dns/serialize.cpp dns/server.cpp - dns/srv_data.cpp - dns/unbound_resolver.cpp + dns/srv_data.cpp) + +if(WITH_SYSTEMD) + target_sources(lokinet-dns PRIVATE dns/nm_platform.cpp dns/sd_platform.cpp) +endif() + +target_link_libraries(lokinet-dns PUBLIC lokinet-platform uvw) +target_link_libraries(lokinet-dns PRIVATE libunbound lokinet-config) + +add_library(lokinet-config + STATIC + config/config.cpp + config/definition.cpp + config/ini.cpp + config/key_manager.cpp) + +target_link_libraries(lokinet-config PUBLIC lokinet-dns lokinet-platform oxenmq::oxenmq) - consensus/table.cpp +add_library(lokinet-amalgum + STATIC consensus/reachability_testing.cpp bootstrap.cpp @@ -130,7 +153,7 @@ add_library(liblokinet dht/taglookup.cpp endpoint_base.cpp - + exit/context.cpp exit/endpoint.cpp exit/exit_messages.cpp @@ -138,7 +161,6 @@ add_library(liblokinet exit/session.cpp handlers/exit.cpp handlers/tun.cpp - hook/shell.cpp iwp/iwp.cpp iwp/linklayer.cpp iwp/message_buffer.cpp @@ -166,7 +188,7 @@ add_library(liblokinet peerstats/types.cpp pow.cpp profiling.cpp - + quic/address.cpp quic/client.cpp quic/connection.cpp @@ -186,7 +208,7 @@ add_library(liblokinet router/rc_gossiper.cpp router/router.cpp router/route_poker.cpp - router/systemd_resolved.cpp + routing/dht_message.cpp routing/message_parser.cpp routing/path_confirm_message.cpp @@ -214,41 +236,74 @@ add_library(liblokinet service/name.cpp service/outbound_context.cpp service/protocol.cpp - service/protocol_type.cpp service/router_lookup_job.cpp service/sendcontext.cpp service/session.cpp service/tag.cpp ) -set_target_properties(liblokinet PROPERTIES OUTPUT_NAME lokinet) - -enable_lto(lokinet-util lokinet-platform liblokinet) - -if(TRACY_ROOT) - target_sources(liblokinet PRIVATE ${TRACY_ROOT}/TracyClient.cpp) -endif() +set(BOOTSTRAP_FALLBACKS) +foreach(bs IN ITEMS MAINNET TESTNET) + if(BOOTSTRAP_FALLBACK_${bs}) + message(STATUS "Building with ${bs} fallback boostrap path \"${BOOTSTRAP_FALLBACK_${bs}}\"") + file(READ "${BOOTSTRAP_FALLBACK_${bs}}" bs_data HEX) + if(bs STREQUAL TESTNET) + set(network "gamma") + elseif(bs STREQUAL MAINNET) + set(network "lokinet") + else() + string(TOLOWER "${bs}" network) + endif() + string(REGEX REPLACE "([0-9a-f][0-9a-f])" "\\\\x\\1" bs_data "${bs_data}") + set(BOOTSTRAP_FALLBACKS "${BOOTSTRAP_FALLBACKS}{\"${network}\"s, \"${bs_data}\"sv},\n") + endif() +endforeach() +configure_file("bootstrap-fallbacks.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-fallbacks.cpp" @ONLY) +target_sources(lokinet-amalgum PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-fallbacks.cpp") -if(TESTNET) - target_sources(liblokinet PRIVATE testnet.c) +if(WITH_PEERSTATS_BACKEND) + target_compile_definitions(lokinet-amalgum PRIVATE -DLOKINET_PEERSTATS_BACKEND) + target_link_libraries(lokinet-amalgum PUBLIC sqlite_orm) endif() if(WITH_HIVE) - target_sources(liblokinet PRIVATE + target_sources(lokinet-amalgum PRIVATE tooling/router_hive.cpp tooling/hive_router.cpp tooling/hive_context.cpp ) endif() -target_link_libraries(liblokinet PUBLIC cxxopts lokinet-platform lokinet-util lokinet-cryptography sqlite_orm ngtcp2) -target_link_libraries(liblokinet PRIVATE libunbound) +# TODO: make libunbound hidden behind a feature flag like sqlite for embedded lokinet +target_link_libraries(lokinet-amalgum PRIVATE libunbound) + +target_link_libraries(lokinet-amalgum PUBLIC + cxxopts + oxenc::oxenc + lokinet-platform + lokinet-config + lokinet-dns + lokinet-util + lokinet-cryptography + ngtcp2_static + oxenmq::oxenmq) + +enable_lto(lokinet-util lokinet-platform lokinet-dns lokinet-config lokinet-amalgum) + +pkg_check_modules(CRYPT libcrypt IMPORTED_TARGET) +if(CRYPT_FOUND AND NOT CMAKE_CROSSCOMPILING) + add_definitions(-DHAVE_CRYPT) + add_library(libcrypt INTERFACE) + target_link_libraries(libcrypt INTERFACE PkgConfig::CRYPT) + target_link_libraries(lokinet-amalgum PRIVATE libcrypt) + message(STATUS "using libcrypt ${CRYPT_VERSION}") +endif() if(BUILD_LIBLOKINET) include(GNUInstallDirs) add_library(lokinet-shared SHARED lokinet_shared.cpp) - target_link_libraries(lokinet-shared PUBLIC liblokinet) + target_link_libraries(lokinet-shared PUBLIC lokinet-amalgum) if(WIN32) set(CMAKE_SHARED_LIBRARY_PREFIX_CXX "") endif() @@ -256,20 +311,16 @@ if(BUILD_LIBLOKINET) if(WIN32) target_link_libraries(lokinet-shared PUBLIC ws2_32 iphlpapi -fstack-protector) install(TARGETS lokinet-shared DESTINATION bin COMPONENT liblokinet) - else() + elseif(NOT APPLE) install(TARGETS lokinet-shared LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT liblokinet) endif() - add_log_tag(lokinet-shared) endif() if(APPLE) add_subdirectory(apple) + target_sources(lokinet-platform PRIVATE util/nop_service_manager.cpp) endif() -foreach(lokinet_lib liblokinet lokinet-platform lokinet-util lokinet-cryptography) - add_log_tag(${lokinet_lib}) -endforeach() - file(GLOB_RECURSE docs_SRC */*.hpp *.hpp) set(DOCS_SRC ${docs_SRC} PARENT_SCOPE) diff --git a/llarp/apple/CMakeLists.txt b/llarp/apple/CMakeLists.txt index 58a54727a..8dd561ef7 100644 --- a/llarp/apple/CMakeLists.txt +++ b/llarp/apple/CMakeLists.txt @@ -3,16 +3,14 @@ cmake_minimum_required(VERSION 3.13) if (BUILD_SHARED_LIBS OR NOT BUILD_STATIC_DEPS OR NOT STATIC_LINK) - message(FATAL_ERROR "macOS builds require a full static build; perhaps use the contrib/macos.sh script to build?") + message(FATAL_ERROR "macOS builds require a full static build; perhaps use the contrib/mac.sh script to build?") endif() -# god made apple so that man may suffer - +# god (steve jobs) made apple so that man may suffer find_library(FOUNDATION Foundation REQUIRED) find_library(NETEXT NetworkExtension REQUIRED) find_library(COREFOUNDATION CoreFoundation REQUIRED) -target_sources(lokinet-util PRIVATE apple_logger.cpp) target_link_libraries(lokinet-util PUBLIC ${FOUNDATION}) target_sources(lokinet-platform PRIVATE vpn_platform.cpp vpn_interface.cpp route_manager.cpp context_wrapper.cpp) @@ -20,33 +18,46 @@ target_sources(lokinet-platform PRIVATE vpn_platform.cpp vpn_interface.cpp route add_executable(lokinet-extension MACOSX_BUNDLE PacketTunnelProvider.m DNSTrampoline.m - ) +) + enable_lto(lokinet-extension) -target_link_libraries(lokinet-extension PRIVATE - liblokinet - ${COREFOUNDATION} - ${NETEXT}) -# Not sure what -fapplication-extension does, but XCode puts it in so... # -fobjc-arc enables automatic reference counting for objective-C code # -e _NSExtensionMain because the appex has that instead of a `main` function entry point, of course. -target_compile_options(lokinet-extension PRIVATE -fapplication-extension -fobjc-arc) -target_link_options(lokinet-extension PRIVATE -fapplication-extension -e _NSExtensionMain) +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) +else() + target_link_options(lokinet-extension PRIVATE -e _NSExtensionMain) +endif() + +if(MACOS_SYSTEM_EXTENSION) + set(bundle_ext systemextension) + set(product_type com.apple.product-type.system-extension) +else() + set(bundle_ext appex) + set(product_type com.apple.product-type.app-extension) +endif() -target_link_libraries(lokinet-extension PUBLIC - liblokinet +target_link_libraries(lokinet-extension PRIVATE + lokinet-amalgum ${COREFOUNDATION} ${NETEXT}) set_target_properties(lokinet-extension PROPERTIES BUNDLE TRUE - BUNDLE_EXTENSION appex - MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/contrib/macos/LokinetExtension.Info.plist.in - XCODE_PRODUCT_TYPE com.apple.product-type.app-extension + BUNDLE_EXTENSION ${bundle_ext} + OUTPUT_NAME org.lokinet.network-extension + MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.Info.plist.in + XCODE_PRODUCT_TYPE ${product_type} ) -add_custom_command(TARGET lokinet-extension - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.provisionprofile - $/Contents/embedded.provisionprofile +if(CODESIGN AND CODESIGN_EXT_PROFILE) + add_custom_command(TARGET lokinet-extension + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CODESIGN_EXT_PROFILE} + $/Contents/embedded.provisionprofile ) +endif() diff --git a/llarp/apple/DNSTrampoline.h b/llarp/apple/DNSTrampoline.h index 4935d43c8..117d567b2 100644 --- a/llarp/apple/DNSTrampoline.h +++ b/llarp/apple/DNSTrampoline.h @@ -5,18 +5,19 @@ extern NSString* error_domain; /** - * "Trampoline" class that listens for UDP DNS packets on port 1053 coming from lokinet's embedded - * libunbound (when exit mode is enabled), wraps them via NetworkExtension's crappy UDP API, then - * sends responses back to libunbound to be parsed/etc. This class knows nothing about DNS, it is - * basically just a UDP packet forwarder. + * "Trampoline" class that listens for UDP DNS packets when we have exit mode enabled. These arrive + * on localhost:1053 coming from lokinet's embedded libunbound (when exit mode is enabled), wraps + * them via NetworkExtension's crappy UDP API, then sends responses back to libunbound to be + * parsed/etc. This class knows nothing about DNS, it is basically just a UDP packet forwarder, but + * using Apple magic reinvented wheel wrappers that are oh so wonderful like everything Apple. * * So for a lokinet configuration of "upstream=1.1.1.1", when exit mode is OFF: - * - DNS requests go to TUNNELIP:53, get sent to libunbound, which forwards them (directly) to the - * upstream DNS server(s). + * - DNS requests go unbound either to 127.0.0.1:53 directly (system extension) or bounced through + * TUNNELIP:53 (app extension), which forwards them (directly) to the upstream DNS server(s). * With exit mode ON: - * - DNS requests go to TUNNELIP:53, get send to libunbound, which forwards them to 127.0.0.1:1053, - * which encapsulates them in Apple's god awful crap, then (on a response) sends them back to - * libunbound. + * - DNS requests go to unbound, as above, and unbound forwards them to 127.0.0.1:1053, which + * encapsulates them in Apple's god awful crap, then (on a response) sends them back to + * libunbound to be delivered back to the requestor. * (This assumes a non-lokinet DNS; .loki and .snode get handled before either of these). */ @interface LLARPDNSTrampoline : NSObject @@ -40,6 +41,7 @@ extern NSString* error_domain; uv_async_t write_trigger; } - (void)startWithUpstreamDns:(NWUDPSession*)dns + listenIp:(NSString*)listenIp listenPort:(uint16_t)listenPort uvLoop:(uv_loop_t*)loop completionHandler:(void (^)(NSError* error))completionHandler; diff --git a/llarp/apple/DNSTrampoline.m b/llarp/apple/DNSTrampoline.m index 0a78a13e2..9ef950c4e 100644 --- a/llarp/apple/DNSTrampoline.m +++ b/llarp/apple/DNSTrampoline.m @@ -1,25 +1,34 @@ #include "DNSTrampoline.h" #include -NSString* error_domain = @"com.loki-project.lokinet"; - +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) { - if (nread < 0) { +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 (nread == 0 || !addr) + { if (buf) free(buf->base); return; } - LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*) socket->data; + 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). @@ -31,59 +40,70 @@ static void on_request(uv_udp_t* socket, ssize_t nread, const uv_buf_t* buf, con [t flushWrites]; } -static void on_sent(uv_udp_send_t* req, int status) { - NSArray* datagrams = (__bridge_transfer NSArray*) req->data; +static void +on_sent(uv_udp_send_t* req, int status) +{ + (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) { - LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*) async->data; +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]; - } - ]; + [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; } @implementation LLARPDNSTrampoline -- (void)startWithUpstreamDns:(NWUDPSession*) dns - listenPort:(uint16_t) listenPort - uvLoop:(uv_loop_t*) loop +- (void)startWithUpstreamDns:(NWUDPSession*)dns + listenIp:(NSString*)listenIp + listenPort:(uint16_t)listenPort + 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; + write_trigger.data = (__bridge void*)self; uv_async_init(loop, &write_trigger, write_flusher); - request_socket.data = (__bridge void*) self; + request_socket.data = (__bridge void*)self; uv_udp_init(loop, &request_socket); struct sockaddr_in recv_addr; - uv_ip4_addr("127.0.0.1", 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}]; + 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); } @@ -93,30 +113,40 @@ static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* b 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)); - } maxDatagrams:NSUIntegerMax]; + [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)); + } + maxDatagrams:NSUIntegerMax]; completionHandler(nil); } @@ -126,11 +156,11 @@ static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* b uv_async_send(&write_trigger); } -- (void) dealloc +- (void)dealloc { NSLog(@"Stopping DNS trampoline"); - uv_close((uv_handle_t*) &request_socket, NULL); - uv_close((uv_handle_t*) &write_trigger, NULL); + 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 b340e56cb..dfcf20311 100644 --- a/llarp/apple/PacketTunnelProvider.m +++ b/llarp/apple/PacketTunnelProvider.m @@ -3,12 +3,18 @@ #include "context_wrapper.h" #include "DNSTrampoline.h" +#define LLARP_APPLE_PACKET_BUF_SIZE 64 + @interface LLARPPacketTunnel : NEPacketTunnelProvider { void* lokinet; - @public NEPacketTunnelNetworkSettings* settings; - @public NEIPv4Route* tun_route4; - @public NEIPv6Route* tun_route6; + llarp_incoming_packet packet_buf[LLARP_APPLE_PACKET_BUF_SIZE]; + @public + NEPacketTunnelNetworkSettings* settings; + @public + NEIPv4Route* tun_route4; + @public + NEIPv6Route* tun_route6; LLARPDNSTrampoline* dns_tramp; } @@ -27,104 +33,133 @@ @end -static void nslogger(const char* msg) { NSLog(@"%s", msg); } +static void +nslogger(const char* 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; NSData* buf = [NSData dataWithBytesNoCopy:(void*)data length:size freeWhenDone:NO]; - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; - NEPacket* packet = [[NEPacket alloc] initWithData:buf protocolFamily: af]; - [t.packetFlow writePacketObjects:@[packet]]; + 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; - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; [t readPackets]; } -static void add_ipv4_route(const char* addr, const char* netmask, void* ctx) { - NEIPv4Route* route = [[NEIPv4Route alloc] - initWithDestinationAddress: [NSString stringWithUTF8String:addr] - subnetMask: [NSString stringWithUTF8String:netmask]]; +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]]; - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; + 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. + [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 arrayByAddingObject:route]; [t updateNetworkSettings]; } -static void del_ipv4_route(const char* addr, const char* netmask, void* ctx) { - 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 (int i = 0; i < routes.count; i++) { +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]) { + [routes[i].destinationSubnetMask isEqualToString:route.destinationSubnetMask]) + { [routes removeObjectAtIndex:i]; i--; } } - if (routes.count != t->settings.IPv4Settings.includedRoutes.count) { + 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) { - NEIPv6Route* route = [[NEIPv6Route alloc] - initWithDestinationAddress: [NSString stringWithUTF8String:addr] - networkPrefixLength: [NSNumber numberWithInt:prefix]]; +static void +add_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; + 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. + [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 arrayByAddingObject:route]; [t updateNetworkSettings]; } -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 (int i = 0; i < routes.count; i++) { +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]) { + [routes[i].destinationNetworkPrefixLength + isEqualToNumber:route.destinationNetworkPrefixLength]) + { [routes removeObjectAtIndex:i]; i--; } } - if (routes.count != t->settings.IPv6Settings.includedRoutes.count) { + if (routes.count != t->settings.IPv6Settings.includedRoutes.count) + { t->settings.IPv6Settings.includedRoutes = routes; [t updateNetworkSettings]; } } -static void add_default_route(void* ctx) { - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; +static void +add_default_route(void* 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]; @@ -132,8 +167,11 @@ static void add_default_route(void* ctx) { [t updateNetworkSettings]; } -static void del_default_route(void* ctx) { - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; +static void +del_default_route(void* 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]; @@ -145,12 +183,25 @@ static void del_default_route(void* ctx) { - (void)readPackets { - [self.packetFlow readPacketObjectsWithCompletionHandler: ^(NSArray* packets) { + [self.packetFlow readPacketObjectsWithCompletionHandler:^(NSArray* packets) { if (lokinet == nil) return; - for (NEPacket* p in packets) { - llarp_apple_incoming(lokinet, p.data.bytes, p.data.length); + + 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) + { + llarp_apple_incoming(lokinet, packet_buf, size); + size = 0; + } } + if (size > 0) + llarp_apple_incoming(lokinet, packet_buf, size); + [self readPackets]; }]; } @@ -167,19 +218,21 @@ static void del_default_route(void* ctx) { .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 - }, + .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"}]; + 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); } @@ -190,7 +243,14 @@ static void del_default_route(void* ctx) { // We don't have a fixed address so just stick some bogus value here: settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.3.2.1"]; - NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[ip]]; +#ifdef MACOS_SYSTEM_EXTENSION + NSString* dns_ip = [NSString stringWithUTF8String:conf.dns_bind_ip]; +#else + // 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 @@ -211,11 +271,12 @@ static void del_default_route(void* ctx) { NWHostEndpoint* upstreamdns_ep; if (strlen(conf.upstream_dns)) - upstreamdns_ep = [NWHostEndpoint endpointWithHostname:[NSString stringWithUTF8String:conf.upstream_dns] port:@(conf.upstream_dns_port).stringValue]; + 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]; + NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[ip] subnetMasks:@[mask]]; + tun_route4 = [[NEIPv4Route alloc] initWithDestinationAddress:ip subnetMask:mask]; ipv4.includedRoutes = @[tun_route4]; settings.IPv4Settings = ipv4; @@ -223,48 +284,62 @@ static void del_default_route(void* ctx) { 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]; + 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); - } - - NSLog(@"Starting DNS exit mode trampoline to %@ on 127.0.0.1:%d", upstreamdns_ep, dns_trampoline_port); - NWUDPSession* upstreamdns = [strongSelf createUDPSessionThroughTunnelToEndpoint:upstreamdns_ep fromEndpoint:nil]; - strongSelf->dns_tramp = [LLARPDNSTrampoline alloc]; - [strongSelf->dns_tramp - startWithUpstreamDns:upstreamdns - 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); - }]; - }]; + [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 { - if (lokinet) { + if (lokinet) + { llarp_apple_shutdown(lokinet); lokinet = nil; } @@ -291,20 +366,35 @@ static void del_default_route(void* ctx) { // // 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 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() +{ + [NEProvider startSystemExtensionMode]; + dispatch_main(); +} + +#endif diff --git a/llarp/apple/apple_logger.cpp b/llarp/apple/apple_logger.cpp deleted file mode 100644 index 6dca15fa3..000000000 --- a/llarp/apple/apple_logger.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "apple_logger.hpp" - -namespace llarp::apple -{ - void - NSLogStream::PreLog( - std::stringstream& ss, - LogLevel lvl, - std::string_view fname, - int lineno, - const std::string& nodename) const - { - ss << "[" << LogLevelToString(lvl) << "] "; - ss << "[" << nodename << "]" - << "(" << thread_id_string() << ") " << log_timestamp() << " " << fname << ":" << lineno - << "\t"; - } - - void - NSLogStream::Print(LogLevel, std::string_view, const std::string& msg) - { - ns_logger(msg.c_str()); - } - -} // namespace llarp::apple diff --git a/llarp/apple/apple_logger.hpp b/llarp/apple/apple_logger.hpp deleted file mode 100644 index 954d5b402..000000000 --- a/llarp/apple/apple_logger.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include - -namespace llarp::apple -{ - struct NSLogStream : public ILogStream - { - using ns_logger_callback = void (*)(const char* log_this); - - NSLogStream(ns_logger_callback logger) : ns_logger{logger} - {} - - void - PreLog( - std::stringstream& s, - LogLevel lvl, - std::string_view fname, - int lineno, - const std::string& nodename) const override; - - void - Print(LogLevel lvl, std::string_view tag, const std::string& msg) override; - - void - PostLog(std::stringstream&) const override - {} - - void - ImmediateFlush() override - {} - - void Tick(llarp_time_t) override - {} - - private: - ns_logger_callback ns_logger; - }; -} // namespace llarp::apple diff --git a/llarp/apple/context_wrapper.cpp b/llarp/apple/context_wrapper.cpp index fd662967c..e30c6c5d6 100644 --- a/llarp/apple/context_wrapper.cpp +++ b/llarp/apple/context_wrapper.cpp @@ -3,20 +3,18 @@ #include #include #include +#include #include -#include #include +#include +#include +#include #include "vpn_interface.hpp" #include "context_wrapper.h" #include "context.hpp" -#include "apple_logger.hpp" namespace { - // The default 127.0.0.1:53 won't work (because we run unprivileged) so remap it to this (unless - // specifically overridden to something else in the config): - const llarp::SockAddr DefaultDNSBind{"127.0.0.1:1153"}; - struct instance_data { llarp::apple::Context context; @@ -29,13 +27,19 @@ namespace } // namespace -const uint16_t dns_trampoline_port = 1053; +// 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) { - llarp::LogContext::Instance().logStream = - std::make_unique(appleconf->ns_logger); + 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 { @@ -43,7 +47,7 @@ llarp_apple_init(llarp_apple_config* appleconf) 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, /*router=*/false); + 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 @@ -51,7 +55,7 @@ llarp_apple_init(llarp_apple_config* appleconf) auto& range = config->network.m_ifaddr; if (!range.addr.h) { - if (auto maybe = llarp::FindFreeRange()) + if (auto maybe = llarp::net::Platform::Default_ptr()->FindFreeRange()) range = *maybe; else throw std::runtime_error{"Could not find any free IP range"}; @@ -85,10 +89,12 @@ llarp_apple_init(llarp_apple_config* appleconf) } } - // The default DNS bind setting just isn't something we can use as a non-root network extension - // so remap the default value to a high port unless explicitly set to something else. - if (config->dns.m_bind == llarp::SockAddr{"127.0.0.1:53"}) - config->dns.m_bind = DefaultDNSBind; +#ifdef MACOS_SYSTEM_EXTENSION + 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()) @@ -166,20 +172,26 @@ llarp_apple_get_uv_loop(void* lokinet) } int -llarp_apple_incoming(void* lokinet, const void* bytes, size_t size) +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 -2; + return -1; - llarp_buffer_t buf{static_cast(bytes), size}; - if (iface->OfferReadPacket(buf)) - return 0; + 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)); + } - llarp::LogError("invalid IP packet: ", llarp::buffer_printer(buf)); - return -1; + iface->MaybeWakeUpperLayers(); + return count; } void diff --git a/llarp/apple/context_wrapper.h b/llarp/apple/context_wrapper.h index 37c8a5c7b..1f09a46d3 100644 --- a/llarp/apple/context_wrapper.h +++ b/llarp/apple/context_wrapper.h @@ -82,6 +82,12 @@ extern "C" 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]; +#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 @@ -135,12 +141,23 @@ extern "C" uv_loop_t* llarp_apple_get_uv_loop(void* lokinet); - /// Called to deliver an incoming packet from the apple layer into lokinet; returns 0 on success, - /// -1 if the packet could not be parsed, -2 if there is no current active VPNInterface associated - /// with the lokinet (which generally means llarp_apple_start wasn't called or failed, or lokinet - /// is in the process of shutting down). + /// 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 void* bytes, size_t size); + 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. diff --git a/llarp/apple/route_manager.cpp b/llarp/apple/route_manager.cpp index 0bf170577..021095eaf 100644 --- a/llarp/apple/route_manager.cpp +++ b/llarp/apple/route_manager.cpp @@ -19,7 +19,7 @@ namespace llarp::apple } std::shared_ptr tun; - router->hiddenServiceContext().ForEachService([&tun](const auto& name, const auto ep) { + router->hiddenServiceContext().ForEachService([&tun](const auto& /*name*/, const auto ep) { tun = std::dynamic_pointer_cast(ep); return !tun; }); @@ -31,21 +31,23 @@ namespace llarp::apple } if (enable) - saved_upstream_dns = - tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, huint16_t{dns_trampoline_port}}}); + tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, {dns_trampoline_port}}}); else - tun->ReconfigureDNS(std::move(saved_upstream_dns)); + tun->ReconfigureDNS(router->GetConfig()->dns.m_upstreamDNS); + trampoline_active = enable; } - void RouteManager::AddDefaultRouteViaInterface(std::string) + 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::DelDefaultRouteViaInterface(std::string) + void + RouteManager::DelDefaultRouteViaInterface(vpn::NetworkInterface&) { check_trampoline(false); if (callback_context and route_callbacks.del_default_route) diff --git a/llarp/apple/route_manager.hpp b/llarp/apple/route_manager.hpp index cb8bb3f1b..69403c04d 100644 --- a/llarp/apple/route_manager.hpp +++ b/llarp/apple/route_manager.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include "context_wrapper.h" namespace llarp::apple @@ -10,24 +10,24 @@ namespace llarp::apple { public: RouteManager(llarp::Context& ctx, llarp_route_callbacks rcs, void* callback_context) - : context{ctx}, route_callbacks{std::move(rcs)}, callback_context{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(IPVariant_t ip, IPVariant_t gateway) override + AddRoute(net::ipaddr_t /*ip*/, net::ipaddr_t /*gateway*/) override {} void - DelRoute(IPVariant_t ip, IPVariant_t gateway) override + DelRoute(net::ipaddr_t /*ip*/, net::ipaddr_t /*gateway*/) override {} void - AddDefaultRouteViaInterface(std::string ifname) override; + AddDefaultRouteViaInterface(vpn::NetworkInterface& vpn) override; void - DelDefaultRouteViaInterface(std::string ifname) override; + DelDefaultRouteViaInterface(vpn::NetworkInterface& vpn) override; void AddRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override; @@ -35,20 +35,19 @@ namespace llarp::apple void DelRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override; - virtual std::vector - GetGatewaysNotOnInterface(std::string ifname) 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.push_back(huint32_t{0}); + std::vector ret; + ret.emplace_back(net::ipv4addr_t{}); return ret; } private: llarp::Context& context; bool trampoline_active = false; - std::vector saved_upstream_dns; void check_trampoline(bool enable); diff --git a/llarp/apple/vpn_interface.cpp b/llarp/apple/vpn_interface.cpp index 079e24aa9..f0b3a6c32 100644 --- a/llarp/apple/vpn_interface.cpp +++ b/llarp/apple/vpn_interface.cpp @@ -1,12 +1,19 @@ #include "vpn_interface.hpp" #include "context.hpp" +#include namespace llarp::apple { VPNInterface::VPNInterface( - Context& ctx, packet_write_callback packet_writer, on_readable_callback on_readable) - : m_PacketWriter{std::move(packet_writer)}, m_OnReadable{std::move(on_readable)} + 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); }); } @@ -21,16 +28,16 @@ namespace llarp::apple return true; } - int - VPNInterface::PollFD() const + void + VPNInterface::MaybeWakeUpperLayers() const { - return -1; + _router->TriggerPump(); } - std::string - VPNInterface::IfName() const + int + VPNInterface::PollFD() const { - return ""; + return -1; } net::IPPacket @@ -46,7 +53,7 @@ namespace llarp::apple VPNInterface::WritePacket(net::IPPacket pkt) { int af_family = pkt.IsV6() ? AF_INET6 : AF_INET; - return m_PacketWriter(af_family, pkt.buf, pkt.sz); + return m_PacketWriter(af_family, pkt.data(), pkt.size()); } } // namespace llarp::apple diff --git a/llarp/apple/vpn_interface.hpp b/llarp/apple/vpn_interface.hpp index c1dff8dbf..b030afd9a 100644 --- a/llarp/apple/vpn_interface.hpp +++ b/llarp/apple/vpn_interface.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -17,7 +17,10 @@ namespace llarp::apple using on_readable_callback = std::function; explicit VPNInterface( - Context& ctx, packet_write_callback packet_writer, on_readable_callback on_readable); + Context& ctx, + packet_write_callback packet_writer, + on_readable_callback on_readable, + AbstractRouter* router); // Method to call when a packet has arrived to deliver the packet to lokinet bool @@ -26,15 +29,15 @@ namespace llarp::apple int PollFD() const override; - std::string - IfName() const override; - net::IPPacket ReadNextPacket() override; bool WritePacket(net::IPPacket pkt) 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. @@ -46,6 +49,8 @@ namespace llarp::apple static inline constexpr auto PacketQueueSize = 1024; thread::Queue m_ReadQueue{PacketQueueSize}; + + AbstractRouter* const _router; }; } // namespace llarp::apple diff --git a/llarp/apple/vpn_platform.cpp b/llarp/apple/vpn_platform.cpp index b11c0b05b..1d1eafb8e 100644 --- a/llarp/apple/vpn_platform.cpp +++ b/llarp/apple/vpn_platform.cpp @@ -15,8 +15,9 @@ namespace llarp::apple , m_OnReadable{std::move(on_readable)} {} - std::shared_ptr VPNPlatform::ObtainInterface(vpn::InterfaceInfo) + std::shared_ptr + VPNPlatform::ObtainInterface(vpn::InterfaceInfo, AbstractRouter* router) { - return std::make_shared(m_Context, m_PacketWriter, m_OnReadable); + return std::make_shared(m_Context, m_PacketWriter, m_OnReadable, router); } } // namespace llarp::apple diff --git a/llarp/apple/vpn_platform.hpp b/llarp/apple/vpn_platform.hpp index 04ce75646..3e9023ee6 100644 --- a/llarp/apple/vpn_platform.hpp +++ b/llarp/apple/vpn_platform.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "vpn_interface.hpp" #include "route_manager.hpp" @@ -16,7 +16,8 @@ namespace llarp::apple llarp_route_callbacks route_callbacks, void* callback_context); - std::shared_ptr ObtainInterface(vpn::InterfaceInfo) override; + std::shared_ptr + ObtainInterface(vpn::InterfaceInfo, AbstractRouter*) override; vpn::IRouteManager& RouteManager() override diff --git a/llarp/bootstrap-fallbacks.cpp.in b/llarp/bootstrap-fallbacks.cpp.in new file mode 100644 index 000000000..5c81ea716 --- /dev/null +++ b/llarp/bootstrap-fallbacks.cpp.in @@ -0,0 +1,25 @@ +#include +#include "llarp/bootstrap.hpp" + +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{ +@BOOTSTRAP_FALLBACKS@ + }) + // clang-format on + { + llarp_buffer_t buf{bootstrap.data(), bootstrap.size()}; + auto& bsl = fallbacks[network]; + bsl.BDecode(&buf); + } + return fallbacks; + } +} // namespace llarp diff --git a/llarp/bootstrap.cpp b/llarp/bootstrap.cpp index f21f3a025..32f830bdc 100644 --- a/llarp/bootstrap.cpp +++ b/llarp/bootstrap.cpp @@ -1,5 +1,8 @@ #include "bootstrap.hpp" #include "util/bencode.hpp" +#include "util/logging.hpp" +#include "util/logging/buffer.hpp" +#include "util/fs.hpp" namespace llarp { @@ -16,9 +19,12 @@ namespace llarp [&](llarp_buffer_t* b, bool more) -> bool { if (more) { - RouterContact rc; + 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; @@ -31,4 +37,35 @@ namespace llarp { return BEncodeWriteList(begin(), end(), buf); } + + void + BootstrapList::AddFromFile(fs::path fpath) + { + bool isListFile = false; + { + std::ifstream inf(fpath.c_str(), std::ios::binary); + if (inf.is_open()) + { + const char ch = inf.get(); + isListFile = ch == 'l'; + } + } + if (isListFile) + { + if (not BDecodeReadFile(fpath, *this)) + { + throw std::runtime_error{fmt::format("failed to read bootstrap list file '{}'", fpath)}; + } + } + else + { + 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); + } + } } // namespace llarp diff --git a/llarp/bootstrap.hpp b/llarp/bootstrap.hpp index e9a62d7e9..c4a4b7d3f 100644 --- a/llarp/bootstrap.hpp +++ b/llarp/bootstrap.hpp @@ -2,6 +2,8 @@ #include "router_contact.hpp" #include +#include +#include "llarp/util/fs.hpp" namespace llarp { @@ -13,7 +15,14 @@ namespace llarp bool BEncode(llarp_buffer_t* buf) const; + void + AddFromFile(fs::path fpath); + void Clear(); }; + + std::unordered_map + load_bootstrap_fallbacks(); + } // namespace llarp diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index 3a2719343..67d92fa2f 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -4,22 +4,23 @@ #include "config/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 namespace llarp { @@ -33,6 +34,17 @@ namespace llarp constexpr int DefaultPublicPort = 1090; using namespace config; + namespace + { + struct ConfigGenParameters_impl : public ConfigGenParameters + { + const llarp::net::Platform* + Net_ptr() const + { + return llarp::net::Platform::Default_ptr(); + } + }; + } // namespace void RouterConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) @@ -58,8 +70,8 @@ namespace llarp }, [this](std::string arg) { if (arg.size() > NetID::size()) - throw std::invalid_argument( - stringify("netid is too long, max length is ", NetID::size())); + throw std::invalid_argument{ + fmt::format("netid is too long, max length is {}", NetID::size())}; m_netId = std::move(arg); }); @@ -75,7 +87,8 @@ namespace llarp }, [=](int arg) { if (arg < minConnections) - throw std::invalid_argument(stringify("min-connections must be >= ", minConnections)); + throw std::invalid_argument{ + fmt::format("min-connections must be >= {}", minConnections)}; m_minConnectedRouters = arg; }); @@ -91,7 +104,8 @@ namespace llarp }, [=](int arg) { if (arg < maxConnections) - throw std::invalid_argument(stringify("max-connections must be >= ", maxConnections)); + throw std::invalid_argument{ + fmt::format("max-connections must be >= {}", maxConnections)}; m_maxConnectedRouters = arg; }); @@ -110,8 +124,8 @@ namespace llarp if (arg.empty()) throw std::invalid_argument("[router]:data-dir is empty"); if (not fs::exists(arg)) - throw std::runtime_error( - stringify("Specified [router]:data-dir ", arg, " does not exist")); + throw std::runtime_error{ + fmt::format("Specified [router]:data-dir {} does not exist", arg)}; m_dataDir = std::move(arg); }); @@ -125,33 +139,24 @@ namespace llarp "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) { - if (not arg.empty()) - { - llarp::LogInfo("public ip ", arg, " size ", arg.size()); + [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 (arg.size() > 15) - throw std::invalid_argument(stringify("Not a valid IPv4 addr: ", arg)); + if (net->IsBogonIP(addr)) + throw std::invalid_argument{ + fmt::format("{} is not a publicly routable ip address", addr)}; - m_publicAddress.setAddress(arg); - } + PublicIP = addr; }); - conf.defineOption("router", "public-address", Hidden, [this](std::string arg) { - if (not arg.empty()) - { - llarp::LogWarn( - "*** WARNING: The config option [router]:public-address=", - arg, - " is deprecated, use public-ip=", - arg, - " instead to avoid this warning and avoid future configuration problems."); - - if (arg.size() > 15) - throw std::invalid_argument(stringify("Not a valid IPv4 addr: ", arg)); - - m_publicAddress.setAddress(arg); - } + 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( @@ -166,8 +171,7 @@ namespace llarp [this](int arg) { if (arg <= 0 || arg > std::numeric_limits::max()) throw std::invalid_argument("public-port must be >= 0 and <= 65536"); - - m_publicAddress.setPort(arg); + PublicPort = ToNet(huint16_t{static_cast(arg)}); }); conf.defineOption( @@ -296,9 +300,9 @@ namespace llarp throw std::invalid_argument{"duplicate strict connect snode: " + value}; }, Comment{ - "Public key of a router which will act as a pinned first-hop. This may be used to", + "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).", + "first hop). This REQUIRES two or more nodes to be specified.", }); conf.defineOption( @@ -317,7 +321,7 @@ namespace llarp ClientOnly, Comment{ "Set the endpoint authentication mechanism.", - "none/whitelist/lmq", + "none/whitelist/lmq/file", }, [this](std::string arg) { if (arg.empty()) @@ -362,10 +366,46 @@ namespace llarp [this](std::string arg) { service::Address addr; if (not addr.FromString(arg)) - throw std::invalid_argument(stringify("bad loki address: ", 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", @@ -373,7 +413,7 @@ namespace llarp ReachableDefault, AssignmentAcceptor(m_reachable), Comment{ - "Determines whether we will publish our snapp's introset to the DHT.", + "Determines whether we will pubish our snapp's introset to the DHT.", }); conf.defineOption( @@ -399,7 +439,7 @@ namespace llarp }, [this](int arg) { if (arg < 3 or arg > 8) - throw std::invalid_argument("[endpoint]:paths must be >= 2 and <= 8"); + throw std::invalid_argument("[endpoint]:paths must be >= 3 and <= 8"); m_Paths = arg; }); @@ -419,9 +459,8 @@ namespace llarp "owned-range", MultiValue, Comment{ - "When in exit mode announce we allow a private range in our introset" - "exmaple:", - "owned-range=10.0.0.0/24", + "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; @@ -435,12 +474,17 @@ namespace llarp "traffic-whitelist", MultiValue, Comment{ - "List of ip traffic whitelist, anything not specified will be dropped by us." - "examples:", - "tcp for all tcp traffic regardless of port", - "0x69 for all packets using ip protocol 0x69" - "udp/53 for udp port 53", - "tcp/smtp for smtp port", + "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) @@ -454,11 +498,15 @@ namespace llarp "network", "exit-node", ClientOnly, + MultiValue, Comment{ "Specify a `.loki` address and an optional ip range to use as an exit broker.", - "Example:", - "exit-node=whatever.loki # maps all exit traffic to whatever.loki", - "exit-node=stuff.loki:100.0.0.0/24 # maps 100.0.0.0/24 to stuff.loki", + "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()) @@ -487,7 +535,7 @@ namespace llarp if (arg != "null" and not exit.FromString(arg)) { - throw std::invalid_argument(stringify("[network]:exit-node bad address: ", arg)); + throw std::invalid_argument{fmt::format("[network]:exit-node bad address: {}", arg)}; } m_ExitMap.Insert(range, exit); }); @@ -496,12 +544,13 @@ namespace llarp "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 time to store codes for different exit nodes.", + "Can be specified multiple times to store codes for different exit nodes.", }, [this](std::string arg) { if (arg.empty()) @@ -531,12 +580,36 @@ namespace llarp m_ExitAuths.emplace(exit, auth); }); + 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", - "lokinetN, starting at 0 (e.g. lokinet0, lokinet1, ...).", + "matching 'lokinetN', starting at N=0 (e.g. lokinet0, lokinet1, ...).", }, AssignmentAcceptor(m_ifname)); @@ -551,7 +624,7 @@ namespace llarp [this](std::string arg) { if (not m_ifaddr.FromString(arg)) { - throw std::invalid_argument(stringify("[network]:ifaddr invalid value: '", arg, "'")); + throw std::invalid_argument{fmt::format("[network]:ifaddr invalid value: '{}'", arg)}; } }); @@ -560,10 +633,10 @@ namespace llarp "ip6-range", ClientOnly, Comment{ - "For all ipv6 exit traffic you will use this as the base address bitwised or'd with " + "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 " + "!!! WARNING !!! Disabling ipv6 tunneling when you have ipv6 routes WILL lead to ", "de-anonymization as lokinet will no longer carry your ipv6 traffic.", }, IP6RangeDefault, @@ -578,9 +651,10 @@ namespace llarp } m_baseV6Address = huint128_t{}; if (not m_baseV6Address->FromString(arg)) - throw std::invalid_argument( - stringify("[network]:ip6-range invalid value: '", arg, "'")); + throw std::invalid_argument{ + fmt::format("[network]:ip6-range invalid value: '{}'", arg)}; }); + // TODO: could be useful for snodes in the future, but currently only implemented for clients: conf.defineOption( "network", @@ -601,7 +675,7 @@ namespace llarp const auto pos = arg.find(":"); if (pos == std::string::npos) { - throw std::invalid_argument(stringify("[endpoint]:mapaddr invalid entry: ", arg)); + 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); @@ -610,18 +684,19 @@ namespace llarp huint32_t ipv4; if (not ipv4.FromString(ipstr)) { - throw std::invalid_argument(stringify("[endpoint]:mapaddr invalid ip: ", ipstr)); + throw std::invalid_argument{fmt::format("[endpoint]:mapaddr invalid ip: {}", ipstr)}; } ip = net::ExpandV4(ipv4); } if (not addr.FromString(addrstr)) { - throw std::invalid_argument( - stringify("[endpoint]:mapaddr invalid addresss: ", addrstr)); + throw std::invalid_argument{ + fmt::format("[endpoint]:mapaddr invalid addresss: {}", addrstr)}; } if (m_mapAddrs.find(ip) != m_mapAddrs.end()) { - throw std::invalid_argument(stringify("[endpoint]:mapaddr ip already mapped: ", ipstr)); + throw std::invalid_argument{ + fmt::format("[endpoint]:mapaddr ip already mapped: {}", ipstr)}; } m_mapAddrs[ip] = addr; }); @@ -638,11 +713,11 @@ namespace llarp [this](std::string arg) { RouterID id; if (not id.FromString(arg)) - throw std::invalid_argument(stringify("Invalid RouterID: ", 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(stringify("Duplicate blacklist-snode: ", arg)); + throw std::invalid_argument{fmt::format("Duplicate blacklist-snode: {}", arg)}; }); // TODO: support SRV records for routers, but for now client only @@ -652,14 +727,18 @@ namespace llarp ClientOnly, MultiValue, Comment{ - "Specify SRV Records for services hosted on the SNApp", - "for more info see https://docs.loki.network/Lokinet/Guides/HostingSNApps/", - "srv=_service._protocol priority weight port target.loki", + "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(stringify("Invalid SRV Record string: ", arg)); + throw std::invalid_argument{fmt::format("Invalid SRV Record string: {}", arg)}; m_SRVRecords.push_back(std::move(newSRV)); }); @@ -669,8 +748,8 @@ namespace llarp "path-alignment-timeout", ClientOnly, Comment{ - "time in seconds how long to wait for a path to align to pivot routers", - "if not provided a sensible default will be used", + "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) @@ -685,9 +764,10 @@ namespace llarp ClientOnly, Default{fs::path{params.defaultDataDir / "addrmap.dat"}}, Comment{ - "persist mapped ephemeral addresses to a file", - "on restart the mappings will be loaded so that ip addresses will not be mapped to a " - "different address", + "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()) @@ -707,22 +787,26 @@ namespace llarp // 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__ - constexpr Default DefaultDNSBind{"127.3.2.1:53"}; +#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 - constexpr Default DefaultDNSBind{"127.0.0.1:53"}; + 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{"1.1.1.1"}; + constexpr Default DefaultUpstreamDNS{"9.9.9.10:53"}; m_upstreamDNS.emplace_back(DefaultUpstreamDNS.val); - if (!m_upstreamDNS.back().getPort()) - m_upstreamDNS.back().setPort(53); conf.defineOption( "dns", "upstream", - DefaultUpstreamDNS, MultiValue, Comment{ "Upstream resolver(s) to use as fallback for non-loki addresses.", @@ -734,25 +818,52 @@ namespace llarp m_upstreamDNS.clear(); first = false; } - if (!arg.empty()) + if (not arg.empty()) { auto& entry = m_upstreamDNS.emplace_back(std::move(arg)); - if (!entry.getPort()) + if (not entry.getPort()) entry.setPort(53); } }); + 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, +#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) { - m_bind = SockAddr{std::move(arg)}; - if (!m_bind.getPort()) - m_bind.setPort(53); + 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( @@ -765,7 +876,7 @@ namespace llarp return; if (not fs::exists(path)) throw std::invalid_argument{ - stringify("cannot add hosts file ", path, " as it does not seem to exist")}; + fmt::format("cannot add hosts file {} as it does not exist", path)}; m_hostfiles.emplace_back(std::move(path)); }); @@ -779,113 +890,204 @@ namespace llarp "(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.addUndeclaredHandler("dns", [this](auto, std::string_view key, std::string_view val) { + m_ExtraOpts.emplace(key, val); + }); } - LinksConfig::LinkInfo - LinksConfig::LinkInfoFromINIValues(std::string_view name, std::string_view value) + void + LinksConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) { - // we treat the INI k:v pair as: - // k: interface name, * indicating outbound - // v: a comma-separated list of values, an int indicating port (everything else ignored) - // this is somewhat of a backwards- and forwards-compatibility thing + 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.", + }); - LinkInfo info; - info.port = 0; - info.addressFamily = AF_INET; + const auto* net_ptr = params.Net_ptr(); - if (name == "address") - { - const IpAddress addr{value}; - if (not addr.hasPort()) - throw std::invalid_argument("no port provided in link address"); - info.interface = addr.toHost(); - info.port = *addr.getPort(); - } - else - { - info.interface = std::string{name}; + static constexpr Default DefaultInboundPort{uint16_t{1090}}; + static constexpr Default DefaultOutboundPort{uint16_t{0}}; - std::vector splits = split(value, ","); - for (std::string_view str : splits) - { - int asNum = std::atoi(str.data()); - if (asNum > 0) - info.port = asNum; + 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); }); - // otherwise, ignore ("future-proofing") + 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 + { + addr = SockAddr{arg}; + if (net_ptr->IsLoopbackAddress(addr->getIP())) + throw std::invalid_argument{fmt::format("{} is a loopback address", arg)}; + } + } + if (not addr) + { + // infer public address + if (auto maybe_ifname = net_ptr->GetBestNetIF()) + addr = net_ptr->GetInterfaceAddr(*maybe_ifname); } - } - - return info; - } - void - LinksConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) - { - constexpr Default DefaultOutboundLinkValue{"0"}; + if (addr) + { + // set port if not explicitly provided + if (addr->getPort() == 0) + addr->setPort(default_port); + } + return addr; + }; - conf.addSectionComments( + conf.defineOption( "bind", - { - "This section specifies network interface names and/or IPs as keys, and", - "ports as values to control the address(es) on which Lokinet listens for", - "incoming data.", - "", - "Examples:", + "inbound", + RelayOnly, + MultiValue, + Comment{ + "IP and/or port to listen on for incoming connections.", "", - " eth0=1090", - " 0.0.0.0=1090", - " 1.2.3.4=1090", + "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.", "", - "The first bind to port 1090 on the network interface 'eth0'; the second binds", - "to port 1090 on all local network interfaces; and the third example binds to", - "port 1090 on the given IP address.", + "Examples:", + " inbound=15.5.29.5:443", + " inbound=10.0.2.2", + " inbound=:1234", "", - "If a private range IP address (or an interface with a private IP) is given, or", - "if the 0.0.0.0 all-address IP is given then you must also specify the", - "public-ip= and public-port= settings in the [router] section with a public", - "address at which this router can be reached.", - "" - "Typically this section can be left blank: if no inbound bind addresses are", - "configured then lokinet will search for a local network interface with a public", - "IP address and use that (with port 1090).", + "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", - "*", - DefaultOutboundLinkValue, - Comment{ - "Specify a source port for **outgoing** Lokinet traffic, for example if you want to", - "set up custom firewall rules based on the originating port. Typically this should", - "be left unset to automatically choose random source ports.", + "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](std::string arg) { m_OutboundLink = LinkInfoFromINIValues("*", arg); }); + [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)); + }); - if (params.isRelay) - { - if (std::string best_if; GetBestNetIF(best_if)) - m_InboundLinks.push_back(LinkInfoFromINIValues(best_if, std::to_string(DefaultPublicPort))); - } conf.addUndeclaredHandler( - "bind", - [&, defaulted = true]( - std::string_view, std::string_view name, std::string_view value) mutable { - if (defaulted) + "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) { - m_InboundLinks.clear(); // Clear the default - defaulted = false; + 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)}; - LinkInfo info = LinkInfoFromINIValues(name, value); - - if (info.port <= 0) - throw std::invalid_argument( - stringify("Invalid [bind] port specified on interface", name)); - - assert(name != "*"); // handled by defineOption("bind", "*", ...) above - - m_InboundLinks.emplace_back(std::move(info)); + InboundListenAddrs.emplace_back(std::move(*addr)); }); } @@ -898,14 +1100,11 @@ namespace llarp "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(stringify( - "Specified bootstrap file ", + throw std::runtime_error{fmt::format( + "Specified bootstrap file {} specified in [{}]:{} does not exist", value, - "specified in [", section, - "]:", - name, - " does not exist")); + name)}; routers.emplace_back(std::move(file)); return true; @@ -963,7 +1162,7 @@ namespace llarp RelayOnly, Default{true}, Comment{ - "Whether or not we should talk to lokid. Must be enabled for staked routers.", + "Whether or not we should talk to oxend. Must be enabled for staked routers.", }, AssignmentAcceptor(whitelistRouters)); @@ -972,21 +1171,22 @@ namespace llarp return; throw std::invalid_argument( "the [lokid]:jsonrpc option is no longer supported; please use the [lokid]:rpc config " - "option instead with lokid's lmq-local-control address -- typically a value such as " - "rpc=ipc:///var/lib/loki/lokid.sock or rpc=ipc:///home/snode/.loki/lokid.sock"); + "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{ - "lokimq control address for for communicating with lokid. Depends on lokid's", + "oxenmq control address for for communicating with oxend. Depends on oxend's", "lmq-local-control configuration option. By default this value should be", - "ipc://LOKID-DATA-DIRECTORY/lokid.sock, such as:", - " rpc=ipc:///var/lib/loki/lokid.sock", - " rpc=ipc:///home/USER/.loki/lokid.sock", - "but can use (non-default) TCP if lokid is configured that way:", + "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); }); @@ -1015,7 +1215,7 @@ namespace llarp "add-node", MultiValue, Comment{ - "Specify a bootstrap file containing a signed RouterContact of a service node", + "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) { @@ -1036,38 +1236,29 @@ namespace llarp { (void)params; - constexpr Default DefaultLogType{"file"}; + constexpr Default DefaultLogType{ + platform::is_android or platform::is_apple ? "system" : "print"}; constexpr Default DefaultLogFile{""}; - constexpr Default DefaultLogLevel{"warn"}; + + const Default DefaultLogLevel{params.isRelay ? "warn" : "info"}; conf.defineOption( "logging", "type", DefaultLogType, - [this](std::string arg) { - LogType type = LogTypeFromString(arg); - if (type == LogType::Unknown) - throw std::invalid_argument(stringify("invalid log type: ", arg)); - - m_logType = type; - }, + [this](std::string arg) { m_logType = log::type_from_string(arg); }, Comment{ "Log type (format). Valid options are:", - " file - plaintext formatting", - " syslog - logs directed to syslog", + " print - print logs to standard output", + " system - logs directed to the system logger (syslog/eventlog/etc.)", + " file - plaintext formatting to a file", }); conf.defineOption( "logging", "level", DefaultLogLevel, - [this](std::string arg) { - std::optional level = LogLevelFromString(arg); - if (not level) - throw std::invalid_argument(stringify("invalid log level value: ", arg)); - - m_logLevel = *level; - }, + [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:", @@ -1076,6 +1267,8 @@ namespace llarp " info", " warn", " error", + " critical", + " none", }); conf.defineOption( @@ -1084,9 +1277,7 @@ namespace llarp DefaultLogFile, AssignmentAcceptor(m_logFile), Comment{ - "When using type=file this is the output filename. If given the value 'stdout' or", - "left empty then logging is printed as standard output rather than written to a", - "file.", + "When using type=file this is the output filename.", }); } @@ -1114,9 +1305,9 @@ namespace llarp m_UniqueHopsNetmaskSize = arg; }, Comment{ - "Netmask for router path selection; each router must be from a distinct IP subnet " + "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 distinct /16 IP addresses."}); + "E.g. 16 ensures that all routers are using IPs from distinct /16 IP ranges."}); #ifdef WITH_GEOIP conf.defineOption( @@ -1128,9 +1319,11 @@ namespace llarp 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", - "e.g. exclude-country=DE", - "can be listed multiple times to exclude multiple countries"}); + "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 } @@ -1155,8 +1348,14 @@ namespace llarp return true; } - Config::Config(fs::path datadir) - : m_DataDir(datadir.empty() ? fs::current_path() : std::move(datadir)) + 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"; }; @@ -1177,15 +1376,25 @@ namespace llarp } void - Config::LoadOverrides() + Config::LoadOverrides(ConfigDefinition& conf) const { + ConfigParser parser; const auto overridesDir = GetOverridesDir(m_DataDir); if (fs::exists(overridesDir)) { util::IterDir(overridesDir, [&](const fs::path& overrideFile) { if (overrideFile.extension() == ".ini") { - m_Parser.LoadFile(overrideFile); + 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) + { + conf.addConfigValue(section, pair.first, pair.second); + } + }); } return true; }); @@ -1199,80 +1408,72 @@ namespace llarp } bool - Config::Load(std::optional fname, bool isRelay) + Config::LoadConfigData(std::string_view ini, std::optional filename, bool isRelay) { - if (not fname.has_value()) - return LoadDefault(isRelay); - try + auto params = MakeGenParams(); + params->isRelay = isRelay; + params->defaultDataDir = m_DataDir; + ConfigDefinition conf{isRelay}; + addBackwardsCompatibleConfigOptions(conf); + initializeConfig(conf, *params); + + for (const auto& item : m_Additional) { - ConfigGenParameters params; - params.isRelay = isRelay; - params.defaultDataDir = m_DataDir; - - ConfigDefinition conf{isRelay}; - initializeConfig(conf, params); - addBackwardsCompatibleConfigOptions(conf); - m_Parser.Clear(); - if (!m_Parser.LoadFile(*fname)) + conf.addConfigValue(item[0], item[1], item[2]); + } + + m_Parser.Clear(); + + if (filename) + m_Parser.Filename(*filename); + else + m_Parser.Filename(fs::path{}); + + if (not m_Parser.LoadFromStr(ini)) + return false; + + m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) { + for (const auto& pair : values) { - return false; + conf.addConfigValue(section, pair.first, pair.second); } - LoadOverrides(); + }); - m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) { - for (const auto& pair : values) - { - conf.addConfigValue(section, pair.first, pair.second); - } - }); + LoadOverrides(conf); - conf.acceptAllOptions(); + conf.process(); - return true; - } - catch (const std::exception& e) - { - LogError("Error trying to init and parse config from file: ", e.what()); - return false; - } + return true; } bool - Config::LoadDefault(bool isRelay) + Config::Load(std::optional fname, bool isRelay) { - try + std::string ini; + if (fname) { - ConfigGenParameters params; - params.isRelay = isRelay; - params.defaultDataDir = m_DataDir; - ConfigDefinition conf{isRelay}; - initializeConfig(conf, params); - - m_Parser.Clear(); - LoadOverrides(); - - /// load additional config options added - for (const auto& [sect, key, val] : m_Additional) + try { - conf.addConfigValue(sect, key, val); + ini = util::slurp_file(*fname); } + catch (const std::exception&) + { + return false; + } + } + return LoadConfigData(ini, fname, isRelay); + } - m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) { - for (const auto& pair : values) - { - conf.addConfigValue(section, pair.first, pair.second); - } - }); - - conf.acceptAllOptions(); + bool + Config::LoadString(std::string_view ini, bool isRelay) + { + return LoadConfigData(ini, std::nullopt, isRelay); + } - return true; - } - catch (const std::exception& e) - { - LogError("Error trying to init default config: ", e.what()); - return false; - } + bool + Config::LoadDefault(bool isRelay) + { + return LoadString("", isRelay); } void @@ -1336,12 +1537,15 @@ namespace llarp confStr = config.generateBaseClientConfig(); // open a filestream - auto stream = llarp::util::OpenFileStream(confFile.c_str(), std::ios::binary); - if (not stream or not stream->is_open()) - throw std::runtime_error(stringify("Failed to open file ", confFile, " for writing")); - - *stream << confStr; - stream->flush(); + try + { + util::dump_file(confFile, confStr); + } + catch (const std::exception& e) + { + throw std::runtime_error{ + fmt::format("Failed to write config data to {}: {}", confFile, e.what())}; + } llarp::LogInfo("Generated new config ", confFile); } @@ -1395,12 +1599,12 @@ namespace llarp std::string Config::generateBaseClientConfig() { - ConfigGenParameters params; - params.isRelay = false; - params.defaultDataDir = m_DataDir; + auto params = MakeGenParams(); + params->isRelay = false; + params->defaultDataDir = m_DataDir; llarp::ConfigDefinition def{false}; - initializeConfig(def, params); + initializeConfig(def, *params); generateCommonConfigComments(def); def.addSectionComments( "paths", @@ -1420,19 +1624,19 @@ namespace llarp std::string Config::generateBaseRouterConfig() { - ConfigGenParameters params; - params.isRelay = true; - params.defaultDataDir = m_DataDir; + auto params = MakeGenParams(); + params->isRelay = true; + params->defaultDataDir = m_DataDir; llarp::ConfigDefinition def{true}; - initializeConfig(def, params); + initializeConfig(def, *params); generateCommonConfigComments(def); - // lokid + // oxend def.addSectionComments( "lokid", { - "Settings for communicating with lokid", + "Settings for communicating with oxend", }); return def.generateINIConfig(true); @@ -1441,9 +1645,9 @@ namespace llarp std::shared_ptr Config::EmbeddedConfig() { - auto config = std::make_shared(fs::path{}); + auto config = std::make_shared(); config->Load(); - config->logging.m_logLevel = eLogNone; + config->logging.m_logLevel = log::Level::off; config->api.m_enableRPCServer = false; config->network.m_endpointType = "null"; config->network.m_saveProfiles = false; diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 03f7a000c..54dbcc443 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -1,10 +1,12 @@ #pragma once #include +#include #include #include #include #include +#include #include "ini.hpp" #include "definition.hpp" #include @@ -37,8 +39,18 @@ namespace llarp /// 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; }; struct RouterConfig @@ -53,8 +65,6 @@ namespace llarp bool m_blockBogons = false; - IpAddress m_publicAddress; - int m_workerThreads = -1; int m_numNetThreads = -1; @@ -66,6 +76,10 @@ namespace llarp std::string m_transportKeyFile; bool m_isRelay = false; + /// deprecated + std::optional PublicIP; + /// deprecated + std::optional PublicPort; void defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); @@ -114,9 +128,12 @@ namespace llarp 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; @@ -129,15 +146,22 @@ namespace llarp std::optional m_AddrMapPersistFile; + bool m_EnableRoutePoker; + bool m_BlackholeRoutes; + void defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); }; struct DnsConfig { - SockAddr m_bind; + 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); @@ -145,19 +169,10 @@ namespace llarp struct LinksConfig { - struct LinkInfo - { - std::string interface; - int addressFamily = -1; - uint16_t port = -1; - }; - /// Create a LinkInfo from the given string. - /// @throws if str does not represent a LinkInfo. - LinkInfo - LinkInfoFromINIValues(std::string_view name, std::string_view value); - - LinkInfo m_OutboundLink; - std::vector m_InboundLinks; + std::optional PublicAddress; + std::optional PublicPort; + std::vector OutboundLinks; + std::vector InboundListenAddrs; void defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); @@ -193,7 +208,7 @@ namespace llarp struct BootstrapConfig { std::vector files; - std::set routers; + BootstrapList routers; bool seednode; void defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); @@ -201,8 +216,8 @@ namespace llarp struct LoggingConfig { - LogType m_logType = LogType::Unknown; - LogLevel m_logLevel = eLogNone; + log::Type m_logType = log::Type::Print; + log::Level m_logLevel = log::Level::off; std::string m_logFile; void @@ -211,9 +226,13 @@ namespace llarp struct Config { - explicit Config(fs::path datadir); + explicit Config(std::optional datadir = std::nullopt); - ~Config() = default; + virtual ~Config() = default; + + /// create generation params (virtual for unit test mock) + virtual std::unique_ptr + MakeGenParams() const; RouterConfig router; NetworkConfig network; @@ -241,6 +260,10 @@ namespace llarp 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(); @@ -275,8 +298,12 @@ namespace llarp bool LoadDefault(bool isRelay); + bool + LoadConfigData( + std::string_view ini, std::optional fname = std::nullopt, bool isRelay = false); + void - LoadOverrides(); + LoadOverrides(ConfigDefinition& conf) const; std::vector> m_Additional; ConfigParser m_Parser; diff --git a/llarp/config/definition.cpp b/llarp/config/definition.cpp index 217cd77d3..76177603c 100644 --- a/llarp/config/definition.cpp +++ b/llarp/config/definition.cpp @@ -1,8 +1,7 @@ #include "definition.hpp" -#include +#include #include -#include #include #include @@ -17,7 +16,7 @@ namespace llarp else if (input == "true" || input == "on" || input == "1" || input == "yes") return true; else - throw std::invalid_argument(stringify(input, " is not a valid bool")); + throw std::invalid_argument{fmt::format("{} is not a valid bool", input)}; } ConfigDefinition& @@ -53,8 +52,8 @@ namespace llarp auto [it, added] = m_definitions[section].try_emplace(std::string{def->name}, std::move(def)); if (!added) - throw std::invalid_argument( - stringify("definition for [", def->section, "]:", def->name, " already exists")); + throw std::invalid_argument{ + fmt::format("definition for [{}]:{} already exists", def->section, def->name)}; m_definitionOrdering[section].push_back(it->first); @@ -79,34 +78,28 @@ namespace llarp { // fallback to undeclared handler if available if (not haveUndeclaredHandler) - throw std::invalid_argument(stringify("unrecognized section [", section, "]")); - else - { - auto& handler = undItr->second; - handler(section, name, value); - return *this; - } + throw std::invalid_argument{fmt::format("unrecognized section [{}]", section)}; + auto& handler = undItr->second; + handler(section, name, value); + 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()) + if (defItr != sectionDefinitions.end()) { - if (not haveUndeclaredHandler) - throw std::invalid_argument(stringify("unrecognized option [", section, "]:", name)); - else - { - auto& handler = undItr->second; - handler(section, name, value); - return *this; - } + OptionDefinition_ptr& definition = defItr->second; + definition->parseValue(std::string(value)); + return *this; } - OptionDefinition_ptr& definition = defItr->second; - definition->parseValue(std::string(value)); + if (not haveUndeclaredHandler) + throw std::invalid_argument{fmt::format("unrecognized option [{}]: {}", section, name)}; + auto& handler = undItr->second; + handler(section, name, value); return *this; } @@ -115,7 +108,7 @@ namespace llarp { auto itr = m_undeclaredHandlers.find(section); if (itr != m_undeclaredHandlers.end()) - throw std::logic_error(stringify("section ", section, " already has a handler")); + throw std::logic_error{fmt::format("section {} already has a handler", section)}; m_undeclaredHandlers[section] = std::move(handler); } @@ -135,8 +128,8 @@ namespace llarp visitDefinitions(section, [&](const std::string&, const OptionDefinition_ptr& def) { if (def->required and def->getNumberFound() < 1) { - throw std::invalid_argument( - stringify("[", section, "]:", def->name, " is required but missing")); + throw std::invalid_argument{ + fmt::format("[{}]:{} is required but missing", section, def->name)}; } // should be handled earlier in OptionDefinition::parseValue() @@ -148,9 +141,9 @@ namespace llarp void ConfigDefinition::acceptAllOptions() { - visitSections([&](const std::string& section, const DefinitionMap&) { + visitSections([this](const std::string& section, const DefinitionMap&) { visitDefinitions( - section, [&](const std::string&, const OptionDefinition_ptr& def) { def->tryAccept(); }); + section, [](const std::string&, const OptionDefinition_ptr& def) { def->tryAccept(); }); }); } @@ -159,10 +152,8 @@ namespace llarp const std::string& section, std::vector comments) { auto& sectionComments = m_sectionComments[section]; - for (size_t i = 0; i < comments.size(); ++i) - { - sectionComments.emplace_back(std::move(comments[i])); - } + for (auto& c : comments) + sectionComments.emplace_back(std::move(c)); } void @@ -182,12 +173,14 @@ namespace llarp std::string ConfigDefinition::generateINIConfig(bool useValues) { - std::ostringstream oss; + std::string ini; + auto ini_append = std::back_inserter(ini); int sectionsVisited = 0; visitSections([&](const std::string& section, const DefinitionMap&) { - std::ostringstream sect_out; + 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; @@ -196,44 +189,52 @@ namespace llarp // (i.e. those handled by UndeclaredValueHandler's) for (const std::string& comment : m_definitionComments[section][name]) { - sect_out << "\n# " << comment; + fmt::format_to(sect_append, "\n# {}", comment); has_comment = true; } if (useValues and def->getNumberFound() > 0) { - sect_out << "\n" << name << "=" << def->valueAsString(false) << "\n"; + for (const auto& val : def->valuesAsString()) + fmt::format_to(sect_append, "\n{}={}", name, val); + *sect_append = '\n'; } - else if (not(def->hidden and not has_comment)) + else if (not def->hidden) { - sect_out << "\n"; - if (not def->required) - sect_out << "#"; - sect_out << name << "=" << def->defaultValueAsString() << "\n"; + 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'; }); - auto sect_str = sect_out.str(); if (sect_str.empty()) return; // Skip sections with no options if (sectionsVisited > 0) - oss << "\n\n"; + ini += "\n\n"; - oss << "[" << section << "]\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]) { - oss << "# " << comment << "\n"; + fmt::format_to(ini_append, "# {}\n", comment); } - oss << "\n" << sect_str; + ini += "\n"; + ini += sect_str; sectionsVisited++; }); - return oss.str(); + return ini; } const OptionDefinition_ptr& @@ -241,12 +242,13 @@ namespace llarp { const auto sectionItr = m_definitions.find(std::string(section)); if (sectionItr == m_definitions.end()) - throw std::invalid_argument(stringify("No config section [", section, "]")); + 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(stringify("No config item ", name, " within section ", section)); + throw std::invalid_argument{ + fmt::format("No config item {} within section {}", name, section)}; return definitionItr->second; } diff --git a/llarp/config/definition.hpp b/llarp/config/definition.hpp index 0e3d638b2..6cacd3180 100644 --- a/llarp/config/definition.hpp +++ b/llarp/config/definition.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -96,12 +97,19 @@ namespace llarp 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< option_flag, remove_cvref_t< - Option>> or std::is_same_v or is_default