Created
June 27, 2013 16:12
-
-
Save olof/5877807 to your computer and use it in GitHub Desktop.
Parsing a DNS packet with erlang. Learning erlang kind of thing. Not complete, but can parse up to, and including, the question section.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
% vim: ts=2:sw=2:noet:tw=80 | |
% dnspkt.erl: Parse a dns query packet. | |
% Copyright 2013, Olof Johansson <[email protected]> | |
% | |
% Copying and distribution of this file, with or without | |
% modification, are permitted in any medium without royalty | |
% provided the copyright notice are preserved. This file is | |
% offered as-is, without any warranty. | |
-module(dnspkt). | |
-export([parse_pkt/1]). | |
-include_lib("eunit/include/eunit.hrl"). | |
-record(dns_header, { | |
id, | |
qr, opcode, aa, tc, rd, ra, z, rcode, | |
qdcount, | |
ancount, | |
nscount, | |
arcount | |
}). | |
-record(dns_question, { | |
name, type, class | |
}). | |
-record(dns_rr, { | |
name, type, class, ttl, rdlength, rdata | |
}). | |
% Parse a dns query packet and return a structure of the | |
% following format: | |
% | |
% { | |
% header, #dns_header, | |
% question, [#dns_question, ...], | |
% answer, {#dns_rr, ...], | |
% auth, {#dns_rr, ...], | |
% additional, {#dns_rr, ...], | |
% } | |
% | |
% dns_question is a record with the fields: | |
% name, type and class. | |
% | |
% dns_rr is a record with the fields: | |
% name, type, class, ttl, rdlength, rdata | |
% | |
% name is a list strings, each corresponding to the labels of | |
% the domain name. You could join the on . to get a human | |
% readable representation. | |
% | |
% type, class, ttl, rdlength are integers. | |
% | |
% rdata is arbitrary data. | |
% | |
% Recommended reading: | |
% RFC 1034 - DNS architecture | |
% RFC 1035 - DNS implementation | |
% | |
parse_pkt(RawPkt) -> | |
{Header, HeaderTail} = parse_header(RawPkt), | |
{Question, QuestionTail} = parse_qsection( | |
HeaderTail, Header#dns_header.qdcount | |
), | |
{Answer, AnswerTail} = parse_section( | |
QuestionTail, Header#dns_header.ancount | |
), | |
{Auth, AuthTail} = parse_section( | |
AnswerTail, Header#dns_header.nscount | |
), | |
{Additional, _AdditionalTail} = parse_section( | |
AuthTail, Header#dns_header.arcount | |
), | |
{ | |
{ header, Header }, | |
{ question, Question }, | |
{ answer, Answer }, | |
{ auth, Auth }, | |
{ additional, Additional } | |
}. | |
% Extracts the first three bytes, and extracting the semantic | |
% meaning of the bits; returns a tuple with a #dns_header record | |
% and the rest of the packet, following the header. | |
parse_header(RawPkt) -> | |
<< | |
Id:16, | |
Qr:1, Opcode:4, Aa:1, Tc:1, Rd:1, Ra:1, Z:3, Rcode:4, | |
Qdcount:16, | |
Ancount:16, | |
Nscount:16, Arcount:16, | |
Tail/binary | |
>> = RawPkt, | |
{#dns_header{ | |
id=Id, | |
qr=Qr, opcode=Opcode, aa=Aa, tc=Tc, rd=Rd, ra=Ra, z=Z, rcode=Rcode, | |
qdcount=Qdcount, | |
ancount=Ancount, | |
nscount=Nscount, | |
arcount=Arcount | |
}, Tail}. | |
% Returns a tuple of {Entries, RestOfDnsPkt} where Entries | |
% is a list of #dns_questions, and RestOfDnsPkt is the part | |
% of the dns packet after the question section. | |
parse_qsection(RawPkt, Count) -> | |
parse_qsection(RawPkt, Count, []). | |
parse_qsection(Tail, 0, Entries) -> | |
{Entries, Tail}; | |
parse_qsection(RawPkt, Count, Entries) -> | |
{Entry, Tail} = parse_qentry(RawPkt), | |
parse_qsection(Tail, Count-1, Entries ++ [Entry]). | |
parse_qentry(RawPkt) -> | |
{Qname, QnameTail} = parse_qname(RawPkt), | |
<<Qtype:16, Qclass:16, Tail/binary>> = QnameTail, | |
{#dns_question{name=Qname, type=Qtype, class=Qclass}, Tail}. | |
parse_section(_RawPkt, 0) -> | |
[]; | |
parse_section(_RawPkt, _Count) -> | |
[]. | |
parse_qname(RawPkt) -> | |
parse_qname(RawPkt, []). | |
parse_qname(RawPkt, Labels) -> | |
case get_label(RawPkt) of | |
{<<"">>, Tail} -> {Labels, Tail}; | |
{L, Tail} -> | |
parse_qname(Tail, Labels ++ [L]) | |
end. | |
% Returns a tuple consisting of a domain name label (e.g. www in | |
% www.example.com) and the rest of the dns packet. | |
get_label(RawPkt) -> | |
<<Len:8, LenTail/binary>> = RawPkt, | |
case Len of | |
0 -> | |
Label = <<"">>, | |
LabelTail = LenTail; | |
_ -> | |
<<Label:Len/binary, LabelTail/binary>> = LenTail | |
end, | |
{Label, LabelTail}. | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% UNIT TESTS, motherfucker do you speak it % | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
parse_header_tests_() -> | |
[ | |
?_assert(parse_header( | |
<< | |
32768:16, | |
1:1, 0:4, 0:1, 0:1, 1:1, 0:1, 0:3, 0:4, | |
1:16, | |
0:16, | |
0:16, | |
0:16, | |
"random data" | |
>>) =:= { | |
#dns_header{ | |
id=32768, | |
qr=1, | |
opcode=0, | |
aa=0, | |
tc=0, | |
rd=1, | |
ra=0, | |
z=0, | |
rcode=0, | |
qdcount=1, | |
ancount=0, | |
nscount=0, | |
arcount=0 | |
}, <<"random data">> | |
} | |
) | |
]. | |
get_label_test_() -> | |
[ | |
?_assert( | |
get_label(<<7:8, "example", 3:8, "com", 0:8, "tail">>) | |
=:= | |
{<<"example">>, <<3:8, "com", 0:8, "tail">>} | |
), | |
?_assert( | |
get_label(<<3:8, "com", 0:8>>) | |
=:= | |
{<<"com">>, <<0:8>>} | |
), | |
?_assert( | |
get_label(<<0:8, "tail">>) | |
=:= | |
{<<"">>, <<"tail">>} | |
), | |
?_assert( | |
get_label(<<0:8>>) | |
=:= | |
{<<"">>, <<"">>} | |
) | |
]. | |
parse_qname_test_() -> | |
[ | |
?_assert( | |
parse_qname(<<7:8, "example", 3:8, "com", 0:8, "tail">>) | |
=:= | |
{[<<"example">>, <<"com">>], <<"tail">>} | |
), | |
?_assert( | |
parse_qname(<<7:8, "example", 3:8, "com", 0:8>>) | |
=:= | |
{[<<"example">>, <<"com">>], <<"">>} | |
), | |
?_assert( | |
parse_qname(<<0:8, "tail">>) | |
=:= | |
{[], <<"tail">>} | |
), | |
?_assert( | |
parse_qname(<<0:8>>) | |
=:= | |
{[], <<"">>} | |
) | |
]. | |
parse_qsection_test_() -> | |
[ | |
?_assert( | |
parse_qsection(<<7:8, "example", 3:8, "com", 0:8, 6:16, 1:16, "tail">>, 1) | |
=:= | |
{ | |
[ | |
#dns_question{ | |
name=[<<"example">>, <<"com">>], | |
type=6, | |
class=1 | |
} | |
], <<"tail">> | |
} | |
), | |
?_assert( | |
parse_qsection(<<7:8, "example", 3:8, "com", 0:8, 6:16, 1:16>>, 1) | |
=:= | |
{ | |
[ | |
#dns_question{ | |
name=[<<"example">>, <<"com">>], | |
type=6, | |
class=1 | |
} | |
], <<"">> | |
} | |
), | |
?_assert( | |
parse_qsection(<<0:8, 1:16, 1:16>>, 1) | |
=:= | |
{ | |
[ | |
#dns_question{ | |
name=[], | |
type=1, | |
class=1 | |
} | |
], <<"">> | |
} | |
), | |
% Multiple question entries in qsection | |
?_assert( | |
parse_qsection( | |
<< | |
0:8, 1:16, 1:16, | |
7:8, "example", 3:8, "com", 0:8, 1:16, 1:16, | |
3:8, "iis", 2:8, "se", 0:8, 1:16, 1:16 | |
>>, 3 | |
) | |
=:= | |
{ | |
[ | |
#dns_question{ | |
name=[], | |
type=1, | |
class=1 | |
}, | |
#dns_question{ | |
name=[<<"example">>, <<"com">>], | |
type=1, | |
class=1 | |
}, | |
#dns_question{ | |
name=[<<"iis">>, <<"se">>], | |
type=1, | |
class=1 | |
} | |
], <<"">> | |
} | |
) | |
]. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment