mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-26 08:35:17 +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)
|
|
}
|