2023-04-22 11:30:30 +00:00
< ? php
namespace App\Classes\Protocol ;
use Illuminate\Support\Collection ;
use Illuminate\Support\Facades\Log ;
2023-04-23 13:08:30 +00:00
use App\Classes\Protocol as BaseProtocol ;
use App\Classes\Sock\SocketClient ;
2023-04-22 11:30:30 +00:00
use App\Http\Controllers\DomainController ;
2023-04-23 13:08:30 +00:00
use App\Models\Address ;
2023-04-22 11:30:30 +00:00
2023-04-23 13:08:30 +00:00
final class DNS extends BaseProtocol
2023-04-22 11:30:30 +00:00
{
private const LOGKEY = 'PD-' ;
2023-04-23 13:08:30 +00:00
private BaseProtocol\DNS\Query $query ;
2023-04-22 11:30:30 +00:00
// DNS Response codes
public const DNS_NOERROR = 0 ; // No error
public const DNS_FORMERR = 1 ; // Format Error - The dns server could not read / understand the query
public const DNS_SERVFAIL = 2 ; // Server Failure - There was a dns error with the dns server
public const DNS_NAMEERR = 3 ; // Name Error - This specifies that the domain name in the query does not exist, it is only valid from an authoritative server
public const DNS_NOTIMPLEMENTED = 4 ; // Not implemented - The requested query is not supported by the dns server
public const DNS_REFUSED = 5 ; // Refused - The dns server refuses to process the dns query
// DNS Query Classes
public const DNS_QUERY_IN = 1 ; // Internet (this is the main one that is used)
// DNS Query Types
public const DNS_TYPE_A = 1 ; // A Records
public const DNS_TYPE_NS = 2 ; // NS Records
public const DNS_TYPE_CNAME = 5 ; // CNAME Records
public const DNS_TYPE_SOA = 6 ; // SOA Records
public const DNS_TYPE_MX = 15 ; // MX Records
public const DNS_TYPE_TXT = 16 ; // TXT Records
public const DNS_TYPE_AAAA = 28 ; // AAAA Records
2023-04-23 13:08:30 +00:00
/**
* Split a domain into a DNS domain string
*
* @ param string $domain
* @ return string
*/
private function domain_split ( string $domain ) : string
{
$a = '' ;
foreach ( explode ( '.' , $domain ) as $item )
$a .= pack ( 'C' , strlen ( $item )) . $item ;
return $a ;
}
public function onConnect ( SocketClient $client ) : ? int
{
// If our parent returns a PID, we've forked
if ( ! parent :: onConnect ( $client )) {
Log :: withContext ([ 'pid' => getmypid ()]);
$this -> setClient ( $client );
$this -> protocol_session ();
Log :: info ( sprintf ( '%s:= onConnect - Connection closed [%s]' , self :: LOGKEY , $client -> address_remote ));
exit ( 0 );
}
return NULL ;
}
protected function protocol_init () : int
{
// N/A
return 0 ;
}
2023-04-22 11:30:30 +00:00
/**
* Handle a DNS query
*
* https :// www . ietf . org / rfc / rfc1035 . txt
* https :// github . com / guyinatuxedo / dns - fuzzer / blob / master / dns . md
*
* labels 63 octets or less
* names 255 octets or less
* TTL positive values of a signed 32 bit number .
* UDP messages 512 octets or less
*
2023-04-23 13:08:30 +00:00
* @ return int
* @ throws \Exception
*
* dig + noedns - t CNAME mail . dcml . au @ 1.1 . 1.1
*
* ; <<>> DiG 9.10 . 6 <<>> + noedns - t CNAME mail . dcml . au @ 1.1 . 1.1
* ;; global options : + cmd
* ;; Got answer :
* ;; ->> HEADER <<- opcode : QUERY , status : NOERROR , id : 6473
* ;; flags : qr rd ra ad ; QUERY : 1 , ANSWER : 1 , AUTHORITY : 0 , ADDITIONAL : 0
*
* ;; QUESTION SECTION :
* ; mail . dcml . au . IN CNAME
2023-04-22 11:30:30 +00:00
*
2023-04-23 13:08:30 +00:00
* ;; ANSWER SECTION :
* mail . dcml . au . 300 IN CNAME l . dege . au .
*
* ;; Query time : 43 msec
* ;; SERVER : 1.1 . 1.1 #53(1.1.1.1)
* ;; WHEN : Thu Apr 20 06 : 47 : 55 PST 2023
* ;; MSG SIZE rcvd : 51
* 06 : 47 : 54.995929 IP 192.168 . 130.184 . 51502 > one . one . one . one . domain : 6473 + CNAME ? mail . dcml . au . ( 30 )
* 0x0000 : cc03 d9cc 88 cb bcd0 7414 d055 0800 4500 ........ t .. U .. E .
* 0x0010 : 003 a eee2 0000 4011 466 e c0a8 82 b8 0101 .:....@. Fn ......
* 0x0020 : 0101 c92e 0035 0026 bb5f | 1949 0120 0001 ..... 5. &. _ . I ....
* 0x0030 : 0000 0000 0000 046 d 6169 6 c04 6463 6 d6c ....... mail . dcml
* 0x0040 : 0261 7500 0005 0001 . au .....
* 06 : 47 : 55.034171 IP one . one . one . one . domain > 192.168 . 130.184 . 51502 : 6473 $ 1 / 0 / 0 CNAME l . dege . au . ( 51 )
* 0x0000 : bcd0 7414 d055 cc03 d9cc 88 cb 0800 4588 .. t .. U ........ E .
* 0x0010 : 004 f 514 a 4000 3 a11 a969 0101 0101 c0a8 . OQJ @.:.. i ......
* 0x0020 : 82 b8 0035 c92e 003 b 9274 | 1949 81 a0 0001 ... 5. .. ; . t . I ....
* 0x0030 : 0001 0000 0000 046 d 6169 6 c04 6463 6 d6c ....... mail . dcml
* 0x0040 : 0261 7500 0005 0001 c00c 0005 0001 0000 . au .............
* 0x0050 : 012 c 000 9 016 c 0464 6567 65 c0 16 . , ... l . dege ..
2023-04-22 11:30:30 +00:00
*/
2023-04-23 13:08:30 +00:00
public function protocol_session () : int
2023-04-22 11:30:30 +00:00
{
Log :: debug ( sprintf ( '%s:+ DNS Query' , self :: LOGKEY ));
2023-04-23 13:08:30 +00:00
$this -> query = new BaseProtocol\DNS\Query ( $this -> client -> read ( 0 , 512 ));
2023-04-22 11:30:30 +00:00
// If there is no query count, then its an error
2023-04-23 13:08:30 +00:00
if ( $this -> query -> qdcount !== 1 ) {
Log :: error ( sprintf ( '%s:! DNS query doesnt have the right number of queries [%d]' , self :: LOGKEY , $this -> query -> qdcount ));
return $this -> reply ( self :: DNS_FORMERR );
2023-04-22 11:30:30 +00:00
}
// We need a minimum of f.n.z.d.root
2023-04-23 13:08:30 +00:00
if ( $this -> query -> labels -> count () < 5 )
return $this -> nameerr ( $this -> query -> labels );
2023-04-22 11:30:30 +00:00
2023-04-23 13:08:30 +00:00
$labels = clone ( $this -> query -> labels );
2023-04-22 11:30:30 +00:00
// First check that it is a query we can answer
// First label should be p.. or f..
if ( ! is_null ( $p = $this -> parse ( 'p' , $labels -> first ())))
$labels -> shift ();
if ( is_null ( $f = $this -> parse ( 'f' , $labels -> shift ())))
return $this -> nameerr ( $labels );
if ( is_null ( $n = $this -> parse ( 'n' , $labels -> shift ())))
return $this -> nameerr ( $labels );
if ( is_null ( $z = $this -> parse ( 'z' , $labels -> shift ())))
return $this -> nameerr ( $labels );
if ( is_null ( $d = $labels -> shift ()))
return $this -> nameerr ( $labels );
// Make sure we have a root/base domain
if ( ! $labels -> count ())
return $this -> nameerr ( $labels );
$rootdn = $labels -> join ( '.' );
$ao = Address :: findFTN ( sprintf ( '%d:%d/%d.%d@%s' , $z , $n , $f , $p , $d ));
// Check we have the right record
if (( ! $ao ) || ( ! $ao -> system -> mailer_address ) || (( $rootdn !== 'ftn' ) && (( ! $ao -> zone -> domain -> dnsdomain ) || ( $ao -> zone -> domain -> dnsdomain !== $d . '.' . $rootdn ))))
return $this -> nameerr ( $labels );
// If the wrong class
2023-04-23 13:08:30 +00:00
if ( $this -> query -> class !== self :: DNS_QUERY_IN ) {
Log :: error ( sprintf ( '%s:! We only service Internet queries [%d]' , self :: LOGKEY , $this -> query -> class ));
return $this -> reply ( self :: DNS_NOTIMPLEMENTED );
2023-04-22 11:30:30 +00:00
}
// Check the class
2023-04-23 13:08:30 +00:00
switch ( $this -> query -> type ) {
2023-04-22 11:30:30 +00:00
case self :: DNS_TYPE_CNAME :
case self :: DNS_TYPE_A :
case self :: DNS_TYPE_AAAA :
Log :: debug ( sprintf ( '%s:= Returning [%s] for DNS query [%s]' , self :: LOGKEY , $ao -> system -> mailer_address , $ao -> ftn ));
$this -> reply (
self :: DNS_NOERROR ,
2023-04-23 13:08:30 +00:00
$this -> query -> domain ,
[ $this -> domain_split ( $ao -> system -> mailer_address ) => self :: DNS_TYPE_CNAME ]);
2023-04-22 11:30:30 +00:00
break ;
default :
2023-04-23 13:08:30 +00:00
Log :: error ( sprintf ( '%s:! We dont support DNS query types [%d]' , self :: LOGKEY , $this -> query -> type ));
2023-04-22 11:30:30 +00:00
$this -> reply ( self :: DNS_NOTIMPLEMENTED );
}
2023-04-23 13:08:30 +00:00
return self :: DNS_NOERROR ;
2023-04-22 11:30:30 +00:00
}
/**
* Return a compression string for a specific offset
*
* @ param int $offset
* @ return string
*/
private function compress ( int $offset ) : string
{
return pack ( 'n' , $offset | ( 3 << 14 ));
}
2023-04-23 13:08:30 +00:00
private function nameerr ( Collection $labels ) : int
2023-04-22 11:30:30 +00:00
{
Log :: error ( sprintf ( '%s:! DNS query for a resource we dont manage [%s]' , self :: LOGKEY , $labels -> join ( '.' )));
2023-04-23 13:08:30 +00:00
return $this -> reply ( self :: DNS_NAMEERR );
2023-04-22 11:30:30 +00:00
}
/**
* Parse a label for a fido address nibble
*
* @ param string $prefix
* @ param string $label
* @ return string | null
*/
private function parse ( string $prefix , string $label ) : ? string
{
$m = [];
return ( preg_match ( '/^' . $prefix . '([0-9]+)+/' , $label , $m ) && ( $m [ 1 ] <= DomainController :: NUMBER_MAX ))
? $m [ 1 ]
: NULL ;
}
/**
* Return a DNS response
*
* @ param int $code
* @ param string $question
* @ param array $answer
* @ return bool
2023-04-23 13:08:30 +00:00
* @ throws \Exception
2023-04-22 11:30:30 +00:00
*/
private function reply ( int $code , string $question = '' , array $answer = []) : bool
{
$header = ( 1 << 15 ); // 1b: Query/Response
$header |= ( 0 << 11 ); // 4b: Opcode
$header |= ( 0 << 10 ); // 1b: Authoritative Answer
$header |= ( 0 << 9 ); // 1b: Truncated
$header |= ( 0 << 8 ); // 1b: Recursion Desired (in queries)
$header |= ( 0 << 7 ); // 1b: Recursion Available (in responses)
$header |= ( 0 << 4 ); // 3b: Zero (future, should be zero)
$header |= $code ; // 4b: Result Code
$q = $question ? 1 : 0 ;
$r = count ( $answer );
$nscount = 0 ;
$arcount = 0 ;
2023-04-23 13:08:30 +00:00
$reply = pack ( 'nnnnnn' , $this -> query -> id , $header , $q , $r , $nscount , $arcount );
2023-04-22 11:30:30 +00:00
// Return the answer
if ( $r ) {
// Question
$reply .= $question ;
// @todo In the case we return a CNAME and an A record, this should reference the CNAME domain when returning the A record
foreach ( $answer as $item => $type )
2023-04-23 13:08:30 +00:00
$reply .= $this -> rr ( $this -> compress ( 12 ), $item , $type , 300 );
2023-04-22 11:30:30 +00:00
} else {
$reply .= $question ;
}
// nscount
//$reply .= $this->rr($this->domain_split('net1.fsxnet.nz'),["a","root-servers","net"],self::DNS_TYPE_NS,300);
2023-04-23 13:08:30 +00:00
if ( ! $this -> client -> send ( $reply , 0 )) {
2023-04-22 11:30:30 +00:00
Log :: error ( sprintf ( '%s:! Error [%s] sending DNS reply to [%s:%d]' ,
self :: LOGKEY ,
socket_strerror ( socket_last_error ()),
2023-04-23 13:08:30 +00:00
$this -> client -> address_remote ,
$this -> client -> port_remote
2023-04-22 11:30:30 +00:00
));
return FALSE ;
}
return TRUE ;
}
/**
* Return a DNS Resource Record
*
* @ param string $query - Domain in the query
* @ param mixed $ars - Answer resources
* @ param int $type - Resource type
* @ param int $ttl - Time to live
* @ return string
*/
2023-04-23 13:08:30 +00:00
private function rr ( string $query , string $ars , int $type , int $ttl ) : string
2023-04-22 11:30:30 +00:00
{
// Reference the domain query in the question
$reply = $query ;
// Record Type
$reply .= pack ( 'n' , $type );
// Internet
$reply .= pack ( 'n' , self :: DNS_QUERY_IN );
$reply .= pack ( 'n' , 0 );
// TTL
$reply .= pack ( 'n' , $ttl );
// Answer
$a = '' ;
switch ( $type ) {
case self :: DNS_TYPE_CNAME :
case self :: DNS_TYPE_NS :
2023-04-23 13:08:30 +00:00
$a = $ars . " \x00 " ;
2023-04-22 11:30:30 +00:00
break ;
case self :: DNS_TYPE_A :
case self :: DNS_TYPE_AAAA :
2023-04-23 13:08:30 +00:00
$a = $ars ;
2023-04-22 11:30:30 +00:00
}
$reply .= pack ( 'n' , strlen ( $a )) . $a ;
return $reply ;
}
}