Blob


1 /* $OpenBSD: relay_udp.c,v 1.52 2026/03/02 19:28:01 rsadowski Exp $ */
3 /*
4 * Copyright (c) 2007 - 2013 Reyk Floeter <reyk@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/time.h>
22 #include <sys/socket.h>
23 #include <sys/tree.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
28 #include <signal.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <stdio.h>
35 #include <event.h>
36 #include <imsg.h>
38 #include "relayd.h"
39 #include "log.h"
41 extern volatile sig_atomic_t relay_sessions;
42 extern objid_t relay_conid;
44 static struct relayd *env = NULL;
45 struct shuffle relay_shuffle;
47 int relay_udp_socket(struct sockaddr_storage *, in_port_t,
48 struct protocol *);
49 void relay_udp_timeout(int, short, void *);
51 void relay_dns_log(struct rsession *, u_int8_t *, size_t);
52 void *relay_dns_validate(struct rsession *,
53 struct relay *, struct sockaddr_storage *,
54 u_int8_t *, size_t);
55 int relay_dns_request(struct rsession *);
56 void relay_udp_response(int, short, void *);
57 void relay_dns_result(struct rsession *, u_int8_t *, size_t);
58 int relay_dns_cmp(struct rsession *, struct rsession *);
60 void
61 relay_udp_privinit(struct relay *rlay)
62 {
63 if (rlay->rl_conf.flags & F_TLS)
64 fatalx("tls over udp is not supported");
65 rlay->rl_conf.flags |= F_UDP;
66 }
68 void
69 relay_udp_init(struct relayd *x_env, struct relay *rlay)
70 {
71 struct protocol *proto = rlay->rl_proto;
73 if (env == NULL)
74 env = x_env;
76 switch (proto->type) {
77 case RELAY_PROTO_DNS:
78 proto->validate = relay_dns_validate;
79 proto->request = relay_dns_request;
80 proto->cmp = relay_dns_cmp;
81 shuffle_init(&relay_shuffle);
82 break;
83 default:
84 fatalx("unsupported udp protocol");
85 break;
86 }
87 }
89 int
90 relay_udp_bind(struct sockaddr_storage *ss, in_port_t port,
91 struct protocol *proto)
92 {
93 int s;
95 if ((s = relay_udp_socket(ss, port, proto)) == -1)
96 return (-1);
98 if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1)
99 goto bad;
101 return (s);
103 bad:
104 close(s);
105 return (-1);
108 int
109 relay_udp_socket(struct sockaddr_storage *ss, in_port_t port,
110 struct protocol *proto)
112 int s = -1, val;
114 if (relay_socket_af(ss, port) == -1)
115 goto bad;
117 if ((s = socket(ss->ss_family, SOCK_DGRAM | SOCK_NONBLOCK,
118 IPPROTO_UDP)) == -1)
119 goto bad;
121 /*
122 * Socket options
123 */
124 if (proto->tcpflags & TCPFLAG_BUFSIZ) {
125 val = proto->tcpbufsiz;
126 if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
127 &val, sizeof(val)) == -1)
128 goto bad;
129 val = proto->tcpbufsiz;
130 if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
131 &val, sizeof(val)) == -1)
132 goto bad;
135 /*
136 * IP options
137 */
138 if (proto->tcpflags & TCPFLAG_IPTTL) {
139 val = (int)proto->tcpipttl;
140 switch (ss->ss_family) {
141 case AF_INET:
142 if (setsockopt(s, IPPROTO_IP, IP_TTL,
143 &val, sizeof(val)) == -1)
144 goto bad;
145 break;
146 case AF_INET6:
147 if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
148 &val, sizeof(val)) == -1)
149 goto bad;
150 break;
153 if (proto->tcpflags & TCPFLAG_IPMINTTL) {
154 val = (int)proto->tcpipminttl;
155 switch (ss->ss_family) {
156 case AF_INET:
157 if (setsockopt(s, IPPROTO_IP, IP_MINTTL,
158 &val, sizeof(val)) == -1)
159 goto bad;
160 break;
161 case AF_INET6:
162 if (setsockopt(s, IPPROTO_IPV6, IPV6_MINHOPCOUNT,
163 &val, sizeof(val)) == -1)
164 goto bad;
165 break;
169 return (s);
171 bad:
172 if (s != -1)
173 close(s);
174 return (-1);
177 void
178 relay_udp_response(int fd, short sig, void *arg)
180 struct rsession *con = arg;
181 struct relay *rlay = con->se_relay;
182 struct protocol *proto = rlay->rl_proto;
183 void *priv = NULL;
184 struct sockaddr_storage ss;
185 u_int8_t buf[IBUF_READ_SIZE];
186 ssize_t len;
187 socklen_t slen;
189 if (sig == EV_TIMEOUT) {
190 relay_udp_timeout(fd, sig, arg);
191 return;
194 if (rlay->rl_conf.flags & F_DISABLE)
195 return;
197 slen = sizeof(ss);
198 if ((len = recvfrom(fd, buf, sizeof(buf), 0,
199 (struct sockaddr*)&ss, &slen)) < 1)
200 return;
202 /* Parse and validate the packet header */
203 if (proto->validate != NULL &&
204 (priv = (*proto->validate)(con, rlay, &ss, buf, len)) == NULL)
205 return;
207 relay_close(con, "unknown response", 1);
208 free(priv);
211 void
212 relay_udp_server(int fd, short sig, void *arg)
214 struct privsep *ps = env->sc_ps;
215 struct relay *rlay = arg;
216 struct protocol *proto = rlay->rl_proto;
217 struct rsession *con = NULL;
218 struct ctl_natlook *cnl = NULL;
219 socklen_t slen;
220 struct timeval tv;
221 struct sockaddr_storage ss;
222 u_int8_t buf[IBUF_READ_SIZE];
223 void *priv = NULL;
224 ssize_t len;
226 event_add(&rlay->rl_ev, NULL);
228 if (rlay->rl_conf.flags & F_DISABLE)
229 return;
231 slen = sizeof(ss);
232 if ((len = recvfrom(fd, buf, sizeof(buf), 0,
233 (struct sockaddr*)&ss, &slen)) < 1)
234 return;
236 if (proto->validate != NULL &&
237 (priv = (*proto->validate)(NULL, rlay, &ss, buf, len)) == NULL)
238 return;
240 if ((con = calloc(1, sizeof(*con))) == NULL) {
241 free(priv);
242 return;
245 /*
246 * Replace the DNS request Id with a random Id.
247 */
248 con->se_priv = priv;
249 con->se_in.s = -1;
250 con->se_out.s = -1;
251 con->se_in.dst = &con->se_out;
252 con->se_out.dst = &con->se_in;
253 con->se_in.con = con;
254 con->se_out.con = con;
255 con->se_relay = rlay;
256 con->se_id = ++relay_conid;
257 con->se_in.dir = RELAY_DIR_REQUEST;
258 con->se_out.dir = RELAY_DIR_RESPONSE;
259 con->se_retry = rlay->rl_conf.dstretry;
260 con->se_out.port = rlay->rl_conf.dstport;
261 switch (ss.ss_family) {
262 case AF_INET:
263 con->se_in.port = ((struct sockaddr_in *)&ss)->sin_port;
264 break;
265 case AF_INET6:
266 con->se_in.port = ((struct sockaddr_in6 *)&ss)->sin6_port;
267 break;
269 bcopy(&ss, &con->se_in.ss, sizeof(con->se_in.ss));
271 getmonotime(&con->se_tv_start);
272 bcopy(&con->se_tv_start, &con->se_tv_last, sizeof(con->se_tv_last));
274 relay_sessions++;
275 SPLAY_INSERT(session_tree, &rlay->rl_sessions, con);
276 relay_session_publish(con);
278 /* Increment the per-relay session counter */
279 rlay->rl_stats[ps->ps_instance].last++;
281 /* Pre-allocate output buffer */
282 con->se_out.output = evbuffer_new();
283 if (con->se_out.output == NULL) {
284 relay_close(con, "failed to allocate output buffer", 1);
285 return;
288 /* Pre-allocate log buffer */
289 con->se_haslog = 0;
290 con->se_log = evbuffer_new();
291 if (con->se_log == NULL) {
292 relay_close(con, "failed to allocate log buffer", 1);
293 return;
296 if (rlay->rl_conf.flags & F_NATLOOK) {
297 if ((cnl = calloc(1, sizeof(*cnl))) == NULL) {
298 relay_close(con, "failed to allocate natlookup", 1);
299 return;
303 /* Save the received data */
304 if (evbuffer_add(con->se_out.output, buf, len) == -1) {
305 relay_close(con, "failed to store buffer", 1);
306 free(cnl);
307 return;
310 if (cnl != NULL) {
311 con->se_cnl = cnl;
312 bzero(cnl, sizeof(*cnl));
313 cnl->in = -1;
314 cnl->id = con->se_id;
315 cnl->proc = ps->ps_instance;
316 cnl->proto = IPPROTO_UDP;
317 bcopy(&con->se_in.ss, &cnl->src, sizeof(cnl->src));
318 bcopy(&rlay->rl_conf.ss, &cnl->dst, sizeof(cnl->dst));
319 proc_compose(env->sc_ps, PROC_PFE,
320 IMSG_NATLOOK, cnl, sizeof(*cnl));
322 /* Schedule timeout */
323 evtimer_set(&con->se_ev, relay_natlook, con);
324 bcopy(&rlay->rl_conf.timeout, &tv, sizeof(tv));
325 evtimer_add(&con->se_ev, &tv);
326 return;
329 relay_session(con);
332 void
333 relay_udp_timeout(int fd, short sig, void *arg)
335 struct rsession *con = arg;
337 if (sig != EV_TIMEOUT)
338 fatalx("invalid timeout event");
340 relay_close(con, "udp timeout", 1);
343 /*
344 * Domain Name System support
345 */
347 struct relay_dns_priv {
348 u_int16_t dp_inkey;
349 u_int16_t dp_outkey;
350 };
352 struct relay_dnshdr {
353 u_int16_t dns_id;
355 u_int8_t dns_flags0;
356 #define DNS_F0_QR 0x80 /* response flag */
357 #define DNS_F0_OPCODE 0x78 /* message type */
358 #define DNS_F0_AA 0x04 /* authoritative answer */
359 #define DNS_F0_TC 0x02 /* truncated message */
360 #define DNS_F0_RD 0x01 /* recursion desired */
362 u_int8_t dns_flags1;
363 #define DNS_F1_RA 0x80 /* recursion available */
364 #define DNS_F1_RES 0x40 /* reserved */
365 #define DNS_F1_AD 0x20 /* authentic data */
366 #define DNS_F1_CD 0x10 /* checking disabled */
367 #define DNS_F1_RCODE 0x0f /* response code */
369 u_int16_t dns_qdcount;
370 u_int16_t dns_ancount;
371 u_int16_t dns_nscount;
372 u_int16_t dns_arcount;
373 } __packed;
375 void
376 relay_dns_log(struct rsession *con, u_int8_t *buf, size_t len)
378 struct relay_dnshdr *hdr = (struct relay_dnshdr *)buf;
380 /* Validate the header length */
381 if (len < sizeof(*hdr)) {
382 log_debug("%s: session %d: short dns packet", __func__,
383 con->se_id);
384 return;
387 log_debug("%s: session %d: %s id 0x%x "
388 "flags 0x%x:0x%x qd %u an %u ns %u ar %u", __func__,
389 con->se_id,
390 hdr->dns_flags0 & DNS_F0_QR ? "response" : "request",
391 ntohs(hdr->dns_id),
392 hdr->dns_flags0,
393 hdr->dns_flags1,
394 ntohs(hdr->dns_qdcount),
395 ntohs(hdr->dns_ancount),
396 ntohs(hdr->dns_nscount),
397 ntohs(hdr->dns_arcount));
400 void *
401 relay_dns_validate(struct rsession *con, struct relay *rlay,
402 struct sockaddr_storage *ss, u_int8_t *buf, size_t len)
404 struct relay_dnshdr *hdr = (struct relay_dnshdr *)buf;
405 struct rsession lookup;
406 u_int16_t key;
407 struct relay_dns_priv *priv, lpriv;
409 /* Validate the header length */
410 if (len < sizeof(*hdr))
411 return (NULL);
413 key = ntohs(hdr->dns_id);
415 /*
416 * Check if the header has the response flag set, otherwise
417 * return 0 to tell the UDP server to create a new session.
418 */
419 if ((hdr->dns_flags0 & DNS_F0_QR) == 0) {
420 priv = malloc(sizeof(struct relay_dns_priv));
421 if (priv == NULL)
422 return (NULL);
423 priv->dp_inkey = shuffle_generate16(&relay_shuffle);
424 priv->dp_outkey = key;
425 return ((void *)priv);
428 /*
429 * Lookup if this response is for a known session and if the
430 * remote host matches the original destination of the request.
431 */
432 if (con == NULL) {
433 lpriv.dp_inkey = key;
434 lookup.se_priv = &lpriv;
435 if ((con = SPLAY_FIND(session_tree,
436 &rlay->rl_sessions, &lookup)) != NULL &&
437 con->se_priv != NULL &&
438 relay_cmp_af(ss, &con->se_out.ss) == 0)
439 relay_dns_result(con, buf, len);
440 } else {
441 priv = con->se_priv;
442 if (priv == NULL || key != priv->dp_inkey) {
443 relay_close(con, "invalid response", 1);
444 return (NULL);
446 relay_dns_result(con, buf, len);
449 /*
450 * This is not a new session, ignore it in the UDP server.
451 */
452 return (NULL);
455 int
456 relay_dns_request(struct rsession *con)
458 struct relay *rlay = con->se_relay;
459 struct relay_dns_priv *priv = con->se_priv;
460 u_int8_t *buf = EVBUFFER_DATA(con->se_out.output);
461 size_t len = EVBUFFER_LENGTH(con->se_out.output);
462 struct relay_dnshdr *hdr;
463 socklen_t slen;
465 if (buf == NULL || priv == NULL || len < 1)
466 return (-1);
467 if (log_getverbose() > 1)
468 relay_dns_log(con, buf, len);
470 getmonotime(&con->se_tv_start);
472 if (!TAILQ_EMPTY(&rlay->rl_tables)) {
473 if (relay_from_table(con) != 0)
474 return (-1);
475 } else if (con->se_out.ss.ss_family == AF_UNSPEC) {
476 bcopy(&rlay->rl_conf.dstss, &con->se_out.ss,
477 sizeof(con->se_out.ss));
478 con->se_out.port = rlay->rl_conf.dstport;
481 if ((con->se_out.s = relay_udp_socket(&con->se_out.ss,
482 con->se_out.port, rlay->rl_proto)) == -1)
483 return (-1);
484 slen = con->se_out.ss.ss_len;
486 hdr = (struct relay_dnshdr *)buf;
487 hdr->dns_id = htons(priv->dp_inkey);
489 retry:
490 if (sendto(con->se_out.s, buf, len, 0,
491 (struct sockaddr *)&con->se_out.ss, slen) == -1) {
492 if (con->se_retry) {
493 con->se_retry--;
494 log_debug("%s: session %d: "
495 "forward failed: %s, %s", __func__,
496 con->se_id, strerror(errno),
497 con->se_retry ? "next retry" : "last retry");
498 goto retry;
500 log_debug("%s: session %d: forward failed: %s", __func__,
501 con->se_id, strerror(errno));
502 return (-1);
505 event_again(&con->se_ev, con->se_out.s, EV_TIMEOUT|EV_READ,
506 relay_udp_response, &con->se_tv_start, &rlay->rl_conf.timeout, con);
508 return (0);
511 void
512 relay_dns_result(struct rsession *con, u_int8_t *buf, size_t len)
514 struct relay *rlay = con->se_relay;
515 struct relay_dns_priv *priv = con->se_priv;
516 struct relay_dnshdr *hdr;
517 socklen_t slen;
519 if (priv == NULL)
520 fatalx("%s: response to invalid session", __func__);
522 if (log_getverbose() > 1)
523 relay_dns_log(con, buf, len);
525 /*
526 * Replace the random DNS request Id with the original Id
527 */
528 hdr = (struct relay_dnshdr *)buf;
529 hdr->dns_id = htons(priv->dp_outkey);
531 slen = con->se_out.ss.ss_len;
532 if (sendto(rlay->rl_s, buf, len, 0,
533 (struct sockaddr *)&con->se_in.ss, slen) == -1) {
534 relay_close(con, "response failed", 1);
535 return;
538 relay_close(con, "session closed", 0);
541 int
542 relay_dns_cmp(struct rsession *a, struct rsession *b)
544 struct relay_dns_priv *ap = a->se_priv;
545 struct relay_dns_priv *bp = b->se_priv;
547 if (ap == NULL || bp == NULL)
548 fatalx("%s: invalid session", __func__);
550 return (memcmp(&ap->dp_inkey, &bp->dp_inkey, sizeof(u_int16_t)));