Created
March 28, 2020 00:25
-
-
Save ConnorRigby/86b0baa4ddac545cf07f7ad64c538ab9 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 Meshy.DNS do | |
use GenServer | |
@mdns_group {224, 0, 0, 251} | |
@mdns_port Application.get_env(: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 | |
defmodule MeshyNode do | |
defstruct ip: nil, | |
services: [], | |
domain: nil, | |
payload: %{} | |
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") | |
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 | |
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 | |
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) | |
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