Merge pull request #1731 from oxen-io/dev

v0.9.6
centos/8 v0.9.6
Jason Rhinelander 3 years ago committed by GitHub
commit 71663fafc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,6 +22,7 @@ local debian_pipeline(name, image,
cmake_extra='',
extra_cmds=[],
jobs=6,
tests=true,
loki_repo=false,
allow_fail=false) = {
kind: 'pipeline',
@ -55,10 +56,12 @@ local debian_pipeline(name, image,
'cmake .. -DWITH_SETCAP=OFF -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always -DCMAKE_BUILD_TYPE='+build_type+' ' +
(if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') +
'-DWITH_LTO=' + (if lto then 'ON ' else 'OFF ') +
(if tests then '' else '-DWITH_TESTS=OFF ') +
cmake_extra,
'VERBOSE=1 make -j' + jobs,
'../contrib/ci/drone-gdb.sh ./test/testAll --use-colour yes',
] + extra_cmds,
]
+ (if tests then ['../contrib/ci/drone-gdb.sh ./test/testAll --use-colour yes'] else [])
+ extra_cmds,
}
],
};
@ -76,7 +79,13 @@ local apk_builder(name, image, extra_cmds=[], allow_fail=false, jobs=6) = {
[if allow_fail then "failure"]: "ignore",
environment: { SSH_KEY: { from_secret: "SSH_KEY" }, ANDROID: "android" },
commands: [
'VERBOSE=1 JOBS='+jobs+' NDK=/usr/lib/android-ndk ./contrib/android.sh'
'VERBOSE=1 JOBS='+jobs+' NDK=/usr/lib/android-ndk ./contrib/android.sh',
'git clone https://github.com/majestrate/lokinet-mobile',
'cp -av lokinet-jni-*/* lokinet-mobile/lokinet_lib/android/src/main/jniLibs/',
'cd lokinet-mobile',
'flutter build apk --debug',
'cd ..',
'cp lokinet-mobile/build/app/outputs/apk/debug/app-debug.apk lokinet.apk'
] + extra_cmds
}
]
@ -191,12 +200,7 @@ local mac_builder(name,
// basic system headers. WTF apple:
'export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"',
'ulimit -n 1024', // because macos sets ulimit to 256 for some reason yeah idk
'mkdir build',
'cd build',
'cmake .. -DCMAKE_CXX_FLAGS=-fcolor-diagnostics -DCMAKE_BUILD_TYPE='+build_type+' ' +
(if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') + cmake_extra,
'VERBOSE=1 make -j' + jobs,
'./test/testAll --use-colour yes',
'./contrib/mac.sh'
] + extra_cmds,
}
]
@ -252,7 +256,7 @@ local mac_builder(name,
]),
// Static build (on bionic) which gets uploaded to builds.lokinet.dev:
debian_pipeline("Static (bionic amd64)", docker_base+'ubuntu-bionic', deps='g++-8 python3-dev automake libtool', lto=true,
debian_pipeline("Static (bionic amd64)", docker_base+'ubuntu-bionic', deps='g++-8 python3-dev automake libtool', lto=true, tests=false,
cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON -DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8 ' +
'-DCMAKE_CXX_FLAGS="-march=x86-64 -mtune=haswell" -DCMAKE_C_FLAGS="-march=x86-64 -mtune=haswell" -DNATIVE_BUILD=OFF ' +
'-DWITH_SYSTEMD=OFF',
@ -274,9 +278,4 @@ local mac_builder(name,
// Macos builds:
mac_builder('macOS (Release)'),
mac_builder('macOS (Debug)', build_type='Debug'),
mac_builder('macOS (Static)', cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON -DDOWNLOAD_SODIUM=FORCE -DDOWNLOAD_CURL=FORCE -DDOWNLOAD_UV=FORCE',
extra_cmds=[
'../contrib/ci/drone-check-static-libs.sh',
'../contrib/ci/drone-static-upload.sh'
]),
]

@ -5,9 +5,14 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Has to be set before `project()`, and ignored on non-macos:
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "macOS deployment target (Apple clang only)")
set(LANGS C CXX)
if(APPLE)
set(LANGS ${LANGS} OBJC Swift)
endif()
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
foreach(lang C CXX)
foreach(lang ${LANGS})
if(NOT DEFINED CMAKE_${lang}_COMPILER_LAUNCHER AND NOT CMAKE_${lang}_COMPILER MATCHES ".*/ccache")
message(STATUS "Enabling ccache for ${lang}")
set(CMAKE_${lang}_COMPILER_LAUNCHER ${CCACHE_PROGRAM} CACHE STRING "")
@ -15,10 +20,17 @@ if(CCACHE_PROGRAM)
endforeach()
endif()
project(lokinet
VERSION 0.9.5
VERSION 0.9.6
DESCRIPTION "lokinet - IP packet onion router"
LANGUAGES C CXX)
LANGUAGES ${LANGS})
if(APPLE)
# Apple build number: must be incremented to submit a new build for the same lokinet version,
# should be reset to 0 when the lokinet version increments.
set(LOKINET_APPLE_BUILD 0)
endif()
set(RELEASE_MOTTO "A Series of Tubes" CACHE STRING "Release motto")
@ -29,10 +41,8 @@ if(RELEASE_MOTTO AND CMAKE_BUILD_TYPE MATCHES "[Rr][Ee][Ll][Ee][Aa][Ss][Ee]")
add_definitions(-DLLARP_RELEASE_MOTTO="${RELEASE_MOTTO}")
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
# Core options
option(USE_AVX2 "enable avx2 code" OFF)
option(USE_NETNS "enable networking namespace support. Linux only" OFF)
@ -94,14 +104,6 @@ endif()
add_definitions(-D${CMAKE_SYSTEM_NAME})
if(MSVC_VERSION)
enable_language(ASM_MASM)
list(APPEND CMAKE_ASM_MASM_SOURCE_FILE_EXTENSIONS s)
add_definitions(-D_WIN32_WINNT=0x0600 -DNOMINMAX -DSODIUM_STATIC)
else()
enable_language(ASM)
endif()
include(cmake/solaris.cmake)
include(cmake/win32.cmake)
@ -184,8 +186,12 @@ else()
endif()
# this is messing with release builds
add_compile_options(-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0)
if(NOT APPLE)
add_compile_options(-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations -Werror=vla)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wno-unknown-warning-option)
endif()
endif()
if (NOT CMAKE_SYSTEM_NAME MATCHES "Linux" AND SHADOW)
message( FATAL_ERROR "shadow-framework is Linux only" )
@ -213,17 +219,6 @@ if(TRACY_ROOT)
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wno-unknown-warning-option)
endif()
if (NOT MSVC_VERSION)
add_compile_options(-Wall -Wextra -Wno-unknown-pragmas)
# vla are evil
add_compile_options(-Werror=vla)
add_compile_options(-Wno-unused-function -Wno-deprecated-declarations)
endif()
include(cmake/coverage.cmake)
# these vars are set by the cmake toolchain spec
@ -231,18 +226,20 @@ if (WOW64_CROSS_COMPILE OR WIN64_CROSS_COMPILE)
include(cmake/cross_compile.cmake)
endif()
if(NATIVE_BUILD)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL ppc64le)
add_compile_options(-mcpu=native -mtune=native)
else()
add_compile_options(-march=native -mtune=native)
endif()
elseif(NOT NON_PC_TARGET)
if (USE_AVX2)
add_compile_options(-march=haswell -mtune=haswell -mfpmath=sse)
else()
# Public binary releases
add_compile_options(-march=nocona -mtune=haswell -mfpmath=sse)
if(NOT APPLE)
if(NATIVE_BUILD)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL ppc64le)
add_compile_options(-mcpu=native -mtune=native)
else()
add_compile_options(-march=native -mtune=native)
endif()
elseif(NOT NON_PC_TARGET)
if (USE_AVX2)
add_compile_options(-march=haswell -mtune=haswell -mfpmath=sse)
else()
# Public binary releases
add_compile_options(-march=nocona -mtune=haswell -mfpmath=sse)
endif()
endif()
endif()
@ -332,12 +329,11 @@ endif()
add_subdirectory(crypto)
add_subdirectory(llarp)
add_subdirectory(daemon)
if(WITH_HIVE)
add_subdirectory(pybind)
endif()
if (NOT SHADOW)
if(WITH_TESTS OR WITH_HIVE)
add_subdirectory(test)
@ -361,6 +357,6 @@ if(NOT TARGET uninstall)
endif()
if(BUILD_PACKAGE)
if(BUILD_PACKAGE AND NOT APPLE)
include(cmake/installer.cmake)
endif()

@ -5,10 +5,10 @@
set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads")
set(OPENSSL_VERSION 1.1.1k CACHE STRING "openssl version")
set(OPENSSL_VERSION 1.1.1l CACHE STRING "openssl version")
set(OPENSSL_MIRROR ${LOCAL_MIRROR} https://www.openssl.org/source CACHE STRING "openssl download mirror(s)")
set(OPENSSL_SOURCE openssl-${OPENSSL_VERSION}.tar.gz)
set(OPENSSL_HASH SHA256=892a0875b9872acd04a9fde79b1f943075d5ea162415de3047c327df33fbaee5
set(OPENSSL_HASH SHA256=0b7a3e5e59c34827fe0c3a74b7ec8baef302b98fa80088d7f9153aa16fa76bd1
CACHE STRING "openssl source hash")
set(EXPAT_VERSION 2.3.0 CACHE STRING "expat version")

@ -7,10 +7,7 @@ if(WIN32)
include(cmake/win32_installer_deps.cmake)
endif()
if(APPLE)
include(cmake/macos_installer_deps.cmake)
endif()
# This must always be last!
include(CPack)

@ -1,113 +0,0 @@
# macos specific cpack stuff goes here
# Here we build lokinet-network-control-panel into 'lokinet-gui.app' in "extra/" where a postinstall
# script will then move it to /Applications/.
set(LOKINET_GUI_REPO "https://github.com/oxen-io/loki-network-control-panel.git"
CACHE STRING "Can be set to override the default lokinet-gui git repository")
set(LOKINET_GUI_CHECKOUT "origin/master"
CACHE STRING "Can be set to specify a particular branch or tag to build from LOKINET_GUI_REPO")
set(MACOS_SIGN_APP "" # FIXME: it doesn't use a Apple Distribution key because WTF knows.
CACHE STRING "enable codesigning of the stuff inside the .app and the lokinet binary -- use a 'Apple Distribution' key (or description) from `security find-identity -v`")
set(MACOS_SIGN_PKG ""
CACHE STRING "enable codesigning of the .pkg -- use a 'Developer ID Installer' key (or description) from `security find-identity -v`")
set(MACOS_NOTARIZE_USER ""
CACHE STRING "set macos notarization username; can also set it in ~/.notarization.cmake")
set(MACOS_NOTARIZE_PASS ""
CACHE STRING "set macos notarization password; can also set it in ~/.notarization.cmake")
set(MACOS_NOTARIZE_ASC ""
CACHE STRING "set macos notarization asc provider; can also set it in ~/.notarization.cmake")
include(ExternalProject)
message(STATUS "Building UninstallLokinet.app")
ExternalProject_Add(lokinet-uninstaller
SOURCE_DIR ${CMAKE_SOURCE_DIR}/contrib/macos/uninstaller
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR} -DMACOS_SIGN=${MACOS_SIGN_APP}
-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}
)
message(STATUS "Building LokinetGUI.app from ${LOKINET_GUI_REPO} @ ${LOKINET_GUI_CHECKOUT}")
if(NOT BUILD_STATIC_DEPS)
message(FATAL_ERROR "Building an installer on macos requires -DBUILD_STATIC_DEPS=ON")
endif()
ExternalProject_Add(lokinet-gui
DEPENDS oxenmq::oxenmq
GIT_REPOSITORY "${LOKINET_GUI_REPO}"
GIT_TAG "${LOKINET_GUI_CHECKOUT}"
CMAKE_ARGS -DMACOS_APP=ON -DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR} -DMACOS_SIGN=${MACOS_SIGN_APP}
-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DBUILD_SHARED_LIBS=OFF
"-DOXENMQ_LIBRARIES=$<TARGET_FILE:oxenmq::oxenmq>$<SEMICOLON>$<TARGET_FILE:libzmq>$<SEMICOLON>$<TARGET_FILE:sodium>"
"-DOXENMQ_INCLUDE_DIRS=$<TARGET_PROPERTY:oxenmq::oxenmq,INCLUDE_DIRECTORIES>"
)
install(PROGRAMS ${CMAKE_SOURCE_DIR}/contrib/macos/lokinet_uninstall.sh
DESTINATION "bin/"
COMPONENT lokinet)
install(DIRECTORY ${PROJECT_BINARY_DIR}/LokinetGUI.app
DESTINATION "../../Applications/Lokinet"
USE_SOURCE_PERMISSIONS
COMPONENT gui
PATTERN "*"
)
install(DIRECTORY ${PROJECT_BINARY_DIR}/UninstallLokinet.app
DESTINATION "../../Applications/Lokinet"
USE_SOURCE_PERMISSIONS
COMPONENT gui
PATTERN "*"
)
# copy files that will be later moved by the postinstall script to proper locations
install(FILES ${CMAKE_SOURCE_DIR}/contrib/macos/lokinet_macos_daemon_script.sh
${CMAKE_SOURCE_DIR}/contrib/macos/network.loki.lokinet.daemon.plist
${CMAKE_SOURCE_DIR}/contrib/macos/lokinet-newsyslog.conf
DESTINATION "extra/"
COMPONENT lokinet)
set(CPACK_COMPONENTS_ALL lokinet gui)
set(CPACK_COMPONENT_LOKINET_DISPLAY_NAME "Lokinet Service")
set(CPACK_COMPONENT_LOKINET_DESCRIPTION "Main Lokinet runtime service, managed by Launchd")
set(CPACK_COMPONENT_GUI_DISPLAY_NAME "Lokinet GUI")
set(CPACK_COMPONENT_GUI_DESCRIPTION "Small GUI which provides stats and limited runtime control of the Lokinet service. Resides in the system tray.")
set(CPACK_GENERATOR "productbuild")
set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/lokinet")
set(CPACK_PREINSTALL_LOKINET_SCRIPT ${CMAKE_SOURCE_DIR}/contrib/macos/preinstall)
set(CPACK_POSTFLIGHT_LOKINET_SCRIPT ${CMAKE_SOURCE_DIR}/contrib/macos/postinstall)
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE.txt")
set(CPACK_PRODUCTBUILD_IDENTITY_NAME "${MACOS_SIGN_PKG}")
if(MACOS_SIGN_APP)
add_custom_target(sign ALL
echo "Signing lokinet and lokinet-vpn binaries"
COMMAND codesign -s "${MACOS_SIGN_APP}" --strict --options runtime --force -vvv $<TARGET_FILE:lokinet> $<TARGET_FILE:lokinet-vpn>
DEPENDS lokinet lokinet-vpn
)
endif()
if(MACOS_SIGN_APP AND MACOS_SIGN_PKG)
if(NOT MACOS_NOTARIZE_USER)
if(EXISTS "$ENV{HOME}/.notarization.cmake")
include("$ENV{HOME}/.notarization.cmake")
endif()
endif()
if(MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC)
message(STATUS "'notarization' target enabled")
configure_file(${CMAKE_SOURCE_DIR}/contrib/macos/notarize.py.in ${CMAKE_CURRENT_BINARY_DIR}/contrib/notarize.py ESCAPE_QUOTES @ONLY)
file(COPY ${CMAKE_CURRENT_BINARY_DIR}/contrib/notarize.py DESTINATION ${PROJECT_BINARY_DIR} FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
add_custom_target(notarize ./notarize.py)
else()
message(WARNING "Not enable 'notarization' target: signing is enabled but notarization info not provided. Create ~/.notarization.cmake or set cmake parameters directly")
endif()
endif()

@ -3,11 +3,13 @@
import sys
import pprint
if len(sys.argv) != 2 or sys.argv[1].startswith('-'):
if len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] == '-'):
f = sys.stdin.buffer
elif len(sys.argv) != 2 or sys.argv[1].startswith('-'):
print("Usage: {} FILE -- dumps a bencoded file".format(sys.argv[0]), file=sys.stderr)
sys.exit(1)
f = open(sys.argv[1], 'rb')
else:
f = open(sys.argv[1], 'rb')
class HexPrinter():
def __init__(self, data):

@ -39,11 +39,10 @@ if [ -e build-windows ]; then
# zipit up yo
archive="$base.zip"
zip -r "$archive" "$base"
elif [ -e build-android ] ; then
elif [ -e lokinet.apk ] ; then
# android af ngl
cp -av lokinet-jni-* "$base"
archive="$base.tar.xz"
tar cJvf "$archive" "$base"
archive="$base.apk"
cp -av lokinet.apk "$archive"
else
cp -av daemon/lokinet daemon/lokinet-vpn daemon/lokinet-bootstrap "$base"
# tar dat shiz up yo

@ -3,6 +3,9 @@
CLANG_FORMAT_DESIRED_VERSION=11
binary=$(which clang-format-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null)
if [ $? -ne 0 ]; then
binary=$(which clang-format-mp-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null)
fi
if [ $? -ne 0 ]; then
binary=$(which clang-format 2>/dev/null)
if [ $? -ne 0 ]; then
@ -18,7 +21,23 @@ fi
cd "$(dirname $0)/../"
if [ "$1" = "verify" ] ; then
exit $($binary --output-replacements-xml $(find jni daemon llarp include pybind | grep -E '\.[hc](pp)?$' | grep -v '\#') | grep '</replacement>' | wc -l)
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
exit 1
fi
else
$binary -i $(find jni daemon llarp include pybind | grep -E '\.[hc](pp)?$' | grep -v '\#') &> /dev/null
$binary -i $(find jni daemon llarp include pybind | grep -E '\.([hc](pp)?|mm)$' | grep -v '\#') &> /dev/null
fi
swift_format=$(which swiftformat 2>/dev/null)
if [ $? -eq 0 ]; then
if [ "$1" = "verify" ] ; then
for f in $(find daemon | grep -E '\.swift$' | grep -v '\#') ; do
if [ $($swift_format --quiet --dryrun < "$f" | diff "$f" - | wc -l) -ne 0 ] ; then
exit 1
fi
done
else
$swift_format --quiet $(find daemon | grep -E '\.swift$' | grep -v '\#')
fi
fi

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 189.4 189.4" style="enable-background:new 0 0 189.4 189.4;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<polygon class="st0" points="113.6,132.6 94.7,151.5 75.8,132.6 56.8,151.5 94.7,189.4 132.6,151.5 "/>
<polygon class="st0" points="132.6,113.6 151.5,94.7 132.6,75.8 151.5,56.8 189.4,94.7 151.5,132.6 "/>
<polygon class="st0" points="56.8,75.8 37.9,94.7 56.8,113.6 37.9,132.6 0,94.7 37.9,56.8 "/>
<polygon class="st0" points="75.8,56.8 94.7,37.9 113.6,56.8 132.6,37.9 94.7,0 56.8,37.9 "/>
<rect x="100.2" y="100.2" transform="matrix(0.7071 0.7071 -0.7071 0.7071 113.6329 -47.0683)" class="st0" width="26.8" height="26.8"/>
<rect x="62.4" y="62.4" transform="matrix(0.7071 0.7071 -0.7071 0.7071 75.7552 -31.3789)" class="st0" width="26.8" height="26.8"/>
<rect x="100.2" y="62.4" transform="matrix(0.7071 0.7071 -0.7071 0.7071 86.8493 -58.1624)" class="st0" width="26.8" height="26.8"/>
<rect x="62.4" y="100.2" transform="matrix(0.7071 0.7071 -0.7071 0.7071 102.5388 -20.2848)" class="st0" width="26.8" height="26.8"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1,39 @@
#!/bin/bash
#
# Build the shit on mac
#
# You will generally need to add: -DCODESIGN_APP=... to make this work, and (unless you are a
# lokinet team member) will need to pay Apple money for your own team ID and arse around with
# provisioning profiles. See macos/README.txt.
#
set -e
set +x
if ! [ -f LICENSE.txt ] || ! [ -d llarp ]; then
echo "You need to run this as ./contrib/mac.sh from the top-level lokinet project directory"
fi
mkdir -p build-mac
cd build-mac
cmake \
-G Ninja \
-DBUILD_STATIC_DEPS=ON \
-DBUILD_PACKAGE=ON \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTING=OFF \
-DBUILD_LIBLOKINET=OFF \
-DWITH_TESTS=OFF \
-DNATIVE_BUILD=OFF \
-DSTATIC_LINK=ON \
-DWITH_SYSTEMD=OFF \
-DFORCE_OXENMQ_SUBMODULE=ON \
-DSUBMODULE_CHECK=OFF \
-DWITH_LTO=ON \
-DCMAKE_BUILD_TYPE=Release \
"$@" \
..
ninja sign
echo -e "Build complete, your app is here:\n"
ls -lad $(pwd)/daemon/lokinet.app
echo ""

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Lokinet</string>
<key>CFBundleExecutable</key>
<string>MacOS/lokinet</string>
<key>CFBundleIdentifier</key>
<string>com.loki-project.lokinet</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>lokinet</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>@lokinet_VERSION@</string>
<key>CFBundleVersion</key>
<string>@lokinet_VERSION@.@LOKINET_APPLE_BUILD@</string>
</dict>
</plist>

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Lokinet</string>
<key>CFBundleExecutable</key>
<string>lokinet-extension</string>
<key>CFBundleIdentifier</key>
<string>com.loki-project.lokinet.network-extension</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleName</key>
<string>lokinet</string>
<key>CFBundleVersion</key>
<string>@lokinet_VERSION@</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>LLARPPacketTunnel</string>
</dict>
</dict>
</plist>

@ -0,0 +1,38 @@
This directory contains the magical incantations and random voodoo symbols needed to coax an Apple
build. There's no reason builds have to be this stupid, except that Apple wants to funnel everyone
into the no-CI, no-help, undocumented, non-toy-apps-need-not-apply modern Apple culture.
This is disgusting.
But it gets worse.
The following two files, in particular, are the very worst manifestations of this already toxic
Apple cancer: they are required for proper permissions to run on macOS, are undocumented, and can
only be regenerated through the entirely closed source Apple Developer backend, for which you have
to pay money first to get a team account (a personal account will not work), and they lock the
resulting binaries to only run on individually selected Apple computers selected at the time the
profile is provisioned (with no ability to allow it to run anywhere).
lokinet.provisionprofile
lokinet-extension.provisionprofile
This is actively hostile to open source development, but that is nothing new for Apple.
In order to make things work, you'll have to replace these provisioning profiles with your own
(after paying Apple for the privilege of developing on their platform, of course) and change all the
team/application/bundle IDs to reference your own team, matching the provisioning profiles. The
provisioning profiles must be a "macOS Development" provisioning profile, and must include the
signing keys and the authorized devices on which you want to run it. (The profiles bundled in this
repository contains the lokinet team's "Apple Development" keys associated with the Oxen project,
and mac dev boxes. This is *useless* for anyone else).
Also take note that you *must not* put a development build `lokinet.app` inside /Applications
because if you do, it won't work because *on top* of the ridiculous signing and entitlement bullshit
that Apple makes you jump through, the rules *also* differ for binaries placed in /Applications
versus binaries placed elsewhere, but like everything else here, it is entirely undocumented.
If you are reading this to try to build Lokinet for yourself for an Apple operating system and
simultaneously care about open source, privacy, or freedom then you, my friend, are a walking
contradiction: you are trying to get Lokinet to work on a platform that actively despises open
source, privacy, and freedom. Even Windows is a better choice in all of these categories than
Apple.

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>SUQ8J2PCT7.com.loki-project.lokinet.network-extension</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>SUQ8J2PCT7</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.get-task-allow</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>SUQ8J2PCT7.com.loki-project.lokinet</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
<string>dns-proxy</string>
<string>dns-settings</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>SUQ8J2PCT7</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.get-task-allow</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -e
codesign --verbose=4 --force -s "@CODESIGN_APPEX@" \
--entitlements "@PROJECT_SOURCE_DIR@/contrib/macos/lokinet-extension.entitlements.plist" \
--deep --strict --timestamp --options=runtime "@SIGN_TARGET@/Contents/PlugIns/lokinet-extension.appex"
for file in "@SIGN_TARGET@/Contents/MacOS/lokinet" "@SIGN_TARGET@" ; do
codesign --verbose=4 --force -s "@CODESIGN_APP@" \
--entitlements "@PROJECT_SOURCE_DIR@/contrib/macos/lokinet.entitlements.plist" \
--deep --strict --timestamp --options=runtime "$file"
done

@ -1,4 +1,9 @@
#!/bin/bash
#
# helper script for me for when i cross compile for windows
# t. jeff
#
set -e
set +x
mkdir -p build-windows

@ -1,7 +1,14 @@
add_executable(lokinet lokinet.cpp)
add_executable(lokinet-vpn lokinet-vpn.cpp)
add_executable(lokinet-bootstrap lokinet-bootstrap.cpp)
enable_lto(lokinet lokinet-vpn lokinet-bootstrap)
if(APPLE)
add_executable(lokinet lokinet.swift)
enable_lto(lokinet)
else()
add_executable(lokinet lokinet.cpp)
add_executable(lokinet-bootstrap lokinet-bootstrap.cpp)
enable_lto(lokinet lokinet-vpn lokinet-bootstrap)
endif()
if(TRACY_ROOT)
target_sources(lokinet PRIVATE ${TRACY_ROOT}/TracyClient.cpp)
@ -23,14 +30,21 @@ if(CMAKE_SYSTEM_NAME MATCHES "Linux")
endif()
endif()
target_link_libraries(lokinet-bootstrap PUBLIC cpr::cpr)
if(NOT WIN32)
find_package(OpenSSL REQUIRED)
# because debian sid's curl doesn't link against openssl for some godawful cursed reason
target_link_libraries(lokinet-bootstrap PUBLIC OpenSSL::SSL OpenSSL::Crypto)
if(NOT APPLE)
target_link_libraries(lokinet-bootstrap PUBLIC cpr::cpr)
if(NOT WIN32)
find_package(OpenSSL REQUIRED)
# because debian sid's curl doesn't link against openssl for some godawful cursed reason
target_link_libraries(lokinet-bootstrap PUBLIC OpenSSL::SSL OpenSSL::Crypto)
endif()
endif()
foreach(exe lokinet lokinet-vpn lokinet-bootstrap)
set(exetargets lokinet lokinet-vpn)
if(NOT APPLE)
list(APPEND exetargets lokinet-bootstrap)
endif()
foreach(exe ${exetargets})
if(WIN32 AND NOT MSVC_VERSION)
target_sources(${exe} PRIVATE ../llarp/win32/version.rc)
target_link_libraries(${exe} PRIVATE -static-libstdc++ -static-libgcc --static -Wl,--pic-executable,-e,mainCRTStartup,--subsystem,console:5.00)
@ -38,18 +52,77 @@ foreach(exe lokinet lokinet-vpn lokinet-bootstrap)
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
target_link_directories(${exe} PRIVATE /usr/local/lib)
endif()
target_link_libraries(${exe} PRIVATE liblokinet)
target_link_libraries(${exe} PUBLIC liblokinet)
if(WITH_JEMALLOC)
target_link_libraries(${exe} PUBLIC jemalloc)
endif()
target_include_directories(${exe} PRIVATE ${CMAKE_SOURCE_DIR})
target_include_directories(${exe} PUBLIC "${PROJECT_SOURCE_DIR}")
target_compile_definitions(${exe} PRIVATE -DVERSIONTAG=${GIT_VERSION_REAL})
add_log_tag(${exe})
if(should_install)
install(TARGETS ${exe} RUNTIME DESTINATION bin COMPONENT lokinet)
if(APPLE)
install(TARGETS ${exe} BUNDLE DESTINATION "${PROJECT_BINARY_DIR}" COMPONENT lokinet)
else()
install(TARGETS ${exe} RUNTIME DESTINATION bin COMPONENT lokinet)
endif()
endif()
endforeach()
if(APPLE)
set(CODESIGN_APP "" CACHE STRING "codesign the macos app using this key identity")
set(CODESIGN_APPEX "${CODESIGN_APP}" CACHE STRING "codesign the internal extension using this key identity; defaults to CODESIGN_APP if empty")
set(mac_icon ${CMAKE_CURRENT_BINARY_DIR}/lokinet.icns)
add_custom_command(OUTPUT ${mac_icon}
COMMAND ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${mac_icon}
DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh)
add_custom_target(icons DEPENDS ${mac_icon})
add_dependencies(lokinet icons lokinet-extension)
file(DOWNLOAD "https://seed.lokinet.org/lokinet.signed" ${CMAKE_CURRENT_BINARY_DIR}/bootstrap.signed)
add_custom_command(TARGET lokinet
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/bootstrap.signed
$<TARGET_BUNDLE_DIR:lokinet-extension>/Contents/Resources/bootstrap.signed
COMMAND mkdir -p $<TARGET_BUNDLE_DIR:lokinet>/Contents/PlugIns
COMMAND cp -a $<TARGET_BUNDLE_DIR:lokinet-extension> $<TARGET_BUNDLE_DIR:lokinet>/Contents/PlugIns/
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.provisionprofile
$<TARGET_BUNDLE_DIR:lokinet>/Contents/embedded.provisionprofile
)
set_target_properties(lokinet
PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_STRING "Lokinet IP Packet Onion Router"
MACOSX_BUNDLE_BUNDLE_NAME "Lokinet"
MACOSX_BUNDLE_BUNDLE_VERSION "${lokinet_VERSION}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${lokinet_VERSION}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${lokinet_VERSION_MAJOR}.${lokinet_VERSION_MINOR}"
MACOSX_BUNDLE_GUI_IDENTIFIER "com.loki-project.lokinet"
MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/contrib/macos/Info.plist.in"
MACOSX_BUNDLE_ICON_FILE "${mac_icon}"
MACOSX_BUNDLE_COPYRIGHT "© 2021, The Oxen Project")
if (CODESIGN_APP AND CODESIGN_APPEX)
message(STATUS "codesigning with ${CODESIGN_APP} (app) ${CODESIGN_APPEX} (appex)")
set(SIGN_TARGET "${CMAKE_CURRENT_BINARY_DIR}/lokinet.app")
configure_file(
"${PROJECT_SOURCE_DIR}/contrib/macos/sign.sh.in"
"${PROJECT_BINARY_DIR}/sign.sh"
@ONLY)
add_custom_target(
sign
DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" lokinet lokinet-extension
COMMAND "${PROJECT_BINARY_DIR}/sign.sh"
)
else()
message(WARNING "Not codesigning: CODESIGN_APP (=${CODESIGN_APP}) and/or CODESIGN_APPEX (=${CODESIGN_APPEX}) are not set")
add_custom_target(
sign
DEPENDS lokinet lokinet-extension
COMMAND "true")
endif()
endif()
if(SETCAP)
install(CODE "execute_process(COMMAND ${SETCAP} cap_net_admin,cap_net_bind_service=+eip ${CMAKE_INSTALL_PREFIX}/bin/lokinet)")
endif()

@ -9,6 +9,7 @@
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#ifndef _WIN32
#include <openssl/x509.h>
@ -28,6 +29,26 @@ namespace
std::cout << msg << std::endl;
return 1;
}
int
print_help(std::string exe)
{
std::cout << R"(Lokinet bootstrap.signed fetchy program thing
Downloads the initial bootstrap.signed for lokinet into a local file from a
default or user defined server via the reachable network.
Usage: )" << exe
<< R"( [bootstrap_url [output_file]]
bootstrap_url can be specified as a full URL, or a special named value
("mainnet" or "testnet") to download from the pre-defined mainnet or testnet
bootstrap URLs.
)";
return 0;
}
} // namespace
int
@ -42,6 +63,15 @@ main(int argc, char* argv[])
std::string bootstrap_url = bootstrap_urls.at("lokinet");
fs::path outputfile{llarp::GetDefaultBootstrap()};
const std::unordered_set<std::string> help_args = {"-h", "--help"};
for (int idx = 1; idx < argc; idx++)
{
const std::string arg{argv[idx]};
if (help_args.count(arg))
return print_help(argv[0]);
}
if (argc > 1)
{
if (auto itr = bootstrap_urls.find(argv[1]); itr != bootstrap_urls.end())
@ -57,7 +87,7 @@ main(int argc, char* argv[])
{
outputfile = fs::path{argv[2]};
}
std::cout << "fetching " << bootstrap_url << std::endl;
cpr::Response resp =
#ifdef _WIN32
cpr::Get(
@ -79,6 +109,7 @@ main(int argc, char* argv[])
{
try
{
std::cout << "writing bootstrap file to: " << outputfile << std::endl;
fs::ofstream ofs{outputfile, std::ios::binary};
ofs.exceptions(fs::ofstream::failbit);
ofs << data;

@ -0,0 +1,100 @@
import AppKit
import Foundation
import NetworkExtension
let app = NSApplication.shared
class LokinetMain: NSObject, NSApplicationDelegate {
var vpnManager = NETunnelProviderManager()
let lokinetComponent = "com.loki-project.lokinet.network-extension"
func applicationDidFinishLaunching(_: Notification) {
setupVPNJizz()
}
func bail() {
app.terminate(self)
}
func setupVPNJizz() {
NSLog("Starting up lokinet")
NETunnelProviderManager.loadAllFromPreferences { [self] (savedManagers: [NETunnelProviderManager]?, error: Error?) in
if let error = error {
NSLog(error.localizedDescription)
bail()
return
}
if let savedManagers = savedManagers {
for manager in savedManagers {
if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.lokinetComponent {
NSLog("%@", manager)
NSLog("Found saved VPN Manager")
self.vpnManager = manager
}
}
}
let providerProtocol = NETunnelProviderProtocol()
providerProtocol.serverAddress = "loki.loki" // Needs to be set to some non-null dummy value
providerProtocol.username = "anonymous"
providerProtocol.providerBundleIdentifier = self.lokinetComponent
providerProtocol.enforceRoutes = true
// macos seems to have trouble when this is true, and reports are that this breaks and
// doesn't do what it says on the tin in the first place. Needs more testing.
providerProtocol.includeAllNetworks = false
self.vpnManager.protocolConfiguration = providerProtocol
self.vpnManager.isEnabled = true
// self.vpnManager.isOnDemandEnabled = true
self.vpnManager.localizedDescription = "lokinet"
self.vpnManager.saveToPreferences(completionHandler: { error -> Void in
if error != nil {
NSLog("Error saving to preferences")
NSLog(error!.localizedDescription)
bail()
} else {
self.vpnManager.loadFromPreferences(completionHandler: { error in
if error != nil {
NSLog("Error loading from preferences")
NSLog(error!.localizedDescription)
bail()
} else {
do {
NSLog("Trying to start")
self.initializeConnectionObserver()
try self.vpnManager.connection.startVPNTunnel()
} catch let error as NSError {
NSLog(error.localizedDescription)
bail()
} catch {
NSLog("There was a fatal error")
bail()
}
}
})
}
})
}
}
func initializeConnectionObserver() {
NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: vpnManager.connection, queue: OperationQueue.main) { _ -> Void in
if self.vpnManager.connection.status == .invalid {
NSLog("VPN configuration is invalid")
} else if self.vpnManager.connection.status == .disconnected {
NSLog("VPN is disconnected.")
} else if self.vpnManager.connection.status == .connecting {
NSLog("VPN is connecting...")
} else if self.vpnManager.connection.status == .reasserting {
NSLog("VPN is reasserting...")
} else if self.vpnManager.connection.status == .disconnecting {
NSLog("VPN is disconnecting...")
} else if self.vpnManager.connection.status == .connected {
NSLog("VPN Connected")
}
}
}
}
let delegate = LokinetMain()
app.delegate = delegate
app.run()

@ -50,6 +50,16 @@ add_library(uvw INTERFACE)
target_include_directories(uvw INTERFACE uvw/src)
target_link_libraries(uvw INTERFACE libuv)
# We don't need any of these as we don't use the ssl crypto helper code at all:
set(ENABLE_GNUTLS OFF CACHE BOOL "Disable gnutls for ngtcp2")
set(ENABLE_OPENSSL OFF CACHE BOOL "Disable openssl for ngtcp2")
set(ENABLE_BORINGSSL OFF CACHE BOOL "Disable boringssl for ngtcp2")
add_definitions(-D_GNU_SOURCE)
add_subdirectory(ngtcp2 EXCLUDE_FROM_ALL)
# cpr configuration. Ideally we'd just do this via add_subdirectory, but cpr's cmake requires
# 3.15+, and we target lower than that (and this is fairly simple to build).
@ -75,11 +85,3 @@ target_link_libraries(cpr PUBLIC CURL::libcurl)
target_include_directories(cpr PUBLIC cpr/include)
target_compile_definitions(cpr PUBLIC CPR_CURL_NOSIGNAL)
add_library(cpr::cpr ALIAS cpr)
# We don't need any of these as we don't use the ssl crypto helper code at all:
set(ENABLE_GNUTLS OFF CACHE BOOL "Disable gnutls for ngtcp2")
set(ENABLE_OPENSSL OFF CACHE BOOL "Disable openssl for ngtcp2")
set(ENABLE_BORINGSSL OFF CACHE BOOL "Disable boringssl for ngtcp2")
add_definitions(-D_GNU_SOURCE)
add_subdirectory(ngtcp2 EXCLUDE_FROM_ALL)

@ -120,7 +120,6 @@ namespace llarp
std::unique_ptr<std::promise<void>> closeWaiter;
};
} // namespace llarp
#endif

@ -1,7 +1,6 @@
include(Version)
add_library(lokinet-util
STATIC
${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp
util/bencode.cpp
util/buffer.cpp
@ -23,8 +22,9 @@ add_library(lokinet-util
util/str.cpp
util/thread/queue_manager.cpp
util/thread/threading.cpp
util/time.cpp
)
util/time.cpp)
add_dependencies(lokinet-util genversion)
target_include_directories(lokinet-util PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR})
@ -52,7 +52,6 @@ add_library(lokinet-platform
net/ip_range.cpp
net/net.cpp
net/net_int.cpp
net/route.cpp
net/sock_addr.cpp
vpn/packet_router.cpp
vpn/platform.cpp
@ -263,6 +262,10 @@ if(BUILD_LIBLOKINET)
add_log_tag(lokinet-shared)
endif()
if(APPLE)
add_subdirectory(apple)
endif()
foreach(lokinet_lib liblokinet lokinet-platform lokinet-util lokinet-cryptography)
add_log_tag(${lokinet_lib})
endforeach()

@ -0,0 +1,52 @@
# 3.13+ so that we can add link libraries to parent targets
cmake_minimum_required(VERSION 3.13)
if (BUILD_SHARED_LIBS OR NOT BUILD_STATIC_DEPS OR NOT STATIC_LINK)
message(FATAL_ERROR "macOS builds require a full static build; perhaps use the contrib/macos.sh script to build?")
endif()
# god made apple so that man may suffer
find_library(FOUNDATION Foundation REQUIRED)
find_library(NETEXT NetworkExtension REQUIRED)
find_library(COREFOUNDATION CoreFoundation REQUIRED)
target_sources(lokinet-util PRIVATE apple_logger.cpp)
target_link_libraries(lokinet-util PUBLIC ${FOUNDATION})
target_sources(lokinet-platform PRIVATE vpn_platform.cpp vpn_interface.cpp route_manager.cpp context_wrapper.cpp)
add_executable(lokinet-extension MACOSX_BUNDLE
PacketTunnelProvider.m
DNSTrampoline.m
)
enable_lto(lokinet-extension)
target_link_libraries(lokinet-extension PRIVATE
liblokinet
${COREFOUNDATION}
${NETEXT})
# Not sure what -fapplication-extension does, but XCode puts it in so...
# -fobjc-arc enables automatic reference counting for objective-C code
# -e _NSExtensionMain because the appex has that instead of a `main` function entry point, of course.
target_compile_options(lokinet-extension PRIVATE -fapplication-extension -fobjc-arc)
target_link_options(lokinet-extension PRIVATE -fapplication-extension -e _NSExtensionMain)
target_link_libraries(lokinet-extension PUBLIC
liblokinet
${COREFOUNDATION}
${NETEXT})
set_target_properties(lokinet-extension PROPERTIES
BUNDLE TRUE
BUNDLE_EXTENSION appex
MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/contrib/macos/LokinetExtension.Info.plist.in
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
)
add_custom_command(TARGET lokinet-extension
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.provisionprofile
$<TARGET_BUNDLE_DIR:lokinet-extension>/Contents/embedded.provisionprofile
)

@ -0,0 +1,51 @@
#pragma once
#include <uv.h>
#include <NetworkExtension/NetworkExtension.h>
extern NSString* error_domain;
/**
* "Trampoline" class that listens for UDP DNS packets on port 1053 coming from lokinet's embedded
* libunbound (when exit mode is enabled), wraps them via NetworkExtension's crappy UDP API, then
* sends responses back to libunbound to be parsed/etc. This class knows nothing about DNS, it is
* basically just a UDP packet forwarder.
*
* So for a lokinet configuration of "upstream=1.1.1.1", when exit mode is OFF:
* - DNS requests go to TUNNELIP:53, get sent to libunbound, which forwards them (directly) to the
* upstream DNS server(s).
* With exit mode ON:
* - DNS requests go to TUNNELIP:53, get send to libunbound, which forwards them to 127.0.0.1:1053,
* which encapsulates them in Apple's god awful crap, then (on a response) sends them back to
* libunbound.
* (This assumes a non-lokinet DNS; .loki and .snode get handled before either of these).
*/
@interface LLARPDNSTrampoline : NSObject
{
// The socket libunbound talks with:
uv_udp_t request_socket;
// The reply address. This is a bit hacky: we configure libunbound to just use single address
// (rather than a range) so that we don't have to worry about tracking different reply addresses.
@public
struct sockaddr reply_addr;
// UDP "session" aimed at the upstream DNS
@public
NWUDPSession* upstream;
// Apple docs say writes could take time *and* the crappy Apple datagram write methods aren't
// callable again until the previous write finishes. Deal with this garbage API by queuing
// everything than using a uv_async to process the queue.
@public
int write_ready;
@public
NSMutableArray<NSData*>* pending_writes;
uv_async_t write_trigger;
}
- (void)startWithUpstreamDns:(NWUDPSession*)dns
listenPort:(uint16_t)listenPort
uvLoop:(uv_loop_t*)loop
completionHandler:(void (^)(NSError* error))completionHandler;
- (void)flushWrites;
- (void)dealloc;
@end

@ -0,0 +1,136 @@
#include "DNSTrampoline.h"
#include <uv.h>
NSString* error_domain = @"com.loki-project.lokinet";
// Receiving an incoming packet, presumably from libunbound. NB: this is called from the libuv
// event loop.
static void on_request(uv_udp_t* socket, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) {
if (nread < 0) {
NSLog(@"Read error: %s", uv_strerror(nread));
free(buf->base);
return;
}
if (nread == 0 || !addr) {
if (buf)
free(buf->base);
return;
}
LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*) socket->data;
// We configure libunbound to use just one single port so we'll just send replies to the last port
// to talk to us. (And we're only listening on localhost in the first place).
t->reply_addr = *addr;
// NSData takes care of calling free(buf->base) for us with this constructor:
[t->pending_writes addObject:[NSData dataWithBytesNoCopy:buf->base length:nread]];
[t flushWrites];
}
static void on_sent(uv_udp_send_t* req, int status) {
NSArray<NSData*>* datagrams = (__bridge_transfer NSArray<NSData*>*) req->data;
free(req);
}
// NB: called from the libuv event loop (so we don't have to worry about the above and this one
// running at once from different threads).
static void write_flusher(uv_async_t* async) {
LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*) async->data;
if (t->pending_writes.count == 0)
return;
NSArray<NSData*>* data = [NSArray<NSData*> arrayWithArray:t->pending_writes];
[t->pending_writes removeAllObjects];
__weak LLARPDNSTrampoline* weakSelf = t;
[t->upstream writeMultipleDatagrams:data completionHandler: ^(NSError* error)
{
if (error)
NSLog(@"Failed to send request to upstream DNS: %@", error);
// Trigger another flush in case anything built up while Apple was doing its things. Just
// call it unconditionally (rather than checking the queue) because this handler is probably
// running in some other thread.
[weakSelf flushWrites];
}
];
}
static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
buf->base = malloc(suggested_size);
buf->len = suggested_size;
}
@implementation LLARPDNSTrampoline
- (void)startWithUpstreamDns:(NWUDPSession*) dns
listenPort:(uint16_t) listenPort
uvLoop:(uv_loop_t*) loop
completionHandler:(void (^)(NSError* error))completionHandler
{
pending_writes = [[NSMutableArray<NSData*> alloc] init];
write_trigger.data = (__bridge void*) self;
uv_async_init(loop, &write_trigger, write_flusher);
request_socket.data = (__bridge void*) self;
uv_udp_init(loop, &request_socket);
struct sockaddr_in recv_addr;
uv_ip4_addr("127.0.0.1", listenPort, &recv_addr);
int ret = uv_udp_bind(&request_socket, (const struct sockaddr*) &recv_addr, UV_UDP_REUSEADDR);
if (ret < 0) {
NSString* errstr = [NSString stringWithFormat:@"Failed to start DNS trampoline: %s", uv_strerror(ret)];
NSError *err = [NSError errorWithDomain:error_domain code:ret userInfo:@{@"Error": errstr}];
NSLog(@"%@", err);
return completionHandler(err);
}
uv_udp_recv_start(&request_socket, alloc_buffer, on_request);
NSLog(@"Starting DNS trampoline");
upstream = dns;
__weak LLARPDNSTrampoline* weakSelf = self;
[upstream setReadHandler: ^(NSArray<NSData*>* datagrams, NSError* error) {
// Reading a reply back from the UDP socket used to talk to upstream
if (error) {
NSLog(@"Reader handler failed: %@", error);
return;
}
LLARPDNSTrampoline* strongSelf = weakSelf;
if (!strongSelf || datagrams.count == 0)
return;
uv_buf_t* buffers = malloc(datagrams.count * sizeof(uv_buf_t));
size_t buf_count = 0;
for (NSData* packet in datagrams) {
buffers[buf_count].base = (void*) packet.bytes;
buffers[buf_count].len = packet.length;
buf_count++;
}
uv_udp_send_t* uvsend = malloc(sizeof(uv_udp_send_t));
uvsend->data = (__bridge_retained void*) datagrams;
int ret = uv_udp_send(uvsend, &strongSelf->request_socket, buffers, buf_count, &strongSelf->reply_addr, on_sent);
free(buffers);
if (ret < 0)
NSLog(@"Error returning DNS responses to unbound: %s", uv_strerror(ret));
} maxDatagrams:NSUIntegerMax];
completionHandler(nil);
}
- (void)flushWrites
{
uv_async_send(&write_trigger);
}
- (void) dealloc
{
NSLog(@"Stopping DNS trampoline");
uv_close((uv_handle_t*) &request_socket, NULL);
uv_close((uv_handle_t*) &write_trigger, NULL);
}
@end

@ -0,0 +1,310 @@
#include <Foundation/Foundation.h>
#include <NetworkExtension/NetworkExtension.h>
#include "context_wrapper.h"
#include "DNSTrampoline.h"
@interface LLARPPacketTunnel : NEPacketTunnelProvider
{
void* lokinet;
@public NEPacketTunnelNetworkSettings* settings;
@public NEIPv4Route* tun_route4;
@public NEIPv6Route* tun_route6;
LLARPDNSTrampoline* dns_tramp;
}
- (void)startTunnelWithOptions:(NSDictionary<NSString*, NSObject*>*)options
completionHandler:(void (^)(NSError* error))completionHandler;
- (void)stopTunnelWithReason:(NEProviderStopReason)reason
completionHandler:(void (^)(void))completionHandler;
- (void)handleAppMessage:(NSData*)messageData
completionHandler:(void (^)(NSData* responseData))completionHandler;
- (void)readPackets;
- (void)updateNetworkSettings;
@end
static void nslogger(const char* msg) { NSLog(@"%s", msg); }
static void packet_writer(int af, const void* data, size_t size, void* ctx) {
if (ctx == nil || data == nil)
return;
NSData* buf = [NSData dataWithBytesNoCopy:(void*)data length:size freeWhenDone:NO];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
NEPacket* packet = [[NEPacket alloc] initWithData:buf protocolFamily: af];
[t.packetFlow writePacketObjects:@[packet]];
}
static void start_packet_reader(void* ctx) {
if (ctx == nil)
return;
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
[t readPackets];
}
static void add_ipv4_route(const char* addr, const char* netmask, void* ctx) {
NEIPv4Route* route = [[NEIPv4Route alloc]
initWithDestinationAddress: [NSString stringWithUTF8String:addr]
subnetMask: [NSString stringWithUTF8String:netmask]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
for (NEIPv4Route* r in t->settings.IPv4Settings.includedRoutes)
if ([r.destinationAddress isEqualToString:route.destinationAddress] &&
[r.destinationSubnetMask isEqualToString:route.destinationSubnetMask])
return; // Already in the settings, nothing to add.
t->settings.IPv4Settings.includedRoutes =
[t->settings.IPv4Settings.includedRoutes arrayByAddingObject:route];
[t updateNetworkSettings];
}
static void del_ipv4_route(const char* addr, const char* netmask, void* ctx) {
NEIPv4Route* route = [[NEIPv4Route alloc]
initWithDestinationAddress: [NSString stringWithUTF8String:addr]
subnetMask: [NSString stringWithUTF8String:netmask]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
NSMutableArray<NEIPv4Route*>* routes = [NSMutableArray arrayWithArray:t->settings.IPv4Settings.includedRoutes];
for (int i = 0; i < routes.count; i++) {
if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] &&
[routes[i].destinationSubnetMask isEqualToString:route.destinationSubnetMask]) {
[routes removeObjectAtIndex:i];
i--;
}
}
if (routes.count != t->settings.IPv4Settings.includedRoutes.count) {
t->settings.IPv4Settings.includedRoutes = routes;
[t updateNetworkSettings];
}
}
static void add_ipv6_route(const char* addr, int prefix, void* ctx) {
NEIPv6Route* route = [[NEIPv6Route alloc]
initWithDestinationAddress: [NSString stringWithUTF8String:addr]
networkPrefixLength: [NSNumber numberWithInt:prefix]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
for (NEIPv6Route* r in t->settings.IPv6Settings.includedRoutes)
if ([r.destinationAddress isEqualToString:route.destinationAddress] &&
[r.destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength])
return; // Already in the settings, nothing to add.
t->settings.IPv6Settings.includedRoutes =
[t->settings.IPv6Settings.includedRoutes arrayByAddingObject:route];
[t updateNetworkSettings];
}
static void del_ipv6_route(const char* addr, int prefix, void* ctx) {
NEIPv6Route* route = [[NEIPv6Route alloc]
initWithDestinationAddress: [NSString stringWithUTF8String:addr]
networkPrefixLength: [NSNumber numberWithInt:prefix]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
NSMutableArray<NEIPv6Route*>* routes = [NSMutableArray arrayWithArray:t->settings.IPv6Settings.includedRoutes];
for (int i = 0; i < routes.count; i++) {
if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] &&
[routes[i].destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength]) {
[routes removeObjectAtIndex:i];
i--;
}
}
if (routes.count != t->settings.IPv6Settings.includedRoutes.count) {
t->settings.IPv6Settings.includedRoutes = routes;
[t updateNetworkSettings];
}
}
static void add_default_route(void* ctx) {
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
t->settings.IPv4Settings.includedRoutes = @[NEIPv4Route.defaultRoute];
t->settings.IPv6Settings.includedRoutes = @[NEIPv6Route.defaultRoute];
[t updateNetworkSettings];
}
static void del_default_route(void* ctx) {
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
t->settings.IPv4Settings.includedRoutes = @[t->tun_route4];
t->settings.IPv6Settings.includedRoutes = @[t->tun_route6];
[t updateNetworkSettings];
}
@implementation LLARPPacketTunnel
- (void)readPackets
{
[self.packetFlow readPacketObjectsWithCompletionHandler: ^(NSArray<NEPacket*>* packets) {
if (lokinet == nil)
return;
for (NEPacket* p in packets) {
llarp_apple_incoming(lokinet, p.data.bytes, p.data.length);
}
[self readPackets];
}];
}
- (void)startTunnelWithOptions:(NSDictionary<NSString*, NSObject*>*)options
completionHandler:(void (^)(NSError*))completionHandler
{
NSString* default_bootstrap = [NSBundle.mainBundle pathForResource:@"bootstrap" ofType:@"signed"];
NSString* home = NSHomeDirectory();
llarp_apple_config conf = {
.config_dir = home.UTF8String,
.default_bootstrap = default_bootstrap.UTF8String,
.ns_logger = nslogger,
.packet_writer = packet_writer,
.start_reading = start_packet_reader,
.route_callbacks = {
.add_ipv4_route = add_ipv4_route,
.del_ipv4_route = del_ipv4_route,
.add_ipv6_route = add_ipv6_route,
.del_ipv6_route = del_ipv6_route,
.add_default_route = add_default_route,
.del_default_route = del_default_route
},
};
lokinet = llarp_apple_init(&conf);
if (!lokinet) {
NSError *init_failure = [NSError errorWithDomain:error_domain code:500 userInfo:@{@"Error": @"Failed to initialize lokinet"}];
NSLog(@"%@", [init_failure localizedDescription]);
return completionHandler(init_failure);
}
NSString* ip = [NSString stringWithUTF8String:conf.tunnel_ipv4_ip];
NSString* mask = [NSString stringWithUTF8String:conf.tunnel_ipv4_netmask];
// We don't have a fixed address so just stick some bogus value here:
settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.3.2.1"];
NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[ip]];
dns.domainName = @"localhost.loki";
dns.matchDomains = @[@""];
// In theory, matchDomains is supposed to be set to DNS suffixes that we resolve. This seems
// highly unreliable, though: often it just doesn't work at all (perhaps only if we make ourselves
// the default route?), and even when it does work, it seems there are secret reasons that some
// domains (such as instagram.com) still won't work because there's some magic sauce in the OS
// that Apple engineers don't want to disclose ("This is what I expected, actually. Although I
// will not comment on what I believe is happening here", from
// https://developer.apple.com/forums/thread/685410).
//
// So the documentation sucks and the feature doesn't appear to work, so as much as it would be
// nice to capture only .loki and .snode when not in exit mode, we can't, so capture everything
// and use our default upstream.
dns.matchDomains = @[@""];
dns.matchDomainsNoSearch = true;
dns.searchDomains = @[];
settings.DNSSettings = dns;
NWHostEndpoint* upstreamdns_ep;
if (strlen(conf.upstream_dns))
upstreamdns_ep = [NWHostEndpoint endpointWithHostname:[NSString stringWithUTF8String:conf.upstream_dns] port:@(conf.upstream_dns_port).stringValue];
NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[ip]
subnetMasks:@[mask]];
tun_route4 = [[NEIPv4Route alloc] initWithDestinationAddress:ip subnetMask: mask];
ipv4.includedRoutes = @[tun_route4];
settings.IPv4Settings = ipv4;
NSString* ip6 = [NSString stringWithUTF8String:conf.tunnel_ipv6_ip];
NSNumber* ip6_prefix = [NSNumber numberWithUnsignedInt:conf.tunnel_ipv6_prefix];
NEIPv6Settings* ipv6 = [[NEIPv6Settings alloc] initWithAddresses:@[ip6]
networkPrefixLengths:@[ip6_prefix]];
tun_route6 = [[NEIPv6Route alloc] initWithDestinationAddress:ip6
networkPrefixLength:ip6_prefix];
ipv6.includedRoutes = @[tun_route6];
settings.IPv6Settings = ipv6;
__weak LLARPPacketTunnel* weakSelf = self;
[self setTunnelNetworkSettings:settings completionHandler:^(NSError* err) {
if (err) {
NSLog(@"Failed to configure lokinet tunnel: %@", err);
return completionHandler(err);
}
LLARPPacketTunnel* strongSelf = weakSelf;
if (!strongSelf)
return completionHandler(nil);
int start_ret = llarp_apple_start(strongSelf->lokinet, (__bridge void*) strongSelf);
if (start_ret != 0) {
NSError *start_failure = [NSError errorWithDomain:error_domain code:start_ret userInfo:@{@"Error": @"Failed to start lokinet"}];
NSLog(@"%@", start_failure);
lokinet = nil;
return completionHandler(start_failure);
}
NSLog(@"Starting DNS exit mode trampoline to %@ on 127.0.0.1:%d", upstreamdns_ep, dns_trampoline_port);
NWUDPSession* upstreamdns = [strongSelf createUDPSessionThroughTunnelToEndpoint:upstreamdns_ep fromEndpoint:nil];
strongSelf->dns_tramp = [LLARPDNSTrampoline alloc];
[strongSelf->dns_tramp
startWithUpstreamDns:upstreamdns
listenPort:dns_trampoline_port
uvLoop:llarp_apple_get_uv_loop(strongSelf->lokinet)
completionHandler:^(NSError* error) {
if (error)
NSLog(@"Error starting dns trampoline: %@", error);
return completionHandler(error);
}];
}];
}
- (void)stopTunnelWithReason:(NEProviderStopReason)reason
completionHandler:(void (^)(void))completionHandler
{
if (lokinet) {
llarp_apple_shutdown(lokinet);
lokinet = nil;
}
completionHandler();
}
- (void)handleAppMessage:(NSData*)messageData
completionHandler:(void (^)(NSData* responseData))completionHandler
{
NSData* response = [NSData dataWithBytesNoCopy:"ok" length:3 freeWhenDone:NO];
completionHandler(response);
}
- (void)updateNetworkSettings
{
self.reasserting = YES;
__weak LLARPPacketTunnel* weakSelf = self;
// Apple documentation says that setting network settings to nil isn't required before setting it
// to a new value. Apple lies: both end up with a routing table that looks exactly the same (from
// both `netstat -rn` and from everything that happens in `route -n monitor`), but if we don't
// call with nil first then everything fails to route to either lokinet *and* clearnet through the
// exit, so there is apparently some special magic internal Apple state that actually *does*
// require the tunnel settings being reset with nil first.
//
// Thanks for the accurate documentation, Apple.
//
[self setTunnelNetworkSettings:nil completionHandler:^(NSError* err) {
if (err)
NSLog(@"Failed to clear lokinet tunnel settings: %@", err);
LLARPPacketTunnel* strongSelf = weakSelf;
if (strongSelf) {
[weakSelf setTunnelNetworkSettings:strongSelf->settings completionHandler:^(NSError* err) {
LLARPPacketTunnel* strongSelf = weakSelf;
if (strongSelf)
strongSelf.reasserting = NO;
if (err)
NSLog(@"Failed to reconfigure lokinet tunnel settings: %@", err);
}];
}
}];
}
@end

@ -0,0 +1,25 @@
#include "apple_logger.hpp"
namespace llarp::apple
{
void
NSLogStream::PreLog(
std::stringstream& ss,
LogLevel lvl,
const char* fname,
int lineno,
const std::string& nodename) const
{
ss << "[" << LogLevelToString(lvl) << "] ";
ss << "[" << nodename << "]"
<< "(" << thread_id_string() << ") " << log_timestamp() << " " << fname << ":" << lineno
<< "\t";
}
void
NSLogStream::Print(LogLevel, const char*, const std::string& msg)
{
ns_logger(msg.c_str());
}
} // namespace llarp::apple

@ -0,0 +1,40 @@
#pragma once
#include <llarp/util/logging/logger.hpp>
#include <llarp/util/logging/logstream.hpp>
namespace llarp::apple
{
struct NSLogStream : public ILogStream
{
using ns_logger_callback = void (*)(const char* log_this);
NSLogStream(ns_logger_callback logger) : ns_logger{logger}
{}
void
PreLog(
std::stringstream& s,
LogLevel lvl,
const char* fname,
int lineno,
const std::string& nodename) const override;
void
Print(LogLevel lvl, const char* tag, const std::string& msg) override;
void
PostLog(std::stringstream& ss) const override
{}
void
ImmediateFlush() override
{}
void Tick(llarp_time_t) override
{}
private:
ns_logger_callback ns_logger;
};
} // namespace llarp::apple

@ -0,0 +1,27 @@
#pragma once
#include <llarp.hpp>
#include "vpn_platform.hpp"
#include "route_manager.hpp"
namespace llarp::apple
{
struct Context : public llarp::Context
{
std::shared_ptr<vpn::Platform>
makeVPNPlatform() override
{
return std::make_shared<VPNPlatform>(
*this, m_PacketWriter, m_OnReadable, route_callbacks, callback_context);
}
// Callbacks that must be set for packet handling *before* calling Setup/Configure/Run; the main
// point of these is to get passed through to VPNInterface, which will be called during setup,
// after construction.
VPNInterface::packet_write_callback m_PacketWriter;
VPNInterface::on_readable_callback m_OnReadable;
llarp_route_callbacks route_callbacks{};
void* callback_context = nullptr;
};
} // namespace llarp::apple

@ -0,0 +1,194 @@
#include <cstdint>
#include <cstring>
#include <cassert>
#include <llarp/net/ip_packet.hpp>
#include <llarp/config/config.hpp>
#include <llarp/util/fs.hpp>
#include <llarp/util/logging/buffer.hpp>
#include <uvw/loop.h>
#include "vpn_interface.hpp"
#include "context_wrapper.h"
#include "context.hpp"
#include "apple_logger.hpp"
namespace
{
// The default 127.0.0.1:53 won't work (because we run unprivileged) so remap it to this (unless
// specifically overridden to something else in the config):
const llarp::SockAddr DefaultDNSBind{"127.0.0.1:1153"};
struct instance_data
{
llarp::apple::Context context;
std::thread runner;
packet_writer_callback packet_writer;
start_reading_callback start_reading;
std::weak_ptr<llarp::apple::VPNInterface> iface;
};
} // namespace
const uint16_t dns_trampoline_port = 1053;
void*
llarp_apple_init(llarp_apple_config* appleconf)
{
llarp::LogContext::Instance().logStream =
std::make_unique<llarp::apple::NSLogStream>(appleconf->ns_logger);
try
{
auto config_dir = fs::u8path(appleconf->config_dir);
auto config = std::make_shared<llarp::Config>(config_dir);
fs::path config_path = config_dir / "lokinet.ini";
if (!fs::exists(config_path))
llarp::ensureConfig(config_dir, config_path, /*overwrite=*/false, /*router=*/false);
config->Load(config_path);
// If no range is specified then go look for a free one, set that in the config, and then return
// it to the caller via the char* parameters.
auto& range = config->network.m_ifaddr;
if (!range.addr.h)
{
if (auto maybe = llarp::FindFreeRange())
range = *maybe;
else
throw std::runtime_error{"Could not find any free IP range"};
}
auto addr = llarp::net::TruncateV6(range.addr).ToString();
auto mask = llarp::net::TruncateV6(range.netmask_bits).ToString();
if (addr.size() > 15 || mask.size() > 15)
throw std::runtime_error{"Unexpected non-IPv4 tunnel range configured"};
std::strncpy(appleconf->tunnel_ipv4_ip, addr.c_str(), sizeof(appleconf->tunnel_ipv4_ip));
std::strncpy(
appleconf->tunnel_ipv4_netmask, mask.c_str(), sizeof(appleconf->tunnel_ipv4_netmask));
// TODO: in the future we want to do this properly with our pubkey (see issue #1705), but that's
// going to take a bit more work because we currently can't *get* the (usually) ephemeral pubkey
// at this stage of lokinet configuration. So for now we just stick our IPv4 address into it
// until #1705 gets implemented.
llarp::huint128_t ipv6{
llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(range.addr).h}};
std::strncpy(
appleconf->tunnel_ipv6_ip, ipv6.ToString().c_str(), sizeof(appleconf->tunnel_ipv6_ip));
appleconf->tunnel_ipv6_prefix = 48;
appleconf->upstream_dns[0] = '\0';
for (auto& upstream : config->dns.m_upstreamDNS)
{
if (upstream.isIPv4())
{
std::strcpy(appleconf->upstream_dns, upstream.hostString().c_str());
appleconf->upstream_dns_port = upstream.getPort();
break;
}
}
// The default DNS bind setting just isn't something we can use as a non-root network extension
// so remap the default value to a high port unless explicitly set to something else.
if (config->dns.m_bind == llarp::SockAddr{"127.0.0.1:53"})
config->dns.m_bind = DefaultDNSBind;
// If no explicit bootstrap then set the system default one included with the app bundle
if (config->bootstrap.files.empty())
config->bootstrap.files.push_back(fs::u8path(appleconf->default_bootstrap));
auto inst = std::make_unique<instance_data>();
inst->context.Configure(std::move(config));
inst->context.route_callbacks = appleconf->route_callbacks;
inst->packet_writer = appleconf->packet_writer;
inst->start_reading = appleconf->start_reading;
return inst.release();
}
catch (const std::exception& e)
{
LogError("Failed to initialize lokinet from config: ", e.what());
}
return nullptr;
}
int
llarp_apple_start(void* lokinet, void* callback_context)
{
auto* inst = static_cast<instance_data*>(lokinet);
inst->context.callback_context = callback_context;
inst->context.m_PacketWriter = [inst, callback_context](int af_family, void* data, size_t size) {
inst->packet_writer(af_family, data, size, callback_context);
return true;
};
inst->context.m_OnReadable = [inst, callback_context](llarp::apple::VPNInterface& iface) {
inst->iface = iface.weak_from_this();
inst->start_reading(callback_context);
};
std::promise<void> result;
inst->runner = std::thread{[inst, &result] {
const llarp::RuntimeOptions opts{};
try
{
inst->context.Setup(opts);
}
catch (...)
{
result.set_exception(std::current_exception());
return;
}
result.set_value();
inst->context.Run(opts);
}};
try
{
result.get_future().get();
}
catch (const std::exception& e)
{
LogError("Failed to initialize lokinet: ", e.what());
return -1;
}
return 0;
}
uv_loop_t*
llarp_apple_get_uv_loop(void* lokinet)
{
auto& inst = *static_cast<instance_data*>(lokinet);
auto uvw = inst.context.loop->MaybeGetUVWLoop();
assert(uvw);
return uvw->raw();
}
int
llarp_apple_incoming(void* lokinet, const void* bytes, size_t size)
{
auto& inst = *static_cast<instance_data*>(lokinet);
auto iface = inst.iface.lock();
if (!iface)
return -2;
llarp_buffer_t buf{static_cast<const uint8_t*>(bytes), size};
if (iface->OfferReadPacket(buf))
return 0;
LogError("invalid IP packet: ", llarp::buffer_printer(buf));
return -1;
}
void
llarp_apple_shutdown(void* lokinet)
{
auto* inst = static_cast<instance_data*>(lokinet);
inst->context.CloseAsync();
inst->context.Wait();
inst->runner.join();
delete inst;
}

@ -0,0 +1,152 @@
#pragma once
// C-linkage wrappers for interacting with a lokinet context, so that we can call them from Swift
// code (which currently doesn't support C++ interoperability at all).
#ifdef __cplusplus
extern "C"
{
#endif
#include <unistd.h>
#include <sys/socket.h>
#include <uv.h>
// Port (on localhost) for our DNS trampoline for bouncing DNS requests through the exit route
// when in exit mode.
extern const uint16_t dns_trampoline_port;
/// C callback function for us to invoke when we need to write a packet
typedef void (*packet_writer_callback)(int af, const void* data, size_t size, void* ctx);
/// C callback function to invoke once we are ready to start receiving packets
typedef void (*start_reading_callback)(void* ctx);
/// C callback that bridges things into NSLog
typedef void (*ns_logger_callback)(const char* msg);
/// C callbacks to add/remove specific and default routes to the tunnel
typedef void (*llarp_route_ipv4_callback)(const char* addr, const char* netmask, void* ctx);
typedef void (*llarp_route_ipv6_callback)(const char* addr, int prefix, void* ctx);
typedef void (*llarp_default_route_callback)(void* ctx);
typedef struct llarp_route_callbacks
{
/// Callback invoked to set up an IPv4 range that should be routed through the tunnel
/// interface. Called with the address and netmask.
llarp_route_ipv4_callback add_ipv4_route;
/// Callback invoked to set the tunnel as the default IPv4 route.
llarp_default_route_callback add_ipv4_default_route;
/// Callback invoked to remove a specific range from the tunnel IPv4 routes. Called with the
/// address and netmask.
llarp_route_ipv4_callback del_ipv4_route;
/// Callback invoked to set up an IPv6 range that should be routed through the tunnel
/// interface. Called with the address and netmask.
llarp_route_ipv6_callback add_ipv6_route;
/// Callback invoked to remove a specific range from the tunnel IPv6 routes. Called with the
/// address and netmask.
llarp_route_ipv6_callback del_ipv6_route;
/// Callback invoked to set the tunnel as the default IPv4/IPv6 route.
llarp_default_route_callback add_default_route;
/// Callback invoked to remove the tunnel as the default IPv4/IPv6 route.
llarp_default_route_callback del_default_route;
} llarp_route_callbacks;
/// Pack of crap to be passed into llarp_apple_init to initialize
typedef struct llarp_apple_config
{
/// lokinet configuration directory, expected to be the application-specific "home" directory,
/// which is where state files are stored and the lokinet.ini will be loaded (or created if it
/// doesn't exist).
const char* config_dir;
/// path to the default bootstrap.signed file included in installation, which will be used by
/// default when no specific bootstrap is in the config file.
const char* default_bootstrap;
/// llarp_apple_init writes the IP address for the primary tunnel IP address here,
/// null-terminated.
char tunnel_ipv4_ip[INET_ADDRSTRLEN];
/// llarp_apple_init writes the netmask of the tunnel address here, null-terminated.
char tunnel_ipv4_netmask[INET_ADDRSTRLEN];
/// Writes the IPv6 address for the tunnel here, null-terminated.
char tunnel_ipv6_ip[INET6_ADDRSTRLEN];
/// IPv6 address prefix.
uint16_t tunnel_ipv6_prefix;
/// The first upstream DNS server's IPv4 address the OS should use when in exit mode.
/// (Currently on mac in exit mode we only support querying the first such configured server).
char upstream_dns[INET_ADDRSTRLEN];
uint16_t upstream_dns_port;
/// \defgroup callbacks Callbacks
/// Callbacks we invoke for various operations that require glue into the Apple network
/// extension APIs. All of these except for ns_logger are passed the pointer provided to
/// llarp_apple_start when invoked.
/// @{
/// simple wrapper around NSLog for lokinet message logging
ns_logger_callback ns_logger;
/// C function callback that will be called when we need to write a packet to the packet
/// tunnel. Will be passed AF_INET or AF_INET6, a void pointer to the data, and the size of
/// the data in bytes.
packet_writer_callback packet_writer;
/// C function callback that will be called when lokinet is setup and ready to start receiving
/// packets from the packet tunnel. This should set up the read handler to deliver packets
/// via llarp_apple_incoming.
start_reading_callback start_reading;
/// Callbacks invoked to add/remove routes to the tunnel.
llarp_route_callbacks route_callbacks;
/// @}
} llarp_apple_config;
/// Initializes a lokinet instance by initializing various objects and loading the configuration
/// (if <config_dir>/lokinet.ini exists). Does not actually start lokinet (call llarp_apple_start
/// for that).
///
/// Returns NULL if there was a problem initializing/loading the configuration, otherwise returns
/// an opaque void pointer that should be passed into the other llarp_apple_* functions.
///
/// \param config pointer to a llarp_apple_config where we get the various settings needed
/// and return the ip/mask/dns fields needed for the tunnel.
void*
llarp_apple_init(llarp_apple_config* config);
/// Starts the lokinet instance in a new thread.
///
/// \param lokinet the void pointer returned by llarp_apple_init
///
/// \param callback_context Opaque pointer that is passed into the various callbacks provided to
/// llarp_apple_init. This code does nothing with this pointer aside from passing it through to
/// callbacks.
///
/// \returns 0 on succesful startup, -1 on failure.
int
llarp_apple_start(void* lokinet, void* callback_context);
/// Returns a pointer to the uv event loop. Must have called llarp_apple_start already.
uv_loop_t*
llarp_apple_get_uv_loop(void* lokinet);
/// Called to deliver an incoming packet from the apple layer into lokinet; returns 0 on success,
/// -1 if the packet could not be parsed, -2 if there is no current active VPNInterface associated
/// with the lokinet (which generally means llarp_apple_start wasn't called or failed, or lokinet
/// is in the process of shutting down).
int
llarp_apple_incoming(void* lokinet, const void* bytes, size_t size);
/// Stops a lokinet instance created with `llarp_apple_initialize`. This waits for lokinet to
/// shut down and rejoins the thread. After this call the given pointer is no longer valid.
void
llarp_apple_shutdown(void* lokinet);
#ifdef __cplusplus
} // extern "C"
#endif

@ -0,0 +1,101 @@
#include "route_manager.hpp"
#include <llarp/handlers/tun.hpp>
#include <llarp/service/context.hpp>
#include <llarp.hpp>
#include <memory>
namespace llarp::apple
{
void
RouteManager::check_trampoline(bool enable)
{
if (trampoline_active == enable)
return;
auto router = context.router;
if (!router)
{
LogError("Cannot reconfigure to use DNS trampoline: no router");
return;
}
std::shared_ptr<llarp::handlers::TunEndpoint> tun;
router->hiddenServiceContext().ForEachService([&tun](const auto& name, const auto ep) {
tun = std::dynamic_pointer_cast<llarp::handlers::TunEndpoint>(ep);
return !tun;
});
if (!tun)
{
LogError("Cannot reconfigure to use DNS trampoline: no tun endpoint found (!?)");
return;
}
if (enable)
saved_upstream_dns =
tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, huint16_t{dns_trampoline_port}}});
else
tun->ReconfigureDNS(std::move(saved_upstream_dns));
trampoline_active = enable;
}
void RouteManager::AddDefaultRouteViaInterface(std::string)
{
check_trampoline(true);
if (callback_context and route_callbacks.add_default_route)
route_callbacks.add_default_route(callback_context);
}
void RouteManager::DelDefaultRouteViaInterface(std::string)
{
check_trampoline(false);
if (callback_context and route_callbacks.del_default_route)
route_callbacks.del_default_route(callback_context);
}
void
RouteManager::AddRouteViaInterface(vpn::NetworkInterface&, IPRange range)
{
check_trampoline(true);
if (callback_context)
{
if (range.IsV4())
{
if (route_callbacks.add_ipv4_route)
route_callbacks.add_ipv4_route(
range.BaseAddressString().c_str(),
net::TruncateV6(range.netmask_bits).ToString().c_str(),
callback_context);
}
else
{
if (route_callbacks.add_ipv6_route)
route_callbacks.add_ipv6_route(
range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context);
}
}
}
void
RouteManager::DelRouteViaInterface(vpn::NetworkInterface&, IPRange range)
{
check_trampoline(false);
if (callback_context)
{
if (range.IsV4())
{
if (route_callbacks.del_ipv4_route)
route_callbacks.del_ipv4_route(
range.BaseAddressString().c_str(),
net::TruncateV6(range.netmask_bits).ToString().c_str(),
callback_context);
}
else
{
if (route_callbacks.del_ipv6_route)
route_callbacks.del_ipv6_route(
range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context);
}
}
}
} // namespace llarp::apple

@ -0,0 +1,59 @@
#pragma once
#include <llarp/router/abstractrouter.hpp>
#include <llarp/ev/vpn.hpp>
#include "context_wrapper.h"
namespace llarp::apple
{
class RouteManager final : public llarp::vpn::IRouteManager
{
public:
RouteManager(llarp::Context& ctx, llarp_route_callbacks rcs, void* callback_context)
: context{ctx}, route_callbacks{std::move(rcs)}, callback_context{callback_context}
{}
/// These are called for poking route holes, but we don't have to do that at all on macos
/// because the appex isn't subject to its own rules.
void
AddRoute(IPVariant_t ip, IPVariant_t gateway) override
{}
void
DelRoute(IPVariant_t ip, IPVariant_t gateway) override
{}
void
AddDefaultRouteViaInterface(std::string ifname) override;
void
DelDefaultRouteViaInterface(std::string ifname) override;
void
AddRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override;
void
DelRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override;
virtual std::vector<IPVariant_t>
GetGatewaysNotOnInterface(std::string ifname) override
{
// We can't get this on mac from our sandbox, but we don't actually need it because we
// ignore the gateway for AddRoute/DelRoute anyway, so just return a zero IP.
std::vector<IPVariant_t> ret;
ret.push_back(huint32_t{0});
return ret;
}
private:
llarp::Context& context;
bool trampoline_active = false;
std::vector<llarp::SockAddr> saved_upstream_dns;
void
check_trampoline(bool enable);
void* callback_context = nullptr;
llarp_route_callbacks route_callbacks;
};
} // namespace llarp::apple

@ -0,0 +1,52 @@
#include "vpn_interface.hpp"
#include "context.hpp"
namespace llarp::apple
{
VPNInterface::VPNInterface(
Context& ctx, packet_write_callback packet_writer, on_readable_callback on_readable)
: m_PacketWriter{std::move(packet_writer)}, m_OnReadable{std::move(on_readable)}
{
ctx.loop->call_soon([this] { m_OnReadable(*this); });
}
bool
VPNInterface::OfferReadPacket(const llarp_buffer_t& buf)
{
llarp::net::IPPacket pkt;
if (!pkt.Load(buf))
return false;
m_ReadQueue.tryPushBack(std::move(pkt));
return true;
}
int
VPNInterface::PollFD() const
{
return -1;
}
std::string
VPNInterface::IfName() const
{
return "";
}
net::IPPacket
VPNInterface::ReadNextPacket()
{
net::IPPacket pkt{};
if (not m_ReadQueue.empty())
pkt = m_ReadQueue.popFront();
return pkt;
}
bool
VPNInterface::WritePacket(net::IPPacket pkt)
{
int af_family = pkt.IsV6() ? AF_INET6 : AF_INET;
return m_PacketWriter(af_family, pkt.buf, pkt.sz);
}
} // namespace llarp::apple

@ -0,0 +1,51 @@
#pragma once
#include <llarp.hpp>
#include <llarp/ev/vpn.hpp>
#include <llarp/util/thread/queue.hpp>
#include <memory>
namespace llarp::apple
{
struct Context;
class VPNInterface final : public vpn::NetworkInterface,
public std::enable_shared_from_this<VPNInterface>
{
public:
using packet_write_callback = std::function<bool(int af_family, void* data, int size)>;
using on_readable_callback = std::function<void(VPNInterface&)>;
explicit VPNInterface(
Context& ctx, packet_write_callback packet_writer, on_readable_callback on_readable);
// Method to call when a packet has arrived to deliver the packet to lokinet
bool
OfferReadPacket(const llarp_buffer_t& buf);
int
PollFD() const override;
std::string
IfName() const override;
net::IPPacket
ReadNextPacket() override;
bool
WritePacket(net::IPPacket pkt) override;
private:
// Function for us to call when we have a packet to emit. Should return true if the packet was
// handed off to the OS successfully.
packet_write_callback m_PacketWriter;
// Called when we are ready to start reading packets
on_readable_callback m_OnReadable;
static inline constexpr auto PacketQueueSize = 1024;
thread::Queue<net::IPPacket> m_ReadQueue{PacketQueueSize};
};
} // namespace llarp::apple

@ -0,0 +1,22 @@
#include "vpn_platform.hpp"
#include "context.hpp"
namespace llarp::apple
{
VPNPlatform::VPNPlatform(
Context& ctx,
VPNInterface::packet_write_callback packet_writer,
VPNInterface::on_readable_callback on_readable,
llarp_route_callbacks route_callbacks,
void* callback_context)
: m_Context{ctx}
, m_RouteManager{ctx, std::move(route_callbacks), callback_context}
, m_PacketWriter{std::move(packet_writer)}
, m_OnReadable{std::move(on_readable)}
{}
std::shared_ptr<vpn::NetworkInterface> VPNPlatform::ObtainInterface(vpn::InterfaceInfo)
{
return std::make_shared<VPNInterface>(m_Context, m_PacketWriter, m_OnReadable);
}
} // namespace llarp::apple

@ -0,0 +1,34 @@
#pragma once
#include <llarp/ev/vpn.hpp>
#include "vpn_interface.hpp"
#include "route_manager.hpp"
namespace llarp::apple
{
class VPNPlatform final : public vpn::Platform
{
public:
explicit VPNPlatform(
Context& ctx,
VPNInterface::packet_write_callback packet_writer,
VPNInterface::on_readable_callback on_readable,
llarp_route_callbacks route_callbacks,
void* callback_context);
std::shared_ptr<vpn::NetworkInterface> ObtainInterface(vpn::InterfaceInfo) override;
vpn::IRouteManager&
RouteManager() override
{
return m_RouteManager;
}
private:
Context& m_Context;
apple::RouteManager m_RouteManager;
VPNInterface::packet_write_callback m_PacketWriter;
VPNInterface::on_readable_callback m_OnReadable;
};
} // namespace llarp::apple

@ -3,7 +3,6 @@
#include "config/definition.hpp"
#include "ini.hpp"
#include <llarp/constants/defaults.hpp>
#include <llarp/constants/files.hpp>
#include <llarp/net/net.hpp>
#include <llarp/net/ip.hpp>
@ -711,6 +710,8 @@ namespace llarp
// Default, but if we get any upstream (including upstream=, i.e. empty string) we clear it
constexpr Default DefaultUpstreamDNS{"1.1.1.1"};
m_upstreamDNS.emplace_back(DefaultUpstreamDNS.val);
if (!m_upstreamDNS.back().getPort())
m_upstreamDNS.back().setPort(53);
conf.defineOption<std::string>(
"dns",
@ -798,7 +799,7 @@ namespace llarp
{
info.interface = std::string{name};
std::vector<std::string_view> splits = split(value, ',');
std::vector<std::string_view> splits = split(value, ",");
for (std::string_view str : splits)
{
int asNum = std::atoi(str.data());

@ -1,18 +0,0 @@
#pragma once
#ifndef DEFAULT_RESOLVER_US
#define DEFAULT_RESOLVER_US "1.1.1.1"
#endif
#ifndef DEFAULT_RESOLVER_EU
#define DEFAULT_RESOLVER_EU "1.1.1.1"
#endif
#ifndef DEFAULT_RESOLVER_AU
#define DEFAULT_RESOLVER_AU "1.1.1.1"
#endif
#ifndef DEFAULT_LOKINET_USER
#define DEFAULT_LOKINET_USER "lokinet"
#endif
#ifndef DEFAULT_LOKINET_GROUP
#define DEFAULT_LOKINET_GROUP "lokinet"
#endif

@ -138,8 +138,8 @@ namespace llarp
if (IsStopping())
return;
if (CallSafe(std::bind(&Context::HandleSignal, this, SIGTERM)))
closeWaiter = std::make_unique<std::promise<void>>();
loop->call([this]() { HandleSignal(SIGTERM); });
closeWaiter = std::make_unique<std::promise<void>>();
}
bool

@ -48,6 +48,12 @@ namespace llarp
return true;
}
util::StatusObject
MessageHeader::ToJSON() const
{
return util::StatusObject{};
}
Message::Message(Message&& other)
: hdr_id(std::move(other.hdr_id))
, hdr_fields(std::move(other.hdr_fields))
@ -74,6 +80,11 @@ namespace llarp
additional.resize(size_t(hdr.ar_count));
}
Message::Message(const Question& question) : hdr_id{0}, hdr_fields{}
{
questions.emplace_back(question);
}
bool
Message::Encode(llarp_buffer_t* buf) const
{
@ -122,6 +133,22 @@ namespace llarp
return true;
}
util::StatusObject
Message::ToJSON() const
{
std::vector<util::StatusObject> ques;
std::vector<util::StatusObject> ans;
for (const auto& q : questions)
{
ques.push_back(q.ToJSON());
}
for (const auto& a : answers)
{
ans.push_back(a.ToJSON());
}
return util::StatusObject{{"questions", ques}, {"answers", ans}};
}
OwnedBuffer
Message::ToBuffer() const
{

@ -33,6 +33,9 @@ namespace llarp
bool
Decode(llarp_buffer_t* buf) override;
util::StatusObject
ToJSON() const override;
bool
operator==(const MessageHeader& other) const
{
@ -44,11 +47,15 @@ namespace llarp
struct Message : public Serialize
{
Message(const MessageHeader& hdr);
explicit Message(const MessageHeader& hdr);
explicit Message(const Question& question);
Message(Message&& other);
Message(const Message& other);
util::StatusObject
ToJSON() const override;
void
AddNXReply(RR_TTL_t ttl = 1);

@ -3,6 +3,7 @@
#include <llarp/util/logging/logger.hpp>
#include <llarp/util/printer.hpp>
#include <llarp/util/str.hpp>
#include "dns.hpp"
namespace llarp
{
@ -17,6 +18,13 @@ namespace llarp
: qname(other.qname), qtype(other.qtype), qclass(other.qclass)
{}
Question::Question(std::string name, QType_t type)
: qname{std::move(name)}, qtype{type}, qclass{qClassIN}
{
if (qname.empty())
throw std::invalid_argument{"qname cannot be empty"};
}
bool
Question::Encode(llarp_buffer_t* buf) const
{
@ -48,6 +56,12 @@ namespace llarp
return true;
}
util::StatusObject
Question::ToJSON() const
{
return util::StatusObject{{"qname", qname}, {"qtype", qtype}, {"qclass", qclass}};
}
bool
Question::IsName(const std::string& other) const
{

@ -14,6 +14,9 @@ namespace llarp
struct Question : public Serialize
{
Question() = default;
explicit Question(std::string name, QType_t type);
Question(Question&& other);
Question(const Question& other);
bool
@ -58,6 +61,9 @@ namespace llarp
/// determine if we are using this TLD
bool
HasTLD(const std::string& tld) const;
util::StatusObject
ToJSON() const override;
};
inline std::ostream&

@ -24,6 +24,14 @@ namespace llarp
, rData(std::move(other.rData))
{}
ResourceRecord::ResourceRecord(Name_t name, RRType_t type, RR_RData_t data)
: rr_name{std::move(name)}
, rr_type{type}
, rr_class{qClassIN}
, ttl{1}
, rData{std::move(data)}
{}
bool
ResourceRecord::Encode(llarp_buffer_t* buf) const
{
@ -77,6 +85,17 @@ namespace llarp
return true;
}
util::StatusObject
ResourceRecord::ToJSON() const
{
return util::StatusObject{
{"name", rr_name},
{"type", rr_type},
{"class", rr_class},
{"ttl", ttl},
{"rdata", std::string{reinterpret_cast<const char*>(rData.data()), rData.size()}}};
}
std::ostream&
ResourceRecord::print(std::ostream& stream, int level, int spaces) const
{

@ -22,12 +22,17 @@ namespace llarp
ResourceRecord(const ResourceRecord& other);
ResourceRecord(ResourceRecord&& other);
explicit ResourceRecord(Name_t name, RRType_t type, RR_RData_t rdata);
bool
Encode(llarp_buffer_t* buf) const override;
bool
Decode(llarp_buffer_t* buf) override;
util::StatusObject
ToJSON() const override;
std::ostream&
print(std::ostream& stream, int level, int spaces) const;

@ -1,7 +1,7 @@
#pragma once
#include <llarp/util/buffer.hpp>
#include <llarp/util/status.hpp>
#include <vector>
namespace llarp
@ -20,6 +20,10 @@ namespace llarp
/// decode entity from buffer
virtual bool
Decode(llarp_buffer_t* buf) = 0;
/// convert this whatever into json
virtual util::StatusObject
ToJSON() const = 0;
};
bool

@ -58,9 +58,9 @@ namespace llarp::dns
return true;
auto failFunc = [self = weak_from_this()](
const SockAddr& from, const SockAddr& to, Message msg) {
const SockAddr& to, const SockAddr& from, Message msg) {
if (auto this_ptr = self.lock())
this_ptr->SendServerMessageBufferTo(from, to, msg.ToBuffer());
this_ptr->SendServerMessageBufferTo(to, from, msg.ToBuffer());
};
auto replyFunc = [self = weak_from_this()](auto&&... args) {
@ -70,6 +70,7 @@ namespace llarp::dns
m_UnboundResolver =
std::make_shared<UnboundResolver>(m_Loop, std::move(replyFunc), std::move(failFunc));
m_Resolvers.clear();
if (not m_UnboundResolver->Init())
{
llarp::LogError("Failed to initialize upstream DNS resolver.");
@ -95,15 +96,22 @@ namespace llarp::dns
}
void
Proxy::SendServerMessageBufferTo(const SockAddr&, const SockAddr& to, llarp_buffer_t buf)
Proxy::SendServerMessageBufferTo(
const SockAddr& to, [[maybe_unused]] const SockAddr& from, llarp_buffer_t buf)
{
if (!m_Server->send(to, buf))
llarp::LogError("dns reply failed");
}
bool
PacketHandler::IsUpstreamResolver(const SockAddr& to, [[maybe_unused]] const SockAddr& from) const
{
return m_Resolvers.count(to);
}
bool
PacketHandler::ShouldHandlePacket(
const SockAddr& to, [[maybe_unused]] const SockAddr& from, llarp_buffer_t buf) const
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) const
{
MessageHeader hdr;
if (not hdr.Decode(&buf))
@ -119,12 +127,11 @@ namespace llarp::dns
if (m_QueryHandler and m_QueryHandler->ShouldHookDNSMessage(msg))
return true;
if (m_Resolvers.find(to) != m_Resolvers.end())
{
return false;
}
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
@ -157,7 +164,7 @@ namespace llarp::dns
// yea it is, let's turn off DoH because god is dead.
msg.AddNXReply();
// press F to pay respects
SendServerMessageBufferTo(resolver, from, msg.ToBuffer());
SendServerMessageBufferTo(from, resolver, msg.ToBuffer());
return;
}
}
@ -165,7 +172,7 @@ namespace llarp::dns
if (m_QueryHandler && m_QueryHandler->ShouldHookDNSMessage(msg))
{
auto reply = [self = shared_from_this(), to = from, resolver](dns::Message msg) {
self->SendServerMessageBufferTo(resolver, to, msg.ToBuffer());
self->SendServerMessageBufferTo(to, resolver, msg.ToBuffer());
};
if (!m_QueryHandler->HandleHookedDNSMessage(std::move(msg), reply))
{
@ -177,7 +184,7 @@ namespace llarp::dns
// no upstream resolvers
// let's serv fail it
msg.AddServFail();
SendServerMessageBufferTo(resolver, from, msg.ToBuffer());
SendServerMessageBufferTo(from, resolver, msg.ToBuffer());
}
else
{

@ -54,7 +54,12 @@ namespace llarp
protected:
virtual void
SendServerMessageBufferTo(const SockAddr& from, const SockAddr& to, llarp_buffer_t buf) = 0;
SendServerMessageBufferTo(const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) = 0;
// Returns true if this packet is something that looks like it's going to an upstream
// resolver, i.e. matches a configured resolver.
virtual bool
IsUpstreamResolver(const SockAddr& to, const SockAddr& from) const;
private:
void
@ -84,7 +89,7 @@ namespace llarp
protected:
void
SendServerMessageBufferTo(
const SockAddr& from, const SockAddr& to, llarp_buffer_t buf) override;
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) override;
private:
std::shared_ptr<UDPHandle> m_Server;

@ -5,6 +5,8 @@
#include <sstream>
#include <llarp/util/str.hpp>
#include <unbound.h>
namespace llarp::dns
{
struct PendingUnboundLookup
@ -25,11 +27,18 @@ namespace llarp::dns
UnboundResolver::Reset()
{
started = false;
if (runner)
#ifdef _WIN32
if (runner.joinable())
{
runner.join();
}
#else
if (udp)
{
runner->join();
runner.reset();
udp->close();
}
udp.reset();
#endif
if (unboundContext)
{
ub_ctx_delete(unboundContext);
@ -37,12 +46,16 @@ namespace llarp::dns
unboundContext = nullptr;
}
UnboundResolver::UnboundResolver(EventLoop_ptr loop, ReplyFunction reply, FailFunction fail)
: unboundContext(nullptr)
, started(false)
, replyFunc(loop->make_caller(std::move(reply)))
, failFunc(loop->make_caller(std::move(fail)))
{}
UnboundResolver::UnboundResolver(EventLoop_ptr _loop, ReplyFunction reply, FailFunction fail)
: unboundContext{nullptr}
, started{false}
, replyFunc{_loop->make_caller(std::move(reply))}
, failFunc{_loop->make_caller(std::move(fail))}
{
#ifndef _WIN32
loop = _loop->MaybeGetUVWLoop();
#endif
}
// static callback
void
@ -58,7 +71,7 @@ namespace llarp::dns
{
Message& msg = lookup->msg;
msg.AddServFail();
this_ptr->failFunc(lookup->resolverAddr, lookup->askerAddr, msg);
this_ptr->failFunc(lookup->askerAddr, lookup->resolverAddr, msg);
ub_resolve_free(result);
return;
}
@ -73,7 +86,7 @@ namespace llarp::dns
buf.cur = buf.base;
hdr.Encode(&buf);
this_ptr->replyFunc(lookup->resolverAddr, lookup->askerAddr, std::move(pkt));
this_ptr->replyFunc(lookup->askerAddr, lookup->resolverAddr, std::move(pkt));
ub_resolve_free(result);
}
@ -94,14 +107,33 @@ namespace llarp::dns
}
ub_ctx_async(unboundContext, 1);
runner = std::make_unique<std::thread>([&]() {
#ifdef _WIN32
runner = std::thread{[&]() {
while (started)
{
if (unboundContext)
ub_wait(unboundContext);
std::this_thread::sleep_for(25ms);
}
});
if (unboundContext)
ub_process(unboundContext);
}};
#else
if (auto loop_ptr = loop.lock())
{
udp = loop_ptr->resource<uvw::PollHandle>(ub_fd(unboundContext));
udp->on<uvw::PollEvent>([ptr = weak_from_this()](auto&, auto&) {
if (auto self = ptr.lock())
{
if (self->unboundContext)
{
ub_process(self->unboundContext);
}
}
});
udp->start(uvw::PollHandle::Event::READABLE);
}
#endif
started = true;
return true;
}
@ -110,7 +142,8 @@ namespace llarp::dns
UnboundResolver::AddUpstreamResolver(const SockAddr& upstreamResolver)
{
std::stringstream ss;
ss << upstreamResolver.hostString();
auto hoststr = upstreamResolver.hostString();
ss << hoststr;
if (const auto port = upstreamResolver.getPort(); port != 53)
ss << "@" << port;
@ -121,6 +154,25 @@ namespace llarp::dns
Reset();
return false;
}
#ifdef __APPLE__
// On Apple, we configure a localhost resolver to trampoline requests through the tunnel to the
// actual upstream (because the network extension itself cannot route through the tunnel using
// normal sockets but instead we "get" to use Apple's interfaces, hurray).
if (hoststr == "127.0.0.1")
{
// Not at all clear why this is needed but without it we get "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.
ub_ctx_set_option(unboundContext, "outgoing-interface:", hoststr.c_str());
// The trampoline expects just a single source port (and sends everything back to it)
ub_ctx_set_option(unboundContext, "outgoing-range:", "1");
ub_ctx_set_option(unboundContext, "outgoing-port-avoid:", "0-65535");
ub_ctx_set_option(unboundContext, "outgoing-port-permit:", "1253");
}
#endif
return true;
}
@ -145,7 +197,7 @@ namespace llarp::dns
if (not unboundContext)
{
msg.AddServFail();
failFunc(to, from, std::move(msg));
failFunc(from, to, std::move(msg));
return;
}
@ -163,7 +215,7 @@ namespace llarp::dns
if (err != 0)
{
msg.AddServFail();
failFunc(to, from, std::move(msg));
failFunc(from, to, std::move(msg));
return;
}
}

@ -1,6 +1,5 @@
#pragma once
#include <unbound.h>
#include <mutex>
#include <atomic>
#include <memory>
@ -13,14 +12,22 @@
#ifdef _WIN32
#include <thread>
#else
#include <uvw.hpp>
#endif
extern "C"
{
struct ub_ctx;
struct ub_result;
}
namespace llarp::dns
{
using ReplyFunction =
std::function<void(const SockAddr& resolver, const SockAddr& source, OwnedBuffer buf)>;
std::function<void(const SockAddr& reply_to, const SockAddr& from_resolver, OwnedBuffer buf)>;
using FailFunction =
std::function<void(const SockAddr& resolver, const SockAddr& source, Message msg)>;
std::function<void(const SockAddr& reply_to, const SockAddr& from_resolver, Message msg)>;
class UnboundResolver : public std::enable_shared_from_this<UnboundResolver>
{
@ -28,11 +35,16 @@ namespace llarp::dns
ub_ctx* unboundContext;
std::atomic<bool> started;
std::unique_ptr<std::thread> runner;
#ifdef _WIN32
std::thread runner;
#else
std::weak_ptr<uvw::Loop> loop;
std::shared_ptr<uvw::PollHandle> udp;
#endif
ReplyFunction replyFunc;
FailFunction failFunc;
void
Reset();

@ -244,7 +244,7 @@ namespace llarp::uv
std::shared_ptr<llarp::vpn::NetworkInterface> netif,
std::function<void(llarp::net::IPPacket)> handler)
{
#ifndef _WIN32
#ifdef __linux__
using event_t = uvw::PollEvent;
auto handle = m_Impl->resource<uvw::PollHandle>(netif->PollFD());
#else
@ -264,7 +264,7 @@ namespace llarp::uv
}
});
#ifndef _WIN32
#ifdef __linux__
handle->start(uvw::PollHandle::Event::READABLE);
#else
handle->start();

@ -4,6 +4,8 @@
#include <llarp/net/ip_packet.hpp>
#include <set>
#include <oxenmq/variant.h>
namespace llarp
{
struct Context;
@ -59,6 +61,44 @@ namespace llarp::vpn
WritePacket(net::IPPacket pkt) = 0;
};
class IRouteManager
{
public:
using IPVariant_t = std::variant<huint32_t, huint128_t>;
IRouteManager() = default;
IRouteManager(const IRouteManager&) = delete;
IRouteManager(IRouteManager&&) = delete;
virtual ~IRouteManager() = default;
virtual void
AddRoute(IPVariant_t ip, IPVariant_t gateway) = 0;
virtual void
DelRoute(IPVariant_t ip, IPVariant_t gateway) = 0;
virtual void
AddDefaultRouteViaInterface(std::string ifname) = 0;
virtual void
DelDefaultRouteViaInterface(std::string ifname) = 0;
virtual void
AddRouteViaInterface(NetworkInterface& vpn, IPRange range) = 0;
virtual void
DelRouteViaInterface(NetworkInterface& vpn, IPRange range) = 0;
virtual std::vector<IPVariant_t>
GetGatewaysNotOnInterface(std::string ifname) = 0;
virtual void
AddBlackhole(){};
virtual void
DelBlackhole(){};
};
/// a vpn platform
/// responsible for obtaining vpn interfaces
class Platform
@ -73,6 +113,10 @@ namespace llarp::vpn
/// blocks until ready, throws on error
virtual std::shared_ptr<NetworkInterface>
ObtainInterface(InterfaceInfo info) = 0;
/// get owned ip route manager for managing routing table
virtual IRouteManager&
RouteManager() = 0;
};
/// create native vpn platform

@ -35,8 +35,8 @@ namespace llarp
namespace handlers
{
// Intercepts DNS IP packets going to an IP on the tun interface; this is currently used on
// Android where binding to a DNS port (i.e. via llarp::dns::Proxy) isn't possible because of OS
// restrictions, but a tun interface *is* available.
// Android and macOS where binding to a DNS port (i.e. via llarp::dns::Proxy) isn't possible
// because of OS restrictions, but a tun interface *is* available.
class DnsInterceptor : public dns::PacketHandler
{
public:
@ -61,6 +61,27 @@ namespace llarp
m_Endpoint->HandleWriteIPPacket(
pkt.ConstBuffer(), net::ExpandV4(from.asIPv4()), net::ExpandV4(to.asIPv4()), 0);
}
#ifdef ANDROID
bool
IsUpstreamResolver(const SockAddr&, const SockAddr&) const override
{
return true;
}
#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
// won't work for us. However when active the mac also only queries the main tunnel IP for
// DNS, so we consider anything else to be upstream-bound DNS to let it through the tunnel.
bool
IsUpstreamResolver(const SockAddr& to, const SockAddr& from) const override
{
return to.asIPv6() != m_Endpoint->GetIfAddr();
}
#endif
};
TunEndpoint::TunEndpoint(AbstractRouter* r, service::Context* parent)
@ -74,7 +95,7 @@ namespace llarp
});
m_PacketRouter = std::make_unique<vpn::PacketRouter>(
[this](net::IPPacket pkt) { HandleGotUserPacket(std::move(pkt)); });
#ifdef ANDROID
#if defined(ANDROID) || defined(__APPLE__)
m_Resolver = std::make_shared<DnsInterceptor>(r, this);
m_PacketRouter->AddUDPHandler(huint16_t{53}, [&](net::IPPacket pkt) {
const size_t ip_header_size = (pkt.Header()->ihl * 4);
@ -87,8 +108,8 @@ namespace llarp
OwnedBuffer buf{pkt.sz - (8 + ip_header_size)};
std::copy_n(ptr + 8, buf.sz, buf.buf.get());
if (m_Resolver->ShouldHandlePacket(laddr, raddr, buf))
m_Resolver->HandlePacket(laddr, raddr, buf);
if (m_Resolver->ShouldHandlePacket(raddr, laddr, buf))
m_Resolver->HandlePacket(raddr, laddr, buf);
else
HandleGotUserPacket(std::move(pkt));
});
@ -136,6 +157,17 @@ namespace llarp
m_Resolver->Restart();
}
std::vector<SockAddr>
TunEndpoint::ReconfigureDNS(std::vector<SockAddr> servers)
{
std::swap(m_UpstreamResolvers, servers);
m_Resolver->Stop();
if (!m_Resolver->Start(
m_LocalResolverAddr.createSockAddr(), m_UpstreamResolvers, m_hostfiles))
llarp::LogError(Name(), " failed to reconfigure DNS server");
return servers;
}
bool
TunEndpoint::Configure(const NetworkConfig& conf, const DnsConfig& dnsConf)
{
@ -909,14 +941,17 @@ namespace llarp
LogError(Name(), " failed to add network interface");
return false;
}
#ifdef __APPLE__
m_OurIPv6 = llarp::huint128_t{
llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(m_OurRange.addr).h}};
#else
const auto maybe = GetInterfaceIPv6Address(m_IfName);
if (maybe.has_value())
{
m_OurIPv6 = *maybe;
LogInfo(Name(), " has ipv6 address ", m_OurIPv6);
}
#endif
Router()->loop()->add_ticker([this] { Flush(); });
// Attempt to register DNS on the interface
@ -974,6 +1009,7 @@ namespace llarp
TunEndpoint::Stop()
{
// save address map if applicable
#ifndef ANDROID
if (m_PersistAddrMapFile)
{
const auto& file = *m_PersistAddrMapFile;
@ -994,6 +1030,7 @@ namespace llarp
maybe->write(data.data(), data.size());
}
}
#endif
if (m_Resolver)
m_Resolver->Stop();
return llarp::service::Endpoint::Stop();

@ -43,6 +43,11 @@ namespace llarp
void
Thaw() override;
// Reconfigures DNS servers and restarts libunbound with the new servers. Returns the old set
// of configured dns servers.
std::vector<SockAddr>
ReconfigureDNS(std::vector<SockAddr> servers);
bool
Configure(const NetworkConfig& conf, const DnsConfig& dnsConf) override;

@ -1,561 +0,0 @@
#include "route.hpp"
#ifdef __linux__
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <net/if.h>
#include <arpa/inet.h>
#ifndef ANDROID
#include <sys/socket.h>
#include <linux/rtnetlink.h>
#endif
#include "net.hpp"
#include <exception>
#include <charconv>
#endif
#ifdef __APPLE__
#include "net.hpp"
#include <llarp/util/str.hpp>
#endif
#ifdef _WIN32
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <stdio.h>
#include <cstring>
#include <locale>
#include <codecvt>
#include "net_int.hpp"
#include "ip.hpp"
#endif
#include <sstream>
#include <llarp/util/logging/logger.hpp>
#include <llarp/util/str.hpp>
namespace llarp::net
{
#ifndef __linux__
void
Execute(std::string cmd)
{
LogInfo(cmd);
system(cmd.c_str());
}
#endif
#ifdef __linux__
#ifndef ANDROID
enum class GatewayMode
{
eFirstHop,
eLowerDefault,
eUpperDefault
};
struct NLSocket
{
NLSocket() : fd(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE))
{
if (fd == -1)
throw std::runtime_error("failed to make netlink socket");
}
~NLSocket()
{
if (fd != -1)
close(fd);
}
const int fd;
};
/* Helper structure for ip address data and attributes */
typedef struct
{
unsigned char family;
unsigned char bitlen;
unsigned char data[sizeof(struct in6_addr)];
} _inet_addr;
/* */
#define NLMSG_TAIL(nmsg) ((struct rtattr*)(((intptr_t)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
/* Add new data to rtattr */
int
rtattr_add(struct nlmsghdr* n, unsigned int maxlen, int type, const void* data, int alen)
{
int len = RTA_LENGTH(alen);
struct rtattr* rta;
if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen)
{
fprintf(stderr, "rtattr_add error: message exceeded bound of %d\n", maxlen);
return -1;
}
rta = NLMSG_TAIL(n);
rta->rta_type = type;
rta->rta_len = len;
if (alen)
{
memcpy(RTA_DATA(rta), data, alen);
}
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
return 0;
}
int
do_route(
int sock,
int cmd,
int flags,
const _inet_addr* dst,
const _inet_addr* gw,
GatewayMode mode,
int if_idx)
{
struct
{
struct nlmsghdr n;
struct rtmsg r;
char buf[4096];
} nl_request{};
/* Initialize request structure */
nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nl_request.n.nlmsg_flags = NLM_F_REQUEST | flags;
nl_request.n.nlmsg_type = cmd;
nl_request.n.nlmsg_pid = getpid();
nl_request.r.rtm_family = dst->family;
nl_request.r.rtm_table = RT_TABLE_MAIN;
if (if_idx)
{
nl_request.r.rtm_scope = RT_SCOPE_LINK;
}
else
{
nl_request.r.rtm_scope = RT_SCOPE_NOWHERE;
}
/* Set additional flags if NOT deleting route */
if (cmd != RTM_DELROUTE)
{
nl_request.r.rtm_protocol = RTPROT_BOOT;
nl_request.r.rtm_type = RTN_UNICAST;
}
nl_request.r.rtm_family = dst->family;
nl_request.r.rtm_dst_len = dst->bitlen;
nl_request.r.rtm_scope = 0;
/* Set gateway */
if (gw->bitlen != 0 and dst->family == AF_INET)
{
rtattr_add(&nl_request.n, sizeof(nl_request), RTA_GATEWAY, &gw->data, gw->bitlen / 8);
}
nl_request.r.rtm_family = gw->family;
if (mode == GatewayMode::eFirstHop)
{
rtattr_add(
&nl_request.n, sizeof(nl_request), /*RTA_NEWDST*/ RTA_DST, &dst->data, dst->bitlen / 8);
/* Set interface */
rtattr_add(&nl_request.n, sizeof(nl_request), RTA_OIF, &if_idx, sizeof(int));
}
if (mode == GatewayMode::eUpperDefault)
{
if (dst->family == AF_INET)
{
rtattr_add(
&nl_request.n,
sizeof(nl_request),
/*RTA_NEWDST*/ RTA_DST,
&dst->data,
sizeof(uint32_t));
}
else
{
rtattr_add(&nl_request.n, sizeof(nl_request), RTA_OIF, &if_idx, sizeof(int));
rtattr_add(
&nl_request.n,
sizeof(nl_request),
/*RTA_NEWDST*/ RTA_DST,
&dst->data,
sizeof(in6_addr));
}
}
/* Send message to the netlink */
return send(sock, &nl_request, sizeof(nl_request), 0);
}
int
read_addr(const char* addr, _inet_addr* res, int bitlen = 32)
{
if (strchr(addr, ':'))
{
res->family = AF_INET6;
res->bitlen = bitlen;
}
else
{
res->family = AF_INET;
res->bitlen = bitlen;
}
return inet_pton(res->family, addr, res->data);
}
#endif
#endif
#ifdef _WIN32
std::wstring
get_win_sys_path()
{
wchar_t win_sys_path[MAX_PATH] = {0};
const wchar_t* default_sys_path = L"C:\\Windows\\system32";
if (!GetSystemDirectoryW(win_sys_path, _countof(win_sys_path)))
{
wcsncpy(win_sys_path, default_sys_path, _countof(win_sys_path));
win_sys_path[_countof(win_sys_path) - 1] = L'\0';
}
return win_sys_path;
}
std::string
RouteCommand()
{
std::wstring wcmd = get_win_sys_path() + L"\\route.exe";
using convert_type = std::codecvt_utf8<wchar_t>;
std::wstring_convert<convert_type, wchar_t> converter;
return converter.to_bytes(wcmd);
}
std::string
NetshCommand()
{
std::wstring wcmd = get_win_sys_path() + L"\\netsh.exe";
using convert_type = std::codecvt_utf8<wchar_t>;
std::wstring_convert<convert_type, wchar_t> converter;
return converter.to_bytes(wcmd);
}
template <typename Visit>
void
ForEachWIN32Interface(Visit visit)
{
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
MIB_IPFORWARDTABLE* pIpForwardTable;
DWORD dwSize = 0;
DWORD dwRetVal = 0;
pIpForwardTable = (MIB_IPFORWARDTABLE*)MALLOC(sizeof(MIB_IPFORWARDTABLE));
if (pIpForwardTable == nullptr)
return;
if (GetIpForwardTable(pIpForwardTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER)
{
FREE(pIpForwardTable);
pIpForwardTable = (MIB_IPFORWARDTABLE*)MALLOC(dwSize);
if (pIpForwardTable == nullptr)
{
return;
}
}
if ((dwRetVal = GetIpForwardTable(pIpForwardTable, &dwSize, 0)) == NO_ERROR)
{
for (int i = 0; i < (int)pIpForwardTable->dwNumEntries; i++)
{
visit(&pIpForwardTable->table[i]);
}
}
FREE(pIpForwardTable);
#undef MALLOC
#undef FREE
}
std::optional<int>
GetInterfaceIndex(huint32_t ip)
{
std::optional<int> ret = std::nullopt;
ForEachWIN32Interface([&ret, n = ToNet(ip)](auto* iface) {
if (ret.has_value())
return;
if (iface->dwForwardNextHop == n.n)
{
ret = iface->dwForwardIfIndex;
}
});
return ret;
}
#endif
void
AddRoute(std::string ip, std::string gateway)
{
LogInfo("Add route: ", ip, " via ", gateway);
#ifdef __linux__
#ifndef ANDROID
NLSocket sock;
int if_idx = 0;
_inet_addr to_addr{};
_inet_addr gw_addr{};
int nl_cmd = RTM_NEWROUTE;
int nl_flags = NLM_F_CREATE | NLM_F_EXCL;
read_addr(gateway.c_str(), &gw_addr);
read_addr(ip.c_str(), &to_addr);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eFirstHop, if_idx);
#endif
#else
std::stringstream ss;
#if _WIN32
ss << RouteCommand() << " ADD " << ip << " MASK 255.255.255.255 " << gateway << " METRIC 2";
#elif __APPLE__
ss << "/sbin/route -n add -host " << ip << " " << gateway;
#else
#error unsupported platform
#endif
Execute(ss.str());
#endif
}
void
DelRoute(std::string ip, std::string gateway)
{
LogInfo("Delete route: ", ip, " via ", gateway);
#ifdef __linux__
#ifndef ANDROID
NLSocket sock;
int if_idx = 0;
_inet_addr to_addr{};
_inet_addr gw_addr{};
int nl_cmd = RTM_DELROUTE;
int nl_flags = 0;
read_addr(gateway.c_str(), &gw_addr);
read_addr(ip.c_str(), &to_addr);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eFirstHop, if_idx);
#endif
#else
std::stringstream ss;
#if _WIN32
ss << RouteCommand() << " DELETE " << ip << " MASK 255.255.255.255 " << gateway << " METRIC 2";
#elif __APPLE__
ss << "/sbin/route -n delete -host " << ip << " " << gateway;
#else
#error unsupported platform
#endif
Execute(ss.str());
#endif
}
void
AddDefaultRouteViaInterface(std::string ifname)
{
LogInfo("Add default route via ", ifname);
#ifdef __linux__
#ifndef ANDROID
NLSocket sock;
int if_idx = if_nametoindex(ifname.c_str());
_inet_addr to_addr{};
_inet_addr gw_addr{};
const auto maybe = GetInterfaceAddr(ifname);
if (not maybe.has_value())
throw std::runtime_error("we dont have our own net interface?");
int nl_cmd = RTM_NEWROUTE;
int nl_flags = NLM_F_CREATE | NLM_F_EXCL;
const auto host = maybe->asIPv4().ToString();
read_addr(host.c_str(), &gw_addr);
read_addr("0.0.0.0", &to_addr, 1);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eLowerDefault, if_idx);
read_addr("128.0.0.0", &to_addr, 1);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
const auto maybeInt = GetInterfaceIPv6Address(ifname);
if (maybeInt.has_value())
{
const auto host = maybeInt->ToString();
LogInfo("add v6 route via ", host);
read_addr(host.c_str(), &gw_addr, 128);
read_addr("::", &to_addr, 2);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
read_addr("4000::", &to_addr, 2);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
read_addr("8000::", &to_addr, 2);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
read_addr("c000::", &to_addr, 2);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
}
#endif
#elif _WIN32
// poke hole for loopback bacause god is dead on windows
Execute(RouteCommand() + " ADD 127.0.0.0 MASK 255.0.0.0 0.0.0.0");
huint32_t ip{};
ip.FromString(ifname);
const auto ipv6 = net::ExpandV4Lan(ip);
Execute(RouteCommand() + " ADD ::/2 " + ipv6.ToString());
Execute(RouteCommand() + " ADD 4000::/2 " + ipv6.ToString());
Execute(RouteCommand() + " ADD 8000::/2 " + ipv6.ToString());
Execute(RouteCommand() + " ADD c000::/2 " + ipv6.ToString());
ifname.back()++;
Execute(RouteCommand() + " ADD 0.0.0.0 MASK 128.0.0.0 " + ifname);
Execute(RouteCommand() + " ADD 128.0.0.0 MASK 128.0.0.0 " + ifname);
#elif __APPLE__
Execute("/sbin/route -n add -cloning -net 0.0.0.0 -netmask 128.0.0.0 -interface " + ifname);
Execute("/sbin/route -n add -cloning -net 128.0.0.0 -netmask 128.0.0.0 -interface " + ifname);
Execute("/sbin/route -n add -inet6 -net ::/2 -interface " + ifname);
Execute("/sbin/route -n add -inet6 -net 4000::/2 -interface " + ifname);
Execute("/sbin/route -n add -inet6 -net 8000::/2 -interface " + ifname);
Execute("/sbin/route -n add -inet6 -net c000::/2 -interface " + ifname);
#else
#error unsupported platform
#endif
}
void
DelDefaultRouteViaInterface(std::string ifname)
{
LogInfo("Remove default route via ", ifname);
#ifdef __linux__
#ifndef ANDROID
NLSocket sock;
int if_idx = if_nametoindex(ifname.c_str());
_inet_addr to_addr{};
_inet_addr gw_addr{};
const auto maybe = GetInterfaceAddr(ifname);
if (not maybe.has_value())
throw std::runtime_error("we dont have our own net interface?");
int nl_cmd = RTM_DELROUTE;
int nl_flags = 0;
const auto host = maybe->asIPv4().ToString();
read_addr(host.c_str(), &gw_addr);
read_addr("0.0.0.0", &to_addr, 1);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eLowerDefault, if_idx);
read_addr("128.0.0.0", &to_addr, 1);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
const auto maybeInt = GetInterfaceIPv6Address(ifname);
if (maybeInt.has_value())
{
const auto host = maybeInt->ToString();
LogInfo("del v6 route via ", host);
read_addr(host.c_str(), &gw_addr, 128);
read_addr("::", &to_addr, 2);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
read_addr("4000::", &to_addr, 2);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
read_addr("8000::", &to_addr, 2);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
read_addr("c000::", &to_addr, 2);
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
}
#endif
#elif _WIN32
huint32_t ip{};
ip.FromString(ifname);
const auto ipv6 = net::ExpandV4Lan(ip);
Execute(RouteCommand() + " DELETE ::/2 " + ipv6.ToString());
Execute(RouteCommand() + " DELETE 4000::/2 " + ipv6.ToString());
Execute(RouteCommand() + " DELETE 8000::/2 " + ipv6.ToString());
Execute(RouteCommand() + " DELETE c000::/2 " + ipv6.ToString());
ifname.back()++;
Execute(RouteCommand() + " DELETE 0.0.0.0 MASK 128.0.0.0 " + ifname);
Execute(RouteCommand() + " DELETE 128.0.0.0 MASK 128.0.0.0 " + ifname);
Execute(RouteCommand() + " DELETE 127.0.0.0 MASK 255.0.0.0 0.0.0.0");
#elif __APPLE__
Execute("/sbin/route -n delete -cloning -net 0.0.0.0 -netmask 128.0.0.0 -interface " + ifname);
Execute(
"/sbin/route -n delete -cloning -net 128.0.0.0 -netmask 128.0.0.0 -interface " + ifname);
Execute("/sbin/route -n delete -inet6 -net ::/2 -interface " + ifname);
Execute("/sbin/route -n delete -inet6 -net 4000::/2 -interface " + ifname);
Execute("/sbin/route -n delete -inet6 -net 8000::/2 -interface " + ifname);
Execute("/sbin/route -n delete -inet6 -net c000::/2 -interface " + ifname);
#else
#error unsupported platform
#endif
}
std::vector<std::string>
GetGatewaysNotOnInterface(std::string ifname)
{
std::vector<std::string> gateways;
#ifdef __linux__
#ifdef ANDROID
#else
std::ifstream inf("/proc/net/route");
for (std::string line; std::getline(inf, line);)
{
const auto parts = split(line, '\t');
if (parts[1].find_first_not_of('0') == std::string::npos and parts[0] != ifname)
{
const auto& ip = parts[2];
if ((ip.size() == sizeof(uint32_t) * 2) and oxenmq::is_hex(ip))
{
huint32_t x{};
oxenmq::from_hex(ip.begin(), ip.end(), reinterpret_cast<char*>(&x.h));
gateways.emplace_back(x.ToString());
}
}
}
#endif
return gateways;
#elif _WIN32
ForEachWIN32Interface([&](auto w32interface) {
struct in_addr gateway, interface_addr;
gateway.S_un.S_addr = (u_long)w32interface->dwForwardDest;
interface_addr.S_un.S_addr = (u_long)w32interface->dwForwardNextHop;
std::string interface_name{inet_ntoa(interface_addr)};
if ((!gateway.S_un.S_addr) and interface_name != ifname)
{
llarp::LogTrace(
"Win32 find gateway: Adding gateway (", interface_name, ") to list of gateways.");
gateways.push_back(std::move(interface_name));
}
});
return gateways;
#elif __APPLE__
LogDebug("get gateways not on ", ifname);
// mac os is so godawful man
FILE* p = popen("/usr/sbin/netstat -rn -f inet", "r");
if (p == nullptr)
{
return gateways;
}
char* line = nullptr;
size_t len = 0;
ssize_t read = 0;
while ((read = getline(&line, &len, p)) != -1)
{
const std::string line_str(line, len);
const auto parts = llarp::split_any(line_str, " "sv, true);
if (parts[0] == "default" and parts[3] != ifname)
{
gateways.emplace_back(parts[1]);
}
}
pclose(p);
return gateways;
#else
#error unsupported platform
#endif
}
} // namespace llarp::net

@ -1,28 +0,0 @@
#pragma once
#include <string>
#include <vector>
namespace llarp::net
{
/// get every ip address that is a gateway that isn't owned by interface with name ifname
std::vector<std::string>
GetGatewaysNotOnInterface(std::string ifname);
/// add route to ipaddr via gateway ip
void
AddRoute(std::string ipaddr, std::string gateway);
/// delete route to ipaddr via gateway ip
void
DelRoute(std::string ipaddr, std::string gateway);
/// add default route via interface with name ifname
void
AddDefaultRouteViaInterface(std::string ifname);
/// delete default route via interface with name ifname
void
DelDefaultRouteViaInterface(std::string ifname);
} // namespace llarp::net

@ -244,7 +244,7 @@ namespace llarp
// NOTE: this potentially involves multiple memory allocations,
// reimplement without split() if it is performance bottleneck
auto splits = split(str, ':');
auto splits = split(str, ":");
// TODO: having ":port" at the end makes this ambiguous with IPv6
// come up with a strategy for implementing
@ -260,7 +260,7 @@ namespace llarp
assert(splits.size() > 0);
// splits[0] should be dot-separated IPv4
auto ipSplits = split(splits[0], '.');
auto ipSplits = split(splits[0], ".");
if (ipSplits.size() != 4)
throw std::invalid_argument(stringify(str, " is not a valid IPv4 address"));
@ -299,24 +299,17 @@ namespace llarp
SockAddr::hostString() const
{
std::string str;
char buf[INET6_ADDRSTRLEN] = {0x0};
if (isIPv4())
{
// handle IPv4 mapped addrs
constexpr auto MaxIPv4PlusPortStringSize = 22;
str.reserve(MaxIPv4PlusPortStringSize);
char buf[128] = {0x0};
inet_ntop(AF_INET, &m_addr4.sin_addr.s_addr, buf, sizeof(buf));
str.append(buf);
str = buf;
}
else
{
constexpr auto MaxIPv6PlusPortStringSize = 128;
str.reserve(MaxIPv6PlusPortStringSize);
char buf[128] = {0x0};
inet_ntop(AF_INET6, &m_addr.sin6_addr.s6_addr, buf, sizeof(buf));
str.reserve(std::strlen(buf) + 2);
str.append("[");
str.append(buf);
str.append("]");

@ -3,6 +3,9 @@
namespace llarp
{
/// The maximum number of peers we will flood a gossiped RC to when propagating an RC
constexpr size_t MaxGossipPeers = 20;
struct I_RCGossiper
{
virtual ~I_RCGossiper() = default;

@ -76,17 +76,37 @@ namespace llarp
DHTImmediateMessage gossip;
gossip.msgs.emplace_back(new dht::GotRouterMessage(dht::Key_t{}, 0, {rc}, false));
// send it to everyone
std::vector<RouterID> gossipTo;
// select peers to gossip to
m_LinkManager->ForEachPeer(
[&](const ILinkSession* peerSession, bool) {
// ensure connected session
if (not(peerSession && peerSession->IsEstablished()))
return;
// check if public router
const auto other_rc = peerSession->GetRemoteRC();
if (not other_rc.IsPublicRouter())
return;
gossipTo.emplace_back(other_rc.pubkey);
},
true);
std::unordered_set<RouterID> keys;
// grab the keys we want to use
std::sample(
gossipTo.begin(), gossipTo.end(), std::inserter(keys, keys.end()), MaxGossipPeers, CSRNG{});
m_LinkManager->ForEachPeer([&](ILinkSession* peerSession) {
// ensure connected session
if (not(peerSession && peerSession->IsEstablished()))
return;
// check if public router
const auto other_rc = peerSession->GetRemoteRC();
if (not other_rc.IsPublicRouter())
// exclude from gossip as we have not selected to use it
if (keys.count(peerSession->GetPubKey()) == 0)
return;
// encode message
ILinkSession::Message_t msg;
ILinkSession::Message_t msg{};
msg.resize(MAX_LINK_MSG_SIZE / 2);
llarp_buffer_t buf(msg);
if (not gossip.BEncode(&buf))

@ -1,7 +1,6 @@
#include "route_poker.hpp"
#include "abstractrouter.hpp"
#include "net/sock_addr.hpp"
#include <llarp/net/route.hpp>
#include <llarp/service/context.hpp>
#include <unordered_set>
@ -30,13 +29,15 @@ namespace llarp
void
RoutePoker::DisableRoute(huint32_t ip, huint32_t gateway)
{
net::DelRoute(ip.ToString(), gateway.ToString());
vpn::IRouteManager& route = m_Router->GetVPNPlatform()->RouteManager();
route.DelRoute(ip, gateway);
}
void
RoutePoker::EnableRoute(huint32_t ip, huint32_t gateway)
{
net::AddRoute(ip.ToString(), gateway.ToString());
vpn::IRouteManager& route = m_Router->GetVPNPlatform()->RouteManager();
route.AddRoute(ip, gateway);
}
void
@ -86,11 +87,13 @@ namespace llarp
RoutePoker::~RoutePoker()
{
vpn::IRouteManager& route = m_Router->GetVPNPlatform()->RouteManager();
for (const auto& [ip, gateway] : m_PokedRoutes)
{
if (gateway.h)
net::DelRoute(ip.ToString(), gateway.ToString());
route.DelRoute(ip, gateway);
}
route.DelBlackhole();
}
std::optional<huint32_t>
@ -100,14 +103,17 @@ namespace llarp
throw std::runtime_error("Attempting to use RoutePoker before calling Init");
const auto ep = m_Router->hiddenServiceContext().GetDefault();
const auto gateways = net::GetGatewaysNotOnInterface(ep->GetIfName());
vpn::IRouteManager& route = m_Router->GetVPNPlatform()->RouteManager();
const auto gateways = route.GetGatewaysNotOnInterface(ep->GetIfName());
if (gateways.empty())
{
return std::nullopt;
}
huint32_t addr{};
addr.FromString(gateways[0]);
return addr;
if (auto* ptr = std::get_if<huint32_t>(&gateways[0]))
{
return huint32_t{*ptr};
}
return std::nullopt;
}
void
@ -185,13 +191,17 @@ namespace llarp
void
RoutePoker::Up()
{
vpn::IRouteManager& route = m_Router->GetVPNPlatform()->RouteManager();
// black hole all routes by default
route.AddBlackhole();
// explicit route pokes for first hops
m_Router->ForEachPeer(
[&](auto session, auto) mutable { AddRoute(session->GetRemoteEndpoint().asIPv4()); },
false);
// add default route
const auto ep = m_Router->hiddenServiceContext().GetDefault();
net::AddDefaultRouteViaInterface(ep->GetIfName());
route.AddDefaultRouteViaInterface(ep->GetIfName());
}
void
@ -203,7 +213,11 @@ namespace llarp
false);
// remove default route
const auto ep = m_Router->hiddenServiceContext().GetDefault();
net::DelDefaultRouteViaInterface(ep->GetIfName());
vpn::IRouteManager& route = m_Router->GetVPNPlatform()->RouteManager();
route.DelDefaultRouteViaInterface(ep->GetIfName());
// delete route blackhole
route.DelBlackhole();
}
} // namespace llarp

@ -12,7 +12,6 @@
#include <llarp/link/server.hpp>
#include <llarp/messages/link_message.hpp>
#include <llarp/net/net.hpp>
#include <llarp/net/route.hpp>
#include <stdexcept>
#include <llarp/util/buffer.hpp>
#include <llarp/util/logging/file_logger.hpp>

@ -22,7 +22,7 @@ namespace llarp::rpc
if (m_AuthURL.empty() or m_AuthMethod.empty())
return;
m_LMQ->connect_remote(
m_AuthURL,
oxenmq::address{m_AuthURL},
[self = shared_from_this()](oxenmq::ConnectionID c) {
self->m_Conn = std::move(c);
LogInfo("connected to endpoint auth server via ", *self->m_Conn);

@ -4,13 +4,13 @@
#include <nlohmann/json.hpp>
#include <llarp/exit/context.hpp>
#include <llarp/net/ip_range.hpp>
#include <llarp/net/route.hpp>
#include <llarp/quic/tunnel.hpp>
#include <llarp/service/context.hpp>
#include <llarp/service/outbound_context.hpp>
#include <llarp/service/auth.hpp>
#include <llarp/service/name.hpp>
#include <llarp/router/abstractrouter.hpp>
#include <llarp/dns/dns.hpp>
namespace llarp::rpc
{
@ -562,6 +562,47 @@ namespace llarp::rpc
});
});
})
.add_request_command(
"dns_query",
[&](oxenmq::Message& msg) {
HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) {
std::string endpoint{"default"};
if (const auto itr = obj.find("endpoint"); itr != obj.end())
{
endpoint = itr->get<std::string>();
}
std::string qname{};
dns::QType_t qtype = dns::qTypeA;
if (const auto itr = obj.find("qname"); itr != obj.end())
{
qname = itr->get<std::string>();
}
if (const auto itr = obj.find("qtype"); itr != obj.end())
{
qtype = itr->get<dns::QType_t>();
}
dns::Message msg{dns::Question{qname, qtype}};
if (auto ep_ptr = (GetEndpointByName(r, endpoint)))
{
if (auto ep = reinterpret_cast<dns::IQueryHandler*>(ep_ptr.get()))
{
if (ep->ShouldHookDNSMessage(msg))
{
ep->HandleHookedDNSMessage(std::move(msg), [reply](dns::Message msg) {
reply(CreateJSONResponse(msg.ToJSON()));
});
return;
}
}
reply(CreateJSONError("dns query not accepted by endpoint"));
return;
}
reply(CreateJSONError("no such endpoint for dns query"));
});
})
.add_request_command("config", [&](oxenmq::Message& msg) {
HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) {
{

@ -11,7 +11,6 @@
#include "identity.hpp"
#include "pendingbuffer.hpp"
#include "protocol.hpp"
#include "quic/server.hpp"
#include "sendcontext.hpp"
#include "service/protocol_type.hpp"
#include "session.hpp"

@ -67,35 +67,6 @@ namespace llarp
return str;
}
std::vector<std::string_view>
split(const std::string_view str, char delimiter)
{
std::vector<std::string_view> splits;
const auto str_size = str.size();
size_t last = 0;
size_t next = 0;
while (last < str_size and next < std::string_view::npos)
{
next = str.find_first_of(delimiter, last);
if (next > last)
{
splits.push_back(str.substr(last, next - last));
last = next;
// advance to next non-delimiter
while (str[last] == delimiter and last < str_size)
last++;
}
else
{
break;
}
}
return splits;
}
using namespace std::literals;
std::vector<std::string_view>

@ -35,14 +35,6 @@ namespace llarp
return o.str();
}
/// Split a string on a given delimiter
//
/// @param str is the string to split
/// @param delimiter is the character to split on
/// @return a vector of std::string_views with the split words, excluding the delimeter
std::vector<std::string_view>
split(const std::string_view str, char delimiter);
using namespace std::literals;
/// Returns true if the first string is equal to the second string, compared case-insensitively.

@ -60,9 +60,32 @@ namespace llarp::vpn
}
};
class AndroidRouteManager : public IRouteManager
{
void AddRoute(IPVariant_t, IPVariant_t) override{};
void DelRoute(IPVariant_t, IPVariant_t) override{};
void AddDefaultRouteViaInterface(std::string) override{};
void DelDefaultRouteViaInterface(std::string) override{};
void
AddRouteViaInterface(NetworkInterface&, IPRange) override{};
void
DelRouteViaInterface(NetworkInterface&, IPRange) override{};
std::vector<IPVariant_t> GetGatewaysNotOnInterface(std::string) override
{
return std::vector<IPVariant_t>{};
};
};
class AndroidPlatform : public Platform
{
const int fd;
AndroidRouteManager _routeManager{};
public:
AndroidPlatform(llarp::Context* ctx) : fd(ctx->androidFD)
@ -73,6 +96,11 @@ namespace llarp::vpn
{
return std::make_shared<AndroidInterface>(std::move(info), fd);
}
IRouteManager&
RouteManager() override
{
return _routeManager;
}
};
} // namespace llarp::vpn

@ -1,173 +0,0 @@
#pragma once
#include <llarp/ev/vpn.hpp>
#include "common.hpp"
#include <sys/kern_control.h>
#include <sys/sys_domain.h>
#include <sys/kern_event.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
namespace llarp::vpn
{
class AppleInterface : public NetworkInterface
{
const int m_FD;
const InterfaceInfo m_Info;
std::string m_IfName;
static void
Exec(std::string cmd)
{
LogDebug(cmd);
system(cmd.c_str());
}
public:
AppleInterface(InterfaceInfo info)
: m_FD{::socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL)}, m_Info{std::move(info)}
{
if (m_FD == -1)
throw std::invalid_argument{"cannot open control socket: " + std::string{strerror(errno)}};
ctl_info cinfo{};
const std::string apple_utun = "com.apple.net.utun_control";
std::copy_n(apple_utun.c_str(), apple_utun.size(), cinfo.ctl_name);
if (::ioctl(m_FD, CTLIOCGINFO, &cinfo) < 0)
{
::close(m_FD);
throw std::runtime_error{"ioctl CTLIOCGINFO call failed: " + std::string{strerror(errno)}};
}
sockaddr_ctl addr{};
addr.sc_id = cinfo.ctl_id;
addr.sc_len = sizeof(addr);
addr.sc_family = AF_SYSTEM;
addr.ss_sysaddr = AF_SYS_CONTROL;
addr.sc_unit = 0;
if (connect(m_FD, (sockaddr*)&addr, sizeof(addr)) < 0)
{
::close(m_FD);
throw std::runtime_error{
"cannot connect to control socket address: " + std::string{strerror(errno)}};
}
uint32_t namesz = IFNAMSIZ;
char name[IFNAMSIZ + 1]{};
if (getsockopt(m_FD, SYSPROTO_CONTROL, 2, name, &namesz) < 0)
{
::close(m_FD);
throw std::runtime_error{
"cannot query for interface name: " + std::string{strerror(errno)}};
}
m_IfName = name;
for (const auto& ifaddr : m_Info.addrs)
{
if (ifaddr.fam == AF_INET)
{
const huint32_t addr = net::TruncateV6(ifaddr.range.addr);
const huint32_t netmask = net::TruncateV6(ifaddr.range.netmask_bits);
const huint32_t daddr = addr & netmask;
Exec(
"/sbin/ifconfig " + m_IfName + " " + addr.ToString() + " " + daddr.ToString()
+ " mtu 1500 netmask 255.255.255.255 up");
Exec(
"/sbin/route add " + daddr.ToString() + " -netmask " + netmask.ToString()
+ " -interface " + m_IfName);
Exec("/sbin/route add " + addr.ToString() + " -interface lo0");
}
else if (ifaddr.fam == AF_INET6)
{
Exec("/sbin/ifconfig " + m_IfName + " inet6 " + ifaddr.range.ToString());
}
}
}
~AppleInterface()
{
::close(m_FD);
}
std::string
IfName() const override
{
return m_IfName;
}
int
PollFD() const override
{
return m_FD;
}
net::IPPacket
ReadNextPacket() override
{
constexpr int uintsize = sizeof(unsigned int);
net::IPPacket pkt{};
unsigned int pktinfo = 0;
const struct iovec vecs[2] = {
{.iov_base = &pktinfo, .iov_len = uintsize},
{.iov_base = pkt.buf, .iov_len = sizeof(pkt.buf)}};
int sz = readv(m_FD, vecs, 2);
if (sz >= uintsize)
pkt.sz = sz - uintsize;
else if (sz >= 0 || errno == EAGAIN || errno == EWOULDBLOCK)
pkt.sz = 0;
else
throw std::error_code{errno, std::system_category()};
return pkt;
}
bool
WritePacket(net::IPPacket pkt) override
{
static unsigned int af4 = htonl(AF_INET);
static unsigned int af6 = htonl(AF_INET6);
const struct iovec vecs[2] = {
{.iov_base = pkt.IsV6() ? &af6 : &af4, .iov_len = sizeof(unsigned int)},
{.iov_base = pkt.buf, .iov_len = pkt.sz}};
ssize_t n = writev(m_FD, vecs, 2);
if (n >= (int)sizeof(unsigned int))
{
n -= sizeof(unsigned int);
return static_cast<size_t>(n) == pkt.sz;
}
return false;
}
};
class ApplePlatform : public Platform
{
public:
std::shared_ptr<NetworkInterface>
ObtainInterface(InterfaceInfo info) override
{
return std::make_shared<AppleInterface>(std::move(info));
}
};
} // namespace llarp::vpn

@ -6,9 +6,19 @@
#include <sys/types.h>
#include <fcntl.h>
#include "common.hpp"
#include <linux/if.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/rtnetlink.h>
#include <llarp/net/net.hpp>
#include <llarp/util/str.hpp>
#include <exception>
namespace llarp::vpn
{
struct in6_ifreq
@ -121,14 +131,334 @@ namespace llarp::vpn
}
};
class LinuxRouteManager : public IRouteManager
{
const int fd;
enum class GatewayMode
{
eFirstHop,
eLowerDefault,
eUpperDefault
};
struct NLRequest
{
nlmsghdr n;
rtmsg r;
char buf[4096];
void
AddData(int type, const void* data, int alen)
{
#define NLMSG_TAIL(nmsg) ((struct rtattr*)(((intptr_t)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
int len = RTA_LENGTH(alen);
rtattr* rta;
if (NLMSG_ALIGN(n.nlmsg_len) + RTA_ALIGN(len) > sizeof(*this))
{
throw std::length_error{"nlrequest add data overflow"};
}
rta = NLMSG_TAIL(&n);
rta->rta_type = type;
rta->rta_len = len;
if (alen)
{
memcpy(RTA_DATA(rta), data, alen);
}
n.nlmsg_len = NLMSG_ALIGN(n.nlmsg_len) + RTA_ALIGN(len);
#undef NLMSG_TAIL
}
};
/* Helper structure for ip address data and attributes */
struct _inet_addr
{
unsigned char family;
unsigned char bitlen;
unsigned char data[sizeof(struct in6_addr)];
_inet_addr(huint32_t addr, size_t bits = 32)
{
family = AF_INET;
bitlen = bits;
htobe32buf(data, addr.h);
}
_inet_addr(huint128_t addr, size_t bits = 128)
{
family = AF_INET6;
bitlen = bits;
const nuint128_t net = ToNet(addr);
std::memcpy(data, &net, 16);
}
};
void
Blackhole(int cmd, int flags, int af)
{
NLRequest nl_request{};
/* Initialize request structure */
nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nl_request.n.nlmsg_flags = NLM_F_REQUEST | flags;
nl_request.n.nlmsg_type = cmd;
nl_request.n.nlmsg_pid = getpid();
nl_request.r.rtm_family = af;
nl_request.r.rtm_table = RT_TABLE_LOCAL;
nl_request.r.rtm_type = RTN_BLACKHOLE;
nl_request.r.rtm_scope = RT_SCOPE_UNIVERSE;
if (af == AF_INET)
{
uint32_t addr{};
nl_request.AddData(RTA_DST, &addr, sizeof(addr));
}
else
{
uint128_t addr{};
nl_request.AddData(RTA_DST, &addr, sizeof(addr));
}
send(fd, &nl_request, sizeof(nl_request), 0);
}
void
Route(
int cmd,
int flags,
const _inet_addr& dst,
const _inet_addr& gw,
GatewayMode mode,
int if_idx)
{
NLRequest nl_request{};
/* Initialize request structure */
nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nl_request.n.nlmsg_flags = NLM_F_REQUEST | flags;
nl_request.n.nlmsg_type = cmd;
nl_request.n.nlmsg_pid = getpid();
nl_request.r.rtm_family = dst.family;
nl_request.r.rtm_table = RT_TABLE_MAIN;
if (if_idx)
{
nl_request.r.rtm_scope = RT_SCOPE_LINK;
}
else
{
nl_request.r.rtm_scope = RT_SCOPE_NOWHERE;
}
/* Set additional flags if NOT deleting route */
if (cmd != RTM_DELROUTE)
{
nl_request.r.rtm_protocol = RTPROT_BOOT;
nl_request.r.rtm_type = RTN_UNICAST;
}
nl_request.r.rtm_family = dst.family;
nl_request.r.rtm_dst_len = dst.bitlen;
nl_request.r.rtm_scope = 0;
/* Set gateway */
if (gw.bitlen != 0 and dst.family == AF_INET)
{
nl_request.AddData(RTA_GATEWAY, &gw.data, gw.bitlen / 8);
}
nl_request.r.rtm_family = gw.family;
if (mode == GatewayMode::eFirstHop)
{
nl_request.AddData(RTA_DST, &dst.data, dst.bitlen / 8);
/* Set interface */
nl_request.AddData(RTA_OIF, &if_idx, sizeof(int));
}
if (mode == GatewayMode::eUpperDefault)
{
if (dst.family == AF_INET)
{
nl_request.AddData(RTA_DST, &dst.data, sizeof(uint32_t));
}
else
{
nl_request.AddData(RTA_OIF, &if_idx, sizeof(int));
nl_request.AddData(RTA_DST, &dst.data, sizeof(in6_addr));
}
}
/* Send message to the netlink */
send(fd, &nl_request, sizeof(nl_request), 0);
}
void
DefaultRouteViaInterface(std::string ifname, int cmd, int flags)
{
int if_idx = if_nametoindex(ifname.c_str());
const auto maybe = GetInterfaceAddr(ifname);
if (not maybe)
throw std::runtime_error{"we dont have our own network interface?"};
const _inet_addr gateway{maybe->asIPv4()};
const _inet_addr lower{ipaddr_ipv4_bits(0, 0, 0, 0), 1};
const _inet_addr upper{ipaddr_ipv4_bits(128, 0, 0, 0), 1};
Route(cmd, flags, lower, gateway, GatewayMode::eLowerDefault, if_idx);
Route(cmd, flags, upper, gateway, GatewayMode::eUpperDefault, if_idx);
if (const auto maybe6 = GetInterfaceIPv6Address(ifname))
{
const _inet_addr gateway6{*maybe6, 128};
for (const std::string str : {"::", "4000::", "8000::", "c000::"})
{
huint128_t _hole{};
_hole.FromString(str);
const _inet_addr hole6{_hole, 2};
Route(cmd, flags, hole6, gateway6, GatewayMode::eUpperDefault, if_idx);
}
}
}
void
RouteViaInterface(int cmd, int flags, std::string ifname, IPRange range)
{
int if_idx = if_nametoindex(ifname.c_str());
if (range.IsV4())
{
const auto maybe = GetInterfaceAddr(ifname);
if (not maybe)
throw std::runtime_error{"we dont have our own network interface?"};
const _inet_addr gateway{maybe->asIPv4()};
const _inet_addr addr{
net::TruncateV6(range.addr), bits::count_bits(net::TruncateV6(range.netmask_bits))};
Route(cmd, flags, addr, gateway, GatewayMode::eUpperDefault, if_idx);
}
else
{
const auto maybe = GetInterfaceIPv6Address(ifname);
if (not maybe)
throw std::runtime_error{"we dont have our own network interface?"};
const _inet_addr gateway{*maybe, 128};
const _inet_addr addr{range.addr, bits::count_bits(range.netmask_bits)};
Route(cmd, flags, addr, gateway, GatewayMode::eUpperDefault, if_idx);
}
}
void
Route(int cmd, int flags, IPVariant_t ip, IPVariant_t gateway)
{
// do bullshit double std::visit because lol variants
std::visit(
[gateway, cmd, flags, this](auto&& ip) {
const _inet_addr toAddr{ip};
std::visit(
[toAddr, cmd, flags, this](auto&& gateway) {
const _inet_addr gwAddr{gateway};
Route(cmd, flags, toAddr, gwAddr, GatewayMode::eFirstHop, 0);
},
gateway);
},
ip);
}
public:
LinuxRouteManager() : fd{socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)}
{
if (fd == -1)
throw std::runtime_error{"failed to make netlink socket"};
}
~LinuxRouteManager()
{
close(fd);
}
void
AddRoute(IPVariant_t ip, IPVariant_t gateway) override
{
Route(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, ip, gateway);
}
void
DelRoute(IPVariant_t ip, IPVariant_t gateway) override
{
Route(RTM_DELROUTE, 0, ip, gateway);
}
void
AddDefaultRouteViaInterface(std::string ifname) override
{
DefaultRouteViaInterface(ifname, RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL);
}
void
DelDefaultRouteViaInterface(std::string ifname) override
{
DefaultRouteViaInterface(ifname, RTM_DELROUTE, 0);
}
void
AddRouteViaInterface(NetworkInterface& vpn, IPRange range) override
{
RouteViaInterface(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, vpn.IfName(), range);
}
void
DelRouteViaInterface(NetworkInterface& vpn, IPRange range) override
{
RouteViaInterface(RTM_DELROUTE, 0, vpn.IfName(), range);
}
std::vector<IPVariant_t>
GetGatewaysNotOnInterface(std::string ifname) override
{
std::vector<IPVariant_t> gateways{};
std::ifstream inf{"/proc/net/route"};
for (std::string line; std::getline(inf, line);)
{
const auto parts = split(line, "\t");
if (parts[1].find_first_not_of('0') == std::string::npos and parts[0] != ifname)
{
const auto& ip = parts[2];
if ((ip.size() == sizeof(uint32_t) * 2) and oxenmq::is_hex(ip))
{
huint32_t x{};
oxenmq::from_hex(ip.begin(), ip.end(), reinterpret_cast<char*>(&x.h));
gateways.emplace_back(x);
}
}
}
return gateways;
}
void
AddBlackhole() override
{
Blackhole(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, AF_INET);
Blackhole(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, AF_INET6);
}
void
DelBlackhole() override
{
Blackhole(RTM_DELROUTE, 0, AF_INET);
Blackhole(RTM_DELROUTE, 0, AF_INET6);
}
};
class LinuxPlatform : public Platform
{
LinuxRouteManager _routeManager{};
public:
std::shared_ptr<NetworkInterface>
ObtainInterface(InterfaceInfo info) override
{
return std::make_shared<LinuxInterface>(std::move(info));
};
IRouteManager&
RouteManager() override
{
return _routeManager;
}
};
} // namespace llarp::vpn

@ -1,3 +1,6 @@
#include <llarp/ev/vpn.hpp>
#ifdef _WIN32
#include "win32.hpp"
#endif
@ -8,9 +11,8 @@
#include "linux.hpp"
#endif
#endif
#ifdef __APPLE__
#include "apple.hpp"
#endif
#include <exception>
namespace llarp::vpn
{
@ -30,7 +32,7 @@ namespace llarp::vpn
#endif
#endif
#ifdef __APPLE__
plat = std::make_shared<vpn::ApplePlatform>();
throw std::runtime_error{"not supported"};
#endif
return plat;
}

@ -117,6 +117,57 @@ namespace llarp::vpn
return deviceid;
}
template <typename Visit>
void
ForEachWIN32Interface(Visit visit)
{
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
MIB_IPFORWARDTABLE* pIpForwardTable;
DWORD dwSize = 0;
DWORD dwRetVal = 0;
pIpForwardTable = (MIB_IPFORWARDTABLE*)MALLOC(sizeof(MIB_IPFORWARDTABLE));
if (pIpForwardTable == nullptr)
return;
if (GetIpForwardTable(pIpForwardTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER)
{
FREE(pIpForwardTable);
pIpForwardTable = (MIB_IPFORWARDTABLE*)MALLOC(dwSize);
if (pIpForwardTable == nullptr)
{
return;
}
}
if ((dwRetVal = GetIpForwardTable(pIpForwardTable, &dwSize, 0)) == NO_ERROR)
{
for (int i = 0; i < (int)pIpForwardTable->dwNumEntries; i++)
{
visit(&pIpForwardTable->table[i]);
}
}
FREE(pIpForwardTable);
#undef MALLOC
#undef FREE
}
std::optional<int>
GetInterfaceIndex(huint32_t ip)
{
std::optional<int> ret = std::nullopt;
ForEachWIN32Interface([&ret, n = ToNet(ip)](auto* iface) {
if (ret.has_value())
return;
if (iface->dwForwardNextHop == n.n)
{
ret = iface->dwForwardIfIndex;
}
});
return ret;
}
class Win32Interface final : public NetworkInterface
{
std::atomic<bool> m_Run;
@ -124,7 +175,7 @@ namespace llarp::vpn
std::vector<std::thread> m_Threads;
thread::Queue<net::IPPacket> m_ReadQueue;
const InterfaceInfo m_Info;
InterfaceInfo m_Info;
static std::wstring
get_win_sys_path()
@ -159,6 +210,16 @@ namespace llarp::vpn
}
public:
static std::string
RouteCommand()
{
std::wstring wcmd = get_win_sys_path() + L"\\route.exe";
using convert_type = std::codecvt_utf8<wchar_t>;
std::wstring_convert<convert_type, wchar_t> converter;
return converter.to_bytes(wcmd);
}
Win32Interface(InterfaceInfo info) : m_ReadQueue{1024}, m_Info{std::move(info)}
{
DWORD len;
@ -213,6 +274,7 @@ namespace llarp::vpn
IPADDR sock[3]{};
const nuint32_t addr = xhtonl(net::TruncateV6(ifaddr.range.addr));
ip = net::TruncateV6(ifaddr.range.addr);
m_Info.ifname = ip.ToString();
const nuint32_t mask = xhtonl(net::TruncateV6(ifaddr.range.netmask_bits));
LogInfo("address ", addr, " netmask ", mask);
sock[0] = addr.n;
@ -306,7 +368,7 @@ namespace llarp::vpn
{
if (ifaddr.fam == AF_INET6)
{
const auto maybe = net::GetInterfaceIndex(ip);
const auto maybe = GetInterfaceIndex(ip);
if (maybe.has_value())
{
NetSH(
@ -348,7 +410,7 @@ namespace llarp::vpn
std::string
IfName() const override
{
return "";
return m_Info.ifname;
}
void
@ -433,8 +495,138 @@ namespace llarp::vpn
}
};
class Win32RouteManager : public IRouteManager
{
void
Execute(std::string cmd) const
{
LogInfo(cmd);
::system(cmd.c_str());
}
static std::string
RouteCommand()
{
return Win32Interface::RouteCommand();
}
void
Route(IPVariant_t ip, IPVariant_t gateway, std::string cmd)
{
std::stringstream ss;
std::string ip_str;
std::string gateway_str;
std::visit([&ip_str](auto&& ip) { ip_str = ip.ToString(); }, ip);
std::visit([&gateway_str](auto&& gateway) { gateway_str = gateway.ToString(); }, gateway);
ss << RouteCommand() << " " << cmd << " " << ip_str << " MASK 255.255.255.255 " << gateway_str
<< " METRIC 2";
Execute(ss.str());
}
void
DefaultRouteViaInterface(std::string ifname, std::string cmd)
{
// poke hole for loopback bacause god is dead on windows
Execute(RouteCommand() + " " + cmd + " 127.0.0.0 MASK 255.0.0.0 0.0.0.0");
huint32_t ip{};
ip.FromString(ifname);
const auto ipv6 = net::ExpandV4Lan(ip);
Execute(RouteCommand() + " " + cmd + " ::/2 " + ipv6.ToString());
Execute(RouteCommand() + " " + cmd + " 4000::/2 " + ipv6.ToString());
Execute(RouteCommand() + " " + cmd + " 8000::/2 " + ipv6.ToString());
Execute(RouteCommand() + " " + cmd + " c000::/2 " + ipv6.ToString());
ifname.back()++;
Execute(RouteCommand() + " " + cmd + " 0.0.0.0 MASK 128.0.0.0 " + ifname);
Execute(RouteCommand() + " " + cmd + " 128.0.0.0 MASK 128.0.0.0 " + ifname);
}
void
RouteViaInterface(std::string ifname, IPRange range, std::string cmd)
{
if (range.IsV4())
{
const huint32_t addr4 = net::TruncateV6(range.addr);
const huint32_t mask4 = net::TruncateV6(range.netmask_bits);
Execute(
RouteCommand() + " " + cmd + " " + addr4.ToString() + " MASK " + mask4.ToString() + " "
+ ifname);
}
else
{
Execute(
RouteCommand() + " " + cmd + range.addr.ToString() + " MASK "
+ range.netmask_bits.ToString() + " " + ifname);
}
}
public:
void
AddRoute(IPVariant_t ip, IPVariant_t gateway) override
{
Route(ip, gateway, "ADD");
}
void
DelRoute(IPVariant_t ip, IPVariant_t gateway) override
{
Route(ip, gateway, "DELETE");
}
void
AddRouteViaInterface(NetworkInterface& vpn, IPRange range) override
{
RouteViaInterface(vpn.IfName(), range, "ADD");
}
void
DelRouteViaInterface(NetworkInterface& vpn, IPRange range) override
{
RouteViaInterface(vpn.IfName(), range, "DELETE");
}
std::vector<IPVariant_t>
GetGatewaysNotOnInterface(std::string ifname) override
{
std::vector<IPVariant_t> gateways;
ForEachWIN32Interface([&](auto w32interface) {
struct in_addr gateway, interface_addr;
gateway.S_un.S_addr = (u_long)w32interface->dwForwardDest;
interface_addr.S_un.S_addr = (u_long)w32interface->dwForwardNextHop;
std::string interface_name{inet_ntoa(interface_addr)};
if ((!gateway.S_un.S_addr) and interface_name != ifname)
{
llarp::LogTrace(
"Win32 find gateway: Adding gateway (", interface_name, ") to list of gateways.");
huint32_t x{};
if (x.FromString(interface_name))
gateways.push_back(x);
}
});
return gateways;
}
void
AddDefaultRouteViaInterface(std::string ifname) override
{
DefaultRouteViaInterface(ifname, "ADD");
}
void
DelDefaultRouteViaInterface(std::string ifname) override
{
DefaultRouteViaInterface(ifname, "DELETE");
}
};
class Win32Platform : public Platform
{
Win32RouteManager _routeManager{};
public:
std::shared_ptr<NetworkInterface>
ObtainInterface(InterfaceInfo info) override
@ -443,6 +635,11 @@ namespace llarp::vpn
netif->Start();
return netif;
};
IRouteManager&
RouteManager() override
{
return _routeManager;
}
};
} // namespace llarp::vpn

@ -1,65 +0,0 @@
#!/usr/bin/env bash
# this shell script will be replaced by a proper program in the future (probably)
#
# from https://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
set -e
helpme=
default_mainnet=https://seed.lokinet.org/lokinet.signed
default_testnet=https://seed.lokinet.org/testnet.signed
default_dest="$HOME/.lokinet/bootstrap.signed"
if [ "$#" -gt 2 ]; then
helpme=y
fi
if [ -z "$1" ] || [ "$1" == "mainnet" ] || [ "$1" == "lokinet" ]; then
url="${default_mainnet}"
elif [ "$1" == "testnet" ]; then
url="${default_testnet}"
elif [[ "$1" = -* ]]; then
helpme=y
else
url="$1"
fi
if [[ "$2" = -* ]]; then
helpme=y
elif [ -n "$2" ]; then
dest="$2"
else
dest="$default_dest"
fi
if [ -n "$helpme" ]; then
echo "Usage: $0 [URL [DEST]] -- download bootstrap file from URL (default: lokinet) and save to DEST (default: $default_dest)."
echo "URL can be a full URL, or else 'lokinet' or 'testnet' to use the default lokinet/testnet seed URL. 'mainnet' can be used"
echo "as an alias for 'lokinet'."
exit 1
fi
destdir="$(dirname $dest)"
if [ ! -d "$destdir" ]; then
mkdir "$destdir"
fi
echo "downloading $url"
# use temp file to not overrwrite existing bootstrap file on fail
#tmp=mktemp
tmp=/tmp/bootstrap.tmp
# MacOS does not have wget without homebrew but does have curl
# Rick also had indicated most BSDs have curl too
if curl -fsSL "$url" >"$tmp"; then
mv "$tmp" "$dest"
echo -e "${GREEN}lokinet successfully bootstrapped${NC}"
else
echo -e "${RED}failed to download bootstrap from $url${NC}"
rm -f "$tmp"
exit 1
fi

@ -62,14 +62,7 @@ alternatively you can build from source, make sure you have cmake, libuv and xco
$ git clone --recursive https://github.com/oxen-io/lokinet
$ cd lokinet
$ mkdir build
$ cd build
$ cmake .. -DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON
$ make -j$(sysctl -n hw.ncpu)
install:
$ sudo make install
$ ./contrib/mac.sh -DCODESIGN_KEY='insert your key identity here' -DCODESIGN_TEAM_ID='team id here'
### Windows
@ -153,12 +146,17 @@ to configure as relay:
## Running on Linux
**DO NOT RUN AS ROOT**, run as normal user. This requires the binary to have the proper setcaps set by `make install` on the binary.
**DO NOT RUN AS ROOT**, run as normal user.
to run, after you create default config:
$ lokinet
This requires the binary to have the proper capabilities which is usually set by `make install` on the binary. If you have errors regarding permissions to open a new interface this can be resolved using:
$ sudo setcap cap_net_admin,cap_net_bind_service=+eip /usr/local/bin/lokinet
## Running on macOS/UNIX/BSD
**YOU HAVE TO RUN AS ROOT**, run using sudo. Elevated privileges are needed to create the virtual tunnel interface.

@ -43,9 +43,9 @@ TEST_CASE("SockAddr fromString", "[SockAddr]")
CHECK_THROWS_WITH(llarp::SockAddr("1.2.3"), "1.2.3 is not a valid IPv4 address");
CHECK_THROWS_WITH(llarp::SockAddr("1.2.3."), "1.2.3. is not a valid IPv4 address");
CHECK_THROWS_WITH(llarp::SockAddr("1.2.3."), "1.2.3. contains invalid numeric value");
CHECK_THROWS_WITH(llarp::SockAddr(".1.2.3"), ".1.2.3 is not a valid IPv4 address");
CHECK_THROWS_WITH(llarp::SockAddr(".1.2.3"), ".1.2.3 contains invalid numeric value");
CHECK_THROWS_WITH(llarp::SockAddr("1.2.3.4.5"), "1.2.3.4.5 is not a valid IPv4 address");

@ -91,7 +91,7 @@ TEST_CASE("neither true nor false string values", "[str][nottruefalse]") {
}
TEST_CASE("split strings with multiple matches", "[str]") {
auto splits = llarp::split("this is a test", ' ');
auto splits = llarp::split("this is a test", " ");
REQUIRE(splits.size() == 4);
REQUIRE(splits[0] == "this");
REQUIRE(splits[1] == "is");
@ -100,13 +100,13 @@ TEST_CASE("split strings with multiple matches", "[str]") {
}
TEST_CASE("split strings with single match", "[str]") {
auto splits = llarp::split("uno", ';');
auto splits = llarp::split("uno", ";");
REQUIRE(splits.size() == 1);
REQUIRE(splits[0] == "uno");
}
TEST_CASE("split strings with consecutive delimiters", "[str]") {
auto splits = llarp::split("a o e u", ' ');
TEST_CASE("split_any strings with consecutive delimiters", "[str]") {
auto splits = llarp::split_any("a o e u", " ");
REQUIRE(splits.size() == 4);
REQUIRE(splits[0] == "a");
REQUIRE(splits[1] == "o");
@ -115,14 +115,35 @@ TEST_CASE("split strings with consecutive delimiters", "[str]") {
}
TEST_CASE("split delimiter-only string", "[str]") {
auto splits = llarp::split(" ", ' ');
REQUIRE(splits.size() == 0);
{
auto splits = llarp::split(" ", " ");
REQUIRE(splits.size() == 5);
}
{
auto splits = llarp::split_any(" ", " ");
REQUIRE(splits.size() == 2);
}
{
auto splits = llarp::split(" ", " ", true);
REQUIRE(splits.size() == 0);
}
splits = llarp::split(" ", ' ');
REQUIRE(splits.size() == 0);
{
auto splits = llarp::split_any(" ", " ", true);
REQUIRE(splits.size() == 0);
}
}
TEST_CASE("split empty string", "[str]") {
auto splits = llarp::split("", ' ');
REQUIRE(splits.size() == 0);
{
auto splits = llarp::split("", " ");
REQUIRE(splits.size() == 1);
}
{
auto splits = llarp::split("", " ", true);
REQUIRE(splits.size() == 0);
}
}

Loading…
Cancel
Save