Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

How to create DNS packet

by deewanagan (Novice)
on Nov 17, 2008 at 23:52 UTC ( [id://724155]=perlquestion: print w/replies, xml ) Need Help??

deewanagan has asked for the wisdom of the Perl Monks concerning the following question:

Hello everyone! I am trying to create a dns packet, using pack/unpack function. but i encountered a problem, and the problem is that i can't pack the query part of the packet,here is its code:
$hostname=$ARGV[0]; $counter=0; ##formate of each label. for (split(/\./,$hostname)){ $label[$counter++] = length; $label[$counter++] = $_; $lformat .= "C a* "; } print "\n@label\n"; $question = pack($lformat."C n2",@labels,0,1,1);
any one's help is appreciated, thank u

Replies are listed 'Best First'.
Re: How to create DNS packet
by gone2015 (Deacon) on Nov 18, 2008 at 01:06 UTC

    So, a DNS message takes the form:

        +---------------------+
        |        Header       |
        +---------------------+
        |       Question      | the question for the name server
        +---------------------+
        |        Answer       | RRs answering the question
        +---------------------+
        |      Authority      | RRs pointing toward an authority
        +---------------------+
        |      Additional     | RRs holding additional information
        +---------------------+
    
    where the header is:
                                        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                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    
    which is 6 network order 16 bit values (the pack/unpack code for those is 'n'). You're constructing a query, so you'll have at least one Question Section, which takes the form:
                                        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                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    
    where the name is a sequence of labels, each of which is a length byte followed by the label characters, terminated by a zero length byte. You'll want to look at the pack/unpack 'C/a*' and a plain 'C' to terminate.

    You appear to almost have that -- where you append "C a* " to your $lformat you want "C/a*". Or you could try:

    my @labels = split(/\./, $hostname) ; my $n = scalar(@labels) ; my $question = pack("(C/a*)$n C n2", @labels, 0, 1, 1) ;

    For some reason the DNS RFC numbers the bits with '0' being the MS bit (!).

    Unpacking the responses is moderately straightforward, once you get over the name handling. Watch out for the message compression stuff -- the length byte in front of a label can only have a value in the range 0x00..0x3F, any length byte >= 0xC0 is the first byte of a two byte offset, pointing somewhere else in the packet for the rest of the name -- note that this is a "jump" not a "call".

    It's a lot of fun !

Re: How to create DNS packet
by almut (Canon) on Nov 18, 2008 at 01:03 UTC

    Can't tell for sure... but your problem might just be that @label and @labels are two different variables.  (Had you been using strictures, you'd probably have caught that typo yourself...)

    #!/usr/bin/perl use strict; use warnings; my $hostname = "foo.bar"; my @labels; my $nlabels = 0; for (split /\./, $hostname) { push @labels, length, $_; $nlabels++; } print "\n@labels\n"; my $question = pack("(C a*)$nlabels C n2", @labels,0,1,1); print '$question: ', join(" ", unpack("(H2)*", $question) ), "\n"; # +hexdump # --> $question: 03 66 6f 6f 03 62 61 72 00 00 01 00 01 # f o o b a r
Re: How to create DNS packet
by deewanagan (Novice) on Nov 18, 2008 at 18:24 UTC
    Hello Everyone! Another Question, when we send the DNS packet, how does its query format look like, i mean does it look like e.g "3www6google3com011" or like "wwwgooglecom011"? Please help! when i send a packet, in wireshark, it shows the Query name like "3.www.6.google.3.com"! any one know y?? i packed it like this (as suggested by others):
    my $hostname = $ARGV[0]; my @labels; my $nlabels = 0; for (split /\./, $hostname) { push @labels, length, $_; $nlabels++; } my $n = scalar(@labels); my $question = pack("(C/a*)$n C n2", @labels, 0, 1, 1) ;
    or used this one:
    my @labels = split(/\./, $hostname) ; my $n = scalar(@labels) ; my $question = pack("(C/a*)$n C n2", @labels, 0, 1, 1) ;

      Use "C a*" when supplying length/label pairs in @labels (as in the first snippet), or "C/a*" when supplying labels only (as in the second snippet). In the latter case, the length bytes will automatically be generated.  (Note that the length bytes (when < 32) might map to non-printable control characters if you try to print them as ASCII... so you probably want to visualize the data in hex representation.)

      I'm no expert in DNS packet format nitty gritties, but the latter snippet looks okay to me...  What exactly doesn't work; any error messages?

      in wireshark, it shows the Query name like "3.www.6.google.3.com"

      I think wireshark is being helpful, and showing the label lengths in human readable form, using '.' as a separator (it won't, after all, appear in any label !)

      This is a little crude:

      sub show { return join('', map { ord($_) > 0x3F ? $_ : sprintf('\\x%02X', ord +($_)) } split(//, $_[0])) ; } ; my $hostname = 'much.ado.about.not.alot.org' ; my @labels = split(/\./, $hostname) ; my $n = scalar(@labels) ; my $question = pack("(C/a*)$n C n2", @labels, 0, 1, 1) ; print "\"", show($question), "\"\n" ;
      but gives:
        "\x04much\x03ado\x05about\x03not\x04alot\x03org\x00\x00\x01\x00\x01"
      
      which looks right to me.

Re: How to create DNS packet
by deewanagan (Novice) on Nov 18, 2008 at 12:18 UTC
    Thanx to both of u!
Re: How to create DNS packet
by deewanagan (Novice) on Nov 19, 2008 at 03:04 UTC
    hi guys, its me again, sorry that i ask too many questions, its because i am new in perl.So i have send the packet to the server, and got response,now i want to unpack it,regardless of its compressed,the problem i get is, after unpacking header, i unpack the question section, into an array,after that i can't go on,because the rest of the packet will also be unpacked in that array.if i don't unpack in array i have to unpack in variables, but problem with that is that i don't know the length of the name,the code.
    my ($id,$qr_opcode_aa_tc_rd,$ra_z_Rcode,$qdcount,$ancount,$nscount,$ar +count,@arrayans,) = unpack("n,B8,B8,n4,(C/a*)$n C n2",$buf);
    thank u!

      Dealing with the responses is where it gets interesting.

      You need a way to handle the response packet as an array of bytes/8-bit-characters. Inter alia, the name compression requires this.

      You could split the response packet into an array @resp = map { ord($_) } split(//, $response) ; and proceed in a C-like fashion to process that as an array of unsigned 8-bit integers -- reconstructing 16-bit integers and character strings.

      Or you can process the string containing response packet directly: using either substr($response, $p, $n) or unpack("\@$p ....", $response) -- where $p is your current "pointer" into the packet.

      One way of using unpack to extract a name is to step along it and count the labels, so you can then unpack it much as you've suggested. This:

      my $n = 0 ; my $s = $p ; my $l ; do { if ($l = unpack("\@$p C", $response)) { $n++ ; } ; $p += $l + 1 ; } while ($l) ;
      sets $n to the number of labels, $s to the start of the name and advances $p past the name in the response packet $response. Then join('.', unpack("\@$s (C/a*)$n", $response)) will do the business. (Compressed names require a bit more code than this, of course.)

      As you have found, you need to process the response piece-meal, because you cannot:

      my ( ...., @labels, undef, $type, $class, ...) = unpack(".... (C/a*) +$n C n n ....") ;
      So a number of unpack operations with '@' are required, each using your $p to start processing in the right place in the response.

      I'd knock up a subroutine to extract a name from a given position in the current resoonse packet. You'll want it to return two things, the name and the position just after the name. Since you're new to Perl, you'll need to learn either that a subroutine can return a list (in List Context, of course), so:

      ($name, $p) = extract_name($response, $p) ; .... sub extract_name { my ($response, $p) = @_ ; my $extracted ... .... return ($extracted, $p) ; } ;
      or that you can do "call by reference" type things:
      sub extract_name { my ($response, $p) = @_ ; my $extracted ... .... $_[1] = $p ; # $_[1] is implicitly a reference to the 2nd +actual argument return $extracted ; } ;
      Enjoy !

        Dear all, can some one please explain this part of the post a little bit more in detail? its from the upper post, I could not get the following part:
        ----------------------------------
        So a number of unpack operations with '@' are required, each using your $p to start processing in the right place in the response.
        I'd knock up a subroutine to extract a name from a given position in the current resoonse packet. You'll want it to return two things, the name and the position just after the name. Since you're new to Perl, you'll need to learn either that a subroutine can return a list (in List Context, of course), so:
        ($name, $p) = extract_name($response, $p) ; .... sub extract_name { my + ($response, $p) = @_ ; my $extracted ... .... return ($extracted, $p +) ; } ;
        or that you can do "call by reference" type things:
        sub extract_name { my ($response, $p) = @_ ; my $extracted ... .... $_[1] = $p ; # $_[1] is implicitly a reference to the 2nd +actual argument return $ +extracted ; } ;
        ------------------------------

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://724155]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (9)
As of 2024-04-24 07:16 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found