Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

var defaultTTL uint32 = 3200

var hostnameFunc = os.Hostname

type serverOpts struct {
ttl uint32
connFactory api.ConnectionFactory
Expand Down Expand Up @@ -88,13 +90,21 @@

var err error
if entry.HostName == "" {
entry.HostName, err = os.Hostname()
entry.HostName, err = hostnameFunc()
if err != nil {
return nil, fmt.Errorf("could not determine host")
}
}

if !strings.HasSuffix(trimDot(entry.HostName), entry.Domain) {
// On MacOS os.Hostname() returns the hostname with the domain at the end but without trailing "."
// e.g. "MacBook-Air.local" in this case we simply add a dot to get a fully qualified mdns domain
if strings.HasSuffix(entry.HostName, trimDot(entry.Domain)) {
entry.HostName += "."
}

// Ensure domain has trailing dot
entry.Domain = fmt.Sprintf("%s.", trimDot(entry.Domain))
if !strings.HasSuffix(entry.HostName, entry.Domain) {
entry.HostName = fmt.Sprintf("%s.%s.", trimDot(entry.HostName), trimDot(entry.Domain))
}

Expand Down Expand Up @@ -147,7 +157,15 @@
return nil, fmt.Errorf("missing port")
}

if !strings.HasSuffix(trimDot(entry.HostName), entry.Domain) {
// On MacOS os.Hostname() returns the hostname with the domain at the end but without trailing "."
// e.g. "MacBook-Air.local" in this case we simply add a dot to get a fully qualified mdns domain
if strings.HasSuffix(entry.HostName, trimDot(entry.Domain)) {
entry.HostName += "."
}

// Ensure domain has trailing dot
entry.Domain = fmt.Sprintf("%s.", trimDot(entry.Domain))
if !strings.HasSuffix(entry.HostName, entry.Domain) {
entry.HostName = fmt.Sprintf("%s.%s.", trimDot(entry.HostName), trimDot(entry.Domain))
}

Expand Down Expand Up @@ -291,10 +309,10 @@
close(s.shouldShutdown)

if s.ipv4conn != nil {
s.ipv4conn.Close()

Check failure on line 312 in server.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `s.ipv4conn.Close` is not checked (errcheck)
}
if s.ipv6conn != nil {
s.ipv6conn.Close()

Check failure on line 315 in server.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `s.ipv6conn.Close` is not checked (errcheck)
}

// Wait for connection and routines to be closed
Expand Down Expand Up @@ -637,7 +655,7 @@
activeIfaces := mergeInterfaces(ipv4ActiveIfaces, ipv6ActiveIfaces)
for _, intf := range activeIfaces {
resp := new(dns.Msg)
resp.MsgHdr.Response = true

Check failure on line 658 in server.go

View workflow job for this annotation

GitHub Actions / lint

QF1008: could remove embedded field "MsgHdr" from selector (staticcheck)
// TODO: make response authoritative if we are the publisher
resp.Compress = true
resp.Answer = []dns.RR{}
Expand All @@ -660,7 +678,7 @@
// announceText sends a Text announcement with cache flush enabled
func (s *Server) announceText() {
resp := new(dns.Msg)
resp.MsgHdr.Response = true

Check failure on line 681 in server.go

View workflow job for this annotation

GitHub Actions / lint

QF1008: could remove embedded field "MsgHdr" from selector (staticcheck)

txt := &dns.TXT{
Hdr: dns.RR_Header{
Expand Down
160 changes: 160 additions & 0 deletions service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package zeroconf

import (
"context"
"fmt"
"log"
"testing"
"time"
Expand Down Expand Up @@ -202,3 +203,162 @@ func TestSubtype(t *testing.T) {
}
})
}

func TestFullyQualifiedDomain(t *testing.T) {
discoverEntry := func(t *testing.T, instance, service, domain string) *ServiceEntry {
t.Helper()

time.Sleep(time.Second)

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

entries := make(chan *ServiceEntry, 100)
expired := make(chan *ServiceEntry, 100)
if err := Browse(ctx, service, fmt.Sprintf("%s.", trimDot(domain)), entries, expired); err != nil {
t.Fatalf("Expected browse success, but got %v", err)
}
<-ctx.Done()

if len(entries) == 0 {
t.Fatal("Expected at least one service entry, but got none")
}

for len(entries) > 0 {
result := <-entries
if result.Instance == instance {
return result
}
}

t.Fatalf("Expected service entry for instance %q, but did not find it", instance)
return nil
}

t.Run("Register", func(t *testing.T) {
testCases := []struct {
name string
hostName string
domain string
expectedHost string
}{
{
name: "short hostname without trailing dot in domain",
hostName: "Laptop-1",
domain: "local",
expectedHost: "Laptop-1.local.",
},
{
name: "short hostname with trailing dot in domain",
hostName: "Laptop-1",
domain: "local.",
expectedHost: "Laptop-1.local.",
},
{
name: "hostname including domain without trailing dot in domain",
hostName: "MacBook-Air.local",
domain: "local",
expectedHost: "MacBook-Air.local.",
},
{
name: "hostname including domain with trailing dot in domain",
hostName: "MacBook-Air.local",
domain: "local.",
expectedHost: "MacBook-Air.local.",
},
}

for i, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
origHostnameFunc := hostnameFunc
hostnameFunc = func() (string, error) {
return tc.hostName, nil
}
t.Cleanup(func() {
hostnameFunc = origHostnameFunc
})

instance := fmt.Sprintf("test-register-fqdn-%d", i)
service := fmt.Sprintf("_fqdn-register-%d._tcp", i)

server, err := Register(instance, service, tc.domain, mdnsPort, []string{"txtv=0"}, nil)
if err != nil {
t.Fatalf("error while registering mdns service: %s", err)
}
t.Cleanup(server.Shutdown)

result := discoverEntry(t, instance, service, tc.domain)
if result.Domain != "local." {
t.Fatalf("Expected domain is local., but got %s", result.Domain)
}
if result.HostName != tc.expectedHost {
t.Fatalf("Expected hostname is %s, but got %s", tc.expectedHost, result.HostName)
}
})
}
})

t.Run("RegisterProxy", func(t *testing.T) {
testCases := []struct {
name string
hostName string
domain string
expectedHost string
}{
{
name: "short hostname without trailing dot in domain",
hostName: "Laptop-1",
domain: "local",
expectedHost: "Laptop-1.local.",
},
{
name: "short hostname with trailing dot in domain",
hostName: "Laptop-1",
domain: "local.",
expectedHost: "Laptop-1.local.",
},
{
name: "hostname including domain without trailing dot in domain",
hostName: "MacBook-Air.local",
domain: "local",
expectedHost: "MacBook-Air.local.",
},
{
name: "hostname including domain with trailing dot in domain",
hostName: "MacBook-Air.local",
domain: "local.",
expectedHost: "MacBook-Air.local.",
},
}

for i, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
instance := fmt.Sprintf("test-registerproxy-fqdn-%d", i)
service := fmt.Sprintf("_fqdn-registerproxy-%d._tcp", i)

server, err := RegisterProxy(
instance,
service,
tc.domain,
mdnsPort,
tc.hostName,
[]string{"192.168.1.100"},
[]string{"txtv=0"},
nil,
)
if err != nil {
t.Fatalf("error while registering proxy mdns service: %s", err)
}
t.Cleanup(server.Shutdown)

result := discoverEntry(t, instance, service, tc.domain)
if result.Domain != "local." {
t.Fatalf("Expected domain is local., but got %s", result.Domain)
}
if result.HostName != tc.expectedHost {
t.Fatalf("Expected hostname is %s, but got %s", tc.expectedHost, result.HostName)
}
})
}
})
}
Loading