blob: 452d908545d279e6d223d2afffa228694dae5bdb [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DNS_PROXY_DOH_CURL_CLIENT_H_
#define DNS_PROXY_DOH_CURL_CLIENT_H_
#include <curl/curl.h>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/time/time.h>
namespace dns_proxy {
// List of HTTP status codes.
constexpr int64_t kHTTPOk = 200;
constexpr int64_t kHTTPTooManyRequests = 429;
class DoHCurlClientInterface {
public:
// Struct to be returned on a curl request.
struct CurlResult {
CurlResult(CURLcode curl_code, int64_t http_code, int64_t retry_delay_ms);
CURLcode curl_code;
int64_t http_code;
int64_t retry_delay_ms;
};
// Callback to be invoked back to the client upon request completion.
// |ctx| is an argument passed by the caller of `Resolve(...)` and passed
// back to the caller as-is through this callback. |ctx| is owned by the
// caller of `Resolve(...)` and the caller is responsible of its lifecycle.
// DoHCurlClient does not own |ctx| and must not interact with |ctx|.
// back to the caller as-is through this callback.
// |res| stores the CURL result code, HTTP code, retry delay of the CURL
// query.
// |msg| and |len| respectively stores the response and length of the
// response of the CURL query.
using QueryCallback = base::RepeatingCallback<void(
void* ctx, const CurlResult& res, unsigned char* msg, size_t len)>;
virtual ~DoHCurlClientInterface() = default;
// Resolve DNS address through DNS-over-HTTPS using DNS query |msg| of size
// |len|. |callback| will be called with |ctx| as its parameter upon query
// completion. `SetNameServers(...)` and SetDoHProviders(...)` must be called
// before calling this function.
// |msg| and |ctx| is owned by the caller of this function. The caller is
// responsible for their lifecycle.
virtual bool Resolve(const char* msg,
int len,
const QueryCallback& callback,
void* ctx) = 0;
// Set standard DNS and DoH servers for running `Resolve(...)`.
virtual void SetNameServers(const std::vector<std::string>& name_servers) = 0;
virtual void SetDoHProviders(
const std::vector<std::string>& doh_providers) = 0;
};
// DoHCurlClient receives a wire-format DNS query and re-send it using secure
// DNS (DNS-over-HTTPS). The caller of DoHCurlClient will get a wire-format
// response done through CURL. Given multiple DoH servers, DoHCurlClient will
// query each servers concurrently. It will return only the first successful
// response OR the last failing response.
class DoHCurlClient : public DoHCurlClientInterface {
public:
DoHCurlClient(base::TimeDelta timeout, int max_concurrent_queries);
explicit DoHCurlClient(base::TimeDelta timeout);
DoHCurlClient(const DoHCurlClient&) = delete;
DoHCurlClient& operator=(const DoHCurlClient&) = delete;
virtual ~DoHCurlClient();
// Resolve DNS address through DNS-over-HTTPS using DNS query |msg| of size
// |len|. |callback| will be called with |ctx| as its parameter upon query
// completion. `SetNameServers(...)` and SetDoHProviders(...)` must be called
// before calling this function.
// |msg| and |ctx| is owned by the caller of this function. The caller is
// responsible for their lifecycle.
bool Resolve(const char* msg,
int len,
const DoHCurlClientInterface::QueryCallback& callback,
void* ctx) override;
// Set standard DNS and DoH servers for running `Resolve(...)`.
void SetNameServers(const std::vector<std::string>& name_servers) override;
void SetDoHProviders(const std::vector<std::string>& doh_providers) override;
// Returns a weak pointer to ensure that callbacks don't run after this class
// is destroyed.
base::WeakPtr<DoHCurlClient> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
// State of an individual query.
struct State {
State(CURL* curl, const QueryCallback& callback, void* ctx, int request_id);
~State();
// Fetch the necessary response and run |callback|.
void RunCallback(CURLMsg* curl_msg, int64_t http_code);
// Set DNS response |msg| of length |len| to |response|.
void SetResponse(char* msg, size_t len);
// Stores the CURL handle for the query.
CURL* curl;
// Stores the response of a query.
std::vector<uint8_t> response;
// Stores the header response.
std::vector<std::string> header;
// |callback| given from the client will be called with |ctx| as its
// parameter. |ctx| is owned by the caller of `Resolve(...)` and will
// be returned to the caller as-is through the parameter of |callback|.
// |ctx| is owned by the caller of `Resolve(...)` and must not be changed
// here.
QueryCallback callback;
void* ctx;
// |header_list| is owned by this struct. It is stored here in order to
// free it when the request is done.
curl_slist* header_list;
// Upon calling resolve, all available DoH providers will be queried
// concurrently. |request_id| is an identifier shared by the queries made
// for a single `Resolve(...)` call.
int request_id;
};
// Initialize CURL handle to resolve wire-format data |data| of length |len|.
// This is done by querying DoH provider |doh_provider|.
// A state containing the CURL handle will be allocated and used to store
// CURL data such as header list and response.
// Lifecycle of the state is handled by the caller of this function.
std::unique_ptr<State> InitCurl(const std::string& doh_provider,
const char* msg,
int len,
const QueryCallback& callback,
void* ctx);
// Callback informed about what to wait for. When called, register or remove
// the socket given from watchers.
// This method signature matches CURL `socket_callback(...)`.
// This callback is registered through CURL.
// |userp| is owned by DoHCurlClient and should be cleaned properly upon
// query completion.
static int SocketCallback(CURL* easy,
curl_socket_t socket_fd,
int what,
void* userp,
void* socketp);
// Callback necessary for CURL to write data, method signature matches CURL
// `write_callback(...)`. This callback is registered through CURL.
//
// This callback function gets called by libcurl as soon as there is data
// received that needs to be saved. For most transfers, this callback gets
// called many times and each invoke delivers another chunk of data.
// |ptr| points to the delivered data, and the size of that data is |nmemb|.
// |size| is always 1. |userdata| refers to the data given to CURL through
// CURLOPT_WRITEDATA option.
//
// |ptr| is owned by CURL and must not be altered in this function.
// |userdata| is owned by DoHCurlClient and should be cleaned properly upon
// query completion.
static size_t WriteCallback(char* ptr,
size_t size,
size_t nmemb,
void* userdata);
// Callback necessary for CURL to write header data.
static size_t HeaderCallback(char* data,
size_t len,
size_t nitems,
void* userp);
// Callback informed when a query timed out.
void TimeoutCallback();
// Callback informed to start the query and to handle timeout, method
// signature matches CURL `timer_callback(...)`. This callback is registered
// through CURL.
//
// |timeout_ms| value of -1 passed to this callback means the caller should
// delete the timer. All other values are valid expire times, in milliseconds.
// |userp| refers to the pointer given to CURL through CURLMOPT_TIMERDATA
// option.
//
// |userp| is owned by DoHCurlClient and should be cleaned properly upon
// query completion.
static int TimerCallback(CURLM* multi, long timeout_ms, void* userp);
// Methods to update CURL socket watchers for asynchronous CURL events.
// When an action is observed, `CheckMultiInfo()` will be called.
void AddReadWatcher(curl_socket_t socket_fd);
void AddWriteWatcher(curl_socket_t socket_fd);
void RemoveWatcher(curl_socket_t socket_fd);
// Callback called whenever an event is ready to be handled by CURL on
// |socket_fd|. This callback is registered through the core event loop.
void OnFileCanReadWithoutBlocking(curl_socket_t socket_fd);
void OnFileCanWriteWithoutBlocking(curl_socket_t socket_fd);
// Checks for a querycompletion and handles its result.
// These functions are called when CURL just finished processing an event.
// |curl_msg| is owned by CURL, DoHCurlClient should not care about its
// lifecycle.
void CheckMultiInfo();
void HandleResult(CURLMsg* curl_msg);
// Cancel an in-flight request denoted by a set of states |states|.
void CancelRequest(const std::set<State*>& states);
// Cancel in-flight request of identifier |request_id|.
void CancelRequest(int request_id);
// Timeout for a CURL query in seconds.
int64_t timeout_seconds_;
// Watchers for available event to be handled by CURL.
std::map<curl_socket_t,
std::unique_ptr<base::FileDescriptorWatcher::Controller>>
read_watchers_;
std::map<curl_socket_t,
std::unique_ptr<base::FileDescriptorWatcher::Controller>>
write_watchers_;
// |name_servers_| to resolve |doh_providers_| address.
std::string name_servers_;
// |doh_providers_| to resolve domain name using DoH.
std::vector<std::string> doh_providers_;
// Maximum number of DoH providers to be queried concurrently.
int max_concurrent_queries_;
// Current query's states keyed by it's CURL handle.
std::map<CURL*, std::unique_ptr<State>> states_;
// Upon calling resolve, all available DoH providers will be queried
// concurrently. |requests_| stores these queries together keyed by their
// unique identifier.
std::map<int, std::set<State*>> requests_;
// Stores ID of a request. |next_request_id| will be incremented for each
// resolve call to keep the unique value.
int next_request_id_;
// CURL multi handle to do asynchronous requests.
CURLM* curlm_;
base::WeakPtrFactory<DoHCurlClient> weak_factory_{this};
};
} // namespace dns_proxy
#endif // DNS_PROXY_DOH_CURL_CLIENT_H_