Update mDNS

This commit is contained in:
Neil Alexander 2021-05-31 21:29:43 +01:00
parent 2ec3c4a0d5
commit b561c540ca
5 changed files with 163 additions and 196 deletions

View file

@ -25,13 +25,9 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
<<<<<<< HEAD
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/mdns"
=======
"github.com/yggdrasil-network/yggdrasil-go/src/core"
>>>>>>> future
"github.com/yggdrasil-network/yggdrasil-go/src/module"
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
@ -326,31 +322,7 @@ func main() {
// Capture the service being stopped on Windows.
<-c
minwinsvc.SetOnExit(n.shutdown)
<<<<<<< HEAD
defer n.shutdown()
// Wait for the terminate/interrupt signal. Once a signal is received, the
// deferred Stop function above will run which will shut down TUN/TAP.
for {
select {
case _ = <-c:
goto exit
case _ = <-r:
if *useconffile != "" {
cfg = readConfig(useconf, useconffile, normaliseconf)
logger.Infoln("Reloading configuration from", *useconffile)
n.core.UpdateConfig(cfg)
n.tuntap.UpdateConfig(cfg)
n.multicast.UpdateConfig(cfg)
n.mdns.UpdateConfig(cfg)
} else {
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
}
}
}
exit:
=======
n.shutdown()
>>>>>>> future
}
func (n *node) shutdown() {

8
go.mod
View file

@ -5,6 +5,7 @@ go 1.16
require (
github.com/Arceliar/ironwood v0.0.0-20210531083357-daeea6bc386a
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979
github.com/brutella/dnssd v1.2.0
github.com/cheggaaa/pb/v3 v3.0.6
github.com/fatih/color v1.10.0 // indirect
github.com/gologme/log v1.2.0
@ -12,13 +13,14 @@ require (
github.com/hjson/hjson-go v3.1.0+incompatible
github.com/kardianos/minwinsvc v1.0.0
github.com/mattn/go-runewidth v0.0.10 // indirect
github.com/miekg/dns v1.1.41 // indirect
github.com/mitchellh/mapstructure v1.4.1
github.com/rivo/uniseg v0.2.0 // indirect
github.com/vishvananda/netlink v1.1.0
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b
golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44
golang.org/x/text v0.3.6
golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf
golang.zx2c4.com/wireguard/windows v0.3.8
)

22
go.sum
View file

@ -4,6 +4,8 @@ github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/brutella/dnssd v1.2.0 h1:bgrSycmZ2+u4BoJxRf1BzSlnViSAfeXWVdujqjLA004=
github.com/brutella/dnssd v1.2.0/go.mod h1:FpJqlQ8+XU6w1vbnG1zJiQPTRE5fvQIRdrcBojMVuuQ=
github.com/cheggaaa/pb/v3 v3.0.6 h1:ULPm1wpzvj60FvmCrX7bIaB80UgbhI+zSaQJKRfCbAs=
github.com/cheggaaa/pb/v3 v3.0.6/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -28,6 +30,9 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@ -39,13 +44,19 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1 h1:4qWs8cYYH6PoEFy4dfhDFgoMGkwAcETd+MmPdCPMzUc=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -56,17 +67,20 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225014209-683adc9d29d7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305215415-5cdee2b1b5a0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b h1:ggRgirZABFolTmi3sn6Ivd9SipZwLedQ5wR0aAKnFxU=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c h1:SW/oilbeWd6f32u3ZvuYGqZ+wivcp//I3Dy/gByk7Wk=
golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.zx2c4.com/wireguard v0.0.0-20210225140808-70b7b7158fc9/go.mod h1:39ZQQ95hUxDxT7opsWy/rtfgvXXc8s30qfZ02df69Fo=
golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf h1:AtdIMfzvVNPXN4kVY/yWS8mvpQogSwtCRJk2y/LBPpg=

View file

@ -104,7 +104,6 @@ func GenerateConfig() *NodeConfig {
cfg.InterfacePeers = map[string][]string{}
cfg.AllowedPublicKeys = []string{}
cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces
cfg.MulticastDNSInterfaces = defaults.GetDefaults().DefaultMulticastDNSInterfaces
cfg.IfName = defaults.GetDefaults().DefaultIfName
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
cfg.SessionFirewall.Enable = false

View file

@ -1,68 +1,76 @@
package mdns
import (
"bytes"
"context"
"crypto/ed25519"
"encoding/hex"
"errors"
"fmt"
"net"
"net/url"
"regexp"
"strings"
"time"
"github.com/Arceliar/phony"
"github.com/brutella/dnssd"
"github.com/gologme/log"
"github.com/neilalexander/mdns"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
const (
MDNSService = "_yggdrasil._tcp"
MDNSDomain = "yggdrasil.local."
"github.com/yggdrasil-network/yggdrasil-go/src/core"
)
type MDNS struct {
phony.Inbox
core *yggdrasil.Core //
config *config.NodeState //
log *log.Logger //
info []string //
instance string //
_running bool // is mDNS running?
_exprs []*regexp.Regexp // mDNS interfaces
_servers map[string]map[string]*mDNSServer // intf -> ip -> *mDNSServer
core *core.Core
config *config.NodeState
context context.Context // global context
cancel context.CancelFunc // cancels all interfaces
log *log.Logger
info map[string]string
responder dnssd.Responder
_running bool
_exprs []*regexp.Regexp
_servers map[string]map[string]*mDNSInterface // intf -> ip -> *mDNSServer
}
type mDNSServer struct {
type mDNSInterface struct {
context context.Context // parent context is in the MDNS struct
cancel context.CancelFunc // cancels this interface only
mdns *MDNS
ourIP net.IP
addr *net.TCPAddr
intf net.Interface
server *mdns.Server
listener *yggdrasil.TcpListener
stop chan struct{}
time time.Time
service dnssd.ServiceHandle
listener *core.TcpListener
}
func (m *MDNS) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error {
m.core = core
var protoVersion = fmt.Sprintf("%d.%d", core.ProtocolMajorVersion, core.ProtocolMinorVersion)
func (m *MDNS) Init(c *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error {
pk := c.PrivateKey().Public().(ed25519.PublicKey)
m.context, m.cancel = context.WithCancel(context.Background())
m.core = c
m.config = state
m.log = log
m.info = []string{
fmt.Sprintf("ed25519=%s", core.SigningPublicKey()),
fmt.Sprintf("curve25519=%s", core.EncryptionPublicKey()),
fmt.Sprintf("versionmajor=%d", yggdrasil.ProtocolMajorVersion),
fmt.Sprintf("versionminor=%d", yggdrasil.ProtocolMinorVersion),
}
if nodeid := core.NodeID(); nodeid != nil {
m.instance = hex.EncodeToString((*nodeid)[:])[:16]
m.info = map[string]string{
"ed25519": hex.EncodeToString(pk),
"proto": protoVersion,
}
current := m.config.GetCurrent()
m._updateConfig(&current)
// Now get a list of interface expressions from the
// config. This will dictate which interfaces we are
// allowed to use.
var exprs []*regexp.Regexp
// Compile each regular expression
for _, exstr := range m.config.Current.MulticastInterfaces {
e, err := regexp.Compile(exstr)
if err != nil {
return err
}
exprs = append(exprs, e)
}
// Update our expression list.
m._exprs = exprs
return nil
}
@ -70,7 +78,9 @@ func (m *MDNS) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logg
func (m *MDNS) Start() error {
var err error
phony.Block(m, func() {
err = m._start()
if err = m._start(); err == nil {
m._running = true
}
})
m.log.Infoln("Started mDNS module")
return err
@ -81,8 +91,14 @@ func (m *MDNS) _start() error {
return errors.New("mDNS module is already running")
}
m._servers = make(map[string]map[string]*mDNSServer)
m._running = true
m._servers = make(map[string]map[string]*mDNSInterface)
var err error
m.responder, err = dnssd.NewResponder()
if err != nil {
return fmt.Errorf("dnssd.NewResponder: %w", err)
}
go m.responder.Respond(m.context) // nolint:errcheck
m.Act(m, m._updateInterfaces)
@ -101,43 +117,13 @@ func (m *MDNS) Stop() error {
func (m *MDNS) _stop() error {
for _, intf := range m._servers {
for _, ip := range intf {
ip.server.Shutdown()
m.responder.Remove(ip.service)
ip.listener.Stop()
}
}
return nil
}
func (m *MDNS) UpdateConfig(config *config.NodeConfig) {
var err error
phony.Block(m, func() {
err = m._updateConfig(config)
})
if err != nil {
m.log.Warnf("Failed to update mDNS config: %s", err)
} else {
m.log.Infof("mDNS configuration updated")
}
}
func (m *MDNS) _updateConfig(config *config.NodeConfig) error {
// Now get a list of interface expressions from the
// config. This will dictate which interfaces we are
// allowed to use.
var exprs []*regexp.Regexp
// Compile each regular expression
for _, exstr := range config.MulticastDNSInterfaces {
e, err := regexp.Compile(exstr)
if err != nil {
return err
}
exprs = append(exprs, e)
}
// Update our expression list.
m._exprs = exprs
return nil
}
func (m *MDNS) SetupAdminHandlers(a *admin.AdminSocket) {}
func (m *MDNS) IsStarted() bool {
@ -197,7 +183,7 @@ func (m *MDNS) _updateInterfaces() {
// Work out which interfaces are new.
for n, addrs := range interfaces {
if _, ok := m._servers[n]; !ok {
m._servers[n] = make(map[string]*mDNSServer)
m._servers[n] = make(map[string]*mDNSInterface)
}
for addr, intf := range addrs {
if _, ok := m._servers[n][addr]; !ok {
@ -232,10 +218,6 @@ func (m *MDNS) _updateInterfaces() {
}
}
}
time.AfterFunc(time.Second, func() {
m.Act(m, m._updateInterfaces)
})
}
func (m *MDNS) _startInterface(intf net.Interface, addr string) error {
@ -247,11 +229,19 @@ func (m *MDNS) _startInterface(intf net.Interface, addr string) error {
// Construct a listener on this address.
// Work out what the listen address of the new TCP listener should be.
ip := net.ParseIP(addr)
listenaddr := fmt.Sprintf("[%s%%%s]:%d", ip, intf.Name, 0) // TODO: linklocalport
listener, err := m.core.ListenTCP(listenaddr)
if err != nil {
return fmt.Errorf("m.core.ListenTCP: %w", err)
listenaddr := fmt.Sprintf("[%s]:%d", ip.String(), m.config.Current.LinkLocalTCPPort)
if ip.To4() != nil {
listenaddr = fmt.Sprintf("%s:%d", ip.String(), m.config.Current.LinkLocalTCPPort)
}
listener, err := m.core.Listen(&url.URL{
Scheme: "tcp",
Host: listenaddr,
}, intf.Name)
if err != nil {
return fmt.Errorf("m.core.ListenTCP (%s): %w", listenaddr, err)
}
fmt.Println("Listener address is", listener.Listener.Addr().String())
// Resolve it as a TCP endpoint so that we can get the IP address and
// port separately.
@ -263,40 +253,38 @@ func (m *MDNS) _startInterface(intf net.Interface, addr string) error {
return fmt.Errorf("net.ResolveTCPAddr: %w", err)
}
// Create a zone.
hostname := fmt.Sprintf("%s.%s", m.instance, MDNSDomain)
zone, err := mdns.NewMDNSService(
m.instance, // instance name
MDNSService, // service name
MDNSDomain, // service domain
hostname, // our hostname
tcpaddr.Port, // TCP listener port
[]net.IP{tcpaddr.IP}, // our IP address
m.info, // TXT record contents
)
if err != nil {
return fmt.Errorf("mdns.NewMDNSService: %w", err)
}
// Create a server.
server, err := mdns.NewServer(&mdns.Config{
Zone: zone,
Iface: &intf,
pk := m.core.PrivateKey().Public().(ed25519.PublicKey)
svc, err := dnssd.NewService(dnssd.Config{
Name: hex.EncodeToString(pk[:8]),
Type: "_yggdrasil._tcp",
Domain: "local",
Text: m.info,
Port: tcpaddr.Port,
IPs: []net.IP{ip},
Ifaces: []string{intf.Name},
})
if err != nil {
return fmt.Errorf("mdns.NewServer: %w", err)
return fmt.Errorf("dnssd.NewService: %w", err)
}
// Add the service to the responder.
handle, err := m.responder.Add(svc)
if err != nil {
return fmt.Errorf("m.responder.Add: %w", err)
}
// Now store information about our new listener and server.
if _, ok := m._servers[intf.Name]; !ok {
m._servers[intf.Name] = make(map[string]*mDNSServer)
m._servers[intf.Name] = make(map[string]*mDNSInterface)
}
m._servers[intf.Name][addr] = &mDNSServer{
ctx, cancel := context.WithCancel(m.context)
m._servers[intf.Name][addr] = &mDNSInterface{
context: ctx,
cancel: cancel,
mdns: m,
intf: intf,
ourIP: tcpaddr.IP,
stop: make(chan struct{}),
server: server,
addr: tcpaddr,
service: handle,
listener: listener,
}
go m._servers[intf.Name][addr].listen()
@ -318,8 +306,8 @@ func (m *MDNS) _stopInterface(intf net.Interface, addr string) error {
}
// Shut down the mDNS server and the TCP listener.
close(server.stop)
server.server.Shutdown()
server.cancel()
m.responder.Remove(server.service)
server.listener.Stop()
// Clean up.
@ -331,67 +319,59 @@ func (m *MDNS) _stopInterface(intf net.Interface, addr string) error {
return nil
}
func (s *mDNSServer) listen() {
s.mdns.log.Debugln("Started listening for mDNS on", s.intf.Name)
incoming := make(chan *mdns.ServiceEntry)
func (s *mDNSInterface) listen() {
ourpk := hex.EncodeToString(s.mdns.core.PrivateKey().Public().(ed25519.PublicKey))
go func() {
defer close(incoming)
if err := mdns.Listen(incoming, s.stop, &s.intf); err != nil {
s.mdns.log.Println("Failed to initialize resolver:", err.Error())
add := func(e dnssd.BrowseEntry) {
if len(e.IPs) == 0 {
return
}
if version := e.Text["proto"]; version != protoVersion {
return
}
if pk := e.Text["ed25519"]; pk == ourpk {
return
}
if e.IfaceName != s.intf.Name {
return
}
service, err := dnssd.LookupInstance(s.context, e.ServiceInstanceName())
if err != nil {
return
}
for _, ip := range service.IPs {
u := &url.URL{
Scheme: "tcp",
RawQuery: "ed25519=" + e.Text["ed25519"],
}
switch {
case ip.To4() == nil: // IPv6
u.Host = fmt.Sprintf("[%s%%%s]:%d", ip.String(), e.IfaceName, service.Port)
case ip.To16() == nil: // IPv4
u.Host = fmt.Sprintf("%s%%%s:%d", ip.String(), e.IfaceName, service.Port)
default:
continue
}
s.mdns.log.Debugln("Calling", u.String())
if err := s.mdns.core.CallPeer(u, e.IfaceName); err != nil {
continue
}
return
}
}
remove := func(e dnssd.BrowseEntry) {
// the service disappeared
}
}()
for {
select {
case <-s.stop:
s.mdns.log.Debugln("Stopped listening for mDNS on", s.intf.Name)
case <-s.context.Done():
return
case entry := <-incoming:
if entry == nil {
return
}
suffix := fmt.Sprintf("%s.%s", MDNSService, MDNSDomain)
if len(entry.Name) <= len(suffix) {
continue
}
if entry.Name[len(entry.Name)-len(suffix):] != suffix {
continue
}
if bytes.Equal(entry.Addr, s.ourIP) {
continue
}
if entry.AddrV6.Zone == "" {
entry.AddrV6.Zone = s.intf.Name
}
fields := parseTXTFields(entry.InfoFields)
addr := fmt.Sprintf("tcp://[%s]:%d", entry.AddrV6.IP, entry.Port)
u, err := url.Parse(addr)
if err != nil {
continue
}
query := u.Query()
if curve, ok := fields["curve25519"]; ok {
query.Set("curve25519", curve)
}
if ed, ok := fields["ed25519"]; ok {
query.Set("ed25519", ed)
}
u.RawQuery = query.Encode()
if err := s.mdns.core.CallPeer(u.String(), entry.AddrV6.Zone); err != nil {
s.mdns.log.Warn("Failed to add peer from mDNS: ", err)
}
default:
ctx, cancel := context.WithTimeout(s.context, time.Second*5)
_ = dnssd.LookupType(ctx, "_yggdrasil._tcp.local.", add, remove)
cancel()
}
}
}
func parseTXTFields(fields []string) map[string]string {
result := make(map[string]string)
for _, field := range fields {
pos := strings.Index(field, "=")
if pos > 0 && len(field) > pos+1 {
result[field[:pos]] = field[pos+1:]
}
}
return result
}