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 | |
40 | struct _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 | |
125 | IPAddress 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 | |
130 | PackedStringArray 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 | |
157 | IP::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 | |
186 | IP::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 | |
197 | IPAddress 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 | |
217 | Array 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 | |
237 | void 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 | |
243 | void 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 | |
256 | PackedStringArray 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 | |
267 | TypedArray<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 | |
290 | void 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 | |
300 | void 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 | |
326 | IP *IP::singleton = nullptr; |
327 | |
328 | IP *IP::get_singleton() { |
329 | return singleton; |
330 | } |
331 | |
332 | IP *(*IP::_create)() = nullptr; |
333 | |
334 | IP *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 | |
340 | IP::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 | |
348 | IP::~IP() { |
349 | resolver->thread_abort.set(); |
350 | resolver->sem.post(); |
351 | resolver->thread.wait_to_finish(); |
352 | |
353 | memdelete(resolver); |
354 | } |
355 | |