Es gibt schraege Setups, da moechte man doch mal wissen, welcher DNS da letztlich als Resolver genutzt wird.
Beispielsweise wird vom ISP einem Dialuprouter ein DNS zugewiesen und der Router meldet sich selbst als DNS fuer die angeschlossenen Rechner. Herauszufinden, welchen DNS der Router benutzt, kann tricky werden.
Wenn man einen authoritative DNS betreibt, kann man die Anfragen der Resolver sichtbar machen.
Das ganze fuer Leute ohne eigenen DNS als Webservice.
RFC1035 angeschaut. Zunaechst hat mich der Paketaufbau interessiert, da findet sich:
4.1. Format
All communications inside of the domain protocol are carried in a single format called a message. The top level format of message is divided into 5 sections (some of which are empty in certain cases) shown below:
+---------------------+ | Header | +---------------------+ | Question | the question for the name server +---------------------+ | Answer | RRs answering the question +---------------------+ | Authority | RRs pointing toward an authority +---------------------+ | Additional | RRs holding additional information +---------------------+
und
4.1.1. Header section format
The header contains the following fields:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
sowie
4.1.2. Question section format
The question section is used to carry the “question” in most queries, i.e., the parameters that define what is being asked. The section contains QDCOUNT (usually 1) entries, each of the following format:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / QNAME / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QTYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QCLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
und
4.1.3. Resource record format
The answer, authority, and additional sections all share the same format: a variable number of resource records, where the number of records is specified in the corresponding count field in the header. Each resource record has the following format:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / / / NAME / | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TTL | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | RDLENGTH | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| / RDATA / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
straight forward also:
die Frage enthaelt Header mit ID (random), evtl. dem Flag “RD” (recursion desired) und QDCOUNT = 1. Es folgt die Question mit dem Hostnamen, dem Query Type 1 (A-Record) und der Query Class 1 (Internet, da gibt's noch CHAOS und HESIOD, never mind).
Die Antwort enthaelt Header mit ID (aus der Frage), den Flags QR=1 (Antwort), AA=1 (Authoritative Answer), RA=0 (Recursion Available - hier nicht implementiert), QDCOUNT = 1 (da die Frage wiederholt wird) und ANCOUNT=1 (da eine Antwort gegeben wird), die urspruengliche Frage mit deren QTYPE und QCLASS gefolgt von dem Resource-Record, der die Antwort darstellt.
Im Falle einer Anfrage, die nicht beantwortet werden kann, wird der RCODE=4 (Not Implemented) mitgegeben. Alles uebrige wird 0.
Ich hab' das Ganze in perl gemacht, weil a.) kann ich fliessend perl, b.) hab' ich keine Lust auf die Untiefen von Pointern und null-terminierten Strings.
ABER: die Bitfickerei - das ist rettungslos verloren.
Die Bits sind im RFC1035 so notiert:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Bit 0 ist dabei das hoechstwertige Bit (MSB) 0x8000 (2^15) und Bit 15 das geringwertigste (LSB) 0x0001 (2^0).
So einen 16-Bit-Wert bekommt man in perl mit unpack(“n”) in eine Variable.
ABER: fuer einen Bitvektor wie:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
wird es finster. Denn unpack(“b b4”) setzt fuer alle “b” an einer Bytegrenze neu auf. Also sind auch diese 16 Bit am Stueck in eine Variable zu packen und wie schon zu Assembler-Zeiten mit AND zu maskieren und zu shiften. Echt.
Der erste Wurf des dummy-dns.
#!/usr/bin/perl use strict; use Data::Dumper; sub build_rr ($); sub build_name ($); sub decode_flags ($); sub encode_flags ($); $|=1; use IO::Socket::INET; my $sock = IO::Socket::INET->new( LocalPort => 53, Proto => 'udp', ); if ( ! $sock ) { die "can't open socket: $!"; } while (my $s = $sock->recv(my $datagram,1500,my $flags)) { my($port, $binary_ipaddr) = sockaddr_in($sock->peername); my $hishost = gethostbyaddr($binary_ipaddr, AF_INET); print "Client: $hishost \n"; my $query = {}; print unpack("H*",$datagram),"\n"; ( $query->{id }, my $_flags, $query->{qdcount}, $query->{ancount}, $query->{nscount}, $query->{arcount}, ) = unpack(" n n n n n n ",$datagram); print "N: ",pack("n",$_flags)," $_flags\n"; $query = { %$query, %{ decode_flags $_flags } }; print "Query: ",Dumper($query); print "D $datagram\n"; my $qname = substr($datagram,6*2); print "qname: $qname\n"; my @QName = (); my $i = 73; # chars f. IPv6 PTR QNAME: { do { my $qnamepart = unpack("C/a",$qname); if ( $qnamepart eq "" ) { last QNAME; } push @QName, $qnamepart; print "QName: @QName\n"; $qname = substr($qname,length($qnamepart)+1); } while ($i--); } print "qname: $qname\n"; $query->{qname} = [ @QName ]; ( undef,$query->{qtype},$query->{qclass} ) = unpack("C n n",$qname); print "qtype: ",$query->{qtype},"\n"; print "qclass: ",$query->{qclass},"\n"; my $ans = undef; if ( $query->{qr } == 0 and $query->{opcode } == 0 and $query->{aa } == 0 and $query->{tc } == 0 and $query->{rd } == 1 and $query->{ra } == 0 and $query->{z } == 0 and $query->{rcode } == 0 and $query->{qdcount} > 0 and $query->{ancount} == 0 and $query->{nscount} == 0 and $query->{arcount} == 0 and $query->{qtype } == 1 and $query->{qclass } == 1 ) { print "ANSWER HERE\n"; my $rr = build_rr { name => $query->{qname} , # requested name type => 1 , # A / host address class => 1 , # IN / internet ttl => 60 , # 60 seconds rdlength => 4 , # 4 bytes rdata => pack ("C C C C", 212,9,165,35 ), }; $ans = build_packet({ id => $query->{id }, qr => 1 , # response opcode => $query->{opcode }, aa => 1 , # authoritative answer tc => 0 , # no truncation rd => 0 , # ra => 0 , # no recursion available z => 0 , # always 0 rcode => 0 , # no error qdcount => 1 , # 1 query ancount => 1 , # 1 answer RR nscount => 0 , # 0 authoritative NS RR arcount => 0 , # additional RR rr => $rr , #qtype => $query->{qtype }, #qclass => $query->{qclass }, }); } else { # error condition print "ERROR HERE\n"; $ans = build_packet({ id => $query->{id }, qr => 1 , # response opcode => $query->{opcode }, aa => 1 , # authoritative answer tc => 0 , # no truncation rd => 0 , # ra => $query->{ra }, # no recursion available z => 0 , # always 0 rcode => 4 , # not implemented qdcount => 0 , ancount => 0 , nscount => 0 , arcount => 0 , rr => "" , qtype => $query->{qtype }, qclass => $query->{qclass }, }); } $sock->send($ans); } sub build_packet ($) { my ($p) = @_; my $_flags = 0; print Dumper($p); $_flags = encode_flags $p; # $_flags |= $p->{rcode } & 0x000f; $_flags <<= 3; # $_flags |= $p->{z } & 0x0007; $_flags <<= 1; # $_flags |= $p->{ra } & 0x0001; $_flags <<= 1; # $_flags |= $p->{rd } & 0x0001; $_flags <<= 1; # $_flags |= $p->{tc } & 0x0001; $_flags <<= 1; # $_flags |= $p->{aa } & 0x0001; $_flags <<= 4; # $_flags |= $p->{opcode} & 0x000f; $_flags <<= 1; # $_flags |= $p->{qr } & 0x0001; print "BPF: $_flags\n"; print "BPF: ",pack("n",$_flags),"\n"; if ( defined $p->{qtype } and defined $p->{qclass } ) { return pack( "n n n n n n a* n n", $p->{id}, $_flags, $p->{qdcount}, $p->{ancount}, $p->{nscount}, $p->{arcount}, $p->{rr }, $p->{qtype }, $p->{qclass }, ); } elsif ( ! defined $p->{qtype } and ! defined $p->{qclass } ) { return pack( "n n n n n n a*", $p->{id}, $_flags, $p->{qdcount}, $p->{ancount}, $p->{nscount}, $p->{arcount}, $p->{rr }, ); } else { die "build_packet() error"; } } sub build_name ($) { join "",map { chr(length($_)) . $_ } @{$_[0]},""; } sub build_rr ($) { my ($p) = @_; print Dumper($p); # 0x0000: 4500 004b bac5 0000 3c11 be84 d409 a001 E..K....<....... # 0x0010: d409 bd43 0035 bf1a 0037 3c31 8e08 8580 ...C.5...7<1.... # 0x0020: 0001 0001 0000 0000 0377 7777 0663 6974 .........www.cit # 0x0030: 6563 7302 6465 0000 0100 01c0 0c00 0100 ecs.de.......... # 0x0040: 0100 0151 8000 04d4 09a5 23 ...Q......# print join " ", "BRR", "type :",$p->{type }, "class :",$p->{class }, "ttl :",$p->{ttl }, "rdlength:",$p->{rdlength }, "rdata :",$p->{rdata }, "\n"; return pack( "a* n n n n n N n a*", build_name $p->{name}, $p->{type }, $p->{class }, 0xc00c,0x0001,0x0001, # repeat label f. q. section [0] $p->{ttl }, $p->{rdlength }, $p->{rdata }, ); } sub encode_flags ($) { my ($p) = @_; my $_flags = ( $p->{rcode } & 0x000f ) | ( $p->{z } & 0x0007 ) << 4 | ( $p->{ra } & 0x0001 ) << 7 | ( $p->{rd } & 0x0001 ) << 8 | ( $p->{tc } & 0x0001 ) << 9 | ( $p->{aa } & 0x0001 ) << 10 | ( $p->{opcode} & 0x000f ) << 11 | ( $p->{qr } & 0x0001 ) << 15 ; print "N: $_flags\n"; return $_flags; } sub decode_flags ($) { my ($_flags) = @_; my $p = {}; print "N: $_flags\n"; $p->{qr } = ( $_flags & 0x8000 ) >> 15; $p->{opcode} = ( $_flags & 0x7800 ) >> 11; $p->{aa } = ( $_flags & 0x0400 ) >> 10; $p->{tc } = ( $_flags & 0x0200 ) >> 9; $p->{rd } = ( $_flags & 0x0100 ) >> 8; $p->{ra } = ( $_flags & 0x0080 ) >> 7; $p->{z } = ( $_flags & 0x0070 ) >> 4; $p->{rcode } = ( $_flags & 0x000f ) ; print "qr: ",$p->{qr },"\n"; print "opcode: ",$p->{opcode },"\n"; print "aa: ",$p->{aa },"\n"; print "tc: ",$p->{tc },"\n"; print "rd: ",$p->{rd },"\n"; print "ra: ",$p->{ra },"\n"; print "z: ",$p->{z },"\n"; print "rcode: ",$p->{rcode },"\n"; return $p; } __END__ # # ein RR in einer DNS-Anwort nach einem A-RR hat lt. RFC 1035 p.28 das Format: # # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | | # / / # / NAME / # | | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | TYPE | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | CLASS | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | TTL | # | | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | RDLENGTH | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| # / RDATA / # / / # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # # Tatsaechlich kommt folgendes von einem DNS als # Antwort zurueck: # # 0x0000: 4500 004b c844 0000 3c11 b105 d409 a001 E..K.D..<....... # # 0x0010: d409 bd43 0035 ca0a 0037 9d6d 21dc 8580 ...C.5...7.m!... # [0] [1] # 0x0020: 0001 0001 0000 0000 0377 7777 0663 6974 .........www.cit # [2] [3] [4] [5] [-------- 6 ------ # 0x0030: 6563 7302 6465 00 0001 0001 c00c 0001 00 ecs.de.......... # ----- 6 --------] [7] [8] [9] [10] [11 # # 0x0040: 01 00015180 0004 d409a523 ...Q......# # -] [12] [13] # # wobei: # [0] - id (sequence) # [1] - flags = RESPONSE OPCODE=0 AUTHORITATIVE-ANSWER=1 TRUNCATION=0 # RECURSION-DESIRED=1 RECURSION-AVAILABLE=1 Z=0 RCODE=0 # [2] - QDCOUNT=1 # [3] - ANCOUNT=1 # [4] - NSCOUNT=0 # [5] - ARCOUNT=0 # Wiederholung des Query-Records: # [6] - urspruenglich angefragter Hostname # [7] - QTYPE=1 host address # [8] - QCLASS=1 Internet # # Antwort-RR: # [9] - 1 1 0 0 0 0 0 1 1 # [9a] [---- 9b ---] # Label an Offset 12[9b] wiederverwenden[9a], das waere [6] # [10] - TYPE=1 host address # [11] - CLASS=1 Internet # [12] - TTL=86400 # [14] - RLENGTH=4 # [15] - RDATA=d409a523 -> 212.9.165.35 # # 00015180 - serial 86400 # d409a523 - 212.9.165.35 #