DNSへの問い合わせをPHPで受け取ってみる

PHPを実行してUDPの53ポートでソケット待ち受け開始。
PHP実行中のマシンをDNSサーバに設定して、PINGとかをどこかに送ってみる。
初めてUDPやったけど、やっぱりUDPTCPより簡単だった。
リクエストを受け取った瞬間に接続が絶たれるから同時接続云々考えなくてもいいみたい。
もっとリクエスト数が多かったら違うのかも?
とりあえず、今回はリクエストをパースするところまで。
 

PHPソース

<?php
// init
set_time_limit(0);

class SocketServerUDP{
    // server settings
    public $address = '0.0.0.0';
    public $port = 53;
    public $read_length = 512;

    // resource
    private $_sock;

    public function __construct()
    {
    }

    public function create()
    {
        // create
        if (($this->_sock = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) === false) {
            throw new Exception("socket_create() failed: ".socket_strerror($this->_sock));
        }
        // bind
        if (@socket_bind($this->_sock, $this->address, $this->port) === false) {
            throw new Exception("socket_bind() failed: ".socket_strerror(socket_last_error($this->_sock)));
        }
        return true;
    }

    public function run()
    {
        while (true) {
            // recvfrom
            $from = '';
            $port = 0;
            if (($msgsock = socket_recvfrom($this->_sock, $buf, $this->read_length, 0, $from, $port)) === false) {
                echo "socket_recvfrom() failed: ".socket_strerror(socket_last_error($this->_sock));
                break;
            }
            $filename = dirname(__FILE__).'/dump/'.date('Ymd-His-').preg_replace('/ .+/', '', microtime());
            file_put_contents($filename, $buf);
            echo "FROM : ".$from."\n";
            echo "PORT : ".$port."\n";
            $dns = new DNS();
            print_r($dns->parseRequest($buf));
        }
    }
}

class DNS
{
    private $_data;

    private $_type = array(
        '1' => 'A',
        '2' => 'NS',
        '5' => 'CNAME',
        '6' => 'SOA',
        '12' => 'PTR',
        '15' => 'MX'
    );

    private $_class = array(
        '1' => 'IN'
    );

    public function parseRequest($request)
    {
        $head = unpack('n*', substr($request, 0, 12));
        $body = substr($request, 12);
        // result
        $data = array(
            'head' => array(
                'id' => $head[1],
                'flag' => array(
                    'qr' => ($head[2] & 32768)>>15,
                    'opcode' => ($head[2] & 30720)>>11,
                    'aa' => ($head[2] & 1024)>>10,
                    'tc' => ($head[2] & 512)>>9,
                    'rd' => ($head[2] & 256)>>8,
                    'ra' => ($head[2] & 128)>>7,
                    'z' => ($head[2] & 112)>>4,
                    'rcode' => ($head[2] & 15)
                ),
                'qdcount' => $head[3],
                'ancount' => $head[4],
                'nscount' => $head[5],
                'arcount' => $head[6]
            ),
            'question' => array()
        );

        $body_arr = array_values(unpack('C*', $body));
        $i = 0;
        for ($j=0; $j<$data['head']['qdcount']; $j++) {
            $question = array(
                'qname' => '',
                'qtype' => 0,
                'qclass' => 0

            );
            while (true) {
                if (!isset($body_arr[$i]) || $body_arr[$i]==0) {
                    break;
                }
                list(,$len) = unpack('h', $body_arr[$i]);
                $question['qname'] .= substr($body, $i+1, $len).'.';
                $i += $len + 1;
            }
            list(,$question['qtype']) = unpack('v', substr($body, $i, 2));
            $i += 2;
            list(,$question['qclass']) = unpack('v', substr($body, $i, 2));
            $i += 2;
            $data['question'][] = $question;
        }
        $this->_data = $data;
        return $data;
    }
}

$ss = new SocketServerUDP();
try {
    $ss->create();
    $ss->run();
} catch(Exception $e) {
    echo $e;
}

 

正引きリクエストをパース

FROM : 127.0.0.1
PORT : 32786
Array
(
    [head] => Array
        (
            [id] => 37150
            [flag] => Array
                (
                    [qr] => 0
                    [opcode] => 0
                    [aa] => 0
                    [tc] => 0
                    [rd] => 1
                    [ra] => 0
                    [z] => 0
                    [rcode] => 0
                )

            [qdcount] => 1
            [ancount] => 0
            [nscount] => 0
            [arcount] => 0
        )

    [question] => Array
        (
            [0] => Array
                (
                    [qname] => google.co.jp.
                    [qtype] => 0
                    [qclass] => 1
                )

        )

)

 

逆引きリクエストをパース

FROM : 127.0.0.1
PORT : 32786
Array
(
    [head] => Array
        (
            [id] => 51892
            [flag] => Array
                (
                    [qr] => 0
                    [opcode] => 0
                    [aa] => 0
                    [tc] => 0
                    [rd] => 1
                    [ra] => 0
                    [z] => 0
                    [rcode] => 0
                )

            [qdcount] => 1
            [ancount] => 0
            [nscount] => 0
            [arcount] => 0
        )

    [question] => Array
        (
            [0] => Array
                (
                    [qname] => 50.0.168.192.in-addr.arpa.
                    [qtype] => 0
                    [qclass] => 12
                )

        )

)

 

参考URL

3 Minutes Networking No.66
http://www5e.biglobe.ne.jp/~aji/3min/66.html
 
DNSの例 (実際のパケットダンプを使った説明)
http://www.db.is.kyushu-u.ac.jp/rinkou/unixnet/f-88.files/frame.htm
 
名前をIPアドレスに変換する---DNS・その2(第54回):TCP/IP再入門
http://pc.nikkeibp.co.jp/article/knowhow/20080825/1007323/?P=2
 
flashのsocketでDNSにリクエスト(1) - あすのかぜ
http://d.hatena.ne.jp/ASnoKaze/20091111/1257998451
 
RFC1035 ドメイン名−実装と仕様書
http://www5d.biglobe.ne.jp/~stssk/rfc/rfc1035j.html