]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-stream.c
resolved: cache stringified transaction key once per transaction
[thirdparty/systemd.git] / src / resolve / resolved-dns-stream.c
CommitLineData
623a4c97
LP
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
b5efdb8a 24#include "alloc-util.h"
3ffd4af2 25#include "fd-util.h"
afc5dbf3 26#include "io-util.h"
623a4c97
LP
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
33static 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
41static 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
b914e211 54static int dns_stream_complete(DnsStream *s, int error) {
623a4c97
LP
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
b914e211
LP
67static int dns_stream_identify(DnsStream *s) {
68 union {
69 struct cmsghdr header; /* For alignment */
40a1eebd 70 uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo))
b914e211
LP
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;
2a1288ff
LP
119
120 CMSG_FOREACH(cmsg, &mh) {
b914e211
LP
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. */
a5f03596 164 if (s->ifindex == LOOPBACK_IFINDEX)
b914e211
LP
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)
56f64d95 179 log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF: %m");
b914e211
LP
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)
56f64d95 183 log_debug_errno(errno, "Failed to invoke IPV6_UNICAST_IF: %m");
b914e211
LP
184 }
185 }
186
187 s->identified = true;
188
189 return 0;
190}
191
623a4c97
LP
192static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) {
193 DnsStream *s = userdata;
194
195 assert(s);
196
b914e211 197 return dns_stream_complete(s, ETIMEDOUT);
623a4c97
LP
198}
199
200static 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
b914e211
LP
206 r = dns_stream_identify(s);
207 if (r < 0)
208 return dns_stream_complete(s, -r);
209
623a4c97
LP
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)
b914e211 227 return dns_stream_complete(s, errno);
623a4c97
LP
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)
b914e211 235 return dns_stream_complete(s, -r);
623a4c97
LP
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)
b914e211 249 return dns_stream_complete(s, errno);
623a4c97 250 } else if (ss == 0)
b914e211 251 return dns_stream_complete(s, ECONNRESET);
623a4c97
LP
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)
b914e211 259 return dns_stream_complete(s, EBADMSG);
623a4c97
LP
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)
b914e211 267 return dns_stream_complete(s, -r);
623a4c97
LP
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)
b914e211 299 return dns_stream_complete(s, errno);
623a4c97 300 } else if (ss == 0)
b914e211 301 return dns_stream_complete(s, ECONNRESET);
623a4c97
LP
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)
b914e211 310 return dns_stream_complete(s, -r);
623a4c97
LP
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))
b914e211 323 return dns_stream_complete(s, 0);
623a4c97
LP
324
325 return 0;
326}
327
328DnsStream *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
347DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free);
348
349int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
350 static const int one = 1;
623a4c97 351 _cleanup_(dns_stream_freep) DnsStream *s = NULL;
623a4c97
LP
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
623a4c97
LP
367 r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
368 if (r < 0)
369 return -errno;
370
623a4c97
LP
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
9a015429
LP
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);
623a4c97
LP
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
395int 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}