NAME

Fugu::Mdnsd - control mdnsd over its control socket

SYNOPSIS

use Fugu::Mdnsd;

my $mdns = Fugu::Mdnsd->new;

$mdns->publish(
    name  => 'My Bridge',
    app   => 'hap',
    proto => 'tcp',
    port  => 51827,
    txt   => 'c#=1.sf=1',
) or warn 'publish failed: ' . $mdns->error;

$mdns->update_txt(txt => 'c#=1.sf=0');

$mdns->withdraw;

DESCRIPTION

Fugu::Mdnsd publishes services with mdnsd(8). The module uses the mdnsd control protocol directly over /var/run/mdnsd.sock, and it starts no mdnsctl(8) child process. It implements no mDNS of its own: every method here is a control operation, and no mDNS packet ever leaves the module. mdnsd(8) sends those. The connection is the lifetime of the advertisement. mdnsd withdraws the service when the socket closes. Thus a daemon keeps the object alive for as long as the daemon must be discoverable. To withdraw the advertisement, the daemon only closes the socket.

The document spec/MDNS-Control.md in this repository specifies the wire protocol. This specification has the message types, the payload layouts, the group state machine, and its timing. The module never logs. Every method returns an outcome and records the most recent failure for error().

new

new(socket_path => $path, timeout => $seconds) creates a handle. $socket_path defaults to /var/run/mdnsd.sock. $timeout sets the maximum time that the publish methods wait for the replies from mdnsd. The default is 10 seconds. A healthy mdnsd confirms publication after approximately four seconds.

The method proves the struct mdns_service template at construction and dies when the encoded size is wrong. That size is a measured fact about the platform, not an input. A template that no longer matches means every publish would send a malformed record.

publish

publish(name => $instance, app => $app, proto => $proto, port => $port, txt => $string, timeout => $seconds) connects if necessary, then publishes. This is the whole startup path of a daemon that advertises one service: one call, and one error to report. The arguments are those of publish_service().

Use connect() and publish_service() separately only when the two steps need different handling.

connect

connect() connects to the control socket. The method returns 1 on success. It returns undef when the socket is absent or unreachable. When mdnsd does not run, this is a normal condition. The caller decides if this condition is important.

publish_service

publish_service(name => $instance, app => $app, proto => $proto, port => $port, txt => $string, timeout => $seconds) advertises one service and waits for mdnsd to confirm publication. name is the service instance name. mdnsd also uses this name as the publish group name. Thus the two names cannot be different. app is the application protocol with no underscore at the start ("hap", not "_hap"). proto must be "tcp" or "udp". txt is the TXT string in its final format: key=value pairs with the . character between them. mdnsd(8) splits the string on this delimiter, with no escape mechanism. The meaning of the keys is not important to the module.

The method returns 1 after mdnsd publishes the service. The method returns undef on input that is too long. This is an error, never a silent truncation. The method also returns undef on an absent connection, on an error reply (for example, a name collision), on an end-of-file, or on a timeout.

format_txt

format_txt(%records) formats TXT records for mdnsd: key=value pairs in sorted key order, joined with ..

The join is mdnsd's format, not the protocol's. mdnsd uses . as the TXT record delimiter and does not support escaping. The sorted order keeps the wire form deterministic. This is a plain function, not a method.

update_txt

update_txt(txt => $string, timeout => $seconds) replaces the TXT record. mdnsd cannot replace records on a held connection. Thus the method withdraws the advertisement and publishes it again over a fresh connection. The method uses the same parameters that the caller gave to publish_service(). When the service is not published, the method does nothing and returns success.

withdraw

withdraw() closes the control socket. This closure withdraws the advertisement. The method always returns 1.

The destructor calls withdraw(). The held socket is the lifetime of the advertisement, so the object going away must withdraw the service, whether the caller remembered to or not.

is_published

is_published() returns true while the module advertises the service on a held connection.

error

error() returns the most recent failure as a short string. The caller can log this string.

SEE ALSO

Fugu::Imsg, mdnsd(8)

AUTHORS

Dick Olsson <hi@senzilla.io>