diff --git a/.drone.jsonnet b/.drone.jsonnet index 2ec9ac4aa..ef8494a68 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -76,6 +76,7 @@ local debian_pipeline(name, 'mkdir build', 'cd build', 'cmake .. -DWITH_SETCAP=OFF -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always -DCMAKE_BUILD_TYPE=' + build_type + ' ' + + (if build_type == 'Debug' then ' -DWARN_DEPRECATED=OFF ' else '') + (if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') + '-DWITH_LTO=' + (if lto then 'ON ' else 'OFF ') + '-DWITH_TESTS=' + (if tests then 'ON ' else 'OFF ') + @@ -121,7 +122,7 @@ local windows_cross_pipeline(name, lto=false, werror=false, cmake_extra='', - toolchain='32', + gui_zip_url='', extra_cmds=[], jobs=6, allow_fail=false) = { @@ -137,16 +138,16 @@ local windows_cross_pipeline(name, image: image, pull: 'always', [if allow_fail then 'failure']: 'ignore', - environment: { SSH_KEY: { from_secret: 'SSH_KEY' }, WINDOWS_BUILD_NAME: toolchain + 'bit' }, + environment: { SSH_KEY: { from_secret: 'SSH_KEY' }, WINDOWS_BUILD_NAME: '64bit' }, commands: [ 'echo "Building on ${DRONE_STAGE_MACHINE}"', 'echo "man-db man-db/auto-update boolean false" | debconf-set-selections', apt_get_quiet + ' update', apt_get_quiet + ' install -y eatmydata', - 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y build-essential cmake git pkg-config ccache g++-mingw-w64-x86-64-posix nsis zip automake libtool', + 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y p7zip-full build-essential cmake git pkg-config ccache g++-mingw-w64-x86-64-posix nsis zip automake libtool', 'update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix', 'update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix', - 'VERBOSE=1 JOBS=' + jobs + ' ./contrib/windows.sh ' + ci_mirror_opts, + 'JOBS=' + jobs + ' VERBOSE=1 ./contrib/windows.sh ' + (if std.length(gui_zip_url) > 0 then '-DBUILD_GUI=OFF -DGUI_ZIP_URL=' + gui_zip_url else '') + ' -DSTRIP_SYMBOLS=ON ' + ci_mirror_opts, ] + extra_cmds, }, ], @@ -277,7 +278,13 @@ local mac_builder(name, // basic system headers. WTF apple: 'export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"', 'ulimit -n 1024', // because macos sets ulimit to 256 for some reason yeah idk - './contrib/mac.sh ' + ci_mirror_opts + ' ' + codesign, + './contrib/mac-configure.sh ' + ci_mirror_opts + (if build_type == 'Debug' then ' -DWARN_DEPRECATED=OFF ' else '') + codesign, + 'cd build-mac', + // We can't use the 'package' target here because making a .dmg requires an active logged in + // macos gui to invoke Finder to invoke the partitioning tool to create a partitioned (!) + // disk image. Most likely the GUI is required because if you lose sight of how pretty the + // surface of macOS is you might see how ugly the insides are. + 'ninja -j' + jobs + ' assemble_gui', ] + extra_cmds, }, ], @@ -349,17 +356,17 @@ local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = { debian_pipeline('Debian stable (armhf)', docker_base + 'debian-stable/arm32v7', arch='arm64', jobs=4), // cross compile targets - linux_cross_pipeline('Cross Compile (mips)', cross_targets=['mips-linux-gnu', 'mipsel-linux-gnu']), linux_cross_pipeline('Cross Compile (arm/arm64)', cross_targets=['arm-linux-gnueabihf', 'aarch64-linux-gnu']), linux_cross_pipeline('Cross Compile (ppc64le)', cross_targets=['powerpc64le-linux-gnu']), + // Not currently building successfully: + //linux_cross_pipeline('Cross Compile (mips)', cross_targets=['mips-linux-gnu', 'mipsel-linux-gnu']), // android apk builder apk_builder('android apk', docker_base + 'flutter', extra_cmds=['UPLOAD_OS=android ./contrib/ci/drone-static-upload.sh']), // Windows builds (x64) windows_cross_pipeline('Windows (amd64)', - docker_base + 'debian-win32-cross', - toolchain='64', + docker_base + 'nodejs-lts', extra_cmds=[ './contrib/ci/drone-static-upload.sh', ]), @@ -371,7 +378,7 @@ local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = { lto=true, tests=false, oxen_repo=true, - cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON ' + + cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON ' + ci_mirror_opts + '-DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8 ' + '-DCMAKE_CXX_FLAGS="-march=x86-64 -mtune=haswell" ' + '-DCMAKE_C_FLAGS="-march=x86-64 -mtune=haswell" ' + @@ -385,7 +392,7 @@ local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = { docker_base + 'debian-buster/arm32v7', arch='arm64', deps=['g++', 'python3-dev', 'automake', 'libtool'], - cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON ' + + cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON ' + ci_mirror_opts + '-DCMAKE_CXX_FLAGS="-march=armv7-a+fp -Wno-psabi" -DCMAKE_C_FLAGS="-march=armv7-a+fp" ' + '-DNATIVE_BUILD=OFF -DWITH_SYSTEMD=OFF -DWITH_BOOTSTRAP=OFF', extra_cmds=[ diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f726a720..87fe55660 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -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) @@ -61,6 +61,8 @@ 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) + include(cmake/enable_lto.cmake) @@ -81,6 +83,14 @@ 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 CACHE BOOL "") @@ -171,13 +181,20 @@ if(NOT TARGET sodium) export(TARGETS sodium NAMESPACE sodium:: FILE sodium-exports.cmake) endif() -if(NOT APPLE) - add_compile_options(-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() +set(warning_flags -Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Werror=vla) +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND warning_flags -Wno-unknown-warning-option) +endif() +if(WARN_DEPRECATED) + list(APPEND warning_flags -Wdeprecated-declarations) +else() + list(APPEND warning_flags -Wno-deprecated-declarations) 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") foreach(type EXE MODULE SHARED STATIC) @@ -186,11 +203,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) -endif() - - include(cmake/coverage.cmake) # these vars are set by the cmake toolchain spec @@ -317,7 +329,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/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index 72f0a2565..62277eaa3 100644 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -5,10 +5,10 @@ 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.5 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=aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a CACHE STRING "openssl source hash") set(EXPAT_VERSION 2.4.8 CACHE STRING "expat version") @@ -19,10 +19,10 @@ set(EXPAT_SOURCE expat-${EXPAT_VERSION}.tar.xz) set(EXPAT_HASH SHA256=f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25 CACHE STRING "expat source hash") -set(UNBOUND_VERSION 1.15.0 CACHE STRING "unbound version") +set(UNBOUND_VERSION 1.16.2 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=0ea65ea63265be677441bd2a28df12098ec5e86c3372240c2874f9bd13752b8b818da81ae6076cf02cbeba3d36e397698a4c2b50570be1a6a8e47f57a0251572 CACHE STRING "unbound source hash") set(SQLITE3_VERSION 3380500 CACHE STRING "sqlite3 version") @@ -61,7 +61,7 @@ set(ZLIB_MIRROR ${LOCAL_MIRROR} https://zlib.net set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.gz) set(ZLIB_HASH SHA256=91844808532e5ce316b3c010929493c0244f3d37593afd6de04f71821d5136d9 CACHE STRING "zlib source hash") - + set(CURL_VERSION 7.83.1 CACHE STRING "curl version") set(CURL_MIRROR ${LOCAL_MIRROR} https://curl.haxx.se/download https://curl.askapache.com CACHE STRING "curl mirror(s)") @@ -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) @@ -223,7 +223,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 +234,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 +292,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 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.cmake b/cmake/gui.cmake index 32dc93116..fcb81b2fd 100644 --- a/cmake/gui.cmake +++ b/cmake/gui.cmake @@ -1,4 +1,3 @@ - set(default_build_gui OFF) set(default_gui_target pack) if(APPLE) @@ -9,24 +8,30 @@ elseif(WIN32) set(default_gui_target win32) endif() +if(WIN32) + option(GUI_EXE "path to an externally built lokinet gui.exe" OFF) +endif() + option(BUILD_GUI "build electron gui from 'gui' submodule source" ${default_build_gui}) 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") + if (BUILD_GUI) message(STATUS "Building lokinet-gui") - - find_program(YARN NAMES yarn yarnpkg REQUIRED) - message(STATUS "Building lokinet-gui with yarn ${YARN}, target ${GUI_YARN_TARGET}") - set(wine_env) - if(WIN32) - set(wine_env WINEDEBUG=-all "WINEPREFIX=${PROJECT_BINARY_DIR}/wineprefix") + # 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}") - add_custom_target(lokinet-gui - COMMAND ${YARN} install --frozen-lockfile && - ${wine_env} ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET} - WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui") + + 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 @@ -38,27 +43,42 @@ if (BUILD_GUI) 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" + -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_target(copy_gui ALL - DEPENDS lokinet lokinet-gui - # FIXME: we really shouldn't be building inside the source directory but this is npm... - COMMAND ${CMAKE_COMMAND} -E copy_if_different + option(GUI_ZIP_FILE "custom lokinet gui for windows from zip file" OFF) + if(GUI_ZIP_FILE) + message(STATUS "using custom lokinet gui from ${GUI_ZIP_FILE}") + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${GUI_ZIP_FILE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) + add_custom_target("${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" COMMAND "true") + elseif(GUI_EXE) + message(STATUS "using custom lokinet gui executable: ${GUI_EXE}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${GUI_EXE}" "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe") + add_custom_target("${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" COMMAND "true") + else() + 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") + endif() + 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 lokinet-gui") + 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 60e39f69b..503fd15dd 100644 --- a/cmake/installer.cmake +++ b/cmake/installer.cmake @@ -5,11 +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/win32.cmake b/cmake/win32.cmake index 73cda29b3..ae3bdb6d2 100644 --- a/cmake/win32.cmake +++ b/cmake/win32.cmake @@ -1,32 +1,52 @@ 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) +# the minimum windows version, set to 6 rn because supporting older windows is hell +set(_winver 0x0600) +add_definitions(-D_WIN32_WINNT=${_winver}) 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 f20034212..e937cabee 100644 --- a/cmake/win32_installer_deps.cmake +++ b/cmake/win32_installer_deps.cmake @@ -1,11 +1,3 @@ -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}) - if(NOT BUILD_GUI) if(NOT GUI_ZIP_URL) set(GUI_ZIP_URL "https://oxen.rocks/oxen-io/lokinet-gui/dev/lokinet-windows-x64-20220331T180338Z-569f90ad8.zip") @@ -25,9 +17,19 @@ if(NOT BUILD_GUI) message(FATAL_ERROR "Downloaded gui archive from ${GUI_ZIP_URL} does not contain gui/lokinet-gui.exe!") endif() endif() - install(DIRECTORY ${CMAKE_BINARY_DIR}/gui DESTINATION share COMPONENT gui) -install(PROGRAMS ${TUNTAP_EXE} DESTINATION bin COMPONENT tuntap) + +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(BOOTSTRAP_FILE "${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed") install(FILES ${BOOTSTRAP_FILE} DESTINATION share COMPONENT lokinet RENAME bootstrap.signed) set(CPACK_PACKAGE_INSTALL_DIRECTORY "Lokinet") @@ -35,7 +37,6 @@ set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/win32-setup/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 "\\\\" "\\\\\\\\") @@ -51,9 +52,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 2d669cae2..065e83e18 100755 --- a/contrib/android-configure.sh +++ b/contrib/android-configure.sh @@ -26,6 +26,7 @@ for abi in $build_abis; do -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 \ @@ -34,6 +35,7 @@ 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 \ @@ -45,7 +47,7 @@ for abi in $build_abis; do -DSUBMODULE_CHECK=OFF \ -DWITH_LTO=OFF \ -DCMAKE_BUILD_TYPE=Release \ - $@ + "$@" cd - done rm -f $build/Makefile diff --git a/contrib/ci/drone-static-upload.sh b/contrib/ci/drone-static-upload.sh index 6180b516f..460d001c2 100755 --- a/contrib/ci/drone-static-upload.sh +++ b/contrib/ci/drone-static-upload.sh @@ -34,8 +34,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" @@ -48,8 +51,9 @@ elif [ -e build-docs ]; then cp -av build-docs/docs/mkdocs.yml build-docs/docs/markdown "$base" tar cJvf "$archive" "$base" elif [ -e build-mac ]; then - archive="$base.dmg" - mv build-mac/Lokinet*.dmg "$archive" + 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" @@ -64,6 +68,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 @@ -71,10 +76,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 <' | wc -l) -ne 0 ] ; then + if [ $($binary --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 + $binary -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/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 70bf4d090..5455457e1 100755 --- a/contrib/mac.sh +++ b/contrib/mac.sh @@ -11,27 +11,14 @@ 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" + 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 \ - "$@" \ - .. -ninja -j1 package +./contrib/mac-configure.sh "$@" +cd build-mac +ninja -j${JOBS:-1} package cd .. echo -e "Build complete, your app is here:\n" diff --git a/contrib/windows-configure.sh b/contrib/windows-configure.sh index c4e193fe3..f41ef3af8 100755 --- a/contrib/windows-configure.sh +++ b/contrib/windows-configure.sh @@ -2,12 +2,25 @@ set -e set -x -root=$(readlink -f "$1") -shift -mkdir -p "$1" -build=$(readlink -f "$1") -shift -cd "$build" +# 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' \ @@ -21,6 +34,7 @@ cmake \ -DBUILD_TESTING=OFF \ -DBUILD_LIBLOKINET=OFF \ -DWITH_TESTS=OFF \ + -DWITH_BOOTSTRAP=OFF \ -DNATIVE_BUILD=OFF \ -DSTATIC_LINK=ON \ -DWITH_SYSTEMD=OFF \ @@ -30,4 +44,4 @@ cmake \ -DFORCE_SPDLOG_SUBMODULE=ON \ -DFORCE_NLOHMANN_SUBMODULE=ON \ -DWITH_LTO=OFF \ - $@ + "$@" diff --git a/contrib/windows.sh b/contrib/windows.sh index ff380aebd..d06adab84 100755 --- a/contrib/windows.sh +++ b/contrib/windows.sh @@ -6,9 +6,8 @@ set -e set +x - - root="$(readlink -f $(dirname $0)/../)" -cd "$root" -./contrib/windows-configure.sh . build-windows "$@" -make package -j${JOBS:-$(nproc)} -C build-windows +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/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index db864b57e..c9ff4aec6 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -46,15 +46,26 @@ if(WITH_BOOTSTRAP) endif() endif() +# 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}") if(should_install) if(APPLE) @@ -71,3 +82,9 @@ endforeach() 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.cpp b/daemon/lokinet.cpp index 53655c860..ab89e790a 100644 --- a/daemon/lokinet.cpp +++ b/daemon/lokinet.cpp @@ -101,7 +101,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 @@ -134,10 +134,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 @@ -270,7 +270,7 @@ run_main_context(std::optional confFile, const llarp::RuntimeOptions o } catch (std::exception& ex) { - llarp::LogError("failed to start up lokinet: {}", ex.what()); + llarp::LogError(fmt::format("failed to start up lokinet: {}", ex.what())); exit_code.set_value(1); return; } @@ -329,8 +329,9 @@ class WindowsServiceStopped 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"; @@ -367,7 +368,7 @@ main(int argc, char* argv[]) return lokinet_main(argc, argv); #else SERVICE_TABLE_ENTRY DispatchTable[] = { - {"lokinet", (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry}, {NULL, NULL}}; + {strdup("lokinet"), (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry}, {NULL, NULL}}; if (lstrcmpi(argv[1], "--win32-daemon") == 0) { start_as_daemon = true; @@ -680,7 +681,7 @@ win32_daemon_entry(DWORD argc, LPTSTR* argv) ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000); // SCM clobbers startup args, regenerate them here argc = 2; - argv[1] = "c:/programdata/lokinet/lokinet.ini"; + argv[1] = strdup("c:\\programdata\\lokinet\\lokinet.ini"); argv[2] = nullptr; lokinet_main(argc, argv); } diff --git a/docs/project-structure.md b/docs/project-structure.md index 6415a69f9..7c06c70f8 100644 --- a/docs/project-structure.md +++ b/docs/project-structure.md @@ -27,7 +27,7 @@ this codebase is a bit large. this is a high level map of the current code struc * `/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/intercepter, the works +* `/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" diff --git a/jni/CMakeLists.txt b/jni/CMakeLists.txt index 000e25aa0..ef24506a3 100644 --- a/jni/CMakeLists.txt +++ b/jni/CMakeLists.txt @@ -2,4 +2,4 @@ add_library(lokinet-android SHARED lokinet_config.cpp lokinet_daemon.cpp) -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 4d4fa1638..8bd90f510 100644 --- a/jni/lokinet_daemon.cpp +++ b/jni/lokinet_daemon.cpp @@ -84,9 +84,8 @@ extern "C" JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv* env, jobject self) { - auto ptr = GetImpl(env, self); - if (const auto& router = ptr->router; ptr and ptr->router) - return router->OutboundUDPSocket(); + if (auto ptr = GetImpl(env, self); ptr and ptr->router) + return ptr->router->OutboundUDPSocket(); return -1; } diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index f95543089..b794f6ead 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -8,7 +8,7 @@ add_library(lokinet-util util/fs.cpp util/json.cpp util/logging/buffer.cpp - util/lokinet_init.c + util/easter_eggs.cpp util/mem.cpp util/str.cpp util/thread/queue_manager.cpp @@ -32,12 +32,11 @@ add_library(lokinet-platform STATIC # for networking ev/ev.cpp - ev/ev_libuv.cpp + ev/libuv.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 @@ -53,41 +52,72 @@ if (ANDROID) endif() if(CMAKE_SYSTEM_NAME MATCHES "Linux") - if(NON_PC_TARGET) - add_import_library(rt) - target_link_libraries(lokinet-platform PUBLIC rt) - endif() + target_sources(lokinet-platform PRIVATE linux/dbus.cpp) 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/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 @@ -116,7 +146,7 @@ add_library(liblokinet dht/taglookup.cpp endpoint_base.cpp - + exit/context.cpp exit/endpoint.cpp exit/exit_messages.cpp @@ -151,7 +181,7 @@ add_library(liblokinet peerstats/types.cpp pow.cpp profiling.cpp - + quic/address.cpp quic/client.cpp quic/connection.cpp @@ -171,7 +201,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 @@ -205,42 +235,42 @@ add_library(liblokinet service/tag.cpp ) -if(WITH_PEERSTATS_BACKEND) - target_compile_definitions(liblokinet PRIVATE -DLOKINET_PEERSTATS_BACKEND) - target_link_libraries(liblokinet PUBLIC sqlite_orm) -endif() - -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) +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 +# 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) -target_link_libraries(liblokinet PRIVATE libunbound) + +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() @@ -248,7 +278,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() diff --git a/llarp/apple/CMakeLists.txt b/llarp/apple/CMakeLists.txt index 71d6c6651..8dd561ef7 100644 --- a/llarp/apple/CMakeLists.txt +++ b/llarp/apple/CMakeLists.txt @@ -18,12 +18,9 @@ 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}) # -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. @@ -43,6 +40,11 @@ else() set(product_type com.apple.product-type.app-extension) endif() +target_link_libraries(lokinet-extension PRIVATE + lokinet-amalgum + ${COREFOUNDATION} + ${NETEXT}) + set_target_properties(lokinet-extension PROPERTIES BUNDLE TRUE BUNDLE_EXTENSION ${bundle_ext} diff --git a/llarp/apple/DNSTrampoline.m b/llarp/apple/DNSTrampoline.m index cbbe211a3..9ef950c4e 100644 --- a/llarp/apple/DNSTrampoline.m +++ b/llarp/apple/DNSTrampoline.m @@ -3,23 +3,32 @@ 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,61 +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 - listenIp:(NSString*) listenIp - 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(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}]; + 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); } @@ -95,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); } @@ -128,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 17e76d4f1..dfcf20311 100644 --- a/llarp/apple/PacketTunnelProvider.m +++ b/llarp/apple/PacketTunnelProvider.m @@ -9,9 +9,12 @@ { void* lokinet; llarp_incoming_packet packet_buf[LLARP_APPLE_PACKET_BUF_SIZE]; - @public NEPacketTunnelNetworkSettings* settings; - @public NEIPv4Route* tun_route4; - @public NEIPv6Route* tun_route6; + @public + NEPacketTunnelNetworkSettings* settings; + @public + NEIPv4Route* tun_route4; + @public + NEIPv6Route* tun_route6; LLARPDNSTrampoline* dns_tramp; } @@ -30,107 +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; - [t.packetFlow writePackets:@[buf] - withProtocols:@[[NSNumber numberWithInt:af]]]; + 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) { +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]]; + 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) { +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 (int i = 0; i < routes.count; i++) { + 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) { +static void +add_default_route(void* ctx) +{ NSLog(@"Making the tunnel the default route"); - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; t->settings.IPv4Settings.includedRoutes = @[NEIPv4Route.defaultRoute]; t->settings.IPv6Settings.includedRoutes = @[NEIPv6Route.defaultRoute]; @@ -138,9 +167,11 @@ static void add_default_route(void* ctx) { [t updateNetworkSettings]; } -static void del_default_route(void* ctx) { +static void +del_default_route(void* ctx) +{ NSLog(@"Removing default route from tunnel"); - LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; + LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; t->settings.IPv4Settings.includedRoutes = @[t->tun_route4]; t->settings.IPv6Settings.includedRoutes = @[t->tun_route6]; @@ -152,12 +183,13 @@ static void del_default_route(void* ctx) { - (void)readPackets { - [self.packetFlow readPacketObjectsWithCompletionHandler: ^(NSArray* packets) { + [self.packetFlow readPacketObjectsWithCompletionHandler:^(NSArray* packets) { if (lokinet == nil) return; size_t size = 0; - for (NEPacket* p in packets) { + for (NEPacket* p in packets) + { packet_buf[size].bytes = p.data.bytes; packet_buf[size].size = p.data.length; size++; @@ -186,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); } @@ -237,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; @@ -249,50 +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); - } - - 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); - }]; - }]; + [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; } @@ -319,29 +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(); +int +main() +{ + [NEProvider startSystemExtensionMode]; + dispatch_main(); } #endif diff --git a/llarp/apple/context_wrapper.cpp b/llarp/apple/context_wrapper.cpp index 4fc85c804..025c0cfb2 100644 --- a/llarp/apple/context_wrapper.cpp +++ b/llarp/apple/context_wrapper.cpp @@ -90,7 +90,7 @@ llarp_apple_init(llarp_apple_config* appleconf) #ifdef MACOS_SYSTEM_EXTENSION std::strncpy( appleconf->dns_bind_ip, - config->dns.m_bind.hostString().c_str(), + config->dns.m_bind.front().hostString().c_str(), sizeof(appleconf->dns_bind_ip)); #endif 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..b69904276 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,22 @@ 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 + void AddRoute(net::ipaddr_t /*ip*/, net::ipaddr_t /*gateway*/) override {} - void - DelRoute(IPVariant_t ip, IPVariant_t gateway) override + void 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 +33,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/config/config.cpp b/llarp/config/config.cpp index 7fc78746d..3cc0c8004 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -140,14 +140,14 @@ 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{fmt::format("{} is not a valid IPv4 address", arg)}; - if (IsIPv4Bogon(addr)) + if (net->IsBogonIP(addr)) throw std::invalid_argument{ fmt::format("{} is not a publicly routable ip address", addr)}; @@ -648,6 +648,7 @@ namespace llarp 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", @@ -775,18 +776,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 Default DefaultDNSBind{platform::is_linux ? "127.3.2.1:53" : "127.0.0.1:53"}; + constexpr std::array DefaultDNSBind{ +#ifdef __linux__ +#ifdef WITH_SYSTEMD + // when we have systemd support add a random high port on loopback as well + // see https://github.com/oxen-io/lokinet/issues/1887#issuecomment-1091897282 + Default{"127.0.0.1:0"}, +#endif + Default{"127.3.2.1:53"}, +#else + Default{"127.0.0.1:53"}, +#endif + }; // Default, but if we get any upstream (including upstream=, i.e. empty string) we clear it - constexpr Default DefaultUpstreamDNS{"9.9.9.10"}; + 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.", @@ -798,25 +807,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( @@ -843,6 +879,11 @@ namespace llarp "(This is not used directly by lokinet itself, but by the lokinet init scripts", "on systems which use resolveconf)", }); + + // forwad the rest to libunbound + conf.addUndeclaredHandler("dns", [this](auto, std::string_view key, std::string_view val) { + m_ExtraOpts.emplace(key, val); + }); } void @@ -1321,15 +1362,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; }); @@ -1343,7 +1394,7 @@ namespace llarp } bool - Config::LoadString(std::string_view ini, bool isRelay) + Config::LoadConfigData(std::string_view ini, std::optional filename, bool isRelay) { auto params = MakeGenParams(); params->isRelay = isRelay; @@ -1351,7 +1402,18 @@ namespace llarp ConfigDefinition conf{isRelay}; initializeConfig(conf, *params); + for (const auto& item : m_Additional) + { + 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; @@ -1362,6 +1424,8 @@ namespace llarp } }); + LoadOverrides(conf); + conf.process(); return true; @@ -1370,37 +1434,24 @@ namespace llarp bool Config::Load(std::optional fname, bool isRelay) { - if (not fname.has_value()) - return LoadDefault(isRelay); - try + std::vector ini{}; + if (fname) { - auto params = MakeGenParams(); - params->isRelay = isRelay; - params->defaultDataDir = m_DataDir; - - ConfigDefinition conf{isRelay}; - initializeConfig(conf, *params); - m_Parser.Clear(); - if (!m_Parser.LoadFile(*fname)) - { + if (not fs::exists(*fname)) return false; - } - LoadOverrides(); - - m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) { - for (const auto& pair : values) - { - conf.addConfigValue(section, pair.first, pair.second); - } - }); - conf.process(); - return true; - } - catch (const std::exception& e) - { - LogError("Error trying to init and parse config from file: ", e.what()); - return false; + fs::ifstream inf{*fname, std::ios::in | std::ios::binary}; + auto sz = inf.seekg(0, std::ios::end).tellg(); + inf.seekg(0, std::ios::beg); + ini.resize(sz); + inf.read(ini.data(), ini.size()); } + return LoadConfigData(std::string_view{ini.data(), ini.size()}, fname, isRelay); + } + + bool + Config::LoadString(std::string_view ini, bool isRelay) + { + return LoadConfigData(ini, std::nullopt, isRelay); } bool diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 0d7a6856d..54dbcc443 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -155,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); @@ -294,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 ccf885c15..aa199ebea 100644 --- a/llarp/config/definition.cpp +++ b/llarp/config/definition.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -176,12 +175,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; @@ -190,44 +191,43 @@ 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{}={}\n", name, val); } else if (not(def->hidden and not has_comment)) { - sect_out << "\n"; - if (not def->required) - sect_out << "#"; - sect_out << name << "=" << def->defaultValueAsString() << "\n"; + for (const auto& val : def->defaultValuesAsString()) + fmt::format_to(sect_append, "\n{}{}={}\n", def->required ? "" : "#", name, val); } }); - 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& diff --git a/llarp/config/definition.hpp b/llarp/config/definition.hpp index b3f9cd0ab..8f9225c28 100644 --- a/llarp/config/definition.hpp +++ b/llarp/config/definition.hpp @@ -97,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