Skip to content

Golang (cgo) binding for Avahi (Multicast DNS and Device Discovery, mDNS/DNS-SD)

License

Notifications You must be signed in to change notification settings

alexpevzner/go-avahi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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)
			}
		}
	}
}

About

Golang (cgo) binding for Avahi (Multicast DNS and Device Discovery, mDNS/DNS-SD)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published