Skip to content

Commit

Permalink
mobile: Fix bugs in the Apple PAC proxy resolver
Browse files Browse the repository at this point in the history
Signed-off-by: Ali Beyad <abeyad@google.com>
  • Loading branch information
abeyad committed Oct 18, 2024
1 parent ee61634 commit 4d8e091
Show file tree
Hide file tree
Showing 19 changed files with 224 additions and 229 deletions.
1 change: 1 addition & 0 deletions mobile/library/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ envoy_cc_library(
"//library/common/http:header_utility_lib",
"//library/common/logger:logger_delegate_lib",
"//library/common/network:connectivity_manager_lib",
"//library/common/network:proxy_api_lib",
"//library/common/stats:utility_lib",
"//library/common/types:c_types_lib",
"@envoy//envoy/server:lifecycle_notifier_interface",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ NetworkConfigurationFilter::resolveProxy(Http::RequestHeaderMap& request_headers
Thread::MainThread::isMainOrTestThread(),
"NetworkConfigurationProxy PAC proxy resolver callback not running on main thread.");
// This is the callback invoked from the Apple APIs resolving the PAC file URL, which
// gets invoked on the Envoy thread. We keep a weak_ptr to this filter instance
// so that, if the stream is canceled and the filter chain is torn down in the meantime,
// we will fail to aquire the weak_ptr lock and won't execute any callbacks on the resolved
// gets invoked on a separate thread. We keep a weak_ptr to this filter instance so that,
// if the stream is canceled and the filter chain is torn down in the meantime, we will
// fail to aquire the weak_ptr lock and won't execute any callbacks on the resolved
// proxies.
if (auto filter_ptr = weak_self.lock()) {
filter_ptr->onProxyResolutionComplete(Network::ProxySettings::create(proxies));
Expand Down
8 changes: 8 additions & 0 deletions mobile/library/common/internal_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "source/common/runtime/runtime_features.h"

#include "absl/synchronization/notification.h"
#include "library/common/network/proxy_api.h"
#include "library/common/stats/utility.h"

namespace Envoy {
Expand Down Expand Up @@ -131,6 +132,13 @@ envoy_status_t InternalEngine::main(std::shared_ptr<Envoy::OptionsImplBase> opti
server_ = main_common->server();
event_dispatcher_ = &server_->dispatcher();

// If proxy resolution APIs are configured on the Engine, set the main thread's dispatcher
// on the proxy resolver for handling callbacks.
auto* proxy_resolver = static_cast<Network::ProxyResolverApi*>(Api::External::retrieveApi("envoy_proxy_resolver", /*allow_absent=*/true));
if (proxy_resolver != nullptr) {
proxy_resolver->resolver->setDispatcher(event_dispatcher_);
}

cv_.notifyAll();
}
END_TRY
Expand Down
4 changes: 4 additions & 0 deletions mobile/library/common/network/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ envoy_cc_library(
repository = "@envoy",
deps = [
":proxy_settings_lib",
"@envoy//envoy/event:dispatcher_interface",
],
)

Expand Down Expand Up @@ -150,6 +151,9 @@ envoy_cc_library(
"//library/common/apple:utility_lib",
"//library/common/extensions/cert_validator/platform_bridge:c_types_lib",
"//library/common/types:c_types_lib",
"@envoy//envoy/event:dispatcher_interface",
"@envoy//source/common/common:thread_impl_lib_posix",
"@envoy//source/common/common:thread_lib",
],
"//conditions:default": [],
}),
Expand Down
49 changes: 35 additions & 14 deletions mobile/library/common/network/apple_pac_proxy_resolver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ namespace Network {

namespace {

CFStringRef pacRunLoopMode() {
static const CFStringRef runloop_mode = CFSTR("envoy.PacProxyResolver");
return runloop_mode;
}

// Creates a CFURLRef from a C++ string URL.
CFURLRef createCFURL(const std::string& url_string) {
auto cf_url_string =
CFStringCreateWithCString(kCFAllocatorDefault, url_string.c_str(), kCFStringEncodingUTF8);
auto cf_url = CFURLCreateWithString(kCFAllocatorDefault, cf_url_string, /*baseURL=*/nullptr);
CFRelease(cf_url_string);
return cf_url;
return CFURLCreateWithBytes(kCFAllocatorDefault,
reinterpret_cast<const UInt8*>(url_string.c_str()),
url_string.length(), kCFStringEncodingUTF8, nullptr);
}

} // namespace
Expand All @@ -31,16 +34,15 @@ void proxyAutoConfigurationResultCallback(void* ptr, CFArrayRef cf_proxies, CFEr
std::unique_ptr<ProxySettingsResolvedCallback> completion_callback(
static_cast<ProxySettingsResolvedCallback*>(ptr));

std::vector<ProxySettings> proxies;
if (cf_error != nullptr || cf_proxies == nullptr) {
if (cf_error == nullptr) {
ENVOY_BUG(cf_error != nullptr, Apple::toString(CFErrorCopyDescription(cf_error)));
// Treat the error case as if no proxy was configured. Seems to be consistent with what iOS
// system (URLSession) is doing.
(*completion_callback)(proxies);
return;
}

for (int i = 0; i < CFArrayGetCount(cf_proxies); i++) {
// Treat the error case as if no proxy was configured. Seems to be consistent with what iOS
// system (URLSession) is doing.
const int num_proxy_entries = cf_proxies != nullptr ? CFArrayGetCount(cf_proxies) : 0;
std::vector<ProxySettings> proxies;
for (int i = 0; i < num_proxy_entries; i++) {
CFDictionaryRef cf_dictionary =
static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(cf_proxies, i));
CFStringRef cf_proxy_type =
Expand All @@ -65,6 +67,8 @@ void proxyAutoConfigurationResultCallback(void* ptr, CFArrayRef cf_proxies, CFEr
}

(*completion_callback)(proxies);

CFRunLoopStop(CFRunLoopGetCurrent());
}

CFRunLoopSourceRef
Expand All @@ -80,13 +84,28 @@ ApplePacProxyResolver::createPacUrlResolverSource(CFURLRef cf_proxy_autoconfigur
}

void ApplePacProxyResolver::resolveProxies(
const std::string& target_url, const std::string& proxy_autoconfiguration_file_url,
const std::string& proxy_autoconfiguration_file_url, const std::string& target_url,
ProxySettingsResolvedCallback proxy_resolution_completed) {
CFURLRef cf_target_url = createCFURL(target_url);
CFURLRef cf_proxy_autoconfiguration_file_url = createCFURL(proxy_autoconfiguration_file_url);
if (cf_target_url == nullptr || cf_proxy_autoconfiguration_file_url == nullptr) {
return;
}

std::unique_ptr<ProxySettingsResolvedCallback> completion_callback =
std::make_unique<ProxySettingsResolvedCallback>(std::move(proxy_resolution_completed));

// Work around <rdar://problem/5530166>. This dummy call to CFNetworkCopyProxiesForURL
// initializes some state within CFNetwork that is required by
// CFNetworkExecuteProxyAutoConfigurationURL.
CFDictionaryRef empty_dictionary =
CFDictionaryCreate(nullptr, nullptr, nullptr, 0, nullptr, nullptr);
CFArrayRef empty_result = CFNetworkCopyProxiesForURL(cf_target_url, empty_dictionary);
CFRelease(empty_dictionary);
if (empty_result) {
CFRelease(empty_result);
}

// According to https://developer.apple.com/documentation/corefoundation/cfstreamclientcontext,
// the version must be 0.
auto context = std::make_unique<CFStreamClientContext>(
Expand All @@ -102,7 +121,9 @@ void ApplePacProxyResolver::resolveProxies(
CFRunLoopSourceRef run_loop_source = createPacUrlResolverSource(
cf_proxy_autoconfiguration_file_url, cf_target_url, context.release());

CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source, kCFRunLoopDefaultMode);
CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source, pacRunLoopMode());
CFRunLoopRunInMode(pacRunLoopMode(), DBL_MAX, false);
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source, pacRunLoopMode());

CFRelease(cf_target_url);
CFRelease(cf_proxy_autoconfiguration_file_url);
Expand Down
6 changes: 3 additions & 3 deletions mobile/library/common/network/apple_pac_proxy_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ class ApplePacProxyResolver {
/**
* Resolves proxy for a given URL using proxy auto configuration file that's hosted at a given
* URL.
* @param target_url A request URL to resolve the proxy for.
* @param proxy_autoconfiguration_file_url A URL at which a proxy configuration file is hosted.
* @param target_url A request URL to resolve the proxy for.
* @param proxy_resolution_completed A function that's called with result proxies as its
* arguments when proxy resolution completes.
*/
void resolveProxies(const std::string& target_url,
const std::string& proxy_autoconfiguration_file_url,
void resolveProxies(const std::string& proxy_autoconfiguration_file_url,
const std::string& target_url,
ProxySettingsResolvedCallback proxy_resolution_completed);

protected:
Expand Down
41 changes: 35 additions & 6 deletions mobile/library/common/network/apple_proxy_resolver.cc
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
#include "library/common/network/apple_proxy_resolver.h"

#include "source/common/common/assert.h"
#include "source/common/common/posix/thread_impl.h"

namespace Envoy {
namespace Network {

AppleProxyResolver::AppleProxyResolver()
: proxy_settings_monitor_(
std::make_unique<AppleSystemProxySettingsMonitor>(proxySettingsUpdater())),
pac_proxy_resolver_(std::make_unique<ApplePacProxyResolver>()) {}
pac_proxy_resolver_(std::make_unique<ApplePacProxyResolver>()),
thread_factory_(Thread::PosixThreadFactory::create()) {}

AppleProxyResolver::~AppleProxyResolver() {
// Wait for the PAC resolution threads to finish.
for (auto& [_, thread] : pac_resolution_threads_) {
if (thread->joinable()) {
thread->join();
}
}
}

SystemProxySettingsReadCallback AppleProxyResolver::proxySettingsUpdater() {
return SystemProxySettingsReadCallback(
Expand All @@ -23,11 +34,14 @@ void AppleProxyResolver::start() {
started_ = true;
}

void AppleProxyResolver::setDispatcher(Event::Dispatcher* dispatcher) { dispatcher_ = dispatcher; }

ProxyResolutionResult
AppleProxyResolver::resolveProxy(const std::string& target_url_string,
std::vector<ProxySettings>& proxies,
ProxySettingsResolvedCallback proxy_resolution_completed) {
ASSERT(started_, "AppleProxyResolver not started.");
ASSERT(dispatcher_ != nullptr, "Dispatcher not set on the AppleProxyResolver.");

std::string pac_file_url;
{
Expand Down Expand Up @@ -55,11 +69,26 @@ AppleProxyResolver::resolveProxy(const std::string& target_url_string,
// implement a caching layer with TTLs for PAC file URL resolution within AppleProxyResolver
// itself.
ASSERT(!pac_file_url.empty(), "PAC file URL must not be empty if PAC is enabled.");
pac_proxy_resolver_->resolveProxies(
pac_file_url, target_url_string,
[proxy_resolution_completed](const std::vector<ProxySettings>& proxies) {
proxy_resolution_completed(proxies);
});
Thread::PosixThreadPtr thread = thread_factory_->createThread(
[this, pac_file_url, target_url_string, callback = std::move(proxy_resolution_completed)] {
pac_proxy_resolver_->resolveProxies(
pac_file_url, target_url_string,
[this, callback](const std::vector<ProxySettings>& proxies) {
auto pac_resolve_thread_id = thread_factory_->currentPthreadId();
dispatcher_->post([this, callback, proxies, pac_resolve_thread_id] {
auto thread_handle = pac_resolution_threads_.extract(pac_resolve_thread_id);
if (!thread_handle.empty()) {
thread_handle.mapped()->join();
}

callback(proxies);
});
});
},
/* options= */ absl::nullopt, /* crash_on_failure=*/false);
Thread::ThreadId thread_id = thread->pthreadId();
pac_resolution_threads_[thread_id] = std::move(thread);

return ProxyResolutionResult::ResultInProgress;
}

Expand Down
13 changes: 12 additions & 1 deletion mobile/library/common/network/apple_proxy_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
#include <functional>
#include <memory>

#include "envoy/event/dispatcher.h"

#include "source/common/common/posix/thread_impl.h"
#include "source/common/common/thread.h"

#include "absl/container/flat_hash_map.h"
#include "library/common/network/apple_pac_proxy_resolver.h"
#include "library/common/network/apple_system_proxy_settings_monitor.h"
#include "library/common/network/proxy_resolver_interface.h"
Expand All @@ -20,7 +26,7 @@ namespace Network {
class AppleProxyResolver : public ProxyResolver {
public:
AppleProxyResolver();
virtual ~AppleProxyResolver() = default;
virtual ~AppleProxyResolver();

/**
* Starts proxy resolver. It needs to be called prior to any proxy resolution attempt.
Expand All @@ -38,6 +44,8 @@ class AppleProxyResolver : public ProxyResolver {
resolveProxy(const std::string& target_url_string, std::vector<ProxySettings>& proxies,
ProxySettingsResolvedCallback proxy_resolution_completed) override;

virtual void setDispatcher(Event::Dispatcher* dispatcher) override;

/*
* Supplies a function that updates this instance's proxy settings.
*/
Expand All @@ -49,6 +57,9 @@ class AppleProxyResolver : public ProxyResolver {
std::unique_ptr<AppleSystemProxySettingsMonitor> proxy_settings_monitor_;
std::unique_ptr<ApplePacProxyResolver> pac_proxy_resolver_;
absl::optional<SystemProxySettings> proxy_settings_;
std::unique_ptr<Thread::PosixThreadFactory> thread_factory_;
absl::flat_hash_map<Thread::ThreadId, Thread::PosixThreadPtr> pac_resolution_threads_;
Event::Dispatcher* dispatcher_;
absl::Mutex mutex_;
bool started_ = false;
};
Expand Down
10 changes: 10 additions & 0 deletions mobile/library/common/network/proxy_resolver_interface.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include "envoy/event/dispatcher.h"

#include "library/common/network/proxy_settings.h"

namespace Envoy {
Expand Down Expand Up @@ -28,6 +30,14 @@ class ProxyResolver {
virtual ProxyResolutionResult
resolveProxy(const std::string& target_url_string, std::vector<ProxySettings>& proxies,
ProxySettingsResolvedCallback proxy_resolution_completed) PURE;

/**
* Sets the Event Dispatcher that a proxy resolver instance can use to post callbacks to.
* Typically, this would be the main Envoy thread's dispatcher (which is also the worker thread
* in Envoy Mobile).
* @param dispatcher The event dispatcher.
*/
virtual void setDispatcher(Event::Dispatcher* dispatcher) PURE;
};

} // namespace Network
Expand Down
2 changes: 0 additions & 2 deletions mobile/test/common/proxy/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ envoy_cc_test_library(
srcs = select({
"@envoy//bazel:apple": [
"test_apple_api_registration.cc",
"test_apple_pac_proxy_resolver.cc",
"test_apple_proxy_resolver.cc",
"test_apple_proxy_settings_monitor.cc",
],
Expand All @@ -18,7 +17,6 @@ envoy_cc_test_library(
hdrs = select({
"@envoy//bazel:apple": [
"test_apple_api_registration.h",
"test_apple_pac_proxy_resolver.h",
"test_apple_proxy_resolver.h",
"test_apple_proxy_settings_monitor.h",
],
Expand Down
6 changes: 0 additions & 6 deletions mobile/test/common/proxy/test_apple_api_registration.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "test/common/proxy/test_apple_api_registration.h"

#include "test/common/proxy/test_apple_pac_proxy_resolver.h"
#include "test/common/proxy/test_apple_proxy_resolver.h"
#include "test/common/proxy/test_apple_proxy_settings_monitor.h"

Expand Down Expand Up @@ -30,11 +29,6 @@ void registerTestAppleProxyResolver(absl::string_view host, int port, const bool
test_resolver->setSettingsMonitorForTest(
std::make_unique<Envoy::Network::TestAppleSystemProxySettingsMonitor>(
std::string(host), port, use_pac_resolver, test_resolver->proxySettingsUpdater()));
if (use_pac_resolver) {
// Create a TestApplePacProxyResolver and set the test resolver to use it.
test_resolver->setPacResolverForTest(
std::make_unique<Envoy::Network::TestApplePacProxyResolver>(std::string(host), port));
}
// Start the resolver, as we do when registering the envoy_proxy_resolver API.
test_resolver->start();
// Create a new test ProxyResolverApi.
Expand Down
Loading

0 comments on commit 4d8e091

Please sign in to comment.