Skip to content

Instantly share code, notes, and snippets.

@ConnorRigby
Created March 28, 2020 00:25
Show Gist options
  • Save ConnorRigby/86b0baa4ddac545cf07f7ad64c538ab9 to your computer and use it in GitHub Desktop.
Save ConnorRigby/86b0baa4ddac545cf07f7ad64c538ab9 to your computer and use it in GitHub Desktop.
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