Commit 8d71fdcd authored by Chen Wei's avatar Chen Wei

DNS over tcp is working

parent b9eb9201
...@@ -30,7 +30,9 @@ static int do_check_sign(struct frec *forward, int status, time_t now, char *nam ...@@ -30,7 +30,9 @@ static int do_check_sign(struct frec *forward, int status, time_t now, char *nam
static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen, static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen,
char *name, char *keyname); char *name, char *keyname);
#endif #endif
static int tcp_conn_serv(struct server *serv, time_t now,
unsigned char *packet, size_t payload_size,
union mysockaddr *peer_addr);
/* Send a UDP packet with its source address set as "source" /* Send a UDP packet with its source address set as "source"
unless nowild is true, when we just send it with the kernel default */ unless nowild is true, when we just send it with the kernel default */
...@@ -461,7 +463,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, ...@@ -461,7 +463,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
#endif #endif
} }
//TODO retry //TODO how to retry here?
if (sendto (fd, (char *) header, plen, 0, if (sendto (fd, (char *) header, plen, 0,
&fwdserv->addr.sa, sa_len (&fwdserv->addr)) == -1) &fwdserv->addr.sa, sa_len (&fwdserv->addr)) == -1)
{ {
...@@ -1635,6 +1637,167 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si ...@@ -1635,6 +1637,167 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
} }
#endif #endif
/* try establish tcp connection to upstream server and forward query
*
* return -1 on tcp connect/read/write error
* 0 on upstream response has 0 length DNS message, or DNSSEC error
* > 0 DNS message length received from upstream server */
static int tcp_conn_serv(struct server *serv, time_t now,
unsigned char *packet, size_t payload_size,
union mysockaddr *peer_addr)
{
unsigned char *payload = packet + 2; /* skip msg length field */
struct dns_header *header = (struct dns_header *) payload;
unsigned int gotname;
unsigned short qtype;
int checking_disabled = header->hb4 & HB4_CD;
unsigned int m = 0;
u16 msg_len;
#ifdef HAVE_DNSSEC
unsigned char *newhash, hash[HASH_SIZE];
if ((newhash = hash_questions (header, (unsigned int) payload_size,
daemon->namebuff)))
memcpy (hash, newhash, HASH_SIZE);
else
memset (hash, 0, HASH_SIZE);
#else
unsigned int crc =
questions_crc (header, (unsigned int) payload_size, daemon->namebuff);
#endif
if (serv->tcpfd == -1)
{
if ((serv->tcpfd =
socket (serv->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
return -1;
#ifdef HAVE_CONNTRACK
/* Copy connection mark of incoming query to outgoing connection. */
if (option_bool (OPT_CONNTRACK))
{
unsigned int mark;
struct all_addr local;
#ifdef HAVE_IPV6
if (local_addr->sa.sa_family == AF_INET6)
local.addr.addr6 = local_addr->in6.sin6_addr;
else
#endif
local.addr.addr4 = local_addr->in.sin_addr;
if (get_incoming_mark (&peer_addr, &local, 1, &mark))
setsockopt (serv->tcpfd, SOL_SOCKET, SO_MARK, &mark,
sizeof (unsigned int));
}
#endif
if (!local_bind (serv->tcpfd, &serv->source_addr, serv->interface, 1) ||
connect (serv->tcpfd, &serv->addr.sa, sa_len (&serv->addr)) == -1)
{
close (serv->tcpfd);
serv->tcpfd = -1;
return -1;
}
#ifdef HAVE_DNSSEC
if (option_bool (OPT_DNSSEC_VALID))
{
size_t new_size =
add_do_bit (header, payload_size, ((char *) header) + 65536);
/* 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;
if (payload_size != new_size)
added_pheader = 1;
payload_size = new_size;
}
#endif
}
/* get query name again for logging - may have been overwritten */
if (!(gotname = extract_request (header, (unsigned int) payload_size,
daemon->namebuff, &qtype)))
strcpy (daemon->namebuff, "query");
u16 n_size = htons (payload_size);
memcpy (packet, &n_size, sizeof (u16));
if (!read_write (serv->tcpfd, packet, payload_size + sizeof (u16), 0) ||
!read_write (serv->tcpfd, (unsigned char *) &msg_len, 2, 1) ||
!(m = (unsigned int) ntohs (msg_len)) ||
!read_write (serv->tcpfd, payload, (int) m, 1))
{
close (serv->tcpfd);
serv->tcpfd = -1;
return -1;
}
if (serv->addr.sa.sa_family == AF_INET)
log_query (F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff,
(struct all_addr *) &serv->addr.in.sin_addr, NULL);
#ifdef HAVE_IPV6
else
log_query (F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff,
(struct all_addr *) &serv->addr.in6.sin6_addr, NULL);
#endif
#ifdef HAVE_DNSSEC
if (option_bool (OPT_DNSSEC_VALID) && !checking_disabled)
{
/* Limit to number of DNSSEC questions, to catch loops and avoid filling
* cache. */
int keycount = DNSSEC_WORK;
int status =
tcp_key_recurse (now, STAT_TRUNCATED, header, m, 0, daemon->namebuff,
daemon->keyname, serv, &keycount);
char *result;
if (keycount == 0)
result = "ABANDONED";
else
result =
(status ==
STAT_SECURE ? "SECURE" : (status ==
STAT_INSECURE ? "INSECURE" : "BOGUS"));
log_query (F_KEYTAG | F_SECSTAT, "result", NULL, result);
if (status == STAT_BOGUS)
no_cache_dnssec = 1;
if (status == STAT_SECURE)
cache_secure = 1;
}
#endif
/* restore CD bit to the value in the query */
if (checking_disabled)
header->hb4 |= HB4_CD;
else
header->hb4 &= ~HB4_CD;
/* There's no point in updating the cache, since this process will exit and
lose the information after a few queries. We make this call for the alias and
bogus-nxdomain side-effects. */
/* If the crc of the question section doesn't match the crc we sent, then
someone might be attempting to insert bogus values into the cache by
sending replies containing questions and bogus answers. */
#ifdef HAVE_DNSSEC
newhash = hash_questions (header, m, daemon->namebuff);
if (!newhash || memcmp (hash, newhash, HASH_SIZE) != 0)
m = 0;
#else
if (crc != questions_crc (header, m, daemon->namebuff))
m = 0;
#endif
return (int) m;
}
/* The daemon forks before calling this: it should deal with one connection, /* The daemon forks before calling this: it should deal with one connection,
blocking as neccessary, and then return. Note, need to be a bit careful blocking as neccessary, and then return. Note, need to be a bit careful
...@@ -1660,11 +1823,12 @@ unsigned char *tcp_request(int confd, time_t now, ...@@ -1660,11 +1823,12 @@ unsigned char *tcp_request(int confd, time_t now,
/* largest field in header is 16-bits, so this is still sufficiently aligned */ /* largest field in header is 16-bits, so this is still sufficiently aligned */
struct dns_header *header = (struct dns_header *)payload; struct dns_header *header = (struct dns_header *)payload;
u16 *length = (u16 *)packet; u16 *length = (u16 *)packet;
struct server *last_server, *fwdserv; struct server *last_server, *fwdserv, *serv;
struct in_addr dst_addr_4; struct in_addr dst_addr_4;
union mysockaddr peer_addr; union mysockaddr peer_addr;
socklen_t peer_len = sizeof(union mysockaddr); socklen_t peer_len = sizeof(union mysockaddr);
int query_count = 0; int query_count = 0;
int ret;
if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1) if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
return packet; return packet;
...@@ -1792,183 +1956,68 @@ unsigned char *tcp_request(int confd, time_t now, ...@@ -1792,183 +1956,68 @@ unsigned char *tcp_request(int confd, time_t now,
} }
} }
//TODO use fwdserv
if (gotname) if (gotname)
flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind, &fwdserv); flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind, &fwdserv);
if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) ret = 0;
last_server = daemon->servers; serv = last_server = NULL;
else /* --address=/xxx/1.2.3.4 */
last_server = daemon->last_server; if (addrp != NULL)
{
if (!flags && last_server) m = 0;
{ }
struct server *firstsendto = NULL; else if (fwdserv != NULL)
#ifdef HAVE_DNSSEC {
unsigned char *newhash, hash[HASH_SIZE]; /* --server=/example.org/1.2.3.4 */
if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff))) serv = fwdserv;
memcpy(hash, newhash, HASH_SIZE); ret = tcp_conn_serv (serv, now, packet, size, &peer_addr);
else }
memset(hash, 0, HASH_SIZE); else
#else {
unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); /* use normal server */
#endif struct server *firstsendto = NULL;
/* Loop round available servers until we succeed in connecting to one. if (option_bool (OPT_ORDER) || !daemon->last_server)
Note that this code subtley ensures that consecutive queries on this connection last_server = daemon->servers;
which can go to the same server, do so. */ else
while (1) last_server = daemon->last_server;
{
if (!firstsendto) while (1)
firstsendto = last_server; {
else if (!firstsendto)
{ firstsendto = last_server;
if (!(last_server = last_server->next)) else
last_server = daemon->servers; {
if (!(last_server = last_server->next))
if (last_server == firstsendto) last_server = daemon->servers;
break;
} if (last_server == firstsendto)
break;
/* server for wrong domain */ }
if (type != (last_server->flags & SERV_TYPE) ||
(type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)) || if (type != (last_server->flags & SERV_TYPE) ||
(last_server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) last_server->flags & SERV_HAS_DOMAIN ||
continue; last_server->flags & SERV_LOOP)
continue;
if (last_server->tcpfd == -1)
{ serv = last_server;
if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) ret = tcp_conn_serv (serv, now, packet, size, &peer_addr);
continue; /* something wrong with tcp connect/read/write */
if (ret <= 0)
#ifdef HAVE_CONNTRACK continue;
/* Copy connection mark of incoming query to outgoing connection. */
if (option_bool(OPT_CONNTRACK)) break;
{ }
unsigned int mark; }
struct all_addr local;
#ifdef HAVE_IPV6
if (local_addr->sa.sa_family == AF_INET6)
local.addr.addr6 = local_addr->in6.sin6_addr;
else
#endif
local.addr.addr4 = local_addr->in.sin_addr;
if (get_incoming_mark(&peer_addr, &local, 1, &mark))
setsockopt(last_server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
}
#endif
if ((!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 1) ||
connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1))
{
close(last_server->tcpfd);
last_server->tcpfd = -1;
continue;
}
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
{
size_t new_size = add_do_bit(header, size, ((char *) header) + 65536);
/* 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;
if (size != new_size)
added_pheader = 1;
size = new_size;
}
#endif
}
*length = htons(size);
/* get query name again for logging - may have been overwritten */
if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
strcpy(daemon->namebuff, "query");
if (!read_write(last_server->tcpfd, packet, size + sizeof(u16), 0) ||
!read_write(last_server->tcpfd, &c1, 1, 1) ||
!read_write(last_server->tcpfd, &c2, 1, 1) ||
!read_write(last_server->tcpfd, payload, (c1 << 8) | c2, 1))
{
close(last_server->tcpfd);
last_server->tcpfd = -1;
continue;
}
m = (c1 << 8) | c2;
if (last_server->addr.sa.sa_family == AF_INET)
log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff,
(struct all_addr *)&last_server->addr.in.sin_addr, NULL);
#ifdef HAVE_IPV6
else
log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff,
(struct all_addr *)&last_server->addr.in6.sin6_addr, NULL);
#endif
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled)
{
int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount);
char *result;
if (keycount == 0)
result = "ABANDONED";
else
result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
log_query(F_KEYTAG | F_SECSTAT, "result", NULL, result);
if (status == STAT_BOGUS)
no_cache_dnssec = 1;
if (status == STAT_SECURE)
cache_secure = 1;
}
#endif
/* restore CD bit to the value in the query */ if (ret > 0)
if (checking_disabled) m = process_reply (header, now, serv, ret,
header->hb4 |= HB4_CD; option_bool (OPT_NO_REBIND) && !norebind,
else no_cache_dnssec, cache_secure, ad_question,
header->hb4 &= ~HB4_CD; do_bit, added_pheader, check_subnet, &peer_addr);
/* There's no point in updating the cache, since this process will exit and
lose the information after a few queries. We make this call for the alias and
bogus-nxdomain side-effects. */
/* If the crc of the question section doesn't match the crc we sent, then
someone might be attempting to insert bogus values into the cache by
sending replies containing questions and bogus answers. */
#ifdef HAVE_DNSSEC
newhash = hash_questions(header, (unsigned int)m, daemon->namebuff);
if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0)
{
m = 0;
break;
}
#else
if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff))
{
m = 0;
break;
}
#endif
m = process_reply(header, now, last_server, (unsigned int)m,
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec,
cache_secure, ad_question, do_bit, added_pheader, check_subnet, &peer_addr);
break;
}
}
/* In case of local answer or no connections made. */ /* In case of local answer or no connections made. */
if (m == 0) if (m == 0)
m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment