Integrate BACKBEAT SDK and resolve KACHING license validation

Major integrations and fixes:
- Added BACKBEAT SDK integration for P2P operation timing
- Implemented beat-aware status tracking for distributed operations
- Added Docker secrets support for secure license management
- Resolved KACHING license validation via HTTPS/TLS
- Updated docker-compose configuration for clean stack deployment
- Disabled rollback policies to prevent deployment failures
- Added license credential storage (CHORUS-DEV-MULTI-001)

Technical improvements:
- BACKBEAT P2P operation tracking with phase management
- Enhanced configuration system with file-based secrets
- Improved error handling for license validation
- Clean separation of KACHING and CHORUS deployment stacks

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-09-06 07:56:26 +10:00
parent 543ab216f9
commit 9bdcbe0447
4730 changed files with 1480093 additions and 1916 deletions

View File

@@ -0,0 +1,76 @@
package client
import (
"context"
"io"
"sync"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/transport"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto"
logging "github.com/ipfs/go-log/v2"
)
var log = logging.Logger("p2p-circuit")
// Client implements the client-side of the p2p-circuit/v2 protocol:
// - it implements dialing through v2 relays
// - it listens for incoming connections through v2 relays.
//
// For backwards compatibility with v1 relays and older nodes, the client will
// also accept relay connections through v1 relays and fallback dial peers using p2p-circuit/v1.
// This allows us to use the v2 code as drop in replacement for v1 in a host without breaking
// existing code and interoperability with older nodes.
type Client struct {
ctx context.Context
ctxCancel context.CancelFunc
host host.Host
upgrader transport.Upgrader
incoming chan accept
mx sync.Mutex
activeDials map[peer.ID]*completion
hopCount map[peer.ID]int
}
var _ io.Closer = &Client{}
var _ transport.Transport = &Client{}
type accept struct {
conn *Conn
writeResponse func() error
}
type completion struct {
ch chan struct{}
relay peer.ID
err error
}
// New constructs a new p2p-circuit/v2 client, attached to the given host and using the given
// upgrader to perform connection upgrades.
func New(h host.Host, upgrader transport.Upgrader) (*Client, error) {
cl := &Client{
host: h,
upgrader: upgrader,
incoming: make(chan accept),
activeDials: make(map[peer.ID]*completion),
hopCount: make(map[peer.ID]int),
}
cl.ctx, cl.ctxCancel = context.WithCancel(context.Background())
return cl, nil
}
// Start registers the circuit (client) protocol stream handlers
func (c *Client) Start() {
c.host.SetStreamHandler(proto.ProtoIDv2Stop, c.handleStreamV2)
}
func (c *Client) Close() error {
c.ctxCancel()
c.host.RemoveStreamHandler(proto.ProtoIDv2Stop)
return nil
}

View File

@@ -0,0 +1,163 @@
package client
import (
"fmt"
"net"
"time"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
tpt "github.com/libp2p/go-libp2p/core/transport"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)
// HopTagWeight is the connection manager weight for connections carrying relay hop streams
var HopTagWeight = 5
type statLimitDuration struct{}
type statLimitData struct{}
var (
StatLimitDuration = statLimitDuration{}
StatLimitData = statLimitData{}
)
type Conn struct {
stream network.Stream
remote peer.AddrInfo
stat network.ConnStats
client *Client
}
type NetAddr struct {
Relay string
Remote string
}
var _ net.Addr = (*NetAddr)(nil)
func (n *NetAddr) Network() string {
return "libp2p-circuit-relay"
}
func (n *NetAddr) String() string {
return fmt.Sprintf("relay[%s-%s]", n.Remote, n.Relay)
}
// Conn interface
var _ manet.Conn = (*Conn)(nil)
func (c *Conn) Close() error {
c.untagHop()
return c.stream.Reset()
}
func (c *Conn) Read(buf []byte) (int, error) {
return c.stream.Read(buf)
}
func (c *Conn) Write(buf []byte) (int, error) {
return c.stream.Write(buf)
}
func (c *Conn) SetDeadline(t time.Time) error {
return c.stream.SetDeadline(t)
}
func (c *Conn) SetReadDeadline(t time.Time) error {
return c.stream.SetReadDeadline(t)
}
func (c *Conn) SetWriteDeadline(t time.Time) error {
return c.stream.SetWriteDeadline(t)
}
// TODO: is it okay to cast c.Conn().RemotePeer() into a multiaddr? might be "user input"
func (c *Conn) RemoteMultiaddr() ma.Multiaddr {
// TODO: We should be able to do this directly without converting to/from a string.
relayAddr, err := ma.NewComponent(
ma.ProtocolWithCode(ma.P_P2P).Name,
c.stream.Conn().RemotePeer().String(),
)
if err != nil {
panic(err)
}
return ma.Join(c.stream.Conn().RemoteMultiaddr(), relayAddr, circuitAddr)
}
func (c *Conn) LocalMultiaddr() ma.Multiaddr {
return c.stream.Conn().LocalMultiaddr()
}
func (c *Conn) LocalAddr() net.Addr {
na, err := manet.ToNetAddr(c.stream.Conn().LocalMultiaddr())
if err != nil {
log.Error("failed to convert local multiaddr to net addr:", err)
return nil
}
return na
}
func (c *Conn) RemoteAddr() net.Addr {
return &NetAddr{
Relay: c.stream.Conn().RemotePeer().String(),
Remote: c.remote.ID.String(),
}
}
// ConnStat interface
var _ network.ConnStat = (*Conn)(nil)
func (c *Conn) Stat() network.ConnStats {
return c.stat
}
// tagHop tags the underlying relay connection so that it can be (somewhat) protected from the
// connection manager as it is an important connection that proxies other connections.
// This is handled here so that the user code doesnt need to bother with this and avoid
// clown shoes situations where a high value peer connection is behind a relayed connection and it is
// implicitly because the connection manager closed the underlying relay connection.
func (c *Conn) tagHop() {
c.client.mx.Lock()
defer c.client.mx.Unlock()
p := c.stream.Conn().RemotePeer()
c.client.hopCount[p]++
if c.client.hopCount[p] == 1 {
c.client.host.ConnManager().TagPeer(p, "relay-hop-stream", HopTagWeight)
}
}
// untagHop removes the relay-hop-stream tag if necessary; it is invoked when a relayed connection
// is closed.
func (c *Conn) untagHop() {
c.client.mx.Lock()
defer c.client.mx.Unlock()
p := c.stream.Conn().RemotePeer()
c.client.hopCount[p]--
if c.client.hopCount[p] == 0 {
c.client.host.ConnManager().UntagPeer(p, "relay-hop-stream")
delete(c.client.hopCount, p)
}
}
type capableConnWithStat interface {
tpt.CapableConn
network.ConnStat
}
type capableConn struct {
capableConnWithStat
}
var transportName = ma.ProtocolWithCode(ma.P_CIRCUIT).Name
func (c capableConn) ConnState() network.ConnectionState {
return network.ConnectionState{
Transport: transportName,
}
}

View File

@@ -0,0 +1,189 @@
package client
import (
"context"
"fmt"
"time"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
pbv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/util"
ma "github.com/multiformats/go-multiaddr"
)
const maxMessageSize = 4096
var DialTimeout = time.Minute
var DialRelayTimeout = 5 * time.Second
// relay protocol errors; used for signalling deduplication
type relayError struct {
err string
}
func (e relayError) Error() string {
return e.err
}
func newRelayError(t string, args ...interface{}) error {
return relayError{err: fmt.Sprintf(t, args...)}
}
func isRelayError(err error) bool {
_, ok := err.(relayError)
return ok
}
// dialer
func (c *Client) dial(ctx context.Context, a ma.Multiaddr, p peer.ID) (*Conn, error) {
// split /a/p2p-circuit/b into (/a, /p2p-circuit/b)
relayaddr, destaddr := ma.SplitFunc(a, func(c ma.Component) bool {
return c.Protocol().Code == ma.P_CIRCUIT
})
// If the address contained no /p2p-circuit part, the second part is nil.
if destaddr == nil {
return nil, fmt.Errorf("%s is not a relay address", a)
}
if relayaddr == nil {
return nil, fmt.Errorf("can't dial a p2p-circuit without specifying a relay: %s", a)
}
dinfo := peer.AddrInfo{ID: p}
// Strip the /p2p-circuit prefix from the destaddr so that we can pass the destination address
// (if present) for active relays
_, destaddr = ma.SplitFirst(destaddr)
if destaddr != nil {
dinfo.Addrs = append(dinfo.Addrs, destaddr)
}
rinfo, err := peer.AddrInfoFromP2pAddr(relayaddr)
if err != nil {
return nil, fmt.Errorf("error parsing relay multiaddr '%s': %w", relayaddr, err)
}
// deduplicate active relay dials to the same peer
retry:
c.mx.Lock()
dedup, active := c.activeDials[p]
if !active {
dedup = &completion{ch: make(chan struct{}), relay: rinfo.ID}
c.activeDials[p] = dedup
}
c.mx.Unlock()
if active {
select {
case <-dedup.ch:
if dedup.err != nil {
if dedup.relay != rinfo.ID {
// different relay, retry
goto retry
}
if !isRelayError(dedup.err) {
// not a relay protocol error, retry
goto retry
}
// don't try the same relay if it failed to connect with a protocol error
return nil, fmt.Errorf("concurrent active dial through the same relay failed with a protocol error")
}
return nil, fmt.Errorf("concurrent active dial succeeded")
case <-ctx.Done():
return nil, ctx.Err()
}
}
conn, err := c.dialPeer(ctx, *rinfo, dinfo)
c.mx.Lock()
dedup.err = err
close(dedup.ch)
delete(c.activeDials, p)
c.mx.Unlock()
return conn, err
}
func (c *Client) dialPeer(ctx context.Context, relay, dest peer.AddrInfo) (*Conn, error) {
log.Debugf("dialing peer %s through relay %s", dest.ID, relay.ID)
if len(relay.Addrs) > 0 {
c.host.Peerstore().AddAddrs(relay.ID, relay.Addrs, peerstore.TempAddrTTL)
}
dialCtx, cancel := context.WithTimeout(ctx, DialRelayTimeout)
defer cancel()
s, err := c.host.NewStream(dialCtx, relay.ID, proto.ProtoIDv2Hop)
if err != nil {
return nil, fmt.Errorf("error opening hop stream to relay: %w", err)
}
return c.connect(s, dest)
}
func (c *Client) connect(s network.Stream, dest peer.AddrInfo) (*Conn, error) {
if err := s.Scope().ReserveMemory(maxMessageSize, network.ReservationPriorityAlways); err != nil {
s.Reset()
return nil, err
}
defer s.Scope().ReleaseMemory(maxMessageSize)
rd := util.NewDelimitedReader(s, maxMessageSize)
wr := util.NewDelimitedWriter(s)
defer rd.Close()
var msg pbv2.HopMessage
msg.Type = pbv2.HopMessage_CONNECT.Enum()
msg.Peer = util.PeerInfoToPeerV2(dest)
s.SetDeadline(time.Now().Add(DialTimeout))
err := wr.WriteMsg(&msg)
if err != nil {
s.Reset()
return nil, err
}
msg.Reset()
err = rd.ReadMsg(&msg)
if err != nil {
s.Reset()
return nil, err
}
s.SetDeadline(time.Time{})
if msg.GetType() != pbv2.HopMessage_STATUS {
s.Reset()
return nil, newRelayError("unexpected relay response; not a status message (%d)", msg.GetType())
}
status := msg.GetStatus()
if status != pbv2.Status_OK {
s.Reset()
return nil, newRelayError("error opening relay circuit: %s (%d)", pbv2.Status_name[int32(status)], status)
}
// check for a limit provided by the relay; if the limit is not nil, then this is a limited
// relay connection and we mark the connection as transient.
var stat network.ConnStats
if limit := msg.GetLimit(); limit != nil {
stat.Transient = true
stat.Extra = make(map[interface{}]interface{})
stat.Extra[StatLimitDuration] = time.Duration(limit.GetDuration()) * time.Second
stat.Extra[StatLimitData] = limit.GetData()
}
return &Conn{stream: s, remote: dest, stat: stat, client: c}, nil
}

View File

@@ -0,0 +1,88 @@
package client
import (
"time"
"github.com/libp2p/go-libp2p/core/network"
pbv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/util"
)
var (
StreamTimeout = 1 * time.Minute
AcceptTimeout = 10 * time.Second
)
func (c *Client) handleStreamV2(s network.Stream) {
log.Debugf("new relay/v2 stream from: %s", s.Conn().RemotePeer())
s.SetReadDeadline(time.Now().Add(StreamTimeout))
rd := util.NewDelimitedReader(s, maxMessageSize)
defer rd.Close()
writeResponse := func(status pbv2.Status) error {
wr := util.NewDelimitedWriter(s)
var msg pbv2.StopMessage
msg.Type = pbv2.StopMessage_STATUS.Enum()
msg.Status = status.Enum()
return wr.WriteMsg(&msg)
}
handleError := func(status pbv2.Status) {
log.Debugf("protocol error: %s (%d)", pbv2.Status_name[int32(status)], status)
err := writeResponse(status)
if err != nil {
s.Reset()
log.Debugf("error writing circuit response: %s", err.Error())
} else {
s.Close()
}
}
var msg pbv2.StopMessage
err := rd.ReadMsg(&msg)
if err != nil {
handleError(pbv2.Status_MALFORMED_MESSAGE)
return
}
// reset stream deadline as message has been read
s.SetReadDeadline(time.Time{})
if msg.GetType() != pbv2.StopMessage_CONNECT {
handleError(pbv2.Status_UNEXPECTED_MESSAGE)
return
}
src, err := util.PeerToPeerInfoV2(msg.GetPeer())
if err != nil {
handleError(pbv2.Status_MALFORMED_MESSAGE)
return
}
// check for a limit provided by the relay; if the limit is not nil, then this is a limited
// relay connection and we mark the connection as transient.
var stat network.ConnStats
if limit := msg.GetLimit(); limit != nil {
stat.Transient = true
stat.Extra = make(map[interface{}]interface{})
stat.Extra[StatLimitDuration] = time.Duration(limit.GetDuration()) * time.Second
stat.Extra[StatLimitData] = limit.GetData()
}
log.Debugf("incoming relay connection from: %s", src.ID)
select {
case c.incoming <- accept{
conn: &Conn{stream: s, remote: src, stat: stat, client: c},
writeResponse: func() error {
return writeResponse(pbv2.Status_OK)
},
}:
case <-time.After(AcceptTimeout):
handleError(pbv2.Status_CONNECTION_FAILED)
}
}

View File

@@ -0,0 +1,54 @@
package client
import (
"net"
"github.com/libp2p/go-libp2p/core/transport"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)
var _ manet.Listener = (*Listener)(nil)
type Listener Client
func (c *Client) Listener() *Listener {
return (*Listener)(c)
}
func (l *Listener) Accept() (manet.Conn, error) {
for {
select {
case evt := <-l.incoming:
err := evt.writeResponse()
if err != nil {
log.Debugf("error writing relay response: %s", err.Error())
evt.conn.stream.Reset()
continue
}
log.Debugf("accepted relay connection from %s through %s", evt.conn.remote.ID, evt.conn.RemoteMultiaddr())
evt.conn.tagHop()
return evt.conn, nil
case <-l.ctx.Done():
return nil, transport.ErrListenerClosed
}
}
}
func (l *Listener) Addr() net.Addr {
return &NetAddr{
Relay: "any",
Remote: "any",
}
}
func (l *Listener) Multiaddr() ma.Multiaddr {
return circuitAddr
}
func (l *Listener) Close() error {
return (*Client)(l).Close()
}

View File

@@ -0,0 +1,159 @@
package client
import (
"context"
"fmt"
"time"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/record"
pbv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/util"
ma "github.com/multiformats/go-multiaddr"
)
var ReserveTimeout = time.Minute
// Reservation is a struct carrying information about a relay/v2 slot reservation.
type Reservation struct {
// Expiration is the expiration time of the reservation
Expiration time.Time
// Addrs contains the vouched public addresses of the reserving peer, which can be
// announced to the network
Addrs []ma.Multiaddr
// LimitDuration is the time limit for which the relay will keep a relayed connection
// open. If 0, there is no limit.
LimitDuration time.Duration
// LimitData is the number of bytes that the relay will relay in each direction before
// resetting a relayed connection.
LimitData uint64
// Voucher is a signed reservation voucher provided by the relay
Voucher *proto.ReservationVoucher
}
// ReservationError is the error returned on failure to reserve a slot in the relay
type ReservationError struct {
// Status is the status returned by the relay for rejecting the reservation
// request. It is set to pbv2.Status_CONNECTION_FAILED on other failures
Status pbv2.Status
// Reason is the reason for reservation failure
Reason string
err error
}
func (re ReservationError) Error() string {
return fmt.Sprintf("reservation error: status: %s reason: %s err: %s", pbv2.Status_name[int32(re.Status)], re.Reason, re.err)
}
func (re ReservationError) Unwrap() error {
return re.err
}
// Reserve reserves a slot in a relay and returns the reservation information.
// Clients must reserve slots in order for the relay to relay connections to them.
func Reserve(ctx context.Context, h host.Host, ai peer.AddrInfo) (*Reservation, error) {
if len(ai.Addrs) > 0 {
h.Peerstore().AddAddrs(ai.ID, ai.Addrs, peerstore.TempAddrTTL)
}
s, err := h.NewStream(ctx, ai.ID, proto.ProtoIDv2Hop)
if err != nil {
return nil, ReservationError{Status: pbv2.Status_CONNECTION_FAILED, Reason: "failed to open stream", err: err}
}
defer s.Close()
rd := util.NewDelimitedReader(s, maxMessageSize)
wr := util.NewDelimitedWriter(s)
defer rd.Close()
var msg pbv2.HopMessage
msg.Type = pbv2.HopMessage_RESERVE.Enum()
s.SetDeadline(time.Now().Add(ReserveTimeout))
if err := wr.WriteMsg(&msg); err != nil {
s.Reset()
return nil, ReservationError{Status: pbv2.Status_CONNECTION_FAILED, Reason: "error writing reservation message", err: err}
}
msg.Reset()
if err := rd.ReadMsg(&msg); err != nil {
s.Reset()
return nil, ReservationError{Status: pbv2.Status_CONNECTION_FAILED, Reason: "error reading reservation response message: %w", err: err}
}
if msg.GetType() != pbv2.HopMessage_STATUS {
return nil, ReservationError{
Status: pbv2.Status_MALFORMED_MESSAGE,
Reason: fmt.Sprintf("unexpected relay response: not a status message (%d)", msg.GetType()),
err: err}
}
if status := msg.GetStatus(); status != pbv2.Status_OK {
return nil, ReservationError{Status: msg.GetStatus(), Reason: "reservation failed"}
}
rsvp := msg.GetReservation()
if rsvp == nil {
return nil, ReservationError{Status: pbv2.Status_MALFORMED_MESSAGE, Reason: "missing reservation info"}
}
result := &Reservation{}
result.Expiration = time.Unix(int64(rsvp.GetExpire()), 0)
if result.Expiration.Before(time.Now()) {
return nil, ReservationError{
Status: pbv2.Status_MALFORMED_MESSAGE,
Reason: fmt.Sprintf("received reservation with expiration date in the past: %s", result.Expiration),
}
}
addrs := rsvp.GetAddrs()
result.Addrs = make([]ma.Multiaddr, 0, len(addrs))
for _, ab := range addrs {
a, err := ma.NewMultiaddrBytes(ab)
if err != nil {
log.Warnf("ignoring unparsable relay address: %s", err)
continue
}
result.Addrs = append(result.Addrs, a)
}
voucherBytes := rsvp.GetVoucher()
if voucherBytes != nil {
_, rec, err := record.ConsumeEnvelope(voucherBytes, proto.RecordDomain)
if err != nil {
return nil, ReservationError{
Status: pbv2.Status_MALFORMED_MESSAGE,
Reason: fmt.Sprintf("error consuming voucher envelope: %s", err),
err: err,
}
}
voucher, ok := rec.(*proto.ReservationVoucher)
if !ok {
return nil, ReservationError{
Status: pbv2.Status_MALFORMED_MESSAGE,
Reason: fmt.Sprintf("unexpected voucher record type: %+T", rec),
}
}
result.Voucher = voucher
}
limit := msg.GetLimit()
if limit != nil {
result.LimitDuration = time.Duration(limit.GetDuration()) * time.Second
result.LimitData = limit.GetData()
}
return result, nil
}

View File

@@ -0,0 +1,100 @@
package client
import (
"context"
"fmt"
"io"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/transport"
ma "github.com/multiformats/go-multiaddr"
)
var circuitProtocol = ma.ProtocolWithCode(ma.P_CIRCUIT)
var circuitAddr = ma.Cast(circuitProtocol.VCode)
// AddTransport constructs a new p2p-circuit/v2 client and adds it as a transport to the
// host network
func AddTransport(h host.Host, upgrader transport.Upgrader) error {
n, ok := h.Network().(transport.TransportNetwork)
if !ok {
return fmt.Errorf("%v is not a transport network", h.Network())
}
c, err := New(h, upgrader)
if err != nil {
return fmt.Errorf("error constructing circuit client: %w", err)
}
err = n.AddTransport(c)
if err != nil {
return fmt.Errorf("error adding circuit transport: %w", err)
}
err = n.Listen(circuitAddr)
if err != nil {
return fmt.Errorf("error listening to circuit addr: %w", err)
}
c.Start()
return nil
}
// Transport interface
var _ transport.Transport = (*Client)(nil)
var _ io.Closer = (*Client)(nil)
func (c *Client) Dial(ctx context.Context, a ma.Multiaddr, p peer.ID) (transport.CapableConn, error) {
connScope, err := c.host.Network().ResourceManager().OpenConnection(network.DirOutbound, false, a)
if err != nil {
return nil, err
}
conn, err := c.dialAndUpgrade(ctx, a, p, connScope)
if err != nil {
connScope.Done()
return nil, err
}
return conn, nil
}
func (c *Client) dialAndUpgrade(ctx context.Context, a ma.Multiaddr, p peer.ID, connScope network.ConnManagementScope) (transport.CapableConn, error) {
if err := connScope.SetPeer(p); err != nil {
return nil, err
}
conn, err := c.dial(ctx, a, p)
if err != nil {
return nil, err
}
conn.tagHop()
cc, err := c.upgrader.Upgrade(ctx, c, conn, network.DirOutbound, p, connScope)
if err != nil {
return nil, err
}
return capableConn{cc.(capableConnWithStat)}, nil
}
func (c *Client) CanDial(addr ma.Multiaddr) bool {
_, err := addr.ValueForProtocol(ma.P_CIRCUIT)
return err == nil
}
func (c *Client) Listen(addr ma.Multiaddr) (transport.Listener, error) {
// TODO connect to the relay and reserve slot if specified
if _, err := addr.ValueForProtocol(ma.P_CIRCUIT); err != nil {
return nil, err
}
return c.upgrader.UpgradeListener(c, c.Listener()), nil
}
func (c *Client) Protocols() []int {
return []int{ma.P_CIRCUIT}
}
func (c *Client) Proxy() bool {
return true
}

View File

@@ -0,0 +1,727 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.30.0
// protoc v3.21.12
// source: pb/circuit.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Status int32
const (
// zero value field required for proto3 compatibility
Status_UNUSED Status = 0
Status_OK Status = 100
Status_RESERVATION_REFUSED Status = 200
Status_RESOURCE_LIMIT_EXCEEDED Status = 201
Status_PERMISSION_DENIED Status = 202
Status_CONNECTION_FAILED Status = 203
Status_NO_RESERVATION Status = 204
Status_MALFORMED_MESSAGE Status = 400
Status_UNEXPECTED_MESSAGE Status = 401
)
// Enum value maps for Status.
var (
Status_name = map[int32]string{
0: "UNUSED",
100: "OK",
200: "RESERVATION_REFUSED",
201: "RESOURCE_LIMIT_EXCEEDED",
202: "PERMISSION_DENIED",
203: "CONNECTION_FAILED",
204: "NO_RESERVATION",
400: "MALFORMED_MESSAGE",
401: "UNEXPECTED_MESSAGE",
}
Status_value = map[string]int32{
"UNUSED": 0,
"OK": 100,
"RESERVATION_REFUSED": 200,
"RESOURCE_LIMIT_EXCEEDED": 201,
"PERMISSION_DENIED": 202,
"CONNECTION_FAILED": 203,
"NO_RESERVATION": 204,
"MALFORMED_MESSAGE": 400,
"UNEXPECTED_MESSAGE": 401,
}
)
func (x Status) Enum() *Status {
p := new(Status)
*p = x
return p
}
func (x Status) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Status) Descriptor() protoreflect.EnumDescriptor {
return file_pb_circuit_proto_enumTypes[0].Descriptor()
}
func (Status) Type() protoreflect.EnumType {
return &file_pb_circuit_proto_enumTypes[0]
}
func (x Status) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Status.Descriptor instead.
func (Status) EnumDescriptor() ([]byte, []int) {
return file_pb_circuit_proto_rawDescGZIP(), []int{0}
}
type HopMessage_Type int32
const (
HopMessage_RESERVE HopMessage_Type = 0
HopMessage_CONNECT HopMessage_Type = 1
HopMessage_STATUS HopMessage_Type = 2
)
// Enum value maps for HopMessage_Type.
var (
HopMessage_Type_name = map[int32]string{
0: "RESERVE",
1: "CONNECT",
2: "STATUS",
}
HopMessage_Type_value = map[string]int32{
"RESERVE": 0,
"CONNECT": 1,
"STATUS": 2,
}
)
func (x HopMessage_Type) Enum() *HopMessage_Type {
p := new(HopMessage_Type)
*p = x
return p
}
func (x HopMessage_Type) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (HopMessage_Type) Descriptor() protoreflect.EnumDescriptor {
return file_pb_circuit_proto_enumTypes[1].Descriptor()
}
func (HopMessage_Type) Type() protoreflect.EnumType {
return &file_pb_circuit_proto_enumTypes[1]
}
func (x HopMessage_Type) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use HopMessage_Type.Descriptor instead.
func (HopMessage_Type) EnumDescriptor() ([]byte, []int) {
return file_pb_circuit_proto_rawDescGZIP(), []int{0, 0}
}
type StopMessage_Type int32
const (
StopMessage_CONNECT StopMessage_Type = 0
StopMessage_STATUS StopMessage_Type = 1
)
// Enum value maps for StopMessage_Type.
var (
StopMessage_Type_name = map[int32]string{
0: "CONNECT",
1: "STATUS",
}
StopMessage_Type_value = map[string]int32{
"CONNECT": 0,
"STATUS": 1,
}
)
func (x StopMessage_Type) Enum() *StopMessage_Type {
p := new(StopMessage_Type)
*p = x
return p
}
func (x StopMessage_Type) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (StopMessage_Type) Descriptor() protoreflect.EnumDescriptor {
return file_pb_circuit_proto_enumTypes[2].Descriptor()
}
func (StopMessage_Type) Type() protoreflect.EnumType {
return &file_pb_circuit_proto_enumTypes[2]
}
func (x StopMessage_Type) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use StopMessage_Type.Descriptor instead.
func (StopMessage_Type) EnumDescriptor() ([]byte, []int) {
return file_pb_circuit_proto_rawDescGZIP(), []int{1, 0}
}
type HopMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// This field is marked optional for backwards compatibility with proto2.
// Users should make sure to always set this.
Type *HopMessage_Type `protobuf:"varint,1,opt,name=type,proto3,enum=circuit.pb.HopMessage_Type,oneof" json:"type,omitempty"`
Peer *Peer `protobuf:"bytes,2,opt,name=peer,proto3,oneof" json:"peer,omitempty"`
Reservation *Reservation `protobuf:"bytes,3,opt,name=reservation,proto3,oneof" json:"reservation,omitempty"`
Limit *Limit `protobuf:"bytes,4,opt,name=limit,proto3,oneof" json:"limit,omitempty"`
Status *Status `protobuf:"varint,5,opt,name=status,proto3,enum=circuit.pb.Status,oneof" json:"status,omitempty"`
}
func (x *HopMessage) Reset() {
*x = HopMessage{}
if protoimpl.UnsafeEnabled {
mi := &file_pb_circuit_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HopMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HopMessage) ProtoMessage() {}
func (x *HopMessage) ProtoReflect() protoreflect.Message {
mi := &file_pb_circuit_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HopMessage.ProtoReflect.Descriptor instead.
func (*HopMessage) Descriptor() ([]byte, []int) {
return file_pb_circuit_proto_rawDescGZIP(), []int{0}
}
func (x *HopMessage) GetType() HopMessage_Type {
if x != nil && x.Type != nil {
return *x.Type
}
return HopMessage_RESERVE
}
func (x *HopMessage) GetPeer() *Peer {
if x != nil {
return x.Peer
}
return nil
}
func (x *HopMessage) GetReservation() *Reservation {
if x != nil {
return x.Reservation
}
return nil
}
func (x *HopMessage) GetLimit() *Limit {
if x != nil {
return x.Limit
}
return nil
}
func (x *HopMessage) GetStatus() Status {
if x != nil && x.Status != nil {
return *x.Status
}
return Status_UNUSED
}
type StopMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// This field is marked optional for backwards compatibility with proto2.
// Users should make sure to always set this.
Type *StopMessage_Type `protobuf:"varint,1,opt,name=type,proto3,enum=circuit.pb.StopMessage_Type,oneof" json:"type,omitempty"`
Peer *Peer `protobuf:"bytes,2,opt,name=peer,proto3,oneof" json:"peer,omitempty"`
Limit *Limit `protobuf:"bytes,3,opt,name=limit,proto3,oneof" json:"limit,omitempty"`
Status *Status `protobuf:"varint,4,opt,name=status,proto3,enum=circuit.pb.Status,oneof" json:"status,omitempty"`
}
func (x *StopMessage) Reset() {
*x = StopMessage{}
if protoimpl.UnsafeEnabled {
mi := &file_pb_circuit_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StopMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StopMessage) ProtoMessage() {}
func (x *StopMessage) ProtoReflect() protoreflect.Message {
mi := &file_pb_circuit_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StopMessage.ProtoReflect.Descriptor instead.
func (*StopMessage) Descriptor() ([]byte, []int) {
return file_pb_circuit_proto_rawDescGZIP(), []int{1}
}
func (x *StopMessage) GetType() StopMessage_Type {
if x != nil && x.Type != nil {
return *x.Type
}
return StopMessage_CONNECT
}
func (x *StopMessage) GetPeer() *Peer {
if x != nil {
return x.Peer
}
return nil
}
func (x *StopMessage) GetLimit() *Limit {
if x != nil {
return x.Limit
}
return nil
}
func (x *StopMessage) GetStatus() Status {
if x != nil && x.Status != nil {
return *x.Status
}
return Status_UNUSED
}
type Peer struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// This field is marked optional for backwards compatibility with proto2.
// Users should make sure to always set this.
Id []byte `protobuf:"bytes,1,opt,name=id,proto3,oneof" json:"id,omitempty"`
Addrs [][]byte `protobuf:"bytes,2,rep,name=addrs,proto3" json:"addrs,omitempty"`
}
func (x *Peer) Reset() {
*x = Peer{}
if protoimpl.UnsafeEnabled {
mi := &file_pb_circuit_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Peer) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Peer) ProtoMessage() {}
func (x *Peer) ProtoReflect() protoreflect.Message {
mi := &file_pb_circuit_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Peer.ProtoReflect.Descriptor instead.
func (*Peer) Descriptor() ([]byte, []int) {
return file_pb_circuit_proto_rawDescGZIP(), []int{2}
}
func (x *Peer) GetId() []byte {
if x != nil {
return x.Id
}
return nil
}
func (x *Peer) GetAddrs() [][]byte {
if x != nil {
return x.Addrs
}
return nil
}
type Reservation struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// This field is marked optional for backwards compatibility with proto2.
// Users should make sure to always set this.
Expire *uint64 `protobuf:"varint,1,opt,name=expire,proto3,oneof" json:"expire,omitempty"` // Unix expiration time (UTC)
Addrs [][]byte `protobuf:"bytes,2,rep,name=addrs,proto3" json:"addrs,omitempty"` // relay addrs for reserving peer
Voucher []byte `protobuf:"bytes,3,opt,name=voucher,proto3,oneof" json:"voucher,omitempty"` // reservation voucher
}
func (x *Reservation) Reset() {
*x = Reservation{}
if protoimpl.UnsafeEnabled {
mi := &file_pb_circuit_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Reservation) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Reservation) ProtoMessage() {}
func (x *Reservation) ProtoReflect() protoreflect.Message {
mi := &file_pb_circuit_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Reservation.ProtoReflect.Descriptor instead.
func (*Reservation) Descriptor() ([]byte, []int) {
return file_pb_circuit_proto_rawDescGZIP(), []int{3}
}
func (x *Reservation) GetExpire() uint64 {
if x != nil && x.Expire != nil {
return *x.Expire
}
return 0
}
func (x *Reservation) GetAddrs() [][]byte {
if x != nil {
return x.Addrs
}
return nil
}
func (x *Reservation) GetVoucher() []byte {
if x != nil {
return x.Voucher
}
return nil
}
type Limit struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Duration *uint32 `protobuf:"varint,1,opt,name=duration,proto3,oneof" json:"duration,omitempty"` // seconds
Data *uint64 `protobuf:"varint,2,opt,name=data,proto3,oneof" json:"data,omitempty"` // bytes
}
func (x *Limit) Reset() {
*x = Limit{}
if protoimpl.UnsafeEnabled {
mi := &file_pb_circuit_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Limit) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Limit) ProtoMessage() {}
func (x *Limit) ProtoReflect() protoreflect.Message {
mi := &file_pb_circuit_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Limit.ProtoReflect.Descriptor instead.
func (*Limit) Descriptor() ([]byte, []int) {
return file_pb_circuit_proto_rawDescGZIP(), []int{4}
}
func (x *Limit) GetDuration() uint32 {
if x != nil && x.Duration != nil {
return *x.Duration
}
return 0
}
func (x *Limit) GetData() uint64 {
if x != nil && x.Data != nil {
return *x.Data
}
return 0
}
var File_pb_circuit_proto protoreflect.FileDescriptor
var file_pb_circuit_proto_rawDesc = []byte{
0x0a, 0x10, 0x70, 0x62, 0x2f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x0a, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x62, 0x22, 0xf1,
0x02, 0x0a, 0x0a, 0x48, 0x6f, 0x70, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x34, 0x0a,
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x63, 0x69,
0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x6f, 0x70, 0x4d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x10, 0x2e, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x62, 0x2e, 0x50,
0x65, 0x65, 0x72, 0x48, 0x01, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x3e,
0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x62,
0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x02, 0x52, 0x0b,
0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2c,
0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e,
0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x6d, 0x69, 0x74,
0x48, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x63,
0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x48, 0x04, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x22, 0x2c, 0x0a,
0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45,
0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12,
0x0a, 0x0a, 0x06, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x10, 0x02, 0x42, 0x07, 0x0a, 0x05, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x42, 0x0e, 0x0a,
0x0c, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x08, 0x0a,
0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x22, 0x96, 0x02, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x70, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x1c, 0x2e, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74,
0x6f, 0x70, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00,
0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x04, 0x70, 0x65, 0x65,
0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69,
0x74, 0x2e, 0x70, 0x62, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x48, 0x01, 0x52, 0x04, 0x70, 0x65, 0x65,
0x72, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x62,
0x2e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x48, 0x02, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88,
0x01, 0x01, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x12, 0x2e, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x62, 0x2e,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x88, 0x01, 0x01, 0x22, 0x1f, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x43,
0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x41, 0x54,
0x55, 0x53, 0x10, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x07, 0x0a,
0x05, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74,
0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x38, 0x0a, 0x04, 0x50,
0x65, 0x65, 0x72, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48,
0x00, 0x52, 0x02, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x72,
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x61, 0x64, 0x64, 0x72, 0x73, 0x42, 0x05,
0x0a, 0x03, 0x5f, 0x69, 0x64, 0x22, 0x76, 0x0a, 0x0b, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x88, 0x01,
0x01, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c,
0x52, 0x05, 0x61, 0x64, 0x64, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x07, 0x76, 0x6f, 0x75, 0x63, 0x68,
0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x76, 0x6f, 0x75, 0x63,
0x68, 0x65, 0x72, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72,
0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x22, 0x57, 0x0a,
0x05, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18,
0x02, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01,
0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x07, 0x0a,
0x05, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x2a, 0xca, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x10, 0x00, 0x12, 0x06, 0x0a,
0x02, 0x4f, 0x4b, 0x10, 0x64, 0x12, 0x18, 0x0a, 0x13, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x41,
0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x46, 0x55, 0x53, 0x45, 0x44, 0x10, 0xc8, 0x01, 0x12,
0x1c, 0x0a, 0x17, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x4c, 0x49, 0x4d, 0x49,
0x54, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0xc9, 0x01, 0x12, 0x16, 0x0a,
0x11, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4e, 0x49,
0x45, 0x44, 0x10, 0xca, 0x01, 0x12, 0x16, 0x0a, 0x11, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54,
0x49, 0x4f, 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0xcb, 0x01, 0x12, 0x13, 0x0a,
0x0e, 0x4e, 0x4f, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10,
0xcc, 0x01, 0x12, 0x16, 0x0a, 0x11, 0x4d, 0x41, 0x4c, 0x46, 0x4f, 0x52, 0x4d, 0x45, 0x44, 0x5f,
0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x90, 0x03, 0x12, 0x17, 0x0a, 0x12, 0x55, 0x4e,
0x45, 0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45,
0x10, 0x91, 0x03, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_pb_circuit_proto_rawDescOnce sync.Once
file_pb_circuit_proto_rawDescData = file_pb_circuit_proto_rawDesc
)
func file_pb_circuit_proto_rawDescGZIP() []byte {
file_pb_circuit_proto_rawDescOnce.Do(func() {
file_pb_circuit_proto_rawDescData = protoimpl.X.CompressGZIP(file_pb_circuit_proto_rawDescData)
})
return file_pb_circuit_proto_rawDescData
}
var file_pb_circuit_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_pb_circuit_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_pb_circuit_proto_goTypes = []interface{}{
(Status)(0), // 0: circuit.pb.Status
(HopMessage_Type)(0), // 1: circuit.pb.HopMessage.Type
(StopMessage_Type)(0), // 2: circuit.pb.StopMessage.Type
(*HopMessage)(nil), // 3: circuit.pb.HopMessage
(*StopMessage)(nil), // 4: circuit.pb.StopMessage
(*Peer)(nil), // 5: circuit.pb.Peer
(*Reservation)(nil), // 6: circuit.pb.Reservation
(*Limit)(nil), // 7: circuit.pb.Limit
}
var file_pb_circuit_proto_depIdxs = []int32{
1, // 0: circuit.pb.HopMessage.type:type_name -> circuit.pb.HopMessage.Type
5, // 1: circuit.pb.HopMessage.peer:type_name -> circuit.pb.Peer
6, // 2: circuit.pb.HopMessage.reservation:type_name -> circuit.pb.Reservation
7, // 3: circuit.pb.HopMessage.limit:type_name -> circuit.pb.Limit
0, // 4: circuit.pb.HopMessage.status:type_name -> circuit.pb.Status
2, // 5: circuit.pb.StopMessage.type:type_name -> circuit.pb.StopMessage.Type
5, // 6: circuit.pb.StopMessage.peer:type_name -> circuit.pb.Peer
7, // 7: circuit.pb.StopMessage.limit:type_name -> circuit.pb.Limit
0, // 8: circuit.pb.StopMessage.status:type_name -> circuit.pb.Status
9, // [9:9] is the sub-list for method output_type
9, // [9:9] is the sub-list for method input_type
9, // [9:9] is the sub-list for extension type_name
9, // [9:9] is the sub-list for extension extendee
0, // [0:9] is the sub-list for field type_name
}
func init() { file_pb_circuit_proto_init() }
func file_pb_circuit_proto_init() {
if File_pb_circuit_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_pb_circuit_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HopMessage); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pb_circuit_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StopMessage); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pb_circuit_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Peer); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pb_circuit_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Reservation); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pb_circuit_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Limit); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_pb_circuit_proto_msgTypes[0].OneofWrappers = []interface{}{}
file_pb_circuit_proto_msgTypes[1].OneofWrappers = []interface{}{}
file_pb_circuit_proto_msgTypes[2].OneofWrappers = []interface{}{}
file_pb_circuit_proto_msgTypes[3].OneofWrappers = []interface{}{}
file_pb_circuit_proto_msgTypes[4].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pb_circuit_proto_rawDesc,
NumEnums: 3,
NumMessages: 5,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_pb_circuit_proto_goTypes,
DependencyIndexes: file_pb_circuit_proto_depIdxs,
EnumInfos: file_pb_circuit_proto_enumTypes,
MessageInfos: file_pb_circuit_proto_msgTypes,
}.Build()
File_pb_circuit_proto = out.File
file_pb_circuit_proto_rawDesc = nil
file_pb_circuit_proto_goTypes = nil
file_pb_circuit_proto_depIdxs = nil
}

View File

@@ -0,0 +1,70 @@
syntax = "proto3";
package circuit.pb;
message HopMessage {
enum Type {
RESERVE = 0;
CONNECT = 1;
STATUS = 2;
}
// This field is marked optional for backwards compatibility with proto2.
// Users should make sure to always set this.
optional Type type = 1;
optional Peer peer = 2;
optional Reservation reservation = 3;
optional Limit limit = 4;
optional Status status = 5;
}
message StopMessage {
enum Type {
CONNECT = 0;
STATUS = 1;
}
// This field is marked optional for backwards compatibility with proto2.
// Users should make sure to always set this.
optional Type type = 1;
optional Peer peer = 2;
optional Limit limit = 3;
optional Status status = 4;
}
message Peer {
// This field is marked optional for backwards compatibility with proto2.
// Users should make sure to always set this.
optional bytes id = 1;
repeated bytes addrs = 2;
}
message Reservation {
// This field is marked optional for backwards compatibility with proto2.
// Users should make sure to always set this.
optional uint64 expire = 1; // Unix expiration time (UTC)
repeated bytes addrs = 2; // relay addrs for reserving peer
optional bytes voucher = 3; // reservation voucher
}
message Limit {
optional uint32 duration = 1; // seconds
optional uint64 data = 2; // bytes
}
enum Status {
// zero value field required for proto3 compatibility
UNUSED = 0;
OK = 100;
RESERVATION_REFUSED = 200;
RESOURCE_LIMIT_EXCEEDED = 201;
PERMISSION_DENIED = 202;
CONNECTION_FAILED = 203;
NO_RESERVATION = 204;
MALFORMED_MESSAGE = 400;
UNEXPECTED_MESSAGE = 401;
}

View File

@@ -0,0 +1,167 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.30.0
// protoc v3.21.12
// source: pb/voucher.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ReservationVoucher struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// These fields are marked optional for backwards compatibility with proto2.
// Users should make sure to always set these.
Relay []byte `protobuf:"bytes,1,opt,name=relay,proto3,oneof" json:"relay,omitempty"`
Peer []byte `protobuf:"bytes,2,opt,name=peer,proto3,oneof" json:"peer,omitempty"`
Expiration *uint64 `protobuf:"varint,3,opt,name=expiration,proto3,oneof" json:"expiration,omitempty"`
}
func (x *ReservationVoucher) Reset() {
*x = ReservationVoucher{}
if protoimpl.UnsafeEnabled {
mi := &file_pb_voucher_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ReservationVoucher) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReservationVoucher) ProtoMessage() {}
func (x *ReservationVoucher) ProtoReflect() protoreflect.Message {
mi := &file_pb_voucher_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ReservationVoucher.ProtoReflect.Descriptor instead.
func (*ReservationVoucher) Descriptor() ([]byte, []int) {
return file_pb_voucher_proto_rawDescGZIP(), []int{0}
}
func (x *ReservationVoucher) GetRelay() []byte {
if x != nil {
return x.Relay
}
return nil
}
func (x *ReservationVoucher) GetPeer() []byte {
if x != nil {
return x.Peer
}
return nil
}
func (x *ReservationVoucher) GetExpiration() uint64 {
if x != nil && x.Expiration != nil {
return *x.Expiration
}
return 0
}
var File_pb_voucher_proto protoreflect.FileDescriptor
var file_pb_voucher_proto_rawDesc = []byte{
0x0a, 0x10, 0x70, 0x62, 0x2f, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x0a, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x2e, 0x70, 0x62, 0x22, 0x8f,
0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x6f,
0x75, 0x63, 0x68, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x05, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x05, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x88, 0x01, 0x01,
0x12, 0x17, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01,
0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x23, 0x0a, 0x0a, 0x65, 0x78, 0x70,
0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x48, 0x02, 0x52,
0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x08,
0x0a, 0x06, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x70, 0x65, 0x65,
0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_pb_voucher_proto_rawDescOnce sync.Once
file_pb_voucher_proto_rawDescData = file_pb_voucher_proto_rawDesc
)
func file_pb_voucher_proto_rawDescGZIP() []byte {
file_pb_voucher_proto_rawDescOnce.Do(func() {
file_pb_voucher_proto_rawDescData = protoimpl.X.CompressGZIP(file_pb_voucher_proto_rawDescData)
})
return file_pb_voucher_proto_rawDescData
}
var file_pb_voucher_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_pb_voucher_proto_goTypes = []interface{}{
(*ReservationVoucher)(nil), // 0: circuit.pb.ReservationVoucher
}
var file_pb_voucher_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_pb_voucher_proto_init() }
func file_pb_voucher_proto_init() {
if File_pb_voucher_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_pb_voucher_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ReservationVoucher); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_pb_voucher_proto_msgTypes[0].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pb_voucher_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_pb_voucher_proto_goTypes,
DependencyIndexes: file_pb_voucher_proto_depIdxs,
MessageInfos: file_pb_voucher_proto_msgTypes,
}.Build()
File_pb_voucher_proto = out.File
file_pb_voucher_proto_rawDesc = nil
file_pb_voucher_proto_goTypes = nil
file_pb_voucher_proto_depIdxs = nil
}

View File

@@ -0,0 +1,11 @@
syntax = "proto3";
package circuit.pb;
message ReservationVoucher {
// These fields are marked optional for backwards compatibility with proto2.
// Users should make sure to always set these.
optional bytes relay = 1;
optional bytes peer = 2;
optional uint64 expiration = 3;
}

View File

@@ -0,0 +1,6 @@
package proto
const (
ProtoIDv2Hop = "/libp2p/circuit/relay/0.2.0/hop"
ProtoIDv2Stop = "/libp2p/circuit/relay/0.2.0/stop"
)

View File

@@ -0,0 +1,69 @@
package proto
import (
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/record"
pbv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb"
"google.golang.org/protobuf/proto"
)
const RecordDomain = "libp2p-relay-rsvp"
// TODO: register in multicodec table in https://github.com/multiformats/multicodec
var RecordCodec = []byte{0x03, 0x02}
func init() {
record.RegisterType(&ReservationVoucher{})
}
type ReservationVoucher struct {
// Relay is the ID of the peer providing relay service
Relay peer.ID
// Peer is the ID of the peer receiving relay service through Relay
Peer peer.ID
// Expiration is the expiration time of the reservation
Expiration time.Time
}
var _ record.Record = (*ReservationVoucher)(nil)
func (rv *ReservationVoucher) Domain() string {
return RecordDomain
}
func (rv *ReservationVoucher) Codec() []byte {
return RecordCodec
}
func (rv *ReservationVoucher) MarshalRecord() ([]byte, error) {
expiration := uint64(rv.Expiration.Unix())
return proto.Marshal(&pbv2.ReservationVoucher{
Relay: []byte(rv.Relay),
Peer: []byte(rv.Peer),
Expiration: &expiration,
})
}
func (rv *ReservationVoucher) UnmarshalRecord(blob []byte) error {
pbrv := pbv2.ReservationVoucher{}
err := proto.Unmarshal(blob, &pbrv)
if err != nil {
return err
}
rv.Relay, err = peer.IDFromBytes(pbrv.GetRelay())
if err != nil {
return err
}
rv.Peer, err = peer.IDFromBytes(pbrv.GetPeer())
if err != nil {
return err
}
rv.Expiration = time.Unix(int64(pbrv.GetExpiration()), 0)
return nil
}

View File

@@ -0,0 +1,17 @@
package relay
import (
"github.com/libp2p/go-libp2p/core/peer"
ma "github.com/multiformats/go-multiaddr"
)
// ACLFilter is an Access Control mechanism for relayed connect.
type ACLFilter interface {
// AllowReserve returns true if a reservation from a peer with the given peer ID and multiaddr
// is allowed.
AllowReserve(p peer.ID, a ma.Multiaddr) bool
// AllowConnect returns true if a source peer, with a given multiaddr is allowed to connect
// to a destination peer.
AllowConnect(src peer.ID, srcAddr ma.Multiaddr, dest peer.ID) bool
}

View File

@@ -0,0 +1,128 @@
package relay
import (
"errors"
"sync"
"time"
asnutil "github.com/libp2p/go-libp2p-asn-util"
"github.com/libp2p/go-libp2p/core/peer"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)
var validity = 30 * time.Minute
var (
errTooManyReservations = errors.New("too many reservations")
errTooManyReservationsForPeer = errors.New("too many reservations for peer")
errTooManyReservationsForIP = errors.New("too many peers for IP address")
errTooManyReservationsForASN = errors.New("too many peers for ASN")
)
// constraints implements various reservation constraints
type constraints struct {
rc *Resources
mutex sync.Mutex
total []time.Time
peers map[peer.ID][]time.Time
ips map[string][]time.Time
asns map[string][]time.Time
}
// newConstraints creates a new constraints object.
// The methods are *not* thread-safe; an external lock must be held if synchronization
// is required.
func newConstraints(rc *Resources) *constraints {
return &constraints{
rc: rc,
peers: make(map[peer.ID][]time.Time),
ips: make(map[string][]time.Time),
asns: make(map[string][]time.Time),
}
}
// AddReservation adds a reservation for a given peer with a given multiaddr.
// If adding this reservation violates IP constraints, an error is returned.
func (c *constraints) AddReservation(p peer.ID, a ma.Multiaddr) error {
c.mutex.Lock()
defer c.mutex.Unlock()
now := time.Now()
c.cleanup(now)
if len(c.total) >= c.rc.MaxReservations {
return errTooManyReservations
}
ip, err := manet.ToIP(a)
if err != nil {
return errors.New("no IP address associated with peer")
}
peerReservations := c.peers[p]
if len(peerReservations) >= c.rc.MaxReservationsPerPeer {
return errTooManyReservationsForPeer
}
ipReservations := c.ips[ip.String()]
if len(ipReservations) >= c.rc.MaxReservationsPerIP {
return errTooManyReservationsForIP
}
var asnReservations []time.Time
var asn string
// Only public addresses have an ASN. Skip checking ASN for private addresses as
// initialising the ASN store is a costly operation. Skipping this check reduces a lot of
// flakiness in tests
if ip.To4() == nil && manet.IsPublicAddr(a) {
asn, _ = asnutil.Store.AsnForIPv6(ip)
if asn != "" {
asnReservations = c.asns[asn]
if len(asnReservations) >= c.rc.MaxReservationsPerASN {
return errTooManyReservationsForASN
}
}
}
expiry := now.Add(validity)
c.total = append(c.total, expiry)
peerReservations = append(peerReservations, expiry)
c.peers[p] = peerReservations
ipReservations = append(ipReservations, expiry)
c.ips[ip.String()] = ipReservations
if asn != "" {
asnReservations = append(asnReservations, expiry)
c.asns[asn] = asnReservations
}
return nil
}
func (c *constraints) cleanupList(l []time.Time, now time.Time) []time.Time {
var index int
for i, t := range l {
if t.After(now) {
break
}
index = i + 1
}
return l[index:]
}
func (c *constraints) cleanup(now time.Time) {
c.total = c.cleanupList(c.total, now)
for k, peerReservations := range c.peers {
c.peers[k] = c.cleanupList(peerReservations, now)
}
for k, ipReservations := range c.ips {
c.ips[k] = c.cleanupList(ipReservations, now)
}
for k, asnReservations := range c.asns {
c.asns[k] = c.cleanupList(asnReservations, now)
}
}

View File

@@ -0,0 +1,268 @@
package relay
import (
"time"
"github.com/libp2p/go-libp2p/p2p/metricshelper"
pbv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb"
"github.com/prometheus/client_golang/prometheus"
)
const metricNamespace = "libp2p_relaysvc"
var (
status = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: metricNamespace,
Name: "status",
Help: "Relay Status",
},
)
reservationsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "reservations_total",
Help: "Relay Reservation Request",
},
[]string{"type"},
)
reservationRequestResponseStatusTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "reservation_request_response_status_total",
Help: "Relay Reservation Request Response Status",
},
[]string{"status"},
)
reservationRejectionsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "reservation_rejections_total",
Help: "Relay Reservation Rejected Reason",
},
[]string{"reason"},
)
connectionsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "connections_total",
Help: "Relay Connection Total",
},
[]string{"type"},
)
connectionRequestResponseStatusTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "connection_request_response_status_total",
Help: "Relay Connection Request Status",
},
[]string{"status"},
)
connectionRejectionsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "connection_rejections_total",
Help: "Relay Connection Rejected Reason",
},
[]string{"reason"},
)
connectionDurationSeconds = prometheus.NewHistogram(
prometheus.HistogramOpts{
Namespace: metricNamespace,
Name: "connection_duration_seconds",
Help: "Relay Connection Duration",
},
)
dataTransferredBytesTotal = prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "data_transferred_bytes_total",
Help: "Bytes Transferred Total",
},
)
collectors = []prometheus.Collector{
status,
reservationsTotal,
reservationRequestResponseStatusTotal,
reservationRejectionsTotal,
connectionsTotal,
connectionRequestResponseStatusTotal,
connectionRejectionsTotal,
connectionDurationSeconds,
dataTransferredBytesTotal,
}
)
const (
requestStatusOK = "ok"
requestStatusRejected = "rejected"
requestStatusError = "error"
)
// MetricsTracer is the interface for tracking metrics for relay service
type MetricsTracer interface {
// RelayStatus tracks whether the service is currently active
RelayStatus(enabled bool)
// ConnectionOpened tracks metrics on opening a relay connection
ConnectionOpened()
// ConnectionClosed tracks metrics on closing a relay connection
ConnectionClosed(d time.Duration)
// ConnectionRequestHandled tracks metrics on handling a relay connection request
ConnectionRequestHandled(status pbv2.Status)
// ReservationAllowed tracks metrics on opening or renewing a relay reservation
ReservationAllowed(isRenewal bool)
// ReservationRequestClosed tracks metrics on closing a relay reservation
ReservationClosed(cnt int)
// ReservationRequestHandled tracks metrics on handling a relay reservation request
ReservationRequestHandled(status pbv2.Status)
// BytesTransferred tracks the total bytes transferred by the relay service
BytesTransferred(cnt int)
}
type metricsTracer struct{}
var _ MetricsTracer = &metricsTracer{}
type metricsTracerSetting struct {
reg prometheus.Registerer
}
type MetricsTracerOption func(*metricsTracerSetting)
func WithRegisterer(reg prometheus.Registerer) MetricsTracerOption {
return func(s *metricsTracerSetting) {
if reg != nil {
s.reg = reg
}
}
}
func NewMetricsTracer(opts ...MetricsTracerOption) MetricsTracer {
setting := &metricsTracerSetting{reg: prometheus.DefaultRegisterer}
for _, opt := range opts {
opt(setting)
}
metricshelper.RegisterCollectors(setting.reg, collectors...)
return &metricsTracer{}
}
func (mt *metricsTracer) RelayStatus(enabled bool) {
if enabled {
status.Set(1)
} else {
status.Set(0)
}
}
func (mt *metricsTracer) ConnectionOpened() {
tags := metricshelper.GetStringSlice()
defer metricshelper.PutStringSlice(tags)
*tags = append(*tags, "opened")
connectionsTotal.WithLabelValues(*tags...).Add(1)
}
func (mt *metricsTracer) ConnectionClosed(d time.Duration) {
tags := metricshelper.GetStringSlice()
defer metricshelper.PutStringSlice(tags)
*tags = append(*tags, "closed")
connectionsTotal.WithLabelValues(*tags...).Add(1)
connectionDurationSeconds.Observe(d.Seconds())
}
func (mt *metricsTracer) ConnectionRequestHandled(status pbv2.Status) {
tags := metricshelper.GetStringSlice()
defer metricshelper.PutStringSlice(tags)
respStatus := getResponseStatus(status)
*tags = append(*tags, respStatus)
connectionRequestResponseStatusTotal.WithLabelValues(*tags...).Add(1)
if respStatus == requestStatusRejected {
*tags = (*tags)[:0]
*tags = append(*tags, getRejectionReason(status))
connectionRejectionsTotal.WithLabelValues(*tags...).Add(1)
}
}
func (mt *metricsTracer) ReservationAllowed(isRenewal bool) {
tags := metricshelper.GetStringSlice()
defer metricshelper.PutStringSlice(tags)
if isRenewal {
*tags = append(*tags, "renewed")
} else {
*tags = append(*tags, "opened")
}
reservationsTotal.WithLabelValues(*tags...).Add(1)
}
func (mt *metricsTracer) ReservationClosed(cnt int) {
tags := metricshelper.GetStringSlice()
defer metricshelper.PutStringSlice(tags)
*tags = append(*tags, "closed")
reservationsTotal.WithLabelValues(*tags...).Add(float64(cnt))
}
func (mt *metricsTracer) ReservationRequestHandled(status pbv2.Status) {
tags := metricshelper.GetStringSlice()
defer metricshelper.PutStringSlice(tags)
respStatus := getResponseStatus(status)
*tags = append(*tags, respStatus)
reservationRequestResponseStatusTotal.WithLabelValues(*tags...).Add(1)
if respStatus == requestStatusRejected {
*tags = (*tags)[:0]
*tags = append(*tags, getRejectionReason(status))
reservationRejectionsTotal.WithLabelValues(*tags...).Add(1)
}
}
func (mt *metricsTracer) BytesTransferred(cnt int) {
dataTransferredBytesTotal.Add(float64(cnt))
}
func getResponseStatus(status pbv2.Status) string {
responseStatus := "unknown"
switch status {
case pbv2.Status_RESERVATION_REFUSED,
pbv2.Status_RESOURCE_LIMIT_EXCEEDED,
pbv2.Status_PERMISSION_DENIED,
pbv2.Status_NO_RESERVATION,
pbv2.Status_MALFORMED_MESSAGE:
responseStatus = requestStatusRejected
case pbv2.Status_UNEXPECTED_MESSAGE, pbv2.Status_CONNECTION_FAILED:
responseStatus = requestStatusError
case pbv2.Status_OK:
responseStatus = requestStatusOK
}
return responseStatus
}
func getRejectionReason(status pbv2.Status) string {
reason := "unknown"
switch status {
case pbv2.Status_RESERVATION_REFUSED:
reason = "ip constraint violation"
case pbv2.Status_RESOURCE_LIMIT_EXCEEDED:
reason = "resource limit exceeded"
case pbv2.Status_PERMISSION_DENIED:
reason = "permission denied"
case pbv2.Status_NO_RESERVATION:
reason = "no reservation"
case pbv2.Status_MALFORMED_MESSAGE:
reason = "malformed message"
}
return reason
}

View File

@@ -0,0 +1,43 @@
package relay
type Option func(*Relay) error
// WithResources is a Relay option that sets specific relay resources for the relay.
func WithResources(rc Resources) Option {
return func(r *Relay) error {
r.rc = rc
return nil
}
}
// WithLimit is a Relay option that sets only the relayed connection limits for the relay.
func WithLimit(limit *RelayLimit) Option {
return func(r *Relay) error {
r.rc.Limit = limit
return nil
}
}
// WithInfiniteLimits is a Relay option that disables limits.
func WithInfiniteLimits() Option {
return func(r *Relay) error {
r.rc.Limit = nil
return nil
}
}
// WithACL is a Relay option that supplies an ACLFilter for access control.
func WithACL(acl ACLFilter) Option {
return func(r *Relay) error {
r.acl = acl
return nil
}
}
// WithMetricsTracer is a Relay option that supplies a MetricsTracer for metrics
func WithMetricsTracer(mt MetricsTracer) Option {
return func(r *Relay) error {
r.metricsTracer = mt
return nil
}
}

View File

@@ -0,0 +1,687 @@
package relay
import (
"context"
"errors"
"fmt"
"io"
"sync"
"sync/atomic"
"time"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/record"
pbv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/util"
logging "github.com/ipfs/go-log/v2"
pool "github.com/libp2p/go-buffer-pool"
asnutil "github.com/libp2p/go-libp2p-asn-util"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)
const (
ServiceName = "libp2p.relay/v2"
ReservationTagWeight = 10
StreamTimeout = time.Minute
ConnectTimeout = 30 * time.Second
HandshakeTimeout = time.Minute
relayHopTag = "relay-v2-hop"
relayHopTagValue = 2
maxMessageSize = 4096
)
var log = logging.Logger("relay")
// Relay is the (limited) relay service object.
type Relay struct {
ctx context.Context
cancel func()
host host.Host
rc Resources
acl ACLFilter
constraints *constraints
scope network.ResourceScopeSpan
notifiee network.Notifiee
mx sync.Mutex
rsvp map[peer.ID]time.Time
conns map[peer.ID]int
closed bool
selfAddr ma.Multiaddr
metricsTracer MetricsTracer
}
// New constructs a new limited relay that can provide relay services in the given host.
func New(h host.Host, opts ...Option) (*Relay, error) {
ctx, cancel := context.WithCancel(context.Background())
r := &Relay{
ctx: ctx,
cancel: cancel,
host: h,
rc: DefaultResources(),
acl: nil,
rsvp: make(map[peer.ID]time.Time),
conns: make(map[peer.ID]int),
}
for _, opt := range opts {
err := opt(r)
if err != nil {
return nil, fmt.Errorf("error applying relay option: %w", err)
}
}
// get a scope for memory reservations at service level
err := h.Network().ResourceManager().ViewService(ServiceName,
func(s network.ServiceScope) error {
var err error
r.scope, err = s.BeginSpan()
return err
})
if err != nil {
return nil, err
}
r.constraints = newConstraints(&r.rc)
r.selfAddr = ma.StringCast(fmt.Sprintf("/p2p/%s", h.ID()))
h.SetStreamHandler(proto.ProtoIDv2Hop, r.handleStream)
r.notifiee = &network.NotifyBundle{DisconnectedF: r.disconnected}
h.Network().Notify(r.notifiee)
if r.metricsTracer != nil {
r.metricsTracer.RelayStatus(true)
}
go r.background()
return r, nil
}
func (r *Relay) Close() error {
r.mx.Lock()
if !r.closed {
r.closed = true
r.mx.Unlock()
r.host.RemoveStreamHandler(proto.ProtoIDv2Hop)
r.host.Network().StopNotify(r.notifiee)
r.scope.Done()
r.cancel()
r.gc()
if r.metricsTracer != nil {
r.metricsTracer.RelayStatus(false)
}
return nil
}
r.mx.Unlock()
return nil
}
func (r *Relay) handleStream(s network.Stream) {
log.Infof("new relay stream from: %s", s.Conn().RemotePeer())
if err := s.Scope().SetService(ServiceName); err != nil {
log.Debugf("error attaching stream to relay service: %s", err)
s.Reset()
return
}
if err := s.Scope().ReserveMemory(maxMessageSize, network.ReservationPriorityAlways); err != nil {
log.Debugf("error reserving memory for stream: %s", err)
s.Reset()
return
}
defer s.Scope().ReleaseMemory(maxMessageSize)
rd := util.NewDelimitedReader(s, maxMessageSize)
defer rd.Close()
s.SetReadDeadline(time.Now().Add(StreamTimeout))
var msg pbv2.HopMessage
err := rd.ReadMsg(&msg)
if err != nil {
r.handleError(s, pbv2.Status_MALFORMED_MESSAGE)
return
}
// reset stream deadline as message has been read
s.SetReadDeadline(time.Time{})
switch msg.GetType() {
case pbv2.HopMessage_RESERVE:
status := r.handleReserve(s)
if r.metricsTracer != nil {
r.metricsTracer.ReservationRequestHandled(status)
}
case pbv2.HopMessage_CONNECT:
status := r.handleConnect(s, &msg)
if r.metricsTracer != nil {
r.metricsTracer.ConnectionRequestHandled(status)
}
default:
r.handleError(s, pbv2.Status_MALFORMED_MESSAGE)
}
}
func (r *Relay) handleReserve(s network.Stream) pbv2.Status {
defer s.Close()
p := s.Conn().RemotePeer()
a := s.Conn().RemoteMultiaddr()
if isRelayAddr(a) {
log.Debugf("refusing relay reservation for %s; reservation attempt over relay connection")
r.handleError(s, pbv2.Status_PERMISSION_DENIED)
return pbv2.Status_PERMISSION_DENIED
}
if r.acl != nil && !r.acl.AllowReserve(p, a) {
log.Debugf("refusing relay reservation for %s; permission denied", p)
r.handleError(s, pbv2.Status_PERMISSION_DENIED)
return pbv2.Status_PERMISSION_DENIED
}
r.mx.Lock()
// Check if relay is still active. Otherwise ConnManager.UnTagPeer will not be called if this block runs after
// Close() call
if r.closed {
r.mx.Unlock()
log.Debugf("refusing relay reservation for %s; relay closed", p)
r.handleError(s, pbv2.Status_PERMISSION_DENIED)
return pbv2.Status_PERMISSION_DENIED
}
now := time.Now()
_, exists := r.rsvp[p]
if !exists {
if err := r.constraints.AddReservation(p, a); err != nil {
r.mx.Unlock()
log.Debugf("refusing relay reservation for %s; IP constraint violation: %s", p, err)
r.handleError(s, pbv2.Status_RESERVATION_REFUSED)
return pbv2.Status_RESERVATION_REFUSED
}
}
expire := now.Add(r.rc.ReservationTTL)
r.rsvp[p] = expire
r.host.ConnManager().TagPeer(p, "relay-reservation", ReservationTagWeight)
r.mx.Unlock()
if r.metricsTracer != nil {
r.metricsTracer.ReservationAllowed(exists)
}
log.Debugf("reserving relay slot for %s", p)
// Delivery of the reservation might fail for a number of reasons.
// For example, the stream might be reset or the connection might be closed before the reservation is received.
// In that case, the reservation will just be garbage collected later.
if err := r.writeResponse(s, pbv2.Status_OK, r.makeReservationMsg(p, expire), r.makeLimitMsg(p)); err != nil {
log.Debugf("error writing reservation response; retracting reservation for %s", p)
s.Reset()
return pbv2.Status_CONNECTION_FAILED
}
return pbv2.Status_OK
}
func (r *Relay) handleConnect(s network.Stream, msg *pbv2.HopMessage) pbv2.Status {
src := s.Conn().RemotePeer()
a := s.Conn().RemoteMultiaddr()
span, err := r.scope.BeginSpan()
if err != nil {
log.Debugf("failed to begin relay transaction: %s", err)
r.handleError(s, pbv2.Status_RESOURCE_LIMIT_EXCEEDED)
return pbv2.Status_RESOURCE_LIMIT_EXCEEDED
}
fail := func(status pbv2.Status) {
span.Done()
r.handleError(s, status)
}
// reserve buffers for the relay
if err := span.ReserveMemory(2*r.rc.BufferSize, network.ReservationPriorityHigh); err != nil {
log.Debugf("error reserving memory for relay: %s", err)
fail(pbv2.Status_RESOURCE_LIMIT_EXCEEDED)
return pbv2.Status_RESOURCE_LIMIT_EXCEEDED
}
if isRelayAddr(a) {
log.Debugf("refusing connection from %s; connection attempt over relay connection")
fail(pbv2.Status_PERMISSION_DENIED)
return pbv2.Status_PERMISSION_DENIED
}
dest, err := util.PeerToPeerInfoV2(msg.GetPeer())
if err != nil {
fail(pbv2.Status_MALFORMED_MESSAGE)
return pbv2.Status_MALFORMED_MESSAGE
}
if r.acl != nil && !r.acl.AllowConnect(src, s.Conn().RemoteMultiaddr(), dest.ID) {
log.Debugf("refusing connection from %s to %s; permission denied", src, dest.ID)
fail(pbv2.Status_PERMISSION_DENIED)
return pbv2.Status_PERMISSION_DENIED
}
r.mx.Lock()
_, rsvp := r.rsvp[dest.ID]
if !rsvp {
r.mx.Unlock()
log.Debugf("refusing connection from %s to %s; no reservation", src, dest.ID)
fail(pbv2.Status_NO_RESERVATION)
return pbv2.Status_NO_RESERVATION
}
srcConns := r.conns[src]
if srcConns >= r.rc.MaxCircuits {
r.mx.Unlock()
log.Debugf("refusing connection from %s to %s; too many connections from %s", src, dest.ID, src)
fail(pbv2.Status_RESOURCE_LIMIT_EXCEEDED)
return pbv2.Status_RESOURCE_LIMIT_EXCEEDED
}
destConns := r.conns[dest.ID]
if destConns >= r.rc.MaxCircuits {
r.mx.Unlock()
log.Debugf("refusing connection from %s to %s; too many connecitons to %s", src, dest.ID, dest.ID)
fail(pbv2.Status_RESOURCE_LIMIT_EXCEEDED)
return pbv2.Status_RESOURCE_LIMIT_EXCEEDED
}
r.addConn(src)
r.addConn(dest.ID)
r.mx.Unlock()
if r.metricsTracer != nil {
r.metricsTracer.ConnectionOpened()
}
connStTime := time.Now()
cleanup := func() {
span.Done()
r.mx.Lock()
r.rmConn(src)
r.rmConn(dest.ID)
r.mx.Unlock()
if r.metricsTracer != nil {
r.metricsTracer.ConnectionClosed(time.Since(connStTime))
}
}
ctx, cancel := context.WithTimeout(r.ctx, ConnectTimeout)
defer cancel()
ctx = network.WithNoDial(ctx, "relay connect")
bs, err := r.host.NewStream(ctx, dest.ID, proto.ProtoIDv2Stop)
if err != nil {
log.Debugf("error opening relay stream to %s: %s", dest.ID, err)
cleanup()
r.handleError(s, pbv2.Status_CONNECTION_FAILED)
return pbv2.Status_CONNECTION_FAILED
}
fail = func(status pbv2.Status) {
bs.Reset()
cleanup()
r.handleError(s, status)
}
if err := bs.Scope().SetService(ServiceName); err != nil {
log.Debugf("error attaching stream to relay service: %s", err)
fail(pbv2.Status_RESOURCE_LIMIT_EXCEEDED)
return pbv2.Status_RESOURCE_LIMIT_EXCEEDED
}
// handshake
if err := bs.Scope().ReserveMemory(maxMessageSize, network.ReservationPriorityAlways); err != nil {
log.Debugf("error reserving memory for stream: %s", err)
fail(pbv2.Status_RESOURCE_LIMIT_EXCEEDED)
return pbv2.Status_RESOURCE_LIMIT_EXCEEDED
}
defer bs.Scope().ReleaseMemory(maxMessageSize)
rd := util.NewDelimitedReader(bs, maxMessageSize)
wr := util.NewDelimitedWriter(bs)
defer rd.Close()
var stopmsg pbv2.StopMessage
stopmsg.Type = pbv2.StopMessage_CONNECT.Enum()
stopmsg.Peer = util.PeerInfoToPeerV2(peer.AddrInfo{ID: src})
stopmsg.Limit = r.makeLimitMsg(dest.ID)
bs.SetDeadline(time.Now().Add(HandshakeTimeout))
err = wr.WriteMsg(&stopmsg)
if err != nil {
log.Debugf("error writing stop handshake")
fail(pbv2.Status_CONNECTION_FAILED)
return pbv2.Status_CONNECTION_FAILED
}
stopmsg.Reset()
err = rd.ReadMsg(&stopmsg)
if err != nil {
log.Debugf("error reading stop response: %s", err.Error())
fail(pbv2.Status_CONNECTION_FAILED)
return pbv2.Status_CONNECTION_FAILED
}
if t := stopmsg.GetType(); t != pbv2.StopMessage_STATUS {
log.Debugf("unexpected stop response; not a status message (%d)", t)
fail(pbv2.Status_CONNECTION_FAILED)
return pbv2.Status_CONNECTION_FAILED
}
if status := stopmsg.GetStatus(); status != pbv2.Status_OK {
log.Debugf("relay stop failure: %d", status)
fail(pbv2.Status_CONNECTION_FAILED)
return pbv2.Status_CONNECTION_FAILED
}
var response pbv2.HopMessage
response.Type = pbv2.HopMessage_STATUS.Enum()
response.Status = pbv2.Status_OK.Enum()
response.Limit = r.makeLimitMsg(dest.ID)
wr = util.NewDelimitedWriter(s)
err = wr.WriteMsg(&response)
if err != nil {
log.Debugf("error writing relay response: %s", err)
bs.Reset()
s.Reset()
cleanup()
return pbv2.Status_CONNECTION_FAILED
}
// reset deadline
bs.SetDeadline(time.Time{})
log.Infof("relaying connection from %s to %s", src, dest.ID)
var goroutines atomic.Int32
goroutines.Store(2)
done := func() {
if goroutines.Add(-1) == 0 {
s.Close()
bs.Close()
cleanup()
}
}
if r.rc.Limit != nil {
deadline := time.Now().Add(r.rc.Limit.Duration)
s.SetDeadline(deadline)
bs.SetDeadline(deadline)
go r.relayLimited(s, bs, src, dest.ID, r.rc.Limit.Data, done)
go r.relayLimited(bs, s, dest.ID, src, r.rc.Limit.Data, done)
} else {
go r.relayUnlimited(s, bs, src, dest.ID, done)
go r.relayUnlimited(bs, s, dest.ID, src, done)
}
return pbv2.Status_OK
}
func (r *Relay) addConn(p peer.ID) {
conns := r.conns[p]
conns++
r.conns[p] = conns
if conns == 1 {
r.host.ConnManager().TagPeer(p, relayHopTag, relayHopTagValue)
}
}
func (r *Relay) rmConn(p peer.ID) {
conns := r.conns[p]
conns--
if conns > 0 {
r.conns[p] = conns
} else {
delete(r.conns, p)
r.host.ConnManager().UntagPeer(p, relayHopTag)
}
}
func (r *Relay) relayLimited(src, dest network.Stream, srcID, destID peer.ID, limit int64, done func()) {
defer done()
buf := pool.Get(r.rc.BufferSize)
defer pool.Put(buf)
limitedSrc := io.LimitReader(src, limit)
count, err := r.copyWithBuffer(dest, limitedSrc, buf)
if err != nil {
log.Debugf("relay copy error: %s", err)
// Reset both.
src.Reset()
dest.Reset()
} else {
// propagate the close
dest.CloseWrite()
if count == limit {
// we've reached the limit, discard further input
src.CloseRead()
}
}
log.Debugf("relayed %d bytes from %s to %s", count, srcID, destID)
}
func (r *Relay) relayUnlimited(src, dest network.Stream, srcID, destID peer.ID, done func()) {
defer done()
buf := pool.Get(r.rc.BufferSize)
defer pool.Put(buf)
count, err := r.copyWithBuffer(dest, src, buf)
if err != nil {
log.Debugf("relay copy error: %s", err)
// Reset both.
src.Reset()
dest.Reset()
} else {
// propagate the close
dest.CloseWrite()
}
log.Debugf("relayed %d bytes from %s to %s", count, srcID, destID)
}
// errInvalidWrite means that a write returned an impossible count.
// copied from io.errInvalidWrite
var errInvalidWrite = errors.New("invalid write result")
// copyWithBuffer copies from src to dst using the provided buf until either EOF is reached
// on src or an error occurs. It reports the number of bytes transferred to metricsTracer.
// The implementation is a modified form of io.CopyBuffer to support metrics tracking.
func (r *Relay) copyWithBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) {
for {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
ew = errInvalidWrite
}
}
written += int64(nw)
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
if r.metricsTracer != nil {
r.metricsTracer.BytesTransferred(nw)
}
}
if er != nil {
if er != io.EOF {
err = er
}
break
}
}
return written, err
}
func (r *Relay) handleError(s network.Stream, status pbv2.Status) {
log.Debugf("relay error: %s (%d)", pbv2.Status_name[int32(status)], status)
err := r.writeResponse(s, status, nil, nil)
if err != nil {
s.Reset()
log.Debugf("error writing relay response: %s", err.Error())
} else {
s.Close()
}
}
func (r *Relay) writeResponse(s network.Stream, status pbv2.Status, rsvp *pbv2.Reservation, limit *pbv2.Limit) error {
wr := util.NewDelimitedWriter(s)
var msg pbv2.HopMessage
msg.Type = pbv2.HopMessage_STATUS.Enum()
msg.Status = status.Enum()
msg.Reservation = rsvp
msg.Limit = limit
return wr.WriteMsg(&msg)
}
func (r *Relay) makeReservationMsg(p peer.ID, expire time.Time) *pbv2.Reservation {
expireUnix := uint64(expire.Unix())
var addrBytes [][]byte
for _, addr := range r.host.Addrs() {
if !manet.IsPublicAddr(addr) {
continue
}
addr = addr.Encapsulate(r.selfAddr)
addrBytes = append(addrBytes, addr.Bytes())
}
rsvp := &pbv2.Reservation{
Expire: &expireUnix,
Addrs: addrBytes,
}
voucher := &proto.ReservationVoucher{
Relay: r.host.ID(),
Peer: p,
Expiration: expire,
}
envelope, err := record.Seal(voucher, r.host.Peerstore().PrivKey(r.host.ID()))
if err != nil {
log.Errorf("error sealing voucher for %s: %s", p, err)
return rsvp
}
blob, err := envelope.Marshal()
if err != nil {
log.Errorf("error marshalling voucher for %s: %s", p, err)
return rsvp
}
rsvp.Voucher = blob
return rsvp
}
func (r *Relay) makeLimitMsg(p peer.ID) *pbv2.Limit {
if r.rc.Limit == nil {
return nil
}
duration := uint32(r.rc.Limit.Duration / time.Second)
data := uint64(r.rc.Limit.Data)
return &pbv2.Limit{
Duration: &duration,
Data: &data,
}
}
func (r *Relay) background() {
asnutil.Store.Init()
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
r.gc()
case <-r.ctx.Done():
return
}
}
}
func (r *Relay) gc() {
r.mx.Lock()
defer r.mx.Unlock()
now := time.Now()
cnt := 0
for p, expire := range r.rsvp {
if r.closed || expire.Before(now) {
delete(r.rsvp, p)
r.host.ConnManager().UntagPeer(p, "relay-reservation")
cnt++
}
}
if r.metricsTracer != nil {
r.metricsTracer.ReservationClosed(cnt)
}
for p, count := range r.conns {
if count == 0 {
delete(r.conns, p)
}
}
}
func (r *Relay) disconnected(n network.Network, c network.Conn) {
p := c.RemotePeer()
if n.Connectedness(p) == network.Connected {
return
}
r.mx.Lock()
_, ok := r.rsvp[p]
if ok {
delete(r.rsvp, p)
}
r.mx.Unlock()
if ok && r.metricsTracer != nil {
r.metricsTracer.ReservationClosed(1)
}
}
func isRelayAddr(a ma.Multiaddr) bool {
_, err := a.ValueForProtocol(ma.P_CIRCUIT)
return err == nil
}

View File

@@ -0,0 +1,66 @@
package relay
import (
"time"
)
// Resources are the resource limits associated with the relay service.
type Resources struct {
// Limit is the (optional) relayed connection limits.
Limit *RelayLimit
// ReservationTTL is the duration of a new (or refreshed reservation).
// Defaults to 1hr.
ReservationTTL time.Duration
// MaxReservations is the maximum number of active relay slots; defaults to 128.
MaxReservations int
// MaxCircuits is the maximum number of open relay connections for each peer; defaults to 16.
MaxCircuits int
// BufferSize is the size of the relayed connection buffers; defaults to 2048.
BufferSize int
// MaxReservationsPerPeer is the maximum number of reservations originating from the same
// peer; default is 4.
MaxReservationsPerPeer int
// MaxReservationsPerIP is the maximum number of reservations originating from the same
// IP address; default is 8.
MaxReservationsPerIP int
// MaxReservationsPerASN is the maximum number of reservations origination from the same
// ASN; default is 32
MaxReservationsPerASN int
}
// RelayLimit are the per relayed connection resource limits.
type RelayLimit struct {
// Duration is the time limit before resetting a relayed connection; defaults to 2min.
Duration time.Duration
// Data is the limit of data relayed (on each direction) before resetting the connection.
// Defaults to 128KB
Data int64
}
// DefaultResources returns a Resources object with the default filled in.
func DefaultResources() Resources {
return Resources{
Limit: DefaultLimit(),
ReservationTTL: time.Hour,
MaxReservations: 128,
MaxCircuits: 16,
BufferSize: 2048,
MaxReservationsPerPeer: 4,
MaxReservationsPerIP: 8,
MaxReservationsPerASN: 32,
}
}
// DefaultLimit returns a RelayLimit object with the defaults filled in.
func DefaultLimit() *RelayLimit {
return &RelayLimit{
Duration: 2 * time.Minute,
Data: 1 << 17, // 128K
}
}

View File

@@ -0,0 +1,66 @@
package util
import (
"errors"
"io"
pool "github.com/libp2p/go-buffer-pool"
"github.com/libp2p/go-msgio/pbio"
"github.com/multiformats/go-varint"
"google.golang.org/protobuf/proto"
)
type DelimitedReader struct {
r io.Reader
buf []byte
}
// The gogo protobuf NewDelimitedReader is buffered, which may eat up stream data.
// So we need to implement a compatible delimited reader that reads unbuffered.
// There is a slowdown from unbuffered reading: when reading the message
// it can take multiple single byte Reads to read the length and another Read
// to read the message payload.
// However, this is not critical performance degradation as
// - the reader is utilized to read one (dialer, stop) or two messages (hop) during
// the handshake, so it's a drop in the water for the connection lifetime.
// - messages are small (max 4k) and the length fits in a couple of bytes,
// so overall we have at most three reads per message.
func NewDelimitedReader(r io.Reader, maxSize int) *DelimitedReader {
return &DelimitedReader{r: r, buf: pool.Get(maxSize)}
}
func (d *DelimitedReader) Close() {
if d.buf != nil {
pool.Put(d.buf)
d.buf = nil
}
}
func (d *DelimitedReader) ReadByte() (byte, error) {
buf := d.buf[:1]
_, err := d.r.Read(buf)
return buf[0], err
}
func (d *DelimitedReader) ReadMsg(msg proto.Message) error {
mlen, err := varint.ReadUvarint(d)
if err != nil {
return err
}
if uint64(len(d.buf)) < mlen {
return errors.New("message too large")
}
buf := d.buf[:mlen]
_, err = io.ReadFull(d.r, buf)
if err != nil {
return err
}
return proto.Unmarshal(buf, msg)
}
func NewDelimitedWriter(w io.Writer) pbio.WriteCloser {
return pbio.NewDelimitedWriter(w)
}

View File

@@ -0,0 +1,44 @@
package util
import (
"errors"
"github.com/libp2p/go-libp2p/core/peer"
pbv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb"
ma "github.com/multiformats/go-multiaddr"
)
func PeerToPeerInfoV2(p *pbv2.Peer) (peer.AddrInfo, error) {
if p == nil {
return peer.AddrInfo{}, errors.New("nil peer")
}
id, err := peer.IDFromBytes(p.Id)
if err != nil {
return peer.AddrInfo{}, err
}
addrs := make([]ma.Multiaddr, 0, len(p.Addrs))
for _, addrBytes := range p.Addrs {
a, err := ma.NewMultiaddrBytes(addrBytes)
if err == nil {
addrs = append(addrs, a)
}
}
return peer.AddrInfo{ID: id, Addrs: addrs}, nil
}
func PeerInfoToPeerV2(pi peer.AddrInfo) *pbv2.Peer {
addrs := make([][]byte, 0, len(pi.Addrs))
for _, addr := range pi.Addrs {
addrs = append(addrs, addr.Bytes())
}
return &pbv2.Peer{
Id: []byte(pi.ID),
Addrs: addrs,
}
}