diff --git a/CMakeLists.txt b/CMakeLists.txt
index 028c5a333..eca295599 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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)
@@ -311,6 +311,6 @@ if(NOT TARGET uninstall)
endif()
-if(BUILD_PACKAGE AND NOT APPLE)
+if(BUILD_PACKAGE)
include(cmake/installer.cmake)
endif()
diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake
index 3ddbe9757..db39eff78 100644
--- a/cmake/StaticBuild.cmake
+++ b/cmake/StaticBuild.cmake
@@ -351,6 +351,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()
diff --git a/cmake/check_for_std_filesystem.cmake b/cmake/check_for_std_filesystem.cmake
index aef0448ec..84248d07e 100644
--- a/cmake/check_for_std_filesystem.cmake
+++ b/cmake/check_for_std_filesystem.cmake
@@ -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)
diff --git a/cmake/installer.cmake b/cmake/installer.cmake
index bdd4958fc..60e39f69b 100644
--- a/cmake/installer.cmake
+++ b/cmake/installer.cmake
@@ -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()
diff --git a/contrib/mac.sh b/contrib/mac.sh
index 0ddcbe3ff..855f6bcc5 100755
--- a/contrib/mac.sh
+++ b/contrib/mac.sh
@@ -18,16 +18,9 @@ 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 \
"$@" \
@@ -35,5 +28,5 @@ cmake \
ninja sign
echo -e "Build complete, your app is here:\n"
-ls -lad $(pwd)/daemon/lokinet.app
+ls -lad $(pwd)/Lokinet.app
echo ""
diff --git a/contrib/macos/Info.plist.in b/contrib/macos/Info.plist.in
deleted file mode 100644
index 9311f2404..000000000
--- a/contrib/macos/Info.plist.in
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- en
- CFBundleDisplayName
- Lokinet
- CFBundleExecutable
- MacOS/lokinet
- CFBundleIdentifier
- com.loki-project.lokinet
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- lokinet
- CFBundlePackageType
- XPC!
- CFBundleShortVersionString
- @lokinet_VERSION@
- CFBundleVersion
- @lokinet_VERSION@.@LOKINET_APPLE_BUILD@
-
-
diff --git a/contrib/macos/LokinetExtension.Info.plist.in b/contrib/macos/LokinetExtension.Info.plist.in
deleted file mode 100644
index 80afb1b94..000000000
--- a/contrib/macos/LokinetExtension.Info.plist.in
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
- CFBundleDisplayName
- Lokinet
-
- CFBundleExecutable
- lokinet-extension
-
- CFBundleIdentifier
- com.loki-project.lokinet.network-extension
-
- CFBundleInfoDictionaryVersion
- 6.0
-
- CFBundlePackageType
- XPC!
-
- CFBundleName
- lokinet
-
- CFBundleVersion
- @lokinet_VERSION@
-
- ITSAppUsesNonExemptEncryption
-
-
- LSMinimumSystemVersion
- 11.0
-
- NSExtension
-
- NSExtensionPointIdentifier
- com.apple.networkextension.packet-tunnel
- NSExtensionPrincipalClass
- LLARPPacketTunnel
-
-
-
diff --git a/contrib/macos/README.txt b/contrib/macos/README.txt
deleted file mode 100644
index 9880ecc3c..000000000
--- a/contrib/macos/README.txt
+++ /dev/null
@@ -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.
diff --git a/contrib/macos/lokinet-extension.Info.plist.in b/contrib/macos/lokinet-extension.Info.plist.in
new file mode 100644
index 000000000..7647393ee
--- /dev/null
+++ b/contrib/macos/lokinet-extension.Info.plist.in
@@ -0,0 +1,64 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+
+ CFBundleDisplayName
+ Lokinet Network Extension
+
+ CFBundleExecutable
+ org.lokinet.network-extension
+
+ CFBundleIdentifier
+ org.lokinet.network-extension
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+
+ CFBundlePackageType
+ SYSX
+
+ CFBundleName
+ org.lokinet.network-extension
+
+ CFBundleVersion
+ @lokinet_VERSION@.@LOKINET_APPLE_BUILD@
+
+ CFBundleShortVersionString
+ @lokinet_VERSION@
+
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+
+ ITSAppUsesNonExemptEncryption
+
+
+ LSMinimumSystemVersion
+ 10.15
+
+ NSHumanReadableCopyright
+ Copyright © 2022 The Oxen Project, licensed under GPLv3-or-later
+
+ NSSystemExtensionUsageDescription
+ Provides Lokinet Network connectivity.
+
+ NetworkExtension
+
+ NEMachServiceName
+ SUQ8J2PCT7.org.lokinet.network-extension
+
+ NEProviderClasses
+
+ com.apple.networkextension.packet-tunnel
+ LLARPPacketTunnel
+
+ com.apple.networkextension.dns-proxy
+ LLARPDNSProxy
+
+
+
+
diff --git a/contrib/macos/lokinet-extension.provisionprofile b/contrib/macos/lokinet-extension.dev.provisionprofile
similarity index 79%
rename from contrib/macos/lokinet-extension.provisionprofile
rename to contrib/macos/lokinet-extension.dev.provisionprofile
index 33605eba6..c7a1b3269 100644
Binary files a/contrib/macos/lokinet-extension.provisionprofile and b/contrib/macos/lokinet-extension.dev.provisionprofile differ
diff --git a/contrib/macos/lokinet-extension.entitlements.plist b/contrib/macos/lokinet-extension.plugin.entitlements.plist
similarity index 75%
rename from contrib/macos/lokinet-extension.entitlements.plist
rename to contrib/macos/lokinet-extension.plugin.entitlements.plist
index 56c32bb55..b8baadbc7 100644
--- a/contrib/macos/lokinet-extension.entitlements.plist
+++ b/contrib/macos/lokinet-extension.plugin.entitlements.plist
@@ -3,11 +3,12 @@
com.apple.application-identifier
- SUQ8J2PCT7.com.loki-project.lokinet.network-extension
+ SUQ8J2PCT7.org.lokinet.network-extension
com.apple.developer.networking.networkextension
- packet-tunnel-provider-systemextension
+ packet-tunnel-provider
+ dns-proxy
com.apple.developer.team-identifier
@@ -16,9 +17,6 @@
com.apple.security.app-sandbox
- com.apple.security.get-task-allow
-
-
com.apple.security.network.client
diff --git a/contrib/macos/lokinet-extension.release.provisionprofile b/contrib/macos/lokinet-extension.release.provisionprofile
new file mode 100644
index 000000000..1eaefd12e
Binary files /dev/null and b/contrib/macos/lokinet-extension.release.provisionprofile differ
diff --git a/contrib/macos/lokinet.entitlements.plist b/contrib/macos/lokinet-extension.sysext.entitlements.plist
similarity index 60%
rename from contrib/macos/lokinet.entitlements.plist
rename to contrib/macos/lokinet-extension.sysext.entitlements.plist
index 040256232..26086f9fe 100644
--- a/contrib/macos/lokinet.entitlements.plist
+++ b/contrib/macos/lokinet-extension.sysext.entitlements.plist
@@ -3,13 +3,12 @@
com.apple.application-identifier
- SUQ8J2PCT7.com.loki-project.lokinet
+ SUQ8J2PCT7.org.lokinet.network-extension
com.apple.developer.networking.networkextension
packet-tunnel-provider-systemextension
- dns-proxy-systemextension
- dns-settings
+ dns-proxy-systemextension
com.apple.developer.team-identifier
@@ -18,13 +17,15 @@
com.apple.security.app-sandbox
- com.apple.security.get-task-allow
-
+ com.apple.security.application-groups
+
+ SUQ8J2PCT7.org.lokinet
+
- com.apple.security.network.client
+ com.apple.security.network.client
-
- com.apple.security.network.server
+
+ com.apple.security.network.server
diff --git a/contrib/macos/lokinet.Info.plist.in b/contrib/macos/lokinet.Info.plist.in
new file mode 100644
index 000000000..c03953cde
--- /dev/null
+++ b/contrib/macos/lokinet.Info.plist.in
@@ -0,0 +1,39 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+
+ CFBundleDisplayName
+ Lokinet
+
+ CFBundleExecutable
+ Lokinet
+
+ CFBundleIdentifier
+ org.lokinet
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+
+ CFBundleName
+ Lokinet
+
+ CFBundlePackageType
+ APPL
+
+ CFBundleShortVersionString
+ @lokinet_VERSION@
+
+ CFBundleVersion
+ @lokinet_VERSION@.@LOKINET_APPLE_BUILD@
+
+ LSMinimumSystemVersion
+ 10.15
+
+ NSHumanReadableCopyright
+ Copyright © 2022 The Oxen Project, licensed under GPLv3-or-later
+
+
+
diff --git a/contrib/macos/lokinet.provisionprofile b/contrib/macos/lokinet.dev.provisionprofile
similarity index 80%
rename from contrib/macos/lokinet.provisionprofile
rename to contrib/macos/lokinet.dev.provisionprofile
index 9fbcd4f7c..e15cccff4 100644
Binary files a/contrib/macos/lokinet.provisionprofile and b/contrib/macos/lokinet.dev.provisionprofile differ
diff --git a/contrib/macos/lokinet.plugin.entitlements.plist b/contrib/macos/lokinet.plugin.entitlements.plist
new file mode 100644
index 000000000..7c172b9e0
--- /dev/null
+++ b/contrib/macos/lokinet.plugin.entitlements.plist
@@ -0,0 +1,28 @@
+
+
+
+
+ com.apple.application-identifier
+ SUQ8J2PCT7.org.lokinet
+
+ com.apple.developer.networking.networkextension
+
+ packet-tunnel-provider
+ dns-proxy
+ dns-settings
+
+
+ com.apple.developer.team-identifier
+ SUQ8J2PCT7
+
+ com.apple.security.app-sandbox
+
+
+ com.apple.security.network.client
+
+
+ com.apple.security.network.server
+
+
+
+
diff --git a/contrib/macos/lokinet.release.provisionprofile b/contrib/macos/lokinet.release.provisionprofile
new file mode 100644
index 000000000..6aaeead39
Binary files /dev/null and b/contrib/macos/lokinet.release.provisionprofile differ
diff --git a/contrib/macos/lokinet.sysext.entitlements.plist b/contrib/macos/lokinet.sysext.entitlements.plist
new file mode 100644
index 000000000..4c08d92f5
--- /dev/null
+++ b/contrib/macos/lokinet.sysext.entitlements.plist
@@ -0,0 +1,36 @@
+
+
+
+
+ com.apple.application-identifier
+ SUQ8J2PCT7.org.lokinet
+
+ com.apple.developer.networking.networkextension
+
+ packet-tunnel-provider-systemextension
+ dns-proxy-systemextension
+ dns-settings
+
+
+ com.apple.developer.team-identifier
+ SUQ8J2PCT7
+
+ com.apple.developer.system-extension.install
+
+
+ com.apple.security.app-sandbox
+
+
+ com.apple.security.application-groups
+
+ SUQ8J2PCT7.org.lokinet
+
+
+ com.apple.security.network.client
+
+
+ com.apple.security.network.server
+
+
+
+
diff --git a/contrib/macos/notarize.py.in b/contrib/macos/notarize.py.in
old mode 100644
new mode 100755
index e042bface..5752e8b55
--- a/contrib/macos/notarize.py.in
+++ b/contrib/macos/notarize.py.in
@@ -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 = "Lokinet.app.notarize.zip"
+print(f"Creating lokinet.app.notarize.zip from lokinet.app")
+if os.path.exists(zipfile):
+ os.remove(zipfile)
+subprocess.run(['zip', '-r', zipfile, app])
-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']:
@@ -70,7 +96,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")
diff --git a/contrib/macos/sign.sh.in b/contrib/macos/sign.sh.in
index 6ebf0859a..a949f1ec5 100755
--- a/contrib/macos/sign.sh.in
+++ b/contrib/macos/sign.sh.in
@@ -1,10 +1,25 @@
#!/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 [ -z "@CODESIGN" ]; then
+ echo "Cannot codesign: this build was not configured with codesigning" >&2
+ exit 1
+fi
+
+for ext in systemextension appex; do
+ netext="@lokinet_ext_dir@/org.lokinet.network-extension.$ext"
+ if [ -e "@SIGN_TARGET@/$netext" ]; then
+ echo -e "\n\e[33;1mSigning $netext...\e[0m\n" >&2
+ codesign --verbose=4 --force -s "@CODESIGN_ID@" \
+ --entitlements "@PROJECT_SOURCE_DIR@/contrib/macos/lokinet-extension.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist" \
+ --deep --strict --timestamp --options=runtime "@SIGN_TARGET@/$netext"
+ fi
+done
+
+for sub in "/Contents/MacOS/Lokinet" "" ; do
+ echo -e "\n\e[33;1mSigning $(basename @SIGN_TARGET@)$sub...\e[0m\n" >&2
+ codesign --verbose=4 --force -s "@CODESIGN_ID@" \
+ --entitlements "@PROJECT_SOURCE_DIR@/contrib/macos/lokinet.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist" \
+ --deep --strict --timestamp --options=runtime "@SIGN_TARGET@$sub"
done
diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt
index 50999b080..93e93162a 100644
--- a/daemon/CMakeLists.txt
+++ b/daemon/CMakeLists.txt
@@ -1,14 +1,18 @@
-add_executable(lokinet-vpn lokinet-vpn.cpp)
+set(exetargets lokinet)
+
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 +46,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,10 +56,13 @@ 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})
+ add_log_tag(${exe})
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()
@@ -68,10 +70,77 @@ foreach(exe ${exetargets})
endforeach()
if(APPLE)
-
- set(CODESIGN_APP "" CACHE STRING "codesign the macos app using this key identity")
- set(CODESIGN_EXT "${CODESIGN_APP}" CACHE STRING "codesign the internal extension using this key identity; defaults to CODESIGN_APP if empty")
+ 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")
+ set(CODESIGN_EXT_PROFILE "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.${default_profile_type}.provisionprofile" CACHE FILEPATH
+ "Path to a .provisionprofile to use for the extension")
+
+ 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}")
+ else()
+ message(WARNING "Codesigning disabled; the resulting build will not run on most macOS systems")
+ endif()
+
+ if(MACOS_SYSTEM_EXTENSION)
+ set(lokinet_ext_dir Contents/Library/SystemExtensions)
+ target_compile_definitions(lokinet PRIVATE MACOS_SYSTEM_EXTENSION)
+ 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()
+ set(lokinet_ext_dir Contents/PlugIns)
+ endif()
+
+ foreach(var CODESIGN_PROFILE CODESIGN_EXT_PROFILE)
+ if(NOT ${var})
+ message(WARNING "Missing a ${var} provisioning profile, and not building a system extension: 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 "${${var}}")
+ message(FATAL_ERROR "Provisioning profile ${${var}} does not exist; fix your -D${var} path")
+ endif()
+ endforeach()
+ message(STATUS "Using ${CODESIGN_PROFILE} provisioning profile")
+ message(STATUS "Using ${CODESIGN_EXT_PROFILE} extension provisioning profile")
set(mac_icon ${CMAKE_CURRENT_BINARY_DIR}/lokinet.icns)
add_custom_command(OUTPUT ${mac_icon}
@@ -79,37 +148,50 @@ if(APPLE)
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)
+ set(post_build_pp)
+ if(CODESIGN AND CODESIGN_PROFILE)
+ set(post_build_pp COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CODESIGN_PROFILE}
+ $/Contents/embedded.provisionprofile)
+ endif()
+
add_custom_command(TARGET lokinet
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed
$/Contents/Resources/bootstrap.signed
- COMMAND mkdir -p $/Contents/PlugIns
- COMMAND cp -a $ $/Contents/PlugIns/
- COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.provisionprofile
- $/Contents/embedded.provisionprofile
- )
+ COMMAND mkdir -p $/${lokinet_ext_dir}
+ COMMAND cp -a $ $/${lokinet_ext_dir}
+ ${post_build_pp}
+ )
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 "com.loki-project.lokinet"
- MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/contrib/macos/Info.plist.in"
+ MACOSX_BUNDLE_GUI_IDENTIFIER "org.lokinet"
+ MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.Info.plist.in"
MACOSX_BUNDLE_ICON_FILE "${mac_icon}"
- MACOSX_BUNDLE_COPYRIGHT "© 2021, The Oxen Project")
+ MACOSX_BUNDLE_COPYRIGHT "© 2022, The Oxen Project"
+ RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}"
+ )
+
if(NOT CODESIGN)
message(STATUS "codesigning disabled")
add_custom_target(
sign
DEPENDS lokinet lokinet-extension
COMMAND "true")
- elseif (CODESIGN_APP AND CODESIGN_EXT)
- message(STATUS "codesigning with ${CODESIGN_APP} (app) ${CODESIGN_EXT} (appex)")
- set(SIGN_TARGET "${CMAKE_CURRENT_BINARY_DIR}/lokinet.app")
+ elseif(CODESIGN)
+ set(SIGN_TARGET "${PROJECT_BINARY_DIR}/lokinet.app")
+ if(MACOS_SYSTEM_EXTENSION)
+ set(LOKINET_ENTITLEMENTS_TYPE sysext)
+ else()
+ set(LOKINET_ENTITLEMENTS_TYPE plugin)
+ endif()
configure_file(
"${PROJECT_SOURCE_DIR}/contrib/macos/sign.sh.in"
"${PROJECT_BINARY_DIR}/sign.sh"
@@ -119,6 +201,25 @@ if(APPLE)
DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" lokinet lokinet-extension
COMMAND "${PROJECT_BINARY_DIR}/sign.sh"
)
+
+ if(NOT (MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC))
+ message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization disabled")
+ endif()
+ if (MACOS_SYSTEM_EXTENSION)
+ set(notarize_py_is_sysext True)
+ else()
+ set(notarize_py_is_sysext False)
+ endif()
+ 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(FATAL_ERROR "CODESIGN_APP (=${CODESIGN_APP}) and/or CODESIGN_EXT (=${CODESIGN_EXT}) are not set. To disable code signing use -DCODESIGN=OFF")
endif()
diff --git a/daemon/lokinet.swift b/daemon/lokinet.swift
index 86277cb88..1d8e0a106 100644
--- a/daemon/lokinet.swift
+++ b/daemon/lokinet.swift
@@ -1,6 +1,7 @@
import AppKit
import Foundation
import NetworkExtension
+import SystemExtensions
let app = NSApplication.shared
@@ -11,14 +12,13 @@ let HELP_STRING = "usage: lokinet [--start|--stop]"
class LokinetMain: NSObject, NSApplicationDelegate {
var vpnManager = NETunnelProviderManager()
- let lokinetComponent = "com.loki-project.lokinet.network-extension"
- var mode = ""
+ var mode = START
+ let netextBundleId = "org.lokinet.network-extension"
func applicationDidFinishLaunching(_: Notification) {
if self.mode == START {
- setupVPNTunnel()
- }
- else if self.mode == STOP {
+ startNetworkExtension()
+ } else if self.mode == STOP {
tearDownVPNTunnel()
} else {
self.result(msg: HELP_STRING)
@@ -46,7 +46,7 @@ class LokinetMain: NSObject, NSApplicationDelegate {
if let savedManagers = savedManagers {
for manager in savedManagers {
- if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.lokinetComponent {
+ if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.netextBundleId {
manager.isEnabled = false
self.result(msg: "Lokinet Down")
return
@@ -57,8 +57,21 @@ class LokinetMain: NSObject, NSApplicationDelegate {
}
}
+ 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")
+
+ NSLog("Starting up Lokinet tunnel")
NETunnelProviderManager.loadAllFromPreferences { [self] (savedManagers: [NETunnelProviderManager]?, error: Error?) in
if let error = error {
self.result(msg: error.localizedDescription)
@@ -67,7 +80,7 @@ class LokinetMain: NSObject, NSApplicationDelegate {
if let savedManagers = savedManagers {
for manager in savedManagers {
- if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.lokinetComponent {
+ if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.netextBundleId {
NSLog("Found saved VPN Manager")
self.vpnManager = manager
}
@@ -76,7 +89,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.
@@ -130,11 +143,42 @@ class LokinetMain: NSObject, NSApplicationDelegate {
}
}
+#if MACOS_SYSTEM_EXTENSION
+
+extension LokinetMain: OSSystemExtensionRequestDelegate {
+
+ func request(_ 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(_ request: OSSystemExtensionRequest, didFailWithError error: Error) {
+ NSLog("System extension request failed: %@", error.localizedDescription)
+ }
+
+ 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 args.count > 1 {
+if args.count <= 2 {
let delegate = LokinetMain()
- delegate.mode = args[1]
+ delegate.mode = args.count > 1 ? args[1] : START
app.delegate = delegate
app.run()
} else {
diff --git a/docs/macos-signing.txt b/docs/macos-signing.txt
index 9e6f2ba07..9e323da8e 100644
--- a/docs/macos-signing.txt
+++ b/docs/macos-signing.txt
@@ -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.
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index 0ea284a78..6514f72a1 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -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)
diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt
index 0782add06..c6dd5e6f6 100644
--- a/llarp/CMakeLists.txt
+++ b/llarp/CMakeLists.txt
@@ -252,7 +252,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()
diff --git a/llarp/apple/CMakeLists.txt b/llarp/apple/CMakeLists.txt
index ad18d9920..30b984653 100644
--- a/llarp/apple/CMakeLists.txt
+++ b/llarp/apple/CMakeLists.txt
@@ -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
- $/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}
+ $/Contents/embedded.provisionprofile
)
+endif()
diff --git a/llarp/apple/DNSTrampoline.h b/llarp/apple/DNSTrampoline.h
index 4935d43c8..117d567b2 100644
--- a/llarp/apple/DNSTrampoline.h
+++ b/llarp/apple/DNSTrampoline.h
@@ -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;
diff --git a/llarp/apple/DNSTrampoline.m b/llarp/apple/DNSTrampoline.m
index 0a78a13e2..cbbe211a3 100644
--- a/llarp/apple/DNSTrampoline.m
+++ b/llarp/apple/DNSTrampoline.m
@@ -1,7 +1,7 @@
#include "DNSTrampoline.h"
#include
-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 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)];
diff --git a/llarp/apple/PacketTunnelProvider.m b/llarp/apple/PacketTunnelProvider.m
index b340e56cb..0dac9953f 100644
--- a/llarp/apple/PacketTunnelProvider.m
+++ b/llarp/apple/PacketTunnelProvider.m
@@ -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* 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,10 @@ 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]];
+ NSString* dns_ip = [NSString stringWithUTF8String:conf.dns_bind_ip];
+
+ 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 +268,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 +282,7 @@ static void del_default_route(void* ctx) {
NSLog(@"Error starting dns trampoline: %@", error);
return completionHandler(error);
}];
- }];
+ }];
}
- (void)stopTunnelWithReason:(NEProviderStopReason)reason
@@ -308,3 +332,12 @@ static void del_default_route(void* ctx) {
}
@end
+
+#ifdef MACOS_SYSTEM_EXTENSION
+
+int main() {
+ [NEProvider startSystemExtensionMode];
+ dispatch_main();
+}
+
+#endif
diff --git a/llarp/apple/context_wrapper.cpp b/llarp/apple/context_wrapper.cpp
index f2a904251..4fc85c804 100644
--- a/llarp/apple/context_wrapper.cpp
+++ b/llarp/apple/context_wrapper.cpp
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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(lokinet);
auto iface = inst.iface.lock();
if (!iface)
- return -2;
+ return -1;
- llarp_buffer_t buf{static_cast(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(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
diff --git a/llarp/apple/context_wrapper.h b/llarp/apple/context_wrapper.h
index 37c8a5c7b..1f09a46d3 100644
--- a/llarp/apple/context_wrapper.h
+++ b/llarp/apple/context_wrapper.h
@@ -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.
diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp
index 90be0b1ee..7fc78746d 100644
--- a/llarp/config/config.cpp
+++ b/llarp/config/config.cpp
@@ -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"};
diff --git a/llarp/constants/apple.hpp b/llarp/constants/apple.hpp
new file mode 100644
index 000000000..46a94f9bc
--- /dev/null
+++ b/llarp/constants/apple.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include
+
+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
diff --git a/llarp/dns/unbound_resolver.cpp b/llarp/dns/unbound_resolver.cpp
index a65728285..cc1fd3d87 100644
--- a/llarp/dns/unbound_resolver.cpp
+++ b/llarp/dns/unbound_resolver.cpp
@@ -1,6 +1,8 @@
#include "unbound_resolver.hpp"
#include "server.hpp"
+#include
+#include
#include
#include
#include
@@ -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;
}
diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp
index e14e37c06..776b2edb1 100644
--- a/llarp/handlers/tun.cpp
+++ b/llarp/handlers/tun.cpp
@@ -72,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
{
@@ -88,7 +89,7 @@ namespace llarp
{
m_PacketRouter = std::make_unique(
[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(r, this);
m_PacketRouter->AddUDPHandler(huint16_t{53}, [&](net::IPPacket pkt) {
const size_t ip_header_size = (pkt.Header()->ihl * 4);