diff --git a/examples/chat-with-mdns/mdns.go b/examples/chat-with-mdns/mdns.go index 1471fc819f..41953d7fc9 100644 --- a/examples/chat-with-mdns/mdns.go +++ b/examples/chat-with-mdns/mdns.go @@ -13,15 +13,15 @@ type discoveryNotifee struct { PeerChan chan peer.AddrInfo } -//interface to be called when new peer is found +// interface to be called when new peer is found func (n *discoveryNotifee) HandlePeerFound(pi peer.AddrInfo) { n.PeerChan <- pi } -//Initialize the MDNS service +// Initialize the MDNS service func initMDNS(ctx context.Context, peerhost host.Host, rendezvous string) chan peer.AddrInfo { // An hour might be a long long period in practical applications. But this is fine for us - ser, err := discovery.NewMdnsService(ctx, peerhost, time.Hour, rendezvous) + ser, err := discovery.NewMdnsService(ctx, peerhost, time.Hour) if err != nil { panic(err) } diff --git a/examples/go.sum b/examples/go.sum index 55b5184eac..46327abc1c 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -61,7 +61,10 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -486,6 +489,8 @@ github.com/libp2p/go-yamux v1.4.1 h1:P1Fe9vF4th5JOxxgQvfbOHkrGqIZniTLf+ddhZp8YTI github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v2 v2.2.0 h1:RwtpYZ2/wVviZ5+3pjC8qdQ4TKnrak0/E01N1UWoAFU= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= +github.com/libp2p/zeroconf/v2 v2.0.0 h1:qYAHAqUVh4hMSfu+iDTZNqH07wLGAvb1+DW4Tx/qUoQ= +github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enEwS+wWeL6yo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.21.2 h1:8LqqL7nBQFDUINadW0fHV/xSaCQJgmJC0Gv+qUnjd78= diff --git a/examples/ipfs-camp-2019/06-Pubsub/main.go b/examples/ipfs-camp-2019/06-Pubsub/main.go index 2522db877f..1ba551a3e5 100644 --- a/examples/ipfs-camp-2019/06-Pubsub/main.go +++ b/examples/ipfs-camp-2019/06-Pubsub/main.go @@ -19,7 +19,7 @@ import ( tls "github.com/libp2p/go-libp2p-tls" yamux "github.com/libp2p/go-libp2p-yamux" "github.com/libp2p/go-libp2p/p2p/discovery" - tcp "github.com/libp2p/go-tcp-transport" + "github.com/libp2p/go-tcp-transport" ws "github.com/libp2p/go-ws-transport" "github.com/multiformats/go-multiaddr" ) @@ -102,7 +102,7 @@ func main() { fmt.Println("Connected to", targetInfo.ID) } - mdns, err := discovery.NewMdnsService(ctx, host, time.Second*10, "") + mdns, err := discovery.NewMdnsService(ctx, host, time.Second*10) if err != nil { panic(err) } diff --git a/examples/ipfs-camp-2019/07-Messaging/main.go b/examples/ipfs-camp-2019/07-Messaging/main.go index 04ef4c5502..6464edf72e 100644 --- a/examples/ipfs-camp-2019/07-Messaging/main.go +++ b/examples/ipfs-camp-2019/07-Messaging/main.go @@ -110,7 +110,7 @@ func main() { fmt.Println("Connected to", targetInfo.ID) - mdns, err := discovery.NewMdnsService(ctx, host, time.Second*10, "") + mdns, err := discovery.NewMdnsService(ctx, host, time.Second*10) if err != nil { panic(err) } diff --git a/examples/ipfs-camp-2019/08-End/main.go b/examples/ipfs-camp-2019/08-End/main.go index d9384933e1..bd410d579f 100644 --- a/examples/ipfs-camp-2019/08-End/main.go +++ b/examples/ipfs-camp-2019/08-End/main.go @@ -109,7 +109,7 @@ func main() { fmt.Println("Connected to", targetInfo.ID) - mdns, err := discovery.NewMdnsService(ctx, host, time.Second*10, "") + mdns, err := discovery.NewMdnsService(ctx, host, time.Second*10) if err != nil { panic(err) } diff --git a/examples/ipfs-camp-2019/go.sum b/examples/ipfs-camp-2019/go.sum index 43f8dfd4ec..367f783c31 100644 --- a/examples/ipfs-camp-2019/go.sum +++ b/examples/ipfs-camp-2019/go.sum @@ -62,7 +62,10 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -490,6 +493,8 @@ github.com/libp2p/go-yamux v1.4.1 h1:P1Fe9vF4th5JOxxgQvfbOHkrGqIZniTLf+ddhZp8YTI github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v2 v2.2.0 h1:RwtpYZ2/wVviZ5+3pjC8qdQ4TKnrak0/E01N1UWoAFU= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= +github.com/libp2p/zeroconf/v2 v2.0.0 h1:qYAHAqUVh4hMSfu+iDTZNqH07wLGAvb1+DW4Tx/qUoQ= +github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enEwS+wWeL6yo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.21.2 h1:8LqqL7nBQFDUINadW0fHV/xSaCQJgmJC0Gv+qUnjd78= diff --git a/examples/pubsub/chat/go.sum b/examples/pubsub/chat/go.sum index b145f8729c..bd42d0162c 100644 --- a/examples/pubsub/chat/go.sum +++ b/examples/pubsub/chat/go.sum @@ -61,7 +61,10 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -450,6 +453,8 @@ github.com/libp2p/go-yamux v1.4.1 h1:P1Fe9vF4th5JOxxgQvfbOHkrGqIZniTLf+ddhZp8YTI github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v2 v2.2.0 h1:RwtpYZ2/wVviZ5+3pjC8qdQ4TKnrak0/E01N1UWoAFU= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= +github.com/libp2p/zeroconf/v2 v2.0.0 h1:qYAHAqUVh4hMSfu+iDTZNqH07wLGAvb1+DW4Tx/qUoQ= +github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enEwS+wWeL6yo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.21.2 h1:8LqqL7nBQFDUINadW0fHV/xSaCQJgmJC0Gv+qUnjd78= diff --git a/examples/pubsub/chat/main.go b/examples/pubsub/chat/main.go index 644d497369..e52483c945 100644 --- a/examples/pubsub/chat/main.go +++ b/examples/pubsub/chat/main.go @@ -107,7 +107,7 @@ func (n *discoveryNotifee) HandlePeerFound(pi peer.AddrInfo) { // This lets us automatically discover peers on the same LAN and connect to them. func setupDiscovery(ctx context.Context, h host.Host) error { // setup mDNS discovery to find local peers - disc, err := discovery.NewMdnsService(ctx, h, DiscoveryInterval, DiscoveryServiceTag) + disc, err := discovery.NewMdnsService(ctx, h, DiscoveryInterval) if err != nil { return err } diff --git a/go.mod b/go.mod index 38cf56d5cf..d370e33738 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/libp2p/go-stream-muxer-multistream v0.3.0 github.com/libp2p/go-tcp-transport v0.2.7 github.com/libp2p/go-ws-transport v0.5.0 + github.com/libp2p/zeroconf/v2 v2.0.0 github.com/multiformats/go-multiaddr v0.4.0 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multistream v0.2.2 diff --git a/go.sum b/go.sum index bfd2eacb34..6561c4ed4d 100644 --- a/go.sum +++ b/go.sum @@ -61,7 +61,10 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -523,6 +526,8 @@ github.com/libp2p/go-yamux v1.4.1 h1:P1Fe9vF4th5JOxxgQvfbOHkrGqIZniTLf+ddhZp8YTI github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v2 v2.2.0 h1:RwtpYZ2/wVviZ5+3pjC8qdQ4TKnrak0/E01N1UWoAFU= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= +github.com/libp2p/zeroconf/v2 v2.0.0 h1:qYAHAqUVh4hMSfu+iDTZNqH07wLGAvb1+DW4Tx/qUoQ= +github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enEwS+wWeL6yo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.21.2 h1:8LqqL7nBQFDUINadW0fHV/xSaCQJgmJC0Gv+qUnjd78= diff --git a/p2p/discovery/mdns.go b/p2p/discovery/mdns.go index d78ace24bf..2d82f307f2 100644 --- a/p2p/discovery/mdns.go +++ b/p2p/discovery/mdns.go @@ -3,220 +3,197 @@ package discovery import ( "context" "errors" - "io" "net" + "strings" "sync" - "time" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" - - logging "github.com/ipfs/go-log/v2" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr/net" - "github.com/whyrusleeping/mdns" + + "github.com/libp2p/zeroconf/v2" ) -func init() { - // don't let mdns use logging... - mdns.DisableLogging = true -} +const ( + mdnsServiceName = "_p2p._udp" + mdnsDomain = "local" + dnsaddrPrefix = "dnsaddr=" +) -var log = logging.Logger("mdns") +type mdnsService struct { + host host.Host -const ServiceTag = "_ipfs-discovery._udp" + // This ctx is passed to the resolver. + // It is closed when Close() is called. + ctx context.Context + ctxCancel context.CancelFunc -type Service interface { - io.Closer - RegisterNotifee(Notifee) - UnregisterNotifee(Notifee) -} + resolverRunning chan struct{} + server *zeroconf.Server -type Notifee interface { - HandlePeerFound(peer.AddrInfo) + mutex sync.Mutex + notifees []Notifee } -type mdnsService struct { - server *mdns.Server - service *mdns.MDNSService - host host.Host - tag string - - lk sync.Mutex - notifees []Notifee - interval time.Duration +func NewMdnsServiceNew(host host.Host) *mdnsService { + ctx, cancel := context.WithCancel(context.Background()) + s := &mdnsService{ + ctx: ctx, + ctxCancel: cancel, + resolverRunning: make(chan struct{}), + host: host, + } + s.startServer() + s.startResolver() + return s } -func getDialableListenAddrs(ph host.Host) ([]*net.TCPAddr, error) { - var out []*net.TCPAddr - addrs, err := ph.Network().InterfaceListenAddresses() - if err != nil { - return nil, err +func (s *mdnsService) Close() error { + s.ctxCancel() + if s.server != nil { + s.server.Shutdown() } + <-s.resolverRunning + return nil +} + +// We don't really care about the IP addresses, but the spec (and various routers / firewalls) require us +// to send A and AAAA records. +func (s *mdnsService) getIPs(addrs []ma.Multiaddr) ([]string, error) { + var ip4, ip6 string for _, addr := range addrs { - na, err := manet.ToNetAddr(addr) + network, hostport, err := manet.DialArgs(addr) if err != nil { continue } - tcp, ok := na.(*net.TCPAddr) - if ok { - out = append(out, tcp) + host, _, err := net.SplitHostPort(hostport) + if err != nil { + continue } + if ip4 == "" && (network == "udp4" || network == "tcp4") { + ip4 = host + } else if ip6 == "" && (network == "udp6" || network == "tcp6") { + ip6 = host + } + } + ips := make([]string, 0, 2) + if ip4 != "" { + ips = append(ips, ip4) } - if len(out) == 0 { - return nil, errors.New("failed to find good external addr from peerhost") + if ip6 != "" { + ips = append(ips, ip6) } - return out, nil + if len(ips) == 0 { + return nil, errors.New("didn't find any IP addresses") + } + return ips, nil } -func NewMdnsService(ctx context.Context, peerhost host.Host, interval time.Duration, serviceTag string) (Service, error) { - - var ipaddrs []net.IP - port := 4001 +func (s *mdnsService) mdnsInstance() string { + return string(s.host.ID()) +} - addrs, err := getDialableListenAddrs(peerhost) +func (s *mdnsService) startServer() error { + interfaceAddrs, err := s.host.Network().InterfaceListenAddresses() if err != nil { - log.Warn(err) - } else { - port = addrs[0].Port - for _, a := range addrs { - ipaddrs = append(ipaddrs, a.IP) - } + return err } - - myid := peerhost.ID().Pretty() - - info := []string{myid} - if serviceTag == "" { - serviceTag = ServiceTag - } - service, err := mdns.NewMDNSService(myid, serviceTag, "", "", port, ipaddrs, info) + addrs, err := peer.AddrInfoToP2pAddrs(&peer.AddrInfo{ + ID: s.host.ID(), + Addrs: interfaceAddrs, + }) if err != nil { - return nil, err + return err } - - // Create the mDNS server, defer shutdown - server, err := mdns.NewServer(&mdns.Config{Zone: service}) - if err != nil { - return nil, err + var txts []string + for _, addr := range addrs { + if manet.IsThinWaist(addr) { // don't announce circuit addresses + txts = append(txts, dnsaddrPrefix+addr.String()) + } } - s := &mdnsService{ - server: server, - service: service, - host: peerhost, - interval: interval, - tag: serviceTag, + ips, err := s.getIPs(addrs) + if err != nil { + return err + } + + server, err := zeroconf.RegisterProxy( + s.mdnsInstance(), + mdnsServiceName, + mdnsDomain, + 4001, + s.host.ID().Pretty(), // TODO: deals with peer IDs longer than 63 characters + ips, + txts, + nil, + ) + if err != nil { + return err } - - go s.pollForEntries(ctx) - - return s, nil + s.server = server + return nil } -func (m *mdnsService) Close() error { - return m.server.Shutdown() -} - -func (m *mdnsService) pollForEntries(ctx context.Context) { - ticker := time.NewTicker(m.interval) - defer ticker.Stop() - - for { - //execute mdns query right away at method call and then with every tick - entriesCh := make(chan *mdns.ServiceEntry, 16) - go func() { - for entry := range entriesCh { - m.handleEntry(entry) +func (s *mdnsService) startResolver() { + entryChan := make(chan *zeroconf.ServiceEntry, 1000) + go func() { + for entry := range entryChan { + // We only care about the TXT records. + // Ignore A, AAAA and PTR. + addrs := make([]ma.Multiaddr, 0, len(entry.Text)) // assume that all TXT records are dnsaddrs + for _, s := range entry.Text { + if !strings.HasPrefix(s, dnsaddrPrefix) { + log.Debug("missing dnsaddr prefix") + continue + } + addr, err := ma.NewMultiaddr(s[len(dnsaddrPrefix):]) + if err != nil { + log.Debugf("failed to parse multiaddr: %s", err) + continue + } + addrs = append(addrs, addr) } - }() - - log.Debug("starting mdns query") - qp := &mdns.QueryParam{ - Domain: "local", - Entries: entriesCh, - Service: m.tag, - Timeout: time.Second * 5, - } - - err := mdns.Query(qp) - if err != nil { - log.Warnw("mdns lookup error", "error", err) + infos, err := peer.AddrInfosFromP2pAddrs(addrs...) + if err != nil { + log.Debugf("failed to get peer info: %s", err) + continue + } + s.mutex.Lock() + for _, info := range infos { + for _, notif := range s.notifees { + go notif.HandlePeerFound(info) + } + } + s.mutex.Unlock() } - close(entriesCh) - log.Debug("mdns query complete") - - select { - case <-ticker.C: - continue - case <-ctx.Done(): - log.Debug("mdns service halting") - return + }() + go func() { + defer close(s.resolverRunning) + if err := zeroconf.Browse(s.ctx, mdnsServiceName, mdnsDomain, entryChan); err != nil { + log.Debugf("zeroconf browsing failed: %s", err) } - } + }() } -func (m *mdnsService) handleEntry(e *mdns.ServiceEntry) { - log.Debugf("Handling MDNS entry: [IPv4 %s][IPv6 %s]:%d %s", e.AddrV4, e.AddrV6, e.Port, e.Info) - mpeer, err := peer.Decode(e.Info) - if err != nil { - log.Warn("Error parsing peer ID from mdns entry: ", err) - return - } - - if mpeer == m.host.ID() { - log.Debug("got our own mdns entry, skipping") - return - } - - var addr net.IP - if e.AddrV4 != nil { - addr = e.AddrV4 - } else if e.AddrV6 != nil { - addr = e.AddrV6 - } else { - log.Warn("Error parsing multiaddr from mdns entry: no IP address found") - return - } - - maddr, err := manet.FromNetAddr(&net.TCPAddr{ - IP: addr, - Port: e.Port, - }) - if err != nil { - log.Warn("Error parsing multiaddr from mdns entry: ", err) - return - } - - pi := peer.AddrInfo{ - ID: mpeer, - Addrs: []ma.Multiaddr{maddr}, - } - - m.lk.Lock() - for _, n := range m.notifees { - go n.HandlePeerFound(pi) - } - m.lk.Unlock() +func (s *mdnsService) RegisterNotifee(n Notifee) { + s.mutex.Lock() + s.notifees = append(s.notifees, n) + s.mutex.Unlock() } -func (m *mdnsService) RegisterNotifee(n Notifee) { - m.lk.Lock() - m.notifees = append(m.notifees, n) - m.lk.Unlock() -} +func (s *mdnsService) UnregisterNotifee(n Notifee) { + s.mutex.Lock() + defer s.mutex.Unlock() -func (m *mdnsService) UnregisterNotifee(n Notifee) { - m.lk.Lock() found := -1 - for i, notif := range m.notifees { + for i, notif := range s.notifees { if notif == n { found = i break } } if found != -1 { - m.notifees = append(m.notifees[:found], m.notifees[found+1:]...) + s.notifees = append(s.notifees[:found], s.notifees[found+1:]...) } - m.lk.Unlock() } diff --git a/p2p/discovery/mdns_legacy.go b/p2p/discovery/mdns_legacy.go new file mode 100644 index 0000000000..82a7c17c50 --- /dev/null +++ b/p2p/discovery/mdns_legacy.go @@ -0,0 +1,209 @@ +package discovery + +import ( + "context" + "errors" + "net" + "sync" + "time" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + + logging "github.com/ipfs/go-log/v2" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" + "github.com/whyrusleeping/mdns" +) + +func init() { + // don't let mdns use logging... + mdns.DisableLogging = true +} + +var log = logging.Logger("mdns") + +const ServiceTag = "_ipfs-discovery._udp" + +type mdnsServiceLegacy struct { + server *mdns.Server + service *mdns.MDNSService + host host.Host + tag string + + lk sync.Mutex + notifees []Notifee + interval time.Duration +} + +var _ Service = &mdnsServiceLegacy{} + +func getDialableListenAddrs(ph host.Host) ([]*net.TCPAddr, error) { + var out []*net.TCPAddr + addrs, err := ph.Network().InterfaceListenAddresses() + if err != nil { + return nil, err + } + for _, addr := range addrs { + na, err := manet.ToNetAddr(addr) + if err != nil { + continue + } + tcp, ok := na.(*net.TCPAddr) + if ok { + out = append(out, tcp) + } + } + if len(out) == 0 { + return nil, errors.New("failed to find good external addr from peerhost") + } + return out, nil +} + +func NewMdnsServiceLegacy(ctx context.Context, peerhost host.Host, interval time.Duration) (Service, error) { + var ipaddrs []net.IP + port := 4001 + + addrs, err := getDialableListenAddrs(peerhost) + if err != nil { + log.Warn(err) + } else { + port = addrs[0].Port + for _, a := range addrs { + ipaddrs = append(ipaddrs, a.IP) + } + } + + myid := peerhost.ID().Pretty() + + info := []string{myid} + service, err := mdns.NewMDNSService(myid, ServiceTag, "", "", port, ipaddrs, info) + if err != nil { + return nil, err + } + + // Create the mDNS server, defer shutdown + server, err := mdns.NewServer(&mdns.Config{Zone: service}) + if err != nil { + return nil, err + } + + s := &mdnsServiceLegacy{ + server: server, + service: service, + host: peerhost, + interval: interval, + tag: ServiceTag, + } + + go s.pollForEntries(ctx) + + return s, nil +} + +func (m *mdnsServiceLegacy) Close() error { + return m.server.Shutdown() +} + +func (m *mdnsServiceLegacy) pollForEntries(ctx context.Context) { + ticker := time.NewTicker(m.interval) + defer ticker.Stop() + + for { + //execute mdns query right away at method call and then with every tick + entriesCh := make(chan *mdns.ServiceEntry, 16) + go func() { + for entry := range entriesCh { + m.handleEntry(entry) + } + }() + + log.Debug("starting mdns query") + qp := &mdns.QueryParam{ + Domain: "local", + Entries: entriesCh, + Service: m.tag, + Timeout: time.Second * 5, + } + + err := mdns.Query(qp) + if err != nil { + log.Warnw("mdns lookup error", "error", err) + } + close(entriesCh) + log.Debug("mdns query complete") + + select { + case <-ticker.C: + continue + case <-ctx.Done(): + log.Debug("mdns service halting") + return + } + } +} + +func (m *mdnsServiceLegacy) handleEntry(e *mdns.ServiceEntry) { + log.Debugf("Handling MDNS entry: [IPv4 %s][IPv6 %s]:%d %s", e.AddrV4, e.AddrV6, e.Port, e.Info) + mpeer, err := peer.Decode(e.Info) + if err != nil { + log.Warn("Error parsing peer ID from mdns entry: ", err) + return + } + + if mpeer == m.host.ID() { + log.Debug("got our own mdns entry, skipping") + return + } + + var addr net.IP + if e.AddrV4 != nil { + addr = e.AddrV4 + } else if e.AddrV6 != nil { + addr = e.AddrV6 + } else { + log.Warn("Error parsing multiaddr from mdns entry: no IP address found") + return + } + + maddr, err := manet.FromNetAddr(&net.TCPAddr{ + IP: addr, + Port: e.Port, + }) + if err != nil { + log.Warn("Error parsing multiaddr from mdns entry: ", err) + return + } + + pi := peer.AddrInfo{ + ID: mpeer, + Addrs: []ma.Multiaddr{maddr}, + } + + m.lk.Lock() + for _, n := range m.notifees { + go n.HandlePeerFound(pi) + } + m.lk.Unlock() +} + +func (m *mdnsServiceLegacy) RegisterNotifee(n Notifee) { + m.lk.Lock() + m.notifees = append(m.notifees, n) + m.lk.Unlock() +} + +func (m *mdnsServiceLegacy) UnregisterNotifee(n Notifee) { + m.lk.Lock() + found := -1 + for i, notif := range m.notifees { + if notif == n { + found = i + break + } + } + if found != -1 { + m.notifees = append(m.notifees[:found], m.notifees[found+1:]...) + } + m.lk.Unlock() +} diff --git a/p2p/discovery/mdns_legacy_test.go b/p2p/discovery/mdns_legacy_test.go new file mode 100644 index 0000000000..d0e9a3f334 --- /dev/null +++ b/p2p/discovery/mdns_legacy_test.go @@ -0,0 +1,52 @@ +package discovery + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + swarmt "github.com/libp2p/go-libp2p-swarm/testing" + bhost "github.com/libp2p/go-libp2p/p2p/host/basic" +) + +type DiscoveryNotifee struct { + h host.Host +} + +func (n *DiscoveryNotifee) HandlePeerFound(pi peer.AddrInfo) { + n.h.Connect(context.Background(), pi) +} + +func TestMdnsDiscovery(t *testing.T) { + //TODO: re-enable when the new lib will get integrated + t.Skip("TestMdnsDiscovery fails randomly with current lib") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + a, err := bhost.NewHost(ctx, swarmt.GenSwarm(t, ctx), nil) + require.NoError(t, err) + b, err := bhost.NewHost(ctx, swarmt.GenSwarm(t, ctx), nil) + require.NoError(t, err) + + sa, err := NewMdnsServiceLegacy(ctx, a, time.Second) + require.NoError(t, err) + + sb, err := NewMdnsServiceLegacy(ctx, b, time.Second) + require.NoError(t, err) + _ = sb + + n := &DiscoveryNotifee{a} + + sa.RegisterNotifee(n) + + time.Sleep(time.Second * 2) + + if err := a.Connect(ctx, peer.AddrInfo{ID: b.ID()}); err != nil { + t.Fatal(err) + } +} diff --git a/p2p/discovery/mdns_mult.go b/p2p/discovery/mdns_mult.go new file mode 100644 index 0000000000..285e15fbdc --- /dev/null +++ b/p2p/discovery/mdns_mult.go @@ -0,0 +1,52 @@ +package discovery + +import ( + "context" + "io" + "time" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" +) + +type Notifee interface { + HandlePeerFound(peer.AddrInfo) +} + +type Service interface { + io.Closer + RegisterNotifee(Notifee) + UnregisterNotifee(Notifee) +} + +func NewMdnsService(ctx context.Context, peerhost host.Host, interval time.Duration) (Service, error) { + legacy, err := NewMdnsServiceLegacy(ctx, peerhost, interval) + if err != nil { + return nil, err + } + return &mdnsServiceMuxer{ + s1: legacy, + s2: NewMdnsServiceNew(peerhost), + }, nil +} + +type mdnsServiceMuxer struct { + s1, s2 Service +} + +var _ Service = &mdnsServiceMuxer{} + +func (m *mdnsServiceMuxer) Close() error { + m.s1.Close() + return m.s2.Close() +} + +func (m *mdnsServiceMuxer) RegisterNotifee(notifee Notifee) { + m.s1.RegisterNotifee(notifee) + m.s2.RegisterNotifee(notifee) +} + +func (m *mdnsServiceMuxer) UnregisterNotifee(notifee Notifee) { + m.s1.UnregisterNotifee(notifee) + m.s2.UnregisterNotifee(notifee) +} diff --git a/p2p/discovery/mdns_test.go b/p2p/discovery/mdns_test.go index 8307eb0db4..6b8aa40d70 100644 --- a/p2p/discovery/mdns_test.go +++ b/p2p/discovery/mdns_test.go @@ -2,51 +2,117 @@ package discovery import ( "context" + "sync" "testing" "time" - "github.com/stretchr/testify/require" - + "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" - swarmt "github.com/libp2p/go-libp2p-swarm/testing" - bhost "github.com/libp2p/go-libp2p/p2p/host/basic" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -type DiscoveryNotifee struct { - h host.Host +func setupMDNS(t *testing.T, notifee Notifee) (host.Host, *mdnsService) { + t.Helper() + host, err := libp2p.New(context.Background(), libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0")) + require.NoError(t, err) + s := NewMdnsServiceNew(host) + s.RegisterNotifee(notifee) + return host, s } -func (n *DiscoveryNotifee) HandlePeerFound(pi peer.AddrInfo) { - n.h.Connect(context.Background(), pi) +type notif struct { + mutex sync.Mutex + infos []peer.AddrInfo } -func TestMdnsDiscovery(t *testing.T) { - //TODO: re-enable when the new lib will get integrated - t.Skip("TestMdnsDiscovery fails randomly with current lib") - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - a, err := bhost.NewHost(ctx, swarmt.GenSwarm(t, ctx), nil) - require.NoError(t, err) - b, err := bhost.NewHost(ctx, swarmt.GenSwarm(t, ctx), nil) - require.NoError(t, err) +var _ Notifee = ¬if{} - sa, err := NewMdnsService(ctx, a, time.Second, "someTag") - require.NoError(t, err) +func (n *notif) HandlePeerFound(info peer.AddrInfo) { + n.mutex.Lock() + n.infos = append(n.infos, info) + n.mutex.Unlock() +} - sb, err := NewMdnsService(ctx, b, time.Second, "someTag") - require.NoError(t, err) - _ = sb +func (n *notif) GetPeers() []peer.AddrInfo { + n.mutex.Lock() + defer n.mutex.Unlock() + infos := make([]peer.AddrInfo, 0, len(n.infos)) + infos = append(infos, n.infos...) + return infos +} - n := &DiscoveryNotifee{a} +func TestSelfDiscovery(t *testing.T) { + notif := ¬if{} + host, s := setupMDNS(t, notif) + defer s.Close() + assert.Eventuallyf( + t, + func() bool { + var found bool + for _, info := range notif.GetPeers() { + if info.ID == host.ID() { + found = true + break + } + } + return found + }, + 5*time.Second, + 5*time.Millisecond, + "expected peer to find itself", + ) +} - sa.RegisterNotifee(n) +func TestOtherDiscovery(t *testing.T) { + const n = 4 - time.Sleep(time.Second * 2) + notifs := make([]*notif, n) + hostIDs := make([]peer.ID, n) + for i := 0; i < n; i++ { + notif := ¬if{} + notifs[i] = notif + var s *mdnsService + host, s := setupMDNS(t, notif) + hostIDs[i] = host.ID() + defer s.Close() + } - if err := a.Connect(ctx, peer.AddrInfo{ID: b.ID()}); err != nil { - t.Fatal(err) + containsAllHostIDs := func(ids []peer.ID) bool { + for _, id := range hostIDs { + var found bool + for _, i := range ids { + if id == i { + found = true + break + } + } + if !found { + return false + } + } + return true } + + assert.Eventuallyf( + t, + func() bool { + for _, notif := range notifs { + infos := notif.GetPeers() + ids := make([]peer.ID, 0, len(infos)) + for _, info := range infos { + ids = append(ids, info.ID) + } + if !containsAllHostIDs(ids) { + return false + } + } + return true + }, + 25*time.Second, + 5*time.Millisecond, + "expected peers to find each other", + ) }