diff --git a/p2p/discovery/mdns.go b/p2p/discovery/mdns.go index a7ec72ab12..b0eb9782c0 100644 --- a/p2p/discovery/mdns.go +++ b/p2p/discovery/mdns.go @@ -10,13 +10,13 @@ import ( "sync" "time" + mdns "github.com/grandcat/zeroconf" logging "github.com/ipfs/go-log" "github.com/libp2p/go-libp2p-host" "github.com/libp2p/go-libp2p-peer" pstore "github.com/libp2p/go-libp2p-peerstore" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" - "github.com/whyrusleeping/mdns" ) var log = logging.Logger("mdns") @@ -35,7 +35,7 @@ type Notifee interface { type mdnsService struct { server *mdns.Server - service *mdns.MDNSService + service *mdns.Resolver host host.Host tag string @@ -44,22 +44,44 @@ type mdnsService struct { interval time.Duration } -func getDialableListenAddrs(ph host.Host) ([]*net.TCPAddr, error) { - var out []*net.TCPAddr - for _, addr := range ph.Addrs() { +// Tries to pick the best port. +func getBestPort(addrs []ma.Multiaddr) (int, error) { + var best *net.TCPAddr + 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 !ok { + continue + } + // Don't bother with multicast and + if tcp.IP.IsMulticast() { + continue + } + // We don't yet support link-local + if tcp.IP.IsLinkLocalUnicast() { + continue + } + // Unspecified listeners are *always* the best choice. + if tcp.IP.IsUnspecified() { + return tcp.Port, nil + } + // If we don't have a best choice, use this addr. + if best == nil { + best = tcp + continue + } + // If the best choice is a loopback address, replace it. + if best.IP.IsLoopback() { + best = tcp } } - if len(out) == 0 { - return nil, errors.New("failed to find good external addr from peerhost") + if best == nil { + return 0, errors.New("failed to find good external addr from peerhost") } - return out, nil + return best.Port, nil } func NewMdnsService(ctx context.Context, peerhost host.Host, interval time.Duration, serviceTag string) (Service, error) { @@ -67,39 +89,31 @@ func NewMdnsService(ctx context.Context, peerhost host.Host, interval time.Durat // TODO: dont let mdns use logging... golog.SetOutput(ioutil.Discard) - var ipaddrs []net.IP - port := 4001 - - addrs, err := getDialableListenAddrs(peerhost) + port, err := getBestPort(peerhost.Network().ListenAddresses()) if err != nil { - log.Warning(err) - } else { - port = addrs[0].Port - for _, a := range addrs { - ipaddrs = append(ipaddrs, a.IP) - } + return nil, err } - myid := peerhost.ID().Pretty() info := []string{myid} if serviceTag == "" { serviceTag = ServiceTag } - service, err := mdns.NewMDNSService(myid, serviceTag, "", "", port, ipaddrs, info) + + resolver, err := mdns.NewResolver(nil) if err != nil { - return nil, err + log.Error("Failed to initialize resolver:", err) } // Create the mDNS server, defer shutdown - server, err := mdns.NewServer(&mdns.Config{Zone: service}) + server, err := mdns.Register(myid, serviceTag, "", port, info, nil) if err != nil { return nil, err } s := &mdnsService{ server: server, - service: service, + service: resolver, host: peerhost, interval: interval, tag: serviceTag, @@ -111,34 +125,32 @@ func NewMdnsService(ctx context.Context, peerhost host.Host, interval time.Durat } func (m *mdnsService) Close() error { - return m.server.Shutdown() + m.server.Shutdown() + // grandcat/zerconf swallows error, satisfy interface + return nil } func (m *mdnsService) pollForEntries(ctx context.Context) { - ticker := time.NewTicker(m.interval) 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 { + go func(results <-chan *mdns.ServiceEntry) { + for entry := range results { m.handleEntry(entry) } - }() + }(entriesCh) 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 { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + if err := m.service.Browse(ctx, m.tag, "local", entriesCh); err != nil { log.Error("mdns lookup error: ", err) } close(entriesCh) + log.Debug("mdns query complete") select { @@ -152,8 +164,12 @@ func (m *mdnsService) pollForEntries(ctx context.Context) { } func (m *mdnsService) handleEntry(e *mdns.ServiceEntry) { - log.Debugf("Handling MDNS entry: %s:%d %s", e.AddrV4, e.Port, e.Info) - mpeer, err := peer.IDB58Decode(e.Info) + if len(e.Text) != 1 { + log.Warningf("Expected exactly one TXT record, got: %v", e.Text) + return + } + // pull out the txt + mpeer, err := peer.IDB58Decode(e.Text[0]) if err != nil { log.Warning("Error parsing peer ID from mdns entry: ", err) return @@ -164,18 +180,23 @@ func (m *mdnsService) handleEntry(e *mdns.ServiceEntry) { return } - maddr, err := manet.FromNetAddr(&net.TCPAddr{ - IP: e.AddrV4, - Port: e.Port, - }) - if err != nil { - log.Warning("Error parsing multiaddr from mdns entry: ", err) - return - } + addrs := make([]net.IP, len(e.AddrIPv4)+len(e.AddrIPv6)) + copy(addrs, e.AddrIPv4) + copy(addrs[len(e.AddrIPv4):], e.AddrIPv6) + + var pi pstore.PeerInfo + for _, ip := range addrs { + log.Debugf("Handling MDNS entry: %s:%d %s", ip, e.Port, e.Text[0]) - pi := pstore.PeerInfo{ - ID: mpeer, - Addrs: []ma.Multiaddr{maddr}, + maddr, err := manet.FromNetAddr(&net.TCPAddr{ + IP: ip, + Port: e.Port, + }) + if err != nil { + log.Errorf("error creating multiaddr from mdns entry (%s:%d): %s", ip, e.Port, err) + return + } + pi.Addrs = append(pi.Addrs, maddr) } m.lk.Lock() diff --git a/p2p/discovery/mdns_test.go b/p2p/discovery/mdns_test.go index bb0e84d6f1..38b131c2f9 100644 --- a/p2p/discovery/mdns_test.go +++ b/p2p/discovery/mdns_test.go @@ -8,9 +8,9 @@ import ( bhost "github.com/libp2p/go-libp2p/p2p/host/basic" host "github.com/libp2p/go-libp2p-host" - swarmt "github.com/libp2p/go-libp2p-swarm/testing" - pstore "github.com/libp2p/go-libp2p-peerstore" + swarmt "github.com/libp2p/go-libp2p-swarm/testing" + ma "github.com/multiformats/go-multiaddr" ) type DiscoveryNotifee struct { @@ -21,6 +21,51 @@ func (n *DiscoveryNotifee) HandlePeerFound(pi pstore.PeerInfo) { n.h.Connect(context.Background(), pi) } +func TestGetBestPort(t *testing.T) { + port, err := getBestPort([]ma.Multiaddr{ma.StringCast("/ip4/1.2.3.4/tcp/2222"), ma.StringCast("/ip4/0.0.0.0/tcp/1234")}) + if err != nil { + t.Fatal(err) + } + if port != 1234 { + t.Errorf("expected port 1234, got port %d", port) + } + + port, err = getBestPort([]ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/2222"), ma.StringCast("/ip4/0.0.0.0/tcp/1234")}) + if err != nil { + t.Fatal(err) + } + if port != 1234 { + t.Errorf("expected port 1234, got port %d", port) + } + + port, err = getBestPort([]ma.Multiaddr{ma.StringCast("/ip4/1.2.3.4/tcp/2222"), ma.StringCast("/ip4/127.0.0.1/tcp/1234")}) + if err != nil { + t.Fatal(err) + } + if port != 2222 { + t.Errorf("expected port 2222, got port %d", port) + } + port, err = getBestPort([]ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/1234"), ma.StringCast("/ip4/1.2.3.4/tcp/2222")}) + if err != nil { + t.Fatal(err) + } + if port != 2222 { + t.Errorf("expected port 2222, got port %d", port) + } + port, err = getBestPort([]ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/1234")}) + if err != nil { + t.Fatal(err) + } + if port != 1234 { + t.Errorf("expected port 1234, got port %d", port) + } + + _, err = getBestPort([]ma.Multiaddr{}) + if err == nil { + t.Fatal("expected error") + } +} + func TestMdnsDiscovery(t *testing.T) { //TODO: re-enable when the new lib will get integrated t.Skip("TestMdnsDiscovery fails randomly with current lib") diff --git a/package.json b/package.json index 1df54e44e0..f0898cb65a 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,6 @@ "name": "go-semver", "version": "0.0.0" }, - { - "hash": "QmNeRzgrSpwbMU7VKLRyfvbqf1nRrAiQ7fEXaBxWGT5Ygr", - "name": "mdns", - "version": "0.1.3" - }, { "hash": "QmPdKqUcHGFdeSpvjVoaTRPPstGif9GBZb5Q56RVw9o69A", "name": "go-ipfs-util", @@ -249,6 +244,12 @@ "hash": "QmSSeQqc5QeuefkaM6JFV5tSF9knLUkXKVhW1eYRiqe72W", "name": "uuid", "version": "0.1.0" + }, + { + "author": "grandcat", + "hash": "QmWrJTtJCQ6kCCTtLBM82Cx9h52QMQ5hcbrsePFuRSHuWC", + "name": "zeroconf", + "version": "0.2.1" } ], "gxVersion": "0.4.0",