diff options
Diffstat (limited to 'src/VBox/NetworkServices/NAT/pxdns.c')
-rw-r--r-- | src/VBox/NetworkServices/NAT/pxdns.c | 932 |
1 files changed, 932 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/NAT/pxdns.c b/src/VBox/NetworkServices/NAT/pxdns.c new file mode 100644 index 00000000..28d018ce --- /dev/null +++ b/src/VBox/NetworkServices/NAT/pxdns.c @@ -0,0 +1,932 @@ +/* $Id: pxdns.c $ */ +/** @file + * NAT Network - DNS proxy. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/* + * Copyright (c) 2003,2004,2005 Armin Wolfermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define LOG_GROUP LOG_GROUP_NAT_SERVICE + +#include "winutils.h" + +#include "proxy.h" +#include "proxy_pollmgr.h" +#include "pxtcp.h" + +#include "lwip/sys.h" +#include "lwip/tcpip.h" +#include "lwip/ip_addr.h" +#include "lwip/udp.h" +#include "lwip/tcp.h" + +#ifndef RT_OS_WINDOWS +#include <sys/poll.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#else +#include "winpoll.h" +#endif + +#include <stdio.h> +#include <string.h> + + +union sockaddr_inet { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; + + +struct request; + + +/** + * DNS Proxy + */ +struct pxdns { + SOCKET sock4; + SOCKET sock6; + + struct pollmgr_handler pmhdl4; + struct pollmgr_handler pmhdl6; + + struct udp_pcb *pcb4; + struct udp_pcb *pcb6; + + struct tcp_pcb *ltcp; + + size_t generation; + size_t nresolvers; + union sockaddr_inet *resolvers; + + u16_t id; + + sys_mutex_t lock; + + size_t active_queries; + size_t expired_queries; + size_t late_answers; + size_t hash_collisions; + +#define TIMEOUT 5 + size_t timeout_slot; + u32_t timeout_mask; + struct request *timeout_list[TIMEOUT]; + +#define HASHSIZE 10 +#define HASH(id) ((id) & ((1 << HASHSIZE) - 1)) + struct request *request_hash[1 << HASHSIZE]; +} g_pxdns; + + +struct request { + /** + * Request ID that we use in relayed request. + */ + u16_t id; + + /** + * pxdns::generation used for this request + */ + size_t generation; + + /** + * Current index into pxdns::resolvers + */ + size_t residx; + + /** + * PCB from which we have received this request. lwIP doesn't + * support listening for both IPv4 and IPv6 on the same pcb, so we + * use two and need to keep track. + */ + struct udp_pcb *pcb; + + /** + * Client this request is from and its original request ID. + */ + ipX_addr_t client_addr; + u16_t client_port; + u16_t client_id; + + /** + * Chaining for pxdns::request_hash + */ + struct request **pprev_hash; + struct request *next_hash; + + /** + * Chaining for pxdns::timeout_list + */ + struct request **pprev_timeout; + struct request *next_timeout; + + /** + * Slot in pxdns::timeout_list + */ + size_t timeout_slot; + + /** + * Pbuf with reply received on pollmgr thread. + */ + struct pbuf *reply; + + /** + * Preallocated lwIP message to send reply from the lwIP thread. + */ + struct tcpip_msg msg_reply; + + /** + * Client request. ID is replaced with ours, original saved in + * client_id. Use a copy since we might need to resend and we + * don't want to hold onto pbuf of the request. + */ + size_t size; + u8_t data[1]; +}; + + +static void pxdns_create_resolver_sockaddrs(struct pxdns *pxdns, + const char **nameservers); + +static err_t pxdns_accept_syn(void *arg, struct tcp_pcb *newpcb, struct pbuf *syn); + +static void pxdns_recv4(void *arg, struct udp_pcb *pcb, struct pbuf *p, + ip_addr_t *addr, u16_t port); +static void pxdns_recv6(void *arg, struct udp_pcb *pcb, struct pbuf *p, + ip6_addr_t *addr, u16_t port); +static void pxdns_query(struct pxdns *pxdns, struct udp_pcb *pcb, struct pbuf *p, + ipX_addr_t *addr, u16_t port); +static void pxdns_timer(void *arg); +static int pxdns_rexmit(struct pxdns *pxdns, struct request *req); +static int pxdns_forward_outbound(struct pxdns *pxdns, struct request *req); + +static int pxdns_pmgr_pump(struct pollmgr_handler *handler, SOCKET fd, int revents); +static void pxdns_pcb_reply(void *ctx); + +static void pxdns_request_register(struct pxdns *pxdns, struct request *req); +static void pxdns_request_deregister(struct pxdns *pxdns, struct request *req); +static struct request *pxdns_request_find(struct pxdns *pxdns, u16_t id); + +static void pxdns_hash_add(struct pxdns *pxdns, struct request *req); +static void pxdns_hash_del(struct pxdns *pxdns, struct request *req); +static void pxdns_timeout_add(struct pxdns *pxdns, struct request *req); +static void pxdns_timeout_del(struct pxdns *pxdns, struct request *req); + +static void pxdns_request_free(struct request *req); + + +err_t +pxdns_init(struct netif *proxy_netif) +{ + struct pxdns *pxdns = &g_pxdns; + err_t error; + + LWIP_UNUSED_ARG(proxy_netif); + + pxdns->ltcp = tcp_new(); + if (pxdns->ltcp != NULL) { + tcp_bind_ip6(pxdns->ltcp, IP6_ADDR_ANY, 53); + pxdns->ltcp = tcp_listen_dual(pxdns->ltcp); + if (pxdns->ltcp != NULL) { + tcp_arg(pxdns->ltcp, pxdns); + tcp_accept_syn(pxdns->ltcp, pxdns_accept_syn); + } + } + + pxdns->pmhdl4.callback = pxdns_pmgr_pump; + pxdns->pmhdl4.data = (void *)pxdns; + pxdns->pmhdl4.slot = -1; + + pxdns->pmhdl6.callback = pxdns_pmgr_pump; + pxdns->pmhdl6.data = (void *)pxdns; + pxdns->pmhdl6.slot = -1; + + pxdns->pcb4 = udp_new(); + if (pxdns->pcb4 == NULL) { + error = ERR_MEM; + goto err_cleanup_pcb; + } + + pxdns->pcb6 = udp_new_ip6(); + if (pxdns->pcb6 == NULL) { + error = ERR_MEM; + goto err_cleanup_pcb; + } + + error = udp_bind(pxdns->pcb4, IP_ADDR_ANY, 53); + if (error != ERR_OK) { + goto err_cleanup_pcb; + } + + error = udp_bind_ip6(pxdns->pcb6, IP6_ADDR_ANY, 53); + if (error != ERR_OK) { + goto err_cleanup_pcb; + } + + udp_recv(pxdns->pcb4, pxdns_recv4, pxdns); + udp_recv_ip6(pxdns->pcb6, pxdns_recv6, pxdns); + + pxdns->sock4 = socket(AF_INET, SOCK_DGRAM, 0); + if (pxdns->sock4 == INVALID_SOCKET) { + goto err_cleanup_pcb; + } + + pxdns->sock6 = socket(AF_INET6, SOCK_DGRAM, 0); + if (pxdns->sock6 == INVALID_SOCKET) { + /* it's ok if the host doesn't support IPv6 */ + /* XXX: TODO: log */ + } + + pxdns->generation = 0; + pxdns->nresolvers = 0; + pxdns->resolvers = NULL; + pxdns_create_resolver_sockaddrs(pxdns, g_proxy_options->nameservers); + + sys_mutex_new(&pxdns->lock); + + pxdns->timeout_slot = 0; + pxdns->timeout_mask = 0; + + /* NB: assumes pollmgr thread is not running yet */ + pollmgr_add(&pxdns->pmhdl4, pxdns->sock4, POLLIN); + if (pxdns->sock6 != INVALID_SOCKET) { + pollmgr_add(&pxdns->pmhdl6, pxdns->sock6, POLLIN); + } + + return ERR_OK; + + err_cleanup_pcb: + if (pxdns->pcb4 != NULL) { + udp_remove(pxdns->pcb4); + pxdns->pcb4 = NULL; + } + if (pxdns->pcb6 != NULL) { + udp_remove(pxdns->pcb6); + pxdns->pcb4 = NULL; + } + + return error; +} + + +/** + * lwIP thread callback to set the new list of nameservers. + */ +void +pxdns_set_nameservers(void *arg) +{ + const char **nameservers = (const char **)arg; + + if (g_proxy_options->nameservers != NULL) { + RTMemFree((void *)g_proxy_options->nameservers); + } + g_proxy_options->nameservers = nameservers; + + pxdns_create_resolver_sockaddrs(&g_pxdns, nameservers); +} + + +/** + * Use this list of nameservers to resolve guest requests. + * + * Runs on lwIP thread, so no new queries or retramsmits compete with + * it for the use of the existing list of resolvers (to be replaced). + */ +static void +pxdns_create_resolver_sockaddrs(struct pxdns *pxdns, const char **nameservers) +{ + struct addrinfo hints; + union sockaddr_inet *resolvers; + size_t nnames, nresolvers; + const char **p; + int status; + + resolvers = NULL; + nresolvers = 0; + + if (nameservers == NULL) { + goto update_resolvers; + } + + nnames = 0; + for (p = nameservers; *p != NULL; ++p) { + ++nnames; + } + + if (nnames == 0) { + goto update_resolvers; + } + + resolvers = (union sockaddr_inet *)calloc(sizeof(resolvers[0]), nnames); + if (resolvers == NULL) { + nresolvers = 0; + goto update_resolvers; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + + for (p = nameservers; *p != NULL; ++p) { + const char *name = *p; + struct addrinfo *ai; + status = getaddrinfo(name, /* "domain" */ "53", &hints, &ai); + if (status != 0) { + /* XXX: log failed resolution */ + continue; + } + + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) { + /* XXX: log unsupported address family */ + freeaddrinfo(ai); + continue; + } + + if (ai->ai_addrlen > sizeof(resolvers[nresolvers])) { + /* XXX: log */ + freeaddrinfo(ai); + continue; + } + + if (ai->ai_family == AF_INET6 && pxdns->sock6 == INVALID_SOCKET) { + /* no IPv6 support on the host, can't use this resolver */ + freeaddrinfo(ai); + continue; + } + + memcpy(&resolvers[nresolvers], ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); + ++nresolvers; + } + + if (nresolvers == 0) { + if (resolvers != NULL) { + free(resolvers); + } + resolvers = NULL; + } + + update_resolvers: + ++pxdns->generation; + if (pxdns->resolvers != NULL) { + free(pxdns->resolvers); + } + pxdns->resolvers = resolvers; + pxdns->nresolvers = nresolvers; +} + + +static void +pxdns_request_free(struct request *req) +{ + LWIP_ASSERT1(req->pprev_hash == NULL); + LWIP_ASSERT1(req->pprev_timeout == NULL); + + if (req->reply != NULL) { + pbuf_free(req->reply); + } + free(req); +} + + +static void +pxdns_hash_add(struct pxdns *pxdns, struct request *req) +{ + struct request **chain; + + LWIP_ASSERT1(req->pprev_hash == NULL); + chain = &pxdns->request_hash[HASH(req->id)]; + if ((req->next_hash = *chain) != NULL) { + (*chain)->pprev_hash = &req->next_hash; + ++pxdns->hash_collisions; + } + *chain = req; + req->pprev_hash = chain; +} + + +static void +pxdns_timeout_add(struct pxdns *pxdns, struct request *req) +{ + struct request **chain; + u32_t omask; + + LWIP_ASSERT1(req->pprev_timeout == NULL); + + req->timeout_slot = pxdns->timeout_slot; + chain = &pxdns->timeout_list[req->timeout_slot]; + if ((req->next_timeout = *chain) != NULL) { + (*chain)->pprev_timeout = &req->next_timeout; + } + *chain = req; + req->pprev_timeout = chain; + + omask = pxdns->timeout_mask; + pxdns->timeout_mask |= 1U << req->timeout_slot; + if (omask == 0) { + sys_timeout(1 * 1000, pxdns_timer, pxdns); + } +} + + +static void +pxdns_hash_del(struct pxdns *pxdns, struct request *req) +{ + LWIP_ASSERT1(req->pprev_hash != NULL); + --pxdns->active_queries; + + if (req->next_hash != NULL) { + req->next_hash->pprev_hash = req->pprev_hash; + } + *req->pprev_hash = req->next_hash; + req->pprev_hash = NULL; + req->next_hash = NULL; +} + + +static void +pxdns_timeout_del(struct pxdns *pxdns, struct request *req) +{ + LWIP_ASSERT1(req->pprev_timeout != NULL); + LWIP_ASSERT1(req->timeout_slot < TIMEOUT); + + if (req->next_timeout != NULL) { + req->next_timeout->pprev_timeout = req->pprev_timeout; + } + *req->pprev_timeout = req->next_timeout; + req->pprev_timeout = NULL; + req->next_timeout = NULL; + + if (pxdns->timeout_list[req->timeout_slot] == NULL) { + pxdns->timeout_mask &= ~(1U << req->timeout_slot); + /* may be on pollmgr thread so no sys_untimeout */ + } +} + + + +/** + * Do bookkeeping on new request. Called from pxdns_query(). + */ +static void +pxdns_request_register(struct pxdns *pxdns, struct request *req) +{ + sys_mutex_lock(&pxdns->lock); + + pxdns_hash_add(pxdns, req); + pxdns_timeout_add(pxdns, req); + ++pxdns->active_queries; + + sys_mutex_unlock(&pxdns->lock); +} + + +static void +pxdns_request_deregister(struct pxdns *pxdns, struct request *req) +{ + sys_mutex_lock(&pxdns->lock); + + pxdns_hash_del(pxdns, req); + pxdns_timeout_del(pxdns, req); + --pxdns->active_queries; + + sys_mutex_unlock(&pxdns->lock); +} + + +/** + * Find request by the id we used when relaying it and remove it from + * id hash and timeout list. Called from pxdns_pmgr_pump() when reply + * comes. + */ +static struct request * +pxdns_request_find(struct pxdns *pxdns, u16_t id) +{ + struct request *req = NULL; + + sys_mutex_lock(&pxdns->lock); + + /* find request in the id->req hash */ + for (req = pxdns->request_hash[HASH(id)]; req != NULL; req = req->next_hash) { + if (req->id == id) { + break; + } + } + + if (req != NULL) { + pxdns_hash_del(pxdns, req); + pxdns_timeout_del(pxdns, req); + --pxdns->active_queries; + } + + sys_mutex_unlock(&pxdns->lock); + return req; +} + + +/** + * Retransmit of g/c expired requests and move timeout slot forward. + */ +static void +pxdns_timer(void *arg) +{ + struct pxdns *pxdns = (struct pxdns *)arg; + struct request **chain, *req; + u32_t mask; + + sys_mutex_lock(&pxdns->lock); + + /* + * Move timeout slot first. New slot points to the list of + * expired requests. If any expired request is retransmitted, we + * keep it on the list (that is now current), effectively + * resetting the timeout. + */ + LWIP_ASSERT1(pxdns->timeout_slot < TIMEOUT); + if (++pxdns->timeout_slot == TIMEOUT) { + pxdns->timeout_slot = 0; + } + + chain = &pxdns->timeout_list[pxdns->timeout_slot]; + req = *chain; + while (req != NULL) { + struct request *expired = req; + req = req->next_timeout; + + if (pxdns_rexmit(pxdns, expired)) { + continue; + } + + pxdns_hash_del(pxdns, expired); + pxdns_timeout_del(pxdns, expired); + ++pxdns->expired_queries; + + pxdns_request_free(expired); + } + + if (pxdns->timeout_list[pxdns->timeout_slot] == NULL) { + pxdns->timeout_mask &= ~(1U << pxdns->timeout_slot); + } + else { + pxdns->timeout_mask |= 1U << pxdns->timeout_slot; + } + mask = pxdns->timeout_mask; + + sys_mutex_unlock(&pxdns->lock); + + if (mask != 0) { + sys_timeout(1 * 1000, pxdns_timer, pxdns); + } +} + + +static void +pxdns_recv4(void *arg, struct udp_pcb *pcb, struct pbuf *p, + ip_addr_t *addr, u16_t port) +{ + struct pxdns *pxdns = (struct pxdns *)arg; + pxdns_query(pxdns, pcb, p, ip_2_ipX(addr), port); +} + +static void +pxdns_recv6(void *arg, struct udp_pcb *pcb, struct pbuf *p, + ip6_addr_t *addr, u16_t port) +{ + struct pxdns *pxdns = (struct pxdns *)arg; + pxdns_query(pxdns, pcb, p, ip6_2_ipX(addr), port); +} + + +static void +pxdns_query(struct pxdns *pxdns, struct udp_pcb *pcb, struct pbuf *p, + ipX_addr_t *addr, u16_t port) +{ + struct request *req; + int sent; + + if (pxdns->nresolvers == 0) { + /* nothing we can do */ + pbuf_free(p); + return; + } + + req = calloc(1, sizeof(struct request) - 1 + p->tot_len); + if (req == NULL) { + pbuf_free(p); + return; + } + + /* copy request data */ + req->size = p->tot_len; + pbuf_copy_partial(p, req->data, p->tot_len, 0); + + /* save client identity and client's request id */ + req->pcb = pcb; + ipX_addr_copy(PCB_ISIPV6(pcb), req->client_addr, *addr); + req->client_port = port; + memcpy(&req->client_id, req->data, sizeof(req->client_id)); + + /* slap our request id onto it */ + req->id = pxdns->id++; + memcpy(req->data, &req->id, sizeof(u16_t)); + + /* resolver to forward to */ + req->generation = pxdns->generation; + req->residx = 0; + + /* prepare for relaying the reply back to guest */ + req->msg_reply.type = TCPIP_MSG_CALLBACK_STATIC; + req->msg_reply.sem = NULL; + req->msg_reply.msg.cb.function = pxdns_pcb_reply; + req->msg_reply.msg.cb.ctx = (void *)req; + + DPRINTF2(("%s: req=%p: client id %d -> id %d\n", + __func__, (void *)req, req->client_id, req->id)); + + pxdns_request_register(pxdns, req); + + sent = pxdns_forward_outbound(pxdns, req); + if (!sent) { + sent = pxdns_rexmit(pxdns, req); + } + if (!sent) { + pxdns_request_deregister(pxdns, req); + pxdns_request_free(req); + } +} + + +/** + * Forward request to the req::residx resolver in the pxdns::resolvers + * array of upstream resolvers. + * + * Returns 1 on success, 0 on failure. + */ +static int +pxdns_forward_outbound(struct pxdns *pxdns, struct request *req) +{ + union sockaddr_inet *resolver; + ssize_t nsent; +#ifdef RT_OS_WINDOWS + const char *pSendData = (const char *)&req->data[0]; + int cbSendData = (int)req->size; + Assert((size_t)cbSendData == req->size); +#else + const void *pSendData = &req->data[0]; + size_t cbSendData = req->size; +#endif + + DPRINTF2(("%s: req %p: sending to resolver #%lu\n", + __func__, (void *)req, (unsigned long)req->residx)); + + LWIP_ASSERT1(req->generation == pxdns->generation); + LWIP_ASSERT1(req->residx < pxdns->nresolvers); + resolver = &pxdns->resolvers[req->residx]; + + if (resolver->sa.sa_family == AF_INET) { + nsent = sendto(pxdns->sock4, pSendData, cbSendData, 0, + &resolver->sa, sizeof(resolver->sin)); + + } + else if (resolver->sa.sa_family == AF_INET6) { + if (pxdns->sock6 != INVALID_SOCKET) { + nsent = sendto(pxdns->sock6, pSendData, cbSendData, 0, + &resolver->sa, sizeof(resolver->sin6)); + } + else { + /* shouldn't happen, we should have weeded out IPv6 resolvers */ + return 0; + } + } + else { + /* shouldn't happen, we should have weeded out unsupported families */ + return 0; + } + + if ((size_t)nsent == req->size) { + return 1; /* sent */ + } + + if (nsent < 0) { + DPRINTF2(("%s: send: %R[sockerr]\n", __func__, SOCKERRNO())); + } + else { + DPRINTF2(("%s: sent only %lu of %lu\n", + __func__, (unsigned long)nsent, (unsigned long)req->size)); + } + return 0; /* not sent, caller will retry as necessary */ +} + + +/** + * Forward request to the next resolver in the pxdns::resolvers array + * of upstream resolvers if there are any left. + */ +static int +pxdns_rexmit(struct pxdns *pxdns, struct request *req) +{ + int sent; + + if (/* __predict_false */ req->generation != pxdns->generation) { + DPRINTF2(("%s: req %p: generation %lu != pxdns generation %lu\n", + __func__, (void *)req, + (unsigned long)req->generation, + (unsigned long)pxdns->generation)); + return 0; + } + + LWIP_ASSERT1(req->residx < pxdns->nresolvers); + do { + if (++req->residx == pxdns->nresolvers) { + return 0; + } + + sent = pxdns_forward_outbound(pxdns, req); + } while (!sent); + + return 1; +} + + +static int +pxdns_pmgr_pump(struct pollmgr_handler *handler, SOCKET fd, int revents) +{ + struct pxdns *pxdns; + struct request *req; + ssize_t nread; + err_t error; + u16_t id; + + pxdns = (struct pxdns *)handler->data; + LWIP_ASSERT1(handler == &pxdns->pmhdl4 || handler == &pxdns->pmhdl6); + LWIP_ASSERT1(fd == (handler == &pxdns->pmhdl4 ? pxdns->sock4 : pxdns->sock6)); + + if (revents & ~(POLLIN|POLLERR)) { + DPRINTF0(("%s: unexpected revents 0x%x\n", __func__, revents)); + return POLLIN; + } + + if (revents & POLLERR) { + int sockerr = -1; + socklen_t optlen = (socklen_t)sizeof(sockerr); + int status; + + status = getsockopt(fd, SOL_SOCKET, + SO_ERROR, (char *)&sockerr, &optlen); + if (status < 0) { + DPRINTF(("%s: sock %d: SO_ERROR failed: %R[sockerr]\n", + __func__, fd, SOCKERRNO())); + } + else { + DPRINTF(("%s: sock %d: %R[sockerr]\n", + __func__, fd, sockerr)); + } + } + + if ((revents & POLLIN) == 0) { + return POLLIN; + } + + +#ifdef RT_OS_WINDOWS + nread = recv(fd, (char *)pollmgr_udpbuf, sizeof(pollmgr_udpbuf), 0); +#else + nread = recv(fd, pollmgr_udpbuf, sizeof(pollmgr_udpbuf), 0); +#endif + if (nread < 0) { + DPRINTF(("%s: %R[sockerr]\n", __func__, SOCKERRNO())); + return POLLIN; + } + + /* check for minimum dns packet length */ + if (nread < 12) { + DPRINTF2(("%s: short reply %lu bytes\n", + __func__, (unsigned long)nread)); + return POLLIN; + } + + /* XXX: shall we proxy back RCODE=Refused responses? */ + + memcpy(&id, pollmgr_udpbuf, sizeof(id)); + req = pxdns_request_find(pxdns, id); + if (req == NULL) { + DPRINTF2(("%s: orphaned reply for %d\n", __func__, id)); + ++pxdns->late_answers; + return POLLIN; + } + + DPRINTF2(("%s: reply for req=%p: id %d -> client id %d\n", + __func__, (void *)req, req->id, req->client_id)); + + req->reply = pbuf_alloc(PBUF_RAW, nread, PBUF_RAM); + if (req->reply == NULL) { + DPRINTF(("%s: pbuf_alloc(%d) failed\n", __func__, (int)nread)); + pxdns_request_free(req); + return POLLIN; + } + + memcpy(pollmgr_udpbuf, &req->client_id, sizeof(req->client_id)); + error = pbuf_take(req->reply, pollmgr_udpbuf, nread); + if (error != ERR_OK) { + DPRINTF(("%s: pbuf_take(%d) failed\n", __func__, (int)nread)); + pxdns_request_free(req); + return POLLIN; + } + + proxy_lwip_post(&req->msg_reply); + return POLLIN; +} + + +/** + * Called on lwIP thread via request::msg_reply callback. + */ +static void +pxdns_pcb_reply(void *ctx) +{ + struct request *req = (struct request *)ctx; + err_t error; + + error = udp_sendto(req->pcb, req->reply, + ipX_2_ip(&req->client_addr), req->client_port); + if (error != ERR_OK) { + DPRINTF(("%s: udp_sendto err %s\n", + __func__, proxy_lwip_strerr(error))); + } + + pxdns_request_free(req); +} + + +/** + * TCP DNS proxy. This kicks in for large replies that don't fit into + * 512 bytes of UDP payload. Client will retry with TCP to get + * complete reply. + */ +static err_t +pxdns_accept_syn(void *arg, struct tcp_pcb *newpcb, struct pbuf *syn) +{ + struct pxdns *pxdns = (struct pxdns *)arg; + union sockaddr_inet *si; + ipX_addr_t *dst; + u16_t dst_port; + + tcp_accepted(pxdns->ltcp); + + if (pxdns->nresolvers == 0) { + return ERR_CONN; + } + + si = &pxdns->resolvers[0]; + + if (si->sa.sa_family == AF_INET6) { + dst = (ipX_addr_t *)&si->sin6.sin6_addr; + dst_port = ntohs(si->sin6.sin6_port); + } + else { + dst = (ipX_addr_t *)&si->sin.sin_addr; + dst_port = ntohs(si->sin.sin_port); + } + + /* + * XXX: TODO: need to implement protocol hooks. E.g. here if + * connect fails, we should try connecting to a different server. + */ + return pxtcp_pcb_accept_outbound(newpcb, syn, + si->sa.sa_family == AF_INET6, dst, dst_port); +} |