mirror of https://github.com/oxen-io/lokinet
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
parent
712b5a5608
commit
329da951b7
@ -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?
|
||||
}
|
||||
}
|
@ -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…
Reference in New Issue