spegel/pkg/routing/bootstrap.go

193 lines
4.3 KiB
Go
Raw Normal View History

2023-01-26 18:48:02 +01:00
package routing
import (
"context"
2024-02-01 11:43:43 +01:00
"errors"
"io"
"net"
2024-02-01 11:43:43 +01:00
"net/http"
"slices"
"strings"
2023-01-27 13:26:49 +01:00
"sync"
2023-01-26 18:48:02 +01:00
"time"
2024-02-01 11:43:43 +01:00
"golang.org/x/sync/errgroup"
"github.com/libp2p/go-libp2p/core/peer"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
"github.com/spegel-org/spegel/pkg/httpx"
2023-01-26 18:48:02 +01:00
)
// Bootstrapper resolves peers to bootstrap with for the P2P router.
2023-01-26 18:48:02 +01:00
type Bootstrapper interface {
// Run starts the bootstrap process. Should be blocking even if not needed.
2023-01-27 13:26:49 +01:00
Run(ctx context.Context, id string) error
// Get returns a list of peers that should be used as bootstrap nodes.
// If the peer ID is empty it will be resolved.
// If the address is missing a port the P2P router port will be used.
Get(ctx context.Context) ([]peer.AddrInfo, error)
2023-01-26 18:48:02 +01:00
}
var _ Bootstrapper = &StaticBootstrapper{}
type StaticBootstrapper struct {
peers []peer.AddrInfo
mx sync.RWMutex
}
func NewStaticBootstrapperFromStrings(peerStrs []string) (*StaticBootstrapper, error) {
peers := []peer.AddrInfo{}
for _, peerStr := range peerStrs {
peer, err := peer.AddrInfoFromString(peerStr)
if err != nil {
return nil, err
}
peers = append(peers, *peer)
}
return NewStaticBootstrapper(peers), nil
}
func NewStaticBootstrapper(peers []peer.AddrInfo) *StaticBootstrapper {
return &StaticBootstrapper{
peers: peers,
}
}
func (b *StaticBootstrapper) Run(ctx context.Context, id string) error {
<-ctx.Done()
return nil
}
func (b *StaticBootstrapper) Get(ctx context.Context) ([]peer.AddrInfo, error) {
b.mx.RLock()
defer b.mx.RUnlock()
return b.peers, nil
}
func (b *StaticBootstrapper) SetPeers(peers []peer.AddrInfo) {
b.mx.Lock()
defer b.mx.Unlock()
b.peers = peers
}
var _ Bootstrapper = &DNSBootstrapper{}
type DNSBootstrapper struct {
resolver *net.Resolver
host string
limit int
}
func NewDNSBootstrapper(host string, limit int) *DNSBootstrapper {
return &DNSBootstrapper{
resolver: &net.Resolver{},
host: host,
limit: limit,
}
}
func (b *DNSBootstrapper) Run(ctx context.Context, id string) error {
<-ctx.Done()
return nil
}
func (b *DNSBootstrapper) Get(ctx context.Context) ([]peer.AddrInfo, error) {
ips, err := b.resolver.LookupIPAddr(ctx, b.host)
if err != nil {
return nil, err
}
if len(ips) == 0 {
return nil, err
}
slices.SortFunc(ips, func(a, b net.IPAddr) int {
return strings.Compare(a.String(), b.String())
})
addrInfos := []peer.AddrInfo{}
for _, ip := range ips {
addr, err := manet.FromIPAndZone(ip.IP, ip.Zone)
if err != nil {
return nil, err
}
addrInfos = append(addrInfos, peer.AddrInfo{
ID: "",
Addrs: []ma.Multiaddr{addr},
})
}
limit := min(len(addrInfos), b.limit)
return addrInfos[:limit], nil
}
var _ Bootstrapper = &HTTPBootstrapper{}
2024-02-01 11:43:43 +01:00
type HTTPBootstrapper struct {
httpClient *http.Client
addr string
peer string
2024-02-01 11:43:43 +01:00
}
func NewHTTPBootstrapper(addr, peer string) *HTTPBootstrapper {
return &HTTPBootstrapper{
httpClient: httpx.BaseClient(),
addr: addr,
peer: peer,
2024-02-01 11:43:43 +01:00
}
}
func (bs *HTTPBootstrapper) Run(ctx context.Context, id string) error {
2024-02-01 11:43:43 +01:00
g, ctx := errgroup.WithContext(ctx)
mux := http.NewServeMux()
mux.HandleFunc("/id", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
//nolint:errcheck // ignore
w.Write([]byte(id))
})
srv := http.Server{
Addr: bs.addr,
2024-02-01 11:43:43 +01:00
Handler: mux,
}
g.Go(func() error {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
})
g.Go(func() error {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return srv.Shutdown(shutdownCtx)
})
return g.Wait()
}
func (bs *HTTPBootstrapper) Get(ctx context.Context) ([]peer.AddrInfo, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, bs.peer, nil)
if err != nil {
return nil, err
}
resp, err := bs.httpClient.Do(req)
if err != nil {
return nil, err
}
defer httpx.DrainAndClose(resp.Body)
err = httpx.CheckResponseStatus(resp, http.StatusOK)
2024-02-01 11:43:43 +01:00
if err != nil {
return nil, err
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
addr, err := ma.NewMultiaddr(string(b))
2024-02-01 11:43:43 +01:00
if err != nil {
return nil, err
}
addrInfo, err := peer.AddrInfoFromP2pAddr(addr)
if err != nil {
return nil, err
}
return []peer.AddrInfo{*addrInfo}, nil
2024-02-01 11:43:43 +01:00
}