Merge pull request #1942 from jagerman/macos-sysex

macOS system extension
pull/1978/head
majestrate 2 years ago committed by GitHub
commit 13c71c3626
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -260,6 +260,7 @@ local mac_builder(name,
cmake_extra='',
extra_cmds=[],
jobs=6,
codesign='-DCODESIGN=OFF',
allow_fail=false) = {
kind: 'pipeline',
type: 'exec',
@ -276,7 +277,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
'./contrib/mac.sh ' + ci_mirror_opts,
'./contrib/mac.sh ' + ci_mirror_opts + ' ' + codesign,
] + extra_cmds,
},
],

3
.gitmodules vendored

@ -36,3 +36,6 @@
[submodule "external/oxen-logging"]
path = external/oxen-logging
url = https://github.com/oxen-io/oxen-logging.git
[submodule "gui"]
path = gui
url = https://github.com/oxen-io/lokinet-gui.git

@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.10) # bionic's cmake version
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Has to be set before `project()`, and ignored on non-macos:
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "macOS deployment target (Apple clang only)")
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "macOS deployment target (Apple clang only)")
option(BUILD_DAEMON "build lokinet daemon and associated utils" ON)
@ -32,7 +32,7 @@ project(lokinet
if(APPLE)
# Apple build number: must be incremented to submit a new build for the same lokinet version,
# should be reset to 0 when the lokinet version increments.
set(LOKINET_APPLE_BUILD 0)
set(LOKINET_APPLE_BUILD 1)
endif()
set(RELEASE_MOTTO "Gluten Free Edition" CACHE STRING "Release motto")
@ -60,6 +60,7 @@ option(WITH_TESTS "build unit tests" OFF)
option(WITH_HIVE "build simulation stubs" OFF)
option(BUILD_PACKAGE "builds extra components for making an installer (with 'make package')" OFF)
option(WITH_BOOTSTRAP "build lokinet-bootstrap tool" ${DEFAULT_WITH_BOOTSTRAP})
option(WITH_PEERSTATS "build with experimental peerstats db support" OFF)
include(cmake/enable_lto.cmake)
@ -82,7 +83,7 @@ endif()
if(BUILD_STATIC_DEPS AND STATIC_LINK)
message(STATUS "we are building static deps so we won't build shared libs")
set(BUILD_SHARED_LIBS OFF)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "")
endif()
include(CheckCXXSourceCompiles)
@ -105,6 +106,7 @@ endif()
include(cmake/solaris.cmake)
include(cmake/win32.cmake)
include(cmake/macos.cmake)
# No in-source building
include(MacroEnsureOutOfSourceBuild)
@ -299,6 +301,12 @@ endif()
add_subdirectory(docs)
include(cmake/gui.cmake)
if(APPLE)
macos_target_setup()
endif()
# uninstall target
if(NOT TARGET uninstall)
configure_file(
@ -311,6 +319,6 @@ if(NOT TARGET uninstall)
endif()
if(BUILD_PACKAGE AND NOT APPLE)
if(BUILD_PACKAGE)
include(cmake/installer.cmake)
endif()

@ -315,8 +315,11 @@ build_external(sodium CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} --
--enable-static --with-pic "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS}")
add_static_target(sodium sodium_external libsodium.a)
build_external(sqlite3)
add_static_target(sqlite3 sqlite3_external libsqlite3.a)
if(WITH_PEERSTATS_BACKEND)
build_external(sqlite3)
add_static_target(sqlite3 sqlite3_external libsqlite3.a)
endif()
if(ARCH_TRIPLET MATCHES mingw)
@ -328,7 +331,9 @@ endif()
if(CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw)
set(zmq_patch
PATCH_COMMAND ${PROJECT_SOURCE_DIR}/contrib/apply-patches.sh ${PROJECT_SOURCE_DIR}/contrib/patches/libzmq-mingw-wepoll.patch ${PROJECT_SOURCE_DIR}/contrib/patches/libzmq-mingw-closesocket.patch)
PATCH_COMMAND ${PROJECT_SOURCE_DIR}/contrib/apply-patches.sh
${PROJECT_SOURCE_DIR}/contrib/patches/libzmq-mingw-wepoll.patch
${PROJECT_SOURCE_DIR}/contrib/patches/libzmq-mingw-unistd.patch)
endif()
build_external(zmq
@ -351,6 +356,15 @@ set_target_properties(libzmq PROPERTIES
INTERFACE_LINK_LIBRARIES "${libzmq_link_libs}"
INTERFACE_COMPILE_DEFINITIONS "ZMQ_STATIC")
#
#
#
# Everything that follows is *only* for lokinet-bootstrap (i.e. if adding new deps put them *above*
# this).
#
#
#
if(NOT WITH_BOOTSTRAP)
return()
endif()
@ -420,7 +434,7 @@ foreach(curl_arch ${curl_arches})
list(APPEND curl_lib_outputs ${curl_prefix}/lib/libcurl.a)
endforeach()
message(STATUS "TARGETS: ${curl_lib_targets}")
if(IOS AND num_arches GREATER 1)
# We are building multiple architectures for different iOS devices, so we need to glue the

@ -44,6 +44,7 @@ if(filesystem_is_good EQUAL 1)
else()
# Probably broken AF macos
message(STATUS "std::filesystem is not available, apparently this compiler isn't C++17 compliant; falling back to ghc::filesystem")
set(GHC_FILESYSTEM_WITH_INSTALL OFF CACHE INTERNAL "")
add_subdirectory(external/ghc-filesystem)
target_link_libraries(filesystem INTERFACE ghc_filesystem)
target_compile_definitions(filesystem INTERFACE USE_GHC_FILESYSTEM)

@ -0,0 +1,64 @@
set(default_build_gui OFF)
set(default_gui_target pack)
if(APPLE)
set(default_build_gui ON)
set(default_gui_target macos:raw)
elseif(WIN32)
set(default_build_gui ON)
set(default_gui_target win32)
endif()
option(BUILD_GUI "build electron gui from 'gui' submodule source" ${default_build_gui})
set(GUI_YARN_TARGET "${default_gui_target}" CACHE STRING "yarn target for building the GUI")
set(GUI_YARN_EXTRA_OPTS "" CACHE STRING "extra options to pass into the yarn build command")
if (BUILD_GUI)
message(STATUS "Building lokinet-gui")
find_program(YARN NAMES yarn yarnpkg REQUIRED)
message(STATUS "Building lokinet-gui with yarn ${YARN}, target ${GUI_YARN_TARGET}")
set(wine_env)
if(WIN32)
set(wine_env WINEDEBUG=-all "WINEPREFIX=${PROJECT_BINARY_DIR}/wineprefix")
endif()
add_custom_target(lokinet-gui
COMMAND ${YARN} install --frozen-lockfile &&
${wine_env} ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET}
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui")
if(APPLE)
add_custom_target(assemble_gui ALL
DEPENDS assemble lokinet-gui
COMMAND mkdir "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Helpers"
COMMAND cp -a "${PROJECT_SOURCE_DIR}/gui/release/mac/Lokinet-GUI.app" "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Helpers/"
COMMAND mkdir -p "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Resources/en.lproj"
COMMAND cp "${PROJECT_SOURCE_DIR}/contrib/macos/InfoPlist.strings" "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Resources/en.lproj/"
COMMAND cp "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Resources/icon.icns" "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Helpers/Lokinet-GUI.app/Contents/Resources/icon.icns"
COMMAND cp "${PROJECT_SOURCE_DIR}/contrib/macos/InfoPlist.strings" "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Helpers/Lokinet-GUI.app/Contents/Resources/en.lproj/"
COMMAND /usr/libexec/PlistBuddy
-c "Delete :CFBundleDisplayName"
-c "Add :LSHasLocalizedDisplayName bool true"
-c "Add :CFBundleDevelopmentRegion string en"
-c "Set :CFBundleShortVersionString ${lokinet_VERSION}"
-c "Set :CFBundleVersion ${lokinet_VERSION}.${LOKINET_APPLE_BUILD}"
"${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Helpers/Lokinet-GUI.app/Contents/Info.plist"
)
elseif(WIN32)
file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gui")
add_custom_target(copy_gui ALL
DEPENDS lokinet lokinet-gui
# FIXME: we really shouldn't be building inside the source directory but this is npm...
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${PROJECT_SOURCE_DIR}/gui/release/Lokinet-GUI_portable.exe"
"${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe"
)
else()
message(FATAL_ERROR "Building/bundling the GUI from this repository is not supported on this platform")
endif()
else()
message(STATUS "Not building lokinet-gui")
endif()

@ -5,6 +5,8 @@ set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
if(WIN32)
include(cmake/win32_installer_deps.cmake)
elseif(APPLE)
set(CPACK_GENERATOR DragNDrop;ZIP)
endif()

@ -0,0 +1,176 @@
if(NOT APPLE)
return()
endif()
option(MACOS_SYSTEM_EXTENSION
"Build the network extension as a system extension rather than a plugin. This must be ON for non-app store release builds, and must be OFF for dev builds and Mac App Store distribution builds"
OFF)
option(CODESIGN "codesign the resulting app and extension" ON)
set(CODESIGN_ID "" CACHE STRING "codesign the macos app using this key identity; if empty we'll try to guess")
set(default_profile_type "dev")
if(MACOS_SYSTEM_EXTENSION)
set(default_profile_type "release")
endif()
set(CODESIGN_PROFILE "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.${default_profile_type}.provisionprofile" CACHE FILEPATH
"Path to a .provisionprofile to use for the main app")
if(CODESIGN AND NOT CODESIGN_ID)
if(MACOS_SYSTEM_EXTENSION)
set(codesign_cert_pattern "Developer ID Application")
else()
set(codesign_cert_pattern "Apple Development")
endif()
execute_process(
COMMAND security find-identity -v -p codesigning
COMMAND sed -n "s/^ *[0-9][0-9]*) *\\([A-F0-9]\\{40\\}\\) *\"\\(${codesign_cert_pattern}.*\\)\"\$/\\1 \\2/p"
RESULT_VARIABLE find_id_exit_code
OUTPUT_VARIABLE find_id_output)
if(NOT find_id_exit_code EQUAL 0)
message(FATAL_ERROR "Finding signing identities with security find-identity failed; try specifying an id using -DCODESIGN_ID=...")
endif()
string(REGEX MATCHALL "(^|\n)[0-9A-F]+" find_id_sign_id "${find_id_output}")
if(NOT find_id_sign_id)
message(FATAL_ERROR "Did not find any \"${codesign_cert_pattern}\" identity; try specifying an id using -DCODESIGN_ID=...")
endif()
if (find_id_sign_id MATCHES ";")
message(FATAL_ERROR "Found multiple \"${codesign_cert_pattern}\" identities:\n${find_id_output}\nSpecify an identify using -DCODESIGN_ID=...")
endif()
set(CODESIGN_ID "${find_id_sign_id}" CACHE STRING "" FORCE)
endif()
if(CODESIGN)
message(STATUS "Codesigning using ${CODESIGN_ID}")
if (NOT MACOS_NOTARIZE_USER AND NOT MACOS_NOTARIZE_PASS AND NOT MACOS_NOTARIZE_ASC AND EXISTS "$ENV{HOME}/.notarization.cmake")
message(STATUS "Loading notarization info from ~/.notarization.cmake")
include("$ENV{HOME}/.notarization.cmake")
endif()
if (MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC)
message(STATUS "Enabling notarization with account ${MACOS_NOTARIZE_ASC}/${MACOS_NOTARIZE_USER}")
else()
message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization will fail; see contrib/macos/README.txt")
endif()
else()
message(WARNING "Codesigning disabled; the resulting build will not run on most macOS systems")
endif()
if(NOT CODESIGN_PROFILE)
message(WARNING "Missing a CODESIGN_PROFILE provisioning profile: Apple will most likely log an uninformative error message to the system log and then kill harmless kittens if you try to run the result")
endif()
if(NOT EXISTS "${CODESIGN_PROFILE}")
message(FATAL_ERROR "Provisioning profile ${CODESIGN_PROFILE} does not exist; fix your -DCODESIGN_PROFILE path")
endif()
message(STATUS "Using ${CODESIGN_PROFILE} provisioning profile")
if(MACOS_SYSTEM_EXTENSION)
set(lokinet_ext_dir Contents/Library/SystemExtensions)
else()
set(lokinet_ext_dir Contents/PlugIns)
endif()
if(CODESIGN)
if(MACOS_SYSTEM_EXTENSION)
set(LOKINET_ENTITLEMENTS_TYPE sysext)
set(notarize_py_is_sysext True)
else()
set(LOKINET_ENTITLEMENTS_TYPE plugin)
set(notarize_py_is_sysext False)
endif()
configure_file(
"${PROJECT_SOURCE_DIR}/contrib/macos/sign.sh.in"
"${PROJECT_BINARY_DIR}/sign.sh"
@ONLY)
add_custom_target(
sign
DEPENDS "${PROJECT_BINARY_DIR}/sign.sh"
COMMAND "${PROJECT_BINARY_DIR}/sign.sh"
)
if(MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC)
configure_file(
"${PROJECT_SOURCE_DIR}/contrib/macos/notarize.py.in"
"${PROJECT_BINARY_DIR}/notarize.py"
@ONLY)
add_custom_target(
notarize
DEPENDS "${PROJECT_BINARY_DIR}/notarize.py" sign
COMMAND "${PROJECT_BINARY_DIR}/notarize.py"
)
else()
message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization disabled")
endif()
else()
add_custom_target(sign COMMAND "true")
add_custom_target(notarize DEPENDS sign COMMAND "true")
endif()
# Called later to set things up, after the main lokinet targets are set up
function(macos_target_setup)
if(MACOS_SYSTEM_EXTENSION)
target_compile_definitions(lokinet PRIVATE MACOS_SYSTEM_EXTENSION)
endif()
set_target_properties(lokinet
PROPERTIES
OUTPUT_NAME Lokinet
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_STRING "Lokinet IP Packet Onion Router"
MACOSX_BUNDLE_BUNDLE_NAME "Lokinet"
MACOSX_BUNDLE_BUNDLE_VERSION "${lokinet_VERSION}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${lokinet_VERSION}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${lokinet_VERSION_MAJOR}.${lokinet_VERSION_MINOR}"
MACOSX_BUNDLE_GUI_IDENTIFIER "org.lokinet"
MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.Info.plist.in"
MACOSX_BUNDLE_COPYRIGHT "© 2022, The Oxen Project"
)
add_custom_target(copy_bootstrap
DEPENDS lokinet-extension
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed
$<TARGET_BUNDLE_DIR:lokinet-extension>/Contents/Resources/bootstrap.signed
)
set(mac_icon ${PROJECT_BINARY_DIR}/lokinet.icns)
add_custom_command(OUTPUT ${mac_icon}
COMMAND ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet-mac.svg ${mac_icon}
DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh)
add_custom_target(icon DEPENDS ${mac_icon})
add_dependencies(lokinet lokinet-extension icon)
if(CODESIGN_PROFILE)
add_custom_target(copy_prov_prof
DEPENDS lokinet
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CODESIGN_PROFILE}
$<TARGET_BUNDLE_DIR:lokinet>/Contents/embedded.provisionprofile
)
else()
add_custom_target(copy_prov_prof COMMAND true)
endif()
add_custom_target(assemble ALL
DEPENDS lokinet lokinet-extension icon copy_prov_prof copy_bootstrap
COMMAND rm -rf "${PROJECT_BINARY_DIR}/Lokinet.app"
COMMAND cp -a $<TARGET_BUNDLE_DIR:lokinet> "${PROJECT_BINARY_DIR}/Lokinet.app"
COMMAND mkdir -p "${PROJECT_BINARY_DIR}/Lokinet.app/${lokinet_ext_dir}"
COMMAND cp -a $<TARGET_BUNDLE_DIR:lokinet-extension> "${PROJECT_BINARY_DIR}/Lokinet.app/${lokinet_ext_dir}/"
COMMAND mkdir -p "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Resources"
COMMAND cp -a "${mac_icon}" "${PROJECT_BINARY_DIR}/Lokinet.app/Contents/Resources/icon.icns"
)
if(CODESIGN)
add_dependencies(sign assemble)
endif()
endfunction()

@ -1,8 +1,3 @@
if(NOT GUI_ZIP_URL)
set(GUI_ZIP_URL "https://oxen.rocks/oxen-io/lokinet-gui/dev/lokinet-windows-x64-20220331T180338Z-569f90ad8.zip")
set(GUI_ZIP_HASH_OPTS EXPECTED_HASH SHA256=316f10489f5907bfa9c74b21f8ef2fdd7b7c7e6a0f5bcedaed2ee5f4004eab52)
endif()
set(TUNTAP_URL "https://build.openvpn.net/downloads/releases/latest/tap-windows-latest-stable.exe")
set(TUNTAP_EXE "${CMAKE_BINARY_DIR}/tuntap-install.exe")
set(BOOTSTRAP_FILE "${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed")
@ -11,13 +6,25 @@ file(DOWNLOAD
${TUNTAP_URL}
${TUNTAP_EXE})
file(DOWNLOAD
if(NOT BUILD_GUI)
if(NOT GUI_ZIP_URL)
set(GUI_ZIP_URL "https://oxen.rocks/oxen-io/lokinet-gui/dev/lokinet-windows-x64-20220331T180338Z-569f90ad8.zip")
set(GUI_ZIP_HASH_OPTS EXPECTED_HASH SHA256=316f10489f5907bfa9c74b21f8ef2fdd7b7c7e6a0f5bcedaed2ee5f4004eab52)
endif()
file(DOWNLOAD
${GUI_ZIP_URL}
${CMAKE_BINARY_DIR}/lokinet-gui.zip
${GUI_ZIP_HASH_OPTS})
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_BINARY_DIR}/lokinet-gui.zip
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
# We expect the produced .zip file above to extract to ./gui/lokinet-gui.exe
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_BINARY_DIR}/lokinet-gui.zip
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
if(NOT EXISTS ${CMAKE_BINARY_DIR}/gui/lokinet-gui.exe)
message(FATAL_ERROR "Downloaded gui archive from ${GUI_ZIP_URL} does not contain gui/lokinet-gui.exe!")
endif()
endif()
install(DIRECTORY ${CMAKE_BINARY_DIR}/gui DESTINATION share COMPONENT gui)
install(PROGRAMS ${TUNTAP_EXE} DESTINATION bin COMPONENT tuntap)

@ -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 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M897.5,1024H126.5C56.6,1024,0,967.4,0,897.5V126.5C0,56.6,56.6,0,126.5,0h771.1C967.4,0,1024,56.6,1024,126.5
v771.1C1024,967.4,967.4,1024,897.5,1024z"/>
</g>
<g>
<polygon points="585.2,658.9 512,732.1 438.8,658.9 365.1,732.1 512,879 658.9,732.1 "/>
<polygon points="658.9,585.2 732.1,512 658.9,438.8 732.1,365.1 879,512 732.1,658.9 "/>
<polygon points="365.1,438.8 291.9,512 365.1,585.2 291.9,658.9 145,512 291.9,365.1 "/>
<polygon points="438.8,365.1 512,291.9 585.2,365.1 658.9,291.9 512,145 365.1,291.9 "/>
<rect x="533.4" y="533.3" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -242.3375 585.3179)" width="103.9" height="103.9"/>
<rect x="386.7" y="386.9" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -181.837 438.6521)" width="103.9" height="103.9"/>
<rect x="533.2" y="386.7" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -138.7545 542.2352)" width="103.9" height="103.9"/>
<rect x="386.9" y="533.5" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -285.4199 481.7348)" width="103.9" height="103.9"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -18,22 +18,16 @@ 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 \
-DWITH_BOOTSTRAP=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
ninja -j1
echo -e "Build complete, your app is here:\n"
ls -lad $(pwd)/daemon/lokinet.app
ls -lad $(pwd)/Lokinet.app
echo ""

@ -1,24 +0,0 @@
<?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>

Binary file not shown.

@ -1,40 +0,0 @@
<?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>

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

@ -0,0 +1,64 @@
<?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 Network Extension</string>
<key>CFBundleExecutable</key>
<string>org.lokinet.network-extension</string>
<key>CFBundleIdentifier</key>
<string>org.lokinet.network-extension</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>SYSX</string>
<key>CFBundleName</key>
<string>org.lokinet.network-extension</string>
<key>CFBundleVersion</key>
<string>@lokinet_VERSION@.@LOKINET_APPLE_BUILD@</string>
<key>CFBundleShortVersionString</key>
<string>@lokinet_VERSION@</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2022 The Oxen Project, licensed under GPLv3-or-later</string>
<key>NSSystemExtensionUsageDescription</key>
<string>Provides Lokinet Network connectivity.</string>
<key>NetworkExtension</key>
<dict>
<key>NEMachServiceName</key>
<string>SUQ8J2PCT7.org.lokinet.network-extension</string>
<key>NEProviderClasses</key>
<dict>
<key>com.apple.networkextension.packet-tunnel</key>
<string>LLARPPacketTunnel</string>
<key>com.apple.networkextension.dns-proxy</key>
<string>LLARPDNSProxy</string>
</dict>
</dict>
</dict>
</plist>

@ -3,11 +3,12 @@
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>SUQ8J2PCT7.com.loki-project.lokinet.network-extension</string>
<string>SUQ8J2PCT7.org.lokinet.network-extension</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
<string>dns-proxy</string>
</array>
<key>com.apple.developer.team-identifier</key>
@ -16,9 +17,6 @@
<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/>

@ -0,0 +1,32 @@
<?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.org.lokinet.network-extension</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider-systemextension</string>
<string>dns-proxy-systemextension</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>SUQ8J2PCT7</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>SUQ8J2PCT7.org.lokinet</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

@ -0,0 +1,45 @@
<?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>CFBundleExecutable</key>
<string>Lokinet</string>
<key>CFBundleIdentifier</key>
<string>org.lokinet</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Lokinet</string>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>@lokinet_VERSION@</string>
<key>CFBundleVersion</key>
<string>@lokinet_VERSION@.@LOKINET_APPLE_BUILD@</string>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2022 The Oxen Project, licensed under GPLv3-or-later</string>
<key>LSUIElement</key>
<true/>
<key>LSHasLocalizedDisplayName</key>
<true/>
</dict>
</plist>

@ -3,13 +3,13 @@
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>SUQ8J2PCT7.com.loki-project.lokinet</string>
<string>SUQ8J2PCT7.org.lokinet</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
<string>dns-proxy</string>
<string>dns-settings</string>
<string>dns-proxy</string>
<string>dns-settings</string>
</array>
<key>com.apple.developer.team-identifier</key>
@ -18,13 +18,10 @@
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.get-task-allow</key>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<key>com.apple.security.network.server</key>
<true/>
</dict>

@ -0,0 +1,36 @@
<?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.org.lokinet</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider-systemextension</string>
<string>dns-proxy-systemextension</string>
<string>dns-settings</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>SUQ8J2PCT7</string>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>SUQ8J2PCT7.org.lokinet</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

@ -14,7 +14,7 @@ done
mv "${outdir}/icon_1024x1024.png" "${outdir}/icon_512x512@2x.png"
for size in 16 32 128 256; do
double=$((size * 2))
cp "${outdir}/icon_${double}x${double}.png" "${outdir}/icon_${size}x${size}@2x.png"
ln -f "${outdir}/icon_${double}x${double}.png" "${outdir}/icon_${size}x${size}@2x.png"
done
iconutil -c icns "${outdir}"

@ -1,26 +0,0 @@
<?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>Label</key>
<string>network.loki.lokinet.daemon</string>
<key>ProgramArguments</key>
<array>
<string>/var/lib/lokinet/lokinet_macos_daemon_script.sh</string>
</array>
<!-- Keep Lokinet alive unless magic file exists -->
<key>KeepAlive</key>
<dict>
<key>PathState</key>
<dict>
<key>/var/lib/lokinet/suspend-launchd-service</key>
<false/>
</dict>
</dict>
<key>StandardOutPath</key>
<string>/var/log/lokinet.log</string>
</dict>
</plist>

@ -4,21 +4,47 @@ import sys
import plistlib
import subprocess
import time
import os
import os.path
def bold_red(x):
return "\x1b[31;1m" + x + "\x1b[0m"
if not @notarize_py_is_sysext@:
print(bold_red("\nUnable to notarize: this lokinet is not built as a system extension\n"), file=sys.stderr)
sys.exit(1)
if not all(("@MACOS_NOTARIZE_USER@", "@MACOS_NOTARIZE_PASS@", "@MACOS_NOTARIZE_ASC@")):
print(bold_red("\nUnable to notarize: one or more required notarization variable not set; see contrib/macos/README.txt\n") +
" Called with -DMACOS_NOTARIZE_USER=@MACOS_NOTARIZE_USER@\n"
" -DMACOS_NOTARIZE_PASS=@MACOS_NOTARIZE_PASS@\n"
" -DMACOS_NOTARIZE_ASC=@MACOS_NOTARIZE_ASC@\n",
file=sys.stderr)
sys.exit(1)
os.chdir("@PROJECT_BINARY_DIR@")
app = "Lokinet.app"
zipfile = f"{app}.notarize.zip"
print(f"Creating {zipfile} from {app}")
if os.path.exists(zipfile):
os.remove(zipfile)
subprocess.run(['ditto', '-v', '-c', '-k', '--sequesterRsrc', '--keepParent', app, zipfile])
pkg = "lokinet-@PROJECT_VERSION@-Darwin.pkg"
userpass = ('--username', "@MACOS_NOTARIZE_USER@", '--password', "@MACOS_NOTARIZE_PASS@")
print("Submitting {} for notarization; this may take a minute...".format(pkg))
print("Submitting {} for notarization; this may take a minute...".format(zipfile))
started = time.time()
result = subprocess.run([
command = [
'xcrun', 'altool',
'--notarize-app',
'--primary-bundle-id', 'org.lokinet.lokinet.pkg.@PROJECT_VERSION@',
'--primary-bundle-id', 'org.lokinet.@PROJECT_VERSION@',
*userpass,
'--asc-provider', "@MACOS_NOTARIZE_ASC@",
'--file', pkg,
'--file', zipfile,
'--output-format', 'xml'
], stdout=subprocess.PIPE)
]
print(command)
result = subprocess.run(command, stdout=subprocess.PIPE)
data = plistlib.loads(result.stdout)
if 'success-message' not in data or 'notarization-upload' not in data or 'RequestUUID' not in data['notarization-upload']:
@ -42,24 +68,27 @@ while not done:
'--notarization-info', uuid,
*userpass,
'--output-format', 'xml'
], stdout=subprocess.PIPE)
result.check_returncode()
data = plistlib.loads(result.stdout)
if 'notarization-info' not in data or 'Status' not in data['notarization-info']:
status = 'Request failed'
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode == 1 and b'Gateway Timeout' in result.stderr:
status = "Apple's servers are trash (aka Gateway Timeout)"
else:
status = data['notarization-info']['Status Message'] if 'Status Message' in data['notarization-info'] else ''
st = data['notarization-info']['Status']
if st == 'success':
success = True
done = True
elif st == 'invalid':
done = True
elif st == 'in progress' and len(status) == 0:
status = 'Notarization in progress'
if done and 'LogFileURL' in data['notarization-info']:
status += '\n\nlog file: {}'.format(data['notarization-info']['LogFileURL'])
result.check_returncode()
data = plistlib.loads(result.stdout)
if 'notarization-info' not in data or 'Status' not in data['notarization-info']:
status = 'Request failed'
else:
status = data['notarization-info']['Status Message'] if 'Status Message' in data['notarization-info'] else ''
st = data['notarization-info']['Status']
if st == 'success':
success = True
done = True
elif st == 'invalid':
done = True
elif st == 'in progress' and len(status) == 0:
status = 'Notarization in progress'
if done and 'LogFileURL' in data['notarization-info']:
status += '\n\nlog file: {}'.format(data['notarization-info']['LogFileURL'])
elapsed = time.time() - started_waiting
mins, secs = int(elapsed // 60), int(elapsed % 60)
@ -70,7 +99,12 @@ print("\n")
if not success:
sys.exit(42)
print("Stapling {}".format(pkg))
result = subprocess.run(['xcrun', 'stapler', 'staple', pkg])
if os.path.exists(zipfile):
os.remove(zipfile)
print("Stapling {}...".format(app), end='')
result = subprocess.run(['xcrun', 'stapler', 'staple', app])
result.check_returncode()
print(" success.\n")

@ -1,10 +1,72 @@
#!/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"
if [ "@CODESIGN@" != "ON" ]; then
echo "Cannot codesign: this build was not configured with codesigning" >&2
exit 1
fi
signit() {
target="$1"
entitlements="$2"
echo -e "\n\e[33;1mSigning ${target/*\/Lokinet.app/Lokinet.app}...\e[0m" >&2
codesign \
--verbose=4 \
--force \
-s "@CODESIGN_ID@" \
--entitlements "$entitlements" \
--strict \
--timestamp \
--options=runtime \
"$target"
}
gui_entitlements="@PROJECT_SOURCE_DIR@/gui/node_modules/app-builder-lib/templates/entitlements.mac.plist"
ext_entitlements="@PROJECT_SOURCE_DIR@/contrib/macos/lokinet-extension.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist"
app_entitlements="@PROJECT_SOURCE_DIR@/contrib/macos/lokinet.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist"
SIGN_TARGET="@PROJECT_BINARY_DIR@/Lokinet.app"
for ext in systemextension appex; do
netext="$SIGN_TARGET/@lokinet_ext_dir@/org.lokinet.network-extension.$ext"
if [ -e "$netext" ]; then
signit "$netext" "$ext_entitlements"
fi
done
if [ "@BUILD_GUI@" == "ON" ]; then
gui_app="$SIGN_TARGET"/Contents/Helpers/Lokinet-GUI.app
gui_sign_targets=()
for bundle in \
"$gui_app"/Contents/Frameworks/*.framework \
"$gui_app"/Contents/Frameworks/*.app
do
if [ -d "$bundle/Libraries" ]; then
gui_sign_targets+=("$bundle"/Libraries/*.dylib)
fi
if [ -d "$bundle/Helpers" ]; then
gui_sign_targets+=("$bundle"/Helpers/*)
fi
if [ -d "$bundle/Resources" ]; then
for f in "$bundle/Resources"/*; do
if [[ -f "$f" && -x "$f" && "$(file -b "$f")" == Mach-O* ]]; then
gui_sign_targets+=("$f")
fi
done
fi
gui_sign_targets+=("$bundle")
done
gui_sign_targets+=("$gui_app")
for target in "${gui_sign_targets[@]}"; do
signit "$target" "$gui_entitlements"
done
signit "$SIGN_TARGET"/Contents/MacOS/Lokinet "$app_entitlements"
fi
signit "$SIGN_TARGET" "$app_entitlements"

@ -1,12 +0,0 @@
diff --git a/tests/testutil.hpp b/tests/testutil.hpp
index c6f5e4de..6a1c8bb8 100644
--- a/tests/testutil.hpp
+++ b/tests/testutil.hpp
@@ -102,7 +102,6 @@ const uint8_t zmtp_ready_sub[27] = {
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdexcept>
-#define close closesocket
typedef int socket_size_t;
inline const char *as_setsockopt_opt_t (const void *opt)
{

@ -0,0 +1,14 @@
diff --git a/tests/testutil.hpp b/tests/testutil.hpp
index c6f5e4de78..09b9fa77e5 100644
--- a/tests/testutil.hpp
+++ b/tests/testutil.hpp
@@ -41,6 +41,9 @@
// For AF_INET and IPPROTO_TCP
#if defined _WIN32
#include "../src/windows.hpp"
+#if defined(__MINGW32__)
+#include <unistd.h>
+#endif
#else
#include <arpa/inet.h>
#include <unistd.h>

@ -14,6 +14,7 @@ cmake \
-DCMAKE_EXE_LINKER_FLAGS=-fstack-protector \
-DCMAKE_CXX_FLAGS=-fdiagnostics-color=always \
-DCMAKE_TOOLCHAIN_FILE="$root/contrib/cross/mingw64.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_STATIC_DEPS=ON \
-DBUILD_PACKAGE=ON \
-DBUILD_SHARED_LIBS=OFF \
@ -28,6 +29,5 @@ cmake \
-DFORCE_FMT_SUBMODULE=ON \
-DFORCE_SPDLOG_SUBMODULE=ON \
-DFORCE_NLOHMANN_SUBMODULE=ON \
-DSUBMODULE_CHECK=OFF \
-DWITH_LTO=OFF \
$@

@ -10,5 +10,5 @@ set +x
root="$(readlink -f $(dirname $0)/../)"
cd "$root"
./contrib/windows-configure.sh . build-windows $@
./contrib/windows-configure.sh . build-windows "$@"
make package -j${JOBS:-$(nproc)} -C build-windows

@ -1,14 +1,17 @@
set(exetargets lokinet)
add_executable(lokinet-vpn lokinet-vpn.cpp)
if(APPLE)
add_executable(lokinet lokinet.swift)
enable_lto(lokinet)
else()
add_executable(lokinet lokinet.cpp)
enable_lto(lokinet lokinet-vpn)
endif()
add_executable(lokinet-vpn lokinet-vpn.cpp)
enable_lto(lokinet lokinet-vpn)
list(APPEND exetargets lokinet-vpn)
if(WITH_BOOTSTRAP)
add_executable(lokinet-bootstrap lokinet-bootstrap.cpp)
list(APPEND exetargets lokinet-bootstrap)
enable_lto(lokinet-bootstrap)
endif()
@ -42,11 +45,6 @@ if(WITH_BOOTSTRAP)
endif()
endif()
set(exetargets lokinet lokinet-vpn)
if(WITH_BOOTSTRAP)
list(APPEND exetargets lokinet-bootstrap)
endif()
foreach(exe ${exetargets})
if(WIN32 AND NOT MSVC_VERSION)
target_sources(${exe} PRIVATE ${CMAKE_BINARY_DIR}/${exe}.rc)
@ -57,70 +55,18 @@ foreach(exe ${exetargets})
endif()
target_link_libraries(${exe} PUBLIC liblokinet)
target_include_directories(${exe} PUBLIC "${PROJECT_SOURCE_DIR}")
target_compile_definitions(${exe} PRIVATE -DVERSIONTAG=${GIT_VERSION_REAL})
if(should_install)
if(APPLE)
install(TARGETS ${exe} BUNDLE DESTINATION "${PROJECT_BINARY_DIR}" COMPONENT lokinet)
install(TARGETS ${exe}
BUNDLE DESTINATION "${PROJECT_BINARY_DIR}"
RUNTIME DESTINATION "."
COMPONENT lokinet)
else()
install(TARGETS ${exe} RUNTIME DESTINATION bin COMPONENT lokinet)
endif()
endif()
endforeach()
if(APPLE)
set(CODESIGN_APP "" CACHE STRING "codesign the macos app using this key identity")
set(CODESIGN_APPEX "${CODESIGN_APP}" CACHE STRING "codesign the internal extension using this key identity; defaults to CODESIGN_APP if empty")
set(mac_icon ${CMAKE_CURRENT_BINARY_DIR}/lokinet.icns)
add_custom_command(OUTPUT ${mac_icon}
COMMAND ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${mac_icon}
DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh)
add_custom_target(icons DEPENDS ${mac_icon})
add_dependencies(lokinet icons lokinet-extension)
add_custom_command(TARGET lokinet
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed
$<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()

@ -1,34 +1,83 @@
import AppKit
import Foundation
import NetworkExtension
import SystemExtensions
let app = NSApplication.shared
let START = "--start"
let STOP = "--stop"
let HELP_STRING = "usage: lokinet {--start|--stop}"
class LokinetMain: NSObject, NSApplicationDelegate {
var vpnManager = NETunnelProviderManager()
let lokinetComponent = "com.loki-project.lokinet.network-extension"
var mode = START
let netextBundleId = "org.lokinet.network-extension"
func applicationDidFinishLaunching(_: Notification) {
setupVPNJizz()
if mode == START {
startNetworkExtension()
} else if mode == STOP {
tearDownVPNTunnel()
} else {
result(msg: HELP_STRING)
}
}
func bail() {
app.terminate(self)
}
func setupVPNJizz() {
NSLog("Starting up lokinet")
func result(msg: String) {
NSLog(msg)
// TODO: does lokinet continue after this?
bail()
}
func tearDownVPNTunnel() {
NSLog("Stopping Lokinet")
NETunnelProviderManager.loadAllFromPreferences { [self] (savedManagers: [NETunnelProviderManager]?, error: Error?) in
if let error = error {
NSLog(error.localizedDescription)
bail()
self.result(msg: error.localizedDescription)
return
}
if let savedManagers = savedManagers {
for manager in savedManagers {
if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.lokinetComponent {
NSLog("%@", manager)
if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.netextBundleId {
manager.connection.stopVPNTunnel()
self.result(msg: "Lokinet Down")
}
}
}
self.result(msg: "Lokinet is not up")
}
}
func startNetworkExtension() {
#if MACOS_SYSTEM_EXTENSION
NSLog("Loading Lokinet network extension")
// Start by activating the system extension
let activationRequest = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: netextBundleId, queue: .main)
activationRequest.delegate = self
OSSystemExtensionManager.shared.submitRequest(activationRequest)
#else
setupVPNTunnel()
#endif
}
func setupVPNTunnel() {
NSLog("Starting up Lokinet tunnel")
NETunnelProviderManager.loadAllFromPreferences { [self] (savedManagers: [NETunnelProviderManager]?, error: Error?) in
if let error = error {
self.result(msg: error.localizedDescription)
return
}
if let savedManagers = savedManagers {
for manager in savedManagers {
if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.netextBundleId {
NSLog("Found saved VPN Manager")
self.vpnManager = manager
}
@ -37,7 +86,7 @@ class LokinetMain: NSObject, NSApplicationDelegate {
let providerProtocol = NETunnelProviderProtocol()
providerProtocol.serverAddress = "loki.loki" // Needs to be set to some non-null dummy value
providerProtocol.username = "anonymous"
providerProtocol.providerBundleIdentifier = self.lokinetComponent
providerProtocol.providerBundleIdentifier = self.netextBundleId
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.
@ -46,28 +95,24 @@ class LokinetMain: NSObject, NSApplicationDelegate {
self.vpnManager.isEnabled = true
// self.vpnManager.isOnDemandEnabled = true
self.vpnManager.localizedDescription = "lokinet"
self.vpnManager.saveToPreferences(completionHandler: { error -> Void in
self.vpnManager.saveToPreferences(completionHandler: { [self] error -> Void in
if error != nil {
NSLog("Error saving to preferences")
NSLog(error!.localizedDescription)
bail()
self.result(msg: error!.localizedDescription)
} else {
self.vpnManager.loadFromPreferences(completionHandler: { error in
if error != nil {
NSLog("Error loading from preferences")
NSLog(error!.localizedDescription)
bail()
self.result(msg: error!.localizedDescription)
} else {
do {
NSLog("Trying to start")
self.initializeConnectionObserver()
try self.vpnManager.connection.startVPNTunnel()
} catch let error as NSError {
NSLog(error.localizedDescription)
bail()
self.result(msg: error.localizedDescription)
} catch {
NSLog("There was a fatal error")
bail()
self.result(msg: "There was a fatal error")
}
}
})
@ -77,11 +122,11 @@ class LokinetMain: NSObject, NSApplicationDelegate {
}
func initializeConnectionObserver() {
NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: vpnManager.connection, queue: OperationQueue.main) { _ -> Void in
NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: vpnManager.connection, queue: OperationQueue.main) { [self] _ -> Void in
if self.vpnManager.connection.status == .invalid {
NSLog("VPN configuration is invalid")
self.result(msg: "VPN configuration is invalid")
} else if self.vpnManager.connection.status == .disconnected {
NSLog("VPN is disconnected.")
self.result(msg: "VPN is disconnected.")
} else if self.vpnManager.connection.status == .connecting {
NSLog("VPN is connecting...")
} else if self.vpnManager.connection.status == .reasserting {
@ -89,12 +134,102 @@ class LokinetMain: NSObject, NSApplicationDelegate {
} else if self.vpnManager.connection.status == .disconnecting {
NSLog("VPN is disconnecting...")
} else if self.vpnManager.connection.status == .connected {
NSLog("VPN Connected")
self.result(msg: "VPN Connected")
}
}
}
}
let delegate = LokinetMain()
app.delegate = delegate
app.run()
#if MACOS_SYSTEM_EXTENSION
extension LokinetMain: OSSystemExtensionRequestDelegate {
func request(_: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) {
guard result == .completed else {
NSLog("Unexpected result %d for system extension request", result.rawValue)
return
}
NSLog("Lokinet system extension loaded")
setupVPNTunnel()
}
func request(_: OSSystemExtensionRequest, didFailWithError error: Error) {
NSLog("System extension request failed: %@", error.localizedDescription)
self.bail()
}
func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) {
NSLog("Extension %@ requires user approval", request.identifier)
}
func request(_ request: OSSystemExtensionRequest,
actionForReplacingExtension existing: OSSystemExtensionProperties,
withExtension extension: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction
{
NSLog("Replacing extension %@ version %@ with version %@", request.identifier, existing.bundleShortVersion, `extension`.bundleShortVersion)
return .replace
}
}
#endif
let args = CommandLine.arguments
// If we are invoked with no arguments then exec the gui. This is dumb, but there doesn't seem to
// be a nicer way to do this on Apple's half-baked platform because:
// - we have three "bundles" we need to manage: the GUI app, the system extension, and the Lokinet
// app (this file) which loads the system extension.
// - if we embed the system extension directly inside the GUI then it fails to launch because the
// electron GUI's requirements (needed for JIT) conflict with the ability to load a system
// extensions.
// - if we embed Lokinet.app inside Lokinet-GUI.app and then the system extension inside Lokinet.app
// then it works, but macos loses track of the system extension and doesn't remove it when you
// remove the application. (It breaks your system, leaving an impossible-to-remove system
// extension, in just the same way it breaks if you don't use Finder to remove the Application.
// Apple used to say (around 2 years ago as of writing) that they would fix this situation "soon",
// but hasn't, and has stopped saying anything about it.)
// - if we try to use multiple executables (one to launch the system extension, one simple shell
// script to execs the embedded GUI app) inside the Lokinet.app and make the GUI the default for
// the application then Lokinet gets killed by gatekeeper because code signing only applies the
// (required-for-system-extensions) provisioningprofile to the main binary in the app.
//
// So we are left needing *one* single binary that isn't the GUI but has to do double-duty for both
// exec'ing the binary and loading lokinet, depending on how it is called.
//
// But of course there is no way to specify command-line arguments to the default binary macOS runs,
// so we can't use a `--gui` flag or anything so abhorrent to macos purity, thus this nasty
// solution:
// - no args -- exec the GUI
// - `--start` -- load the system extension and start lokinet
// - `--stop` -- stop lokinet
//
// macOS: land of half-baked implementations and nasty hacks to make anything work.
if args.count == 1 {
let gui_path = Bundle.main.resourcePath! + "/../Helpers/Lokinet-GUI.app"
if !FileManager.default.fileExists(atPath: gui_path) {
NSLog("Could not find gui app at %@", gui_path)
exit(1)
}
let gui_url = URL(fileURLWithPath: gui_path, isDirectory: false)
let gui_app_conf = NSWorkspace.OpenConfiguration()
let group = DispatchGroup()
group.enter()
NSWorkspace.shared.openApplication(at: gui_url, configuration: gui_app_conf,
completionHandler: { (app, error) in
if error != nil {
NSLog("Error launching gui: %@", error!.localizedDescription)
} else {
NSLog("Lauched GUI");
}
group.leave()
})
group.wait()
} else if args.count == 2 {
let delegate = LokinetMain()
delegate.mode = args[1]
app.delegate = delegate
app.run()
} else {
NSLog(HELP_STRING)
}

@ -1,73 +1,123 @@
Codesigning and notarization on macOS
If you are reading this to try to build Lokinet for yourself for an Apple operating system and
simultaneously care about open source, privacy, or freedom then you, my friend, are a walking
contradiction: you are trying to get Lokinet to work on a platform that actively despises open
source, privacy, and freedom. Even Windows is a better choice in all of these categories than
Apple.
This is painful. Thankfully most of the pain is now in CMake and a python script.
This directory contains the magical incantations and random voodoo symbols needed to coax an Apple
build. There's no reason builds have to be this stupid, except that Apple wants to funnel everyone
into the no-CI, no-help, undocumented, non-toy-apps-need-not-apply modern Apple culture.
To build, codesign, and notarized and installer package, CMake needs to be invoked with:
This is disgusting.
cd build
rm -rf * # optional but recommended
cmake .. -DBUILD_PACKAGE=ON -DDOWNLOAD_SODIUM=ON -DMACOS_SIGN_APP=ABC123... -DMACOS_SIGN_PKG=DEF456...
But it gets worse.
where the ABC123... key is a "Developer ID Installer" key and PKG key is a "Developer ID
Application" key. You have to go through a bunch of pain, pay Apple money, and then read a bunch of
poorly written documentation that doesn't help very much to create these and get them working. But once you have them
set up in Keychain, you should be able to list your keys with:
The following two files, in particular, are the very worst manifestations of this already toxic
Apple cancer: they are required for proper permissions to run on macOS, are undocumented, and can
only be regenerated through the entirely closed source Apple Developer backend, for which you have
to pay money first to get a team account (a personal account will not work), and they lock the
resulting binaries to only run on individually selected Apple computers selected at the time the
profile is provisioned (with no ability to allow it to run anywhere).
security find-identity -v
lokinet.dev.provisionprofile
lokinet-extension.dev.provisionprofile
and you should see (at least) one "Developer ID Installer: ..." and one "Developer ID Application:
...". You need both for reasons that only Apple knows. The former is used to sign the installer
.pkg, and the latter is used to sign everything *inside* the .pkg, and you can't use the same key
for both because Apple designed code signing by marketing committee rather than ask any actual
competent software developers how code signing should work.
This is actively hostile to open source development, but that is nothing new for Apple.
Either way, these two values can be specified either by hex value or description string that
`security find-identity -v` spits out.
There are also release provisioning profiles
You also need to set up the notarization parameters; these can either be specified directly on the
cmake command line by adding:
lokinet.release.provisionprofile
lokinet-extension.release.provisionprofile
-DMACOS_NOTARIZE_ASC=XYZ123 -DMACOS_NOTARIZE_USER=me@example.com -DMACOS_NOTARIZE_PASS=@keychain:codesigning-password
These ones allow distribution of the app, but only if notarized, and again require notarization plus
signing by a (paid) Apple developer account.
or, more simply, by putting them inside a `~/.notarization.cmake` file that will be included if it
exists (and the MACOS_SIGN_* variables are set) -- see below.
In order to make things work, you'll have to replace these provisioning profiles with your own
(after paying Apple for the privilege of developing on their platform, of course) and change all the
team/application/bundle IDs to reference your own team, matching the provisioning profiles. The dev
provisioning profiles must be a "macOS Development" provisioning profile, and must include the
signing keys and the authorized devices on which you want to run it. (The profiles bundled in this
repository contains the lokinet team's "Apple Development" keys associated with the Oxen project,
and mac dev boxes. This is *useless* for anyone else).
These three values here are:
For release builds, you still need a provisioning profile, but it must be a "Distribution: Developer
ID" provisioning profile, and are tied to a (paid) Developer ID. The ones in the repository are
attached to the Oxen Project Developer ID and are useless to anyone else.
MACOS_NOTARIZE_ASC:
Once you have that in place, you need to build and sign the package using a certificate matching
your provisioning profile before your Apple system will allow it to run. (That's right, your $2000
box won't let you run programs you build from source on it unless you also subscribe to a $100/year
Apple developer account).
Organization-specific unique value; this is printed inside (brackets) when you run: `security
find-identity -v`:
Okay, so now that you have paid Apple more money for the privilege of using your own computer,
here's how you make a signed lokinet app:
1) 1C75DDBF884DEF3D5927C3F29BB7FC5ADAE2E1B3 "Apple Development: me@example.com (ABC123XYZ9)"
1) Decide which type of build you are doing: a lokinet system extension, or an app extension. The
former must be signed and notarized and will only work when placed in the /Applications folder,
but will not work as a dev build and cannot be distributed outside the Mac App Store. The latter
is usable as a dev build, but still requires a signature and Apple-provided provisioningprofile
listing the limited number of devices on which it is allowed to run.
MACOS_NOTARIZE_USER:
For system extension builds you want to add the -DMACOS_SYSTEM_EXTENSION=ON flag to cmake.
Your Apple Developer login.
2) Figure out the certificate to use for signing and make sure you have it installed. For a
distributable system extension build you need a "Developer ID Application" key and certificate,
issued by your paid developer.apple.com account. For dev builds you need a "Apple Development"
certificate.
MACOS_NOTARIZE_PASS:
In most cases you don't need to specify these; the default cmake script will figure them out.
(If it can't, e.g. because you have multiple of the right type installed, it will error with the
keys it found).
This should be an app-specific password created for signing on the Apple Developer website. You
*can* specify it directly, but it is much better to use the magic `@keychain:blah` value, where
'blah' is a password name recorded in Keychain. To get that in place you run:
To be explicit, use `security find-identity -v` to list your keys, then list the key identity
with -DCODESIGN_ID=.....
export HISTFILE='' # for bash: you don't want to store this in your history
xcrun altool --store-password-in-keychain-item "NOTARIZE_PASSWORD" -u "user" -p "password"
3) If you are doing a system extension build you will need to provide notarization login information by adding:
where NOTARIZE_PASSWORD is just some name for the password (I called it 'blah' or
'codesigning-password' above), and the "user" and "password" are replaced with your actual Apple
Developer account device-specific login credentials.
-DMACOS_NOTARIZE_ASC=XYZ123 -DMACOS_NOTARIZE_USER=me@example.com -DMACOS_NOTARIZE_PASS=@keychain:codesigning-password
Optionally, put these last three inside a `~/.notarization.cmake` file:
a) The first value (XYZ123) needs to be the organization-specific unique value, and is printed in
brackets in the certificate description. For example:
set(MACOS_NOTARIZE_USER "jagerman@jagerman.com")
set(MACOS_NOTARIZE_PASS "@keychain:codesigning-password")
set(MACOS_NOTARIZE_ASC "SUQ8J2PCT7")
15095CD1E6AF441ABC69BDC52EE186A18200A49F "Developer ID Application: Some Developer (ABC123XYZ9)"
Then, finally, you can build the package from the build directory with:
would require ABC123XYZ9 for this field.
make package -j4 # or whatever -j makes you happy
make notarize
b) The USER field is your Apple Developer login e-mail address.
The former builds and signs the package, the latter submits it for notarization. This can take a
few minutes; the script polls Apple's server until it is finished passing or failing notarization.
c) The PASS field is a keychain reference holding your "Application-Specific Password". To set
up such a password for your account, consult Apple documentation. Once you have it, load it
into your keychain via:
export HISTFILE='' # Don't want to store this in the shell history
xcrun altool --store-password-in-keychain-item "codesigning-password" -u "user" -p "password"
You can change "codesigning-password" to whatever you want (just make sure it agrees with the
-DMACOS_NOTARIZE_PASS option you build with). "user" and "password" should be your developer
account device-specific login credentials provided by Apple.
To make your life easier, stash these settings into a `~/.notarization.cmake` file inside your
home directory; if you have not specified them in the build, and this file exists, lokinet's
cmake will load it:
set(MACOS_NOTARIZE_USER "me@example.com")
set(MACOS_NOTARIZE_PASS "@keychain:codesigning-password")
set(MACOS_NOTARIZE_ASC "ABC123XYZ9")
4) Build and sign the package; there is a script `contrib/mac.sh` that can help (extra cmake options
you need can be appended to the end), or you can build yourself in a build directory. See the
script for the other cmake options that are typically needed. Note that `-G Ninja` (as well as a
working ninja builder) are required.
If you get an error `errSecInternalComponent` this is Apple's highly descriptive way of telling
you that you need to unlock your keychain, which you can do by running `security unlock`.
If doing it yourself, `ninja sign` will build and then sign the app.
If you need to also notarize (e.g. for a system extension build) run `./notarize.py` from the
build directory (or alternatively `ninja notarize`, but the former gives you status output while
it runs).
5) Packaging the app: you want to use `-DBUILD_PACKAGE=ON` when configuring with cmake and then,
once all signing and notarization is complete, run `cpack` which will give you a .dmg and a .zip
containing the release.

@ -41,7 +41,7 @@ endif()
macro(system_or_submodule BIGNAME smallname pkgconf subdir)
option(FORCE_${BIGNAME}_SUBMODULE "force using ${smallname} submodule" OFF)
if(NOT STATIC AND NOT FORCE_${BIGNAME}_SUBMODULE)
if(NOT BUILD_STATIC_DEPS AND NOT FORCE_${BIGNAME}_SUBMODULE AND NOT FORCE_ALL_SUBMODULES)
pkg_check_modules(${BIGNAME} ${pkgconf} IMPORTED_TARGET)
endif()
if(${BIGNAME}_FOUND)
@ -64,6 +64,7 @@ endmacro()
system_or_submodule(OXENC oxenc liboxenc>=1.0.3 oxen-encoding)
system_or_submodule(OXENMQ oxenmq liboxenmq>=1.2.12 oxen-mq)
set(JSON_BuildTests OFF CACHE INTERNAL "")
set(JSON_Install OFF CACHE INTERNAL "")
system_or_submodule(NLOHMANN nlohmann_json nlohmann_json>=3.7.0 nlohmann)
if (STATIC OR FORCE_SPDLOG_SUBMODULE OR FORCE_FMT_SUBMODULE)

1
gui

@ -0,0 +1 @@
Subproject commit abcd94814e261ffd88104357e3946201d0d2c8e0

@ -205,6 +205,11 @@ add_library(liblokinet
service/tag.cpp
)
if(WITH_PEERSTATS_BACKEND)
target_compile_definitions(liblokinet PRIVATE -DLOKINET_PEERSTATS_BACKEND)
target_link_libraries(liblokinet PUBLIC sqlite_orm)
endif()
set_target_properties(liblokinet PROPERTIES OUTPUT_NAME lokinet)
enable_lto(lokinet-util lokinet-platform liblokinet)
@ -227,7 +232,6 @@ target_link_libraries(liblokinet PUBLIC
lokinet-platform
lokinet-util
lokinet-cryptography
sqlite_orm
ngtcp2_static
oxenmq::oxenmq)
target_link_libraries(liblokinet PRIVATE libunbound)
@ -252,7 +256,7 @@ if(BUILD_LIBLOKINET)
if(WIN32)
target_link_libraries(lokinet-shared PUBLIC ws2_32 iphlpapi -fstack-protector)
install(TARGETS lokinet-shared DESTINATION bin COMPONENT liblokinet)
else()
elseif(NOT APPLE)
install(TARGETS lokinet-shared LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT liblokinet)
endif()
endif()

@ -3,7 +3,7 @@
cmake_minimum_required(VERSION 3.13)
if (BUILD_SHARED_LIBS OR NOT BUILD_STATIC_DEPS OR NOT STATIC_LINK)
message(FATAL_ERROR "macOS builds require a full static build; perhaps use the contrib/macos.sh script to build?")
message(FATAL_ERROR "macOS builds require a full static build; perhaps use the contrib/mac.sh script to build?")
endif()
# god (steve jobs) made apple so that man may suffer
@ -25,26 +25,37 @@ target_link_libraries(lokinet-extension PRIVATE
${COREFOUNDATION}
${NETEXT})
# Not sure what -fapplication-extension does, but XCode puts it in so...
# -fobjc-arc enables automatic reference counting for objective-C code
# -e _NSExtensionMain because the appex has that instead of a `main` function entry point, of course.
target_compile_options(lokinet-extension PRIVATE -fapplication-extension -fobjc-arc)
target_link_options(lokinet-extension PRIVATE -fapplication-extension -e _NSExtensionMain)
target_compile_options(lokinet-extension PRIVATE -fobjc-arc)
if(MACOS_SYSTEM_EXTENSION)
target_compile_definitions(lokinet-extension PRIVATE MACOS_SYSTEM_EXTENSION)
target_compile_definitions(lokinet-util PUBLIC MACOS_SYSTEM_EXTENSION)
else()
target_link_options(lokinet-extension PRIVATE -e _NSExtensionMain)
endif()
target_link_libraries(lokinet-extension PUBLIC
liblokinet
${COREFOUNDATION}
${NETEXT})
if(MACOS_SYSTEM_EXTENSION)
set(bundle_ext systemextension)
set(product_type com.apple.product-type.system-extension)
else()
set(bundle_ext appex)
set(product_type com.apple.product-type.app-extension)
endif()
set_target_properties(lokinet-extension PROPERTIES
BUNDLE TRUE
BUNDLE_EXTENSION appex
MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/contrib/macos/LokinetExtension.Info.plist.in
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
BUNDLE_EXTENSION ${bundle_ext}
OUTPUT_NAME org.lokinet.network-extension
MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.Info.plist.in
XCODE_PRODUCT_TYPE ${product_type}
)
add_custom_command(TARGET lokinet-extension
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.provisionprofile
$<TARGET_BUNDLE_DIR:lokinet-extension>/Contents/embedded.provisionprofile
if(CODESIGN AND CODESIGN_EXT_PROFILE)
add_custom_command(TARGET lokinet-extension
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CODESIGN_EXT_PROFILE}
$<TARGET_BUNDLE_DIR:lokinet-extension>/Contents/embedded.provisionprofile
)
endif()

@ -5,18 +5,19 @@
extern NSString* error_domain;
/**
* "Trampoline" class that listens for UDP DNS packets on port 1053 coming from lokinet's embedded
* libunbound (when exit mode is enabled), wraps them via NetworkExtension's crappy UDP API, then
* sends responses back to libunbound to be parsed/etc. This class knows nothing about DNS, it is
* basically just a UDP packet forwarder.
* "Trampoline" class that listens for UDP DNS packets when we have exit mode enabled. These arrive
* on localhost:1053 coming from lokinet's embedded libunbound (when exit mode is enabled), wraps
* them via NetworkExtension's crappy UDP API, then sends responses back to libunbound to be
* parsed/etc. This class knows nothing about DNS, it is basically just a UDP packet forwarder, but
* using Apple magic reinvented wheel wrappers that are oh so wonderful like everything Apple.
*
* So for a lokinet configuration of "upstream=1.1.1.1", when exit mode is OFF:
* - DNS requests go to TUNNELIP:53, get sent to libunbound, which forwards them (directly) to the
* upstream DNS server(s).
* - DNS requests go unbound either to 127.0.0.1:53 directly (system extension) or bounced through
* TUNNELIP:53 (app extension), which forwards them (directly) to the upstream DNS server(s).
* With exit mode ON:
* - DNS requests go to TUNNELIP:53, get send to libunbound, which forwards them to 127.0.0.1:1053,
* which encapsulates them in Apple's god awful crap, then (on a response) sends them back to
* libunbound.
* - DNS requests go to unbound, as above, and unbound forwards them to 127.0.0.1:1053, which
* encapsulates them in Apple's god awful crap, then (on a response) sends them back to
* libunbound to be delivered back to the requestor.
* (This assumes a non-lokinet DNS; .loki and .snode get handled before either of these).
*/
@interface LLARPDNSTrampoline : NSObject
@ -40,6 +41,7 @@ extern NSString* error_domain;
uv_async_t write_trigger;
}
- (void)startWithUpstreamDns:(NWUDPSession*)dns
listenIp:(NSString*)listenIp
listenPort:(uint16_t)listenPort
uvLoop:(uv_loop_t*)loop
completionHandler:(void (^)(NSError* error))completionHandler;

@ -1,7 +1,7 @@
#include "DNSTrampoline.h"
#include <uv.h>
NSString* error_domain = @"com.loki-project.lokinet";
NSString* error_domain = @"org.lokinet";
// Receiving an incoming packet, presumably from libunbound. NB: this is called from the libuv
@ -68,10 +68,12 @@ static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* b
@implementation LLARPDNSTrampoline
- (void)startWithUpstreamDns:(NWUDPSession*) dns
listenIp:(NSString*) listenIp
listenPort:(uint16_t) listenPort
uvLoop:(uv_loop_t*) loop
completionHandler:(void (^)(NSError* error))completionHandler
{
NSLog(@"Setting up trampoline");
pending_writes = [[NSMutableArray<NSData*> alloc] init];
write_trigger.data = (__bridge void*) self;
uv_async_init(loop, &write_trigger, write_flusher);
@ -79,7 +81,7 @@ static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* b
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);
uv_ip4_addr(listenIp.UTF8String, listenPort, &recv_addr);
int ret = uv_udp_bind(&request_socket, (const struct sockaddr*) &recv_addr, UV_UDP_REUSEADDR);
if (ret < 0) {
NSString* errstr = [NSString stringWithFormat:@"Failed to start DNS trampoline: %s", uv_strerror(ret)];

@ -3,9 +3,12 @@
#include "context_wrapper.h"
#include "DNSTrampoline.h"
#define LLARP_APPLE_PACKET_BUF_SIZE 64
@interface LLARPPacketTunnel : NEPacketTunnelProvider
{
void* lokinet;
llarp_incoming_packet packet_buf[LLARP_APPLE_PACKET_BUF_SIZE];
@public NEPacketTunnelNetworkSettings* settings;
@public NEIPv4Route* tun_route4;
@public NEIPv6Route* tun_route6;
@ -35,8 +38,8 @@ static void packet_writer(int af, const void* data, size_t size, void* ctx) {
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]];
[t.packetFlow writePackets:@[buf]
withProtocols:@[[NSNumber numberWithInt:af]]];
}
static void start_packet_reader(void* ctx) {
@ -48,6 +51,7 @@ static void start_packet_reader(void* ctx) {
}
static void add_ipv4_route(const char* addr, const char* netmask, void* ctx) {
NSLog(@"Adding IPv4 route %s:%s to packet tunnel", addr, netmask);
NEIPv4Route* route = [[NEIPv4Route alloc]
initWithDestinationAddress: [NSString stringWithUTF8String:addr]
subnetMask: [NSString stringWithUTF8String:netmask]];
@ -65,6 +69,7 @@ static void add_ipv4_route(const char* addr, const char* netmask, void* ctx) {
}
static void del_ipv4_route(const char* addr, const char* netmask, void* ctx) {
NSLog(@"Removing IPv4 route %s:%s to packet tunnel", addr, netmask);
NEIPv4Route* route = [[NEIPv4Route alloc]
initWithDestinationAddress: [NSString stringWithUTF8String:addr]
subnetMask: [NSString stringWithUTF8String:netmask]];
@ -124,6 +129,7 @@ static void del_ipv6_route(const char* addr, int prefix, void* ctx) {
}
static void add_default_route(void* ctx) {
NSLog(@"Making the tunnel the default route");
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
t->settings.IPv4Settings.includedRoutes = @[NEIPv4Route.defaultRoute];
@ -133,6 +139,7 @@ static void add_default_route(void* ctx) {
}
static void del_default_route(void* ctx) {
NSLog(@"Removing default route from tunnel");
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
t->settings.IPv4Settings.includedRoutes = @[t->tun_route4];
@ -148,9 +155,21 @@ static void del_default_route(void* ctx) {
[self.packetFlow readPacketObjectsWithCompletionHandler: ^(NSArray<NEPacket*>* packets) {
if (lokinet == nil)
return;
size_t size = 0;
for (NEPacket* p in packets) {
llarp_apple_incoming(lokinet, p.data.bytes, p.data.length);
packet_buf[size].bytes = p.data.bytes;
packet_buf[size].size = p.data.length;
size++;
if (size >= LLARP_APPLE_PACKET_BUF_SIZE)
{
llarp_apple_incoming(lokinet, packet_buf, size);
size = 0;
}
}
if (size > 0)
llarp_apple_incoming(lokinet, packet_buf, size);
[self readPackets];
}];
}
@ -190,7 +209,14 @@ static void del_default_route(void* ctx) {
// We don't have a fixed address so just stick some bogus value here:
settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.3.2.1"];
NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[ip]];
#ifdef MACOS_SYSTEM_EXTENSION
NSString* dns_ip = [NSString stringWithUTF8String:conf.dns_bind_ip];
#else
// TODO: placeholder
NSString* dns_ip = ip;
#endif
NSLog(@"setting dns to %@", dns_ip);
NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[dns_ip]];
dns.domainName = @"localhost.loki";
dns.matchDomains = @[@""];
// In theory, matchDomains is supposed to be set to DNS suffixes that we resolve. This seems
@ -246,11 +272,13 @@ static void del_default_route(void* ctx) {
return completionHandler(start_failure);
}
NSLog(@"Starting DNS exit mode trampoline to %@ on 127.0.0.1:%d", upstreamdns_ep, dns_trampoline_port);
NSString* dns_tramp_ip = @"127.0.0.1";
NSLog(@"Starting DNS exit mode trampoline to %@ on %@:%d", upstreamdns_ep, dns_tramp_ip, dns_trampoline_port);
NWUDPSession* upstreamdns = [strongSelf createUDPSessionThroughTunnelToEndpoint:upstreamdns_ep fromEndpoint:nil];
strongSelf->dns_tramp = [LLARPDNSTrampoline alloc];
[strongSelf->dns_tramp
startWithUpstreamDns:upstreamdns
listenIp:dns_tramp_ip
listenPort:dns_trampoline_port
uvLoop:llarp_apple_get_uv_loop(strongSelf->lokinet)
completionHandler:^(NSError* error) {
@ -258,7 +286,7 @@ static void del_default_route(void* ctx) {
NSLog(@"Error starting dns trampoline: %@", error);
return completionHandler(error);
}];
}];
}];
}
- (void)stopTunnelWithReason:(NEProviderStopReason)reason
@ -308,3 +336,12 @@ static void del_default_route(void* ctx) {
}
@end
#ifdef MACOS_SYSTEM_EXTENSION
int main() {
[NEProvider startSystemExtensionMode];
dispatch_main();
}
#endif

@ -3,6 +3,7 @@
#include <cassert>
#include <llarp/net/ip_packet.hpp>
#include <llarp/config/config.hpp>
#include <llarp/constants/apple.hpp>
#include <llarp/util/fs.hpp>
#include <uvw/loop.h>
#include <llarp/util/logging.hpp>
@ -14,10 +15,6 @@
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;
@ -30,7 +27,8 @@ namespace
} // namespace
const uint16_t dns_trampoline_port = 1053;
// Expose this with C linkage so that objective-c can use it
extern "C" const uint16_t dns_trampoline_port = llarp::apple::dns_trampoline_port;
void*
llarp_apple_init(llarp_apple_config* appleconf)
@ -89,10 +87,12 @@ llarp_apple_init(llarp_apple_config* appleconf)
}
}
// The default DNS bind setting just isn't something we can use as a non-root network extension
// so remap the default value to a high port unless explicitly set to something else.
if (config->dns.m_bind == llarp::SockAddr{"127.0.0.1:53"})
config->dns.m_bind = DefaultDNSBind;
#ifdef MACOS_SYSTEM_EXTENSION
std::strncpy(
appleconf->dns_bind_ip,
config->dns.m_bind.hostString().c_str(),
sizeof(appleconf->dns_bind_ip));
#endif
// If no explicit bootstrap then set the system default one included with the app bundle
if (config->bootstrap.files.empty())
@ -170,20 +170,26 @@ llarp_apple_get_uv_loop(void* lokinet)
}
int
llarp_apple_incoming(void* lokinet, const void* bytes, size_t size)
llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size)
{
auto& inst = *static_cast<instance_data*>(lokinet);
auto iface = inst.iface.lock();
if (!iface)
return -2;
return -1;
llarp_buffer_t buf{static_cast<const uint8_t*>(bytes), size};
if (iface->OfferReadPacket(buf))
return 0;
int count = 0;
for (size_t i = 0; i < size; i++)
{
llarp_buffer_t buf{static_cast<const uint8_t*>(packets[i].bytes), packets[i].size};
if (iface->OfferReadPacket(buf))
count++;
else
llarp::LogError("invalid IP packet: ", llarp::buffer_printer(buf));
}
llarp::LogError("invalid IP packet: ", llarp::buffer_printer(buf));
return -1;
iface->MaybeWakeUpperLayers();
return count;
}
void

@ -82,6 +82,12 @@ extern "C"
char upstream_dns[INET_ADDRSTRLEN];
uint16_t upstream_dns_port;
#ifdef MACOS_SYSTEM_EXTENSION
/// DNS bind IP; llarp_apple_init writes the lokinet config value here so that we know (in Apple
/// API code) what to set DNS to when lokinet gets turned on. Null terminated.
char dns_bind_ip[INET_ADDRSTRLEN];
#endif
/// \defgroup callbacks Callbacks
/// Callbacks we invoke for various operations that require glue into the Apple network
/// extension APIs. All of these except for ns_logger are passed the pointer provided to
@ -135,12 +141,23 @@ extern "C"
uv_loop_t*
llarp_apple_get_uv_loop(void* lokinet);
/// Called to deliver an incoming packet from the apple layer into lokinet; returns 0 on success,
/// -1 if the packet could not be parsed, -2 if there is no current active VPNInterface associated
/// with the lokinet (which generally means llarp_apple_start wasn't called or failed, or lokinet
/// is in the process of shutting down).
/// Struct of packet data; a C array of tests gets passed to llarp_apple_incoming
typedef struct llarp_incoming_packet
{
const void* bytes;
size_t size;
} llarp_incoming_packet;
/// Called to deliver one or more incoming packets from the apple layer into lokinet. Takes a C
/// array of `llarp_incoming_packets` with pointers/sizes set to the individual new packets that
/// have arrived.
///
/// Returns the number of valid packets on success (which can be less than the number of provided
/// packets, if some failed to parse), or -1 if there is no current active VPNInterface associated
/// with the lokinet instance (which generally means llarp_apple_start wasn't called or failed, or
/// lokinet is in the process of shutting down).
int
llarp_apple_incoming(void* lokinet, const void* bytes, size_t size);
llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size);
/// Stops a lokinet instance created with `llarp_apple_initialize`. This waits for lokinet to
/// shut down and rejoins the thread. After this call the given pointer is no longer valid.

@ -775,11 +775,7 @@ namespace llarp
// Most non-linux platforms have loopback as 127.0.0.1/32, but linux uses 127.0.0.1/8 so that we
// can bind to other 127.* IPs to avoid conflicting with something else that may be listening on
// 127.0.0.1:53.
#ifdef __linux__
constexpr Default DefaultDNSBind{"127.3.2.1:53"};
#else
constexpr Default DefaultDNSBind{"127.0.0.1:53"};
#endif
constexpr Default DefaultDNSBind{platform::is_linux ? "127.3.2.1:53" : "127.0.0.1:53"};
// Default, but if we get any upstream (including upstream=, i.e. empty string) we clear it
constexpr Default DefaultUpstreamDNS{"9.9.9.10"};

@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
namespace llarp::apple
{
/// Localhost port on macOS where we proxy DNS requests *through* the tunnel, because without
/// calling into special snowflake Apple network APIs an extension's network connections all go
/// around the tunnel, even when the tunnel is (supposedly) the default route.
inline constexpr std::uint16_t dns_trampoline_port = 1053;
/// We query the above trampoline from unbound with this fixed source port (so that the trampoline
/// is simplified by not having to track different ports for different requests).
inline constexpr std::uint16_t dns_trampoline_source_port = 1054;
} // namespace llarp::apple

@ -32,7 +32,7 @@ namespace llarp::platform
/// are we an apple platform ?
inline constexpr bool is_apple =
#ifdef __apple__
#ifdef __APPLE__
true
#else
false

@ -1,6 +1,8 @@
#include "unbound_resolver.hpp"
#include "server.hpp"
#include <llarp/constants/apple.hpp>
#include <llarp/constants/platform.hpp>
#include <llarp/util/buffer.hpp>
#include <sstream>
#include <llarp/util/str.hpp>
@ -144,37 +146,51 @@ namespace llarp::dns
bool
UnboundResolver::AddUpstreamResolver(const SockAddr& upstreamResolver)
{
std::stringstream ss;
auto hoststr = upstreamResolver.hostString();
ss << hoststr;
const auto hoststr = upstreamResolver.hostString();
std::string upstream = hoststr;
if (const auto port = upstreamResolver.getPort(); port != 53)
ss << "@" << port;
const auto port = upstreamResolver.getPort();
if (port != 53)
{
upstream += '@';
upstream += std::to_string(port);
}
const auto str = ss.str();
if (ub_ctx_set_fwd(unboundContext, str.c_str()) != 0)
LogError("Adding upstream resolver ", upstream);
if (ub_ctx_set_fwd(unboundContext, upstream.c_str()) != 0)
{
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")
if constexpr (platform::is_apple)
{
// 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");
// On Apple, when we turn on exit mode, we can't directly connect to upstream from here
// because, from within the network extension, macOS ignores setting the tunnel as the default
// route and would leak all DNS; instead we have to bounce things through the objective C
// trampoline code so that it can call into Apple's special snowflake API to set up a socket
// that has the magic Apple snowflake sauce added on top so that it actually routes through
// the tunnel instead of around it.
//
// This behaviour is all carefully and explicitly documented by Apple with plenty of examples
// and other exposition, of course, just like all of their wonderful new APIs to reinvent
// standard unix interfaces.
if (hoststr == "127.0.0.1" && port == apple::dns_trampoline_port)
{
// 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:", "127.0.0.1");
// 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:",
std::to_string(apple::dns_trampoline_source_port).c_str());
}
}
#endif
return true;
}

@ -25,6 +25,7 @@
#include <llarp/rpc/endpoint_rpc.hpp>
#include <llarp/util/str.hpp>
#include <llarp/dns/srv_data.hpp>
#include <llarp/constants/platform.hpp>
#include <oxenc/bt.h>
@ -71,9 +72,10 @@ namespace llarp
#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.
// upstream DNS won't be set in our resolvers, which is why the vanilla IsUpstreamResolver,
// above, won't work for us). However when active the mac also only queries the main tunnel
// IP for DNS, so we consider anything else to be upstream-bound DNS to let it through the
// tunnel.
bool
IsUpstreamResolver(const SockAddr& to, const SockAddr& from) const override
{
@ -87,7 +89,7 @@ namespace llarp
{
m_PacketRouter = std::make_unique<vpn::PacketRouter>(
[this](net::IPPacket pkt) { HandleGotUserPacket(std::move(pkt)); });
#if defined(ANDROID) || defined(__APPLE__)
#if defined(ANDROID) || (defined(__APPLE__) && !defined(MACOS_SYSTEM_EXTENSION))
m_Resolver = std::make_shared<DnsInterceptor>(r, this);
m_PacketRouter->AddUDPHandler(huint16_t{53}, [&](net::IPPacket pkt) {
const size_t ip_header_size = (pkt.Header()->ihl * 4);
@ -1066,6 +1068,15 @@ namespace llarp
src = pkt.srcv6();
}
if constexpr (llarp::platform::is_apple)
{
if (dst == m_OurIP)
{
HandleWriteIPPacket(pkt.ConstBuffer(), src, dst, 0);
return;
}
}
if (m_state->m_ExitEnabled)
{
dst = net::ExpandV4(net::TruncateV6(dst));

@ -6,6 +6,8 @@
namespace llarp
{
#ifdef LOKINET_PEERSTATS_BACKEND
PeerDb::PeerDb()
{
m_lastFlush.store({});
@ -297,4 +299,66 @@ namespace llarp
return obj;
}
#else // !LOKINET_PEERSTATS
// Empty stubs
PeerDb::PeerDb()
{
throw std::logic_error{"Peer stats backend not enabled!"};
}
void PeerDb::loadDatabase(std::optional<fs::path>)
{}
void
PeerDb::flushDatabase()
{}
void
PeerDb::accumulatePeerStats(const RouterID&, const PeerStats&)
{}
void
PeerDb::modifyPeerStats(const RouterID&, std::function<void(PeerStats&)>)
{}
std::optional<PeerStats>
PeerDb::getCurrentPeerStats(const RouterID&) const
{
return std::nullopt;
}
std::vector<PeerStats>
PeerDb::listAllPeerStats() const
{
return {};
}
std::vector<PeerStats>
PeerDb::listPeerStats(const std::vector<RouterID>&) const
{
return {};
}
void
PeerDb::handleGossipedRC(const RouterContact&, llarp_time_t)
{}
void
PeerDb::configure(const RouterConfig& routerConfig)
{}
bool
PeerDb::shouldFlush(llarp_time_t now)
{}
util::StatusObject
PeerDb::ExtractStatus() const
{
return {};
}
#endif
}; // namespace llarp

@ -4,14 +4,15 @@
#include <functional>
#include <unordered_map>
#include <sqlite_orm/sqlite_orm.h>
#include <llarp/util/fs.hpp>
#include <llarp/config/config.hpp>
#include <llarp/router_id.hpp>
#include <llarp/util/time.hpp>
#include <llarp/util/status.hpp>
#include "types.hpp"
#ifdef LOKINET_PEERSTATS_BACKEND
#include "orm.hpp"
#endif
namespace llarp
{
@ -126,6 +127,7 @@ namespace llarp
util::StatusObject
ExtractStatus() const;
#ifdef LOKINET_PEERSTATS_BACKEND
private:
std::unordered_map<RouterID, PeerStats> m_peerStats;
mutable std::mutex m_statsLock;
@ -133,6 +135,7 @@ namespace llarp
std::unique_ptr<PeerDbStorage> m_storage;
std::atomic<llarp_time_t> m_lastFlush;
#endif
};
} // namespace llarp

@ -93,9 +93,9 @@ build 1 or many cross targets:
### MacOS <span id="mac-install" />
Lokinet ~~is~~ will be available on the Apple App store.
Source code compilation of Lokinet by end users is not supported or permitted by apple on their platforms, see [this](contrib/macos/README.txt) for more information.
Source code compilation of Lokinet by end users is not supported or permitted by apple on their platforms, see [this](contrib/macos/README.txt) for more information. If you find this disagreeable consider using a platform that permits compiling from source.
If you find this disagreeable consider using a platform that permits compiling from source.
### Windows <span id="windows-install" />

Loading…
Cancel
Save