]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-stream.c
resolved: cache stringified transaction key once per transaction
[thirdparty/systemd.git] / src / resolve / resolved-dns-stream.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <netinet/tcp.h>
23
24 #include "alloc-util.h"
25 #include "fd-util.h"
26 #include "io-util.h"
27 #include "missing.h"
28 #include "resolved-dns-stream.h"
29
30 #define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC)
31 #define DNS_STREAMS_MAX 128
32
33 static void dns_stream_stop(DnsStream *s) {
34 assert(s);
35
36 s->io_event_source = sd_event_source_unref(s->io_event_source);
37 s->timeout_event_source = sd_event_source_unref(s->timeout_event_source);
38 s->fd = safe_close(s->fd);
39 }
40
41 static int dns_stream_update_io(DnsStream *s) {
42 int f = 0;
43
44 assert(s);
45
46 if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size)
47 f |= EPOLLOUT;
48 if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size)
49 f |= EPOLLIN;
50
51 return sd_event_source_set_io_events(s->io_event_source, f);
52 }
53
54 static int dns_stream_complete(DnsStream *s, int error) {
55 assert(s);
56
57 dns_stream_stop(s);
58
59 if (s->complete)
60 s->complete(s, error);
61 else
62 dns_stream_free(s);
63
64 return 0;
65 }
66
67 static int dns_stream_identify(DnsStream *s) {
68 union {
69 struct cmsghdr header; /* For alignment */
70 uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo))
71 + EXTRA_CMSG_SPACE /* kernel appears to require extra space */];
72 } control;
73 struct msghdr mh = {};
74 struct cmsghdr *cmsg;
75 socklen_t sl;
76 int r;
77
78 assert(s);
79
80 if (s->identified)
81 return 0;
82
83 /* Query the local side */
84 s->local_salen = sizeof(s->local);
85 r = getsockname(s->fd, &s->local.sa, &s->local_salen);
86 if (r < 0)
87 return -errno;
88 if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0)
89 s->ifindex = s->local.in6.sin6_scope_id;
90
91 /* Query the remote side */
92 s->peer_salen = sizeof(s->peer);
93 r = getpeername(s->fd, &s->peer.sa, &s->peer_salen);
94 if (r < 0)
95 return -errno;
96 if (s->peer.sa.sa_family == AF_INET6 && s->ifindex <= 0)
97 s->ifindex = s->peer.in6.sin6_scope_id;
98
99 /* Check consistency */
100 assert(s->peer.sa.sa_family == s->local.sa.sa_family);
101 assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6));
102
103 /* Query connection meta information */
104 sl = sizeof(control);
105 if (s->peer.sa.sa_family == AF_INET) {
106 r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl);
107 if (r < 0)
108 return -errno;
109 } else if (s->peer.sa.sa_family == AF_INET6) {
110
111 r = getsockopt(s->fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl);
112 if (r < 0)
113 return -errno;
114 } else
115 return -EAFNOSUPPORT;
116
117 mh.msg_control = &control;
118 mh.msg_controllen = sl;
119
120 CMSG_FOREACH(cmsg, &mh) {
121
122 if (cmsg->cmsg_level == IPPROTO_IPV6) {
123 assert(s->peer.sa.sa_family == AF_INET6);
124
125 switch (cmsg->cmsg_type) {
126
127 case IPV6_PKTINFO: {
128 struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg);
129
130 if (s->ifindex <= 0)
131 s->ifindex = i->ipi6_ifindex;
132 break;
133 }
134
135 case IPV6_HOPLIMIT:
136 s->ttl = *(int *) CMSG_DATA(cmsg);
137 break;
138 }
139
140 } else if (cmsg->cmsg_level == IPPROTO_IP) {
141 assert(s->peer.sa.sa_family == AF_INET);
142
143 switch (cmsg->cmsg_type) {
144
145 case IP_PKTINFO: {
146 struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg);
147
148 if (s->ifindex <= 0)
149 s->ifindex = i->ipi_ifindex;
150 break;
151 }
152
153 case IP_TTL:
154 s->ttl = *(int *) CMSG_DATA(cmsg);
155 break;
156 }
157 }
158 }
159
160 /* The Linux kernel sets the interface index to the loopback
161 * device if the connection came from the local host since it
162 * avoids the routing table in such a case. Let's unset the
163 * interface index in such a case. */
164 if (s->ifindex == LOOPBACK_IFINDEX)
165 s->ifindex = 0;
166
167 /* If we don't know the interface index still, we look for the
168 * first local interface with a matching address. Yuck! */
169 if (s->ifindex <= 0)
170 s->ifindex = manager_find_ifindex(s->manager, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr);
171
172 if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) {
173 uint32_t ifindex = htobe32(s->ifindex);
174
175 /* Make sure all packets for this connection are sent on the same interface */
176 if (s->local.sa.sa_family == AF_INET) {
177 r = setsockopt(s->fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex));
178 if (r < 0)
179 log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF: %m");
180 } else if (s->local.sa.sa_family == AF_INET6) {
181 r = setsockopt(s->fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex));
182 if (r < 0)
183 log_debug_errno(errno, "Failed to invoke IPV6_UNICAST_IF: %m");
184 }
185 }
186
187 s->identified = true;
188
189 return 0;
190 }
191
192 static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) {
193 DnsStream *s = userdata;
194
195 assert(s);
196
197 return dns_stream_complete(s, ETIMEDOUT);
198 }
199
200 static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
201 DnsStream *s = userdata;
202 int r;
203
204 assert(s);
205
206 r = dns_stream_identify(s);
207 if (r < 0)
208 return dns_stream_complete(s, -r);
209
210 if ((revents & EPOLLOUT) &&
211 s->write_packet &&
212 s->n_written < sizeof(s->write_size) + s->write_packet->size) {
213
214 struct iovec iov[2];
215 ssize_t ss;
216
217 iov[0].iov_base = &s->write_size;
218 iov[0].iov_len = sizeof(s->write_size);
219 iov[1].iov_base = DNS_PACKET_DATA(s->write_packet);
220 iov[1].iov_len = s->write_packet->size;
221
222 IOVEC_INCREMENT(iov, 2, s->n_written);
223
224 ss = writev(fd, iov, 2);
225 if (ss < 0) {
226 if (errno != EINTR && errno != EAGAIN)
227 return dns_stream_complete(s, errno);
228 } else
229 s->n_written += ss;
230
231 /* Are we done? If so, disable the event source for EPOLLOUT */
232 if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) {
233 r = dns_stream_update_io(s);
234 if (r < 0)
235 return dns_stream_complete(s, -r);
236 }
237 }
238
239 if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) &&
240 (!s->read_packet ||
241 s->n_read < sizeof(s->read_size) + s->read_packet->size)) {
242
243 if (s->n_read < sizeof(s->read_size)) {
244 ssize_t ss;
245
246 ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read);
247 if (ss < 0) {
248 if (errno != EINTR && errno != EAGAIN)
249 return dns_stream_complete(s, errno);
250 } else if (ss == 0)
251 return dns_stream_complete(s, ECONNRESET);
252 else
253 s->n_read += ss;
254 }
255
256 if (s->n_read >= sizeof(s->read_size)) {
257
258 if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE)
259 return dns_stream_complete(s, EBADMSG);
260
261 if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) {
262 ssize_t ss;
263
264 if (!s->read_packet) {
265 r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size));
266 if (r < 0)
267 return dns_stream_complete(s, -r);
268
269 s->read_packet->size = be16toh(s->read_size);
270 s->read_packet->ipproto = IPPROTO_TCP;
271 s->read_packet->family = s->peer.sa.sa_family;
272 s->read_packet->ttl = s->ttl;
273 s->read_packet->ifindex = s->ifindex;
274
275 if (s->read_packet->family == AF_INET) {
276 s->read_packet->sender.in = s->peer.in.sin_addr;
277 s->read_packet->sender_port = be16toh(s->peer.in.sin_port);
278 s->read_packet->destination.in = s->local.in.sin_addr;
279 s->read_packet->destination_port = be16toh(s->local.in.sin_port);
280 } else {
281 assert(s->read_packet->family == AF_INET6);
282 s->read_packet->sender.in6 = s->peer.in6.sin6_addr;
283 s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port);
284 s->read_packet->destination.in6 = s->local.in6.sin6_addr;
285 s->read_packet->destination_port = be16toh(s->local.in6.sin6_port);
286
287 if (s->read_packet->ifindex == 0)
288 s->read_packet->ifindex = s->peer.in6.sin6_scope_id;
289 if (s->read_packet->ifindex == 0)
290 s->read_packet->ifindex = s->local.in6.sin6_scope_id;
291 }
292 }
293
294 ss = read(fd,
295 (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size),
296 sizeof(s->read_size) + be16toh(s->read_size) - s->n_read);
297 if (ss < 0) {
298 if (errno != EINTR && errno != EAGAIN)
299 return dns_stream_complete(s, errno);
300 } else if (ss == 0)
301 return dns_stream_complete(s, ECONNRESET);
302 else
303 s->n_read += ss;
304 }
305
306 /* Are we done? If so, disable the event source for EPOLLIN */
307 if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) {
308 r = dns_stream_update_io(s);
309 if (r < 0)
310 return dns_stream_complete(s, -r);
311
312 /* If there's a packet handler
313 * installed, call that. Note that
314 * this is optional... */
315 if (s->on_packet)
316 return s->on_packet(s);
317 }
318 }
319 }
320
321 if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) &&
322 (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size))
323 return dns_stream_complete(s, 0);
324
325 return 0;
326 }
327
328 DnsStream *dns_stream_free(DnsStream *s) {
329 if (!s)
330 return NULL;
331
332 dns_stream_stop(s);
333
334 if (s->manager) {
335 LIST_REMOVE(streams, s->manager->dns_streams, s);
336 s->manager->n_dns_streams--;
337 }
338
339 dns_packet_unref(s->write_packet);
340 dns_packet_unref(s->read_packet);
341
342 free(s);
343
344 return 0;
345 }
346
347 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free);
348
349 int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
350 static const int one = 1;
351 _cleanup_(dns_stream_freep) DnsStream *s = NULL;
352 int r;
353
354 assert(m);
355 assert(fd >= 0);
356
357 if (m->n_dns_streams > DNS_STREAMS_MAX)
358 return -EBUSY;
359
360 s = new0(DnsStream, 1);
361 if (!s)
362 return -ENOMEM;
363
364 s->fd = -1;
365 s->protocol = protocol;
366
367 r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
368 if (r < 0)
369 return -errno;
370
371 r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s);
372 if (r < 0)
373 return r;
374
375 r = sd_event_add_time(
376 m->event,
377 &s->timeout_event_source,
378 clock_boottime_or_monotonic(),
379 now(clock_boottime_or_monotonic()) + DNS_STREAM_TIMEOUT_USEC, 0,
380 on_stream_timeout, s);
381 if (r < 0)
382 return r;
383
384 LIST_PREPEND(streams, m->dns_streams, s);
385 s->manager = m;
386 s->fd = fd;
387 m->n_dns_streams++;
388
389 *ret = s;
390 s = NULL;
391
392 return 0;
393 }
394
395 int dns_stream_write_packet(DnsStream *s, DnsPacket *p) {
396 assert(s);
397
398 if (s->write_packet)
399 return -EBUSY;
400
401 s->write_packet = dns_packet_ref(p);
402 s->write_size = htobe16(p->size);
403 s->n_written = 0;
404
405 return dns_stream_update_io(s);
406 }