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