mirror of https://github.com/oxen-io/lokinet
Merge remote-tracking branch 'origin/dev' into debian/bullseye
commit
a93ab96466
@ -0,0 +1 @@
|
|||||||
|
5.4.2
|
@ -1,113 +0,0 @@
|
|||||||
# macos specific cpack stuff goes here
|
|
||||||
|
|
||||||
# Here we build lokinet-network-control-panel into 'lokinet-gui.app' in "extra/" where a postinstall
|
|
||||||
# script will then move it to /Applications/.
|
|
||||||
|
|
||||||
set(LOKINET_GUI_REPO "https://github.com/oxen-io/loki-network-control-panel.git"
|
|
||||||
CACHE STRING "Can be set to override the default lokinet-gui git repository")
|
|
||||||
set(LOKINET_GUI_CHECKOUT "origin/master"
|
|
||||||
CACHE STRING "Can be set to specify a particular branch or tag to build from LOKINET_GUI_REPO")
|
|
||||||
set(MACOS_SIGN_APP "" # FIXME: it doesn't use a Apple Distribution key because WTF knows.
|
|
||||||
CACHE STRING "enable codesigning of the stuff inside the .app and the lokinet binary -- use a 'Apple Distribution' key (or description) from `security find-identity -v`")
|
|
||||||
set(MACOS_SIGN_PKG ""
|
|
||||||
CACHE STRING "enable codesigning of the .pkg -- use a 'Developer ID Installer' key (or description) from `security find-identity -v`")
|
|
||||||
set(MACOS_NOTARIZE_USER ""
|
|
||||||
CACHE STRING "set macos notarization username; can also set it in ~/.notarization.cmake")
|
|
||||||
set(MACOS_NOTARIZE_PASS ""
|
|
||||||
CACHE STRING "set macos notarization password; can also set it in ~/.notarization.cmake")
|
|
||||||
set(MACOS_NOTARIZE_ASC ""
|
|
||||||
CACHE STRING "set macos notarization asc provider; can also set it in ~/.notarization.cmake")
|
|
||||||
|
|
||||||
include(ExternalProject)
|
|
||||||
|
|
||||||
message(STATUS "Building UninstallLokinet.app")
|
|
||||||
|
|
||||||
ExternalProject_Add(lokinet-uninstaller
|
|
||||||
SOURCE_DIR ${CMAKE_SOURCE_DIR}/contrib/macos/uninstaller
|
|
||||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR} -DMACOS_SIGN=${MACOS_SIGN_APP}
|
|
||||||
-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}
|
|
||||||
)
|
|
||||||
|
|
||||||
message(STATUS "Building LokinetGUI.app from ${LOKINET_GUI_REPO} @ ${LOKINET_GUI_CHECKOUT}")
|
|
||||||
|
|
||||||
if(NOT BUILD_STATIC_DEPS)
|
|
||||||
message(FATAL_ERROR "Building an installer on macos requires -DBUILD_STATIC_DEPS=ON")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ExternalProject_Add(lokinet-gui
|
|
||||||
DEPENDS oxenmq::oxenmq
|
|
||||||
GIT_REPOSITORY "${LOKINET_GUI_REPO}"
|
|
||||||
GIT_TAG "${LOKINET_GUI_CHECKOUT}"
|
|
||||||
CMAKE_ARGS -DMACOS_APP=ON -DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR} -DMACOS_SIGN=${MACOS_SIGN_APP}
|
|
||||||
-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DBUILD_SHARED_LIBS=OFF
|
|
||||||
"-DOXENMQ_LIBRARIES=$<TARGET_FILE:oxenmq::oxenmq>$<SEMICOLON>$<TARGET_FILE:libzmq>$<SEMICOLON>$<TARGET_FILE:sodium>"
|
|
||||||
"-DOXENMQ_INCLUDE_DIRS=$<TARGET_PROPERTY:oxenmq::oxenmq,INCLUDE_DIRECTORIES>"
|
|
||||||
)
|
|
||||||
|
|
||||||
install(PROGRAMS ${CMAKE_SOURCE_DIR}/contrib/macos/lokinet_uninstall.sh
|
|
||||||
DESTINATION "bin/"
|
|
||||||
COMPONENT lokinet)
|
|
||||||
|
|
||||||
install(DIRECTORY ${PROJECT_BINARY_DIR}/LokinetGUI.app
|
|
||||||
DESTINATION "../../Applications/Lokinet"
|
|
||||||
USE_SOURCE_PERMISSIONS
|
|
||||||
COMPONENT gui
|
|
||||||
PATTERN "*"
|
|
||||||
)
|
|
||||||
|
|
||||||
install(DIRECTORY ${PROJECT_BINARY_DIR}/UninstallLokinet.app
|
|
||||||
DESTINATION "../../Applications/Lokinet"
|
|
||||||
USE_SOURCE_PERMISSIONS
|
|
||||||
COMPONENT gui
|
|
||||||
PATTERN "*"
|
|
||||||
)
|
|
||||||
|
|
||||||
# copy files that will be later moved by the postinstall script to proper locations
|
|
||||||
install(FILES ${CMAKE_SOURCE_DIR}/contrib/macos/lokinet_macos_daemon_script.sh
|
|
||||||
${CMAKE_SOURCE_DIR}/contrib/macos/network.loki.lokinet.daemon.plist
|
|
||||||
${CMAKE_SOURCE_DIR}/contrib/macos/lokinet-newsyslog.conf
|
|
||||||
DESTINATION "extra/"
|
|
||||||
COMPONENT lokinet)
|
|
||||||
|
|
||||||
set(CPACK_COMPONENTS_ALL lokinet gui)
|
|
||||||
|
|
||||||
set(CPACK_COMPONENT_LOKINET_DISPLAY_NAME "Lokinet Service")
|
|
||||||
set(CPACK_COMPONENT_LOKINET_DESCRIPTION "Main Lokinet runtime service, managed by Launchd")
|
|
||||||
|
|
||||||
set(CPACK_COMPONENT_GUI_DISPLAY_NAME "Lokinet GUI")
|
|
||||||
set(CPACK_COMPONENT_GUI_DESCRIPTION "Small GUI which provides stats and limited runtime control of the Lokinet service. Resides in the system tray.")
|
|
||||||
|
|
||||||
set(CPACK_GENERATOR "productbuild")
|
|
||||||
set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/lokinet")
|
|
||||||
set(CPACK_PREINSTALL_LOKINET_SCRIPT ${CMAKE_SOURCE_DIR}/contrib/macos/preinstall)
|
|
||||||
set(CPACK_POSTFLIGHT_LOKINET_SCRIPT ${CMAKE_SOURCE_DIR}/contrib/macos/postinstall)
|
|
||||||
|
|
||||||
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE.txt")
|
|
||||||
|
|
||||||
set(CPACK_PRODUCTBUILD_IDENTITY_NAME "${MACOS_SIGN_PKG}")
|
|
||||||
|
|
||||||
if(MACOS_SIGN_APP)
|
|
||||||
add_custom_target(sign ALL
|
|
||||||
echo "Signing lokinet and lokinet-vpn binaries"
|
|
||||||
COMMAND codesign -s "${MACOS_SIGN_APP}" --strict --options runtime --force -vvv $<TARGET_FILE:lokinet> $<TARGET_FILE:lokinet-vpn>
|
|
||||||
DEPENDS lokinet lokinet-vpn
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(MACOS_SIGN_APP AND MACOS_SIGN_PKG)
|
|
||||||
if(NOT MACOS_NOTARIZE_USER)
|
|
||||||
if(EXISTS "$ENV{HOME}/.notarization.cmake")
|
|
||||||
include("$ENV{HOME}/.notarization.cmake")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
if(MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC)
|
|
||||||
message(STATUS "'notarization' target enabled")
|
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/contrib/macos/notarize.py.in ${CMAKE_CURRENT_BINARY_DIR}/contrib/notarize.py ESCAPE_QUOTES @ONLY)
|
|
||||||
file(COPY ${CMAKE_CURRENT_BINARY_DIR}/contrib/notarize.py DESTINATION ${PROJECT_BINARY_DIR} FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
|
|
||||||
add_custom_target(notarize ./notarize.py)
|
|
||||||
else()
|
|
||||||
message(WARNING "Not enable 'notarization' target: signing is enabled but notarization info not provided. Create ~/.notarization.cmake or set cmake parameters directly")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 189.4 189.4" style="enable-background:new 0 0 189.4 189.4;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<polygon class="st0" points="113.6,132.6 94.7,151.5 75.8,132.6 56.8,151.5 94.7,189.4 132.6,151.5 "/>
|
||||||
|
<polygon class="st0" points="132.6,113.6 151.5,94.7 132.6,75.8 151.5,56.8 189.4,94.7 151.5,132.6 "/>
|
||||||
|
<polygon class="st0" points="56.8,75.8 37.9,94.7 56.8,113.6 37.9,132.6 0,94.7 37.9,56.8 "/>
|
||||||
|
<polygon class="st0" points="75.8,56.8 94.7,37.9 113.6,56.8 132.6,37.9 94.7,0 56.8,37.9 "/>
|
||||||
|
|
||||||
|
<rect x="100.2" y="100.2" transform="matrix(0.7071 0.7071 -0.7071 0.7071 113.6329 -47.0683)" class="st0" width="26.8" height="26.8"/>
|
||||||
|
|
||||||
|
<rect x="62.4" y="62.4" transform="matrix(0.7071 0.7071 -0.7071 0.7071 75.7552 -31.3789)" class="st0" width="26.8" height="26.8"/>
|
||||||
|
|
||||||
|
<rect x="100.2" y="62.4" transform="matrix(0.7071 0.7071 -0.7071 0.7071 86.8493 -58.1624)" class="st0" width="26.8" height="26.8"/>
|
||||||
|
|
||||||
|
<rect x="62.4" y="100.2" transform="matrix(0.7071 0.7071 -0.7071 0.7071 102.5388 -20.2848)" class="st0" width="26.8" height="26.8"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Build the shit on mac
|
||||||
|
#
|
||||||
|
# You will generally need to add: -DCODESIGN_APP=... to make this work, and (unless you are a
|
||||||
|
# lokinet team member) will need to pay Apple money for your own team ID and arse around with
|
||||||
|
# provisioning profiles. See macos/README.txt.
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set +x
|
||||||
|
if ! [ -f LICENSE.txt ] || ! [ -d llarp ]; then
|
||||||
|
echo "You need to run this as ./contrib/mac.sh from the top-level lokinet project directory"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p build-mac
|
||||||
|
cd build-mac
|
||||||
|
cmake \
|
||||||
|
-G Ninja \
|
||||||
|
-DBUILD_STATIC_DEPS=ON \
|
||||||
|
-DBUILD_PACKAGE=ON \
|
||||||
|
-DBUILD_SHARED_LIBS=OFF \
|
||||||
|
-DBUILD_TESTING=OFF \
|
||||||
|
-DBUILD_LIBLOKINET=OFF \
|
||||||
|
-DWITH_TESTS=OFF \
|
||||||
|
-DNATIVE_BUILD=OFF \
|
||||||
|
-DSTATIC_LINK=ON \
|
||||||
|
-DWITH_SYSTEMD=OFF \
|
||||||
|
-DFORCE_OXENMQ_SUBMODULE=ON \
|
||||||
|
-DSUBMODULE_CHECK=OFF \
|
||||||
|
-DWITH_LTO=ON \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
"$@" \
|
||||||
|
..
|
||||||
|
ninja sign
|
||||||
|
|
||||||
|
echo -e "Build complete, your app is here:\n"
|
||||||
|
ls -lad $(pwd)/daemon/lokinet.app
|
||||||
|
echo ""
|
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Lokinet</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>MacOS/lokinet</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.loki-project.lokinet</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>lokinet</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>XPC!</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>@lokinet_VERSION@</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>@lokinet_VERSION@.@LOKINET_APPLE_BUILD@</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Lokinet</string>
|
||||||
|
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>lokinet-extension</string>
|
||||||
|
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.loki-project.lokinet.network-extension</string>
|
||||||
|
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>XPC!</string>
|
||||||
|
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>lokinet</string>
|
||||||
|
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>@lokinet_VERSION@</string>
|
||||||
|
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
|
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>11.0</string>
|
||||||
|
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.networkextension.packet-tunnel</string>
|
||||||
|
<key>NSExtensionPrincipalClass</key>
|
||||||
|
<string>LLARPPacketTunnel</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,38 @@
|
|||||||
|
This directory contains the magical incantations and random voodoo symbols needed to coax an Apple
|
||||||
|
build. There's no reason builds have to be this stupid, except that Apple wants to funnel everyone
|
||||||
|
into the no-CI, no-help, undocumented, non-toy-apps-need-not-apply modern Apple culture.
|
||||||
|
|
||||||
|
This is disgusting.
|
||||||
|
|
||||||
|
But it gets worse.
|
||||||
|
|
||||||
|
The following two files, in particular, are the very worst manifestations of this already toxic
|
||||||
|
Apple cancer: they are required for proper permissions to run on macOS, are undocumented, and can
|
||||||
|
only be regenerated through the entirely closed source Apple Developer backend, for which you have
|
||||||
|
to pay money first to get a team account (a personal account will not work), and they lock the
|
||||||
|
resulting binaries to only run on individually selected Apple computers selected at the time the
|
||||||
|
profile is provisioned (with no ability to allow it to run anywhere).
|
||||||
|
|
||||||
|
lokinet.provisionprofile
|
||||||
|
lokinet-extension.provisionprofile
|
||||||
|
|
||||||
|
This is actively hostile to open source development, but that is nothing new for Apple.
|
||||||
|
|
||||||
|
In order to make things work, you'll have to replace these provisioning profiles with your own
|
||||||
|
(after paying Apple for the privilege of developing on their platform, of course) and change all the
|
||||||
|
team/application/bundle IDs to reference your own team, matching the provisioning profiles. The
|
||||||
|
provisioning profiles must be a "macOS Development" provisioning profile, and must include the
|
||||||
|
signing keys and the authorized devices on which you want to run it. (The profiles bundled in this
|
||||||
|
repository contains the lokinet team's "Apple Development" keys associated with the Oxen project,
|
||||||
|
and mac dev boxes. This is *useless* for anyone else).
|
||||||
|
|
||||||
|
Also take note that you *must not* put a development build `lokinet.app` inside /Applications
|
||||||
|
because if you do, it won't work because *on top* of the ridiculous signing and entitlement bullshit
|
||||||
|
that Apple makes you jump through, the rules *also* differ for binaries placed in /Applications
|
||||||
|
versus binaries placed elsewhere, but like everything else here, it is entirely undocumented.
|
||||||
|
|
||||||
|
If you are reading this to try to build Lokinet for yourself for an Apple operating system and
|
||||||
|
simultaneously care about open source, privacy, or freedom then you, my friend, are a walking
|
||||||
|
contradiction: you are trying to get Lokinet to work on a platform that actively despises open
|
||||||
|
source, privacy, and freedom. Even Windows is a better choice in all of these categories than
|
||||||
|
Apple.
|
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.application-identifier</key>
|
||||||
|
<string>SUQ8J2PCT7.com.loki-project.lokinet.network-extension</string>
|
||||||
|
|
||||||
|
<key>com.apple.developer.networking.networkextension</key>
|
||||||
|
<array>
|
||||||
|
<string>packet-tunnel-provider</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<key>com.apple.developer.team-identifier</key>
|
||||||
|
<string>SUQ8J2PCT7</string>
|
||||||
|
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>com.apple.security.get-task-allow</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
</dict>
|
||||||
|
</plist>
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.application-identifier</key>
|
||||||
|
<string>SUQ8J2PCT7.com.loki-project.lokinet</string>
|
||||||
|
|
||||||
|
<key>com.apple.developer.networking.networkextension</key>
|
||||||
|
<array>
|
||||||
|
<string>packet-tunnel-provider</string>
|
||||||
|
<string>dns-proxy</string>
|
||||||
|
<string>dns-settings</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<key>com.apple.developer.team-identifier</key>
|
||||||
|
<string>SUQ8J2PCT7</string>
|
||||||
|
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>com.apple.security.get-task-allow</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
</dict>
|
||||||
|
</plist>
|
Binary file not shown.
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
codesign --verbose=4 --force -s "@CODESIGN_APPEX@" \
|
||||||
|
--entitlements "@PROJECT_SOURCE_DIR@/contrib/macos/lokinet-extension.entitlements.plist" \
|
||||||
|
--deep --strict --timestamp --options=runtime "@SIGN_TARGET@/Contents/PlugIns/lokinet-extension.appex"
|
||||||
|
for file in "@SIGN_TARGET@/Contents/MacOS/lokinet" "@SIGN_TARGET@" ; do
|
||||||
|
codesign --verbose=4 --force -s "@CODESIGN_APP@" \
|
||||||
|
--entitlements "@PROJECT_SOURCE_DIR@/contrib/macos/lokinet.entitlements.plist" \
|
||||||
|
--deep --strict --timestamp --options=runtime "$file"
|
||||||
|
done
|
@ -0,0 +1,100 @@
|
|||||||
|
import AppKit
|
||||||
|
import Foundation
|
||||||
|
import NetworkExtension
|
||||||
|
|
||||||
|
let app = NSApplication.shared
|
||||||
|
|
||||||
|
class LokinetMain: NSObject, NSApplicationDelegate {
|
||||||
|
var vpnManager = NETunnelProviderManager()
|
||||||
|
let lokinetComponent = "com.loki-project.lokinet.network-extension"
|
||||||
|
|
||||||
|
func applicationDidFinishLaunching(_: Notification) {
|
||||||
|
setupVPNJizz()
|
||||||
|
}
|
||||||
|
|
||||||
|
func bail() {
|
||||||
|
app.terminate(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupVPNJizz() {
|
||||||
|
NSLog("Starting up lokinet")
|
||||||
|
NETunnelProviderManager.loadAllFromPreferences { [self] (savedManagers: [NETunnelProviderManager]?, error: Error?) in
|
||||||
|
if let error = error {
|
||||||
|
NSLog(error.localizedDescription)
|
||||||
|
bail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let savedManagers = savedManagers {
|
||||||
|
for manager in savedManagers {
|
||||||
|
if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.lokinetComponent {
|
||||||
|
NSLog("%@", manager)
|
||||||
|
NSLog("Found saved VPN Manager")
|
||||||
|
self.vpnManager = manager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let providerProtocol = NETunnelProviderProtocol()
|
||||||
|
providerProtocol.serverAddress = "loki.loki" // Needs to be set to some non-null dummy value
|
||||||
|
providerProtocol.username = "anonymous"
|
||||||
|
providerProtocol.providerBundleIdentifier = self.lokinetComponent
|
||||||
|
providerProtocol.enforceRoutes = true
|
||||||
|
// macos seems to have trouble when this is true, and reports are that this breaks and
|
||||||
|
// doesn't do what it says on the tin in the first place. Needs more testing.
|
||||||
|
providerProtocol.includeAllNetworks = false
|
||||||
|
self.vpnManager.protocolConfiguration = providerProtocol
|
||||||
|
self.vpnManager.isEnabled = true
|
||||||
|
// self.vpnManager.isOnDemandEnabled = true
|
||||||
|
self.vpnManager.localizedDescription = "lokinet"
|
||||||
|
self.vpnManager.saveToPreferences(completionHandler: { error -> Void in
|
||||||
|
if error != nil {
|
||||||
|
NSLog("Error saving to preferences")
|
||||||
|
NSLog(error!.localizedDescription)
|
||||||
|
bail()
|
||||||
|
} else {
|
||||||
|
self.vpnManager.loadFromPreferences(completionHandler: { error in
|
||||||
|
if error != nil {
|
||||||
|
NSLog("Error loading from preferences")
|
||||||
|
NSLog(error!.localizedDescription)
|
||||||
|
bail()
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
NSLog("Trying to start")
|
||||||
|
self.initializeConnectionObserver()
|
||||||
|
try self.vpnManager.connection.startVPNTunnel()
|
||||||
|
} catch let error as NSError {
|
||||||
|
NSLog(error.localizedDescription)
|
||||||
|
bail()
|
||||||
|
} catch {
|
||||||
|
NSLog("There was a fatal error")
|
||||||
|
bail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializeConnectionObserver() {
|
||||||
|
NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: vpnManager.connection, queue: OperationQueue.main) { _ -> Void in
|
||||||
|
if self.vpnManager.connection.status == .invalid {
|
||||||
|
NSLog("VPN configuration is invalid")
|
||||||
|
} else if self.vpnManager.connection.status == .disconnected {
|
||||||
|
NSLog("VPN is disconnected.")
|
||||||
|
} else if self.vpnManager.connection.status == .connecting {
|
||||||
|
NSLog("VPN is connecting...")
|
||||||
|
} else if self.vpnManager.connection.status == .reasserting {
|
||||||
|
NSLog("VPN is reasserting...")
|
||||||
|
} else if self.vpnManager.connection.status == .disconnecting {
|
||||||
|
NSLog("VPN is disconnecting...")
|
||||||
|
} else if self.vpnManager.connection.status == .connected {
|
||||||
|
NSLog("VPN Connected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let delegate = LokinetMain()
|
||||||
|
app.delegate = delegate
|
||||||
|
app.run()
|
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
# 3.13+ so that we can add link libraries to parent targets
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
if (BUILD_SHARED_LIBS OR NOT BUILD_STATIC_DEPS OR NOT STATIC_LINK)
|
||||||
|
message(FATAL_ERROR "macOS builds require a full static build; perhaps use the contrib/macos.sh script to build?")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# god made apple so that man may suffer
|
||||||
|
|
||||||
|
find_library(FOUNDATION Foundation REQUIRED)
|
||||||
|
find_library(NETEXT NetworkExtension REQUIRED)
|
||||||
|
find_library(COREFOUNDATION CoreFoundation REQUIRED)
|
||||||
|
|
||||||
|
target_sources(lokinet-util PRIVATE apple_logger.cpp)
|
||||||
|
target_link_libraries(lokinet-util PUBLIC ${FOUNDATION})
|
||||||
|
|
||||||
|
target_sources(lokinet-platform PRIVATE vpn_platform.cpp vpn_interface.cpp route_manager.cpp context_wrapper.cpp)
|
||||||
|
|
||||||
|
add_executable(lokinet-extension MACOSX_BUNDLE
|
||||||
|
PacketTunnelProvider.m
|
||||||
|
DNSTrampoline.m
|
||||||
|
)
|
||||||
|
enable_lto(lokinet-extension)
|
||||||
|
target_link_libraries(lokinet-extension PRIVATE
|
||||||
|
liblokinet
|
||||||
|
${COREFOUNDATION}
|
||||||
|
${NETEXT})
|
||||||
|
|
||||||
|
# Not sure what -fapplication-extension does, but XCode puts it in so...
|
||||||
|
# -fobjc-arc enables automatic reference counting for objective-C code
|
||||||
|
# -e _NSExtensionMain because the appex has that instead of a `main` function entry point, of course.
|
||||||
|
target_compile_options(lokinet-extension PRIVATE -fapplication-extension -fobjc-arc)
|
||||||
|
target_link_options(lokinet-extension PRIVATE -fapplication-extension -e _NSExtensionMain)
|
||||||
|
|
||||||
|
target_link_libraries(lokinet-extension PUBLIC
|
||||||
|
liblokinet
|
||||||
|
${COREFOUNDATION}
|
||||||
|
${NETEXT})
|
||||||
|
|
||||||
|
set_target_properties(lokinet-extension PROPERTIES
|
||||||
|
BUNDLE TRUE
|
||||||
|
BUNDLE_EXTENSION appex
|
||||||
|
MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/contrib/macos/LokinetExtension.Info.plist.in
|
||||||
|
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(TARGET lokinet-extension
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.provisionprofile
|
||||||
|
$<TARGET_BUNDLE_DIR:lokinet-extension>/Contents/embedded.provisionprofile
|
||||||
|
)
|
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <uv.h>
|
||||||
|
#include <NetworkExtension/NetworkExtension.h>
|
||||||
|
|
||||||
|
extern NSString* error_domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Trampoline" class that listens for UDP DNS packets on port 1053 coming from lokinet's embedded
|
||||||
|
* libunbound (when exit mode is enabled), wraps them via NetworkExtension's crappy UDP API, then
|
||||||
|
* sends responses back to libunbound to be parsed/etc. This class knows nothing about DNS, it is
|
||||||
|
* basically just a UDP packet forwarder.
|
||||||
|
*
|
||||||
|
* So for a lokinet configuration of "upstream=1.1.1.1", when exit mode is OFF:
|
||||||
|
* - DNS requests go to TUNNELIP:53, get sent to libunbound, which forwards them (directly) to the
|
||||||
|
* upstream DNS server(s).
|
||||||
|
* With exit mode ON:
|
||||||
|
* - DNS requests go to TUNNELIP:53, get send to libunbound, which forwards them to 127.0.0.1:1053,
|
||||||
|
* which encapsulates them in Apple's god awful crap, then (on a response) sends them back to
|
||||||
|
* libunbound.
|
||||||
|
* (This assumes a non-lokinet DNS; .loki and .snode get handled before either of these).
|
||||||
|
*/
|
||||||
|
@interface LLARPDNSTrampoline : NSObject
|
||||||
|
{
|
||||||
|
// The socket libunbound talks with:
|
||||||
|
uv_udp_t request_socket;
|
||||||
|
// The reply address. This is a bit hacky: we configure libunbound to just use single address
|
||||||
|
// (rather than a range) so that we don't have to worry about tracking different reply addresses.
|
||||||
|
@public
|
||||||
|
struct sockaddr reply_addr;
|
||||||
|
// UDP "session" aimed at the upstream DNS
|
||||||
|
@public
|
||||||
|
NWUDPSession* upstream;
|
||||||
|
// Apple docs say writes could take time *and* the crappy Apple datagram write methods aren't
|
||||||
|
// callable again until the previous write finishes. Deal with this garbage API by queuing
|
||||||
|
// everything than using a uv_async to process the queue.
|
||||||
|
@public
|
||||||
|
int write_ready;
|
||||||
|
@public
|
||||||
|
NSMutableArray<NSData*>* pending_writes;
|
||||||
|
uv_async_t write_trigger;
|
||||||
|
}
|
||||||
|
- (void)startWithUpstreamDns:(NWUDPSession*)dns
|
||||||
|
listenPort:(uint16_t)listenPort
|
||||||
|
uvLoop:(uv_loop_t*)loop
|
||||||
|
completionHandler:(void (^)(NSError* error))completionHandler;
|
||||||
|
|
||||||
|
- (void)flushWrites;
|
||||||
|
|
||||||
|
- (void)dealloc;
|
||||||
|
|
||||||
|
@end
|
@ -0,0 +1,136 @@
|
|||||||
|
#include "DNSTrampoline.h"
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
|
NSString* error_domain = @"com.loki-project.lokinet";
|
||||||
|
|
||||||
|
|
||||||
|
// Receiving an incoming packet, presumably from libunbound. NB: this is called from the libuv
|
||||||
|
// event loop.
|
||||||
|
static void on_request(uv_udp_t* socket, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) {
|
||||||
|
if (nread < 0) {
|
||||||
|
NSLog(@"Read error: %s", uv_strerror(nread));
|
||||||
|
free(buf->base);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nread == 0 || !addr) {
|
||||||
|
if (buf)
|
||||||
|
free(buf->base);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*) socket->data;
|
||||||
|
|
||||||
|
// We configure libunbound to use just one single port so we'll just send replies to the last port
|
||||||
|
// to talk to us. (And we're only listening on localhost in the first place).
|
||||||
|
t->reply_addr = *addr;
|
||||||
|
|
||||||
|
// NSData takes care of calling free(buf->base) for us with this constructor:
|
||||||
|
[t->pending_writes addObject:[NSData dataWithBytesNoCopy:buf->base length:nread]];
|
||||||
|
|
||||||
|
[t flushWrites];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_sent(uv_udp_send_t* req, int status) {
|
||||||
|
NSArray<NSData*>* datagrams = (__bridge_transfer NSArray<NSData*>*) req->data;
|
||||||
|
free(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: called from the libuv event loop (so we don't have to worry about the above and this one
|
||||||
|
// running at once from different threads).
|
||||||
|
static void write_flusher(uv_async_t* async) {
|
||||||
|
LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*) async->data;
|
||||||
|
if (t->pending_writes.count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NSArray<NSData*>* data = [NSArray<NSData*> arrayWithArray:t->pending_writes];
|
||||||
|
[t->pending_writes removeAllObjects];
|
||||||
|
__weak LLARPDNSTrampoline* weakSelf = t;
|
||||||
|
[t->upstream writeMultipleDatagrams:data completionHandler: ^(NSError* error)
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
NSLog(@"Failed to send request to upstream DNS: %@", error);
|
||||||
|
|
||||||
|
// Trigger another flush in case anything built up while Apple was doing its things. Just
|
||||||
|
// call it unconditionally (rather than checking the queue) because this handler is probably
|
||||||
|
// running in some other thread.
|
||||||
|
[weakSelf flushWrites];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
|
||||||
|
buf->base = malloc(suggested_size);
|
||||||
|
buf->len = suggested_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@implementation LLARPDNSTrampoline
|
||||||
|
|
||||||
|
- (void)startWithUpstreamDns:(NWUDPSession*) dns
|
||||||
|
listenPort:(uint16_t) listenPort
|
||||||
|
uvLoop:(uv_loop_t*) loop
|
||||||
|
completionHandler:(void (^)(NSError* error))completionHandler
|
||||||
|
{
|
||||||
|
pending_writes = [[NSMutableArray<NSData*> alloc] init];
|
||||||
|
write_trigger.data = (__bridge void*) self;
|
||||||
|
uv_async_init(loop, &write_trigger, write_flusher);
|
||||||
|
|
||||||
|
request_socket.data = (__bridge void*) self;
|
||||||
|
uv_udp_init(loop, &request_socket);
|
||||||
|
struct sockaddr_in recv_addr;
|
||||||
|
uv_ip4_addr("127.0.0.1", listenPort, &recv_addr);
|
||||||
|
int ret = uv_udp_bind(&request_socket, (const struct sockaddr*) &recv_addr, UV_UDP_REUSEADDR);
|
||||||
|
if (ret < 0) {
|
||||||
|
NSString* errstr = [NSString stringWithFormat:@"Failed to start DNS trampoline: %s", uv_strerror(ret)];
|
||||||
|
NSError *err = [NSError errorWithDomain:error_domain code:ret userInfo:@{@"Error": errstr}];
|
||||||
|
NSLog(@"%@", err);
|
||||||
|
return completionHandler(err);
|
||||||
|
}
|
||||||
|
uv_udp_recv_start(&request_socket, alloc_buffer, on_request);
|
||||||
|
|
||||||
|
NSLog(@"Starting DNS trampoline");
|
||||||
|
|
||||||
|
upstream = dns;
|
||||||
|
__weak LLARPDNSTrampoline* weakSelf = self;
|
||||||
|
[upstream setReadHandler: ^(NSArray<NSData*>* datagrams, NSError* error) {
|
||||||
|
// Reading a reply back from the UDP socket used to talk to upstream
|
||||||
|
if (error) {
|
||||||
|
NSLog(@"Reader handler failed: %@", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LLARPDNSTrampoline* strongSelf = weakSelf;
|
||||||
|
if (!strongSelf || datagrams.count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uv_buf_t* buffers = malloc(datagrams.count * sizeof(uv_buf_t));
|
||||||
|
size_t buf_count = 0;
|
||||||
|
for (NSData* packet in datagrams) {
|
||||||
|
buffers[buf_count].base = (void*) packet.bytes;
|
||||||
|
buffers[buf_count].len = packet.length;
|
||||||
|
buf_count++;
|
||||||
|
}
|
||||||
|
uv_udp_send_t* uvsend = malloc(sizeof(uv_udp_send_t));
|
||||||
|
uvsend->data = (__bridge_retained void*) datagrams;
|
||||||
|
int ret = uv_udp_send(uvsend, &strongSelf->request_socket, buffers, buf_count, &strongSelf->reply_addr, on_sent);
|
||||||
|
free(buffers);
|
||||||
|
if (ret < 0)
|
||||||
|
NSLog(@"Error returning DNS responses to unbound: %s", uv_strerror(ret));
|
||||||
|
} maxDatagrams:NSUIntegerMax];
|
||||||
|
|
||||||
|
completionHandler(nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)flushWrites
|
||||||
|
{
|
||||||
|
uv_async_send(&write_trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) dealloc
|
||||||
|
{
|
||||||
|
NSLog(@"Stopping DNS trampoline");
|
||||||
|
uv_close((uv_handle_t*) &request_socket, NULL);
|
||||||
|
uv_close((uv_handle_t*) &write_trigger, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
@ -0,0 +1,310 @@
|
|||||||
|
#include <Foundation/Foundation.h>
|
||||||
|
#include <NetworkExtension/NetworkExtension.h>
|
||||||
|
#include "context_wrapper.h"
|
||||||
|
#include "DNSTrampoline.h"
|
||||||
|
|
||||||
|
@interface LLARPPacketTunnel : NEPacketTunnelProvider
|
||||||
|
{
|
||||||
|
void* lokinet;
|
||||||
|
@public NEPacketTunnelNetworkSettings* settings;
|
||||||
|
@public NEIPv4Route* tun_route4;
|
||||||
|
@public NEIPv6Route* tun_route6;
|
||||||
|
LLARPDNSTrampoline* dns_tramp;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)startTunnelWithOptions:(NSDictionary<NSString*, NSObject*>*)options
|
||||||
|
completionHandler:(void (^)(NSError* error))completionHandler;
|
||||||
|
|
||||||
|
- (void)stopTunnelWithReason:(NEProviderStopReason)reason
|
||||||
|
completionHandler:(void (^)(void))completionHandler;
|
||||||
|
|
||||||
|
- (void)handleAppMessage:(NSData*)messageData
|
||||||
|
completionHandler:(void (^)(NSData* responseData))completionHandler;
|
||||||
|
|
||||||
|
- (void)readPackets;
|
||||||
|
|
||||||
|
- (void)updateNetworkSettings;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
static void nslogger(const char* msg) { NSLog(@"%s", msg); }
|
||||||
|
|
||||||
|
static void packet_writer(int af, const void* data, size_t size, void* ctx) {
|
||||||
|
if (ctx == nil || data == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NSData* buf = [NSData dataWithBytesNoCopy:(void*)data length:size freeWhenDone:NO];
|
||||||
|
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
|
||||||
|
NEPacket* packet = [[NEPacket alloc] initWithData:buf protocolFamily: af];
|
||||||
|
[t.packetFlow writePacketObjects:@[packet]];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void start_packet_reader(void* ctx) {
|
||||||
|
if (ctx == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
|
||||||
|
[t readPackets];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_ipv4_route(const char* addr, const char* netmask, void* ctx) {
|
||||||
|
NEIPv4Route* route = [[NEIPv4Route alloc]
|
||||||
|
initWithDestinationAddress: [NSString stringWithUTF8String:addr]
|
||||||
|
subnetMask: [NSString stringWithUTF8String:netmask]];
|
||||||
|
|
||||||
|
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
|
||||||
|
for (NEIPv4Route* r in t->settings.IPv4Settings.includedRoutes)
|
||||||
|
if ([r.destinationAddress isEqualToString:route.destinationAddress] &&
|
||||||
|
[r.destinationSubnetMask isEqualToString:route.destinationSubnetMask])
|
||||||
|
return; // Already in the settings, nothing to add.
|
||||||
|
|
||||||
|
t->settings.IPv4Settings.includedRoutes =
|
||||||
|
[t->settings.IPv4Settings.includedRoutes arrayByAddingObject:route];
|
||||||
|
|
||||||
|
[t updateNetworkSettings];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void del_ipv4_route(const char* addr, const char* netmask, void* ctx) {
|
||||||
|
NEIPv4Route* route = [[NEIPv4Route alloc]
|
||||||
|
initWithDestinationAddress: [NSString stringWithUTF8String:addr]
|
||||||
|
subnetMask: [NSString stringWithUTF8String:netmask]];
|
||||||
|
|
||||||
|
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
|
||||||
|
NSMutableArray<NEIPv4Route*>* routes = [NSMutableArray arrayWithArray:t->settings.IPv4Settings.includedRoutes];
|
||||||
|
for (int i = 0; i < routes.count; i++) {
|
||||||
|
if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] &&
|
||||||
|
[routes[i].destinationSubnetMask isEqualToString:route.destinationSubnetMask]) {
|
||||||
|
[routes removeObjectAtIndex:i];
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routes.count != t->settings.IPv4Settings.includedRoutes.count) {
|
||||||
|
t->settings.IPv4Settings.includedRoutes = routes;
|
||||||
|
[t updateNetworkSettings];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_ipv6_route(const char* addr, int prefix, void* ctx) {
|
||||||
|
NEIPv6Route* route = [[NEIPv6Route alloc]
|
||||||
|
initWithDestinationAddress: [NSString stringWithUTF8String:addr]
|
||||||
|
networkPrefixLength: [NSNumber numberWithInt:prefix]];
|
||||||
|
|
||||||
|
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
|
||||||
|
for (NEIPv6Route* r in t->settings.IPv6Settings.includedRoutes)
|
||||||
|
if ([r.destinationAddress isEqualToString:route.destinationAddress] &&
|
||||||
|
[r.destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength])
|
||||||
|
return; // Already in the settings, nothing to add.
|
||||||
|
|
||||||
|
t->settings.IPv6Settings.includedRoutes =
|
||||||
|
[t->settings.IPv6Settings.includedRoutes arrayByAddingObject:route];
|
||||||
|
|
||||||
|
[t updateNetworkSettings];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void del_ipv6_route(const char* addr, int prefix, void* ctx) {
|
||||||
|
NEIPv6Route* route = [[NEIPv6Route alloc]
|
||||||
|
initWithDestinationAddress: [NSString stringWithUTF8String:addr]
|
||||||
|
networkPrefixLength: [NSNumber numberWithInt:prefix]];
|
||||||
|
|
||||||
|
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
|
||||||
|
NSMutableArray<NEIPv6Route*>* routes = [NSMutableArray arrayWithArray:t->settings.IPv6Settings.includedRoutes];
|
||||||
|
for (int i = 0; i < routes.count; i++) {
|
||||||
|
if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] &&
|
||||||
|
[routes[i].destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength]) {
|
||||||
|
[routes removeObjectAtIndex:i];
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routes.count != t->settings.IPv6Settings.includedRoutes.count) {
|
||||||
|
t->settings.IPv6Settings.includedRoutes = routes;
|
||||||
|
[t updateNetworkSettings];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_default_route(void* ctx) {
|
||||||
|
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
|
||||||
|
|
||||||
|
t->settings.IPv4Settings.includedRoutes = @[NEIPv4Route.defaultRoute];
|
||||||
|
t->settings.IPv6Settings.includedRoutes = @[NEIPv6Route.defaultRoute];
|
||||||
|
|
||||||
|
[t updateNetworkSettings];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void del_default_route(void* ctx) {
|
||||||
|
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
|
||||||
|
|
||||||
|
t->settings.IPv4Settings.includedRoutes = @[t->tun_route4];
|
||||||
|
t->settings.IPv6Settings.includedRoutes = @[t->tun_route6];
|
||||||
|
|
||||||
|
[t updateNetworkSettings];
|
||||||
|
}
|
||||||
|
|
||||||
|
@implementation LLARPPacketTunnel
|
||||||
|
|
||||||
|
- (void)readPackets
|
||||||
|
{
|
||||||
|
[self.packetFlow readPacketObjectsWithCompletionHandler: ^(NSArray<NEPacket*>* packets) {
|
||||||
|
if (lokinet == nil)
|
||||||
|
return;
|
||||||
|
for (NEPacket* p in packets) {
|
||||||
|
llarp_apple_incoming(lokinet, p.data.bytes, p.data.length);
|
||||||
|
}
|
||||||
|
[self readPackets];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)startTunnelWithOptions:(NSDictionary<NSString*, NSObject*>*)options
|
||||||
|
completionHandler:(void (^)(NSError*))completionHandler
|
||||||
|
{
|
||||||
|
NSString* default_bootstrap = [NSBundle.mainBundle pathForResource:@"bootstrap" ofType:@"signed"];
|
||||||
|
NSString* home = NSHomeDirectory();
|
||||||
|
|
||||||
|
llarp_apple_config conf = {
|
||||||
|
.config_dir = home.UTF8String,
|
||||||
|
.default_bootstrap = default_bootstrap.UTF8String,
|
||||||
|
.ns_logger = nslogger,
|
||||||
|
.packet_writer = packet_writer,
|
||||||
|
.start_reading = start_packet_reader,
|
||||||
|
.route_callbacks = {
|
||||||
|
.add_ipv4_route = add_ipv4_route,
|
||||||
|
.del_ipv4_route = del_ipv4_route,
|
||||||
|
.add_ipv6_route = add_ipv6_route,
|
||||||
|
.del_ipv6_route = del_ipv6_route,
|
||||||
|
.add_default_route = add_default_route,
|
||||||
|
.del_default_route = del_default_route
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
lokinet = llarp_apple_init(&conf);
|
||||||
|
if (!lokinet) {
|
||||||
|
NSError *init_failure = [NSError errorWithDomain:error_domain code:500 userInfo:@{@"Error": @"Failed to initialize lokinet"}];
|
||||||
|
NSLog(@"%@", [init_failure localizedDescription]);
|
||||||
|
return completionHandler(init_failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* ip = [NSString stringWithUTF8String:conf.tunnel_ipv4_ip];
|
||||||
|
NSString* mask = [NSString stringWithUTF8String:conf.tunnel_ipv4_netmask];
|
||||||
|
|
||||||
|
// We don't have a fixed address so just stick some bogus value here:
|
||||||
|
settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.3.2.1"];
|
||||||
|
|
||||||
|
NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[ip]];
|
||||||
|
dns.domainName = @"localhost.loki";
|
||||||
|
dns.matchDomains = @[@""];
|
||||||
|
// In theory, matchDomains is supposed to be set to DNS suffixes that we resolve. This seems
|
||||||
|
// highly unreliable, though: often it just doesn't work at all (perhaps only if we make ourselves
|
||||||
|
// the default route?), and even when it does work, it seems there are secret reasons that some
|
||||||
|
// domains (such as instagram.com) still won't work because there's some magic sauce in the OS
|
||||||
|
// that Apple engineers don't want to disclose ("This is what I expected, actually. Although I
|
||||||
|
// will not comment on what I believe is happening here", from
|
||||||
|
// https://developer.apple.com/forums/thread/685410).
|
||||||
|
//
|
||||||
|
// So the documentation sucks and the feature doesn't appear to work, so as much as it would be
|
||||||
|
// nice to capture only .loki and .snode when not in exit mode, we can't, so capture everything
|
||||||
|
// and use our default upstream.
|
||||||
|
dns.matchDomains = @[@""];
|
||||||
|
dns.matchDomainsNoSearch = true;
|
||||||
|
dns.searchDomains = @[];
|
||||||
|
settings.DNSSettings = dns;
|
||||||
|
|
||||||
|
NWHostEndpoint* upstreamdns_ep;
|
||||||
|
if (strlen(conf.upstream_dns))
|
||||||
|
upstreamdns_ep = [NWHostEndpoint endpointWithHostname:[NSString stringWithUTF8String:conf.upstream_dns] port:@(conf.upstream_dns_port).stringValue];
|
||||||
|
|
||||||
|
NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[ip]
|
||||||
|
subnetMasks:@[mask]];
|
||||||
|
tun_route4 = [[NEIPv4Route alloc] initWithDestinationAddress:ip subnetMask: mask];
|
||||||
|
ipv4.includedRoutes = @[tun_route4];
|
||||||
|
settings.IPv4Settings = ipv4;
|
||||||
|
|
||||||
|
NSString* ip6 = [NSString stringWithUTF8String:conf.tunnel_ipv6_ip];
|
||||||
|
NSNumber* ip6_prefix = [NSNumber numberWithUnsignedInt:conf.tunnel_ipv6_prefix];
|
||||||
|
NEIPv6Settings* ipv6 = [[NEIPv6Settings alloc] initWithAddresses:@[ip6]
|
||||||
|
networkPrefixLengths:@[ip6_prefix]];
|
||||||
|
tun_route6 = [[NEIPv6Route alloc] initWithDestinationAddress:ip6
|
||||||
|
networkPrefixLength:ip6_prefix];
|
||||||
|
ipv6.includedRoutes = @[tun_route6];
|
||||||
|
settings.IPv6Settings = ipv6;
|
||||||
|
|
||||||
|
__weak LLARPPacketTunnel* weakSelf = self;
|
||||||
|
[self setTunnelNetworkSettings:settings completionHandler:^(NSError* err) {
|
||||||
|
if (err) {
|
||||||
|
NSLog(@"Failed to configure lokinet tunnel: %@", err);
|
||||||
|
return completionHandler(err);
|
||||||
|
}
|
||||||
|
LLARPPacketTunnel* strongSelf = weakSelf;
|
||||||
|
if (!strongSelf)
|
||||||
|
return completionHandler(nil);
|
||||||
|
|
||||||
|
int start_ret = llarp_apple_start(strongSelf->lokinet, (__bridge void*) strongSelf);
|
||||||
|
if (start_ret != 0) {
|
||||||
|
NSError *start_failure = [NSError errorWithDomain:error_domain code:start_ret userInfo:@{@"Error": @"Failed to start lokinet"}];
|
||||||
|
NSLog(@"%@", start_failure);
|
||||||
|
lokinet = nil;
|
||||||
|
return completionHandler(start_failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSLog(@"Starting DNS exit mode trampoline to %@ on 127.0.0.1:%d", upstreamdns_ep, dns_trampoline_port);
|
||||||
|
NWUDPSession* upstreamdns = [strongSelf createUDPSessionThroughTunnelToEndpoint:upstreamdns_ep fromEndpoint:nil];
|
||||||
|
strongSelf->dns_tramp = [LLARPDNSTrampoline alloc];
|
||||||
|
[strongSelf->dns_tramp
|
||||||
|
startWithUpstreamDns:upstreamdns
|
||||||
|
listenPort:dns_trampoline_port
|
||||||
|
uvLoop:llarp_apple_get_uv_loop(strongSelf->lokinet)
|
||||||
|
completionHandler:^(NSError* error) {
|
||||||
|
if (error)
|
||||||
|
NSLog(@"Error starting dns trampoline: %@", error);
|
||||||
|
return completionHandler(error);
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)stopTunnelWithReason:(NEProviderStopReason)reason
|
||||||
|
completionHandler:(void (^)(void))completionHandler
|
||||||
|
{
|
||||||
|
if (lokinet) {
|
||||||
|
llarp_apple_shutdown(lokinet);
|
||||||
|
lokinet = nil;
|
||||||
|
}
|
||||||
|
completionHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)handleAppMessage:(NSData*)messageData
|
||||||
|
completionHandler:(void (^)(NSData* responseData))completionHandler
|
||||||
|
{
|
||||||
|
NSData* response = [NSData dataWithBytesNoCopy:"ok" length:3 freeWhenDone:NO];
|
||||||
|
completionHandler(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateNetworkSettings
|
||||||
|
{
|
||||||
|
self.reasserting = YES;
|
||||||
|
__weak LLARPPacketTunnel* weakSelf = self;
|
||||||
|
// Apple documentation says that setting network settings to nil isn't required before setting it
|
||||||
|
// to a new value. Apple lies: both end up with a routing table that looks exactly the same (from
|
||||||
|
// both `netstat -rn` and from everything that happens in `route -n monitor`), but if we don't
|
||||||
|
// call with nil first then everything fails to route to either lokinet *and* clearnet through the
|
||||||
|
// exit, so there is apparently some special magic internal Apple state that actually *does*
|
||||||
|
// require the tunnel settings being reset with nil first.
|
||||||
|
//
|
||||||
|
// Thanks for the accurate documentation, Apple.
|
||||||
|
//
|
||||||
|
[self setTunnelNetworkSettings:nil completionHandler:^(NSError* err) {
|
||||||
|
if (err)
|
||||||
|
NSLog(@"Failed to clear lokinet tunnel settings: %@", err);
|
||||||
|
LLARPPacketTunnel* strongSelf = weakSelf;
|
||||||
|
if (strongSelf) {
|
||||||
|
[weakSelf setTunnelNetworkSettings:strongSelf->settings completionHandler:^(NSError* err) {
|
||||||
|
LLARPPacketTunnel* strongSelf = weakSelf;
|
||||||
|
if (strongSelf)
|
||||||
|
strongSelf.reasserting = NO;
|
||||||
|
if (err)
|
||||||
|
NSLog(@"Failed to reconfigure lokinet tunnel settings: %@", err);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
@ -0,0 +1,25 @@
|
|||||||
|
#include "apple_logger.hpp"
|
||||||
|
|
||||||
|
namespace llarp::apple
|
||||||
|
{
|
||||||
|
void
|
||||||
|
NSLogStream::PreLog(
|
||||||
|
std::stringstream& ss,
|
||||||
|
LogLevel lvl,
|
||||||
|
const char* fname,
|
||||||
|
int lineno,
|
||||||
|
const std::string& nodename) const
|
||||||
|
{
|
||||||
|
ss << "[" << LogLevelToString(lvl) << "] ";
|
||||||
|
ss << "[" << nodename << "]"
|
||||||
|
<< "(" << thread_id_string() << ") " << log_timestamp() << " " << fname << ":" << lineno
|
||||||
|
<< "\t";
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
NSLogStream::Print(LogLevel, const char*, const std::string& msg)
|
||||||
|
{
|
||||||
|
ns_logger(msg.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace llarp::apple
|
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <llarp/util/logging/logger.hpp>
|
||||||
|
#include <llarp/util/logging/logstream.hpp>
|
||||||
|
|
||||||
|
namespace llarp::apple
|
||||||
|
{
|
||||||
|
struct NSLogStream : public ILogStream
|
||||||
|
{
|
||||||
|
using ns_logger_callback = void (*)(const char* log_this);
|
||||||
|
|
||||||
|
NSLogStream(ns_logger_callback logger) : ns_logger{logger}
|
||||||
|
{}
|
||||||
|
|
||||||
|
void
|
||||||
|
PreLog(
|
||||||
|
std::stringstream& s,
|
||||||
|
LogLevel lvl,
|
||||||
|
const char* fname,
|
||||||
|
int lineno,
|
||||||
|
const std::string& nodename) const override;
|
||||||
|
|
||||||
|
void
|
||||||
|
Print(LogLevel lvl, const char* tag, const std::string& msg) override;
|
||||||
|
|
||||||
|
void
|
||||||
|
PostLog(std::stringstream& ss) const override
|
||||||
|
{}
|
||||||
|
|
||||||
|
void
|
||||||
|
ImmediateFlush() override
|
||||||
|
{}
|
||||||
|
|
||||||
|
void Tick(llarp_time_t) override
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ns_logger_callback ns_logger;
|
||||||
|
};
|
||||||
|
} // namespace llarp::apple
|
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <llarp.hpp>
|
||||||
|
#include "vpn_platform.hpp"
|
||||||
|
#include "route_manager.hpp"
|
||||||
|
|
||||||
|
namespace llarp::apple
|
||||||
|
{
|
||||||
|
struct Context : public llarp::Context
|
||||||
|
{
|
||||||
|
std::shared_ptr<vpn::Platform>
|
||||||
|
makeVPNPlatform() override
|
||||||
|
{
|
||||||
|
return std::make_shared<VPNPlatform>(
|
||||||
|
*this, m_PacketWriter, m_OnReadable, route_callbacks, callback_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callbacks that must be set for packet handling *before* calling Setup/Configure/Run; the main
|
||||||
|
// point of these is to get passed through to VPNInterface, which will be called during setup,
|
||||||
|
// after construction.
|
||||||
|
VPNInterface::packet_write_callback m_PacketWriter;
|
||||||
|
VPNInterface::on_readable_callback m_OnReadable;
|
||||||
|
llarp_route_callbacks route_callbacks{};
|
||||||
|
void* callback_context = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace llarp::apple
|
@ -0,0 +1,194 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cassert>
|
||||||
|
#include <llarp/net/ip_packet.hpp>
|
||||||
|
#include <llarp/config/config.hpp>
|
||||||
|
#include <llarp/util/fs.hpp>
|
||||||
|
#include <llarp/util/logging/buffer.hpp>
|
||||||
|
#include <uvw/loop.h>
|
||||||
|
#include "vpn_interface.hpp"
|
||||||
|
#include "context_wrapper.h"
|
||||||
|
#include "context.hpp"
|
||||||
|
#include "apple_logger.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// The default 127.0.0.1:53 won't work (because we run unprivileged) so remap it to this (unless
|
||||||
|
// specifically overridden to something else in the config):
|
||||||
|
const llarp::SockAddr DefaultDNSBind{"127.0.0.1:1153"};
|
||||||
|
|
||||||
|
struct instance_data
|
||||||
|
{
|
||||||
|
llarp::apple::Context context;
|
||||||
|
std::thread runner;
|
||||||
|
packet_writer_callback packet_writer;
|
||||||
|
start_reading_callback start_reading;
|
||||||
|
|
||||||
|
std::weak_ptr<llarp::apple::VPNInterface> iface;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
const uint16_t dns_trampoline_port = 1053;
|
||||||
|
|
||||||
|
void*
|
||||||
|
llarp_apple_init(llarp_apple_config* appleconf)
|
||||||
|
{
|
||||||
|
llarp::LogContext::Instance().logStream =
|
||||||
|
std::make_unique<llarp::apple::NSLogStream>(appleconf->ns_logger);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto config_dir = fs::u8path(appleconf->config_dir);
|
||||||
|
auto config = std::make_shared<llarp::Config>(config_dir);
|
||||||
|
fs::path config_path = config_dir / "lokinet.ini";
|
||||||
|
if (!fs::exists(config_path))
|
||||||
|
llarp::ensureConfig(config_dir, config_path, /*overwrite=*/false, /*router=*/false);
|
||||||
|
config->Load(config_path);
|
||||||
|
|
||||||
|
// If no range is specified then go look for a free one, set that in the config, and then return
|
||||||
|
// it to the caller via the char* parameters.
|
||||||
|
auto& range = config->network.m_ifaddr;
|
||||||
|
if (!range.addr.h)
|
||||||
|
{
|
||||||
|
if (auto maybe = llarp::FindFreeRange())
|
||||||
|
range = *maybe;
|
||||||
|
else
|
||||||
|
throw std::runtime_error{"Could not find any free IP range"};
|
||||||
|
}
|
||||||
|
auto addr = llarp::net::TruncateV6(range.addr).ToString();
|
||||||
|
auto mask = llarp::net::TruncateV6(range.netmask_bits).ToString();
|
||||||
|
if (addr.size() > 15 || mask.size() > 15)
|
||||||
|
throw std::runtime_error{"Unexpected non-IPv4 tunnel range configured"};
|
||||||
|
std::strncpy(appleconf->tunnel_ipv4_ip, addr.c_str(), sizeof(appleconf->tunnel_ipv4_ip));
|
||||||
|
std::strncpy(
|
||||||
|
appleconf->tunnel_ipv4_netmask, mask.c_str(), sizeof(appleconf->tunnel_ipv4_netmask));
|
||||||
|
|
||||||
|
// TODO: in the future we want to do this properly with our pubkey (see issue #1705), but that's
|
||||||
|
// going to take a bit more work because we currently can't *get* the (usually) ephemeral pubkey
|
||||||
|
// at this stage of lokinet configuration. So for now we just stick our IPv4 address into it
|
||||||
|
// until #1705 gets implemented.
|
||||||
|
llarp::huint128_t ipv6{
|
||||||
|
llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(range.addr).h}};
|
||||||
|
std::strncpy(
|
||||||
|
appleconf->tunnel_ipv6_ip, ipv6.ToString().c_str(), sizeof(appleconf->tunnel_ipv6_ip));
|
||||||
|
appleconf->tunnel_ipv6_prefix = 48;
|
||||||
|
|
||||||
|
appleconf->upstream_dns[0] = '\0';
|
||||||
|
for (auto& upstream : config->dns.m_upstreamDNS)
|
||||||
|
{
|
||||||
|
if (upstream.isIPv4())
|
||||||
|
{
|
||||||
|
std::strcpy(appleconf->upstream_dns, upstream.hostString().c_str());
|
||||||
|
appleconf->upstream_dns_port = upstream.getPort();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default DNS bind setting just isn't something we can use as a non-root network extension
|
||||||
|
// so remap the default value to a high port unless explicitly set to something else.
|
||||||
|
if (config->dns.m_bind == llarp::SockAddr{"127.0.0.1:53"})
|
||||||
|
config->dns.m_bind = DefaultDNSBind;
|
||||||
|
|
||||||
|
// If no explicit bootstrap then set the system default one included with the app bundle
|
||||||
|
if (config->bootstrap.files.empty())
|
||||||
|
config->bootstrap.files.push_back(fs::u8path(appleconf->default_bootstrap));
|
||||||
|
|
||||||
|
auto inst = std::make_unique<instance_data>();
|
||||||
|
inst->context.Configure(std::move(config));
|
||||||
|
inst->context.route_callbacks = appleconf->route_callbacks;
|
||||||
|
|
||||||
|
inst->packet_writer = appleconf->packet_writer;
|
||||||
|
inst->start_reading = appleconf->start_reading;
|
||||||
|
|
||||||
|
return inst.release();
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LogError("Failed to initialize lokinet from config: ", e.what());
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
llarp_apple_start(void* lokinet, void* callback_context)
|
||||||
|
{
|
||||||
|
auto* inst = static_cast<instance_data*>(lokinet);
|
||||||
|
|
||||||
|
inst->context.callback_context = callback_context;
|
||||||
|
|
||||||
|
inst->context.m_PacketWriter = [inst, callback_context](int af_family, void* data, size_t size) {
|
||||||
|
inst->packet_writer(af_family, data, size, callback_context);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
inst->context.m_OnReadable = [inst, callback_context](llarp::apple::VPNInterface& iface) {
|
||||||
|
inst->iface = iface.weak_from_this();
|
||||||
|
inst->start_reading(callback_context);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::promise<void> result;
|
||||||
|
inst->runner = std::thread{[inst, &result] {
|
||||||
|
const llarp::RuntimeOptions opts{};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
inst->context.Setup(opts);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
result.set_exception(std::current_exception());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.set_value();
|
||||||
|
inst->context.Run(opts);
|
||||||
|
}};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result.get_future().get();
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LogError("Failed to initialize lokinet: ", e.what());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uv_loop_t*
|
||||||
|
llarp_apple_get_uv_loop(void* lokinet)
|
||||||
|
{
|
||||||
|
auto& inst = *static_cast<instance_data*>(lokinet);
|
||||||
|
auto uvw = inst.context.loop->MaybeGetUVWLoop();
|
||||||
|
assert(uvw);
|
||||||
|
return uvw->raw();
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
llarp_apple_incoming(void* lokinet, const void* bytes, size_t size)
|
||||||
|
{
|
||||||
|
auto& inst = *static_cast<instance_data*>(lokinet);
|
||||||
|
|
||||||
|
auto iface = inst.iface.lock();
|
||||||
|
if (!iface)
|
||||||
|
return -2;
|
||||||
|
|
||||||
|
llarp_buffer_t buf{static_cast<const uint8_t*>(bytes), size};
|
||||||
|
if (iface->OfferReadPacket(buf))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
LogError("invalid IP packet: ", llarp::buffer_printer(buf));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
llarp_apple_shutdown(void* lokinet)
|
||||||
|
{
|
||||||
|
auto* inst = static_cast<instance_data*>(lokinet);
|
||||||
|
|
||||||
|
inst->context.CloseAsync();
|
||||||
|
inst->context.Wait();
|
||||||
|
inst->runner.join();
|
||||||
|
delete inst;
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// C-linkage wrappers for interacting with a lokinet context, so that we can call them from Swift
|
||||||
|
// code (which currently doesn't support C++ interoperability at all).
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
|
// Port (on localhost) for our DNS trampoline for bouncing DNS requests through the exit route
|
||||||
|
// when in exit mode.
|
||||||
|
extern const uint16_t dns_trampoline_port;
|
||||||
|
|
||||||
|
/// C callback function for us to invoke when we need to write a packet
|
||||||
|
typedef void (*packet_writer_callback)(int af, const void* data, size_t size, void* ctx);
|
||||||
|
|
||||||
|
/// C callback function to invoke once we are ready to start receiving packets
|
||||||
|
typedef void (*start_reading_callback)(void* ctx);
|
||||||
|
|
||||||
|
/// C callback that bridges things into NSLog
|
||||||
|
typedef void (*ns_logger_callback)(const char* msg);
|
||||||
|
|
||||||
|
/// C callbacks to add/remove specific and default routes to the tunnel
|
||||||
|
typedef void (*llarp_route_ipv4_callback)(const char* addr, const char* netmask, void* ctx);
|
||||||
|
typedef void (*llarp_route_ipv6_callback)(const char* addr, int prefix, void* ctx);
|
||||||
|
typedef void (*llarp_default_route_callback)(void* ctx);
|
||||||
|
typedef struct llarp_route_callbacks
|
||||||
|
{
|
||||||
|
/// Callback invoked to set up an IPv4 range that should be routed through the tunnel
|
||||||
|
/// interface. Called with the address and netmask.
|
||||||
|
llarp_route_ipv4_callback add_ipv4_route;
|
||||||
|
|
||||||
|
/// Callback invoked to set the tunnel as the default IPv4 route.
|
||||||
|
llarp_default_route_callback add_ipv4_default_route;
|
||||||
|
|
||||||
|
/// Callback invoked to remove a specific range from the tunnel IPv4 routes. Called with the
|
||||||
|
/// address and netmask.
|
||||||
|
llarp_route_ipv4_callback del_ipv4_route;
|
||||||
|
|
||||||
|
/// Callback invoked to set up an IPv6 range that should be routed through the tunnel
|
||||||
|
/// interface. Called with the address and netmask.
|
||||||
|
llarp_route_ipv6_callback add_ipv6_route;
|
||||||
|
|
||||||
|
/// Callback invoked to remove a specific range from the tunnel IPv6 routes. Called with the
|
||||||
|
/// address and netmask.
|
||||||
|
llarp_route_ipv6_callback del_ipv6_route;
|
||||||
|
|
||||||
|
/// Callback invoked to set the tunnel as the default IPv4/IPv6 route.
|
||||||
|
llarp_default_route_callback add_default_route;
|
||||||
|
|
||||||
|
/// Callback invoked to remove the tunnel as the default IPv4/IPv6 route.
|
||||||
|
llarp_default_route_callback del_default_route;
|
||||||
|
} llarp_route_callbacks;
|
||||||
|
|
||||||
|
/// Pack of crap to be passed into llarp_apple_init to initialize
|
||||||
|
typedef struct llarp_apple_config
|
||||||
|
{
|
||||||
|
/// lokinet configuration directory, expected to be the application-specific "home" directory,
|
||||||
|
/// which is where state files are stored and the lokinet.ini will be loaded (or created if it
|
||||||
|
/// doesn't exist).
|
||||||
|
const char* config_dir;
|
||||||
|
/// path to the default bootstrap.signed file included in installation, which will be used by
|
||||||
|
/// default when no specific bootstrap is in the config file.
|
||||||
|
const char* default_bootstrap;
|
||||||
|
/// llarp_apple_init writes the IP address for the primary tunnel IP address here,
|
||||||
|
/// null-terminated.
|
||||||
|
char tunnel_ipv4_ip[INET_ADDRSTRLEN];
|
||||||
|
/// llarp_apple_init writes the netmask of the tunnel address here, null-terminated.
|
||||||
|
char tunnel_ipv4_netmask[INET_ADDRSTRLEN];
|
||||||
|
/// Writes the IPv6 address for the tunnel here, null-terminated.
|
||||||
|
char tunnel_ipv6_ip[INET6_ADDRSTRLEN];
|
||||||
|
/// IPv6 address prefix.
|
||||||
|
uint16_t tunnel_ipv6_prefix;
|
||||||
|
|
||||||
|
/// The first upstream DNS server's IPv4 address the OS should use when in exit mode.
|
||||||
|
/// (Currently on mac in exit mode we only support querying the first such configured server).
|
||||||
|
char upstream_dns[INET_ADDRSTRLEN];
|
||||||
|
uint16_t upstream_dns_port;
|
||||||
|
|
||||||
|
/// \defgroup callbacks Callbacks
|
||||||
|
/// Callbacks we invoke for various operations that require glue into the Apple network
|
||||||
|
/// extension APIs. All of these except for ns_logger are passed the pointer provided to
|
||||||
|
/// llarp_apple_start when invoked.
|
||||||
|
/// @{
|
||||||
|
|
||||||
|
/// simple wrapper around NSLog for lokinet message logging
|
||||||
|
ns_logger_callback ns_logger;
|
||||||
|
|
||||||
|
/// C function callback that will be called when we need to write a packet to the packet
|
||||||
|
/// tunnel. Will be passed AF_INET or AF_INET6, a void pointer to the data, and the size of
|
||||||
|
/// the data in bytes.
|
||||||
|
packet_writer_callback packet_writer;
|
||||||
|
|
||||||
|
/// C function callback that will be called when lokinet is setup and ready to start receiving
|
||||||
|
/// packets from the packet tunnel. This should set up the read handler to deliver packets
|
||||||
|
/// via llarp_apple_incoming.
|
||||||
|
start_reading_callback start_reading;
|
||||||
|
|
||||||
|
/// Callbacks invoked to add/remove routes to the tunnel.
|
||||||
|
llarp_route_callbacks route_callbacks;
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
} llarp_apple_config;
|
||||||
|
|
||||||
|
/// Initializes a lokinet instance by initializing various objects and loading the configuration
|
||||||
|
/// (if <config_dir>/lokinet.ini exists). Does not actually start lokinet (call llarp_apple_start
|
||||||
|
/// for that).
|
||||||
|
///
|
||||||
|
/// Returns NULL if there was a problem initializing/loading the configuration, otherwise returns
|
||||||
|
/// an opaque void pointer that should be passed into the other llarp_apple_* functions.
|
||||||
|
///
|
||||||
|
/// \param config pointer to a llarp_apple_config where we get the various settings needed
|
||||||
|
/// and return the ip/mask/dns fields needed for the tunnel.
|
||||||
|
void*
|
||||||
|
llarp_apple_init(llarp_apple_config* config);
|
||||||
|
|
||||||
|
/// Starts the lokinet instance in a new thread.
|
||||||
|
///
|
||||||
|
/// \param lokinet the void pointer returned by llarp_apple_init
|
||||||
|
///
|
||||||
|
/// \param callback_context Opaque pointer that is passed into the various callbacks provided to
|
||||||
|
/// llarp_apple_init. This code does nothing with this pointer aside from passing it through to
|
||||||
|
/// callbacks.
|
||||||
|
///
|
||||||
|
/// \returns 0 on succesful startup, -1 on failure.
|
||||||
|
int
|
||||||
|
llarp_apple_start(void* lokinet, void* callback_context);
|
||||||
|
|
||||||
|
/// Returns a pointer to the uv event loop. Must have called llarp_apple_start already.
|
||||||
|
uv_loop_t*
|
||||||
|
llarp_apple_get_uv_loop(void* lokinet);
|
||||||
|
|
||||||
|
/// Called to deliver an incoming packet from the apple layer into lokinet; returns 0 on success,
|
||||||
|
/// -1 if the packet could not be parsed, -2 if there is no current active VPNInterface associated
|
||||||
|
/// with the lokinet (which generally means llarp_apple_start wasn't called or failed, or lokinet
|
||||||
|
/// is in the process of shutting down).
|
||||||
|
int
|
||||||
|
llarp_apple_incoming(void* lokinet, const void* bytes, size_t size);
|
||||||
|
|
||||||
|
/// Stops a lokinet instance created with `llarp_apple_initialize`. This waits for lokinet to
|
||||||
|
/// shut down and rejoins the thread. After this call the given pointer is no longer valid.
|
||||||
|
void
|
||||||
|
llarp_apple_shutdown(void* lokinet);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // extern "C"
|
||||||
|
#endif
|
@ -0,0 +1,101 @@
|
|||||||
|
#include "route_manager.hpp"
|
||||||
|
#include <llarp/handlers/tun.hpp>
|
||||||
|
#include <llarp/service/context.hpp>
|
||||||
|
#include <llarp.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace llarp::apple
|
||||||
|
{
|
||||||
|
void
|
||||||
|
RouteManager::check_trampoline(bool enable)
|
||||||
|
{
|
||||||
|
if (trampoline_active == enable)
|
||||||
|
return;
|
||||||
|
auto router = context.router;
|
||||||
|
if (!router)
|
||||||
|
{
|
||||||
|
LogError("Cannot reconfigure to use DNS trampoline: no router");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<llarp::handlers::TunEndpoint> tun;
|
||||||
|
router->hiddenServiceContext().ForEachService([&tun](const auto& name, const auto ep) {
|
||||||
|
tun = std::dynamic_pointer_cast<llarp::handlers::TunEndpoint>(ep);
|
||||||
|
return !tun;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tun)
|
||||||
|
{
|
||||||
|
LogError("Cannot reconfigure to use DNS trampoline: no tun endpoint found (!?)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable)
|
||||||
|
saved_upstream_dns =
|
||||||
|
tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, huint16_t{dns_trampoline_port}}});
|
||||||
|
else
|
||||||
|
tun->ReconfigureDNS(std::move(saved_upstream_dns));
|
||||||
|
trampoline_active = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouteManager::AddDefaultRouteViaInterface(std::string)
|
||||||
|
{
|
||||||
|
check_trampoline(true);
|
||||||
|
if (callback_context and route_callbacks.add_default_route)
|
||||||
|
route_callbacks.add_default_route(callback_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouteManager::DelDefaultRouteViaInterface(std::string)
|
||||||
|
{
|
||||||
|
check_trampoline(false);
|
||||||
|
if (callback_context and route_callbacks.del_default_route)
|
||||||
|
route_callbacks.del_default_route(callback_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
RouteManager::AddRouteViaInterface(vpn::NetworkInterface&, IPRange range)
|
||||||
|
{
|
||||||
|
check_trampoline(true);
|
||||||
|
if (callback_context)
|
||||||
|
{
|
||||||
|
if (range.IsV4())
|
||||||
|
{
|
||||||
|
if (route_callbacks.add_ipv4_route)
|
||||||
|
route_callbacks.add_ipv4_route(
|
||||||
|
range.BaseAddressString().c_str(),
|
||||||
|
net::TruncateV6(range.netmask_bits).ToString().c_str(),
|
||||||
|
callback_context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (route_callbacks.add_ipv6_route)
|
||||||
|
route_callbacks.add_ipv6_route(
|
||||||
|
range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
RouteManager::DelRouteViaInterface(vpn::NetworkInterface&, IPRange range)
|
||||||
|
{
|
||||||
|
check_trampoline(false);
|
||||||
|
if (callback_context)
|
||||||
|
{
|
||||||
|
if (range.IsV4())
|
||||||
|
{
|
||||||
|
if (route_callbacks.del_ipv4_route)
|
||||||
|
route_callbacks.del_ipv4_route(
|
||||||
|
range.BaseAddressString().c_str(),
|
||||||
|
net::TruncateV6(range.netmask_bits).ToString().c_str(),
|
||||||
|
callback_context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (route_callbacks.del_ipv6_route)
|
||||||
|
route_callbacks.del_ipv6_route(
|
||||||
|
range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace llarp::apple
|
@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <llarp/router/abstractrouter.hpp>
|
||||||
|
#include <llarp/ev/vpn.hpp>
|
||||||
|
#include "context_wrapper.h"
|
||||||
|
|
||||||
|
namespace llarp::apple
|
||||||
|
{
|
||||||
|
class RouteManager final : public llarp::vpn::IRouteManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RouteManager(llarp::Context& ctx, llarp_route_callbacks rcs, void* callback_context)
|
||||||
|
: context{ctx}, route_callbacks{std::move(rcs)}, callback_context{callback_context}
|
||||||
|
{}
|
||||||
|
|
||||||
|
/// These are called for poking route holes, but we don't have to do that at all on macos
|
||||||
|
/// because the appex isn't subject to its own rules.
|
||||||
|
void
|
||||||
|
AddRoute(IPVariant_t ip, IPVariant_t gateway) override
|
||||||
|
{}
|
||||||
|
|
||||||
|
void
|
||||||
|
DelRoute(IPVariant_t ip, IPVariant_t gateway) override
|
||||||
|
{}
|
||||||
|
|
||||||
|
void
|
||||||
|
AddDefaultRouteViaInterface(std::string ifname) override;
|
||||||
|
|
||||||
|
void
|
||||||
|
DelDefaultRouteViaInterface(std::string ifname) override;
|
||||||
|
|
||||||
|
void
|
||||||
|
AddRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override;
|
||||||
|
|
||||||
|
void
|
||||||
|
DelRouteViaInterface(vpn::NetworkInterface& vpn, IPRange range) override;
|
||||||
|
|
||||||
|
virtual std::vector<IPVariant_t>
|
||||||
|
GetGatewaysNotOnInterface(std::string ifname) override
|
||||||
|
{
|
||||||
|
// We can't get this on mac from our sandbox, but we don't actually need it because we
|
||||||
|
// ignore the gateway for AddRoute/DelRoute anyway, so just return a zero IP.
|
||||||
|
std::vector<IPVariant_t> ret;
|
||||||
|
ret.push_back(huint32_t{0});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
llarp::Context& context;
|
||||||
|
bool trampoline_active = false;
|
||||||
|
std::vector<llarp::SockAddr> saved_upstream_dns;
|
||||||
|
void
|
||||||
|
check_trampoline(bool enable);
|
||||||
|
|
||||||
|
void* callback_context = nullptr;
|
||||||
|
llarp_route_callbacks route_callbacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace llarp::apple
|
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
#include "vpn_interface.hpp"
|
||||||
|
#include "context.hpp"
|
||||||
|
|
||||||
|
namespace llarp::apple
|
||||||
|
{
|
||||||
|
VPNInterface::VPNInterface(
|
||||||
|
Context& ctx, packet_write_callback packet_writer, on_readable_callback on_readable)
|
||||||
|
: m_PacketWriter{std::move(packet_writer)}, m_OnReadable{std::move(on_readable)}
|
||||||
|
{
|
||||||
|
ctx.loop->call_soon([this] { m_OnReadable(*this); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
VPNInterface::OfferReadPacket(const llarp_buffer_t& buf)
|
||||||
|
{
|
||||||
|
llarp::net::IPPacket pkt;
|
||||||
|
if (!pkt.Load(buf))
|
||||||
|
return false;
|
||||||
|
m_ReadQueue.tryPushBack(std::move(pkt));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
VPNInterface::PollFD() const
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
VPNInterface::IfName() const
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
net::IPPacket
|
||||||
|
VPNInterface::ReadNextPacket()
|
||||||
|
{
|
||||||
|
net::IPPacket pkt{};
|
||||||
|
if (not m_ReadQueue.empty())
|
||||||
|
pkt = m_ReadQueue.popFront();
|
||||||
|
return pkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
VPNInterface::WritePacket(net::IPPacket pkt)
|
||||||
|
{
|
||||||
|
int af_family = pkt.IsV6() ? AF_INET6 : AF_INET;
|
||||||
|
return m_PacketWriter(af_family, pkt.buf, pkt.sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace llarp::apple
|
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <llarp.hpp>
|
||||||
|
#include <llarp/ev/vpn.hpp>
|
||||||
|
#include <llarp/util/thread/queue.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace llarp::apple
|
||||||
|
{
|
||||||
|
struct Context;
|
||||||
|
|
||||||
|
class VPNInterface final : public vpn::NetworkInterface,
|
||||||
|
public std::enable_shared_from_this<VPNInterface>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using packet_write_callback = std::function<bool(int af_family, void* data, int size)>;
|
||||||
|
using on_readable_callback = std::function<void(VPNInterface&)>;
|
||||||
|
|
||||||
|
explicit VPNInterface(
|
||||||
|
Context& ctx, packet_write_callback packet_writer, on_readable_callback on_readable);
|
||||||
|
|
||||||
|
// Method to call when a packet has arrived to deliver the packet to lokinet
|
||||||
|
bool
|
||||||
|
OfferReadPacket(const llarp_buffer_t& buf);
|
||||||
|
|
||||||
|
int
|
||||||
|
PollFD() const override;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
IfName() const override;
|
||||||
|
|
||||||
|
net::IPPacket
|
||||||
|
ReadNextPacket() override;
|
||||||
|
|
||||||
|
bool
|
||||||
|
WritePacket(net::IPPacket pkt) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Function for us to call when we have a packet to emit. Should return true if the packet was
|
||||||
|
// handed off to the OS successfully.
|
||||||
|
packet_write_callback m_PacketWriter;
|
||||||
|
|
||||||
|
// Called when we are ready to start reading packets
|
||||||
|
on_readable_callback m_OnReadable;
|
||||||
|
|
||||||
|
static inline constexpr auto PacketQueueSize = 1024;
|
||||||
|
|
||||||
|
thread::Queue<net::IPPacket> m_ReadQueue{PacketQueueSize};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace llarp::apple
|
@ -0,0 +1,22 @@
|
|||||||
|
#include "vpn_platform.hpp"
|
||||||
|
#include "context.hpp"
|
||||||
|
|
||||||
|
namespace llarp::apple
|
||||||
|
{
|
||||||
|
VPNPlatform::VPNPlatform(
|
||||||
|
Context& ctx,
|
||||||
|
VPNInterface::packet_write_callback packet_writer,
|
||||||
|
VPNInterface::on_readable_callback on_readable,
|
||||||
|
llarp_route_callbacks route_callbacks,
|
||||||
|
void* callback_context)
|
||||||
|
: m_Context{ctx}
|
||||||
|
, m_RouteManager{ctx, std::move(route_callbacks), callback_context}
|
||||||
|
, m_PacketWriter{std::move(packet_writer)}
|
||||||
|
, m_OnReadable{std::move(on_readable)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::shared_ptr<vpn::NetworkInterface> VPNPlatform::ObtainInterface(vpn::InterfaceInfo)
|
||||||
|
{
|
||||||
|
return std::make_shared<VPNInterface>(m_Context, m_PacketWriter, m_OnReadable);
|
||||||
|
}
|
||||||
|
} // namespace llarp::apple
|
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <llarp/ev/vpn.hpp>
|
||||||
|
#include "vpn_interface.hpp"
|
||||||
|
#include "route_manager.hpp"
|
||||||
|
|
||||||
|
namespace llarp::apple
|
||||||
|
{
|
||||||
|
class VPNPlatform final : public vpn::Platform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit VPNPlatform(
|
||||||
|
Context& ctx,
|
||||||
|
VPNInterface::packet_write_callback packet_writer,
|
||||||
|
VPNInterface::on_readable_callback on_readable,
|
||||||
|
llarp_route_callbacks route_callbacks,
|
||||||
|
void* callback_context);
|
||||||
|
|
||||||
|
std::shared_ptr<vpn::NetworkInterface> ObtainInterface(vpn::InterfaceInfo) override;
|
||||||
|
|
||||||
|
vpn::IRouteManager&
|
||||||
|
RouteManager() override
|
||||||
|
{
|
||||||
|
return m_RouteManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Context& m_Context;
|
||||||
|
apple::RouteManager m_RouteManager;
|
||||||
|
VPNInterface::packet_write_callback m_PacketWriter;
|
||||||
|
VPNInterface::on_readable_callback m_OnReadable;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace llarp::apple
|
@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#ifndef DEFAULT_RESOLVER_US
|
|
||||||
#define DEFAULT_RESOLVER_US "1.1.1.1"
|
|
||||||
#endif
|
|
||||||
#ifndef DEFAULT_RESOLVER_EU
|
|
||||||
#define DEFAULT_RESOLVER_EU "1.1.1.1"
|
|
||||||
#endif
|
|
||||||
#ifndef DEFAULT_RESOLVER_AU
|
|
||||||
#define DEFAULT_RESOLVER_AU "1.1.1.1"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef DEFAULT_LOKINET_USER
|
|
||||||
#define DEFAULT_LOKINET_USER "lokinet"
|
|
||||||
#endif
|
|
||||||
#ifndef DEFAULT_LOKINET_GROUP
|
|
||||||
#define DEFAULT_LOKINET_GROUP "lokinet"
|
|
||||||
#endif
|
|
@ -1,561 +0,0 @@
|
|||||||
#include "route.hpp"
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <net/if.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#ifndef ANDROID
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <linux/rtnetlink.h>
|
|
||||||
#endif
|
|
||||||
#include "net.hpp"
|
|
||||||
#include <exception>
|
|
||||||
#include <charconv>
|
|
||||||
#endif
|
|
||||||
#ifdef __APPLE__
|
|
||||||
#include "net.hpp"
|
|
||||||
#include <llarp/util/str.hpp>
|
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#include <iphlpapi.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <cstring>
|
|
||||||
#include <locale>
|
|
||||||
#include <codecvt>
|
|
||||||
#include "net_int.hpp"
|
|
||||||
#include "ip.hpp"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <llarp/util/logging/logger.hpp>
|
|
||||||
#include <llarp/util/str.hpp>
|
|
||||||
|
|
||||||
namespace llarp::net
|
|
||||||
{
|
|
||||||
#ifndef __linux__
|
|
||||||
void
|
|
||||||
Execute(std::string cmd)
|
|
||||||
{
|
|
||||||
LogInfo(cmd);
|
|
||||||
system(cmd.c_str());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#ifndef ANDROID
|
|
||||||
|
|
||||||
enum class GatewayMode
|
|
||||||
{
|
|
||||||
eFirstHop,
|
|
||||||
eLowerDefault,
|
|
||||||
eUpperDefault
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NLSocket
|
|
||||||
{
|
|
||||||
NLSocket() : fd(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE))
|
|
||||||
{
|
|
||||||
if (fd == -1)
|
|
||||||
throw std::runtime_error("failed to make netlink socket");
|
|
||||||
}
|
|
||||||
|
|
||||||
~NLSocket()
|
|
||||||
{
|
|
||||||
if (fd != -1)
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int fd;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Helper structure for ip address data and attributes */
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
unsigned char family;
|
|
||||||
unsigned char bitlen;
|
|
||||||
unsigned char data[sizeof(struct in6_addr)];
|
|
||||||
} _inet_addr;
|
|
||||||
|
|
||||||
/* */
|
|
||||||
|
|
||||||
#define NLMSG_TAIL(nmsg) ((struct rtattr*)(((intptr_t)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
|
|
||||||
|
|
||||||
/* Add new data to rtattr */
|
|
||||||
int
|
|
||||||
rtattr_add(struct nlmsghdr* n, unsigned int maxlen, int type, const void* data, int alen)
|
|
||||||
{
|
|
||||||
int len = RTA_LENGTH(alen);
|
|
||||||
struct rtattr* rta;
|
|
||||||
|
|
||||||
if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "rtattr_add error: message exceeded bound of %d\n", maxlen);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
rta = NLMSG_TAIL(n);
|
|
||||||
rta->rta_type = type;
|
|
||||||
rta->rta_len = len;
|
|
||||||
|
|
||||||
if (alen)
|
|
||||||
{
|
|
||||||
memcpy(RTA_DATA(rta), data, alen);
|
|
||||||
}
|
|
||||||
|
|
||||||
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
do_route(
|
|
||||||
int sock,
|
|
||||||
int cmd,
|
|
||||||
int flags,
|
|
||||||
const _inet_addr* dst,
|
|
||||||
const _inet_addr* gw,
|
|
||||||
GatewayMode mode,
|
|
||||||
int if_idx)
|
|
||||||
{
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
struct nlmsghdr n;
|
|
||||||
struct rtmsg r;
|
|
||||||
char buf[4096];
|
|
||||||
} nl_request{};
|
|
||||||
|
|
||||||
/* Initialize request structure */
|
|
||||||
nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
|
||||||
nl_request.n.nlmsg_flags = NLM_F_REQUEST | flags;
|
|
||||||
nl_request.n.nlmsg_type = cmd;
|
|
||||||
nl_request.n.nlmsg_pid = getpid();
|
|
||||||
nl_request.r.rtm_family = dst->family;
|
|
||||||
nl_request.r.rtm_table = RT_TABLE_MAIN;
|
|
||||||
if (if_idx)
|
|
||||||
{
|
|
||||||
nl_request.r.rtm_scope = RT_SCOPE_LINK;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nl_request.r.rtm_scope = RT_SCOPE_NOWHERE;
|
|
||||||
}
|
|
||||||
/* Set additional flags if NOT deleting route */
|
|
||||||
if (cmd != RTM_DELROUTE)
|
|
||||||
{
|
|
||||||
nl_request.r.rtm_protocol = RTPROT_BOOT;
|
|
||||||
nl_request.r.rtm_type = RTN_UNICAST;
|
|
||||||
}
|
|
||||||
|
|
||||||
nl_request.r.rtm_family = dst->family;
|
|
||||||
nl_request.r.rtm_dst_len = dst->bitlen;
|
|
||||||
nl_request.r.rtm_scope = 0;
|
|
||||||
|
|
||||||
/* Set gateway */
|
|
||||||
if (gw->bitlen != 0 and dst->family == AF_INET)
|
|
||||||
{
|
|
||||||
rtattr_add(&nl_request.n, sizeof(nl_request), RTA_GATEWAY, &gw->data, gw->bitlen / 8);
|
|
||||||
}
|
|
||||||
nl_request.r.rtm_family = gw->family;
|
|
||||||
if (mode == GatewayMode::eFirstHop)
|
|
||||||
{
|
|
||||||
rtattr_add(
|
|
||||||
&nl_request.n, sizeof(nl_request), /*RTA_NEWDST*/ RTA_DST, &dst->data, dst->bitlen / 8);
|
|
||||||
/* Set interface */
|
|
||||||
rtattr_add(&nl_request.n, sizeof(nl_request), RTA_OIF, &if_idx, sizeof(int));
|
|
||||||
}
|
|
||||||
if (mode == GatewayMode::eUpperDefault)
|
|
||||||
{
|
|
||||||
if (dst->family == AF_INET)
|
|
||||||
{
|
|
||||||
rtattr_add(
|
|
||||||
&nl_request.n,
|
|
||||||
sizeof(nl_request),
|
|
||||||
/*RTA_NEWDST*/ RTA_DST,
|
|
||||||
&dst->data,
|
|
||||||
sizeof(uint32_t));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rtattr_add(&nl_request.n, sizeof(nl_request), RTA_OIF, &if_idx, sizeof(int));
|
|
||||||
rtattr_add(
|
|
||||||
&nl_request.n,
|
|
||||||
sizeof(nl_request),
|
|
||||||
/*RTA_NEWDST*/ RTA_DST,
|
|
||||||
&dst->data,
|
|
||||||
sizeof(in6_addr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Send message to the netlink */
|
|
||||||
return send(sock, &nl_request, sizeof(nl_request), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
read_addr(const char* addr, _inet_addr* res, int bitlen = 32)
|
|
||||||
{
|
|
||||||
if (strchr(addr, ':'))
|
|
||||||
{
|
|
||||||
res->family = AF_INET6;
|
|
||||||
res->bitlen = bitlen;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
res->family = AF_INET;
|
|
||||||
res->bitlen = bitlen;
|
|
||||||
}
|
|
||||||
return inet_pton(res->family, addr, res->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
|
|
||||||
std::wstring
|
|
||||||
get_win_sys_path()
|
|
||||||
{
|
|
||||||
wchar_t win_sys_path[MAX_PATH] = {0};
|
|
||||||
const wchar_t* default_sys_path = L"C:\\Windows\\system32";
|
|
||||||
|
|
||||||
if (!GetSystemDirectoryW(win_sys_path, _countof(win_sys_path)))
|
|
||||||
{
|
|
||||||
wcsncpy(win_sys_path, default_sys_path, _countof(win_sys_path));
|
|
||||||
win_sys_path[_countof(win_sys_path) - 1] = L'\0';
|
|
||||||
}
|
|
||||||
return win_sys_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string
|
|
||||||
RouteCommand()
|
|
||||||
{
|
|
||||||
std::wstring wcmd = get_win_sys_path() + L"\\route.exe";
|
|
||||||
|
|
||||||
using convert_type = std::codecvt_utf8<wchar_t>;
|
|
||||||
std::wstring_convert<convert_type, wchar_t> converter;
|
|
||||||
return converter.to_bytes(wcmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string
|
|
||||||
NetshCommand()
|
|
||||||
{
|
|
||||||
std::wstring wcmd = get_win_sys_path() + L"\\netsh.exe";
|
|
||||||
|
|
||||||
using convert_type = std::codecvt_utf8<wchar_t>;
|
|
||||||
std::wstring_convert<convert_type, wchar_t> converter;
|
|
||||||
return converter.to_bytes(wcmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Visit>
|
|
||||||
void
|
|
||||||
ForEachWIN32Interface(Visit visit)
|
|
||||||
{
|
|
||||||
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
|
|
||||||
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
|
|
||||||
MIB_IPFORWARDTABLE* pIpForwardTable;
|
|
||||||
DWORD dwSize = 0;
|
|
||||||
DWORD dwRetVal = 0;
|
|
||||||
|
|
||||||
pIpForwardTable = (MIB_IPFORWARDTABLE*)MALLOC(sizeof(MIB_IPFORWARDTABLE));
|
|
||||||
if (pIpForwardTable == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (GetIpForwardTable(pIpForwardTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER)
|
|
||||||
{
|
|
||||||
FREE(pIpForwardTable);
|
|
||||||
pIpForwardTable = (MIB_IPFORWARDTABLE*)MALLOC(dwSize);
|
|
||||||
if (pIpForwardTable == nullptr)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((dwRetVal = GetIpForwardTable(pIpForwardTable, &dwSize, 0)) == NO_ERROR)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < (int)pIpForwardTable->dwNumEntries; i++)
|
|
||||||
{
|
|
||||||
visit(&pIpForwardTable->table[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FREE(pIpForwardTable);
|
|
||||||
#undef MALLOC
|
|
||||||
#undef FREE
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<int>
|
|
||||||
GetInterfaceIndex(huint32_t ip)
|
|
||||||
{
|
|
||||||
std::optional<int> ret = std::nullopt;
|
|
||||||
ForEachWIN32Interface([&ret, n = ToNet(ip)](auto* iface) {
|
|
||||||
if (ret.has_value())
|
|
||||||
return;
|
|
||||||
if (iface->dwForwardNextHop == n.n)
|
|
||||||
{
|
|
||||||
ret = iface->dwForwardIfIndex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void
|
|
||||||
AddRoute(std::string ip, std::string gateway)
|
|
||||||
{
|
|
||||||
LogInfo("Add route: ", ip, " via ", gateway);
|
|
||||||
#ifdef __linux__
|
|
||||||
#ifndef ANDROID
|
|
||||||
NLSocket sock;
|
|
||||||
int if_idx = 0;
|
|
||||||
_inet_addr to_addr{};
|
|
||||||
_inet_addr gw_addr{};
|
|
||||||
int nl_cmd = RTM_NEWROUTE;
|
|
||||||
int nl_flags = NLM_F_CREATE | NLM_F_EXCL;
|
|
||||||
read_addr(gateway.c_str(), &gw_addr);
|
|
||||||
read_addr(ip.c_str(), &to_addr);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eFirstHop, if_idx);
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
std::stringstream ss;
|
|
||||||
#if _WIN32
|
|
||||||
ss << RouteCommand() << " ADD " << ip << " MASK 255.255.255.255 " << gateway << " METRIC 2";
|
|
||||||
#elif __APPLE__
|
|
||||||
ss << "/sbin/route -n add -host " << ip << " " << gateway;
|
|
||||||
#else
|
|
||||||
#error unsupported platform
|
|
||||||
#endif
|
|
||||||
Execute(ss.str());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
DelRoute(std::string ip, std::string gateway)
|
|
||||||
{
|
|
||||||
LogInfo("Delete route: ", ip, " via ", gateway);
|
|
||||||
#ifdef __linux__
|
|
||||||
#ifndef ANDROID
|
|
||||||
NLSocket sock;
|
|
||||||
int if_idx = 0;
|
|
||||||
_inet_addr to_addr{};
|
|
||||||
_inet_addr gw_addr{};
|
|
||||||
int nl_cmd = RTM_DELROUTE;
|
|
||||||
int nl_flags = 0;
|
|
||||||
read_addr(gateway.c_str(), &gw_addr);
|
|
||||||
read_addr(ip.c_str(), &to_addr);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eFirstHop, if_idx);
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
std::stringstream ss;
|
|
||||||
#if _WIN32
|
|
||||||
ss << RouteCommand() << " DELETE " << ip << " MASK 255.255.255.255 " << gateway << " METRIC 2";
|
|
||||||
#elif __APPLE__
|
|
||||||
ss << "/sbin/route -n delete -host " << ip << " " << gateway;
|
|
||||||
#else
|
|
||||||
#error unsupported platform
|
|
||||||
#endif
|
|
||||||
Execute(ss.str());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
AddDefaultRouteViaInterface(std::string ifname)
|
|
||||||
{
|
|
||||||
LogInfo("Add default route via ", ifname);
|
|
||||||
#ifdef __linux__
|
|
||||||
#ifndef ANDROID
|
|
||||||
NLSocket sock;
|
|
||||||
int if_idx = if_nametoindex(ifname.c_str());
|
|
||||||
_inet_addr to_addr{};
|
|
||||||
_inet_addr gw_addr{};
|
|
||||||
const auto maybe = GetInterfaceAddr(ifname);
|
|
||||||
if (not maybe.has_value())
|
|
||||||
throw std::runtime_error("we dont have our own net interface?");
|
|
||||||
int nl_cmd = RTM_NEWROUTE;
|
|
||||||
int nl_flags = NLM_F_CREATE | NLM_F_EXCL;
|
|
||||||
const auto host = maybe->asIPv4().ToString();
|
|
||||||
read_addr(host.c_str(), &gw_addr);
|
|
||||||
read_addr("0.0.0.0", &to_addr, 1);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eLowerDefault, if_idx);
|
|
||||||
read_addr("128.0.0.0", &to_addr, 1);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
|
|
||||||
const auto maybeInt = GetInterfaceIPv6Address(ifname);
|
|
||||||
if (maybeInt.has_value())
|
|
||||||
{
|
|
||||||
const auto host = maybeInt->ToString();
|
|
||||||
LogInfo("add v6 route via ", host);
|
|
||||||
read_addr(host.c_str(), &gw_addr, 128);
|
|
||||||
read_addr("::", &to_addr, 2);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
|
|
||||||
read_addr("4000::", &to_addr, 2);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
|
|
||||||
read_addr("8000::", &to_addr, 2);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
|
|
||||||
read_addr("c000::", &to_addr, 2);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#elif _WIN32
|
|
||||||
// poke hole for loopback bacause god is dead on windows
|
|
||||||
Execute(RouteCommand() + " ADD 127.0.0.0 MASK 255.0.0.0 0.0.0.0");
|
|
||||||
|
|
||||||
huint32_t ip{};
|
|
||||||
ip.FromString(ifname);
|
|
||||||
const auto ipv6 = net::ExpandV4Lan(ip);
|
|
||||||
|
|
||||||
Execute(RouteCommand() + " ADD ::/2 " + ipv6.ToString());
|
|
||||||
Execute(RouteCommand() + " ADD 4000::/2 " + ipv6.ToString());
|
|
||||||
Execute(RouteCommand() + " ADD 8000::/2 " + ipv6.ToString());
|
|
||||||
Execute(RouteCommand() + " ADD c000::/2 " + ipv6.ToString());
|
|
||||||
ifname.back()++;
|
|
||||||
Execute(RouteCommand() + " ADD 0.0.0.0 MASK 128.0.0.0 " + ifname);
|
|
||||||
Execute(RouteCommand() + " ADD 128.0.0.0 MASK 128.0.0.0 " + ifname);
|
|
||||||
|
|
||||||
#elif __APPLE__
|
|
||||||
Execute("/sbin/route -n add -cloning -net 0.0.0.0 -netmask 128.0.0.0 -interface " + ifname);
|
|
||||||
Execute("/sbin/route -n add -cloning -net 128.0.0.0 -netmask 128.0.0.0 -interface " + ifname);
|
|
||||||
|
|
||||||
Execute("/sbin/route -n add -inet6 -net ::/2 -interface " + ifname);
|
|
||||||
Execute("/sbin/route -n add -inet6 -net 4000::/2 -interface " + ifname);
|
|
||||||
Execute("/sbin/route -n add -inet6 -net 8000::/2 -interface " + ifname);
|
|
||||||
Execute("/sbin/route -n add -inet6 -net c000::/2 -interface " + ifname);
|
|
||||||
#else
|
|
||||||
#error unsupported platform
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
DelDefaultRouteViaInterface(std::string ifname)
|
|
||||||
{
|
|
||||||
LogInfo("Remove default route via ", ifname);
|
|
||||||
#ifdef __linux__
|
|
||||||
#ifndef ANDROID
|
|
||||||
NLSocket sock;
|
|
||||||
int if_idx = if_nametoindex(ifname.c_str());
|
|
||||||
_inet_addr to_addr{};
|
|
||||||
_inet_addr gw_addr{};
|
|
||||||
const auto maybe = GetInterfaceAddr(ifname);
|
|
||||||
|
|
||||||
if (not maybe.has_value())
|
|
||||||
throw std::runtime_error("we dont have our own net interface?");
|
|
||||||
int nl_cmd = RTM_DELROUTE;
|
|
||||||
int nl_flags = 0;
|
|
||||||
const auto host = maybe->asIPv4().ToString();
|
|
||||||
read_addr(host.c_str(), &gw_addr);
|
|
||||||
read_addr("0.0.0.0", &to_addr, 1);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eLowerDefault, if_idx);
|
|
||||||
read_addr("128.0.0.0", &to_addr, 1);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
|
|
||||||
|
|
||||||
const auto maybeInt = GetInterfaceIPv6Address(ifname);
|
|
||||||
if (maybeInt.has_value())
|
|
||||||
{
|
|
||||||
const auto host = maybeInt->ToString();
|
|
||||||
LogInfo("del v6 route via ", host);
|
|
||||||
read_addr(host.c_str(), &gw_addr, 128);
|
|
||||||
read_addr("::", &to_addr, 2);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
|
|
||||||
read_addr("4000::", &to_addr, 2);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
|
|
||||||
read_addr("8000::", &to_addr, 2);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
|
|
||||||
read_addr("c000::", &to_addr, 2);
|
|
||||||
do_route(sock.fd, nl_cmd, nl_flags, &to_addr, &gw_addr, GatewayMode::eUpperDefault, if_idx);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#elif _WIN32
|
|
||||||
huint32_t ip{};
|
|
||||||
ip.FromString(ifname);
|
|
||||||
const auto ipv6 = net::ExpandV4Lan(ip);
|
|
||||||
|
|
||||||
Execute(RouteCommand() + " DELETE ::/2 " + ipv6.ToString());
|
|
||||||
Execute(RouteCommand() + " DELETE 4000::/2 " + ipv6.ToString());
|
|
||||||
Execute(RouteCommand() + " DELETE 8000::/2 " + ipv6.ToString());
|
|
||||||
Execute(RouteCommand() + " DELETE c000::/2 " + ipv6.ToString());
|
|
||||||
ifname.back()++;
|
|
||||||
Execute(RouteCommand() + " DELETE 0.0.0.0 MASK 128.0.0.0 " + ifname);
|
|
||||||
Execute(RouteCommand() + " DELETE 128.0.0.0 MASK 128.0.0.0 " + ifname);
|
|
||||||
Execute(RouteCommand() + " DELETE 127.0.0.0 MASK 255.0.0.0 0.0.0.0");
|
|
||||||
#elif __APPLE__
|
|
||||||
Execute("/sbin/route -n delete -cloning -net 0.0.0.0 -netmask 128.0.0.0 -interface " + ifname);
|
|
||||||
Execute(
|
|
||||||
"/sbin/route -n delete -cloning -net 128.0.0.0 -netmask 128.0.0.0 -interface " + ifname);
|
|
||||||
|
|
||||||
Execute("/sbin/route -n delete -inet6 -net ::/2 -interface " + ifname);
|
|
||||||
Execute("/sbin/route -n delete -inet6 -net 4000::/2 -interface " + ifname);
|
|
||||||
Execute("/sbin/route -n delete -inet6 -net 8000::/2 -interface " + ifname);
|
|
||||||
Execute("/sbin/route -n delete -inet6 -net c000::/2 -interface " + ifname);
|
|
||||||
|
|
||||||
#else
|
|
||||||
#error unsupported platform
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string>
|
|
||||||
GetGatewaysNotOnInterface(std::string ifname)
|
|
||||||
{
|
|
||||||
std::vector<std::string> gateways;
|
|
||||||
#ifdef __linux__
|
|
||||||
#ifdef ANDROID
|
|
||||||
#else
|
|
||||||
std::ifstream inf("/proc/net/route");
|
|
||||||
for (std::string line; std::getline(inf, line);)
|
|
||||||
{
|
|
||||||
const auto parts = split(line, '\t');
|
|
||||||
if (parts[1].find_first_not_of('0') == std::string::npos and parts[0] != ifname)
|
|
||||||
{
|
|
||||||
const auto& ip = parts[2];
|
|
||||||
if ((ip.size() == sizeof(uint32_t) * 2) and oxenmq::is_hex(ip))
|
|
||||||
{
|
|
||||||
huint32_t x{};
|
|
||||||
oxenmq::from_hex(ip.begin(), ip.end(), reinterpret_cast<char*>(&x.h));
|
|
||||||
gateways.emplace_back(x.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return gateways;
|
|
||||||
#elif _WIN32
|
|
||||||
ForEachWIN32Interface([&](auto w32interface) {
|
|
||||||
struct in_addr gateway, interface_addr;
|
|
||||||
gateway.S_un.S_addr = (u_long)w32interface->dwForwardDest;
|
|
||||||
interface_addr.S_un.S_addr = (u_long)w32interface->dwForwardNextHop;
|
|
||||||
std::string interface_name{inet_ntoa(interface_addr)};
|
|
||||||
if ((!gateway.S_un.S_addr) and interface_name != ifname)
|
|
||||||
{
|
|
||||||
llarp::LogTrace(
|
|
||||||
"Win32 find gateway: Adding gateway (", interface_name, ") to list of gateways.");
|
|
||||||
gateways.push_back(std::move(interface_name));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return gateways;
|
|
||||||
#elif __APPLE__
|
|
||||||
LogDebug("get gateways not on ", ifname);
|
|
||||||
// mac os is so godawful man
|
|
||||||
FILE* p = popen("/usr/sbin/netstat -rn -f inet", "r");
|
|
||||||
if (p == nullptr)
|
|
||||||
{
|
|
||||||
return gateways;
|
|
||||||
}
|
|
||||||
char* line = nullptr;
|
|
||||||
size_t len = 0;
|
|
||||||
ssize_t read = 0;
|
|
||||||
while ((read = getline(&line, &len, p)) != -1)
|
|
||||||
{
|
|
||||||
const std::string line_str(line, len);
|
|
||||||
const auto parts = llarp::split_any(line_str, " "sv, true);
|
|
||||||
if (parts[0] == "default" and parts[3] != ifname)
|
|
||||||
{
|
|
||||||
gateways.emplace_back(parts[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pclose(p);
|
|
||||||
return gateways;
|
|
||||||
#else
|
|
||||||
#error unsupported platform
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace llarp::net
|
|
@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace llarp::net
|
|
||||||
{
|
|
||||||
/// get every ip address that is a gateway that isn't owned by interface with name ifname
|
|
||||||
std::vector<std::string>
|
|
||||||
GetGatewaysNotOnInterface(std::string ifname);
|
|
||||||
|
|
||||||
/// add route to ipaddr via gateway ip
|
|
||||||
void
|
|
||||||
AddRoute(std::string ipaddr, std::string gateway);
|
|
||||||
|
|
||||||
/// delete route to ipaddr via gateway ip
|
|
||||||
void
|
|
||||||
DelRoute(std::string ipaddr, std::string gateway);
|
|
||||||
|
|
||||||
/// add default route via interface with name ifname
|
|
||||||
void
|
|
||||||
AddDefaultRouteViaInterface(std::string ifname);
|
|
||||||
|
|
||||||
/// delete default route via interface with name ifname
|
|
||||||
void
|
|
||||||
DelDefaultRouteViaInterface(std::string ifname);
|
|
||||||
|
|
||||||
} // namespace llarp::net
|
|
@ -1,173 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <llarp/ev/vpn.hpp>
|
|
||||||
#include "common.hpp"
|
|
||||||
|
|
||||||
#include <sys/kern_control.h>
|
|
||||||
#include <sys/sys_domain.h>
|
|
||||||
#include <sys/kern_event.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/param.h>
|
|
||||||
#include <sys/uio.h>
|
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <net/if.h>
|
|
||||||
#include <net/if_var.h>
|
|
||||||
#include <net/if_types.h>
|
|
||||||
#include <net/route.h>
|
|
||||||
#include <netinet/if_ether.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <ifaddrs.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
namespace llarp::vpn
|
|
||||||
{
|
|
||||||
class AppleInterface : public NetworkInterface
|
|
||||||
{
|
|
||||||
const int m_FD;
|
|
||||||
const InterfaceInfo m_Info;
|
|
||||||
std::string m_IfName;
|
|
||||||
|
|
||||||
static void
|
|
||||||
Exec(std::string cmd)
|
|
||||||
{
|
|
||||||
LogDebug(cmd);
|
|
||||||
system(cmd.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
AppleInterface(InterfaceInfo info)
|
|
||||||
: m_FD{::socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL)}, m_Info{std::move(info)}
|
|
||||||
{
|
|
||||||
if (m_FD == -1)
|
|
||||||
throw std::invalid_argument{"cannot open control socket: " + std::string{strerror(errno)}};
|
|
||||||
|
|
||||||
ctl_info cinfo{};
|
|
||||||
const std::string apple_utun = "com.apple.net.utun_control";
|
|
||||||
std::copy_n(apple_utun.c_str(), apple_utun.size(), cinfo.ctl_name);
|
|
||||||
if (::ioctl(m_FD, CTLIOCGINFO, &cinfo) < 0)
|
|
||||||
{
|
|
||||||
::close(m_FD);
|
|
||||||
throw std::runtime_error{"ioctl CTLIOCGINFO call failed: " + std::string{strerror(errno)}};
|
|
||||||
}
|
|
||||||
sockaddr_ctl addr{};
|
|
||||||
addr.sc_id = cinfo.ctl_id;
|
|
||||||
|
|
||||||
addr.sc_len = sizeof(addr);
|
|
||||||
addr.sc_family = AF_SYSTEM;
|
|
||||||
addr.ss_sysaddr = AF_SYS_CONTROL;
|
|
||||||
addr.sc_unit = 0;
|
|
||||||
|
|
||||||
if (connect(m_FD, (sockaddr*)&addr, sizeof(addr)) < 0)
|
|
||||||
{
|
|
||||||
::close(m_FD);
|
|
||||||
throw std::runtime_error{
|
|
||||||
"cannot connect to control socket address: " + std::string{strerror(errno)}};
|
|
||||||
}
|
|
||||||
uint32_t namesz = IFNAMSIZ;
|
|
||||||
char name[IFNAMSIZ + 1]{};
|
|
||||||
if (getsockopt(m_FD, SYSPROTO_CONTROL, 2, name, &namesz) < 0)
|
|
||||||
{
|
|
||||||
::close(m_FD);
|
|
||||||
throw std::runtime_error{
|
|
||||||
"cannot query for interface name: " + std::string{strerror(errno)}};
|
|
||||||
}
|
|
||||||
m_IfName = name;
|
|
||||||
for (const auto& ifaddr : m_Info.addrs)
|
|
||||||
{
|
|
||||||
if (ifaddr.fam == AF_INET)
|
|
||||||
{
|
|
||||||
const huint32_t addr = net::TruncateV6(ifaddr.range.addr);
|
|
||||||
const huint32_t netmask = net::TruncateV6(ifaddr.range.netmask_bits);
|
|
||||||
const huint32_t daddr = addr & netmask;
|
|
||||||
Exec(
|
|
||||||
"/sbin/ifconfig " + m_IfName + " " + addr.ToString() + " " + daddr.ToString()
|
|
||||||
+ " mtu 1500 netmask 255.255.255.255 up");
|
|
||||||
Exec(
|
|
||||||
"/sbin/route add " + daddr.ToString() + " -netmask " + netmask.ToString()
|
|
||||||
+ " -interface " + m_IfName);
|
|
||||||
Exec("/sbin/route add " + addr.ToString() + " -interface lo0");
|
|
||||||
}
|
|
||||||
else if (ifaddr.fam == AF_INET6)
|
|
||||||
{
|
|
||||||
Exec("/sbin/ifconfig " + m_IfName + " inet6 " + ifaddr.range.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~AppleInterface()
|
|
||||||
{
|
|
||||||
::close(m_FD);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string
|
|
||||||
IfName() const override
|
|
||||||
{
|
|
||||||
return m_IfName;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
PollFD() const override
|
|
||||||
{
|
|
||||||
return m_FD;
|
|
||||||
}
|
|
||||||
|
|
||||||
net::IPPacket
|
|
||||||
ReadNextPacket() override
|
|
||||||
{
|
|
||||||
constexpr int uintsize = sizeof(unsigned int);
|
|
||||||
net::IPPacket pkt{};
|
|
||||||
unsigned int pktinfo = 0;
|
|
||||||
const struct iovec vecs[2] = {
|
|
||||||
{.iov_base = &pktinfo, .iov_len = uintsize},
|
|
||||||
{.iov_base = pkt.buf, .iov_len = sizeof(pkt.buf)}};
|
|
||||||
int sz = readv(m_FD, vecs, 2);
|
|
||||||
if (sz >= uintsize)
|
|
||||||
pkt.sz = sz - uintsize;
|
|
||||||
else if (sz >= 0 || errno == EAGAIN || errno == EWOULDBLOCK)
|
|
||||||
pkt.sz = 0;
|
|
||||||
else
|
|
||||||
throw std::error_code{errno, std::system_category()};
|
|
||||||
return pkt;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
WritePacket(net::IPPacket pkt) override
|
|
||||||
{
|
|
||||||
static unsigned int af4 = htonl(AF_INET);
|
|
||||||
static unsigned int af6 = htonl(AF_INET6);
|
|
||||||
|
|
||||||
const struct iovec vecs[2] = {
|
|
||||||
{.iov_base = pkt.IsV6() ? &af6 : &af4, .iov_len = sizeof(unsigned int)},
|
|
||||||
{.iov_base = pkt.buf, .iov_len = pkt.sz}};
|
|
||||||
|
|
||||||
ssize_t n = writev(m_FD, vecs, 2);
|
|
||||||
if (n >= (int)sizeof(unsigned int))
|
|
||||||
{
|
|
||||||
n -= sizeof(unsigned int);
|
|
||||||
return static_cast<size_t>(n) == pkt.sz;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ApplePlatform : public Platform
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::shared_ptr<NetworkInterface>
|
|
||||||
ObtainInterface(InterfaceInfo info) override
|
|
||||||
{
|
|
||||||
return std::make_shared<AppleInterface>(std::move(info));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace llarp::vpn
|
|
@ -1,65 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# this shell script will be replaced by a proper program in the future (probably)
|
|
||||||
#
|
|
||||||
# from https://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
helpme=
|
|
||||||
default_mainnet=https://seed.lokinet.org/lokinet.signed
|
|
||||||
default_testnet=https://seed.lokinet.org/testnet.signed
|
|
||||||
default_dest="$HOME/.lokinet/bootstrap.signed"
|
|
||||||
|
|
||||||
if [ "$#" -gt 2 ]; then
|
|
||||||
helpme=y
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$1" ] || [ "$1" == "mainnet" ] || [ "$1" == "lokinet" ]; then
|
|
||||||
url="${default_mainnet}"
|
|
||||||
elif [ "$1" == "testnet" ]; then
|
|
||||||
url="${default_testnet}"
|
|
||||||
elif [[ "$1" = -* ]]; then
|
|
||||||
helpme=y
|
|
||||||
else
|
|
||||||
url="$1"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$2" = -* ]]; then
|
|
||||||
helpme=y
|
|
||||||
elif [ -n "$2" ]; then
|
|
||||||
dest="$2"
|
|
||||||
else
|
|
||||||
dest="$default_dest"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$helpme" ]; then
|
|
||||||
echo "Usage: $0 [URL [DEST]] -- download bootstrap file from URL (default: lokinet) and save to DEST (default: $default_dest)."
|
|
||||||
echo "URL can be a full URL, or else 'lokinet' or 'testnet' to use the default lokinet/testnet seed URL. 'mainnet' can be used"
|
|
||||||
echo "as an alias for 'lokinet'."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
destdir="$(dirname $dest)"
|
|
||||||
if [ ! -d "$destdir" ]; then
|
|
||||||
mkdir "$destdir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "downloading $url"
|
|
||||||
|
|
||||||
# use temp file to not overrwrite existing bootstrap file on fail
|
|
||||||
#tmp=mktemp
|
|
||||||
tmp=/tmp/bootstrap.tmp
|
|
||||||
|
|
||||||
# MacOS does not have wget without homebrew but does have curl
|
|
||||||
# Rick also had indicated most BSDs have curl too
|
|
||||||
if curl -fsSL "$url" >"$tmp"; then
|
|
||||||
mv "$tmp" "$dest"
|
|
||||||
echo -e "${GREEN}lokinet successfully bootstrapped${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}failed to download bootstrap from $url${NC}"
|
|
||||||
rm -f "$tmp"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
Loading…
Reference in New Issue