diff options
author | Reyk Floeter <reyk@cvs.openbsd.org> | 2007-09-10 11:59:23 +0000 |
---|---|---|
committer | Reyk Floeter <reyk@cvs.openbsd.org> | 2007-09-10 11:59:23 +0000 |
commit | b14b87d182b30166aaaee000506e869c4d1ebe8a (patch) | |
tree | 3b27c9614cace9547acfb62f3cdad0078951a01a | |
parent | b7f3134a110c4e84f093227c029085ffdc7c8bae (diff) |
add support for relaying DNS traffic (with a little bit of packet
header randomization). this adds an infrastructure to support
UDP-based protocols.
ok gilles@, tested by some
-rw-r--r-- | usr.sbin/hoststated/Makefile | 6 | ||||
-rw-r--r-- | usr.sbin/hoststated/hoststated.conf.5 | 22 | ||||
-rw-r--r-- | usr.sbin/hoststated/hoststated.h | 18 | ||||
-rw-r--r-- | usr.sbin/hoststated/parse.y | 4 | ||||
-rw-r--r-- | usr.sbin/hoststated/relay.c | 106 | ||||
-rw-r--r-- | usr.sbin/hoststated/relay_udp.c | 470 | ||||
-rw-r--r-- | usr.sbin/relayd/Makefile | 6 | ||||
-rw-r--r-- | usr.sbin/relayd/parse.y | 4 | ||||
-rw-r--r-- | usr.sbin/relayd/relay.c | 106 | ||||
-rw-r--r-- | usr.sbin/relayd/relay_udp.c | 470 | ||||
-rw-r--r-- | usr.sbin/relayd/relayd.conf.5 | 22 | ||||
-rw-r--r-- | usr.sbin/relayd/relayd.h | 18 |
12 files changed, 1208 insertions, 44 deletions
diff --git a/usr.sbin/hoststated/Makefile b/usr.sbin/hoststated/Makefile index 83c2467d45f..a2db29c1ffc 100644 --- a/usr.sbin/hoststated/Makefile +++ b/usr.sbin/hoststated/Makefile @@ -1,8 +1,8 @@ -# $OpenBSD: Makefile,v 1.12 2007/05/29 17:12:04 reyk Exp $ +# $OpenBSD: Makefile,v 1.13 2007/09/10 11:59:22 reyk Exp $ PROG= hoststated -SRCS= parse.y log.c control.c buffer.c imsg.c hoststated.c \ - ssl.c pfe.c pfe_filter.c hce.c relay.c carp.c \ +SRCS= parse.y log.c control.c buffer.c imsg.c hoststated.c \ + ssl.c pfe.c pfe_filter.c hce.c relay.c relay_udp.c carp.c \ check_icmp.c check_tcp.c check_script.c MAN= hoststated.8 hoststated.conf.5 diff --git a/usr.sbin/hoststated/hoststated.conf.5 b/usr.sbin/hoststated/hoststated.conf.5 index 78f2c5d8308..dfd3fb4ea57 100644 --- a/usr.sbin/hoststated/hoststated.conf.5 +++ b/usr.sbin/hoststated/hoststated.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: hoststated.conf.5,v 1.48 2007/09/05 09:15:10 reyk Exp $ +.\" $OpenBSD: hoststated.conf.5,v 1.49 2007/09/10 11:59:22 reyk Exp $ .\" .\" Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org> .\" Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@spootnik.org> @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: September 5 2007 $ +.Dd $Mdocdate: September 10 2007 $ .Dt HOSTSTATED.CONF 5 .Os .Sh NAME @@ -459,7 +459,7 @@ The protocol configuration directives are described below. .Bl -tag -width Ds .It Ic protocol Ar type Enable special handling of the specified application layer protocol. -The supported protocols are: +The supported TCP protocols are: .Pp .Bl -tag -width http -offset indent -compact .It Ic http @@ -468,6 +468,22 @@ Handle the Hypertext Transfer Protocol .It Ic tcp Generic handler for TCP-based protocols. .El +.Pp +.Xr hoststated 8 +also supports relaying of UDP protocols. +There is no generic handler for UDP-based protocols because it is a +stateless datagram-based protocol which requires to look into the +application layer protocol to find any possible state information. +The supported UDP protocols are: +.Pp +.Bl -tag -width http -offset indent -compact +.It Ic dns +Domain Name System (DNS) protocol. +The request IDs in the DNS header will be used to match the state. +.Xr hoststated 8 +will replace these IDs with random values to compensate for +predictable values generated by some hosts. +.El .It Xo .Op Ar direction .Op Ar type diff --git a/usr.sbin/hoststated/hoststated.h b/usr.sbin/hoststated/hoststated.h index 7853098d0c8..b7103cd6153 100644 --- a/usr.sbin/hoststated/hoststated.h +++ b/usr.sbin/hoststated/hoststated.h @@ -1,4 +1,4 @@ -/* $OpenBSD: hoststated.h,v 1.59 2007/09/07 08:20:24 reyk Exp $ */ +/* $OpenBSD: hoststated.h,v 1.60 2007/09/10 11:59:22 reyk Exp $ */ /* * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@spootnik.org> @@ -303,6 +303,7 @@ TAILQ_HEAD(addresslist, address); #define F_DEMOTE 0x00002000 #define F_LOOKUP_PATH 0x00004000 #define F_DEMOTED 0x00008000 +#define F_UDP 0x00010000 struct host_config { objid_t id; @@ -390,6 +391,7 @@ TAILQ_HEAD(servicelist, service); struct session { objid_t id; + u_int32_t key; objid_t relayid; struct ctl_relay_event in; struct ctl_relay_event out; @@ -452,7 +454,8 @@ RB_HEAD(proto_tree, protonode); enum prototype { RELAY_PROTO_TCP = 0, - RELAY_PROTO_HTTP = 1 + RELAY_PROTO_HTTP = 1, + RELAY_PROTO_DNS = 2 }; #define TCPFLAG_NODELAY 0x01 @@ -472,6 +475,7 @@ enum prototype { #define SSLCIPHERS_DEFAULT "HIGH:!ADH" +struct relay; struct protocol { objid_t id; u_int32_t flags; @@ -493,6 +497,10 @@ struct protocol { struct proto_tree response_tree; int (*cmp)(struct session *, struct session *); + int (*validate)(struct relay *, + struct sockaddr_storage *, + u_int8_t *, size_t, u_int32_t *); + int (*request)(struct session *); TAILQ_ENTRY(protocol) entry; }; @@ -702,6 +710,12 @@ int relay_session_cmp(struct session *, struct session *); RB_PROTOTYPE(proto_tree, protonode, nodes, relay_proto_cmp); SPLAY_PROTOTYPE(session_tree, session, nodes, relay_session_cmp); +/* relay_udp.c */ +void relay_udp_privinit(struct hoststated *, struct relay *); +int relay_udp_bind(struct sockaddr_storage *, in_port_t, + struct protocol *); +void relay_udp_server(int, short, void *); + /* check_icmp.c */ void icmp_init(struct hoststated *); void schedule_icmp(struct hoststated *, struct host *); diff --git a/usr.sbin/hoststated/parse.y b/usr.sbin/hoststated/parse.y index c0a0b1e47c5..53ea7fedd00 100644 --- a/usr.sbin/hoststated/parse.y +++ b/usr.sbin/hoststated/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.53 2007/09/07 07:54:58 reyk Exp $ */ +/* $OpenBSD: parse.y,v 1.54 2007/09/10 11:59:22 reyk Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> @@ -181,6 +181,8 @@ proto_type : TCP { $$ = RELAY_PROTO_TCP; } | STRING { if (strcmp("http", $1) == 0) { $$ = RELAY_PROTO_HTTP; + } else if (strcmp("dns", $1) == 0) { + $$ = RELAY_PROTO_DNS; } else { yyerror("invalid protocol type: %s", $1); free($1); diff --git a/usr.sbin/hoststated/relay.c b/usr.sbin/hoststated/relay.c index 6769cdfb705..ec6025cc3f9 100644 --- a/usr.sbin/hoststated/relay.c +++ b/usr.sbin/hoststated/relay.c @@ -1,4 +1,4 @@ -/* $OpenBSD: relay.c,v 1.42 2007/09/07 08:20:24 reyk Exp $ */ +/* $OpenBSD: relay.c,v 1.43 2007/09/10 11:59:22 reyk Exp $ */ /* * Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org> @@ -58,6 +58,7 @@ void relay_privinit(void); void relay_protodebug(struct relay *); void relay_init(void); void relay_launch(void); +int relay_socket_af(struct sockaddr_storage *, in_port_t); int relay_socket(struct sockaddr_storage *, in_port_t, struct protocol *); int relay_socket_listen(struct sockaddr_storage *, in_port_t, @@ -108,6 +109,8 @@ int relay_bufferevent_write_chunk(struct ctl_relay_event *, struct evbuffer *, size_t); int relay_bufferevent_write(struct ctl_relay_event *, void *, size_t); +int relay_cmp_af(struct sockaddr_storage *, + struct sockaddr_storage *); static __inline int relay_proto_cmp(struct protonode *, struct protonode *); extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t, @@ -287,6 +290,9 @@ relay_protodebug(struct relay *rlay) case RELAY_PROTO_HTTP: fprintf(stderr, "http\n"); break; + case RELAY_PROTO_DNS: + fprintf(stderr, "dns\n"); + break; } name = "request"; @@ -366,12 +372,27 @@ relay_privinit(void) if (debug) relay_protodebug(rlay); + switch (rlay->proto->type) { + case RELAY_PROTO_DNS: + relay_udp_privinit(env, rlay); + break; + case RELAY_PROTO_TCP: + case RELAY_PROTO_HTTP: + /* Use defaults */ + break; + } + if ((rlay->conf.flags & F_SSL) && (rlay->ctx = relay_ssl_ctx_create(rlay)) == NULL) fatal("relay_launch: failed to create SSL context"); - if ((rlay->s = relay_socket_listen(&rlay->conf.ss, - rlay->conf.port, rlay->proto)) == -1) + if (rlay->conf.flags & F_UDP) + rlay->s = relay_udp_bind(&rlay->conf.ss, + rlay->conf.port, rlay->proto); + else + rlay->s = relay_socket_listen(&rlay->conf.ss, + rlay->conf.port, rlay->proto); + if (rlay->s == -1) fatal("relay_launch: failed to listen"); } } @@ -486,25 +507,27 @@ void relay_launch(void) { struct relay *rlay; + void (*callback)(int, short, void *); TAILQ_FOREACH(rlay, &env->relays, entry) { log_debug("relay_launch: running relay %s", rlay->conf.name); rlay->up = HOST_UP; + if (rlay->conf.flags & F_UDP) + callback = relay_udp_server; + else + callback = relay_accept; + event_set(&rlay->ev, rlay->s, EV_READ|EV_PERSIST, - relay_accept, rlay); + callback, rlay); event_add(&rlay->ev, NULL); } } int -relay_socket(struct sockaddr_storage *ss, in_port_t port, - struct protocol *proto) +relay_socket_af(struct sockaddr_storage *ss, in_port_t port) { - int s = -1, val; - struct linger lng; - switch (ss->ss_family) { case AF_INET: ((struct sockaddr_in *)ss)->sin_port = port; @@ -516,8 +539,23 @@ relay_socket(struct sockaddr_storage *ss, in_port_t port, ((struct sockaddr_in6 *)ss)->sin6_len = sizeof(struct sockaddr_in6); break; + default: + return (-1); } + return (0); +} + +int +relay_socket(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s = -1, val; + struct linger lng; + + if (relay_socket_af(ss, port) == -1) + goto bad; + if ((s = socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) goto bad; @@ -1646,17 +1684,29 @@ relay_natlook(int fd, short event, void *arg) void relay_session(struct session *con) { - struct relay *rlay = (struct relay *)con->relay; + struct relay *rlay = (struct relay *)con->relay; + struct ctl_relay_event *in = &con->in, *out = &con->out; - if (bcmp(&rlay->conf.ss, &con->out.ss, sizeof(con->out.ss)) == 0 && - con->out.port == rlay->conf.port) { + if (bcmp(&rlay->conf.ss, &out->ss, sizeof(out->ss)) == 0 && + out->port == rlay->conf.port) { log_debug("relay_session: session %d: looping", con->id); relay_close(con, "session aborted"); return; } - if ((rlay->conf.flags & F_SSL) && (con->in.ssl == NULL)) { + if (rlay->conf.flags & F_UDP) { + /* + * Call the UDP protocol-specific handler + */ + if (rlay->proto->request == NULL) + fatalx("invalide UDP session"); + if ((*rlay->proto->request)(con) == -1) + relay_close(con, "session failed"); + return; + } + + if ((rlay->conf.flags & F_SSL) && (in->ssl == NULL)) { relay_ssl_transaction(con); return; } @@ -2356,6 +2406,36 @@ relay_bufferevent_write(struct ctl_relay_event *cre, void *data, size_t size) return (bufferevent_write(cre->bev, data, size)); } +int +relay_cmp_af(struct sockaddr_storage *a, struct sockaddr_storage *b) +{ + struct sockaddr_in ia, ib; + struct sockaddr_in6 ia6, ib6; + + switch (a->ss_family) { + case AF_INET: + bcopy(a, &ia, sizeof(struct sockaddr_in)); + bcopy(b, &ib, sizeof(struct sockaddr_in)); + + return (memcmp(&ia.sin_addr, &ib.sin_addr, + sizeof(ia.sin_addr)) + + memcmp(&ia.sin_port, &ib.sin_port, + sizeof(ia.sin_port))); + break; + case AF_INET6: + bcopy(a, &ia6, sizeof(struct sockaddr_in6)); + bcopy(b, &ib6, sizeof(struct sockaddr_in6)); + + return (memcmp(&ia6.sin6_addr, &ib6.sin6_addr, + sizeof(ia6.sin6_addr)) + + memcmp(&ia6.sin6_port, &ib6.sin6_port, + sizeof(ia6.sin6_port))); + break; + default: + return (-1); + } +} + static __inline int relay_proto_cmp(struct protonode *a, struct protonode *b) { diff --git a/usr.sbin/hoststated/relay_udp.c b/usr.sbin/hoststated/relay_udp.c new file mode 100644 index 00000000000..35f389694de --- /dev/null +++ b/usr.sbin/hoststated/relay_udp.c @@ -0,0 +1,470 @@ +/* $OpenBSD: relay_udp.c,v 1.1 2007/09/10 11:59:22 reyk Exp $ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/queue.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/tree.h> +#include <sys/hash.h> + +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <net/if.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include <err.h> +#include <pwd.h> +#include <event.h> +#include <fnmatch.h> + +#include <openssl/ssl.h> + +#include "hoststated.h" + +extern volatile sig_atomic_t relay_sessions; +extern objid_t relay_conid; +extern int proc_id; +extern struct imsgbuf *ibuf_pfe; +extern int debug; + +extern void relay_close(struct session *, const char *); +extern void relay_natlook(int, short, void *); +extern void relay_session(struct session *); +extern int relay_from_table(struct session *); +extern int relay_socket_af(struct sockaddr_storage *, in_port_t); +extern int relay_cmp_af(struct sockaddr_storage *, + struct sockaddr_storage *); + +struct hoststated *env = NULL; + +int relay_udp_socket(struct sockaddr_storage *, in_port_t, + struct protocol *); +void relay_udp_request(struct session *); +void relay_udp_timeout(int, short, void *); + +void relay_dns_log(struct session *, u_int8_t *); +int relay_dns_validate(struct relay *, struct sockaddr_storage *, + u_int8_t *, size_t, u_int32_t *); +int relay_dns_request(struct session *); +void relay_dns_response(struct session *, u_int8_t *, size_t); +int relay_dns_cmp(struct session *, struct session *); + +void +relay_udp_privinit(struct hoststated *x_env, struct relay *rlay) +{ + struct protocol *proto = rlay->proto; + + if (env == NULL) + env = x_env; + + if (rlay->conf.flags & F_SSL) + fatalx("ssl over udp is not supported"); + rlay->conf.flags |= F_UDP; + + switch (proto->type) { + case RELAY_PROTO_DNS: + proto->validate = relay_dns_validate; + proto->request = relay_dns_request; + proto->cmp = relay_dns_cmp; + break; + default: + fatalx("unsupported udp protocol"); + break; + } +} + +int +relay_udp_bind(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s; + + if ((s = relay_udp_socket(ss, port, proto)) == -1) + return (-1); + + if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1) + goto bad; + + return (s); + + bad: + close(s); + return (-1); +} + +int +relay_udp_socket(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s = -1, val; + + if (relay_socket_af(ss, port) == -1) + goto bad; + + if ((s = socket(ss->ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) + goto bad; + + /* + * Socket options + */ + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) + goto bad; + if (proto->tcpflags & TCPFLAG_BUFSIZ) { + val = proto->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, + &val, sizeof(val)) == -1) + goto bad; + val = proto->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, + &val, sizeof(val)) == -1) + goto bad; + } + + /* + * IP options + */ + if (proto->tcpflags & TCPFLAG_IPTTL) { + val = (int)proto->tcpipttl; + if (setsockopt(s, IPPROTO_IP, IP_TTL, + &val, sizeof(val)) == -1) + goto bad; + } + if (proto->tcpflags & TCPFLAG_IPMINTTL) { + val = (int)proto->tcpipminttl; + if (setsockopt(s, IPPROTO_IP, IP_MINTTL, + &val, sizeof(val)) == -1) + goto bad; + } + + return (s); + + bad: + if (s != -1) + close(s); + return (-1); +} + +void +relay_udp_server(int fd, short sig, void *arg) +{ + struct relay *rlay = (struct relay *)arg; + struct protocol *proto = rlay->proto; + struct session *con = NULL; + struct ctl_natlook *cnl = NULL; + socklen_t slen; + struct timeval tv; + struct sockaddr_storage ss; + u_int8_t buf[READ_BUF_SIZE]; + u_int32_t key = 0; + ssize_t len; + + if (relay_sessions >= RELAY_MAX_SESSIONS || + rlay->conf.flags & F_DISABLE) + return; + + slen = sizeof(ss); + if ((len = recvfrom(fd, buf, sizeof(buf), 0, + (struct sockaddr*)&ss, &slen)) < 1) + return; + + /* Parse and validate the packet header */ + if (proto->validate != NULL && + (*proto->validate)(rlay, &ss, buf, len, &key) != 0) + return; + + if ((con = (struct session *) + calloc(1, sizeof(struct session))) == NULL) + return; + + con->key = key; + con->in.s = -1; + con->out.s = -1; + con->in.dst = &con->out; + con->out.dst = &con->in; + con->in.con = con; + con->out.con = con; + con->relay = rlay; + con->id = ++relay_conid; + con->outkey = rlay->dstkey; + con->in.tree = &proto->request_tree; + con->out.tree = &proto->response_tree; + con->in.dir = RELAY_DIR_REQUEST; + con->out.dir = RELAY_DIR_RESPONSE; + con->retry = rlay->conf.dstretry; + gettimeofday(&con->tv_start, NULL); + bcopy(&con->tv_start, &con->tv_last, sizeof(con->tv_last)); + bcopy(&ss, &con->in.ss, sizeof(con->in.ss)); + con->out.port = rlay->conf.dstport; + switch (ss.ss_family) { + case AF_INET: + con->in.port = ((struct sockaddr_in *)&ss)->sin_port; + break; + case AF_INET6: + con->in.port = ((struct sockaddr_in6 *)&ss)->sin6_port; + break; + } + + relay_sessions++; + SPLAY_INSERT(session_tree, &rlay->sessions, con); + + /* Increment the per-relay session counter */ + rlay->stats[proc_id].last++; + + /* Pre-allocate output buffer */ + con->out.output = evbuffer_new(); + if (con->out.output == NULL) { + relay_close(con, "failed to allocate output buffer"); + return; + } + + /* Pre-allocate log buffer */ + con->log = evbuffer_new(); + if (con->log == NULL) { + relay_close(con, "failed to allocate log buffer"); + return; + } + + if (rlay->conf.flags & F_NATLOOK) { + if ((cnl = (struct ctl_natlook *) + calloc(1, sizeof(struct ctl_natlook))) == NULL) { + relay_close(con, "failed to allocate natlookup"); + return; + } + } + + /* Save the received data */ + if (evbuffer_add(con->out.output, buf, len) == -1) { + relay_close(con, "failed to store buffer"); + return; + } + + if (rlay->conf.flags & F_NATLOOK && cnl != NULL) { + con->cnl = cnl;; + bzero(cnl, sizeof(*cnl)); + cnl->in = -1; + cnl->id = con->id; + cnl->proc = proc_id; + bcopy(&con->in.ss, &cnl->src, sizeof(cnl->src)); + bcopy(&rlay->conf.ss, &cnl->dst, sizeof(cnl->dst)); + imsg_compose(ibuf_pfe, IMSG_NATLOOK, 0, 0, -1, cnl, + sizeof(*cnl)); + + /* Schedule timeout */ + evtimer_set(&con->ev, relay_natlook, con); + bcopy(&rlay->conf.timeout, &tv, sizeof(tv)); + evtimer_add(&con->ev, &tv); + return; + } + + relay_session(con); +} + +void +relay_udp_timeout(int fd, short sig, void *arg) +{ + struct session *con = (struct session *)arg; + + if (sig != EV_TIMEOUT) + fatalx("invalid timeout event"); + + relay_close(con, "udp timeout"); +} + +/* + * Domain Name System support + */ + +struct relay_dnshdr { + u_int16_t dns_id; + + u_int8_t dns_flags0; +#define DNS_F0_QR 0x80 /* response flag */ +#define DNS_F0_OPCODE 0x78 /* message type */ +#define DNS_F0_AA 0x04 /* authorative answer */ +#define DNS_F0_TC 0x02 /* truncated message */ +#define DNS_F0_RD 0x01 /* recursion desired */ + + u_int8_t dns_flags1; +#define DNS_F1_RA 0x80 /* recursion available */ +#define DNS_F1_RES 0x40 /* reserved */ +#define DNS_F1_AD 0x20 /* authentic data */ +#define DNS_F1_CD 0x10 /* checking disabled */ +#define DNS_F1_RCODE 0x0f /* response code */ + + u_int16_t dns_qdcount; + u_int16_t dns_ancount; + u_int16_t dns_nscount; + u_int16_t dns_arcount; +} __packed; + +void +relay_dns_log(struct session *con, u_int8_t *buf) +{ + struct relay_dnshdr *hdr = (struct relay_dnshdr *)buf; + + log_debug("relay_dns_log: session %d: %s id 0x%x " + "flags 0x%x:0x%x qd %u an %u ns %u ar %u", + con->id, + hdr->dns_flags0 & DNS_F0_QR ? "response" : "request", + ntohs(hdr->dns_id), + hdr->dns_flags0, + hdr->dns_flags1, + ntohs(hdr->dns_qdcount), + ntohs(hdr->dns_ancount), + ntohs(hdr->dns_nscount), + ntohs(hdr->dns_arcount)); +} + +int +relay_dns_validate(struct relay *rlay, struct sockaddr_storage *ss, + u_int8_t *buf, size_t len, u_int32_t *key) +{ + struct relay_dnshdr *hdr = (struct relay_dnshdr *)buf; + struct session *con, lookup; + + /* Validate the header length */ + if (len < sizeof(*hdr)) + return (-1); + + *key = ntohs(hdr->dns_id); + + /* + * Check if the header has the response flag set, otherwise + * return 0 to tell the UDP server to create a new session. + */ + if ((hdr->dns_flags0 & DNS_F0_QR) == 0) + return (0); + + /* + * Lookup if this response is for a known session and if the + * remote host matches the original destination of the request. + */ + lookup.key = *key; + if ((con = SPLAY_FIND(session_tree, + &rlay->sessions, &lookup)) != NULL && + relay_cmp_af(ss, &con->out.ss) == 0) + relay_dns_response(con, buf, len); + + /* + * This is not a new session, ignore it in the UDP server. + */ + return (-1); +} + +int +relay_dns_request(struct session *con) +{ + struct relay *rlay = (struct relay *)con->relay; + u_int8_t *buf = EVBUFFER_DATA(con->out.output); + size_t len = EVBUFFER_LENGTH(con->out.output); + struct relay_dnshdr *hdr; + socklen_t slen; + + if (buf == NULL || len < 1) + return (-1); + if (debug) + relay_dns_log(con, buf); + + if (gettimeofday(&con->tv_start, NULL)) + return (-1); + + if (rlay->dsttable != NULL) { + if (relay_from_table(con) != 0) + return (-1); + } else if (con->out.ss.ss_family == AF_UNSPEC) { + bcopy(&rlay->conf.dstss, &con->out.ss, sizeof(con->out.ss)); + con->out.port = rlay->conf.dstport; + } + + if (relay_socket_af(&con->out.ss, con->out.port) == -1) + return (-1); + slen = con->out.ss.ss_len; + + /* + * Replace the DNS request Id with a random Id. + */ + hdr = (struct relay_dnshdr *)buf; + con->outkey = con->key; + con->key = arc4random() & 0xffff; + hdr->dns_id = htons(con->key); + + retry: + if (sendto(rlay->s, buf, len, 0, + (struct sockaddr *)&con->out.ss, slen) == -1) { + if (con->retry) { + con->retry--; + log_debug("relay_dns_request: session %d: " + "forward failed: %s, %s", + con->id, strerror(errno), + con->retry ? "next retry" : "last retry"); + goto retry; + } + log_debug("relay_dns_request: session %d: forward failed: %s", + con->id, strerror(errno)); + return (-1); + } + + event_again(&con->ev, con->out.s, EV_TIMEOUT, + relay_udp_timeout, &con->tv_start, &env->timeout, con); + + return (0); +} + +void +relay_dns_response(struct session *con, u_int8_t *buf, size_t len) +{ + struct relay *rlay = (struct relay *)con->relay; + struct relay_dnshdr *hdr; + socklen_t slen; + + if (debug) + relay_dns_log(con, buf); + + /* + * Replace the random DNS request Id with the original Id + */ + hdr = (struct relay_dnshdr *)buf; + hdr->dns_id = htons(con->outkey); + + slen = con->out.ss.ss_len; + if (sendto(rlay->s, buf, len, 0, + (struct sockaddr *)&con->in.ss, slen) == -1) { + relay_close(con, "response failed"); + return; + } + + relay_close(con, "session closed"); +} + +int +relay_dns_cmp(struct session *a, struct session *b) +{ + return (memcmp(&a->key, &b->key, sizeof(a->key))); +} diff --git a/usr.sbin/relayd/Makefile b/usr.sbin/relayd/Makefile index 83c2467d45f..a2db29c1ffc 100644 --- a/usr.sbin/relayd/Makefile +++ b/usr.sbin/relayd/Makefile @@ -1,8 +1,8 @@ -# $OpenBSD: Makefile,v 1.12 2007/05/29 17:12:04 reyk Exp $ +# $OpenBSD: Makefile,v 1.13 2007/09/10 11:59:22 reyk Exp $ PROG= hoststated -SRCS= parse.y log.c control.c buffer.c imsg.c hoststated.c \ - ssl.c pfe.c pfe_filter.c hce.c relay.c carp.c \ +SRCS= parse.y log.c control.c buffer.c imsg.c hoststated.c \ + ssl.c pfe.c pfe_filter.c hce.c relay.c relay_udp.c carp.c \ check_icmp.c check_tcp.c check_script.c MAN= hoststated.8 hoststated.conf.5 diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y index c0a0b1e47c5..53ea7fedd00 100644 --- a/usr.sbin/relayd/parse.y +++ b/usr.sbin/relayd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.53 2007/09/07 07:54:58 reyk Exp $ */ +/* $OpenBSD: parse.y,v 1.54 2007/09/10 11:59:22 reyk Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org> @@ -181,6 +181,8 @@ proto_type : TCP { $$ = RELAY_PROTO_TCP; } | STRING { if (strcmp("http", $1) == 0) { $$ = RELAY_PROTO_HTTP; + } else if (strcmp("dns", $1) == 0) { + $$ = RELAY_PROTO_DNS; } else { yyerror("invalid protocol type: %s", $1); free($1); diff --git a/usr.sbin/relayd/relay.c b/usr.sbin/relayd/relay.c index 6769cdfb705..ec6025cc3f9 100644 --- a/usr.sbin/relayd/relay.c +++ b/usr.sbin/relayd/relay.c @@ -1,4 +1,4 @@ -/* $OpenBSD: relay.c,v 1.42 2007/09/07 08:20:24 reyk Exp $ */ +/* $OpenBSD: relay.c,v 1.43 2007/09/10 11:59:22 reyk Exp $ */ /* * Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org> @@ -58,6 +58,7 @@ void relay_privinit(void); void relay_protodebug(struct relay *); void relay_init(void); void relay_launch(void); +int relay_socket_af(struct sockaddr_storage *, in_port_t); int relay_socket(struct sockaddr_storage *, in_port_t, struct protocol *); int relay_socket_listen(struct sockaddr_storage *, in_port_t, @@ -108,6 +109,8 @@ int relay_bufferevent_write_chunk(struct ctl_relay_event *, struct evbuffer *, size_t); int relay_bufferevent_write(struct ctl_relay_event *, void *, size_t); +int relay_cmp_af(struct sockaddr_storage *, + struct sockaddr_storage *); static __inline int relay_proto_cmp(struct protonode *, struct protonode *); extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t, @@ -287,6 +290,9 @@ relay_protodebug(struct relay *rlay) case RELAY_PROTO_HTTP: fprintf(stderr, "http\n"); break; + case RELAY_PROTO_DNS: + fprintf(stderr, "dns\n"); + break; } name = "request"; @@ -366,12 +372,27 @@ relay_privinit(void) if (debug) relay_protodebug(rlay); + switch (rlay->proto->type) { + case RELAY_PROTO_DNS: + relay_udp_privinit(env, rlay); + break; + case RELAY_PROTO_TCP: + case RELAY_PROTO_HTTP: + /* Use defaults */ + break; + } + if ((rlay->conf.flags & F_SSL) && (rlay->ctx = relay_ssl_ctx_create(rlay)) == NULL) fatal("relay_launch: failed to create SSL context"); - if ((rlay->s = relay_socket_listen(&rlay->conf.ss, - rlay->conf.port, rlay->proto)) == -1) + if (rlay->conf.flags & F_UDP) + rlay->s = relay_udp_bind(&rlay->conf.ss, + rlay->conf.port, rlay->proto); + else + rlay->s = relay_socket_listen(&rlay->conf.ss, + rlay->conf.port, rlay->proto); + if (rlay->s == -1) fatal("relay_launch: failed to listen"); } } @@ -486,25 +507,27 @@ void relay_launch(void) { struct relay *rlay; + void (*callback)(int, short, void *); TAILQ_FOREACH(rlay, &env->relays, entry) { log_debug("relay_launch: running relay %s", rlay->conf.name); rlay->up = HOST_UP; + if (rlay->conf.flags & F_UDP) + callback = relay_udp_server; + else + callback = relay_accept; + event_set(&rlay->ev, rlay->s, EV_READ|EV_PERSIST, - relay_accept, rlay); + callback, rlay); event_add(&rlay->ev, NULL); } } int -relay_socket(struct sockaddr_storage *ss, in_port_t port, - struct protocol *proto) +relay_socket_af(struct sockaddr_storage *ss, in_port_t port) { - int s = -1, val; - struct linger lng; - switch (ss->ss_family) { case AF_INET: ((struct sockaddr_in *)ss)->sin_port = port; @@ -516,8 +539,23 @@ relay_socket(struct sockaddr_storage *ss, in_port_t port, ((struct sockaddr_in6 *)ss)->sin6_len = sizeof(struct sockaddr_in6); break; + default: + return (-1); } + return (0); +} + +int +relay_socket(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s = -1, val; + struct linger lng; + + if (relay_socket_af(ss, port) == -1) + goto bad; + if ((s = socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) goto bad; @@ -1646,17 +1684,29 @@ relay_natlook(int fd, short event, void *arg) void relay_session(struct session *con) { - struct relay *rlay = (struct relay *)con->relay; + struct relay *rlay = (struct relay *)con->relay; + struct ctl_relay_event *in = &con->in, *out = &con->out; - if (bcmp(&rlay->conf.ss, &con->out.ss, sizeof(con->out.ss)) == 0 && - con->out.port == rlay->conf.port) { + if (bcmp(&rlay->conf.ss, &out->ss, sizeof(out->ss)) == 0 && + out->port == rlay->conf.port) { log_debug("relay_session: session %d: looping", con->id); relay_close(con, "session aborted"); return; } - if ((rlay->conf.flags & F_SSL) && (con->in.ssl == NULL)) { + if (rlay->conf.flags & F_UDP) { + /* + * Call the UDP protocol-specific handler + */ + if (rlay->proto->request == NULL) + fatalx("invalide UDP session"); + if ((*rlay->proto->request)(con) == -1) + relay_close(con, "session failed"); + return; + } + + if ((rlay->conf.flags & F_SSL) && (in->ssl == NULL)) { relay_ssl_transaction(con); return; } @@ -2356,6 +2406,36 @@ relay_bufferevent_write(struct ctl_relay_event *cre, void *data, size_t size) return (bufferevent_write(cre->bev, data, size)); } +int +relay_cmp_af(struct sockaddr_storage *a, struct sockaddr_storage *b) +{ + struct sockaddr_in ia, ib; + struct sockaddr_in6 ia6, ib6; + + switch (a->ss_family) { + case AF_INET: + bcopy(a, &ia, sizeof(struct sockaddr_in)); + bcopy(b, &ib, sizeof(struct sockaddr_in)); + + return (memcmp(&ia.sin_addr, &ib.sin_addr, + sizeof(ia.sin_addr)) + + memcmp(&ia.sin_port, &ib.sin_port, + sizeof(ia.sin_port))); + break; + case AF_INET6: + bcopy(a, &ia6, sizeof(struct sockaddr_in6)); + bcopy(b, &ib6, sizeof(struct sockaddr_in6)); + + return (memcmp(&ia6.sin6_addr, &ib6.sin6_addr, + sizeof(ia6.sin6_addr)) + + memcmp(&ia6.sin6_port, &ib6.sin6_port, + sizeof(ia6.sin6_port))); + break; + default: + return (-1); + } +} + static __inline int relay_proto_cmp(struct protonode *a, struct protonode *b) { diff --git a/usr.sbin/relayd/relay_udp.c b/usr.sbin/relayd/relay_udp.c new file mode 100644 index 00000000000..35f389694de --- /dev/null +++ b/usr.sbin/relayd/relay_udp.c @@ -0,0 +1,470 @@ +/* $OpenBSD: relay_udp.c,v 1.1 2007/09/10 11:59:22 reyk Exp $ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/queue.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/tree.h> +#include <sys/hash.h> + +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <net/if.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include <err.h> +#include <pwd.h> +#include <event.h> +#include <fnmatch.h> + +#include <openssl/ssl.h> + +#include "hoststated.h" + +extern volatile sig_atomic_t relay_sessions; +extern objid_t relay_conid; +extern int proc_id; +extern struct imsgbuf *ibuf_pfe; +extern int debug; + +extern void relay_close(struct session *, const char *); +extern void relay_natlook(int, short, void *); +extern void relay_session(struct session *); +extern int relay_from_table(struct session *); +extern int relay_socket_af(struct sockaddr_storage *, in_port_t); +extern int relay_cmp_af(struct sockaddr_storage *, + struct sockaddr_storage *); + +struct hoststated *env = NULL; + +int relay_udp_socket(struct sockaddr_storage *, in_port_t, + struct protocol *); +void relay_udp_request(struct session *); +void relay_udp_timeout(int, short, void *); + +void relay_dns_log(struct session *, u_int8_t *); +int relay_dns_validate(struct relay *, struct sockaddr_storage *, + u_int8_t *, size_t, u_int32_t *); +int relay_dns_request(struct session *); +void relay_dns_response(struct session *, u_int8_t *, size_t); +int relay_dns_cmp(struct session *, struct session *); + +void +relay_udp_privinit(struct hoststated *x_env, struct relay *rlay) +{ + struct protocol *proto = rlay->proto; + + if (env == NULL) + env = x_env; + + if (rlay->conf.flags & F_SSL) + fatalx("ssl over udp is not supported"); + rlay->conf.flags |= F_UDP; + + switch (proto->type) { + case RELAY_PROTO_DNS: + proto->validate = relay_dns_validate; + proto->request = relay_dns_request; + proto->cmp = relay_dns_cmp; + break; + default: + fatalx("unsupported udp protocol"); + break; + } +} + +int +relay_udp_bind(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s; + + if ((s = relay_udp_socket(ss, port, proto)) == -1) + return (-1); + + if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1) + goto bad; + + return (s); + + bad: + close(s); + return (-1); +} + +int +relay_udp_socket(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s = -1, val; + + if (relay_socket_af(ss, port) == -1) + goto bad; + + if ((s = socket(ss->ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) + goto bad; + + /* + * Socket options + */ + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) + goto bad; + if (proto->tcpflags & TCPFLAG_BUFSIZ) { + val = proto->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, + &val, sizeof(val)) == -1) + goto bad; + val = proto->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, + &val, sizeof(val)) == -1) + goto bad; + } + + /* + * IP options + */ + if (proto->tcpflags & TCPFLAG_IPTTL) { + val = (int)proto->tcpipttl; + if (setsockopt(s, IPPROTO_IP, IP_TTL, + &val, sizeof(val)) == -1) + goto bad; + } + if (proto->tcpflags & TCPFLAG_IPMINTTL) { + val = (int)proto->tcpipminttl; + if (setsockopt(s, IPPROTO_IP, IP_MINTTL, + &val, sizeof(val)) == -1) + goto bad; + } + + return (s); + + bad: + if (s != -1) + close(s); + return (-1); +} + +void +relay_udp_server(int fd, short sig, void *arg) +{ + struct relay *rlay = (struct relay *)arg; + struct protocol *proto = rlay->proto; + struct session *con = NULL; + struct ctl_natlook *cnl = NULL; + socklen_t slen; + struct timeval tv; + struct sockaddr_storage ss; + u_int8_t buf[READ_BUF_SIZE]; + u_int32_t key = 0; + ssize_t len; + + if (relay_sessions >= RELAY_MAX_SESSIONS || + rlay->conf.flags & F_DISABLE) + return; + + slen = sizeof(ss); + if ((len = recvfrom(fd, buf, sizeof(buf), 0, + (struct sockaddr*)&ss, &slen)) < 1) + return; + + /* Parse and validate the packet header */ + if (proto->validate != NULL && + (*proto->validate)(rlay, &ss, buf, len, &key) != 0) + return; + + if ((con = (struct session *) + calloc(1, sizeof(struct session))) == NULL) + return; + + con->key = key; + con->in.s = -1; + con->out.s = -1; + con->in.dst = &con->out; + con->out.dst = &con->in; + con->in.con = con; + con->out.con = con; + con->relay = rlay; + con->id = ++relay_conid; + con->outkey = rlay->dstkey; + con->in.tree = &proto->request_tree; + con->out.tree = &proto->response_tree; + con->in.dir = RELAY_DIR_REQUEST; + con->out.dir = RELAY_DIR_RESPONSE; + con->retry = rlay->conf.dstretry; + gettimeofday(&con->tv_start, NULL); + bcopy(&con->tv_start, &con->tv_last, sizeof(con->tv_last)); + bcopy(&ss, &con->in.ss, sizeof(con->in.ss)); + con->out.port = rlay->conf.dstport; + switch (ss.ss_family) { + case AF_INET: + con->in.port = ((struct sockaddr_in *)&ss)->sin_port; + break; + case AF_INET6: + con->in.port = ((struct sockaddr_in6 *)&ss)->sin6_port; + break; + } + + relay_sessions++; + SPLAY_INSERT(session_tree, &rlay->sessions, con); + + /* Increment the per-relay session counter */ + rlay->stats[proc_id].last++; + + /* Pre-allocate output buffer */ + con->out.output = evbuffer_new(); + if (con->out.output == NULL) { + relay_close(con, "failed to allocate output buffer"); + return; + } + + /* Pre-allocate log buffer */ + con->log = evbuffer_new(); + if (con->log == NULL) { + relay_close(con, "failed to allocate log buffer"); + return; + } + + if (rlay->conf.flags & F_NATLOOK) { + if ((cnl = (struct ctl_natlook *) + calloc(1, sizeof(struct ctl_natlook))) == NULL) { + relay_close(con, "failed to allocate natlookup"); + return; + } + } + + /* Save the received data */ + if (evbuffer_add(con->out.output, buf, len) == -1) { + relay_close(con, "failed to store buffer"); + return; + } + + if (rlay->conf.flags & F_NATLOOK && cnl != NULL) { + con->cnl = cnl;; + bzero(cnl, sizeof(*cnl)); + cnl->in = -1; + cnl->id = con->id; + cnl->proc = proc_id; + bcopy(&con->in.ss, &cnl->src, sizeof(cnl->src)); + bcopy(&rlay->conf.ss, &cnl->dst, sizeof(cnl->dst)); + imsg_compose(ibuf_pfe, IMSG_NATLOOK, 0, 0, -1, cnl, + sizeof(*cnl)); + + /* Schedule timeout */ + evtimer_set(&con->ev, relay_natlook, con); + bcopy(&rlay->conf.timeout, &tv, sizeof(tv)); + evtimer_add(&con->ev, &tv); + return; + } + + relay_session(con); +} + +void +relay_udp_timeout(int fd, short sig, void *arg) +{ + struct session *con = (struct session *)arg; + + if (sig != EV_TIMEOUT) + fatalx("invalid timeout event"); + + relay_close(con, "udp timeout"); +} + +/* + * Domain Name System support + */ + +struct relay_dnshdr { + u_int16_t dns_id; + + u_int8_t dns_flags0; +#define DNS_F0_QR 0x80 /* response flag */ +#define DNS_F0_OPCODE 0x78 /* message type */ +#define DNS_F0_AA 0x04 /* authorative answer */ +#define DNS_F0_TC 0x02 /* truncated message */ +#define DNS_F0_RD 0x01 /* recursion desired */ + + u_int8_t dns_flags1; +#define DNS_F1_RA 0x80 /* recursion available */ +#define DNS_F1_RES 0x40 /* reserved */ +#define DNS_F1_AD 0x20 /* authentic data */ +#define DNS_F1_CD 0x10 /* checking disabled */ +#define DNS_F1_RCODE 0x0f /* response code */ + + u_int16_t dns_qdcount; + u_int16_t dns_ancount; + u_int16_t dns_nscount; + u_int16_t dns_arcount; +} __packed; + +void +relay_dns_log(struct session *con, u_int8_t *buf) +{ + struct relay_dnshdr *hdr = (struct relay_dnshdr *)buf; + + log_debug("relay_dns_log: session %d: %s id 0x%x " + "flags 0x%x:0x%x qd %u an %u ns %u ar %u", + con->id, + hdr->dns_flags0 & DNS_F0_QR ? "response" : "request", + ntohs(hdr->dns_id), + hdr->dns_flags0, + hdr->dns_flags1, + ntohs(hdr->dns_qdcount), + ntohs(hdr->dns_ancount), + ntohs(hdr->dns_nscount), + ntohs(hdr->dns_arcount)); +} + +int +relay_dns_validate(struct relay *rlay, struct sockaddr_storage *ss, + u_int8_t *buf, size_t len, u_int32_t *key) +{ + struct relay_dnshdr *hdr = (struct relay_dnshdr *)buf; + struct session *con, lookup; + + /* Validate the header length */ + if (len < sizeof(*hdr)) + return (-1); + + *key = ntohs(hdr->dns_id); + + /* + * Check if the header has the response flag set, otherwise + * return 0 to tell the UDP server to create a new session. + */ + if ((hdr->dns_flags0 & DNS_F0_QR) == 0) + return (0); + + /* + * Lookup if this response is for a known session and if the + * remote host matches the original destination of the request. + */ + lookup.key = *key; + if ((con = SPLAY_FIND(session_tree, + &rlay->sessions, &lookup)) != NULL && + relay_cmp_af(ss, &con->out.ss) == 0) + relay_dns_response(con, buf, len); + + /* + * This is not a new session, ignore it in the UDP server. + */ + return (-1); +} + +int +relay_dns_request(struct session *con) +{ + struct relay *rlay = (struct relay *)con->relay; + u_int8_t *buf = EVBUFFER_DATA(con->out.output); + size_t len = EVBUFFER_LENGTH(con->out.output); + struct relay_dnshdr *hdr; + socklen_t slen; + + if (buf == NULL || len < 1) + return (-1); + if (debug) + relay_dns_log(con, buf); + + if (gettimeofday(&con->tv_start, NULL)) + return (-1); + + if (rlay->dsttable != NULL) { + if (relay_from_table(con) != 0) + return (-1); + } else if (con->out.ss.ss_family == AF_UNSPEC) { + bcopy(&rlay->conf.dstss, &con->out.ss, sizeof(con->out.ss)); + con->out.port = rlay->conf.dstport; + } + + if (relay_socket_af(&con->out.ss, con->out.port) == -1) + return (-1); + slen = con->out.ss.ss_len; + + /* + * Replace the DNS request Id with a random Id. + */ + hdr = (struct relay_dnshdr *)buf; + con->outkey = con->key; + con->key = arc4random() & 0xffff; + hdr->dns_id = htons(con->key); + + retry: + if (sendto(rlay->s, buf, len, 0, + (struct sockaddr *)&con->out.ss, slen) == -1) { + if (con->retry) { + con->retry--; + log_debug("relay_dns_request: session %d: " + "forward failed: %s, %s", + con->id, strerror(errno), + con->retry ? "next retry" : "last retry"); + goto retry; + } + log_debug("relay_dns_request: session %d: forward failed: %s", + con->id, strerror(errno)); + return (-1); + } + + event_again(&con->ev, con->out.s, EV_TIMEOUT, + relay_udp_timeout, &con->tv_start, &env->timeout, con); + + return (0); +} + +void +relay_dns_response(struct session *con, u_int8_t *buf, size_t len) +{ + struct relay *rlay = (struct relay *)con->relay; + struct relay_dnshdr *hdr; + socklen_t slen; + + if (debug) + relay_dns_log(con, buf); + + /* + * Replace the random DNS request Id with the original Id + */ + hdr = (struct relay_dnshdr *)buf; + hdr->dns_id = htons(con->outkey); + + slen = con->out.ss.ss_len; + if (sendto(rlay->s, buf, len, 0, + (struct sockaddr *)&con->in.ss, slen) == -1) { + relay_close(con, "response failed"); + return; + } + + relay_close(con, "session closed"); +} + +int +relay_dns_cmp(struct session *a, struct session *b) +{ + return (memcmp(&a->key, &b->key, sizeof(a->key))); +} diff --git a/usr.sbin/relayd/relayd.conf.5 b/usr.sbin/relayd/relayd.conf.5 index 71f13ba1199..8bc3d443978 100644 --- a/usr.sbin/relayd/relayd.conf.5 +++ b/usr.sbin/relayd/relayd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: relayd.conf.5,v 1.48 2007/09/05 09:15:10 reyk Exp $ +.\" $OpenBSD: relayd.conf.5,v 1.49 2007/09/10 11:59:22 reyk Exp $ .\" .\" Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org> .\" Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@spootnik.org> @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: September 5 2007 $ +.Dd $Mdocdate: September 10 2007 $ .Dt HOSTSTATED.CONF 5 .Os .Sh NAME @@ -459,7 +459,7 @@ The protocol configuration directives are described below. .Bl -tag -width Ds .It Ic protocol Ar type Enable special handling of the specified application layer protocol. -The supported protocols are: +The supported TCP protocols are: .Pp .Bl -tag -width http -offset indent -compact .It Ic http @@ -468,6 +468,22 @@ Handle the Hypertext Transfer Protocol .It Ic tcp Generic handler for TCP-based protocols. .El +.Pp +.Xr hoststated 8 +also supports relaying of UDP protocols. +There is no generic handler for UDP-based protocols because it is a +stateless datagram-based protocol which requires to look into the +application layer protocol to find any possible state information. +The supported UDP protocols are: +.Pp +.Bl -tag -width http -offset indent -compact +.It Ic dns +Domain Name System (DNS) protocol. +The request IDs in the DNS header will be used to match the state. +.Xr hoststated 8 +will replace these IDs with random values to compensate for +predictable values generated by some hosts. +.El .It Xo .Op Ar direction .Op Ar type diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h index cd22d7bf4cd..6bfe46f7dd0 100644 --- a/usr.sbin/relayd/relayd.h +++ b/usr.sbin/relayd/relayd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: relayd.h,v 1.59 2007/09/07 08:20:24 reyk Exp $ */ +/* $OpenBSD: relayd.h,v 1.60 2007/09/10 11:59:22 reyk Exp $ */ /* * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@spootnik.org> @@ -303,6 +303,7 @@ TAILQ_HEAD(addresslist, address); #define F_DEMOTE 0x00002000 #define F_LOOKUP_PATH 0x00004000 #define F_DEMOTED 0x00008000 +#define F_UDP 0x00010000 struct host_config { objid_t id; @@ -390,6 +391,7 @@ TAILQ_HEAD(servicelist, service); struct session { objid_t id; + u_int32_t key; objid_t relayid; struct ctl_relay_event in; struct ctl_relay_event out; @@ -452,7 +454,8 @@ RB_HEAD(proto_tree, protonode); enum prototype { RELAY_PROTO_TCP = 0, - RELAY_PROTO_HTTP = 1 + RELAY_PROTO_HTTP = 1, + RELAY_PROTO_DNS = 2 }; #define TCPFLAG_NODELAY 0x01 @@ -472,6 +475,7 @@ enum prototype { #define SSLCIPHERS_DEFAULT "HIGH:!ADH" +struct relay; struct protocol { objid_t id; u_int32_t flags; @@ -493,6 +497,10 @@ struct protocol { struct proto_tree response_tree; int (*cmp)(struct session *, struct session *); + int (*validate)(struct relay *, + struct sockaddr_storage *, + u_int8_t *, size_t, u_int32_t *); + int (*request)(struct session *); TAILQ_ENTRY(protocol) entry; }; @@ -702,6 +710,12 @@ int relay_session_cmp(struct session *, struct session *); RB_PROTOTYPE(proto_tree, protonode, nodes, relay_proto_cmp); SPLAY_PROTOTYPE(session_tree, session, nodes, relay_session_cmp); +/* relay_udp.c */ +void relay_udp_privinit(struct hoststated *, struct relay *); +int relay_udp_bind(struct sockaddr_storage *, in_port_t, + struct protocol *); +void relay_udp_server(int, short, void *); + /* check_icmp.c */ void icmp_init(struct hoststated *); void schedule_icmp(struct hoststated *, struct host *); |