-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathbackend_avahi.go
More file actions
142 lines (121 loc) · 4.46 KB
/
backend_avahi.go
File metadata and controls
142 lines (121 loc) · 4.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package zeroconf
import (
"fmt"
"github.com/godbus/dbus/v5"
)
const (
avahiService = "org.freedesktop.Avahi"
avahiServerPath = "/"
avahiServerIface = "org.freedesktop.Avahi.Server"
avahiEntryGroupIface = "org.freedesktop.Avahi.EntryGroup"
// Avahi constants
avahiIfUnspec = int32(-1) // AVAHI_IF_UNSPEC - use all interfaces
avahiProtoUnspec = int32(-1) // AVAHI_PROTO_UNSPEC - use both IPv4 and IPv6
)
// AvahiRegistrar implements ServiceRegistrar using avahi-daemon via D-Bus.
// This allows go-librespot to share the mDNS responder with other services
// on the system instead of running its own.
//
// Compatibility: Requires avahi-daemon 0.6.x or later (uses stable D-Bus API).
// Tested with avahi 0.7 and 0.8.
type AvahiRegistrar struct {
conn *dbus.Conn
entryGroup dbus.BusObject
version string
}
// NewAvahiRegistrar creates a new avahi-daemon service registrar.
// It connects to the system D-Bus and prepares to register services via avahi.
func NewAvahiRegistrar() (*AvahiRegistrar, error) {
conn, err := dbus.SystemBus()
if err != nil {
return nil, fmt.Errorf("failed to connect to system bus: %w", err)
}
// Verify avahi-daemon is available by calling GetHostName (available in all versions)
server := conn.Object(avahiService, avahiServerPath)
var hostname string
err = server.Call(avahiServerIface+".GetHostName", 0).Store(&hostname)
if err != nil {
_ = conn.Close()
return nil, fmt.Errorf("failed to connect to avahi-daemon (is it running?): %w", err)
}
// Try to get version for logging (optional, may fail on older versions)
version := getAvahiVersion(server)
return &AvahiRegistrar{conn: conn, version: version}, nil
}
// getAvahiVersion attempts to retrieve the avahi-daemon version.
// Returns "unknown" if version cannot be determined.
func getAvahiVersion(server dbus.BusObject) string {
// Try GetVersionString first (available in avahi 0.8+)
var versionStr string
if err := server.Call(avahiServerIface+".GetVersionString", 0).Store(&versionStr); err == nil {
return versionStr
}
// Try GetAPIVersion (returns a single uint32)
var apiVersion uint32
if err := server.Call(avahiServerIface+".GetAPIVersion", 0).Store(&apiVersion); err == nil {
return fmt.Sprintf("API v%d", apiVersion)
}
return "unknown"
}
// Version returns the avahi-daemon version string.
func (a *AvahiRegistrar) Version() string {
return a.version
}
// Register publishes the service via avahi-daemon.
func (a *AvahiRegistrar) Register(name, serviceType, domain string, port int, txt []string) error {
server := a.conn.Object(avahiService, avahiServerPath)
// Create a new entry group for our service
var groupPath dbus.ObjectPath
err := server.Call(avahiServerIface+".EntryGroupNew", 0).Store(&groupPath)
if err != nil {
return fmt.Errorf("failed to create entry group: %w", err)
}
a.entryGroup = a.conn.Object(avahiService, groupPath)
// Convert TXT records to [][]byte format required by avahi
txtBytes := make([][]byte, len(txt))
for i, t := range txt {
txtBytes[i] = []byte(t)
}
// AddService signature: iiussssqaay
// interface (i): network interface index, -1 for all
// protocol (i): IP protocol, -1 for both IPv4/IPv6
// flags (u): publish flags, 0 for default
// name (s): service instance name
// type (s): service type (e.g., "_spotify-connect._tcp")
// domain (s): domain to publish in (e.g., "local")
// host (s): hostname, empty for default
// port (q): port number (uint16)
// txt (aay): TXT record data as array of byte arrays
err = a.entryGroup.Call(avahiEntryGroupIface+".AddService", 0,
avahiIfUnspec, // interface
avahiProtoUnspec, // protocol
uint32(0), // flags
name, // service name
serviceType, // service type
domain, // domain
"", // host (empty = use default hostname)
uint16(port), // port
txtBytes, // TXT records
).Err
if err != nil {
return fmt.Errorf("failed to add service: %w", err)
}
// Commit the entry group to publish the service
err = a.entryGroup.Call(avahiEntryGroupIface+".Commit", 0).Err
if err != nil {
return fmt.Errorf("failed to commit entry group: %w", err)
}
return nil
}
// Shutdown removes the service from avahi and releases resources.
func (a *AvahiRegistrar) Shutdown() {
if a.entryGroup != nil {
// Free the entry group (this also unpublishes the service)
_ = a.entryGroup.Call(avahiEntryGroupIface+".Free", 0).Err
a.entryGroup = nil
}
if a.conn != nil {
_ = a.conn.Close()
a.conn = nil
}
}