summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReyk Floeter <reyk@cvs.openbsd.org>2007-09-10 11:59:23 +0000
committerReyk Floeter <reyk@cvs.openbsd.org>2007-09-10 11:59:23 +0000
commitb14b87d182b30166aaaee000506e869c4d1ebe8a (patch)
tree3b27c9614cace9547acfb62f3cdad0078951a01a
parentb7f3134a110c4e84f093227c029085ffdc7c8bae (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/Makefile6
-rw-r--r--usr.sbin/hoststated/hoststated.conf.522
-rw-r--r--usr.sbin/hoststated/hoststated.h18
-rw-r--r--usr.sbin/hoststated/parse.y4
-rw-r--r--usr.sbin/hoststated/relay.c106
-rw-r--r--usr.sbin/hoststated/relay_udp.c470
-rw-r--r--usr.sbin/relayd/Makefile6
-rw-r--r--usr.sbin/relayd/parse.y4
-rw-r--r--usr.sbin/relayd/relay.c106
-rw-r--r--usr.sbin/relayd/relay_udp.c470
-rw-r--r--usr.sbin/relayd/relayd.conf.522
-rw-r--r--usr.sbin/relayd/relayd.h18
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 *);