// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "android_webview/browser/aw_pac_processor.h"
#include <android/multinetwork.h>
#include <arpa/inet.h>
#include <dlfcn.h>
#include <netdb.h>
#include <unistd.h>
#include <cstddef>
#include <memory>
#include <string>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/threading/thread_restrictions.h"
#include "net/base/address_list.h"
#include "net/base/completion_once_callback.h"
#include "net/base/net_errors.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/network_isolation_key.h"
#include "net/proxy_resolution/pac_file_data.h"
#include "net/proxy_resolution/proxy_info.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "android_webview/browser_jni_headers/AwPacProcessor_jni.h"
using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
using base::android::JavaRef;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
namespace android_webview {
namespace {
static_assert(NETWORK_UNSPECIFIED == 0,
"Java side AwPacProcessor#NETWORK_UNSPECIFIED needs update");
typedef int (*getaddrinfofornetwork_ptr_t)(net_handle_t,
const char*,
const char*,
const struct addrinfo*,
struct addrinfo**);
// The definition of android_getaddrinfofornetwork is conditional
// on __ANDROID_API__ >= 23. It's not possible to just have a runtime check for
// the SDK level to guard a call that might not exist on older platform
// versions: all native function imports are resolved at load time and loading
// the library will fail if they're unresolvable. Therefore we need to search
// for the function via dlsym.
int AndroidGetAddrInfoForNetwork(net_handle_t network,
const char* node,
const char* service,
const struct addrinfo* hints,
struct addrinfo** res) {
static getaddrinfofornetwork_ptr_t getaddrinfofornetwork = [] {
getaddrinfofornetwork_ptr_t ptr =
reinterpret_cast<getaddrinfofornetwork_ptr_t>(
dlsym(RTLD_DEFAULT, "android_getaddrinfofornetwork"));
DCHECK(ptr);
return ptr;
}();
return getaddrinfofornetwork(network, node, service, hints, res);
}
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() {
struct ThreadHolder {
base::Thread thread_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
ThreadHolder() : thread_("AwPacProcessor") {
thread_.Start();
task_runner_ = thread_.task_runner();
}
};
static ThreadHolder thread_holder;
return thread_holder.task_runner_;
}
proxy_resolver::ProxyResolverV8TracingFactory* GetProxyResolverFactory() {
static std::unique_ptr<proxy_resolver::ProxyResolverV8TracingFactory>
factory = proxy_resolver::ProxyResolverV8TracingFactory::Create();
return factory.get();
}
} // namespace
// TODO(amalova): We could use a separate thread or thread pool for executing
// blocking DNS queries, to get better performance.
class HostResolver : public proxy_resolver::ProxyHostResolver {
public:
std::unique_ptr<proxy_resolver::ProxyHostResolver::Request> CreateRequest(
const std::string& hostname,
net::ProxyResolveDnsOperation operation,
const net::NetworkAnonymizationKey&) override {
return std::make_unique<RequestImpl>(hostname, operation, net_handle_,
link_addresses_);
}
void SetNetworkAndLinkAddresses(
const net_handle_t net_handle,
const std::vector<net::IPAddress>& link_addresses) {
link_addresses_ = link_addresses;
net_handle_ = net_handle;
}
private:
net_handle_t net_handle_ = 0;
std::vector<net::IPAddress> link_addresses_;
class RequestImpl : public proxy_resolver::ProxyHostResolver::Request {
public:
RequestImpl(const std::string& hostname,
net::ProxyResolveDnsOperation operation,
net_handle_t net_handle,
const std::vector<net::IPAddress>& link_addresses)
: hostname_(hostname),
operation_(operation),
net_handle_(net_handle),
link_addresses_(link_addresses) {}
~RequestImpl() override = default;
int Start(net::CompletionOnceCallback callback) override {
bool success = false;
switch (operation_) {
case net::ProxyResolveDnsOperation::DNS_RESOLVE:
success = DnsResolveImpl(hostname_);
break;
case net::ProxyResolveDnsOperation::DNS_RESOLVE_EX:
success = DnsResolveExImpl(hostname_);
break;
case net::ProxyResolveDnsOperation::MY_IP_ADDRESS:
success = MyIpAddressImpl();
break;
case net::ProxyResolveDnsOperation::MY_IP_ADDRESS_EX:
success = MyIpAddressExImpl();
break;
}
return success ? net::OK : net::ERR_NAME_RESOLUTION_FAILED;
}
const std::vector<net::IPAddress>& GetResults() const override {
return results_;
}
private:
bool IsNetworkSpecified() { return net_handle_ != NETWORK_UNSPECIFIED; }
bool MyIpAddressImpl() {
// For network-aware queries the results are set from Java on
// NetworkCallback#onLinkPropertiesChanged.
// See SetNetworkAndLinkAddresses.
if (IsNetworkSpecified()) {
results_.push_back(link_addresses_.front());
return true;
}
std::string my_hostname = GetHostName();
if (my_hostname.empty())
return false;
return DnsResolveImpl(my_hostname);
}
bool MyIpAddressExImpl() {
if (IsNetworkSpecified()) {
results_ = link_addresses_;
return true;
}
std::string my_hostname = GetHostName();
if (my_hostname.empty())
return false;
return DnsResolveExImpl(my_hostname);
}
bool DnsResolveImpl(const std::string& host) {
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
struct addrinfo* res = nullptr;
int result = IsNetworkSpecified()
? AndroidGetAddrInfoForNetwork(net_handle_, host.c_str(),
nullptr, &hints, &res)
: getaddrinfo(host.c_str(), nullptr, &hints, &res);
if (result != 0) {
return false;
}
net::AddressList address_list = net::AddressList::CreateFromAddrinfo(res);
results_.push_back(address_list.front().address());
freeaddrinfo(res);
return !results_.empty();
}
bool DnsResolveExImpl(const std::string& host) {
struct addrinfo* res = nullptr;
int result = IsNetworkSpecified()
? AndroidGetAddrInfoForNetwork(net_handle_, host.c_str(),
nullptr, nullptr, &res)
: getaddrinfo(host.c_str(), nullptr, nullptr, &res);
if (result != 0) {
return false;
}
net::AddressList address_list = net::AddressList::CreateFromAddrinfo(res);
for (net::IPEndPoint endpoint : address_list.endpoints()) {
results_.push_back(endpoint.address());
}
freeaddrinfo(res);
return !results_.empty();
}
std::string GetHostName() {
char buffer[HOST_NAME_MAX + 1];
if (gethostname(buffer, HOST_NAME_MAX + 1) != 0) {
return std::string();
}
// It's unspecified whether gethostname NULL-terminates if the hostname
// must be truncated and no error is returned if that happens.
buffer[HOST_NAME_MAX] = '\0';
return std::string(buffer);
}
const std::string hostname_;
const net::ProxyResolveDnsOperation operation_;
net_handle_t net_handle_;
std::vector<net::IPAddress> results_;
std::vector<net::IPAddress> link_addresses_;
};
};
class Bindings : public proxy_resolver::ProxyResolverV8Tracing::Bindings {
public:
Bindings(HostResolver* host_resolver) : host_resolver_(host_resolver) {}
void Alert(const std::u16string& message) override {}
void OnError(int line_number, const std::u16string& message) override {}
proxy_resolver::ProxyHostResolver* GetHostResolver() override {
return host_resolver_;
}
net::NetLogWithSource GetNetLogWithSource() override {
return net::NetLogWithSource();
}
private:
raw_ptr<HostResolver> host_resolver_;
};
// Public methods of AwPacProcessor may be called on multiple threads.
// ProxyResolverV8TracingFactory/ProxyResolverV8Tracing
// expects its public interface to always be called on the same thread with
// Chromium task runner so it can post it back to that thread
// with the result of the queries.
//
// Job and its subclasses wrap queries from public methods of AwPacProcessor,
// post them on a special thread and blocks on WaitableEvent
// until the query is finished. |OnSignal| is passed to
// ProxyResolverV8TracingFactory/ProxyResolverV8Tracing methods.
// This callback is called once the request is processed and,
// it signals WaitableEvent and returns result to the calling thread.
//
// ProxyResolverV8TracingFactory/ProxyResolverV8Tracing behaviour is the
// following: if the corresponding request is destroyed,
// the query is cancelled and the callback is never called.
// That means that we need to signal WaitableEvent to unblock calling thread
// when we cancel Job. We keep track of unfinished Jobs in |jobs_|. This field
// is always accessed on the same thread.
//
// All Jobs must be cancelled prior to destruction of |proxy_resolver_| since
// its destructor asserts there are no pending requests.
class Job {
public:
virtual ~Job() = default;
bool ExecSync() {
GetTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&Job::Exec, base::Unretained(this)));
event_.Wait();
return net_error_ == net::OK;
}
void Exec() {
processor_->jobs_.insert(this);
std::move(task_).Run();
}
virtual void Cancel() = 0;
void OnSignal(int net_error) {
net_error_ = net_error;
// Both SetProxyScriptJob#request_ and MakeProxyRequestJob#request_
// have to be destroyed on the same thread on which they are created.
// If we destroy them before callback is called the request_ is cancelled.
// Reset them here on the correct thread when the job is already finished
// so no cancellation occurs.
Cancel();
}
base::OnceClosure task_;
int net_error_ = net::ERR_ABORTED;
base::WaitableEvent event_;
raw_ptr<AwPacProcessor> processor_;
};
class SetProxyScriptJob : public Job {
public:
SetProxyScriptJob(AwPacProcessor* processor, std::string script) {
processor_ = processor;
task_ = base::BindOnce(
&AwPacProcessor::SetProxyScriptNative, base::Unretained(processor_),
&request_, std::move(script),
base::BindOnce(&SetProxyScriptJob::OnSignal, base::Unretained(this)));
}
void Cancel() override {
processor_->jobs_.erase(this);
request_.reset();
event_.Signal();
}
private:
std::unique_ptr<net::ProxyResolverFactory::Request> request_;
};
class MakeProxyRequestJob : public Job {
public:
MakeProxyRequestJob(AwPacProcessor* processor, std::string url) {
processor_ = processor;
task_ = base::BindOnce(
&AwPacProcessor::MakeProxyRequestNative, base::Unretained(processor_),
&request_, std::move(url), &proxy_info_,
base::BindOnce(&MakeProxyRequestJob::OnSignal, base::Unretained(this)));
}
void Cancel() override {
processor_->jobs_.erase(this);
request_.reset();
event_.Signal();
}
net::ProxyInfo proxy_info() { return proxy_info_; }
private:
net::ProxyInfo proxy_info_;
std::unique_ptr<net::ProxyResolver::Request> request_;
};
AwPacProcessor::AwPacProcessor() {
host_resolver_ = std::make_unique<HostResolver>();
}
AwPacProcessor::~AwPacProcessor() {
base::WaitableEvent event;
// |proxy_resolver_| must be destroyed on the same thread it is created.
GetTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&AwPacProcessor::Destroy, base::Unretained(this),
base::Unretained(&event)));
event.Wait();
}
void AwPacProcessor::Destroy(base::WaitableEvent* event) {
// Cancel all unfinished jobs to unblock calling thread.
for (Job* job : jobs_) {
job->Cancel();
}
proxy_resolver_.reset();
event->Signal();
}
void AwPacProcessor::DestroyNative(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
delete this;
}
void AwPacProcessor::SetProxyScriptNative(
std::unique_ptr<net::ProxyResolverFactory::Request>* request,
const std::string& script,
net::CompletionOnceCallback complete) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
GetProxyResolverFactory()->CreateProxyResolverV8Tracing(
net::PacFileData::FromUTF8(script),
std::make_unique<Bindings>(host_resolver_.get()), &proxy_resolver_,
std::move(complete), request);
}
void AwPacProcessor::MakeProxyRequestNative(
std::unique_ptr<net::ProxyResolver::Request>* request,
const std::string& url,
net::ProxyInfo* proxy_info,
net::CompletionOnceCallback complete) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
if (proxy_resolver_) {
proxy_resolver_->GetProxyForURL(
GURL(url), net::NetworkAnonymizationKey(), proxy_info,
std::move(complete), request,
std::make_unique<Bindings>(host_resolver_.get()));
} else {
std::move(complete).Run(net::ERR_FAILED);
}
}
bool AwPacProcessor::SetProxyScript(std::string script) {
SetProxyScriptJob job(this, script);
return job.ExecSync();
}
jboolean AwPacProcessor::SetProxyScript(JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& jscript) {
std::string script = ConvertJavaStringToUTF8(env, jscript);
return SetProxyScript(script);
}
bool AwPacProcessor::MakeProxyRequest(std::string url, std::string* result) {
MakeProxyRequestJob job(this, url);
if (job.ExecSync()) {
if (job.proxy_info().ContainsMultiProxyChain()) {
// Multi-proxy chains cannot be represented as a PAC string.
return false;
}
*result = job.proxy_info().ToPacString();
return true;
} else {
return false;
}
}
ScopedJavaLocalRef<jstring> AwPacProcessor::MakeProxyRequest(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& jurl) {
std::string url = ConvertJavaStringToUTF8(env, jurl);
std::string result;
if (MakeProxyRequest(url, &result)) {
return ConvertUTF8ToJavaString(env, result);
} else {
return nullptr;
}
}
void AwPacProcessor::SetNetworkAndLinkAddresses(
JNIEnv* env,
net_handle_t net_handle,
const base::android::JavaParamRef<jobjectArray>& jlink_addresses) {
std::vector<std::string> string_link_addresses;
base::android::AppendJavaStringArrayToStringVector(env, jlink_addresses,
&string_link_addresses);
std::vector<net::IPAddress> link_addresses;
for (const std::string& address : string_link_addresses) {
net::IPAddress ip_address;
if (ip_address.AssignFromIPLiteral(address)) {
link_addresses.push_back(ip_address);
} else {
LOG(ERROR) << "Not a supported IP literal: " << address;
}
}
GetTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&HostResolver::SetNetworkAndLinkAddresses,
base::Unretained(host_resolver_.get()),
net_handle, std::move(link_addresses)));
}
static jlong JNI_AwPacProcessor_CreateNativePacProcessor(JNIEnv* env) {
AwPacProcessor* processor = new AwPacProcessor();
return reinterpret_cast<intptr_t>(processor);
}
static void JNI_AwPacProcessor_InitializeEnvironment(JNIEnv* env) {
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("AwPacProcessor");
}
} // namespace android_webview