mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 10:18:25 +00:00
Improve connection handling when talking to TCP upsteam servers.
Specifically, be prepared to open a new connection when we want to make multiple queries but the upstream server accepts fewer queries per connection.
This commit is contained in:
@@ -65,6 +65,11 @@ version 2.77
|
|||||||
Thanks to Kevin Darbyshire-Bryant and Eric Luehrsen
|
Thanks to Kevin Darbyshire-Bryant and Eric Luehrsen
|
||||||
for pushing this.
|
for pushing this.
|
||||||
|
|
||||||
|
Improve connection handling when talking to TCP upsteam
|
||||||
|
servers. Specifically, be prepared to open a new TCP
|
||||||
|
connection when we want to make multiple queries
|
||||||
|
but the upstream server accepts fewer queries per connection.
|
||||||
|
|
||||||
|
|
||||||
version 2.76
|
version 2.76
|
||||||
Include 0.0.0.0/8 in DNS rebind checks. This range
|
Include 0.0.0.0/8 in DNS rebind checks. This range
|
||||||
|
|||||||
@@ -485,6 +485,7 @@ union mysockaddr {
|
|||||||
#define SERV_FROM_FILE 4096 /* read from --servers-file */
|
#define SERV_FROM_FILE 4096 /* read from --servers-file */
|
||||||
#define SERV_LOOP 8192 /* server causes forwarding loop */
|
#define SERV_LOOP 8192 /* server causes forwarding loop */
|
||||||
#define SERV_DO_DNSSEC 16384 /* Validate DNSSEC when using this server */
|
#define SERV_DO_DNSSEC 16384 /* Validate DNSSEC when using this server */
|
||||||
|
#define SERV_GOT_TCP 32768 /* Got some data from the TCP connection */
|
||||||
|
|
||||||
struct serverfd {
|
struct serverfd {
|
||||||
int fd;
|
int fd;
|
||||||
|
|||||||
193
src/forward.c
193
src/forward.c
@@ -1459,14 +1459,15 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
|||||||
unsigned char *payload = NULL;
|
unsigned char *payload = NULL;
|
||||||
struct dns_header *new_header = NULL;
|
struct dns_header *new_header = NULL;
|
||||||
u16 *length = NULL;
|
u16 *length = NULL;
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
int type = SERV_DO_DNSSEC;
|
int type = SERV_DO_DNSSEC;
|
||||||
char *domain;
|
char *domain;
|
||||||
size_t m;
|
size_t m;
|
||||||
unsigned char c1, c2;
|
unsigned char c1, c2;
|
||||||
|
struct server *firstsendto = NULL;
|
||||||
|
|
||||||
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
|
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
|
||||||
if (--(*keycount) == 0)
|
if (--(*keycount) == 0)
|
||||||
new_status = STAT_ABANDONED;
|
new_status = STAT_ABANDONED;
|
||||||
@@ -1504,81 +1505,86 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
|||||||
/* Find server to forward to. This will normally be the
|
/* Find server to forward to. This will normally be the
|
||||||
same as for the original query, but may be another if
|
same as for the original query, but may be another if
|
||||||
servers for domains are involved. */
|
servers for domains are involved. */
|
||||||
if (search_servers(now, NULL, F_QUERY, keyname, &type, &domain, NULL) == 0)
|
if (search_servers(now, NULL, F_QUERY, keyname, &type, &domain, NULL) != 0)
|
||||||
{
|
|
||||||
struct server *start = server, *new_server = NULL;
|
|
||||||
type &= ~SERV_DO_DNSSEC;
|
|
||||||
|
|
||||||
while (1)
|
|
||||||
{
|
|
||||||
if (type == (start->flags & SERV_TYPE) &&
|
|
||||||
(type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) &&
|
|
||||||
!(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
|
|
||||||
{
|
|
||||||
new_server = start;
|
|
||||||
if (server == start)
|
|
||||||
{
|
|
||||||
new_server = NULL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(start = start->next))
|
|
||||||
start = daemon->servers;
|
|
||||||
if (start == server)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (new_server)
|
|
||||||
{
|
|
||||||
server = new_server;
|
|
||||||
/* may need to make new connection. */
|
|
||||||
if (server->tcpfd == -1)
|
|
||||||
{
|
|
||||||
if ((server->tcpfd = socket(server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
|
|
||||||
{
|
|
||||||
new_status = STAT_ABANDONED;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HAVE_CONNTRACK
|
|
||||||
/* Copy connection mark of incoming query to outgoing connection. */
|
|
||||||
if (have_mark)
|
|
||||||
setsockopt(server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!local_bind(server->tcpfd, &server->source_addr, server->interface, 1) ||
|
|
||||||
connect(server->tcpfd, &server->addr.sa, sa_len(&server->addr)) == -1)
|
|
||||||
{
|
|
||||||
close(server->tcpfd);
|
|
||||||
server->tcpfd = -1;
|
|
||||||
new_status = STAT_ABANDONED;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!read_write(server->tcpfd, packet, m + sizeof(u16), 0) ||
|
|
||||||
!read_write(server->tcpfd, &c1, 1, 1) ||
|
|
||||||
!read_write(server->tcpfd, &c2, 1, 1) ||
|
|
||||||
!read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
|
|
||||||
{
|
{
|
||||||
new_status = STAT_ABANDONED;
|
new_status = STAT_ABANDONED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
m = (c1 << 8) | c2;
|
type &= ~SERV_DO_DNSSEC;
|
||||||
|
|
||||||
new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount);
|
while (1)
|
||||||
|
{
|
||||||
|
if (!firstsendto)
|
||||||
|
firstsendto = server;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!(server = server->next))
|
||||||
|
server = daemon->servers;
|
||||||
|
if (server == firstsendto)
|
||||||
|
{
|
||||||
|
/* can't find server to accept our query. */
|
||||||
|
new_status = STAT_ABANDONED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type != (server->flags & SERV_TYPE) ||
|
||||||
|
(type == SERV_HAS_DOMAIN && !hostname_isequal(domain, server->domain)) ||
|
||||||
|
(server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
retry:
|
||||||
|
/* may need to make new connection. */
|
||||||
|
if (server->tcpfd == -1)
|
||||||
|
{
|
||||||
|
if ((server->tcpfd = socket(server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
|
||||||
|
continue; /* No good, next server */
|
||||||
|
|
||||||
|
#ifdef HAVE_CONNTRACK
|
||||||
|
/* Copy connection mark of incoming query to outgoing connection. */
|
||||||
|
if (have_mark)
|
||||||
|
setsockopt(server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!local_bind(server->tcpfd, &server->source_addr, server->interface, 1) ||
|
||||||
|
connect(server->tcpfd, &server->addr.sa, sa_len(&server->addr)) == -1)
|
||||||
|
{
|
||||||
|
close(server->tcpfd);
|
||||||
|
server->tcpfd = -1;
|
||||||
|
continue; /* No good, next server */
|
||||||
|
}
|
||||||
|
|
||||||
|
server->flags &= ~SERV_GOT_TCP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!read_write(server->tcpfd, packet, m + sizeof(u16), 0) ||
|
||||||
|
!read_write(server->tcpfd, &c1, 1, 1) ||
|
||||||
|
!read_write(server->tcpfd, &c2, 1, 1) ||
|
||||||
|
!read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
|
||||||
|
{
|
||||||
|
close(server->tcpfd);
|
||||||
|
server->tcpfd = -1;
|
||||||
|
/* We get data then EOF, reopen connection to same server,
|
||||||
|
else try next. This avoids DoS from a server which accepts
|
||||||
|
connections and then closes them. */
|
||||||
|
if (server->flags & SERV_GOT_TCP)
|
||||||
|
goto retry;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
server->flags |= SERV_GOT_TCP;
|
||||||
|
|
||||||
|
m = (c1 << 8) | c2;
|
||||||
|
new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (new_status != STAT_OK)
|
if (new_status != STAT_OK)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet)
|
if (packet)
|
||||||
free(packet);
|
free(packet);
|
||||||
|
|
||||||
@@ -1820,7 +1826,8 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
(type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)) ||
|
(type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)) ||
|
||||||
(last_server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
|
(last_server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
retry:
|
||||||
if (last_server->tcpfd == -1)
|
if (last_server->tcpfd == -1)
|
||||||
{
|
{
|
||||||
if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
|
if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
|
||||||
@@ -1840,25 +1847,27 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
last_server->flags &= ~SERV_GOT_TCP;
|
||||||
if (option_bool(OPT_DNSSEC_VALID) && (last_server->flags & SERV_DO_DNSSEC))
|
|
||||||
{
|
|
||||||
new_size = add_do_bit(header, size, ((unsigned char *) header) + 65536);
|
|
||||||
|
|
||||||
if (size != new_size)
|
|
||||||
{
|
|
||||||
added_pheader = 1;
|
|
||||||
size = new_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For debugging, set Checking Disabled, otherwise, have the upstream check too,
|
|
||||||
this allows it to select auth servers when one is returning bad data. */
|
|
||||||
if (option_bool(OPT_DNSSEC_DEBUG))
|
|
||||||
header->hb4 |= HB4_CD;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_DNSSEC
|
||||||
|
if (option_bool(OPT_DNSSEC_VALID) && (last_server->flags & SERV_DO_DNSSEC))
|
||||||
|
{
|
||||||
|
new_size = add_do_bit(header, size, ((unsigned char *) header) + 65536);
|
||||||
|
|
||||||
|
if (size != new_size)
|
||||||
|
{
|
||||||
|
added_pheader = 1;
|
||||||
|
size = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For debugging, set Checking Disabled, otherwise, have the upstream check too,
|
||||||
|
this allows it to select auth servers when one is returning bad data. */
|
||||||
|
if (option_bool(OPT_DNSSEC_DEBUG))
|
||||||
|
header->hb4 |= HB4_CD;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
*length = htons(size);
|
*length = htons(size);
|
||||||
|
|
||||||
/* get query name again for logging - may have been overwritten */
|
/* get query name again for logging - may have been overwritten */
|
||||||
@@ -1872,9 +1881,17 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
{
|
{
|
||||||
close(last_server->tcpfd);
|
close(last_server->tcpfd);
|
||||||
last_server->tcpfd = -1;
|
last_server->tcpfd = -1;
|
||||||
continue;
|
/* We get data then EOF, reopen connection to same server,
|
||||||
}
|
else try next. This avoids DoS from a server which accepts
|
||||||
|
connections and then closes them. */
|
||||||
|
if (last_server->flags & SERV_GOT_TCP)
|
||||||
|
goto retry;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_server->flags |= SERV_GOT_TCP;
|
||||||
|
|
||||||
m = (c1 << 8) | c2;
|
m = (c1 << 8) | c2;
|
||||||
|
|
||||||
if (last_server->addr.sa.sa_family == AF_INET)
|
if (last_server->addr.sa.sa_family == AF_INET)
|
||||||
|
|||||||
Reference in New Issue
Block a user