2018-04-19 20:18:42 +03:00
|
|
|
package rendezvous
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2021-09-30 10:54:48 -04:00
|
|
|
"errors"
|
2018-04-19 20:18:42 +03:00
|
|
|
"fmt"
|
2018-04-24 21:26:06 +03:00
|
|
|
"math/rand"
|
2018-04-19 20:18:42 +03:00
|
|
|
"time"
|
|
|
|
|
|
2021-09-28 15:37:10 -04:00
|
|
|
pb "github.com/status-im/go-libp2p-rendezvous/pb"
|
2018-04-19 20:18:42 +03:00
|
|
|
|
|
|
|
|
ggio "github.com/gogo/protobuf/io"
|
2019-05-28 14:41:28 -04:00
|
|
|
|
|
|
|
|
"github.com/libp2p/go-libp2p-core/host"
|
|
|
|
|
inet "github.com/libp2p/go-libp2p-core/network"
|
|
|
|
|
"github.com/libp2p/go-libp2p-core/peer"
|
2018-04-19 20:18:42 +03:00
|
|
|
)
|
|
|
|
|
|
2018-04-24 18:43:43 +03:00
|
|
|
var (
|
|
|
|
|
DiscoverAsyncInterval = 2 * time.Minute
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-28 12:05:21 +03:00
|
|
|
type RendezvousPoint interface {
|
2019-05-31 18:58:46 -04:00
|
|
|
Register(ctx context.Context, ns string, ttl int) (time.Duration, error)
|
2021-09-29 14:33:27 -04:00
|
|
|
Discover(ctx context.Context, ns string, limit int) ([]Registration, error)
|
2018-04-21 18:51:18 +03:00
|
|
|
DiscoverAsync(ctx context.Context, ns string) (<-chan Registration, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Registration struct {
|
2019-05-28 14:41:28 -04:00
|
|
|
Peer peer.AddrInfo
|
2018-04-21 18:51:18 +03:00
|
|
|
Ns string
|
|
|
|
|
Ttl int
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
2018-04-28 12:05:21 +03:00
|
|
|
type RendezvousClient interface {
|
2019-05-31 18:58:46 -04:00
|
|
|
Register(ctx context.Context, ns string, ttl int) (time.Duration, error)
|
2021-09-29 14:33:27 -04:00
|
|
|
Discover(ctx context.Context, ns string, limit int) ([]peer.AddrInfo, error)
|
2019-05-28 14:41:28 -04:00
|
|
|
DiscoverAsync(ctx context.Context, ns string) (<-chan peer.AddrInfo, error)
|
2018-04-28 12:05:21 +03:00
|
|
|
}
|
|
|
|
|
|
2021-09-30 10:39:04 -04:00
|
|
|
func NewRendezvousPoint(host host.Host) RendezvousPoint {
|
2018-04-28 12:05:21 +03:00
|
|
|
return &rendezvousPoint{
|
2018-04-19 20:18:42 +03:00
|
|
|
host: host,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-28 12:05:21 +03:00
|
|
|
type rendezvousPoint struct {
|
2018-04-19 20:18:42 +03:00
|
|
|
host host.Host
|
2018-04-28 12:05:21 +03:00
|
|
|
}
|
|
|
|
|
|
2021-09-30 10:39:04 -04:00
|
|
|
func NewRendezvousClient(host host.Host) RendezvousClient {
|
|
|
|
|
return NewRendezvousClientWithPoint(NewRendezvousPoint(host))
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
2018-04-28 12:05:21 +03:00
|
|
|
func NewRendezvousClientWithPoint(rp RendezvousPoint) RendezvousClient {
|
|
|
|
|
return &rendezvousClient{rp: rp}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type rendezvousClient struct {
|
|
|
|
|
rp RendezvousPoint
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-30 10:39:04 -04:00
|
|
|
func (r *rendezvousPoint) getRandomPeer() (peer.ID, error) {
|
|
|
|
|
var peerIDs []peer.ID
|
|
|
|
|
for _, peer := range r.host.Peerstore().Peers() {
|
|
|
|
|
protocols, err := r.host.Peerstore().SupportsProtocols(peer, string(RendezvousID_v001))
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Error("error obtaining the protocols supported by peers", err)
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
if len(protocols) > 0 {
|
|
|
|
|
peerIDs = append(peerIDs, peer)
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-30 10:54:48 -04:00
|
|
|
|
|
|
|
|
if len(peerIDs) == 0 {
|
|
|
|
|
return "", errors.New("no peers available")
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-30 10:39:04 -04:00
|
|
|
return peerIDs[rand.Intn(len(peerIDs))], nil // nolint: gosec
|
2021-09-28 16:27:40 -04:00
|
|
|
}
|
|
|
|
|
|
2019-05-31 18:58:46 -04:00
|
|
|
func (rp *rendezvousPoint) Register(ctx context.Context, ns string, ttl int) (time.Duration, error) {
|
2021-09-30 10:39:04 -04:00
|
|
|
randomPeer, err := rp.getRandomPeer()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s, err := rp.host.NewStream(ctx, randomPeer, RendezvousID_v001)
|
2018-04-19 20:18:42 +03:00
|
|
|
if err != nil {
|
2019-06-05 13:44:44 -04:00
|
|
|
return 0, err
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
2020-10-28 14:39:45 +01:00
|
|
|
defer s.Reset()
|
2018-04-19 20:18:42 +03:00
|
|
|
|
2018-04-23 11:54:25 +03:00
|
|
|
r := ggio.NewDelimitedReader(s, inet.MessageSizeMax)
|
2018-04-19 20:18:42 +03:00
|
|
|
w := ggio.NewDelimitedWriter(s)
|
|
|
|
|
|
2019-05-28 14:41:28 -04:00
|
|
|
req := newRegisterMessage(ns, peer.AddrInfo{ID: rp.host.ID(), Addrs: rp.host.Addrs()}, ttl)
|
2018-04-20 13:07:01 +03:00
|
|
|
err = w.WriteMsg(req)
|
2018-04-19 20:18:42 +03:00
|
|
|
if err != nil {
|
2019-05-31 18:58:46 -04:00
|
|
|
return 0, err
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var res pb.Message
|
|
|
|
|
err = r.ReadMsg(&res)
|
|
|
|
|
if err != nil {
|
2019-05-31 18:58:46 -04:00
|
|
|
return 0, err
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if res.GetType() != pb.Message_REGISTER_RESPONSE {
|
2019-05-31 18:58:46 -04:00
|
|
|
return 0, fmt.Errorf("Unexpected response: %s", res.GetType().String())
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
2019-05-31 18:58:46 -04:00
|
|
|
response := res.GetRegisterResponse()
|
|
|
|
|
status := response.GetStatus()
|
2018-04-19 20:18:42 +03:00
|
|
|
if status != pb.Message_OK {
|
2019-05-31 18:58:46 -04:00
|
|
|
return 0, RendezvousError{Status: status, Text: res.GetRegisterResponse().GetStatusText()}
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
2021-09-29 14:33:27 -04:00
|
|
|
return time.Duration(response.Ttl) * time.Second, nil
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
2019-05-31 18:58:46 -04:00
|
|
|
func (rc *rendezvousClient) Register(ctx context.Context, ns string, ttl int) (time.Duration, error) {
|
2018-04-23 14:50:46 +03:00
|
|
|
if ttl < 120 {
|
2019-05-31 18:58:46 -04:00
|
|
|
return 0, fmt.Errorf("registration TTL is too short")
|
2018-04-23 14:50:46 +03:00
|
|
|
}
|
|
|
|
|
|
2019-05-31 18:58:46 -04:00
|
|
|
returnedTTL, err := rc.rp.Register(ctx, ns, ttl)
|
2018-04-19 22:29:52 +03:00
|
|
|
if err != nil {
|
2019-05-31 18:58:46 -04:00
|
|
|
return 0, err
|
2018-04-19 22:29:52 +03:00
|
|
|
}
|
|
|
|
|
|
2018-04-28 12:05:21 +03:00
|
|
|
go registerRefresh(ctx, rc.rp, ns, ttl)
|
2019-05-31 18:58:46 -04:00
|
|
|
return returnedTTL, nil
|
2018-04-19 22:29:52 +03:00
|
|
|
}
|
|
|
|
|
|
2018-04-28 12:05:21 +03:00
|
|
|
func registerRefresh(ctx context.Context, rz RendezvousPoint, ns string, ttl int) {
|
2018-04-24 21:26:06 +03:00
|
|
|
var refresh time.Duration
|
|
|
|
|
errcount := 0
|
2018-04-19 22:29:52 +03:00
|
|
|
|
|
|
|
|
for {
|
2018-04-24 21:26:06 +03:00
|
|
|
if errcount > 0 {
|
|
|
|
|
// do randomized exponential backoff, up to ~4 hours
|
|
|
|
|
if errcount > 7 {
|
|
|
|
|
errcount = 7
|
|
|
|
|
}
|
|
|
|
|
backoff := 2 << uint(errcount)
|
|
|
|
|
refresh = 5*time.Minute + time.Duration(rand.Intn(backoff*60000))*time.Millisecond
|
|
|
|
|
} else {
|
|
|
|
|
refresh = time.Duration(ttl-30) * time.Second
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-19 22:29:52 +03:00
|
|
|
select {
|
2018-04-23 14:50:46 +03:00
|
|
|
case <-time.After(refresh):
|
2018-04-19 22:29:52 +03:00
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
}
|
2018-04-20 13:07:01 +03:00
|
|
|
|
2019-06-05 13:44:44 -04:00
|
|
|
_, err := rz.Register(ctx, ns, ttl)
|
2018-04-20 13:07:01 +03:00
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Error registering [%s]: %s", ns, err.Error())
|
2018-04-24 21:26:06 +03:00
|
|
|
errcount++
|
|
|
|
|
} else {
|
|
|
|
|
errcount = 0
|
2018-04-20 13:07:01 +03:00
|
|
|
}
|
2018-04-19 22:29:52 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-29 14:33:27 -04:00
|
|
|
func (rp *rendezvousPoint) Discover(ctx context.Context, ns string, limit int) ([]Registration, error) {
|
2021-09-30 10:39:04 -04:00
|
|
|
randomPeer, err := rp.getRandomPeer()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s, err := rp.host.NewStream(ctx, randomPeer, RendezvousID_v001)
|
2018-04-19 20:18:42 +03:00
|
|
|
if err != nil {
|
2021-09-29 14:33:27 -04:00
|
|
|
return nil, err
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
2020-10-28 14:39:45 +01:00
|
|
|
defer s.Reset()
|
2018-04-19 20:18:42 +03:00
|
|
|
|
2018-04-23 11:54:25 +03:00
|
|
|
r := ggio.NewDelimitedReader(s, inet.MessageSizeMax)
|
2018-04-19 20:18:42 +03:00
|
|
|
w := ggio.NewDelimitedWriter(s)
|
|
|
|
|
|
2021-09-29 14:33:27 -04:00
|
|
|
return discoverQuery(ns, limit, r, w)
|
2018-04-20 13:07:01 +03:00
|
|
|
}
|
|
|
|
|
|
2021-09-29 14:33:27 -04:00
|
|
|
func discoverQuery(ns string, limit int, r ggio.Reader, w ggio.Writer) ([]Registration, error) {
|
|
|
|
|
req := newDiscoverMessage(ns, limit)
|
2018-04-19 22:29:52 +03:00
|
|
|
err := w.WriteMsg(req)
|
2018-04-19 20:18:42 +03:00
|
|
|
if err != nil {
|
2021-09-29 14:33:27 -04:00
|
|
|
return nil, err
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var res pb.Message
|
|
|
|
|
err = r.ReadMsg(&res)
|
|
|
|
|
if err != nil {
|
2021-09-29 14:33:27 -04:00
|
|
|
return nil, err
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if res.GetType() != pb.Message_DISCOVER_RESPONSE {
|
2021-09-29 14:33:27 -04:00
|
|
|
return nil, fmt.Errorf("Unexpected response: %s", res.GetType().String())
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
2018-04-21 12:06:27 +03:00
|
|
|
status := res.GetDiscoverResponse().GetStatus()
|
|
|
|
|
if status != pb.Message_OK {
|
2021-09-29 14:33:27 -04:00
|
|
|
return nil, RendezvousError{Status: status, Text: res.GetDiscoverResponse().GetStatusText()}
|
2018-04-21 12:06:27 +03:00
|
|
|
}
|
|
|
|
|
|
2018-04-19 20:18:42 +03:00
|
|
|
regs := res.GetDiscoverResponse().GetRegistrations()
|
2018-04-21 18:51:18 +03:00
|
|
|
result := make([]Registration, 0, len(regs))
|
2018-04-19 20:18:42 +03:00
|
|
|
for _, reg := range regs {
|
|
|
|
|
pi, err := pbToPeerInfo(reg.GetPeer())
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Invalid peer info: %s", err.Error())
|
|
|
|
|
continue
|
|
|
|
|
}
|
2018-04-21 18:51:18 +03:00
|
|
|
result = append(result, Registration{Peer: pi, Ns: reg.GetNs(), Ttl: int(reg.GetTtl())})
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
2021-09-29 14:33:27 -04:00
|
|
|
return result, nil
|
2018-04-19 20:18:42 +03:00
|
|
|
}
|
|
|
|
|
|
2018-04-28 12:05:21 +03:00
|
|
|
func (rp *rendezvousPoint) DiscoverAsync(ctx context.Context, ns string) (<-chan Registration, error) {
|
2021-09-30 10:39:04 -04:00
|
|
|
randomPeer, err := rp.getRandomPeer()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s, err := rp.host.NewStream(ctx, randomPeer, RendezvousID_v001)
|
2018-04-19 20:18:42 +03:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-21 18:51:18 +03:00
|
|
|
ch := make(chan Registration)
|
2018-04-20 13:07:01 +03:00
|
|
|
go discoverAsync(ctx, ns, s, ch)
|
2018-04-19 20:18:42 +03:00
|
|
|
return ch, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-21 18:51:18 +03:00
|
|
|
func discoverAsync(ctx context.Context, ns string, s inet.Stream, ch chan Registration) {
|
2020-10-28 14:39:45 +01:00
|
|
|
defer s.Reset()
|
2018-04-19 20:18:42 +03:00
|
|
|
defer close(ch)
|
|
|
|
|
|
2018-04-23 11:54:25 +03:00
|
|
|
r := ggio.NewDelimitedReader(s, inet.MessageSizeMax)
|
2018-04-20 13:07:01 +03:00
|
|
|
w := ggio.NewDelimitedWriter(s)
|
|
|
|
|
|
2018-04-23 13:29:19 +03:00
|
|
|
const batch = 200
|
2018-04-19 20:18:42 +03:00
|
|
|
|
2018-04-19 22:29:52 +03:00
|
|
|
var (
|
2021-09-29 14:33:27 -04:00
|
|
|
regs []Registration
|
|
|
|
|
err error
|
2018-04-19 22:29:52 +03:00
|
|
|
)
|
2018-04-20 13:07:01 +03:00
|
|
|
|
2018-04-19 20:18:42 +03:00
|
|
|
for {
|
2021-09-29 14:33:27 -04:00
|
|
|
regs, err = discoverQuery(ns, batch, r, w)
|
2018-04-19 20:18:42 +03:00
|
|
|
if err != nil {
|
2018-04-25 11:06:08 +03:00
|
|
|
// TODO robust error recovery
|
2021-09-29 14:33:27 -04:00
|
|
|
// - handle closed streams with backoff + new stream
|
2018-04-20 12:01:35 +03:00
|
|
|
log.Errorf("Error in discovery [%s]: %s", ns, err.Error())
|
2018-04-19 20:18:42 +03:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-21 18:51:18 +03:00
|
|
|
for _, reg := range regs {
|
2018-04-19 20:18:42 +03:00
|
|
|
select {
|
2018-04-21 18:51:18 +03:00
|
|
|
case ch <- reg:
|
2018-04-19 20:18:42 +03:00
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-23 13:29:19 +03:00
|
|
|
if len(regs) < batch {
|
2018-04-23 13:38:34 +03:00
|
|
|
// TODO adaptive backoff for heavily loaded rendezvous points
|
2018-04-19 20:18:42 +03:00
|
|
|
select {
|
2018-04-24 18:43:43 +03:00
|
|
|
case <-time.After(DiscoverAsyncInterval):
|
2018-04-19 20:18:42 +03:00
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-21 18:51:18 +03:00
|
|
|
|
2021-09-29 14:33:27 -04:00
|
|
|
func (rc *rendezvousClient) Discover(ctx context.Context, ns string, limit int) ([]peer.AddrInfo, error) {
|
|
|
|
|
regs, err := rc.rp.Discover(ctx, ns, limit)
|
2018-04-21 18:51:18 +03:00
|
|
|
if err != nil {
|
2021-09-29 14:33:27 -04:00
|
|
|
return nil, err
|
2018-04-21 18:51:18 +03:00
|
|
|
}
|
|
|
|
|
|
2019-05-28 14:41:28 -04:00
|
|
|
pinfos := make([]peer.AddrInfo, len(regs))
|
2018-04-21 18:51:18 +03:00
|
|
|
for i, reg := range regs {
|
|
|
|
|
pinfos[i] = reg.Peer
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-29 14:33:27 -04:00
|
|
|
return pinfos, nil
|
2018-04-21 18:51:18 +03:00
|
|
|
}
|
|
|
|
|
|
2019-05-28 14:41:28 -04:00
|
|
|
func (rc *rendezvousClient) DiscoverAsync(ctx context.Context, ns string) (<-chan peer.AddrInfo, error) {
|
2018-04-28 12:05:21 +03:00
|
|
|
rch, err := rc.rp.DiscoverAsync(ctx, ns)
|
2018-04-21 18:51:18 +03:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 14:41:28 -04:00
|
|
|
ch := make(chan peer.AddrInfo)
|
2018-04-21 18:51:18 +03:00
|
|
|
go discoverPeersAsync(ctx, rch, ch)
|
|
|
|
|
return ch, nil
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 14:41:28 -04:00
|
|
|
func discoverPeersAsync(ctx context.Context, rch <-chan Registration, ch chan peer.AddrInfo) {
|
2018-04-21 18:51:18 +03:00
|
|
|
defer close(ch)
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case reg, ok := <-rch:
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case ch <- reg.Peer:
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|