Apple OS interface cleanup & refactoring

- Add a C callback interface (context_wrapper.h) between lokinet and the
  objective-C code so that:
  - we can use objective-C (rather than objective-C++), which seems more
    likely to be supported by Apple into the future;
  - we minimize the amount of code that needs to be aware of the Apple
    APIs.
  - this replaces apple logger objective c++ implementation with a plain
    c++ implementation that takes a very simple C callback (provided
    from the obj-c code) to actually make the call to NSLog.

- Add various documentation to the code of what is going on.

- Send all DNS traffic to the primary IP on the tun interface.  The
  match prefixes simply don't work as advertised, and have weird shit
  (like even if you get it working for some domains, "instagram.com"
  still doesn't because of god-knows-what Apple internal politics).

- Drop the dns proxy code as we don't need it anymore.

- Don't use 9.9.9.9 for default DNS.  (We might consider the unfiltered
  9.9.9.10 as an alternative default, but if we do it should be a global
  lokinet change rather than a Mac-specific change).

- Parse a lokinet.ini in the data directory, if it exists.  (Since we
  are sandboxed, it is an app-specific "home" directory so is probably
  buried god knows where, but at least the GUI ought to be able to get
  it to let users add things to it).

- This commit also adds a swift version of the PacketTunnelProvider
  glue, which ought to work in theory, but the *tooling* for cmake is so
  underdeveloped that I couldn't find any way to actually get the damn
  thing working.  So I'm committing it here anyway (and will revert it
  away in the next commit) in case we someday want to switch to it.

-
pull/1688/head
Jason Rhinelander 3 years ago committed by Jeff Becker
parent 712b5a5608
commit 329da951b7
No known key found for this signature in database
GPG Key ID: F357B3B42F6F9B05

@ -18,7 +18,7 @@ fi
cd "$(dirname $0)/../"
if [ "$1" = "verify" ] ; then
if [ $($binary --output-replacements-xml $(find jni daemon llarp include pybind | grep -E '\.([hc](pp)?|mm)$' | grep -v '\#') | grep '</replacement>' | wc -l) -ne 0 ] ; then
if [ $($binary --output-replacements-xml $(find jni daemon llarp include pybind | grep -E '\.([hc](pp)?|mm?)$' | grep -v '\#') | grep '</replacement>' | wc -l) -ne 0 ] ; then
exit 1
fi
else

@ -3,9 +3,6 @@ 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"
codesign --verbose=4 --force -s "@CODESIGN_APPEX@" \
--entitlements "@PROJECT_SOURCE_DIR@/contrib/macos/lokinet-dnsproxy.entitlements.plist" \
--deep --strict --timestamp --options=runtime "@SIGN_TARGET@/Contents/PlugIns/lokinet-dnsproxy.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" \

@ -88,7 +88,6 @@ if(APPLE)
$<TARGET_BUNDLE_DIR:lokinet-extension>/Contents/Resources/bootstrap.signed
COMMAND mkdir -p $<TARGET_BUNDLE_DIR:lokinet>/Contents/PlugIns
COMMAND cp -a $<TARGET_BUNDLE_DIR:lokinet-extension> $<TARGET_BUNDLE_DIR:lokinet>/Contents/PlugIns/
COMMAND cp -a $<TARGET_BUNDLE_DIR:lokinet-dnsproxy> $<TARGET_BUNDLE_DIR:lokinet>/Contents/PlugIns/
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.provisionprofile
$<TARGET_BUNDLE_DIR:lokinet>/Contents/embedded.provisionprofile
)
@ -114,14 +113,14 @@ if(APPLE)
@ONLY)
add_custom_target(
sign
DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" lokinet lokinet-extension lokinet-dnsproxy
DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" lokinet lokinet-extension
COMMAND "${PROJECT_BINARY_DIR}/sign.sh"
)
else()
message(WARNING "Not codesigning: CODESIGN_APP (=${CODESIGN_APP}) and/or CODESIGN_APPEX (=${CODESIGN_APPEX}) are not set")
add_custom_target(
sign
DEPENDS lokinet lokinet-extension lokinet-dnsproxy
DEPENDS lokinet lokinet-extension
COMMAND "true")
endif()
endif()

@ -12,16 +12,26 @@ find_library(FOUNDATION Foundation REQUIRED)
find_library(NETEXT NetworkExtension REQUIRED)
find_library(COREFOUNDATION CoreFoundation REQUIRED)
target_sources(lokinet-util PRIVATE apple_logger.mm)
target_sources(lokinet-util PRIVATE apple_logger.cpp)
target_link_libraries(lokinet-util PUBLIC ${FOUNDATION})
add_executable(lokinet-extension MACOSX_BUNDLE framework.mm)
add_executable(lokinet-dnsproxy MACOSX_BUNDLE dnsproxy.mm)
target_link_libraries(lokinet-extension PUBLIC
target_sources(lokinet-platform PRIVATE vpn_interface.cpp context_wrapper.cpp)
add_executable(lokinet-extension MACOSX_BUNDLE
PacketTunnelProvider.m
)
target_link_libraries(lokinet-extension PRIVATE
liblokinet
${COREFOUNDATION}
${NETEXT})
target_link_libraries(lokinet-dnsproxy PUBLIC
# 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})
@ -33,29 +43,8 @@ set_target_properties(lokinet-extension PROPERTIES
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
)
# Not sure what -fapplication-extension does, but XCode puts it in so...
# -e _NSExtensionMain because it has that instead of a `main` function entry point, of course.
target_link_options(lokinet-extension PRIVATE -fapplication-extension -e _NSExtensionMain)
target_compile_options(lokinet-extension PRIVATE -fapplication-extension -fobjc-arc)
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
)
set_target_properties(lokinet-dnsproxy PROPERTIES
BUNDLE TRUE
BUNDLE_EXTENSION appex
MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/contrib/macos/LokinetDNSProxy.Info.plist.in
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
)
target_link_options(lokinet-dnsproxy PRIVATE -fapplication-extension -e _NSExtensionMain)
target_compile_options(lokinet-dnsproxy PRIVATE -fapplication-extension -fobjc-arc)
add_custom_command(TARGET lokinet-dnsproxy
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-dnsproxy.provisionprofile
$<TARGET_BUNDLE_DIR:lokinet-dnsproxy>/Contents/embedded.provisionprofile
)

@ -0,0 +1,134 @@
#include <Foundation/Foundation.h>
#include <NetworkExtension/NetworkExtension.h>
#include "context_wrapper.h"
NSString* error_domain = @"com.loki-project.lokinet";
@interface LLARPPacketTunnel : NEPacketTunnelProvider
{
void* lokinet;
}
- (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;
@end
void nslogger(const char* msg) { NSLog(@"%s", msg); }
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]];
}
void start_packet_reader(void* ctx) {
if (ctx == nil)
return;
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*) ctx;
[t readPackets];
}
@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
{
char ip_buf[16];
char mask_buf[16];
char dns_buf[16];
NSString* default_bootstrap = [[NSBundle mainBundle] pathForResource:@"bootstrap" ofType:@"signed"];
lokinet = llarp_apple_init(nslogger, NSHomeDirectory().UTF8String, default_bootstrap.UTF8String, ip_buf, mask_buf, dns_buf);
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 alloc] initWithUTF8String:ip_buf];
NSString* mask = [[NSString alloc] initWithUTF8String:mask_buf];
NSString* dnsaddr = [[NSString alloc] initWithUTF8String:dns_buf];
NEPacketTunnelNetworkSettings* settings =
[[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.0.0.1"];
NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[dnsaddr]];
dns.domainName = @"localhost.loki";
// 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 = @[];
NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[ip]
subnetMasks:@[mask]];
settings.IPv4Settings = ipv4;
settings.DNSSettings = dns;
[self setTunnelNetworkSettings:settings completionHandler:^(NSError* err) {
if (err) {
NSLog(@"Failed to configure lokinet tunnel: %@", err);
return completionHandler(err);
}
int start_ret = llarp_apple_start(lokinet, packet_writer, start_packet_reader, (__bridge void*) self);
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);
}
completionHandler(nil);
}];
}
- (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);
}
@end

@ -0,0 +1,129 @@
/// Lokinet network extension swift glue layer
import NetworkExtension
enum LokinetError: Error {
case runtimeError(String)
}
func packet_writer(af: Int32, data: UnsafeRawPointer?, size: Int, ctx: UnsafeMutableRawPointer?) {
if ctx == nil || data == nil {
return
}
let tunnel = ctx!.assumingMemoryBound(to: PacketTunnelProvider.self).pointee
let packet = NEPacket(
data: Data(bytes: data!, count: 1),
protocolFamily: sa_family_t(af))
tunnel.packetFlow.writePacketObjects([packet])
}
func start_packet_reader(ctx: UnsafeMutableRawPointer?) {
let tunnel = ctx?.assumingMemoryBound(to: PacketTunnelProvider.self).pointee
tunnel?.readHandler()
}
class PacketTunnelProvider: NEPacketTunnelProvider {
var lokinet: UnsafeMutableRawPointer?
func readHandler() {
packetFlow.readPacketObjects() { (packets: [NEPacket]) in
if self.lokinet == nil {
return
}
for p in packets {
p.data.withUnsafeBytes { (buf: UnsafeRawBufferPointer) -> Void in
llarp_apple_incoming(self.lokinet, buf.baseAddress!, buf.count)
}
}
self.readHandler()
}
}
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
var ip_buf = Array<CChar>(repeating: 0, count: 16)
var mask_buf = Array<CChar>(repeating: 0, count: 16)
var dns_buf = Array<CChar>(repeating: 0, count: 16)
lokinet = llarp_apple_init(NSHomeDirectory(), &ip_buf, &mask_buf, &dns_buf)
if lokinet == nil {
NSLog("Lokinet initialization failed!")
completionHandler(LokinetError.runtimeError("Lokinet initialization failed"))
return
}
let ip = String(cString: ip_buf)
let mask = String(cString: mask_buf)
let dns_addr = String(cString: dns_buf)
NSLog("Lokinet configured with address %s/%s, dns %s", ip, mask, dns_addr)
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
settings.ipv4Settings = NEIPv4Settings(addresses: [ip], subnetMasks: [mask])
let dns = NEDNSSettings(servers: [dns_addr])
dns.domainName = "localhost.loki"
dns.matchDomains = ["snode", "loki"]
dns.matchDomainsNoSearch = true
dns.searchDomains = []
settings.dnsSettings = dns
let condition = NSCondition()
condition.lock()
defer { condition.unlock() }
var system_error: Error?
setTunnelNetworkSettings(settings) { error in
system_error = error
condition.signal()
}
condition.wait()
if let error = system_error {
NSLog("Failed to set up tunnel: %s", error.localizedDescription)
lokinet = nil
completionHandler(error)
return
}
var myself = self
let start_ret = llarp_apple_start(lokinet, packet_writer, start_packet_reader, &myself)
if start_ret != 0 {
NSLog("Lokinet failed to start!")
lokinet = nil
completionHandler(LokinetError.runtimeError("Lokinet failed to start"))
return
}
completionHandler(nil)
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
if lokinet != nil {
llarp_apple_shutdown(lokinet)
lokinet = nil
}
completionHandler()
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: @escaping () -> Void) {
// FIXME - do we need to kick lokinet here?
completionHandler()
}
override func wake() {
// FIXME - do we need to kick lokinet here?
}
}

@ -1,9 +1,6 @@
#include "apple_logger.hpp"
#include <llarp/util/logging/logger_internal.hpp>
#include <Foundation/Foundation.h>
namespace llarp
namespace llarp::apple
{
void
NSLogStream::PreLog(
@ -22,15 +19,7 @@ namespace llarp
void
NSLogStream::Print(LogLevel, const char*, const std::string& msg)
{
const char* msg_ptr = msg.c_str();
const char* msg_fmt = "%s";
NSString* fmt = [[NSString alloc] initWithUTF8String:msg_ptr];
NSString* str = [[NSString alloc] initWithUTF8String:msg_fmt];
NSLog(fmt, str);
ns_logger(msg.c_str());
}
void
NSLogStream::PostLog(std::stringstream&) const
{}
} // namespace llarp
} // namespace llarp::apple

@ -1,11 +1,17 @@
#pragma once
#include <llarp/util/logging/logger.hpp>
#include <llarp/util/logging/logstream.hpp>
namespace llarp
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,
@ -18,13 +24,17 @@ namespace llarp
Print(LogLevel lvl, const char* tag, const std::string& msg) override;
void
PostLog(std::stringstream& ss) const override;
PostLog(std::stringstream& ss) const override
{}
virtual void
void
ImmediateFlush() override
{}
void Tick(llarp_time_t) override
{}
private:
ns_logger_callback ns_logger;
};
} // namespace llarp
} // namespace llarp::apple

@ -0,0 +1,23 @@
#pragma once
#include <llarp.hpp>
#include "vpn_platform.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);
}
// 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;
};
} // namespace llarp::apple

@ -0,0 +1,162 @@
#include <cstdint>
#include <cstring>
#include <llarp/net/ip_packet.hpp>
#include <llarp/config/config.hpp>
#include <llarp/util/fs.hpp>
#include <llarp/util/logging/buffer.hpp>
#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;
std::weak_ptr<llarp::apple::VPNInterface> iface;
};
} // namespace
void*
llarp_apple_init(
ns_logger_callback ns_logger,
const char* config_dir_,
const char* default_bootstrap,
char* ip,
char* netmask,
char* dns)
{
llarp::LogContext::Instance().logStream = std::make_unique<llarp::apple::NSLogStream>(ns_logger);
try
{
auto config_dir = fs::u8path(config_dir_);
auto config = std::make_shared<llarp::Config>(config_dir);
std::optional<fs::path> config_path = config_dir / "lokinet.ini";
if (!fs::exists(*config_path))
config_path.reset();
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::strcpy(ip, addr.c_str());
std::strcpy(netmask, mask.c_str());
// XXX possibly DNS needs to be the .0 instead of the .1 because mac reasons?
std::strcpy(dns, addr.c_str());
// 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(default_bootstrap));
auto inst = std::make_unique<instance_data>();
inst->context.Configure(std::move(config));
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,
packet_writer_callback packet_writer,
start_reading_callback start_reading,
void* callback_context)
{
auto* inst = static_cast<instance_data*>(lokinet);
inst->context.m_PacketWriter = [inst, packet_writer, callback_context](
int af_family, void* data, size_t size) {
packet_writer(af_family, data, size, callback_context);
return true;
};
inst->context.m_OnReadable =
[inst, start_reading, callback_context](llarp::apple::VPNInterface& iface) {
inst->iface = iface.weak_from_this();
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;
}
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,94 @@
#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>
/// 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);
/// 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 logger a logger callback that we pass log messages to to relay them (i.e. via NSLog).
///
/// \param config_dir the lokinet configuration directory where lokinet.ini can be and the various
/// other lokinet state files go.
///
/// \param default_bootstrap the path to the default bootstrap.signed included in installation,
/// which will be used if no explicit bootstrap is set in the config file.
///
/// \param ip - char buffer where we will write the primary tunnel IP address as a string such as
/// "172.16.0.0". Will write up to 16 characters (including the null terminator). This will be
/// the tunnel IP from the lokinet.ini, if it exists and specifies a range, otherwise we'll
/// configure lokinet to use a currently-unused range and return that.
///
/// \param netmask the tunnel netmask as a string such as "255.255.0.0". Will write up to 16
/// characters (including the null terminator).
///
/// \param dns the DNS address that should be configured to query lokinet, as a string such as
/// "172.16.0.1". Will write up to 16 characters (including the null terminator).
void*
llarp_apple_init(
ns_logger_callback ns_logger,
const char* config_dir,
const char* default_bootstrap,
char* ip,
char* netmask,
char* dns);
/// Starts the lokinet instance in a new thread.
///
/// \param packet_writer 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, the size
/// of the data in bytes, and the opaque callback_context pointer.
///
/// \param start_reading 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. This is called with a single argument of the opaque
/// callback_context pointer.
///
/// \param callback_context Opaque pointer that is passed into the packet_writer and start_reading
/// callback, intended to allow context to be passed through to the callbacks. 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,
packet_writer_callback packet_writer,
start_reading_callback start_reading,
void* callback_context);
/// 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,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,31 @@
#pragma once
#include <llarp/ev/vpn.hpp>
#include "vpn_interface.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)
: m_Context{ctx}
, m_PacketWriter{std::move(packet_writer)}
, m_OnReadable{std::move(on_readable)}
{}
std::shared_ptr<vpn::NetworkInterface> ObtainInterface(vpn::InterfaceInfo) override
{
return std::make_shared<VPNInterface>(m_Context, m_PacketWriter, m_OnReadable);
}
private:
Context& m_Context;
VPNInterface::packet_write_callback m_PacketWriter;
VPNInterface::on_readable_callback m_OnReadable;
};
} // namespace llarp::apple
Loading…
Cancel
Save