Update SocketClient to support UDP. Change DNS queries to use SocketClient
This commit is contained in:
parent
073be20ceb
commit
b1c62ae227
@ -383,7 +383,7 @@ abstract class Protocol
|
|||||||
*
|
*
|
||||||
* @param SocketClient $client
|
* @param SocketClient $client
|
||||||
*/
|
*/
|
||||||
private function setClient(SocketClient $client): void
|
protected function setClient(SocketClient $client): void
|
||||||
{
|
{
|
||||||
$this->client = $client;
|
$this->client = $client;
|
||||||
}
|
}
|
||||||
|
@ -2,22 +2,19 @@
|
|||||||
|
|
||||||
namespace App\Classes\Protocol;
|
namespace App\Classes\Protocol;
|
||||||
|
|
||||||
use App\Models\Address;
|
|
||||||
use App\Models\Domain;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
use App\Classes\Protocol as BaseProtocol;
|
||||||
|
use App\Classes\Sock\SocketClient;
|
||||||
use App\Http\Controllers\DomainController;
|
use App\Http\Controllers\DomainController;
|
||||||
|
use App\Models\Address;
|
||||||
|
|
||||||
final class DNS
|
final class DNS extends BaseProtocol
|
||||||
{
|
{
|
||||||
private const LOGKEY = 'PD-';
|
private const LOGKEY = 'PD-';
|
||||||
|
|
||||||
private string $rx_buf;
|
private BaseProtocol\DNS\Query $query;
|
||||||
private array $header;
|
|
||||||
private array $remote;
|
|
||||||
private int $rx_ptr;
|
|
||||||
private \Socket $socket;
|
|
||||||
|
|
||||||
// DNS Response codes
|
// DNS Response codes
|
||||||
public const DNS_NOERROR = 0; // No error
|
public const DNS_NOERROR = 0; // No error
|
||||||
@ -40,15 +37,43 @@ final class DNS
|
|||||||
|
|
||||||
public const DNS_TYPE_AAAA = 28; // AAAA Records
|
public const DNS_TYPE_AAAA = 28; // AAAA Records
|
||||||
|
|
||||||
// https://github.com/guyinatuxedo/dns-fuzzer/blob/master/dns.md
|
/**
|
||||||
private const header = [ // Struct of a DNS query
|
* Split a domain into a DNS domain string
|
||||||
'id' => [0x00,'n',1], // ID
|
*
|
||||||
'header' => [0x01,'n',1], // Header
|
* @param string $domain
|
||||||
'qdcount' => [0x02,'n',1], // Entries in the question
|
* @return string
|
||||||
'ancount' => [0x03,'n',1], // Resource Records in the answer
|
*/
|
||||||
'nscount' => [0x04,'n',1], // Server Resource Records in the answer
|
private function domain_split(string $domain): string
|
||||||
'arcount' => [0x05,'n',1], // Resource Records in the addition records section
|
{
|
||||||
];
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a DNS query
|
* Handle a DNS query
|
||||||
@ -61,79 +86,58 @@ final class DNS
|
|||||||
* TTL positive values of a signed 32 bit number.
|
* TTL positive values of a signed 32 bit number.
|
||||||
* UDP messages 512 octets or less
|
* UDP messages 512 octets or less
|
||||||
*
|
*
|
||||||
* @param array $remote
|
* @return int
|
||||||
* @param string $buf
|
* @throws \Exception
|
||||||
* @param \Socket $socket
|
|
||||||
* @return void
|
|
||||||
*
|
*
|
||||||
dig +noedns -t CNAME mail.dcml.au @1.1.1.1
|
* 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
|
* ; <<>> DiG 9.10.6 <<>> +noedns -t CNAME mail.dcml.au @1.1.1.1
|
||||||
;; global options: +cmd
|
* ;; global options: +cmd
|
||||||
;; Got answer:
|
* ;; Got answer:
|
||||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6473
|
* ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6473
|
||||||
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
|
* ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
|
||||||
|
*
|
||||||
;; QUESTION SECTION:
|
* ;; QUESTION SECTION:
|
||||||
;mail.dcml.au. IN CNAME
|
* ;mail.dcml.au. IN CNAME
|
||||||
|
*
|
||||||
;; ANSWER SECTION:
|
* ;; ANSWER SECTION:
|
||||||
mail.dcml.au. 300 IN CNAME l.dege.au.
|
* mail.dcml.au. 300 IN CNAME l.dege.au.
|
||||||
|
*
|
||||||
;; Query time: 43 msec
|
* ;; Query time: 43 msec
|
||||||
;; SERVER: 1.1.1.1#53(1.1.1.1)
|
* ;; SERVER: 1.1.1.1#53(1.1.1.1)
|
||||||
;; WHEN: Thu Apr 20 06:47:55 PST 2023
|
* ;; WHEN: Thu Apr 20 06:47:55 PST 2023
|
||||||
;; MSG SIZE rcvd: 51
|
* ;; 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)
|
* 06:47:54.995929 IP 192.168.130.184.51502 > one.one.one.one.domain: 6473+ CNAME? mail.dcml.au. (30)
|
||||||
0x0000: cc03 d9cc 88cb bcd0 7414 d055 0800 4500 ........t..U..E.
|
* 0x0000: cc03 d9cc 88cb bcd0 7414 d055 0800 4500 ........t..U..E.
|
||||||
0x0010: 003a eee2 0000 4011 466e c0a8 82b8 0101 .:....@.Fn......
|
* 0x0010: 003a eee2 0000 4011 466e c0a8 82b8 0101 .:....@.Fn......
|
||||||
0x0020: 0101 c92e 0035 0026 bb5f|1949 0120 0001 .....5.&._.I....
|
* 0x0020: 0101 c92e 0035 0026 bb5f|1949 0120 0001 .....5.&._.I....
|
||||||
0x0030: 0000 0000 0000 046d 6169 6c04 6463 6d6c .......mail.dcml
|
* 0x0030: 0000 0000 0000 046d 6169 6c04 6463 6d6c .......mail.dcml
|
||||||
0x0040: 0261 7500 0005 0001 .au.....
|
* 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)
|
* 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 88cb 0800 4588 ..t..U........E.
|
* 0x0000: bcd0 7414 d055 cc03 d9cc 88cb 0800 4588 ..t..U........E.
|
||||||
0x0010: 004f 514a 4000 3a11 a969 0101 0101 c0a8 .OQJ@.:..i......
|
* 0x0010: 004f 514a 4000 3a11 a969 0101 0101 c0a8 .OQJ@.:..i......
|
||||||
0x0020: 82b8 0035 c92e 003b 9274|1949 81a0 0001 ...5...;.t.I....
|
* 0x0020: 82b8 0035 c92e 003b 9274|1949 81a0 0001 ...5...;.t.I....
|
||||||
0x0030: 0001 0000 0000 046d 6169 6c04 6463 6d6c .......mail.dcml
|
* 0x0030: 0001 0000 0000 046d 6169 6c04 6463 6d6c .......mail.dcml
|
||||||
0x0040: 0261 7500 0005 0001 c00c 0005 0001 0000 .au.............
|
* 0x0040: 0261 7500 0005 0001 c00c 0005 0001 0000 .au.............
|
||||||
0x0050: 012c 0009 016c 0464 6567 65c0 16 .,...l.dege..
|
* 0x0050: 012c 0009 016c 0464 6567 65c0 16 .,...l.dege..
|
||||||
*/
|
*/
|
||||||
public function onConnect(array $remote,string $buf,\Socket $socket)
|
public function protocol_session(): int
|
||||||
{
|
{
|
||||||
Log::debug(sprintf('%s:+ DNS Query',self::LOGKEY));
|
Log::debug(sprintf('%s:+ DNS Query',self::LOGKEY));
|
||||||
$header_len = collect(self::header)->sum(function($item) { return $item[2]*2; });
|
|
||||||
|
|
||||||
$this->rx_buf = $buf;
|
$this->query = new BaseProtocol\DNS\Query($this->client->read(0,512));
|
||||||
$this->rx_ptr = 0;
|
|
||||||
|
|
||||||
// DNS Query header
|
|
||||||
$this->header = unpack(self::unpackheader(self::header),$buf);
|
|
||||||
$this->rx_ptr += $header_len;
|
|
||||||
|
|
||||||
$this->remote = $remote;
|
|
||||||
$this->socket = $socket;
|
|
||||||
|
|
||||||
// If there is no query count, then its an error
|
// If there is no query count, then its an error
|
||||||
if ($this->header['qdcount'] !== 1) {
|
if ($this->query->qdcount !== 1) {
|
||||||
Log::error(sprintf('%s:! DNS query doesnt have the right number of queries [%d]',self::LOGKEY,$this->header['qdcount']));
|
Log::error(sprintf('%s:! DNS query doesnt have the right number of queries [%d]',self::LOGKEY,$this->query->qdcount));
|
||||||
$this->reply(self::DNS_FORMERR);
|
return $this->reply(self::DNS_FORMERR);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the query elements
|
|
||||||
$labels = collect();
|
|
||||||
while (($len=ord($this->read(1))) !== 0x00)
|
|
||||||
$labels->push($this->read($len));
|
|
||||||
|
|
||||||
$this->question = substr($this->rx_buf,$header_len,$this->rx_ptr-$header_len+4);
|
|
||||||
|
|
||||||
// We need a minimum of f.n.z.d.root
|
// We need a minimum of f.n.z.d.root
|
||||||
if ($labels->count() < 5) {
|
if ($this->query->labels->count() < 5)
|
||||||
Log::error(sprintf('%s:! DNS query for a resource we dont manage [%s]',self::LOGKEY,$labels->join('.')));
|
return $this->nameerr($this->query->labels);
|
||||||
$this->reply(self::DNS_NAMEERR);
|
|
||||||
|
|
||||||
return;
|
$labels = clone($this->query->labels);
|
||||||
}
|
|
||||||
|
|
||||||
// First check that it is a query we can answer
|
// First check that it is a query we can answer
|
||||||
// First label should be p.. or f..
|
// First label should be p.. or f..
|
||||||
@ -164,32 +168,30 @@ dig +noedns -t CNAME mail.dcml.au @1.1.1.1
|
|||||||
if ((! $ao) || (! $ao->system->mailer_address) || (($rootdn !== 'ftn') && ((! $ao->zone->domain->dnsdomain) || ($ao->zone->domain->dnsdomain !== $d.'.'.$rootdn))))
|
if ((! $ao) || (! $ao->system->mailer_address) || (($rootdn !== 'ftn') && ((! $ao->zone->domain->dnsdomain) || ($ao->zone->domain->dnsdomain !== $d.'.'.$rootdn))))
|
||||||
return $this->nameerr($labels);
|
return $this->nameerr($labels);
|
||||||
|
|
||||||
// Get the query type/class
|
|
||||||
$result = unpack('ntype/nclass',$x=$this->read(4));
|
|
||||||
|
|
||||||
// If the wrong class
|
// If the wrong class
|
||||||
if ($result['class'] !== self::DNS_QUERY_IN) {
|
if ($this->query->class !== self::DNS_QUERY_IN) {
|
||||||
Log::error(sprintf('%s:! We only service Internet queries [%d]',self::LOGKEY,$result['class']));
|
Log::error(sprintf('%s:! We only service Internet queries [%d]',self::LOGKEY,$this->query->class));
|
||||||
$this->reply(self::DNS_NOTIMPLEMENTED);
|
return $this->reply(self::DNS_NOTIMPLEMENTED);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the class
|
// Check the class
|
||||||
switch ($result['type']) {
|
switch ($this->query->type) {
|
||||||
case self::DNS_TYPE_CNAME:
|
case self::DNS_TYPE_CNAME:
|
||||||
case self::DNS_TYPE_A:
|
case self::DNS_TYPE_A:
|
||||||
case self::DNS_TYPE_AAAA:
|
case self::DNS_TYPE_AAAA:
|
||||||
Log::debug(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->mailer_address,$ao->ftn));
|
Log::debug(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->mailer_address,$ao->ftn));
|
||||||
$this->reply(
|
$this->reply(
|
||||||
self::DNS_NOERROR,
|
self::DNS_NOERROR,
|
||||||
$this->question,
|
$this->query->domain,
|
||||||
[serialize(explode('.',$ao->system->mailer_address)) => self::DNS_TYPE_CNAME]);
|
[$this->domain_split($ao->system->mailer_address) => self::DNS_TYPE_CNAME]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Log::error(sprintf('%s:! We dont support DNS query types [%d]',self::LOGKEY,$result['type']));
|
Log::error(sprintf('%s:! We dont support DNS query types [%d]',self::LOGKEY,$this->query->type));
|
||||||
$this->reply(self::DNS_NOTIMPLEMENTED);
|
$this->reply(self::DNS_NOTIMPLEMENTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return self::DNS_NOERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -203,11 +205,11 @@ dig +noedns -t CNAME mail.dcml.au @1.1.1.1
|
|||||||
return pack('n',$offset | (3 << 14));
|
return pack('n',$offset | (3 << 14));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function nameerr(Collection $labels)
|
private function nameerr(Collection $labels): int
|
||||||
{
|
{
|
||||||
Log::error(sprintf('%s:! DNS query for a resource we dont manage [%s]',self::LOGKEY,$labels->join('.')));
|
Log::error(sprintf('%s:! DNS query for a resource we dont manage [%s]',self::LOGKEY,$labels->join('.')));
|
||||||
|
|
||||||
$this->reply(self::DNS_NAMEERR);
|
return $this->reply(self::DNS_NAMEERR);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -233,6 +235,7 @@ dig +noedns -t CNAME mail.dcml.au @1.1.1.1
|
|||||||
* @param string $question
|
* @param string $question
|
||||||
* @param array $answer
|
* @param array $answer
|
||||||
* @return bool
|
* @return bool
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private function reply(int $code,string $question='',array $answer=[]): bool
|
private function reply(int $code,string $question='',array $answer=[]): bool
|
||||||
{
|
{
|
||||||
@ -250,7 +253,7 @@ dig +noedns -t CNAME mail.dcml.au @1.1.1.1
|
|||||||
$nscount = 0;
|
$nscount = 0;
|
||||||
$arcount = 0;
|
$arcount = 0;
|
||||||
|
|
||||||
$reply = pack('nnnnnn',$this->header['id'],$header,$q,$r,$nscount,$arcount);
|
$reply = pack('nnnnnn',$this->query->id,$header,$q,$r,$nscount,$arcount);
|
||||||
|
|
||||||
// Return the answer
|
// Return the answer
|
||||||
if ($r) {
|
if ($r) {
|
||||||
@ -259,7 +262,7 @@ dig +noedns -t CNAME mail.dcml.au @1.1.1.1
|
|||||||
|
|
||||||
// @todo In the case we return a CNAME and an A record, this should reference the CNAME domain when returning the A record
|
// @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)
|
foreach ($answer as $item => $type)
|
||||||
$reply .= $this->rr($this->compress(12),unserialize($item),$type,300);
|
$reply .= $this->rr($this->compress(12),$item,$type,300);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$reply .= $question;
|
$reply .= $question;
|
||||||
@ -268,12 +271,12 @@ dig +noedns -t CNAME mail.dcml.au @1.1.1.1
|
|||||||
// nscount
|
// nscount
|
||||||
//$reply .= $this->rr($this->domain_split('net1.fsxnet.nz'),["a","root-servers","net"],self::DNS_TYPE_NS,300);
|
//$reply .= $this->rr($this->domain_split('net1.fsxnet.nz'),["a","root-servers","net"],self::DNS_TYPE_NS,300);
|
||||||
|
|
||||||
if (! socket_sendto($this->socket,$reply,strlen($reply),0,(string)$this->remote['ip'],(int)$this->remote['port'])) {
|
if (! $this->client->send($reply,0)) {
|
||||||
Log::error(sprintf('%s:! Error [%s] sending DNS reply to [%s:%d]',
|
Log::error(sprintf('%s:! Error [%s] sending DNS reply to [%s:%d]',
|
||||||
self::LOGKEY,
|
self::LOGKEY,
|
||||||
socket_strerror(socket_last_error()),
|
socket_strerror(socket_last_error()),
|
||||||
$this->remote['ip'],
|
$this->client->address_remote,
|
||||||
$this->remote['port']
|
$this->client->port_remote
|
||||||
));
|
));
|
||||||
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@ -282,30 +285,6 @@ dig +noedns -t CNAME mail.dcml.au @1.1.1.1
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function domain_split(string $domain): string
|
|
||||||
{
|
|
||||||
$a = '';
|
|
||||||
|
|
||||||
foreach (explode('.',$domain) as $item)
|
|
||||||
$a .= pack('C',strlen($item)).$item;
|
|
||||||
|
|
||||||
return $a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read from a rx_buf
|
|
||||||
*
|
|
||||||
* @param int $len
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function read(int $len): string
|
|
||||||
{
|
|
||||||
$result = substr($this->rx_buf,$this->rx_ptr,$len);
|
|
||||||
$this->rx_ptr += $len;
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a DNS Resource Record
|
* Return a DNS Resource Record
|
||||||
*
|
*
|
||||||
@ -315,7 +294,7 @@ dig +noedns -t CNAME mail.dcml.au @1.1.1.1
|
|||||||
* @param int $ttl - Time to live
|
* @param int $ttl - Time to live
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function rr(string $query,mixed $ars,int $type,int $ttl): string
|
private function rr(string $query,string $ars,int $type,int $ttl): string
|
||||||
{
|
{
|
||||||
// Reference the domain query in the question
|
// Reference the domain query in the question
|
||||||
$reply = $query;
|
$reply = $query;
|
||||||
@ -336,36 +315,16 @@ dig +noedns -t CNAME mail.dcml.au @1.1.1.1
|
|||||||
switch ($type) {
|
switch ($type) {
|
||||||
case self::DNS_TYPE_CNAME:
|
case self::DNS_TYPE_CNAME:
|
||||||
case self::DNS_TYPE_NS:
|
case self::DNS_TYPE_NS:
|
||||||
foreach ($ars as $item)
|
$a = $ars."\x00";
|
||||||
$a .= pack('C',strlen($item)).$item;
|
|
||||||
|
|
||||||
$a .= "\x00";
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case self::DNS_TYPE_A:
|
case self::DNS_TYPE_A:
|
||||||
case self::DNS_TYPE_AAAA:
|
case self::DNS_TYPE_AAAA:
|
||||||
$a .= $ars;
|
$a = $ars;
|
||||||
}
|
}
|
||||||
|
|
||||||
$reply .= pack('n',strlen($a)).$a;
|
$reply .= pack('n',strlen($a)).$a;
|
||||||
|
|
||||||
return $reply;
|
return $reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unpack our configured DNS header
|
|
||||||
*
|
|
||||||
* @param array $pack
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected static function unpackheader(array $pack): string
|
|
||||||
{
|
|
||||||
return join('/',
|
|
||||||
collect($pack)
|
|
||||||
->sortBy(function($k,$v) {return $k[0];})
|
|
||||||
->transform(function($k,$v) {return $k[1].$v;})
|
|
||||||
->values()
|
|
||||||
->toArray()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
88
app/Classes/Protocol/DNS/Query.php
Normal file
88
app/Classes/Protocol/DNS/Query.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Classes\Protocol\DNS;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
final class Query
|
||||||
|
{
|
||||||
|
private string $buf;
|
||||||
|
private int $class;
|
||||||
|
private string $domain;
|
||||||
|
private int $id;
|
||||||
|
private int $type;
|
||||||
|
|
||||||
|
private Collection $labels;
|
||||||
|
|
||||||
|
// https://github.com/guyinatuxedo/dns-fuzzer/blob/master/dns.md
|
||||||
|
private const header = [ // Struct of a DNS query
|
||||||
|
'id' => [0x00,'n',1], // ID
|
||||||
|
'header' => [0x01,'n',1], // Header
|
||||||
|
'qdcount' => [0x02,'n',1], // Entries in the question
|
||||||
|
'ancount' => [0x03,'n',1], // Resource Records in the answer
|
||||||
|
'nscount' => [0x04,'n',1], // Server Resource Records in the answer
|
||||||
|
'arcount' => [0x05,'n',1], // Resource Records in the addition records section
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(string $buf) {
|
||||||
|
$this->buf = $buf;
|
||||||
|
$rx_ptr = 0;
|
||||||
|
|
||||||
|
// DNS Query header
|
||||||
|
$header = unpack(self::unpackheader(self::header),$buf);
|
||||||
|
$rx_ptr += $this->header_len();
|
||||||
|
|
||||||
|
$this->id = $header['id'];
|
||||||
|
$this->qdcount = $header['qdcount'];
|
||||||
|
|
||||||
|
// Get the domain elements
|
||||||
|
$this->labels = collect();
|
||||||
|
|
||||||
|
while (($len=ord(substr($this->buf,$rx_ptr++,1))) !== 0x00) {
|
||||||
|
$this->labels->push(substr($this->buf,$rx_ptr,$len));
|
||||||
|
$rx_ptr += $len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the query type/class
|
||||||
|
$result = unpack('ntype/nclass',substr($this->buf,$rx_ptr,4));
|
||||||
|
$rx_ptr += 4;
|
||||||
|
$this->type = $result['type'];
|
||||||
|
$this->class = $result['class'];
|
||||||
|
|
||||||
|
$this->domain = substr($this->buf,$x=$this->header_len(),$rx_ptr-$x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'class':
|
||||||
|
case 'domain':
|
||||||
|
case 'id':
|
||||||
|
case 'labels':
|
||||||
|
case 'qdcount':
|
||||||
|
case 'type':
|
||||||
|
return $this->{$key};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function header_len() {
|
||||||
|
return collect(self::header)->sum(function($item) { return $item[2]*2; });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpack our configured DNS header
|
||||||
|
*
|
||||||
|
* @param array $pack
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function unpackheader(array $pack): string
|
||||||
|
{
|
||||||
|
return join('/',
|
||||||
|
collect($pack)
|
||||||
|
->sortBy(function($k,$v) {return $k[0];})
|
||||||
|
->transform(function($k,$v) {return $k[1].$v;})
|
||||||
|
->values()
|
||||||
|
->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -56,11 +56,13 @@ final class SocketClient {
|
|||||||
private string $rx_buf = '';
|
private string $rx_buf = '';
|
||||||
|
|
||||||
public function __construct (\Socket $connection) {
|
public function __construct (\Socket $connection) {
|
||||||
|
$this->connection = $connection;
|
||||||
|
|
||||||
|
if ($this->type === 'TCP') {
|
||||||
socket_getsockname($connection,$this->address_local,$this->port_local);
|
socket_getsockname($connection,$this->address_local,$this->port_local);
|
||||||
socket_getpeername($connection,$this->address_remote,$this->port_remote);
|
socket_getpeername($connection,$this->address_remote,$this->port_remote);
|
||||||
Log::info(sprintf('%s:+ Connection host [%s] on port [%d]',self::LOGKEY,$this->address_remote,$this->port_remote));
|
Log::info(sprintf('%s:+ Connection host [%s] on port [%d] (%s)',self::LOGKEY,$this->address_remote,$this->port_remote,$this->type));
|
||||||
|
}
|
||||||
$this->connection = $connection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __get($key) {
|
public function __get($key) {
|
||||||
@ -73,6 +75,17 @@ final class SocketClient {
|
|||||||
case 'speed':
|
case 'speed':
|
||||||
return Arr::get($this->session,$key);
|
return Arr::get($this->session,$key);
|
||||||
|
|
||||||
|
case 'type':
|
||||||
|
switch ($x=socket_get_option($this->connection,SOL_SOCKET,SO_TYPE)) {
|
||||||
|
case SOCK_STREAM:
|
||||||
|
return 'TCP';
|
||||||
|
case SOCK_DGRAM:
|
||||||
|
return 'UDP';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return sprintf('UNKNOWN [%d]',$x);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \Exception(sprintf('%s:! Unknown key [%s]:',self::LOGKEY,$key));
|
throw new \Exception(sprintf('%s:! Unknown key [%s]:',self::LOGKEY,$key));
|
||||||
}
|
}
|
||||||
@ -226,6 +239,7 @@ final class SocketClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a client socket
|
* Create a client socket
|
||||||
|
*
|
||||||
* @param string $address
|
* @param string $address
|
||||||
* @param int $port
|
* @param int $port
|
||||||
* @return static
|
* @return static
|
||||||
@ -292,24 +306,43 @@ final class SocketClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Read data from the socket.
|
* Read data from the socket.
|
||||||
* If we only want 1 character, we'll return the ASCII value of the data received
|
|
||||||
*
|
*
|
||||||
* @param int $timeout
|
* @param int $timeout
|
||||||
* @param int $len
|
* @param int $len
|
||||||
* @return int|string
|
* @param int $size
|
||||||
|
* @return string
|
||||||
* @throws SocketException
|
* @throws SocketException
|
||||||
*/
|
*/
|
||||||
public function read(int $timeout,int $len=1024)
|
public function read(int $timeout,int $len=1024,int $size=1024): string
|
||||||
{
|
{
|
||||||
if ($this->DEBUG)
|
if ($this->DEBUG)
|
||||||
Log::debug(sprintf('%s:+ Start [%d] (%d)',self::LOGKEY,$len,$timeout));
|
Log::debug(sprintf('%s:+ Start [%d] (%d)',self::LOGKEY,$len,$timeout));
|
||||||
|
|
||||||
|
// We have data in our buffer
|
||||||
|
if ($this->rx_left >= $len) {
|
||||||
|
$result = substr($this->rx_buf,$this->rx_ptr,$len);
|
||||||
|
$this->rx_ptr += $len;
|
||||||
|
$this->rx_left -= $len;
|
||||||
|
|
||||||
|
if ($this->rx_left === 0) {
|
||||||
|
$this->rx_buf = '';
|
||||||
|
$this->rx_ptr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
if ($timeout AND ($this->hasData($timeout) === 0))
|
if ($timeout AND ($this->hasData($timeout) === 0))
|
||||||
return '';
|
return '';
|
||||||
|
|
||||||
$buf = '';
|
$buf = '';
|
||||||
try {
|
try {
|
||||||
$rc = socket_recv($this->connection,$buf, $len,MSG_DONTWAIT);
|
if ($this->type === 'TCP')
|
||||||
|
$rc = socket_recv($this->connection,$buf, $size,MSG_DONTWAIT);
|
||||||
|
|
||||||
|
else {
|
||||||
|
$rc = socket_recvfrom($this->connection,$buf, $size,MSG_DONTWAIT,$this->address_remote,$this->port_remote);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error(sprintf('%s: - socket_recv Exception [%s]',self::LOGKEY,$e->getMessage()));
|
Log::error(sprintf('%s: - socket_recv Exception [%s]',self::LOGKEY,$e->getMessage()));
|
||||||
@ -320,8 +353,23 @@ final class SocketClient {
|
|||||||
if ($this->DEBUG)
|
if ($this->DEBUG)
|
||||||
Log::debug(sprintf('%s: - Read [%d]',self::LOGKEY,$rc));
|
Log::debug(sprintf('%s: - Read [%d]',self::LOGKEY,$rc));
|
||||||
|
|
||||||
if ($rc === FALSE)
|
if ($rc === FALSE) {
|
||||||
|
// If we have something in the buffer, we'll send it
|
||||||
|
if ($this->rx_left && $this->rx_left < $len) {
|
||||||
|
$return = substr($this->rx_buf,$this->rx_ptr);
|
||||||
|
|
||||||
|
$this->rx_left = 0;
|
||||||
|
$this->rx_ptr = 0;
|
||||||
|
$this->rx_buf = '';
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
throw new SocketException($x=socket_last_error($this->connection),socket_strerror($x));
|
throw new SocketException($x=socket_last_error($this->connection),socket_strerror($x));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->rx_buf .= $buf;
|
||||||
|
$this->rx_left += strlen($buf);
|
||||||
|
|
||||||
// If our buffer is null, see if we have any out of band data.
|
// If our buffer is null, see if we have any out of band data.
|
||||||
// @todo We throw an errorexception when the socket is closed by the remote I think.
|
// @todo We throw an errorexception when the socket is closed by the remote I think.
|
||||||
@ -334,7 +382,7 @@ final class SocketClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return is_null($buf) ? '' : $buf;
|
return $this->read($timeout,$len,$size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -343,7 +391,7 @@ final class SocketClient {
|
|||||||
*
|
*
|
||||||
* @param int $timeout
|
* @param int $timeout
|
||||||
* @return int
|
* @return int
|
||||||
* @throws SocketException
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function read_ch(int $timeout): int
|
public function read_ch(int $timeout): int
|
||||||
{
|
{
|
||||||
@ -410,7 +458,9 @@ final class SocketClient {
|
|||||||
if (is_null($length))
|
if (is_null($length))
|
||||||
$length = strlen($message);
|
$length = strlen($message);
|
||||||
|
|
||||||
return socket_write($this->connection,$message,$length);
|
return ($this->type === 'TCP')
|
||||||
|
? socket_write($this->connection,$message,$length)
|
||||||
|
: socket_sendto($this->connection,$message,$length,0,$this->address_remote,$this->port_remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,14 +138,15 @@ final class SocketServer {
|
|||||||
|
|
||||||
private function loop_udp()
|
private function loop_udp()
|
||||||
{
|
{
|
||||||
$buf = '';
|
|
||||||
$remote = [];
|
|
||||||
$remote['ip'] = NULL;
|
|
||||||
$remote['port'] = NULL;
|
|
||||||
|
|
||||||
while (TRUE) {
|
while (TRUE) {
|
||||||
if (socket_recvfrom($this->server,$buf,512,MSG_WAITALL,$remote['ip'],$remote['port']))
|
$r = new SocketClient($this->server);
|
||||||
$this->handler[0]->{$this->handler[1]}($remote,$buf,$this->server);
|
|
||||||
|
if ($r->hasData(30)) {
|
||||||
|
$this->handler[0]->{$this->handler[1]}($r);
|
||||||
|
|
||||||
|
// Sleep so our thread has a chance to pick up the data from our connection
|
||||||
|
usleep(50000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user