#include "dnscrypt.h" #include "block.h" typedef struct SendtoWithRetryCtx_ { void (*cb) (UDPRequest *udp_request); const void *buffer; UDPRequest *udp_request; const struct sockaddr *dest_addr; evutil_socket_t handle; size_t length; ev_socklen_t dest_len; int flags; } SendtoWithRetryCtx; /* Forward declarations. */ static int sendto_with_retry(SendtoWithRetryCtx *const ctx); #ifndef UDP_BUFFER_SIZE # define UDP_BUFFER_SIZE 2097152 #endif #ifndef UDP_DELAY_BETWEEN_RETRIES # define UDP_DELAY_BETWEEN_RETRIES 1 #endif #ifndef SO_RCVBUFFORCE # define SO_RCVBUFFORCE SO_RCVBUF #endif #ifndef SO_SNDBUFFORCE # define SO_SNDBUFFORCE SO_SNDBUF #endif #ifndef EVUTIL_ERR_RW_RETRIABLE # ifndef _WIN32 # define EVUTIL_ERR_RW_RETRIABLE(e) ((e) == EINTR || (e) == EAGAIN) # else # define EVUTIL_ERR_RW_RETRIABLE(e) ((e) == WSAEWOULDBLOCK || (e) == WSAEINTR) # endif #endif static int udp_request_cmp(const UDPRequest *r1, const UDPRequest *r2) { if (r1->hash < r2->hash) { return -1; } else if (r1->hash > r2->hash) { return 1; } else if (r1->id < r2->id) { return -1; } else if (r1->id > r2->id) { return 1; } else if (r1->gen < r2->gen) { return -1; } else if (r1->gen > r2->gen) { return 1; } return 0; } RB_GENERATE_STATIC(UDPRequestQueue_, UDPRequest_, queue, udp_request_cmp) static void udp_tune(evutil_socket_t const handle) { if (handle == -1) { return; } setsockopt(handle, IPPROTO_IP, IP_TOS, (void *) (int []) { 0x70}, sizeof(int)); setsockopt(handle, SOL_SOCKET, SO_RCVBUFFORCE, (void *)(int[]) { UDP_BUFFER_SIZE}, sizeof(int)); setsockopt(handle, SOL_SOCKET, SO_SNDBUFFORCE, (void *)(int[]) { UDP_BUFFER_SIZE}, sizeof(int)); #if defined(IP_PMTUDISC_OMIT) setsockopt(handle, IPPROTO_IP, IP_MTU_DISCOVER, (void *) (int []) { IP_PMTUDISC_OMIT }, sizeof (int)); #elif defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) setsockopt(handle, IPPROTO_IP, IP_MTU_DISCOVER, (void *)(int[]) { IP_PMTUDISC_DONT}, sizeof(int)); #elif defined(IP_DONTFRAG) setsockopt(handle, IPPROTO_IP, IP_DONTFRAG, (void *)(int[]) { 0}, sizeof(int)); #endif #if defined(__linux__) && defined(SO_REUSEPORT) setsockopt(handle, SOL_SOCKET, SO_REUSEPORT, (void *)(int[]) { 1}, sizeof(int)); #endif } static void client_to_proxy_cb_sendto_cb(UDPRequest *const udp_request) { (void)udp_request; } static void udp_request_kill(UDPRequest *const udp_request) { if (udp_request == NULL || udp_request->status.is_dying) return; udp_request->status.is_dying = 1; // free struct context *c; if (udp_request->sendto_retry_timer != NULL) { free(event_get_callback_arg(udp_request->sendto_retry_timer)); event_free(udp_request->sendto_retry_timer); udp_request->sendto_retry_timer = NULL; } if (udp_request->timeout_timer != NULL) { event_free(udp_request->timeout_timer); udp_request->timeout_timer = NULL; } c = udp_request->context; if (udp_request->status.is_in_queue != 0) { assert(!RB_EMPTY(&c->udp_request_queue)); RB_REMOVE(UDPRequestQueue_, &c->udp_request_queue, udp_request); assert(c->connections > 0); c->connections--; } udp_request->context = NULL; free(udp_request); } int udp_listener_kill_oldest_request(struct context *c) { if (RB_EMPTY(&c->udp_request_queue)) return -1; udp_request_kill(RB_MIN(UDPRequestQueue_, &c->udp_request_queue)); return 0; } static void sendto_with_retry_timer_cb(evutil_socket_t retry_timer_handle, short ev_flags, void *const ctx_) { SendtoWithRetryCtx *const ctx = ctx_; (void)ev_flags; assert(retry_timer_handle == event_get_fd(ctx->udp_request->sendto_retry_timer)); sendto_with_retry(ctx); } static int sendto_with_retry(SendtoWithRetryCtx *const ctx) { void (*cb) (UDPRequest *udp_request); SendtoWithRetryCtx *ctx_cb; UDPRequest *udp_request = ctx->udp_request; int err; bool retriable; if (sendto(ctx->handle, ctx->buffer, ctx->length, ctx->flags, ctx->dest_addr, ctx->dest_len) == (ssize_t) ctx->length) { cb = ctx->cb; if (udp_request->sendto_retry_timer != NULL) { ctx_cb = event_get_callback_arg(udp_request->sendto_retry_timer); assert(ctx_cb != NULL); assert(ctx_cb->udp_request == ctx->udp_request); assert(ctx_cb->buffer == ctx->buffer); free(ctx_cb); event_free(udp_request->sendto_retry_timer); udp_request->sendto_retry_timer = NULL; } if (cb) { cb(udp_request); } return 0; } err = evutil_socket_geterror(udp_request->client_proxy_handle); logger(LOG_WARNING, "sendto: [%s]", evutil_socket_error_to_string(err)); retriable = (err == ENOBUFS || err == ENOMEM || err == EAGAIN || err == EINTR); if (retriable == 0) { udp_request_kill(udp_request); return -1; } assert(DNS_QUERY_TIMEOUT < UCHAR_MAX); if (++(udp_request->retries) > DNS_QUERY_TIMEOUT) { udp_request_kill(udp_request); return -1; } if (udp_request->sendto_retry_timer != NULL) { ctx_cb = event_get_callback_arg(udp_request->sendto_retry_timer); assert(ctx_cb != NULL); assert(ctx_cb->udp_request == ctx->udp_request); assert(ctx_cb->buffer == ctx->buffer); } else { if ((ctx_cb = malloc(sizeof *ctx_cb)) == NULL) { udp_request_kill(udp_request); return -1; } assert(ctx_cb == event_get_callback_arg(udp_request->sendto_retry_timer)); *ctx_cb = *ctx; if ((udp_request->sendto_retry_timer = evtimer_new(udp_request->context->event_loop, sendto_with_retry_timer_cb, ctx_cb)) == NULL) { free(ctx_cb); udp_request_kill(udp_request); return -1; } } const struct timeval tv = { .tv_sec = (time_t) UDP_DELAY_BETWEEN_RETRIES,.tv_usec = 0 }; evtimer_add(udp_request->sendto_retry_timer, &tv); return -1; } static void timeout_timer_cb(evutil_socket_t timeout_timer_handle, short ev_flags, void *const udp_request_) { UDPRequest *const udp_request = udp_request_; (void)ev_flags; (void)timeout_timer_handle; logger(LOG_DEBUG, "resolver timeout (UDP)"); udp_request_kill(udp_request); } /** * Return 0 if served. */ static int self_serve_cert_file(struct context *c, struct dns_header *header, size_t dns_query_len, size_t max_len, UDPRequest *udp_request) { int ret = dnscrypt_self_serve_cert_file(c, header, &dns_query_len, max_len); if (ret == 0) { SendtoWithRetryCtx retry_ctx = { .udp_request = udp_request, .handle = udp_request->client_proxy_handle, .buffer = header, .length = dns_query_len, .flags = 0, .dest_addr = (struct sockaddr *)&udp_request->client_sockaddr, .dest_len = udp_request->client_sockaddr_len, .cb = udp_request_kill }; sendto_with_retry(&retry_ctx); return 0; } logger(LOG_DEBUG, "failed to serve cert file, err: %d", ret); return ret; } static void client_to_proxy_cb(evutil_socket_t client_proxy_handle, short ev_flags, void *const context) { logger(LOG_DEBUG, "client to proxy cb"); const size_t sizeof_dns_query = DNS_MAX_PACKET_SIZE_UDP; static uint8_t *dns_query = NULL; struct context *c = context; UDPRequest *udp_request; ssize_t nread; size_t dns_query_len = 0; if (dns_query == NULL && (dns_query = sodium_malloc(sizeof_dns_query)) == NULL) { return; } (void)ev_flags; assert(client_proxy_handle == c->udp_listener_handle); udp_request = calloc(1, sizeof(*udp_request)); if (udp_request == NULL) return; udp_request->context = c; udp_request->sendto_retry_timer = NULL; udp_request->timeout_timer = NULL; udp_request->client_proxy_handle = client_proxy_handle; udp_request->client_sockaddr_len = sizeof(udp_request->client_sockaddr); memset(&udp_request->status, 0, sizeof(udp_request->status)); nread = recvfrom(client_proxy_handle, (void *)dns_query, sizeof_dns_query, 0, (struct sockaddr *)&udp_request->client_sockaddr, &udp_request->client_sockaddr_len); if (nread < 0) { const int err = evutil_socket_geterror(client_proxy_handle); if (!EVUTIL_ERR_RW_RETRIABLE(err)) { logger(LOG_WARNING, "recvfrom(client): [%s]", evutil_socket_error_to_string(err)); } udp_request_kill(udp_request); return; } if (nread < (ssize_t) DNS_HEADER_SIZE || nread > sizeof_dns_query) { logger(LOG_WARNING, "Short query received"); udp_request_kill(udp_request); return; } dns_query_len = (size_t) nread; assert(dns_query_len <= sizeof_dns_query); assert(SIZE_MAX - DNSCRYPT_MAX_PADDING - DNSCRYPT_QUERY_HEADER_SIZE > dns_query_len); udp_request->len = (uint16_t) dns_query_len; // decrypt if encrypted struct dnscrypt_query_header *dnscrypt_header = (struct dnscrypt_query_header *)dns_query; debug_assert(sizeof c->keypairs[0].crypt_publickey >= DNSCRYPT_MAGIC_HEADER_LEN); if ((udp_request->cert = find_cert(c, dnscrypt_header->magic_query, dns_query_len)) == NULL) { udp_request->is_dnscrypted = false; } else { if (dnscrypt_server_uncurve(c, udp_request->cert, udp_request->client_nonce, udp_request->nmkey, dns_query, &dns_query_len) != 0 || dns_query_len < DNS_HEADER_SIZE) { logger(LOG_DEBUG, "Received a suspicious query from the client"); udp_request_kill(udp_request); return; } udp_request->is_dnscrypted = true; } struct dns_header *header = (struct dns_header *)dns_query; // self serve signed certificate for provider name? if (!udp_request->is_dnscrypted) { if (self_serve_cert_file(c, header, dns_query_len, sizeof_dns_query, udp_request) == 0) return; if (!c->allow_not_dnscrypted) { logger(LOG_DEBUG, "Unauthenticated query received over UDP"); udp_request_kill(udp_request); return; } } udp_request->is_blocked = is_blocked(c, header, dns_query_len); udp_request->id = ntohs(header->id); if (questions_hash(&udp_request->hash, header, dns_query_len, c->namebuff, c->hash_key) != 0) { logger(LOG_DEBUG, "Received a suspicious query from the client"); udp_request_kill(udp_request); return; } static uint16_t gen; udp_request->gen = gen++; udp_request->status.is_in_queue = 1; c->connections++; RB_INSERT(UDPRequestQueue_, &c->udp_request_queue, udp_request); udp_request->timeout_timer = evtimer_new(udp_request->context->event_loop, timeout_timer_cb, udp_request); if (udp_request->timeout_timer) { const struct timeval tv = { .tv_sec = (time_t) DNS_QUERY_TIMEOUT,.tv_usec = 0 }; evtimer_add(udp_request->timeout_timer, &tv); } SendtoWithRetryCtx retry_ctx = { .udp_request = udp_request,.handle = c->udp_resolver_handle,.buffer = dns_query,.length = dns_query_len,.flags = 0,.dest_addr = (struct sockaddr *)&c->resolver_sockaddr,.dest_len = c->resolver_sockaddr_len,.cb = client_to_proxy_cb_sendto_cb }; sendto_with_retry(&retry_ctx); } /* * Find corresponding request by DNS id and hash of questions. */ static UDPRequest * lookup_request(struct context *c, uint16_t id, uint64_t hash) { UDPRequest *found_udp_request; UDPRequest scanned_udp_request; scanned_udp_request.hash = hash; scanned_udp_request.id = id; scanned_udp_request.gen = (uint16_t) 0U; found_udp_request = RB_NFIND(UDPRequestQueue_, &c->udp_request_queue, &scanned_udp_request); if (found_udp_request == NULL || found_udp_request->hash != hash || found_udp_request->id != id) { return NULL; } return found_udp_request; } static int maybe_truncate(uint8_t *const dns_reply, size_t *const dns_reply_len_p, size_t query_len) { struct dns_header *header = (struct dns_header *)dns_reply; uint8_t *ansp; if (*dns_reply_len_p <= sizeof(struct dns_header)) { *dns_reply_len_p = 0; return -1; } if (query_len >= *dns_reply_len_p) { return 0; } if (!(ansp = skip_questions(header, *dns_reply_len_p))) { *dns_reply_len_p = sizeof(struct dns_header); return -1; } *dns_reply_len_p = (size_t) (ansp - dns_reply); header->hb3 |= HB3_TC; header->ancount = htons(0); header->nscount = htons(0); header->arcount = htons(0); return 0; } static void resolver_to_proxy_cb(evutil_socket_t proxy_resolver_handle, short ev_flags, void *const context) { logger(LOG_DEBUG, "resolver to proxy cb"); const size_t sizeof_dns_reply = DNS_MAX_PACKET_SIZE_UDP; static uint8_t *dns_reply = NULL; struct context *c = context; UDPRequest *udp_request = NULL; struct sockaddr_storage resolver_sockaddr; ev_socklen_t resolver_sockaddr_len = sizeof(struct sockaddr_storage); ssize_t nread; size_t dns_reply_len = (size_t) 0U; (void)ev_flags; if (dns_reply == NULL && (dns_reply = sodium_malloc(sizeof_dns_reply)) == NULL) { return; } nread = recvfrom(proxy_resolver_handle, (void *)dns_reply, sizeof_dns_reply, 0, (struct sockaddr *)&resolver_sockaddr, &resolver_sockaddr_len); if (nread < 0) { const int err = evutil_socket_geterror(proxy_resolver_handle); if (!EVUTIL_ERR_RW_RETRIABLE(err)) { logger(LOG_WARNING, "recvfrom(resolver): [%s]", evutil_socket_error_to_string(err)); } return; } if (evutil_sockaddr_cmp((const struct sockaddr *)&resolver_sockaddr, (const struct sockaddr *) &c->resolver_sockaddr, 1) != 0) { logger(LOG_WARNING, "Received a resolver reply from a different resolver"); return; } dns_reply_len = nread; struct dns_header *header = (struct dns_header *)dns_reply; uint16_t id = ntohs(header->id); uint64_t hash; if (questions_hash(&hash, header, dns_reply_len, c->namebuff, c->hash_key) != 0) { logger(LOG_ERR, "Received an invalid response from the server"); return; } udp_request = lookup_request(c, id, hash); if (udp_request == NULL) { logger(LOG_DEBUG, "Received a reply that doesn't match any active query"); return; } size_t max_reply_size = DNS_MAX_PACKET_SIZE_UDP; size_t max_len = dns_reply_len + DNSCRYPT_MAX_PADDING + DNSCRYPT_REPLY_HEADER_SIZE; if (max_len > max_reply_size) max_len = max_reply_size; if (udp_request->is_blocked) { struct dns_header *p = (struct dns_header *) dns_reply; SET_RCODE(p, REFUSED); } maybe_truncate(dns_reply, &dns_reply_len, udp_request->len); if (udp_request->is_dnscrypted) { if (dnscrypt_server_curve (c, udp_request->cert, udp_request->client_nonce, udp_request->nmkey, dns_reply, &dns_reply_len, max_len) != 0) { logger(LOG_ERR, "Curving reply failed."); return; } } SendtoWithRetryCtx retry_ctx = { .udp_request = udp_request,.handle = udp_request->client_proxy_handle,.buffer = dns_reply,.length = dns_reply_len,.flags = 0,.dest_addr = (struct sockaddr *)&udp_request->client_sockaddr,.dest_len = udp_request->client_sockaddr_len,.cb = udp_request_kill }; sendto_with_retry(&retry_ctx); } int udp_listener_bind(struct context *c) { // listen socket & bind assert(c->udp_listener_handle == -1); if ((c->udp_listener_handle = socket(c->local_sockaddr.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) { logger(LOG_ERR, "Unable to create a socket (UDP)"); return -1; } udp_tune(c->udp_listener_handle); evutil_make_socket_closeonexec(c->udp_listener_handle); evutil_make_socket_nonblocking(c->udp_listener_handle); if (bind (c->udp_listener_handle, (struct sockaddr *)&c->local_sockaddr, c->local_sockaddr_len) != 0) { logger(LOG_ERR, "Unable to bind (UDP) [%s]", evutil_socket_error_to_string(evutil_socket_geterror (c->udp_listener_handle))); evutil_closesocket(c->udp_listener_handle); c->udp_listener_handle = -1; return -1; } // resolver socket assert(c->udp_resolver_handle == -1); if ((c->udp_resolver_handle = socket(c->resolver_sockaddr.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) { logger(LOG_ERR, "Unable to create a socket to the resolver"); evutil_closesocket(c->udp_resolver_handle); c->udp_listener_handle = -1; } evutil_make_socket_closeonexec(c->udp_resolver_handle); evutil_make_socket_nonblocking(c->udp_resolver_handle); udp_tune(c->udp_resolver_handle); /* Bind source IP:port if --outgoing-address is provided */ if(c->outgoing_address && bind(c->udp_resolver_handle, (struct sockaddr *)&c->outgoing_sockaddr, c->outgoing_sockaddr_len) != 0) { logger(LOG_ERR, "Unable to bind (UDP) [%s]", evutil_socket_error_to_string(evutil_socket_geterror (c->udp_resolver_handle))); evutil_closesocket(c->udp_resolver_handle); c->udp_resolver_handle = -1; return -1; } RB_INIT(&c->udp_request_queue); return 0; } void udp_listener_stop(struct context *c) { event_free(c->udp_resolver_event); c->udp_resolver_event = NULL; while (udp_listener_kill_oldest_request(c) == 0); logger(LOG_INFO, "UDP listener shut down"); } int udp_listener_start(struct context *c) { assert(c->udp_listener_handle != -1); if ((c->udp_listener_event = event_new(c->event_loop, c->udp_listener_handle, EV_READ | EV_PERSIST, client_to_proxy_cb, c)) == NULL) { return -1; } if (event_add(c->udp_listener_event, NULL) != 0) { udp_listener_stop(c); return -1; } assert(c->udp_resolver_handle != -1); if ((c->udp_resolver_event = event_new(c->event_loop, c->udp_resolver_handle, EV_READ | EV_PERSIST, resolver_to_proxy_cb, c)) == NULL) { udp_listener_stop(c); return -1; } if (event_add(c->udp_resolver_event, NULL) != 0) { udp_listener_stop(c); return -1; } return 0; }