xref: /haiku/src/kits/network/libnetapi/NetworkAddressResolver.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
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 
17 static bool
18 strip_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 
42 BNetworkAddressResolver::BNetworkAddressResolver()
43 	:
44 	BReferenceable(),
45 	fInfo(NULL),
46 	fStatus(B_NO_INIT)
47 {
48 }
49 
50 
51 BNetworkAddressResolver::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 
61 BNetworkAddressResolver::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 
72 BNetworkAddressResolver::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 
83 BNetworkAddressResolver::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 
94 BNetworkAddressResolver::~BNetworkAddressResolver()
95 {
96 	Unset();
97 }
98 
99 
100 status_t
101 BNetworkAddressResolver::InitCheck() const
102 {
103 	return fStatus;
104 }
105 
106 
107 void
108 BNetworkAddressResolver::Unset()
109 {
110 	if (fInfo != NULL) {
111 		freeaddrinfo(fInfo);
112 		fInfo = NULL;
113 	}
114 	fStatus = B_NO_INIT;
115 }
116 
117 
118 status_t
119 BNetworkAddressResolver::SetTo(const char* address, uint16 port, uint32 flags)
120 {
121 	return SetTo(AF_UNSPEC, address, port, flags);
122 }
123 
124 
125 status_t
126 BNetworkAddressResolver::SetTo(const char* address, const char* service,
127 	uint32 flags)
128 {
129 	return SetTo(AF_UNSPEC, address, service, flags);
130 }
131 
132 
133 status_t
134 BNetworkAddressResolver::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 
144 status_t
145 BNetworkAddressResolver::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 
215 status_t
216 BNetworkAddressResolver::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 
242 status_t
243 BNetworkAddressResolver::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>
272 BNetworkAddressResolver::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>
280 BNetworkAddressResolver::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>
287 BNetworkAddressResolver::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>
298 BNetworkAddressResolver::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 
343 BLocker BNetworkAddressResolver::sCacheLock("DNS cache");
344 BObjectList<BNetworkAddressResolver::CacheEntry>
345 	BNetworkAddressResolver::sCacheMap;
346