mirror of
				https://source.quilibrium.com/quilibrium/ceremonyclient.git
				synced 2025-11-04 00:27:41 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			203 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package mdns
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
	"io"
 | 
						|
	"math/rand"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
 | 
						|
	"github.com/libp2p/go-libp2p/core/host"
 | 
						|
	"github.com/libp2p/go-libp2p/core/peer"
 | 
						|
 | 
						|
	"github.com/libp2p/zeroconf/v2"
 | 
						|
 | 
						|
	logging "github.com/ipfs/go-log/v2"
 | 
						|
	ma "github.com/multiformats/go-multiaddr"
 | 
						|
	manet "github.com/multiformats/go-multiaddr/net"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	ServiceName   = "_p2p._udp"
 | 
						|
	mdnsDomain    = "local"
 | 
						|
	dnsaddrPrefix = "dnsaddr="
 | 
						|
)
 | 
						|
 | 
						|
var log = logging.Logger("mdns")
 | 
						|
 | 
						|
type Service interface {
 | 
						|
	Start() error
 | 
						|
	io.Closer
 | 
						|
}
 | 
						|
 | 
						|
type Notifee interface {
 | 
						|
	HandlePeerFound(peer.AddrInfo)
 | 
						|
}
 | 
						|
 | 
						|
type mdnsService struct {
 | 
						|
	host        host.Host
 | 
						|
	serviceName string
 | 
						|
	peerName    string
 | 
						|
 | 
						|
	// The context is canceled when Close() is called.
 | 
						|
	ctx       context.Context
 | 
						|
	ctxCancel context.CancelFunc
 | 
						|
 | 
						|
	resolverWG sync.WaitGroup
 | 
						|
	server     *zeroconf.Server
 | 
						|
 | 
						|
	notifee Notifee
 | 
						|
}
 | 
						|
 | 
						|
func NewMdnsService(host host.Host, serviceName string, notifee Notifee) *mdnsService {
 | 
						|
	if serviceName == "" {
 | 
						|
		serviceName = ServiceName
 | 
						|
	}
 | 
						|
	s := &mdnsService{
 | 
						|
		host:        host,
 | 
						|
		serviceName: serviceName,
 | 
						|
		peerName:    randomString(32 + rand.Intn(32)), // generate a random string between 32 and 63 characters long
 | 
						|
		notifee:     notifee,
 | 
						|
	}
 | 
						|
	s.ctx, s.ctxCancel = context.WithCancel(context.Background())
 | 
						|
	return s
 | 
						|
}
 | 
						|
 | 
						|
func (s *mdnsService) Start() error {
 | 
						|
	if err := s.startServer(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	s.startResolver(s.ctx)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *mdnsService) Close() error {
 | 
						|
	s.ctxCancel()
 | 
						|
	if s.server != nil {
 | 
						|
		s.server.Shutdown()
 | 
						|
	}
 | 
						|
	s.resolverWG.Wait()
 | 
						|
	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 {
 | 
						|
		first, _ := ma.SplitFirst(addr)
 | 
						|
		if first == nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if ip4 == "" && first.Protocol().Code == ma.P_IP4 {
 | 
						|
			ip4 = first.Value()
 | 
						|
		} else if ip6 == "" && first.Protocol().Code == ma.P_IP6 {
 | 
						|
			ip6 = first.Value()
 | 
						|
		}
 | 
						|
	}
 | 
						|
	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) 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.peerName,
 | 
						|
		s.serviceName,
 | 
						|
		mdnsDomain,
 | 
						|
		4001, // we have to pass in a port number here, but libp2p only uses the TXT records
 | 
						|
		s.peerName,
 | 
						|
		ips,
 | 
						|
		txts,
 | 
						|
		nil,
 | 
						|
	)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	s.server = server
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *mdnsService) startResolver(ctx context.Context) {
 | 
						|
	s.resolverWG.Add(2)
 | 
						|
	entryChan := make(chan *zeroconf.ServiceEntry, 1000)
 | 
						|
	go func() {
 | 
						|
		defer s.resolverWG.Done()
 | 
						|
		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
 | 
						|
			}
 | 
						|
			for _, info := range infos {
 | 
						|
				if info.ID == s.host.ID() {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				go s.notifee.HandlePeerFound(info)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	go func() {
 | 
						|
		defer s.resolverWG.Done()
 | 
						|
		if err := zeroconf.Browse(ctx, s.serviceName, mdnsDomain, entryChan); err != nil {
 | 
						|
			log.Debugf("zeroconf browsing failed: %s", err)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
}
 | 
						|
 | 
						|
func randomString(l int) string {
 | 
						|
	const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
 | 
						|
	s := make([]byte, 0, l)
 | 
						|
	for i := 0; i < l; i++ {
 | 
						|
		s = append(s, alphabet[rand.Intn(len(alphabet))])
 | 
						|
	}
 | 
						|
	return string(s)
 | 
						|
}
 |