// Copyright 2019 The Chromium Authors. All rights reserved. // 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 #include #include #include #include #include #include #include #include "android_webview/browser_jni_headers/AwPacProcessor_jni.h" #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/bind.h" #include "base/logging.h" #include "base/memory/raw_ptr.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_isolation_key.h" #include "net/proxy_resolution/pac_file_data.h" #include "net/proxy_resolution/proxy_info.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; class NetworkIsolationKey; 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( dlsym(RTLD_DEFAULT, "android_getaddrinfofornetwork")); DCHECK(ptr); return ptr; }(); return getaddrinfofornetwork(network, node, service, hints, res); } scoped_refptr GetTaskRunner() { struct ThreadHolder { base::Thread thread_; scoped_refptr 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 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 CreateRequest( const std::string& hostname, net::ProxyResolveDnsOperation operation, const net::NetworkIsolationKey&) override { return std::make_unique(hostname, operation, net_handle_, link_addresses_); } void SetNetworkAndLinkAddresses( const net_handle_t net_handle, const std::vector& link_addresses) { link_addresses_ = link_addresses; net_handle_ = net_handle; } private: net_handle_t net_handle_ = 0; std::vector 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& 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& 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 results_; std::vector 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 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 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 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 request_; }; AwPacProcessor::AwPacProcessor() { host_resolver_ = std::make_unique(); } 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 (auto* job : jobs_) { job->Cancel(); } proxy_resolver_.reset(); event->Signal(); } void AwPacProcessor::DestroyNative( JNIEnv* env, const base::android::JavaParamRef& obj) { delete this; } void AwPacProcessor::SetProxyScriptNative( std::unique_ptr* request, const std::string& script, net::CompletionOnceCallback complete) { DCHECK(GetTaskRunner()->BelongsToCurrentThread()); GetProxyResolverFactory()->CreateProxyResolverV8Tracing( net::PacFileData::FromUTF8(script), std::make_unique(host_resolver_.get()), &proxy_resolver_, std::move(complete), request); } void AwPacProcessor::MakeProxyRequestNative( std::unique_ptr* request, const std::string& url, net::ProxyInfo* proxy_info, net::CompletionOnceCallback complete) { DCHECK(GetTaskRunner()->BelongsToCurrentThread()); if (proxy_resolver_) { proxy_resolver_->GetProxyForURL( GURL(url), net::NetworkIsolationKey(), proxy_info, std::move(complete), request, std::make_unique(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& obj, const JavaParamRef& 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()) { *result = job.proxy_info().ToPacString(); return true; } else { return false; } } ScopedJavaLocalRef AwPacProcessor::MakeProxyRequest( JNIEnv* env, const JavaParamRef& obj, const JavaParamRef& 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& jlink_addresses) { std::vector string_link_addresses; base::android::AppendJavaStringArrayToStringVector(env, jlink_addresses, &string_link_addresses); std::vector 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(processor); } static void JNI_AwPacProcessor_InitializeEnvironment(JNIEnv* env) { base::ThreadPoolInstance::CreateAndStartWithDefaultParams("AwPacProcessor"); } } // namespace android_webview