Created
July 28, 2020 18:59
-
-
Save ConnorRigby/a5a59ff1a39d290f9a8cc54664088001 to your computer and use it in GitHub Desktop.
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
defmodule DoubleDNS do | |
@moduledoc "DoubleDNS" | |
use GenServer | |
require Logger | |
@mdns_group {224, 0, 0, 251} | |
@mdns_port 5353 | |
@dns_port 53 | |
defmodule State do | |
defstruct multicast_if: nil, | |
mdns_socket: nil, | |
mdns_entries: [], | |
dns_socket: nil, | |
dot_local: nil | |
end | |
@query_packet %DNS.Record{ | |
header: %DNS.Header{}, | |
qdlist: [] | |
} | |
def find_epmd(socket) do | |
packet = %DNS.Record{ | |
@query_packet | |
| :qdlist => [ | |
%DNS.Query{domain: '_epmd._tcp', type: :ptr, class: :in} | |
] | |
} | |
p = DNS.Record.encode(packet) | |
:ok = :gen_udp.send(socket, @mdns_group, @mdns_port, p) | |
end | |
def start_link(multicast_if) do | |
GenServer.start_link(__MODULE__, multicast_if, name: __MODULE__) | |
end | |
@impl GenServer | |
def init({_, _, _, _} = multicast_if) do | |
{:ok, hostname} = :inet.gethostname() | |
dot_local = "#{hostname}.local" | |
send(self(), :open_mdns) | |
send(self(), :open_dns) | |
{:ok, %State{multicast_if: multicast_if, dot_local: dot_local}} | |
end | |
@impl GenServer | |
def handle_info(:open_mdns, state) do | |
udp_options = [ | |
:binary, | |
broadcast: true, | |
active: true, | |
ip: {0, 0, 0, 0}, | |
ifaddr: {0, 0, 0, 0}, | |
add_membership: {@mdns_group, {0, 0, 0, 0}}, | |
multicast_if: state.multicast_if, | |
multicast_loop: true, | |
multicast_ttl: 32, | |
reuseaddr: true | |
] | |
case :gen_udp.open(@mdns_port, udp_options) do | |
{:ok, socket} -> | |
{:noreply, %State{state | mdns_socket: socket}} | |
error -> | |
{:stop, {:multicast, error}, state} | |
end | |
end | |
def handle_info(:open_dns, state) do | |
case :gen_udp.open(@dns_port, [:binary, active: true, reuseaddr: true]) do | |
{:ok, socket} -> | |
{:noreply, %State{state | dns_socket: socket}} | |
error -> | |
{:stop, error, state} | |
end | |
end | |
def handle_info({:udp, socket, ip, _port, packet}, %{mdns_socket: socket} = state) do | |
record = DNS.Record.decode(packet) | |
state = handle_mdns(record.anlist, ip, state) | |
# IO.inspect(record, label: "multicast packet") | |
{:noreply, state} | |
end | |
def handle_info({:udp, socket, ip, port, packet}, %{dns_socket: socket} = state) do | |
record = DNS.Record.decode(packet) | |
{answers, state} = handle_dns(record.qdlist, ip, [], state) | |
response = DNS.Record.encode(%{record | anlist: answers}) | |
_ = :gen_udp.send(socket, ip, port, response) | |
{:noreply, state} | |
end | |
def handle_mdns([%{type: :a, domain: domain, data: data} = resource | rest], ip, state) do | |
# IO.inspect(resource, label: "adding mdns resource") | |
_ = find_epmd(state.mdns_socket) | |
index = | |
Enum.find_index(state.mdns_entries, fn | |
%{domain: ^domain, data: ^data} -> true | |
_ -> false | |
end) | |
mdns_entries = | |
if index do | |
List.update_at(state.mdns_entries, index, fn _ -> resource end) | |
else | |
[resource | state.mdns_entries] | |
end | |
handle_mdns(rest, ip, %{state | mdns_entries: mdns_entries}) | |
end | |
# %DNS.Resource{ | |
# bm: [], | |
# class: :in, | |
# cnt: 0, | |
# data: {0, 0, 4369, 'meshy-080c.local'}, | |
# domain: 'meshy-080c._epmd._tcp.local', | |
# func: false, | |
# tm: :undefined, | |
# ttl: 120, | |
# type: :srv | |
# } | |
def handle_mdns([%{type: :srv, data: {_, _, 4369, node_name}} | rest], ip, state) do | |
if String.equivalent?(node_name, state.dot_local) do | |
handle_mdns(rest, ip, state) | |
else | |
node_name = :"meshy@#{node_name}" | |
if node_name in Node.list() do | |
handle_mdns(rest, ip, state) | |
else | |
Logger.info("Trying to connect to: #{node_name}") | |
case Node.connect(node_name) do | |
true -> Logger.info("Connected to #{node_name}") | |
false -> Logger.error("Failed to connect to #{node_name}") | |
end | |
handle_mdns(rest, ip, state) | |
end | |
end | |
end | |
def handle_mdns([_unknown | rest], ip, state) do | |
# IO.inspect(unknown, label: "mdns packet from: #{inspect(ip)}") | |
handle_mdns(rest, ip, state) | |
end | |
def handle_mdns([], _ip, state) do | |
state | |
end | |
def handle_dns([%{type: :a, domain: domain} = q | rest], ip, results, state) do | |
if String.equivalent?(domain, state.dot_local) do | |
# IO.inspect(q, lable: "responding to query with dot_local") | |
answer = make_record(q.domain, q.type, 120, state.multicast_if) | |
handle_dns(rest, ip, [answer | results], state) | |
else | |
answers = | |
Enum.filter(state.mdns_entries, fn | |
%{domain: ^domain} -> true | |
_ -> false | |
end) | |
|> Enum.map(fn | |
entry -> | |
make_record(q.domain, q.type, 120, entry.data) | |
end) | |
IO.inspect(q, label: "responding to query with: #{inspect(answers)}") | |
handle_dns(rest, ip, answers ++ results, state) | |
end | |
end | |
def handle_dns([_unknown | rest], ip, results, state) do | |
# IO.inspect(unknown, label: "dns packet from: #{inspect(ip)}") | |
handle_dns(rest, ip, results, state) | |
end | |
def handle_dns([], _, results, state) do | |
{Enum.reverse(results), state} | |
end | |
def make_record(domain, type, ttl, data) do | |
%DNS.Resource{ | |
domain: domain, | |
class: :in, | |
type: type, | |
ttl: ttl, | |
data: data | |
} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment