Line | Count | Source |
1 | | /* |
2 | | * Name server resolution |
3 | | * |
4 | | * Copyright 2020 HAProxy Technologies |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU General Public License |
8 | | * as published by the Free Software Foundation; either version |
9 | | * 2 of the License, or (at your option) any later version. |
10 | | * |
11 | | */ |
12 | | |
13 | | #include <errno.h> |
14 | | #include <stdio.h> |
15 | | #include <stdlib.h> |
16 | | #include <string.h> |
17 | | #include <unistd.h> |
18 | | |
19 | | #include <sys/types.h> |
20 | | |
21 | | #include <haproxy/action.h> |
22 | | #include <haproxy/api.h> |
23 | | #include <haproxy/applet.h> |
24 | | #include <haproxy/cfgparse.h> |
25 | | #include <haproxy/channel.h> |
26 | | #include <haproxy/check.h> |
27 | | #include <haproxy/cli.h> |
28 | | #include <haproxy/dgram.h> |
29 | | #include <haproxy/dns.h> |
30 | | #include <haproxy/dns_ring.h> |
31 | | #include <haproxy/errors.h> |
32 | | #include <haproxy/fd.h> |
33 | | #include <haproxy/log.h> |
34 | | #include <haproxy/protocol.h> |
35 | | #include <haproxy/sc_strm.h> |
36 | | #include <haproxy/stconn.h> |
37 | | #include <haproxy/stream.h> |
38 | | #include <haproxy/tools.h> |
39 | | |
40 | | static THREAD_LOCAL char *dns_msg_trash; |
41 | | |
42 | | DECLARE_STATIC_TYPED_POOL(dns_session_pool, "dns_session", struct dns_session); |
43 | | DECLARE_STATIC_TYPED_POOL(dns_query_pool, "dns_query", struct dns_query); |
44 | | DECLARE_STATIC_POOL(dns_msg_buf, "dns_msg_buf", DNS_MSG_RING_MAX_SIZE); |
45 | | |
46 | | /* Opens an UDP socket on the namesaver's IP/Port, if required. Returns 0 on |
47 | | * success, -1 otherwise. ns->dgram must be defined. |
48 | | */ |
49 | | static int dns_connect_nameserver(struct dns_nameserver *ns) |
50 | 0 | { |
51 | 0 | struct dgram_conn *dgram = &ns->dgram->conn; |
52 | 0 | const struct protocol *proto; |
53 | 0 | int fd; |
54 | | |
55 | | /* Already connected */ |
56 | 0 | if (dgram->t.sock.fd != -1) |
57 | 0 | return 0; |
58 | | |
59 | | /* Create an UDP socket and connect it on the nameserver's IP/Port */ |
60 | 0 | proto = protocol_lookup(dgram->addr.to.ss_family, PROTO_TYPE_DGRAM, 1); |
61 | 0 | BUG_ON(!proto); |
62 | 0 | if ((fd = socket(proto->fam->sock_domain, proto->sock_type, proto->sock_prot)) == -1) { |
63 | 0 | send_log(NULL, LOG_WARNING, |
64 | 0 | "DNS : section '%s': can't create socket for nameserver '%s'.\n", |
65 | 0 | ns->counters->pid, ns->id); |
66 | 0 | return -1; |
67 | 0 | } |
68 | | |
69 | 0 | switch (proto->fam->sock_domain) { |
70 | 0 | case AF_INET: { |
71 | 0 | struct sockaddr_in address = { |
72 | 0 | .sin_family = AF_INET, |
73 | 0 | .sin_port = 0, |
74 | 0 | .sin_addr = { .s_addr = INADDR_ANY } |
75 | 0 | }; |
76 | 0 | if (bind(fd, (struct sockaddr *)&address, sizeof(address)) < 0) { |
77 | 0 | send_log(NULL, LOG_WARNING, |
78 | 0 | "DNS : section '%s': can't bind socket for nameserver '%s' on 0.0.0.0:0.\n", |
79 | 0 | ns->counters->pid, ns->id); |
80 | 0 | close(fd); |
81 | 0 | return -1; |
82 | 0 | } |
83 | 0 | break; |
84 | 0 | } |
85 | 0 | case AF_INET6: { |
86 | 0 | struct sockaddr_in6 address6 = { |
87 | 0 | .sin6_family = AF_INET6, |
88 | 0 | .sin6_port = 0, |
89 | 0 | .sin6_addr = in6addr_any, |
90 | 0 | .sin6_flowinfo = 0, |
91 | 0 | .sin6_scope_id = 0 |
92 | 0 | }; |
93 | 0 | if (bind(fd, (struct sockaddr *)&address6, sizeof(address6)) < 0) { |
94 | 0 | send_log(NULL, LOG_WARNING, |
95 | 0 | "DNS : section '%s': can't bind socket for nameserver '%s' on :::0.\n", |
96 | 0 | ns->counters->pid, ns->id); |
97 | 0 | close(fd); |
98 | 0 | return -1; |
99 | 0 | } |
100 | 0 | break; |
101 | 0 | } |
102 | 0 | case AF_UNIX: |
103 | | /* if IPC is used via local domain sockets, we don't expect that |
104 | | * the path to DNS server socket can change dynamically. |
105 | | */ |
106 | 0 | if (connect(fd, (struct sockaddr*)&dgram->addr.to, get_addr_len(&dgram->addr.to)) == -1) { |
107 | 0 | send_log(NULL, LOG_WARNING, |
108 | 0 | "DNS : section '%s': can't connect socket for nameserver '%s'.\n", |
109 | 0 | ns->counters->id, ns->id); |
110 | 0 | close(fd); |
111 | 0 | return -1; |
112 | 0 | } |
113 | 0 | break; |
114 | 0 | default: |
115 | 0 | close(fd); |
116 | 0 | BUG_ON(1, "DNS: Unsupported address family."); |
117 | 0 | } |
118 | | |
119 | | /* Make the socket non blocking */ |
120 | 0 | fd_set_nonblock(fd); |
121 | | |
122 | | /* Add the fd in the fd list and update its parameters */ |
123 | 0 | dgram->t.sock.fd = fd; |
124 | | |
125 | | /* let's stick the FD to the initiator thread, this will ensure that |
126 | | * most of the time, a resolver will not try to access its structure |
127 | | * at the same time as a response is processed, and will eliminate |
128 | | * locking contention. |
129 | | */ |
130 | 0 | fd_insert(fd, dgram, dgram_fd_handler, tgid, ti->ltid_bit); |
131 | 0 | fd_want_recv(fd); |
132 | 0 | return 0; |
133 | 0 | } |
134 | | |
135 | | /* Sends a message using server type <type> on nameserver <ns>. |
136 | | * It returns message length on success |
137 | | * or -1 in error case |
138 | | * 0 is returned in case of output ring buffer is full |
139 | | */ |
140 | | int dns_send_nameserver(struct dns_nameserver *ns, enum dns_server_type type, void *buf, size_t len) |
141 | 0 | { |
142 | 0 | int ret = -1; |
143 | |
|
144 | 0 | if (dns_server_type_is_dgram(type) && ns->dgram) { |
145 | 0 | struct dgram_conn *dgram = &ns->dgram->conn; |
146 | 0 | int fd; |
147 | |
|
148 | 0 | HA_SPIN_LOCK(DNS_LOCK, &dgram->lock); |
149 | 0 | fd = dgram->t.sock.fd; |
150 | 0 | if (fd == -1) { |
151 | 0 | if (dns_connect_nameserver(ns) == -1) { |
152 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
153 | 0 | return -1; |
154 | 0 | } |
155 | 0 | fd = dgram->t.sock.fd; |
156 | 0 | } |
157 | | |
158 | 0 | if (dgram->addr.to.ss_family == AF_UNIX) { |
159 | | /* we do connect for AF_UNIX sockets and from the man |
160 | | * sendto: "If sendto() is used on a connection-mode |
161 | | * (SOCK_STREAM, SOCK_SEQPACKET) socket, the arguments |
162 | | * dest_addr and addrlen are ignored (and the error |
163 | | * EISCONN may be returned when they are not NULL and 0)..." |
164 | | */ |
165 | 0 | ret = send(fd, buf, len, 0); |
166 | 0 | } else |
167 | 0 | ret = sendto(fd, buf, len, 0, (struct sockaddr*)&dgram->addr.to, get_addr_len(&dgram->addr.to)); |
168 | |
|
169 | 0 | if (ret < 0) { |
170 | 0 | if (errno == EAGAIN || errno == EWOULDBLOCK) { |
171 | 0 | struct ist myist; |
172 | |
|
173 | 0 | myist = ist2(buf, len); |
174 | 0 | ret = dns_ring_write(ns->dgram->ring_req, DNS_MAX_MSG_SIZE, NULL, 0, &myist, 1); |
175 | 0 | if (!ret) { |
176 | 0 | ns->counters->snd_error++; |
177 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
178 | 0 | return -1; |
179 | 0 | } |
180 | 0 | fd_cant_send(fd); |
181 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
182 | 0 | return ret; |
183 | 0 | } |
184 | 0 | ns->counters->snd_error++; |
185 | 0 | fd_delete(fd); |
186 | 0 | dgram->t.sock.fd = -1; |
187 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
188 | 0 | return -1; |
189 | 0 | } |
190 | 0 | ns->counters->sent++; |
191 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
192 | 0 | } |
193 | 0 | else if (dns_server_type_is_stream(type) && ns->stream) { |
194 | 0 | struct ist myist; |
195 | |
|
196 | 0 | myist = ist2(buf, len); |
197 | 0 | ret = dns_ring_write(ns->stream->ring_req, DNS_MAX_MSG_SIZE, NULL, 0, &myist, 1); |
198 | 0 | if (!ret) { |
199 | 0 | ns->counters->snd_error++; |
200 | 0 | return -1; |
201 | 0 | } |
202 | 0 | task_wakeup(ns->stream->task_req, TASK_WOKEN_MSG); |
203 | 0 | return ret; |
204 | 0 | } |
205 | | |
206 | 0 | return ret; |
207 | 0 | } |
208 | | |
209 | | void dns_session_free(struct dns_session *); |
210 | | |
211 | | /* Receives a DNS message using server type <type> on nameserver <ns>. |
212 | | * Returns message length |
213 | | * 0 is returned if no more message available |
214 | | * -1 in error case |
215 | | */ |
216 | | ssize_t dns_recv_nameserver(struct dns_nameserver *ns, enum dns_server_type type, void *data, size_t size) |
217 | 0 | { |
218 | 0 | ssize_t ret = -1; |
219 | |
|
220 | 0 | if (dns_server_type_is_dgram(type) && ns->dgram) { |
221 | 0 | struct dgram_conn *dgram = &ns->dgram->conn; |
222 | 0 | struct sockaddr_storage from = {0}; |
223 | 0 | socklen_t fromlen = sizeof(from); |
224 | 0 | int fd; |
225 | |
|
226 | 0 | HA_SPIN_LOCK(DNS_LOCK, &dgram->lock); |
227 | 0 | fd = dgram->t.sock.fd; |
228 | 0 | if (fd == -1) { |
229 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
230 | 0 | return -1; |
231 | 0 | } |
232 | | |
233 | 0 | if ((ret = recvfrom(fd, data, size, 0, (struct sockaddr *)&from, &fromlen)) < 0) { |
234 | 0 | if (errno == EAGAIN || errno == EWOULDBLOCK) { |
235 | 0 | fd_cant_recv(fd); |
236 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
237 | 0 | return 0; |
238 | 0 | } |
239 | 0 | fd_delete(fd); |
240 | 0 | dgram->t.sock.fd = -1; |
241 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
242 | 0 | return -1; |
243 | 0 | } |
244 | 0 | if ((dgram->addr.to.ss_family == AF_INET || dgram->addr.to.ss_family == AF_INET6) && |
245 | 0 | ipcmp(&from, &dgram->addr.to, 1) != 0) { |
246 | | /* reply from unexpected source -- silently discard */ |
247 | 0 | fd_want_recv(fd); |
248 | 0 | ret = 0; |
249 | 0 | } |
250 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
251 | 0 | } |
252 | 0 | else if (dns_server_type_is_stream(type) && ns->stream) { |
253 | 0 | struct dns_stream_server *dss = ns->stream; |
254 | 0 | struct dns_session *ds; |
255 | |
|
256 | 0 | HA_SPIN_LOCK(DNS_LOCK, &dss->lock); |
257 | |
|
258 | 0 | if (!LIST_ISEMPTY(&dss->wait_sess)) { |
259 | 0 | ds = LIST_NEXT(&dss->wait_sess, struct dns_session *, waiter); |
260 | 0 | ret = ds->rx_msg.len < size ? ds->rx_msg.len : size; |
261 | 0 | memcpy(data, ds->rx_msg.area, ret); |
262 | |
|
263 | 0 | ds->rx_msg.len = 0; |
264 | | |
265 | | /* This barrier is here to ensure that all data is |
266 | | * stored if the appctx detect the elem is out of the |
267 | | * list. |
268 | | */ |
269 | 0 | __ha_barrier_store(); |
270 | |
|
271 | 0 | LIST_DEL_INIT(&ds->waiter); |
272 | |
|
273 | 0 | if (ds->appctx) { |
274 | | /* This second barrier is here to ensure that |
275 | | * the waked up appctx won't miss that the elem |
276 | | * is removed from the list. |
277 | | */ |
278 | 0 | __ha_barrier_store(); |
279 | | |
280 | | /* awake appctx because it may have other |
281 | | * message to receive |
282 | | */ |
283 | 0 | appctx_wakeup(ds->appctx); |
284 | | |
285 | | /* dns_session could already be into free_sess list |
286 | | * so we firstly remove it */ |
287 | 0 | LIST_DEL_INIT(&ds->list); |
288 | | |
289 | | /* decrease nb_queries to free a slot for a new query on that sess */ |
290 | 0 | ds->nb_queries--; |
291 | 0 | if (ds->nb_queries) { |
292 | | /* it remains pipelined unanswered request |
293 | | * into this session but we just decrease |
294 | | * the counter so the session |
295 | | * can not be full of pipelined requests |
296 | | * so we can add if to free_sess list |
297 | | * to receive a new request |
298 | | */ |
299 | 0 | LIST_INSERT(&ds->dss->free_sess, &ds->list); |
300 | 0 | } |
301 | 0 | else { |
302 | | /* there is no more pipelined requests |
303 | | * into this session, so we move it |
304 | | * to idle_sess list */ |
305 | 0 | LIST_INSERT(&ds->dss->idle_sess, &ds->list); |
306 | | |
307 | | /* update the counter of idle sessions */ |
308 | 0 | ds->dss->idle_conns++; |
309 | | |
310 | | /* Note: this is useless there to update |
311 | | * the max_active_conns since we increase |
312 | | * the idle count */ |
313 | 0 | } |
314 | 0 | } |
315 | 0 | else { |
316 | | /* there is no more appctx for this session |
317 | | * it means it is ready to die |
318 | | */ |
319 | 0 | dns_session_free(ds); |
320 | 0 | } |
321 | | |
322 | |
|
323 | 0 | } |
324 | |
|
325 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dss->lock); |
326 | 0 | } |
327 | | |
328 | 0 | return ret; |
329 | 0 | } |
330 | | |
331 | | static void dns_resolve_recv(struct dgram_conn *dgram) |
332 | 0 | { |
333 | 0 | struct dns_nameserver *ns; |
334 | 0 | int fd; |
335 | |
|
336 | 0 | HA_SPIN_LOCK(DNS_LOCK, &dgram->lock); |
337 | |
|
338 | 0 | fd = dgram->t.sock.fd; |
339 | | |
340 | | /* check if ready for reading */ |
341 | 0 | if ((fd == -1) || !fd_recv_ready(fd)) { |
342 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
343 | 0 | return; |
344 | 0 | } |
345 | | |
346 | | /* no need to go further if we can't retrieve the nameserver */ |
347 | 0 | if ((ns = dgram->owner) == NULL) { |
348 | 0 | _HA_ATOMIC_AND(&fdtab[fd].state, ~(FD_POLL_HUP|FD_POLL_ERR)); |
349 | 0 | fd_stop_recv(fd); |
350 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
351 | 0 | return; |
352 | 0 | } |
353 | | |
354 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
355 | |
|
356 | 0 | ns->process_responses(ns, DNS_SERVER_DGRAM); |
357 | 0 | } |
358 | | |
359 | | /* Called when a dns network socket is ready to send data */ |
360 | | static void dns_resolve_send(struct dgram_conn *dgram) |
361 | 0 | { |
362 | 0 | int fd; |
363 | 0 | struct dns_nameserver *ns; |
364 | 0 | struct dns_ring *ring; |
365 | 0 | struct buffer *buf; |
366 | 0 | uint64_t msg_len; |
367 | 0 | size_t len, cnt, ofs; |
368 | |
|
369 | 0 | HA_SPIN_LOCK(DNS_LOCK, &dgram->lock); |
370 | |
|
371 | 0 | fd = dgram->t.sock.fd; |
372 | | |
373 | | /* check if ready for sending */ |
374 | 0 | if ((fd == -1) || !fd_send_ready(fd)) { |
375 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
376 | 0 | return; |
377 | 0 | } |
378 | | |
379 | | /* no need to go further if we can't retrieve the nameserver */ |
380 | 0 | if ((ns = dgram->owner) == NULL) { |
381 | 0 | _HA_ATOMIC_AND(&fdtab[fd].state, ~(FD_POLL_HUP|FD_POLL_ERR)); |
382 | 0 | fd_stop_send(fd); |
383 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
384 | 0 | return; |
385 | 0 | } |
386 | | |
387 | 0 | ring = ns->dgram->ring_req; |
388 | 0 | buf = &ring->buf; |
389 | |
|
390 | 0 | HA_RWLOCK_RDLOCK(DNS_LOCK, &ring->lock); |
391 | | |
392 | | /* explanation for the initialization below: it would be better to do |
393 | | * this in the parsing function but this would occasionally result in |
394 | | * dropped events because we'd take a reference on the oldest message |
395 | | * and keep it while being scheduled. Thus instead let's take it the |
396 | | * first time we enter here so that we have a chance to pass many |
397 | | * existing messages before grabbing a reference to a location. This |
398 | | * value cannot be produced after initialization. |
399 | | */ |
400 | 0 | if (unlikely(ns->dgram->ofs_req == ~0)) { |
401 | 0 | ns->dgram->ofs_req = b_peek_ofs(buf, 0); |
402 | 0 | HA_ATOMIC_INC(b_orig(buf) + ns->dgram->ofs_req); |
403 | 0 | } |
404 | | |
405 | | /* we were already there, adjust the offset to be relative to |
406 | | * the buffer's head and remove us from the counter. |
407 | | */ |
408 | 0 | ofs = ns->dgram->ofs_req - b_head_ofs(buf); |
409 | 0 | if (ns->dgram->ofs_req < b_head_ofs(buf)) |
410 | 0 | ofs += b_size(buf); |
411 | 0 | BUG_ON(ofs >= buf->size); |
412 | 0 | HA_ATOMIC_DEC(b_peek(buf, ofs)); |
413 | |
|
414 | 0 | while (ofs + 1 < b_data(buf)) { |
415 | 0 | int ret; |
416 | |
|
417 | 0 | cnt = 1; |
418 | 0 | len = b_peek_varint(buf, ofs + cnt, &msg_len); |
419 | 0 | if (!len) |
420 | 0 | break; |
421 | 0 | cnt += len; |
422 | 0 | BUG_ON(msg_len + ofs + cnt + 1 > b_data(buf)); |
423 | 0 | if (unlikely(msg_len > DNS_MAX_MSG_SIZE)) { |
424 | | /* too large a message to ever fit, let's skip it */ |
425 | 0 | ofs += cnt + msg_len; |
426 | 0 | continue; |
427 | 0 | } |
428 | | |
429 | 0 | len = b_getblk(buf, dns_msg_trash, msg_len, ofs + cnt); |
430 | |
|
431 | 0 | ret = send(fd, dns_msg_trash, len, 0); |
432 | 0 | if (ret < 0) { |
433 | 0 | if (errno == EAGAIN || errno == EWOULDBLOCK) { |
434 | 0 | fd_cant_send(fd); |
435 | 0 | goto out; |
436 | 0 | } |
437 | 0 | ns->counters->snd_error++; |
438 | 0 | fd_delete(fd); |
439 | 0 | fd = dgram->t.sock.fd = -1; |
440 | 0 | goto out; |
441 | 0 | } |
442 | 0 | ns->counters->sent++; |
443 | |
|
444 | 0 | ofs += cnt + len; |
445 | 0 | } |
446 | | |
447 | | /* we don't want/need to be waked up any more for sending |
448 | | * because all ring content is sent */ |
449 | 0 | fd_stop_send(fd); |
450 | |
|
451 | 0 | out: |
452 | 0 | HA_ATOMIC_INC(b_peek(buf, ofs)); |
453 | 0 | ns->dgram->ofs_req = b_peek_ofs(buf, ofs); |
454 | 0 | HA_RWLOCK_RDUNLOCK(DNS_LOCK, &ring->lock); |
455 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dgram->lock); |
456 | 0 | } |
457 | | |
458 | | /* proto_udp callback functions for a DNS resolution */ |
459 | | struct dgram_data_cb dns_dgram_cb = { |
460 | | .recv = dns_resolve_recv, |
461 | | .send = dns_resolve_send, |
462 | | }; |
463 | | |
464 | | int dns_dgram_init(struct dns_nameserver *ns, struct sockaddr_storage *sk) |
465 | 0 | { |
466 | 0 | struct dns_dgram_server *dgram; |
467 | |
|
468 | 0 | if ((dgram = calloc(1, sizeof(*dgram))) == NULL) |
469 | 0 | return -1; |
470 | | |
471 | | /* Leave dgram partially initialized, no FD attached for |
472 | | * now. */ |
473 | 0 | dgram->conn.owner = ns; |
474 | 0 | dgram->conn.data = &dns_dgram_cb; |
475 | 0 | dgram->conn.t.sock.fd = -1; |
476 | 0 | dgram->conn.addr.to = *sk; |
477 | 0 | HA_SPIN_INIT(&dgram->conn.lock); |
478 | |
|
479 | 0 | dgram->ofs_req = ~0; /* init ring offset */ |
480 | 0 | dgram->ring_req = dns_ring_new(2*DNS_MSG_RING_MAX_SIZE); |
481 | 0 | if (!dgram->ring_req) { |
482 | 0 | ha_alert("memory allocation error initializing the ring for nameserver.\n"); |
483 | 0 | goto out; |
484 | 0 | } |
485 | | |
486 | | /* attach the task as reader */ |
487 | 0 | if (!dns_ring_attach(dgram->ring_req)) { |
488 | | /* mark server attached to the ring */ |
489 | 0 | ha_alert("nameserver sets too many watchers > 255 on ring. This is a bug and should not happen.\n"); |
490 | 0 | goto out; |
491 | 0 | } |
492 | 0 | ns->dgram = dgram; |
493 | 0 | return 0; |
494 | 0 | out: |
495 | 0 | dns_ring_free(dgram->ring_req); |
496 | |
|
497 | 0 | free(dgram); |
498 | |
|
499 | 0 | return -1; |
500 | 0 | } |
501 | | |
502 | | /* |
503 | | * IO Handler to handle message push to dns tcp server |
504 | | * It takes its context from appctx->svcctx. |
505 | | */ |
506 | | static void dns_session_io_handler(struct appctx *appctx) |
507 | 0 | { |
508 | 0 | struct dns_session *ds = appctx->svcctx; |
509 | 0 | struct dns_ring *ring = &ds->ring; |
510 | 0 | struct buffer *buf = &ring->buf; |
511 | 0 | uint64_t msg_len; |
512 | 0 | int available_room; |
513 | 0 | size_t len, cnt, ofs; |
514 | 0 | int ret = 0; |
515 | |
|
516 | 0 | if (unlikely(applet_fl_test(appctx, APPCTX_FL_EOS|APPCTX_FL_ERROR))) { |
517 | 0 | applet_reset_input(appctx); |
518 | 0 | goto out; |
519 | 0 | } |
520 | | |
521 | | /* if stopping was requested, close immediately */ |
522 | 0 | if (unlikely(stopping)) |
523 | 0 | goto close; |
524 | | |
525 | | /* we want to be sure to not miss that we have been awaked for a shutdown */ |
526 | 0 | __ha_barrier_load(); |
527 | | |
528 | | /* that means the connection was requested to shutdown |
529 | | * for instance idle expire */ |
530 | 0 | if (ds->shutdown) |
531 | 0 | goto close; |
532 | | |
533 | | /* if the connection is not established, inform the stream that we want |
534 | | * to be notified whenever the connection completes. |
535 | | */ |
536 | 0 | if (se_fl_test(appctx->sedesc, SE_FL_APPLET_NEED_CONN)) { |
537 | 0 | applet_need_more_data(appctx); |
538 | 0 | applet_have_more_data(appctx); |
539 | 0 | goto out; |
540 | 0 | } |
541 | | |
542 | 0 | if (applet_get_outbuf(appctx) == NULL) { |
543 | 0 | applet_have_more_data(appctx); |
544 | 0 | goto out; |
545 | 0 | } |
546 | | |
547 | 0 | HA_RWLOCK_WRLOCK(DNS_LOCK, &ring->lock); |
548 | 0 | MT_LIST_DELETE(&appctx->wait_entry); |
549 | 0 | HA_RWLOCK_WRUNLOCK(DNS_LOCK, &ring->lock); |
550 | |
|
551 | 0 | HA_RWLOCK_RDLOCK(DNS_LOCK, &ring->lock); |
552 | | |
553 | | /* explanation for the initialization below: it would be better to do |
554 | | * this in the parsing function but this would occasionally result in |
555 | | * dropped events because we'd take a reference on the oldest message |
556 | | * and keep it while being scheduled. Thus instead let's take it the |
557 | | * first time we enter here so that we have a chance to pass many |
558 | | * existing messages before grabbing a reference to a location. This |
559 | | * value cannot be produced after initialization. |
560 | | */ |
561 | 0 | if (unlikely(ds->ofs == ~0)) { |
562 | 0 | ds->ofs = b_peek_ofs(buf, 0); |
563 | 0 | HA_ATOMIC_INC(b_orig(buf) + ds->ofs); |
564 | 0 | } |
565 | | |
566 | | /* we were already there, adjust the offset to be relative to |
567 | | * the buffer's head and remove us from the counter. |
568 | | */ |
569 | 0 | ofs = ds->ofs - b_head_ofs(buf); |
570 | 0 | if (ds->ofs < b_head_ofs(buf)) |
571 | 0 | ofs += b_size(buf); |
572 | |
|
573 | 0 | BUG_ON(ofs >= buf->size); |
574 | 0 | HA_ATOMIC_DEC(b_peek(buf, ofs)); |
575 | | |
576 | | /* in following loop, ofs always points to the counter byte that |
577 | | * precedes the message so that we can take our reference there if we |
578 | | * have to stop before the end (ret=0). |
579 | | */ |
580 | 0 | ret = 1; |
581 | 0 | while (ofs + 1 < b_data(buf)) { |
582 | 0 | struct dns_query *query; |
583 | 0 | uint16_t original_qid; |
584 | 0 | uint16_t new_qid; |
585 | |
|
586 | 0 | cnt = 1; |
587 | 0 | len = b_peek_varint(buf, ofs + cnt, &msg_len); |
588 | 0 | if (!len) |
589 | 0 | break; |
590 | 0 | cnt += len; |
591 | 0 | BUG_ON(msg_len + ofs + cnt + 1 > b_data(buf)); |
592 | | |
593 | | /* retrieve available room on output channel */ |
594 | 0 | available_room = applet_output_room(appctx); |
595 | | |
596 | | /* tx_msg_offset null means we are at the start of a new message */ |
597 | 0 | if (!ds->tx_msg_offset) { |
598 | 0 | uint16_t slen; |
599 | | |
600 | | /* check if there is enough room to put message len and query id */ |
601 | 0 | if (available_room < sizeof(slen) + sizeof(new_qid)) { |
602 | 0 | applet_have_more_data(appctx); |
603 | 0 | ret = 0; |
604 | 0 | break; |
605 | 0 | } |
606 | | |
607 | | /* put msg len into then channel */ |
608 | 0 | slen = (uint16_t)msg_len; |
609 | 0 | slen = htons(slen); |
610 | 0 | applet_putblk(appctx, (char *)&slen, sizeof(slen)); |
611 | 0 | available_room -= sizeof(slen); |
612 | | |
613 | | /* backup original query id */ |
614 | 0 | len = b_getblk(buf, (char *)&original_qid, sizeof(original_qid), ofs + cnt); |
615 | 0 | if (!len) { |
616 | | /* should never happen since messages are atomically |
617 | | * written into ring |
618 | | */ |
619 | 0 | ret = 0; |
620 | 0 | break; |
621 | 0 | } |
622 | | |
623 | | /* generates new query id */ |
624 | 0 | new_qid = ++ds->query_counter; |
625 | 0 | new_qid = htons(new_qid); |
626 | | |
627 | | /* put new query id into the channel */ |
628 | 0 | applet_putblk(appctx, (char *)&new_qid, sizeof(new_qid)); |
629 | 0 | available_room -= sizeof(new_qid); |
630 | | |
631 | | /* keep query id mapping */ |
632 | |
|
633 | 0 | query = pool_alloc(dns_query_pool); |
634 | 0 | if (query) { |
635 | 0 | query->qid.key = new_qid; |
636 | 0 | query->original_qid = original_qid; |
637 | 0 | query->expire = tick_add(now_ms, 5000); |
638 | 0 | LIST_INIT(&query->list); |
639 | 0 | if (LIST_ISEMPTY(&ds->queries)) { |
640 | | /* enable task to handle expire */ |
641 | 0 | ds->task_exp->expire = query->expire; |
642 | | /* ensure this will be executed by the same |
643 | | * thread than ds_session_release |
644 | | * to ensure session_release is free |
645 | | * to destroy the task */ |
646 | 0 | task_queue(ds->task_exp); |
647 | 0 | } |
648 | 0 | LIST_APPEND(&ds->queries, &query->list); |
649 | 0 | eb32_insert(&ds->query_ids, &query->qid); |
650 | 0 | ds->onfly_queries++; |
651 | 0 | HA_ATOMIC_STORE(&ds->dss->consecutive_errors, 0); |
652 | 0 | } |
653 | | |
654 | | /* update the tx_offset to handle output in 16k streams */ |
655 | 0 | ds->tx_msg_offset = sizeof(original_qid); |
656 | |
|
657 | 0 | } |
658 | | |
659 | | /* check if it remains available room on output chan */ |
660 | 0 | if (unlikely(!available_room)) { |
661 | 0 | applet_have_more_data(appctx); |
662 | 0 | ret = 0; |
663 | 0 | break; |
664 | 0 | } |
665 | | |
666 | 0 | chunk_reset(&trash); |
667 | 0 | if ((msg_len - ds->tx_msg_offset) > available_room) { |
668 | | /* remaining msg data is too large to be written in output channel at one time */ |
669 | |
|
670 | 0 | len = b_getblk(buf, trash.area, available_room, ofs + cnt + ds->tx_msg_offset); |
671 | | |
672 | | /* update offset to complete mesg forwarding later */ |
673 | 0 | ds->tx_msg_offset += len; |
674 | 0 | } |
675 | 0 | else { |
676 | | /* remaining msg data can be written in output channel at one time */ |
677 | 0 | len = b_getblk(buf, trash.area, msg_len - ds->tx_msg_offset, ofs + cnt + ds->tx_msg_offset); |
678 | | |
679 | | /* reset tx_msg_offset to mark forward fully processed */ |
680 | 0 | ds->tx_msg_offset = 0; |
681 | 0 | } |
682 | 0 | trash.data += len; |
683 | |
|
684 | 0 | if (applet_putchk(appctx, &trash) == -1) { |
685 | | /* should never happen since we |
686 | | * check available_room is large |
687 | | * enough here. |
688 | | */ |
689 | 0 | ret = 0; |
690 | 0 | break; |
691 | 0 | } |
692 | | |
693 | 0 | if (ds->tx_msg_offset) { |
694 | | /* msg was not fully processed, we must be awake to drain pending data */ |
695 | 0 | applet_have_more_data(appctx); |
696 | 0 | ret = 0; |
697 | 0 | break; |
698 | 0 | } |
699 | | /* switch to next message */ |
700 | 0 | ofs += cnt + msg_len; |
701 | 0 | } |
702 | | |
703 | 0 | HA_ATOMIC_INC(b_peek(buf, ofs)); |
704 | 0 | ds->ofs = b_peek_ofs(buf, ofs); |
705 | |
|
706 | 0 | HA_RWLOCK_RDUNLOCK(DNS_LOCK, &ring->lock); |
707 | |
|
708 | 0 | if (ret) { |
709 | | /* let's be woken up once new request to write arrived */ |
710 | 0 | HA_RWLOCK_WRLOCK(DNS_LOCK, &ring->lock); |
711 | 0 | BUG_ON(MT_LIST_INLIST(&appctx->wait_entry)); |
712 | 0 | MT_LIST_APPEND(&ring->waiters, &appctx->wait_entry); |
713 | 0 | HA_RWLOCK_WRUNLOCK(DNS_LOCK, &ring->lock); |
714 | 0 | applet_have_no_more_data(appctx); |
715 | 0 | } |
716 | | |
717 | | /* if session is not a waiter it means there is no committed |
718 | | * message into rx_buf and we are free to use it |
719 | | * Note: we need a load barrier here to not miss the |
720 | | * delete from the list |
721 | | */ |
722 | 0 | __ha_barrier_load(); |
723 | 0 | if (!LIST_INLIST_ATOMIC(&ds->waiter)) { |
724 | 0 | if (applet_get_inbuf(appctx) == NULL) { |
725 | 0 | applet_need_more_data(appctx); |
726 | 0 | goto out; |
727 | 0 | } |
728 | | |
729 | 0 | while (1) { |
730 | 0 | uint16_t query_id; |
731 | 0 | struct eb32_node *eb; |
732 | 0 | struct dns_query *query; |
733 | |
|
734 | 0 | if (!ds->rx_msg.len) { |
735 | | /* retrieve message len */ |
736 | 0 | ret = applet_getblk(appctx, (char *)&msg_len, 2, 0); |
737 | 0 | if (ret <= 0) { |
738 | 0 | if (ret == -1) |
739 | 0 | goto error; |
740 | 0 | applet_need_more_data(appctx); |
741 | 0 | break; |
742 | 0 | } |
743 | | |
744 | | /* mark as consumed */ |
745 | 0 | applet_skip_input(appctx, 2); |
746 | | |
747 | | /* store message len */ |
748 | 0 | ds->rx_msg.len = ntohs(msg_len); |
749 | 0 | if (!ds->rx_msg.len) |
750 | 0 | continue; |
751 | 0 | } |
752 | | |
753 | 0 | if (applet_input_data(appctx) + ds->rx_msg.offset < ds->rx_msg.len) { |
754 | | /* message only partially available */ |
755 | | |
756 | | /* read available data */ |
757 | 0 | ret = applet_getblk(appctx, ds->rx_msg.area + ds->rx_msg.offset, applet_input_data(appctx), 0); |
758 | 0 | if (ret <= 0) { |
759 | 0 | if (ret == -1) |
760 | 0 | goto error; |
761 | 0 | applet_need_more_data(appctx); |
762 | 0 | break; |
763 | 0 | } |
764 | | |
765 | | /* update message offset */ |
766 | 0 | ds->rx_msg.offset += applet_input_data(appctx); |
767 | | |
768 | | /* consume all pending data from the channel */ |
769 | 0 | applet_skip_input(appctx, applet_input_data(appctx)); |
770 | | |
771 | | /* we need to wait for more data */ |
772 | 0 | applet_need_more_data(appctx); |
773 | 0 | break; |
774 | 0 | } |
775 | | |
776 | | /* enough data is available into the channel to read the message until the end */ |
777 | | |
778 | | /* read from the channel until the end of the message */ |
779 | 0 | ret = applet_getblk(appctx, ds->rx_msg.area + ds->rx_msg.offset, ds->rx_msg.len - ds->rx_msg.offset, 0); |
780 | 0 | if (ret <= 0) { |
781 | 0 | if (ret == -1) |
782 | 0 | goto error; |
783 | 0 | applet_need_more_data(appctx); |
784 | 0 | break; |
785 | 0 | } |
786 | | |
787 | | /* consume all data until the end of the message from the channel */ |
788 | 0 | applet_skip_input(appctx, ds->rx_msg.len - ds->rx_msg.offset); |
789 | | |
790 | | /* reset reader offset to 0 for next message reand */ |
791 | 0 | ds->rx_msg.offset = 0; |
792 | | |
793 | | /* try remap query id to original */ |
794 | 0 | memcpy(&query_id, ds->rx_msg.area, sizeof(query_id)); |
795 | 0 | eb = eb32_lookup(&ds->query_ids, query_id); |
796 | 0 | if (!eb) { |
797 | | /* query id not found means we have an unknown corresponding |
798 | | * request, perhaps server's bug or or the query reached |
799 | | * timeout |
800 | | */ |
801 | 0 | ds->rx_msg.len = 0; |
802 | 0 | continue; |
803 | 0 | } |
804 | | |
805 | | /* re-map the original query id set by the requester */ |
806 | 0 | query = eb32_entry(eb, struct dns_query, qid); |
807 | 0 | memcpy(ds->rx_msg.area, &query->original_qid, sizeof(query->original_qid)); |
808 | | |
809 | | /* remove query ids mapping from pending queries list/tree */ |
810 | 0 | eb32_delete(&query->qid); |
811 | 0 | LIST_DELETE(&query->list); |
812 | 0 | pool_free(dns_query_pool, query); |
813 | 0 | ds->onfly_queries--; |
814 | | |
815 | | /* the dns_session is also added in queue of the |
816 | | * wait_sess list where the task processing |
817 | | * response will pop available responses |
818 | | */ |
819 | 0 | HA_SPIN_LOCK(DNS_LOCK, &ds->dss->lock); |
820 | |
|
821 | 0 | BUG_ON(LIST_INLIST(&ds->waiter)); |
822 | 0 | LIST_APPEND(&ds->dss->wait_sess, &ds->waiter); |
823 | |
|
824 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &ds->dss->lock); |
825 | | |
826 | | /* awake the task processing the responses */ |
827 | 0 | task_wakeup(ds->dss->task_rsp, TASK_WOKEN_INIT); |
828 | |
|
829 | 0 | break; |
830 | 0 | } |
831 | 0 | } |
832 | | |
833 | 0 | out: |
834 | 0 | return; |
835 | | |
836 | 0 | close: |
837 | 0 | applet_set_eos(appctx); |
838 | 0 | goto out; |
839 | | |
840 | 0 | error: |
841 | 0 | applet_set_eos(appctx); |
842 | 0 | applet_set_error(appctx); |
843 | 0 | goto out; |
844 | 0 | } |
845 | | |
846 | | void dns_queries_flush(struct dns_session *ds) |
847 | 0 | { |
848 | 0 | struct dns_query *query, *queryb; |
849 | |
|
850 | 0 | list_for_each_entry_safe(query, queryb, &ds->queries, list) { |
851 | 0 | eb32_delete(&query->qid); |
852 | 0 | LIST_DELETE(&query->list); |
853 | 0 | pool_free(dns_query_pool, query); |
854 | 0 | } |
855 | 0 | } |
856 | | |
857 | | void dns_session_free(struct dns_session *ds) |
858 | 0 | { |
859 | 0 | pool_free(dns_msg_buf, ds->rx_msg.area); |
860 | 0 | pool_free(dns_msg_buf, ds->tx_ring_area); |
861 | 0 | task_destroy(ds->task_exp); |
862 | |
|
863 | 0 | dns_queries_flush(ds); |
864 | | |
865 | | /* Ensure to remove this session from external lists |
866 | | * Note: we are under the lock of dns_stream_server |
867 | | * which own the heads of those lists. |
868 | | */ |
869 | 0 | LIST_DEL_INIT(&ds->waiter); |
870 | 0 | LIST_DEL_INIT(&ds->list); |
871 | |
|
872 | 0 | ds->dss->cur_conns--; |
873 | | /* Note: this is useless to update |
874 | | * max_active_conns here because |
875 | | * we decrease the value |
876 | | */ |
877 | |
|
878 | 0 | BUG_ON(!LIST_ISEMPTY(&ds->list)); |
879 | 0 | BUG_ON(!LIST_ISEMPTY(&ds->waiter)); |
880 | 0 | BUG_ON(!LIST_ISEMPTY(&ds->queries)); |
881 | 0 | BUG_ON(!MT_LIST_ISEMPTY(&ds->ring.waiters)); |
882 | 0 | BUG_ON(!eb_is_empty(&ds->query_ids)); |
883 | 0 | pool_free(dns_session_pool, ds); |
884 | 0 | } |
885 | | |
886 | | static struct appctx *dns_session_create(struct dns_session *ds, int tempo); |
887 | | |
888 | | static int dns_session_init(struct appctx *appctx) |
889 | 0 | { |
890 | 0 | struct dns_session *ds = appctx->svcctx; |
891 | 0 | struct stream *s; |
892 | 0 | struct sockaddr_storage *addr = NULL; |
893 | |
|
894 | 0 | if (!sockaddr_alloc(&addr, &ds->dss->srv->addr, sizeof(ds->dss->srv->addr))) |
895 | 0 | goto error; |
896 | | |
897 | 0 | if (appctx_finalize_startup(appctx, ds->dss->srv->proxy, &BUF_NULL) == -1) |
898 | 0 | goto error; |
899 | | |
900 | 0 | s = appctx_strm(appctx); |
901 | 0 | s->scb->dst = addr; |
902 | 0 | s->scb->flags |= (SC_FL_RCV_ONCE|SC_FL_NOLINGER); |
903 | 0 | stream_set_srv_target(s, ds->dss->srv); |
904 | 0 | s->flags = SF_ASSIGNED; |
905 | |
|
906 | 0 | s->do_log = NULL; |
907 | 0 | s->uniq_id = 0; |
908 | |
|
909 | 0 | se_need_remote_conn(appctx->sedesc); |
910 | 0 | applet_expect_no_data(appctx); |
911 | 0 | ds->appctx = appctx; |
912 | 0 | appctx->t->expire = TICK_ETERNITY; |
913 | 0 | return 0; |
914 | | |
915 | 0 | error: |
916 | 0 | sockaddr_free(&addr); |
917 | 0 | return -1; |
918 | 0 | } |
919 | | |
920 | | /* |
921 | | * Function to release a DNS tcp session |
922 | | */ |
923 | | static void dns_session_release(struct appctx *appctx) |
924 | 0 | { |
925 | 0 | struct dns_session *ds = appctx->svcctx; |
926 | 0 | struct dns_stream_server *dss __maybe_unused; |
927 | 0 | int consecutive_errors; |
928 | |
|
929 | 0 | if (!ds) |
930 | 0 | return; |
931 | | |
932 | | /* We do not call dns_ring_appctx_detach here |
933 | | * because we want to keep readers counters |
934 | | * to retry a conn with a different appctx. |
935 | | */ |
936 | 0 | HA_RWLOCK_WRLOCK(DNS_LOCK, &ds->ring.lock); |
937 | 0 | MT_LIST_DELETE(&appctx->wait_entry); |
938 | 0 | HA_RWLOCK_WRUNLOCK(DNS_LOCK, &ds->ring.lock); |
939 | |
|
940 | 0 | dss = ds->dss; |
941 | |
|
942 | 0 | HA_SPIN_LOCK(DNS_LOCK, &dss->lock); |
943 | 0 | LIST_DEL_INIT(&ds->list); |
944 | |
|
945 | 0 | if (stopping) { |
946 | 0 | dns_session_free(ds); |
947 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dss->lock); |
948 | 0 | return; |
949 | 0 | } |
950 | | |
951 | 0 | if (!ds->nb_queries) { |
952 | | /* this is an idle session */ |
953 | | /* Note: this is useless to update max_active_sess |
954 | | * here because we decrease idle_conns but |
955 | | * dns_session_free decrease curconns |
956 | | */ |
957 | |
|
958 | 0 | ds->dss->idle_conns--; |
959 | 0 | dns_session_free(ds); |
960 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dss->lock); |
961 | 0 | return; |
962 | 0 | } |
963 | | |
964 | 0 | if (ds->onfly_queries == ds->nb_queries) { |
965 | | /* the session can be released because |
966 | | * it means that all queries AND |
967 | | * responses are in fly */ |
968 | 0 | dns_session_free(ds); |
969 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dss->lock); |
970 | 0 | return; |
971 | 0 | } |
972 | | |
973 | | /* if there is no pending complete response |
974 | | * message, ensure to reset |
975 | | * message offsets if the session |
976 | | * was closed with an incomplete pending response |
977 | | */ |
978 | 0 | if (!LIST_INLIST(&ds->waiter)) |
979 | 0 | ds->rx_msg.len = ds->rx_msg.offset = 0; |
980 | | |
981 | | /* we flush pending sent queries because we never |
982 | | * have responses |
983 | | */ |
984 | 0 | ds->nb_queries -= ds->onfly_queries; |
985 | 0 | dns_queries_flush(ds); |
986 | | |
987 | | /* reset offset to be sure to start from message start */ |
988 | 0 | ds->tx_msg_offset = 0; |
989 | |
|
990 | 0 | consecutive_errors = HA_ATOMIC_LOAD(&ds->dss->consecutive_errors); |
991 | | /* we know ds encountered an error because it failed to send all |
992 | | * its queries: increase consecutive_errors (we take some precautions |
993 | | * to prevent the counter from overflowing since it is atomically |
994 | | * updated) |
995 | | */ |
996 | 0 | while (consecutive_errors < DNS_MAX_DSS_CONSECUTIVE_ERRORS && |
997 | 0 | !HA_ATOMIC_CAS(&ds->dss->consecutive_errors, |
998 | 0 | &consecutive_errors, consecutive_errors + 1) && |
999 | 0 | __ha_cpu_relax()); |
1000 | | |
1001 | | /* here the ofs and the attached counter |
1002 | | * are kept unchanged |
1003 | | */ |
1004 | | |
1005 | | /* Create a new appctx, We hope we can |
1006 | | * create from the release callback! */ |
1007 | 0 | ds->appctx = dns_session_create(ds, 1); |
1008 | 0 | if (!ds->appctx) { |
1009 | 0 | dns_session_free(ds); |
1010 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dss->lock); |
1011 | 0 | return; |
1012 | 0 | } |
1013 | | |
1014 | 0 | if (ds->nb_queries < DNS_STREAM_MAX_PIPELINED_REQ) |
1015 | 0 | LIST_INSERT(&ds->dss->free_sess, &ds->list); |
1016 | |
|
1017 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dss->lock); |
1018 | 0 | } |
1019 | | |
1020 | | /* DNS tcp session applet */ |
1021 | | static struct applet dns_session_applet = { |
1022 | | .obj_type = OBJ_TYPE_APPLET, |
1023 | | .flags = APPLET_FL_NEW_API, |
1024 | | .name = "<STRMDNS>", /* used for logging */ |
1025 | | .fct = dns_session_io_handler, |
1026 | | .rcv_buf = appctx_raw_rcv_buf, |
1027 | | .snd_buf = appctx_raw_snd_buf, |
1028 | | .init = dns_session_init, |
1029 | | .release = dns_session_release, |
1030 | | }; |
1031 | | |
1032 | | /* |
1033 | | * Function used to create an appctx for a DNS session |
1034 | | * It sets its context into appctx->svcctx. |
1035 | | * if <tempo> is set, then the session startup will be delayed by 1 |
1036 | | * second |
1037 | | */ |
1038 | | static struct appctx *dns_session_create(struct dns_session *ds, int tempo) |
1039 | 0 | { |
1040 | 0 | struct appctx *appctx; |
1041 | |
|
1042 | 0 | appctx = appctx_new_here(&dns_session_applet, NULL); |
1043 | 0 | if (!appctx) |
1044 | 0 | goto out_close; |
1045 | 0 | appctx->svcctx = (void *)ds; |
1046 | |
|
1047 | 0 | if (!tempo) { |
1048 | 0 | if (appctx_init(appctx) == -1) { |
1049 | 0 | ha_alert("out of memory in dns_session_create().\n"); |
1050 | 0 | goto out_free_appctx; |
1051 | 0 | } |
1052 | 0 | } |
1053 | 0 | else |
1054 | 0 | appctx_schedule(appctx, tick_add(now_ms, MS_TO_TICKS(1000))); |
1055 | | |
1056 | 0 | return appctx; |
1057 | | |
1058 | | /* Error unrolling */ |
1059 | 0 | out_free_appctx: |
1060 | 0 | appctx_free_on_early_error(appctx); |
1061 | 0 | out_close: |
1062 | 0 | return NULL; |
1063 | 0 | } |
1064 | | |
1065 | | /* Task processing expiration of unresponded queries, this one is supposed |
1066 | | * to be stuck on the same thread than the appctx handler |
1067 | | */ |
1068 | | static struct task *dns_process_query_exp(struct task *t, void *context, unsigned int state) |
1069 | 0 | { |
1070 | 0 | struct dns_session *ds = (struct dns_session *)context; |
1071 | 0 | struct dns_query *query, *queryb; |
1072 | |
|
1073 | 0 | t->expire = TICK_ETERNITY; |
1074 | |
|
1075 | 0 | list_for_each_entry_safe(query, queryb, &ds->queries, list) { |
1076 | 0 | if (tick_is_expired(query->expire, now_ms)) { |
1077 | 0 | eb32_delete(&query->qid); |
1078 | 0 | LIST_DELETE(&query->list); |
1079 | 0 | pool_free(dns_query_pool, query); |
1080 | 0 | ds->onfly_queries--; |
1081 | 0 | } |
1082 | 0 | else { |
1083 | 0 | t->expire = query->expire; |
1084 | 0 | break; |
1085 | 0 | } |
1086 | 0 | } |
1087 | |
|
1088 | 0 | return t; |
1089 | 0 | } |
1090 | | |
1091 | | /* Task processing expiration of idle sessions */ |
1092 | | static struct task *dns_process_idle_exp(struct task *t, void *context, unsigned int state) |
1093 | 0 | { |
1094 | 0 | struct dns_stream_server *dss = (struct dns_stream_server *)context; |
1095 | 0 | struct dns_session *ds, *dsb; |
1096 | 0 | int target = 0; |
1097 | 0 | int cur_active_conns; |
1098 | |
|
1099 | 0 | HA_SPIN_LOCK(DNS_LOCK, &dss->lock); |
1100 | | |
1101 | |
|
1102 | 0 | cur_active_conns = dss->cur_conns - dss->idle_conns; |
1103 | 0 | if (cur_active_conns > dss->max_active_conns) |
1104 | 0 | dss->max_active_conns = cur_active_conns; |
1105 | |
|
1106 | 0 | target = (dss->max_active_conns - cur_active_conns) / 2; |
1107 | 0 | list_for_each_entry_safe(ds, dsb, &dss->idle_sess, list) { |
1108 | 0 | if (!stopping && !target) |
1109 | 0 | break; |
1110 | | |
1111 | | /* remove conn to pending list to ensure it won't be reused */ |
1112 | 0 | LIST_DEL_INIT(&ds->list); |
1113 | | |
1114 | | /* force session shutdown */ |
1115 | 0 | ds->shutdown = 1; |
1116 | | |
1117 | | /* to be sure that the appctx won't miss shutdown */ |
1118 | 0 | __ha_barrier_store(); |
1119 | | |
1120 | | /* wake appctx to perform the shutdown */ |
1121 | 0 | appctx_wakeup(ds->appctx); |
1122 | 0 | } |
1123 | | |
1124 | | /* reset max to current active conns */ |
1125 | 0 | dss->max_active_conns = cur_active_conns; |
1126 | |
|
1127 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dss->lock); |
1128 | |
|
1129 | 0 | t->expire = tick_add(now_ms, 5000); |
1130 | |
|
1131 | 0 | return t; |
1132 | 0 | } |
1133 | | |
1134 | | struct dns_session *dns_session_new(struct dns_stream_server *dss) |
1135 | 0 | { |
1136 | 0 | struct dns_session *ds; |
1137 | |
|
1138 | 0 | if (dss->maxconn && (dss->maxconn <= dss->cur_conns)) |
1139 | 0 | return NULL; |
1140 | | |
1141 | 0 | if (HA_ATOMIC_LOAD(&dss->consecutive_errors) >= DNS_MAX_DSS_CONSECUTIVE_ERRORS) |
1142 | 0 | return NULL; |
1143 | | |
1144 | 0 | ds = pool_zalloc(dns_session_pool); |
1145 | 0 | if (!ds) |
1146 | 0 | return NULL; |
1147 | | |
1148 | 0 | ds->ofs = ~0; |
1149 | 0 | ds->dss = dss; |
1150 | 0 | LIST_INIT(&ds->list); |
1151 | 0 | LIST_INIT(&ds->queries); |
1152 | 0 | LIST_INIT(&ds->waiter); |
1153 | 0 | ds->rx_msg.offset = ds->rx_msg.len = 0; |
1154 | 0 | ds->rx_msg.area = NULL; |
1155 | 0 | ds->tx_ring_area = NULL; |
1156 | 0 | ds->task_exp = NULL; |
1157 | 0 | ds->appctx = NULL; |
1158 | 0 | ds->shutdown = 0; |
1159 | 0 | ds->nb_queries = 0; |
1160 | 0 | ds->query_ids = EB_ROOT_UNIQUE; |
1161 | 0 | ds->rx_msg.area = pool_alloc(dns_msg_buf); |
1162 | 0 | if (!ds->rx_msg.area) |
1163 | 0 | goto error; |
1164 | | |
1165 | 0 | ds->tx_ring_area = pool_alloc(dns_msg_buf); |
1166 | 0 | if (!ds->tx_ring_area) |
1167 | 0 | goto error; |
1168 | | |
1169 | 0 | dns_ring_init(&ds->ring, ds->tx_ring_area, DNS_MSG_RING_MAX_SIZE); |
1170 | | /* never fail because it is the first watcher attached to the ring */ |
1171 | 0 | DISGUISE(dns_ring_attach(&ds->ring)); |
1172 | |
|
1173 | 0 | if ((ds->task_exp = task_new_here()) == NULL) |
1174 | 0 | goto error; |
1175 | | |
1176 | 0 | ds->task_exp->process = dns_process_query_exp; |
1177 | 0 | ds->task_exp->context = ds; |
1178 | |
|
1179 | 0 | ds->appctx = dns_session_create(ds, 0); |
1180 | 0 | if (!ds->appctx) |
1181 | 0 | goto error; |
1182 | | |
1183 | 0 | dss->cur_conns++; |
1184 | |
|
1185 | 0 | return ds; |
1186 | | |
1187 | 0 | error: |
1188 | 0 | task_destroy(ds->task_exp); |
1189 | 0 | pool_free(dns_msg_buf, ds->rx_msg.area); |
1190 | 0 | pool_free(dns_msg_buf, ds->tx_ring_area); |
1191 | |
|
1192 | 0 | pool_free(dns_session_pool, ds); |
1193 | |
|
1194 | 0 | return NULL; |
1195 | 0 | } |
1196 | | |
1197 | | /* |
1198 | | * Task used to consume pending messages from nameserver ring |
1199 | | * and forward them to dns_session ring. |
1200 | | * Note: If no slot found a new dns_session is allocated |
1201 | | */ |
1202 | | static struct task *dns_process_req(struct task *t, void *context, unsigned int state) |
1203 | 0 | { |
1204 | 0 | struct dns_nameserver *ns = (struct dns_nameserver *)context; |
1205 | 0 | struct dns_stream_server *dss = ns->stream; |
1206 | 0 | struct dns_ring *ring = dss->ring_req; |
1207 | 0 | struct buffer *buf = &ring->buf; |
1208 | 0 | uint64_t msg_len; |
1209 | 0 | size_t len, cnt, ofs; |
1210 | 0 | struct dns_session *ds, *ads; |
1211 | 0 | HA_SPIN_LOCK(DNS_LOCK, &dss->lock); |
1212 | |
|
1213 | 0 | HA_RWLOCK_RDLOCK(DNS_LOCK, &ring->lock); |
1214 | | |
1215 | | /* explanation for the initialization below: it would be better to do |
1216 | | * this in the parsing function but this would occasionally result in |
1217 | | * dropped events because we'd take a reference on the oldest message |
1218 | | * and keep it while being scheduled. Thus instead let's take it the |
1219 | | * first time we enter here so that we have a chance to pass many |
1220 | | * existing messages before grabbing a reference to a location. This |
1221 | | * value cannot be produced after initialization. |
1222 | | */ |
1223 | 0 | if (unlikely(dss->ofs_req == ~0)) { |
1224 | 0 | dss->ofs_req = b_peek_ofs(buf, 0); |
1225 | 0 | HA_ATOMIC_INC(b_orig(buf) + dss->ofs_req); |
1226 | 0 | } |
1227 | | |
1228 | | /* we were already there, adjust the offset to be relative to |
1229 | | * the buffer's head and remove us from the counter. |
1230 | | */ |
1231 | 0 | ofs = dss->ofs_req - b_head_ofs(buf); |
1232 | 0 | if (dss->ofs_req < b_head_ofs(buf)) |
1233 | 0 | ofs += b_size(buf); |
1234 | |
|
1235 | 0 | BUG_ON(ofs >= buf->size); |
1236 | 0 | HA_ATOMIC_DEC(b_peek(buf, ofs)); |
1237 | |
|
1238 | 0 | while (ofs + 1 < b_data(buf)) { |
1239 | 0 | struct ist myist; |
1240 | |
|
1241 | 0 | cnt = 1; |
1242 | 0 | len = b_peek_varint(buf, ofs + cnt, &msg_len); |
1243 | 0 | if (!len) |
1244 | 0 | break; |
1245 | 0 | cnt += len; |
1246 | 0 | BUG_ON(msg_len + ofs + cnt + 1 > b_data(buf)); |
1247 | 0 | if (unlikely(msg_len > DNS_MAX_MSG_SIZE)) { |
1248 | | /* too large a message to ever fit, let's skip it */ |
1249 | 0 | ofs += cnt + msg_len; |
1250 | 0 | continue; |
1251 | 0 | } |
1252 | | |
1253 | 0 | len = b_getblk(buf, dns_msg_trash, msg_len, ofs + cnt); |
1254 | |
|
1255 | 0 | myist = ist2(dns_msg_trash, len); |
1256 | |
|
1257 | 0 | ads = NULL; |
1258 | | /* try to push request into active sess with free slot */ |
1259 | 0 | if (!LIST_ISEMPTY(&dss->free_sess)) { |
1260 | 0 | ds = LIST_NEXT(&dss->free_sess, struct dns_session *, list); |
1261 | |
|
1262 | 0 | if (dns_ring_write(&ds->ring, DNS_MAX_MSG_SIZE, NULL, 0, &myist, 1) > 0) { |
1263 | 0 | ds->nb_queries++; |
1264 | 0 | if (ds->nb_queries >= DNS_STREAM_MAX_PIPELINED_REQ) |
1265 | 0 | LIST_DEL_INIT(&ds->list); |
1266 | 0 | ads = ds; |
1267 | 0 | } |
1268 | 0 | else { |
1269 | | /* it means we were unable to put a request in this slot, |
1270 | | * it may be close to be full so we put it at the end |
1271 | | * of free conn list */ |
1272 | 0 | LIST_DEL_INIT(&ds->list); |
1273 | 0 | LIST_APPEND(&dss->free_sess, &ds->list); |
1274 | 0 | } |
1275 | 0 | } |
1276 | |
|
1277 | 0 | if (!ads) { |
1278 | | /* try to push request into idle, this one should have enough free space */ |
1279 | 0 | if (!LIST_ISEMPTY(&dss->idle_sess)) { |
1280 | 0 | ds = LIST_NEXT(&dss->idle_sess, struct dns_session *, list); |
1281 | | |
1282 | | /* ring is empty so this dns_ring_write should never fail */ |
1283 | 0 | dns_ring_write(&ds->ring, DNS_MAX_MSG_SIZE, NULL, 0, &myist, 1); |
1284 | 0 | ds->nb_queries++; |
1285 | 0 | LIST_DEL_INIT(&ds->list); |
1286 | |
|
1287 | 0 | ds->dss->idle_conns--; |
1288 | | |
1289 | | /* we may have to update the max_active_conns */ |
1290 | 0 | if (ds->dss->max_active_conns < ds->dss->cur_conns - ds->dss->idle_conns) |
1291 | 0 | ds->dss->max_active_conns = ds->dss->cur_conns - ds->dss->idle_conns; |
1292 | | |
1293 | | /* since we may unable to find a free list to handle |
1294 | | * this request, this request may be large and fill |
1295 | | * the ring buffer so we prefer to put at the end of free |
1296 | | * list. */ |
1297 | 0 | LIST_APPEND(&dss->free_sess, &ds->list); |
1298 | 0 | ads = ds; |
1299 | 0 | } |
1300 | 0 | } |
1301 | | |
1302 | | /* we didn't find a session available with large enough room */ |
1303 | 0 | if (!ads) { |
1304 | | /* allocate a new session */ |
1305 | 0 | ads = dns_session_new(dss); |
1306 | 0 | if (ads) { |
1307 | | /* ring is empty so this dns_ring_write should never fail */ |
1308 | 0 | dns_ring_write(&ads->ring, DNS_MAX_MSG_SIZE, NULL, 0, &myist, 1); |
1309 | 0 | ads->nb_queries++; |
1310 | 0 | LIST_INSERT(&dss->free_sess, &ads->list); |
1311 | 0 | } |
1312 | 0 | else |
1313 | 0 | ns->counters->snd_error++; |
1314 | 0 | } |
1315 | |
|
1316 | 0 | if (ads) |
1317 | 0 | ns->counters->sent++; |
1318 | |
|
1319 | 0 | ofs += cnt + len; |
1320 | 0 | } |
1321 | | |
1322 | 0 | HA_ATOMIC_INC(b_peek(buf, ofs)); |
1323 | 0 | dss->ofs_req = b_peek_ofs(buf, ofs); |
1324 | 0 | HA_RWLOCK_RDUNLOCK(DNS_LOCK, &ring->lock); |
1325 | | |
1326 | |
|
1327 | 0 | HA_SPIN_UNLOCK(DNS_LOCK, &dss->lock); |
1328 | 0 | return t; |
1329 | 0 | } |
1330 | | |
1331 | | /* |
1332 | | * Task used to consume response |
1333 | | * Note: upper layer callback is called |
1334 | | */ |
1335 | | static struct task *dns_process_rsp(struct task *t, void *context, unsigned int state) |
1336 | 0 | { |
1337 | 0 | struct dns_nameserver *ns = (struct dns_nameserver *)context; |
1338 | |
|
1339 | 0 | ns->process_responses(ns, DNS_SERVER_STREAM); |
1340 | |
|
1341 | 0 | return t; |
1342 | 0 | } |
1343 | | |
1344 | | /* Function used to initialize an TCP nameserver */ |
1345 | | int dns_stream_init(struct dns_nameserver *ns, struct server *srv) |
1346 | 0 | { |
1347 | 0 | struct dns_stream_server *dss = NULL; |
1348 | |
|
1349 | 0 | dss = calloc(1, sizeof(*dss)); |
1350 | 0 | if (!dss) { |
1351 | 0 | ha_alert("memory allocation error initializing dns tcp server '%s'.\n", srv->id); |
1352 | 0 | goto out; |
1353 | 0 | } |
1354 | | |
1355 | 0 | dss->srv = srv; |
1356 | 0 | dss->maxconn = srv->maxconn; |
1357 | |
|
1358 | 0 | dss->ofs_req = ~0; /* init ring offset */ |
1359 | 0 | dss->ring_req = dns_ring_new(2*DNS_MSG_RING_MAX_SIZE); |
1360 | 0 | if (!dss->ring_req) { |
1361 | 0 | ha_alert("memory allocation error initializing the ring for dns tcp server '%s'.\n", srv->id); |
1362 | 0 | goto out; |
1363 | 0 | } |
1364 | | /* Create the task associated to the resolver target handling conns */ |
1365 | 0 | if ((dss->task_req = task_new_anywhere()) == NULL) { |
1366 | 0 | ha_alert("memory allocation error initializing req task for dns tcp server '%s'.\n", srv->id); |
1367 | 0 | goto out; |
1368 | 0 | } |
1369 | | |
1370 | | /* Update task's parameters */ |
1371 | 0 | dss->task_req->process = dns_process_req; |
1372 | 0 | dss->task_req->context = ns; |
1373 | | |
1374 | | /* attach the task as reader */ |
1375 | 0 | if (!dns_ring_attach(dss->ring_req)) { |
1376 | | /* mark server attached to the ring */ |
1377 | 0 | ha_alert("server '%s': too many watchers for ring. this should never happen.\n", srv->id); |
1378 | 0 | goto out; |
1379 | 0 | } |
1380 | | |
1381 | | /* Create the task associated to the resolver target handling conns */ |
1382 | 0 | if ((dss->task_rsp = task_new_anywhere()) == NULL) { |
1383 | 0 | ha_alert("memory allocation error initializing rsp task for dns tcp server '%s'.\n", srv->id); |
1384 | 0 | goto out; |
1385 | 0 | } |
1386 | | |
1387 | | /* Update task's parameters */ |
1388 | 0 | dss->task_rsp->process = dns_process_rsp; |
1389 | 0 | dss->task_rsp->context = ns; |
1390 | | |
1391 | | /* Create the task associated to the resolver target handling conns */ |
1392 | 0 | if ((dss->task_idle = task_new_anywhere()) == NULL) { |
1393 | 0 | ha_alert("memory allocation error initializing idle task for dns tcp server '%s'.\n", srv->id); |
1394 | 0 | goto out; |
1395 | 0 | } |
1396 | | |
1397 | | /* Update task's parameters */ |
1398 | 0 | dss->task_idle->process = dns_process_idle_exp; |
1399 | 0 | dss->task_idle->context = dss; |
1400 | 0 | dss->task_idle->expire = tick_add(now_ms, 5000); |
1401 | |
|
1402 | 0 | LIST_INIT(&dss->free_sess); |
1403 | 0 | LIST_INIT(&dss->idle_sess); |
1404 | 0 | LIST_INIT(&dss->wait_sess); |
1405 | 0 | HA_SPIN_INIT(&dss->lock); |
1406 | 0 | ns->stream = dss; |
1407 | 0 | return 0; |
1408 | 0 | out: |
1409 | 0 | if (dss && dss->task_rsp) |
1410 | 0 | task_destroy(dss->task_rsp); |
1411 | 0 | if (dss && dss->task_req) |
1412 | 0 | task_destroy(dss->task_req); |
1413 | 0 | if (dss && dss->ring_req) |
1414 | 0 | dns_ring_free(dss->ring_req); |
1415 | |
|
1416 | 0 | free(dss); |
1417 | 0 | return -1; |
1418 | 0 | } |
1419 | | |
1420 | | /* Releases transport allocations owned by <ns>. */ |
1421 | | void dns_nameserver_deinit(struct dns_nameserver *ns) |
1422 | 0 | { |
1423 | 0 | if (ns->dgram) { |
1424 | 0 | dns_ring_free(ns->dgram->ring_req); |
1425 | 0 | free(ns->dgram); |
1426 | 0 | ns->dgram = NULL; |
1427 | 0 | } |
1428 | |
|
1429 | 0 | if (ns->stream) { |
1430 | 0 | dns_ring_free(ns->stream->ring_req); |
1431 | 0 | task_destroy(ns->stream->task_req); |
1432 | 0 | task_destroy(ns->stream->task_rsp); |
1433 | 0 | task_destroy(ns->stream->task_idle); |
1434 | 0 | free(ns->stream); |
1435 | 0 | ns->stream = NULL; |
1436 | 0 | } |
1437 | 0 | } |
1438 | | |
1439 | | int init_dns_buffers() |
1440 | 0 | { |
1441 | 0 | dns_msg_trash = malloc(DNS_MAX_MSG_SIZE); |
1442 | 0 | if (!dns_msg_trash) |
1443 | 0 | return 0; |
1444 | | |
1445 | 0 | return 1; |
1446 | 0 | } |
1447 | | |
1448 | | void deinit_dns_buffers() |
1449 | 0 | { |
1450 | | ha_free(&dns_msg_trash); |
1451 | 0 | } |
1452 | | |
1453 | | REGISTER_PER_THREAD_ALLOC(init_dns_buffers); |
1454 | | REGISTER_PER_THREAD_FREE(deinit_dns_buffers); |