Madness

Madness queries mDNS and publishes DNS-SD services from Elixir. Queries support IPv4 and IPv6 and return records as a lazy stream or process messages. Publishing delegates to Avahi or Apple's mDNSResponder daemon.

Installation

Add Madness to mix.exs:

def deps do
[
{:madness, "~> 0.5.0"}
]
end

Publishing through Avahi uses the required rebus dependency included by Madness. Publishing on one specific interface also requires the optional inertial package:

{:inertial, "~> 2.2"}

Querying

request/2 returns a stream by default:

records =
Madness.request({"_http._tcp.local", :ptr})
|> Enum.to_list()

Multiple questions can share one query:

Madness.request([
{"mydevice.local", :a},
{"mydevice.local", :aaaa}
])
|> Enum.to_list()

For an OTP process, use message mode:

{:ok, ref} = Madness.request({"_http._tcp.local", :ptr}, into: :self)
receive do
{^ref, %Madness.Record{} = record} -> IO.inspect(record)
{^ref, :done} -> :ok
end

Responses include Answer and Additional records. Use record.metadata.section to distinguish them. Non-PTR queries can finish once all questions are answered, including by an NSEC negative answer. PTR queries wait for the configured timeout because more responders may reply.

Common options are timeout: 5_000, family: :any | :inet | :inet6, interface: :any | name | index, and unicast_response: true | false. See Madness.request/2 for the complete API.

Publishing

Publish a service and keep the returned handle:

{:ok, publication} =
Madness.publish(
name: "Web",
type: "_http._tcp",
port: 4000,
txt: %{"path" => "/"}
)
:ok = Madness.unpublish(publication)

The publication follows the lifetime of its owner process and is withdrawn automatically when that process exits. Pass owner: when a short-lived setup process publishes for a long-lived server. Monitor publication.pid if the service should be republished after a daemon restart or another terminal publication failure.

Publishing uses config :madness, backend: :auto by default. Automatic selection probes Avahi and then mDNSResponder, settling on the first reachable daemon. You can configure :avahi, :mdns_responder, a custom Madness.Responder module, or false. The native responder is not implemented.

Avahi supports interface: and family: scopes. mDNSResponder supports interface: but cannot restrict an advertisement to one address family; Madness returns an error rather than widening a requested scope. An interface-scoped publication requires inertial and terminates if the bound interface disappears or can no longer be observed.

See Madness.publish/2 and Madness.Service for service fields, lifecycle notifications, subtypes, and validation rules.

Development

Run the normal checks with:

mix precommit

The unit suite uses in-process daemon fakes. With Docker available, mix test.avahi runs the Avahi integration suite against a real daemon.

License

MIT