Merge pull request #1969 from majestrate/wintun-windivert-2022-08-02

use wintun and windivert for windows platform bits
pull/1994/head
Jason Rhinelander 2 years ago committed by GitHub
commit 8b321612da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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=[

@ -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("$<$<OR:$<COMPILE_LANGUAGE:CXX>,$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:OBJC>>:${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()

@ -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

@ -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")

@ -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()

@ -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()

@ -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($<$<COMPILE_LANGUAGE:C>:-Wno-bad-function-cast>)
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-cast-function-type>)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-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})

@ -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")

@ -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

@ -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 <<SFTP
$mkdirs
put $archive $upload_to
$put_debug
SFTP
set +o xtrace

@ -0,0 +1,2 @@
[logging]
level=debug

@ -0,0 +1,5 @@
#
# "suggested" default exit node config
#
[network]
exit-node=exit.loki

@ -0,0 +1,5 @@
#
# persist .loki address in a private key file in the data dir
#
[network]
keyfile=lokinet-addr.privkey

@ -21,23 +21,23 @@ fi
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 '</replacement>' | 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 '</replacement>' | 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

@ -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"

@ -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"

@ -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 \
$@
"$@"

@ -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

@ -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 $<TARGET_FILE:${exe}> $<TARGET_FILE:${exe}>.debug
COMMAND ${CMAKE_STRIP} ARGS --strip-all $<TARGET_FILE:${exe}>)
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 $<TARGET_FILE:lokinet>.debug
DEPENDS lokinet)
endif()

@ -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<fs::path> 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);
}

@ -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"

@ -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)

@ -84,9 +84,8 @@ extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv* env, jobject self)
{
auto ptr = GetImpl<llarp::Context>(env, self);
if (const auto& router = ptr->router; ptr and ptr->router)
return router->OutboundUDPSocket();
if (auto ptr = GetImpl<llarp::Context>(env, self); ptr and ptr->router)
return ptr->router->OutboundUDPSocket();
return -1;
}

@ -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()

@ -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}

@ -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<NSData*>* datagrams = (__bridge_transfer NSArray<NSData*>*) req->data;
static void
on_sent(uv_udp_send_t* req, int status)
{
(void)status;
NSArray<NSData*>* datagrams = (__bridge_transfer NSArray<NSData*>*)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<NSData*>* data = [NSArray<NSData*> 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<NSData*> 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<NSData*>* 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<NSData*>* 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

@ -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<NEIPv4Route*>* 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<NEIPv4Route*>* 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<NEIPv6Route*>* 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<NEIPv6Route*>* 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<NEPacket*>* packets) {
[self.packetFlow readPacketObjectsWithCompletionHandler:^(NSArray<NEPacket*>* 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

@ -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

@ -19,7 +19,7 @@ namespace llarp::apple
}
std::shared_ptr<llarp::handlers::TunEndpoint> 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<llarp::handlers::TunEndpoint>(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)

@ -1,7 +1,7 @@
#pragma once
#include <llarp/router/abstractrouter.hpp>
#include <llarp/ev/vpn.hpp>
#include <llarp/vpn/platform.hpp>
#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<IPVariant_t>
GetGatewaysNotOnInterface(std::string ifname) override
std::vector<net::ipaddr_t>
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<IPVariant_t> ret;
ret.push_back(huint32_t{0});
std::vector<net::ipaddr_t> ret;
ret.emplace_back(net::ipv4addr_t{});
return ret;
}
private:
llarp::Context& context;
bool trampoline_active = false;
std::vector<llarp::SockAddr> saved_upstream_dns;
void
check_trampoline(bool enable);

@ -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

@ -1,7 +1,7 @@
#pragma once
#include <llarp.hpp>
#include <llarp/ev/vpn.hpp>
#include <llarp/vpn/platform.hpp>
#include <llarp/util/thread/queue.hpp>
#include <memory>
@ -29,9 +29,6 @@ namespace llarp::apple
int
PollFD() const override;
std::string
IfName() const override;
net::IPPacket
ReadNextPacket() override;

@ -1,6 +1,6 @@
#pragma once
#include <llarp/ev/vpn.hpp>
#include <llarp/vpn/platform.hpp>
#include "vpn_interface.hpp"
#include "route_manager.hpp"

@ -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<std::string>(
"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<std::string>(
"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<bool>(
"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<std::string>(
"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<std::string>(
"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<fs::path>(
@ -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<fs::path> 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<fs::path> fname, bool isRelay)
{
if (not fname.has_value())
return LoadDefault(isRelay);
try
std::vector<char> 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

@ -155,9 +155,13 @@ namespace llarp
struct DnsConfig
{
SockAddr m_bind;
bool m_raw_dns;
std::vector<SockAddr> m_bind;
std::vector<SockAddr> m_upstreamDNS;
std::vector<fs::path> m_hostfiles;
std::optional<SockAddr> m_QueryBind;
std::unordered_multimap<std::string, std::string> 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<fs::path> fname = std::nullopt, bool isRelay = false);
void
LoadOverrides();
LoadOverrides(ConfigDefinition& conf) const;
std::vector<std::array<std::string, 3>> m_Additional;
ConfigParser m_Parser;

@ -2,7 +2,6 @@
#include <llarp/util/logging.hpp>
#include <iterator>
#include <sstream>
#include <stdexcept>
#include <cassert>
@ -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&

@ -97,12 +97,19 @@ namespace llarp
template <typename U>
constexpr bool is_default<U&> = is_default<remove_cvref_t<U>>;
template <typename T>
constexpr bool is_default_array = false;
template <typename T, size_t N>
constexpr bool is_default_array<std::array<Default<T>, N>> = true;
template <typename U>
constexpr bool is_default_array<U&> = is_default_array<remove_cvref_t<U>>;
template <typename T, typename Option>
constexpr bool is_option =
std::is_base_of_v<
option_flag,
remove_cvref_t<
Option>> or std::is_same_v<Comment, Option> or is_default<Option> or std::is_invocable_v<remove_cvref_t<Option>, T>;
Option>> or std::is_same_v<Comment, Option> or is_default<Option> or is_default_array<Option> or std::is_invocable_v<remove_cvref_t<Option>, T>;
} // namespace config
/// A base class for specifying config options and their constraints. The basic to/from string
@ -128,8 +135,8 @@ namespace llarp
/// Subclasses should provide their default value as a string
///
/// @return the option's default value represented as a string
virtual std::string
defaultValueAsString() = 0;
virtual std::vector<std::string>
defaultValuesAsString() = 0;
/// Subclasses should parse and store the provided input
///
@ -143,13 +150,11 @@ namespace llarp
virtual size_t
getNumberFound() const = 0;
/// Subclasess should write their parsed value as a string, optionally falling back to any
/// specified default if `useDefault` is true.
/// Subclasess should write their parsed values as strings.
///
/// @param useDefault should specify whether to fallback to default when possible
/// @return the option's value as a string
virtual std::string
valueAsString(bool useDefault) = 0;
/// @return the option's value(s) as strings
virtual std::vector<std::string>
valuesAsString() = 0;
/// Subclassess should call their acceptor, if present. See OptionDefinition for more details.
///
@ -170,9 +175,9 @@ namespace llarp
std::vector<std::string> comments;
};
/// The primary type-aware implementation of OptionDefinitionBase, this templated class allows
/// for implementations which can use the std::ostringstream and std::istringstream for to/from
/// string functionality.
/// The primary type-aware implementation of OptionDefinitionBase, this templated class allows for
/// implementations which can use fmt::format for conversion to string and std::istringstream for
/// input from string.
///
/// Note that types (T) used as template parameters here must be used verbatim when calling
/// ConfigDefinition::getConfigValue(). Similar types such as uint32_t and int32_t cannot be
@ -184,7 +189,7 @@ namespace llarp
///
/// @param defaultValue_ is used in the following situations:
/// 1) as the return value for getValue() if there is no parsed value and required==false
/// 2) as the output in defaultValueAsString(), used to generate config files
/// 2) as the output in defaultValuesAsString(), used to generate config files
/// 3) as the output in valueAsString(), used to generate config files
///
/// @param opts - 0 or more of config::Required, config::Hidden, config::Default{...}, etc.
@ -202,18 +207,28 @@ namespace llarp
(extractComments(std::forward<Options>(opts)), ...);
}
/// Extracts a default value from an config::Default<U>; ignores anything that isn't an
/// config::Default<U>.
/// Extracts a default value from an config::Default<U> or an array of defaults (for
/// multi-valued options with multi-value default); ignores anything else.
template <typename U>
void
extractDefault(U&& defaultValue_)
{
if constexpr (config::is_default<U>)
if constexpr (config::is_default_array<U>)
{
if (!multiValued)
throw std::logic_error{"Array config defaults require multiValue mode"};
defaultValues.clear();
defaultValues.reserve(defaultValue_.size());
for (const auto& def : defaultValue_)
defaultValues.push_back(def.val);
}
else if constexpr (config::is_default<U>)
{
static_assert(
std::is_convertible_v<decltype(std::forward<U>(defaultValue_).val), T>,
"Cannot convert given llarp::config::Default to the required value type");
defaultValue = std::forward<U>(defaultValue_).val;
defaultValues = {std::forward<U>(defaultValue_).val};
}
}
@ -236,33 +251,22 @@ namespace llarp
comments = std::forward<U>(comment).comments;
}
/// Returns the first parsed value, if available. Otherwise, provides the default value if the
/// option is not required. Otherwise, returns an empty optional.
/// Returns the first parsed value, if available. Otherwise, provides the (first) default value
/// if the option is not required. Otherwise, returns an empty optional.
///
/// @return an optional with the parsed value, the default value, or no value.
/// @return an optional with the parsed value, the (first) default value, or no value.
std::optional<T>
getValue() const
{
if (parsedValues.empty())
return required ? std::nullopt : defaultValue;
{
if (required || defaultValues.empty())
return std::nullopt;
return defaultValues.front();
}
return parsedValues.front();
}
/// Returns the value at the given index.
///
/// @param index
/// @return the value at the given index, if it exists
/// @throws range_error exception if index >= size
T
getValueAt(size_t index) const
{
if (index >= parsedValues.size())
throw std::range_error{
fmt::format("no value at index {}, size: {}", index, parsedValues.size())};
return parsedValues[index];
}
/// Returns the number of values found.
///
/// @return number of values found
@ -272,18 +276,21 @@ namespace llarp
return parsedValues.size();
}
std::string
defaultValueAsString() override
std::vector<std::string>
defaultValuesAsString() override
{
if (!defaultValue)
return "";
if (defaultValues.empty())
return {};
if constexpr (std::is_same_v<fs::path, T>)
return defaultValue->string();
std::ostringstream oss;
oss << *defaultValue;
return oss.str();
return {{defaultValues.front().u8string()}};
else
{
std::vector<std::string> def_strs;
def_strs.reserve(defaultValues.size());
for (const auto& v : defaultValues)
def_strs.push_back(fmt::format("{}", v));
return def_strs;
}
}
void
@ -317,21 +324,21 @@ namespace llarp
}
}
std::string
valueAsString(bool useDefault) override
std::vector<std::string>
valuesAsString() override
{
std::ostringstream oss;
if (parsedValues.size() > 0)
oss << parsedValues[0];
else if (useDefault and defaultValue)
oss << *defaultValue;
return oss.str();
if (parsedValues.empty())
return {};
std::vector<std::string> result;
result.reserve(parsedValues.size());
for (const auto& v : parsedValues)
result.push_back(fmt::format("{}", v));
return result;
}
/// Attempts to call the acceptor function, if present. This function may throw if the value is
/// not acceptable. Additionally, tryAccept should not be called if the option is required and
/// no value has been provided.
/// Attempts to call the acceptor function, if present. This function may throw if the value
/// is not acceptable. Additionally, tryAccept should not be called if the option is required
/// and no value has been provided.
///
/// @throws if required and no value present or if the acceptor throws
void
@ -350,8 +357,9 @@ namespace llarp
if (multiValued)
{
// add default value in multi value mode
if (defaultValue and parsedValues.empty())
acceptor(*defaultValue);
if (parsedValues.empty() and not defaultValues.empty())
for (const auto& v : defaultValues)
acceptor(v);
for (auto value : parsedValues)
{
@ -367,7 +375,7 @@ namespace llarp
}
}
std::optional<T> defaultValue;
std::vector<T> defaultValues;
std::vector<T> parsedValues;
std::function<void(T)> acceptor;
};
@ -389,8 +397,8 @@ namespace llarp
// map of section-name to map-of-definitions
using SectionMap = std::unordered_map<std::string, DefinitionMap>;
/// A ConfigDefinition holds an ordered set of OptionDefinitions defining the allowable values and
/// their constraints (specified through calls to defineOption()).
/// A ConfigDefinition holds an ordered set of OptionDefinitions defining the allowable values
/// and their constraints (specified through calls to defineOption()).
///
/// The layout and grouping of the config options are modelled after the INI file format; each
/// option has a name and is grouped under a section. Duplicate option names are allowed only if
@ -400,9 +408,9 @@ namespace llarp
/// Configured values (e.g. those encountered when parsing a file) can be provided through calls
/// to addConfigValue(). These take a std::string as a value, which is automatically parsed.
///
/// The ConfigDefinition can be used to print out a full config string (or file), including fields
/// with defaults and optionally fields which have a specified value (values provided through
/// calls to addConfigValue()).
/// The ConfigDefinition can be used to print out a full config string (or file), including
/// fields with defaults and optionally fields which have a specified value (values provided
/// through calls to addConfigValue()).
struct ConfigDefinition
{
explicit ConfigDefinition(bool relay) : relay{relay}
@ -411,8 +419,8 @@ namespace llarp
/// Specify the parameters and type of a configuration option. The parameters are members of
/// OptionDefinitionBase; the type is inferred from OptionDefinition's template parameter T.
///
/// This function should be called for every option that this Configuration supports, and should
/// be done before any other interactions involving that option.
/// This function should be called for every option that this Configuration supports, and
/// should be done before any other interactions involving that option.
///
/// @param def should be a unique_ptr to a valid subclass of OptionDefinitionBase
/// @return `*this` for chaining calls
@ -420,8 +428,8 @@ namespace llarp
ConfigDefinition&
defineOption(OptionDefinition_ptr def);
/// Convenience function which calls defineOption with a OptionDefinition of the specified type
/// and with parameters passed through to OptionDefinition's constructor.
/// Convenience function which calls defineOption with a OptionDefinition of the specified
/// type and with parameters passed through to OptionDefinition's constructor.
template <typename T, typename... Params>
ConfigDefinition&
defineOption(Params&&... args)
@ -433,8 +441,9 @@ namespace llarp
/// representing the type used by the option (e.g. the type provided when defineOption() was
/// called).
///
/// If the specified option doesn't exist, an exception will be thrown. Otherwise, the option's
/// parseValue() will be invoked, and should throw an exception if the string can't be parsed.
/// If the specified option doesn't exist, an exception will be thrown. Otherwise, the
/// option's parseValue() will be invoked, and should throw an exception if the string can't
/// be parsed.
///
/// @param section is the section this value resides in
/// @param name is the name of the value
@ -493,9 +502,9 @@ namespace llarp
void
validateRequiredFields();
/// Accept all options. This will call the acceptor (if present) on each option. Note that this
/// should only be called if all required fields are present (that is, validateRequiredFields()
/// has been or could be called without throwing).
/// Accept all options. This will call the acceptor (if present) on each option. Note that
/// this should only be called if all required fields are present (that is,
/// validateRequiredFields() has been or could be called without throwing).
///
/// @throws if any option's acceptor throws
void

@ -17,7 +17,7 @@ namespace llarp
{
{
std::ifstream f(fname, std::ios::in | std::ios::binary);
if (!f.is_open())
if (not f.is_open())
return false;
f.seekg(0, std::ios::end);
m_Data.resize(f.tellg());

@ -47,6 +47,12 @@ namespace llarp
void
Save();
inline void
Filename(fs::path f)
{
m_FileName = f;
};
private:
bool
Parse();

@ -1,17 +0,0 @@
#include "table.hpp"
#include <llarp/crypto/crypto.hpp>
namespace llarp
{
namespace consensus
{
ShortHash
Table::CalculateHash() const
{
ShortHash h;
const llarp_buffer_t buf(begin()->data(), size());
CryptoManager::instance()->shorthash(h, buf);
return h;
}
} // namespace consensus
} // namespace llarp

@ -1,17 +0,0 @@
#pragma once
#include <llarp/crypto/types.hpp>
#include <vector>
namespace llarp
{
namespace consensus
{
/// consensus table
struct Table : public std::vector<RouterID>
{
ShortHash
CalculateHash() const;
};
} // namespace consensus
} // namespace llarp

@ -1,4 +1,5 @@
#pragma once
#include "platform.hpp"
#include <llarp/util/fs.hpp>
@ -21,20 +22,22 @@ namespace llarp
inline fs::path
GetDefaultDataDir()
{
#ifdef _WIN32
return "C:/programdata/lokinet";
#else
fs::path datadir{"/var/lib/lokinet"};
if (auto uid = ::geteuid())
if constexpr (not platform::is_windows)
{
if (auto* pw = getpwuid(uid))
fs::path datadir{"/var/lib/lokinet"};
#ifndef _WIN32
if (auto uid = geteuid())
{
datadir = fs::path{pw->pw_dir} / ".lokinet";
if (auto* pw = getpwuid(uid))
{
datadir = fs::path{pw->pw_dir} / ".lokinet";
}
}
}
return datadir;
#endif
return datadir;
}
else
return "C:\\ProgramData\\Lokinet";
}
inline fs::path

@ -0,0 +1,7 @@
#pragma once
namespace llarp::constants
{
constexpr auto udp_header_bytes = 8;
constexpr auto ip_header_min_bytes = 20;
} // namespace llarp::constants

@ -12,6 +12,15 @@ namespace llarp::platform
#endif
;
/// building with systemd enabled ?
inline constexpr bool with_systemd =
#ifdef WITH_SYSTEMD
true
#else
false
#endif
;
/// are we freebsd ?
inline constexpr bool is_freebsd =
#ifdef __FreeBSD__
@ -39,6 +48,15 @@ namespace llarp::platform
#endif
;
/// are we building as an apple system extension
inline constexpr bool is_apple_sysex =
#ifdef MACOS_SYSTEM_EXTENSION
true
#else
false
#endif
;
/// are we an android platform ?
inline constexpr bool is_android =
#ifdef ANDROID
@ -57,9 +75,26 @@ namespace llarp::platform
#endif
;
/// are we running with pybind simulation mode enabled?
inline constexpr bool is_simulation =
#ifdef LOKINET_HIVE
true
#else
false
#endif
;
/// do we have systemd support ?
// on cross compiles sometimes weird permutations of target and host make this value not correct,
// this ensures it always is
inline constexpr bool has_systemd = is_linux and with_systemd and not(is_android or is_windows);
/// are we using macos ?
inline constexpr bool is_macos = is_apple and not is_iphone;
/// are we a mobile phone ?
inline constexpr bool is_mobile = is_android or is_iphone;
/// does this platform support native ipv6 ?
// TODO: make windows support ipv6
inline constexpr bool supports_ipv6 = not is_windows;
} // namespace llarp::platform

@ -35,7 +35,7 @@ namespace llarp
// first key
if (firstKey)
{
if (!(*key == "A"))
if (!(key->startswith("A")))
return false;
if (!bencode_read_string(buffer, &strbuf))
return false;

@ -26,11 +26,11 @@ namespace llarp::dht
bool
FindNameMessage::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* val)
{
if (key == "H")
if (key.startswith("H"))
{
return NameHash.BDecode(val);
}
if (key == "T")
if (key.startswith("T"))
{
return bencode_read_integer(val, &TxID);
}

@ -105,7 +105,7 @@ namespace llarp
{
llarp_buffer_t strbuf;
if (key == "E")
if (key.startswith("E"))
{
uint64_t result;
if (!bencode_read_integer(val, &result))
@ -115,7 +115,7 @@ namespace llarp
return true;
}
if (key == "I")
if (key.startswith("I"))
{
uint64_t result;
if (!bencode_read_integer(val, &result))
@ -124,7 +124,7 @@ namespace llarp
iterative = result != 0;
return true;
}
if (key == "K")
if (key.startswith("K"))
{
if (!bencode_read_string(val, &strbuf))
return false;
@ -134,11 +134,11 @@ namespace llarp
std::copy(strbuf.base, strbuf.base + targetKey.SIZE, targetKey.begin());
return true;
}
if (key == "T")
if (key.startswith("T"))
{
return bencode_read_integer(val, &txid);
}
if (key == "V")
if (key.startswith("V"))
{
return bencode_read_integer(val, &version);
}

@ -79,11 +79,11 @@ namespace llarp
bool
GotIntroMessage::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* buf)
{
if (key == "I")
if (key.startswith("I"))
{
return BEncodeReadList(found, buf);
}
if (key == "K")
if (key.startswith("K"))
{
if (closer) // duplicate key?
return false;

@ -27,7 +27,7 @@ namespace llarp::dht
bool
GotNameMessage::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* val)
{
if (key == "D")
if (key.startswith("D"))
{
llarp_buffer_t str{};
if (not bencode_read_string(val, &str))
@ -38,11 +38,11 @@ namespace llarp::dht
std::copy_n(str.cur, str.sz, result.ciphertext.data());
return true;
}
if (key == "N")
if (key.startswith("N"))
{
return result.nonce.BDecode(val);
}
if (key == "T")
if (key.startswith("T"))
{
return bencode_read_integer(val, &TxID);
}

@ -53,22 +53,22 @@ namespace llarp
bool
GotRouterMessage::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* val)
{
if (key == "K")
if (key.startswith("K"))
{
if (closerTarget) // duplicate key?
return false;
closerTarget = std::make_unique<dht::Key_t>();
return closerTarget->BDecode(val);
}
if (key == "N")
if (key.startswith("N"))
{
return BEncodeReadList(nearKeys, val);
}
if (key == "R")
if (key.startswith("R"))
{
return BEncodeReadList(foundRCs, val);
}
if (key == "T")
if (key.startswith("T"))
{
return bencode_read_integer(val, &txid);
}

@ -13,6 +13,8 @@ namespace llarp
{
namespace dns
{
static auto logcat = log::Cat("dns");
bool
MessageHeader::Encode(llarp_buffer_t* buf) const
{
@ -116,16 +118,16 @@ namespace llarp
{
if (!qd.Decode(buf))
{
LogError("failed to decode question");
log::error(logcat, "failed to decode question");
return false;
}
LogDebug("dns question: ", qd);
log::debug(logcat, "question: {}", qd);
}
for (auto& an : answers)
{
if (not an.Decode(buf))
{
LogDebug("failed to decode answer");
log::debug(logcat, "failed to decode answer");
return false;
}
}
@ -414,5 +416,17 @@ namespace llarp
fmt::format("{}", fmt::join(additional, ",")));
}
std::optional<Message>
MaybeParseDNSMessage(llarp_buffer_t buf)
{
MessageHeader hdr{};
if (not hdr.Decode(&buf))
return std::nullopt;
Message msg{hdr};
if (not msg.Decode(&buf))
return std::nullopt;
return msg;
}
} // namespace dns
} // namespace llarp

@ -103,6 +103,9 @@ namespace llarp
std::vector<ResourceRecord> authorities;
std::vector<ResourceRecord> additional;
};
std::optional<Message>
MaybeParseDNSMessage(llarp_buffer_t buf);
} // namespace dns
template <>

@ -0,0 +1,21 @@
#include "nm_platform.hpp"
#ifdef WITH_SYSTEMD
extern "C"
{
#include <net/if.h>
}
#include <llarp/linux/dbus.hpp>
using namespace std::literals;
namespace llarp::dns::nm
{
void
Platform::set_resolver(unsigned int, llarp::SockAddr, bool)
{
// todo: implement me eventually
}
} // namespace llarp::dns::nm
#endif

@ -0,0 +1,23 @@
#pragma once
#include "platform.hpp"
#include <llarp/constants/platform.hpp>
#include <type_traits>
#include <unordered_map>
namespace llarp::dns
{
namespace nm
{
// a dns platform that sets dns via network manager
class Platform : public I_Platform
{
public:
virtual ~Platform() = default;
void
set_resolver(unsigned int index, llarp::SockAddr dns, bool global) override;
};
}; // namespace nm
using NM_Platform_t = std::conditional_t<false, nm::Platform, Null_Platform>;
} // namespace llarp::dns

@ -0,0 +1,32 @@
#include "platform.hpp"
namespace llarp::dns
{
void
Multi_Platform::add_impl(std::unique_ptr<I_Platform> impl)
{
m_Impls.emplace_back(std::move(impl));
}
void
Multi_Platform::set_resolver(unsigned int index, llarp::SockAddr dns, bool global)
{
if (m_Impls.empty())
return;
size_t fails{0};
for (const auto& ptr : m_Impls)
{
try
{
ptr->set_resolver(index, dns, global);
}
catch (std::exception& ex)
{
log::warning(log::Cat("dns"), "{}", ex.what());
fails++;
}
}
if (fails == m_Impls.size())
throw std::runtime_error{"tried all ways to set resolver and failed"};
}
} // namespace llarp::dns

@ -0,0 +1,59 @@
#pragma once
#include <string>
#include <memory>
#include <llarp/net/sock_addr.hpp>
#include <llarp/util/logging.hpp>
#include <stdexcept>
#ifndef _WIN32
#include <net/if.h>
#endif
namespace llarp::dns
{
/// sets dns settings in a platform dependant way
class I_Platform
{
public:
virtual ~I_Platform() = default;
/// Attempts to set lokinet as the DNS server.
/// throws if unsupported or fails.
///
///
/// \param if_index -- the interface index to which we add the DNS servers, this can be gotten
/// from the interface name e.g. lokitun0 (Typically tun_endpoint.GetIfName().) and then put
/// through if_nametoindex().
/// \param dns -- the listening address of the lokinet DNS server
/// \param global -- whether to set up lokinet for all DNS queries (true) or just .loki & .snode
/// addresses (false).
virtual void
set_resolver(unsigned int if_index, llarp::SockAddr dns, bool global) = 0;
};
/// a dns platform does silently does nothing, successfully
class Null_Platform : public I_Platform
{
public:
~Null_Platform() override = default;
void
set_resolver(unsigned int, llarp::SockAddr, bool) override
{}
};
/// a collection of dns platforms that are tried in order when setting dns
class Multi_Platform : public I_Platform
{
std::vector<std::unique_ptr<I_Platform>> m_Impls;
public:
~Multi_Platform() override = default;
/// add a platform to be owned
void
add_impl(std::unique_ptr<I_Platform> impl);
/// try all owned platforms to set the resolver, throws if none of them work
void
set_resolver(unsigned int if_index, llarp::SockAddr dns, bool global) override;
};
} // namespace llarp::dns

@ -8,6 +8,8 @@ namespace llarp
{
namespace dns
{
static auto logcat = log::Cat("dns");
Question::Question(Question&& other)
: qname(std::move(other.qname))
, qtype(std::move(other.qtype))
@ -41,17 +43,17 @@ namespace llarp
qname = *std::move(name);
else
{
llarp::LogError("failed to decode name");
log::error(logcat, "failed to decode name");
return false;
}
if (!buf->read_uint16(qtype))
{
llarp::LogError("failed to decode type");
log::error(logcat, "failed to decode type");
return false;
}
if (!buf->read_uint16(qclass))
{
llarp::LogError("failed to decode class");
log::error(logcat, "failed to decode class");
return false;
}
return true;

@ -8,6 +8,8 @@ namespace llarp
{
namespace dns
{
static auto logcat = log::Cat("dns");
ResourceRecord::ResourceRecord(const ResourceRecord& other)
: rr_name(other.rr_name)
, rr_type(other.rr_type)
@ -64,22 +66,22 @@ namespace llarp
return false;
if (!buf->read_uint16(rr_type))
{
llarp::LogDebug("failed to decode rr type");
log::debug(logcat, "failed to decode rr type");
return false;
}
if (!buf->read_uint16(rr_class))
{
llarp::LogDebug("failed to decode rr class");
log::debug(logcat, "failed to decode rr class");
return false;
}
if (!buf->read_uint32(ttl))
{
llarp::LogDebug("failed to decode ttl");
log::debug(logcat, "failed to decode ttl");
return false;
}
if (!DecodeRData(buf, rData))
{
llarp::LogDebug("failed to decode rr rdata ", *this);
log::debug(logcat, "failed to decode rr rdata {}", *this);
return false;
}
return true;

@ -0,0 +1,128 @@
#ifdef WITH_SYSTEMD
#include "sd_platform.hpp"
extern "C"
{
#include <net/if.h>
}
#include <llarp/linux/dbus.hpp>
using namespace std::literals;
namespace llarp::dns::sd
{
void
Platform::set_resolver(unsigned int if_ndx, llarp::SockAddr dns, bool global)
{
linux::DBUS _dbus{
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager"};
// This passing address by bytes and using two separate calls for ipv4/ipv6 is gross, but
// the alternative is to build up a bunch of crap with va_args, which is slightly more
// gross.
const bool isStandardDNSPort = dns.getPort() == 53;
if (dns.isIPv6())
{
auto ipv6 = dns.getIPv6();
static_assert(sizeof(ipv6) == 16);
auto* a = reinterpret_cast<const uint8_t*>(&ipv6);
if (isStandardDNSPort)
{
_dbus(
"SetLinkDNS",
"ia(iay)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET6, // network address type
(int)16, // network addr byte size
// clang-format off
a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7],
a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15] // yuck
// clang-format on
);
}
else
{
_dbus(
"SetLinkDNSEx",
"ia(iayqs)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET6, // network address type
(int)16, // network addr byte size
// clang-format off
a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7],
a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], // yuck
// clang-format on
(uint16_t)dns.getPort(),
nullptr // dns server name (for TLS SNI which we don't care about)
);
}
}
else
{
auto ipv4 = dns.getIPv4();
static_assert(sizeof(ipv4) == 4);
auto* a = reinterpret_cast<const uint8_t*>(&ipv4);
if (isStandardDNSPort)
{
_dbus(
"SetLinkDNS",
"ia(iay)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET, // network address type
(int)4, // network addr byte size
// clang-format off
a[0], a[1], a[2], a[3] // yuck
// clang-format on
);
}
else
{
_dbus(
"SetLinkDNSEx",
"ia(iayqs)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET, // network address type
(int)4, // network addr byte size
// clang-format off
a[0], a[1], a[2], a[3], // yuck
// clang-format on
(uint16_t)dns.getPort(),
nullptr // dns server name (for TLS SNI which we don't care about)
);
}
}
if (global)
// Setting "." as a routing domain gives this DNS server higher priority in resolution
// compared to dns servers that are set without a domain (e.g. the default for a
// DHCP-configured DNS server)
_dbus(
"SetLinkDomains",
"ia(sb)",
(int32_t)if_ndx,
(int)1, // array size
"." // global DNS root
);
else
// Only resolve .loki and .snode through lokinet (so you keep using your local DNS
// server for everything else, which is nicer than forcing everything though lokinet's
// upstream DNS).
_dbus(
"SetLinkDomains",
"ia(sb)",
(int32_t)if_ndx,
(int)2, // array size
"loki", // domain
(int)1, // routing domain = true
"snode", // domain
(int)1 // routing domain = true
);
}
} // namespace llarp::dns::sd
#endif

@ -0,0 +1,23 @@
#pragma once
#include "platform.hpp"
#include <llarp/constants/platform.hpp>
#include <type_traits>
namespace llarp::dns
{
namespace sd
{
/// a dns platform that sets dns via systemd resolved
class Platform : public I_Platform
{
public:
virtual ~Platform() = default;
void
set_resolver(unsigned int if_index, llarp::SockAddr dns, bool global) override;
};
} // namespace sd
using SD_Platform_t =
std::conditional_t<llarp::platform::has_systemd, sd::Platform, Null_Platform>;
} // namespace llarp::dns

@ -1,161 +1,690 @@
#include "server.hpp"
#include <llarp/constants/platform.hpp>
#include <llarp/constants/apple.hpp>
#include "dns.hpp"
#include <iterator>
#include <llarp/crypto/crypto.hpp>
#include <array>
#include <stdexcept>
#include <utility>
#include <llarp/ev/udp_handle.hpp>
#include <optional>
#include <memory>
#include <unbound.h>
#include <uvw.hpp>
#include "oxen/log.hpp"
#include "sd_platform.hpp"
#include "nm_platform.hpp"
#include "win32_platform.hpp"
namespace llarp::dns
{
PacketHandler::PacketHandler(EventLoop_ptr loop, IQueryHandler* h)
: m_QueryHandler{h}, m_Loop{std::move(loop)}
{}
static auto logcat = log::Cat("dns");
Proxy::Proxy(EventLoop_ptr loop, IQueryHandler* h)
: PacketHandler{loop, h}, m_Loop(std::move(loop))
void
QueryJob_Base::Cancel() const
{
m_Server = m_Loop->make_udp(
[this](UDPHandle&, SockAddr a, OwnedBuffer buf) { HandlePacket(a, a, buf); });
Message reply{m_Query};
reply.AddServFail();
SendReply(reply.ToBuffer());
}
void
PacketHandler::Stop()
/// sucks up udp packets from a bound socket and feeds it to a server
class UDPReader : public PacketSource_Base, public std::enable_shared_from_this<UDPReader>
{
if (m_UnboundResolver)
m_UnboundResolver->Stop();
}
Server& m_DNS;
std::shared_ptr<llarp::UDPHandle> m_udp;
SockAddr m_LocalAddr;
bool
Proxy::Start(SockAddr addr, std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles)
public:
explicit UDPReader(Server& dns, const EventLoop_ptr& loop, llarp::SockAddr bindaddr)
: m_DNS{dns}
{
m_udp = loop->make_udp([&](auto&, SockAddr src, llarp::OwnedBuffer buf) {
if (src == m_LocalAddr)
return;
if (not m_DNS.MaybeHandlePacket(shared_from_this(), m_LocalAddr, src, std::move(buf)))
{
log::warning(logcat, "did not handle dns packet from {} to {}", src, m_LocalAddr);
}
});
m_udp->listen(bindaddr);
if (auto maybe_addr = BoundOn())
{
m_LocalAddr = *maybe_addr;
}
else
throw std::runtime_error{"cannot find which address our dns socket is bound on"};
}
std::optional<SockAddr>
BoundOn() const override
{
return m_udp->LocalAddr();
}
bool
WouldLoop(const SockAddr& to, const SockAddr&) const override
{
return to != m_LocalAddr;
}
void
SendTo(const SockAddr& to, const SockAddr&, llarp::OwnedBuffer buf) const override
{
m_udp->send(to, std::move(buf));
}
void
Stop() override
{
m_udp->close();
}
};
namespace libunbound
{
if (not PacketHandler::Start(addr, std::move(resolvers), std::move(hostfiles)))
return false;
return m_Server->listen(addr);
class Resolver;
class Query : public QueryJob_Base
{
std::weak_ptr<Resolver> parent;
std::shared_ptr<PacketSource_Base> src;
SockAddr resolverAddr;
SockAddr askerAddr;
public:
explicit Query(
std::weak_ptr<Resolver> parent_,
Message query,
std::shared_ptr<PacketSource_Base> pktsrc,
SockAddr toaddr,
SockAddr fromaddr)
: QueryJob_Base{std::move(query)}
, parent{parent_}
, src{std::move(pktsrc)}
, resolverAddr{std::move(toaddr)}
, askerAddr{std::move(fromaddr)}
{}
virtual void
SendReply(llarp::OwnedBuffer replyBuf) const override;
};
/// Resolver_Base that uses libunbound
class Resolver final : public Resolver_Base, public std::enable_shared_from_this<Resolver>
{
ub_ctx* m_ctx = nullptr;
std::weak_ptr<EventLoop> m_Loop;
#ifdef _WIN32
// windows is dumb so we do ub mainloop in a thread
std::thread runner;
std::atomic<bool> running;
#else
std::shared_ptr<uvw::PollHandle> m_Poller;
#endif
std::optional<SockAddr> m_LocalAddr;
struct ub_result_deleter
{
void
operator()(ub_result* ptr)
{
::ub_resolve_free(ptr);
}
};
const net::Platform*
Net_ptr() const
{
return m_Loop.lock()->Net_ptr();
}
static void
Callback(void* data, int err, ub_result* _result)
{
// take ownership of ub_result
std::unique_ptr<ub_result, ub_result_deleter> result{_result};
// take ownership of our query
std::unique_ptr<Query> query{static_cast<Query*>(data)};
if (err)
{
// some kind of error from upstream
log::warning(logcat, "Upstream DNS failure: {}", ub_strerror(err));
query->Cancel();
return;
}
// rewrite response
OwnedBuffer pkt{(const byte_t*)result->answer_packet, (size_t)result->answer_len};
llarp_buffer_t buf{pkt};
MessageHeader hdr;
hdr.Decode(&buf);
hdr.id = query->Underlying().hdr_id;
buf.cur = buf.base;
hdr.Encode(&buf);
// send reply
query->SendReply(std::move(pkt));
}
void
AddUpstreamResolver(const SockAddr& dns)
{
std::string str = dns.hostString();
if (const auto port = dns.getPort(); port != 53)
fmt::format_to(std::back_inserter(str), "@{}", port);
if (auto err = ub_ctx_set_fwd(m_ctx, str.c_str()))
{
throw std::runtime_error{
fmt::format("cannot use {} as upstream dns: {}", str, ub_strerror(err))};
}
}
bool
ConfigureAppleTrampoline(const SockAddr& dns)
{
// On Apple, when we turn on exit mode, we tear down and then reestablish the unbound
// resolver: in exit mode, we set use upstream to a localhost trampoline that redirects
// packets through the tunnel. In non-exit mode, we directly use the upstream, so we look
// here for a reconfiguration to use the trampoline port to check which state we're in.
//
// We have to do all this crap because we can't directly connect to upstream from here:
// within the network extension, macOS ignores the tunnel we are managing and so, if we
// didn't do this, all our DNS queries would leak out around the tunnel. Instead we have to
// bounce things through the objective C trampoline code (which is what actually handles the
// upstream querying) so that it can call into Apple's special snowflake API to set up a
// socket that has the magic Apple snowflake sauce added on top so that it actually routes
// through the tunnel instead of around it.
//
// But the trampoline *always* tries to send the packet through the tunnel, and that will
// only work in exit mode.
//
// All of this macos behaviour is all carefully and explicitly documented by Apple with
// plenty of examples and other exposition, of course, just like all of their wonderful new
// APIs to reinvent standard unix interfaces with half-baked replacements.
if constexpr (platform::is_apple)
{
if (dns.hostString() == "127.0.0.1" and dns.getPort() == apple::dns_trampoline_port)
{
// macOS is stupid: the default (0.0.0.0) fails with "send failed: Can't assign
// requested address" when unbound tries to connect to the localhost address using a
// source address of 0.0.0.0. Yay apple.
SetOpt("outgoing-interface:", "127.0.0.1");
// The trampoline expects just a single source port (and sends everything back to it).
SetOpt("outgoing-range:", "1");
SetOpt("outgoing-port-avoid:", "0-65535");
SetOpt("outgoing-port-permit:", "{}", apple::dns_trampoline_source_port);
return true;
}
}
return false;
}
void
ConfigureUpstream(const llarp::DnsConfig& conf)
{
bool is_apple_tramp = false;
// set up forward dns
for (const auto& dns : conf.m_upstreamDNS)
{
AddUpstreamResolver(dns);
is_apple_tramp = is_apple_tramp or ConfigureAppleTrampoline(dns);
}
if (auto maybe_addr = conf.m_QueryBind; maybe_addr and not is_apple_tramp)
{
SockAddr addr{*maybe_addr};
std::string host{addr.hostString()};
if (addr.getPort() == 0)
{
// unbound manages their own sockets because of COURSE it does. so we find an open port
// on our system and use it so we KNOW what it is before giving it to unbound to
// explicitly bind to JUST that port.
auto fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
#ifdef _WIN32
if (fd == INVALID_SOCKET)
#else
if (fd == -1)
#endif
{
throw std::invalid_argument{
fmt::format("Failed to create UDP socket for unbound: {}", strerror(errno))};
}
#ifdef _WIN32
#define CLOSE closesocket
#else
#define CLOSE close
#endif
if (0 != bind(fd, static_cast<const sockaddr*>(addr), addr.sockaddr_len()))
{
CLOSE(fd);
throw std::invalid_argument{
fmt::format("Failed to bind UDP socket for unbound: {}", strerror(errno))};
}
struct sockaddr_storage sas;
auto* sa = reinterpret_cast<struct sockaddr*>(&sas);
socklen_t sa_len = sizeof(sas);
int rc = getsockname(fd, sa, &sa_len);
CLOSE(fd);
#undef CLOSE
if (rc != 0)
{
throw std::invalid_argument{
fmt::format("Failed to query UDP port for unbound: {}", strerror(errno))};
}
addr = SockAddr{*sa};
}
m_LocalAddr = addr;
log::info(logcat, "sending dns queries from {}:{}", host, addr.getPort());
// set up query bind port if needed
SetOpt("outgoing-interface:", host);
SetOpt("outgoing-range:", "1");
SetOpt("outgoing-port-avoid:", "0-65535");
SetOpt("outgoing-port-permit:", "{}", addr.getPort());
}
}
void
SetOpt(const std::string& key, const std::string& val)
{
ub_ctx_set_option(m_ctx, key.c_str(), val.c_str());
}
// Wrapper around the above that takes 3+ arguments: the 2nd arg gets formatted with the
// remaining args, and the formatted string passed to the above as `val`.
template <typename... FmtArgs, std::enable_if_t<sizeof...(FmtArgs), int> = 0>
void
SetOpt(const std::string& key, std::string_view format, FmtArgs&&... args)
{
SetOpt(key, fmt::format(format, std::forward<FmtArgs>(args)...));
}
// Copy of the DNS config (a copy because on some platforms, like Apple, we change the applied
// upstream DNS settings when turning on/off exit mode).
llarp::DnsConfig m_conf;
public:
explicit Resolver(const EventLoop_ptr& loop, llarp::DnsConfig conf)
: m_Loop{loop}, m_conf{std::move(conf)}
{
Up(m_conf);
}
~Resolver() override
{
Down();
}
std::string_view
ResolverName() const override
{
return "unbound";
}
virtual std::optional<SockAddr>
GetLocalAddr() const override
{
return m_LocalAddr;
}
void
Up(const llarp::DnsConfig& conf)
{
if (m_ctx)
throw std::logic_error{"Internal error: attempt to Up() dns server multiple times"};
m_ctx = ::ub_ctx_create();
// set libunbound settings
SetOpt("do-tcp:", "no");
for (const auto& [k, v] : conf.m_ExtraOpts)
SetOpt(k, v);
// add host files
for (const auto& file : conf.m_hostfiles)
{
const auto str = file.u8string();
if (auto ret = ub_ctx_hosts(m_ctx, str.c_str()))
{
throw std::runtime_error{
fmt::format("Failed to add host file {}: {}", file, ub_strerror(ret))};
}
}
ConfigureUpstream(conf);
// set async
ub_ctx_async(m_ctx, 1);
// setup mainloop
#ifdef _WIN32
running = true;
runner = std::thread{[this]() {
while (running)
{
ub_wait(m_ctx);
std::this_thread::sleep_for(10ms);
}
ub_process(m_ctx);
}};
#else
if (auto loop = m_Loop.lock())
{
if (auto loop_ptr = loop->MaybeGetUVWLoop())
{
m_Poller = loop_ptr->resource<uvw::PollHandle>(ub_fd(m_ctx));
m_Poller->on<uvw::PollEvent>([this](auto&, auto&) { ub_process(m_ctx); });
m_Poller->start(uvw::PollHandle::Event::READABLE);
return;
}
}
throw std::runtime_error{"no uvw loop"};
#endif
}
void
Down() override
{
#ifdef _WIN32
if (running.exchange(false))
runner.join();
#else
if (m_Poller)
m_Poller->close();
#endif
if (m_ctx)
{
::ub_ctx_delete(m_ctx);
m_ctx = nullptr;
}
}
int
Rank() const override
{
return 10;
}
void
ResetResolver(std::optional<std::vector<SockAddr>> replace_upstream) override
{
Down();
if (replace_upstream)
m_conf.m_upstreamDNS = std::move(*replace_upstream);
Up(m_conf);
}
bool
WouldLoop(const SockAddr& to, const SockAddr& from) const override
{
#if defined(ANDROID)
(void)to;
(void)from;
return false;
#else
const auto& vec = m_conf.m_upstreamDNS;
return std::find(vec.begin(), vec.end(), to) != std::end(vec)
or std::find(vec.begin(), vec.end(), from) != std::end(vec);
#endif
}
template <typename Callable>
void
call(Callable&& f)
{
if (auto loop = m_Loop.lock())
loop->call(std::forward<Callable>(f));
else
log::critical(logcat, "no mainloop?");
}
bool
MaybeHookDNS(
std::shared_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to,
const SockAddr& from) override
{
if (WouldLoop(to, from))
return false;
// we use this unique ptr to clean up on fail
auto tmp = std::make_unique<Query>(weak_from_this(), query, source, to, from);
// no questions, send fail
if (query.questions.empty())
{
tmp->Cancel();
return true;
}
for (const auto& q : query.questions)
{
// dont process .loki or .snode
if (q.HasTLD(".loki") or q.HasTLD(".snode"))
{
tmp->Cancel();
return true;
}
}
const auto& q = query.questions[0];
if (auto err = ub_resolve_async(
m_ctx,
q.Name().c_str(),
q.qtype,
q.qclass,
tmp.get(),
&Resolver::Callback,
nullptr))
{
log::warning(
logcat, "failed to send upstream query with libunbound: {}", ub_strerror(err));
tmp->Cancel();
}
else
{
// Leak the bare pointer we gave to unbound; we'll recapture it in Callback
(void)tmp.release();
}
return true;
}
};
void
Query::SendReply(llarp::OwnedBuffer replyBuf) const
{
if (auto ptr = parent.lock())
{
ptr->call([src = src, from = resolverAddr, to = askerAddr, buf = replyBuf.copy()] {
src->SendTo(to, from, OwnedBuffer::copy_from(buf));
});
}
else
log::error(logcat, "no source or parent");
}
} // namespace libunbound
Server::Server(EventLoop_ptr loop, llarp::DnsConfig conf, unsigned int netif)
: m_Loop{std::move(loop)}
, m_Config{std::move(conf)}
, m_Platform{CreatePlatform()}
, m_NetIfIndex{std::move(netif)}
{}
std::vector<std::weak_ptr<Resolver_Base>>
Server::GetAllResolvers() const
{
return {m_Resolvers.begin(), m_Resolvers.end()};
}
void
PacketHandler::Restart()
Server::Start()
{
if (m_UnboundResolver)
// set up udp sockets
for (const auto& addr : m_Config.m_bind)
{
LogInfo("reset libunbound's internal stuff");
m_UnboundResolver->Init();
if (auto ptr = MakePacketSourceOn(addr, m_Config))
AddPacketSource(std::move(ptr));
}
// add default resolver as needed
if (auto ptr = MakeDefaultResolver())
AddResolver(ptr);
}
bool
PacketHandler::Start(SockAddr, std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles)
std::shared_ptr<I_Platform>
Server::CreatePlatform() const
{
return SetupUnboundResolver(std::move(resolvers), std::move(hostfiles));
auto plat = std::make_shared<Multi_Platform>();
if constexpr (llarp::platform::has_systemd)
{
plat->add_impl(std::make_unique<SD_Platform_t>());
plat->add_impl(std::make_unique<NM_Platform_t>());
}
if constexpr (llarp::platform::is_windows)
{
plat->add_impl(std::make_unique<Win32_Platform_t>());
}
return plat;
}
bool
PacketHandler::SetupUnboundResolver(
std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles)
std::shared_ptr<PacketSource_Base>
Server::MakePacketSourceOn(const llarp::SockAddr& addr, const llarp::DnsConfig&)
{
// if we have no resolvers don't set up unbound
if (resolvers.empty())
return true;
auto failFunc = [self = weak_from_this()](
const SockAddr& to, const SockAddr& from, Message msg) {
if (auto this_ptr = self.lock())
this_ptr->SendServerMessageBufferTo(to, from, msg.ToBuffer());
};
auto replyFunc = [self = weak_from_this()](auto&&... args) {
if (auto this_ptr = self.lock())
this_ptr->SendServerMessageBufferTo(std::forward<decltype(args)>(args)...);
};
return std::make_shared<UDPReader>(*this, m_Loop, addr);
}
m_UnboundResolver =
std::make_shared<UnboundResolver>(m_Loop, std::move(replyFunc), std::move(failFunc));
m_Resolvers.clear();
if (not m_UnboundResolver->Init())
std::shared_ptr<Resolver_Base>
Server::MakeDefaultResolver()
{
if (m_Config.m_upstreamDNS.empty())
{
llarp::LogError("Failed to initialize upstream DNS resolver.");
m_UnboundResolver = nullptr;
return false;
log::info(
logcat,
"explicitly no upstream dns providers specified, we will not resolve anything but .loki "
"and .snode");
return nullptr;
}
for (const auto& resolver : resolvers)
return std::make_shared<libunbound::Resolver>(m_Loop, m_Config);
}
std::vector<SockAddr>
Server::BoundPacketSourceAddrs() const
{
std::vector<SockAddr> addrs;
for (const auto& src : m_PacketSources)
{
if (not m_UnboundResolver->AddUpstreamResolver(resolver))
{
llarp::LogError("Failed to add upstream DNS server: ", resolver);
m_UnboundResolver = nullptr;
return false;
}
m_Resolvers.emplace(resolver);
if (auto ptr = src.lock())
if (auto maybe_addr = ptr->BoundOn())
addrs.emplace_back(*maybe_addr);
}
for (const auto& path : hostfiles)
return addrs;
}
std::optional<SockAddr>
Server::FirstBoundPacketSourceAddr() const
{
for (const auto& src : m_PacketSources)
{
m_UnboundResolver->AddHostsFile(path);
if (auto ptr = src.lock())
if (auto bound = ptr->BoundOn())
return bound;
}
return std::nullopt;
}
return true;
void
Server::AddResolver(std::weak_ptr<Resolver_Base> resolver)
{
m_Resolvers.insert(resolver);
}
void
Proxy::SendServerMessageBufferTo(
const SockAddr& to, [[maybe_unused]] const SockAddr& from, llarp_buffer_t buf)
Server::AddResolver(std::shared_ptr<Resolver_Base> resolver)
{
if (!m_Server->send(to, buf))
llarp::LogError("dns reply failed");
m_OwnedResolvers.insert(resolver);
AddResolver(std::weak_ptr<Resolver_Base>{resolver});
}
bool
PacketHandler::IsUpstreamResolver(const SockAddr& to, [[maybe_unused]] const SockAddr& from) const
void
Server::AddPacketSource(std::weak_ptr<PacketSource_Base> pkt)
{
return m_Resolvers.count(to);
m_PacketSources.push_back(pkt);
}
bool
PacketHandler::ShouldHandlePacket(
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) const
void
Server::AddPacketSource(std::shared_ptr<PacketSource_Base> pkt)
{
MessageHeader hdr;
if (not hdr.Decode(&buf))
AddPacketSource(std::weak_ptr<PacketSource_Base>{pkt});
m_OwnedPacketSources.push_back(std::move(pkt));
}
void
Server::Stop()
{
for (const auto& resolver : m_Resolvers)
{
return false;
if (auto ptr = resolver.lock())
ptr->Down();
}
}
Message msg{hdr};
if (not msg.Decode(&buf))
void
Server::Reset()
{
for (const auto& resolver : m_Resolvers)
{
return false;
if (auto ptr = resolver.lock())
ptr->ResetResolver();
}
if (m_QueryHandler and m_QueryHandler->ShouldHookDNSMessage(msg))
return true;
// If this request is going to an upstream resolver then we want to let it through (i.e. don't
// handle it), and so want to return false. If we have something else then we want to
// intercept it to route it through our caching libunbound server (which then redirects the
// request to the lokinet-configured upstream, if not cached).
return !IsUpstreamResolver(to, from);
}
void
PacketHandler::HandlePacket(const SockAddr& resolver, const SockAddr& from, llarp_buffer_t buf)
Server::SetDNSMode(bool all_queries)
{
MessageHeader hdr;
if (not hdr.Decode(&buf))
if (auto maybe_addr = FirstBoundPacketSourceAddr())
m_Platform->set_resolver(m_NetIfIndex, *maybe_addr, all_queries);
}
bool
Server::MaybeHandlePacket(
std::shared_ptr<PacketSource_Base> ptr,
const SockAddr& to,
const SockAddr& from,
llarp::OwnedBuffer buf)
{
// dont process to prevent feedback loop
if (ptr->WouldLoop(to, from))
{
llarp::LogWarn("failed to parse dns header from ", from);
return;
log::warning(logcat, "preventing dns packet replay to={} from={}", to, from);
return false;
}
Message msg(hdr);
if (not msg.Decode(&buf))
auto maybe = MaybeParseDNSMessage(buf);
if (not maybe)
{
llarp::LogWarn("failed to parse dns message from ", from);
return;
log::warning(logcat, "invalid dns message format from {} to dns listener on {}", from, to);
return false;
}
auto& msg = *maybe;
// we don't provide a DoH resolver because it requires verified TLS
// TLS needs X509/ASN.1-DER and opting into the Root CA Cabal
// thankfully mozilla added a backdoor that allows ISPs to turn it off
// so we disable DoH for firefox using mozilla's ISP backdoor
// see: https://github.com/loki-project/loki-network/issues/832
// see: https://github.com/oxen-io/lokinet/issues/832
for (const auto& q : msg.questions)
{
// is this firefox looking for their backdoor record?
@ -163,32 +692,23 @@ namespace llarp::dns
{
// yea it is, let's turn off DoH because god is dead.
msg.AddNXReply();
// press F to pay respects
SendServerMessageBufferTo(from, resolver, msg.ToBuffer());
return;
// press F to pay respects and send it back where it came from
ptr->SendTo(from, to, msg.ToBuffer());
return true;
}
}
if (m_QueryHandler && m_QueryHandler->ShouldHookDNSMessage(msg))
for (const auto& resolver : m_Resolvers)
{
auto reply = [self = shared_from_this(), to = from, resolver](dns::Message msg) {
self->SendServerMessageBufferTo(to, resolver, msg.ToBuffer());
};
if (!m_QueryHandler->HandleHookedDNSMessage(std::move(msg), reply))
if (auto res_ptr = resolver.lock())
{
llarp::LogWarn("failed to handle hooked dns");
log::debug(
logcat, "check resolver {} for dns from {} to {}", res_ptr->ResolverName(), from, to);
if (res_ptr->MaybeHookDNS(ptr, msg, to, from))
return true;
}
}
else if (not m_UnboundResolver)
{
// no upstream resolvers
// let's serv fail it
msg.AddServFail();
SendServerMessageBufferTo(from, resolver, msg.ToBuffer());
}
else
{
m_UnboundResolver->Lookup(resolver, from, std::move(msg));
}
return false;
}
} // namespace llarp::dns

@ -1,99 +1,297 @@
#pragma once
#include "message.hpp"
#include "platform.hpp"
#include <llarp/config/config.hpp>
#include <llarp/ev/ev.hpp>
#include <llarp/net/net.hpp>
#include "unbound_resolver.hpp"
#include <llarp/util/fs.hpp>
#include <set>
#include <unordered_map>
namespace llarp
namespace llarp::dns
{
namespace dns
/// a job handling 1 dns query
class QueryJob_Base
{
protected:
/// the original dns query
Message m_Query;
public:
explicit QueryJob_Base(Message query) : m_Query{std::move(query)}
{}
virtual ~QueryJob_Base() = default;
Message&
Underlying()
{
return m_Query;
}
const Message&
Underlying() const
{
return m_Query;
}
/// cancel this operation and inform anyone who cares
void
Cancel() const;
/// send a raw buffer back to the querier
virtual void
SendReply(llarp::OwnedBuffer replyBuf) const = 0;
};
class PacketSource_Base
{
public:
virtual ~PacketSource_Base() = default;
/// return true if traffic with source and dest addresses would cause a
/// loop in resolution and thus should not be sent to query handlers
virtual bool
WouldLoop(const SockAddr& to, const SockAddr& from) const = 0;
/// send packet with src and dst address containing buf on this packet source
virtual void
SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const = 0;
/// stop reading packets and end operation
virtual void
Stop() = 0;
/// returns the sockaddr we are bound on if applicable
virtual std::optional<SockAddr>
BoundOn() const = 0;
};
/// a packet source which will override the sendto function of an wrapped packet source to
/// construct a raw ip packet as a reply
class PacketSource_Wrapper : public PacketSource_Base
{
/// handler of dns query hooking
class IQueryHandler
std::weak_ptr<PacketSource_Base> m_Wrapped;
std::function<void(net::IPPacket)> m_WritePacket;
public:
explicit PacketSource_Wrapper(
std::weak_ptr<PacketSource_Base> wrapped, std::function<void(net::IPPacket)> write_packet)
: m_Wrapped{wrapped}, m_WritePacket{write_packet}
{}
bool
WouldLoop(const SockAddr& to, const SockAddr& from) const override
{
public:
virtual ~IQueryHandler() = default;
if (auto ptr = m_Wrapped.lock())
return ptr->WouldLoop(to, from);
return true;
}
/// return true if we should hook this message
virtual bool
ShouldHookDNSMessage(const Message& msg) const = 0;
void
SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const override
{
m_WritePacket(net::IPPacket::make_udp(from, to, std::move(buf)));
}
/// handle a hooked message
virtual bool
HandleHookedDNSMessage(Message query, std::function<void(Message)> sendReply) = 0;
};
/// stop reading packets and end operation
void
Stop() override
{
if (auto ptr = m_Wrapped.lock())
ptr->Stop();
}
// Base class for DNS lookups
class PacketHandler : public std::enable_shared_from_this<PacketHandler>
/// returns the sockaddr we are bound on if applicable
std::optional<SockAddr>
BoundOn() const override
{
public:
explicit PacketHandler(EventLoop_ptr loop, IQueryHandler* handler);
if (auto ptr = m_Wrapped.lock())
return ptr->BoundOn();
return std::nullopt;
}
};
virtual ~PacketHandler() = default;
/// non complex implementation of QueryJob_Base for use in things that
/// only ever called on the mainloop thread
class QueryJob : public QueryJob_Base, std::enable_shared_from_this<QueryJob>
{
std::shared_ptr<PacketSource_Base> src;
const SockAddr resolver;
const SockAddr asker;
public:
explicit QueryJob(
std::shared_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to_,
const SockAddr& from_)
: QueryJob_Base{query}, src{source}, resolver{to_}, asker{from_}
{}
virtual bool
Start(
SockAddr localaddr,
std::vector<SockAddr> upstreamResolvers,
std::vector<fs::path> hostfiles);
void
SendReply(llarp::OwnedBuffer replyBuf) const override
{
src->SendTo(asker, resolver, std::move(replyBuf));
}
};
void
Stop();
/// handler of dns query hooking
/// intercepts dns for internal processing
class Resolver_Base
{
protected:
/// return the sorting order for this resolver
/// lower means it will be tried first
virtual int
Rank() const = 0;
void
Restart();
public:
virtual ~Resolver_Base() = default;
void
HandlePacket(const SockAddr& resolver, const SockAddr& from, llarp_buffer_t buf);
/// less than via rank
bool
operator<(const Resolver_Base& other) const
{
return Rank() < other.Rank();
}
bool
ShouldHandlePacket(const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) const;
/// greater than via rank
bool
operator>(const Resolver_Base& other) const
{
return Rank() > other.Rank();
}
protected:
virtual void
SendServerMessageBufferTo(const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) = 0;
/// get local socket address that queries are sent from
virtual std::optional<SockAddr>
GetLocalAddr() const
{
return std::nullopt;
}
// Returns true if this packet is something that looks like it's going to an upstream
// resolver, i.e. matches a configured resolver.
virtual bool
IsUpstreamResolver(const SockAddr& to, const SockAddr& from) const;
/// get printable name
virtual std::string_view
ResolverName() const = 0;
private:
void
HandleUpstreamFailure(const SockAddr& from, const SockAddr& to, Message msg);
/// reset the resolver state, optionally replace upstream info with new info. The default base
/// implementation does nothing.
virtual void
ResetResolver(
[[maybe_unused]] std::optional<std::vector<SockAddr>> replace_upstream = std::nullopt)
{}
bool
SetupUnboundResolver(std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles);
/// cancel all pending requests and cease further operation. Default operation is a no-op.
virtual void
Down()
{}
IQueryHandler* const m_QueryHandler;
std::set<SockAddr> m_Resolvers;
std::shared_ptr<UnboundResolver> m_UnboundResolver;
EventLoop_ptr m_Loop;
};
/// attempt to handle a dns message
/// returns true if we consumed this query and it should not be processed again
virtual bool
MaybeHookDNS(
std::shared_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to,
const SockAddr& from) = 0;
// Proxying DNS handler that listens on a UDP port for proper DNS requests.
class Proxy : public PacketHandler
/// Returns true if a packet with to and from addresses is something that would cause a
/// resolution loop and thus should not be used on this resolver
virtual bool
WouldLoop(const SockAddr& to, const SockAddr& from) const
{
public:
explicit Proxy(EventLoop_ptr loop, IQueryHandler* handler);
bool
Start(
SockAddr localaddr,
std::vector<SockAddr> upstreamResolvers,
std::vector<fs::path> hostfiles) override;
protected:
void
SendServerMessageBufferTo(
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) override;
private:
std::shared_ptr<UDPHandle> m_Server;
EventLoop_ptr m_Loop;
};
} // namespace dns
} // namespace llarp
(void)to;
(void)from;
return false;
}
};
// Base class for DNS proxy
class Server : public std::enable_shared_from_this<Server>
{
protected:
/// add a packet source to this server, does share ownership
void
AddPacketSource(std::shared_ptr<PacketSource_Base> resolver);
/// add a resolver to this packet handler, does share ownership
void
AddResolver(std::shared_ptr<Resolver_Base> resolver);
/// create the platform dependant dns stuff
virtual std::shared_ptr<I_Platform>
CreatePlatform() const;
public:
virtual ~Server() = default;
explicit Server(EventLoop_ptr loop, llarp::DnsConfig conf, unsigned int netif_index);
/// returns all sockaddr we have from all of our PacketSources
std::vector<SockAddr>
BoundPacketSourceAddrs() const;
/// returns the first sockaddr we have on our packet sources if we have one
std::optional<SockAddr>
FirstBoundPacketSourceAddr() const;
/// add a resolver to this packet handler, does not share ownership
void
AddResolver(std::weak_ptr<Resolver_Base> resolver);
/// add a packet source to this server, does not share ownership
void
AddPacketSource(std::weak_ptr<PacketSource_Base> resolver);
/// create a packet source bound on bindaddr but does not add it
virtual std::shared_ptr<PacketSource_Base>
MakePacketSourceOn(const SockAddr& bindaddr, const llarp::DnsConfig& conf);
/// sets up all internal binds and such and begins operation
virtual void
Start();
/// stops all operation
virtual void
Stop();
/// reset the internal state
virtual void
Reset();
/// create the default resolver for out config
virtual std::shared_ptr<Resolver_Base>
MakeDefaultResolver();
std::vector<std::weak_ptr<Resolver_Base>>
GetAllResolvers() const;
/// feed a packet buffer from a packet source.
/// returns true if we decided to process the packet and consumed it
/// returns false if we dont want to process the packet
bool
MaybeHandlePacket(
std::shared_ptr<PacketSource_Base> pktsource,
const SockAddr& resolver,
const SockAddr& from,
llarp::OwnedBuffer buf);
/// set which dns mode we are in.
/// true for intercepting all queries. false for just .loki and .snode
void
SetDNSMode(bool all_queries);
protected:
EventLoop_ptr m_Loop;
llarp::DnsConfig m_Config;
std::shared_ptr<I_Platform> m_Platform;
private:
const unsigned int m_NetIfIndex;
std::set<std::shared_ptr<Resolver_Base>, ComparePtr<std::shared_ptr<Resolver_Base>>>
m_OwnedResolvers;
std::set<std::weak_ptr<Resolver_Base>, CompareWeakPtr<Resolver_Base>> m_Resolvers;
std::vector<std::weak_ptr<PacketSource_Base>> m_PacketSources;
std::vector<std::shared_ptr<PacketSource_Base>> m_OwnedPacketSources;
};
} // namespace llarp::dns

@ -10,6 +10,8 @@
namespace llarp::dns
{
static auto logcat = log::Cat("dns");
bool
SRVData::IsValid() const
{
@ -22,7 +24,7 @@ namespace llarp::dns
// check target size is not absurd
if (target.size() > TARGET_MAX_SIZE)
{
LogWarn("SRVData target larger than max size (", TARGET_MAX_SIZE, ")");
log::warning(logcat, "SRVData target larger than max size ({})", TARGET_MAX_SIZE);
return false;
}
@ -41,7 +43,7 @@ namespace llarp::dns
}
// if we're here, target is invalid
LogWarn("SRVData invalid");
log::warning(logcat, "SRVData invalid");
return false;
}
@ -64,14 +66,14 @@ namespace llarp::dns
bool
SRVData::fromString(std::string_view srvString)
{
LogDebug("SRVData::fromString(\"", srvString, "\")");
log::debug(logcat, "SRVData::fromString(\"{}\")", srvString);
// split on spaces, discard trailing empty strings
auto splits = split(srvString, " ", false);
if (splits.size() != 5 && splits.size() != 4)
{
LogWarn("SRV record should have either 4 or 5 space-separated parts");
log::warning(logcat, "SRV record should have either 4 or 5 space-separated parts");
return false;
}
@ -79,19 +81,19 @@ namespace llarp::dns
if (not parse_int(splits[1], priority))
{
LogWarn("SRV record failed to parse \"", splits[1], "\" as uint16_t (priority)");
log::warning(logcat, "SRV record failed to parse \"{}\" as uint16_t (priority)", splits[1]);
return false;
}
if (not parse_int(splits[2], weight))
{
LogWarn("SRV record failed to parse \"", splits[2], "\" as uint16_t (weight)");
log::warning(logcat, "SRV record failed to parse \"{}\" as uint16_t (weight)", splits[2]);
return false;
}
if (not parse_int(splits[3], port))
{
LogWarn("SRV record failed to parse \"", splits[3], "\" as uint16_t (port)");
log::warning(logcat, "SRV record failed to parse \"{}\" as uint16_t (port)", splits[3]);
return false;
}

@ -11,6 +11,8 @@
namespace llarp::dns
{
static auto logcat = log::Cat("dns");
struct PendingUnboundLookup
{
std::weak_ptr<UnboundResolver> resolver;
@ -156,7 +158,7 @@ namespace llarp::dns
upstream += std::to_string(port);
}
LogError("Adding upstream resolver ", upstream);
log::info("Adding upstream resolver ", upstream);
if (ub_ctx_set_fwd(unboundContext, upstream.c_str()) != 0)
{
Reset();
@ -198,17 +200,12 @@ namespace llarp::dns
void
UnboundResolver::AddHostsFile(const fs::path& file)
{
LogDebug("adding hosts file ", file);
log::debug(logcat, "adding hosts file {}", file);
const auto str = file.u8string();
if (auto ret = ub_ctx_hosts(unboundContext, str.c_str()))
{
throw std::runtime_error{
fmt::format("Failed to add host file {}: {}", file, ub_strerror(ret))};
}
else
{
LogInfo("added hosts file ", file);
}
log::info(logcat, "added hosts file {}", file);
}
void

@ -0,0 +1,51 @@
#include "win32_platform.hpp"
#include <llarp/net/net.hpp>
namespace llarp::dns::win32
{
void
Platform::set_resolver(unsigned int index, llarp::SockAddr dns, bool)
{
#ifdef _WIN32
// clear any previous dns settings
m_UndoDNS.clear();
auto interfaces = m_Loop->Net_ptr()->AllNetworkInterfaces();
// remove dns
{
std::vector<llarp::win32::OneShotExec> jobs;
for (const auto& ent : interfaces)
{
if (ent.index == index)
continue;
jobs.emplace_back(
"netsh.exe", fmt::format("interface ipv4 delete dns \"{}\" all", ent.name));
jobs.emplace_back(
"netsh.exe", fmt::format("interface ipv6 delete dns \"{}\" all", ent.name));
}
}
// add new dns
{
std::vector<llarp::win32::OneShotExec> jobs;
for (const auto& ent : interfaces)
{
if (ent.index == index)
continue;
jobs.emplace_back(
"netsh.exe",
fmt::format("interface ipv4 add dns \"{}\" {} validate=no", ent.name, dns.asIPv4()));
jobs.emplace_back(
"netsh.exe",
fmt::format("interface ipv6 add dns \"{}\" {} validate=no", ent.name, dns.asIPv6()));
m_UndoDNS.emplace_back("netsh.exe", fmt::format("", index));
}
m_UndoDNS.emplace_back("netsh.exe", "winsock reset");
}
// flush dns
llarp::win32::Exec("ipconfig.exe", "/flushdns");
#endif
}
} // namespace llarp::dns::win32

@ -0,0 +1,8 @@
#pragma once
#include "platform.hpp"
namespace llarp::dns
{
// TODO: implement me
using Win32_Platform_t = Null_Platform;
} // namespace llarp::dns

@ -23,6 +23,11 @@ namespace llarp
class TunnelManager;
}
namespace dns
{
class Server;
}
class EndpointBase
{
std::unordered_set<dns::SRVData> m_SRVRecords;
@ -72,6 +77,13 @@ namespace llarp
void
PutSRVRecord(dns::SRVData srv);
/// get dns serverr if we have on on this endpoint
virtual std::shared_ptr<dns::Server>
DNS() const
{
return nullptr;
};
/// called when srv data changes in some way
virtual void
SRVRecordsChanged() = 0;

@ -6,8 +6,8 @@
#include <cstring>
#include <string_view>
// We libuv now
#include "ev_libuv.hpp"
#include "libuv.hpp"
#include <llarp/net/net.hpp>
namespace llarp
{
@ -16,4 +16,11 @@ namespace llarp
{
return std::make_shared<llarp::uv::Loop>(queueLength);
}
const net::Platform*
EventLoop::Net_ptr() const
{
return net::Platform::Default_ptr();
}
} // namespace llarp

@ -4,7 +4,7 @@
#include <llarp/util/time.hpp>
#include <llarp/util/thread/threading.hpp>
#include <llarp/constants/evloop.hpp>
#include <llarp/net/interface_info.hpp>
#include <algorithm>
#include <deque>
#include <list>
@ -28,8 +28,10 @@ namespace llarp
namespace net
{
class Platform;
struct IPPacket;
}
} // namespace net
/// distinct event loop waker upper; used to idempotently schedule a task on the next event loop
///
@ -184,6 +186,9 @@ namespace llarp
virtual ~EventLoop() = default;
virtual const net::Platform*
Net_ptr() const;
using UDPReceiveFunc = std::function<void(UDPHandle&, SockAddr src, llarp::OwnedBuffer buf)>;
// Constructs a UDP socket that can be used for sending and/or receiving
@ -218,7 +223,6 @@ namespace llarp
return nullptr;
}
protected:
// Triggers an event loop wakeup; use when something has been done that requires the event loop
// to wake up (e.g. adding to queues). This is called implicitly by call() and call_soon().
// Idempotent and thread-safe.

@ -1,12 +1,12 @@
#include "ev_libuv.hpp"
#include "vpn.hpp"
#include "libuv.hpp"
#include <memory>
#include <thread>
#include <type_traits>
#include <cstring>
#include <llarp/util/exceptions.hpp>
#include <llarp/util/thread/queue.hpp>
#include <cstring>
#include "ev.hpp"
#include <llarp/vpn/platform.hpp>
#include <uvw.hpp>
@ -72,6 +72,13 @@ namespace llarp::uv
bool
send(const SockAddr& dest, const llarp_buffer_t& buf) override;
std::optional<SockAddr>
LocalAddr() const override
{
auto addr = handle->sock<uvw::IPv4>();
return SockAddr{addr.ip, huint16_t{static_cast<uint16_t>(addr.port)}};
}
std::optional<int>
file_descriptor() override
{
@ -244,19 +251,21 @@ namespace llarp::uv
using event_t = uvw::PrepareEvent;
auto handle = m_Impl->resource<uvw::PrepareHandle>();
#endif
if (!handle)
return false;
handle->on<event_t>([netif = std::move(netif), handler = std::move(handler)](
const event_t&, [[maybe_unused]] auto& handle) {
for (auto pkt = netif->ReadNextPacket(); pkt.sz > 0; pkt = netif->ReadNextPacket())
for (auto pkt = netif->ReadNextPacket(); true; pkt = netif->ReadNextPacket())
{
LogDebug("got packet ", pkt.sz);
if (pkt.empty())
return;
if (handler)
handler(std::move(pkt));
// on windows/apple, vpn packet io does not happen as an io action that wakes up the event
// loop thus, we must manually wake up the event loop when we get a packet on our interface.
// on linux this is a nop
// on linux/android this is a nop
netif->MaybeWakeUpperLayers();
}
});

@ -33,6 +33,10 @@ namespace llarp
return std::nullopt;
}
/// returns the local address we are bound on
virtual std::optional<SockAddr>
LocalAddr() const = 0;
// Base class destructor
virtual ~UDPHandle() = default;

@ -110,7 +110,7 @@ namespace llarp
bool
Endpoint::QueueOutboundTraffic(
PathID_t path, ManagedBuffer buf, uint64_t counter, service::ProtocolType t)
PathID_t path, std::vector<byte_t> buf, uint64_t counter, service::ProtocolType t)
{
const service::ConvoTag tag{path.as_array()};
if (t == service::ProtocolType::QUIC)
@ -118,18 +118,19 @@ namespace llarp
auto quic = m_Parent->GetQUICTunnel();
if (not quic)
return false;
quic->receive_packet(tag, buf.underlying);
m_TxRate += buf.size();
quic->receive_packet(tag, std::move(buf));
m_LastActive = m_Parent->Now();
m_TxRate += buf.underlying.sz;
return true;
}
// queue overflow
if (m_UpstreamQueue.size() > MaxUpstreamQueueSize)
return false;
llarp::net::IPPacket pkt;
if (!pkt.Load(buf.underlying))
llarp::net::IPPacket pkt{std::move(buf)};
if (pkt.empty())
return false;
if (pkt.IsV6() && m_Parent->SupportsV6())
{
huint128_t dst;
@ -152,24 +153,19 @@ namespace llarp
{
return false;
}
m_UpstreamQueue.emplace(pkt, counter);
m_TxRate += buf.underlying.sz;
m_TxRate += pkt.size();
m_UpstreamQueue.emplace(std::move(pkt), counter);
m_LastActive = m_Parent->Now();
return true;
}
bool
Endpoint::QueueInboundTraffic(ManagedBuffer buf, service::ProtocolType type)
Endpoint::QueueInboundTraffic(std::vector<byte_t> buf, service::ProtocolType type)
{
llarp::net::IPPacket pkt{};
if (type == service::ProtocolType::QUIC)
if (type != service::ProtocolType::QUIC)
{
pkt.sz = std::min(buf.underlying.sz, sizeof(pkt.buf));
std::copy_n(buf.underlying.base, pkt.sz, pkt.buf);
}
else
{
if (!pkt.Load(buf.underlying))
llarp::net::IPPacket pkt{std::move(buf)};
if (pkt.empty())
return false;
huint128_t src;
@ -181,11 +177,11 @@ namespace llarp
pkt.UpdateIPv6Address(src, m_IP);
else
pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(src)), xhtonl(net::TruncateV6(m_IP)));
buf = pkt.steal();
}
const auto _pktbuf = pkt.ConstBuffer();
auto& pktbuf = _pktbuf.underlying;
const uint8_t queue_idx = pktbuf.sz / llarp::routing::ExitPadSize;
const uint8_t queue_idx = buf.size() / llarp::routing::ExitPadSize;
if (m_DownstreamQueues.find(queue_idx) == m_DownstreamQueues.end())
m_DownstreamQueues.emplace(queue_idx, InboundTrafficQueue_t{});
auto& queue = m_DownstreamQueues.at(queue_idx);
@ -193,17 +189,17 @@ namespace llarp
{
queue.emplace_back();
queue.back().protocol = type;
return queue.back().PutBuffer(pktbuf, m_Counter++);
return queue.back().PutBuffer(std::move(buf), m_Counter++);
}
auto& msg = queue.back();
if (msg.Size() + pktbuf.sz > llarp::routing::ExitPadSize)
if (msg.Size() + buf.size() > llarp::routing::ExitPadSize)
{
queue.emplace_back();
queue.back().protocol = type;
return queue.back().PutBuffer(pktbuf, m_Counter++);
return queue.back().PutBuffer(std::move(buf), m_Counter++);
}
msg.protocol = type;
return msg.PutBuffer(pktbuf, m_Counter++);
return msg.PutBuffer(std::move(buf), m_Counter++);
}
bool
@ -212,7 +208,8 @@ namespace llarp
// flush upstream queue
while (m_UpstreamQueue.size())
{
m_Parent->QueueOutboundTraffic(m_UpstreamQueue.top().pkt);
m_Parent->QueueOutboundTraffic(
const_cast<net::IPPacket&>(m_UpstreamQueue.top().pkt).steal());
m_UpstreamQueue.pop();
}
// flush downstream queue

@ -58,7 +58,7 @@ namespace llarp
/// queue traffic from service node / internet to be transmitted
bool
QueueInboundTraffic(ManagedBuffer buff, service::ProtocolType t);
QueueInboundTraffic(std::vector<byte_t> data, service::ProtocolType t);
/// flush inbound and outbound traffic queues
bool
@ -68,7 +68,7 @@ namespace llarp
/// does ip rewrite here
bool
QueueOutboundTraffic(
PathID_t txid, ManagedBuffer pkt, uint64_t counter, service::ProtocolType t);
PathID_t txid, std::vector<byte_t> data, uint64_t counter, service::ProtocolType t);
/// update local path id and cascade information to parent
/// return true if success
@ -122,7 +122,7 @@ namespace llarp
struct UpstreamBuffer
{
UpstreamBuffer(const llarp::net::IPPacket& p, uint64_t c) : pkt(p), counter(c)
UpstreamBuffer(llarp::net::IPPacket p, uint64_t c) : pkt{std::move(p)}, counter(c)
{}
llarp::net::IPPacket pkt;

@ -213,8 +213,8 @@ namespace llarp
if (m_WritePacket)
{
llarp::net::IPPacket pkt;
if (!pkt.Load(buf))
llarp::net::IPPacket pkt{buf.view_all()};
if (pkt.empty())
return false;
m_LastUse = m_router->Now();
m_Downstream.emplace(counter, pkt);
@ -235,9 +235,7 @@ namespace llarp
BaseSession::QueueUpstreamTraffic(
llarp::net::IPPacket pkt, const size_t N, service::ProtocolType t)
{
const auto pktbuf = pkt.ConstBuffer();
const llarp_buffer_t& buf = pktbuf;
auto& queue = m_Upstream[buf.sz / N];
auto& queue = m_Upstream[pkt.size() / N];
// queue overflow
if (queue.size() >= MaxUpstreamQueueLength)
return false;
@ -245,18 +243,18 @@ namespace llarp
{
queue.emplace_back();
queue.back().protocol = t;
return queue.back().PutBuffer(buf, m_Counter++);
return queue.back().PutBuffer(llarp_buffer_t{pkt}, m_Counter++);
}
auto& back = queue.back();
// pack to nearest N
if (back.Size() + buf.sz > N)
if (back.Size() + pkt.size() > N)
{
queue.emplace_back();
queue.back().protocol = t;
return queue.back().PutBuffer(buf, m_Counter++);
return queue.back().PutBuffer(llarp_buffer_t{pkt}, m_Counter++);
}
back.protocol = t;
return back.PutBuffer(buf, m_Counter++);
return back.PutBuffer(llarp_buffer_t{pkt}, m_Counter++);
}
bool
@ -333,7 +331,7 @@ namespace llarp
while (m_Downstream.size())
{
if (m_WritePacket)
m_WritePacket(m_Downstream.top().second.ConstBuffer());
m_WritePacket(const_cast<net::IPPacket&>(m_Downstream.top().second).steal());
m_Downstream.pop();
}
}
@ -369,8 +367,8 @@ namespace llarp
void
SNodeSession::SendPacketToRemote(const llarp_buffer_t& buf, service::ProtocolType t)
{
net::IPPacket pkt;
if (not pkt.Load(buf))
net::IPPacket pkt{buf.view_all()};
if (pkt.empty())
return;
pkt.ZeroAddresses();
QueueUpstreamTraffic(std::move(pkt), llarp::routing::ExitPadSize, t);
@ -379,9 +377,10 @@ namespace llarp
void
ExitSession::SendPacketToRemote(const llarp_buffer_t& buf, service::ProtocolType t)
{
net::IPPacket pkt;
if (not pkt.Load(buf))
net::IPPacket pkt{buf.view_all()};
if (pkt.empty())
return;
pkt.ZeroSourceAddress();
QueueUpstreamTraffic(std::move(pkt), llarp::routing::ExitPadSize, t);
}

@ -18,11 +18,7 @@ namespace llarp
namespace handlers
{
ExitEndpoint::ExitEndpoint(std::string name, AbstractRouter* r)
: m_Router(r)
, m_Resolver(std::make_shared<dns::Proxy>(r->loop(), this))
, m_Name(std::move(name))
, m_LocalResolverAddr{"127.0.0.1:53"}
, m_QUIC{std::make_shared<quic::TunnelManager>(*this)}
: m_Router(r), m_Name(std::move(name)), m_QUIC{std::make_shared<quic::TunnelManager>(*this)}
{
m_ShouldInitTun = true;
m_QUIC = std::make_shared<quic::TunnelManager>(*this);
@ -113,7 +109,7 @@ namespace llarp
{
if (not itr->second->LooksDead(Now()))
{
if (itr->second->QueueInboundTraffic(ManagedBuffer{payload}, type))
if (itr->second->QueueInboundTraffic(payload.copy(), type))
return true;
}
}
@ -121,10 +117,10 @@ namespace llarp
if (not m_Router->PathToRouterAllowed(*rid))
return false;
ObtainSNodeSession(*rid, [data = payload.copy(), type](auto session) {
ObtainSNodeSession(*rid, [pkt = payload.copy(), type](auto session) mutable {
if (session and session->IsReady())
{
session->SendPacketToRemote(data, type);
session->SendPacketToRemote(std::move(pkt), type);
}
});
}
@ -211,6 +207,22 @@ namespace llarp
return false;
}
bool
ExitEndpoint::MaybeHookDNS(
std::shared_ptr<dns::PacketSource_Base> source,
const dns::Message& query,
const SockAddr& to,
const SockAddr& from)
{
if (not ShouldHookDNSMessage(query))
return false;
auto job = std::make_shared<dns::QueryJob>(source, query, to, from);
if (not HandleHookedDNSMessage(query, [job](auto msg) { job->SendReply(msg.ToBuffer()); }))
job->Cancel();
return true;
}
bool
ExitEndpoint::HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)> reply)
{
@ -362,20 +374,24 @@ namespace llarp
{
while (not m_InetToNetwork.empty())
{
net::IPPacket pkt{m_InetToNetwork.top()};
m_InetToNetwork.pop();
auto& top = m_InetToNetwork.top();
PubKey pk;
// get a session by public key
std::optional<PubKey> maybe_pk;
{
auto itr = m_IPToKey.find(pkt.dstv6());
if (itr == m_IPToKey.end())
{
// drop
LogWarn(Name(), " dropping packet, has no session at ", pkt.dstv6());
continue;
}
pk = itr->second;
auto itr = m_IPToKey.find(top.dstv6());
if (itr != m_IPToKey.end())
maybe_pk = itr->second;
}
auto buf = const_cast<net::IPPacket&>(top).steal();
m_InetToNetwork.pop();
// we have no session for public key so drop
if (not maybe_pk)
continue; // we are in a while loop
const auto& pk = *maybe_pk;
// check if this key is a service node
if (m_SNodeKeys.count(pk))
{
@ -385,13 +401,14 @@ namespace llarp
auto itr = m_SNodeSessions.find(pk);
if (itr != m_SNodeSessions.end())
{
itr->second->SendPacketToRemote(pkt.ConstBuffer(), service::ProtocolType::TrafficV4);
itr->second->SendPacketToRemote(std::move(buf), service::ProtocolType::TrafficV4);
// we are in a while loop
continue;
}
}
auto tryFlushingTraffic = [&](exit::Endpoint* const ep) -> bool {
if (!ep->QueueInboundTraffic(
ManagedBuffer{pkt.Buffer()}, service::ProtocolType::TrafficV4))
auto tryFlushingTraffic =
[this, buf = std::move(buf), pk](exit::Endpoint* const ep) -> bool {
if (!ep->QueueInboundTraffic(buf, service::ProtocolType::TrafficV4))
{
LogWarn(
Name(),
@ -439,13 +456,14 @@ namespace llarp
m_IPToKey[ip] = us;
m_IPActivity[ip] = std::numeric_limits<llarp_time_t>::max();
m_SNodeKeys.insert(us);
if (m_ShouldInitTun)
{
vpn::InterfaceInfo info;
info.ifname = m_ifname;
info.addrs.emplace(m_OurRange);
info.addrs.emplace_back(m_OurRange);
m_NetIf = GetRouter()->GetVPNPlatform()->ObtainInterface(std::move(info), m_Router);
m_NetIf = GetRouter()->GetVPNPlatform()->CreateInterface(std::move(info), m_Router);
if (not m_NetIf)
{
llarp::LogError("Could not create interface");
@ -459,9 +477,12 @@ namespace llarp
}
GetRouter()->loop()->add_ticker([this] { Flush(); });
#ifndef _WIN32
m_Resolver = std::make_shared<dns::Server>(
m_Router->loop(), m_DNSConf, if_nametoindex(m_ifname.c_str()));
m_Resolver->Start();
llarp::LogInfo("Trying to start resolver ", m_LocalResolverAddr);
return m_Resolver->Start(m_LocalResolverAddr, m_UpstreamResolvers, {});
#endif
}
return true;
}
@ -586,7 +607,7 @@ namespace llarp
rc.srvRecords.clear();
for (auto& record : srvRecords)
rc.srvRecords.emplace_back(record);
// set the version to 1 because we have srv records
// set the verssion to 1 because we have srv records
rc.version = 1;
return rc;
});
@ -641,8 +662,8 @@ namespace llarp
bool
ExitEndpoint::QueueSNodePacket(const llarp_buffer_t& buf, huint128_t from)
{
net::IPPacket pkt;
if (!pkt.Load(buf))
net::IPPacket pkt{buf.view_all()};
if (pkt.empty())
return false;
// rewrite ip
if (m_UseV6)
@ -698,14 +719,14 @@ namespace llarp
return true;
}
*/
m_DNSConf = dnsConfig;
if (networkConfig.m_endpointType == "null")
{
m_ShouldInitTun = false;
}
m_LocalResolverAddr = dnsConfig.m_bind;
m_UpstreamResolvers = dnsConfig.m_upstreamDNS;
m_OurRange = networkConfig.m_ifaddr;
if (!m_OurRange.addr.h)
{
@ -736,8 +757,6 @@ namespace llarp
return llarp::SockAddr{ifaddr, huint16_t{port}};
});
}
// TODO: "exit-whitelist" and "exit-blacklist"
// (which weren't originally implemented)
}
huint128_t

@ -10,8 +10,27 @@ namespace llarp
struct AbstractRouter;
namespace handlers
{
struct ExitEndpoint : public dns::IQueryHandler, public EndpointBase
struct ExitEndpoint : public dns::Resolver_Base, public EndpointBase
{
int
Rank() const override
{
return 0;
};
std::string_view
ResolverName() const override
{
return "snode";
}
bool
MaybeHookDNS(
std::shared_ptr<dns::PacketSource_Base> source,
const dns::Message& query,
const SockAddr& to,
const SockAddr& from) override;
ExitEndpoint(std::string name, AbstractRouter* r);
~ExitEndpoint() override;
@ -66,10 +85,10 @@ namespace llarp
SupportsV6() const;
bool
ShouldHookDNSMessage(const dns::Message& msg) const override;
ShouldHookDNSMessage(const dns::Message& msg) const;
bool
HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)>) override;
HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)>);
void
LookupServiceAsync(
@ -174,7 +193,7 @@ namespace llarp
KickIdentOffExit(const PubKey& pk);
AbstractRouter* m_Router;
std::shared_ptr<dns::Proxy> m_Resolver;
std::shared_ptr<dns::Server> m_Resolver;
bool m_ShouldInitTun;
std::string m_Name;
bool m_PermitExit;
@ -220,6 +239,7 @@ namespace llarp
/// internet to llarp packet queue
PacketQueue_t m_InetToNetwork;
bool m_UseV6;
DnsConfig m_DNSConf;
};
} // namespace handlers
} // namespace llarp

@ -17,7 +17,7 @@ namespace llarp::handlers
, m_PacketRouter{new vpn::EgresPacketRouter{[](auto from, auto pkt) {
var::visit(
[&pkt](auto&& from) {
LogError("unhandled traffic from: ", from, " of ", pkt.sz, " bytes");
LogError("unhandled traffic from: ", from, " of ", pkt.size(), " bytes");
},
from);
}}}

@ -12,7 +12,7 @@
#include <llarp/ev/ev.hpp>
#include <llarp/net/net.hpp>
#include <llarp/router/abstractrouter.hpp>
#include <llarp/router/systemd_resolved.hpp>
#include <llarp/router/route_poker.hpp>
#include <llarp/service/context.hpp>
#include <llarp/service/outbound_context.hpp>
#include <llarp/service/endpoint_state.hpp>
@ -24,7 +24,9 @@
#include <llarp/quic/tunnel.hpp>
#include <llarp/rpc/endpoint_rpc.hpp>
#include <llarp/util/str.hpp>
#include <llarp/util/logging/buffer.hpp>
#include <llarp/dns/srv_data.hpp>
#include <llarp/constants/net.hpp>
#include <llarp/constants/platform.hpp>
#include <oxenc/bt.h>
@ -33,83 +35,180 @@ namespace llarp
{
namespace handlers
{
// Intercepts DNS IP packets going to an IP on the tun interface; this is currently used on
// Android and macOS where binding to a DNS port (i.e. via llarp::dns::Proxy) isn't possible
// because of OS restrictions, but a tun interface *is* available.
class DnsInterceptor : public dns::PacketHandler
bool
TunEndpoint::MaybeHookDNS(
std::shared_ptr<dns::PacketSource_Base> source,
const dns::Message& query,
const SockAddr& to,
const SockAddr& from)
{
if (not ShouldHookDNSMessage(query))
return false;
auto job = std::make_shared<dns::QueryJob>(source, query, to, from);
if (HandleHookedDNSMessage(query, [job](auto msg) { job->SendReply(msg.ToBuffer()); }))
Router()->TriggerPump();
else
job->Cancel();
return true;
}
/// Intercepts DNS IP packets on platforms where binding to a low port isn't viable.
/// (windows/macos/ios/android ... aka everything that is not linux... funny that)
class DnsInterceptor : public dns::PacketSource_Base
{
std::function<void(net::IPPacket)> m_Reply;
net::ipaddr_t m_OurIP;
llarp::DnsConfig m_Config;
public:
TunEndpoint* const m_Endpoint;
explicit DnsInterceptor(
std::function<void(net::IPPacket)> reply, net::ipaddr_t our_ip, llarp::DnsConfig conf)
: m_Reply{std::move(reply)}, m_OurIP{std::move(our_ip)}, m_Config{std::move(conf)}
{}
explicit DnsInterceptor(AbstractRouter* router, TunEndpoint* ep)
: dns::PacketHandler{router->loop(), ep}, m_Endpoint{ep} {};
~DnsInterceptor() override = default;
void
SendServerMessageBufferTo(
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) override
SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const override
{
const auto pkt = net::IPPacket::UDP(
from.getIPv4(),
ToNet(huint16_t{from.getPort()}),
to.getIPv4(),
ToNet(huint16_t{to.getPort()}),
buf);
if (pkt.sz == 0)
auto pkt = net::IPPacket::make_udp(from, to, std::move(buf));
if (pkt.empty())
return;
m_Endpoint->HandleWriteIPPacket(
pkt.ConstBuffer(), net::ExpandV4(from.asIPv4()), net::ExpandV4(to.asIPv4()), 0);
m_Reply(std::move(pkt));
}
#ifdef ANDROID
bool
IsUpstreamResolver(const SockAddr&, const SockAddr&) const override
void
Stop() override{};
std::optional<SockAddr>
BoundOn() const override
{
return true;
return std::nullopt;
}
#endif
#ifdef __APPLE__
// DNS on Apple is a bit weird because in order for the NetworkExtension itself to send data
// through the tunnel we have to proxy DNS requests through Apple APIs (and so our actual
// upstream DNS won't be set in our resolvers, which is why the vanilla IsUpstreamResolver,
// above, won't work for us). However when active the mac also only queries the main tunnel
// IP for DNS, so we consider anything else to be upstream-bound DNS to let it through the
// tunnel.
bool
IsUpstreamResolver(const SockAddr& to, const SockAddr& from) const override
WouldLoop(const SockAddr& to, const SockAddr& from) const override
{
return to.asIPv6() != m_Endpoint->GetIfAddr();
if constexpr (platform::is_apple)
{
// DNS on Apple is a bit weird because in order for the NetworkExtension itself to send
// data through the tunnel we have to proxy DNS requests through Apple APIs (and so our
// actual upstream DNS won't be set in our resolvers, which is why the vanilla WouldLoop
// won't work for us). However when active the mac also only queries the main tunnel IP
// for DNS, so we consider anything else to be upstream-bound DNS to let it through the
// tunnel.
return to.getIP() != m_OurIP;
}
else if (auto maybe_addr = m_Config.m_QueryBind)
{
const auto& addr = *maybe_addr;
// omit traffic to and from our dns socket
return addr == to or addr == from;
}
return false;
}
};
class TunDNS : public dns::Server
{
std::optional<SockAddr> m_QueryBind;
net::ipaddr_t m_OurIP;
TunEndpoint* const m_Endpoint;
public:
std::shared_ptr<dns::PacketSource_Base> PacketSource;
virtual ~TunDNS() = default;
explicit TunDNS(TunEndpoint* ep, const llarp::DnsConfig& conf)
: dns::Server{ep->Router()->loop(), conf, 0}
, m_QueryBind{conf.m_QueryBind}
, m_OurIP{ToNet(ep->GetIfAddr())}
, m_Endpoint{ep}
{}
std::shared_ptr<dns::PacketSource_Base>
MakePacketSourceOn(const SockAddr&, const llarp::DnsConfig& conf) override
{
auto ptr = std::make_shared<DnsInterceptor>(
[ep = m_Endpoint](auto pkt) {
ep->HandleWriteIPPacket(pkt.ConstBuffer(), pkt.srcv6(), pkt.dstv6(), 0);
},
m_OurIP,
conf);
PacketSource = ptr;
return ptr;
}
#endif
};
TunEndpoint::TunEndpoint(AbstractRouter* r, service::Context* parent)
: service::Endpoint(r, parent)
: service::Endpoint{r, parent}
{
m_PacketRouter = std::make_unique<vpn::PacketRouter>(
m_PacketRouter = std::make_shared<vpn::PacketRouter>(
[this](net::IPPacket pkt) { HandleGotUserPacket(std::move(pkt)); });
#if defined(ANDROID) || (defined(__APPLE__) && !defined(MACOS_SYSTEM_EXTENSION))
m_Resolver = std::make_shared<DnsInterceptor>(r, this);
m_PacketRouter->AddUDPHandler(huint16_t{53}, [&](net::IPPacket pkt) {
const size_t ip_header_size = (pkt.Header()->ihl * 4);
const uint8_t* ptr = pkt.buf + ip_header_size;
const auto dst = ToNet(pkt.dstv4());
const auto src = ToNet(pkt.srcv4());
const SockAddr laddr{src, nuint16_t{*reinterpret_cast<const uint16_t*>(ptr)}};
const SockAddr raddr{dst, nuint16_t{*reinterpret_cast<const uint16_t*>(ptr + 2)}};
OwnedBuffer buf{pkt.sz - (8 + ip_header_size)};
std::copy_n(ptr + 8, buf.sz, buf.buf.get());
if (m_Resolver->ShouldHandlePacket(raddr, laddr, buf))
m_Resolver->HandlePacket(raddr, laddr, buf);
else
}
void
TunEndpoint::SetupDNS()
{
const auto& info = GetVPNInterface()->Info();
if (m_DnsConfig.m_raw_dns)
{
auto dns = std::make_shared<TunDNS>(this, m_DnsConfig);
m_DNS = dns;
m_PacketRouter->AddUDPHandler(huint16_t{53}, [this, dns](net::IPPacket pkt) {
auto dns_pkt_src = dns->PacketSource;
if (const auto& reply = pkt.reply)
dns_pkt_src = std::make_shared<dns::PacketSource_Wrapper>(dns_pkt_src, reply);
if (dns->MaybeHandlePacket(
std::move(dns_pkt_src), pkt.dst(), pkt.src(), *pkt.L4OwnedBuffer()))
return;
HandleGotUserPacket(std::move(pkt));
});
#else
m_Resolver = std::make_shared<dns::Proxy>(r->loop(), this);
#endif
});
}
else
m_DNS = std::make_shared<dns::Server>(Loop(), m_DnsConfig, info.index);
m_DNS->AddResolver(weak_from_this());
m_DNS->Start();
if (m_DnsConfig.m_raw_dns)
{
if (auto vpn = Router()->GetVPNPlatform())
{
// get the first local address we know of
std::optional<SockAddr> localaddr;
for (auto res : m_DNS->GetAllResolvers())
{
if (auto ptr = res.lock())
{
localaddr = ptr->GetLocalAddr();
if (localaddr)
break;
}
}
if (platform::is_windows)
{
auto dns_io = vpn->create_packet_io(0, localaddr);
Router()->loop()->add_ticker([r = Router(), dns_io, handler = m_PacketRouter]() {
net::IPPacket pkt = dns_io->ReadNextPacket();
while (not pkt.empty())
{
handler->HandleIPPacket(std::move(pkt));
pkt = dns_io->ReadNextPacket();
}
});
m_RawDNS = dns_io;
}
}
if (m_RawDNS)
m_RawDNS->Start();
}
}
util::StatusObject
@ -118,11 +217,21 @@ namespace llarp
auto obj = service::Endpoint::ExtractStatus();
obj["ifaddr"] = m_OurRange.ToString();
obj["ifname"] = m_IfName;
std::vector<std::string> resolvers;
for (const auto& addr : m_UpstreamResolvers)
resolvers.emplace_back(addr.ToString());
obj["ustreamResolvers"] = resolvers;
obj["localResolver"] = m_LocalResolverAddr.ToString();
std::vector<std::string> upstreamRes;
for (const auto& ent : m_DnsConfig.m_upstreamDNS)
upstreamRes.emplace_back(ent.ToString());
obj["ustreamResolvers"] = upstreamRes;
std::vector<std::string> localRes;
for (const auto& ent : m_DnsConfig.m_bind)
localRes.emplace_back(ent.ToString());
obj["localResolvers"] = localRes;
// for backwards compat
if (not m_DnsConfig.m_bind.empty())
obj["localResolver"] = localRes[0];
util::StatusObject ips{};
for (const auto& item : m_IPActivity)
{
@ -147,19 +256,21 @@ namespace llarp
void
TunEndpoint::Thaw()
{
if (m_Resolver)
m_Resolver->Restart();
if (m_DNS)
m_DNS->Reset();
}
std::vector<SockAddr>
void
TunEndpoint::ReconfigureDNS(std::vector<SockAddr> servers)
{
std::swap(m_UpstreamResolvers, servers);
m_Resolver->Stop();
if (!m_Resolver->Start(
m_LocalResolverAddr.createSockAddr(), m_UpstreamResolvers, m_hostfiles))
llarp::LogError(Name(), " failed to reconfigure DNS server");
return servers;
if (m_DNS)
{
for (auto weak : m_DNS->GetAllResolvers())
{
if (auto ptr = weak.lock())
ptr->ResetResolver(servers);
}
}
}
bool
@ -199,13 +310,10 @@ namespace llarp
m_AuthPolicy = std::move(auth);
}
m_DnsConfig = dnsConf;
m_TrafficPolicy = conf.m_TrafficPolicy;
m_OwnedRanges = conf.m_OwnedRanges;
m_LocalResolverAddr = dnsConf.m_bind;
m_UpstreamResolvers = dnsConf.m_upstreamDNS;
m_hostfiles = dnsConf.m_hostfiles;
m_BaseV6Address = conf.m_baseV6Address;
if (conf.m_PathAlignmentTimeout)
@ -354,7 +462,6 @@ namespace llarp
return llarp::SockAddr{net::TruncateV6(GetIfAddr()), huint16_t{port}};
});
}
return Endpoint::Configure(conf, dnsConf);
}
@ -763,20 +870,29 @@ namespace llarp
}
else if (msg.questions[0].qtype == dns::qTypeSRV)
{
llarp::service::Address addr;
auto srv_for = msg.questions[0].Subdomains();
auto name = msg.questions[0].qname;
if (is_localhost_loki(msg))
{
msg.AddSRVReply(introSet().GetMatchingSRVRecords(msg.questions[0].Subdomains()));
msg.AddSRVReply(introSet().GetMatchingSRVRecords(srv_for));
reply(msg);
return true;
}
else if (addr.FromString(qname, ".loki"))
{
llarp::LogDebug("SRV request for: ", qname);
return ReplyToLokiSRVWhenReady(addr, std::make_shared<dns::Message>(msg));
}
LookupServiceAsync(
name,
srv_for,
[reply, msg = std::make_shared<dns::Message>(std::move(msg))](auto records) {
if (records.empty())
{
msg->AddNXReply();
}
else
{
msg->AddSRVReply(records);
}
reply(*msg);
});
return true;
}
else
{
@ -862,11 +978,8 @@ namespace llarp
bool
TunEndpoint::Start()
{
if (!Endpoint::Start())
{
llarp::LogWarn("Couldn't start endpoint");
if (not Endpoint::Start())
return false;
}
return SetupNetworking();
}
@ -893,62 +1006,59 @@ namespace llarp
}
vpn::InterfaceInfo info;
info.addrs.emplace(m_OurRange);
info.addrs.emplace_back(m_OurRange);
if (m_BaseV6Address)
{
IPRange v6range = m_OurRange;
v6range.addr = (*m_BaseV6Address) | m_OurRange.addr;
LogInfo(Name(), " using v6 range: ", v6range);
info.addrs.emplace(v6range, AF_INET6);
info.addrs.emplace_back(v6range, AF_INET6);
}
info.ifname = m_IfName;
info.dnsaddr.FromString(m_LocalResolverAddr.toHost());
LogInfo(Name(), " setting up network...");
try
{
m_NetIf = Router()->GetVPNPlatform()->ObtainInterface(std::move(info), Router());
m_NetIf = Router()->GetVPNPlatform()->CreateInterface(std::move(info), Router());
}
catch (std::exception& ex)
{
LogError(Name(), " failed to set up network interface: ", ex.what());
}
if (not m_NetIf)
{
LogError(Name(), " failed to obtain network interface");
return false;
}
m_IfName = m_NetIf->IfName();
m_IfName = m_NetIf->Info().ifname;
LogInfo(Name(), " got network interface ", m_IfName);
if (not Router()->loop()->add_network_interface(m_NetIf, [this](net::IPPacket pkt) {
m_PacketRouter->HandleIPPacket(std::move(pkt));
}))
auto handle_packet = [netif = m_NetIf, pkt_router = m_PacketRouter](auto pkt) {
pkt.reply = [netif](auto pkt) { netif->WritePacket(std::move(pkt)); };
pkt_router->HandleIPPacket(std::move(pkt));
};
if (not Router()->loop()->add_network_interface(m_NetIf, std::move(handle_packet)))
{
LogError(Name(), " failed to add network interface");
return false;
}
#ifdef __APPLE__
m_OurIPv6 = llarp::huint128_t{
llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(m_OurRange.addr).h}};
#else
const auto maybe = m_router->Net().GetInterfaceIPv6Address(m_IfName);
if (maybe.has_value())
if constexpr (not llarp::platform::is_apple)
{
m_OurIPv6 = *maybe;
LogInfo(Name(), " has ipv6 address ", m_OurIPv6);
if (auto maybe = m_router->Net().GetInterfaceIPv6Address(m_IfName))
{
m_OurIPv6 = *maybe;
LogInfo(Name(), " has ipv6 address ", m_OurIPv6);
}
}
#endif
// Attempt to register DNS on the interface
systemd_resolved_set_dns(
m_IfName,
m_LocalResolverAddr.createSockAddr(),
false /* just .loki/.snode DNS initially */);
LogInfo(Name(), " setting up dns...");
SetupDNS();
Loop()->call_soon([this]() { m_router->routePoker()->SetDNSMode(false); });
return HasAddress(ourAddr);
}
@ -970,18 +1080,7 @@ namespace llarp
TunEndpoint::SetupNetworking()
{
llarp::LogInfo("Set Up networking for ", Name());
if (!SetupTun())
{
llarp::LogError(Name(), " failed to set up network interface");
return false;
}
if (!m_Resolver->Start(
m_LocalResolverAddr.createSockAddr(), m_UpstreamResolvers, m_hostfiles))
{
llarp::LogError(Name(), " failed to start DNS server");
return false;
}
return true;
return SetupTun();
}
void
@ -993,9 +1092,13 @@ namespace llarp
bool
TunEndpoint::Stop()
{
// stop vpn tunnel
if (m_NetIf)
m_NetIf->Stop();
if (m_RawDNS)
m_RawDNS->Stop();
// save address map if applicable
#ifndef ANDROID
if (m_PersistAddrMapFile)
if (m_PersistAddrMapFile and not platform::is_android)
{
const auto& file = *m_PersistAddrMapFile;
LogInfo(Name(), " saving address map to ", file);
@ -1015,9 +1118,8 @@ namespace llarp
maybe->write(data.data(), data.size());
}
}
#endif
if (m_Resolver)
m_Resolver->Stop();
if (m_DNS)
m_DNS->Stop();
return llarp::service::Endpoint::Stop();
}
@ -1029,12 +1131,18 @@ namespace llarp
// is it already mapped? return the mapping
if (auto itr = m_ExitIPToExitAddress.find(ip); itr != m_ExitIPToExitAddress.end())
return itr->second;
const auto& net = m_router->Net();
const bool is_bogon = net.IsBogonIP(ip);
// build up our candidates to choose
std::unordered_set<service::Address> candidates;
for (const auto& entry : m_ExitMap.FindAllEntries(ip))
{
// make sure it is allowed by the range if the ip is a bogon
if (not IsBogon(ip) or entry.first.BogonContains(ip))
// in the event the exit's range is a bogon range, make sure the ip is located in that range
// to allow it
if ((is_bogon and net.IsBogonRange(entry.first) and entry.first.Contains(ip))
or entry.first.Contains(ip))
candidates.emplace(entry.second);
}
// no candidates? bail.
@ -1096,13 +1204,20 @@ namespace llarp
return;
}
std::function<void(void)> extra_cb;
if (not HasFlowToService(addr))
{
extra_cb = [poker = Router()->routePoker()]() { poker->Up(); };
}
pkt.ZeroSourceAddress();
MarkAddressOutbound(addr);
EnsurePathToService(
addr,
[pkt, this](service::Address addr, service::OutboundContext* ctx) {
[pkt, extra_cb, this](service::Address addr, service::OutboundContext* ctx) {
if (ctx)
{
if (extra_cb)
extra_cb();
ctx->SendPacketToRemote(pkt.ConstBuffer(), service::ProtocolType::Exit);
Router()->TriggerPump();
return;

@ -2,33 +2,56 @@
#include <llarp/dns/server.hpp>
#include <llarp/ev/ev.hpp>
#include <llarp/ev/vpn.hpp>
#include <llarp/net/ip.hpp>
#include <llarp/net/ip_packet.hpp>
#include <llarp/net/net.hpp>
#include <llarp/service/endpoint.hpp>
#include <llarp/service/protocol_type.hpp>
#include <llarp/util/priority_queue.hpp>
#include <llarp/util/thread/threading.hpp>
#include <llarp/vpn/packet_router.hpp>
#include <llarp/vpn/platform.hpp>
#include <future>
#include <type_traits>
#include <variant>
#include <llarp/service/protocol_type.hpp>
#include <llarp/util/priority_queue.hpp>
namespace llarp
{
namespace handlers
{
struct TunEndpoint : public service::Endpoint,
public dns::IQueryHandler,
public dns::Resolver_Base,
public std::enable_shared_from_this<TunEndpoint>
{
TunEndpoint(AbstractRouter* r, llarp::service::Context* parent);
~TunEndpoint() override;
vpn::NetworkInterface*
GetVPNInterface() override
{
return m_NetIf.get();
}
int
Rank() const override
{
return 0;
}
std::string_view
ResolverName() const override
{
return "lokinet";
}
bool
MaybeHookDNS(
std::shared_ptr<dns::PacketSource_Base> source,
const dns::Message& query,
const SockAddr& to,
const SockAddr& from) override;
path::PathSet_ptr
GetSelf() override
{
@ -44,9 +67,8 @@ namespace llarp
void
Thaw() override;
// Reconfigures DNS servers and restarts libunbound with the new servers. Returns the old set
// of configured dns servers.
std::vector<SockAddr>
// Reconfigures DNS servers and restarts libunbound with the new servers.
void
ReconfigureDNS(std::vector<SockAddr> servers);
bool
@ -71,11 +93,10 @@ namespace llarp
SupportsV6() const override;
bool
ShouldHookDNSMessage(const dns::Message& msg) const override;
ShouldHookDNSMessage(const dns::Message& msg) const;
bool
HandleHookedDNSMessage(
dns::Message query, std::function<void(dns::Message)> sendreply) override;
HandleHookedDNSMessage(dns::Message query, std::function<void(dns::Message)> sendreply);
void
TickTun(llarp_time_t now);
@ -96,6 +117,16 @@ namespace llarp
bool
SetupTun();
void
SetupDNS();
/// overrides Endpoint
std::shared_ptr<dns::Server>
DNS() const override
{
return m_DNS;
};
/// overrides Endpoint
bool
SetupNetworking() override;
@ -249,8 +280,11 @@ namespace llarp
query->AddNXReply();
reply(*query);
}
/// our dns resolver
std::shared_ptr<dns::PacketHandler> m_Resolver;
/// dns subsystem for this endpoint
std::shared_ptr<dns::Server> m_DNS;
DnsConfig m_DnsConfig;
/// maps ip address to timestamp last active
std::unordered_map<huint128_t, llarp_time_t> m_IPActivity;
@ -265,12 +299,6 @@ namespace llarp
huint128_t m_MaxIP;
/// our ip range we are using
llarp::IPRange m_OurRange;
/// upstream dns resolver list
std::vector<SockAddr> m_UpstreamResolvers;
/// dns host files list
std::vector<fs::path> m_hostfiles;
/// local dns
IpAddress m_LocalResolverAddr;
/// list of strict connect addresses for hooks
std::vector<IpAddress> m_StrictConnectAddrs;
/// use v6?
@ -281,7 +309,7 @@ namespace llarp
std::shared_ptr<vpn::NetworkInterface> m_NetIf;
std::unique_ptr<vpn::PacketRouter> m_PacketRouter;
std::shared_ptr<vpn::PacketRouter> m_PacketRouter;
std::optional<net::TrafficPolicy> m_TrafficPolicy;
/// ranges we advetise as reachable
@ -291,6 +319,9 @@ namespace llarp
/// a file to load / store the ephemeral address map to
std::optional<fs::path> m_PersistAddrMapFile;
/// for raw packet dns
std::shared_ptr<vpn::I_Packet_IO> m_RawDNS;
};
} // namespace handlers

@ -0,0 +1,26 @@
#ifdef WITH_SYSTEMD
#include "dbus.hpp"
namespace llarp::linux
{
system_bus_exception::system_bus_exception(int err)
: std::runtime_error{"cannot connect to system bus: " + std::string{strerror(-err)}}
{}
dbus_call_exception::dbus_call_exception(std::string meth, int err)
: std::runtime_error{
"failed to call dbus function '" + meth + "': " + std::string{strerror(-err)}}
{}
DBUS::DBUS(std::string _interface, std::string _instance, std::string _call)
: m_interface{std::move(_interface)}
, m_instance{std::move(_instance)}
, m_call{std::move(_call)}
{
sd_bus* bus{nullptr};
if (auto err = sd_bus_open_system(&bus); err < 0)
throw system_bus_exception{err};
m_ptr.reset(bus);
}
} // namespace llarp::linux
#endif

@ -0,0 +1,69 @@
#pragma once
extern "C"
{
#include <systemd/sd-bus.h>
}
#include <memory>
#include <stdexcept>
#include <string>
namespace llarp::linux
{
/// exception for connecting to system bus
class system_bus_exception : public std::runtime_error
{
public:
explicit system_bus_exception(int err);
};
/// exception for a failed calling of a dbus method
class dbus_call_exception : public std::runtime_error
{
public:
explicit dbus_call_exception(std::string meth, int err);
};
class DBUS
{
struct sd_bus_deleter
{
void
operator()(sd_bus* ptr) const
{
sd_bus_unref(ptr);
}
};
std::unique_ptr<sd_bus, sd_bus_deleter> m_ptr;
const std::string m_interface;
const std::string m_instance;
const std::string m_call;
public:
DBUS(std::string _interface, std::string _instance, std::string _call);
template <typename... T>
void
operator()(std::string method, const char* arg_format, T... args)
{
sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus_message* msg = nullptr;
auto r = sd_bus_call_method(
m_ptr.get(),
m_interface.c_str(),
m_instance.c_str(),
m_call.c_str(),
method.c_str(),
&error,
&msg,
arg_format,
args...);
if (r < 0)
throw dbus_call_exception{std::move(method), r};
sd_bus_message_unref(msg);
sd_bus_error_free(&error);
}
};
} // namespace llarp::linux

@ -228,7 +228,7 @@ struct lokinet_context
[[nodiscard]] std::optional<int>
make_udp_handler(
const std::shared_ptr<llarp::service::Endpoint>& ep,
llarp::huint16_t exposePort,
llarp::net::port_t exposePort,
lokinet_udp_flow_filter filter,
lokinet_udp_flow_recv_func recv,
lokinet_udp_flow_timeout_func timeout,
@ -245,16 +245,16 @@ struct lokinet_context
}
});
}
std::weak_ptr<llarp::service::Endpoint> weak{ep};
auto udp = std::make_shared<UDPHandler>(
next_socket_id(), llarp::ToNet(exposePort), filter, recv, timeout, user, std::weak_ptr{ep});
next_socket_id(), exposePort, filter, recv, timeout, user, weak);
auto id = udp->m_SocketID;
std::promise<bool> result;
impl->router->loop()->call([ep, &result, udp, exposePort]() {
if (auto pkt = ep->EgresPacketRouter())
{
pkt->AddUDPHandler(exposePort, [udp](auto from, auto pkt) {
pkt->AddUDPHandler(llarp::net::ToHost(exposePort), [udp](auto from, auto pkt) {
udp->HandlePacketFrom(std::move(from), std::move(pkt));
});
result.set_value(true);
@ -903,8 +903,8 @@ extern "C"
auto lock = ctx->acquire();
if (auto ep = ctx->endpoint())
{
if (auto maybe =
ctx->make_udp_handler(ep, llarp::huint16_t{exposedPort}, filter, recv, timeout, user))
if (auto maybe = ctx->make_udp_handler(
ep, llarp::net::port_t::from_host(exposedPort), filter, recv, timeout, user))
{
result->socket_id = *maybe;
return 0;
@ -934,7 +934,7 @@ extern "C"
return EINVAL;
std::shared_ptr<llarp::EndpointBase> ep;
llarp::nuint16_t srcport{0};
llarp::nuint16_t dstport{llarp::ToNet(llarp::huint16_t{remote->remote_port})};
auto dstport = llarp::net::port_t::from_host(remote->remote_port);
{
auto lock = ctx->acquire();
if (auto itr = ctx->udp_sockets.find(remote->socket_id); itr != ctx->udp_sockets.end())
@ -954,7 +954,7 @@ extern "C"
dstport,
llarp_buffer_t{reinterpret_cast<const uint8_t*>(ptr), len});
if (pkt.sz == 0)
if (pkt.empty())
return EINVAL;
std::promise<int> ret;
ctx->impl->router->loop()->call([addr = *maybe, pkt = std::move(pkt), ep, &ret]() {

@ -14,9 +14,9 @@ namespace llarp
bool
DHTImmediateMessage::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* buf)
{
if (key == "m")
if (key.startswith("m"))
return llarp::dht::DecodeMesssageList(dht::Key_t(session->GetPubKey()), buf, msgs);
if (key == "v")
if (key.startswith("v"))
{
if (!bencode_read_integer(buf, &version))
return false;

@ -39,7 +39,7 @@ namespace llarp
bool
DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* buf) override
{
if (key == "a")
if (key.startswith("a"))
{
llarp_buffer_t strbuf;
if (!bencode_read_string(buf, &strbuf))

@ -11,7 +11,7 @@ namespace llarp
bool
LinkIntroMessage::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* buf)
{
if (key == "a")
if (key.startswith("a"))
{
llarp_buffer_t strbuf;
if (!bencode_read_string(buf, &strbuf))
@ -20,18 +20,18 @@ namespace llarp
return false;
return *strbuf.cur == 'i';
}
if (key == "n")
if (key.startswith("n"))
{
if (N.BDecode(buf))
return true;
llarp::LogWarn("failed to decode nonce in LIM");
return false;
}
if (key == "p")
if (key.startswith("p"))
{
return bencode_read_integer(buf, &P);
}
if (key == "r")
if (key.startswith("r"))
{
if (rc.BDecode(buf))
return true;
@ -39,7 +39,7 @@ namespace llarp
llarp::DumpBuffer(*buf);
return false;
}
if (key == "v")
if (key.startswith("v"))
{
if (!bencode_read_integer(buf, &version))
return false;
@ -52,7 +52,7 @@ namespace llarp
llarp::LogDebug("LIM version ", version);
return true;
}
if (key == "z")
if (key.startswith("z"))
{
return Z.BDecode(buf);
}

@ -45,7 +45,7 @@ namespace llarp
if (!key)
return false;
// we are expecting the first key to be 'a'
if (!(*key == "a"))
if (!key->startswith("a"))
{
llarp::LogWarn("message has no message type");
return false;

@ -22,7 +22,7 @@ namespace llarp
bool
LR_CommitMessage::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* buf)
{
if (key == "c")
if (key.startswith("c"))
{
/// so we dont put it into the shitty queue
pathid.Fill('c');
@ -131,7 +131,7 @@ namespace llarp
return false;
if (!BEncodeMaybeReadDictEntry("t", txid, read, *key, buffer))
return false;
if (*key == "u")
if (key->startswith("u"))
{
nextRC = std::make_unique<RouterContact>();
return nextRC->BDecode(buffer);
@ -139,7 +139,7 @@ namespace llarp
if (!BEncodeMaybeVerifyVersion(
"v", version, llarp::constants::proto_version, read, *key, buffer))
return false;
if (*key == "w")
if (key->startswith("w"))
{
// check for duplicate
if (work)

@ -60,25 +60,25 @@ namespace llarp
LR_StatusMessage::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* buf)
{
bool read = false;
if (key == "c")
if (key.startswith("c"))
{
return BEncodeReadArray(frames, buf);
}
if (key == "p")
if (key.startswith("p"))
{
if (!BEncodeMaybeReadDictEntry("p", pathid, read, key, buf))
{
return false;
}
}
else if (key == "s")
else if (key.startswith("s"))
{
if (!BEncodeMaybeReadDictInt("s", status, read, key, buf))
{
return false;
}
}
else if (key == "v")
else if (key.startswith("v"))
{
if (!BEncodeMaybeVerifyVersion("v", version, llarp::constants::proto_version, read, key, buf))
{

@ -41,7 +41,7 @@ namespace llarp
llarp_buffer_t strbuf;
// rank
if (key == "c")
if (key.startswith("c"))
{
if (!bencode_read_integer(buf, &i))
return false;
@ -54,7 +54,7 @@ namespace llarp
}
// dialect
if (key == "d")
if (key.startswith("d"))
{
if (!bencode_read_string(buf, &strbuf))
return false;
@ -67,13 +67,13 @@ namespace llarp
}
// encryption public key
if (key == "e")
if (key.startswith("e"))
{
return pubkey.BDecode(buf);
}
// ip address
if (key == "i")
if (key.startswith("i"))
{
if (!bencode_read_string(buf, &strbuf))
return false;
@ -87,7 +87,7 @@ namespace llarp
}
// port
if (key == "p")
if (key.startswith("p"))
{
if (!bencode_read_integer(buf, &i))
return false;
@ -100,7 +100,7 @@ namespace llarp
}
// version
if (key == "v")
if (key.startswith("v"))
{
if (!bencode_read_integer(buf, &i))
return false;

@ -50,9 +50,19 @@ namespace llarp
fromSockAddr(const SockAddr& address);
/// get this as an explicit v4 or explicit v6
std::variant<nuint32_t, nuint128_t>
net::ipaddr_t
IP() const;
/// get this as an v4 or throw if it is not one
inline net::ipv4addr_t
IPv4() const
{
auto ip = IP();
if (auto* ptr = std::get_if<net::ipv4addr_t>(&ip))
return *ptr;
throw std::runtime_error{"no ipv4 address found in address info"};
}
std::string
ToString() const;
};

@ -0,0 +1,36 @@
#pragma once
#include "ip_range.hpp"
namespace llarp
{
static constexpr std::array bogonRanges_v6 = {
// zero
IPRange{huint128_t{0}, netmask_ipv6_bits(128)},
// loopback
IPRange{huint128_t{1}, netmask_ipv6_bits(128)},
// yggdrasil
IPRange{huint128_t{uint128_t{0x0200'0000'0000'0000UL, 0UL}}, netmask_ipv6_bits(7)},
// multicast
IPRange{huint128_t{uint128_t{0xff00'0000'0000'0000UL, 0UL}}, netmask_ipv6_bits(8)},
// local
IPRange{huint128_t{uint128_t{0xfc00'0000'0000'0000UL, 0UL}}, netmask_ipv6_bits(8)},
// local
IPRange{huint128_t{uint128_t{0xf800'0000'0000'0000UL, 0UL}}, netmask_ipv6_bits(8)}};
static constexpr std::array bogonRanges_v4 = {
IPRange::FromIPv4(0, 0, 0, 0, 8),
IPRange::FromIPv4(10, 0, 0, 0, 8),
IPRange::FromIPv4(100, 64, 0, 0, 10),
IPRange::FromIPv4(127, 0, 0, 0, 8),
IPRange::FromIPv4(169, 254, 0, 0, 16),
IPRange::FromIPv4(172, 16, 0, 0, 12),
IPRange::FromIPv4(192, 0, 0, 0, 24),
IPRange::FromIPv4(192, 0, 2, 0, 24),
IPRange::FromIPv4(192, 88, 99, 0, 24),
IPRange::FromIPv4(192, 168, 0, 0, 16),
IPRange::FromIPv4(198, 18, 0, 0, 15),
IPRange::FromIPv4(198, 51, 100, 0, 24),
IPRange::FromIPv4(203, 0, 113, 0, 24),
IPRange::FromIPv4(224, 0, 0, 0, 4),
IPRange::FromIPv4(240, 0, 0, 0, 4)};
} // namespace llarp

@ -69,7 +69,7 @@ namespace llarp
return false;
if (!BEncodeMaybeReadDictInt("v", version, read, k, buf))
return false;
if (k == "a")
if (k.startswith("a"))
{
in6_addr tmp;
if (not bdecode_ip_string(buf, tmp))
@ -79,7 +79,7 @@ namespace llarp
ipAddress = IpAddress(addr);
return true;
}
if (k == "b")
if (k.startswith("b"))
{
in6_addr tmp;
if (not bdecode_ip_string(buf, tmp))

@ -0,0 +1,21 @@
#pragma once
#include <string>
#include <vector>
#include "ip_range.hpp"
namespace llarp::net
{
/// info about a network interface lokinet does not own
struct InterfaceInfo
{
/// human readable name of interface
std::string name;
/// interface's index
int index;
/// the addresses owned by this interface
std::vector<IPRange> addrs;
/// a gateway we can use if it exists
std::optional<ipaddr_t> gateway;
};
} // namespace llarp::net

@ -127,7 +127,7 @@ namespace llarp
SockAddr addr(m_ipAddress);
const auto* addr6 = static_cast<const sockaddr_in6*>(addr);
const uint8_t* raw = addr6->sin6_addr.s6_addr;
return IsIPv4Bogon(ipaddr_ipv4_bits(raw[12], raw[13], raw[14], raw[15]));
return IPRange::V4MappedRange().Contains(ipaddr_ipv4_bits(raw[12], raw[13], raw[14], raw[15]));
}
std::string

@ -19,14 +19,12 @@ namespace llarp
/// As a convenience, it can produce a SockAddr for dealing with network libraries which depend
/// sockaddr structs. However, it does not keep this as a member variable and isn't responsible
/// for its lifetime/memory/etc.
///
/// TODO: IPv6 is not currently supported.
struct IpAddress
struct [[deprecated("use llarp::SockAddr instead")]] IpAddress
{
/// Empty constructor.
IpAddress() = default;
/// move construtor
IpAddress(IpAddress&&) = default;
IpAddress(IpAddress &&) = default;
/// copy construct
IpAddress(const IpAddress&);
@ -56,80 +54,64 @@ namespace llarp
/// @param addr is an SockAddr to initialize from.
IpAddress(const SockAddr& addr);
IpAddress&
operator=(const sockaddr& other);
IpAddress& operator=(const sockaddr& other);
/// move assignment
IpAddress&
operator=(IpAddress&& other);
IpAddress& operator=(IpAddress&& other);
/// copy assignment
IpAddress&
operator=(const IpAddress& other);
IpAddress& operator=(const IpAddress& other);
/// Return the port. Returns -1 if no port has been provided.
///
/// @return the port, if present
std::optional<uint16_t>
getPort() const;
std::optional<uint16_t> getPort() const;
/// Return true if we have a port set otherwise return false
bool
hasPort() const;
bool hasPort() const;
/// Set the port.
///
/// @param port
void
setPort(std::optional<uint16_t> port);
void setPort(std::optional<uint16_t> port);
/// Set the IP address. Follows the same logic as the constructor with the same signature, but
/// doesn't overwrite the port if the port isn't present in the string.
void
setAddress(std::string_view str);
void
setAddress(std::string_view str, std::optional<uint16_t> port);
void setAddress(std::string_view str);
void setAddress(std::string_view str, std::optional<uint16_t> port);
/// Returns true if this is an IPv4 address (or an IPv6 address representing an IPv4 address)
///
/// TODO: could return an int (e.g. 4 or 6) or an enum
///
/// @return true if this is an IPv4 address, false otherwise
bool
isIPv4();
bool isIPv4();
/// Returns true if this represents a valid IpAddress, false otherwise.
///
/// @return whether or not this IpAddress is empty
bool
isEmpty() const;
bool isEmpty() const;
/// Creates an instance of SockAddr representing this IpAddress.
///
/// @return an instance of a SockAddr created from this IpAddress
SockAddr
createSockAddr() const;
SockAddr createSockAddr() const;
/// Returns true if this IpAddress is a bogon, false otherwise
///
/// @return whether or not this IpAddress is a bogon
bool
isBogon() const;
bool isBogon() const;
/// Returns a string representing this IpAddress
///
/// @return string representation of this IpAddress
std::string
ToString() const;
std::string ToString() const;
std::string
toHost() const;
std::string toHost() const;
huint32_t
toIP() const;
huint32_t toIP() const;
huint128_t
toIP6() const;
huint128_t toIP6() const;
// TODO: other utility functions left over from Addr which may be useful
// IsBogon() const;
@ -137,11 +119,9 @@ namespace llarp
// std::hash
// to string / stream / etc
bool
operator<(const IpAddress& other) const;
bool operator<(const IpAddress& other) const;
bool
operator==(const IpAddress& other) const;
bool operator==(const IpAddress& other) const;
private:
bool m_empty = true;

@ -1,6 +1,6 @@
#include "ip_packet.hpp"
#include "ip.hpp"
#include <llarp/constants/net.hpp>
#include <llarp/util/buffer.hpp>
#include <llarp/util/mem.hpp>
#include <llarp/util/str.hpp>
@ -17,22 +17,6 @@ namespace llarp::net
{
constexpr uint32_t ipv6_flowlabel_mask = 0b0000'0000'0000'1111'1111'1111'1111'1111;
template <bool little>
struct ipv6_header_preamble
{
unsigned char pad_small : 4;
unsigned char version : 4;
uint8_t pad[3];
};
template <>
struct ipv6_header_preamble<false>
{
unsigned char version : 4;
unsigned char pad_small : 4;
uint8_t pad[3];
};
/// get 20 bit truncated flow label in network order
llarp::nuint32_t
ipv6_header::FlowLabel() const
@ -75,7 +59,6 @@ namespace llarp::net
}
throw std::invalid_argument{"no such ip protocol: '" + data + "'"};
}
inline static uint32_t*
in6_uint32_ptr(in6_addr& addr)
{
@ -106,30 +89,55 @@ namespace llarp::net
return ExpandV4(dstv4());
}
bool
IPPacket::Load(const llarp_buffer_t& pkt)
IPPacket::IPPacket(byte_view_t view)
{
if (view.size() < MinSize)
{
_buf.resize(0);
return;
}
_buf.resize(view.size());
std::copy_n(view.data(), size(), data());
}
IPPacket::IPPacket(size_t sz)
{
if (sz and sz < MinSize)
throw std::invalid_argument{"buffer size is too small to hold an ip packet"};
_buf.resize(sz);
}
SockAddr
IPPacket::src() const
{
const auto port = SrcPort().value_or(net::port_t{});
if (IsV4())
return SockAddr{ToNet(srcv4()), port};
else
return SockAddr{ToNet(srcv6()), port};
}
SockAddr
IPPacket::dst() const
{
if (pkt.sz > sizeof(buf) or pkt.sz == 0)
return false;
sz = pkt.sz;
std::copy_n(pkt.base, sz, buf);
return true;
auto port = *DstPort();
if (IsV4())
return SockAddr{ToNet(dstv4()), port};
else
return SockAddr{ToNet(dstv6()), port};
}
ManagedBuffer
IPPacket::ConstBuffer() const
IPPacket::IPPacket(std::vector<byte_t>&& stolen) : _buf{stolen}
{
const byte_t* ptr = buf;
llarp_buffer_t b(ptr, sz);
return ManagedBuffer(b);
if (size() < MinSize)
_buf.resize(0);
}
ManagedBuffer
IPPacket::Buffer()
byte_view_t
IPPacket::view() const
{
byte_t* ptr = buf;
llarp_buffer_t b(ptr, sz);
return ManagedBuffer(b);
return byte_view_t{data(), size()};
}
std::optional<nuint16_t>
@ -139,7 +147,7 @@ namespace llarp::net
{
case IPProtocol::TCP:
case IPProtocol::UDP:
return nuint16_t{*reinterpret_cast<const uint16_t*>(buf + (Header()->ihl * 4) + 2)};
return nuint16_t{*reinterpret_cast<const uint16_t*>(data() + (Header()->ihl * 4) + 2)};
default:
return std::nullopt;
}
@ -148,11 +156,12 @@ namespace llarp::net
std::optional<nuint16_t>
IPPacket::SrcPort() const
{
switch (IPProtocol{Header()->protocol})
IPProtocol proto{Header()->protocol};
switch (proto)
{
case IPProtocol::TCP:
case IPProtocol::UDP:
return nuint16_t{*reinterpret_cast<const uint16_t*>(buf + (Header()->ihl * 4))};
return nuint16_t{*reinterpret_cast<const uint16_t*>(data() + (Header()->ihl * 4))};
default:
return std::nullopt;
}
@ -397,7 +406,8 @@ namespace llarp::net
auto oSrcIP = nuint32_t{hdr->saddr};
auto oDstIP = nuint32_t{hdr->daddr};
auto* buf = data();
auto sz = size();
// L4 checksum
auto ihs = size_t(hdr->ihl * 4);
if (ihs <= sz)
@ -435,7 +445,7 @@ namespace llarp::net
IPPacket::UpdateIPv6Address(huint128_t src, huint128_t dst, std::optional<nuint32_t> flowlabel)
{
const size_t ihs = 4 + 4 + 16 + 16;
const auto sz = size();
// XXX should've been checked at upper level?
if (sz <= ihs)
return;
@ -459,11 +469,11 @@ namespace llarp::net
const uint32_t* nDstIP = in6_uint32_ptr(hdr->dstaddr);
// TODO IPv6 header options
auto pld = buf + ihs;
auto* pld = data() + ihs;
auto psz = sz - ihs;
size_t fragoff = 0;
auto nextproto = hdr->proto;
auto nextproto = hdr->protocol;
for (;;)
{
switch (nextproto)
@ -554,31 +564,27 @@ namespace llarp::net
if (IsV4())
{
constexpr auto icmp_Header_size = 8;
constexpr auto ip_Header_size = 20;
net::IPPacket pkt{};
auto* pkt_Header = pkt.Header();
auto ip_Header_size = Header()->ihl * 4;
auto pkt_size = (icmp_Header_size + ip_Header_size) * 2;
net::IPPacket pkt{static_cast<size_t>(pkt_size)};
auto* pkt_Header = pkt.Header();
pkt_Header->version = 4;
pkt_Header->ihl = 0x05;
pkt_Header->tos = 0;
pkt_Header->check = 0;
pkt_Header->tot_len = ntohs(icmp_Header_size + ip_Header_size);
pkt_Header->tot_len = ntohs(pkt_size);
pkt_Header->saddr = Header()->daddr;
pkt_Header->daddr = Header()->saddr;
pkt_Header->protocol = 1; // ICMP
pkt_Header->ttl = 1;
pkt_Header->ttl = Header()->ttl;
pkt_Header->frag_off = htons(0b0100000000000000);
// size pf ip header
const size_t l3_HeaderSize = Header()->ihl * 4;
// size of l4 packet to reflect back
const size_t l4_PacketSize = 8;
pkt_Header->tot_len += ntohs(l4_PacketSize + l3_HeaderSize);
uint16_t* checksum;
uint8_t* itr = pkt.buf + (pkt_Header->ihl * 4);
uint8_t* itr = pkt.data() + ip_Header_size;
uint8_t* icmp_begin = itr; // type 'destination unreachable'
*itr++ = 3;
// code 'Destination host unknown error'
// code 'Destination host unknown error'
*itr++ = 7;
// checksum + unused
oxenc::write_host_as_big<uint32_t>(0, itr);
@ -588,14 +594,13 @@ namespace llarp::net
oxenc::write_host_as_big<uint16_t>(1500, itr);
itr += 2;
// copy ip header and first 8 bytes of datagram for icmp rject
std::copy_n(buf, l4_PacketSize + l3_HeaderSize, itr);
itr += l4_PacketSize + l3_HeaderSize;
std::copy_n(data(), ip_Header_size + icmp_Header_size, itr);
itr += ip_Header_size + icmp_Header_size;
// calculate checksum of ip header
pkt_Header->check = ipchksum(pkt.buf, pkt_Header->ihl * 4);
pkt_Header->check = ipchksum(pkt.data(), ip_Header_size);
const auto icmp_size = std::distance(icmp_begin, itr);
// calculate icmp checksum
*checksum = ipchksum(icmp_begin, icmp_size);
pkt.sz = ntohs(pkt_Header->tot_len);
return pkt;
}
return std::nullopt;
@ -614,56 +619,85 @@ namespace llarp::net
return std::nullopt;
// check for invalid size
if (sz < (hdr->ihl * 4) + l4_HeaderSize)
if (size() < (hdr->ihl * 4) + l4_HeaderSize)
return std::nullopt;
const uint8_t* ptr = buf + ((hdr->ihl * 4) + l4_HeaderSize);
return std::make_pair(reinterpret_cast<const char*>(ptr), std::distance(ptr, buf + sz));
const uint8_t* ptr = data() + ((hdr->ihl * 4) + l4_HeaderSize);
return std::make_pair(reinterpret_cast<const char*>(ptr), std::distance(ptr, data() + size()));
}
IPPacket
IPPacket::UDP(
nuint32_t srcaddr,
nuint16_t srcport,
nuint32_t dstaddr,
nuint16_t dstport,
const llarp_buffer_t& buf)
namespace
{
net::IPPacket pkt;
if (buf.sz + 28 > sizeof(pkt.buf))
IPPacket
make_ip4_udp(
net::ipv4addr_t srcaddr,
net::port_t srcport,
net::ipv4addr_t dstaddr,
net::port_t dstport,
std::vector<byte_t> udp_data)
{
pkt.sz = 0;
constexpr auto pkt_overhead = constants::udp_header_bytes + constants::ip_header_min_bytes;
net::IPPacket pkt{udp_data.size() + pkt_overhead};
auto* hdr = pkt.Header();
pkt.data()[1] = 0;
hdr->version = 4;
hdr->ihl = 5;
hdr->tot_len = htons(pkt_overhead + udp_data.size());
hdr->protocol = 0x11; // udp
hdr->ttl = 64;
hdr->frag_off = htons(0b0100000000000000);
hdr->saddr = srcaddr.n;
hdr->daddr = dstaddr.n;
// make udp packet
uint8_t* ptr = pkt.data() + constants::ip_header_min_bytes;
std::memcpy(ptr, &srcport.n, 2);
ptr += 2;
std::memcpy(ptr, &dstport.n, 2);
ptr += 2;
oxenc::write_host_as_big(
static_cast<uint16_t>(udp_data.size() + constants::udp_header_bytes), ptr);
ptr += 2;
oxenc::write_host_as_big(uint16_t{0}, ptr); // checksum
ptr += 2;
std::copy_n(udp_data.data(), udp_data.size(), ptr);
hdr->check = 0;
hdr->check = net::ipchksum(pkt.data(), 20);
return pkt;
}
auto* hdr = pkt.Header();
pkt.buf[1] = 0;
hdr->version = 4;
hdr->ihl = 5;
hdr->tot_len = htons(buf.sz + 28);
hdr->protocol = 0x11; // udp
hdr->ttl = 64;
hdr->frag_off = htons(0b0100000000000000);
hdr->saddr = srcaddr.n;
hdr->daddr = dstaddr.n;
// make udp packet
uint8_t* ptr = pkt.buf + 20;
std::memcpy(ptr, &srcport.n, 2);
ptr += 2;
std::memcpy(ptr, &dstport.n, 2);
ptr += 2;
oxenc::write_host_as_big(static_cast<uint16_t>(buf.sz + 8), ptr);
ptr += 2;
oxenc::write_host_as_big(uint16_t{0}, ptr); // checksum
ptr += 2;
std::copy_n(buf.base, buf.sz, ptr);
hdr->check = 0;
hdr->check = net::ipchksum(pkt.buf, 20);
pkt.sz = 28 + buf.sz;
return pkt;
} // namespace
IPPacket
IPPacket::make_udp(
net::ipaddr_t srcaddr,
net::port_t srcport,
net::ipaddr_t dstaddr,
net::port_t dstport,
std::vector<byte_t> udp_data)
{
auto getfam = [](auto&& v) {
if (std::holds_alternative<net::ipv4addr_t>(v))
return AF_INET;
else if (std::holds_alternative<net::ipv6addr_t>(v))
return AF_INET6;
else
return AF_UNSPEC;
};
auto fam = getfam(srcaddr);
if (fam != getfam(dstaddr))
return net::IPPacket{size_t{}};
if (fam == AF_INET)
{
return make_ip4_udp(
*std::get_if<net::ipv4addr_t>(&srcaddr),
srcport,
*std::get_if<net::ipv4addr_t>(&dstaddr),
dstport,
std::move(udp_data));
}
// TODO: ipv6
return net::IPPacket{size_t{}};
}
} // namespace llarp::net

@ -1,5 +1,6 @@
#pragma once
#include <oxenc/endian.h>
#include <llarp/ev/ev.hpp>
#include "net.hpp"
#include <llarp/util/buffer.hpp>
@ -10,11 +11,10 @@
namespace llarp::net
{
template <bool is_little_endian>
struct ip_header_le
{
unsigned int ihl : 4;
unsigned int version : 4;
uint8_t ihl : 4;
uint8_t version : 4;
uint8_t tos;
uint16_t tot_len;
uint16_t id;
@ -26,11 +26,10 @@ namespace llarp::net
uint32_t daddr;
};
template <>
struct ip_header_le<false>
struct ip_header_be
{
unsigned int version : 4;
unsigned int ihl : 4;
uint8_t version : 4;
uint8_t ihl : 4;
uint8_t tos;
uint16_t tot_len;
uint16_t id;
@ -42,9 +41,10 @@ namespace llarp::net
uint32_t daddr;
};
using ip_header = ip_header_le<oxenc::little_endian>;
using ip_header = std::conditional_t<oxenc::little_endian, ip_header_le, ip_header_be>;
static_assert(sizeof(ip_header) == 20);
template <bool little>
struct ipv6_header_preamble_le
{
unsigned char pad_small : 4;
@ -52,24 +52,28 @@ namespace llarp::net
uint8_t pad[3];
};
template <>
struct ipv6_header_preamble_le<false>
struct ipv6_header_preamble_be
{
unsigned char version : 4;
unsigned char pad_small : 4;
uint8_t pad[3];
};
using ipv6_header_preamble =
std::conditional_t<oxenc::little_endian, ipv6_header_preamble_le, ipv6_header_preamble_be>;
static_assert(sizeof(ipv6_header_preamble) == 4);
struct ipv6_header
{
union
{
ipv6_header_preamble_le<oxenc::little_endian> preamble;
ipv6_header_preamble preamble;
uint32_t flowlabel;
} preamble;
uint16_t payload_len;
uint8_t proto;
uint8_t protocol;
uint8_t hoplimit;
in6_addr srcaddr;
in6_addr dstaddr;
@ -81,6 +85,8 @@ namespace llarp::net
FlowLabel(llarp::nuint32_t label);
};
static_assert(sizeof(ipv6_header) == 40);
/// "well known" ip protocols
/// TODO: extend this to non "well known values"
enum class IPProtocol : uint8_t
@ -109,34 +115,119 @@ namespace llarp::net
/// an Packet
struct IPPacket
{
static constexpr size_t MaxSize = 1500;
static constexpr size_t _max_size = 1500;
llarp_time_t timestamp;
size_t sz;
std::vector<byte_t> _buf;
alignas(ip_header) byte_t buf[MaxSize];
public:
IPPacket() : IPPacket{size_t{}}
{}
/// create an ip packet buffer of all zeros of size sz
explicit IPPacket(size_t sz);
/// create an ip packet from a view
explicit IPPacket(byte_view_t);
/// create an ip packet from a vector we then own
IPPacket(std::vector<byte_t>&&);
static IPPacket
~IPPacket() = default;
static constexpr size_t MaxSize = _max_size;
static constexpr size_t MinSize = 20;
[[deprecated("deprecated because of llarp_buffer_t")]] static IPPacket
UDP(nuint32_t srcaddr,
nuint16_t srcport,
nuint32_t dstaddr,
nuint16_t dstport,
const llarp_buffer_t& data);
const llarp_buffer_t& data)
{
return make_udp(srcaddr, srcport, dstaddr, dstport, data.copy());
}
static IPPacket
make_udp(
net::ipaddr_t srcaddr,
net::port_t srcport,
net::ipaddr_t dstaddr,
net::port_t dstport,
std::vector<byte_t> udp_body);
static inline IPPacket
make_udp(SockAddr src, SockAddr dst, std::variant<OwnedBuffer, std::vector<byte_t>> udp_body)
{
if (auto* vec = std::get_if<std::vector<byte_t>>(&udp_body))
return make_udp(src.getIP(), src.port(), dst.getIP(), dst.port(), std::move(*vec));
else if (auto* buf = std::get_if<OwnedBuffer>(&udp_body))
return make_udp(src, dst, buf->copy());
else
return net::IPPacket{size_t{}};
}
[[deprecated("deprecated because of llarp_buffer_t")]] inline bool
Load(const llarp_buffer_t& buf)
{
_buf = buf.copy();
if (size() >= MinSize)
return true;
_buf.resize(0);
return false;
}
ManagedBuffer
Buffer();
[[deprecated("deprecated because of llarp_buffer_t")]] inline llarp_buffer_t
ConstBuffer() const
{
return llarp_buffer_t{_buf};
}
ManagedBuffer
ConstBuffer() const;
/// steal the underlying vector
inline std::vector<byte_t>
steal()
{
std::vector<byte_t> buf;
buf.resize(0);
std::swap(_buf, buf);
return buf;
}
bool
Load(const llarp_buffer_t& buf);
inline byte_t*
data()
{
return _buf.data();
}
inline const byte_t*
data() const
{
return _buf.data();
}
constexpr size_t
capacity() const
{
return _max_size;
}
inline size_t
size() const
{
return _buf.size();
}
inline bool
empty() const
{
return _buf.empty();
}
byte_view_t
view() const;
struct CompareSize
{
bool
operator()(const IPPacket& left, const IPPacket& right)
{
return left.sz < right.sz;
return left.size() < right.size();
}
};
@ -152,25 +243,25 @@ namespace llarp::net
inline ip_header*
Header()
{
return reinterpret_cast<ip_header*>(&buf[0]);
return reinterpret_cast<ip_header*>(data());
}
inline const ip_header*
Header() const
{
return reinterpret_cast<const ip_header*>(&buf[0]);
return reinterpret_cast<const ip_header*>(data());
}
inline ipv6_header*
HeaderV6()
{
return reinterpret_cast<ipv6_header*>(&buf[0]);
return reinterpret_cast<ipv6_header*>(data());
}
inline const ipv6_header*
HeaderV6() const
{
return reinterpret_cast<const ipv6_header*>(&buf[0]);
return reinterpret_cast<const ipv6_header*>(data());
}
inline int
@ -179,6 +270,15 @@ namespace llarp::net
return Header()->version;
}
inline byte_t
protocol() const
{
if (IsV4())
return Header()->protocol;
else
return HeaderV6()->protocol;
}
inline bool
IsV4() const
{
@ -226,6 +326,12 @@ namespace llarp::net
huint128_t
dst4to6Lan() const;
SockAddr
src() const;
SockAddr
dst() const;
/// get destination port if applicable
std::optional<nuint16_t>
DstPort() const;
@ -238,6 +344,14 @@ namespace llarp::net
std::optional<std::pair<const char*, size_t>>
L4Data() const;
inline std::optional<OwnedBuffer>
L4OwnedBuffer() const
{
if (auto data = L4Data())
return OwnedBuffer{reinterpret_cast<const byte_t*>(data->first), data->second};
return std::nullopt;
}
void
UpdateIPv4Address(nuint32_t src, nuint32_t dst);
@ -256,6 +370,8 @@ namespace llarp::net
/// make an icmp unreachable reply packet based of this ip packet
std::optional<IPPacket>
MakeICMPUnreachable() const;
std::function<void(net::IPPacket)> reply;
};
/// generate ip checksum

@ -90,4 +90,15 @@ namespace llarp
return addr.ToString();
}
std::string
IPRange::NetmaskString() const
{
if (IsV4())
{
const huint32_t mask = net::TruncateV6(netmask_bits);
return mask.ToString();
}
return netmask_bits.ToString();
}
} // namespace llarp

@ -9,10 +9,6 @@
namespace llarp
{
/// forward declare
bool
IsBogon(huint128_t ip);
struct IPRange
{
using Addr_t = huint128_t;
@ -25,6 +21,12 @@ namespace llarp
: addr{std::move(address)}, netmask_bits{std::move(netmask)}
{}
static constexpr IPRange
V4MappedRange()
{
return IPRange{huint128_t{0x0000'ffff'0000'0000UL}, netmask_ipv6_bits(96)};
}
static constexpr IPRange
FromIPv4(byte_t a, byte_t b, byte_t c, byte_t d, byte_t mask)
{
@ -42,8 +44,7 @@ namespace llarp
constexpr bool
IsV4() const
{
constexpr auto ipv4_map = IPRange{huint128_t{0x0000'ffff'0000'0000UL}, netmask_ipv6_bits(96)};
return ipv4_map.Contains(addr);
return V4MappedRange().Contains(addr);
}
/// get address family
@ -55,27 +56,6 @@ namespace llarp
return AF_INET6;
}
/// return true if we intersect with a bogon range
bool
BogonRange() const
{
// special case for 0.0.0.0/0
if (IsV4() and netmask_bits == netmask_ipv6_bits(96))
return false;
// special case for ::/0
if (netmask_bits == huint128_t{0})
return false;
return IsBogon(addr) or IsBogon(HighestAddr());
}
/// return true if we intersect with a bogon range *and* we contain the given address
template <typename Addr>
bool
BogonContains(Addr&& addr) const
{
return BogonRange() and Contains(std::forward<Addr>(addr));
}
/// return the number of bits set in the hostmask
constexpr int
HostmaskBits() const
@ -117,6 +97,12 @@ namespace llarp
return Contains(net::ExpandV4(ip));
}
inline bool
Contains(const net::ipaddr_t& ip) const
{
return var::visit([this](auto&& ip) { return Contains(ToHost(ip)); }, ip);
}
/// get the highest address on this range
constexpr huint128_t
HighestAddr() const
@ -147,6 +133,9 @@ namespace llarp
std::string
BaseAddressString() const;
std::string
NetmaskString() const;
bool
FromString(std::string str);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save