github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/p2p/discovery/mdns.go (about)

     1  package discovery
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"io/ioutil"
     7  	golog "log"
     8  	"net"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/cryptix/mdns"
    13  	ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
    14  	manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
    15  
    16  	"github.com/ipfs/go-ipfs/p2p/host"
    17  	"github.com/ipfs/go-ipfs/p2p/peer"
    18  	u "github.com/ipfs/go-ipfs/util"
    19  )
    20  
    21  var log = u.Logger("mdns")
    22  
    23  const ServiceTag = "discovery.ipfs.io"
    24  
    25  type Service interface {
    26  	io.Closer
    27  	RegisterNotifee(Notifee)
    28  	UnregisterNotifee(Notifee)
    29  }
    30  
    31  type Notifee interface {
    32  	HandlePeerFound(peer.PeerInfo)
    33  }
    34  
    35  type mdnsService struct {
    36  	server  *mdns.Server
    37  	service *mdns.MDNSService
    38  	host    host.Host
    39  
    40  	lk       sync.Mutex
    41  	notifees []Notifee
    42  	interval time.Duration
    43  }
    44  
    45  func getDialableListenAddrs(ph host.Host) ([]*net.TCPAddr, error) {
    46  	var out []*net.TCPAddr
    47  	for _, addr := range ph.Addrs() {
    48  		na, err := manet.ToNetAddr(addr)
    49  		if err != nil {
    50  			continue
    51  		}
    52  		tcp, ok := na.(*net.TCPAddr)
    53  		if ok {
    54  			out = append(out, tcp)
    55  		}
    56  	}
    57  	if len(out) == 0 {
    58  		return nil, errors.New("failed to find good external addr from peerhost")
    59  	}
    60  	return out, nil
    61  }
    62  
    63  func NewMdnsService(peerhost host.Host, interval time.Duration) (Service, error) {
    64  
    65  	// TODO: dont let mdns use logging...
    66  	golog.SetOutput(ioutil.Discard)
    67  
    68  	var ipaddrs []net.IP
    69  	port := 4001
    70  
    71  	addrs, err := getDialableListenAddrs(peerhost)
    72  	if err != nil {
    73  		log.Warning(err)
    74  	} else {
    75  		port = addrs[0].Port
    76  		for _, a := range addrs {
    77  			ipaddrs = append(ipaddrs, a.IP)
    78  		}
    79  	}
    80  
    81  	myid := peerhost.ID().Pretty()
    82  
    83  	info := []string{myid}
    84  	service, err := mdns.NewMDNSService(myid, ServiceTag, "", "", port, ipaddrs, info)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	// Create the mDNS server, defer shutdown
    90  	server, err := mdns.NewServer(&mdns.Config{Zone: service})
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	s := &mdnsService{
    96  		server:   server,
    97  		service:  service,
    98  		host:     peerhost,
    99  		interval: interval,
   100  	}
   101  
   102  	go s.pollForEntries()
   103  
   104  	return s, nil
   105  }
   106  
   107  func (m *mdnsService) Close() error {
   108  	return m.server.Shutdown()
   109  }
   110  
   111  func (m *mdnsService) pollForEntries() {
   112  	ticker := time.NewTicker(m.interval)
   113  	for {
   114  		select {
   115  		case <-ticker.C:
   116  			entriesCh := make(chan *mdns.ServiceEntry, 16)
   117  			go func() {
   118  				for entry := range entriesCh {
   119  					m.handleEntry(entry)
   120  				}
   121  			}()
   122  
   123  			qp := mdns.QueryParam{}
   124  			qp.Domain = "local"
   125  			qp.Entries = entriesCh
   126  			qp.Service = ServiceTag
   127  			qp.Timeout = time.Second * 5
   128  
   129  			err := mdns.Query(&qp)
   130  			if err != nil {
   131  				log.Error("mdns lookup error: ", err)
   132  			}
   133  			close(entriesCh)
   134  		}
   135  	}
   136  }
   137  
   138  func (m *mdnsService) handleEntry(e *mdns.ServiceEntry) {
   139  	mpeer, err := peer.IDB58Decode(e.Info)
   140  	if err != nil {
   141  		log.Warning("Error parsing peer ID from mdns entry: ", err)
   142  		return
   143  	}
   144  
   145  	if mpeer == m.host.ID() {
   146  		return
   147  	}
   148  
   149  	maddr, err := manet.FromNetAddr(&net.TCPAddr{
   150  		IP:   e.AddrV4,
   151  		Port: e.Port,
   152  	})
   153  	if err != nil {
   154  		log.Warning("Error parsing multiaddr from mdns entry: ", err)
   155  		return
   156  	}
   157  
   158  	pi := peer.PeerInfo{
   159  		ID:    mpeer,
   160  		Addrs: []ma.Multiaddr{maddr},
   161  	}
   162  
   163  	m.lk.Lock()
   164  	for _, n := range m.notifees {
   165  		n.HandlePeerFound(pi)
   166  	}
   167  	m.lk.Unlock()
   168  }
   169  
   170  func (m *mdnsService) RegisterNotifee(n Notifee) {
   171  	m.lk.Lock()
   172  	m.notifees = append(m.notifees, n)
   173  	m.lk.Unlock()
   174  }
   175  
   176  func (m *mdnsService) UnregisterNotifee(n Notifee) {
   177  	m.lk.Lock()
   178  	found := -1
   179  	for i, notif := range m.notifees {
   180  		if notif == n {
   181  			found = i
   182  			break
   183  		}
   184  	}
   185  	if found != -1 {
   186  		m.notifees = append(m.notifees[:found], m.notifees[found+1:]...)
   187  	}
   188  	m.lk.Unlock()
   189  }