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) {
{