support tcp protocol

This commit is contained in:
Yecheng Fu 2012-11-22 17:40:04 +08:00
parent 723a512a2d
commit 1e4c13352e
11 changed files with 515 additions and 15 deletions

View File

@ -20,6 +20,7 @@ LIB_H = dnscrypt.h udp_request.h edns.h logger.h dnscrypt-proxy/src/libevent/inc
LIB_OBJS += dnscrypt.o
LIB_OBJS += udp_request.o
LIB_OBJS += tcp_request.o
LIB_OBJS += edns.o
LIB_OBJS += logger.o
LIB_OBJS += main.o

View File

@ -11,8 +11,6 @@ This is dnscrypt wrapper (server-side dnscrypt proxy), which helps to add dnscry
This software is modified from
[dnscrypt-proxy](https://github.com/opendns/dnscrypt-proxy).
Only udp protocol is supported now, tcp is work in progress.
INSTALLATION
============

2
TODO
View File

@ -1,2 +1,2 @@
* support tcp protocol
* reconnect to resolver server?
* test framework?

View File

@ -37,6 +37,8 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/queue.h>
#include <netinet/tcp.h>
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[1]))
#define COMPILER_ASSERT(X) (void) sizeof(char[(X) ? 1 : -1])

View File

@ -2,8 +2,10 @@
#define DNSCRYPT_H
#include "compat.h"
#include <sys/queue.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <crypto_box.h>
#include <crypto_stream.h>
@ -72,6 +74,7 @@
#include "edns.h"
#include "udp_request.h"
#include "tcp_request.h"
#include "rfc1035.h"
#include "logger.h"
#include "salsa20_random.h"
@ -80,6 +83,8 @@
#define DNSCRYPT_QUERY_HEADER_SIZE \
(DNSCRYPT_MAGIC_HEADER_LEN + crypto_box_PUBLICKEYBYTES + crypto_box_HALF_NONCEBYTES + crypto_box_MACBYTES)
#define DNSCRYPT_RESPONSE_HEADER_SIZE \
(DNSCRYPT_MAGIC_HEADER_LEN + crypto_box_NONCEBYTES + crypto_box_MACBYTES)
#define DNSCRYPT_REPLY_HEADER_SIZE \
(DNSCRYPT_MAGIC_HEADER_LEN + crypto_box_HALF_NONCEBYTES * 2 + crypto_box_MACBYTES)
@ -91,6 +96,8 @@ struct context {
ev_socklen_t resolver_sockaddr_len;
const char *resolver_address;
const char *listen_address;
struct evconnlistener *tcp_conn_listener;
struct event *tcp_accept_timer;
struct event *udp_listener_event;
struct event *udp_resolver_event;
evutil_socket_t udp_listener_handle;
@ -107,7 +114,6 @@ struct context {
/* Process stuff. */
bool daemonize;
bool tcp_only;
char *user;
uid_t user_id;
gid_t user_group;

12
main.c
View File

@ -197,7 +197,6 @@ main(int argc, const char **argv)
OPT_STRING('r', "resolver-address", &c.resolver_address, "upstream dns resolver server (<address:port>)"),
OPT_STRING('u', "user", &c.user, "run as given user"),
OPT_BOOLEAN('d', "daemonize", &c.daemonize, "run as daemon (default: off)"),
/*OPT_BOOLEAN('t', "tcp-only", &c.tcp_only, "use tcp only (default: off)"),*/
OPT_BOOLEAN('V', "verbose", &verbose, "show verbose logs (specify more -VVV to increase verbosity)"),
OPT_STRING('l', "logfile", &c.logfile, "log file path (default: stdout)"),
OPT_BOOLEAN(0, "gen-provider-keypair", &gen_provider_keypair, "generate provider key pair"),
@ -367,11 +366,13 @@ main(int argc, const char **argv)
exit(1);
}
if (udp_listern_bind(&c) != 0) {
if (udp_listener_bind(&c) != 0 ||
tcp_listener_bind(&c) != 0) {
exit(1);
}
if (udp_listener_start(&c) != 0) {
if (udp_listener_start(&c) != 0 ||
tcp_listener_start(&c) != 0) {
logger(LOG_ERR, "Unable to start udp listener.");
exit(1);
}
@ -380,5 +381,10 @@ main(int argc, const char **argv)
event_base_dispatch(c.event_loop);
logger(LOG_INFO, "Stopping proxy");
udp_listener_stop(&c);
tcp_listener_stop(&c);
event_base_free(c.event_loop);
return 0;
}

441
tcp_request.c Normal file
View File

@ -0,0 +1,441 @@
#include "dnscrypt.h"
static void
tcp_request_kill(TCPRequest * const tcp_request)
{
if (tcp_request == NULL || tcp_request->status.is_dying) {
return;
}
tcp_request->status.is_dying = 1;
struct context *c;
if (tcp_request->timeout_timer != NULL) {
event_free(tcp_request->timeout_timer);
tcp_request->timeout_timer = NULL;
}
if (tcp_request->client_proxy_bev != NULL) {
bufferevent_free(tcp_request->client_proxy_bev);
tcp_request->client_proxy_bev = NULL;
}
if (tcp_request->proxy_resolver_bev != NULL) {
bufferevent_free(tcp_request->proxy_resolver_bev);
tcp_request->proxy_resolver_bev = NULL;
}
if (tcp_request->proxy_resolver_query_evbuf != NULL) {
evbuffer_free(tcp_request->proxy_resolver_query_evbuf);
tcp_request->proxy_resolver_query_evbuf = NULL;
}
c = tcp_request->context;
if (tcp_request->status.is_in_queue != 0) {
assert(! TAILQ_EMPTY(&c->tcp_request_queue));
TAILQ_REMOVE(&c->tcp_request_queue, tcp_request, queue);
assert(c->connections > 0U);
c->connections--;
}
tcp_request->context = NULL;
free(tcp_request);
}
static void
tcp_tune(evutil_socket_t handle)
{
if (handle == -1) {
return;
}
setsockopt(handle, IPPROTO_TCP, TCP_NODELAY,
(void *) (int []) { 1 }, sizeof (int));
}
static void
timeout_timer_cb(evutil_socket_t timeout_timer_handle, short ev_flags,
void * const tcp_request_)
{
TCPRequest * const tcp_request = tcp_request_;
(void) ev_flags;
(void) timeout_timer_handle;
logger(LOG_WARNING, "resolver timeout (TCP)");
tcp_request_kill(tcp_request);
}
int
tcp_listener_kill_oldest_request(struct context *c)
{
if (TAILQ_EMPTY(&c->tcp_request_queue)) {
return -1;
}
tcp_request_kill(TAILQ_FIRST(&c->tcp_request_queue));
return 0;
}
static void
client_proxy_read_cb(struct bufferevent * const client_proxy_bev,
void * const tcp_request_)
{
uint8_t dns_query[DNS_MAX_PACKET_SIZE_TCP - 2U];
uint8_t dns_query_len_buf[2];
uint8_t dns_curved_query_len_buf[2];
TCPRequest *tcp_request = tcp_request_;
struct context *c = tcp_request->context;
struct evbuffer *input = bufferevent_get_input(client_proxy_bev);
size_t available_size;
size_t dns_query_len;
size_t max_query_size;
if (tcp_request->status.has_dns_query_len == 0) {
assert(evbuffer_get_length(input) >= (size_t) 2U);
evbuffer_remove(input, dns_query_len_buf, sizeof dns_query_len_buf);
tcp_request->dns_query_len = (size_t)
((dns_query_len_buf[0] << 8) | dns_query_len_buf[1]);
tcp_request->status.has_dns_query_len = 1;
}
assert(tcp_request->status.has_dns_query_len != 0);
dns_query_len = tcp_request->dns_query_len;
if (dns_query_len < (size_t) DNS_HEADER_SIZE) {
logger(LOG_WARNING, "Short query received");
tcp_request_kill(tcp_request);
return;
}
available_size = evbuffer_get_length(input);
if (available_size < dns_query_len) {
bufferevent_setwatermark(tcp_request->client_proxy_bev,
EV_READ, dns_query_len, dns_query_len);
return;
}
assert(available_size >= dns_query_len);
bufferevent_disable(tcp_request->client_proxy_bev, EV_READ);
assert(tcp_request->proxy_resolver_query_evbuf == NULL);
if ((tcp_request->proxy_resolver_query_evbuf = evbuffer_new()) == NULL) {
tcp_request_kill(tcp_request);
return;
}
if ((ssize_t)
evbuffer_remove_buffer(input, tcp_request->proxy_resolver_query_evbuf,
dns_query_len) != (ssize_t) dns_query_len) {
tcp_request_kill(tcp_request);
return;
}
assert(dns_query_len <= sizeof dns_query);
if ((ssize_t) evbuffer_remove(tcp_request->proxy_resolver_query_evbuf,
dns_query, dns_query_len)
!= (ssize_t) dns_query_len) {
tcp_request_kill(tcp_request);
return;
}
max_query_size = sizeof dns_query;
assert(max_query_size < DNS_MAX_PACKET_SIZE_TCP);
assert(SIZE_MAX - DNSCRYPT_MAX_PADDING - DNSCRYPT_QUERY_HEADER_SIZE
> dns_query_len);
size_t max_len = dns_query_len + DNSCRYPT_MAX_PADDING + DNSCRYPT_QUERY_HEADER_SIZE;
if (max_len > max_query_size) {
max_len = max_query_size;
}
if (dns_query_len + DNSCRYPT_QUERY_HEADER_SIZE > max_len) {
tcp_request_kill(tcp_request);
return;
}
assert(max_len <= DNS_MAX_PACKET_SIZE_TCP - 2U);
assert(max_len <= sizeof dns_query);
assert(dns_query_len <= max_len);
// decrypt if encrypted
struct dnscrypt_query_header *dnscrypt_header = (struct dnscrypt_query_header *)dns_query;
if (memcmp(dnscrypt_header->magic_query, CERT_MAGIC_HEADER, DNSCRYPT_MAGIC_HEADER_LEN) == 0) {
if (dnscrypt_server_uncurve(c, tcp_request->client_nonce, tcp_request->nmkey, dns_query, &dns_query_len) != 0) {
logger(LOG_WARNING, "Received a suspicious query from the client");
tcp_request_kill(tcp_request);
return;
}
tcp_request->is_dnscrypted = true;
} else {
tcp_request->is_dnscrypted = false;
}
dns_curved_query_len_buf[0] = (dns_query_len >> 8) & 0xff;
dns_curved_query_len_buf[1] = dns_query_len & 0xff;
if (bufferevent_write(tcp_request->proxy_resolver_bev,
dns_curved_query_len_buf, (size_t) 2U) != 0 ||
bufferevent_write(tcp_request->proxy_resolver_bev, dns_query,
(size_t) dns_query_len) != 0) {
tcp_request_kill(tcp_request);
return;
}
bufferevent_enable(tcp_request->proxy_resolver_bev, EV_READ);
}
static void
client_proxy_event_cb(struct bufferevent * const client_proxy_bev,
const short events, void * const tcp_request_)
{
TCPRequest * const tcp_request = tcp_request_;
(void) client_proxy_bev;
(void) events;
tcp_request_kill(tcp_request);
}
static void
client_proxy_write_cb(struct bufferevent * const client_proxy_bev,
void * const tcp_request_)
{
TCPRequest * const tcp_request = tcp_request_;
(void) client_proxy_bev;
tcp_request_kill(tcp_request);
}
static void
proxy_resolver_event_cb(struct bufferevent * const proxy_resolver_bev,
const short events, void * const tcp_request_)
{
TCPRequest * const tcp_request = tcp_request_;
(void) proxy_resolver_bev;
if ((events & BEV_EVENT_ERROR) != 0) {
tcp_request_kill(tcp_request);
return;
}
if ((events & BEV_EVENT_CONNECTED) == 0) {
tcp_tune(bufferevent_getfd(proxy_resolver_bev));
return;
}
}
static void
resolver_proxy_read_cb(struct bufferevent * const proxy_resolver_bev,
void * const tcp_request_)
{
uint8_t dns_reply_len_buf[2];
uint8_t dns_uncurved_reply_len_buf[2];
uint8_t *dns_reply;
TCPRequest *tcp_request = tcp_request_;
struct context *c = tcp_request->context;
struct evbuffer *input = bufferevent_get_input(proxy_resolver_bev);
size_t available_size;
size_t dns_reply_len;
logger(LOG_DEBUG, "Resolver read callback.");
if (tcp_request->status.has_dns_reply_len == 0) {
assert(evbuffer_get_length(input) >= (size_t) 2U);
evbuffer_remove(input, dns_reply_len_buf, sizeof dns_reply_len_buf);
tcp_request->dns_reply_len = (size_t)
((dns_reply_len_buf[0] << 8) | dns_reply_len_buf[1]);
tcp_request->status.has_dns_reply_len = 1;
}
assert(tcp_request->status.has_dns_reply_len != 0);
dns_reply_len = tcp_request->dns_reply_len;
if (dns_reply_len <
(size_t) DNS_HEADER_SIZE + DNSCRYPT_RESPONSE_HEADER_SIZE) {
logger(LOG_WARNING, "Short reply received");
tcp_request_kill(tcp_request);
return;
}
available_size = evbuffer_get_length(input);
if (available_size < dns_reply_len) {
bufferevent_setwatermark(tcp_request->proxy_resolver_bev,
EV_READ, dns_reply_len, dns_reply_len);
return;
}
assert(available_size >= dns_reply_len);
dns_reply = evbuffer_pullup(input, (ssize_t) dns_reply_len);
if (dns_reply == NULL) {
tcp_request_kill(tcp_request);
return;
}
size_t max_len = dns_reply_len + DNSCRYPT_MAX_PADDING + DNSCRYPT_REPLY_HEADER_SIZE;
if (tcp_request->is_dnscrypted) {
if (dnscrypt_server_curve(c, tcp_request->client_nonce, tcp_request->nmkey, dns_reply, &dns_reply_len, max_len) != 0) {
logger(LOG_ERR, "Curving reply failed.");
return;
}
}
dns_uncurved_reply_len_buf[0] = (dns_reply_len >> 8) & 0xff;
dns_uncurved_reply_len_buf[1] = dns_reply_len & 0xff;
if (bufferevent_write(tcp_request->client_proxy_bev,
dns_uncurved_reply_len_buf, (size_t) 2U) != 0 ||
bufferevent_write(tcp_request->client_proxy_bev, dns_reply,
dns_reply_len) != 0) {
tcp_request_kill(tcp_request);
return;
}
bufferevent_enable(tcp_request->client_proxy_bev, EV_WRITE);
bufferevent_free(tcp_request->proxy_resolver_bev);
tcp_request->proxy_resolver_bev = NULL;
}
static void
tcp_connection_cb(struct evconnlistener * const tcp_conn_listener,
evutil_socket_t handle,
struct sockaddr * const client_sockaddr,
const int client_sockaddr_len_int,
void * const context)
{
logger(LOG_DEBUG, "Accepted a connection.");
struct context *c = context;
TCPRequest *tcp_request;
(void) tcp_conn_listener;
(void) client_sockaddr;
(void) client_sockaddr_len_int;
if ((tcp_request = calloc((size_t) 1U, sizeof *tcp_request)) == NULL) {
return;
}
tcp_request->context = c;
tcp_request->timeout_timer = NULL;
tcp_request->proxy_resolver_query_evbuf = NULL;
tcp_request->client_proxy_bev = bufferevent_socket_new(c->event_loop,
handle, BEV_OPT_CLOSE_ON_FREE);
if (tcp_request->client_proxy_bev == NULL) {
evutil_closesocket(handle);
free(tcp_request);
return;
}
tcp_request->proxy_resolver_bev = bufferevent_socket_new(c->event_loop, -1,
BEV_OPT_CLOSE_ON_FREE);
if (tcp_request->proxy_resolver_bev == NULL) {
bufferevent_free(tcp_request->client_proxy_bev);
tcp_request->client_proxy_bev = NULL;
free(tcp_request);
return;
}
if (c->connections >=
c->connections_max) {
if (tcp_listener_kill_oldest_request(c) != 0) {
udp_listener_kill_oldest_request(c);
}
}
c->connections++;
assert(c->connections
<= c->connections_max);
TAILQ_INSERT_TAIL(&c->tcp_request_queue,
tcp_request, queue);
memset(&tcp_request->status, 0, sizeof tcp_request->status);
tcp_request->status.is_in_queue = 1;
if ((tcp_request->timeout_timer =
evtimer_new(tcp_request->context->event_loop,
timeout_timer_cb, tcp_request)) == NULL) {
tcp_request_kill(tcp_request);
return;
}
const struct timeval tv = {
.tv_sec = (time_t) DNS_QUERY_TIMEOUT, .tv_usec = 0
};
evtimer_add(tcp_request->timeout_timer, &tv);
bufferevent_setwatermark(tcp_request->client_proxy_bev,
EV_READ, (size_t) 2U,
(size_t) DNS_MAX_PACKET_SIZE_TCP);
bufferevent_setcb(tcp_request->client_proxy_bev,
client_proxy_read_cb, client_proxy_write_cb,
client_proxy_event_cb, tcp_request);
if (bufferevent_socket_connect
(tcp_request->proxy_resolver_bev,
(struct sockaddr *) &c->resolver_sockaddr,
(int) c->resolver_sockaddr_len) != 0) {
tcp_request_kill(tcp_request);
return;
}
bufferevent_setwatermark(tcp_request->proxy_resolver_bev,
EV_READ, (size_t) 2U,
(size_t) DNS_MAX_PACKET_SIZE_TCP);
bufferevent_setcb(tcp_request->proxy_resolver_bev,
resolver_proxy_read_cb, NULL, proxy_resolver_event_cb,
tcp_request);
bufferevent_enable(tcp_request->client_proxy_bev, EV_READ);
}
static void
tcp_accept_timer_cb(evutil_socket_t handle, const short event,
void * const context)
{
struct context *c = context;
(void) handle;
(void) event;
event_free(c->tcp_accept_timer);
c->tcp_accept_timer = NULL;
evconnlistener_enable(c->tcp_conn_listener);
}
static void
tcp_accept_error_cb(struct evconnlistener * const tcp_conn_listener,
void * const context)
{
struct context *c = context;
(void)tcp_conn_listener;
if (c->tcp_accept_timer == NULL) {
c->tcp_accept_timer = evtimer_new
(c->event_loop, tcp_accept_timer_cb, c);
if (c->tcp_accept_timer == NULL) {
return;
}
}
if (evtimer_pending(c->tcp_accept_timer, NULL)) {
return;
}
evconnlistener_disable(c->tcp_conn_listener);
const struct timeval tv = {
.tv_sec = (time_t)1,
.tv_usec = 0
};
evtimer_add(c->tcp_accept_timer, &tv);
}
int
tcp_listener_bind(struct context *c)
{
assert(c->tcp_conn_listener == NULL);
#ifndef LEV_OPT_DEFERRED_ACCEPT
# define LEV_OPT_DEFERRED_ACCEPT 0
#endif
c->tcp_conn_listener =
evconnlistener_new_bind(c->event_loop,
tcp_connection_cb, c,
LEV_OPT_CLOSE_ON_FREE |
LEV_OPT_CLOSE_ON_EXEC |
LEV_OPT_REUSEABLE |
LEV_OPT_DEFERRED_ACCEPT,
TCP_REQUEST_BACKLOG,
(struct sockaddr *)
&c->local_sockaddr,
(int) c->local_sockaddr_len);
if (c->tcp_conn_listener == NULL) {
logger(LOG_ERR, "Unable to bind (TCP)");
return -1;
}
if (evconnlistener_disable(c->tcp_conn_listener) != 0) {
evconnlistener_free(c->tcp_conn_listener);
c->tcp_conn_listener = NULL;
return -1;
}
evconnlistener_set_error_cb(c->tcp_conn_listener,
tcp_accept_error_cb);
TAILQ_INIT(&c->tcp_request_queue);
return 0;
}
int
tcp_listener_start(struct context *c)
{
assert(c->tcp_conn_listener != NULL);
if (evconnlistener_enable(c->tcp_conn_listener) != 0) {
return -1;
}
return 0;
}
void
tcp_listener_stop(struct context *c)
{
evconnlistener_free(c->tcp_conn_listener);
c->tcp_conn_listener = NULL;
while (tcp_listener_kill_oldest_request(c) != 0) { }
logger(LOG_INFO, "TCP listener shut down");
}

40
tcp_request.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef TCP_REQUEST_H
#define TCP_REQUEST_H
#include "dnscrypt.h"
#define DNS_MAX_PACKET_SIZE_TCP (65535U + 2U)
#ifndef TCP_REQUEST_BACKLOG
# define TCP_REQUEST_BACKLOG 128
#endif
struct context;
typedef struct TCPRequestStatus_ {
bool has_dns_query_len : 1;
bool has_dns_reply_len : 1;
bool is_in_queue : 1;
bool is_dying : 1;
} TCPRequestStatus;
typedef struct TCPRequest_ {
uint8_t client_nonce[crypto_box_HALF_NONCEBYTES];
uint8_t nmkey[crypto_box_BEFORENMBYTES];
TAILQ_ENTRY(TCPRequest_) queue;
struct bufferevent *client_proxy_bev;
struct bufferevent *proxy_resolver_bev;
struct evbuffer *proxy_resolver_query_evbuf;
struct context *context;
struct event *timeout_timer;
TCPRequestStatus status;
size_t dns_query_len;
size_t dns_reply_len;
bool is_dnscrypted;
} TCPRequest;
int tcp_listener_bind(struct context *c);
int tcp_listener_start(struct context *c);
void tcp_listener_stop(struct context *c);
#endif

9
test.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# directly to dnscrypt-wrapper
dig +short -p 54 twitter.com @127.0.0.1
dig +short -p 54 twitter.com @127.0.0.1 +tcp
# through dnscrypt-proxy
dig +short -p 55 twitter.com @127.0.0.1
dig +short -p 55 twitter.com @127.0.0.1 +tcp

View File

@ -85,7 +85,7 @@ udp_request_kill(UDPRequest * const udp_request)
free(udp_request);
}
static int
int
udp_listener_kill_oldest_request(struct context *c)
{
if (TAILQ_EMPTY(&c->udp_request_queue))
@ -333,10 +333,6 @@ client_to_proxy_cb(evutil_socket_t client_proxy_handle, short ev_flags,
dns_query_len = (size_t) nread;
assert(dns_query_len <= sizeof(dns_query));
if (udp_request->context->tcp_only != 0) {
proxy_client_send_truncated(udp_request, dns_query, dns_query_len);
return;
}
assert(SIZE_MAX - DNSCRYPT_MAX_PADDING - DNSCRYPT_QUERY_HEADER_SIZE >
dns_query_len);
@ -473,7 +469,7 @@ resolver_to_proxy_cb(evutil_socket_t proxy_resolver_handle, short ev_flags,
}
int
udp_listern_bind(struct context *c)
udp_listener_bind(struct context *c)
{
// listen socket & bind
assert(c->udp_listener_handle == -1);

View File

@ -30,8 +30,9 @@ typedef struct UDPRequest_ {
typedef TAILQ_HEAD(TCPRequestQueue_, TCPRequest_) TCPRequestQueue;
typedef TAILQ_HEAD(UDPRequestQueue_, UDPRequest_) UDPRequestQueue;
int udp_listern_bind(struct context *c);
int udp_listener_bind(struct context *c);
int udp_listener_start(struct context *c);
void udp_listener_stop(struct context *c);
int udp_listener_kill_oldest_request(struct context *c);
#endif