blob: 2680063b1f3ee4f707e8d04b8d2dd0f9d916e063 [file] [log] [blame]
Jason Jeremy Iman15c32782021-01-27 04:19:43 +09001// Copyright 2021 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef DNS_PROXY_DOH_CURL_CLIENT_H_
6#define DNS_PROXY_DOH_CURL_CLIENT_H_
7
8#include <curl/curl.h>
9
10#include <map>
11#include <memory>
Jason Jeremy Iman90512982021-01-31 18:43:58 +090012#include <set>
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090013#include <string>
14#include <vector>
15
16#include <base/files/file_descriptor_watcher_posix.h>
17#include <base/time/time.h>
18
19namespace dns_proxy {
20
Jason Jeremy Iman1bb71c22021-01-26 21:49:55 +090021// List of HTTP status codes.
22constexpr int64_t kHTTPOk = 200;
Jason Jeremy Iman845f2932021-01-31 16:12:13 +090023constexpr int64_t kHTTPTooManyRequests = 429;
Jason Jeremy Iman1bb71c22021-01-26 21:49:55 +090024
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090025// DoHCurlClient receives a wire-format DNS query and re-send it using secure
26// DNS (DNS-over-HTTPS). The caller of DoHCurlClient will get a wire-format
27// response done through CURL. Given multiple DoH servers, DoHCurlClient will
28// query each servers concurrently. It will return only the first successful
29// response OR the last failing response.
30class DoHCurlClient {
31 public:
Jason Jeremy Iman845f2932021-01-31 16:12:13 +090032 // Struct to be returned on a curl request.
33 struct CurlResult {
34 CurlResult(CURLcode curl_code, int64_t http_code, int64_t retry_delay_ms);
35
36 CURLcode curl_code;
37 int64_t http_code;
38 int64_t retry_delay_ms;
39 };
40
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090041 // Callback to be invoked back to the client upon request completion.
42 // |ctx| is an argument passed by the caller of `Resolve(...)` and passed
43 // back to the caller as-is through this callback. |ctx| is owned by the
44 // caller of `Resolve(...)` and the caller is responsible of its lifecycle.
45 // DoHCurlClient does not own |ctx| and must not interact with |ctx|.
Jason Jeremy Iman845f2932021-01-31 16:12:13 +090046 // back to the caller as-is through this callback.
47 // |res| stores the CURL result code, HTTP code, retry delay of the CURL
48 // query.
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090049 // |msg| and |len| respectively stores the response and length of the
50 // response of the CURL query.
Jason Jeremy Iman90512982021-01-31 18:43:58 +090051 using QueryCallback = base::RepeatingCallback<void(
Jason Jeremy Iman845f2932021-01-31 16:12:13 +090052 void* ctx, const CurlResult& res, uint8_t* msg, size_t len)>;
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090053
Jason Jeremy Iman90512982021-01-31 18:43:58 +090054 DoHCurlClient(base::TimeDelta timeout, int max_concurrent_queries);
Jason Jeremy Iman63fd8152021-02-01 05:28:04 +090055 explicit DoHCurlClient(base::TimeDelta timeout);
56 virtual ~DoHCurlClient();
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090057
58 // Resolve DNS address through DNS-over-HTTPS using DNS query |msg| of size
59 // |len|. |callback| will be called with |ctx| as its parameter upon query
60 // completion. `SetNameServers(...)` and SetDoHProviders(...)` must be called
61 // before calling this function.
62 // |msg| and |ctx| is owned by the caller of this function. The caller is
63 // responsible for their lifecycle.
Jason Jeremy Iman63fd8152021-02-01 05:28:04 +090064 virtual bool Resolve(const char* msg,
65 int len,
66 const QueryCallback& callback,
67 void* ctx);
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090068
69 // Set standard DNS and DoH servers for running `Resolve(...)`.
Jason Jeremy Iman63fd8152021-02-01 05:28:04 +090070 virtual void SetNameServers(const std::vector<std::string>& name_servers);
71 virtual void SetDoHProviders(const std::vector<std::string>& doh_providers);
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090072
73 private:
Jason Jeremy Iman90512982021-01-31 18:43:58 +090074 // State of an individual query.
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090075 struct State {
Jason Jeremy Iman90512982021-01-31 18:43:58 +090076 State(CURL* curl, const QueryCallback& callback, void* ctx, int request_id);
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090077 ~State();
78
79 // Fetch the necessary response and run |callback|.
Jason Jeremy Iman90512982021-01-31 18:43:58 +090080 void RunCallback(CURLMsg* curl_msg, int64_t http_code);
Jason Jeremy Iman845f2932021-01-31 16:12:13 +090081
Jason Jeremy Iman90512982021-01-31 18:43:58 +090082 // Set DNS response |msg| of length |len| to |response|.
Jason Jeremy Iman845f2932021-01-31 16:12:13 +090083 void SetResponse(char* msg, size_t len);
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090084
Jason Jeremy Iman90512982021-01-31 18:43:58 +090085 // Stores the CURL handle for the query.
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090086 CURL* curl;
87
Jason Jeremy Iman90512982021-01-31 18:43:58 +090088 // Stores the response of a query.
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090089 std::vector<uint8_t> response;
90
Jason Jeremy Iman845f2932021-01-31 16:12:13 +090091 // Stores the header response.
92 std::vector<std::string> header;
93
Jason Jeremy Iman15c32782021-01-27 04:19:43 +090094 // |callback| given from the client will be called with |ctx| as its
95 // parameter. |ctx| is owned by the caller of `Resolve(...)` and will
96 // be returned to the caller as-is through the parameter of |callback|.
97 // |ctx| is owned by the caller of `Resolve(...)` and must not be changed
98 // here.
99 QueryCallback callback;
100 void* ctx;
101
102 // |header_list| is owned by this struct. It is stored here in order to
103 // free it when the request is done.
104 curl_slist* header_list;
Jason Jeremy Iman90512982021-01-31 18:43:58 +0900105
106 // Upon calling resolve, all available DoH providers will be queried
107 // concurrently. |request_id| is an identifier shared by the queries made
108 // for a single `Resolve(...)` call.
109 int request_id;
Jason Jeremy Iman15c32782021-01-27 04:19:43 +0900110 };
111
Jason Jeremy Iman90512982021-01-31 18:43:58 +0900112 // Initialize CURL handle to resolve wire-format data |data| of length |len|.
113 // This is done by querying DoH provider |doh_provider|.
114 // A state containing the CURL handle will be allocated and used to store
115 // CURL data such as header list and response.
116 // Lifecycle of the state is handled by the caller of this function.
117 std::unique_ptr<State> InitCurl(const std::string& doh_provider,
118 const char* msg,
119 int len,
120 const QueryCallback& callback,
121 void* ctx);
122
Jason Jeremy Iman15c32782021-01-27 04:19:43 +0900123 // Callback informed about what to wait for. When called, register or remove
124 // the socket given from watchers.
125 // This method signature matches CURL `socket_callback(...)`.
126 // This callback is registered through CURL.
127 // |userp| is owned by DoHCurlClient and should be cleaned properly upon
128 // query completion.
129 static int SocketCallback(CURL* easy,
130 curl_socket_t socket_fd,
131 int what,
132 void* userp,
133 void* socketp);
134
135 // Callback necessary for CURL to write data, method signature matches CURL
136 // `write_callback(...)`. This callback is registered through CURL.
137 //
138 // This callback function gets called by libcurl as soon as there is data
139 // received that needs to be saved. For most transfers, this callback gets
140 // called many times and each invoke delivers another chunk of data.
141 // |ptr| points to the delivered data, and the size of that data is |nmemb|.
142 // |size| is always 1. |userdata| refers to the data given to CURL through
143 // CURLOPT_WRITEDATA option.
144 //
145 // |ptr| is owned by CURL and must not be altered in this function.
146 // |userdata| is owned by DoHCurlClient and should be cleaned properly upon
147 // query completion.
148 static size_t WriteCallback(char* ptr,
149 size_t size,
150 size_t nmemb,
151 void* userdata);
152
Jason Jeremy Iman845f2932021-01-31 16:12:13 +0900153 // Callback necessary for CURL to write header data.
154 static size_t HeaderCallback(void* data,
155 size_t len,
156 size_t nitems,
157 void* userp);
158
Jason Jeremy Iman90512982021-01-31 18:43:58 +0900159 // Callback informed when a query timed out.
Jason Jeremy Iman845f2932021-01-31 16:12:13 +0900160 void TimeoutCallback();
161
Jason Jeremy Iman90512982021-01-31 18:43:58 +0900162 // Callback informed to start the query and to handle timeout, method
Jason Jeremy Iman15c32782021-01-27 04:19:43 +0900163 // signature matches CURL `timer_callback(...)`. This callback is registered
164 // through CURL.
165 //
166 // |timeout_ms| value of -1 passed to this callback means the caller should
Jason Jeremy Iman845f2932021-01-31 16:12:13 +0900167 // delete the timer. All other values are valid expire times, in milliseconds.
168 // |userp| refers to the pointer given to CURL through CURLMOPT_TIMERDATA
169 // option.
Jason Jeremy Iman15c32782021-01-27 04:19:43 +0900170 //
171 // |userp| is owned by DoHCurlClient and should be cleaned properly upon
172 // query completion.
173 static int TimerCallback(CURLM* multi, int64_t timeout_ms, void* userp);
174
175 // Methods to update CURL socket watchers for asynchronous CURL events.
176 // When an action is observed, `CheckMultiInfo()` will be called.
177 void AddReadWatcher(curl_socket_t socket_fd);
178 void AddWriteWatcher(curl_socket_t socket_fd);
179 void RemoveWatcher(curl_socket_t socket_fd);
180
181 // Callback called whenever an event is ready to be handled by CURL on
182 // |socket_fd|. This callback is registered through the core event loop.
183 void OnFileCanReadWithoutBlocking(curl_socket_t socket_fd);
184 void OnFileCanWriteWithoutBlocking(curl_socket_t socket_fd);
185
Jason Jeremy Iman90512982021-01-31 18:43:58 +0900186 // Checks for a querycompletion and handles its result.
Jason Jeremy Iman15c32782021-01-27 04:19:43 +0900187 // These functions are called when CURL just finished processing an event.
188 // |curl_msg| is owned by CURL, DoHCurlClient should not care about its
189 // lifecycle.
190 void CheckMultiInfo();
191 void HandleResult(CURLMsg* curl_msg);
192
Jason Jeremy Iman90512982021-01-31 18:43:58 +0900193 // Cancel an in-flight request denoted by a set of states |states|.
194 void CancelRequest(const std::set<State*>& states);
195
196 // Cancel in-flight request of identifier |request_id|.
197 void CancelRequest(int request_id);
198
Jason Jeremy Iman15c32782021-01-27 04:19:43 +0900199 // Timeout for a CURL query in seconds.
200 int64_t timeout_seconds_;
201
202 // Watchers for available event to be handled by CURL.
203 std::map<curl_socket_t,
204 std::unique_ptr<base::FileDescriptorWatcher::Controller>>
205 read_watchers_;
206 std::map<curl_socket_t,
207 std::unique_ptr<base::FileDescriptorWatcher::Controller>>
208 write_watchers_;
209
210 // |name_servers_| to resolve |doh_providers_| address.
211 std::string name_servers_;
212
213 // |doh_providers_| to resolve domain name using DoH.
214 std::vector<std::string> doh_providers_;
215
Jason Jeremy Iman90512982021-01-31 18:43:58 +0900216 // Maximum number of DoH providers to be queried concurrently.
217 int max_concurrent_queries_;
218
219 // Current query's states keyed by it's CURL handle.
Jason Jeremy Iman15c32782021-01-27 04:19:43 +0900220 std::map<CURL*, std::unique_ptr<State>> states_;
221
Jason Jeremy Iman90512982021-01-31 18:43:58 +0900222 // Upon calling resolve, all available DoH providers will be queried
223 // concurrently. |requests_| stores these queries together keyed by their
224 // unique identifier.
225 std::map<int, std::set<State*>> requests_;
226
227 // Stores ID of a request. |next_request_id| will be incremented for each
228 // resolve call to keep the unique value.
229 int next_request_id_;
230
Jason Jeremy Iman15c32782021-01-27 04:19:43 +0900231 // CURL multi handle to do asynchronous requests.
232 CURLM* curlm_;
233
234 base::WeakPtrFactory<DoHCurlClient> weak_factory_{this};
235};
236} // namespace dns_proxy
237
238#endif // DNS_PROXY_DOH_CURL_CLIENT_H_