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', 'mkdir build',
'cd build', 'cd build',
'cmake .. -DWITH_SETCAP=OFF -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always -DCMAKE_BUILD_TYPE=' + build_type + ' ' + '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 '') + (if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') +
'-DWITH_LTO=' + (if lto then 'ON ' else 'OFF ') + '-DWITH_LTO=' + (if lto then 'ON ' else 'OFF ') +
'-DWITH_TESTS=' + (if tests then 'ON ' else 'OFF ') + '-DWITH_TESTS=' + (if tests then 'ON ' else 'OFF ') +
@ -121,7 +122,7 @@ local windows_cross_pipeline(name,
lto=false, lto=false,
werror=false, werror=false,
cmake_extra='', cmake_extra='',
toolchain='32', gui_zip_url='',
extra_cmds=[], extra_cmds=[],
jobs=6, jobs=6,
allow_fail=false) = { allow_fail=false) = {
@ -137,16 +138,16 @@ local windows_cross_pipeline(name,
image: image, image: image,
pull: 'always', pull: 'always',
[if allow_fail then 'failure']: 'ignore', [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: [ commands: [
'echo "Building on ${DRONE_STAGE_MACHINE}"', 'echo "Building on ${DRONE_STAGE_MACHINE}"',
'echo "man-db man-db/auto-update boolean false" | debconf-set-selections', 'echo "man-db man-db/auto-update boolean false" | debconf-set-selections',
apt_get_quiet + ' update', apt_get_quiet + ' update',
apt_get_quiet + ' install -y eatmydata', 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-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', '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, ] + extra_cmds,
}, },
], ],
@ -277,7 +278,13 @@ local mac_builder(name,
// basic system headers. WTF apple: // basic system headers. WTF apple:
'export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"', 'export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"',
'ulimit -n 1024', // because macos sets ulimit to 256 for some reason yeah idk '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, ] + 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), debian_pipeline('Debian stable (armhf)', docker_base + 'debian-stable/arm32v7', arch='arm64', jobs=4),
// cross compile targets // 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 (arm/arm64)', cross_targets=['arm-linux-gnueabihf', 'aarch64-linux-gnu']),
linux_cross_pipeline('Cross Compile (ppc64le)', cross_targets=['powerpc64le-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 // android apk builder
apk_builder('android apk', docker_base + 'flutter', extra_cmds=['UPLOAD_OS=android ./contrib/ci/drone-static-upload.sh']), apk_builder('android apk', docker_base + 'flutter', extra_cmds=['UPLOAD_OS=android ./contrib/ci/drone-static-upload.sh']),
// Windows builds (x64) // Windows builds (x64)
windows_cross_pipeline('Windows (amd64)', windows_cross_pipeline('Windows (amd64)',
docker_base + 'debian-win32-cross', docker_base + 'nodejs-lts',
toolchain='64',
extra_cmds=[ extra_cmds=[
'./contrib/ci/drone-static-upload.sh', './contrib/ci/drone-static-upload.sh',
]), ]),
@ -371,7 +378,7 @@ local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = {
lto=true, lto=true,
tests=false, tests=false,
oxen_repo=true, 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_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8 ' +
'-DCMAKE_CXX_FLAGS="-march=x86-64 -mtune=haswell" ' + '-DCMAKE_CXX_FLAGS="-march=x86-64 -mtune=haswell" ' +
'-DCMAKE_C_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', docker_base + 'debian-buster/arm32v7',
arch='arm64', arch='arm64',
deps=['g++', 'python3-dev', 'automake', 'libtool'], 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" ' + '-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', '-DNATIVE_BUILD=OFF -DWITH_SYSTEMD=OFF -DWITH_BOOTSTRAP=OFF',
extra_cmds=[ 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) 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(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_BOOTSTRAP "build lokinet-bootstrap tool" ${DEFAULT_WITH_BOOTSTRAP})
option(WITH_PEERSTATS "build with experimental peerstats db support" OFF) 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) include(cmake/enable_lto.cmake)
@ -81,6 +83,14 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo) set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif() 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) if(BUILD_STATIC_DEPS AND STATIC_LINK)
message(STATUS "we are building static deps so we won't build shared libs") message(STATUS "we are building static deps so we won't build shared libs")
set(BUILD_SHARED_LIBS OFF CACHE BOOL "") set(BUILD_SHARED_LIBS OFF CACHE BOOL "")
@ -171,13 +181,20 @@ if(NOT TARGET sodium)
export(TARGETS sodium NAMESPACE sodium:: FILE sodium-exports.cmake) export(TARGETS sodium NAMESPACE sodium:: FILE sodium-exports.cmake)
endif() endif()
if(NOT APPLE) set(warning_flags -Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Werror=vla)
add_compile_options(-Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations -Werror=vla) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") list(APPEND warning_flags -Wno-unknown-warning-option)
add_compile_options(-Wno-unknown-warning-option) endif()
endif() if(WARN_DEPRECATED)
list(APPEND warning_flags -Wdeprecated-declarations)
else()
list(APPEND warning_flags -Wno-deprecated-declarations)
endif() 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) if(XSAN)
string(APPEND CMAKE_CXX_FLAGS_DEBUG " -fsanitize=${XSAN} -fno-omit-frame-pointer -fno-sanitize-recover") string(APPEND CMAKE_CXX_FLAGS_DEBUG " -fsanitize=${XSAN} -fno-omit-frame-pointer -fno-sanitize-recover")
foreach(type EXE MODULE SHARED STATIC) foreach(type EXE MODULE SHARED STATIC)
@ -186,11 +203,6 @@ if(XSAN)
message(STATUS "Doing a ${XSAN} sanitizer build") message(STATUS "Doing a ${XSAN} sanitizer build")
endif() endif()
if(CMAKE_BUILD_TYPE MATCHES "[Dd][Ee][Bb][Uu][Gg]")
add_definitions(-DLOKINET_DEBUG)
endif()
include(cmake/coverage.cmake) include(cmake/coverage.cmake)
# these vars are set by the cmake toolchain spec # 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) COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
endif() endif()
if(BUILD_PACKAGE AND NOT APPLE) if(BUILD_PACKAGE AND NOT APPLE)
include(cmake/installer.cmake) include(cmake/installer.cmake)
endif()
if(TARGET package)
add_dependencies(package assemble_gui)
endif() endif()

@ -5,10 +5,10 @@
set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads") 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_MIRROR ${LOCAL_MIRROR} https://www.openssl.org/source CACHE STRING "openssl download mirror(s)")
set(OPENSSL_SOURCE openssl-${OPENSSL_VERSION}.tar.gz) set(OPENSSL_SOURCE openssl-${OPENSSL_VERSION}.tar.gz)
set(OPENSSL_HASH SHA256=9384a2b0570dd80358841464677115df785edb941c71211f75076d72fe6b438f set(OPENSSL_HASH SHA256=aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a
CACHE STRING "openssl source hash") CACHE STRING "openssl source hash")
set(EXPAT_VERSION 2.4.8 CACHE STRING "expat version") 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 set(EXPAT_HASH SHA256=f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25
CACHE STRING "expat source hash") 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_MIRROR ${LOCAL_MIRROR} https://nlnetlabs.nl/downloads/unbound CACHE STRING "unbound download mirror(s)")
set(UNBOUND_SOURCE unbound-${UNBOUND_VERSION}.tar.gz) set(UNBOUND_SOURCE unbound-${UNBOUND_VERSION}.tar.gz)
set(UNBOUND_HASH SHA256=a480dc6c8937447b98d161fe911ffc76cfaffa2da18788781314e81339f1126f set(UNBOUND_HASH SHA512=0ea65ea63265be677441bd2a28df12098ec5e86c3372240c2874f9bd13752b8b818da81ae6076cf02cbeba3d36e397698a4c2b50570be1a6a8e47f57a0251572
CACHE STRING "unbound source hash") CACHE STRING "unbound source hash")
set(SQLITE3_VERSION 3380500 CACHE STRING "sqlite3 version") 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_SOURCE zlib-${ZLIB_VERSION}.tar.gz)
set(ZLIB_HASH SHA256=91844808532e5ce316b3c010929493c0244f3d37593afd6de04f71821d5136d9 set(ZLIB_HASH SHA256=91844808532e5ce316b3c010929493c0244f3d37593afd6de04f71821d5136d9
CACHE STRING "zlib source hash") CACHE STRING "zlib source hash")
set(CURL_VERSION 7.83.1 CACHE STRING "curl version") set(CURL_VERSION 7.83.1 CACHE STRING "curl version")
set(CURL_MIRROR ${LOCAL_MIRROR} https://curl.haxx.se/download https://curl.askapache.com set(CURL_MIRROR ${LOCAL_MIRROR} https://curl.haxx.se/download https://curl.askapache.com
CACHE STRING "curl mirror(s)") CACHE STRING "curl mirror(s)")
@ -125,21 +125,21 @@ if(ANDROID)
set(android_toolchain_prefix x86_64) set(android_toolchain_prefix x86_64)
set(android_toolchain_suffix linux-android) set(android_toolchain_suffix linux-android)
elseif(CMAKE_ANDROID_ARCH_ABI MATCHES x86) elseif(CMAKE_ANDROID_ARCH_ABI MATCHES x86)
set(android_machine i686) set(android_machine x86)
set(cross_host "--host=i686-linux-android") set(cross_host "--host=i686-linux-android")
set(android_compiler_prefix i686) set(android_compiler_prefix i686)
set(android_compiler_suffix linux-android23) set(android_compiler_suffix linux-android23)
set(android_toolchain_prefix i686) set(android_toolchain_prefix i686)
set(android_toolchain_suffix linux-android) set(android_toolchain_suffix linux-android)
elseif(CMAKE_ANDROID_ARCH_ABI MATCHES armeabi-v7a) elseif(CMAKE_ANDROID_ARCH_ABI MATCHES armeabi-v7a)
set(android_machine armv7) set(android_machine arm)
set(cross_host "--host=armv7a-linux-androideabi") set(cross_host "--host=armv7a-linux-androideabi")
set(android_compiler_prefix armv7a) set(android_compiler_prefix armv7a)
set(android_compiler_suffix linux-androideabi23) set(android_compiler_suffix linux-androideabi23)
set(android_toolchain_prefix arm) set(android_toolchain_prefix arm)
set(android_toolchain_suffix linux-androideabi) set(android_toolchain_suffix linux-androideabi)
elseif(CMAKE_ANDROID_ARCH_ABI MATCHES arm64-v8a) elseif(CMAKE_ANDROID_ARCH_ABI MATCHES arm64-v8a)
set(android_machine aarch64) set(android_machine arm64)
set(cross_host "--host=aarch64-linux-android") set(cross_host "--host=aarch64-linux-android")
set(android_compiler_prefix aarch64) set(android_compiler_prefix aarch64)
set(android_compiler_suffix linux-android23) set(android_compiler_suffix linux-android23)
@ -223,7 +223,7 @@ build_external(libuv
add_static_target(libuv libuv_external libuv.a) add_static_target(libuv libuv_external libuv.a)
target_link_libraries(libuv INTERFACE ${CMAKE_DL_LIBS}) target_link_libraries(libuv INTERFACE ${CMAKE_DL_LIBS})
build_external(zlib build_external(zlib
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS} -fPIC" ${cross_extra} ./configure --prefix=${DEPS_DESTDIR} --static CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS} -fPIC" ${cross_extra} ./configure --prefix=${DEPS_DESTDIR} --static
BUILD_BYPRODUCTS BUILD_BYPRODUCTS
@ -234,43 +234,51 @@ add_static_target(zlib zlib_external libz.a)
set(openssl_system_env "") set(openssl_system_env "")
set(openssl_arch "")
set(openssl_configure_command ./config) set(openssl_configure_command ./config)
set(openssl_flags "CFLAGS=${deps_CFLAGS}")
if(CMAKE_CROSSCOMPILING) if(CMAKE_CROSSCOMPILING)
if(ARCH_TRIPLET STREQUAL x86_64-w64-mingw32) 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) 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) 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) set(openssl_extra_opts no-asm)
elseif(ARCH_TRIPLET STREQUAL mips64-linux-gnuabi64) elseif(ARCH_TRIPLET STREQUAL mips64-linux-gnuabi64)
set(openssl_system_env SYSTEM=Linux MACHINE=mips64) set(openssl_arch linux-mips64)
set(openssl_configure_command ./Configure linux64-mips64)
elseif(ARCH_TRIPLET STREQUAL mips-linux-gnu) 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) 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) elseif(ARCH_TRIPLET STREQUAL aarch64-linux-gnu)
# cross compile arm64 # cross compile arm64
set(openssl_system_env SYSTEM=Linux MACHINE=aarch64) set(openssl_arch linux-aarch64)
elseif(ARCH_TRIPLET MATCHES arm-linux) elseif(ARCH_TRIPLET MATCHES arm-linux)
# cross compile armhf # cross compile armhf
set(openssl_system_env SYSTEM=Linux MACHINE=armv4) set(openssl_arch linux-armv4)
elseif(ARCH_TRIPLET MATCHES powerpc64le) elseif(ARCH_TRIPLET MATCHES powerpc64le)
# cross compile ppc64le # cross compile ppc64le
set(openssl_system_env SYSTEM=Linux MACHINE=ppc64le) set(openssl_arch linux-ppc64le)
endif() endif()
elseif(CMAKE_C_FLAGS MATCHES "-march=armv7") elseif(CMAKE_C_FLAGS MATCHES "-march=armv7")
# Help openssl figure out that we're building from armv7 even if on armv8 hardware: # 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() endif()
build_external(openssl build_external(openssl
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env CC=${deps_cc} ${openssl_system_env} ${openssl_configure_command} 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 --prefix=${DEPS_DESTDIR} --libdir=lib ${openssl_extra_opts}
no-heartbeats no-md2 no-rc5 no-rdrand no-rfc3779 no-sctp no-ssl-trace no-ssl2 no-ssl3 no-shared no-capieng no-dso no-dtls1 no-ec_nistp_64_gcc_128 no-gost
no-static-engine no-tests no-weak-ssl-ciphers no-zlib no-zlib-dynamic "CFLAGS=${deps_CFLAGS}" 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 INSTALL_COMMAND ${_make} install_sw
BUILD_BYPRODUCTS BUILD_BYPRODUCTS
${DEPS_DESTDIR}/lib/libssl.a ${DEPS_DESTDIR}/lib/libcrypto.a ${DEPS_DESTDIR}/lib/libssl.a ${DEPS_DESTDIR}/lib/libcrypto.a
@ -284,7 +292,6 @@ endif()
set(OPENSSL_INCLUDE_DIR ${DEPS_DESTDIR}/include) set(OPENSSL_INCLUDE_DIR ${DEPS_DESTDIR}/include)
set(OPENSSL_CRYPTO_LIBRARY ${DEPS_DESTDIR}/lib/libcrypto.a ${DEPS_DESTDIR}/lib/libssl.a) 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}) set(OPENSSL_ROOT_DIR ${DEPS_DESTDIR})
build_external(expat build_external(expat

@ -2,6 +2,9 @@
include(CheckIPOSupported) include(CheckIPOSupported)
option(WITH_LTO "enable lto on compile time" ON) option(WITH_LTO "enable lto on compile time" ON)
if(WITH_LTO) 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) check_ipo_supported(RESULT IPO_ENABLED OUTPUT ipo_error)
if(IPO_ENABLED) if(IPO_ENABLED)
message(STATUS "LTO enabled") message(STATUS "LTO enabled")

@ -1,4 +1,3 @@
set(default_build_gui OFF) set(default_build_gui OFF)
set(default_gui_target pack) set(default_gui_target pack)
if(APPLE) if(APPLE)
@ -9,24 +8,30 @@ elseif(WIN32)
set(default_gui_target win32) set(default_gui_target win32)
endif() 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}) 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_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") set(GUI_YARN_EXTRA_OPTS "" CACHE STRING "extra options to pass into the yarn build command")
if (BUILD_GUI) if (BUILD_GUI)
message(STATUS "Building lokinet-gui") message(STATUS "Building lokinet-gui")
# allow manually specifying yarn with -DYARN=
find_program(YARN NAMES yarn yarnpkg REQUIRED) if(NOT YARN)
message(STATUS "Building lokinet-gui with yarn ${YARN}, target ${GUI_YARN_TARGET}") find_program(YARN NAMES yarnpkg yarn REQUIRED)
set(wine_env)
if(WIN32)
set(wine_env WINEDEBUG=-all "WINEPREFIX=${PROJECT_BINARY_DIR}/wineprefix")
endif() endif()
message(STATUS "Building lokinet-gui with yarn ${YARN}, target ${GUI_YARN_TARGET}")
add_custom_target(lokinet-gui
COMMAND ${YARN} install --frozen-lockfile && if(NOT WIN32)
${wine_env} ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET} add_custom_target(lokinet-gui
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui") COMMAND ${YARN} install --frozen-lockfile &&
${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET}
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui")
endif()
if(APPLE) if(APPLE)
add_custom_target(assemble_gui ALL 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 "${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 cp "${PROJECT_SOURCE_DIR}/contrib/macos/InfoPlist.strings" "${lokinet_app}/Contents/Helpers/Lokinet-GUI.app/Contents/Resources/en.lproj/"
COMMAND /usr/libexec/PlistBuddy COMMAND /usr/libexec/PlistBuddy
-c "Delete :CFBundleDisplayName" -c "Delete :CFBundleDisplayName"
-c "Add :LSHasLocalizedDisplayName bool true" -c "Add :LSHasLocalizedDisplayName bool true"
-c "Add :CFBundleDevelopmentRegion string en" -c "Add :CFBundleDevelopmentRegion string en"
-c "Set :CFBundleShortVersionString ${lokinet_VERSION}" -c "Set :CFBundleShortVersionString ${lokinet_VERSION}"
-c "Set :CFBundleVersion ${lokinet_VERSION}.${LOKINET_APPLE_BUILD}" -c "Set :CFBundleVersion ${lokinet_VERSION}.${LOKINET_APPLE_BUILD}"
"${lokinet_app}/Contents/Helpers/Lokinet-GUI.app/Contents/Info.plist" "${lokinet_app}/Contents/Helpers/Lokinet-GUI.app/Contents/Info.plist"
) )
elseif(WIN32) elseif(WIN32)
file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gui") file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gui")
add_custom_target(copy_gui ALL option(GUI_ZIP_FILE "custom lokinet gui for windows from zip file" OFF)
DEPENDS lokinet lokinet-gui if(GUI_ZIP_FILE)
# FIXME: we really shouldn't be building inside the source directory but this is npm... message(STATUS "using custom lokinet gui from ${GUI_ZIP_FILE}")
COMMAND ${CMAKE_COMMAND} -E copy_if_different 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_SOURCE_DIR}/gui/release/Lokinet-GUI_portable.exe"
"${PROJECT_BINARY_DIR}/gui/lokinet-gui.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() else()
message(FATAL_ERROR "Building/bundling the GUI from this repository is not supported on this platform") message(FATAL_ERROR "Building/bundling the GUI from this repository is not supported on this platform")
endif() endif()
else() 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() endif()

@ -5,11 +5,42 @@ set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
if(WIN32) if(WIN32)
include(cmake/win32_installer_deps.cmake) 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) elseif(APPLE)
set(CPACK_GENERATOR DragNDrop;ZIP) set(CPACK_GENERATOR DragNDrop;ZIP)
get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS)
list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified")
endif() endif()
# This must always be last!
include(CPack) 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) if(NOT WIN32)
return() return()
endif() endif()
if (NOT STATIC_LINK)
message(FATAL_ERROR "windows requires static builds (thanks balmer)")
endif()
enable_language(RC) enable_language(RC)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) option(WITH_WINDOWS_32 "build 32 bit windows" OFF)
if(NOT MSVC_VERSION) # unlike unix where you get a *single* compiler ID string in .comment
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-bad-function-cast>) # GNU ld sees fit to merge *all* the .ident sections in object files
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-cast-function-type>) # to .r[o]data section one after the other!
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-fpermissive>) add_compile_options(-fno-ident -Wa,-mbig-obj)
# unlike unix where you get a *single* compiler ID string in .comment # the minimum windows version, set to 6 rn because supporting older windows is hell
# GNU ld sees fit to merge *all* the .ident sections in object files set(_winver 0x0600)
# to .r[o]data section one after the other! add_definitions(-D_WIN32_WINNT=${_winver})
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()
if(EMBEDDED_CFG) if(EMBEDDED_CFG)
link_libatomic() link_libatomic()
endif() 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) set(WINDIVERT_VERSION 2.2.0-A CACHE STRING "windivert version")
message("must ship compiler runtime libraries with this build: libwinpthread-1.dll, libgcc_s_dw2-1.dll, and libstdc++-6.dll") set(WINDIVERT_MIRROR https://reqrypt.org/download
message("for release builds, turn on STATIC_LINK in cmake options") CACHE STRING "windivert mirror(s)")
endif() 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 BUILD_GUI)
if(NOT GUI_ZIP_URL) if(NOT GUI_ZIP_URL)
set(GUI_ZIP_URL "https://oxen.rocks/oxen-io/lokinet-gui/dev/lokinet-windows-x64-20220331T180338Z-569f90ad8.zip") set(GUI_ZIP_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!") message(FATAL_ERROR "Downloaded gui archive from ${GUI_ZIP_URL} does not contain gui/lokinet-gui.exe!")
endif() endif()
endif() endif()
install(DIRECTORY ${CMAKE_BINARY_DIR}/gui DESTINATION share COMPONENT gui) 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) install(FILES ${BOOTSTRAP_FILE} DESTINATION share COMPONENT lokinet RENAME bootstrap.signed)
set(CPACK_PACKAGE_INSTALL_DIRECTORY "Lokinet") 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_DEFINES "RequestExecutionLevel admin")
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
function(read_nsis_file filename outvar) function(read_nsis_file filename outvar)
file(STRINGS "${filename}" _outvar) file(STRINGS "${filename}" _outvar)
list(TRANSFORM _outvar REPLACE "\\\\" "\\\\\\\\") 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_PREINSTALL_COMMANDS "${_extra_preinstall}")
set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${_extra_install}") 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_CREATE_ICONS_EXTRA "${_extra_create_icons}")
set(CPACK_NSIS_DELETE_ICONS_EXTRA "${_extra_delete_icons}") set(CPACK_NSIS_DELETE_ICONS_EXTRA "${_extra_delete_icons}")
get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) set(CPACK_NSIS_COMPRESSOR "/SOLID lzma")
list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified")

@ -26,6 +26,7 @@ for abi in $build_abis; do
-DANDROID_ABI=$abi \ -DANDROID_ABI=$abi \
-DANDROID_ARM_MODE=arm \ -DANDROID_ARM_MODE=arm \
-DANDROID_PLATFORM=android-23 \ -DANDROID_PLATFORM=android-23 \
-DANDROID_API=23 \
-DANDROID_STL=c++_static \ -DANDROID_STL=c++_static \
-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \ -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
-DBUILD_STATIC_DEPS=ON \ -DBUILD_STATIC_DEPS=ON \
@ -34,6 +35,7 @@ for abi in $build_abis; do
-DBUILD_TESTING=OFF \ -DBUILD_TESTING=OFF \
-DBUILD_LIBLOKINET=OFF \ -DBUILD_LIBLOKINET=OFF \
-DWITH_TESTS=OFF \ -DWITH_TESTS=OFF \
-DWITH_BOOTSTRAP=OFF \
-DNATIVE_BUILD=OFF \ -DNATIVE_BUILD=OFF \
-DSTATIC_LINK=ON \ -DSTATIC_LINK=ON \
-DWITH_SYSTEMD=OFF \ -DWITH_SYSTEMD=OFF \
@ -45,7 +47,7 @@ for abi in $build_abis; do
-DSUBMODULE_CHECK=OFF \ -DSUBMODULE_CHECK=OFF \
-DWITH_LTO=OFF \ -DWITH_LTO=OFF \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
$@ "$@"
cd - cd -
done done
rm -f $build/Makefile rm -f $build/Makefile

@ -34,8 +34,11 @@ else
fi fi
mkdir -v "$base" mkdir -v "$base"
if [ -e build-windows ]; then if [ -e build/win32 ]; then
cp -av build-windows/lokinet-*.exe "$base" # 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 # zipit up yo
archive="$base.zip" archive="$base.zip"
zip -r "$archive" "$base" 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" cp -av build-docs/docs/mkdocs.yml build-docs/docs/markdown "$base"
tar cJvf "$archive" "$base" tar cJvf "$archive" "$base"
elif [ -e build-mac ]; then elif [ -e build-mac ]; then
archive="$base.dmg" archive="$base.tar.xz"
mv build-mac/Lokinet*.dmg "$archive" mv build-mac/Lokinet*/ "$base"
tar cJvf "$archive" "$base"
else else
cp -av daemon/lokinet daemon/lokinet-vpn "$base" cp -av daemon/lokinet daemon/lokinet-vpn "$base"
cp -av ../contrib/bootstrap/mainnet.signed "$base/bootstrap.signed" 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 # -mkdir a/, -mkdir a/b/, -mkdir a/b/c/, ... commands. The leading `-` allows the command to fail
# without error. # without error.
upload_dirs=(${upload_to//\// }) upload_dirs=(${upload_to//\// })
put_debug=
mkdirs= mkdirs=
dir_tmp="" dir_tmp=""
for p in "${upload_dirs[@]}"; do for p in "${upload_dirs[@]}"; do
@ -71,10 +76,13 @@ for p in "${upload_dirs[@]}"; do
mkdirs="$mkdirs mkdirs="$mkdirs
-mkdir $dir_tmp" -mkdir $dir_tmp"
done 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 sftp -i ssh_key -b - -o StrictHostKeyChecking=off drone@oxen.rocks <<SFTP
$mkdirs $mkdirs
put $archive $upload_to put $archive $upload_to
$put_debug
SFTP SFTP
set +o xtrace 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)/../" cd "$(dirname $0)/../"
if [ "$1" = "verify" ] ; then 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 exit 2
fi fi
else 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 fi
swift_format=$(command -v swiftformat 2>/dev/null) swift_format=$(command -v swiftformat 2>/dev/null)
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
if [ "$1" = "verify" ] ; 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 if [ $($swift_format --quiet --dryrun < "$f" | diff "$f" - | wc -l) -ne 0 ] ; then
exit 3 exit 3
fi fi
done done
else else
$swift_format --quiet $(find daemon | grep -E '\.swift$' | grep -v '\#') $swift_format --quiet $(find daemon | grep -E '\.swift$' | grep -v '#')
fi fi
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 set -x
if ! [ -f LICENSE ] || ! [ -d llarp ]; then 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 fi
mkdir -p build-mac ./contrib/mac-configure.sh "$@"
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
cd build-mac
ninja -j${JOBS:-1} package
cd .. cd ..
echo -e "Build complete, your app is here:\n" echo -e "Build complete, your app is here:\n"

@ -2,12 +2,25 @@
set -e set -e
set -x set -x
root=$(readlink -f "$1") # Usage: windows-configure.sh [rootdir [builddir]] -DWHATEVER=BLAH ...
shift
mkdir -p "$1" if [ $# -ge 1 ] && [[ "$1" != -* ]]; then
build=$(readlink -f "$1") root="$1"
shift shift
cd "$build" 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 \ cmake \
-S "$root" -B "$build" \ -S "$root" -B "$build" \
-G 'Unix Makefiles' \ -G 'Unix Makefiles' \
@ -21,6 +34,7 @@ cmake \
-DBUILD_TESTING=OFF \ -DBUILD_TESTING=OFF \
-DBUILD_LIBLOKINET=OFF \ -DBUILD_LIBLOKINET=OFF \
-DWITH_TESTS=OFF \ -DWITH_TESTS=OFF \
-DWITH_BOOTSTRAP=OFF \
-DNATIVE_BUILD=OFF \ -DNATIVE_BUILD=OFF \
-DSTATIC_LINK=ON \ -DSTATIC_LINK=ON \
-DWITH_SYSTEMD=OFF \ -DWITH_SYSTEMD=OFF \
@ -30,4 +44,4 @@ cmake \
-DFORCE_SPDLOG_SUBMODULE=ON \ -DFORCE_SPDLOG_SUBMODULE=ON \
-DFORCE_NLOHMANN_SUBMODULE=ON \ -DFORCE_NLOHMANN_SUBMODULE=ON \
-DWITH_LTO=OFF \ -DWITH_LTO=OFF \
$@ "$@"

@ -6,9 +6,8 @@
set -e set -e
set +x set +x
root="$(readlink -f $(dirname $0)/../)" root="$(readlink -f $(dirname $0)/../)"
cd "$root" mkdir -p $root/build/win32
./contrib/windows-configure.sh . build-windows "$@" $root/contrib/windows-configure.sh $root $root/build/win32 "$@"
make package -j${JOBS:-$(nproc)} -C build-windows make package -j${JOBS:-$(nproc)} -C $root/build/win32

@ -46,15 +46,26 @@ if(WITH_BOOTSTRAP)
endif() endif()
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}) foreach(exe ${exetargets})
if(WIN32 AND NOT MSVC_VERSION) if(WIN32)
target_sources(${exe} PRIVATE ${CMAKE_BINARY_DIR}/${exe}.rc) 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 -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") elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
target_link_directories(${exe} PRIVATE /usr/local/lib) target_link_directories(${exe} PRIVATE /usr/local/lib)
endif() 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}") target_include_directories(${exe} PUBLIC "${PROJECT_SOURCE_DIR}")
if(should_install) if(should_install)
if(APPLE) if(APPLE)
@ -71,3 +82,9 @@ endforeach()
if(SETCAP) if(SETCAP)
install(CODE "execute_process(COMMAND ${SETCAP} cap_net_admin,cap_net_bind_service=+eip ${CMAKE_INSTALL_PREFIX}/bin/lokinet)") install(CODE "execute_process(COMMAND ${SETCAP} cap_net_admin,cap_net_bind_service=+eip ${CMAKE_INSTALL_PREFIX}/bin/lokinet)")
endif() 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 // Create the service
schService = CreateService( schService = CreateService(
schSCManager, // SCM database schSCManager, // SCM database
"lokinet", // name of service strdup("lokinet"), // name of service
"Lokinet for Windows", // service name to display "Lokinet for Windows", // service name to display
SERVICE_ALL_ACCESS, // desired access SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type SERVICE_WIN32_OWN_PROCESS, // service type
@ -134,10 +134,10 @@ insert_description()
SC_HANDLE schSCManager; SC_HANDLE schSCManager;
SC_HANDLE schService; SC_HANDLE schService;
SERVICE_DESCRIPTION sd; SERVICE_DESCRIPTION sd;
LPTSTR szDesc = LPTSTR szDesc = strdup(
"LokiNET is a free, open source, private, " "LokiNET is a free, open source, private, "
"decentralized, \"market based sybil resistant\" " "decentralized, \"market based sybil resistant\" "
"and IP based onion routing network"; "and IP based onion routing network");
// Get a handle to the SCM database. // Get a handle to the SCM database.
schSCManager = OpenSCManager( schSCManager = OpenSCManager(
NULL, // local computer NULL, // local computer
@ -270,7 +270,7 @@ run_main_context(std::optional<fs::path> confFile, const llarp::RuntimeOptions o
} }
catch (std::exception& ex) 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); exit_code.set_value(1);
return; return;
} }
@ -329,8 +329,9 @@ class WindowsServiceStopped
LONG LONG
GenerateDump(EXCEPTION_POINTERS* pExceptionPointers) GenerateDump(EXCEPTION_POINTERS* pExceptionPointers)
{ {
const DWORD flags = MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData const auto flags = (MINIDUMP_TYPE)(
| MiniDumpWithUnloadedModules | MiniDumpWithThreadInfo; MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData
| MiniDumpWithUnloadedModules | MiniDumpWithThreadInfo);
std::stringstream ss; std::stringstream ss;
ss << "C:\\ProgramData\\lokinet\\crash-" << llarp::time_now_ms().count() << ".dmp"; 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); return lokinet_main(argc, argv);
#else #else
SERVICE_TABLE_ENTRY DispatchTable[] = { 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) if (lstrcmpi(argv[1], "--win32-daemon") == 0)
{ {
start_as_daemon = true; start_as_daemon = true;
@ -680,7 +681,7 @@ win32_daemon_entry(DWORD argc, LPTSTR* argv)
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000); ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
// SCM clobbers startup args, regenerate them here // SCM clobbers startup args, regenerate them here
argc = 2; argc = 2;
argv[1] = "c:/programdata/lokinet/lokinet.ini"; argv[1] = strdup("c:\\programdata\\lokinet\\lokinet.ini");
argv[2] = nullptr; argv[2] = nullptr;
lokinet_main(argc, argv); 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/constants`: contains all compile time constants
* `/llarp/crypto`: cryptography interface and implementation, includes various secure helpers * `/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/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/ev`: event loop interfaces and implementations
* `/llarp/exit`: `.snode` endpoint "backend" * `/llarp/exit`: `.snode` endpoint "backend"
* `/llarp/handlers`: packet endpoint "frontends" * `/llarp/handlers`: packet endpoint "frontends"

@ -2,4 +2,4 @@ add_library(lokinet-android
SHARED SHARED
lokinet_config.cpp lokinet_config.cpp
lokinet_daemon.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 JNIEXPORT jint JNICALL
Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv* env, jobject self) Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv* env, jobject self)
{ {
auto ptr = GetImpl<llarp::Context>(env, self); if (auto ptr = GetImpl<llarp::Context>(env, self); ptr and ptr->router)
if (const auto& router = ptr->router; ptr and ptr->router) return ptr->router->OutboundUDPSocket();
return router->OutboundUDPSocket();
return -1; return -1;
} }

@ -8,7 +8,7 @@ add_library(lokinet-util
util/fs.cpp util/fs.cpp
util/json.cpp util/json.cpp
util/logging/buffer.cpp util/logging/buffer.cpp
util/lokinet_init.c util/easter_eggs.cpp
util/mem.cpp util/mem.cpp
util/str.cpp util/str.cpp
util/thread/queue_manager.cpp util/thread/queue_manager.cpp
@ -32,12 +32,11 @@ add_library(lokinet-platform
STATIC STATIC
# for networking # for networking
ev/ev.cpp ev/ev.cpp
ev/ev_libuv.cpp ev/libuv.cpp
net/ip.cpp net/ip.cpp
net/ip_address.cpp net/ip_address.cpp
net/ip_packet.cpp net/ip_packet.cpp
net/ip_range.cpp net/ip_range.cpp
net/net.cpp
net/net_int.cpp net/net_int.cpp
net/sock_addr.cpp net/sock_addr.cpp
vpn/packet_router.cpp vpn/packet_router.cpp
@ -53,41 +52,72 @@ if (ANDROID)
endif() endif()
if(CMAKE_SYSTEM_NAME MATCHES "Linux") if(CMAKE_SYSTEM_NAME MATCHES "Linux")
if(NON_PC_TARGET) target_sources(lokinet-platform PRIVATE linux/dbus.cpp)
add_import_library(rt)
target_link_libraries(lokinet-platform PUBLIC rt)
endif()
endif() endif()
if (WIN32) if (WIN32)
target_sources(lokinet-platform PRIVATE target_sources(lokinet-platform PRIVATE
win32/win32_inet.c net/win32.cpp
win32/win32_intrnl.c) vpn/win32.cpp
win32/exec.cpp)
target_link_libraries(lokinet-platform PUBLIC iphlpapi) 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() endif()
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
target_include_directories(lokinet-platform SYSTEM PUBLIC /usr/local/include) target_include_directories(lokinet-platform SYSTEM PUBLIC /usr/local/include)
endif() endif()
add_library(liblokinet add_library(lokinet-dns
STATIC STATIC
config/config.cpp
config/definition.cpp
config/ini.cpp
config/key_manager.cpp
dns/message.cpp dns/message.cpp
dns/name.cpp dns/name.cpp
dns/platform.cpp
dns/question.cpp dns/question.cpp
dns/rr.cpp dns/rr.cpp
dns/serialize.cpp dns/serialize.cpp
dns/server.cpp dns/server.cpp
dns/srv_data.cpp dns/srv_data.cpp)
dns/unbound_resolver.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 consensus/reachability_testing.cpp
bootstrap.cpp bootstrap.cpp
@ -116,7 +146,7 @@ add_library(liblokinet
dht/taglookup.cpp dht/taglookup.cpp
endpoint_base.cpp endpoint_base.cpp
exit/context.cpp exit/context.cpp
exit/endpoint.cpp exit/endpoint.cpp
exit/exit_messages.cpp exit/exit_messages.cpp
@ -151,7 +181,7 @@ add_library(liblokinet
peerstats/types.cpp peerstats/types.cpp
pow.cpp pow.cpp
profiling.cpp profiling.cpp
quic/address.cpp quic/address.cpp
quic/client.cpp quic/client.cpp
quic/connection.cpp quic/connection.cpp
@ -171,7 +201,7 @@ add_library(liblokinet
router/rc_gossiper.cpp router/rc_gossiper.cpp
router/router.cpp router/router.cpp
router/route_poker.cpp router/route_poker.cpp
router/systemd_resolved.cpp
routing/dht_message.cpp routing/dht_message.cpp
routing/message_parser.cpp routing/message_parser.cpp
routing/path_confirm_message.cpp routing/path_confirm_message.cpp
@ -205,42 +235,42 @@ add_library(liblokinet
service/tag.cpp 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(WITH_PEERSTATS_BACKEND)
target_compile_definitions(lokinet-amalgum PRIVATE -DLOKINET_PEERSTATS_BACKEND)
if(TRACY_ROOT) target_link_libraries(lokinet-amalgum PUBLIC sqlite_orm)
target_sources(liblokinet PRIVATE ${TRACY_ROOT}/TracyClient.cpp)
endif() endif()
if(WITH_HIVE) if(WITH_HIVE)
target_sources(liblokinet PRIVATE target_sources(lokinet-amalgum PRIVATE
tooling/router_hive.cpp tooling/router_hive.cpp
tooling/hive_router.cpp tooling/hive_router.cpp
tooling/hive_context.cpp tooling/hive_context.cpp
) )
endif() 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 cxxopts
oxenc::oxenc oxenc::oxenc
lokinet-platform lokinet-platform
lokinet-config
lokinet-dns
lokinet-util lokinet-util
lokinet-cryptography lokinet-cryptography
ngtcp2_static ngtcp2_static
oxenmq::oxenmq) 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) pkg_check_modules(CRYPT libcrypt IMPORTED_TARGET)
if(CRYPT_FOUND AND NOT CMAKE_CROSSCOMPILING) if(CRYPT_FOUND AND NOT CMAKE_CROSSCOMPILING)
add_definitions(-DHAVE_CRYPT) add_definitions(-DHAVE_CRYPT)
add_library(libcrypt INTERFACE) add_library(libcrypt INTERFACE)
target_link_libraries(libcrypt INTERFACE PkgConfig::CRYPT) 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}") message(STATUS "using libcrypt ${CRYPT_VERSION}")
endif() endif()
@ -248,7 +278,7 @@ endif()
if(BUILD_LIBLOKINET) if(BUILD_LIBLOKINET)
include(GNUInstallDirs) include(GNUInstallDirs)
add_library(lokinet-shared SHARED lokinet_shared.cpp) 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) if(WIN32)
set(CMAKE_SHARED_LIBRARY_PREFIX_CXX "") set(CMAKE_SHARED_LIBRARY_PREFIX_CXX "")
endif() endif()

@ -18,12 +18,9 @@ target_sources(lokinet-platform PRIVATE vpn_platform.cpp vpn_interface.cpp route
add_executable(lokinet-extension MACOSX_BUNDLE add_executable(lokinet-extension MACOSX_BUNDLE
PacketTunnelProvider.m PacketTunnelProvider.m
DNSTrampoline.m DNSTrampoline.m
) )
enable_lto(lokinet-extension) enable_lto(lokinet-extension)
target_link_libraries(lokinet-extension PRIVATE
liblokinet
${COREFOUNDATION}
${NETEXT})
# -fobjc-arc enables automatic reference counting for objective-C code # -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. # -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) set(product_type com.apple.product-type.app-extension)
endif() endif()
target_link_libraries(lokinet-extension PRIVATE
lokinet-amalgum
${COREFOUNDATION}
${NETEXT})
set_target_properties(lokinet-extension PROPERTIES set_target_properties(lokinet-extension PROPERTIES
BUNDLE TRUE BUNDLE TRUE
BUNDLE_EXTENSION ${bundle_ext} BUNDLE_EXTENSION ${bundle_ext}

@ -3,23 +3,32 @@
NSString* error_domain = @"org.lokinet"; NSString* error_domain = @"org.lokinet";
// Receiving an incoming packet, presumably from libunbound. NB: this is called from the libuv // Receiving an incoming packet, presumably from libunbound. NB: this is called from the libuv
// event loop. // event loop.
static void on_request(uv_udp_t* socket, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) { static void
if (nread < 0) { 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)); NSLog(@"Read error: %s", uv_strerror(nread));
free(buf->base); free(buf->base);
return; return;
} }
if (nread == 0 || !addr) { if (nread == 0 || !addr)
{
if (buf) if (buf)
free(buf->base); free(buf->base);
return; 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 // 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). // 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]; [t flushWrites];
} }
static void on_sent(uv_udp_send_t* req, int status) { static void
NSArray<NSData*>* datagrams = (__bridge_transfer NSArray<NSData*>*) req->data; on_sent(uv_udp_send_t* req, int status)
{
(void)status;
NSArray<NSData*>* datagrams = (__bridge_transfer NSArray<NSData*>*)req->data;
(void)datagrams;
free(req); free(req);
} }
// NB: called from the libuv event loop (so we don't have to worry about the above and this one // 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). // running at once from different threads).
static void write_flusher(uv_async_t* async) { static void
LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*) async->data; write_flusher(uv_async_t* async)
{
LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)async->data;
if (t->pending_writes.count == 0) if (t->pending_writes.count == 0)
return; return;
NSArray<NSData*>* data = [NSArray<NSData*> arrayWithArray:t->pending_writes]; NSArray<NSData*>* data = [NSArray<NSData*> arrayWithArray:t->pending_writes];
[t->pending_writes removeAllObjects]; [t->pending_writes removeAllObjects];
__weak LLARPDNSTrampoline* weakSelf = t; __weak LLARPDNSTrampoline* weakSelf = t;
[t->upstream writeMultipleDatagrams:data completionHandler: ^(NSError* error) [t->upstream writeMultipleDatagrams:data
{ completionHandler:^(NSError* error) {
if (error) if (error)
NSLog(@"Failed to send request to upstream DNS: %@", 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 // Trigger another flush in case anything built up while Apple was doing its
// call it unconditionally (rather than checking the queue) because this handler is probably // things. Just call it unconditionally (rather than checking the queue)
// running in some other thread. // because this handler is probably running in some other thread.
[weakSelf flushWrites]; [weakSelf flushWrites];
} }];
];
} }
static void
static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
{
(void)handle;
buf->base = malloc(suggested_size); buf->base = malloc(suggested_size);
buf->len = suggested_size; buf->len = suggested_size;
} }
@implementation LLARPDNSTrampoline @implementation LLARPDNSTrampoline
- (void)startWithUpstreamDns:(NWUDPSession*) dns - (void)startWithUpstreamDns:(NWUDPSession*)dns
listenIp:(NSString*) listenIp listenIp:(NSString*)listenIp
listenPort:(uint16_t) listenPort listenPort:(uint16_t)listenPort
uvLoop:(uv_loop_t*) loop uvLoop:(uv_loop_t*)loop
completionHandler:(void (^)(NSError* error))completionHandler completionHandler:(void (^)(NSError* error))completionHandler
{ {
NSLog(@"Setting up trampoline"); NSLog(@"Setting up trampoline");
pending_writes = [[NSMutableArray<NSData*> alloc] init]; 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); 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); uv_udp_init(loop, &request_socket);
struct sockaddr_in recv_addr; struct sockaddr_in recv_addr;
uv_ip4_addr(listenIp.UTF8String, listenPort, &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); int ret = uv_udp_bind(&request_socket, (const struct sockaddr*)&recv_addr, UV_UDP_REUSEADDR);
if (ret < 0) { 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}]; 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); NSLog(@"%@", err);
return completionHandler(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; upstream = dns;
__weak LLARPDNSTrampoline* weakSelf = self; __weak LLARPDNSTrampoline* weakSelf = self;
[upstream setReadHandler: ^(NSArray<NSData*>* datagrams, NSError* error) { [upstream
// Reading a reply back from the UDP socket used to talk to upstream setReadHandler:^(NSArray<NSData*>* datagrams, NSError* error) {
if (error) { // Reading a reply back from the UDP socket used to talk to upstream
NSLog(@"Reader handler failed: %@", error); if (error)
return; {
} NSLog(@"Reader handler failed: %@", error);
LLARPDNSTrampoline* strongSelf = weakSelf; return;
if (!strongSelf || datagrams.count == 0) }
return; LLARPDNSTrampoline* strongSelf = weakSelf;
if (!strongSelf || datagrams.count == 0)
uv_buf_t* buffers = malloc(datagrams.count * sizeof(uv_buf_t)); return;
size_t buf_count = 0;
for (NSData* packet in datagrams) { uv_buf_t* buffers = malloc(datagrams.count * sizeof(uv_buf_t));
buffers[buf_count].base = (void*) packet.bytes; size_t buf_count = 0;
buffers[buf_count].len = packet.length; for (NSData* packet in datagrams)
buf_count++; {
} buffers[buf_count].base = (void*)packet.bytes;
uv_udp_send_t* uvsend = malloc(sizeof(uv_udp_send_t)); buffers[buf_count].len = packet.length;
uvsend->data = (__bridge_retained void*) datagrams; buf_count++;
int ret = uv_udp_send(uvsend, &strongSelf->request_socket, buffers, buf_count, &strongSelf->reply_addr, on_sent); }
free(buffers); uv_udp_send_t* uvsend = malloc(sizeof(uv_udp_send_t));
if (ret < 0) uvsend->data = (__bridge_retained void*)datagrams;
NSLog(@"Error returning DNS responses to unbound: %s", uv_strerror(ret)); int ret = uv_udp_send(
} maxDatagrams:NSUIntegerMax]; 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); 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); uv_async_send(&write_trigger);
} }
- (void) dealloc - (void)dealloc
{ {
NSLog(@"Stopping DNS trampoline"); NSLog(@"Stopping DNS trampoline");
uv_close((uv_handle_t*) &request_socket, NULL); uv_close((uv_handle_t*)&request_socket, NULL);
uv_close((uv_handle_t*) &write_trigger, NULL); uv_close((uv_handle_t*)&write_trigger, NULL);
} }
@end @end

@ -9,9 +9,12 @@
{ {
void* lokinet; void* lokinet;
llarp_incoming_packet packet_buf[LLARP_APPLE_PACKET_BUF_SIZE]; llarp_incoming_packet packet_buf[LLARP_APPLE_PACKET_BUF_SIZE];
@public NEPacketTunnelNetworkSettings* settings; @public
@public NEIPv4Route* tun_route4; NEPacketTunnelNetworkSettings* settings;
@public NEIPv6Route* tun_route6; @public
NEIPv4Route* tun_route4;
@public
NEIPv6Route* tun_route6;
LLARPDNSTrampoline* dns_tramp; LLARPDNSTrampoline* dns_tramp;
} }
@ -30,107 +33,133 @@
@end @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) if (ctx == nil || data == nil)
return; return;
NSData* buf = [NSData dataWithBytesNoCopy:(void*)data length:size freeWhenDone:NO]; NSData* buf = [NSData dataWithBytesNoCopy:(void*)data length:size freeWhenDone:NO];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
[t.packetFlow writePackets:@[buf] [t.packetFlow writePackets:@[buf] withProtocols:@[[NSNumber numberWithInt:af]]];
withProtocols:@[[NSNumber numberWithInt:af]]];
} }
static void start_packet_reader(void* ctx) { static void
start_packet_reader(void* ctx)
{
if (ctx == nil) if (ctx == nil)
return; return;
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
[t readPackets]; [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); NSLog(@"Adding IPv4 route %s:%s to packet tunnel", addr, netmask);
NEIPv4Route* route = [[NEIPv4Route alloc] NEIPv4Route* route =
initWithDestinationAddress: [NSString stringWithUTF8String:addr] [[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
subnetMask: [NSString stringWithUTF8String:netmask]]; subnetMask:[NSString stringWithUTF8String:netmask]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
for (NEIPv4Route* r in t->settings.IPv4Settings.includedRoutes) for (NEIPv4Route* r in t->settings.IPv4Settings.includedRoutes)
if ([r.destinationAddress isEqualToString:route.destinationAddress] && if ([r.destinationAddress isEqualToString:route.destinationAddress] &&
[r.destinationSubnetMask isEqualToString:route.destinationSubnetMask]) [r.destinationSubnetMask isEqualToString:route.destinationSubnetMask])
return; // Already in the settings, nothing to add. return; // Already in the settings, nothing to add.
t->settings.IPv4Settings.includedRoutes = t->settings.IPv4Settings.includedRoutes =
[t->settings.IPv4Settings.includedRoutes arrayByAddingObject:route]; [t->settings.IPv4Settings.includedRoutes arrayByAddingObject:route];
[t updateNetworkSettings]; [t updateNetworkSettings];
} }
static void del_ipv4_route(const char* addr, const char* netmask, void* ctx) { static void
del_ipv4_route(const char* addr, const char* netmask, void* ctx)
{
NSLog(@"Removing IPv4 route %s:%s to packet tunnel", addr, netmask); NSLog(@"Removing IPv4 route %s:%s to packet tunnel", addr, netmask);
NEIPv4Route* route = [[NEIPv4Route alloc] NEIPv4Route* route =
initWithDestinationAddress: [NSString stringWithUTF8String:addr] [[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
subnetMask: [NSString stringWithUTF8String:netmask]]; subnetMask:[NSString stringWithUTF8String:netmask]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
NSMutableArray<NEIPv4Route*>* routes = [NSMutableArray arrayWithArray:t->settings.IPv4Settings.includedRoutes]; NSMutableArray<NEIPv4Route*>* routes =
for (int i = 0; i < routes.count; i++) { [NSMutableArray arrayWithArray:t->settings.IPv4Settings.includedRoutes];
for (size_t i = 0; i < routes.count; i++)
{
if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] && if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] &&
[routes[i].destinationSubnetMask isEqualToString:route.destinationSubnetMask]) { [routes[i].destinationSubnetMask isEqualToString:route.destinationSubnetMask])
{
[routes removeObjectAtIndex:i]; [routes removeObjectAtIndex:i];
i--; i--;
} }
} }
if (routes.count != t->settings.IPv4Settings.includedRoutes.count) { if (routes.count != t->settings.IPv4Settings.includedRoutes.count)
{
t->settings.IPv4Settings.includedRoutes = routes; t->settings.IPv4Settings.includedRoutes = routes;
[t updateNetworkSettings]; [t updateNetworkSettings];
} }
} }
static void add_ipv6_route(const char* addr, int prefix, void* ctx) { static void
NEIPv6Route* route = [[NEIPv6Route alloc] add_ipv6_route(const char* addr, int prefix, void* ctx)
initWithDestinationAddress: [NSString stringWithUTF8String:addr] {
networkPrefixLength: [NSNumber numberWithInt:prefix]]; NEIPv6Route* route =
[[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
networkPrefixLength:[NSNumber numberWithInt:prefix]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
for (NEIPv6Route* r in t->settings.IPv6Settings.includedRoutes) for (NEIPv6Route* r in t->settings.IPv6Settings.includedRoutes)
if ([r.destinationAddress isEqualToString:route.destinationAddress] && if ([r.destinationAddress isEqualToString:route.destinationAddress] &&
[r.destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength]) [r.destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength])
return; // Already in the settings, nothing to add. return; // Already in the settings, nothing to add.
t->settings.IPv6Settings.includedRoutes = t->settings.IPv6Settings.includedRoutes =
[t->settings.IPv6Settings.includedRoutes arrayByAddingObject:route]; [t->settings.IPv6Settings.includedRoutes arrayByAddingObject:route];
[t updateNetworkSettings]; [t updateNetworkSettings];
} }
static void del_ipv6_route(const char* addr, int prefix, void* ctx) { static void
NEIPv6Route* route = [[NEIPv6Route alloc] del_ipv6_route(const char* addr, int prefix, void* ctx)
initWithDestinationAddress: [NSString stringWithUTF8String:addr] {
networkPrefixLength: [NSNumber numberWithInt:prefix]]; NEIPv6Route* route =
[[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx; networkPrefixLength:[NSNumber numberWithInt:prefix]];
NSMutableArray<NEIPv6Route*>* routes = [NSMutableArray arrayWithArray:t->settings.IPv6Settings.includedRoutes];
for (int i = 0; i < routes.count; i++) { 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] && if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] &&
[routes[i].destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength]) { [routes[i].destinationNetworkPrefixLength
isEqualToNumber:route.destinationNetworkPrefixLength])
{
[routes removeObjectAtIndex:i]; [routes removeObjectAtIndex:i];
i--; i--;
} }
} }
if (routes.count != t->settings.IPv6Settings.includedRoutes.count) { if (routes.count != t->settings.IPv6Settings.includedRoutes.count)
{
t->settings.IPv6Settings.includedRoutes = routes; t->settings.IPv6Settings.includedRoutes = routes;
[t updateNetworkSettings]; [t updateNetworkSettings];
} }
} }
static void add_default_route(void* ctx) { static void
add_default_route(void* ctx)
{
NSLog(@"Making the tunnel the default route"); 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.IPv4Settings.includedRoutes = @[NEIPv4Route.defaultRoute];
t->settings.IPv6Settings.includedRoutes = @[NEIPv6Route.defaultRoute]; t->settings.IPv6Settings.includedRoutes = @[NEIPv6Route.defaultRoute];
@ -138,9 +167,11 @@ static void add_default_route(void* ctx) {
[t updateNetworkSettings]; [t updateNetworkSettings];
} }
static void del_default_route(void* ctx) { static void
del_default_route(void* ctx)
{
NSLog(@"Removing default route from tunnel"); 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.IPv4Settings.includedRoutes = @[t->tun_route4];
t->settings.IPv6Settings.includedRoutes = @[t->tun_route6]; t->settings.IPv6Settings.includedRoutes = @[t->tun_route6];
@ -152,12 +183,13 @@ static void del_default_route(void* ctx) {
- (void)readPackets - (void)readPackets
{ {
[self.packetFlow readPacketObjectsWithCompletionHandler: ^(NSArray<NEPacket*>* packets) { [self.packetFlow readPacketObjectsWithCompletionHandler:^(NSArray<NEPacket*>* packets) {
if (lokinet == nil) if (lokinet == nil)
return; return;
size_t size = 0; size_t size = 0;
for (NEPacket* p in packets) { for (NEPacket* p in packets)
{
packet_buf[size].bytes = p.data.bytes; packet_buf[size].bytes = p.data.bytes;
packet_buf[size].size = p.data.length; packet_buf[size].size = p.data.length;
size++; size++;
@ -186,19 +218,21 @@ static void del_default_route(void* ctx) {
.ns_logger = nslogger, .ns_logger = nslogger,
.packet_writer = packet_writer, .packet_writer = packet_writer,
.start_reading = start_packet_reader, .start_reading = start_packet_reader,
.route_callbacks = { .route_callbacks =
.add_ipv4_route = add_ipv4_route, {.add_ipv4_route = add_ipv4_route,
.del_ipv4_route = del_ipv4_route, .del_ipv4_route = del_ipv4_route,
.add_ipv6_route = add_ipv6_route, .add_ipv6_route = add_ipv6_route,
.del_ipv6_route = del_ipv6_route, .del_ipv6_route = del_ipv6_route,
.add_default_route = add_default_route, .add_default_route = add_default_route,
.del_default_route = del_default_route .del_default_route = del_default_route},
},
}; };
lokinet = llarp_apple_init(&conf); lokinet = llarp_apple_init(&conf);
if (!lokinet) { if (!lokinet)
NSError *init_failure = [NSError errorWithDomain:error_domain code:500 userInfo:@{@"Error": @"Failed to initialize lokinet"}]; {
NSError* init_failure = [NSError errorWithDomain:error_domain
code:500
userInfo:@{@"Error": @"Failed to initialize lokinet"}];
NSLog(@"%@", [init_failure localizedDescription]); NSLog(@"%@", [init_failure localizedDescription]);
return completionHandler(init_failure); return completionHandler(init_failure);
} }
@ -237,11 +271,12 @@ static void del_default_route(void* ctx) {
NWHostEndpoint* upstreamdns_ep; NWHostEndpoint* upstreamdns_ep;
if (strlen(conf.upstream_dns)) 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] NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[ip] subnetMasks:@[mask]];
subnetMasks:@[mask]]; tun_route4 = [[NEIPv4Route alloc] initWithDestinationAddress:ip subnetMask:mask];
tun_route4 = [[NEIPv4Route alloc] initWithDestinationAddress:ip subnetMask: mask];
ipv4.includedRoutes = @[tun_route4]; ipv4.includedRoutes = @[tun_route4];
settings.IPv4Settings = ipv4; settings.IPv4Settings = ipv4;
@ -249,50 +284,62 @@ static void del_default_route(void* ctx) {
NSNumber* ip6_prefix = [NSNumber numberWithUnsignedInt:conf.tunnel_ipv6_prefix]; NSNumber* ip6_prefix = [NSNumber numberWithUnsignedInt:conf.tunnel_ipv6_prefix];
NEIPv6Settings* ipv6 = [[NEIPv6Settings alloc] initWithAddresses:@[ip6] NEIPv6Settings* ipv6 = [[NEIPv6Settings alloc] initWithAddresses:@[ip6]
networkPrefixLengths:@[ip6_prefix]]; networkPrefixLengths:@[ip6_prefix]];
tun_route6 = [[NEIPv6Route alloc] initWithDestinationAddress:ip6 tun_route6 = [[NEIPv6Route alloc] initWithDestinationAddress:ip6 networkPrefixLength:ip6_prefix];
networkPrefixLength:ip6_prefix];
ipv6.includedRoutes = @[tun_route6]; ipv6.includedRoutes = @[tun_route6];
settings.IPv6Settings = ipv6; settings.IPv6Settings = ipv6;
__weak LLARPPacketTunnel* weakSelf = self; __weak LLARPPacketTunnel* weakSelf = self;
[self setTunnelNetworkSettings:settings completionHandler:^(NSError* err) { [self setTunnelNetworkSettings:settings
if (err) { completionHandler:^(NSError* err) {
NSLog(@"Failed to configure lokinet tunnel: %@", err); if (err)
return completionHandler(err); {
} NSLog(@"Failed to configure lokinet tunnel: %@", err);
LLARPPacketTunnel* strongSelf = weakSelf; return completionHandler(err);
if (!strongSelf) }
return completionHandler(nil); LLARPPacketTunnel* strongSelf = weakSelf;
if (!strongSelf)
int start_ret = llarp_apple_start(strongSelf->lokinet, (__bridge void*) strongSelf); return completionHandler(nil);
if (start_ret != 0) {
NSError *start_failure = [NSError errorWithDomain:error_domain code:start_ret userInfo:@{@"Error": @"Failed to start lokinet"}]; int start_ret = llarp_apple_start(strongSelf->lokinet, (__bridge void*)strongSelf);
NSLog(@"%@", start_failure); if (start_ret != 0)
lokinet = nil; {
return completionHandler(start_failure); NSError* start_failure =
} [NSError errorWithDomain:error_domain
code:start_ret
NSString* dns_tramp_ip = @"127.0.0.1"; userInfo:@{@"Error": @"Failed to start lokinet"}];
NSLog(@"Starting DNS exit mode trampoline to %@ on %@:%d", upstreamdns_ep, dns_tramp_ip, dns_trampoline_port); NSLog(@"%@", start_failure);
NWUDPSession* upstreamdns = [strongSelf createUDPSessionThroughTunnelToEndpoint:upstreamdns_ep fromEndpoint:nil]; lokinet = nil;
strongSelf->dns_tramp = [LLARPDNSTrampoline alloc]; return completionHandler(start_failure);
[strongSelf->dns_tramp }
startWithUpstreamDns:upstreamdns
listenIp:dns_tramp_ip NSString* dns_tramp_ip = @"127.0.0.1";
listenPort:dns_trampoline_port NSLog(
uvLoop:llarp_apple_get_uv_loop(strongSelf->lokinet) @"Starting DNS exit mode trampoline to %@ on %@:%d",
completionHandler:^(NSError* error) { upstreamdns_ep,
if (error) dns_tramp_ip,
NSLog(@"Error starting dns trampoline: %@", error); dns_trampoline_port);
return completionHandler(error); 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 - (void)stopTunnelWithReason:(NEProviderStopReason)reason
completionHandler:(void (^)(void))completionHandler completionHandler:(void (^)(void))completionHandler
{ {
if (lokinet) { if (lokinet)
{
llarp_apple_shutdown(lokinet); llarp_apple_shutdown(lokinet);
lokinet = nil; lokinet = nil;
} }
@ -319,29 +366,35 @@ static void del_default_route(void* ctx) {
// //
// Thanks for the accurate documentation, Apple. // Thanks for the accurate documentation, Apple.
// //
[self setTunnelNetworkSettings:nil completionHandler:^(NSError* err) { [self setTunnelNetworkSettings:nil
if (err) completionHandler:^(NSError* err) {
NSLog(@"Failed to clear lokinet tunnel settings: %@", err); if (err)
LLARPPacketTunnel* strongSelf = weakSelf; NSLog(@"Failed to clear lokinet tunnel settings: %@", err);
if (strongSelf) { LLARPPacketTunnel* strongSelf = weakSelf;
[weakSelf setTunnelNetworkSettings:strongSelf->settings completionHandler:^(NSError* err) { if (strongSelf)
LLARPPacketTunnel* strongSelf = weakSelf; {
if (strongSelf) [weakSelf
strongSelf.reasserting = NO; setTunnelNetworkSettings:strongSelf->settings
if (err) completionHandler:^(NSError* err) {
NSLog(@"Failed to reconfigure lokinet tunnel settings: %@", err); LLARPPacketTunnel* strongSelf = weakSelf;
}]; if (strongSelf)
} strongSelf.reasserting = NO;
}]; if (err)
NSLog(@"Failed to reconfigure lokinet tunnel settings: %@", err);
}];
}
}];
} }
@end @end
#ifdef MACOS_SYSTEM_EXTENSION #ifdef MACOS_SYSTEM_EXTENSION
int main() { int
[NEProvider startSystemExtensionMode]; main()
dispatch_main(); {
[NEProvider startSystemExtensionMode];
dispatch_main();
} }
#endif #endif

@ -90,7 +90,7 @@ llarp_apple_init(llarp_apple_config* appleconf)
#ifdef MACOS_SYSTEM_EXTENSION #ifdef MACOS_SYSTEM_EXTENSION
std::strncpy( std::strncpy(
appleconf->dns_bind_ip, appleconf->dns_bind_ip,
config->dns.m_bind.hostString().c_str(), config->dns.m_bind.front().hostString().c_str(),
sizeof(appleconf->dns_bind_ip)); sizeof(appleconf->dns_bind_ip));
#endif #endif

@ -19,7 +19,7 @@ namespace llarp::apple
} }
std::shared_ptr<llarp::handlers::TunEndpoint> tun; 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); tun = std::dynamic_pointer_cast<llarp::handlers::TunEndpoint>(ep);
return !tun; return !tun;
}); });
@ -31,21 +31,23 @@ namespace llarp::apple
} }
if (enable) if (enable)
saved_upstream_dns = tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, {dns_trampoline_port}}});
tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, huint16_t{dns_trampoline_port}}});
else else
tun->ReconfigureDNS(std::move(saved_upstream_dns)); tun->ReconfigureDNS(router->GetConfig()->dns.m_upstreamDNS);
trampoline_active = enable; trampoline_active = enable;
} }
void RouteManager::AddDefaultRouteViaInterface(std::string) void
RouteManager::AddDefaultRouteViaInterface(vpn::NetworkInterface&)
{ {
check_trampoline(true); check_trampoline(true);
if (callback_context and route_callbacks.add_default_route) if (callback_context and route_callbacks.add_default_route)
route_callbacks.add_default_route(callback_context); route_callbacks.add_default_route(callback_context);
} }
void RouteManager::DelDefaultRouteViaInterface(std::string) void
RouteManager::DelDefaultRouteViaInterface(vpn::NetworkInterface&)
{ {
check_trampoline(false); check_trampoline(false);
if (callback_context and route_callbacks.del_default_route) if (callback_context and route_callbacks.del_default_route)

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <llarp/router/abstractrouter.hpp> #include <llarp/router/abstractrouter.hpp>
#include <llarp/ev/vpn.hpp> #include <llarp/vpn/platform.hpp>
#include "context_wrapper.h" #include "context_wrapper.h"
namespace llarp::apple namespace llarp::apple
@ -10,24 +10,22 @@ namespace llarp::apple
{ {
public: public:
RouteManager(llarp::Context& ctx, llarp_route_callbacks rcs, void* callback_context) 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 /// 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. /// because the appex isn't subject to its own rules.
void void AddRoute(net::ipaddr_t /*ip*/, net::ipaddr_t /*gateway*/) override
AddRoute(IPVariant_t ip, IPVariant_t gateway) override
{} {}
void void DelRoute(net::ipaddr_t /*ip*/, net::ipaddr_t /*gateway*/) override
DelRoute(IPVariant_t ip, IPVariant_t gateway) override
{} {}
void void
AddDefaultRouteViaInterface(std::string ifname) override; AddDefaultRouteViaInterface(vpn::NetworkInterface& vpn) override;
void void
DelDefaultRouteViaInterface(std::string ifname) override; DelDefaultRouteViaInterface(vpn::NetworkInterface& vpn) override;
void void
AddRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override; AddRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override;
@ -35,20 +33,19 @@ namespace llarp::apple
void void
DelRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override; DelRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override;
virtual std::vector<IPVariant_t> std::vector<net::ipaddr_t>
GetGatewaysNotOnInterface(std::string ifname) override GetGatewaysNotOnInterface(vpn::NetworkInterface& /*vpn*/) override
{ {
// We can't get this on mac from our sandbox, but we don't actually need it because we // 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. // ignore the gateway for AddRoute/DelRoute anyway, so just return a zero IP.
std::vector<IPVariant_t> ret; std::vector<net::ipaddr_t> ret;
ret.push_back(huint32_t{0}); ret.emplace_back(net::ipv4addr_t{});
return ret; return ret;
} }
private: private:
llarp::Context& context; llarp::Context& context;
bool trampoline_active = false; bool trampoline_active = false;
std::vector<llarp::SockAddr> saved_upstream_dns;
void void
check_trampoline(bool enable); check_trampoline(bool enable);

@ -10,7 +10,8 @@ namespace llarp::apple
packet_write_callback packet_writer, packet_write_callback packet_writer,
on_readable_callback on_readable, on_readable_callback on_readable,
AbstractRouter* router) AbstractRouter* router)
: m_PacketWriter{std::move(packet_writer)} : vpn::NetworkInterface{{}}
, m_PacketWriter{std::move(packet_writer)}
, m_OnReadable{std::move(on_readable)} , m_OnReadable{std::move(on_readable)}
, _router{router} , _router{router}
{ {
@ -39,12 +40,6 @@ namespace llarp::apple
return -1; return -1;
} }
std::string
VPNInterface::IfName() const
{
return "";
}
net::IPPacket net::IPPacket
VPNInterface::ReadNextPacket() VPNInterface::ReadNextPacket()
{ {
@ -58,7 +53,7 @@ namespace llarp::apple
VPNInterface::WritePacket(net::IPPacket pkt) VPNInterface::WritePacket(net::IPPacket pkt)
{ {
int af_family = pkt.IsV6() ? AF_INET6 : AF_INET; 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 } // namespace llarp::apple

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

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

@ -140,14 +140,14 @@ namespace llarp
"this setting specifies the public IP at which this router is reachable. When", "this setting specifies the public IP at which this router is reachable. When",
"provided the public-port option must also be specified.", "provided the public-port option must also be specified.",
}, },
[this](std::string arg) { [this, net = params.Net_ptr()](std::string arg) {
if (arg.empty()) if (arg.empty())
return; return;
nuint32_t addr{}; nuint32_t addr{};
if (not addr.FromString(arg)) if (not addr.FromString(arg))
throw std::invalid_argument{fmt::format("{} is not a valid IPv4 address", 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{ throw std::invalid_argument{
fmt::format("{} is not a publicly routable ip address", addr)}; fmt::format("{} is not a publicly routable ip address", addr)};
@ -648,6 +648,7 @@ namespace llarp
throw std::invalid_argument{ throw std::invalid_argument{
fmt::format("[network]:ip6-range invalid value: '{}'", arg)}; fmt::format("[network]:ip6-range invalid value: '{}'", arg)};
}); });
// TODO: could be useful for snodes in the future, but currently only implemented for clients: // TODO: could be useful for snodes in the future, but currently only implemented for clients:
conf.defineOption<std::string>( conf.defineOption<std::string>(
"network", "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 // 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 // can bind to other 127.* IPs to avoid conflicting with something else that may be listening on
// 127.0.0.1:53. // 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 // 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); m_upstreamDNS.emplace_back(DefaultUpstreamDNS.val);
if (!m_upstreamDNS.back().getPort())
m_upstreamDNS.back().setPort(53);
conf.defineOption<std::string>( conf.defineOption<std::string>(
"dns", "dns",
"upstream", "upstream",
DefaultUpstreamDNS,
MultiValue, MultiValue,
Comment{ Comment{
"Upstream resolver(s) to use as fallback for non-loki addresses.", "Upstream resolver(s) to use as fallback for non-loki addresses.",
@ -798,25 +807,52 @@ namespace llarp
m_upstreamDNS.clear(); m_upstreamDNS.clear();
first = false; first = false;
} }
if (!arg.empty()) if (not arg.empty())
{ {
auto& entry = m_upstreamDNS.emplace_back(std::move(arg)); auto& entry = m_upstreamDNS.emplace_back(std::move(arg));
if (!entry.getPort()) if (not entry.getPort())
entry.setPort(53); 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>( conf.defineOption<std::string>(
"dns", "dns",
"bind", "bind",
DefaultDNSBind, DefaultDNSBind,
MultiValue,
Comment{ Comment{
"Address to bind to for handling DNS requests.", "Address to bind to for handling DNS requests.",
}, },
[=](std::string arg) { [=](std::string arg) {
m_bind = SockAddr{std::move(arg)}; SockAddr addr{arg};
if (!m_bind.getPort()) // set dns port if no explicit port specified
m_bind.setPort(53); // 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>( conf.defineOption<fs::path>(
@ -843,6 +879,11 @@ namespace llarp
"(This is not used directly by lokinet itself, but by the lokinet init scripts", "(This is not used directly by lokinet itself, but by the lokinet init scripts",
"on systems which use resolveconf)", "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 void
@ -1321,15 +1362,25 @@ namespace llarp
} }
void void
Config::LoadOverrides() Config::LoadOverrides(ConfigDefinition& conf) const
{ {
ConfigParser parser;
const auto overridesDir = GetOverridesDir(m_DataDir); const auto overridesDir = GetOverridesDir(m_DataDir);
if (fs::exists(overridesDir)) if (fs::exists(overridesDir))
{ {
util::IterDir(overridesDir, [&](const fs::path& overrideFile) { util::IterDir(overridesDir, [&](const fs::path& overrideFile) {
if (overrideFile.extension() == ".ini") 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; return true;
}); });
@ -1343,7 +1394,7 @@ namespace llarp
} }
bool 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(); auto params = MakeGenParams();
params->isRelay = isRelay; params->isRelay = isRelay;
@ -1351,7 +1402,18 @@ namespace llarp
ConfigDefinition conf{isRelay}; ConfigDefinition conf{isRelay};
initializeConfig(conf, *params); initializeConfig(conf, *params);
for (const auto& item : m_Additional)
{
conf.addConfigValue(item[0], item[1], item[2]);
}
m_Parser.Clear(); m_Parser.Clear();
if (filename)
m_Parser.Filename(*filename);
else
m_Parser.Filename(fs::path{});
if (not m_Parser.LoadFromStr(ini)) if (not m_Parser.LoadFromStr(ini))
return false; return false;
@ -1362,6 +1424,8 @@ namespace llarp
} }
}); });
LoadOverrides(conf);
conf.process(); conf.process();
return true; return true;
@ -1370,37 +1434,24 @@ namespace llarp
bool bool
Config::Load(std::optional<fs::path> fname, bool isRelay) Config::Load(std::optional<fs::path> fname, bool isRelay)
{ {
if (not fname.has_value()) std::vector<char> ini{};
return LoadDefault(isRelay); if (fname)
try
{ {
auto params = MakeGenParams(); if (not fs::exists(*fname))
params->isRelay = isRelay;
params->defaultDataDir = m_DataDir;
ConfigDefinition conf{isRelay};
initializeConfig(conf, *params);
m_Parser.Clear();
if (!m_Parser.LoadFile(*fname))
{
return false; return false;
} fs::ifstream inf{*fname, std::ios::in | std::ios::binary};
LoadOverrides(); auto sz = inf.seekg(0, std::ios::end).tellg();
inf.seekg(0, std::ios::beg);
m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) { ini.resize(sz);
for (const auto& pair : values) inf.read(ini.data(), ini.size());
{
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;
} }
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 bool

@ -155,9 +155,13 @@ namespace llarp
struct DnsConfig struct DnsConfig
{ {
SockAddr m_bind; bool m_raw_dns;
std::vector<SockAddr> m_bind;
std::vector<SockAddr> m_upstreamDNS; std::vector<SockAddr> m_upstreamDNS;
std::vector<fs::path> m_hostfiles; std::vector<fs::path> m_hostfiles;
std::optional<SockAddr> m_QueryBind;
std::unordered_multimap<std::string, std::string> m_ExtraOpts;
void void
defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params);
@ -294,8 +298,12 @@ namespace llarp
bool bool
LoadDefault(bool isRelay); LoadDefault(bool isRelay);
bool
LoadConfigData(
std::string_view ini, std::optional<fs::path> fname = std::nullopt, bool isRelay = false);
void void
LoadOverrides(); LoadOverrides(ConfigDefinition& conf) const;
std::vector<std::array<std::string, 3>> m_Additional; std::vector<std::array<std::string, 3>> m_Additional;
ConfigParser m_Parser; ConfigParser m_Parser;

@ -2,7 +2,6 @@
#include <llarp/util/logging.hpp> #include <llarp/util/logging.hpp>
#include <iterator> #include <iterator>
#include <sstream>
#include <stdexcept> #include <stdexcept>
#include <cassert> #include <cassert>
@ -176,12 +175,14 @@ namespace llarp
std::string std::string
ConfigDefinition::generateINIConfig(bool useValues) ConfigDefinition::generateINIConfig(bool useValues)
{ {
std::ostringstream oss; std::string ini;
auto ini_append = std::back_inserter(ini);
int sectionsVisited = 0; int sectionsVisited = 0;
visitSections([&](const std::string& section, const DefinitionMap&) { 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) { visitDefinitions(section, [&](const std::string& name, const OptionDefinition_ptr& def) {
bool has_comment = false; bool has_comment = false;
@ -190,44 +191,43 @@ namespace llarp
// (i.e. those handled by UndeclaredValueHandler's) // (i.e. those handled by UndeclaredValueHandler's)
for (const std::string& comment : m_definitionComments[section][name]) for (const std::string& comment : m_definitionComments[section][name])
{ {
sect_out << "\n# " << comment; fmt::format_to(sect_append, "\n# {}", comment);
has_comment = true; has_comment = true;
} }
if (useValues and def->getNumberFound() > 0) 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)) else if (not(def->hidden and not has_comment))
{ {
sect_out << "\n"; for (const auto& val : def->defaultValuesAsString())
if (not def->required) fmt::format_to(sect_append, "\n{}{}={}\n", def->required ? "" : "#", name, val);
sect_out << "#";
sect_out << name << "=" << def->defaultValueAsString() << "\n";
} }
}); });
auto sect_str = sect_out.str();
if (sect_str.empty()) if (sect_str.empty())
return; // Skip sections with no options return; // Skip sections with no options
if (sectionsVisited > 0) 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 will create empty objects as a side effect of map's operator[]
// TODO: this also won't handle sections which have no definition // TODO: this also won't handle sections which have no definition
for (const std::string& comment : m_sectionComments[section]) 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++; sectionsVisited++;
}); });
return oss.str(); return ini;
} }
const OptionDefinition_ptr& const OptionDefinition_ptr&

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

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

@ -47,6 +47,12 @@ namespace llarp
void void
Save(); Save();
inline void
Filename(fs::path f)
{
m_FileName = f;
};
private: private:
bool bool
Parse(); 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 #pragma once
#include "platform.hpp"
#include <llarp/util/fs.hpp> #include <llarp/util/fs.hpp>
@ -21,20 +22,22 @@ namespace llarp
inline fs::path inline fs::path
GetDefaultDataDir() GetDefaultDataDir()
{ {
#ifdef _WIN32 if constexpr (not platform::is_windows)
return "C:/programdata/lokinet";
#else
fs::path datadir{"/var/lib/lokinet"};
if (auto uid = ::geteuid())
{ {
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 #endif
return datadir;
}
else
return "C:\\ProgramData\\Lokinet";
} }
inline fs::path 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 #endif
; ;
/// building with systemd enabled ?
inline constexpr bool with_systemd =
#ifdef WITH_SYSTEMD
true
#else
false
#endif
;
/// are we freebsd ? /// are we freebsd ?
inline constexpr bool is_freebsd = inline constexpr bool is_freebsd =
#ifdef __FreeBSD__ #ifdef __FreeBSD__
@ -39,6 +48,15 @@ namespace llarp::platform
#endif #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 ? /// are we an android platform ?
inline constexpr bool is_android = inline constexpr bool is_android =
#ifdef ANDROID #ifdef ANDROID
@ -57,9 +75,26 @@ namespace llarp::platform
#endif #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 ? /// are we a mobile phone ?
inline constexpr bool is_mobile = is_android or is_iphone; inline constexpr bool is_mobile = is_android or is_iphone;
/// does this platform support native ipv6 ? /// does this platform support native ipv6 ?
// TODO: make windows support ipv6
inline constexpr bool supports_ipv6 = not is_windows; inline constexpr bool supports_ipv6 = not is_windows;
} // namespace llarp::platform } // namespace llarp::platform

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

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

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

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

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

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

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

@ -103,6 +103,9 @@ namespace llarp
std::vector<ResourceRecord> authorities; std::vector<ResourceRecord> authorities;
std::vector<ResourceRecord> additional; std::vector<ResourceRecord> additional;
}; };
std::optional<Message>
MaybeParseDNSMessage(llarp_buffer_t buf);
} // namespace dns } // namespace dns
template <> 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 namespace dns
{ {
static auto logcat = log::Cat("dns");
Question::Question(Question&& other) Question::Question(Question&& other)
: qname(std::move(other.qname)) : qname(std::move(other.qname))
, qtype(std::move(other.qtype)) , qtype(std::move(other.qtype))
@ -41,17 +43,17 @@ namespace llarp
qname = *std::move(name); qname = *std::move(name);
else else
{ {
llarp::LogError("failed to decode name"); log::error(logcat, "failed to decode name");
return false; return false;
} }
if (!buf->read_uint16(qtype)) if (!buf->read_uint16(qtype))
{ {
llarp::LogError("failed to decode type"); log::error(logcat, "failed to decode type");
return false; return false;
} }
if (!buf->read_uint16(qclass)) if (!buf->read_uint16(qclass))
{ {
llarp::LogError("failed to decode class"); log::error(logcat, "failed to decode class");
return false; return false;
} }
return true; return true;

@ -8,6 +8,8 @@ namespace llarp
{ {
namespace dns namespace dns
{ {
static auto logcat = log::Cat("dns");
ResourceRecord::ResourceRecord(const ResourceRecord& other) ResourceRecord::ResourceRecord(const ResourceRecord& other)
: rr_name(other.rr_name) : rr_name(other.rr_name)
, rr_type(other.rr_type) , rr_type(other.rr_type)
@ -64,22 +66,22 @@ namespace llarp
return false; return false;
if (!buf->read_uint16(rr_type)) if (!buf->read_uint16(rr_type))
{ {
llarp::LogDebug("failed to decode rr type"); log::debug(logcat, "failed to decode rr type");
return false; return false;
} }
if (!buf->read_uint16(rr_class)) if (!buf->read_uint16(rr_class))
{ {
llarp::LogDebug("failed to decode rr class"); log::debug(logcat, "failed to decode rr class");
return false; return false;
} }
if (!buf->read_uint32(ttl)) if (!buf->read_uint32(ttl))
{ {
llarp::LogDebug("failed to decode ttl"); log::debug(logcat, "failed to decode ttl");
return false; return false;
} }
if (!DecodeRData(buf, rData)) if (!DecodeRData(buf, rData))
{ {
llarp::LogDebug("failed to decode rr rdata ", *this); log::debug(logcat, "failed to decode rr rdata {}", *this);
return false; return false;
} }
return true; 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 "server.hpp"
#include <llarp/constants/platform.hpp>
#include <llarp/constants/apple.hpp>
#include "dns.hpp" #include "dns.hpp"
#include <iterator>
#include <llarp/crypto/crypto.hpp> #include <llarp/crypto/crypto.hpp>
#include <array> #include <array>
#include <stdexcept>
#include <utility> #include <utility>
#include <llarp/ev/udp_handle.hpp> #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 namespace llarp::dns
{ {
PacketHandler::PacketHandler(EventLoop_ptr loop, IQueryHandler* h) static auto logcat = log::Cat("dns");
: m_QueryHandler{h}, m_Loop{std::move(loop)}
{}
Proxy::Proxy(EventLoop_ptr loop, IQueryHandler* h) void
: PacketHandler{loop, h}, m_Loop(std::move(loop)) QueryJob_Base::Cancel() const
{ {
m_Server = m_Loop->make_udp( Message reply{m_Query};
[this](UDPHandle&, SockAddr a, OwnedBuffer buf) { HandlePacket(a, a, buf); }); reply.AddServFail();
SendReply(reply.ToBuffer());
} }
void /// sucks up udp packets from a bound socket and feeds it to a server
PacketHandler::Stop() class UDPReader : public PacketSource_Base, public std::enable_shared_from_this<UDPReader>
{ {
if (m_UnboundResolver) Server& m_DNS;
m_UnboundResolver->Stop(); std::shared_ptr<llarp::UDPHandle> m_udp;
} SockAddr m_LocalAddr;
bool public:
Proxy::Start(SockAddr addr, std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles) 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))) class Resolver;
return false;
return m_Server->listen(addr); 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 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"); if (auto ptr = MakePacketSourceOn(addr, m_Config))
m_UnboundResolver->Init(); AddPacketSource(std::move(ptr));
} }
// add default resolver as needed
if (auto ptr = MakeDefaultResolver())
AddResolver(ptr);
} }
bool std::shared_ptr<I_Platform>
PacketHandler::Start(SockAddr, std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles) 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 std::shared_ptr<PacketSource_Base>
PacketHandler::SetupUnboundResolver( Server::MakePacketSourceOn(const llarp::SockAddr& addr, const llarp::DnsConfig&)
std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles)
{ {
// if we have no resolvers don't set up unbound return std::make_shared<UDPReader>(*this, m_Loop, addr);
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)...);
};
m_UnboundResolver = std::shared_ptr<Resolver_Base>
std::make_shared<UnboundResolver>(m_Loop, std::move(replyFunc), std::move(failFunc)); Server::MakeDefaultResolver()
m_Resolvers.clear(); {
if (not m_UnboundResolver->Init()) if (m_Config.m_upstreamDNS.empty())
{ {
llarp::LogError("Failed to initialize upstream DNS resolver."); log::info(
m_UnboundResolver = nullptr; logcat,
return false; "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)) if (auto ptr = src.lock())
{ if (auto maybe_addr = ptr->BoundOn())
llarp::LogError("Failed to add upstream DNS server: ", resolver); addrs.emplace_back(*maybe_addr);
m_UnboundResolver = nullptr;
return false;
}
m_Resolvers.emplace(resolver);
} }
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 void
Proxy::SendServerMessageBufferTo( Server::AddResolver(std::shared_ptr<Resolver_Base> resolver)
const SockAddr& to, [[maybe_unused]] const SockAddr& from, llarp_buffer_t buf)
{ {
if (!m_Server->send(to, buf)) m_OwnedResolvers.insert(resolver);
llarp::LogError("dns reply failed"); AddResolver(std::weak_ptr<Resolver_Base>{resolver});
} }
bool void
PacketHandler::IsUpstreamResolver(const SockAddr& to, [[maybe_unused]] const SockAddr& from) const Server::AddPacketSource(std::weak_ptr<PacketSource_Base> pkt)
{ {
return m_Resolvers.count(to); m_PacketSources.push_back(pkt);
} }
bool void
PacketHandler::ShouldHandlePacket( Server::AddPacketSource(std::shared_ptr<PacketSource_Base> pkt)
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) const
{ {
MessageHeader hdr; AddPacketSource(std::weak_ptr<PacketSource_Base>{pkt});
if (not hdr.Decode(&buf)) 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}; void
if (not msg.Decode(&buf)) 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 void
PacketHandler::HandlePacket(const SockAddr& resolver, const SockAddr& from, llarp_buffer_t buf) Server::SetDNSMode(bool all_queries)
{ {
MessageHeader hdr; if (auto maybe_addr = FirstBoundPacketSourceAddr())
if (not hdr.Decode(&buf)) 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); log::warning(logcat, "preventing dns packet replay to={} from={}", to, from);
return; return false;
} }
Message msg(hdr); auto maybe = MaybeParseDNSMessage(buf);
if (not msg.Decode(&buf)) if (not maybe)
{ {
llarp::LogWarn("failed to parse dns message from ", from); log::warning(logcat, "invalid dns message format from {} to dns listener on {}", from, to);
return; return false;
} }
auto& msg = *maybe;
// we don't provide a DoH resolver because it requires verified TLS // 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 // 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 // thankfully mozilla added a backdoor that allows ISPs to turn it off
// so we disable DoH for firefox using mozilla's ISP backdoor // 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) for (const auto& q : msg.questions)
{ {
// is this firefox looking for their backdoor record? // 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. // yea it is, let's turn off DoH because god is dead.
msg.AddNXReply(); msg.AddNXReply();
// press F to pay respects // press F to pay respects and send it back where it came from
SendServerMessageBufferTo(from, resolver, msg.ToBuffer()); ptr->SendTo(from, to, msg.ToBuffer());
return; 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) { if (auto res_ptr = resolver.lock())
self->SendServerMessageBufferTo(to, resolver, msg.ToBuffer());
};
if (!m_QueryHandler->HandleHookedDNSMessage(std::move(msg), reply))
{ {
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) return false;
{
// no upstream resolvers
// let's serv fail it
msg.AddServFail();
SendServerMessageBufferTo(from, resolver, msg.ToBuffer());
}
else
{
m_UnboundResolver->Lookup(resolver, from, std::move(msg));
}
} }
} // namespace llarp::dns } // namespace llarp::dns

@ -1,99 +1,297 @@
#pragma once #pragma once
#include "message.hpp" #include "message.hpp"
#include "platform.hpp"
#include <llarp/config/config.hpp>
#include <llarp/ev/ev.hpp> #include <llarp/ev/ev.hpp>
#include <llarp/net/net.hpp> #include <llarp/net/net.hpp>
#include "unbound_resolver.hpp" #include <llarp/util/fs.hpp>
#include <set>
#include <unordered_map> namespace llarp::dns
namespace llarp
{ {
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 std::weak_ptr<PacketSource_Base> m_Wrapped;
class IQueryHandler 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: if (auto ptr = m_Wrapped.lock())
virtual ~IQueryHandler() = default; return ptr->WouldLoop(to, from);
return true;
}
/// return true if we should hook this message void
virtual bool SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const override
ShouldHookDNSMessage(const Message& msg) const = 0; {
m_WritePacket(net::IPPacket::make_udp(from, to, std::move(buf)));
}
/// handle a hooked message /// stop reading packets and end operation
virtual bool void
HandleHookedDNSMessage(Message query, std::function<void(Message)> sendReply) = 0; Stop() override
}; {
if (auto ptr = m_Wrapped.lock())
ptr->Stop();
}
// Base class for DNS lookups /// returns the sockaddr we are bound on if applicable
class PacketHandler : public std::enable_shared_from_this<PacketHandler> std::optional<SockAddr>
BoundOn() const override
{ {
public: if (auto ptr = m_Wrapped.lock())
explicit PacketHandler(EventLoop_ptr loop, IQueryHandler* handler); 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 void
Start( SendReply(llarp::OwnedBuffer replyBuf) const override
SockAddr localaddr, {
std::vector<SockAddr> upstreamResolvers, src->SendTo(asker, resolver, std::move(replyBuf));
std::vector<fs::path> hostfiles); }
};
void /// handler of dns query hooking
Stop(); /// 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 public:
Restart(); virtual ~Resolver_Base() = default;
void /// less than via rank
HandlePacket(const SockAddr& resolver, const SockAddr& from, llarp_buffer_t buf); bool
operator<(const Resolver_Base& other) const
{
return Rank() < other.Rank();
}
bool /// greater than via rank
ShouldHandlePacket(const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) const; bool
operator>(const Resolver_Base& other) const
{
return Rank() > other.Rank();
}
protected: /// get local socket address that queries are sent from
virtual void virtual std::optional<SockAddr>
SendServerMessageBufferTo(const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) = 0; GetLocalAddr() const
{
return std::nullopt;
}
// Returns true if this packet is something that looks like it's going to an upstream /// get printable name
// resolver, i.e. matches a configured resolver. virtual std::string_view
virtual bool ResolverName() const = 0;
IsUpstreamResolver(const SockAddr& to, const SockAddr& from) const;
private: /// reset the resolver state, optionally replace upstream info with new info. The default base
void /// implementation does nothing.
HandleUpstreamFailure(const SockAddr& from, const SockAddr& to, Message msg); virtual void
ResetResolver(
[[maybe_unused]] std::optional<std::vector<SockAddr>> replace_upstream = std::nullopt)
{}
bool /// cancel all pending requests and cease further operation. Default operation is a no-op.
SetupUnboundResolver(std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles); virtual void
Down()
{}
IQueryHandler* const m_QueryHandler; /// attempt to handle a dns message
std::set<SockAddr> m_Resolvers; /// returns true if we consumed this query and it should not be processed again
std::shared_ptr<UnboundResolver> m_UnboundResolver; virtual bool
EventLoop_ptr m_Loop; 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. /// Returns true if a packet with to and from addresses is something that would cause a
class Proxy : public PacketHandler /// resolution loop and thus should not be used on this resolver
virtual bool
WouldLoop(const SockAddr& to, const SockAddr& from) const
{ {
public: (void)to;
explicit Proxy(EventLoop_ptr loop, IQueryHandler* handler); (void)from;
return false;
bool }
Start( };
SockAddr localaddr,
std::vector<SockAddr> upstreamResolvers, // Base class for DNS proxy
std::vector<fs::path> hostfiles) override; class Server : public std::enable_shared_from_this<Server>
{
protected: protected:
void /// add a packet source to this server, does share ownership
SendServerMessageBufferTo( void
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) override; AddPacketSource(std::shared_ptr<PacketSource_Base> resolver);
/// add a resolver to this packet handler, does share ownership
private: void
std::shared_ptr<UDPHandle> m_Server; AddResolver(std::shared_ptr<Resolver_Base> resolver);
EventLoop_ptr m_Loop;
}; /// create the platform dependant dns stuff
} // namespace dns virtual std::shared_ptr<I_Platform>
} // namespace llarp 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 namespace llarp::dns
{ {
static auto logcat = log::Cat("dns");
bool bool
SRVData::IsValid() const SRVData::IsValid() const
{ {
@ -22,7 +24,7 @@ namespace llarp::dns
// check target size is not absurd // check target size is not absurd
if (target.size() > TARGET_MAX_SIZE) 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; return false;
} }
@ -41,7 +43,7 @@ namespace llarp::dns
} }
// if we're here, target is invalid // if we're here, target is invalid
LogWarn("SRVData invalid"); log::warning(logcat, "SRVData invalid");
return false; return false;
} }
@ -64,14 +66,14 @@ namespace llarp::dns
bool bool
SRVData::fromString(std::string_view srvString) SRVData::fromString(std::string_view srvString)
{ {
LogDebug("SRVData::fromString(\"", srvString, "\")"); log::debug(logcat, "SRVData::fromString(\"{}\")", srvString);
// split on spaces, discard trailing empty strings // split on spaces, discard trailing empty strings
auto splits = split(srvString, " ", false); auto splits = split(srvString, " ", false);
if (splits.size() != 5 && splits.size() != 4) 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; return false;
} }
@ -79,19 +81,19 @@ namespace llarp::dns
if (not parse_int(splits[1], priority)) 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; return false;
} }
if (not parse_int(splits[2], weight)) 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; return false;
} }
if (not parse_int(splits[3], port)) 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; return false;
} }

@ -11,6 +11,8 @@
namespace llarp::dns namespace llarp::dns
{ {
static auto logcat = log::Cat("dns");
struct PendingUnboundLookup struct PendingUnboundLookup
{ {
std::weak_ptr<UnboundResolver> resolver; std::weak_ptr<UnboundResolver> resolver;
@ -156,7 +158,7 @@ namespace llarp::dns
upstream += std::to_string(port); 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) if (ub_ctx_set_fwd(unboundContext, upstream.c_str()) != 0)
{ {
Reset(); Reset();
@ -198,17 +200,12 @@ namespace llarp::dns
void void
UnboundResolver::AddHostsFile(const fs::path& file) UnboundResolver::AddHostsFile(const fs::path& file)
{ {
LogDebug("adding hosts file ", file); log::debug(logcat, "adding hosts file {}", file);
const auto str = file.u8string(); const auto str = file.u8string();
if (auto ret = ub_ctx_hosts(unboundContext, str.c_str())) if (auto ret = ub_ctx_hosts(unboundContext, str.c_str()))
{
throw std::runtime_error{ throw std::runtime_error{
fmt::format("Failed to add host file {}: {}", file, ub_strerror(ret))}; fmt::format("Failed to add host file {}: {}", file, ub_strerror(ret))};
} log::info(logcat, "added hosts file {}", file);
else
{
LogInfo("added hosts file ", file);
}
} }
void 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; class TunnelManager;
} }
namespace dns
{
class Server;
}
class EndpointBase class EndpointBase
{ {
std::unordered_set<dns::SRVData> m_SRVRecords; std::unordered_set<dns::SRVData> m_SRVRecords;
@ -72,6 +77,13 @@ namespace llarp
void void
PutSRVRecord(dns::SRVData srv); 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 /// called when srv data changes in some way
virtual void virtual void
SRVRecordsChanged() = 0; SRVRecordsChanged() = 0;

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

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

@ -1,12 +1,12 @@
#include "ev_libuv.hpp" #include "libuv.hpp"
#include "vpn.hpp"
#include <memory> #include <memory>
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>
#include <cstring>
#include <llarp/util/exceptions.hpp> #include <llarp/util/exceptions.hpp>
#include <llarp/util/thread/queue.hpp> #include <llarp/util/thread/queue.hpp>
#include <cstring> #include <llarp/vpn/platform.hpp>
#include "ev.hpp"
#include <uvw.hpp> #include <uvw.hpp>
@ -72,6 +72,13 @@ namespace llarp::uv
bool bool
send(const SockAddr& dest, const llarp_buffer_t& buf) override; 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> std::optional<int>
file_descriptor() override file_descriptor() override
{ {
@ -244,19 +251,21 @@ namespace llarp::uv
using event_t = uvw::PrepareEvent; using event_t = uvw::PrepareEvent;
auto handle = m_Impl->resource<uvw::PrepareHandle>(); auto handle = m_Impl->resource<uvw::PrepareHandle>();
#endif #endif
if (!handle) if (!handle)
return false; return false;
handle->on<event_t>([netif = std::move(netif), handler = std::move(handler)]( handle->on<event_t>([netif = std::move(netif), handler = std::move(handler)](
const event_t&, [[maybe_unused]] auto& handle) { 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) if (handler)
handler(std::move(pkt)); handler(std::move(pkt));
// on windows/apple, vpn packet io does not happen as an io action that wakes up the event // 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. // 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(); netif->MaybeWakeUpperLayers();
} }
}); });

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

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

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

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

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

@ -10,8 +10,27 @@ namespace llarp
struct AbstractRouter; struct AbstractRouter;
namespace handlers 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(std::string name, AbstractRouter* r);
~ExitEndpoint() override; ~ExitEndpoint() override;
@ -66,10 +85,10 @@ namespace llarp
SupportsV6() const; SupportsV6() const;
bool bool
ShouldHookDNSMessage(const dns::Message& msg) const override; ShouldHookDNSMessage(const dns::Message& msg) const;
bool bool
HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)>) override; HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)>);
void void
LookupServiceAsync( LookupServiceAsync(
@ -174,7 +193,7 @@ namespace llarp
KickIdentOffExit(const PubKey& pk); KickIdentOffExit(const PubKey& pk);
AbstractRouter* m_Router; AbstractRouter* m_Router;
std::shared_ptr<dns::Proxy> m_Resolver; std::shared_ptr<dns::Server> m_Resolver;
bool m_ShouldInitTun; bool m_ShouldInitTun;
std::string m_Name; std::string m_Name;
bool m_PermitExit; bool m_PermitExit;
@ -220,6 +239,7 @@ namespace llarp
/// internet to llarp packet queue /// internet to llarp packet queue
PacketQueue_t m_InetToNetwork; PacketQueue_t m_InetToNetwork;
bool m_UseV6; bool m_UseV6;
DnsConfig m_DNSConf;
}; };
} // namespace handlers } // namespace handlers
} // namespace llarp } // namespace llarp

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

@ -12,7 +12,7 @@
#include <llarp/ev/ev.hpp> #include <llarp/ev/ev.hpp>
#include <llarp/net/net.hpp> #include <llarp/net/net.hpp>
#include <llarp/router/abstractrouter.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/context.hpp>
#include <llarp/service/outbound_context.hpp> #include <llarp/service/outbound_context.hpp>
#include <llarp/service/endpoint_state.hpp> #include <llarp/service/endpoint_state.hpp>
@ -24,7 +24,9 @@
#include <llarp/quic/tunnel.hpp> #include <llarp/quic/tunnel.hpp>
#include <llarp/rpc/endpoint_rpc.hpp> #include <llarp/rpc/endpoint_rpc.hpp>
#include <llarp/util/str.hpp> #include <llarp/util/str.hpp>
#include <llarp/util/logging/buffer.hpp>
#include <llarp/dns/srv_data.hpp> #include <llarp/dns/srv_data.hpp>
#include <llarp/constants/net.hpp>
#include <llarp/constants/platform.hpp> #include <llarp/constants/platform.hpp>
#include <oxenc/bt.h> #include <oxenc/bt.h>
@ -33,83 +35,180 @@ namespace llarp
{ {
namespace handlers namespace handlers
{ {
// Intercepts DNS IP packets going to an IP on the tun interface; this is currently used on bool
// Android and macOS where binding to a DNS port (i.e. via llarp::dns::Proxy) isn't possible TunEndpoint::MaybeHookDNS(
// because of OS restrictions, but a tun interface *is* available. std::shared_ptr<dns::PacketSource_Base> source,
class DnsInterceptor : public dns::PacketHandler 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: 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) ~DnsInterceptor() override = default;
: dns::PacketHandler{router->loop(), ep}, m_Endpoint{ep} {};
void void
SendServerMessageBufferTo( SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const override
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) override
{ {
const auto pkt = net::IPPacket::UDP( auto pkt = net::IPPacket::make_udp(from, to, std::move(buf));
from.getIPv4(),
ToNet(huint16_t{from.getPort()}), if (pkt.empty())
to.getIPv4(),
ToNet(huint16_t{to.getPort()}),
buf);
if (pkt.sz == 0)
return; return;
m_Endpoint->HandleWriteIPPacket( m_Reply(std::move(pkt));
pkt.ConstBuffer(), net::ExpandV4(from.asIPv4()), net::ExpandV4(to.asIPv4()), 0);
} }
#ifdef ANDROID void
bool Stop() override{};
IsUpstreamResolver(const SockAddr&, const SockAddr&) const 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 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) 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)); }); [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) { void
const size_t ip_header_size = (pkt.Header()->ihl * 4); TunEndpoint::SetupDNS()
{
const uint8_t* ptr = pkt.buf + ip_header_size; const auto& info = GetVPNInterface()->Info();
const auto dst = ToNet(pkt.dstv4()); if (m_DnsConfig.m_raw_dns)
const auto src = ToNet(pkt.srcv4()); {
const SockAddr laddr{src, nuint16_t{*reinterpret_cast<const uint16_t*>(ptr)}}; auto dns = std::make_shared<TunDNS>(this, m_DnsConfig);
const SockAddr raddr{dst, nuint16_t{*reinterpret_cast<const uint16_t*>(ptr + 2)}}; m_DNS = dns;
OwnedBuffer buf{pkt.sz - (8 + ip_header_size)}; m_PacketRouter->AddUDPHandler(huint16_t{53}, [this, dns](net::IPPacket pkt) {
std::copy_n(ptr + 8, buf.sz, buf.buf.get()); auto dns_pkt_src = dns->PacketSource;
if (m_Resolver->ShouldHandlePacket(raddr, laddr, buf)) if (const auto& reply = pkt.reply)
m_Resolver->HandlePacket(raddr, laddr, buf); dns_pkt_src = std::make_shared<dns::PacketSource_Wrapper>(dns_pkt_src, reply);
else if (dns->MaybeHandlePacket(
std::move(dns_pkt_src), pkt.dst(), pkt.src(), *pkt.L4OwnedBuffer()))
return;
HandleGotUserPacket(std::move(pkt)); HandleGotUserPacket(std::move(pkt));
}); });
#else }
m_Resolver = std::make_shared<dns::Proxy>(r->loop(), this); else
#endif 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 util::StatusObject
@ -118,11 +217,21 @@ namespace llarp
auto obj = service::Endpoint::ExtractStatus(); auto obj = service::Endpoint::ExtractStatus();
obj["ifaddr"] = m_OurRange.ToString(); obj["ifaddr"] = m_OurRange.ToString();
obj["ifname"] = m_IfName; obj["ifname"] = m_IfName;
std::vector<std::string> resolvers;
for (const auto& addr : m_UpstreamResolvers) std::vector<std::string> upstreamRes;
resolvers.emplace_back(addr.ToString()); for (const auto& ent : m_DnsConfig.m_upstreamDNS)
obj["ustreamResolvers"] = resolvers; upstreamRes.emplace_back(ent.ToString());
obj["localResolver"] = m_LocalResolverAddr.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{}; util::StatusObject ips{};
for (const auto& item : m_IPActivity) for (const auto& item : m_IPActivity)
{ {
@ -147,19 +256,21 @@ namespace llarp
void void
TunEndpoint::Thaw() TunEndpoint::Thaw()
{ {
if (m_Resolver) if (m_DNS)
m_Resolver->Restart(); m_DNS->Reset();
} }
std::vector<SockAddr> void
TunEndpoint::ReconfigureDNS(std::vector<SockAddr> servers) TunEndpoint::ReconfigureDNS(std::vector<SockAddr> servers)
{ {
std::swap(m_UpstreamResolvers, servers); if (m_DNS)
m_Resolver->Stop(); {
if (!m_Resolver->Start( for (auto weak : m_DNS->GetAllResolvers())
m_LocalResolverAddr.createSockAddr(), m_UpstreamResolvers, m_hostfiles)) {
llarp::LogError(Name(), " failed to reconfigure DNS server"); if (auto ptr = weak.lock())
return servers; ptr->ResetResolver(servers);
}
}
} }
bool bool
@ -199,13 +310,10 @@ namespace llarp
m_AuthPolicy = std::move(auth); m_AuthPolicy = std::move(auth);
} }
m_DnsConfig = dnsConf;
m_TrafficPolicy = conf.m_TrafficPolicy; m_TrafficPolicy = conf.m_TrafficPolicy;
m_OwnedRanges = conf.m_OwnedRanges; 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; m_BaseV6Address = conf.m_baseV6Address;
if (conf.m_PathAlignmentTimeout) if (conf.m_PathAlignmentTimeout)
@ -354,7 +462,6 @@ namespace llarp
return llarp::SockAddr{net::TruncateV6(GetIfAddr()), huint16_t{port}}; return llarp::SockAddr{net::TruncateV6(GetIfAddr()), huint16_t{port}};
}); });
} }
return Endpoint::Configure(conf, dnsConf); return Endpoint::Configure(conf, dnsConf);
} }
@ -763,20 +870,29 @@ namespace llarp
} }
else if (msg.questions[0].qtype == dns::qTypeSRV) 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)) if (is_localhost_loki(msg))
{ {
msg.AddSRVReply(introSet().GetMatchingSRVRecords(msg.questions[0].Subdomains())); msg.AddSRVReply(introSet().GetMatchingSRVRecords(srv_for));
reply(msg); reply(msg);
return true; return true;
} }
else if (addr.FromString(qname, ".loki")) LookupServiceAsync(
{ name,
llarp::LogDebug("SRV request for: ", qname); srv_for,
[reply, msg = std::make_shared<dns::Message>(std::move(msg))](auto records) {
return ReplyToLokiSRVWhenReady(addr, std::make_shared<dns::Message>(msg)); if (records.empty())
} {
msg->AddNXReply();
}
else
{
msg->AddSRVReply(records);
}
reply(*msg);
});
return true;
} }
else else
{ {
@ -862,11 +978,8 @@ namespace llarp
bool bool
TunEndpoint::Start() TunEndpoint::Start()
{ {
if (!Endpoint::Start()) if (not Endpoint::Start())
{
llarp::LogWarn("Couldn't start endpoint");
return false; return false;
}
return SetupNetworking(); return SetupNetworking();
} }
@ -893,62 +1006,59 @@ namespace llarp
} }
vpn::InterfaceInfo info; vpn::InterfaceInfo info;
info.addrs.emplace(m_OurRange); info.addrs.emplace_back(m_OurRange);
if (m_BaseV6Address) if (m_BaseV6Address)
{ {
IPRange v6range = m_OurRange; IPRange v6range = m_OurRange;
v6range.addr = (*m_BaseV6Address) | m_OurRange.addr; v6range.addr = (*m_BaseV6Address) | m_OurRange.addr;
LogInfo(Name(), " using v6 range: ", v6range); LogInfo(Name(), " using v6 range: ", v6range);
info.addrs.emplace(v6range, AF_INET6); info.addrs.emplace_back(v6range, AF_INET6);
} }
info.ifname = m_IfName; info.ifname = m_IfName;
info.dnsaddr.FromString(m_LocalResolverAddr.toHost());
LogInfo(Name(), " setting up network..."); LogInfo(Name(), " setting up network...");
try try
{ {
m_NetIf = Router()->GetVPNPlatform()->ObtainInterface(std::move(info), Router()); m_NetIf = Router()->GetVPNPlatform()->CreateInterface(std::move(info), Router());
} }
catch (std::exception& ex) catch (std::exception& ex)
{ {
LogError(Name(), " failed to set up network interface: ", ex.what()); LogError(Name(), " failed to set up network interface: ", ex.what());
}
if (not m_NetIf)
{
LogError(Name(), " failed to obtain network interface");
return false; return false;
} }
m_IfName = m_NetIf->IfName();
m_IfName = m_NetIf->Info().ifname;
LogInfo(Name(), " got network interface ", m_IfName); LogInfo(Name(), " got network interface ", m_IfName);
if (not Router()->loop()->add_network_interface(m_NetIf, [this](net::IPPacket pkt) { auto handle_packet = [netif = m_NetIf, pkt_router = m_PacketRouter](auto pkt) {
m_PacketRouter->HandleIPPacket(std::move(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"); LogError(Name(), " failed to add network interface");
return false; return false;
} }
#ifdef __APPLE__
m_OurIPv6 = llarp::huint128_t{ m_OurIPv6 = llarp::huint128_t{
llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(m_OurRange.addr).h}}; 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 constexpr (not llarp::platform::is_apple)
if (maybe.has_value())
{ {
m_OurIPv6 = *maybe; if (auto maybe = m_router->Net().GetInterfaceIPv6Address(m_IfName))
LogInfo(Name(), " has ipv6 address ", m_OurIPv6); {
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); return HasAddress(ourAddr);
} }
@ -970,18 +1080,7 @@ namespace llarp
TunEndpoint::SetupNetworking() TunEndpoint::SetupNetworking()
{ {
llarp::LogInfo("Set Up networking for ", Name()); llarp::LogInfo("Set Up networking for ", Name());
if (!SetupTun()) return 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;
} }
void void
@ -993,9 +1092,13 @@ namespace llarp
bool bool
TunEndpoint::Stop() TunEndpoint::Stop()
{ {
// stop vpn tunnel
if (m_NetIf)
m_NetIf->Stop();
if (m_RawDNS)
m_RawDNS->Stop();
// save address map if applicable // save address map if applicable
#ifndef ANDROID if (m_PersistAddrMapFile and not platform::is_android)
if (m_PersistAddrMapFile)
{ {
const auto& file = *m_PersistAddrMapFile; const auto& file = *m_PersistAddrMapFile;
LogInfo(Name(), " saving address map to ", file); LogInfo(Name(), " saving address map to ", file);
@ -1015,9 +1118,8 @@ namespace llarp
maybe->write(data.data(), data.size()); maybe->write(data.data(), data.size());
} }
} }
#endif if (m_DNS)
if (m_Resolver) m_DNS->Stop();
m_Resolver->Stop();
return llarp::service::Endpoint::Stop(); return llarp::service::Endpoint::Stop();
} }
@ -1029,12 +1131,18 @@ namespace llarp
// is it already mapped? return the mapping // is it already mapped? return the mapping
if (auto itr = m_ExitIPToExitAddress.find(ip); itr != m_ExitIPToExitAddress.end()) if (auto itr = m_ExitIPToExitAddress.find(ip); itr != m_ExitIPToExitAddress.end())
return itr->second; return itr->second;
const auto& net = m_router->Net();
const bool is_bogon = net.IsBogonIP(ip);
// build up our candidates to choose // build up our candidates to choose
std::unordered_set<service::Address> candidates; std::unordered_set<service::Address> candidates;
for (const auto& entry : m_ExitMap.FindAllEntries(ip)) for (const auto& entry : m_ExitMap.FindAllEntries(ip))
{ {
// make sure it is allowed by the range if the ip is a bogon // in the event the exit's range is a bogon range, make sure the ip is located in that range
if (not IsBogon(ip) or entry.first.BogonContains(ip)) // 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); candidates.emplace(entry.second);
} }
// no candidates? bail. // no candidates? bail.
@ -1096,13 +1204,20 @@ namespace llarp
return; return;
} }
std::function<void(void)> extra_cb;
if (not HasFlowToService(addr))
{
extra_cb = [poker = Router()->routePoker()]() { poker->Up(); };
}
pkt.ZeroSourceAddress(); pkt.ZeroSourceAddress();
MarkAddressOutbound(addr); MarkAddressOutbound(addr);
EnsurePathToService( EnsurePathToService(
addr, addr,
[pkt, this](service::Address addr, service::OutboundContext* ctx) { [pkt, extra_cb, this](service::Address addr, service::OutboundContext* ctx) {
if (ctx) if (ctx)
{ {
if (extra_cb)
extra_cb();
ctx->SendPacketToRemote(pkt.ConstBuffer(), service::ProtocolType::Exit); ctx->SendPacketToRemote(pkt.ConstBuffer(), service::ProtocolType::Exit);
Router()->TriggerPump(); Router()->TriggerPump();
return; return;

@ -2,33 +2,56 @@
#include <llarp/dns/server.hpp> #include <llarp/dns/server.hpp>
#include <llarp/ev/ev.hpp> #include <llarp/ev/ev.hpp>
#include <llarp/ev/vpn.hpp>
#include <llarp/net/ip.hpp> #include <llarp/net/ip.hpp>
#include <llarp/net/ip_packet.hpp> #include <llarp/net/ip_packet.hpp>
#include <llarp/net/net.hpp> #include <llarp/net/net.hpp>
#include <llarp/service/endpoint.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/util/thread/threading.hpp>
#include <llarp/vpn/packet_router.hpp> #include <llarp/vpn/packet_router.hpp>
#include <llarp/vpn/platform.hpp>
#include <future> #include <future>
#include <type_traits> #include <type_traits>
#include <variant> #include <variant>
#include <llarp/service/protocol_type.hpp>
#include <llarp/util/priority_queue.hpp>
namespace llarp namespace llarp
{ {
namespace handlers namespace handlers
{ {
struct TunEndpoint : public service::Endpoint, struct TunEndpoint : public service::Endpoint,
public dns::IQueryHandler, public dns::Resolver_Base,
public std::enable_shared_from_this<TunEndpoint> public std::enable_shared_from_this<TunEndpoint>
{ {
TunEndpoint(AbstractRouter* r, llarp::service::Context* parent); TunEndpoint(AbstractRouter* r, llarp::service::Context* parent);
~TunEndpoint() override; ~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 path::PathSet_ptr
GetSelf() override GetSelf() override
{ {
@ -44,9 +67,8 @@ namespace llarp
void void
Thaw() override; Thaw() override;
// Reconfigures DNS servers and restarts libunbound with the new servers. Returns the old set // Reconfigures DNS servers and restarts libunbound with the new servers.
// of configured dns servers. void
std::vector<SockAddr>
ReconfigureDNS(std::vector<SockAddr> servers); ReconfigureDNS(std::vector<SockAddr> servers);
bool bool
@ -71,11 +93,10 @@ namespace llarp
SupportsV6() const override; SupportsV6() const override;
bool bool
ShouldHookDNSMessage(const dns::Message& msg) const override; ShouldHookDNSMessage(const dns::Message& msg) const;
bool bool
HandleHookedDNSMessage( HandleHookedDNSMessage(dns::Message query, std::function<void(dns::Message)> sendreply);
dns::Message query, std::function<void(dns::Message)> sendreply) override;
void void
TickTun(llarp_time_t now); TickTun(llarp_time_t now);
@ -96,6 +117,16 @@ namespace llarp
bool bool
SetupTun(); SetupTun();
void
SetupDNS();
/// overrides Endpoint
std::shared_ptr<dns::Server>
DNS() const override
{
return m_DNS;
};
/// overrides Endpoint /// overrides Endpoint
bool bool
SetupNetworking() override; SetupNetworking() override;
@ -249,8 +280,11 @@ namespace llarp
query->AddNXReply(); query->AddNXReply();
reply(*query); 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 /// maps ip address to timestamp last active
std::unordered_map<huint128_t, llarp_time_t> m_IPActivity; std::unordered_map<huint128_t, llarp_time_t> m_IPActivity;
@ -265,12 +299,6 @@ namespace llarp
huint128_t m_MaxIP; huint128_t m_MaxIP;
/// our ip range we are using /// our ip range we are using
llarp::IPRange m_OurRange; 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 /// list of strict connect addresses for hooks
std::vector<IpAddress> m_StrictConnectAddrs; std::vector<IpAddress> m_StrictConnectAddrs;
/// use v6? /// use v6?
@ -281,7 +309,7 @@ namespace llarp
std::shared_ptr<vpn::NetworkInterface> m_NetIf; 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; std::optional<net::TrafficPolicy> m_TrafficPolicy;
/// ranges we advetise as reachable /// ranges we advetise as reachable
@ -291,6 +319,9 @@ namespace llarp
/// a file to load / store the ephemeral address map to /// a file to load / store the ephemeral address map to
std::optional<fs::path> m_PersistAddrMapFile; std::optional<fs::path> m_PersistAddrMapFile;
/// for raw packet dns
std::shared_ptr<vpn::I_Packet_IO> m_RawDNS;
}; };
} // namespace handlers } // 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> [[nodiscard]] std::optional<int>
make_udp_handler( make_udp_handler(
const std::shared_ptr<llarp::service::Endpoint>& ep, 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_filter filter,
lokinet_udp_flow_recv_func recv, lokinet_udp_flow_recv_func recv,
lokinet_udp_flow_timeout_func timeout, 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>( 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; auto id = udp->m_SocketID;
std::promise<bool> result; std::promise<bool> result;
impl->router->loop()->call([ep, &result, udp, exposePort]() { impl->router->loop()->call([ep, &result, udp, exposePort]() {
if (auto pkt = ep->EgresPacketRouter()) 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)); udp->HandlePacketFrom(std::move(from), std::move(pkt));
}); });
result.set_value(true); result.set_value(true);
@ -903,8 +903,8 @@ extern "C"
auto lock = ctx->acquire(); auto lock = ctx->acquire();
if (auto ep = ctx->endpoint()) if (auto ep = ctx->endpoint())
{ {
if (auto maybe = if (auto maybe = ctx->make_udp_handler(
ctx->make_udp_handler(ep, llarp::huint16_t{exposedPort}, filter, recv, timeout, user)) ep, llarp::net::port_t::from_host(exposedPort), filter, recv, timeout, user))
{ {
result->socket_id = *maybe; result->socket_id = *maybe;
return 0; return 0;
@ -934,7 +934,7 @@ extern "C"
return EINVAL; return EINVAL;
std::shared_ptr<llarp::EndpointBase> ep; std::shared_ptr<llarp::EndpointBase> ep;
llarp::nuint16_t srcport{0}; 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(); auto lock = ctx->acquire();
if (auto itr = ctx->udp_sockets.find(remote->socket_id); itr != ctx->udp_sockets.end()) if (auto itr = ctx->udp_sockets.find(remote->socket_id); itr != ctx->udp_sockets.end())
@ -954,7 +954,7 @@ extern "C"
dstport, dstport,
llarp_buffer_t{reinterpret_cast<const uint8_t*>(ptr), len}); llarp_buffer_t{reinterpret_cast<const uint8_t*>(ptr), len});
if (pkt.sz == 0) if (pkt.empty())
return EINVAL; return EINVAL;
std::promise<int> ret; std::promise<int> ret;
ctx->impl->router->loop()->call([addr = *maybe, pkt = std::move(pkt), ep, &ret]() { ctx->impl->router->loop()->call([addr = *maybe, pkt = std::move(pkt), ep, &ret]() {

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

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

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

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

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

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

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

@ -50,9 +50,19 @@ namespace llarp
fromSockAddr(const SockAddr& address); fromSockAddr(const SockAddr& address);
/// get this as an explicit v4 or explicit v6 /// get this as an explicit v4 or explicit v6
std::variant<nuint32_t, nuint128_t> net::ipaddr_t
IP() const; 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 std::string
ToString() const; 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; return false;
if (!BEncodeMaybeReadDictInt("v", version, read, k, buf)) if (!BEncodeMaybeReadDictInt("v", version, read, k, buf))
return false; return false;
if (k == "a") if (k.startswith("a"))
{ {
in6_addr tmp; in6_addr tmp;
if (not bdecode_ip_string(buf, tmp)) if (not bdecode_ip_string(buf, tmp))
@ -79,7 +79,7 @@ namespace llarp
ipAddress = IpAddress(addr); ipAddress = IpAddress(addr);
return true; return true;
} }
if (k == "b") if (k.startswith("b"))
{ {
in6_addr tmp; in6_addr tmp;
if (not bdecode_ip_string(buf, 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); SockAddr addr(m_ipAddress);
const auto* addr6 = static_cast<const sockaddr_in6*>(addr); const auto* addr6 = static_cast<const sockaddr_in6*>(addr);
const uint8_t* raw = addr6->sin6_addr.s6_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 std::string

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

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

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <oxenc/endian.h>
#include <llarp/ev/ev.hpp> #include <llarp/ev/ev.hpp>
#include "net.hpp" #include "net.hpp"
#include <llarp/util/buffer.hpp> #include <llarp/util/buffer.hpp>
@ -10,11 +11,10 @@
namespace llarp::net namespace llarp::net
{ {
template <bool is_little_endian>
struct ip_header_le struct ip_header_le
{ {
unsigned int ihl : 4; uint8_t ihl : 4;
unsigned int version : 4; uint8_t version : 4;
uint8_t tos; uint8_t tos;
uint16_t tot_len; uint16_t tot_len;
uint16_t id; uint16_t id;
@ -26,11 +26,10 @@ namespace llarp::net
uint32_t daddr; uint32_t daddr;
}; };
template <> struct ip_header_be
struct ip_header_le<false>
{ {
unsigned int version : 4; uint8_t version : 4;
unsigned int ihl : 4; uint8_t ihl : 4;
uint8_t tos; uint8_t tos;
uint16_t tot_len; uint16_t tot_len;
uint16_t id; uint16_t id;
@ -42,9 +41,10 @@ namespace llarp::net
uint32_t daddr; 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 struct ipv6_header_preamble_le
{ {
unsigned char pad_small : 4; unsigned char pad_small : 4;
@ -52,24 +52,28 @@ namespace llarp::net
uint8_t pad[3]; uint8_t pad[3];
}; };
template <> struct ipv6_header_preamble_be
struct ipv6_header_preamble_le<false>
{ {
unsigned char version : 4; unsigned char version : 4;
unsigned char pad_small : 4; unsigned char pad_small : 4;
uint8_t pad[3]; 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 struct ipv6_header
{ {
union union
{ {
ipv6_header_preamble_le<oxenc::little_endian> preamble; ipv6_header_preamble preamble;
uint32_t flowlabel; uint32_t flowlabel;
} preamble; } preamble;
uint16_t payload_len; uint16_t payload_len;
uint8_t proto; uint8_t protocol;
uint8_t hoplimit; uint8_t hoplimit;
in6_addr srcaddr; in6_addr srcaddr;
in6_addr dstaddr; in6_addr dstaddr;
@ -81,6 +85,8 @@ namespace llarp::net
FlowLabel(llarp::nuint32_t label); FlowLabel(llarp::nuint32_t label);
}; };
static_assert(sizeof(ipv6_header) == 40);
/// "well known" ip protocols /// "well known" ip protocols
/// TODO: extend this to non "well known values" /// TODO: extend this to non "well known values"
enum class IPProtocol : uint8_t enum class IPProtocol : uint8_t
@ -109,34 +115,119 @@ namespace llarp::net
/// an Packet /// an Packet
struct IPPacket struct IPPacket
{ {
static constexpr size_t MaxSize = 1500; static constexpr size_t _max_size = 1500;
llarp_time_t timestamp; 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, UDP(nuint32_t srcaddr,
nuint16_t srcport, nuint16_t srcport,
nuint32_t dstaddr, nuint32_t dstaddr,
nuint16_t dstport, 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 [[deprecated("deprecated because of llarp_buffer_t")]] inline llarp_buffer_t
Buffer(); ConstBuffer() const
{
return llarp_buffer_t{_buf};
}
ManagedBuffer /// steal the underlying vector
ConstBuffer() const; inline std::vector<byte_t>
steal()
{
std::vector<byte_t> buf;
buf.resize(0);
std::swap(_buf, buf);
return buf;
}
bool inline byte_t*
Load(const llarp_buffer_t& buf); 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 struct CompareSize
{ {
bool bool
operator()(const IPPacket& left, const IPPacket& right) 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* inline ip_header*
Header() Header()
{ {
return reinterpret_cast<ip_header*>(&buf[0]); return reinterpret_cast<ip_header*>(data());
} }
inline const ip_header* inline const ip_header*
Header() const Header() const
{ {
return reinterpret_cast<const ip_header*>(&buf[0]); return reinterpret_cast<const ip_header*>(data());
} }
inline ipv6_header* inline ipv6_header*
HeaderV6() HeaderV6()
{ {
return reinterpret_cast<ipv6_header*>(&buf[0]); return reinterpret_cast<ipv6_header*>(data());
} }
inline const ipv6_header* inline const ipv6_header*
HeaderV6() const HeaderV6() const
{ {
return reinterpret_cast<const ipv6_header*>(&buf[0]); return reinterpret_cast<const ipv6_header*>(data());
} }
inline int inline int
@ -179,6 +270,15 @@ namespace llarp::net
return Header()->version; return Header()->version;
} }
inline byte_t
protocol() const
{
if (IsV4())
return Header()->protocol;
else
return HeaderV6()->protocol;
}
inline bool inline bool
IsV4() const IsV4() const
{ {
@ -226,6 +326,12 @@ namespace llarp::net
huint128_t huint128_t
dst4to6Lan() const; dst4to6Lan() const;
SockAddr
src() const;
SockAddr
dst() const;
/// get destination port if applicable /// get destination port if applicable
std::optional<nuint16_t> std::optional<nuint16_t>
DstPort() const; DstPort() const;
@ -238,6 +344,14 @@ namespace llarp::net
std::optional<std::pair<const char*, size_t>> std::optional<std::pair<const char*, size_t>>
L4Data() const; 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 void
UpdateIPv4Address(nuint32_t src, nuint32_t dst); 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 /// make an icmp unreachable reply packet based of this ip packet
std::optional<IPPacket> std::optional<IPPacket>
MakeICMPUnreachable() const; MakeICMPUnreachable() const;
std::function<void(net::IPPacket)> reply;
}; };
/// generate ip checksum /// generate ip checksum

@ -90,4 +90,15 @@ namespace llarp
return addr.ToString(); 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 } // namespace llarp

@ -9,10 +9,6 @@
namespace llarp namespace llarp
{ {
/// forward declare
bool
IsBogon(huint128_t ip);
struct IPRange struct IPRange
{ {
using Addr_t = huint128_t; using Addr_t = huint128_t;
@ -25,6 +21,12 @@ namespace llarp
: addr{std::move(address)}, netmask_bits{std::move(netmask)} : 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 static constexpr IPRange
FromIPv4(byte_t a, byte_t b, byte_t c, byte_t d, byte_t mask) FromIPv4(byte_t a, byte_t b, byte_t c, byte_t d, byte_t mask)
{ {
@ -42,8 +44,7 @@ namespace llarp
constexpr bool constexpr bool
IsV4() const IsV4() const
{ {
constexpr auto ipv4_map = IPRange{huint128_t{0x0000'ffff'0000'0000UL}, netmask_ipv6_bits(96)}; return V4MappedRange().Contains(addr);
return ipv4_map.Contains(addr);
} }
/// get address family /// get address family
@ -55,27 +56,6 @@ namespace llarp
return AF_INET6; 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 /// return the number of bits set in the hostmask
constexpr int constexpr int
HostmaskBits() const HostmaskBits() const
@ -117,6 +97,12 @@ namespace llarp
return Contains(net::ExpandV4(ip)); 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 /// get the highest address on this range
constexpr huint128_t constexpr huint128_t
HighestAddr() const HighestAddr() const
@ -147,6 +133,9 @@ namespace llarp
std::string std::string
BaseAddressString() const; BaseAddressString() const;
std::string
NetmaskString() const;
bool bool
FromString(std::string str); FromString(std::string str);

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

Loading…
Cancel
Save