Permalink
Browse files

Purports to support multicast DNS and DNS-SD in Erlang.

Only supports one off queries. Will do more later, with any luck.
Early commit, makes it easier to share between workstations.
  • Loading branch information...
0 parents commit 5eafb3b82d47f1634373cd1995a53f66839c9d70 @msantos committed Feb 9, 2010
Showing with 518 additions and 0 deletions.
  1. +3 −0 .gitignore
  2. +6 −0 Emakefile
  3. +16 −0 Makefile
  4. +54 −0 README
  5. +52 −0 include/emdns.hrl
  6. +233 −0 src/emdns.erl
  7. +154 −0 src/emdns_server.erl
@@ -0,0 +1,3 @@
+*.[oa]
+*.beam
+.*.swp
@@ -0,0 +1,6 @@
+{["src/*"],
+ [{i, "include"},
+ {outdir, "ebin"},
+ debug_info]
+}.
+
@@ -0,0 +1,16 @@
+
+ERL=erl
+APP=emdns
+
+all: dir erl
+
+dir:
+ -@mkdir -p priv/tmp ebin
+
+erl:
+ @$(ERL) -noinput +B \
+ -eval 'case make:all() of up_to_date -> halt(0); error -> halt(1) end.'
+
+clean:
+ @rm -fv ebin/*.beam
+
54 README
@@ -0,0 +1,54 @@
+
+emdns is a cleverly named implementation of the multicast DNS (aka
+Bonjour, aka ZeroConf) and DNS-SD (DNS Service Discovery) protocols
+for Erlang.
+
+Eventually, it will support:
+
+ http://files.multicastdns.org/draft-cheshire-dnsext-multicastdns.txt
+
+
+BUILD
+
+make
+
+
+QUICK START
+
+Assume you have two hosts "nul" and "ack". Any multicast DNS services
+are disabled on "nul". "ack" runs Mac OS X.
+
+Host Entry
+
+1. Login to "ack". From a shell, run:
+
+$ ping nul.local
+ping: unknown host nul.local
+
+2. On "nul", start erl:
+
+$ erl -pa ebin
+> emdns_server:start().
+{ok,<0.35.0>}
+> emdns_server:debug(true).
+ok
+> A = emdns:service(host). % assumes "nul" has a reverse DNS entry
+<<0,0,132,...>>
+> emdns_server:send(A).
+ok
+
+3. On "ack", run:
+
+$ ping nul.local
+PING nul.local (192.168.1.10): 56 data bytes
+64 bytes from 192.168.1.10: icmp_seq=0 ttl=64 time=4.471 ms
+64 bytes from 192.168.1.10: icmp_seq=1 ttl=64 time=2.611 ms
+
+4. Set up a DAAP service
+
+> D = emdns:service(daap).
+> emdns_server:send(D).
+
+5. On "ack", in iTunes, check "Devices" for a entry named "erlMOTE".
+
+
@@ -0,0 +1,52 @@
+%% Copyright (c) 2010, Michael Santos <michael.santos@gmail.com>
+%% All rights reserved.
+%%
+%% Redistribution and use in source and binary forms, with or without
+%% modification, are permitted provided that the following conditions
+%% are met:
+%%
+%% Redistributions of source code must retain the above copyright
+%% notice, this list of conditions and the following disclaimer.
+%%
+%% Redistributions in binary form must reproduce the above copyright
+%% notice, this list of conditions and the following disclaimer in the
+%% documentation and/or other materials provided with the distribution.
+%%
+%% Neither the name of the author nor the names of its contributors
+%% may be used to endorse or promote products derived from this software
+%% without specific prior written permission.
+%%
+%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+%% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+%% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+%% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+%% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+%% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+%% POSSIBILITY OF SUCH DAMAGE.
+
+-include_lib("kernel/src/inet_dns.hrl").
+
+-define(MDNS_PORT, 5353).
+-define(MDNS_ADDRESS, {224,0,0,251}).
+
+-define(TRUE, 1).
+-define(FALSE, 0).
+
+% XXX some of the encoding functions in inet_dns match
+% XXX on class 'in'. Using CLASS_CF will cause the
+% XXX decoding to fail.
+% XXX
+% XXX Either grovel through the binary returned by inet_dns:encode/1
+% XXX and manually replace the value (ewww) or make a copy of inet_dns
+% XXX and modify it.
+-define(CLASS_CF, 32769). % CLASS: cache flush: class IN, with top bit set to 1
+
+-define(TYPE_NSEC, 47). % TYPE: NSEC, negate, used to indicate a AAAA does not
+ % exist for the host
+
+
@@ -0,0 +1,233 @@
+%% Copyright (c) 2010, Michael Santos <michael.santos@gmail.com>
+%% All rights reserved.
+%%
+%% Redistribution and use in source and binary forms, with or without
+%% modification, are permitted provided that the following conditions
+%% are met:
+%%
+%% Redistributions of source code must retain the above copyright
+%% notice, this list of conditions and the following disclaimer.
+%%
+%% Redistributions in binary form must reproduce the above copyright
+%% notice, this list of conditions and the following disclaimer in the
+%% documentation and/or other materials provided with the distribution.
+%%
+%% Neither the name of the author nor the names of its contributors
+%% may be used to endorse or promote products derived from this software
+%% without specific prior written permission.
+%%
+%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+%% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+%% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+%% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+%% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+%% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+%% POSSIBILITY OF SUCH DAMAGE.
+
+-module(emdns).
+
+-include_lib("kernel/include/inet.hrl").
+-include("emdns.hrl").
+
+-define(TTL_SHORT, 120).
+-define(TTL_LONG, 4500).
+
+-export([ttl/1, type/1, encode/1, service/1]).
+-export([hostname/0,local_address/0,reverse_address/1]).
+
+
+%%%
+%%% Record construction
+%%%
+ttl(short) ->
+ ?TTL_SHORT;
+ttl(long) ->
+ ?TTL_LONG.
+
+
+type(header) ->
+ % standard, authoritative mdns header
+ #dns_header{
+ id = 0,
+ opcode = ?QUERY,
+ aa = ?TRUE, % authoritative
+ qr = ?TRUE % response flag
+ };
+
+type({ptr, L}) when is_list(L) ->
+ Domain = proplists:get_value(domain, L, reverse_address(local_address())),
+ Class = proplists:get_value(class, L, in),
+ TTL = proplists:get_value(ttl, L, ?TTL_SHORT),
+ Data = proplists:get_value(data, L, hostname()),
+
+ #dns_rr{
+ domain = Domain,
+ type = ptr,
+ class = Class,
+ ttl = TTL,
+ data = Data
+ };
+
+type({srv, L}) when is_list(L) ->
+ Domain = proplists:get_value(domain, L, hostname()),
+ Class = proplists:get_value(class, L, in),
+ TTL = proplists:get_value(ttl, L, ?TTL_SHORT),
+ Data = proplists:get_value(data, L, hostname()),
+
+ case Data of
+ {A,B,C,D} when is_integer(A), is_integer(B), is_integer(C), is_list(D) ->
+ #dns_rr{
+ domain = Domain,
+ type = srv,
+ class = Class,
+ ttl = TTL,
+ data = {0,0,1024,"nul.local"}
+ };
+ _ ->
+ {err, fmt}
+ end;
+
+type({txt, L}) when is_list(L) ->
+ Domain = proplists:get_value(domain, L, hostname()),
+ Class = proplists:get_value(class, L, in), % should be cache flush
+ TTL = proplists:get_value(ttl, L, ?TTL_LONG),
+ Data = proplists:get_value(data, L, []),
+
+ case Data of
+ Any when is_list(Any) ->
+ #dns_rr{
+ domain = Domain,
+ type = txt,
+ class = Class,
+ ttl = TTL,
+ data = Data
+ };
+ _ ->
+ {err, fmt}
+ end;
+
+type({a, L}) when is_list(L) ->
+ Domain = proplists:get_value(domain, L, hostname()),
+ Class = proplists:get_value(class, L, in),
+ TTL = proplists:get_value(ttl, L, ?TTL_SHORT),
+ {IP1,IP2,IP3,IP4} = proplists:get_value(data, L, local_address()),
+
+ #dns_rr{
+ domain = Domain,
+ type = a,
+ class = Class,
+ ttl = TTL,
+ data = <<IP1,IP2,IP3,IP4>>
+ };
+
+type({any, L}) when is_list(L) ->
+ Domain = proplists:get_value(domain, L, hostname()),
+ Class = proplists:get_value(class, L, in),
+ TTL = proplists:get_value(ttl, L, ?TTL_SHORT),
+
+ #dns_rr{
+ domain = Domain,
+ type = any,
+ class = Class,
+ ttl = TTL
+ }.
+
+
+encode(L) when is_list(L) ->
+ Header = proplists:get_value(header, L, type(header)),
+ Queries = proplists:get_value(queries, L, []),
+ Answers = proplists:get_value(answers, L, []),
+ Resources = proplists:get_value(resources, L, []),
+
+ Record = #dns_rec{
+ header = Header,
+ qdlist = Queries,
+ anlist = Answers,
+ arlist = Resources
+ },
+ inet_dns:encode(Record).
+
+
+%%%
+%%% Services
+%%%
+service(probe) ->
+ Any = type({any, [{class, ?CLASS_CF}]}),
+ encode([{answers, [Any]}]);
+
+service(host) ->
+ A = type({a, [{class, ?CLASS_CF}]}),
+ encode([{answers, [A]}]);
+
+% See:
+% http://dacp.jsharkey.org/
+% http://blog.mycroes.nl/2008/08/pairing-itunes-remote-app-with-your-own.html
+service(daap) ->
+ Service = "0000000000000000000000000000000000000011._touch-remote._tcp.local",
+ Domain = "nul.local",
+ Hostname = hostname(),
+ IP = local_address(),
+
+ PTR = type({ptr, [
+ {domain, "_touch-remote._tcp.local"},
+ {ttl, ?TTL_LONG},
+ {data, Service}
+ ]}),
+
+ TXT = type({txt, [
+ {domain, Service},
+ {ttl, 4500},
+ {data, [
+ "DvNm=erlMOTE",
+ "RemV=10000",
+ "DvTy=iPod",
+ "RemN=Remote",
+ "txtvers=1",
+ "Pair=0000000000000011"
+ ]}
+ ]}),
+
+ SRV = type({srv, [
+ {domain, Service},
+ {data, {0,0,1024,Hostname}} % XXX should allow setting port
+ ]}),
+
+ A = type({a, [
+ {domain, Domain},
+ {class, ?CLASS_CF},
+ {data, IP}
+ ]}),
+
+ encode([{answers, [PTR, TXT, SRV, A]}]).
+
+
+%%
+%% Utilities
+%%
+
+reverse_address(Addr) when is_tuple(Addr) ->
+ % 212.213.168.192.in-addr.arpa
+ lists:flatten([ integer_to_list(N) ++ "." ||
+ N <- lists:reverse(tuple_to_list(Addr)) ]
+ ++ "in-addr.arpa").
+
+
+%%
+%% Defaults
+%%
+
+hostname() ->
+ % inet:gethostname/0 will never fail
+ hd(string:tokens(element(2,inet:gethostname()), ".")) ++ ".local".
+
+local_address() ->
+ Host = element(2, inet:gethostname()),
+ {ok, HE} = inet:gethostbyname(Host),
+ hd(HE#hostent.h_addr_list).
+
+
Oops, something went wrong.

0 comments on commit 5eafb3b

Please sign in to comment.