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