2018-04-09 23:04:13 +02:00
/* Goal: a fully standards compliant basic authoritative server. In <1000 lines.
2018-04-01 18:31:41 +02:00
Non - goals : notifications , slaving zones , name compression , edns ,
performance
*/
# include <cstdint>
# include <vector>
# include <map>
# include <stdexcept>
# include "sclasses.hh"
# include "dns.hh"
2018-04-02 13:25:19 +02:00
# include "safearray.hh"
2018-04-03 13:18:37 +02:00
# include <thread>
2018-04-09 12:54:27 +02:00
# include <signal.h>
2018-04-12 16:22:35 +02:00
# include "record-types.hh"
2018-04-08 23:20:11 +02:00
# include "dns-storage.hh"
2018-04-01 18:31:41 +02:00
using namespace std ;
2018-04-12 16:31:43 +02:00
void addAdditional ( const DNSNode * bestzone , const DNSName & zone , const vector < DNSName > & toresolve , DNSMessageWriter & response )
2018-04-09 23:04:13 +02:00
{
for ( auto addname : toresolve ) {
2018-04-11 22:12:57 +02:00
cout < < " Doing additional or glue lookup for " < < addname < < " in " < < zone < < endl ;
2018-04-09 23:04:13 +02:00
if ( ! addname . makeRelative ( zone ) ) {
cout < < addname < < " is not within our zone, not doing glue " < < endl ;
continue ;
}
2018-04-12 16:31:43 +02:00
DNSName wuh ;
2018-04-09 23:04:13 +02:00
auto addnode = bestzone - > find ( addname , wuh ) ;
if ( ! addnode | | ! addname . empty ( ) ) {
cout < < " Found nothing, continuing " < < endl ;
continue ;
}
for ( auto & type : { DNSType : : A , DNSType : : AAAA } ) {
auto iter2 = addnode - > rrsets . find ( type ) ;
if ( iter2 ! = addnode - > rrsets . end ( ) ) {
const auto & rrset = iter2 - > second ;
for ( const auto & rr : rrset . contents ) {
response . putRR ( DNSSection : : Additional , wuh + zone , type , rrset . ttl , rr ) ;
}
}
}
}
}
2018-04-09 16:43:24 +02:00
bool processQuestion ( const DNSNode & zones , DNSMessageReader & dm , const ComboAddress & local , const ComboAddress & remote , DNSMessageWriter & response )
2018-04-01 18:31:41 +02:00
{
2018-04-13 11:02:59 +02:00
DNSName qname ;
DNSType qtype ;
dm . getQuestion ( qname , qtype ) ;
2018-04-13 15:23:00 +02:00
2018-04-13 11:02:59 +02:00
DNSName origname = qname ; // we need this for error reporting, we munch the original name
cout < < " Received a query from " < < remote . toStringWithPort ( ) < < " for " < < qname < < " and type " < < qtype < < endl ;
2018-04-11 22:12:57 +02:00
try {
2018-04-13 17:48:32 +02:00
response . dh . id = dm . dh . id ; response . dh . rd = dm . dh . rd ;
2018-04-11 22:12:57 +02:00
response . dh . ad = response . dh . ra = response . dh . aa = 0 ;
2018-04-13 17:56:51 +02:00
response . dh . qr = 1 ; response . dh . opcode = dm . dh . opcode ;
2018-04-13 17:39:44 +02:00
uint16_t newsize ; bool doBit ;
if ( dm . getEDNS ( & newsize , & doBit ) ) {
if ( dm . d_ednsVersion ! = 0 ) {
cout < < " Bad EDNS version: " < < ( int ) dm . d_ednsVersion < < endl ;
response . setEDNS ( newsize , doBit , RCode : : Badvers ) ;
return true ;
}
response . setEDNS ( newsize , doBit ) ;
}
2018-04-03 18:03:59 +02:00
2018-04-13 11:02:59 +02:00
if ( qtype = = DNSType : : AXFR | | qtype = = DNSType : : IXFR ) {
2018-04-11 22:12:57 +02:00
cout < < " Query was for AXFR or IXFR over UDP, can't do that " < < endl ;
response . dh . rcode = ( int ) RCode : : Servfail ;
return true ;
}
2018-04-03 18:03:59 +02:00
2018-04-11 22:12:57 +02:00
if ( dm . dh . opcode ! = 0 ) {
cout < < " Query had non-zero opcode " < < dm . dh . opcode < < " , sending NOTIMP " < < endl ;
response . dh . rcode = ( int ) RCode : : Notimp ;
return true ;
}
2018-04-10 00:05:41 +02:00
2018-04-13 11:02:59 +02:00
DNSName zonename ;
auto fnd = zones . find ( qname , zonename ) ;
2018-04-13 13:37:17 +02:00
if ( ! fnd | | ! fnd - > zone ) {
2018-04-13 11:02:59 +02:00
cout < < " No zone matched " < < endl ;
response . dh . rcode = ( uint8_t ) RCode : : Refused ;
return true ;
}
2018-04-11 22:12:57 +02:00
2018-04-13 11:02:59 +02:00
cout < < " --- \n Found best zone: " < < zonename < < " , qname now " < < qname < < endl ;
response . dh . aa = 1 ;
2018-04-11 22:12:57 +02:00
2018-04-13 11:02:59 +02:00
auto bestzone = fnd - > zone ;
DNSName searchname ( qname ) , lastnode , zonecutname ;
const DNSNode * passedZonecut = 0 ;
int CNAMELoopCount = 0 ;
loopCNAME : ;
auto node = bestzone - > find ( searchname , lastnode , & passedZonecut , & zonecutname ) ;
if ( passedZonecut ) {
response . dh . aa = false ;
cout < < " This is a delegation, zonecutname: ' " < < zonecutname < < " ' " < < endl ;
2018-04-03 18:03:59 +02:00
2018-04-13 11:02:59 +02:00
for ( const auto & rr : passedZonecut - > rrsets ) {
cout < < " Have type " < < rr . first < < endl ;
}
auto iter = passedZonecut - > rrsets . find ( DNSType : : NS ) ;
if ( iter ! = passedZonecut - > rrsets . end ( ) ) {
const auto & rrset = iter - > second ;
vector < DNSName > toresolve ;
for ( const auto & rr : rrset . contents ) {
response . putRR ( DNSSection : : Authority , zonecutname + zonename , DNSType : : NS , rrset . ttl , rr ) ;
toresolve . push_back ( dynamic_cast < NSGen * > ( rr . get ( ) ) - > d_name ) ;
2018-04-03 18:03:59 +02:00
}
2018-04-13 11:02:59 +02:00
addAdditional ( bestzone , zonename , toresolve , response ) ;
2018-04-03 18:03:59 +02:00
}
2018-04-13 11:02:59 +02:00
}
else if ( ! searchname . empty ( ) ) {
cout < < " This is an NXDOMAIN situation " < < endl ;
if ( ! CNAMELoopCount ) // RFC 1034, 4.3.2, step 3.c
2018-04-11 22:12:57 +02:00
response . dh . rcode = ( int ) RCode : : Nxdomain ;
2018-04-13 11:02:59 +02:00
const auto & rrset = bestzone - > rrsets [ DNSType : : SOA ] ;
2018-04-11 22:12:57 +02:00
2018-04-13 11:02:59 +02:00
response . putRR ( DNSSection : : Authority , zonename , DNSType : : SOA , rrset . ttl , rrset . contents [ 0 ] ) ;
}
else {
cout < < " Found something in zone ' " < < zonename < < " ' for lhs ' " < < qname < < " ', searchname now ' " < < searchname < < " ', lastnode ' " < < lastnode < < " ', passedZonecut= " < < passedZonecut < < endl ;
2018-04-13 15:44:11 +02:00
decltype ( node - > rrsets ) : : const_iterator iter ;
2018-04-13 11:02:59 +02:00
vector < DNSName > additional ;
if ( iter = node - > rrsets . find ( DNSType : : CNAME ) , iter ! = node - > rrsets . end ( ) ) {
cout < < " We have a CNAME! " < < endl ;
const auto & rrset = iter - > second ;
response . putRR ( DNSSection : : Answer , lastnode + zonename , DNSType : : CNAME , rrset . ttl , rrset . contents [ 0 ] ) ;
DNSName target = dynamic_cast < CNAMEGen * > ( rrset . contents [ 0 ] . get ( ) ) - > d_name ;
2018-04-12 16:16:31 +02:00
2018-04-13 11:02:59 +02:00
if ( target . makeRelative ( zonename ) ) {
cout < < " Should follow CNAME to " < < target < < " within our zone " < < endl ;
searchname = target ;
2018-04-13 17:39:44 +02:00
if ( qtype ! = DNSType : : CNAME & & CNAMELoopCount + + < 10 ) { // do not loop if they *wanted* the CNAME
2018-04-13 11:02:59 +02:00
lastnode . clear ( ) ;
zonecutname . clear ( ) ;
goto loopCNAME ;
2018-04-12 16:16:31 +02:00
}
}
2018-04-13 11:02:59 +02:00
else
cout < < " CNAME points to record " < < target < < " in other zone, good luck " < < endl ;
}
else if ( iter = node - > rrsets . find ( qtype ) , iter ! = node - > rrsets . end ( ) | | ( ! node - > rrsets . empty ( ) & & qtype = = DNSType : : ANY ) ) {
auto range = make_pair ( iter , iter ) ;
if ( qtype = = DNSType : : ANY )
range = make_pair ( node - > rrsets . begin ( ) , node - > rrsets . end ( ) ) ;
else
+ + range . second ;
for ( auto i2 = range . first ; i2 ! = range . second ; + + i2 ) {
const auto & rrset = i2 - > second ;
2018-04-12 16:16:31 +02:00
for ( const auto & rr : rrset . contents ) {
2018-04-13 11:02:59 +02:00
response . putRR ( DNSSection : : Answer , lastnode + zonename , i2 - > first , rrset . ttl , rr ) ;
if ( i2 - > first = = DNSType : : MX )
2018-04-12 16:16:31 +02:00
additional . push_back ( dynamic_cast < MXGen * > ( rr . get ( ) ) - > d_name ) ;
}
}
2018-04-11 22:12:57 +02:00
}
2018-04-13 11:02:59 +02:00
else {
cout < < " Node exists, qtype doesn't, NOERROR situation, inserting SOA " < < endl ;
const auto & rrset = bestzone - > rrsets [ DNSType : : SOA ] ;
response . putRR ( DNSSection : : Answer , zonename , DNSType : : SOA , rrset . ttl , rrset . contents [ 0 ] ) ;
}
addAdditional ( bestzone , zonename , additional , response ) ;
2018-04-11 22:12:57 +02:00
}
return true ;
2018-04-03 18:03:59 +02:00
}
2018-04-11 22:12:57 +02:00
catch ( std : : out_of_range & e ) { // exceeded packet size
2018-04-13 11:02:59 +02:00
cout < < " Query for ' " < < origname < < " '| " < < qtype < < " got truncated " < < endl ;
2018-04-13 15:23:00 +02:00
response . clearRRs ( ) ;
response . dh . aa = 0 ; response . dh . tc = 1 ;
2018-04-11 22:12:57 +02:00
return true ;
}
catch ( std : : exception & e ) {
cout < < " Error processing query: " < < e . what ( ) < < endl ;
return false ;
2018-04-03 18:03:59 +02:00
}
}
2018-04-01 18:31:41 +02:00
2018-04-03 18:03:59 +02:00
void udpThread ( ComboAddress local , Socket * sock , const DNSNode * zones )
{
2018-04-01 18:31:41 +02:00
for ( ; ; ) {
ComboAddress remote ( local ) ;
2018-04-13 13:37:17 +02:00
string message = SRecvfrom ( * sock , 512 , remote ) ;
DNSMessageReader dm ( message ) ;
2018-04-01 18:31:41 +02:00
2018-04-03 13:18:37 +02:00
if ( dm . dh . qr ) {
2018-04-01 18:31:41 +02:00
cerr < < " Dropping non-query from " < < remote . toStringWithPort ( ) < < endl ;
2018-04-03 13:18:37 +02:00
continue ;
2018-04-01 18:31:41 +02:00
}
2018-04-13 15:23:00 +02:00
DNSName qname ;
DNSType qtype ;
dm . getQuestion ( qname , qtype ) ;
DNSMessageWriter response ( qname , qtype ) ;
2018-04-03 18:03:59 +02:00
if ( processQuestion ( * zones , dm , local , remote , response ) ) {
2018-04-08 23:20:11 +02:00
cout < < " Sending response with rcode " < < ( RCode ) response . dh . rcode < < endl ;
2018-04-13 15:23:00 +02:00
string ret = response . serialize ( ) ;
SSendto ( * sock , ret , remote ) ;
2018-04-03 18:03:59 +02:00
}
}
}
2018-04-01 18:31:41 +02:00
2018-04-09 16:43:24 +02:00
void writeTCPResponse ( int sock , const DNSMessageWriter & response )
2018-04-08 23:20:11 +02:00
{
string ser = " 00 " + response . serialize ( ) ;
2018-04-09 20:49:37 +02:00
cout < < " Sending a message of " < < ser . size ( ) < < " bytes in response " < < endl ;
2018-04-08 23:20:11 +02:00
uint16_t len = htons ( ser . length ( ) - 2 ) ;
ser [ 0 ] = * ( ( char * ) & len ) ;
ser [ 1 ] = * ( ( ( char * ) & len ) + 1 ) ;
SWriten ( sock , ser ) ;
}
2018-04-03 18:03:59 +02:00
void tcpClientThread ( ComboAddress local , ComboAddress remote , int s , const DNSNode * zones )
{
Socket sock ( s ) ;
cout < < " TCP Connection from " < < remote . toStringWithPort ( ) < < endl ;
for ( ; ; ) {
uint16_t len ;
string message = SRead ( sock , 2 ) ;
if ( message . size ( ) ! = 2 )
break ;
memcpy ( & len , & message . at ( 1 ) - 1 , 2 ) ;
len = htons ( len ) ;
if ( len > 512 ) {
cerr < < " Remote " < < remote . toStringWithPort ( ) < < " sent question that was too big " < < endl ;
return ;
2018-04-03 13:18:37 +02:00
}
2018-04-03 18:03:59 +02:00
if ( len < sizeof ( dnsheader ) ) {
cerr < < " Dropping query from " < < remote . toStringWithPort ( ) < < " , too short " < < endl ;
return ;
2018-04-03 13:18:37 +02:00
}
2018-04-03 18:03:59 +02:00
message = SRead ( sock , len ) ;
2018-04-13 13:37:17 +02:00
DNSMessageReader dm ( message ) ;
2018-04-03 18:03:59 +02:00
if ( dm . dh . qr ) {
cerr < < " Dropping non-query from " < < remote . toStringWithPort ( ) < < endl ;
return ;
}
2018-04-12 16:31:43 +02:00
DNSName name ;
2018-04-03 18:03:59 +02:00
DNSType type ;
dm . getQuestion ( name , type ) ;
2018-04-13 15:23:00 +02:00
DNSMessageWriter response ( name , type , std : : numeric_limits < uint16_t > : : max ( ) ) ;
2018-04-08 23:20:11 +02:00
2018-04-03 18:03:59 +02:00
if ( type = = DNSType : : AXFR ) {
cout < < " Should do AXFR for " < < name < < endl ;
2018-04-08 23:20:11 +02:00
2018-04-13 15:23:00 +02:00
response . dh . id = dm . dh . id ;
2018-04-13 13:37:17 +02:00
response . dh . ad = response . dh . ra = response . dh . aa = 0 ;
response . dh . qr = 1 ;
2018-04-12 16:31:43 +02:00
DNSName zone ;
2018-04-08 23:20:11 +02:00
auto fnd = zones - > find ( name , zone ) ;
2018-04-09 23:04:13 +02:00
if ( ! fnd | | ! fnd - > zone | | ! name . empty ( ) | | ! fnd - > zone - > rrsets . count ( DNSType : : SOA ) ) {
cout < < " This was not a zone, or zone had no SOA " < < endl ;
2018-04-13 13:37:17 +02:00
response . dh . rcode = ( int ) RCode : : Refused ;
writeTCPResponse ( sock , response ) ;
continue ;
2018-04-08 23:20:11 +02:00
}
cout < < " Have zone, walking it " < < endl ;
auto node = fnd - > zone ;
// send SOA
response . putRR ( DNSSection : : Answer , zone , DNSType : : SOA , node - > rrsets [ DNSType : : SOA ] . ttl , node - > rrsets [ DNSType : : SOA ] . contents [ 0 ] ) ;
writeTCPResponse ( sock , response ) ;
2018-04-13 15:23:00 +02:00
response . clearRRs ( ) ;
2018-04-08 23:20:11 +02:00
// send all other records
2018-04-12 16:31:43 +02:00
node - > visit ( [ & response , & sock , & name , & type , & zone ] ( const DNSName & nname , const DNSNode * n ) {
2018-04-08 23:20:11 +02:00
for ( const auto & p : n - > rrsets ) {
if ( p . first = = DNSType : : SOA )
continue ;
for ( const auto & rr : p . second . contents ) {
retry :
try {
response . putRR ( DNSSection : : Answer , nname , p . first , p . second . ttl , rr ) ;
}
2018-04-11 22:12:57 +02:00
catch ( std : : out_of_range & e ) { // exceeded packet size
2018-04-08 23:20:11 +02:00
writeTCPResponse ( sock , response ) ;
2018-04-13 15:23:00 +02:00
response . clearRRs ( ) ;
2018-04-08 23:20:11 +02:00
goto retry ;
}
}
}
} , zone ) ;
writeTCPResponse ( sock , response ) ;
2018-04-13 15:23:00 +02:00
response . clearRRs ( ) ;
2018-04-08 23:20:11 +02:00
// send SOA again
response . putRR ( DNSSection : : Answer , zone , DNSType : : SOA , node - > rrsets [ DNSType : : SOA ] . ttl , node - > rrsets [ DNSType : : SOA ] . contents [ 0 ] ) ;
writeTCPResponse ( sock , response ) ;
2018-04-03 18:03:59 +02:00
return ;
2018-04-01 18:31:41 +02:00
}
else {
2018-04-03 18:03:59 +02:00
if ( processQuestion ( * zones , dm , local , remote , response ) ) {
2018-04-08 23:20:11 +02:00
writeTCPResponse ( sock , response ) ;
2018-04-03 18:03:59 +02:00
}
else
return ;
2018-04-01 18:31:41 +02:00
}
}
2018-04-03 13:18:37 +02:00
}
int main ( int argc , char * * argv )
2018-04-09 23:04:13 +02:00
try
2018-04-03 13:18:37 +02:00
{
2018-04-09 23:04:13 +02:00
if ( argc ! = 2 ) {
cerr < < " Syntax: tdns ipaddress:port " < < endl ;
return ( EXIT_FAILURE ) ;
}
2018-04-09 12:54:27 +02:00
signal ( SIGPIPE , SIG_IGN ) ;
2018-04-03 13:18:37 +02:00
ComboAddress local ( argv [ 1 ] , 53 ) ;
2018-04-03 18:03:59 +02:00
Socket udplistener ( local . sin4 . sin_family , SOCK_DGRAM ) ;
SBind ( udplistener , local ) ;
Socket tcplistener ( local . sin4 . sin_family , SOCK_STREAM ) ;
SSetsockopt ( tcplistener , SOL_SOCKET , SO_REUSEPORT , 1 ) ;
SBind ( tcplistener , local ) ;
SListen ( tcplistener , 10 ) ;
2018-04-03 13:18:37 +02:00
DNSNode zones ;
loadZones ( zones ) ;
2018-04-03 18:03:59 +02:00
thread udpServer ( udpThread , local , & udplistener , & zones ) ;
2018-04-03 13:18:37 +02:00
2018-04-03 18:03:59 +02:00
for ( ; ; ) {
2018-04-13 13:39:59 +02:00
ComboAddress remote ( local ) ; // so it has room for IPv6
2018-04-03 18:03:59 +02:00
int client = SAccept ( tcplistener , remote ) ;
thread t ( tcpClientThread , local , remote , client , & zones ) ;
t . detach ( ) ;
}
2018-04-01 18:31:41 +02:00
}
2018-04-09 23:04:13 +02:00
catch ( std : : exception & e )
{
cerr < < " Fatal error: " < < e . what ( ) < < endl ;
return EXIT_FAILURE ;
}