Replace UPnP/SSDP with DNS-SD #104262

Open
opened 2023-10-21 13:19:45 +02:00 by Sybren A. Stüvel · 0 comments

Currently Flamenco uses UPnP/SSDP for auto-discovery of Managers on the local network. This has some issues, most notably other applications hogging the necessary UDP port (Spotify is a common example).

It's likely better to move to DNS Service Discovery (DNS-SD), which uses multicast DNS. It's widely used for exposing services & devices on a network; Chromecasts, ESPHome devices, and printers are commonly exposed like this.

Since DNS-SD also works by listening on a multicast UDP port, it could have the same compatibility issues as UPnP/SSDP. To resolve that, Flamenco should rely on platform-specific APIs as much as possible, so that a system daemon can handle the actual network traffic.

The code below can be used to expose a service via DNS-SD. It seems to work well on my Windows 10 Pro machine at home, but hasn't been tested widely. It could be used as a fallback, when the system-specific approaches above fail or are otherwise unavailable.

package main

import (
	"context"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/brutella/dnssd"
	"github.com/mattn/go-colorable"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func main() {
	output := zerolog.ConsoleWriter{Out: colorable.NewColorableStdout(), TimeFormat: time.RFC3339}
	log.Logger = log.Output(output)

	managerConfig := dnssd.Config{
		Name: "manager",
		Type: "_flamenco._tcp",
		Port: 8080,
	}
	managerService, err := dnssd.NewService(managerConfig)
	if err != nil {
		log.Fatal().Err(err).Msg("cannot start dns-sd service")
	}

	responder, err := dnssd.NewResponder()
	if err != nil {
		log.Fatal().Err(err).Msg("cannot create new responder")
	}
	hdl, err := responder.Add(managerService)
	if err != nil {
		log.Fatal().Err(err).Msg("cannot add service to responder")
	}

	hdl.UpdateText(map[string]string{
		"version": "3.3-beta0",
	}, responder)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		err := responder.Respond(ctx)
		if err != nil {
			log.Fatal().Err(err).Msg("error from responder")
		}
	}()

	log.Info().Msg("serving queries")

	// Clean exit.
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
	select {
	case <-sig:
		// Exit by user
	case <-time.After(time.Minute * 5):
		// Exit by timeout
	}

	log.Info().Msg("Shutting down")
	cancel()
	wg.Wait()
}
Currently Flamenco uses UPnP/SSDP for auto-discovery of Managers on the local network. This has some issues, most notably other applications hogging the necessary UDP port (Spotify is a common example). It's likely better to move to DNS Service Discovery (DNS-SD), which uses multicast DNS. It's widely used for exposing services & devices on a network; Chromecasts, ESPHome devices, and printers are commonly exposed like this. Since DNS-SD also works by listening on a multicast UDP port, it could have the same compatibility issues as UPnP/SSDP. To resolve that, Flamenco should rely on platform-specific APIs as much as possible, so that a system daemon can handle the actual network traffic. - Windows: https://learn.microsoft.com/en-us/windows/win32/api/windns/nf-windns-dnsserviceregister - macOS: likely via the Bonjour API; specifics unknown. - Linux: likely via systemd; specifics unknown. The code below can be used to expose a service via DNS-SD. It seems to work well on my Windows 10 Pro machine at home, but hasn't been tested widely. It could be used as a fallback, when the system-specific approaches above fail or are otherwise unavailable. ```go package main import ( "context" "os" "os/signal" "sync" "syscall" "time" "github.com/brutella/dnssd" "github.com/mattn/go-colorable" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main() { output := zerolog.ConsoleWriter{Out: colorable.NewColorableStdout(), TimeFormat: time.RFC3339} log.Logger = log.Output(output) managerConfig := dnssd.Config{ Name: "manager", Type: "_flamenco._tcp", Port: 8080, } managerService, err := dnssd.NewService(managerConfig) if err != nil { log.Fatal().Err(err).Msg("cannot start dns-sd service") } responder, err := dnssd.NewResponder() if err != nil { log.Fatal().Err(err).Msg("cannot create new responder") } hdl, err := responder.Add(managerService) if err != nil { log.Fatal().Err(err).Msg("cannot add service to responder") } hdl.UpdateText(map[string]string{ "version": "3.3-beta0", }, responder) ctx, cancel := context.WithCancel(context.Background()) defer cancel() wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() err := responder.Respond(ctx) if err != nil { log.Fatal().Err(err).Msg("error from responder") } }() log.Info().Msg("serving queries") // Clean exit. sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGTERM) select { case <-sig: // Exit by user case <-time.After(time.Minute * 5): // Exit by timeout } log.Info().Msg("Shutting down") cancel() wg.Wait() } ```
Sybren A. Stüvel added the
Type
Design
label 2023-10-21 13:19:45 +02:00
Sybren A. Stüvel added the
Priority
Low
label 2024-03-04 14:19:24 +01:00
Sign in to join this conversation.
No Milestone
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: studio/flamenco#104262
No description provided.