diff --git a/.drone.jsonnet b/.drone.jsonnet index 97fd42718..acd8ee3a6 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -1,46 +1,46 @@ -local distro = "fedora-35"; -local distro_name = 'Fedora 35'; -local distro_docker = 'fedora:35'; +local distro = 'fedora-36'; +local distro_name = 'Fedora 36'; +local distro_docker = 'fedora:36'; local submodules = { - name: 'submodules', - image: 'drone/git', - commands: ['git fetch --tags', 'git submodule update --init --recursive --depth=1'] + name: 'submodules', + image: 'drone/git', + commands: ['git fetch --tags', 'git submodule update --init --recursive --depth=1'], }; -local dnf(arch) = 'dnf -y --setopt install_weak_deps=False --setopt cachedir=/cache/'+distro+'/'+arch+'/${DRONE_STAGE_MACHINE} '; +local dnf(arch) = 'dnf -y --setopt install_weak_deps=False --setopt cachedir=/cache/' + distro + '/' + arch + '/${DRONE_STAGE_MACHINE} '; local rpm_pipeline(image, buildarch='amd64', rpmarch='x86_64', jobs=6) = { - kind: 'pipeline', - type: 'docker', - name: distro_name + ' (' + rpmarch + ')', - platform: { arch: buildarch }, - steps: [ - submodules, - { - name: 'build', - image: image, - environment: { - SSH_KEY: { from_secret: "SSH_KEY" }, - RPM_BUILD_NCPUS: jobs - }, - commands: [ - 'echo "Building on ${DRONE_STAGE_MACHINE}"', - dnf(rpmarch) + 'distro-sync', - dnf(rpmarch) + 'install rpm-build git-archive-all dnf-plugins-core ccache', - dnf(rpmarch) + 'config-manager --add-repo https://rpm.oxen.io/fedora/oxen.repo', - 'pkg_src_base="$(rpm -q --queryformat=\'%{NAME}-%{VERSION}\n\' --specfile SPECS/lokinet.spec | head -n 1)"', - 'git-archive-all --prefix $pkg_src_base/ SOURCES/$pkg_src_base.src.tar.gz', - dnf(rpmarch) + 'builddep --spec SPECS/lokinet.spec', - 'if [ -n "$CCACHE_DIR" ]; then mkdir -pv ~/.cache; ln -sv "$CCACHE_DIR" ~/.cache/ccache; fi', - 'rpmbuild --define "_topdir $(pwd)" -bb SPECS/lokinet.spec', - './SPECS/ci-upload.sh ' + distro + ' ' + rpmarch, - ], - } - ] + kind: 'pipeline', + type: 'docker', + name: distro_name + ' (' + rpmarch + ')', + platform: { arch: buildarch }, + steps: [ + submodules, + { + name: 'build', + image: image, + environment: { + SSH_KEY: { from_secret: 'SSH_KEY' }, + RPM_BUILD_NCPUS: jobs, + }, + commands: [ + 'echo "Building on ${DRONE_STAGE_MACHINE}"', + dnf(rpmarch) + 'distro-sync', + dnf(rpmarch) + 'install rpm-build git-archive-all dnf-plugins-core ccache', + dnf(rpmarch) + 'config-manager --add-repo https://rpm.oxen.io/fedora/oxen.repo', + 'pkg_src_base="$(rpm -q --queryformat=\'%{NAME}-%{VERSION}\n\' --specfile SPECS/lokinet.spec | head -n 1)"', + 'git-archive-all --prefix $pkg_src_base/ SOURCES/$pkg_src_base.src.tar.gz', + dnf(rpmarch) + 'builddep --spec SPECS/lokinet.spec', + 'if [ -n "$CCACHE_DIR" ]; then mkdir -pv ~/.cache; ln -sv "$CCACHE_DIR" ~/.cache/ccache; fi', + 'rpmbuild --define "_topdir $(pwd)" -bb SPECS/lokinet.spec', + './SPECS/ci-upload.sh ' + distro + ' ' + rpmarch, + ], + }, + ], }; [ - rpm_pipeline(distro_docker), - rpm_pipeline("arm64v8/" + distro_docker, buildarch='arm64', rpmarch="aarch64", jobs=4) + rpm_pipeline(distro_docker), + rpm_pipeline('arm64v8/' + distro_docker, buildarch='arm64', rpmarch='aarch64', jobs=3), ] diff --git a/.gitmodules b/.gitmodules index 60d5d9621..7ba32fba6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,9 +10,6 @@ [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 @@ -36,3 +33,9 @@ [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 bf5e6cabd..13514b766 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ -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) @@ -25,17 +25,17 @@ endif() project(lokinet - VERSION 0.9.9 + 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 "Gluten Free Edition" CACHE STRING "Release motto") +set(RELEASE_MOTTO "Our Lord And Savior" CACHE STRING "Release motto") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") @@ -51,18 +51,20 @@ 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" 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) @@ -83,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) @@ -99,7 +109,6 @@ 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) if (STATIC_LINK) @@ -107,10 +116,11 @@ if (STATIC_LINK) 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) @@ -175,49 +185,19 @@ if(NOT TARGET sodium) export(TARGETS sodium NAMESPACE sodium:: FILE sodium-exports.cmake) endif() -option(FORCE_OXENC_SUBMODULE "force using oxen-encoding submodule" OFF) -if(NOT FORCE_OXENC_SUBMODULE) - pkg_check_modules(OXENC liboxenc>=1.0.3 IMPORTED_TARGET) -endif() - -if(OXENC_FOUND) - if(NOT TARGET PkgConfig::OXENC AND CMAKE_VERSION VERSION_LESS "3.21") - # Work around cmake bug 22180 (PkgConfig::OXENC not set if no flags needed): - add_library(_empty_oxenc INTERFACE) - add_library(oxenc::oxenc ALIAS _empty_oxenc) - else() - add_library(oxenc::oxenc ALIAS PkgConfig::OXENC) - endif() - message(STATUS "Found system liboxenc ${OXENC_VERSION}") -else() - message(STATUS "using oxen-encoding submodule") - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/oxen-encoding) - add_library(oxenc::oxenc ALIAS oxenc) -endif() - -option(FORCE_OXENMQ_SUBMODULE "force using oxenmq submodule" OFF) -if(NOT FORCE_OXENMQ_SUBMODULE) - pkg_check_modules(OXENMQ liboxenmq>=1.2.12 IMPORTED_TARGET) +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::oxenmq ALIAS PkgConfig::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_CURRENT_SOURCE_DIR}/external/oxen-mq) + list(APPEND warning_flags -Wno-deprecated-declarations) 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() -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") @@ -227,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 @@ -269,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) @@ -319,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) @@ -339,12 +294,8 @@ 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) @@ -356,17 +307,21 @@ 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( @@ -378,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/SOURCES/default-dns.patch b/SOURCES/default-dns.patch deleted file mode 100644 index 32b7cc014..000000000 --- a/SOURCES/default-dns.patch +++ /dev/null @@ -1,27 +0,0 @@ -From cdad3e7f093c4b0c69f73580e4fbacc24067db96 Mon Sep 17 00:00:00 2001 -From: necro-nemsis -Date: Sun, 19 Jun 2022 06:47:01 -0400 -Subject: [PATCH] Change default port to 953 - ---- - llarp/config/config.cpp | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp -index 6bff9611..9c89bce6 100644 ---- a/llarp/config/config.cpp -+++ b/llarp/config/config.cpp -@@ -760,7 +760,10 @@ namespace llarp - // can bind to other 127.* IPs to avoid conflicting with something else that may be listening on - // 127.0.0.1:53. - #ifdef __linux__ -- constexpr Default DefaultDNSBind{"127.3.2.1:53"}; -+ // Fedora's systemd-resolved seems unable to connect to 127.3.2.1 for unknown reasons, -+ // however since systemd-resolved is perfectly happy with a different port so listen on -+ // localhost:953 as a workaround. -+ constexpr Default DefaultDNSBind{"127.0.0.1:953"}; - #else - constexpr Default DefaultDNSBind{"127.0.0.1:53"}; - #endif --- -2.30.2 diff --git a/SOURCES/lokinet.service b/SOURCES/lokinet.service index 3b0e990d0..a54a51a6c 100644 --- a/SOURCES/lokinet.service +++ b/SOURCES/lokinet.service @@ -1,6 +1,5 @@ [Unit] Description=LokiNET: Anonymous Network layer thingydoo, client -AssertFileNotEmpty=/var/lib/lokinet/bootstrap.signed Wants=network-online.target After=network-online.target diff --git a/SPECS/lokinet.spec b/SPECS/lokinet.spec index e9a0c0a97..a9c59d51c 100644 --- a/SPECS/lokinet.spec +++ b/SPECS/lokinet.spec @@ -1,5 +1,5 @@ Name: lokinet -Version: 0.9.9 +Version: 0.9.11 Release: 1%{?dist} Summary: Lokinet anonymous, decentralized overlay network @@ -19,11 +19,6 @@ BuildRequires: systemd-devel BuildRequires: systemd-rpm-macros BuildRequires: libcurl-devel BuildRequires: jemalloc-devel -BuildRequires: libsqlite3x-devel - -# Changes the default dns listener to 127.0.0.1:953 because Fedora's systemd-resolved doesn't like -# talking to 127.3.2.1:53 for unknown reasons. -Patch1: default-dns.patch Requires: lokinet-bin = %{version}-%{release} %{?systemd_requires} @@ -92,7 +87,7 @@ install -m755 contrib/py/admin/lokinetmon $RPM_BUILD_ROOT/%{_bindir}/ install -Dm644 SOURCES/lokinet.service $RPM_BUILD_ROOT/%{_unitdir}/lokinet.service install -Dm644 contrib/systemd-resolved/lokinet.rules $RPM_BUILD_ROOT/%{_datadir}/polkit-1/rules.d/50-lokinet.rules install -Dm644 SOURCES/dnssec-lokinet.negative $RPM_BUILD_ROOT%{_exec_prefix}/lib/dnssec-trust-anchors.d/lokinet.negative -install -Dm644 SOURCES/bootstrap.signed $RPM_BUILD_ROOT%{_sharedstatedir}/lokinet/bootstrap.signed +install -Dm644 contrib/bootstrap/mainnet.signed $RPM_BUILD_ROOT%{_sharedstatedir}/lokinet/bootstrap.signed %files @@ -122,7 +117,7 @@ if ! getent group _loki >/dev/null; then groupadd --system _loki fi if ! getent passwd _lokinet >/dev/null; then - useradd --badnames --system --home-dir /var/lib/lokinet --group _loki --comment "Lokinet system user" _lokinet + useradd --badname --system --home-dir /var/lib/lokinet --group _loki --comment "Lokinet system user" _lokinet fi # Make sure the _lokinet user is part of the _loki group (in case it already existed) @@ -153,6 +148,14 @@ fi %systemd_postun lokinet.service %changelog +* Thu Dec 15 2022 Technical Tumbleweed - 0.9.11-1 +- bump version +- remove dns Patch1 +- remove external/date submodule +- external/oxen-logging & gui submodule added during merge +- [jason@oxen.io] remove no-longer-rerequired sqlite dependency +- [jason@oxen.io] remove no-longer-wanted assertion from service file + * Wed Jun 29 2022 Technical Tumbleweed - 0.9.9-1 - bump version - cmake flags for no system library search diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index 3ddbe9757..78e77a370 100644 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -5,31 +5,31 @@ set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads") -set(OPENSSL_VERSION 1.1.1o 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=9384a2b0570dd80358841464677115df785edb941c71211f75076d72fe6b438f +set(OPENSSL_HASH SHA256=83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e CACHE STRING "openssl source hash") -set(EXPAT_VERSION 2.4.8 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 SHA256=f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25 +set(EXPAT_HASH SHA512=2da73b991b7c0c54440485c787e5edeb3567230204e31b3cac1c3a6713ec6f9f1554d3afffc0f8336168dfd5df02db4a69bcf21b4d959723d14162d13ab87516 CACHE STRING "expat source hash") -set(UNBOUND_VERSION 1.15.0 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=a480dc6c8937447b98d161fe911ffc76cfaffa2da18788781314e81339f1126f +set(UNBOUND_HASH SHA512=f6b9f279330fb19b5feca09524959940aad8c4e064528aa82b369c726d77e9e8e5ca23f366f6e9edcf2c061b96f482ed7a2c26ac70fc15ae5762b3d7e36a5284 CACHE STRING "unbound source hash") -set(SQLITE3_VERSION 3380500 CACHE STRING "sqlite3 version") +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 SHA3_256=ab649fea76f49a6ec7f907f001d87b8bd76dec0679c783e3992284c5a882a98c +set(SQLITE3_HASH SHA3_256=7ee8f02b21edb4489df5082b5cf5b7ef47bcebcdb0e209bf14240db69633c878 CACHE STRING "sqlite3 source hash") set(SODIUM_VERSION 1.0.18 CACHE STRING "libsodium version") @@ -48,25 +48,25 @@ set(ZMQ_SOURCE zeromq-${ZMQ_VERSION}.tar.gz) set(ZMQ_HASH SHA512=e198ef9f82d392754caadd547537666d4fba0afd7d027749b3adae450516bcf284d241d4616cad3cb4ad9af8c10373d456de92dc6d115b037941659f141e7c0e CACHE STRING "libzmq source hash") -set(LIBUV_VERSION 1.44.1 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=b4f8944e2c79e3a6a31ded6cccbe4c0eeada50db6bc8a448d7015642795012a4b80ffeef7ca455bb093c59a8950d0e1430566c3c2fa87b73f82699098162d834 +set(LIBUV_HASH SHA512=91197ff9303112567bbb915bbb88058050e2ad1c048815a3b57c054635d5dc7df458b956089d785475290132236cb0edcfae830f5d749de29a9a3213eeaf0b20 CACHE STRING "libuv source hash") -set(ZLIB_VERSION 1.2.12 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 SHA256=91844808532e5ce316b3c010929493c0244f3d37593afd6de04f71821d5136d9 +set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.xz) +set(ZLIB_HASH SHA256=d14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98 CACHE STRING "zlib source hash") - -set(CURL_VERSION 7.83.1 CACHE STRING "curl version") + +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=2cb9c2356e7263a1272fd1435ef7cdebf2cd21400ec287b068396deb705c22c4 +set(CURL_HASH SHA512=18e03a3c00f22125e07bddb18becbf5acdca22baeb7b29f45ef189a5c56f95b2d51247813f7a9a90f04eb051739e9aa7d3a1c5be397bae75d763a2b918d1b656 CACHE STRING "curl source hash") include(ExternalProject) @@ -125,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) @@ -167,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)) @@ -223,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 @@ -234,43 +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_system_env SYSTEM=Linux MACHINE=mips64) - set(openssl_configure_command ./Configure linux64-mips64) + set(openssl_arch linux-mips64) elseif(ARCH_TRIPLET STREQUAL mips-linux-gnu) - set(openssl_system_env SYSTEM=Linux MACHINE=mips) + set(openssl_arch linux-mips32) elseif(ARCH_TRIPLET STREQUAL mipsel-linux-gnu) - set(openssl_system_env SYSTEM=Linux MACHINE=mipsel) + set(openssl_arch linux-mips) elseif(ARCH_TRIPLET STREQUAL aarch64-linux-gnu) # cross compile arm64 - set(openssl_system_env SYSTEM=Linux MACHINE=aarch64) + set(openssl_arch linux-aarch64) elseif(ARCH_TRIPLET MATCHES arm-linux) # cross compile armhf - set(openssl_system_env SYSTEM=Linux MACHINE=armv4) + set(openssl_arch linux-armv4) elseif(ARCH_TRIPLET MATCHES powerpc64le) # cross compile ppc64le - set(openssl_system_env SYSTEM=Linux MACHINE=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} ${openssl_configure_command} - --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}" + --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 @@ -284,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 @@ -294,8 +306,15 @@ 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} @@ -315,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) @@ -328,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 @@ -351,6 +375,15 @@ 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() @@ -420,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 a017995eb..9b786ce85 100644 --- a/cmake/Version.cmake +++ b/cmake/Version.cmake @@ -1,26 +1,31 @@ # 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. -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() +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() + 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() 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 bdd4958fc..503fd15dd 100644 --- a/cmake/installer.cmake +++ b/cmake/installer.cmake @@ -5,9 +5,42 @@ 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/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/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 73cda29b3..a8f977c28 100644 --- a/cmake/win32.cmake +++ b/cmake/win32.cmake @@ -1,32 +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) - # the minimum windows version, set to 6 rn because supporting older windows is hell - set(_winver 0x0600) - add_definitions(-DWINVER=${_winver} -D_WIN32_WINNT=${_winver}) -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 825eda90f..8099e9a00 100644 --- a/cmake/win32_installer_deps.cmake +++ b/cmake/win32_installer_deps.cmake @@ -1,34 +1,29 @@ -if(NOT GUI_ZIP_URL) - set(GUI_ZIP_URL "https://oxen.rocks/oxen-io/lokinet-gui/dev/lokinet-windows-x64-20220331T180338Z-569f90ad8.zip") - set(GUI_ZIP_HASH_OPTS EXPECTED_HASH SHA256=316f10489f5907bfa9c74b21f8ef2fdd7b7c7e6a0f5bcedaed2ee5f4004eab52) +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) - function(read_nsis_file filename outvar) file(STRINGS "${filename}" _outvar) list(TRANSFORM _outvar REPLACE "\\\\" "\\\\\\\\") @@ -44,9 +39,8 @@ read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_delete_icons.nsis" _extra_ 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_EXTRA_UNINSTALL_COMMANDS "${_extra_uninstall}") set(CPACK_NSIS_CREATE_ICONS_EXTRA "${_extra_create_icons}") set(CPACK_NSIS_DELETE_ICONS_EXTRA "${_extra_delete_icons}") -get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) -list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified") +set(CPACK_NSIS_COMPRESSOR "/SOLID lzma") diff --git a/contrib/android-configure.sh b/contrib/android-configure.sh index 2130b07cf..065e83e18 100755 --- a/contrib/android-configure.sh +++ b/contrib/android-configure.sh @@ -1,17 +1,18 @@ #!/bin/bash set -e -set +x default_abis="armeabi-v7a arm64-v8a x86_64" build_abis=${ABIS:-$default_abis} -test x$NDK = x && echo "NDK env var not set" +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 $(dirname $0)/../)" -build=$root/build-android +root=$(readlink -f "$1") +shift +build=$(readlink -f "$1") +shift mkdir -p $build cd $build @@ -19,11 +20,13 @@ 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 \ @@ -32,15 +35,19 @@ for abi in $build_abis; do -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 \ - $@ $root + "$@" cd - done rm -f $build/Makefile @@ -51,4 +58,11 @@ for abi in $build_abis; do 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 a5d720206..bcb55d5a4 100755 --- a/contrib/android.sh +++ b/contrib/android.sh @@ -2,9 +2,7 @@ set -e set +x -test x$NDK = x && echo "NDK env var not set" -test x$NDK = x && exit 1 root="$(readlink -f $(dirname $0)/../)" cd "$root" -./contrib/android-configure.sh $@ +./contrib/android-configure.sh . build-android "$@" make -C build-android -j ${JOBS:-$(nproc)} 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/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 9449ff6dd..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" @@ -47,9 +56,13 @@ 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" @@ -61,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 @@ -68,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 <> $root/build-cross/Makefile 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/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/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/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 0ddcbe3ff..0a51fe672 100755 --- a/contrib/mac.sh +++ b/contrib/mac.sh @@ -8,32 +8,20 @@ # set -e -set +x +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" + 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/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/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/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 7520936e9..d06adab84 100755 --- a/contrib/windows.sh +++ b/contrib/windows.sh @@ -6,26 +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=OFF \ - -DWITH_TESTS=OFF \ - -DNATIVE_BUILD=OFF \ - -DSTATIC_LINK=ON \ - -DWITH_SYSTEMD=OFF \ - -DFORCE_OXENMQ_SUBMODULE=ON \ - -DFORCE_OXENC_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 233c436ce..c9ff4aec6 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -1,14 +1,18 @@ +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) + list(APPEND exetargets lokinet-bootstrap) enable_lto(lokinet-bootstrap) endif() @@ -42,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) + 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 ebddf84e1..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, " @@ -410,7 +398,6 @@ lokinet_main(int argc, char* argv[]) ("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()) - ("c,colour", "colour output", cxxopts::value()->default_value("true")) ("config", "path to lokinet.ini configuration file", cxxopts::value()) ; // clang-format on @@ -424,18 +411,11 @@ lokinet_main(int argc, char* argv[]) { auto result = options.parse(argc, argv); - 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; @@ -542,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.", "⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠄⠄⠄⠄", @@ -575,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); @@ -604,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(); @@ -613,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) @@ -645,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/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/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/ideal-ux.md b/docs/ideal-ux.md index 2050892d5..7cda8e29a 100644 --- a/docs/ideal-ux.md +++ b/docs/ideal-ux.md @@ -1,3 +1,42 @@ -# How Do I use lokinet? +# What does Lokinet actually do? -`// TODO: this` +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/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/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/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/readme.md b/docs/readme.md index 1b132b26f..534a19497 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -2,14 +2,26 @@ 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) -[How Do I use Lokinet?](ideal-ux.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 @@ -19,7 +31,4 @@ This is where Lokinet documentation lives. [How do I embed lokinet into my application?](liblokinet-dev-guide.md) -## Lokinet Internals - -[Build Doxygen Docs for internals](doxygen.md) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 0f935161b..d235c1ec2 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -12,13 +12,23 @@ 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) @@ -29,23 +39,57 @@ if(SUBMODULE_CHECK) 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) @@ -80,4 +124,19 @@ if(WITH_BOOTSTRAP) 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/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/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 index 79193e58f..a869ae2b0 160000 --- a/external/oxen-encoding +++ b/external/oxen-encoding @@ -1 +1 @@ -Subproject commit 79193e58fb26624d40cd2e95156f78160f2b9b3e +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 eadb37c76..ac6ef82ff 160000 --- a/external/oxen-mq +++ b/external/oxen-mq @@ -1 +1 @@ -Subproject commit eadb37c7654150bef18497773718f15ef843734a +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..37f274e86 --- /dev/null +++ b/gui @@ -0,0 +1 @@ +Subproject commit 37f274e86fea7fe0fcc472727398d118a6917854 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 b09b20e2c..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,17 +15,27 @@ 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); - typedef void (*lokinet_logger_func)(const char*, void*); + /// Function pointer to invoke with lokinet log messages + typedef void (*lokinet_logger_func)(const char* message, void* context); - /// set a custom logger function + /// 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* user); + 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 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 40786322b..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,24 +24,20 @@ target_link_libraries(lokinet-util PUBLIC lokinet-cryptography nlohmann_json::nlohmann_json filesystem - date::date 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 @@ -62,47 +49,82 @@ target_link_libraries(lokinet-platform PUBLIC lokinet-cryptography lokinet-util 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) - consensus/table.cpp +target_link_libraries(lokinet-config PUBLIC lokinet-dns lokinet-platform oxenmq::oxenmq) + +add_library(lokinet-amalgum + STATIC consensus/reachability_testing.cpp bootstrap.cpp @@ -131,7 +153,7 @@ add_library(liblokinet dht/taglookup.cpp endpoint_base.cpp - + exit/context.cpp exit/endpoint.cpp exit/exit_messages.cpp @@ -139,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 @@ -167,7 +188,7 @@ add_library(liblokinet peerstats/types.cpp pow.cpp profiling.cpp - + quic/address.cpp quic/client.cpp quic/connection.cpp @@ -187,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 @@ -215,41 +236,66 @@ 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 oxenc::oxenc lokinet-platform lokinet-util lokinet-cryptography sqlite_orm ngtcp2_static oxenmq::oxenmq) -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(liblokinet PRIVATE libcrypt) + target_link_libraries(lokinet-amalgum PRIVATE libcrypt) message(STATUS "using libcrypt ${CRYPT_VERSION}") endif() @@ -257,7 +303,7 @@ 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() @@ -265,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 c54cef00a..f0b3a6c32 100644 --- a/llarp/apple/vpn_interface.cpp +++ b/llarp/apple/vpn_interface.cpp @@ -10,7 +10,8 @@ namespace llarp::apple packet_write_callback packet_writer, on_readable_callback on_readable, AbstractRouter* router) - : m_PacketWriter{std::move(packet_writer)} + : vpn::NetworkInterface{{}} + , m_PacketWriter{std::move(packet_writer)} , m_OnReadable{std::move(on_readable)} , _router{router} { @@ -39,12 +40,6 @@ namespace llarp::apple return -1; } - std::string - VPNInterface::IfName() const - { - return ""; - } - net::IPPacket VPNInterface::ReadNextPacket() { @@ -58,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 762227f91..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 @@ -29,9 +29,6 @@ namespace llarp::apple int PollFD() const override; - std::string - IfName() const override; - net::IPPacket ReadNextPacket() override; diff --git a/llarp/apple/vpn_platform.hpp b/llarp/apple/vpn_platform.hpp index 0cf7f469b..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" 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 32f273de4..32f830bdc 100644 --- a/llarp/bootstrap.cpp +++ b/llarp/bootstrap.cpp @@ -1,7 +1,8 @@ #include "bootstrap.hpp" #include "util/bencode.hpp" -#include "util/logging/logger.hpp" +#include "util/logging.hpp" #include "util/logging/buffer.hpp" +#include "util/fs.hpp" namespace llarp { @@ -36,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 6bff96112..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,18 +139,18 @@ 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) { + [this, net = params.Net_ptr()](std::string arg) { if (arg.empty()) return; nuint32_t addr{}; if (not addr.FromString(arg)) - throw std::invalid_argument{stringify(arg, " is not a valid IPv4 address")}; + throw std::invalid_argument{fmt::format("{} is not a valid IPv4 address", arg)}; - if (IsIPv4Bogon(addr)) + if (net->IsBogonIP(addr)) throw std::invalid_argument{ - stringify(addr, " looks like it is not a publicly routable ip address")}; + fmt::format("{} is not a publicly routable ip address", addr)}; - m_PublicIP = addr; + PublicIP = addr; }); conf.defineOption("router", "public-address", Hidden, [](std::string) { @@ -157,7 +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_PublicPort = ToNet(huint16_t{static_cast(arg)}); + PublicPort = ToNet(huint16_t{static_cast(arg)}); }); conf.defineOption( @@ -286,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( @@ -352,7 +366,7 @@ 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)); }); @@ -368,7 +382,7 @@ namespace llarp [this](fs::path arg) { if (not fs::exists(arg)) throw std::invalid_argument{ - stringify("cannot load auth file ", arg, " as it does not seem to exist")}; + fmt::format("cannot load auth file {}: file does not exist", arg)}; m_AuthFiles.emplace(std::move(arg)); }); conf.defineOption( @@ -399,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( @@ -425,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; }); @@ -445,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; @@ -461,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) @@ -483,9 +501,12 @@ namespace llarp 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()) @@ -514,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); }); @@ -566,10 +587,10 @@ namespace llarp Default{true}, Comment{ "Enable / disable automatic route configuration.", - "When this is enabled and an exit is used Lokinet will automatically configure " - "operating system routes to route traffic through the exit node.", - "This is enabled by default, but can be disabled to perform advanced exit routing " - "configuration manually."}, + "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( @@ -579,8 +600,8 @@ namespace llarp Default{true}, Comment{ "Enable / disable route configuration blackholes.", - "When enabled lokinet will drop ip4 and ip6 not included in exit config.", - "Enabled by default."}, + "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( @@ -588,7 +609,7 @@ namespace llarp "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)); @@ -603,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)}; } }); @@ -612,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, @@ -630,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", @@ -653,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); @@ -662,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; }); @@ -690,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 @@ -704,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)); }); @@ -721,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) @@ -737,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()) @@ -759,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{"9.9.9.10"}; + 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.", @@ -786,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( @@ -817,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)); }); @@ -831,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.m_interface = addr.toHost(); - info.port = *addr.getPort(); - } - else - { - info.m_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)); }); } @@ -950,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; @@ -1015,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)); @@ -1024,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); }); @@ -1067,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) { @@ -1088,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:", @@ -1128,6 +1267,8 @@ namespace llarp " info", " warn", " error", + " critical", + " none", }); conf.defineOption( @@ -1136,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.", }); } @@ -1166,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( @@ -1180,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 } @@ -1207,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"; }; @@ -1229,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; }); @@ -1251,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 @@ -1388,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); } @@ -1447,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", @@ -1472,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); @@ -1493,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 975a8ac99..54dbcc443 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "ini.hpp" #include "definition.hpp" #include @@ -38,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 @@ -54,9 +65,6 @@ namespace llarp bool m_blockBogons = false; - std::optional m_PublicIP; - nuint16_t m_PublicPort; - int m_workerThreads = -1; int m_numNetThreads = -1; @@ -68,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); @@ -143,9 +155,13 @@ namespace llarp 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); @@ -153,19 +169,10 @@ namespace llarp struct LinksConfig { - struct LinkInfo - { - std::string m_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); @@ -209,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 @@ -219,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; @@ -249,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(); @@ -283,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