diff --git a/src/tsuru_mdns.erl b/src/tsuru_mdns.erl new file mode 100644 index 0000000000000000000000000000000000000000..9fecae10ff858ee2f1ee11e909d6b31b2b50d196 --- /dev/null +++ b/src/tsuru_mdns.erl @@ -0,0 +1,115 @@ +-module(tsuru_mdns). +-author("erikh"). + +-include_lib("kernel/src/inet_dns.hrl"). + +-define(MDNS_ADDR, {224, 0, 0, 251}). +-define(MDNS_PORT, 5353). + +%% API +-export([publish_broker/3, discover_brokers/1, discover_brokers/2]). + +%% Exported types +-export_type([brokers/0]). +-type brokers() :: [{Address :: string(), Port :: inet:port_number()}]. + +-spec(publish_broker(Domain :: string(), Address :: string(), Port :: inet:port_number()) -> + {ok, Pid :: pid()} | + {error, Reason :: inet:posix()}). +publish_broker(Domain, Address, Port) -> + case gen_udp:open(?MDNS_PORT, [{active, false}, {reuseaddr, true}, {ip, ?MDNS_ADDR}, {multicast_ttl, 4}, {multicast_loop, false}, {mode, binary}, {add_membership, {?MDNS_ADDR, {0, 0, 0, 0}}}]) of + {ok, Socket} -> + Response = #dns_rec{ + header = #dns_header{qr = 1, opcode = ?QUERY}, + anlist = [ + #dns_rr{ + domain = Domain, type = ?S_SRV, data = {0, 0, Port, Address} + } + ] + }, + spawn_link(fun() -> receive_request(Domain, Socket, Response) end); + {error, Reason} -> + {error, Reason} + end. + +-spec(receive_request(Domain :: string(), Socket :: port(), Response :: #dns_rec{}) -> any()). +receive_request(Domain, Socket, Response) -> + case gen_udp:recv(Socket, 1024) of + {ok, {Address, Port, Packet}} -> + handle_request(Domain, Address, Port, Packet, Response), + receive_request(Domain, Socket, Response); + {error, _Reason} -> + receive_request(Domain, Socket, Response) + end. + +-spec(handle_request(Domain :: string(), Address :: inet:ip_address(), Port :: inet:port_number(), Packet :: binary(), Response :: #dns_rec{}) -> ok). +handle_request(Domain, Address, Port, Packet, Response) -> + case inet_dns:decode(Packet) of + {ok, Request} -> + case Request#dns_rec.qdlist of + [{dns_query, Domain, ptr, in}] -> + {ok, Socket} = gen_udp:open(0), + gen_udp:send(Socket, Address, Port, inet_dns:encode(Response)), + gen_udp:close(Socket); + _Other -> + ok + end; + _Error -> + ok + end. + +-spec(discover_brokers(Domain :: string()) -> + {ok, Brokers :: brokers()} | + {error, Reason :: not_owner | inet:posix()}). +discover_brokers(Domain) -> + discover_brokers(Domain, 3000). + +-spec(discover_brokers(Domain :: string(), Timeout :: pos_integer()) -> + {ok, Brokers :: brokers()} | + {error, Reason :: not_owner | inet:posix()}). +discover_brokers(Domain, Timeout) -> + case gen_udp:open(0, [{broadcast, true}, {active, false}, {mode, binary}]) of + {ok, Socket} -> + Request = #dns_rec{header = #dns_header{}, qdlist = [#dns_query{domain = Domain, type = ptr, class = in}]}, + gen_udp:send(Socket, ?MDNS_ADDR, ?MDNS_PORT, inet_dns:encode(Request)), + receive_response(Domain, Socket, Timeout, []); + {error, Reason} -> + {error, Reason} + end. + +-spec(receive_response(Domain :: string(), Socket :: port(), Timeout :: pos_integer(), Brokers :: brokers()) -> + {ok, Brokers :: brokers()} | + {error, Reason :: not_owner | inet:posix()}). +receive_response(Domain, Socket, Timeout, Brokers) -> + case gen_udp:recv(Socket, 1024, Timeout) of + {ok, {Address, Port, Packet}} -> + receive_response(Domain, Socket, Timeout, handle_response(Domain, Address, Port, Packet, Brokers)); + {error, timeout} -> + {ok, Brokers}; + {error, Reason} -> + {error, Reason} + end. + +-spec(handle_response(Domain :: string(), Address :: inet:ip_address(), Port :: inet:port_number(), Packet :: binary(), Brokers :: brokers()) -> Brokers :: brokers()). +handle_response(Domain, Address, _Port, Packet, Brokers) -> + case inet_dns:decode(Packet) of + {ok, Request} -> + Header = Request#dns_rec.header, + if + Header#dns_header.qr -> + case Request#dns_rec.anlist of + [#dns_rr{domain = Domain, data = {0, 0, BrokerPort, "0.0.0.0"}}] -> + Broker = {inet_parse:ntoa(Address), BrokerPort}, + [Broker | Brokers]; + [#dns_rr{domain = Domain, data = {0, 0, BrokerPort, BrokerAddress}}] -> + Broker = {BrokerAddress, BrokerPort}, + [Broker | Brokers]; + _ -> + Brokers + end; + true -> + Brokers + end; + {error, _Reason} -> + Brokers + end. diff --git a/test/tsuru_mdns_test.erl b/test/tsuru_mdns_test.erl new file mode 100644 index 0000000000000000000000000000000000000000..fe965a313652c4b4aed63ee93b7e8d21ef6b98c8 --- /dev/null +++ b/test/tsuru_mdns_test.erl @@ -0,0 +1,15 @@ +-module(tsuru_mdns_test). +-author("erikh"). + +-include_lib("eunit/include/eunit.hrl"). + +publish_test() -> + {ok, NoBrokers} = tsuru_mdns:discover_brokers("_test._tcp.local", 1000), + ?assertEqual(NoBrokers, []), + tsuru_mdns:publish_broker("_test._tcp.local", "0.0.0.0", 8080), + Pid = tsuru_mdns:publish_broker("_test._tcp.local", "127.0.0.1", 8081), + tsuru_mdns:publish_broker("_other._tcp.local", "0.0.0.0", 8082), + {ok, TestBrokers} = tsuru_mdns:discover_brokers("_test._tcp.local"), + ?assert(proplists:is_defined("127.0.0.1", TestBrokers)), + + ok.