diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a72a1b8c..af550c638 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.10) # bionic's cmake version set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Has to be set before `project()`, and ignored on non-macos: -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "macOS deployment target (Apple clang only)") +set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "macOS deployment target (Apple clang only)") set(LANGS ASM C CXX) if(APPLE) diff --git a/contrib/macos/LokinetDNSProxy.Info.plist.in b/contrib/macos/LokinetDNSProxy.Info.plist.in new file mode 100644 index 000000000..522b476ec --- /dev/null +++ b/contrib/macos/LokinetDNSProxy.Info.plist.in @@ -0,0 +1,40 @@ + + + + + CFBundleDisplayName + Lokinet + + CFBundleExecutable + lokinet-dnsproxy + + CFBundleIdentifier + com.loki-project.lokinet.dns-proxy + + CFBundleInfoDictionaryVersion + 6.0 + + CFBundlePackageType + XPC! + + CFBundleName + lokinet + + CFBundleVersion + @LOKINET_VERSION@ + + ITSAppUsesNonExemptEncryption + + + LSMinimumSystemVersion + 11.0 + + NSExtension + + NSExtensionPointIdentifier + com.apple.networkextension.dns-proxy + NSExtensionPrincipalClass + DNSProvider + + + diff --git a/contrib/macos/lokinet-dnsproxy.entitlements.plist b/contrib/macos/lokinet-dnsproxy.entitlements.plist new file mode 100644 index 000000000..fa43bbd2b --- /dev/null +++ b/contrib/macos/lokinet-dnsproxy.entitlements.plist @@ -0,0 +1,29 @@ + + + + + com.apple.application-identifier + SUQ8J2PCT7.com.loki-project.lokinet.dns-proxy + + com.apple.developer.networking.networkextension + + dns-proxy + + + com.apple.developer.team-identifier + SUQ8J2PCT7 + + com.apple.security.app-sandbox + + + com.apple.security.get-task-allow + + + com.apple.security.network.client + + + com.apple.security.network.server + + + + diff --git a/contrib/macos/lokinet-dnsproxy.provisionprofile b/contrib/macos/lokinet-dnsproxy.provisionprofile new file mode 100644 index 000000000..87cd403bb Binary files /dev/null and b/contrib/macos/lokinet-dnsproxy.provisionprofile differ diff --git a/contrib/macos/lokinet.entitlements.plist b/contrib/macos/lokinet.entitlements.plist index 155d265ba..3869f5b04 100644 --- a/contrib/macos/lokinet.entitlements.plist +++ b/contrib/macos/lokinet.entitlements.plist @@ -8,6 +8,8 @@ com.apple.developer.networking.networkextension packet-tunnel-provider + dns-proxy + dns-settings com.apple.developer.team-identifier @@ -18,5 +20,12 @@ com.apple.security.get-task-allow + + com.apple.security.network.client + + + com.apple.security.network.server + + diff --git a/contrib/macos/sign.sh.in b/contrib/macos/sign.sh.in index 6ebf0859a..af3d18d55 100755 --- a/contrib/macos/sign.sh.in +++ b/contrib/macos/sign.sh.in @@ -3,6 +3,9 @@ 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" \ diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index 10f509bbc..ac0598f74 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -1,14 +1,17 @@ + +add_executable(lokinet-vpn lokinet-vpn.cpp) if(APPLE) set(LOKINET_SWIFT_SOURCES lokinet.swift) add_executable(lokinet ${LOKINET_SWIFT_SOURCES}) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Lokinet.modulemap.in ${CMAKE_CURRENT_BINARY_DIR}/swift/LokinetExtension/module.modulemap ESCAPE_QUOTES @ONLY) target_include_directories(lokinet PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/swift) else() add_executable(lokinet lokinet.cpp) - add_executable(lokinet-vpn lokinet-vpn.cpp) add_executable(lokinet-bootstrap lokinet-bootstrap.cpp) enable_lto(lokinet lokinet-vpn lokinet-bootstrap) endif() + if(TRACY_ROOT) target_sources(lokinet PRIVATE ${TRACY_ROOT}/TracyClient.cpp) endif() @@ -38,9 +41,9 @@ if(NOT APPLE) endif() endif() -set(exetargets lokinet) +set(exetargets lokinet lokinet-vpn) if(NOT APPLE) - list(APPEND exetargets lokinet-vpn lokinet-bootstrap) + list(APPEND exetargets lokinet-bootstrap) endif() foreach(exe ${exetargets}) @@ -76,10 +79,14 @@ if(APPLE) COMMAND ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${CMAKE_CURRENT_BINARY_DIR}/lokinet.icns DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh) add_dependencies(lokinet icons lokinet-extension) + file(DOWNLOAD "https://seed.lokinet.org/lokinet.signed" ${CMAKE_CURRENT_BINARY_DIR}/bootstrap.signed) add_custom_command(TARGET lokinet POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/bootstrap.signed + $/Contents/Resources/bootstrap.signed COMMAND mkdir -p $/Contents/PlugIns - COMMAND cp -au $ $/Contents/PlugIns/ + COMMAND cp -a $ $/Contents/PlugIns/ + COMMAND cp -a $ $/Contents/PlugIns/ COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.provisionprofile $/Contents/embedded.provisionprofile ) @@ -105,7 +112,7 @@ if(APPLE) @ONLY) add_custom_target( sign - DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" lokinet lokinet-extension + DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" lokinet lokinet-extension lokinet-dnsproxy COMMAND "${PROJECT_BINARY_DIR}/sign.sh" ) else() diff --git a/daemon/lokinet.swift b/daemon/lokinet.swift index 564cbf62a..a12c1da79 100644 --- a/daemon/lokinet.swift +++ b/daemon/lokinet.swift @@ -8,7 +8,7 @@ let app = NSApplication.shared class LokinetMain: NSObject, NSApplicationDelegate { var vpnManager = NETunnelProviderManager() let lokinetComponent = "com.loki-project.lokinet.network-extension" - var lokinetAdminTimer: DispatchSourceTimer? + var dnsComponent = "com.loki-project.lokinet.dns-proxy" func applicationDidFinishLaunching(_: Notification) { setupVPNJizz() @@ -18,12 +18,67 @@ class LokinetMain: NSObject, NSApplicationDelegate { app.terminate(self) } + func setupDNSJizz() { + NSLog("setting up dns settings") + let dns = NEDNSSettingsManager.shared() + let settings = NEDNSSettings(servers: ["172.16.0.1"]) + dns.dnsSettings = settings + dns.loadFromPreferences { [self] (error: Error?) -> Void in + if let error = error { + NSLog(error.localizedDescription) + bail() + return + } + dns.saveToPreferences { [self] (error: Error?) -> Void in + if let error = error { + NSLog(error.localizedDescription) + bail() + return + } + NSLog("dns setting set up probably") + } + } + } + + func setupDNSProxyJizz() { + NSLog("setting up dns proxy") + let dns = NEDNSProxyManager.shared() + let provider = NEDNSProxyProviderProtocol() + provider.providerBundleIdentifier = dnsComponent + provider.username = "Anonymous" + provider.serverAddress = "loki.loki" + provider.includeAllNetworks = true + provider.enforceRoutes = true + dns.providerProtocol = provider + dns.localizedDescription = "lokinet dns" + dns.loadFromPreferences { [self] (error: Error?) -> Void in + if let error = error { + NSLog(error.localizedDescription) + bail() + return + } + provider.includeAllNetworks = true + provider.enforceRoutes = true + dns.isEnabled = true + dns.saveToPreferences { [self] (error: Error?) -> Void in + if let error = error { + NSLog(error.localizedDescription) + bail() + return + } + self.initDNSObserver() + NSLog("dns is up probably") + } + } + } + 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 { @@ -44,7 +99,10 @@ class LokinetMain: NSObject, NSApplicationDelegate { providerProtocol.includeAllNetworks = false self.vpnManager.protocolConfiguration = providerProtocol self.vpnManager.isEnabled = true - self.vpnManager.isOnDemandEnabled = true + // self.vpnManager.isOnDemandEnabled = true + let rules = NEAppRule() + rules.matchDomains = ["*.snode", "*.loki"] + self.vpnManager.appRules = [rules] self.vpnManager.localizedDescription = "lokinet" self.vpnManager.saveToPreferences(completionHandler: { error -> Void in if error != nil { @@ -76,6 +134,13 @@ class LokinetMain: NSObject, NSApplicationDelegate { } } + func initDNSObserver() { + NotificationCenter.default.addObserver(forName: NSNotification.Name.NEDNSProxyConfigurationDidChange, object: NEDNSProxyManager.shared(), queue: OperationQueue.main) { _ -> Void in + let dns = NEDNSProxyManager.shared() + NSLog("%@", dns) + } + } + func initializeConnectionObserver() { NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: vpnManager.connection, queue: OperationQueue.main) { _ -> Void in if self.vpnManager.connection.status == .invalid { @@ -88,6 +153,9 @@ class LokinetMain: NSObject, NSApplicationDelegate { 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") + self.setupDNSJizz() } } } diff --git a/include/lokinet-dnsproxy.hpp b/include/lokinet-dnsproxy.hpp new file mode 100644 index 000000000..8f0545090 --- /dev/null +++ b/include/lokinet-dnsproxy.hpp @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +struct DNSImpl; + +@interface DNSProvider : NEDNSProxyProvider +{ + struct DNSImpl* m_Impl; +} +- (void)startProxyWithOptions:(NSDictionary*)options + completionHandler:(void (^)(NSError* error))completionHandler; + +- (void)stopProxyWithReason:(NEProviderStopReason)reason + completionHandler:(void (^)(void))completionHandler; + +- (BOOL)handleNewFlow:(NEAppProxyFlow*)flow; + +@end diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index a07c8b70d..ca7e25867 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -1,14 +1,12 @@ include(Version) -add_library(lokinet-util - STATIC +set(lokinet_util_src ${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp util/bencode.cpp util/buffer.cpp util/fs.cpp util/json.cpp util/logging/android_logger.cpp - util/logging/apple_logger.mm util/logging/buffer.cpp util/logging/file_logger.cpp util/logging/json_logger.cpp @@ -24,8 +22,18 @@ add_library(lokinet-util util/str.cpp util/thread/queue_manager.cpp util/thread/threading.cpp - util/time.cpp -) + util/time.cpp) + +if(APPLE) + list(APPEND lokinet_util_src + util/logging/apple_logger.mm) +endif() + + +add_library(lokinet-util + STATIC + ${lokinet_util_src}) + add_dependencies(lokinet-util genversion) target_include_directories(lokinet-util PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}) @@ -274,11 +282,16 @@ if(APPLE) find_library(COREFOUNDATION CoreFoundation REQUIRED) add_executable(lokinet-extension MACOSX_BUNDLE framework.mm) + add_executable(lokinet-dnsproxy MACOSX_BUNDLE dnsproxy.mm) target_link_libraries(lokinet-extension PUBLIC liblokinet ${COREFOUNDATION} ${NETEXT}) - + target_link_libraries(lokinet-dnsproxy PUBLIC + liblokinet + ${COREFOUNDATION} + ${NETEXT}) + set_target_properties(lokinet-extension PROPERTIES BUNDLE TRUE BUNDLE_EXTENSION appex @@ -289,7 +302,7 @@ if(APPLE) # 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) + target_compile_options(lokinet-extension PRIVATE -fapplication-extension -fobjc-arc) add_custom_command(TARGET lokinet-extension POST_BUILD @@ -297,6 +310,21 @@ if(APPLE) $/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 + $/Contents/embedded.provisionprofile + ) endif() diff --git a/llarp/apple.hpp b/llarp/apple.hpp new file mode 100644 index 000000000..dc35a9d95 --- /dev/null +++ b/llarp/apple.hpp @@ -0,0 +1,40 @@ +#pragma once +#ifdef __APPLE__ +#include +#include +#include + +static std::string_view +DataAsStringView(NSData* data) +{ + return std::string_view{reinterpret_cast(data.bytes), data.length}; +} + +static NSData* +StringViewToData(std::string_view data) +{ + const char* ptr = data.data(); + const size_t sz = data.size(); + return [NSData dataWithBytes:ptr length:sz]; +} + +static NSString* +StringToNSString(std::string data) +{ + NSData* ptr = StringViewToData(std::string_view{data}); + return [[NSString alloc] initWithData:ptr encoding:NSUTF8StringEncoding]; +} + +static std::string +NSStringToString(NSString* str) +{ + return std::string{[str UTF8String]}; +} + +static std::string +NSObjectToString(NSObject* obj) +{ + return NSStringToString([NSString stringWithFormat:@"%@", obj]); +} + +#endif diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index 3542e9326..c10893dc8 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -1453,7 +1453,6 @@ namespace llarp auto config = std::make_shared(fs::path{}); config->Load(); config->logging.m_logLevel = eLogInfo; - config->api.m_enableRPCServer = false; config->network.m_saveProfiles = false; config->bootstrap.files.clear(); return config; diff --git a/llarp/dns/message.cpp b/llarp/dns/message.cpp index 99ffed763..f10e9b9c6 100644 --- a/llarp/dns/message.cpp +++ b/llarp/dns/message.cpp @@ -48,6 +48,12 @@ namespace llarp return true; } + util::StatusObject + MessageHeader::ToJSON() const + { + return util::StatusObject{}; + } + Message::Message(Message&& other) : hdr_id(std::move(other.hdr_id)) , hdr_fields(std::move(other.hdr_fields)) @@ -74,6 +80,11 @@ namespace llarp additional.resize(size_t(hdr.ar_count)); } + Message::Message(const Question& question) : hdr_id{0}, hdr_fields{} + { + questions.emplace_back(question); + } + bool Message::Encode(llarp_buffer_t* buf) const { @@ -122,6 +133,22 @@ namespace llarp return true; } + util::StatusObject + Message::ToJSON() const + { + std::vector ques; + std::vector ans; + for (const auto& q : questions) + { + ques.push_back(q.ToJSON()); + } + for (const auto& a : answers) + { + ans.push_back(a.ToJSON()); + } + return util::StatusObject{{"questions", ques}, {"answers", ans}}; + } + OwnedBuffer Message::ToBuffer() const { diff --git a/llarp/dns/message.hpp b/llarp/dns/message.hpp index b7d4571b1..937c37d97 100644 --- a/llarp/dns/message.hpp +++ b/llarp/dns/message.hpp @@ -33,6 +33,9 @@ namespace llarp bool Decode(llarp_buffer_t* buf) override; + util::StatusObject + ToJSON() const override; + bool operator==(const MessageHeader& other) const { @@ -44,11 +47,15 @@ namespace llarp struct Message : public Serialize { - Message(const MessageHeader& hdr); + explicit Message(const MessageHeader& hdr); + explicit Message(const Question& question); Message(Message&& other); Message(const Message& other); + util::StatusObject + ToJSON() const override; + void AddNXReply(RR_TTL_t ttl = 1); diff --git a/llarp/dns/question.cpp b/llarp/dns/question.cpp index 7ec066c30..a8c60c260 100644 --- a/llarp/dns/question.cpp +++ b/llarp/dns/question.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "dns.hpp" namespace llarp { @@ -17,6 +18,13 @@ namespace llarp : qname(other.qname), qtype(other.qtype), qclass(other.qclass) {} + Question::Question(std::string name, QType_t type) + : qname{std::move(name)}, qtype{type}, qclass{qClassIN} + { + if (qname.empty()) + throw std::invalid_argument{"qname cannot be empty"}; + } + bool Question::Encode(llarp_buffer_t* buf) const { @@ -48,6 +56,12 @@ namespace llarp return true; } + util::StatusObject + Question::ToJSON() const + { + return util::StatusObject{{"qname", qname}, {"qtype", qtype}, {"qclass", qclass}}; + } + bool Question::IsName(const std::string& other) const { diff --git a/llarp/dns/question.hpp b/llarp/dns/question.hpp index f2fc00076..5434bbcdd 100644 --- a/llarp/dns/question.hpp +++ b/llarp/dns/question.hpp @@ -14,6 +14,9 @@ namespace llarp struct Question : public Serialize { Question() = default; + + explicit Question(std::string name, QType_t type); + Question(Question&& other); Question(const Question& other); bool @@ -58,6 +61,9 @@ namespace llarp /// determine if we are using this TLD bool HasTLD(const std::string& tld) const; + + util::StatusObject + ToJSON() const override; }; inline std::ostream& diff --git a/llarp/dns/rr.cpp b/llarp/dns/rr.cpp index 3fac87e9b..82ed20470 100644 --- a/llarp/dns/rr.cpp +++ b/llarp/dns/rr.cpp @@ -24,6 +24,14 @@ namespace llarp , rData(std::move(other.rData)) {} + ResourceRecord::ResourceRecord(Name_t name, RRType_t type, RR_RData_t data) + : rr_name{std::move(name)} + , rr_type{type} + , rr_class{qClassIN} + , ttl{1} + , rData{std::move(data)} + {} + bool ResourceRecord::Encode(llarp_buffer_t* buf) const { @@ -77,6 +85,17 @@ namespace llarp return true; } + util::StatusObject + ResourceRecord::ToJSON() const + { + return util::StatusObject{ + {"name", rr_name}, + {"type", rr_type}, + {"class", rr_class}, + {"ttl", ttl}, + {"rdata", std::string{reinterpret_cast(rData.data()), rData.size()}}}; + } + std::ostream& ResourceRecord::print(std::ostream& stream, int level, int spaces) const { diff --git a/llarp/dns/rr.hpp b/llarp/dns/rr.hpp index 0b50235da..e9fa72c27 100644 --- a/llarp/dns/rr.hpp +++ b/llarp/dns/rr.hpp @@ -22,12 +22,17 @@ namespace llarp ResourceRecord(const ResourceRecord& other); ResourceRecord(ResourceRecord&& other); + explicit ResourceRecord(Name_t name, RRType_t type, RR_RData_t rdata); + bool Encode(llarp_buffer_t* buf) const override; bool Decode(llarp_buffer_t* buf) override; + util::StatusObject + ToJSON() const override; + std::ostream& print(std::ostream& stream, int level, int spaces) const; diff --git a/llarp/dns/serialize.hpp b/llarp/dns/serialize.hpp index 8834de04c..94388362a 100644 --- a/llarp/dns/serialize.hpp +++ b/llarp/dns/serialize.hpp @@ -1,7 +1,7 @@ #pragma once #include - +#include #include namespace llarp @@ -20,6 +20,10 @@ namespace llarp /// decode entity from buffer virtual bool Decode(llarp_buffer_t* buf) = 0; + + /// convert this whatever into json + virtual util::StatusObject + ToJSON() const = 0; }; bool diff --git a/llarp/dnsproxy.mm b/llarp/dnsproxy.mm new file mode 100644 index 000000000..56663118c --- /dev/null +++ b/llarp/dnsproxy.mm @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +struct DNSImpl +{ + oxenmq::OxenMQ m_MQ; + std::optional m_Conn; + + explicit DNSImpl(oxenmq::address rpc) + { + m_MQ.start(); + m_MQ.connect_remote( + rpc, [this](auto conn) { m_Conn = conn; }, nullptr); + } + + bool + ShouldHookFlow(NEAppProxyFlow* flow) const + { + LogInfo(NSObjectToString(flow)); + return true; + } + + void + RelayDNSData(NEAppProxyUDPFlow* flow, NWEndpoint* remote, NSData* data) + { + if (not m_Conn) + return; + auto view = DataAsStringView(data); + + llarp_buffer_t buf{view}; + llarp::dns::MessageHeader hdr{}; + if (not hdr.Decode(&buf)) + return; + llarp::dns::Message msg{hdr}; + if (not msg.Decode(&buf)) + return; + llarp::util::StatusObject request{ + {"qname", msg.questions[0].qname}, {"qtype", msg.questions[0].qtype}}; + m_MQ.request( + *m_Conn, + "llarp.dns_query", + [flow, remote, msg = std::make_shared(std::move(msg))]( + bool good, std::vector parts) { + auto closeHandler = [flow](NSError* err) { + [flow closeWriteWithError:err]; + [flow closeReadWithError:err]; + }; + if (good and parts.size() == 1) + { + try + { + const auto obj = nlohmann::json::parse(parts[0]); + const auto result = obj["result"]; + if (const auto itr = result.find("answers"); itr != result.end()) + { + for (const auto& result : (*itr)) + { + llarp::dns::RR_RData_t rdata; + if (const auto data_itr = result.find("rdata"); data_itr != result.end()) + { + const auto data = data_itr->get(); + rdata.resize(data.size()); + std::copy_n(data.begin(), data.size(), rdata.begin()); + } + else + continue; + + msg->answers.emplace_back( + result["name"].get(), + result["type"].get(), + rdata); + } + } + } + catch (std::exception& ex) + { + LogError("dns query failed: ", ex.what()); + return; + } + const auto buf = msg->ToBuffer(); + NSData* data = StringViewToData( + std::string_view{reinterpret_cast(buf.buf.get()), buf.sz}); + [flow writeDatagrams:@[data] sentByEndpoints:@[remote] completionHandler:closeHandler]; + } + else + closeHandler(nullptr); + }, + request.dump()); + } + + void + HandleUDPFlow(NEAppProxyUDPFlow* flow) + { + auto handler = + [this, flow]( + NSArray* datagrams, NSArray* remoteEndpoints, NSError* error) { + if (error) + return; + NSInteger num = [datagrams count]; + for (NSInteger idx = 0; idx < num; ++idx) + { + RelayDNSData(flow, [remoteEndpoints objectAtIndex:idx], [datagrams objectAtIndex:idx]); + } + }; + [flow readDatagramsWithCompletionHandler:handler]; + } +}; + +@implementation DNSProvider + +- (void)startProxyWithOptions:(NSDictionary*)options + completionHandler:(void (^)(NSError* error))completionHandler +{ + m_Impl = new DNSImpl{oxenmq::address{"tcp://127.0.0.1:1190"}}; + completionHandler(nil); +} + +- (void)stopProxyWithReason:(NEProviderStopReason)reason + completionHandler:(void (^)(void))completionHandler +{ + if (m_Impl) + { + delete m_Impl; + m_Impl = nullptr; + } + completionHandler(); +} + +- (BOOL)handleNewFlow:(NEAppProxyFlow*)flow +{ + if (not [flow isKindOfClass:[NEAppProxyUDPFlow class]]) + return NO; + if (m_Impl->ShouldHookFlow(flow)) + { + NEAppProxyUDPFlow* udp = (NEAppProxyUDPFlow*)flow; + auto handler = [impl = m_Impl, udp](NSError* err) { + if (err) + return; + impl->HandleUDPFlow(udp); + }; + [flow openWithLocalEndpoint:nil completionHandler:handler]; + return YES; + } + return NO; +} + +@end diff --git a/llarp/ev/ev_libuv.cpp b/llarp/ev/ev_libuv.cpp index f9ce3c4b4..8f38750c1 100644 --- a/llarp/ev/ev_libuv.cpp +++ b/llarp/ev/ev_libuv.cpp @@ -244,7 +244,7 @@ namespace llarp::uv std::shared_ptr netif, std::function handler) { -#ifndef _WIN32 +#ifdef __linux__ using event_t = uvw::PollEvent; auto handle = m_Impl->resource(netif->PollFD()); #else @@ -264,7 +264,7 @@ namespace llarp::uv } }); -#ifndef _WIN32 +#ifdef __linux__ handle->start(uvw::PollHandle::Event::READABLE); #else handle->start(); diff --git a/llarp/framework.mm b/llarp/framework.mm index 32cde8978..f0e2b1521 100644 --- a/llarp/framework.mm +++ b/llarp/framework.mm @@ -5,8 +5,13 @@ #include #include #include +#include +#include +#include +#include -#include +const llarp::SockAddr DefaultDNSBind{"127.0.0.1:1153"}; +const llarp::SockAddr DefaultUpstreamDNS{"9.9.9.9:53"}; namespace llarp::apple { @@ -21,7 +26,7 @@ namespace llarp::apple makeVPNPlatform() override; void - Start(); + Start(std::string_view bootstrap); private: NEPacketTunnelProvider* const m_Tunnel; @@ -42,20 +47,39 @@ namespace llarp::apple llarp::net::IPPacket pkt; const llarp_buffer_t buf{static_cast(data.bytes), data.length}; if (pkt.Load(buf)) + { m_ReadQueue.tryPushBack(std::move(pkt)); + } + else + { + LogError("invalid IP packet: ", llarp::buffer_printer(DataAsStringView(data))); + } } public: - explicit VPNInterface(NEPacketTunnelProvider* tunnel) + explicit VPNInterface(NEPacketTunnelProvider* tunnel, llarp::Context* context) : m_Tunnel{tunnel}, m_ReadQueue{PacketQueueSize} { - auto handler = [this](NSArray* packets, NSArray*) { - NSUInteger num = [packets count]; - for (NSUInteger idx = 0; idx < num; ++idx) - { - NSData* pkt = [packets objectAtIndex:idx]; - OfferReadPacket(pkt); - } + context->loop->call_soon([this]() { Read(); }); + } + + void + HandleReadEvent(NSArray* packets, NSArray* protos) + { + NSUInteger num = [packets count]; + for (NSUInteger idx = 0; idx < num; ++idx) + { + NSData* pkt = [packets objectAtIndex:idx]; + OfferReadPacket(pkt); + } + Read(); + } + + void + Read() + { + auto handler = [this](NSArray* packets, NSArray* protos) { + HandleReadEvent(packets, protos); }; [m_Tunnel.packetFlow readPacketsWithCompletionHandler:handler]; } @@ -84,27 +108,27 @@ namespace llarp::apple bool WritePacket(net::IPPacket pkt) override { - const sa_family_t fam = pkt.IsV6() ? AF_INET6 : AF_INET; - const uint8_t* pktbuf = pkt.buf; + NSNumber* fam = [NSNumber numberWithInteger:(pkt.IsV6() ? AF_INET6 : AF_INET)]; + void* pktbuf = pkt.buf; const size_t pktsz = pkt.sz; - NSData* datapkt = [NSData dataWithBytes:pktbuf length:pktsz]; - NEPacket* npkt = [[NEPacket alloc] initWithData:datapkt protocolFamily:fam]; - NSArray* pkts = @[npkt]; - return [m_Tunnel.packetFlow writePacketObjects:pkts]; + NSData* datapkt = [NSData dataWithBytesNoCopy:pktbuf length:pktsz]; + return [m_Tunnel.packetFlow writePackets:@[datapkt] withProtocols:@[fam]]; } }; class VPNPlatform final : public vpn::Platform { NEPacketTunnelProvider* const m_Tunnel; + Context* const m_Context; public: - explicit VPNPlatform(NEPacketTunnelProvider* tunnel) : m_Tunnel{tunnel} + explicit VPNPlatform(NEPacketTunnelProvider* tunnel, Context* context) + : m_Tunnel{tunnel}, m_Context{context} {} std::shared_ptr ObtainInterface(vpn::InterfaceInfo) override { - return std::make_shared(m_Tunnel); + return std::make_shared(m_Tunnel, m_Context); } }; @@ -113,16 +137,20 @@ namespace llarp::apple {} void - FrameworkContext::Start() + FrameworkContext::Start(std::string_view bootstrap) { std::promise result; - m_Runner = std::make_unique([&result, this]() { + m_Runner = std::make_unique([&result, bootstrap = std::string{bootstrap}, this]() { const RuntimeOptions opts{}; try { + auto config = llarp::Config::NetworkExtensionConfig(); + config->bootstrap.files.emplace_back(bootstrap); + config->dns.m_bind = DefaultDNSBind; + config->dns.m_upstreamDNS.push_back(DefaultUpstreamDNS); + Configure(std::move(config)); Setup(opts); - Configure(llarp::Config::NetworkExtensionConfig()); } catch (std::exception&) { @@ -140,7 +168,7 @@ namespace llarp::apple std::shared_ptr FrameworkContext::makeVPNPlatform() { - return std::make_shared(m_Tunnel); + return std::make_shared(m_Tunnel, this); } } @@ -154,9 +182,10 @@ struct ContextWrapper {} void - Start() + Start(std::string_view bootstrap) { - m_Context->Start(); + llarp::LogContext::Instance().logStream.reset(new llarp::NSLogStream{}); + m_Context->Start(std::move(bootstrap)); } void @@ -167,35 +196,42 @@ struct ContextWrapper } }; -static std::string_view -DataAsStringView(NSData* data) -{ - return std::string_view{reinterpret_cast(data.bytes), data.length}; -} - -static NSData* -StringViewToData(std::string_view data) -{ - const char* ptr = data.data(); - const size_t sz = data.size(); - return [NSData dataWithBytes:ptr length:sz]; -} - @implementation LLARPPacketTunnel - (void)startTunnelWithOptions:(NSDictionary*)options completionHandler:(void (^)(NSError*))completionHandler { - NSLog(@"OMG startTunnelWithOptions"); + llarp::huint32_t addr_{}; + llarp::huint32_t mask_{}; + if (auto maybe = llarp::FindFreeRange()) + { + addr_ = llarp::net::TruncateV6(maybe->addr); + mask_ = llarp::net::TruncateV6(maybe->netmask_bits); + } + NSString* addr = StringToNSString(addr_.ToString()); + NSString* mask = StringToNSString(mask_.ToString()); + + NSBundle* main = [NSBundle mainBundle]; + NSString* res = [main pathForResource:@"bootstrap" ofType:@"signed"]; + NSData* path = [res dataUsingEncoding:NSUTF8StringEncoding]; + m_Context = new ContextWrapper{self}; - m_Context->Start(); - [self setTunnelNetworkSettings:nullptr completionHandler:completionHandler]; + m_Context->Start(DataAsStringView(path)); + + NEPacketTunnelNetworkSettings* settings = + [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.0.0.1"]; + NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[addr]]; + NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[addr] + subnetMasks:@[@"255.255.255.255"]]; + ipv4.includedRoutes = @[[[NEIPv4Route alloc] initWithDestinationAddress:addr subnetMask:mask]]; + settings.IPv4Settings = ipv4; + settings.DNSSettings = dns; + [self setTunnelNetworkSettings:settings completionHandler:completionHandler]; } - (void)stopTunnelWithReason:(NEProviderStopReason)reason completionHandler:(void (^)(void))completionHandler { - NSLog(@"STOP TUNNEL"); if (m_Context) { m_Context->Stop(); @@ -209,9 +245,6 @@ StringViewToData(std::string_view data) completionHandler:(void (^)(NSData* responseData))completionHandler { const auto data = DataAsStringView(messageData); - LogInfo("app message: ", data); - completionHandler(StringViewToData("ok")); } - @end diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index 81a02fad3..8da523b22 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -74,7 +74,7 @@ namespace llarp }); m_PacketRouter = std::make_unique( [this](net::IPPacket pkt) { HandleGotUserPacket(std::move(pkt)); }); -#ifdef ANDROID +#if defined(ANDROID) || defined(__APPLE__) m_Resolver = std::make_shared(r, this); m_PacketRouter->AddUDPHandler(huint16_t{53}, [&](net::IPPacket pkt) { const size_t ip_header_size = (pkt.Header()->ihl * 4); diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index 38045d6c4..8e880a187 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace llarp::rpc { @@ -561,6 +562,47 @@ namespace llarp::rpc }); }); }) + .add_request_command( + "dns_query", + [&](oxenmq::Message& msg) { + HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) { + std::string endpoint{"default"}; + if (const auto itr = obj.find("endpoint"); itr != obj.end()) + { + endpoint = itr->get(); + } + std::string qname{}; + dns::QType_t qtype = dns::qTypeA; + if (const auto itr = obj.find("qname"); itr != obj.end()) + { + qname = itr->get(); + } + + if (const auto itr = obj.find("qtype"); itr != obj.end()) + { + qtype = itr->get(); + } + + dns::Message msg{dns::Question{qname, qtype}}; + + if (auto ep_ptr = (GetEndpointByName(r, endpoint))) + { + if (auto ep = reinterpret_cast(ep_ptr.get())) + { + if (ep->ShouldHookDNSMessage(msg)) + { + ep->HandleHookedDNSMessage(std::move(msg), [reply](dns::Message msg) { + reply(CreateJSONResponse(msg.ToJSON())); + }); + return; + } + } + reply(CreateJSONError("dns query not accepted by endpoint")); + return; + } + reply(CreateJSONError("no such endpoint for dns query")); + }); + }) .add_request_command("config", [&](oxenmq::Message& msg) { HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) { {