User Tools

Site Tools


project:whichismydns

Which is my DNS ?

Problem

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.

Idee

Wenn man einen authoritative DNS betreibt, kann man die Anfragen der Resolver sichtbar machen.

Das ganze fuer Leute ohne eigenen DNS als Webservice.

Plan

  • Auf einem authoritative DNS kann man die Anfragen der Resolver (per querylog oder tcpdump) sichtbar machen.
    • Das Querylog kann ziemlich gross und unhandlich werden, Log-Rotation bringt bei Ablaeufen, die Log-Dateien folgen, so ihre eigenen Probleme mit.
    • tcpdump zerparsen muss vllt. auch nicht sein.
  • Ueberhaupt: ein ganzer Nameserver mit viel mehr Funktionalitaet als hier benoetigt und dann doch nicht mit der, die erforderlich ist?
  • Es sollte doch moeglich sein, fuer den Kleinkram (Antwort auf einen Request) das selbst zu coden. Das koennte einem die Daten direkt so liefern, wie sie benoetigt werden.
  • Damit man den Request zuverlaessig wiederfindet, bietet sich an, den Host-Part als hinreichend eindeutigen Zufallsstring zu formulieren (z.B. 16 Byte von /dev/urandom lesen und in Hex darstellen).
  • Um den Einfluss von DNS-Caching klein zu halten, wird eine TTL von 60s gewaehlt.
  • Der Ablauf:
    • HTTP-RQ auf die Website
    • Die aufgerufene Seite liefert per HTTP-Server-Push zwei Seiten.
    • Die erste zurueckgelieferte Seite enthaelt ein <IMG> in dessen URL ein variabler, eindeutiger Hostname angegeben ist.
    • Der Browser fragt diesen Hostname beim DNS an.
      • da der Name erstmalig auftaucht, wird die komplette Delegationskette traversiert und die Query trifft beim selbst implementierten DNS ein.
      • Der protokolliert die anfragende IP zusammen mit dem angefragten Hostname und
      • liefert die IP des Systemes zurueck, auf dem der Webservice laeuft.
    • Die zweite zurueckgelieferte Seite enthaelt das Ergebnis.
    • Fuer den Prozess ist es egal, ob der DNS was zurueckliefert und ob das <IMG> ausgeliefert wird. Es muss nur der Request der IMG-URL beim DNS ankommen.

DNS

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.

Hassle mit den Bits

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.

Dummy-DNS

Der erste Wurf des dummy-dns.

  • kann A-Records zurueckliefern.
  • erzeugt viel Debug-Output
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
# 
project/whichismydns.txt · Last modified: 2016/03/15 00:11 by 91.89.129.106