vBulletin Search Engine Optimization
| |||||||
| Register | FAQ | Members List | Calendar | Search | Today's Posts | Mark Forums Read |
| ||||
| hi, this code adds support for dns relaying/loadbalancing and a general udp interface for hoststated relay. there has been some discussion about dns loadbalancing on misc in the past. i know that it is probably better to use normal caches, servers, anycast and the dns "p2p" network, but i implemented dns support in hoststated to start with udp. because of the stateless nature of udp, it is not possible to write a generic udp handler, the daemon needs to look for state information in the application layer protocol itself which is very simple with dns. a fancy cookie is that it replaces the dns request id with random values (like the scrub random-id). it is still some kind of experimental... please test! reyk - the example configuration: ---snip--- # Increase the timeout to wait for DNS replies timeout 5000 table domain { real port domain # We do not support UDP checks, use ICMP instead check icmp # Some public DNS caches host 81.3.3.81 host 81.3.2.130 } protocol domain { # Use the UDP dns handler protocol dns # No protocol nodes yet... } relay domain { # Listen to "any" to receive the replies. Other DNS requests # on the external interface should be statefully blocked by pf. listen on 0.0.0.0 port domain # round-robin should be safe for DNS table domain roundrobin # Use the protocol as defined above protocol domain } ---snap--- - a test (don't run any named on localhost): $ nslookup www.openbsd.org 127.0.0.1 Server: 127.0.0.1 Address: 127.0.0.1#53 Non-authoritative answer: Name: www.openbsd.org Address: 129.128.5.191 - debug output from hoststated running in foreground (the outbound id is randomized): relay_dns_log: session 1: request id 0x24f4 flags 0x1:0x0 qd 1 an 0 ns 0 ar 0 relay_dns_log: session 1: response id 0x4dcb flags 0x81:0x80 qd 1 an 1 ns 5 ar 5 relay domain, session 1 (1 active), 127.0.0.1 -> 81.3.2.130:53, session closed - the diff: Index: Makefile ================================================== ================= RCS file: /cvs/src/usr.sbin/hoststated/Makefile,v retrieving revision 1.12 diff -u -p -r1.12 Makefile --- Makefile 29 May 2007 17:12:04 -0000 1.12 +++ Makefile 5 Sep 2007 10:30:10 -0000 @@ -1,8 +1,8 @@ # $OpenBSD: Makefile,v 1.12 2007/05/29 17:12:04 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 Index: hoststated.conf.5 ================================================== ================= RCS file: /cvs/src/usr.sbin/hoststated/hoststated.conf.5,v retrieving revision 1.48 diff -u -p -r1.48 hoststated.conf.5 --- hoststated.conf.5 5 Sep 2007 09:15:10 -0000 1.48 +++ hoststated.conf.5 5 Sep 2007 10:30:17 -0000 @@ -459,7 +459,7 @@ The protocol configuration directives ar .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 @@ -467,6 +467,22 @@ Handle the Hypertext Transfer Protocol (HTTP or "HTTPS" if encapsulated in a SSL tunnel). .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 Index: hoststated.h ================================================== ================= RCS file: /cvs/src/usr.sbin/hoststated/hoststated.h,v retrieving revision 1.55 diff -u -p -r1.55 hoststated.h --- hoststated.h 5 Sep 2007 08:48:42 -0000 1.55 +++ hoststated.h 5 Sep 2007 10:30:19 -0000 @@ -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; struct ctl_relay_event in; struct ctl_relay_event out; u_int32_t outkey; @@ -451,7 +453,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 @@ -471,6 +474,7 @@ enum prototype { #define SSLCIPHERS_DEFAULT "HIGH:!ADH" +struct relay; struct protocol { objid_t id; u_int32_t flags; @@ -492,6 +496,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; }; @@ -697,6 +705,12 @@ int relay_session_cmp(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 *); Index: parse.y ================================================== ================= RCS file: /cvs/src/usr.sbin/hoststated/parse.y,v retrieving revision 1.52 diff -u -p -r1.52 parse.y --- parse.y 5 Sep 2007 08:48:42 -0000 1.52 +++ parse.y 5 Sep 2007 10:30:27 -0000 @@ -181,6 +181,8 @@ proto_type : TCP { $$ = RELAY_PROTO_T | 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); Index: relay.c ================================================== ================= RCS file: /cvs/src/usr.sbin/hoststated/relay.c,v retrieving revision 1.40 diff -u -p -r1.40 relay.c --- relay.c 5 Sep 2007 10:25:13 -0000 1.40 +++ relay.c 5 Sep 2007 10:30:33 -0000 @@ -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, @@ -109,6 +110,8 @@ int relay_bufferevent_write_chunk(stru 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, @@ -288,6 +291,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"; @@ -367,12 +373,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"); } } @@ -487,25 +508,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; @@ -517,8 +540,23 @@ relay_socket(struct sockaddr_storage *ss ((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; @@ -1651,17 +1689,29 @@ relay_natlook(int fd, short event, void 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; } @@ -2350,6 +2400,36 @@ relay_bufferevent_write(struct ctl_relay if (cre->bev == NULL) return (evbuffer_add(cre->output, data, 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 Index: relay_udp.c ================================================== ================= RCS file: relay_udp.c diff -N relay_udp.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ relay_udp.c 5 Sep 2007 10:30:33 -0000 @@ -0,0 +1,461 @@ +/* $OpenBSD$ */ + +/* + * 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)); + + 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))); +} |