1/*
2 * Copyright 2010-2011, Axel D��rfler, axeld@pinc-software.de.
3 * Copyright 2015-2017, Adrien Destugues, pulkomandy@pulkomandy.tk.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8#include <NetworkAddressResolver.h>
9
10#include <errno.h>
11#include <netdb.h>
12
13#include <Autolock.h>
14#include <NetworkAddress.h>
15
16
17static bool
18strip_port(BString& host, BString& port)
19{
20	int32 first = host.FindFirst(':');
21	int32 separator = host.FindLast(':');
22	if (separator != first
23			&& (separator == 0 || host.ByteAt(separator - 1) != ']')) {
24		return false;
25	}
26
27	if (separator != -1) {
28		// looks like there is a port
29		host.CopyInto(port, separator + 1, -1);
30		host.Truncate(separator);
31
32		return true;
33	}
34
35	return false;
36}
37
38
39// #pragma mark -
40
41
42BNetworkAddressResolver::BNetworkAddressResolver()
43	:
44	BReferenceable(),
45	fInfo(NULL),
46	fStatus(B_NO_INIT)
47{
48}
49
50
51BNetworkAddressResolver::BNetworkAddressResolver(const char* address,
52	uint16 port, uint32 flags)
53	:
54	BReferenceable(),
55	fInfo(NULL),
56	fStatus(B_NO_INIT)
57{
58	SetTo(address, port, flags);
59}
60
61BNetworkAddressResolver::BNetworkAddressResolver(const char* address,
62	const char* service, uint32 flags)
63	:
64	BReferenceable(),
65	fInfo(NULL),
66	fStatus(B_NO_INIT)
67{
68	SetTo(address, service, flags);
69}
70
71
72BNetworkAddressResolver::BNetworkAddressResolver(int family,
73	const char* address, uint16 port, uint32 flags)
74	:
75	BReferenceable(),
76	fInfo(NULL),
77	fStatus(B_NO_INIT)
78{
79	SetTo(family, address, port, flags);
80}
81
82
83BNetworkAddressResolver::BNetworkAddressResolver(int family,
84	const char* address, const char* service, uint32 flags)
85	:
86	BReferenceable(),
87	fInfo(NULL),
88	fStatus(B_NO_INIT)
89{
90	SetTo(family, address, service, flags);
91}
92
93
94BNetworkAddressResolver::~BNetworkAddressResolver()
95{
96	Unset();
97}
98
99
100status_t
101BNetworkAddressResolver::InitCheck() const
102{
103	return fStatus;
104}
105
106
107void
108BNetworkAddressResolver::Unset()
109{
110	if (fInfo != NULL) {
111		freeaddrinfo(fInfo);
112		fInfo = NULL;
113	}
114	fStatus = B_NO_INIT;
115}
116
117
118status_t
119BNetworkAddressResolver::SetTo(const char* address, uint16 port, uint32 flags)
120{
121	return SetTo(AF_UNSPEC, address, port, flags);
122}
123
124
125status_t
126BNetworkAddressResolver::SetTo(const char* address, const char* service,
127	uint32 flags)
128{
129	return SetTo(AF_UNSPEC, address, service, flags);
130}
131
132
133status_t
134BNetworkAddressResolver::SetTo(int family, const char* address, uint16 port,
135	uint32 flags)
136{
137	BString service;
138	service << port;
139
140	return SetTo(family, address, port != 0 ? service.String() : NULL, flags);
141}
142
143
144status_t
145BNetworkAddressResolver::SetTo(int family, const char* host,
146	const char* service, uint32 flags)
147{
148	Unset();
149
150	// Check if the address contains a port
151
152	BString hostString(host);
153
154	BString portString;
155	if (!strip_port(hostString, portString) && service != NULL)
156		portString = service;
157
158	// Resolve address
159
160	addrinfo hint = {0};
161	hint.ai_family = family;
162	if ((flags & B_NO_ADDRESS_RESOLUTION) != 0)
163		hint.ai_flags |= AI_NUMERICHOST;
164	else if ((flags & B_UNCONFIGURED_ADDRESS_FAMILIES) == 0)
165		hint.ai_flags |= AI_ADDRCONFIG;
166
167	if (host == NULL && portString.Length() == 0) {
168		portString = "0";
169		hint.ai_flags |= AI_PASSIVE;
170	}
171
172	int status = getaddrinfo(host != NULL ? hostString.String() : NULL,
173		portString.Length() != 0 ? portString.String() : NULL, &hint, &fInfo);
174	if (status == 0)
175		return fStatus = B_OK;
176
177	// Map errors
178	// TODO: improve error reporting, maybe add specific error codes?
179
180	switch (status) {
181		case EAI_ADDRFAMILY:
182		case EAI_BADFLAGS:
183		case EAI_PROTOCOL:
184		case EAI_BADHINTS:
185		case EAI_SOCKTYPE:
186		case EAI_SERVICE:
187		case EAI_NONAME:
188		case EAI_FAMILY:
189			fStatus = B_BAD_VALUE;
190			break;
191
192		case EAI_SYSTEM:
193			fStatus = errno;
194			break;
195
196		case EAI_OVERFLOW:
197		case EAI_MEMORY:
198			fStatus = B_NO_MEMORY;
199			break;
200
201		case EAI_AGAIN:
202			// TODO: better error code to denote temporary failure?
203			fStatus = B_TIMED_OUT;
204			break;
205
206		default:
207			fStatus = B_ERROR;
208			break;
209	}
210
211	return fStatus;
212}
213
214
215status_t
216BNetworkAddressResolver::GetNextAddress(uint32* cookie,
217	BNetworkAddress& address) const
218{
219	if (fStatus != B_OK)
220		return fStatus;
221
222	// Skip previous info entries
223
224	addrinfo* info = fInfo;
225	int32 first = *cookie;
226	for (int32 index = 0; index < first && info != NULL; index++) {
227		info = info->ai_next;
228	}
229
230	if (info == NULL)
231		return B_BAD_VALUE;
232
233	// Return current
234
235	address.SetTo(*info->ai_addr, info->ai_addrlen);
236	(*cookie)++;
237
238	return B_OK;
239}
240
241
242status_t
243BNetworkAddressResolver::GetNextAddress(int family, uint32* cookie,
244	BNetworkAddress& address) const
245{
246	if (fStatus != B_OK)
247		return fStatus;
248
249	// Skip previous info entries, and those that have a non-matching family
250
251	addrinfo* info = fInfo;
252	int32 first = *cookie;
253	for (int32 index = 0; index < first && info != NULL; index++)
254		info = info->ai_next;
255
256	while (info != NULL && info->ai_family != family)
257		info = info->ai_next;
258
259	if (info == NULL)
260		return B_BAD_VALUE;
261
262	// Return current
263
264	address.SetTo(*info->ai_addr, info->ai_addrlen);
265	(*cookie)++;
266
267	return B_OK;
268}
269
270
271/*static*/ BReference<const BNetworkAddressResolver>
272BNetworkAddressResolver::Resolve(const char* address, const char* service,
273	uint32 flags)
274{
275	return Resolve(AF_UNSPEC, address, service, flags);
276}
277
278
279/*static*/ BReference<const BNetworkAddressResolver>
280BNetworkAddressResolver::Resolve(const char* address, uint16 port, uint32 flags)
281{
282	return Resolve(AF_UNSPEC, address, port, flags);
283}
284
285
286/*static*/ BReference<const BNetworkAddressResolver>
287BNetworkAddressResolver::Resolve(int family, const char* address,
288	uint16 port, uint32 flags)
289{
290	BString service;
291	service << port;
292
293	return Resolve(family, address, port == 0 ? NULL : service.String(), flags);
294}
295
296
297/*static*/ BReference<const BNetworkAddressResolver>
298BNetworkAddressResolver::Resolve(int family, const char* address,
299	const char* service, uint32 flags)
300{
301	BAutolock locker(&sCacheLock);
302
303	// TODO it may be faster to use an hash map to have faster lookup of the
304	// cache. However, we also need to access the cache by LRU, and for that
305	// a doubly-linked list is better. We should have these two share the same
306	// items, so it's easy to remove the LRU from the map, or insert a new
307	// item in both structures.
308	for (int i = 0; i < sCacheMap.CountItems(); i++) {
309		CacheEntry* entry = sCacheMap.ItemAt(i);
310		if (entry->Matches(family, address, service, flags)) {
311			// This entry is now the MRU, move to end of list.
312			// TODO if the item is old (more than 1 minute), it should be
313			// dropped and a new request made.
314			sCacheMap.MoveItem(i, sCacheMap.CountItems());
315			return entry->fResolver;
316		}
317	}
318
319	// Cache miss! Unlock the cache while we perform the costly address
320	// resolution
321	locker.Unlock();
322
323	BNetworkAddressResolver* resolver = new(std::nothrow)
324		BNetworkAddressResolver(family, address, service, flags);
325
326	if (resolver != NULL && resolver->InitCheck() == B_OK) {
327		CacheEntry* entry = new(std::nothrow) CacheEntry(family, address,
328			service, flags, resolver);
329
330		locker.Lock();
331		// TODO adjust capacity. Chrome uses 256 entries with a timeout of
332		// 1 minute, IE uses 1000 entries with a timeout of 30 seconds.
333		if (sCacheMap.CountItems() > 255)
334			delete sCacheMap.RemoveItemAt(0);
335
336		if (entry)
337			sCacheMap.AddItem(entry, sCacheMap.CountItems());
338	}
339
340	return BReference<const BNetworkAddressResolver>(resolver, true);
341}
342
343BLocker BNetworkAddressResolver::sCacheLock("DNS cache");
344BObjectList<BNetworkAddressResolver::CacheEntry>
345	BNetworkAddressResolver::sCacheMap;
346