1/**************************************************************************/
2/* ip.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "ip.h"
32
33#include "core/os/semaphore.h"
34#include "core/os/thread.h"
35#include "core/templates/hash_map.h"
36#include "core/variant/typed_array.h"
37
38/************* RESOLVER ******************/
39
40struct _IP_ResolverPrivate {
41 struct QueueItem {
42 SafeNumeric<IP::ResolverStatus> status;
43
44 List<IPAddress> response;
45
46 String hostname;
47 IP::Type type;
48
49 void clear() {
50 status.set(IP::RESOLVER_STATUS_NONE);
51 response.clear();
52 type = IP::TYPE_NONE;
53 hostname = "";
54 };
55
56 QueueItem() {
57 clear();
58 }
59 };
60
61 QueueItem queue[IP::RESOLVER_MAX_QUERIES];
62
63 IP::ResolverID find_empty_id() const {
64 for (int i = 0; i < IP::RESOLVER_MAX_QUERIES; i++) {
65 if (queue[i].status.get() == IP::RESOLVER_STATUS_NONE) {
66 return i;
67 }
68 }
69 return IP::RESOLVER_INVALID_ID;
70 }
71
72 Mutex mutex;
73 Semaphore sem;
74
75 Thread thread;
76 SafeFlag thread_abort;
77
78 void resolve_queues() {
79 for (int i = 0; i < IP::RESOLVER_MAX_QUERIES; i++) {
80 if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) {
81 continue;
82 }
83
84 mutex.lock();
85 List<IPAddress> response;
86 String hostname = queue[i].hostname;
87 IP::Type type = queue[i].type;
88 mutex.unlock();
89
90 // We should not lock while resolving the hostname,
91 // only when modifying the queue.
92 IP::get_singleton()->_resolve_hostname(response, hostname, type);
93
94 MutexLock lock(mutex);
95 // Could have been completed by another function, or deleted.
96 if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) {
97 continue;
98 }
99 // We might be overriding another result, but we don't care as long as the result is valid.
100 if (response.size()) {
101 String key = get_cache_key(hostname, type);
102 cache[key] = response;
103 }
104 queue[i].response = response;
105 queue[i].status.set(response.is_empty() ? IP::RESOLVER_STATUS_ERROR : IP::RESOLVER_STATUS_DONE);
106 }
107 }
108
109 static void _thread_function(void *self) {
110 _IP_ResolverPrivate *ipr = static_cast<_IP_ResolverPrivate *>(self);
111
112 while (!ipr->thread_abort.is_set()) {
113 ipr->sem.wait();
114 ipr->resolve_queues();
115 }
116 }
117
118 HashMap<String, List<IPAddress>> cache;
119
120 static String get_cache_key(String p_hostname, IP::Type p_type) {
121 return itos(p_type) + p_hostname;
122 }
123};
124
125IPAddress IP::resolve_hostname(const String &p_hostname, IP::Type p_type) {
126 const PackedStringArray addresses = resolve_hostname_addresses(p_hostname, p_type);
127 return addresses.size() ? (IPAddress)addresses[0] : IPAddress();
128}
129
130PackedStringArray IP::resolve_hostname_addresses(const String &p_hostname, Type p_type) {
131 List<IPAddress> res;
132 String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type);
133
134 resolver->mutex.lock();
135 if (resolver->cache.has(key)) {
136 res = resolver->cache[key];
137 } else {
138 // This should be run unlocked so the resolver thread can keep resolving
139 // other requests.
140 resolver->mutex.unlock();
141 _resolve_hostname(res, p_hostname, p_type);
142 resolver->mutex.lock();
143 // We might be overriding another result, but we don't care as long as the result is valid.
144 if (res.size()) {
145 resolver->cache[key] = res;
146 }
147 }
148 resolver->mutex.unlock();
149
150 PackedStringArray result;
151 for (int i = 0; i < res.size(); ++i) {
152 result.push_back(String(res[i]));
153 }
154 return result;
155}
156
157IP::ResolverID IP::resolve_hostname_queue_item(const String &p_hostname, IP::Type p_type) {
158 MutexLock lock(resolver->mutex);
159
160 ResolverID id = resolver->find_empty_id();
161
162 if (id == RESOLVER_INVALID_ID) {
163 WARN_PRINT("Out of resolver queries");
164 return id;
165 }
166
167 String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type);
168 resolver->queue[id].hostname = p_hostname;
169 resolver->queue[id].type = p_type;
170 if (resolver->cache.has(key)) {
171 resolver->queue[id].response = resolver->cache[key];
172 resolver->queue[id].status.set(IP::RESOLVER_STATUS_DONE);
173 } else {
174 resolver->queue[id].response = List<IPAddress>();
175 resolver->queue[id].status.set(IP::RESOLVER_STATUS_WAITING);
176 if (resolver->thread.is_started()) {
177 resolver->sem.post();
178 } else {
179 resolver->resolve_queues();
180 }
181 }
182
183 return id;
184}
185
186IP::ResolverStatus IP::get_resolve_item_status(ResolverID p_id) const {
187 ERR_FAIL_INDEX_V_MSG(p_id, IP::RESOLVER_MAX_QUERIES, IP::RESOLVER_STATUS_NONE, vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES));
188
189 IP::ResolverStatus res = resolver->queue[p_id].status.get();
190 if (res == IP::RESOLVER_STATUS_NONE) {
191 ERR_PRINT("Condition status == IP::RESOLVER_STATUS_NONE");
192 return IP::RESOLVER_STATUS_NONE;
193 }
194 return res;
195}
196
197IPAddress IP::get_resolve_item_address(ResolverID p_id) const {
198 ERR_FAIL_INDEX_V_MSG(p_id, IP::RESOLVER_MAX_QUERIES, IPAddress(), vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES));
199
200 MutexLock lock(resolver->mutex);
201
202 if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) {
203 ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet.");
204 return IPAddress();
205 }
206
207 List<IPAddress> res = resolver->queue[p_id].response;
208
209 for (int i = 0; i < res.size(); ++i) {
210 if (res[i].is_valid()) {
211 return res[i];
212 }
213 }
214 return IPAddress();
215}
216
217Array IP::get_resolve_item_addresses(ResolverID p_id) const {
218 ERR_FAIL_INDEX_V_MSG(p_id, IP::RESOLVER_MAX_QUERIES, Array(), vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES));
219 MutexLock lock(resolver->mutex);
220
221 if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) {
222 ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet.");
223 return Array();
224 }
225
226 List<IPAddress> res = resolver->queue[p_id].response;
227
228 Array result;
229 for (int i = 0; i < res.size(); ++i) {
230 if (res[i].is_valid()) {
231 result.push_back(String(res[i]));
232 }
233 }
234 return result;
235}
236
237void IP::erase_resolve_item(ResolverID p_id) {
238 ERR_FAIL_INDEX_MSG(p_id, IP::RESOLVER_MAX_QUERIES, vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES));
239
240 resolver->queue[p_id].status.set(IP::RESOLVER_STATUS_NONE);
241}
242
243void IP::clear_cache(const String &p_hostname) {
244 MutexLock lock(resolver->mutex);
245
246 if (p_hostname.is_empty()) {
247 resolver->cache.clear();
248 } else {
249 resolver->cache.erase(_IP_ResolverPrivate::get_cache_key(p_hostname, IP::TYPE_NONE));
250 resolver->cache.erase(_IP_ResolverPrivate::get_cache_key(p_hostname, IP::TYPE_IPV4));
251 resolver->cache.erase(_IP_ResolverPrivate::get_cache_key(p_hostname, IP::TYPE_IPV6));
252 resolver->cache.erase(_IP_ResolverPrivate::get_cache_key(p_hostname, IP::TYPE_ANY));
253 }
254}
255
256PackedStringArray IP::_get_local_addresses() const {
257 PackedStringArray addresses;
258 List<IPAddress> ip_addresses;
259 get_local_addresses(&ip_addresses);
260 for (const IPAddress &E : ip_addresses) {
261 addresses.push_back(E);
262 }
263
264 return addresses;
265}
266
267TypedArray<Dictionary> IP::_get_local_interfaces() const {
268 TypedArray<Dictionary> results;
269 HashMap<String, Interface_Info> interfaces;
270 get_local_interfaces(&interfaces);
271 for (KeyValue<String, Interface_Info> &E : interfaces) {
272 Interface_Info &c = E.value;
273 Dictionary rc;
274 rc["name"] = c.name;
275 rc["friendly"] = c.name_friendly;
276 rc["index"] = c.index;
277
278 Array ips;
279 for (const IPAddress &F : c.ip_addresses) {
280 ips.push_front(F);
281 }
282 rc["addresses"] = ips;
283
284 results.push_front(rc);
285 }
286
287 return results;
288}
289
290void IP::get_local_addresses(List<IPAddress> *r_addresses) const {
291 HashMap<String, Interface_Info> interfaces;
292 get_local_interfaces(&interfaces);
293 for (const KeyValue<String, Interface_Info> &E : interfaces) {
294 for (const IPAddress &F : E.value.ip_addresses) {
295 r_addresses->push_front(F);
296 }
297 }
298}
299
300void IP::_bind_methods() {
301 ClassDB::bind_method(D_METHOD("resolve_hostname", "host", "ip_type"), &IP::resolve_hostname, DEFVAL(IP::TYPE_ANY));
302 ClassDB::bind_method(D_METHOD("resolve_hostname_addresses", "host", "ip_type"), &IP::resolve_hostname_addresses, DEFVAL(IP::TYPE_ANY));
303 ClassDB::bind_method(D_METHOD("resolve_hostname_queue_item", "host", "ip_type"), &IP::resolve_hostname_queue_item, DEFVAL(IP::TYPE_ANY));
304 ClassDB::bind_method(D_METHOD("get_resolve_item_status", "id"), &IP::get_resolve_item_status);
305 ClassDB::bind_method(D_METHOD("get_resolve_item_address", "id"), &IP::get_resolve_item_address);
306 ClassDB::bind_method(D_METHOD("get_resolve_item_addresses", "id"), &IP::get_resolve_item_addresses);
307 ClassDB::bind_method(D_METHOD("erase_resolve_item", "id"), &IP::erase_resolve_item);
308 ClassDB::bind_method(D_METHOD("get_local_addresses"), &IP::_get_local_addresses);
309 ClassDB::bind_method(D_METHOD("get_local_interfaces"), &IP::_get_local_interfaces);
310 ClassDB::bind_method(D_METHOD("clear_cache", "hostname"), &IP::clear_cache, DEFVAL(""));
311
312 BIND_ENUM_CONSTANT(RESOLVER_STATUS_NONE);
313 BIND_ENUM_CONSTANT(RESOLVER_STATUS_WAITING);
314 BIND_ENUM_CONSTANT(RESOLVER_STATUS_DONE);
315 BIND_ENUM_CONSTANT(RESOLVER_STATUS_ERROR);
316
317 BIND_CONSTANT(RESOLVER_MAX_QUERIES);
318 BIND_CONSTANT(RESOLVER_INVALID_ID);
319
320 BIND_ENUM_CONSTANT(TYPE_NONE);
321 BIND_ENUM_CONSTANT(TYPE_IPV4);
322 BIND_ENUM_CONSTANT(TYPE_IPV6);
323 BIND_ENUM_CONSTANT(TYPE_ANY);
324}
325
326IP *IP::singleton = nullptr;
327
328IP *IP::get_singleton() {
329 return singleton;
330}
331
332IP *(*IP::_create)() = nullptr;
333
334IP *IP::create() {
335 ERR_FAIL_COND_V_MSG(singleton, nullptr, "IP singleton already exist.");
336 ERR_FAIL_NULL_V(_create, nullptr);
337 return _create();
338}
339
340IP::IP() {
341 singleton = this;
342 resolver = memnew(_IP_ResolverPrivate);
343
344 resolver->thread_abort.clear();
345 resolver->thread.start(_IP_ResolverPrivate::_thread_function, resolver);
346}
347
348IP::~IP() {
349 resolver->thread_abort.set();
350 resolver->sem.post();
351 resolver->thread.wait_to_finish();
352
353 memdelete(resolver);
354}
355