- emdns: An unfinished multicast DNS server with unspecified yet no doubt awesome features. Someday I'll finish it. Maybe.
- spood: A strange, little program; a spoofing DNS proxy that will send out your DNS requests from somebody else's IP address and sniff the responses. Maybe (if you're somewhat sketchy) you could use it to hide your DNS lookups. Maybe, you could use it to ramp up your DNS requests on networks that throttle them down. Not that I would do any of that.
- seds: a DNS server that tunnels TCP/IP. I'm typing this blog over a DNS tunnel right now, stress testing it (with my blazing fast ASCII input) and trying to make seds crash. Also stress testing my patience.
- decode/1 takes a binary and returns a #dns_rec{} record or {error, fmt} if the DNS payload cannot be decoded
- encode/1 as you might expect, does the inverse, taking an appropriate record and returning a binary
-record(dns_rec, { header, %% dns_header record qdlist = [], %% list of question entries anlist = [], %% list of answer entries nslist = [], %% list of authority entries arlist = [] %% list of resource entries }).
- The DNS header is another record:
-record(dns_header, { id = 0, %% ushort query identification number %% byte F0 qr = 0, %% :1 response flag opcode = 0, %% :4 purpose of message aa = 0, %% :1 authoritive answer tc = 0, %% :1 truncated message rd = 0, %% :1 recursion desired %% byte F1 ra = 0, %% :1 recursion available pr = 0, %% :1 primary server required (non standard) %% :2 unused bits rcode = 0 %% :4 response code }).
While the defaults are initialized to small integers, inet_dns replaces them with atoms. So, the 1 bit values are either the atoms 'true' or 'false' and the opcode is set to an atom, for example, 'query'. Both integers and the atom representations are usually accepted by the functions though.
- qdlist is a list of DNS query records:
-record(dns_query, { domain, %% query domain type, %% query type class %% query class }).
- domain is a string representing the domain name, e.g., "foo.bar.example.com"
- type is an atom describing the DNS type: a, cname, txt, null, srv, ns, ...
- class will most commonly be 'in' (Internet), though multicast DNS uses "cache flush" (32769) for some operations
- domain is a string representing the domain name, e.g., "foo.bar.example.com"
Making a valid Erlang DNS query would look something like:
-module(dns). -compile(export_all). -include_lib("kernel/src/inet_dns.hrl"). q(Domain, NS) -> Query = inet_dns:encode( #dns_rec{ header = #dns_header{ id = crypto:rand_uniform(1,16#FFFF), opcode = 'query', rd = true }, qdlist = [#dns_query{ domain = Domain, type = a, class = in }] }), {ok, Socket} = gen_udp:open(0, [binary, {active, false}]), gen_udp:send(Socket, NS, 53, Query), {ok, {NS, 53, Reply}} = gen_udp:recv(Socket, 65535), inet_dns:decode(Reply).I enabled recursion because the request will be going through the one of the public Google nameservers (8.8.8.8) instead of going directly through the authoritative nameserver.
Testing the results:
$ erl Erlang R14A (erts-5.8) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.8 (abort with ^G) 1> {ok, Q} = dns:q("listincomprehension.com", {8,8,8,8}). {ok,{dns_rec,{dns_header,7296,true,'query',false,false, true,true,false,0}, [{dns_query,"listincomprehension.com",a,in}], [{dns_rr,"listincomprehension.com",a,in,0,656, {216,239,32,21}, undefined,[],false}, {dns_rr,"listincomprehension.com",a,in,0,656, {216,239,34,21}, undefined,[],false}, {dns_rr,"listincomprehension.com",a,in,0,656, {216,239,36,21}, undefined,[],false}, {dns_rr,"listincomprehension.com",a,in,0,656, {216,239,38,21}, undefined,[],false}], [],[]}} 2> rr("/usr/local/lib/erlang/lib/kernel-2.14/src/inet_dns.hrl"). [dns_header,dns_query,dns_rec,dns_rr,dns_rr_opt] 3> Q. #dns_rec{header = #dns_header{id = 7296,qr = true, opcode = 'query',aa = false,tc = false,rd = true,ra = true, pr = false,rcode = 0}, qdlist = [#dns_query{domain = "listincomprehension.com", type = a,class = in}], anlist = [#dns_rr{domain = "listincomprehension.com", type = a,class = in,cnt = 0,ttl = 656, data = {216,239,32,21}, tm = undefined,bm = [],func = false}, #dns_rr{domain = "listincomprehension.com",type = a, class = in,cnt = 0,ttl = 656, data = {216,239,34,21}, tm = undefined,bm = [],func = false}, #dns_rr{domain = "listincomprehension.com",type = a, class = in,cnt = 0,ttl = 656, data = {216,239,36,21}, tm = undefined,bm = [],func = false}, #dns_rr{domain = "listincomprehension.com",type = a, class = in,cnt = 0,ttl = 656, data = {216,239,38,21}, tm = undefined,bm = [],func = false}], nslist = [],arlist = []}The records are displayed as tuples. You can pretty print the records by using the shell rr() command to include the header file wherever it is on your system.
The query returned the same packet we sent with some changes to the header:
- The response flag (qr) is set to true
- The recursion available flag (ra) is also set to true
-record(dns_rr, { domain = "", %% resource domain type = any, %% resource type class = in, %% reource class cnt = 0, %% access count ttl = 0, %% time to live data = [], %% raw data %% tm, %% creation time bm = [], %% Bitmap storing domain character case information. func = false %% Optional function calculating the data field. }).The data field is interesting. Although it's initialized as an empty list, the data structure bound to it depends on the DNS record type. For example, from the ones I remember:
- A: tuple representing the IP address
- TXT: a list of strings
- NULL: a binary
- CNAME: a domain name string appropriately "labelled" (canonicalized by the "."'s), e.g., "ghs.google.com". inet_dns takes care of breaking the domain name into the appropriate, compressed domain name -- a weird form where the "."'s are replaced by nulls and each component is prefaced by a length or a pointer redirecting to another field (hence the compression).
Pattern Matching
The cool thing is that, since the DNS records are nested records, its very easy to pattern match on the results. Modifying the example above:-module(dns1). -compile(export_all). -include_lib("kernel/src/inet_dns.hrl"). q(Type, Domain, NS) -> Query = inet_dns:encode( #dns_rec{ header = #dns_header{ id = crypto:rand_uniform(1,16#FFFF), opcode = 'query', rd = true }, qdlist = [#dns_query{ domain = Domain, type = Type, class = in }] }), {ok, Socket} = gen_udp:open(0, [binary, {active, true}]), gen_udp:send(Socket, NS, 53, Query), loop(Socket, Type, Domain, NS). loop(Socket, Type, Domain, NS) -> receive {udp, Socket, NS, _, Packet} -> {ok, Response} = inet_dns:decode(Packet), match(Type, Domain, Response) end. match(a, Domain, #dns_rec{ header = #dns_header{ qr = true, opcode = 'query' }, qdlist = [#dns_query{ domain = Domain, type = a, class = in }], anlist = [#dns_rr{ domain = Domain, type = a, class = in, data = {IP1, IP2, IP3, IP4} }|_]}) -> {a, Domain, {IP1,IP2,IP3,IP4}}; match(cname, Domain, #dns_rec{ header = #dns_header{ qr = true, opcode = 'query' }, qdlist = [#dns_query{ domain = Domain, type = cname, class = in }], anlist = [#dns_rr{ domain = Domain, type = cname, class = in, data = Data }|_]}) -> {cname, Domain, Data}.
And the results:
$ erl
Erlang R14A (erts-5.8) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.8 (abort with ^G)
1> dns1:q(cname, "blog.listincomprehension.com", {8,8,8,8}).
{cname,"blog.listincomprehension.com","ghs.google.com"}
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.