Resolver: TCP support.

Resend DNS query over TCP once UDP response came truncated.
This commit is contained in:
Roman Arutyunyan 2016-01-28 15:28:20 +03:00
parent 1e17ee9366
commit 766b190779
2 changed files with 554 additions and 56 deletions

View file

@ -12,6 +12,9 @@
#define NGX_RESOLVER_UDP_SIZE 4096
#define NGX_RESOLVER_TCP_RSIZE (2 + 65535)
#define NGX_RESOLVER_TCP_WSIZE 8192
typedef struct {
u_char ident_hi;
@ -54,6 +57,7 @@ typedef struct {
ngx_int_t ngx_udp_connect(ngx_resolver_connection_t *rec);
ngx_int_t ngx_tcp_connect(ngx_resolver_connection_t *rec);
static void ngx_resolver_cleanup(void *data);
@ -64,6 +68,10 @@ static void ngx_resolver_expire(ngx_resolver_t *r, ngx_rbtree_t *tree,
ngx_queue_t *queue);
static ngx_int_t ngx_resolver_send_query(ngx_resolver_t *r,
ngx_resolver_node_t *rn);
static ngx_int_t ngx_resolver_send_udp_query(ngx_resolver_t *r,
ngx_resolver_connection_t *rec, u_char *query, u_short qlen);
static ngx_int_t ngx_resolver_send_tcp_query(ngx_resolver_t *r,
ngx_resolver_connection_t *rec, u_char *query, u_short qlen);
static ngx_int_t ngx_resolver_create_name_query(ngx_resolver_t *r,
ngx_resolver_node_t *rn, ngx_str_t *name);
static ngx_int_t ngx_resolver_create_addr_query(ngx_resolver_t *r,
@ -72,12 +80,14 @@ static void ngx_resolver_resend_handler(ngx_event_t *ev);
static time_t ngx_resolver_resend(ngx_resolver_t *r, ngx_rbtree_t *tree,
ngx_queue_t *queue);
static ngx_uint_t ngx_resolver_resend_empty(ngx_resolver_t *r);
static void ngx_resolver_read_response(ngx_event_t *rev);
static void ngx_resolver_udp_read(ngx_event_t *rev);
static void ngx_resolver_tcp_write(ngx_event_t *wev);
static void ngx_resolver_tcp_read(ngx_event_t *rev);
static void ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf,
size_t n);
size_t n, ngx_uint_t tcp);
static void ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t n,
ngx_uint_t ident, ngx_uint_t code, ngx_uint_t qtype,
ngx_uint_t nan, ngx_uint_t ans);
ngx_uint_t nan, ngx_uint_t trunc, ngx_uint_t ans);
static void ngx_resolver_process_ptr(ngx_resolver_t *r, u_char *buf, size_t n,
ngx_uint_t ident, ngx_uint_t code, ngx_uint_t nan);
static ngx_resolver_node_t *ngx_resolver_lookup_name(ngx_resolver_t *r,
@ -165,6 +175,7 @@ ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names, ngx_uint_t n)
r->ident = -1;
r->resend_timeout = 5;
r->tcp_timeout = 5;
r->expire = 30;
r->valid = 0;
@ -241,6 +252,7 @@ ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names, ngx_uint_t n)
rec[j].sockaddr = u.addrs[j].sockaddr;
rec[j].socklen = u.addrs[j].socklen;
rec[j].server = u.addrs[j].name;
rec[j].resolver = r;
}
}
@ -279,6 +291,10 @@ ngx_resolver_cleanup(void *data)
if (rec[i].udp) {
ngx_close_connection(rec[i].udp);
}
if (rec[i].tcp) {
ngx_close_connection(rec[i].tcp);
}
}
ngx_free(r);
@ -691,8 +707,10 @@ ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx,
}
rn->naddrs = (u_short) -1;
rn->tcp = 0;
#if (NGX_HAVE_INET6)
rn->naddrs6 = r->ipv6 ? (u_short) -1 : 0;
rn->tcp6 = 0;
#endif
if (ngx_resolver_send_query(r, rn) != NGX_OK) {
@ -908,8 +926,10 @@ ngx_resolve_addr(ngx_resolver_ctx_t *ctx)
}
rn->naddrs = (u_short) -1;
rn->tcp = 0;
#if (NGX_HAVE_INET6)
rn->naddrs6 = (u_short) -1;
rn->tcp6 = 0;
#endif
if (ngx_resolver_send_query(r, rn) != NGX_OK) {
@ -1104,60 +1124,161 @@ ngx_resolver_expire(ngx_resolver_t *r, ngx_rbtree_t *tree, ngx_queue_t *queue)
static ngx_int_t
ngx_resolver_send_query(ngx_resolver_t *r, ngx_resolver_node_t *rn)
{
ssize_t n;
ngx_int_t rc;
ngx_resolver_connection_t *rec;
rec = r->connections.elts;
rec = &rec[rn->last_connection];
if (rec->udp == NULL) {
if (rec->log.handler == NULL) {
rec->log = *r->log;
rec->log.handler = ngx_resolver_log_error;
rec->log.data = rec;
rec->log.action = "resolving";
if (ngx_udp_connect(rec) != NGX_OK) {
return NGX_ERROR;
}
rec->udp->data = r;
rec->udp->read->handler = ngx_resolver_read_response;
rec->udp->read->resolver = 1;
}
if (rn->naddrs == (u_short) -1) {
n = ngx_send(rec->udp, rn->query, rn->qlen);
rc = rn->tcp ? ngx_resolver_send_tcp_query(r, rec, rn->query, rn->qlen)
: ngx_resolver_send_udp_query(r, rec, rn->query, rn->qlen);
if (n == -1) {
return NGX_ERROR;
}
if ((size_t) n != (size_t) rn->qlen) {
ngx_log_error(NGX_LOG_CRIT, &rec->log, 0, "send() incomplete");
return NGX_ERROR;
if (rc != NGX_OK) {
return rc;
}
}
#if (NGX_HAVE_INET6)
if (rn->query6 && rn->naddrs6 == (u_short) -1) {
n = ngx_send(rec->udp, rn->query6, rn->qlen);
rc = rn->tcp6
? ngx_resolver_send_tcp_query(r, rec, rn->query6, rn->qlen)
: ngx_resolver_send_udp_query(r, rec, rn->query6, rn->qlen);
if (n == -1) {
return NGX_ERROR;
}
if ((size_t) n != (size_t) rn->qlen) {
ngx_log_error(NGX_LOG_CRIT, &rec->log, 0, "send() incomplete");
return NGX_ERROR;
if (rc != NGX_OK) {
return rc;
}
}
#endif
return NGX_OK;
}
static ngx_int_t
ngx_resolver_send_udp_query(ngx_resolver_t *r, ngx_resolver_connection_t *rec,
u_char *query, u_short qlen)
{
ssize_t n;
if (rec->udp == NULL) {
if (ngx_udp_connect(rec) != NGX_OK) {
return NGX_ERROR;
}
rec->udp->data = rec;
rec->udp->read->handler = ngx_resolver_udp_read;
rec->udp->read->resolver = 1;
}
n = ngx_send(rec->udp, query, qlen);
if (n == -1) {
return NGX_ERROR;
}
if ((size_t) n != (size_t) qlen) {
ngx_log_error(NGX_LOG_CRIT, &rec->log, 0, "send() incomplete");
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_resolver_send_tcp_query(ngx_resolver_t *r, ngx_resolver_connection_t *rec,
u_char *query, u_short qlen)
{
ngx_buf_t *b;
ngx_int_t rc;
rc = NGX_OK;
if (rec->tcp == NULL) {
b = rec->read_buf;
if (b == NULL) {
b = ngx_resolver_calloc(r, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_ERROR;
}
b->start = ngx_resolver_alloc(r, NGX_RESOLVER_TCP_RSIZE);
if (b->start == NULL) {
return NGX_ERROR;
}
b->end = b->start + NGX_RESOLVER_TCP_RSIZE;
rec->read_buf = b;
}
b->pos = b->start;
b->last = b->start;
b = rec->write_buf;
if (b == NULL) {
b = ngx_resolver_calloc(r, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_ERROR;
}
b->start = ngx_resolver_alloc(r, NGX_RESOLVER_TCP_WSIZE);
if (b->start == NULL) {
return NGX_ERROR;
}
b->end = b->start + NGX_RESOLVER_TCP_WSIZE;
rec->write_buf = b;
}
b->pos = b->start;
b->last = b->start;
rc = ngx_tcp_connect(rec);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
rec->tcp->data = rec;
rec->tcp->write->handler = ngx_resolver_tcp_write;
rec->tcp->read->handler = ngx_resolver_tcp_read;
rec->tcp->read->resolver = 1;
ngx_add_timer(rec->tcp->write, (ngx_msec_t) (r->tcp_timeout * 1000));
}
b = rec->write_buf;
if (b->end - b->last < 2 + qlen) {
ngx_log_error(NGX_LOG_CRIT, &rec->log, 0, "buffer overflow");
return NGX_ERROR;
}
*b->last++ = (u_char) (qlen >> 8);
*b->last++ = (u_char) qlen;
b->last = ngx_cpymem(b->last, query, qlen);
if (rc == NGX_OK) {
ngx_resolver_tcp_write(rec->tcp->write);
}
return NGX_OK;
}
static void
ngx_resolver_resend_handler(ngx_event_t *ev)
{
@ -1282,13 +1403,15 @@ ngx_resolver_resend_empty(ngx_resolver_t *r)
static void
ngx_resolver_read_response(ngx_event_t *rev)
ngx_resolver_udp_read(ngx_event_t *rev)
{
ssize_t n;
ngx_connection_t *c;
u_char buf[NGX_RESOLVER_UDP_SIZE];
ssize_t n;
ngx_connection_t *c;
ngx_resolver_connection_t *rec;
u_char buf[NGX_RESOLVER_UDP_SIZE];
c = rev->data;
rec = c->data;
do {
n = ngx_udp_recv(c, buf, NGX_RESOLVER_UDP_SIZE);
@ -1297,17 +1420,144 @@ ngx_resolver_read_response(ngx_event_t *rev)
return;
}
ngx_resolver_process_response(c->data, buf, n);
ngx_resolver_process_response(rec->resolver, buf, n, 0);
} while (rev->ready);
}
static void
ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf, size_t n)
ngx_resolver_tcp_write(ngx_event_t *wev)
{
off_t sent;
ssize_t n;
ngx_buf_t *b;
ngx_resolver_t *r;
ngx_connection_t *c;
ngx_resolver_connection_t *rec;
c = wev->data;
rec = c->data;
b = rec->write_buf;
r = rec->resolver;
if (wev->timedout) {
goto failed;
}
sent = c->sent;
while (wev->ready && b->pos < b->last) {
n = ngx_send(c, b->pos, b->last - b->pos);
if (n == NGX_AGAIN) {
break;
}
if (n == NGX_ERROR) {
goto failed;
}
b->pos += n;
}
if (b->pos != b->start) {
b->last = ngx_movemem(b->start, b->pos, b->last - b->pos);
b->pos = b->start;
}
if (c->sent != sent) {
ngx_add_timer(wev, (ngx_msec_t) (r->tcp_timeout * 1000));
}
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
goto failed;
}
return;
failed:
ngx_close_connection(c);
rec->tcp = NULL;
}
static void
ngx_resolver_tcp_read(ngx_event_t *rev)
{
u_char *p;
size_t size;
ssize_t n;
u_short qlen;
ngx_buf_t *b;
ngx_resolver_t *r;
ngx_connection_t *c;
ngx_resolver_connection_t *rec;
c = rev->data;
rec = c->data;
b = rec->read_buf;
r = rec->resolver;
while (rev->ready) {
n = ngx_recv(c, b->last, b->end - b->last);
if (n == NGX_AGAIN) {
break;
}
if (n == NGX_ERROR || n == 0) {
goto failed;
}
b->last += n;
for ( ;; ) {
p = b->pos;
size = b->last - p;
if (size < 2) {
break;
}
qlen = (u_short) *p++ << 8;
qlen += *p++;
if (size < (size_t) (2 + qlen)) {
break;
}
ngx_resolver_process_response(r, p, qlen, 1);
b->pos += 2 + qlen;
}
if (b->pos != b->start) {
b->last = ngx_movemem(b->start, b->pos, b->last - b->pos);
b->pos = b->start;
}
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
goto failed;
}
return;
failed:
ngx_close_connection(c);
rec->tcp = NULL;
}
static void
ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf, size_t n,
ngx_uint_t tcp)
{
char *err;
ngx_uint_t i, times, ident, qident, flags, code, nqs, nan,
ngx_uint_t i, times, ident, qident, flags, code, nqs, nan, trunc,
qtype, qclass;
#if (NGX_HAVE_INET6)
ngx_uint_t qident6;
@ -1327,6 +1577,7 @@ ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf, size_t n)
flags = (response->flags_hi << 8) + response->flags_lo;
nqs = (response->nqs_hi << 8) + response->nqs_lo;
nan = (response->nan_hi << 8) + response->nan_lo;
trunc = flags & 0x0200;
ngx_log_debug6(NGX_LOG_DEBUG_CORE, r->log, 0,
"resolver DNS response %ui fl:%04Xui %ui/%ui/%ud/%ud",
@ -1335,9 +1586,10 @@ ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf, size_t n)
(response->nar_hi << 8) + response->nar_lo);
/* response to a standard query */
if ((flags & 0xf870) != 0x8000) {
if ((flags & 0xf870) != 0x8000 || (trunc && tcp)) {
ngx_log_error(r->log_level, r->log, 0,
"invalid DNS response %ui fl:%04Xui", ident, flags);
"invalid %s DNS response %ui fl:%04Xui",
tcp ? "TCP" : "UDP", ident, flags);
return;
}
@ -1427,7 +1679,7 @@ found:
case NGX_RESOLVE_AAAA:
#endif
ngx_resolver_process_a(r, buf, n, ident, code, qtype, nan,
ngx_resolver_process_a(r, buf, n, ident, code, qtype, nan, trunc,
i + sizeof(ngx_resolver_qs_t));
break;
@ -1476,23 +1728,24 @@ dns_error:
static void
ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t last,
ngx_uint_t ident, ngx_uint_t code, ngx_uint_t qtype,
ngx_uint_t nan, ngx_uint_t ans)
ngx_uint_t nan, ngx_uint_t trunc, ngx_uint_t ans)
{
char *err;
u_char *cname;
size_t len;
int32_t ttl;
uint32_t hash;
in_addr_t *addr;
ngx_str_t name;
ngx_addr_t *addrs;
ngx_uint_t type, class, qident, naddrs, a, i, n, start;
char *err;
u_char *cname;
size_t len;
int32_t ttl;
uint32_t hash;
in_addr_t *addr;
ngx_str_t name;
ngx_addr_t *addrs;
ngx_uint_t type, class, qident, naddrs, a, i, n, start;
#if (NGX_HAVE_INET6)
struct in6_addr *addr6;
struct in6_addr *addr6;
#endif
ngx_resolver_an_t *an;
ngx_resolver_ctx_t *ctx, *next;
ngx_resolver_node_t *rn;
ngx_resolver_an_t *an;
ngx_resolver_ctx_t *ctx, *next;
ngx_resolver_node_t *rn;
ngx_resolver_connection_t *rec;
if (ngx_resolver_copy(r, &name, buf,
buf + sizeof(ngx_resolver_hdr_t), buf + last)
@ -1528,6 +1781,11 @@ ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t last,
goto failed;
}
if (trunc && rn->tcp6) {
ngx_resolver_free(r, name.data);
goto failed;
}
qident = (rn->query6[0] << 8) + rn->query6[1];
break;
@ -1542,6 +1800,11 @@ ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t last,
goto failed;
}
if (trunc && rn->tcp) {
ngx_resolver_free(r, name.data);
goto failed;
}
qident = (rn->query[0] << 8) + rn->query[1];
}
@ -1555,6 +1818,45 @@ ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t last,
ngx_resolver_free(r, name.data);
if (trunc) {
ngx_queue_remove(&rn->queue);
if (rn->waiting == NULL) {
ngx_rbtree_delete(&r->name_rbtree, &rn->node);
ngx_resolver_free_node(r, rn);
goto next;
}
rec = r->connections.elts;
rec = &rec[rn->last_connection];
switch (qtype) {
#if (NGX_HAVE_INET6)
case NGX_RESOLVE_AAAA:
rn->tcp6 = 1;
(void) ngx_resolver_send_tcp_query(r, rec, rn->query6, rn->qlen);
break;
#endif
default: /* NGX_RESOLVE_A */
rn->tcp = 1;
(void) ngx_resolver_send_tcp_query(r, rec, rn->query, rn->qlen);
}
rn->expire = ngx_time() + r->resend_timeout;
ngx_queue_insert_head(&r->name_resend_queue, &rn->queue);
goto next;
}
if (code == 0 && rn->code) {
code = rn->code;
}
@ -3186,3 +3488,186 @@ failed:
return NGX_ERROR;
}
ngx_int_t
ngx_tcp_connect(ngx_resolver_connection_t *rec)
{
int rc;
ngx_int_t event;
ngx_err_t err;
ngx_uint_t level;
ngx_socket_t s;
ngx_event_t *rev, *wev;
ngx_connection_t *c;
s = ngx_socket(rec->sockaddr->sa_family, SOCK_STREAM, 0);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "TCP socket %d", s);
if (s == (ngx_socket_t) -1) {
ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
ngx_socket_n " failed");
return NGX_ERROR;
}
c = ngx_get_connection(s, &rec->log);
if (c == NULL) {
if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
ngx_close_socket_n "failed");
}
return NGX_ERROR;
}
if (ngx_nonblocking(s) == -1) {
ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
ngx_nonblocking_n " failed");
goto failed;
}
rev = c->read;
wev = c->write;
rev->log = &rec->log;
wev->log = &rec->log;
rec->tcp = c;
c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
if (ngx_add_conn) {
if (ngx_add_conn(c) == NGX_ERROR) {
goto failed;
}
}
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, &rec->log, 0,
"connect to %V, fd:%d #%uA", &rec->server, s, c->number);
rc = connect(s, rec->sockaddr, rec->socklen);
if (rc == -1) {
err = ngx_socket_errno;
if (err != NGX_EINPROGRESS
#if (NGX_WIN32)
/* Winsock returns WSAEWOULDBLOCK (NGX_EAGAIN) */
&& err != NGX_EAGAIN
#endif
)
{
if (err == NGX_ECONNREFUSED
#if (NGX_LINUX)
/*
* Linux returns EAGAIN instead of ECONNREFUSED
* for unix sockets if listen queue is full
*/
|| err == NGX_EAGAIN
#endif
|| err == NGX_ECONNRESET
|| err == NGX_ENETDOWN
|| err == NGX_ENETUNREACH
|| err == NGX_EHOSTDOWN
|| err == NGX_EHOSTUNREACH)
{
level = NGX_LOG_ERR;
} else {
level = NGX_LOG_CRIT;
}
ngx_log_error(level, c->log, err, "connect() to %V failed",
&rec->server);
ngx_close_connection(c);
rec->tcp = NULL;
return NGX_ERROR;
}
}
if (ngx_add_conn) {
if (rc == -1) {
/* NGX_EINPROGRESS */
return NGX_AGAIN;
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "connected");
wev->ready = 1;
return NGX_OK;
}
if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &rec->log, ngx_socket_errno,
"connect(): %d", rc);
if (ngx_blocking(s) == -1) {
ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno,
ngx_blocking_n " failed");
goto failed;
}
/*
* FreeBSD's aio allows to post an operation on non-connected socket.
* NT does not support it.
*
* TODO: check in Win32, etc. As workaround we can use NGX_ONESHOT_EVENT
*/
rev->ready = 1;
wev->ready = 1;
return NGX_OK;
}
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
/* kqueue */
event = NGX_CLEAR_EVENT;
} else {
/* select, poll, /dev/poll */
event = NGX_LEVEL_EVENT;
}
if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) {
goto failed;
}
if (rc == -1) {
/* NGX_EINPROGRESS */
if (ngx_add_event(wev, NGX_WRITE_EVENT, event) != NGX_OK) {
goto failed;
}
return NGX_AGAIN;
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "connected");
wev->ready = 1;
return NGX_OK;
failed:
ngx_close_connection(c);
rec->tcp = NULL;
return NGX_ERROR;
}

View file

@ -36,12 +36,19 @@
#define NGX_RESOLVER_MAX_RECURSION 50
typedef struct ngx_resolver_s ngx_resolver_t;
typedef struct {
ngx_connection_t *udp;
ngx_connection_t *tcp;
struct sockaddr *sockaddr;
socklen_t socklen;
ngx_str_t server;
ngx_log_t log;
ngx_buf_t *read_buf;
ngx_buf_t *write_buf;
ngx_resolver_t *resolver;
} ngx_resolver_connection_t;
@ -93,13 +100,18 @@ typedef struct {
time_t valid;
uint32_t ttl;
unsigned tcp:1;
#if (NGX_HAVE_INET6)
unsigned tcp6:1;
#endif
ngx_uint_t last_connection;
ngx_resolver_ctx_t *waiting;
} ngx_resolver_node_t;
typedef struct {
struct ngx_resolver_s {
/* has to be pointer because of "incomplete type" */
ngx_event_t *event;
void *dummy;
@ -133,11 +145,12 @@ typedef struct {
#endif
time_t resend_timeout;
time_t tcp_timeout;
time_t expire;
time_t valid;
ngx_uint_t log_level;
} ngx_resolver_t;
};
struct ngx_resolver_ctx_s {