diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 3ca9433f..7ed035bd 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -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() { diff --git a/go.mod b/go.mod index 07d340e1..861c10ab 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index cafa8c84..0e42574d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/src/config/config.go b/src/config/config.go index 91413aca..073e1597 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -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 diff --git a/src/mdns/mdns.go b/src/mdns/mdns.go index ed545dd7..59675e57 100644 --- a/src/mdns/mdns.go +++ b/src/mdns/mdns.go @@ -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(¤t) + // 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 -}