Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 1 | // 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 Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 12 | #include <set> |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 13 | #include <string> |
| 14 | #include <vector> |
| 15 | |
| 16 | #include <base/files/file_descriptor_watcher_posix.h> |
| 17 | #include <base/time/time.h> |
| 18 | |
| 19 | namespace dns_proxy { |
| 20 | |
Jason Jeremy Iman | 1bb71c2 | 2021-01-26 21:49:55 +0900 | [diff] [blame] | 21 | // List of HTTP status codes. |
| 22 | constexpr int64_t kHTTPOk = 200; |
Jason Jeremy Iman | 845f293 | 2021-01-31 16:12:13 +0900 | [diff] [blame] | 23 | constexpr int64_t kHTTPTooManyRequests = 429; |
Jason Jeremy Iman | 1bb71c2 | 2021-01-26 21:49:55 +0900 | [diff] [blame] | 24 | |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 25 | // 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. |
| 30 | class DoHCurlClient { |
| 31 | public: |
Jason Jeremy Iman | 845f293 | 2021-01-31 16:12:13 +0900 | [diff] [blame] | 32 | // 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 Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 41 | // 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 Iman | 845f293 | 2021-01-31 16:12:13 +0900 | [diff] [blame] | 46 | // 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 Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 49 | // |msg| and |len| respectively stores the response and length of the |
| 50 | // response of the CURL query. |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 51 | using QueryCallback = base::RepeatingCallback<void( |
Jason Jeremy Iman | 845f293 | 2021-01-31 16:12:13 +0900 | [diff] [blame] | 52 | void* ctx, const CurlResult& res, uint8_t* msg, size_t len)>; |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 53 | |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 54 | DoHCurlClient(base::TimeDelta timeout, int max_concurrent_queries); |
Jason Jeremy Iman | 63fd815 | 2021-02-01 05:28:04 +0900 | [diff] [blame] | 55 | explicit DoHCurlClient(base::TimeDelta timeout); |
| 56 | virtual ~DoHCurlClient(); |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 57 | |
| 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 Iman | 63fd815 | 2021-02-01 05:28:04 +0900 | [diff] [blame] | 64 | virtual bool Resolve(const char* msg, |
| 65 | int len, |
| 66 | const QueryCallback& callback, |
| 67 | void* ctx); |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 68 | |
| 69 | // Set standard DNS and DoH servers for running `Resolve(...)`. |
Jason Jeremy Iman | 63fd815 | 2021-02-01 05:28:04 +0900 | [diff] [blame] | 70 | virtual void SetNameServers(const std::vector<std::string>& name_servers); |
| 71 | virtual void SetDoHProviders(const std::vector<std::string>& doh_providers); |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 72 | |
| 73 | private: |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 74 | // State of an individual query. |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 75 | struct State { |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 76 | State(CURL* curl, const QueryCallback& callback, void* ctx, int request_id); |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 77 | ~State(); |
| 78 | |
| 79 | // Fetch the necessary response and run |callback|. |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 80 | void RunCallback(CURLMsg* curl_msg, int64_t http_code); |
Jason Jeremy Iman | 845f293 | 2021-01-31 16:12:13 +0900 | [diff] [blame] | 81 | |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 82 | // Set DNS response |msg| of length |len| to |response|. |
Jason Jeremy Iman | 845f293 | 2021-01-31 16:12:13 +0900 | [diff] [blame] | 83 | void SetResponse(char* msg, size_t len); |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 84 | |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 85 | // Stores the CURL handle for the query. |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 86 | CURL* curl; |
| 87 | |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 88 | // Stores the response of a query. |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 89 | std::vector<uint8_t> response; |
| 90 | |
Jason Jeremy Iman | 845f293 | 2021-01-31 16:12:13 +0900 | [diff] [blame] | 91 | // Stores the header response. |
| 92 | std::vector<std::string> header; |
| 93 | |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 94 | // |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 Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 105 | |
| 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 Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 110 | }; |
| 111 | |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 112 | // 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 Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 123 | // 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 Iman | 845f293 | 2021-01-31 16:12:13 +0900 | [diff] [blame] | 153 | // 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 Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 159 | // Callback informed when a query timed out. |
Jason Jeremy Iman | 845f293 | 2021-01-31 16:12:13 +0900 | [diff] [blame] | 160 | void TimeoutCallback(); |
| 161 | |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 162 | // Callback informed to start the query and to handle timeout, method |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 163 | // 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 Iman | 845f293 | 2021-01-31 16:12:13 +0900 | [diff] [blame] | 167 | // 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 Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 170 | // |
| 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 Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 186 | // Checks for a querycompletion and handles its result. |
Jason Jeremy Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 187 | // 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 Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 193 | // 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 Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 199 | // 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 Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 216 | // 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 Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 220 | std::map<CURL*, std::unique_ptr<State>> states_; |
| 221 | |
Jason Jeremy Iman | 9051298 | 2021-01-31 18:43:58 +0900 | [diff] [blame] | 222 | // 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 Iman | 15c3278 | 2021-01-27 04:19:43 +0900 | [diff] [blame] | 231 | // 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_ |