111 lines
2.8 KiB
Go
111 lines
2.8 KiB
Go
|
package autonat
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
|
||
|
pb "github.com/libp2p/go-libp2p-autonat/pb"
|
||
|
"github.com/libp2p/go-libp2p-core/helpers"
|
||
|
|
||
|
ggio "github.com/gogo/protobuf/io"
|
||
|
"github.com/libp2p/go-libp2p-core/host"
|
||
|
"github.com/libp2p/go-libp2p-core/network"
|
||
|
"github.com/libp2p/go-libp2p-core/peer"
|
||
|
ma "github.com/multiformats/go-multiaddr"
|
||
|
)
|
||
|
|
||
|
// AutoNATClient is a stateless client interface to AutoNAT peers
|
||
|
type AutoNATClient interface {
|
||
|
// DialBack requests from a peer providing AutoNAT services to test dial back
|
||
|
// and report the address on a successful connection.
|
||
|
DialBack(ctx context.Context, p peer.ID) (ma.Multiaddr, error)
|
||
|
}
|
||
|
|
||
|
// AutoNATError is the class of errors signalled by AutoNAT services
|
||
|
type AutoNATError struct {
|
||
|
Status pb.Message_ResponseStatus
|
||
|
Text string
|
||
|
}
|
||
|
|
||
|
// GetAddrs is a function that returns the addresses to dial back
|
||
|
type GetAddrs func() []ma.Multiaddr
|
||
|
|
||
|
// NewAutoNATClient creates a fresh instance of an AutoNATClient
|
||
|
// If getAddrs is nil, h.Addrs will be used
|
||
|
func NewAutoNATClient(h host.Host, getAddrs GetAddrs) AutoNATClient {
|
||
|
if getAddrs == nil {
|
||
|
getAddrs = h.Addrs
|
||
|
}
|
||
|
return &client{h: h, getAddrs: getAddrs}
|
||
|
}
|
||
|
|
||
|
type client struct {
|
||
|
h host.Host
|
||
|
getAddrs GetAddrs
|
||
|
}
|
||
|
|
||
|
func (c *client) DialBack(ctx context.Context, p peer.ID) (ma.Multiaddr, error) {
|
||
|
s, err := c.h.NewStream(ctx, p, AutoNATProto)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
// Might as well just reset the stream. Once we get to this point, we
|
||
|
// don't care about being nice.
|
||
|
defer helpers.FullClose(s)
|
||
|
|
||
|
r := ggio.NewDelimitedReader(s, network.MessageSizeMax)
|
||
|
w := ggio.NewDelimitedWriter(s)
|
||
|
|
||
|
req := newDialMessage(peer.AddrInfo{ID: c.h.ID(), Addrs: c.getAddrs()})
|
||
|
err = w.WriteMsg(req)
|
||
|
if err != nil {
|
||
|
s.Reset()
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var res pb.Message
|
||
|
err = r.ReadMsg(&res)
|
||
|
if err != nil {
|
||
|
s.Reset()
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if res.GetType() != pb.Message_DIAL_RESPONSE {
|
||
|
return nil, fmt.Errorf("Unexpected response: %s", res.GetType().String())
|
||
|
}
|
||
|
|
||
|
status := res.GetDialResponse().GetStatus()
|
||
|
switch status {
|
||
|
case pb.Message_OK:
|
||
|
addr := res.GetDialResponse().GetAddr()
|
||
|
return ma.NewMultiaddrBytes(addr)
|
||
|
|
||
|
default:
|
||
|
return nil, AutoNATError{Status: status, Text: res.GetDialResponse().GetStatusText()}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (e AutoNATError) Error() string {
|
||
|
return fmt.Sprintf("AutoNAT error: %s (%s)", e.Text, e.Status.String())
|
||
|
}
|
||
|
|
||
|
func (e AutoNATError) IsDialError() bool {
|
||
|
return e.Status == pb.Message_E_DIAL_ERROR
|
||
|
}
|
||
|
|
||
|
func (e AutoNATError) IsDialRefused() bool {
|
||
|
return e.Status == pb.Message_E_DIAL_REFUSED
|
||
|
}
|
||
|
|
||
|
// IsDialError returns true if the AutoNAT peer signalled an error dialing back
|
||
|
func IsDialError(e error) bool {
|
||
|
ae, ok := e.(AutoNATError)
|
||
|
return ok && ae.IsDialError()
|
||
|
}
|
||
|
|
||
|
// IsDialRefused returns true if the AutoNAT peer signalled refusal to dial back
|
||
|
func IsDialRefused(e error) bool {
|
||
|
ae, ok := e.(AutoNATError)
|
||
|
return ok && ae.IsDialRefused()
|
||
|
}
|