/src/h2o/lib/common/hostinfo.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2015 DeNA Co., Ltd., Kazuho Oku |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include "h2o/hostinfo.h" |
23 | | |
24 | | struct st_h2o_hostinfo_getaddr_req_t { |
25 | | h2o_multithread_receiver_t *_receiver; |
26 | | h2o_hostinfo_getaddr_cb _cb; |
27 | | void *cbdata; |
28 | | h2o_linklist_t _pending; |
29 | | union { |
30 | | struct { |
31 | | char *name; |
32 | | char *serv; |
33 | | struct addrinfo hints; |
34 | | } _in; |
35 | | struct { |
36 | | h2o_multithread_message_t message; |
37 | | const char *errstr; |
38 | | struct addrinfo *ai; |
39 | | } _out; |
40 | | }; |
41 | | }; |
42 | | |
43 | | static struct { |
44 | | pthread_mutex_t mutex; |
45 | | pthread_cond_t cond; |
46 | | h2o_linklist_t pending; /* anchor of h2o_hostinfo_getaddr_req_t::_pending */ |
47 | | size_t num_threads; |
48 | | size_t num_threads_idle; |
49 | | } queue = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, {&queue.pending, &queue.pending}, 0, 0}; |
50 | | |
51 | | size_t h2o_hostinfo_max_threads = 1; |
52 | | |
53 | | /* generic errors (https://tools.ietf.org/html/rfc8499#section-3) */ |
54 | | const char h2o_hostinfo_error_nxdomain[] = "hostname does not exist"; |
55 | | const char h2o_hostinfo_error_nodata[] = "no address associated with hostname"; |
56 | | const char h2o_hostinfo_error_refused[] = "non-recoverable failure in name resolution"; |
57 | | const char h2o_hostinfo_error_servfail[] = "temporary failure in name resolution"; |
58 | | |
59 | | /* errors specfic to getaddrinfo */ |
60 | | const char h2o_hostinfo_error_gai_addrfamily[] = "address family for hostname not supported"; |
61 | | const char h2o_hostinfo_error_gai_badflags[] = "bad value for ai_flags"; |
62 | | const char h2o_hostinfo_error_gai_family[] = "ai_family not supported"; |
63 | | const char h2o_hostinfo_error_gai_memory[] = "memory allocation failure"; |
64 | | const char h2o_hostinfo_error_gai_service[] = "servname not supported for ai_socktype"; |
65 | | const char h2o_hostinfo_error_gai_socktype[] = "ai_socktype not supported"; |
66 | | const char h2o_hostinfo_error_gai_system[] = "system error"; |
67 | | const char h2o_hostinfo_error_gai_other[] = "name resolution failed"; |
68 | | |
69 | | static void create_lookup_thread_if_necessary(void); |
70 | | |
71 | | static const char *hostinfo_error_from_gai_error(int ret) |
72 | 0 | { |
73 | 0 | switch (ret) { |
74 | 0 | case EAI_NONAME: |
75 | 0 | return h2o_hostinfo_error_nxdomain; |
76 | 0 | #ifdef EAI_NODATA /* obsoleted in RFC 3493 and not supported by FreeBSD */ |
77 | 0 | case EAI_NODATA: |
78 | 0 | return h2o_hostinfo_error_nodata; |
79 | 0 | #endif |
80 | 0 | case EAI_FAIL: |
81 | 0 | return h2o_hostinfo_error_refused; |
82 | 0 | case EAI_AGAIN: |
83 | 0 | return h2o_hostinfo_error_servfail; |
84 | 0 | #ifdef EAI_ADDRFAMILY |
85 | 0 | case EAI_ADDRFAMILY: |
86 | 0 | return h2o_hostinfo_error_gai_addrfamily; |
87 | 0 | #endif |
88 | 0 | case EAI_BADFLAGS: |
89 | 0 | return h2o_hostinfo_error_gai_badflags; |
90 | 0 | case EAI_FAMILY: |
91 | 0 | return h2o_hostinfo_error_gai_family; |
92 | 0 | case EAI_MEMORY: |
93 | 0 | return h2o_hostinfo_error_gai_memory; |
94 | 0 | case EAI_SERVICE: |
95 | 0 | return h2o_hostinfo_error_gai_service; |
96 | 0 | case EAI_SOCKTYPE: |
97 | 0 | return h2o_hostinfo_error_gai_socktype; |
98 | 0 | case EAI_SYSTEM: |
99 | 0 | return h2o_hostinfo_error_gai_system; |
100 | 0 | default: |
101 | 0 | return h2o_hostinfo_error_gai_other; |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | static void lookup_and_respond(h2o_hostinfo_getaddr_req_t *req) |
106 | 0 | { |
107 | 0 | struct addrinfo *res; |
108 | |
|
109 | 0 | int ret = getaddrinfo(req->_in.name, req->_in.serv, &req->_in.hints, &res); |
110 | 0 | req->_out.message = (h2o_multithread_message_t){{NULL}}; |
111 | 0 | if (ret != 0) { |
112 | 0 | req->_out.errstr = hostinfo_error_from_gai_error(ret); |
113 | 0 | req->_out.ai = NULL; |
114 | 0 | } else { |
115 | 0 | req->_out.errstr = NULL; |
116 | 0 | req->_out.ai = res; |
117 | 0 | } |
118 | |
|
119 | 0 | h2o_multithread_send_message(req->_receiver, &req->_out.message); |
120 | 0 | } |
121 | | |
122 | | static void *lookup_thread_main(void *_unused) |
123 | 0 | { |
124 | 0 | pthread_mutex_lock(&queue.mutex); |
125 | |
|
126 | 0 | while (1) { |
127 | 0 | --queue.num_threads_idle; |
128 | 0 | while (!h2o_linklist_is_empty(&queue.pending)) { |
129 | 0 | h2o_hostinfo_getaddr_req_t *req = H2O_STRUCT_FROM_MEMBER(h2o_hostinfo_getaddr_req_t, _pending, queue.pending.next); |
130 | 0 | h2o_linklist_unlink(&req->_pending); |
131 | 0 | create_lookup_thread_if_necessary(); |
132 | 0 | pthread_mutex_unlock(&queue.mutex); |
133 | 0 | lookup_and_respond(req); |
134 | 0 | pthread_mutex_lock(&queue.mutex); |
135 | 0 | } |
136 | 0 | ++queue.num_threads_idle; |
137 | 0 | pthread_cond_wait(&queue.cond, &queue.mutex); |
138 | 0 | } |
139 | |
|
140 | 0 | h2o_fatal("unreachable"); |
141 | 0 | return NULL; |
142 | 0 | } |
143 | | |
144 | | static void create_lookup_thread_if_necessary(void) |
145 | 0 | { |
146 | | /* do nothing if there's no need to, or if we are already at the maximum. */ |
147 | 0 | if (queue.num_threads_idle != 0 || h2o_linklist_is_empty(&queue.pending)) |
148 | 0 | return; |
149 | 0 | if (queue.num_threads == h2o_hostinfo_max_threads) |
150 | 0 | return; |
151 | | |
152 | 0 | pthread_t tid; |
153 | 0 | pthread_attr_t attr; |
154 | 0 | int ret; |
155 | 0 | pthread_attr_init(&attr); |
156 | 0 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
157 | 0 | if ((ret = pthread_create(&tid, &attr, lookup_thread_main, NULL)) != 0) { |
158 | 0 | char buf[128]; |
159 | 0 | if (queue.num_threads == 0) { |
160 | 0 | h2o_fatal("failed to start first thread for getaddrinfo: %s", h2o_strerror_r(ret, buf, sizeof(buf))); |
161 | 0 | } else { |
162 | 0 | h2o_error_printf("pthread_create(for getaddrinfo): %s", h2o_strerror_r(ret, buf, sizeof(buf))); |
163 | 0 | } |
164 | 0 | return; |
165 | 0 | } |
166 | 0 | pthread_attr_destroy(&attr); |
167 | |
|
168 | 0 | ++queue.num_threads; |
169 | 0 | ++queue.num_threads_idle; |
170 | 0 | } |
171 | | |
172 | | static void dispatch_hostinfo_getaddr(h2o_hostinfo_getaddr_req_t *req) |
173 | 0 | { |
174 | 0 | pthread_mutex_lock(&queue.mutex); |
175 | |
|
176 | 0 | h2o_linklist_insert(&queue.pending, &req->_pending); |
177 | |
|
178 | 0 | create_lookup_thread_if_necessary(); |
179 | |
|
180 | 0 | pthread_cond_signal(&queue.cond); |
181 | 0 | pthread_mutex_unlock(&queue.mutex); |
182 | 0 | } |
183 | | |
184 | | h2o_hostinfo_getaddr_req_t *h2o_hostinfo_getaddr(h2o_multithread_receiver_t *receiver, h2o_iovec_t name, h2o_iovec_t serv, |
185 | | int family, int socktype, int protocol, int flags, h2o_hostinfo_getaddr_cb cb, |
186 | | void *cbdata) |
187 | 0 | { |
188 | 0 | h2o_hostinfo_getaddr_req_t *req = h2o_mem_alloc(sizeof(*req) + name.len + 1 + serv.len + 1); |
189 | 0 | req->_receiver = receiver; |
190 | 0 | req->_cb = cb; |
191 | 0 | req->cbdata = cbdata; |
192 | 0 | req->_pending = (h2o_linklist_t){NULL}; |
193 | 0 | req->_in.name = (char *)req + sizeof(*req); |
194 | 0 | memcpy(req->_in.name, name.base, name.len); |
195 | 0 | req->_in.name[name.len] = '\0'; |
196 | 0 | req->_in.serv = req->_in.name + name.len + 1; |
197 | 0 | memcpy(req->_in.serv, serv.base, serv.len); |
198 | 0 | req->_in.serv[serv.len] = '\0'; |
199 | 0 | memset(&req->_in.hints, 0, sizeof(req->_in.hints)); |
200 | 0 | req->_in.hints.ai_family = family; |
201 | 0 | req->_in.hints.ai_socktype = socktype; |
202 | 0 | req->_in.hints.ai_protocol = protocol; |
203 | 0 | req->_in.hints.ai_flags = flags; |
204 | |
|
205 | 0 | dispatch_hostinfo_getaddr(req); |
206 | |
|
207 | 0 | return req; |
208 | 0 | } |
209 | | |
210 | | void h2o_hostinfo_getaddr_cancel(h2o_hostinfo_getaddr_req_t *req) |
211 | 0 | { |
212 | 0 | int should_free = 0; |
213 | |
|
214 | 0 | pthread_mutex_lock(&queue.mutex); |
215 | |
|
216 | 0 | if (h2o_linklist_is_linked(&req->_pending)) { |
217 | 0 | h2o_linklist_unlink(&req->_pending); |
218 | 0 | should_free = 1; |
219 | 0 | } else { |
220 | 0 | req->_cb = NULL; |
221 | 0 | } |
222 | |
|
223 | 0 | pthread_mutex_unlock(&queue.mutex); |
224 | |
|
225 | 0 | if (should_free) |
226 | 0 | free(req); |
227 | 0 | } |
228 | | |
229 | | void h2o_hostinfo_getaddr_receiver(h2o_multithread_receiver_t *receiver, h2o_linklist_t *messages) |
230 | 0 | { |
231 | 0 | while (!h2o_linklist_is_empty(messages)) { |
232 | 0 | h2o_hostinfo_getaddr_req_t *req = H2O_STRUCT_FROM_MEMBER(h2o_hostinfo_getaddr_req_t, _out.message.link, messages->next); |
233 | 0 | h2o_linklist_unlink(&req->_out.message.link); |
234 | 0 | h2o_hostinfo_getaddr_cb cb = req->_cb; |
235 | 0 | if (cb != NULL) { |
236 | 0 | req->_cb = NULL; |
237 | 0 | cb(req, req->_out.errstr, req->_out.ai, req->cbdata); |
238 | 0 | } |
239 | 0 | if (req->_out.ai != NULL) |
240 | 0 | freeaddrinfo(req->_out.ai); |
241 | 0 | free(req); |
242 | 0 | } |
243 | 0 | } |
244 | | |
245 | | static const char *fetch_aton_digit(const char *p, const char *end, unsigned char *value) |
246 | 0 | { |
247 | 0 | size_t ndigits = 0; |
248 | 0 | int v = 0; |
249 | |
|
250 | 0 | while (p != end && ('0' <= *p && *p <= '9')) { |
251 | 0 | v = v * 10 + *p++ - '0'; |
252 | 0 | ++ndigits; |
253 | 0 | } |
254 | 0 | if (!(1 <= ndigits && ndigits <= 3)) |
255 | 0 | return NULL; |
256 | 0 | if (v > 255) |
257 | 0 | return NULL; |
258 | 0 | *value = (unsigned char)v; |
259 | 0 | return p; |
260 | 0 | } |
261 | | |
262 | | int h2o_hostinfo_aton(h2o_iovec_t host, struct in_addr *addr) |
263 | 0 | { |
264 | 0 | union { |
265 | 0 | int32_t n; |
266 | 0 | unsigned char c[4]; |
267 | 0 | } value; |
268 | 0 | const char *p = host.base, *end = p + host.len; |
269 | 0 | size_t ndots = 0; |
270 | |
|
271 | 0 | while (1) { |
272 | 0 | if ((p = fetch_aton_digit(p, end, value.c + ndots)) == NULL) |
273 | 0 | return -1; |
274 | 0 | if (ndots == 3) |
275 | 0 | break; |
276 | 0 | if (p == end || !(*p == '.')) |
277 | 0 | return -1; |
278 | 0 | ++p; |
279 | 0 | ++ndots; |
280 | 0 | } |
281 | 0 | if (p != end) |
282 | 0 | return -1; |
283 | | |
284 | 0 | addr->s_addr = value.n; |
285 | 0 | return 0; |
286 | 0 | } |