From 623bfd9eba54bd397e80f3cf719a83f937fe8a4b Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 5 Jul 2021 20:11:56 -0700 Subject: [PATCH] run both mDNS services in parallel --- examples/go.sum | 3 + examples/ipfs-camp-2019/go.sum | 3 + examples/pubsub/chat/go.sum | 3 + go.mod | 1 + go.sum | 3 + p2p/discovery/mdns.go | 196 ++++++++++++++++++++++++++++++ p2p/discovery/mdns_legacy.go | 23 +--- p2p/discovery/mdns_legacy_test.go | 4 +- p2p/discovery/mdns_mult.go | 52 ++++++++ p2p/discovery/mdns_test.go | 126 +++++++++++++++++++ 10 files changed, 394 insertions(+), 20 deletions(-) create mode 100644 p2p/discovery/mdns.go create mode 100644 p2p/discovery/mdns_mult.go create mode 100644 p2p/discovery/mdns_test.go diff --git a/examples/go.sum b/examples/go.sum index cda76e49f0..4adaaf73c6 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -60,6 +60,7 @@ 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -199,6 +200,8 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grandcat/zeroconf v1.0.1-0.20210705173249-1df3d2672866 h1:Zhb043u1Nzw/ftKuExAZl3L+O2lc5YkPcQvqM8NknYc= +github.com/grandcat/zeroconf v1.0.1-0.20210705173249-1df3d2672866/go.mod h1:I6CSXU4zCGL08JOk9NbcT0ofAgnIkS/fVXbYzfSoDic= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= diff --git a/examples/ipfs-camp-2019/go.sum b/examples/ipfs-camp-2019/go.sum index cc2f93d52a..f677261a06 100644 --- a/examples/ipfs-camp-2019/go.sum +++ b/examples/ipfs-camp-2019/go.sum @@ -61,6 +61,7 @@ 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -199,6 +200,8 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grandcat/zeroconf v1.0.1-0.20210705173249-1df3d2672866 h1:Zhb043u1Nzw/ftKuExAZl3L+O2lc5YkPcQvqM8NknYc= +github.com/grandcat/zeroconf v1.0.1-0.20210705173249-1df3d2672866/go.mod h1:I6CSXU4zCGL08JOk9NbcT0ofAgnIkS/fVXbYzfSoDic= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= diff --git a/examples/pubsub/chat/go.sum b/examples/pubsub/chat/go.sum index 68f6e9c93e..e157daaf76 100644 --- a/examples/pubsub/chat/go.sum +++ b/examples/pubsub/chat/go.sum @@ -60,6 +60,7 @@ 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -200,6 +201,8 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grandcat/zeroconf v1.0.1-0.20210705173249-1df3d2672866 h1:Zhb043u1Nzw/ftKuExAZl3L+O2lc5YkPcQvqM8NknYc= +github.com/grandcat/zeroconf v1.0.1-0.20210705173249-1df3d2672866/go.mod h1:I6CSXU4zCGL08JOk9NbcT0ofAgnIkS/fVXbYzfSoDic= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= diff --git a/go.mod b/go.mod index 1d2bcee4c4..0b7775eb12 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/btcsuite/btcd v0.21.0-beta // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/gogo/protobuf v1.3.2 + github.com/grandcat/zeroconf v1.0.1-0.20210705173249-1df3d2672866 github.com/ipfs/go-cid v0.0.7 github.com/ipfs/go-datastore v0.4.5 github.com/ipfs/go-detect-race v0.0.1 diff --git a/go.sum b/go.sum index bc45158681..5eedf3ad1c 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,7 @@ 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -202,6 +203,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grandcat/zeroconf v1.0.1-0.20210705173249-1df3d2672866 h1:Zhb043u1Nzw/ftKuExAZl3L+O2lc5YkPcQvqM8NknYc= +github.com/grandcat/zeroconf v1.0.1-0.20210705173249-1df3d2672866/go.mod h1:I6CSXU4zCGL08JOk9NbcT0ofAgnIkS/fVXbYzfSoDic= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= diff --git a/p2p/discovery/mdns.go b/p2p/discovery/mdns.go new file mode 100644 index 0000000000..1f5271c692 --- /dev/null +++ b/p2p/discovery/mdns.go @@ -0,0 +1,196 @@ +package discovery + +import ( + "context" + "errors" + "net" + "strings" + "sync" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" + + "github.com/grandcat/zeroconf" +) + +const ( + mdnsServiceName = "_p2p._udp" + mdnsDomain = "local" + dnsaddrPrefix = "dnsaddr=" +) + +type mdnsService struct { + host host.Host + + // This ctx is passed to the resolver. + // It is closed when Close() is called. + ctx context.Context + ctxCancel context.CancelFunc + + server *zeroconf.Server + + mutex sync.Mutex + notifees []Notifee +} + +func NewMdnsServiceNew(host host.Host) *mdnsService { + ctx, cancel := context.WithCancel(context.Background()) + s := &mdnsService{ + ctx: ctx, + ctxCancel: cancel, + host: host, + } + s.startServer() + s.startResolver() + return s +} + +func (s *mdnsService) Close() error { + s.ctxCancel() + if s.server != nil { + s.server.Shutdown() + } + 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 { + network, hostport, err := manet.DialArgs(addr) + if err != nil { + continue + } + 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 ip6 != "" { + ips = append(ips, ip6) + } + if len(ips) == 0 { + return nil, errors.New("didn't find any IP addresses") + } + return ips, nil +} + +func (s *mdnsService) mdnsInstance() string { + return string(s.host.ID()) +} + +func (s *mdnsService) startServer() error { + interfaceAddrs, err := s.host.Network().InterfaceListenAddresses() + if err != nil { + return err + } + addrs, err := peer.AddrInfoToP2pAddrs(&peer.AddrInfo{ + ID: s.host.ID(), + Addrs: interfaceAddrs, + }) + if err != nil { + return err + } + var txts []string + for _, addr := range addrs { + if manet.IsThinWaist(addr) { // don't announce circuit addresses + txts = append(txts, dnsaddrPrefix+addr.String()) + } + } + + 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 + } + s.server = server + return nil +} + +func (s *mdnsService) startResolver() error { + resolver, err := zeroconf.NewResolver() + if err != nil { + return err + } + + 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) + } + 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() + } + }() + return resolver.Browse(s.ctx, mdnsServiceName, mdnsDomain, entryChan) +} + +func (s *mdnsService) RegisterNotifee(n Notifee) { + s.mutex.Lock() + s.notifees = append(s.notifees, n) + s.mutex.Unlock() +} + +func (s *mdnsService) UnregisterNotifee(n Notifee) { + s.mutex.Lock() + defer s.mutex.Unlock() + + found := -1 + for i, notif := range s.notifees { + if notif == n { + found = i + break + } + } + if found != -1 { + s.notifees = append(s.notifees[:found], s.notifees[found+1:]...) + } +} diff --git a/p2p/discovery/mdns_legacy.go b/p2p/discovery/mdns_legacy.go index 44044c7842..82a7c17c50 100644 --- a/p2p/discovery/mdns_legacy.go +++ b/p2p/discovery/mdns_legacy.go @@ -3,7 +3,6 @@ package discovery import ( "context" "errors" - "io" "net" "sync" "time" @@ -26,16 +25,6 @@ var log = logging.Logger("mdns") const ServiceTag = "_ipfs-discovery._udp" -type Service interface { - io.Closer - RegisterNotifee(Notifee) - UnregisterNotifee(Notifee) -} - -type Notifee interface { - HandlePeerFound(peer.AddrInfo) -} - type mdnsServiceLegacy struct { server *mdns.Server service *mdns.MDNSService @@ -47,6 +36,8 @@ type mdnsServiceLegacy struct { interval time.Duration } +var _ Service = &mdnsServiceLegacy{} + func getDialableListenAddrs(ph host.Host) ([]*net.TCPAddr, error) { var out []*net.TCPAddr addrs, err := ph.Network().InterfaceListenAddresses() @@ -69,8 +60,7 @@ func getDialableListenAddrs(ph host.Host) ([]*net.TCPAddr, error) { return out, nil } -func NewMdnsService(ctx context.Context, peerhost host.Host, interval time.Duration, serviceTag string) (Service, error) { - +func NewMdnsServiceLegacy(ctx context.Context, peerhost host.Host, interval time.Duration) (Service, error) { var ipaddrs []net.IP port := 4001 @@ -87,10 +77,7 @@ func NewMdnsService(ctx context.Context, peerhost host.Host, interval time.Durat myid := peerhost.ID().Pretty() info := []string{myid} - if serviceTag == "" { - serviceTag = ServiceTag - } - service, err := mdns.NewMDNSService(myid, serviceTag, "", "", port, ipaddrs, info) + service, err := mdns.NewMDNSService(myid, ServiceTag, "", "", port, ipaddrs, info) if err != nil { return nil, err } @@ -106,7 +93,7 @@ func NewMdnsService(ctx context.Context, peerhost host.Host, interval time.Durat service: service, host: peerhost, interval: interval, - tag: serviceTag, + tag: ServiceTag, } go s.pollForEntries(ctx) diff --git a/p2p/discovery/mdns_legacy_test.go b/p2p/discovery/mdns_legacy_test.go index 8f1e5bec91..0d82357cc9 100644 --- a/p2p/discovery/mdns_legacy_test.go +++ b/p2p/discovery/mdns_legacy_test.go @@ -30,12 +30,12 @@ func TestMdnsDiscovery(t *testing.T) { a := bhost.New(swarmt.GenSwarm(t, ctx)) b := bhost.New(swarmt.GenSwarm(t, ctx)) - sa, err := NewMdnsService(ctx, a, time.Second, "someTag") + sa, err := NewMdnsService(ctx, a, time.Second) if err != nil { t.Fatal(err) } - sb, err := NewMdnsService(ctx, b, time.Second, "someTag") + sb, err := NewMdnsService(ctx, b, time.Second) if 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 new file mode 100644 index 0000000000..9322c05b6d --- /dev/null +++ b/p2p/discovery/mdns_test.go @@ -0,0 +1,126 @@ +package discovery + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + + "github.com/stretchr/testify/assert" +) + +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")) + if err != nil { + t.Fatal(err) + } + s := NewMdnsServiceNew(host) + s.RegisterNotifee(notifee) + return host, s +} + +type notif struct { + mutex sync.Mutex + infos []peer.AddrInfo +} + +var _ Notifee = ¬if{} + +func (n *notif) HandlePeerFound(info peer.AddrInfo) { + n.mutex.Lock() + n.infos = append(n.infos, info) + fmt.Printf("Discovered %s at %v.\n", info.ID, info.Addrs) + n.mutex.Unlock() +} + +func (n *notif) GetPeers() []peer.AddrInfo { + n.mutex.Lock() + defer n.mutex.Unlock() + infos := make([]peer.AddrInfo, 0, len(n.infos)) + for _, info := range n.infos { + infos = append(infos, info) + } + return infos +} + +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", + ) +} + +func TestOtherDiscovery(t *testing.T) { + const n = 4 + + 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() + time.Sleep(2*time.Second) + } + // fmt.Println(hostIDs) + + 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 peer to find itself", + ) +} +