avahi

package module
v0.0.0-...-55f3c23 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 25, 2024 License: BSD-2-Clause Imports: 14 Imported by: 0

README

Golang (cgo) binding for Avahi

godoc.org GitHub Go Report Card

import "github.com/alexpevzner/go-avahi"

Package avahi provides a fairly complete CGo-based Golang binding for Avahi client.

Package reference:

Introduction

Avahi is the standard implementation of Multicast DNS and DNS-SD for Linux, and likely for some BSD systems as well. This technology is essential for automatic network configuration, service discovery on local networks, and driverless printing and scanning. It also can be useful for the peer services discovery in a cloud.

Please notice, there is an alternative Avahi binding in pure Go:

This package has the following key differences:

  • This is CGo binding, not pure Go
  • It uses native/stdlib types, where appropriate. For example, IP addresses returned as [netip.AddrPort]
  • It uses a single channel for all events reported by an object, so add/remove events cannot be reordered
  • It survives Avahi restart
  • Integer values, like various flags, DNS class and type and so own, have their own type, not a generic int16/int32
  • And the last but not least, it attempts to fill the gaps in Avahi documentation, which is not very detailed

There is also DNS implementation on pure Go:

This library is comprehensive, high-quality, and quite popular. It is possible (and not very difficult) to implement MDNS/DNS-SD directly on top of it, allowing the entire protocol to run within the user process without relying on a system daemon like Avahi.

There are several existing implementations; however, I don't have experience with them, so I can't provide a review.

One inherent disadvantage of all these implementations is that they do not work with local services operating via the loopback network interface. MDNS is a multicast-based protocol, and the loopback interface does not support multicasting. System daemons like Avahi do not actually use multicasting for loopback services; instead, they emulate the publishing and discovery functionality for those services. An in-process implementation cannot achieve this.

Avahi documentation

Avahi API documentation, to be honest, is not easy to read. It lacks significant details and hard to understand unless you have a lot of a-priory knowledge in the subject.

Among other things, this package attempts to fill this gap. As its exported objects map very closely to the native C API objects (except Go vs C naming conventions and using channels instead of callbacks), the package reference may be useful as a generic Avahi API reference, regardless of programming language you use.

So even if you are the C or Python programmer, you may find package reference useful for you.

Build requirements

This package requires Go 1.18 or newer. This is an easy requirement, because Go 1.18 was released at March 2022, so must distros should be up to date.

As it is CGo binding, it requires avahi-devel (or avahi-client, the exact name may depend on your distro) package to be installed. On most Linux distros it is an easy to achieve.

You will need also a working C compiler. This is easy in a case of native build, but in a case of cross-compiling may require some additional effort.

This package was developed and tested at Fedora 40, but expected to work at all other distros.

Runtime requirements

This package requires a working Avahi daemon and libavahi-client dynamic libraries installed on a system. In most cases it should work out of box.

An Example

The following simple example demonstrates usage of the API provided by this package. This simple program scans local network for available network printers and outputs found devices.

// github.com/alexpevzner/go-avahi example

package main

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/alexpevzner/go-avahi"
)

// checkErr terminates a program if err is not nil.
func checkErr(err error, format string, args ...any) {
	if err != nil {
		msg := fmt.Sprintf(format, args...)
		fmt.Printf("%s: %s\n", msg, err)
		os.Exit(1)
	}
}

// The main function.
func main() {
	// Create a Client with enabled workarounds for Avahi bugs
	clnt, err := avahi.NewClient(avahi.ClientLoopbackWorkarounds)
	checkErr(err, "avahi.NewClient")

	defer clnt.Close()

	// Create context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Create poller to simplify event loop.
	poller := avahi.NewPoller()

	// Create ServiceBrowsers for a variety of printer types
	svctypes := []string{
		"_printer._tcp",        // LPD protocol
		"_pdl-datastream._tcp", // HP JetDirect
		"_ipp._tcp",            // IPP over HTTPS
		"_ipps._tcp",           // IPP over HTTPS
	}

	for _, svctype := range svctypes {
		browser, err := avahi.NewServiceBrowser(
			clnt,
			avahi.IfIndexUnspec,
			avahi.ProtocolUnspec,
			svctype,
			"",
			avahi.LookupUseMulticast)

		checkErr(err, "browse %q", svctype)
		poller.AddServiceBrowser(browser)
	}

	// Wait for Browser events. Create resolvers on a fly.
	//
	// Here we use asynchronous API, so we can start resolvers
	// early to run in background.
	//
	// Run until we found/resolve all we expect or timeout occurs.
	wanted := make(map[string]struct{})
	for _, svctype := range svctypes {
		wanted[svctype] = struct{}{}
	}

	for ctx.Err() == nil && len(wanted) > 0 {
		evnt, _ := poller.Poll(ctx)

		switch evnt := evnt.(type) {
		case *avahi.ServiceBrowserEvent:
			switch evnt.Event {
			case avahi.BrowserNew:
				resolver, err := avahi.NewServiceResolver(
					clnt,
					evnt.IfIdx,
					evnt.Proto,
					evnt.InstanceName,
					evnt.SvcType,
					evnt.Domain,
					avahi.ProtocolUnspec,
					avahi.LookupUseMulticast)

				checkErr(err, "resolve %q", evnt.InstanceName)
				poller.AddServiceResolver(resolver)
				wanted[evnt.InstanceName] = struct{}{}

			case avahi.BrowserAllForNow:
				delete(wanted, evnt.SvcType)

			case avahi.BrowserFailure:
				err = evnt.Err
				checkErr(err, "browse %q", evnt.SvcType)
			}

		case *avahi.ServiceResolverEvent:
			switch evnt.Event {
			case avahi.ResolverFound:
				fmt.Printf("Found new device:\n"+
					"  Name:       %s:\n"+
					"  Type:       %s\n"+
					"  IP address: %s:%d\n",
					evnt.InstanceName,
					evnt.SvcType,
					evnt.Addr,
					evnt.Port)

				delete(wanted, evnt.InstanceName)

			case avahi.ResolverFailure:
				err = evnt.Err
				checkErr(err, "resolve %q", evnt.InstanceName)
			}
		}
	}
}

Documentation

Overview

Package avahi provides a fairly complete CGo binding for Avahi client.

Avahi is the standard implementation of Multicast DNS and DNS-SD for Linux, and likely for some BSD systems as well. This technology is essential for automatic network configuration, service discovery on local networks, and driverless printing and scanning. It also can be useful for the peer services discovery in a cloud.

Package philosophy

The Avahi API wrapper, provided by this package, attempts to be as close to the original Avahi C API and as transparent, as possible. However, the following differences still exist:

  • Events are reported via channels, not via callbacks, as in C
  • AvahiPoll object is not exposed and handled internally
  • Workaround for Avahi localhost handling bug is provided (for details, see "Loopback interface handling and localhost" section above).

A bit of theory (Multicast DNS and DNS-SD essentials)

Avahi API is much simpler to understand when reader knows the basics of the Multicast DNS and DNS-SD protocols.

DNS is a kind of a distributed key-value database. In classical (unicast) DNS, records are maintained by the hierarchy of servers, while in the MDNS each participating host maintains its own records by itself and responds when somebody asks, usually using multicast UDP as transport.

In the case of classical DNS, clients perform database queries by contacting DNS servers. In contrast, with multicast DNS, clients send their queries to all other hosts in the vicinity using UDP multicast (e.g., "Hey! I need an IP address for the 'example.local' hostname. Who knows the answer?"). The hosts then respond by their own. To speed things up, when a new host connects to the network, it announces its resource records (RRs) to all interested parties and attempts to notify others of its removal just before it disconnects. Clients can capture and cache this information, eliminating the need for a slow network query each time this information is requires.

Each entry in the DNS database (called the Resource Record, RR) is identified by the search key, which consist of:

  • record name
  • record class
  • record type

The record name always looks like domain name, i.e., it is a string that consists from the dot-separated labels. The "example.com" name consists of two labels: "example" and "com".

This syntax is used even for names, which are not domains by themselves. For example, "1.0.0.127.in-addr.arpa" is the IP address "127.0.0.1", written using a DNS name syntax (please notice the reverse order of labels), and "_http._tcp.local" is the collective name of all HTTP servers running over TCP on a local network.

To distinguish between normal domains and pseudo-domains, a number of special top-level domains have been reserved for this purpose, like "in-addr.arpa" for IP addresses

DNS defines many classes, but the only class relevant to multicast DNS is IN, which stands for "Internet." That's all there is to it.

Record type is more important, as many record types are being used. We will not attempt to list them all, the most important for as are the following:

A       - these records contains one or more IPv4 addresses
AAAA    - these records contains one or more IPv6 addresses
PTR     - the pointer record. They point to some other domain name
SRV     - service descriptor
TXT     - contains a lot of additional information, represented
          as a list of key=value textual pairs.

Once we have a record name and type, we can query a record value. Interpretation of this value depends on a record type.

Now lets manually discover all IPP printers in our local network. We will use the small utility, mcdig, which allows to manually perform the Multicast DNS queries.

First of all, lets list all services on a network around. This is a query of the "_services._dns-sd._udp.local" records of type PTR, and mcdig will return the following answer (shortened):

$ mcdig _services._dns-sd._udp.local ptr
;; ANSWER SECTION:
_services._dns-sd._udp.local.   4500    IN      PTR     _http._tcp.local.
_services._dns-sd._udp.local.   4500    IN      PTR     _https._tcp.local.
_services._dns-sd._udp.local.   4500    IN      PTR     _ipp._tcp.local.
_services._dns-sd._udp.local.   4500    IN      PTR     _ipps._tcp.local.
_services._dns-sd._udp.local.   4500    IN      PTR     _printer._tcp.local.

This is the same list as avahi-browse -a returns, and programmatically it can be obtained, using the ServiceTypeBrowser object.

Please notice, the "_services._dns-sd._udp.<domain>" is a reserved name for this purpose and <domain> is usually "local"; this top-level domain name is reserved for this purpose.

Now we see that somebody in our network provide the "_http._tcp.local." service (IPP printing), "_http._tcp.local." service (HTTP server) and so on. In a typical network there will be many services and they will duplicate in the answer.

Now, we are only interested in the IPP printers, so:

$ mcdig _ipp._tcp.local. ptr
;; ANSWER SECTION:
_ipp._tcp.local.   4500    IN     PTR     Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local.

Now we have a so called service instance name, "Kyocera ECOSYS M2040dn". Please notice, unlike classical DNS, MDNS labels may contain spaces (and virtually any valid UTF-8 characters), but although these labels looks like human-readable names, they are network-unique (which is enforced by the protocol) and can be used to unambiguously identify the device.

The same list will be returned by the avahi-browse _ipp._tcp command (please notice, the .local suffix is implied here) or using the ServiceBrowser object.

Now we need to know a bit more about the device, so the next two queries are:

$ mcdig Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local. srv
Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local.   120    IN    SRV    0 0 631 KM7B6A91.local.

$ mcdig Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local. txt
Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local.   4500   IN    TXT    "txtvers=1" "pdl=image/pwg-raster,..." ...

It brings us the following information:

  • SRV record contains a hostname (which is not the same as the instance name, and often is not as friendly and human-readable) and IP port (631, the third parameter in the SRV RR)

  • TXT record contains a lot of "key=value" pairs which describe many characteristics of device.

And the final step is to obtain device's IP addresses. Here we need the hostname, obtained at the previous steps:

$ mcdig KM7B6A91.local. a
KM7B6A91.local.	120     IN      A       192.168.1.102

$ mcdig KM7B6A91.local. aaaa
KM7B6A91.local.	120     IN      AAAA    fe80::217:c8ff:fe7b:6a91

The response is really huge and significantly shortened here. The TXT record is omitted at all, as it really large.

So the whole picture looks as following:

INPUT: "_ipp._tcp.local."                               (the service type)
 |
 --> Query PTR record
      |
      --> "Kyocera ECOSYS M2040dn._ipp._tcp.local."     (the instance name)
          |
          |-> Query SRC record
          |    |
          |    |-> 631                                  (TCP port)
          |    |
          |    --> "KM7B6A91.local."                    (the hostname)
          |          |
          |          |-> Query A record
          |          |    |
          |          |    --> 192.168.1.102             (IPv4 address)
          |          |
          |          --> Query AAAA record
          |               |
          |               --> fe80::217:c8ff:fe7b:6a91  (IPv6 address)
          |
          -> Query TXT record
              |
              --> A lot of key=value pairs              (device description)

So a lot of work indeed!

The same information can be obtained programmatically, using the ServiceResolver object, and the service resolver actually perform all these steps under the hood.

And finally, we can lookup IP address by hostname and hostname by IP address:

$ mcdig KM7B6A91.local. a
;; ANSWER SECTION:
KM7B6A91.local.                120  IN    A       192.168.1.102

$ mcdig 102.1.168.192.in-addr.arpa ptr
;; ANSWER SECTION:
102.1.168.192.in-addr.arpa.    120  IN    PTR     KM7B6A91.local.

It corresponds to avahi commands "avahi-resolve-host-name KM7B6A91.local" and "avahi-resolve-address 192.168.1.102".

The HostNameResolver and AddressResolver objects provide the similar functionality in a form of API.

Key objects

The key objects exposed by this package are:

These objects have 1:1 relations to the corresponding avahi objects (i.e., Client represents AvahiClient, DomainBrowser represennts AvahiDomainBrowser and so on).

These objects are explicitly created with appropriate constructor functions (e.g., NewClient, NewDomainBrowser, NewServiceResolver and so on).

All these objects report their state change and discovered information using provided channel (use Chan() method to obtain the channel). There is also a context.Context-aware Get() methods which can be used to wait for the next event.

As these objects own some resources, such as DBus connection to the avahi-daemon, which is not automatically released when objects are garbage-collected, this is important to call appropriate Close method, when object is not longer in use.

Once object is closed, the sending side of its event channel is closed too, which effectively unblocks all users waiting for events.

Client

The Client represents a client connection to the avahi-daemon. Client is the required parameter for creation of Browsers and Resolvers and "owns" these objects.

Client has a state and this state can change dynamically. Changes in the Client state reported as a series of [ClientEVENT] events, reported via the Client.Chan channel or Client.Get convenience wrapper.

The Client itself can survive avahi-daemon (and DBus server) failure and restart. If it happens, ClientStateFailure event will be reported, followed by ClientStateConnecting and finally ClientStateRunning, when client connection will be recovered. However, all Browsers, Resolvers and EntryGroup-s owned by the Client will fail (with BrowserFailure/ResolverFailure/EntryGroupStateFailure events) and will not be restarted automatically. If it happens, application needs to close and re-create these objects.

The Client manages underlying AvahiPoll object (Avahi event loop) automatically and doesn't expose it via its interface.

Browsers

Browser constantly monitors the network for newly discovered or removed objects of the specified type and report discovered information as a series of events, delivered via provided channel.

More technically, browser monitors the network for reception of the MDNS messages of the browser-specific type and reports these messages as browser events.

There are 5 types of browser events, represented as values of the BrowserEvent integer type:

  • BrowserNew - new object was discovered on a network
  • BrowserRemove - the object was removed from the network
  • BrowserCacheExhausted - one-time hint event, that notifies the user that all entries from the avahi-daemon cache have been sent
  • BrowserAllForNow - one-time hint event, that notifies the user that more events are are unlikely to be shown in the near feature
  • BrowserFailure - browsing failed and needs to be restarted

Avahi documentation doesn't explain in detail, when BrowserAllForNow is generated, but generally, it is generated after an one-second interval from the reception of MDNS message of related type has been expired.

Each browser has a constructor function (e.g., NewDomainBrowser) and three methods:

  • Chan, which returns the event channel
  • Get, the convenience wrapper which waits for the next event and can be canceled using context.Context parameter
  • Close, which closes the browser.

This is important to call Close method when browser is not longer in use.

Resolvers

Resolver performs a series of appropriate MDNS queries to resolve supplied parameters into the requested information, depending on Resolver type (e.g,, ServiceResolver will resolve service name into hostname, IP address:port and TXT record).

Like Browsers, Resolvers return discovered information as a series of resolver events.

There are 2 types of resolver events, represented by integer value of the ResolverEvent type:

  • ResolverFound - new portion of required information received from the network
  • ResolverFailure - resolving failed and needs to be restarted

Please notice a single query may return multiple ResolverFound events. For example, if target has multiple IP addresses, each address will be reported via separate event.

Unlike the Browser, the Resolver does not provide any indication of which event is considered "last" in the sequence. Technically, there is no definitive "last" event, as a continuously running Resolver will generate a ResolverFound event each time the service data changes. However, if we simply need to connect to a discovered service, we must eventually stop waiting. A reasonable approach would be to wait for a meaningful duration (for example, 1 second) after the last event in the sequence arrives.

EntryGroup

EntryGroup implements Avahi publishing API. This is, essentially, a collection of resource entries which can be published "atomically", i.e., either the whole group is published or not.

Records can be added to the EntryGroup using EntryGroup.AddService, EntryGroup.AddAddress and EntryGroup.AddRecord methods. Existing services can be modified, using the EntryGroup.AddServiceSubtype and EntryGroup.UpdateServiceTxt methods. Once group is configured, application must call EntryGroup.Commit for changes to take effect.

When records are added, even before Commit, Avahi performs some basic checking of the group consistency, and if consistency is violated or added records contains invalid data, the appropriate call will fail with suitable error code.

When publishing services, there is no way to set service IP address explicitly. Instead, Avahi deduces appropriate IP address, based on the network interface being used and available addresses assigned to that interface.

Like other objects, EntryGroup maintains a dynamic state and reports its state changes using EntryGroupEvent which can be received either via the channel, returned by EntryGroup.Chan or via the EntryGroup.Get convenience wrapper.

As the protocol requires, EntryGroup implies a conflict checking, so this process takes some time. As result of this process, the EntryGroup will eventually come into the either EntryGroupStateEstablished or EntryGroupStateCollision state.

Unfortunately, in a case of collision there is no detailed reporting, which entry has caused a collision. So it is not recommended to mix unrelated entries in the same group.

IP4 vs IP6

When new Browser or Resolver is created, the 3rd parameter of constructor function specified a transport protocol, used for queries.

Some Resolver constructors have a second parameter of the Protocol type, the "addrproto" parameter. This parameter specifies which kind of addresses, IP4 or IP6, we are interested in output (technically, which kind of address records, A or AAAA, are queried).

If you create a Browser, using ProtocolUnspec transport protocol, it will report both IP4 and IP6 RRs and report them as separate events.

A new Resolver, created with ProtocolUnspec transport protocol will use IP6 as its transport protocol, as if ProtocolIP6 was specified.

If "addrproto" is specified as ProtocolUnspec, Resolver will always query for addresses that match the transport protocol.

It can be summarized by the following table:

proto           addrproto       transport       query for

ProtocolIP4     ProtocolIP4     IP4             IP4
ProtocolIP4     ProtocolIP6     IP4		IP6
ProtocolIP4     ProtocolUnspec  IP4             IP4

ProtocolIP6     ProtocolIP4     IP6             IP4
ProtocolIP6     ProtocolIP6     IP6             IP6
ProtocolIP6     ProtocolUnspec  IP6             IP6

ProtocolUnspec  ProtocolIP4     IP6             IP4
ProtocolUnspec  ProtocolIP6     IP6             IP6
ProtocolUnspec  ProtocolUnspec  IP6             IP6

By default the Avahi daemon publishes both IP4 and IP6 addresses when queried over IP4, but only IP6 addresses, when queried over IP6. This default can be changed using 'publish-aaaa-on-ipv4' and 'publish-a-on-ipv6' in 'avahi-daemon.conf').

Other servers (especially DNS-SD servers found on devices, like printers or scanners) may have a different, sometimes surprising, behavior.

So it makes sense to perform queries of all four transport/address combinations and merge results.

Loopback interface handling and localhost

As loopback network interface doesn't support multicasting, Avahi just emulates the appropriate functionality.

Loopback support is essentially for implementing the IPP over USB protocol, and ipp-usb daemon actively uses it. It allows the many modern printers and scanners to work seamlessly under the Linux OS.

Unfortunately, loopback support is broken in Avahi. This is a long story, but in short:

  • Services, published at the loopback address (127.0.0.1 or ::1) are erroneously reported by AvahiServiceResolver as being published at the real hostname and domain, instead of "localhost.localdomain"
  • AvahiAddressResolver also resolves these addresses using real hostname and domain
  • AvahiHostNameResolver doesn't resolve neither "localhost" nor "localhost.localdomain".

This library provides a workaround, but it needs to be explicitly enabled, using the ClientLoopbackWorkarounds flag:

clnt, err := NewClient(ClientLoopbackWorkarounds)

If this flag is in use, the following changes will occur:

  • ServiceResolver and AddressResolver will return "localhost.localdomain" for the loopback addresses
  • HostNameResolver will resolve "localhost" and "localhost.localdomain" as either 127.0.0.1 or ::1, depending on a value of the proto parameter for the NewHostNameResolver call. Please notice that if proto is ProtocolUnspec, NewHostNameResolver will use by default ProtocolIP6, to be consistent with other Avahi API (see section "IP4 vs IP6" for details).

IP addresses

This package uniformly uses netip.Addr to represent addresses. Unlike net.Addr, this format is compact, convenient and comparable.

When addresses are received from Avahi (for example, as a part of ServiceResolverEvent), the following rules apply:

  • IPv4 addresses are represented as 4-byte netip.Addr, not as 16-byte IP6-mapped IP4 addresses.
  • Link-local IPv6 addresses come with zone. For zone, numerical, not symbolic, format is used (i.e., fe80::1ff:fe23:4567:890a%3, not fe80::1ff:fe23:4567:890a%eth2)

When address is sent from application to Avahi, the following rules apply:

  • Both genuine IP4 and IP6-mapped IP4 addresses are equally accepted
  • For IP6 addresses, zone is ignored

The Poller

Poller is the helper object that allows to simplify the event loop when working with many instances of Browsers and Resolvers (the typical case for Avahi programming).

Poller is not a part of the native Avahi API and added here for convenience.

Poller allows to "connect" many event sources to the single object and use it's Poller.Poll methods to gather events from all the connected objects.

See project's README.md for the usage example.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DNSDecodeA

func DNSDecodeA(rdata []byte) netip.Addr

DNSDecodeA decodes A type resource record.

It returns a real IPv4 (not IPv6-encoded IPv4) address.

RecordBrowserEvent.RData can be used as input. Errors reported by returning zero netip.Addr

func DNSDecodeAAAA

func DNSDecodeAAAA(rdata []byte) netip.Addr

DNSDecodeAAAA decodes AAAA type resource record.

RecordBrowserEvent.RData can be used as input. Errors reported by returning zero netip.Addr

func DNSDecodeTXT

func DNSDecodeTXT(rdata []byte) []string

DNSDecodeTXT decodes TXT type resource record.

RecordBrowserEvent.RData can be used as input. Errors reported by returning nil slice.

func DomainEqual

func DomainEqual(d1, d2 string) bool

DomainEqual reports if two domain names are equal.

Note, invalid domain names are never equal to anything else, including itself.

func DomainFrom

func DomainFrom(labels []string) string

DomainFrom makes escaped domain name string from a sequence of unescaped labels:

["Ex.Ample", "com"] -> "Ex\.Ample.com"

Note, this function is not guaranteed to escape labels exactly as Avahi does, but output is anyway correct.

func DomainNormalize

func DomainNormalize(d string) string

DomainNormalize normalizes the domain name by removing unneeded escaping.

In a case of error it returns empty string.

func DomainServiceNameJoin

func DomainServiceNameJoin(instance, svctype, domain string) string

DomainServiceNameJoin merges two parts of the full service name (instance name, service type and domain name) into the full service name.

  • instance MUST be unescaped label
  • svctype and domain MUST be escaped domain names
  • instance and svctype MUST NOT be empty

In a case of error it returns empty strings. Strong validation of input strings is not performed here.

func DomainServiceNameSplit

func DomainServiceNameSplit(nm string) (instance, svctype, domain string)

DomainServiceNameSplit splits service name into instance, service type and domain components:

"Kyocera ECOSYS M2040dn._ipp._tcp.local" -->
    --> ["Kyocera ECOSYS M2040dn", "_ipp._tcp", "local"]

In a case of error it returns empty strings

func DomainSlice

func DomainSlice(d string) []string

DomainSlice splits escaped domain name into a sequence of unescaped labels:

"Ex\.Ample.com" -> ["Ex.Ample", "com"]

In a case of error it returns nil.

func DomainToLower

func DomainToLower(d string) string

DomainToLower converts domain name to upper case.

It only touches ASCII uppercase letters "a" to "z", leaving all other bytes untouched.

See RFC6762, 16. Multicast DNS Character Set for details.

func DomainToUpper

func DomainToUpper(d string) string

DomainToUpper converts domain name to upper case.

It only touches ASCII lowercase letters "a" to "z", leaving all other bytes untouched.

See RFC6762, 16. Multicast DNS Character Set for details.

Types

type AddressResolver

type AddressResolver struct {
	// contains filtered or unexported fields
}

AddressResolver resolves hostname by IP address.

func NewAddressResolver

func NewAddressResolver(
	clnt *Client,
	ifidx IfIndex,
	proto Protocol,
	addr netip.Addr,
	flags LookupFlags) (*AddressResolver, error)

NewAddressResolver creates a new AddressResolver.

AddressResolver resolves hostname by provided IP address. Roughly speaking, it does the work similar to gethostbyaddr using MDNS. Resolved information is reported via channel returned by the AddressResolver.Chan.

Function parameters:

  • clnt is the pointer to Client
  • ifidx is the network interface index. Use IfIndexUnspec to specify all interfaces.
  • proto is the IP4/IP6 protocol, used as transport for queries. If set to ProtocolUnspec, both protocols will be used.
  • addr is the IP address for which hostname discovery is performed.
  • flags provide some lookup options. See LookupFlags for details.

AddressResolver must be closed after use with the AddressResolver.Close function call.

func (*AddressResolver) Chan

func (resolver *AddressResolver) Chan() <-chan *AddressResolverEvent

Chan returns channel where [AddressResolverEvent]s are sent.

func (*AddressResolver) Close

func (resolver *AddressResolver) Close()

Close closes the AddressResolver and releases allocated resources. It closes the event channel, effectively unblocking pending readers.

Note, double close is safe.

func (*AddressResolver) Get

func (resolver *AddressResolver) Get(ctx context.Context) (
	*AddressResolverEvent, error)

Get waits for the next AddressResolverEvent.

It returns:

  • event, nil - if event available
  • nil, error - if context is canceled
  • nil, nil - if AddressResolver was closed

type AddressResolverEvent

type AddressResolverEvent struct {
	Event    ResolverEvent     // Event code
	IfIdx    IfIndex           // Network interface index
	Proto    Protocol          // Network protocol
	Err      ErrCode           // In a case of ResolverFailure
	Flags    LookupResultFlags // Lookup flags
	Addr     netip.Addr        // IP address (mirrored)
	Hostname string            // Hostname (resolved)
}

AddressResolverEvent represents events, generated by the AddressResolver.

type BrowserEvent

type BrowserEvent int

BrowserEvent is the CGo representation of AvahiBrowserEvent.

const (
	// New object discovered on the network.
	BrowserNew BrowserEvent = C.AVAHI_BROWSER_NEW

	// The object has been removed from the network.
	BrowserRemove BrowserEvent = C.AVAHI_BROWSER_REMOVE

	// One-time event, to notify the user that all entries from
	// the cache have been sent.
	BrowserCacheExhausted BrowserEvent = C.AVAHI_BROWSER_CACHE_EXHAUSTED

	// One-time event, to hint the user that more records
	// are unlikely to be shown in the near feature.
	BrowserAllForNow BrowserEvent = C.AVAHI_BROWSER_ALL_FOR_NOW

	// Browsing failed with a error.
	BrowserFailure BrowserEvent = C.AVAHI_BROWSER_FAILURE
)

BrowserEvent values:

func (BrowserEvent) String

func (e BrowserEvent) String() string

String returns a name of BrowserEvent

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client represents a client connection to the Avahi daemon.

Client may change its state dynamically. ClientState changes reported as a series of ClientEvent via the channel returned by the Client.Chan call.

When Client is not in use anymore, it must be closed using the Client.Close call to free associated resources. Closing the client closes its event notifications channel, effectively unblocking pending readers.

func NewClient

func NewClient(flags ClientFlags) (*Client, error)

NewClient creates a new Client.

func (*Client) Chan

func (clnt *Client) Chan() <-chan *ClientEvent

Chan returns a channel where ClientState change events are delivered.

Client.Close closes the sending side of this channel, effectively unblocking pending receivers.

func (*Client) Close

func (clnt *Client) Close()

Close closes a Client.

Note, double close is safe.

func (*Client) Get

func (clnt *Client) Get(ctx context.Context) (*ClientEvent, error)

Get waits for the next ClientEvent.

It returns:

  • event, nil - on success
  • nil, error - if context is canceled
  • nil, nil - if Client was closed

func (*Client) GetDomainName

func (clnt *Client) GetDomainName() string

GetDomainName returns domain name (e.g., "local")

func (*Client) GetHostFQDN

func (clnt *Client) GetHostFQDN() string

GetHostFQDN returns FQDN host name (e.g., "name.local")

func (*Client) GetHostName

func (clnt *Client) GetHostName() string

GetHostName returns host name (e.g., "name")

func (*Client) GetVersionString

func (clnt *Client) GetVersionString() string

GetVersionString returns avahi-daemon version string

type ClientEvent

type ClientEvent struct {
	State ClientState // New client state
	Err   ErrCode     // Only for ClientStateFailure
}

ClientEvent represents events, generated by the Client.

type ClientFlags

type ClientFlags int

ClientFlags modify certain aspects of the Client behavior.

const (
	// Loopback handling in Avahi is broken. In particular:
	//   - AvahiServiceResolver and AvahiAddressResolver
	//     return real host name and domain for loopback addresses
	//   - AvahiHostNameResolver doesn't resolve neither
	//     "localhost" nor "localhost.localdomain".
	//
	// Among other things, it breaks IPP over USB support. This
	// protocol uses Avahi for local service discovery and has
	// a strong requirement to use "localhost" as a hostname
	// when working with local services.
	//
	// If Client is created with this flag, the following
	// workarounds are enabled:
	//   - Host name and domain returned by ServiceResolver and
	//     AddressResolver for the loopback addresses (127.0.0.1
	//     and ::1) are forced to be localhost.localdomain
	//   - HostNameResolver resolves "localhost" and
	//     "localhost.localdomain" as 127.0.0.1.
	ClientLoopbackWorkarounds ClientFlags = 1 << iota
)

ClientFlags bits:

type ClientState

type ClientState int

ClientState represents a Client state.

const (
	// Avahi server is being registering host RRs on a network
	ClientStateRegistering ClientState = C.AVAHI_CLIENT_S_REGISTERING

	// Ahavi server is up and running
	ClientStateRunning ClientState = C.AVAHI_CLIENT_S_RUNNING

	// Avahi server was not able to register host RRs due to collision
	// with some another host.
	//
	// Administrator needs to update the host name to avoid the
	// collision.
	ClientStateCollision ClientState = C.AVAHI_CLIENT_S_COLLISION

	// Avahi server failure.
	ClientStateFailure ClientState = C.AVAHI_CLIENT_FAILURE

	// Avahi Client is trying to connect the server.
	ClientStateConnecting ClientState = C.AVAHI_CLIENT_CONNECTING
)

ClientState values:

func (ClientState) String

func (state ClientState) String() string

String returns name of the ClientState.

type DNSClass

type DNSClass int

DNSClass represents a DNS record class. See RFC1035, 3.2.4. for details.

const (
	DNSClassIN DNSClass = 1
)

DNSClass values:

type DNSType

type DNSType int

DNSType represents a DNS record type.

For details, see:

const (
	DNSTypeA     DNSType = 1  // IP4 host address
	DNSTypeNS    DNSType = 2  // An authoritative name server
	DNSTypeCNAME DNSType = 5  // The canonical name for an alias
	DNSTypeSOA   DNSType = 6  // SOA record
	DNSTypePTR   DNSType = 12 // A domain name pointer
	DNSTypeHINFO DNSType = 13 // Host information
	DNSTypeMX    DNSType = 15 // Mail exchange
	DNSTypeTXT   DNSType = 16 // Text strings
	DNSTypeAAAA  DNSType = 28 // IP6 host address (RFC3596)
	DNSTypeSRV   DNSType = 33 // Service record (RFC2782)
)

DNSType values

type DomainBrowser

type DomainBrowser struct {
	// contains filtered or unexported fields
}

DomainBrowser performs discovery of browsing and registration domains. See NewDomainBrowser and RFC6763, 11 for details.

func NewDomainBrowser

func NewDomainBrowser(
	clnt *Client,
	ifidx IfIndex,
	proto Protocol,
	domain string,
	btype DomainBrowserType,
	flags LookupFlags) (*DomainBrowser, error)

NewDomainBrowser creates a new DomainBrowser.

DomainBrowser constantly monitors the network for the available browsing/registration domains reports discovered information as a series of DomainBrowserEvent events via channel returned by the DomainBrowser.Chan

Avahi documentation doesn't give a lot of explanation about purpose of this functionality, but RFC6763, 11 gives some technical for details. In short, DomainBrowser performs DNS PTR queries in the following special domains:

DomainBrowserBrowse:            b._dns-sd._udp.<domain>.
DomainBrowserBrowseDefault:    db._dns-sd._udp.<domain>.
DomainBrowserRegister:          r._dns-sd._udp.<domain>.
DomainBrowserRegisterDefault:  dr._dns-sd._udp.<domain>.
DomainBrowserLegacy:           lb._dns-sd._udp.<domain>.

According to RFC6763, the <domain> is usually "local", (meaning "perform the query using link-local multicast") or it may be learned through some other mechanism, such as the DHCP "Domain" option (option code 15) RFC2132.

So network administrator can configure some MDNS responder located in the local network to provide this information for applications.

In fact, this mechanism seems to be rarely used in practice and provided here just for consistency.

Function parameters:

  • clnt is the pointer to Client
  • ifidx is the network interface index. Use IfIndexUnspec to monitor all interfaces.
  • proto is the IP4/IP6 protocol, used as transport for queries. If set to ProtocolUnspec, both protocols will be used.
  • domain is domain where domains are looked. If set to "", the default domain is used, which depends on a avahi-daemon configuration and usually is ".local"
  • btype specified a type of domains being browses. See DomainBrowserType for details.
  • flags provide some lookup options. See LookupFlags for details.

DomainBrowser must be closed after use with the DomainBrowser.Close function call.

func (*DomainBrowser) Chan

func (browser *DomainBrowser) Chan() <-chan *DomainBrowserEvent

Chan returns channel where [DomainBrowserEvent]s are sent.

func (*DomainBrowser) Close

func (browser *DomainBrowser) Close()

Close closes the DomainBrowser and releases allocated resources. It closes the event channel, effectively unblocking pending readers.

Note, double close is safe.

func (*DomainBrowser) Get

func (browser *DomainBrowser) Get(ctx context.Context) (*DomainBrowserEvent,
	error)

Get waits for the next DomainBrowserEvent.

It returns:

  • event, nil - if event available
  • nil, error - if context is canceled
  • nil, nil - if DomainBrowser was closed

type DomainBrowserEvent

type DomainBrowserEvent struct {
	Event  BrowserEvent      // Event code
	IfIdx  IfIndex           // Network interface index
	Proto  Protocol          // Network protocol
	Err    ErrCode           // In a case of BrowserFailure
	Flags  LookupResultFlags // Lookup flags
	Domain string            // Domain name
}

DomainBrowserEvent represents events, generated by the DomainBrowser.

type DomainBrowserType

type DomainBrowserType int

DomainBrowserType specifies a type of domain to browse for.

const (
	// Request list of available browsing domains.
	DomainBrowserBrowse DomainBrowserType = C.AVAHI_DOMAIN_BROWSER_BROWSE

	// Request the default browsing domain.
	DomainBrowserBrowseDefault DomainBrowserType = C.AVAHI_DOMAIN_BROWSER_BROWSE_DEFAULT

	// Request list of available registering domains.
	DomainBrowserRegister DomainBrowserType = C.AVAHI_DOMAIN_BROWSER_REGISTER

	// Request the default registering domains.
	DomainBrowserRegisterDefault DomainBrowserType = C.AVAHI_DOMAIN_BROWSER_REGISTER_DEFAULT

	// Request for "legacy browsing" domains. See RFC6763, 11 for details.
	DomainBrowserLegacy DomainBrowserType = C.AVAHI_DOMAIN_BROWSER_BROWSE_LEGACY
)

DomainBrowserType values:

type EntryGroup

type EntryGroup struct {
	// contains filtered or unexported fields
}

EntryGroup represents a group of RR records published via avahi-daemon.

All entries in the group are published or updated atomically.

func NewEntryGroup

func NewEntryGroup(clnt *Client) (*EntryGroup, error)

NewEntryGroup creates a new EntryGroup.

func (*EntryGroup) AddAddress

func (egrp *EntryGroup) AddAddress(
	rec *EntryGroupAddress,
	flags PublishFlags) error

AddAddress adds host/address pair.

func (*EntryGroup) AddRecord

func (egrp *EntryGroup) AddRecord(
	rec *EntryGroupRecord,
	flags PublishFlags) error

AddRecord adds a raw DNS record

func (*EntryGroup) AddService

func (egrp *EntryGroup) AddService(
	svc *EntryGroupService,
	flags PublishFlags) error

AddService adds a service registration

func (*EntryGroup) AddServiceSubtype

func (egrp *EntryGroup) AddServiceSubtype(
	svcid *EntryGroupServiceIdent,
	subtype string,
	flags PublishFlags) error

AddServiceSubtype adds subtype for the existent service.

func (*EntryGroup) Chan

func (egrp *EntryGroup) Chan() <-chan *EntryGroupEvent

Chan returns channel where [EntryGroupEvent]s are sent.

func (*EntryGroup) Close

func (egrp *EntryGroup) Close()

Close closed the EntryGroup.

Note, double close is safe

func (*EntryGroup) Commit

func (egrp *EntryGroup) Commit() error

Commit changes to the EntryGroup.

func (*EntryGroup) Get

func (egrp *EntryGroup) Get(ctx context.Context) (*EntryGroupEvent, error)

Get waits for the next EntryGroupEvent.

It returns:

  • event, nil - if event available
  • nil, error - if context is canceled
  • nil, nil - if EntryGroup was closed

func (*EntryGroup) IsEmpty

func (egrp *EntryGroup) IsEmpty() bool

IsEmpty reports if EntryGroup is empty.

func (*EntryGroup) Reset

func (egrp *EntryGroup) Reset() error

Reset (purge) the EntryGroup. This takes effect immediately (without commit).

func (*EntryGroup) UpdateServiceTxt

func (egrp *EntryGroup) UpdateServiceTxt(
	svcid *EntryGroupServiceIdent,
	txt []string,
	flags PublishFlags) error

UpdateServiceTxt updates TXT record for the existent service.

type EntryGroupAddress

type EntryGroupAddress struct {
	IfIdx    IfIndex    // Network interface index
	Proto    Protocol   // Publishing network protocol
	Hostname string     // Host name (use "" for default)
	Addr     netip.Addr // IP address
}

EntryGroupAddress represents a host address registration.

type EntryGroupEvent

type EntryGroupEvent struct {
	State EntryGroupState // Entry group state
	Err   ErrCode         // In a case of EntryGroupStateFailure
}

EntryGroupEvent represents an EntryGroup state change event.

type EntryGroupRecord

type EntryGroupRecord struct {
	IfIdx  IfIndex       // Network interface index
	Proto  Protocol      // Publishing network protocol
	Name   string        // Record name
	RClass DNSClass      // Record DNS class
	RType  DNSType       // Record DNS type
	TTL    time.Duration // DNS TTL, rounded to seconds and must fit int32
	RData  []byte        // Record data
}

EntryGroupRecord represents a raw DNS record that can be added to the EntryGroup.

type EntryGroupService

type EntryGroupService struct {
	IfIdx        IfIndex  // Network interface index
	Proto        Protocol // Publishing network protocol
	InstanceName string   // Service instance name
	SvcType      string   // Service type
	Domain       string   // Service domain (use "" for default)
	Hostname     string   // Host name (use "" for default)
	Port         int      // IP port
	Txt          []string // TXT record ("key=value"...)
}

EntryGroupService represents a service registration.

type EntryGroupServiceIdent

type EntryGroupServiceIdent struct {
	IfIdx        IfIndex  // Network interface index
	Proto        Protocol // Publishing network protocol
	InstanceName string   // Service instance name
	SvcType      string   // Service type
	Domain       string   // Service domain (use "" for default)
}

EntryGroupServiceIdent contains common set of parameters that identify a service in EntryGroup.

Please notice, services are identified by the DNS record name, which is EntryGroupServiceIdent.InstanceName, but all other parameters must match. In another words, you can't have two distinct entries in the EntryGroup with the same InstanceName and difference in other parameters (in particular, you can't define per-interface or per-protocol distinct entries).

type EntryGroupState

type EntryGroupState int

EntryGroupState represents an EntryGroup state.

const (
	// The group has not yet been committed
	EntryGroupStateUncommited EntryGroupState = C.AVAHI_ENTRY_GROUP_UNCOMMITED

	// The group is currently being registered
	EntryGroupStateRegistering EntryGroupState = C.AVAHI_ENTRY_GROUP_REGISTERING

	// The group has been successfully established
	EntryGroupStateEstablished EntryGroupState = C.AVAHI_ENTRY_GROUP_ESTABLISHED

	// A name collision for one of entries in the group has been detected.
	// The entries has been withdrawn.
	EntryGroupStateCollision EntryGroupState = C.AVAHI_ENTRY_GROUP_COLLISION

	// Some kind of failure has been detected, the entries has been withdrawn.
	EntryGroupStateFailure EntryGroupState = C.AVAHI_ENTRY_GROUP_FAILURE
)

EntryGroupState values:

func (EntryGroupState) String

func (state EntryGroupState) String() string

String returns a name of the EntryGroupState.

type ErrCode

type ErrCode int

ErrCode represents an Avahi error code

const (
	// No error
	NoError ErrCode = C.AVAHI_OK
	// Generic error code
	ErrFailure ErrCode = C.AVAHI_ERR_FAILURE
	// Object was in a bad state
	ErrBadState ErrCode = C.AVAHI_ERR_BAD_STATE
	// Invalid host name
	ErrInvalidHostName ErrCode = C.AVAHI_ERR_INVALID_HOST_NAME
	// Invalid domain name
	ErrInvalidDomainName ErrCode = C.AVAHI_ERR_INVALID_DOMAIN_NAME
	// No suitable network protocol available
	ErrNoNetwork ErrCode = C.AVAHI_ERR_NO_NETWORK
	// Invalid DNS TTL
	ErrInvalidTTL ErrCode = C.AVAHI_ERR_INVALID_TTL
	// RR key is pattern
	ErrIsPattern ErrCode = C.AVAHI_ERR_IS_PATTERN
	// Name collision
	ErrCollision ErrCode = C.AVAHI_ERR_COLLISION
	// Invalid RR
	ErrInvalidRecord ErrCode = C.AVAHI_ERR_INVALID_RECORD

	// Invalid service name
	ErrInvalidServiceName ErrCode = C.AVAHI_ERR_INVALID_SERVICE_NAME
	// Invalid service type
	ErrInvalidServiceType ErrCode = C.AVAHI_ERR_INVALID_SERVICE_TYPE
	// Invalid port number
	ErrInvalidPort ErrCode = C.AVAHI_ERR_INVALID_PORT
	// Invalid key
	ErrInvalidKey ErrCode = C.AVAHI_ERR_INVALID_KEY
	// Invalid address
	ErrInvalidAddress ErrCode = C.AVAHI_ERR_INVALID_ADDRESS
	// Timeout reached
	ErrTimeout ErrCode = C.AVAHI_ERR_TIMEOUT
	// Too many clients
	ErrTooManyClients ErrCode = C.AVAHI_ERR_TOO_MANY_CLIENTS
	// Too many objects
	ErrTooManyObjects ErrCode = C.AVAHI_ERR_TOO_MANY_OBJECTS
	// Too many entries
	ErrTooManyEntries ErrCode = C.AVAHI_ERR_TOO_MANY_ENTRIES
	// OS error
	ErrOS ErrCode = C.AVAHI_ERR_OS

	// Access denied
	ErrAccessDenied ErrCode = C.AVAHI_ERR_ACCESS_DENIED
	// Invalid operation
	ErrInvalidOperation ErrCode = C.AVAHI_ERR_INVALID_OPERATION
	// An unexpected D-Bus error occurred
	ErrDbusError ErrCode = C.AVAHI_ERR_DBUS_ERROR
	// Daemon connection failed
	ErrDisconnected ErrCode = C.AVAHI_ERR_DISCONNECTED
	// Memory exhausted
	ErrNoMemory ErrCode = C.AVAHI_ERR_NO_MEMORY
	// The object passed to this function was invalid
	ErrInvalidObject ErrCode = C.AVAHI_ERR_INVALID_OBJECT
	// Daemon not running
	ErrNoDaemon ErrCode = C.AVAHI_ERR_NO_DAEMON
	// Invalid interface
	ErrInvalidInterface ErrCode = C.AVAHI_ERR_INVALID_INTERFACE
	// Invalid protocol
	ErrInvalidProtocol ErrCode = C.AVAHI_ERR_INVALID_PROTOCOL
	// Invalid flags
	ErrInvalidFlags ErrCode = C.AVAHI_ERR_INVALID_FLAGS

	// Not found
	ErrNotFound ErrCode = C.AVAHI_ERR_NOT_FOUND
	// Configuration error
	ErrInvalidConfig ErrCode = C.AVAHI_ERR_INVALID_CONFIG
	// Verson mismatch
	ErrVersionMismatch ErrCode = C.AVAHI_ERR_VERSION_MISMATCH
	// Invalid service subtype
	ErrInvalidServiceSubtype ErrCode = C.AVAHI_ERR_INVALID_SERVICE_SUBTYPE
	// Invalid packet
	ErrInvalidPacket ErrCode = C.AVAHI_ERR_INVALID_PACKET
	// Invlaid DNS return code
	ErrInvalidDNSError ErrCode = C.AVAHI_ERR_INVALID_DNS_ERROR
	// DNS Error: Form error
	ErrDNSFormerr ErrCode = C.AVAHI_ERR_DNS_FORMERR
	// DNS Error: Server Failure
	ErrDNSSERVFAIL ErrCode = C.AVAHI_ERR_DNS_SERVFAIL
	// DNS Error: No such domain
	ErrDNSNXDOMAIN ErrCode = C.AVAHI_ERR_DNS_NXDOMAIN
	// DNS Error: Not implemented
	ErrDNSNotimp ErrCode = C.AVAHI_ERR_DNS_NOTIMP

	// DNS Error: Operation refused
	ErrDNSREFUSED ErrCode = C.AVAHI_ERR_DNS_REFUSED
	// DNS Error: YXDOMAIN
	ErrDNSYXDOMAIN ErrCode = C.AVAHI_ERR_DNS_YXDOMAIN
	// DNS Error: YXRRSET
	ErrDNSYXRRSET ErrCode = C.AVAHI_ERR_DNS_YXRRSET
	// DNS Error: NXRRSET
	ErrDNSNXRRSET ErrCode = C.AVAHI_ERR_DNS_NXRRSET
	// DNS Error: Not authorized
	ErrDNSNOTAUTH ErrCode = C.AVAHI_ERR_DNS_NOTAUTH
	// DNS Error: NOTZONE
	ErrDNSNOTZONE ErrCode = C.AVAHI_ERR_DNS_NOTZONE

	// Invalid RDATA
	ErrInvalidRDATA ErrCode = C.AVAHI_ERR_INVALID_RDATA
	// Invalid DNS class
	ErrInvalidDNSClass ErrCode = C.AVAHI_ERR_INVALID_DNS_CLASS
	// Invalid DNS type
	ErrInvalidDNSType ErrCode = C.AVAHI_ERR_INVALID_DNS_TYPE
	// Not supported
	ErrNotSupported ErrCode = C.AVAHI_ERR_NOT_SUPPORTED

	// Operation not permitted
	ErrNotPermitted ErrCode = C.AVAHI_ERR_NOT_PERMITTED
	// Invalid argument
	ErrInvalidArgument ErrCode = C.AVAHI_ERR_INVALID_ARGUMENT
	// Is empty
	ErrIsEmpty ErrCode = C.AVAHI_ERR_IS_EMPTY
	// The requested operation is invalid because it is redundant
	ErrNoChange ErrCode = C.AVAHI_ERR_NO_CHANGE
)

Error codes:

func (ErrCode) Error

func (err ErrCode) Error() string

Error returns error string. It implements error interface.

type HostNameResolver

type HostNameResolver struct {
	// contains filtered or unexported fields
}

HostNameResolver resolves hostname by IP address.

func NewHostNameResolver

func NewHostNameResolver(
	clnt *Client,
	ifidx IfIndex,
	proto Protocol,
	hostname string,
	addrproto Protocol,
	flags LookupFlags) (*HostNameResolver, error)

NewHostNameResolver creates a new HostNameResolver.

HostNameResolver resolves IP addresses by provided hostname. Roughly speaking, it does the work similar to gethostbyname using MDNS. Resolved information is reported via channel returned by the HostNameResolver.Chan.

This is important to understand the proper usage of the "proto" and "addrproto" parameters and difference between them. Please read the "IP4 vs IP6" section of the package Overview for technical details.

Function parameters:

  • clnt is the pointer to Client
  • ifidx is the network interface index. Use IfIndexUnspec to specify all interfaces.
  • proto is the IP4/IP6 protocol, used as transport for queries. If set to ProtocolUnspec, both protocols will be used.
  • hostname is the name of the host to lookup for
  • flags provide some lookup options. See LookupFlags for details.

HostNameResolver must be closed after use with the HostNameResolver.Close function call.

func (*HostNameResolver) Chan

func (resolver *HostNameResolver) Chan() <-chan *HostNameResolverEvent

Chan returns channel where [HostNameResolverEvent]s are sent.

func (*HostNameResolver) Close

func (resolver *HostNameResolver) Close()

Close closes the HostNameResolver and releases allocated resources. It closes the event channel, effectively unblocking pending readers.

Note, double close is safe

func (*HostNameResolver) Get

Get waits for the next HostNameResolverEvent.

It returns:

  • event, nil - if event available
  • nil, error - if context is canceled
  • nil, nil - if HostNameResolver was closed

type HostNameResolverEvent

type HostNameResolverEvent struct {
	Event    ResolverEvent     // Event code
	IfIdx    IfIndex           // Network interface index
	Proto    Protocol          // Network protocol
	Err      ErrCode           // In a case of ResolverFailure
	Flags    LookupResultFlags // Lookup flags
	Hostname string            // Hostname (mirrored)
	Addr     netip.Addr        // IP address (resolved)
}

HostNameResolverEvent represents events, generated by the HostNameResolver.

type IfIndex

type IfIndex int

IfIndex specifies network interface index

const (
	IfIndexUnspec IfIndex = C.AVAHI_PROTO_UNSPEC
)

IfIndex values:

func Loopback

func Loopback() (IfIndex, error)

Loopback returns index of the loopback network interface.

This function may fail, if net.Interfaces fails or there is no loopback interface in the response.

As this function is extremely unlikely to fail, you may consider using MustLoopback instead.

func MustLoopback

func MustLoopback() IfIndex

MustLoopback returns index of the loopback network interface.

This is convenience wrapper around the Loopback function. If Loopback function fails, MustLoopback panics instead of returning the error.

type LookupFlags

type LookupFlags int

LookupFlags provides some options for lookup functions

const (
	// Force lookup via wide area DNS
	LookupUseWideArea LookupFlags = C.AVAHI_LOOKUP_USE_WIDE_AREA

	// Force lookup via multicast DNS
	LookupUseMulticast LookupFlags = C.AVAHI_LOOKUP_USE_MULTICAST

	// When doing service resolving, don't lookup TXT record
	LookupNoTXT LookupFlags = C.AVAHI_LOOKUP_NO_TXT

	// When doing service resolving, don't lookup A/AAAA records
	LookupNoAddress LookupFlags = C.AVAHI_LOOKUP_NO_ADDRESS
)

LookupFlags values.

Please notice, LookupUseWideArea and LookupUseMulticast are mutually exclusive. Each of these flags forces the particular lookup methods, so if both are set, no valid lookup methods remains. The following simple table summarize usage of these flags:

Flags combination                         Meaning

0                                         WAN + Multicast
LookupUseWideArea                         WAN only
LookupUseMulticast                        mDNS only
LookupUseWideArea | LookupUseMulticast    Invalid

LookupNoTXT and LookupNoAddress are only meaningful for creating ServiceResolver with the NewServiceResolver function. Using them for any other purpose may result in ErrInvalidFlags error.

func (LookupFlags) String

func (flags LookupFlags) String() string

String returns LookupFlags as string, for debugging

type LookupResultFlags

type LookupResultFlags int

LookupResultFlags provides some additional information about lookup response.

const (
	// This response originates from the cache
	LookupResultCached LookupResultFlags = C.AVAHI_LOOKUP_RESULT_CACHED

	// This response originates from wide area DNS
	LookupResultWideArea LookupResultFlags = C.AVAHI_LOOKUP_RESULT_WIDE_AREA

	// This response originates from multicast DNS
	LookupResultMulticast LookupResultFlags = C.AVAHI_LOOKUP_RESULT_MULTICAST

	// This record/service resides on and was announced by the local host.
	// Only available in service and record browsers and only on
	// BrowserNew event.
	LookupResultLocal LookupResultFlags = C.AVAHI_LOOKUP_RESULT_LOCAL

	// This service belongs to the same local client as the browser object.
	// Only for service browsers and only on BrowserNew event.
	LookupResultOurOwn LookupResultFlags = C.AVAHI_LOOKUP_RESULT_OUR_OWN

	// The returned data was defined statically by server configuration.
	LookupResultStatic LookupResultFlags = C.AVAHI_LOOKUP_RESULT_STATIC
)

LookupResultFlags bits:

func (LookupResultFlags) String

func (flags LookupResultFlags) String() string

String returns LookupResultFlags as string, for debugging

type Poller

type Poller struct {
	// contains filtered or unexported fields
}

Poller is the convenience object, that implements a centralized events reception from multiple sources.

Multiple Event sources (Client, Browsers, Resolvers and EntryGroup) can be added to the Poller. Poller combines their events flows together and makes it available via single Poller.Poll API call.

func NewPoller

func NewPoller() *Poller

NewPoller creates a new Poller

func (*Poller) AddAddressResolver

func (p *Poller) AddAddressResolver(resolver *AddressResolver)

AddAddressResolver adds AddressResolver as the event source.

func (*Poller) AddClient

func (p *Poller) AddClient(clnt *Client)

AddClient adds Client as the event source.

func (*Poller) AddDomainBrowser

func (p *Poller) AddDomainBrowser(browser *DomainBrowser)

AddDomainBrowser adds DomainBrowser as the event source.

func (*Poller) AddHostNameResolver

func (p *Poller) AddHostNameResolver(resolver *HostNameResolver)

AddHostNameResolver adds HostNameResolver as the event source.

func (*Poller) AddRecordBrowser

func (p *Poller) AddRecordBrowser(browser *RecordBrowser)

AddRecordBrowser adds RecordBrowser as the event source.

func (*Poller) AddServiceBrowser

func (p *Poller) AddServiceBrowser(browser *ServiceBrowser)

AddServiceBrowser adds ServiceBrowser as the event source.

func (*Poller) AddServiceResolver

func (p *Poller) AddServiceResolver(resolver *ServiceResolver)

AddServiceResolver adds ServiceResolver as the event source.

func (*Poller) AddServiceTypeBrowser

func (p *Poller) AddServiceTypeBrowser(browser *ServiceTypeBrowser)

AddServiceTypeBrowser adds ServiceTypeBrowser as the event source.

func (*Poller) Poll

func (p *Poller) Poll(ctx context.Context) (any, error)

Poll waits for the next event from any of registered sources.

It returns:

  • nil, error - if context is canceled
  • event, nil - if event is available

The returned event is one of the following:

If source is added while Poll is active, it may or may not affect the pending Poll, no guarantees are provided here except for safety guarantees.

Events, received from the same source, are never reordered between each other, but events from different sources may be reordered.

Adding the same source to the multiple Pollers has roughly the same effect as reading the same channel from multiple goroutines and generally not recommended.

type Protocol

type Protocol int

Protocol specifies IP4/IP6 protocol

const (
	ProtocolIP4    Protocol = C.AVAHI_PROTO_INET
	ProtocolIP6    Protocol = C.AVAHI_PROTO_INET6
	ProtocolUnspec Protocol = C.AVAHI_PROTO_UNSPEC
)

Protocol values:

func (Protocol) String

func (proto Protocol) String() string

String returns name of the Protocol.

type PublishFlags

type PublishFlags int

PublishFlags represents flags for publishing functions

const (
	// RRset is intended to be unique
	PublishUnique PublishFlags = C.AVAHI_PUBLISH_UNIQUE
	// Though the RRset is intended to be unique no probes shall be sent
	PublishNoProbe PublishFlags = C.AVAHI_PUBLISH_NO_PROBE
	// Do not announce this RR to other hosts
	PublishNoAnnounce PublishFlags = C.AVAHI_PUBLISH_NO_ANNOUNCE
	// Allow multiple local records of this type
	PublishAllowMultiple PublishFlags = C.AVAHI_PUBLISH_ALLOW_MULTIPLE
)

PublishFlags for raw records:

const (
	// Don't create a reverse (PTR) entry
	PublishNoReverse PublishFlags = C.AVAHI_PUBLISH_NO_REVERSE
	// Do not implicitly add the local service cookie to TXT data
	PublishNoCookie PublishFlags = C.AVAHI_PUBLISH_NO_COOKIE
)

PublishFlags for address records:

const (
	// Update existing records instead of adding new ones
	PublishUpdate PublishFlags = C.AVAHI_PUBLISH_UPDATE
	// Register the record using wide area DNS (i.e. unicast DNS update)
	PublishUseWideArea PublishFlags = C.AVAHI_PUBLISH_USE_WIDE_AREA
	// Register the record using multicast DNS
	PublishUseMulticast PublishFlags = C.AVAHI_PUBLISH_USE_MULTICAST
)

Other PublishFlags:

func (PublishFlags) String

func (flags PublishFlags) String() string

String returns PublishFlags as string, for debugging

type RecordBrowser

type RecordBrowser struct {
	// contains filtered or unexported fields
}

RecordBrowser is the generic browser for resource records of the specified name, class and type.

func NewRecordBrowser

func NewRecordBrowser(
	clnt *Client,
	ifidx IfIndex,
	proto Protocol,
	name string,
	dnsclass DNSClass,
	dnstype DNSType,
	flags LookupFlags) (*RecordBrowser, error)

NewRecordBrowser creates a new RecordBrowser.

RecordBrowser is the generic browser for RRs of the specified name, class and type. It uses RecordBrowserEvent to report discovered information as via channel returned by the RecordBrowser.Chan

Function parameters:

  • clnt is the pointer to Client
  • ifidx is the network interface index. Use IfIndexUnspec to monitor all interfaces.
  • proto is the IP4/IP6 protocol, used as transport for queries. If set to ProtocolUnspec, both protocols will be used.
  • name is the RR name to look for
  • dnsclass is the DNS class to look for (most likely, DNSClassIN)
  • dnstype is the DNS record type.
  • flags provide some lookup options. See LookupFlags for details.

RecordBrowser must be closed after use with the RecordBrowser.Close function call.

func (*RecordBrowser) Chan

func (browser *RecordBrowser) Chan() <-chan *RecordBrowserEvent

Chan returns channel where [RecordBrowserEvent]s are sent.

func (*RecordBrowser) Close

func (browser *RecordBrowser) Close()

Close closes the RecordBrowser and releases allocated resources. It closes the event channel, effectively unblocking pending readers.

Note, double close is safe.

func (*RecordBrowser) Get

func (browser *RecordBrowser) Get(ctx context.Context) (*RecordBrowserEvent,
	error)

Get waits for the next RecordBrowserEvent.

It returns:

  • event, nil - if event available
  • nil, error - if context is canceled
  • nil, nil - if RecordBrowser was closed

type RecordBrowserEvent

type RecordBrowserEvent struct {
	Event  BrowserEvent      // Event code
	IfIdx  IfIndex           // Network interface index
	Proto  Protocol          // Network protocol
	Err    ErrCode           // In a case of BrowserFailure
	Flags  LookupResultFlags // Lookup flags
	Name   string            // Record name
	RClass DNSClass          // Record DNS class
	RType  DNSType           // Record DNS type
	RData  []byte            // Record data
}

RecordBrowserEvent represents events, generated by the RecordBrowser.

type ResolverEvent

type ResolverEvent int

ResolverEvent is the CGo representation of the AvahiResolverEvent.

const (
	// Successful resolving
	ResolverFound ResolverEvent = C.AVAHI_RESOLVER_FOUND

	// Resolving failed due to some reason.
	ResolverFailure ResolverEvent = C.AVAHI_RESOLVER_FAILURE
)

ResolverEvent values:

func (ResolverEvent) String

func (e ResolverEvent) String() string

String returns a name of ResolverEvent

type ServiceBrowser

type ServiceBrowser struct {
	// contains filtered or unexported fields
}

ServiceBrowser reports available services of the specified type.

Service type is a string that looks like "_http._tcp", "_ipp._tcp" and so on.

func NewServiceBrowser

func NewServiceBrowser(
	clnt *Client,
	ifidx IfIndex,
	proto Protocol,
	svctype, domain string,
	flags LookupFlags) (*ServiceBrowser, error)

NewServiceBrowser creates a new ServiceBrowser.

ServiceBrowser constantly monitors the network for the available services of specified type and reports discovered information as a series of ServiceBrowserEvent events via channel returned by the ServiceBrowser.Chan

Technically speaking, ServiceBrowser monitors network for the PTR records with the name <svctype>.<domain>, with domain defaulted to "local". I.e., if requested svctype is the "_http._tcp" and domain is "", it will look for the PTR records with name "_http._tcp.local.".

Function parameters:

  • clnt is the pointer to Client
  • ifidx is the network interface index. Use IfIndexUnspec to monitor all interfaces.
  • proto is the IP4/IP6 protocol, used as transport for queries. If set to ProtocolUnspec, both protocols will be used.
  • svctype is the service type we are looking for (e.g., "_http._tcp")
  • domain is domain where service is looked. If set to "", the default domain is used, which depends on a avahi-daemon configuration and usually is ".local"
  • flags provide some lookup options. See LookupFlags for details.

ServiceBrowser must be closed after use with the ServiceBrowser.Close function call.

func (*ServiceBrowser) Chan

func (browser *ServiceBrowser) Chan() <-chan *ServiceBrowserEvent

Chan returns channel where [ServiceBrowserEvent]s are sent.

func (*ServiceBrowser) Close

func (browser *ServiceBrowser) Close()

Close closes the ServiceBrowser and releases allocated resources. It closes the event channel, effectively unblocking pending readers.

Note, double close is safe.

func (*ServiceBrowser) Get

func (browser *ServiceBrowser) Get(ctx context.Context) (*ServiceBrowserEvent,
	error)

Get waits for the next ServiceBrowserEvent.

It returns:

  • event, nil - if event available
  • nil, error - if context is canceled
  • nil, nil - if ServiceBrowser was closed

type ServiceBrowserEvent

type ServiceBrowserEvent struct {
	Event        BrowserEvent      // Event code
	IfIdx        IfIndex           // Network interface index
	Proto        Protocol          // Network protocol
	Err          ErrCode           // In a case of BrowserFailure
	Flags        LookupResultFlags // Lookup flags
	InstanceName string            // Service instance name
	SvcType      string            // Service type
	Domain       string            // Service domain
}

ServiceBrowserEvent represents events, generated by the ServiceBrowser.

type ServiceResolver

type ServiceResolver struct {
	// contains filtered or unexported fields
}

ServiceResolver resolves hostname, IP address and TXT record of the discovered services.

func NewServiceResolver

func NewServiceResolver(
	clnt *Client,
	ifidx IfIndex,
	proto Protocol,
	instname, svctype, domain string,
	addrproto Protocol,
	flags LookupFlags) (*ServiceResolver, error)

NewServiceResolver creates a new ServiceResolver.

ServiceResolver resolves hostname, IP address and TXT record of the services, previously discovered by the ServiceBrowser by service instance name ([ServiceBrowserEvent.InstanceName]). Resolved information is reported via channel returned by the ServiceResolver.Chan.

If IP address and/or TXT record is not needed, resolving of these parameters may be suppressed, using LookupNoAddress/LookupNoTXT LookupFlags.

Please notice, it is a common practice to register a service with a zero port value as a "placeholder" for missed service. For example, printers always register the "_printer._tcp" service to reserve the service name, but if LPD protocol is actually not supported, it will be registered with zero port.

This is important to understand the proper usage of the "proto" and "addrproto" parameters and difference between them. Please read the "IP4 vs IP6" section of the package Overview for technical details.

Function parameters:

  • clnt is the pointer to Client
  • ifidx is the network interface index. Use IfIndexUnspec to specify all interfaces.
  • proto is the IP4/IP6 protocol, used as transport for queries. If set to ProtocolUnspec, both protocols will be used.
  • instname is the service instance name, as reported by [ServiceBrowserEvent.InstanceName]
  • svctype is the service type we are looking for (e.g., "_http._tcp")
  • domain is domain where service is looked. If set to "", the default domain is used, which depends on a avahi-daemon configuration and usually is ".local"
  • addrproto specifies a protocol family of IP addresses we are interested in. See explanation above for details.
  • flags provide some lookup options. See LookupFlags for details.

ServiceResolver must be closed after use with the ServiceResolver.Close function call.

func (*ServiceResolver) Chan

func (resolver *ServiceResolver) Chan() <-chan *ServiceResolverEvent

Chan returns channel where [ServiceResolverEvent]s are sent.

func (*ServiceResolver) Close

func (resolver *ServiceResolver) Close()

Close closes the ServiceResolver and releases allocated resources. It closes the event channel, effectively unblocking pending readers.

func (*ServiceResolver) Get

func (resolver *ServiceResolver) Get(ctx context.Context) (
	*ServiceResolverEvent, error)

Get waits for the next ServiceResolverEvent.

It returns:

  • event, nil - if event available
  • nil, error - if context is canceled
  • nil, nil - if ServiceResolver was closed

type ServiceResolverEvent

type ServiceResolverEvent struct {
	Event        ResolverEvent     // Event code
	IfIdx        IfIndex           // Network interface index
	Proto        Protocol          // Network protocol
	Err          ErrCode           // In a case of ResolverFailure
	Flags        LookupResultFlags // Lookup flags
	InstanceName string            // Service instance name (mirrored)
	SvcType      string            // Service type (mirrored)
	Domain       string            // Service domain (mirrored)
	Hostname     string            // Service hostname (resolved)
	Port         uint16            // Service IP port (resolved)
	Addr         netip.Addr        // Service IP address (resolved)
	Txt          []string          // TXT record ("key=value"...) (resolved)
}

ServiceResolverEvent represents events, generated by the ServiceResolver.

Notes:

  • Addr is not available, if NewServiceResolver is called with the LookupNoAddress flag
  • Txt is not available, if NewServiceResolver is called with the LookupNoTXT flag
  • Port is always available, but may be 0, which indicates, that service doesn't actually process responses and exists as a service instance name placeholder only.

func (*ServiceResolverEvent) FQDN

func (evnt *ServiceResolverEvent) FQDN() string

FQDN returns a Fully Qualified Domain Name by joining Hostname and Domain.

type ServiceTypeBrowser

type ServiceTypeBrowser struct {
	// contains filtered or unexported fields
}

ServiceTypeBrowser reports available service types across the network.

If you a looking for services of the particular type, probably you need to use ServiceBrowser instead.

func NewServiceTypeBrowser

func NewServiceTypeBrowser(
	clnt *Client,
	ifidx IfIndex,
	proto Protocol,
	domain string,
	flags LookupFlags) (*ServiceTypeBrowser, error)

NewServiceTypeBrowser creates a new ServiceTypeBrowser.

ServiceTypeBrowser constantly monitors the network for available service types and reports discovered information as a series of ServiceTypeBrowserEvent events via channel returned by the ServiceTypeBrowser.Chan.

Technically speaking, NewServiceTypeBrowser monitors network for the PTR records with the name _services._dns-sd._udp.<domain>, with the domain defaulted to "local", which yields a list of all available services on the network. See RFC6763, 9 for details.

Function parameters:

  • clnt is the pointer to Client
  • ifidx is the network interface index. Use IfIndexUnspec to monitor all interfaces.
  • proto is the IP4/IP6 protocol, used as transport for queries. If set to ProtocolUnspec, both protocols will be used.
  • domain is domain where service is looked. If set to "", the default domain is used, which depends on a avahi-daemon configuration and usually is ".local"
  • flags provide some lookup options. See LookupFlags for details.

ServiceTypeBrowser must be closed after use with the ServiceTypeBrowser.Close function call.

func (*ServiceTypeBrowser) Chan

func (browser *ServiceTypeBrowser) Chan() <-chan *ServiceTypeBrowserEvent

Chan returns channel where [ServiceTypeBrowserEvent]s are sent.

func (*ServiceTypeBrowser) Close

func (browser *ServiceTypeBrowser) Close()

Close closes the ServiceTypeBrowser and releases allocated resources. It closes the event channel, effectively unblocking pending readers.

Note, double close is safe.

func (*ServiceTypeBrowser) Get

Get waits for the next ServiceTypeBrowserEvent.

It returns:

  • event, nil - if event available
  • nil, error - if context is canceled
  • nil, nil - if ServiceTypeBrowser was closed

type ServiceTypeBrowserEvent

type ServiceTypeBrowserEvent struct {
	Event   BrowserEvent      // Event code
	IfIdx   IfIndex           // Network interface index
	Proto   Protocol          // Network protocol
	Err     ErrCode           // In a case of BrowserFailure
	Flags   LookupResultFlags // Lookup flags
	SvcType string            // Service type
	Domain  string            // Service domain
}

ServiceTypeBrowserEvent represents events, generated by the ServiceTypeBrowser.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL