169 lines
5.1 KiB
Go
169 lines
5.1 KiB
Go
package ipfscluster
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
|
|
config "github.com/ipfs-cluster/ipfs-cluster/config"
|
|
ds "github.com/ipfs/go-datastore"
|
|
namespace "github.com/ipfs/go-datastore/namespace"
|
|
ipns "github.com/ipfs/go-ipns"
|
|
libp2p "github.com/libp2p/go-libp2p"
|
|
crypto "github.com/libp2p/go-libp2p/core/crypto"
|
|
host "github.com/libp2p/go-libp2p/core/host"
|
|
network "github.com/libp2p/go-libp2p/core/network"
|
|
corepnet "github.com/libp2p/go-libp2p/core/pnet"
|
|
routing "github.com/libp2p/go-libp2p/core/routing"
|
|
dht "github.com/libp2p/go-libp2p-kad-dht"
|
|
dual "github.com/libp2p/go-libp2p-kad-dht/dual"
|
|
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
|
record "github.com/libp2p/go-libp2p-record"
|
|
connmgr "github.com/libp2p/go-libp2p/p2p/net/connmgr"
|
|
identify "github.com/libp2p/go-libp2p/p2p/protocol/identify"
|
|
noise "github.com/libp2p/go-libp2p/p2p/security/noise"
|
|
libp2ptls "github.com/libp2p/go-libp2p/p2p/security/tls"
|
|
libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic"
|
|
tcp "github.com/libp2p/go-libp2p/p2p/transport/tcp"
|
|
websocket "github.com/libp2p/go-libp2p/p2p/transport/websocket"
|
|
)
|
|
|
|
const dhtNamespace = "dht"
|
|
|
|
var _ = libp2pquic.NewTransport
|
|
|
|
func init() {
|
|
// Cluster peers should advertise their public IPs as soon as they
|
|
// learn about them. Default for this is 4, which prevents clusters
|
|
// with less than 4 peers to advertise an external address they know
|
|
// of, therefore they cannot be remembered by other peers asap. This
|
|
// affects dockerized setups mostly. This may announce non-dialable
|
|
// NATed addresses too eagerly, but they should progressively be
|
|
// cleaned up.
|
|
identify.ActivationThresh = 1
|
|
}
|
|
|
|
// NewClusterHost creates a fully-featured libp2p Host with the options from
|
|
// the provided cluster configuration. Using that host, it creates pubsub and
|
|
// a DHT instances (persisting to the given datastore), for shared use by all
|
|
// cluster components. The returned host uses the DHT for routing. Relay and
|
|
// NATService are additionally setup for this host.
|
|
func NewClusterHost(
|
|
ctx context.Context,
|
|
ident *config.Identity,
|
|
cfg *Config,
|
|
ds ds.Datastore,
|
|
) (host.Host, *pubsub.PubSub, *dual.DHT, error) {
|
|
|
|
// Set the default dial timeout for all libp2p connections. It is not
|
|
// very good to touch this global variable here, but the alternative
|
|
// is to used a modify context everywhere, even if the user supplies
|
|
// it.
|
|
network.DialPeerTimeout = cfg.DialPeerTimeout
|
|
|
|
connman, err := connmgr.NewConnManager(cfg.ConnMgr.LowWater, cfg.ConnMgr.HighWater, connmgr.WithGracePeriod(cfg.ConnMgr.GracePeriod))
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
var idht *dual.DHT
|
|
opts := []libp2p.Option{
|
|
libp2p.ListenAddrs(cfg.ListenAddr...),
|
|
libp2p.NATPortMap(),
|
|
libp2p.ConnectionManager(connman),
|
|
libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) {
|
|
idht, err = newDHT(ctx, h, ds)
|
|
return idht, err
|
|
}),
|
|
libp2p.EnableNATService(),
|
|
libp2p.EnableRelay(),
|
|
libp2p.EnableAutoRelay(),
|
|
libp2p.EnableHolePunching(),
|
|
}
|
|
|
|
if cfg.EnableRelayHop {
|
|
opts = append(opts, libp2p.EnableRelayService())
|
|
}
|
|
|
|
h, err := newHost(
|
|
ctx,
|
|
cfg.Secret,
|
|
ident.PrivateKey,
|
|
opts...,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
psub, err := newPubSub(ctx, h)
|
|
if err != nil {
|
|
h.Close()
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
return h, psub, idht, nil
|
|
}
|
|
|
|
// newHost creates a base cluster host without dht, pubsub, relay or nat etc.
|
|
// mostly used for testing.
|
|
func newHost(ctx context.Context, psk corepnet.PSK, priv crypto.PrivKey, opts ...libp2p.Option) (host.Host, error) {
|
|
finalOpts := []libp2p.Option{
|
|
libp2p.Identity(priv),
|
|
}
|
|
finalOpts = append(finalOpts, baseOpts(psk)...)
|
|
finalOpts = append(finalOpts, opts...)
|
|
|
|
h, err := libp2p.New(
|
|
finalOpts...,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return h, nil
|
|
}
|
|
|
|
func baseOpts(psk corepnet.PSK) []libp2p.Option {
|
|
return []libp2p.Option{
|
|
libp2p.PrivateNetwork(psk),
|
|
libp2p.EnableNATService(),
|
|
libp2p.Security(noise.ID, noise.New),
|
|
libp2p.Security(libp2ptls.ID, libp2ptls.New),
|
|
// TODO: quic does not support private networks
|
|
// libp2p.DefaultTransports,
|
|
libp2p.NoTransports,
|
|
libp2p.Transport(tcp.NewTCPTransport),
|
|
libp2p.Transport(websocket.New),
|
|
}
|
|
}
|
|
|
|
func newDHT(ctx context.Context, h host.Host, store ds.Datastore, extraopts ...dual.Option) (*dual.DHT, error) {
|
|
opts := []dual.Option{
|
|
dual.DHTOption(dht.NamespacedValidator("pk", record.PublicKeyValidator{})),
|
|
dual.DHTOption(dht.NamespacedValidator("ipns", ipns.Validator{KeyBook: h.Peerstore()})),
|
|
dual.DHTOption(dht.Concurrency(10)),
|
|
}
|
|
|
|
opts = append(opts, extraopts...)
|
|
|
|
if batchingDs, ok := store.(ds.Batching); ok {
|
|
dhtDatastore := namespace.Wrap(batchingDs, ds.NewKey(dhtNamespace))
|
|
opts = append(opts, dual.DHTOption(dht.Datastore(dhtDatastore)))
|
|
logger.Debug("enabling DHT record persistence to datastore")
|
|
}
|
|
|
|
return dual.New(ctx, h, opts...)
|
|
}
|
|
|
|
func newPubSub(ctx context.Context, h host.Host) (*pubsub.PubSub, error) {
|
|
return pubsub.NewGossipSub(
|
|
ctx,
|
|
h,
|
|
pubsub.WithMessageSigning(true),
|
|
pubsub.WithStrictSignatureVerification(true),
|
|
)
|
|
}
|
|
|
|
// EncodeProtectorKey converts a byte slice to its hex string representation.
|
|
func EncodeProtectorKey(secretBytes []byte) string {
|
|
return hex.EncodeToString(secretBytes)
|
|
}
|