xref: /openbsd-src/usr.sbin/relayd/relay_udp.c (revision e15601b92a455e88bc02e7402d07250a11bffa0a)
1*e15601b9Sjsg /*	$OpenBSD: relay_udp.c,v 1.51 2024/05/18 06:34:46 jsg Exp $	*/
22380f4f2Sreyk 
32380f4f2Sreyk /*
4d535e21cSreyk  * Copyright (c) 2007 - 2013 Reyk Floeter <reyk@openbsd.org>
52380f4f2Sreyk  *
62380f4f2Sreyk  * Permission to use, copy, modify, and distribute this software for any
72380f4f2Sreyk  * purpose with or without fee is hereby granted, provided that the above
82380f4f2Sreyk  * copyright notice and this permission notice appear in all copies.
92380f4f2Sreyk  *
102380f4f2Sreyk  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
112380f4f2Sreyk  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
122380f4f2Sreyk  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
132380f4f2Sreyk  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
142380f4f2Sreyk  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
152380f4f2Sreyk  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
162380f4f2Sreyk  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
172380f4f2Sreyk  */
182380f4f2Sreyk 
192380f4f2Sreyk #include <sys/types.h>
200ca734d7Sreyk #include <sys/queue.h>
212380f4f2Sreyk #include <sys/time.h>
222380f4f2Sreyk #include <sys/socket.h>
232380f4f2Sreyk #include <sys/tree.h>
242380f4f2Sreyk 
252380f4f2Sreyk #include <netinet/in.h>
26f04ff968Sreyk #include <arpa/inet.h>
272380f4f2Sreyk 
28e2318a52Sderaadt #include <signal.h>
292380f4f2Sreyk #include <errno.h>
302380f4f2Sreyk #include <fcntl.h>
312380f4f2Sreyk #include <stdlib.h>
322380f4f2Sreyk #include <string.h>
332380f4f2Sreyk #include <unistd.h>
342380f4f2Sreyk #include <stdio.h>
352380f4f2Sreyk #include <event.h>
36f04ff968Sreyk #include <imsg.h>
372380f4f2Sreyk 
38748ceb64Sreyk #include "relayd.h"
392380f4f2Sreyk 
402380f4f2Sreyk extern volatile sig_atomic_t relay_sessions;
412380f4f2Sreyk extern objid_t relay_conid;
422380f4f2Sreyk 
43cb8b0e56Sreyk static struct relayd *env = NULL;
448661b3ffSreyk struct shuffle relay_shuffle;
452380f4f2Sreyk 
462380f4f2Sreyk int		 relay_udp_socket(struct sockaddr_storage *, in_port_t,
472380f4f2Sreyk 		    struct protocol *);
482380f4f2Sreyk void		 relay_udp_timeout(int, short, void *);
492380f4f2Sreyk 
50f4a6e73bSreyk void		 relay_dns_log(struct rsession *, u_int8_t *, size_t);
51f4a6e73bSreyk void		*relay_dns_validate(struct rsession *,
52bec706feSreyk 		    struct relay *, struct sockaddr_storage *,
530dbc5f9eSreyk 		    u_int8_t *, size_t);
54f4a6e73bSreyk int		 relay_dns_request(struct rsession *);
55bec706feSreyk void		 relay_udp_response(int, short, void *);
56f4a6e73bSreyk void		 relay_dns_result(struct rsession *, u_int8_t *, size_t);
57f4a6e73bSreyk int		 relay_dns_cmp(struct rsession *, struct rsession *);
582380f4f2Sreyk 
592380f4f2Sreyk void
relay_udp_privinit(struct relay * rlay)60f164d32fSbenno relay_udp_privinit(struct relay *rlay)
612380f4f2Sreyk {
627bb52228Sreyk 	if (rlay->rl_conf.flags & F_TLS)
637bb52228Sreyk 		fatalx("tls over udp is not supported");
644a5b9b3eSreyk 	rlay->rl_conf.flags |= F_UDP;
658661b3ffSreyk }
668661b3ffSreyk 
678661b3ffSreyk void
relay_udp_init(struct relayd * x_env,struct relay * rlay)68f164d32fSbenno relay_udp_init(struct relayd *x_env, struct relay *rlay)
698661b3ffSreyk {
708661b3ffSreyk 	struct protocol		*proto = rlay->rl_proto;
712380f4f2Sreyk 
72f164d32fSbenno 	if (env == NULL)
73f164d32fSbenno 		env = x_env;
74f164d32fSbenno 
752380f4f2Sreyk 	switch (proto->type) {
762380f4f2Sreyk 	case RELAY_PROTO_DNS:
772380f4f2Sreyk 		proto->validate = relay_dns_validate;
782380f4f2Sreyk 		proto->request = relay_dns_request;
792380f4f2Sreyk 		proto->cmp = relay_dns_cmp;
808661b3ffSreyk 		shuffle_init(&relay_shuffle);
812380f4f2Sreyk 		break;
822380f4f2Sreyk 	default:
832380f4f2Sreyk 		fatalx("unsupported udp protocol");
842380f4f2Sreyk 		break;
852380f4f2Sreyk 	}
862380f4f2Sreyk }
872380f4f2Sreyk 
882380f4f2Sreyk int
relay_udp_bind(struct sockaddr_storage * ss,in_port_t port,struct protocol * proto)892380f4f2Sreyk relay_udp_bind(struct sockaddr_storage *ss, in_port_t port,
902380f4f2Sreyk     struct protocol *proto)
912380f4f2Sreyk {
922380f4f2Sreyk 	int s;
932380f4f2Sreyk 
942380f4f2Sreyk 	if ((s = relay_udp_socket(ss, port, proto)) == -1)
952380f4f2Sreyk 		return (-1);
962380f4f2Sreyk 
972380f4f2Sreyk 	if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1)
982380f4f2Sreyk 		goto bad;
992380f4f2Sreyk 
1002380f4f2Sreyk 	return (s);
1012380f4f2Sreyk 
1022380f4f2Sreyk  bad:
1032380f4f2Sreyk 	close(s);
1042380f4f2Sreyk 	return (-1);
1052380f4f2Sreyk }
1062380f4f2Sreyk 
1072380f4f2Sreyk int
relay_udp_socket(struct sockaddr_storage * ss,in_port_t port,struct protocol * proto)1082380f4f2Sreyk relay_udp_socket(struct sockaddr_storage *ss, in_port_t port,
1092380f4f2Sreyk     struct protocol *proto)
1102380f4f2Sreyk {
1112380f4f2Sreyk 	int s = -1, val;
1122380f4f2Sreyk 
1132380f4f2Sreyk 	if (relay_socket_af(ss, port) == -1)
1142380f4f2Sreyk 		goto bad;
1152380f4f2Sreyk 
116b045ffeeSreyk 	if ((s = socket(ss->ss_family, SOCK_DGRAM | SOCK_NONBLOCK,
117b045ffeeSreyk 	    IPPROTO_UDP)) == -1)
1182380f4f2Sreyk 		goto bad;
1192380f4f2Sreyk 
1202380f4f2Sreyk 	/*
1212380f4f2Sreyk 	 * Socket options
1222380f4f2Sreyk 	 */
1232380f4f2Sreyk 	if (proto->tcpflags & TCPFLAG_BUFSIZ) {
1242380f4f2Sreyk 		val = proto->tcpbufsiz;
1252380f4f2Sreyk 		if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
1262380f4f2Sreyk 		    &val, sizeof(val)) == -1)
1272380f4f2Sreyk 			goto bad;
1282380f4f2Sreyk 		val = proto->tcpbufsiz;
1292380f4f2Sreyk 		if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
1302380f4f2Sreyk 		    &val, sizeof(val)) == -1)
1312380f4f2Sreyk 			goto bad;
1322380f4f2Sreyk 	}
1332380f4f2Sreyk 
1342380f4f2Sreyk 	/*
1352380f4f2Sreyk 	 * IP options
1362380f4f2Sreyk 	 */
1372380f4f2Sreyk 	if (proto->tcpflags & TCPFLAG_IPTTL) {
1382380f4f2Sreyk 		val = (int)proto->tcpipttl;
139829b7945Sjca 		switch (ss->ss_family) {
140829b7945Sjca 		case AF_INET:
1412380f4f2Sreyk 			if (setsockopt(s, IPPROTO_IP, IP_TTL,
1422380f4f2Sreyk 			    &val, sizeof(val)) == -1)
1432380f4f2Sreyk 				goto bad;
144829b7945Sjca 			break;
145829b7945Sjca 		case AF_INET6:
146829b7945Sjca 			if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
147829b7945Sjca 			    &val, sizeof(val)) == -1)
148829b7945Sjca 				goto bad;
149829b7945Sjca 			break;
150829b7945Sjca 		}
1512380f4f2Sreyk 	}
1522380f4f2Sreyk 	if (proto->tcpflags & TCPFLAG_IPMINTTL) {
1532380f4f2Sreyk 		val = (int)proto->tcpipminttl;
154829b7945Sjca 		switch (ss->ss_family) {
155829b7945Sjca 		case AF_INET:
1562380f4f2Sreyk 			if (setsockopt(s, IPPROTO_IP, IP_MINTTL,
1572380f4f2Sreyk 			    &val, sizeof(val)) == -1)
1582380f4f2Sreyk 				goto bad;
159829b7945Sjca 			break;
160829b7945Sjca 		case AF_INET6:
161829b7945Sjca 			if (setsockopt(s, IPPROTO_IPV6, IPV6_MINHOPCOUNT,
162829b7945Sjca 			    &val, sizeof(val)) == -1)
163829b7945Sjca 				goto bad;
164829b7945Sjca 			break;
165829b7945Sjca 		}
1662380f4f2Sreyk 	}
1672380f4f2Sreyk 
1682380f4f2Sreyk 	return (s);
1692380f4f2Sreyk 
1702380f4f2Sreyk  bad:
1712380f4f2Sreyk 	if (s != -1)
1722380f4f2Sreyk 		close(s);
1732380f4f2Sreyk 	return (-1);
1742380f4f2Sreyk }
1752380f4f2Sreyk 
1762380f4f2Sreyk void
relay_udp_response(int fd,short sig,void * arg)177bec706feSreyk relay_udp_response(int fd, short sig, void *arg)
178bec706feSreyk {
17948240b8fSbluhm 	struct rsession		*con = arg;
180bec706feSreyk 	struct relay		*rlay = con->se_relay;
181bec706feSreyk 	struct protocol		*proto = rlay->rl_proto;
182ba83ed70Sreyk 	void			*priv = NULL;
183bec706feSreyk 	struct sockaddr_storage	 ss;
184e39620e5Snicm 	u_int8_t		 buf[IBUF_READ_SIZE];
185bec706feSreyk 	ssize_t			 len;
186bec706feSreyk 	socklen_t		 slen;
187bec706feSreyk 
188bec706feSreyk 	if (sig == EV_TIMEOUT) {
189bec706feSreyk 		relay_udp_timeout(fd, sig, arg);
190bec706feSreyk 		return;
191bec706feSreyk 	}
192bec706feSreyk 
193ea42f25aSclaudio 	if (rlay->rl_conf.flags & F_DISABLE)
194bec706feSreyk 		return;
195bec706feSreyk 
196bec706feSreyk 	slen = sizeof(ss);
197bec706feSreyk 	if ((len = recvfrom(fd, buf, sizeof(buf), 0,
198bec706feSreyk 	    (struct sockaddr*)&ss, &slen)) < 1)
199bec706feSreyk 		return;
200bec706feSreyk 
201bec706feSreyk 	/* Parse and validate the packet header */
202bec706feSreyk 	if (proto->validate != NULL &&
203ba83ed70Sreyk 	    (priv = (*proto->validate)(con, rlay, &ss, buf, len)) == NULL)
204bec706feSreyk 		return;
205ba83ed70Sreyk 
2060be9d00aSbenno 	relay_close(con, "unknown response", 1);
207ba83ed70Sreyk 	free(priv);
208bec706feSreyk }
209bec706feSreyk 
210bec706feSreyk void
relay_udp_server(int fd,short sig,void * arg)2112380f4f2Sreyk relay_udp_server(int fd, short sig, void *arg)
2122380f4f2Sreyk {
213325f6e14Sreyk 	struct privsep *ps = env->sc_ps;
21448240b8fSbluhm 	struct relay *rlay = arg;
2154a5b9b3eSreyk 	struct protocol *proto = rlay->rl_proto;
216f4a6e73bSreyk 	struct rsession *con = NULL;
2172380f4f2Sreyk 	struct ctl_natlook *cnl = NULL;
2182380f4f2Sreyk 	socklen_t slen;
2192380f4f2Sreyk 	struct timeval tv;
2202380f4f2Sreyk 	struct sockaddr_storage ss;
221e39620e5Snicm 	u_int8_t buf[IBUF_READ_SIZE];
2220dbc5f9eSreyk 	void *priv = NULL;
2232380f4f2Sreyk 	ssize_t len;
2242380f4f2Sreyk 
225b3ad2344Sreyk 	event_add(&rlay->rl_ev, NULL);
226b3ad2344Sreyk 
227ea42f25aSclaudio 	if (rlay->rl_conf.flags & F_DISABLE)
2282380f4f2Sreyk 		return;
2292380f4f2Sreyk 
2302380f4f2Sreyk 	slen = sizeof(ss);
2312380f4f2Sreyk 	if ((len = recvfrom(fd, buf, sizeof(buf), 0,
2322380f4f2Sreyk 	    (struct sockaddr*)&ss, &slen)) < 1)
2332380f4f2Sreyk 		return;
2342380f4f2Sreyk 
2352380f4f2Sreyk 	if (proto->validate != NULL &&
236bec706feSreyk 	    (priv = (*proto->validate)(NULL, rlay, &ss, buf, len)) == NULL)
2372380f4f2Sreyk 		return;
2382380f4f2Sreyk 
23948240b8fSbluhm 	if ((con = calloc(1, sizeof(*con))) == NULL) {
2400dbc5f9eSreyk 		free(priv);
2412380f4f2Sreyk 		return;
2420dbc5f9eSreyk 	}
2432380f4f2Sreyk 
2445fce3f02Sreyk 	/*
2455fce3f02Sreyk 	 * Replace the DNS request Id with a random Id.
2465fce3f02Sreyk 	 */
2470dbc5f9eSreyk 	con->se_priv = priv;
248f8eb77d7Sthib 	con->se_in.s = -1;
249f8eb77d7Sthib 	con->se_out.s = -1;
250f8eb77d7Sthib 	con->se_in.dst = &con->se_out;
251f8eb77d7Sthib 	con->se_out.dst = &con->se_in;
252f8eb77d7Sthib 	con->se_in.con = con;
253f8eb77d7Sthib 	con->se_out.con = con;
254f8eb77d7Sthib 	con->se_relay = rlay;
255f8eb77d7Sthib 	con->se_id = ++relay_conid;
256f8eb77d7Sthib 	con->se_in.dir = RELAY_DIR_REQUEST;
257f8eb77d7Sthib 	con->se_out.dir = RELAY_DIR_RESPONSE;
258f8eb77d7Sthib 	con->se_retry = rlay->rl_conf.dstretry;
259f8eb77d7Sthib 	con->se_out.port = rlay->rl_conf.dstport;
2602380f4f2Sreyk 	switch (ss.ss_family) {
2612380f4f2Sreyk 	case AF_INET:
262f8eb77d7Sthib 		con->se_in.port = ((struct sockaddr_in *)&ss)->sin_port;
2632380f4f2Sreyk 		break;
2642380f4f2Sreyk 	case AF_INET6:
265f8eb77d7Sthib 		con->se_in.port = ((struct sockaddr_in6 *)&ss)->sin6_port;
2662380f4f2Sreyk 		break;
2672380f4f2Sreyk 	}
268fd1841a3Sreyk 	bcopy(&ss, &con->se_in.ss, sizeof(con->se_in.ss));
269fd1841a3Sreyk 
270fd1841a3Sreyk 	getmonotime(&con->se_tv_start);
271fd1841a3Sreyk 	bcopy(&con->se_tv_start, &con->se_tv_last, sizeof(con->se_tv_last));
2722380f4f2Sreyk 
2732380f4f2Sreyk 	relay_sessions++;
2744a5b9b3eSreyk 	SPLAY_INSERT(session_tree, &rlay->rl_sessions, con);
2756e07057bSblambert 	relay_session_publish(con);
2762380f4f2Sreyk 
2772380f4f2Sreyk 	/* Increment the per-relay session counter */
278325f6e14Sreyk 	rlay->rl_stats[ps->ps_instance].last++;
2792380f4f2Sreyk 
2802380f4f2Sreyk 	/* Pre-allocate output buffer */
281f8eb77d7Sthib 	con->se_out.output = evbuffer_new();
282f8eb77d7Sthib 	if (con->se_out.output == NULL) {
2830be9d00aSbenno 		relay_close(con, "failed to allocate output buffer", 1);
2842380f4f2Sreyk 		return;
2852380f4f2Sreyk 	}
2862380f4f2Sreyk 
2872380f4f2Sreyk 	/* Pre-allocate log buffer */
288c84d6099Sbenno 	con->se_haslog = 0;
289f8eb77d7Sthib 	con->se_log = evbuffer_new();
290f8eb77d7Sthib 	if (con->se_log == NULL) {
2910be9d00aSbenno 		relay_close(con, "failed to allocate log buffer", 1);
2922380f4f2Sreyk 		return;
2932380f4f2Sreyk 	}
2942380f4f2Sreyk 
2954a5b9b3eSreyk 	if (rlay->rl_conf.flags & F_NATLOOK) {
29648240b8fSbluhm 		if ((cnl = calloc(1, sizeof(*cnl))) == NULL) {
2970be9d00aSbenno 			relay_close(con, "failed to allocate natlookup", 1);
2982380f4f2Sreyk 			return;
2992380f4f2Sreyk 		}
3002380f4f2Sreyk 	}
3012380f4f2Sreyk 
3022380f4f2Sreyk 	/* Save the received data */
303f8eb77d7Sthib 	if (evbuffer_add(con->se_out.output, buf, len) == -1) {
3040be9d00aSbenno 		relay_close(con, "failed to store buffer", 1);
305e24b2b8eSreyk 		free(cnl);
3062380f4f2Sreyk 		return;
3072380f4f2Sreyk 	}
3082380f4f2Sreyk 
3098d083e57Sreyk 	if (cnl != NULL) {
310f8eb77d7Sthib 		con->se_cnl = cnl;
3112380f4f2Sreyk 		bzero(cnl, sizeof(*cnl));
3122380f4f2Sreyk 		cnl->in = -1;
313f8eb77d7Sthib 		cnl->id = con->se_id;
314325f6e14Sreyk 		cnl->proc = ps->ps_instance;
315aeda6e0eSreyk 		cnl->proto = IPPROTO_UDP;
316f8eb77d7Sthib 		bcopy(&con->se_in.ss, &cnl->src, sizeof(cnl->src));
3174a5b9b3eSreyk 		bcopy(&rlay->rl_conf.ss, &cnl->dst, sizeof(cnl->dst));
318c2c37c5dSreyk 		proc_compose(env->sc_ps, PROC_PFE,
319c2c37c5dSreyk 		    IMSG_NATLOOK, cnl, sizeof(*cnl));
3202380f4f2Sreyk 
3212380f4f2Sreyk 		/* Schedule timeout */
322f8eb77d7Sthib 		evtimer_set(&con->se_ev, relay_natlook, con);
3234a5b9b3eSreyk 		bcopy(&rlay->rl_conf.timeout, &tv, sizeof(tv));
324f8eb77d7Sthib 		evtimer_add(&con->se_ev, &tv);
3252380f4f2Sreyk 		return;
3262380f4f2Sreyk 	}
3272380f4f2Sreyk 
3282380f4f2Sreyk 	relay_session(con);
3292380f4f2Sreyk }
3302380f4f2Sreyk 
3312380f4f2Sreyk void
relay_udp_timeout(int fd,short sig,void * arg)3322380f4f2Sreyk relay_udp_timeout(int fd, short sig, void *arg)
3332380f4f2Sreyk {
33448240b8fSbluhm 	struct rsession		*con = arg;
3352380f4f2Sreyk 
3362380f4f2Sreyk 	if (sig != EV_TIMEOUT)
3372380f4f2Sreyk 		fatalx("invalid timeout event");
3382380f4f2Sreyk 
3390be9d00aSbenno 	relay_close(con, "udp timeout", 1);
3402380f4f2Sreyk }
3412380f4f2Sreyk 
3422380f4f2Sreyk /*
3432380f4f2Sreyk  * Domain Name System support
3442380f4f2Sreyk  */
3452380f4f2Sreyk 
3460dbc5f9eSreyk struct relay_dns_priv {
3470dbc5f9eSreyk 	u_int16_t	dp_inkey;
3480dbc5f9eSreyk 	u_int16_t	dp_outkey;
3490dbc5f9eSreyk };
3500dbc5f9eSreyk 
3512380f4f2Sreyk struct relay_dnshdr {
3522380f4f2Sreyk 	u_int16_t	dns_id;
3532380f4f2Sreyk 
3542380f4f2Sreyk 	u_int8_t	dns_flags0;
3552380f4f2Sreyk #define  DNS_F0_QR	0x80		/* response flag */
3562380f4f2Sreyk #define  DNS_F0_OPCODE	0x78		/* message type */
3573a50f0a9Sjmc #define  DNS_F0_AA	0x04		/* authoritative answer */
3582380f4f2Sreyk #define  DNS_F0_TC	0x02		/* truncated message */
3592380f4f2Sreyk #define  DNS_F0_RD	0x01		/* recursion desired */
3602380f4f2Sreyk 
3612380f4f2Sreyk 	u_int8_t	dns_flags1;
3622380f4f2Sreyk #define  DNS_F1_RA	0x80		/* recursion available */
3632380f4f2Sreyk #define  DNS_F1_RES	0x40		/* reserved */
3642380f4f2Sreyk #define  DNS_F1_AD	0x20		/* authentic data */
3652380f4f2Sreyk #define  DNS_F1_CD	0x10		/* checking disabled */
3662380f4f2Sreyk #define  DNS_F1_RCODE	0x0f		/* response code */
3672380f4f2Sreyk 
3682380f4f2Sreyk 	u_int16_t	dns_qdcount;
3692380f4f2Sreyk 	u_int16_t	dns_ancount;
3702380f4f2Sreyk 	u_int16_t	dns_nscount;
3712380f4f2Sreyk 	u_int16_t	dns_arcount;
3722380f4f2Sreyk } __packed;
3732380f4f2Sreyk 
3742380f4f2Sreyk void
relay_dns_log(struct rsession * con,u_int8_t * buf,size_t len)375f4a6e73bSreyk relay_dns_log(struct rsession *con, u_int8_t *buf, size_t len)
3762380f4f2Sreyk {
3772380f4f2Sreyk 	struct relay_dnshdr	*hdr = (struct relay_dnshdr *)buf;
3782380f4f2Sreyk 
3790ab1bea4Sreyk 	/* Validate the header length */
3800ab1bea4Sreyk 	if (len < sizeof(*hdr)) {
38185a8c65fSreyk 		log_debug("%s: session %d: short dns packet", __func__,
3820ab1bea4Sreyk 		    con->se_id);
3830ab1bea4Sreyk 		return;
3840ab1bea4Sreyk 	}
3850ab1bea4Sreyk 
38685a8c65fSreyk 	log_debug("%s: session %d: %s id 0x%x "
38785a8c65fSreyk 	    "flags 0x%x:0x%x qd %u an %u ns %u ar %u", __func__,
388f8eb77d7Sthib 	    con->se_id,
3892380f4f2Sreyk 	    hdr->dns_flags0 & DNS_F0_QR ? "response" : "request",
3902380f4f2Sreyk 	    ntohs(hdr->dns_id),
3912380f4f2Sreyk 	    hdr->dns_flags0,
3922380f4f2Sreyk 	    hdr->dns_flags1,
3932380f4f2Sreyk 	    ntohs(hdr->dns_qdcount),
3942380f4f2Sreyk 	    ntohs(hdr->dns_ancount),
3952380f4f2Sreyk 	    ntohs(hdr->dns_nscount),
3962380f4f2Sreyk 	    ntohs(hdr->dns_arcount));
3972380f4f2Sreyk }
3982380f4f2Sreyk 
3990dbc5f9eSreyk void *
relay_dns_validate(struct rsession * con,struct relay * rlay,struct sockaddr_storage * ss,u_int8_t * buf,size_t len)400f4a6e73bSreyk relay_dns_validate(struct rsession *con, struct relay *rlay,
401bec706feSreyk     struct sockaddr_storage *ss, u_int8_t *buf, size_t len)
4022380f4f2Sreyk {
4032380f4f2Sreyk 	struct relay_dnshdr	*hdr = (struct relay_dnshdr *)buf;
404f4a6e73bSreyk 	struct rsession		 lookup;
4050dbc5f9eSreyk 	u_int16_t		 key;
4060dbc5f9eSreyk 	struct relay_dns_priv	*priv, lpriv;
4072380f4f2Sreyk 
4082380f4f2Sreyk 	/* Validate the header length */
4092380f4f2Sreyk 	if (len < sizeof(*hdr))
4100dbc5f9eSreyk 		return (NULL);
4112380f4f2Sreyk 
4120dbc5f9eSreyk 	key = ntohs(hdr->dns_id);
4132380f4f2Sreyk 
4142380f4f2Sreyk 	/*
4152380f4f2Sreyk 	 * Check if the header has the response flag set, otherwise
4162380f4f2Sreyk 	 * return 0 to tell the UDP server to create a new session.
4172380f4f2Sreyk 	 */
4180dbc5f9eSreyk 	if ((hdr->dns_flags0 & DNS_F0_QR) == 0) {
4190dbc5f9eSreyk 		priv = malloc(sizeof(struct relay_dns_priv));
4200dbc5f9eSreyk 		if (priv == NULL)
4210dbc5f9eSreyk 			return (NULL);
4228661b3ffSreyk 		priv->dp_inkey = shuffle_generate16(&relay_shuffle);
4230dbc5f9eSreyk 		priv->dp_outkey = key;
4240dbc5f9eSreyk 		return ((void *)priv);
4250dbc5f9eSreyk 	}
4262380f4f2Sreyk 
4272380f4f2Sreyk 	/*
4282380f4f2Sreyk 	 * Lookup if this response is for a known session and if the
4292380f4f2Sreyk 	 * remote host matches the original destination of the request.
4302380f4f2Sreyk 	 */
431bec706feSreyk 	if (con == NULL) {
4320dbc5f9eSreyk 		lpriv.dp_inkey = key;
4330dbc5f9eSreyk 		lookup.se_priv = &lpriv;
4342380f4f2Sreyk 		if ((con = SPLAY_FIND(session_tree,
435bec706feSreyk 		    &rlay->rl_sessions, &lookup)) != NULL &&
436bec706feSreyk 		    con->se_priv != NULL &&
437f8eb77d7Sthib 		    relay_cmp_af(ss, &con->se_out.ss) == 0)
438bec706feSreyk 			relay_dns_result(con, buf, len);
439ba83ed70Sreyk 	} else {
44048240b8fSbluhm 		priv = con->se_priv;
441ba83ed70Sreyk 		if (priv == NULL || key != priv->dp_inkey) {
4420be9d00aSbenno 			relay_close(con, "invalid response", 1);
443ba83ed70Sreyk 			return (NULL);
444ba83ed70Sreyk 		}
445bec706feSreyk 		relay_dns_result(con, buf, len);
446ba83ed70Sreyk 	}
4472380f4f2Sreyk 
4482380f4f2Sreyk 	/*
4492380f4f2Sreyk 	 * This is not a new session, ignore it in the UDP server.
4502380f4f2Sreyk 	 */
4510dbc5f9eSreyk 	return (NULL);
4522380f4f2Sreyk }
4532380f4f2Sreyk 
4542380f4f2Sreyk int
relay_dns_request(struct rsession * con)455f4a6e73bSreyk relay_dns_request(struct rsession *con)
4562380f4f2Sreyk {
45748240b8fSbluhm 	struct relay		*rlay = con->se_relay;
45848240b8fSbluhm 	struct relay_dns_priv	*priv = con->se_priv;
459f8eb77d7Sthib 	u_int8_t		*buf = EVBUFFER_DATA(con->se_out.output);
460f8eb77d7Sthib 	size_t			 len = EVBUFFER_LENGTH(con->se_out.output);
4612380f4f2Sreyk 	struct relay_dnshdr	*hdr;
4622380f4f2Sreyk 	socklen_t		 slen;
4632380f4f2Sreyk 
4640dbc5f9eSreyk 	if (buf == NULL || priv == NULL || len < 1)
4652380f4f2Sreyk 		return (-1);
466871fc12cSreyk 	if (log_getverbose() > 1)
4670ab1bea4Sreyk 		relay_dns_log(con, buf, len);
4682380f4f2Sreyk 
469fd1841a3Sreyk 	getmonotime(&con->se_tv_start);
4702380f4f2Sreyk 
471416fa9c0Sreyk 	if (!TAILQ_EMPTY(&rlay->rl_tables)) {
4722380f4f2Sreyk 		if (relay_from_table(con) != 0)
4732380f4f2Sreyk 			return (-1);
474f8eb77d7Sthib 	} else if (con->se_out.ss.ss_family == AF_UNSPEC) {
47538426f8cSreyk 		bcopy(&rlay->rl_conf.dstss, &con->se_out.ss,
47638426f8cSreyk 		    sizeof(con->se_out.ss));
477f8eb77d7Sthib 		con->se_out.port = rlay->rl_conf.dstport;
4782380f4f2Sreyk 	}
4792380f4f2Sreyk 
480bec706feSreyk 	if ((con->se_out.s = relay_udp_socket(&con->se_out.ss,
481bec706feSreyk 	    con->se_out.port, rlay->rl_proto)) == -1)
4822380f4f2Sreyk 		return (-1);
483f8eb77d7Sthib 	slen = con->se_out.ss.ss_len;
4842380f4f2Sreyk 
4852380f4f2Sreyk 	hdr = (struct relay_dnshdr *)buf;
4860dbc5f9eSreyk 	hdr->dns_id = htons(priv->dp_inkey);
4872380f4f2Sreyk 
4882380f4f2Sreyk  retry:
489bec706feSreyk 	if (sendto(con->se_out.s, buf, len, 0,
490f8eb77d7Sthib 	    (struct sockaddr *)&con->se_out.ss, slen) == -1) {
491f8eb77d7Sthib 		if (con->se_retry) {
492f8eb77d7Sthib 			con->se_retry--;
49385a8c65fSreyk 			log_debug("%s: session %d: "
49485a8c65fSreyk 			    "forward failed: %s, %s", __func__,
495f8eb77d7Sthib 			    con->se_id, strerror(errno),
496f8eb77d7Sthib 			    con->se_retry ? "next retry" : "last retry");
4972380f4f2Sreyk 			goto retry;
4982380f4f2Sreyk 		}
49985a8c65fSreyk 		log_debug("%s: session %d: forward failed: %s", __func__,
500f8eb77d7Sthib 		    con->se_id, strerror(errno));
5012380f4f2Sreyk 		return (-1);
5022380f4f2Sreyk 	}
5032380f4f2Sreyk 
504bec706feSreyk 	event_again(&con->se_ev, con->se_out.s, EV_TIMEOUT|EV_READ,
505b3ad2344Sreyk 	    relay_udp_response, &con->se_tv_start, &rlay->rl_conf.timeout, con);
5062380f4f2Sreyk 
5072380f4f2Sreyk 	return (0);
5082380f4f2Sreyk }
5092380f4f2Sreyk 
5102380f4f2Sreyk void
relay_dns_result(struct rsession * con,u_int8_t * buf,size_t len)511f4a6e73bSreyk relay_dns_result(struct rsession *con, u_int8_t *buf, size_t len)
5122380f4f2Sreyk {
51348240b8fSbluhm 	struct relay		*rlay = con->se_relay;
51448240b8fSbluhm 	struct relay_dns_priv	*priv = con->se_priv;
5152380f4f2Sreyk 	struct relay_dnshdr	*hdr;
5162380f4f2Sreyk 	socklen_t		 slen;
5172380f4f2Sreyk 
5180dbc5f9eSreyk 	if (priv == NULL)
519efc39811Sbenno 		fatalx("%s: response to invalid session", __func__);
5200dbc5f9eSreyk 
521871fc12cSreyk 	if (log_getverbose() > 1)
5220ab1bea4Sreyk 		relay_dns_log(con, buf, len);
5232380f4f2Sreyk 
5242380f4f2Sreyk 	/*
5252380f4f2Sreyk 	 * Replace the random DNS request Id with the original Id
5262380f4f2Sreyk 	 */
5272380f4f2Sreyk 	hdr = (struct relay_dnshdr *)buf;
5280dbc5f9eSreyk 	hdr->dns_id = htons(priv->dp_outkey);
5292380f4f2Sreyk 
530f8eb77d7Sthib 	slen = con->se_out.ss.ss_len;
5314a5b9b3eSreyk 	if (sendto(rlay->rl_s, buf, len, 0,
532f8eb77d7Sthib 	    (struct sockaddr *)&con->se_in.ss, slen) == -1) {
5330be9d00aSbenno 		relay_close(con, "response failed", 1);
5342380f4f2Sreyk 		return;
5352380f4f2Sreyk 	}
5362380f4f2Sreyk 
5370be9d00aSbenno 	relay_close(con, "session closed", 0);
5382380f4f2Sreyk }
5392380f4f2Sreyk 
5402380f4f2Sreyk int
relay_dns_cmp(struct rsession * a,struct rsession * b)541f4a6e73bSreyk relay_dns_cmp(struct rsession *a, struct rsession *b)
5422380f4f2Sreyk {
54348240b8fSbluhm 	struct relay_dns_priv	*ap = a->se_priv;
54448240b8fSbluhm 	struct relay_dns_priv	*bp = b->se_priv;
5450dbc5f9eSreyk 
5460dbc5f9eSreyk 	if (ap == NULL || bp == NULL)
547efc39811Sbenno 		fatalx("%s: invalid session", __func__);
5480dbc5f9eSreyk 
5490dbc5f9eSreyk 	return (memcmp(&ap->dp_inkey, &bp->dp_inkey, sizeof(u_int16_t)));
5502380f4f2Sreyk }
551